aboutsummaryrefslogtreecommitdiffstats
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/9pfs/Makefile.objs9
-rw-r--r--hw/9pfs/codir.c168
-rw-r--r--hw/9pfs/cofile.c275
-rw-r--r--hw/9pfs/cofs.c364
-rw-r--r--hw/9pfs/coxattr.c107
-rw-r--r--hw/9pfs/virtio-9p-coth.c82
-rw-r--r--hw/9pfs/virtio-9p-coth.h107
-rw-r--r--hw/9pfs/virtio-9p-device.c173
-rw-r--r--hw/9pfs/virtio-9p-handle.c708
-rw-r--r--hw/9pfs/virtio-9p-local.c1280
-rw-r--r--hw/9pfs/virtio-9p-posix-acl.c185
-rw-r--r--hw/9pfs/virtio-9p-proxy.c1219
-rw-r--r--hw/9pfs/virtio-9p-proxy.h95
-rw-r--r--hw/9pfs/virtio-9p-synth.c573
-rw-r--r--hw/9pfs/virtio-9p-synth.h54
-rw-r--r--hw/9pfs/virtio-9p-xattr-user.c128
-rw-r--r--hw/9pfs/virtio-9p-xattr.c164
-rw-r--r--hw/9pfs/virtio-9p-xattr.h120
-rw-r--r--hw/9pfs/virtio-9p.c3300
-rw-r--r--hw/9pfs/virtio-9p.h394
-rw-r--r--hw/Makefile.objs36
-rw-r--r--hw/acpi/Makefile.objs7
-rw-r--r--hw/acpi/acpi_interface.c15
-rw-r--r--hw/acpi/aml-build.c1217
-rw-r--r--hw/acpi/bios-linker-loader.c159
-rw-r--r--hw/acpi/core.c710
-rw-r--r--hw/acpi/cpu_hotplug.c76
-rw-r--r--hw/acpi/ich9.c484
-rw-r--r--hw/acpi/memory_hotplug.c304
-rw-r--r--hw/acpi/pcihp.c334
-rw-r--r--hw/acpi/piix4.c643
-rw-r--r--hw/acpi/tco.c264
-rw-r--r--hw/alpha/Makefile.objs1
-rw-r--r--hw/alpha/alpha_sys.h21
-rw-r--r--hw/alpha/dp264.c184
-rw-r--r--hw/alpha/pci.c91
-rw-r--r--hw/alpha/typhoon.c956
-rw-r--r--hw/arm/Makefile.objs15
-rw-r--r--hw/arm/allwinner-a10.c127
-rw-r--r--hw/arm/armv7m.c266
-rw-r--r--hw/arm/boot.c805
-rw-r--r--hw/arm/collie.c72
-rw-r--r--hw/arm/cubieboard.c89
-rw-r--r--hw/arm/digic.c121
-rw-r--r--hw/arm/digic_boards.c162
-rw-r--r--hw/arm/exynos4210.c383
-rw-r--r--hw/arm/exynos4_boards.c169
-rw-r--r--hw/arm/gumstix.c142
-rw-r--r--hw/arm/highbank.c416
-rw-r--r--hw/arm/integratorcp.c684
-rw-r--r--hw/arm/kzm.c153
-rw-r--r--hw/arm/mainstone.c202
-rw-r--r--hw/arm/musicpal.c1752
-rw-r--r--hw/arm/netduino2.c57
-rw-r--r--hw/arm/nseries.c1436
-rw-r--r--hw/arm/omap1.c4089
-rw-r--r--hw/arm/omap2.c2692
-rw-r--r--hw/arm/omap_sx1.c238
-rw-r--r--hw/arm/palm.c283
-rw-r--r--hw/arm/pxa2xx.c2354
-rw-r--r--hw/arm/pxa2xx_gpio.c356
-rw-r--r--hw/arm/pxa2xx_pic.c337
-rw-r--r--hw/arm/realview.c439
-rw-r--r--hw/arm/spitz.c1149
-rw-r--r--hw/arm/stellaris.c1438
-rw-r--r--hw/arm/stm32f205_soc.c160
-rw-r--r--hw/arm/strongarm.c1659
-rw-r--r--hw/arm/strongarm.h68
-rw-r--r--hw/arm/sysbus-fdt.c247
-rw-r--r--hw/arm/tosa.c306
-rw-r--r--hw/arm/versatilepb.c437
-rw-r--r--hw/arm/vexpress.c808
-rw-r--r--hw/arm/virt-acpi-build.c697
-rw-r--r--hw/arm/virt.c977
-rw-r--r--hw/arm/xilinx_zynq.c272
-rw-r--r--hw/arm/xlnx-ep108.c82
-rw-r--r--hw/arm/xlnx-zynqmp.c272
-rw-r--r--hw/arm/z2.c386
-rw-r--r--hw/audio/Makefile.objs18
-rw-r--r--hw/audio/ac97.c1430
-rw-r--r--hw/audio/adlib.c389
-rw-r--r--hw/audio/cs4231.c186
-rw-r--r--hw/audio/cs4231a.c707
-rw-r--r--hw/audio/es1370.c1080
-rw-r--r--hw/audio/fmopl.c1394
-rw-r--r--hw/audio/fmopl.h174
-rw-r--r--hw/audio/gus.c318
-rw-r--r--hw/audio/gusemu.h105
-rw-r--r--hw/audio/gusemu_hal.c554
-rw-r--r--hw/audio/gusemu_mixer.c240
-rw-r--r--hw/audio/gustate.h132
-rw-r--r--hw/audio/hda-codec-common.h456
-rw-r--r--hw/audio/hda-codec.c731
-rw-r--r--hw/audio/intel-hda-defs.h717
-rw-r--r--hw/audio/intel-hda.c1343
-rw-r--r--hw/audio/intel-hda.h72
-rw-r--r--hw/audio/lm4549.c335
-rw-r--r--hw/audio/lm4549.h43
-rw-r--r--hw/audio/marvell_88w8618.c306
-rw-r--r--hw/audio/milkymist-ac97.c348
-rw-r--r--hw/audio/pcspk.c213
-rw-r--r--hw/audio/pl041.c649
-rw-r--r--hw/audio/pl041.h135
-rw-r--r--hw/audio/pl041.hx81
-rw-r--r--hw/audio/sb16.c1427
-rw-r--r--hw/audio/wm8750.c722
-rw-r--r--hw/block/Makefile.objs15
-rw-r--r--hw/block/block.c91
-rw-r--r--hw/block/cdrom.c155
-rw-r--r--hw/block/dataplane/Makefile.objs1
-rw-r--r--hw/block/dataplane/virtio-blk.c341
-rw-r--r--hw/block/dataplane/virtio-blk.h30
-rw-r--r--hw/block/ecc.c90
-rw-r--r--hw/block/fdc.c2529
-rw-r--r--hw/block/hd-geometry.c165
-rw-r--r--hw/block/m25p80.c711
-rw-r--r--hw/block/nand.c799
-rw-r--r--hw/block/nvme.c967
-rw-r--r--hw/block/nvme.h712
-rw-r--r--hw/block/onenand.c848
-rw-r--r--hw/block/pflash_cfi01.c954
-rw-r--r--hw/block/pflash_cfi02.c795
-rw-r--r--hw/block/tc58128.c180
-rw-r--r--hw/block/virtio-blk.c1018
-rw-r--r--hw/block/xen_blkif.h115
-rw-r--r--hw/block/xen_disk.c1106
-rw-r--r--hw/bt/Makefile.objs3
-rw-r--r--hw/bt/core.c144
-rw-r--r--hw/bt/hci-csr.c454
-rw-r--r--hw/bt/hci.c2265
-rw-r--r--hw/bt/hid.c553
-rw-r--r--hw/bt/l2cap.c1365
-rw-r--r--hw/bt/sdp.c967
-rw-r--r--hw/char/Makefile.objs29
-rw-r--r--hw/char/cadence_uart.c536
-rw-r--r--hw/char/debugcon.c140
-rw-r--r--hw/char/digic-uart.c197
-rw-r--r--hw/char/escc.c1052
-rw-r--r--hw/char/etraxfs_ser.c255
-rw-r--r--hw/char/exynos4210_uart.c676
-rw-r--r--hw/char/grlib_apbuart.c298
-rw-r--r--hw/char/imx_serial.c472
-rw-r--r--hw/char/ipoctal232.c603
-rw-r--r--hw/char/lm32_juart.c164
-rw-r--r--hw/char/lm32_uart.c304
-rw-r--r--hw/char/mcf_uart.c307
-rw-r--r--hw/char/milkymist-uart.c255
-rw-r--r--hw/char/omap_uart.c188
-rw-r--r--hw/char/parallel.c643
-rw-r--r--hw/char/pl011.c342
-rw-r--r--hw/char/sclpconsole-lm.c383
-rw-r--r--hw/char/sclpconsole.c285
-rw-r--r--hw/char/serial-isa.c145
-rw-r--r--hw/char/serial-pci.c274
-rw-r--r--hw/char/serial.c972
-rw-r--r--hw/char/sh_serial.c408
-rw-r--r--hw/char/spapr_vty.c245
-rw-r--r--hw/char/stm32f2xx_usart.c232
-rw-r--r--hw/char/virtio-console.c217
-rw-r--r--hw/char/virtio-serial-bus.c1134
-rw-r--r--hw/char/xen_console.c305
-rw-r--r--hw/char/xilinx_uartlite.c248
-rw-r--r--hw/core/Makefile.objs17
-rw-r--r--hw/core/empty_slot.c102
-rw-r--r--hw/core/fw-path-provider.c52
-rw-r--r--hw/core/hotplug.c59
-rw-r--r--hw/core/irq.c158
-rw-r--r--hw/core/loader.c1089
-rw-r--r--hw/core/machine.c485
-rw-r--r--hw/core/nmi.c104
-rw-r--r--hw/core/null-machine.c35
-rw-r--r--hw/core/platform-bus.c252
-rw-r--r--hw/core/ptimer.c230
-rw-r--r--hw/core/qdev-properties-system.c425
-rw-r--r--hw/core/qdev-properties.c1113
-rw-r--r--hw/core/qdev.c1349
-rw-r--r--hw/core/stream.c32
-rw-r--r--hw/core/sysbus.c369
-rw-r--r--hw/core/uboot_image.h158
-rw-r--r--hw/cpu/Makefile.objs6
-rw-r--r--hw/cpu/a15mpcore.c140
-rw-r--r--hw/cpu/a9mpcore.c179
-rw-r--r--hw/cpu/arm11mpcore.c172
-rw-r--r--hw/cpu/icc_bus.c118
-rw-r--r--hw/cpu/realview_mpcore.c139
-rw-r--r--hw/cris/Makefile.objs2
-rw-r--r--hw/cris/axis_dev88.c366
-rw-r--r--hw/cris/boot.c98
-rw-r--r--hw/cris/boot.h15
-rw-r--r--hw/display/Makefile.objs40
-rw-r--r--hw/display/ads7846.c177
-rw-r--r--hw/display/blizzard.c986
-rw-r--r--hw/display/blizzard_template.h146
-rw-r--r--hw/display/cg3.c397
-rw-r--r--hw/display/cirrus_vga.c3089
-rw-r--r--hw/display/cirrus_vga_rop.h207
-rw-r--r--hw/display/cirrus_vga_rop2.h281
-rw-r--r--hw/display/exynos4210_fimd.c1953
-rw-r--r--hw/display/framebuffer.c124
-rw-r--r--hw/display/framebuffer.h65
-rw-r--r--hw/display/g364fb.c558
-rw-r--r--hw/display/jazz_led.c311
-rw-r--r--hw/display/milkymist-tmu2.c494
-rw-r--r--hw/display/milkymist-vgafb.c353
-rw-r--r--hw/display/milkymist-vgafb_template.h74
-rw-r--r--hw/display/omap_dss.c1091
-rw-r--r--hw/display/omap_lcd_template.h175
-rw-r--r--hw/display/omap_lcdc.c420
-rw-r--r--hw/display/pl110.c538
-rw-r--r--hw/display/pl110_template.h399
-rw-r--r--hw/display/pxa2xx_lcd.c1064
-rw-r--r--hw/display/pxa2xx_template.h447
-rw-r--r--hw/display/qxl-logger.c275
-rw-r--r--hw/display/qxl-render.c297
-rw-r--r--hw/display/qxl.c2357
-rw-r--r--hw/display/qxl.h174
-rw-r--r--hw/display/sm501.c1455
-rw-r--r--hw/display/sm501_template.h145
-rw-r--r--hw/display/ssd0303.c330
-rw-r--r--hw/display/ssd0323.c401
-rw-r--r--hw/display/tc6393xb.c596
-rw-r--r--hw/display/tc6393xb_template.h72
-rw-r--r--hw/display/tcx.c1105
-rw-r--r--hw/display/vga-helpers.h439
-rw-r--r--hw/display/vga-isa-mm.c142
-rw-r--r--hw/display/vga-isa.c105
-rw-r--r--hw/display/vga-pci.c386
-rw-r--r--hw/display/vga.c2252
-rw-r--r--hw/display/vga.h159
-rw-r--r--hw/display/vga_int.h228
-rw-r--r--hw/display/virtio-gpu-pci.c76
-rw-r--r--hw/display/virtio-gpu.c919
-rw-r--r--hw/display/virtio-vga.c182
-rw-r--r--hw/display/vmware_vga.c1365
-rw-r--r--hw/display/xenfb.c1014
-rw-r--r--hw/dma/Makefile.objs13
-rw-r--r--hw/dma/etraxfs_dma.c781
-rw-r--r--hw/dma/i82374.c178
-rw-r--r--hw/dma/i8257.c598
-rw-r--r--hw/dma/omap_dma.c2104
-rw-r--r--hw/dma/pl080.c422
-rw-r--r--hw/dma/pl330.c1666
-rw-r--r--hw/dma/puv3_dma.c113
-rw-r--r--hw/dma/pxa2xx_dma.c576
-rw-r--r--hw/dma/rc4030.c841
-rw-r--r--hw/dma/soc_dma.c366
-rw-r--r--hw/dma/sparc32_dma.c322
-rw-r--r--hw/dma/sun4m_iommu.c392
-rw-r--r--hw/dma/xilinx_axidma.c674
-rw-r--r--hw/gpio/Makefile.objs7
-rw-r--r--hw/gpio/max7310.c217
-rw-r--r--hw/gpio/mpc8xxx.c217
-rw-r--r--hw/gpio/omap_gpio.c808
-rw-r--r--hw/gpio/pl061.c367
-rw-r--r--hw/gpio/puv3_gpio.c145
-rw-r--r--hw/gpio/zaurus.c306
-rw-r--r--hw/i2c/Makefile.objs7
-rw-r--r--hw/i2c/bitbang_i2c.c252
-rw-r--r--hw/i2c/bitbang_i2c.h14
-rw-r--r--hw/i2c/core.c245
-rw-r--r--hw/i2c/exynos4210_i2c.c336
-rw-r--r--hw/i2c/omap_i2c.c504
-rw-r--r--hw/i2c/pm_smbus.c227
-rw-r--r--hw/i2c/smbus.c361
-rw-r--r--hw/i2c/smbus_eeprom.c158
-rw-r--r--hw/i2c/smbus_ich9.c129
-rw-r--r--hw/i2c/versatile_i2c.c113
-rw-r--r--hw/i386/Makefile.objs33
-rw-r--r--hw/i386/acpi-build.c1939
-rw-r--r--hw/i386/acpi-build.h9
-rw-r--r--hw/i386/acpi-dsdt-cpu-hotplug.dsl90
-rw-r--r--hw/i386/acpi-dsdt-dbug.dsl41
-rw-r--r--hw/i386/acpi-dsdt-hpet.dsl48
-rw-r--r--hw/i386/acpi-dsdt-isa.dsl117
-rw-r--r--hw/i386/acpi-dsdt-mem-hotplug.dsl171
-rw-r--r--hw/i386/acpi-dsdt.dsl304
-rw-r--r--hw/i386/acpi-dsdt.hex.generated2972
-rw-r--r--hw/i386/intel_iommu.c1967
-rw-r--r--hw/i386/intel_iommu_internal.h389
-rw-r--r--hw/i386/kvm/Makefile.objs1
-rw-r--r--hw/i386/kvm/apic.c218
-rw-r--r--hw/i386/kvm/clock.c209
-rw-r--r--hw/i386/kvm/i8254.c335
-rw-r--r--hw/i386/kvm/i8259.c162
-rw-r--r--hw/i386/kvm/ioapic.c168
-rw-r--r--hw/i386/kvm/pci-assign.c1968
-rw-r--r--hw/i386/kvmvapic.c865
-rw-r--r--hw/i386/multiboot.c371
-rw-r--r--hw/i386/multiboot.h14
-rw-r--r--hw/i386/pc.c1965
-rw-r--r--hw/i386/pc_piix.c933
-rw-r--r--hw/i386/pc_q35.c492
-rw-r--r--hw/i386/pc_sysfw.c251
-rw-r--r--hw/i386/q35-acpi-dsdt.dsl435
-rw-r--r--hw/i386/q35-acpi-dsdt.hex.generated7610
-rw-r--r--hw/i386/smbios.c1102
-rw-r--r--hw/i386/xen/Makefile.objs1
-rw-r--r--hw/i386/xen/xen_apic.c98
-rw-r--r--hw/i386/xen/xen_platform.c450
-rw-r--r--hw/i386/xen/xen_pvdevice.c135
-rw-r--r--hw/ide/Makefile.objs12
-rw-r--r--hw/ide/ahci.c1702
-rw-r--r--hw/ide/ahci.h372
-rw-r--r--hw/ide/atapi.c1261
-rw-r--r--hw/ide/cmd646.c433
-rw-r--r--hw/ide/core.c2723
-rw-r--r--hw/ide/ich.c182
-rw-r--r--hw/ide/internal.h584
-rw-r--r--hw/ide/isa.c134
-rw-r--r--hw/ide/macio.c627
-rw-r--r--hw/ide/microdrive.c637
-rw-r--r--hw/ide/mmio.c183
-rw-r--r--hw/ide/pci.c485
-rw-r--r--hw/ide/pci.h76
-rw-r--r--hw/ide/piix.c305
-rw-r--r--hw/ide/qdev.c367
-rw-r--r--hw/ide/via.c235
-rw-r--r--hw/input/Makefile.objs19
-rw-r--r--hw/input/adb.c593
-rw-r--r--hw/input/hid.c611
-rw-r--r--hw/input/lm832x.c524
-rw-r--r--hw/input/milkymist-softusb.c317
-rw-r--r--hw/input/pckbd.c594
-rw-r--r--hw/input/pl050.c205
-rw-r--r--hw/input/ps2.c807
-rw-r--r--hw/input/pxa2xx_keypad.c334
-rw-r--r--hw/input/stellaris_input.c87
-rw-r--r--hw/input/tsc2005.c593
-rw-r--r--hw/input/tsc210x.c1275
-rw-r--r--hw/input/virtio-input-hid.c514
-rw-r--r--hw/input/virtio-input-host.c189
-rw-r--r--hw/input/virtio-input.c293
-rw-r--r--hw/input/vmmouse.c303
-rw-r--r--hw/intc/Makefile.objs30
-rw-r--r--hw/intc/allwinner-a10-pic.c212
-rw-r--r--hw/intc/apic.c921
-rw-r--r--hw/intc/apic_common.c456
-rw-r--r--hw/intc/arm_gic.c1160
-rw-r--r--hw/intc/arm_gic_common.c204
-rw-r--r--hw/intc/arm_gic_kvm.c663
-rw-r--r--hw/intc/arm_gicv2m.c192
-rw-r--r--hw/intc/armv7m_nvic.c572
-rw-r--r--hw/intc/etraxfs_pic.c193
-rw-r--r--hw/intc/exynos4210_combiner.c458
-rw-r--r--hw/intc/exynos4210_gic.c471
-rw-r--r--hw/intc/gic_internal.h103
-rw-r--r--hw/intc/grlib_irqmp.c374
-rw-r--r--hw/intc/heathrow_pic.c213
-rw-r--r--hw/intc/i8259.c523
-rw-r--r--hw/intc/i8259_common.c162
-rw-r--r--hw/intc/imx_avic.c406
-rw-r--r--hw/intc/ioapic.c261
-rw-r--r--hw/intc/ioapic_common.c123
-rw-r--r--hw/intc/lm32_pic.c203
-rw-r--r--hw/intc/omap_intc.c667
-rw-r--r--hw/intc/openpic.c1661
-rw-r--r--hw/intc/openpic_kvm.c293
-rw-r--r--hw/intc/pl190.c292
-rw-r--r--hw/intc/puv3_intc.c140
-rw-r--r--hw/intc/realview_gic.c87
-rw-r--r--hw/intc/s390_flic.c99
-rw-r--r--hw/intc/s390_flic_kvm.c432
-rw-r--r--hw/intc/sh_intc.c512
-rw-r--r--hw/intc/slavio_intctl.c471
-rw-r--r--hw/intc/xics.c1084
-rw-r--r--hw/intc/xics_kvm.c508
-rw-r--r--hw/intc/xilinx_intc.c201
-rw-r--r--hw/ipack/Makefile.objs2
-rw-r--r--hw/ipack/ipack.c119
-rw-r--r--hw/ipack/tpci200.c656
-rw-r--r--hw/isa/Makefile.objs8
-rw-r--r--hw/isa/apm.c102
-rw-r--r--hw/isa/i82378.c145
-rw-r--r--hw/isa/isa-bus.c298
-rw-r--r--hw/isa/lpc_ich9.c747
-rw-r--r--hw/isa/pc87312.c402
-rw-r--r--hw/isa/piix4.c139
-rw-r--r--hw/isa/vt82c686.c511
-rw-r--r--hw/lm32/Makefile.objs3
-rw-r--r--hw/lm32/lm32.h29
-rw-r--r--hw/lm32/lm32_boards.c315
-rw-r--r--hw/lm32/lm32_hwsetup.h179
-rw-r--r--hw/lm32/milkymist-hw.h207
-rw-r--r--hw/lm32/milkymist.c224
-rw-r--r--hw/m68k/Makefile.objs4
-rw-r--r--hw/m68k/an5206.c103
-rw-r--r--hw/m68k/dummy_m68k.c86
-rw-r--r--hw/m68k/mcf5206.c548
-rw-r--r--hw/m68k/mcf5208.c309
-rw-r--r--hw/m68k/mcf_intc.c168
-rw-r--r--hw/mem/Makefile.objs1
-rw-r--r--hw/mem/pc-dimm.c456
-rw-r--r--hw/microblaze/Makefile.objs3
-rw-r--r--hw/microblaze/boot.c211
-rw-r--r--hw/microblaze/boot.h12
-rw-r--r--hw/microblaze/petalogix_ml605_mmu.c221
-rw-r--r--hw/microblaze/petalogix_s3adsp1800_mmu.c139
-rw-r--r--hw/mips/Makefile.objs5
-rw-r--r--hw/mips/addr.c39
-rw-r--r--hw/mips/cputimer.c157
-rw-r--r--hw/mips/gt64xxx_pci.c1259
-rw-r--r--hw/mips/mips_fulong2e.c406
-rw-r--r--hw/mips/mips_int.c78
-rw-r--r--hw/mips/mips_jazz.c383
-rw-r--r--hw/mips/mips_malta.c1238
-rw-r--r--hw/mips/mips_mipssim.c245
-rw-r--r--hw/mips/mips_r4k.c314
-rw-r--r--hw/misc/Makefile.objs42
-rw-r--r--hw/misc/a9scu.c152
-rw-r--r--hw/misc/applesmc.c279
-rw-r--r--hw/misc/arm11scu.c100
-rw-r--r--hw/misc/arm_integrator_debug.c99
-rw-r--r--hw/misc/arm_l2x0.c198
-rw-r--r--hw/misc/arm_sysctl.c656
-rw-r--r--hw/misc/cbus.c618
-rw-r--r--hw/misc/debugexit.c76
-rw-r--r--hw/misc/eccmemctl.c344
-rw-r--r--hw/misc/edu.c408
-rw-r--r--hw/misc/exynos4210_pmu.c502
-rw-r--r--hw/misc/imx_ccm.c326
-rw-r--r--hw/misc/ivshmem.c891
-rw-r--r--hw/misc/macio/Makefile.objs3
-rw-r--r--hw/misc/macio/cuda.c756
-rw-r--r--hw/misc/macio/mac_dbdma.c766
-rw-r--r--hw/misc/macio/macio.c446
-rw-r--r--hw/misc/max111x.c214
-rw-r--r--hw/misc/milkymist-hpdmc.c174
-rw-r--r--hw/misc/milkymist-pfpu.c548
-rw-r--r--hw/misc/mst_fpga.c266
-rw-r--r--hw/misc/omap_clk.c1264
-rw-r--r--hw/misc/omap_gpmc.c897
-rw-r--r--hw/misc/omap_l4.c163
-rw-r--r--hw/misc/omap_sdrc.c169
-rw-r--r--hw/misc/omap_tap.c117
-rw-r--r--hw/misc/pc-testdev.c207
-rw-r--r--hw/misc/pci-testdev.c330
-rw-r--r--hw/misc/puv3_pm.c153
-rw-r--r--hw/misc/pvpanic.c144
-rw-r--r--hw/misc/sga.c67
-rw-r--r--hw/misc/slavio_misc.c517
-rw-r--r--hw/misc/stm32f2xx_syscfg.c160
-rw-r--r--hw/misc/tmp105.c272
-rw-r--r--hw/misc/tmp105.h47
-rw-r--r--hw/misc/vmport.c181
-rw-r--r--hw/misc/zynq_slcr.c458
-rw-r--r--hw/moxie/Makefile.objs2
-rw-r--r--hw/moxie/moxiesim.c161
-rw-r--r--hw/net/Makefile.objs42
-rw-r--r--hw/net/allwinner_emac.c533
-rw-r--r--hw/net/cadence_gem.c1248
-rw-r--r--hw/net/dp8393x.c910
-rw-r--r--hw/net/e1000.c1694
-rw-r--r--hw/net/e1000_regs.h902
-rw-r--r--hw/net/eepro100.c2114
-rw-r--r--hw/net/etraxfs_eth.c646
-rw-r--r--hw/net/fsl_etsec/etsec.c456
-rw-r--r--hw/net/fsl_etsec/etsec.h176
-rw-r--r--hw/net/fsl_etsec/miim.c146
-rw-r--r--hw/net/fsl_etsec/registers.c295
-rw-r--r--hw/net/fsl_etsec/registers.h320
-rw-r--r--hw/net/fsl_etsec/rings.c655
-rw-r--r--hw/net/lan9118.c1392
-rw-r--r--hw/net/lance.c183
-rw-r--r--hw/net/mcf_fec.c534
-rw-r--r--hw/net/milkymist-minimac2.c540
-rw-r--r--hw/net/mipsnet.c286
-rw-r--r--hw/net/ne2000-isa.c152
-rw-r--r--hw/net/ne2000.c801
-rw-r--r--hw/net/ne2000.h40
-rw-r--r--hw/net/opencores_eth.c764
-rw-r--r--hw/net/pcnet-pci.c374
-rw-r--r--hw/net/pcnet.c1761
-rw-r--r--hw/net/pcnet.h67
-rw-r--r--hw/net/rocker/qmp-norocker.c50
-rw-r--r--hw/net/rocker/rocker.c1553
-rw-r--r--hw/net/rocker/rocker.h84
-rw-r--r--hw/net/rocker/rocker_desc.c377
-rw-r--r--hw/net/rocker/rocker_desc.h53
-rw-r--r--hw/net/rocker/rocker_fp.c263
-rw-r--r--hw/net/rocker/rocker_fp.h53
-rw-r--r--hw/net/rocker/rocker_hw.h493
-rw-r--r--hw/net/rocker/rocker_of_dpa.c2630
-rw-r--r--hw/net/rocker/rocker_of_dpa.h22
-rw-r--r--hw/net/rocker/rocker_tlv.h244
-rw-r--r--hw/net/rocker/rocker_world.c106
-rw-r--r--hw/net/rocker/rocker_world.h60
-rw-r--r--hw/net/rtl8139.c3562
-rw-r--r--hw/net/smc91c111.c807
-rw-r--r--hw/net/spapr_llan.c569
-rw-r--r--hw/net/stellaris_enet.c503
-rw-r--r--hw/net/vhost_net.c459
-rw-r--r--hw/net/virtio-net.c1864
-rw-r--r--hw/net/vmware_utils.h143
-rw-r--r--hw/net/vmxnet3.c2587
-rw-r--r--hw/net/vmxnet3.h755
-rw-r--r--hw/net/vmxnet_debug.h115
-rw-r--r--hw/net/vmxnet_rx_pkt.c186
-rw-r--r--hw/net/vmxnet_rx_pkt.h176
-rw-r--r--hw/net/vmxnet_tx_pkt.c580
-rw-r--r--hw/net/vmxnet_tx_pkt.h148
-rw-r--r--hw/net/xen_nic.c422
-rw-r--r--hw/net/xgmac.c432
-rw-r--r--hw/net/xilinx_axienet.c1082
-rw-r--r--hw/net/xilinx_ethlite.c273
-rw-r--r--hw/nvram/Makefile.objs5
-rw-r--r--hw/nvram/ds1225y.c169
-rw-r--r--hw/nvram/eeprom93xx.c336
-rw-r--r--hw/nvram/fw_cfg.c753
-rw-r--r--hw/nvram/mac_nvram.c213
-rw-r--r--hw/nvram/spapr_nvram.c248
-rw-r--r--hw/openrisc/Makefile.objs2
-rw-r--r--hw/openrisc/cputimer.c112
-rw-r--r--hw/openrisc/openrisc_sim.c148
-rw-r--r--hw/openrisc/pic_cpu.c60
-rw-r--r--hw/pci-bridge/Makefile.objs7
-rw-r--r--hw/pci-bridge/dec.c161
-rw-r--r--hw/pci-bridge/dec.h10
-rw-r--r--hw/pci-bridge/i82801b11.c109
-rw-r--r--hw/pci-bridge/ioh3420.c221
-rw-r--r--hw/pci-bridge/ioh3420.h6
-rw-r--r--hw/pci-bridge/pci_bridge_dev.c257
-rw-r--r--hw/pci-bridge/pci_expander_bridge.c286
-rw-r--r--hw/pci-bridge/xio3130_downstream.c209
-rw-r--r--hw/pci-bridge/xio3130_downstream.h11
-rw-r--r--hw/pci-bridge/xio3130_upstream.c182
-rw-r--r--hw/pci-bridge/xio3130_upstream.h10
-rw-r--r--hw/pci-host/Makefile.objs18
-rw-r--r--hw/pci-host/apb.c875
-rw-r--r--hw/pci-host/bonito.c841
-rw-r--r--hw/pci-host/gpex.c154
-rw-r--r--hw/pci-host/grackle.c166
-rw-r--r--hw/pci-host/pam.c69
-rw-r--r--hw/pci-host/piix.c785
-rw-r--r--hw/pci-host/ppce500.c550
-rw-r--r--hw/pci-host/prep.c404
-rw-r--r--hw/pci-host/q35.c578
-rw-r--r--hw/pci-host/uninorth.c515
-rw-r--r--hw/pci-host/versatile.c548
-rw-r--r--hw/pci/Makefile.objs9
-rw-r--r--hw/pci/msi.c400
-rw-r--r--hw/pci/msix.c614
-rw-r--r--hw/pci/pci-stub.c36
-rw-r--r--hw/pci/pci.c2472
-rw-r--r--hw/pci/pci_bridge.c419
-rw-r--r--hw/pci/pci_host.c191
-rw-r--r--hw/pci/pcie.c634
-rw-r--r--hw/pci/pcie_aer.c1042
-rw-r--r--hw/pci/pcie_host.c146
-rw-r--r--hw/pci/pcie_port.c178
-rw-r--r--hw/pci/shpc.c721
-rw-r--r--hw/pci/slotid_cap.c45
-rw-r--r--hw/pcmcia/Makefile.objs2
-rw-r--r--hw/pcmcia/pcmcia.c24
-rw-r--r--hw/pcmcia/pxa2xx.c264
-rw-r--r--hw/ppc/Makefile.objs23
-rw-r--r--hw/ppc/e500-ccsr.h17
-rw-r--r--hw/ppc/e500.c1085
-rw-r--r--hw/ppc/e500.h29
-rw-r--r--hw/ppc/e500plat.c73
-rw-r--r--hw/ppc/mac.h183
-rw-r--r--hw/ppc/mac_newworld.c530
-rw-r--r--hw/ppc/mac_oldworld.c377
-rw-r--r--hw/ppc/mpc8544_guts.c140
-rw-r--r--hw/ppc/mpc8544ds.c65
-rw-r--r--hw/ppc/ppc.c1339
-rw-r--r--hw/ppc/ppc405.h81
-rw-r--r--hw/ppc/ppc405_boards.c679
-rw-r--r--hw/ppc/ppc405_uc.c2551
-rw-r--r--hw/ppc/ppc440_bamboo.c307
-rw-r--r--hw/ppc/ppc4xx_devs.c734
-rw-r--r--hw/ppc/ppc4xx_pci.c392
-rw-r--r--hw/ppc/ppc_booke.c365
-rw-r--r--hw/ppc/ppce500_spin.c224
-rw-r--r--hw/ppc/prep.c714
-rw-r--r--hw/ppc/spapr.c1998
-rw-r--r--hw/ppc/spapr_drc.c745
-rw-r--r--hw/ppc/spapr_events.c565
-rw-r--r--hw/ppc/spapr_hcall.c1018
-rw-r--r--hw/ppc/spapr_iommu.c479
-rw-r--r--hw/ppc/spapr_pci.c1843
-rw-r--r--hw/ppc/spapr_pci_vfio.c280
-rw-r--r--hw/ppc/spapr_rtas.c752
-rw-r--r--hw/ppc/spapr_rtc.c211
-rw-r--r--hw/ppc/spapr_vio.c705
-rw-r--r--hw/ppc/virtex_ml507.c311
-rw-r--r--hw/s390x/Makefile.objs11
-rw-r--r--hw/s390x/css.c1558
-rw-r--r--hw/s390x/css.h111
-rw-r--r--hw/s390x/event-facility.c430
-rw-r--r--hw/s390x/ipl.c333
-rw-r--r--hw/s390x/ipl.h25
-rw-r--r--hw/s390x/s390-pci-bus.c596
-rw-r--r--hw/s390x/s390-pci-bus.h251
-rw-r--r--hw/s390x/s390-pci-inst.c839
-rw-r--r--hw/s390x/s390-pci-inst.h289
-rw-r--r--hw/s390x/s390-virtio-bus.c763
-rw-r--r--hw/s390x/s390-virtio-bus.h186
-rw-r--r--hw/s390x/s390-virtio-ccw.c307
-rw-r--r--hw/s390x/s390-virtio-hcall.c40
-rw-r--r--hw/s390x/s390-virtio.c366
-rw-r--r--hw/s390x/s390-virtio.h30
-rw-r--r--hw/s390x/sclp.c479
-rw-r--r--hw/s390x/sclpcpu.c114
-rw-r--r--hw/s390x/sclpquiesce.c142
-rw-r--r--hw/s390x/virtio-ccw.c1948
-rw-r--r--hw/s390x/virtio-ccw.h216
-rw-r--r--hw/scsi/Makefile.objs13
-rw-r--r--hw/scsi/esp-pci.c529
-rw-r--r--hw/scsi/esp.c743
-rw-r--r--hw/scsi/lsi53c895a.c2163
-rw-r--r--hw/scsi/megasas.c2547
-rw-r--r--hw/scsi/mfi.h1272
-rw-r--r--hw/scsi/scsi-bus.c2049
-rw-r--r--hw/scsi/scsi-disk.c2780
-rw-r--r--hw/scsi/scsi-generic.c496
-rw-r--r--hw/scsi/spapr_vscsi.c1302
-rw-r--r--hw/scsi/srp.h247
-rw-r--r--hw/scsi/vhost-scsi.c351
-rw-r--r--hw/scsi/viosrp.h216
-rw-r--r--hw/scsi/virtio-scsi-dataplane.c316
-rw-r--r--hw/scsi/virtio-scsi.c1021
-rw-r--r--hw/scsi/vmw_pvscsi.c1219
-rw-r--r--hw/scsi/vmw_pvscsi.h434
-rw-r--r--hw/sd/Makefile.objs8
-rw-r--r--hw/sd/milkymist-memcard.c316
-rw-r--r--hw/sd/omap_mmc.c648
-rw-r--r--hw/sd/pl181.c527
-rw-r--r--hw/sd/pxa2xx_mmci.c504
-rw-r--r--hw/sd/sd.c1765
-rw-r--r--hw/sd/sdhci.c1319
-rw-r--r--hw/sd/sdhci.h295
-rw-r--r--hw/sd/ssi-sd.c289
-rw-r--r--hw/sh4/Makefile.objs4
-rw-r--r--hw/sh4/r2d.c368
-rw-r--r--hw/sh4/sh7750.c842
-rw-r--r--hw/sh4/sh7750_regnames.c97
-rw-r--r--hw/sh4/sh7750_regnames.h6
-rw-r--r--hw/sh4/sh7750_regs.h1277
-rw-r--r--hw/sh4/sh_pci.c204
-rw-r--r--hw/sh4/shix.c102
-rw-r--r--hw/sparc/Makefile.objs1
-rw-r--r--hw/sparc/leon3.c230
-rw-r--r--hw/sparc/sun4m.c1521
-rw-r--r--hw/sparc64/Makefile.objs1
-rw-r--r--hw/sparc64/sun4u.c1008
-rw-r--r--hw/ssi/Makefile.objs6
-rw-r--r--hw/ssi/omap_spi.c374
-rw-r--r--hw/ssi/pl022.c326
-rw-r--r--hw/ssi/ssi.c174
-rw-r--r--hw/ssi/xilinx_spi.c390
-rw-r--r--hw/ssi/xilinx_spips.c771
-rw-r--r--hw/timer/Makefile.objs35
-rw-r--r--hw/timer/a9gtimer.c369
-rw-r--r--hw/timer/allwinner-a10-pit.c295
-rw-r--r--hw/timer/arm_mptimer.c300
-rw-r--r--hw/timer/arm_timer.c410
-rw-r--r--hw/timer/cadence_ttc.c491
-rw-r--r--hw/timer/digic-timer.c162
-rw-r--r--hw/timer/ds1338.c240
-rw-r--r--hw/timer/etraxfs_timer.c357
-rw-r--r--hw/timer/exynos4210_mct.c1481
-rw-r--r--hw/timer/exynos4210_pwm.c425
-rw-r--r--hw/timer/exynos4210_rtc.c595
-rw-r--r--hw/timer/grlib_gptimer.c411
-rw-r--r--hw/timer/hpet.c797
-rw-r--r--hw/timer/i8254.c383
-rw-r--r--hw/timer/i8254_common.c306
-rw-r--r--hw/timer/imx_epit.c410
-rw-r--r--hw/timer/imx_gpt.c557
-rw-r--r--hw/timer/lm32_timer.c235
-rw-r--r--hw/timer/m48t59.c946
-rw-r--r--hw/timer/mc146818rtc.c965
-rw-r--r--hw/timer/milkymist-sysctl.c341
-rw-r--r--hw/timer/omap_gptimer.c488
-rw-r--r--hw/timer/omap_synctimer.c102
-rw-r--r--hw/timer/pl031.c269
-rw-r--r--hw/timer/puv3_ost.c156
-rw-r--r--hw/timer/pxa2xx_timer.c596
-rw-r--r--hw/timer/sh_timer.c334
-rw-r--r--hw/timer/slavio_timer.c434
-rw-r--r--hw/timer/stm32f2xx_timer.c328
-rw-r--r--hw/timer/tusb6010.c816
-rw-r--r--hw/timer/twl92230.c887
-rw-r--r--hw/timer/xilinx_timer.c265
-rw-r--r--hw/tpm/Makefile.objs2
-rw-r--r--hw/tpm/tpm_int.h75
-rw-r--r--hw/tpm/tpm_passthrough.c543
-rw-r--r--hw/tpm/tpm_tis.c1099
-rw-r--r--hw/tpm/tpm_tis.h70
-rw-r--r--hw/tpm/tpm_util.c126
-rw-r--r--hw/tpm/tpm_util.h28
-rw-r--r--hw/tricore/Makefile.objs1
-rw-r--r--hw/tricore/tricore_testboard.c124
-rw-r--r--hw/unicore32/Makefile.objs4
-rw-r--r--hw/unicore32/puv3.c145
-rw-r--r--hw/usb/Makefile.objs39
-rw-r--r--hw/usb/bus.c758
-rw-r--r--hw/usb/ccid-card-emulated.c602
-rw-r--r--hw/usb/ccid-card-passthru.c413
-rw-r--r--hw/usb/ccid.h65
-rw-r--r--hw/usb/combined-packet.c187
-rw-r--r--hw/usb/core.c794
-rw-r--r--hw/usb/desc-msos.c238
-rw-r--r--hw/usb/desc.c804
-rw-r--r--hw/usb/desc.h245
-rw-r--r--hw/usb/dev-audio.c702
-rw-r--r--hw/usb/dev-bluetooth.c579
-rw-r--r--hw/usb/dev-hid.c881
-rw-r--r--hw/usb/dev-hub.c594
-rw-r--r--hw/usb/dev-mtp.c1134
-rw-r--r--hw/usb/dev-network.c1448
-rw-r--r--hw/usb/dev-serial.c649
-rw-r--r--hw/usb/dev-smartcard-reader.c1505
-rw-r--r--hw/usb/dev-storage.c867
-rw-r--r--hw/usb/dev-uas.c960
-rw-r--r--hw/usb/dev-wacom.c385
-rw-r--r--hw/usb/hcd-ehci-pci.c273
-rw-r--r--hw/usb/hcd-ehci-sysbus.c229
-rw-r--r--hw/usb/hcd-ehci.c2534
-rw-r--r--hw/usb/hcd-ehci.h383
-rw-r--r--hw/usb/hcd-musb.c1552
-rw-r--r--hw/usb/hcd-ohci.c2157
-rw-r--r--hw/usb/hcd-uhci.c1423
-rw-r--r--hw/usb/hcd-xhci.c3916
-rw-r--r--hw/usb/host-legacy.c143
-rw-r--r--hw/usb/host-libusb.c1685
-rw-r--r--hw/usb/host-stub.c47
-rw-r--r--hw/usb/host.h44
-rw-r--r--hw/usb/libhw.c70
-rw-r--r--hw/usb/quirks-ftdi-ids.h1255
-rw-r--r--hw/usb/quirks-pl2303-ids.h150
-rw-r--r--hw/usb/quirks.c53
-rw-r--r--hw/usb/quirks.h910
-rw-r--r--hw/usb/redirect.c2519
-rw-r--r--hw/vfio/Makefile.objs6
-rw-r--r--hw/vfio/calxeda-xgmac.c55
-rw-r--r--hw/vfio/common.c972
-rw-r--r--hw/vfio/pci.c3797
-rw-r--r--hw/vfio/platform.c715
-rw-r--r--hw/virtio/Makefile.objs8
-rw-r--r--hw/virtio/dataplane/Makefile.objs1
-rw-r--r--hw/virtio/dataplane/vring.c494
-rw-r--r--hw/virtio/vhost-backend.c69
-rw-r--r--hw/virtio/vhost-user.c351
-rw-r--r--hw/virtio/vhost.c1186
-rw-r--r--hw/virtio/virtio-balloon.c463
-rw-r--r--hw/virtio/virtio-bus.c178
-rw-r--r--hw/virtio/virtio-mmio.c579
-rw-r--r--hw/virtio/virtio-pci.c2254
-rw-r--r--hw/virtio/virtio-pci.h294
-rw-r--r--hw/virtio/virtio-rng.c267
-rw-r--r--hw/virtio/virtio.c1651
-rw-r--r--hw/watchdog/Makefile.objs4
-rw-r--r--hw/watchdog/watchdog.c152
-rw-r--r--hw/watchdog/wdt_diag288.c130
-rw-r--r--hw/watchdog/wdt_i6300esb.c470
-rw-r--r--hw/watchdog/wdt_ib700.c156
-rw-r--r--hw/xen/Makefile.objs5
-rw-r--r--hw/xen/xen-host-pci-device.c396
-rw-r--r--hw/xen/xen-host-pci-device.h55
-rw-r--r--hw/xen/xen_backend.c808
-rw-r--r--hw/xen/xen_devconfig.c175
-rw-r--r--hw/xen/xen_pt.c885
-rw-r--r--hw/xen/xen_pt.h309
-rw-r--r--hw/xen/xen_pt_config_init.c1936
-rw-r--r--hw/xen/xen_pt_msi.c623
-rw-r--r--hw/xenpv/Makefile.objs2
-rw-r--r--hw/xenpv/xen_domainbuild.c299
-rw-r--r--hw/xenpv/xen_domainbuild.h13
-rw-r--r--hw/xenpv/xen_machine_pv.c109
-rw-r--r--hw/xtensa/Makefile.objs3
-rw-r--r--hw/xtensa/bootparam.h49
-rw-r--r--hw/xtensa/pic_cpu.c166
-rw-r--r--hw/xtensa/sim.c120
-rw-r--r--hw/xtensa/xtfpga.c466
775 files changed, 398890 insertions, 0 deletions
diff --git a/hw/9pfs/Makefile.objs b/hw/9pfs/Makefile.objs
new file mode 100644
index 00000000..1e9b595c
--- /dev/null
+++ b/hw/9pfs/Makefile.objs
@@ -0,0 +1,9 @@
+common-obj-y = virtio-9p.o
+common-obj-y += virtio-9p-local.o virtio-9p-xattr.o
+common-obj-y += virtio-9p-xattr-user.o virtio-9p-posix-acl.o
+common-obj-y += virtio-9p-coth.o cofs.o codir.o cofile.o
+common-obj-y += coxattr.o virtio-9p-synth.o
+common-obj-$(CONFIG_OPEN_BY_HANDLE) += virtio-9p-handle.o
+common-obj-y += virtio-9p-proxy.o
+
+obj-y += virtio-9p-device.o
diff --git a/hw/9pfs/codir.c b/hw/9pfs/codir.c
new file mode 100644
index 00000000..65ad3298
--- /dev/null
+++ b/hw/9pfs/codir.c
@@ -0,0 +1,168 @@
+
+/*
+ * Virtio 9p backend
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "fsdev/qemu-fsdev.h"
+#include "qemu/thread.h"
+#include "block/coroutine.h"
+#include "virtio-9p-coth.h"
+
+int v9fs_co_readdir_r(V9fsPDU *pdu, V9fsFidState *fidp, struct dirent *dent,
+ struct dirent **result)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ errno = 0;
+ err = s->ops->readdir_r(&s->ctx, &fidp->fs, dent, result);
+ if (!*result && errno) {
+ err = -errno;
+ } else {
+ err = 0;
+ }
+ });
+ return err;
+}
+
+off_t v9fs_co_telldir(V9fsPDU *pdu, V9fsFidState *fidp)
+{
+ off_t err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->telldir(&s->ctx, &fidp->fs);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ return err;
+}
+
+void v9fs_co_seekdir(V9fsPDU *pdu, V9fsFidState *fidp, off_t offset)
+{
+ V9fsState *s = pdu->s;
+ if (v9fs_request_cancelled(pdu)) {
+ return;
+ }
+ v9fs_co_run_in_worker(
+ {
+ s->ops->seekdir(&s->ctx, &fidp->fs, offset);
+ });
+}
+
+void v9fs_co_rewinddir(V9fsPDU *pdu, V9fsFidState *fidp)
+{
+ V9fsState *s = pdu->s;
+ if (v9fs_request_cancelled(pdu)) {
+ return;
+ }
+ v9fs_co_run_in_worker(
+ {
+ s->ops->rewinddir(&s->ctx, &fidp->fs);
+ });
+}
+
+int v9fs_co_mkdir(V9fsPDU *pdu, V9fsFidState *fidp, V9fsString *name,
+ mode_t mode, uid_t uid, gid_t gid, struct stat *stbuf)
+{
+ int err;
+ FsCred cred;
+ V9fsPath path;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ cred_init(&cred);
+ cred.fc_mode = mode;
+ cred.fc_uid = uid;
+ cred.fc_gid = gid;
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->mkdir(&s->ctx, &fidp->path, name->data, &cred);
+ if (err < 0) {
+ err = -errno;
+ } else {
+ v9fs_path_init(&path);
+ err = v9fs_name_to_path(s, &fidp->path, name->data, &path);
+ if (!err) {
+ err = s->ops->lstat(&s->ctx, &path, stbuf);
+ if (err < 0) {
+ err = -errno;
+ }
+ }
+ v9fs_path_free(&path);
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_opendir(V9fsPDU *pdu, V9fsFidState *fidp)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->opendir(&s->ctx, &fidp->path, &fidp->fs);
+ if (err < 0) {
+ err = -errno;
+ } else {
+ err = 0;
+ }
+ });
+ v9fs_path_unlock(s);
+ if (!err) {
+ total_open_fd++;
+ if (total_open_fd > open_fd_hw) {
+ v9fs_reclaim_fd(pdu);
+ }
+ }
+ return err;
+}
+
+int v9fs_co_closedir(V9fsPDU *pdu, V9fsFidOpenState *fs)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->closedir(&s->ctx, fs);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ if (!err) {
+ total_open_fd--;
+ }
+ return err;
+}
diff --git a/hw/9pfs/cofile.c b/hw/9pfs/cofile.c
new file mode 100644
index 00000000..2efebf35
--- /dev/null
+++ b/hw/9pfs/cofile.c
@@ -0,0 +1,275 @@
+
+/*
+ * Virtio 9p backend
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "fsdev/qemu-fsdev.h"
+#include "qemu/thread.h"
+#include "block/coroutine.h"
+#include "virtio-9p-coth.h"
+
+int v9fs_co_st_gen(V9fsPDU *pdu, V9fsPath *path, mode_t st_mode,
+ V9fsStatDotl *v9stat)
+{
+ int err = 0;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ if (s->ctx.exops.get_st_gen) {
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ctx.exops.get_st_gen(&s->ctx, path, st_mode,
+ &v9stat->st_gen);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ }
+ return err;
+}
+
+int v9fs_co_lstat(V9fsPDU *pdu, V9fsPath *path, struct stat *stbuf)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->lstat(&s->ctx, path, stbuf);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_fstat(V9fsPDU *pdu, V9fsFidState *fidp, struct stat *stbuf)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->fstat(&s->ctx, fidp->fid_type, &fidp->fs, stbuf);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ /*
+ * Some FS driver (local:mapped-file) can't support fetching attributes
+ * using file descriptor. Use Path name in that case.
+ */
+ if (err == -EOPNOTSUPP) {
+ err = v9fs_co_lstat(pdu, &fidp->path, stbuf);
+ if (err == -ENOENT) {
+ /*
+ * fstat on an unlinked file. Work with partial results
+ * returned from s->ops->fstat
+ */
+ err = 0;
+ }
+ }
+ return err;
+}
+
+int v9fs_co_open(V9fsPDU *pdu, V9fsFidState *fidp, int flags)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->open(&s->ctx, &fidp->path, flags, &fidp->fs);
+ if (err == -1) {
+ err = -errno;
+ } else {
+ err = 0;
+ }
+ });
+ v9fs_path_unlock(s);
+ if (!err) {
+ total_open_fd++;
+ if (total_open_fd > open_fd_hw) {
+ v9fs_reclaim_fd(pdu);
+ }
+ }
+ return err;
+}
+
+int v9fs_co_open2(V9fsPDU *pdu, V9fsFidState *fidp, V9fsString *name, gid_t gid,
+ int flags, int mode, struct stat *stbuf)
+{
+ int err;
+ FsCred cred;
+ V9fsPath path;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ cred_init(&cred);
+ cred.fc_mode = mode & 07777;
+ cred.fc_uid = fidp->uid;
+ cred.fc_gid = gid;
+ /*
+ * Hold the directory fid lock so that directory path name
+ * don't change. Read lock is fine because this fid cannot
+ * be used by any other operation.
+ */
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->open2(&s->ctx, &fidp->path,
+ name->data, flags, &cred, &fidp->fs);
+ if (err < 0) {
+ err = -errno;
+ } else {
+ v9fs_path_init(&path);
+ err = v9fs_name_to_path(s, &fidp->path, name->data, &path);
+ if (!err) {
+ err = s->ops->lstat(&s->ctx, &path, stbuf);
+ if (err < 0) {
+ err = -errno;
+ s->ops->close(&s->ctx, &fidp->fs);
+ } else {
+ v9fs_path_copy(&fidp->path, &path);
+ }
+ } else {
+ s->ops->close(&s->ctx, &fidp->fs);
+ }
+ v9fs_path_free(&path);
+ }
+ });
+ v9fs_path_unlock(s);
+ if (!err) {
+ total_open_fd++;
+ if (total_open_fd > open_fd_hw) {
+ v9fs_reclaim_fd(pdu);
+ }
+ }
+ return err;
+}
+
+int v9fs_co_close(V9fsPDU *pdu, V9fsFidOpenState *fs)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->close(&s->ctx, fs);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ if (!err) {
+ total_open_fd--;
+ }
+ return err;
+}
+
+int v9fs_co_fsync(V9fsPDU *pdu, V9fsFidState *fidp, int datasync)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->fsync(&s->ctx, fidp->fid_type, &fidp->fs, datasync);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ return err;
+}
+
+int v9fs_co_link(V9fsPDU *pdu, V9fsFidState *oldfid,
+ V9fsFidState *newdirfid, V9fsString *name)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->link(&s->ctx, &oldfid->path,
+ &newdirfid->path, name->data);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_pwritev(V9fsPDU *pdu, V9fsFidState *fidp,
+ struct iovec *iov, int iovcnt, int64_t offset)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->pwritev(&s->ctx, &fidp->fs, iov, iovcnt, offset);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ return err;
+}
+
+int v9fs_co_preadv(V9fsPDU *pdu, V9fsFidState *fidp,
+ struct iovec *iov, int iovcnt, int64_t offset)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->preadv(&s->ctx, &fidp->fs, iov, iovcnt, offset);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ return err;
+}
diff --git a/hw/9pfs/cofs.c b/hw/9pfs/cofs.c
new file mode 100644
index 00000000..42ee614e
--- /dev/null
+++ b/hw/9pfs/cofs.c
@@ -0,0 +1,364 @@
+
+/*
+ * Virtio 9p backend
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "fsdev/qemu-fsdev.h"
+#include "qemu/thread.h"
+#include "block/coroutine.h"
+#include "virtio-9p-coth.h"
+
+static ssize_t __readlink(V9fsState *s, V9fsPath *path, V9fsString *buf)
+{
+ ssize_t len, maxlen = PATH_MAX;
+
+ buf->data = g_malloc(PATH_MAX);
+ for(;;) {
+ len = s->ops->readlink(&s->ctx, path, buf->data, maxlen);
+ if (len < 0) {
+ g_free(buf->data);
+ buf->data = NULL;
+ buf->size = 0;
+ break;
+ } else if (len == maxlen) {
+ /*
+ * We dodn't have space to put the NULL or we have more
+ * to read. Increase the size and try again
+ */
+ maxlen *= 2;
+ g_free(buf->data);
+ buf->data = g_malloc(maxlen);
+ continue;
+ }
+ /*
+ * Null terminate the readlink output
+ */
+ buf->data[len] = '\0';
+ buf->size = len;
+ break;
+ }
+ return len;
+}
+
+int v9fs_co_readlink(V9fsPDU *pdu, V9fsPath *path, V9fsString *buf)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = __readlink(s, path, buf);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_statfs(V9fsPDU *pdu, V9fsPath *path, struct statfs *stbuf)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->statfs(&s->ctx, path, stbuf);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_chmod(V9fsPDU *pdu, V9fsPath *path, mode_t mode)
+{
+ int err;
+ FsCred cred;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ cred_init(&cred);
+ cred.fc_mode = mode;
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->chmod(&s->ctx, path, &cred);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_utimensat(V9fsPDU *pdu, V9fsPath *path,
+ struct timespec times[2])
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->utimensat(&s->ctx, path, times);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_chown(V9fsPDU *pdu, V9fsPath *path, uid_t uid, gid_t gid)
+{
+ int err;
+ FsCred cred;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ cred_init(&cred);
+ cred.fc_uid = uid;
+ cred.fc_gid = gid;
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->chown(&s->ctx, path, &cred);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_truncate(V9fsPDU *pdu, V9fsPath *path, off_t size)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->truncate(&s->ctx, path, size);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_mknod(V9fsPDU *pdu, V9fsFidState *fidp, V9fsString *name, uid_t uid,
+ gid_t gid, dev_t dev, mode_t mode, struct stat *stbuf)
+{
+ int err;
+ V9fsPath path;
+ FsCred cred;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ cred_init(&cred);
+ cred.fc_uid = uid;
+ cred.fc_gid = gid;
+ cred.fc_mode = mode;
+ cred.fc_rdev = dev;
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->mknod(&s->ctx, &fidp->path, name->data, &cred);
+ if (err < 0) {
+ err = -errno;
+ } else {
+ v9fs_path_init(&path);
+ err = v9fs_name_to_path(s, &fidp->path, name->data, &path);
+ if (!err) {
+ err = s->ops->lstat(&s->ctx, &path, stbuf);
+ if (err < 0) {
+ err = -errno;
+ }
+ }
+ v9fs_path_free(&path);
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+/* Only works with path name based fid */
+int v9fs_co_remove(V9fsPDU *pdu, V9fsPath *path)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->remove(&s->ctx, path->data);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_unlinkat(V9fsPDU *pdu, V9fsPath *path, V9fsString *name, int flags)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->unlinkat(&s->ctx, path, name->data, flags);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+/* Only work with path name based fid */
+int v9fs_co_rename(V9fsPDU *pdu, V9fsPath *oldpath, V9fsPath *newpath)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->rename(&s->ctx, oldpath->data, newpath->data);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ return err;
+}
+
+int v9fs_co_renameat(V9fsPDU *pdu, V9fsPath *olddirpath, V9fsString *oldname,
+ V9fsPath *newdirpath, V9fsString *newname)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->renameat(&s->ctx, olddirpath, oldname->data,
+ newdirpath, newname->data);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ return err;
+}
+
+int v9fs_co_symlink(V9fsPDU *pdu, V9fsFidState *dfidp, V9fsString *name,
+ const char *oldpath, gid_t gid, struct stat *stbuf)
+{
+ int err;
+ FsCred cred;
+ V9fsPath path;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ cred_init(&cred);
+ cred.fc_uid = dfidp->uid;
+ cred.fc_gid = gid;
+ cred.fc_mode = 0777;
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->symlink(&s->ctx, oldpath, &dfidp->path,
+ name->data, &cred);
+ if (err < 0) {
+ err = -errno;
+ } else {
+ v9fs_path_init(&path);
+ err = v9fs_name_to_path(s, &dfidp->path, name->data, &path);
+ if (!err) {
+ err = s->ops->lstat(&s->ctx, &path, stbuf);
+ if (err < 0) {
+ err = -errno;
+ }
+ }
+ v9fs_path_free(&path);
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+/*
+ * For path name based fid we don't block. So we can
+ * directly call the fs driver ops.
+ */
+int v9fs_co_name_to_path(V9fsPDU *pdu, V9fsPath *dirpath,
+ const char *name, V9fsPath *path)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT) {
+ err = s->ops->name_to_path(&s->ctx, dirpath, name, path);
+ if (err < 0) {
+ err = -errno;
+ }
+ } else {
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->name_to_path(&s->ctx, dirpath, name, path);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ }
+ return err;
+}
diff --git a/hw/9pfs/coxattr.c b/hw/9pfs/coxattr.c
new file mode 100644
index 00000000..18ee08df
--- /dev/null
+++ b/hw/9pfs/coxattr.c
@@ -0,0 +1,107 @@
+
+/*
+ * Virtio 9p backend
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "fsdev/qemu-fsdev.h"
+#include "qemu/thread.h"
+#include "block/coroutine.h"
+#include "virtio-9p-coth.h"
+
+int v9fs_co_llistxattr(V9fsPDU *pdu, V9fsPath *path, void *value, size_t size)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->llistxattr(&s->ctx, path, value, size);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_lgetxattr(V9fsPDU *pdu, V9fsPath *path,
+ V9fsString *xattr_name,
+ void *value, size_t size)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->lgetxattr(&s->ctx, path,
+ xattr_name->data,
+ value, size);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_lsetxattr(V9fsPDU *pdu, V9fsPath *path,
+ V9fsString *xattr_name, void *value,
+ size_t size, int flags)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->lsetxattr(&s->ctx, path,
+ xattr_name->data, value,
+ size, flags);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
+
+int v9fs_co_lremovexattr(V9fsPDU *pdu, V9fsPath *path,
+ V9fsString *xattr_name)
+{
+ int err;
+ V9fsState *s = pdu->s;
+
+ if (v9fs_request_cancelled(pdu)) {
+ return -EINTR;
+ }
+ v9fs_path_read_lock(s);
+ v9fs_co_run_in_worker(
+ {
+ err = s->ops->lremovexattr(&s->ctx, path, xattr_name->data);
+ if (err < 0) {
+ err = -errno;
+ }
+ });
+ v9fs_path_unlock(s);
+ return err;
+}
diff --git a/hw/9pfs/virtio-9p-coth.c b/hw/9pfs/virtio-9p-coth.c
new file mode 100644
index 00000000..8185c533
--- /dev/null
+++ b/hw/9pfs/virtio-9p-coth.c
@@ -0,0 +1,82 @@
+/*
+ * Virtio 9p backend
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Harsh Prateek Bora <harsh@linux.vnet.ibm.com>
+ * Venkateswararao Jujjuri(JV) <jvrao@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "fsdev/qemu-fsdev.h"
+#include "qemu/thread.h"
+#include "qemu/event_notifier.h"
+#include "block/coroutine.h"
+#include "virtio-9p-coth.h"
+
+/* v9fs glib thread pool */
+static V9fsThPool v9fs_pool;
+
+void co_run_in_worker_bh(void *opaque)
+{
+ Coroutine *co = opaque;
+ g_thread_pool_push(v9fs_pool.pool, co, NULL);
+}
+
+static void v9fs_qemu_process_req_done(EventNotifier *e)
+{
+ Coroutine *co;
+
+ event_notifier_test_and_clear(e);
+
+ while ((co = g_async_queue_try_pop(v9fs_pool.completed)) != NULL) {
+ qemu_coroutine_enter(co, NULL);
+ }
+}
+
+static void v9fs_thread_routine(gpointer data, gpointer user_data)
+{
+ Coroutine *co = data;
+
+ qemu_coroutine_enter(co, NULL);
+
+ g_async_queue_push(v9fs_pool.completed, co);
+
+ event_notifier_set(&v9fs_pool.e);
+}
+
+int v9fs_init_worker_threads(void)
+{
+ int ret = 0;
+ V9fsThPool *p = &v9fs_pool;
+ sigset_t set, oldset;
+
+ sigfillset(&set);
+ /* Leave signal handling to the iothread. */
+ pthread_sigmask(SIG_SETMASK, &set, &oldset);
+
+ p->pool = g_thread_pool_new(v9fs_thread_routine, p, -1, FALSE, NULL);
+ if (!p->pool) {
+ ret = -1;
+ goto err_out;
+ }
+ p->completed = g_async_queue_new();
+ if (!p->completed) {
+ /*
+ * We are going to terminate.
+ * So don't worry about cleanup
+ */
+ ret = -1;
+ goto err_out;
+ }
+ event_notifier_init(&p->e, 0);
+
+ event_notifier_set_handler(&p->e, v9fs_qemu_process_req_done);
+err_out:
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ return ret;
+}
diff --git a/hw/9pfs/virtio-9p-coth.h b/hw/9pfs/virtio-9p-coth.h
new file mode 100644
index 00000000..4f51b250
--- /dev/null
+++ b/hw/9pfs/virtio-9p-coth.h
@@ -0,0 +1,107 @@
+/*
+ * Virtio 9p backend
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Harsh Prateek Bora <harsh@linux.vnet.ibm.com>
+ * Venkateswararao Jujjuri(JV) <jvrao@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef _QEMU_VIRTIO_9P_COTH_H
+#define _QEMU_VIRTIO_9P_COTH_H
+
+#include "qemu/thread.h"
+#include "block/coroutine.h"
+#include "virtio-9p.h"
+#include <glib.h>
+
+typedef struct V9fsThPool {
+ EventNotifier e;
+
+ GThreadPool *pool;
+ GAsyncQueue *completed;
+} V9fsThPool;
+
+/*
+ * we want to use bottom half because we want to make sure the below
+ * sequence of events.
+ *
+ * 1. Yield the coroutine in the QEMU thread.
+ * 2. Submit the coroutine to a worker thread.
+ * 3. Enter the coroutine in the worker thread.
+ * we cannot swap step 1 and 2, because that would imply worker thread
+ * can enter coroutine while step1 is still running
+ */
+#define v9fs_co_run_in_worker(code_block) \
+ do { \
+ QEMUBH *co_bh; \
+ co_bh = qemu_bh_new(co_run_in_worker_bh, \
+ qemu_coroutine_self()); \
+ qemu_bh_schedule(co_bh); \
+ /* \
+ * yield in qemu thread and re-enter back \
+ * in glib worker thread \
+ */ \
+ qemu_coroutine_yield(); \
+ qemu_bh_delete(co_bh); \
+ code_block; \
+ /* re-enter back to qemu thread */ \
+ qemu_coroutine_yield(); \
+ } while (0)
+
+extern void co_run_in_worker_bh(void *);
+extern int v9fs_init_worker_threads(void);
+extern int v9fs_co_readlink(V9fsPDU *, V9fsPath *, V9fsString *);
+extern int v9fs_co_readdir_r(V9fsPDU *, V9fsFidState *,
+ struct dirent *, struct dirent **result);
+extern off_t v9fs_co_telldir(V9fsPDU *, V9fsFidState *);
+extern void v9fs_co_seekdir(V9fsPDU *, V9fsFidState *, off_t);
+extern void v9fs_co_rewinddir(V9fsPDU *, V9fsFidState *);
+extern int v9fs_co_statfs(V9fsPDU *, V9fsPath *, struct statfs *);
+extern int v9fs_co_lstat(V9fsPDU *, V9fsPath *, struct stat *);
+extern int v9fs_co_chmod(V9fsPDU *, V9fsPath *, mode_t);
+extern int v9fs_co_utimensat(V9fsPDU *, V9fsPath *, struct timespec [2]);
+extern int v9fs_co_chown(V9fsPDU *, V9fsPath *, uid_t, gid_t);
+extern int v9fs_co_truncate(V9fsPDU *, V9fsPath *, off_t);
+extern int v9fs_co_llistxattr(V9fsPDU *, V9fsPath *, void *, size_t);
+extern int v9fs_co_lgetxattr(V9fsPDU *, V9fsPath *,
+ V9fsString *, void *, size_t);
+extern int v9fs_co_mknod(V9fsPDU *, V9fsFidState *, V9fsString *, uid_t,
+ gid_t, dev_t, mode_t, struct stat *);
+extern int v9fs_co_mkdir(V9fsPDU *, V9fsFidState *, V9fsString *,
+ mode_t, uid_t, gid_t, struct stat *);
+extern int v9fs_co_remove(V9fsPDU *, V9fsPath *);
+extern int v9fs_co_rename(V9fsPDU *, V9fsPath *, V9fsPath *);
+extern int v9fs_co_unlinkat(V9fsPDU *, V9fsPath *, V9fsString *, int flags);
+extern int v9fs_co_renameat(V9fsPDU *, V9fsPath *, V9fsString *,
+ V9fsPath *, V9fsString *);
+extern int v9fs_co_fstat(V9fsPDU *, V9fsFidState *, struct stat *);
+extern int v9fs_co_opendir(V9fsPDU *, V9fsFidState *);
+extern int v9fs_co_open(V9fsPDU *, V9fsFidState *, int);
+extern int v9fs_co_open2(V9fsPDU *, V9fsFidState *, V9fsString *,
+ gid_t, int, int, struct stat *);
+extern int v9fs_co_lsetxattr(V9fsPDU *, V9fsPath *, V9fsString *,
+ void *, size_t, int);
+extern int v9fs_co_lremovexattr(V9fsPDU *, V9fsPath *, V9fsString *);
+extern int v9fs_co_closedir(V9fsPDU *, V9fsFidOpenState *);
+extern int v9fs_co_close(V9fsPDU *, V9fsFidOpenState *);
+extern int v9fs_co_fsync(V9fsPDU *, V9fsFidState *, int);
+extern int v9fs_co_symlink(V9fsPDU *, V9fsFidState *, V9fsString *,
+ const char *, gid_t, struct stat *);
+extern int v9fs_co_link(V9fsPDU *, V9fsFidState *,
+ V9fsFidState *, V9fsString *);
+extern int v9fs_co_pwritev(V9fsPDU *, V9fsFidState *,
+ struct iovec *, int, int64_t);
+extern int v9fs_co_preadv(V9fsPDU *, V9fsFidState *,
+ struct iovec *, int, int64_t);
+extern int v9fs_co_name_to_path(V9fsPDU *, V9fsPath *,
+ const char *, V9fsPath *);
+extern int v9fs_co_st_gen(V9fsPDU *pdu, V9fsPath *path, mode_t,
+ V9fsStatDotl *v9stat);
+
+#endif
diff --git a/hw/9pfs/virtio-9p-device.c b/hw/9pfs/virtio-9p-device.c
new file mode 100644
index 00000000..93a407c4
--- /dev/null
+++ b/hw/9pfs/virtio-9p-device.c
@@ -0,0 +1,173 @@
+/*
+ * Virtio 9p backend
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-9p.h"
+#include "hw/i386/pc.h"
+#include "qemu/sockets.h"
+#include "virtio-9p.h"
+#include "fsdev/qemu-fsdev.h"
+#include "virtio-9p-xattr.h"
+#include "virtio-9p-coth.h"
+#include "hw/virtio/virtio-access.h"
+
+static uint64_t virtio_9p_get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
+{
+ virtio_add_feature(&features, VIRTIO_9P_MOUNT_TAG);
+ return features;
+}
+
+static void virtio_9p_get_config(VirtIODevice *vdev, uint8_t *config)
+{
+ int len;
+ struct virtio_9p_config *cfg;
+ V9fsState *s = VIRTIO_9P(vdev);
+
+ len = strlen(s->tag);
+ cfg = g_malloc0(sizeof(struct virtio_9p_config) + len);
+ virtio_stw_p(vdev, &cfg->tag_len, len);
+ /* We don't copy the terminating null to config space */
+ memcpy(cfg->tag, s->tag, len);
+ memcpy(config, cfg, s->config_size);
+ g_free(cfg);
+}
+
+static void virtio_9p_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ V9fsState *s = VIRTIO_9P(dev);
+ int i, len;
+ struct stat stat;
+ FsDriverEntry *fse;
+ V9fsPath path;
+
+ virtio_init(vdev, "virtio-9p", VIRTIO_ID_9P,
+ sizeof(struct virtio_9p_config) + MAX_TAG_LEN);
+
+ /* initialize pdu allocator */
+ QLIST_INIT(&s->free_list);
+ QLIST_INIT(&s->active_list);
+ for (i = 0; i < (MAX_REQ - 1); i++) {
+ QLIST_INSERT_HEAD(&s->free_list, &s->pdus[i], next);
+ }
+
+ s->vq = virtio_add_queue(vdev, MAX_REQ, handle_9p_output);
+
+ v9fs_path_init(&path);
+
+ fse = get_fsdev_fsentry(s->fsconf.fsdev_id);
+
+ if (!fse) {
+ /* We don't have a fsdev identified by fsdev_id */
+ error_setg(errp, "Virtio-9p device couldn't find fsdev with the "
+ "id = %s",
+ s->fsconf.fsdev_id ? s->fsconf.fsdev_id : "NULL");
+ goto out;
+ }
+
+ if (!s->fsconf.tag) {
+ /* we haven't specified a mount_tag */
+ error_setg(errp, "fsdev with id %s needs mount_tag arguments",
+ s->fsconf.fsdev_id);
+ goto out;
+ }
+
+ s->ctx.export_flags = fse->export_flags;
+ s->ctx.fs_root = g_strdup(fse->path);
+ s->ctx.exops.get_st_gen = NULL;
+ len = strlen(s->fsconf.tag);
+ if (len > MAX_TAG_LEN - 1) {
+ error_setg(errp, "mount tag '%s' (%d bytes) is longer than "
+ "maximum (%d bytes)", s->fsconf.tag, len, MAX_TAG_LEN - 1);
+ goto out;
+ }
+
+ s->tag = g_strdup(s->fsconf.tag);
+ s->ctx.uid = -1;
+
+ s->ops = fse->ops;
+ s->config_size = sizeof(struct virtio_9p_config) + len;
+ s->fid_list = NULL;
+ qemu_co_rwlock_init(&s->rename_lock);
+
+ if (s->ops->init(&s->ctx) < 0) {
+ error_setg(errp, "Virtio-9p Failed to initialize fs-driver with id:%s"
+ " and export path:%s", s->fsconf.fsdev_id, s->ctx.fs_root);
+ goto out;
+ }
+ if (v9fs_init_worker_threads() < 0) {
+ error_setg(errp, "worker thread initialization failed");
+ goto out;
+ }
+
+ /*
+ * Check details of export path, We need to use fs driver
+ * call back to do that. Since we are in the init path, we don't
+ * use co-routines here.
+ */
+ if (s->ops->name_to_path(&s->ctx, NULL, "/", &path) < 0) {
+ error_setg(errp,
+ "error in converting name to path %s", strerror(errno));
+ goto out;
+ }
+ if (s->ops->lstat(&s->ctx, &path, &stat)) {
+ error_setg(errp, "share path %s does not exist", fse->path);
+ goto out;
+ } else if (!S_ISDIR(stat.st_mode)) {
+ error_setg(errp, "share path %s is not a directory", fse->path);
+ goto out;
+ }
+ v9fs_path_free(&path);
+
+ return;
+out:
+ g_free(s->ctx.fs_root);
+ g_free(s->tag);
+ virtio_cleanup(vdev);
+ v9fs_path_free(&path);
+}
+
+/* virtio-9p device */
+
+static Property virtio_9p_properties[] = {
+ DEFINE_PROP_STRING("mount_tag", V9fsState, fsconf.tag),
+ DEFINE_PROP_STRING("fsdev", V9fsState, fsconf.fsdev_id),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_9p_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ dc->props = virtio_9p_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ vdc->realize = virtio_9p_device_realize;
+ vdc->get_features = virtio_9p_get_features;
+ vdc->get_config = virtio_9p_get_config;
+}
+
+static const TypeInfo virtio_device_info = {
+ .name = TYPE_VIRTIO_9P,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(V9fsState),
+ .class_init = virtio_9p_class_init,
+};
+
+static void virtio_9p_register_types(void)
+{
+ type_register_static(&virtio_device_info);
+}
+
+type_init(virtio_9p_register_types)
diff --git a/hw/9pfs/virtio-9p-handle.c b/hw/9pfs/virtio-9p-handle.c
new file mode 100644
index 00000000..13eabb98
--- /dev/null
+++ b/hw/9pfs/virtio-9p-handle.c
@@ -0,0 +1,708 @@
+/*
+ * Virtio 9p handle callback
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/virtio.h"
+#include "virtio-9p.h"
+#include "virtio-9p-xattr.h"
+#include <arpa/inet.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "qemu/xattr.h"
+#include <unistd.h>
+#include <linux/fs.h>
+#ifdef CONFIG_LINUX_MAGIC_H
+#include <linux/magic.h>
+#endif
+#include <sys/ioctl.h>
+
+#ifndef XFS_SUPER_MAGIC
+#define XFS_SUPER_MAGIC 0x58465342
+#endif
+#ifndef EXT2_SUPER_MAGIC
+#define EXT2_SUPER_MAGIC 0xEF53
+#endif
+#ifndef REISERFS_SUPER_MAGIC
+#define REISERFS_SUPER_MAGIC 0x52654973
+#endif
+#ifndef BTRFS_SUPER_MAGIC
+#define BTRFS_SUPER_MAGIC 0x9123683E
+#endif
+
+struct handle_data {
+ int mountfd;
+ int handle_bytes;
+};
+
+static inline int name_to_handle(int dirfd, const char *name,
+ struct file_handle *fh, int *mnt_id, int flags)
+{
+ return name_to_handle_at(dirfd, name, fh, mnt_id, flags);
+}
+
+static inline int open_by_handle(int mountfd, const char *fh, int flags)
+{
+ return open_by_handle_at(mountfd, (struct file_handle *)fh, flags);
+}
+
+static int handle_update_file_cred(int dirfd, const char *name, FsCred *credp)
+{
+ int fd, ret;
+ fd = openat(dirfd, name, O_NONBLOCK | O_NOFOLLOW);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = fchownat(fd, "", credp->fc_uid, credp->fc_gid, AT_EMPTY_PATH);
+ if (ret < 0) {
+ goto err_out;
+ }
+ ret = fchmod(fd, credp->fc_mode & 07777);
+err_out:
+ close(fd);
+ return ret;
+}
+
+
+static int handle_lstat(FsContext *fs_ctx, V9fsPath *fs_path,
+ struct stat *stbuf)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)fs_ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_PATH);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = fstatat(fd, "", stbuf, AT_EMPTY_PATH);
+ close(fd);
+ return ret;
+}
+
+static ssize_t handle_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
+ char *buf, size_t bufsz)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)fs_ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_PATH);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = readlinkat(fd, "", buf, bufsz);
+ close(fd);
+ return ret;
+}
+
+static int handle_close(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return close(fs->fd);
+}
+
+static int handle_closedir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return closedir(fs->dir);
+}
+
+static int handle_open(FsContext *ctx, V9fsPath *fs_path,
+ int flags, V9fsFidOpenState *fs)
+{
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ fs->fd = open_by_handle(data->mountfd, fs_path->data, flags);
+ return fs->fd;
+}
+
+static int handle_opendir(FsContext *ctx,
+ V9fsPath *fs_path, V9fsFidOpenState *fs)
+{
+ int ret;
+ ret = handle_open(ctx, fs_path, O_DIRECTORY, fs);
+ if (ret < 0) {
+ return -1;
+ }
+ fs->dir = fdopendir(ret);
+ if (!fs->dir) {
+ return -1;
+ }
+ return 0;
+}
+
+static void handle_rewinddir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ rewinddir(fs->dir);
+}
+
+static off_t handle_telldir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return telldir(fs->dir);
+}
+
+static int handle_readdir_r(FsContext *ctx, V9fsFidOpenState *fs,
+ struct dirent *entry,
+ struct dirent **result)
+{
+ return readdir_r(fs->dir, entry, result);
+}
+
+static void handle_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
+{
+ seekdir(fs->dir, off);
+}
+
+static ssize_t handle_preadv(FsContext *ctx, V9fsFidOpenState *fs,
+ const struct iovec *iov,
+ int iovcnt, off_t offset)
+{
+#ifdef CONFIG_PREADV
+ return preadv(fs->fd, iov, iovcnt, offset);
+#else
+ int err = lseek(fs->fd, offset, SEEK_SET);
+ if (err == -1) {
+ return err;
+ } else {
+ return readv(fs->fd, iov, iovcnt);
+ }
+#endif
+}
+
+static ssize_t handle_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
+ const struct iovec *iov,
+ int iovcnt, off_t offset)
+{
+ ssize_t ret;
+#ifdef CONFIG_PREADV
+ ret = pwritev(fs->fd, iov, iovcnt, offset);
+#else
+ int err = lseek(fs->fd, offset, SEEK_SET);
+ if (err == -1) {
+ return err;
+ } else {
+ ret = writev(fs->fd, iov, iovcnt);
+ }
+#endif
+#ifdef CONFIG_SYNC_FILE_RANGE
+ if (ret > 0 && ctx->export_flags & V9FS_IMMEDIATE_WRITEOUT) {
+ /*
+ * Initiate a writeback. This is not a data integrity sync.
+ * We want to ensure that we don't leave dirty pages in the cache
+ * after write when writeout=immediate is sepcified.
+ */
+ sync_file_range(fs->fd, offset, ret,
+ SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE);
+ }
+#endif
+ return ret;
+}
+
+static int handle_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)fs_ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_NONBLOCK);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = fchmod(fd, credp->fc_mode);
+ close(fd);
+ return ret;
+}
+
+static int handle_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
+ const char *name, FsCred *credp)
+{
+ int dirfd, ret;
+ struct handle_data *data = (struct handle_data *)fs_ctx->private;
+
+ dirfd = open_by_handle(data->mountfd, dir_path->data, O_PATH);
+ if (dirfd < 0) {
+ return dirfd;
+ }
+ ret = mknodat(dirfd, name, credp->fc_mode, credp->fc_rdev);
+ if (!ret) {
+ ret = handle_update_file_cred(dirfd, name, credp);
+ }
+ close(dirfd);
+ return ret;
+}
+
+static int handle_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
+ const char *name, FsCred *credp)
+{
+ int dirfd, ret;
+ struct handle_data *data = (struct handle_data *)fs_ctx->private;
+
+ dirfd = open_by_handle(data->mountfd, dir_path->data, O_PATH);
+ if (dirfd < 0) {
+ return dirfd;
+ }
+ ret = mkdirat(dirfd, name, credp->fc_mode);
+ if (!ret) {
+ ret = handle_update_file_cred(dirfd, name, credp);
+ }
+ close(dirfd);
+ return ret;
+}
+
+static int handle_fstat(FsContext *fs_ctx, int fid_type,
+ V9fsFidOpenState *fs, struct stat *stbuf)
+{
+ int fd;
+
+ if (fid_type == P9_FID_DIR) {
+ fd = dirfd(fs->dir);
+ } else {
+ fd = fs->fd;
+ }
+ return fstat(fd, stbuf);
+}
+
+static int handle_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
+ int flags, FsCred *credp, V9fsFidOpenState *fs)
+{
+ int ret;
+ int dirfd, fd;
+ struct handle_data *data = (struct handle_data *)fs_ctx->private;
+
+ dirfd = open_by_handle(data->mountfd, dir_path->data, O_PATH);
+ if (dirfd < 0) {
+ return dirfd;
+ }
+ fd = openat(dirfd, name, flags | O_NOFOLLOW, credp->fc_mode);
+ if (fd >= 0) {
+ ret = handle_update_file_cred(dirfd, name, credp);
+ if (ret < 0) {
+ close(fd);
+ fd = ret;
+ } else {
+ fs->fd = fd;
+ }
+ }
+ close(dirfd);
+ return fd;
+}
+
+
+static int handle_symlink(FsContext *fs_ctx, const char *oldpath,
+ V9fsPath *dir_path, const char *name, FsCred *credp)
+{
+ int fd, dirfd, ret;
+ struct handle_data *data = (struct handle_data *)fs_ctx->private;
+
+ dirfd = open_by_handle(data->mountfd, dir_path->data, O_PATH);
+ if (dirfd < 0) {
+ return dirfd;
+ }
+ ret = symlinkat(oldpath, dirfd, name);
+ if (!ret) {
+ fd = openat(dirfd, name, O_PATH | O_NOFOLLOW);
+ if (fd < 0) {
+ ret = fd;
+ goto err_out;
+ }
+ ret = fchownat(fd, "", credp->fc_uid, credp->fc_gid, AT_EMPTY_PATH);
+ close(fd);
+ }
+err_out:
+ close(dirfd);
+ return ret;
+}
+
+static int handle_link(FsContext *ctx, V9fsPath *oldpath,
+ V9fsPath *dirpath, const char *name)
+{
+ int oldfd, newdirfd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ oldfd = open_by_handle(data->mountfd, oldpath->data, O_PATH);
+ if (oldfd < 0) {
+ return oldfd;
+ }
+ newdirfd = open_by_handle(data->mountfd, dirpath->data, O_PATH);
+ if (newdirfd < 0) {
+ close(oldfd);
+ return newdirfd;
+ }
+ ret = linkat(oldfd, "", newdirfd, name, AT_EMPTY_PATH);
+ close(newdirfd);
+ close(oldfd);
+ return ret;
+}
+
+static int handle_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_NONBLOCK | O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = ftruncate(fd, size);
+ close(fd);
+ return ret;
+}
+
+static int handle_rename(FsContext *ctx, const char *oldpath,
+ const char *newpath)
+{
+ errno = EOPNOTSUPP;
+ return -1;
+}
+
+static int handle_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)fs_ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_PATH);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = fchownat(fd, "", credp->fc_uid, credp->fc_gid, AT_EMPTY_PATH);
+ close(fd);
+ return ret;
+}
+
+static int handle_utimensat(FsContext *ctx, V9fsPath *fs_path,
+ const struct timespec *buf)
+{
+ int ret;
+#ifdef CONFIG_UTIMENSAT
+ int fd;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_NONBLOCK);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = futimens(fd, buf);
+ close(fd);
+#else
+ ret = -1;
+ errno = ENOSYS;
+#endif
+ return ret;
+}
+
+static int handle_remove(FsContext *ctx, const char *path)
+{
+ errno = EOPNOTSUPP;
+ return -1;
+}
+
+static int handle_fsync(FsContext *ctx, int fid_type,
+ V9fsFidOpenState *fs, int datasync)
+{
+ int fd;
+
+ if (fid_type == P9_FID_DIR) {
+ fd = dirfd(fs->dir);
+ } else {
+ fd = fs->fd;
+ }
+
+ if (datasync) {
+ return qemu_fdatasync(fd);
+ } else {
+ return fsync(fd);
+ }
+}
+
+static int handle_statfs(FsContext *ctx, V9fsPath *fs_path,
+ struct statfs *stbuf)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_NONBLOCK);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = fstatfs(fd, stbuf);
+ close(fd);
+ return ret;
+}
+
+static ssize_t handle_lgetxattr(FsContext *ctx, V9fsPath *fs_path,
+ const char *name, void *value, size_t size)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_NONBLOCK);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = fgetxattr(fd, name, value, size);
+ close(fd);
+ return ret;
+}
+
+static ssize_t handle_llistxattr(FsContext *ctx, V9fsPath *fs_path,
+ void *value, size_t size)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_NONBLOCK);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = flistxattr(fd, value, size);
+ close(fd);
+ return ret;
+}
+
+static int handle_lsetxattr(FsContext *ctx, V9fsPath *fs_path, const char *name,
+ void *value, size_t size, int flags)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_NONBLOCK);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = fsetxattr(fd, name, value, size, flags);
+ close(fd);
+ return ret;
+}
+
+static int handle_lremovexattr(FsContext *ctx, V9fsPath *fs_path,
+ const char *name)
+{
+ int fd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ fd = open_by_handle(data->mountfd, fs_path->data, O_NONBLOCK);
+ if (fd < 0) {
+ return fd;
+ }
+ ret = fremovexattr(fd, name);
+ close(fd);
+ return ret;
+}
+
+static int handle_name_to_path(FsContext *ctx, V9fsPath *dir_path,
+ const char *name, V9fsPath *target)
+{
+ char *buffer;
+ struct file_handle *fh;
+ int dirfd, ret, mnt_id;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ /* "." and ".." are not allowed */
+ if (!strcmp(name, ".") || !strcmp(name, "..")) {
+ errno = EINVAL;
+ return -1;
+
+ }
+ if (dir_path) {
+ dirfd = open_by_handle(data->mountfd, dir_path->data, O_PATH);
+ } else {
+ /* relative to export root */
+ buffer = rpath(ctx, ".");
+ dirfd = open(buffer, O_DIRECTORY);
+ g_free(buffer);
+ }
+ if (dirfd < 0) {
+ return dirfd;
+ }
+ fh = g_malloc(sizeof(struct file_handle) + data->handle_bytes);
+ fh->handle_bytes = data->handle_bytes;
+ /* add a "./" at the beginning of the path */
+ buffer = g_strdup_printf("./%s", name);
+ /* flag = 0 imply don't follow symlink */
+ ret = name_to_handle(dirfd, buffer, fh, &mnt_id, 0);
+ if (!ret) {
+ target->data = (char *)fh;
+ target->size = sizeof(struct file_handle) + data->handle_bytes;
+ } else {
+ g_free(fh);
+ }
+ close(dirfd);
+ g_free(buffer);
+ return ret;
+}
+
+static int handle_renameat(FsContext *ctx, V9fsPath *olddir,
+ const char *old_name, V9fsPath *newdir,
+ const char *new_name)
+{
+ int olddirfd, newdirfd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+
+ olddirfd = open_by_handle(data->mountfd, olddir->data, O_PATH);
+ if (olddirfd < 0) {
+ return olddirfd;
+ }
+ newdirfd = open_by_handle(data->mountfd, newdir->data, O_PATH);
+ if (newdirfd < 0) {
+ close(olddirfd);
+ return newdirfd;
+ }
+ ret = renameat(olddirfd, old_name, newdirfd, new_name);
+ close(newdirfd);
+ close(olddirfd);
+ return ret;
+}
+
+static int handle_unlinkat(FsContext *ctx, V9fsPath *dir,
+ const char *name, int flags)
+{
+ int dirfd, ret;
+ struct handle_data *data = (struct handle_data *)ctx->private;
+ int rflags;
+
+ dirfd = open_by_handle(data->mountfd, dir->data, O_PATH);
+ if (dirfd < 0) {
+ return dirfd;
+ }
+
+ rflags = 0;
+ if (flags & P9_DOTL_AT_REMOVEDIR) {
+ rflags |= AT_REMOVEDIR;
+ }
+
+ ret = unlinkat(dirfd, name, rflags);
+
+ close(dirfd);
+ return ret;
+}
+
+static int handle_ioc_getversion(FsContext *ctx, V9fsPath *path,
+ mode_t st_mode, uint64_t *st_gen)
+{
+#ifdef FS_IOC_GETVERSION
+ int err;
+ V9fsFidOpenState fid_open;
+
+ /*
+ * Do not try to open special files like device nodes, fifos etc
+ * We can get fd for regular files and directories only
+ */
+ if (!S_ISREG(st_mode) && !S_ISDIR(st_mode)) {
+ errno = ENOTTY;
+ return -1;
+ }
+ err = handle_open(ctx, path, O_RDONLY, &fid_open);
+ if (err < 0) {
+ return err;
+ }
+ err = ioctl(fid_open.fd, FS_IOC_GETVERSION, st_gen);
+ handle_close(ctx, &fid_open);
+ return err;
+#else
+ errno = ENOTTY;
+ return -1;
+#endif
+}
+
+static int handle_init(FsContext *ctx)
+{
+ int ret, mnt_id;
+ struct statfs stbuf;
+ struct file_handle fh;
+ struct handle_data *data = g_malloc(sizeof(struct handle_data));
+
+ data->mountfd = open(ctx->fs_root, O_DIRECTORY);
+ if (data->mountfd < 0) {
+ ret = data->mountfd;
+ goto err_out;
+ }
+ ret = statfs(ctx->fs_root, &stbuf);
+ if (!ret) {
+ switch (stbuf.f_type) {
+ case EXT2_SUPER_MAGIC:
+ case BTRFS_SUPER_MAGIC:
+ case REISERFS_SUPER_MAGIC:
+ case XFS_SUPER_MAGIC:
+ ctx->exops.get_st_gen = handle_ioc_getversion;
+ break;
+ }
+ }
+ memset(&fh, 0, sizeof(struct file_handle));
+ ret = name_to_handle(data->mountfd, ".", &fh, &mnt_id, 0);
+ if (ret && errno == EOVERFLOW) {
+ data->handle_bytes = fh.handle_bytes;
+ ctx->private = data;
+ ret = 0;
+ goto out;
+ }
+ /* we got 0 byte handle ? */
+ ret = -1;
+ close(data->mountfd);
+err_out:
+ g_free(data);
+out:
+ return ret;
+}
+
+static int handle_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
+{
+ const char *sec_model = qemu_opt_get(opts, "security_model");
+ const char *path = qemu_opt_get(opts, "path");
+
+ if (sec_model) {
+ fprintf(stderr, "Invalid argument security_model specified with handle fsdriver\n");
+ return -1;
+ }
+
+ if (!path) {
+ fprintf(stderr, "fsdev: No path specified.\n");
+ return -1;
+ }
+ fse->path = g_strdup(path);
+ return 0;
+
+}
+
+FileOperations handle_ops = {
+ .parse_opts = handle_parse_opts,
+ .init = handle_init,
+ .lstat = handle_lstat,
+ .readlink = handle_readlink,
+ .close = handle_close,
+ .closedir = handle_closedir,
+ .open = handle_open,
+ .opendir = handle_opendir,
+ .rewinddir = handle_rewinddir,
+ .telldir = handle_telldir,
+ .readdir_r = handle_readdir_r,
+ .seekdir = handle_seekdir,
+ .preadv = handle_preadv,
+ .pwritev = handle_pwritev,
+ .chmod = handle_chmod,
+ .mknod = handle_mknod,
+ .mkdir = handle_mkdir,
+ .fstat = handle_fstat,
+ .open2 = handle_open2,
+ .symlink = handle_symlink,
+ .link = handle_link,
+ .truncate = handle_truncate,
+ .rename = handle_rename,
+ .chown = handle_chown,
+ .utimensat = handle_utimensat,
+ .remove = handle_remove,
+ .fsync = handle_fsync,
+ .statfs = handle_statfs,
+ .lgetxattr = handle_lgetxattr,
+ .llistxattr = handle_llistxattr,
+ .lsetxattr = handle_lsetxattr,
+ .lremovexattr = handle_lremovexattr,
+ .name_to_path = handle_name_to_path,
+ .renameat = handle_renameat,
+ .unlinkat = handle_unlinkat,
+};
diff --git a/hw/9pfs/virtio-9p-local.c b/hw/9pfs/virtio-9p-local.c
new file mode 100644
index 00000000..f1f2e257
--- /dev/null
+++ b/hw/9pfs/virtio-9p-local.c
@@ -0,0 +1,1280 @@
+/*
+ * Virtio 9p Posix callback
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/virtio.h"
+#include "virtio-9p.h"
+#include "virtio-9p-xattr.h"
+#include "fsdev/qemu-fsdev.h" /* local_ops */
+#include <arpa/inet.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "qemu/xattr.h"
+#include <libgen.h>
+#include <linux/fs.h>
+#ifdef CONFIG_LINUX_MAGIC_H
+#include <linux/magic.h>
+#endif
+#include <sys/ioctl.h>
+
+#ifndef XFS_SUPER_MAGIC
+#define XFS_SUPER_MAGIC 0x58465342
+#endif
+#ifndef EXT2_SUPER_MAGIC
+#define EXT2_SUPER_MAGIC 0xEF53
+#endif
+#ifndef REISERFS_SUPER_MAGIC
+#define REISERFS_SUPER_MAGIC 0x52654973
+#endif
+#ifndef BTRFS_SUPER_MAGIC
+#define BTRFS_SUPER_MAGIC 0x9123683E
+#endif
+
+#define VIRTFS_META_DIR ".virtfs_metadata"
+
+static char *local_mapped_attr_path(FsContext *ctx, const char *path)
+{
+ int dirlen;
+ const char *name = strrchr(path, '/');
+ if (name) {
+ dirlen = name - path;
+ ++name;
+ } else {
+ name = path;
+ dirlen = 0;
+ }
+ return g_strdup_printf("%s/%.*s/%s/%s", ctx->fs_root,
+ dirlen, path, VIRTFS_META_DIR, name);
+}
+
+static FILE *local_fopen(const char *path, const char *mode)
+{
+ int fd, o_mode = 0;
+ FILE *fp;
+ int flags = O_NOFOLLOW;
+ /*
+ * only supports two modes
+ */
+ if (mode[0] == 'r') {
+ flags |= O_RDONLY;
+ } else if (mode[0] == 'w') {
+ flags |= O_WRONLY | O_TRUNC | O_CREAT;
+ o_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+ } else {
+ return NULL;
+ }
+ fd = open(path, flags, o_mode);
+ if (fd == -1) {
+ return NULL;
+ }
+ fp = fdopen(fd, mode);
+ if (!fp) {
+ close(fd);
+ }
+ return fp;
+}
+
+#define ATTR_MAX 100
+static void local_mapped_file_attr(FsContext *ctx, const char *path,
+ struct stat *stbuf)
+{
+ FILE *fp;
+ char buf[ATTR_MAX];
+ char *attr_path;
+
+ attr_path = local_mapped_attr_path(ctx, path);
+ fp = local_fopen(attr_path, "r");
+ g_free(attr_path);
+ if (!fp) {
+ return;
+ }
+ memset(buf, 0, ATTR_MAX);
+ while (fgets(buf, ATTR_MAX, fp)) {
+ if (!strncmp(buf, "virtfs.uid", 10)) {
+ stbuf->st_uid = atoi(buf+11);
+ } else if (!strncmp(buf, "virtfs.gid", 10)) {
+ stbuf->st_gid = atoi(buf+11);
+ } else if (!strncmp(buf, "virtfs.mode", 11)) {
+ stbuf->st_mode = atoi(buf+12);
+ } else if (!strncmp(buf, "virtfs.rdev", 11)) {
+ stbuf->st_rdev = atoi(buf+12);
+ }
+ memset(buf, 0, ATTR_MAX);
+ }
+ fclose(fp);
+}
+
+static int local_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
+{
+ int err;
+ char *buffer;
+ char *path = fs_path->data;
+
+ buffer = rpath(fs_ctx, path);
+ err = lstat(buffer, stbuf);
+ if (err) {
+ goto err_out;
+ }
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ /* Actual credentials are part of extended attrs */
+ uid_t tmp_uid;
+ gid_t tmp_gid;
+ mode_t tmp_mode;
+ dev_t tmp_dev;
+ if (getxattr(buffer, "user.virtfs.uid", &tmp_uid, sizeof(uid_t)) > 0) {
+ stbuf->st_uid = le32_to_cpu(tmp_uid);
+ }
+ if (getxattr(buffer, "user.virtfs.gid", &tmp_gid, sizeof(gid_t)) > 0) {
+ stbuf->st_gid = le32_to_cpu(tmp_gid);
+ }
+ if (getxattr(buffer, "user.virtfs.mode",
+ &tmp_mode, sizeof(mode_t)) > 0) {
+ stbuf->st_mode = le32_to_cpu(tmp_mode);
+ }
+ if (getxattr(buffer, "user.virtfs.rdev", &tmp_dev, sizeof(dev_t)) > 0) {
+ stbuf->st_rdev = le64_to_cpu(tmp_dev);
+ }
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ local_mapped_file_attr(fs_ctx, path, stbuf);
+ }
+
+err_out:
+ g_free(buffer);
+ return err;
+}
+
+static int local_create_mapped_attr_dir(FsContext *ctx, const char *path)
+{
+ int err;
+ char *attr_dir;
+ char *tmp_path = g_strdup(path);
+
+ attr_dir = g_strdup_printf("%s/%s/%s",
+ ctx->fs_root, dirname(tmp_path), VIRTFS_META_DIR);
+
+ err = mkdir(attr_dir, 0700);
+ if (err < 0 && errno == EEXIST) {
+ err = 0;
+ }
+ g_free(attr_dir);
+ g_free(tmp_path);
+ return err;
+}
+
+static int local_set_mapped_file_attr(FsContext *ctx,
+ const char *path, FsCred *credp)
+{
+ FILE *fp;
+ int ret = 0;
+ char buf[ATTR_MAX];
+ char *attr_path;
+ int uid = -1, gid = -1, mode = -1, rdev = -1;
+
+ attr_path = local_mapped_attr_path(ctx, path);
+ fp = local_fopen(attr_path, "r");
+ if (!fp) {
+ goto create_map_file;
+ }
+ memset(buf, 0, ATTR_MAX);
+ while (fgets(buf, ATTR_MAX, fp)) {
+ if (!strncmp(buf, "virtfs.uid", 10)) {
+ uid = atoi(buf+11);
+ } else if (!strncmp(buf, "virtfs.gid", 10)) {
+ gid = atoi(buf+11);
+ } else if (!strncmp(buf, "virtfs.mode", 11)) {
+ mode = atoi(buf+12);
+ } else if (!strncmp(buf, "virtfs.rdev", 11)) {
+ rdev = atoi(buf+12);
+ }
+ memset(buf, 0, ATTR_MAX);
+ }
+ fclose(fp);
+ goto update_map_file;
+
+create_map_file:
+ ret = local_create_mapped_attr_dir(ctx, path);
+ if (ret < 0) {
+ goto err_out;
+ }
+
+update_map_file:
+ fp = local_fopen(attr_path, "w");
+ if (!fp) {
+ ret = -1;
+ goto err_out;
+ }
+
+ if (credp->fc_uid != -1) {
+ uid = credp->fc_uid;
+ }
+ if (credp->fc_gid != -1) {
+ gid = credp->fc_gid;
+ }
+ if (credp->fc_mode != -1) {
+ mode = credp->fc_mode;
+ }
+ if (credp->fc_rdev != -1) {
+ rdev = credp->fc_rdev;
+ }
+
+
+ if (uid != -1) {
+ fprintf(fp, "virtfs.uid=%d\n", uid);
+ }
+ if (gid != -1) {
+ fprintf(fp, "virtfs.gid=%d\n", gid);
+ }
+ if (mode != -1) {
+ fprintf(fp, "virtfs.mode=%d\n", mode);
+ }
+ if (rdev != -1) {
+ fprintf(fp, "virtfs.rdev=%d\n", rdev);
+ }
+ fclose(fp);
+
+err_out:
+ g_free(attr_path);
+ return ret;
+}
+
+static int local_set_xattr(const char *path, FsCred *credp)
+{
+ int err;
+
+ if (credp->fc_uid != -1) {
+ uint32_t tmp_uid = cpu_to_le32(credp->fc_uid);
+ err = setxattr(path, "user.virtfs.uid", &tmp_uid, sizeof(uid_t), 0);
+ if (err) {
+ return err;
+ }
+ }
+ if (credp->fc_gid != -1) {
+ uint32_t tmp_gid = cpu_to_le32(credp->fc_gid);
+ err = setxattr(path, "user.virtfs.gid", &tmp_gid, sizeof(gid_t), 0);
+ if (err) {
+ return err;
+ }
+ }
+ if (credp->fc_mode != -1) {
+ uint32_t tmp_mode = cpu_to_le32(credp->fc_mode);
+ err = setxattr(path, "user.virtfs.mode", &tmp_mode, sizeof(mode_t), 0);
+ if (err) {
+ return err;
+ }
+ }
+ if (credp->fc_rdev != -1) {
+ uint64_t tmp_rdev = cpu_to_le64(credp->fc_rdev);
+ err = setxattr(path, "user.virtfs.rdev", &tmp_rdev, sizeof(dev_t), 0);
+ if (err) {
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int local_post_create_passthrough(FsContext *fs_ctx, const char *path,
+ FsCred *credp)
+{
+ char *buffer;
+
+ buffer = rpath(fs_ctx, path);
+ if (lchown(buffer, credp->fc_uid, credp->fc_gid) < 0) {
+ /*
+ * If we fail to change ownership and if we are
+ * using security model none. Ignore the error
+ */
+ if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
+ goto err;
+ }
+ }
+
+ if (chmod(buffer, credp->fc_mode & 07777) < 0) {
+ goto err;
+ }
+
+ g_free(buffer);
+ return 0;
+err:
+ g_free(buffer);
+ return -1;
+}
+
+static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
+ char *buf, size_t bufsz)
+{
+ ssize_t tsize = -1;
+ char *buffer;
+ char *path = fs_path->data;
+
+ if ((fs_ctx->export_flags & V9FS_SM_MAPPED) ||
+ (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
+ int fd;
+ buffer = rpath(fs_ctx, path);
+ fd = open(buffer, O_RDONLY | O_NOFOLLOW);
+ g_free(buffer);
+ if (fd == -1) {
+ return -1;
+ }
+ do {
+ tsize = read(fd, (void *)buf, bufsz);
+ } while (tsize == -1 && errno == EINTR);
+ close(fd);
+ } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
+ (fs_ctx->export_flags & V9FS_SM_NONE)) {
+ buffer = rpath(fs_ctx, path);
+ tsize = readlink(buffer, buf, bufsz);
+ g_free(buffer);
+ }
+ return tsize;
+}
+
+static int local_close(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return close(fs->fd);
+}
+
+static int local_closedir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return closedir(fs->dir);
+}
+
+static int local_open(FsContext *ctx, V9fsPath *fs_path,
+ int flags, V9fsFidOpenState *fs)
+{
+ char *buffer;
+ char *path = fs_path->data;
+
+ buffer = rpath(ctx, path);
+ fs->fd = open(buffer, flags | O_NOFOLLOW);
+ g_free(buffer);
+ return fs->fd;
+}
+
+static int local_opendir(FsContext *ctx,
+ V9fsPath *fs_path, V9fsFidOpenState *fs)
+{
+ char *buffer;
+ char *path = fs_path->data;
+
+ buffer = rpath(ctx, path);
+ fs->dir = opendir(buffer);
+ g_free(buffer);
+ if (!fs->dir) {
+ return -1;
+ }
+ return 0;
+}
+
+static void local_rewinddir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ rewinddir(fs->dir);
+}
+
+static off_t local_telldir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return telldir(fs->dir);
+}
+
+static int local_readdir_r(FsContext *ctx, V9fsFidOpenState *fs,
+ struct dirent *entry,
+ struct dirent **result)
+{
+ int ret;
+
+again:
+ ret = readdir_r(fs->dir, entry, result);
+ if (ctx->export_flags & V9FS_SM_MAPPED) {
+ entry->d_type = DT_UNKNOWN;
+ } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ if (!ret && *result != NULL &&
+ !strcmp(entry->d_name, VIRTFS_META_DIR)) {
+ /* skp the meta data directory */
+ goto again;
+ }
+ entry->d_type = DT_UNKNOWN;
+ }
+ return ret;
+}
+
+static void local_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
+{
+ seekdir(fs->dir, off);
+}
+
+static ssize_t local_preadv(FsContext *ctx, V9fsFidOpenState *fs,
+ const struct iovec *iov,
+ int iovcnt, off_t offset)
+{
+#ifdef CONFIG_PREADV
+ return preadv(fs->fd, iov, iovcnt, offset);
+#else
+ int err = lseek(fs->fd, offset, SEEK_SET);
+ if (err == -1) {
+ return err;
+ } else {
+ return readv(fs->fd, iov, iovcnt);
+ }
+#endif
+}
+
+static ssize_t local_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
+ const struct iovec *iov,
+ int iovcnt, off_t offset)
+{
+ ssize_t ret
+;
+#ifdef CONFIG_PREADV
+ ret = pwritev(fs->fd, iov, iovcnt, offset);
+#else
+ int err = lseek(fs->fd, offset, SEEK_SET);
+ if (err == -1) {
+ return err;
+ } else {
+ ret = writev(fs->fd, iov, iovcnt);
+ }
+#endif
+#ifdef CONFIG_SYNC_FILE_RANGE
+ if (ret > 0 && ctx->export_flags & V9FS_IMMEDIATE_WRITEOUT) {
+ /*
+ * Initiate a writeback. This is not a data integrity sync.
+ * We want to ensure that we don't leave dirty pages in the cache
+ * after write when writeout=immediate is sepcified.
+ */
+ sync_file_range(fs->fd, offset, ret,
+ SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE);
+ }
+#endif
+ return ret;
+}
+
+static int local_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
+{
+ char *buffer;
+ int ret = -1;
+ char *path = fs_path->data;
+
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ buffer = rpath(fs_ctx, path);
+ ret = local_set_xattr(buffer, credp);
+ g_free(buffer);
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ return local_set_mapped_file_attr(fs_ctx, path, credp);
+ } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
+ (fs_ctx->export_flags & V9FS_SM_NONE)) {
+ buffer = rpath(fs_ctx, path);
+ ret = chmod(buffer, credp->fc_mode);
+ g_free(buffer);
+ }
+ return ret;
+}
+
+static int local_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
+ const char *name, FsCred *credp)
+{
+ char *path;
+ int err = -1;
+ int serrno = 0;
+ V9fsString fullname;
+ char *buffer = NULL;
+
+ v9fs_string_init(&fullname);
+ v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
+ path = fullname.data;
+
+ /* Determine the security model */
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ buffer = rpath(fs_ctx, path);
+ err = mknod(buffer, SM_LOCAL_MODE_BITS|S_IFREG, 0);
+ if (err == -1) {
+ goto out;
+ }
+ err = local_set_xattr(buffer, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+
+ buffer = rpath(fs_ctx, path);
+ err = mknod(buffer, SM_LOCAL_MODE_BITS|S_IFREG, 0);
+ if (err == -1) {
+ goto out;
+ }
+ err = local_set_mapped_file_attr(fs_ctx, path, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
+ (fs_ctx->export_flags & V9FS_SM_NONE)) {
+ buffer = rpath(fs_ctx, path);
+ err = mknod(buffer, credp->fc_mode, credp->fc_rdev);
+ if (err == -1) {
+ goto out;
+ }
+ err = local_post_create_passthrough(fs_ctx, path, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ }
+ goto out;
+
+err_end:
+ remove(buffer);
+ errno = serrno;
+out:
+ g_free(buffer);
+ v9fs_string_free(&fullname);
+ return err;
+}
+
+static int local_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
+ const char *name, FsCred *credp)
+{
+ char *path;
+ int err = -1;
+ int serrno = 0;
+ V9fsString fullname;
+ char *buffer = NULL;
+
+ v9fs_string_init(&fullname);
+ v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
+ path = fullname.data;
+
+ /* Determine the security model */
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ buffer = rpath(fs_ctx, path);
+ err = mkdir(buffer, SM_LOCAL_DIR_MODE_BITS);
+ if (err == -1) {
+ goto out;
+ }
+ credp->fc_mode = credp->fc_mode|S_IFDIR;
+ err = local_set_xattr(buffer, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ buffer = rpath(fs_ctx, path);
+ err = mkdir(buffer, SM_LOCAL_DIR_MODE_BITS);
+ if (err == -1) {
+ goto out;
+ }
+ credp->fc_mode = credp->fc_mode|S_IFDIR;
+ err = local_set_mapped_file_attr(fs_ctx, path, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
+ (fs_ctx->export_flags & V9FS_SM_NONE)) {
+ buffer = rpath(fs_ctx, path);
+ err = mkdir(buffer, credp->fc_mode);
+ if (err == -1) {
+ goto out;
+ }
+ err = local_post_create_passthrough(fs_ctx, path, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ }
+ goto out;
+
+err_end:
+ remove(buffer);
+ errno = serrno;
+out:
+ g_free(buffer);
+ v9fs_string_free(&fullname);
+ return err;
+}
+
+static int local_fstat(FsContext *fs_ctx, int fid_type,
+ V9fsFidOpenState *fs, struct stat *stbuf)
+{
+ int err, fd;
+
+ if (fid_type == P9_FID_DIR) {
+ fd = dirfd(fs->dir);
+ } else {
+ fd = fs->fd;
+ }
+
+ err = fstat(fd, stbuf);
+ if (err) {
+ return err;
+ }
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ /* Actual credentials are part of extended attrs */
+ uid_t tmp_uid;
+ gid_t tmp_gid;
+ mode_t tmp_mode;
+ dev_t tmp_dev;
+
+ if (fgetxattr(fd, "user.virtfs.uid", &tmp_uid, sizeof(uid_t)) > 0) {
+ stbuf->st_uid = le32_to_cpu(tmp_uid);
+ }
+ if (fgetxattr(fd, "user.virtfs.gid", &tmp_gid, sizeof(gid_t)) > 0) {
+ stbuf->st_gid = le32_to_cpu(tmp_gid);
+ }
+ if (fgetxattr(fd, "user.virtfs.mode", &tmp_mode, sizeof(mode_t)) > 0) {
+ stbuf->st_mode = le32_to_cpu(tmp_mode);
+ }
+ if (fgetxattr(fd, "user.virtfs.rdev", &tmp_dev, sizeof(dev_t)) > 0) {
+ stbuf->st_rdev = le64_to_cpu(tmp_dev);
+ }
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+ return err;
+}
+
+static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
+ int flags, FsCred *credp, V9fsFidOpenState *fs)
+{
+ char *path;
+ int fd = -1;
+ int err = -1;
+ int serrno = 0;
+ V9fsString fullname;
+ char *buffer = NULL;
+
+ /*
+ * Mark all the open to not follow symlinks
+ */
+ flags |= O_NOFOLLOW;
+
+ v9fs_string_init(&fullname);
+ v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
+ path = fullname.data;
+
+ /* Determine the security model */
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ buffer = rpath(fs_ctx, path);
+ fd = open(buffer, flags, SM_LOCAL_MODE_BITS);
+ if (fd == -1) {
+ err = fd;
+ goto out;
+ }
+ credp->fc_mode = credp->fc_mode|S_IFREG;
+ /* Set cleint credentials in xattr */
+ err = local_set_xattr(buffer, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ buffer = rpath(fs_ctx, path);
+ fd = open(buffer, flags, SM_LOCAL_MODE_BITS);
+ if (fd == -1) {
+ err = fd;
+ goto out;
+ }
+ credp->fc_mode = credp->fc_mode|S_IFREG;
+ /* Set client credentials in .virtfs_metadata directory files */
+ err = local_set_mapped_file_attr(fs_ctx, path, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
+ (fs_ctx->export_flags & V9FS_SM_NONE)) {
+ buffer = rpath(fs_ctx, path);
+ fd = open(buffer, flags, credp->fc_mode);
+ if (fd == -1) {
+ err = fd;
+ goto out;
+ }
+ err = local_post_create_passthrough(fs_ctx, path, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ }
+ err = fd;
+ fs->fd = fd;
+ goto out;
+
+err_end:
+ close(fd);
+ remove(buffer);
+ errno = serrno;
+out:
+ g_free(buffer);
+ v9fs_string_free(&fullname);
+ return err;
+}
+
+
+static int local_symlink(FsContext *fs_ctx, const char *oldpath,
+ V9fsPath *dir_path, const char *name, FsCred *credp)
+{
+ int err = -1;
+ int serrno = 0;
+ char *newpath;
+ V9fsString fullname;
+ char *buffer = NULL;
+
+ v9fs_string_init(&fullname);
+ v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
+ newpath = fullname.data;
+
+ /* Determine the security model */
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ int fd;
+ ssize_t oldpath_size, write_size;
+ buffer = rpath(fs_ctx, newpath);
+ fd = open(buffer, O_CREAT|O_EXCL|O_RDWR|O_NOFOLLOW, SM_LOCAL_MODE_BITS);
+ if (fd == -1) {
+ err = fd;
+ goto out;
+ }
+ /* Write the oldpath (target) to the file. */
+ oldpath_size = strlen(oldpath);
+ do {
+ write_size = write(fd, (void *)oldpath, oldpath_size);
+ } while (write_size == -1 && errno == EINTR);
+
+ if (write_size != oldpath_size) {
+ serrno = errno;
+ close(fd);
+ err = -1;
+ goto err_end;
+ }
+ close(fd);
+ /* Set cleint credentials in symlink's xattr */
+ credp->fc_mode = credp->fc_mode|S_IFLNK;
+ err = local_set_xattr(buffer, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ int fd;
+ ssize_t oldpath_size, write_size;
+ buffer = rpath(fs_ctx, newpath);
+ fd = open(buffer, O_CREAT|O_EXCL|O_RDWR|O_NOFOLLOW, SM_LOCAL_MODE_BITS);
+ if (fd == -1) {
+ err = fd;
+ goto out;
+ }
+ /* Write the oldpath (target) to the file. */
+ oldpath_size = strlen(oldpath);
+ do {
+ write_size = write(fd, (void *)oldpath, oldpath_size);
+ } while (write_size == -1 && errno == EINTR);
+
+ if (write_size != oldpath_size) {
+ serrno = errno;
+ close(fd);
+ err = -1;
+ goto err_end;
+ }
+ close(fd);
+ /* Set cleint credentials in symlink's xattr */
+ credp->fc_mode = credp->fc_mode|S_IFLNK;
+ err = local_set_mapped_file_attr(fs_ctx, newpath, credp);
+ if (err == -1) {
+ serrno = errno;
+ goto err_end;
+ }
+ } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
+ (fs_ctx->export_flags & V9FS_SM_NONE)) {
+ buffer = rpath(fs_ctx, newpath);
+ err = symlink(oldpath, buffer);
+ if (err) {
+ goto out;
+ }
+ err = lchown(buffer, credp->fc_uid, credp->fc_gid);
+ if (err == -1) {
+ /*
+ * If we fail to change ownership and if we are
+ * using security model none. Ignore the error
+ */
+ if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
+ serrno = errno;
+ goto err_end;
+ } else
+ err = 0;
+ }
+ }
+ goto out;
+
+err_end:
+ remove(buffer);
+ errno = serrno;
+out:
+ g_free(buffer);
+ v9fs_string_free(&fullname);
+ return err;
+}
+
+static int local_link(FsContext *ctx, V9fsPath *oldpath,
+ V9fsPath *dirpath, const char *name)
+{
+ int ret;
+ V9fsString newpath;
+ char *buffer, *buffer1;
+
+ v9fs_string_init(&newpath);
+ v9fs_string_sprintf(&newpath, "%s/%s", dirpath->data, name);
+
+ buffer = rpath(ctx, oldpath->data);
+ buffer1 = rpath(ctx, newpath.data);
+ ret = link(buffer, buffer1);
+ g_free(buffer);
+ g_free(buffer1);
+
+ /* now link the virtfs_metadata files */
+ if (!ret && (ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
+ /* Link the .virtfs_metadata files. Create the metada directory */
+ ret = local_create_mapped_attr_dir(ctx, newpath.data);
+ if (ret < 0) {
+ goto err_out;
+ }
+ buffer = local_mapped_attr_path(ctx, oldpath->data);
+ buffer1 = local_mapped_attr_path(ctx, newpath.data);
+ ret = link(buffer, buffer1);
+ g_free(buffer);
+ g_free(buffer1);
+ if (ret < 0 && errno != ENOENT) {
+ goto err_out;
+ }
+ }
+err_out:
+ v9fs_string_free(&newpath);
+ return ret;
+}
+
+static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
+{
+ char *buffer;
+ int ret;
+ char *path = fs_path->data;
+
+ buffer = rpath(ctx, path);
+ ret = truncate(buffer, size);
+ g_free(buffer);
+ return ret;
+}
+
+static int local_rename(FsContext *ctx, const char *oldpath,
+ const char *newpath)
+{
+ int err;
+ char *buffer, *buffer1;
+
+ if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ err = local_create_mapped_attr_dir(ctx, newpath);
+ if (err < 0) {
+ return err;
+ }
+ /* rename the .virtfs_metadata files */
+ buffer = local_mapped_attr_path(ctx, oldpath);
+ buffer1 = local_mapped_attr_path(ctx, newpath);
+ err = rename(buffer, buffer1);
+ g_free(buffer);
+ g_free(buffer1);
+ if (err < 0 && errno != ENOENT) {
+ return err;
+ }
+ }
+
+ buffer = rpath(ctx, oldpath);
+ buffer1 = rpath(ctx, newpath);
+ err = rename(buffer, buffer1);
+ g_free(buffer);
+ g_free(buffer1);
+ return err;
+}
+
+static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
+{
+ char *buffer;
+ int ret = -1;
+ char *path = fs_path->data;
+
+ if ((credp->fc_uid == -1 && credp->fc_gid == -1) ||
+ (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
+ (fs_ctx->export_flags & V9FS_SM_NONE)) {
+ buffer = rpath(fs_ctx, path);
+ ret = lchown(buffer, credp->fc_uid, credp->fc_gid);
+ g_free(buffer);
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ buffer = rpath(fs_ctx, path);
+ ret = local_set_xattr(buffer, credp);
+ g_free(buffer);
+ } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ return local_set_mapped_file_attr(fs_ctx, path, credp);
+ }
+ return ret;
+}
+
+static int local_utimensat(FsContext *s, V9fsPath *fs_path,
+ const struct timespec *buf)
+{
+ char *buffer;
+ int ret;
+ char *path = fs_path->data;
+
+ buffer = rpath(s, path);
+ ret = qemu_utimens(buffer, buf);
+ g_free(buffer);
+ return ret;
+}
+
+static int local_remove(FsContext *ctx, const char *path)
+{
+ int err;
+ struct stat stbuf;
+ char *buffer;
+
+ if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ buffer = rpath(ctx, path);
+ err = lstat(buffer, &stbuf);
+ g_free(buffer);
+ if (err) {
+ goto err_out;
+ }
+ /*
+ * If directory remove .virtfs_metadata contained in the
+ * directory
+ */
+ if (S_ISDIR(stbuf.st_mode)) {
+ buffer = g_strdup_printf("%s/%s/%s", ctx->fs_root,
+ path, VIRTFS_META_DIR);
+ err = remove(buffer);
+ g_free(buffer);
+ if (err < 0 && errno != ENOENT) {
+ /*
+ * We didn't had the .virtfs_metadata file. May be file created
+ * in non-mapped mode ?. Ignore ENOENT.
+ */
+ goto err_out;
+ }
+ }
+ /*
+ * Now remove the name from parent directory
+ * .virtfs_metadata directory
+ */
+ buffer = local_mapped_attr_path(ctx, path);
+ err = remove(buffer);
+ g_free(buffer);
+ if (err < 0 && errno != ENOENT) {
+ /*
+ * We didn't had the .virtfs_metadata file. May be file created
+ * in non-mapped mode ?. Ignore ENOENT.
+ */
+ goto err_out;
+ }
+ }
+
+ buffer = rpath(ctx, path);
+ err = remove(buffer);
+ g_free(buffer);
+err_out:
+ return err;
+}
+
+static int local_fsync(FsContext *ctx, int fid_type,
+ V9fsFidOpenState *fs, int datasync)
+{
+ int fd;
+
+ if (fid_type == P9_FID_DIR) {
+ fd = dirfd(fs->dir);
+ } else {
+ fd = fs->fd;
+ }
+
+ if (datasync) {
+ return qemu_fdatasync(fd);
+ } else {
+ return fsync(fd);
+ }
+}
+
+static int local_statfs(FsContext *s, V9fsPath *fs_path, struct statfs *stbuf)
+{
+ char *buffer;
+ int ret;
+ char *path = fs_path->data;
+
+ buffer = rpath(s, path);
+ ret = statfs(buffer, stbuf);
+ g_free(buffer);
+ return ret;
+}
+
+static ssize_t local_lgetxattr(FsContext *ctx, V9fsPath *fs_path,
+ const char *name, void *value, size_t size)
+{
+ char *path = fs_path->data;
+
+ return v9fs_get_xattr(ctx, path, name, value, size);
+}
+
+static ssize_t local_llistxattr(FsContext *ctx, V9fsPath *fs_path,
+ void *value, size_t size)
+{
+ char *path = fs_path->data;
+
+ return v9fs_list_xattr(ctx, path, value, size);
+}
+
+static int local_lsetxattr(FsContext *ctx, V9fsPath *fs_path, const char *name,
+ void *value, size_t size, int flags)
+{
+ char *path = fs_path->data;
+
+ return v9fs_set_xattr(ctx, path, name, value, size, flags);
+}
+
+static int local_lremovexattr(FsContext *ctx, V9fsPath *fs_path,
+ const char *name)
+{
+ char *path = fs_path->data;
+
+ return v9fs_remove_xattr(ctx, path, name);
+}
+
+static int local_name_to_path(FsContext *ctx, V9fsPath *dir_path,
+ const char *name, V9fsPath *target)
+{
+ if (dir_path) {
+ v9fs_string_sprintf((V9fsString *)target, "%s/%s",
+ dir_path->data, name);
+ } else {
+ v9fs_string_sprintf((V9fsString *)target, "%s", name);
+ }
+ /* Bump the size for including terminating NULL */
+ target->size++;
+ return 0;
+}
+
+static int local_renameat(FsContext *ctx, V9fsPath *olddir,
+ const char *old_name, V9fsPath *newdir,
+ const char *new_name)
+{
+ int ret;
+ V9fsString old_full_name, new_full_name;
+
+ v9fs_string_init(&old_full_name);
+ v9fs_string_init(&new_full_name);
+
+ v9fs_string_sprintf(&old_full_name, "%s/%s", olddir->data, old_name);
+ v9fs_string_sprintf(&new_full_name, "%s/%s", newdir->data, new_name);
+
+ ret = local_rename(ctx, old_full_name.data, new_full_name.data);
+ v9fs_string_free(&old_full_name);
+ v9fs_string_free(&new_full_name);
+ return ret;
+}
+
+static int local_unlinkat(FsContext *ctx, V9fsPath *dir,
+ const char *name, int flags)
+{
+ int ret;
+ V9fsString fullname;
+ char *buffer;
+
+ v9fs_string_init(&fullname);
+
+ v9fs_string_sprintf(&fullname, "%s/%s", dir->data, name);
+ if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ if (flags == AT_REMOVEDIR) {
+ /*
+ * If directory remove .virtfs_metadata contained in the
+ * directory
+ */
+ buffer = g_strdup_printf("%s/%s/%s", ctx->fs_root,
+ fullname.data, VIRTFS_META_DIR);
+ ret = remove(buffer);
+ g_free(buffer);
+ if (ret < 0 && errno != ENOENT) {
+ /*
+ * We didn't had the .virtfs_metadata file. May be file created
+ * in non-mapped mode ?. Ignore ENOENT.
+ */
+ goto err_out;
+ }
+ }
+ /*
+ * Now remove the name from parent directory
+ * .virtfs_metadata directory.
+ */
+ buffer = local_mapped_attr_path(ctx, fullname.data);
+ ret = remove(buffer);
+ g_free(buffer);
+ if (ret < 0 && errno != ENOENT) {
+ /*
+ * We didn't had the .virtfs_metadata file. May be file created
+ * in non-mapped mode ?. Ignore ENOENT.
+ */
+ goto err_out;
+ }
+ }
+ /* Remove the name finally */
+ buffer = rpath(ctx, fullname.data);
+ ret = remove(buffer);
+ g_free(buffer);
+
+err_out:
+ v9fs_string_free(&fullname);
+ return ret;
+}
+
+static int local_ioc_getversion(FsContext *ctx, V9fsPath *path,
+ mode_t st_mode, uint64_t *st_gen)
+{
+#ifdef FS_IOC_GETVERSION
+ int err;
+ V9fsFidOpenState fid_open;
+
+ /*
+ * Do not try to open special files like device nodes, fifos etc
+ * We can get fd for regular files and directories only
+ */
+ if (!S_ISREG(st_mode) && !S_ISDIR(st_mode)) {
+ errno = ENOTTY;
+ return -1;
+ }
+ err = local_open(ctx, path, O_RDONLY, &fid_open);
+ if (err < 0) {
+ return err;
+ }
+ err = ioctl(fid_open.fd, FS_IOC_GETVERSION, st_gen);
+ local_close(ctx, &fid_open);
+ return err;
+#else
+ errno = ENOTTY;
+ return -1;
+#endif
+}
+
+static int local_init(FsContext *ctx)
+{
+ int err = 0;
+ struct statfs stbuf;
+
+ if (ctx->export_flags & V9FS_SM_PASSTHROUGH) {
+ ctx->xops = passthrough_xattr_ops;
+ } else if (ctx->export_flags & V9FS_SM_MAPPED) {
+ ctx->xops = mapped_xattr_ops;
+ } else if (ctx->export_flags & V9FS_SM_NONE) {
+ ctx->xops = none_xattr_ops;
+ } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ /*
+ * xattr operation for mapped-file and passthrough
+ * remain same.
+ */
+ ctx->xops = passthrough_xattr_ops;
+ }
+ ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
+#ifdef FS_IOC_GETVERSION
+ /*
+ * use ioc_getversion only if the iocl is definied
+ */
+ err = statfs(ctx->fs_root, &stbuf);
+ if (!err) {
+ switch (stbuf.f_type) {
+ case EXT2_SUPER_MAGIC:
+ case BTRFS_SUPER_MAGIC:
+ case REISERFS_SUPER_MAGIC:
+ case XFS_SUPER_MAGIC:
+ ctx->exops.get_st_gen = local_ioc_getversion;
+ break;
+ }
+ }
+#endif
+ return err;
+}
+
+static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
+{
+ const char *sec_model = qemu_opt_get(opts, "security_model");
+ const char *path = qemu_opt_get(opts, "path");
+
+ if (!sec_model) {
+ fprintf(stderr, "security model not specified, "
+ "local fs needs security model\nvalid options are:"
+ "\tsecurity_model=[passthrough|mapped|none]\n");
+ return -1;
+ }
+
+ if (!strcmp(sec_model, "passthrough")) {
+ fse->export_flags |= V9FS_SM_PASSTHROUGH;
+ } else if (!strcmp(sec_model, "mapped") ||
+ !strcmp(sec_model, "mapped-xattr")) {
+ fse->export_flags |= V9FS_SM_MAPPED;
+ } else if (!strcmp(sec_model, "none")) {
+ fse->export_flags |= V9FS_SM_NONE;
+ } else if (!strcmp(sec_model, "mapped-file")) {
+ fse->export_flags |= V9FS_SM_MAPPED_FILE;
+ } else {
+ fprintf(stderr, "Invalid security model %s specified, valid options are"
+ "\n\t [passthrough|mapped-xattr|mapped-file|none]\n",
+ sec_model);
+ return -1;
+ }
+
+ if (!path) {
+ fprintf(stderr, "fsdev: No path specified.\n");
+ return -1;
+ }
+ fse->path = g_strdup(path);
+
+ return 0;
+}
+
+FileOperations local_ops = {
+ .parse_opts = local_parse_opts,
+ .init = local_init,
+ .lstat = local_lstat,
+ .readlink = local_readlink,
+ .close = local_close,
+ .closedir = local_closedir,
+ .open = local_open,
+ .opendir = local_opendir,
+ .rewinddir = local_rewinddir,
+ .telldir = local_telldir,
+ .readdir_r = local_readdir_r,
+ .seekdir = local_seekdir,
+ .preadv = local_preadv,
+ .pwritev = local_pwritev,
+ .chmod = local_chmod,
+ .mknod = local_mknod,
+ .mkdir = local_mkdir,
+ .fstat = local_fstat,
+ .open2 = local_open2,
+ .symlink = local_symlink,
+ .link = local_link,
+ .truncate = local_truncate,
+ .rename = local_rename,
+ .chown = local_chown,
+ .utimensat = local_utimensat,
+ .remove = local_remove,
+ .fsync = local_fsync,
+ .statfs = local_statfs,
+ .lgetxattr = local_lgetxattr,
+ .llistxattr = local_llistxattr,
+ .lsetxattr = local_lsetxattr,
+ .lremovexattr = local_lremovexattr,
+ .name_to_path = local_name_to_path,
+ .renameat = local_renameat,
+ .unlinkat = local_unlinkat,
+};
diff --git a/hw/9pfs/virtio-9p-posix-acl.c b/hw/9pfs/virtio-9p-posix-acl.c
new file mode 100644
index 00000000..09dad071
--- /dev/null
+++ b/hw/9pfs/virtio-9p-posix-acl.c
@@ -0,0 +1,185 @@
+/*
+ * Virtio 9p system.posix* xattr callback
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include <sys/types.h>
+#include "qemu/xattr.h"
+#include "hw/virtio/virtio.h"
+#include "virtio-9p.h"
+#include "fsdev/file-op-9p.h"
+#include "virtio-9p-xattr.h"
+
+#define MAP_ACL_ACCESS "user.virtfs.system.posix_acl_access"
+#define MAP_ACL_DEFAULT "user.virtfs.system.posix_acl_default"
+#define ACL_ACCESS "system.posix_acl_access"
+#define ACL_DEFAULT "system.posix_acl_default"
+
+static ssize_t mp_pacl_getxattr(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size)
+{
+ char *buffer;
+ ssize_t ret;
+
+ buffer = rpath(ctx, path);
+ ret = lgetxattr(buffer, MAP_ACL_ACCESS, value, size);
+ g_free(buffer);
+ return ret;
+}
+
+static ssize_t mp_pacl_listxattr(FsContext *ctx, const char *path,
+ char *name, void *value, size_t osize)
+{
+ ssize_t len = sizeof(ACL_ACCESS);
+
+ if (!value) {
+ return len;
+ }
+
+ if (osize < len) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* len includes the trailing NUL */
+ memcpy(value, ACL_ACCESS, len);
+ return 0;
+}
+
+static int mp_pacl_setxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size, int flags)
+{
+ char *buffer;
+ int ret;
+
+ buffer = rpath(ctx, path);
+ ret = lsetxattr(buffer, MAP_ACL_ACCESS, value, size, flags);
+ g_free(buffer);
+ return ret;
+}
+
+static int mp_pacl_removexattr(FsContext *ctx,
+ const char *path, const char *name)
+{
+ int ret;
+ char *buffer;
+
+ buffer = rpath(ctx, path);
+ ret = lremovexattr(buffer, MAP_ACL_ACCESS);
+ if (ret == -1 && errno == ENODATA) {
+ /*
+ * We don't get ENODATA error when trying to remove a
+ * posix acl that is not present. So don't throw the error
+ * even in case of mapped security model
+ */
+ errno = 0;
+ ret = 0;
+ }
+ g_free(buffer);
+ return ret;
+}
+
+static ssize_t mp_dacl_getxattr(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size)
+{
+ char *buffer;
+ ssize_t ret;
+
+ buffer = rpath(ctx, path);
+ ret = lgetxattr(buffer, MAP_ACL_DEFAULT, value, size);
+ g_free(buffer);
+ return ret;
+}
+
+static ssize_t mp_dacl_listxattr(FsContext *ctx, const char *path,
+ char *name, void *value, size_t osize)
+{
+ ssize_t len = sizeof(ACL_DEFAULT);
+
+ if (!value) {
+ return len;
+ }
+
+ if (osize < len) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* len includes the trailing NUL */
+ memcpy(value, ACL_DEFAULT, len);
+ return 0;
+}
+
+static int mp_dacl_setxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size, int flags)
+{
+ char *buffer;
+ int ret;
+
+ buffer = rpath(ctx, path);
+ ret = lsetxattr(buffer, MAP_ACL_DEFAULT, value, size, flags);
+ g_free(buffer);
+ return ret;
+}
+
+static int mp_dacl_removexattr(FsContext *ctx,
+ const char *path, const char *name)
+{
+ int ret;
+ char *buffer;
+
+ buffer = rpath(ctx, path);
+ ret = lremovexattr(buffer, MAP_ACL_DEFAULT);
+ if (ret == -1 && errno == ENODATA) {
+ /*
+ * We don't get ENODATA error when trying to remove a
+ * posix acl that is not present. So don't throw the error
+ * even in case of mapped security model
+ */
+ errno = 0;
+ ret = 0;
+ }
+ g_free(buffer);
+ return ret;
+}
+
+
+XattrOperations mapped_pacl_xattr = {
+ .name = "system.posix_acl_access",
+ .getxattr = mp_pacl_getxattr,
+ .setxattr = mp_pacl_setxattr,
+ .listxattr = mp_pacl_listxattr,
+ .removexattr = mp_pacl_removexattr,
+};
+
+XattrOperations mapped_dacl_xattr = {
+ .name = "system.posix_acl_default",
+ .getxattr = mp_dacl_getxattr,
+ .setxattr = mp_dacl_setxattr,
+ .listxattr = mp_dacl_listxattr,
+ .removexattr = mp_dacl_removexattr,
+};
+
+XattrOperations passthrough_acl_xattr = {
+ .name = "system.posix_acl_",
+ .getxattr = pt_getxattr,
+ .setxattr = pt_setxattr,
+ .listxattr = pt_listxattr,
+ .removexattr = pt_removexattr,
+};
+
+XattrOperations none_acl_xattr = {
+ .name = "system.posix_acl_",
+ .getxattr = notsup_getxattr,
+ .setxattr = notsup_setxattr,
+ .listxattr = notsup_listxattr,
+ .removexattr = notsup_removexattr,
+};
diff --git a/hw/9pfs/virtio-9p-proxy.c b/hw/9pfs/virtio-9p-proxy.c
new file mode 100644
index 00000000..1bc7881f
--- /dev/null
+++ b/hw/9pfs/virtio-9p-proxy.c
@@ -0,0 +1,1219 @@
+/*
+ * Virtio 9p Proxy callback
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * M. Mohan Kumar <mohan@in.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ */
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "hw/virtio/virtio.h"
+#include "virtio-9p.h"
+#include "qemu/error-report.h"
+#include "fsdev/qemu-fsdev.h"
+#include "virtio-9p-proxy.h"
+
+typedef struct V9fsProxy {
+ int sockfd;
+ QemuMutex mutex;
+ struct iovec in_iovec;
+ struct iovec out_iovec;
+} V9fsProxy;
+
+/*
+ * Return received file descriptor on success in *status.
+ * errno is also returned on *status (which will be < 0)
+ * return < 0 on transport error.
+ */
+static int v9fs_receivefd(int sockfd, int *status)
+{
+ struct iovec iov;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ int retval, data, fd;
+ union MsgControl msg_control;
+
+ iov.iov_base = &data;
+ iov.iov_len = sizeof(data);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &msg_control;
+ msg.msg_controllen = sizeof(msg_control);
+
+ do {
+ retval = recvmsg(sockfd, &msg, 0);
+ } while (retval < 0 && errno == EINTR);
+ if (retval <= 0) {
+ return retval;
+ }
+ /*
+ * data is set to V9FS_FD_VALID, if ancillary data is sent. If this
+ * request doesn't need ancillary data (fd) or an error occurred,
+ * data is set to negative errno value.
+ */
+ if (data != V9FS_FD_VALID) {
+ *status = data;
+ return 0;
+ }
+ /*
+ * File descriptor (fd) is sent in the ancillary data. Check if we
+ * indeed received it. One of the reasons to fail to receive it is if
+ * we exceeded the maximum number of file descriptors!
+ */
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
+ cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS) {
+ continue;
+ }
+ fd = *((int *)CMSG_DATA(cmsg));
+ *status = fd;
+ return 0;
+ }
+ *status = -ENFILE; /* Ancillary data sent but not received */
+ return 0;
+}
+
+static ssize_t socket_read(int sockfd, void *buff, size_t size)
+{
+ ssize_t retval, total = 0;
+
+ while (size) {
+ retval = read(sockfd, buff, size);
+ if (retval == 0) {
+ return -EIO;
+ }
+ if (retval < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return -errno;
+ }
+ size -= retval;
+ buff += retval;
+ total += retval;
+ }
+ return total;
+}
+
+/* Converts proxy_statfs to VFS statfs structure */
+static void prstatfs_to_statfs(struct statfs *stfs, ProxyStatFS *prstfs)
+{
+ memset(stfs, 0, sizeof(*stfs));
+ stfs->f_type = prstfs->f_type;
+ stfs->f_bsize = prstfs->f_bsize;
+ stfs->f_blocks = prstfs->f_blocks;
+ stfs->f_bfree = prstfs->f_bfree;
+ stfs->f_bavail = prstfs->f_bavail;
+ stfs->f_files = prstfs->f_files;
+ stfs->f_ffree = prstfs->f_ffree;
+ stfs->f_fsid.__val[0] = prstfs->f_fsid[0] & 0xFFFFFFFFU;
+ stfs->f_fsid.__val[1] = prstfs->f_fsid[1] >> 32 & 0xFFFFFFFFU;
+ stfs->f_namelen = prstfs->f_namelen;
+ stfs->f_frsize = prstfs->f_frsize;
+}
+
+/* Converts proxy_stat structure to VFS stat structure */
+static void prstat_to_stat(struct stat *stbuf, ProxyStat *prstat)
+{
+ memset(stbuf, 0, sizeof(*stbuf));
+ stbuf->st_dev = prstat->st_dev;
+ stbuf->st_ino = prstat->st_ino;
+ stbuf->st_nlink = prstat->st_nlink;
+ stbuf->st_mode = prstat->st_mode;
+ stbuf->st_uid = prstat->st_uid;
+ stbuf->st_gid = prstat->st_gid;
+ stbuf->st_rdev = prstat->st_rdev;
+ stbuf->st_size = prstat->st_size;
+ stbuf->st_blksize = prstat->st_blksize;
+ stbuf->st_blocks = prstat->st_blocks;
+ stbuf->st_atim.tv_sec = prstat->st_atim_sec;
+ stbuf->st_atim.tv_nsec = prstat->st_atim_nsec;
+ stbuf->st_mtime = prstat->st_mtim_sec;
+ stbuf->st_mtim.tv_nsec = prstat->st_mtim_nsec;
+ stbuf->st_ctime = prstat->st_ctim_sec;
+ stbuf->st_ctim.tv_nsec = prstat->st_ctim_nsec;
+}
+
+/*
+ * Response contains two parts
+ * {header, data}
+ * header.type == T_ERROR, data -> -errno
+ * header.type == T_SUCCESS, data -> response
+ * size of errno/response is given by header.size
+ * returns < 0, on transport error. response is
+ * valid only if status >= 0.
+ */
+static int v9fs_receive_response(V9fsProxy *proxy, int type,
+ int *status, void *response)
+{
+ int retval;
+ ProxyHeader header;
+ struct iovec *reply = &proxy->in_iovec;
+
+ *status = 0;
+ reply->iov_len = 0;
+ retval = socket_read(proxy->sockfd, reply->iov_base, PROXY_HDR_SZ);
+ if (retval < 0) {
+ return retval;
+ }
+ reply->iov_len = PROXY_HDR_SZ;
+ proxy_unmarshal(reply, 0, "dd", &header.type, &header.size);
+ /*
+ * if response size > PROXY_MAX_IO_SZ, read the response but ignore it and
+ * return -ENOBUFS
+ */
+ if (header.size > PROXY_MAX_IO_SZ) {
+ int count;
+ while (header.size > 0) {
+ count = MIN(PROXY_MAX_IO_SZ, header.size);
+ count = socket_read(proxy->sockfd, reply->iov_base, count);
+ if (count < 0) {
+ return count;
+ }
+ header.size -= count;
+ }
+ *status = -ENOBUFS;
+ return 0;
+ }
+
+ retval = socket_read(proxy->sockfd,
+ reply->iov_base + PROXY_HDR_SZ, header.size);
+ if (retval < 0) {
+ return retval;
+ }
+ reply->iov_len += header.size;
+ /* there was an error during processing request */
+ if (header.type == T_ERROR) {
+ int ret;
+ ret = proxy_unmarshal(reply, PROXY_HDR_SZ, "d", status);
+ if (ret < 0) {
+ *status = ret;
+ }
+ return 0;
+ }
+
+ switch (type) {
+ case T_LSTAT: {
+ ProxyStat prstat;
+ retval = proxy_unmarshal(reply, PROXY_HDR_SZ,
+ "qqqdddqqqqqqqqqq", &prstat.st_dev,
+ &prstat.st_ino, &prstat.st_nlink,
+ &prstat.st_mode, &prstat.st_uid,
+ &prstat.st_gid, &prstat.st_rdev,
+ &prstat.st_size, &prstat.st_blksize,
+ &prstat.st_blocks,
+ &prstat.st_atim_sec, &prstat.st_atim_nsec,
+ &prstat.st_mtim_sec, &prstat.st_mtim_nsec,
+ &prstat.st_ctim_sec, &prstat.st_ctim_nsec);
+ prstat_to_stat(response, &prstat);
+ break;
+ }
+ case T_STATFS: {
+ ProxyStatFS prstfs;
+ retval = proxy_unmarshal(reply, PROXY_HDR_SZ,
+ "qqqqqqqqqqq", &prstfs.f_type,
+ &prstfs.f_bsize, &prstfs.f_blocks,
+ &prstfs.f_bfree, &prstfs.f_bavail,
+ &prstfs.f_files, &prstfs.f_ffree,
+ &prstfs.f_fsid[0], &prstfs.f_fsid[1],
+ &prstfs.f_namelen, &prstfs.f_frsize);
+ prstatfs_to_statfs(response, &prstfs);
+ break;
+ }
+ case T_READLINK: {
+ V9fsString target;
+ v9fs_string_init(&target);
+ retval = proxy_unmarshal(reply, PROXY_HDR_SZ, "s", &target);
+ strcpy(response, target.data);
+ v9fs_string_free(&target);
+ break;
+ }
+ case T_LGETXATTR:
+ case T_LLISTXATTR: {
+ V9fsString xattr;
+ v9fs_string_init(&xattr);
+ retval = proxy_unmarshal(reply, PROXY_HDR_SZ, "s", &xattr);
+ memcpy(response, xattr.data, xattr.size);
+ v9fs_string_free(&xattr);
+ break;
+ }
+ case T_GETVERSION:
+ proxy_unmarshal(reply, PROXY_HDR_SZ, "q", response);
+ break;
+ default:
+ return -1;
+ }
+ if (retval < 0) {
+ *status = retval;
+ }
+ return 0;
+}
+
+/*
+ * return < 0 on transport error.
+ * *status is valid only if return >= 0
+ */
+static int v9fs_receive_status(V9fsProxy *proxy,
+ struct iovec *reply, int *status)
+{
+ int retval;
+ ProxyHeader header;
+
+ *status = 0;
+ reply->iov_len = 0;
+ retval = socket_read(proxy->sockfd, reply->iov_base, PROXY_HDR_SZ);
+ if (retval < 0) {
+ return retval;
+ }
+ reply->iov_len = PROXY_HDR_SZ;
+ proxy_unmarshal(reply, 0, "dd", &header.type, &header.size);
+ if (header.size != sizeof(int)) {
+ *status = -ENOBUFS;
+ return 0;
+ }
+ retval = socket_read(proxy->sockfd,
+ reply->iov_base + PROXY_HDR_SZ, header.size);
+ if (retval < 0) {
+ return retval;
+ }
+ reply->iov_len += header.size;
+ proxy_unmarshal(reply, PROXY_HDR_SZ, "d", status);
+ return 0;
+}
+
+/*
+ * Proxy->header and proxy->request written to socket by QEMU process.
+ * This request read by proxy helper process
+ * returns 0 on success and -errno on error
+ */
+static int v9fs_request(V9fsProxy *proxy, int type,
+ void *response, const char *fmt, ...)
+{
+ dev_t rdev;
+ va_list ap;
+ int size = 0;
+ int retval = 0;
+ uint64_t offset;
+ ProxyHeader header = { 0, 0};
+ struct timespec spec[2];
+ int flags, mode, uid, gid;
+ V9fsString *name, *value;
+ V9fsString *path, *oldpath;
+ struct iovec *iovec = NULL, *reply = NULL;
+
+ qemu_mutex_lock(&proxy->mutex);
+
+ if (proxy->sockfd == -1) {
+ retval = -EIO;
+ goto err_out;
+ }
+ iovec = &proxy->out_iovec;
+ reply = &proxy->in_iovec;
+ va_start(ap, fmt);
+ switch (type) {
+ case T_OPEN:
+ path = va_arg(ap, V9fsString *);
+ flags = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sd", path, flags);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_OPEN;
+ }
+ break;
+ case T_CREATE:
+ path = va_arg(ap, V9fsString *);
+ flags = va_arg(ap, int);
+ mode = va_arg(ap, int);
+ uid = va_arg(ap, int);
+ gid = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sdddd", path,
+ flags, mode, uid, gid);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_CREATE;
+ }
+ break;
+ case T_MKNOD:
+ path = va_arg(ap, V9fsString *);
+ mode = va_arg(ap, int);
+ rdev = va_arg(ap, long int);
+ uid = va_arg(ap, int);
+ gid = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ddsdq",
+ uid, gid, path, mode, rdev);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_MKNOD;
+ }
+ break;
+ case T_MKDIR:
+ path = va_arg(ap, V9fsString *);
+ mode = va_arg(ap, int);
+ uid = va_arg(ap, int);
+ gid = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ddsd",
+ uid, gid, path, mode);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_MKDIR;
+ }
+ break;
+ case T_SYMLINK:
+ oldpath = va_arg(ap, V9fsString *);
+ path = va_arg(ap, V9fsString *);
+ uid = va_arg(ap, int);
+ gid = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ddss",
+ uid, gid, oldpath, path);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_SYMLINK;
+ }
+ break;
+ case T_LINK:
+ oldpath = va_arg(ap, V9fsString *);
+ path = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ss",
+ oldpath, path);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_LINK;
+ }
+ break;
+ case T_LSTAT:
+ path = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "s", path);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_LSTAT;
+ }
+ break;
+ case T_READLINK:
+ path = va_arg(ap, V9fsString *);
+ size = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sd", path, size);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_READLINK;
+ }
+ break;
+ case T_STATFS:
+ path = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "s", path);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_STATFS;
+ }
+ break;
+ case T_CHMOD:
+ path = va_arg(ap, V9fsString *);
+ mode = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sd", path, mode);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_CHMOD;
+ }
+ break;
+ case T_CHOWN:
+ path = va_arg(ap, V9fsString *);
+ uid = va_arg(ap, int);
+ gid = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sdd", path, uid, gid);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_CHOWN;
+ }
+ break;
+ case T_TRUNCATE:
+ path = va_arg(ap, V9fsString *);
+ offset = va_arg(ap, uint64_t);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sq", path, offset);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_TRUNCATE;
+ }
+ break;
+ case T_UTIME:
+ path = va_arg(ap, V9fsString *);
+ spec[0].tv_sec = va_arg(ap, long);
+ spec[0].tv_nsec = va_arg(ap, long);
+ spec[1].tv_sec = va_arg(ap, long);
+ spec[1].tv_nsec = va_arg(ap, long);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sqqqq", path,
+ spec[0].tv_sec, spec[1].tv_nsec,
+ spec[1].tv_sec, spec[1].tv_nsec);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_UTIME;
+ }
+ break;
+ case T_RENAME:
+ oldpath = va_arg(ap, V9fsString *);
+ path = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ss", oldpath, path);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_RENAME;
+ }
+ break;
+ case T_REMOVE:
+ path = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "s", path);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_REMOVE;
+ }
+ break;
+ case T_LGETXATTR:
+ size = va_arg(ap, int);
+ path = va_arg(ap, V9fsString *);
+ name = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ,
+ "dss", size, path, name);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_LGETXATTR;
+ }
+ break;
+ case T_LLISTXATTR:
+ size = va_arg(ap, int);
+ path = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ds", size, path);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_LLISTXATTR;
+ }
+ break;
+ case T_LSETXATTR:
+ path = va_arg(ap, V9fsString *);
+ name = va_arg(ap, V9fsString *);
+ value = va_arg(ap, V9fsString *);
+ size = va_arg(ap, int);
+ flags = va_arg(ap, int);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "sssdd",
+ path, name, value, size, flags);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_LSETXATTR;
+ }
+ break;
+ case T_LREMOVEXATTR:
+ path = va_arg(ap, V9fsString *);
+ name = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "ss", path, name);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_LREMOVEXATTR;
+ }
+ break;
+ case T_GETVERSION:
+ path = va_arg(ap, V9fsString *);
+ retval = proxy_marshal(iovec, PROXY_HDR_SZ, "s", path);
+ if (retval > 0) {
+ header.size = retval;
+ header.type = T_GETVERSION;
+ }
+ break;
+ default:
+ error_report("Invalid type %d", type);
+ retval = -EINVAL;
+ break;
+ }
+ va_end(ap);
+
+ if (retval < 0) {
+ goto err_out;
+ }
+
+ /* marshal the header details */
+ proxy_marshal(iovec, 0, "dd", header.type, header.size);
+ header.size += PROXY_HDR_SZ;
+
+ retval = qemu_write_full(proxy->sockfd, iovec->iov_base, header.size);
+ if (retval != header.size) {
+ goto close_error;
+ }
+
+ switch (type) {
+ case T_OPEN:
+ case T_CREATE:
+ /*
+ * A file descriptor is returned as response for
+ * T_OPEN,T_CREATE on success
+ */
+ if (v9fs_receivefd(proxy->sockfd, &retval) < 0) {
+ goto close_error;
+ }
+ break;
+ case T_MKNOD:
+ case T_MKDIR:
+ case T_SYMLINK:
+ case T_LINK:
+ case T_CHMOD:
+ case T_CHOWN:
+ case T_RENAME:
+ case T_TRUNCATE:
+ case T_UTIME:
+ case T_REMOVE:
+ case T_LSETXATTR:
+ case T_LREMOVEXATTR:
+ if (v9fs_receive_status(proxy, reply, &retval) < 0) {
+ goto close_error;
+ }
+ break;
+ case T_LSTAT:
+ case T_READLINK:
+ case T_STATFS:
+ case T_GETVERSION:
+ if (v9fs_receive_response(proxy, type, &retval, response) < 0) {
+ goto close_error;
+ }
+ break;
+ case T_LGETXATTR:
+ case T_LLISTXATTR:
+ if (!size) {
+ if (v9fs_receive_status(proxy, reply, &retval) < 0) {
+ goto close_error;
+ }
+ } else {
+ if (v9fs_receive_response(proxy, type, &retval, response) < 0) {
+ goto close_error;
+ }
+ }
+ break;
+ }
+
+err_out:
+ qemu_mutex_unlock(&proxy->mutex);
+ return retval;
+
+close_error:
+ close(proxy->sockfd);
+ proxy->sockfd = -1;
+ qemu_mutex_unlock(&proxy->mutex);
+ return -EIO;
+}
+
+static int proxy_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
+{
+ int retval;
+ retval = v9fs_request(fs_ctx->private, T_LSTAT, stbuf, "s", fs_path);
+ if (retval < 0) {
+ errno = -retval;
+ return -1;
+ }
+ return retval;
+}
+
+static ssize_t proxy_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
+ char *buf, size_t bufsz)
+{
+ int retval;
+ retval = v9fs_request(fs_ctx->private, T_READLINK, buf, "sd",
+ fs_path, bufsz);
+ if (retval < 0) {
+ errno = -retval;
+ return -1;
+ }
+ return strlen(buf);
+}
+
+static int proxy_close(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return close(fs->fd);
+}
+
+static int proxy_closedir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return closedir(fs->dir);
+}
+
+static int proxy_open(FsContext *ctx, V9fsPath *fs_path,
+ int flags, V9fsFidOpenState *fs)
+{
+ fs->fd = v9fs_request(ctx->private, T_OPEN, NULL, "sd", fs_path, flags);
+ if (fs->fd < 0) {
+ errno = -fs->fd;
+ fs->fd = -1;
+ }
+ return fs->fd;
+}
+
+static int proxy_opendir(FsContext *ctx,
+ V9fsPath *fs_path, V9fsFidOpenState *fs)
+{
+ int serrno, fd;
+
+ fs->dir = NULL;
+ fd = v9fs_request(ctx->private, T_OPEN, NULL, "sd", fs_path, O_DIRECTORY);
+ if (fd < 0) {
+ errno = -fd;
+ return -1;
+ }
+ fs->dir = fdopendir(fd);
+ if (!fs->dir) {
+ serrno = errno;
+ close(fd);
+ errno = serrno;
+ return -1;
+ }
+ return 0;
+}
+
+static void proxy_rewinddir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ rewinddir(fs->dir);
+}
+
+static off_t proxy_telldir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ return telldir(fs->dir);
+}
+
+static int proxy_readdir_r(FsContext *ctx, V9fsFidOpenState *fs,
+ struct dirent *entry,
+ struct dirent **result)
+{
+ return readdir_r(fs->dir, entry, result);
+}
+
+static void proxy_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
+{
+ seekdir(fs->dir, off);
+}
+
+static ssize_t proxy_preadv(FsContext *ctx, V9fsFidOpenState *fs,
+ const struct iovec *iov,
+ int iovcnt, off_t offset)
+{
+ ssize_t ret;
+#ifdef CONFIG_PREADV
+ ret = preadv(fs->fd, iov, iovcnt, offset);
+#else
+ ret = lseek(fs->fd, offset, SEEK_SET);
+ if (ret >= 0) {
+ ret = readv(fs->fd, iov, iovcnt);
+ }
+#endif
+ return ret;
+}
+
+static ssize_t proxy_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
+ const struct iovec *iov,
+ int iovcnt, off_t offset)
+{
+ ssize_t ret;
+
+#ifdef CONFIG_PREADV
+ ret = pwritev(fs->fd, iov, iovcnt, offset);
+#else
+ ret = lseek(fs->fd, offset, SEEK_SET);
+ if (ret >= 0) {
+ ret = writev(fs->fd, iov, iovcnt);
+ }
+#endif
+#ifdef CONFIG_SYNC_FILE_RANGE
+ if (ret > 0 && ctx->export_flags & V9FS_IMMEDIATE_WRITEOUT) {
+ /*
+ * Initiate a writeback. This is not a data integrity sync.
+ * We want to ensure that we don't leave dirty pages in the cache
+ * after write when writeout=immediate is sepcified.
+ */
+ sync_file_range(fs->fd, offset, ret,
+ SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE);
+ }
+#endif
+ return ret;
+}
+
+static int proxy_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
+{
+ int retval;
+ retval = v9fs_request(fs_ctx->private, T_CHMOD, NULL, "sd",
+ fs_path, credp->fc_mode);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static int proxy_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
+ const char *name, FsCred *credp)
+{
+ int retval;
+ V9fsString fullname;
+
+ v9fs_string_init(&fullname);
+ v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
+
+ retval = v9fs_request(fs_ctx->private, T_MKNOD, NULL, "sdqdd",
+ &fullname, credp->fc_mode, credp->fc_rdev,
+ credp->fc_uid, credp->fc_gid);
+ v9fs_string_free(&fullname);
+ if (retval < 0) {
+ errno = -retval;
+ retval = -1;
+ }
+ return retval;
+}
+
+static int proxy_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
+ const char *name, FsCred *credp)
+{
+ int retval;
+ V9fsString fullname;
+
+ v9fs_string_init(&fullname);
+ v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
+
+ retval = v9fs_request(fs_ctx->private, T_MKDIR, NULL, "sddd", &fullname,
+ credp->fc_mode, credp->fc_uid, credp->fc_gid);
+ v9fs_string_free(&fullname);
+ if (retval < 0) {
+ errno = -retval;
+ retval = -1;
+ }
+ v9fs_string_free(&fullname);
+ return retval;
+}
+
+static int proxy_fstat(FsContext *fs_ctx, int fid_type,
+ V9fsFidOpenState *fs, struct stat *stbuf)
+{
+ int fd;
+
+ if (fid_type == P9_FID_DIR) {
+ fd = dirfd(fs->dir);
+ } else {
+ fd = fs->fd;
+ }
+ return fstat(fd, stbuf);
+}
+
+static int proxy_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
+ int flags, FsCred *credp, V9fsFidOpenState *fs)
+{
+ V9fsString fullname;
+
+ v9fs_string_init(&fullname);
+ v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
+
+ fs->fd = v9fs_request(fs_ctx->private, T_CREATE, NULL, "sdddd",
+ &fullname, flags, credp->fc_mode,
+ credp->fc_uid, credp->fc_gid);
+ v9fs_string_free(&fullname);
+ if (fs->fd < 0) {
+ errno = -fs->fd;
+ fs->fd = -1;
+ }
+ return fs->fd;
+}
+
+static int proxy_symlink(FsContext *fs_ctx, const char *oldpath,
+ V9fsPath *dir_path, const char *name, FsCred *credp)
+{
+ int retval;
+ V9fsString fullname, target;
+
+ v9fs_string_init(&fullname);
+ v9fs_string_init(&target);
+
+ v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
+ v9fs_string_sprintf(&target, "%s", oldpath);
+
+ retval = v9fs_request(fs_ctx->private, T_SYMLINK, NULL, "ssdd",
+ &target, &fullname, credp->fc_uid, credp->fc_gid);
+ v9fs_string_free(&fullname);
+ v9fs_string_free(&target);
+ if (retval < 0) {
+ errno = -retval;
+ retval = -1;
+ }
+ return retval;
+}
+
+static int proxy_link(FsContext *ctx, V9fsPath *oldpath,
+ V9fsPath *dirpath, const char *name)
+{
+ int retval;
+ V9fsString newpath;
+
+ v9fs_string_init(&newpath);
+ v9fs_string_sprintf(&newpath, "%s/%s", dirpath->data, name);
+
+ retval = v9fs_request(ctx->private, T_LINK, NULL, "ss", oldpath, &newpath);
+ v9fs_string_free(&newpath);
+ if (retval < 0) {
+ errno = -retval;
+ retval = -1;
+ }
+ return retval;
+}
+
+static int proxy_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
+{
+ int retval;
+
+ retval = v9fs_request(ctx->private, T_TRUNCATE, NULL, "sq", fs_path, size);
+ if (retval < 0) {
+ errno = -retval;
+ return -1;
+ }
+ return 0;
+}
+
+static int proxy_rename(FsContext *ctx, const char *oldpath,
+ const char *newpath)
+{
+ int retval;
+ V9fsString oldname, newname;
+
+ v9fs_string_init(&oldname);
+ v9fs_string_init(&newname);
+
+ v9fs_string_sprintf(&oldname, "%s", oldpath);
+ v9fs_string_sprintf(&newname, "%s", newpath);
+ retval = v9fs_request(ctx->private, T_RENAME, NULL, "ss",
+ &oldname, &newname);
+ v9fs_string_free(&oldname);
+ v9fs_string_free(&newname);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static int proxy_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
+{
+ int retval;
+ retval = v9fs_request(fs_ctx->private, T_CHOWN, NULL, "sdd",
+ fs_path, credp->fc_uid, credp->fc_gid);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static int proxy_utimensat(FsContext *s, V9fsPath *fs_path,
+ const struct timespec *buf)
+{
+ int retval;
+ retval = v9fs_request(s->private, T_UTIME, NULL, "sqqqq",
+ fs_path,
+ buf[0].tv_sec, buf[0].tv_nsec,
+ buf[1].tv_sec, buf[1].tv_nsec);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static int proxy_remove(FsContext *ctx, const char *path)
+{
+ int retval;
+ V9fsString name;
+ v9fs_string_init(&name);
+ v9fs_string_sprintf(&name, "%s", path);
+ retval = v9fs_request(ctx->private, T_REMOVE, NULL, "s", &name);
+ v9fs_string_free(&name);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static int proxy_fsync(FsContext *ctx, int fid_type,
+ V9fsFidOpenState *fs, int datasync)
+{
+ int fd;
+
+ if (fid_type == P9_FID_DIR) {
+ fd = dirfd(fs->dir);
+ } else {
+ fd = fs->fd;
+ }
+
+ if (datasync) {
+ return qemu_fdatasync(fd);
+ } else {
+ return fsync(fd);
+ }
+}
+
+static int proxy_statfs(FsContext *s, V9fsPath *fs_path, struct statfs *stbuf)
+{
+ int retval;
+ retval = v9fs_request(s->private, T_STATFS, stbuf, "s", fs_path);
+ if (retval < 0) {
+ errno = -retval;
+ return -1;
+ }
+ return retval;
+}
+
+static ssize_t proxy_lgetxattr(FsContext *ctx, V9fsPath *fs_path,
+ const char *name, void *value, size_t size)
+{
+ int retval;
+ V9fsString xname;
+
+ v9fs_string_init(&xname);
+ v9fs_string_sprintf(&xname, "%s", name);
+ retval = v9fs_request(ctx->private, T_LGETXATTR, value, "dss", size,
+ fs_path, &xname);
+ v9fs_string_free(&xname);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static ssize_t proxy_llistxattr(FsContext *ctx, V9fsPath *fs_path,
+ void *value, size_t size)
+{
+ int retval;
+ retval = v9fs_request(ctx->private, T_LLISTXATTR, value, "ds", size,
+ fs_path);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static int proxy_lsetxattr(FsContext *ctx, V9fsPath *fs_path, const char *name,
+ void *value, size_t size, int flags)
+{
+ int retval;
+ V9fsString xname, xvalue;
+
+ v9fs_string_init(&xname);
+ v9fs_string_sprintf(&xname, "%s", name);
+
+ v9fs_string_init(&xvalue);
+ xvalue.size = size;
+ xvalue.data = g_malloc(size);
+ memcpy(xvalue.data, value, size);
+
+ retval = v9fs_request(ctx->private, T_LSETXATTR, value, "sssdd",
+ fs_path, &xname, &xvalue, size, flags);
+ v9fs_string_free(&xname);
+ v9fs_string_free(&xvalue);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static int proxy_lremovexattr(FsContext *ctx, V9fsPath *fs_path,
+ const char *name)
+{
+ int retval;
+ V9fsString xname;
+
+ v9fs_string_init(&xname);
+ v9fs_string_sprintf(&xname, "%s", name);
+ retval = v9fs_request(ctx->private, T_LREMOVEXATTR, NULL, "ss",
+ fs_path, &xname);
+ v9fs_string_free(&xname);
+ if (retval < 0) {
+ errno = -retval;
+ }
+ return retval;
+}
+
+static int proxy_name_to_path(FsContext *ctx, V9fsPath *dir_path,
+ const char *name, V9fsPath *target)
+{
+ if (dir_path) {
+ v9fs_string_sprintf((V9fsString *)target, "%s/%s",
+ dir_path->data, name);
+ } else {
+ v9fs_string_sprintf((V9fsString *)target, "%s", name);
+ }
+ /* Bump the size for including terminating NULL */
+ target->size++;
+ return 0;
+}
+
+static int proxy_renameat(FsContext *ctx, V9fsPath *olddir,
+ const char *old_name, V9fsPath *newdir,
+ const char *new_name)
+{
+ int ret;
+ V9fsString old_full_name, new_full_name;
+
+ v9fs_string_init(&old_full_name);
+ v9fs_string_init(&new_full_name);
+
+ v9fs_string_sprintf(&old_full_name, "%s/%s", olddir->data, old_name);
+ v9fs_string_sprintf(&new_full_name, "%s/%s", newdir->data, new_name);
+
+ ret = proxy_rename(ctx, old_full_name.data, new_full_name.data);
+ v9fs_string_free(&old_full_name);
+ v9fs_string_free(&new_full_name);
+ return ret;
+}
+
+static int proxy_unlinkat(FsContext *ctx, V9fsPath *dir,
+ const char *name, int flags)
+{
+ int ret;
+ V9fsString fullname;
+ v9fs_string_init(&fullname);
+
+ v9fs_string_sprintf(&fullname, "%s/%s", dir->data, name);
+ ret = proxy_remove(ctx, fullname.data);
+ v9fs_string_free(&fullname);
+
+ return ret;
+}
+
+static int proxy_ioc_getversion(FsContext *fs_ctx, V9fsPath *path,
+ mode_t st_mode, uint64_t *st_gen)
+{
+ int err;
+
+ /* Do not try to open special files like device nodes, fifos etc
+ * we can get fd for regular files and directories only
+ */
+ if (!S_ISREG(st_mode) && !S_ISDIR(st_mode)) {
+ errno = ENOTTY;
+ return -1;
+ }
+ err = v9fs_request(fs_ctx->private, T_GETVERSION, st_gen, "s", path);
+ if (err < 0) {
+ errno = -err;
+ err = -1;
+ }
+ return err;
+}
+
+static int connect_namedsocket(const char *path)
+{
+ int sockfd, size;
+ struct sockaddr_un helper;
+
+ if (strlen(path) >= sizeof(helper.sun_path)) {
+ fprintf(stderr, "Socket name too large\n");
+ return -1;
+ }
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sockfd < 0) {
+ fprintf(stderr, "failed to create socket: %s\n", strerror(errno));
+ return -1;
+ }
+ strcpy(helper.sun_path, path);
+ helper.sun_family = AF_UNIX;
+ size = strlen(helper.sun_path) + sizeof(helper.sun_family);
+ if (connect(sockfd, (struct sockaddr *)&helper, size) < 0) {
+ fprintf(stderr, "failed to connect to %s: %s\n", path, strerror(errno));
+ close(sockfd);
+ return -1;
+ }
+
+ /* remove the socket for security reasons */
+ unlink(path);
+ return sockfd;
+}
+
+static int proxy_parse_opts(QemuOpts *opts, struct FsDriverEntry *fs)
+{
+ const char *socket = qemu_opt_get(opts, "socket");
+ const char *sock_fd = qemu_opt_get(opts, "sock_fd");
+
+ if (!socket && !sock_fd) {
+ fprintf(stderr, "socket and sock_fd none of the option specified\n");
+ return -1;
+ }
+ if (socket && sock_fd) {
+ fprintf(stderr, "Both socket and sock_fd options specified\n");
+ return -1;
+ }
+ if (socket) {
+ fs->path = g_strdup(socket);
+ fs->export_flags = V9FS_PROXY_SOCK_NAME;
+ } else {
+ fs->path = g_strdup(sock_fd);
+ fs->export_flags = V9FS_PROXY_SOCK_FD;
+ }
+ return 0;
+}
+
+static int proxy_init(FsContext *ctx)
+{
+ V9fsProxy *proxy = g_malloc(sizeof(V9fsProxy));
+ int sock_id;
+
+ if (ctx->export_flags & V9FS_PROXY_SOCK_NAME) {
+ sock_id = connect_namedsocket(ctx->fs_root);
+ } else {
+ sock_id = atoi(ctx->fs_root);
+ if (sock_id < 0) {
+ fprintf(stderr, "socket descriptor not initialized\n");
+ }
+ }
+ if (sock_id < 0) {
+ g_free(proxy);
+ return -1;
+ }
+ g_free(ctx->fs_root);
+ ctx->fs_root = NULL;
+
+ proxy->in_iovec.iov_base = g_malloc(PROXY_MAX_IO_SZ + PROXY_HDR_SZ);
+ proxy->in_iovec.iov_len = PROXY_MAX_IO_SZ + PROXY_HDR_SZ;
+ proxy->out_iovec.iov_base = g_malloc(PROXY_MAX_IO_SZ + PROXY_HDR_SZ);
+ proxy->out_iovec.iov_len = PROXY_MAX_IO_SZ + PROXY_HDR_SZ;
+
+ ctx->private = proxy;
+ proxy->sockfd = sock_id;
+ qemu_mutex_init(&proxy->mutex);
+
+ ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
+ ctx->exops.get_st_gen = proxy_ioc_getversion;
+ return 0;
+}
+
+FileOperations proxy_ops = {
+ .parse_opts = proxy_parse_opts,
+ .init = proxy_init,
+ .lstat = proxy_lstat,
+ .readlink = proxy_readlink,
+ .close = proxy_close,
+ .closedir = proxy_closedir,
+ .open = proxy_open,
+ .opendir = proxy_opendir,
+ .rewinddir = proxy_rewinddir,
+ .telldir = proxy_telldir,
+ .readdir_r = proxy_readdir_r,
+ .seekdir = proxy_seekdir,
+ .preadv = proxy_preadv,
+ .pwritev = proxy_pwritev,
+ .chmod = proxy_chmod,
+ .mknod = proxy_mknod,
+ .mkdir = proxy_mkdir,
+ .fstat = proxy_fstat,
+ .open2 = proxy_open2,
+ .symlink = proxy_symlink,
+ .link = proxy_link,
+ .truncate = proxy_truncate,
+ .rename = proxy_rename,
+ .chown = proxy_chown,
+ .utimensat = proxy_utimensat,
+ .remove = proxy_remove,
+ .fsync = proxy_fsync,
+ .statfs = proxy_statfs,
+ .lgetxattr = proxy_lgetxattr,
+ .llistxattr = proxy_llistxattr,
+ .lsetxattr = proxy_lsetxattr,
+ .lremovexattr = proxy_lremovexattr,
+ .name_to_path = proxy_name_to_path,
+ .renameat = proxy_renameat,
+ .unlinkat = proxy_unlinkat,
+};
diff --git a/hw/9pfs/virtio-9p-proxy.h b/hw/9pfs/virtio-9p-proxy.h
new file mode 100644
index 00000000..005c1ad7
--- /dev/null
+++ b/hw/9pfs/virtio-9p-proxy.h
@@ -0,0 +1,95 @@
+/*
+ * Virtio 9p Proxy callback
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * M. Mohan Kumar <mohan@in.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ */
+#ifndef _QEMU_VIRTIO_9P_PROXY_H
+#define _QEMU_VIRTIO_9P_PROXY_H
+
+#define PROXY_MAX_IO_SZ (64 * 1024)
+#define V9FS_FD_VALID INT_MAX
+
+/*
+ * proxy iovec only support one element and
+ * marsha/unmarshal doesn't do little endian conversion.
+ */
+#define proxy_unmarshal(in_sg, offset, fmt, args...) \
+ v9fs_unmarshal(in_sg, 1, offset, 0, fmt, ##args)
+#define proxy_marshal(out_sg, offset, fmt, args...) \
+ v9fs_marshal(out_sg, 1, offset, 0, fmt, ##args)
+
+union MsgControl {
+ struct cmsghdr cmsg;
+ char control[CMSG_SPACE(sizeof(int))];
+};
+
+typedef struct {
+ uint32_t type;
+ uint32_t size;
+} ProxyHeader;
+
+#define PROXY_HDR_SZ (sizeof(ProxyHeader))
+
+enum {
+ T_SUCCESS = 0,
+ T_ERROR,
+ T_OPEN,
+ T_CREATE,
+ T_MKNOD,
+ T_MKDIR,
+ T_SYMLINK,
+ T_LINK,
+ T_LSTAT,
+ T_READLINK,
+ T_STATFS,
+ T_CHMOD,
+ T_CHOWN,
+ T_TRUNCATE,
+ T_UTIME,
+ T_RENAME,
+ T_REMOVE,
+ T_LGETXATTR,
+ T_LLISTXATTR,
+ T_LSETXATTR,
+ T_LREMOVEXATTR,
+ T_GETVERSION,
+};
+
+typedef struct {
+ uint64_t st_dev;
+ uint64_t st_ino;
+ uint64_t st_nlink;
+ uint32_t st_mode;
+ uint32_t st_uid;
+ uint32_t st_gid;
+ uint64_t st_rdev;
+ uint64_t st_size;
+ uint64_t st_blksize;
+ uint64_t st_blocks;
+ uint64_t st_atim_sec;
+ uint64_t st_atim_nsec;
+ uint64_t st_mtim_sec;
+ uint64_t st_mtim_nsec;
+ uint64_t st_ctim_sec;
+ uint64_t st_ctim_nsec;
+} ProxyStat;
+
+typedef struct {
+ uint64_t f_type;
+ uint64_t f_bsize;
+ uint64_t f_blocks;
+ uint64_t f_bfree;
+ uint64_t f_bavail;
+ uint64_t f_files;
+ uint64_t f_ffree;
+ uint64_t f_fsid[2];
+ uint64_t f_namelen;
+ uint64_t f_frsize;
+} ProxyStatFS;
+#endif
diff --git a/hw/9pfs/virtio-9p-synth.c b/hw/9pfs/virtio-9p-synth.c
new file mode 100644
index 00000000..a0ab9a86
--- /dev/null
+++ b/hw/9pfs/virtio-9p-synth.c
@@ -0,0 +1,573 @@
+/*
+ * Virtio 9p synthetic file system support
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Malahal Naineni <malahal@us.ibm.com>
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/virtio.h"
+#include "virtio-9p.h"
+#include "virtio-9p-xattr.h"
+#include "fsdev/qemu-fsdev.h"
+#include "virtio-9p-synth.h"
+#include "qemu/rcu.h"
+#include "qemu/rcu_queue.h"
+#include <sys/stat.h>
+
+/* Root node for synth file system */
+static V9fsSynthNode v9fs_synth_root = {
+ .name = "/",
+ .actual_attr = {
+ .mode = 0555 | S_IFDIR,
+ .nlink = 1,
+ },
+ .attr = &v9fs_synth_root.actual_attr,
+};
+
+static QemuMutex v9fs_synth_mutex;
+static int v9fs_synth_node_count;
+/* set to 1 when the synth fs is ready */
+static int v9fs_synth_fs;
+
+static V9fsSynthNode *v9fs_add_dir_node(V9fsSynthNode *parent, int mode,
+ const char *name,
+ V9fsSynthNodeAttr *attr, int inode)
+{
+ V9fsSynthNode *node;
+
+ /* Add directory type and remove write bits */
+ mode = ((mode & 0777) | S_IFDIR) & ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ node = g_malloc0(sizeof(V9fsSynthNode));
+ if (attr) {
+ /* We are adding .. or . entries */
+ node->attr = attr;
+ node->attr->nlink++;
+ } else {
+ node->attr = &node->actual_attr;
+ node->attr->inode = inode;
+ node->attr->nlink = 1;
+ /* We don't allow write to directories */
+ node->attr->mode = mode;
+ node->attr->write = NULL;
+ node->attr->read = NULL;
+ }
+ node->private = node;
+ pstrcpy(node->name, sizeof(node->name), name);
+ QLIST_INSERT_HEAD_RCU(&parent->child, node, sibling);
+ return node;
+}
+
+int qemu_v9fs_synth_mkdir(V9fsSynthNode *parent, int mode,
+ const char *name, V9fsSynthNode **result)
+{
+ int ret;
+ V9fsSynthNode *node, *tmp;
+
+ if (!v9fs_synth_fs) {
+ return EAGAIN;
+ }
+ if (!name || (strlen(name) >= NAME_MAX)) {
+ return EINVAL;
+ }
+ if (!parent) {
+ parent = &v9fs_synth_root;
+ }
+ qemu_mutex_lock(&v9fs_synth_mutex);
+ QLIST_FOREACH(tmp, &parent->child, sibling) {
+ if (!strcmp(tmp->name, name)) {
+ ret = EEXIST;
+ goto err_out;
+ }
+ }
+ /* Add the name */
+ node = v9fs_add_dir_node(parent, mode, name, NULL, v9fs_synth_node_count++);
+ v9fs_add_dir_node(node, parent->attr->mode, "..",
+ parent->attr, parent->attr->inode);
+ v9fs_add_dir_node(node, node->attr->mode, ".",
+ node->attr, node->attr->inode);
+ *result = node;
+ ret = 0;
+err_out:
+ qemu_mutex_unlock(&v9fs_synth_mutex);
+ return ret;
+}
+
+int qemu_v9fs_synth_add_file(V9fsSynthNode *parent, int mode,
+ const char *name, v9fs_synth_read read,
+ v9fs_synth_write write, void *arg)
+{
+ int ret;
+ V9fsSynthNode *node, *tmp;
+
+ if (!v9fs_synth_fs) {
+ return EAGAIN;
+ }
+ if (!name || (strlen(name) >= NAME_MAX)) {
+ return EINVAL;
+ }
+ if (!parent) {
+ parent = &v9fs_synth_root;
+ }
+
+ qemu_mutex_lock(&v9fs_synth_mutex);
+ QLIST_FOREACH(tmp, &parent->child, sibling) {
+ if (!strcmp(tmp->name, name)) {
+ ret = EEXIST;
+ goto err_out;
+ }
+ }
+ /* Add file type and remove write bits */
+ mode = ((mode & 0777) | S_IFREG);
+ node = g_malloc0(sizeof(V9fsSynthNode));
+ node->attr = &node->actual_attr;
+ node->attr->inode = v9fs_synth_node_count++;
+ node->attr->nlink = 1;
+ node->attr->read = read;
+ node->attr->write = write;
+ node->attr->mode = mode;
+ node->private = arg;
+ pstrcpy(node->name, sizeof(node->name), name);
+ QLIST_INSERT_HEAD_RCU(&parent->child, node, sibling);
+ ret = 0;
+err_out:
+ qemu_mutex_unlock(&v9fs_synth_mutex);
+ return ret;
+}
+
+static void v9fs_synth_fill_statbuf(V9fsSynthNode *node, struct stat *stbuf)
+{
+ stbuf->st_dev = 0;
+ stbuf->st_ino = node->attr->inode;
+ stbuf->st_mode = node->attr->mode;
+ stbuf->st_nlink = node->attr->nlink;
+ stbuf->st_uid = 0;
+ stbuf->st_gid = 0;
+ stbuf->st_rdev = 0;
+ stbuf->st_size = 0;
+ stbuf->st_blksize = 0;
+ stbuf->st_blocks = 0;
+ stbuf->st_atime = 0;
+ stbuf->st_mtime = 0;
+ stbuf->st_ctime = 0;
+}
+
+static int v9fs_synth_lstat(FsContext *fs_ctx,
+ V9fsPath *fs_path, struct stat *stbuf)
+{
+ V9fsSynthNode *node = *(V9fsSynthNode **)fs_path->data;
+
+ v9fs_synth_fill_statbuf(node, stbuf);
+ return 0;
+}
+
+static int v9fs_synth_fstat(FsContext *fs_ctx, int fid_type,
+ V9fsFidOpenState *fs, struct stat *stbuf)
+{
+ V9fsSynthOpenState *synth_open = fs->private;
+ v9fs_synth_fill_statbuf(synth_open->node, stbuf);
+ return 0;
+}
+
+static int v9fs_synth_opendir(FsContext *ctx,
+ V9fsPath *fs_path, V9fsFidOpenState *fs)
+{
+ V9fsSynthOpenState *synth_open;
+ V9fsSynthNode *node = *(V9fsSynthNode **)fs_path->data;
+
+ synth_open = g_malloc(sizeof(*synth_open));
+ synth_open->node = node;
+ node->open_count++;
+ fs->private = synth_open;
+ return 0;
+}
+
+static int v9fs_synth_closedir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ V9fsSynthOpenState *synth_open = fs->private;
+ V9fsSynthNode *node = synth_open->node;
+
+ node->open_count--;
+ g_free(synth_open);
+ fs->private = NULL;
+ return 0;
+}
+
+static off_t v9fs_synth_telldir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ V9fsSynthOpenState *synth_open = fs->private;
+ return synth_open->offset;
+}
+
+static void v9fs_synth_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
+{
+ V9fsSynthOpenState *synth_open = fs->private;
+ synth_open->offset = off;
+}
+
+static void v9fs_synth_rewinddir(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ v9fs_synth_seekdir(ctx, fs, 0);
+}
+
+static void v9fs_synth_direntry(V9fsSynthNode *node,
+ struct dirent *entry, off_t off)
+{
+ strcpy(entry->d_name, node->name);
+ entry->d_ino = node->attr->inode;
+ entry->d_off = off + 1;
+}
+
+static int v9fs_synth_get_dentry(V9fsSynthNode *dir, struct dirent *entry,
+ struct dirent **result, off_t off)
+{
+ int i = 0;
+ V9fsSynthNode *node;
+
+ rcu_read_lock();
+ QLIST_FOREACH(node, &dir->child, sibling) {
+ /* This is the off child of the directory */
+ if (i == off) {
+ break;
+ }
+ i++;
+ }
+ rcu_read_unlock();
+ if (!node) {
+ /* end of directory */
+ *result = NULL;
+ return 0;
+ }
+ v9fs_synth_direntry(node, entry, off);
+ *result = entry;
+ return 0;
+}
+
+static int v9fs_synth_readdir_r(FsContext *ctx, V9fsFidOpenState *fs,
+ struct dirent *entry, struct dirent **result)
+{
+ int ret;
+ V9fsSynthOpenState *synth_open = fs->private;
+ V9fsSynthNode *node = synth_open->node;
+ ret = v9fs_synth_get_dentry(node, entry, result, synth_open->offset);
+ if (!ret && *result != NULL) {
+ synth_open->offset++;
+ }
+ return ret;
+}
+
+static int v9fs_synth_open(FsContext *ctx, V9fsPath *fs_path,
+ int flags, V9fsFidOpenState *fs)
+{
+ V9fsSynthOpenState *synth_open;
+ V9fsSynthNode *node = *(V9fsSynthNode **)fs_path->data;
+
+ synth_open = g_malloc(sizeof(*synth_open));
+ synth_open->node = node;
+ node->open_count++;
+ fs->private = synth_open;
+ return 0;
+}
+
+static int v9fs_synth_open2(FsContext *fs_ctx, V9fsPath *dir_path,
+ const char *name, int flags,
+ FsCred *credp, V9fsFidOpenState *fs)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static int v9fs_synth_close(FsContext *ctx, V9fsFidOpenState *fs)
+{
+ V9fsSynthOpenState *synth_open = fs->private;
+ V9fsSynthNode *node = synth_open->node;
+
+ node->open_count--;
+ g_free(synth_open);
+ fs->private = NULL;
+ return 0;
+}
+
+static ssize_t v9fs_synth_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
+ const struct iovec *iov,
+ int iovcnt, off_t offset)
+{
+ int i, count = 0, wcount;
+ V9fsSynthOpenState *synth_open = fs->private;
+ V9fsSynthNode *node = synth_open->node;
+ if (!node->attr->write) {
+ errno = EPERM;
+ return -1;
+ }
+ for (i = 0; i < iovcnt; i++) {
+ wcount = node->attr->write(iov[i].iov_base, iov[i].iov_len,
+ offset, node->private);
+ offset += wcount;
+ count += wcount;
+ /* If we wrote less than requested. we are done */
+ if (wcount < iov[i].iov_len) {
+ break;
+ }
+ }
+ return count;
+}
+
+static ssize_t v9fs_synth_preadv(FsContext *ctx, V9fsFidOpenState *fs,
+ const struct iovec *iov,
+ int iovcnt, off_t offset)
+{
+ int i, count = 0, rcount;
+ V9fsSynthOpenState *synth_open = fs->private;
+ V9fsSynthNode *node = synth_open->node;
+ if (!node->attr->read) {
+ errno = EPERM;
+ return -1;
+ }
+ for (i = 0; i < iovcnt; i++) {
+ rcount = node->attr->read(iov[i].iov_base, iov[i].iov_len,
+ offset, node->private);
+ offset += rcount;
+ count += rcount;
+ /* If we read less than requested. we are done */
+ if (rcount < iov[i].iov_len) {
+ break;
+ }
+ }
+ return count;
+}
+
+static int v9fs_synth_truncate(FsContext *ctx, V9fsPath *path, off_t offset)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static int v9fs_synth_chmod(FsContext *fs_ctx, V9fsPath *path, FsCred *credp)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_mknod(FsContext *fs_ctx, V9fsPath *path,
+ const char *buf, FsCred *credp)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_mkdir(FsContext *fs_ctx, V9fsPath *path,
+ const char *buf, FsCred *credp)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static ssize_t v9fs_synth_readlink(FsContext *fs_ctx, V9fsPath *path,
+ char *buf, size_t bufsz)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static int v9fs_synth_symlink(FsContext *fs_ctx, const char *oldpath,
+ V9fsPath *newpath, const char *buf, FsCred *credp)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_link(FsContext *fs_ctx, V9fsPath *oldpath,
+ V9fsPath *newpath, const char *buf)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_rename(FsContext *ctx, const char *oldpath,
+ const char *newpath)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_chown(FsContext *fs_ctx, V9fsPath *path, FsCred *credp)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_utimensat(FsContext *fs_ctx, V9fsPath *path,
+ const struct timespec *buf)
+{
+ errno = EPERM;
+ return 0;
+}
+
+static int v9fs_synth_remove(FsContext *ctx, const char *path)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_fsync(FsContext *ctx, int fid_type,
+ V9fsFidOpenState *fs, int datasync)
+{
+ errno = ENOSYS;
+ return 0;
+}
+
+static int v9fs_synth_statfs(FsContext *s, V9fsPath *fs_path,
+ struct statfs *stbuf)
+{
+ stbuf->f_type = 0xABCD;
+ stbuf->f_bsize = 512;
+ stbuf->f_blocks = 0;
+ stbuf->f_files = v9fs_synth_node_count;
+ stbuf->f_namelen = NAME_MAX;
+ return 0;
+}
+
+static ssize_t v9fs_synth_lgetxattr(FsContext *ctx, V9fsPath *path,
+ const char *name, void *value, size_t size)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static ssize_t v9fs_synth_llistxattr(FsContext *ctx, V9fsPath *path,
+ void *value, size_t size)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static int v9fs_synth_lsetxattr(FsContext *ctx, V9fsPath *path,
+ const char *name, void *value,
+ size_t size, int flags)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static int v9fs_synth_lremovexattr(FsContext *ctx,
+ V9fsPath *path, const char *name)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static int v9fs_synth_name_to_path(FsContext *ctx, V9fsPath *dir_path,
+ const char *name, V9fsPath *target)
+{
+ V9fsSynthNode *node;
+ V9fsSynthNode *dir_node;
+
+ /* "." and ".." are not allowed */
+ if (!strcmp(name, ".") || !strcmp(name, "..")) {
+ errno = EINVAL;
+ return -1;
+
+ }
+ if (!dir_path) {
+ dir_node = &v9fs_synth_root;
+ } else {
+ dir_node = *(V9fsSynthNode **)dir_path->data;
+ }
+ if (!strcmp(name, "/")) {
+ node = dir_node;
+ goto out;
+ }
+ /* search for the name in the childern */
+ rcu_read_lock();
+ QLIST_FOREACH(node, &dir_node->child, sibling) {
+ if (!strcmp(node->name, name)) {
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ if (!node) {
+ errno = ENOENT;
+ return -1;
+ }
+out:
+ /* Copy the node pointer to fid */
+ target->data = g_malloc(sizeof(void *));
+ memcpy(target->data, &node, sizeof(void *));
+ target->size = sizeof(void *);
+ return 0;
+}
+
+static int v9fs_synth_renameat(FsContext *ctx, V9fsPath *olddir,
+ const char *old_name, V9fsPath *newdir,
+ const char *new_name)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_unlinkat(FsContext *ctx, V9fsPath *dir,
+ const char *name, int flags)
+{
+ errno = EPERM;
+ return -1;
+}
+
+static int v9fs_synth_init(FsContext *ctx)
+{
+ QLIST_INIT(&v9fs_synth_root.child);
+ qemu_mutex_init(&v9fs_synth_mutex);
+
+ /* Add "." and ".." entries for root */
+ v9fs_add_dir_node(&v9fs_synth_root, v9fs_synth_root.attr->mode,
+ "..", v9fs_synth_root.attr, v9fs_synth_root.attr->inode);
+ v9fs_add_dir_node(&v9fs_synth_root, v9fs_synth_root.attr->mode,
+ ".", v9fs_synth_root.attr, v9fs_synth_root.attr->inode);
+
+ /* Mark the subsystem is ready for use */
+ v9fs_synth_fs = 1;
+ return 0;
+}
+
+FileOperations synth_ops = {
+ .init = v9fs_synth_init,
+ .lstat = v9fs_synth_lstat,
+ .readlink = v9fs_synth_readlink,
+ .close = v9fs_synth_close,
+ .closedir = v9fs_synth_closedir,
+ .open = v9fs_synth_open,
+ .opendir = v9fs_synth_opendir,
+ .rewinddir = v9fs_synth_rewinddir,
+ .telldir = v9fs_synth_telldir,
+ .readdir_r = v9fs_synth_readdir_r,
+ .seekdir = v9fs_synth_seekdir,
+ .preadv = v9fs_synth_preadv,
+ .pwritev = v9fs_synth_pwritev,
+ .chmod = v9fs_synth_chmod,
+ .mknod = v9fs_synth_mknod,
+ .mkdir = v9fs_synth_mkdir,
+ .fstat = v9fs_synth_fstat,
+ .open2 = v9fs_synth_open2,
+ .symlink = v9fs_synth_symlink,
+ .link = v9fs_synth_link,
+ .truncate = v9fs_synth_truncate,
+ .rename = v9fs_synth_rename,
+ .chown = v9fs_synth_chown,
+ .utimensat = v9fs_synth_utimensat,
+ .remove = v9fs_synth_remove,
+ .fsync = v9fs_synth_fsync,
+ .statfs = v9fs_synth_statfs,
+ .lgetxattr = v9fs_synth_lgetxattr,
+ .llistxattr = v9fs_synth_llistxattr,
+ .lsetxattr = v9fs_synth_lsetxattr,
+ .lremovexattr = v9fs_synth_lremovexattr,
+ .name_to_path = v9fs_synth_name_to_path,
+ .renameat = v9fs_synth_renameat,
+ .unlinkat = v9fs_synth_unlinkat,
+};
diff --git a/hw/9pfs/virtio-9p-synth.h b/hw/9pfs/virtio-9p-synth.h
new file mode 100644
index 00000000..ab05a8e7
--- /dev/null
+++ b/hw/9pfs/virtio-9p-synth.h
@@ -0,0 +1,54 @@
+/*
+ * Virtio 9p
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+#ifndef HW_9PFS_VIRTIO9P_SYNTH_H
+#define HW_9PFS_VIRTIO9P_SYNTH_H 1
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <limits.h>
+
+typedef struct V9fsSynthNode V9fsSynthNode;
+typedef ssize_t (*v9fs_synth_read)(void *buf, int len, off_t offset,
+ void *arg);
+typedef ssize_t (*v9fs_synth_write)(void *buf, int len, off_t offset,
+ void *arg);
+typedef struct V9fsSynthNodeAttr {
+ int mode;
+ int inode;
+ int nlink;
+ v9fs_synth_read read;
+ v9fs_synth_write write;
+} V9fsSynthNodeAttr;
+
+struct V9fsSynthNode {
+ QLIST_HEAD(, V9fsSynthNode) child;
+ QLIST_ENTRY(V9fsSynthNode) sibling;
+ char name[NAME_MAX];
+ V9fsSynthNodeAttr *attr;
+ V9fsSynthNodeAttr actual_attr;
+ void *private;
+ int open_count;
+};
+
+typedef struct V9fsSynthOpenState {
+ off_t offset;
+ V9fsSynthNode *node;
+} V9fsSynthOpenState;
+
+extern int qemu_v9fs_synth_mkdir(V9fsSynthNode *parent, int mode,
+ const char *name, V9fsSynthNode **result);
+extern int qemu_v9fs_synth_add_file(V9fsSynthNode *parent, int mode,
+ const char *name, v9fs_synth_read read,
+ v9fs_synth_write write, void *arg);
+
+#endif
diff --git a/hw/9pfs/virtio-9p-xattr-user.c b/hw/9pfs/virtio-9p-xattr-user.c
new file mode 100644
index 00000000..46133e06
--- /dev/null
+++ b/hw/9pfs/virtio-9p-xattr-user.c
@@ -0,0 +1,128 @@
+/*
+ * Virtio 9p user. xattr callback
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include <sys/types.h>
+#include "hw/virtio/virtio.h"
+#include "virtio-9p.h"
+#include "fsdev/file-op-9p.h"
+#include "virtio-9p-xattr.h"
+
+
+static ssize_t mp_user_getxattr(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size)
+{
+ char *buffer;
+ ssize_t ret;
+
+ if (strncmp(name, "user.virtfs.", 12) == 0) {
+ /*
+ * Don't allow fetch of user.virtfs namesapce
+ * in case of mapped security
+ */
+ errno = ENOATTR;
+ return -1;
+ }
+ buffer = rpath(ctx, path);
+ ret = lgetxattr(buffer, name, value, size);
+ g_free(buffer);
+ return ret;
+}
+
+static ssize_t mp_user_listxattr(FsContext *ctx, const char *path,
+ char *name, void *value, size_t size)
+{
+ int name_size = strlen(name) + 1;
+ if (strncmp(name, "user.virtfs.", 12) == 0) {
+
+ /* check if it is a mapped posix acl */
+ if (strncmp(name, "user.virtfs.system.posix_acl_", 29) == 0) {
+ /* adjust the name and size */
+ name += 12;
+ name_size -= 12;
+ } else {
+ /*
+ * Don't allow fetch of user.virtfs namesapce
+ * in case of mapped security
+ */
+ return 0;
+ }
+ }
+ if (!value) {
+ return name_size;
+ }
+
+ if (size < name_size) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* name_size includes the trailing NUL. */
+ memcpy(value, name, name_size);
+ return name_size;
+}
+
+static int mp_user_setxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size, int flags)
+{
+ char *buffer;
+ int ret;
+
+ if (strncmp(name, "user.virtfs.", 12) == 0) {
+ /*
+ * Don't allow fetch of user.virtfs namesapce
+ * in case of mapped security
+ */
+ errno = EACCES;
+ return -1;
+ }
+ buffer = rpath(ctx, path);
+ ret = lsetxattr(buffer, name, value, size, flags);
+ g_free(buffer);
+ return ret;
+}
+
+static int mp_user_removexattr(FsContext *ctx,
+ const char *path, const char *name)
+{
+ char *buffer;
+ int ret;
+
+ if (strncmp(name, "user.virtfs.", 12) == 0) {
+ /*
+ * Don't allow fetch of user.virtfs namesapce
+ * in case of mapped security
+ */
+ errno = EACCES;
+ return -1;
+ }
+ buffer = rpath(ctx, path);
+ ret = lremovexattr(buffer, name);
+ g_free(buffer);
+ return ret;
+}
+
+XattrOperations mapped_user_xattr = {
+ .name = "user.",
+ .getxattr = mp_user_getxattr,
+ .setxattr = mp_user_setxattr,
+ .listxattr = mp_user_listxattr,
+ .removexattr = mp_user_removexattr,
+};
+
+XattrOperations passthrough_user_xattr = {
+ .name = "user.",
+ .getxattr = pt_getxattr,
+ .setxattr = pt_setxattr,
+ .listxattr = pt_listxattr,
+ .removexattr = pt_removexattr,
+};
diff --git a/hw/9pfs/virtio-9p-xattr.c b/hw/9pfs/virtio-9p-xattr.c
new file mode 100644
index 00000000..07183887
--- /dev/null
+++ b/hw/9pfs/virtio-9p-xattr.c
@@ -0,0 +1,164 @@
+/*
+ * Virtio 9p xattr callback
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/virtio.h"
+#include "virtio-9p.h"
+#include "fsdev/file-op-9p.h"
+#include "virtio-9p-xattr.h"
+
+
+static XattrOperations *get_xattr_operations(XattrOperations **h,
+ const char *name)
+{
+ XattrOperations *xops;
+ for (xops = *(h)++; xops != NULL; xops = *(h)++) {
+ if (!strncmp(name, xops->name, strlen(xops->name))) {
+ return xops;
+ }
+ }
+ return NULL;
+}
+
+ssize_t v9fs_get_xattr(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size)
+{
+ XattrOperations *xops = get_xattr_operations(ctx->xops, name);
+ if (xops) {
+ return xops->getxattr(ctx, path, name, value, size);
+ }
+ errno = EOPNOTSUPP;
+ return -1;
+}
+
+ssize_t pt_listxattr(FsContext *ctx, const char *path,
+ char *name, void *value, size_t size)
+{
+ int name_size = strlen(name) + 1;
+ if (!value) {
+ return name_size;
+ }
+
+ if (size < name_size) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* no need for strncpy: name_size is strlen(name)+1 */
+ memcpy(value, name, name_size);
+ return name_size;
+}
+
+
+/*
+ * Get the list and pass to each layer to find out whether
+ * to send the data or not
+ */
+ssize_t v9fs_list_xattr(FsContext *ctx, const char *path,
+ void *value, size_t vsize)
+{
+ ssize_t size = 0;
+ char *buffer;
+ void *ovalue = value;
+ XattrOperations *xops;
+ char *orig_value, *orig_value_start;
+ ssize_t xattr_len, parsed_len = 0, attr_len;
+
+ /* Get the actual len */
+ buffer = rpath(ctx, path);
+ xattr_len = llistxattr(buffer, value, 0);
+ if (xattr_len <= 0) {
+ g_free(buffer);
+ return xattr_len;
+ }
+
+ /* Now fetch the xattr and find the actual size */
+ orig_value = g_malloc(xattr_len);
+ xattr_len = llistxattr(buffer, orig_value, xattr_len);
+ g_free(buffer);
+
+ /* store the orig pointer */
+ orig_value_start = orig_value;
+ while (xattr_len > parsed_len) {
+ xops = get_xattr_operations(ctx->xops, orig_value);
+ if (!xops) {
+ goto next_entry;
+ }
+
+ if (!value) {
+ size += xops->listxattr(ctx, path, orig_value, value, vsize);
+ } else {
+ size = xops->listxattr(ctx, path, orig_value, value, vsize);
+ if (size < 0) {
+ goto err_out;
+ }
+ value += size;
+ vsize -= size;
+ }
+next_entry:
+ /* Got the next entry */
+ attr_len = strlen(orig_value) + 1;
+ parsed_len += attr_len;
+ orig_value += attr_len;
+ }
+ if (value) {
+ size = value - ovalue;
+ }
+
+err_out:
+ g_free(orig_value_start);
+ return size;
+}
+
+int v9fs_set_xattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size, int flags)
+{
+ XattrOperations *xops = get_xattr_operations(ctx->xops, name);
+ if (xops) {
+ return xops->setxattr(ctx, path, name, value, size, flags);
+ }
+ errno = EOPNOTSUPP;
+ return -1;
+
+}
+
+int v9fs_remove_xattr(FsContext *ctx,
+ const char *path, const char *name)
+{
+ XattrOperations *xops = get_xattr_operations(ctx->xops, name);
+ if (xops) {
+ return xops->removexattr(ctx, path, name);
+ }
+ errno = EOPNOTSUPP;
+ return -1;
+
+}
+
+XattrOperations *mapped_xattr_ops[] = {
+ &mapped_user_xattr,
+ &mapped_pacl_xattr,
+ &mapped_dacl_xattr,
+ NULL,
+};
+
+XattrOperations *passthrough_xattr_ops[] = {
+ &passthrough_user_xattr,
+ &passthrough_acl_xattr,
+ NULL,
+};
+
+/* for .user none model should be same as passthrough */
+XattrOperations *none_xattr_ops[] = {
+ &passthrough_user_xattr,
+ &none_acl_xattr,
+ NULL,
+};
diff --git a/hw/9pfs/virtio-9p-xattr.h b/hw/9pfs/virtio-9p-xattr.h
new file mode 100644
index 00000000..327b32b5
--- /dev/null
+++ b/hw/9pfs/virtio-9p-xattr.h
@@ -0,0 +1,120 @@
+/*
+ * Virtio 9p
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+#ifndef _QEMU_VIRTIO_9P_XATTR_H
+#define _QEMU_VIRTIO_9P_XATTR_H
+
+#include "qemu/xattr.h"
+
+typedef struct xattr_operations
+{
+ const char *name;
+ ssize_t (*getxattr)(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size);
+ ssize_t (*listxattr)(FsContext *ctx, const char *path,
+ char *name, void *value, size_t size);
+ int (*setxattr)(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size, int flags);
+ int (*removexattr)(FsContext *ctx,
+ const char *path, const char *name);
+} XattrOperations;
+
+
+extern XattrOperations mapped_user_xattr;
+extern XattrOperations passthrough_user_xattr;
+
+extern XattrOperations mapped_pacl_xattr;
+extern XattrOperations mapped_dacl_xattr;
+extern XattrOperations passthrough_acl_xattr;
+extern XattrOperations none_acl_xattr;
+
+extern XattrOperations *mapped_xattr_ops[];
+extern XattrOperations *passthrough_xattr_ops[];
+extern XattrOperations *none_xattr_ops[];
+
+ssize_t v9fs_get_xattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size);
+ssize_t v9fs_list_xattr(FsContext *ctx, const char *path, void *value,
+ size_t vsize);
+int v9fs_set_xattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size, int flags);
+int v9fs_remove_xattr(FsContext *ctx, const char *path, const char *name);
+ssize_t pt_listxattr(FsContext *ctx, const char *path, char *name, void *value,
+ size_t size);
+
+static inline ssize_t pt_getxattr(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size)
+{
+ char *buffer;
+ ssize_t ret;
+
+ buffer = rpath(ctx, path);
+ ret = lgetxattr(buffer, name, value, size);
+ g_free(buffer);
+ return ret;
+}
+
+static inline int pt_setxattr(FsContext *ctx, const char *path,
+ const char *name, void *value,
+ size_t size, int flags)
+{
+ char *buffer;
+ int ret;
+
+ buffer = rpath(ctx, path);
+ ret = lsetxattr(buffer, name, value, size, flags);
+ g_free(buffer);
+ return ret;
+}
+
+static inline int pt_removexattr(FsContext *ctx,
+ const char *path, const char *name)
+{
+ char *buffer;
+ int ret;
+
+ buffer = rpath(ctx, path);
+ ret = lremovexattr(path, name);
+ g_free(buffer);
+ return ret;
+}
+
+static inline ssize_t notsup_getxattr(FsContext *ctx, const char *path,
+ const char *name, void *value,
+ size_t size)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static inline int notsup_setxattr(FsContext *ctx, const char *path,
+ const char *name, void *value,
+ size_t size, int flags)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static inline ssize_t notsup_listxattr(FsContext *ctx, const char *path,
+ char *name, void *value, size_t size)
+{
+ return 0;
+}
+
+static inline int notsup_removexattr(FsContext *ctx,
+ const char *path, const char *name)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+#endif
diff --git a/hw/9pfs/virtio-9p.c b/hw/9pfs/virtio-9p.c
new file mode 100644
index 00000000..f972731f
--- /dev/null
+++ b/hw/9pfs/virtio-9p.c
@@ -0,0 +1,3300 @@
+/*
+ * Virtio 9p backend
+ *
+ * Copyright IBM, Corp. 2010
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/virtio.h"
+#include "hw/i386/pc.h"
+#include "qemu/error-report.h"
+#include "qemu/iov.h"
+#include "qemu/sockets.h"
+#include "virtio-9p.h"
+#include "fsdev/qemu-fsdev.h"
+#include "virtio-9p-xattr.h"
+#include "virtio-9p-coth.h"
+#include "trace.h"
+#include "migration/migration.h"
+
+int open_fd_hw;
+int total_open_fd;
+static int open_fd_rc;
+
+enum {
+ Oread = 0x00,
+ Owrite = 0x01,
+ Ordwr = 0x02,
+ Oexec = 0x03,
+ Oexcl = 0x04,
+ Otrunc = 0x10,
+ Orexec = 0x20,
+ Orclose = 0x40,
+ Oappend = 0x80,
+};
+
+static int omode_to_uflags(int8_t mode)
+{
+ int ret = 0;
+
+ switch (mode & 3) {
+ case Oread:
+ ret = O_RDONLY;
+ break;
+ case Ordwr:
+ ret = O_RDWR;
+ break;
+ case Owrite:
+ ret = O_WRONLY;
+ break;
+ case Oexec:
+ ret = O_RDONLY;
+ break;
+ }
+
+ if (mode & Otrunc) {
+ ret |= O_TRUNC;
+ }
+
+ if (mode & Oappend) {
+ ret |= O_APPEND;
+ }
+
+ if (mode & Oexcl) {
+ ret |= O_EXCL;
+ }
+
+ return ret;
+}
+
+struct dotl_openflag_map {
+ int dotl_flag;
+ int open_flag;
+};
+
+static int dotl_to_open_flags(int flags)
+{
+ int i;
+ /*
+ * We have same bits for P9_DOTL_READONLY, P9_DOTL_WRONLY
+ * and P9_DOTL_NOACCESS
+ */
+ int oflags = flags & O_ACCMODE;
+
+ struct dotl_openflag_map dotl_oflag_map[] = {
+ { P9_DOTL_CREATE, O_CREAT },
+ { P9_DOTL_EXCL, O_EXCL },
+ { P9_DOTL_NOCTTY , O_NOCTTY },
+ { P9_DOTL_TRUNC, O_TRUNC },
+ { P9_DOTL_APPEND, O_APPEND },
+ { P9_DOTL_NONBLOCK, O_NONBLOCK } ,
+ { P9_DOTL_DSYNC, O_DSYNC },
+ { P9_DOTL_FASYNC, FASYNC },
+ { P9_DOTL_DIRECT, O_DIRECT },
+ { P9_DOTL_LARGEFILE, O_LARGEFILE },
+ { P9_DOTL_DIRECTORY, O_DIRECTORY },
+ { P9_DOTL_NOFOLLOW, O_NOFOLLOW },
+ { P9_DOTL_NOATIME, O_NOATIME },
+ { P9_DOTL_SYNC, O_SYNC },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(dotl_oflag_map); i++) {
+ if (flags & dotl_oflag_map[i].dotl_flag) {
+ oflags |= dotl_oflag_map[i].open_flag;
+ }
+ }
+
+ return oflags;
+}
+
+void cred_init(FsCred *credp)
+{
+ credp->fc_uid = -1;
+ credp->fc_gid = -1;
+ credp->fc_mode = -1;
+ credp->fc_rdev = -1;
+}
+
+static int get_dotl_openflags(V9fsState *s, int oflags)
+{
+ int flags;
+ /*
+ * Filter the client open flags
+ */
+ flags = dotl_to_open_flags(oflags);
+ flags &= ~(O_NOCTTY | O_ASYNC | O_CREAT);
+ /*
+ * Ignore direct disk access hint until the server supports it.
+ */
+ flags &= ~O_DIRECT;
+ return flags;
+}
+
+void v9fs_path_init(V9fsPath *path)
+{
+ path->data = NULL;
+ path->size = 0;
+}
+
+void v9fs_path_free(V9fsPath *path)
+{
+ g_free(path->data);
+ path->data = NULL;
+ path->size = 0;
+}
+
+void v9fs_path_copy(V9fsPath *lhs, V9fsPath *rhs)
+{
+ v9fs_path_free(lhs);
+ lhs->data = g_malloc(rhs->size);
+ memcpy(lhs->data, rhs->data, rhs->size);
+ lhs->size = rhs->size;
+}
+
+int v9fs_name_to_path(V9fsState *s, V9fsPath *dirpath,
+ const char *name, V9fsPath *path)
+{
+ int err;
+ err = s->ops->name_to_path(&s->ctx, dirpath, name, path);
+ if (err < 0) {
+ err = -errno;
+ }
+ return err;
+}
+
+/*
+ * Return TRUE if s1 is an ancestor of s2.
+ *
+ * E.g. "a/b" is an ancestor of "a/b/c" but not of "a/bc/d".
+ * As a special case, We treat s1 as ancestor of s2 if they are same!
+ */
+static int v9fs_path_is_ancestor(V9fsPath *s1, V9fsPath *s2)
+{
+ if (!strncmp(s1->data, s2->data, s1->size - 1)) {
+ if (s2->data[s1->size - 1] == '\0' || s2->data[s1->size - 1] == '/') {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static size_t v9fs_string_size(V9fsString *str)
+{
+ return str->size;
+}
+
+/*
+ * returns 0 if fid got re-opened, 1 if not, < 0 on error */
+static int v9fs_reopen_fid(V9fsPDU *pdu, V9fsFidState *f)
+{
+ int err = 1;
+ if (f->fid_type == P9_FID_FILE) {
+ if (f->fs.fd == -1) {
+ do {
+ err = v9fs_co_open(pdu, f, f->open_flags);
+ } while (err == -EINTR && !pdu->cancelled);
+ }
+ } else if (f->fid_type == P9_FID_DIR) {
+ if (f->fs.dir == NULL) {
+ do {
+ err = v9fs_co_opendir(pdu, f);
+ } while (err == -EINTR && !pdu->cancelled);
+ }
+ }
+ return err;
+}
+
+static V9fsFidState *get_fid(V9fsPDU *pdu, int32_t fid)
+{
+ int err;
+ V9fsFidState *f;
+ V9fsState *s = pdu->s;
+
+ for (f = s->fid_list; f; f = f->next) {
+ BUG_ON(f->clunked);
+ if (f->fid == fid) {
+ /*
+ * Update the fid ref upfront so that
+ * we don't get reclaimed when we yield
+ * in open later.
+ */
+ f->ref++;
+ /*
+ * check whether we need to reopen the
+ * file. We might have closed the fd
+ * while trying to free up some file
+ * descriptors.
+ */
+ err = v9fs_reopen_fid(pdu, f);
+ if (err < 0) {
+ f->ref--;
+ return NULL;
+ }
+ /*
+ * Mark the fid as referenced so that the LRU
+ * reclaim won't close the file descriptor
+ */
+ f->flags |= FID_REFERENCED;
+ return f;
+ }
+ }
+ return NULL;
+}
+
+static V9fsFidState *alloc_fid(V9fsState *s, int32_t fid)
+{
+ V9fsFidState *f;
+
+ for (f = s->fid_list; f; f = f->next) {
+ /* If fid is already there return NULL */
+ BUG_ON(f->clunked);
+ if (f->fid == fid) {
+ return NULL;
+ }
+ }
+ f = g_malloc0(sizeof(V9fsFidState));
+ f->fid = fid;
+ f->fid_type = P9_FID_NONE;
+ f->ref = 1;
+ /*
+ * Mark the fid as referenced so that the LRU
+ * reclaim won't close the file descriptor
+ */
+ f->flags |= FID_REFERENCED;
+ f->next = s->fid_list;
+ s->fid_list = f;
+
+ return f;
+}
+
+static int v9fs_xattr_fid_clunk(V9fsPDU *pdu, V9fsFidState *fidp)
+{
+ int retval = 0;
+
+ if (fidp->fs.xattr.copied_len == -1) {
+ /* getxattr/listxattr fid */
+ goto free_value;
+ }
+ /*
+ * if this is fid for setxattr. clunk should
+ * result in setxattr localcall
+ */
+ if (fidp->fs.xattr.len != fidp->fs.xattr.copied_len) {
+ /* clunk after partial write */
+ retval = -EINVAL;
+ goto free_out;
+ }
+ if (fidp->fs.xattr.len) {
+ retval = v9fs_co_lsetxattr(pdu, &fidp->path, &fidp->fs.xattr.name,
+ fidp->fs.xattr.value,
+ fidp->fs.xattr.len,
+ fidp->fs.xattr.flags);
+ } else {
+ retval = v9fs_co_lremovexattr(pdu, &fidp->path, &fidp->fs.xattr.name);
+ }
+free_out:
+ v9fs_string_free(&fidp->fs.xattr.name);
+free_value:
+ g_free(fidp->fs.xattr.value);
+ return retval;
+}
+
+static int free_fid(V9fsPDU *pdu, V9fsFidState *fidp)
+{
+ int retval = 0;
+
+ if (fidp->fid_type == P9_FID_FILE) {
+ /* If we reclaimed the fd no need to close */
+ if (fidp->fs.fd != -1) {
+ retval = v9fs_co_close(pdu, &fidp->fs);
+ }
+ } else if (fidp->fid_type == P9_FID_DIR) {
+ if (fidp->fs.dir != NULL) {
+ retval = v9fs_co_closedir(pdu, &fidp->fs);
+ }
+ } else if (fidp->fid_type == P9_FID_XATTR) {
+ retval = v9fs_xattr_fid_clunk(pdu, fidp);
+ }
+ v9fs_path_free(&fidp->path);
+ g_free(fidp);
+ return retval;
+}
+
+static int put_fid(V9fsPDU *pdu, V9fsFidState *fidp)
+{
+ BUG_ON(!fidp->ref);
+ fidp->ref--;
+ /*
+ * Don't free the fid if it is in reclaim list
+ */
+ if (!fidp->ref && fidp->clunked) {
+ if (fidp->fid == pdu->s->root_fid) {
+ /*
+ * if the clunked fid is root fid then we
+ * have unmounted the fs on the client side.
+ * delete the migration blocker. Ideally, this
+ * should be hooked to transport close notification
+ */
+ if (pdu->s->migration_blocker) {
+ migrate_del_blocker(pdu->s->migration_blocker);
+ error_free(pdu->s->migration_blocker);
+ pdu->s->migration_blocker = NULL;
+ }
+ }
+ return free_fid(pdu, fidp);
+ }
+ return 0;
+}
+
+static V9fsFidState *clunk_fid(V9fsState *s, int32_t fid)
+{
+ V9fsFidState **fidpp, *fidp;
+
+ for (fidpp = &s->fid_list; *fidpp; fidpp = &(*fidpp)->next) {
+ if ((*fidpp)->fid == fid) {
+ break;
+ }
+ }
+ if (*fidpp == NULL) {
+ return NULL;
+ }
+ fidp = *fidpp;
+ *fidpp = fidp->next;
+ fidp->clunked = 1;
+ return fidp;
+}
+
+void v9fs_reclaim_fd(V9fsPDU *pdu)
+{
+ int reclaim_count = 0;
+ V9fsState *s = pdu->s;
+ V9fsFidState *f, *reclaim_list = NULL;
+
+ for (f = s->fid_list; f; f = f->next) {
+ /*
+ * Unlink fids cannot be reclaimed. Check
+ * for them and skip them. Also skip fids
+ * currently being operated on.
+ */
+ if (f->ref || f->flags & FID_NON_RECLAIMABLE) {
+ continue;
+ }
+ /*
+ * if it is a recently referenced fid
+ * we leave the fid untouched and clear the
+ * reference bit. We come back to it later
+ * in the next iteration. (a simple LRU without
+ * moving list elements around)
+ */
+ if (f->flags & FID_REFERENCED) {
+ f->flags &= ~FID_REFERENCED;
+ continue;
+ }
+ /*
+ * Add fids to reclaim list.
+ */
+ if (f->fid_type == P9_FID_FILE) {
+ if (f->fs.fd != -1) {
+ /*
+ * Up the reference count so that
+ * a clunk request won't free this fid
+ */
+ f->ref++;
+ f->rclm_lst = reclaim_list;
+ reclaim_list = f;
+ f->fs_reclaim.fd = f->fs.fd;
+ f->fs.fd = -1;
+ reclaim_count++;
+ }
+ } else if (f->fid_type == P9_FID_DIR) {
+ if (f->fs.dir != NULL) {
+ /*
+ * Up the reference count so that
+ * a clunk request won't free this fid
+ */
+ f->ref++;
+ f->rclm_lst = reclaim_list;
+ reclaim_list = f;
+ f->fs_reclaim.dir = f->fs.dir;
+ f->fs.dir = NULL;
+ reclaim_count++;
+ }
+ }
+ if (reclaim_count >= open_fd_rc) {
+ break;
+ }
+ }
+ /*
+ * Now close the fid in reclaim list. Free them if they
+ * are already clunked.
+ */
+ while (reclaim_list) {
+ f = reclaim_list;
+ reclaim_list = f->rclm_lst;
+ if (f->fid_type == P9_FID_FILE) {
+ v9fs_co_close(pdu, &f->fs_reclaim);
+ } else if (f->fid_type == P9_FID_DIR) {
+ v9fs_co_closedir(pdu, &f->fs_reclaim);
+ }
+ f->rclm_lst = NULL;
+ /*
+ * Now drop the fid reference, free it
+ * if clunked.
+ */
+ put_fid(pdu, f);
+ }
+}
+
+static int v9fs_mark_fids_unreclaim(V9fsPDU *pdu, V9fsPath *path)
+{
+ int err;
+ V9fsState *s = pdu->s;
+ V9fsFidState *fidp, head_fid;
+
+ head_fid.next = s->fid_list;
+ for (fidp = s->fid_list; fidp; fidp = fidp->next) {
+ if (fidp->path.size != path->size) {
+ continue;
+ }
+ if (!memcmp(fidp->path.data, path->data, path->size)) {
+ /* Mark the fid non reclaimable. */
+ fidp->flags |= FID_NON_RECLAIMABLE;
+
+ /* reopen the file/dir if already closed */
+ err = v9fs_reopen_fid(pdu, fidp);
+ if (err < 0) {
+ return -1;
+ }
+ /*
+ * Go back to head of fid list because
+ * the list could have got updated when
+ * switched to the worker thread
+ */
+ if (err == 0) {
+ fidp = &head_fid;
+ }
+ }
+ }
+ return 0;
+}
+
+static void virtfs_reset(V9fsPDU *pdu)
+{
+ V9fsState *s = pdu->s;
+ V9fsFidState *fidp = NULL;
+
+ /* Free all fids */
+ while (s->fid_list) {
+ fidp = s->fid_list;
+ s->fid_list = fidp->next;
+
+ if (fidp->ref) {
+ fidp->clunked = 1;
+ } else {
+ free_fid(pdu, fidp);
+ }
+ }
+ if (fidp) {
+ /* One or more unclunked fids found... */
+ error_report("9pfs:%s: One or more uncluncked fids "
+ "found during reset", __func__);
+ }
+}
+
+#define P9_QID_TYPE_DIR 0x80
+#define P9_QID_TYPE_SYMLINK 0x02
+
+#define P9_STAT_MODE_DIR 0x80000000
+#define P9_STAT_MODE_APPEND 0x40000000
+#define P9_STAT_MODE_EXCL 0x20000000
+#define P9_STAT_MODE_MOUNT 0x10000000
+#define P9_STAT_MODE_AUTH 0x08000000
+#define P9_STAT_MODE_TMP 0x04000000
+#define P9_STAT_MODE_SYMLINK 0x02000000
+#define P9_STAT_MODE_LINK 0x01000000
+#define P9_STAT_MODE_DEVICE 0x00800000
+#define P9_STAT_MODE_NAMED_PIPE 0x00200000
+#define P9_STAT_MODE_SOCKET 0x00100000
+#define P9_STAT_MODE_SETUID 0x00080000
+#define P9_STAT_MODE_SETGID 0x00040000
+#define P9_STAT_MODE_SETVTX 0x00010000
+
+#define P9_STAT_MODE_TYPE_BITS (P9_STAT_MODE_DIR | \
+ P9_STAT_MODE_SYMLINK | \
+ P9_STAT_MODE_LINK | \
+ P9_STAT_MODE_DEVICE | \
+ P9_STAT_MODE_NAMED_PIPE | \
+ P9_STAT_MODE_SOCKET)
+
+/* This is the algorithm from ufs in spfs */
+static void stat_to_qid(const struct stat *stbuf, V9fsQID *qidp)
+{
+ size_t size;
+
+ memset(&qidp->path, 0, sizeof(qidp->path));
+ size = MIN(sizeof(stbuf->st_ino), sizeof(qidp->path));
+ memcpy(&qidp->path, &stbuf->st_ino, size);
+ qidp->version = stbuf->st_mtime ^ (stbuf->st_size << 8);
+ qidp->type = 0;
+ if (S_ISDIR(stbuf->st_mode)) {
+ qidp->type |= P9_QID_TYPE_DIR;
+ }
+ if (S_ISLNK(stbuf->st_mode)) {
+ qidp->type |= P9_QID_TYPE_SYMLINK;
+ }
+}
+
+static int fid_to_qid(V9fsPDU *pdu, V9fsFidState *fidp, V9fsQID *qidp)
+{
+ struct stat stbuf;
+ int err;
+
+ err = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
+ if (err < 0) {
+ return err;
+ }
+ stat_to_qid(&stbuf, qidp);
+ return 0;
+}
+
+static V9fsPDU *alloc_pdu(V9fsState *s)
+{
+ V9fsPDU *pdu = NULL;
+
+ if (!QLIST_EMPTY(&s->free_list)) {
+ pdu = QLIST_FIRST(&s->free_list);
+ QLIST_REMOVE(pdu, next);
+ QLIST_INSERT_HEAD(&s->active_list, pdu, next);
+ }
+ return pdu;
+}
+
+static void free_pdu(V9fsState *s, V9fsPDU *pdu)
+{
+ if (pdu) {
+ /*
+ * Cancelled pdu are added back to the freelist
+ * by flush request .
+ */
+ if (!pdu->cancelled) {
+ QLIST_REMOVE(pdu, next);
+ QLIST_INSERT_HEAD(&s->free_list, pdu, next);
+ }
+ }
+}
+
+/*
+ * We don't do error checking for pdu_marshal/unmarshal here
+ * because we always expect to have enough space to encode
+ * error details
+ */
+static void complete_pdu(V9fsState *s, V9fsPDU *pdu, ssize_t len)
+{
+ int8_t id = pdu->id + 1; /* Response */
+
+ if (len < 0) {
+ int err = -len;
+ len = 7;
+
+ if (s->proto_version != V9FS_PROTO_2000L) {
+ V9fsString str;
+
+ str.data = strerror(err);
+ str.size = strlen(str.data);
+
+ len += pdu_marshal(pdu, len, "s", &str);
+ id = P9_RERROR;
+ }
+
+ len += pdu_marshal(pdu, len, "d", err);
+
+ if (s->proto_version == V9FS_PROTO_2000L) {
+ id = P9_RLERROR;
+ }
+ trace_v9fs_rerror(pdu->tag, pdu->id, err); /* Trace ERROR */
+ }
+
+ /* fill out the header */
+ pdu_marshal(pdu, 0, "dbw", (int32_t)len, id, pdu->tag);
+
+ /* keep these in sync */
+ pdu->size = len;
+ pdu->id = id;
+
+ /* push onto queue and notify */
+ virtqueue_push(s->vq, &pdu->elem, len);
+
+ /* FIXME: we should batch these completions */
+ virtio_notify(VIRTIO_DEVICE(s), s->vq);
+
+ /* Now wakeup anybody waiting in flush for this request */
+ qemu_co_queue_next(&pdu->complete);
+
+ free_pdu(s, pdu);
+}
+
+static mode_t v9mode_to_mode(uint32_t mode, V9fsString *extension)
+{
+ mode_t ret;
+
+ ret = mode & 0777;
+ if (mode & P9_STAT_MODE_DIR) {
+ ret |= S_IFDIR;
+ }
+
+ if (mode & P9_STAT_MODE_SYMLINK) {
+ ret |= S_IFLNK;
+ }
+ if (mode & P9_STAT_MODE_SOCKET) {
+ ret |= S_IFSOCK;
+ }
+ if (mode & P9_STAT_MODE_NAMED_PIPE) {
+ ret |= S_IFIFO;
+ }
+ if (mode & P9_STAT_MODE_DEVICE) {
+ if (extension->size && extension->data[0] == 'c') {
+ ret |= S_IFCHR;
+ } else {
+ ret |= S_IFBLK;
+ }
+ }
+
+ if (!(ret&~0777)) {
+ ret |= S_IFREG;
+ }
+
+ if (mode & P9_STAT_MODE_SETUID) {
+ ret |= S_ISUID;
+ }
+ if (mode & P9_STAT_MODE_SETGID) {
+ ret |= S_ISGID;
+ }
+ if (mode & P9_STAT_MODE_SETVTX) {
+ ret |= S_ISVTX;
+ }
+
+ return ret;
+}
+
+static int donttouch_stat(V9fsStat *stat)
+{
+ if (stat->type == -1 &&
+ stat->dev == -1 &&
+ stat->qid.type == -1 &&
+ stat->qid.version == -1 &&
+ stat->qid.path == -1 &&
+ stat->mode == -1 &&
+ stat->atime == -1 &&
+ stat->mtime == -1 &&
+ stat->length == -1 &&
+ !stat->name.size &&
+ !stat->uid.size &&
+ !stat->gid.size &&
+ !stat->muid.size &&
+ stat->n_uid == -1 &&
+ stat->n_gid == -1 &&
+ stat->n_muid == -1) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void v9fs_stat_init(V9fsStat *stat)
+{
+ v9fs_string_init(&stat->name);
+ v9fs_string_init(&stat->uid);
+ v9fs_string_init(&stat->gid);
+ v9fs_string_init(&stat->muid);
+ v9fs_string_init(&stat->extension);
+}
+
+static void v9fs_stat_free(V9fsStat *stat)
+{
+ v9fs_string_free(&stat->name);
+ v9fs_string_free(&stat->uid);
+ v9fs_string_free(&stat->gid);
+ v9fs_string_free(&stat->muid);
+ v9fs_string_free(&stat->extension);
+}
+
+static uint32_t stat_to_v9mode(const struct stat *stbuf)
+{
+ uint32_t mode;
+
+ mode = stbuf->st_mode & 0777;
+ if (S_ISDIR(stbuf->st_mode)) {
+ mode |= P9_STAT_MODE_DIR;
+ }
+
+ if (S_ISLNK(stbuf->st_mode)) {
+ mode |= P9_STAT_MODE_SYMLINK;
+ }
+
+ if (S_ISSOCK(stbuf->st_mode)) {
+ mode |= P9_STAT_MODE_SOCKET;
+ }
+
+ if (S_ISFIFO(stbuf->st_mode)) {
+ mode |= P9_STAT_MODE_NAMED_PIPE;
+ }
+
+ if (S_ISBLK(stbuf->st_mode) || S_ISCHR(stbuf->st_mode)) {
+ mode |= P9_STAT_MODE_DEVICE;
+ }
+
+ if (stbuf->st_mode & S_ISUID) {
+ mode |= P9_STAT_MODE_SETUID;
+ }
+
+ if (stbuf->st_mode & S_ISGID) {
+ mode |= P9_STAT_MODE_SETGID;
+ }
+
+ if (stbuf->st_mode & S_ISVTX) {
+ mode |= P9_STAT_MODE_SETVTX;
+ }
+
+ return mode;
+}
+
+static int stat_to_v9stat(V9fsPDU *pdu, V9fsPath *name,
+ const struct stat *stbuf,
+ V9fsStat *v9stat)
+{
+ int err;
+ const char *str;
+
+ memset(v9stat, 0, sizeof(*v9stat));
+
+ stat_to_qid(stbuf, &v9stat->qid);
+ v9stat->mode = stat_to_v9mode(stbuf);
+ v9stat->atime = stbuf->st_atime;
+ v9stat->mtime = stbuf->st_mtime;
+ v9stat->length = stbuf->st_size;
+
+ v9fs_string_null(&v9stat->uid);
+ v9fs_string_null(&v9stat->gid);
+ v9fs_string_null(&v9stat->muid);
+
+ v9stat->n_uid = stbuf->st_uid;
+ v9stat->n_gid = stbuf->st_gid;
+ v9stat->n_muid = 0;
+
+ v9fs_string_null(&v9stat->extension);
+
+ if (v9stat->mode & P9_STAT_MODE_SYMLINK) {
+ err = v9fs_co_readlink(pdu, name, &v9stat->extension);
+ if (err < 0) {
+ return err;
+ }
+ } else if (v9stat->mode & P9_STAT_MODE_DEVICE) {
+ v9fs_string_sprintf(&v9stat->extension, "%c %u %u",
+ S_ISCHR(stbuf->st_mode) ? 'c' : 'b',
+ major(stbuf->st_rdev), minor(stbuf->st_rdev));
+ } else if (S_ISDIR(stbuf->st_mode) || S_ISREG(stbuf->st_mode)) {
+ v9fs_string_sprintf(&v9stat->extension, "%s %lu",
+ "HARDLINKCOUNT", (unsigned long)stbuf->st_nlink);
+ }
+
+ str = strrchr(name->data, '/');
+ if (str) {
+ str += 1;
+ } else {
+ str = name->data;
+ }
+
+ v9fs_string_sprintf(&v9stat->name, "%s", str);
+
+ v9stat->size = 61 +
+ v9fs_string_size(&v9stat->name) +
+ v9fs_string_size(&v9stat->uid) +
+ v9fs_string_size(&v9stat->gid) +
+ v9fs_string_size(&v9stat->muid) +
+ v9fs_string_size(&v9stat->extension);
+ return 0;
+}
+
+#define P9_STATS_MODE 0x00000001ULL
+#define P9_STATS_NLINK 0x00000002ULL
+#define P9_STATS_UID 0x00000004ULL
+#define P9_STATS_GID 0x00000008ULL
+#define P9_STATS_RDEV 0x00000010ULL
+#define P9_STATS_ATIME 0x00000020ULL
+#define P9_STATS_MTIME 0x00000040ULL
+#define P9_STATS_CTIME 0x00000080ULL
+#define P9_STATS_INO 0x00000100ULL
+#define P9_STATS_SIZE 0x00000200ULL
+#define P9_STATS_BLOCKS 0x00000400ULL
+
+#define P9_STATS_BTIME 0x00000800ULL
+#define P9_STATS_GEN 0x00001000ULL
+#define P9_STATS_DATA_VERSION 0x00002000ULL
+
+#define P9_STATS_BASIC 0x000007ffULL /* Mask for fields up to BLOCKS */
+#define P9_STATS_ALL 0x00003fffULL /* Mask for All fields above */
+
+
+static void stat_to_v9stat_dotl(V9fsState *s, const struct stat *stbuf,
+ V9fsStatDotl *v9lstat)
+{
+ memset(v9lstat, 0, sizeof(*v9lstat));
+
+ v9lstat->st_mode = stbuf->st_mode;
+ v9lstat->st_nlink = stbuf->st_nlink;
+ v9lstat->st_uid = stbuf->st_uid;
+ v9lstat->st_gid = stbuf->st_gid;
+ v9lstat->st_rdev = stbuf->st_rdev;
+ v9lstat->st_size = stbuf->st_size;
+ v9lstat->st_blksize = stbuf->st_blksize;
+ v9lstat->st_blocks = stbuf->st_blocks;
+ v9lstat->st_atime_sec = stbuf->st_atime;
+ v9lstat->st_atime_nsec = stbuf->st_atim.tv_nsec;
+ v9lstat->st_mtime_sec = stbuf->st_mtime;
+ v9lstat->st_mtime_nsec = stbuf->st_mtim.tv_nsec;
+ v9lstat->st_ctime_sec = stbuf->st_ctime;
+ v9lstat->st_ctime_nsec = stbuf->st_ctim.tv_nsec;
+ /* Currently we only support BASIC fields in stat */
+ v9lstat->st_result_mask = P9_STATS_BASIC;
+
+ stat_to_qid(stbuf, &v9lstat->qid);
+}
+
+static void print_sg(struct iovec *sg, int cnt)
+{
+ int i;
+
+ printf("sg[%d]: {", cnt);
+ for (i = 0; i < cnt; i++) {
+ if (i) {
+ printf(", ");
+ }
+ printf("(%p, %zd)", sg[i].iov_base, sg[i].iov_len);
+ }
+ printf("}\n");
+}
+
+/* Will call this only for path name based fid */
+static void v9fs_fix_path(V9fsPath *dst, V9fsPath *src, int len)
+{
+ V9fsPath str;
+ v9fs_path_init(&str);
+ v9fs_path_copy(&str, dst);
+ v9fs_string_sprintf((V9fsString *)dst, "%s%s", src->data, str.data+len);
+ v9fs_path_free(&str);
+ /* +1 to include terminating NULL */
+ dst->size++;
+}
+
+static inline bool is_ro_export(FsContext *ctx)
+{
+ return ctx->export_flags & V9FS_RDONLY;
+}
+
+static void v9fs_version(void *opaque)
+{
+ ssize_t err;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+ V9fsString version;
+ size_t offset = 7;
+
+ v9fs_string_init(&version);
+ err = pdu_unmarshal(pdu, offset, "ds", &s->msize, &version);
+ if (err < 0) {
+ offset = err;
+ goto out;
+ }
+ trace_v9fs_version(pdu->tag, pdu->id, s->msize, version.data);
+
+ virtfs_reset(pdu);
+
+ if (!strcmp(version.data, "9P2000.u")) {
+ s->proto_version = V9FS_PROTO_2000U;
+ } else if (!strcmp(version.data, "9P2000.L")) {
+ s->proto_version = V9FS_PROTO_2000L;
+ } else {
+ v9fs_string_sprintf(&version, "unknown");
+ }
+
+ err = pdu_marshal(pdu, offset, "ds", s->msize, &version);
+ if (err < 0) {
+ offset = err;
+ goto out;
+ }
+ offset += err;
+ trace_v9fs_version_return(pdu->tag, pdu->id, s->msize, version.data);
+out:
+ complete_pdu(s, pdu, offset);
+ v9fs_string_free(&version);
+}
+
+static void v9fs_attach(void *opaque)
+{
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+ int32_t fid, afid, n_uname;
+ V9fsString uname, aname;
+ V9fsFidState *fidp;
+ size_t offset = 7;
+ V9fsQID qid;
+ ssize_t err;
+
+ v9fs_string_init(&uname);
+ v9fs_string_init(&aname);
+ err = pdu_unmarshal(pdu, offset, "ddssd", &fid,
+ &afid, &uname, &aname, &n_uname);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_attach(pdu->tag, pdu->id, fid, afid, uname.data, aname.data);
+
+ fidp = alloc_fid(s, fid);
+ if (fidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ fidp->uid = n_uname;
+ err = v9fs_co_name_to_path(pdu, NULL, "/", &fidp->path);
+ if (err < 0) {
+ err = -EINVAL;
+ clunk_fid(s, fid);
+ goto out;
+ }
+ err = fid_to_qid(pdu, fidp, &qid);
+ if (err < 0) {
+ err = -EINVAL;
+ clunk_fid(s, fid);
+ goto out;
+ }
+ err = pdu_marshal(pdu, offset, "Q", &qid);
+ if (err < 0) {
+ clunk_fid(s, fid);
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_attach_return(pdu->tag, pdu->id,
+ qid.type, qid.version, qid.path);
+ /*
+ * disable migration if we haven't done already.
+ * attach could get called multiple times for the same export.
+ */
+ if (!s->migration_blocker) {
+ s->root_fid = fid;
+ error_setg(&s->migration_blocker,
+ "Migration is disabled when VirtFS export path '%s' is mounted in the guest using mount_tag '%s'",
+ s->ctx.fs_root ? s->ctx.fs_root : "NULL", s->tag);
+ migrate_add_blocker(s->migration_blocker);
+ }
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+ v9fs_string_free(&uname);
+ v9fs_string_free(&aname);
+}
+
+static void v9fs_stat(void *opaque)
+{
+ int32_t fid;
+ V9fsStat v9stat;
+ ssize_t err = 0;
+ size_t offset = 7;
+ struct stat stbuf;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ err = pdu_unmarshal(pdu, offset, "d", &fid);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_stat(pdu->tag, pdu->id, fid);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ err = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ err = stat_to_v9stat(pdu, &fidp->path, &stbuf, &v9stat);
+ if (err < 0) {
+ goto out;
+ }
+ err = pdu_marshal(pdu, offset, "wS", 0, &v9stat);
+ if (err < 0) {
+ v9fs_stat_free(&v9stat);
+ goto out;
+ }
+ trace_v9fs_stat_return(pdu->tag, pdu->id, v9stat.mode,
+ v9stat.atime, v9stat.mtime, v9stat.length);
+ err += offset;
+ v9fs_stat_free(&v9stat);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+}
+
+static void v9fs_getattr(void *opaque)
+{
+ int32_t fid;
+ size_t offset = 7;
+ ssize_t retval = 0;
+ struct stat stbuf;
+ V9fsFidState *fidp;
+ uint64_t request_mask;
+ V9fsStatDotl v9stat_dotl;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ retval = pdu_unmarshal(pdu, offset, "dq", &fid, &request_mask);
+ if (retval < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_getattr(pdu->tag, pdu->id, fid, request_mask);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ retval = -ENOENT;
+ goto out_nofid;
+ }
+ /*
+ * Currently we only support BASIC fields in stat, so there is no
+ * need to look at request_mask.
+ */
+ retval = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
+ if (retval < 0) {
+ goto out;
+ }
+ stat_to_v9stat_dotl(s, &stbuf, &v9stat_dotl);
+
+ /* fill st_gen if requested and supported by underlying fs */
+ if (request_mask & P9_STATS_GEN) {
+ retval = v9fs_co_st_gen(pdu, &fidp->path, stbuf.st_mode, &v9stat_dotl);
+ switch (retval) {
+ case 0:
+ /* we have valid st_gen: update result mask */
+ v9stat_dotl.st_result_mask |= P9_STATS_GEN;
+ break;
+ case -EINTR:
+ /* request cancelled, e.g. by Tflush */
+ goto out;
+ default:
+ /* failed to get st_gen: not fatal, ignore */
+ break;
+ }
+ }
+ retval = pdu_marshal(pdu, offset, "A", &v9stat_dotl);
+ if (retval < 0) {
+ goto out;
+ }
+ retval += offset;
+ trace_v9fs_getattr_return(pdu->tag, pdu->id, v9stat_dotl.st_result_mask,
+ v9stat_dotl.st_mode, v9stat_dotl.st_uid,
+ v9stat_dotl.st_gid);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, retval);
+}
+
+/* Attribute flags */
+#define P9_ATTR_MODE (1 << 0)
+#define P9_ATTR_UID (1 << 1)
+#define P9_ATTR_GID (1 << 2)
+#define P9_ATTR_SIZE (1 << 3)
+#define P9_ATTR_ATIME (1 << 4)
+#define P9_ATTR_MTIME (1 << 5)
+#define P9_ATTR_CTIME (1 << 6)
+#define P9_ATTR_ATIME_SET (1 << 7)
+#define P9_ATTR_MTIME_SET (1 << 8)
+
+#define P9_ATTR_MASK 127
+
+static void v9fs_setattr(void *opaque)
+{
+ int err = 0;
+ int32_t fid;
+ V9fsFidState *fidp;
+ size_t offset = 7;
+ V9fsIattr v9iattr;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ err = pdu_unmarshal(pdu, offset, "dI", &fid, &v9iattr);
+ if (err < 0) {
+ goto out_nofid;
+ }
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ if (v9iattr.valid & P9_ATTR_MODE) {
+ err = v9fs_co_chmod(pdu, &fidp->path, v9iattr.mode);
+ if (err < 0) {
+ goto out;
+ }
+ }
+ if (v9iattr.valid & (P9_ATTR_ATIME | P9_ATTR_MTIME)) {
+ struct timespec times[2];
+ if (v9iattr.valid & P9_ATTR_ATIME) {
+ if (v9iattr.valid & P9_ATTR_ATIME_SET) {
+ times[0].tv_sec = v9iattr.atime_sec;
+ times[0].tv_nsec = v9iattr.atime_nsec;
+ } else {
+ times[0].tv_nsec = UTIME_NOW;
+ }
+ } else {
+ times[0].tv_nsec = UTIME_OMIT;
+ }
+ if (v9iattr.valid & P9_ATTR_MTIME) {
+ if (v9iattr.valid & P9_ATTR_MTIME_SET) {
+ times[1].tv_sec = v9iattr.mtime_sec;
+ times[1].tv_nsec = v9iattr.mtime_nsec;
+ } else {
+ times[1].tv_nsec = UTIME_NOW;
+ }
+ } else {
+ times[1].tv_nsec = UTIME_OMIT;
+ }
+ err = v9fs_co_utimensat(pdu, &fidp->path, times);
+ if (err < 0) {
+ goto out;
+ }
+ }
+ /*
+ * If the only valid entry in iattr is ctime we can call
+ * chown(-1,-1) to update the ctime of the file
+ */
+ if ((v9iattr.valid & (P9_ATTR_UID | P9_ATTR_GID)) ||
+ ((v9iattr.valid & P9_ATTR_CTIME)
+ && !((v9iattr.valid & P9_ATTR_MASK) & ~P9_ATTR_CTIME))) {
+ if (!(v9iattr.valid & P9_ATTR_UID)) {
+ v9iattr.uid = -1;
+ }
+ if (!(v9iattr.valid & P9_ATTR_GID)) {
+ v9iattr.gid = -1;
+ }
+ err = v9fs_co_chown(pdu, &fidp->path, v9iattr.uid,
+ v9iattr.gid);
+ if (err < 0) {
+ goto out;
+ }
+ }
+ if (v9iattr.valid & (P9_ATTR_SIZE)) {
+ err = v9fs_co_truncate(pdu, &fidp->path, v9iattr.size);
+ if (err < 0) {
+ goto out;
+ }
+ }
+ err = offset;
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+}
+
+static int v9fs_walk_marshal(V9fsPDU *pdu, uint16_t nwnames, V9fsQID *qids)
+{
+ int i;
+ ssize_t err;
+ size_t offset = 7;
+
+ err = pdu_marshal(pdu, offset, "w", nwnames);
+ if (err < 0) {
+ return err;
+ }
+ offset += err;
+ for (i = 0; i < nwnames; i++) {
+ err = pdu_marshal(pdu, offset, "Q", &qids[i]);
+ if (err < 0) {
+ return err;
+ }
+ offset += err;
+ }
+ return offset;
+}
+
+static void v9fs_walk(void *opaque)
+{
+ int name_idx;
+ V9fsQID *qids = NULL;
+ int i, err = 0;
+ V9fsPath dpath, path;
+ uint16_t nwnames;
+ struct stat stbuf;
+ size_t offset = 7;
+ int32_t fid, newfid;
+ V9fsString *wnames = NULL;
+ V9fsFidState *fidp;
+ V9fsFidState *newfidp = NULL;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ err = pdu_unmarshal(pdu, offset, "ddw", &fid, &newfid, &nwnames);
+ if (err < 0) {
+ complete_pdu(s, pdu, err);
+ return ;
+ }
+ offset += err;
+
+ trace_v9fs_walk(pdu->tag, pdu->id, fid, newfid, nwnames);
+
+ if (nwnames && nwnames <= P9_MAXWELEM) {
+ wnames = g_malloc0(sizeof(wnames[0]) * nwnames);
+ qids = g_malloc0(sizeof(qids[0]) * nwnames);
+ for (i = 0; i < nwnames; i++) {
+ err = pdu_unmarshal(pdu, offset, "s", &wnames[i]);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ offset += err;
+ }
+ } else if (nwnames > P9_MAXWELEM) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ v9fs_path_init(&dpath);
+ v9fs_path_init(&path);
+ /*
+ * Both dpath and path initially poin to fidp.
+ * Needed to handle request with nwnames == 0
+ */
+ v9fs_path_copy(&dpath, &fidp->path);
+ v9fs_path_copy(&path, &fidp->path);
+ for (name_idx = 0; name_idx < nwnames; name_idx++) {
+ err = v9fs_co_name_to_path(pdu, &dpath, wnames[name_idx].data, &path);
+ if (err < 0) {
+ goto out;
+ }
+ err = v9fs_co_lstat(pdu, &path, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ stat_to_qid(&stbuf, &qids[name_idx]);
+ v9fs_path_copy(&dpath, &path);
+ }
+ if (fid == newfid) {
+ BUG_ON(fidp->fid_type != P9_FID_NONE);
+ v9fs_path_copy(&fidp->path, &path);
+ } else {
+ newfidp = alloc_fid(s, newfid);
+ if (newfidp == NULL) {
+ err = -EINVAL;
+ goto out;
+ }
+ newfidp->uid = fidp->uid;
+ v9fs_path_copy(&newfidp->path, &path);
+ }
+ err = v9fs_walk_marshal(pdu, nwnames, qids);
+ trace_v9fs_walk_return(pdu->tag, pdu->id, nwnames, qids);
+out:
+ put_fid(pdu, fidp);
+ if (newfidp) {
+ put_fid(pdu, newfidp);
+ }
+ v9fs_path_free(&dpath);
+ v9fs_path_free(&path);
+out_nofid:
+ complete_pdu(s, pdu, err);
+ if (nwnames && nwnames <= P9_MAXWELEM) {
+ for (name_idx = 0; name_idx < nwnames; name_idx++) {
+ v9fs_string_free(&wnames[name_idx]);
+ }
+ g_free(wnames);
+ g_free(qids);
+ }
+}
+
+static int32_t get_iounit(V9fsPDU *pdu, V9fsPath *path)
+{
+ struct statfs stbuf;
+ int32_t iounit = 0;
+ V9fsState *s = pdu->s;
+
+ /*
+ * iounit should be multiples of f_bsize (host filesystem block size
+ * and as well as less than (client msize - P9_IOHDRSZ))
+ */
+ if (!v9fs_co_statfs(pdu, path, &stbuf)) {
+ iounit = stbuf.f_bsize;
+ iounit *= (s->msize - P9_IOHDRSZ)/stbuf.f_bsize;
+ }
+ if (!iounit) {
+ iounit = s->msize - P9_IOHDRSZ;
+ }
+ return iounit;
+}
+
+static void v9fs_open(void *opaque)
+{
+ int flags;
+ int32_t fid;
+ int32_t mode;
+ V9fsQID qid;
+ int iounit = 0;
+ ssize_t err = 0;
+ size_t offset = 7;
+ struct stat stbuf;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ if (s->proto_version == V9FS_PROTO_2000L) {
+ err = pdu_unmarshal(pdu, offset, "dd", &fid, &mode);
+ } else {
+ uint8_t modebyte;
+ err = pdu_unmarshal(pdu, offset, "db", &fid, &modebyte);
+ mode = modebyte;
+ }
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_open(pdu->tag, pdu->id, fid, mode);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ BUG_ON(fidp->fid_type != P9_FID_NONE);
+
+ err = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ stat_to_qid(&stbuf, &qid);
+ if (S_ISDIR(stbuf.st_mode)) {
+ err = v9fs_co_opendir(pdu, fidp);
+ if (err < 0) {
+ goto out;
+ }
+ fidp->fid_type = P9_FID_DIR;
+ err = pdu_marshal(pdu, offset, "Qd", &qid, 0);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ } else {
+ if (s->proto_version == V9FS_PROTO_2000L) {
+ flags = get_dotl_openflags(s, mode);
+ } else {
+ flags = omode_to_uflags(mode);
+ }
+ if (is_ro_export(&s->ctx)) {
+ if (mode & O_WRONLY || mode & O_RDWR ||
+ mode & O_APPEND || mode & O_TRUNC) {
+ err = -EROFS;
+ goto out;
+ }
+ }
+ err = v9fs_co_open(pdu, fidp, flags);
+ if (err < 0) {
+ goto out;
+ }
+ fidp->fid_type = P9_FID_FILE;
+ fidp->open_flags = flags;
+ if (flags & O_EXCL) {
+ /*
+ * We let the host file system do O_EXCL check
+ * We should not reclaim such fd
+ */
+ fidp->flags |= FID_NON_RECLAIMABLE;
+ }
+ iounit = get_iounit(pdu, &fidp->path);
+ err = pdu_marshal(pdu, offset, "Qd", &qid, iounit);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ }
+ trace_v9fs_open_return(pdu->tag, pdu->id,
+ qid.type, qid.version, qid.path, iounit);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+}
+
+static void v9fs_lcreate(void *opaque)
+{
+ int32_t dfid, flags, mode;
+ gid_t gid;
+ ssize_t err = 0;
+ ssize_t offset = 7;
+ V9fsString name;
+ V9fsFidState *fidp;
+ struct stat stbuf;
+ V9fsQID qid;
+ int32_t iounit;
+ V9fsPDU *pdu = opaque;
+
+ v9fs_string_init(&name);
+ err = pdu_unmarshal(pdu, offset, "dsddd", &dfid,
+ &name, &flags, &mode, &gid);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_lcreate(pdu->tag, pdu->id, dfid, flags, mode, gid);
+
+ fidp = get_fid(pdu, dfid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+
+ flags = get_dotl_openflags(pdu->s, flags);
+ err = v9fs_co_open2(pdu, fidp, &name, gid,
+ flags | O_CREAT, mode, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ fidp->fid_type = P9_FID_FILE;
+ fidp->open_flags = flags;
+ if (flags & O_EXCL) {
+ /*
+ * We let the host file system do O_EXCL check
+ * We should not reclaim such fd
+ */
+ fidp->flags |= FID_NON_RECLAIMABLE;
+ }
+ iounit = get_iounit(pdu, &fidp->path);
+ stat_to_qid(&stbuf, &qid);
+ err = pdu_marshal(pdu, offset, "Qd", &qid, iounit);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_lcreate_return(pdu->tag, pdu->id,
+ qid.type, qid.version, qid.path, iounit);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(pdu->s, pdu, err);
+ v9fs_string_free(&name);
+}
+
+static void v9fs_fsync(void *opaque)
+{
+ int err;
+ int32_t fid;
+ int datasync;
+ size_t offset = 7;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ err = pdu_unmarshal(pdu, offset, "dd", &fid, &datasync);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_fsync(pdu->tag, pdu->id, fid, datasync);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ err = v9fs_co_fsync(pdu, fidp, datasync);
+ if (!err) {
+ err = offset;
+ }
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+}
+
+static void v9fs_clunk(void *opaque)
+{
+ int err;
+ int32_t fid;
+ size_t offset = 7;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ err = pdu_unmarshal(pdu, offset, "d", &fid);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_clunk(pdu->tag, pdu->id, fid);
+
+ fidp = clunk_fid(s, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ /*
+ * Bump the ref so that put_fid will
+ * free the fid.
+ */
+ fidp->ref++;
+ err = put_fid(pdu, fidp);
+ if (!err) {
+ err = offset;
+ }
+out_nofid:
+ complete_pdu(s, pdu, err);
+}
+
+static int v9fs_xattr_read(V9fsState *s, V9fsPDU *pdu, V9fsFidState *fidp,
+ uint64_t off, uint32_t max_count)
+{
+ ssize_t err;
+ size_t offset = 7;
+ int read_count;
+ int64_t xattr_len;
+
+ xattr_len = fidp->fs.xattr.len;
+ read_count = xattr_len - off;
+ if (read_count > max_count) {
+ read_count = max_count;
+ } else if (read_count < 0) {
+ /*
+ * read beyond XATTR value
+ */
+ read_count = 0;
+ }
+ err = pdu_marshal(pdu, offset, "d", read_count);
+ if (err < 0) {
+ return err;
+ }
+ offset += err;
+ err = v9fs_pack(pdu->elem.in_sg, pdu->elem.in_num, offset,
+ ((char *)fidp->fs.xattr.value) + off,
+ read_count);
+ if (err < 0) {
+ return err;
+ }
+ offset += err;
+ return offset;
+}
+
+static int v9fs_do_readdir_with_stat(V9fsPDU *pdu,
+ V9fsFidState *fidp, uint32_t max_count)
+{
+ V9fsPath path;
+ V9fsStat v9stat;
+ int len, err = 0;
+ int32_t count = 0;
+ struct stat stbuf;
+ off_t saved_dir_pos;
+ struct dirent *dent, *result;
+
+ /* save the directory position */
+ saved_dir_pos = v9fs_co_telldir(pdu, fidp);
+ if (saved_dir_pos < 0) {
+ return saved_dir_pos;
+ }
+
+ dent = g_malloc(sizeof(struct dirent));
+
+ while (1) {
+ v9fs_path_init(&path);
+ err = v9fs_co_readdir_r(pdu, fidp, dent, &result);
+ if (err || !result) {
+ break;
+ }
+ err = v9fs_co_name_to_path(pdu, &fidp->path, dent->d_name, &path);
+ if (err < 0) {
+ goto out;
+ }
+ err = v9fs_co_lstat(pdu, &path, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ err = stat_to_v9stat(pdu, &path, &stbuf, &v9stat);
+ if (err < 0) {
+ goto out;
+ }
+ /* 11 = 7 + 4 (7 = start offset, 4 = space for storing count) */
+ len = pdu_marshal(pdu, 11 + count, "S", &v9stat);
+ if ((len != (v9stat.size + 2)) || ((count + len) > max_count)) {
+ /* Ran out of buffer. Set dir back to old position and return */
+ v9fs_co_seekdir(pdu, fidp, saved_dir_pos);
+ v9fs_stat_free(&v9stat);
+ v9fs_path_free(&path);
+ g_free(dent);
+ return count;
+ }
+ count += len;
+ v9fs_stat_free(&v9stat);
+ v9fs_path_free(&path);
+ saved_dir_pos = dent->d_off;
+ }
+out:
+ g_free(dent);
+ v9fs_path_free(&path);
+ if (err < 0) {
+ return err;
+ }
+ return count;
+}
+
+/*
+ * Create a QEMUIOVector for a sub-region of PDU iovecs
+ *
+ * @qiov: uninitialized QEMUIOVector
+ * @skip: number of bytes to skip from beginning of PDU
+ * @size: number of bytes to include
+ * @is_write: true - write, false - read
+ *
+ * The resulting QEMUIOVector has heap-allocated iovecs and must be cleaned up
+ * with qemu_iovec_destroy().
+ */
+static void v9fs_init_qiov_from_pdu(QEMUIOVector *qiov, V9fsPDU *pdu,
+ size_t skip, size_t size,
+ bool is_write)
+{
+ QEMUIOVector elem;
+ struct iovec *iov;
+ unsigned int niov;
+
+ if (is_write) {
+ iov = pdu->elem.out_sg;
+ niov = pdu->elem.out_num;
+ } else {
+ iov = pdu->elem.in_sg;
+ niov = pdu->elem.in_num;
+ }
+
+ qemu_iovec_init_external(&elem, iov, niov);
+ qemu_iovec_init(qiov, niov);
+ qemu_iovec_concat(qiov, &elem, skip, size);
+}
+
+static void v9fs_read(void *opaque)
+{
+ int32_t fid;
+ uint64_t off;
+ ssize_t err = 0;
+ int32_t count = 0;
+ size_t offset = 7;
+ uint32_t max_count;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ err = pdu_unmarshal(pdu, offset, "dqd", &fid, &off, &max_count);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_read(pdu->tag, pdu->id, fid, off, max_count);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ if (fidp->fid_type == P9_FID_DIR) {
+
+ if (off == 0) {
+ v9fs_co_rewinddir(pdu, fidp);
+ }
+ count = v9fs_do_readdir_with_stat(pdu, fidp, max_count);
+ if (count < 0) {
+ err = count;
+ goto out;
+ }
+ err = pdu_marshal(pdu, offset, "d", count);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset + count;
+ } else if (fidp->fid_type == P9_FID_FILE) {
+ QEMUIOVector qiov_full;
+ QEMUIOVector qiov;
+ int32_t len;
+
+ v9fs_init_qiov_from_pdu(&qiov_full, pdu, offset + 4, max_count, false);
+ qemu_iovec_init(&qiov, qiov_full.niov);
+ do {
+ qemu_iovec_reset(&qiov);
+ qemu_iovec_concat(&qiov, &qiov_full, count, qiov_full.size - count);
+ if (0) {
+ print_sg(qiov.iov, qiov.niov);
+ }
+ /* Loop in case of EINTR */
+ do {
+ len = v9fs_co_preadv(pdu, fidp, qiov.iov, qiov.niov, off);
+ if (len >= 0) {
+ off += len;
+ count += len;
+ }
+ } while (len == -EINTR && !pdu->cancelled);
+ if (len < 0) {
+ /* IO error return the error */
+ err = len;
+ goto out;
+ }
+ } while (count < max_count && len > 0);
+ err = pdu_marshal(pdu, offset, "d", count);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset + count;
+ qemu_iovec_destroy(&qiov);
+ qemu_iovec_destroy(&qiov_full);
+ } else if (fidp->fid_type == P9_FID_XATTR) {
+ err = v9fs_xattr_read(s, pdu, fidp, off, max_count);
+ } else {
+ err = -EINVAL;
+ }
+ trace_v9fs_read_return(pdu->tag, pdu->id, count, err);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+}
+
+static size_t v9fs_readdir_data_size(V9fsString *name)
+{
+ /*
+ * Size of each dirent on the wire: size of qid (13) + size of offset (8)
+ * size of type (1) + size of name.size (2) + strlen(name.data)
+ */
+ return 24 + v9fs_string_size(name);
+}
+
+static int v9fs_do_readdir(V9fsPDU *pdu,
+ V9fsFidState *fidp, int32_t max_count)
+{
+ size_t size;
+ V9fsQID qid;
+ V9fsString name;
+ int len, err = 0;
+ int32_t count = 0;
+ off_t saved_dir_pos;
+ struct dirent *dent, *result;
+
+ /* save the directory position */
+ saved_dir_pos = v9fs_co_telldir(pdu, fidp);
+ if (saved_dir_pos < 0) {
+ return saved_dir_pos;
+ }
+
+ dent = g_malloc(sizeof(struct dirent));
+
+ while (1) {
+ err = v9fs_co_readdir_r(pdu, fidp, dent, &result);
+ if (err || !result) {
+ break;
+ }
+ v9fs_string_init(&name);
+ v9fs_string_sprintf(&name, "%s", dent->d_name);
+ if ((count + v9fs_readdir_data_size(&name)) > max_count) {
+ /* Ran out of buffer. Set dir back to old position and return */
+ v9fs_co_seekdir(pdu, fidp, saved_dir_pos);
+ v9fs_string_free(&name);
+ g_free(dent);
+ return count;
+ }
+ /*
+ * Fill up just the path field of qid because the client uses
+ * only that. To fill the entire qid structure we will have
+ * to stat each dirent found, which is expensive
+ */
+ size = MIN(sizeof(dent->d_ino), sizeof(qid.path));
+ memcpy(&qid.path, &dent->d_ino, size);
+ /* Fill the other fields with dummy values */
+ qid.type = 0;
+ qid.version = 0;
+
+ /* 11 = 7 + 4 (7 = start offset, 4 = space for storing count) */
+ len = pdu_marshal(pdu, 11 + count, "Qqbs",
+ &qid, dent->d_off,
+ dent->d_type, &name);
+ if (len < 0) {
+ v9fs_co_seekdir(pdu, fidp, saved_dir_pos);
+ v9fs_string_free(&name);
+ g_free(dent);
+ return len;
+ }
+ count += len;
+ v9fs_string_free(&name);
+ saved_dir_pos = dent->d_off;
+ }
+ g_free(dent);
+ if (err < 0) {
+ return err;
+ }
+ return count;
+}
+
+static void v9fs_readdir(void *opaque)
+{
+ int32_t fid;
+ V9fsFidState *fidp;
+ ssize_t retval = 0;
+ size_t offset = 7;
+ uint64_t initial_offset;
+ int32_t count;
+ uint32_t max_count;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ retval = pdu_unmarshal(pdu, offset, "dqd", &fid,
+ &initial_offset, &max_count);
+ if (retval < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_readdir(pdu->tag, pdu->id, fid, initial_offset, max_count);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ retval = -EINVAL;
+ goto out_nofid;
+ }
+ if (!fidp->fs.dir) {
+ retval = -EINVAL;
+ goto out;
+ }
+ if (initial_offset == 0) {
+ v9fs_co_rewinddir(pdu, fidp);
+ } else {
+ v9fs_co_seekdir(pdu, fidp, initial_offset);
+ }
+ count = v9fs_do_readdir(pdu, fidp, max_count);
+ if (count < 0) {
+ retval = count;
+ goto out;
+ }
+ retval = pdu_marshal(pdu, offset, "d", count);
+ if (retval < 0) {
+ goto out;
+ }
+ retval += count + offset;
+ trace_v9fs_readdir_return(pdu->tag, pdu->id, count, retval);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, retval);
+}
+
+static int v9fs_xattr_write(V9fsState *s, V9fsPDU *pdu, V9fsFidState *fidp,
+ uint64_t off, uint32_t count,
+ struct iovec *sg, int cnt)
+{
+ int i, to_copy;
+ ssize_t err = 0;
+ int write_count;
+ int64_t xattr_len;
+ size_t offset = 7;
+
+
+ xattr_len = fidp->fs.xattr.len;
+ write_count = xattr_len - off;
+ if (write_count > count) {
+ write_count = count;
+ } else if (write_count < 0) {
+ /*
+ * write beyond XATTR value len specified in
+ * xattrcreate
+ */
+ err = -ENOSPC;
+ goto out;
+ }
+ err = pdu_marshal(pdu, offset, "d", write_count);
+ if (err < 0) {
+ return err;
+ }
+ err += offset;
+ fidp->fs.xattr.copied_len += write_count;
+ /*
+ * Now copy the content from sg list
+ */
+ for (i = 0; i < cnt; i++) {
+ if (write_count > sg[i].iov_len) {
+ to_copy = sg[i].iov_len;
+ } else {
+ to_copy = write_count;
+ }
+ memcpy((char *)fidp->fs.xattr.value + off, sg[i].iov_base, to_copy);
+ /* updating vs->off since we are not using below */
+ off += to_copy;
+ write_count -= to_copy;
+ }
+out:
+ return err;
+}
+
+static void v9fs_write(void *opaque)
+{
+ ssize_t err;
+ int32_t fid;
+ uint64_t off;
+ uint32_t count;
+ int32_t len = 0;
+ int32_t total = 0;
+ size_t offset = 7;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+ QEMUIOVector qiov_full;
+ QEMUIOVector qiov;
+
+ err = pdu_unmarshal(pdu, offset, "dqd", &fid, &off, &count);
+ if (err < 0) {
+ complete_pdu(s, pdu, err);
+ return;
+ }
+ offset += err;
+ v9fs_init_qiov_from_pdu(&qiov_full, pdu, offset, count, true);
+ trace_v9fs_write(pdu->tag, pdu->id, fid, off, count, qiov_full.niov);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ if (fidp->fid_type == P9_FID_FILE) {
+ if (fidp->fs.fd == -1) {
+ err = -EINVAL;
+ goto out;
+ }
+ } else if (fidp->fid_type == P9_FID_XATTR) {
+ /*
+ * setxattr operation
+ */
+ err = v9fs_xattr_write(s, pdu, fidp, off, count,
+ qiov_full.iov, qiov_full.niov);
+ goto out;
+ } else {
+ err = -EINVAL;
+ goto out;
+ }
+ qemu_iovec_init(&qiov, qiov_full.niov);
+ do {
+ qemu_iovec_reset(&qiov);
+ qemu_iovec_concat(&qiov, &qiov_full, total, qiov_full.size - total);
+ if (0) {
+ print_sg(qiov.iov, qiov.niov);
+ }
+ /* Loop in case of EINTR */
+ do {
+ len = v9fs_co_pwritev(pdu, fidp, qiov.iov, qiov.niov, off);
+ if (len >= 0) {
+ off += len;
+ total += len;
+ }
+ } while (len == -EINTR && !pdu->cancelled);
+ if (len < 0) {
+ /* IO error return the error */
+ err = len;
+ goto out_qiov;
+ }
+ } while (total < count && len > 0);
+
+ offset = 7;
+ err = pdu_marshal(pdu, offset, "d", total);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_write_return(pdu->tag, pdu->id, total, err);
+out_qiov:
+ qemu_iovec_destroy(&qiov);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ qemu_iovec_destroy(&qiov_full);
+ complete_pdu(s, pdu, err);
+}
+
+static void v9fs_create(void *opaque)
+{
+ int32_t fid;
+ int err = 0;
+ size_t offset = 7;
+ V9fsFidState *fidp;
+ V9fsQID qid;
+ int32_t perm;
+ int8_t mode;
+ V9fsPath path;
+ struct stat stbuf;
+ V9fsString name;
+ V9fsString extension;
+ int iounit;
+ V9fsPDU *pdu = opaque;
+
+ v9fs_path_init(&path);
+ v9fs_string_init(&name);
+ v9fs_string_init(&extension);
+ err = pdu_unmarshal(pdu, offset, "dsdbs", &fid, &name,
+ &perm, &mode, &extension);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_create(pdu->tag, pdu->id, fid, name.data, perm, mode);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ if (perm & P9_STAT_MODE_DIR) {
+ err = v9fs_co_mkdir(pdu, fidp, &name, perm & 0777,
+ fidp->uid, -1, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ err = v9fs_co_name_to_path(pdu, &fidp->path, name.data, &path);
+ if (err < 0) {
+ goto out;
+ }
+ v9fs_path_copy(&fidp->path, &path);
+ err = v9fs_co_opendir(pdu, fidp);
+ if (err < 0) {
+ goto out;
+ }
+ fidp->fid_type = P9_FID_DIR;
+ } else if (perm & P9_STAT_MODE_SYMLINK) {
+ err = v9fs_co_symlink(pdu, fidp, &name,
+ extension.data, -1 , &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ err = v9fs_co_name_to_path(pdu, &fidp->path, name.data, &path);
+ if (err < 0) {
+ goto out;
+ }
+ v9fs_path_copy(&fidp->path, &path);
+ } else if (perm & P9_STAT_MODE_LINK) {
+ int32_t ofid = atoi(extension.data);
+ V9fsFidState *ofidp = get_fid(pdu, ofid);
+ if (ofidp == NULL) {
+ err = -EINVAL;
+ goto out;
+ }
+ err = v9fs_co_link(pdu, ofidp, fidp, &name);
+ put_fid(pdu, ofidp);
+ if (err < 0) {
+ goto out;
+ }
+ err = v9fs_co_name_to_path(pdu, &fidp->path, name.data, &path);
+ if (err < 0) {
+ fidp->fid_type = P9_FID_NONE;
+ goto out;
+ }
+ v9fs_path_copy(&fidp->path, &path);
+ err = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
+ if (err < 0) {
+ fidp->fid_type = P9_FID_NONE;
+ goto out;
+ }
+ } else if (perm & P9_STAT_MODE_DEVICE) {
+ char ctype;
+ uint32_t major, minor;
+ mode_t nmode = 0;
+
+ if (sscanf(extension.data, "%c %u %u", &ctype, &major, &minor) != 3) {
+ err = -errno;
+ goto out;
+ }
+
+ switch (ctype) {
+ case 'c':
+ nmode = S_IFCHR;
+ break;
+ case 'b':
+ nmode = S_IFBLK;
+ break;
+ default:
+ err = -EIO;
+ goto out;
+ }
+
+ nmode |= perm & 0777;
+ err = v9fs_co_mknod(pdu, fidp, &name, fidp->uid, -1,
+ makedev(major, minor), nmode, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ err = v9fs_co_name_to_path(pdu, &fidp->path, name.data, &path);
+ if (err < 0) {
+ goto out;
+ }
+ v9fs_path_copy(&fidp->path, &path);
+ } else if (perm & P9_STAT_MODE_NAMED_PIPE) {
+ err = v9fs_co_mknod(pdu, fidp, &name, fidp->uid, -1,
+ 0, S_IFIFO | (perm & 0777), &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ err = v9fs_co_name_to_path(pdu, &fidp->path, name.data, &path);
+ if (err < 0) {
+ goto out;
+ }
+ v9fs_path_copy(&fidp->path, &path);
+ } else if (perm & P9_STAT_MODE_SOCKET) {
+ err = v9fs_co_mknod(pdu, fidp, &name, fidp->uid, -1,
+ 0, S_IFSOCK | (perm & 0777), &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ err = v9fs_co_name_to_path(pdu, &fidp->path, name.data, &path);
+ if (err < 0) {
+ goto out;
+ }
+ v9fs_path_copy(&fidp->path, &path);
+ } else {
+ err = v9fs_co_open2(pdu, fidp, &name, -1,
+ omode_to_uflags(mode)|O_CREAT, perm, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ fidp->fid_type = P9_FID_FILE;
+ fidp->open_flags = omode_to_uflags(mode);
+ if (fidp->open_flags & O_EXCL) {
+ /*
+ * We let the host file system do O_EXCL check
+ * We should not reclaim such fd
+ */
+ fidp->flags |= FID_NON_RECLAIMABLE;
+ }
+ }
+ iounit = get_iounit(pdu, &fidp->path);
+ stat_to_qid(&stbuf, &qid);
+ err = pdu_marshal(pdu, offset, "Qd", &qid, iounit);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_create_return(pdu->tag, pdu->id,
+ qid.type, qid.version, qid.path, iounit);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(pdu->s, pdu, err);
+ v9fs_string_free(&name);
+ v9fs_string_free(&extension);
+ v9fs_path_free(&path);
+}
+
+static void v9fs_symlink(void *opaque)
+{
+ V9fsPDU *pdu = opaque;
+ V9fsString name;
+ V9fsString symname;
+ V9fsFidState *dfidp;
+ V9fsQID qid;
+ struct stat stbuf;
+ int32_t dfid;
+ int err = 0;
+ gid_t gid;
+ size_t offset = 7;
+
+ v9fs_string_init(&name);
+ v9fs_string_init(&symname);
+ err = pdu_unmarshal(pdu, offset, "dssd", &dfid, &name, &symname, &gid);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_symlink(pdu->tag, pdu->id, dfid, name.data, symname.data, gid);
+
+ dfidp = get_fid(pdu, dfid);
+ if (dfidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ err = v9fs_co_symlink(pdu, dfidp, &name, symname.data, gid, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ stat_to_qid(&stbuf, &qid);
+ err = pdu_marshal(pdu, offset, "Q", &qid);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_symlink_return(pdu->tag, pdu->id,
+ qid.type, qid.version, qid.path);
+out:
+ put_fid(pdu, dfidp);
+out_nofid:
+ complete_pdu(pdu->s, pdu, err);
+ v9fs_string_free(&name);
+ v9fs_string_free(&symname);
+}
+
+static void v9fs_flush(void *opaque)
+{
+ ssize_t err;
+ int16_t tag;
+ size_t offset = 7;
+ V9fsPDU *cancel_pdu;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ err = pdu_unmarshal(pdu, offset, "w", &tag);
+ if (err < 0) {
+ complete_pdu(s, pdu, err);
+ return;
+ }
+ trace_v9fs_flush(pdu->tag, pdu->id, tag);
+
+ QLIST_FOREACH(cancel_pdu, &s->active_list, next) {
+ if (cancel_pdu->tag == tag) {
+ break;
+ }
+ }
+ if (cancel_pdu) {
+ cancel_pdu->cancelled = 1;
+ /*
+ * Wait for pdu to complete.
+ */
+ qemu_co_queue_wait(&cancel_pdu->complete);
+ cancel_pdu->cancelled = 0;
+ free_pdu(pdu->s, cancel_pdu);
+ }
+ complete_pdu(s, pdu, 7);
+}
+
+static void v9fs_link(void *opaque)
+{
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+ int32_t dfid, oldfid;
+ V9fsFidState *dfidp, *oldfidp;
+ V9fsString name;
+ size_t offset = 7;
+ int err = 0;
+
+ v9fs_string_init(&name);
+ err = pdu_unmarshal(pdu, offset, "dds", &dfid, &oldfid, &name);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_link(pdu->tag, pdu->id, dfid, oldfid, name.data);
+
+ dfidp = get_fid(pdu, dfid);
+ if (dfidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+
+ oldfidp = get_fid(pdu, oldfid);
+ if (oldfidp == NULL) {
+ err = -ENOENT;
+ goto out;
+ }
+ err = v9fs_co_link(pdu, oldfidp, dfidp, &name);
+ if (!err) {
+ err = offset;
+ }
+out:
+ put_fid(pdu, dfidp);
+out_nofid:
+ v9fs_string_free(&name);
+ complete_pdu(s, pdu, err);
+}
+
+/* Only works with path name based fid */
+static void v9fs_remove(void *opaque)
+{
+ int32_t fid;
+ int err = 0;
+ size_t offset = 7;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+
+ err = pdu_unmarshal(pdu, offset, "d", &fid);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_remove(pdu->tag, pdu->id, fid);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ /* if fs driver is not path based, return EOPNOTSUPP */
+ if (!(pdu->s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT)) {
+ err = -EOPNOTSUPP;
+ goto out_err;
+ }
+ /*
+ * IF the file is unlinked, we cannot reopen
+ * the file later. So don't reclaim fd
+ */
+ err = v9fs_mark_fids_unreclaim(pdu, &fidp->path);
+ if (err < 0) {
+ goto out_err;
+ }
+ err = v9fs_co_remove(pdu, &fidp->path);
+ if (!err) {
+ err = offset;
+ }
+out_err:
+ /* For TREMOVE we need to clunk the fid even on failed remove */
+ clunk_fid(pdu->s, fidp->fid);
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(pdu->s, pdu, err);
+}
+
+static void v9fs_unlinkat(void *opaque)
+{
+ int err = 0;
+ V9fsString name;
+ int32_t dfid, flags;
+ size_t offset = 7;
+ V9fsPath path;
+ V9fsFidState *dfidp;
+ V9fsPDU *pdu = opaque;
+
+ v9fs_string_init(&name);
+ err = pdu_unmarshal(pdu, offset, "dsd", &dfid, &name, &flags);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ dfidp = get_fid(pdu, dfid);
+ if (dfidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ /*
+ * IF the file is unlinked, we cannot reopen
+ * the file later. So don't reclaim fd
+ */
+ v9fs_path_init(&path);
+ err = v9fs_co_name_to_path(pdu, &dfidp->path, name.data, &path);
+ if (err < 0) {
+ goto out_err;
+ }
+ err = v9fs_mark_fids_unreclaim(pdu, &path);
+ if (err < 0) {
+ goto out_err;
+ }
+ err = v9fs_co_unlinkat(pdu, &dfidp->path, &name, flags);
+ if (!err) {
+ err = offset;
+ }
+out_err:
+ put_fid(pdu, dfidp);
+ v9fs_path_free(&path);
+out_nofid:
+ complete_pdu(pdu->s, pdu, err);
+ v9fs_string_free(&name);
+}
+
+
+/* Only works with path name based fid */
+static int v9fs_complete_rename(V9fsPDU *pdu, V9fsFidState *fidp,
+ int32_t newdirfid, V9fsString *name)
+{
+ char *end;
+ int err = 0;
+ V9fsPath new_path;
+ V9fsFidState *tfidp;
+ V9fsState *s = pdu->s;
+ V9fsFidState *dirfidp = NULL;
+ char *old_name, *new_name;
+
+ v9fs_path_init(&new_path);
+ if (newdirfid != -1) {
+ dirfidp = get_fid(pdu, newdirfid);
+ if (dirfidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ BUG_ON(dirfidp->fid_type != P9_FID_NONE);
+ v9fs_co_name_to_path(pdu, &dirfidp->path, name->data, &new_path);
+ } else {
+ old_name = fidp->path.data;
+ end = strrchr(old_name, '/');
+ if (end) {
+ end++;
+ } else {
+ end = old_name;
+ }
+ new_name = g_malloc0(end - old_name + name->size + 1);
+ strncat(new_name, old_name, end - old_name);
+ strncat(new_name + (end - old_name), name->data, name->size);
+ v9fs_co_name_to_path(pdu, NULL, new_name, &new_path);
+ g_free(new_name);
+ }
+ err = v9fs_co_rename(pdu, &fidp->path, &new_path);
+ if (err < 0) {
+ goto out;
+ }
+ /*
+ * Fixup fid's pointing to the old name to
+ * start pointing to the new name
+ */
+ for (tfidp = s->fid_list; tfidp; tfidp = tfidp->next) {
+ if (v9fs_path_is_ancestor(&fidp->path, &tfidp->path)) {
+ /* replace the name */
+ v9fs_fix_path(&tfidp->path, &new_path, strlen(fidp->path.data));
+ }
+ }
+out:
+ if (dirfidp) {
+ put_fid(pdu, dirfidp);
+ }
+ v9fs_path_free(&new_path);
+out_nofid:
+ return err;
+}
+
+/* Only works with path name based fid */
+static void v9fs_rename(void *opaque)
+{
+ int32_t fid;
+ ssize_t err = 0;
+ size_t offset = 7;
+ V9fsString name;
+ int32_t newdirfid;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ v9fs_string_init(&name);
+ err = pdu_unmarshal(pdu, offset, "dds", &fid, &newdirfid, &name);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ BUG_ON(fidp->fid_type != P9_FID_NONE);
+ /* if fs driver is not path based, return EOPNOTSUPP */
+ if (!(pdu->s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT)) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+ v9fs_path_write_lock(s);
+ err = v9fs_complete_rename(pdu, fidp, newdirfid, &name);
+ v9fs_path_unlock(s);
+ if (!err) {
+ err = offset;
+ }
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+ v9fs_string_free(&name);
+}
+
+static void v9fs_fix_fid_paths(V9fsPDU *pdu, V9fsPath *olddir,
+ V9fsString *old_name, V9fsPath *newdir,
+ V9fsString *new_name)
+{
+ V9fsFidState *tfidp;
+ V9fsPath oldpath, newpath;
+ V9fsState *s = pdu->s;
+
+
+ v9fs_path_init(&oldpath);
+ v9fs_path_init(&newpath);
+ v9fs_co_name_to_path(pdu, olddir, old_name->data, &oldpath);
+ v9fs_co_name_to_path(pdu, newdir, new_name->data, &newpath);
+
+ /*
+ * Fixup fid's pointing to the old name to
+ * start pointing to the new name
+ */
+ for (tfidp = s->fid_list; tfidp; tfidp = tfidp->next) {
+ if (v9fs_path_is_ancestor(&oldpath, &tfidp->path)) {
+ /* replace the name */
+ v9fs_fix_path(&tfidp->path, &newpath, strlen(oldpath.data));
+ }
+ }
+ v9fs_path_free(&oldpath);
+ v9fs_path_free(&newpath);
+}
+
+static int v9fs_complete_renameat(V9fsPDU *pdu, int32_t olddirfid,
+ V9fsString *old_name, int32_t newdirfid,
+ V9fsString *new_name)
+{
+ int err = 0;
+ V9fsState *s = pdu->s;
+ V9fsFidState *newdirfidp = NULL, *olddirfidp = NULL;
+
+ olddirfidp = get_fid(pdu, olddirfid);
+ if (olddirfidp == NULL) {
+ err = -ENOENT;
+ goto out;
+ }
+ if (newdirfid != -1) {
+ newdirfidp = get_fid(pdu, newdirfid);
+ if (newdirfidp == NULL) {
+ err = -ENOENT;
+ goto out;
+ }
+ } else {
+ newdirfidp = get_fid(pdu, olddirfid);
+ }
+
+ err = v9fs_co_renameat(pdu, &olddirfidp->path, old_name,
+ &newdirfidp->path, new_name);
+ if (err < 0) {
+ goto out;
+ }
+ if (s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT) {
+ /* Only for path based fid we need to do the below fixup */
+ v9fs_fix_fid_paths(pdu, &olddirfidp->path, old_name,
+ &newdirfidp->path, new_name);
+ }
+out:
+ if (olddirfidp) {
+ put_fid(pdu, olddirfidp);
+ }
+ if (newdirfidp) {
+ put_fid(pdu, newdirfidp);
+ }
+ return err;
+}
+
+static void v9fs_renameat(void *opaque)
+{
+ ssize_t err = 0;
+ size_t offset = 7;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+ int32_t olddirfid, newdirfid;
+ V9fsString old_name, new_name;
+
+ v9fs_string_init(&old_name);
+ v9fs_string_init(&new_name);
+ err = pdu_unmarshal(pdu, offset, "dsds", &olddirfid,
+ &old_name, &newdirfid, &new_name);
+ if (err < 0) {
+ goto out_err;
+ }
+
+ v9fs_path_write_lock(s);
+ err = v9fs_complete_renameat(pdu, olddirfid,
+ &old_name, newdirfid, &new_name);
+ v9fs_path_unlock(s);
+ if (!err) {
+ err = offset;
+ }
+
+out_err:
+ complete_pdu(s, pdu, err);
+ v9fs_string_free(&old_name);
+ v9fs_string_free(&new_name);
+}
+
+static void v9fs_wstat(void *opaque)
+{
+ int32_t fid;
+ int err = 0;
+ int16_t unused;
+ V9fsStat v9stat;
+ size_t offset = 7;
+ struct stat stbuf;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ v9fs_stat_init(&v9stat);
+ err = pdu_unmarshal(pdu, offset, "dwS", &fid, &unused, &v9stat);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_wstat(pdu->tag, pdu->id, fid,
+ v9stat.mode, v9stat.atime, v9stat.mtime);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ /* do we need to sync the file? */
+ if (donttouch_stat(&v9stat)) {
+ err = v9fs_co_fsync(pdu, fidp, 0);
+ goto out;
+ }
+ if (v9stat.mode != -1) {
+ uint32_t v9_mode;
+ err = v9fs_co_lstat(pdu, &fidp->path, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ v9_mode = stat_to_v9mode(&stbuf);
+ if ((v9stat.mode & P9_STAT_MODE_TYPE_BITS) !=
+ (v9_mode & P9_STAT_MODE_TYPE_BITS)) {
+ /* Attempting to change the type */
+ err = -EIO;
+ goto out;
+ }
+ err = v9fs_co_chmod(pdu, &fidp->path,
+ v9mode_to_mode(v9stat.mode,
+ &v9stat.extension));
+ if (err < 0) {
+ goto out;
+ }
+ }
+ if (v9stat.mtime != -1 || v9stat.atime != -1) {
+ struct timespec times[2];
+ if (v9stat.atime != -1) {
+ times[0].tv_sec = v9stat.atime;
+ times[0].tv_nsec = 0;
+ } else {
+ times[0].tv_nsec = UTIME_OMIT;
+ }
+ if (v9stat.mtime != -1) {
+ times[1].tv_sec = v9stat.mtime;
+ times[1].tv_nsec = 0;
+ } else {
+ times[1].tv_nsec = UTIME_OMIT;
+ }
+ err = v9fs_co_utimensat(pdu, &fidp->path, times);
+ if (err < 0) {
+ goto out;
+ }
+ }
+ if (v9stat.n_gid != -1 || v9stat.n_uid != -1) {
+ err = v9fs_co_chown(pdu, &fidp->path, v9stat.n_uid, v9stat.n_gid);
+ if (err < 0) {
+ goto out;
+ }
+ }
+ if (v9stat.name.size != 0) {
+ err = v9fs_complete_rename(pdu, fidp, -1, &v9stat.name);
+ if (err < 0) {
+ goto out;
+ }
+ }
+ if (v9stat.length != -1) {
+ err = v9fs_co_truncate(pdu, &fidp->path, v9stat.length);
+ if (err < 0) {
+ goto out;
+ }
+ }
+ err = offset;
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ v9fs_stat_free(&v9stat);
+ complete_pdu(s, pdu, err);
+}
+
+static int v9fs_fill_statfs(V9fsState *s, V9fsPDU *pdu, struct statfs *stbuf)
+{
+ uint32_t f_type;
+ uint32_t f_bsize;
+ uint64_t f_blocks;
+ uint64_t f_bfree;
+ uint64_t f_bavail;
+ uint64_t f_files;
+ uint64_t f_ffree;
+ uint64_t fsid_val;
+ uint32_t f_namelen;
+ size_t offset = 7;
+ int32_t bsize_factor;
+
+ /*
+ * compute bsize factor based on host file system block size
+ * and client msize
+ */
+ bsize_factor = (s->msize - P9_IOHDRSZ)/stbuf->f_bsize;
+ if (!bsize_factor) {
+ bsize_factor = 1;
+ }
+ f_type = stbuf->f_type;
+ f_bsize = stbuf->f_bsize;
+ f_bsize *= bsize_factor;
+ /*
+ * f_bsize is adjusted(multiplied) by bsize factor, so we need to
+ * adjust(divide) the number of blocks, free blocks and available
+ * blocks by bsize factor
+ */
+ f_blocks = stbuf->f_blocks/bsize_factor;
+ f_bfree = stbuf->f_bfree/bsize_factor;
+ f_bavail = stbuf->f_bavail/bsize_factor;
+ f_files = stbuf->f_files;
+ f_ffree = stbuf->f_ffree;
+ fsid_val = (unsigned int) stbuf->f_fsid.__val[0] |
+ (unsigned long long)stbuf->f_fsid.__val[1] << 32;
+ f_namelen = stbuf->f_namelen;
+
+ return pdu_marshal(pdu, offset, "ddqqqqqqd",
+ f_type, f_bsize, f_blocks, f_bfree,
+ f_bavail, f_files, f_ffree,
+ fsid_val, f_namelen);
+}
+
+static void v9fs_statfs(void *opaque)
+{
+ int32_t fid;
+ ssize_t retval = 0;
+ size_t offset = 7;
+ V9fsFidState *fidp;
+ struct statfs stbuf;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ retval = pdu_unmarshal(pdu, offset, "d", &fid);
+ if (retval < 0) {
+ goto out_nofid;
+ }
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ retval = -ENOENT;
+ goto out_nofid;
+ }
+ retval = v9fs_co_statfs(pdu, &fidp->path, &stbuf);
+ if (retval < 0) {
+ goto out;
+ }
+ retval = v9fs_fill_statfs(s, pdu, &stbuf);
+ if (retval < 0) {
+ goto out;
+ }
+ retval += offset;
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, retval);
+}
+
+static void v9fs_mknod(void *opaque)
+{
+
+ int mode;
+ gid_t gid;
+ int32_t fid;
+ V9fsQID qid;
+ int err = 0;
+ int major, minor;
+ size_t offset = 7;
+ V9fsString name;
+ struct stat stbuf;
+ V9fsFidState *fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ v9fs_string_init(&name);
+ err = pdu_unmarshal(pdu, offset, "dsdddd", &fid, &name, &mode,
+ &major, &minor, &gid);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_mknod(pdu->tag, pdu->id, fid, mode, major, minor);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ err = v9fs_co_mknod(pdu, fidp, &name, fidp->uid, gid,
+ makedev(major, minor), mode, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ stat_to_qid(&stbuf, &qid);
+ err = pdu_marshal(pdu, offset, "Q", &qid);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_mknod_return(pdu->tag, pdu->id,
+ qid.type, qid.version, qid.path);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+ v9fs_string_free(&name);
+}
+
+/*
+ * Implement posix byte range locking code
+ * Server side handling of locking code is very simple, because 9p server in
+ * QEMU can handle only one client. And most of the lock handling
+ * (like conflict, merging) etc is done by the VFS layer itself, so no need to
+ * do any thing in * qemu 9p server side lock code path.
+ * So when a TLOCK request comes, always return success
+ */
+static void v9fs_lock(void *opaque)
+{
+ int8_t status;
+ V9fsFlock flock;
+ size_t offset = 7;
+ struct stat stbuf;
+ V9fsFidState *fidp;
+ int32_t fid, err = 0;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ status = P9_LOCK_ERROR;
+ v9fs_string_init(&flock.client_id);
+ err = pdu_unmarshal(pdu, offset, "dbdqqds", &fid, &flock.type,
+ &flock.flags, &flock.start, &flock.length,
+ &flock.proc_id, &flock.client_id);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_lock(pdu->tag, pdu->id, fid,
+ flock.type, flock.start, flock.length);
+
+
+ /* We support only block flag now (that too ignored currently) */
+ if (flock.flags & ~P9_LOCK_FLAGS_BLOCK) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ err = v9fs_co_fstat(pdu, fidp, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ status = P9_LOCK_SUCCESS;
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ err = pdu_marshal(pdu, offset, "b", status);
+ if (err > 0) {
+ err += offset;
+ }
+ trace_v9fs_lock_return(pdu->tag, pdu->id, status);
+ complete_pdu(s, pdu, err);
+ v9fs_string_free(&flock.client_id);
+}
+
+/*
+ * When a TGETLOCK request comes, always return success because all lock
+ * handling is done by client's VFS layer.
+ */
+static void v9fs_getlock(void *opaque)
+{
+ size_t offset = 7;
+ struct stat stbuf;
+ V9fsFidState *fidp;
+ V9fsGetlock glock;
+ int32_t fid, err = 0;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ v9fs_string_init(&glock.client_id);
+ err = pdu_unmarshal(pdu, offset, "dbqqds", &fid, &glock.type,
+ &glock.start, &glock.length, &glock.proc_id,
+ &glock.client_id);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_getlock(pdu->tag, pdu->id, fid,
+ glock.type, glock.start, glock.length);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ err = v9fs_co_fstat(pdu, fidp, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ glock.type = P9_LOCK_TYPE_UNLCK;
+ err = pdu_marshal(pdu, offset, "bqqds", glock.type,
+ glock.start, glock.length, glock.proc_id,
+ &glock.client_id);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_getlock_return(pdu->tag, pdu->id, glock.type, glock.start,
+ glock.length, glock.proc_id);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+ v9fs_string_free(&glock.client_id);
+}
+
+static void v9fs_mkdir(void *opaque)
+{
+ V9fsPDU *pdu = opaque;
+ size_t offset = 7;
+ int32_t fid;
+ struct stat stbuf;
+ V9fsQID qid;
+ V9fsString name;
+ V9fsFidState *fidp;
+ gid_t gid;
+ int mode;
+ int err = 0;
+
+ v9fs_string_init(&name);
+ err = pdu_unmarshal(pdu, offset, "dsdd", &fid, &name, &mode, &gid);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_mkdir(pdu->tag, pdu->id, fid, name.data, mode, gid);
+
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ err = v9fs_co_mkdir(pdu, fidp, &name, mode, fidp->uid, gid, &stbuf);
+ if (err < 0) {
+ goto out;
+ }
+ stat_to_qid(&stbuf, &qid);
+ err = pdu_marshal(pdu, offset, "Q", &qid);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_mkdir_return(pdu->tag, pdu->id,
+ qid.type, qid.version, qid.path, err);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(pdu->s, pdu, err);
+ v9fs_string_free(&name);
+}
+
+static void v9fs_xattrwalk(void *opaque)
+{
+ int64_t size;
+ V9fsString name;
+ ssize_t err = 0;
+ size_t offset = 7;
+ int32_t fid, newfid;
+ V9fsFidState *file_fidp;
+ V9fsFidState *xattr_fidp = NULL;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ v9fs_string_init(&name);
+ err = pdu_unmarshal(pdu, offset, "dds", &fid, &newfid, &name);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_xattrwalk(pdu->tag, pdu->id, fid, newfid, name.data);
+
+ file_fidp = get_fid(pdu, fid);
+ if (file_fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+ xattr_fidp = alloc_fid(s, newfid);
+ if (xattr_fidp == NULL) {
+ err = -EINVAL;
+ goto out;
+ }
+ v9fs_path_copy(&xattr_fidp->path, &file_fidp->path);
+ if (name.data == NULL) {
+ /*
+ * listxattr request. Get the size first
+ */
+ size = v9fs_co_llistxattr(pdu, &xattr_fidp->path, NULL, 0);
+ if (size < 0) {
+ err = size;
+ clunk_fid(s, xattr_fidp->fid);
+ goto out;
+ }
+ /*
+ * Read the xattr value
+ */
+ xattr_fidp->fs.xattr.len = size;
+ xattr_fidp->fid_type = P9_FID_XATTR;
+ xattr_fidp->fs.xattr.copied_len = -1;
+ if (size) {
+ xattr_fidp->fs.xattr.value = g_malloc(size);
+ err = v9fs_co_llistxattr(pdu, &xattr_fidp->path,
+ xattr_fidp->fs.xattr.value,
+ xattr_fidp->fs.xattr.len);
+ if (err < 0) {
+ clunk_fid(s, xattr_fidp->fid);
+ goto out;
+ }
+ }
+ err = pdu_marshal(pdu, offset, "q", size);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ } else {
+ /*
+ * specific xattr fid. We check for xattr
+ * presence also collect the xattr size
+ */
+ size = v9fs_co_lgetxattr(pdu, &xattr_fidp->path,
+ &name, NULL, 0);
+ if (size < 0) {
+ err = size;
+ clunk_fid(s, xattr_fidp->fid);
+ goto out;
+ }
+ /*
+ * Read the xattr value
+ */
+ xattr_fidp->fs.xattr.len = size;
+ xattr_fidp->fid_type = P9_FID_XATTR;
+ xattr_fidp->fs.xattr.copied_len = -1;
+ if (size) {
+ xattr_fidp->fs.xattr.value = g_malloc(size);
+ err = v9fs_co_lgetxattr(pdu, &xattr_fidp->path,
+ &name, xattr_fidp->fs.xattr.value,
+ xattr_fidp->fs.xattr.len);
+ if (err < 0) {
+ clunk_fid(s, xattr_fidp->fid);
+ goto out;
+ }
+ }
+ err = pdu_marshal(pdu, offset, "q", size);
+ if (err < 0) {
+ goto out;
+ }
+ err += offset;
+ }
+ trace_v9fs_xattrwalk_return(pdu->tag, pdu->id, size);
+out:
+ put_fid(pdu, file_fidp);
+ if (xattr_fidp) {
+ put_fid(pdu, xattr_fidp);
+ }
+out_nofid:
+ complete_pdu(s, pdu, err);
+ v9fs_string_free(&name);
+}
+
+static void v9fs_xattrcreate(void *opaque)
+{
+ int flags;
+ int32_t fid;
+ int64_t size;
+ ssize_t err = 0;
+ V9fsString name;
+ size_t offset = 7;
+ V9fsFidState *file_fidp;
+ V9fsFidState *xattr_fidp;
+ V9fsPDU *pdu = opaque;
+ V9fsState *s = pdu->s;
+
+ v9fs_string_init(&name);
+ err = pdu_unmarshal(pdu, offset, "dsqd", &fid, &name, &size, &flags);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_xattrcreate(pdu->tag, pdu->id, fid, name.data, size, flags);
+
+ file_fidp = get_fid(pdu, fid);
+ if (file_fidp == NULL) {
+ err = -EINVAL;
+ goto out_nofid;
+ }
+ /* Make the file fid point to xattr */
+ xattr_fidp = file_fidp;
+ xattr_fidp->fid_type = P9_FID_XATTR;
+ xattr_fidp->fs.xattr.copied_len = 0;
+ xattr_fidp->fs.xattr.len = size;
+ xattr_fidp->fs.xattr.flags = flags;
+ v9fs_string_init(&xattr_fidp->fs.xattr.name);
+ v9fs_string_copy(&xattr_fidp->fs.xattr.name, &name);
+ xattr_fidp->fs.xattr.value = g_malloc(size);
+ err = offset;
+ put_fid(pdu, file_fidp);
+out_nofid:
+ complete_pdu(s, pdu, err);
+ v9fs_string_free(&name);
+}
+
+static void v9fs_readlink(void *opaque)
+{
+ V9fsPDU *pdu = opaque;
+ size_t offset = 7;
+ V9fsString target;
+ int32_t fid;
+ int err = 0;
+ V9fsFidState *fidp;
+
+ err = pdu_unmarshal(pdu, offset, "d", &fid);
+ if (err < 0) {
+ goto out_nofid;
+ }
+ trace_v9fs_readlink(pdu->tag, pdu->id, fid);
+ fidp = get_fid(pdu, fid);
+ if (fidp == NULL) {
+ err = -ENOENT;
+ goto out_nofid;
+ }
+
+ v9fs_string_init(&target);
+ err = v9fs_co_readlink(pdu, &fidp->path, &target);
+ if (err < 0) {
+ goto out;
+ }
+ err = pdu_marshal(pdu, offset, "s", &target);
+ if (err < 0) {
+ v9fs_string_free(&target);
+ goto out;
+ }
+ err += offset;
+ trace_v9fs_readlink_return(pdu->tag, pdu->id, target.data);
+ v9fs_string_free(&target);
+out:
+ put_fid(pdu, fidp);
+out_nofid:
+ complete_pdu(pdu->s, pdu, err);
+}
+
+static CoroutineEntry *pdu_co_handlers[] = {
+ [P9_TREADDIR] = v9fs_readdir,
+ [P9_TSTATFS] = v9fs_statfs,
+ [P9_TGETATTR] = v9fs_getattr,
+ [P9_TSETATTR] = v9fs_setattr,
+ [P9_TXATTRWALK] = v9fs_xattrwalk,
+ [P9_TXATTRCREATE] = v9fs_xattrcreate,
+ [P9_TMKNOD] = v9fs_mknod,
+ [P9_TRENAME] = v9fs_rename,
+ [P9_TLOCK] = v9fs_lock,
+ [P9_TGETLOCK] = v9fs_getlock,
+ [P9_TRENAMEAT] = v9fs_renameat,
+ [P9_TREADLINK] = v9fs_readlink,
+ [P9_TUNLINKAT] = v9fs_unlinkat,
+ [P9_TMKDIR] = v9fs_mkdir,
+ [P9_TVERSION] = v9fs_version,
+ [P9_TLOPEN] = v9fs_open,
+ [P9_TATTACH] = v9fs_attach,
+ [P9_TSTAT] = v9fs_stat,
+ [P9_TWALK] = v9fs_walk,
+ [P9_TCLUNK] = v9fs_clunk,
+ [P9_TFSYNC] = v9fs_fsync,
+ [P9_TOPEN] = v9fs_open,
+ [P9_TREAD] = v9fs_read,
+#if 0
+ [P9_TAUTH] = v9fs_auth,
+#endif
+ [P9_TFLUSH] = v9fs_flush,
+ [P9_TLINK] = v9fs_link,
+ [P9_TSYMLINK] = v9fs_symlink,
+ [P9_TCREATE] = v9fs_create,
+ [P9_TLCREATE] = v9fs_lcreate,
+ [P9_TWRITE] = v9fs_write,
+ [P9_TWSTAT] = v9fs_wstat,
+ [P9_TREMOVE] = v9fs_remove,
+};
+
+static void v9fs_op_not_supp(void *opaque)
+{
+ V9fsPDU *pdu = opaque;
+ complete_pdu(pdu->s, pdu, -EOPNOTSUPP);
+}
+
+static void v9fs_fs_ro(void *opaque)
+{
+ V9fsPDU *pdu = opaque;
+ complete_pdu(pdu->s, pdu, -EROFS);
+}
+
+static inline bool is_read_only_op(V9fsPDU *pdu)
+{
+ switch (pdu->id) {
+ case P9_TREADDIR:
+ case P9_TSTATFS:
+ case P9_TGETATTR:
+ case P9_TXATTRWALK:
+ case P9_TLOCK:
+ case P9_TGETLOCK:
+ case P9_TREADLINK:
+ case P9_TVERSION:
+ case P9_TLOPEN:
+ case P9_TATTACH:
+ case P9_TSTAT:
+ case P9_TWALK:
+ case P9_TCLUNK:
+ case P9_TFSYNC:
+ case P9_TOPEN:
+ case P9_TREAD:
+ case P9_TAUTH:
+ case P9_TFLUSH:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void submit_pdu(V9fsState *s, V9fsPDU *pdu)
+{
+ Coroutine *co;
+ CoroutineEntry *handler;
+
+ if (pdu->id >= ARRAY_SIZE(pdu_co_handlers) ||
+ (pdu_co_handlers[pdu->id] == NULL)) {
+ handler = v9fs_op_not_supp;
+ } else {
+ handler = pdu_co_handlers[pdu->id];
+ }
+
+ if (is_ro_export(&s->ctx) && !is_read_only_op(pdu)) {
+ handler = v9fs_fs_ro;
+ }
+ co = qemu_coroutine_create(handler);
+ qemu_coroutine_enter(co, pdu);
+}
+
+void handle_9p_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+ V9fsState *s = (V9fsState *)vdev;
+ V9fsPDU *pdu;
+ ssize_t len;
+
+ while ((pdu = alloc_pdu(s)) &&
+ (len = virtqueue_pop(vq, &pdu->elem)) != 0) {
+ struct {
+ uint32_t size_le;
+ uint8_t id;
+ uint16_t tag_le;
+ } QEMU_PACKED out;
+ int len;
+
+ pdu->s = s;
+ BUG_ON(pdu->elem.out_num == 0 || pdu->elem.in_num == 0);
+ QEMU_BUILD_BUG_ON(sizeof out != 7);
+
+ len = iov_to_buf(pdu->elem.out_sg, pdu->elem.out_num, 0,
+ &out, sizeof out);
+ BUG_ON(len != sizeof out);
+
+ pdu->size = le32_to_cpu(out.size_le);
+
+ pdu->id = out.id;
+ pdu->tag = le16_to_cpu(out.tag_le);
+
+ qemu_co_queue_init(&pdu->complete);
+ submit_pdu(s, pdu);
+ }
+ free_pdu(s, pdu);
+}
+
+static void __attribute__((__constructor__)) virtio_9p_set_fd_limit(void)
+{
+ struct rlimit rlim;
+ if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
+ fprintf(stderr, "Failed to get the resource limit\n");
+ exit(1);
+ }
+ open_fd_hw = rlim.rlim_cur - MIN(400, rlim.rlim_cur/3);
+ open_fd_rc = rlim.rlim_cur/2;
+}
diff --git a/hw/9pfs/virtio-9p.h b/hw/9pfs/virtio-9p.h
new file mode 100644
index 00000000..2e7d4885
--- /dev/null
+++ b/hw/9pfs/virtio-9p.h
@@ -0,0 +1,394 @@
+#ifndef _QEMU_VIRTIO_9P_H
+#define _QEMU_VIRTIO_9P_H
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <utime.h>
+#include <sys/resource.h>
+#include <glib.h>
+#include "standard-headers/linux/virtio_9p.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-9p.h"
+#include "fsdev/file-op-9p.h"
+#include "fsdev/virtio-9p-marshal.h"
+#include "qemu/thread.h"
+#include "block/coroutine.h"
+
+enum {
+ P9_TLERROR = 6,
+ P9_RLERROR,
+ P9_TSTATFS = 8,
+ P9_RSTATFS,
+ P9_TLOPEN = 12,
+ P9_RLOPEN,
+ P9_TLCREATE = 14,
+ P9_RLCREATE,
+ P9_TSYMLINK = 16,
+ P9_RSYMLINK,
+ P9_TMKNOD = 18,
+ P9_RMKNOD,
+ P9_TRENAME = 20,
+ P9_RRENAME,
+ P9_TREADLINK = 22,
+ P9_RREADLINK,
+ P9_TGETATTR = 24,
+ P9_RGETATTR,
+ P9_TSETATTR = 26,
+ P9_RSETATTR,
+ P9_TXATTRWALK = 30,
+ P9_RXATTRWALK,
+ P9_TXATTRCREATE = 32,
+ P9_RXATTRCREATE,
+ P9_TREADDIR = 40,
+ P9_RREADDIR,
+ P9_TFSYNC = 50,
+ P9_RFSYNC,
+ P9_TLOCK = 52,
+ P9_RLOCK,
+ P9_TGETLOCK = 54,
+ P9_RGETLOCK,
+ P9_TLINK = 70,
+ P9_RLINK,
+ P9_TMKDIR = 72,
+ P9_RMKDIR,
+ P9_TRENAMEAT = 74,
+ P9_RRENAMEAT,
+ P9_TUNLINKAT = 76,
+ P9_RUNLINKAT,
+ P9_TVERSION = 100,
+ P9_RVERSION,
+ P9_TAUTH = 102,
+ P9_RAUTH,
+ P9_TATTACH = 104,
+ P9_RATTACH,
+ P9_TERROR = 106,
+ P9_RERROR,
+ P9_TFLUSH = 108,
+ P9_RFLUSH,
+ P9_TWALK = 110,
+ P9_RWALK,
+ P9_TOPEN = 112,
+ P9_ROPEN,
+ P9_TCREATE = 114,
+ P9_RCREATE,
+ P9_TREAD = 116,
+ P9_RREAD,
+ P9_TWRITE = 118,
+ P9_RWRITE,
+ P9_TCLUNK = 120,
+ P9_RCLUNK,
+ P9_TREMOVE = 122,
+ P9_RREMOVE,
+ P9_TSTAT = 124,
+ P9_RSTAT,
+ P9_TWSTAT = 126,
+ P9_RWSTAT,
+};
+
+
+/* qid.types */
+enum {
+ P9_QTDIR = 0x80,
+ P9_QTAPPEND = 0x40,
+ P9_QTEXCL = 0x20,
+ P9_QTMOUNT = 0x10,
+ P9_QTAUTH = 0x08,
+ P9_QTTMP = 0x04,
+ P9_QTSYMLINK = 0x02,
+ P9_QTLINK = 0x01,
+ P9_QTFILE = 0x00,
+};
+
+enum p9_proto_version {
+ V9FS_PROTO_2000U = 0x01,
+ V9FS_PROTO_2000L = 0x02,
+};
+
+#define P9_NOTAG (u16)(~0)
+#define P9_NOFID (u32)(~0)
+#define P9_MAXWELEM 16
+
+#define FID_REFERENCED 0x1
+#define FID_NON_RECLAIMABLE 0x2
+static inline char *rpath(FsContext *ctx, const char *path)
+{
+ return g_strdup_printf("%s/%s", ctx->fs_root, path);
+}
+
+/*
+ * ample room for Twrite/Rread header
+ * size[4] Tread/Twrite tag[2] fid[4] offset[8] count[4]
+ */
+#define P9_IOHDRSZ 24
+
+typedef struct V9fsPDU V9fsPDU;
+struct V9fsState;
+
+struct V9fsPDU
+{
+ uint32_t size;
+ uint16_t tag;
+ uint8_t id;
+ uint8_t cancelled;
+ CoQueue complete;
+ VirtQueueElement elem;
+ struct V9fsState *s;
+ QLIST_ENTRY(V9fsPDU) next;
+};
+
+
+/* FIXME
+ * 1) change user needs to set groups and stuff
+ */
+
+#define MAX_REQ 128
+#define MAX_TAG_LEN 32
+
+#define BUG_ON(cond) assert(!(cond))
+
+typedef struct V9fsFidState V9fsFidState;
+
+enum {
+ P9_FID_NONE = 0,
+ P9_FID_FILE,
+ P9_FID_DIR,
+ P9_FID_XATTR,
+};
+
+typedef struct V9fsXattr
+{
+ int64_t copied_len;
+ int64_t len;
+ void *value;
+ V9fsString name;
+ int flags;
+} V9fsXattr;
+
+/*
+ * Filled by fs driver on open and other
+ * calls.
+ */
+union V9fsFidOpenState {
+ int fd;
+ DIR *dir;
+ V9fsXattr xattr;
+ /*
+ * private pointer for fs drivers, that
+ * have its own internal representation of
+ * open files.
+ */
+ void *private;
+};
+
+struct V9fsFidState
+{
+ int fid_type;
+ int32_t fid;
+ V9fsPath path;
+ V9fsFidOpenState fs;
+ V9fsFidOpenState fs_reclaim;
+ int flags;
+ int open_flags;
+ uid_t uid;
+ int ref;
+ int clunked;
+ V9fsFidState *next;
+ V9fsFidState *rclm_lst;
+};
+
+typedef struct V9fsState
+{
+ VirtIODevice parent_obj;
+ VirtQueue *vq;
+ V9fsPDU pdus[MAX_REQ];
+ QLIST_HEAD(, V9fsPDU) free_list;
+ QLIST_HEAD(, V9fsPDU) active_list;
+ V9fsFidState *fid_list;
+ FileOperations *ops;
+ FsContext ctx;
+ char *tag;
+ size_t config_size;
+ enum p9_proto_version proto_version;
+ int32_t msize;
+ /*
+ * lock ensuring atomic path update
+ * on rename.
+ */
+ CoRwlock rename_lock;
+ int32_t root_fid;
+ Error *migration_blocker;
+ V9fsConf fsconf;
+} V9fsState;
+
+typedef struct V9fsStatState {
+ V9fsPDU *pdu;
+ size_t offset;
+ V9fsStat v9stat;
+ V9fsFidState *fidp;
+ struct stat stbuf;
+} V9fsStatState;
+
+typedef struct V9fsOpenState {
+ V9fsPDU *pdu;
+ size_t offset;
+ int32_t mode;
+ V9fsFidState *fidp;
+ V9fsQID qid;
+ struct stat stbuf;
+ int iounit;
+} V9fsOpenState;
+
+typedef struct V9fsReadState {
+ V9fsPDU *pdu;
+ size_t offset;
+ int32_t count;
+ int32_t total;
+ int64_t off;
+ V9fsFidState *fidp;
+ struct iovec iov[128]; /* FIXME: bad, bad, bad */
+ struct iovec *sg;
+ off_t dir_pos;
+ struct dirent *dent;
+ struct stat stbuf;
+ V9fsString name;
+ V9fsStat v9stat;
+ int32_t len;
+ int32_t cnt;
+ int32_t max_count;
+} V9fsReadState;
+
+typedef struct V9fsWriteState {
+ V9fsPDU *pdu;
+ size_t offset;
+ int32_t len;
+ int32_t count;
+ int32_t total;
+ int64_t off;
+ V9fsFidState *fidp;
+ struct iovec iov[128]; /* FIXME: bad, bad, bad */
+ struct iovec *sg;
+ int cnt;
+} V9fsWriteState;
+
+typedef struct V9fsMkState {
+ V9fsPDU *pdu;
+ size_t offset;
+ V9fsQID qid;
+ struct stat stbuf;
+ V9fsString name;
+ V9fsString fullname;
+} V9fsMkState;
+
+/* 9p2000.L open flags */
+#define P9_DOTL_RDONLY 00000000
+#define P9_DOTL_WRONLY 00000001
+#define P9_DOTL_RDWR 00000002
+#define P9_DOTL_NOACCESS 00000003
+#define P9_DOTL_CREATE 00000100
+#define P9_DOTL_EXCL 00000200
+#define P9_DOTL_NOCTTY 00000400
+#define P9_DOTL_TRUNC 00001000
+#define P9_DOTL_APPEND 00002000
+#define P9_DOTL_NONBLOCK 00004000
+#define P9_DOTL_DSYNC 00010000
+#define P9_DOTL_FASYNC 00020000
+#define P9_DOTL_DIRECT 00040000
+#define P9_DOTL_LARGEFILE 00100000
+#define P9_DOTL_DIRECTORY 00200000
+#define P9_DOTL_NOFOLLOW 00400000
+#define P9_DOTL_NOATIME 01000000
+#define P9_DOTL_CLOEXEC 02000000
+#define P9_DOTL_SYNC 04000000
+
+/* 9p2000.L at flags */
+#define P9_DOTL_AT_REMOVEDIR 0x200
+
+/* 9P2000.L lock type */
+#define P9_LOCK_TYPE_RDLCK 0
+#define P9_LOCK_TYPE_WRLCK 1
+#define P9_LOCK_TYPE_UNLCK 2
+
+#define P9_LOCK_SUCCESS 0
+#define P9_LOCK_BLOCKED 1
+#define P9_LOCK_ERROR 2
+#define P9_LOCK_GRACE 3
+
+#define P9_LOCK_FLAGS_BLOCK 1
+#define P9_LOCK_FLAGS_RECLAIM 2
+
+typedef struct V9fsFlock
+{
+ uint8_t type;
+ uint32_t flags;
+ uint64_t start; /* absolute offset */
+ uint64_t length;
+ uint32_t proc_id;
+ V9fsString client_id;
+} V9fsFlock;
+
+typedef struct V9fsGetlock
+{
+ uint8_t type;
+ uint64_t start; /* absolute offset */
+ uint64_t length;
+ uint32_t proc_id;
+ V9fsString client_id;
+} V9fsGetlock;
+
+extern int open_fd_hw;
+extern int total_open_fd;
+
+size_t pdu_packunpack(void *addr, struct iovec *sg, int sg_count,
+ size_t offset, size_t size, int pack);
+
+static inline size_t do_pdu_unpack(void *dst, struct iovec *sg, int sg_count,
+ size_t offset, size_t size)
+{
+ return pdu_packunpack(dst, sg, sg_count, offset, size, 0);
+}
+
+static inline void v9fs_path_write_lock(V9fsState *s)
+{
+ if (s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT) {
+ qemu_co_rwlock_wrlock(&s->rename_lock);
+ }
+}
+
+static inline void v9fs_path_read_lock(V9fsState *s)
+{
+ if (s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT) {
+ qemu_co_rwlock_rdlock(&s->rename_lock);
+ }
+}
+
+static inline void v9fs_path_unlock(V9fsState *s)
+{
+ if (s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT) {
+ qemu_co_rwlock_unlock(&s->rename_lock);
+ }
+}
+
+static inline uint8_t v9fs_request_cancelled(V9fsPDU *pdu)
+{
+ return pdu->cancelled;
+}
+
+extern void handle_9p_output(VirtIODevice *vdev, VirtQueue *vq);
+extern void v9fs_reclaim_fd(V9fsPDU *pdu);
+extern void v9fs_path_init(V9fsPath *path);
+extern void v9fs_path_free(V9fsPath *path);
+extern void v9fs_path_copy(V9fsPath *lhs, V9fsPath *rhs);
+extern int v9fs_name_to_path(V9fsState *s, V9fsPath *dirpath,
+ const char *name, V9fsPath *path);
+
+#define pdu_marshal(pdu, offset, fmt, args...) \
+ v9fs_marshal(pdu->elem.in_sg, pdu->elem.in_num, offset, 1, fmt, ##args)
+#define pdu_unmarshal(pdu, offset, fmt, args...) \
+ v9fs_unmarshal(pdu->elem.out_sg, pdu->elem.out_num, offset, 1, fmt, ##args)
+
+#define TYPE_VIRTIO_9P "virtio-9p-device"
+#define VIRTIO_9P(obj) \
+ OBJECT_CHECK(V9fsState, (obj), TYPE_VIRTIO_9P)
+
+#endif
diff --git a/hw/Makefile.objs b/hw/Makefile.objs
new file mode 100644
index 00000000..73afa41b
--- /dev/null
+++ b/hw/Makefile.objs
@@ -0,0 +1,36 @@
+devices-dirs-$(call land, $(CONFIG_VIRTIO),$(call land,$(CONFIG_VIRTFS),$(CONFIG_PCI))) += 9pfs/
+devices-dirs-$(CONFIG_ACPI) += acpi/
+devices-dirs-$(CONFIG_SOFTMMU) += audio/
+devices-dirs-$(CONFIG_SOFTMMU) += block/
+devices-dirs-$(CONFIG_SOFTMMU) += bt/
+devices-dirs-$(CONFIG_SOFTMMU) += char/
+devices-dirs-$(CONFIG_SOFTMMU) += cpu/
+devices-dirs-$(CONFIG_SOFTMMU) += display/
+devices-dirs-$(CONFIG_SOFTMMU) += dma/
+devices-dirs-$(CONFIG_SOFTMMU) += gpio/
+devices-dirs-$(CONFIG_SOFTMMU) += i2c/
+devices-dirs-$(CONFIG_SOFTMMU) += ide/
+devices-dirs-$(CONFIG_SOFTMMU) += input/
+devices-dirs-$(CONFIG_SOFTMMU) += intc/
+devices-dirs-$(CONFIG_IPACK) += ipack/
+devices-dirs-$(CONFIG_SOFTMMU) += isa/
+devices-dirs-$(CONFIG_SOFTMMU) += misc/
+devices-dirs-$(CONFIG_SOFTMMU) += net/
+devices-dirs-$(CONFIG_SOFTMMU) += nvram/
+devices-dirs-$(CONFIG_SOFTMMU) += pci/
+devices-dirs-$(CONFIG_PCI) += pci-bridge/ pci-host/
+devices-dirs-$(CONFIG_SOFTMMU) += pcmcia/
+devices-dirs-$(CONFIG_SOFTMMU) += scsi/
+devices-dirs-$(CONFIG_SOFTMMU) += sd/
+devices-dirs-$(CONFIG_SOFTMMU) += ssi/
+devices-dirs-$(CONFIG_SOFTMMU) += timer/
+devices-dirs-$(CONFIG_TPM) += tpm/
+devices-dirs-$(CONFIG_SOFTMMU) += usb/
+devices-dirs-$(CONFIG_SOFTMMU) += vfio/
+devices-dirs-$(CONFIG_VIRTIO) += virtio/
+devices-dirs-$(CONFIG_SOFTMMU) += watchdog/
+devices-dirs-$(CONFIG_SOFTMMU) += xen/
+devices-dirs-$(CONFIG_MEM_HOTPLUG) += mem/
+devices-dirs-y += core/
+common-obj-y += $(devices-dirs-y)
+obj-y += $(devices-dirs-y)
diff --git a/hw/acpi/Makefile.objs b/hw/acpi/Makefile.objs
new file mode 100644
index 00000000..7d3230c2
--- /dev/null
+++ b/hw/acpi/Makefile.objs
@@ -0,0 +1,7 @@
+common-obj-$(CONFIG_ACPI_X86) += core.o piix4.o pcihp.o
+common-obj-$(CONFIG_ACPI_X86_ICH) += ich9.o tco.o
+common-obj-$(CONFIG_ACPI_CPU_HOTPLUG) += cpu_hotplug.o
+common-obj-$(CONFIG_ACPI_MEMORY_HOTPLUG) += memory_hotplug.o
+common-obj-$(CONFIG_ACPI) += acpi_interface.o
+common-obj-$(CONFIG_ACPI) += bios-linker-loader.o
+common-obj-$(CONFIG_ACPI) += aml-build.o
diff --git a/hw/acpi/acpi_interface.c b/hw/acpi/acpi_interface.c
new file mode 100644
index 00000000..c181bb22
--- /dev/null
+++ b/hw/acpi/acpi_interface.c
@@ -0,0 +1,15 @@
+#include "hw/acpi/acpi_dev_interface.h"
+#include "qemu/module.h"
+
+static void register_types(void)
+{
+ static const TypeInfo acpi_dev_if_info = {
+ .name = TYPE_ACPI_DEVICE_IF,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(AcpiDeviceIfClass),
+ };
+
+ type_register_static(&acpi_dev_if_info);
+}
+
+type_init(register_types)
diff --git a/hw/acpi/aml-build.c b/hw/acpi/aml-build.c
new file mode 100644
index 00000000..0d4b3247
--- /dev/null
+++ b/hw/acpi/aml-build.c
@@ -0,0 +1,1217 @@
+/* Support for generating ACPI tables and passing them to Guests
+ *
+ * Copyright (C) 2015 Red Hat Inc
+ *
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ * Author: Igor Mammedov <imammedo@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gprintf.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+#include "hw/acpi/aml-build.h"
+#include "qemu/bswap.h"
+#include "qemu/bitops.h"
+#include "hw/acpi/bios-linker-loader.h"
+
+static GArray *build_alloc_array(void)
+{
+ return g_array_new(false, true /* clear */, 1);
+}
+
+static void build_free_array(GArray *array)
+{
+ g_array_free(array, true);
+}
+
+static void build_prepend_byte(GArray *array, uint8_t val)
+{
+ g_array_prepend_val(array, val);
+}
+
+static void build_append_byte(GArray *array, uint8_t val)
+{
+ g_array_append_val(array, val);
+}
+
+static void build_append_array(GArray *array, GArray *val)
+{
+ g_array_append_vals(array, val->data, val->len);
+}
+
+#define ACPI_NAMESEG_LEN 4
+
+static void
+build_append_nameseg(GArray *array, const char *seg)
+{
+ int len;
+
+ len = strlen(seg);
+ assert(len <= ACPI_NAMESEG_LEN);
+
+ g_array_append_vals(array, seg, len);
+ /* Pad up to ACPI_NAMESEG_LEN characters if necessary. */
+ g_array_append_vals(array, "____", ACPI_NAMESEG_LEN - len);
+}
+
+static void GCC_FMT_ATTR(2, 0)
+build_append_namestringv(GArray *array, const char *format, va_list ap)
+{
+ char *s;
+ char **segs;
+ char **segs_iter;
+ int seg_count = 0;
+
+ s = g_strdup_vprintf(format, ap);
+ segs = g_strsplit(s, ".", 0);
+ g_free(s);
+
+ /* count segments */
+ segs_iter = segs;
+ while (*segs_iter) {
+ ++segs_iter;
+ ++seg_count;
+ }
+ /*
+ * ACPI 5.0 spec: 20.2.2 Name Objects Encoding:
+ * "SegCount can be from 1 to 255"
+ */
+ assert(seg_count > 0 && seg_count <= 255);
+
+ /* handle RootPath || PrefixPath */
+ s = *segs;
+ while (*s == '\\' || *s == '^') {
+ build_append_byte(array, *s);
+ ++s;
+ }
+
+ switch (seg_count) {
+ case 1:
+ if (!*s) {
+ build_append_byte(array, 0x00); /* NullName */
+ } else {
+ build_append_nameseg(array, s);
+ }
+ break;
+
+ case 2:
+ build_append_byte(array, 0x2E); /* DualNamePrefix */
+ build_append_nameseg(array, s);
+ build_append_nameseg(array, segs[1]);
+ break;
+ default:
+ build_append_byte(array, 0x2F); /* MultiNamePrefix */
+ build_append_byte(array, seg_count);
+
+ /* handle the 1st segment manually due to prefix/root path */
+ build_append_nameseg(array, s);
+
+ /* add the rest of segments */
+ segs_iter = segs + 1;
+ while (*segs_iter) {
+ build_append_nameseg(array, *segs_iter);
+ ++segs_iter;
+ }
+ break;
+ }
+ g_strfreev(segs);
+}
+
+GCC_FMT_ATTR(2, 3)
+static void build_append_namestring(GArray *array, const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ build_append_namestringv(array, format, ap);
+ va_end(ap);
+}
+
+/* 5.4 Definition Block Encoding */
+enum {
+ PACKAGE_LENGTH_1BYTE_SHIFT = 6, /* Up to 63 - use extra 2 bits. */
+ PACKAGE_LENGTH_2BYTE_SHIFT = 4,
+ PACKAGE_LENGTH_3BYTE_SHIFT = 12,
+ PACKAGE_LENGTH_4BYTE_SHIFT = 20,
+};
+
+static void
+build_prepend_package_length(GArray *package, unsigned length, bool incl_self)
+{
+ uint8_t byte;
+ unsigned length_bytes;
+
+ if (length + 1 < (1 << PACKAGE_LENGTH_1BYTE_SHIFT)) {
+ length_bytes = 1;
+ } else if (length + 2 < (1 << PACKAGE_LENGTH_3BYTE_SHIFT)) {
+ length_bytes = 2;
+ } else if (length + 3 < (1 << PACKAGE_LENGTH_4BYTE_SHIFT)) {
+ length_bytes = 3;
+ } else {
+ length_bytes = 4;
+ }
+
+ /*
+ * NamedField uses PkgLength encoding but it doesn't include length
+ * of PkgLength itself.
+ */
+ if (incl_self) {
+ /*
+ * PkgLength is the length of the inclusive length of the data
+ * and PkgLength's length itself when used for terms with
+ * explitit length.
+ */
+ length += length_bytes;
+ }
+
+ switch (length_bytes) {
+ case 1:
+ byte = length;
+ build_prepend_byte(package, byte);
+ return;
+ case 4:
+ byte = length >> PACKAGE_LENGTH_4BYTE_SHIFT;
+ build_prepend_byte(package, byte);
+ length &= (1 << PACKAGE_LENGTH_4BYTE_SHIFT) - 1;
+ /* fall through */
+ case 3:
+ byte = length >> PACKAGE_LENGTH_3BYTE_SHIFT;
+ build_prepend_byte(package, byte);
+ length &= (1 << PACKAGE_LENGTH_3BYTE_SHIFT) - 1;
+ /* fall through */
+ case 2:
+ byte = length >> PACKAGE_LENGTH_2BYTE_SHIFT;
+ build_prepend_byte(package, byte);
+ length &= (1 << PACKAGE_LENGTH_2BYTE_SHIFT) - 1;
+ /* fall through */
+ }
+ /*
+ * Most significant two bits of byte zero indicate how many following bytes
+ * are in PkgLength encoding.
+ */
+ byte = ((length_bytes - 1) << PACKAGE_LENGTH_1BYTE_SHIFT) | length;
+ build_prepend_byte(package, byte);
+}
+
+static void
+build_append_pkg_length(GArray *array, unsigned length, bool incl_self)
+{
+ GArray *tmp = build_alloc_array();
+
+ build_prepend_package_length(tmp, length, incl_self);
+ build_append_array(array, tmp);
+ build_free_array(tmp);
+}
+
+static void build_package(GArray *package, uint8_t op)
+{
+ build_prepend_package_length(package, package->len, true);
+ build_prepend_byte(package, op);
+}
+
+static void build_extop_package(GArray *package, uint8_t op)
+{
+ build_package(package, op);
+ build_prepend_byte(package, 0x5B); /* ExtOpPrefix */
+}
+
+static void build_append_int_noprefix(GArray *table, uint64_t value, int size)
+{
+ int i;
+
+ for (i = 0; i < size; ++i) {
+ build_append_byte(table, value & 0xFF);
+ value = value >> 8;
+ }
+}
+
+static void build_append_int(GArray *table, uint64_t value)
+{
+ if (value == 0x00) {
+ build_append_byte(table, 0x00); /* ZeroOp */
+ } else if (value == 0x01) {
+ build_append_byte(table, 0x01); /* OneOp */
+ } else if (value <= 0xFF) {
+ build_append_byte(table, 0x0A); /* BytePrefix */
+ build_append_int_noprefix(table, value, 1);
+ } else if (value <= 0xFFFF) {
+ build_append_byte(table, 0x0B); /* WordPrefix */
+ build_append_int_noprefix(table, value, 2);
+ } else if (value <= 0xFFFFFFFF) {
+ build_append_byte(table, 0x0C); /* DWordPrefix */
+ build_append_int_noprefix(table, value, 4);
+ } else {
+ build_append_byte(table, 0x0E); /* QWordPrefix */
+ build_append_int_noprefix(table, value, 8);
+ }
+}
+
+static GPtrArray *alloc_list;
+
+static Aml *aml_alloc(void)
+{
+ Aml *var = g_new0(typeof(*var), 1);
+
+ g_ptr_array_add(alloc_list, var);
+ var->block_flags = AML_NO_OPCODE;
+ var->buf = build_alloc_array();
+ return var;
+}
+
+static Aml *aml_opcode(uint8_t op)
+{
+ Aml *var = aml_alloc();
+
+ var->op = op;
+ var->block_flags = AML_OPCODE;
+ return var;
+}
+
+static Aml *aml_bundle(uint8_t op, AmlBlockFlags flags)
+{
+ Aml *var = aml_alloc();
+
+ var->op = op;
+ var->block_flags = flags;
+ return var;
+}
+
+static void aml_free(gpointer data, gpointer user_data)
+{
+ Aml *var = data;
+ build_free_array(var->buf);
+ g_free(var);
+}
+
+Aml *init_aml_allocator(void)
+{
+ Aml *var;
+
+ assert(!alloc_list);
+ alloc_list = g_ptr_array_new();
+ var = aml_alloc();
+ return var;
+}
+
+void free_aml_allocator(void)
+{
+ g_ptr_array_foreach(alloc_list, aml_free, NULL);
+ g_ptr_array_free(alloc_list, true);
+ alloc_list = 0;
+}
+
+/* pack data with DefBuffer encoding */
+static void build_buffer(GArray *array, uint8_t op)
+{
+ GArray *data = build_alloc_array();
+
+ build_append_int(data, array->len);
+ g_array_prepend_vals(array, data->data, data->len);
+ build_free_array(data);
+ build_package(array, op);
+}
+
+void aml_append(Aml *parent_ctx, Aml *child)
+{
+ GArray *buf = build_alloc_array();
+ build_append_array(buf, child->buf);
+
+ switch (child->block_flags) {
+ case AML_OPCODE:
+ build_append_byte(parent_ctx->buf, child->op);
+ break;
+ case AML_EXT_PACKAGE:
+ build_extop_package(buf, child->op);
+ break;
+ case AML_PACKAGE:
+ build_package(buf, child->op);
+ break;
+ case AML_RES_TEMPLATE:
+ build_append_byte(buf, 0x79); /* EndTag */
+ /*
+ * checksum operations are treated as succeeded if checksum
+ * field is zero. [ACPI Spec 1.0b, 6.4.2.8 End Tag]
+ */
+ build_append_byte(buf, 0);
+ /* fall through, to pack resources in buffer */
+ case AML_BUFFER:
+ build_buffer(buf, child->op);
+ break;
+ case AML_NO_OPCODE:
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ build_append_array(parent_ctx->buf, buf);
+ build_free_array(buf);
+}
+
+/* ACPI 1.0b: 16.2.5.1 Namespace Modifier Objects Encoding: DefScope */
+Aml *aml_scope(const char *name_format, ...)
+{
+ va_list ap;
+ Aml *var = aml_bundle(0x10 /* ScopeOp */, AML_PACKAGE);
+ va_start(ap, name_format);
+ build_append_namestringv(var->buf, name_format, ap);
+ va_end(ap);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefReturn */
+Aml *aml_return(Aml *val)
+{
+ Aml *var = aml_opcode(0xA4 /* ReturnOp */);
+ aml_append(var, val);
+ return var;
+}
+
+/*
+ * ACPI 1.0b: 16.2.3 Data Objects Encoding:
+ * encodes: ByteConst, WordConst, DWordConst, QWordConst, ZeroOp, OneOp
+ */
+Aml *aml_int(const uint64_t val)
+{
+ Aml *var = aml_alloc();
+ build_append_int(var->buf, val);
+ return var;
+}
+
+/*
+ * helper to construct NameString, which returns Aml object
+ * for using with aml_append or other aml_* terms
+ */
+Aml *aml_name(const char *name_format, ...)
+{
+ va_list ap;
+ Aml *var = aml_alloc();
+ va_start(ap, name_format);
+ build_append_namestringv(var->buf, name_format, ap);
+ va_end(ap);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.1 Namespace Modifier Objects Encoding: DefName */
+Aml *aml_name_decl(const char *name, Aml *val)
+{
+ Aml *var = aml_opcode(0x08 /* NameOp */);
+ build_append_namestring(var->buf, "%s", name);
+ aml_append(var, val);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.6.1 Arg Objects Encoding */
+Aml *aml_arg(int pos)
+{
+ Aml *var;
+ uint8_t op = 0x68 /* ARG0 op */ + pos;
+
+ assert(pos <= 6);
+ var = aml_opcode(op);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefStore */
+Aml *aml_store(Aml *val, Aml *target)
+{
+ Aml *var = aml_opcode(0x70 /* StoreOp */);
+ aml_append(var, val);
+ aml_append(var, target);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefAnd */
+Aml *aml_and(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x7B /* AndOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefOr */
+Aml *aml_or(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x7D /* OrOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefShiftLeft */
+Aml *aml_shiftleft(Aml *arg1, Aml *count)
+{
+ Aml *var = aml_opcode(0x79 /* ShiftLeftOp */);
+ aml_append(var, arg1);
+ aml_append(var, count);
+ build_append_byte(var->buf, 0x00); /* NullNameOp */
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefShiftRight */
+Aml *aml_shiftright(Aml *arg1, Aml *count)
+{
+ Aml *var = aml_opcode(0x7A /* ShiftRightOp */);
+ aml_append(var, arg1);
+ aml_append(var, count);
+ build_append_byte(var->buf, 0x00); /* NullNameOp */
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLLess */
+Aml *aml_lless(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x95 /* LLessOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefAdd */
+Aml *aml_add(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x72 /* AddOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefIncrement */
+Aml *aml_increment(Aml *arg)
+{
+ Aml *var = aml_opcode(0x75 /* IncrementOp */);
+ aml_append(var, arg);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefIndex */
+Aml *aml_index(Aml *arg1, Aml *idx)
+{
+ Aml *var = aml_opcode(0x88 /* IndexOp */);
+ aml_append(var, arg1);
+ aml_append(var, idx);
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefNotify */
+Aml *aml_notify(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x86 /* NotifyOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* helper to call method with 1 argument */
+Aml *aml_call1(const char *method, Aml *arg1)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ return var;
+}
+
+/* helper to call method with 2 arguments */
+Aml *aml_call2(const char *method, Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* helper to call method with 3 arguments */
+Aml *aml_call3(const char *method, Aml *arg1, Aml *arg2, Aml *arg3)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ aml_append(var, arg3);
+ return var;
+}
+
+/* helper to call method with 4 arguments */
+Aml *aml_call4(const char *method, Aml *arg1, Aml *arg2, Aml *arg3, Aml *arg4)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ aml_append(var, arg3);
+ aml_append(var, arg4);
+ return var;
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.4 32-Bit Fixed Location Memory Range Descriptor
+ * (Type 1, Large Item Name 0x6)
+ */
+Aml *aml_memory32_fixed(uint32_t addr, uint32_t size,
+ AmlReadAndWrite read_and_write)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x86); /* Memory32Fixed Resource Descriptor */
+ build_append_byte(var->buf, 9); /* Length, bits[7:0] value = 9 */
+ build_append_byte(var->buf, 0); /* Length, bits[15:8] value = 0 */
+ build_append_byte(var->buf, read_and_write); /* Write status, 1 rw 0 ro */
+
+ /* Range base address */
+ build_append_byte(var->buf, extract32(addr, 0, 8)); /* bits[7:0] */
+ build_append_byte(var->buf, extract32(addr, 8, 8)); /* bits[15:8] */
+ build_append_byte(var->buf, extract32(addr, 16, 8)); /* bits[23:16] */
+ build_append_byte(var->buf, extract32(addr, 24, 8)); /* bits[31:24] */
+
+ /* Range length */
+ build_append_byte(var->buf, extract32(size, 0, 8)); /* bits[7:0] */
+ build_append_byte(var->buf, extract32(size, 8, 8)); /* bits[15:8] */
+ build_append_byte(var->buf, extract32(size, 16, 8)); /* bits[23:16] */
+ build_append_byte(var->buf, extract32(size, 24, 8)); /* bits[31:24] */
+ return var;
+}
+
+/*
+ * ACPI 5.0: 6.4.3.6 Extended Interrupt Descriptor
+ * Type 1, Large Item Name 0x9
+ */
+Aml *aml_interrupt(AmlConsumerAndProducer con_and_pro,
+ AmlLevelAndEdge level_and_edge,
+ AmlActiveHighAndLow high_and_low, AmlShared shared,
+ uint32_t irq)
+{
+ Aml *var = aml_alloc();
+ uint8_t irq_flags = con_and_pro | (level_and_edge << 1)
+ | (high_and_low << 2) | (shared << 3);
+
+ build_append_byte(var->buf, 0x89); /* Extended irq descriptor */
+ build_append_byte(var->buf, 6); /* Length, bits[7:0] minimum value = 6 */
+ build_append_byte(var->buf, 0); /* Length, bits[15:8] minimum value = 0 */
+ build_append_byte(var->buf, irq_flags); /* Interrupt Vector Information. */
+ build_append_byte(var->buf, 0x01); /* Interrupt table length = 1 */
+
+ /* Interrupt Number */
+ build_append_byte(var->buf, extract32(irq, 0, 8)); /* bits[7:0] */
+ build_append_byte(var->buf, extract32(irq, 8, 8)); /* bits[15:8] */
+ build_append_byte(var->buf, extract32(irq, 16, 8)); /* bits[23:16] */
+ build_append_byte(var->buf, extract32(irq, 24, 8)); /* bits[31:24] */
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.2.5 I/O Port Descriptor */
+Aml *aml_io(AmlIODecode dec, uint16_t min_base, uint16_t max_base,
+ uint8_t aln, uint8_t len)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x47); /* IO port descriptor */
+ build_append_byte(var->buf, dec);
+ build_append_byte(var->buf, min_base & 0xff);
+ build_append_byte(var->buf, (min_base >> 8) & 0xff);
+ build_append_byte(var->buf, max_base & 0xff);
+ build_append_byte(var->buf, (max_base >> 8) & 0xff);
+ build_append_byte(var->buf, aln);
+ build_append_byte(var->buf, len);
+ return var;
+}
+
+/*
+ * ACPI 1.0b: 6.4.2.1.1 ASL Macro for IRQ Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.64 IRQNoFlags (Interrupt Resource Descriptor Macro)
+ * 6.4.2.1 IRQ Descriptor
+ */
+Aml *aml_irq_no_flags(uint8_t irq)
+{
+ uint16_t irq_mask;
+ Aml *var = aml_alloc();
+
+ assert(irq < 16);
+ build_append_byte(var->buf, 0x22); /* IRQ descriptor 2 byte form */
+
+ irq_mask = 1U << irq;
+ build_append_byte(var->buf, irq_mask & 0xFF); /* IRQ mask bits[7:0] */
+ build_append_byte(var->buf, irq_mask >> 8); /* IRQ mask bits[15:8] */
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLNot */
+Aml *aml_lnot(Aml *arg)
+{
+ Aml *var = aml_opcode(0x92 /* LNotOp */);
+ aml_append(var, arg);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLEqual */
+Aml *aml_equal(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x93 /* LequalOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefIfElse */
+Aml *aml_if(Aml *predicate)
+{
+ Aml *var = aml_bundle(0xA0 /* IfOp */, AML_PACKAGE);
+ aml_append(var, predicate);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefElse */
+Aml *aml_else(void)
+{
+ Aml *var = aml_bundle(0xA1 /* ElseOp */, AML_PACKAGE);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefWhile */
+Aml *aml_while(Aml *predicate)
+{
+ Aml *var = aml_bundle(0xA2 /* WhileOp */, AML_PACKAGE);
+ aml_append(var, predicate);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefMethod */
+Aml *aml_method(const char *name, int arg_count)
+{
+ Aml *var = aml_bundle(0x14 /* MethodOp */, AML_PACKAGE);
+ build_append_namestring(var->buf, "%s", name);
+ build_append_byte(var->buf, arg_count); /* MethodFlags: ArgCount */
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefDevice */
+Aml *aml_device(const char *name_format, ...)
+{
+ va_list ap;
+ Aml *var = aml_bundle(0x82 /* DeviceOp */, AML_EXT_PACKAGE);
+ va_start(ap, name_format);
+ build_append_namestringv(var->buf, name_format, ap);
+ va_end(ap);
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.1 ASL Macros for Resource Descriptors */
+Aml *aml_resource_template(void)
+{
+ /* ResourceTemplate is a buffer of Resources with EndTag at the end */
+ Aml *var = aml_bundle(0x11 /* BufferOp */, AML_RES_TEMPLATE);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefBuffer
+ * Pass byte_list as NULL to request uninitialized buffer to reserve space.
+ */
+Aml *aml_buffer(int buffer_size, uint8_t *byte_list)
+{
+ int i;
+ Aml *var = aml_bundle(0x11 /* BufferOp */, AML_BUFFER);
+
+ for (i = 0; i < buffer_size; i++) {
+ if (byte_list == NULL) {
+ build_append_byte(var->buf, 0x0);
+ } else {
+ build_append_byte(var->buf, byte_list[i]);
+ }
+ }
+
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefPackage */
+Aml *aml_package(uint8_t num_elements)
+{
+ Aml *var = aml_bundle(0x12 /* PackageOp */, AML_PACKAGE);
+ build_append_byte(var->buf, num_elements);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefOpRegion */
+Aml *aml_operation_region(const char *name, AmlRegionSpace rs,
+ uint32_t offset, uint32_t len)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x5B); /* ExtOpPrefix */
+ build_append_byte(var->buf, 0x80); /* OpRegionOp */
+ build_append_namestring(var->buf, "%s", name);
+ build_append_byte(var->buf, rs);
+ build_append_int(var->buf, offset);
+ build_append_int(var->buf, len);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: NamedField */
+Aml *aml_named_field(const char *name, unsigned length)
+{
+ Aml *var = aml_alloc();
+ build_append_nameseg(var->buf, name);
+ build_append_pkg_length(var->buf, length, false);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: ReservedField */
+Aml *aml_reserved_field(unsigned length)
+{
+ Aml *var = aml_alloc();
+ /* ReservedField := 0x00 PkgLength */
+ build_append_byte(var->buf, 0x00);
+ build_append_pkg_length(var->buf, length, false);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefField */
+Aml *aml_field(const char *name, AmlAccessType type, AmlUpdateRule rule)
+{
+ Aml *var = aml_bundle(0x81 /* FieldOp */, AML_EXT_PACKAGE);
+ uint8_t flags = rule << 5 | type;
+
+ build_append_namestring(var->buf, "%s", name);
+ build_append_byte(var->buf, flags);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefCreateDWordField */
+Aml *aml_create_dword_field(Aml *srcbuf, Aml *index, const char *name)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x8A); /* CreateDWordFieldOp */
+ aml_append(var, srcbuf);
+ aml_append(var, index);
+ build_append_namestring(var->buf, "%s", name);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.3 Data Objects Encoding: String */
+Aml *aml_string(const char *name_format, ...)
+{
+ Aml *var = aml_opcode(0x0D /* StringPrefix */);
+ va_list ap;
+ char *s;
+ int len;
+
+ va_start(ap, name_format);
+ len = g_vasprintf(&s, name_format, ap);
+ va_end(ap);
+
+ g_array_append_vals(var->buf, s, len + 1);
+ g_free(s);
+
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.6.2 Local Objects Encoding */
+Aml *aml_local(int num)
+{
+ Aml *var;
+ uint8_t op = 0x60 /* Local0Op */ + num;
+
+ assert(num <= 7);
+ var = aml_opcode(op);
+ return var;
+}
+
+/* ACPI 2.0a: 17.2.2 Data Objects Encoding: DefVarPackage */
+Aml *aml_varpackage(uint32_t num_elements)
+{
+ Aml *var = aml_bundle(0x13 /* VarPackageOp */, AML_PACKAGE);
+ build_append_int(var->buf, num_elements);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefProcessor */
+Aml *aml_processor(uint8_t proc_id, uint32_t pblk_addr, uint8_t pblk_len,
+ const char *name_format, ...)
+{
+ va_list ap;
+ Aml *var = aml_bundle(0x83 /* ProcessorOp */, AML_EXT_PACKAGE);
+ va_start(ap, name_format);
+ build_append_namestringv(var->buf, name_format, ap);
+ va_end(ap);
+ build_append_byte(var->buf, proc_id); /* ProcID */
+ build_append_int_noprefix(var->buf, pblk_addr, sizeof(pblk_addr));
+ build_append_byte(var->buf, pblk_len); /* PblkLen */
+ return var;
+}
+
+static uint8_t Hex2Digit(char c)
+{
+ if (c >= 'A') {
+ return c - 'A' + 10;
+ }
+
+ return c - '0';
+}
+
+/* ACPI 1.0b: 15.2.3.6.4.1 EISAID Macro - Convert EISA ID String To Integer */
+Aml *aml_eisaid(const char *str)
+{
+ Aml *var = aml_alloc();
+ uint32_t id;
+
+ g_assert(strlen(str) == 7);
+ id = (str[0] - 0x40) << 26 |
+ (str[1] - 0x40) << 21 |
+ (str[2] - 0x40) << 16 |
+ Hex2Digit(str[3]) << 12 |
+ Hex2Digit(str[4]) << 8 |
+ Hex2Digit(str[5]) << 4 |
+ Hex2Digit(str[6]);
+
+ build_append_byte(var->buf, 0x0C); /* DWordPrefix */
+ build_append_int_noprefix(var->buf, bswap32(id), sizeof(id));
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.3.5.5 Word Address Space Descriptor: bytes 3-5 */
+static Aml *aml_as_desc_header(AmlResourceType type, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlDecode dec,
+ uint8_t type_flags)
+{
+ uint8_t flags = max_fixed | min_fixed | dec;
+ Aml *var = aml_alloc();
+
+ build_append_byte(var->buf, type);
+ build_append_byte(var->buf, flags);
+ build_append_byte(var->buf, type_flags); /* Type Specific Flags */
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.3.5.5 Word Address Space Descriptor */
+static Aml *aml_word_as_desc(AmlResourceType type, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlDecode dec,
+ uint16_t addr_gran, uint16_t addr_min,
+ uint16_t addr_max, uint16_t addr_trans,
+ uint16_t len, uint8_t type_flags)
+{
+ Aml *var = aml_alloc();
+
+ build_append_byte(var->buf, 0x88); /* Word Address Space Descriptor */
+ /* minimum length since we do not encode optional fields */
+ build_append_byte(var->buf, 0x0D);
+ build_append_byte(var->buf, 0x0);
+
+ aml_append(var,
+ aml_as_desc_header(type, min_fixed, max_fixed, dec, type_flags));
+ build_append_int_noprefix(var->buf, addr_gran, sizeof(addr_gran));
+ build_append_int_noprefix(var->buf, addr_min, sizeof(addr_min));
+ build_append_int_noprefix(var->buf, addr_max, sizeof(addr_max));
+ build_append_int_noprefix(var->buf, addr_trans, sizeof(addr_trans));
+ build_append_int_noprefix(var->buf, len, sizeof(len));
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.3.5.3 DWord Address Space Descriptor */
+static Aml *aml_dword_as_desc(AmlResourceType type, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlDecode dec,
+ uint32_t addr_gran, uint32_t addr_min,
+ uint32_t addr_max, uint32_t addr_trans,
+ uint32_t len, uint8_t type_flags)
+{
+ Aml *var = aml_alloc();
+
+ build_append_byte(var->buf, 0x87); /* DWord Address Space Descriptor */
+ /* minimum length since we do not encode optional fields */
+ build_append_byte(var->buf, 23);
+ build_append_byte(var->buf, 0x0);
+
+
+ aml_append(var,
+ aml_as_desc_header(type, min_fixed, max_fixed, dec, type_flags));
+ build_append_int_noprefix(var->buf, addr_gran, sizeof(addr_gran));
+ build_append_int_noprefix(var->buf, addr_min, sizeof(addr_min));
+ build_append_int_noprefix(var->buf, addr_max, sizeof(addr_max));
+ build_append_int_noprefix(var->buf, addr_trans, sizeof(addr_trans));
+ build_append_int_noprefix(var->buf, len, sizeof(len));
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.3.5.1 QWord Address Space Descriptor */
+static Aml *aml_qword_as_desc(AmlResourceType type, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlDecode dec,
+ uint64_t addr_gran, uint64_t addr_min,
+ uint64_t addr_max, uint64_t addr_trans,
+ uint64_t len, uint8_t type_flags)
+{
+ Aml *var = aml_alloc();
+
+ build_append_byte(var->buf, 0x8A); /* QWord Address Space Descriptor */
+ /* minimum length since we do not encode optional fields */
+ build_append_byte(var->buf, 0x2B);
+ build_append_byte(var->buf, 0x0);
+
+ aml_append(var,
+ aml_as_desc_header(type, min_fixed, max_fixed, dec, type_flags));
+ build_append_int_noprefix(var->buf, addr_gran, sizeof(addr_gran));
+ build_append_int_noprefix(var->buf, addr_min, sizeof(addr_min));
+ build_append_int_noprefix(var->buf, addr_max, sizeof(addr_max));
+ build_append_int_noprefix(var->buf, addr_trans, sizeof(addr_trans));
+ build_append_int_noprefix(var->buf, len, sizeof(len));
+ return var;
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.6 ASL Macros for WORD Address Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.141 WordBusNumber (Word Bus Number Resource Descriptor Macro)
+ */
+Aml *aml_word_bus_number(AmlMinFixed min_fixed, AmlMaxFixed max_fixed,
+ AmlDecode dec, uint16_t addr_gran,
+ uint16_t addr_min, uint16_t addr_max,
+ uint16_t addr_trans, uint16_t len)
+
+{
+ return aml_word_as_desc(AML_BUS_NUMBER_RANGE, min_fixed, max_fixed, dec,
+ addr_gran, addr_min, addr_max, addr_trans, len, 0);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.6 ASL Macros for WORD Address Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.142 WordIO (Word IO Resource Descriptor Macro)
+ */
+Aml *aml_word_io(AmlMinFixed min_fixed, AmlMaxFixed max_fixed,
+ AmlDecode dec, AmlISARanges isa_ranges,
+ uint16_t addr_gran, uint16_t addr_min,
+ uint16_t addr_max, uint16_t addr_trans,
+ uint16_t len)
+
+{
+ return aml_word_as_desc(AML_IO_RANGE, min_fixed, max_fixed, dec,
+ addr_gran, addr_min, addr_max, addr_trans, len,
+ isa_ranges);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.4 ASL Macros for DWORD Address Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.33 DWordIO (DWord IO Resource Descriptor Macro)
+ */
+Aml *aml_dword_io(AmlMinFixed min_fixed, AmlMaxFixed max_fixed,
+ AmlDecode dec, AmlISARanges isa_ranges,
+ uint32_t addr_gran, uint32_t addr_min,
+ uint32_t addr_max, uint32_t addr_trans,
+ uint32_t len)
+
+{
+ return aml_dword_as_desc(AML_IO_RANGE, min_fixed, max_fixed, dec,
+ addr_gran, addr_min, addr_max, addr_trans, len,
+ isa_ranges);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.4 ASL Macros for DWORD Address Space Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.34 DWordMemory (DWord Memory Resource Descriptor Macro)
+ */
+Aml *aml_dword_memory(AmlDecode dec, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlCacheable cacheable,
+ AmlReadAndWrite read_and_write,
+ uint32_t addr_gran, uint32_t addr_min,
+ uint32_t addr_max, uint32_t addr_trans,
+ uint32_t len)
+{
+ uint8_t flags = read_and_write | (cacheable << 1);
+
+ return aml_dword_as_desc(AML_MEMORY_RANGE, min_fixed, max_fixed,
+ dec, addr_gran, addr_min, addr_max,
+ addr_trans, len, flags);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.2 ASL Macros for QWORD Address Space Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.102 QWordMemory (QWord Memory Resource Descriptor Macro)
+ */
+Aml *aml_qword_memory(AmlDecode dec, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlCacheable cacheable,
+ AmlReadAndWrite read_and_write,
+ uint64_t addr_gran, uint64_t addr_min,
+ uint64_t addr_max, uint64_t addr_trans,
+ uint64_t len)
+{
+ uint8_t flags = read_and_write | (cacheable << 1);
+
+ return aml_qword_as_desc(AML_MEMORY_RANGE, min_fixed, max_fixed,
+ dec, addr_gran, addr_min, addr_max,
+ addr_trans, len, flags);
+}
+
+static uint8_t Hex2Byte(const char *src)
+{
+ int hi, lo;
+
+ hi = Hex2Digit(src[0]);
+ assert(hi >= 0);
+ assert(hi <= 15);
+
+ lo = Hex2Digit(src[1]);
+ assert(lo >= 0);
+ assert(lo <= 15);
+ return (hi << 4) | lo;
+}
+
+/*
+ * ACPI 3.0: 17.5.124 ToUUID (Convert String to UUID Macro)
+ * e.g. UUID: aabbccdd-eeff-gghh-iijj-kkllmmnnoopp
+ * call aml_touuid("aabbccdd-eeff-gghh-iijj-kkllmmnnoopp");
+ */
+Aml *aml_touuid(const char *uuid)
+{
+ Aml *var = aml_bundle(0x11 /* BufferOp */, AML_BUFFER);
+
+ assert(strlen(uuid) == 36);
+ assert(uuid[8] == '-');
+ assert(uuid[13] == '-');
+ assert(uuid[18] == '-');
+ assert(uuid[23] == '-');
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 6)); /* dd - at offset 00 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 4)); /* cc - at offset 01 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 2)); /* bb - at offset 02 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 0)); /* aa - at offset 03 */
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 11)); /* ff - at offset 04 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 9)); /* ee - at offset 05 */
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 16)); /* hh - at offset 06 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 14)); /* gg - at offset 07 */
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 19)); /* ii - at offset 08 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 21)); /* jj - at offset 09 */
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 24)); /* kk - at offset 10 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 26)); /* ll - at offset 11 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 28)); /* mm - at offset 12 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 30)); /* nn - at offset 13 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 32)); /* oo - at offset 14 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 34)); /* pp - at offset 15 */
+
+ return var;
+}
+
+/*
+ * ACPI 2.0b: 16.2.3.6.4.3 Unicode Macro (Convert Ascii String To Unicode)
+ */
+Aml *aml_unicode(const char *str)
+{
+ int i = 0;
+ Aml *var = aml_bundle(0x11 /* BufferOp */, AML_BUFFER);
+
+ do {
+ build_append_byte(var->buf, str[i]);
+ build_append_byte(var->buf, 0);
+ i++;
+ } while (i <= strlen(str));
+
+ return var;
+}
+
+void
+build_header(GArray *linker, GArray *table_data,
+ AcpiTableHeader *h, const char *sig, int len, uint8_t rev)
+{
+ memcpy(&h->signature, sig, 4);
+ h->length = cpu_to_le32(len);
+ h->revision = rev;
+ memcpy(h->oem_id, ACPI_BUILD_APPNAME6, 6);
+ memcpy(h->oem_table_id, ACPI_BUILD_APPNAME4, 4);
+ memcpy(h->oem_table_id + 4, sig, 4);
+ h->oem_revision = cpu_to_le32(1);
+ memcpy(h->asl_compiler_id, ACPI_BUILD_APPNAME4, 4);
+ h->asl_compiler_revision = cpu_to_le32(1);
+ h->checksum = 0;
+ /* Checksum to be filled in by Guest linker */
+ bios_linker_loader_add_checksum(linker, ACPI_BUILD_TABLE_FILE,
+ table_data->data, h, len, &h->checksum);
+}
+
+void *acpi_data_push(GArray *table_data, unsigned size)
+{
+ unsigned off = table_data->len;
+ g_array_set_size(table_data, off + size);
+ return table_data->data + off;
+}
+
+unsigned acpi_data_len(GArray *table)
+{
+#if GLIB_CHECK_VERSION(2, 22, 0)
+ assert(g_array_get_element_size(table) == 1);
+#endif
+ return table->len;
+}
+
+void acpi_add_table(GArray *table_offsets, GArray *table_data)
+{
+ uint32_t offset = cpu_to_le32(table_data->len);
+ g_array_append_val(table_offsets, offset);
+}
+
+void acpi_build_tables_init(AcpiBuildTables *tables)
+{
+ tables->rsdp = g_array_new(false, true /* clear */, 1);
+ tables->table_data = g_array_new(false, true /* clear */, 1);
+ tables->tcpalog = g_array_new(false, true /* clear */, 1);
+ tables->linker = bios_linker_loader_init();
+}
+
+void acpi_build_tables_cleanup(AcpiBuildTables *tables, bool mfre)
+{
+ void *linker_data = bios_linker_loader_cleanup(tables->linker);
+ g_free(linker_data);
+ g_array_free(tables->rsdp, true);
+ g_array_free(tables->table_data, true);
+ g_array_free(tables->tcpalog, mfre);
+}
+
+/* Build rsdt table */
+void
+build_rsdt(GArray *table_data, GArray *linker, GArray *table_offsets)
+{
+ AcpiRsdtDescriptorRev1 *rsdt;
+ size_t rsdt_len;
+ int i;
+ const int table_data_len = (sizeof(uint32_t) * table_offsets->len);
+
+ rsdt_len = sizeof(*rsdt) + table_data_len;
+ rsdt = acpi_data_push(table_data, rsdt_len);
+ memcpy(rsdt->table_offset_entry, table_offsets->data, table_data_len);
+ for (i = 0; i < table_offsets->len; ++i) {
+ /* rsdt->table_offset_entry to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker,
+ ACPI_BUILD_TABLE_FILE,
+ ACPI_BUILD_TABLE_FILE,
+ table_data, &rsdt->table_offset_entry[i],
+ sizeof(uint32_t));
+ }
+ build_header(linker, table_data,
+ (void *)rsdt, "RSDT", rsdt_len, 1);
+}
diff --git a/hw/acpi/bios-linker-loader.c b/hw/acpi/bios-linker-loader.c
new file mode 100644
index 00000000..d9382f82
--- /dev/null
+++ b/hw/acpi/bios-linker-loader.c
@@ -0,0 +1,159 @@
+/* Dynamic linker/loader of ACPI tables
+ *
+ * Copyright (C) 2013 Red Hat Inc
+ *
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "hw/acpi/bios-linker-loader.h"
+#include "hw/nvram/fw_cfg.h"
+
+#include "qemu/bswap.h"
+
+#define BIOS_LINKER_LOADER_FILESZ FW_CFG_MAX_FILE_PATH
+
+struct BiosLinkerLoaderEntry {
+ uint32_t command;
+ union {
+ /*
+ * COMMAND_ALLOCATE - allocate a table from @alloc.file
+ * subject to @alloc.align alignment (must be power of 2)
+ * and @alloc.zone (can be HIGH or FSEG) requirements.
+ *
+ * Must appear exactly once for each file, and before
+ * this file is referenced by any other command.
+ */
+ struct {
+ char file[BIOS_LINKER_LOADER_FILESZ];
+ uint32_t align;
+ uint8_t zone;
+ } alloc;
+
+ /*
+ * COMMAND_ADD_POINTER - patch the table (originating from
+ * @dest_file) at @pointer.offset, by adding a pointer to the table
+ * originating from @src_file. 1,2,4 or 8 byte unsigned
+ * addition is used depending on @pointer.size.
+ */
+ struct {
+ char dest_file[BIOS_LINKER_LOADER_FILESZ];
+ char src_file[BIOS_LINKER_LOADER_FILESZ];
+ uint32_t offset;
+ uint8_t size;
+ } pointer;
+
+ /*
+ * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by
+ * @cksum_start and @cksum_length fields,
+ * and then add the value at @cksum.offset.
+ * Checksum simply sums -X for each byte X in the range
+ * using 8-bit math.
+ */
+ struct {
+ char file[BIOS_LINKER_LOADER_FILESZ];
+ uint32_t offset;
+ uint32_t start;
+ uint32_t length;
+ } cksum;
+
+ /* padding */
+ char pad[124];
+ };
+} QEMU_PACKED;
+typedef struct BiosLinkerLoaderEntry BiosLinkerLoaderEntry;
+
+enum {
+ BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1,
+ BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2,
+ BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3,
+};
+
+enum {
+ BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1,
+ BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2,
+};
+
+GArray *bios_linker_loader_init(void)
+{
+ return g_array_new(false, true /* clear */, 1);
+}
+
+/* Free linker wrapper and return the linker array. */
+void *bios_linker_loader_cleanup(GArray *linker)
+{
+ return g_array_free(linker, false);
+}
+
+void bios_linker_loader_alloc(GArray *linker,
+ const char *file,
+ uint32_t alloc_align,
+ bool alloc_fseg)
+{
+ BiosLinkerLoaderEntry entry;
+
+ memset(&entry, 0, sizeof entry);
+ strncpy(entry.alloc.file, file, sizeof entry.alloc.file - 1);
+ entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ALLOCATE);
+ entry.alloc.align = cpu_to_le32(alloc_align);
+ entry.alloc.zone = cpu_to_le32(alloc_fseg ?
+ BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG :
+ BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH);
+
+ /* Alloc entries must come first, so prepend them */
+ g_array_prepend_vals(linker, &entry, sizeof entry);
+}
+
+void bios_linker_loader_add_checksum(GArray *linker, const char *file,
+ void *table,
+ void *start, unsigned size,
+ uint8_t *checksum)
+{
+ BiosLinkerLoaderEntry entry;
+
+ memset(&entry, 0, sizeof entry);
+ strncpy(entry.cksum.file, file, sizeof entry.cksum.file - 1);
+ entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM);
+ entry.cksum.offset = cpu_to_le32(checksum - (uint8_t *)table);
+ entry.cksum.start = cpu_to_le32((uint8_t *)start - (uint8_t *)table);
+ entry.cksum.length = cpu_to_le32(size);
+
+ g_array_append_vals(linker, &entry, sizeof entry);
+}
+
+void bios_linker_loader_add_pointer(GArray *linker,
+ const char *dest_file,
+ const char *src_file,
+ GArray *table, void *pointer,
+ uint8_t pointer_size)
+{
+ BiosLinkerLoaderEntry entry;
+ size_t offset = (gchar *)pointer - table->data;
+
+ memset(&entry, 0, sizeof entry);
+ strncpy(entry.pointer.dest_file, dest_file,
+ sizeof entry.pointer.dest_file - 1);
+ strncpy(entry.pointer.src_file, src_file,
+ sizeof entry.pointer.src_file - 1);
+ entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_POINTER);
+ assert(table->len >= offset + pointer_size);
+ entry.pointer.offset = cpu_to_le32(offset);
+ entry.pointer.size = pointer_size;
+ assert(pointer_size == 1 || pointer_size == 2 ||
+ pointer_size == 4 || pointer_size == 8);
+
+ g_array_append_vals(linker, &entry, sizeof entry);
+}
diff --git a/hw/acpi/core.c b/hw/acpi/core.c
new file mode 100644
index 00000000..21e113d7
--- /dev/null
+++ b/hw/acpi/core.c
@@ -0,0 +1,710 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "sysemu/sysemu.h"
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/acpi/acpi.h"
+#include "hw/nvram/fw_cfg.h"
+#include "qemu/config-file.h"
+#include "qapi/opts-visitor.h"
+#include "qapi/dealloc-visitor.h"
+#include "qapi-visit.h"
+#include "qapi-event.h"
+
+struct acpi_table_header {
+ uint16_t _length; /* our length, not actual part of the hdr */
+ /* allows easier parsing for fw_cfg clients */
+ char sig[4]; /* ACPI signature (4 ASCII characters) */
+ uint32_t length; /* Length of table, in bytes, including header */
+ uint8_t revision; /* ACPI Specification minor version # */
+ uint8_t checksum; /* To make sum of entire table == 0 */
+ char oem_id[6]; /* OEM identification */
+ char oem_table_id[8]; /* OEM table identification */
+ uint32_t oem_revision; /* OEM revision number */
+ char asl_compiler_id[4]; /* ASL compiler vendor ID */
+ uint32_t asl_compiler_revision; /* ASL compiler revision number */
+} QEMU_PACKED;
+
+#define ACPI_TABLE_HDR_SIZE sizeof(struct acpi_table_header)
+#define ACPI_TABLE_PFX_SIZE sizeof(uint16_t) /* size of the extra prefix */
+
+static const char unsigned dfl_hdr[ACPI_TABLE_HDR_SIZE - ACPI_TABLE_PFX_SIZE] =
+ "QEMU\0\0\0\0\1\0" /* sig (4), len(4), revno (1), csum (1) */
+ "QEMUQEQEMUQEMU\1\0\0\0" /* OEM id (6), table (8), revno (4) */
+ "QEMU\1\0\0\0" /* ASL compiler ID (4), version (4) */
+ ;
+
+char unsigned *acpi_tables;
+size_t acpi_tables_len;
+
+static QemuOptsList qemu_acpi_opts = {
+ .name = "acpi",
+ .implied_opt_name = "data",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_acpi_opts.head),
+ .desc = { { 0 } } /* validated with OptsVisitor */
+};
+
+static void acpi_register_config(void)
+{
+ qemu_add_opts(&qemu_acpi_opts);
+}
+
+machine_init(acpi_register_config);
+
+static int acpi_checksum(const uint8_t *data, int len)
+{
+ int sum, i;
+ sum = 0;
+ for (i = 0; i < len; i++) {
+ sum += data[i];
+ }
+ return (-sum) & 0xff;
+}
+
+
+/* Install a copy of the ACPI table specified in @blob.
+ *
+ * If @has_header is set, @blob starts with the System Description Table Header
+ * structure. Otherwise, "dfl_hdr" is prepended. In any case, each header field
+ * is optionally overwritten from @hdrs.
+ *
+ * It is valid to call this function with
+ * (@blob == NULL && bloblen == 0 && !has_header).
+ *
+ * @hdrs->file and @hdrs->data are ignored.
+ *
+ * SIZE_MAX is considered "infinity" in this function.
+ *
+ * The number of tables that can be installed is not limited, but the 16-bit
+ * counter at the beginning of "acpi_tables" wraps around after UINT16_MAX.
+ */
+static void acpi_table_install(const char unsigned *blob, size_t bloblen,
+ bool has_header,
+ const struct AcpiTableOptions *hdrs,
+ Error **errp)
+{
+ size_t body_start;
+ const char unsigned *hdr_src;
+ size_t body_size, acpi_payload_size;
+ struct acpi_table_header *ext_hdr;
+ unsigned changed_fields;
+
+ /* Calculate where the ACPI table body starts within the blob, plus where
+ * to copy the ACPI table header from.
+ */
+ if (has_header) {
+ /* _length | ACPI header in blob | blob body
+ * ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^
+ * ACPI_TABLE_PFX_SIZE sizeof dfl_hdr body_size
+ * == body_start
+ *
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * acpi_payload_size == bloblen
+ */
+ body_start = sizeof dfl_hdr;
+
+ if (bloblen < body_start) {
+ error_setg(errp, "ACPI table claiming to have header is too "
+ "short, available: %zu, expected: %zu", bloblen,
+ body_start);
+ return;
+ }
+ hdr_src = blob;
+ } else {
+ /* _length | ACPI header in template | blob body
+ * ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^
+ * ACPI_TABLE_PFX_SIZE sizeof dfl_hdr body_size
+ * == bloblen
+ *
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * acpi_payload_size
+ */
+ body_start = 0;
+ hdr_src = dfl_hdr;
+ }
+ body_size = bloblen - body_start;
+ acpi_payload_size = sizeof dfl_hdr + body_size;
+
+ if (acpi_payload_size > UINT16_MAX) {
+ error_setg(errp, "ACPI table too big, requested: %zu, max: %u",
+ acpi_payload_size, (unsigned)UINT16_MAX);
+ return;
+ }
+
+ /* We won't fail from here on. Initialize / extend the globals. */
+ if (acpi_tables == NULL) {
+ acpi_tables_len = sizeof(uint16_t);
+ acpi_tables = g_malloc0(acpi_tables_len);
+ }
+
+ acpi_tables = g_realloc(acpi_tables, acpi_tables_len +
+ ACPI_TABLE_PFX_SIZE +
+ sizeof dfl_hdr + body_size);
+
+ ext_hdr = (struct acpi_table_header *)(acpi_tables + acpi_tables_len);
+ acpi_tables_len += ACPI_TABLE_PFX_SIZE;
+
+ memcpy(acpi_tables + acpi_tables_len, hdr_src, sizeof dfl_hdr);
+ acpi_tables_len += sizeof dfl_hdr;
+
+ if (blob != NULL) {
+ memcpy(acpi_tables + acpi_tables_len, blob + body_start, body_size);
+ acpi_tables_len += body_size;
+ }
+
+ /* increase number of tables */
+ stw_le_p(acpi_tables, lduw_le_p(acpi_tables) + 1u);
+
+ /* Update the header fields. The strings need not be NUL-terminated. */
+ changed_fields = 0;
+ ext_hdr->_length = cpu_to_le16(acpi_payload_size);
+
+ if (hdrs->has_sig) {
+ strncpy(ext_hdr->sig, hdrs->sig, sizeof ext_hdr->sig);
+ ++changed_fields;
+ }
+
+ if (has_header && le32_to_cpu(ext_hdr->length) != acpi_payload_size) {
+ fprintf(stderr,
+ "warning: ACPI table has wrong length, header says "
+ "%" PRIu32 ", actual size %zu bytes\n",
+ le32_to_cpu(ext_hdr->length), acpi_payload_size);
+ }
+ ext_hdr->length = cpu_to_le32(acpi_payload_size);
+
+ if (hdrs->has_rev) {
+ ext_hdr->revision = hdrs->rev;
+ ++changed_fields;
+ }
+
+ ext_hdr->checksum = 0;
+
+ if (hdrs->has_oem_id) {
+ strncpy(ext_hdr->oem_id, hdrs->oem_id, sizeof ext_hdr->oem_id);
+ ++changed_fields;
+ }
+ if (hdrs->has_oem_table_id) {
+ strncpy(ext_hdr->oem_table_id, hdrs->oem_table_id,
+ sizeof ext_hdr->oem_table_id);
+ ++changed_fields;
+ }
+ if (hdrs->has_oem_rev) {
+ ext_hdr->oem_revision = cpu_to_le32(hdrs->oem_rev);
+ ++changed_fields;
+ }
+ if (hdrs->has_asl_compiler_id) {
+ strncpy(ext_hdr->asl_compiler_id, hdrs->asl_compiler_id,
+ sizeof ext_hdr->asl_compiler_id);
+ ++changed_fields;
+ }
+ if (hdrs->has_asl_compiler_rev) {
+ ext_hdr->asl_compiler_revision = cpu_to_le32(hdrs->asl_compiler_rev);
+ ++changed_fields;
+ }
+
+ if (!has_header && changed_fields == 0) {
+ fprintf(stderr, "warning: ACPI table: no headers are specified\n");
+ }
+
+ /* recalculate checksum */
+ ext_hdr->checksum = acpi_checksum((const char unsigned *)ext_hdr +
+ ACPI_TABLE_PFX_SIZE, acpi_payload_size);
+}
+
+void acpi_table_add(const QemuOpts *opts, Error **errp)
+{
+ AcpiTableOptions *hdrs = NULL;
+ Error *err = NULL;
+ char **pathnames = NULL;
+ char **cur;
+ size_t bloblen = 0;
+ char unsigned *blob = NULL;
+
+ {
+ OptsVisitor *ov;
+
+ ov = opts_visitor_new(opts);
+ visit_type_AcpiTableOptions(opts_get_visitor(ov), &hdrs, NULL, &err);
+ opts_visitor_cleanup(ov);
+ }
+
+ if (err) {
+ goto out;
+ }
+ if (hdrs->has_file == hdrs->has_data) {
+ error_setg(&err, "'-acpitable' requires one of 'data' or 'file'");
+ goto out;
+ }
+
+ pathnames = g_strsplit(hdrs->has_file ? hdrs->file : hdrs->data, ":", 0);
+ if (pathnames == NULL || pathnames[0] == NULL) {
+ error_setg(&err, "'-acpitable' requires at least one pathname");
+ goto out;
+ }
+
+ /* now read in the data files, reallocating buffer as needed */
+ for (cur = pathnames; *cur; ++cur) {
+ int fd = open(*cur, O_RDONLY | O_BINARY);
+
+ if (fd < 0) {
+ error_setg(&err, "can't open file %s: %s", *cur, strerror(errno));
+ goto out;
+ }
+
+ for (;;) {
+ char unsigned data[8192];
+ ssize_t r;
+
+ r = read(fd, data, sizeof data);
+ if (r == 0) {
+ break;
+ } else if (r > 0) {
+ blob = g_realloc(blob, bloblen + r);
+ memcpy(blob + bloblen, data, r);
+ bloblen += r;
+ } else if (errno != EINTR) {
+ error_setg(&err, "can't read file %s: %s",
+ *cur, strerror(errno));
+ close(fd);
+ goto out;
+ }
+ }
+
+ close(fd);
+ }
+
+ acpi_table_install(blob, bloblen, hdrs->has_file, hdrs, &err);
+
+out:
+ g_free(blob);
+ g_strfreev(pathnames);
+
+ if (hdrs != NULL) {
+ QapiDeallocVisitor *dv;
+
+ dv = qapi_dealloc_visitor_new();
+ visit_type_AcpiTableOptions(qapi_dealloc_get_visitor(dv), &hdrs, NULL,
+ NULL);
+ qapi_dealloc_visitor_cleanup(dv);
+ }
+
+ error_propagate(errp, err);
+}
+
+static bool acpi_table_builtin = false;
+
+void acpi_table_add_builtin(const QemuOpts *opts, Error **errp)
+{
+ acpi_table_builtin = true;
+ acpi_table_add(opts, errp);
+}
+
+unsigned acpi_table_len(void *current)
+{
+ struct acpi_table_header *hdr = current - sizeof(hdr->_length);
+ return hdr->_length;
+}
+
+static
+void *acpi_table_hdr(void *h)
+{
+ struct acpi_table_header *hdr = h;
+ return &hdr->sig;
+}
+
+uint8_t *acpi_table_first(void)
+{
+ if (acpi_table_builtin || !acpi_tables) {
+ return NULL;
+ }
+ return acpi_table_hdr(acpi_tables + ACPI_TABLE_PFX_SIZE);
+}
+
+uint8_t *acpi_table_next(uint8_t *current)
+{
+ uint8_t *next = current + acpi_table_len(current);
+
+ if (next - acpi_tables >= acpi_tables_len) {
+ return NULL;
+ } else {
+ return acpi_table_hdr(next);
+ }
+}
+
+static void acpi_notify_wakeup(Notifier *notifier, void *data)
+{
+ ACPIREGS *ar = container_of(notifier, ACPIREGS, wakeup);
+ WakeupReason *reason = data;
+
+ switch (*reason) {
+ case QEMU_WAKEUP_REASON_RTC:
+ ar->pm1.evt.sts |=
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_RT_CLOCK_STATUS);
+ break;
+ case QEMU_WAKEUP_REASON_PMTIMER:
+ ar->pm1.evt.sts |=
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_TIMER_STATUS);
+ break;
+ case QEMU_WAKEUP_REASON_OTHER:
+ /* ACPI_BITMASK_WAKE_STATUS should be set on resume.
+ Pretend that resume was caused by power button */
+ ar->pm1.evt.sts |=
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_POWER_BUTTON_STATUS);
+ break;
+ default:
+ break;
+ }
+}
+
+/* ACPI PM1a EVT */
+uint16_t acpi_pm1_evt_get_sts(ACPIREGS *ar)
+{
+ /* Compare ns-clock, not PM timer ticks, because
+ acpi_pm_tmr_update function uses ns for setting the timer. */
+ int64_t d = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ if (d >= muldiv64(ar->tmr.overflow_time,
+ get_ticks_per_sec(), PM_TIMER_FREQUENCY)) {
+ ar->pm1.evt.sts |= ACPI_BITMASK_TIMER_STATUS;
+ }
+ return ar->pm1.evt.sts;
+}
+
+static void acpi_pm1_evt_write_sts(ACPIREGS *ar, uint16_t val)
+{
+ uint16_t pm1_sts = acpi_pm1_evt_get_sts(ar);
+ if (pm1_sts & val & ACPI_BITMASK_TIMER_STATUS) {
+ /* if TMRSTS is reset, then compute the new overflow time */
+ acpi_pm_tmr_calc_overflow_time(ar);
+ }
+ ar->pm1.evt.sts &= ~val;
+}
+
+static void acpi_pm1_evt_write_en(ACPIREGS *ar, uint16_t val)
+{
+ ar->pm1.evt.en = val;
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_RTC,
+ val & ACPI_BITMASK_RT_CLOCK_ENABLE);
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_PMTIMER,
+ val & ACPI_BITMASK_TIMER_ENABLE);
+}
+
+void acpi_pm1_evt_power_down(ACPIREGS *ar)
+{
+ if (ar->pm1.evt.en & ACPI_BITMASK_POWER_BUTTON_ENABLE) {
+ ar->pm1.evt.sts |= ACPI_BITMASK_POWER_BUTTON_STATUS;
+ ar->tmr.update_sci(ar);
+ }
+}
+
+void acpi_pm1_evt_reset(ACPIREGS *ar)
+{
+ ar->pm1.evt.sts = 0;
+ ar->pm1.evt.en = 0;
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_RTC, 0);
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_PMTIMER, 0);
+}
+
+static uint64_t acpi_pm_evt_read(void *opaque, hwaddr addr, unsigned width)
+{
+ ACPIREGS *ar = opaque;
+ switch (addr) {
+ case 0:
+ return acpi_pm1_evt_get_sts(ar);
+ case 2:
+ return ar->pm1.evt.en;
+ default:
+ return 0;
+ }
+}
+
+static void acpi_pm_evt_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ ACPIREGS *ar = opaque;
+ switch (addr) {
+ case 0:
+ acpi_pm1_evt_write_sts(ar, val);
+ ar->pm1.evt.update_sci(ar);
+ break;
+ case 2:
+ acpi_pm1_evt_write_en(ar, val);
+ ar->pm1.evt.update_sci(ar);
+ break;
+ }
+}
+
+static const MemoryRegionOps acpi_pm_evt_ops = {
+ .read = acpi_pm_evt_read,
+ .write = acpi_pm_evt_write,
+ .valid.min_access_size = 2,
+ .valid.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void acpi_pm1_evt_init(ACPIREGS *ar, acpi_update_sci_fn update_sci,
+ MemoryRegion *parent)
+{
+ ar->pm1.evt.update_sci = update_sci;
+ memory_region_init_io(&ar->pm1.evt.io, memory_region_owner(parent),
+ &acpi_pm_evt_ops, ar, "acpi-evt", 4);
+ memory_region_add_subregion(parent, 0, &ar->pm1.evt.io);
+}
+
+/* ACPI PM_TMR */
+void acpi_pm_tmr_update(ACPIREGS *ar, bool enable)
+{
+ int64_t expire_time;
+
+ /* schedule a timer interruption if needed */
+ if (enable) {
+ expire_time = muldiv64(ar->tmr.overflow_time, get_ticks_per_sec(),
+ PM_TIMER_FREQUENCY);
+ timer_mod(ar->tmr.timer, expire_time);
+ } else {
+ timer_del(ar->tmr.timer);
+ }
+}
+
+void acpi_pm_tmr_calc_overflow_time(ACPIREGS *ar)
+{
+ int64_t d = acpi_pm_tmr_get_clock();
+ ar->tmr.overflow_time = (d + 0x800000LL) & ~0x7fffffLL;
+}
+
+static uint32_t acpi_pm_tmr_get(ACPIREGS *ar)
+{
+ uint32_t d = acpi_pm_tmr_get_clock();
+ return d & 0xffffff;
+}
+
+static void acpi_pm_tmr_timer(void *opaque)
+{
+ ACPIREGS *ar = opaque;
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_PMTIMER);
+ ar->tmr.update_sci(ar);
+}
+
+static uint64_t acpi_pm_tmr_read(void *opaque, hwaddr addr, unsigned width)
+{
+ return acpi_pm_tmr_get(opaque);
+}
+
+static void acpi_pm_tmr_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ /* nothing */
+}
+
+static const MemoryRegionOps acpi_pm_tmr_ops = {
+ .read = acpi_pm_tmr_read,
+ .write = acpi_pm_tmr_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void acpi_pm_tmr_init(ACPIREGS *ar, acpi_update_sci_fn update_sci,
+ MemoryRegion *parent)
+{
+ ar->tmr.update_sci = update_sci;
+ ar->tmr.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, acpi_pm_tmr_timer, ar);
+ memory_region_init_io(&ar->tmr.io, memory_region_owner(parent),
+ &acpi_pm_tmr_ops, ar, "acpi-tmr", 4);
+ memory_region_clear_global_locking(&ar->tmr.io);
+ memory_region_add_subregion(parent, 8, &ar->tmr.io);
+}
+
+void acpi_pm_tmr_reset(ACPIREGS *ar)
+{
+ ar->tmr.overflow_time = 0;
+ timer_del(ar->tmr.timer);
+}
+
+/* ACPI PM1aCNT */
+static void acpi_pm1_cnt_write(ACPIREGS *ar, uint16_t val)
+{
+ ar->pm1.cnt.cnt = val & ~(ACPI_BITMASK_SLEEP_ENABLE);
+
+ if (val & ACPI_BITMASK_SLEEP_ENABLE) {
+ /* change suspend type */
+ uint16_t sus_typ = (val >> 10) & 7;
+ switch(sus_typ) {
+ case 0: /* soft power off */
+ qemu_system_shutdown_request();
+ break;
+ case 1:
+ qemu_system_suspend_request();
+ break;
+ default:
+ if (sus_typ == ar->pm1.cnt.s4_val) { /* S4 request */
+ qapi_event_send_suspend_disk(&error_abort);
+ qemu_system_shutdown_request();
+ }
+ break;
+ }
+ }
+}
+
+void acpi_pm1_cnt_update(ACPIREGS *ar,
+ bool sci_enable, bool sci_disable)
+{
+ /* ACPI specs 3.0, 4.7.2.5 */
+ if (sci_enable) {
+ ar->pm1.cnt.cnt |= ACPI_BITMASK_SCI_ENABLE;
+ } else if (sci_disable) {
+ ar->pm1.cnt.cnt &= ~ACPI_BITMASK_SCI_ENABLE;
+ }
+}
+
+static uint64_t acpi_pm_cnt_read(void *opaque, hwaddr addr, unsigned width)
+{
+ ACPIREGS *ar = opaque;
+ return ar->pm1.cnt.cnt;
+}
+
+static void acpi_pm_cnt_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ acpi_pm1_cnt_write(opaque, val);
+}
+
+static const MemoryRegionOps acpi_pm_cnt_ops = {
+ .read = acpi_pm_cnt_read,
+ .write = acpi_pm_cnt_write,
+ .valid.min_access_size = 2,
+ .valid.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void acpi_pm1_cnt_init(ACPIREGS *ar, MemoryRegion *parent,
+ bool disable_s3, bool disable_s4, uint8_t s4_val)
+{
+ FWCfgState *fw_cfg;
+
+ ar->pm1.cnt.s4_val = s4_val;
+ ar->wakeup.notify = acpi_notify_wakeup;
+ qemu_register_wakeup_notifier(&ar->wakeup);
+ memory_region_init_io(&ar->pm1.cnt.io, memory_region_owner(parent),
+ &acpi_pm_cnt_ops, ar, "acpi-cnt", 2);
+ memory_region_add_subregion(parent, 4, &ar->pm1.cnt.io);
+
+ fw_cfg = fw_cfg_find();
+ if (fw_cfg) {
+ uint8_t suspend[6] = {128, 0, 0, 129, 128, 128};
+ suspend[3] = 1 | ((!disable_s3) << 7);
+ suspend[4] = s4_val | ((!disable_s4) << 7);
+
+ fw_cfg_add_file(fw_cfg, "etc/system-states", g_memdup(suspend, 6), 6);
+ }
+}
+
+void acpi_pm1_cnt_reset(ACPIREGS *ar)
+{
+ ar->pm1.cnt.cnt = 0;
+}
+
+/* ACPI GPE */
+void acpi_gpe_init(ACPIREGS *ar, uint8_t len)
+{
+ ar->gpe.len = len;
+ /* Only first len / 2 bytes are ever used,
+ * but the caller in ich9.c migrates full len bytes.
+ * TODO: fix ich9.c and drop the extra allocation.
+ */
+ ar->gpe.sts = g_malloc0(len);
+ ar->gpe.en = g_malloc0(len);
+}
+
+void acpi_gpe_reset(ACPIREGS *ar)
+{
+ memset(ar->gpe.sts, 0, ar->gpe.len / 2);
+ memset(ar->gpe.en, 0, ar->gpe.len / 2);
+}
+
+static uint8_t *acpi_gpe_ioport_get_ptr(ACPIREGS *ar, uint32_t addr)
+{
+ uint8_t *cur = NULL;
+
+ if (addr < ar->gpe.len / 2) {
+ cur = ar->gpe.sts + addr;
+ } else if (addr < ar->gpe.len) {
+ cur = ar->gpe.en + addr - ar->gpe.len / 2;
+ } else {
+ abort();
+ }
+
+ return cur;
+}
+
+void acpi_gpe_ioport_writeb(ACPIREGS *ar, uint32_t addr, uint32_t val)
+{
+ uint8_t *cur;
+
+ cur = acpi_gpe_ioport_get_ptr(ar, addr);
+ if (addr < ar->gpe.len / 2) {
+ /* GPE_STS */
+ *cur = (*cur) & ~val;
+ } else if (addr < ar->gpe.len) {
+ /* GPE_EN */
+ *cur = val;
+ } else {
+ abort();
+ }
+}
+
+uint32_t acpi_gpe_ioport_readb(ACPIREGS *ar, uint32_t addr)
+{
+ uint8_t *cur;
+ uint32_t val;
+
+ cur = acpi_gpe_ioport_get_ptr(ar, addr);
+ val = 0;
+ if (cur != NULL) {
+ val = *cur;
+ }
+
+ return val;
+}
+
+void acpi_send_gpe_event(ACPIREGS *ar, qemu_irq irq,
+ AcpiGPEStatusBits status)
+{
+ ar->gpe.sts[0] |= status;
+ acpi_update_sci(ar, irq);
+}
+
+void acpi_update_sci(ACPIREGS *regs, qemu_irq irq)
+{
+ int sci_level, pm1a_sts;
+
+ pm1a_sts = acpi_pm1_evt_get_sts(regs);
+
+ sci_level = ((pm1a_sts &
+ regs->pm1.evt.en & ACPI_BITMASK_PM1_COMMON_ENABLED) != 0) ||
+ ((regs->gpe.sts[0] & regs->gpe.en[0]) != 0);
+
+ qemu_set_irq(irq, sci_level);
+
+ /* schedule a timer interruption if needed */
+ acpi_pm_tmr_update(regs,
+ (regs->pm1.evt.en & ACPI_BITMASK_TIMER_ENABLE) &&
+ !(pm1a_sts & ACPI_BITMASK_TIMER_STATUS));
+}
diff --git a/hw/acpi/cpu_hotplug.c b/hw/acpi/cpu_hotplug.c
new file mode 100644
index 00000000..f5b9972f
--- /dev/null
+++ b/hw/acpi/cpu_hotplug.c
@@ -0,0 +1,76 @@
+/*
+ * QEMU ACPI hotplug utilities
+ *
+ * Copyright (C) 2013 Red Hat Inc
+ *
+ * Authors:
+ * Igor Mammedov <imammedo@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/hw.h"
+#include "hw/acpi/cpu_hotplug.h"
+
+static uint64_t cpu_status_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ AcpiCpuHotplug *cpus = opaque;
+ uint64_t val = cpus->sts[addr];
+
+ return val;
+}
+
+static void cpu_status_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ /* TODO: implement VCPU removal on guest signal that CPU can be removed */
+}
+
+static const MemoryRegionOps AcpiCpuHotplug_ops = {
+ .read = cpu_status_read,
+ .write = cpu_status_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void acpi_set_cpu_present_bit(AcpiCpuHotplug *g, CPUState *cpu,
+ Error **errp)
+{
+ CPUClass *k = CPU_GET_CLASS(cpu);
+ int64_t cpu_id;
+
+ cpu_id = k->get_arch_id(cpu);
+ if ((cpu_id / 8) >= ACPI_GPE_PROC_LEN) {
+ error_setg(errp, "acpi: invalid cpu id: %" PRIi64, cpu_id);
+ return;
+ }
+
+ g->sts[cpu_id / 8] |= (1 << (cpu_id % 8));
+}
+
+void acpi_cpu_plug_cb(ACPIREGS *ar, qemu_irq irq,
+ AcpiCpuHotplug *g, DeviceState *dev, Error **errp)
+{
+ acpi_set_cpu_present_bit(g, CPU(dev), errp);
+ if (*errp != NULL) {
+ return;
+ }
+
+ acpi_send_gpe_event(ar, irq, ACPI_CPU_HOTPLUG_STATUS);
+}
+
+void acpi_cpu_hotplug_init(MemoryRegion *parent, Object *owner,
+ AcpiCpuHotplug *gpe_cpu, uint16_t base)
+{
+ CPUState *cpu;
+
+ CPU_FOREACH(cpu) {
+ acpi_set_cpu_present_bit(gpe_cpu, cpu, &error_abort);
+ }
+ memory_region_init_io(&gpe_cpu->io, owner, &AcpiCpuHotplug_ops,
+ gpe_cpu, "acpi-cpu-hotplug", ACPI_GPE_PROC_LEN);
+ memory_region_add_subregion(parent, base, &gpe_cpu->io);
+}
diff --git a/hw/acpi/ich9.c b/hw/acpi/ich9.c
new file mode 100644
index 00000000..1c7fcfa9
--- /dev/null
+++ b/hw/acpi/ich9.c
@@ -0,0 +1,484 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This is based on acpi.c.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/hw.h"
+#include "qapi/visitor.h"
+#include "hw/i386/pc.h"
+#include "hw/pci/pci.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/tco.h"
+#include "sysemu/kvm.h"
+#include "exec/address-spaces.h"
+
+#include "hw/i386/ich9.h"
+#include "hw/mem/pc-dimm.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+#define ICH9_DEBUG(fmt, ...) \
+do { printf("%s "fmt, __func__, ## __VA_ARGS__); } while (0)
+#else
+#define ICH9_DEBUG(fmt, ...) do { } while (0)
+#endif
+
+static void ich9_pm_update_sci_fn(ACPIREGS *regs)
+{
+ ICH9LPCPMRegs *pm = container_of(regs, ICH9LPCPMRegs, acpi_regs);
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
+}
+
+static uint64_t ich9_gpe_readb(void *opaque, hwaddr addr, unsigned width)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ return acpi_gpe_ioport_readb(&pm->acpi_regs, addr);
+}
+
+static void ich9_gpe_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ acpi_gpe_ioport_writeb(&pm->acpi_regs, addr, val);
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
+}
+
+static const MemoryRegionOps ich9_gpe_ops = {
+ .read = ich9_gpe_readb,
+ .write = ich9_gpe_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t ich9_smi_readl(void *opaque, hwaddr addr, unsigned width)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ switch (addr) {
+ case 0:
+ return pm->smi_en;
+ case 4:
+ return pm->smi_sts;
+ default:
+ return 0;
+ }
+}
+
+static void ich9_smi_writel(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ TCOIORegs *tr = &pm->tco_regs;
+ uint64_t tco_en;
+
+ switch (addr) {
+ case 0:
+ tco_en = pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN;
+ /* once TCO_LOCK bit is set, TCO_EN bit cannot be overwritten */
+ if (tr->tco.cnt1 & TCO_LOCK) {
+ val = (val & ~ICH9_PMIO_SMI_EN_TCO_EN) | tco_en;
+ }
+ pm->smi_en &= ~pm->smi_en_wmask;
+ pm->smi_en |= (val & pm->smi_en_wmask);
+ break;
+ }
+}
+
+static const MemoryRegionOps ich9_smi_ops = {
+ .read = ich9_smi_readl,
+ .write = ich9_smi_writel,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void ich9_pm_iospace_update(ICH9LPCPMRegs *pm, uint32_t pm_io_base)
+{
+ ICH9_DEBUG("to 0x%x\n", pm_io_base);
+
+ assert((pm_io_base & ICH9_PMIO_MASK) == 0);
+
+ pm->pm_io_base = pm_io_base;
+ memory_region_transaction_begin();
+ memory_region_set_enabled(&pm->io, pm->pm_io_base != 0);
+ memory_region_set_address(&pm->io, pm->pm_io_base);
+ memory_region_transaction_commit();
+}
+
+static int ich9_pm_post_load(void *opaque, int version_id)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ uint32_t pm_io_base = pm->pm_io_base;
+ pm->pm_io_base = 0;
+ ich9_pm_iospace_update(pm, pm_io_base);
+ return 0;
+}
+
+#define VMSTATE_GPE_ARRAY(_field, _state) \
+ { \
+ .name = (stringify(_field)), \
+ .version_id = 0, \
+ .num = ICH9_PMIO_GPE0_LEN, \
+ .info = &vmstate_info_uint8, \
+ .size = sizeof(uint8_t), \
+ .flags = VMS_ARRAY | VMS_POINTER, \
+ .offset = vmstate_offset_pointer(_state, _field, uint8_t), \
+ }
+
+static bool vmstate_test_use_memhp(void *opaque)
+{
+ ICH9LPCPMRegs *s = opaque;
+ return s->acpi_memory_hotplug.is_enabled;
+}
+
+static const VMStateDescription vmstate_memhp_state = {
+ .name = "ich9_pm/memhp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .needed = vmstate_test_use_memhp,
+ .fields = (VMStateField[]) {
+ VMSTATE_MEMORY_HOTPLUG(acpi_memory_hotplug, ICH9LPCPMRegs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool vmstate_test_use_tco(void *opaque)
+{
+ ICH9LPCPMRegs *s = opaque;
+ return s->enable_tco;
+}
+
+static const VMStateDescription vmstate_tco_io_state = {
+ .name = "ich9_pm/tco",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .needed = vmstate_test_use_tco,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(tco_regs, ICH9LPCPMRegs, 1, vmstate_tco_io_sts,
+ TCOIORegs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_ich9_pm = {
+ .name = "ich9_pm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ich9_pm_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(acpi_regs.pm1.evt.sts, ICH9LPCPMRegs),
+ VMSTATE_UINT16(acpi_regs.pm1.evt.en, ICH9LPCPMRegs),
+ VMSTATE_UINT16(acpi_regs.pm1.cnt.cnt, ICH9LPCPMRegs),
+ VMSTATE_TIMER_PTR(acpi_regs.tmr.timer, ICH9LPCPMRegs),
+ VMSTATE_INT64(acpi_regs.tmr.overflow_time, ICH9LPCPMRegs),
+ VMSTATE_GPE_ARRAY(acpi_regs.gpe.sts, ICH9LPCPMRegs),
+ VMSTATE_GPE_ARRAY(acpi_regs.gpe.en, ICH9LPCPMRegs),
+ VMSTATE_UINT32(smi_en, ICH9LPCPMRegs),
+ VMSTATE_UINT32(smi_sts, ICH9LPCPMRegs),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_memhp_state,
+ &vmstate_tco_io_state,
+ NULL
+ }
+};
+
+static void pm_reset(void *opaque)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ ich9_pm_iospace_update(pm, 0);
+
+ acpi_pm1_evt_reset(&pm->acpi_regs);
+ acpi_pm1_cnt_reset(&pm->acpi_regs);
+ acpi_pm_tmr_reset(&pm->acpi_regs);
+ acpi_gpe_reset(&pm->acpi_regs);
+
+ pm->smi_en = 0;
+ if (!pm->smm_enabled) {
+ /* Mark SMM as already inited to prevent SMM from running. */
+ pm->smi_en |= ICH9_PMIO_SMI_EN_APMC_EN;
+ }
+ pm->smi_en_wmask = ~0;
+
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
+}
+
+static void pm_powerdown_req(Notifier *n, void *opaque)
+{
+ ICH9LPCPMRegs *pm = container_of(n, ICH9LPCPMRegs, powerdown_notifier);
+
+ acpi_pm1_evt_power_down(&pm->acpi_regs);
+}
+
+void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm,
+ bool smm_enabled, bool enable_tco,
+ qemu_irq sci_irq)
+{
+ memory_region_init(&pm->io, OBJECT(lpc_pci), "ich9-pm", ICH9_PMIO_SIZE);
+ memory_region_set_enabled(&pm->io, false);
+ memory_region_add_subregion(pci_address_space_io(lpc_pci),
+ 0, &pm->io);
+
+ acpi_pm_tmr_init(&pm->acpi_regs, ich9_pm_update_sci_fn, &pm->io);
+ acpi_pm1_evt_init(&pm->acpi_regs, ich9_pm_update_sci_fn, &pm->io);
+ acpi_pm1_cnt_init(&pm->acpi_regs, &pm->io, pm->disable_s3, pm->disable_s4,
+ pm->s4_val);
+
+ acpi_gpe_init(&pm->acpi_regs, ICH9_PMIO_GPE0_LEN);
+ memory_region_init_io(&pm->io_gpe, OBJECT(lpc_pci), &ich9_gpe_ops, pm,
+ "acpi-gpe0", ICH9_PMIO_GPE0_LEN);
+ memory_region_add_subregion(&pm->io, ICH9_PMIO_GPE0_STS, &pm->io_gpe);
+
+ memory_region_init_io(&pm->io_smi, OBJECT(lpc_pci), &ich9_smi_ops, pm,
+ "acpi-smi", 8);
+ memory_region_add_subregion(&pm->io, ICH9_PMIO_SMI_EN, &pm->io_smi);
+
+ pm->smm_enabled = smm_enabled;
+
+ pm->enable_tco = enable_tco;
+ if (pm->enable_tco) {
+ acpi_pm_tco_init(&pm->tco_regs, &pm->io);
+ }
+
+ pm->irq = sci_irq;
+ qemu_register_reset(pm_reset, pm);
+ pm->powerdown_notifier.notify = pm_powerdown_req;
+ qemu_register_powerdown_notifier(&pm->powerdown_notifier);
+
+ acpi_cpu_hotplug_init(pci_address_space_io(lpc_pci), OBJECT(lpc_pci),
+ &pm->gpe_cpu, ICH9_CPU_HOTPLUG_IO_BASE);
+
+ if (pm->acpi_memory_hotplug.is_enabled) {
+ acpi_memory_hotplug_init(pci_address_space_io(lpc_pci), OBJECT(lpc_pci),
+ &pm->acpi_memory_hotplug);
+ }
+}
+
+static void ich9_pm_get_gpe0_blk(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ uint32_t value = pm->pm_io_base + ICH9_PMIO_GPE0_STS;
+
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static bool ich9_pm_get_memory_hotplug_support(Object *obj, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ return s->pm.acpi_memory_hotplug.is_enabled;
+}
+
+static void ich9_pm_set_memory_hotplug_support(Object *obj, bool value,
+ Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ s->pm.acpi_memory_hotplug.is_enabled = value;
+}
+
+static void ich9_pm_get_disable_s3(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ uint8_t value = pm->disable_s3;
+
+ visit_type_uint8(v, &value, name, errp);
+}
+
+static void ich9_pm_set_disable_s3(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ Error *local_err = NULL;
+ uint8_t value;
+
+ visit_type_uint8(v, &value, name, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ pm->disable_s3 = value;
+out:
+ error_propagate(errp, local_err);
+}
+
+static void ich9_pm_get_disable_s4(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ uint8_t value = pm->disable_s4;
+
+ visit_type_uint8(v, &value, name, errp);
+}
+
+static void ich9_pm_set_disable_s4(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ Error *local_err = NULL;
+ uint8_t value;
+
+ visit_type_uint8(v, &value, name, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ pm->disable_s4 = value;
+out:
+ error_propagate(errp, local_err);
+}
+
+static void ich9_pm_get_s4_val(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ uint8_t value = pm->s4_val;
+
+ visit_type_uint8(v, &value, name, errp);
+}
+
+static void ich9_pm_set_s4_val(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ Error *local_err = NULL;
+ uint8_t value;
+
+ visit_type_uint8(v, &value, name, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ pm->s4_val = value;
+out:
+ error_propagate(errp, local_err);
+}
+
+static bool ich9_pm_get_enable_tco(Object *obj, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+ return s->pm.enable_tco;
+}
+
+static void ich9_pm_set_enable_tco(Object *obj, bool value, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+ s->pm.enable_tco = value;
+}
+
+void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm, Error **errp)
+{
+ static const uint32_t gpe0_len = ICH9_PMIO_GPE0_LEN;
+ pm->acpi_memory_hotplug.is_enabled = true;
+ pm->disable_s3 = 0;
+ pm->disable_s4 = 0;
+ pm->s4_val = 2;
+
+ object_property_add_uint32_ptr(obj, ACPI_PM_PROP_PM_IO_BASE,
+ &pm->pm_io_base, errp);
+ object_property_add(obj, ACPI_PM_PROP_GPE0_BLK, "uint32",
+ ich9_pm_get_gpe0_blk,
+ NULL, NULL, pm, NULL);
+ object_property_add_uint32_ptr(obj, ACPI_PM_PROP_GPE0_BLK_LEN,
+ &gpe0_len, errp);
+ object_property_add_bool(obj, "memory-hotplug-support",
+ ich9_pm_get_memory_hotplug_support,
+ ich9_pm_set_memory_hotplug_support,
+ NULL);
+ object_property_add(obj, ACPI_PM_PROP_S3_DISABLED, "uint8",
+ ich9_pm_get_disable_s3,
+ ich9_pm_set_disable_s3,
+ NULL, pm, NULL);
+ object_property_add(obj, ACPI_PM_PROP_S4_DISABLED, "uint8",
+ ich9_pm_get_disable_s4,
+ ich9_pm_set_disable_s4,
+ NULL, pm, NULL);
+ object_property_add(obj, ACPI_PM_PROP_S4_VAL, "uint8",
+ ich9_pm_get_s4_val,
+ ich9_pm_set_s4_val,
+ NULL, pm, NULL);
+ object_property_add_bool(obj, ACPI_PM_PROP_TCO_ENABLED,
+ ich9_pm_get_enable_tco,
+ ich9_pm_set_enable_tco,
+ NULL);
+}
+
+void ich9_pm_device_plug_cb(ICH9LPCPMRegs *pm, DeviceState *dev, Error **errp)
+{
+ if (pm->acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_plug_cb(&pm->acpi_regs, pm->irq, &pm->acpi_memory_hotplug,
+ dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
+ acpi_cpu_plug_cb(&pm->acpi_regs, pm->irq, &pm->gpe_cpu, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device plug request for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+void ich9_pm_device_unplug_request_cb(ICH9LPCPMRegs *pm, DeviceState *dev,
+ Error **errp)
+{
+ if (pm->acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_request_cb(&pm->acpi_regs, pm->irq,
+ &pm->acpi_memory_hotplug, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug request for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+void ich9_pm_device_unplug_cb(ICH9LPCPMRegs *pm, DeviceState *dev,
+ Error **errp)
+{
+ if (pm->acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_cb(&pm->acpi_memory_hotplug, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+void ich9_pm_ospm_status(AcpiDeviceIf *adev, ACPIOSTInfoList ***list)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(adev);
+
+ acpi_memory_ospm_status(&s->pm.acpi_memory_hotplug, list);
+}
diff --git a/hw/acpi/memory_hotplug.c b/hw/acpi/memory_hotplug.c
new file mode 100644
index 00000000..2ff0d5ce
--- /dev/null
+++ b/hw/acpi/memory_hotplug.c
@@ -0,0 +1,304 @@
+#include "hw/acpi/memory_hotplug.h"
+#include "hw/acpi/pc-hotplug.h"
+#include "hw/mem/pc-dimm.h"
+#include "hw/boards.h"
+#include "hw/qdev-core.h"
+#include "trace.h"
+#include "qapi-event.h"
+
+static ACPIOSTInfo *acpi_memory_device_status(int slot, MemStatus *mdev)
+{
+ ACPIOSTInfo *info = g_new0(ACPIOSTInfo, 1);
+
+ info->slot_type = ACPI_SLOT_TYPE_DIMM;
+ info->slot = g_strdup_printf("%d", slot);
+ info->source = mdev->ost_event;
+ info->status = mdev->ost_status;
+ if (mdev->dimm) {
+ DeviceState *dev = DEVICE(mdev->dimm);
+ if (dev->id) {
+ info->device = g_strdup(dev->id);
+ info->has_device = true;
+ }
+ }
+ return info;
+}
+
+void acpi_memory_ospm_status(MemHotplugState *mem_st, ACPIOSTInfoList ***list)
+{
+ int i;
+
+ for (i = 0; i < mem_st->dev_count; i++) {
+ ACPIOSTInfoList *elem = g_new0(ACPIOSTInfoList, 1);
+ elem->value = acpi_memory_device_status(i, &mem_st->devs[i]);
+ elem->next = NULL;
+ **list = elem;
+ *list = &elem->next;
+ }
+}
+
+static uint64_t acpi_memory_hotplug_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ uint32_t val = 0;
+ MemHotplugState *mem_st = opaque;
+ MemStatus *mdev;
+ Object *o;
+
+ if (mem_st->selector >= mem_st->dev_count) {
+ trace_mhp_acpi_invalid_slot_selected(mem_st->selector);
+ return 0;
+ }
+
+ mdev = &mem_st->devs[mem_st->selector];
+ o = OBJECT(mdev->dimm);
+ switch (addr) {
+ case 0x0: /* Lo part of phys address where DIMM is mapped */
+ val = o ? object_property_get_int(o, PC_DIMM_ADDR_PROP, NULL) : 0;
+ trace_mhp_acpi_read_addr_lo(mem_st->selector, val);
+ break;
+ case 0x4: /* Hi part of phys address where DIMM is mapped */
+ val = o ? object_property_get_int(o, PC_DIMM_ADDR_PROP, NULL) >> 32 : 0;
+ trace_mhp_acpi_read_addr_hi(mem_st->selector, val);
+ break;
+ case 0x8: /* Lo part of DIMM size */
+ val = o ? object_property_get_int(o, PC_DIMM_SIZE_PROP, NULL) : 0;
+ trace_mhp_acpi_read_size_lo(mem_st->selector, val);
+ break;
+ case 0xc: /* Hi part of DIMM size */
+ val = o ? object_property_get_int(o, PC_DIMM_SIZE_PROP, NULL) >> 32 : 0;
+ trace_mhp_acpi_read_size_hi(mem_st->selector, val);
+ break;
+ case 0x10: /* node proximity for _PXM method */
+ val = o ? object_property_get_int(o, PC_DIMM_NODE_PROP, NULL) : 0;
+ trace_mhp_acpi_read_pxm(mem_st->selector, val);
+ break;
+ case 0x14: /* pack and return is_* fields */
+ val |= mdev->is_enabled ? 1 : 0;
+ val |= mdev->is_inserting ? 2 : 0;
+ val |= mdev->is_removing ? 4 : 0;
+ trace_mhp_acpi_read_flags(mem_st->selector, val);
+ break;
+ default:
+ val = ~0;
+ break;
+ }
+ return val;
+}
+
+static void acpi_memory_hotplug_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ MemHotplugState *mem_st = opaque;
+ MemStatus *mdev;
+ ACPIOSTInfo *info;
+ DeviceState *dev = NULL;
+ HotplugHandler *hotplug_ctrl = NULL;
+ Error *local_err = NULL;
+
+ if (!mem_st->dev_count) {
+ return;
+ }
+
+ if (addr) {
+ if (mem_st->selector >= mem_st->dev_count) {
+ trace_mhp_acpi_invalid_slot_selected(mem_st->selector);
+ return;
+ }
+ }
+
+ switch (addr) {
+ case 0x0: /* DIMM slot selector */
+ mem_st->selector = data;
+ trace_mhp_acpi_write_slot(mem_st->selector);
+ break;
+ case 0x4: /* _OST event */
+ mdev = &mem_st->devs[mem_st->selector];
+ if (data == 1) {
+ /* TODO: handle device insert OST event */
+ } else if (data == 3) {
+ /* TODO: handle device remove OST event */
+ }
+ mdev->ost_event = data;
+ trace_mhp_acpi_write_ost_ev(mem_st->selector, mdev->ost_event);
+ break;
+ case 0x8: /* _OST status */
+ mdev = &mem_st->devs[mem_st->selector];
+ mdev->ost_status = data;
+ trace_mhp_acpi_write_ost_status(mem_st->selector, mdev->ost_status);
+ /* TODO: implement memory removal on guest signal */
+
+ info = acpi_memory_device_status(mem_st->selector, mdev);
+ qapi_event_send_acpi_device_ost(info, &error_abort);
+ qapi_free_ACPIOSTInfo(info);
+ break;
+ case 0x14: /* set is_* fields */
+ mdev = &mem_st->devs[mem_st->selector];
+ if (data & 2) { /* clear insert event */
+ mdev->is_inserting = false;
+ trace_mhp_acpi_clear_insert_evt(mem_st->selector);
+ } else if (data & 4) {
+ mdev->is_removing = false;
+ trace_mhp_acpi_clear_remove_evt(mem_st->selector);
+ } else if (data & 8) {
+ if (!mdev->is_enabled) {
+ trace_mhp_acpi_ejecting_invalid_slot(mem_st->selector);
+ break;
+ }
+
+ dev = DEVICE(mdev->dimm);
+ hotplug_ctrl = qdev_get_hotplug_handler(dev);
+ /* call pc-dimm unplug cb */
+ hotplug_handler_unplug(hotplug_ctrl, dev, &local_err);
+ if (local_err) {
+ trace_mhp_acpi_pc_dimm_delete_failed(mem_st->selector);
+ qapi_event_send_mem_unplug_error(dev->id,
+ error_get_pretty(local_err),
+ &error_abort);
+ break;
+ }
+ trace_mhp_acpi_pc_dimm_deleted(mem_st->selector);
+ }
+ break;
+ default:
+ break;
+ }
+
+}
+static const MemoryRegionOps acpi_memory_hotplug_ops = {
+ .read = acpi_memory_hotplug_read,
+ .write = acpi_memory_hotplug_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+void acpi_memory_hotplug_init(MemoryRegion *as, Object *owner,
+ MemHotplugState *state)
+{
+ MachineState *machine = MACHINE(qdev_get_machine());
+
+ state->dev_count = machine->ram_slots;
+ if (!state->dev_count) {
+ return;
+ }
+
+ state->devs = g_malloc0(sizeof(*state->devs) * state->dev_count);
+ memory_region_init_io(&state->io, owner, &acpi_memory_hotplug_ops, state,
+ "acpi-mem-hotplug", ACPI_MEMORY_HOTPLUG_IO_LEN);
+ memory_region_add_subregion(as, ACPI_MEMORY_HOTPLUG_BASE, &state->io);
+}
+
+/**
+ * acpi_memory_slot_status:
+ * @mem_st: memory hotplug state
+ * @dev: device
+ * @errp: set in case of an error
+ *
+ * Obtain a single memory slot status.
+ *
+ * This function will be called by memory unplug request cb and unplug cb.
+ */
+static MemStatus *
+acpi_memory_slot_status(MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ Error *local_err = NULL;
+ int slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP,
+ &local_err);
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ if (slot >= mem_st->dev_count) {
+ char *dev_path = object_get_canonical_path(OBJECT(dev));
+ error_setg(errp, "acpi_memory_slot_status: "
+ "device [%s] returned invalid memory slot[%d]",
+ dev_path, slot);
+ g_free(dev_path);
+ return NULL;
+ }
+
+ return &mem_st->devs[slot];
+}
+
+void acpi_memory_plug_cb(ACPIREGS *ar, qemu_irq irq, MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ MemStatus *mdev;
+
+ mdev = acpi_memory_slot_status(mem_st, dev, errp);
+ if (!mdev) {
+ return;
+ }
+
+ mdev->dimm = dev;
+ mdev->is_enabled = true;
+ mdev->is_inserting = true;
+
+ /* do ACPI magic */
+ acpi_send_gpe_event(ar, irq, ACPI_MEMORY_HOTPLUG_STATUS);
+ return;
+}
+
+void acpi_memory_unplug_request_cb(ACPIREGS *ar, qemu_irq irq,
+ MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ MemStatus *mdev;
+
+ mdev = acpi_memory_slot_status(mem_st, dev, errp);
+ if (!mdev) {
+ return;
+ }
+
+ mdev->is_removing = true;
+
+ /* Do ACPI magic */
+ acpi_send_gpe_event(ar, irq, ACPI_MEMORY_HOTPLUG_STATUS);
+}
+
+void acpi_memory_unplug_cb(MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ MemStatus *mdev;
+
+ mdev = acpi_memory_slot_status(mem_st, dev, errp);
+ if (!mdev) {
+ return;
+ }
+
+ mdev->is_enabled = false;
+ mdev->dimm = NULL;
+}
+
+static const VMStateDescription vmstate_memhp_sts = {
+ .name = "memory hotplug device state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(is_enabled, MemStatus),
+ VMSTATE_BOOL(is_inserting, MemStatus),
+ VMSTATE_UINT32(ost_event, MemStatus),
+ VMSTATE_UINT32(ost_status, MemStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_memory_hotplug = {
+ .name = "memory hotplug state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(selector, MemHotplugState),
+ VMSTATE_STRUCT_VARRAY_POINTER_UINT32(devs, MemHotplugState, dev_count,
+ vmstate_memhp_sts, MemStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
diff --git a/hw/acpi/pcihp.c b/hw/acpi/pcihp.c
new file mode 100644
index 00000000..fbbc4dde
--- /dev/null
+++ b/hw/acpi/pcihp.c
@@ -0,0 +1,334 @@
+/*
+ * QEMU<->ACPI BIOS PCI hotplug interface
+ *
+ * QEMU supports PCI hotplug via ACPI. This module
+ * implements the interface between QEMU and the ACPI BIOS.
+ * Interface specification - see docs/specs/acpi_pci_hotplug.txt
+ *
+ * Copyright (c) 2013, Red Hat Inc, Michael S. Tsirkin (mst@redhat.com)
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/acpi/pcihp.h"
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/pci/pci.h"
+#include "hw/acpi/acpi.h"
+#include "sysemu/sysemu.h"
+#include "exec/ioport.h"
+#include "exec/address-spaces.h"
+#include "hw/pci/pci_bus.h"
+#include "qom/qom-qobject.h"
+#include "qapi/qmp/qint.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+# define ACPI_PCIHP_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define ACPI_PCIHP_DPRINTF(format, ...) do { } while (0)
+#endif
+
+#define ACPI_PCIHP_ADDR 0xae00
+#define ACPI_PCIHP_SIZE 0x0014
+#define ACPI_PCIHP_LEGACY_SIZE 0x000f
+#define PCI_UP_BASE 0x0000
+#define PCI_DOWN_BASE 0x0004
+#define PCI_EJ_BASE 0x0008
+#define PCI_RMV_BASE 0x000c
+#define PCI_SEL_BASE 0x0010
+
+typedef struct AcpiPciHpFind {
+ int bsel;
+ PCIBus *bus;
+} AcpiPciHpFind;
+
+static int acpi_pcihp_get_bsel(PCIBus *bus)
+{
+ Error *local_err = NULL;
+ int64_t bsel = object_property_get_int(OBJECT(bus), ACPI_PCIHP_PROP_BSEL,
+ &local_err);
+
+ if (local_err || bsel < 0 || bsel >= ACPI_PCIHP_MAX_HOTPLUG_BUS) {
+ if (local_err) {
+ error_free(local_err);
+ }
+ return -1;
+ } else {
+ return bsel;
+ }
+}
+
+static void acpi_pcihp_test_hotplug_bus(PCIBus *bus, void *opaque)
+{
+ AcpiPciHpFind *find = opaque;
+ if (find->bsel == acpi_pcihp_get_bsel(bus)) {
+ find->bus = bus;
+ }
+}
+
+static PCIBus *acpi_pcihp_find_hotplug_bus(AcpiPciHpState *s, int bsel)
+{
+ AcpiPciHpFind find = { .bsel = bsel, .bus = NULL };
+
+ if (bsel < 0) {
+ return NULL;
+ }
+
+ pci_for_each_bus(s->root, acpi_pcihp_test_hotplug_bus, &find);
+
+ /* Make bsel 0 eject root bus if bsel property is not set,
+ * for compatibility with non acpi setups.
+ * TODO: really needed?
+ */
+ if (!bsel && !find.bus) {
+ find.bus = s->root;
+ }
+ return find.bus;
+}
+
+static bool acpi_pcihp_pc_no_hotplug(AcpiPciHpState *s, PCIDevice *dev)
+{
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+ /*
+ * ACPI doesn't allow hotplug of bridge devices. Don't allow
+ * hot-unplug of bridge devices unless they were added by hotplug
+ * (and so, not described by acpi).
+ */
+ return (pc->is_bridge && !dev->qdev.hotplugged) || !dc->hotpluggable;
+}
+
+static void acpi_pcihp_eject_slot(AcpiPciHpState *s, unsigned bsel, unsigned slots)
+{
+ BusChild *kid, *next;
+ int slot = ctz32(slots);
+ PCIBus *bus = acpi_pcihp_find_hotplug_bus(s, bsel);
+
+ if (!bus) {
+ return;
+ }
+
+ /* Mark request as complete */
+ s->acpi_pcihp_pci_status[bsel].down &= ~(1U << slot);
+ s->acpi_pcihp_pci_status[bsel].up &= ~(1U << slot);
+
+ QTAILQ_FOREACH_SAFE(kid, &bus->qbus.children, sibling, next) {
+ DeviceState *qdev = kid->child;
+ PCIDevice *dev = PCI_DEVICE(qdev);
+ if (PCI_SLOT(dev->devfn) == slot) {
+ if (!acpi_pcihp_pc_no_hotplug(s, dev)) {
+ object_unparent(OBJECT(qdev));
+ }
+ }
+ }
+}
+
+static void acpi_pcihp_update_hotplug_bus(AcpiPciHpState *s, int bsel)
+{
+ BusChild *kid, *next;
+ PCIBus *bus = acpi_pcihp_find_hotplug_bus(s, bsel);
+
+ /* Execute any pending removes during reset */
+ while (s->acpi_pcihp_pci_status[bsel].down) {
+ acpi_pcihp_eject_slot(s, bsel, s->acpi_pcihp_pci_status[bsel].down);
+ }
+
+ s->acpi_pcihp_pci_status[bsel].hotplug_enable = ~0;
+
+ if (!bus) {
+ return;
+ }
+ QTAILQ_FOREACH_SAFE(kid, &bus->qbus.children, sibling, next) {
+ DeviceState *qdev = kid->child;
+ PCIDevice *pdev = PCI_DEVICE(qdev);
+ int slot = PCI_SLOT(pdev->devfn);
+
+ if (acpi_pcihp_pc_no_hotplug(s, pdev)) {
+ s->acpi_pcihp_pci_status[bsel].hotplug_enable &= ~(1U << slot);
+ }
+ }
+}
+
+static void acpi_pcihp_update(AcpiPciHpState *s)
+{
+ int i;
+
+ for (i = 0; i < ACPI_PCIHP_MAX_HOTPLUG_BUS; ++i) {
+ acpi_pcihp_update_hotplug_bus(s, i);
+ }
+}
+
+void acpi_pcihp_reset(AcpiPciHpState *s)
+{
+ acpi_pcihp_update(s);
+}
+
+void acpi_pcihp_device_plug_cb(ACPIREGS *ar, qemu_irq irq, AcpiPciHpState *s,
+ DeviceState *dev, Error **errp)
+{
+ PCIDevice *pdev = PCI_DEVICE(dev);
+ int slot = PCI_SLOT(pdev->devfn);
+ int bsel = acpi_pcihp_get_bsel(pdev->bus);
+ if (bsel < 0) {
+ error_setg(errp, "Unsupported bus. Bus doesn't have property '"
+ ACPI_PCIHP_PROP_BSEL "' set");
+ return;
+ }
+
+ /* Don't send event when device is enabled during qemu machine creation:
+ * it is present on boot, no hotplug event is necessary. We do send an
+ * event when the device is disabled later. */
+ if (!dev->hotplugged) {
+ return;
+ }
+
+ s->acpi_pcihp_pci_status[bsel].up |= (1U << slot);
+
+ acpi_send_gpe_event(ar, irq, ACPI_PCI_HOTPLUG_STATUS);
+}
+
+void acpi_pcihp_device_unplug_cb(ACPIREGS *ar, qemu_irq irq, AcpiPciHpState *s,
+ DeviceState *dev, Error **errp)
+{
+ PCIDevice *pdev = PCI_DEVICE(dev);
+ int slot = PCI_SLOT(pdev->devfn);
+ int bsel = acpi_pcihp_get_bsel(pdev->bus);
+ if (bsel < 0) {
+ error_setg(errp, "Unsupported bus. Bus doesn't have property '"
+ ACPI_PCIHP_PROP_BSEL "' set");
+ return;
+ }
+
+ s->acpi_pcihp_pci_status[bsel].down |= (1U << slot);
+
+ acpi_send_gpe_event(ar, irq, ACPI_PCI_HOTPLUG_STATUS);
+}
+
+static uint64_t pci_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ AcpiPciHpState *s = opaque;
+ uint32_t val = 0;
+ int bsel = s->hotplug_select;
+
+ if (bsel < 0 || bsel >= ACPI_PCIHP_MAX_HOTPLUG_BUS) {
+ return 0;
+ }
+
+ switch (addr) {
+ case PCI_UP_BASE:
+ val = s->acpi_pcihp_pci_status[bsel].up;
+ if (!s->legacy_piix) {
+ s->acpi_pcihp_pci_status[bsel].up = 0;
+ }
+ ACPI_PCIHP_DPRINTF("pci_up_read %" PRIu32 "\n", val);
+ break;
+ case PCI_DOWN_BASE:
+ val = s->acpi_pcihp_pci_status[bsel].down;
+ ACPI_PCIHP_DPRINTF("pci_down_read %" PRIu32 "\n", val);
+ break;
+ case PCI_EJ_BASE:
+ /* No feature defined yet */
+ ACPI_PCIHP_DPRINTF("pci_features_read %" PRIu32 "\n", val);
+ break;
+ case PCI_RMV_BASE:
+ val = s->acpi_pcihp_pci_status[bsel].hotplug_enable;
+ ACPI_PCIHP_DPRINTF("pci_rmv_read %" PRIu32 "\n", val);
+ break;
+ case PCI_SEL_BASE:
+ val = s->hotplug_select;
+ ACPI_PCIHP_DPRINTF("pci_sel_read %" PRIu32 "\n", val);
+ default:
+ break;
+ }
+
+ return val;
+}
+
+static void pci_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ AcpiPciHpState *s = opaque;
+ switch (addr) {
+ case PCI_EJ_BASE:
+ if (s->hotplug_select >= ACPI_PCIHP_MAX_HOTPLUG_BUS) {
+ break;
+ }
+ acpi_pcihp_eject_slot(s, s->hotplug_select, data);
+ ACPI_PCIHP_DPRINTF("pciej write %" HWADDR_PRIx " <== %" PRIu64 "\n",
+ addr, data);
+ break;
+ case PCI_SEL_BASE:
+ s->hotplug_select = data;
+ ACPI_PCIHP_DPRINTF("pcisel write %" HWADDR_PRIx " <== %" PRIu64 "\n",
+ addr, data);
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps acpi_pcihp_io_ops = {
+ .read = pci_read,
+ .write = pci_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+void acpi_pcihp_init(Object *owner, AcpiPciHpState *s, PCIBus *root_bus,
+ MemoryRegion *address_space_io, bool bridges_enabled)
+{
+ s->io_len = ACPI_PCIHP_SIZE;
+ s->io_base = ACPI_PCIHP_ADDR;
+
+ s->root= root_bus;
+ s->legacy_piix = !bridges_enabled;
+
+ if (s->legacy_piix) {
+ unsigned *bus_bsel = g_malloc(sizeof *bus_bsel);
+
+ s->io_len = ACPI_PCIHP_LEGACY_SIZE;
+
+ *bus_bsel = ACPI_PCIHP_BSEL_DEFAULT;
+ object_property_add_uint32_ptr(OBJECT(root_bus), ACPI_PCIHP_PROP_BSEL,
+ bus_bsel, NULL);
+ }
+
+ memory_region_init_io(&s->io, owner, &acpi_pcihp_io_ops, s,
+ "acpi-pci-hotplug", s->io_len);
+ memory_region_add_subregion(address_space_io, s->io_base, &s->io);
+
+ object_property_add_uint16_ptr(owner, ACPI_PCIHP_IO_BASE_PROP, &s->io_base,
+ &error_abort);
+ object_property_add_uint16_ptr(owner, ACPI_PCIHP_IO_LEN_PROP, &s->io_len,
+ &error_abort);
+}
+
+const VMStateDescription vmstate_acpi_pcihp_pci_status = {
+ .name = "acpi_pcihp_pci_status",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(up, AcpiPciHpPciStatus),
+ VMSTATE_UINT32(down, AcpiPciHpPciStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
diff --git a/hw/acpi/piix4.c b/hw/acpi/piix4.c
new file mode 100644
index 00000000..2cd2fee8
--- /dev/null
+++ b/hw/acpi/piix4.c
@@ -0,0 +1,643 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/apm.h"
+#include "hw/i2c/pm_smbus.h"
+#include "hw/pci/pci.h"
+#include "hw/acpi/acpi.h"
+#include "sysemu/sysemu.h"
+#include "qemu/range.h"
+#include "exec/ioport.h"
+#include "hw/nvram/fw_cfg.h"
+#include "exec/address-spaces.h"
+#include "hw/acpi/piix4.h"
+#include "hw/acpi/pcihp.h"
+#include "hw/acpi/cpu_hotplug.h"
+#include "hw/hotplug.h"
+#include "hw/mem/pc-dimm.h"
+#include "hw/acpi/memory_hotplug.h"
+#include "hw/acpi/acpi_dev_interface.h"
+#include "hw/xen/xen.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+# define PIIX4_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define PIIX4_DPRINTF(format, ...) do { } while (0)
+#endif
+
+#define GPE_BASE 0xafe0
+#define GPE_LEN 4
+
+struct pci_status {
+ uint32_t up; /* deprecated, maintained for migration compatibility */
+ uint32_t down;
+};
+
+typedef struct PIIX4PMState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion io;
+ uint32_t io_base;
+
+ MemoryRegion io_gpe;
+ ACPIREGS ar;
+
+ APMState apm;
+
+ PMSMBus smb;
+ uint32_t smb_io_base;
+
+ qemu_irq irq;
+ qemu_irq smi_irq;
+ int smm_enabled;
+ Notifier machine_ready;
+ Notifier powerdown_notifier;
+
+ AcpiPciHpState acpi_pci_hotplug;
+ bool use_acpi_pci_hotplug;
+
+ uint8_t disable_s3;
+ uint8_t disable_s4;
+ uint8_t s4_val;
+
+ AcpiCpuHotplug gpe_cpu;
+
+ MemHotplugState acpi_memory_hotplug;
+} PIIX4PMState;
+
+#define TYPE_PIIX4_PM "PIIX4_PM"
+
+#define PIIX4_PM(obj) \
+ OBJECT_CHECK(PIIX4PMState, (obj), TYPE_PIIX4_PM)
+
+static void piix4_acpi_system_hot_add_init(MemoryRegion *parent,
+ PCIBus *bus, PIIX4PMState *s);
+
+#define ACPI_ENABLE 0xf1
+#define ACPI_DISABLE 0xf0
+
+static void pm_tmr_timer(ACPIREGS *ar)
+{
+ PIIX4PMState *s = container_of(ar, PIIX4PMState, ar);
+ acpi_update_sci(&s->ar, s->irq);
+}
+
+static void apm_ctrl_changed(uint32_t val, void *arg)
+{
+ PIIX4PMState *s = arg;
+ PCIDevice *d = PCI_DEVICE(s);
+
+ /* ACPI specs 3.0, 4.7.2.5 */
+ acpi_pm1_cnt_update(&s->ar, val == ACPI_ENABLE, val == ACPI_DISABLE);
+ if (val == ACPI_ENABLE || val == ACPI_DISABLE) {
+ return;
+ }
+
+ if (d->config[0x5b] & (1 << 1)) {
+ if (s->smi_irq) {
+ qemu_irq_raise(s->smi_irq);
+ }
+ }
+}
+
+static void pm_io_space_update(PIIX4PMState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ s->io_base = le32_to_cpu(*(uint32_t *)(d->config + 0x40));
+ s->io_base &= 0xffc0;
+
+ memory_region_transaction_begin();
+ memory_region_set_enabled(&s->io, d->config[0x80] & 1);
+ memory_region_set_address(&s->io, s->io_base);
+ memory_region_transaction_commit();
+}
+
+static void smbus_io_space_update(PIIX4PMState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ s->smb_io_base = le32_to_cpu(*(uint32_t *)(d->config + 0x90));
+ s->smb_io_base &= 0xffc0;
+
+ memory_region_transaction_begin();
+ memory_region_set_enabled(&s->smb.io, d->config[0xd2] & 1);
+ memory_region_set_address(&s->smb.io, s->smb_io_base);
+ memory_region_transaction_commit();
+}
+
+static void pm_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ pci_default_write_config(d, address, val, len);
+ if (range_covers_byte(address, len, 0x80) ||
+ ranges_overlap(address, len, 0x40, 4)) {
+ pm_io_space_update((PIIX4PMState *)d);
+ }
+ if (range_covers_byte(address, len, 0xd2) ||
+ ranges_overlap(address, len, 0x90, 4)) {
+ smbus_io_space_update((PIIX4PMState *)d);
+ }
+}
+
+static int vmstate_acpi_post_load(void *opaque, int version_id)
+{
+ PIIX4PMState *s = opaque;
+
+ pm_io_space_update(s);
+ return 0;
+}
+
+#define VMSTATE_GPE_ARRAY(_field, _state) \
+ { \
+ .name = (stringify(_field)), \
+ .version_id = 0, \
+ .info = &vmstate_info_uint16, \
+ .size = sizeof(uint16_t), \
+ .flags = VMS_SINGLE | VMS_POINTER, \
+ .offset = vmstate_offset_pointer(_state, _field, uint8_t), \
+ }
+
+static const VMStateDescription vmstate_gpe = {
+ .name = "gpe",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_GPE_ARRAY(sts, ACPIGPE),
+ VMSTATE_GPE_ARRAY(en, ACPIGPE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_status = {
+ .name = "pci_status",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(up, struct AcpiPciHpPciStatus),
+ VMSTATE_UINT32(down, struct AcpiPciHpPciStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int acpi_load_old(QEMUFile *f, void *opaque, int version_id)
+{
+ PIIX4PMState *s = opaque;
+ int ret, i;
+ uint16_t temp;
+
+ ret = pci_device_load(PCI_DEVICE(s), f);
+ if (ret < 0) {
+ return ret;
+ }
+ qemu_get_be16s(f, &s->ar.pm1.evt.sts);
+ qemu_get_be16s(f, &s->ar.pm1.evt.en);
+ qemu_get_be16s(f, &s->ar.pm1.cnt.cnt);
+
+ ret = vmstate_load_state(f, &vmstate_apm, &s->apm, 1);
+ if (ret) {
+ return ret;
+ }
+
+ timer_get(f, s->ar.tmr.timer);
+ qemu_get_sbe64s(f, &s->ar.tmr.overflow_time);
+
+ qemu_get_be16s(f, (uint16_t *)s->ar.gpe.sts);
+ for (i = 0; i < 3; i++) {
+ qemu_get_be16s(f, &temp);
+ }
+
+ qemu_get_be16s(f, (uint16_t *)s->ar.gpe.en);
+ for (i = 0; i < 3; i++) {
+ qemu_get_be16s(f, &temp);
+ }
+
+ ret = vmstate_load_state(f, &vmstate_pci_status,
+ &s->acpi_pci_hotplug.acpi_pcihp_pci_status[ACPI_PCIHP_BSEL_DEFAULT], 1);
+ return ret;
+}
+
+static bool vmstate_test_use_acpi_pci_hotplug(void *opaque, int version_id)
+{
+ PIIX4PMState *s = opaque;
+ return s->use_acpi_pci_hotplug;
+}
+
+static bool vmstate_test_no_use_acpi_pci_hotplug(void *opaque, int version_id)
+{
+ PIIX4PMState *s = opaque;
+ return !s->use_acpi_pci_hotplug;
+}
+
+static bool vmstate_test_use_memhp(void *opaque)
+{
+ PIIX4PMState *s = opaque;
+ return s->acpi_memory_hotplug.is_enabled;
+}
+
+static const VMStateDescription vmstate_memhp_state = {
+ .name = "piix4_pm/memhp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .needed = vmstate_test_use_memhp,
+ .fields = (VMStateField[]) {
+ VMSTATE_MEMORY_HOTPLUG(acpi_memory_hotplug, PIIX4PMState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* qemu-kvm 1.2 uses version 3 but advertised as 2
+ * To support incoming qemu-kvm 1.2 migration, change version_id
+ * and minimum_version_id to 2 below (which breaks migration from
+ * qemu 1.2).
+ *
+ */
+static const VMStateDescription vmstate_acpi = {
+ .name = "piix4_pm",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .minimum_version_id_old = 1,
+ .load_state_old = acpi_load_old,
+ .post_load = vmstate_acpi_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PIIX4PMState),
+ VMSTATE_UINT16(ar.pm1.evt.sts, PIIX4PMState),
+ VMSTATE_UINT16(ar.pm1.evt.en, PIIX4PMState),
+ VMSTATE_UINT16(ar.pm1.cnt.cnt, PIIX4PMState),
+ VMSTATE_STRUCT(apm, PIIX4PMState, 0, vmstate_apm, APMState),
+ VMSTATE_TIMER_PTR(ar.tmr.timer, PIIX4PMState),
+ VMSTATE_INT64(ar.tmr.overflow_time, PIIX4PMState),
+ VMSTATE_STRUCT(ar.gpe, PIIX4PMState, 2, vmstate_gpe, ACPIGPE),
+ VMSTATE_STRUCT_TEST(
+ acpi_pci_hotplug.acpi_pcihp_pci_status[ACPI_PCIHP_BSEL_DEFAULT],
+ PIIX4PMState,
+ vmstate_test_no_use_acpi_pci_hotplug,
+ 2, vmstate_pci_status,
+ struct AcpiPciHpPciStatus),
+ VMSTATE_PCI_HOTPLUG(acpi_pci_hotplug, PIIX4PMState,
+ vmstate_test_use_acpi_pci_hotplug),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_memhp_state,
+ NULL
+ }
+};
+
+static void piix4_reset(void *opaque)
+{
+ PIIX4PMState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+ uint8_t *pci_conf = d->config;
+
+ pci_conf[0x58] = 0;
+ pci_conf[0x59] = 0;
+ pci_conf[0x5a] = 0;
+ pci_conf[0x5b] = 0;
+
+ pci_conf[0x40] = 0x01; /* PM io base read only bit */
+ pci_conf[0x80] = 0;
+
+ if (!s->smm_enabled) {
+ /* Mark SMM as already inited (until KVM supports SMM). */
+ pci_conf[0x5B] = 0x02;
+ }
+ pm_io_space_update(s);
+ acpi_pcihp_reset(&s->acpi_pci_hotplug);
+}
+
+static void piix4_pm_powerdown_req(Notifier *n, void *opaque)
+{
+ PIIX4PMState *s = container_of(n, PIIX4PMState, powerdown_notifier);
+
+ assert(s != NULL);
+ acpi_pm1_evt_power_down(&s->ar);
+}
+
+static void piix4_device_plug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(hotplug_dev);
+
+ if (s->acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_plug_cb(&s->ar, s->irq, &s->acpi_memory_hotplug, dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_plug_cb(&s->ar, s->irq, &s->acpi_pci_hotplug, dev,
+ errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
+ acpi_cpu_plug_cb(&s->ar, s->irq, &s->gpe_cpu, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device plug request for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void piix4_device_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(hotplug_dev);
+
+ if (s->acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_request_cb(&s->ar, s->irq, &s->acpi_memory_hotplug,
+ dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_unplug_cb(&s->ar, s->irq, &s->acpi_pci_hotplug, dev,
+ errp);
+ } else {
+ error_setg(errp, "acpi: device unplug request for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void piix4_device_unplug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(hotplug_dev);
+
+ if (s->acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_cb(&s->acpi_memory_hotplug, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void piix4_update_bus_hotplug(PCIBus *pci_bus, void *opaque)
+{
+ PIIX4PMState *s = opaque;
+
+ qbus_set_hotplug_handler(BUS(pci_bus), DEVICE(s), &error_abort);
+}
+
+static void piix4_pm_machine_ready(Notifier *n, void *opaque)
+{
+ PIIX4PMState *s = container_of(n, PIIX4PMState, machine_ready);
+ PCIDevice *d = PCI_DEVICE(s);
+ MemoryRegion *io_as = pci_address_space_io(d);
+ uint8_t *pci_conf;
+
+ pci_conf = d->config;
+ pci_conf[0x5f] = 0x10 |
+ (memory_region_present(io_as, 0x378) ? 0x80 : 0);
+ pci_conf[0x63] = 0x60;
+ pci_conf[0x67] = (memory_region_present(io_as, 0x3f8) ? 0x08 : 0) |
+ (memory_region_present(io_as, 0x2f8) ? 0x90 : 0);
+
+ if (s->use_acpi_pci_hotplug) {
+ pci_for_each_bus(d->bus, piix4_update_bus_hotplug, s);
+ } else {
+ piix4_update_bus_hotplug(d->bus, s);
+ }
+}
+
+static void piix4_pm_add_propeties(PIIX4PMState *s)
+{
+ static const uint8_t acpi_enable_cmd = ACPI_ENABLE;
+ static const uint8_t acpi_disable_cmd = ACPI_DISABLE;
+ static const uint32_t gpe0_blk = GPE_BASE;
+ static const uint32_t gpe0_blk_len = GPE_LEN;
+ static const uint16_t sci_int = 9;
+
+ object_property_add_uint8_ptr(OBJECT(s), ACPI_PM_PROP_ACPI_ENABLE_CMD,
+ &acpi_enable_cmd, NULL);
+ object_property_add_uint8_ptr(OBJECT(s), ACPI_PM_PROP_ACPI_DISABLE_CMD,
+ &acpi_disable_cmd, NULL);
+ object_property_add_uint32_ptr(OBJECT(s), ACPI_PM_PROP_GPE0_BLK,
+ &gpe0_blk, NULL);
+ object_property_add_uint32_ptr(OBJECT(s), ACPI_PM_PROP_GPE0_BLK_LEN,
+ &gpe0_blk_len, NULL);
+ object_property_add_uint16_ptr(OBJECT(s), ACPI_PM_PROP_SCI_INT,
+ &sci_int, NULL);
+ object_property_add_uint32_ptr(OBJECT(s), ACPI_PM_PROP_PM_IO_BASE,
+ &s->io_base, NULL);
+}
+
+static void piix4_pm_realize(PCIDevice *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(dev);
+ uint8_t *pci_conf;
+
+ pci_conf = dev->config;
+ pci_conf[0x06] = 0x80;
+ pci_conf[0x07] = 0x02;
+ pci_conf[0x09] = 0x00;
+ pci_conf[0x3d] = 0x01; // interrupt pin 1
+
+ /* APM */
+ apm_init(dev, &s->apm, apm_ctrl_changed, s);
+
+ if (!s->smm_enabled) {
+ /* Mark SMM as already inited to prevent SMM from running. KVM does not
+ * support SMM mode. */
+ pci_conf[0x5B] = 0x02;
+ }
+
+ /* XXX: which specification is used ? The i82731AB has different
+ mappings */
+ pci_conf[0x90] = s->smb_io_base | 1;
+ pci_conf[0x91] = s->smb_io_base >> 8;
+ pci_conf[0xd2] = 0x09;
+ pm_smbus_init(DEVICE(dev), &s->smb);
+ memory_region_set_enabled(&s->smb.io, pci_conf[0xd2] & 1);
+ memory_region_add_subregion(pci_address_space_io(dev),
+ s->smb_io_base, &s->smb.io);
+
+ memory_region_init(&s->io, OBJECT(s), "piix4-pm", 64);
+ memory_region_set_enabled(&s->io, false);
+ memory_region_add_subregion(pci_address_space_io(dev),
+ 0, &s->io);
+
+ acpi_pm_tmr_init(&s->ar, pm_tmr_timer, &s->io);
+ acpi_pm1_evt_init(&s->ar, pm_tmr_timer, &s->io);
+ acpi_pm1_cnt_init(&s->ar, &s->io, s->disable_s3, s->disable_s4, s->s4_val);
+ acpi_gpe_init(&s->ar, GPE_LEN);
+
+ s->powerdown_notifier.notify = piix4_pm_powerdown_req;
+ qemu_register_powerdown_notifier(&s->powerdown_notifier);
+
+ s->machine_ready.notify = piix4_pm_machine_ready;
+ qemu_add_machine_init_done_notifier(&s->machine_ready);
+ qemu_register_reset(piix4_reset, s);
+
+ piix4_acpi_system_hot_add_init(pci_address_space_io(dev), dev->bus, s);
+
+ piix4_pm_add_propeties(s);
+}
+
+Object *piix4_pm_find(void)
+{
+ bool ambig;
+ Object *o = object_resolve_path_type("", TYPE_PIIX4_PM, &ambig);
+
+ if (ambig || !o) {
+ return NULL;
+ }
+ return o;
+}
+
+I2CBus *piix4_pm_init(PCIBus *bus, int devfn, uint32_t smb_io_base,
+ qemu_irq sci_irq, qemu_irq smi_irq,
+ int smm_enabled, DeviceState **piix4_pm)
+{
+ DeviceState *dev;
+ PIIX4PMState *s;
+
+ dev = DEVICE(pci_create(bus, devfn, TYPE_PIIX4_PM));
+ qdev_prop_set_uint32(dev, "smb_io_base", smb_io_base);
+ if (piix4_pm) {
+ *piix4_pm = dev;
+ }
+
+ s = PIIX4_PM(dev);
+ s->irq = sci_irq;
+ s->smi_irq = smi_irq;
+ s->smm_enabled = smm_enabled;
+ if (xen_enabled()) {
+ s->use_acpi_pci_hotplug = false;
+ }
+
+ qdev_init_nofail(dev);
+
+ return s->smb.smbus;
+}
+
+static uint64_t gpe_readb(void *opaque, hwaddr addr, unsigned width)
+{
+ PIIX4PMState *s = opaque;
+ uint32_t val = acpi_gpe_ioport_readb(&s->ar, addr);
+
+ PIIX4_DPRINTF("gpe read %" HWADDR_PRIx " == %" PRIu32 "\n", addr, val);
+ return val;
+}
+
+static void gpe_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ PIIX4PMState *s = opaque;
+
+ acpi_gpe_ioport_writeb(&s->ar, addr, val);
+ acpi_update_sci(&s->ar, s->irq);
+
+ PIIX4_DPRINTF("gpe write %" HWADDR_PRIx " <== %" PRIu64 "\n", addr, val);
+}
+
+static const MemoryRegionOps piix4_gpe_ops = {
+ .read = gpe_readb,
+ .write = gpe_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void piix4_acpi_system_hot_add_init(MemoryRegion *parent,
+ PCIBus *bus, PIIX4PMState *s)
+{
+ memory_region_init_io(&s->io_gpe, OBJECT(s), &piix4_gpe_ops, s,
+ "acpi-gpe0", GPE_LEN);
+ memory_region_add_subregion(parent, GPE_BASE, &s->io_gpe);
+
+ acpi_pcihp_init(OBJECT(s), &s->acpi_pci_hotplug, bus, parent,
+ s->use_acpi_pci_hotplug);
+
+ acpi_cpu_hotplug_init(parent, OBJECT(s), &s->gpe_cpu,
+ PIIX4_CPU_HOTPLUG_IO_BASE);
+
+ if (s->acpi_memory_hotplug.is_enabled) {
+ acpi_memory_hotplug_init(parent, OBJECT(s), &s->acpi_memory_hotplug);
+ }
+}
+
+static void piix4_ospm_status(AcpiDeviceIf *adev, ACPIOSTInfoList ***list)
+{
+ PIIX4PMState *s = PIIX4_PM(adev);
+
+ acpi_memory_ospm_status(&s->acpi_memory_hotplug, list);
+}
+
+static Property piix4_pm_properties[] = {
+ DEFINE_PROP_UINT32("smb_io_base", PIIX4PMState, smb_io_base, 0),
+ DEFINE_PROP_UINT8(ACPI_PM_PROP_S3_DISABLED, PIIX4PMState, disable_s3, 0),
+ DEFINE_PROP_UINT8(ACPI_PM_PROP_S4_DISABLED, PIIX4PMState, disable_s4, 0),
+ DEFINE_PROP_UINT8(ACPI_PM_PROP_S4_VAL, PIIX4PMState, s4_val, 2),
+ DEFINE_PROP_BOOL("acpi-pci-hotplug-with-bridge-support", PIIX4PMState,
+ use_acpi_pci_hotplug, true),
+ DEFINE_PROP_BOOL("memory-hotplug-support", PIIX4PMState,
+ acpi_memory_hotplug.is_enabled, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void piix4_pm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(klass);
+
+ k->realize = piix4_pm_realize;
+ k->config_write = pm_write_config;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82371AB_3;
+ k->revision = 0x03;
+ k->class_id = PCI_CLASS_BRIDGE_OTHER;
+ dc->desc = "PM";
+ dc->vmsd = &vmstate_acpi;
+ dc->props = piix4_pm_properties;
+ /*
+ * Reason: part of PIIX4 southbridge, needs to be wired up,
+ * e.g. by mips_malta_init()
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+ dc->hotpluggable = false;
+ hc->plug = piix4_device_plug_cb;
+ hc->unplug_request = piix4_device_unplug_request_cb;
+ hc->unplug = piix4_device_unplug_cb;
+ adevc->ospm_status = piix4_ospm_status;
+}
+
+static const TypeInfo piix4_pm_info = {
+ .name = TYPE_PIIX4_PM,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PIIX4PMState),
+ .class_init = piix4_pm_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { TYPE_ACPI_DEVICE_IF },
+ { }
+ }
+};
+
+static void piix4_pm_register_types(void)
+{
+ type_register_static(&piix4_pm_info);
+}
+
+type_init(piix4_pm_register_types)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c
new file mode 100644
index 00000000..7a026c25
--- /dev/null
+++ b/hw/acpi/tco.c
@@ -0,0 +1,264 @@
+/*
+ * QEMU ICH9 TCO emulation
+ *
+ * Copyright (c) 2015 Paulo Alcantara <pcacjr@zytor.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu-common.h"
+#include "sysemu/watchdog.h"
+#include "hw/i386/ich9.h"
+
+#include "hw/acpi/tco.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+#define TCO_DEBUG(fmt, ...) \
+ do { \
+ fprintf(stderr, "%s "fmt, __func__, ## __VA_ARGS__); \
+ } while (0)
+#else
+#define TCO_DEBUG(fmt, ...) do { } while (0)
+#endif
+
+enum {
+ TCO_RLD_DEFAULT = 0x0000,
+ TCO_DAT_IN_DEFAULT = 0x00,
+ TCO_DAT_OUT_DEFAULT = 0x00,
+ TCO1_STS_DEFAULT = 0x0000,
+ TCO2_STS_DEFAULT = 0x0000,
+ TCO1_CNT_DEFAULT = 0x0000,
+ TCO2_CNT_DEFAULT = 0x0008,
+ TCO_MESSAGE1_DEFAULT = 0x00,
+ TCO_MESSAGE2_DEFAULT = 0x00,
+ TCO_WDCNT_DEFAULT = 0x00,
+ TCO_TMR_DEFAULT = 0x0004,
+ SW_IRQ_GEN_DEFAULT = 0x03,
+};
+
+static inline void tco_timer_reload(TCOIORegs *tr)
+{
+ tr->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ ((int64_t)(tr->tco.tmr & TCO_TMR_MASK) * TCO_TICK_NSEC);
+ timer_mod(tr->tco_timer, tr->expire_time);
+}
+
+static inline void tco_timer_stop(TCOIORegs *tr)
+{
+ tr->expire_time = -1;
+}
+
+static void tco_timer_expired(void *opaque)
+{
+ TCOIORegs *tr = opaque;
+ ICH9LPCPMRegs *pm = container_of(tr, ICH9LPCPMRegs, tco_regs);
+ ICH9LPCState *lpc = container_of(pm, ICH9LPCState, pm);
+ uint32_t gcs = pci_get_long(lpc->chip_config + ICH9_CC_GCS);
+
+ tr->tco.rld = 0;
+ tr->tco.sts1 |= TCO_TIMEOUT;
+ if (++tr->timeouts_no == 2) {
+ tr->tco.sts2 |= TCO_SECOND_TO_STS;
+ tr->tco.sts2 |= TCO_BOOT_STS;
+ tr->timeouts_no = 0;
+
+ if (!lpc->pin_strap.spkr_hi && !(gcs & ICH9_CC_GCS_NO_REBOOT)) {
+ watchdog_perform_action();
+ tco_timer_stop(tr);
+ return;
+ }
+ }
+
+ if (pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN) {
+ ich9_generate_smi();
+ } else {
+ ich9_generate_nmi();
+ }
+ tr->tco.rld = tr->tco.tmr;
+ tco_timer_reload(tr);
+}
+
+/* NOTE: values of 0 or 1 will be ignored by ICH */
+static inline int can_start_tco_timer(TCOIORegs *tr)
+{
+ return !(tr->tco.cnt1 & TCO_TMR_HLT) && tr->tco.tmr > 1;
+}
+
+static uint32_t tco_ioport_readw(TCOIORegs *tr, uint32_t addr)
+{
+ uint16_t rld;
+
+ switch (addr) {
+ case TCO_RLD:
+ if (tr->expire_time != -1) {
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ int64_t elapsed = (tr->expire_time - now) / TCO_TICK_NSEC;
+ rld = (uint16_t)elapsed | (tr->tco.rld & ~TCO_RLD_MASK);
+ } else {
+ rld = tr->tco.rld;
+ }
+ return rld;
+ case TCO_DAT_IN:
+ return tr->tco.din;
+ case TCO_DAT_OUT:
+ return tr->tco.dout;
+ case TCO1_STS:
+ return tr->tco.sts1;
+ case TCO2_STS:
+ return tr->tco.sts2;
+ case TCO1_CNT:
+ return tr->tco.cnt1;
+ case TCO2_CNT:
+ return tr->tco.cnt2;
+ case TCO_MESSAGE1:
+ return tr->tco.msg1;
+ case TCO_MESSAGE2:
+ return tr->tco.msg2;
+ case TCO_WDCNT:
+ return tr->tco.wdcnt;
+ case TCO_TMR:
+ return tr->tco.tmr;
+ case SW_IRQ_GEN:
+ return tr->sw_irq_gen;
+ }
+ return 0;
+}
+
+static void tco_ioport_writew(TCOIORegs *tr, uint32_t addr, uint32_t val)
+{
+ switch (addr) {
+ case TCO_RLD:
+ tr->timeouts_no = 0;
+ if (can_start_tco_timer(tr)) {
+ tr->tco.rld = tr->tco.tmr;
+ tco_timer_reload(tr);
+ } else {
+ tr->tco.rld = val;
+ }
+ break;
+ case TCO_DAT_IN:
+ tr->tco.din = val;
+ tr->tco.sts1 |= SW_TCO_SMI;
+ ich9_generate_smi();
+ break;
+ case TCO_DAT_OUT:
+ tr->tco.dout = val;
+ tr->tco.sts1 |= TCO_INT_STS;
+ /* TODO: cause an interrupt, as selected by the TCO_INT_SEL bits */
+ break;
+ case TCO1_STS:
+ tr->tco.sts1 = val & TCO1_STS_MASK;
+ break;
+ case TCO2_STS:
+ tr->tco.sts2 = val & TCO2_STS_MASK;
+ break;
+ case TCO1_CNT:
+ val &= TCO1_CNT_MASK;
+ /*
+ * once TCO_LOCK bit is set, it can not be cleared by software. a reset
+ * is required to change this bit from 1 to 0 -- it defaults to 0.
+ */
+ tr->tco.cnt1 = val | (tr->tco.cnt1 & TCO_LOCK);
+ if (can_start_tco_timer(tr)) {
+ tr->tco.rld = tr->tco.tmr;
+ tco_timer_reload(tr);
+ } else {
+ tco_timer_stop(tr);
+ }
+ break;
+ case TCO2_CNT:
+ tr->tco.cnt2 = val;
+ break;
+ case TCO_MESSAGE1:
+ tr->tco.msg1 = val;
+ break;
+ case TCO_MESSAGE2:
+ tr->tco.msg2 = val;
+ break;
+ case TCO_WDCNT:
+ tr->tco.wdcnt = val;
+ break;
+ case TCO_TMR:
+ tr->tco.tmr = val;
+ break;
+ case SW_IRQ_GEN:
+ tr->sw_irq_gen = val;
+ break;
+ }
+}
+
+static uint64_t tco_io_readw(void *opaque, hwaddr addr, unsigned width)
+{
+ TCOIORegs *tr = opaque;
+ return tco_ioport_readw(tr, addr);
+}
+
+static void tco_io_writew(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ TCOIORegs *tr = opaque;
+ tco_ioport_writew(tr, addr, val);
+}
+
+static const MemoryRegionOps tco_io_ops = {
+ .read = tco_io_readw,
+ .write = tco_io_writew,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void acpi_pm_tco_init(TCOIORegs *tr, MemoryRegion *parent)
+{
+ *tr = (TCOIORegs) {
+ .tco = {
+ .rld = TCO_RLD_DEFAULT,
+ .din = TCO_DAT_IN_DEFAULT,
+ .dout = TCO_DAT_OUT_DEFAULT,
+ .sts1 = TCO1_STS_DEFAULT,
+ .sts2 = TCO2_STS_DEFAULT,
+ .cnt1 = TCO1_CNT_DEFAULT,
+ .cnt2 = TCO2_CNT_DEFAULT,
+ .msg1 = TCO_MESSAGE1_DEFAULT,
+ .msg2 = TCO_MESSAGE2_DEFAULT,
+ .wdcnt = TCO_WDCNT_DEFAULT,
+ .tmr = TCO_TMR_DEFAULT,
+ },
+ .sw_irq_gen = SW_IRQ_GEN_DEFAULT,
+ .tco_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tco_timer_expired, tr),
+ .expire_time = -1,
+ .timeouts_no = 0,
+ };
+ memory_region_init_io(&tr->io, memory_region_owner(parent),
+ &tco_io_ops, tr, "sm-tco", ICH9_PMIO_TCO_LEN);
+ memory_region_add_subregion(parent, ICH9_PMIO_TCO_RLD, &tr->io);
+}
+
+const VMStateDescription vmstate_tco_io_sts = {
+ .name = "tco io device status",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(tco.rld, TCOIORegs),
+ VMSTATE_UINT8(tco.din, TCOIORegs),
+ VMSTATE_UINT8(tco.dout, TCOIORegs),
+ VMSTATE_UINT16(tco.sts1, TCOIORegs),
+ VMSTATE_UINT16(tco.sts2, TCOIORegs),
+ VMSTATE_UINT16(tco.cnt1, TCOIORegs),
+ VMSTATE_UINT16(tco.cnt2, TCOIORegs),
+ VMSTATE_UINT8(tco.msg1, TCOIORegs),
+ VMSTATE_UINT8(tco.msg2, TCOIORegs),
+ VMSTATE_UINT8(tco.wdcnt, TCOIORegs),
+ VMSTATE_UINT16(tco.tmr, TCOIORegs),
+ VMSTATE_UINT8(sw_irq_gen, TCOIORegs),
+ VMSTATE_TIMER_PTR(tco_timer, TCOIORegs),
+ VMSTATE_INT64(expire_time, TCOIORegs),
+ VMSTATE_UINT8(timeouts_no, TCOIORegs),
+ VMSTATE_END_OF_LIST()
+ }
+};
diff --git a/hw/alpha/Makefile.objs b/hw/alpha/Makefile.objs
new file mode 100644
index 00000000..5c742756
--- /dev/null
+++ b/hw/alpha/Makefile.objs
@@ -0,0 +1 @@
+obj-y += dp264.o pci.o typhoon.o
diff --git a/hw/alpha/alpha_sys.h b/hw/alpha/alpha_sys.h
new file mode 100644
index 00000000..e11025b4
--- /dev/null
+++ b/hw/alpha/alpha_sys.h
@@ -0,0 +1,21 @@
+/* Alpha cores and system support chips. */
+
+#ifndef HW_ALPHA_H
+#define HW_ALPHA_H 1
+
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "hw/ide.h"
+#include "hw/i386/pc.h"
+#include "hw/irq.h"
+
+
+PCIBus *typhoon_init(ram_addr_t, ISABus **, qemu_irq *, AlphaCPU *[4],
+ pci_map_irq_fn);
+
+/* alpha_pci.c. */
+extern const MemoryRegionOps alpha_pci_ignore_ops;
+extern const MemoryRegionOps alpha_pci_conf1_ops;
+extern const MemoryRegionOps alpha_pci_iack_ops;
+
+#endif
diff --git a/hw/alpha/dp264.c b/hw/alpha/dp264.c
new file mode 100644
index 00000000..f86e7bb8
--- /dev/null
+++ b/hw/alpha/dp264.c
@@ -0,0 +1,184 @@
+/*
+ * QEMU Alpha DP264/CLIPPER hardware system emulator.
+ *
+ * Choose CLIPPER IRQ mappings over, say, DP264, MONET, or WEBBRICK
+ * variants because CLIPPER doesn't have an SMC669 SuperIO controller
+ * that we need to emulate as well.
+ */
+
+#include "hw/hw.h"
+#include "elf.h"
+#include "hw/loader.h"
+#include "hw/boards.h"
+#include "alpha_sys.h"
+#include "sysemu/sysemu.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/ide.h"
+#include "hw/timer/i8254.h"
+#include "hw/char/serial.h"
+
+#define MAX_IDE_BUS 2
+
+static uint64_t cpu_alpha_superpage_to_phys(void *opaque, uint64_t addr)
+{
+ if (((addr >> 41) & 3) == 2) {
+ addr &= 0xffffffffffull;
+ }
+ return addr;
+}
+
+/* Note that there are at least 3 viewpoints of IRQ numbers on Alpha systems.
+ (0) The dev_irq_n lines into the cpu, which we totally ignore,
+ (1) The DRIR lines in the typhoon chipset,
+ (2) The "vector" aka mangled interrupt number reported by SRM PALcode,
+ (3) The interrupt number assigned by the kernel.
+ The following function is concerned with (1) only. */
+
+static int clipper_pci_map_irq(PCIDevice *d, int irq_num)
+{
+ int slot = d->devfn >> 3;
+
+ assert(irq_num >= 0 && irq_num <= 3);
+
+ return (slot + 1) * 4 + irq_num;
+}
+
+static void clipper_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ AlphaCPU *cpus[4];
+ PCIBus *pci_bus;
+ ISABus *isa_bus;
+ qemu_irq rtc_irq;
+ long size, i;
+ char *palcode_filename;
+ uint64_t palcode_entry, palcode_low, palcode_high;
+ uint64_t kernel_entry, kernel_low, kernel_high;
+
+ /* Create up to 4 cpus. */
+ memset(cpus, 0, sizeof(cpus));
+ for (i = 0; i < smp_cpus; ++i) {
+ cpus[i] = cpu_alpha_init(cpu_model ? cpu_model : "ev67");
+ }
+
+ cpus[0]->env.trap_arg0 = ram_size;
+ cpus[0]->env.trap_arg1 = 0;
+ cpus[0]->env.trap_arg2 = smp_cpus;
+
+ /* Init the chipset. */
+ pci_bus = typhoon_init(ram_size, &isa_bus, &rtc_irq, cpus,
+ clipper_pci_map_irq);
+
+ /* Since we have an SRM-compatible PALcode, use the SRM epoch. */
+ rtc_init(isa_bus, 1900, rtc_irq);
+
+ pit_init(isa_bus, 0x40, 0, NULL);
+ isa_create_simple(isa_bus, "i8042");
+
+ /* VGA setup. Don't bother loading the bios. */
+ pci_vga_init(pci_bus);
+
+ /* Serial code setup. */
+ serial_hds_isa_init(isa_bus, MAX_SERIAL_PORTS);
+
+ /* Network setup. e1000 is good enough, failing Tulip support. */
+ for (i = 0; i < nb_nics; i++) {
+ pci_nic_init_nofail(&nd_table[i], pci_bus, "e1000", NULL);
+ }
+
+ /* IDE disk setup. */
+ {
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+
+ pci_cmd646_ide_init(pci_bus, hd, 0);
+ }
+
+ /* Load PALcode. Given that this is not "real" cpu palcode,
+ but one explicitly written for the emulation, we might as
+ well load it directly from and ELF image. */
+ palcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS,
+ bios_name ? bios_name : "palcode-clipper");
+ if (palcode_filename == NULL) {
+ hw_error("no palcode provided\n");
+ exit(1);
+ }
+ size = load_elf(palcode_filename, cpu_alpha_superpage_to_phys,
+ NULL, &palcode_entry, &palcode_low, &palcode_high,
+ 0, EM_ALPHA, 0);
+ if (size < 0) {
+ hw_error("could not load palcode '%s'\n", palcode_filename);
+ exit(1);
+ }
+ g_free(palcode_filename);
+
+ /* Start all cpus at the PALcode RESET entry point. */
+ for (i = 0; i < smp_cpus; ++i) {
+ cpus[i]->env.pal_mode = 1;
+ cpus[i]->env.pc = palcode_entry;
+ cpus[i]->env.palbr = palcode_entry;
+ }
+
+ /* Load a kernel. */
+ if (kernel_filename) {
+ uint64_t param_offset;
+
+ size = load_elf(kernel_filename, cpu_alpha_superpage_to_phys,
+ NULL, &kernel_entry, &kernel_low, &kernel_high,
+ 0, EM_ALPHA, 0);
+ if (size < 0) {
+ hw_error("could not load kernel '%s'\n", kernel_filename);
+ exit(1);
+ }
+
+ cpus[0]->env.trap_arg1 = kernel_entry;
+
+ param_offset = kernel_low - 0x6000;
+
+ if (kernel_cmdline) {
+ pstrcpy_targphys("cmdline", param_offset, 0x100, kernel_cmdline);
+ }
+
+ if (initrd_filename) {
+ long initrd_base, initrd_size;
+
+ initrd_size = get_image_size(initrd_filename);
+ if (initrd_size < 0) {
+ hw_error("could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+
+ /* Put the initrd image as high in memory as possible. */
+ initrd_base = (ram_size - initrd_size) & TARGET_PAGE_MASK;
+ load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+
+ address_space_stq(&address_space_memory, param_offset + 0x100,
+ initrd_base + 0xfffffc0000000000ULL,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ address_space_stq(&address_space_memory, param_offset + 0x108,
+ initrd_size, MEMTXATTRS_UNSPECIFIED, NULL);
+ }
+ }
+}
+
+static QEMUMachine clipper_machine = {
+ .name = "clipper",
+ .desc = "Alpha DP264/CLIPPER",
+ .init = clipper_init,
+ .max_cpus = 4,
+ .is_default = 1,
+};
+
+static void clipper_machine_init(void)
+{
+ qemu_register_machine(&clipper_machine);
+}
+
+machine_init(clipper_machine_init);
diff --git a/hw/alpha/pci.c b/hw/alpha/pci.c
new file mode 100644
index 00000000..d839dd55
--- /dev/null
+++ b/hw/alpha/pci.c
@@ -0,0 +1,91 @@
+/*
+ * QEMU Alpha PCI support functions.
+ *
+ * Some of this isn't very Alpha specific at all.
+ *
+ * ??? Sparse memory access not implemented.
+ */
+
+#include "config.h"
+#include "alpha_sys.h"
+#include "qemu/log.h"
+#include "sysemu/sysemu.h"
+
+
+/* Fallback for unassigned PCI I/O operations. Avoids MCHK. */
+
+static uint64_t ignore_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0;
+}
+
+static void ignore_write(void *opaque, hwaddr addr, uint64_t v, unsigned size)
+{
+}
+
+const MemoryRegionOps alpha_pci_ignore_ops = {
+ .read = ignore_read,
+ .write = ignore_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 8,
+ },
+};
+
+
+/* PCI config space reads/writes, to byte-word addressable memory. */
+static uint64_t bw_conf1_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCIBus *b = opaque;
+ return pci_data_read(b, addr, size);
+}
+
+static void bw_conf1_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIBus *b = opaque;
+ pci_data_write(b, addr, val, size);
+}
+
+const MemoryRegionOps alpha_pci_conf1_ops = {
+ .read = bw_conf1_read,
+ .write = bw_conf1_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+/* PCI/EISA Interrupt Acknowledge Cycle. */
+
+static uint64_t iack_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return pic_read_irq(isa_pic);
+}
+
+static void special_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ qemu_log("pci: special write cycle");
+}
+
+const MemoryRegionOps alpha_pci_iack_ops = {
+ .read = iack_read,
+ .write = special_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
diff --git a/hw/alpha/typhoon.c b/hw/alpha/typhoon.c
new file mode 100644
index 00000000..421162e1
--- /dev/null
+++ b/hw/alpha/typhoon.c
@@ -0,0 +1,956 @@
+/*
+ * DEC 21272 (TSUNAMI/TYPHOON) chipset emulation.
+ *
+ * Written by Richard Henderson.
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ */
+
+#include "cpu.h"
+#include "hw/hw.h"
+#include "hw/devices.h"
+#include "sysemu/sysemu.h"
+#include "alpha_sys.h"
+#include "exec/address-spaces.h"
+
+
+#define TYPE_TYPHOON_PCI_HOST_BRIDGE "typhoon-pcihost"
+
+typedef struct TyphoonCchip {
+ MemoryRegion region;
+ uint64_t misc;
+ uint64_t drir;
+ uint64_t dim[4];
+ uint32_t iic[4];
+ AlphaCPU *cpu[4];
+} TyphoonCchip;
+
+typedef struct TyphoonWindow {
+ uint64_t wba;
+ uint64_t wsm;
+ uint64_t tba;
+} TyphoonWindow;
+
+typedef struct TyphoonPchip {
+ MemoryRegion region;
+ MemoryRegion reg_iack;
+ MemoryRegion reg_mem;
+ MemoryRegion reg_io;
+ MemoryRegion reg_conf;
+
+ AddressSpace iommu_as;
+ MemoryRegion iommu;
+
+ uint64_t ctl;
+ TyphoonWindow win[4];
+} TyphoonPchip;
+
+#define TYPHOON_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(TyphoonState, (obj), TYPE_TYPHOON_PCI_HOST_BRIDGE)
+
+typedef struct TyphoonState {
+ PCIHostState parent_obj;
+
+ TyphoonCchip cchip;
+ TyphoonPchip pchip;
+ MemoryRegion dchip_region;
+ MemoryRegion ram_region;
+} TyphoonState;
+
+/* Called when one of DRIR or DIM changes. */
+static void cpu_irq_change(AlphaCPU *cpu, uint64_t req)
+{
+ /* If there are any non-masked interrupts, tell the cpu. */
+ if (cpu != NULL) {
+ CPUState *cs = CPU(cpu);
+ if (req) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ }
+}
+
+static uint64_t cchip_read(void *opaque, hwaddr addr, unsigned size)
+{
+ CPUState *cpu = current_cpu;
+ TyphoonState *s = opaque;
+ uint64_t ret = 0;
+
+ switch (addr) {
+ case 0x0000:
+ /* CSC: Cchip System Configuration Register. */
+ /* All sorts of data here; probably the only thing relevant is
+ PIP<14> Pchip 1 Present = 0. */
+ break;
+
+ case 0x0040:
+ /* MTR: Memory Timing Register. */
+ /* All sorts of stuff related to real DRAM. */
+ break;
+
+ case 0x0080:
+ /* MISC: Miscellaneous Register. */
+ ret = s->cchip.misc | (cpu->cpu_index & 3);
+ break;
+
+ case 0x00c0:
+ /* MPD: Memory Presence Detect Register. */
+ break;
+
+ case 0x0100: /* AAR0 */
+ case 0x0140: /* AAR1 */
+ case 0x0180: /* AAR2 */
+ case 0x01c0: /* AAR3 */
+ /* AAR: Array Address Register. */
+ /* All sorts of information about DRAM. */
+ break;
+
+ case 0x0200:
+ /* DIM0: Device Interrupt Mask Register, CPU0. */
+ ret = s->cchip.dim[0];
+ break;
+ case 0x0240:
+ /* DIM1: Device Interrupt Mask Register, CPU1. */
+ ret = s->cchip.dim[1];
+ break;
+ case 0x0280:
+ /* DIR0: Device Interrupt Request Register, CPU0. */
+ ret = s->cchip.dim[0] & s->cchip.drir;
+ break;
+ case 0x02c0:
+ /* DIR1: Device Interrupt Request Register, CPU1. */
+ ret = s->cchip.dim[1] & s->cchip.drir;
+ break;
+ case 0x0300:
+ /* DRIR: Device Raw Interrupt Request Register. */
+ ret = s->cchip.drir;
+ break;
+
+ case 0x0340:
+ /* PRBEN: Probe Enable Register. */
+ break;
+
+ case 0x0380:
+ /* IIC0: Interval Ignore Count Register, CPU0. */
+ ret = s->cchip.iic[0];
+ break;
+ case 0x03c0:
+ /* IIC1: Interval Ignore Count Register, CPU1. */
+ ret = s->cchip.iic[1];
+ break;
+
+ case 0x0400: /* MPR0 */
+ case 0x0440: /* MPR1 */
+ case 0x0480: /* MPR2 */
+ case 0x04c0: /* MPR3 */
+ /* MPR: Memory Programming Register. */
+ break;
+
+ case 0x0580:
+ /* TTR: TIGbus Timing Register. */
+ /* All sorts of stuff related to interrupt delivery timings. */
+ break;
+ case 0x05c0:
+ /* TDR: TIGbug Device Timing Register. */
+ break;
+
+ case 0x0600:
+ /* DIM2: Device Interrupt Mask Register, CPU2. */
+ ret = s->cchip.dim[2];
+ break;
+ case 0x0640:
+ /* DIM3: Device Interrupt Mask Register, CPU3. */
+ ret = s->cchip.dim[3];
+ break;
+ case 0x0680:
+ /* DIR2: Device Interrupt Request Register, CPU2. */
+ ret = s->cchip.dim[2] & s->cchip.drir;
+ break;
+ case 0x06c0:
+ /* DIR3: Device Interrupt Request Register, CPU3. */
+ ret = s->cchip.dim[3] & s->cchip.drir;
+ break;
+
+ case 0x0700:
+ /* IIC2: Interval Ignore Count Register, CPU2. */
+ ret = s->cchip.iic[2];
+ break;
+ case 0x0740:
+ /* IIC3: Interval Ignore Count Register, CPU3. */
+ ret = s->cchip.iic[3];
+ break;
+
+ case 0x0780:
+ /* PWR: Power Management Control. */
+ break;
+
+ case 0x0c00: /* CMONCTLA */
+ case 0x0c40: /* CMONCTLB */
+ case 0x0c80: /* CMONCNT01 */
+ case 0x0cc0: /* CMONCNT23 */
+ break;
+
+ default:
+ cpu_unassigned_access(cpu, addr, false, false, 0, size);
+ return -1;
+ }
+
+ return ret;
+}
+
+static uint64_t dchip_read(void *opaque, hwaddr addr, unsigned size)
+{
+ /* Skip this. It's all related to DRAM timing and setup. */
+ return 0;
+}
+
+static uint64_t pchip_read(void *opaque, hwaddr addr, unsigned size)
+{
+ TyphoonState *s = opaque;
+ uint64_t ret = 0;
+
+ switch (addr) {
+ case 0x0000:
+ /* WSBA0: Window Space Base Address Register. */
+ ret = s->pchip.win[0].wba;
+ break;
+ case 0x0040:
+ /* WSBA1 */
+ ret = s->pchip.win[1].wba;
+ break;
+ case 0x0080:
+ /* WSBA2 */
+ ret = s->pchip.win[2].wba;
+ break;
+ case 0x00c0:
+ /* WSBA3 */
+ ret = s->pchip.win[3].wba;
+ break;
+
+ case 0x0100:
+ /* WSM0: Window Space Mask Register. */
+ ret = s->pchip.win[0].wsm;
+ break;
+ case 0x0140:
+ /* WSM1 */
+ ret = s->pchip.win[1].wsm;
+ break;
+ case 0x0180:
+ /* WSM2 */
+ ret = s->pchip.win[2].wsm;
+ break;
+ case 0x01c0:
+ /* WSM3 */
+ ret = s->pchip.win[3].wsm;
+ break;
+
+ case 0x0200:
+ /* TBA0: Translated Base Address Register. */
+ ret = s->pchip.win[0].tba;
+ break;
+ case 0x0240:
+ /* TBA1 */
+ ret = s->pchip.win[1].tba;
+ break;
+ case 0x0280:
+ /* TBA2 */
+ ret = s->pchip.win[2].tba;
+ break;
+ case 0x02c0:
+ /* TBA3 */
+ ret = s->pchip.win[3].tba;
+ break;
+
+ case 0x0300:
+ /* PCTL: Pchip Control Register. */
+ ret = s->pchip.ctl;
+ break;
+ case 0x0340:
+ /* PLAT: Pchip Master Latency Register. */
+ break;
+ case 0x03c0:
+ /* PERROR: Pchip Error Register. */
+ break;
+ case 0x0400:
+ /* PERRMASK: Pchip Error Mask Register. */
+ break;
+ case 0x0440:
+ /* PERRSET: Pchip Error Set Register. */
+ break;
+ case 0x0480:
+ /* TLBIV: Translation Buffer Invalidate Virtual Register (WO). */
+ break;
+ case 0x04c0:
+ /* TLBIA: Translation Buffer Invalidate All Register (WO). */
+ break;
+ case 0x0500: /* PMONCTL */
+ case 0x0540: /* PMONCNT */
+ case 0x0800: /* SPRST */
+ break;
+
+ default:
+ cpu_unassigned_access(current_cpu, addr, false, false, 0, size);
+ return -1;
+ }
+
+ return ret;
+}
+
+static void cchip_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TyphoonState *s = opaque;
+ uint64_t oldval, newval;
+
+ switch (addr) {
+ case 0x0000:
+ /* CSC: Cchip System Configuration Register. */
+ /* All sorts of data here; nothing relevant RW. */
+ break;
+
+ case 0x0040:
+ /* MTR: Memory Timing Register. */
+ /* All sorts of stuff related to real DRAM. */
+ break;
+
+ case 0x0080:
+ /* MISC: Miscellaneous Register. */
+ newval = oldval = s->cchip.misc;
+ newval &= ~(val & 0x10000ff0); /* W1C fields */
+ if (val & 0x100000) {
+ newval &= ~0xff0000ull; /* ACL clears ABT and ABW */
+ } else {
+ newval |= val & 0x00f00000; /* ABT field is W1S */
+ if ((newval & 0xf0000) == 0) {
+ newval |= val & 0xf0000; /* ABW field is W1S iff zero */
+ }
+ }
+ newval |= (val & 0xf000) >> 4; /* IPREQ field sets IPINTR. */
+
+ newval &= ~0xf0000000000ull; /* WO and RW fields */
+ newval |= val & 0xf0000000000ull;
+ s->cchip.misc = newval;
+
+ /* Pass on changes to IPI and ITI state. */
+ if ((newval ^ oldval) & 0xff0) {
+ int i;
+ for (i = 0; i < 4; ++i) {
+ AlphaCPU *cpu = s->cchip.cpu[i];
+ if (cpu != NULL) {
+ CPUState *cs = CPU(cpu);
+ /* IPI can be either cleared or set by the write. */
+ if (newval & (1 << (i + 8))) {
+ cpu_interrupt(cs, CPU_INTERRUPT_SMP);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_SMP);
+ }
+
+ /* ITI can only be cleared by the write. */
+ if ((newval & (1 << (i + 4))) == 0) {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_TIMER);
+ }
+ }
+ }
+ }
+ break;
+
+ case 0x00c0:
+ /* MPD: Memory Presence Detect Register. */
+ break;
+
+ case 0x0100: /* AAR0 */
+ case 0x0140: /* AAR1 */
+ case 0x0180: /* AAR2 */
+ case 0x01c0: /* AAR3 */
+ /* AAR: Array Address Register. */
+ /* All sorts of information about DRAM. */
+ break;
+
+ case 0x0200: /* DIM0 */
+ /* DIM: Device Interrupt Mask Register, CPU0. */
+ s->cchip.dim[0] = val;
+ cpu_irq_change(s->cchip.cpu[0], val & s->cchip.drir);
+ break;
+ case 0x0240: /* DIM1 */
+ /* DIM: Device Interrupt Mask Register, CPU1. */
+ s->cchip.dim[0] = val;
+ cpu_irq_change(s->cchip.cpu[1], val & s->cchip.drir);
+ break;
+
+ case 0x0280: /* DIR0 (RO) */
+ case 0x02c0: /* DIR1 (RO) */
+ case 0x0300: /* DRIR (RO) */
+ break;
+
+ case 0x0340:
+ /* PRBEN: Probe Enable Register. */
+ break;
+
+ case 0x0380: /* IIC0 */
+ s->cchip.iic[0] = val & 0xffffff;
+ break;
+ case 0x03c0: /* IIC1 */
+ s->cchip.iic[1] = val & 0xffffff;
+ break;
+
+ case 0x0400: /* MPR0 */
+ case 0x0440: /* MPR1 */
+ case 0x0480: /* MPR2 */
+ case 0x04c0: /* MPR3 */
+ /* MPR: Memory Programming Register. */
+ break;
+
+ case 0x0580:
+ /* TTR: TIGbus Timing Register. */
+ /* All sorts of stuff related to interrupt delivery timings. */
+ break;
+ case 0x05c0:
+ /* TDR: TIGbug Device Timing Register. */
+ break;
+
+ case 0x0600:
+ /* DIM2: Device Interrupt Mask Register, CPU2. */
+ s->cchip.dim[2] = val;
+ cpu_irq_change(s->cchip.cpu[2], val & s->cchip.drir);
+ break;
+ case 0x0640:
+ /* DIM3: Device Interrupt Mask Register, CPU3. */
+ s->cchip.dim[3] = val;
+ cpu_irq_change(s->cchip.cpu[3], val & s->cchip.drir);
+ break;
+
+ case 0x0680: /* DIR2 (RO) */
+ case 0x06c0: /* DIR3 (RO) */
+ break;
+
+ case 0x0700: /* IIC2 */
+ s->cchip.iic[2] = val & 0xffffff;
+ break;
+ case 0x0740: /* IIC3 */
+ s->cchip.iic[3] = val & 0xffffff;
+ break;
+
+ case 0x0780:
+ /* PWR: Power Management Control. */
+ break;
+
+ case 0x0c00: /* CMONCTLA */
+ case 0x0c40: /* CMONCTLB */
+ case 0x0c80: /* CMONCNT01 */
+ case 0x0cc0: /* CMONCNT23 */
+ break;
+
+ default:
+ cpu_unassigned_access(current_cpu, addr, true, false, 0, size);
+ return;
+ }
+}
+
+static void dchip_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ /* Skip this. It's all related to DRAM timing and setup. */
+}
+
+static void pchip_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TyphoonState *s = opaque;
+ uint64_t oldval;
+
+ switch (addr) {
+ case 0x0000:
+ /* WSBA0: Window Space Base Address Register. */
+ s->pchip.win[0].wba = val & 0xfff00003u;
+ break;
+ case 0x0040:
+ /* WSBA1 */
+ s->pchip.win[1].wba = val & 0xfff00003u;
+ break;
+ case 0x0080:
+ /* WSBA2 */
+ s->pchip.win[2].wba = val & 0xfff00003u;
+ break;
+ case 0x00c0:
+ /* WSBA3 */
+ s->pchip.win[3].wba = (val & 0x80fff00001ull) | 2;
+ break;
+
+ case 0x0100:
+ /* WSM0: Window Space Mask Register. */
+ s->pchip.win[0].wsm = val & 0xfff00000u;
+ break;
+ case 0x0140:
+ /* WSM1 */
+ s->pchip.win[1].wsm = val & 0xfff00000u;
+ break;
+ case 0x0180:
+ /* WSM2 */
+ s->pchip.win[2].wsm = val & 0xfff00000u;
+ break;
+ case 0x01c0:
+ /* WSM3 */
+ s->pchip.win[3].wsm = val & 0xfff00000u;
+ break;
+
+ case 0x0200:
+ /* TBA0: Translated Base Address Register. */
+ s->pchip.win[0].tba = val & 0x7fffffc00ull;
+ break;
+ case 0x0240:
+ /* TBA1 */
+ s->pchip.win[1].tba = val & 0x7fffffc00ull;
+ break;
+ case 0x0280:
+ /* TBA2 */
+ s->pchip.win[2].tba = val & 0x7fffffc00ull;
+ break;
+ case 0x02c0:
+ /* TBA3 */
+ s->pchip.win[3].tba = val & 0x7fffffc00ull;
+ break;
+
+ case 0x0300:
+ /* PCTL: Pchip Control Register. */
+ oldval = s->pchip.ctl;
+ oldval &= ~0x00001cff0fc7ffull; /* RW fields */
+ oldval |= val & 0x00001cff0fc7ffull;
+ s->pchip.ctl = oldval;
+ break;
+
+ case 0x0340:
+ /* PLAT: Pchip Master Latency Register. */
+ break;
+ case 0x03c0:
+ /* PERROR: Pchip Error Register. */
+ break;
+ case 0x0400:
+ /* PERRMASK: Pchip Error Mask Register. */
+ break;
+ case 0x0440:
+ /* PERRSET: Pchip Error Set Register. */
+ break;
+
+ case 0x0480:
+ /* TLBIV: Translation Buffer Invalidate Virtual Register. */
+ break;
+
+ case 0x04c0:
+ /* TLBIA: Translation Buffer Invalidate All Register (WO). */
+ break;
+
+ case 0x0500:
+ /* PMONCTL */
+ case 0x0540:
+ /* PMONCNT */
+ case 0x0800:
+ /* SPRST */
+ break;
+
+ default:
+ cpu_unassigned_access(current_cpu, addr, true, false, 0, size);
+ return;
+ }
+}
+
+static const MemoryRegionOps cchip_ops = {
+ .read = cchip_read,
+ .write = cchip_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ },
+};
+
+static const MemoryRegionOps dchip_ops = {
+ .read = dchip_read,
+ .write = dchip_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ },
+};
+
+static const MemoryRegionOps pchip_ops = {
+ .read = pchip_read,
+ .write = pchip_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ },
+};
+
+/* A subroutine of typhoon_translate_iommu that builds an IOMMUTLBEntry
+ using the given translated address and mask. */
+static bool make_iommu_tlbe(hwaddr taddr, hwaddr mask, IOMMUTLBEntry *ret)
+{
+ *ret = (IOMMUTLBEntry) {
+ .target_as = &address_space_memory,
+ .translated_addr = taddr,
+ .addr_mask = mask,
+ .perm = IOMMU_RW,
+ };
+ return true;
+}
+
+/* A subroutine of typhoon_translate_iommu that handles scatter-gather
+ translation, given the address of the PTE. */
+static bool pte_translate(hwaddr pte_addr, IOMMUTLBEntry *ret)
+{
+ uint64_t pte = address_space_ldq(&address_space_memory, pte_addr,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+
+ /* Check valid bit. */
+ if ((pte & 1) == 0) {
+ return false;
+ }
+
+ return make_iommu_tlbe((pte & 0x3ffffe) << 12, 0x1fff, ret);
+}
+
+/* A subroutine of typhoon_translate_iommu that handles one of the
+ four single-address-cycle translation windows. */
+static bool window_translate(TyphoonWindow *win, hwaddr addr,
+ IOMMUTLBEntry *ret)
+{
+ uint32_t wba = win->wba;
+ uint64_t wsm = win->wsm;
+ uint64_t tba = win->tba;
+ uint64_t wsm_ext = wsm | 0xfffff;
+
+ /* Check for window disabled. */
+ if ((wba & 1) == 0) {
+ return false;
+ }
+
+ /* Check for window hit. */
+ if ((addr & ~wsm_ext) != (wba & 0xfff00000u)) {
+ return false;
+ }
+
+ if (wba & 2) {
+ /* Scatter-gather translation. */
+ hwaddr pte_addr;
+
+ /* See table 10-6, Generating PTE address for PCI DMA Address. */
+ pte_addr = tba & ~(wsm >> 10);
+ pte_addr |= (addr & (wsm | 0xfe000)) >> 10;
+ return pte_translate(pte_addr, ret);
+ } else {
+ /* Direct-mapped translation. */
+ return make_iommu_tlbe(tba & ~wsm_ext, wsm_ext, ret);
+ }
+}
+
+/* Handle PCI-to-system address translation. */
+/* TODO: A translation failure here ought to set PCI error codes on the
+ Pchip and generate a machine check interrupt. */
+static IOMMUTLBEntry typhoon_translate_iommu(MemoryRegion *iommu, hwaddr addr,
+ bool is_write)
+{
+ TyphoonPchip *pchip = container_of(iommu, TyphoonPchip, iommu);
+ IOMMUTLBEntry ret;
+ int i;
+
+ if (addr <= 0xffffffffu) {
+ /* Single-address cycle. */
+
+ /* Check for the Window Hole, inhibiting matching. */
+ if ((pchip->ctl & 0x20)
+ && addr >= 0x80000
+ && addr <= 0xfffff) {
+ goto failure;
+ }
+
+ /* Check the first three windows. */
+ for (i = 0; i < 3; ++i) {
+ if (window_translate(&pchip->win[i], addr, &ret)) {
+ goto success;
+ }
+ }
+
+ /* Check the fourth window for DAC disable. */
+ if ((pchip->win[3].wba & 0x80000000000ull) == 0
+ && window_translate(&pchip->win[3], addr, &ret)) {
+ goto success;
+ }
+ } else {
+ /* Double-address cycle. */
+
+ if (addr >= 0x10000000000ull && addr < 0x20000000000ull) {
+ /* Check for the DMA monster window. */
+ if (pchip->ctl & 0x40) {
+ /* See 10.1.4.4; in particular <39:35> is ignored. */
+ make_iommu_tlbe(0, 0x007ffffffffull, &ret);
+ goto success;
+ }
+ }
+
+ if (addr >= 0x80000000000ull && addr <= 0xfffffffffffull) {
+ /* Check the fourth window for DAC enable and window enable. */
+ if ((pchip->win[3].wba & 0x80000000001ull) == 0x80000000001ull) {
+ uint64_t pte_addr;
+
+ pte_addr = pchip->win[3].tba & 0x7ffc00000ull;
+ pte_addr |= (addr & 0xffffe000u) >> 10;
+ if (pte_translate(pte_addr, &ret)) {
+ goto success;
+ }
+ }
+ }
+ }
+
+ failure:
+ ret = (IOMMUTLBEntry) { .perm = IOMMU_NONE };
+ success:
+ return ret;
+}
+
+static const MemoryRegionIOMMUOps typhoon_iommu_ops = {
+ .translate = typhoon_translate_iommu,
+};
+
+static AddressSpace *typhoon_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn)
+{
+ TyphoonState *s = opaque;
+ return &s->pchip.iommu_as;
+}
+
+static void typhoon_set_irq(void *opaque, int irq, int level)
+{
+ TyphoonState *s = opaque;
+ uint64_t drir;
+ int i;
+
+ /* Set/Reset the bit in CCHIP.DRIR based on IRQ+LEVEL. */
+ drir = s->cchip.drir;
+ if (level) {
+ drir |= 1ull << irq;
+ } else {
+ drir &= ~(1ull << irq);
+ }
+ s->cchip.drir = drir;
+
+ for (i = 0; i < 4; ++i) {
+ cpu_irq_change(s->cchip.cpu[i], s->cchip.dim[i] & drir);
+ }
+}
+
+static void typhoon_set_isa_irq(void *opaque, int irq, int level)
+{
+ typhoon_set_irq(opaque, 55, level);
+}
+
+static void typhoon_set_timer_irq(void *opaque, int irq, int level)
+{
+ TyphoonState *s = opaque;
+ int i;
+
+ /* Thankfully, the mc146818rtc code doesn't track the IRQ state,
+ and so we don't have to worry about missing interrupts just
+ because we never actually ACK the interrupt. Just ignore any
+ case of the interrupt level going low. */
+ if (level == 0) {
+ return;
+ }
+
+ /* Deliver the interrupt to each CPU, considering each CPU's IIC. */
+ for (i = 0; i < 4; ++i) {
+ AlphaCPU *cpu = s->cchip.cpu[i];
+ if (cpu != NULL) {
+ uint32_t iic = s->cchip.iic[i];
+
+ /* ??? The verbage in Section 10.2.2.10 isn't 100% clear.
+ Bit 24 is the OverFlow bit, RO, and set when the count
+ decrements past 0. When is OF cleared? My guess is that
+ OF is actually cleared when the IIC is written, and that
+ the ICNT field always decrements. At least, that's an
+ interpretation that makes sense, and "allows the CPU to
+ determine exactly how mant interval timer ticks were
+ skipped". At least within the next 4M ticks... */
+
+ iic = ((iic - 1) & 0x1ffffff) | (iic & 0x1000000);
+ s->cchip.iic[i] = iic;
+
+ if (iic & 0x1000000) {
+ /* Set the ITI bit for this cpu. */
+ s->cchip.misc |= 1 << (i + 4);
+ /* And signal the interrupt. */
+ cpu_interrupt(CPU(cpu), CPU_INTERRUPT_TIMER);
+ }
+ }
+ }
+}
+
+static void typhoon_alarm_timer(void *opaque)
+{
+ TyphoonState *s = (TyphoonState *)((uintptr_t)opaque & ~3);
+ int cpu = (uintptr_t)opaque & 3;
+
+ /* Set the ITI bit for this cpu. */
+ s->cchip.misc |= 1 << (cpu + 4);
+ cpu_interrupt(CPU(s->cchip.cpu[cpu]), CPU_INTERRUPT_TIMER);
+}
+
+PCIBus *typhoon_init(ram_addr_t ram_size, ISABus **isa_bus,
+ qemu_irq *p_rtc_irq,
+ AlphaCPU *cpus[4], pci_map_irq_fn sys_map_irq)
+{
+ const uint64_t MB = 1024 * 1024;
+ const uint64_t GB = 1024 * MB;
+ MemoryRegion *addr_space = get_system_memory();
+ DeviceState *dev;
+ TyphoonState *s;
+ PCIHostState *phb;
+ PCIBus *b;
+ int i;
+
+ dev = qdev_create(NULL, TYPE_TYPHOON_PCI_HOST_BRIDGE);
+ qdev_init_nofail(dev);
+
+ s = TYPHOON_PCI_HOST_BRIDGE(dev);
+ phb = PCI_HOST_BRIDGE(dev);
+
+ s->cchip.misc = 0x800000000ull; /* Revision: Typhoon. */
+ s->pchip.win[3].wba = 2; /* Window 3 SG always enabled. */
+
+ /* Remember the CPUs so that we can deliver interrupts to them. */
+ for (i = 0; i < 4; i++) {
+ AlphaCPU *cpu = cpus[i];
+ s->cchip.cpu[i] = cpu;
+ if (cpu != NULL) {
+ cpu->alarm_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ typhoon_alarm_timer,
+ (void *)((uintptr_t)s + i));
+ }
+ }
+
+ *p_rtc_irq = qemu_allocate_irq(typhoon_set_timer_irq, s, 0);
+
+ /* Main memory region, 0x00.0000.0000. Real hardware supports 32GB,
+ but the address space hole reserved at this point is 8TB. */
+ memory_region_allocate_system_memory(&s->ram_region, OBJECT(s), "ram",
+ ram_size);
+ memory_region_add_subregion(addr_space, 0, &s->ram_region);
+
+ /* TIGbus, 0x801.0000.0000, 1GB. */
+ /* ??? The TIGbus is used for delivering interrupts, and access to
+ the flash ROM. I'm not sure that we need to implement it at all. */
+
+ /* Pchip0 CSRs, 0x801.8000.0000, 256MB. */
+ memory_region_init_io(&s->pchip.region, OBJECT(s), &pchip_ops, s, "pchip0",
+ 256*MB);
+ memory_region_add_subregion(addr_space, 0x80180000000ULL,
+ &s->pchip.region);
+
+ /* Cchip CSRs, 0x801.A000.0000, 256MB. */
+ memory_region_init_io(&s->cchip.region, OBJECT(s), &cchip_ops, s, "cchip0",
+ 256*MB);
+ memory_region_add_subregion(addr_space, 0x801a0000000ULL,
+ &s->cchip.region);
+
+ /* Dchip CSRs, 0x801.B000.0000, 256MB. */
+ memory_region_init_io(&s->dchip_region, OBJECT(s), &dchip_ops, s, "dchip0",
+ 256*MB);
+ memory_region_add_subregion(addr_space, 0x801b0000000ULL,
+ &s->dchip_region);
+
+ /* Pchip0 PCI memory, 0x800.0000.0000, 4GB. */
+ memory_region_init(&s->pchip.reg_mem, OBJECT(s), "pci0-mem", 4*GB);
+ memory_region_add_subregion(addr_space, 0x80000000000ULL,
+ &s->pchip.reg_mem);
+
+ /* Pchip0 PCI I/O, 0x801.FC00.0000, 32MB. */
+ memory_region_init_io(&s->pchip.reg_io, OBJECT(s), &alpha_pci_ignore_ops,
+ NULL, "pci0-io", 32*MB);
+ memory_region_add_subregion(addr_space, 0x801fc000000ULL,
+ &s->pchip.reg_io);
+
+ b = pci_register_bus(dev, "pci",
+ typhoon_set_irq, sys_map_irq, s,
+ &s->pchip.reg_mem, &s->pchip.reg_io,
+ 0, 64, TYPE_PCI_BUS);
+ phb->bus = b;
+
+ /* Host memory as seen from the PCI side, via the IOMMU. */
+ memory_region_init_iommu(&s->pchip.iommu, OBJECT(s), &typhoon_iommu_ops,
+ "iommu-typhoon", UINT64_MAX);
+ address_space_init(&s->pchip.iommu_as, &s->pchip.iommu, "pchip0-pci");
+ pci_setup_iommu(b, typhoon_pci_dma_iommu, s);
+
+ /* Pchip0 PCI special/interrupt acknowledge, 0x801.F800.0000, 64MB. */
+ memory_region_init_io(&s->pchip.reg_iack, OBJECT(s), &alpha_pci_iack_ops,
+ b, "pci0-iack", 64*MB);
+ memory_region_add_subregion(addr_space, 0x801f8000000ULL,
+ &s->pchip.reg_iack);
+
+ /* Pchip0 PCI configuration, 0x801.FE00.0000, 16MB. */
+ memory_region_init_io(&s->pchip.reg_conf, OBJECT(s), &alpha_pci_conf1_ops,
+ b, "pci0-conf", 16*MB);
+ memory_region_add_subregion(addr_space, 0x801fe000000ULL,
+ &s->pchip.reg_conf);
+
+ /* For the record, these are the mappings for the second PCI bus.
+ We can get away with not implementing them because we indicate
+ via the Cchip.CSC<PIP> bit that Pchip1 is not present. */
+ /* Pchip1 PCI memory, 0x802.0000.0000, 4GB. */
+ /* Pchip1 CSRs, 0x802.8000.0000, 256MB. */
+ /* Pchip1 PCI special/interrupt acknowledge, 0x802.F800.0000, 64MB. */
+ /* Pchip1 PCI I/O, 0x802.FC00.0000, 32MB. */
+ /* Pchip1 PCI configuration, 0x802.FE00.0000, 16MB. */
+
+ /* Init the ISA bus. */
+ /* ??? Technically there should be a cy82c693ub pci-isa bridge. */
+ {
+ qemu_irq *isa_irqs;
+
+ *isa_bus = isa_bus_new(NULL, get_system_memory(), &s->pchip.reg_io);
+ isa_irqs = i8259_init(*isa_bus,
+ qemu_allocate_irq(typhoon_set_isa_irq, s, 0));
+ isa_bus_irqs(*isa_bus, isa_irqs);
+ }
+
+ return b;
+}
+
+static int typhoon_pcihost_init(SysBusDevice *dev)
+{
+ return 0;
+}
+
+static void typhoon_pcihost_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = typhoon_pcihost_init;
+}
+
+static const TypeInfo typhoon_pcihost_info = {
+ .name = TYPE_TYPHOON_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(TyphoonState),
+ .class_init = typhoon_pcihost_class_init,
+};
+
+static void typhoon_register_types(void)
+{
+ type_register_static(&typhoon_pcihost_info);
+}
+
+type_init(typhoon_register_types)
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
new file mode 100644
index 00000000..cf346c1d
--- /dev/null
+++ b/hw/arm/Makefile.objs
@@ -0,0 +1,15 @@
+obj-y += boot.o collie.o exynos4_boards.o gumstix.o highbank.o
+obj-$(CONFIG_DIGIC) += digic_boards.o
+obj-y += integratorcp.o kzm.o mainstone.o musicpal.o nseries.o
+obj-y += omap_sx1.o palm.o realview.o spitz.o stellaris.o
+obj-y += tosa.o versatilepb.o vexpress.o virt.o xilinx_zynq.o z2.o
+obj-$(CONFIG_ACPI) += virt-acpi-build.o
+obj-y += netduino2.o
+obj-y += sysbus-fdt.o
+
+obj-y += armv7m.o exynos4210.o pxa2xx.o pxa2xx_gpio.o pxa2xx_pic.o
+obj-$(CONFIG_DIGIC) += digic.o
+obj-y += omap1.o omap2.o strongarm.o
+obj-$(CONFIG_ALLWINNER_A10) += allwinner-a10.o cubieboard.o
+obj-$(CONFIG_STM32F205_SOC) += stm32f205_soc.o
+obj-$(CONFIG_XLNX_ZYNQMP) += xlnx-zynqmp.o xlnx-ep108.o
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
new file mode 100644
index 00000000..43dc0a12
--- /dev/null
+++ b/hw/arm/allwinner-a10.c
@@ -0,0 +1,127 @@
+/*
+ * Allwinner A10 SoC emulation
+ *
+ * Copyright (C) 2013 Li Guang
+ * Written by Li Guang <lig.fnst@cn.fujitsu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/devices.h"
+#include "hw/arm/allwinner-a10.h"
+
+static void aw_a10_init(Object *obj)
+{
+ AwA10State *s = AW_A10(obj);
+
+ object_initialize(&s->cpu, sizeof(s->cpu), "cortex-a8-" TYPE_ARM_CPU);
+ object_property_add_child(obj, "cpu", OBJECT(&s->cpu), NULL);
+
+ object_initialize(&s->intc, sizeof(s->intc), TYPE_AW_A10_PIC);
+ qdev_set_parent_bus(DEVICE(&s->intc), sysbus_get_default());
+
+ object_initialize(&s->timer, sizeof(s->timer), TYPE_AW_A10_PIT);
+ qdev_set_parent_bus(DEVICE(&s->timer), sysbus_get_default());
+
+ object_initialize(&s->emac, sizeof(s->emac), TYPE_AW_EMAC);
+ qdev_set_parent_bus(DEVICE(&s->emac), sysbus_get_default());
+ /* FIXME use qdev NIC properties instead of nd_table[] */
+ if (nd_table[0].used) {
+ qemu_check_nic_model(&nd_table[0], TYPE_AW_EMAC);
+ qdev_set_nic_properties(DEVICE(&s->emac), &nd_table[0]);
+ }
+}
+
+static void aw_a10_realize(DeviceState *dev, Error **errp)
+{
+ AwA10State *s = AW_A10(dev);
+ SysBusDevice *sysbusdev;
+ uint8_t i;
+ qemu_irq fiq, irq;
+ Error *err = NULL;
+
+ object_property_set_bool(OBJECT(&s->cpu), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ irq = qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_IRQ);
+ fiq = qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_FIQ);
+
+ object_property_set_bool(OBJECT(&s->intc), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbusdev = SYS_BUS_DEVICE(&s->intc);
+ sysbus_mmio_map(sysbusdev, 0, AW_A10_PIC_REG_BASE);
+ sysbus_connect_irq(sysbusdev, 0, irq);
+ sysbus_connect_irq(sysbusdev, 1, fiq);
+ for (i = 0; i < AW_A10_PIC_INT_NR; i++) {
+ s->irq[i] = qdev_get_gpio_in(DEVICE(&s->intc), i);
+ }
+
+ object_property_set_bool(OBJECT(&s->timer), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbusdev = SYS_BUS_DEVICE(&s->timer);
+ sysbus_mmio_map(sysbusdev, 0, AW_A10_PIT_REG_BASE);
+ sysbus_connect_irq(sysbusdev, 0, s->irq[22]);
+ sysbus_connect_irq(sysbusdev, 1, s->irq[23]);
+ sysbus_connect_irq(sysbusdev, 2, s->irq[24]);
+ sysbus_connect_irq(sysbusdev, 3, s->irq[25]);
+ sysbus_connect_irq(sysbusdev, 4, s->irq[67]);
+ sysbus_connect_irq(sysbusdev, 5, s->irq[68]);
+
+ object_property_set_bool(OBJECT(&s->emac), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbusdev = SYS_BUS_DEVICE(&s->emac);
+ sysbus_mmio_map(sysbusdev, 0, AW_A10_EMAC_BASE);
+ sysbus_connect_irq(sysbusdev, 0, s->irq[55]);
+
+ /* FIXME use a qdev chardev prop instead of serial_hds[] */
+ serial_mm_init(get_system_memory(), AW_A10_UART0_REG_BASE, 2, s->irq[1],
+ 115200, serial_hds[0], DEVICE_NATIVE_ENDIAN);
+}
+
+static void aw_a10_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = aw_a10_realize;
+
+ /*
+ * Reason: creates an ARM CPU, thus use after free(), see
+ * arm_cpu_class_init()
+ */
+ dc->cannot_destroy_with_object_finalize_yet = true;
+}
+
+static const TypeInfo aw_a10_type_info = {
+ .name = TYPE_AW_A10,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(AwA10State),
+ .instance_init = aw_a10_init,
+ .class_init = aw_a10_class_init,
+};
+
+static void aw_a10_register_types(void)
+{
+ type_register_static(&aw_a10_type_info);
+}
+
+type_init(aw_a10_register_types)
diff --git a/hw/arm/armv7m.c b/hw/arm/armv7m.c
new file mode 100644
index 00000000..c6eab6de
--- /dev/null
+++ b/hw/arm/armv7m.c
@@ -0,0 +1,266 @@
+/*
+ * ARMV7M System emulation.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "sysemu/qtest.h"
+#include "qemu/error-report.h"
+
+/* Bitbanded IO. Each word corresponds to a single bit. */
+
+/* Get the byte address of the real memory for a bitband access. */
+static inline uint32_t bitband_addr(void * opaque, uint32_t addr)
+{
+ uint32_t res;
+
+ res = *(uint32_t *)opaque;
+ res |= (addr & 0x1ffffff) >> 5;
+ return res;
+
+}
+
+static uint32_t bitband_readb(void *opaque, hwaddr offset)
+{
+ uint8_t v;
+ cpu_physical_memory_read(bitband_addr(opaque, offset), &v, 1);
+ return (v & (1 << ((offset >> 2) & 7))) != 0;
+}
+
+static void bitband_writeb(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ uint32_t addr;
+ uint8_t mask;
+ uint8_t v;
+ addr = bitband_addr(opaque, offset);
+ mask = (1 << ((offset >> 2) & 7));
+ cpu_physical_memory_read(addr, &v, 1);
+ if (value & 1)
+ v |= mask;
+ else
+ v &= ~mask;
+ cpu_physical_memory_write(addr, &v, 1);
+}
+
+static uint32_t bitband_readw(void *opaque, hwaddr offset)
+{
+ uint32_t addr;
+ uint16_t mask;
+ uint16_t v;
+ addr = bitband_addr(opaque, offset) & ~1;
+ mask = (1 << ((offset >> 2) & 15));
+ mask = tswap16(mask);
+ cpu_physical_memory_read(addr, &v, 2);
+ return (v & mask) != 0;
+}
+
+static void bitband_writew(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ uint32_t addr;
+ uint16_t mask;
+ uint16_t v;
+ addr = bitband_addr(opaque, offset) & ~1;
+ mask = (1 << ((offset >> 2) & 15));
+ mask = tswap16(mask);
+ cpu_physical_memory_read(addr, &v, 2);
+ if (value & 1)
+ v |= mask;
+ else
+ v &= ~mask;
+ cpu_physical_memory_write(addr, &v, 2);
+}
+
+static uint32_t bitband_readl(void *opaque, hwaddr offset)
+{
+ uint32_t addr;
+ uint32_t mask;
+ uint32_t v;
+ addr = bitband_addr(opaque, offset) & ~3;
+ mask = (1 << ((offset >> 2) & 31));
+ mask = tswap32(mask);
+ cpu_physical_memory_read(addr, &v, 4);
+ return (v & mask) != 0;
+}
+
+static void bitband_writel(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ uint32_t addr;
+ uint32_t mask;
+ uint32_t v;
+ addr = bitband_addr(opaque, offset) & ~3;
+ mask = (1 << ((offset >> 2) & 31));
+ mask = tswap32(mask);
+ cpu_physical_memory_read(addr, &v, 4);
+ if (value & 1)
+ v |= mask;
+ else
+ v &= ~mask;
+ cpu_physical_memory_write(addr, &v, 4);
+}
+
+static const MemoryRegionOps bitband_ops = {
+ .old_mmio = {
+ .read = { bitband_readb, bitband_readw, bitband_readl, },
+ .write = { bitband_writeb, bitband_writew, bitband_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+#define TYPE_BITBAND "ARM,bitband-memory"
+#define BITBAND(obj) OBJECT_CHECK(BitBandState, (obj), TYPE_BITBAND)
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t base;
+} BitBandState;
+
+static int bitband_init(SysBusDevice *dev)
+{
+ BitBandState *s = BITBAND(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &bitband_ops, &s->base,
+ "bitband", 0x02000000);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static void armv7m_bitband_init(void)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, TYPE_BITBAND);
+ qdev_prop_set_uint32(dev, "base", 0x20000000);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x22000000);
+
+ dev = qdev_create(NULL, TYPE_BITBAND);
+ qdev_prop_set_uint32(dev, "base", 0x40000000);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x42000000);
+}
+
+/* Board init. */
+
+static void armv7m_reset(void *opaque)
+{
+ ARMCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+/* Init CPU and memory for a v7-M based board.
+ mem_size is in bytes.
+ Returns the NVIC array. */
+
+qemu_irq *armv7m_init(MemoryRegion *system_memory, int mem_size, int num_irq,
+ const char *kernel_filename, const char *cpu_model)
+{
+ ARMCPU *cpu;
+ CPUARMState *env;
+ DeviceState *nvic;
+ qemu_irq *pic = g_new(qemu_irq, num_irq);
+ int image_size;
+ uint64_t entry;
+ uint64_t lowaddr;
+ int i;
+ int big_endian;
+ MemoryRegion *hack = g_new(MemoryRegion, 1);
+
+ if (cpu_model == NULL) {
+ cpu_model = "cortex-m3";
+ }
+ cpu = cpu_arm_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ armv7m_bitband_init();
+
+ nvic = qdev_create(NULL, "armv7m_nvic");
+ qdev_prop_set_uint32(nvic, "num-irq", num_irq);
+ env->nvic = nvic;
+ qdev_init_nofail(nvic);
+ sysbus_connect_irq(SYS_BUS_DEVICE(nvic), 0,
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ));
+ for (i = 0; i < num_irq; i++) {
+ pic[i] = qdev_get_gpio_in(nvic, i);
+ }
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ big_endian = 1;
+#else
+ big_endian = 0;
+#endif
+
+ if (!kernel_filename && !qtest_enabled()) {
+ fprintf(stderr, "Guest image must be specified (using -kernel)\n");
+ exit(1);
+ }
+
+ if (kernel_filename) {
+ image_size = load_elf(kernel_filename, NULL, NULL, &entry, &lowaddr,
+ NULL, big_endian, ELF_MACHINE, 1);
+ if (image_size < 0) {
+ image_size = load_image_targphys(kernel_filename, 0, mem_size);
+ lowaddr = 0;
+ }
+ if (image_size < 0) {
+ error_report("Could not load kernel '%s'", kernel_filename);
+ exit(1);
+ }
+ }
+
+ /* Hack to map an additional page of ram at the top of the address
+ space. This stops qemu complaining about executing code outside RAM
+ when returning from an exception. */
+ memory_region_init_ram(hack, NULL, "armv7m.hack", 0x1000, &error_abort);
+ vmstate_register_ram_global(hack);
+ memory_region_add_subregion(system_memory, 0xfffff000, hack);
+
+ qemu_register_reset(armv7m_reset, cpu);
+ return pic;
+}
+
+static Property bitband_properties[] = {
+ DEFINE_PROP_UINT32("base", BitBandState, base, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void bitband_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = bitband_init;
+ dc->props = bitband_properties;
+}
+
+static const TypeInfo bitband_info = {
+ .name = TYPE_BITBAND,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BitBandState),
+ .class_init = bitband_class_init,
+};
+
+static void armv7m_register_types(void)
+{
+ type_register_static(&bitband_info);
+}
+
+type_init(armv7m_register_types)
diff --git a/hw/arm/boot.c b/hw/arm/boot.c
new file mode 100644
index 00000000..5b969cda
--- /dev/null
+++ b/hw/arm/boot.c
@@ -0,0 +1,805 @@
+/*
+ * ARM kernel loader.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "config.h"
+#include "hw/hw.h"
+#include "hw/arm/arm.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "sysemu/device_tree.h"
+#include "qemu/config-file.h"
+#include "exec/address-spaces.h"
+
+/* Kernel boot protocol is specified in the kernel docs
+ * Documentation/arm/Booting and Documentation/arm64/booting.txt
+ * They have different preferred image load offsets from system RAM base.
+ */
+#define KERNEL_ARGS_ADDR 0x100
+#define KERNEL_LOAD_ADDR 0x00010000
+#define KERNEL64_LOAD_ADDR 0x00080000
+
+typedef enum {
+ FIXUP_NONE = 0, /* do nothing */
+ FIXUP_TERMINATOR, /* end of insns */
+ FIXUP_BOARDID, /* overwrite with board ID number */
+ FIXUP_ARGPTR, /* overwrite with pointer to kernel args */
+ FIXUP_ENTRYPOINT, /* overwrite with kernel entry point */
+ FIXUP_GIC_CPU_IF, /* overwrite with GIC CPU interface address */
+ FIXUP_BOOTREG, /* overwrite with boot register address */
+ FIXUP_DSB, /* overwrite with correct DSB insn for cpu */
+ FIXUP_MAX,
+} FixupType;
+
+typedef struct ARMInsnFixup {
+ uint32_t insn;
+ FixupType fixup;
+} ARMInsnFixup;
+
+static const ARMInsnFixup bootloader_aarch64[] = {
+ { 0x580000c0 }, /* ldr x0, arg ; Load the lower 32-bits of DTB */
+ { 0xaa1f03e1 }, /* mov x1, xzr */
+ { 0xaa1f03e2 }, /* mov x2, xzr */
+ { 0xaa1f03e3 }, /* mov x3, xzr */
+ { 0x58000084 }, /* ldr x4, entry ; Load the lower 32-bits of kernel entry */
+ { 0xd61f0080 }, /* br x4 ; Jump to the kernel entry point */
+ { 0, FIXUP_ARGPTR }, /* arg: .word @DTB Lower 32-bits */
+ { 0 }, /* .word @DTB Higher 32-bits */
+ { 0, FIXUP_ENTRYPOINT }, /* entry: .word @Kernel Entry Lower 32-bits */
+ { 0 }, /* .word @Kernel Entry Higher 32-bits */
+ { 0, FIXUP_TERMINATOR }
+};
+
+/* The worlds second smallest bootloader. Set r0-r2, then jump to kernel. */
+static const ARMInsnFixup bootloader[] = {
+ { 0xe3a00000 }, /* mov r0, #0 */
+ { 0xe59f1004 }, /* ldr r1, [pc, #4] */
+ { 0xe59f2004 }, /* ldr r2, [pc, #4] */
+ { 0xe59ff004 }, /* ldr pc, [pc, #4] */
+ { 0, FIXUP_BOARDID },
+ { 0, FIXUP_ARGPTR },
+ { 0, FIXUP_ENTRYPOINT },
+ { 0, FIXUP_TERMINATOR }
+};
+
+/* Handling for secondary CPU boot in a multicore system.
+ * Unlike the uniprocessor/primary CPU boot, this is platform
+ * dependent. The default code here is based on the secondary
+ * CPU boot protocol used on realview/vexpress boards, with
+ * some parameterisation to increase its flexibility.
+ * QEMU platform models for which this code is not appropriate
+ * should override write_secondary_boot and secondary_cpu_reset_hook
+ * instead.
+ *
+ * This code enables the interrupt controllers for the secondary
+ * CPUs and then puts all the secondary CPUs into a loop waiting
+ * for an interprocessor interrupt and polling a configurable
+ * location for the kernel secondary CPU entry point.
+ */
+#define DSB_INSN 0xf57ff04f
+#define CP15_DSB_INSN 0xee070f9a /* mcr cp15, 0, r0, c7, c10, 4 */
+
+static const ARMInsnFixup smpboot[] = {
+ { 0xe59f2028 }, /* ldr r2, gic_cpu_if */
+ { 0xe59f0028 }, /* ldr r0, bootreg_addr */
+ { 0xe3a01001 }, /* mov r1, #1 */
+ { 0xe5821000 }, /* str r1, [r2] - set GICC_CTLR.Enable */
+ { 0xe3a010ff }, /* mov r1, #0xff */
+ { 0xe5821004 }, /* str r1, [r2, 4] - set GIC_PMR.Priority to 0xff */
+ { 0, FIXUP_DSB }, /* dsb */
+ { 0xe320f003 }, /* wfi */
+ { 0xe5901000 }, /* ldr r1, [r0] */
+ { 0xe1110001 }, /* tst r1, r1 */
+ { 0x0afffffb }, /* beq <wfi> */
+ { 0xe12fff11 }, /* bx r1 */
+ { 0, FIXUP_GIC_CPU_IF }, /* gic_cpu_if: .word 0x.... */
+ { 0, FIXUP_BOOTREG }, /* bootreg_addr: .word 0x.... */
+ { 0, FIXUP_TERMINATOR }
+};
+
+static void write_bootloader(const char *name, hwaddr addr,
+ const ARMInsnFixup *insns, uint32_t *fixupcontext)
+{
+ /* Fix up the specified bootloader fragment and write it into
+ * guest memory using rom_add_blob_fixed(). fixupcontext is
+ * an array giving the values to write in for the fixup types
+ * which write a value into the code array.
+ */
+ int i, len;
+ uint32_t *code;
+
+ len = 0;
+ while (insns[len].fixup != FIXUP_TERMINATOR) {
+ len++;
+ }
+
+ code = g_new0(uint32_t, len);
+
+ for (i = 0; i < len; i++) {
+ uint32_t insn = insns[i].insn;
+ FixupType fixup = insns[i].fixup;
+
+ switch (fixup) {
+ case FIXUP_NONE:
+ break;
+ case FIXUP_BOARDID:
+ case FIXUP_ARGPTR:
+ case FIXUP_ENTRYPOINT:
+ case FIXUP_GIC_CPU_IF:
+ case FIXUP_BOOTREG:
+ case FIXUP_DSB:
+ insn = fixupcontext[fixup];
+ break;
+ default:
+ abort();
+ }
+ code[i] = tswap32(insn);
+ }
+
+ rom_add_blob_fixed(name, code, len * sizeof(uint32_t), addr);
+
+ g_free(code);
+}
+
+static void default_write_secondary(ARMCPU *cpu,
+ const struct arm_boot_info *info)
+{
+ uint32_t fixupcontext[FIXUP_MAX];
+
+ fixupcontext[FIXUP_GIC_CPU_IF] = info->gic_cpu_if_addr;
+ fixupcontext[FIXUP_BOOTREG] = info->smp_bootreg_addr;
+ if (arm_feature(&cpu->env, ARM_FEATURE_V7)) {
+ fixupcontext[FIXUP_DSB] = DSB_INSN;
+ } else {
+ fixupcontext[FIXUP_DSB] = CP15_DSB_INSN;
+ }
+
+ write_bootloader("smpboot", info->smp_loader_start,
+ smpboot, fixupcontext);
+}
+
+static void default_reset_secondary(ARMCPU *cpu,
+ const struct arm_boot_info *info)
+{
+ CPUState *cs = CPU(cpu);
+
+ address_space_stl_notdirty(&address_space_memory, info->smp_bootreg_addr,
+ 0, MEMTXATTRS_UNSPECIFIED, NULL);
+ cpu_set_pc(cs, info->smp_loader_start);
+}
+
+static inline bool have_dtb(const struct arm_boot_info *info)
+{
+ return info->dtb_filename || info->get_dtb;
+}
+
+#define WRITE_WORD(p, value) do { \
+ address_space_stl_notdirty(&address_space_memory, p, value, \
+ MEMTXATTRS_UNSPECIFIED, NULL); \
+ p += 4; \
+} while (0)
+
+static void set_kernel_args(const struct arm_boot_info *info)
+{
+ int initrd_size = info->initrd_size;
+ hwaddr base = info->loader_start;
+ hwaddr p;
+
+ p = base + KERNEL_ARGS_ADDR;
+ /* ATAG_CORE */
+ WRITE_WORD(p, 5);
+ WRITE_WORD(p, 0x54410001);
+ WRITE_WORD(p, 1);
+ WRITE_WORD(p, 0x1000);
+ WRITE_WORD(p, 0);
+ /* ATAG_MEM */
+ /* TODO: handle multiple chips on one ATAG list */
+ WRITE_WORD(p, 4);
+ WRITE_WORD(p, 0x54410002);
+ WRITE_WORD(p, info->ram_size);
+ WRITE_WORD(p, info->loader_start);
+ if (initrd_size) {
+ /* ATAG_INITRD2 */
+ WRITE_WORD(p, 4);
+ WRITE_WORD(p, 0x54420005);
+ WRITE_WORD(p, info->initrd_start);
+ WRITE_WORD(p, initrd_size);
+ }
+ if (info->kernel_cmdline && *info->kernel_cmdline) {
+ /* ATAG_CMDLINE */
+ int cmdline_size;
+
+ cmdline_size = strlen(info->kernel_cmdline);
+ cpu_physical_memory_write(p + 8, info->kernel_cmdline,
+ cmdline_size + 1);
+ cmdline_size = (cmdline_size >> 2) + 1;
+ WRITE_WORD(p, cmdline_size + 2);
+ WRITE_WORD(p, 0x54410009);
+ p += cmdline_size * 4;
+ }
+ if (info->atag_board) {
+ /* ATAG_BOARD */
+ int atag_board_len;
+ uint8_t atag_board_buf[0x1000];
+
+ atag_board_len = (info->atag_board(info, atag_board_buf) + 3) & ~3;
+ WRITE_WORD(p, (atag_board_len + 8) >> 2);
+ WRITE_WORD(p, 0x414f4d50);
+ cpu_physical_memory_write(p, atag_board_buf, atag_board_len);
+ p += atag_board_len;
+ }
+ /* ATAG_END */
+ WRITE_WORD(p, 0);
+ WRITE_WORD(p, 0);
+}
+
+static void set_kernel_args_old(const struct arm_boot_info *info)
+{
+ hwaddr p;
+ const char *s;
+ int initrd_size = info->initrd_size;
+ hwaddr base = info->loader_start;
+
+ /* see linux/include/asm-arm/setup.h */
+ p = base + KERNEL_ARGS_ADDR;
+ /* page_size */
+ WRITE_WORD(p, 4096);
+ /* nr_pages */
+ WRITE_WORD(p, info->ram_size / 4096);
+ /* ramdisk_size */
+ WRITE_WORD(p, 0);
+#define FLAG_READONLY 1
+#define FLAG_RDLOAD 4
+#define FLAG_RDPROMPT 8
+ /* flags */
+ WRITE_WORD(p, FLAG_READONLY | FLAG_RDLOAD | FLAG_RDPROMPT);
+ /* rootdev */
+ WRITE_WORD(p, (31 << 8) | 0); /* /dev/mtdblock0 */
+ /* video_num_cols */
+ WRITE_WORD(p, 0);
+ /* video_num_rows */
+ WRITE_WORD(p, 0);
+ /* video_x */
+ WRITE_WORD(p, 0);
+ /* video_y */
+ WRITE_WORD(p, 0);
+ /* memc_control_reg */
+ WRITE_WORD(p, 0);
+ /* unsigned char sounddefault */
+ /* unsigned char adfsdrives */
+ /* unsigned char bytes_per_char_h */
+ /* unsigned char bytes_per_char_v */
+ WRITE_WORD(p, 0);
+ /* pages_in_bank[4] */
+ WRITE_WORD(p, 0);
+ WRITE_WORD(p, 0);
+ WRITE_WORD(p, 0);
+ WRITE_WORD(p, 0);
+ /* pages_in_vram */
+ WRITE_WORD(p, 0);
+ /* initrd_start */
+ if (initrd_size) {
+ WRITE_WORD(p, info->initrd_start);
+ } else {
+ WRITE_WORD(p, 0);
+ }
+ /* initrd_size */
+ WRITE_WORD(p, initrd_size);
+ /* rd_start */
+ WRITE_WORD(p, 0);
+ /* system_rev */
+ WRITE_WORD(p, 0);
+ /* system_serial_low */
+ WRITE_WORD(p, 0);
+ /* system_serial_high */
+ WRITE_WORD(p, 0);
+ /* mem_fclk_21285 */
+ WRITE_WORD(p, 0);
+ /* zero unused fields */
+ while (p < base + KERNEL_ARGS_ADDR + 256 + 1024) {
+ WRITE_WORD(p, 0);
+ }
+ s = info->kernel_cmdline;
+ if (s) {
+ cpu_physical_memory_write(p, s, strlen(s) + 1);
+ } else {
+ WRITE_WORD(p, 0);
+ }
+}
+
+/**
+ * load_dtb() - load a device tree binary image into memory
+ * @addr: the address to load the image at
+ * @binfo: struct describing the boot environment
+ * @addr_limit: upper limit of the available memory area at @addr
+ *
+ * Load a device tree supplied by the machine or by the user with the
+ * '-dtb' command line option, and put it at offset @addr in target
+ * memory.
+ *
+ * If @addr_limit contains a meaningful value (i.e., it is strictly greater
+ * than @addr), the device tree is only loaded if its size does not exceed
+ * the limit.
+ *
+ * Returns: the size of the device tree image on success,
+ * 0 if the image size exceeds the limit,
+ * -1 on errors.
+ *
+ * Note: Must not be called unless have_dtb(binfo) is true.
+ */
+static int load_dtb(hwaddr addr, const struct arm_boot_info *binfo,
+ hwaddr addr_limit)
+{
+ void *fdt = NULL;
+ int size, rc;
+ uint32_t acells, scells;
+
+ if (binfo->dtb_filename) {
+ char *filename;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename);
+ if (!filename) {
+ fprintf(stderr, "Couldn't open dtb file %s\n", binfo->dtb_filename);
+ goto fail;
+ }
+
+ fdt = load_device_tree(filename, &size);
+ if (!fdt) {
+ fprintf(stderr, "Couldn't open dtb file %s\n", filename);
+ g_free(filename);
+ goto fail;
+ }
+ g_free(filename);
+ } else {
+ fdt = binfo->get_dtb(binfo, &size);
+ if (!fdt) {
+ fprintf(stderr, "Board was unable to create a dtb blob\n");
+ goto fail;
+ }
+ }
+
+ if (addr_limit > addr && size > (addr_limit - addr)) {
+ /* Installing the device tree blob at addr would exceed addr_limit.
+ * Whether this constitutes failure is up to the caller to decide,
+ * so just return 0 as size, i.e., no error.
+ */
+ g_free(fdt);
+ return 0;
+ }
+
+ acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells");
+ scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells");
+ if (acells == 0 || scells == 0) {
+ fprintf(stderr, "dtb file invalid (#address-cells or #size-cells 0)\n");
+ goto fail;
+ }
+
+ if (scells < 2 && binfo->ram_size >= (1ULL << 32)) {
+ /* This is user error so deserves a friendlier error message
+ * than the failure of setprop_sized_cells would provide
+ */
+ fprintf(stderr, "qemu: dtb file not compatible with "
+ "RAM size > 4GB\n");
+ goto fail;
+ }
+
+ rc = qemu_fdt_setprop_sized_cells(fdt, "/memory", "reg",
+ acells, binfo->loader_start,
+ scells, binfo->ram_size);
+ if (rc < 0) {
+ fprintf(stderr, "couldn't set /memory/reg\n");
+ goto fail;
+ }
+
+ if (binfo->kernel_cmdline && *binfo->kernel_cmdline) {
+ rc = qemu_fdt_setprop_string(fdt, "/chosen", "bootargs",
+ binfo->kernel_cmdline);
+ if (rc < 0) {
+ fprintf(stderr, "couldn't set /chosen/bootargs\n");
+ goto fail;
+ }
+ }
+
+ if (binfo->initrd_size) {
+ rc = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-start",
+ binfo->initrd_start);
+ if (rc < 0) {
+ fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n");
+ goto fail;
+ }
+
+ rc = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-end",
+ binfo->initrd_start + binfo->initrd_size);
+ if (rc < 0) {
+ fprintf(stderr, "couldn't set /chosen/linux,initrd-end\n");
+ goto fail;
+ }
+ }
+
+ if (binfo->modify_dtb) {
+ binfo->modify_dtb(binfo, fdt);
+ }
+
+ qemu_fdt_dumpdtb(fdt, size);
+
+ /* Put the DTB into the memory map as a ROM image: this will ensure
+ * the DTB is copied again upon reset, even if addr points into RAM.
+ */
+ rom_add_blob_fixed("dtb", fdt, size, addr);
+
+ g_free(fdt);
+
+ return size;
+
+fail:
+ g_free(fdt);
+ return -1;
+}
+
+static void do_cpu_reset(void *opaque)
+{
+ ARMCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+ CPUARMState *env = &cpu->env;
+ const struct arm_boot_info *info = env->boot_info;
+
+ cpu_reset(cs);
+ if (info) {
+ if (!info->is_linux) {
+ /* Jump to the entry point. */
+ uint64_t entry = info->entry;
+
+ if (!env->aarch64) {
+ env->thumb = info->entry & 1;
+ entry &= 0xfffffffe;
+ }
+ cpu_set_pc(cs, entry);
+ } else {
+ /* If we are booting Linux then we need to check whether we are
+ * booting into secure or non-secure state and adjust the state
+ * accordingly. Out of reset, ARM is defined to be in secure state
+ * (SCR.NS = 0), we change that here if non-secure boot has been
+ * requested.
+ */
+ if (arm_feature(env, ARM_FEATURE_EL3)) {
+ /* AArch64 is defined to come out of reset into EL3 if enabled.
+ * If we are booting Linux then we need to adjust our EL as
+ * Linux expects us to be in EL2 or EL1. AArch32 resets into
+ * SVC, which Linux expects, so no privilege/exception level to
+ * adjust.
+ */
+ if (env->aarch64) {
+ if (arm_feature(env, ARM_FEATURE_EL2)) {
+ env->pstate = PSTATE_MODE_EL2h;
+ } else {
+ env->pstate = PSTATE_MODE_EL1h;
+ }
+ }
+
+ /* Set to non-secure if not a secure boot */
+ if (!info->secure_boot) {
+ /* Linux expects non-secure state */
+ env->cp15.scr_el3 |= SCR_NS;
+ }
+ }
+
+ if (cs == first_cpu) {
+ cpu_set_pc(cs, info->loader_start);
+
+ if (!have_dtb(info)) {
+ if (old_param) {
+ set_kernel_args_old(info);
+ } else {
+ set_kernel_args(info);
+ }
+ }
+ } else {
+ info->secondary_cpu_reset_hook(cpu, info);
+ }
+ }
+ }
+}
+
+/**
+ * load_image_to_fw_cfg() - Load an image file into an fw_cfg entry identified
+ * by key.
+ * @fw_cfg: The firmware config instance to store the data in.
+ * @size_key: The firmware config key to store the size of the loaded
+ * data under, with fw_cfg_add_i32().
+ * @data_key: The firmware config key to store the loaded data under,
+ * with fw_cfg_add_bytes().
+ * @image_name: The name of the image file to load. If it is NULL, the
+ * function returns without doing anything.
+ * @try_decompress: Whether the image should be decompressed (gunzipped) before
+ * adding it to fw_cfg. If decompression fails, the image is
+ * loaded as-is.
+ *
+ * In case of failure, the function prints an error message to stderr and the
+ * process exits with status 1.
+ */
+static void load_image_to_fw_cfg(FWCfgState *fw_cfg, uint16_t size_key,
+ uint16_t data_key, const char *image_name,
+ bool try_decompress)
+{
+ size_t size = -1;
+ uint8_t *data;
+
+ if (image_name == NULL) {
+ return;
+ }
+
+ if (try_decompress) {
+ size = load_image_gzipped_buffer(image_name,
+ LOAD_IMAGE_MAX_GUNZIP_BYTES, &data);
+ }
+
+ if (size == (size_t)-1) {
+ gchar *contents;
+ gsize length;
+
+ if (!g_file_get_contents(image_name, &contents, &length, NULL)) {
+ fprintf(stderr, "failed to load \"%s\"\n", image_name);
+ exit(1);
+ }
+ size = length;
+ data = (uint8_t *)contents;
+ }
+
+ fw_cfg_add_i32(fw_cfg, size_key, size);
+ fw_cfg_add_bytes(fw_cfg, data_key, data, size);
+}
+
+static void arm_load_kernel_notify(Notifier *notifier, void *data)
+{
+ CPUState *cs;
+ int kernel_size;
+ int initrd_size;
+ int is_linux = 0;
+ uint64_t elf_entry, elf_low_addr, elf_high_addr;
+ int elf_machine;
+ hwaddr entry, kernel_load_offset;
+ int big_endian;
+ static const ARMInsnFixup *primary_loader;
+ ArmLoadKernelNotifier *n = DO_UPCAST(ArmLoadKernelNotifier,
+ notifier, notifier);
+ ARMCPU *cpu = n->cpu;
+ struct arm_boot_info *info =
+ container_of(n, struct arm_boot_info, load_kernel_notifier);
+
+ /* Load the kernel. */
+ if (!info->kernel_filename || info->firmware_loaded) {
+
+ if (have_dtb(info)) {
+ /* If we have a device tree blob, but no kernel to supply it to (or
+ * the kernel is supposed to be loaded by the bootloader), copy the
+ * DTB to the base of RAM for the bootloader to pick up.
+ */
+ if (load_dtb(info->loader_start, info, 0) < 0) {
+ exit(1);
+ }
+ }
+
+ if (info->kernel_filename) {
+ FWCfgState *fw_cfg;
+ bool try_decompressing_kernel;
+
+ fw_cfg = fw_cfg_find();
+ try_decompressing_kernel = arm_feature(&cpu->env,
+ ARM_FEATURE_AARCH64);
+
+ /* Expose the kernel, the command line, and the initrd in fw_cfg.
+ * We don't process them here at all, it's all left to the
+ * firmware.
+ */
+ load_image_to_fw_cfg(fw_cfg,
+ FW_CFG_KERNEL_SIZE, FW_CFG_KERNEL_DATA,
+ info->kernel_filename,
+ try_decompressing_kernel);
+ load_image_to_fw_cfg(fw_cfg,
+ FW_CFG_INITRD_SIZE, FW_CFG_INITRD_DATA,
+ info->initrd_filename, false);
+
+ if (info->kernel_cmdline) {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE,
+ strlen(info->kernel_cmdline) + 1);
+ fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA,
+ info->kernel_cmdline);
+ }
+ }
+
+ /* We will start from address 0 (typically a boot ROM image) in the
+ * same way as hardware.
+ */
+ return;
+ }
+
+ if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) {
+ primary_loader = bootloader_aarch64;
+ kernel_load_offset = KERNEL64_LOAD_ADDR;
+ elf_machine = EM_AARCH64;
+ } else {
+ primary_loader = bootloader;
+ kernel_load_offset = KERNEL_LOAD_ADDR;
+ elf_machine = EM_ARM;
+ }
+
+ info->dtb_filename = qemu_opt_get(qemu_get_machine_opts(), "dtb");
+
+ if (!info->secondary_cpu_reset_hook) {
+ info->secondary_cpu_reset_hook = default_reset_secondary;
+ }
+ if (!info->write_secondary_boot) {
+ info->write_secondary_boot = default_write_secondary;
+ }
+
+ if (info->nb_cpus == 0)
+ info->nb_cpus = 1;
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ big_endian = 1;
+#else
+ big_endian = 0;
+#endif
+
+ /* We want to put the initrd far enough into RAM that when the
+ * kernel is uncompressed it will not clobber the initrd. However
+ * on boards without much RAM we must ensure that we still leave
+ * enough room for a decent sized initrd, and on boards with large
+ * amounts of RAM we must avoid the initrd being so far up in RAM
+ * that it is outside lowmem and inaccessible to the kernel.
+ * So for boards with less than 256MB of RAM we put the initrd
+ * halfway into RAM, and for boards with 256MB of RAM or more we put
+ * the initrd at 128MB.
+ */
+ info->initrd_start = info->loader_start +
+ MIN(info->ram_size / 2, 128 * 1024 * 1024);
+
+ /* Assume that raw images are linux kernels, and ELF images are not. */
+ kernel_size = load_elf(info->kernel_filename, NULL, NULL, &elf_entry,
+ &elf_low_addr, &elf_high_addr, big_endian,
+ elf_machine, 1);
+ if (kernel_size > 0 && have_dtb(info)) {
+ /* If there is still some room left at the base of RAM, try and put
+ * the DTB there like we do for images loaded with -bios or -pflash.
+ */
+ if (elf_low_addr > info->loader_start
+ || elf_high_addr < info->loader_start) {
+ /* Pass elf_low_addr as address limit to load_dtb if it may be
+ * pointing into RAM, otherwise pass '0' (no limit)
+ */
+ if (elf_low_addr < info->loader_start) {
+ elf_low_addr = 0;
+ }
+ if (load_dtb(info->loader_start, info, elf_low_addr) < 0) {
+ exit(1);
+ }
+ }
+ }
+ entry = elf_entry;
+ if (kernel_size < 0) {
+ kernel_size = load_uimage(info->kernel_filename, &entry, NULL,
+ &is_linux, NULL, NULL);
+ }
+ /* On aarch64, it's the bootloader's job to uncompress the kernel. */
+ if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64) && kernel_size < 0) {
+ entry = info->loader_start + kernel_load_offset;
+ kernel_size = load_image_gzipped(info->kernel_filename, entry,
+ info->ram_size - kernel_load_offset);
+ is_linux = 1;
+ }
+ if (kernel_size < 0) {
+ entry = info->loader_start + kernel_load_offset;
+ kernel_size = load_image_targphys(info->kernel_filename, entry,
+ info->ram_size - kernel_load_offset);
+ is_linux = 1;
+ }
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ info->kernel_filename);
+ exit(1);
+ }
+ info->entry = entry;
+ if (is_linux) {
+ uint32_t fixupcontext[FIXUP_MAX];
+
+ if (info->initrd_filename) {
+ initrd_size = load_ramdisk(info->initrd_filename,
+ info->initrd_start,
+ info->ram_size -
+ info->initrd_start);
+ if (initrd_size < 0) {
+ initrd_size = load_image_targphys(info->initrd_filename,
+ info->initrd_start,
+ info->ram_size -
+ info->initrd_start);
+ }
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initrd '%s'\n",
+ info->initrd_filename);
+ exit(1);
+ }
+ } else {
+ initrd_size = 0;
+ }
+ info->initrd_size = initrd_size;
+
+ fixupcontext[FIXUP_BOARDID] = info->board_id;
+
+ /* for device tree boot, we pass the DTB directly in r2. Otherwise
+ * we point to the kernel args.
+ */
+ if (have_dtb(info)) {
+ hwaddr align;
+ hwaddr dtb_start;
+
+ if (elf_machine == EM_AARCH64) {
+ /*
+ * Some AArch64 kernels on early bootup map the fdt region as
+ *
+ * [ ALIGN_DOWN(fdt, 2MB) ... ALIGN_DOWN(fdt, 2MB) + 2MB ]
+ *
+ * Let's play safe and prealign it to 2MB to give us some space.
+ */
+ align = 2 * 1024 * 1024;
+ } else {
+ /*
+ * Some 32bit kernels will trash anything in the 4K page the
+ * initrd ends in, so make sure the DTB isn't caught up in that.
+ */
+ align = 4096;
+ }
+
+ /* Place the DTB after the initrd in memory with alignment. */
+ dtb_start = QEMU_ALIGN_UP(info->initrd_start + initrd_size, align);
+ if (load_dtb(dtb_start, info, 0) < 0) {
+ exit(1);
+ }
+ fixupcontext[FIXUP_ARGPTR] = dtb_start;
+ } else {
+ fixupcontext[FIXUP_ARGPTR] = info->loader_start + KERNEL_ARGS_ADDR;
+ if (info->ram_size >= (1ULL << 32)) {
+ fprintf(stderr, "qemu: RAM size must be less than 4GB to boot"
+ " Linux kernel using ATAGS (try passing a device tree"
+ " using -dtb)\n");
+ exit(1);
+ }
+ }
+ fixupcontext[FIXUP_ENTRYPOINT] = entry;
+
+ write_bootloader("bootloader", info->loader_start,
+ primary_loader, fixupcontext);
+
+ if (info->nb_cpus > 1) {
+ info->write_secondary_boot(cpu, info);
+ }
+ }
+ info->is_linux = is_linux;
+
+ for (cs = CPU(cpu); cs; cs = CPU_NEXT(cs)) {
+ ARM_CPU(cs)->env.boot_info = info;
+ }
+}
+
+void arm_load_kernel(ARMCPU *cpu, struct arm_boot_info *info)
+{
+ CPUState *cs;
+
+ info->load_kernel_notifier.cpu = cpu;
+ info->load_kernel_notifier.notifier.notify = arm_load_kernel_notify;
+ qemu_add_machine_init_done_notifier(&info->load_kernel_notifier.notifier);
+
+ /* CPU objects (unlike devices) are not automatically reset on system
+ * reset, so we must always register a handler to do so. If we're
+ * actually loading a kernel, the handler is also responsible for
+ * arranging that we start it correctly.
+ */
+ for (cs = CPU(cpu); cs; cs = CPU_NEXT(cs)) {
+ qemu_register_reset(do_cpu_reset, ARM_CPU(cs));
+ }
+}
diff --git a/hw/arm/collie.c b/hw/arm/collie.c
new file mode 100644
index 00000000..6c9b82fc
--- /dev/null
+++ b/hw/arm/collie.c
@@ -0,0 +1,72 @@
+/*
+ * SA-1110-based Sharp Zaurus SL-5500 platform.
+ *
+ * Copyright (C) 2011 Dmitry Eremin-Solenikov
+ *
+ * This code is licensed under GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/boards.h"
+#include "hw/devices.h"
+#include "strongarm.h"
+#include "hw/arm/arm.h"
+#include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+
+static struct arm_boot_info collie_binfo = {
+ .loader_start = SA_SDCS0,
+ .ram_size = 0x20000000,
+};
+
+static void collie_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ StrongARMState *s;
+ DriveInfo *dinfo;
+ MemoryRegion *sysmem = get_system_memory();
+
+ if (!cpu_model) {
+ cpu_model = "sa1110";
+ }
+
+ s = sa1110_init(sysmem, collie_binfo.ram_size, cpu_model);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ pflash_cfi01_register(SA_CS0, NULL, "collie.fl1", 0x02000000,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ (64 * 1024), 512, 4, 0x00, 0x00, 0x00, 0x00, 0);
+
+ dinfo = drive_get(IF_PFLASH, 0, 1);
+ pflash_cfi01_register(SA_CS1, NULL, "collie.fl2", 0x02000000,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ (64 * 1024), 512, 4, 0x00, 0x00, 0x00, 0x00, 0);
+
+ sysbus_create_simple("scoop", 0x40800000, NULL);
+
+ collie_binfo.kernel_filename = kernel_filename;
+ collie_binfo.kernel_cmdline = kernel_cmdline;
+ collie_binfo.initrd_filename = initrd_filename;
+ collie_binfo.board_id = 0x208;
+ arm_load_kernel(s->cpu, &collie_binfo);
+}
+
+static QEMUMachine collie_machine = {
+ .name = "collie",
+ .desc = "Collie PDA (SA-1110)",
+ .init = collie_init,
+};
+
+static void collie_machine_init(void)
+{
+ qemu_register_machine(&collie_machine);
+}
+
+machine_init(collie_machine_init)
diff --git a/hw/arm/cubieboard.c b/hw/arm/cubieboard.c
new file mode 100644
index 00000000..1582250e
--- /dev/null
+++ b/hw/arm/cubieboard.c
@@ -0,0 +1,89 @@
+/*
+ * cubieboard emulation
+ *
+ * Copyright (C) 2013 Li Guang
+ * Written by Li Guang <lig.fnst@cn.fujitsu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "hw/arm/allwinner-a10.h"
+
+static struct arm_boot_info cubieboard_binfo = {
+ .loader_start = AW_A10_SDRAM_BASE,
+ .board_id = 0x1008,
+};
+
+typedef struct CubieBoardState {
+ AwA10State *a10;
+ MemoryRegion sdram;
+} CubieBoardState;
+
+static void cubieboard_init(MachineState *machine)
+{
+ CubieBoardState *s = g_new(CubieBoardState, 1);
+ Error *err = NULL;
+
+ s->a10 = AW_A10(object_new(TYPE_AW_A10));
+
+ object_property_set_int(OBJECT(&s->a10->emac), 1, "phy-addr", &err);
+ if (err != NULL) {
+ error_report("Couldn't set phy address: %s", error_get_pretty(err));
+ exit(1);
+ }
+
+ object_property_set_int(OBJECT(&s->a10->timer), 32768, "clk0-freq", &err);
+ if (err != NULL) {
+ error_report("Couldn't set clk0 frequency: %s", error_get_pretty(err));
+ exit(1);
+ }
+
+ object_property_set_int(OBJECT(&s->a10->timer), 24000000, "clk1-freq",
+ &err);
+ if (err != NULL) {
+ error_report("Couldn't set clk1 frequency: %s", error_get_pretty(err));
+ exit(1);
+ }
+
+ object_property_set_bool(OBJECT(s->a10), true, "realized", &err);
+ if (err != NULL) {
+ error_report("Couldn't realize Allwinner A10: %s",
+ error_get_pretty(err));
+ exit(1);
+ }
+
+ memory_region_allocate_system_memory(&s->sdram, NULL, "cubieboard.ram",
+ machine->ram_size);
+ memory_region_add_subregion(get_system_memory(), AW_A10_SDRAM_BASE,
+ &s->sdram);
+
+ cubieboard_binfo.ram_size = machine->ram_size;
+ cubieboard_binfo.kernel_filename = machine->kernel_filename;
+ cubieboard_binfo.kernel_cmdline = machine->kernel_cmdline;
+ arm_load_kernel(&s->a10->cpu, &cubieboard_binfo);
+}
+
+static QEMUMachine cubieboard_machine = {
+ .name = "cubieboard",
+ .desc = "cubietech cubieboard",
+ .init = cubieboard_init,
+};
+
+
+static void cubieboard_machine_init(void)
+{
+ qemu_register_machine(&cubieboard_machine);
+}
+
+machine_init(cubieboard_machine_init)
diff --git a/hw/arm/digic.c b/hw/arm/digic.c
new file mode 100644
index 00000000..90f8190c
--- /dev/null
+++ b/hw/arm/digic.c
@@ -0,0 +1,121 @@
+/*
+ * QEMU model of the Canon DIGIC SoC.
+ *
+ * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com>
+ *
+ * This model is based on reverse engineering efforts
+ * made by CHDK (http://chdk.wikia.com) and
+ * Magic Lantern (http://www.magiclantern.fm) projects
+ * contributors.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "hw/arm/digic.h"
+
+#define DIGIC4_TIMER_BASE(n) (0xc0210000 + (n) * 0x100)
+
+#define DIGIC_UART_BASE 0xc0800000
+
+static void digic_init(Object *obj)
+{
+ DigicState *s = DIGIC(obj);
+ DeviceState *dev;
+ int i;
+
+ object_initialize(&s->cpu, sizeof(s->cpu), "arm946-" TYPE_ARM_CPU);
+ object_property_add_child(obj, "cpu", OBJECT(&s->cpu), NULL);
+
+ for (i = 0; i < DIGIC4_NB_TIMERS; i++) {
+#define DIGIC_TIMER_NAME_MLEN 11
+ char name[DIGIC_TIMER_NAME_MLEN];
+
+ object_initialize(&s->timer[i], sizeof(s->timer[i]), TYPE_DIGIC_TIMER);
+ dev = DEVICE(&s->timer[i]);
+ qdev_set_parent_bus(dev, sysbus_get_default());
+ snprintf(name, DIGIC_TIMER_NAME_MLEN, "timer[%d]", i);
+ object_property_add_child(obj, name, OBJECT(&s->timer[i]), NULL);
+ }
+
+ object_initialize(&s->uart, sizeof(s->uart), TYPE_DIGIC_UART);
+ dev = DEVICE(&s->uart);
+ qdev_set_parent_bus(dev, sysbus_get_default());
+ object_property_add_child(obj, "uart", OBJECT(&s->uart), NULL);
+}
+
+static void digic_realize(DeviceState *dev, Error **errp)
+{
+ DigicState *s = DIGIC(dev);
+ Error *err = NULL;
+ SysBusDevice *sbd;
+ int i;
+
+ object_property_set_bool(OBJECT(&s->cpu), true, "reset-hivecs", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ object_property_set_bool(OBJECT(&s->cpu), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ for (i = 0; i < DIGIC4_NB_TIMERS; i++) {
+ object_property_set_bool(OBJECT(&s->timer[i]), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ sbd = SYS_BUS_DEVICE(&s->timer[i]);
+ sysbus_mmio_map(sbd, 0, DIGIC4_TIMER_BASE(i));
+ }
+
+ object_property_set_bool(OBJECT(&s->uart), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ sbd = SYS_BUS_DEVICE(&s->uart);
+ sysbus_mmio_map(sbd, 0, DIGIC_UART_BASE);
+}
+
+static void digic_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = digic_realize;
+
+ /*
+ * Reason: creates an ARM CPU, thus use after free(), see
+ * arm_cpu_class_init()
+ */
+ dc->cannot_destroy_with_object_finalize_yet = true;
+}
+
+static const TypeInfo digic_type_info = {
+ .name = TYPE_DIGIC,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(DigicState),
+ .instance_init = digic_init,
+ .class_init = digic_class_init,
+};
+
+static void digic_register_types(void)
+{
+ type_register_static(&digic_type_info);
+}
+
+type_init(digic_register_types)
diff --git a/hw/arm/digic_boards.c b/hw/arm/digic_boards.c
new file mode 100644
index 00000000..f8ba9e59
--- /dev/null
+++ b/hw/arm/digic_boards.c
@@ -0,0 +1,162 @@
+/*
+ * QEMU model of the Canon DIGIC boards (cameras indeed :).
+ *
+ * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com>
+ *
+ * This model is based on reverse engineering efforts
+ * made by CHDK (http://chdk.wikia.com) and
+ * Magic Lantern (http://www.magiclantern.fm) projects
+ * contributors.
+ *
+ * See docs here:
+ * http://magiclantern.wikia.com/wiki/Register_Map
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "hw/boards.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+#include "hw/arm/digic.h"
+#include "hw/block/flash.h"
+#include "hw/loader.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+
+#define DIGIC4_ROM0_BASE 0xf0000000
+#define DIGIC4_ROM1_BASE 0xf8000000
+#define DIGIC4_ROM_MAX_SIZE 0x08000000
+
+typedef struct DigicBoardState {
+ DigicState *digic;
+ MemoryRegion ram;
+} DigicBoardState;
+
+typedef struct DigicBoard {
+ hwaddr ram_size;
+ void (*add_rom0)(DigicBoardState *, hwaddr, const char *);
+ const char *rom0_def_filename;
+ void (*add_rom1)(DigicBoardState *, hwaddr, const char *);
+ const char *rom1_def_filename;
+} DigicBoard;
+
+static void digic4_board_setup_ram(DigicBoardState *s, hwaddr ram_size)
+{
+ memory_region_allocate_system_memory(&s->ram, NULL, "ram", ram_size);
+ memory_region_add_subregion(get_system_memory(), 0, &s->ram);
+}
+
+static void digic4_board_init(DigicBoard *board)
+{
+ Error *err = NULL;
+
+ DigicBoardState *s = g_new(DigicBoardState, 1);
+
+ s->digic = DIGIC(object_new(TYPE_DIGIC));
+ object_property_set_bool(OBJECT(s->digic), true, "realized", &err);
+ if (err != NULL) {
+ error_report("Couldn't realize DIGIC SoC: %s",
+ error_get_pretty(err));
+ exit(1);
+ }
+
+ digic4_board_setup_ram(s, board->ram_size);
+
+ if (board->add_rom0) {
+ board->add_rom0(s, DIGIC4_ROM0_BASE, board->rom0_def_filename);
+ }
+
+ if (board->add_rom1) {
+ board->add_rom1(s, DIGIC4_ROM1_BASE, board->rom1_def_filename);
+ }
+}
+
+static void digic_load_rom(DigicBoardState *s, hwaddr addr,
+ hwaddr max_size, const char *def_filename)
+{
+ target_long rom_size;
+ const char *filename;
+
+ if (qtest_enabled()) {
+ /* qtest runs no code so don't attempt a ROM load which
+ * could fail and result in a spurious test failure.
+ */
+ return;
+ }
+
+ if (bios_name) {
+ filename = bios_name;
+ } else {
+ filename = def_filename;
+ }
+
+ if (filename) {
+ char *fn = qemu_find_file(QEMU_FILE_TYPE_BIOS, filename);
+
+ if (!fn) {
+ error_report("Couldn't find rom image '%s'.", filename);
+ exit(1);
+ }
+
+ rom_size = load_image_targphys(fn, addr, max_size);
+ if (rom_size < 0 || rom_size > max_size) {
+ error_report("Couldn't load rom image '%s'.", filename);
+ exit(1);
+ }
+ g_free(fn);
+ }
+}
+
+/*
+ * Samsung K8P3215UQB
+ * 64M Bit (4Mx16) Page Mode / Multi-Bank NOR Flash Memory
+ */
+static void digic4_add_k8p3215uqb_rom(DigicBoardState *s, hwaddr addr,
+ const char *def_filename)
+{
+#define FLASH_K8P3215UQB_SIZE (4 * 1024 * 1024)
+#define FLASH_K8P3215UQB_SECTOR_SIZE (64 * 1024)
+
+ pflash_cfi02_register(addr, NULL, "pflash", FLASH_K8P3215UQB_SIZE,
+ NULL, FLASH_K8P3215UQB_SECTOR_SIZE,
+ FLASH_K8P3215UQB_SIZE / FLASH_K8P3215UQB_SECTOR_SIZE,
+ DIGIC4_ROM_MAX_SIZE / FLASH_K8P3215UQB_SIZE,
+ 4,
+ 0x00EC, 0x007E, 0x0003, 0x0001,
+ 0x0555, 0x2aa, 0);
+
+ digic_load_rom(s, addr, FLASH_K8P3215UQB_SIZE, def_filename);
+}
+
+static DigicBoard digic4_board_canon_a1100 = {
+ .ram_size = 64 * 1024 * 1024,
+ .add_rom1 = digic4_add_k8p3215uqb_rom,
+ .rom1_def_filename = "canon-a1100-rom1.bin",
+};
+
+static void canon_a1100_init(MachineState *machine)
+{
+ digic4_board_init(&digic4_board_canon_a1100);
+}
+
+static QEMUMachine canon_a1100 = {
+ .name = "canon-a1100",
+ .desc = "Canon PowerShot A1100 IS",
+ .init = &canon_a1100_init,
+};
+
+static void digic_register_machines(void)
+{
+ qemu_register_machine(&canon_a1100);
+}
+
+machine_init(digic_register_machines)
diff --git a/hw/arm/exynos4210.c b/hw/arm/exynos4210.c
new file mode 100644
index 00000000..c55fab81
--- /dev/null
+++ b/hw/arm/exynos4210.c
@@ -0,0 +1,383 @@
+/*
+ * Samsung exynos4210 SoC emulation
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ * Maksim Kozlov <m.kozlov@samsung.com>
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ * Igor Mitsyanko <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/boards.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/loader.h"
+#include "hw/arm/exynos4210.h"
+#include "hw/usb/hcd-ehci.h"
+
+#define EXYNOS4210_CHIPID_ADDR 0x10000000
+
+/* PWM */
+#define EXYNOS4210_PWM_BASE_ADDR 0x139D0000
+
+/* RTC */
+#define EXYNOS4210_RTC_BASE_ADDR 0x10070000
+
+/* MCT */
+#define EXYNOS4210_MCT_BASE_ADDR 0x10050000
+
+/* I2C */
+#define EXYNOS4210_I2C_SHIFT 0x00010000
+#define EXYNOS4210_I2C_BASE_ADDR 0x13860000
+/* Interrupt Group of External Interrupt Combiner for I2C */
+#define EXYNOS4210_I2C_INTG 27
+#define EXYNOS4210_HDMI_INTG 16
+
+/* UART's definitions */
+#define EXYNOS4210_UART0_BASE_ADDR 0x13800000
+#define EXYNOS4210_UART1_BASE_ADDR 0x13810000
+#define EXYNOS4210_UART2_BASE_ADDR 0x13820000
+#define EXYNOS4210_UART3_BASE_ADDR 0x13830000
+#define EXYNOS4210_UART0_FIFO_SIZE 256
+#define EXYNOS4210_UART1_FIFO_SIZE 64
+#define EXYNOS4210_UART2_FIFO_SIZE 16
+#define EXYNOS4210_UART3_FIFO_SIZE 16
+/* Interrupt Group of External Interrupt Combiner for UART */
+#define EXYNOS4210_UART_INT_GRP 26
+
+/* External GIC */
+#define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR 0x10480000
+#define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR 0x10490000
+
+/* Combiner */
+#define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000
+#define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000
+
+/* PMU SFR base address */
+#define EXYNOS4210_PMU_BASE_ADDR 0x10020000
+
+/* Display controllers (FIMD) */
+#define EXYNOS4210_FIMD0_BASE_ADDR 0x11C00000
+
+/* EHCI */
+#define EXYNOS4210_EHCI_BASE_ADDR 0x12580000
+
+static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
+ 0x09, 0x00, 0x00, 0x00 };
+
+static uint64_t exynos4210_chipid_and_omr_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ assert(offset < sizeof(chipid_and_omr));
+ return chipid_and_omr[offset];
+}
+
+static void exynos4210_chipid_and_omr_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ return;
+}
+
+static const MemoryRegionOps exynos4210_chipid_and_omr_ops = {
+ .read = exynos4210_chipid_and_omr_read,
+ .write = exynos4210_chipid_and_omr_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .max_access_size = 1,
+ }
+};
+
+void exynos4210_write_secondary(ARMCPU *cpu,
+ const struct arm_boot_info *info)
+{
+ int n;
+ uint32_t smpboot[] = {
+ 0xe59f3034, /* ldr r3, External gic_cpu_if */
+ 0xe59f2034, /* ldr r2, Internal gic_cpu_if */
+ 0xe59f0034, /* ldr r0, startaddr */
+ 0xe3a01001, /* mov r1, #1 */
+ 0xe5821000, /* str r1, [r2] */
+ 0xe5831000, /* str r1, [r3] */
+ 0xe3a010ff, /* mov r1, #0xff */
+ 0xe5821004, /* str r1, [r2, #4] */
+ 0xe5831004, /* str r1, [r3, #4] */
+ 0xf57ff04f, /* dsb */
+ 0xe320f003, /* wfi */
+ 0xe5901000, /* ldr r1, [r0] */
+ 0xe1110001, /* tst r1, r1 */
+ 0x0afffffb, /* beq <wfi> */
+ 0xe12fff11, /* bx r1 */
+ EXYNOS4210_EXT_GIC_CPU_BASE_ADDR,
+ 0, /* gic_cpu_if: base address of Internal GIC CPU interface */
+ 0 /* bootreg: Boot register address is held here */
+ };
+ smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr;
+ smpboot[ARRAY_SIZE(smpboot) - 2] = info->gic_cpu_if_addr;
+ for (n = 0; n < ARRAY_SIZE(smpboot); n++) {
+ smpboot[n] = tswap32(smpboot[n]);
+ }
+ rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot),
+ info->smp_loader_start);
+}
+
+Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
+ unsigned long ram_size)
+{
+ int i, n;
+ Exynos4210State *s = g_new(Exynos4210State, 1);
+ qemu_irq gate_irq[EXYNOS4210_NCPUS][EXYNOS4210_IRQ_GATE_NINPUTS];
+ unsigned long mem_size;
+ DeviceState *dev;
+ SysBusDevice *busdev;
+ ObjectClass *cpu_oc;
+
+ cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, "cortex-a9");
+ assert(cpu_oc);
+
+ for (n = 0; n < EXYNOS4210_NCPUS; n++) {
+ Object *cpuobj = object_new(object_class_get_name(cpu_oc));
+ Error *err = NULL;
+
+ /* By default A9 CPUs have EL3 enabled. This board does not currently
+ * support EL3 so the CPU EL3 property is disabled before realization.
+ */
+ if (object_property_find(cpuobj, "has_el3", NULL)) {
+ object_property_set_bool(cpuobj, false, "has_el3", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ s->cpu[n] = ARM_CPU(cpuobj);
+ object_property_set_int(cpuobj, EXYNOS4210_SMP_PRIVATE_BASE_ADDR,
+ "reset-cbar", &error_abort);
+ object_property_set_bool(cpuobj, true, "realized", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ /*** IRQs ***/
+
+ s->irq_table = exynos4210_init_irq(&s->irqs);
+
+ /* IRQ Gate */
+ for (i = 0; i < EXYNOS4210_NCPUS; i++) {
+ dev = qdev_create(NULL, "exynos4210.irq_gate");
+ qdev_prop_set_uint32(dev, "n_in", EXYNOS4210_IRQ_GATE_NINPUTS);
+ qdev_init_nofail(dev);
+ /* Get IRQ Gate input in gate_irq */
+ for (n = 0; n < EXYNOS4210_IRQ_GATE_NINPUTS; n++) {
+ gate_irq[i][n] = qdev_get_gpio_in(dev, n);
+ }
+ busdev = SYS_BUS_DEVICE(dev);
+
+ /* Connect IRQ Gate output to CPU's IRQ line */
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(DEVICE(s->cpu[i]), ARM_CPU_IRQ));
+ }
+
+ /* Private memory region and Internal GIC */
+ dev = qdev_create(NULL, "a9mpcore_priv");
+ qdev_prop_set_uint32(dev, "num-cpu", EXYNOS4210_NCPUS);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR);
+ for (n = 0; n < EXYNOS4210_NCPUS; n++) {
+ sysbus_connect_irq(busdev, n, gate_irq[n][0]);
+ }
+ for (n = 0; n < EXYNOS4210_INT_GIC_NIRQ; n++) {
+ s->irqs.int_gic_irq[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ /* Cache controller */
+ sysbus_create_simple("l2x0", EXYNOS4210_L2X0_BASE_ADDR, NULL);
+
+ /* External GIC */
+ dev = qdev_create(NULL, "exynos4210.gic");
+ qdev_prop_set_uint32(dev, "num-cpu", EXYNOS4210_NCPUS);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ /* Map CPU interface */
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_GIC_CPU_BASE_ADDR);
+ /* Map Distributer interface */
+ sysbus_mmio_map(busdev, 1, EXYNOS4210_EXT_GIC_DIST_BASE_ADDR);
+ for (n = 0; n < EXYNOS4210_NCPUS; n++) {
+ sysbus_connect_irq(busdev, n, gate_irq[n][1]);
+ }
+ for (n = 0; n < EXYNOS4210_EXT_GIC_NIRQ; n++) {
+ s->irqs.ext_gic_irq[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ /* Internal Interrupt Combiner */
+ dev = qdev_create(NULL, "exynos4210.combiner");
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+ sysbus_connect_irq(busdev, n, s->irqs.int_gic_irq[n]);
+ }
+ exynos4210_combiner_get_gpioin(&s->irqs, dev, 0);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_INT_COMBINER_BASE_ADDR);
+
+ /* External Interrupt Combiner */
+ dev = qdev_create(NULL, "exynos4210.combiner");
+ qdev_prop_set_uint32(dev, "external", 1);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+ sysbus_connect_irq(busdev, n, s->irqs.ext_gic_irq[n]);
+ }
+ exynos4210_combiner_get_gpioin(&s->irqs, dev, 1);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_COMBINER_BASE_ADDR);
+
+ /* Initialize board IRQs. */
+ exynos4210_init_board_irqs(&s->irqs);
+
+ /*** Memory ***/
+
+ /* Chip-ID and OMR */
+ memory_region_init_io(&s->chipid_mem, NULL, &exynos4210_chipid_and_omr_ops,
+ NULL, "exynos4210.chipid", sizeof(chipid_and_omr));
+ memory_region_add_subregion(system_mem, EXYNOS4210_CHIPID_ADDR,
+ &s->chipid_mem);
+
+ /* Internal ROM */
+ memory_region_init_ram(&s->irom_mem, NULL, "exynos4210.irom",
+ EXYNOS4210_IROM_SIZE, &error_abort);
+ vmstate_register_ram_global(&s->irom_mem);
+ memory_region_set_readonly(&s->irom_mem, true);
+ memory_region_add_subregion(system_mem, EXYNOS4210_IROM_BASE_ADDR,
+ &s->irom_mem);
+ /* mirror of iROM */
+ memory_region_init_alias(&s->irom_alias_mem, NULL, "exynos4210.irom_alias",
+ &s->irom_mem,
+ 0,
+ EXYNOS4210_IROM_SIZE);
+ memory_region_set_readonly(&s->irom_alias_mem, true);
+ memory_region_add_subregion(system_mem, EXYNOS4210_IROM_MIRROR_BASE_ADDR,
+ &s->irom_alias_mem);
+
+ /* Internal RAM */
+ memory_region_init_ram(&s->iram_mem, NULL, "exynos4210.iram",
+ EXYNOS4210_IRAM_SIZE, &error_abort);
+ vmstate_register_ram_global(&s->iram_mem);
+ memory_region_add_subregion(system_mem, EXYNOS4210_IRAM_BASE_ADDR,
+ &s->iram_mem);
+
+ /* DRAM */
+ mem_size = ram_size;
+ if (mem_size > EXYNOS4210_DRAM_MAX_SIZE) {
+ memory_region_init_ram(&s->dram1_mem, NULL, "exynos4210.dram1",
+ mem_size - EXYNOS4210_DRAM_MAX_SIZE, &error_abort);
+ vmstate_register_ram_global(&s->dram1_mem);
+ memory_region_add_subregion(system_mem, EXYNOS4210_DRAM1_BASE_ADDR,
+ &s->dram1_mem);
+ mem_size = EXYNOS4210_DRAM_MAX_SIZE;
+ }
+ memory_region_init_ram(&s->dram0_mem, NULL, "exynos4210.dram0", mem_size,
+ &error_abort);
+ vmstate_register_ram_global(&s->dram0_mem);
+ memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
+ &s->dram0_mem);
+
+ /* PMU.
+ * The only reason of existence at the moment is that secondary CPU boot
+ * loader uses PMU INFORM5 register as a holding pen.
+ */
+ sysbus_create_simple("exynos4210.pmu", EXYNOS4210_PMU_BASE_ADDR, NULL);
+
+ /* PWM */
+ sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR,
+ s->irq_table[exynos4210_get_irq(22, 0)],
+ s->irq_table[exynos4210_get_irq(22, 1)],
+ s->irq_table[exynos4210_get_irq(22, 2)],
+ s->irq_table[exynos4210_get_irq(22, 3)],
+ s->irq_table[exynos4210_get_irq(22, 4)],
+ NULL);
+ /* RTC */
+ sysbus_create_varargs("exynos4210.rtc", EXYNOS4210_RTC_BASE_ADDR,
+ s->irq_table[exynos4210_get_irq(23, 0)],
+ s->irq_table[exynos4210_get_irq(23, 1)],
+ NULL);
+
+ /* Multi Core Timer */
+ dev = qdev_create(NULL, "exynos4210.mct");
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ for (n = 0; n < 4; n++) {
+ /* Connect global timer interrupts to Combiner gpio_in */
+ sysbus_connect_irq(busdev, n,
+ s->irq_table[exynos4210_get_irq(1, 4 + n)]);
+ }
+ /* Connect local timer interrupts to Combiner gpio_in */
+ sysbus_connect_irq(busdev, 4,
+ s->irq_table[exynos4210_get_irq(51, 0)]);
+ sysbus_connect_irq(busdev, 5,
+ s->irq_table[exynos4210_get_irq(35, 3)]);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
+
+ /*** I2C ***/
+ for (n = 0; n < EXYNOS4210_I2C_NUMBER; n++) {
+ uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n;
+ qemu_irq i2c_irq;
+
+ if (n < 8) {
+ i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_I2C_INTG, n)];
+ } else {
+ i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_HDMI_INTG, 1)];
+ }
+
+ dev = qdev_create(NULL, "exynos4210.i2c");
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(busdev, 0, i2c_irq);
+ sysbus_mmio_map(busdev, 0, addr);
+ s->i2c_if[n] = (I2CBus *)qdev_get_child_bus(dev, "i2c");
+ }
+
+
+ /*** UARTs ***/
+ exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
+ EXYNOS4210_UART0_FIFO_SIZE, 0, NULL,
+ s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 0)]);
+
+ exynos4210_uart_create(EXYNOS4210_UART1_BASE_ADDR,
+ EXYNOS4210_UART1_FIFO_SIZE, 1, NULL,
+ s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 1)]);
+
+ exynos4210_uart_create(EXYNOS4210_UART2_BASE_ADDR,
+ EXYNOS4210_UART2_FIFO_SIZE, 2, NULL,
+ s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 2)]);
+
+ exynos4210_uart_create(EXYNOS4210_UART3_BASE_ADDR,
+ EXYNOS4210_UART3_FIFO_SIZE, 3, NULL,
+ s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
+
+ /*** Display controller (FIMD) ***/
+ sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
+ s->irq_table[exynos4210_get_irq(11, 0)],
+ s->irq_table[exynos4210_get_irq(11, 1)],
+ s->irq_table[exynos4210_get_irq(11, 2)],
+ NULL);
+
+ sysbus_create_simple(TYPE_EXYNOS4210_EHCI, EXYNOS4210_EHCI_BASE_ADDR,
+ s->irq_table[exynos4210_get_irq(28, 3)]);
+
+ return s;
+}
diff --git a/hw/arm/exynos4_boards.c b/hw/arm/exynos4_boards.c
new file mode 100644
index 00000000..d644db1e
--- /dev/null
+++ b/hw/arm/exynos4_boards.c
@@ -0,0 +1,169 @@
+/*
+ * Samsung exynos4 SoC based boards emulation
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ * Maksim Kozlov <m.kozlov@samsung.com>
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ * Igor Mitsyanko <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "hw/arm/arm.h"
+#include "exec/address-spaces.h"
+#include "hw/arm/exynos4210.h"
+#include "hw/boards.h"
+
+#undef DEBUG
+
+//#define DEBUG
+
+#ifdef DEBUG
+ #undef PRINT_DEBUG
+ #define PRINT_DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+#else
+ #define PRINT_DEBUG(fmt, args...) do {} while (0)
+#endif
+
+#define SMDK_LAN9118_BASE_ADDR 0x05000000
+
+typedef enum Exynos4BoardType {
+ EXYNOS4_BOARD_NURI,
+ EXYNOS4_BOARD_SMDKC210,
+ EXYNOS4_NUM_OF_BOARDS
+} Exynos4BoardType;
+
+static int exynos4_board_id[EXYNOS4_NUM_OF_BOARDS] = {
+ [EXYNOS4_BOARD_NURI] = 0xD33,
+ [EXYNOS4_BOARD_SMDKC210] = 0xB16,
+};
+
+static int exynos4_board_smp_bootreg_addr[EXYNOS4_NUM_OF_BOARDS] = {
+ [EXYNOS4_BOARD_NURI] = EXYNOS4210_SECOND_CPU_BOOTREG,
+ [EXYNOS4_BOARD_SMDKC210] = EXYNOS4210_SECOND_CPU_BOOTREG,
+};
+
+static unsigned long exynos4_board_ram_size[EXYNOS4_NUM_OF_BOARDS] = {
+ [EXYNOS4_BOARD_NURI] = 0x40000000,
+ [EXYNOS4_BOARD_SMDKC210] = 0x40000000,
+};
+
+static struct arm_boot_info exynos4_board_binfo = {
+ .loader_start = EXYNOS4210_BASE_BOOT_ADDR,
+ .smp_loader_start = EXYNOS4210_SMP_BOOT_ADDR,
+ .nb_cpus = EXYNOS4210_NCPUS,
+ .write_secondary_boot = exynos4210_write_secondary,
+};
+
+static QEMUMachine exynos4_machines[EXYNOS4_NUM_OF_BOARDS];
+
+static void lan9215_init(uint32_t base, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ /* This should be a 9215 but the 9118 is close enough */
+ if (nd_table[0].used) {
+ qemu_check_nic_model(&nd_table[0], "lan9118");
+ dev = qdev_create(NULL, "lan9118");
+ qdev_set_nic_properties(dev, &nd_table[0]);
+ qdev_prop_set_uint32(dev, "mode_16bit", 1);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, base);
+ sysbus_connect_irq(s, 0, irq);
+ }
+}
+
+static Exynos4210State *exynos4_boards_init_common(MachineState *machine,
+ Exynos4BoardType board_type)
+{
+ if (smp_cpus != EXYNOS4210_NCPUS && !qtest_enabled()) {
+ fprintf(stderr, "%s board supports only %d CPU cores. Ignoring smp_cpus"
+ " value.\n",
+ exynos4_machines[board_type].name,
+ exynos4_machines[board_type].max_cpus);
+ }
+
+ exynos4_board_binfo.ram_size = exynos4_board_ram_size[board_type];
+ exynos4_board_binfo.board_id = exynos4_board_id[board_type];
+ exynos4_board_binfo.smp_bootreg_addr =
+ exynos4_board_smp_bootreg_addr[board_type];
+ exynos4_board_binfo.kernel_filename = machine->kernel_filename;
+ exynos4_board_binfo.initrd_filename = machine->initrd_filename;
+ exynos4_board_binfo.kernel_cmdline = machine->kernel_cmdline;
+ exynos4_board_binfo.gic_cpu_if_addr =
+ EXYNOS4210_SMP_PRIVATE_BASE_ADDR + 0x100;
+
+ PRINT_DEBUG("\n ram_size: %luMiB [0x%08lx]\n"
+ " kernel_filename: %s\n"
+ " kernel_cmdline: %s\n"
+ " initrd_filename: %s\n",
+ exynos4_board_ram_size[board_type] / 1048576,
+ exynos4_board_ram_size[board_type],
+ machine->kernel_filename,
+ machine->kernel_cmdline,
+ machine->initrd_filename);
+
+ return exynos4210_init(get_system_memory(),
+ exynos4_board_ram_size[board_type]);
+}
+
+static void nuri_init(MachineState *machine)
+{
+ exynos4_boards_init_common(machine, EXYNOS4_BOARD_NURI);
+
+ arm_load_kernel(ARM_CPU(first_cpu), &exynos4_board_binfo);
+}
+
+static void smdkc210_init(MachineState *machine)
+{
+ Exynos4210State *s = exynos4_boards_init_common(machine,
+ EXYNOS4_BOARD_SMDKC210);
+
+ lan9215_init(SMDK_LAN9118_BASE_ADDR,
+ qemu_irq_invert(s->irq_table[exynos4210_get_irq(37, 1)]));
+ arm_load_kernel(ARM_CPU(first_cpu), &exynos4_board_binfo);
+}
+
+static QEMUMachine exynos4_machines[EXYNOS4_NUM_OF_BOARDS] = {
+ [EXYNOS4_BOARD_NURI] = {
+ .name = "nuri",
+ .desc = "Samsung NURI board (Exynos4210)",
+ .init = nuri_init,
+ .max_cpus = EXYNOS4210_NCPUS,
+ },
+ [EXYNOS4_BOARD_SMDKC210] = {
+ .name = "smdkc210",
+ .desc = "Samsung SMDKC210 board (Exynos4210)",
+ .init = smdkc210_init,
+ .max_cpus = EXYNOS4210_NCPUS,
+ },
+};
+
+static void exynos4_machine_init(void)
+{
+ qemu_register_machine(&exynos4_machines[EXYNOS4_BOARD_NURI]);
+ qemu_register_machine(&exynos4_machines[EXYNOS4_BOARD_SMDKC210]);
+}
+
+machine_init(exynos4_machine_init);
diff --git a/hw/arm/gumstix.c b/hw/arm/gumstix.c
new file mode 100644
index 00000000..8103278b
--- /dev/null
+++ b/hw/arm/gumstix.c
@@ -0,0 +1,142 @@
+/*
+ * Gumstix Platforms
+ *
+ * Copyright (c) 2007 by Thorsten Zitterell <info@bitmux.org>
+ *
+ * Code based on spitz platform by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+/*
+ * Example usage:
+ *
+ * connex:
+ * =======
+ * create image:
+ * # dd of=flash bs=1k count=16k if=/dev/zero
+ * # dd of=flash bs=1k conv=notrunc if=u-boot.bin
+ * # dd of=flash bs=1k conv=notrunc seek=256 if=rootfs.arm_nofpu.jffs2
+ * start it:
+ * # qemu-system-arm -M connex -pflash flash -monitor null -nographic
+ *
+ * verdex:
+ * =======
+ * create image:
+ * # dd of=flash bs=1k count=32k if=/dev/zero
+ * # dd of=flash bs=1k conv=notrunc if=u-boot.bin
+ * # dd of=flash bs=1k conv=notrunc seek=256 if=rootfs.arm_nofpu.jffs2
+ * # dd of=flash bs=1k conv=notrunc seek=31744 if=uImage
+ * start it:
+ * # qemu-system-arm -M verdex -pflash flash -monitor null -nographic -m 289
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "net/net.h"
+#include "hw/block/flash.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "sysemu/qtest.h"
+
+static const int sector_len = 128 * 1024;
+
+static void connex_init(MachineState *machine)
+{
+ PXA2xxState *cpu;
+ DriveInfo *dinfo;
+ int be;
+ MemoryRegion *address_space_mem = get_system_memory();
+
+ uint32_t connex_rom = 0x01000000;
+ uint32_t connex_ram = 0x04000000;
+
+ cpu = pxa255_init(address_space_mem, connex_ram);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ if (!dinfo && !qtest_enabled()) {
+ fprintf(stderr, "A flash image must be given with the "
+ "'pflash' parameter\n");
+ exit(1);
+ }
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ be = 1;
+#else
+ be = 0;
+#endif
+ if (!pflash_cfi01_register(0x00000000, NULL, "connext.rom", connex_rom,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ sector_len, connex_rom / sector_len,
+ 2, 0, 0, 0, 0, be)) {
+ fprintf(stderr, "qemu: Error registering flash memory.\n");
+ exit(1);
+ }
+
+ /* Interrupt line of NIC is connected to GPIO line 36 */
+ smc91c111_init(&nd_table[0], 0x04000300,
+ qdev_get_gpio_in(cpu->gpio, 36));
+}
+
+static void verdex_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ PXA2xxState *cpu;
+ DriveInfo *dinfo;
+ int be;
+ MemoryRegion *address_space_mem = get_system_memory();
+
+ uint32_t verdex_rom = 0x02000000;
+ uint32_t verdex_ram = 0x10000000;
+
+ cpu = pxa270_init(address_space_mem, verdex_ram, cpu_model ?: "pxa270-c0");
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ if (!dinfo && !qtest_enabled()) {
+ fprintf(stderr, "A flash image must be given with the "
+ "'pflash' parameter\n");
+ exit(1);
+ }
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ be = 1;
+#else
+ be = 0;
+#endif
+ if (!pflash_cfi01_register(0x00000000, NULL, "verdex.rom", verdex_rom,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ sector_len, verdex_rom / sector_len,
+ 2, 0, 0, 0, 0, be)) {
+ fprintf(stderr, "qemu: Error registering flash memory.\n");
+ exit(1);
+ }
+
+ /* Interrupt line of NIC is connected to GPIO line 99 */
+ smc91c111_init(&nd_table[0], 0x04000300,
+ qdev_get_gpio_in(cpu->gpio, 99));
+}
+
+static QEMUMachine connex_machine = {
+ .name = "connex",
+ .desc = "Gumstix Connex (PXA255)",
+ .init = connex_init,
+};
+
+static QEMUMachine verdex_machine = {
+ .name = "verdex",
+ .desc = "Gumstix Verdex (PXA270)",
+ .init = verdex_init,
+};
+
+static void gumstix_machine_init(void)
+{
+ qemu_register_machine(&connex_machine);
+ qemu_register_machine(&verdex_machine);
+}
+
+machine_init(gumstix_machine_init);
diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c
new file mode 100644
index 00000000..f8353a78
--- /dev/null
+++ b/hw/arm/highbank.c
@@ -0,0 +1,416 @@
+/*
+ * Calxeda Highbank SoC emulation
+ *
+ * Copyright (c) 2010-2012 Calxeda
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "hw/loader.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+
+#define SMP_BOOT_ADDR 0x100
+#define SMP_BOOT_REG 0x40
+#define MPCORE_PERIPHBASE 0xfff10000
+
+#define NIRQ_GIC 160
+
+/* Board init. */
+
+static void hb_write_secondary(ARMCPU *cpu, const struct arm_boot_info *info)
+{
+ int n;
+ uint32_t smpboot[] = {
+ 0xee100fb0, /* mrc p15, 0, r0, c0, c0, 5 - read current core id */
+ 0xe210000f, /* ands r0, r0, #0x0f */
+ 0xe3a03040, /* mov r3, #0x40 - jump address is 0x40 + 0x10 * core id */
+ 0xe0830200, /* add r0, r3, r0, lsl #4 */
+ 0xe59f2024, /* ldr r2, privbase */
+ 0xe3a01001, /* mov r1, #1 */
+ 0xe5821100, /* str r1, [r2, #256] - set GICC_CTLR.Enable */
+ 0xe3a010ff, /* mov r1, #0xff */
+ 0xe5821104, /* str r1, [r2, #260] - set GICC_PMR.Priority to 0xff */
+ 0xf57ff04f, /* dsb */
+ 0xe320f003, /* wfi */
+ 0xe5901000, /* ldr r1, [r0] */
+ 0xe1110001, /* tst r1, r1 */
+ 0x0afffffb, /* beq <wfi> */
+ 0xe12fff11, /* bx r1 */
+ MPCORE_PERIPHBASE /* privbase: MPCore peripheral base address. */
+ };
+ for (n = 0; n < ARRAY_SIZE(smpboot); n++) {
+ smpboot[n] = tswap32(smpboot[n]);
+ }
+ rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), SMP_BOOT_ADDR);
+}
+
+static void hb_reset_secondary(ARMCPU *cpu, const struct arm_boot_info *info)
+{
+ CPUARMState *env = &cpu->env;
+
+ switch (info->nb_cpus) {
+ case 4:
+ address_space_stl_notdirty(&address_space_memory,
+ SMP_BOOT_REG + 0x30, 0,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ case 3:
+ address_space_stl_notdirty(&address_space_memory,
+ SMP_BOOT_REG + 0x20, 0,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ case 2:
+ address_space_stl_notdirty(&address_space_memory,
+ SMP_BOOT_REG + 0x10, 0,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ env->regs[15] = SMP_BOOT_ADDR;
+ break;
+ default:
+ break;
+ }
+}
+
+#define NUM_REGS 0x200
+static void hb_regs_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ uint32_t *regs = opaque;
+
+ if (offset == 0xf00) {
+ if (value == 1 || value == 2) {
+ qemu_system_reset_request();
+ } else if (value == 3) {
+ qemu_system_shutdown_request();
+ }
+ }
+
+ regs[offset/4] = value;
+}
+
+static uint64_t hb_regs_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t *regs = opaque;
+ uint32_t value = regs[offset/4];
+
+ if ((offset == 0x100) || (offset == 0x108) || (offset == 0x10C)) {
+ value |= 0x30000000;
+ }
+
+ return value;
+}
+
+static const MemoryRegionOps hb_mem_ops = {
+ .read = hb_regs_read,
+ .write = hb_regs_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+#define TYPE_HIGHBANK_REGISTERS "highbank-regs"
+#define HIGHBANK_REGISTERS(obj) \
+ OBJECT_CHECK(HighbankRegsState, (obj), TYPE_HIGHBANK_REGISTERS)
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t regs[NUM_REGS];
+} HighbankRegsState;
+
+static VMStateDescription vmstate_highbank_regs = {
+ .name = "highbank-regs",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, HighbankRegsState, NUM_REGS),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void highbank_regs_reset(DeviceState *dev)
+{
+ HighbankRegsState *s = HIGHBANK_REGISTERS(dev);
+
+ s->regs[0x40] = 0x05F20121;
+ s->regs[0x41] = 0x2;
+ s->regs[0x42] = 0x05F30121;
+ s->regs[0x43] = 0x05F40121;
+}
+
+static int highbank_regs_init(SysBusDevice *dev)
+{
+ HighbankRegsState *s = HIGHBANK_REGISTERS(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &hb_mem_ops, s->regs,
+ "highbank_regs", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void highbank_regs_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ sbc->init = highbank_regs_init;
+ dc->desc = "Calxeda Highbank registers";
+ dc->vmsd = &vmstate_highbank_regs;
+ dc->reset = highbank_regs_reset;
+}
+
+static const TypeInfo highbank_regs_info = {
+ .name = TYPE_HIGHBANK_REGISTERS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(HighbankRegsState),
+ .class_init = highbank_regs_class_init,
+};
+
+static void highbank_regs_register_types(void)
+{
+ type_register_static(&highbank_regs_info);
+}
+
+type_init(highbank_regs_register_types)
+
+static struct arm_boot_info highbank_binfo;
+
+enum cxmachines {
+ CALXEDA_HIGHBANK,
+ CALXEDA_MIDWAY,
+};
+
+/* ram_size must be set to match the upper bound of memory in the
+ * device tree (linux/arch/arm/boot/dts/highbank.dts), which is
+ * normally 0xff900000 or -m 4089. When running this board on a
+ * 32-bit host, set the reg value of memory to 0xf7ff00000 in the
+ * device tree and pass -m 2047 to QEMU.
+ */
+static void calxeda_init(MachineState *machine, enum cxmachines machine_id)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ DeviceState *dev = NULL;
+ SysBusDevice *busdev;
+ qemu_irq pic[128];
+ int n;
+ qemu_irq cpu_irq[4];
+ qemu_irq cpu_fiq[4];
+ MemoryRegion *sysram;
+ MemoryRegion *dram;
+ MemoryRegion *sysmem;
+ char *sysboot_filename;
+
+ if (!cpu_model) {
+ switch (machine_id) {
+ case CALXEDA_HIGHBANK:
+ cpu_model = "cortex-a9";
+ break;
+ case CALXEDA_MIDWAY:
+ cpu_model = "cortex-a15";
+ break;
+ }
+ }
+
+ for (n = 0; n < smp_cpus; n++) {
+ ObjectClass *oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model);
+ Object *cpuobj;
+ ARMCPU *cpu;
+ Error *err = NULL;
+
+ if (!oc) {
+ error_report("Unable to find CPU definition");
+ exit(1);
+ }
+
+ cpuobj = object_new(object_class_get_name(oc));
+ cpu = ARM_CPU(cpuobj);
+
+ /* By default A9 and A15 CPUs have EL3 enabled. This board does not
+ * currently support EL3 so the CPU EL3 property is disabled before
+ * realization.
+ */
+ if (object_property_find(cpuobj, "has_el3", NULL)) {
+ object_property_set_bool(cpuobj, false, "has_el3", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ if (object_property_find(cpuobj, "reset-cbar", NULL)) {
+ object_property_set_int(cpuobj, MPCORE_PERIPHBASE,
+ "reset-cbar", &error_abort);
+ }
+ object_property_set_bool(cpuobj, true, "realized", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ);
+ cpu_fiq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ);
+ }
+
+ sysmem = get_system_memory();
+ dram = g_new(MemoryRegion, 1);
+ memory_region_allocate_system_memory(dram, NULL, "highbank.dram", ram_size);
+ /* SDRAM at address zero. */
+ memory_region_add_subregion(sysmem, 0, dram);
+
+ sysram = g_new(MemoryRegion, 1);
+ memory_region_init_ram(sysram, NULL, "highbank.sysram", 0x8000,
+ &error_abort);
+ memory_region_add_subregion(sysmem, 0xfff88000, sysram);
+ if (bios_name != NULL) {
+ sysboot_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (sysboot_filename != NULL) {
+ if (load_image_targphys(sysboot_filename, 0xfff88000, 0x8000) < 0) {
+ hw_error("Unable to load %s\n", bios_name);
+ }
+ g_free(sysboot_filename);
+ } else {
+ hw_error("Unable to find %s\n", bios_name);
+ }
+ }
+
+ switch (machine_id) {
+ case CALXEDA_HIGHBANK:
+ dev = qdev_create(NULL, "l2x0");
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, 0xfff12000);
+
+ dev = qdev_create(NULL, "a9mpcore_priv");
+ break;
+ case CALXEDA_MIDWAY:
+ dev = qdev_create(NULL, "a15mpcore_priv");
+ break;
+ }
+ qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+ qdev_prop_set_uint32(dev, "num-irq", NIRQ_GIC);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, MPCORE_PERIPHBASE);
+ for (n = 0; n < smp_cpus; n++) {
+ sysbus_connect_irq(busdev, n, cpu_irq[n]);
+ sysbus_connect_irq(busdev, n + smp_cpus, cpu_fiq[n]);
+ }
+
+ for (n = 0; n < 128; n++) {
+ pic[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ dev = qdev_create(NULL, "sp804");
+ qdev_prop_set_uint32(dev, "freq0", 150000000);
+ qdev_prop_set_uint32(dev, "freq1", 150000000);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, 0xfff34000);
+ sysbus_connect_irq(busdev, 0, pic[18]);
+ sysbus_create_simple("pl011", 0xfff36000, pic[20]);
+
+ dev = qdev_create(NULL, "highbank-regs");
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, 0xfff3c000);
+
+ sysbus_create_simple("pl061", 0xfff30000, pic[14]);
+ sysbus_create_simple("pl061", 0xfff31000, pic[15]);
+ sysbus_create_simple("pl061", 0xfff32000, pic[16]);
+ sysbus_create_simple("pl061", 0xfff33000, pic[17]);
+ sysbus_create_simple("pl031", 0xfff35000, pic[19]);
+ sysbus_create_simple("pl022", 0xfff39000, pic[23]);
+
+ sysbus_create_simple("sysbus-ahci", 0xffe08000, pic[83]);
+
+ if (nd_table[0].used) {
+ qemu_check_nic_model(&nd_table[0], "xgmac");
+ dev = qdev_create(NULL, "xgmac");
+ qdev_set_nic_properties(dev, &nd_table[0]);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xfff50000);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[77]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, pic[78]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, pic[79]);
+
+ qemu_check_nic_model(&nd_table[1], "xgmac");
+ dev = qdev_create(NULL, "xgmac");
+ qdev_set_nic_properties(dev, &nd_table[1]);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xfff51000);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[80]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, pic[81]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, pic[82]);
+ }
+
+ highbank_binfo.ram_size = ram_size;
+ highbank_binfo.kernel_filename = kernel_filename;
+ highbank_binfo.kernel_cmdline = kernel_cmdline;
+ highbank_binfo.initrd_filename = initrd_filename;
+ /* highbank requires a dtb in order to boot, and the dtb will override
+ * the board ID. The following value is ignored, so set it to -1 to be
+ * clear that the value is meaningless.
+ */
+ highbank_binfo.board_id = -1;
+ highbank_binfo.nb_cpus = smp_cpus;
+ highbank_binfo.loader_start = 0;
+ highbank_binfo.write_secondary_boot = hb_write_secondary;
+ highbank_binfo.secondary_cpu_reset_hook = hb_reset_secondary;
+ arm_load_kernel(ARM_CPU(first_cpu), &highbank_binfo);
+}
+
+static void highbank_init(MachineState *machine)
+{
+ calxeda_init(machine, CALXEDA_HIGHBANK);
+}
+
+static void midway_init(MachineState *machine)
+{
+ calxeda_init(machine, CALXEDA_MIDWAY);
+}
+
+static QEMUMachine highbank_machine = {
+ .name = "highbank",
+ .desc = "Calxeda Highbank (ECX-1000)",
+ .init = highbank_init,
+ .block_default_type = IF_SCSI,
+ .max_cpus = 4,
+};
+
+static QEMUMachine midway_machine = {
+ .name = "midway",
+ .desc = "Calxeda Midway (ECX-2000)",
+ .init = midway_init,
+ .block_default_type = IF_SCSI,
+ .max_cpus = 4,
+};
+
+static void calxeda_machines_init(void)
+{
+ qemu_register_machine(&highbank_machine);
+ qemu_register_machine(&midway_machine);
+}
+
+machine_init(calxeda_machines_init);
diff --git a/hw/arm/integratorcp.c b/hw/arm/integratorcp.c
new file mode 100644
index 00000000..0fbbf997
--- /dev/null
+++ b/hw/arm/integratorcp.c
@@ -0,0 +1,684 @@
+/*
+ * ARM Integrator CP System emulation.
+ *
+ * Copyright (c) 2005-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL
+ */
+
+#include "hw/sysbus.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "hw/arm/arm.h"
+#include "hw/misc/arm_integrator_debug.h"
+#include "net/net.h"
+#include "exec/address-spaces.h"
+#include "sysemu/sysemu.h"
+#include "qemu/error-report.h"
+
+#define TYPE_INTEGRATOR_CM "integrator_core"
+#define INTEGRATOR_CM(obj) \
+ OBJECT_CHECK(IntegratorCMState, (obj), TYPE_INTEGRATOR_CM)
+
+typedef struct IntegratorCMState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t memsz;
+ MemoryRegion flash;
+ uint32_t cm_osc;
+ uint32_t cm_ctrl;
+ uint32_t cm_lock;
+ uint32_t cm_auxosc;
+ uint32_t cm_sdram;
+ uint32_t cm_init;
+ uint32_t cm_flags;
+ uint32_t cm_nvflags;
+ uint32_t cm_refcnt_offset;
+ uint32_t int_level;
+ uint32_t irq_enabled;
+ uint32_t fiq_enabled;
+} IntegratorCMState;
+
+static uint8_t integrator_spd[128] = {
+ 128, 8, 4, 11, 9, 1, 64, 0, 2, 0xa0, 0xa0, 0, 0, 8, 0, 1,
+ 0xe, 4, 0x1c, 1, 2, 0x20, 0xc0, 0, 0, 0, 0, 0x30, 0x28, 0x30, 0x28, 0x40
+};
+
+static uint64_t integratorcm_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ IntegratorCMState *s = opaque;
+ if (offset >= 0x100 && offset < 0x200) {
+ /* CM_SPD */
+ if (offset >= 0x180)
+ return 0;
+ return integrator_spd[offset >> 2];
+ }
+ switch (offset >> 2) {
+ case 0: /* CM_ID */
+ return 0x411a3001;
+ case 1: /* CM_PROC */
+ return 0;
+ case 2: /* CM_OSC */
+ return s->cm_osc;
+ case 3: /* CM_CTRL */
+ return s->cm_ctrl;
+ case 4: /* CM_STAT */
+ return 0x00100000;
+ case 5: /* CM_LOCK */
+ if (s->cm_lock == 0xa05f) {
+ return 0x1a05f;
+ } else {
+ return s->cm_lock;
+ }
+ case 6: /* CM_LMBUSCNT */
+ /* ??? High frequency timer. */
+ hw_error("integratorcm_read: CM_LMBUSCNT");
+ case 7: /* CM_AUXOSC */
+ return s->cm_auxosc;
+ case 8: /* CM_SDRAM */
+ return s->cm_sdram;
+ case 9: /* CM_INIT */
+ return s->cm_init;
+ case 10: /* CM_REFCNT */
+ /* This register, CM_REFCNT, provides a 32-bit count value.
+ * The count increments at the fixed reference clock frequency of 24MHz
+ * and can be used as a real-time counter.
+ */
+ return (uint32_t)muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24,
+ 1000) - s->cm_refcnt_offset;
+ case 12: /* CM_FLAGS */
+ return s->cm_flags;
+ case 14: /* CM_NVFLAGS */
+ return s->cm_nvflags;
+ case 16: /* CM_IRQ_STAT */
+ return s->int_level & s->irq_enabled;
+ case 17: /* CM_IRQ_RSTAT */
+ return s->int_level;
+ case 18: /* CM_IRQ_ENSET */
+ return s->irq_enabled;
+ case 20: /* CM_SOFT_INTSET */
+ return s->int_level & 1;
+ case 24: /* CM_FIQ_STAT */
+ return s->int_level & s->fiq_enabled;
+ case 25: /* CM_FIQ_RSTAT */
+ return s->int_level;
+ case 26: /* CM_FIQ_ENSET */
+ return s->fiq_enabled;
+ case 32: /* CM_VOLTAGE_CTL0 */
+ case 33: /* CM_VOLTAGE_CTL1 */
+ case 34: /* CM_VOLTAGE_CTL2 */
+ case 35: /* CM_VOLTAGE_CTL3 */
+ /* ??? Voltage control unimplemented. */
+ return 0;
+ default:
+ hw_error("integratorcm_read: Unimplemented offset 0x%x\n",
+ (int)offset);
+ return 0;
+ }
+}
+
+static void integratorcm_do_remap(IntegratorCMState *s)
+{
+ /* Sync memory region state with CM_CTRL REMAP bit:
+ * bit 0 => flash at address 0; bit 1 => RAM
+ */
+ memory_region_set_enabled(&s->flash, !(s->cm_ctrl & 4));
+}
+
+static void integratorcm_set_ctrl(IntegratorCMState *s, uint32_t value)
+{
+ if (value & 8) {
+ qemu_system_reset_request();
+ }
+ if ((s->cm_ctrl ^ value) & 1) {
+ /* (value & 1) != 0 means the green "MISC LED" is lit.
+ * We don't have any nice place to display LEDs. printf is a bad
+ * idea because Linux uses the LED as a heartbeat and the output
+ * will swamp anything else on the terminal.
+ */
+ }
+ /* Note that the RESET bit [3] always reads as zero */
+ s->cm_ctrl = (s->cm_ctrl & ~5) | (value & 5);
+ integratorcm_do_remap(s);
+}
+
+static void integratorcm_update(IntegratorCMState *s)
+{
+ /* ??? The CPU irq/fiq is raised when either the core module or base PIC
+ are active. */
+ if (s->int_level & (s->irq_enabled | s->fiq_enabled))
+ hw_error("Core module interrupt\n");
+}
+
+static void integratorcm_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IntegratorCMState *s = opaque;
+ switch (offset >> 2) {
+ case 2: /* CM_OSC */
+ if (s->cm_lock == 0xa05f)
+ s->cm_osc = value;
+ break;
+ case 3: /* CM_CTRL */
+ integratorcm_set_ctrl(s, value);
+ break;
+ case 5: /* CM_LOCK */
+ s->cm_lock = value & 0xffff;
+ break;
+ case 7: /* CM_AUXOSC */
+ if (s->cm_lock == 0xa05f)
+ s->cm_auxosc = value;
+ break;
+ case 8: /* CM_SDRAM */
+ s->cm_sdram = value;
+ break;
+ case 9: /* CM_INIT */
+ /* ??? This can change the memory bus frequency. */
+ s->cm_init = value;
+ break;
+ case 12: /* CM_FLAGSS */
+ s->cm_flags |= value;
+ break;
+ case 13: /* CM_FLAGSC */
+ s->cm_flags &= ~value;
+ break;
+ case 14: /* CM_NVFLAGSS */
+ s->cm_nvflags |= value;
+ break;
+ case 15: /* CM_NVFLAGSS */
+ s->cm_nvflags &= ~value;
+ break;
+ case 18: /* CM_IRQ_ENSET */
+ s->irq_enabled |= value;
+ integratorcm_update(s);
+ break;
+ case 19: /* CM_IRQ_ENCLR */
+ s->irq_enabled &= ~value;
+ integratorcm_update(s);
+ break;
+ case 20: /* CM_SOFT_INTSET */
+ s->int_level |= (value & 1);
+ integratorcm_update(s);
+ break;
+ case 21: /* CM_SOFT_INTCLR */
+ s->int_level &= ~(value & 1);
+ integratorcm_update(s);
+ break;
+ case 26: /* CM_FIQ_ENSET */
+ s->fiq_enabled |= value;
+ integratorcm_update(s);
+ break;
+ case 27: /* CM_FIQ_ENCLR */
+ s->fiq_enabled &= ~value;
+ integratorcm_update(s);
+ break;
+ case 32: /* CM_VOLTAGE_CTL0 */
+ case 33: /* CM_VOLTAGE_CTL1 */
+ case 34: /* CM_VOLTAGE_CTL2 */
+ case 35: /* CM_VOLTAGE_CTL3 */
+ /* ??? Voltage control unimplemented. */
+ break;
+ default:
+ hw_error("integratorcm_write: Unimplemented offset 0x%x\n",
+ (int)offset);
+ break;
+ }
+}
+
+/* Integrator/CM control registers. */
+
+static const MemoryRegionOps integratorcm_ops = {
+ .read = integratorcm_read,
+ .write = integratorcm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int integratorcm_init(SysBusDevice *dev)
+{
+ IntegratorCMState *s = INTEGRATOR_CM(dev);
+
+ s->cm_osc = 0x01000048;
+ /* ??? What should the high bits of this value be? */
+ s->cm_auxosc = 0x0007feff;
+ s->cm_sdram = 0x00011122;
+ if (s->memsz >= 256) {
+ integrator_spd[31] = 64;
+ s->cm_sdram |= 0x10;
+ } else if (s->memsz >= 128) {
+ integrator_spd[31] = 32;
+ s->cm_sdram |= 0x0c;
+ } else if (s->memsz >= 64) {
+ integrator_spd[31] = 16;
+ s->cm_sdram |= 0x08;
+ } else if (s->memsz >= 32) {
+ integrator_spd[31] = 4;
+ s->cm_sdram |= 0x04;
+ } else {
+ integrator_spd[31] = 2;
+ }
+ memcpy(integrator_spd + 73, "QEMU-MEMORY", 11);
+ s->cm_init = 0x00000112;
+ s->cm_refcnt_offset = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24,
+ 1000);
+ memory_region_init_ram(&s->flash, OBJECT(s), "integrator.flash", 0x100000,
+ &error_abort);
+ vmstate_register_ram_global(&s->flash);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &integratorcm_ops, s,
+ "integratorcm", 0x00800000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ integratorcm_do_remap(s);
+ /* ??? Save/restore. */
+ return 0;
+}
+
+/* Integrator/CP hardware emulation. */
+/* Primary interrupt controller. */
+
+#define TYPE_INTEGRATOR_PIC "integrator_pic"
+#define INTEGRATOR_PIC(obj) \
+ OBJECT_CHECK(icp_pic_state, (obj), TYPE_INTEGRATOR_PIC)
+
+typedef struct icp_pic_state {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t level;
+ uint32_t irq_enabled;
+ uint32_t fiq_enabled;
+ qemu_irq parent_irq;
+ qemu_irq parent_fiq;
+} icp_pic_state;
+
+static void icp_pic_update(icp_pic_state *s)
+{
+ uint32_t flags;
+
+ flags = (s->level & s->irq_enabled);
+ qemu_set_irq(s->parent_irq, flags != 0);
+ flags = (s->level & s->fiq_enabled);
+ qemu_set_irq(s->parent_fiq, flags != 0);
+}
+
+static void icp_pic_set_irq(void *opaque, int irq, int level)
+{
+ icp_pic_state *s = (icp_pic_state *)opaque;
+ if (level)
+ s->level |= 1 << irq;
+ else
+ s->level &= ~(1 << irq);
+ icp_pic_update(s);
+}
+
+static uint64_t icp_pic_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ icp_pic_state *s = (icp_pic_state *)opaque;
+
+ switch (offset >> 2) {
+ case 0: /* IRQ_STATUS */
+ return s->level & s->irq_enabled;
+ case 1: /* IRQ_RAWSTAT */
+ return s->level;
+ case 2: /* IRQ_ENABLESET */
+ return s->irq_enabled;
+ case 4: /* INT_SOFTSET */
+ return s->level & 1;
+ case 8: /* FRQ_STATUS */
+ return s->level & s->fiq_enabled;
+ case 9: /* FRQ_RAWSTAT */
+ return s->level;
+ case 10: /* FRQ_ENABLESET */
+ return s->fiq_enabled;
+ case 3: /* IRQ_ENABLECLR */
+ case 5: /* INT_SOFTCLR */
+ case 11: /* FRQ_ENABLECLR */
+ default:
+ printf ("icp_pic_read: Bad register offset 0x%x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void icp_pic_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ icp_pic_state *s = (icp_pic_state *)opaque;
+
+ switch (offset >> 2) {
+ case 2: /* IRQ_ENABLESET */
+ s->irq_enabled |= value;
+ break;
+ case 3: /* IRQ_ENABLECLR */
+ s->irq_enabled &= ~value;
+ break;
+ case 4: /* INT_SOFTSET */
+ if (value & 1)
+ icp_pic_set_irq(s, 0, 1);
+ break;
+ case 5: /* INT_SOFTCLR */
+ if (value & 1)
+ icp_pic_set_irq(s, 0, 0);
+ break;
+ case 10: /* FRQ_ENABLESET */
+ s->fiq_enabled |= value;
+ break;
+ case 11: /* FRQ_ENABLECLR */
+ s->fiq_enabled &= ~value;
+ break;
+ case 0: /* IRQ_STATUS */
+ case 1: /* IRQ_RAWSTAT */
+ case 8: /* FRQ_STATUS */
+ case 9: /* FRQ_RAWSTAT */
+ default:
+ printf ("icp_pic_write: Bad register offset 0x%x\n", (int)offset);
+ return;
+ }
+ icp_pic_update(s);
+}
+
+static const MemoryRegionOps icp_pic_ops = {
+ .read = icp_pic_read,
+ .write = icp_pic_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int icp_pic_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ icp_pic_state *s = INTEGRATOR_PIC(dev);
+
+ qdev_init_gpio_in(dev, icp_pic_set_irq, 32);
+ sysbus_init_irq(sbd, &s->parent_irq);
+ sysbus_init_irq(sbd, &s->parent_fiq);
+ memory_region_init_io(&s->iomem, OBJECT(s), &icp_pic_ops, s,
+ "icp-pic", 0x00800000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ return 0;
+}
+
+/* CP control registers. */
+
+#define TYPE_ICP_CONTROL_REGS "icp-ctrl-regs"
+#define ICP_CONTROL_REGS(obj) \
+ OBJECT_CHECK(ICPCtrlRegsState, (obj), TYPE_ICP_CONTROL_REGS)
+
+typedef struct ICPCtrlRegsState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+
+ qemu_irq mmc_irq;
+ uint32_t intreg_state;
+} ICPCtrlRegsState;
+
+#define ICP_GPIO_MMC_WPROT "mmc-wprot"
+#define ICP_GPIO_MMC_CARDIN "mmc-cardin"
+
+#define ICP_INTREG_WPROT (1 << 0)
+#define ICP_INTREG_CARDIN (1 << 3)
+
+static uint64_t icp_control_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ ICPCtrlRegsState *s = opaque;
+
+ switch (offset >> 2) {
+ case 0: /* CP_IDFIELD */
+ return 0x41034003;
+ case 1: /* CP_FLASHPROG */
+ return 0;
+ case 2: /* CP_INTREG */
+ return s->intreg_state;
+ case 3: /* CP_DECODE */
+ return 0x11;
+ default:
+ hw_error("icp_control_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void icp_control_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ ICPCtrlRegsState *s = opaque;
+
+ switch (offset >> 2) {
+ case 2: /* CP_INTREG */
+ s->intreg_state &= ~(value & ICP_INTREG_CARDIN);
+ qemu_set_irq(s->mmc_irq, !!(s->intreg_state & ICP_INTREG_CARDIN));
+ break;
+ case 1: /* CP_FLASHPROG */
+ case 3: /* CP_DECODE */
+ /* Nothing interesting implemented yet. */
+ break;
+ default:
+ hw_error("icp_control_write: Bad offset %x\n", (int)offset);
+ }
+}
+
+static const MemoryRegionOps icp_control_ops = {
+ .read = icp_control_read,
+ .write = icp_control_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void icp_control_mmc_wprot(void *opaque, int line, int level)
+{
+ ICPCtrlRegsState *s = opaque;
+
+ s->intreg_state &= ~ICP_INTREG_WPROT;
+ if (level) {
+ s->intreg_state |= ICP_INTREG_WPROT;
+ }
+}
+
+static void icp_control_mmc_cardin(void *opaque, int line, int level)
+{
+ ICPCtrlRegsState *s = opaque;
+
+ /* line is released by writing to CP_INTREG */
+ if (level) {
+ s->intreg_state |= ICP_INTREG_CARDIN;
+ qemu_set_irq(s->mmc_irq, 1);
+ }
+}
+
+static void icp_control_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ICPCtrlRegsState *s = ICP_CONTROL_REGS(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &icp_control_ops, s,
+ "icp_ctrl_regs", 0x00800000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ qdev_init_gpio_in_named(dev, icp_control_mmc_wprot, ICP_GPIO_MMC_WPROT, 1);
+ qdev_init_gpio_in_named(dev, icp_control_mmc_cardin,
+ ICP_GPIO_MMC_CARDIN, 1);
+ sysbus_init_irq(sbd, &s->mmc_irq);
+}
+
+
+/* Board init. */
+
+static struct arm_boot_info integrator_binfo = {
+ .loader_start = 0x0,
+ .board_id = 0x113,
+};
+
+static void integratorcp_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ ObjectClass *cpu_oc;
+ Object *cpuobj;
+ ARMCPU *cpu;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *ram_alias = g_new(MemoryRegion, 1);
+ qemu_irq pic[32];
+ DeviceState *dev, *sic, *icp;
+ int i;
+ Error *err = NULL;
+
+ if (!cpu_model) {
+ cpu_model = "arm926";
+ }
+
+ cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model);
+ if (!cpu_oc) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+
+ cpuobj = object_new(object_class_get_name(cpu_oc));
+
+ /* By default ARM1176 CPUs have EL3 enabled. This board does not
+ * currently support EL3 so the CPU EL3 property is disabled before
+ * realization.
+ */
+ if (object_property_find(cpuobj, "has_el3", NULL)) {
+ object_property_set_bool(cpuobj, false, "has_el3", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ object_property_set_bool(cpuobj, true, "realized", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+
+ cpu = ARM_CPU(cpuobj);
+
+ memory_region_allocate_system_memory(ram, NULL, "integrator.ram",
+ ram_size);
+ /* ??? On a real system the first 1Mb is mapped as SSRAM or boot flash. */
+ /* ??? RAM should repeat to fill physical memory space. */
+ /* SDRAM at address zero*/
+ memory_region_add_subregion(address_space_mem, 0, ram);
+ /* And again at address 0x80000000 */
+ memory_region_init_alias(ram_alias, NULL, "ram.alias", ram, 0, ram_size);
+ memory_region_add_subregion(address_space_mem, 0x80000000, ram_alias);
+
+ dev = qdev_create(NULL, TYPE_INTEGRATOR_CM);
+ qdev_prop_set_uint32(dev, "memsz", ram_size >> 20);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map((SysBusDevice *)dev, 0, 0x10000000);
+
+ dev = sysbus_create_varargs(TYPE_INTEGRATOR_PIC, 0x14000000,
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ),
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ),
+ NULL);
+ for (i = 0; i < 32; i++) {
+ pic[i] = qdev_get_gpio_in(dev, i);
+ }
+ sic = sysbus_create_simple(TYPE_INTEGRATOR_PIC, 0xca000000, pic[26]);
+ sysbus_create_varargs("integrator_pit", 0x13000000,
+ pic[5], pic[6], pic[7], NULL);
+ sysbus_create_simple("pl031", 0x15000000, pic[8]);
+ sysbus_create_simple("pl011", 0x16000000, pic[1]);
+ sysbus_create_simple("pl011", 0x17000000, pic[2]);
+ icp = sysbus_create_simple(TYPE_ICP_CONTROL_REGS, 0xcb000000,
+ qdev_get_gpio_in(sic, 3));
+ sysbus_create_simple("pl050_keyboard", 0x18000000, pic[3]);
+ sysbus_create_simple("pl050_mouse", 0x19000000, pic[4]);
+ sysbus_create_simple(TYPE_INTEGRATOR_DEBUG, 0x1a000000, 0);
+
+ dev = sysbus_create_varargs("pl181", 0x1c000000, pic[23], pic[24], NULL);
+ qdev_connect_gpio_out(dev, 0,
+ qdev_get_gpio_in_named(icp, ICP_GPIO_MMC_WPROT, 0));
+ qdev_connect_gpio_out(dev, 1,
+ qdev_get_gpio_in_named(icp, ICP_GPIO_MMC_CARDIN, 0));
+
+ if (nd_table[0].used)
+ smc91c111_init(&nd_table[0], 0xc8000000, pic[27]);
+
+ sysbus_create_simple("pl110", 0xc0000000, pic[22]);
+
+ integrator_binfo.ram_size = ram_size;
+ integrator_binfo.kernel_filename = kernel_filename;
+ integrator_binfo.kernel_cmdline = kernel_cmdline;
+ integrator_binfo.initrd_filename = initrd_filename;
+ arm_load_kernel(cpu, &integrator_binfo);
+}
+
+static QEMUMachine integratorcp_machine = {
+ .name = "integratorcp",
+ .desc = "ARM Integrator/CP (ARM926EJ-S)",
+ .init = integratorcp_init,
+};
+
+static void integratorcp_machine_init(void)
+{
+ qemu_register_machine(&integratorcp_machine);
+}
+
+machine_init(integratorcp_machine_init);
+
+static Property core_properties[] = {
+ DEFINE_PROP_UINT32("memsz", IntegratorCMState, memsz, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void core_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = integratorcm_init;
+ dc->props = core_properties;
+}
+
+static const TypeInfo core_info = {
+ .name = TYPE_INTEGRATOR_CM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IntegratorCMState),
+ .class_init = core_class_init,
+};
+
+static void icp_pic_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = icp_pic_init;
+}
+
+static const TypeInfo icp_pic_info = {
+ .name = TYPE_INTEGRATOR_PIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(icp_pic_state),
+ .class_init = icp_pic_class_init,
+};
+
+static const TypeInfo icp_ctrl_regs_info = {
+ .name = TYPE_ICP_CONTROL_REGS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ICPCtrlRegsState),
+ .instance_init = icp_control_init,
+};
+
+static void integratorcp_register_types(void)
+{
+ type_register_static(&icp_pic_info);
+ type_register_static(&core_info);
+ type_register_static(&icp_ctrl_regs_info);
+}
+
+type_init(integratorcp_register_types)
diff --git a/hw/arm/kzm.c b/hw/arm/kzm.c
new file mode 100644
index 00000000..5be0369a
--- /dev/null
+++ b/hw/arm/kzm.c
@@ -0,0 +1,153 @@
+/*
+ * KZM Board System emulation.
+ *
+ * Copyright (c) 2008 OKL and 2011 NICTA
+ * Written by Hans at OK-Labs
+ * Updated by Peter Chubb.
+ *
+ * This code is licensed under the GPL, version 2 or later.
+ * See the file `COPYING' in the top level directory.
+ *
+ * It (partially) emulates a Kyoto Microcomputer
+ * KZM-ARM11-01 evaluation board, with a Freescale
+ * i.MX31 SoC
+ */
+
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "hw/hw.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/char/serial.h"
+#include "hw/arm/imx.h"
+
+ /* Memory map for Kzm Emulation Baseboard:
+ * 0x00000000-0x00003fff 16k secure ROM IGNORED
+ * 0x00004000-0x00407fff Reserved IGNORED
+ * 0x00404000-0x00407fff ROM IGNORED
+ * 0x00408000-0x0fffffff Reserved IGNORED
+ * 0x10000000-0x1fffbfff RAM aliasing IGNORED
+ * 0x1fffc000-0x1fffffff RAM EMULATED
+ * 0x20000000-0x2fffffff Reserved IGNORED
+ * 0x30000000-0x7fffffff I.MX31 Internal Register Space
+ * 0x43f00000 IO_AREA0
+ * 0x43f90000 UART1 EMULATED
+ * 0x43f94000 UART2 EMULATED
+ * 0x68000000 AVIC EMULATED
+ * 0x53f80000 CCM EMULATED
+ * 0x53f94000 PIT 1 EMULATED
+ * 0x53f98000 PIT 2 EMULATED
+ * 0x53f90000 GPT EMULATED
+ * 0x80000000-0x87ffffff RAM EMULATED
+ * 0x88000000-0x8fffffff RAM Aliasing EMULATED
+ * 0xa0000000-0xafffffff NAND Flash IGNORED
+ * 0xb0000000-0xb3ffffff Unavailable IGNORED
+ * 0xb4000000-0xb4000fff 8-bit free space IGNORED
+ * 0xb4001000-0xb400100f Board control IGNORED
+ * 0xb4001003 DIP switch
+ * 0xb4001010-0xb400101f 7-segment LED IGNORED
+ * 0xb4001020-0xb400102f LED IGNORED
+ * 0xb4001030-0xb400103f LED IGNORED
+ * 0xb4001040-0xb400104f FPGA, UART EMULATED
+ * 0xb4001050-0xb400105f FPGA, UART EMULATED
+ * 0xb4001060-0xb40fffff FPGA IGNORED
+ * 0xb6000000-0xb61fffff LAN controller EMULATED
+ * 0xb6200000-0xb62fffff FPGA NAND Controller IGNORED
+ * 0xb6300000-0xb7ffffff Free IGNORED
+ * 0xb8000000-0xb8004fff Memory control registers IGNORED
+ * 0xc0000000-0xc3ffffff PCMCIA/CF IGNORED
+ * 0xc4000000-0xffffffff Reserved IGNORED
+ */
+
+#define KZM_RAMADDRESS (0x80000000)
+#define KZM_FPGA (0xb4001040)
+
+static struct arm_boot_info kzm_binfo = {
+ .loader_start = KZM_RAMADDRESS,
+ .board_id = 1722,
+};
+
+static void kzm_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ ARMCPU *cpu;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+ MemoryRegion *ram_alias = g_new(MemoryRegion, 1);
+ DeviceState *dev;
+ DeviceState *ccm;
+
+ if (!cpu_model) {
+ cpu_model = "arm1136";
+ }
+
+ cpu = cpu_arm_init(cpu_model);
+ if (!cpu) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+
+ /* On a real system, the first 16k is a `secure boot rom' */
+
+ memory_region_allocate_system_memory(ram, NULL, "kzm.ram", ram_size);
+ memory_region_add_subregion(address_space_mem, KZM_RAMADDRESS, ram);
+
+ memory_region_init_alias(ram_alias, NULL, "ram.alias", ram, 0, ram_size);
+ memory_region_add_subregion(address_space_mem, 0x88000000, ram_alias);
+
+ memory_region_init_ram(sram, NULL, "kzm.sram", 0x4000, &error_abort);
+ memory_region_add_subregion(address_space_mem, 0x1FFFC000, sram);
+
+ dev = sysbus_create_varargs("imx_avic", 0x68000000,
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ),
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ),
+ NULL);
+
+ imx_serial_create(0, 0x43f90000, qdev_get_gpio_in(dev, 45));
+ imx_serial_create(1, 0x43f94000, qdev_get_gpio_in(dev, 32));
+
+ ccm = sysbus_create_simple("imx_ccm", 0x53f80000, NULL);
+
+ imx_timerp_create(0x53f94000, qdev_get_gpio_in(dev, 28), ccm);
+ imx_timerp_create(0x53f98000, qdev_get_gpio_in(dev, 27), ccm);
+ imx_timerg_create(0x53f90000, qdev_get_gpio_in(dev, 29), ccm);
+
+ if (nd_table[0].used) {
+ lan9118_init(&nd_table[0], 0xb6000000, qdev_get_gpio_in(dev, 52));
+ }
+
+ if (serial_hds[2]) { /* touchscreen */
+ serial_mm_init(address_space_mem, KZM_FPGA+0x10, 0,
+ qdev_get_gpio_in(dev, 52),
+ 14745600, serial_hds[2],
+ DEVICE_NATIVE_ENDIAN);
+ }
+
+ kzm_binfo.ram_size = ram_size;
+ kzm_binfo.kernel_filename = kernel_filename;
+ kzm_binfo.kernel_cmdline = kernel_cmdline;
+ kzm_binfo.initrd_filename = initrd_filename;
+ kzm_binfo.nb_cpus = 1;
+ arm_load_kernel(cpu, &kzm_binfo);
+}
+
+static QEMUMachine kzm_machine = {
+ .name = "kzm",
+ .desc = "ARM KZM Emulation Baseboard (ARM1136)",
+ .init = kzm_init,
+};
+
+static void kzm_machine_init(void)
+{
+ qemu_register_machine(&kzm_machine);
+}
+
+machine_init(kzm_machine_init)
diff --git a/hw/arm/mainstone.c b/hw/arm/mainstone.c
new file mode 100644
index 00000000..0da02a67
--- /dev/null
+++ b/hw/arm/mainstone.c
@@ -0,0 +1,202 @@
+/*
+ * PXA270-based Intel Mainstone platforms.
+ *
+ * Copyright (c) 2007 by Armin Kuster <akuster@kama-aina.net> or
+ * <akuster@mvista.com>
+ *
+ * Code based on spitz platform by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "hw/arm/arm.h"
+#include "net/net.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "sysemu/qtest.h"
+
+/* Device addresses */
+#define MST_FPGA_PHYS 0x08000000
+#define MST_ETH_PHYS 0x10000300
+#define MST_FLASH_0 0x00000000
+#define MST_FLASH_1 0x04000000
+
+/* IRQ definitions */
+#define MMC_IRQ 0
+#define USIM_IRQ 1
+#define USBC_IRQ 2
+#define ETHERNET_IRQ 3
+#define AC97_IRQ 4
+#define PEN_IRQ 5
+#define MSINS_IRQ 6
+#define EXBRD_IRQ 7
+#define S0_CD_IRQ 9
+#define S0_STSCHG_IRQ 10
+#define S0_IRQ 11
+#define S1_CD_IRQ 13
+#define S1_STSCHG_IRQ 14
+#define S1_IRQ 15
+
+static const struct keymap map[0xE0] = {
+ [0 ... 0xDF] = { -1, -1 },
+ [0x1e] = {0,0}, /* a */
+ [0x30] = {0,1}, /* b */
+ [0x2e] = {0,2}, /* c */
+ [0x20] = {0,3}, /* d */
+ [0x12] = {0,4}, /* e */
+ [0x21] = {0,5}, /* f */
+ [0x22] = {1,0}, /* g */
+ [0x23] = {1,1}, /* h */
+ [0x17] = {1,2}, /* i */
+ [0x24] = {1,3}, /* j */
+ [0x25] = {1,4}, /* k */
+ [0x26] = {1,5}, /* l */
+ [0x32] = {2,0}, /* m */
+ [0x31] = {2,1}, /* n */
+ [0x18] = {2,2}, /* o */
+ [0x19] = {2,3}, /* p */
+ [0x10] = {2,4}, /* q */
+ [0x13] = {2,5}, /* r */
+ [0x1f] = {3,0}, /* s */
+ [0x14] = {3,1}, /* t */
+ [0x16] = {3,2}, /* u */
+ [0x2f] = {3,3}, /* v */
+ [0x11] = {3,4}, /* w */
+ [0x2d] = {3,5}, /* x */
+ [0x15] = {4,2}, /* y */
+ [0x2c] = {4,3}, /* z */
+ [0xc7] = {5,0}, /* Home */
+ [0x2a] = {5,1}, /* shift */
+ /*
+ * There are two matrix positions which map to space,
+ * but QEMU can only use one of them for the reverse
+ * mapping, so simply use the second one.
+ */
+ /* [0x39] = {5,2}, space */
+ [0x39] = {5,3}, /* space */
+ /*
+ * Matrix position {5,4} and other keys are missing here.
+ * TODO: Compare with Linux code and test real hardware.
+ */
+ [0x1c] = {5,5}, /* enter (TODO: might be wrong) */
+ [0xc8] = {6,0}, /* up */
+ [0xd0] = {6,1}, /* down */
+ [0xcb] = {6,2}, /* left */
+ [0xcd] = {6,3}, /* right */
+};
+
+enum mainstone_model_e { mainstone };
+
+#define MAINSTONE_RAM 0x04000000
+#define MAINSTONE_ROM 0x00800000
+#define MAINSTONE_FLASH 0x02000000
+
+static struct arm_boot_info mainstone_binfo = {
+ .loader_start = PXA2XX_SDRAM_BASE,
+ .ram_size = 0x04000000,
+};
+
+static void mainstone_common_init(MemoryRegion *address_space_mem,
+ MachineState *machine,
+ enum mainstone_model_e model, int arm_id)
+{
+ uint32_t sector_len = 256 * 1024;
+ hwaddr mainstone_flash_base[] = { MST_FLASH_0, MST_FLASH_1 };
+ PXA2xxState *mpu;
+ DeviceState *mst_irq;
+ DriveInfo *dinfo;
+ int i;
+ int be;
+ MemoryRegion *rom = g_new(MemoryRegion, 1);
+ const char *cpu_model = machine->cpu_model;
+
+ if (!cpu_model)
+ cpu_model = "pxa270-c5";
+
+ /* Setup CPU & memory */
+ mpu = pxa270_init(address_space_mem, mainstone_binfo.ram_size, cpu_model);
+ memory_region_init_ram(rom, NULL, "mainstone.rom", MAINSTONE_ROM,
+ &error_abort);
+ vmstate_register_ram_global(rom);
+ memory_region_set_readonly(rom, true);
+ memory_region_add_subregion(address_space_mem, 0, rom);
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ be = 1;
+#else
+ be = 0;
+#endif
+ /* There are two 32MiB flash devices on the board */
+ for (i = 0; i < 2; i ++) {
+ dinfo = drive_get(IF_PFLASH, 0, i);
+ if (!dinfo) {
+ if (qtest_enabled()) {
+ break;
+ }
+ fprintf(stderr, "Two flash images must be given with the "
+ "'pflash' parameter\n");
+ exit(1);
+ }
+
+ if (!pflash_cfi01_register(mainstone_flash_base[i], NULL,
+ i ? "mainstone.flash1" : "mainstone.flash0",
+ MAINSTONE_FLASH,
+ blk_by_legacy_dinfo(dinfo),
+ sector_len, MAINSTONE_FLASH / sector_len,
+ 4, 0, 0, 0, 0, be)) {
+ fprintf(stderr, "qemu: Error registering flash memory.\n");
+ exit(1);
+ }
+ }
+
+ mst_irq = sysbus_create_simple("mainstone-fpga", MST_FPGA_PHYS,
+ qdev_get_gpio_in(mpu->gpio, 0));
+
+ /* setup keypad */
+ pxa27x_register_keypad(mpu->kp, map, 0xe0);
+
+ /* MMC/SD host */
+ pxa2xx_mmci_handlers(mpu->mmc, NULL, qdev_get_gpio_in(mst_irq, MMC_IRQ));
+
+ pxa2xx_pcmcia_set_irq_cb(mpu->pcmcia[0],
+ qdev_get_gpio_in(mst_irq, S0_IRQ),
+ qdev_get_gpio_in(mst_irq, S0_CD_IRQ));
+ pxa2xx_pcmcia_set_irq_cb(mpu->pcmcia[1],
+ qdev_get_gpio_in(mst_irq, S1_IRQ),
+ qdev_get_gpio_in(mst_irq, S1_CD_IRQ));
+
+ smc91c111_init(&nd_table[0], MST_ETH_PHYS,
+ qdev_get_gpio_in(mst_irq, ETHERNET_IRQ));
+
+ mainstone_binfo.kernel_filename = machine->kernel_filename;
+ mainstone_binfo.kernel_cmdline = machine->kernel_cmdline;
+ mainstone_binfo.initrd_filename = machine->initrd_filename;
+ mainstone_binfo.board_id = arm_id;
+ arm_load_kernel(mpu->cpu, &mainstone_binfo);
+}
+
+static void mainstone_init(MachineState *machine)
+{
+ mainstone_common_init(get_system_memory(), machine, mainstone, 0x196);
+}
+
+static QEMUMachine mainstone2_machine = {
+ .name = "mainstone",
+ .desc = "Mainstone II (PXA27x)",
+ .init = mainstone_init,
+};
+
+static void mainstone_machine_init(void)
+{
+ qemu_register_machine(&mainstone2_machine);
+}
+
+machine_init(mainstone_machine_init);
diff --git a/hw/arm/musicpal.c b/hw/arm/musicpal.c
new file mode 100644
index 00000000..42f66b33
--- /dev/null
+++ b/hw/arm/musicpal.c
@@ -0,0 +1,1752 @@
+/*
+ * Marvell MV88W8618 / Freecom MusicPal emulation.
+ *
+ * Copyright (c) 2008 Jan Kiszka
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/char/serial.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "hw/block/flash.h"
+#include "ui/console.h"
+#include "hw/i2c/i2c.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "ui/pixel_ops.h"
+
+#define MP_MISC_BASE 0x80002000
+#define MP_MISC_SIZE 0x00001000
+
+#define MP_ETH_BASE 0x80008000
+#define MP_ETH_SIZE 0x00001000
+
+#define MP_WLAN_BASE 0x8000C000
+#define MP_WLAN_SIZE 0x00000800
+
+#define MP_UART1_BASE 0x8000C840
+#define MP_UART2_BASE 0x8000C940
+
+#define MP_GPIO_BASE 0x8000D000
+#define MP_GPIO_SIZE 0x00001000
+
+#define MP_FLASHCFG_BASE 0x90006000
+#define MP_FLASHCFG_SIZE 0x00001000
+
+#define MP_AUDIO_BASE 0x90007000
+
+#define MP_PIC_BASE 0x90008000
+#define MP_PIC_SIZE 0x00001000
+
+#define MP_PIT_BASE 0x90009000
+#define MP_PIT_SIZE 0x00001000
+
+#define MP_LCD_BASE 0x9000c000
+#define MP_LCD_SIZE 0x00001000
+
+#define MP_SRAM_BASE 0xC0000000
+#define MP_SRAM_SIZE 0x00020000
+
+#define MP_RAM_DEFAULT_SIZE 32*1024*1024
+#define MP_FLASH_SIZE_MAX 32*1024*1024
+
+#define MP_TIMER1_IRQ 4
+#define MP_TIMER2_IRQ 5
+#define MP_TIMER3_IRQ 6
+#define MP_TIMER4_IRQ 7
+#define MP_EHCI_IRQ 8
+#define MP_ETH_IRQ 9
+#define MP_UART1_IRQ 11
+#define MP_UART2_IRQ 11
+#define MP_GPIO_IRQ 12
+#define MP_RTC_IRQ 28
+#define MP_AUDIO_IRQ 30
+
+/* Wolfson 8750 I2C address */
+#define MP_WM_ADDR 0x1A
+
+/* Ethernet register offsets */
+#define MP_ETH_SMIR 0x010
+#define MP_ETH_PCXR 0x408
+#define MP_ETH_SDCMR 0x448
+#define MP_ETH_ICR 0x450
+#define MP_ETH_IMR 0x458
+#define MP_ETH_FRDP0 0x480
+#define MP_ETH_FRDP1 0x484
+#define MP_ETH_FRDP2 0x488
+#define MP_ETH_FRDP3 0x48C
+#define MP_ETH_CRDP0 0x4A0
+#define MP_ETH_CRDP1 0x4A4
+#define MP_ETH_CRDP2 0x4A8
+#define MP_ETH_CRDP3 0x4AC
+#define MP_ETH_CTDP0 0x4E0
+#define MP_ETH_CTDP1 0x4E4
+
+/* MII PHY access */
+#define MP_ETH_SMIR_DATA 0x0000FFFF
+#define MP_ETH_SMIR_ADDR 0x03FF0000
+#define MP_ETH_SMIR_OPCODE (1 << 26) /* Read value */
+#define MP_ETH_SMIR_RDVALID (1 << 27)
+
+/* PHY registers */
+#define MP_ETH_PHY1_BMSR 0x00210000
+#define MP_ETH_PHY1_PHYSID1 0x00410000
+#define MP_ETH_PHY1_PHYSID2 0x00610000
+
+#define MP_PHY_BMSR_LINK 0x0004
+#define MP_PHY_BMSR_AUTONEG 0x0008
+
+#define MP_PHY_88E3015 0x01410E20
+
+/* TX descriptor status */
+#define MP_ETH_TX_OWN (1U << 31)
+
+/* RX descriptor status */
+#define MP_ETH_RX_OWN (1U << 31)
+
+/* Interrupt cause/mask bits */
+#define MP_ETH_IRQ_RX_BIT 0
+#define MP_ETH_IRQ_RX (1 << MP_ETH_IRQ_RX_BIT)
+#define MP_ETH_IRQ_TXHI_BIT 2
+#define MP_ETH_IRQ_TXLO_BIT 3
+
+/* Port config bits */
+#define MP_ETH_PCXR_2BSM_BIT 28 /* 2-byte incoming suffix */
+
+/* SDMA command bits */
+#define MP_ETH_CMD_TXHI (1 << 23)
+#define MP_ETH_CMD_TXLO (1 << 22)
+
+typedef struct mv88w8618_tx_desc {
+ uint32_t cmdstat;
+ uint16_t res;
+ uint16_t bytes;
+ uint32_t buffer;
+ uint32_t next;
+} mv88w8618_tx_desc;
+
+typedef struct mv88w8618_rx_desc {
+ uint32_t cmdstat;
+ uint16_t bytes;
+ uint16_t buffer_size;
+ uint32_t buffer;
+ uint32_t next;
+} mv88w8618_rx_desc;
+
+#define TYPE_MV88W8618_ETH "mv88w8618_eth"
+#define MV88W8618_ETH(obj) \
+ OBJECT_CHECK(mv88w8618_eth_state, (obj), TYPE_MV88W8618_ETH)
+
+typedef struct mv88w8618_eth_state {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ uint32_t smir;
+ uint32_t icr;
+ uint32_t imr;
+ int mmio_index;
+ uint32_t vlan_header;
+ uint32_t tx_queue[2];
+ uint32_t rx_queue[4];
+ uint32_t frx_queue[4];
+ uint32_t cur_rx[4];
+ NICState *nic;
+ NICConf conf;
+} mv88w8618_eth_state;
+
+static void eth_rx_desc_put(uint32_t addr, mv88w8618_rx_desc *desc)
+{
+ cpu_to_le32s(&desc->cmdstat);
+ cpu_to_le16s(&desc->bytes);
+ cpu_to_le16s(&desc->buffer_size);
+ cpu_to_le32s(&desc->buffer);
+ cpu_to_le32s(&desc->next);
+ cpu_physical_memory_write(addr, desc, sizeof(*desc));
+}
+
+static void eth_rx_desc_get(uint32_t addr, mv88w8618_rx_desc *desc)
+{
+ cpu_physical_memory_read(addr, desc, sizeof(*desc));
+ le32_to_cpus(&desc->cmdstat);
+ le16_to_cpus(&desc->bytes);
+ le16_to_cpus(&desc->buffer_size);
+ le32_to_cpus(&desc->buffer);
+ le32_to_cpus(&desc->next);
+}
+
+static ssize_t eth_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ mv88w8618_eth_state *s = qemu_get_nic_opaque(nc);
+ uint32_t desc_addr;
+ mv88w8618_rx_desc desc;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ desc_addr = s->cur_rx[i];
+ if (!desc_addr) {
+ continue;
+ }
+ do {
+ eth_rx_desc_get(desc_addr, &desc);
+ if ((desc.cmdstat & MP_ETH_RX_OWN) && desc.buffer_size >= size) {
+ cpu_physical_memory_write(desc.buffer + s->vlan_header,
+ buf, size);
+ desc.bytes = size + s->vlan_header;
+ desc.cmdstat &= ~MP_ETH_RX_OWN;
+ s->cur_rx[i] = desc.next;
+
+ s->icr |= MP_ETH_IRQ_RX;
+ if (s->icr & s->imr) {
+ qemu_irq_raise(s->irq);
+ }
+ eth_rx_desc_put(desc_addr, &desc);
+ return size;
+ }
+ desc_addr = desc.next;
+ } while (desc_addr != s->rx_queue[i]);
+ }
+ return size;
+}
+
+static void eth_tx_desc_put(uint32_t addr, mv88w8618_tx_desc *desc)
+{
+ cpu_to_le32s(&desc->cmdstat);
+ cpu_to_le16s(&desc->res);
+ cpu_to_le16s(&desc->bytes);
+ cpu_to_le32s(&desc->buffer);
+ cpu_to_le32s(&desc->next);
+ cpu_physical_memory_write(addr, desc, sizeof(*desc));
+}
+
+static void eth_tx_desc_get(uint32_t addr, mv88w8618_tx_desc *desc)
+{
+ cpu_physical_memory_read(addr, desc, sizeof(*desc));
+ le32_to_cpus(&desc->cmdstat);
+ le16_to_cpus(&desc->res);
+ le16_to_cpus(&desc->bytes);
+ le32_to_cpus(&desc->buffer);
+ le32_to_cpus(&desc->next);
+}
+
+static void eth_send(mv88w8618_eth_state *s, int queue_index)
+{
+ uint32_t desc_addr = s->tx_queue[queue_index];
+ mv88w8618_tx_desc desc;
+ uint32_t next_desc;
+ uint8_t buf[2048];
+ int len;
+
+ do {
+ eth_tx_desc_get(desc_addr, &desc);
+ next_desc = desc.next;
+ if (desc.cmdstat & MP_ETH_TX_OWN) {
+ len = desc.bytes;
+ if (len < 2048) {
+ cpu_physical_memory_read(desc.buffer, buf, len);
+ qemu_send_packet(qemu_get_queue(s->nic), buf, len);
+ }
+ desc.cmdstat &= ~MP_ETH_TX_OWN;
+ s->icr |= 1 << (MP_ETH_IRQ_TXLO_BIT - queue_index);
+ eth_tx_desc_put(desc_addr, &desc);
+ }
+ desc_addr = next_desc;
+ } while (desc_addr != s->tx_queue[queue_index]);
+}
+
+static uint64_t mv88w8618_eth_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ mv88w8618_eth_state *s = opaque;
+
+ switch (offset) {
+ case MP_ETH_SMIR:
+ if (s->smir & MP_ETH_SMIR_OPCODE) {
+ switch (s->smir & MP_ETH_SMIR_ADDR) {
+ case MP_ETH_PHY1_BMSR:
+ return MP_PHY_BMSR_LINK | MP_PHY_BMSR_AUTONEG |
+ MP_ETH_SMIR_RDVALID;
+ case MP_ETH_PHY1_PHYSID1:
+ return (MP_PHY_88E3015 >> 16) | MP_ETH_SMIR_RDVALID;
+ case MP_ETH_PHY1_PHYSID2:
+ return (MP_PHY_88E3015 & 0xFFFF) | MP_ETH_SMIR_RDVALID;
+ default:
+ return MP_ETH_SMIR_RDVALID;
+ }
+ }
+ return 0;
+
+ case MP_ETH_ICR:
+ return s->icr;
+
+ case MP_ETH_IMR:
+ return s->imr;
+
+ case MP_ETH_FRDP0 ... MP_ETH_FRDP3:
+ return s->frx_queue[(offset - MP_ETH_FRDP0)/4];
+
+ case MP_ETH_CRDP0 ... MP_ETH_CRDP3:
+ return s->rx_queue[(offset - MP_ETH_CRDP0)/4];
+
+ case MP_ETH_CTDP0 ... MP_ETH_CTDP1:
+ return s->tx_queue[(offset - MP_ETH_CTDP0)/4];
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_eth_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ mv88w8618_eth_state *s = opaque;
+
+ switch (offset) {
+ case MP_ETH_SMIR:
+ s->smir = value;
+ break;
+
+ case MP_ETH_PCXR:
+ s->vlan_header = ((value >> MP_ETH_PCXR_2BSM_BIT) & 1) * 2;
+ break;
+
+ case MP_ETH_SDCMR:
+ if (value & MP_ETH_CMD_TXHI) {
+ eth_send(s, 1);
+ }
+ if (value & MP_ETH_CMD_TXLO) {
+ eth_send(s, 0);
+ }
+ if (value & (MP_ETH_CMD_TXHI | MP_ETH_CMD_TXLO) && s->icr & s->imr) {
+ qemu_irq_raise(s->irq);
+ }
+ break;
+
+ case MP_ETH_ICR:
+ s->icr &= value;
+ break;
+
+ case MP_ETH_IMR:
+ s->imr = value;
+ if (s->icr & s->imr) {
+ qemu_irq_raise(s->irq);
+ }
+ break;
+
+ case MP_ETH_FRDP0 ... MP_ETH_FRDP3:
+ s->frx_queue[(offset - MP_ETH_FRDP0)/4] = value;
+ break;
+
+ case MP_ETH_CRDP0 ... MP_ETH_CRDP3:
+ s->rx_queue[(offset - MP_ETH_CRDP0)/4] =
+ s->cur_rx[(offset - MP_ETH_CRDP0)/4] = value;
+ break;
+
+ case MP_ETH_CTDP0 ... MP_ETH_CTDP1:
+ s->tx_queue[(offset - MP_ETH_CTDP0)/4] = value;
+ break;
+ }
+}
+
+static const MemoryRegionOps mv88w8618_eth_ops = {
+ .read = mv88w8618_eth_read,
+ .write = mv88w8618_eth_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void eth_cleanup(NetClientState *nc)
+{
+ mv88w8618_eth_state *s = qemu_get_nic_opaque(nc);
+
+ s->nic = NULL;
+}
+
+static NetClientInfo net_mv88w8618_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = eth_receive,
+ .cleanup = eth_cleanup,
+};
+
+static int mv88w8618_eth_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ mv88w8618_eth_state *s = MV88W8618_ETH(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+ s->nic = qemu_new_nic(&net_mv88w8618_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_eth_ops, s,
+ "mv88w8618-eth", MP_ETH_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ return 0;
+}
+
+static const VMStateDescription mv88w8618_eth_vmsd = {
+ .name = "mv88w8618_eth",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(smir, mv88w8618_eth_state),
+ VMSTATE_UINT32(icr, mv88w8618_eth_state),
+ VMSTATE_UINT32(imr, mv88w8618_eth_state),
+ VMSTATE_UINT32(vlan_header, mv88w8618_eth_state),
+ VMSTATE_UINT32_ARRAY(tx_queue, mv88w8618_eth_state, 2),
+ VMSTATE_UINT32_ARRAY(rx_queue, mv88w8618_eth_state, 4),
+ VMSTATE_UINT32_ARRAY(frx_queue, mv88w8618_eth_state, 4),
+ VMSTATE_UINT32_ARRAY(cur_rx, mv88w8618_eth_state, 4),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property mv88w8618_eth_properties[] = {
+ DEFINE_NIC_PROPERTIES(mv88w8618_eth_state, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mv88w8618_eth_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mv88w8618_eth_init;
+ dc->vmsd = &mv88w8618_eth_vmsd;
+ dc->props = mv88w8618_eth_properties;
+}
+
+static const TypeInfo mv88w8618_eth_info = {
+ .name = TYPE_MV88W8618_ETH,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(mv88w8618_eth_state),
+ .class_init = mv88w8618_eth_class_init,
+};
+
+/* LCD register offsets */
+#define MP_LCD_IRQCTRL 0x180
+#define MP_LCD_IRQSTAT 0x184
+#define MP_LCD_SPICTRL 0x1ac
+#define MP_LCD_INST 0x1bc
+#define MP_LCD_DATA 0x1c0
+
+/* Mode magics */
+#define MP_LCD_SPI_DATA 0x00100011
+#define MP_LCD_SPI_CMD 0x00104011
+#define MP_LCD_SPI_INVALID 0x00000000
+
+/* Commmands */
+#define MP_LCD_INST_SETPAGE0 0xB0
+/* ... */
+#define MP_LCD_INST_SETPAGE7 0xB7
+
+#define MP_LCD_TEXTCOLOR 0xe0e0ff /* RRGGBB */
+
+#define TYPE_MUSICPAL_LCD "musicpal_lcd"
+#define MUSICPAL_LCD(obj) \
+ OBJECT_CHECK(musicpal_lcd_state, (obj), TYPE_MUSICPAL_LCD)
+
+typedef struct musicpal_lcd_state {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t brightness;
+ uint32_t mode;
+ uint32_t irqctrl;
+ uint32_t page;
+ uint32_t page_off;
+ QemuConsole *con;
+ uint8_t video_ram[128*64/8];
+} musicpal_lcd_state;
+
+static uint8_t scale_lcd_color(musicpal_lcd_state *s, uint8_t col)
+{
+ switch (s->brightness) {
+ case 7:
+ return col;
+ case 0:
+ return 0;
+ default:
+ return (col * s->brightness) / 7;
+ }
+}
+
+#define SET_LCD_PIXEL(depth, type) \
+static inline void glue(set_lcd_pixel, depth) \
+ (musicpal_lcd_state *s, int x, int y, type col) \
+{ \
+ int dx, dy; \
+ DisplaySurface *surface = qemu_console_surface(s->con); \
+ type *pixel = &((type *) surface_data(surface))[(y * 128 * 3 + x) * 3]; \
+\
+ for (dy = 0; dy < 3; dy++, pixel += 127 * 3) \
+ for (dx = 0; dx < 3; dx++, pixel++) \
+ *pixel = col; \
+}
+SET_LCD_PIXEL(8, uint8_t)
+SET_LCD_PIXEL(16, uint16_t)
+SET_LCD_PIXEL(32, uint32_t)
+
+static void lcd_refresh(void *opaque)
+{
+ musicpal_lcd_state *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int x, y, col;
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ return;
+#define LCD_REFRESH(depth, func) \
+ case depth: \
+ col = func(scale_lcd_color(s, (MP_LCD_TEXTCOLOR >> 16) & 0xff), \
+ scale_lcd_color(s, (MP_LCD_TEXTCOLOR >> 8) & 0xff), \
+ scale_lcd_color(s, MP_LCD_TEXTCOLOR & 0xff)); \
+ for (x = 0; x < 128; x++) { \
+ for (y = 0; y < 64; y++) { \
+ if (s->video_ram[x + (y/8)*128] & (1 << (y % 8))) { \
+ glue(set_lcd_pixel, depth)(s, x, y, col); \
+ } else { \
+ glue(set_lcd_pixel, depth)(s, x, y, 0); \
+ } \
+ } \
+ } \
+ break;
+ LCD_REFRESH(8, rgb_to_pixel8)
+ LCD_REFRESH(16, rgb_to_pixel16)
+ LCD_REFRESH(32, (is_surface_bgr(surface) ?
+ rgb_to_pixel32bgr : rgb_to_pixel32))
+ default:
+ hw_error("unsupported colour depth %i\n",
+ surface_bits_per_pixel(surface));
+ }
+
+ dpy_gfx_update(s->con, 0, 0, 128*3, 64*3);
+}
+
+static void lcd_invalidate(void *opaque)
+{
+}
+
+static void musicpal_lcd_gpio_brightness_in(void *opaque, int irq, int level)
+{
+ musicpal_lcd_state *s = opaque;
+ s->brightness &= ~(1 << irq);
+ s->brightness |= level << irq;
+}
+
+static uint64_t musicpal_lcd_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ musicpal_lcd_state *s = opaque;
+
+ switch (offset) {
+ case MP_LCD_IRQCTRL:
+ return s->irqctrl;
+
+ default:
+ return 0;
+ }
+}
+
+static void musicpal_lcd_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ musicpal_lcd_state *s = opaque;
+
+ switch (offset) {
+ case MP_LCD_IRQCTRL:
+ s->irqctrl = value;
+ break;
+
+ case MP_LCD_SPICTRL:
+ if (value == MP_LCD_SPI_DATA || value == MP_LCD_SPI_CMD) {
+ s->mode = value;
+ } else {
+ s->mode = MP_LCD_SPI_INVALID;
+ }
+ break;
+
+ case MP_LCD_INST:
+ if (value >= MP_LCD_INST_SETPAGE0 && value <= MP_LCD_INST_SETPAGE7) {
+ s->page = value - MP_LCD_INST_SETPAGE0;
+ s->page_off = 0;
+ }
+ break;
+
+ case MP_LCD_DATA:
+ if (s->mode == MP_LCD_SPI_CMD) {
+ if (value >= MP_LCD_INST_SETPAGE0 &&
+ value <= MP_LCD_INST_SETPAGE7) {
+ s->page = value - MP_LCD_INST_SETPAGE0;
+ s->page_off = 0;
+ }
+ } else if (s->mode == MP_LCD_SPI_DATA) {
+ s->video_ram[s->page*128 + s->page_off] = value;
+ s->page_off = (s->page_off + 1) & 127;
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps musicpal_lcd_ops = {
+ .read = musicpal_lcd_read,
+ .write = musicpal_lcd_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const GraphicHwOps musicpal_gfx_ops = {
+ .invalidate = lcd_invalidate,
+ .gfx_update = lcd_refresh,
+};
+
+static int musicpal_lcd_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ musicpal_lcd_state *s = MUSICPAL_LCD(dev);
+
+ s->brightness = 7;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &musicpal_lcd_ops, s,
+ "musicpal-lcd", MP_LCD_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->con = graphic_console_init(dev, 0, &musicpal_gfx_ops, s);
+ qemu_console_resize(s->con, 128*3, 64*3);
+
+ qdev_init_gpio_in(dev, musicpal_lcd_gpio_brightness_in, 3);
+
+ return 0;
+}
+
+static const VMStateDescription musicpal_lcd_vmsd = {
+ .name = "musicpal_lcd",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(brightness, musicpal_lcd_state),
+ VMSTATE_UINT32(mode, musicpal_lcd_state),
+ VMSTATE_UINT32(irqctrl, musicpal_lcd_state),
+ VMSTATE_UINT32(page, musicpal_lcd_state),
+ VMSTATE_UINT32(page_off, musicpal_lcd_state),
+ VMSTATE_BUFFER(video_ram, musicpal_lcd_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void musicpal_lcd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = musicpal_lcd_init;
+ dc->vmsd = &musicpal_lcd_vmsd;
+}
+
+static const TypeInfo musicpal_lcd_info = {
+ .name = TYPE_MUSICPAL_LCD,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(musicpal_lcd_state),
+ .class_init = musicpal_lcd_class_init,
+};
+
+/* PIC register offsets */
+#define MP_PIC_STATUS 0x00
+#define MP_PIC_ENABLE_SET 0x08
+#define MP_PIC_ENABLE_CLR 0x0C
+
+#define TYPE_MV88W8618_PIC "mv88w8618_pic"
+#define MV88W8618_PIC(obj) \
+ OBJECT_CHECK(mv88w8618_pic_state, (obj), TYPE_MV88W8618_PIC)
+
+typedef struct mv88w8618_pic_state {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t level;
+ uint32_t enabled;
+ qemu_irq parent_irq;
+} mv88w8618_pic_state;
+
+static void mv88w8618_pic_update(mv88w8618_pic_state *s)
+{
+ qemu_set_irq(s->parent_irq, (s->level & s->enabled));
+}
+
+static void mv88w8618_pic_set_irq(void *opaque, int irq, int level)
+{
+ mv88w8618_pic_state *s = opaque;
+
+ if (level) {
+ s->level |= 1 << irq;
+ } else {
+ s->level &= ~(1 << irq);
+ }
+ mv88w8618_pic_update(s);
+}
+
+static uint64_t mv88w8618_pic_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ mv88w8618_pic_state *s = opaque;
+
+ switch (offset) {
+ case MP_PIC_STATUS:
+ return s->level & s->enabled;
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_pic_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ mv88w8618_pic_state *s = opaque;
+
+ switch (offset) {
+ case MP_PIC_ENABLE_SET:
+ s->enabled |= value;
+ break;
+
+ case MP_PIC_ENABLE_CLR:
+ s->enabled &= ~value;
+ s->level &= ~value;
+ break;
+ }
+ mv88w8618_pic_update(s);
+}
+
+static void mv88w8618_pic_reset(DeviceState *d)
+{
+ mv88w8618_pic_state *s = MV88W8618_PIC(d);
+
+ s->level = 0;
+ s->enabled = 0;
+}
+
+static const MemoryRegionOps mv88w8618_pic_ops = {
+ .read = mv88w8618_pic_read,
+ .write = mv88w8618_pic_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int mv88w8618_pic_init(SysBusDevice *dev)
+{
+ mv88w8618_pic_state *s = MV88W8618_PIC(dev);
+
+ qdev_init_gpio_in(DEVICE(dev), mv88w8618_pic_set_irq, 32);
+ sysbus_init_irq(dev, &s->parent_irq);
+ memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_pic_ops, s,
+ "musicpal-pic", MP_PIC_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static const VMStateDescription mv88w8618_pic_vmsd = {
+ .name = "mv88w8618_pic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(level, mv88w8618_pic_state),
+ VMSTATE_UINT32(enabled, mv88w8618_pic_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mv88w8618_pic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mv88w8618_pic_init;
+ dc->reset = mv88w8618_pic_reset;
+ dc->vmsd = &mv88w8618_pic_vmsd;
+}
+
+static const TypeInfo mv88w8618_pic_info = {
+ .name = TYPE_MV88W8618_PIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(mv88w8618_pic_state),
+ .class_init = mv88w8618_pic_class_init,
+};
+
+/* PIT register offsets */
+#define MP_PIT_TIMER1_LENGTH 0x00
+/* ... */
+#define MP_PIT_TIMER4_LENGTH 0x0C
+#define MP_PIT_CONTROL 0x10
+#define MP_PIT_TIMER1_VALUE 0x14
+/* ... */
+#define MP_PIT_TIMER4_VALUE 0x20
+#define MP_BOARD_RESET 0x34
+
+/* Magic board reset value (probably some watchdog behind it) */
+#define MP_BOARD_RESET_MAGIC 0x10000
+
+typedef struct mv88w8618_timer_state {
+ ptimer_state *ptimer;
+ uint32_t limit;
+ int freq;
+ qemu_irq irq;
+} mv88w8618_timer_state;
+
+#define TYPE_MV88W8618_PIT "mv88w8618_pit"
+#define MV88W8618_PIT(obj) \
+ OBJECT_CHECK(mv88w8618_pit_state, (obj), TYPE_MV88W8618_PIT)
+
+typedef struct mv88w8618_pit_state {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ mv88w8618_timer_state timer[4];
+} mv88w8618_pit_state;
+
+static void mv88w8618_timer_tick(void *opaque)
+{
+ mv88w8618_timer_state *s = opaque;
+
+ qemu_irq_raise(s->irq);
+}
+
+static void mv88w8618_timer_init(SysBusDevice *dev, mv88w8618_timer_state *s,
+ uint32_t freq)
+{
+ QEMUBH *bh;
+
+ sysbus_init_irq(dev, &s->irq);
+ s->freq = freq;
+
+ bh = qemu_bh_new(mv88w8618_timer_tick, s);
+ s->ptimer = ptimer_init(bh);
+}
+
+static uint64_t mv88w8618_pit_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ mv88w8618_pit_state *s = opaque;
+ mv88w8618_timer_state *t;
+
+ switch (offset) {
+ case MP_PIT_TIMER1_VALUE ... MP_PIT_TIMER4_VALUE:
+ t = &s->timer[(offset-MP_PIT_TIMER1_VALUE) >> 2];
+ return ptimer_get_count(t->ptimer);
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_pit_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ mv88w8618_pit_state *s = opaque;
+ mv88w8618_timer_state *t;
+ int i;
+
+ switch (offset) {
+ case MP_PIT_TIMER1_LENGTH ... MP_PIT_TIMER4_LENGTH:
+ t = &s->timer[offset >> 2];
+ t->limit = value;
+ if (t->limit > 0) {
+ ptimer_set_limit(t->ptimer, t->limit, 1);
+ } else {
+ ptimer_stop(t->ptimer);
+ }
+ break;
+
+ case MP_PIT_CONTROL:
+ for (i = 0; i < 4; i++) {
+ t = &s->timer[i];
+ if (value & 0xf && t->limit > 0) {
+ ptimer_set_limit(t->ptimer, t->limit, 0);
+ ptimer_set_freq(t->ptimer, t->freq);
+ ptimer_run(t->ptimer, 0);
+ } else {
+ ptimer_stop(t->ptimer);
+ }
+ value >>= 4;
+ }
+ break;
+
+ case MP_BOARD_RESET:
+ if (value == MP_BOARD_RESET_MAGIC) {
+ qemu_system_reset_request();
+ }
+ break;
+ }
+}
+
+static void mv88w8618_pit_reset(DeviceState *d)
+{
+ mv88w8618_pit_state *s = MV88W8618_PIT(d);
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ ptimer_stop(s->timer[i].ptimer);
+ s->timer[i].limit = 0;
+ }
+}
+
+static const MemoryRegionOps mv88w8618_pit_ops = {
+ .read = mv88w8618_pit_read,
+ .write = mv88w8618_pit_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int mv88w8618_pit_init(SysBusDevice *dev)
+{
+ mv88w8618_pit_state *s = MV88W8618_PIT(dev);
+ int i;
+
+ /* Letting them all run at 1 MHz is likely just a pragmatic
+ * simplification. */
+ for (i = 0; i < 4; i++) {
+ mv88w8618_timer_init(dev, &s->timer[i], 1000000);
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_pit_ops, s,
+ "musicpal-pit", MP_PIT_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static const VMStateDescription mv88w8618_timer_vmsd = {
+ .name = "timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PTIMER(ptimer, mv88w8618_timer_state),
+ VMSTATE_UINT32(limit, mv88w8618_timer_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription mv88w8618_pit_vmsd = {
+ .name = "mv88w8618_pit",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(timer, mv88w8618_pit_state, 4, 1,
+ mv88w8618_timer_vmsd, mv88w8618_timer_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mv88w8618_pit_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mv88w8618_pit_init;
+ dc->reset = mv88w8618_pit_reset;
+ dc->vmsd = &mv88w8618_pit_vmsd;
+}
+
+static const TypeInfo mv88w8618_pit_info = {
+ .name = TYPE_MV88W8618_PIT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(mv88w8618_pit_state),
+ .class_init = mv88w8618_pit_class_init,
+};
+
+/* Flash config register offsets */
+#define MP_FLASHCFG_CFGR0 0x04
+
+#define TYPE_MV88W8618_FLASHCFG "mv88w8618_flashcfg"
+#define MV88W8618_FLASHCFG(obj) \
+ OBJECT_CHECK(mv88w8618_flashcfg_state, (obj), TYPE_MV88W8618_FLASHCFG)
+
+typedef struct mv88w8618_flashcfg_state {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t cfgr0;
+} mv88w8618_flashcfg_state;
+
+static uint64_t mv88w8618_flashcfg_read(void *opaque,
+ hwaddr offset,
+ unsigned size)
+{
+ mv88w8618_flashcfg_state *s = opaque;
+
+ switch (offset) {
+ case MP_FLASHCFG_CFGR0:
+ return s->cfgr0;
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_flashcfg_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ mv88w8618_flashcfg_state *s = opaque;
+
+ switch (offset) {
+ case MP_FLASHCFG_CFGR0:
+ s->cfgr0 = value;
+ break;
+ }
+}
+
+static const MemoryRegionOps mv88w8618_flashcfg_ops = {
+ .read = mv88w8618_flashcfg_read,
+ .write = mv88w8618_flashcfg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int mv88w8618_flashcfg_init(SysBusDevice *dev)
+{
+ mv88w8618_flashcfg_state *s = MV88W8618_FLASHCFG(dev);
+
+ s->cfgr0 = 0xfffe4285; /* Default as set by U-Boot for 8 MB flash */
+ memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_flashcfg_ops, s,
+ "musicpal-flashcfg", MP_FLASHCFG_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static const VMStateDescription mv88w8618_flashcfg_vmsd = {
+ .name = "mv88w8618_flashcfg",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cfgr0, mv88w8618_flashcfg_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mv88w8618_flashcfg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mv88w8618_flashcfg_init;
+ dc->vmsd = &mv88w8618_flashcfg_vmsd;
+}
+
+static const TypeInfo mv88w8618_flashcfg_info = {
+ .name = TYPE_MV88W8618_FLASHCFG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(mv88w8618_flashcfg_state),
+ .class_init = mv88w8618_flashcfg_class_init,
+};
+
+/* Misc register offsets */
+#define MP_MISC_BOARD_REVISION 0x18
+
+#define MP_BOARD_REVISION 0x31
+
+typedef struct {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+} MusicPalMiscState;
+
+#define TYPE_MUSICPAL_MISC "musicpal-misc"
+#define MUSICPAL_MISC(obj) \
+ OBJECT_CHECK(MusicPalMiscState, (obj), TYPE_MUSICPAL_MISC)
+
+static uint64_t musicpal_misc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ switch (offset) {
+ case MP_MISC_BOARD_REVISION:
+ return MP_BOARD_REVISION;
+
+ default:
+ return 0;
+ }
+}
+
+static void musicpal_misc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+}
+
+static const MemoryRegionOps musicpal_misc_ops = {
+ .read = musicpal_misc_read,
+ .write = musicpal_misc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void musicpal_misc_init(Object *obj)
+{
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ MusicPalMiscState *s = MUSICPAL_MISC(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &musicpal_misc_ops, NULL,
+ "musicpal-misc", MP_MISC_SIZE);
+ sysbus_init_mmio(sd, &s->iomem);
+}
+
+static const TypeInfo musicpal_misc_info = {
+ .name = TYPE_MUSICPAL_MISC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = musicpal_misc_init,
+ .instance_size = sizeof(MusicPalMiscState),
+};
+
+/* WLAN register offsets */
+#define MP_WLAN_MAGIC1 0x11c
+#define MP_WLAN_MAGIC2 0x124
+
+static uint64_t mv88w8618_wlan_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ switch (offset) {
+ /* Workaround to allow loading the binary-only wlandrv.ko crap
+ * from the original Freecom firmware. */
+ case MP_WLAN_MAGIC1:
+ return ~3;
+ case MP_WLAN_MAGIC2:
+ return -1;
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_wlan_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+}
+
+static const MemoryRegionOps mv88w8618_wlan_ops = {
+ .read = mv88w8618_wlan_read,
+ .write =mv88w8618_wlan_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int mv88w8618_wlan_init(SysBusDevice *dev)
+{
+ MemoryRegion *iomem = g_new(MemoryRegion, 1);
+
+ memory_region_init_io(iomem, OBJECT(dev), &mv88w8618_wlan_ops, NULL,
+ "musicpal-wlan", MP_WLAN_SIZE);
+ sysbus_init_mmio(dev, iomem);
+ return 0;
+}
+
+/* GPIO register offsets */
+#define MP_GPIO_OE_LO 0x008
+#define MP_GPIO_OUT_LO 0x00c
+#define MP_GPIO_IN_LO 0x010
+#define MP_GPIO_IER_LO 0x014
+#define MP_GPIO_IMR_LO 0x018
+#define MP_GPIO_ISR_LO 0x020
+#define MP_GPIO_OE_HI 0x508
+#define MP_GPIO_OUT_HI 0x50c
+#define MP_GPIO_IN_HI 0x510
+#define MP_GPIO_IER_HI 0x514
+#define MP_GPIO_IMR_HI 0x518
+#define MP_GPIO_ISR_HI 0x520
+
+/* GPIO bits & masks */
+#define MP_GPIO_LCD_BRIGHTNESS 0x00070000
+#define MP_GPIO_I2C_DATA_BIT 29
+#define MP_GPIO_I2C_CLOCK_BIT 30
+
+/* LCD brightness bits in GPIO_OE_HI */
+#define MP_OE_LCD_BRIGHTNESS 0x0007
+
+#define TYPE_MUSICPAL_GPIO "musicpal_gpio"
+#define MUSICPAL_GPIO(obj) \
+ OBJECT_CHECK(musicpal_gpio_state, (obj), TYPE_MUSICPAL_GPIO)
+
+typedef struct musicpal_gpio_state {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t lcd_brightness;
+ uint32_t out_state;
+ uint32_t in_state;
+ uint32_t ier;
+ uint32_t imr;
+ uint32_t isr;
+ qemu_irq irq;
+ qemu_irq out[5]; /* 3 brightness out + 2 lcd (data and clock ) */
+} musicpal_gpio_state;
+
+static void musicpal_gpio_brightness_update(musicpal_gpio_state *s) {
+ int i;
+ uint32_t brightness;
+
+ /* compute brightness ratio */
+ switch (s->lcd_brightness) {
+ case 0x00000007:
+ brightness = 0;
+ break;
+
+ case 0x00020000:
+ brightness = 1;
+ break;
+
+ case 0x00020001:
+ brightness = 2;
+ break;
+
+ case 0x00040000:
+ brightness = 3;
+ break;
+
+ case 0x00010006:
+ brightness = 4;
+ break;
+
+ case 0x00020005:
+ brightness = 5;
+ break;
+
+ case 0x00040003:
+ brightness = 6;
+ break;
+
+ case 0x00030004:
+ default:
+ brightness = 7;
+ }
+
+ /* set lcd brightness GPIOs */
+ for (i = 0; i <= 2; i++) {
+ qemu_set_irq(s->out[i], (brightness >> i) & 1);
+ }
+}
+
+static void musicpal_gpio_pin_event(void *opaque, int pin, int level)
+{
+ musicpal_gpio_state *s = opaque;
+ uint32_t mask = 1 << pin;
+ uint32_t delta = level << pin;
+ uint32_t old = s->in_state & mask;
+
+ s->in_state &= ~mask;
+ s->in_state |= delta;
+
+ if ((old ^ delta) &&
+ ((level && (s->imr & mask)) || (!level && (s->ier & mask)))) {
+ s->isr = mask;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static uint64_t musicpal_gpio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ musicpal_gpio_state *s = opaque;
+
+ switch (offset) {
+ case MP_GPIO_OE_HI: /* used for LCD brightness control */
+ return s->lcd_brightness & MP_OE_LCD_BRIGHTNESS;
+
+ case MP_GPIO_OUT_LO:
+ return s->out_state & 0xFFFF;
+ case MP_GPIO_OUT_HI:
+ return s->out_state >> 16;
+
+ case MP_GPIO_IN_LO:
+ return s->in_state & 0xFFFF;
+ case MP_GPIO_IN_HI:
+ return s->in_state >> 16;
+
+ case MP_GPIO_IER_LO:
+ return s->ier & 0xFFFF;
+ case MP_GPIO_IER_HI:
+ return s->ier >> 16;
+
+ case MP_GPIO_IMR_LO:
+ return s->imr & 0xFFFF;
+ case MP_GPIO_IMR_HI:
+ return s->imr >> 16;
+
+ case MP_GPIO_ISR_LO:
+ return s->isr & 0xFFFF;
+ case MP_GPIO_ISR_HI:
+ return s->isr >> 16;
+
+ default:
+ return 0;
+ }
+}
+
+static void musicpal_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ musicpal_gpio_state *s = opaque;
+ switch (offset) {
+ case MP_GPIO_OE_HI: /* used for LCD brightness control */
+ s->lcd_brightness = (s->lcd_brightness & MP_GPIO_LCD_BRIGHTNESS) |
+ (value & MP_OE_LCD_BRIGHTNESS);
+ musicpal_gpio_brightness_update(s);
+ break;
+
+ case MP_GPIO_OUT_LO:
+ s->out_state = (s->out_state & 0xFFFF0000) | (value & 0xFFFF);
+ break;
+ case MP_GPIO_OUT_HI:
+ s->out_state = (s->out_state & 0xFFFF) | (value << 16);
+ s->lcd_brightness = (s->lcd_brightness & 0xFFFF) |
+ (s->out_state & MP_GPIO_LCD_BRIGHTNESS);
+ musicpal_gpio_brightness_update(s);
+ qemu_set_irq(s->out[3], (s->out_state >> MP_GPIO_I2C_DATA_BIT) & 1);
+ qemu_set_irq(s->out[4], (s->out_state >> MP_GPIO_I2C_CLOCK_BIT) & 1);
+ break;
+
+ case MP_GPIO_IER_LO:
+ s->ier = (s->ier & 0xFFFF0000) | (value & 0xFFFF);
+ break;
+ case MP_GPIO_IER_HI:
+ s->ier = (s->ier & 0xFFFF) | (value << 16);
+ break;
+
+ case MP_GPIO_IMR_LO:
+ s->imr = (s->imr & 0xFFFF0000) | (value & 0xFFFF);
+ break;
+ case MP_GPIO_IMR_HI:
+ s->imr = (s->imr & 0xFFFF) | (value << 16);
+ break;
+ }
+}
+
+static const MemoryRegionOps musicpal_gpio_ops = {
+ .read = musicpal_gpio_read,
+ .write = musicpal_gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void musicpal_gpio_reset(DeviceState *d)
+{
+ musicpal_gpio_state *s = MUSICPAL_GPIO(d);
+
+ s->lcd_brightness = 0;
+ s->out_state = 0;
+ s->in_state = 0xffffffff;
+ s->ier = 0;
+ s->imr = 0;
+ s->isr = 0;
+}
+
+static int musicpal_gpio_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ musicpal_gpio_state *s = MUSICPAL_GPIO(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &musicpal_gpio_ops, s,
+ "musicpal-gpio", MP_GPIO_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ qdev_init_gpio_out(dev, s->out, ARRAY_SIZE(s->out));
+
+ qdev_init_gpio_in(dev, musicpal_gpio_pin_event, 32);
+
+ return 0;
+}
+
+static const VMStateDescription musicpal_gpio_vmsd = {
+ .name = "musicpal_gpio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(lcd_brightness, musicpal_gpio_state),
+ VMSTATE_UINT32(out_state, musicpal_gpio_state),
+ VMSTATE_UINT32(in_state, musicpal_gpio_state),
+ VMSTATE_UINT32(ier, musicpal_gpio_state),
+ VMSTATE_UINT32(imr, musicpal_gpio_state),
+ VMSTATE_UINT32(isr, musicpal_gpio_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void musicpal_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = musicpal_gpio_init;
+ dc->reset = musicpal_gpio_reset;
+ dc->vmsd = &musicpal_gpio_vmsd;
+}
+
+static const TypeInfo musicpal_gpio_info = {
+ .name = TYPE_MUSICPAL_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(musicpal_gpio_state),
+ .class_init = musicpal_gpio_class_init,
+};
+
+/* Keyboard codes & masks */
+#define KEY_RELEASED 0x80
+#define KEY_CODE 0x7f
+
+#define KEYCODE_TAB 0x0f
+#define KEYCODE_ENTER 0x1c
+#define KEYCODE_F 0x21
+#define KEYCODE_M 0x32
+
+#define KEYCODE_EXTENDED 0xe0
+#define KEYCODE_UP 0x48
+#define KEYCODE_DOWN 0x50
+#define KEYCODE_LEFT 0x4b
+#define KEYCODE_RIGHT 0x4d
+
+#define MP_KEY_WHEEL_VOL (1 << 0)
+#define MP_KEY_WHEEL_VOL_INV (1 << 1)
+#define MP_KEY_WHEEL_NAV (1 << 2)
+#define MP_KEY_WHEEL_NAV_INV (1 << 3)
+#define MP_KEY_BTN_FAVORITS (1 << 4)
+#define MP_KEY_BTN_MENU (1 << 5)
+#define MP_KEY_BTN_VOLUME (1 << 6)
+#define MP_KEY_BTN_NAVIGATION (1 << 7)
+
+#define TYPE_MUSICPAL_KEY "musicpal_key"
+#define MUSICPAL_KEY(obj) \
+ OBJECT_CHECK(musicpal_key_state, (obj), TYPE_MUSICPAL_KEY)
+
+typedef struct musicpal_key_state {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t kbd_extended;
+ uint32_t pressed_keys;
+ qemu_irq out[8];
+} musicpal_key_state;
+
+static void musicpal_key_event(void *opaque, int keycode)
+{
+ musicpal_key_state *s = opaque;
+ uint32_t event = 0;
+ int i;
+
+ if (keycode == KEYCODE_EXTENDED) {
+ s->kbd_extended = 1;
+ return;
+ }
+
+ if (s->kbd_extended) {
+ switch (keycode & KEY_CODE) {
+ case KEYCODE_UP:
+ event = MP_KEY_WHEEL_NAV | MP_KEY_WHEEL_NAV_INV;
+ break;
+
+ case KEYCODE_DOWN:
+ event = MP_KEY_WHEEL_NAV;
+ break;
+
+ case KEYCODE_LEFT:
+ event = MP_KEY_WHEEL_VOL | MP_KEY_WHEEL_VOL_INV;
+ break;
+
+ case KEYCODE_RIGHT:
+ event = MP_KEY_WHEEL_VOL;
+ break;
+ }
+ } else {
+ switch (keycode & KEY_CODE) {
+ case KEYCODE_F:
+ event = MP_KEY_BTN_FAVORITS;
+ break;
+
+ case KEYCODE_TAB:
+ event = MP_KEY_BTN_VOLUME;
+ break;
+
+ case KEYCODE_ENTER:
+ event = MP_KEY_BTN_NAVIGATION;
+ break;
+
+ case KEYCODE_M:
+ event = MP_KEY_BTN_MENU;
+ break;
+ }
+ /* Do not repeat already pressed buttons */
+ if (!(keycode & KEY_RELEASED) && (s->pressed_keys & event)) {
+ event = 0;
+ }
+ }
+
+ if (event) {
+ /* Raise GPIO pin first if repeating a key */
+ if (!(keycode & KEY_RELEASED) && (s->pressed_keys & event)) {
+ for (i = 0; i <= 7; i++) {
+ if (event & (1 << i)) {
+ qemu_set_irq(s->out[i], 1);
+ }
+ }
+ }
+ for (i = 0; i <= 7; i++) {
+ if (event & (1 << i)) {
+ qemu_set_irq(s->out[i], !!(keycode & KEY_RELEASED));
+ }
+ }
+ if (keycode & KEY_RELEASED) {
+ s->pressed_keys &= ~event;
+ } else {
+ s->pressed_keys |= event;
+ }
+ }
+
+ s->kbd_extended = 0;
+}
+
+static int musicpal_key_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ musicpal_key_state *s = MUSICPAL_KEY(dev);
+
+ memory_region_init(&s->iomem, OBJECT(s), "dummy", 0);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->kbd_extended = 0;
+ s->pressed_keys = 0;
+
+ qdev_init_gpio_out(dev, s->out, ARRAY_SIZE(s->out));
+
+ qemu_add_kbd_event_handler(musicpal_key_event, s);
+
+ return 0;
+}
+
+static const VMStateDescription musicpal_key_vmsd = {
+ .name = "musicpal_key",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(kbd_extended, musicpal_key_state),
+ VMSTATE_UINT32(pressed_keys, musicpal_key_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void musicpal_key_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = musicpal_key_init;
+ dc->vmsd = &musicpal_key_vmsd;
+}
+
+static const TypeInfo musicpal_key_info = {
+ .name = TYPE_MUSICPAL_KEY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(musicpal_key_state),
+ .class_init = musicpal_key_class_init,
+};
+
+static struct arm_boot_info musicpal_binfo = {
+ .loader_start = 0x0,
+ .board_id = 0x20e,
+};
+
+static void musicpal_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ ARMCPU *cpu;
+ qemu_irq pic[32];
+ DeviceState *dev;
+ DeviceState *i2c_dev;
+ DeviceState *lcd_dev;
+ DeviceState *key_dev;
+ DeviceState *wm8750_dev;
+ SysBusDevice *s;
+ I2CBus *i2c;
+ int i;
+ unsigned long flash_size;
+ DriveInfo *dinfo;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+
+ if (!cpu_model) {
+ cpu_model = "arm926";
+ }
+ cpu = cpu_arm_init(cpu_model);
+ if (!cpu) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+
+ /* For now we use a fixed - the original - RAM size */
+ memory_region_allocate_system_memory(ram, NULL, "musicpal.ram",
+ MP_RAM_DEFAULT_SIZE);
+ memory_region_add_subregion(address_space_mem, 0, ram);
+
+ memory_region_init_ram(sram, NULL, "musicpal.sram", MP_SRAM_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(address_space_mem, MP_SRAM_BASE, sram);
+
+ dev = sysbus_create_simple(TYPE_MV88W8618_PIC, MP_PIC_BASE,
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ));
+ for (i = 0; i < 32; i++) {
+ pic[i] = qdev_get_gpio_in(dev, i);
+ }
+ sysbus_create_varargs(TYPE_MV88W8618_PIT, MP_PIT_BASE, pic[MP_TIMER1_IRQ],
+ pic[MP_TIMER2_IRQ], pic[MP_TIMER3_IRQ],
+ pic[MP_TIMER4_IRQ], NULL);
+
+ if (serial_hds[0]) {
+ serial_mm_init(address_space_mem, MP_UART1_BASE, 2, pic[MP_UART1_IRQ],
+ 1825000, serial_hds[0], DEVICE_NATIVE_ENDIAN);
+ }
+ if (serial_hds[1]) {
+ serial_mm_init(address_space_mem, MP_UART2_BASE, 2, pic[MP_UART2_IRQ],
+ 1825000, serial_hds[1], DEVICE_NATIVE_ENDIAN);
+ }
+
+ /* Register flash */
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ if (dinfo) {
+ BlockBackend *blk = blk_by_legacy_dinfo(dinfo);
+
+ flash_size = blk_getlength(blk);
+ if (flash_size != 8*1024*1024 && flash_size != 16*1024*1024 &&
+ flash_size != 32*1024*1024) {
+ fprintf(stderr, "Invalid flash image size\n");
+ exit(1);
+ }
+
+ /*
+ * The original U-Boot accesses the flash at 0xFE000000 instead of
+ * 0xFF800000 (if there is 8 MB flash). So remap flash access if the
+ * image is smaller than 32 MB.
+ */
+#ifdef TARGET_WORDS_BIGENDIAN
+ pflash_cfi02_register(0x100000000ULL-MP_FLASH_SIZE_MAX, NULL,
+ "musicpal.flash", flash_size,
+ blk, 0x10000, (flash_size + 0xffff) >> 16,
+ MP_FLASH_SIZE_MAX / flash_size,
+ 2, 0x00BF, 0x236D, 0x0000, 0x0000,
+ 0x5555, 0x2AAA, 1);
+#else
+ pflash_cfi02_register(0x100000000ULL-MP_FLASH_SIZE_MAX, NULL,
+ "musicpal.flash", flash_size,
+ blk, 0x10000, (flash_size + 0xffff) >> 16,
+ MP_FLASH_SIZE_MAX / flash_size,
+ 2, 0x00BF, 0x236D, 0x0000, 0x0000,
+ 0x5555, 0x2AAA, 0);
+#endif
+
+ }
+ sysbus_create_simple(TYPE_MV88W8618_FLASHCFG, MP_FLASHCFG_BASE, NULL);
+
+ qemu_check_nic_model(&nd_table[0], "mv88w8618");
+ dev = qdev_create(NULL, TYPE_MV88W8618_ETH);
+ qdev_set_nic_properties(dev, &nd_table[0]);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, MP_ETH_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[MP_ETH_IRQ]);
+
+ sysbus_create_simple("mv88w8618_wlan", MP_WLAN_BASE, NULL);
+
+ sysbus_create_simple(TYPE_MUSICPAL_MISC, MP_MISC_BASE, NULL);
+
+ dev = sysbus_create_simple(TYPE_MUSICPAL_GPIO, MP_GPIO_BASE,
+ pic[MP_GPIO_IRQ]);
+ i2c_dev = sysbus_create_simple("gpio_i2c", -1, NULL);
+ i2c = (I2CBus *)qdev_get_child_bus(i2c_dev, "i2c");
+
+ lcd_dev = sysbus_create_simple(TYPE_MUSICPAL_LCD, MP_LCD_BASE, NULL);
+ key_dev = sysbus_create_simple(TYPE_MUSICPAL_KEY, -1, NULL);
+
+ /* I2C read data */
+ qdev_connect_gpio_out(i2c_dev, 0,
+ qdev_get_gpio_in(dev, MP_GPIO_I2C_DATA_BIT));
+ /* I2C data */
+ qdev_connect_gpio_out(dev, 3, qdev_get_gpio_in(i2c_dev, 0));
+ /* I2C clock */
+ qdev_connect_gpio_out(dev, 4, qdev_get_gpio_in(i2c_dev, 1));
+
+ for (i = 0; i < 3; i++) {
+ qdev_connect_gpio_out(dev, i, qdev_get_gpio_in(lcd_dev, i));
+ }
+ for (i = 0; i < 4; i++) {
+ qdev_connect_gpio_out(key_dev, i, qdev_get_gpio_in(dev, i + 8));
+ }
+ for (i = 4; i < 8; i++) {
+ qdev_connect_gpio_out(key_dev, i, qdev_get_gpio_in(dev, i + 15));
+ }
+
+ wm8750_dev = i2c_create_slave(i2c, "wm8750", MP_WM_ADDR);
+ dev = qdev_create(NULL, "mv88w8618_audio");
+ s = SYS_BUS_DEVICE(dev);
+ qdev_prop_set_ptr(dev, "wm8750", wm8750_dev);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(s, 0, MP_AUDIO_BASE);
+ sysbus_connect_irq(s, 0, pic[MP_AUDIO_IRQ]);
+
+ musicpal_binfo.ram_size = MP_RAM_DEFAULT_SIZE;
+ musicpal_binfo.kernel_filename = kernel_filename;
+ musicpal_binfo.kernel_cmdline = kernel_cmdline;
+ musicpal_binfo.initrd_filename = initrd_filename;
+ arm_load_kernel(cpu, &musicpal_binfo);
+}
+
+static QEMUMachine musicpal_machine = {
+ .name = "musicpal",
+ .desc = "Marvell 88w8618 / MusicPal (ARM926EJ-S)",
+ .init = musicpal_init,
+};
+
+static void musicpal_machine_init(void)
+{
+ qemu_register_machine(&musicpal_machine);
+}
+
+machine_init(musicpal_machine_init);
+
+static void mv88w8618_wlan_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = mv88w8618_wlan_init;
+}
+
+static const TypeInfo mv88w8618_wlan_info = {
+ .name = "mv88w8618_wlan",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .class_init = mv88w8618_wlan_class_init,
+};
+
+static void musicpal_register_types(void)
+{
+ type_register_static(&mv88w8618_pic_info);
+ type_register_static(&mv88w8618_pit_info);
+ type_register_static(&mv88w8618_flashcfg_info);
+ type_register_static(&mv88w8618_eth_info);
+ type_register_static(&mv88w8618_wlan_info);
+ type_register_static(&musicpal_lcd_info);
+ type_register_static(&musicpal_gpio_info);
+ type_register_static(&musicpal_key_info);
+ type_register_static(&musicpal_misc_info);
+}
+
+type_init(musicpal_register_types)
diff --git a/hw/arm/netduino2.c b/hw/arm/netduino2.c
new file mode 100644
index 00000000..8f26780e
--- /dev/null
+++ b/hw/arm/netduino2.c
@@ -0,0 +1,57 @@
+/*
+ * Netduino 2 Machine Model
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * 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.
+ */
+
+#include "hw/boards.h"
+#include "qemu/error-report.h"
+#include "hw/arm/stm32f205_soc.h"
+
+static void netduino2_init(MachineState *machine)
+{
+ DeviceState *dev;
+ Error *err = NULL;
+
+ dev = qdev_create(NULL, TYPE_STM32F205_SOC);
+ if (machine->kernel_filename) {
+ qdev_prop_set_string(dev, "kernel-filename", machine->kernel_filename);
+ }
+ qdev_prop_set_string(dev, "cpu-model", "cortex-m3");
+ object_property_set_bool(OBJECT(dev), true, "realized", &err);
+ if (err != NULL) {
+ error_report("%s", error_get_pretty(err));
+ exit(1);
+ }
+}
+
+static QEMUMachine netduino2_machine = {
+ .name = "netduino2",
+ .desc = "Netduino 2 Machine",
+ .init = netduino2_init,
+};
+
+static void netduino2_machine_init(void)
+{
+ qemu_register_machine(&netduino2_machine);
+}
+
+machine_init(netduino2_machine_init);
diff --git a/hw/arm/nseries.c b/hw/arm/nseries.c
new file mode 100644
index 00000000..a659e852
--- /dev/null
+++ b/hw/arm/nseries.c
@@ -0,0 +1,1436 @@
+/*
+ * Nokia N-series internet tablets.
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "sysemu/sysemu.h"
+#include "hw/arm/omap.h"
+#include "hw/arm/arm.h"
+#include "hw/irq.h"
+#include "ui/console.h"
+#include "hw/boards.h"
+#include "hw/i2c/i2c.h"
+#include "hw/devices.h"
+#include "hw/block/flash.h"
+#include "hw/hw.h"
+#include "hw/bt.h"
+#include "hw/loader.h"
+#include "sysemu/block-backend.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+
+/* Nokia N8x0 support */
+struct n800_s {
+ struct omap_mpu_state_s *mpu;
+
+ struct rfbi_chip_s blizzard;
+ struct {
+ void *opaque;
+ uint32_t (*txrx)(void *opaque, uint32_t value, int len);
+ uWireSlave *chip;
+ } ts;
+
+ int keymap[0x80];
+ DeviceState *kbd;
+
+ DeviceState *usb;
+ void *retu;
+ void *tahvo;
+ DeviceState *nand;
+};
+
+/* GPIO pins */
+#define N8X0_TUSB_ENABLE_GPIO 0
+#define N800_MMC2_WP_GPIO 8
+#define N800_UNKNOWN_GPIO0 9 /* out */
+#define N810_MMC2_VIOSD_GPIO 9
+#define N810_HEADSET_AMP_GPIO 10
+#define N800_CAM_TURN_GPIO 12
+#define N810_GPS_RESET_GPIO 12
+#define N800_BLIZZARD_POWERDOWN_GPIO 15
+#define N800_MMC1_WP_GPIO 23
+#define N810_MMC2_VSD_GPIO 23
+#define N8X0_ONENAND_GPIO 26
+#define N810_BLIZZARD_RESET_GPIO 30
+#define N800_UNKNOWN_GPIO2 53 /* out */
+#define N8X0_TUSB_INT_GPIO 58
+#define N8X0_BT_WKUP_GPIO 61
+#define N8X0_STI_GPIO 62
+#define N8X0_CBUS_SEL_GPIO 64
+#define N8X0_CBUS_DAT_GPIO 65
+#define N8X0_CBUS_CLK_GPIO 66
+#define N8X0_WLAN_IRQ_GPIO 87
+#define N8X0_BT_RESET_GPIO 92
+#define N8X0_TEA5761_CS_GPIO 93
+#define N800_UNKNOWN_GPIO 94
+#define N810_TSC_RESET_GPIO 94
+#define N800_CAM_ACT_GPIO 95
+#define N810_GPS_WAKEUP_GPIO 95
+#define N8X0_MMC_CS_GPIO 96
+#define N8X0_WLAN_PWR_GPIO 97
+#define N8X0_BT_HOST_WKUP_GPIO 98
+#define N810_SPEAKER_AMP_GPIO 101
+#define N810_KB_LOCK_GPIO 102
+#define N800_TSC_TS_GPIO 103
+#define N810_TSC_TS_GPIO 106
+#define N8X0_HEADPHONE_GPIO 107
+#define N8X0_RETU_GPIO 108
+#define N800_TSC_KP_IRQ_GPIO 109
+#define N810_KEYBOARD_GPIO 109
+#define N800_BAT_COVER_GPIO 110
+#define N810_SLIDE_GPIO 110
+#define N8X0_TAHVO_GPIO 111
+#define N800_UNKNOWN_GPIO4 112 /* out */
+#define N810_SLEEPX_LED_GPIO 112
+#define N800_TSC_RESET_GPIO 118 /* ? */
+#define N810_AIC33_RESET_GPIO 118
+#define N800_TSC_UNKNOWN_GPIO 119 /* out */
+#define N8X0_TMP105_GPIO 125
+
+/* Config */
+#define BT_UART 0
+#define XLDR_LL_UART 1
+
+/* Addresses on the I2C bus 0 */
+#define N810_TLV320AIC33_ADDR 0x18 /* Audio CODEC */
+#define N8X0_TCM825x_ADDR 0x29 /* Camera */
+#define N810_LP5521_ADDR 0x32 /* LEDs */
+#define N810_TSL2563_ADDR 0x3d /* Light sensor */
+#define N810_LM8323_ADDR 0x45 /* Keyboard */
+/* Addresses on the I2C bus 1 */
+#define N8X0_TMP105_ADDR 0x48 /* Temperature sensor */
+#define N8X0_MENELAUS_ADDR 0x72 /* Power management */
+
+/* Chipselects on GPMC NOR interface */
+#define N8X0_ONENAND_CS 0
+#define N8X0_USB_ASYNC_CS 1
+#define N8X0_USB_SYNC_CS 4
+
+#define N8X0_BD_ADDR 0x00, 0x1a, 0x89, 0x9e, 0x3e, 0x81
+
+static void n800_mmc_cs_cb(void *opaque, int line, int level)
+{
+ /* TODO: this seems to actually be connected to the menelaus, to
+ * which also both MMC slots connect. */
+ omap_mmc_enable((struct omap_mmc_s *) opaque, !level);
+}
+
+static void n8x0_gpio_setup(struct n800_s *s)
+{
+ qdev_connect_gpio_out(s->mpu->gpio, N8X0_MMC_CS_GPIO,
+ qemu_allocate_irq(n800_mmc_cs_cb, s->mpu->mmc, 0));
+ qemu_irq_lower(qdev_get_gpio_in(s->mpu->gpio, N800_BAT_COVER_GPIO));
+}
+
+#define MAEMO_CAL_HEADER(...) \
+ 'C', 'o', 'n', 'F', 0x02, 0x00, 0x04, 0x00, \
+ __VA_ARGS__, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+static const uint8_t n8x0_cal_wlan_mac[] = {
+ MAEMO_CAL_HEADER('w', 'l', 'a', 'n', '-', 'm', 'a', 'c')
+ 0x1c, 0x00, 0x00, 0x00, 0x47, 0xd6, 0x69, 0xb3,
+ 0x30, 0x08, 0xa0, 0x83, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x89, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00,
+ 0x5d, 0x00, 0x00, 0x00, 0xc1, 0x00, 0x00, 0x00,
+};
+
+static const uint8_t n8x0_cal_bt_id[] = {
+ MAEMO_CAL_HEADER('b', 't', '-', 'i', 'd', 0, 0, 0)
+ 0x0a, 0x00, 0x00, 0x00, 0xa3, 0x4b, 0xf6, 0x96,
+ 0xa8, 0xeb, 0xb2, 0x41, 0x00, 0x00, 0x00, 0x00,
+ N8X0_BD_ADDR,
+};
+
+static void n8x0_nand_setup(struct n800_s *s)
+{
+ char *otp_region;
+ DriveInfo *dinfo;
+
+ s->nand = qdev_create(NULL, "onenand");
+ qdev_prop_set_uint16(s->nand, "manufacturer_id", NAND_MFR_SAMSUNG);
+ /* Either 0x40 or 0x48 are OK for the device ID */
+ qdev_prop_set_uint16(s->nand, "device_id", 0x48);
+ qdev_prop_set_uint16(s->nand, "version_id", 0);
+ qdev_prop_set_int32(s->nand, "shift", 1);
+ dinfo = drive_get(IF_MTD, 0, 0);
+ if (dinfo) {
+ qdev_prop_set_drive_nofail(s->nand, "drive",
+ blk_by_legacy_dinfo(dinfo));
+ }
+ qdev_init_nofail(s->nand);
+ sysbus_connect_irq(SYS_BUS_DEVICE(s->nand), 0,
+ qdev_get_gpio_in(s->mpu->gpio, N8X0_ONENAND_GPIO));
+ omap_gpmc_attach(s->mpu->gpmc, N8X0_ONENAND_CS,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(s->nand), 0));
+ otp_region = onenand_raw_otp(s->nand);
+
+ memcpy(otp_region + 0x000, n8x0_cal_wlan_mac, sizeof(n8x0_cal_wlan_mac));
+ memcpy(otp_region + 0x800, n8x0_cal_bt_id, sizeof(n8x0_cal_bt_id));
+ /* XXX: in theory should also update the OOB for both pages */
+}
+
+static qemu_irq n8x0_system_powerdown;
+
+static void n8x0_powerdown_req(Notifier *n, void *opaque)
+{
+ qemu_irq_raise(n8x0_system_powerdown);
+}
+
+static Notifier n8x0_system_powerdown_notifier = {
+ .notify = n8x0_powerdown_req
+};
+
+static void n8x0_i2c_setup(struct n800_s *s)
+{
+ DeviceState *dev;
+ qemu_irq tmp_irq = qdev_get_gpio_in(s->mpu->gpio, N8X0_TMP105_GPIO);
+ I2CBus *i2c = omap_i2c_bus(s->mpu->i2c[0]);
+
+ /* Attach a menelaus PM chip */
+ dev = i2c_create_slave(i2c, "twl92230", N8X0_MENELAUS_ADDR);
+ qdev_connect_gpio_out(dev, 3,
+ qdev_get_gpio_in(s->mpu->ih[0],
+ OMAP_INT_24XX_SYS_NIRQ));
+
+ n8x0_system_powerdown = qdev_get_gpio_in(dev, 3);
+ qemu_register_powerdown_notifier(&n8x0_system_powerdown_notifier);
+
+ /* Attach a TMP105 PM chip (A0 wired to ground) */
+ dev = i2c_create_slave(i2c, "tmp105", N8X0_TMP105_ADDR);
+ qdev_connect_gpio_out(dev, 0, tmp_irq);
+}
+
+/* Touchscreen and keypad controller */
+static MouseTransformInfo n800_pointercal = {
+ .x = 800,
+ .y = 480,
+ .a = { 14560, -68, -3455208, -39, -9621, 35152972, 65536 },
+};
+
+static MouseTransformInfo n810_pointercal = {
+ .x = 800,
+ .y = 480,
+ .a = { 15041, 148, -4731056, 171, -10238, 35933380, 65536 },
+};
+
+#define RETU_KEYCODE 61 /* F3 */
+
+static void n800_key_event(void *opaque, int keycode)
+{
+ struct n800_s *s = (struct n800_s *) opaque;
+ int code = s->keymap[keycode & 0x7f];
+
+ if (code == -1) {
+ if ((keycode & 0x7f) == RETU_KEYCODE) {
+ retu_key_event(s->retu, !(keycode & 0x80));
+ }
+ return;
+ }
+
+ tsc210x_key_event(s->ts.chip, code, !(keycode & 0x80));
+}
+
+static const int n800_keys[16] = {
+ -1,
+ 72, /* Up */
+ 63, /* Home (F5) */
+ -1,
+ 75, /* Left */
+ 28, /* Enter */
+ 77, /* Right */
+ -1,
+ 1, /* Cycle (ESC) */
+ 80, /* Down */
+ 62, /* Menu (F4) */
+ -1,
+ 66, /* Zoom- (F8) */
+ 64, /* FullScreen (F6) */
+ 65, /* Zoom+ (F7) */
+ -1,
+};
+
+static void n800_tsc_kbd_setup(struct n800_s *s)
+{
+ int i;
+
+ /* XXX: are the three pins inverted inside the chip between the
+ * tsc and the cpu (N4111)? */
+ qemu_irq penirq = NULL; /* NC */
+ qemu_irq kbirq = qdev_get_gpio_in(s->mpu->gpio, N800_TSC_KP_IRQ_GPIO);
+ qemu_irq dav = qdev_get_gpio_in(s->mpu->gpio, N800_TSC_TS_GPIO);
+
+ s->ts.chip = tsc2301_init(penirq, kbirq, dav);
+ s->ts.opaque = s->ts.chip->opaque;
+ s->ts.txrx = tsc210x_txrx;
+
+ for (i = 0; i < 0x80; i++) {
+ s->keymap[i] = -1;
+ }
+ for (i = 0; i < 0x10; i++) {
+ if (n800_keys[i] >= 0) {
+ s->keymap[n800_keys[i]] = i;
+ }
+ }
+
+ qemu_add_kbd_event_handler(n800_key_event, s);
+
+ tsc210x_set_transform(s->ts.chip, &n800_pointercal);
+}
+
+static void n810_tsc_setup(struct n800_s *s)
+{
+ qemu_irq pintdav = qdev_get_gpio_in(s->mpu->gpio, N810_TSC_TS_GPIO);
+
+ s->ts.opaque = tsc2005_init(pintdav);
+ s->ts.txrx = tsc2005_txrx;
+
+ tsc2005_set_transform(s->ts.opaque, &n810_pointercal);
+}
+
+/* N810 Keyboard controller */
+static void n810_key_event(void *opaque, int keycode)
+{
+ struct n800_s *s = (struct n800_s *) opaque;
+ int code = s->keymap[keycode & 0x7f];
+
+ if (code == -1) {
+ if ((keycode & 0x7f) == RETU_KEYCODE) {
+ retu_key_event(s->retu, !(keycode & 0x80));
+ }
+ return;
+ }
+
+ lm832x_key_event(s->kbd, code, !(keycode & 0x80));
+}
+
+#define M 0
+
+static int n810_keys[0x80] = {
+ [0x01] = 16, /* Q */
+ [0x02] = 37, /* K */
+ [0x03] = 24, /* O */
+ [0x04] = 25, /* P */
+ [0x05] = 14, /* Backspace */
+ [0x06] = 30, /* A */
+ [0x07] = 31, /* S */
+ [0x08] = 32, /* D */
+ [0x09] = 33, /* F */
+ [0x0a] = 34, /* G */
+ [0x0b] = 35, /* H */
+ [0x0c] = 36, /* J */
+
+ [0x11] = 17, /* W */
+ [0x12] = 62, /* Menu (F4) */
+ [0x13] = 38, /* L */
+ [0x14] = 40, /* ' (Apostrophe) */
+ [0x16] = 44, /* Z */
+ [0x17] = 45, /* X */
+ [0x18] = 46, /* C */
+ [0x19] = 47, /* V */
+ [0x1a] = 48, /* B */
+ [0x1b] = 49, /* N */
+ [0x1c] = 42, /* Shift (Left shift) */
+ [0x1f] = 65, /* Zoom+ (F7) */
+
+ [0x21] = 18, /* E */
+ [0x22] = 39, /* ; (Semicolon) */
+ [0x23] = 12, /* - (Minus) */
+ [0x24] = 13, /* = (Equal) */
+ [0x2b] = 56, /* Fn (Left Alt) */
+ [0x2c] = 50, /* M */
+ [0x2f] = 66, /* Zoom- (F8) */
+
+ [0x31] = 19, /* R */
+ [0x32] = 29 | M, /* Right Ctrl */
+ [0x34] = 57, /* Space */
+ [0x35] = 51, /* , (Comma) */
+ [0x37] = 72 | M, /* Up */
+ [0x3c] = 82 | M, /* Compose (Insert) */
+ [0x3f] = 64, /* FullScreen (F6) */
+
+ [0x41] = 20, /* T */
+ [0x44] = 52, /* . (Dot) */
+ [0x46] = 77 | M, /* Right */
+ [0x4f] = 63, /* Home (F5) */
+ [0x51] = 21, /* Y */
+ [0x53] = 80 | M, /* Down */
+ [0x55] = 28, /* Enter */
+ [0x5f] = 1, /* Cycle (ESC) */
+
+ [0x61] = 22, /* U */
+ [0x64] = 75 | M, /* Left */
+
+ [0x71] = 23, /* I */
+#if 0
+ [0x75] = 28 | M, /* KP Enter (KP Enter) */
+#else
+ [0x75] = 15, /* KP Enter (Tab) */
+#endif
+};
+
+#undef M
+
+static void n810_kbd_setup(struct n800_s *s)
+{
+ qemu_irq kbd_irq = qdev_get_gpio_in(s->mpu->gpio, N810_KEYBOARD_GPIO);
+ int i;
+
+ for (i = 0; i < 0x80; i++) {
+ s->keymap[i] = -1;
+ }
+ for (i = 0; i < 0x80; i++) {
+ if (n810_keys[i] > 0) {
+ s->keymap[n810_keys[i]] = i;
+ }
+ }
+
+ qemu_add_kbd_event_handler(n810_key_event, s);
+
+ /* Attach the LM8322 keyboard to the I2C bus,
+ * should happen in n8x0_i2c_setup and s->kbd be initialised here. */
+ s->kbd = i2c_create_slave(omap_i2c_bus(s->mpu->i2c[0]),
+ "lm8323", N810_LM8323_ADDR);
+ qdev_connect_gpio_out(s->kbd, 0, kbd_irq);
+}
+
+/* LCD MIPI DBI-C controller (URAL) */
+struct mipid_s {
+ int resp[4];
+ int param[4];
+ int p;
+ int pm;
+ int cmd;
+
+ int sleep;
+ int booster;
+ int te;
+ int selfcheck;
+ int partial;
+ int normal;
+ int vscr;
+ int invert;
+ int onoff;
+ int gamma;
+ uint32_t id;
+};
+
+static void mipid_reset(struct mipid_s *s)
+{
+ s->pm = 0;
+ s->cmd = 0;
+
+ s->sleep = 1;
+ s->booster = 0;
+ s->selfcheck =
+ (1 << 7) | /* Register loading OK. */
+ (1 << 5) | /* The chip is attached. */
+ (1 << 4); /* Display glass still in one piece. */
+ s->te = 0;
+ s->partial = 0;
+ s->normal = 1;
+ s->vscr = 0;
+ s->invert = 0;
+ s->onoff = 1;
+ s->gamma = 0;
+}
+
+static uint32_t mipid_txrx(void *opaque, uint32_t cmd, int len)
+{
+ struct mipid_s *s = (struct mipid_s *) opaque;
+ uint8_t ret;
+
+ if (len > 9) {
+ hw_error("%s: FIXME: bad SPI word width %i\n", __FUNCTION__, len);
+ }
+
+ if (s->p >= ARRAY_SIZE(s->resp)) {
+ ret = 0;
+ } else {
+ ret = s->resp[s->p++];
+ }
+ if (s->pm-- > 0) {
+ s->param[s->pm] = cmd;
+ } else {
+ s->cmd = cmd;
+ }
+
+ switch (s->cmd) {
+ case 0x00: /* NOP */
+ break;
+
+ case 0x01: /* SWRESET */
+ mipid_reset(s);
+ break;
+
+ case 0x02: /* BSTROFF */
+ s->booster = 0;
+ break;
+ case 0x03: /* BSTRON */
+ s->booster = 1;
+ break;
+
+ case 0x04: /* RDDID */
+ s->p = 0;
+ s->resp[0] = (s->id >> 16) & 0xff;
+ s->resp[1] = (s->id >> 8) & 0xff;
+ s->resp[2] = (s->id >> 0) & 0xff;
+ break;
+
+ case 0x06: /* RD_RED */
+ case 0x07: /* RD_GREEN */
+ /* XXX the bootloader sometimes issues RD_BLUE meaning RDDID so
+ * for the bootloader one needs to change this. */
+ case 0x08: /* RD_BLUE */
+ s->p = 0;
+ /* TODO: return first pixel components */
+ s->resp[0] = 0x01;
+ break;
+
+ case 0x09: /* RDDST */
+ s->p = 0;
+ s->resp[0] = s->booster << 7;
+ s->resp[1] = (5 << 4) | (s->partial << 2) |
+ (s->sleep << 1) | s->normal;
+ s->resp[2] = (s->vscr << 7) | (s->invert << 5) |
+ (s->onoff << 2) | (s->te << 1) | (s->gamma >> 2);
+ s->resp[3] = s->gamma << 6;
+ break;
+
+ case 0x0a: /* RDDPM */
+ s->p = 0;
+ s->resp[0] = (s->onoff << 2) | (s->normal << 3) | (s->sleep << 4) |
+ (s->partial << 5) | (s->sleep << 6) | (s->booster << 7);
+ break;
+ case 0x0b: /* RDDMADCTR */
+ s->p = 0;
+ s->resp[0] = 0;
+ break;
+ case 0x0c: /* RDDCOLMOD */
+ s->p = 0;
+ s->resp[0] = 5; /* 65K colours */
+ break;
+ case 0x0d: /* RDDIM */
+ s->p = 0;
+ s->resp[0] = (s->invert << 5) | (s->vscr << 7) | s->gamma;
+ break;
+ case 0x0e: /* RDDSM */
+ s->p = 0;
+ s->resp[0] = s->te << 7;
+ break;
+ case 0x0f: /* RDDSDR */
+ s->p = 0;
+ s->resp[0] = s->selfcheck;
+ break;
+
+ case 0x10: /* SLPIN */
+ s->sleep = 1;
+ break;
+ case 0x11: /* SLPOUT */
+ s->sleep = 0;
+ s->selfcheck ^= 1 << 6; /* POFF self-diagnosis Ok */
+ break;
+
+ case 0x12: /* PTLON */
+ s->partial = 1;
+ s->normal = 0;
+ s->vscr = 0;
+ break;
+ case 0x13: /* NORON */
+ s->partial = 0;
+ s->normal = 1;
+ s->vscr = 0;
+ break;
+
+ case 0x20: /* INVOFF */
+ s->invert = 0;
+ break;
+ case 0x21: /* INVON */
+ s->invert = 1;
+ break;
+
+ case 0x22: /* APOFF */
+ case 0x23: /* APON */
+ goto bad_cmd;
+
+ case 0x25: /* WRCNTR */
+ if (s->pm < 0) {
+ s->pm = 1;
+ }
+ goto bad_cmd;
+
+ case 0x26: /* GAMSET */
+ if (!s->pm) {
+ s->gamma = ctz32(s->param[0] & 0xf);
+ if (s->gamma == 32) {
+ s->gamma = -1; /* XXX: should this be 0? */
+ }
+ } else if (s->pm < 0) {
+ s->pm = 1;
+ }
+ break;
+
+ case 0x28: /* DISPOFF */
+ s->onoff = 0;
+ break;
+ case 0x29: /* DISPON */
+ s->onoff = 1;
+ break;
+
+ case 0x2a: /* CASET */
+ case 0x2b: /* RASET */
+ case 0x2c: /* RAMWR */
+ case 0x2d: /* RGBSET */
+ case 0x2e: /* RAMRD */
+ case 0x30: /* PTLAR */
+ case 0x33: /* SCRLAR */
+ goto bad_cmd;
+
+ case 0x34: /* TEOFF */
+ s->te = 0;
+ break;
+ case 0x35: /* TEON */
+ if (!s->pm) {
+ s->te = 1;
+ } else if (s->pm < 0) {
+ s->pm = 1;
+ }
+ break;
+
+ case 0x36: /* MADCTR */
+ goto bad_cmd;
+
+ case 0x37: /* VSCSAD */
+ s->partial = 0;
+ s->normal = 0;
+ s->vscr = 1;
+ break;
+
+ case 0x38: /* IDMOFF */
+ case 0x39: /* IDMON */
+ case 0x3a: /* COLMOD */
+ goto bad_cmd;
+
+ case 0xb0: /* CLKINT / DISCTL */
+ case 0xb1: /* CLKEXT */
+ if (s->pm < 0) {
+ s->pm = 2;
+ }
+ break;
+
+ case 0xb4: /* FRMSEL */
+ break;
+
+ case 0xb5: /* FRM8SEL */
+ case 0xb6: /* TMPRNG / INIESC */
+ case 0xb7: /* TMPHIS / NOP2 */
+ case 0xb8: /* TMPREAD / MADCTL */
+ case 0xba: /* DISTCTR */
+ case 0xbb: /* EPVOL */
+ goto bad_cmd;
+
+ case 0xbd: /* Unknown */
+ s->p = 0;
+ s->resp[0] = 0;
+ s->resp[1] = 1;
+ break;
+
+ case 0xc2: /* IFMOD */
+ if (s->pm < 0) {
+ s->pm = 2;
+ }
+ break;
+
+ case 0xc6: /* PWRCTL */
+ case 0xc7: /* PPWRCTL */
+ case 0xd0: /* EPWROUT */
+ case 0xd1: /* EPWRIN */
+ case 0xd4: /* RDEV */
+ case 0xd5: /* RDRR */
+ goto bad_cmd;
+
+ case 0xda: /* RDID1 */
+ s->p = 0;
+ s->resp[0] = (s->id >> 16) & 0xff;
+ break;
+ case 0xdb: /* RDID2 */
+ s->p = 0;
+ s->resp[0] = (s->id >> 8) & 0xff;
+ break;
+ case 0xdc: /* RDID3 */
+ s->p = 0;
+ s->resp[0] = (s->id >> 0) & 0xff;
+ break;
+
+ default:
+ bad_cmd:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: unknown command %02x\n", __func__, s->cmd);
+ break;
+ }
+
+ return ret;
+}
+
+static void *mipid_init(void)
+{
+ struct mipid_s *s = (struct mipid_s *) g_malloc0(sizeof(*s));
+
+ s->id = 0x838f03;
+ mipid_reset(s);
+
+ return s;
+}
+
+static void n8x0_spi_setup(struct n800_s *s)
+{
+ void *tsc = s->ts.opaque;
+ void *mipid = mipid_init();
+
+ omap_mcspi_attach(s->mpu->mcspi[0], s->ts.txrx, tsc, 0);
+ omap_mcspi_attach(s->mpu->mcspi[0], mipid_txrx, mipid, 1);
+}
+
+/* This task is normally performed by the bootloader. If we're loading
+ * a kernel directly, we need to enable the Blizzard ourselves. */
+static void n800_dss_init(struct rfbi_chip_s *chip)
+{
+ uint8_t *fb_blank;
+
+ chip->write(chip->opaque, 0, 0x2a); /* LCD Width register */
+ chip->write(chip->opaque, 1, 0x64);
+ chip->write(chip->opaque, 0, 0x2c); /* LCD HNDP register */
+ chip->write(chip->opaque, 1, 0x1e);
+ chip->write(chip->opaque, 0, 0x2e); /* LCD Height 0 register */
+ chip->write(chip->opaque, 1, 0xe0);
+ chip->write(chip->opaque, 0, 0x30); /* LCD Height 1 register */
+ chip->write(chip->opaque, 1, 0x01);
+ chip->write(chip->opaque, 0, 0x32); /* LCD VNDP register */
+ chip->write(chip->opaque, 1, 0x06);
+ chip->write(chip->opaque, 0, 0x68); /* Display Mode register */
+ chip->write(chip->opaque, 1, 1); /* Enable bit */
+
+ chip->write(chip->opaque, 0, 0x6c);
+ chip->write(chip->opaque, 1, 0x00); /* Input X Start Position */
+ chip->write(chip->opaque, 1, 0x00); /* Input X Start Position */
+ chip->write(chip->opaque, 1, 0x00); /* Input Y Start Position */
+ chip->write(chip->opaque, 1, 0x00); /* Input Y Start Position */
+ chip->write(chip->opaque, 1, 0x1f); /* Input X End Position */
+ chip->write(chip->opaque, 1, 0x03); /* Input X End Position */
+ chip->write(chip->opaque, 1, 0xdf); /* Input Y End Position */
+ chip->write(chip->opaque, 1, 0x01); /* Input Y End Position */
+ chip->write(chip->opaque, 1, 0x00); /* Output X Start Position */
+ chip->write(chip->opaque, 1, 0x00); /* Output X Start Position */
+ chip->write(chip->opaque, 1, 0x00); /* Output Y Start Position */
+ chip->write(chip->opaque, 1, 0x00); /* Output Y Start Position */
+ chip->write(chip->opaque, 1, 0x1f); /* Output X End Position */
+ chip->write(chip->opaque, 1, 0x03); /* Output X End Position */
+ chip->write(chip->opaque, 1, 0xdf); /* Output Y End Position */
+ chip->write(chip->opaque, 1, 0x01); /* Output Y End Position */
+ chip->write(chip->opaque, 1, 0x01); /* Input Data Format */
+ chip->write(chip->opaque, 1, 0x01); /* Data Source Select */
+
+ fb_blank = memset(g_malloc(800 * 480 * 2), 0xff, 800 * 480 * 2);
+ /* Display Memory Data Port */
+ chip->block(chip->opaque, 1, fb_blank, 800 * 480 * 2, 800);
+ g_free(fb_blank);
+}
+
+static void n8x0_dss_setup(struct n800_s *s)
+{
+ s->blizzard.opaque = s1d13745_init(NULL);
+ s->blizzard.block = s1d13745_write_block;
+ s->blizzard.write = s1d13745_write;
+ s->blizzard.read = s1d13745_read;
+
+ omap_rfbi_attach(s->mpu->dss, 0, &s->blizzard);
+}
+
+static void n8x0_cbus_setup(struct n800_s *s)
+{
+ qemu_irq dat_out = qdev_get_gpio_in(s->mpu->gpio, N8X0_CBUS_DAT_GPIO);
+ qemu_irq retu_irq = qdev_get_gpio_in(s->mpu->gpio, N8X0_RETU_GPIO);
+ qemu_irq tahvo_irq = qdev_get_gpio_in(s->mpu->gpio, N8X0_TAHVO_GPIO);
+
+ CBus *cbus = cbus_init(dat_out);
+
+ qdev_connect_gpio_out(s->mpu->gpio, N8X0_CBUS_CLK_GPIO, cbus->clk);
+ qdev_connect_gpio_out(s->mpu->gpio, N8X0_CBUS_DAT_GPIO, cbus->dat);
+ qdev_connect_gpio_out(s->mpu->gpio, N8X0_CBUS_SEL_GPIO, cbus->sel);
+
+ cbus_attach(cbus, s->retu = retu_init(retu_irq, 1));
+ cbus_attach(cbus, s->tahvo = tahvo_init(tahvo_irq, 1));
+}
+
+static void n8x0_uart_setup(struct n800_s *s)
+{
+ CharDriverState *radio = uart_hci_init(
+ qdev_get_gpio_in(s->mpu->gpio, N8X0_BT_HOST_WKUP_GPIO));
+
+ qdev_connect_gpio_out(s->mpu->gpio, N8X0_BT_RESET_GPIO,
+ csrhci_pins_get(radio)[csrhci_pin_reset]);
+ qdev_connect_gpio_out(s->mpu->gpio, N8X0_BT_WKUP_GPIO,
+ csrhci_pins_get(radio)[csrhci_pin_wakeup]);
+
+ omap_uart_attach(s->mpu->uart[BT_UART], radio);
+}
+
+static void n8x0_usb_setup(struct n800_s *s)
+{
+ SysBusDevice *dev;
+ s->usb = qdev_create(NULL, "tusb6010");
+ dev = SYS_BUS_DEVICE(s->usb);
+ qdev_init_nofail(s->usb);
+ sysbus_connect_irq(dev, 0,
+ qdev_get_gpio_in(s->mpu->gpio, N8X0_TUSB_INT_GPIO));
+ /* Using the NOR interface */
+ omap_gpmc_attach(s->mpu->gpmc, N8X0_USB_ASYNC_CS,
+ sysbus_mmio_get_region(dev, 0));
+ omap_gpmc_attach(s->mpu->gpmc, N8X0_USB_SYNC_CS,
+ sysbus_mmio_get_region(dev, 1));
+ qdev_connect_gpio_out(s->mpu->gpio, N8X0_TUSB_ENABLE_GPIO,
+ qdev_get_gpio_in(s->usb, 0)); /* tusb_pwr */
+}
+
+/* Setup done before the main bootloader starts by some early setup code
+ * - used when we want to run the main bootloader in emulation. This
+ * isn't documented. */
+static uint32_t n800_pinout[104] = {
+ 0x080f00d8, 0x00d40808, 0x03080808, 0x080800d0,
+ 0x00dc0808, 0x0b0f0f00, 0x080800b4, 0x00c00808,
+ 0x08080808, 0x180800c4, 0x00b80000, 0x08080808,
+ 0x080800bc, 0x00cc0808, 0x08081818, 0x18180128,
+ 0x01241800, 0x18181818, 0x000000f0, 0x01300000,
+ 0x00001b0b, 0x1b0f0138, 0x00e0181b, 0x1b031b0b,
+ 0x180f0078, 0x00740018, 0x0f0f0f1a, 0x00000080,
+ 0x007c0000, 0x00000000, 0x00000088, 0x00840000,
+ 0x00000000, 0x00000094, 0x00980300, 0x0f180003,
+ 0x0000008c, 0x00900f0f, 0x0f0f1b00, 0x0f00009c,
+ 0x01140000, 0x1b1b0f18, 0x0818013c, 0x01400008,
+ 0x00001818, 0x000b0110, 0x010c1800, 0x0b030b0f,
+ 0x181800f4, 0x00f81818, 0x00000018, 0x000000fc,
+ 0x00401808, 0x00000000, 0x0f1b0030, 0x003c0008,
+ 0x00000000, 0x00000038, 0x00340000, 0x00000000,
+ 0x1a080070, 0x00641a1a, 0x08080808, 0x08080060,
+ 0x005c0808, 0x08080808, 0x08080058, 0x00540808,
+ 0x08080808, 0x0808006c, 0x00680808, 0x08080808,
+ 0x000000a8, 0x00b00000, 0x08080808, 0x000000a0,
+ 0x00a40000, 0x00000000, 0x08ff0050, 0x004c0808,
+ 0xffffffff, 0xffff0048, 0x0044ffff, 0xffffffff,
+ 0x000000ac, 0x01040800, 0x08080b0f, 0x18180100,
+ 0x01081818, 0x0b0b1808, 0x1a0300e4, 0x012c0b1a,
+ 0x02020018, 0x0b000134, 0x011c0800, 0x0b1b1b00,
+ 0x0f0000c8, 0x00ec181b, 0x000f0f02, 0x00180118,
+ 0x01200000, 0x0f0b1b1b, 0x0f0200e8, 0x0000020b,
+};
+
+static void n800_setup_nolo_tags(void *sram_base)
+{
+ int i;
+ uint32_t *p = sram_base + 0x8000;
+ uint32_t *v = sram_base + 0xa000;
+
+ memset(p, 0, 0x3000);
+
+ strcpy((void *) (p + 0), "QEMU N800");
+
+ strcpy((void *) (p + 8), "F5");
+
+ stl_p(p + 10, 0x04f70000);
+ strcpy((void *) (p + 9), "RX-34");
+
+ /* RAM size in MB? */
+ stl_p(p + 12, 0x80);
+
+ /* Pointer to the list of tags */
+ stl_p(p + 13, OMAP2_SRAM_BASE + 0x9000);
+
+ /* The NOLO tags start here */
+ p = sram_base + 0x9000;
+#define ADD_TAG(tag, len) \
+ stw_p((uint16_t *) p + 0, tag); \
+ stw_p((uint16_t *) p + 1, len); p++; \
+ stl_p(p++, OMAP2_SRAM_BASE | (((void *) v - sram_base) & 0xffff));
+
+ /* OMAP STI console? Pin out settings? */
+ ADD_TAG(0x6e01, 414);
+ for (i = 0; i < ARRAY_SIZE(n800_pinout); i++) {
+ stl_p(v++, n800_pinout[i]);
+ }
+
+ /* Kernel memsize? */
+ ADD_TAG(0x6e05, 1);
+ stl_p(v++, 2);
+
+ /* NOLO serial console */
+ ADD_TAG(0x6e02, 4);
+ stl_p(v++, XLDR_LL_UART); /* UART number (1 - 3) */
+
+#if 0
+ /* CBUS settings (Retu/AVilma) */
+ ADD_TAG(0x6e03, 6);
+ stw_p((uint16_t *) v + 0, 65); /* CBUS GPIO0 */
+ stw_p((uint16_t *) v + 1, 66); /* CBUS GPIO1 */
+ stw_p((uint16_t *) v + 2, 64); /* CBUS GPIO2 */
+ v += 2;
+#endif
+
+ /* Nokia ASIC BB5 (Retu/Tahvo) */
+ ADD_TAG(0x6e0a, 4);
+ stw_p((uint16_t *) v + 0, 111); /* "Retu" interrupt GPIO */
+ stw_p((uint16_t *) v + 1, 108); /* "Tahvo" interrupt GPIO */
+ v++;
+
+ /* LCD console? */
+ ADD_TAG(0x6e04, 4);
+ stw_p((uint16_t *) v + 0, 30); /* ??? */
+ stw_p((uint16_t *) v + 1, 24); /* ??? */
+ v++;
+
+#if 0
+ /* LCD settings */
+ ADD_TAG(0x6e06, 2);
+ stw_p((uint16_t *) (v++), 15); /* ??? */
+#endif
+
+ /* I^2C (Menelaus) */
+ ADD_TAG(0x6e07, 4);
+ stl_p(v++, 0x00720000); /* ??? */
+
+ /* Unknown */
+ ADD_TAG(0x6e0b, 6);
+ stw_p((uint16_t *) v + 0, 94); /* ??? */
+ stw_p((uint16_t *) v + 1, 23); /* ??? */
+ stw_p((uint16_t *) v + 2, 0); /* ??? */
+ v += 2;
+
+ /* OMAP gpio switch info */
+ ADD_TAG(0x6e0c, 80);
+ strcpy((void *) v, "bat_cover"); v += 3;
+ stw_p((uint16_t *) v + 0, 110); /* GPIO num ??? */
+ stw_p((uint16_t *) v + 1, 1); /* GPIO num ??? */
+ v += 2;
+ strcpy((void *) v, "cam_act"); v += 3;
+ stw_p((uint16_t *) v + 0, 95); /* GPIO num ??? */
+ stw_p((uint16_t *) v + 1, 32); /* GPIO num ??? */
+ v += 2;
+ strcpy((void *) v, "cam_turn"); v += 3;
+ stw_p((uint16_t *) v + 0, 12); /* GPIO num ??? */
+ stw_p((uint16_t *) v + 1, 33); /* GPIO num ??? */
+ v += 2;
+ strcpy((void *) v, "headphone"); v += 3;
+ stw_p((uint16_t *) v + 0, 107); /* GPIO num ??? */
+ stw_p((uint16_t *) v + 1, 17); /* GPIO num ??? */
+ v += 2;
+
+ /* Bluetooth */
+ ADD_TAG(0x6e0e, 12);
+ stl_p(v++, 0x5c623d01); /* ??? */
+ stl_p(v++, 0x00000201); /* ??? */
+ stl_p(v++, 0x00000000); /* ??? */
+
+ /* CX3110x WLAN settings */
+ ADD_TAG(0x6e0f, 8);
+ stl_p(v++, 0x00610025); /* ??? */
+ stl_p(v++, 0xffff0057); /* ??? */
+
+ /* MMC host settings */
+ ADD_TAG(0x6e10, 12);
+ stl_p(v++, 0xffff000f); /* ??? */
+ stl_p(v++, 0xffffffff); /* ??? */
+ stl_p(v++, 0x00000060); /* ??? */
+
+ /* OneNAND chip select */
+ ADD_TAG(0x6e11, 10);
+ stl_p(v++, 0x00000401); /* ??? */
+ stl_p(v++, 0x0002003a); /* ??? */
+ stl_p(v++, 0x00000002); /* ??? */
+
+ /* TEA5761 sensor settings */
+ ADD_TAG(0x6e12, 2);
+ stl_p(v++, 93); /* GPIO num ??? */
+
+#if 0
+ /* Unknown tag */
+ ADD_TAG(6e09, 0);
+
+ /* Kernel UART / console */
+ ADD_TAG(6e12, 0);
+#endif
+
+ /* End of the list */
+ stl_p(p++, 0x00000000);
+ stl_p(p++, 0x00000000);
+}
+
+/* This task is normally performed by the bootloader. If we're loading
+ * a kernel directly, we need to set up GPMC mappings ourselves. */
+static void n800_gpmc_init(struct n800_s *s)
+{
+ uint32_t config7 =
+ (0xf << 8) | /* MASKADDRESS */
+ (1 << 6) | /* CSVALID */
+ (4 << 0); /* BASEADDRESS */
+
+ cpu_physical_memory_write(0x6800a078, /* GPMC_CONFIG7_0 */
+ &config7, sizeof(config7));
+}
+
+/* Setup sequence done by the bootloader */
+static void n8x0_boot_init(void *opaque)
+{
+ struct n800_s *s = (struct n800_s *) opaque;
+ uint32_t buf;
+
+ /* PRCM setup */
+#define omap_writel(addr, val) \
+ buf = (val); \
+ cpu_physical_memory_write(addr, &buf, sizeof(buf))
+
+ omap_writel(0x48008060, 0x41); /* PRCM_CLKSRC_CTRL */
+ omap_writel(0x48008070, 1); /* PRCM_CLKOUT_CTRL */
+ omap_writel(0x48008078, 0); /* PRCM_CLKEMUL_CTRL */
+ omap_writel(0x48008090, 0); /* PRCM_VOLTSETUP */
+ omap_writel(0x48008094, 0); /* PRCM_CLKSSETUP */
+ omap_writel(0x48008098, 0); /* PRCM_POLCTRL */
+ omap_writel(0x48008140, 2); /* CM_CLKSEL_MPU */
+ omap_writel(0x48008148, 0); /* CM_CLKSTCTRL_MPU */
+ omap_writel(0x48008158, 1); /* RM_RSTST_MPU */
+ omap_writel(0x480081c8, 0x15); /* PM_WKDEP_MPU */
+ omap_writel(0x480081d4, 0x1d4); /* PM_EVGENCTRL_MPU */
+ omap_writel(0x480081d8, 0); /* PM_EVEGENONTIM_MPU */
+ omap_writel(0x480081dc, 0); /* PM_EVEGENOFFTIM_MPU */
+ omap_writel(0x480081e0, 0xc); /* PM_PWSTCTRL_MPU */
+ omap_writel(0x48008200, 0x047e7ff7); /* CM_FCLKEN1_CORE */
+ omap_writel(0x48008204, 0x00000004); /* CM_FCLKEN2_CORE */
+ omap_writel(0x48008210, 0x047e7ff1); /* CM_ICLKEN1_CORE */
+ omap_writel(0x48008214, 0x00000004); /* CM_ICLKEN2_CORE */
+ omap_writel(0x4800821c, 0x00000000); /* CM_ICLKEN4_CORE */
+ omap_writel(0x48008230, 0); /* CM_AUTOIDLE1_CORE */
+ omap_writel(0x48008234, 0); /* CM_AUTOIDLE2_CORE */
+ omap_writel(0x48008238, 7); /* CM_AUTOIDLE3_CORE */
+ omap_writel(0x4800823c, 0); /* CM_AUTOIDLE4_CORE */
+ omap_writel(0x48008240, 0x04360626); /* CM_CLKSEL1_CORE */
+ omap_writel(0x48008244, 0x00000014); /* CM_CLKSEL2_CORE */
+ omap_writel(0x48008248, 0); /* CM_CLKSTCTRL_CORE */
+ omap_writel(0x48008300, 0x00000000); /* CM_FCLKEN_GFX */
+ omap_writel(0x48008310, 0x00000000); /* CM_ICLKEN_GFX */
+ omap_writel(0x48008340, 0x00000001); /* CM_CLKSEL_GFX */
+ omap_writel(0x48008400, 0x00000004); /* CM_FCLKEN_WKUP */
+ omap_writel(0x48008410, 0x00000004); /* CM_ICLKEN_WKUP */
+ omap_writel(0x48008440, 0x00000000); /* CM_CLKSEL_WKUP */
+ omap_writel(0x48008500, 0x000000cf); /* CM_CLKEN_PLL */
+ omap_writel(0x48008530, 0x0000000c); /* CM_AUTOIDLE_PLL */
+ omap_writel(0x48008540, /* CM_CLKSEL1_PLL */
+ (0x78 << 12) | (6 << 8));
+ omap_writel(0x48008544, 2); /* CM_CLKSEL2_PLL */
+
+ /* GPMC setup */
+ n800_gpmc_init(s);
+
+ /* Video setup */
+ n800_dss_init(&s->blizzard);
+
+ /* CPU setup */
+ s->mpu->cpu->env.GE = 0x5;
+
+ /* If the machine has a slided keyboard, open it */
+ if (s->kbd) {
+ qemu_irq_raise(qdev_get_gpio_in(s->mpu->gpio, N810_SLIDE_GPIO));
+ }
+}
+
+#define OMAP_TAG_NOKIA_BT 0x4e01
+#define OMAP_TAG_WLAN_CX3110X 0x4e02
+#define OMAP_TAG_CBUS 0x4e03
+#define OMAP_TAG_EM_ASIC_BB5 0x4e04
+
+static struct omap_gpiosw_info_s {
+ const char *name;
+ int line;
+ int type;
+} n800_gpiosw_info[] = {
+ {
+ "bat_cover", N800_BAT_COVER_GPIO,
+ OMAP_GPIOSW_TYPE_COVER | OMAP_GPIOSW_INVERTED,
+ }, {
+ "cam_act", N800_CAM_ACT_GPIO,
+ OMAP_GPIOSW_TYPE_ACTIVITY,
+ }, {
+ "cam_turn", N800_CAM_TURN_GPIO,
+ OMAP_GPIOSW_TYPE_ACTIVITY | OMAP_GPIOSW_INVERTED,
+ }, {
+ "headphone", N8X0_HEADPHONE_GPIO,
+ OMAP_GPIOSW_TYPE_CONNECTION | OMAP_GPIOSW_INVERTED,
+ },
+ { NULL }
+}, n810_gpiosw_info[] = {
+ {
+ "gps_reset", N810_GPS_RESET_GPIO,
+ OMAP_GPIOSW_TYPE_ACTIVITY | OMAP_GPIOSW_OUTPUT,
+ }, {
+ "gps_wakeup", N810_GPS_WAKEUP_GPIO,
+ OMAP_GPIOSW_TYPE_ACTIVITY | OMAP_GPIOSW_OUTPUT,
+ }, {
+ "headphone", N8X0_HEADPHONE_GPIO,
+ OMAP_GPIOSW_TYPE_CONNECTION | OMAP_GPIOSW_INVERTED,
+ }, {
+ "kb_lock", N810_KB_LOCK_GPIO,
+ OMAP_GPIOSW_TYPE_COVER | OMAP_GPIOSW_INVERTED,
+ }, {
+ "sleepx_led", N810_SLEEPX_LED_GPIO,
+ OMAP_GPIOSW_TYPE_ACTIVITY | OMAP_GPIOSW_INVERTED | OMAP_GPIOSW_OUTPUT,
+ }, {
+ "slide", N810_SLIDE_GPIO,
+ OMAP_GPIOSW_TYPE_COVER | OMAP_GPIOSW_INVERTED,
+ },
+ { NULL }
+};
+
+static struct omap_partition_info_s {
+ uint32_t offset;
+ uint32_t size;
+ int mask;
+ const char *name;
+} n800_part_info[] = {
+ { 0x00000000, 0x00020000, 0x3, "bootloader" },
+ { 0x00020000, 0x00060000, 0x0, "config" },
+ { 0x00080000, 0x00200000, 0x0, "kernel" },
+ { 0x00280000, 0x00200000, 0x3, "initfs" },
+ { 0x00480000, 0x0fb80000, 0x3, "rootfs" },
+
+ { 0, 0, 0, NULL }
+}, n810_part_info[] = {
+ { 0x00000000, 0x00020000, 0x3, "bootloader" },
+ { 0x00020000, 0x00060000, 0x0, "config" },
+ { 0x00080000, 0x00220000, 0x0, "kernel" },
+ { 0x002a0000, 0x00400000, 0x0, "initfs" },
+ { 0x006a0000, 0x0f960000, 0x0, "rootfs" },
+
+ { 0, 0, 0, NULL }
+};
+
+static bdaddr_t n8x0_bd_addr = {{ N8X0_BD_ADDR }};
+
+static int n8x0_atag_setup(void *p, int model)
+{
+ uint8_t *b;
+ uint16_t *w;
+ uint32_t *l;
+ struct omap_gpiosw_info_s *gpiosw;
+ struct omap_partition_info_s *partition;
+ const char *tag;
+
+ w = p;
+
+ stw_p(w++, OMAP_TAG_UART); /* u16 tag */
+ stw_p(w++, 4); /* u16 len */
+ stw_p(w++, (1 << 2) | (1 << 1) | (1 << 0)); /* uint enabled_uarts */
+ w++;
+
+#if 0
+ stw_p(w++, OMAP_TAG_SERIAL_CONSOLE); /* u16 tag */
+ stw_p(w++, 4); /* u16 len */
+ stw_p(w++, XLDR_LL_UART + 1); /* u8 console_uart */
+ stw_p(w++, 115200); /* u32 console_speed */
+#endif
+
+ stw_p(w++, OMAP_TAG_LCD); /* u16 tag */
+ stw_p(w++, 36); /* u16 len */
+ strcpy((void *) w, "QEMU LCD panel"); /* char panel_name[16] */
+ w += 8;
+ strcpy((void *) w, "blizzard"); /* char ctrl_name[16] */
+ w += 8;
+ stw_p(w++, N810_BLIZZARD_RESET_GPIO); /* TODO: n800 s16 nreset_gpio */
+ stw_p(w++, 24); /* u8 data_lines */
+
+ stw_p(w++, OMAP_TAG_CBUS); /* u16 tag */
+ stw_p(w++, 8); /* u16 len */
+ stw_p(w++, N8X0_CBUS_CLK_GPIO); /* s16 clk_gpio */
+ stw_p(w++, N8X0_CBUS_DAT_GPIO); /* s16 dat_gpio */
+ stw_p(w++, N8X0_CBUS_SEL_GPIO); /* s16 sel_gpio */
+ w++;
+
+ stw_p(w++, OMAP_TAG_EM_ASIC_BB5); /* u16 tag */
+ stw_p(w++, 4); /* u16 len */
+ stw_p(w++, N8X0_RETU_GPIO); /* s16 retu_irq_gpio */
+ stw_p(w++, N8X0_TAHVO_GPIO); /* s16 tahvo_irq_gpio */
+
+ gpiosw = (model == 810) ? n810_gpiosw_info : n800_gpiosw_info;
+ for (; gpiosw->name; gpiosw++) {
+ stw_p(w++, OMAP_TAG_GPIO_SWITCH); /* u16 tag */
+ stw_p(w++, 20); /* u16 len */
+ strcpy((void *) w, gpiosw->name); /* char name[12] */
+ w += 6;
+ stw_p(w++, gpiosw->line); /* u16 gpio */
+ stw_p(w++, gpiosw->type);
+ stw_p(w++, 0);
+ stw_p(w++, 0);
+ }
+
+ stw_p(w++, OMAP_TAG_NOKIA_BT); /* u16 tag */
+ stw_p(w++, 12); /* u16 len */
+ b = (void *) w;
+ stb_p(b++, 0x01); /* u8 chip_type (CSR) */
+ stb_p(b++, N8X0_BT_WKUP_GPIO); /* u8 bt_wakeup_gpio */
+ stb_p(b++, N8X0_BT_HOST_WKUP_GPIO); /* u8 host_wakeup_gpio */
+ stb_p(b++, N8X0_BT_RESET_GPIO); /* u8 reset_gpio */
+ stb_p(b++, BT_UART + 1); /* u8 bt_uart */
+ memcpy(b, &n8x0_bd_addr, 6); /* u8 bd_addr[6] */
+ b += 6;
+ stb_p(b++, 0x02); /* u8 bt_sysclk (38.4) */
+ w = (void *) b;
+
+ stw_p(w++, OMAP_TAG_WLAN_CX3110X); /* u16 tag */
+ stw_p(w++, 8); /* u16 len */
+ stw_p(w++, 0x25); /* u8 chip_type */
+ stw_p(w++, N8X0_WLAN_PWR_GPIO); /* s16 power_gpio */
+ stw_p(w++, N8X0_WLAN_IRQ_GPIO); /* s16 irq_gpio */
+ stw_p(w++, -1); /* s16 spi_cs_gpio */
+
+ stw_p(w++, OMAP_TAG_MMC); /* u16 tag */
+ stw_p(w++, 16); /* u16 len */
+ if (model == 810) {
+ stw_p(w++, 0x23f); /* unsigned flags */
+ stw_p(w++, -1); /* s16 power_pin */
+ stw_p(w++, -1); /* s16 switch_pin */
+ stw_p(w++, -1); /* s16 wp_pin */
+ stw_p(w++, 0x240); /* unsigned flags */
+ stw_p(w++, 0xc000); /* s16 power_pin */
+ stw_p(w++, 0x0248); /* s16 switch_pin */
+ stw_p(w++, 0xc000); /* s16 wp_pin */
+ } else {
+ stw_p(w++, 0xf); /* unsigned flags */
+ stw_p(w++, -1); /* s16 power_pin */
+ stw_p(w++, -1); /* s16 switch_pin */
+ stw_p(w++, -1); /* s16 wp_pin */
+ stw_p(w++, 0); /* unsigned flags */
+ stw_p(w++, 0); /* s16 power_pin */
+ stw_p(w++, 0); /* s16 switch_pin */
+ stw_p(w++, 0); /* s16 wp_pin */
+ }
+
+ stw_p(w++, OMAP_TAG_TEA5761); /* u16 tag */
+ stw_p(w++, 4); /* u16 len */
+ stw_p(w++, N8X0_TEA5761_CS_GPIO); /* u16 enable_gpio */
+ w++;
+
+ partition = (model == 810) ? n810_part_info : n800_part_info;
+ for (; partition->name; partition++) {
+ stw_p(w++, OMAP_TAG_PARTITION); /* u16 tag */
+ stw_p(w++, 28); /* u16 len */
+ strcpy((void *) w, partition->name); /* char name[16] */
+ l = (void *) (w + 8);
+ stl_p(l++, partition->size); /* unsigned int size */
+ stl_p(l++, partition->offset); /* unsigned int offset */
+ stl_p(l++, partition->mask); /* unsigned int mask_flags */
+ w = (void *) l;
+ }
+
+ stw_p(w++, OMAP_TAG_BOOT_REASON); /* u16 tag */
+ stw_p(w++, 12); /* u16 len */
+#if 0
+ strcpy((void *) w, "por"); /* char reason_str[12] */
+ strcpy((void *) w, "charger"); /* char reason_str[12] */
+ strcpy((void *) w, "32wd_to"); /* char reason_str[12] */
+ strcpy((void *) w, "sw_rst"); /* char reason_str[12] */
+ strcpy((void *) w, "mbus"); /* char reason_str[12] */
+ strcpy((void *) w, "unknown"); /* char reason_str[12] */
+ strcpy((void *) w, "swdg_to"); /* char reason_str[12] */
+ strcpy((void *) w, "sec_vio"); /* char reason_str[12] */
+ strcpy((void *) w, "pwr_key"); /* char reason_str[12] */
+ strcpy((void *) w, "rtc_alarm"); /* char reason_str[12] */
+#else
+ strcpy((void *) w, "pwr_key"); /* char reason_str[12] */
+#endif
+ w += 6;
+
+ tag = (model == 810) ? "RX-44" : "RX-34";
+ stw_p(w++, OMAP_TAG_VERSION_STR); /* u16 tag */
+ stw_p(w++, 24); /* u16 len */
+ strcpy((void *) w, "product"); /* char component[12] */
+ w += 6;
+ strcpy((void *) w, tag); /* char version[12] */
+ w += 6;
+
+ stw_p(w++, OMAP_TAG_VERSION_STR); /* u16 tag */
+ stw_p(w++, 24); /* u16 len */
+ strcpy((void *) w, "hw-build"); /* char component[12] */
+ w += 6;
+ strcpy((void *) w, "QEMU ");
+ pstrcat((void *) w, 12, qemu_get_version()); /* char version[12] */
+ w += 6;
+
+ tag = (model == 810) ? "1.1.10-qemu" : "1.1.6-qemu";
+ stw_p(w++, OMAP_TAG_VERSION_STR); /* u16 tag */
+ stw_p(w++, 24); /* u16 len */
+ strcpy((void *) w, "nolo"); /* char component[12] */
+ w += 6;
+ strcpy((void *) w, tag); /* char version[12] */
+ w += 6;
+
+ return (void *) w - p;
+}
+
+static int n800_atag_setup(const struct arm_boot_info *info, void *p)
+{
+ return n8x0_atag_setup(p, 800);
+}
+
+static int n810_atag_setup(const struct arm_boot_info *info, void *p)
+{
+ return n8x0_atag_setup(p, 810);
+}
+
+static void n8x0_init(MachineState *machine,
+ struct arm_boot_info *binfo, int model)
+{
+ MemoryRegion *sysmem = get_system_memory();
+ struct n800_s *s = (struct n800_s *) g_malloc0(sizeof(*s));
+ int sdram_size = binfo->ram_size;
+
+ s->mpu = omap2420_mpu_init(sysmem, sdram_size, machine->cpu_model);
+
+ /* Setup peripherals
+ *
+ * Believed external peripherals layout in the N810:
+ * (spi bus 1)
+ * tsc2005
+ * lcd_mipid
+ * (spi bus 2)
+ * Conexant cx3110x (WLAN)
+ * optional: pc2400m (WiMAX)
+ * (i2c bus 0)
+ * TLV320AIC33 (audio codec)
+ * TCM825x (camera by Toshiba)
+ * lp5521 (clever LEDs)
+ * tsl2563 (light sensor, hwmon, model 7, rev. 0)
+ * lm8323 (keypad, manf 00, rev 04)
+ * (i2c bus 1)
+ * tmp105 (temperature sensor, hwmon)
+ * menelaus (pm)
+ * (somewhere on i2c - maybe N800-only)
+ * tea5761 (FM tuner)
+ * (serial 0)
+ * GPS
+ * (some serial port)
+ * csr41814 (Bluetooth)
+ */
+ n8x0_gpio_setup(s);
+ n8x0_nand_setup(s);
+ n8x0_i2c_setup(s);
+ if (model == 800) {
+ n800_tsc_kbd_setup(s);
+ } else if (model == 810) {
+ n810_tsc_setup(s);
+ n810_kbd_setup(s);
+ }
+ n8x0_spi_setup(s);
+ n8x0_dss_setup(s);
+ n8x0_cbus_setup(s);
+ n8x0_uart_setup(s);
+ if (usb_enabled()) {
+ n8x0_usb_setup(s);
+ }
+
+ if (machine->kernel_filename) {
+ /* Or at the linux loader. */
+ binfo->kernel_filename = machine->kernel_filename;
+ binfo->kernel_cmdline = machine->kernel_cmdline;
+ binfo->initrd_filename = machine->initrd_filename;
+ arm_load_kernel(s->mpu->cpu, binfo);
+
+ qemu_register_reset(n8x0_boot_init, s);
+ }
+
+ if (option_rom[0].name &&
+ (machine->boot_order[0] == 'n' || !machine->kernel_filename)) {
+ uint8_t nolo_tags[0x10000];
+ /* No, wait, better start at the ROM. */
+ s->mpu->cpu->env.regs[15] = OMAP2_Q2_BASE + 0x400000;
+
+ /* This is intended for loading the `secondary.bin' program from
+ * Nokia images (the NOLO bootloader). The entry point seems
+ * to be at OMAP2_Q2_BASE + 0x400000.
+ *
+ * The `2nd.bin' files contain some kind of earlier boot code and
+ * for them the entry point needs to be set to OMAP2_SRAM_BASE.
+ *
+ * The code above is for loading the `zImage' file from Nokia
+ * images. */
+ load_image_targphys(option_rom[0].name,
+ OMAP2_Q2_BASE + 0x400000,
+ sdram_size - 0x400000);
+
+ n800_setup_nolo_tags(nolo_tags);
+ cpu_physical_memory_write(OMAP2_SRAM_BASE, nolo_tags, 0x10000);
+ }
+}
+
+static struct arm_boot_info n800_binfo = {
+ .loader_start = OMAP2_Q2_BASE,
+ /* Actually two chips of 0x4000000 bytes each */
+ .ram_size = 0x08000000,
+ .board_id = 0x4f7,
+ .atag_board = n800_atag_setup,
+};
+
+static struct arm_boot_info n810_binfo = {
+ .loader_start = OMAP2_Q2_BASE,
+ /* Actually two chips of 0x4000000 bytes each */
+ .ram_size = 0x08000000,
+ /* 0x60c and 0x6bf (WiMAX Edition) have been assigned but are not
+ * used by some older versions of the bootloader and 5555 is used
+ * instead (including versions that shipped with many devices). */
+ .board_id = 0x60c,
+ .atag_board = n810_atag_setup,
+};
+
+static void n800_init(MachineState *machine)
+{
+ n8x0_init(machine, &n800_binfo, 800);
+}
+
+static void n810_init(MachineState *machine)
+{
+ n8x0_init(machine, &n810_binfo, 810);
+}
+
+static QEMUMachine n800_machine = {
+ .name = "n800",
+ .desc = "Nokia N800 tablet aka. RX-34 (OMAP2420)",
+ .init = n800_init,
+ .default_boot_order = "",
+};
+
+static QEMUMachine n810_machine = {
+ .name = "n810",
+ .desc = "Nokia N810 tablet aka. RX-44 (OMAP2420)",
+ .init = n810_init,
+ .default_boot_order = "",
+};
+
+static void nseries_machine_init(void)
+{
+ qemu_register_machine(&n800_machine);
+ qemu_register_machine(&n810_machine);
+}
+
+machine_init(nseries_machine_init);
diff --git a/hw/arm/omap1.c b/hw/arm/omap1.c
new file mode 100644
index 00000000..de2b2892
--- /dev/null
+++ b/hw/arm/omap1.c
@@ -0,0 +1,4089 @@
+/*
+ * TI OMAP processors emulation.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/boards.h"
+#include "hw/hw.h"
+#include "hw/arm/arm.h"
+#include "hw/arm/omap.h"
+#include "sysemu/sysemu.h"
+#include "hw/arm/soc_dma.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "qemu/range.h"
+#include "hw/sysbus.h"
+
+/* Should signal the TCMI/GPMC */
+uint32_t omap_badwidth_read8(void *opaque, hwaddr addr)
+{
+ uint8_t ret;
+
+ OMAP_8B_REG(addr);
+ cpu_physical_memory_read(addr, &ret, 1);
+ return ret;
+}
+
+void omap_badwidth_write8(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ uint8_t val8 = value;
+
+ OMAP_8B_REG(addr);
+ cpu_physical_memory_write(addr, &val8, 1);
+}
+
+uint32_t omap_badwidth_read16(void *opaque, hwaddr addr)
+{
+ uint16_t ret;
+
+ OMAP_16B_REG(addr);
+ cpu_physical_memory_read(addr, &ret, 2);
+ return ret;
+}
+
+void omap_badwidth_write16(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ uint16_t val16 = value;
+
+ OMAP_16B_REG(addr);
+ cpu_physical_memory_write(addr, &val16, 2);
+}
+
+uint32_t omap_badwidth_read32(void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ OMAP_32B_REG(addr);
+ cpu_physical_memory_read(addr, &ret, 4);
+ return ret;
+}
+
+void omap_badwidth_write32(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ OMAP_32B_REG(addr);
+ cpu_physical_memory_write(addr, &value, 4);
+}
+
+/* MPU OS timers */
+struct omap_mpu_timer_s {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ omap_clk clk;
+ uint32_t val;
+ int64_t time;
+ QEMUTimer *timer;
+ QEMUBH *tick;
+ int64_t rate;
+ int it_ena;
+
+ int enable;
+ int ptv;
+ int ar;
+ int st;
+ uint32_t reset_val;
+};
+
+static inline uint32_t omap_timer_read(struct omap_mpu_timer_s *timer)
+{
+ uint64_t distance = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - timer->time;
+
+ if (timer->st && timer->enable && timer->rate)
+ return timer->val - muldiv64(distance >> (timer->ptv + 1),
+ timer->rate, get_ticks_per_sec());
+ else
+ return timer->val;
+}
+
+static inline void omap_timer_sync(struct omap_mpu_timer_s *timer)
+{
+ timer->val = omap_timer_read(timer);
+ timer->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+static inline void omap_timer_update(struct omap_mpu_timer_s *timer)
+{
+ int64_t expires;
+
+ if (timer->enable && timer->st && timer->rate) {
+ timer->val = timer->reset_val; /* Should skip this on clk enable */
+ expires = muldiv64((uint64_t) timer->val << (timer->ptv + 1),
+ get_ticks_per_sec(), timer->rate);
+
+ /* If timer expiry would be sooner than in about 1 ms and
+ * auto-reload isn't set, then fire immediately. This is a hack
+ * to make systems like PalmOS run in acceptable time. PalmOS
+ * sets the interval to a very low value and polls the status bit
+ * in a busy loop when it wants to sleep just a couple of CPU
+ * ticks. */
+ if (expires > (get_ticks_per_sec() >> 10) || timer->ar)
+ timer_mod(timer->timer, timer->time + expires);
+ else
+ qemu_bh_schedule(timer->tick);
+ } else
+ timer_del(timer->timer);
+}
+
+static void omap_timer_fire(void *opaque)
+{
+ struct omap_mpu_timer_s *timer = opaque;
+
+ if (!timer->ar) {
+ timer->val = 0;
+ timer->st = 0;
+ }
+
+ if (timer->it_ena)
+ /* Edge-triggered irq */
+ qemu_irq_pulse(timer->irq);
+}
+
+static void omap_timer_tick(void *opaque)
+{
+ struct omap_mpu_timer_s *timer = (struct omap_mpu_timer_s *) opaque;
+
+ omap_timer_sync(timer);
+ omap_timer_fire(timer);
+ omap_timer_update(timer);
+}
+
+static void omap_timer_clk_update(void *opaque, int line, int on)
+{
+ struct omap_mpu_timer_s *timer = (struct omap_mpu_timer_s *) opaque;
+
+ omap_timer_sync(timer);
+ timer->rate = on ? omap_clk_getrate(timer->clk) : 0;
+ omap_timer_update(timer);
+}
+
+static void omap_timer_clk_setup(struct omap_mpu_timer_s *timer)
+{
+ omap_clk_adduser(timer->clk,
+ qemu_allocate_irq(omap_timer_clk_update, timer, 0));
+ timer->rate = omap_clk_getrate(timer->clk);
+}
+
+static uint64_t omap_mpu_timer_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* CNTL_TIMER */
+ return (s->enable << 5) | (s->ptv << 2) | (s->ar << 1) | s->st;
+
+ case 0x04: /* LOAD_TIM */
+ break;
+
+ case 0x08: /* READ_TIM */
+ return omap_timer_read(s);
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_mpu_timer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* CNTL_TIMER */
+ omap_timer_sync(s);
+ s->enable = (value >> 5) & 1;
+ s->ptv = (value >> 2) & 7;
+ s->ar = (value >> 1) & 1;
+ s->st = value & 1;
+ omap_timer_update(s);
+ return;
+
+ case 0x04: /* LOAD_TIM */
+ s->reset_val = value;
+ return;
+
+ case 0x08: /* READ_TIM */
+ OMAP_RO_REG(addr);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_mpu_timer_ops = {
+ .read = omap_mpu_timer_read,
+ .write = omap_mpu_timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void omap_mpu_timer_reset(struct omap_mpu_timer_s *s)
+{
+ timer_del(s->timer);
+ s->enable = 0;
+ s->reset_val = 31337;
+ s->val = 0;
+ s->ptv = 0;
+ s->ar = 0;
+ s->st = 0;
+ s->it_ena = 1;
+}
+
+static struct omap_mpu_timer_s *omap_mpu_timer_init(MemoryRegion *system_memory,
+ hwaddr base,
+ qemu_irq irq, omap_clk clk)
+{
+ struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *)
+ g_malloc0(sizeof(struct omap_mpu_timer_s));
+
+ s->irq = irq;
+ s->clk = clk;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_timer_tick, s);
+ s->tick = qemu_bh_new(omap_timer_fire, s);
+ omap_mpu_timer_reset(s);
+ omap_timer_clk_setup(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_mpu_timer_ops, s,
+ "omap-mpu-timer", 0x100);
+
+ memory_region_add_subregion(system_memory, base, &s->iomem);
+
+ return s;
+}
+
+/* Watchdog timer */
+struct omap_watchdog_timer_s {
+ struct omap_mpu_timer_s timer;
+ MemoryRegion iomem;
+ uint8_t last_wr;
+ int mode;
+ int free;
+ int reset;
+};
+
+static uint64_t omap_wd_timer_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *) opaque;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* CNTL_TIMER */
+ return (s->timer.ptv << 9) | (s->timer.ar << 8) |
+ (s->timer.st << 7) | (s->free << 1);
+
+ case 0x04: /* READ_TIMER */
+ return omap_timer_read(&s->timer);
+
+ case 0x08: /* TIMER_MODE */
+ return s->mode << 15;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_wd_timer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *) opaque;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* CNTL_TIMER */
+ omap_timer_sync(&s->timer);
+ s->timer.ptv = (value >> 9) & 7;
+ s->timer.ar = (value >> 8) & 1;
+ s->timer.st = (value >> 7) & 1;
+ s->free = (value >> 1) & 1;
+ omap_timer_update(&s->timer);
+ break;
+
+ case 0x04: /* LOAD_TIMER */
+ s->timer.reset_val = value & 0xffff;
+ break;
+
+ case 0x08: /* TIMER_MODE */
+ if (!s->mode && ((value >> 15) & 1))
+ omap_clk_get(s->timer.clk);
+ s->mode |= (value >> 15) & 1;
+ if (s->last_wr == 0xf5) {
+ if ((value & 0xff) == 0xa0) {
+ if (s->mode) {
+ s->mode = 0;
+ omap_clk_put(s->timer.clk);
+ }
+ } else {
+ /* XXX: on T|E hardware somehow this has no effect,
+ * on Zire 71 it works as specified. */
+ s->reset = 1;
+ qemu_system_reset_request();
+ }
+ }
+ s->last_wr = value & 0xff;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_wd_timer_ops = {
+ .read = omap_wd_timer_read,
+ .write = omap_wd_timer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_wd_timer_reset(struct omap_watchdog_timer_s *s)
+{
+ timer_del(s->timer.timer);
+ if (!s->mode)
+ omap_clk_get(s->timer.clk);
+ s->mode = 1;
+ s->free = 1;
+ s->reset = 0;
+ s->timer.enable = 1;
+ s->timer.it_ena = 1;
+ s->timer.reset_val = 0xffff;
+ s->timer.val = 0;
+ s->timer.st = 0;
+ s->timer.ptv = 0;
+ s->timer.ar = 0;
+ omap_timer_update(&s->timer);
+}
+
+static struct omap_watchdog_timer_s *omap_wd_timer_init(MemoryRegion *memory,
+ hwaddr base,
+ qemu_irq irq, omap_clk clk)
+{
+ struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *)
+ g_malloc0(sizeof(struct omap_watchdog_timer_s));
+
+ s->timer.irq = irq;
+ s->timer.clk = clk;
+ s->timer.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_timer_tick, &s->timer);
+ omap_wd_timer_reset(s);
+ omap_timer_clk_setup(&s->timer);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_wd_timer_ops, s,
+ "omap-wd-timer", 0x100);
+ memory_region_add_subregion(memory, base, &s->iomem);
+
+ return s;
+}
+
+/* 32-kHz timer */
+struct omap_32khz_timer_s {
+ struct omap_mpu_timer_s timer;
+ MemoryRegion iomem;
+};
+
+static uint64_t omap_os_timer_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* TVR */
+ return s->timer.reset_val;
+
+ case 0x04: /* TCR */
+ return omap_timer_read(&s->timer);
+
+ case 0x08: /* CR */
+ return (s->timer.ar << 3) | (s->timer.it_ena << 2) | s->timer.st;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_os_timer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* TVR */
+ s->timer.reset_val = value & 0x00ffffff;
+ break;
+
+ case 0x04: /* TCR */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x08: /* CR */
+ s->timer.ar = (value >> 3) & 1;
+ s->timer.it_ena = (value >> 2) & 1;
+ if (s->timer.st != (value & 1) || (value & 2)) {
+ omap_timer_sync(&s->timer);
+ s->timer.enable = value & 1;
+ s->timer.st = value & 1;
+ omap_timer_update(&s->timer);
+ }
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_os_timer_ops = {
+ .read = omap_os_timer_read,
+ .write = omap_os_timer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_os_timer_reset(struct omap_32khz_timer_s *s)
+{
+ timer_del(s->timer.timer);
+ s->timer.enable = 0;
+ s->timer.it_ena = 0;
+ s->timer.reset_val = 0x00ffffff;
+ s->timer.val = 0;
+ s->timer.st = 0;
+ s->timer.ptv = 0;
+ s->timer.ar = 1;
+}
+
+static struct omap_32khz_timer_s *omap_os_timer_init(MemoryRegion *memory,
+ hwaddr base,
+ qemu_irq irq, omap_clk clk)
+{
+ struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *)
+ g_malloc0(sizeof(struct omap_32khz_timer_s));
+
+ s->timer.irq = irq;
+ s->timer.clk = clk;
+ s->timer.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_timer_tick, &s->timer);
+ omap_os_timer_reset(s);
+ omap_timer_clk_setup(&s->timer);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_os_timer_ops, s,
+ "omap-os-timer", 0x800);
+ memory_region_add_subregion(memory, base, &s->iomem);
+
+ return s;
+}
+
+/* Ultra Low-Power Device Module */
+static uint64_t omap_ulpd_pm_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+ uint16_t ret;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x14: /* IT_STATUS */
+ ret = s->ulpd_pm_regs[addr >> 2];
+ s->ulpd_pm_regs[addr >> 2] = 0;
+ qemu_irq_lower(qdev_get_gpio_in(s->ih[1], OMAP_INT_GAUGE_32K));
+ return ret;
+
+ case 0x18: /* Reserved */
+ case 0x1c: /* Reserved */
+ case 0x20: /* Reserved */
+ case 0x28: /* Reserved */
+ case 0x2c: /* Reserved */
+ OMAP_BAD_REG(addr);
+ /* fall through */
+ case 0x00: /* COUNTER_32_LSB */
+ case 0x04: /* COUNTER_32_MSB */
+ case 0x08: /* COUNTER_HIGH_FREQ_LSB */
+ case 0x0c: /* COUNTER_HIGH_FREQ_MSB */
+ case 0x10: /* GAUGING_CTRL */
+ case 0x24: /* SETUP_ANALOG_CELL3_ULPD1 */
+ case 0x30: /* CLOCK_CTRL */
+ case 0x34: /* SOFT_REQ */
+ case 0x38: /* COUNTER_32_FIQ */
+ case 0x3c: /* DPLL_CTRL */
+ case 0x40: /* STATUS_REQ */
+ /* XXX: check clk::usecount state for every clock */
+ case 0x48: /* LOCL_TIME */
+ case 0x4c: /* APLL_CTRL */
+ case 0x50: /* POWER_CTRL */
+ return s->ulpd_pm_regs[addr >> 2];
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static inline void omap_ulpd_clk_update(struct omap_mpu_state_s *s,
+ uint16_t diff, uint16_t value)
+{
+ if (diff & (1 << 4)) /* USB_MCLK_EN */
+ omap_clk_onoff(omap_findclk(s, "usb_clk0"), (value >> 4) & 1);
+ if (diff & (1 << 5)) /* DIS_USB_PVCI_CLK */
+ omap_clk_onoff(omap_findclk(s, "usb_w2fc_ck"), (~value >> 5) & 1);
+}
+
+static inline void omap_ulpd_req_update(struct omap_mpu_state_s *s,
+ uint16_t diff, uint16_t value)
+{
+ if (diff & (1 << 0)) /* SOFT_DPLL_REQ */
+ omap_clk_canidle(omap_findclk(s, "dpll4"), (~value >> 0) & 1);
+ if (diff & (1 << 1)) /* SOFT_COM_REQ */
+ omap_clk_canidle(omap_findclk(s, "com_mclk_out"), (~value >> 1) & 1);
+ if (diff & (1 << 2)) /* SOFT_SDW_REQ */
+ omap_clk_canidle(omap_findclk(s, "bt_mclk_out"), (~value >> 2) & 1);
+ if (diff & (1 << 3)) /* SOFT_USB_REQ */
+ omap_clk_canidle(omap_findclk(s, "usb_clk0"), (~value >> 3) & 1);
+}
+
+static void omap_ulpd_pm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+ int64_t now, ticks;
+ int div, mult;
+ static const int bypass_div[4] = { 1, 2, 4, 4 };
+ uint16_t diff;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* COUNTER_32_LSB */
+ case 0x04: /* COUNTER_32_MSB */
+ case 0x08: /* COUNTER_HIGH_FREQ_LSB */
+ case 0x0c: /* COUNTER_HIGH_FREQ_MSB */
+ case 0x14: /* IT_STATUS */
+ case 0x40: /* STATUS_REQ */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* GAUGING_CTRL */
+ /* Bits 0 and 1 seem to be confused in the OMAP 310 TRM */
+ if ((s->ulpd_pm_regs[addr >> 2] ^ value) & 1) {
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ if (value & 1)
+ s->ulpd_gauge_start = now;
+ else {
+ now -= s->ulpd_gauge_start;
+
+ /* 32-kHz ticks */
+ ticks = muldiv64(now, 32768, get_ticks_per_sec());
+ s->ulpd_pm_regs[0x00 >> 2] = (ticks >> 0) & 0xffff;
+ s->ulpd_pm_regs[0x04 >> 2] = (ticks >> 16) & 0xffff;
+ if (ticks >> 32) /* OVERFLOW_32K */
+ s->ulpd_pm_regs[0x14 >> 2] |= 1 << 2;
+
+ /* High frequency ticks */
+ ticks = muldiv64(now, 12000000, get_ticks_per_sec());
+ s->ulpd_pm_regs[0x08 >> 2] = (ticks >> 0) & 0xffff;
+ s->ulpd_pm_regs[0x0c >> 2] = (ticks >> 16) & 0xffff;
+ if (ticks >> 32) /* OVERFLOW_HI_FREQ */
+ s->ulpd_pm_regs[0x14 >> 2] |= 1 << 1;
+
+ s->ulpd_pm_regs[0x14 >> 2] |= 1 << 0; /* IT_GAUGING */
+ qemu_irq_raise(qdev_get_gpio_in(s->ih[1], OMAP_INT_GAUGE_32K));
+ }
+ }
+ s->ulpd_pm_regs[addr >> 2] = value;
+ break;
+
+ case 0x18: /* Reserved */
+ case 0x1c: /* Reserved */
+ case 0x20: /* Reserved */
+ case 0x28: /* Reserved */
+ case 0x2c: /* Reserved */
+ OMAP_BAD_REG(addr);
+ /* fall through */
+ case 0x24: /* SETUP_ANALOG_CELL3_ULPD1 */
+ case 0x38: /* COUNTER_32_FIQ */
+ case 0x48: /* LOCL_TIME */
+ case 0x50: /* POWER_CTRL */
+ s->ulpd_pm_regs[addr >> 2] = value;
+ break;
+
+ case 0x30: /* CLOCK_CTRL */
+ diff = s->ulpd_pm_regs[addr >> 2] ^ value;
+ s->ulpd_pm_regs[addr >> 2] = value & 0x3f;
+ omap_ulpd_clk_update(s, diff, value);
+ break;
+
+ case 0x34: /* SOFT_REQ */
+ diff = s->ulpd_pm_regs[addr >> 2] ^ value;
+ s->ulpd_pm_regs[addr >> 2] = value & 0x1f;
+ omap_ulpd_req_update(s, diff, value);
+ break;
+
+ case 0x3c: /* DPLL_CTRL */
+ /* XXX: OMAP310 TRM claims bit 3 is PLL_ENABLE, and bit 4 is
+ * omitted altogether, probably a typo. */
+ /* This register has identical semantics with DPLL(1:3) control
+ * registers, see omap_dpll_write() */
+ diff = s->ulpd_pm_regs[addr >> 2] & value;
+ s->ulpd_pm_regs[addr >> 2] = value & 0x2fff;
+ if (diff & (0x3ff << 2)) {
+ if (value & (1 << 4)) { /* PLL_ENABLE */
+ div = ((value >> 5) & 3) + 1; /* PLL_DIV */
+ mult = MIN((value >> 7) & 0x1f, 1); /* PLL_MULT */
+ } else {
+ div = bypass_div[((value >> 2) & 3)]; /* BYPASS_DIV */
+ mult = 1;
+ }
+ omap_clk_setrate(omap_findclk(s, "dpll4"), div, mult);
+ }
+
+ /* Enter the desired mode. */
+ s->ulpd_pm_regs[addr >> 2] =
+ (s->ulpd_pm_regs[addr >> 2] & 0xfffe) |
+ ((s->ulpd_pm_regs[addr >> 2] >> 4) & 1);
+
+ /* Act as if the lock is restored. */
+ s->ulpd_pm_regs[addr >> 2] |= 2;
+ break;
+
+ case 0x4c: /* APLL_CTRL */
+ diff = s->ulpd_pm_regs[addr >> 2] & value;
+ s->ulpd_pm_regs[addr >> 2] = value & 0xf;
+ if (diff & (1 << 0)) /* APLL_NDPLL_SWITCH */
+ omap_clk_reparent(omap_findclk(s, "ck_48m"), omap_findclk(s,
+ (value & (1 << 0)) ? "apll" : "dpll4"));
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_ulpd_pm_ops = {
+ .read = omap_ulpd_pm_read,
+ .write = omap_ulpd_pm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_ulpd_pm_reset(struct omap_mpu_state_s *mpu)
+{
+ mpu->ulpd_pm_regs[0x00 >> 2] = 0x0001;
+ mpu->ulpd_pm_regs[0x04 >> 2] = 0x0000;
+ mpu->ulpd_pm_regs[0x08 >> 2] = 0x0001;
+ mpu->ulpd_pm_regs[0x0c >> 2] = 0x0000;
+ mpu->ulpd_pm_regs[0x10 >> 2] = 0x0000;
+ mpu->ulpd_pm_regs[0x18 >> 2] = 0x01;
+ mpu->ulpd_pm_regs[0x1c >> 2] = 0x01;
+ mpu->ulpd_pm_regs[0x20 >> 2] = 0x01;
+ mpu->ulpd_pm_regs[0x24 >> 2] = 0x03ff;
+ mpu->ulpd_pm_regs[0x28 >> 2] = 0x01;
+ mpu->ulpd_pm_regs[0x2c >> 2] = 0x01;
+ omap_ulpd_clk_update(mpu, mpu->ulpd_pm_regs[0x30 >> 2], 0x0000);
+ mpu->ulpd_pm_regs[0x30 >> 2] = 0x0000;
+ omap_ulpd_req_update(mpu, mpu->ulpd_pm_regs[0x34 >> 2], 0x0000);
+ mpu->ulpd_pm_regs[0x34 >> 2] = 0x0000;
+ mpu->ulpd_pm_regs[0x38 >> 2] = 0x0001;
+ mpu->ulpd_pm_regs[0x3c >> 2] = 0x2211;
+ mpu->ulpd_pm_regs[0x40 >> 2] = 0x0000; /* FIXME: dump a real STATUS_REQ */
+ mpu->ulpd_pm_regs[0x48 >> 2] = 0x960;
+ mpu->ulpd_pm_regs[0x4c >> 2] = 0x08;
+ mpu->ulpd_pm_regs[0x50 >> 2] = 0x08;
+ omap_clk_setrate(omap_findclk(mpu, "dpll4"), 1, 4);
+ omap_clk_reparent(omap_findclk(mpu, "ck_48m"), omap_findclk(mpu, "dpll4"));
+}
+
+static void omap_ulpd_pm_init(MemoryRegion *system_memory,
+ hwaddr base,
+ struct omap_mpu_state_s *mpu)
+{
+ memory_region_init_io(&mpu->ulpd_pm_iomem, NULL, &omap_ulpd_pm_ops, mpu,
+ "omap-ulpd-pm", 0x800);
+ memory_region_add_subregion(system_memory, base, &mpu->ulpd_pm_iomem);
+ omap_ulpd_pm_reset(mpu);
+}
+
+/* OMAP Pin Configuration */
+static uint64_t omap_pin_cfg_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* FUNC_MUX_CTRL_0 */
+ case 0x04: /* FUNC_MUX_CTRL_1 */
+ case 0x08: /* FUNC_MUX_CTRL_2 */
+ return s->func_mux_ctrl[addr >> 2];
+
+ case 0x0c: /* COMP_MODE_CTRL_0 */
+ return s->comp_mode_ctrl[0];
+
+ case 0x10: /* FUNC_MUX_CTRL_3 */
+ case 0x14: /* FUNC_MUX_CTRL_4 */
+ case 0x18: /* FUNC_MUX_CTRL_5 */
+ case 0x1c: /* FUNC_MUX_CTRL_6 */
+ case 0x20: /* FUNC_MUX_CTRL_7 */
+ case 0x24: /* FUNC_MUX_CTRL_8 */
+ case 0x28: /* FUNC_MUX_CTRL_9 */
+ case 0x2c: /* FUNC_MUX_CTRL_A */
+ case 0x30: /* FUNC_MUX_CTRL_B */
+ case 0x34: /* FUNC_MUX_CTRL_C */
+ case 0x38: /* FUNC_MUX_CTRL_D */
+ return s->func_mux_ctrl[(addr >> 2) - 1];
+
+ case 0x40: /* PULL_DWN_CTRL_0 */
+ case 0x44: /* PULL_DWN_CTRL_1 */
+ case 0x48: /* PULL_DWN_CTRL_2 */
+ case 0x4c: /* PULL_DWN_CTRL_3 */
+ return s->pull_dwn_ctrl[(addr & 0xf) >> 2];
+
+ case 0x50: /* GATE_INH_CTRL_0 */
+ return s->gate_inh_ctrl[0];
+
+ case 0x60: /* VOLTAGE_CTRL_0 */
+ return s->voltage_ctrl[0];
+
+ case 0x70: /* TEST_DBG_CTRL_0 */
+ return s->test_dbg_ctrl[0];
+
+ case 0x80: /* MOD_CONF_CTRL_0 */
+ return s->mod_conf_ctrl[0];
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static inline void omap_pin_funcmux0_update(struct omap_mpu_state_s *s,
+ uint32_t diff, uint32_t value)
+{
+ if (s->compat1509) {
+ if (diff & (1 << 9)) /* BLUETOOTH */
+ omap_clk_onoff(omap_findclk(s, "bt_mclk_out"),
+ (~value >> 9) & 1);
+ if (diff & (1 << 7)) /* USB.CLKO */
+ omap_clk_onoff(omap_findclk(s, "usb.clko"),
+ (value >> 7) & 1);
+ }
+}
+
+static inline void omap_pin_funcmux1_update(struct omap_mpu_state_s *s,
+ uint32_t diff, uint32_t value)
+{
+ if (s->compat1509) {
+ if (diff & (1U << 31)) {
+ /* MCBSP3_CLK_HIZ_DI */
+ omap_clk_onoff(omap_findclk(s, "mcbsp3.clkx"), (value >> 31) & 1);
+ }
+ if (diff & (1 << 1)) {
+ /* CLK32K */
+ omap_clk_onoff(omap_findclk(s, "clk32k_out"), (~value >> 1) & 1);
+ }
+ }
+}
+
+static inline void omap_pin_modconf1_update(struct omap_mpu_state_s *s,
+ uint32_t diff, uint32_t value)
+{
+ if (diff & (1U << 31)) {
+ /* CONF_MOD_UART3_CLK_MODE_R */
+ omap_clk_reparent(omap_findclk(s, "uart3_ck"),
+ omap_findclk(s, ((value >> 31) & 1) ?
+ "ck_48m" : "armper_ck"));
+ }
+ if (diff & (1 << 30)) /* CONF_MOD_UART2_CLK_MODE_R */
+ omap_clk_reparent(omap_findclk(s, "uart2_ck"),
+ omap_findclk(s, ((value >> 30) & 1) ?
+ "ck_48m" : "armper_ck"));
+ if (diff & (1 << 29)) /* CONF_MOD_UART1_CLK_MODE_R */
+ omap_clk_reparent(omap_findclk(s, "uart1_ck"),
+ omap_findclk(s, ((value >> 29) & 1) ?
+ "ck_48m" : "armper_ck"));
+ if (diff & (1 << 23)) /* CONF_MOD_MMC_SD_CLK_REQ_R */
+ omap_clk_reparent(omap_findclk(s, "mmc_ck"),
+ omap_findclk(s, ((value >> 23) & 1) ?
+ "ck_48m" : "armper_ck"));
+ if (diff & (1 << 12)) /* CONF_MOD_COM_MCLK_12_48_S */
+ omap_clk_reparent(omap_findclk(s, "com_mclk_out"),
+ omap_findclk(s, ((value >> 12) & 1) ?
+ "ck_48m" : "armper_ck"));
+ if (diff & (1 << 9)) /* CONF_MOD_USB_HOST_HHC_UHO */
+ omap_clk_onoff(omap_findclk(s, "usb_hhc_ck"), (value >> 9) & 1);
+}
+
+static void omap_pin_cfg_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+ uint32_t diff;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* FUNC_MUX_CTRL_0 */
+ diff = s->func_mux_ctrl[addr >> 2] ^ value;
+ s->func_mux_ctrl[addr >> 2] = value;
+ omap_pin_funcmux0_update(s, diff, value);
+ return;
+
+ case 0x04: /* FUNC_MUX_CTRL_1 */
+ diff = s->func_mux_ctrl[addr >> 2] ^ value;
+ s->func_mux_ctrl[addr >> 2] = value;
+ omap_pin_funcmux1_update(s, diff, value);
+ return;
+
+ case 0x08: /* FUNC_MUX_CTRL_2 */
+ s->func_mux_ctrl[addr >> 2] = value;
+ return;
+
+ case 0x0c: /* COMP_MODE_CTRL_0 */
+ s->comp_mode_ctrl[0] = value;
+ s->compat1509 = (value != 0x0000eaef);
+ omap_pin_funcmux0_update(s, ~0, s->func_mux_ctrl[0]);
+ omap_pin_funcmux1_update(s, ~0, s->func_mux_ctrl[1]);
+ return;
+
+ case 0x10: /* FUNC_MUX_CTRL_3 */
+ case 0x14: /* FUNC_MUX_CTRL_4 */
+ case 0x18: /* FUNC_MUX_CTRL_5 */
+ case 0x1c: /* FUNC_MUX_CTRL_6 */
+ case 0x20: /* FUNC_MUX_CTRL_7 */
+ case 0x24: /* FUNC_MUX_CTRL_8 */
+ case 0x28: /* FUNC_MUX_CTRL_9 */
+ case 0x2c: /* FUNC_MUX_CTRL_A */
+ case 0x30: /* FUNC_MUX_CTRL_B */
+ case 0x34: /* FUNC_MUX_CTRL_C */
+ case 0x38: /* FUNC_MUX_CTRL_D */
+ s->func_mux_ctrl[(addr >> 2) - 1] = value;
+ return;
+
+ case 0x40: /* PULL_DWN_CTRL_0 */
+ case 0x44: /* PULL_DWN_CTRL_1 */
+ case 0x48: /* PULL_DWN_CTRL_2 */
+ case 0x4c: /* PULL_DWN_CTRL_3 */
+ s->pull_dwn_ctrl[(addr & 0xf) >> 2] = value;
+ return;
+
+ case 0x50: /* GATE_INH_CTRL_0 */
+ s->gate_inh_ctrl[0] = value;
+ return;
+
+ case 0x60: /* VOLTAGE_CTRL_0 */
+ s->voltage_ctrl[0] = value;
+ return;
+
+ case 0x70: /* TEST_DBG_CTRL_0 */
+ s->test_dbg_ctrl[0] = value;
+ return;
+
+ case 0x80: /* MOD_CONF_CTRL_0 */
+ diff = s->mod_conf_ctrl[0] ^ value;
+ s->mod_conf_ctrl[0] = value;
+ omap_pin_modconf1_update(s, diff, value);
+ return;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_pin_cfg_ops = {
+ .read = omap_pin_cfg_read,
+ .write = omap_pin_cfg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_pin_cfg_reset(struct omap_mpu_state_s *mpu)
+{
+ /* Start in Compatibility Mode. */
+ mpu->compat1509 = 1;
+ omap_pin_funcmux0_update(mpu, mpu->func_mux_ctrl[0], 0);
+ omap_pin_funcmux1_update(mpu, mpu->func_mux_ctrl[1], 0);
+ omap_pin_modconf1_update(mpu, mpu->mod_conf_ctrl[0], 0);
+ memset(mpu->func_mux_ctrl, 0, sizeof(mpu->func_mux_ctrl));
+ memset(mpu->comp_mode_ctrl, 0, sizeof(mpu->comp_mode_ctrl));
+ memset(mpu->pull_dwn_ctrl, 0, sizeof(mpu->pull_dwn_ctrl));
+ memset(mpu->gate_inh_ctrl, 0, sizeof(mpu->gate_inh_ctrl));
+ memset(mpu->voltage_ctrl, 0, sizeof(mpu->voltage_ctrl));
+ memset(mpu->test_dbg_ctrl, 0, sizeof(mpu->test_dbg_ctrl));
+ memset(mpu->mod_conf_ctrl, 0, sizeof(mpu->mod_conf_ctrl));
+}
+
+static void omap_pin_cfg_init(MemoryRegion *system_memory,
+ hwaddr base,
+ struct omap_mpu_state_s *mpu)
+{
+ memory_region_init_io(&mpu->pin_cfg_iomem, NULL, &omap_pin_cfg_ops, mpu,
+ "omap-pin-cfg", 0x800);
+ memory_region_add_subregion(system_memory, base, &mpu->pin_cfg_iomem);
+ omap_pin_cfg_reset(mpu);
+}
+
+/* Device Identification, Die Identification */
+static uint64_t omap_id_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0xfffe1800: /* DIE_ID_LSB */
+ return 0xc9581f0e;
+ case 0xfffe1804: /* DIE_ID_MSB */
+ return 0xa8858bfa;
+
+ case 0xfffe2000: /* PRODUCT_ID_LSB */
+ return 0x00aaaafc;
+ case 0xfffe2004: /* PRODUCT_ID_MSB */
+ return 0xcafeb574;
+
+ case 0xfffed400: /* JTAG_ID_LSB */
+ switch (s->mpu_model) {
+ case omap310:
+ return 0x03310315;
+ case omap1510:
+ return 0x03310115;
+ default:
+ hw_error("%s: bad mpu model\n", __FUNCTION__);
+ }
+ break;
+
+ case 0xfffed404: /* JTAG_ID_MSB */
+ switch (s->mpu_model) {
+ case omap310:
+ return 0xfb57402f;
+ case omap1510:
+ return 0xfb47002f;
+ default:
+ hw_error("%s: bad mpu model\n", __FUNCTION__);
+ }
+ break;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_id_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap_id_ops = {
+ .read = omap_id_read,
+ .write = omap_id_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_id_init(MemoryRegion *memory, struct omap_mpu_state_s *mpu)
+{
+ memory_region_init_io(&mpu->id_iomem, NULL, &omap_id_ops, mpu,
+ "omap-id", 0x100000000ULL);
+ memory_region_init_alias(&mpu->id_iomem_e18, NULL, "omap-id-e18", &mpu->id_iomem,
+ 0xfffe1800, 0x800);
+ memory_region_add_subregion(memory, 0xfffe1800, &mpu->id_iomem_e18);
+ memory_region_init_alias(&mpu->id_iomem_ed4, NULL, "omap-id-ed4", &mpu->id_iomem,
+ 0xfffed400, 0x100);
+ memory_region_add_subregion(memory, 0xfffed400, &mpu->id_iomem_ed4);
+ if (!cpu_is_omap15xx(mpu)) {
+ memory_region_init_alias(&mpu->id_iomem_ed4, NULL, "omap-id-e20",
+ &mpu->id_iomem, 0xfffe2000, 0x800);
+ memory_region_add_subregion(memory, 0xfffe2000, &mpu->id_iomem_e20);
+ }
+}
+
+/* MPUI Control (Dummy) */
+static uint64_t omap_mpui_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* CTRL */
+ return s->mpui_ctrl;
+ case 0x04: /* DEBUG_ADDR */
+ return 0x01ffffff;
+ case 0x08: /* DEBUG_DATA */
+ return 0xffffffff;
+ case 0x0c: /* DEBUG_FLAG */
+ return 0x00000800;
+ case 0x10: /* STATUS */
+ return 0x00000000;
+
+ /* Not in OMAP310 */
+ case 0x14: /* DSP_STATUS */
+ case 0x18: /* DSP_BOOT_CONFIG */
+ return 0x00000000;
+ case 0x1c: /* DSP_MPUI_CONFIG */
+ return 0x0000ffff;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_mpui_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* CTRL */
+ s->mpui_ctrl = value & 0x007fffff;
+ break;
+
+ case 0x04: /* DEBUG_ADDR */
+ case 0x08: /* DEBUG_DATA */
+ case 0x0c: /* DEBUG_FLAG */
+ case 0x10: /* STATUS */
+ /* Not in OMAP310 */
+ case 0x14: /* DSP_STATUS */
+ OMAP_RO_REG(addr);
+ break;
+ case 0x18: /* DSP_BOOT_CONFIG */
+ case 0x1c: /* DSP_MPUI_CONFIG */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_mpui_ops = {
+ .read = omap_mpui_read,
+ .write = omap_mpui_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_mpui_reset(struct omap_mpu_state_s *s)
+{
+ s->mpui_ctrl = 0x0003ff1b;
+}
+
+static void omap_mpui_init(MemoryRegion *memory, hwaddr base,
+ struct omap_mpu_state_s *mpu)
+{
+ memory_region_init_io(&mpu->mpui_iomem, NULL, &omap_mpui_ops, mpu,
+ "omap-mpui", 0x100);
+ memory_region_add_subregion(memory, base, &mpu->mpui_iomem);
+
+ omap_mpui_reset(mpu);
+}
+
+/* TIPB Bridges */
+struct omap_tipb_bridge_s {
+ qemu_irq abort;
+ MemoryRegion iomem;
+
+ int width_intr;
+ uint16_t control;
+ uint16_t alloc;
+ uint16_t buffer;
+ uint16_t enh_control;
+};
+
+static uint64_t omap_tipb_bridge_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *) opaque;
+
+ if (size < 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* TIPB_CNTL */
+ return s->control;
+ case 0x04: /* TIPB_BUS_ALLOC */
+ return s->alloc;
+ case 0x08: /* MPU_TIPB_CNTL */
+ return s->buffer;
+ case 0x0c: /* ENHANCED_TIPB_CNTL */
+ return s->enh_control;
+ case 0x10: /* ADDRESS_DBG */
+ case 0x14: /* DATA_DEBUG_LOW */
+ case 0x18: /* DATA_DEBUG_HIGH */
+ return 0xffff;
+ case 0x1c: /* DEBUG_CNTR_SIG */
+ return 0x00f8;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_tipb_bridge_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *) opaque;
+
+ if (size < 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* TIPB_CNTL */
+ s->control = value & 0xffff;
+ break;
+
+ case 0x04: /* TIPB_BUS_ALLOC */
+ s->alloc = value & 0x003f;
+ break;
+
+ case 0x08: /* MPU_TIPB_CNTL */
+ s->buffer = value & 0x0003;
+ break;
+
+ case 0x0c: /* ENHANCED_TIPB_CNTL */
+ s->width_intr = !(value & 2);
+ s->enh_control = value & 0x000f;
+ break;
+
+ case 0x10: /* ADDRESS_DBG */
+ case 0x14: /* DATA_DEBUG_LOW */
+ case 0x18: /* DATA_DEBUG_HIGH */
+ case 0x1c: /* DEBUG_CNTR_SIG */
+ OMAP_RO_REG(addr);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_tipb_bridge_ops = {
+ .read = omap_tipb_bridge_read,
+ .write = omap_tipb_bridge_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_tipb_bridge_reset(struct omap_tipb_bridge_s *s)
+{
+ s->control = 0xffff;
+ s->alloc = 0x0009;
+ s->buffer = 0x0000;
+ s->enh_control = 0x000f;
+}
+
+static struct omap_tipb_bridge_s *omap_tipb_bridge_init(
+ MemoryRegion *memory, hwaddr base,
+ qemu_irq abort_irq, omap_clk clk)
+{
+ struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *)
+ g_malloc0(sizeof(struct omap_tipb_bridge_s));
+
+ s->abort = abort_irq;
+ omap_tipb_bridge_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_tipb_bridge_ops, s,
+ "omap-tipb-bridge", 0x100);
+ memory_region_add_subregion(memory, base, &s->iomem);
+
+ return s;
+}
+
+/* Dummy Traffic Controller's Memory Interface */
+static uint64_t omap_tcmi_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+ uint32_t ret;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* IMIF_PRIO */
+ case 0x04: /* EMIFS_PRIO */
+ case 0x08: /* EMIFF_PRIO */
+ case 0x0c: /* EMIFS_CONFIG */
+ case 0x10: /* EMIFS_CS0_CONFIG */
+ case 0x14: /* EMIFS_CS1_CONFIG */
+ case 0x18: /* EMIFS_CS2_CONFIG */
+ case 0x1c: /* EMIFS_CS3_CONFIG */
+ case 0x24: /* EMIFF_MRS */
+ case 0x28: /* TIMEOUT1 */
+ case 0x2c: /* TIMEOUT2 */
+ case 0x30: /* TIMEOUT3 */
+ case 0x3c: /* EMIFF_SDRAM_CONFIG_2 */
+ case 0x40: /* EMIFS_CFG_DYN_WAIT */
+ return s->tcmi_regs[addr >> 2];
+
+ case 0x20: /* EMIFF_SDRAM_CONFIG */
+ ret = s->tcmi_regs[addr >> 2];
+ s->tcmi_regs[addr >> 2] &= ~1; /* XXX: Clear SLRF on SDRAM access */
+ /* XXX: We can try using the VGA_DIRTY flag for this */
+ return ret;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_tcmi_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* IMIF_PRIO */
+ case 0x04: /* EMIFS_PRIO */
+ case 0x08: /* EMIFF_PRIO */
+ case 0x10: /* EMIFS_CS0_CONFIG */
+ case 0x14: /* EMIFS_CS1_CONFIG */
+ case 0x18: /* EMIFS_CS2_CONFIG */
+ case 0x1c: /* EMIFS_CS3_CONFIG */
+ case 0x20: /* EMIFF_SDRAM_CONFIG */
+ case 0x24: /* EMIFF_MRS */
+ case 0x28: /* TIMEOUT1 */
+ case 0x2c: /* TIMEOUT2 */
+ case 0x30: /* TIMEOUT3 */
+ case 0x3c: /* EMIFF_SDRAM_CONFIG_2 */
+ case 0x40: /* EMIFS_CFG_DYN_WAIT */
+ s->tcmi_regs[addr >> 2] = value;
+ break;
+ case 0x0c: /* EMIFS_CONFIG */
+ s->tcmi_regs[addr >> 2] = (value & 0xf) | (1 << 4);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_tcmi_ops = {
+ .read = omap_tcmi_read,
+ .write = omap_tcmi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_tcmi_reset(struct omap_mpu_state_s *mpu)
+{
+ mpu->tcmi_regs[0x00 >> 2] = 0x00000000;
+ mpu->tcmi_regs[0x04 >> 2] = 0x00000000;
+ mpu->tcmi_regs[0x08 >> 2] = 0x00000000;
+ mpu->tcmi_regs[0x0c >> 2] = 0x00000010;
+ mpu->tcmi_regs[0x10 >> 2] = 0x0010fffb;
+ mpu->tcmi_regs[0x14 >> 2] = 0x0010fffb;
+ mpu->tcmi_regs[0x18 >> 2] = 0x0010fffb;
+ mpu->tcmi_regs[0x1c >> 2] = 0x0010fffb;
+ mpu->tcmi_regs[0x20 >> 2] = 0x00618800;
+ mpu->tcmi_regs[0x24 >> 2] = 0x00000037;
+ mpu->tcmi_regs[0x28 >> 2] = 0x00000000;
+ mpu->tcmi_regs[0x2c >> 2] = 0x00000000;
+ mpu->tcmi_regs[0x30 >> 2] = 0x00000000;
+ mpu->tcmi_regs[0x3c >> 2] = 0x00000003;
+ mpu->tcmi_regs[0x40 >> 2] = 0x00000000;
+}
+
+static void omap_tcmi_init(MemoryRegion *memory, hwaddr base,
+ struct omap_mpu_state_s *mpu)
+{
+ memory_region_init_io(&mpu->tcmi_iomem, NULL, &omap_tcmi_ops, mpu,
+ "omap-tcmi", 0x100);
+ memory_region_add_subregion(memory, base, &mpu->tcmi_iomem);
+ omap_tcmi_reset(mpu);
+}
+
+/* Digital phase-locked loops control */
+struct dpll_ctl_s {
+ MemoryRegion iomem;
+ uint16_t mode;
+ omap_clk dpll;
+};
+
+static uint64_t omap_dpll_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct dpll_ctl_s *s = (struct dpll_ctl_s *) opaque;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ if (addr == 0x00) /* CTL_REG */
+ return s->mode;
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_dpll_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct dpll_ctl_s *s = (struct dpll_ctl_s *) opaque;
+ uint16_t diff;
+ static const int bypass_div[4] = { 1, 2, 4, 4 };
+ int div, mult;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ if (addr == 0x00) { /* CTL_REG */
+ /* See omap_ulpd_pm_write() too */
+ diff = s->mode & value;
+ s->mode = value & 0x2fff;
+ if (diff & (0x3ff << 2)) {
+ if (value & (1 << 4)) { /* PLL_ENABLE */
+ div = ((value >> 5) & 3) + 1; /* PLL_DIV */
+ mult = MIN((value >> 7) & 0x1f, 1); /* PLL_MULT */
+ } else {
+ div = bypass_div[((value >> 2) & 3)]; /* BYPASS_DIV */
+ mult = 1;
+ }
+ omap_clk_setrate(s->dpll, div, mult);
+ }
+
+ /* Enter the desired mode. */
+ s->mode = (s->mode & 0xfffe) | ((s->mode >> 4) & 1);
+
+ /* Act as if the lock is restored. */
+ s->mode |= 2;
+ } else {
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_dpll_ops = {
+ .read = omap_dpll_read,
+ .write = omap_dpll_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_dpll_reset(struct dpll_ctl_s *s)
+{
+ s->mode = 0x2002;
+ omap_clk_setrate(s->dpll, 1, 1);
+}
+
+static struct dpll_ctl_s *omap_dpll_init(MemoryRegion *memory,
+ hwaddr base, omap_clk clk)
+{
+ struct dpll_ctl_s *s = g_malloc0(sizeof(*s));
+ memory_region_init_io(&s->iomem, NULL, &omap_dpll_ops, s, "omap-dpll", 0x100);
+
+ s->dpll = clk;
+ omap_dpll_reset(s);
+
+ memory_region_add_subregion(memory, base, &s->iomem);
+ return s;
+}
+
+/* MPU Clock/Reset/Power Mode Control */
+static uint64_t omap_clkm_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* ARM_CKCTL */
+ return s->clkm.arm_ckctl;
+
+ case 0x04: /* ARM_IDLECT1 */
+ return s->clkm.arm_idlect1;
+
+ case 0x08: /* ARM_IDLECT2 */
+ return s->clkm.arm_idlect2;
+
+ case 0x0c: /* ARM_EWUPCT */
+ return s->clkm.arm_ewupct;
+
+ case 0x10: /* ARM_RSTCT1 */
+ return s->clkm.arm_rstct1;
+
+ case 0x14: /* ARM_RSTCT2 */
+ return s->clkm.arm_rstct2;
+
+ case 0x18: /* ARM_SYSST */
+ return (s->clkm.clocking_scheme << 11) | s->clkm.cold_start;
+
+ case 0x1c: /* ARM_CKOUT1 */
+ return s->clkm.arm_ckout1;
+
+ case 0x20: /* ARM_CKOUT2 */
+ break;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static inline void omap_clkm_ckctl_update(struct omap_mpu_state_s *s,
+ uint16_t diff, uint16_t value)
+{
+ omap_clk clk;
+
+ if (diff & (1 << 14)) { /* ARM_INTHCK_SEL */
+ if (value & (1 << 14))
+ /* Reserved */;
+ else {
+ clk = omap_findclk(s, "arminth_ck");
+ omap_clk_reparent(clk, omap_findclk(s, "tc_ck"));
+ }
+ }
+ if (diff & (1 << 12)) { /* ARM_TIMXO */
+ clk = omap_findclk(s, "armtim_ck");
+ if (value & (1 << 12))
+ omap_clk_reparent(clk, omap_findclk(s, "clkin"));
+ else
+ omap_clk_reparent(clk, omap_findclk(s, "ck_gen1"));
+ }
+ /* XXX: en_dspck */
+ if (diff & (3 << 10)) { /* DSPMMUDIV */
+ clk = omap_findclk(s, "dspmmu_ck");
+ omap_clk_setrate(clk, 1 << ((value >> 10) & 3), 1);
+ }
+ if (diff & (3 << 8)) { /* TCDIV */
+ clk = omap_findclk(s, "tc_ck");
+ omap_clk_setrate(clk, 1 << ((value >> 8) & 3), 1);
+ }
+ if (diff & (3 << 6)) { /* DSPDIV */
+ clk = omap_findclk(s, "dsp_ck");
+ omap_clk_setrate(clk, 1 << ((value >> 6) & 3), 1);
+ }
+ if (diff & (3 << 4)) { /* ARMDIV */
+ clk = omap_findclk(s, "arm_ck");
+ omap_clk_setrate(clk, 1 << ((value >> 4) & 3), 1);
+ }
+ if (diff & (3 << 2)) { /* LCDDIV */
+ clk = omap_findclk(s, "lcd_ck");
+ omap_clk_setrate(clk, 1 << ((value >> 2) & 3), 1);
+ }
+ if (diff & (3 << 0)) { /* PERDIV */
+ clk = omap_findclk(s, "armper_ck");
+ omap_clk_setrate(clk, 1 << ((value >> 0) & 3), 1);
+ }
+}
+
+static inline void omap_clkm_idlect1_update(struct omap_mpu_state_s *s,
+ uint16_t diff, uint16_t value)
+{
+ omap_clk clk;
+
+ if (value & (1 << 11)) { /* SETARM_IDLE */
+ cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HALT);
+ }
+ if (!(value & (1 << 10))) /* WKUP_MODE */
+ qemu_system_shutdown_request(); /* XXX: disable wakeup from IRQ */
+
+#define SET_CANIDLE(clock, bit) \
+ if (diff & (1 << bit)) { \
+ clk = omap_findclk(s, clock); \
+ omap_clk_canidle(clk, (value >> bit) & 1); \
+ }
+ SET_CANIDLE("mpuwd_ck", 0) /* IDLWDT_ARM */
+ SET_CANIDLE("armxor_ck", 1) /* IDLXORP_ARM */
+ SET_CANIDLE("mpuper_ck", 2) /* IDLPER_ARM */
+ SET_CANIDLE("lcd_ck", 3) /* IDLLCD_ARM */
+ SET_CANIDLE("lb_ck", 4) /* IDLLB_ARM */
+ SET_CANIDLE("hsab_ck", 5) /* IDLHSAB_ARM */
+ SET_CANIDLE("tipb_ck", 6) /* IDLIF_ARM */
+ SET_CANIDLE("dma_ck", 6) /* IDLIF_ARM */
+ SET_CANIDLE("tc_ck", 6) /* IDLIF_ARM */
+ SET_CANIDLE("dpll1", 7) /* IDLDPLL_ARM */
+ SET_CANIDLE("dpll2", 7) /* IDLDPLL_ARM */
+ SET_CANIDLE("dpll3", 7) /* IDLDPLL_ARM */
+ SET_CANIDLE("mpui_ck", 8) /* IDLAPI_ARM */
+ SET_CANIDLE("armtim_ck", 9) /* IDLTIM_ARM */
+}
+
+static inline void omap_clkm_idlect2_update(struct omap_mpu_state_s *s,
+ uint16_t diff, uint16_t value)
+{
+ omap_clk clk;
+
+#define SET_ONOFF(clock, bit) \
+ if (diff & (1 << bit)) { \
+ clk = omap_findclk(s, clock); \
+ omap_clk_onoff(clk, (value >> bit) & 1); \
+ }
+ SET_ONOFF("mpuwd_ck", 0) /* EN_WDTCK */
+ SET_ONOFF("armxor_ck", 1) /* EN_XORPCK */
+ SET_ONOFF("mpuper_ck", 2) /* EN_PERCK */
+ SET_ONOFF("lcd_ck", 3) /* EN_LCDCK */
+ SET_ONOFF("lb_ck", 4) /* EN_LBCK */
+ SET_ONOFF("hsab_ck", 5) /* EN_HSABCK */
+ SET_ONOFF("mpui_ck", 6) /* EN_APICK */
+ SET_ONOFF("armtim_ck", 7) /* EN_TIMCK */
+ SET_CANIDLE("dma_ck", 8) /* DMACK_REQ */
+ SET_ONOFF("arm_gpio_ck", 9) /* EN_GPIOCK */
+ SET_ONOFF("lbfree_ck", 10) /* EN_LBFREECK */
+}
+
+static inline void omap_clkm_ckout1_update(struct omap_mpu_state_s *s,
+ uint16_t diff, uint16_t value)
+{
+ omap_clk clk;
+
+ if (diff & (3 << 4)) { /* TCLKOUT */
+ clk = omap_findclk(s, "tclk_out");
+ switch ((value >> 4) & 3) {
+ case 1:
+ omap_clk_reparent(clk, omap_findclk(s, "ck_gen3"));
+ omap_clk_onoff(clk, 1);
+ break;
+ case 2:
+ omap_clk_reparent(clk, omap_findclk(s, "tc_ck"));
+ omap_clk_onoff(clk, 1);
+ break;
+ default:
+ omap_clk_onoff(clk, 0);
+ }
+ }
+ if (diff & (3 << 2)) { /* DCLKOUT */
+ clk = omap_findclk(s, "dclk_out");
+ switch ((value >> 2) & 3) {
+ case 0:
+ omap_clk_reparent(clk, omap_findclk(s, "dspmmu_ck"));
+ break;
+ case 1:
+ omap_clk_reparent(clk, omap_findclk(s, "ck_gen2"));
+ break;
+ case 2:
+ omap_clk_reparent(clk, omap_findclk(s, "dsp_ck"));
+ break;
+ case 3:
+ omap_clk_reparent(clk, omap_findclk(s, "ck_ref14"));
+ break;
+ }
+ }
+ if (diff & (3 << 0)) { /* ACLKOUT */
+ clk = omap_findclk(s, "aclk_out");
+ switch ((value >> 0) & 3) {
+ case 1:
+ omap_clk_reparent(clk, omap_findclk(s, "ck_gen1"));
+ omap_clk_onoff(clk, 1);
+ break;
+ case 2:
+ omap_clk_reparent(clk, omap_findclk(s, "arm_ck"));
+ omap_clk_onoff(clk, 1);
+ break;
+ case 3:
+ omap_clk_reparent(clk, omap_findclk(s, "ck_ref14"));
+ omap_clk_onoff(clk, 1);
+ break;
+ default:
+ omap_clk_onoff(clk, 0);
+ }
+ }
+}
+
+static void omap_clkm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+ uint16_t diff;
+ omap_clk clk;
+ static const char *clkschemename[8] = {
+ "fully synchronous", "fully asynchronous", "synchronous scalable",
+ "mix mode 1", "mix mode 2", "bypass mode", "mix mode 3", "mix mode 4",
+ };
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* ARM_CKCTL */
+ diff = s->clkm.arm_ckctl ^ value;
+ s->clkm.arm_ckctl = value & 0x7fff;
+ omap_clkm_ckctl_update(s, diff, value);
+ return;
+
+ case 0x04: /* ARM_IDLECT1 */
+ diff = s->clkm.arm_idlect1 ^ value;
+ s->clkm.arm_idlect1 = value & 0x0fff;
+ omap_clkm_idlect1_update(s, diff, value);
+ return;
+
+ case 0x08: /* ARM_IDLECT2 */
+ diff = s->clkm.arm_idlect2 ^ value;
+ s->clkm.arm_idlect2 = value & 0x07ff;
+ omap_clkm_idlect2_update(s, diff, value);
+ return;
+
+ case 0x0c: /* ARM_EWUPCT */
+ s->clkm.arm_ewupct = value & 0x003f;
+ return;
+
+ case 0x10: /* ARM_RSTCT1 */
+ diff = s->clkm.arm_rstct1 ^ value;
+ s->clkm.arm_rstct1 = value & 0x0007;
+ if (value & 9) {
+ qemu_system_reset_request();
+ s->clkm.cold_start = 0xa;
+ }
+ if (diff & ~value & 4) { /* DSP_RST */
+ omap_mpui_reset(s);
+ omap_tipb_bridge_reset(s->private_tipb);
+ omap_tipb_bridge_reset(s->public_tipb);
+ }
+ if (diff & 2) { /* DSP_EN */
+ clk = omap_findclk(s, "dsp_ck");
+ omap_clk_canidle(clk, (~value >> 1) & 1);
+ }
+ return;
+
+ case 0x14: /* ARM_RSTCT2 */
+ s->clkm.arm_rstct2 = value & 0x0001;
+ return;
+
+ case 0x18: /* ARM_SYSST */
+ if ((s->clkm.clocking_scheme ^ (value >> 11)) & 7) {
+ s->clkm.clocking_scheme = (value >> 11) & 7;
+ printf("%s: clocking scheme set to %s\n", __FUNCTION__,
+ clkschemename[s->clkm.clocking_scheme]);
+ }
+ s->clkm.cold_start &= value & 0x3f;
+ return;
+
+ case 0x1c: /* ARM_CKOUT1 */
+ diff = s->clkm.arm_ckout1 ^ value;
+ s->clkm.arm_ckout1 = value & 0x003f;
+ omap_clkm_ckout1_update(s, diff, value);
+ return;
+
+ case 0x20: /* ARM_CKOUT2 */
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_clkm_ops = {
+ .read = omap_clkm_read,
+ .write = omap_clkm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t omap_clkdsp_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+ CPUState *cpu = CPU(s->cpu);
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x04: /* DSP_IDLECT1 */
+ return s->clkm.dsp_idlect1;
+
+ case 0x08: /* DSP_IDLECT2 */
+ return s->clkm.dsp_idlect2;
+
+ case 0x14: /* DSP_RSTCT2 */
+ return s->clkm.dsp_rstct2;
+
+ case 0x18: /* DSP_SYSST */
+ cpu = CPU(s->cpu);
+ return (s->clkm.clocking_scheme << 11) | s->clkm.cold_start |
+ (cpu->halted << 6); /* Quite useless... */
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static inline void omap_clkdsp_idlect1_update(struct omap_mpu_state_s *s,
+ uint16_t diff, uint16_t value)
+{
+ omap_clk clk;
+
+ SET_CANIDLE("dspxor_ck", 1); /* IDLXORP_DSP */
+}
+
+static inline void omap_clkdsp_idlect2_update(struct omap_mpu_state_s *s,
+ uint16_t diff, uint16_t value)
+{
+ omap_clk clk;
+
+ SET_ONOFF("dspxor_ck", 1); /* EN_XORPCK */
+}
+
+static void omap_clkdsp_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+ uint16_t diff;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x04: /* DSP_IDLECT1 */
+ diff = s->clkm.dsp_idlect1 ^ value;
+ s->clkm.dsp_idlect1 = value & 0x01f7;
+ omap_clkdsp_idlect1_update(s, diff, value);
+ break;
+
+ case 0x08: /* DSP_IDLECT2 */
+ s->clkm.dsp_idlect2 = value & 0x0037;
+ diff = s->clkm.dsp_idlect1 ^ value;
+ omap_clkdsp_idlect2_update(s, diff, value);
+ break;
+
+ case 0x14: /* DSP_RSTCT2 */
+ s->clkm.dsp_rstct2 = value & 0x0001;
+ break;
+
+ case 0x18: /* DSP_SYSST */
+ s->clkm.cold_start &= value & 0x3f;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_clkdsp_ops = {
+ .read = omap_clkdsp_read,
+ .write = omap_clkdsp_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_clkm_reset(struct omap_mpu_state_s *s)
+{
+ if (s->wdt && s->wdt->reset)
+ s->clkm.cold_start = 0x6;
+ s->clkm.clocking_scheme = 0;
+ omap_clkm_ckctl_update(s, ~0, 0x3000);
+ s->clkm.arm_ckctl = 0x3000;
+ omap_clkm_idlect1_update(s, s->clkm.arm_idlect1 ^ 0x0400, 0x0400);
+ s->clkm.arm_idlect1 = 0x0400;
+ omap_clkm_idlect2_update(s, s->clkm.arm_idlect2 ^ 0x0100, 0x0100);
+ s->clkm.arm_idlect2 = 0x0100;
+ s->clkm.arm_ewupct = 0x003f;
+ s->clkm.arm_rstct1 = 0x0000;
+ s->clkm.arm_rstct2 = 0x0000;
+ s->clkm.arm_ckout1 = 0x0015;
+ s->clkm.dpll1_mode = 0x2002;
+ omap_clkdsp_idlect1_update(s, s->clkm.dsp_idlect1 ^ 0x0040, 0x0040);
+ s->clkm.dsp_idlect1 = 0x0040;
+ omap_clkdsp_idlect2_update(s, ~0, 0x0000);
+ s->clkm.dsp_idlect2 = 0x0000;
+ s->clkm.dsp_rstct2 = 0x0000;
+}
+
+static void omap_clkm_init(MemoryRegion *memory, hwaddr mpu_base,
+ hwaddr dsp_base, struct omap_mpu_state_s *s)
+{
+ memory_region_init_io(&s->clkm_iomem, NULL, &omap_clkm_ops, s,
+ "omap-clkm", 0x100);
+ memory_region_init_io(&s->clkdsp_iomem, NULL, &omap_clkdsp_ops, s,
+ "omap-clkdsp", 0x1000);
+
+ s->clkm.arm_idlect1 = 0x03ff;
+ s->clkm.arm_idlect2 = 0x0100;
+ s->clkm.dsp_idlect1 = 0x0002;
+ omap_clkm_reset(s);
+ s->clkm.cold_start = 0x3a;
+
+ memory_region_add_subregion(memory, mpu_base, &s->clkm_iomem);
+ memory_region_add_subregion(memory, dsp_base, &s->clkdsp_iomem);
+}
+
+/* MPU I/O */
+struct omap_mpuio_s {
+ qemu_irq irq;
+ qemu_irq kbd_irq;
+ qemu_irq *in;
+ qemu_irq handler[16];
+ qemu_irq wakeup;
+ MemoryRegion iomem;
+
+ uint16_t inputs;
+ uint16_t outputs;
+ uint16_t dir;
+ uint16_t edge;
+ uint16_t mask;
+ uint16_t ints;
+
+ uint16_t debounce;
+ uint16_t latch;
+ uint8_t event;
+
+ uint8_t buttons[5];
+ uint8_t row_latch;
+ uint8_t cols;
+ int kbd_mask;
+ int clk;
+};
+
+static void omap_mpuio_set(void *opaque, int line, int level)
+{
+ struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque;
+ uint16_t prev = s->inputs;
+
+ if (level)
+ s->inputs |= 1 << line;
+ else
+ s->inputs &= ~(1 << line);
+
+ if (((1 << line) & s->dir & ~s->mask) && s->clk) {
+ if ((s->edge & s->inputs & ~prev) | (~s->edge & ~s->inputs & prev)) {
+ s->ints |= 1 << line;
+ qemu_irq_raise(s->irq);
+ /* TODO: wakeup */
+ }
+ if ((s->event & (1 << 0)) && /* SET_GPIO_EVENT_MODE */
+ (s->event >> 1) == line) /* PIN_SELECT */
+ s->latch = s->inputs;
+ }
+}
+
+static void omap_mpuio_kbd_update(struct omap_mpuio_s *s)
+{
+ int i;
+ uint8_t *row, rows = 0, cols = ~s->cols;
+
+ for (row = s->buttons + 4, i = 1 << 4; i; row --, i >>= 1)
+ if (*row & cols)
+ rows |= i;
+
+ qemu_set_irq(s->kbd_irq, rows && !s->kbd_mask && s->clk);
+ s->row_latch = ~rows;
+}
+
+static uint64_t omap_mpuio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ uint16_t ret;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* INPUT_LATCH */
+ return s->inputs;
+
+ case 0x04: /* OUTPUT_REG */
+ return s->outputs;
+
+ case 0x08: /* IO_CNTL */
+ return s->dir;
+
+ case 0x10: /* KBR_LATCH */
+ return s->row_latch;
+
+ case 0x14: /* KBC_REG */
+ return s->cols;
+
+ case 0x18: /* GPIO_EVENT_MODE_REG */
+ return s->event;
+
+ case 0x1c: /* GPIO_INT_EDGE_REG */
+ return s->edge;
+
+ case 0x20: /* KBD_INT */
+ return (~s->row_latch & 0x1f) && !s->kbd_mask;
+
+ case 0x24: /* GPIO_INT */
+ ret = s->ints;
+ s->ints &= s->mask;
+ if (ret)
+ qemu_irq_lower(s->irq);
+ return ret;
+
+ case 0x28: /* KBD_MASKIT */
+ return s->kbd_mask;
+
+ case 0x2c: /* GPIO_MASKIT */
+ return s->mask;
+
+ case 0x30: /* GPIO_DEBOUNCING_REG */
+ return s->debounce;
+
+ case 0x34: /* GPIO_LATCH_REG */
+ return s->latch;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_mpuio_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ uint16_t diff;
+ int ln;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x04: /* OUTPUT_REG */
+ diff = (s->outputs ^ value) & ~s->dir;
+ s->outputs = value;
+ while ((ln = ctz32(diff)) != 32) {
+ if (s->handler[ln])
+ qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+ diff &= ~(1 << ln);
+ }
+ break;
+
+ case 0x08: /* IO_CNTL */
+ diff = s->outputs & (s->dir ^ value);
+ s->dir = value;
+
+ value = s->outputs & ~s->dir;
+ while ((ln = ctz32(diff)) != 32) {
+ if (s->handler[ln])
+ qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+ diff &= ~(1 << ln);
+ }
+ break;
+
+ case 0x14: /* KBC_REG */
+ s->cols = value;
+ omap_mpuio_kbd_update(s);
+ break;
+
+ case 0x18: /* GPIO_EVENT_MODE_REG */
+ s->event = value & 0x1f;
+ break;
+
+ case 0x1c: /* GPIO_INT_EDGE_REG */
+ s->edge = value;
+ break;
+
+ case 0x28: /* KBD_MASKIT */
+ s->kbd_mask = value & 1;
+ omap_mpuio_kbd_update(s);
+ break;
+
+ case 0x2c: /* GPIO_MASKIT */
+ s->mask = value;
+ break;
+
+ case 0x30: /* GPIO_DEBOUNCING_REG */
+ s->debounce = value & 0x1ff;
+ break;
+
+ case 0x00: /* INPUT_LATCH */
+ case 0x10: /* KBR_LATCH */
+ case 0x20: /* KBD_INT */
+ case 0x24: /* GPIO_INT */
+ case 0x34: /* GPIO_LATCH_REG */
+ OMAP_RO_REG(addr);
+ return;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_mpuio_ops = {
+ .read = omap_mpuio_read,
+ .write = omap_mpuio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_mpuio_reset(struct omap_mpuio_s *s)
+{
+ s->inputs = 0;
+ s->outputs = 0;
+ s->dir = ~0;
+ s->event = 0;
+ s->edge = 0;
+ s->kbd_mask = 0;
+ s->mask = 0;
+ s->debounce = 0;
+ s->latch = 0;
+ s->ints = 0;
+ s->row_latch = 0x1f;
+ s->clk = 1;
+}
+
+static void omap_mpuio_onoff(void *opaque, int line, int on)
+{
+ struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque;
+
+ s->clk = on;
+ if (on)
+ omap_mpuio_kbd_update(s);
+}
+
+static struct omap_mpuio_s *omap_mpuio_init(MemoryRegion *memory,
+ hwaddr base,
+ qemu_irq kbd_int, qemu_irq gpio_int, qemu_irq wakeup,
+ omap_clk clk)
+{
+ struct omap_mpuio_s *s = (struct omap_mpuio_s *)
+ g_malloc0(sizeof(struct omap_mpuio_s));
+
+ s->irq = gpio_int;
+ s->kbd_irq = kbd_int;
+ s->wakeup = wakeup;
+ s->in = qemu_allocate_irqs(omap_mpuio_set, s, 16);
+ omap_mpuio_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_mpuio_ops, s,
+ "omap-mpuio", 0x800);
+ memory_region_add_subregion(memory, base, &s->iomem);
+
+ omap_clk_adduser(clk, qemu_allocate_irq(omap_mpuio_onoff, s, 0));
+
+ return s;
+}
+
+qemu_irq *omap_mpuio_in_get(struct omap_mpuio_s *s)
+{
+ return s->in;
+}
+
+void omap_mpuio_out_set(struct omap_mpuio_s *s, int line, qemu_irq handler)
+{
+ if (line >= 16 || line < 0)
+ hw_error("%s: No GPIO line %i\n", __FUNCTION__, line);
+ s->handler[line] = handler;
+}
+
+void omap_mpuio_key(struct omap_mpuio_s *s, int row, int col, int down)
+{
+ if (row >= 5 || row < 0)
+ hw_error("%s: No key %i-%i\n", __FUNCTION__, col, row);
+
+ if (down)
+ s->buttons[row] |= 1 << col;
+ else
+ s->buttons[row] &= ~(1 << col);
+
+ omap_mpuio_kbd_update(s);
+}
+
+/* MicroWire Interface */
+struct omap_uwire_s {
+ MemoryRegion iomem;
+ qemu_irq txirq;
+ qemu_irq rxirq;
+ qemu_irq txdrq;
+
+ uint16_t txbuf;
+ uint16_t rxbuf;
+ uint16_t control;
+ uint16_t setup[5];
+
+ uWireSlave *chip[4];
+};
+
+static void omap_uwire_transfer_start(struct omap_uwire_s *s)
+{
+ int chipselect = (s->control >> 10) & 3; /* INDEX */
+ uWireSlave *slave = s->chip[chipselect];
+
+ if ((s->control >> 5) & 0x1f) { /* NB_BITS_WR */
+ if (s->control & (1 << 12)) /* CS_CMD */
+ if (slave && slave->send)
+ slave->send(slave->opaque,
+ s->txbuf >> (16 - ((s->control >> 5) & 0x1f)));
+ s->control &= ~(1 << 14); /* CSRB */
+ /* TODO: depending on s->setup[4] bits [1:0] assert an IRQ or
+ * a DRQ. When is the level IRQ supposed to be reset? */
+ }
+
+ if ((s->control >> 0) & 0x1f) { /* NB_BITS_RD */
+ if (s->control & (1 << 12)) /* CS_CMD */
+ if (slave && slave->receive)
+ s->rxbuf = slave->receive(slave->opaque);
+ s->control |= 1 << 15; /* RDRB */
+ /* TODO: depending on s->setup[4] bits [1:0] assert an IRQ or
+ * a DRQ. When is the level IRQ supposed to be reset? */
+ }
+}
+
+static uint64_t omap_uwire_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_uwire_s *s = (struct omap_uwire_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* RDR */
+ s->control &= ~(1 << 15); /* RDRB */
+ return s->rxbuf;
+
+ case 0x04: /* CSR */
+ return s->control;
+
+ case 0x08: /* SR1 */
+ return s->setup[0];
+ case 0x0c: /* SR2 */
+ return s->setup[1];
+ case 0x10: /* SR3 */
+ return s->setup[2];
+ case 0x14: /* SR4 */
+ return s->setup[3];
+ case 0x18: /* SR5 */
+ return s->setup[4];
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_uwire_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_uwire_s *s = (struct omap_uwire_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* TDR */
+ s->txbuf = value; /* TD */
+ if ((s->setup[4] & (1 << 2)) && /* AUTO_TX_EN */
+ ((s->setup[4] & (1 << 3)) || /* CS_TOGGLE_TX_EN */
+ (s->control & (1 << 12)))) { /* CS_CMD */
+ s->control |= 1 << 14; /* CSRB */
+ omap_uwire_transfer_start(s);
+ }
+ break;
+
+ case 0x04: /* CSR */
+ s->control = value & 0x1fff;
+ if (value & (1 << 13)) /* START */
+ omap_uwire_transfer_start(s);
+ break;
+
+ case 0x08: /* SR1 */
+ s->setup[0] = value & 0x003f;
+ break;
+
+ case 0x0c: /* SR2 */
+ s->setup[1] = value & 0x0fc0;
+ break;
+
+ case 0x10: /* SR3 */
+ s->setup[2] = value & 0x0003;
+ break;
+
+ case 0x14: /* SR4 */
+ s->setup[3] = value & 0x0001;
+ break;
+
+ case 0x18: /* SR5 */
+ s->setup[4] = value & 0x000f;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_uwire_ops = {
+ .read = omap_uwire_read,
+ .write = omap_uwire_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_uwire_reset(struct omap_uwire_s *s)
+{
+ s->control = 0;
+ s->setup[0] = 0;
+ s->setup[1] = 0;
+ s->setup[2] = 0;
+ s->setup[3] = 0;
+ s->setup[4] = 0;
+}
+
+static struct omap_uwire_s *omap_uwire_init(MemoryRegion *system_memory,
+ hwaddr base,
+ qemu_irq txirq, qemu_irq rxirq,
+ qemu_irq dma,
+ omap_clk clk)
+{
+ struct omap_uwire_s *s = (struct omap_uwire_s *)
+ g_malloc0(sizeof(struct omap_uwire_s));
+
+ s->txirq = txirq;
+ s->rxirq = rxirq;
+ s->txdrq = dma;
+ omap_uwire_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_uwire_ops, s, "omap-uwire", 0x800);
+ memory_region_add_subregion(system_memory, base, &s->iomem);
+
+ return s;
+}
+
+void omap_uwire_attach(struct omap_uwire_s *s,
+ uWireSlave *slave, int chipselect)
+{
+ if (chipselect < 0 || chipselect > 3) {
+ fprintf(stderr, "%s: Bad chipselect %i\n", __FUNCTION__, chipselect);
+ exit(-1);
+ }
+
+ s->chip[chipselect] = slave;
+}
+
+/* Pseudonoise Pulse-Width Light Modulator */
+struct omap_pwl_s {
+ MemoryRegion iomem;
+ uint8_t output;
+ uint8_t level;
+ uint8_t enable;
+ int clk;
+};
+
+static void omap_pwl_update(struct omap_pwl_s *s)
+{
+ int output = (s->clk && s->enable) ? s->level : 0;
+
+ if (output != s->output) {
+ s->output = output;
+ printf("%s: Backlight now at %i/256\n", __FUNCTION__, output);
+ }
+}
+
+static uint64_t omap_pwl_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_pwl_s *s = (struct omap_pwl_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 1) {
+ return omap_badwidth_read8(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* PWL_LEVEL */
+ return s->level;
+ case 0x04: /* PWL_CTRL */
+ return s->enable;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_pwl_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_pwl_s *s = (struct omap_pwl_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 1) {
+ omap_badwidth_write8(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* PWL_LEVEL */
+ s->level = value;
+ omap_pwl_update(s);
+ break;
+ case 0x04: /* PWL_CTRL */
+ s->enable = value & 1;
+ omap_pwl_update(s);
+ break;
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_pwl_ops = {
+ .read = omap_pwl_read,
+ .write = omap_pwl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_pwl_reset(struct omap_pwl_s *s)
+{
+ s->output = 0;
+ s->level = 0;
+ s->enable = 0;
+ s->clk = 1;
+ omap_pwl_update(s);
+}
+
+static void omap_pwl_clk_update(void *opaque, int line, int on)
+{
+ struct omap_pwl_s *s = (struct omap_pwl_s *) opaque;
+
+ s->clk = on;
+ omap_pwl_update(s);
+}
+
+static struct omap_pwl_s *omap_pwl_init(MemoryRegion *system_memory,
+ hwaddr base,
+ omap_clk clk)
+{
+ struct omap_pwl_s *s = g_malloc0(sizeof(*s));
+
+ omap_pwl_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_pwl_ops, s,
+ "omap-pwl", 0x800);
+ memory_region_add_subregion(system_memory, base, &s->iomem);
+
+ omap_clk_adduser(clk, qemu_allocate_irq(omap_pwl_clk_update, s, 0));
+ return s;
+}
+
+/* Pulse-Width Tone module */
+struct omap_pwt_s {
+ MemoryRegion iomem;
+ uint8_t frc;
+ uint8_t vrc;
+ uint8_t gcr;
+ omap_clk clk;
+};
+
+static uint64_t omap_pwt_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_pwt_s *s = (struct omap_pwt_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 1) {
+ return omap_badwidth_read8(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* FRC */
+ return s->frc;
+ case 0x04: /* VCR */
+ return s->vrc;
+ case 0x08: /* GCR */
+ return s->gcr;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_pwt_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_pwt_s *s = (struct omap_pwt_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 1) {
+ omap_badwidth_write8(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* FRC */
+ s->frc = value & 0x3f;
+ break;
+ case 0x04: /* VRC */
+ if ((value ^ s->vrc) & 1) {
+ if (value & 1)
+ printf("%s: %iHz buzz on\n", __FUNCTION__, (int)
+ /* 1.5 MHz from a 12-MHz or 13-MHz PWT_CLK */
+ ((omap_clk_getrate(s->clk) >> 3) /
+ /* Pre-multiplexer divider */
+ ((s->gcr & 2) ? 1 : 154) /
+ /* Octave multiplexer */
+ (2 << (value & 3)) *
+ /* 101/107 divider */
+ ((value & (1 << 2)) ? 101 : 107) *
+ /* 49/55 divider */
+ ((value & (1 << 3)) ? 49 : 55) *
+ /* 50/63 divider */
+ ((value & (1 << 4)) ? 50 : 63) *
+ /* 80/127 divider */
+ ((value & (1 << 5)) ? 80 : 127) /
+ (107 * 55 * 63 * 127)));
+ else
+ printf("%s: silence!\n", __FUNCTION__);
+ }
+ s->vrc = value & 0x7f;
+ break;
+ case 0x08: /* GCR */
+ s->gcr = value & 3;
+ break;
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_pwt_ops = {
+ .read =omap_pwt_read,
+ .write = omap_pwt_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_pwt_reset(struct omap_pwt_s *s)
+{
+ s->frc = 0;
+ s->vrc = 0;
+ s->gcr = 0;
+}
+
+static struct omap_pwt_s *omap_pwt_init(MemoryRegion *system_memory,
+ hwaddr base,
+ omap_clk clk)
+{
+ struct omap_pwt_s *s = g_malloc0(sizeof(*s));
+ s->clk = clk;
+ omap_pwt_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_pwt_ops, s,
+ "omap-pwt", 0x800);
+ memory_region_add_subregion(system_memory, base, &s->iomem);
+ return s;
+}
+
+/* Real-time Clock module */
+struct omap_rtc_s {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq alarm;
+ QEMUTimer *clk;
+
+ uint8_t interrupts;
+ uint8_t status;
+ int16_t comp_reg;
+ int running;
+ int pm_am;
+ int auto_comp;
+ int round;
+ struct tm alarm_tm;
+ time_t alarm_ti;
+
+ struct tm current_tm;
+ time_t ti;
+ uint64_t tick;
+};
+
+static void omap_rtc_interrupts_update(struct omap_rtc_s *s)
+{
+ /* s->alarm is level-triggered */
+ qemu_set_irq(s->alarm, (s->status >> 6) & 1);
+}
+
+static void omap_rtc_alarm_update(struct omap_rtc_s *s)
+{
+ s->alarm_ti = mktimegm(&s->alarm_tm);
+ if (s->alarm_ti == -1)
+ printf("%s: conversion failed\n", __FUNCTION__);
+}
+
+static uint64_t omap_rtc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_rtc_s *s = (struct omap_rtc_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ uint8_t i;
+
+ if (size != 1) {
+ return omap_badwidth_read8(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* SECONDS_REG */
+ return to_bcd(s->current_tm.tm_sec);
+
+ case 0x04: /* MINUTES_REG */
+ return to_bcd(s->current_tm.tm_min);
+
+ case 0x08: /* HOURS_REG */
+ if (s->pm_am)
+ return ((s->current_tm.tm_hour > 11) << 7) |
+ to_bcd(((s->current_tm.tm_hour - 1) % 12) + 1);
+ else
+ return to_bcd(s->current_tm.tm_hour);
+
+ case 0x0c: /* DAYS_REG */
+ return to_bcd(s->current_tm.tm_mday);
+
+ case 0x10: /* MONTHS_REG */
+ return to_bcd(s->current_tm.tm_mon + 1);
+
+ case 0x14: /* YEARS_REG */
+ return to_bcd(s->current_tm.tm_year % 100);
+
+ case 0x18: /* WEEK_REG */
+ return s->current_tm.tm_wday;
+
+ case 0x20: /* ALARM_SECONDS_REG */
+ return to_bcd(s->alarm_tm.tm_sec);
+
+ case 0x24: /* ALARM_MINUTES_REG */
+ return to_bcd(s->alarm_tm.tm_min);
+
+ case 0x28: /* ALARM_HOURS_REG */
+ if (s->pm_am)
+ return ((s->alarm_tm.tm_hour > 11) << 7) |
+ to_bcd(((s->alarm_tm.tm_hour - 1) % 12) + 1);
+ else
+ return to_bcd(s->alarm_tm.tm_hour);
+
+ case 0x2c: /* ALARM_DAYS_REG */
+ return to_bcd(s->alarm_tm.tm_mday);
+
+ case 0x30: /* ALARM_MONTHS_REG */
+ return to_bcd(s->alarm_tm.tm_mon + 1);
+
+ case 0x34: /* ALARM_YEARS_REG */
+ return to_bcd(s->alarm_tm.tm_year % 100);
+
+ case 0x40: /* RTC_CTRL_REG */
+ return (s->pm_am << 3) | (s->auto_comp << 2) |
+ (s->round << 1) | s->running;
+
+ case 0x44: /* RTC_STATUS_REG */
+ i = s->status;
+ s->status &= ~0x3d;
+ return i;
+
+ case 0x48: /* RTC_INTERRUPTS_REG */
+ return s->interrupts;
+
+ case 0x4c: /* RTC_COMP_LSB_REG */
+ return ((uint16_t) s->comp_reg) & 0xff;
+
+ case 0x50: /* RTC_COMP_MSB_REG */
+ return ((uint16_t) s->comp_reg) >> 8;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_rtc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_rtc_s *s = (struct omap_rtc_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ struct tm new_tm;
+ time_t ti[2];
+
+ if (size != 1) {
+ omap_badwidth_write8(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* SECONDS_REG */
+#ifdef ALMDEBUG
+ printf("RTC SEC_REG <-- %02x\n", value);
+#endif
+ s->ti -= s->current_tm.tm_sec;
+ s->ti += from_bcd(value);
+ return;
+
+ case 0x04: /* MINUTES_REG */
+#ifdef ALMDEBUG
+ printf("RTC MIN_REG <-- %02x\n", value);
+#endif
+ s->ti -= s->current_tm.tm_min * 60;
+ s->ti += from_bcd(value) * 60;
+ return;
+
+ case 0x08: /* HOURS_REG */
+#ifdef ALMDEBUG
+ printf("RTC HRS_REG <-- %02x\n", value);
+#endif
+ s->ti -= s->current_tm.tm_hour * 3600;
+ if (s->pm_am) {
+ s->ti += (from_bcd(value & 0x3f) & 12) * 3600;
+ s->ti += ((value >> 7) & 1) * 43200;
+ } else
+ s->ti += from_bcd(value & 0x3f) * 3600;
+ return;
+
+ case 0x0c: /* DAYS_REG */
+#ifdef ALMDEBUG
+ printf("RTC DAY_REG <-- %02x\n", value);
+#endif
+ s->ti -= s->current_tm.tm_mday * 86400;
+ s->ti += from_bcd(value) * 86400;
+ return;
+
+ case 0x10: /* MONTHS_REG */
+#ifdef ALMDEBUG
+ printf("RTC MTH_REG <-- %02x\n", value);
+#endif
+ memcpy(&new_tm, &s->current_tm, sizeof(new_tm));
+ new_tm.tm_mon = from_bcd(value);
+ ti[0] = mktimegm(&s->current_tm);
+ ti[1] = mktimegm(&new_tm);
+
+ if (ti[0] != -1 && ti[1] != -1) {
+ s->ti -= ti[0];
+ s->ti += ti[1];
+ } else {
+ /* A less accurate version */
+ s->ti -= s->current_tm.tm_mon * 2592000;
+ s->ti += from_bcd(value) * 2592000;
+ }
+ return;
+
+ case 0x14: /* YEARS_REG */
+#ifdef ALMDEBUG
+ printf("RTC YRS_REG <-- %02x\n", value);
+#endif
+ memcpy(&new_tm, &s->current_tm, sizeof(new_tm));
+ new_tm.tm_year += from_bcd(value) - (new_tm.tm_year % 100);
+ ti[0] = mktimegm(&s->current_tm);
+ ti[1] = mktimegm(&new_tm);
+
+ if (ti[0] != -1 && ti[1] != -1) {
+ s->ti -= ti[0];
+ s->ti += ti[1];
+ } else {
+ /* A less accurate version */
+ s->ti -= (time_t)(s->current_tm.tm_year % 100) * 31536000;
+ s->ti += (time_t)from_bcd(value) * 31536000;
+ }
+ return;
+
+ case 0x18: /* WEEK_REG */
+ return; /* Ignored */
+
+ case 0x20: /* ALARM_SECONDS_REG */
+#ifdef ALMDEBUG
+ printf("ALM SEC_REG <-- %02x\n", value);
+#endif
+ s->alarm_tm.tm_sec = from_bcd(value);
+ omap_rtc_alarm_update(s);
+ return;
+
+ case 0x24: /* ALARM_MINUTES_REG */
+#ifdef ALMDEBUG
+ printf("ALM MIN_REG <-- %02x\n", value);
+#endif
+ s->alarm_tm.tm_min = from_bcd(value);
+ omap_rtc_alarm_update(s);
+ return;
+
+ case 0x28: /* ALARM_HOURS_REG */
+#ifdef ALMDEBUG
+ printf("ALM HRS_REG <-- %02x\n", value);
+#endif
+ if (s->pm_am)
+ s->alarm_tm.tm_hour =
+ ((from_bcd(value & 0x3f)) % 12) +
+ ((value >> 7) & 1) * 12;
+ else
+ s->alarm_tm.tm_hour = from_bcd(value);
+ omap_rtc_alarm_update(s);
+ return;
+
+ case 0x2c: /* ALARM_DAYS_REG */
+#ifdef ALMDEBUG
+ printf("ALM DAY_REG <-- %02x\n", value);
+#endif
+ s->alarm_tm.tm_mday = from_bcd(value);
+ omap_rtc_alarm_update(s);
+ return;
+
+ case 0x30: /* ALARM_MONTHS_REG */
+#ifdef ALMDEBUG
+ printf("ALM MON_REG <-- %02x\n", value);
+#endif
+ s->alarm_tm.tm_mon = from_bcd(value);
+ omap_rtc_alarm_update(s);
+ return;
+
+ case 0x34: /* ALARM_YEARS_REG */
+#ifdef ALMDEBUG
+ printf("ALM YRS_REG <-- %02x\n", value);
+#endif
+ s->alarm_tm.tm_year = from_bcd(value);
+ omap_rtc_alarm_update(s);
+ return;
+
+ case 0x40: /* RTC_CTRL_REG */
+#ifdef ALMDEBUG
+ printf("RTC CONTROL <-- %02x\n", value);
+#endif
+ s->pm_am = (value >> 3) & 1;
+ s->auto_comp = (value >> 2) & 1;
+ s->round = (value >> 1) & 1;
+ s->running = value & 1;
+ s->status &= 0xfd;
+ s->status |= s->running << 1;
+ return;
+
+ case 0x44: /* RTC_STATUS_REG */
+#ifdef ALMDEBUG
+ printf("RTC STATUSL <-- %02x\n", value);
+#endif
+ s->status &= ~((value & 0xc0) ^ 0x80);
+ omap_rtc_interrupts_update(s);
+ return;
+
+ case 0x48: /* RTC_INTERRUPTS_REG */
+#ifdef ALMDEBUG
+ printf("RTC INTRS <-- %02x\n", value);
+#endif
+ s->interrupts = value;
+ return;
+
+ case 0x4c: /* RTC_COMP_LSB_REG */
+#ifdef ALMDEBUG
+ printf("RTC COMPLSB <-- %02x\n", value);
+#endif
+ s->comp_reg &= 0xff00;
+ s->comp_reg |= 0x00ff & value;
+ return;
+
+ case 0x50: /* RTC_COMP_MSB_REG */
+#ifdef ALMDEBUG
+ printf("RTC COMPMSB <-- %02x\n", value);
+#endif
+ s->comp_reg &= 0x00ff;
+ s->comp_reg |= 0xff00 & (value << 8);
+ return;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_rtc_ops = {
+ .read = omap_rtc_read,
+ .write = omap_rtc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_rtc_tick(void *opaque)
+{
+ struct omap_rtc_s *s = opaque;
+
+ if (s->round) {
+ /* Round to nearest full minute. */
+ if (s->current_tm.tm_sec < 30)
+ s->ti -= s->current_tm.tm_sec;
+ else
+ s->ti += 60 - s->current_tm.tm_sec;
+
+ s->round = 0;
+ }
+
+ localtime_r(&s->ti, &s->current_tm);
+
+ if ((s->interrupts & 0x08) && s->ti == s->alarm_ti) {
+ s->status |= 0x40;
+ omap_rtc_interrupts_update(s);
+ }
+
+ if (s->interrupts & 0x04)
+ switch (s->interrupts & 3) {
+ case 0:
+ s->status |= 0x04;
+ qemu_irq_pulse(s->irq);
+ break;
+ case 1:
+ if (s->current_tm.tm_sec)
+ break;
+ s->status |= 0x08;
+ qemu_irq_pulse(s->irq);
+ break;
+ case 2:
+ if (s->current_tm.tm_sec || s->current_tm.tm_min)
+ break;
+ s->status |= 0x10;
+ qemu_irq_pulse(s->irq);
+ break;
+ case 3:
+ if (s->current_tm.tm_sec ||
+ s->current_tm.tm_min || s->current_tm.tm_hour)
+ break;
+ s->status |= 0x20;
+ qemu_irq_pulse(s->irq);
+ break;
+ }
+
+ /* Move on */
+ if (s->running)
+ s->ti ++;
+ s->tick += 1000;
+
+ /*
+ * Every full hour add a rough approximation of the compensation
+ * register to the 32kHz Timer (which drives the RTC) value.
+ */
+ if (s->auto_comp && !s->current_tm.tm_sec && !s->current_tm.tm_min)
+ s->tick += s->comp_reg * 1000 / 32768;
+
+ timer_mod(s->clk, s->tick);
+}
+
+static void omap_rtc_reset(struct omap_rtc_s *s)
+{
+ struct tm tm;
+
+ s->interrupts = 0;
+ s->comp_reg = 0;
+ s->running = 0;
+ s->pm_am = 0;
+ s->auto_comp = 0;
+ s->round = 0;
+ s->tick = qemu_clock_get_ms(rtc_clock);
+ memset(&s->alarm_tm, 0, sizeof(s->alarm_tm));
+ s->alarm_tm.tm_mday = 0x01;
+ s->status = 1 << 7;
+ qemu_get_timedate(&tm, 0);
+ s->ti = mktimegm(&tm);
+
+ omap_rtc_alarm_update(s);
+ omap_rtc_tick(s);
+}
+
+static struct omap_rtc_s *omap_rtc_init(MemoryRegion *system_memory,
+ hwaddr base,
+ qemu_irq timerirq, qemu_irq alarmirq,
+ omap_clk clk)
+{
+ struct omap_rtc_s *s = (struct omap_rtc_s *)
+ g_malloc0(sizeof(struct omap_rtc_s));
+
+ s->irq = timerirq;
+ s->alarm = alarmirq;
+ s->clk = timer_new_ms(rtc_clock, omap_rtc_tick, s);
+
+ omap_rtc_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_rtc_ops, s,
+ "omap-rtc", 0x800);
+ memory_region_add_subregion(system_memory, base, &s->iomem);
+
+ return s;
+}
+
+/* Multi-channel Buffered Serial Port interfaces */
+struct omap_mcbsp_s {
+ MemoryRegion iomem;
+ qemu_irq txirq;
+ qemu_irq rxirq;
+ qemu_irq txdrq;
+ qemu_irq rxdrq;
+
+ uint16_t spcr[2];
+ uint16_t rcr[2];
+ uint16_t xcr[2];
+ uint16_t srgr[2];
+ uint16_t mcr[2];
+ uint16_t pcr;
+ uint16_t rcer[8];
+ uint16_t xcer[8];
+ int tx_rate;
+ int rx_rate;
+ int tx_req;
+ int rx_req;
+
+ I2SCodec *codec;
+ QEMUTimer *source_timer;
+ QEMUTimer *sink_timer;
+};
+
+static void omap_mcbsp_intr_update(struct omap_mcbsp_s *s)
+{
+ int irq;
+
+ switch ((s->spcr[0] >> 4) & 3) { /* RINTM */
+ case 0:
+ irq = (s->spcr[0] >> 1) & 1; /* RRDY */
+ break;
+ case 3:
+ irq = (s->spcr[0] >> 3) & 1; /* RSYNCERR */
+ break;
+ default:
+ irq = 0;
+ break;
+ }
+
+ if (irq)
+ qemu_irq_pulse(s->rxirq);
+
+ switch ((s->spcr[1] >> 4) & 3) { /* XINTM */
+ case 0:
+ irq = (s->spcr[1] >> 1) & 1; /* XRDY */
+ break;
+ case 3:
+ irq = (s->spcr[1] >> 3) & 1; /* XSYNCERR */
+ break;
+ default:
+ irq = 0;
+ break;
+ }
+
+ if (irq)
+ qemu_irq_pulse(s->txirq);
+}
+
+static void omap_mcbsp_rx_newdata(struct omap_mcbsp_s *s)
+{
+ if ((s->spcr[0] >> 1) & 1) /* RRDY */
+ s->spcr[0] |= 1 << 2; /* RFULL */
+ s->spcr[0] |= 1 << 1; /* RRDY */
+ qemu_irq_raise(s->rxdrq);
+ omap_mcbsp_intr_update(s);
+}
+
+static void omap_mcbsp_source_tick(void *opaque)
+{
+ struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque;
+ static const int bps[8] = { 0, 1, 1, 2, 2, 2, -255, -255 };
+
+ if (!s->rx_rate)
+ return;
+ if (s->rx_req)
+ printf("%s: Rx FIFO overrun\n", __FUNCTION__);
+
+ s->rx_req = s->rx_rate << bps[(s->rcr[0] >> 5) & 7];
+
+ omap_mcbsp_rx_newdata(s);
+ timer_mod(s->source_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ get_ticks_per_sec());
+}
+
+static void omap_mcbsp_rx_start(struct omap_mcbsp_s *s)
+{
+ if (!s->codec || !s->codec->rts)
+ omap_mcbsp_source_tick(s);
+ else if (s->codec->in.len) {
+ s->rx_req = s->codec->in.len;
+ omap_mcbsp_rx_newdata(s);
+ }
+}
+
+static void omap_mcbsp_rx_stop(struct omap_mcbsp_s *s)
+{
+ timer_del(s->source_timer);
+}
+
+static void omap_mcbsp_rx_done(struct omap_mcbsp_s *s)
+{
+ s->spcr[0] &= ~(1 << 1); /* RRDY */
+ qemu_irq_lower(s->rxdrq);
+ omap_mcbsp_intr_update(s);
+}
+
+static void omap_mcbsp_tx_newdata(struct omap_mcbsp_s *s)
+{
+ s->spcr[1] |= 1 << 1; /* XRDY */
+ qemu_irq_raise(s->txdrq);
+ omap_mcbsp_intr_update(s);
+}
+
+static void omap_mcbsp_sink_tick(void *opaque)
+{
+ struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque;
+ static const int bps[8] = { 0, 1, 1, 2, 2, 2, -255, -255 };
+
+ if (!s->tx_rate)
+ return;
+ if (s->tx_req)
+ printf("%s: Tx FIFO underrun\n", __FUNCTION__);
+
+ s->tx_req = s->tx_rate << bps[(s->xcr[0] >> 5) & 7];
+
+ omap_mcbsp_tx_newdata(s);
+ timer_mod(s->sink_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ get_ticks_per_sec());
+}
+
+static void omap_mcbsp_tx_start(struct omap_mcbsp_s *s)
+{
+ if (!s->codec || !s->codec->cts)
+ omap_mcbsp_sink_tick(s);
+ else if (s->codec->out.size) {
+ s->tx_req = s->codec->out.size;
+ omap_mcbsp_tx_newdata(s);
+ }
+}
+
+static void omap_mcbsp_tx_done(struct omap_mcbsp_s *s)
+{
+ s->spcr[1] &= ~(1 << 1); /* XRDY */
+ qemu_irq_lower(s->txdrq);
+ omap_mcbsp_intr_update(s);
+ if (s->codec && s->codec->cts)
+ s->codec->tx_swallow(s->codec->opaque);
+}
+
+static void omap_mcbsp_tx_stop(struct omap_mcbsp_s *s)
+{
+ s->tx_req = 0;
+ omap_mcbsp_tx_done(s);
+ timer_del(s->sink_timer);
+}
+
+static void omap_mcbsp_req_update(struct omap_mcbsp_s *s)
+{
+ int prev_rx_rate, prev_tx_rate;
+ int rx_rate = 0, tx_rate = 0;
+ int cpu_rate = 1500000; /* XXX */
+
+ /* TODO: check CLKSTP bit */
+ if (s->spcr[1] & (1 << 6)) { /* GRST */
+ if (s->spcr[0] & (1 << 0)) { /* RRST */
+ if ((s->srgr[1] & (1 << 13)) && /* CLKSM */
+ (s->pcr & (1 << 8))) { /* CLKRM */
+ if (~s->pcr & (1 << 7)) /* SCLKME */
+ rx_rate = cpu_rate /
+ ((s->srgr[0] & 0xff) + 1); /* CLKGDV */
+ } else
+ if (s->codec)
+ rx_rate = s->codec->rx_rate;
+ }
+
+ if (s->spcr[1] & (1 << 0)) { /* XRST */
+ if ((s->srgr[1] & (1 << 13)) && /* CLKSM */
+ (s->pcr & (1 << 9))) { /* CLKXM */
+ if (~s->pcr & (1 << 7)) /* SCLKME */
+ tx_rate = cpu_rate /
+ ((s->srgr[0] & 0xff) + 1); /* CLKGDV */
+ } else
+ if (s->codec)
+ tx_rate = s->codec->tx_rate;
+ }
+ }
+ prev_tx_rate = s->tx_rate;
+ prev_rx_rate = s->rx_rate;
+ s->tx_rate = tx_rate;
+ s->rx_rate = rx_rate;
+
+ if (s->codec)
+ s->codec->set_rate(s->codec->opaque, rx_rate, tx_rate);
+
+ if (!prev_tx_rate && tx_rate)
+ omap_mcbsp_tx_start(s);
+ else if (s->tx_rate && !tx_rate)
+ omap_mcbsp_tx_stop(s);
+
+ if (!prev_rx_rate && rx_rate)
+ omap_mcbsp_rx_start(s);
+ else if (prev_tx_rate && !tx_rate)
+ omap_mcbsp_rx_stop(s);
+}
+
+static uint64_t omap_mcbsp_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ uint16_t ret;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* DRR2 */
+ if (((s->rcr[0] >> 5) & 7) < 3) /* RWDLEN1 */
+ return 0x0000;
+ /* Fall through. */
+ case 0x02: /* DRR1 */
+ if (s->rx_req < 2) {
+ printf("%s: Rx FIFO underrun\n", __FUNCTION__);
+ omap_mcbsp_rx_done(s);
+ } else {
+ s->tx_req -= 2;
+ if (s->codec && s->codec->in.len >= 2) {
+ ret = s->codec->in.fifo[s->codec->in.start ++] << 8;
+ ret |= s->codec->in.fifo[s->codec->in.start ++];
+ s->codec->in.len -= 2;
+ } else
+ ret = 0x0000;
+ if (!s->tx_req)
+ omap_mcbsp_rx_done(s);
+ return ret;
+ }
+ return 0x0000;
+
+ case 0x04: /* DXR2 */
+ case 0x06: /* DXR1 */
+ return 0x0000;
+
+ case 0x08: /* SPCR2 */
+ return s->spcr[1];
+ case 0x0a: /* SPCR1 */
+ return s->spcr[0];
+ case 0x0c: /* RCR2 */
+ return s->rcr[1];
+ case 0x0e: /* RCR1 */
+ return s->rcr[0];
+ case 0x10: /* XCR2 */
+ return s->xcr[1];
+ case 0x12: /* XCR1 */
+ return s->xcr[0];
+ case 0x14: /* SRGR2 */
+ return s->srgr[1];
+ case 0x16: /* SRGR1 */
+ return s->srgr[0];
+ case 0x18: /* MCR2 */
+ return s->mcr[1];
+ case 0x1a: /* MCR1 */
+ return s->mcr[0];
+ case 0x1c: /* RCERA */
+ return s->rcer[0];
+ case 0x1e: /* RCERB */
+ return s->rcer[1];
+ case 0x20: /* XCERA */
+ return s->xcer[0];
+ case 0x22: /* XCERB */
+ return s->xcer[1];
+ case 0x24: /* PCR0 */
+ return s->pcr;
+ case 0x26: /* RCERC */
+ return s->rcer[2];
+ case 0x28: /* RCERD */
+ return s->rcer[3];
+ case 0x2a: /* XCERC */
+ return s->xcer[2];
+ case 0x2c: /* XCERD */
+ return s->xcer[3];
+ case 0x2e: /* RCERE */
+ return s->rcer[4];
+ case 0x30: /* RCERF */
+ return s->rcer[5];
+ case 0x32: /* XCERE */
+ return s->xcer[4];
+ case 0x34: /* XCERF */
+ return s->xcer[5];
+ case 0x36: /* RCERG */
+ return s->rcer[6];
+ case 0x38: /* RCERH */
+ return s->rcer[7];
+ case 0x3a: /* XCERG */
+ return s->xcer[6];
+ case 0x3c: /* XCERH */
+ return s->xcer[7];
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_mcbsp_writeh(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ switch (offset) {
+ case 0x00: /* DRR2 */
+ case 0x02: /* DRR1 */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x04: /* DXR2 */
+ if (((s->xcr[0] >> 5) & 7) < 3) /* XWDLEN1 */
+ return;
+ /* Fall through. */
+ case 0x06: /* DXR1 */
+ if (s->tx_req > 1) {
+ s->tx_req -= 2;
+ if (s->codec && s->codec->cts) {
+ s->codec->out.fifo[s->codec->out.len ++] = (value >> 8) & 0xff;
+ s->codec->out.fifo[s->codec->out.len ++] = (value >> 0) & 0xff;
+ }
+ if (s->tx_req < 2)
+ omap_mcbsp_tx_done(s);
+ } else
+ printf("%s: Tx FIFO overrun\n", __FUNCTION__);
+ return;
+
+ case 0x08: /* SPCR2 */
+ s->spcr[1] &= 0x0002;
+ s->spcr[1] |= 0x03f9 & value;
+ s->spcr[1] |= 0x0004 & (value << 2); /* XEMPTY := XRST */
+ if (~value & 1) /* XRST */
+ s->spcr[1] &= ~6;
+ omap_mcbsp_req_update(s);
+ return;
+ case 0x0a: /* SPCR1 */
+ s->spcr[0] &= 0x0006;
+ s->spcr[0] |= 0xf8f9 & value;
+ if (value & (1 << 15)) /* DLB */
+ printf("%s: Digital Loopback mode enable attempt\n", __FUNCTION__);
+ if (~value & 1) { /* RRST */
+ s->spcr[0] &= ~6;
+ s->rx_req = 0;
+ omap_mcbsp_rx_done(s);
+ }
+ omap_mcbsp_req_update(s);
+ return;
+
+ case 0x0c: /* RCR2 */
+ s->rcr[1] = value & 0xffff;
+ return;
+ case 0x0e: /* RCR1 */
+ s->rcr[0] = value & 0x7fe0;
+ return;
+ case 0x10: /* XCR2 */
+ s->xcr[1] = value & 0xffff;
+ return;
+ case 0x12: /* XCR1 */
+ s->xcr[0] = value & 0x7fe0;
+ return;
+ case 0x14: /* SRGR2 */
+ s->srgr[1] = value & 0xffff;
+ omap_mcbsp_req_update(s);
+ return;
+ case 0x16: /* SRGR1 */
+ s->srgr[0] = value & 0xffff;
+ omap_mcbsp_req_update(s);
+ return;
+ case 0x18: /* MCR2 */
+ s->mcr[1] = value & 0x03e3;
+ if (value & 3) /* XMCM */
+ printf("%s: Tx channel selection mode enable attempt\n",
+ __FUNCTION__);
+ return;
+ case 0x1a: /* MCR1 */
+ s->mcr[0] = value & 0x03e1;
+ if (value & 1) /* RMCM */
+ printf("%s: Rx channel selection mode enable attempt\n",
+ __FUNCTION__);
+ return;
+ case 0x1c: /* RCERA */
+ s->rcer[0] = value & 0xffff;
+ return;
+ case 0x1e: /* RCERB */
+ s->rcer[1] = value & 0xffff;
+ return;
+ case 0x20: /* XCERA */
+ s->xcer[0] = value & 0xffff;
+ return;
+ case 0x22: /* XCERB */
+ s->xcer[1] = value & 0xffff;
+ return;
+ case 0x24: /* PCR0 */
+ s->pcr = value & 0x7faf;
+ return;
+ case 0x26: /* RCERC */
+ s->rcer[2] = value & 0xffff;
+ return;
+ case 0x28: /* RCERD */
+ s->rcer[3] = value & 0xffff;
+ return;
+ case 0x2a: /* XCERC */
+ s->xcer[2] = value & 0xffff;
+ return;
+ case 0x2c: /* XCERD */
+ s->xcer[3] = value & 0xffff;
+ return;
+ case 0x2e: /* RCERE */
+ s->rcer[4] = value & 0xffff;
+ return;
+ case 0x30: /* RCERF */
+ s->rcer[5] = value & 0xffff;
+ return;
+ case 0x32: /* XCERE */
+ s->xcer[4] = value & 0xffff;
+ return;
+ case 0x34: /* XCERF */
+ s->xcer[5] = value & 0xffff;
+ return;
+ case 0x36: /* RCERG */
+ s->rcer[6] = value & 0xffff;
+ return;
+ case 0x38: /* RCERH */
+ s->rcer[7] = value & 0xffff;
+ return;
+ case 0x3a: /* XCERG */
+ s->xcer[6] = value & 0xffff;
+ return;
+ case 0x3c: /* XCERH */
+ s->xcer[7] = value & 0xffff;
+ return;
+ }
+
+ OMAP_BAD_REG(addr);
+}
+
+static void omap_mcbsp_writew(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (offset == 0x04) { /* DXR */
+ if (((s->xcr[0] >> 5) & 7) < 3) /* XWDLEN1 */
+ return;
+ if (s->tx_req > 3) {
+ s->tx_req -= 4;
+ if (s->codec && s->codec->cts) {
+ s->codec->out.fifo[s->codec->out.len ++] =
+ (value >> 24) & 0xff;
+ s->codec->out.fifo[s->codec->out.len ++] =
+ (value >> 16) & 0xff;
+ s->codec->out.fifo[s->codec->out.len ++] =
+ (value >> 8) & 0xff;
+ s->codec->out.fifo[s->codec->out.len ++] =
+ (value >> 0) & 0xff;
+ }
+ if (s->tx_req < 4)
+ omap_mcbsp_tx_done(s);
+ } else
+ printf("%s: Tx FIFO overrun\n", __FUNCTION__);
+ return;
+ }
+
+ omap_badwidth_write16(opaque, addr, value);
+}
+
+static void omap_mcbsp_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ switch (size) {
+ case 2:
+ omap_mcbsp_writeh(opaque, addr, value);
+ break;
+ case 4:
+ omap_mcbsp_writew(opaque, addr, value);
+ break;
+ default:
+ omap_badwidth_write16(opaque, addr, value);
+ }
+}
+
+static const MemoryRegionOps omap_mcbsp_ops = {
+ .read = omap_mcbsp_read,
+ .write = omap_mcbsp_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_mcbsp_reset(struct omap_mcbsp_s *s)
+{
+ memset(&s->spcr, 0, sizeof(s->spcr));
+ memset(&s->rcr, 0, sizeof(s->rcr));
+ memset(&s->xcr, 0, sizeof(s->xcr));
+ s->srgr[0] = 0x0001;
+ s->srgr[1] = 0x2000;
+ memset(&s->mcr, 0, sizeof(s->mcr));
+ memset(&s->pcr, 0, sizeof(s->pcr));
+ memset(&s->rcer, 0, sizeof(s->rcer));
+ memset(&s->xcer, 0, sizeof(s->xcer));
+ s->tx_req = 0;
+ s->rx_req = 0;
+ s->tx_rate = 0;
+ s->rx_rate = 0;
+ timer_del(s->source_timer);
+ timer_del(s->sink_timer);
+}
+
+static struct omap_mcbsp_s *omap_mcbsp_init(MemoryRegion *system_memory,
+ hwaddr base,
+ qemu_irq txirq, qemu_irq rxirq,
+ qemu_irq *dma, omap_clk clk)
+{
+ struct omap_mcbsp_s *s = (struct omap_mcbsp_s *)
+ g_malloc0(sizeof(struct omap_mcbsp_s));
+
+ s->txirq = txirq;
+ s->rxirq = rxirq;
+ s->txdrq = dma[0];
+ s->rxdrq = dma[1];
+ s->sink_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_mcbsp_sink_tick, s);
+ s->source_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_mcbsp_source_tick, s);
+ omap_mcbsp_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_mcbsp_ops, s, "omap-mcbsp", 0x800);
+ memory_region_add_subregion(system_memory, base, &s->iomem);
+
+ return s;
+}
+
+static void omap_mcbsp_i2s_swallow(void *opaque, int line, int level)
+{
+ struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque;
+
+ if (s->rx_rate) {
+ s->rx_req = s->codec->in.len;
+ omap_mcbsp_rx_newdata(s);
+ }
+}
+
+static void omap_mcbsp_i2s_start(void *opaque, int line, int level)
+{
+ struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque;
+
+ if (s->tx_rate) {
+ s->tx_req = s->codec->out.size;
+ omap_mcbsp_tx_newdata(s);
+ }
+}
+
+void omap_mcbsp_i2s_attach(struct omap_mcbsp_s *s, I2SCodec *slave)
+{
+ s->codec = slave;
+ slave->rx_swallow = qemu_allocate_irq(omap_mcbsp_i2s_swallow, s, 0);
+ slave->tx_start = qemu_allocate_irq(omap_mcbsp_i2s_start, s, 0);
+}
+
+/* LED Pulse Generators */
+struct omap_lpg_s {
+ MemoryRegion iomem;
+ QEMUTimer *tm;
+
+ uint8_t control;
+ uint8_t power;
+ int64_t on;
+ int64_t period;
+ int clk;
+ int cycle;
+};
+
+static void omap_lpg_tick(void *opaque)
+{
+ struct omap_lpg_s *s = opaque;
+
+ if (s->cycle)
+ timer_mod(s->tm, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->period - s->on);
+ else
+ timer_mod(s->tm, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->on);
+
+ s->cycle = !s->cycle;
+ printf("%s: LED is %s\n", __FUNCTION__, s->cycle ? "on" : "off");
+}
+
+static void omap_lpg_update(struct omap_lpg_s *s)
+{
+ int64_t on, period = 1, ticks = 1000;
+ static const int per[8] = { 1, 2, 4, 8, 12, 16, 20, 24 };
+
+ if (~s->control & (1 << 6)) /* LPGRES */
+ on = 0;
+ else if (s->control & (1 << 7)) /* PERM_ON */
+ on = period;
+ else {
+ period = muldiv64(ticks, per[s->control & 7], /* PERCTRL */
+ 256 / 32);
+ on = (s->clk && s->power) ? muldiv64(ticks,
+ per[(s->control >> 3) & 7], 256) : 0; /* ONCTRL */
+ }
+
+ timer_del(s->tm);
+ if (on == period && s->on < s->period)
+ printf("%s: LED is on\n", __FUNCTION__);
+ else if (on == 0 && s->on)
+ printf("%s: LED is off\n", __FUNCTION__);
+ else if (on && (on != s->on || period != s->period)) {
+ s->cycle = 0;
+ s->on = on;
+ s->period = period;
+ omap_lpg_tick(s);
+ return;
+ }
+
+ s->on = on;
+ s->period = period;
+}
+
+static void omap_lpg_reset(struct omap_lpg_s *s)
+{
+ s->control = 0x00;
+ s->power = 0x00;
+ s->clk = 1;
+ omap_lpg_update(s);
+}
+
+static uint64_t omap_lpg_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_lpg_s *s = (struct omap_lpg_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 1) {
+ return omap_badwidth_read8(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* LCR */
+ return s->control;
+
+ case 0x04: /* PMR */
+ return s->power;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_lpg_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_lpg_s *s = (struct omap_lpg_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 1) {
+ omap_badwidth_write8(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* LCR */
+ if (~value & (1 << 6)) /* LPGRES */
+ omap_lpg_reset(s);
+ s->control = value & 0xff;
+ omap_lpg_update(s);
+ return;
+
+ case 0x04: /* PMR */
+ s->power = value & 0x01;
+ omap_lpg_update(s);
+ return;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_lpg_ops = {
+ .read = omap_lpg_read,
+ .write = omap_lpg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_lpg_clk_update(void *opaque, int line, int on)
+{
+ struct omap_lpg_s *s = (struct omap_lpg_s *) opaque;
+
+ s->clk = on;
+ omap_lpg_update(s);
+}
+
+static struct omap_lpg_s *omap_lpg_init(MemoryRegion *system_memory,
+ hwaddr base, omap_clk clk)
+{
+ struct omap_lpg_s *s = (struct omap_lpg_s *)
+ g_malloc0(sizeof(struct omap_lpg_s));
+
+ s->tm = timer_new_ms(QEMU_CLOCK_VIRTUAL, omap_lpg_tick, s);
+
+ omap_lpg_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_lpg_ops, s, "omap-lpg", 0x800);
+ memory_region_add_subregion(system_memory, base, &s->iomem);
+
+ omap_clk_adduser(clk, qemu_allocate_irq(omap_lpg_clk_update, s, 0));
+
+ return s;
+}
+
+/* MPUI Peripheral Bridge configuration */
+static uint64_t omap_mpui_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ if (addr == OMAP_MPUI_BASE) /* CMR */
+ return 0xfe4d;
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_mpui_io_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ /* FIXME: infinite loop */
+ omap_badwidth_write16(opaque, addr, value);
+}
+
+static const MemoryRegionOps omap_mpui_io_ops = {
+ .read = omap_mpui_io_read,
+ .write = omap_mpui_io_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_setup_mpui_io(MemoryRegion *system_memory,
+ struct omap_mpu_state_s *mpu)
+{
+ memory_region_init_io(&mpu->mpui_io_iomem, NULL, &omap_mpui_io_ops, mpu,
+ "omap-mpui-io", 0x7fff);
+ memory_region_add_subregion(system_memory, OMAP_MPUI_BASE,
+ &mpu->mpui_io_iomem);
+}
+
+/* General chip reset */
+static void omap1_mpu_reset(void *opaque)
+{
+ struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque;
+
+ omap_dma_reset(mpu->dma);
+ omap_mpu_timer_reset(mpu->timer[0]);
+ omap_mpu_timer_reset(mpu->timer[1]);
+ omap_mpu_timer_reset(mpu->timer[2]);
+ omap_wd_timer_reset(mpu->wdt);
+ omap_os_timer_reset(mpu->os_timer);
+ omap_lcdc_reset(mpu->lcd);
+ omap_ulpd_pm_reset(mpu);
+ omap_pin_cfg_reset(mpu);
+ omap_mpui_reset(mpu);
+ omap_tipb_bridge_reset(mpu->private_tipb);
+ omap_tipb_bridge_reset(mpu->public_tipb);
+ omap_dpll_reset(mpu->dpll[0]);
+ omap_dpll_reset(mpu->dpll[1]);
+ omap_dpll_reset(mpu->dpll[2]);
+ omap_uart_reset(mpu->uart[0]);
+ omap_uart_reset(mpu->uart[1]);
+ omap_uart_reset(mpu->uart[2]);
+ omap_mmc_reset(mpu->mmc);
+ omap_mpuio_reset(mpu->mpuio);
+ omap_uwire_reset(mpu->microwire);
+ omap_pwl_reset(mpu->pwl);
+ omap_pwt_reset(mpu->pwt);
+ omap_rtc_reset(mpu->rtc);
+ omap_mcbsp_reset(mpu->mcbsp1);
+ omap_mcbsp_reset(mpu->mcbsp2);
+ omap_mcbsp_reset(mpu->mcbsp3);
+ omap_lpg_reset(mpu->led[0]);
+ omap_lpg_reset(mpu->led[1]);
+ omap_clkm_reset(mpu);
+ cpu_reset(CPU(mpu->cpu));
+}
+
+static const struct omap_map_s {
+ hwaddr phys_dsp;
+ hwaddr phys_mpu;
+ uint32_t size;
+ const char *name;
+} omap15xx_dsp_mm[] = {
+ /* Strobe 0 */
+ { 0xe1010000, 0xfffb0000, 0x800, "UART1 BT" }, /* CS0 */
+ { 0xe1010800, 0xfffb0800, 0x800, "UART2 COM" }, /* CS1 */
+ { 0xe1011800, 0xfffb1800, 0x800, "McBSP1 audio" }, /* CS3 */
+ { 0xe1012000, 0xfffb2000, 0x800, "MCSI2 communication" }, /* CS4 */
+ { 0xe1012800, 0xfffb2800, 0x800, "MCSI1 BT u-Law" }, /* CS5 */
+ { 0xe1013000, 0xfffb3000, 0x800, "uWire" }, /* CS6 */
+ { 0xe1013800, 0xfffb3800, 0x800, "I^2C" }, /* CS7 */
+ { 0xe1014000, 0xfffb4000, 0x800, "USB W2FC" }, /* CS8 */
+ { 0xe1014800, 0xfffb4800, 0x800, "RTC" }, /* CS9 */
+ { 0xe1015000, 0xfffb5000, 0x800, "MPUIO" }, /* CS10 */
+ { 0xe1015800, 0xfffb5800, 0x800, "PWL" }, /* CS11 */
+ { 0xe1016000, 0xfffb6000, 0x800, "PWT" }, /* CS12 */
+ { 0xe1017000, 0xfffb7000, 0x800, "McBSP3" }, /* CS14 */
+ { 0xe1017800, 0xfffb7800, 0x800, "MMC" }, /* CS15 */
+ { 0xe1019000, 0xfffb9000, 0x800, "32-kHz timer" }, /* CS18 */
+ { 0xe1019800, 0xfffb9800, 0x800, "UART3" }, /* CS19 */
+ { 0xe101c800, 0xfffbc800, 0x800, "TIPB switches" }, /* CS25 */
+ /* Strobe 1 */
+ { 0xe101e000, 0xfffce000, 0x800, "GPIOs" }, /* CS28 */
+
+ { 0 }
+};
+
+static void omap_setup_dsp_mapping(MemoryRegion *system_memory,
+ const struct omap_map_s *map)
+{
+ MemoryRegion *io;
+
+ for (; map->phys_dsp; map ++) {
+ io = g_new(MemoryRegion, 1);
+ memory_region_init_alias(io, NULL, map->name,
+ system_memory, map->phys_mpu, map->size);
+ memory_region_add_subregion(system_memory, map->phys_dsp, io);
+ }
+}
+
+void omap_mpu_wakeup(void *opaque, int irq, int req)
+{
+ struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque;
+ CPUState *cpu = CPU(mpu->cpu);
+
+ if (cpu->halted) {
+ cpu_interrupt(cpu, CPU_INTERRUPT_EXITTB);
+ }
+}
+
+static const struct dma_irq_map omap1_dma_irq_map[] = {
+ { 0, OMAP_INT_DMA_CH0_6 },
+ { 0, OMAP_INT_DMA_CH1_7 },
+ { 0, OMAP_INT_DMA_CH2_8 },
+ { 0, OMAP_INT_DMA_CH3 },
+ { 0, OMAP_INT_DMA_CH4 },
+ { 0, OMAP_INT_DMA_CH5 },
+ { 1, OMAP_INT_1610_DMA_CH6 },
+ { 1, OMAP_INT_1610_DMA_CH7 },
+ { 1, OMAP_INT_1610_DMA_CH8 },
+ { 1, OMAP_INT_1610_DMA_CH9 },
+ { 1, OMAP_INT_1610_DMA_CH10 },
+ { 1, OMAP_INT_1610_DMA_CH11 },
+ { 1, OMAP_INT_1610_DMA_CH12 },
+ { 1, OMAP_INT_1610_DMA_CH13 },
+ { 1, OMAP_INT_1610_DMA_CH14 },
+ { 1, OMAP_INT_1610_DMA_CH15 }
+};
+
+/* DMA ports for OMAP1 */
+static int omap_validate_emiff_addr(struct omap_mpu_state_s *s,
+ hwaddr addr)
+{
+ return range_covers_byte(OMAP_EMIFF_BASE, s->sdram_size, addr);
+}
+
+static int omap_validate_emifs_addr(struct omap_mpu_state_s *s,
+ hwaddr addr)
+{
+ return range_covers_byte(OMAP_EMIFS_BASE, OMAP_EMIFF_BASE - OMAP_EMIFS_BASE,
+ addr);
+}
+
+static int omap_validate_imif_addr(struct omap_mpu_state_s *s,
+ hwaddr addr)
+{
+ return range_covers_byte(OMAP_IMIF_BASE, s->sram_size, addr);
+}
+
+static int omap_validate_tipb_addr(struct omap_mpu_state_s *s,
+ hwaddr addr)
+{
+ return range_covers_byte(0xfffb0000, 0xffff0000 - 0xfffb0000, addr);
+}
+
+static int omap_validate_local_addr(struct omap_mpu_state_s *s,
+ hwaddr addr)
+{
+ return range_covers_byte(OMAP_LOCALBUS_BASE, 0x1000000, addr);
+}
+
+static int omap_validate_tipb_mpui_addr(struct omap_mpu_state_s *s,
+ hwaddr addr)
+{
+ return range_covers_byte(0xe1010000, 0xe1020004 - 0xe1010000, addr);
+}
+
+struct omap_mpu_state_s *omap310_mpu_init(MemoryRegion *system_memory,
+ unsigned long sdram_size,
+ const char *core)
+{
+ int i;
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *)
+ g_malloc0(sizeof(struct omap_mpu_state_s));
+ qemu_irq dma_irqs[6];
+ DriveInfo *dinfo;
+ SysBusDevice *busdev;
+
+ if (!core)
+ core = "ti925t";
+
+ /* Core */
+ s->mpu_model = omap310;
+ s->cpu = cpu_arm_init(core);
+ if (s->cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ s->sdram_size = sdram_size;
+ s->sram_size = OMAP15XX_SRAM_SIZE;
+
+ s->wakeup = qemu_allocate_irq(omap_mpu_wakeup, s, 0);
+
+ /* Clocks */
+ omap_clk_init(s);
+
+ /* Memory-mapped stuff */
+ memory_region_allocate_system_memory(&s->emiff_ram, NULL, "omap1.dram",
+ s->sdram_size);
+ memory_region_add_subregion(system_memory, OMAP_EMIFF_BASE, &s->emiff_ram);
+ memory_region_init_ram(&s->imif_ram, NULL, "omap1.sram", s->sram_size,
+ &error_abort);
+ vmstate_register_ram_global(&s->imif_ram);
+ memory_region_add_subregion(system_memory, OMAP_IMIF_BASE, &s->imif_ram);
+
+ omap_clkm_init(system_memory, 0xfffece00, 0xe1008000, s);
+
+ s->ih[0] = qdev_create(NULL, "omap-intc");
+ qdev_prop_set_uint32(s->ih[0], "size", 0x100);
+ qdev_prop_set_ptr(s->ih[0], "clk", omap_findclk(s, "arminth_ck"));
+ qdev_init_nofail(s->ih[0]);
+ busdev = SYS_BUS_DEVICE(s->ih[0]);
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ));
+ sysbus_connect_irq(busdev, 1,
+ qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ));
+ sysbus_mmio_map(busdev, 0, 0xfffecb00);
+ s->ih[1] = qdev_create(NULL, "omap-intc");
+ qdev_prop_set_uint32(s->ih[1], "size", 0x800);
+ qdev_prop_set_ptr(s->ih[1], "clk", omap_findclk(s, "arminth_ck"));
+ qdev_init_nofail(s->ih[1]);
+ busdev = SYS_BUS_DEVICE(s->ih[1]);
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_15XX_IH2_IRQ));
+ /* The second interrupt controller's FIQ output is not wired up */
+ sysbus_mmio_map(busdev, 0, 0xfffe0000);
+
+ for (i = 0; i < 6; i++) {
+ dma_irqs[i] = qdev_get_gpio_in(s->ih[omap1_dma_irq_map[i].ih],
+ omap1_dma_irq_map[i].intr);
+ }
+ s->dma = omap_dma_init(0xfffed800, dma_irqs, system_memory,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_DMA_LCD),
+ s, omap_findclk(s, "dma_ck"), omap_dma_3_1);
+
+ s->port[emiff ].addr_valid = omap_validate_emiff_addr;
+ s->port[emifs ].addr_valid = omap_validate_emifs_addr;
+ s->port[imif ].addr_valid = omap_validate_imif_addr;
+ s->port[tipb ].addr_valid = omap_validate_tipb_addr;
+ s->port[local ].addr_valid = omap_validate_local_addr;
+ s->port[tipb_mpui].addr_valid = omap_validate_tipb_mpui_addr;
+
+ /* Register SDRAM and SRAM DMA ports for fast transfers. */
+ soc_dma_port_add_mem(s->dma, memory_region_get_ram_ptr(&s->emiff_ram),
+ OMAP_EMIFF_BASE, s->sdram_size);
+ soc_dma_port_add_mem(s->dma, memory_region_get_ram_ptr(&s->imif_ram),
+ OMAP_IMIF_BASE, s->sram_size);
+
+ s->timer[0] = omap_mpu_timer_init(system_memory, 0xfffec500,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_TIMER1),
+ omap_findclk(s, "mputim_ck"));
+ s->timer[1] = omap_mpu_timer_init(system_memory, 0xfffec600,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_TIMER2),
+ omap_findclk(s, "mputim_ck"));
+ s->timer[2] = omap_mpu_timer_init(system_memory, 0xfffec700,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_TIMER3),
+ omap_findclk(s, "mputim_ck"));
+
+ s->wdt = omap_wd_timer_init(system_memory, 0xfffec800,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_WD_TIMER),
+ omap_findclk(s, "armwdt_ck"));
+
+ s->os_timer = omap_os_timer_init(system_memory, 0xfffb9000,
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_OS_TIMER),
+ omap_findclk(s, "clk32-kHz"));
+
+ s->lcd = omap_lcdc_init(system_memory, 0xfffec000,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_LCD_CTRL),
+ omap_dma_get_lcdch(s->dma),
+ omap_findclk(s, "lcd_ck"));
+
+ omap_ulpd_pm_init(system_memory, 0xfffe0800, s);
+ omap_pin_cfg_init(system_memory, 0xfffe1000, s);
+ omap_id_init(system_memory, s);
+
+ omap_mpui_init(system_memory, 0xfffec900, s);
+
+ s->private_tipb = omap_tipb_bridge_init(system_memory, 0xfffeca00,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_BRIDGE_PRIV),
+ omap_findclk(s, "tipb_ck"));
+ s->public_tipb = omap_tipb_bridge_init(system_memory, 0xfffed300,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_BRIDGE_PUB),
+ omap_findclk(s, "tipb_ck"));
+
+ omap_tcmi_init(system_memory, 0xfffecc00, s);
+
+ s->uart[0] = omap_uart_init(0xfffb0000,
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_UART1),
+ omap_findclk(s, "uart1_ck"),
+ omap_findclk(s, "uart1_ck"),
+ s->drq[OMAP_DMA_UART1_TX], s->drq[OMAP_DMA_UART1_RX],
+ "uart1",
+ serial_hds[0]);
+ s->uart[1] = omap_uart_init(0xfffb0800,
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_UART2),
+ omap_findclk(s, "uart2_ck"),
+ omap_findclk(s, "uart2_ck"),
+ s->drq[OMAP_DMA_UART2_TX], s->drq[OMAP_DMA_UART2_RX],
+ "uart2",
+ serial_hds[0] ? serial_hds[1] : NULL);
+ s->uart[2] = omap_uart_init(0xfffb9800,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_UART3),
+ omap_findclk(s, "uart3_ck"),
+ omap_findclk(s, "uart3_ck"),
+ s->drq[OMAP_DMA_UART3_TX], s->drq[OMAP_DMA_UART3_RX],
+ "uart3",
+ serial_hds[0] && serial_hds[1] ? serial_hds[2] : NULL);
+
+ s->dpll[0] = omap_dpll_init(system_memory, 0xfffecf00,
+ omap_findclk(s, "dpll1"));
+ s->dpll[1] = omap_dpll_init(system_memory, 0xfffed000,
+ omap_findclk(s, "dpll2"));
+ s->dpll[2] = omap_dpll_init(system_memory, 0xfffed100,
+ omap_findclk(s, "dpll3"));
+
+ dinfo = drive_get(IF_SD, 0, 0);
+ if (!dinfo) {
+ fprintf(stderr, "qemu: missing SecureDigital device\n");
+ exit(1);
+ }
+ s->mmc = omap_mmc_init(0xfffb7800, system_memory,
+ blk_by_legacy_dinfo(dinfo),
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_OQN),
+ &s->drq[OMAP_DMA_MMC_TX],
+ omap_findclk(s, "mmc_ck"));
+
+ s->mpuio = omap_mpuio_init(system_memory, 0xfffb5000,
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_KEYBOARD),
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_MPUIO),
+ s->wakeup, omap_findclk(s, "clk32-kHz"));
+
+ s->gpio = qdev_create(NULL, "omap-gpio");
+ qdev_prop_set_int32(s->gpio, "mpu_model", s->mpu_model);
+ qdev_prop_set_ptr(s->gpio, "clk", omap_findclk(s, "arm_gpio_ck"));
+ qdev_init_nofail(s->gpio);
+ sysbus_connect_irq(SYS_BUS_DEVICE(s->gpio), 0,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_GPIO_BANK1));
+ sysbus_mmio_map(SYS_BUS_DEVICE(s->gpio), 0, 0xfffce000);
+
+ s->microwire = omap_uwire_init(system_memory, 0xfffb3000,
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_uWireTX),
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_uWireRX),
+ s->drq[OMAP_DMA_UWIRE_TX], omap_findclk(s, "mpuper_ck"));
+
+ s->pwl = omap_pwl_init(system_memory, 0xfffb5800,
+ omap_findclk(s, "armxor_ck"));
+ s->pwt = omap_pwt_init(system_memory, 0xfffb6000,
+ omap_findclk(s, "armxor_ck"));
+
+ s->i2c[0] = qdev_create(NULL, "omap_i2c");
+ qdev_prop_set_uint8(s->i2c[0], "revision", 0x11);
+ qdev_prop_set_ptr(s->i2c[0], "fclk", omap_findclk(s, "mpuper_ck"));
+ qdev_init_nofail(s->i2c[0]);
+ busdev = SYS_BUS_DEVICE(s->i2c[0]);
+ sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(s->ih[1], OMAP_INT_I2C));
+ sysbus_connect_irq(busdev, 1, s->drq[OMAP_DMA_I2C_TX]);
+ sysbus_connect_irq(busdev, 2, s->drq[OMAP_DMA_I2C_RX]);
+ sysbus_mmio_map(busdev, 0, 0xfffb3800);
+
+ s->rtc = omap_rtc_init(system_memory, 0xfffb4800,
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_RTC_TIMER),
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_RTC_ALARM),
+ omap_findclk(s, "clk32-kHz"));
+
+ s->mcbsp1 = omap_mcbsp_init(system_memory, 0xfffb1800,
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_McBSP1TX),
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_McBSP1RX),
+ &s->drq[OMAP_DMA_MCBSP1_TX], omap_findclk(s, "dspxor_ck"));
+ s->mcbsp2 = omap_mcbsp_init(system_memory, 0xfffb1000,
+ qdev_get_gpio_in(s->ih[0],
+ OMAP_INT_310_McBSP2_TX),
+ qdev_get_gpio_in(s->ih[0],
+ OMAP_INT_310_McBSP2_RX),
+ &s->drq[OMAP_DMA_MCBSP2_TX], omap_findclk(s, "mpuper_ck"));
+ s->mcbsp3 = omap_mcbsp_init(system_memory, 0xfffb7000,
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_McBSP3TX),
+ qdev_get_gpio_in(s->ih[1], OMAP_INT_McBSP3RX),
+ &s->drq[OMAP_DMA_MCBSP3_TX], omap_findclk(s, "dspxor_ck"));
+
+ s->led[0] = omap_lpg_init(system_memory,
+ 0xfffbd000, omap_findclk(s, "clk32-kHz"));
+ s->led[1] = omap_lpg_init(system_memory,
+ 0xfffbd800, omap_findclk(s, "clk32-kHz"));
+
+ /* Register mappings not currenlty implemented:
+ * MCSI2 Comm fffb2000 - fffb27ff (not mapped on OMAP310)
+ * MCSI1 Bluetooth fffb2800 - fffb2fff (not mapped on OMAP310)
+ * USB W2FC fffb4000 - fffb47ff
+ * Camera Interface fffb6800 - fffb6fff
+ * USB Host fffba000 - fffba7ff
+ * FAC fffba800 - fffbafff
+ * HDQ/1-Wire fffbc000 - fffbc7ff
+ * TIPB switches fffbc800 - fffbcfff
+ * Mailbox fffcf000 - fffcf7ff
+ * Local bus IF fffec100 - fffec1ff
+ * Local bus MMU fffec200 - fffec2ff
+ * DSP MMU fffed200 - fffed2ff
+ */
+
+ omap_setup_dsp_mapping(system_memory, omap15xx_dsp_mm);
+ omap_setup_mpui_io(system_memory, s);
+
+ qemu_register_reset(omap1_mpu_reset, s);
+
+ return s;
+}
diff --git a/hw/arm/omap2.c b/hw/arm/omap2.c
new file mode 100644
index 00000000..e39b3172
--- /dev/null
+++ b/hw/arm/omap2.c
@@ -0,0 +1,2692 @@
+/*
+ * TI OMAP processors emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/boards.h"
+#include "hw/hw.h"
+#include "hw/arm/arm.h"
+#include "hw/arm/omap.h"
+#include "sysemu/sysemu.h"
+#include "qemu/timer.h"
+#include "sysemu/char.h"
+#include "hw/block/flash.h"
+#include "hw/arm/soc_dma.h"
+#include "hw/sysbus.h"
+#include "audio/audio.h"
+
+/* Enhanced Audio Controller (CODEC only) */
+struct omap_eac_s {
+ qemu_irq irq;
+ MemoryRegion iomem;
+
+ uint16_t sysconfig;
+ uint8_t config[4];
+ uint8_t control;
+ uint8_t address;
+ uint16_t data;
+ uint8_t vtol;
+ uint8_t vtsl;
+ uint16_t mixer;
+ uint16_t gain[4];
+ uint8_t att;
+ uint16_t max[7];
+
+ struct {
+ qemu_irq txdrq;
+ qemu_irq rxdrq;
+ uint32_t (*txrx)(void *opaque, uint32_t, int);
+ void *opaque;
+
+#define EAC_BUF_LEN 1024
+ uint32_t rxbuf[EAC_BUF_LEN];
+ int rxoff;
+ int rxlen;
+ int rxavail;
+ uint32_t txbuf[EAC_BUF_LEN];
+ int txlen;
+ int txavail;
+
+ int enable;
+ int rate;
+
+ uint16_t config[4];
+
+ /* These need to be moved to the actual codec */
+ QEMUSoundCard card;
+ SWVoiceIn *in_voice;
+ SWVoiceOut *out_voice;
+ int hw_enable;
+ } codec;
+
+ struct {
+ uint8_t control;
+ uint16_t config;
+ } modem, bt;
+};
+
+static inline void omap_eac_interrupt_update(struct omap_eac_s *s)
+{
+ qemu_set_irq(s->irq, (s->codec.config[1] >> 14) & 1); /* AURDI */
+}
+
+static inline void omap_eac_in_dmarequest_update(struct omap_eac_s *s)
+{
+ qemu_set_irq(s->codec.rxdrq, (s->codec.rxavail || s->codec.rxlen) &&
+ ((s->codec.config[1] >> 12) & 1)); /* DMAREN */
+}
+
+static inline void omap_eac_out_dmarequest_update(struct omap_eac_s *s)
+{
+ qemu_set_irq(s->codec.txdrq, s->codec.txlen < s->codec.txavail &&
+ ((s->codec.config[1] >> 11) & 1)); /* DMAWEN */
+}
+
+static inline void omap_eac_in_refill(struct omap_eac_s *s)
+{
+ int left = MIN(EAC_BUF_LEN - s->codec.rxlen, s->codec.rxavail) << 2;
+ int start = ((s->codec.rxoff + s->codec.rxlen) & (EAC_BUF_LEN - 1)) << 2;
+ int leftwrap = MIN(left, (EAC_BUF_LEN << 2) - start);
+ int recv = 1;
+ uint8_t *buf = (uint8_t *) s->codec.rxbuf + start;
+
+ left -= leftwrap;
+ start = 0;
+ while (leftwrap && (recv = AUD_read(s->codec.in_voice, buf + start,
+ leftwrap)) > 0) { /* Be defensive */
+ start += recv;
+ leftwrap -= recv;
+ }
+ if (recv <= 0)
+ s->codec.rxavail = 0;
+ else
+ s->codec.rxavail -= start >> 2;
+ s->codec.rxlen += start >> 2;
+
+ if (recv > 0 && left > 0) {
+ start = 0;
+ while (left && (recv = AUD_read(s->codec.in_voice,
+ (uint8_t *) s->codec.rxbuf + start,
+ left)) > 0) { /* Be defensive */
+ start += recv;
+ left -= recv;
+ }
+ if (recv <= 0)
+ s->codec.rxavail = 0;
+ else
+ s->codec.rxavail -= start >> 2;
+ s->codec.rxlen += start >> 2;
+ }
+}
+
+static inline void omap_eac_out_empty(struct omap_eac_s *s)
+{
+ int left = s->codec.txlen << 2;
+ int start = 0;
+ int sent = 1;
+
+ while (left && (sent = AUD_write(s->codec.out_voice,
+ (uint8_t *) s->codec.txbuf + start,
+ left)) > 0) { /* Be defensive */
+ start += sent;
+ left -= sent;
+ }
+
+ if (!sent) {
+ s->codec.txavail = 0;
+ omap_eac_out_dmarequest_update(s);
+ }
+
+ if (start)
+ s->codec.txlen = 0;
+}
+
+static void omap_eac_in_cb(void *opaque, int avail_b)
+{
+ struct omap_eac_s *s = (struct omap_eac_s *) opaque;
+
+ s->codec.rxavail = avail_b >> 2;
+ omap_eac_in_refill(s);
+ /* TODO: possibly discard current buffer if overrun */
+ omap_eac_in_dmarequest_update(s);
+}
+
+static void omap_eac_out_cb(void *opaque, int free_b)
+{
+ struct omap_eac_s *s = (struct omap_eac_s *) opaque;
+
+ s->codec.txavail = free_b >> 2;
+ if (s->codec.txlen)
+ omap_eac_out_empty(s);
+ else
+ omap_eac_out_dmarequest_update(s);
+}
+
+static void omap_eac_enable_update(struct omap_eac_s *s)
+{
+ s->codec.enable = !(s->codec.config[1] & 1) && /* EACPWD */
+ (s->codec.config[1] & 2) && /* AUDEN */
+ s->codec.hw_enable;
+}
+
+static const int omap_eac_fsint[4] = {
+ 8000,
+ 11025,
+ 22050,
+ 44100,
+};
+
+static const int omap_eac_fsint2[8] = {
+ 8000,
+ 11025,
+ 22050,
+ 44100,
+ 48000,
+ 0, 0, 0,
+};
+
+static const int omap_eac_fsint3[16] = {
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 24000,
+ 32000,
+ 44100,
+ 48000,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void omap_eac_rate_update(struct omap_eac_s *s)
+{
+ int fsint[3];
+
+ fsint[2] = (s->codec.config[3] >> 9) & 0xf;
+ fsint[1] = (s->codec.config[2] >> 0) & 0x7;
+ fsint[0] = (s->codec.config[0] >> 6) & 0x3;
+ if (fsint[2] < 0xf)
+ s->codec.rate = omap_eac_fsint3[fsint[2]];
+ else if (fsint[1] < 0x7)
+ s->codec.rate = omap_eac_fsint2[fsint[1]];
+ else
+ s->codec.rate = omap_eac_fsint[fsint[0]];
+}
+
+static void omap_eac_volume_update(struct omap_eac_s *s)
+{
+ /* TODO */
+}
+
+static void omap_eac_format_update(struct omap_eac_s *s)
+{
+ struct audsettings fmt;
+
+ /* The hardware buffers at most one sample */
+ if (s->codec.rxlen)
+ s->codec.rxlen = 1;
+
+ if (s->codec.in_voice) {
+ AUD_set_active_in(s->codec.in_voice, 0);
+ AUD_close_in(&s->codec.card, s->codec.in_voice);
+ s->codec.in_voice = NULL;
+ }
+ if (s->codec.out_voice) {
+ omap_eac_out_empty(s);
+ AUD_set_active_out(s->codec.out_voice, 0);
+ AUD_close_out(&s->codec.card, s->codec.out_voice);
+ s->codec.out_voice = NULL;
+ s->codec.txavail = 0;
+ }
+ /* Discard what couldn't be written */
+ s->codec.txlen = 0;
+
+ omap_eac_enable_update(s);
+ if (!s->codec.enable)
+ return;
+
+ omap_eac_rate_update(s);
+ fmt.endianness = ((s->codec.config[0] >> 8) & 1); /* LI_BI */
+ fmt.nchannels = ((s->codec.config[0] >> 10) & 1) ? 2 : 1; /* MN_ST */
+ fmt.freq = s->codec.rate;
+ /* TODO: signedness possibly depends on the CODEC hardware - or
+ * does I2S specify it? */
+ /* All register writes are 16 bits so we we store 16-bit samples
+ * in the buffers regardless of AGCFR[B8_16] value. */
+ fmt.fmt = AUD_FMT_U16;
+
+ s->codec.in_voice = AUD_open_in(&s->codec.card, s->codec.in_voice,
+ "eac.codec.in", s, omap_eac_in_cb, &fmt);
+ s->codec.out_voice = AUD_open_out(&s->codec.card, s->codec.out_voice,
+ "eac.codec.out", s, omap_eac_out_cb, &fmt);
+
+ omap_eac_volume_update(s);
+
+ AUD_set_active_in(s->codec.in_voice, 1);
+ AUD_set_active_out(s->codec.out_voice, 1);
+}
+
+static void omap_eac_reset(struct omap_eac_s *s)
+{
+ s->sysconfig = 0;
+ s->config[0] = 0x0c;
+ s->config[1] = 0x09;
+ s->config[2] = 0xab;
+ s->config[3] = 0x03;
+ s->control = 0x00;
+ s->address = 0x00;
+ s->data = 0x0000;
+ s->vtol = 0x00;
+ s->vtsl = 0x00;
+ s->mixer = 0x0000;
+ s->gain[0] = 0xe7e7;
+ s->gain[1] = 0x6767;
+ s->gain[2] = 0x6767;
+ s->gain[3] = 0x6767;
+ s->att = 0xce;
+ s->max[0] = 0;
+ s->max[1] = 0;
+ s->max[2] = 0;
+ s->max[3] = 0;
+ s->max[4] = 0;
+ s->max[5] = 0;
+ s->max[6] = 0;
+
+ s->modem.control = 0x00;
+ s->modem.config = 0x0000;
+ s->bt.control = 0x00;
+ s->bt.config = 0x0000;
+ s->codec.config[0] = 0x0649;
+ s->codec.config[1] = 0x0000;
+ s->codec.config[2] = 0x0007;
+ s->codec.config[3] = 0x1ffc;
+ s->codec.rxoff = 0;
+ s->codec.rxlen = 0;
+ s->codec.txlen = 0;
+ s->codec.rxavail = 0;
+ s->codec.txavail = 0;
+
+ omap_eac_format_update(s);
+ omap_eac_interrupt_update(s);
+}
+
+static uint64_t omap_eac_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_eac_s *s = (struct omap_eac_s *) opaque;
+ uint32_t ret;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x000: /* CPCFR1 */
+ return s->config[0];
+ case 0x004: /* CPCFR2 */
+ return s->config[1];
+ case 0x008: /* CPCFR3 */
+ return s->config[2];
+ case 0x00c: /* CPCFR4 */
+ return s->config[3];
+
+ case 0x010: /* CPTCTL */
+ return s->control | ((s->codec.rxavail + s->codec.rxlen > 0) << 7) |
+ ((s->codec.txlen < s->codec.txavail) << 5);
+
+ case 0x014: /* CPTTADR */
+ return s->address;
+ case 0x018: /* CPTDATL */
+ return s->data & 0xff;
+ case 0x01c: /* CPTDATH */
+ return s->data >> 8;
+ case 0x020: /* CPTVSLL */
+ return s->vtol;
+ case 0x024: /* CPTVSLH */
+ return s->vtsl | (3 << 5); /* CRDY1 | CRDY2 */
+ case 0x040: /* MPCTR */
+ return s->modem.control;
+ case 0x044: /* MPMCCFR */
+ return s->modem.config;
+ case 0x060: /* BPCTR */
+ return s->bt.control;
+ case 0x064: /* BPMCCFR */
+ return s->bt.config;
+ case 0x080: /* AMSCFR */
+ return s->mixer;
+ case 0x084: /* AMVCTR */
+ return s->gain[0];
+ case 0x088: /* AM1VCTR */
+ return s->gain[1];
+ case 0x08c: /* AM2VCTR */
+ return s->gain[2];
+ case 0x090: /* AM3VCTR */
+ return s->gain[3];
+ case 0x094: /* ASTCTR */
+ return s->att;
+ case 0x098: /* APD1LCR */
+ return s->max[0];
+ case 0x09c: /* APD1RCR */
+ return s->max[1];
+ case 0x0a0: /* APD2LCR */
+ return s->max[2];
+ case 0x0a4: /* APD2RCR */
+ return s->max[3];
+ case 0x0a8: /* APD3LCR */
+ return s->max[4];
+ case 0x0ac: /* APD3RCR */
+ return s->max[5];
+ case 0x0b0: /* APD4R */
+ return s->max[6];
+ case 0x0b4: /* ADWR */
+ /* This should be write-only? Docs list it as read-only. */
+ return 0x0000;
+ case 0x0b8: /* ADRDR */
+ if (likely(s->codec.rxlen > 1)) {
+ ret = s->codec.rxbuf[s->codec.rxoff ++];
+ s->codec.rxlen --;
+ s->codec.rxoff &= EAC_BUF_LEN - 1;
+ return ret;
+ } else if (s->codec.rxlen) {
+ ret = s->codec.rxbuf[s->codec.rxoff ++];
+ s->codec.rxlen --;
+ s->codec.rxoff &= EAC_BUF_LEN - 1;
+ if (s->codec.rxavail)
+ omap_eac_in_refill(s);
+ omap_eac_in_dmarequest_update(s);
+ return ret;
+ }
+ return 0x0000;
+ case 0x0bc: /* AGCFR */
+ return s->codec.config[0];
+ case 0x0c0: /* AGCTR */
+ return s->codec.config[1] | ((s->codec.config[1] & 2) << 14);
+ case 0x0c4: /* AGCFR2 */
+ return s->codec.config[2];
+ case 0x0c8: /* AGCFR3 */
+ return s->codec.config[3];
+ case 0x0cc: /* MBPDMACTR */
+ case 0x0d0: /* MPDDMARR */
+ case 0x0d8: /* MPUDMARR */
+ case 0x0e4: /* BPDDMARR */
+ case 0x0ec: /* BPUDMARR */
+ return 0x0000;
+
+ case 0x100: /* VERSION_NUMBER */
+ return 0x0010;
+
+ case 0x104: /* SYSCONFIG */
+ return s->sysconfig;
+
+ case 0x108: /* SYSSTATUS */
+ return 1 | 0xe; /* RESETDONE | stuff */
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_eac_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_eac_s *s = (struct omap_eac_s *) opaque;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x098: /* APD1LCR */
+ case 0x09c: /* APD1RCR */
+ case 0x0a0: /* APD2LCR */
+ case 0x0a4: /* APD2RCR */
+ case 0x0a8: /* APD3LCR */
+ case 0x0ac: /* APD3RCR */
+ case 0x0b0: /* APD4R */
+ case 0x0b8: /* ADRDR */
+ case 0x0d0: /* MPDDMARR */
+ case 0x0d8: /* MPUDMARR */
+ case 0x0e4: /* BPDDMARR */
+ case 0x0ec: /* BPUDMARR */
+ case 0x100: /* VERSION_NUMBER */
+ case 0x108: /* SYSSTATUS */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x000: /* CPCFR1 */
+ s->config[0] = value & 0xff;
+ omap_eac_format_update(s);
+ break;
+ case 0x004: /* CPCFR2 */
+ s->config[1] = value & 0xff;
+ omap_eac_format_update(s);
+ break;
+ case 0x008: /* CPCFR3 */
+ s->config[2] = value & 0xff;
+ omap_eac_format_update(s);
+ break;
+ case 0x00c: /* CPCFR4 */
+ s->config[3] = value & 0xff;
+ omap_eac_format_update(s);
+ break;
+
+ case 0x010: /* CPTCTL */
+ /* Assuming TXF and TXE bits are read-only... */
+ s->control = value & 0x5f;
+ omap_eac_interrupt_update(s);
+ break;
+
+ case 0x014: /* CPTTADR */
+ s->address = value & 0xff;
+ break;
+ case 0x018: /* CPTDATL */
+ s->data &= 0xff00;
+ s->data |= value & 0xff;
+ break;
+ case 0x01c: /* CPTDATH */
+ s->data &= 0x00ff;
+ s->data |= value << 8;
+ break;
+ case 0x020: /* CPTVSLL */
+ s->vtol = value & 0xf8;
+ break;
+ case 0x024: /* CPTVSLH */
+ s->vtsl = value & 0x9f;
+ break;
+ case 0x040: /* MPCTR */
+ s->modem.control = value & 0x8f;
+ break;
+ case 0x044: /* MPMCCFR */
+ s->modem.config = value & 0x7fff;
+ break;
+ case 0x060: /* BPCTR */
+ s->bt.control = value & 0x8f;
+ break;
+ case 0x064: /* BPMCCFR */
+ s->bt.config = value & 0x7fff;
+ break;
+ case 0x080: /* AMSCFR */
+ s->mixer = value & 0x0fff;
+ break;
+ case 0x084: /* AMVCTR */
+ s->gain[0] = value & 0xffff;
+ break;
+ case 0x088: /* AM1VCTR */
+ s->gain[1] = value & 0xff7f;
+ break;
+ case 0x08c: /* AM2VCTR */
+ s->gain[2] = value & 0xff7f;
+ break;
+ case 0x090: /* AM3VCTR */
+ s->gain[3] = value & 0xff7f;
+ break;
+ case 0x094: /* ASTCTR */
+ s->att = value & 0xff;
+ break;
+
+ case 0x0b4: /* ADWR */
+ s->codec.txbuf[s->codec.txlen ++] = value;
+ if (unlikely(s->codec.txlen == EAC_BUF_LEN ||
+ s->codec.txlen == s->codec.txavail)) {
+ if (s->codec.txavail)
+ omap_eac_out_empty(s);
+ /* Discard what couldn't be written */
+ s->codec.txlen = 0;
+ }
+ break;
+
+ case 0x0bc: /* AGCFR */
+ s->codec.config[0] = value & 0x07ff;
+ omap_eac_format_update(s);
+ break;
+ case 0x0c0: /* AGCTR */
+ s->codec.config[1] = value & 0x780f;
+ omap_eac_format_update(s);
+ break;
+ case 0x0c4: /* AGCFR2 */
+ s->codec.config[2] = value & 0x003f;
+ omap_eac_format_update(s);
+ break;
+ case 0x0c8: /* AGCFR3 */
+ s->codec.config[3] = value & 0xffff;
+ omap_eac_format_update(s);
+ break;
+ case 0x0cc: /* MBPDMACTR */
+ case 0x0d4: /* MPDDMAWR */
+ case 0x0e0: /* MPUDMAWR */
+ case 0x0e8: /* BPDDMAWR */
+ case 0x0f0: /* BPUDMAWR */
+ break;
+
+ case 0x104: /* SYSCONFIG */
+ if (value & (1 << 1)) /* SOFTRESET */
+ omap_eac_reset(s);
+ s->sysconfig = value & 0x31d;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_eac_ops = {
+ .read = omap_eac_read,
+ .write = omap_eac_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static struct omap_eac_s *omap_eac_init(struct omap_target_agent_s *ta,
+ qemu_irq irq, qemu_irq *drq, omap_clk fclk, omap_clk iclk)
+{
+ struct omap_eac_s *s = (struct omap_eac_s *)
+ g_malloc0(sizeof(struct omap_eac_s));
+
+ s->irq = irq;
+ s->codec.rxdrq = *drq ++;
+ s->codec.txdrq = *drq;
+ omap_eac_reset(s);
+
+ AUD_register_card("OMAP EAC", &s->codec.card);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_eac_ops, s, "omap.eac",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ return s;
+}
+
+/* STI/XTI (emulation interface) console - reverse engineered only */
+struct omap_sti_s {
+ qemu_irq irq;
+ MemoryRegion iomem;
+ MemoryRegion iomem_fifo;
+ CharDriverState *chr;
+
+ uint32_t sysconfig;
+ uint32_t systest;
+ uint32_t irqst;
+ uint32_t irqen;
+ uint32_t clkcontrol;
+ uint32_t serial_config;
+};
+
+#define STI_TRACE_CONSOLE_CHANNEL 239
+#define STI_TRACE_CONTROL_CHANNEL 253
+
+static inline void omap_sti_interrupt_update(struct omap_sti_s *s)
+{
+ qemu_set_irq(s->irq, s->irqst & s->irqen);
+}
+
+static void omap_sti_reset(struct omap_sti_s *s)
+{
+ s->sysconfig = 0;
+ s->irqst = 0;
+ s->irqen = 0;
+ s->clkcontrol = 0;
+ s->serial_config = 0;
+
+ omap_sti_interrupt_update(s);
+}
+
+static uint64_t omap_sti_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_sti_s *s = (struct omap_sti_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* STI_REVISION */
+ return 0x10;
+
+ case 0x10: /* STI_SYSCONFIG */
+ return s->sysconfig;
+
+ case 0x14: /* STI_SYSSTATUS / STI_RX_STATUS / XTI_SYSSTATUS */
+ return 0x00;
+
+ case 0x18: /* STI_IRQSTATUS */
+ return s->irqst;
+
+ case 0x1c: /* STI_IRQSETEN / STI_IRQCLREN */
+ return s->irqen;
+
+ case 0x24: /* STI_ER / STI_DR / XTI_TRACESELECT */
+ case 0x28: /* STI_RX_DR / XTI_RXDATA */
+ /* TODO */
+ return 0;
+
+ case 0x2c: /* STI_CLK_CTRL / XTI_SCLKCRTL */
+ return s->clkcontrol;
+
+ case 0x30: /* STI_SERIAL_CFG / XTI_SCONFIG */
+ return s->serial_config;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_sti_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_sti_s *s = (struct omap_sti_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* STI_REVISION */
+ case 0x14: /* STI_SYSSTATUS / STI_RX_STATUS / XTI_SYSSTATUS */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x10: /* STI_SYSCONFIG */
+ if (value & (1 << 1)) /* SOFTRESET */
+ omap_sti_reset(s);
+ s->sysconfig = value & 0xfe;
+ break;
+
+ case 0x18: /* STI_IRQSTATUS */
+ s->irqst &= ~value;
+ omap_sti_interrupt_update(s);
+ break;
+
+ case 0x1c: /* STI_IRQSETEN / STI_IRQCLREN */
+ s->irqen = value & 0xffff;
+ omap_sti_interrupt_update(s);
+ break;
+
+ case 0x2c: /* STI_CLK_CTRL / XTI_SCLKCRTL */
+ s->clkcontrol = value & 0xff;
+ break;
+
+ case 0x30: /* STI_SERIAL_CFG / XTI_SCONFIG */
+ s->serial_config = value & 0xff;
+ break;
+
+ case 0x24: /* STI_ER / STI_DR / XTI_TRACESELECT */
+ case 0x28: /* STI_RX_DR / XTI_RXDATA */
+ /* TODO */
+ return;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_sti_ops = {
+ .read = omap_sti_read,
+ .write = omap_sti_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t omap_sti_fifo_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_sti_fifo_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_sti_s *s = (struct omap_sti_s *) opaque;
+ int ch = addr >> 6;
+ uint8_t byte = value;
+
+ if (size != 1) {
+ omap_badwidth_write8(opaque, addr, size);
+ return;
+ }
+
+ if (ch == STI_TRACE_CONTROL_CHANNEL) {
+ /* Flush channel <i>value</i>. */
+ qemu_chr_fe_write(s->chr, (const uint8_t *) "\r", 1);
+ } else if (ch == STI_TRACE_CONSOLE_CHANNEL || 1) {
+ if (value == 0xc0 || value == 0xc3) {
+ /* Open channel <i>ch</i>. */
+ } else if (value == 0x00)
+ qemu_chr_fe_write(s->chr, (const uint8_t *) "\n", 1);
+ else
+ qemu_chr_fe_write(s->chr, &byte, 1);
+ }
+}
+
+static const MemoryRegionOps omap_sti_fifo_ops = {
+ .read = omap_sti_fifo_read,
+ .write = omap_sti_fifo_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static struct omap_sti_s *omap_sti_init(struct omap_target_agent_s *ta,
+ MemoryRegion *sysmem,
+ hwaddr channel_base, qemu_irq irq, omap_clk clk,
+ CharDriverState *chr)
+{
+ struct omap_sti_s *s = (struct omap_sti_s *)
+ g_malloc0(sizeof(struct omap_sti_s));
+
+ s->irq = irq;
+ omap_sti_reset(s);
+
+ s->chr = chr ?: qemu_chr_new("null", "null", NULL);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_sti_ops, s, "omap.sti",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ memory_region_init_io(&s->iomem_fifo, NULL, &omap_sti_fifo_ops, s,
+ "omap.sti.fifo", 0x10000);
+ memory_region_add_subregion(sysmem, channel_base, &s->iomem_fifo);
+
+ return s;
+}
+
+/* L4 Interconnect */
+#define L4TA(n) (n)
+#define L4TAO(n) ((n) + 39)
+
+static const struct omap_l4_region_s omap_l4_region[125] = {
+ [ 1] = { 0x40800, 0x800, 32 }, /* Initiator agent */
+ [ 2] = { 0x41000, 0x1000, 32 }, /* Link agent */
+ [ 0] = { 0x40000, 0x800, 32 }, /* Address and protection */
+ [ 3] = { 0x00000, 0x1000, 32 | 16 | 8 }, /* System Control and Pinout */
+ [ 4] = { 0x01000, 0x1000, 32 | 16 | 8 }, /* L4TAO1 */
+ [ 5] = { 0x04000, 0x1000, 32 | 16 }, /* 32K Timer */
+ [ 6] = { 0x05000, 0x1000, 32 | 16 | 8 }, /* L4TAO2 */
+ [ 7] = { 0x08000, 0x800, 32 }, /* PRCM Region A */
+ [ 8] = { 0x08800, 0x800, 32 }, /* PRCM Region B */
+ [ 9] = { 0x09000, 0x1000, 32 | 16 | 8 }, /* L4TAO */
+ [ 10] = { 0x12000, 0x1000, 32 | 16 | 8 }, /* Test (BCM) */
+ [ 11] = { 0x13000, 0x1000, 32 | 16 | 8 }, /* L4TA1 */
+ [ 12] = { 0x14000, 0x1000, 32 }, /* Test/emulation (TAP) */
+ [ 13] = { 0x15000, 0x1000, 32 | 16 | 8 }, /* L4TA2 */
+ [ 14] = { 0x18000, 0x1000, 32 | 16 | 8 }, /* GPIO1 */
+ [ 16] = { 0x1a000, 0x1000, 32 | 16 | 8 }, /* GPIO2 */
+ [ 18] = { 0x1c000, 0x1000, 32 | 16 | 8 }, /* GPIO3 */
+ [ 19] = { 0x1e000, 0x1000, 32 | 16 | 8 }, /* GPIO4 */
+ [ 15] = { 0x19000, 0x1000, 32 | 16 | 8 }, /* Quad GPIO TOP */
+ [ 17] = { 0x1b000, 0x1000, 32 | 16 | 8 }, /* L4TA3 */
+ [ 20] = { 0x20000, 0x1000, 32 | 16 | 8 }, /* WD Timer 1 (Secure) */
+ [ 22] = { 0x22000, 0x1000, 32 | 16 | 8 }, /* WD Timer 2 (OMAP) */
+ [ 21] = { 0x21000, 0x1000, 32 | 16 | 8 }, /* Dual WD timer TOP */
+ [ 23] = { 0x23000, 0x1000, 32 | 16 | 8 }, /* L4TA4 */
+ [ 24] = { 0x28000, 0x1000, 32 | 16 | 8 }, /* GP Timer 1 */
+ [ 25] = { 0x29000, 0x1000, 32 | 16 | 8 }, /* L4TA7 */
+ [ 26] = { 0x48000, 0x2000, 32 | 16 | 8 }, /* Emulation (ARM11ETB) */
+ [ 27] = { 0x4a000, 0x1000, 32 | 16 | 8 }, /* L4TA9 */
+ [ 28] = { 0x50000, 0x400, 32 | 16 | 8 }, /* Display top */
+ [ 29] = { 0x50400, 0x400, 32 | 16 | 8 }, /* Display control */
+ [ 30] = { 0x50800, 0x400, 32 | 16 | 8 }, /* Display RFBI */
+ [ 31] = { 0x50c00, 0x400, 32 | 16 | 8 }, /* Display encoder */
+ [ 32] = { 0x51000, 0x1000, 32 | 16 | 8 }, /* L4TA10 */
+ [ 33] = { 0x52000, 0x400, 32 | 16 | 8 }, /* Camera top */
+ [ 34] = { 0x52400, 0x400, 32 | 16 | 8 }, /* Camera core */
+ [ 35] = { 0x52800, 0x400, 32 | 16 | 8 }, /* Camera DMA */
+ [ 36] = { 0x52c00, 0x400, 32 | 16 | 8 }, /* Camera MMU */
+ [ 37] = { 0x53000, 0x1000, 32 | 16 | 8 }, /* L4TA11 */
+ [ 38] = { 0x56000, 0x1000, 32 | 16 | 8 }, /* sDMA */
+ [ 39] = { 0x57000, 0x1000, 32 | 16 | 8 }, /* L4TA12 */
+ [ 40] = { 0x58000, 0x1000, 32 | 16 | 8 }, /* SSI top */
+ [ 41] = { 0x59000, 0x1000, 32 | 16 | 8 }, /* SSI GDD */
+ [ 42] = { 0x5a000, 0x1000, 32 | 16 | 8 }, /* SSI Port1 */
+ [ 43] = { 0x5b000, 0x1000, 32 | 16 | 8 }, /* SSI Port2 */
+ [ 44] = { 0x5c000, 0x1000, 32 | 16 | 8 }, /* L4TA13 */
+ [ 45] = { 0x5e000, 0x1000, 32 | 16 | 8 }, /* USB OTG */
+ [ 46] = { 0x5f000, 0x1000, 32 | 16 | 8 }, /* L4TAO4 */
+ [ 47] = { 0x60000, 0x1000, 32 | 16 | 8 }, /* Emulation (WIN_TRACER1SDRC) */
+ [ 48] = { 0x61000, 0x1000, 32 | 16 | 8 }, /* L4TA14 */
+ [ 49] = { 0x62000, 0x1000, 32 | 16 | 8 }, /* Emulation (WIN_TRACER2GPMC) */
+ [ 50] = { 0x63000, 0x1000, 32 | 16 | 8 }, /* L4TA15 */
+ [ 51] = { 0x64000, 0x1000, 32 | 16 | 8 }, /* Emulation (WIN_TRACER3OCM) */
+ [ 52] = { 0x65000, 0x1000, 32 | 16 | 8 }, /* L4TA16 */
+ [ 53] = { 0x66000, 0x300, 32 | 16 | 8 }, /* Emulation (WIN_TRACER4L4) */
+ [ 54] = { 0x67000, 0x1000, 32 | 16 | 8 }, /* L4TA17 */
+ [ 55] = { 0x68000, 0x1000, 32 | 16 | 8 }, /* Emulation (XTI) */
+ [ 56] = { 0x69000, 0x1000, 32 | 16 | 8 }, /* L4TA18 */
+ [ 57] = { 0x6a000, 0x1000, 16 | 8 }, /* UART1 */
+ [ 58] = { 0x6b000, 0x1000, 32 | 16 | 8 }, /* L4TA19 */
+ [ 59] = { 0x6c000, 0x1000, 16 | 8 }, /* UART2 */
+ [ 60] = { 0x6d000, 0x1000, 32 | 16 | 8 }, /* L4TA20 */
+ [ 61] = { 0x6e000, 0x1000, 16 | 8 }, /* UART3 */
+ [ 62] = { 0x6f000, 0x1000, 32 | 16 | 8 }, /* L4TA21 */
+ [ 63] = { 0x70000, 0x1000, 16 }, /* I2C1 */
+ [ 64] = { 0x71000, 0x1000, 32 | 16 | 8 }, /* L4TAO5 */
+ [ 65] = { 0x72000, 0x1000, 16 }, /* I2C2 */
+ [ 66] = { 0x73000, 0x1000, 32 | 16 | 8 }, /* L4TAO6 */
+ [ 67] = { 0x74000, 0x1000, 16 }, /* McBSP1 */
+ [ 68] = { 0x75000, 0x1000, 32 | 16 | 8 }, /* L4TAO7 */
+ [ 69] = { 0x76000, 0x1000, 16 }, /* McBSP2 */
+ [ 70] = { 0x77000, 0x1000, 32 | 16 | 8 }, /* L4TAO8 */
+ [ 71] = { 0x24000, 0x1000, 32 | 16 | 8 }, /* WD Timer 3 (DSP) */
+ [ 72] = { 0x25000, 0x1000, 32 | 16 | 8 }, /* L4TA5 */
+ [ 73] = { 0x26000, 0x1000, 32 | 16 | 8 }, /* WD Timer 4 (IVA) */
+ [ 74] = { 0x27000, 0x1000, 32 | 16 | 8 }, /* L4TA6 */
+ [ 75] = { 0x2a000, 0x1000, 32 | 16 | 8 }, /* GP Timer 2 */
+ [ 76] = { 0x2b000, 0x1000, 32 | 16 | 8 }, /* L4TA8 */
+ [ 77] = { 0x78000, 0x1000, 32 | 16 | 8 }, /* GP Timer 3 */
+ [ 78] = { 0x79000, 0x1000, 32 | 16 | 8 }, /* L4TA22 */
+ [ 79] = { 0x7a000, 0x1000, 32 | 16 | 8 }, /* GP Timer 4 */
+ [ 80] = { 0x7b000, 0x1000, 32 | 16 | 8 }, /* L4TA23 */
+ [ 81] = { 0x7c000, 0x1000, 32 | 16 | 8 }, /* GP Timer 5 */
+ [ 82] = { 0x7d000, 0x1000, 32 | 16 | 8 }, /* L4TA24 */
+ [ 83] = { 0x7e000, 0x1000, 32 | 16 | 8 }, /* GP Timer 6 */
+ [ 84] = { 0x7f000, 0x1000, 32 | 16 | 8 }, /* L4TA25 */
+ [ 85] = { 0x80000, 0x1000, 32 | 16 | 8 }, /* GP Timer 7 */
+ [ 86] = { 0x81000, 0x1000, 32 | 16 | 8 }, /* L4TA26 */
+ [ 87] = { 0x82000, 0x1000, 32 | 16 | 8 }, /* GP Timer 8 */
+ [ 88] = { 0x83000, 0x1000, 32 | 16 | 8 }, /* L4TA27 */
+ [ 89] = { 0x84000, 0x1000, 32 | 16 | 8 }, /* GP Timer 9 */
+ [ 90] = { 0x85000, 0x1000, 32 | 16 | 8 }, /* L4TA28 */
+ [ 91] = { 0x86000, 0x1000, 32 | 16 | 8 }, /* GP Timer 10 */
+ [ 92] = { 0x87000, 0x1000, 32 | 16 | 8 }, /* L4TA29 */
+ [ 93] = { 0x88000, 0x1000, 32 | 16 | 8 }, /* GP Timer 11 */
+ [ 94] = { 0x89000, 0x1000, 32 | 16 | 8 }, /* L4TA30 */
+ [ 95] = { 0x8a000, 0x1000, 32 | 16 | 8 }, /* GP Timer 12 */
+ [ 96] = { 0x8b000, 0x1000, 32 | 16 | 8 }, /* L4TA31 */
+ [ 97] = { 0x90000, 0x1000, 16 }, /* EAC */
+ [ 98] = { 0x91000, 0x1000, 32 | 16 | 8 }, /* L4TA32 */
+ [ 99] = { 0x92000, 0x1000, 16 }, /* FAC */
+ [100] = { 0x93000, 0x1000, 32 | 16 | 8 }, /* L4TA33 */
+ [101] = { 0x94000, 0x1000, 32 | 16 | 8 }, /* IPC (MAILBOX) */
+ [102] = { 0x95000, 0x1000, 32 | 16 | 8 }, /* L4TA34 */
+ [103] = { 0x98000, 0x1000, 32 | 16 | 8 }, /* SPI1 */
+ [104] = { 0x99000, 0x1000, 32 | 16 | 8 }, /* L4TA35 */
+ [105] = { 0x9a000, 0x1000, 32 | 16 | 8 }, /* SPI2 */
+ [106] = { 0x9b000, 0x1000, 32 | 16 | 8 }, /* L4TA36 */
+ [107] = { 0x9c000, 0x1000, 16 | 8 }, /* MMC SDIO */
+ [108] = { 0x9d000, 0x1000, 32 | 16 | 8 }, /* L4TAO9 */
+ [109] = { 0x9e000, 0x1000, 32 | 16 | 8 }, /* MS_PRO */
+ [110] = { 0x9f000, 0x1000, 32 | 16 | 8 }, /* L4TAO10 */
+ [111] = { 0xa0000, 0x1000, 32 }, /* RNG */
+ [112] = { 0xa1000, 0x1000, 32 | 16 | 8 }, /* L4TAO11 */
+ [113] = { 0xa2000, 0x1000, 32 }, /* DES3DES */
+ [114] = { 0xa3000, 0x1000, 32 | 16 | 8 }, /* L4TAO12 */
+ [115] = { 0xa4000, 0x1000, 32 }, /* SHA1MD5 */
+ [116] = { 0xa5000, 0x1000, 32 | 16 | 8 }, /* L4TAO13 */
+ [117] = { 0xa6000, 0x1000, 32 }, /* AES */
+ [118] = { 0xa7000, 0x1000, 32 | 16 | 8 }, /* L4TA37 */
+ [119] = { 0xa8000, 0x2000, 32 }, /* PKA */
+ [120] = { 0xaa000, 0x1000, 32 | 16 | 8 }, /* L4TA38 */
+ [121] = { 0xb0000, 0x1000, 32 }, /* MG */
+ [122] = { 0xb1000, 0x1000, 32 | 16 | 8 },
+ [123] = { 0xb2000, 0x1000, 32 }, /* HDQ/1-Wire */
+ [124] = { 0xb3000, 0x1000, 32 | 16 | 8 }, /* L4TA39 */
+};
+
+static const struct omap_l4_agent_info_s omap_l4_agent_info[54] = {
+ { 0, 0, 3, 2 }, /* L4IA initiatior agent */
+ { L4TAO(1), 3, 2, 1 }, /* Control and pinout module */
+ { L4TAO(2), 5, 2, 1 }, /* 32K timer */
+ { L4TAO(3), 7, 3, 2 }, /* PRCM */
+ { L4TA(1), 10, 2, 1 }, /* BCM */
+ { L4TA(2), 12, 2, 1 }, /* Test JTAG */
+ { L4TA(3), 14, 6, 3 }, /* Quad GPIO */
+ { L4TA(4), 20, 4, 3 }, /* WD timer 1/2 */
+ { L4TA(7), 24, 2, 1 }, /* GP timer 1 */
+ { L4TA(9), 26, 2, 1 }, /* ATM11 ETB */
+ { L4TA(10), 28, 5, 4 }, /* Display subsystem */
+ { L4TA(11), 33, 5, 4 }, /* Camera subsystem */
+ { L4TA(12), 38, 2, 1 }, /* sDMA */
+ { L4TA(13), 40, 5, 4 }, /* SSI */
+ { L4TAO(4), 45, 2, 1 }, /* USB */
+ { L4TA(14), 47, 2, 1 }, /* Win Tracer1 */
+ { L4TA(15), 49, 2, 1 }, /* Win Tracer2 */
+ { L4TA(16), 51, 2, 1 }, /* Win Tracer3 */
+ { L4TA(17), 53, 2, 1 }, /* Win Tracer4 */
+ { L4TA(18), 55, 2, 1 }, /* XTI */
+ { L4TA(19), 57, 2, 1 }, /* UART1 */
+ { L4TA(20), 59, 2, 1 }, /* UART2 */
+ { L4TA(21), 61, 2, 1 }, /* UART3 */
+ { L4TAO(5), 63, 2, 1 }, /* I2C1 */
+ { L4TAO(6), 65, 2, 1 }, /* I2C2 */
+ { L4TAO(7), 67, 2, 1 }, /* McBSP1 */
+ { L4TAO(8), 69, 2, 1 }, /* McBSP2 */
+ { L4TA(5), 71, 2, 1 }, /* WD Timer 3 (DSP) */
+ { L4TA(6), 73, 2, 1 }, /* WD Timer 4 (IVA) */
+ { L4TA(8), 75, 2, 1 }, /* GP Timer 2 */
+ { L4TA(22), 77, 2, 1 }, /* GP Timer 3 */
+ { L4TA(23), 79, 2, 1 }, /* GP Timer 4 */
+ { L4TA(24), 81, 2, 1 }, /* GP Timer 5 */
+ { L4TA(25), 83, 2, 1 }, /* GP Timer 6 */
+ { L4TA(26), 85, 2, 1 }, /* GP Timer 7 */
+ { L4TA(27), 87, 2, 1 }, /* GP Timer 8 */
+ { L4TA(28), 89, 2, 1 }, /* GP Timer 9 */
+ { L4TA(29), 91, 2, 1 }, /* GP Timer 10 */
+ { L4TA(30), 93, 2, 1 }, /* GP Timer 11 */
+ { L4TA(31), 95, 2, 1 }, /* GP Timer 12 */
+ { L4TA(32), 97, 2, 1 }, /* EAC */
+ { L4TA(33), 99, 2, 1 }, /* FAC */
+ { L4TA(34), 101, 2, 1 }, /* IPC */
+ { L4TA(35), 103, 2, 1 }, /* SPI1 */
+ { L4TA(36), 105, 2, 1 }, /* SPI2 */
+ { L4TAO(9), 107, 2, 1 }, /* MMC SDIO */
+ { L4TAO(10), 109, 2, 1 },
+ { L4TAO(11), 111, 2, 1 }, /* RNG */
+ { L4TAO(12), 113, 2, 1 }, /* DES3DES */
+ { L4TAO(13), 115, 2, 1 }, /* SHA1MD5 */
+ { L4TA(37), 117, 2, 1 }, /* AES */
+ { L4TA(38), 119, 2, 1 }, /* PKA */
+ { -1, 121, 2, 1 },
+ { L4TA(39), 123, 2, 1 }, /* HDQ/1-Wire */
+};
+
+#define omap_l4ta(bus, cs) \
+ omap_l4ta_get(bus, omap_l4_region, omap_l4_agent_info, L4TA(cs))
+#define omap_l4tao(bus, cs) \
+ omap_l4ta_get(bus, omap_l4_region, omap_l4_agent_info, L4TAO(cs))
+
+/* Power, Reset, and Clock Management */
+struct omap_prcm_s {
+ qemu_irq irq[3];
+ struct omap_mpu_state_s *mpu;
+ MemoryRegion iomem0;
+ MemoryRegion iomem1;
+
+ uint32_t irqst[3];
+ uint32_t irqen[3];
+
+ uint32_t sysconfig;
+ uint32_t voltctrl;
+ uint32_t scratch[20];
+
+ uint32_t clksrc[1];
+ uint32_t clkout[1];
+ uint32_t clkemul[1];
+ uint32_t clkpol[1];
+ uint32_t clksel[8];
+ uint32_t clken[12];
+ uint32_t clkctrl[4];
+ uint32_t clkidle[7];
+ uint32_t setuptime[2];
+
+ uint32_t wkup[3];
+ uint32_t wken[3];
+ uint32_t wkst[3];
+ uint32_t rst[4];
+ uint32_t rstctrl[1];
+ uint32_t power[4];
+ uint32_t rsttime_wkup;
+
+ uint32_t ev;
+ uint32_t evtime[2];
+
+ int dpll_lock, apll_lock[2];
+};
+
+static void omap_prcm_int_update(struct omap_prcm_s *s, int dom)
+{
+ qemu_set_irq(s->irq[dom], s->irqst[dom] & s->irqen[dom]);
+ /* XXX or is the mask applied before PRCM_IRQSTATUS_* ? */
+}
+
+static uint64_t omap_prcm_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_prcm_s *s = (struct omap_prcm_s *) opaque;
+ uint32_t ret;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x000: /* PRCM_REVISION */
+ return 0x10;
+
+ case 0x010: /* PRCM_SYSCONFIG */
+ return s->sysconfig;
+
+ case 0x018: /* PRCM_IRQSTATUS_MPU */
+ return s->irqst[0];
+
+ case 0x01c: /* PRCM_IRQENABLE_MPU */
+ return s->irqen[0];
+
+ case 0x050: /* PRCM_VOLTCTRL */
+ return s->voltctrl;
+ case 0x054: /* PRCM_VOLTST */
+ return s->voltctrl & 3;
+
+ case 0x060: /* PRCM_CLKSRC_CTRL */
+ return s->clksrc[0];
+ case 0x070: /* PRCM_CLKOUT_CTRL */
+ return s->clkout[0];
+ case 0x078: /* PRCM_CLKEMUL_CTRL */
+ return s->clkemul[0];
+ case 0x080: /* PRCM_CLKCFG_CTRL */
+ case 0x084: /* PRCM_CLKCFG_STATUS */
+ return 0;
+
+ case 0x090: /* PRCM_VOLTSETUP */
+ return s->setuptime[0];
+
+ case 0x094: /* PRCM_CLKSSETUP */
+ return s->setuptime[1];
+
+ case 0x098: /* PRCM_POLCTRL */
+ return s->clkpol[0];
+
+ case 0x0b0: /* GENERAL_PURPOSE1 */
+ case 0x0b4: /* GENERAL_PURPOSE2 */
+ case 0x0b8: /* GENERAL_PURPOSE3 */
+ case 0x0bc: /* GENERAL_PURPOSE4 */
+ case 0x0c0: /* GENERAL_PURPOSE5 */
+ case 0x0c4: /* GENERAL_PURPOSE6 */
+ case 0x0c8: /* GENERAL_PURPOSE7 */
+ case 0x0cc: /* GENERAL_PURPOSE8 */
+ case 0x0d0: /* GENERAL_PURPOSE9 */
+ case 0x0d4: /* GENERAL_PURPOSE10 */
+ case 0x0d8: /* GENERAL_PURPOSE11 */
+ case 0x0dc: /* GENERAL_PURPOSE12 */
+ case 0x0e0: /* GENERAL_PURPOSE13 */
+ case 0x0e4: /* GENERAL_PURPOSE14 */
+ case 0x0e8: /* GENERAL_PURPOSE15 */
+ case 0x0ec: /* GENERAL_PURPOSE16 */
+ case 0x0f0: /* GENERAL_PURPOSE17 */
+ case 0x0f4: /* GENERAL_PURPOSE18 */
+ case 0x0f8: /* GENERAL_PURPOSE19 */
+ case 0x0fc: /* GENERAL_PURPOSE20 */
+ return s->scratch[(addr - 0xb0) >> 2];
+
+ case 0x140: /* CM_CLKSEL_MPU */
+ return s->clksel[0];
+ case 0x148: /* CM_CLKSTCTRL_MPU */
+ return s->clkctrl[0];
+
+ case 0x158: /* RM_RSTST_MPU */
+ return s->rst[0];
+ case 0x1c8: /* PM_WKDEP_MPU */
+ return s->wkup[0];
+ case 0x1d4: /* PM_EVGENCTRL_MPU */
+ return s->ev;
+ case 0x1d8: /* PM_EVEGENONTIM_MPU */
+ return s->evtime[0];
+ case 0x1dc: /* PM_EVEGENOFFTIM_MPU */
+ return s->evtime[1];
+ case 0x1e0: /* PM_PWSTCTRL_MPU */
+ return s->power[0];
+ case 0x1e4: /* PM_PWSTST_MPU */
+ return 0;
+
+ case 0x200: /* CM_FCLKEN1_CORE */
+ return s->clken[0];
+ case 0x204: /* CM_FCLKEN2_CORE */
+ return s->clken[1];
+ case 0x210: /* CM_ICLKEN1_CORE */
+ return s->clken[2];
+ case 0x214: /* CM_ICLKEN2_CORE */
+ return s->clken[3];
+ case 0x21c: /* CM_ICLKEN4_CORE */
+ return s->clken[4];
+
+ case 0x220: /* CM_IDLEST1_CORE */
+ /* TODO: check the actual iclk status */
+ return 0x7ffffff9;
+ case 0x224: /* CM_IDLEST2_CORE */
+ /* TODO: check the actual iclk status */
+ return 0x00000007;
+ case 0x22c: /* CM_IDLEST4_CORE */
+ /* TODO: check the actual iclk status */
+ return 0x0000001f;
+
+ case 0x230: /* CM_AUTOIDLE1_CORE */
+ return s->clkidle[0];
+ case 0x234: /* CM_AUTOIDLE2_CORE */
+ return s->clkidle[1];
+ case 0x238: /* CM_AUTOIDLE3_CORE */
+ return s->clkidle[2];
+ case 0x23c: /* CM_AUTOIDLE4_CORE */
+ return s->clkidle[3];
+
+ case 0x240: /* CM_CLKSEL1_CORE */
+ return s->clksel[1];
+ case 0x244: /* CM_CLKSEL2_CORE */
+ return s->clksel[2];
+
+ case 0x248: /* CM_CLKSTCTRL_CORE */
+ return s->clkctrl[1];
+
+ case 0x2a0: /* PM_WKEN1_CORE */
+ return s->wken[0];
+ case 0x2a4: /* PM_WKEN2_CORE */
+ return s->wken[1];
+
+ case 0x2b0: /* PM_WKST1_CORE */
+ return s->wkst[0];
+ case 0x2b4: /* PM_WKST2_CORE */
+ return s->wkst[1];
+ case 0x2c8: /* PM_WKDEP_CORE */
+ return 0x1e;
+
+ case 0x2e0: /* PM_PWSTCTRL_CORE */
+ return s->power[1];
+ case 0x2e4: /* PM_PWSTST_CORE */
+ return 0x000030 | (s->power[1] & 0xfc00);
+
+ case 0x300: /* CM_FCLKEN_GFX */
+ return s->clken[5];
+ case 0x310: /* CM_ICLKEN_GFX */
+ return s->clken[6];
+ case 0x320: /* CM_IDLEST_GFX */
+ /* TODO: check the actual iclk status */
+ return 0x00000001;
+ case 0x340: /* CM_CLKSEL_GFX */
+ return s->clksel[3];
+ case 0x348: /* CM_CLKSTCTRL_GFX */
+ return s->clkctrl[2];
+ case 0x350: /* RM_RSTCTRL_GFX */
+ return s->rstctrl[0];
+ case 0x358: /* RM_RSTST_GFX */
+ return s->rst[1];
+ case 0x3c8: /* PM_WKDEP_GFX */
+ return s->wkup[1];
+
+ case 0x3e0: /* PM_PWSTCTRL_GFX */
+ return s->power[2];
+ case 0x3e4: /* PM_PWSTST_GFX */
+ return s->power[2] & 3;
+
+ case 0x400: /* CM_FCLKEN_WKUP */
+ return s->clken[7];
+ case 0x410: /* CM_ICLKEN_WKUP */
+ return s->clken[8];
+ case 0x420: /* CM_IDLEST_WKUP */
+ /* TODO: check the actual iclk status */
+ return 0x0000003f;
+ case 0x430: /* CM_AUTOIDLE_WKUP */
+ return s->clkidle[4];
+ case 0x440: /* CM_CLKSEL_WKUP */
+ return s->clksel[4];
+ case 0x450: /* RM_RSTCTRL_WKUP */
+ return 0;
+ case 0x454: /* RM_RSTTIME_WKUP */
+ return s->rsttime_wkup;
+ case 0x458: /* RM_RSTST_WKUP */
+ return s->rst[2];
+ case 0x4a0: /* PM_WKEN_WKUP */
+ return s->wken[2];
+ case 0x4b0: /* PM_WKST_WKUP */
+ return s->wkst[2];
+
+ case 0x500: /* CM_CLKEN_PLL */
+ return s->clken[9];
+ case 0x520: /* CM_IDLEST_CKGEN */
+ ret = 0x0000070 | (s->apll_lock[0] << 9) | (s->apll_lock[1] << 8);
+ if (!(s->clksel[6] & 3))
+ /* Core uses 32-kHz clock */
+ ret |= 3 << 0;
+ else if (!s->dpll_lock)
+ /* DPLL not locked, core uses ref_clk */
+ ret |= 1 << 0;
+ else
+ /* Core uses DPLL */
+ ret |= 2 << 0;
+ return ret;
+ case 0x530: /* CM_AUTOIDLE_PLL */
+ return s->clkidle[5];
+ case 0x540: /* CM_CLKSEL1_PLL */
+ return s->clksel[5];
+ case 0x544: /* CM_CLKSEL2_PLL */
+ return s->clksel[6];
+
+ case 0x800: /* CM_FCLKEN_DSP */
+ return s->clken[10];
+ case 0x810: /* CM_ICLKEN_DSP */
+ return s->clken[11];
+ case 0x820: /* CM_IDLEST_DSP */
+ /* TODO: check the actual iclk status */
+ return 0x00000103;
+ case 0x830: /* CM_AUTOIDLE_DSP */
+ return s->clkidle[6];
+ case 0x840: /* CM_CLKSEL_DSP */
+ return s->clksel[7];
+ case 0x848: /* CM_CLKSTCTRL_DSP */
+ return s->clkctrl[3];
+ case 0x850: /* RM_RSTCTRL_DSP */
+ return 0;
+ case 0x858: /* RM_RSTST_DSP */
+ return s->rst[3];
+ case 0x8c8: /* PM_WKDEP_DSP */
+ return s->wkup[2];
+ case 0x8e0: /* PM_PWSTCTRL_DSP */
+ return s->power[3];
+ case 0x8e4: /* PM_PWSTST_DSP */
+ return 0x008030 | (s->power[3] & 0x3003);
+
+ case 0x8f0: /* PRCM_IRQSTATUS_DSP */
+ return s->irqst[1];
+ case 0x8f4: /* PRCM_IRQENABLE_DSP */
+ return s->irqen[1];
+
+ case 0x8f8: /* PRCM_IRQSTATUS_IVA */
+ return s->irqst[2];
+ case 0x8fc: /* PRCM_IRQENABLE_IVA */
+ return s->irqen[2];
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_prcm_apll_update(struct omap_prcm_s *s)
+{
+ int mode[2];
+
+ mode[0] = (s->clken[9] >> 6) & 3;
+ s->apll_lock[0] = (mode[0] == 3);
+ mode[1] = (s->clken[9] >> 2) & 3;
+ s->apll_lock[1] = (mode[1] == 3);
+ /* TODO: update clocks */
+
+ if (mode[0] == 1 || mode[0] == 2 || mode[1] == 1 || mode[1] == 2)
+ fprintf(stderr, "%s: bad EN_54M_PLL or bad EN_96M_PLL\n",
+ __FUNCTION__);
+}
+
+static void omap_prcm_dpll_update(struct omap_prcm_s *s)
+{
+ omap_clk dpll = omap_findclk(s->mpu, "dpll");
+ omap_clk dpll_x2 = omap_findclk(s->mpu, "dpll");
+ omap_clk core = omap_findclk(s->mpu, "core_clk");
+ int mode = (s->clken[9] >> 0) & 3;
+ int mult, div;
+
+ mult = (s->clksel[5] >> 12) & 0x3ff;
+ div = (s->clksel[5] >> 8) & 0xf;
+ if (mult == 0 || mult == 1)
+ mode = 1; /* Bypass */
+
+ s->dpll_lock = 0;
+ switch (mode) {
+ case 0:
+ fprintf(stderr, "%s: bad EN_DPLL\n", __FUNCTION__);
+ break;
+ case 1: /* Low-power bypass mode (Default) */
+ case 2: /* Fast-relock bypass mode */
+ omap_clk_setrate(dpll, 1, 1);
+ omap_clk_setrate(dpll_x2, 1, 1);
+ break;
+ case 3: /* Lock mode */
+ s->dpll_lock = 1; /* After 20 FINT cycles (ref_clk / (div + 1)). */
+
+ omap_clk_setrate(dpll, div + 1, mult);
+ omap_clk_setrate(dpll_x2, div + 1, mult * 2);
+ break;
+ }
+
+ switch ((s->clksel[6] >> 0) & 3) {
+ case 0:
+ omap_clk_reparent(core, omap_findclk(s->mpu, "clk32-kHz"));
+ break;
+ case 1:
+ omap_clk_reparent(core, dpll);
+ break;
+ case 2:
+ /* Default */
+ omap_clk_reparent(core, dpll_x2);
+ break;
+ case 3:
+ fprintf(stderr, "%s: bad CORE_CLK_SRC\n", __FUNCTION__);
+ break;
+ }
+}
+
+static void omap_prcm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_prcm_s *s = (struct omap_prcm_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x000: /* PRCM_REVISION */
+ case 0x054: /* PRCM_VOLTST */
+ case 0x084: /* PRCM_CLKCFG_STATUS */
+ case 0x1e4: /* PM_PWSTST_MPU */
+ case 0x220: /* CM_IDLEST1_CORE */
+ case 0x224: /* CM_IDLEST2_CORE */
+ case 0x22c: /* CM_IDLEST4_CORE */
+ case 0x2c8: /* PM_WKDEP_CORE */
+ case 0x2e4: /* PM_PWSTST_CORE */
+ case 0x320: /* CM_IDLEST_GFX */
+ case 0x3e4: /* PM_PWSTST_GFX */
+ case 0x420: /* CM_IDLEST_WKUP */
+ case 0x520: /* CM_IDLEST_CKGEN */
+ case 0x820: /* CM_IDLEST_DSP */
+ case 0x8e4: /* PM_PWSTST_DSP */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x010: /* PRCM_SYSCONFIG */
+ s->sysconfig = value & 1;
+ break;
+
+ case 0x018: /* PRCM_IRQSTATUS_MPU */
+ s->irqst[0] &= ~value;
+ omap_prcm_int_update(s, 0);
+ break;
+ case 0x01c: /* PRCM_IRQENABLE_MPU */
+ s->irqen[0] = value & 0x3f;
+ omap_prcm_int_update(s, 0);
+ break;
+
+ case 0x050: /* PRCM_VOLTCTRL */
+ s->voltctrl = value & 0xf1c3;
+ break;
+
+ case 0x060: /* PRCM_CLKSRC_CTRL */
+ s->clksrc[0] = value & 0xdb;
+ /* TODO update clocks */
+ break;
+
+ case 0x070: /* PRCM_CLKOUT_CTRL */
+ s->clkout[0] = value & 0xbbbb;
+ /* TODO update clocks */
+ break;
+
+ case 0x078: /* PRCM_CLKEMUL_CTRL */
+ s->clkemul[0] = value & 1;
+ /* TODO update clocks */
+ break;
+
+ case 0x080: /* PRCM_CLKCFG_CTRL */
+ break;
+
+ case 0x090: /* PRCM_VOLTSETUP */
+ s->setuptime[0] = value & 0xffff;
+ break;
+ case 0x094: /* PRCM_CLKSSETUP */
+ s->setuptime[1] = value & 0xffff;
+ break;
+
+ case 0x098: /* PRCM_POLCTRL */
+ s->clkpol[0] = value & 0x701;
+ break;
+
+ case 0x0b0: /* GENERAL_PURPOSE1 */
+ case 0x0b4: /* GENERAL_PURPOSE2 */
+ case 0x0b8: /* GENERAL_PURPOSE3 */
+ case 0x0bc: /* GENERAL_PURPOSE4 */
+ case 0x0c0: /* GENERAL_PURPOSE5 */
+ case 0x0c4: /* GENERAL_PURPOSE6 */
+ case 0x0c8: /* GENERAL_PURPOSE7 */
+ case 0x0cc: /* GENERAL_PURPOSE8 */
+ case 0x0d0: /* GENERAL_PURPOSE9 */
+ case 0x0d4: /* GENERAL_PURPOSE10 */
+ case 0x0d8: /* GENERAL_PURPOSE11 */
+ case 0x0dc: /* GENERAL_PURPOSE12 */
+ case 0x0e0: /* GENERAL_PURPOSE13 */
+ case 0x0e4: /* GENERAL_PURPOSE14 */
+ case 0x0e8: /* GENERAL_PURPOSE15 */
+ case 0x0ec: /* GENERAL_PURPOSE16 */
+ case 0x0f0: /* GENERAL_PURPOSE17 */
+ case 0x0f4: /* GENERAL_PURPOSE18 */
+ case 0x0f8: /* GENERAL_PURPOSE19 */
+ case 0x0fc: /* GENERAL_PURPOSE20 */
+ s->scratch[(addr - 0xb0) >> 2] = value;
+ break;
+
+ case 0x140: /* CM_CLKSEL_MPU */
+ s->clksel[0] = value & 0x1f;
+ /* TODO update clocks */
+ break;
+ case 0x148: /* CM_CLKSTCTRL_MPU */
+ s->clkctrl[0] = value & 0x1f;
+ break;
+
+ case 0x158: /* RM_RSTST_MPU */
+ s->rst[0] &= ~value;
+ break;
+ case 0x1c8: /* PM_WKDEP_MPU */
+ s->wkup[0] = value & 0x15;
+ break;
+
+ case 0x1d4: /* PM_EVGENCTRL_MPU */
+ s->ev = value & 0x1f;
+ break;
+ case 0x1d8: /* PM_EVEGENONTIM_MPU */
+ s->evtime[0] = value;
+ break;
+ case 0x1dc: /* PM_EVEGENOFFTIM_MPU */
+ s->evtime[1] = value;
+ break;
+
+ case 0x1e0: /* PM_PWSTCTRL_MPU */
+ s->power[0] = value & 0xc0f;
+ break;
+
+ case 0x200: /* CM_FCLKEN1_CORE */
+ s->clken[0] = value & 0xbfffffff;
+ /* TODO update clocks */
+ /* The EN_EAC bit only gets/puts func_96m_clk. */
+ break;
+ case 0x204: /* CM_FCLKEN2_CORE */
+ s->clken[1] = value & 0x00000007;
+ /* TODO update clocks */
+ break;
+ case 0x210: /* CM_ICLKEN1_CORE */
+ s->clken[2] = value & 0xfffffff9;
+ /* TODO update clocks */
+ /* The EN_EAC bit only gets/puts core_l4_iclk. */
+ break;
+ case 0x214: /* CM_ICLKEN2_CORE */
+ s->clken[3] = value & 0x00000007;
+ /* TODO update clocks */
+ break;
+ case 0x21c: /* CM_ICLKEN4_CORE */
+ s->clken[4] = value & 0x0000001f;
+ /* TODO update clocks */
+ break;
+
+ case 0x230: /* CM_AUTOIDLE1_CORE */
+ s->clkidle[0] = value & 0xfffffff9;
+ /* TODO update clocks */
+ break;
+ case 0x234: /* CM_AUTOIDLE2_CORE */
+ s->clkidle[1] = value & 0x00000007;
+ /* TODO update clocks */
+ break;
+ case 0x238: /* CM_AUTOIDLE3_CORE */
+ s->clkidle[2] = value & 0x00000007;
+ /* TODO update clocks */
+ break;
+ case 0x23c: /* CM_AUTOIDLE4_CORE */
+ s->clkidle[3] = value & 0x0000001f;
+ /* TODO update clocks */
+ break;
+
+ case 0x240: /* CM_CLKSEL1_CORE */
+ s->clksel[1] = value & 0x0fffbf7f;
+ /* TODO update clocks */
+ break;
+
+ case 0x244: /* CM_CLKSEL2_CORE */
+ s->clksel[2] = value & 0x00fffffc;
+ /* TODO update clocks */
+ break;
+
+ case 0x248: /* CM_CLKSTCTRL_CORE */
+ s->clkctrl[1] = value & 0x7;
+ break;
+
+ case 0x2a0: /* PM_WKEN1_CORE */
+ s->wken[0] = value & 0x04667ff8;
+ break;
+ case 0x2a4: /* PM_WKEN2_CORE */
+ s->wken[1] = value & 0x00000005;
+ break;
+
+ case 0x2b0: /* PM_WKST1_CORE */
+ s->wkst[0] &= ~value;
+ break;
+ case 0x2b4: /* PM_WKST2_CORE */
+ s->wkst[1] &= ~value;
+ break;
+
+ case 0x2e0: /* PM_PWSTCTRL_CORE */
+ s->power[1] = (value & 0x00fc3f) | (1 << 2);
+ break;
+
+ case 0x300: /* CM_FCLKEN_GFX */
+ s->clken[5] = value & 6;
+ /* TODO update clocks */
+ break;
+ case 0x310: /* CM_ICLKEN_GFX */
+ s->clken[6] = value & 1;
+ /* TODO update clocks */
+ break;
+ case 0x340: /* CM_CLKSEL_GFX */
+ s->clksel[3] = value & 7;
+ /* TODO update clocks */
+ break;
+ case 0x348: /* CM_CLKSTCTRL_GFX */
+ s->clkctrl[2] = value & 1;
+ break;
+ case 0x350: /* RM_RSTCTRL_GFX */
+ s->rstctrl[0] = value & 1;
+ /* TODO: reset */
+ break;
+ case 0x358: /* RM_RSTST_GFX */
+ s->rst[1] &= ~value;
+ break;
+ case 0x3c8: /* PM_WKDEP_GFX */
+ s->wkup[1] = value & 0x13;
+ break;
+ case 0x3e0: /* PM_PWSTCTRL_GFX */
+ s->power[2] = (value & 0x00c0f) | (3 << 2);
+ break;
+
+ case 0x400: /* CM_FCLKEN_WKUP */
+ s->clken[7] = value & 0xd;
+ /* TODO update clocks */
+ break;
+ case 0x410: /* CM_ICLKEN_WKUP */
+ s->clken[8] = value & 0x3f;
+ /* TODO update clocks */
+ break;
+ case 0x430: /* CM_AUTOIDLE_WKUP */
+ s->clkidle[4] = value & 0x0000003f;
+ /* TODO update clocks */
+ break;
+ case 0x440: /* CM_CLKSEL_WKUP */
+ s->clksel[4] = value & 3;
+ /* TODO update clocks */
+ break;
+ case 0x450: /* RM_RSTCTRL_WKUP */
+ /* TODO: reset */
+ if (value & 2)
+ qemu_system_reset_request();
+ break;
+ case 0x454: /* RM_RSTTIME_WKUP */
+ s->rsttime_wkup = value & 0x1fff;
+ break;
+ case 0x458: /* RM_RSTST_WKUP */
+ s->rst[2] &= ~value;
+ break;
+ case 0x4a0: /* PM_WKEN_WKUP */
+ s->wken[2] = value & 0x00000005;
+ break;
+ case 0x4b0: /* PM_WKST_WKUP */
+ s->wkst[2] &= ~value;
+ break;
+
+ case 0x500: /* CM_CLKEN_PLL */
+ if (value & 0xffffff30)
+ fprintf(stderr, "%s: write 0s in CM_CLKEN_PLL for "
+ "future compatibility\n", __FUNCTION__);
+ if ((s->clken[9] ^ value) & 0xcc) {
+ s->clken[9] &= ~0xcc;
+ s->clken[9] |= value & 0xcc;
+ omap_prcm_apll_update(s);
+ }
+ if ((s->clken[9] ^ value) & 3) {
+ s->clken[9] &= ~3;
+ s->clken[9] |= value & 3;
+ omap_prcm_dpll_update(s);
+ }
+ break;
+ case 0x530: /* CM_AUTOIDLE_PLL */
+ s->clkidle[5] = value & 0x000000cf;
+ /* TODO update clocks */
+ break;
+ case 0x540: /* CM_CLKSEL1_PLL */
+ if (value & 0xfc4000d7)
+ fprintf(stderr, "%s: write 0s in CM_CLKSEL1_PLL for "
+ "future compatibility\n", __FUNCTION__);
+ if ((s->clksel[5] ^ value) & 0x003fff00) {
+ s->clksel[5] = value & 0x03bfff28;
+ omap_prcm_dpll_update(s);
+ }
+ /* TODO update the other clocks */
+
+ s->clksel[5] = value & 0x03bfff28;
+ break;
+ case 0x544: /* CM_CLKSEL2_PLL */
+ if (value & ~3)
+ fprintf(stderr, "%s: write 0s in CM_CLKSEL2_PLL[31:2] for "
+ "future compatibility\n", __FUNCTION__);
+ if (s->clksel[6] != (value & 3)) {
+ s->clksel[6] = value & 3;
+ omap_prcm_dpll_update(s);
+ }
+ break;
+
+ case 0x800: /* CM_FCLKEN_DSP */
+ s->clken[10] = value & 0x501;
+ /* TODO update clocks */
+ break;
+ case 0x810: /* CM_ICLKEN_DSP */
+ s->clken[11] = value & 0x2;
+ /* TODO update clocks */
+ break;
+ case 0x830: /* CM_AUTOIDLE_DSP */
+ s->clkidle[6] = value & 0x2;
+ /* TODO update clocks */
+ break;
+ case 0x840: /* CM_CLKSEL_DSP */
+ s->clksel[7] = value & 0x3fff;
+ /* TODO update clocks */
+ break;
+ case 0x848: /* CM_CLKSTCTRL_DSP */
+ s->clkctrl[3] = value & 0x101;
+ break;
+ case 0x850: /* RM_RSTCTRL_DSP */
+ /* TODO: reset */
+ break;
+ case 0x858: /* RM_RSTST_DSP */
+ s->rst[3] &= ~value;
+ break;
+ case 0x8c8: /* PM_WKDEP_DSP */
+ s->wkup[2] = value & 0x13;
+ break;
+ case 0x8e0: /* PM_PWSTCTRL_DSP */
+ s->power[3] = (value & 0x03017) | (3 << 2);
+ break;
+
+ case 0x8f0: /* PRCM_IRQSTATUS_DSP */
+ s->irqst[1] &= ~value;
+ omap_prcm_int_update(s, 1);
+ break;
+ case 0x8f4: /* PRCM_IRQENABLE_DSP */
+ s->irqen[1] = value & 0x7;
+ omap_prcm_int_update(s, 1);
+ break;
+
+ case 0x8f8: /* PRCM_IRQSTATUS_IVA */
+ s->irqst[2] &= ~value;
+ omap_prcm_int_update(s, 2);
+ break;
+ case 0x8fc: /* PRCM_IRQENABLE_IVA */
+ s->irqen[2] = value & 0x7;
+ omap_prcm_int_update(s, 2);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_prcm_ops = {
+ .read = omap_prcm_read,
+ .write = omap_prcm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_prcm_reset(struct omap_prcm_s *s)
+{
+ s->sysconfig = 0;
+ s->irqst[0] = 0;
+ s->irqst[1] = 0;
+ s->irqst[2] = 0;
+ s->irqen[0] = 0;
+ s->irqen[1] = 0;
+ s->irqen[2] = 0;
+ s->voltctrl = 0x1040;
+ s->ev = 0x14;
+ s->evtime[0] = 0;
+ s->evtime[1] = 0;
+ s->clkctrl[0] = 0;
+ s->clkctrl[1] = 0;
+ s->clkctrl[2] = 0;
+ s->clkctrl[3] = 0;
+ s->clken[1] = 7;
+ s->clken[3] = 7;
+ s->clken[4] = 0;
+ s->clken[5] = 0;
+ s->clken[6] = 0;
+ s->clken[7] = 0xc;
+ s->clken[8] = 0x3e;
+ s->clken[9] = 0x0d;
+ s->clken[10] = 0;
+ s->clken[11] = 0;
+ s->clkidle[0] = 0;
+ s->clkidle[2] = 7;
+ s->clkidle[3] = 0;
+ s->clkidle[4] = 0;
+ s->clkidle[5] = 0x0c;
+ s->clkidle[6] = 0;
+ s->clksel[0] = 0x01;
+ s->clksel[1] = 0x02100121;
+ s->clksel[2] = 0x00000000;
+ s->clksel[3] = 0x01;
+ s->clksel[4] = 0;
+ s->clksel[7] = 0x0121;
+ s->wkup[0] = 0x15;
+ s->wkup[1] = 0x13;
+ s->wkup[2] = 0x13;
+ s->wken[0] = 0x04667ff8;
+ s->wken[1] = 0x00000005;
+ s->wken[2] = 5;
+ s->wkst[0] = 0;
+ s->wkst[1] = 0;
+ s->wkst[2] = 0;
+ s->power[0] = 0x00c;
+ s->power[1] = 4;
+ s->power[2] = 0x0000c;
+ s->power[3] = 0x14;
+ s->rstctrl[0] = 1;
+ s->rst[3] = 1;
+ omap_prcm_apll_update(s);
+ omap_prcm_dpll_update(s);
+}
+
+static void omap_prcm_coldreset(struct omap_prcm_s *s)
+{
+ s->setuptime[0] = 0;
+ s->setuptime[1] = 0;
+ memset(&s->scratch, 0, sizeof(s->scratch));
+ s->rst[0] = 0x01;
+ s->rst[1] = 0x00;
+ s->rst[2] = 0x01;
+ s->clken[0] = 0;
+ s->clken[2] = 0;
+ s->clkidle[1] = 0;
+ s->clksel[5] = 0;
+ s->clksel[6] = 2;
+ s->clksrc[0] = 0x43;
+ s->clkout[0] = 0x0303;
+ s->clkemul[0] = 0;
+ s->clkpol[0] = 0x100;
+ s->rsttime_wkup = 0x1002;
+
+ omap_prcm_reset(s);
+}
+
+static struct omap_prcm_s *omap_prcm_init(struct omap_target_agent_s *ta,
+ qemu_irq mpu_int, qemu_irq dsp_int, qemu_irq iva_int,
+ struct omap_mpu_state_s *mpu)
+{
+ struct omap_prcm_s *s = (struct omap_prcm_s *)
+ g_malloc0(sizeof(struct omap_prcm_s));
+
+ s->irq[0] = mpu_int;
+ s->irq[1] = dsp_int;
+ s->irq[2] = iva_int;
+ s->mpu = mpu;
+ omap_prcm_coldreset(s);
+
+ memory_region_init_io(&s->iomem0, NULL, &omap_prcm_ops, s, "omap.pcrm0",
+ omap_l4_region_size(ta, 0));
+ memory_region_init_io(&s->iomem1, NULL, &omap_prcm_ops, s, "omap.pcrm1",
+ omap_l4_region_size(ta, 1));
+ omap_l4_attach(ta, 0, &s->iomem0);
+ omap_l4_attach(ta, 1, &s->iomem1);
+
+ return s;
+}
+
+/* System and Pinout control */
+struct omap_sysctl_s {
+ struct omap_mpu_state_s *mpu;
+ MemoryRegion iomem;
+
+ uint32_t sysconfig;
+ uint32_t devconfig;
+ uint32_t psaconfig;
+ uint32_t padconf[0x45];
+ uint8_t obs;
+ uint32_t msuspendmux[5];
+};
+
+static uint32_t omap_sysctl_read8(void *opaque, hwaddr addr)
+{
+
+ struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque;
+ int pad_offset, byte_offset;
+ int value;
+
+ switch (addr) {
+ case 0x030 ... 0x140: /* CONTROL_PADCONF - only used in the POP */
+ pad_offset = (addr - 0x30) >> 2;
+ byte_offset = (addr - 0x30) & (4 - 1);
+
+ value = s->padconf[pad_offset];
+ value = (value >> (byte_offset * 8)) & 0xff;
+
+ return value;
+
+ default:
+ break;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static uint32_t omap_sysctl_read(void *opaque, hwaddr addr)
+{
+ struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque;
+
+ switch (addr) {
+ case 0x000: /* CONTROL_REVISION */
+ return 0x20;
+
+ case 0x010: /* CONTROL_SYSCONFIG */
+ return s->sysconfig;
+
+ case 0x030 ... 0x140: /* CONTROL_PADCONF - only used in the POP */
+ return s->padconf[(addr - 0x30) >> 2];
+
+ case 0x270: /* CONTROL_DEBOBS */
+ return s->obs;
+
+ case 0x274: /* CONTROL_DEVCONF */
+ return s->devconfig;
+
+ case 0x28c: /* CONTROL_EMU_SUPPORT */
+ return 0;
+
+ case 0x290: /* CONTROL_MSUSPENDMUX_0 */
+ return s->msuspendmux[0];
+ case 0x294: /* CONTROL_MSUSPENDMUX_1 */
+ return s->msuspendmux[1];
+ case 0x298: /* CONTROL_MSUSPENDMUX_2 */
+ return s->msuspendmux[2];
+ case 0x29c: /* CONTROL_MSUSPENDMUX_3 */
+ return s->msuspendmux[3];
+ case 0x2a0: /* CONTROL_MSUSPENDMUX_4 */
+ return s->msuspendmux[4];
+ case 0x2a4: /* CONTROL_MSUSPENDMUX_5 */
+ return 0;
+
+ case 0x2b8: /* CONTROL_PSA_CTRL */
+ return s->psaconfig;
+ case 0x2bc: /* CONTROL_PSA_CMD */
+ case 0x2c0: /* CONTROL_PSA_VALUE */
+ return 0;
+
+ case 0x2b0: /* CONTROL_SEC_CTRL */
+ return 0x800000f1;
+ case 0x2d0: /* CONTROL_SEC_EMU */
+ return 0x80000015;
+ case 0x2d4: /* CONTROL_SEC_TAP */
+ return 0x8000007f;
+ case 0x2b4: /* CONTROL_SEC_TEST */
+ case 0x2f0: /* CONTROL_SEC_STATUS */
+ case 0x2f4: /* CONTROL_SEC_ERR_STATUS */
+ /* Secure mode is not present on general-pusrpose device. Outside
+ * secure mode these values cannot be read or written. */
+ return 0;
+
+ case 0x2d8: /* CONTROL_OCM_RAM_PERM */
+ return 0xff;
+ case 0x2dc: /* CONTROL_OCM_PUB_RAM_ADD */
+ case 0x2e0: /* CONTROL_EXT_SEC_RAM_START_ADD */
+ case 0x2e4: /* CONTROL_EXT_SEC_RAM_STOP_ADD */
+ /* No secure mode so no Extended Secure RAM present. */
+ return 0;
+
+ case 0x2f8: /* CONTROL_STATUS */
+ /* Device Type => General-purpose */
+ return 0x0300;
+ case 0x2fc: /* CONTROL_GENERAL_PURPOSE_STATUS */
+
+ case 0x300: /* CONTROL_RPUB_KEY_H_0 */
+ case 0x304: /* CONTROL_RPUB_KEY_H_1 */
+ case 0x308: /* CONTROL_RPUB_KEY_H_2 */
+ case 0x30c: /* CONTROL_RPUB_KEY_H_3 */
+ return 0xdecafbad;
+
+ case 0x310: /* CONTROL_RAND_KEY_0 */
+ case 0x314: /* CONTROL_RAND_KEY_1 */
+ case 0x318: /* CONTROL_RAND_KEY_2 */
+ case 0x31c: /* CONTROL_RAND_KEY_3 */
+ case 0x320: /* CONTROL_CUST_KEY_0 */
+ case 0x324: /* CONTROL_CUST_KEY_1 */
+ case 0x330: /* CONTROL_TEST_KEY_0 */
+ case 0x334: /* CONTROL_TEST_KEY_1 */
+ case 0x338: /* CONTROL_TEST_KEY_2 */
+ case 0x33c: /* CONTROL_TEST_KEY_3 */
+ case 0x340: /* CONTROL_TEST_KEY_4 */
+ case 0x344: /* CONTROL_TEST_KEY_5 */
+ case 0x348: /* CONTROL_TEST_KEY_6 */
+ case 0x34c: /* CONTROL_TEST_KEY_7 */
+ case 0x350: /* CONTROL_TEST_KEY_8 */
+ case 0x354: /* CONTROL_TEST_KEY_9 */
+ /* Can only be accessed in secure mode and when C_FieldAccEnable
+ * bit is set in CONTROL_SEC_CTRL.
+ * TODO: otherwise an interconnect access error is generated. */
+ return 0;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_sysctl_write8(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque;
+ int pad_offset, byte_offset;
+ int prev_value;
+
+ switch (addr) {
+ case 0x030 ... 0x140: /* CONTROL_PADCONF - only used in the POP */
+ pad_offset = (addr - 0x30) >> 2;
+ byte_offset = (addr - 0x30) & (4 - 1);
+
+ prev_value = s->padconf[pad_offset];
+ prev_value &= ~(0xff << (byte_offset * 8));
+ prev_value |= ((value & 0x1f1f1f1f) << (byte_offset * 8)) & 0x1f1f1f1f;
+ s->padconf[pad_offset] = prev_value;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ break;
+ }
+}
+
+static void omap_sysctl_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque;
+
+ switch (addr) {
+ case 0x000: /* CONTROL_REVISION */
+ case 0x2a4: /* CONTROL_MSUSPENDMUX_5 */
+ case 0x2c0: /* CONTROL_PSA_VALUE */
+ case 0x2f8: /* CONTROL_STATUS */
+ case 0x2fc: /* CONTROL_GENERAL_PURPOSE_STATUS */
+ case 0x300: /* CONTROL_RPUB_KEY_H_0 */
+ case 0x304: /* CONTROL_RPUB_KEY_H_1 */
+ case 0x308: /* CONTROL_RPUB_KEY_H_2 */
+ case 0x30c: /* CONTROL_RPUB_KEY_H_3 */
+ case 0x310: /* CONTROL_RAND_KEY_0 */
+ case 0x314: /* CONTROL_RAND_KEY_1 */
+ case 0x318: /* CONTROL_RAND_KEY_2 */
+ case 0x31c: /* CONTROL_RAND_KEY_3 */
+ case 0x320: /* CONTROL_CUST_KEY_0 */
+ case 0x324: /* CONTROL_CUST_KEY_1 */
+ case 0x330: /* CONTROL_TEST_KEY_0 */
+ case 0x334: /* CONTROL_TEST_KEY_1 */
+ case 0x338: /* CONTROL_TEST_KEY_2 */
+ case 0x33c: /* CONTROL_TEST_KEY_3 */
+ case 0x340: /* CONTROL_TEST_KEY_4 */
+ case 0x344: /* CONTROL_TEST_KEY_5 */
+ case 0x348: /* CONTROL_TEST_KEY_6 */
+ case 0x34c: /* CONTROL_TEST_KEY_7 */
+ case 0x350: /* CONTROL_TEST_KEY_8 */
+ case 0x354: /* CONTROL_TEST_KEY_9 */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x010: /* CONTROL_SYSCONFIG */
+ s->sysconfig = value & 0x1e;
+ break;
+
+ case 0x030 ... 0x140: /* CONTROL_PADCONF - only used in the POP */
+ /* XXX: should check constant bits */
+ s->padconf[(addr - 0x30) >> 2] = value & 0x1f1f1f1f;
+ break;
+
+ case 0x270: /* CONTROL_DEBOBS */
+ s->obs = value & 0xff;
+ break;
+
+ case 0x274: /* CONTROL_DEVCONF */
+ s->devconfig = value & 0xffffc7ff;
+ break;
+
+ case 0x28c: /* CONTROL_EMU_SUPPORT */
+ break;
+
+ case 0x290: /* CONTROL_MSUSPENDMUX_0 */
+ s->msuspendmux[0] = value & 0x3fffffff;
+ break;
+ case 0x294: /* CONTROL_MSUSPENDMUX_1 */
+ s->msuspendmux[1] = value & 0x3fffffff;
+ break;
+ case 0x298: /* CONTROL_MSUSPENDMUX_2 */
+ s->msuspendmux[2] = value & 0x3fffffff;
+ break;
+ case 0x29c: /* CONTROL_MSUSPENDMUX_3 */
+ s->msuspendmux[3] = value & 0x3fffffff;
+ break;
+ case 0x2a0: /* CONTROL_MSUSPENDMUX_4 */
+ s->msuspendmux[4] = value & 0x3fffffff;
+ break;
+
+ case 0x2b8: /* CONTROL_PSA_CTRL */
+ s->psaconfig = value & 0x1c;
+ s->psaconfig |= (value & 0x20) ? 2 : 1;
+ break;
+ case 0x2bc: /* CONTROL_PSA_CMD */
+ break;
+
+ case 0x2b0: /* CONTROL_SEC_CTRL */
+ case 0x2b4: /* CONTROL_SEC_TEST */
+ case 0x2d0: /* CONTROL_SEC_EMU */
+ case 0x2d4: /* CONTROL_SEC_TAP */
+ case 0x2d8: /* CONTROL_OCM_RAM_PERM */
+ case 0x2dc: /* CONTROL_OCM_PUB_RAM_ADD */
+ case 0x2e0: /* CONTROL_EXT_SEC_RAM_START_ADD */
+ case 0x2e4: /* CONTROL_EXT_SEC_RAM_STOP_ADD */
+ case 0x2f0: /* CONTROL_SEC_STATUS */
+ case 0x2f4: /* CONTROL_SEC_ERR_STATUS */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_sysctl_ops = {
+ .old_mmio = {
+ .read = {
+ omap_sysctl_read8,
+ omap_badwidth_read32, /* TODO */
+ omap_sysctl_read,
+ },
+ .write = {
+ omap_sysctl_write8,
+ omap_badwidth_write32, /* TODO */
+ omap_sysctl_write,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_sysctl_reset(struct omap_sysctl_s *s)
+{
+ /* (power-on reset) */
+ s->sysconfig = 0;
+ s->obs = 0;
+ s->devconfig = 0x0c000000;
+ s->msuspendmux[0] = 0x00000000;
+ s->msuspendmux[1] = 0x00000000;
+ s->msuspendmux[2] = 0x00000000;
+ s->msuspendmux[3] = 0x00000000;
+ s->msuspendmux[4] = 0x00000000;
+ s->psaconfig = 1;
+
+ s->padconf[0x00] = 0x000f0f0f;
+ s->padconf[0x01] = 0x00000000;
+ s->padconf[0x02] = 0x00000000;
+ s->padconf[0x03] = 0x00000000;
+ s->padconf[0x04] = 0x00000000;
+ s->padconf[0x05] = 0x00000000;
+ s->padconf[0x06] = 0x00000000;
+ s->padconf[0x07] = 0x00000000;
+ s->padconf[0x08] = 0x08080800;
+ s->padconf[0x09] = 0x08080808;
+ s->padconf[0x0a] = 0x08080808;
+ s->padconf[0x0b] = 0x08080808;
+ s->padconf[0x0c] = 0x08080808;
+ s->padconf[0x0d] = 0x08080800;
+ s->padconf[0x0e] = 0x08080808;
+ s->padconf[0x0f] = 0x08080808;
+ s->padconf[0x10] = 0x18181808; /* | 0x07070700 if SBoot3 */
+ s->padconf[0x11] = 0x18181818; /* | 0x07070707 if SBoot3 */
+ s->padconf[0x12] = 0x18181818; /* | 0x07070707 if SBoot3 */
+ s->padconf[0x13] = 0x18181818; /* | 0x07070707 if SBoot3 */
+ s->padconf[0x14] = 0x18181818; /* | 0x00070707 if SBoot3 */
+ s->padconf[0x15] = 0x18181818;
+ s->padconf[0x16] = 0x18181818; /* | 0x07000000 if SBoot3 */
+ s->padconf[0x17] = 0x1f001f00;
+ s->padconf[0x18] = 0x1f1f1f1f;
+ s->padconf[0x19] = 0x00000000;
+ s->padconf[0x1a] = 0x1f180000;
+ s->padconf[0x1b] = 0x00001f1f;
+ s->padconf[0x1c] = 0x1f001f00;
+ s->padconf[0x1d] = 0x00000000;
+ s->padconf[0x1e] = 0x00000000;
+ s->padconf[0x1f] = 0x08000000;
+ s->padconf[0x20] = 0x08080808;
+ s->padconf[0x21] = 0x08080808;
+ s->padconf[0x22] = 0x0f080808;
+ s->padconf[0x23] = 0x0f0f0f0f;
+ s->padconf[0x24] = 0x000f0f0f;
+ s->padconf[0x25] = 0x1f1f1f0f;
+ s->padconf[0x26] = 0x080f0f1f;
+ s->padconf[0x27] = 0x070f1808;
+ s->padconf[0x28] = 0x0f070707;
+ s->padconf[0x29] = 0x000f0f1f;
+ s->padconf[0x2a] = 0x0f0f0f1f;
+ s->padconf[0x2b] = 0x08000000;
+ s->padconf[0x2c] = 0x0000001f;
+ s->padconf[0x2d] = 0x0f0f1f00;
+ s->padconf[0x2e] = 0x1f1f0f0f;
+ s->padconf[0x2f] = 0x0f1f1f1f;
+ s->padconf[0x30] = 0x0f0f0f0f;
+ s->padconf[0x31] = 0x0f1f0f1f;
+ s->padconf[0x32] = 0x0f0f0f0f;
+ s->padconf[0x33] = 0x0f1f0f1f;
+ s->padconf[0x34] = 0x1f1f0f0f;
+ s->padconf[0x35] = 0x0f0f1f1f;
+ s->padconf[0x36] = 0x0f0f1f0f;
+ s->padconf[0x37] = 0x0f0f0f0f;
+ s->padconf[0x38] = 0x1f18180f;
+ s->padconf[0x39] = 0x1f1f1f1f;
+ s->padconf[0x3a] = 0x00001f1f;
+ s->padconf[0x3b] = 0x00000000;
+ s->padconf[0x3c] = 0x00000000;
+ s->padconf[0x3d] = 0x0f0f0f0f;
+ s->padconf[0x3e] = 0x18000f0f;
+ s->padconf[0x3f] = 0x00070000;
+ s->padconf[0x40] = 0x00000707;
+ s->padconf[0x41] = 0x0f1f0700;
+ s->padconf[0x42] = 0x1f1f070f;
+ s->padconf[0x43] = 0x0008081f;
+ s->padconf[0x44] = 0x00000800;
+}
+
+static struct omap_sysctl_s *omap_sysctl_init(struct omap_target_agent_s *ta,
+ omap_clk iclk, struct omap_mpu_state_s *mpu)
+{
+ struct omap_sysctl_s *s = (struct omap_sysctl_s *)
+ g_malloc0(sizeof(struct omap_sysctl_s));
+
+ s->mpu = mpu;
+ omap_sysctl_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_sysctl_ops, s, "omap.sysctl",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ return s;
+}
+
+/* General chip reset */
+static void omap2_mpu_reset(void *opaque)
+{
+ struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque;
+
+ omap_dma_reset(mpu->dma);
+ omap_prcm_reset(mpu->prcm);
+ omap_sysctl_reset(mpu->sysc);
+ omap_gp_timer_reset(mpu->gptimer[0]);
+ omap_gp_timer_reset(mpu->gptimer[1]);
+ omap_gp_timer_reset(mpu->gptimer[2]);
+ omap_gp_timer_reset(mpu->gptimer[3]);
+ omap_gp_timer_reset(mpu->gptimer[4]);
+ omap_gp_timer_reset(mpu->gptimer[5]);
+ omap_gp_timer_reset(mpu->gptimer[6]);
+ omap_gp_timer_reset(mpu->gptimer[7]);
+ omap_gp_timer_reset(mpu->gptimer[8]);
+ omap_gp_timer_reset(mpu->gptimer[9]);
+ omap_gp_timer_reset(mpu->gptimer[10]);
+ omap_gp_timer_reset(mpu->gptimer[11]);
+ omap_synctimer_reset(mpu->synctimer);
+ omap_sdrc_reset(mpu->sdrc);
+ omap_gpmc_reset(mpu->gpmc);
+ omap_dss_reset(mpu->dss);
+ omap_uart_reset(mpu->uart[0]);
+ omap_uart_reset(mpu->uart[1]);
+ omap_uart_reset(mpu->uart[2]);
+ omap_mmc_reset(mpu->mmc);
+ omap_mcspi_reset(mpu->mcspi[0]);
+ omap_mcspi_reset(mpu->mcspi[1]);
+ cpu_reset(CPU(mpu->cpu));
+}
+
+static int omap2_validate_addr(struct omap_mpu_state_s *s,
+ hwaddr addr)
+{
+ return 1;
+}
+
+static const struct dma_irq_map omap2_dma_irq_map[] = {
+ { 0, OMAP_INT_24XX_SDMA_IRQ0 },
+ { 0, OMAP_INT_24XX_SDMA_IRQ1 },
+ { 0, OMAP_INT_24XX_SDMA_IRQ2 },
+ { 0, OMAP_INT_24XX_SDMA_IRQ3 },
+};
+
+struct omap_mpu_state_s *omap2420_mpu_init(MemoryRegion *sysmem,
+ unsigned long sdram_size,
+ const char *core)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *)
+ g_malloc0(sizeof(struct omap_mpu_state_s));
+ qemu_irq dma_irqs[4];
+ DriveInfo *dinfo;
+ int i;
+ SysBusDevice *busdev;
+ struct omap_target_agent_s *ta;
+
+ /* Core */
+ s->mpu_model = omap2420;
+ s->cpu = cpu_arm_init(core ?: "arm1136-r2");
+ if (s->cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ s->sdram_size = sdram_size;
+ s->sram_size = OMAP242X_SRAM_SIZE;
+
+ s->wakeup = qemu_allocate_irq(omap_mpu_wakeup, s, 0);
+
+ /* Clocks */
+ omap_clk_init(s);
+
+ /* Memory-mapped stuff */
+ memory_region_allocate_system_memory(&s->sdram, NULL, "omap2.dram",
+ s->sdram_size);
+ memory_region_add_subregion(sysmem, OMAP2_Q2_BASE, &s->sdram);
+ memory_region_init_ram(&s->sram, NULL, "omap2.sram", s->sram_size,
+ &error_abort);
+ vmstate_register_ram_global(&s->sram);
+ memory_region_add_subregion(sysmem, OMAP2_SRAM_BASE, &s->sram);
+
+ s->l4 = omap_l4_init(sysmem, OMAP2_L4_BASE, 54);
+
+ /* Actually mapped at any 2K boundary in the ARM11 private-peripheral if */
+ s->ih[0] = qdev_create(NULL, "omap2-intc");
+ qdev_prop_set_uint8(s->ih[0], "revision", 0x21);
+ qdev_prop_set_ptr(s->ih[0], "fclk", omap_findclk(s, "mpu_intc_fclk"));
+ qdev_prop_set_ptr(s->ih[0], "iclk", omap_findclk(s, "mpu_intc_iclk"));
+ qdev_init_nofail(s->ih[0]);
+ busdev = SYS_BUS_DEVICE(s->ih[0]);
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ));
+ sysbus_connect_irq(busdev, 1,
+ qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ));
+ sysbus_mmio_map(busdev, 0, 0x480fe000);
+ s->prcm = omap_prcm_init(omap_l4tao(s->l4, 3),
+ qdev_get_gpio_in(s->ih[0],
+ OMAP_INT_24XX_PRCM_MPU_IRQ),
+ NULL, NULL, s);
+
+ s->sysc = omap_sysctl_init(omap_l4tao(s->l4, 1),
+ omap_findclk(s, "omapctrl_iclk"), s);
+
+ for (i = 0; i < 4; i++) {
+ dma_irqs[i] = qdev_get_gpio_in(s->ih[omap2_dma_irq_map[i].ih],
+ omap2_dma_irq_map[i].intr);
+ }
+ s->dma = omap_dma4_init(0x48056000, dma_irqs, sysmem, s, 256, 32,
+ omap_findclk(s, "sdma_iclk"),
+ omap_findclk(s, "sdma_fclk"));
+ s->port->addr_valid = omap2_validate_addr;
+
+ /* Register SDRAM and SRAM ports for fast DMA transfers. */
+ soc_dma_port_add_mem(s->dma, memory_region_get_ram_ptr(&s->sdram),
+ OMAP2_Q2_BASE, s->sdram_size);
+ soc_dma_port_add_mem(s->dma, memory_region_get_ram_ptr(&s->sram),
+ OMAP2_SRAM_BASE, s->sram_size);
+
+ s->uart[0] = omap2_uart_init(sysmem, omap_l4ta(s->l4, 19),
+ qdev_get_gpio_in(s->ih[0],
+ OMAP_INT_24XX_UART1_IRQ),
+ omap_findclk(s, "uart1_fclk"),
+ omap_findclk(s, "uart1_iclk"),
+ s->drq[OMAP24XX_DMA_UART1_TX],
+ s->drq[OMAP24XX_DMA_UART1_RX],
+ "uart1",
+ serial_hds[0]);
+ s->uart[1] = omap2_uart_init(sysmem, omap_l4ta(s->l4, 20),
+ qdev_get_gpio_in(s->ih[0],
+ OMAP_INT_24XX_UART2_IRQ),
+ omap_findclk(s, "uart2_fclk"),
+ omap_findclk(s, "uart2_iclk"),
+ s->drq[OMAP24XX_DMA_UART2_TX],
+ s->drq[OMAP24XX_DMA_UART2_RX],
+ "uart2",
+ serial_hds[0] ? serial_hds[1] : NULL);
+ s->uart[2] = omap2_uart_init(sysmem, omap_l4ta(s->l4, 21),
+ qdev_get_gpio_in(s->ih[0],
+ OMAP_INT_24XX_UART3_IRQ),
+ omap_findclk(s, "uart3_fclk"),
+ omap_findclk(s, "uart3_iclk"),
+ s->drq[OMAP24XX_DMA_UART3_TX],
+ s->drq[OMAP24XX_DMA_UART3_RX],
+ "uart3",
+ serial_hds[0] && serial_hds[1] ? serial_hds[2] : NULL);
+
+ s->gptimer[0] = omap_gp_timer_init(omap_l4ta(s->l4, 7),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER1),
+ omap_findclk(s, "wu_gpt1_clk"),
+ omap_findclk(s, "wu_l4_iclk"));
+ s->gptimer[1] = omap_gp_timer_init(omap_l4ta(s->l4, 8),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER2),
+ omap_findclk(s, "core_gpt2_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[2] = omap_gp_timer_init(omap_l4ta(s->l4, 22),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER3),
+ omap_findclk(s, "core_gpt3_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[3] = omap_gp_timer_init(omap_l4ta(s->l4, 23),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER4),
+ omap_findclk(s, "core_gpt4_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[4] = omap_gp_timer_init(omap_l4ta(s->l4, 24),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER5),
+ omap_findclk(s, "core_gpt5_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[5] = omap_gp_timer_init(omap_l4ta(s->l4, 25),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER6),
+ omap_findclk(s, "core_gpt6_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[6] = omap_gp_timer_init(omap_l4ta(s->l4, 26),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER7),
+ omap_findclk(s, "core_gpt7_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[7] = omap_gp_timer_init(omap_l4ta(s->l4, 27),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER8),
+ omap_findclk(s, "core_gpt8_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[8] = omap_gp_timer_init(omap_l4ta(s->l4, 28),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER9),
+ omap_findclk(s, "core_gpt9_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[9] = omap_gp_timer_init(omap_l4ta(s->l4, 29),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER10),
+ omap_findclk(s, "core_gpt10_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[10] = omap_gp_timer_init(omap_l4ta(s->l4, 30),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER11),
+ omap_findclk(s, "core_gpt11_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+ s->gptimer[11] = omap_gp_timer_init(omap_l4ta(s->l4, 31),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER12),
+ omap_findclk(s, "core_gpt12_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+
+ omap_tap_init(omap_l4ta(s->l4, 2), s);
+
+ s->synctimer = omap_synctimer_init(omap_l4tao(s->l4, 2), s,
+ omap_findclk(s, "clk32-kHz"),
+ omap_findclk(s, "core_l4_iclk"));
+
+ s->i2c[0] = qdev_create(NULL, "omap_i2c");
+ qdev_prop_set_uint8(s->i2c[0], "revision", 0x34);
+ qdev_prop_set_ptr(s->i2c[0], "iclk", omap_findclk(s, "i2c1.iclk"));
+ qdev_prop_set_ptr(s->i2c[0], "fclk", omap_findclk(s, "i2c1.fclk"));
+ qdev_init_nofail(s->i2c[0]);
+ busdev = SYS_BUS_DEVICE(s->i2c[0]);
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_I2C1_IRQ));
+ sysbus_connect_irq(busdev, 1, s->drq[OMAP24XX_DMA_I2C1_TX]);
+ sysbus_connect_irq(busdev, 2, s->drq[OMAP24XX_DMA_I2C1_RX]);
+ sysbus_mmio_map(busdev, 0, omap_l4_region_base(omap_l4tao(s->l4, 5), 0));
+
+ s->i2c[1] = qdev_create(NULL, "omap_i2c");
+ qdev_prop_set_uint8(s->i2c[1], "revision", 0x34);
+ qdev_prop_set_ptr(s->i2c[1], "iclk", omap_findclk(s, "i2c2.iclk"));
+ qdev_prop_set_ptr(s->i2c[1], "fclk", omap_findclk(s, "i2c2.fclk"));
+ qdev_init_nofail(s->i2c[1]);
+ busdev = SYS_BUS_DEVICE(s->i2c[1]);
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_I2C2_IRQ));
+ sysbus_connect_irq(busdev, 1, s->drq[OMAP24XX_DMA_I2C2_TX]);
+ sysbus_connect_irq(busdev, 2, s->drq[OMAP24XX_DMA_I2C2_RX]);
+ sysbus_mmio_map(busdev, 0, omap_l4_region_base(omap_l4tao(s->l4, 6), 0));
+
+ s->gpio = qdev_create(NULL, "omap2-gpio");
+ qdev_prop_set_int32(s->gpio, "mpu_model", s->mpu_model);
+ qdev_prop_set_ptr(s->gpio, "iclk", omap_findclk(s, "gpio_iclk"));
+ qdev_prop_set_ptr(s->gpio, "fclk0", omap_findclk(s, "gpio1_dbclk"));
+ qdev_prop_set_ptr(s->gpio, "fclk1", omap_findclk(s, "gpio2_dbclk"));
+ qdev_prop_set_ptr(s->gpio, "fclk2", omap_findclk(s, "gpio3_dbclk"));
+ qdev_prop_set_ptr(s->gpio, "fclk3", omap_findclk(s, "gpio4_dbclk"));
+ if (s->mpu_model == omap2430) {
+ qdev_prop_set_ptr(s->gpio, "fclk4", omap_findclk(s, "gpio5_dbclk"));
+ }
+ qdev_init_nofail(s->gpio);
+ busdev = SYS_BUS_DEVICE(s->gpio);
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPIO_BANK1));
+ sysbus_connect_irq(busdev, 3,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPIO_BANK2));
+ sysbus_connect_irq(busdev, 6,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPIO_BANK3));
+ sysbus_connect_irq(busdev, 9,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPIO_BANK4));
+ if (s->mpu_model == omap2430) {
+ sysbus_connect_irq(busdev, 12,
+ qdev_get_gpio_in(s->ih[0],
+ OMAP_INT_243X_GPIO_BANK5));
+ }
+ ta = omap_l4ta(s->l4, 3);
+ sysbus_mmio_map(busdev, 0, omap_l4_region_base(ta, 1));
+ sysbus_mmio_map(busdev, 1, omap_l4_region_base(ta, 0));
+ sysbus_mmio_map(busdev, 2, omap_l4_region_base(ta, 2));
+ sysbus_mmio_map(busdev, 3, omap_l4_region_base(ta, 4));
+ sysbus_mmio_map(busdev, 4, omap_l4_region_base(ta, 5));
+
+ s->sdrc = omap_sdrc_init(sysmem, 0x68009000);
+ s->gpmc = omap_gpmc_init(s, 0x6800a000,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPMC_IRQ),
+ s->drq[OMAP24XX_DMA_GPMC]);
+
+ dinfo = drive_get(IF_SD, 0, 0);
+ if (!dinfo) {
+ fprintf(stderr, "qemu: missing SecureDigital device\n");
+ exit(1);
+ }
+ s->mmc = omap2_mmc_init(omap_l4tao(s->l4, 9),
+ blk_by_legacy_dinfo(dinfo),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_MMC_IRQ),
+ &s->drq[OMAP24XX_DMA_MMC1_TX],
+ omap_findclk(s, "mmc_fclk"), omap_findclk(s, "mmc_iclk"));
+
+ s->mcspi[0] = omap_mcspi_init(omap_l4ta(s->l4, 35), 4,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_MCSPI1_IRQ),
+ &s->drq[OMAP24XX_DMA_SPI1_TX0],
+ omap_findclk(s, "spi1_fclk"),
+ omap_findclk(s, "spi1_iclk"));
+ s->mcspi[1] = omap_mcspi_init(omap_l4ta(s->l4, 36), 2,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_MCSPI2_IRQ),
+ &s->drq[OMAP24XX_DMA_SPI2_TX0],
+ omap_findclk(s, "spi2_fclk"),
+ omap_findclk(s, "spi2_iclk"));
+
+ s->dss = omap_dss_init(omap_l4ta(s->l4, 10), sysmem, 0x68000800,
+ /* XXX wire M_IRQ_25, D_L2_IRQ_30 and I_IRQ_13 together */
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_DSS_IRQ),
+ s->drq[OMAP24XX_DMA_DSS],
+ omap_findclk(s, "dss_clk1"), omap_findclk(s, "dss_clk2"),
+ omap_findclk(s, "dss_54m_clk"),
+ omap_findclk(s, "dss_l3_iclk"),
+ omap_findclk(s, "dss_l4_iclk"));
+
+ omap_sti_init(omap_l4ta(s->l4, 18), sysmem, 0x54000000,
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_STI),
+ omap_findclk(s, "emul_ck"),
+ serial_hds[0] && serial_hds[1] && serial_hds[2] ?
+ serial_hds[3] : NULL);
+
+ s->eac = omap_eac_init(omap_l4ta(s->l4, 32),
+ qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_EAC_IRQ),
+ /* Ten consecutive lines */
+ &s->drq[OMAP24XX_DMA_EAC_AC_RD],
+ omap_findclk(s, "func_96m_clk"),
+ omap_findclk(s, "core_l4_iclk"));
+
+ /* All register mappings (includin those not currenlty implemented):
+ * SystemControlMod 48000000 - 48000fff
+ * SystemControlL4 48001000 - 48001fff
+ * 32kHz Timer Mod 48004000 - 48004fff
+ * 32kHz Timer L4 48005000 - 48005fff
+ * PRCM ModA 48008000 - 480087ff
+ * PRCM ModB 48008800 - 48008fff
+ * PRCM L4 48009000 - 48009fff
+ * TEST-BCM Mod 48012000 - 48012fff
+ * TEST-BCM L4 48013000 - 48013fff
+ * TEST-TAP Mod 48014000 - 48014fff
+ * TEST-TAP L4 48015000 - 48015fff
+ * GPIO1 Mod 48018000 - 48018fff
+ * GPIO Top 48019000 - 48019fff
+ * GPIO2 Mod 4801a000 - 4801afff
+ * GPIO L4 4801b000 - 4801bfff
+ * GPIO3 Mod 4801c000 - 4801cfff
+ * GPIO4 Mod 4801e000 - 4801efff
+ * WDTIMER1 Mod 48020000 - 48010fff
+ * WDTIMER Top 48021000 - 48011fff
+ * WDTIMER2 Mod 48022000 - 48012fff
+ * WDTIMER L4 48023000 - 48013fff
+ * WDTIMER3 Mod 48024000 - 48014fff
+ * WDTIMER3 L4 48025000 - 48015fff
+ * WDTIMER4 Mod 48026000 - 48016fff
+ * WDTIMER4 L4 48027000 - 48017fff
+ * GPTIMER1 Mod 48028000 - 48018fff
+ * GPTIMER1 L4 48029000 - 48019fff
+ * GPTIMER2 Mod 4802a000 - 4801afff
+ * GPTIMER2 L4 4802b000 - 4801bfff
+ * L4-Config AP 48040000 - 480407ff
+ * L4-Config IP 48040800 - 48040fff
+ * L4-Config LA 48041000 - 48041fff
+ * ARM11ETB Mod 48048000 - 48049fff
+ * ARM11ETB L4 4804a000 - 4804afff
+ * DISPLAY Top 48050000 - 480503ff
+ * DISPLAY DISPC 48050400 - 480507ff
+ * DISPLAY RFBI 48050800 - 48050bff
+ * DISPLAY VENC 48050c00 - 48050fff
+ * DISPLAY L4 48051000 - 48051fff
+ * CAMERA Top 48052000 - 480523ff
+ * CAMERA core 48052400 - 480527ff
+ * CAMERA DMA 48052800 - 48052bff
+ * CAMERA MMU 48052c00 - 48052fff
+ * CAMERA L4 48053000 - 48053fff
+ * SDMA Mod 48056000 - 48056fff
+ * SDMA L4 48057000 - 48057fff
+ * SSI Top 48058000 - 48058fff
+ * SSI GDD 48059000 - 48059fff
+ * SSI Port1 4805a000 - 4805afff
+ * SSI Port2 4805b000 - 4805bfff
+ * SSI L4 4805c000 - 4805cfff
+ * USB Mod 4805e000 - 480fefff
+ * USB L4 4805f000 - 480fffff
+ * WIN_TRACER1 Mod 48060000 - 48060fff
+ * WIN_TRACER1 L4 48061000 - 48061fff
+ * WIN_TRACER2 Mod 48062000 - 48062fff
+ * WIN_TRACER2 L4 48063000 - 48063fff
+ * WIN_TRACER3 Mod 48064000 - 48064fff
+ * WIN_TRACER3 L4 48065000 - 48065fff
+ * WIN_TRACER4 Top 48066000 - 480660ff
+ * WIN_TRACER4 ETT 48066100 - 480661ff
+ * WIN_TRACER4 WT 48066200 - 480662ff
+ * WIN_TRACER4 L4 48067000 - 48067fff
+ * XTI Mod 48068000 - 48068fff
+ * XTI L4 48069000 - 48069fff
+ * UART1 Mod 4806a000 - 4806afff
+ * UART1 L4 4806b000 - 4806bfff
+ * UART2 Mod 4806c000 - 4806cfff
+ * UART2 L4 4806d000 - 4806dfff
+ * UART3 Mod 4806e000 - 4806efff
+ * UART3 L4 4806f000 - 4806ffff
+ * I2C1 Mod 48070000 - 48070fff
+ * I2C1 L4 48071000 - 48071fff
+ * I2C2 Mod 48072000 - 48072fff
+ * I2C2 L4 48073000 - 48073fff
+ * McBSP1 Mod 48074000 - 48074fff
+ * McBSP1 L4 48075000 - 48075fff
+ * McBSP2 Mod 48076000 - 48076fff
+ * McBSP2 L4 48077000 - 48077fff
+ * GPTIMER3 Mod 48078000 - 48078fff
+ * GPTIMER3 L4 48079000 - 48079fff
+ * GPTIMER4 Mod 4807a000 - 4807afff
+ * GPTIMER4 L4 4807b000 - 4807bfff
+ * GPTIMER5 Mod 4807c000 - 4807cfff
+ * GPTIMER5 L4 4807d000 - 4807dfff
+ * GPTIMER6 Mod 4807e000 - 4807efff
+ * GPTIMER6 L4 4807f000 - 4807ffff
+ * GPTIMER7 Mod 48080000 - 48080fff
+ * GPTIMER7 L4 48081000 - 48081fff
+ * GPTIMER8 Mod 48082000 - 48082fff
+ * GPTIMER8 L4 48083000 - 48083fff
+ * GPTIMER9 Mod 48084000 - 48084fff
+ * GPTIMER9 L4 48085000 - 48085fff
+ * GPTIMER10 Mod 48086000 - 48086fff
+ * GPTIMER10 L4 48087000 - 48087fff
+ * GPTIMER11 Mod 48088000 - 48088fff
+ * GPTIMER11 L4 48089000 - 48089fff
+ * GPTIMER12 Mod 4808a000 - 4808afff
+ * GPTIMER12 L4 4808b000 - 4808bfff
+ * EAC Mod 48090000 - 48090fff
+ * EAC L4 48091000 - 48091fff
+ * FAC Mod 48092000 - 48092fff
+ * FAC L4 48093000 - 48093fff
+ * MAILBOX Mod 48094000 - 48094fff
+ * MAILBOX L4 48095000 - 48095fff
+ * SPI1 Mod 48098000 - 48098fff
+ * SPI1 L4 48099000 - 48099fff
+ * SPI2 Mod 4809a000 - 4809afff
+ * SPI2 L4 4809b000 - 4809bfff
+ * MMC/SDIO Mod 4809c000 - 4809cfff
+ * MMC/SDIO L4 4809d000 - 4809dfff
+ * MS_PRO Mod 4809e000 - 4809efff
+ * MS_PRO L4 4809f000 - 4809ffff
+ * RNG Mod 480a0000 - 480a0fff
+ * RNG L4 480a1000 - 480a1fff
+ * DES3DES Mod 480a2000 - 480a2fff
+ * DES3DES L4 480a3000 - 480a3fff
+ * SHA1MD5 Mod 480a4000 - 480a4fff
+ * SHA1MD5 L4 480a5000 - 480a5fff
+ * AES Mod 480a6000 - 480a6fff
+ * AES L4 480a7000 - 480a7fff
+ * PKA Mod 480a8000 - 480a9fff
+ * PKA L4 480aa000 - 480aafff
+ * MG Mod 480b0000 - 480b0fff
+ * MG L4 480b1000 - 480b1fff
+ * HDQ/1-wire Mod 480b2000 - 480b2fff
+ * HDQ/1-wire L4 480b3000 - 480b3fff
+ * MPU interrupt 480fe000 - 480fefff
+ * STI channel base 54000000 - 5400ffff
+ * IVA RAM 5c000000 - 5c01ffff
+ * IVA ROM 5c020000 - 5c027fff
+ * IMG_BUF_A 5c040000 - 5c040fff
+ * IMG_BUF_B 5c042000 - 5c042fff
+ * VLCDS 5c048000 - 5c0487ff
+ * IMX_COEF 5c049000 - 5c04afff
+ * IMX_CMD 5c051000 - 5c051fff
+ * VLCDQ 5c053000 - 5c0533ff
+ * VLCDH 5c054000 - 5c054fff
+ * SEQ_CMD 5c055000 - 5c055fff
+ * IMX_REG 5c056000 - 5c0560ff
+ * VLCD_REG 5c056100 - 5c0561ff
+ * SEQ_REG 5c056200 - 5c0562ff
+ * IMG_BUF_REG 5c056300 - 5c0563ff
+ * SEQIRQ_REG 5c056400 - 5c0564ff
+ * OCP_REG 5c060000 - 5c060fff
+ * SYSC_REG 5c070000 - 5c070fff
+ * MMU_REG 5d000000 - 5d000fff
+ * sDMA R 68000400 - 680005ff
+ * sDMA W 68000600 - 680007ff
+ * Display Control 68000800 - 680009ff
+ * DSP subsystem 68000a00 - 68000bff
+ * MPU subsystem 68000c00 - 68000dff
+ * IVA subsystem 68001000 - 680011ff
+ * USB 68001200 - 680013ff
+ * Camera 68001400 - 680015ff
+ * VLYNQ (firewall) 68001800 - 68001bff
+ * VLYNQ 68001e00 - 68001fff
+ * SSI 68002000 - 680021ff
+ * L4 68002400 - 680025ff
+ * DSP (firewall) 68002800 - 68002bff
+ * DSP subsystem 68002e00 - 68002fff
+ * IVA (firewall) 68003000 - 680033ff
+ * IVA 68003600 - 680037ff
+ * GFX 68003a00 - 68003bff
+ * CMDWR emulation 68003c00 - 68003dff
+ * SMS 68004000 - 680041ff
+ * OCM 68004200 - 680043ff
+ * GPMC 68004400 - 680045ff
+ * RAM (firewall) 68005000 - 680053ff
+ * RAM (err login) 68005400 - 680057ff
+ * ROM (firewall) 68005800 - 68005bff
+ * ROM (err login) 68005c00 - 68005fff
+ * GPMC (firewall) 68006000 - 680063ff
+ * GPMC (err login) 68006400 - 680067ff
+ * SMS (err login) 68006c00 - 68006fff
+ * SMS registers 68008000 - 68008fff
+ * SDRC registers 68009000 - 68009fff
+ * GPMC registers 6800a000 6800afff
+ */
+
+ qemu_register_reset(omap2_mpu_reset, s);
+
+ return s;
+}
diff --git a/hw/arm/omap_sx1.c b/hw/arm/omap_sx1.c
new file mode 100644
index 00000000..4b0f7f9c
--- /dev/null
+++ b/hw/arm/omap_sx1.c
@@ -0,0 +1,238 @@
+/* omap_sx1.c Support for the Siemens SX1 smartphone emulation.
+ *
+ * Copyright (C) 2008
+ * Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
+ * Copyright (C) 2007 Vladimir Ananiev <vovan888@gmail.com>
+ *
+ * based on PalmOne's (TM) PDAs support (palm.c)
+ */
+
+/*
+ * PalmOne's (TM) PDAs.
+ *
+ * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/arm/omap.h"
+#include "hw/boards.h"
+#include "hw/arm/arm.h"
+#include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/qtest.h"
+#include "exec/address-spaces.h"
+
+/*****************************************************************************/
+/* Siemens SX1 Cellphone V1 */
+/* - ARM OMAP310 processor
+ * - SRAM 192 kB
+ * - SDRAM 32 MB at 0x10000000
+ * - Boot flash 16 MB at 0x00000000
+ * - Application flash 8 MB at 0x04000000
+ * - 3 serial ports
+ * - 1 SecureDigital
+ * - 1 LCD display
+ * - 1 RTC
+ */
+
+/*****************************************************************************/
+/* Siemens SX1 Cellphone V2 */
+/* - ARM OMAP310 processor
+ * - SRAM 192 kB
+ * - SDRAM 32 MB at 0x10000000
+ * - Boot flash 32 MB at 0x00000000
+ * - 3 serial ports
+ * - 1 SecureDigital
+ * - 1 LCD display
+ * - 1 RTC
+ */
+
+static uint64_t static_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t *val = (uint32_t *) opaque;
+ uint32_t mask = (4 / size) - 1;
+
+ return *val >> ((offset & mask) << 3);
+}
+
+static void static_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+#ifdef SPY
+ printf("%s: value %" PRIx64 " %u bytes written at 0x%x\n",
+ __func__, value, size, (int)offset);
+#endif
+}
+
+static const MemoryRegionOps static_ops = {
+ .read = static_read,
+ .write = static_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+#define sdram_size 0x02000000
+#define sector_size (128 * 1024)
+#define flash0_size (16 * 1024 * 1024)
+#define flash1_size ( 8 * 1024 * 1024)
+#define flash2_size (32 * 1024 * 1024)
+#define total_ram_v1 (sdram_size + flash0_size + flash1_size + OMAP15XX_SRAM_SIZE)
+#define total_ram_v2 (sdram_size + flash2_size + OMAP15XX_SRAM_SIZE)
+
+static struct arm_boot_info sx1_binfo = {
+ .loader_start = OMAP_EMIFF_BASE,
+ .ram_size = sdram_size,
+ .board_id = 0x265,
+};
+
+static void sx1_init(MachineState *machine, const int version)
+{
+ struct omap_mpu_state_s *mpu;
+ MemoryRegion *address_space = get_system_memory();
+ MemoryRegion *flash = g_new(MemoryRegion, 1);
+ MemoryRegion *cs = g_new(MemoryRegion, 4);
+ static uint32_t cs0val = 0x00213090;
+ static uint32_t cs1val = 0x00215070;
+ static uint32_t cs2val = 0x00001139;
+ static uint32_t cs3val = 0x00001139;
+ DriveInfo *dinfo;
+ int fl_idx;
+ uint32_t flash_size = flash0_size;
+ int be;
+
+ if (version == 2) {
+ flash_size = flash2_size;
+ }
+
+ mpu = omap310_mpu_init(address_space, sx1_binfo.ram_size,
+ machine->cpu_model);
+
+ /* External Flash (EMIFS) */
+ memory_region_init_ram(flash, NULL, "omap_sx1.flash0-0", flash_size,
+ &error_abort);
+ vmstate_register_ram_global(flash);
+ memory_region_set_readonly(flash, true);
+ memory_region_add_subregion(address_space, OMAP_CS0_BASE, flash);
+
+ memory_region_init_io(&cs[0], NULL, &static_ops, &cs0val,
+ "sx1.cs0", OMAP_CS0_SIZE - flash_size);
+ memory_region_add_subregion(address_space,
+ OMAP_CS0_BASE + flash_size, &cs[0]);
+
+
+ memory_region_init_io(&cs[2], NULL, &static_ops, &cs2val,
+ "sx1.cs2", OMAP_CS2_SIZE);
+ memory_region_add_subregion(address_space,
+ OMAP_CS2_BASE, &cs[2]);
+
+ memory_region_init_io(&cs[3], NULL, &static_ops, &cs3val,
+ "sx1.cs3", OMAP_CS3_SIZE);
+ memory_region_add_subregion(address_space,
+ OMAP_CS2_BASE, &cs[3]);
+
+ fl_idx = 0;
+#ifdef TARGET_WORDS_BIGENDIAN
+ be = 1;
+#else
+ be = 0;
+#endif
+
+ if ((dinfo = drive_get(IF_PFLASH, 0, fl_idx)) != NULL) {
+ if (!pflash_cfi01_register(OMAP_CS0_BASE, NULL,
+ "omap_sx1.flash0-1", flash_size,
+ blk_by_legacy_dinfo(dinfo),
+ sector_size, flash_size / sector_size,
+ 4, 0, 0, 0, 0, be)) {
+ fprintf(stderr, "qemu: Error registering flash memory %d.\n",
+ fl_idx);
+ }
+ fl_idx++;
+ }
+
+ if ((version == 1) &&
+ (dinfo = drive_get(IF_PFLASH, 0, fl_idx)) != NULL) {
+ MemoryRegion *flash_1 = g_new(MemoryRegion, 1);
+ memory_region_init_ram(flash_1, NULL, "omap_sx1.flash1-0", flash1_size,
+ &error_abort);
+ vmstate_register_ram_global(flash_1);
+ memory_region_set_readonly(flash_1, true);
+ memory_region_add_subregion(address_space, OMAP_CS1_BASE, flash_1);
+
+ memory_region_init_io(&cs[1], NULL, &static_ops, &cs1val,
+ "sx1.cs1", OMAP_CS1_SIZE - flash1_size);
+ memory_region_add_subregion(address_space,
+ OMAP_CS1_BASE + flash1_size, &cs[1]);
+
+ if (!pflash_cfi01_register(OMAP_CS1_BASE, NULL,
+ "omap_sx1.flash1-1", flash1_size,
+ blk_by_legacy_dinfo(dinfo),
+ sector_size, flash1_size / sector_size,
+ 4, 0, 0, 0, 0, be)) {
+ fprintf(stderr, "qemu: Error registering flash memory %d.\n",
+ fl_idx);
+ }
+ fl_idx++;
+ } else {
+ memory_region_init_io(&cs[1], NULL, &static_ops, &cs1val,
+ "sx1.cs1", OMAP_CS1_SIZE);
+ memory_region_add_subregion(address_space,
+ OMAP_CS1_BASE, &cs[1]);
+ }
+
+ if (!machine->kernel_filename && !fl_idx && !qtest_enabled()) {
+ fprintf(stderr, "Kernel or Flash image must be specified\n");
+ exit(1);
+ }
+
+ /* Load the kernel. */
+ sx1_binfo.kernel_filename = machine->kernel_filename;
+ sx1_binfo.kernel_cmdline = machine->kernel_cmdline;
+ sx1_binfo.initrd_filename = machine->initrd_filename;
+ arm_load_kernel(mpu->cpu, &sx1_binfo);
+
+ /* TODO: fix next line */
+ //~ qemu_console_resize(ds, 640, 480);
+}
+
+static void sx1_init_v1(MachineState *machine)
+{
+ sx1_init(machine, 1);
+}
+
+static void sx1_init_v2(MachineState *machine)
+{
+ sx1_init(machine, 2);
+}
+
+static QEMUMachine sx1_machine_v2 = {
+ .name = "sx1",
+ .desc = "Siemens SX1 (OMAP310) V2",
+ .init = sx1_init_v2,
+};
+
+static QEMUMachine sx1_machine_v1 = {
+ .name = "sx1-v1",
+ .desc = "Siemens SX1 (OMAP310) V1",
+ .init = sx1_init_v1,
+};
+
+static void sx1_machine_init(void)
+{
+ qemu_register_machine(&sx1_machine_v2);
+ qemu_register_machine(&sx1_machine_v1);
+}
+
+machine_init(sx1_machine_init);
diff --git a/hw/arm/palm.c b/hw/arm/palm.c
new file mode 100644
index 00000000..7f1cfb8f
--- /dev/null
+++ b/hw/arm/palm.c
@@ -0,0 +1,283 @@
+/*
+ * PalmOne's (TM) PDAs.
+ *
+ * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "audio/audio.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+#include "ui/console.h"
+#include "hw/arm/omap.h"
+#include "hw/boards.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "hw/loader.h"
+#include "exec/address-spaces.h"
+
+static uint32_t static_readb(void *opaque, hwaddr offset)
+{
+ uint32_t *val = (uint32_t *) opaque;
+ return *val >> ((offset & 3) << 3);
+}
+
+static uint32_t static_readh(void *opaque, hwaddr offset)
+{
+ uint32_t *val = (uint32_t *) opaque;
+ return *val >> ((offset & 1) << 3);
+}
+
+static uint32_t static_readw(void *opaque, hwaddr offset)
+{
+ uint32_t *val = (uint32_t *) opaque;
+ return *val >> ((offset & 0) << 3);
+}
+
+static void static_write(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+#ifdef SPY
+ printf("%s: value %08lx written at " PA_FMT "\n",
+ __FUNCTION__, value, offset);
+#endif
+}
+
+static const MemoryRegionOps static_ops = {
+ .old_mmio = {
+ .read = { static_readb, static_readh, static_readw, },
+ .write = { static_write, static_write, static_write, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* Palm Tunsgten|E support */
+
+/* Shared GPIOs */
+#define PALMTE_USBDETECT_GPIO 0
+#define PALMTE_USB_OR_DC_GPIO 1
+#define PALMTE_TSC_GPIO 4
+#define PALMTE_PINTDAV_GPIO 6
+#define PALMTE_MMC_WP_GPIO 8
+#define PALMTE_MMC_POWER_GPIO 9
+#define PALMTE_HDQ_GPIO 11
+#define PALMTE_HEADPHONES_GPIO 14
+#define PALMTE_SPEAKER_GPIO 15
+/* MPU private GPIOs */
+#define PALMTE_DC_GPIO 2
+#define PALMTE_MMC_SWITCH_GPIO 4
+#define PALMTE_MMC1_GPIO 6
+#define PALMTE_MMC2_GPIO 7
+#define PALMTE_MMC3_GPIO 11
+
+static MouseTransformInfo palmte_pointercal = {
+ .x = 320,
+ .y = 320,
+ .a = { -5909, 8, 22465308, 104, 7644, -1219972, 65536 },
+};
+
+static void palmte_microwire_setup(struct omap_mpu_state_s *cpu)
+{
+ uWireSlave *tsc;
+
+ tsc = tsc2102_init(qdev_get_gpio_in(cpu->gpio, PALMTE_PINTDAV_GPIO));
+
+ omap_uwire_attach(cpu->microwire, tsc, 0);
+ omap_mcbsp_i2s_attach(cpu->mcbsp1, tsc210x_codec(tsc));
+
+ tsc210x_set_transform(tsc, &palmte_pointercal);
+}
+
+static struct {
+ int row;
+ int column;
+} palmte_keymap[0x80] = {
+ [0 ... 0x7f] = { -1, -1 },
+ [0x3b] = { 0, 0 }, /* F1 -> Calendar */
+ [0x3c] = { 1, 0 }, /* F2 -> Contacts */
+ [0x3d] = { 2, 0 }, /* F3 -> Tasks List */
+ [0x3e] = { 3, 0 }, /* F4 -> Note Pad */
+ [0x01] = { 4, 0 }, /* Esc -> Power */
+ [0x4b] = { 0, 1 }, /* Left */
+ [0x50] = { 1, 1 }, /* Down */
+ [0x48] = { 2, 1 }, /* Up */
+ [0x4d] = { 3, 1 }, /* Right */
+ [0x4c] = { 4, 1 }, /* Centre */
+ [0x39] = { 4, 1 }, /* Spc -> Centre */
+};
+
+static void palmte_button_event(void *opaque, int keycode)
+{
+ struct omap_mpu_state_s *cpu = (struct omap_mpu_state_s *) opaque;
+
+ if (palmte_keymap[keycode & 0x7f].row != -1)
+ omap_mpuio_key(cpu->mpuio,
+ palmte_keymap[keycode & 0x7f].row,
+ palmte_keymap[keycode & 0x7f].column,
+ !(keycode & 0x80));
+}
+
+static void palmte_onoff_gpios(void *opaque, int line, int level)
+{
+ switch (line) {
+ case 0:
+ printf("%s: current to MMC/SD card %sabled.\n",
+ __FUNCTION__, level ? "dis" : "en");
+ break;
+ case 1:
+ printf("%s: internal speaker amplifier %s.\n",
+ __FUNCTION__, level ? "down" : "on");
+ break;
+
+ /* These LCD & Audio output signals have not been identified yet. */
+ case 2:
+ case 3:
+ case 4:
+ printf("%s: LCD GPIO%i %s.\n",
+ __FUNCTION__, line - 1, level ? "high" : "low");
+ break;
+ case 5:
+ case 6:
+ printf("%s: Audio GPIO%i %s.\n",
+ __FUNCTION__, line - 4, level ? "high" : "low");
+ break;
+ }
+}
+
+static void palmte_gpio_setup(struct omap_mpu_state_s *cpu)
+{
+ qemu_irq *misc_gpio;
+
+ omap_mmc_handlers(cpu->mmc,
+ qdev_get_gpio_in(cpu->gpio, PALMTE_MMC_WP_GPIO),
+ qemu_irq_invert(omap_mpuio_in_get(cpu->mpuio)
+ [PALMTE_MMC_SWITCH_GPIO]));
+
+ misc_gpio = qemu_allocate_irqs(palmte_onoff_gpios, cpu, 7);
+ qdev_connect_gpio_out(cpu->gpio, PALMTE_MMC_POWER_GPIO, misc_gpio[0]);
+ qdev_connect_gpio_out(cpu->gpio, PALMTE_SPEAKER_GPIO, misc_gpio[1]);
+ qdev_connect_gpio_out(cpu->gpio, 11, misc_gpio[2]);
+ qdev_connect_gpio_out(cpu->gpio, 12, misc_gpio[3]);
+ qdev_connect_gpio_out(cpu->gpio, 13, misc_gpio[4]);
+ omap_mpuio_out_set(cpu->mpuio, 1, misc_gpio[5]);
+ omap_mpuio_out_set(cpu->mpuio, 3, misc_gpio[6]);
+
+ /* Reset some inputs to initial state. */
+ qemu_irq_lower(qdev_get_gpio_in(cpu->gpio, PALMTE_USBDETECT_GPIO));
+ qemu_irq_lower(qdev_get_gpio_in(cpu->gpio, PALMTE_USB_OR_DC_GPIO));
+ qemu_irq_lower(qdev_get_gpio_in(cpu->gpio, 4));
+ qemu_irq_lower(qdev_get_gpio_in(cpu->gpio, PALMTE_HEADPHONES_GPIO));
+ qemu_irq_lower(omap_mpuio_in_get(cpu->mpuio)[PALMTE_DC_GPIO]);
+ qemu_irq_raise(omap_mpuio_in_get(cpu->mpuio)[6]);
+ qemu_irq_raise(omap_mpuio_in_get(cpu->mpuio)[7]);
+ qemu_irq_raise(omap_mpuio_in_get(cpu->mpuio)[11]);
+}
+
+static struct arm_boot_info palmte_binfo = {
+ .loader_start = OMAP_EMIFF_BASE,
+ .ram_size = 0x02000000,
+ .board_id = 0x331,
+};
+
+static void palmte_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ MemoryRegion *address_space_mem = get_system_memory();
+ struct omap_mpu_state_s *mpu;
+ int flash_size = 0x00800000;
+ int sdram_size = palmte_binfo.ram_size;
+ static uint32_t cs0val = 0xffffffff;
+ static uint32_t cs1val = 0x0000e1a0;
+ static uint32_t cs2val = 0x0000e1a0;
+ static uint32_t cs3val = 0xe1a0e1a0;
+ int rom_size, rom_loaded = 0;
+ MemoryRegion *flash = g_new(MemoryRegion, 1);
+ MemoryRegion *cs = g_new(MemoryRegion, 4);
+
+ mpu = omap310_mpu_init(address_space_mem, sdram_size, cpu_model);
+
+ /* External Flash (EMIFS) */
+ memory_region_init_ram(flash, NULL, "palmte.flash", flash_size,
+ &error_abort);
+ vmstate_register_ram_global(flash);
+ memory_region_set_readonly(flash, true);
+ memory_region_add_subregion(address_space_mem, OMAP_CS0_BASE, flash);
+
+ memory_region_init_io(&cs[0], NULL, &static_ops, &cs0val, "palmte-cs0",
+ OMAP_CS0_SIZE - flash_size);
+ memory_region_add_subregion(address_space_mem, OMAP_CS0_BASE + flash_size,
+ &cs[0]);
+ memory_region_init_io(&cs[1], NULL, &static_ops, &cs1val, "palmte-cs1",
+ OMAP_CS1_SIZE);
+ memory_region_add_subregion(address_space_mem, OMAP_CS1_BASE, &cs[1]);
+ memory_region_init_io(&cs[2], NULL, &static_ops, &cs2val, "palmte-cs2",
+ OMAP_CS2_SIZE);
+ memory_region_add_subregion(address_space_mem, OMAP_CS2_BASE, &cs[2]);
+ memory_region_init_io(&cs[3], NULL, &static_ops, &cs3val, "palmte-cs3",
+ OMAP_CS3_SIZE);
+ memory_region_add_subregion(address_space_mem, OMAP_CS3_BASE, &cs[3]);
+
+ palmte_microwire_setup(mpu);
+
+ qemu_add_kbd_event_handler(palmte_button_event, mpu);
+
+ palmte_gpio_setup(mpu);
+
+ /* Setup initial (reset) machine state */
+ if (nb_option_roms) {
+ rom_size = get_image_size(option_rom[0].name);
+ if (rom_size > flash_size) {
+ fprintf(stderr, "%s: ROM image too big (%x > %x)\n",
+ __FUNCTION__, rom_size, flash_size);
+ rom_size = 0;
+ }
+ if (rom_size > 0) {
+ rom_size = load_image_targphys(option_rom[0].name, OMAP_CS0_BASE,
+ flash_size);
+ rom_loaded = 1;
+ }
+ if (rom_size < 0) {
+ fprintf(stderr, "%s: error loading '%s'\n",
+ __FUNCTION__, option_rom[0].name);
+ }
+ }
+
+ if (!rom_loaded && !kernel_filename && !qtest_enabled()) {
+ fprintf(stderr, "Kernel or ROM image must be specified\n");
+ exit(1);
+ }
+
+ /* Load the kernel. */
+ palmte_binfo.kernel_filename = kernel_filename;
+ palmte_binfo.kernel_cmdline = kernel_cmdline;
+ palmte_binfo.initrd_filename = initrd_filename;
+ arm_load_kernel(mpu->cpu, &palmte_binfo);
+}
+
+static QEMUMachine palmte_machine = {
+ .name = "cheetah",
+ .desc = "Palm Tungsten|E aka. Cheetah PDA (OMAP310)",
+ .init = palmte_init,
+};
+
+static void palmte_machine_init(void)
+{
+ qemu_register_machine(&palmte_machine);
+}
+
+machine_init(palmte_machine_init);
diff --git a/hw/arm/pxa2xx.c b/hw/arm/pxa2xx.c
new file mode 100644
index 00000000..d94e2077
--- /dev/null
+++ b/hw/arm/pxa2xx.c
@@ -0,0 +1,2354 @@
+/*
+ * Intel XScale PXA255/270 processor support.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/pxa.h"
+#include "sysemu/sysemu.h"
+#include "hw/char/serial.h"
+#include "hw/i2c/i2c.h"
+#include "hw/ssi.h"
+#include "sysemu/char.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+
+static struct {
+ hwaddr io_base;
+ int irqn;
+} pxa255_serial[] = {
+ { 0x40100000, PXA2XX_PIC_FFUART },
+ { 0x40200000, PXA2XX_PIC_BTUART },
+ { 0x40700000, PXA2XX_PIC_STUART },
+ { 0x41600000, PXA25X_PIC_HWUART },
+ { 0, 0 }
+}, pxa270_serial[] = {
+ { 0x40100000, PXA2XX_PIC_FFUART },
+ { 0x40200000, PXA2XX_PIC_BTUART },
+ { 0x40700000, PXA2XX_PIC_STUART },
+ { 0, 0 }
+};
+
+typedef struct PXASSPDef {
+ hwaddr io_base;
+ int irqn;
+} PXASSPDef;
+
+#if 0
+static PXASSPDef pxa250_ssp[] = {
+ { 0x41000000, PXA2XX_PIC_SSP },
+ { 0, 0 }
+};
+#endif
+
+static PXASSPDef pxa255_ssp[] = {
+ { 0x41000000, PXA2XX_PIC_SSP },
+ { 0x41400000, PXA25X_PIC_NSSP },
+ { 0, 0 }
+};
+
+#if 0
+static PXASSPDef pxa26x_ssp[] = {
+ { 0x41000000, PXA2XX_PIC_SSP },
+ { 0x41400000, PXA25X_PIC_NSSP },
+ { 0x41500000, PXA26X_PIC_ASSP },
+ { 0, 0 }
+};
+#endif
+
+static PXASSPDef pxa27x_ssp[] = {
+ { 0x41000000, PXA2XX_PIC_SSP },
+ { 0x41700000, PXA27X_PIC_SSP2 },
+ { 0x41900000, PXA2XX_PIC_SSP3 },
+ { 0, 0 }
+};
+
+#define PMCR 0x00 /* Power Manager Control register */
+#define PSSR 0x04 /* Power Manager Sleep Status register */
+#define PSPR 0x08 /* Power Manager Scratch-Pad register */
+#define PWER 0x0c /* Power Manager Wake-Up Enable register */
+#define PRER 0x10 /* Power Manager Rising-Edge Detect Enable register */
+#define PFER 0x14 /* Power Manager Falling-Edge Detect Enable register */
+#define PEDR 0x18 /* Power Manager Edge-Detect Status register */
+#define PCFR 0x1c /* Power Manager General Configuration register */
+#define PGSR0 0x20 /* Power Manager GPIO Sleep-State register 0 */
+#define PGSR1 0x24 /* Power Manager GPIO Sleep-State register 1 */
+#define PGSR2 0x28 /* Power Manager GPIO Sleep-State register 2 */
+#define PGSR3 0x2c /* Power Manager GPIO Sleep-State register 3 */
+#define RCSR 0x30 /* Reset Controller Status register */
+#define PSLR 0x34 /* Power Manager Sleep Configuration register */
+#define PTSR 0x38 /* Power Manager Standby Configuration register */
+#define PVCR 0x40 /* Power Manager Voltage Change Control register */
+#define PUCR 0x4c /* Power Manager USIM Card Control/Status register */
+#define PKWR 0x50 /* Power Manager Keyboard Wake-Up Enable register */
+#define PKSR 0x54 /* Power Manager Keyboard Level-Detect Status */
+#define PCMD0 0x80 /* Power Manager I2C Command register File 0 */
+#define PCMD31 0xfc /* Power Manager I2C Command register File 31 */
+
+static uint64_t pxa2xx_pm_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PXA2xxState *s = (PXA2xxState *) opaque;
+
+ switch (addr) {
+ case PMCR ... PCMD31:
+ if (addr & 3)
+ goto fail;
+
+ return s->pm_regs[addr >> 2];
+ default:
+ fail:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void pxa2xx_pm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ PXA2xxState *s = (PXA2xxState *) opaque;
+
+ switch (addr) {
+ case PMCR:
+ /* Clear the write-one-to-clear bits... */
+ s->pm_regs[addr >> 2] &= ~(value & 0x2a);
+ /* ...and set the plain r/w bits */
+ s->pm_regs[addr >> 2] &= ~0x15;
+ s->pm_regs[addr >> 2] |= value & 0x15;
+ break;
+
+ case PSSR: /* Read-clean registers */
+ case RCSR:
+ case PKSR:
+ s->pm_regs[addr >> 2] &= ~value;
+ break;
+
+ default: /* Read-write registers */
+ if (!(addr & 3)) {
+ s->pm_regs[addr >> 2] = value;
+ break;
+ }
+
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps pxa2xx_pm_ops = {
+ .read = pxa2xx_pm_read,
+ .write = pxa2xx_pm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pxa2xx_pm = {
+ .name = "pxa2xx_pm",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(pm_regs, PXA2xxState, 0x40),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define CCCR 0x00 /* Core Clock Configuration register */
+#define CKEN 0x04 /* Clock Enable register */
+#define OSCC 0x08 /* Oscillator Configuration register */
+#define CCSR 0x0c /* Core Clock Status register */
+
+static uint64_t pxa2xx_cm_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PXA2xxState *s = (PXA2xxState *) opaque;
+
+ switch (addr) {
+ case CCCR:
+ case CKEN:
+ case OSCC:
+ return s->cm_regs[addr >> 2];
+
+ case CCSR:
+ return s->cm_regs[CCCR >> 2] | (3 << 28);
+
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void pxa2xx_cm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ PXA2xxState *s = (PXA2xxState *) opaque;
+
+ switch (addr) {
+ case CCCR:
+ case CKEN:
+ s->cm_regs[addr >> 2] = value;
+ break;
+
+ case OSCC:
+ s->cm_regs[addr >> 2] &= ~0x6c;
+ s->cm_regs[addr >> 2] |= value & 0x6e;
+ if ((value >> 1) & 1) /* OON */
+ s->cm_regs[addr >> 2] |= 1 << 0; /* Oscillator is now stable */
+ break;
+
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps pxa2xx_cm_ops = {
+ .read = pxa2xx_cm_read,
+ .write = pxa2xx_cm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pxa2xx_cm = {
+ .name = "pxa2xx_cm",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(cm_regs, PXA2xxState, 4),
+ VMSTATE_UINT32(clkcfg, PXA2xxState),
+ VMSTATE_UINT32(pmnc, PXA2xxState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint64_t pxa2xx_clkcfg_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+ PXA2xxState *s = (PXA2xxState *)ri->opaque;
+ return s->clkcfg;
+}
+
+static void pxa2xx_clkcfg_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ PXA2xxState *s = (PXA2xxState *)ri->opaque;
+ s->clkcfg = value & 0xf;
+ if (value & 2) {
+ printf("%s: CPU frequency change attempt\n", __func__);
+ }
+}
+
+static void pxa2xx_pwrmode_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ PXA2xxState *s = (PXA2xxState *)ri->opaque;
+ static const char *pwrmode[8] = {
+ "Normal", "Idle", "Deep-idle", "Standby",
+ "Sleep", "reserved (!)", "reserved (!)", "Deep-sleep",
+ };
+
+ if (value & 8) {
+ printf("%s: CPU voltage change attempt\n", __func__);
+ }
+ switch (value & 7) {
+ case 0:
+ /* Do nothing */
+ break;
+
+ case 1:
+ /* Idle */
+ if (!(s->cm_regs[CCCR >> 2] & (1U << 31))) { /* CPDIS */
+ cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HALT);
+ break;
+ }
+ /* Fall through. */
+
+ case 2:
+ /* Deep-Idle */
+ cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HALT);
+ s->pm_regs[RCSR >> 2] |= 0x8; /* Set GPR */
+ goto message;
+
+ case 3:
+ s->cpu->env.uncached_cpsr = ARM_CPU_MODE_SVC;
+ s->cpu->env.daif = PSTATE_A | PSTATE_F | PSTATE_I;
+ s->cpu->env.cp15.sctlr_ns = 0;
+ s->cpu->env.cp15.cpacr_el1 = 0;
+ s->cpu->env.cp15.ttbr0_el[1] = 0;
+ s->cpu->env.cp15.dacr_ns = 0;
+ s->pm_regs[PSSR >> 2] |= 0x8; /* Set STS */
+ s->pm_regs[RCSR >> 2] |= 0x8; /* Set GPR */
+
+ /*
+ * The scratch-pad register is almost universally used
+ * for storing the return address on suspend. For the
+ * lack of a resuming bootloader, perform a jump
+ * directly to that address.
+ */
+ memset(s->cpu->env.regs, 0, 4 * 15);
+ s->cpu->env.regs[15] = s->pm_regs[PSPR >> 2];
+
+#if 0
+ buffer = 0xe59ff000; /* ldr pc, [pc, #0] */
+ cpu_physical_memory_write(0, &buffer, 4);
+ buffer = s->pm_regs[PSPR >> 2];
+ cpu_physical_memory_write(8, &buffer, 4);
+#endif
+
+ /* Suspend */
+ cpu_interrupt(current_cpu, CPU_INTERRUPT_HALT);
+
+ goto message;
+
+ default:
+ message:
+ printf("%s: machine entered %s mode\n", __func__,
+ pwrmode[value & 7]);
+ }
+}
+
+static uint64_t pxa2xx_cppmnc_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+ PXA2xxState *s = (PXA2xxState *)ri->opaque;
+ return s->pmnc;
+}
+
+static void pxa2xx_cppmnc_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ PXA2xxState *s = (PXA2xxState *)ri->opaque;
+ s->pmnc = value;
+}
+
+static uint64_t pxa2xx_cpccnt_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+ PXA2xxState *s = (PXA2xxState *)ri->opaque;
+ if (s->pmnc & 1) {
+ return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ } else {
+ return 0;
+ }
+}
+
+static const ARMCPRegInfo pxa_cp_reginfo[] = {
+ /* cp14 crm==1: perf registers */
+ { .name = "CPPMNC", .cp = 14, .crn = 0, .crm = 1, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_IO,
+ .readfn = pxa2xx_cppmnc_read, .writefn = pxa2xx_cppmnc_write },
+ { .name = "CPCCNT", .cp = 14, .crn = 1, .crm = 1, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_IO,
+ .readfn = pxa2xx_cpccnt_read, .writefn = arm_cp_write_ignore },
+ { .name = "CPINTEN", .cp = 14, .crn = 4, .crm = 1, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
+ { .name = "CPFLAG", .cp = 14, .crn = 5, .crm = 1, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
+ { .name = "CPEVTSEL", .cp = 14, .crn = 8, .crm = 1, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
+ /* cp14 crm==2: performance count registers */
+ { .name = "CPPMN0", .cp = 14, .crn = 0, .crm = 2, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
+ { .name = "CPPMN1", .cp = 14, .crn = 1, .crm = 2, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
+ { .name = "CPPMN2", .cp = 14, .crn = 2, .crm = 2, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
+ { .name = "CPPMN3", .cp = 14, .crn = 2, .crm = 3, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
+ /* cp14 crn==6: CLKCFG */
+ { .name = "CLKCFG", .cp = 14, .crn = 6, .crm = 0, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_IO,
+ .readfn = pxa2xx_clkcfg_read, .writefn = pxa2xx_clkcfg_write },
+ /* cp14 crn==7: PWRMODE */
+ { .name = "PWRMODE", .cp = 14, .crn = 7, .crm = 0, .opc1 = 0, .opc2 = 0,
+ .access = PL1_RW, .type = ARM_CP_IO,
+ .readfn = arm_cp_read_zero, .writefn = pxa2xx_pwrmode_write },
+ REGINFO_SENTINEL
+};
+
+static void pxa2xx_setup_cp14(PXA2xxState *s)
+{
+ define_arm_cp_regs_with_opaque(s->cpu, pxa_cp_reginfo, s);
+}
+
+#define MDCNFG 0x00 /* SDRAM Configuration register */
+#define MDREFR 0x04 /* SDRAM Refresh Control register */
+#define MSC0 0x08 /* Static Memory Control register 0 */
+#define MSC1 0x0c /* Static Memory Control register 1 */
+#define MSC2 0x10 /* Static Memory Control register 2 */
+#define MECR 0x14 /* Expansion Memory Bus Config register */
+#define SXCNFG 0x1c /* Synchronous Static Memory Config register */
+#define MCMEM0 0x28 /* PC Card Memory Socket 0 Timing register */
+#define MCMEM1 0x2c /* PC Card Memory Socket 1 Timing register */
+#define MCATT0 0x30 /* PC Card Attribute Socket 0 register */
+#define MCATT1 0x34 /* PC Card Attribute Socket 1 register */
+#define MCIO0 0x38 /* PC Card I/O Socket 0 Timing register */
+#define MCIO1 0x3c /* PC Card I/O Socket 1 Timing register */
+#define MDMRS 0x40 /* SDRAM Mode Register Set Config register */
+#define BOOT_DEF 0x44 /* Boot-time Default Configuration register */
+#define ARB_CNTL 0x48 /* Arbiter Control register */
+#define BSCNTR0 0x4c /* Memory Buffer Strength Control register 0 */
+#define BSCNTR1 0x50 /* Memory Buffer Strength Control register 1 */
+#define LCDBSCNTR 0x54 /* LCD Buffer Strength Control register */
+#define MDMRSLP 0x58 /* Low Power SDRAM Mode Set Config register */
+#define BSCNTR2 0x5c /* Memory Buffer Strength Control register 2 */
+#define BSCNTR3 0x60 /* Memory Buffer Strength Control register 3 */
+#define SA1110 0x64 /* SA-1110 Memory Compatibility register */
+
+static uint64_t pxa2xx_mm_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PXA2xxState *s = (PXA2xxState *) opaque;
+
+ switch (addr) {
+ case MDCNFG ... SA1110:
+ if ((addr & 3) == 0)
+ return s->mm_regs[addr >> 2];
+
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void pxa2xx_mm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ PXA2xxState *s = (PXA2xxState *) opaque;
+
+ switch (addr) {
+ case MDCNFG ... SA1110:
+ if ((addr & 3) == 0) {
+ s->mm_regs[addr >> 2] = value;
+ break;
+ }
+
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps pxa2xx_mm_ops = {
+ .read = pxa2xx_mm_read,
+ .write = pxa2xx_mm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pxa2xx_mm = {
+ .name = "pxa2xx_mm",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(mm_regs, PXA2xxState, 0x1a),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define TYPE_PXA2XX_SSP "pxa2xx-ssp"
+#define PXA2XX_SSP(obj) \
+ OBJECT_CHECK(PXA2xxSSPState, (obj), TYPE_PXA2XX_SSP)
+
+/* Synchronous Serial Ports */
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ uint32_t enable;
+ SSIBus *bus;
+
+ uint32_t sscr[2];
+ uint32_t sspsp;
+ uint32_t ssto;
+ uint32_t ssitr;
+ uint32_t sssr;
+ uint8_t sstsa;
+ uint8_t ssrsa;
+ uint8_t ssacd;
+
+ uint32_t rx_fifo[16];
+ uint32_t rx_level;
+ uint32_t rx_start;
+} PXA2xxSSPState;
+
+static bool pxa2xx_ssp_vmstate_validate(void *opaque, int version_id)
+{
+ PXA2xxSSPState *s = opaque;
+
+ return s->rx_start < sizeof(s->rx_fifo);
+}
+
+static const VMStateDescription vmstate_pxa2xx_ssp = {
+ .name = "pxa2xx-ssp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(enable, PXA2xxSSPState),
+ VMSTATE_UINT32_ARRAY(sscr, PXA2xxSSPState, 2),
+ VMSTATE_UINT32(sspsp, PXA2xxSSPState),
+ VMSTATE_UINT32(ssto, PXA2xxSSPState),
+ VMSTATE_UINT32(ssitr, PXA2xxSSPState),
+ VMSTATE_UINT32(sssr, PXA2xxSSPState),
+ VMSTATE_UINT8(sstsa, PXA2xxSSPState),
+ VMSTATE_UINT8(ssrsa, PXA2xxSSPState),
+ VMSTATE_UINT8(ssacd, PXA2xxSSPState),
+ VMSTATE_UINT32(rx_level, PXA2xxSSPState),
+ VMSTATE_UINT32(rx_start, PXA2xxSSPState),
+ VMSTATE_VALIDATE("fifo is 16 bytes", pxa2xx_ssp_vmstate_validate),
+ VMSTATE_UINT32_ARRAY(rx_fifo, PXA2xxSSPState, 16),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define SSCR0 0x00 /* SSP Control register 0 */
+#define SSCR1 0x04 /* SSP Control register 1 */
+#define SSSR 0x08 /* SSP Status register */
+#define SSITR 0x0c /* SSP Interrupt Test register */
+#define SSDR 0x10 /* SSP Data register */
+#define SSTO 0x28 /* SSP Time-Out register */
+#define SSPSP 0x2c /* SSP Programmable Serial Protocol register */
+#define SSTSA 0x30 /* SSP TX Time Slot Active register */
+#define SSRSA 0x34 /* SSP RX Time Slot Active register */
+#define SSTSS 0x38 /* SSP Time Slot Status register */
+#define SSACD 0x3c /* SSP Audio Clock Divider register */
+
+/* Bitfields for above registers */
+#define SSCR0_SPI(x) (((x) & 0x30) == 0x00)
+#define SSCR0_SSP(x) (((x) & 0x30) == 0x10)
+#define SSCR0_UWIRE(x) (((x) & 0x30) == 0x20)
+#define SSCR0_PSP(x) (((x) & 0x30) == 0x30)
+#define SSCR0_SSE (1 << 7)
+#define SSCR0_RIM (1 << 22)
+#define SSCR0_TIM (1 << 23)
+#define SSCR0_MOD (1U << 31)
+#define SSCR0_DSS(x) (((((x) >> 16) & 0x10) | ((x) & 0xf)) + 1)
+#define SSCR1_RIE (1 << 0)
+#define SSCR1_TIE (1 << 1)
+#define SSCR1_LBM (1 << 2)
+#define SSCR1_MWDS (1 << 5)
+#define SSCR1_TFT(x) ((((x) >> 6) & 0xf) + 1)
+#define SSCR1_RFT(x) ((((x) >> 10) & 0xf) + 1)
+#define SSCR1_EFWR (1 << 14)
+#define SSCR1_PINTE (1 << 18)
+#define SSCR1_TINTE (1 << 19)
+#define SSCR1_RSRE (1 << 20)
+#define SSCR1_TSRE (1 << 21)
+#define SSCR1_EBCEI (1 << 29)
+#define SSITR_INT (7 << 5)
+#define SSSR_TNF (1 << 2)
+#define SSSR_RNE (1 << 3)
+#define SSSR_TFS (1 << 5)
+#define SSSR_RFS (1 << 6)
+#define SSSR_ROR (1 << 7)
+#define SSSR_PINT (1 << 18)
+#define SSSR_TINT (1 << 19)
+#define SSSR_EOC (1 << 20)
+#define SSSR_TUR (1 << 21)
+#define SSSR_BCE (1 << 23)
+#define SSSR_RW 0x00bc0080
+
+static void pxa2xx_ssp_int_update(PXA2xxSSPState *s)
+{
+ int level = 0;
+
+ level |= s->ssitr & SSITR_INT;
+ level |= (s->sssr & SSSR_BCE) && (s->sscr[1] & SSCR1_EBCEI);
+ level |= (s->sssr & SSSR_TUR) && !(s->sscr[0] & SSCR0_TIM);
+ level |= (s->sssr & SSSR_EOC) && (s->sssr & (SSSR_TINT | SSSR_PINT));
+ level |= (s->sssr & SSSR_TINT) && (s->sscr[1] & SSCR1_TINTE);
+ level |= (s->sssr & SSSR_PINT) && (s->sscr[1] & SSCR1_PINTE);
+ level |= (s->sssr & SSSR_ROR) && !(s->sscr[0] & SSCR0_RIM);
+ level |= (s->sssr & SSSR_RFS) && (s->sscr[1] & SSCR1_RIE);
+ level |= (s->sssr & SSSR_TFS) && (s->sscr[1] & SSCR1_TIE);
+ qemu_set_irq(s->irq, !!level);
+}
+
+static void pxa2xx_ssp_fifo_update(PXA2xxSSPState *s)
+{
+ s->sssr &= ~(0xf << 12); /* Clear RFL */
+ s->sssr &= ~(0xf << 8); /* Clear TFL */
+ s->sssr &= ~SSSR_TFS;
+ s->sssr &= ~SSSR_TNF;
+ if (s->enable) {
+ s->sssr |= ((s->rx_level - 1) & 0xf) << 12;
+ if (s->rx_level >= SSCR1_RFT(s->sscr[1]))
+ s->sssr |= SSSR_RFS;
+ else
+ s->sssr &= ~SSSR_RFS;
+ if (s->rx_level)
+ s->sssr |= SSSR_RNE;
+ else
+ s->sssr &= ~SSSR_RNE;
+ /* TX FIFO is never filled, so it is always in underrun
+ condition if SSP is enabled */
+ s->sssr |= SSSR_TFS;
+ s->sssr |= SSSR_TNF;
+ }
+
+ pxa2xx_ssp_int_update(s);
+}
+
+static uint64_t pxa2xx_ssp_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PXA2xxSSPState *s = (PXA2xxSSPState *) opaque;
+ uint32_t retval;
+
+ switch (addr) {
+ case SSCR0:
+ return s->sscr[0];
+ case SSCR1:
+ return s->sscr[1];
+ case SSPSP:
+ return s->sspsp;
+ case SSTO:
+ return s->ssto;
+ case SSITR:
+ return s->ssitr;
+ case SSSR:
+ return s->sssr | s->ssitr;
+ case SSDR:
+ if (!s->enable)
+ return 0xffffffff;
+ if (s->rx_level < 1) {
+ printf("%s: SSP Rx Underrun\n", __FUNCTION__);
+ return 0xffffffff;
+ }
+ s->rx_level --;
+ retval = s->rx_fifo[s->rx_start ++];
+ s->rx_start &= 0xf;
+ pxa2xx_ssp_fifo_update(s);
+ return retval;
+ case SSTSA:
+ return s->sstsa;
+ case SSRSA:
+ return s->ssrsa;
+ case SSTSS:
+ return 0;
+ case SSACD:
+ return s->ssacd;
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void pxa2xx_ssp_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ PXA2xxSSPState *s = (PXA2xxSSPState *) opaque;
+ uint32_t value = value64;
+
+ switch (addr) {
+ case SSCR0:
+ s->sscr[0] = value & 0xc7ffffff;
+ s->enable = value & SSCR0_SSE;
+ if (value & SSCR0_MOD)
+ printf("%s: Attempt to use network mode\n", __FUNCTION__);
+ if (s->enable && SSCR0_DSS(value) < 4)
+ printf("%s: Wrong data size: %i bits\n", __FUNCTION__,
+ SSCR0_DSS(value));
+ if (!(value & SSCR0_SSE)) {
+ s->sssr = 0;
+ s->ssitr = 0;
+ s->rx_level = 0;
+ }
+ pxa2xx_ssp_fifo_update(s);
+ break;
+
+ case SSCR1:
+ s->sscr[1] = value;
+ if (value & (SSCR1_LBM | SSCR1_EFWR))
+ printf("%s: Attempt to use SSP test mode\n", __FUNCTION__);
+ pxa2xx_ssp_fifo_update(s);
+ break;
+
+ case SSPSP:
+ s->sspsp = value;
+ break;
+
+ case SSTO:
+ s->ssto = value;
+ break;
+
+ case SSITR:
+ s->ssitr = value & SSITR_INT;
+ pxa2xx_ssp_int_update(s);
+ break;
+
+ case SSSR:
+ s->sssr &= ~(value & SSSR_RW);
+ pxa2xx_ssp_int_update(s);
+ break;
+
+ case SSDR:
+ if (SSCR0_UWIRE(s->sscr[0])) {
+ if (s->sscr[1] & SSCR1_MWDS)
+ value &= 0xffff;
+ else
+ value &= 0xff;
+ } else
+ /* Note how 32bits overflow does no harm here */
+ value &= (1 << SSCR0_DSS(s->sscr[0])) - 1;
+
+ /* Data goes from here to the Tx FIFO and is shifted out from
+ * there directly to the slave, no need to buffer it.
+ */
+ if (s->enable) {
+ uint32_t readval;
+ readval = ssi_transfer(s->bus, value);
+ if (s->rx_level < 0x10) {
+ s->rx_fifo[(s->rx_start + s->rx_level ++) & 0xf] = readval;
+ } else {
+ s->sssr |= SSSR_ROR;
+ }
+ }
+ pxa2xx_ssp_fifo_update(s);
+ break;
+
+ case SSTSA:
+ s->sstsa = value;
+ break;
+
+ case SSRSA:
+ s->ssrsa = value;
+ break;
+
+ case SSACD:
+ s->ssacd = value;
+ break;
+
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps pxa2xx_ssp_ops = {
+ .read = pxa2xx_ssp_read,
+ .write = pxa2xx_ssp_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pxa2xx_ssp_reset(DeviceState *d)
+{
+ PXA2xxSSPState *s = PXA2XX_SSP(d);
+
+ s->enable = 0;
+ s->sscr[0] = s->sscr[1] = 0;
+ s->sspsp = 0;
+ s->ssto = 0;
+ s->ssitr = 0;
+ s->sssr = 0;
+ s->sstsa = 0;
+ s->ssrsa = 0;
+ s->ssacd = 0;
+ s->rx_start = s->rx_level = 0;
+}
+
+static int pxa2xx_ssp_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PXA2xxSSPState *s = PXA2XX_SSP(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_ssp_ops, s,
+ "pxa2xx-ssp", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->bus = ssi_create_bus(dev, "ssi");
+ return 0;
+}
+
+/* Real-Time Clock */
+#define RCNR 0x00 /* RTC Counter register */
+#define RTAR 0x04 /* RTC Alarm register */
+#define RTSR 0x08 /* RTC Status register */
+#define RTTR 0x0c /* RTC Timer Trim register */
+#define RDCR 0x10 /* RTC Day Counter register */
+#define RYCR 0x14 /* RTC Year Counter register */
+#define RDAR1 0x18 /* RTC Wristwatch Day Alarm register 1 */
+#define RYAR1 0x1c /* RTC Wristwatch Year Alarm register 1 */
+#define RDAR2 0x20 /* RTC Wristwatch Day Alarm register 2 */
+#define RYAR2 0x24 /* RTC Wristwatch Year Alarm register 2 */
+#define SWCR 0x28 /* RTC Stopwatch Counter register */
+#define SWAR1 0x2c /* RTC Stopwatch Alarm register 1 */
+#define SWAR2 0x30 /* RTC Stopwatch Alarm register 2 */
+#define RTCPICR 0x34 /* RTC Periodic Interrupt Counter register */
+#define PIAR 0x38 /* RTC Periodic Interrupt Alarm register */
+
+#define TYPE_PXA2XX_RTC "pxa2xx_rtc"
+#define PXA2XX_RTC(obj) \
+ OBJECT_CHECK(PXA2xxRTCState, (obj), TYPE_PXA2XX_RTC)
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t rttr;
+ uint32_t rtsr;
+ uint32_t rtar;
+ uint32_t rdar1;
+ uint32_t rdar2;
+ uint32_t ryar1;
+ uint32_t ryar2;
+ uint32_t swar1;
+ uint32_t swar2;
+ uint32_t piar;
+ uint32_t last_rcnr;
+ uint32_t last_rdcr;
+ uint32_t last_rycr;
+ uint32_t last_swcr;
+ uint32_t last_rtcpicr;
+ int64_t last_hz;
+ int64_t last_sw;
+ int64_t last_pi;
+ QEMUTimer *rtc_hz;
+ QEMUTimer *rtc_rdal1;
+ QEMUTimer *rtc_rdal2;
+ QEMUTimer *rtc_swal1;
+ QEMUTimer *rtc_swal2;
+ QEMUTimer *rtc_pi;
+ qemu_irq rtc_irq;
+} PXA2xxRTCState;
+
+static inline void pxa2xx_rtc_int_update(PXA2xxRTCState *s)
+{
+ qemu_set_irq(s->rtc_irq, !!(s->rtsr & 0x2553));
+}
+
+static void pxa2xx_rtc_hzupdate(PXA2xxRTCState *s)
+{
+ int64_t rt = qemu_clock_get_ms(rtc_clock);
+ s->last_rcnr += ((rt - s->last_hz) << 15) /
+ (1000 * ((s->rttr & 0xffff) + 1));
+ s->last_rdcr += ((rt - s->last_hz) << 15) /
+ (1000 * ((s->rttr & 0xffff) + 1));
+ s->last_hz = rt;
+}
+
+static void pxa2xx_rtc_swupdate(PXA2xxRTCState *s)
+{
+ int64_t rt = qemu_clock_get_ms(rtc_clock);
+ if (s->rtsr & (1 << 12))
+ s->last_swcr += (rt - s->last_sw) / 10;
+ s->last_sw = rt;
+}
+
+static void pxa2xx_rtc_piupdate(PXA2xxRTCState *s)
+{
+ int64_t rt = qemu_clock_get_ms(rtc_clock);
+ if (s->rtsr & (1 << 15))
+ s->last_swcr += rt - s->last_pi;
+ s->last_pi = rt;
+}
+
+static inline void pxa2xx_rtc_alarm_update(PXA2xxRTCState *s,
+ uint32_t rtsr)
+{
+ if ((rtsr & (1 << 2)) && !(rtsr & (1 << 0)))
+ timer_mod(s->rtc_hz, s->last_hz +
+ (((s->rtar - s->last_rcnr) * 1000 *
+ ((s->rttr & 0xffff) + 1)) >> 15));
+ else
+ timer_del(s->rtc_hz);
+
+ if ((rtsr & (1 << 5)) && !(rtsr & (1 << 4)))
+ timer_mod(s->rtc_rdal1, s->last_hz +
+ (((s->rdar1 - s->last_rdcr) * 1000 *
+ ((s->rttr & 0xffff) + 1)) >> 15)); /* TODO: fixup */
+ else
+ timer_del(s->rtc_rdal1);
+
+ if ((rtsr & (1 << 7)) && !(rtsr & (1 << 6)))
+ timer_mod(s->rtc_rdal2, s->last_hz +
+ (((s->rdar2 - s->last_rdcr) * 1000 *
+ ((s->rttr & 0xffff) + 1)) >> 15)); /* TODO: fixup */
+ else
+ timer_del(s->rtc_rdal2);
+
+ if ((rtsr & 0x1200) == 0x1200 && !(rtsr & (1 << 8)))
+ timer_mod(s->rtc_swal1, s->last_sw +
+ (s->swar1 - s->last_swcr) * 10); /* TODO: fixup */
+ else
+ timer_del(s->rtc_swal1);
+
+ if ((rtsr & 0x1800) == 0x1800 && !(rtsr & (1 << 10)))
+ timer_mod(s->rtc_swal2, s->last_sw +
+ (s->swar2 - s->last_swcr) * 10); /* TODO: fixup */
+ else
+ timer_del(s->rtc_swal2);
+
+ if ((rtsr & 0xc000) == 0xc000 && !(rtsr & (1 << 13)))
+ timer_mod(s->rtc_pi, s->last_pi +
+ (s->piar & 0xffff) - s->last_rtcpicr);
+ else
+ timer_del(s->rtc_pi);
+}
+
+static inline void pxa2xx_rtc_hz_tick(void *opaque)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+ s->rtsr |= (1 << 0);
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ pxa2xx_rtc_int_update(s);
+}
+
+static inline void pxa2xx_rtc_rdal1_tick(void *opaque)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+ s->rtsr |= (1 << 4);
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ pxa2xx_rtc_int_update(s);
+}
+
+static inline void pxa2xx_rtc_rdal2_tick(void *opaque)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+ s->rtsr |= (1 << 6);
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ pxa2xx_rtc_int_update(s);
+}
+
+static inline void pxa2xx_rtc_swal1_tick(void *opaque)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+ s->rtsr |= (1 << 8);
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ pxa2xx_rtc_int_update(s);
+}
+
+static inline void pxa2xx_rtc_swal2_tick(void *opaque)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+ s->rtsr |= (1 << 10);
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ pxa2xx_rtc_int_update(s);
+}
+
+static inline void pxa2xx_rtc_pi_tick(void *opaque)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+ s->rtsr |= (1 << 13);
+ pxa2xx_rtc_piupdate(s);
+ s->last_rtcpicr = 0;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ pxa2xx_rtc_int_update(s);
+}
+
+static uint64_t pxa2xx_rtc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+
+ switch (addr) {
+ case RTTR:
+ return s->rttr;
+ case RTSR:
+ return s->rtsr;
+ case RTAR:
+ return s->rtar;
+ case RDAR1:
+ return s->rdar1;
+ case RDAR2:
+ return s->rdar2;
+ case RYAR1:
+ return s->ryar1;
+ case RYAR2:
+ return s->ryar2;
+ case SWAR1:
+ return s->swar1;
+ case SWAR2:
+ return s->swar2;
+ case PIAR:
+ return s->piar;
+ case RCNR:
+ return s->last_rcnr +
+ ((qemu_clock_get_ms(rtc_clock) - s->last_hz) << 15) /
+ (1000 * ((s->rttr & 0xffff) + 1));
+ case RDCR:
+ return s->last_rdcr +
+ ((qemu_clock_get_ms(rtc_clock) - s->last_hz) << 15) /
+ (1000 * ((s->rttr & 0xffff) + 1));
+ case RYCR:
+ return s->last_rycr;
+ case SWCR:
+ if (s->rtsr & (1 << 12))
+ return s->last_swcr +
+ (qemu_clock_get_ms(rtc_clock) - s->last_sw) / 10;
+ else
+ return s->last_swcr;
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void pxa2xx_rtc_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+ uint32_t value = value64;
+
+ switch (addr) {
+ case RTTR:
+ if (!(s->rttr & (1U << 31))) {
+ pxa2xx_rtc_hzupdate(s);
+ s->rttr = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ }
+ break;
+
+ case RTSR:
+ if ((s->rtsr ^ value) & (1 << 15))
+ pxa2xx_rtc_piupdate(s);
+
+ if ((s->rtsr ^ value) & (1 << 12))
+ pxa2xx_rtc_swupdate(s);
+
+ if (((s->rtsr ^ value) & 0x4aac) | (value & ~0xdaac))
+ pxa2xx_rtc_alarm_update(s, value);
+
+ s->rtsr = (value & 0xdaac) | (s->rtsr & ~(value & ~0xdaac));
+ pxa2xx_rtc_int_update(s);
+ break;
+
+ case RTAR:
+ s->rtar = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case RDAR1:
+ s->rdar1 = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case RDAR2:
+ s->rdar2 = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case RYAR1:
+ s->ryar1 = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case RYAR2:
+ s->ryar2 = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case SWAR1:
+ pxa2xx_rtc_swupdate(s);
+ s->swar1 = value;
+ s->last_swcr = 0;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case SWAR2:
+ s->swar2 = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case PIAR:
+ s->piar = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case RCNR:
+ pxa2xx_rtc_hzupdate(s);
+ s->last_rcnr = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case RDCR:
+ pxa2xx_rtc_hzupdate(s);
+ s->last_rdcr = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case RYCR:
+ s->last_rycr = value;
+ break;
+
+ case SWCR:
+ pxa2xx_rtc_swupdate(s);
+ s->last_swcr = value;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ case RTCPICR:
+ pxa2xx_rtc_piupdate(s);
+ s->last_rtcpicr = value & 0xffff;
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+ break;
+
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_rtc_ops = {
+ .read = pxa2xx_rtc_read,
+ .write = pxa2xx_rtc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pxa2xx_rtc_init(SysBusDevice *dev)
+{
+ PXA2xxRTCState *s = PXA2XX_RTC(dev);
+ struct tm tm;
+ int wom;
+
+ s->rttr = 0x7fff;
+ s->rtsr = 0;
+
+ qemu_get_timedate(&tm, 0);
+ wom = ((tm.tm_mday - 1) / 7) + 1;
+
+ s->last_rcnr = (uint32_t) mktimegm(&tm);
+ s->last_rdcr = (wom << 20) | ((tm.tm_wday + 1) << 17) |
+ (tm.tm_hour << 12) | (tm.tm_min << 6) | tm.tm_sec;
+ s->last_rycr = ((tm.tm_year + 1900) << 9) |
+ ((tm.tm_mon + 1) << 5) | tm.tm_mday;
+ s->last_swcr = (tm.tm_hour << 19) |
+ (tm.tm_min << 13) | (tm.tm_sec << 7);
+ s->last_rtcpicr = 0;
+ s->last_hz = s->last_sw = s->last_pi = qemu_clock_get_ms(rtc_clock);
+
+ s->rtc_hz = timer_new_ms(rtc_clock, pxa2xx_rtc_hz_tick, s);
+ s->rtc_rdal1 = timer_new_ms(rtc_clock, pxa2xx_rtc_rdal1_tick, s);
+ s->rtc_rdal2 = timer_new_ms(rtc_clock, pxa2xx_rtc_rdal2_tick, s);
+ s->rtc_swal1 = timer_new_ms(rtc_clock, pxa2xx_rtc_swal1_tick, s);
+ s->rtc_swal2 = timer_new_ms(rtc_clock, pxa2xx_rtc_swal2_tick, s);
+ s->rtc_pi = timer_new_ms(rtc_clock, pxa2xx_rtc_pi_tick, s);
+
+ sysbus_init_irq(dev, &s->rtc_irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_rtc_ops, s,
+ "pxa2xx-rtc", 0x10000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void pxa2xx_rtc_pre_save(void *opaque)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+
+ pxa2xx_rtc_hzupdate(s);
+ pxa2xx_rtc_piupdate(s);
+ pxa2xx_rtc_swupdate(s);
+}
+
+static int pxa2xx_rtc_post_load(void *opaque, int version_id)
+{
+ PXA2xxRTCState *s = (PXA2xxRTCState *) opaque;
+
+ pxa2xx_rtc_alarm_update(s, s->rtsr);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_pxa2xx_rtc_regs = {
+ .name = "pxa2xx_rtc",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_save = pxa2xx_rtc_pre_save,
+ .post_load = pxa2xx_rtc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(rttr, PXA2xxRTCState),
+ VMSTATE_UINT32(rtsr, PXA2xxRTCState),
+ VMSTATE_UINT32(rtar, PXA2xxRTCState),
+ VMSTATE_UINT32(rdar1, PXA2xxRTCState),
+ VMSTATE_UINT32(rdar2, PXA2xxRTCState),
+ VMSTATE_UINT32(ryar1, PXA2xxRTCState),
+ VMSTATE_UINT32(ryar2, PXA2xxRTCState),
+ VMSTATE_UINT32(swar1, PXA2xxRTCState),
+ VMSTATE_UINT32(swar2, PXA2xxRTCState),
+ VMSTATE_UINT32(piar, PXA2xxRTCState),
+ VMSTATE_UINT32(last_rcnr, PXA2xxRTCState),
+ VMSTATE_UINT32(last_rdcr, PXA2xxRTCState),
+ VMSTATE_UINT32(last_rycr, PXA2xxRTCState),
+ VMSTATE_UINT32(last_swcr, PXA2xxRTCState),
+ VMSTATE_UINT32(last_rtcpicr, PXA2xxRTCState),
+ VMSTATE_INT64(last_hz, PXA2xxRTCState),
+ VMSTATE_INT64(last_sw, PXA2xxRTCState),
+ VMSTATE_INT64(last_pi, PXA2xxRTCState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void pxa2xx_rtc_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pxa2xx_rtc_init;
+ dc->desc = "PXA2xx RTC Controller";
+ dc->vmsd = &vmstate_pxa2xx_rtc_regs;
+}
+
+static const TypeInfo pxa2xx_rtc_sysbus_info = {
+ .name = TYPE_PXA2XX_RTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxRTCState),
+ .class_init = pxa2xx_rtc_sysbus_class_init,
+};
+
+/* I2C Interface */
+
+#define TYPE_PXA2XX_I2C_SLAVE "pxa2xx-i2c-slave"
+#define PXA2XX_I2C_SLAVE(obj) \
+ OBJECT_CHECK(PXA2xxI2CSlaveState, (obj), TYPE_PXA2XX_I2C_SLAVE)
+
+typedef struct PXA2xxI2CSlaveState {
+ I2CSlave parent_obj;
+
+ PXA2xxI2CState *host;
+} PXA2xxI2CSlaveState;
+
+#define TYPE_PXA2XX_I2C "pxa2xx_i2c"
+#define PXA2XX_I2C(obj) \
+ OBJECT_CHECK(PXA2xxI2CState, (obj), TYPE_PXA2XX_I2C)
+
+struct PXA2xxI2CState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ PXA2xxI2CSlaveState *slave;
+ I2CBus *bus;
+ qemu_irq irq;
+ uint32_t offset;
+ uint32_t region_size;
+
+ uint16_t control;
+ uint16_t status;
+ uint8_t ibmr;
+ uint8_t data;
+};
+
+#define IBMR 0x80 /* I2C Bus Monitor register */
+#define IDBR 0x88 /* I2C Data Buffer register */
+#define ICR 0x90 /* I2C Control register */
+#define ISR 0x98 /* I2C Status register */
+#define ISAR 0xa0 /* I2C Slave Address register */
+
+static void pxa2xx_i2c_update(PXA2xxI2CState *s)
+{
+ uint16_t level = 0;
+ level |= s->status & s->control & (1 << 10); /* BED */
+ level |= (s->status & (1 << 7)) && (s->control & (1 << 9)); /* IRF */
+ level |= (s->status & (1 << 6)) && (s->control & (1 << 8)); /* ITE */
+ level |= s->status & (1 << 9); /* SAD */
+ qemu_set_irq(s->irq, !!level);
+}
+
+/* These are only stubs now. */
+static void pxa2xx_i2c_event(I2CSlave *i2c, enum i2c_event event)
+{
+ PXA2xxI2CSlaveState *slave = PXA2XX_I2C_SLAVE(i2c);
+ PXA2xxI2CState *s = slave->host;
+
+ switch (event) {
+ case I2C_START_SEND:
+ s->status |= (1 << 9); /* set SAD */
+ s->status &= ~(1 << 0); /* clear RWM */
+ break;
+ case I2C_START_RECV:
+ s->status |= (1 << 9); /* set SAD */
+ s->status |= 1 << 0; /* set RWM */
+ break;
+ case I2C_FINISH:
+ s->status |= (1 << 4); /* set SSD */
+ break;
+ case I2C_NACK:
+ s->status |= 1 << 1; /* set ACKNAK */
+ break;
+ }
+ pxa2xx_i2c_update(s);
+}
+
+static int pxa2xx_i2c_rx(I2CSlave *i2c)
+{
+ PXA2xxI2CSlaveState *slave = PXA2XX_I2C_SLAVE(i2c);
+ PXA2xxI2CState *s = slave->host;
+
+ if ((s->control & (1 << 14)) || !(s->control & (1 << 6))) {
+ return 0;
+ }
+
+ if (s->status & (1 << 0)) { /* RWM */
+ s->status |= 1 << 6; /* set ITE */
+ }
+ pxa2xx_i2c_update(s);
+
+ return s->data;
+}
+
+static int pxa2xx_i2c_tx(I2CSlave *i2c, uint8_t data)
+{
+ PXA2xxI2CSlaveState *slave = PXA2XX_I2C_SLAVE(i2c);
+ PXA2xxI2CState *s = slave->host;
+
+ if ((s->control & (1 << 14)) || !(s->control & (1 << 6))) {
+ return 1;
+ }
+
+ if (!(s->status & (1 << 0))) { /* RWM */
+ s->status |= 1 << 7; /* set IRF */
+ s->data = data;
+ }
+ pxa2xx_i2c_update(s);
+
+ return 1;
+}
+
+static uint64_t pxa2xx_i2c_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PXA2xxI2CState *s = (PXA2xxI2CState *) opaque;
+ I2CSlave *slave;
+
+ addr -= s->offset;
+ switch (addr) {
+ case ICR:
+ return s->control;
+ case ISR:
+ return s->status | (i2c_bus_busy(s->bus) << 2);
+ case ISAR:
+ slave = I2C_SLAVE(s->slave);
+ return slave->address;
+ case IDBR:
+ return s->data;
+ case IBMR:
+ if (s->status & (1 << 2))
+ s->ibmr ^= 3; /* Fake SCL and SDA pin changes */
+ else
+ s->ibmr = 0;
+ return s->ibmr;
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void pxa2xx_i2c_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ PXA2xxI2CState *s = (PXA2xxI2CState *) opaque;
+ uint32_t value = value64;
+ int ack;
+
+ addr -= s->offset;
+ switch (addr) {
+ case ICR:
+ s->control = value & 0xfff7;
+ if ((value & (1 << 3)) && (value & (1 << 6))) { /* TB and IUE */
+ /* TODO: slave mode */
+ if (value & (1 << 0)) { /* START condition */
+ if (s->data & 1)
+ s->status |= 1 << 0; /* set RWM */
+ else
+ s->status &= ~(1 << 0); /* clear RWM */
+ ack = !i2c_start_transfer(s->bus, s->data >> 1, s->data & 1);
+ } else {
+ if (s->status & (1 << 0)) { /* RWM */
+ s->data = i2c_recv(s->bus);
+ if (value & (1 << 2)) /* ACKNAK */
+ i2c_nack(s->bus);
+ ack = 1;
+ } else
+ ack = !i2c_send(s->bus, s->data);
+ }
+
+ if (value & (1 << 1)) /* STOP condition */
+ i2c_end_transfer(s->bus);
+
+ if (ack) {
+ if (value & (1 << 0)) /* START condition */
+ s->status |= 1 << 6; /* set ITE */
+ else
+ if (s->status & (1 << 0)) /* RWM */
+ s->status |= 1 << 7; /* set IRF */
+ else
+ s->status |= 1 << 6; /* set ITE */
+ s->status &= ~(1 << 1); /* clear ACKNAK */
+ } else {
+ s->status |= 1 << 6; /* set ITE */
+ s->status |= 1 << 10; /* set BED */
+ s->status |= 1 << 1; /* set ACKNAK */
+ }
+ }
+ if (!(value & (1 << 3)) && (value & (1 << 6))) /* !TB and IUE */
+ if (value & (1 << 4)) /* MA */
+ i2c_end_transfer(s->bus);
+ pxa2xx_i2c_update(s);
+ break;
+
+ case ISR:
+ s->status &= ~(value & 0x07f0);
+ pxa2xx_i2c_update(s);
+ break;
+
+ case ISAR:
+ i2c_set_slave_address(I2C_SLAVE(s->slave), value & 0x7f);
+ break;
+
+ case IDBR:
+ s->data = value & 0xff;
+ break;
+
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_i2c_ops = {
+ .read = pxa2xx_i2c_read,
+ .write = pxa2xx_i2c_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pxa2xx_i2c_slave = {
+ .name = "pxa2xx_i2c_slave",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_I2C_SLAVE(parent_obj, PXA2xxI2CSlaveState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pxa2xx_i2c = {
+ .name = "pxa2xx_i2c",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(control, PXA2xxI2CState),
+ VMSTATE_UINT16(status, PXA2xxI2CState),
+ VMSTATE_UINT8(ibmr, PXA2xxI2CState),
+ VMSTATE_UINT8(data, PXA2xxI2CState),
+ VMSTATE_STRUCT_POINTER(slave, PXA2xxI2CState,
+ vmstate_pxa2xx_i2c_slave, PXA2xxI2CSlaveState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int pxa2xx_i2c_slave_init(I2CSlave *i2c)
+{
+ /* Nothing to do. */
+ return 0;
+}
+
+static void pxa2xx_i2c_slave_class_init(ObjectClass *klass, void *data)
+{
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = pxa2xx_i2c_slave_init;
+ k->event = pxa2xx_i2c_event;
+ k->recv = pxa2xx_i2c_rx;
+ k->send = pxa2xx_i2c_tx;
+}
+
+static const TypeInfo pxa2xx_i2c_slave_info = {
+ .name = TYPE_PXA2XX_I2C_SLAVE,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(PXA2xxI2CSlaveState),
+ .class_init = pxa2xx_i2c_slave_class_init,
+};
+
+PXA2xxI2CState *pxa2xx_i2c_init(hwaddr base,
+ qemu_irq irq, uint32_t region_size)
+{
+ DeviceState *dev;
+ SysBusDevice *i2c_dev;
+ PXA2xxI2CState *s;
+ I2CBus *i2cbus;
+
+ dev = qdev_create(NULL, TYPE_PXA2XX_I2C);
+ qdev_prop_set_uint32(dev, "size", region_size + 1);
+ qdev_prop_set_uint32(dev, "offset", base & region_size);
+ qdev_init_nofail(dev);
+
+ i2c_dev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(i2c_dev, 0, base & ~region_size);
+ sysbus_connect_irq(i2c_dev, 0, irq);
+
+ s = PXA2XX_I2C(i2c_dev);
+ /* FIXME: Should the slave device really be on a separate bus? */
+ i2cbus = i2c_init_bus(dev, "dummy");
+ dev = i2c_create_slave(i2cbus, TYPE_PXA2XX_I2C_SLAVE, 0);
+ s->slave = PXA2XX_I2C_SLAVE(dev);
+ s->slave->host = s;
+
+ return s;
+}
+
+static int pxa2xx_i2c_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PXA2xxI2CState *s = PXA2XX_I2C(dev);
+
+ s->bus = i2c_init_bus(dev, "i2c");
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_i2c_ops, s,
+ "pxa2xx-i2c", s->region_size);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ return 0;
+}
+
+I2CBus *pxa2xx_i2c_bus(PXA2xxI2CState *s)
+{
+ return s->bus;
+}
+
+static Property pxa2xx_i2c_properties[] = {
+ DEFINE_PROP_UINT32("size", PXA2xxI2CState, region_size, 0x10000),
+ DEFINE_PROP_UINT32("offset", PXA2xxI2CState, offset, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxa2xx_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pxa2xx_i2c_initfn;
+ dc->desc = "PXA2xx I2C Bus Controller";
+ dc->vmsd = &vmstate_pxa2xx_i2c;
+ dc->props = pxa2xx_i2c_properties;
+}
+
+static const TypeInfo pxa2xx_i2c_info = {
+ .name = TYPE_PXA2XX_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxI2CState),
+ .class_init = pxa2xx_i2c_class_init,
+};
+
+/* PXA Inter-IC Sound Controller */
+static void pxa2xx_i2s_reset(PXA2xxI2SState *i2s)
+{
+ i2s->rx_len = 0;
+ i2s->tx_len = 0;
+ i2s->fifo_len = 0;
+ i2s->clk = 0x1a;
+ i2s->control[0] = 0x00;
+ i2s->control[1] = 0x00;
+ i2s->status = 0x00;
+ i2s->mask = 0x00;
+}
+
+#define SACR_TFTH(val) ((val >> 8) & 0xf)
+#define SACR_RFTH(val) ((val >> 12) & 0xf)
+#define SACR_DREC(val) (val & (1 << 3))
+#define SACR_DPRL(val) (val & (1 << 4))
+
+static inline void pxa2xx_i2s_update(PXA2xxI2SState *i2s)
+{
+ int rfs, tfs;
+ rfs = SACR_RFTH(i2s->control[0]) < i2s->rx_len &&
+ !SACR_DREC(i2s->control[1]);
+ tfs = (i2s->tx_len || i2s->fifo_len < SACR_TFTH(i2s->control[0])) &&
+ i2s->enable && !SACR_DPRL(i2s->control[1]);
+
+ qemu_set_irq(i2s->rx_dma, rfs);
+ qemu_set_irq(i2s->tx_dma, tfs);
+
+ i2s->status &= 0xe0;
+ if (i2s->fifo_len < 16 || !i2s->enable)
+ i2s->status |= 1 << 0; /* TNF */
+ if (i2s->rx_len)
+ i2s->status |= 1 << 1; /* RNE */
+ if (i2s->enable)
+ i2s->status |= 1 << 2; /* BSY */
+ if (tfs)
+ i2s->status |= 1 << 3; /* TFS */
+ if (rfs)
+ i2s->status |= 1 << 4; /* RFS */
+ if (!(i2s->tx_len && i2s->enable))
+ i2s->status |= i2s->fifo_len << 8; /* TFL */
+ i2s->status |= MAX(i2s->rx_len, 0xf) << 12; /* RFL */
+
+ qemu_set_irq(i2s->irq, i2s->status & i2s->mask);
+}
+
+#define SACR0 0x00 /* Serial Audio Global Control register */
+#define SACR1 0x04 /* Serial Audio I2S/MSB-Justified Control register */
+#define SASR0 0x0c /* Serial Audio Interface and FIFO Status register */
+#define SAIMR 0x14 /* Serial Audio Interrupt Mask register */
+#define SAICR 0x18 /* Serial Audio Interrupt Clear register */
+#define SADIV 0x60 /* Serial Audio Clock Divider register */
+#define SADR 0x80 /* Serial Audio Data register */
+
+static uint64_t pxa2xx_i2s_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PXA2xxI2SState *s = (PXA2xxI2SState *) opaque;
+
+ switch (addr) {
+ case SACR0:
+ return s->control[0];
+ case SACR1:
+ return s->control[1];
+ case SASR0:
+ return s->status;
+ case SAIMR:
+ return s->mask;
+ case SAICR:
+ return 0;
+ case SADIV:
+ return s->clk;
+ case SADR:
+ if (s->rx_len > 0) {
+ s->rx_len --;
+ pxa2xx_i2s_update(s);
+ return s->codec_in(s->opaque);
+ }
+ return 0;
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void pxa2xx_i2s_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ PXA2xxI2SState *s = (PXA2xxI2SState *) opaque;
+ uint32_t *sample;
+
+ switch (addr) {
+ case SACR0:
+ if (value & (1 << 3)) /* RST */
+ pxa2xx_i2s_reset(s);
+ s->control[0] = value & 0xff3d;
+ if (!s->enable && (value & 1) && s->tx_len) { /* ENB */
+ for (sample = s->fifo; s->fifo_len > 0; s->fifo_len --, sample ++)
+ s->codec_out(s->opaque, *sample);
+ s->status &= ~(1 << 7); /* I2SOFF */
+ }
+ if (value & (1 << 4)) /* EFWR */
+ printf("%s: Attempt to use special function\n", __FUNCTION__);
+ s->enable = (value & 9) == 1; /* ENB && !RST*/
+ pxa2xx_i2s_update(s);
+ break;
+ case SACR1:
+ s->control[1] = value & 0x0039;
+ if (value & (1 << 5)) /* ENLBF */
+ printf("%s: Attempt to use loopback function\n", __FUNCTION__);
+ if (value & (1 << 4)) /* DPRL */
+ s->fifo_len = 0;
+ pxa2xx_i2s_update(s);
+ break;
+ case SAIMR:
+ s->mask = value & 0x0078;
+ pxa2xx_i2s_update(s);
+ break;
+ case SAICR:
+ s->status &= ~(value & (3 << 5));
+ pxa2xx_i2s_update(s);
+ break;
+ case SADIV:
+ s->clk = value & 0x007f;
+ break;
+ case SADR:
+ if (s->tx_len && s->enable) {
+ s->tx_len --;
+ pxa2xx_i2s_update(s);
+ s->codec_out(s->opaque, value);
+ } else if (s->fifo_len < 16) {
+ s->fifo[s->fifo_len ++] = value;
+ pxa2xx_i2s_update(s);
+ }
+ break;
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_i2s_ops = {
+ .read = pxa2xx_i2s_read,
+ .write = pxa2xx_i2s_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pxa2xx_i2s = {
+ .name = "pxa2xx_i2s",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(control, PXA2xxI2SState, 2),
+ VMSTATE_UINT32(status, PXA2xxI2SState),
+ VMSTATE_UINT32(mask, PXA2xxI2SState),
+ VMSTATE_UINT32(clk, PXA2xxI2SState),
+ VMSTATE_INT32(enable, PXA2xxI2SState),
+ VMSTATE_INT32(rx_len, PXA2xxI2SState),
+ VMSTATE_INT32(tx_len, PXA2xxI2SState),
+ VMSTATE_INT32(fifo_len, PXA2xxI2SState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pxa2xx_i2s_data_req(void *opaque, int tx, int rx)
+{
+ PXA2xxI2SState *s = (PXA2xxI2SState *) opaque;
+ uint32_t *sample;
+
+ /* Signal FIFO errors */
+ if (s->enable && s->tx_len)
+ s->status |= 1 << 5; /* TUR */
+ if (s->enable && s->rx_len)
+ s->status |= 1 << 6; /* ROR */
+
+ /* Should be tx - MIN(tx, s->fifo_len) but we don't really need to
+ * handle the cases where it makes a difference. */
+ s->tx_len = tx - s->fifo_len;
+ s->rx_len = rx;
+ /* Note that is s->codec_out wasn't set, we wouldn't get called. */
+ if (s->enable)
+ for (sample = s->fifo; s->fifo_len; s->fifo_len --, sample ++)
+ s->codec_out(s->opaque, *sample);
+ pxa2xx_i2s_update(s);
+}
+
+static PXA2xxI2SState *pxa2xx_i2s_init(MemoryRegion *sysmem,
+ hwaddr base,
+ qemu_irq irq, qemu_irq rx_dma, qemu_irq tx_dma)
+{
+ PXA2xxI2SState *s = (PXA2xxI2SState *)
+ g_malloc0(sizeof(PXA2xxI2SState));
+
+ s->irq = irq;
+ s->rx_dma = rx_dma;
+ s->tx_dma = tx_dma;
+ s->data_req = pxa2xx_i2s_data_req;
+
+ pxa2xx_i2s_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &pxa2xx_i2s_ops, s,
+ "pxa2xx-i2s", 0x100000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ vmstate_register(NULL, base, &vmstate_pxa2xx_i2s, s);
+
+ return s;
+}
+
+/* PXA Fast Infra-red Communications Port */
+#define TYPE_PXA2XX_FIR "pxa2xx-fir"
+#define PXA2XX_FIR(obj) OBJECT_CHECK(PXA2xxFIrState, (obj), TYPE_PXA2XX_FIR)
+
+struct PXA2xxFIrState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq rx_dma;
+ qemu_irq tx_dma;
+ uint32_t enable;
+ CharDriverState *chr;
+
+ uint8_t control[3];
+ uint8_t status[2];
+
+ uint32_t rx_len;
+ uint32_t rx_start;
+ uint8_t rx_fifo[64];
+};
+
+static void pxa2xx_fir_reset(DeviceState *d)
+{
+ PXA2xxFIrState *s = PXA2XX_FIR(d);
+
+ s->control[0] = 0x00;
+ s->control[1] = 0x00;
+ s->control[2] = 0x00;
+ s->status[0] = 0x00;
+ s->status[1] = 0x00;
+ s->enable = 0;
+}
+
+static inline void pxa2xx_fir_update(PXA2xxFIrState *s)
+{
+ static const int tresh[4] = { 8, 16, 32, 0 };
+ int intr = 0;
+ if ((s->control[0] & (1 << 4)) && /* RXE */
+ s->rx_len >= tresh[s->control[2] & 3]) /* TRIG */
+ s->status[0] |= 1 << 4; /* RFS */
+ else
+ s->status[0] &= ~(1 << 4); /* RFS */
+ if (s->control[0] & (1 << 3)) /* TXE */
+ s->status[0] |= 1 << 3; /* TFS */
+ else
+ s->status[0] &= ~(1 << 3); /* TFS */
+ if (s->rx_len)
+ s->status[1] |= 1 << 2; /* RNE */
+ else
+ s->status[1] &= ~(1 << 2); /* RNE */
+ if (s->control[0] & (1 << 4)) /* RXE */
+ s->status[1] |= 1 << 0; /* RSY */
+ else
+ s->status[1] &= ~(1 << 0); /* RSY */
+
+ intr |= (s->control[0] & (1 << 5)) && /* RIE */
+ (s->status[0] & (1 << 4)); /* RFS */
+ intr |= (s->control[0] & (1 << 6)) && /* TIE */
+ (s->status[0] & (1 << 3)); /* TFS */
+ intr |= (s->control[2] & (1 << 4)) && /* TRAIL */
+ (s->status[0] & (1 << 6)); /* EOC */
+ intr |= (s->control[0] & (1 << 2)) && /* TUS */
+ (s->status[0] & (1 << 1)); /* TUR */
+ intr |= s->status[0] & 0x25; /* FRE, RAB, EIF */
+
+ qemu_set_irq(s->rx_dma, (s->status[0] >> 4) & 1);
+ qemu_set_irq(s->tx_dma, (s->status[0] >> 3) & 1);
+
+ qemu_set_irq(s->irq, intr && s->enable);
+}
+
+#define ICCR0 0x00 /* FICP Control register 0 */
+#define ICCR1 0x04 /* FICP Control register 1 */
+#define ICCR2 0x08 /* FICP Control register 2 */
+#define ICDR 0x0c /* FICP Data register */
+#define ICSR0 0x14 /* FICP Status register 0 */
+#define ICSR1 0x18 /* FICP Status register 1 */
+#define ICFOR 0x1c /* FICP FIFO Occupancy Status register */
+
+static uint64_t pxa2xx_fir_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PXA2xxFIrState *s = (PXA2xxFIrState *) opaque;
+ uint8_t ret;
+
+ switch (addr) {
+ case ICCR0:
+ return s->control[0];
+ case ICCR1:
+ return s->control[1];
+ case ICCR2:
+ return s->control[2];
+ case ICDR:
+ s->status[0] &= ~0x01;
+ s->status[1] &= ~0x72;
+ if (s->rx_len) {
+ s->rx_len --;
+ ret = s->rx_fifo[s->rx_start ++];
+ s->rx_start &= 63;
+ pxa2xx_fir_update(s);
+ return ret;
+ }
+ printf("%s: Rx FIFO underrun.\n", __FUNCTION__);
+ break;
+ case ICSR0:
+ return s->status[0];
+ case ICSR1:
+ return s->status[1] | (1 << 3); /* TNF */
+ case ICFOR:
+ return s->rx_len;
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void pxa2xx_fir_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ PXA2xxFIrState *s = (PXA2xxFIrState *) opaque;
+ uint32_t value = value64;
+ uint8_t ch;
+
+ switch (addr) {
+ case ICCR0:
+ s->control[0] = value;
+ if (!(value & (1 << 4))) /* RXE */
+ s->rx_len = s->rx_start = 0;
+ if (!(value & (1 << 3))) { /* TXE */
+ /* Nop */
+ }
+ s->enable = value & 1; /* ITR */
+ if (!s->enable)
+ s->status[0] = 0;
+ pxa2xx_fir_update(s);
+ break;
+ case ICCR1:
+ s->control[1] = value;
+ break;
+ case ICCR2:
+ s->control[2] = value & 0x3f;
+ pxa2xx_fir_update(s);
+ break;
+ case ICDR:
+ if (s->control[2] & (1 << 2)) /* TXP */
+ ch = value;
+ else
+ ch = ~value;
+ if (s->chr && s->enable && (s->control[0] & (1 << 3))) /* TXE */
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ break;
+ case ICSR0:
+ s->status[0] &= ~(value & 0x66);
+ pxa2xx_fir_update(s);
+ break;
+ case ICFOR:
+ break;
+ default:
+ printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_fir_ops = {
+ .read = pxa2xx_fir_read,
+ .write = pxa2xx_fir_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pxa2xx_fir_is_empty(void *opaque)
+{
+ PXA2xxFIrState *s = (PXA2xxFIrState *) opaque;
+ return (s->rx_len < 64);
+}
+
+static void pxa2xx_fir_rx(void *opaque, const uint8_t *buf, int size)
+{
+ PXA2xxFIrState *s = (PXA2xxFIrState *) opaque;
+ if (!(s->control[0] & (1 << 4))) /* RXE */
+ return;
+
+ while (size --) {
+ s->status[1] |= 1 << 4; /* EOF */
+ if (s->rx_len >= 64) {
+ s->status[1] |= 1 << 6; /* ROR */
+ break;
+ }
+
+ if (s->control[2] & (1 << 3)) /* RXP */
+ s->rx_fifo[(s->rx_start + s->rx_len ++) & 63] = *(buf ++);
+ else
+ s->rx_fifo[(s->rx_start + s->rx_len ++) & 63] = ~*(buf ++);
+ }
+
+ pxa2xx_fir_update(s);
+}
+
+static void pxa2xx_fir_event(void *opaque, int event)
+{
+}
+
+static void pxa2xx_fir_instance_init(Object *obj)
+{
+ PXA2xxFIrState *s = PXA2XX_FIR(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &pxa2xx_fir_ops, s,
+ "pxa2xx-fir", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->rx_dma);
+ sysbus_init_irq(sbd, &s->tx_dma);
+}
+
+static void pxa2xx_fir_realize(DeviceState *dev, Error **errp)
+{
+ PXA2xxFIrState *s = PXA2XX_FIR(dev);
+
+ if (s->chr) {
+ qemu_chr_fe_claim_no_fail(s->chr);
+ qemu_chr_add_handlers(s->chr, pxa2xx_fir_is_empty,
+ pxa2xx_fir_rx, pxa2xx_fir_event, s);
+ }
+}
+
+static bool pxa2xx_fir_vmstate_validate(void *opaque, int version_id)
+{
+ PXA2xxFIrState *s = opaque;
+
+ return s->rx_start < ARRAY_SIZE(s->rx_fifo);
+}
+
+static const VMStateDescription pxa2xx_fir_vmsd = {
+ .name = "pxa2xx-fir",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(enable, PXA2xxFIrState),
+ VMSTATE_UINT8_ARRAY(control, PXA2xxFIrState, 3),
+ VMSTATE_UINT8_ARRAY(status, PXA2xxFIrState, 2),
+ VMSTATE_UINT32(rx_len, PXA2xxFIrState),
+ VMSTATE_UINT32(rx_start, PXA2xxFIrState),
+ VMSTATE_VALIDATE("fifo is 64 bytes", pxa2xx_fir_vmstate_validate),
+ VMSTATE_UINT8_ARRAY(rx_fifo, PXA2xxFIrState, 64),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property pxa2xx_fir_properties[] = {
+ DEFINE_PROP_CHR("chardev", PXA2xxFIrState, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxa2xx_fir_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pxa2xx_fir_realize;
+ dc->vmsd = &pxa2xx_fir_vmsd;
+ dc->props = pxa2xx_fir_properties;
+ dc->reset = pxa2xx_fir_reset;
+}
+
+static const TypeInfo pxa2xx_fir_info = {
+ .name = TYPE_PXA2XX_FIR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxFIrState),
+ .class_init = pxa2xx_fir_class_init,
+ .instance_init = pxa2xx_fir_instance_init,
+};
+
+static PXA2xxFIrState *pxa2xx_fir_init(MemoryRegion *sysmem,
+ hwaddr base,
+ qemu_irq irq, qemu_irq rx_dma,
+ qemu_irq tx_dma,
+ CharDriverState *chr)
+{
+ DeviceState *dev;
+ SysBusDevice *sbd;
+
+ dev = qdev_create(NULL, TYPE_PXA2XX_FIR);
+ qdev_prop_set_chr(dev, "chardev", chr);
+ qdev_init_nofail(dev);
+ sbd = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(sbd, 0, base);
+ sysbus_connect_irq(sbd, 0, irq);
+ sysbus_connect_irq(sbd, 1, rx_dma);
+ sysbus_connect_irq(sbd, 2, tx_dma);
+ return PXA2XX_FIR(dev);
+}
+
+static void pxa2xx_reset(void *opaque, int line, int level)
+{
+ PXA2xxState *s = (PXA2xxState *) opaque;
+
+ if (level && (s->pm_regs[PCFR >> 2] & 0x10)) { /* GPR_EN */
+ cpu_reset(CPU(s->cpu));
+ /* TODO: reset peripherals */
+ }
+}
+
+/* Initialise a PXA270 integrated chip (ARM based core). */
+PXA2xxState *pxa270_init(MemoryRegion *address_space,
+ unsigned int sdram_size, const char *revision)
+{
+ PXA2xxState *s;
+ int i;
+ DriveInfo *dinfo;
+ s = (PXA2xxState *) g_malloc0(sizeof(PXA2xxState));
+
+ if (revision && strncmp(revision, "pxa27", 5)) {
+ fprintf(stderr, "Machine requires a PXA27x processor.\n");
+ exit(1);
+ }
+ if (!revision)
+ revision = "pxa270";
+
+ s->cpu = cpu_arm_init(revision);
+ if (s->cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ s->reset = qemu_allocate_irq(pxa2xx_reset, s, 0);
+
+ /* SDRAM & Internal Memory Storage */
+ memory_region_init_ram(&s->sdram, NULL, "pxa270.sdram", sdram_size,
+ &error_abort);
+ vmstate_register_ram_global(&s->sdram);
+ memory_region_add_subregion(address_space, PXA2XX_SDRAM_BASE, &s->sdram);
+ memory_region_init_ram(&s->internal, NULL, "pxa270.internal", 0x40000,
+ &error_abort);
+ vmstate_register_ram_global(&s->internal);
+ memory_region_add_subregion(address_space, PXA2XX_INTERNAL_BASE,
+ &s->internal);
+
+ s->pic = pxa2xx_pic_init(0x40d00000, s->cpu);
+
+ s->dma = pxa27x_dma_init(0x40000000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_DMA));
+
+ sysbus_create_varargs("pxa27x-timer", 0x40a00000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 0),
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 1),
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 2),
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 3),
+ qdev_get_gpio_in(s->pic, PXA27X_PIC_OST_4_11),
+ NULL);
+
+ s->gpio = pxa2xx_gpio_init(0x40e00000, s->cpu, s->pic, 121);
+
+ dinfo = drive_get(IF_SD, 0, 0);
+ if (!dinfo) {
+ fprintf(stderr, "qemu: missing SecureDigital device\n");
+ exit(1);
+ }
+ s->mmc = pxa2xx_mmci_init(address_space, 0x41100000,
+ blk_by_legacy_dinfo(dinfo),
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_MMC),
+ qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_MMCI),
+ qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_MMCI));
+
+ for (i = 0; pxa270_serial[i].io_base; i++) {
+ if (serial_hds[i]) {
+ serial_mm_init(address_space, pxa270_serial[i].io_base, 2,
+ qdev_get_gpio_in(s->pic, pxa270_serial[i].irqn),
+ 14857000 / 16, serial_hds[i],
+ DEVICE_NATIVE_ENDIAN);
+ } else {
+ break;
+ }
+ }
+ if (serial_hds[i])
+ s->fir = pxa2xx_fir_init(address_space, 0x40800000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_ICP),
+ qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_ICP),
+ qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_ICP),
+ serial_hds[i]);
+
+ s->lcd = pxa2xx_lcdc_init(address_space, 0x44000000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_LCD));
+
+ s->cm_base = 0x41300000;
+ s->cm_regs[CCCR >> 2] = 0x02000210; /* 416.0 MHz */
+ s->clkcfg = 0x00000009; /* Turbo mode active */
+ memory_region_init_io(&s->cm_iomem, NULL, &pxa2xx_cm_ops, s, "pxa2xx-cm", 0x1000);
+ memory_region_add_subregion(address_space, s->cm_base, &s->cm_iomem);
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_cm, s);
+
+ pxa2xx_setup_cp14(s);
+
+ s->mm_base = 0x48000000;
+ s->mm_regs[MDMRS >> 2] = 0x00020002;
+ s->mm_regs[MDREFR >> 2] = 0x03ca4000;
+ s->mm_regs[MECR >> 2] = 0x00000001; /* Two PC Card sockets */
+ memory_region_init_io(&s->mm_iomem, NULL, &pxa2xx_mm_ops, s, "pxa2xx-mm", 0x1000);
+ memory_region_add_subregion(address_space, s->mm_base, &s->mm_iomem);
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_mm, s);
+
+ s->pm_base = 0x40f00000;
+ memory_region_init_io(&s->pm_iomem, NULL, &pxa2xx_pm_ops, s, "pxa2xx-pm", 0x100);
+ memory_region_add_subregion(address_space, s->pm_base, &s->pm_iomem);
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_pm, s);
+
+ for (i = 0; pxa27x_ssp[i].io_base; i ++);
+ s->ssp = (SSIBus **)g_malloc0(sizeof(SSIBus *) * i);
+ for (i = 0; pxa27x_ssp[i].io_base; i ++) {
+ DeviceState *dev;
+ dev = sysbus_create_simple(TYPE_PXA2XX_SSP, pxa27x_ssp[i].io_base,
+ qdev_get_gpio_in(s->pic, pxa27x_ssp[i].irqn));
+ s->ssp[i] = (SSIBus *)qdev_get_child_bus(dev, "ssi");
+ }
+
+ if (usb_enabled()) {
+ sysbus_create_simple("sysbus-ohci", 0x4c000000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_USBH1));
+ }
+
+ s->pcmcia[0] = pxa2xx_pcmcia_init(address_space, 0x20000000);
+ s->pcmcia[1] = pxa2xx_pcmcia_init(address_space, 0x30000000);
+
+ sysbus_create_simple(TYPE_PXA2XX_RTC, 0x40900000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_RTCALARM));
+
+ s->i2c[0] = pxa2xx_i2c_init(0x40301600,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_I2C), 0xffff);
+ s->i2c[1] = pxa2xx_i2c_init(0x40f00100,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_PWRI2C), 0xff);
+
+ s->i2s = pxa2xx_i2s_init(address_space, 0x40400000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_I2S),
+ qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_I2S),
+ qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_I2S));
+
+ s->kp = pxa27x_keypad_init(address_space, 0x41500000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_KEYPAD));
+
+ /* GPIO1 resets the processor */
+ /* The handler can be overridden by board-specific code */
+ qdev_connect_gpio_out(s->gpio, 1, s->reset);
+ return s;
+}
+
+/* Initialise a PXA255 integrated chip (ARM based core). */
+PXA2xxState *pxa255_init(MemoryRegion *address_space, unsigned int sdram_size)
+{
+ PXA2xxState *s;
+ int i;
+ DriveInfo *dinfo;
+
+ s = (PXA2xxState *) g_malloc0(sizeof(PXA2xxState));
+
+ s->cpu = cpu_arm_init("pxa255");
+ if (s->cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ s->reset = qemu_allocate_irq(pxa2xx_reset, s, 0);
+
+ /* SDRAM & Internal Memory Storage */
+ memory_region_init_ram(&s->sdram, NULL, "pxa255.sdram", sdram_size,
+ &error_abort);
+ vmstate_register_ram_global(&s->sdram);
+ memory_region_add_subregion(address_space, PXA2XX_SDRAM_BASE, &s->sdram);
+ memory_region_init_ram(&s->internal, NULL, "pxa255.internal",
+ PXA2XX_INTERNAL_SIZE, &error_abort);
+ vmstate_register_ram_global(&s->internal);
+ memory_region_add_subregion(address_space, PXA2XX_INTERNAL_BASE,
+ &s->internal);
+
+ s->pic = pxa2xx_pic_init(0x40d00000, s->cpu);
+
+ s->dma = pxa255_dma_init(0x40000000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_DMA));
+
+ sysbus_create_varargs("pxa25x-timer", 0x40a00000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 0),
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 1),
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 2),
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 3),
+ NULL);
+
+ s->gpio = pxa2xx_gpio_init(0x40e00000, s->cpu, s->pic, 85);
+
+ dinfo = drive_get(IF_SD, 0, 0);
+ if (!dinfo) {
+ fprintf(stderr, "qemu: missing SecureDigital device\n");
+ exit(1);
+ }
+ s->mmc = pxa2xx_mmci_init(address_space, 0x41100000,
+ blk_by_legacy_dinfo(dinfo),
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_MMC),
+ qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_MMCI),
+ qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_MMCI));
+
+ for (i = 0; pxa255_serial[i].io_base; i++) {
+ if (serial_hds[i]) {
+ serial_mm_init(address_space, pxa255_serial[i].io_base, 2,
+ qdev_get_gpio_in(s->pic, pxa255_serial[i].irqn),
+ 14745600 / 16, serial_hds[i],
+ DEVICE_NATIVE_ENDIAN);
+ } else {
+ break;
+ }
+ }
+ if (serial_hds[i])
+ s->fir = pxa2xx_fir_init(address_space, 0x40800000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_ICP),
+ qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_ICP),
+ qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_ICP),
+ serial_hds[i]);
+
+ s->lcd = pxa2xx_lcdc_init(address_space, 0x44000000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_LCD));
+
+ s->cm_base = 0x41300000;
+ s->cm_regs[CCCR >> 2] = 0x02000210; /* 416.0 MHz */
+ s->clkcfg = 0x00000009; /* Turbo mode active */
+ memory_region_init_io(&s->cm_iomem, NULL, &pxa2xx_cm_ops, s, "pxa2xx-cm", 0x1000);
+ memory_region_add_subregion(address_space, s->cm_base, &s->cm_iomem);
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_cm, s);
+
+ pxa2xx_setup_cp14(s);
+
+ s->mm_base = 0x48000000;
+ s->mm_regs[MDMRS >> 2] = 0x00020002;
+ s->mm_regs[MDREFR >> 2] = 0x03ca4000;
+ s->mm_regs[MECR >> 2] = 0x00000001; /* Two PC Card sockets */
+ memory_region_init_io(&s->mm_iomem, NULL, &pxa2xx_mm_ops, s, "pxa2xx-mm", 0x1000);
+ memory_region_add_subregion(address_space, s->mm_base, &s->mm_iomem);
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_mm, s);
+
+ s->pm_base = 0x40f00000;
+ memory_region_init_io(&s->pm_iomem, NULL, &pxa2xx_pm_ops, s, "pxa2xx-pm", 0x100);
+ memory_region_add_subregion(address_space, s->pm_base, &s->pm_iomem);
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_pm, s);
+
+ for (i = 0; pxa255_ssp[i].io_base; i ++);
+ s->ssp = (SSIBus **)g_malloc0(sizeof(SSIBus *) * i);
+ for (i = 0; pxa255_ssp[i].io_base; i ++) {
+ DeviceState *dev;
+ dev = sysbus_create_simple(TYPE_PXA2XX_SSP, pxa255_ssp[i].io_base,
+ qdev_get_gpio_in(s->pic, pxa255_ssp[i].irqn));
+ s->ssp[i] = (SSIBus *)qdev_get_child_bus(dev, "ssi");
+ }
+
+ if (usb_enabled()) {
+ sysbus_create_simple("sysbus-ohci", 0x4c000000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_USBH1));
+ }
+
+ s->pcmcia[0] = pxa2xx_pcmcia_init(address_space, 0x20000000);
+ s->pcmcia[1] = pxa2xx_pcmcia_init(address_space, 0x30000000);
+
+ sysbus_create_simple(TYPE_PXA2XX_RTC, 0x40900000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_RTCALARM));
+
+ s->i2c[0] = pxa2xx_i2c_init(0x40301600,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_I2C), 0xffff);
+ s->i2c[1] = pxa2xx_i2c_init(0x40f00100,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_PWRI2C), 0xff);
+
+ s->i2s = pxa2xx_i2s_init(address_space, 0x40400000,
+ qdev_get_gpio_in(s->pic, PXA2XX_PIC_I2S),
+ qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_I2S),
+ qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_I2S));
+
+ /* GPIO1 resets the processor */
+ /* The handler can be overridden by board-specific code */
+ qdev_connect_gpio_out(s->gpio, 1, s->reset);
+ return s;
+}
+
+static void pxa2xx_ssp_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ sdc->init = pxa2xx_ssp_init;
+ dc->reset = pxa2xx_ssp_reset;
+ dc->vmsd = &vmstate_pxa2xx_ssp;
+}
+
+static const TypeInfo pxa2xx_ssp_info = {
+ .name = TYPE_PXA2XX_SSP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxSSPState),
+ .class_init = pxa2xx_ssp_class_init,
+};
+
+static void pxa2xx_register_types(void)
+{
+ type_register_static(&pxa2xx_i2c_slave_info);
+ type_register_static(&pxa2xx_ssp_info);
+ type_register_static(&pxa2xx_i2c_info);
+ type_register_static(&pxa2xx_rtc_sysbus_info);
+ type_register_static(&pxa2xx_fir_info);
+}
+
+type_init(pxa2xx_register_types)
diff --git a/hw/arm/pxa2xx_gpio.c b/hw/arm/pxa2xx_gpio.c
new file mode 100644
index 00000000..c89c8045
--- /dev/null
+++ b/hw/arm/pxa2xx_gpio.c
@@ -0,0 +1,356 @@
+/*
+ * Intel XScale PXA255/270 GPIO controller emulation.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/arm/pxa.h"
+
+#define PXA2XX_GPIO_BANKS 4
+
+#define TYPE_PXA2XX_GPIO "pxa2xx-gpio"
+#define PXA2XX_GPIO(obj) \
+ OBJECT_CHECK(PXA2xxGPIOInfo, (obj), TYPE_PXA2XX_GPIO)
+
+typedef struct PXA2xxGPIOInfo PXA2xxGPIOInfo;
+struct PXA2xxGPIOInfo {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ qemu_irq irq0, irq1, irqX;
+ int lines;
+ int ncpu;
+ ARMCPU *cpu;
+
+ /* XXX: GNU C vectors are more suitable */
+ uint32_t ilevel[PXA2XX_GPIO_BANKS];
+ uint32_t olevel[PXA2XX_GPIO_BANKS];
+ uint32_t dir[PXA2XX_GPIO_BANKS];
+ uint32_t rising[PXA2XX_GPIO_BANKS];
+ uint32_t falling[PXA2XX_GPIO_BANKS];
+ uint32_t status[PXA2XX_GPIO_BANKS];
+ uint32_t gafr[PXA2XX_GPIO_BANKS * 2];
+
+ uint32_t prev_level[PXA2XX_GPIO_BANKS];
+ qemu_irq handler[PXA2XX_GPIO_BANKS * 32];
+ qemu_irq read_notify;
+};
+
+static struct {
+ enum {
+ GPIO_NONE,
+ GPLR,
+ GPSR,
+ GPCR,
+ GPDR,
+ GRER,
+ GFER,
+ GEDR,
+ GAFR_L,
+ GAFR_U,
+ } reg;
+ int bank;
+} pxa2xx_gpio_regs[0x200] = {
+ [0 ... 0x1ff] = { GPIO_NONE, 0 },
+#define PXA2XX_REG(reg, a0, a1, a2, a3) \
+ [a0] = { reg, 0 }, [a1] = { reg, 1 }, [a2] = { reg, 2 }, [a3] = { reg, 3 },
+
+ PXA2XX_REG(GPLR, 0x000, 0x004, 0x008, 0x100)
+ PXA2XX_REG(GPSR, 0x018, 0x01c, 0x020, 0x118)
+ PXA2XX_REG(GPCR, 0x024, 0x028, 0x02c, 0x124)
+ PXA2XX_REG(GPDR, 0x00c, 0x010, 0x014, 0x10c)
+ PXA2XX_REG(GRER, 0x030, 0x034, 0x038, 0x130)
+ PXA2XX_REG(GFER, 0x03c, 0x040, 0x044, 0x13c)
+ PXA2XX_REG(GEDR, 0x048, 0x04c, 0x050, 0x148)
+ PXA2XX_REG(GAFR_L, 0x054, 0x05c, 0x064, 0x06c)
+ PXA2XX_REG(GAFR_U, 0x058, 0x060, 0x068, 0x070)
+};
+
+static void pxa2xx_gpio_irq_update(PXA2xxGPIOInfo *s)
+{
+ if (s->status[0] & (1 << 0))
+ qemu_irq_raise(s->irq0);
+ else
+ qemu_irq_lower(s->irq0);
+
+ if (s->status[0] & (1 << 1))
+ qemu_irq_raise(s->irq1);
+ else
+ qemu_irq_lower(s->irq1);
+
+ if ((s->status[0] & ~3) | s->status[1] | s->status[2] | s->status[3])
+ qemu_irq_raise(s->irqX);
+ else
+ qemu_irq_lower(s->irqX);
+}
+
+/* Bitmap of pins used as standby and sleep wake-up sources. */
+static const int pxa2xx_gpio_wake[PXA2XX_GPIO_BANKS] = {
+ 0x8003fe1b, 0x002001fc, 0xec080000, 0x0012007f,
+};
+
+static void pxa2xx_gpio_set(void *opaque, int line, int level)
+{
+ PXA2xxGPIOInfo *s = (PXA2xxGPIOInfo *) opaque;
+ CPUState *cpu = CPU(s->cpu);
+ int bank;
+ uint32_t mask;
+
+ if (line >= s->lines) {
+ printf("%s: No GPIO pin %i\n", __FUNCTION__, line);
+ return;
+ }
+
+ bank = line >> 5;
+ mask = 1U << (line & 31);
+
+ if (level) {
+ s->status[bank] |= s->rising[bank] & mask &
+ ~s->ilevel[bank] & ~s->dir[bank];
+ s->ilevel[bank] |= mask;
+ } else {
+ s->status[bank] |= s->falling[bank] & mask &
+ s->ilevel[bank] & ~s->dir[bank];
+ s->ilevel[bank] &= ~mask;
+ }
+
+ if (s->status[bank] & mask)
+ pxa2xx_gpio_irq_update(s);
+
+ /* Wake-up GPIOs */
+ if (cpu->halted && (mask & ~s->dir[bank] & pxa2xx_gpio_wake[bank])) {
+ cpu_interrupt(cpu, CPU_INTERRUPT_EXITTB);
+ }
+}
+
+static void pxa2xx_gpio_handler_update(PXA2xxGPIOInfo *s) {
+ uint32_t level, diff;
+ int i, bit, line;
+ for (i = 0; i < PXA2XX_GPIO_BANKS; i ++) {
+ level = s->olevel[i] & s->dir[i];
+
+ for (diff = s->prev_level[i] ^ level; diff; diff ^= 1 << bit) {
+ bit = ctz32(diff);
+ line = bit + 32 * i;
+ qemu_set_irq(s->handler[line], (level >> bit) & 1);
+ }
+
+ s->prev_level[i] = level;
+ }
+}
+
+static uint64_t pxa2xx_gpio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PXA2xxGPIOInfo *s = (PXA2xxGPIOInfo *) opaque;
+ uint32_t ret;
+ int bank;
+ if (offset >= 0x200)
+ return 0;
+
+ bank = pxa2xx_gpio_regs[offset].bank;
+ switch (pxa2xx_gpio_regs[offset].reg) {
+ case GPDR: /* GPIO Pin-Direction registers */
+ return s->dir[bank];
+
+ case GPSR: /* GPIO Pin-Output Set registers */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pxa2xx GPIO: read from write only register GPSR\n");
+ return 0;
+
+ case GPCR: /* GPIO Pin-Output Clear registers */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pxa2xx GPIO: read from write only register GPCR\n");
+ return 0;
+
+ case GRER: /* GPIO Rising-Edge Detect Enable registers */
+ return s->rising[bank];
+
+ case GFER: /* GPIO Falling-Edge Detect Enable registers */
+ return s->falling[bank];
+
+ case GAFR_L: /* GPIO Alternate Function registers */
+ return s->gafr[bank * 2];
+
+ case GAFR_U: /* GPIO Alternate Function registers */
+ return s->gafr[bank * 2 + 1];
+
+ case GPLR: /* GPIO Pin-Level registers */
+ ret = (s->olevel[bank] & s->dir[bank]) |
+ (s->ilevel[bank] & ~s->dir[bank]);
+ qemu_irq_raise(s->read_notify);
+ return ret;
+
+ case GEDR: /* GPIO Edge Detect Status registers */
+ return s->status[bank];
+
+ default:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxGPIOInfo *s = (PXA2xxGPIOInfo *) opaque;
+ int bank;
+ if (offset >= 0x200)
+ return;
+
+ bank = pxa2xx_gpio_regs[offset].bank;
+ switch (pxa2xx_gpio_regs[offset].reg) {
+ case GPDR: /* GPIO Pin-Direction registers */
+ s->dir[bank] = value;
+ pxa2xx_gpio_handler_update(s);
+ break;
+
+ case GPSR: /* GPIO Pin-Output Set registers */
+ s->olevel[bank] |= value;
+ pxa2xx_gpio_handler_update(s);
+ break;
+
+ case GPCR: /* GPIO Pin-Output Clear registers */
+ s->olevel[bank] &= ~value;
+ pxa2xx_gpio_handler_update(s);
+ break;
+
+ case GRER: /* GPIO Rising-Edge Detect Enable registers */
+ s->rising[bank] = value;
+ break;
+
+ case GFER: /* GPIO Falling-Edge Detect Enable registers */
+ s->falling[bank] = value;
+ break;
+
+ case GAFR_L: /* GPIO Alternate Function registers */
+ s->gafr[bank * 2] = value;
+ break;
+
+ case GAFR_U: /* GPIO Alternate Function registers */
+ s->gafr[bank * 2 + 1] = value;
+ break;
+
+ case GEDR: /* GPIO Edge Detect Status registers */
+ s->status[bank] &= ~value;
+ pxa2xx_gpio_irq_update(s);
+ break;
+
+ default:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+}
+
+static const MemoryRegionOps pxa_gpio_ops = {
+ .read = pxa2xx_gpio_read,
+ .write = pxa2xx_gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+DeviceState *pxa2xx_gpio_init(hwaddr base,
+ ARMCPU *cpu, DeviceState *pic, int lines)
+{
+ CPUState *cs = CPU(cpu);
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, TYPE_PXA2XX_GPIO);
+ qdev_prop_set_int32(dev, "lines", lines);
+ qdev_prop_set_int32(dev, "ncpu", cs->cpu_index);
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
+ qdev_get_gpio_in(pic, PXA2XX_PIC_GPIO_0));
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1,
+ qdev_get_gpio_in(pic, PXA2XX_PIC_GPIO_1));
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2,
+ qdev_get_gpio_in(pic, PXA2XX_PIC_GPIO_X));
+
+ return dev;
+}
+
+static int pxa2xx_gpio_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PXA2xxGPIOInfo *s = PXA2XX_GPIO(dev);
+
+ s->cpu = ARM_CPU(qemu_get_cpu(s->ncpu));
+
+ qdev_init_gpio_in(dev, pxa2xx_gpio_set, s->lines);
+ qdev_init_gpio_out(dev, s->handler, s->lines);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pxa_gpio_ops, s, "pxa2xx-gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq0);
+ sysbus_init_irq(sbd, &s->irq1);
+ sysbus_init_irq(sbd, &s->irqX);
+
+ return 0;
+}
+
+/*
+ * Registers a callback to notify on GPLR reads. This normally
+ * shouldn't be needed but it is used for the hack on Spitz machines.
+ */
+void pxa2xx_gpio_read_notifier(DeviceState *dev, qemu_irq handler)
+{
+ PXA2xxGPIOInfo *s = PXA2XX_GPIO(dev);
+
+ s->read_notify = handler;
+}
+
+static const VMStateDescription vmstate_pxa2xx_gpio_regs = {
+ .name = "pxa2xx-gpio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(ilevel, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS),
+ VMSTATE_UINT32_ARRAY(olevel, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS),
+ VMSTATE_UINT32_ARRAY(dir, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS),
+ VMSTATE_UINT32_ARRAY(rising, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS),
+ VMSTATE_UINT32_ARRAY(falling, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS),
+ VMSTATE_UINT32_ARRAY(status, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS),
+ VMSTATE_UINT32_ARRAY(gafr, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS * 2),
+ VMSTATE_UINT32_ARRAY(prev_level, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static Property pxa2xx_gpio_properties[] = {
+ DEFINE_PROP_INT32("lines", PXA2xxGPIOInfo, lines, 0),
+ DEFINE_PROP_INT32("ncpu", PXA2xxGPIOInfo, ncpu, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxa2xx_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pxa2xx_gpio_initfn;
+ dc->desc = "PXA2xx GPIO controller";
+ dc->props = pxa2xx_gpio_properties;
+ dc->vmsd = &vmstate_pxa2xx_gpio_regs;
+}
+
+static const TypeInfo pxa2xx_gpio_info = {
+ .name = TYPE_PXA2XX_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxGPIOInfo),
+ .class_init = pxa2xx_gpio_class_init,
+};
+
+static void pxa2xx_gpio_register_types(void)
+{
+ type_register_static(&pxa2xx_gpio_info);
+}
+
+type_init(pxa2xx_gpio_register_types)
diff --git a/hw/arm/pxa2xx_pic.c b/hw/arm/pxa2xx_pic.c
new file mode 100644
index 00000000..d41ac934
--- /dev/null
+++ b/hw/arm/pxa2xx_pic.c
@@ -0,0 +1,337 @@
+/*
+ * Intel XScale PXA Programmable Interrupt Controller.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Copyright (c) 2006 Thorsten Zitterell
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "hw/sysbus.h"
+
+#define ICIP 0x00 /* Interrupt Controller IRQ Pending register */
+#define ICMR 0x04 /* Interrupt Controller Mask register */
+#define ICLR 0x08 /* Interrupt Controller Level register */
+#define ICFP 0x0c /* Interrupt Controller FIQ Pending register */
+#define ICPR 0x10 /* Interrupt Controller Pending register */
+#define ICCR 0x14 /* Interrupt Controller Control register */
+#define ICHP 0x18 /* Interrupt Controller Highest Priority register */
+#define IPR0 0x1c /* Interrupt Controller Priority register 0 */
+#define IPR31 0x98 /* Interrupt Controller Priority register 31 */
+#define ICIP2 0x9c /* Interrupt Controller IRQ Pending register 2 */
+#define ICMR2 0xa0 /* Interrupt Controller Mask register 2 */
+#define ICLR2 0xa4 /* Interrupt Controller Level register 2 */
+#define ICFP2 0xa8 /* Interrupt Controller FIQ Pending register 2 */
+#define ICPR2 0xac /* Interrupt Controller Pending register 2 */
+#define IPR32 0xb0 /* Interrupt Controller Priority register 32 */
+#define IPR39 0xcc /* Interrupt Controller Priority register 39 */
+
+#define PXA2XX_PIC_SRCS 40
+
+#define TYPE_PXA2XX_PIC "pxa2xx_pic"
+#define PXA2XX_PIC(obj) \
+ OBJECT_CHECK(PXA2xxPICState, (obj), TYPE_PXA2XX_PIC)
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ ARMCPU *cpu;
+ uint32_t int_enabled[2];
+ uint32_t int_pending[2];
+ uint32_t is_fiq[2];
+ uint32_t int_idle;
+ uint32_t priority[PXA2XX_PIC_SRCS];
+} PXA2xxPICState;
+
+static void pxa2xx_pic_update(void *opaque)
+{
+ uint32_t mask[2];
+ PXA2xxPICState *s = (PXA2xxPICState *) opaque;
+ CPUState *cpu = CPU(s->cpu);
+
+ if (cpu->halted) {
+ mask[0] = s->int_pending[0] & (s->int_enabled[0] | s->int_idle);
+ mask[1] = s->int_pending[1] & (s->int_enabled[1] | s->int_idle);
+ if (mask[0] || mask[1]) {
+ cpu_interrupt(cpu, CPU_INTERRUPT_EXITTB);
+ }
+ }
+
+ mask[0] = s->int_pending[0] & s->int_enabled[0];
+ mask[1] = s->int_pending[1] & s->int_enabled[1];
+
+ if ((mask[0] & s->is_fiq[0]) || (mask[1] & s->is_fiq[1])) {
+ cpu_interrupt(cpu, CPU_INTERRUPT_FIQ);
+ } else {
+ cpu_reset_interrupt(cpu, CPU_INTERRUPT_FIQ);
+ }
+
+ if ((mask[0] & ~s->is_fiq[0]) || (mask[1] & ~s->is_fiq[1])) {
+ cpu_interrupt(cpu, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD);
+ }
+}
+
+/* Note: Here level means state of the signal on a pin, not
+ * IRQ/FIQ distinction as in PXA Developer Manual. */
+static void pxa2xx_pic_set_irq(void *opaque, int irq, int level)
+{
+ PXA2xxPICState *s = (PXA2xxPICState *) opaque;
+ int int_set = (irq >= 32);
+ irq &= 31;
+
+ if (level)
+ s->int_pending[int_set] |= 1 << irq;
+ else
+ s->int_pending[int_set] &= ~(1 << irq);
+
+ pxa2xx_pic_update(opaque);
+}
+
+static inline uint32_t pxa2xx_pic_highest(PXA2xxPICState *s) {
+ int i, int_set, irq;
+ uint32_t bit, mask[2];
+ uint32_t ichp = 0x003f003f; /* Both IDs invalid */
+
+ mask[0] = s->int_pending[0] & s->int_enabled[0];
+ mask[1] = s->int_pending[1] & s->int_enabled[1];
+
+ for (i = PXA2XX_PIC_SRCS - 1; i >= 0; i --) {
+ irq = s->priority[i] & 0x3f;
+ if ((s->priority[i] & (1U << 31)) && irq < PXA2XX_PIC_SRCS) {
+ /* Source peripheral ID is valid. */
+ bit = 1 << (irq & 31);
+ int_set = (irq >= 32);
+
+ if (mask[int_set] & bit & s->is_fiq[int_set]) {
+ /* FIQ asserted */
+ ichp &= 0xffff0000;
+ ichp |= (1 << 15) | irq;
+ }
+
+ if (mask[int_set] & bit & ~s->is_fiq[int_set]) {
+ /* IRQ asserted */
+ ichp &= 0x0000ffff;
+ ichp |= (1U << 31) | (irq << 16);
+ }
+ }
+ }
+
+ return ichp;
+}
+
+static uint64_t pxa2xx_pic_mem_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PXA2xxPICState *s = (PXA2xxPICState *) opaque;
+
+ switch (offset) {
+ case ICIP: /* IRQ Pending register */
+ return s->int_pending[0] & ~s->is_fiq[0] & s->int_enabled[0];
+ case ICIP2: /* IRQ Pending register 2 */
+ return s->int_pending[1] & ~s->is_fiq[1] & s->int_enabled[1];
+ case ICMR: /* Mask register */
+ return s->int_enabled[0];
+ case ICMR2: /* Mask register 2 */
+ return s->int_enabled[1];
+ case ICLR: /* Level register */
+ return s->is_fiq[0];
+ case ICLR2: /* Level register 2 */
+ return s->is_fiq[1];
+ case ICCR: /* Idle mask */
+ return (s->int_idle == 0);
+ case ICFP: /* FIQ Pending register */
+ return s->int_pending[0] & s->is_fiq[0] & s->int_enabled[0];
+ case ICFP2: /* FIQ Pending register 2 */
+ return s->int_pending[1] & s->is_fiq[1] & s->int_enabled[1];
+ case ICPR: /* Pending register */
+ return s->int_pending[0];
+ case ICPR2: /* Pending register 2 */
+ return s->int_pending[1];
+ case IPR0 ... IPR31:
+ return s->priority[0 + ((offset - IPR0 ) >> 2)];
+ case IPR32 ... IPR39:
+ return s->priority[32 + ((offset - IPR32) >> 2)];
+ case ICHP: /* Highest Priority register */
+ return pxa2xx_pic_highest(s);
+ default:
+ printf("%s: Bad register offset " REG_FMT "\n", __FUNCTION__, offset);
+ return 0;
+ }
+}
+
+static void pxa2xx_pic_mem_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxPICState *s = (PXA2xxPICState *) opaque;
+
+ switch (offset) {
+ case ICMR: /* Mask register */
+ s->int_enabled[0] = value;
+ break;
+ case ICMR2: /* Mask register 2 */
+ s->int_enabled[1] = value;
+ break;
+ case ICLR: /* Level register */
+ s->is_fiq[0] = value;
+ break;
+ case ICLR2: /* Level register 2 */
+ s->is_fiq[1] = value;
+ break;
+ case ICCR: /* Idle mask */
+ s->int_idle = (value & 1) ? 0 : ~0;
+ break;
+ case IPR0 ... IPR31:
+ s->priority[0 + ((offset - IPR0 ) >> 2)] = value & 0x8000003f;
+ break;
+ case IPR32 ... IPR39:
+ s->priority[32 + ((offset - IPR32) >> 2)] = value & 0x8000003f;
+ break;
+ default:
+ printf("%s: Bad register offset " REG_FMT "\n", __FUNCTION__, offset);
+ return;
+ }
+ pxa2xx_pic_update(opaque);
+}
+
+/* Interrupt Controller Coprocessor Space Register Mapping */
+static const int pxa2xx_cp_reg_map[0x10] = {
+ [0x0 ... 0xf] = -1,
+ [0x0] = ICIP,
+ [0x1] = ICMR,
+ [0x2] = ICLR,
+ [0x3] = ICFP,
+ [0x4] = ICPR,
+ [0x5] = ICHP,
+ [0x6] = ICIP2,
+ [0x7] = ICMR2,
+ [0x8] = ICLR2,
+ [0x9] = ICFP2,
+ [0xa] = ICPR2,
+};
+
+static uint64_t pxa2xx_pic_cp_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+ int offset = pxa2xx_cp_reg_map[ri->crn];
+ return pxa2xx_pic_mem_read(ri->opaque, offset, 4);
+}
+
+static void pxa2xx_pic_cp_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ int offset = pxa2xx_cp_reg_map[ri->crn];
+ pxa2xx_pic_mem_write(ri->opaque, offset, value, 4);
+}
+
+#define REGINFO_FOR_PIC_CP(NAME, CRN) \
+ { .name = NAME, .cp = 6, .crn = CRN, .crm = 0, .opc1 = 0, .opc2 = 0, \
+ .access = PL1_RW, .type = ARM_CP_IO, \
+ .readfn = pxa2xx_pic_cp_read, .writefn = pxa2xx_pic_cp_write }
+
+static const ARMCPRegInfo pxa_pic_cp_reginfo[] = {
+ REGINFO_FOR_PIC_CP("ICIP", 0),
+ REGINFO_FOR_PIC_CP("ICMR", 1),
+ REGINFO_FOR_PIC_CP("ICLR", 2),
+ REGINFO_FOR_PIC_CP("ICFP", 3),
+ REGINFO_FOR_PIC_CP("ICPR", 4),
+ REGINFO_FOR_PIC_CP("ICHP", 5),
+ REGINFO_FOR_PIC_CP("ICIP2", 6),
+ REGINFO_FOR_PIC_CP("ICMR2", 7),
+ REGINFO_FOR_PIC_CP("ICLR2", 8),
+ REGINFO_FOR_PIC_CP("ICFP2", 9),
+ REGINFO_FOR_PIC_CP("ICPR2", 0xa),
+ REGINFO_SENTINEL
+};
+
+static const MemoryRegionOps pxa2xx_pic_ops = {
+ .read = pxa2xx_pic_mem_read,
+ .write = pxa2xx_pic_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pxa2xx_pic_post_load(void *opaque, int version_id)
+{
+ pxa2xx_pic_update(opaque);
+ return 0;
+}
+
+DeviceState *pxa2xx_pic_init(hwaddr base, ARMCPU *cpu)
+{
+ DeviceState *dev = qdev_create(NULL, TYPE_PXA2XX_PIC);
+ PXA2xxPICState *s = PXA2XX_PIC(dev);
+
+ s->cpu = cpu;
+
+ s->int_pending[0] = 0;
+ s->int_pending[1] = 0;
+ s->int_enabled[0] = 0;
+ s->int_enabled[1] = 0;
+ s->is_fiq[0] = 0;
+ s->is_fiq[1] = 0;
+
+ qdev_init_nofail(dev);
+
+ qdev_init_gpio_in(dev, pxa2xx_pic_set_irq, PXA2XX_PIC_SRCS);
+
+ /* Enable IC memory-mapped registers access. */
+ memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_pic_ops, s,
+ "pxa2xx-pic", 0x00100000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+
+ /* Enable IC coprocessor access. */
+ define_arm_cp_regs_with_opaque(cpu, pxa_pic_cp_reginfo, s);
+
+ return dev;
+}
+
+static VMStateDescription vmstate_pxa2xx_pic_regs = {
+ .name = "pxa2xx_pic",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = pxa2xx_pic_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(int_enabled, PXA2xxPICState, 2),
+ VMSTATE_UINT32_ARRAY(int_pending, PXA2xxPICState, 2),
+ VMSTATE_UINT32_ARRAY(is_fiq, PXA2xxPICState, 2),
+ VMSTATE_UINT32(int_idle, PXA2xxPICState),
+ VMSTATE_UINT32_ARRAY(priority, PXA2xxPICState, PXA2XX_PIC_SRCS),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static int pxa2xx_pic_initfn(SysBusDevice *dev)
+{
+ return 0;
+}
+
+static void pxa2xx_pic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pxa2xx_pic_initfn;
+ dc->desc = "PXA2xx PIC";
+ dc->vmsd = &vmstate_pxa2xx_pic_regs;
+}
+
+static const TypeInfo pxa2xx_pic_info = {
+ .name = TYPE_PXA2XX_PIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxPICState),
+ .class_init = pxa2xx_pic_class_init,
+};
+
+static void pxa2xx_pic_register_types(void)
+{
+ type_register_static(&pxa2xx_pic_info);
+}
+
+type_init(pxa2xx_pic_register_types)
diff --git a/hw/arm/realview.c b/hw/arm/realview.c
new file mode 100644
index 00000000..ef2788d3
--- /dev/null
+++ b/hw/arm/realview.c
@@ -0,0 +1,439 @@
+/*
+ * ARM RealView Baseboard System emulation.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/arm/primecell.h"
+#include "hw/devices.h"
+#include "hw/pci/pci.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/i2c/i2c.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+
+#define SMP_BOOT_ADDR 0xe0000000
+#define SMP_BOOTREG_ADDR 0x10000030
+
+/* Board init. */
+
+static struct arm_boot_info realview_binfo = {
+ .smp_loader_start = SMP_BOOT_ADDR,
+ .smp_bootreg_addr = SMP_BOOTREG_ADDR,
+};
+
+/* The following two lists must be consistent. */
+enum realview_board_type {
+ BOARD_EB,
+ BOARD_EB_MPCORE,
+ BOARD_PB_A8,
+ BOARD_PBX_A9,
+};
+
+static const int realview_board_id[] = {
+ 0x33b,
+ 0x33b,
+ 0x769,
+ 0x76d
+};
+
+static void realview_init(MachineState *machine,
+ enum realview_board_type board_type)
+{
+ ARMCPU *cpu = NULL;
+ CPUARMState *env;
+ ObjectClass *cpu_oc;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ram_lo;
+ MemoryRegion *ram_hi = g_new(MemoryRegion, 1);
+ MemoryRegion *ram_alias = g_new(MemoryRegion, 1);
+ MemoryRegion *ram_hack = g_new(MemoryRegion, 1);
+ DeviceState *dev, *sysctl, *gpio2, *pl041;
+ SysBusDevice *busdev;
+ qemu_irq pic[64];
+ qemu_irq mmc_irq[2];
+ PCIBus *pci_bus = NULL;
+ NICInfo *nd;
+ I2CBus *i2c;
+ int n;
+ int done_nic = 0;
+ qemu_irq cpu_irq[4];
+ int is_mpcore = 0;
+ int is_pb = 0;
+ uint32_t proc_id = 0;
+ uint32_t sys_id;
+ ram_addr_t low_ram_size;
+ ram_addr_t ram_size = machine->ram_size;
+ hwaddr periphbase = 0;
+
+ switch (board_type) {
+ case BOARD_EB:
+ break;
+ case BOARD_EB_MPCORE:
+ is_mpcore = 1;
+ periphbase = 0x10100000;
+ break;
+ case BOARD_PB_A8:
+ is_pb = 1;
+ break;
+ case BOARD_PBX_A9:
+ is_mpcore = 1;
+ is_pb = 1;
+ periphbase = 0x1f000000;
+ break;
+ }
+
+ cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, machine->cpu_model);
+ if (!cpu_oc) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+
+ for (n = 0; n < smp_cpus; n++) {
+ Object *cpuobj = object_new(object_class_get_name(cpu_oc));
+ Error *err = NULL;
+
+ /* By default A9,A15 and ARM1176 CPUs have EL3 enabled. This board
+ * does not currently support EL3 so the CPU EL3 property is disabled
+ * before realization.
+ */
+ if (object_property_find(cpuobj, "has_el3", NULL)) {
+ object_property_set_bool(cpuobj, false, "has_el3", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ if (is_pb && is_mpcore) {
+ object_property_set_int(cpuobj, periphbase, "reset-cbar", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ object_property_set_bool(cpuobj, true, "realized", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+
+ cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpuobj), ARM_CPU_IRQ);
+ }
+ cpu = ARM_CPU(first_cpu);
+ env = &cpu->env;
+ if (arm_feature(env, ARM_FEATURE_V7)) {
+ if (is_mpcore) {
+ proc_id = 0x0c000000;
+ } else {
+ proc_id = 0x0e000000;
+ }
+ } else if (arm_feature(env, ARM_FEATURE_V6K)) {
+ proc_id = 0x06000000;
+ } else if (arm_feature(env, ARM_FEATURE_V6)) {
+ proc_id = 0x04000000;
+ } else {
+ proc_id = 0x02000000;
+ }
+
+ if (is_pb && ram_size > 0x20000000) {
+ /* Core tile RAM. */
+ ram_lo = g_new(MemoryRegion, 1);
+ low_ram_size = ram_size - 0x20000000;
+ ram_size = 0x20000000;
+ memory_region_init_ram(ram_lo, NULL, "realview.lowmem", low_ram_size,
+ &error_abort);
+ vmstate_register_ram_global(ram_lo);
+ memory_region_add_subregion(sysmem, 0x20000000, ram_lo);
+ }
+
+ memory_region_init_ram(ram_hi, NULL, "realview.highmem", ram_size,
+ &error_abort);
+ vmstate_register_ram_global(ram_hi);
+ low_ram_size = ram_size;
+ if (low_ram_size > 0x10000000)
+ low_ram_size = 0x10000000;
+ /* SDRAM at address zero. */
+ memory_region_init_alias(ram_alias, NULL, "realview.alias",
+ ram_hi, 0, low_ram_size);
+ memory_region_add_subregion(sysmem, 0, ram_alias);
+ if (is_pb) {
+ /* And again at a high address. */
+ memory_region_add_subregion(sysmem, 0x70000000, ram_hi);
+ } else {
+ ram_size = low_ram_size;
+ }
+
+ sys_id = is_pb ? 0x01780500 : 0xc1400400;
+ sysctl = qdev_create(NULL, "realview_sysctl");
+ qdev_prop_set_uint32(sysctl, "sys_id", sys_id);
+ qdev_prop_set_uint32(sysctl, "proc_id", proc_id);
+ qdev_init_nofail(sysctl);
+ sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, 0x10000000);
+
+ if (is_mpcore) {
+ dev = qdev_create(NULL, is_pb ? "a9mpcore_priv": "realview_mpcore");
+ qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, periphbase);
+ for (n = 0; n < smp_cpus; n++) {
+ sysbus_connect_irq(busdev, n, cpu_irq[n]);
+ }
+ sysbus_create_varargs("l2x0", periphbase + 0x2000, NULL);
+ /* Both A9 and 11MPCore put the GIC CPU i/f at base + 0x100 */
+ realview_binfo.gic_cpu_if_addr = periphbase + 0x100;
+ } else {
+ uint32_t gic_addr = is_pb ? 0x1e000000 : 0x10040000;
+ /* For now just create the nIRQ GIC, and ignore the others. */
+ dev = sysbus_create_simple("realview_gic", gic_addr, cpu_irq[0]);
+ }
+ for (n = 0; n < 64; n++) {
+ pic[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ pl041 = qdev_create(NULL, "pl041");
+ qdev_prop_set_uint32(pl041, "nc_fifo_depth", 512);
+ qdev_init_nofail(pl041);
+ sysbus_mmio_map(SYS_BUS_DEVICE(pl041), 0, 0x10004000);
+ sysbus_connect_irq(SYS_BUS_DEVICE(pl041), 0, pic[19]);
+
+ sysbus_create_simple("pl050_keyboard", 0x10006000, pic[20]);
+ sysbus_create_simple("pl050_mouse", 0x10007000, pic[21]);
+
+ sysbus_create_simple("pl011", 0x10009000, pic[12]);
+ sysbus_create_simple("pl011", 0x1000a000, pic[13]);
+ sysbus_create_simple("pl011", 0x1000b000, pic[14]);
+ sysbus_create_simple("pl011", 0x1000c000, pic[15]);
+
+ /* DMA controller is optional, apparently. */
+ sysbus_create_simple("pl081", 0x10030000, pic[24]);
+
+ sysbus_create_simple("sp804", 0x10011000, pic[4]);
+ sysbus_create_simple("sp804", 0x10012000, pic[5]);
+
+ sysbus_create_simple("pl061", 0x10013000, pic[6]);
+ sysbus_create_simple("pl061", 0x10014000, pic[7]);
+ gpio2 = sysbus_create_simple("pl061", 0x10015000, pic[8]);
+
+ sysbus_create_simple("pl111", 0x10020000, pic[23]);
+
+ dev = sysbus_create_varargs("pl181", 0x10005000, pic[17], pic[18], NULL);
+ /* Wire up MMC card detect and read-only signals. These have
+ * to go to both the PL061 GPIO and the sysctl register.
+ * Note that the PL181 orders these lines (readonly,inserted)
+ * and the PL061 has them the other way about. Also the card
+ * detect line is inverted.
+ */
+ mmc_irq[0] = qemu_irq_split(
+ qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_WPROT),
+ qdev_get_gpio_in(gpio2, 1));
+ mmc_irq[1] = qemu_irq_split(
+ qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_CARDIN),
+ qemu_irq_invert(qdev_get_gpio_in(gpio2, 0)));
+ qdev_connect_gpio_out(dev, 0, mmc_irq[0]);
+ qdev_connect_gpio_out(dev, 1, mmc_irq[1]);
+
+ sysbus_create_simple("pl031", 0x10017000, pic[10]);
+
+ if (!is_pb) {
+ dev = qdev_create(NULL, "realview_pci");
+ busdev = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(busdev, 0, 0x10019000); /* PCI controller registers */
+ sysbus_mmio_map(busdev, 1, 0x60000000); /* PCI self-config */
+ sysbus_mmio_map(busdev, 2, 0x61000000); /* PCI config */
+ sysbus_mmio_map(busdev, 3, 0x62000000); /* PCI I/O */
+ sysbus_mmio_map(busdev, 4, 0x63000000); /* PCI memory window 1 */
+ sysbus_mmio_map(busdev, 5, 0x64000000); /* PCI memory window 2 */
+ sysbus_mmio_map(busdev, 6, 0x68000000); /* PCI memory window 3 */
+ sysbus_connect_irq(busdev, 0, pic[48]);
+ sysbus_connect_irq(busdev, 1, pic[49]);
+ sysbus_connect_irq(busdev, 2, pic[50]);
+ sysbus_connect_irq(busdev, 3, pic[51]);
+ pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci");
+ if (usb_enabled()) {
+ pci_create_simple(pci_bus, -1, "pci-ohci");
+ }
+ n = drive_get_max_bus(IF_SCSI);
+ while (n >= 0) {
+ pci_create_simple(pci_bus, -1, "lsi53c895a");
+ n--;
+ }
+ }
+ for(n = 0; n < nb_nics; n++) {
+ nd = &nd_table[n];
+
+ if (!done_nic && (!nd->model ||
+ strcmp(nd->model, is_pb ? "lan9118" : "smc91c111") == 0)) {
+ if (is_pb) {
+ lan9118_init(nd, 0x4e000000, pic[28]);
+ } else {
+ smc91c111_init(nd, 0x4e000000, pic[28]);
+ }
+ done_nic = 1;
+ } else {
+ if (pci_bus) {
+ pci_nic_init_nofail(nd, pci_bus, "rtl8139", NULL);
+ }
+ }
+ }
+
+ dev = sysbus_create_simple("versatile_i2c", 0x10002000, NULL);
+ i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
+ i2c_create_slave(i2c, "ds1338", 0x68);
+
+ /* Memory map for RealView Emulation Baseboard: */
+ /* 0x10000000 System registers. */
+ /* 0x10001000 System controller. */
+ /* 0x10002000 Two-Wire Serial Bus. */
+ /* 0x10003000 Reserved. */
+ /* 0x10004000 AACI. */
+ /* 0x10005000 MCI. */
+ /* 0x10006000 KMI0. */
+ /* 0x10007000 KMI1. */
+ /* 0x10008000 Character LCD. (EB) */
+ /* 0x10009000 UART0. */
+ /* 0x1000a000 UART1. */
+ /* 0x1000b000 UART2. */
+ /* 0x1000c000 UART3. */
+ /* 0x1000d000 SSPI. */
+ /* 0x1000e000 SCI. */
+ /* 0x1000f000 Reserved. */
+ /* 0x10010000 Watchdog. */
+ /* 0x10011000 Timer 0+1. */
+ /* 0x10012000 Timer 2+3. */
+ /* 0x10013000 GPIO 0. */
+ /* 0x10014000 GPIO 1. */
+ /* 0x10015000 GPIO 2. */
+ /* 0x10002000 Two-Wire Serial Bus - DVI. (PB) */
+ /* 0x10017000 RTC. */
+ /* 0x10018000 DMC. */
+ /* 0x10019000 PCI controller config. */
+ /* 0x10020000 CLCD. */
+ /* 0x10030000 DMA Controller. */
+ /* 0x10040000 GIC1. (EB) */
+ /* 0x10050000 GIC2. (EB) */
+ /* 0x10060000 GIC3. (EB) */
+ /* 0x10070000 GIC4. (EB) */
+ /* 0x10080000 SMC. */
+ /* 0x1e000000 GIC1. (PB) */
+ /* 0x1e001000 GIC2. (PB) */
+ /* 0x1e002000 GIC3. (PB) */
+ /* 0x1e003000 GIC4. (PB) */
+ /* 0x40000000 NOR flash. */
+ /* 0x44000000 DoC flash. */
+ /* 0x48000000 SRAM. */
+ /* 0x4c000000 Configuration flash. */
+ /* 0x4e000000 Ethernet. */
+ /* 0x4f000000 USB. */
+ /* 0x50000000 PISMO. */
+ /* 0x54000000 PISMO. */
+ /* 0x58000000 PISMO. */
+ /* 0x5c000000 PISMO. */
+ /* 0x60000000 PCI. */
+ /* 0x60000000 PCI Self Config. */
+ /* 0x61000000 PCI Config. */
+ /* 0x62000000 PCI IO. */
+ /* 0x63000000 PCI mem 0. */
+ /* 0x64000000 PCI mem 1. */
+ /* 0x68000000 PCI mem 2. */
+
+ /* ??? Hack to map an additional page of ram for the secondary CPU
+ startup code. I guess this works on real hardware because the
+ BootROM happens to be in ROM/flash or in memory that isn't clobbered
+ until after Linux boots the secondary CPUs. */
+ memory_region_init_ram(ram_hack, NULL, "realview.hack", 0x1000,
+ &error_abort);
+ vmstate_register_ram_global(ram_hack);
+ memory_region_add_subregion(sysmem, SMP_BOOT_ADDR, ram_hack);
+
+ realview_binfo.ram_size = ram_size;
+ realview_binfo.kernel_filename = machine->kernel_filename;
+ realview_binfo.kernel_cmdline = machine->kernel_cmdline;
+ realview_binfo.initrd_filename = machine->initrd_filename;
+ realview_binfo.nb_cpus = smp_cpus;
+ realview_binfo.board_id = realview_board_id[board_type];
+ realview_binfo.loader_start = (board_type == BOARD_PB_A8 ? 0x70000000 : 0);
+ arm_load_kernel(ARM_CPU(first_cpu), &realview_binfo);
+}
+
+static void realview_eb_init(MachineState *machine)
+{
+ if (!machine->cpu_model) {
+ machine->cpu_model = "arm926";
+ }
+ realview_init(machine, BOARD_EB);
+}
+
+static void realview_eb_mpcore_init(MachineState *machine)
+{
+ if (!machine->cpu_model) {
+ machine->cpu_model = "arm11mpcore";
+ }
+ realview_init(machine, BOARD_EB_MPCORE);
+}
+
+static void realview_pb_a8_init(MachineState *machine)
+{
+ if (!machine->cpu_model) {
+ machine->cpu_model = "cortex-a8";
+ }
+ realview_init(machine, BOARD_PB_A8);
+}
+
+static void realview_pbx_a9_init(MachineState *machine)
+{
+ if (!machine->cpu_model) {
+ machine->cpu_model = "cortex-a9";
+ }
+ realview_init(machine, BOARD_PBX_A9);
+}
+
+static QEMUMachine realview_eb_machine = {
+ .name = "realview-eb",
+ .desc = "ARM RealView Emulation Baseboard (ARM926EJ-S)",
+ .init = realview_eb_init,
+ .block_default_type = IF_SCSI,
+};
+
+static QEMUMachine realview_eb_mpcore_machine = {
+ .name = "realview-eb-mpcore",
+ .desc = "ARM RealView Emulation Baseboard (ARM11MPCore)",
+ .init = realview_eb_mpcore_init,
+ .block_default_type = IF_SCSI,
+ .max_cpus = 4,
+};
+
+static QEMUMachine realview_pb_a8_machine = {
+ .name = "realview-pb-a8",
+ .desc = "ARM RealView Platform Baseboard for Cortex-A8",
+ .init = realview_pb_a8_init,
+};
+
+static QEMUMachine realview_pbx_a9_machine = {
+ .name = "realview-pbx-a9",
+ .desc = "ARM RealView Platform Baseboard Explore for Cortex-A9",
+ .init = realview_pbx_a9_init,
+ .block_default_type = IF_SCSI,
+ .max_cpus = 4,
+};
+
+static void realview_machine_init(void)
+{
+ qemu_register_machine(&realview_eb_machine);
+ qemu_register_machine(&realview_eb_mpcore_machine);
+ qemu_register_machine(&realview_pb_a8_machine);
+ qemu_register_machine(&realview_pbx_a9_machine);
+}
+
+machine_init(realview_machine_init);
diff --git a/hw/arm/spitz.c b/hw/arm/spitz.c
new file mode 100644
index 00000000..5bf032a6
--- /dev/null
+++ b/hw/arm/spitz.c
@@ -0,0 +1,1149 @@
+/*
+ * PXA270-based Clamshell PDA platforms.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "hw/arm/arm.h"
+#include "sysemu/sysemu.h"
+#include "hw/pcmcia.h"
+#include "hw/i2c/i2c.h"
+#include "hw/ssi.h"
+#include "hw/block/flash.h"
+#include "qemu/timer.h"
+#include "hw/devices.h"
+#include "hw/arm/sharpsl.h"
+#include "ui/console.h"
+#include "audio/audio.h"
+#include "hw/boards.h"
+#include "sysemu/block-backend.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+
+#undef REG_FMT
+#define REG_FMT "0x%02lx"
+
+/* Spitz Flash */
+#define FLASH_BASE 0x0c000000
+#define FLASH_ECCLPLB 0x00 /* Line parity 7 - 0 bit */
+#define FLASH_ECCLPUB 0x04 /* Line parity 15 - 8 bit */
+#define FLASH_ECCCP 0x08 /* Column parity 5 - 0 bit */
+#define FLASH_ECCCNTR 0x0c /* ECC byte counter */
+#define FLASH_ECCCLRR 0x10 /* Clear ECC */
+#define FLASH_FLASHIO 0x14 /* Flash I/O */
+#define FLASH_FLASHCTL 0x18 /* Flash Control */
+
+#define FLASHCTL_CE0 (1 << 0)
+#define FLASHCTL_CLE (1 << 1)
+#define FLASHCTL_ALE (1 << 2)
+#define FLASHCTL_WP (1 << 3)
+#define FLASHCTL_CE1 (1 << 4)
+#define FLASHCTL_RYBY (1 << 5)
+#define FLASHCTL_NCE (FLASHCTL_CE0 | FLASHCTL_CE1)
+
+#define TYPE_SL_NAND "sl-nand"
+#define SL_NAND(obj) OBJECT_CHECK(SLNANDState, (obj), TYPE_SL_NAND)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ DeviceState *nand;
+ uint8_t ctl;
+ uint8_t manf_id;
+ uint8_t chip_id;
+ ECCState ecc;
+} SLNANDState;
+
+static uint64_t sl_read(void *opaque, hwaddr addr, unsigned size)
+{
+ SLNANDState *s = (SLNANDState *) opaque;
+ int ryby;
+
+ switch (addr) {
+#define BSHR(byte, from, to) ((s->ecc.lp[byte] >> (from - to)) & (1 << to))
+ case FLASH_ECCLPLB:
+ return BSHR(0, 4, 0) | BSHR(0, 5, 2) | BSHR(0, 6, 4) | BSHR(0, 7, 6) |
+ BSHR(1, 4, 1) | BSHR(1, 5, 3) | BSHR(1, 6, 5) | BSHR(1, 7, 7);
+
+#define BSHL(byte, from, to) ((s->ecc.lp[byte] << (to - from)) & (1 << to))
+ case FLASH_ECCLPUB:
+ return BSHL(0, 0, 0) | BSHL(0, 1, 2) | BSHL(0, 2, 4) | BSHL(0, 3, 6) |
+ BSHL(1, 0, 1) | BSHL(1, 1, 3) | BSHL(1, 2, 5) | BSHL(1, 3, 7);
+
+ case FLASH_ECCCP:
+ return s->ecc.cp;
+
+ case FLASH_ECCCNTR:
+ return s->ecc.count & 0xff;
+
+ case FLASH_FLASHCTL:
+ nand_getpins(s->nand, &ryby);
+ if (ryby)
+ return s->ctl | FLASHCTL_RYBY;
+ else
+ return s->ctl;
+
+ case FLASH_FLASHIO:
+ if (size == 4) {
+ return ecc_digest(&s->ecc, nand_getio(s->nand)) |
+ (ecc_digest(&s->ecc, nand_getio(s->nand)) << 16);
+ }
+ return ecc_digest(&s->ecc, nand_getio(s->nand));
+
+ default:
+ zaurus_printf("Bad register offset " REG_FMT "\n", (unsigned long)addr);
+ }
+ return 0;
+}
+
+static void sl_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SLNANDState *s = (SLNANDState *) opaque;
+
+ switch (addr) {
+ case FLASH_ECCCLRR:
+ /* Value is ignored. */
+ ecc_reset(&s->ecc);
+ break;
+
+ case FLASH_FLASHCTL:
+ s->ctl = value & 0xff & ~FLASHCTL_RYBY;
+ nand_setpins(s->nand,
+ s->ctl & FLASHCTL_CLE,
+ s->ctl & FLASHCTL_ALE,
+ s->ctl & FLASHCTL_NCE,
+ s->ctl & FLASHCTL_WP,
+ 0);
+ break;
+
+ case FLASH_FLASHIO:
+ nand_setio(s->nand, ecc_digest(&s->ecc, value & 0xff));
+ break;
+
+ default:
+ zaurus_printf("Bad register offset " REG_FMT "\n", (unsigned long)addr);
+ }
+}
+
+enum {
+ FLASH_128M,
+ FLASH_1024M,
+};
+
+static const MemoryRegionOps sl_ops = {
+ .read = sl_read,
+ .write = sl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void sl_flash_register(PXA2xxState *cpu, int size)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, TYPE_SL_NAND);
+
+ qdev_prop_set_uint8(dev, "manf_id", NAND_MFR_SAMSUNG);
+ if (size == FLASH_128M)
+ qdev_prop_set_uint8(dev, "chip_id", 0x73);
+ else if (size == FLASH_1024M)
+ qdev_prop_set_uint8(dev, "chip_id", 0xf1);
+
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, FLASH_BASE);
+}
+
+static int sl_nand_init(SysBusDevice *dev)
+{
+ SLNANDState *s = SL_NAND(dev);
+ DriveInfo *nand;
+
+ s->ctl = 0;
+ /* FIXME use a qdev drive property instead of drive_get() */
+ nand = drive_get(IF_MTD, 0, 0);
+ s->nand = nand_init(nand ? blk_by_legacy_dinfo(nand) : NULL,
+ s->manf_id, s->chip_id);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &sl_ops, s, "sl", 0x40);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+/* Spitz Keyboard */
+
+#define SPITZ_KEY_STROBE_NUM 11
+#define SPITZ_KEY_SENSE_NUM 7
+
+static const int spitz_gpio_key_sense[SPITZ_KEY_SENSE_NUM] = {
+ 12, 17, 91, 34, 36, 38, 39
+};
+
+static const int spitz_gpio_key_strobe[SPITZ_KEY_STROBE_NUM] = {
+ 88, 23, 24, 25, 26, 27, 52, 103, 107, 108, 114
+};
+
+/* Eighth additional row maps the special keys */
+static int spitz_keymap[SPITZ_KEY_SENSE_NUM + 1][SPITZ_KEY_STROBE_NUM] = {
+ { 0x1d, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0a, 0x0b, 0x0e, 0x3f, 0x40 },
+ { -1 , 0x03, 0x05, 0x13, 0x15, 0x09, 0x17, 0x18, 0x19, 0x41, 0x42 },
+ { 0x0f, 0x10, 0x12, 0x14, 0x22, 0x16, 0x24, 0x25, -1 , -1 , -1 },
+ { 0x3c, 0x11, 0x1f, 0x21, 0x2f, 0x23, 0x32, 0x26, -1 , 0x36, -1 },
+ { 0x3b, 0x1e, 0x20, 0x2e, 0x30, 0x31, 0x34, -1 , 0x1c, 0x2a, -1 },
+ { 0x44, 0x2c, 0x2d, 0x0c, 0x39, 0x33, -1 , 0x48, -1 , -1 , 0x38 },
+ { 0x37, 0x3d, -1 , 0x45, 0x57, 0x58, 0x4b, 0x50, 0x4d, -1 , -1 },
+ { 0x52, 0x43, 0x01, 0x47, 0x49, -1 , -1 , -1 , -1 , -1 , -1 },
+};
+
+#define SPITZ_GPIO_AK_INT 13 /* Remote control */
+#define SPITZ_GPIO_SYNC 16 /* Sync button */
+#define SPITZ_GPIO_ON_KEY 95 /* Power button */
+#define SPITZ_GPIO_SWA 97 /* Lid */
+#define SPITZ_GPIO_SWB 96 /* Tablet mode */
+
+/* The special buttons are mapped to unused keys */
+static const int spitz_gpiomap[5] = {
+ SPITZ_GPIO_AK_INT, SPITZ_GPIO_SYNC, SPITZ_GPIO_ON_KEY,
+ SPITZ_GPIO_SWA, SPITZ_GPIO_SWB,
+};
+
+#define TYPE_SPITZ_KEYBOARD "spitz-keyboard"
+#define SPITZ_KEYBOARD(obj) \
+ OBJECT_CHECK(SpitzKeyboardState, (obj), TYPE_SPITZ_KEYBOARD)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ qemu_irq sense[SPITZ_KEY_SENSE_NUM];
+ qemu_irq gpiomap[5];
+ int keymap[0x80];
+ uint16_t keyrow[SPITZ_KEY_SENSE_NUM];
+ uint16_t strobe_state;
+ uint16_t sense_state;
+
+ uint16_t pre_map[0x100];
+ uint16_t modifiers;
+ uint16_t imodifiers;
+ uint8_t fifo[16];
+ int fifopos, fifolen;
+ QEMUTimer *kbdtimer;
+} SpitzKeyboardState;
+
+static void spitz_keyboard_sense_update(SpitzKeyboardState *s)
+{
+ int i;
+ uint16_t strobe, sense = 0;
+ for (i = 0; i < SPITZ_KEY_SENSE_NUM; i ++) {
+ strobe = s->keyrow[i] & s->strobe_state;
+ if (strobe) {
+ sense |= 1 << i;
+ if (!(s->sense_state & (1 << i)))
+ qemu_irq_raise(s->sense[i]);
+ } else if (s->sense_state & (1 << i))
+ qemu_irq_lower(s->sense[i]);
+ }
+
+ s->sense_state = sense;
+}
+
+static void spitz_keyboard_strobe(void *opaque, int line, int level)
+{
+ SpitzKeyboardState *s = (SpitzKeyboardState *) opaque;
+
+ if (level)
+ s->strobe_state |= 1 << line;
+ else
+ s->strobe_state &= ~(1 << line);
+ spitz_keyboard_sense_update(s);
+}
+
+static void spitz_keyboard_keydown(SpitzKeyboardState *s, int keycode)
+{
+ int spitz_keycode = s->keymap[keycode & 0x7f];
+ if (spitz_keycode == -1)
+ return;
+
+ /* Handle the additional keys */
+ if ((spitz_keycode >> 4) == SPITZ_KEY_SENSE_NUM) {
+ qemu_set_irq(s->gpiomap[spitz_keycode & 0xf], (keycode < 0x80));
+ return;
+ }
+
+ if (keycode & 0x80)
+ s->keyrow[spitz_keycode >> 4] &= ~(1 << (spitz_keycode & 0xf));
+ else
+ s->keyrow[spitz_keycode >> 4] |= 1 << (spitz_keycode & 0xf);
+
+ spitz_keyboard_sense_update(s);
+}
+
+#define SPITZ_MOD_SHIFT (1 << 7)
+#define SPITZ_MOD_CTRL (1 << 8)
+#define SPITZ_MOD_FN (1 << 9)
+
+#define QUEUE_KEY(c) s->fifo[(s->fifopos + s->fifolen ++) & 0xf] = c
+
+static void spitz_keyboard_handler(void *opaque, int keycode)
+{
+ SpitzKeyboardState *s = opaque;
+ uint16_t code;
+ int mapcode;
+ switch (keycode) {
+ case 0x2a: /* Left Shift */
+ s->modifiers |= 1;
+ break;
+ case 0xaa:
+ s->modifiers &= ~1;
+ break;
+ case 0x36: /* Right Shift */
+ s->modifiers |= 2;
+ break;
+ case 0xb6:
+ s->modifiers &= ~2;
+ break;
+ case 0x1d: /* Control */
+ s->modifiers |= 4;
+ break;
+ case 0x9d:
+ s->modifiers &= ~4;
+ break;
+ case 0x38: /* Alt */
+ s->modifiers |= 8;
+ break;
+ case 0xb8:
+ s->modifiers &= ~8;
+ break;
+ }
+
+ code = s->pre_map[mapcode = ((s->modifiers & 3) ?
+ (keycode | SPITZ_MOD_SHIFT) :
+ (keycode & ~SPITZ_MOD_SHIFT))];
+
+ if (code != mapcode) {
+#if 0
+ if ((code & SPITZ_MOD_SHIFT) && !(s->modifiers & 1)) {
+ QUEUE_KEY(0x2a | (keycode & 0x80));
+ }
+ if ((code & SPITZ_MOD_CTRL) && !(s->modifiers & 4)) {
+ QUEUE_KEY(0x1d | (keycode & 0x80));
+ }
+ if ((code & SPITZ_MOD_FN) && !(s->modifiers & 8)) {
+ QUEUE_KEY(0x38 | (keycode & 0x80));
+ }
+ if ((code & SPITZ_MOD_FN) && (s->modifiers & 1)) {
+ QUEUE_KEY(0x2a | (~keycode & 0x80));
+ }
+ if ((code & SPITZ_MOD_FN) && (s->modifiers & 2)) {
+ QUEUE_KEY(0x36 | (~keycode & 0x80));
+ }
+#else
+ if (keycode & 0x80) {
+ if ((s->imodifiers & 1 ) && !(s->modifiers & 1))
+ QUEUE_KEY(0x2a | 0x80);
+ if ((s->imodifiers & 4 ) && !(s->modifiers & 4))
+ QUEUE_KEY(0x1d | 0x80);
+ if ((s->imodifiers & 8 ) && !(s->modifiers & 8))
+ QUEUE_KEY(0x38 | 0x80);
+ if ((s->imodifiers & 0x10) && (s->modifiers & 1))
+ QUEUE_KEY(0x2a);
+ if ((s->imodifiers & 0x20) && (s->modifiers & 2))
+ QUEUE_KEY(0x36);
+ s->imodifiers = 0;
+ } else {
+ if ((code & SPITZ_MOD_SHIFT) &&
+ !((s->modifiers | s->imodifiers) & 1)) {
+ QUEUE_KEY(0x2a);
+ s->imodifiers |= 1;
+ }
+ if ((code & SPITZ_MOD_CTRL) &&
+ !((s->modifiers | s->imodifiers) & 4)) {
+ QUEUE_KEY(0x1d);
+ s->imodifiers |= 4;
+ }
+ if ((code & SPITZ_MOD_FN) &&
+ !((s->modifiers | s->imodifiers) & 8)) {
+ QUEUE_KEY(0x38);
+ s->imodifiers |= 8;
+ }
+ if ((code & SPITZ_MOD_FN) && (s->modifiers & 1) &&
+ !(s->imodifiers & 0x10)) {
+ QUEUE_KEY(0x2a | 0x80);
+ s->imodifiers |= 0x10;
+ }
+ if ((code & SPITZ_MOD_FN) && (s->modifiers & 2) &&
+ !(s->imodifiers & 0x20)) {
+ QUEUE_KEY(0x36 | 0x80);
+ s->imodifiers |= 0x20;
+ }
+ }
+#endif
+ }
+
+ QUEUE_KEY((code & 0x7f) | (keycode & 0x80));
+}
+
+static void spitz_keyboard_tick(void *opaque)
+{
+ SpitzKeyboardState *s = (SpitzKeyboardState *) opaque;
+
+ if (s->fifolen) {
+ spitz_keyboard_keydown(s, s->fifo[s->fifopos ++]);
+ s->fifolen --;
+ if (s->fifopos >= 16)
+ s->fifopos = 0;
+ }
+
+ timer_mod(s->kbdtimer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ get_ticks_per_sec() / 32);
+}
+
+static void spitz_keyboard_pre_map(SpitzKeyboardState *s)
+{
+ int i;
+ for (i = 0; i < 0x100; i ++)
+ s->pre_map[i] = i;
+ s->pre_map[0x02 | SPITZ_MOD_SHIFT] = 0x02 | SPITZ_MOD_SHIFT; /* exclam */
+ s->pre_map[0x28 | SPITZ_MOD_SHIFT] = 0x03 | SPITZ_MOD_SHIFT; /* quotedbl */
+ s->pre_map[0x04 | SPITZ_MOD_SHIFT] = 0x04 | SPITZ_MOD_SHIFT; /* # */
+ s->pre_map[0x05 | SPITZ_MOD_SHIFT] = 0x05 | SPITZ_MOD_SHIFT; /* dollar */
+ s->pre_map[0x06 | SPITZ_MOD_SHIFT] = 0x06 | SPITZ_MOD_SHIFT; /* percent */
+ s->pre_map[0x08 | SPITZ_MOD_SHIFT] = 0x07 | SPITZ_MOD_SHIFT; /* ampersand */
+ s->pre_map[0x28] = 0x08 | SPITZ_MOD_SHIFT; /* ' */
+ s->pre_map[0x0a | SPITZ_MOD_SHIFT] = 0x09 | SPITZ_MOD_SHIFT; /* ( */
+ s->pre_map[0x0b | SPITZ_MOD_SHIFT] = 0x0a | SPITZ_MOD_SHIFT; /* ) */
+ s->pre_map[0x29 | SPITZ_MOD_SHIFT] = 0x0b | SPITZ_MOD_SHIFT; /* tilde */
+ s->pre_map[0x03 | SPITZ_MOD_SHIFT] = 0x0c | SPITZ_MOD_SHIFT; /* at */
+ s->pre_map[0xd3] = 0x0e | SPITZ_MOD_FN; /* Delete */
+ s->pre_map[0x3a] = 0x0f | SPITZ_MOD_FN; /* Caps_Lock */
+ s->pre_map[0x07 | SPITZ_MOD_SHIFT] = 0x11 | SPITZ_MOD_FN; /* ^ */
+ s->pre_map[0x0d] = 0x12 | SPITZ_MOD_FN; /* equal */
+ s->pre_map[0x0d | SPITZ_MOD_SHIFT] = 0x13 | SPITZ_MOD_FN; /* plus */
+ s->pre_map[0x1a] = 0x14 | SPITZ_MOD_FN; /* [ */
+ s->pre_map[0x1b] = 0x15 | SPITZ_MOD_FN; /* ] */
+ s->pre_map[0x1a | SPITZ_MOD_SHIFT] = 0x16 | SPITZ_MOD_FN; /* { */
+ s->pre_map[0x1b | SPITZ_MOD_SHIFT] = 0x17 | SPITZ_MOD_FN; /* } */
+ s->pre_map[0x27] = 0x22 | SPITZ_MOD_FN; /* semicolon */
+ s->pre_map[0x27 | SPITZ_MOD_SHIFT] = 0x23 | SPITZ_MOD_FN; /* colon */
+ s->pre_map[0x09 | SPITZ_MOD_SHIFT] = 0x24 | SPITZ_MOD_FN; /* asterisk */
+ s->pre_map[0x2b] = 0x25 | SPITZ_MOD_FN; /* backslash */
+ s->pre_map[0x2b | SPITZ_MOD_SHIFT] = 0x26 | SPITZ_MOD_FN; /* bar */
+ s->pre_map[0x0c | SPITZ_MOD_SHIFT] = 0x30 | SPITZ_MOD_FN; /* _ */
+ s->pre_map[0x33 | SPITZ_MOD_SHIFT] = 0x33 | SPITZ_MOD_FN; /* less */
+ s->pre_map[0x35] = 0x33 | SPITZ_MOD_SHIFT; /* slash */
+ s->pre_map[0x34 | SPITZ_MOD_SHIFT] = 0x34 | SPITZ_MOD_FN; /* greater */
+ s->pre_map[0x35 | SPITZ_MOD_SHIFT] = 0x34 | SPITZ_MOD_SHIFT; /* question */
+ s->pre_map[0x49] = 0x48 | SPITZ_MOD_FN; /* Page_Up */
+ s->pre_map[0x51] = 0x50 | SPITZ_MOD_FN; /* Page_Down */
+
+ s->modifiers = 0;
+ s->imodifiers = 0;
+ s->fifopos = 0;
+ s->fifolen = 0;
+}
+
+#undef SPITZ_MOD_SHIFT
+#undef SPITZ_MOD_CTRL
+#undef SPITZ_MOD_FN
+
+static int spitz_keyboard_post_load(void *opaque, int version_id)
+{
+ SpitzKeyboardState *s = (SpitzKeyboardState *) opaque;
+
+ /* Release all pressed keys */
+ memset(s->keyrow, 0, sizeof(s->keyrow));
+ spitz_keyboard_sense_update(s);
+ s->modifiers = 0;
+ s->imodifiers = 0;
+ s->fifopos = 0;
+ s->fifolen = 0;
+
+ return 0;
+}
+
+static void spitz_keyboard_register(PXA2xxState *cpu)
+{
+ int i;
+ DeviceState *dev;
+ SpitzKeyboardState *s;
+
+ dev = sysbus_create_simple(TYPE_SPITZ_KEYBOARD, -1, NULL);
+ s = SPITZ_KEYBOARD(dev);
+
+ for (i = 0; i < SPITZ_KEY_SENSE_NUM; i ++)
+ qdev_connect_gpio_out(dev, i, qdev_get_gpio_in(cpu->gpio, spitz_gpio_key_sense[i]));
+
+ for (i = 0; i < 5; i ++)
+ s->gpiomap[i] = qdev_get_gpio_in(cpu->gpio, spitz_gpiomap[i]);
+
+ if (!graphic_rotate)
+ s->gpiomap[4] = qemu_irq_invert(s->gpiomap[4]);
+
+ for (i = 0; i < 5; i++)
+ qemu_set_irq(s->gpiomap[i], 0);
+
+ for (i = 0; i < SPITZ_KEY_STROBE_NUM; i ++)
+ qdev_connect_gpio_out(cpu->gpio, spitz_gpio_key_strobe[i],
+ qdev_get_gpio_in(dev, i));
+
+ timer_mod(s->kbdtimer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+
+ qemu_add_kbd_event_handler(spitz_keyboard_handler, s);
+}
+
+static int spitz_keyboard_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ SpitzKeyboardState *s = SPITZ_KEYBOARD(dev);
+ int i, j;
+
+ for (i = 0; i < 0x80; i ++)
+ s->keymap[i] = -1;
+ for (i = 0; i < SPITZ_KEY_SENSE_NUM + 1; i ++)
+ for (j = 0; j < SPITZ_KEY_STROBE_NUM; j ++)
+ if (spitz_keymap[i][j] != -1)
+ s->keymap[spitz_keymap[i][j]] = (i << 4) | j;
+
+ spitz_keyboard_pre_map(s);
+
+ s->kbdtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, spitz_keyboard_tick, s);
+ qdev_init_gpio_in(dev, spitz_keyboard_strobe, SPITZ_KEY_STROBE_NUM);
+ qdev_init_gpio_out(dev, s->sense, SPITZ_KEY_SENSE_NUM);
+
+ return 0;
+}
+
+/* LCD backlight controller */
+
+#define LCDTG_RESCTL 0x00
+#define LCDTG_PHACTRL 0x01
+#define LCDTG_DUTYCTRL 0x02
+#define LCDTG_POWERREG0 0x03
+#define LCDTG_POWERREG1 0x04
+#define LCDTG_GPOR3 0x05
+#define LCDTG_PICTRL 0x06
+#define LCDTG_POLCTRL 0x07
+
+typedef struct {
+ SSISlave ssidev;
+ uint32_t bl_intensity;
+ uint32_t bl_power;
+} SpitzLCDTG;
+
+static void spitz_bl_update(SpitzLCDTG *s)
+{
+ if (s->bl_power && s->bl_intensity)
+ zaurus_printf("LCD Backlight now at %i/63\n", s->bl_intensity);
+ else
+ zaurus_printf("LCD Backlight now off\n");
+}
+
+/* FIXME: Implement GPIO properly and remove this hack. */
+static SpitzLCDTG *spitz_lcdtg;
+
+static inline void spitz_bl_bit5(void *opaque, int line, int level)
+{
+ SpitzLCDTG *s = spitz_lcdtg;
+ int prev = s->bl_intensity;
+
+ if (level)
+ s->bl_intensity &= ~0x20;
+ else
+ s->bl_intensity |= 0x20;
+
+ if (s->bl_power && prev != s->bl_intensity)
+ spitz_bl_update(s);
+}
+
+static inline void spitz_bl_power(void *opaque, int line, int level)
+{
+ SpitzLCDTG *s = spitz_lcdtg;
+ s->bl_power = !!level;
+ spitz_bl_update(s);
+}
+
+static uint32_t spitz_lcdtg_transfer(SSISlave *dev, uint32_t value)
+{
+ SpitzLCDTG *s = FROM_SSI_SLAVE(SpitzLCDTG, dev);
+ int addr;
+ addr = value >> 5;
+ value &= 0x1f;
+
+ switch (addr) {
+ case LCDTG_RESCTL:
+ if (value)
+ zaurus_printf("LCD in QVGA mode\n");
+ else
+ zaurus_printf("LCD in VGA mode\n");
+ break;
+
+ case LCDTG_DUTYCTRL:
+ s->bl_intensity &= ~0x1f;
+ s->bl_intensity |= value;
+ if (s->bl_power)
+ spitz_bl_update(s);
+ break;
+
+ case LCDTG_POWERREG0:
+ /* Set common voltage to M62332FP */
+ break;
+ }
+ return 0;
+}
+
+static int spitz_lcdtg_init(SSISlave *dev)
+{
+ SpitzLCDTG *s = FROM_SSI_SLAVE(SpitzLCDTG, dev);
+
+ spitz_lcdtg = s;
+ s->bl_power = 0;
+ s->bl_intensity = 0x20;
+
+ return 0;
+}
+
+/* SSP devices */
+
+#define CORGI_SSP_PORT 2
+
+#define SPITZ_GPIO_LCDCON_CS 53
+#define SPITZ_GPIO_ADS7846_CS 14
+#define SPITZ_GPIO_MAX1111_CS 20
+#define SPITZ_GPIO_TP_INT 11
+
+static DeviceState *max1111;
+
+/* "Demux" the signal based on current chipselect */
+typedef struct {
+ SSISlave ssidev;
+ SSIBus *bus[3];
+ uint32_t enable[3];
+} CorgiSSPState;
+
+static uint32_t corgi_ssp_transfer(SSISlave *dev, uint32_t value)
+{
+ CorgiSSPState *s = FROM_SSI_SLAVE(CorgiSSPState, dev);
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (s->enable[i]) {
+ return ssi_transfer(s->bus[i], value);
+ }
+ }
+ return 0;
+}
+
+static void corgi_ssp_gpio_cs(void *opaque, int line, int level)
+{
+ CorgiSSPState *s = (CorgiSSPState *)opaque;
+ assert(line >= 0 && line < 3);
+ s->enable[line] = !level;
+}
+
+#define MAX1111_BATT_VOLT 1
+#define MAX1111_BATT_TEMP 2
+#define MAX1111_ACIN_VOLT 3
+
+#define SPITZ_BATTERY_TEMP 0xe0 /* About 2.9V */
+#define SPITZ_BATTERY_VOLT 0xd0 /* About 4.0V */
+#define SPITZ_CHARGEON_ACIN 0x80 /* About 5.0V */
+
+static void spitz_adc_temp_on(void *opaque, int line, int level)
+{
+ if (!max1111)
+ return;
+
+ if (level)
+ max111x_set_input(max1111, MAX1111_BATT_TEMP, SPITZ_BATTERY_TEMP);
+ else
+ max111x_set_input(max1111, MAX1111_BATT_TEMP, 0);
+}
+
+static int corgi_ssp_init(SSISlave *d)
+{
+ DeviceState *dev = DEVICE(d);
+ CorgiSSPState *s = FROM_SSI_SLAVE(CorgiSSPState, d);
+
+ qdev_init_gpio_in(dev, corgi_ssp_gpio_cs, 3);
+ s->bus[0] = ssi_create_bus(dev, "ssi0");
+ s->bus[1] = ssi_create_bus(dev, "ssi1");
+ s->bus[2] = ssi_create_bus(dev, "ssi2");
+
+ return 0;
+}
+
+static void spitz_ssp_attach(PXA2xxState *cpu)
+{
+ DeviceState *mux;
+ DeviceState *dev;
+ void *bus;
+
+ mux = ssi_create_slave(cpu->ssp[CORGI_SSP_PORT - 1], "corgi-ssp");
+
+ bus = qdev_get_child_bus(mux, "ssi0");
+ ssi_create_slave(bus, "spitz-lcdtg");
+
+ bus = qdev_get_child_bus(mux, "ssi1");
+ dev = ssi_create_slave(bus, "ads7846");
+ qdev_connect_gpio_out(dev, 0,
+ qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_TP_INT));
+
+ bus = qdev_get_child_bus(mux, "ssi2");
+ max1111 = ssi_create_slave(bus, "max1111");
+ max111x_set_input(max1111, MAX1111_BATT_VOLT, SPITZ_BATTERY_VOLT);
+ max111x_set_input(max1111, MAX1111_BATT_TEMP, 0);
+ max111x_set_input(max1111, MAX1111_ACIN_VOLT, SPITZ_CHARGEON_ACIN);
+
+ qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_LCDCON_CS,
+ qdev_get_gpio_in(mux, 0));
+ qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_ADS7846_CS,
+ qdev_get_gpio_in(mux, 1));
+ qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_MAX1111_CS,
+ qdev_get_gpio_in(mux, 2));
+}
+
+/* CF Microdrive */
+
+static void spitz_microdrive_attach(PXA2xxState *cpu, int slot)
+{
+ PCMCIACardState *md;
+ DriveInfo *dinfo;
+
+ dinfo = drive_get(IF_IDE, 0, 0);
+ if (!dinfo || dinfo->media_cd)
+ return;
+ md = dscm1xxxx_init(dinfo);
+ pxa2xx_pcmcia_attach(cpu->pcmcia[slot], md);
+}
+
+/* Wm8750 and Max7310 on I2C */
+
+#define AKITA_MAX_ADDR 0x18
+#define SPITZ_WM_ADDRL 0x1b
+#define SPITZ_WM_ADDRH 0x1a
+
+#define SPITZ_GPIO_WM 5
+
+static void spitz_wm8750_addr(void *opaque, int line, int level)
+{
+ I2CSlave *wm = (I2CSlave *) opaque;
+ if (level)
+ i2c_set_slave_address(wm, SPITZ_WM_ADDRH);
+ else
+ i2c_set_slave_address(wm, SPITZ_WM_ADDRL);
+}
+
+static void spitz_i2c_setup(PXA2xxState *cpu)
+{
+ /* Attach the CPU on one end of our I2C bus. */
+ I2CBus *bus = pxa2xx_i2c_bus(cpu->i2c[0]);
+
+ DeviceState *wm;
+
+ /* Attach a WM8750 to the bus */
+ wm = i2c_create_slave(bus, "wm8750", 0);
+
+ spitz_wm8750_addr(wm, 0, 0);
+ qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_WM,
+ qemu_allocate_irq(spitz_wm8750_addr, wm, 0));
+ /* .. and to the sound interface. */
+ cpu->i2s->opaque = wm;
+ cpu->i2s->codec_out = wm8750_dac_dat;
+ cpu->i2s->codec_in = wm8750_adc_dat;
+ wm8750_data_req_set(wm, cpu->i2s->data_req, cpu->i2s);
+}
+
+static void spitz_akita_i2c_setup(PXA2xxState *cpu)
+{
+ /* Attach a Max7310 to Akita I2C bus. */
+ i2c_create_slave(pxa2xx_i2c_bus(cpu->i2c[0]), "max7310",
+ AKITA_MAX_ADDR);
+}
+
+/* Other peripherals */
+
+static void spitz_out_switch(void *opaque, int line, int level)
+{
+ switch (line) {
+ case 0:
+ zaurus_printf("Charging %s.\n", level ? "off" : "on");
+ break;
+ case 1:
+ zaurus_printf("Discharging %s.\n", level ? "on" : "off");
+ break;
+ case 2:
+ zaurus_printf("Green LED %s.\n", level ? "on" : "off");
+ break;
+ case 3:
+ zaurus_printf("Orange LED %s.\n", level ? "on" : "off");
+ break;
+ case 4:
+ spitz_bl_bit5(opaque, line, level);
+ break;
+ case 5:
+ spitz_bl_power(opaque, line, level);
+ break;
+ case 6:
+ spitz_adc_temp_on(opaque, line, level);
+ break;
+ }
+}
+
+#define SPITZ_SCP_LED_GREEN 1
+#define SPITZ_SCP_JK_B 2
+#define SPITZ_SCP_CHRG_ON 3
+#define SPITZ_SCP_MUTE_L 4
+#define SPITZ_SCP_MUTE_R 5
+#define SPITZ_SCP_CF_POWER 6
+#define SPITZ_SCP_LED_ORANGE 7
+#define SPITZ_SCP_JK_A 8
+#define SPITZ_SCP_ADC_TEMP_ON 9
+#define SPITZ_SCP2_IR_ON 1
+#define SPITZ_SCP2_AKIN_PULLUP 2
+#define SPITZ_SCP2_BACKLIGHT_CONT 7
+#define SPITZ_SCP2_BACKLIGHT_ON 8
+#define SPITZ_SCP2_MIC_BIAS 9
+
+static void spitz_scoop_gpio_setup(PXA2xxState *cpu,
+ DeviceState *scp0, DeviceState *scp1)
+{
+ qemu_irq *outsignals = qemu_allocate_irqs(spitz_out_switch, cpu, 8);
+
+ qdev_connect_gpio_out(scp0, SPITZ_SCP_CHRG_ON, outsignals[0]);
+ qdev_connect_gpio_out(scp0, SPITZ_SCP_JK_B, outsignals[1]);
+ qdev_connect_gpio_out(scp0, SPITZ_SCP_LED_GREEN, outsignals[2]);
+ qdev_connect_gpio_out(scp0, SPITZ_SCP_LED_ORANGE, outsignals[3]);
+
+ if (scp1) {
+ qdev_connect_gpio_out(scp1, SPITZ_SCP2_BACKLIGHT_CONT, outsignals[4]);
+ qdev_connect_gpio_out(scp1, SPITZ_SCP2_BACKLIGHT_ON, outsignals[5]);
+ }
+
+ qdev_connect_gpio_out(scp0, SPITZ_SCP_ADC_TEMP_ON, outsignals[6]);
+}
+
+#define SPITZ_GPIO_HSYNC 22
+#define SPITZ_GPIO_SD_DETECT 9
+#define SPITZ_GPIO_SD_WP 81
+#define SPITZ_GPIO_ON_RESET 89
+#define SPITZ_GPIO_BAT_COVER 90
+#define SPITZ_GPIO_CF1_IRQ 105
+#define SPITZ_GPIO_CF1_CD 94
+#define SPITZ_GPIO_CF2_IRQ 106
+#define SPITZ_GPIO_CF2_CD 93
+
+static int spitz_hsync;
+
+static void spitz_lcd_hsync_handler(void *opaque, int line, int level)
+{
+ PXA2xxState *cpu = (PXA2xxState *) opaque;
+ qemu_set_irq(qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_HSYNC), spitz_hsync);
+ spitz_hsync ^= 1;
+}
+
+static void spitz_gpio_setup(PXA2xxState *cpu, int slots)
+{
+ qemu_irq lcd_hsync;
+ /*
+ * Bad hack: We toggle the LCD hsync GPIO on every GPIO status
+ * read to satisfy broken guests that poll-wait for hsync.
+ * Simulating a real hsync event would be less practical and
+ * wouldn't guarantee that a guest ever exits the loop.
+ */
+ spitz_hsync = 0;
+ lcd_hsync = qemu_allocate_irq(spitz_lcd_hsync_handler, cpu, 0);
+ pxa2xx_gpio_read_notifier(cpu->gpio, lcd_hsync);
+ pxa2xx_lcd_vsync_notifier(cpu->lcd, lcd_hsync);
+
+ /* MMC/SD host */
+ pxa2xx_mmci_handlers(cpu->mmc,
+ qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_SD_WP),
+ qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_SD_DETECT));
+
+ /* Battery lock always closed */
+ qemu_irq_raise(qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_BAT_COVER));
+
+ /* Handle reset */
+ qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_ON_RESET, cpu->reset);
+
+ /* PCMCIA signals: card's IRQ and Card-Detect */
+ if (slots >= 1)
+ pxa2xx_pcmcia_set_irq_cb(cpu->pcmcia[0],
+ qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_CF1_IRQ),
+ qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_CF1_CD));
+ if (slots >= 2)
+ pxa2xx_pcmcia_set_irq_cb(cpu->pcmcia[1],
+ qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_CF2_IRQ),
+ qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_CF2_CD));
+}
+
+/* Board init. */
+enum spitz_model_e { spitz, akita, borzoi, terrier };
+
+#define SPITZ_RAM 0x04000000
+#define SPITZ_ROM 0x00800000
+
+static struct arm_boot_info spitz_binfo = {
+ .loader_start = PXA2XX_SDRAM_BASE,
+ .ram_size = 0x04000000,
+};
+
+static void spitz_common_init(MachineState *machine,
+ enum spitz_model_e model, int arm_id)
+{
+ PXA2xxState *mpu;
+ DeviceState *scp0, *scp1 = NULL;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *rom = g_new(MemoryRegion, 1);
+ const char *cpu_model = machine->cpu_model;
+
+ if (!cpu_model)
+ cpu_model = (model == terrier) ? "pxa270-c5" : "pxa270-c0";
+
+ /* Setup CPU & memory */
+ mpu = pxa270_init(address_space_mem, spitz_binfo.ram_size, cpu_model);
+
+ sl_flash_register(mpu, (model == spitz) ? FLASH_128M : FLASH_1024M);
+
+ memory_region_init_ram(rom, NULL, "spitz.rom", SPITZ_ROM, &error_abort);
+ vmstate_register_ram_global(rom);
+ memory_region_set_readonly(rom, true);
+ memory_region_add_subregion(address_space_mem, 0, rom);
+
+ /* Setup peripherals */
+ spitz_keyboard_register(mpu);
+
+ spitz_ssp_attach(mpu);
+
+ scp0 = sysbus_create_simple("scoop", 0x10800000, NULL);
+ if (model != akita) {
+ scp1 = sysbus_create_simple("scoop", 0x08800040, NULL);
+ }
+
+ spitz_scoop_gpio_setup(mpu, scp0, scp1);
+
+ spitz_gpio_setup(mpu, (model == akita) ? 1 : 2);
+
+ spitz_i2c_setup(mpu);
+
+ if (model == akita)
+ spitz_akita_i2c_setup(mpu);
+
+ if (model == terrier)
+ /* A 6.0 GB microdrive is permanently sitting in CF slot 1. */
+ spitz_microdrive_attach(mpu, 1);
+ else if (model != akita)
+ /* A 4.0 GB microdrive is permanently sitting in CF slot 0. */
+ spitz_microdrive_attach(mpu, 0);
+
+ spitz_binfo.kernel_filename = machine->kernel_filename;
+ spitz_binfo.kernel_cmdline = machine->kernel_cmdline;
+ spitz_binfo.initrd_filename = machine->initrd_filename;
+ spitz_binfo.board_id = arm_id;
+ arm_load_kernel(mpu->cpu, &spitz_binfo);
+ sl_bootparam_write(SL_PXA_PARAM_BASE);
+}
+
+static void spitz_init(MachineState *machine)
+{
+ spitz_common_init(machine, spitz, 0x2c9);
+}
+
+static void borzoi_init(MachineState *machine)
+{
+ spitz_common_init(machine, borzoi, 0x33f);
+}
+
+static void akita_init(MachineState *machine)
+{
+ spitz_common_init(machine, akita, 0x2e8);
+}
+
+static void terrier_init(MachineState *machine)
+{
+ spitz_common_init(machine, terrier, 0x33f);
+}
+
+static QEMUMachine akitapda_machine = {
+ .name = "akita",
+ .desc = "Akita PDA (PXA270)",
+ .init = akita_init,
+};
+
+static QEMUMachine spitzpda_machine = {
+ .name = "spitz",
+ .desc = "Spitz PDA (PXA270)",
+ .init = spitz_init,
+};
+
+static QEMUMachine borzoipda_machine = {
+ .name = "borzoi",
+ .desc = "Borzoi PDA (PXA270)",
+ .init = borzoi_init,
+};
+
+static QEMUMachine terrierpda_machine = {
+ .name = "terrier",
+ .desc = "Terrier PDA (PXA270)",
+ .init = terrier_init,
+};
+
+static void spitz_machine_init(void)
+{
+ qemu_register_machine(&akitapda_machine);
+ qemu_register_machine(&spitzpda_machine);
+ qemu_register_machine(&borzoipda_machine);
+ qemu_register_machine(&terrierpda_machine);
+}
+
+machine_init(spitz_machine_init);
+
+static bool is_version_0(void *opaque, int version_id)
+{
+ return version_id == 0;
+}
+
+static VMStateDescription vmstate_sl_nand_info = {
+ .name = "sl-nand",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(ctl, SLNANDState),
+ VMSTATE_STRUCT(ecc, SLNANDState, 0, vmstate_ecc_state, ECCState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static Property sl_nand_properties[] = {
+ DEFINE_PROP_UINT8("manf_id", SLNANDState, manf_id, NAND_MFR_SAMSUNG),
+ DEFINE_PROP_UINT8("chip_id", SLNANDState, chip_id, 0xf1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sl_nand_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = sl_nand_init;
+ dc->vmsd = &vmstate_sl_nand_info;
+ dc->props = sl_nand_properties;
+ /* Reason: init() method uses drive_get() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo sl_nand_info = {
+ .name = TYPE_SL_NAND,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SLNANDState),
+ .class_init = sl_nand_class_init,
+};
+
+static VMStateDescription vmstate_spitz_kbd = {
+ .name = "spitz-keyboard",
+ .version_id = 1,
+ .minimum_version_id = 0,
+ .post_load = spitz_keyboard_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(sense_state, SpitzKeyboardState),
+ VMSTATE_UINT16(strobe_state, SpitzKeyboardState),
+ VMSTATE_UNUSED_TEST(is_version_0, 5),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static Property spitz_keyboard_properties[] = {
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void spitz_keyboard_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = spitz_keyboard_init;
+ dc->vmsd = &vmstate_spitz_kbd;
+ dc->props = spitz_keyboard_properties;
+}
+
+static const TypeInfo spitz_keyboard_info = {
+ .name = TYPE_SPITZ_KEYBOARD,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SpitzKeyboardState),
+ .class_init = spitz_keyboard_class_init,
+};
+
+static const VMStateDescription vmstate_corgi_ssp_regs = {
+ .name = "corgi-ssp",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_SSI_SLAVE(ssidev, CorgiSSPState),
+ VMSTATE_UINT32_ARRAY(enable, CorgiSSPState, 3),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static void corgi_ssp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = corgi_ssp_init;
+ k->transfer = corgi_ssp_transfer;
+ dc->vmsd = &vmstate_corgi_ssp_regs;
+}
+
+static const TypeInfo corgi_ssp_info = {
+ .name = "corgi-ssp",
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(CorgiSSPState),
+ .class_init = corgi_ssp_class_init,
+};
+
+static const VMStateDescription vmstate_spitz_lcdtg_regs = {
+ .name = "spitz-lcdtg",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_SSI_SLAVE(ssidev, SpitzLCDTG),
+ VMSTATE_UINT32(bl_intensity, SpitzLCDTG),
+ VMSTATE_UINT32(bl_power, SpitzLCDTG),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static void spitz_lcdtg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = spitz_lcdtg_init;
+ k->transfer = spitz_lcdtg_transfer;
+ dc->vmsd = &vmstate_spitz_lcdtg_regs;
+}
+
+static const TypeInfo spitz_lcdtg_info = {
+ .name = "spitz-lcdtg",
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(SpitzLCDTG),
+ .class_init = spitz_lcdtg_class_init,
+};
+
+static void spitz_register_types(void)
+{
+ type_register_static(&corgi_ssp_info);
+ type_register_static(&spitz_lcdtg_info);
+ type_register_static(&spitz_keyboard_info);
+ type_register_static(&sl_nand_info);
+}
+
+type_init(spitz_register_types)
diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c
new file mode 100644
index 00000000..cb515ec7
--- /dev/null
+++ b/hw/arm/stellaris.c
@@ -0,0 +1,1438 @@
+/*
+ * Luminary Micro Stellaris peripherals
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/ssi.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "qemu/timer.h"
+#include "hw/i2c/i2c.h"
+#include "net/net.h"
+#include "hw/boards.h"
+#include "exec/address-spaces.h"
+
+#define GPIO_A 0
+#define GPIO_B 1
+#define GPIO_C 2
+#define GPIO_D 3
+#define GPIO_E 4
+#define GPIO_F 5
+#define GPIO_G 6
+
+#define BP_OLED_I2C 0x01
+#define BP_OLED_SSI 0x02
+#define BP_GAMEPAD 0x04
+
+#define NUM_IRQ_LINES 64
+
+typedef const struct {
+ const char *name;
+ uint32_t did0;
+ uint32_t did1;
+ uint32_t dc0;
+ uint32_t dc1;
+ uint32_t dc2;
+ uint32_t dc3;
+ uint32_t dc4;
+ uint32_t peripherals;
+} stellaris_board_info;
+
+/* General purpose timer module. */
+
+#define TYPE_STELLARIS_GPTM "stellaris-gptm"
+#define STELLARIS_GPTM(obj) \
+ OBJECT_CHECK(gptm_state, (obj), TYPE_STELLARIS_GPTM)
+
+typedef struct gptm_state {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t config;
+ uint32_t mode[2];
+ uint32_t control;
+ uint32_t state;
+ uint32_t mask;
+ uint32_t load[2];
+ uint32_t match[2];
+ uint32_t prescale[2];
+ uint32_t match_prescale[2];
+ uint32_t rtc;
+ int64_t tick[2];
+ struct gptm_state *opaque[2];
+ QEMUTimer *timer[2];
+ /* The timers have an alternate output used to trigger the ADC. */
+ qemu_irq trigger;
+ qemu_irq irq;
+} gptm_state;
+
+static void gptm_update_irq(gptm_state *s)
+{
+ int level;
+ level = (s->state & s->mask) != 0;
+ qemu_set_irq(s->irq, level);
+}
+
+static void gptm_stop(gptm_state *s, int n)
+{
+ timer_del(s->timer[n]);
+}
+
+static void gptm_reload(gptm_state *s, int n, int reset)
+{
+ int64_t tick;
+ if (reset)
+ tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ else
+ tick = s->tick[n];
+
+ if (s->config == 0) {
+ /* 32-bit CountDown. */
+ uint32_t count;
+ count = s->load[0] | (s->load[1] << 16);
+ tick += (int64_t)count * system_clock_scale;
+ } else if (s->config == 1) {
+ /* 32-bit RTC. 1Hz tick. */
+ tick += get_ticks_per_sec();
+ } else if (s->mode[n] == 0xa) {
+ /* PWM mode. Not implemented. */
+ } else {
+ hw_error("TODO: 16-bit timer mode 0x%x\n", s->mode[n]);
+ }
+ s->tick[n] = tick;
+ timer_mod(s->timer[n], tick);
+}
+
+static void gptm_tick(void *opaque)
+{
+ gptm_state **p = (gptm_state **)opaque;
+ gptm_state *s;
+ int n;
+
+ s = *p;
+ n = p - s->opaque;
+ if (s->config == 0) {
+ s->state |= 1;
+ if ((s->control & 0x20)) {
+ /* Output trigger. */
+ qemu_irq_pulse(s->trigger);
+ }
+ if (s->mode[0] & 1) {
+ /* One-shot. */
+ s->control &= ~1;
+ } else {
+ /* Periodic. */
+ gptm_reload(s, 0, 0);
+ }
+ } else if (s->config == 1) {
+ /* RTC. */
+ uint32_t match;
+ s->rtc++;
+ match = s->match[0] | (s->match[1] << 16);
+ if (s->rtc > match)
+ s->rtc = 0;
+ if (s->rtc == 0) {
+ s->state |= 8;
+ }
+ gptm_reload(s, 0, 0);
+ } else if (s->mode[n] == 0xa) {
+ /* PWM mode. Not implemented. */
+ } else {
+ hw_error("TODO: 16-bit timer mode 0x%x\n", s->mode[n]);
+ }
+ gptm_update_irq(s);
+}
+
+static uint64_t gptm_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ gptm_state *s = (gptm_state *)opaque;
+
+ switch (offset) {
+ case 0x00: /* CFG */
+ return s->config;
+ case 0x04: /* TAMR */
+ return s->mode[0];
+ case 0x08: /* TBMR */
+ return s->mode[1];
+ case 0x0c: /* CTL */
+ return s->control;
+ case 0x18: /* IMR */
+ return s->mask;
+ case 0x1c: /* RIS */
+ return s->state;
+ case 0x20: /* MIS */
+ return s->state & s->mask;
+ case 0x24: /* CR */
+ return 0;
+ case 0x28: /* TAILR */
+ return s->load[0] | ((s->config < 4) ? (s->load[1] << 16) : 0);
+ case 0x2c: /* TBILR */
+ return s->load[1];
+ case 0x30: /* TAMARCHR */
+ return s->match[0] | ((s->config < 4) ? (s->match[1] << 16) : 0);
+ case 0x34: /* TBMATCHR */
+ return s->match[1];
+ case 0x38: /* TAPR */
+ return s->prescale[0];
+ case 0x3c: /* TBPR */
+ return s->prescale[1];
+ case 0x40: /* TAPMR */
+ return s->match_prescale[0];
+ case 0x44: /* TBPMR */
+ return s->match_prescale[1];
+ case 0x48: /* TAR */
+ if (s->config == 1) {
+ return s->rtc;
+ }
+ qemu_log_mask(LOG_UNIMP,
+ "GPTM: read of TAR but timer read not supported");
+ return 0;
+ case 0x4c: /* TBR */
+ qemu_log_mask(LOG_UNIMP,
+ "GPTM: read of TBR but timer read not supported");
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "GPTM: read at bad offset 0x%x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void gptm_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ gptm_state *s = (gptm_state *)opaque;
+ uint32_t oldval;
+
+ /* The timers should be disabled before changing the configuration.
+ We take advantage of this and defer everything until the timer
+ is enabled. */
+ switch (offset) {
+ case 0x00: /* CFG */
+ s->config = value;
+ break;
+ case 0x04: /* TAMR */
+ s->mode[0] = value;
+ break;
+ case 0x08: /* TBMR */
+ s->mode[1] = value;
+ break;
+ case 0x0c: /* CTL */
+ oldval = s->control;
+ s->control = value;
+ /* TODO: Implement pause. */
+ if ((oldval ^ value) & 1) {
+ if (value & 1) {
+ gptm_reload(s, 0, 1);
+ } else {
+ gptm_stop(s, 0);
+ }
+ }
+ if (((oldval ^ value) & 0x100) && s->config >= 4) {
+ if (value & 0x100) {
+ gptm_reload(s, 1, 1);
+ } else {
+ gptm_stop(s, 1);
+ }
+ }
+ break;
+ case 0x18: /* IMR */
+ s->mask = value & 0x77;
+ gptm_update_irq(s);
+ break;
+ case 0x24: /* CR */
+ s->state &= ~value;
+ break;
+ case 0x28: /* TAILR */
+ s->load[0] = value & 0xffff;
+ if (s->config < 4) {
+ s->load[1] = value >> 16;
+ }
+ break;
+ case 0x2c: /* TBILR */
+ s->load[1] = value & 0xffff;
+ break;
+ case 0x30: /* TAMARCHR */
+ s->match[0] = value & 0xffff;
+ if (s->config < 4) {
+ s->match[1] = value >> 16;
+ }
+ break;
+ case 0x34: /* TBMATCHR */
+ s->match[1] = value >> 16;
+ break;
+ case 0x38: /* TAPR */
+ s->prescale[0] = value;
+ break;
+ case 0x3c: /* TBPR */
+ s->prescale[1] = value;
+ break;
+ case 0x40: /* TAPMR */
+ s->match_prescale[0] = value;
+ break;
+ case 0x44: /* TBPMR */
+ s->match_prescale[0] = value;
+ break;
+ default:
+ hw_error("gptm_write: Bad offset 0x%x\n", (int)offset);
+ }
+ gptm_update_irq(s);
+}
+
+static const MemoryRegionOps gptm_ops = {
+ .read = gptm_read,
+ .write = gptm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_stellaris_gptm = {
+ .name = "stellaris_gptm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(config, gptm_state),
+ VMSTATE_UINT32_ARRAY(mode, gptm_state, 2),
+ VMSTATE_UINT32(control, gptm_state),
+ VMSTATE_UINT32(state, gptm_state),
+ VMSTATE_UINT32(mask, gptm_state),
+ VMSTATE_UNUSED(8),
+ VMSTATE_UINT32_ARRAY(load, gptm_state, 2),
+ VMSTATE_UINT32_ARRAY(match, gptm_state, 2),
+ VMSTATE_UINT32_ARRAY(prescale, gptm_state, 2),
+ VMSTATE_UINT32_ARRAY(match_prescale, gptm_state, 2),
+ VMSTATE_UINT32(rtc, gptm_state),
+ VMSTATE_INT64_ARRAY(tick, gptm_state, 2),
+ VMSTATE_TIMER_PTR_ARRAY(timer, gptm_state, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int stellaris_gptm_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ gptm_state *s = STELLARIS_GPTM(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_out(dev, &s->trigger, 1);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &gptm_ops, s,
+ "gptm", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->opaque[0] = s->opaque[1] = s;
+ s->timer[0] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[0]);
+ s->timer[1] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[1]);
+ vmstate_register(dev, -1, &vmstate_stellaris_gptm, s);
+ return 0;
+}
+
+
+/* System controller. */
+
+typedef struct {
+ MemoryRegion iomem;
+ uint32_t pborctl;
+ uint32_t ldopctl;
+ uint32_t int_status;
+ uint32_t int_mask;
+ uint32_t resc;
+ uint32_t rcc;
+ uint32_t rcc2;
+ uint32_t rcgc[3];
+ uint32_t scgc[3];
+ uint32_t dcgc[3];
+ uint32_t clkvclr;
+ uint32_t ldoarst;
+ uint32_t user0;
+ uint32_t user1;
+ qemu_irq irq;
+ stellaris_board_info *board;
+} ssys_state;
+
+static void ssys_update(ssys_state *s)
+{
+ qemu_set_irq(s->irq, (s->int_status & s->int_mask) != 0);
+}
+
+static uint32_t pllcfg_sandstorm[16] = {
+ 0x31c0, /* 1 Mhz */
+ 0x1ae0, /* 1.8432 Mhz */
+ 0x18c0, /* 2 Mhz */
+ 0xd573, /* 2.4576 Mhz */
+ 0x37a6, /* 3.57954 Mhz */
+ 0x1ae2, /* 3.6864 Mhz */
+ 0x0c40, /* 4 Mhz */
+ 0x98bc, /* 4.906 Mhz */
+ 0x935b, /* 4.9152 Mhz */
+ 0x09c0, /* 5 Mhz */
+ 0x4dee, /* 5.12 Mhz */
+ 0x0c41, /* 6 Mhz */
+ 0x75db, /* 6.144 Mhz */
+ 0x1ae6, /* 7.3728 Mhz */
+ 0x0600, /* 8 Mhz */
+ 0x585b /* 8.192 Mhz */
+};
+
+static uint32_t pllcfg_fury[16] = {
+ 0x3200, /* 1 Mhz */
+ 0x1b20, /* 1.8432 Mhz */
+ 0x1900, /* 2 Mhz */
+ 0xf42b, /* 2.4576 Mhz */
+ 0x37e3, /* 3.57954 Mhz */
+ 0x1b21, /* 3.6864 Mhz */
+ 0x0c80, /* 4 Mhz */
+ 0x98ee, /* 4.906 Mhz */
+ 0xd5b4, /* 4.9152 Mhz */
+ 0x0a00, /* 5 Mhz */
+ 0x4e27, /* 5.12 Mhz */
+ 0x1902, /* 6 Mhz */
+ 0xec1c, /* 6.144 Mhz */
+ 0x1b23, /* 7.3728 Mhz */
+ 0x0640, /* 8 Mhz */
+ 0xb11c /* 8.192 Mhz */
+};
+
+#define DID0_VER_MASK 0x70000000
+#define DID0_VER_0 0x00000000
+#define DID0_VER_1 0x10000000
+
+#define DID0_CLASS_MASK 0x00FF0000
+#define DID0_CLASS_SANDSTORM 0x00000000
+#define DID0_CLASS_FURY 0x00010000
+
+static int ssys_board_class(const ssys_state *s)
+{
+ uint32_t did0 = s->board->did0;
+ switch (did0 & DID0_VER_MASK) {
+ case DID0_VER_0:
+ return DID0_CLASS_SANDSTORM;
+ case DID0_VER_1:
+ switch (did0 & DID0_CLASS_MASK) {
+ case DID0_CLASS_SANDSTORM:
+ case DID0_CLASS_FURY:
+ return did0 & DID0_CLASS_MASK;
+ }
+ /* for unknown classes, fall through */
+ default:
+ hw_error("ssys_board_class: Unknown class 0x%08x\n", did0);
+ }
+}
+
+static uint64_t ssys_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ ssys_state *s = (ssys_state *)opaque;
+
+ switch (offset) {
+ case 0x000: /* DID0 */
+ return s->board->did0;
+ case 0x004: /* DID1 */
+ return s->board->did1;
+ case 0x008: /* DC0 */
+ return s->board->dc0;
+ case 0x010: /* DC1 */
+ return s->board->dc1;
+ case 0x014: /* DC2 */
+ return s->board->dc2;
+ case 0x018: /* DC3 */
+ return s->board->dc3;
+ case 0x01c: /* DC4 */
+ return s->board->dc4;
+ case 0x030: /* PBORCTL */
+ return s->pborctl;
+ case 0x034: /* LDOPCTL */
+ return s->ldopctl;
+ case 0x040: /* SRCR0 */
+ return 0;
+ case 0x044: /* SRCR1 */
+ return 0;
+ case 0x048: /* SRCR2 */
+ return 0;
+ case 0x050: /* RIS */
+ return s->int_status;
+ case 0x054: /* IMC */
+ return s->int_mask;
+ case 0x058: /* MISC */
+ return s->int_status & s->int_mask;
+ case 0x05c: /* RESC */
+ return s->resc;
+ case 0x060: /* RCC */
+ return s->rcc;
+ case 0x064: /* PLLCFG */
+ {
+ int xtal;
+ xtal = (s->rcc >> 6) & 0xf;
+ switch (ssys_board_class(s)) {
+ case DID0_CLASS_FURY:
+ return pllcfg_fury[xtal];
+ case DID0_CLASS_SANDSTORM:
+ return pllcfg_sandstorm[xtal];
+ default:
+ hw_error("ssys_read: Unhandled class for PLLCFG read.\n");
+ return 0;
+ }
+ }
+ case 0x070: /* RCC2 */
+ return s->rcc2;
+ case 0x100: /* RCGC0 */
+ return s->rcgc[0];
+ case 0x104: /* RCGC1 */
+ return s->rcgc[1];
+ case 0x108: /* RCGC2 */
+ return s->rcgc[2];
+ case 0x110: /* SCGC0 */
+ return s->scgc[0];
+ case 0x114: /* SCGC1 */
+ return s->scgc[1];
+ case 0x118: /* SCGC2 */
+ return s->scgc[2];
+ case 0x120: /* DCGC0 */
+ return s->dcgc[0];
+ case 0x124: /* DCGC1 */
+ return s->dcgc[1];
+ case 0x128: /* DCGC2 */
+ return s->dcgc[2];
+ case 0x150: /* CLKVCLR */
+ return s->clkvclr;
+ case 0x160: /* LDOARST */
+ return s->ldoarst;
+ case 0x1e0: /* USER0 */
+ return s->user0;
+ case 0x1e4: /* USER1 */
+ return s->user1;
+ default:
+ hw_error("ssys_read: Bad offset 0x%x\n", (int)offset);
+ return 0;
+ }
+}
+
+static bool ssys_use_rcc2(ssys_state *s)
+{
+ return (s->rcc2 >> 31) & 0x1;
+}
+
+/*
+ * Caculate the sys. clock period in ms.
+ */
+static void ssys_calculate_system_clock(ssys_state *s)
+{
+ if (ssys_use_rcc2(s)) {
+ system_clock_scale = 5 * (((s->rcc2 >> 23) & 0x3f) + 1);
+ } else {
+ system_clock_scale = 5 * (((s->rcc >> 23) & 0xf) + 1);
+ }
+}
+
+static void ssys_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ ssys_state *s = (ssys_state *)opaque;
+
+ switch (offset) {
+ case 0x030: /* PBORCTL */
+ s->pborctl = value & 0xffff;
+ break;
+ case 0x034: /* LDOPCTL */
+ s->ldopctl = value & 0x1f;
+ break;
+ case 0x040: /* SRCR0 */
+ case 0x044: /* SRCR1 */
+ case 0x048: /* SRCR2 */
+ fprintf(stderr, "Peripheral reset not implemented\n");
+ break;
+ case 0x054: /* IMC */
+ s->int_mask = value & 0x7f;
+ break;
+ case 0x058: /* MISC */
+ s->int_status &= ~value;
+ break;
+ case 0x05c: /* RESC */
+ s->resc = value & 0x3f;
+ break;
+ case 0x060: /* RCC */
+ if ((s->rcc & (1 << 13)) != 0 && (value & (1 << 13)) == 0) {
+ /* PLL enable. */
+ s->int_status |= (1 << 6);
+ }
+ s->rcc = value;
+ ssys_calculate_system_clock(s);
+ break;
+ case 0x070: /* RCC2 */
+ if (ssys_board_class(s) == DID0_CLASS_SANDSTORM) {
+ break;
+ }
+
+ if ((s->rcc2 & (1 << 13)) != 0 && (value & (1 << 13)) == 0) {
+ /* PLL enable. */
+ s->int_status |= (1 << 6);
+ }
+ s->rcc2 = value;
+ ssys_calculate_system_clock(s);
+ break;
+ case 0x100: /* RCGC0 */
+ s->rcgc[0] = value;
+ break;
+ case 0x104: /* RCGC1 */
+ s->rcgc[1] = value;
+ break;
+ case 0x108: /* RCGC2 */
+ s->rcgc[2] = value;
+ break;
+ case 0x110: /* SCGC0 */
+ s->scgc[0] = value;
+ break;
+ case 0x114: /* SCGC1 */
+ s->scgc[1] = value;
+ break;
+ case 0x118: /* SCGC2 */
+ s->scgc[2] = value;
+ break;
+ case 0x120: /* DCGC0 */
+ s->dcgc[0] = value;
+ break;
+ case 0x124: /* DCGC1 */
+ s->dcgc[1] = value;
+ break;
+ case 0x128: /* DCGC2 */
+ s->dcgc[2] = value;
+ break;
+ case 0x150: /* CLKVCLR */
+ s->clkvclr = value;
+ break;
+ case 0x160: /* LDOARST */
+ s->ldoarst = value;
+ break;
+ default:
+ hw_error("ssys_write: Bad offset 0x%x\n", (int)offset);
+ }
+ ssys_update(s);
+}
+
+static const MemoryRegionOps ssys_ops = {
+ .read = ssys_read,
+ .write = ssys_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ssys_reset(void *opaque)
+{
+ ssys_state *s = (ssys_state *)opaque;
+
+ s->pborctl = 0x7ffd;
+ s->rcc = 0x078e3ac0;
+
+ if (ssys_board_class(s) == DID0_CLASS_SANDSTORM) {
+ s->rcc2 = 0;
+ } else {
+ s->rcc2 = 0x07802810;
+ }
+ s->rcgc[0] = 1;
+ s->scgc[0] = 1;
+ s->dcgc[0] = 1;
+ ssys_calculate_system_clock(s);
+}
+
+static int stellaris_sys_post_load(void *opaque, int version_id)
+{
+ ssys_state *s = opaque;
+
+ ssys_calculate_system_clock(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_stellaris_sys = {
+ .name = "stellaris_sys",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .post_load = stellaris_sys_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(pborctl, ssys_state),
+ VMSTATE_UINT32(ldopctl, ssys_state),
+ VMSTATE_UINT32(int_mask, ssys_state),
+ VMSTATE_UINT32(int_status, ssys_state),
+ VMSTATE_UINT32(resc, ssys_state),
+ VMSTATE_UINT32(rcc, ssys_state),
+ VMSTATE_UINT32_V(rcc2, ssys_state, 2),
+ VMSTATE_UINT32_ARRAY(rcgc, ssys_state, 3),
+ VMSTATE_UINT32_ARRAY(scgc, ssys_state, 3),
+ VMSTATE_UINT32_ARRAY(dcgc, ssys_state, 3),
+ VMSTATE_UINT32(clkvclr, ssys_state),
+ VMSTATE_UINT32(ldoarst, ssys_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int stellaris_sys_init(uint32_t base, qemu_irq irq,
+ stellaris_board_info * board,
+ uint8_t *macaddr)
+{
+ ssys_state *s;
+
+ s = (ssys_state *)g_malloc0(sizeof(ssys_state));
+ s->irq = irq;
+ s->board = board;
+ /* Most devices come preprogrammed with a MAC address in the user data. */
+ s->user0 = macaddr[0] | (macaddr[1] << 8) | (macaddr[2] << 16);
+ s->user1 = macaddr[3] | (macaddr[4] << 8) | (macaddr[5] << 16);
+
+ memory_region_init_io(&s->iomem, NULL, &ssys_ops, s, "ssys", 0x00001000);
+ memory_region_add_subregion(get_system_memory(), base, &s->iomem);
+ ssys_reset(s);
+ vmstate_register(NULL, -1, &vmstate_stellaris_sys, s);
+ return 0;
+}
+
+
+/* I2C controller. */
+
+#define TYPE_STELLARIS_I2C "stellaris-i2c"
+#define STELLARIS_I2C(obj) \
+ OBJECT_CHECK(stellaris_i2c_state, (obj), TYPE_STELLARIS_I2C)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ I2CBus *bus;
+ qemu_irq irq;
+ MemoryRegion iomem;
+ uint32_t msa;
+ uint32_t mcs;
+ uint32_t mdr;
+ uint32_t mtpr;
+ uint32_t mimr;
+ uint32_t mris;
+ uint32_t mcr;
+} stellaris_i2c_state;
+
+#define STELLARIS_I2C_MCS_BUSY 0x01
+#define STELLARIS_I2C_MCS_ERROR 0x02
+#define STELLARIS_I2C_MCS_ADRACK 0x04
+#define STELLARIS_I2C_MCS_DATACK 0x08
+#define STELLARIS_I2C_MCS_ARBLST 0x10
+#define STELLARIS_I2C_MCS_IDLE 0x20
+#define STELLARIS_I2C_MCS_BUSBSY 0x40
+
+static uint64_t stellaris_i2c_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ stellaris_i2c_state *s = (stellaris_i2c_state *)opaque;
+
+ switch (offset) {
+ case 0x00: /* MSA */
+ return s->msa;
+ case 0x04: /* MCS */
+ /* We don't emulate timing, so the controller is never busy. */
+ return s->mcs | STELLARIS_I2C_MCS_IDLE;
+ case 0x08: /* MDR */
+ return s->mdr;
+ case 0x0c: /* MTPR */
+ return s->mtpr;
+ case 0x10: /* MIMR */
+ return s->mimr;
+ case 0x14: /* MRIS */
+ return s->mris;
+ case 0x18: /* MMIS */
+ return s->mris & s->mimr;
+ case 0x20: /* MCR */
+ return s->mcr;
+ default:
+ hw_error("strllaris_i2c_read: Bad offset 0x%x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void stellaris_i2c_update(stellaris_i2c_state *s)
+{
+ int level;
+
+ level = (s->mris & s->mimr) != 0;
+ qemu_set_irq(s->irq, level);
+}
+
+static void stellaris_i2c_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ stellaris_i2c_state *s = (stellaris_i2c_state *)opaque;
+
+ switch (offset) {
+ case 0x00: /* MSA */
+ s->msa = value & 0xff;
+ break;
+ case 0x04: /* MCS */
+ if ((s->mcr & 0x10) == 0) {
+ /* Disabled. Do nothing. */
+ break;
+ }
+ /* Grab the bus if this is starting a transfer. */
+ if ((value & 2) && (s->mcs & STELLARIS_I2C_MCS_BUSBSY) == 0) {
+ if (i2c_start_transfer(s->bus, s->msa >> 1, s->msa & 1)) {
+ s->mcs |= STELLARIS_I2C_MCS_ARBLST;
+ } else {
+ s->mcs &= ~STELLARIS_I2C_MCS_ARBLST;
+ s->mcs |= STELLARIS_I2C_MCS_BUSBSY;
+ }
+ }
+ /* If we don't have the bus then indicate an error. */
+ if (!i2c_bus_busy(s->bus)
+ || (s->mcs & STELLARIS_I2C_MCS_BUSBSY) == 0) {
+ s->mcs |= STELLARIS_I2C_MCS_ERROR;
+ break;
+ }
+ s->mcs &= ~STELLARIS_I2C_MCS_ERROR;
+ if (value & 1) {
+ /* Transfer a byte. */
+ /* TODO: Handle errors. */
+ if (s->msa & 1) {
+ /* Recv */
+ s->mdr = i2c_recv(s->bus) & 0xff;
+ } else {
+ /* Send */
+ i2c_send(s->bus, s->mdr);
+ }
+ /* Raise an interrupt. */
+ s->mris |= 1;
+ }
+ if (value & 4) {
+ /* Finish transfer. */
+ i2c_end_transfer(s->bus);
+ s->mcs &= ~STELLARIS_I2C_MCS_BUSBSY;
+ }
+ break;
+ case 0x08: /* MDR */
+ s->mdr = value & 0xff;
+ break;
+ case 0x0c: /* MTPR */
+ s->mtpr = value & 0xff;
+ break;
+ case 0x10: /* MIMR */
+ s->mimr = 1;
+ break;
+ case 0x1c: /* MICR */
+ s->mris &= ~value;
+ break;
+ case 0x20: /* MCR */
+ if (value & 1)
+ hw_error(
+ "stellaris_i2c_write: Loopback not implemented\n");
+ if (value & 0x20)
+ hw_error(
+ "stellaris_i2c_write: Slave mode not implemented\n");
+ s->mcr = value & 0x31;
+ break;
+ default:
+ hw_error("stellaris_i2c_write: Bad offset 0x%x\n",
+ (int)offset);
+ }
+ stellaris_i2c_update(s);
+}
+
+static void stellaris_i2c_reset(stellaris_i2c_state *s)
+{
+ if (s->mcs & STELLARIS_I2C_MCS_BUSBSY)
+ i2c_end_transfer(s->bus);
+
+ s->msa = 0;
+ s->mcs = 0;
+ s->mdr = 0;
+ s->mtpr = 1;
+ s->mimr = 0;
+ s->mris = 0;
+ s->mcr = 0;
+ stellaris_i2c_update(s);
+}
+
+static const MemoryRegionOps stellaris_i2c_ops = {
+ .read = stellaris_i2c_read,
+ .write = stellaris_i2c_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_stellaris_i2c = {
+ .name = "stellaris_i2c",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(msa, stellaris_i2c_state),
+ VMSTATE_UINT32(mcs, stellaris_i2c_state),
+ VMSTATE_UINT32(mdr, stellaris_i2c_state),
+ VMSTATE_UINT32(mtpr, stellaris_i2c_state),
+ VMSTATE_UINT32(mimr, stellaris_i2c_state),
+ VMSTATE_UINT32(mris, stellaris_i2c_state),
+ VMSTATE_UINT32(mcr, stellaris_i2c_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int stellaris_i2c_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ stellaris_i2c_state *s = STELLARIS_I2C(dev);
+ I2CBus *bus;
+
+ sysbus_init_irq(sbd, &s->irq);
+ bus = i2c_init_bus(dev, "i2c");
+ s->bus = bus;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &stellaris_i2c_ops, s,
+ "i2c", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ /* ??? For now we only implement the master interface. */
+ stellaris_i2c_reset(s);
+ vmstate_register(dev, -1, &vmstate_stellaris_i2c, s);
+ return 0;
+}
+
+/* Analogue to Digital Converter. This is only partially implemented,
+ enough for applications that use a combined ADC and timer tick. */
+
+#define STELLARIS_ADC_EM_CONTROLLER 0
+#define STELLARIS_ADC_EM_COMP 1
+#define STELLARIS_ADC_EM_EXTERNAL 4
+#define STELLARIS_ADC_EM_TIMER 5
+#define STELLARIS_ADC_EM_PWM0 6
+#define STELLARIS_ADC_EM_PWM1 7
+#define STELLARIS_ADC_EM_PWM2 8
+
+#define STELLARIS_ADC_FIFO_EMPTY 0x0100
+#define STELLARIS_ADC_FIFO_FULL 0x1000
+
+#define TYPE_STELLARIS_ADC "stellaris-adc"
+#define STELLARIS_ADC(obj) \
+ OBJECT_CHECK(stellaris_adc_state, (obj), TYPE_STELLARIS_ADC)
+
+typedef struct StellarisADCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t actss;
+ uint32_t ris;
+ uint32_t im;
+ uint32_t emux;
+ uint32_t ostat;
+ uint32_t ustat;
+ uint32_t sspri;
+ uint32_t sac;
+ struct {
+ uint32_t state;
+ uint32_t data[16];
+ } fifo[4];
+ uint32_t ssmux[4];
+ uint32_t ssctl[4];
+ uint32_t noise;
+ qemu_irq irq[4];
+} stellaris_adc_state;
+
+static uint32_t stellaris_adc_fifo_read(stellaris_adc_state *s, int n)
+{
+ int tail;
+
+ tail = s->fifo[n].state & 0xf;
+ if (s->fifo[n].state & STELLARIS_ADC_FIFO_EMPTY) {
+ s->ustat |= 1 << n;
+ } else {
+ s->fifo[n].state = (s->fifo[n].state & ~0xf) | ((tail + 1) & 0xf);
+ s->fifo[n].state &= ~STELLARIS_ADC_FIFO_FULL;
+ if (tail + 1 == ((s->fifo[n].state >> 4) & 0xf))
+ s->fifo[n].state |= STELLARIS_ADC_FIFO_EMPTY;
+ }
+ return s->fifo[n].data[tail];
+}
+
+static void stellaris_adc_fifo_write(stellaris_adc_state *s, int n,
+ uint32_t value)
+{
+ int head;
+
+ /* TODO: Real hardware has limited size FIFOs. We have a full 16 entry
+ FIFO fir each sequencer. */
+ head = (s->fifo[n].state >> 4) & 0xf;
+ if (s->fifo[n].state & STELLARIS_ADC_FIFO_FULL) {
+ s->ostat |= 1 << n;
+ return;
+ }
+ s->fifo[n].data[head] = value;
+ head = (head + 1) & 0xf;
+ s->fifo[n].state &= ~STELLARIS_ADC_FIFO_EMPTY;
+ s->fifo[n].state = (s->fifo[n].state & ~0xf0) | (head << 4);
+ if ((s->fifo[n].state & 0xf) == head)
+ s->fifo[n].state |= STELLARIS_ADC_FIFO_FULL;
+}
+
+static void stellaris_adc_update(stellaris_adc_state *s)
+{
+ int level;
+ int n;
+
+ for (n = 0; n < 4; n++) {
+ level = (s->ris & s->im & (1 << n)) != 0;
+ qemu_set_irq(s->irq[n], level);
+ }
+}
+
+static void stellaris_adc_trigger(void *opaque, int irq, int level)
+{
+ stellaris_adc_state *s = (stellaris_adc_state *)opaque;
+ int n;
+
+ for (n = 0; n < 4; n++) {
+ if ((s->actss & (1 << n)) == 0) {
+ continue;
+ }
+
+ if (((s->emux >> (n * 4)) & 0xff) != 5) {
+ continue;
+ }
+
+ /* Some applications use the ADC as a random number source, so introduce
+ some variation into the signal. */
+ s->noise = s->noise * 314159 + 1;
+ /* ??? actual inputs not implemented. Return an arbitrary value. */
+ stellaris_adc_fifo_write(s, n, 0x200 + ((s->noise >> 16) & 7));
+ s->ris |= (1 << n);
+ stellaris_adc_update(s);
+ }
+}
+
+static void stellaris_adc_reset(stellaris_adc_state *s)
+{
+ int n;
+
+ for (n = 0; n < 4; n++) {
+ s->ssmux[n] = 0;
+ s->ssctl[n] = 0;
+ s->fifo[n].state = STELLARIS_ADC_FIFO_EMPTY;
+ }
+}
+
+static uint64_t stellaris_adc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ stellaris_adc_state *s = (stellaris_adc_state *)opaque;
+
+ /* TODO: Implement this. */
+ if (offset >= 0x40 && offset < 0xc0) {
+ int n;
+ n = (offset - 0x40) >> 5;
+ switch (offset & 0x1f) {
+ case 0x00: /* SSMUX */
+ return s->ssmux[n];
+ case 0x04: /* SSCTL */
+ return s->ssctl[n];
+ case 0x08: /* SSFIFO */
+ return stellaris_adc_fifo_read(s, n);
+ case 0x0c: /* SSFSTAT */
+ return s->fifo[n].state;
+ default:
+ break;
+ }
+ }
+ switch (offset) {
+ case 0x00: /* ACTSS */
+ return s->actss;
+ case 0x04: /* RIS */
+ return s->ris;
+ case 0x08: /* IM */
+ return s->im;
+ case 0x0c: /* ISC */
+ return s->ris & s->im;
+ case 0x10: /* OSTAT */
+ return s->ostat;
+ case 0x14: /* EMUX */
+ return s->emux;
+ case 0x18: /* USTAT */
+ return s->ustat;
+ case 0x20: /* SSPRI */
+ return s->sspri;
+ case 0x30: /* SAC */
+ return s->sac;
+ default:
+ hw_error("strllaris_adc_read: Bad offset 0x%x\n",
+ (int)offset);
+ return 0;
+ }
+}
+
+static void stellaris_adc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ stellaris_adc_state *s = (stellaris_adc_state *)opaque;
+
+ /* TODO: Implement this. */
+ if (offset >= 0x40 && offset < 0xc0) {
+ int n;
+ n = (offset - 0x40) >> 5;
+ switch (offset & 0x1f) {
+ case 0x00: /* SSMUX */
+ s->ssmux[n] = value & 0x33333333;
+ return;
+ case 0x04: /* SSCTL */
+ if (value != 6) {
+ hw_error("ADC: Unimplemented sequence %" PRIx64 "\n",
+ value);
+ }
+ s->ssctl[n] = value;
+ return;
+ default:
+ break;
+ }
+ }
+ switch (offset) {
+ case 0x00: /* ACTSS */
+ s->actss = value & 0xf;
+ break;
+ case 0x08: /* IM */
+ s->im = value;
+ break;
+ case 0x0c: /* ISC */
+ s->ris &= ~value;
+ break;
+ case 0x10: /* OSTAT */
+ s->ostat &= ~value;
+ break;
+ case 0x14: /* EMUX */
+ s->emux = value;
+ break;
+ case 0x18: /* USTAT */
+ s->ustat &= ~value;
+ break;
+ case 0x20: /* SSPRI */
+ s->sspri = value;
+ break;
+ case 0x28: /* PSSI */
+ hw_error("Not implemented: ADC sample initiate\n");
+ break;
+ case 0x30: /* SAC */
+ s->sac = value;
+ break;
+ default:
+ hw_error("stellaris_adc_write: Bad offset 0x%x\n", (int)offset);
+ }
+ stellaris_adc_update(s);
+}
+
+static const MemoryRegionOps stellaris_adc_ops = {
+ .read = stellaris_adc_read,
+ .write = stellaris_adc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_stellaris_adc = {
+ .name = "stellaris_adc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(actss, stellaris_adc_state),
+ VMSTATE_UINT32(ris, stellaris_adc_state),
+ VMSTATE_UINT32(im, stellaris_adc_state),
+ VMSTATE_UINT32(emux, stellaris_adc_state),
+ VMSTATE_UINT32(ostat, stellaris_adc_state),
+ VMSTATE_UINT32(ustat, stellaris_adc_state),
+ VMSTATE_UINT32(sspri, stellaris_adc_state),
+ VMSTATE_UINT32(sac, stellaris_adc_state),
+ VMSTATE_UINT32(fifo[0].state, stellaris_adc_state),
+ VMSTATE_UINT32_ARRAY(fifo[0].data, stellaris_adc_state, 16),
+ VMSTATE_UINT32(ssmux[0], stellaris_adc_state),
+ VMSTATE_UINT32(ssctl[0], stellaris_adc_state),
+ VMSTATE_UINT32(fifo[1].state, stellaris_adc_state),
+ VMSTATE_UINT32_ARRAY(fifo[1].data, stellaris_adc_state, 16),
+ VMSTATE_UINT32(ssmux[1], stellaris_adc_state),
+ VMSTATE_UINT32(ssctl[1], stellaris_adc_state),
+ VMSTATE_UINT32(fifo[2].state, stellaris_adc_state),
+ VMSTATE_UINT32_ARRAY(fifo[2].data, stellaris_adc_state, 16),
+ VMSTATE_UINT32(ssmux[2], stellaris_adc_state),
+ VMSTATE_UINT32(ssctl[2], stellaris_adc_state),
+ VMSTATE_UINT32(fifo[3].state, stellaris_adc_state),
+ VMSTATE_UINT32_ARRAY(fifo[3].data, stellaris_adc_state, 16),
+ VMSTATE_UINT32(ssmux[3], stellaris_adc_state),
+ VMSTATE_UINT32(ssctl[3], stellaris_adc_state),
+ VMSTATE_UINT32(noise, stellaris_adc_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int stellaris_adc_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ stellaris_adc_state *s = STELLARIS_ADC(dev);
+ int n;
+
+ for (n = 0; n < 4; n++) {
+ sysbus_init_irq(sbd, &s->irq[n]);
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &stellaris_adc_ops, s,
+ "adc", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ stellaris_adc_reset(s);
+ qdev_init_gpio_in(dev, stellaris_adc_trigger, 1);
+ vmstate_register(dev, -1, &vmstate_stellaris_adc, s);
+ return 0;
+}
+
+/* Board init. */
+static stellaris_board_info stellaris_boards[] = {
+ { "LM3S811EVB",
+ 0,
+ 0x0032000e,
+ 0x001f001f, /* dc0 */
+ 0x001132bf,
+ 0x01071013,
+ 0x3f0f01ff,
+ 0x0000001f,
+ BP_OLED_I2C
+ },
+ { "LM3S6965EVB",
+ 0x10010002,
+ 0x1073402e,
+ 0x00ff007f, /* dc0 */
+ 0x001133ff,
+ 0x030f5317,
+ 0x0f0f87ff,
+ 0x5000007f,
+ BP_OLED_SSI | BP_GAMEPAD
+ }
+};
+
+static void stellaris_init(const char *kernel_filename, const char *cpu_model,
+ stellaris_board_info *board)
+{
+ static const int uart_irq[] = {5, 6, 33, 34};
+ static const int timer_irq[] = {19, 21, 23, 35};
+ static const uint32_t gpio_addr[7] =
+ { 0x40004000, 0x40005000, 0x40006000, 0x40007000,
+ 0x40024000, 0x40025000, 0x40026000};
+ static const int gpio_irq[7] = {0, 1, 2, 3, 4, 30, 31};
+
+ qemu_irq *pic;
+ DeviceState *gpio_dev[7];
+ qemu_irq gpio_in[7][8];
+ qemu_irq gpio_out[7][8];
+ qemu_irq adc;
+ int sram_size;
+ int flash_size;
+ I2CBus *i2c;
+ DeviceState *dev;
+ int i;
+ int j;
+
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+ MemoryRegion *flash = g_new(MemoryRegion, 1);
+ MemoryRegion *system_memory = get_system_memory();
+
+ flash_size = (((board->dc0 & 0xffff) + 1) << 1) * 1024;
+ sram_size = ((board->dc0 >> 18) + 1) * 1024;
+
+ /* Flash programming is done via the SCU, so pretend it is ROM. */
+ memory_region_init_ram(flash, NULL, "stellaris.flash", flash_size,
+ &error_abort);
+ vmstate_register_ram_global(flash);
+ memory_region_set_readonly(flash, true);
+ memory_region_add_subregion(system_memory, 0, flash);
+
+ memory_region_init_ram(sram, NULL, "stellaris.sram", sram_size,
+ &error_abort);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(system_memory, 0x20000000, sram);
+
+ pic = armv7m_init(system_memory, flash_size, NUM_IRQ_LINES,
+ kernel_filename, cpu_model);
+
+ if (board->dc1 & (1 << 16)) {
+ dev = sysbus_create_varargs(TYPE_STELLARIS_ADC, 0x40038000,
+ pic[14], pic[15], pic[16], pic[17], NULL);
+ adc = qdev_get_gpio_in(dev, 0);
+ } else {
+ adc = NULL;
+ }
+ for (i = 0; i < 4; i++) {
+ if (board->dc2 & (0x10000 << i)) {
+ dev = sysbus_create_simple(TYPE_STELLARIS_GPTM,
+ 0x40030000 + i * 0x1000,
+ pic[timer_irq[i]]);
+ /* TODO: This is incorrect, but we get away with it because
+ the ADC output is only ever pulsed. */
+ qdev_connect_gpio_out(dev, 0, adc);
+ }
+ }
+
+ stellaris_sys_init(0x400fe000, pic[28], board, nd_table[0].macaddr.a);
+
+ for (i = 0; i < 7; i++) {
+ if (board->dc4 & (1 << i)) {
+ gpio_dev[i] = sysbus_create_simple("pl061_luminary", gpio_addr[i],
+ pic[gpio_irq[i]]);
+ for (j = 0; j < 8; j++) {
+ gpio_in[i][j] = qdev_get_gpio_in(gpio_dev[i], j);
+ gpio_out[i][j] = NULL;
+ }
+ }
+ }
+
+ if (board->dc2 & (1 << 12)) {
+ dev = sysbus_create_simple(TYPE_STELLARIS_I2C, 0x40020000, pic[8]);
+ i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
+ if (board->peripherals & BP_OLED_I2C) {
+ i2c_create_slave(i2c, "ssd0303", 0x3d);
+ }
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (board->dc2 & (1 << i)) {
+ sysbus_create_simple("pl011_luminary", 0x4000c000 + i * 0x1000,
+ pic[uart_irq[i]]);
+ }
+ }
+ if (board->dc2 & (1 << 4)) {
+ dev = sysbus_create_simple("pl022", 0x40008000, pic[7]);
+ if (board->peripherals & BP_OLED_SSI) {
+ void *bus;
+ DeviceState *sddev;
+ DeviceState *ssddev;
+
+ /* Some boards have both an OLED controller and SD card connected to
+ * the same SSI port, with the SD card chip select connected to a
+ * GPIO pin. Technically the OLED chip select is connected to the
+ * SSI Fss pin. We do not bother emulating that as both devices
+ * should never be selected simultaneously, and our OLED controller
+ * ignores stray 0xff commands that occur when deselecting the SD
+ * card.
+ */
+ bus = qdev_get_child_bus(dev, "ssi");
+
+ sddev = ssi_create_slave(bus, "ssi-sd");
+ ssddev = ssi_create_slave(bus, "ssd0323");
+ gpio_out[GPIO_D][0] = qemu_irq_split(
+ qdev_get_gpio_in_named(sddev, SSI_GPIO_CS, 0),
+ qdev_get_gpio_in_named(ssddev, SSI_GPIO_CS, 0));
+ gpio_out[GPIO_C][7] = qdev_get_gpio_in(ssddev, 0);
+
+ /* Make sure the select pin is high. */
+ qemu_irq_raise(gpio_out[GPIO_D][0]);
+ }
+ }
+ if (board->dc4 & (1 << 28)) {
+ DeviceState *enet;
+
+ qemu_check_nic_model(&nd_table[0], "stellaris");
+
+ enet = qdev_create(NULL, "stellaris_enet");
+ qdev_set_nic_properties(enet, &nd_table[0]);
+ qdev_init_nofail(enet);
+ sysbus_mmio_map(SYS_BUS_DEVICE(enet), 0, 0x40048000);
+ sysbus_connect_irq(SYS_BUS_DEVICE(enet), 0, pic[42]);
+ }
+ if (board->peripherals & BP_GAMEPAD) {
+ qemu_irq gpad_irq[5];
+ static const int gpad_keycode[5] = { 0xc8, 0xd0, 0xcb, 0xcd, 0x1d };
+
+ gpad_irq[0] = qemu_irq_invert(gpio_in[GPIO_E][0]); /* up */
+ gpad_irq[1] = qemu_irq_invert(gpio_in[GPIO_E][1]); /* down */
+ gpad_irq[2] = qemu_irq_invert(gpio_in[GPIO_E][2]); /* left */
+ gpad_irq[3] = qemu_irq_invert(gpio_in[GPIO_E][3]); /* right */
+ gpad_irq[4] = qemu_irq_invert(gpio_in[GPIO_F][1]); /* select */
+
+ stellaris_gamepad_init(5, gpad_irq, gpad_keycode);
+ }
+ for (i = 0; i < 7; i++) {
+ if (board->dc4 & (1 << i)) {
+ for (j = 0; j < 8; j++) {
+ if (gpio_out[i][j]) {
+ qdev_connect_gpio_out(gpio_dev[i], j, gpio_out[i][j]);
+ }
+ }
+ }
+ }
+}
+
+/* FIXME: Figure out how to generate these from stellaris_boards. */
+static void lm3s811evb_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ stellaris_init(kernel_filename, cpu_model, &stellaris_boards[0]);
+}
+
+static void lm3s6965evb_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ stellaris_init(kernel_filename, cpu_model, &stellaris_boards[1]);
+}
+
+static QEMUMachine lm3s811evb_machine = {
+ .name = "lm3s811evb",
+ .desc = "Stellaris LM3S811EVB",
+ .init = lm3s811evb_init,
+};
+
+static QEMUMachine lm3s6965evb_machine = {
+ .name = "lm3s6965evb",
+ .desc = "Stellaris LM3S6965EVB",
+ .init = lm3s6965evb_init,
+};
+
+static void stellaris_machine_init(void)
+{
+ qemu_register_machine(&lm3s811evb_machine);
+ qemu_register_machine(&lm3s6965evb_machine);
+}
+
+machine_init(stellaris_machine_init);
+
+static void stellaris_i2c_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = stellaris_i2c_init;
+}
+
+static const TypeInfo stellaris_i2c_info = {
+ .name = TYPE_STELLARIS_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(stellaris_i2c_state),
+ .class_init = stellaris_i2c_class_init,
+};
+
+static void stellaris_gptm_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = stellaris_gptm_init;
+}
+
+static const TypeInfo stellaris_gptm_info = {
+ .name = TYPE_STELLARIS_GPTM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(gptm_state),
+ .class_init = stellaris_gptm_class_init,
+};
+
+static void stellaris_adc_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = stellaris_adc_init;
+}
+
+static const TypeInfo stellaris_adc_info = {
+ .name = TYPE_STELLARIS_ADC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(stellaris_adc_state),
+ .class_init = stellaris_adc_class_init,
+};
+
+static void stellaris_register_types(void)
+{
+ type_register_static(&stellaris_i2c_info);
+ type_register_static(&stellaris_gptm_info);
+ type_register_static(&stellaris_adc_info);
+}
+
+type_init(stellaris_register_types)
diff --git a/hw/arm/stm32f205_soc.c b/hw/arm/stm32f205_soc.c
new file mode 100644
index 00000000..0f3bdc77
--- /dev/null
+++ b/hw/arm/stm32f205_soc.c
@@ -0,0 +1,160 @@
+/*
+ * STM32F205 SoC
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * 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.
+ */
+
+#include "hw/arm/arm.h"
+#include "exec/address-spaces.h"
+#include "hw/arm/stm32f205_soc.h"
+
+/* At the moment only Timer 2 to 5 are modelled */
+static const uint32_t timer_addr[STM_NUM_TIMERS] = { 0x40000000, 0x40000400,
+ 0x40000800, 0x40000C00 };
+static const uint32_t usart_addr[STM_NUM_USARTS] = { 0x40011000, 0x40004400,
+ 0x40004800, 0x40004C00, 0x40005000, 0x40011400 };
+
+static const int timer_irq[STM_NUM_TIMERS] = {28, 29, 30, 50};
+static const int usart_irq[STM_NUM_USARTS] = {37, 38, 39, 52, 53, 71};
+
+static void stm32f205_soc_initfn(Object *obj)
+{
+ STM32F205State *s = STM32F205_SOC(obj);
+ int i;
+
+ object_initialize(&s->syscfg, sizeof(s->syscfg), TYPE_STM32F2XX_SYSCFG);
+ qdev_set_parent_bus(DEVICE(&s->syscfg), sysbus_get_default());
+
+ for (i = 0; i < STM_NUM_USARTS; i++) {
+ object_initialize(&s->usart[i], sizeof(s->usart[i]),
+ TYPE_STM32F2XX_USART);
+ qdev_set_parent_bus(DEVICE(&s->usart[i]), sysbus_get_default());
+ }
+
+ for (i = 0; i < STM_NUM_TIMERS; i++) {
+ object_initialize(&s->timer[i], sizeof(s->timer[i]),
+ TYPE_STM32F2XX_TIMER);
+ qdev_set_parent_bus(DEVICE(&s->timer[i]), sysbus_get_default());
+ }
+}
+
+static void stm32f205_soc_realize(DeviceState *dev_soc, Error **errp)
+{
+ STM32F205State *s = STM32F205_SOC(dev_soc);
+ DeviceState *syscfgdev, *usartdev, *timerdev;
+ SysBusDevice *syscfgbusdev, *usartbusdev, *timerbusdev;
+ qemu_irq *pic;
+ Error *err = NULL;
+ int i;
+
+ MemoryRegion *system_memory = get_system_memory();
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+ MemoryRegion *flash = g_new(MemoryRegion, 1);
+ MemoryRegion *flash_alias = g_new(MemoryRegion, 1);
+
+ memory_region_init_ram(flash, NULL, "STM32F205.flash", FLASH_SIZE,
+ &error_abort);
+ memory_region_init_alias(flash_alias, NULL, "STM32F205.flash.alias",
+ flash, 0, FLASH_SIZE);
+
+ vmstate_register_ram_global(flash);
+
+ memory_region_set_readonly(flash, true);
+ memory_region_set_readonly(flash_alias, true);
+
+ memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, flash);
+ memory_region_add_subregion(system_memory, 0, flash_alias);
+
+ memory_region_init_ram(sram, NULL, "STM32F205.sram", SRAM_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(system_memory, SRAM_BASE_ADDRESS, sram);
+
+ pic = armv7m_init(get_system_memory(), FLASH_SIZE, 96,
+ s->kernel_filename, s->cpu_model);
+
+ /* System configuration controller */
+ syscfgdev = DEVICE(&s->syscfg);
+ object_property_set_bool(OBJECT(&s->syscfg), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ syscfgbusdev = SYS_BUS_DEVICE(syscfgdev);
+ sysbus_mmio_map(syscfgbusdev, 0, 0x40013800);
+ sysbus_connect_irq(syscfgbusdev, 0, pic[71]);
+
+ /* Attach UART (uses USART registers) and USART controllers */
+ for (i = 0; i < STM_NUM_USARTS; i++) {
+ usartdev = DEVICE(&(s->usart[i]));
+ object_property_set_bool(OBJECT(&s->usart[i]), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ usartbusdev = SYS_BUS_DEVICE(usartdev);
+ sysbus_mmio_map(usartbusdev, 0, usart_addr[i]);
+ sysbus_connect_irq(usartbusdev, 0, pic[usart_irq[i]]);
+ }
+
+ /* Timer 2 to 5 */
+ for (i = 0; i < STM_NUM_TIMERS; i++) {
+ timerdev = DEVICE(&(s->timer[i]));
+ qdev_prop_set_uint64(timerdev, "clock-frequency", 1000000000);
+ object_property_set_bool(OBJECT(&s->timer[i]), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ timerbusdev = SYS_BUS_DEVICE(timerdev);
+ sysbus_mmio_map(timerbusdev, 0, timer_addr[i]);
+ sysbus_connect_irq(timerbusdev, 0, pic[timer_irq[i]]);
+ }
+}
+
+static Property stm32f205_soc_properties[] = {
+ DEFINE_PROP_STRING("kernel-filename", STM32F205State, kernel_filename),
+ DEFINE_PROP_STRING("cpu-model", STM32F205State, cpu_model),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void stm32f205_soc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = stm32f205_soc_realize;
+ dc->props = stm32f205_soc_properties;
+}
+
+static const TypeInfo stm32f205_soc_info = {
+ .name = TYPE_STM32F205_SOC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F205State),
+ .instance_init = stm32f205_soc_initfn,
+ .class_init = stm32f205_soc_class_init,
+};
+
+static void stm32f205_soc_types(void)
+{
+ type_register_static(&stm32f205_soc_info);
+}
+
+type_init(stm32f205_soc_types)
diff --git a/hw/arm/strongarm.c b/hw/arm/strongarm.c
new file mode 100644
index 00000000..da9fc1d5
--- /dev/null
+++ b/hw/arm/strongarm.c
@@ -0,0 +1,1659 @@
+/*
+ * StrongARM SA-1100/SA-1110 emulation
+ *
+ * Copyright (C) 2011 Dmitry Eremin-Solenikov
+ *
+ * Largely based on StrongARM emulation:
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * UART code based on QEMU 16550A UART emulation
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/boards.h"
+#include "hw/sysbus.h"
+#include "strongarm.h"
+#include "qemu/error-report.h"
+#include "hw/arm/arm.h"
+#include "sysemu/char.h"
+#include "sysemu/sysemu.h"
+#include "hw/ssi.h"
+
+//#define DEBUG
+
+/*
+ TODO
+ - Implement cp15, c14 ?
+ - Implement cp15, c15 !!! (idle used in L)
+ - Implement idle mode handling/DIM
+ - Implement sleep mode/Wake sources
+ - Implement reset control
+ - Implement memory control regs
+ - PCMCIA handling
+ - Maybe support MBGNT/MBREQ
+ - DMA channels
+ - GPCLK
+ - IrDA
+ - MCP
+ - Enhance UART with modem signals
+ */
+
+#ifdef DEBUG
+# define DPRINTF(format, ...) printf(format , ## __VA_ARGS__)
+#else
+# define DPRINTF(format, ...) do { } while (0)
+#endif
+
+static struct {
+ hwaddr io_base;
+ int irq;
+} sa_serial[] = {
+ { 0x80010000, SA_PIC_UART1 },
+ { 0x80030000, SA_PIC_UART2 },
+ { 0x80050000, SA_PIC_UART3 },
+ { 0, 0 }
+};
+
+/* Interrupt Controller */
+
+#define TYPE_STRONGARM_PIC "strongarm_pic"
+#define STRONGARM_PIC(obj) \
+ OBJECT_CHECK(StrongARMPICState, (obj), TYPE_STRONGARM_PIC)
+
+typedef struct StrongARMPICState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq fiq;
+
+ uint32_t pending;
+ uint32_t enabled;
+ uint32_t is_fiq;
+ uint32_t int_idle;
+} StrongARMPICState;
+
+#define ICIP 0x00
+#define ICMR 0x04
+#define ICLR 0x08
+#define ICFP 0x10
+#define ICPR 0x20
+#define ICCR 0x0c
+
+#define SA_PIC_SRCS 32
+
+
+static void strongarm_pic_update(void *opaque)
+{
+ StrongARMPICState *s = opaque;
+
+ /* FIXME: reflect DIM */
+ qemu_set_irq(s->fiq, s->pending & s->enabled & s->is_fiq);
+ qemu_set_irq(s->irq, s->pending & s->enabled & ~s->is_fiq);
+}
+
+static void strongarm_pic_set_irq(void *opaque, int irq, int level)
+{
+ StrongARMPICState *s = opaque;
+
+ if (level) {
+ s->pending |= 1 << irq;
+ } else {
+ s->pending &= ~(1 << irq);
+ }
+
+ strongarm_pic_update(s);
+}
+
+static uint64_t strongarm_pic_mem_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ StrongARMPICState *s = opaque;
+
+ switch (offset) {
+ case ICIP:
+ return s->pending & ~s->is_fiq & s->enabled;
+ case ICMR:
+ return s->enabled;
+ case ICLR:
+ return s->is_fiq;
+ case ICCR:
+ return s->int_idle == 0;
+ case ICFP:
+ return s->pending & s->is_fiq & s->enabled;
+ case ICPR:
+ return s->pending;
+ default:
+ printf("%s: Bad register offset 0x" TARGET_FMT_plx "\n",
+ __func__, offset);
+ return 0;
+ }
+}
+
+static void strongarm_pic_mem_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ StrongARMPICState *s = opaque;
+
+ switch (offset) {
+ case ICMR:
+ s->enabled = value;
+ break;
+ case ICLR:
+ s->is_fiq = value;
+ break;
+ case ICCR:
+ s->int_idle = (value & 1) ? 0 : ~0;
+ break;
+ default:
+ printf("%s: Bad register offset 0x" TARGET_FMT_plx "\n",
+ __func__, offset);
+ break;
+ }
+ strongarm_pic_update(s);
+}
+
+static const MemoryRegionOps strongarm_pic_ops = {
+ .read = strongarm_pic_mem_read,
+ .write = strongarm_pic_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int strongarm_pic_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ StrongARMPICState *s = STRONGARM_PIC(dev);
+
+ qdev_init_gpio_in(dev, strongarm_pic_set_irq, SA_PIC_SRCS);
+ memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_pic_ops, s,
+ "pic", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->fiq);
+
+ return 0;
+}
+
+static int strongarm_pic_post_load(void *opaque, int version_id)
+{
+ strongarm_pic_update(opaque);
+ return 0;
+}
+
+static VMStateDescription vmstate_strongarm_pic_regs = {
+ .name = "strongarm_pic",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = strongarm_pic_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(pending, StrongARMPICState),
+ VMSTATE_UINT32(enabled, StrongARMPICState),
+ VMSTATE_UINT32(is_fiq, StrongARMPICState),
+ VMSTATE_UINT32(int_idle, StrongARMPICState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void strongarm_pic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = strongarm_pic_initfn;
+ dc->desc = "StrongARM PIC";
+ dc->vmsd = &vmstate_strongarm_pic_regs;
+}
+
+static const TypeInfo strongarm_pic_info = {
+ .name = TYPE_STRONGARM_PIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(StrongARMPICState),
+ .class_init = strongarm_pic_class_init,
+};
+
+/* Real-Time Clock */
+#define RTAR 0x00 /* RTC Alarm register */
+#define RCNR 0x04 /* RTC Counter register */
+#define RTTR 0x08 /* RTC Timer Trim register */
+#define RTSR 0x10 /* RTC Status register */
+
+#define RTSR_AL (1 << 0) /* RTC Alarm detected */
+#define RTSR_HZ (1 << 1) /* RTC 1Hz detected */
+#define RTSR_ALE (1 << 2) /* RTC Alarm enable */
+#define RTSR_HZE (1 << 3) /* RTC 1Hz enable */
+
+/* 16 LSB of RTTR are clockdiv for internal trim logic,
+ * trim delete isn't emulated, so
+ * f = 32 768 / (RTTR_trim + 1) */
+
+#define TYPE_STRONGARM_RTC "strongarm-rtc"
+#define STRONGARM_RTC(obj) \
+ OBJECT_CHECK(StrongARMRTCState, (obj), TYPE_STRONGARM_RTC)
+
+typedef struct StrongARMRTCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t rttr;
+ uint32_t rtsr;
+ uint32_t rtar;
+ uint32_t last_rcnr;
+ int64_t last_hz;
+ QEMUTimer *rtc_alarm;
+ QEMUTimer *rtc_hz;
+ qemu_irq rtc_irq;
+ qemu_irq rtc_hz_irq;
+} StrongARMRTCState;
+
+static inline void strongarm_rtc_int_update(StrongARMRTCState *s)
+{
+ qemu_set_irq(s->rtc_irq, s->rtsr & RTSR_AL);
+ qemu_set_irq(s->rtc_hz_irq, s->rtsr & RTSR_HZ);
+}
+
+static void strongarm_rtc_hzupdate(StrongARMRTCState *s)
+{
+ int64_t rt = qemu_clock_get_ms(rtc_clock);
+ s->last_rcnr += ((rt - s->last_hz) << 15) /
+ (1000 * ((s->rttr & 0xffff) + 1));
+ s->last_hz = rt;
+}
+
+static inline void strongarm_rtc_timer_update(StrongARMRTCState *s)
+{
+ if ((s->rtsr & RTSR_HZE) && !(s->rtsr & RTSR_HZ)) {
+ timer_mod(s->rtc_hz, s->last_hz + 1000);
+ } else {
+ timer_del(s->rtc_hz);
+ }
+
+ if ((s->rtsr & RTSR_ALE) && !(s->rtsr & RTSR_AL)) {
+ timer_mod(s->rtc_alarm, s->last_hz +
+ (((s->rtar - s->last_rcnr) * 1000 *
+ ((s->rttr & 0xffff) + 1)) >> 15));
+ } else {
+ timer_del(s->rtc_alarm);
+ }
+}
+
+static inline void strongarm_rtc_alarm_tick(void *opaque)
+{
+ StrongARMRTCState *s = opaque;
+ s->rtsr |= RTSR_AL;
+ strongarm_rtc_timer_update(s);
+ strongarm_rtc_int_update(s);
+}
+
+static inline void strongarm_rtc_hz_tick(void *opaque)
+{
+ StrongARMRTCState *s = opaque;
+ s->rtsr |= RTSR_HZ;
+ strongarm_rtc_timer_update(s);
+ strongarm_rtc_int_update(s);
+}
+
+static uint64_t strongarm_rtc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ StrongARMRTCState *s = opaque;
+
+ switch (addr) {
+ case RTTR:
+ return s->rttr;
+ case RTSR:
+ return s->rtsr;
+ case RTAR:
+ return s->rtar;
+ case RCNR:
+ return s->last_rcnr +
+ ((qemu_clock_get_ms(rtc_clock) - s->last_hz) << 15) /
+ (1000 * ((s->rttr & 0xffff) + 1));
+ default:
+ printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr);
+ return 0;
+ }
+}
+
+static void strongarm_rtc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ StrongARMRTCState *s = opaque;
+ uint32_t old_rtsr;
+
+ switch (addr) {
+ case RTTR:
+ strongarm_rtc_hzupdate(s);
+ s->rttr = value;
+ strongarm_rtc_timer_update(s);
+ break;
+
+ case RTSR:
+ old_rtsr = s->rtsr;
+ s->rtsr = (value & (RTSR_ALE | RTSR_HZE)) |
+ (s->rtsr & ~(value & (RTSR_AL | RTSR_HZ)));
+
+ if (s->rtsr != old_rtsr) {
+ strongarm_rtc_timer_update(s);
+ }
+
+ strongarm_rtc_int_update(s);
+ break;
+
+ case RTAR:
+ s->rtar = value;
+ strongarm_rtc_timer_update(s);
+ break;
+
+ case RCNR:
+ strongarm_rtc_hzupdate(s);
+ s->last_rcnr = value;
+ strongarm_rtc_timer_update(s);
+ break;
+
+ default:
+ printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr);
+ }
+}
+
+static const MemoryRegionOps strongarm_rtc_ops = {
+ .read = strongarm_rtc_read,
+ .write = strongarm_rtc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int strongarm_rtc_init(SysBusDevice *dev)
+{
+ StrongARMRTCState *s = STRONGARM_RTC(dev);
+ struct tm tm;
+
+ s->rttr = 0x0;
+ s->rtsr = 0;
+
+ qemu_get_timedate(&tm, 0);
+
+ s->last_rcnr = (uint32_t) mktimegm(&tm);
+ s->last_hz = qemu_clock_get_ms(rtc_clock);
+
+ s->rtc_alarm = timer_new_ms(rtc_clock, strongarm_rtc_alarm_tick, s);
+ s->rtc_hz = timer_new_ms(rtc_clock, strongarm_rtc_hz_tick, s);
+
+ sysbus_init_irq(dev, &s->rtc_irq);
+ sysbus_init_irq(dev, &s->rtc_hz_irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_rtc_ops, s,
+ "rtc", 0x10000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void strongarm_rtc_pre_save(void *opaque)
+{
+ StrongARMRTCState *s = opaque;
+
+ strongarm_rtc_hzupdate(s);
+}
+
+static int strongarm_rtc_post_load(void *opaque, int version_id)
+{
+ StrongARMRTCState *s = opaque;
+
+ strongarm_rtc_timer_update(s);
+ strongarm_rtc_int_update(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_strongarm_rtc_regs = {
+ .name = "strongarm-rtc",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_save = strongarm_rtc_pre_save,
+ .post_load = strongarm_rtc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(rttr, StrongARMRTCState),
+ VMSTATE_UINT32(rtsr, StrongARMRTCState),
+ VMSTATE_UINT32(rtar, StrongARMRTCState),
+ VMSTATE_UINT32(last_rcnr, StrongARMRTCState),
+ VMSTATE_INT64(last_hz, StrongARMRTCState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void strongarm_rtc_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = strongarm_rtc_init;
+ dc->desc = "StrongARM RTC Controller";
+ dc->vmsd = &vmstate_strongarm_rtc_regs;
+}
+
+static const TypeInfo strongarm_rtc_sysbus_info = {
+ .name = TYPE_STRONGARM_RTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(StrongARMRTCState),
+ .class_init = strongarm_rtc_sysbus_class_init,
+};
+
+/* GPIO */
+#define GPLR 0x00
+#define GPDR 0x04
+#define GPSR 0x08
+#define GPCR 0x0c
+#define GRER 0x10
+#define GFER 0x14
+#define GEDR 0x18
+#define GAFR 0x1c
+
+#define TYPE_STRONGARM_GPIO "strongarm-gpio"
+#define STRONGARM_GPIO(obj) \
+ OBJECT_CHECK(StrongARMGPIOInfo, (obj), TYPE_STRONGARM_GPIO)
+
+typedef struct StrongARMGPIOInfo StrongARMGPIOInfo;
+struct StrongARMGPIOInfo {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ qemu_irq handler[28];
+ qemu_irq irqs[11];
+ qemu_irq irqX;
+
+ uint32_t ilevel;
+ uint32_t olevel;
+ uint32_t dir;
+ uint32_t rising;
+ uint32_t falling;
+ uint32_t status;
+ uint32_t gafr;
+
+ uint32_t prev_level;
+};
+
+
+static void strongarm_gpio_irq_update(StrongARMGPIOInfo *s)
+{
+ int i;
+ for (i = 0; i < 11; i++) {
+ qemu_set_irq(s->irqs[i], s->status & (1 << i));
+ }
+
+ qemu_set_irq(s->irqX, (s->status & ~0x7ff));
+}
+
+static void strongarm_gpio_set(void *opaque, int line, int level)
+{
+ StrongARMGPIOInfo *s = opaque;
+ uint32_t mask;
+
+ mask = 1 << line;
+
+ if (level) {
+ s->status |= s->rising & mask &
+ ~s->ilevel & ~s->dir;
+ s->ilevel |= mask;
+ } else {
+ s->status |= s->falling & mask &
+ s->ilevel & ~s->dir;
+ s->ilevel &= ~mask;
+ }
+
+ if (s->status & mask) {
+ strongarm_gpio_irq_update(s);
+ }
+}
+
+static void strongarm_gpio_handler_update(StrongARMGPIOInfo *s)
+{
+ uint32_t level, diff;
+ int bit;
+
+ level = s->olevel & s->dir;
+
+ for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
+ bit = ctz32(diff);
+ qemu_set_irq(s->handler[bit], (level >> bit) & 1);
+ }
+
+ s->prev_level = level;
+}
+
+static uint64_t strongarm_gpio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ StrongARMGPIOInfo *s = opaque;
+
+ switch (offset) {
+ case GPDR: /* GPIO Pin-Direction registers */
+ return s->dir;
+
+ case GPSR: /* GPIO Pin-Output Set registers */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "strongarm GPIO: read from write only register GPSR\n");
+ return 0;
+
+ case GPCR: /* GPIO Pin-Output Clear registers */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "strongarm GPIO: read from write only register GPCR\n");
+ return 0;
+
+ case GRER: /* GPIO Rising-Edge Detect Enable registers */
+ return s->rising;
+
+ case GFER: /* GPIO Falling-Edge Detect Enable registers */
+ return s->falling;
+
+ case GAFR: /* GPIO Alternate Function registers */
+ return s->gafr;
+
+ case GPLR: /* GPIO Pin-Level registers */
+ return (s->olevel & s->dir) |
+ (s->ilevel & ~s->dir);
+
+ case GEDR: /* GPIO Edge Detect Status registers */
+ return s->status;
+
+ default:
+ printf("%s: Bad offset 0x" TARGET_FMT_plx "\n", __func__, offset);
+ }
+
+ return 0;
+}
+
+static void strongarm_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ StrongARMGPIOInfo *s = opaque;
+
+ switch (offset) {
+ case GPDR: /* GPIO Pin-Direction registers */
+ s->dir = value;
+ strongarm_gpio_handler_update(s);
+ break;
+
+ case GPSR: /* GPIO Pin-Output Set registers */
+ s->olevel |= value;
+ strongarm_gpio_handler_update(s);
+ break;
+
+ case GPCR: /* GPIO Pin-Output Clear registers */
+ s->olevel &= ~value;
+ strongarm_gpio_handler_update(s);
+ break;
+
+ case GRER: /* GPIO Rising-Edge Detect Enable registers */
+ s->rising = value;
+ break;
+
+ case GFER: /* GPIO Falling-Edge Detect Enable registers */
+ s->falling = value;
+ break;
+
+ case GAFR: /* GPIO Alternate Function registers */
+ s->gafr = value;
+ break;
+
+ case GEDR: /* GPIO Edge Detect Status registers */
+ s->status &= ~value;
+ strongarm_gpio_irq_update(s);
+ break;
+
+ default:
+ printf("%s: Bad offset 0x" TARGET_FMT_plx "\n", __func__, offset);
+ }
+}
+
+static const MemoryRegionOps strongarm_gpio_ops = {
+ .read = strongarm_gpio_read,
+ .write = strongarm_gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static DeviceState *strongarm_gpio_init(hwaddr base,
+ DeviceState *pic)
+{
+ DeviceState *dev;
+ int i;
+
+ dev = qdev_create(NULL, TYPE_STRONGARM_GPIO);
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ for (i = 0; i < 12; i++)
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), i,
+ qdev_get_gpio_in(pic, SA_PIC_GPIO0_EDGE + i));
+
+ return dev;
+}
+
+static int strongarm_gpio_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ StrongARMGPIOInfo *s = STRONGARM_GPIO(dev);
+ int i;
+
+ qdev_init_gpio_in(dev, strongarm_gpio_set, 28);
+ qdev_init_gpio_out(dev, s->handler, 28);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_gpio_ops, s,
+ "gpio", 0x1000);
+
+ sysbus_init_mmio(sbd, &s->iomem);
+ for (i = 0; i < 11; i++) {
+ sysbus_init_irq(sbd, &s->irqs[i]);
+ }
+ sysbus_init_irq(sbd, &s->irqX);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_strongarm_gpio_regs = {
+ .name = "strongarm-gpio",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ilevel, StrongARMGPIOInfo),
+ VMSTATE_UINT32(olevel, StrongARMGPIOInfo),
+ VMSTATE_UINT32(dir, StrongARMGPIOInfo),
+ VMSTATE_UINT32(rising, StrongARMGPIOInfo),
+ VMSTATE_UINT32(falling, StrongARMGPIOInfo),
+ VMSTATE_UINT32(status, StrongARMGPIOInfo),
+ VMSTATE_UINT32(gafr, StrongARMGPIOInfo),
+ VMSTATE_UINT32(prev_level, StrongARMGPIOInfo),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void strongarm_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = strongarm_gpio_initfn;
+ dc->desc = "StrongARM GPIO controller";
+ dc->vmsd = &vmstate_strongarm_gpio_regs;
+}
+
+static const TypeInfo strongarm_gpio_info = {
+ .name = TYPE_STRONGARM_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(StrongARMGPIOInfo),
+ .class_init = strongarm_gpio_class_init,
+};
+
+/* Peripheral Pin Controller */
+#define PPDR 0x00
+#define PPSR 0x04
+#define PPAR 0x08
+#define PSDR 0x0c
+#define PPFR 0x10
+
+#define TYPE_STRONGARM_PPC "strongarm-ppc"
+#define STRONGARM_PPC(obj) \
+ OBJECT_CHECK(StrongARMPPCInfo, (obj), TYPE_STRONGARM_PPC)
+
+typedef struct StrongARMPPCInfo StrongARMPPCInfo;
+struct StrongARMPPCInfo {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq handler[28];
+
+ uint32_t ilevel;
+ uint32_t olevel;
+ uint32_t dir;
+ uint32_t ppar;
+ uint32_t psdr;
+ uint32_t ppfr;
+
+ uint32_t prev_level;
+};
+
+static void strongarm_ppc_set(void *opaque, int line, int level)
+{
+ StrongARMPPCInfo *s = opaque;
+
+ if (level) {
+ s->ilevel |= 1 << line;
+ } else {
+ s->ilevel &= ~(1 << line);
+ }
+}
+
+static void strongarm_ppc_handler_update(StrongARMPPCInfo *s)
+{
+ uint32_t level, diff;
+ int bit;
+
+ level = s->olevel & s->dir;
+
+ for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
+ bit = ctz32(diff);
+ qemu_set_irq(s->handler[bit], (level >> bit) & 1);
+ }
+
+ s->prev_level = level;
+}
+
+static uint64_t strongarm_ppc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ StrongARMPPCInfo *s = opaque;
+
+ switch (offset) {
+ case PPDR: /* PPC Pin Direction registers */
+ return s->dir | ~0x3fffff;
+
+ case PPSR: /* PPC Pin State registers */
+ return (s->olevel & s->dir) |
+ (s->ilevel & ~s->dir) |
+ ~0x3fffff;
+
+ case PPAR:
+ return s->ppar | ~0x41000;
+
+ case PSDR:
+ return s->psdr;
+
+ case PPFR:
+ return s->ppfr | ~0x7f001;
+
+ default:
+ printf("%s: Bad offset 0x" TARGET_FMT_plx "\n", __func__, offset);
+ }
+
+ return 0;
+}
+
+static void strongarm_ppc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ StrongARMPPCInfo *s = opaque;
+
+ switch (offset) {
+ case PPDR: /* PPC Pin Direction registers */
+ s->dir = value & 0x3fffff;
+ strongarm_ppc_handler_update(s);
+ break;
+
+ case PPSR: /* PPC Pin State registers */
+ s->olevel = value & s->dir & 0x3fffff;
+ strongarm_ppc_handler_update(s);
+ break;
+
+ case PPAR:
+ s->ppar = value & 0x41000;
+ break;
+
+ case PSDR:
+ s->psdr = value & 0x3fffff;
+ break;
+
+ case PPFR:
+ s->ppfr = value & 0x7f001;
+ break;
+
+ default:
+ printf("%s: Bad offset 0x" TARGET_FMT_plx "\n", __func__, offset);
+ }
+}
+
+static const MemoryRegionOps strongarm_ppc_ops = {
+ .read = strongarm_ppc_read,
+ .write = strongarm_ppc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int strongarm_ppc_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ StrongARMPPCInfo *s = STRONGARM_PPC(dev);
+
+ qdev_init_gpio_in(dev, strongarm_ppc_set, 22);
+ qdev_init_gpio_out(dev, s->handler, 22);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_ppc_ops, s,
+ "ppc", 0x1000);
+
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_strongarm_ppc_regs = {
+ .name = "strongarm-ppc",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ilevel, StrongARMPPCInfo),
+ VMSTATE_UINT32(olevel, StrongARMPPCInfo),
+ VMSTATE_UINT32(dir, StrongARMPPCInfo),
+ VMSTATE_UINT32(ppar, StrongARMPPCInfo),
+ VMSTATE_UINT32(psdr, StrongARMPPCInfo),
+ VMSTATE_UINT32(ppfr, StrongARMPPCInfo),
+ VMSTATE_UINT32(prev_level, StrongARMPPCInfo),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void strongarm_ppc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = strongarm_ppc_init;
+ dc->desc = "StrongARM PPC controller";
+ dc->vmsd = &vmstate_strongarm_ppc_regs;
+}
+
+static const TypeInfo strongarm_ppc_info = {
+ .name = TYPE_STRONGARM_PPC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(StrongARMPPCInfo),
+ .class_init = strongarm_ppc_class_init,
+};
+
+/* UART Ports */
+#define UTCR0 0x00
+#define UTCR1 0x04
+#define UTCR2 0x08
+#define UTCR3 0x0c
+#define UTDR 0x14
+#define UTSR0 0x1c
+#define UTSR1 0x20
+
+#define UTCR0_PE (1 << 0) /* Parity enable */
+#define UTCR0_OES (1 << 1) /* Even parity */
+#define UTCR0_SBS (1 << 2) /* 2 stop bits */
+#define UTCR0_DSS (1 << 3) /* 8-bit data */
+
+#define UTCR3_RXE (1 << 0) /* Rx enable */
+#define UTCR3_TXE (1 << 1) /* Tx enable */
+#define UTCR3_BRK (1 << 2) /* Force Break */
+#define UTCR3_RIE (1 << 3) /* Rx int enable */
+#define UTCR3_TIE (1 << 4) /* Tx int enable */
+#define UTCR3_LBM (1 << 5) /* Loopback */
+
+#define UTSR0_TFS (1 << 0) /* Tx FIFO nearly empty */
+#define UTSR0_RFS (1 << 1) /* Rx FIFO nearly full */
+#define UTSR0_RID (1 << 2) /* Receiver Idle */
+#define UTSR0_RBB (1 << 3) /* Receiver begin break */
+#define UTSR0_REB (1 << 4) /* Receiver end break */
+#define UTSR0_EIF (1 << 5) /* Error in FIFO */
+
+#define UTSR1_RNE (1 << 1) /* Receive FIFO not empty */
+#define UTSR1_TNF (1 << 2) /* Transmit FIFO not full */
+#define UTSR1_PRE (1 << 3) /* Parity error */
+#define UTSR1_FRE (1 << 4) /* Frame error */
+#define UTSR1_ROR (1 << 5) /* Receive Over Run */
+
+#define RX_FIFO_PRE (1 << 8)
+#define RX_FIFO_FRE (1 << 9)
+#define RX_FIFO_ROR (1 << 10)
+
+#define TYPE_STRONGARM_UART "strongarm-uart"
+#define STRONGARM_UART(obj) \
+ OBJECT_CHECK(StrongARMUARTState, (obj), TYPE_STRONGARM_UART)
+
+typedef struct StrongARMUARTState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint8_t utcr0;
+ uint16_t brd;
+ uint8_t utcr3;
+ uint8_t utsr0;
+ uint8_t utsr1;
+
+ uint8_t tx_fifo[8];
+ uint8_t tx_start;
+ uint8_t tx_len;
+ uint16_t rx_fifo[12]; /* value + error flags in high bits */
+ uint8_t rx_start;
+ uint8_t rx_len;
+
+ uint64_t char_transmit_time; /* time to transmit a char in ticks*/
+ bool wait_break_end;
+ QEMUTimer *rx_timeout_timer;
+ QEMUTimer *tx_timer;
+} StrongARMUARTState;
+
+static void strongarm_uart_update_status(StrongARMUARTState *s)
+{
+ uint16_t utsr1 = 0;
+
+ if (s->tx_len != 8) {
+ utsr1 |= UTSR1_TNF;
+ }
+
+ if (s->rx_len != 0) {
+ uint16_t ent = s->rx_fifo[s->rx_start];
+
+ utsr1 |= UTSR1_RNE;
+ if (ent & RX_FIFO_PRE) {
+ s->utsr1 |= UTSR1_PRE;
+ }
+ if (ent & RX_FIFO_FRE) {
+ s->utsr1 |= UTSR1_FRE;
+ }
+ if (ent & RX_FIFO_ROR) {
+ s->utsr1 |= UTSR1_ROR;
+ }
+ }
+
+ s->utsr1 = utsr1;
+}
+
+static void strongarm_uart_update_int_status(StrongARMUARTState *s)
+{
+ uint16_t utsr0 = s->utsr0 &
+ (UTSR0_REB | UTSR0_RBB | UTSR0_RID);
+ int i;
+
+ if ((s->utcr3 & UTCR3_TXE) &&
+ (s->utcr3 & UTCR3_TIE) &&
+ s->tx_len <= 4) {
+ utsr0 |= UTSR0_TFS;
+ }
+
+ if ((s->utcr3 & UTCR3_RXE) &&
+ (s->utcr3 & UTCR3_RIE) &&
+ s->rx_len > 4) {
+ utsr0 |= UTSR0_RFS;
+ }
+
+ for (i = 0; i < s->rx_len && i < 4; i++)
+ if (s->rx_fifo[(s->rx_start + i) % 12] & ~0xff) {
+ utsr0 |= UTSR0_EIF;
+ break;
+ }
+
+ s->utsr0 = utsr0;
+ qemu_set_irq(s->irq, utsr0);
+}
+
+static void strongarm_uart_update_parameters(StrongARMUARTState *s)
+{
+ int speed, parity, data_bits, stop_bits, frame_size;
+ QEMUSerialSetParams ssp;
+
+ /* Start bit. */
+ frame_size = 1;
+ if (s->utcr0 & UTCR0_PE) {
+ /* Parity bit. */
+ frame_size++;
+ if (s->utcr0 & UTCR0_OES) {
+ parity = 'E';
+ } else {
+ parity = 'O';
+ }
+ } else {
+ parity = 'N';
+ }
+ if (s->utcr0 & UTCR0_SBS) {
+ stop_bits = 2;
+ } else {
+ stop_bits = 1;
+ }
+
+ data_bits = (s->utcr0 & UTCR0_DSS) ? 8 : 7;
+ frame_size += data_bits + stop_bits;
+ speed = 3686400 / 16 / (s->brd + 1);
+ ssp.speed = speed;
+ ssp.parity = parity;
+ ssp.data_bits = data_bits;
+ ssp.stop_bits = stop_bits;
+ s->char_transmit_time = (get_ticks_per_sec() / speed) * frame_size;
+ if (s->chr) {
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+ }
+
+ DPRINTF(stderr, "%s speed=%d parity=%c data=%d stop=%d\n", s->chr->label,
+ speed, parity, data_bits, stop_bits);
+}
+
+static void strongarm_uart_rx_to(void *opaque)
+{
+ StrongARMUARTState *s = opaque;
+
+ if (s->rx_len) {
+ s->utsr0 |= UTSR0_RID;
+ strongarm_uart_update_int_status(s);
+ }
+}
+
+static void strongarm_uart_rx_push(StrongARMUARTState *s, uint16_t c)
+{
+ if ((s->utcr3 & UTCR3_RXE) == 0) {
+ /* rx disabled */
+ return;
+ }
+
+ if (s->wait_break_end) {
+ s->utsr0 |= UTSR0_REB;
+ s->wait_break_end = false;
+ }
+
+ if (s->rx_len < 12) {
+ s->rx_fifo[(s->rx_start + s->rx_len) % 12] = c;
+ s->rx_len++;
+ } else
+ s->rx_fifo[(s->rx_start + 11) % 12] |= RX_FIFO_ROR;
+}
+
+static int strongarm_uart_can_receive(void *opaque)
+{
+ StrongARMUARTState *s = opaque;
+
+ if (s->rx_len == 12) {
+ return 0;
+ }
+ /* It's best not to get more than 2/3 of RX FIFO, so advertise that much */
+ if (s->rx_len < 8) {
+ return 8 - s->rx_len;
+ }
+ return 1;
+}
+
+static void strongarm_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ StrongARMUARTState *s = opaque;
+ int i;
+
+ for (i = 0; i < size; i++) {
+ strongarm_uart_rx_push(s, buf[i]);
+ }
+
+ /* call the timeout receive callback in 3 char transmit time */
+ timer_mod(s->rx_timeout_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 3);
+
+ strongarm_uart_update_status(s);
+ strongarm_uart_update_int_status(s);
+}
+
+static void strongarm_uart_event(void *opaque, int event)
+{
+ StrongARMUARTState *s = opaque;
+ if (event == CHR_EVENT_BREAK) {
+ s->utsr0 |= UTSR0_RBB;
+ strongarm_uart_rx_push(s, RX_FIFO_FRE);
+ s->wait_break_end = true;
+ strongarm_uart_update_status(s);
+ strongarm_uart_update_int_status(s);
+ }
+}
+
+static void strongarm_uart_tx(void *opaque)
+{
+ StrongARMUARTState *s = opaque;
+ uint64_t new_xmit_ts = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ if (s->utcr3 & UTCR3_LBM) /* loopback */ {
+ strongarm_uart_receive(s, &s->tx_fifo[s->tx_start], 1);
+ } else if (s->chr) {
+ qemu_chr_fe_write(s->chr, &s->tx_fifo[s->tx_start], 1);
+ }
+
+ s->tx_start = (s->tx_start + 1) % 8;
+ s->tx_len--;
+ if (s->tx_len) {
+ timer_mod(s->tx_timer, new_xmit_ts + s->char_transmit_time);
+ }
+ strongarm_uart_update_status(s);
+ strongarm_uart_update_int_status(s);
+}
+
+static uint64_t strongarm_uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ StrongARMUARTState *s = opaque;
+ uint16_t ret;
+
+ switch (addr) {
+ case UTCR0:
+ return s->utcr0;
+
+ case UTCR1:
+ return s->brd >> 8;
+
+ case UTCR2:
+ return s->brd & 0xff;
+
+ case UTCR3:
+ return s->utcr3;
+
+ case UTDR:
+ if (s->rx_len != 0) {
+ ret = s->rx_fifo[s->rx_start];
+ s->rx_start = (s->rx_start + 1) % 12;
+ s->rx_len--;
+ strongarm_uart_update_status(s);
+ strongarm_uart_update_int_status(s);
+ return ret;
+ }
+ return 0;
+
+ case UTSR0:
+ return s->utsr0;
+
+ case UTSR1:
+ return s->utsr1;
+
+ default:
+ printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr);
+ return 0;
+ }
+}
+
+static void strongarm_uart_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ StrongARMUARTState *s = opaque;
+
+ switch (addr) {
+ case UTCR0:
+ s->utcr0 = value & 0x7f;
+ strongarm_uart_update_parameters(s);
+ break;
+
+ case UTCR1:
+ s->brd = (s->brd & 0xff) | ((value & 0xf) << 8);
+ strongarm_uart_update_parameters(s);
+ break;
+
+ case UTCR2:
+ s->brd = (s->brd & 0xf00) | (value & 0xff);
+ strongarm_uart_update_parameters(s);
+ break;
+
+ case UTCR3:
+ s->utcr3 = value & 0x3f;
+ if ((s->utcr3 & UTCR3_RXE) == 0) {
+ s->rx_len = 0;
+ }
+ if ((s->utcr3 & UTCR3_TXE) == 0) {
+ s->tx_len = 0;
+ }
+ strongarm_uart_update_status(s);
+ strongarm_uart_update_int_status(s);
+ break;
+
+ case UTDR:
+ if ((s->utcr3 & UTCR3_TXE) && s->tx_len != 8) {
+ s->tx_fifo[(s->tx_start + s->tx_len) % 8] = value;
+ s->tx_len++;
+ strongarm_uart_update_status(s);
+ strongarm_uart_update_int_status(s);
+ if (s->tx_len == 1) {
+ strongarm_uart_tx(s);
+ }
+ }
+ break;
+
+ case UTSR0:
+ s->utsr0 = s->utsr0 & ~(value &
+ (UTSR0_REB | UTSR0_RBB | UTSR0_RID));
+ strongarm_uart_update_int_status(s);
+ break;
+
+ default:
+ printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr);
+ }
+}
+
+static const MemoryRegionOps strongarm_uart_ops = {
+ .read = strongarm_uart_read,
+ .write = strongarm_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int strongarm_uart_init(SysBusDevice *dev)
+{
+ StrongARMUARTState *s = STRONGARM_UART(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_uart_ops, s,
+ "uart", 0x10000);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+
+ s->rx_timeout_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, strongarm_uart_rx_to, s);
+ s->tx_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, strongarm_uart_tx, s);
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr,
+ strongarm_uart_can_receive,
+ strongarm_uart_receive,
+ strongarm_uart_event,
+ s);
+ }
+
+ return 0;
+}
+
+static void strongarm_uart_reset(DeviceState *dev)
+{
+ StrongARMUARTState *s = STRONGARM_UART(dev);
+
+ s->utcr0 = UTCR0_DSS; /* 8 data, no parity */
+ s->brd = 23; /* 9600 */
+ /* enable send & recv - this actually violates spec */
+ s->utcr3 = UTCR3_TXE | UTCR3_RXE;
+
+ s->rx_len = s->tx_len = 0;
+
+ strongarm_uart_update_parameters(s);
+ strongarm_uart_update_status(s);
+ strongarm_uart_update_int_status(s);
+}
+
+static int strongarm_uart_post_load(void *opaque, int version_id)
+{
+ StrongARMUARTState *s = opaque;
+
+ strongarm_uart_update_parameters(s);
+ strongarm_uart_update_status(s);
+ strongarm_uart_update_int_status(s);
+
+ /* tx and restart timer */
+ if (s->tx_len) {
+ strongarm_uart_tx(s);
+ }
+
+ /* restart rx timeout timer */
+ if (s->rx_len) {
+ timer_mod(s->rx_timeout_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 3);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_strongarm_uart_regs = {
+ .name = "strongarm-uart",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = strongarm_uart_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(utcr0, StrongARMUARTState),
+ VMSTATE_UINT16(brd, StrongARMUARTState),
+ VMSTATE_UINT8(utcr3, StrongARMUARTState),
+ VMSTATE_UINT8(utsr0, StrongARMUARTState),
+ VMSTATE_UINT8_ARRAY(tx_fifo, StrongARMUARTState, 8),
+ VMSTATE_UINT8(tx_start, StrongARMUARTState),
+ VMSTATE_UINT8(tx_len, StrongARMUARTState),
+ VMSTATE_UINT16_ARRAY(rx_fifo, StrongARMUARTState, 12),
+ VMSTATE_UINT8(rx_start, StrongARMUARTState),
+ VMSTATE_UINT8(rx_len, StrongARMUARTState),
+ VMSTATE_BOOL(wait_break_end, StrongARMUARTState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static Property strongarm_uart_properties[] = {
+ DEFINE_PROP_CHR("chardev", StrongARMUARTState, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void strongarm_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = strongarm_uart_init;
+ dc->desc = "StrongARM UART controller";
+ dc->reset = strongarm_uart_reset;
+ dc->vmsd = &vmstate_strongarm_uart_regs;
+ dc->props = strongarm_uart_properties;
+}
+
+static const TypeInfo strongarm_uart_info = {
+ .name = TYPE_STRONGARM_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(StrongARMUARTState),
+ .class_init = strongarm_uart_class_init,
+};
+
+/* Synchronous Serial Ports */
+
+#define TYPE_STRONGARM_SSP "strongarm-ssp"
+#define STRONGARM_SSP(obj) \
+ OBJECT_CHECK(StrongARMSSPState, (obj), TYPE_STRONGARM_SSP)
+
+typedef struct StrongARMSSPState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ SSIBus *bus;
+
+ uint16_t sscr[2];
+ uint16_t sssr;
+
+ uint16_t rx_fifo[8];
+ uint8_t rx_level;
+ uint8_t rx_start;
+} StrongARMSSPState;
+
+#define SSCR0 0x60 /* SSP Control register 0 */
+#define SSCR1 0x64 /* SSP Control register 1 */
+#define SSDR 0x6c /* SSP Data register */
+#define SSSR 0x74 /* SSP Status register */
+
+/* Bitfields for above registers */
+#define SSCR0_SPI(x) (((x) & 0x30) == 0x00)
+#define SSCR0_SSP(x) (((x) & 0x30) == 0x10)
+#define SSCR0_UWIRE(x) (((x) & 0x30) == 0x20)
+#define SSCR0_PSP(x) (((x) & 0x30) == 0x30)
+#define SSCR0_SSE (1 << 7)
+#define SSCR0_DSS(x) (((x) & 0xf) + 1)
+#define SSCR1_RIE (1 << 0)
+#define SSCR1_TIE (1 << 1)
+#define SSCR1_LBM (1 << 2)
+#define SSSR_TNF (1 << 2)
+#define SSSR_RNE (1 << 3)
+#define SSSR_TFS (1 << 5)
+#define SSSR_RFS (1 << 6)
+#define SSSR_ROR (1 << 7)
+#define SSSR_RW 0x0080
+
+static void strongarm_ssp_int_update(StrongARMSSPState *s)
+{
+ int level = 0;
+
+ level |= (s->sssr & SSSR_ROR);
+ level |= (s->sssr & SSSR_RFS) && (s->sscr[1] & SSCR1_RIE);
+ level |= (s->sssr & SSSR_TFS) && (s->sscr[1] & SSCR1_TIE);
+ qemu_set_irq(s->irq, level);
+}
+
+static void strongarm_ssp_fifo_update(StrongARMSSPState *s)
+{
+ s->sssr &= ~SSSR_TFS;
+ s->sssr &= ~SSSR_TNF;
+ if (s->sscr[0] & SSCR0_SSE) {
+ if (s->rx_level >= 4) {
+ s->sssr |= SSSR_RFS;
+ } else {
+ s->sssr &= ~SSSR_RFS;
+ }
+ if (s->rx_level) {
+ s->sssr |= SSSR_RNE;
+ } else {
+ s->sssr &= ~SSSR_RNE;
+ }
+ /* TX FIFO is never filled, so it is always in underrun
+ condition if SSP is enabled */
+ s->sssr |= SSSR_TFS;
+ s->sssr |= SSSR_TNF;
+ }
+
+ strongarm_ssp_int_update(s);
+}
+
+static uint64_t strongarm_ssp_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ StrongARMSSPState *s = opaque;
+ uint32_t retval;
+
+ switch (addr) {
+ case SSCR0:
+ return s->sscr[0];
+ case SSCR1:
+ return s->sscr[1];
+ case SSSR:
+ return s->sssr;
+ case SSDR:
+ if (~s->sscr[0] & SSCR0_SSE) {
+ return 0xffffffff;
+ }
+ if (s->rx_level < 1) {
+ printf("%s: SSP Rx Underrun\n", __func__);
+ return 0xffffffff;
+ }
+ s->rx_level--;
+ retval = s->rx_fifo[s->rx_start++];
+ s->rx_start &= 0x7;
+ strongarm_ssp_fifo_update(s);
+ return retval;
+ default:
+ printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr);
+ break;
+ }
+ return 0;
+}
+
+static void strongarm_ssp_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ StrongARMSSPState *s = opaque;
+
+ switch (addr) {
+ case SSCR0:
+ s->sscr[0] = value & 0xffbf;
+ if ((s->sscr[0] & SSCR0_SSE) && SSCR0_DSS(value) < 4) {
+ printf("%s: Wrong data size: %i bits\n", __func__,
+ (int)SSCR0_DSS(value));
+ }
+ if (!(value & SSCR0_SSE)) {
+ s->sssr = 0;
+ s->rx_level = 0;
+ }
+ strongarm_ssp_fifo_update(s);
+ break;
+
+ case SSCR1:
+ s->sscr[1] = value & 0x2f;
+ if (value & SSCR1_LBM) {
+ printf("%s: Attempt to use SSP LBM mode\n", __func__);
+ }
+ strongarm_ssp_fifo_update(s);
+ break;
+
+ case SSSR:
+ s->sssr &= ~(value & SSSR_RW);
+ strongarm_ssp_int_update(s);
+ break;
+
+ case SSDR:
+ if (SSCR0_UWIRE(s->sscr[0])) {
+ value &= 0xff;
+ } else
+ /* Note how 32bits overflow does no harm here */
+ value &= (1 << SSCR0_DSS(s->sscr[0])) - 1;
+
+ /* Data goes from here to the Tx FIFO and is shifted out from
+ * there directly to the slave, no need to buffer it.
+ */
+ if (s->sscr[0] & SSCR0_SSE) {
+ uint32_t readval;
+ if (s->sscr[1] & SSCR1_LBM) {
+ readval = value;
+ } else {
+ readval = ssi_transfer(s->bus, value);
+ }
+
+ if (s->rx_level < 0x08) {
+ s->rx_fifo[(s->rx_start + s->rx_level++) & 0x7] = readval;
+ } else {
+ s->sssr |= SSSR_ROR;
+ }
+ }
+ strongarm_ssp_fifo_update(s);
+ break;
+
+ default:
+ printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps strongarm_ssp_ops = {
+ .read = strongarm_ssp_read,
+ .write = strongarm_ssp_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int strongarm_ssp_post_load(void *opaque, int version_id)
+{
+ StrongARMSSPState *s = opaque;
+
+ strongarm_ssp_fifo_update(s);
+
+ return 0;
+}
+
+static int strongarm_ssp_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ StrongARMSSPState *s = STRONGARM_SSP(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_ssp_ops, s,
+ "ssp", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->bus = ssi_create_bus(dev, "ssi");
+ return 0;
+}
+
+static void strongarm_ssp_reset(DeviceState *dev)
+{
+ StrongARMSSPState *s = STRONGARM_SSP(dev);
+
+ s->sssr = 0x03; /* 3 bit data, SPI, disabled */
+ s->rx_start = 0;
+ s->rx_level = 0;
+}
+
+static const VMStateDescription vmstate_strongarm_ssp_regs = {
+ .name = "strongarm-ssp",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = strongarm_ssp_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16_ARRAY(sscr, StrongARMSSPState, 2),
+ VMSTATE_UINT16(sssr, StrongARMSSPState),
+ VMSTATE_UINT16_ARRAY(rx_fifo, StrongARMSSPState, 8),
+ VMSTATE_UINT8(rx_start, StrongARMSSPState),
+ VMSTATE_UINT8(rx_level, StrongARMSSPState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void strongarm_ssp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = strongarm_ssp_init;
+ dc->desc = "StrongARM SSP controller";
+ dc->reset = strongarm_ssp_reset;
+ dc->vmsd = &vmstate_strongarm_ssp_regs;
+}
+
+static const TypeInfo strongarm_ssp_info = {
+ .name = TYPE_STRONGARM_SSP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(StrongARMSSPState),
+ .class_init = strongarm_ssp_class_init,
+};
+
+/* Main CPU functions */
+StrongARMState *sa1110_init(MemoryRegion *sysmem,
+ unsigned int sdram_size, const char *rev)
+{
+ StrongARMState *s;
+ int i;
+
+ s = g_malloc0(sizeof(StrongARMState));
+
+ if (!rev) {
+ rev = "sa1110-b5";
+ }
+
+ if (strncmp(rev, "sa1110", 6)) {
+ error_report("Machine requires a SA1110 processor.");
+ exit(1);
+ }
+
+ s->cpu = cpu_arm_init(rev);
+
+ if (!s->cpu) {
+ error_report("Unable to find CPU definition");
+ exit(1);
+ }
+
+ memory_region_allocate_system_memory(&s->sdram, NULL, "strongarm.sdram",
+ sdram_size);
+ memory_region_add_subregion(sysmem, SA_SDCS0, &s->sdram);
+
+ s->pic = sysbus_create_varargs("strongarm_pic", 0x90050000,
+ qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ),
+ qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ),
+ NULL);
+
+ sysbus_create_varargs("pxa25x-timer", 0x90000000,
+ qdev_get_gpio_in(s->pic, SA_PIC_OSTC0),
+ qdev_get_gpio_in(s->pic, SA_PIC_OSTC1),
+ qdev_get_gpio_in(s->pic, SA_PIC_OSTC2),
+ qdev_get_gpio_in(s->pic, SA_PIC_OSTC3),
+ NULL);
+
+ sysbus_create_simple(TYPE_STRONGARM_RTC, 0x90010000,
+ qdev_get_gpio_in(s->pic, SA_PIC_RTC_ALARM));
+
+ s->gpio = strongarm_gpio_init(0x90040000, s->pic);
+
+ s->ppc = sysbus_create_varargs(TYPE_STRONGARM_PPC, 0x90060000, NULL);
+
+ for (i = 0; sa_serial[i].io_base; i++) {
+ DeviceState *dev = qdev_create(NULL, TYPE_STRONGARM_UART);
+ qdev_prop_set_chr(dev, "chardev", serial_hds[i]);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0,
+ sa_serial[i].io_base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
+ qdev_get_gpio_in(s->pic, sa_serial[i].irq));
+ }
+
+ s->ssp = sysbus_create_varargs(TYPE_STRONGARM_SSP, 0x80070000,
+ qdev_get_gpio_in(s->pic, SA_PIC_SSP), NULL);
+ s->ssp_bus = (SSIBus *)qdev_get_child_bus(s->ssp, "ssi");
+
+ return s;
+}
+
+static void strongarm_register_types(void)
+{
+ type_register_static(&strongarm_pic_info);
+ type_register_static(&strongarm_rtc_sysbus_info);
+ type_register_static(&strongarm_gpio_info);
+ type_register_static(&strongarm_ppc_info);
+ type_register_static(&strongarm_uart_info);
+ type_register_static(&strongarm_ssp_info);
+}
+
+type_init(strongarm_register_types)
diff --git a/hw/arm/strongarm.h b/hw/arm/strongarm.h
new file mode 100644
index 00000000..2893f944
--- /dev/null
+++ b/hw/arm/strongarm.h
@@ -0,0 +1,68 @@
+#ifndef _STRONGARM_H
+#define _STRONGARM_H
+
+#include "exec/memory.h"
+
+#define SA_CS0 0x00000000
+#define SA_CS1 0x08000000
+#define SA_CS2 0x10000000
+#define SA_CS3 0x18000000
+#define SA_PCMCIA_CS0 0x20000000
+#define SA_PCMCIA_CS1 0x30000000
+#define SA_CS4 0x40000000
+#define SA_CS5 0x48000000
+/* system registers here */
+#define SA_SDCS0 0xc0000000
+#define SA_SDCS1 0xc8000000
+#define SA_SDCS2 0xd0000000
+#define SA_SDCS3 0xd8000000
+
+enum {
+ SA_PIC_GPIO0_EDGE = 0,
+ SA_PIC_GPIO1_EDGE,
+ SA_PIC_GPIO2_EDGE,
+ SA_PIC_GPIO3_EDGE,
+ SA_PIC_GPIO4_EDGE,
+ SA_PIC_GPIO5_EDGE,
+ SA_PIC_GPIO6_EDGE,
+ SA_PIC_GPIO7_EDGE,
+ SA_PIC_GPIO8_EDGE,
+ SA_PIC_GPIO9_EDGE,
+ SA_PIC_GPIO10_EDGE,
+ SA_PIC_GPIOX_EDGE,
+ SA_PIC_LCD,
+ SA_PIC_UDC,
+ SA_PIC_RSVD1,
+ SA_PIC_UART1,
+ SA_PIC_UART2,
+ SA_PIC_UART3,
+ SA_PIC_MCP,
+ SA_PIC_SSP,
+ SA_PIC_DMA_CH0,
+ SA_PIC_DMA_CH1,
+ SA_PIC_DMA_CH2,
+ SA_PIC_DMA_CH3,
+ SA_PIC_DMA_CH4,
+ SA_PIC_DMA_CH5,
+ SA_PIC_OSTC0,
+ SA_PIC_OSTC1,
+ SA_PIC_OSTC2,
+ SA_PIC_OSTC3,
+ SA_PIC_RTC_HZ,
+ SA_PIC_RTC_ALARM,
+};
+
+typedef struct {
+ ARMCPU *cpu;
+ MemoryRegion sdram;
+ DeviceState *pic;
+ DeviceState *gpio;
+ DeviceState *ppc;
+ DeviceState *ssp;
+ SSIBus *ssp_bus;
+} StrongARMState;
+
+StrongARMState *sa1110_init(MemoryRegion *sysmem,
+ unsigned int sdram_size, const char *rev);
+
+#endif
diff --git a/hw/arm/sysbus-fdt.c b/hw/arm/sysbus-fdt.c
new file mode 100644
index 00000000..9d28797c
--- /dev/null
+++ b/hw/arm/sysbus-fdt.c
@@ -0,0 +1,247 @@
+/*
+ * ARM Platform Bus device tree generation helpers
+ *
+ * Copyright (c) 2014 Linaro Limited
+ *
+ * Authors:
+ * Alex Graf <agraf@suse.de>
+ * Eric Auger <eric.auger@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/arm/sysbus-fdt.h"
+#include "qemu/error-report.h"
+#include "sysemu/device_tree.h"
+#include "hw/platform-bus.h"
+#include "sysemu/sysemu.h"
+#include "hw/vfio/vfio-platform.h"
+#include "hw/vfio/vfio-calxeda-xgmac.h"
+#include "hw/arm/fdt.h"
+
+/*
+ * internal struct that contains the information to create dynamic
+ * sysbus device node
+ */
+typedef struct PlatformBusFDTData {
+ void *fdt; /* device tree handle */
+ int irq_start; /* index of the first IRQ usable by platform bus devices */
+ const char *pbus_node_name; /* name of the platform bus node */
+ PlatformBusDevice *pbus;
+} PlatformBusFDTData;
+
+/*
+ * struct used when calling the machine init done notifier
+ * that constructs the fdt nodes of platform bus devices
+ */
+typedef struct PlatformBusFDTNotifierParams {
+ Notifier notifier;
+ ARMPlatformBusFDTParams *fdt_params;
+} PlatformBusFDTNotifierParams;
+
+/* struct that associates a device type name and a node creation function */
+typedef struct NodeCreationPair {
+ const char *typename;
+ int (*add_fdt_node_fn)(SysBusDevice *sbdev, void *opaque);
+} NodeCreationPair;
+
+/* Device Specific Code */
+
+/**
+ * add_calxeda_midway_xgmac_fdt_node
+ *
+ * Generates a simple node with following properties:
+ * compatible string, regs, interrupts, dma-coherent
+ */
+static int add_calxeda_midway_xgmac_fdt_node(SysBusDevice *sbdev, void *opaque)
+{
+ PlatformBusFDTData *data = opaque;
+ PlatformBusDevice *pbus = data->pbus;
+ void *fdt = data->fdt;
+ const char *parent_node = data->pbus_node_name;
+ int compat_str_len, i, ret = -1;
+ char *nodename;
+ uint32_t *irq_attr, *reg_attr;
+ uint64_t mmio_base, irq_number;
+ VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
+ VFIODevice *vbasedev = &vdev->vbasedev;
+
+ mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
+ nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node,
+ vbasedev->name, mmio_base);
+ qemu_fdt_add_subnode(fdt, nodename);
+
+ compat_str_len = strlen(vdev->compat) + 1;
+ qemu_fdt_setprop(fdt, nodename, "compatible",
+ vdev->compat, compat_str_len);
+
+ qemu_fdt_setprop(fdt, nodename, "dma-coherent", "", 0);
+
+ reg_attr = g_new(uint32_t, vbasedev->num_regions * 2);
+ for (i = 0; i < vbasedev->num_regions; i++) {
+ mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, i);
+ reg_attr[2 * i] = cpu_to_be32(mmio_base);
+ reg_attr[2 * i + 1] = cpu_to_be32(
+ memory_region_size(&vdev->regions[i]->mem));
+ }
+ ret = qemu_fdt_setprop(fdt, nodename, "reg", reg_attr,
+ vbasedev->num_regions * 2 * sizeof(uint32_t));
+ if (ret) {
+ error_report("could not set reg property of node %s", nodename);
+ goto fail_reg;
+ }
+
+ irq_attr = g_new(uint32_t, vbasedev->num_irqs * 3);
+ for (i = 0; i < vbasedev->num_irqs; i++) {
+ irq_number = platform_bus_get_irqn(pbus, sbdev , i)
+ + data->irq_start;
+ irq_attr[3 * i] = cpu_to_be32(GIC_FDT_IRQ_TYPE_SPI);
+ irq_attr[3 * i + 1] = cpu_to_be32(irq_number);
+ irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_LEVEL_HI);
+ }
+ ret = qemu_fdt_setprop(fdt, nodename, "interrupts",
+ irq_attr, vbasedev->num_irqs * 3 * sizeof(uint32_t));
+ if (ret) {
+ error_report("could not set interrupts property of node %s",
+ nodename);
+ }
+ g_free(irq_attr);
+fail_reg:
+ g_free(reg_attr);
+ g_free(nodename);
+ return ret;
+}
+
+/* list of supported dynamic sysbus devices */
+static const NodeCreationPair add_fdt_node_functions[] = {
+ {TYPE_VFIO_CALXEDA_XGMAC, add_calxeda_midway_xgmac_fdt_node},
+ {"", NULL}, /* last element */
+};
+
+/* Generic Code */
+
+/**
+ * add_fdt_node - add the device tree node of a dynamic sysbus device
+ *
+ * @sbdev: handle to the sysbus device
+ * @opaque: handle to the PlatformBusFDTData
+ *
+ * Checks the sysbus type belongs to the list of device types that
+ * are dynamically instantiable and if so call the node creation
+ * function.
+ */
+static int add_fdt_node(SysBusDevice *sbdev, void *opaque)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(add_fdt_node_functions); i++) {
+ if (!strcmp(object_get_typename(OBJECT(sbdev)),
+ add_fdt_node_functions[i].typename)) {
+ ret = add_fdt_node_functions[i].add_fdt_node_fn(sbdev, opaque);
+ assert(!ret);
+ return 0;
+ }
+ }
+ error_report("Device %s can not be dynamically instantiated",
+ qdev_fw_name(DEVICE(sbdev)));
+ exit(1);
+}
+
+/**
+ * add_all_platform_bus_fdt_nodes - create all the platform bus nodes
+ *
+ * builds the parent platform bus node and all the nodes of dynamic
+ * sysbus devices attached to it.
+ */
+static void add_all_platform_bus_fdt_nodes(ARMPlatformBusFDTParams *fdt_params)
+{
+ const char platcomp[] = "qemu,platform\0simple-bus";
+ PlatformBusDevice *pbus;
+ DeviceState *dev;
+ gchar *node;
+ uint64_t addr, size;
+ int irq_start, dtb_size;
+ struct arm_boot_info *info = fdt_params->binfo;
+ const ARMPlatformBusSystemParams *params = fdt_params->system_params;
+ const char *intc = fdt_params->intc;
+ void *fdt = info->get_dtb(info, &dtb_size);
+
+ /*
+ * If the user provided a dtb, we assume the dynamic sysbus nodes
+ * already are integrated there. This corresponds to a use case where
+ * the dynamic sysbus nodes are complex and their generation is not yet
+ * supported. In that case the user can take charge of the guest dt
+ * while qemu takes charge of the qom stuff.
+ */
+ if (info->dtb_filename) {
+ return;
+ }
+
+ assert(fdt);
+
+ node = g_strdup_printf("/platform@%"PRIx64, params->platform_bus_base);
+ addr = params->platform_bus_base;
+ size = params->platform_bus_size;
+ irq_start = params->platform_bus_first_irq;
+
+ /* Create a /platform node that we can put all devices into */
+ qemu_fdt_add_subnode(fdt, node);
+ qemu_fdt_setprop(fdt, node, "compatible", platcomp, sizeof(platcomp));
+
+ /* Our platform bus region is less than 32bits, so 1 cell is enough for
+ * address and size
+ */
+ qemu_fdt_setprop_cells(fdt, node, "#size-cells", 1);
+ qemu_fdt_setprop_cells(fdt, node, "#address-cells", 1);
+ qemu_fdt_setprop_cells(fdt, node, "ranges", 0, addr >> 32, addr, size);
+
+ qemu_fdt_setprop_phandle(fdt, node, "interrupt-parent", intc);
+
+ dev = qdev_find_recursive(sysbus_get_default(), TYPE_PLATFORM_BUS_DEVICE);
+ pbus = PLATFORM_BUS_DEVICE(dev);
+
+ /* We can only create dt nodes for dynamic devices when they're ready */
+ assert(pbus->done_gathering);
+
+ PlatformBusFDTData data = {
+ .fdt = fdt,
+ .irq_start = irq_start,
+ .pbus_node_name = node,
+ .pbus = pbus,
+ };
+
+ /* Loop through all dynamic sysbus devices and create their node */
+ foreach_dynamic_sysbus_device(add_fdt_node, &data);
+
+ g_free(node);
+}
+
+static void platform_bus_fdt_notify(Notifier *notifier, void *data)
+{
+ PlatformBusFDTNotifierParams *p = DO_UPCAST(PlatformBusFDTNotifierParams,
+ notifier, notifier);
+
+ add_all_platform_bus_fdt_nodes(p->fdt_params);
+ g_free(p->fdt_params);
+ g_free(p);
+}
+
+void arm_register_platform_bus_fdt_creator(ARMPlatformBusFDTParams *fdt_params)
+{
+ PlatformBusFDTNotifierParams *p = g_new(PlatformBusFDTNotifierParams, 1);
+
+ p->fdt_params = fdt_params;
+ p->notifier.notify = platform_bus_fdt_notify;
+ qemu_add_machine_init_done_notifier(&p->notifier);
+}
diff --git a/hw/arm/tosa.c b/hw/arm/tosa.c
new file mode 100644
index 00000000..73572ebe
--- /dev/null
+++ b/hw/arm/tosa.c
@@ -0,0 +1,306 @@
+/* vim:set shiftwidth=4 ts=4 et: */
+/*
+ * PXA255 Sharp Zaurus SL-6000 PDA platform
+ *
+ * Copyright (c) 2008 Dmitry Baryshkov
+ *
+ * Code based on spitz platform by Andrzej Zaborowski <balrog@zabor.org>
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "hw/arm/sharpsl.h"
+#include "hw/pcmcia.h"
+#include "hw/boards.h"
+#include "hw/i2c/i2c.h"
+#include "hw/ssi.h"
+#include "sysemu/block-backend.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+
+#define TOSA_RAM 0x04000000
+#define TOSA_ROM 0x00800000
+
+#define TOSA_GPIO_USB_IN (5)
+#define TOSA_GPIO_nSD_DETECT (9)
+#define TOSA_GPIO_ON_RESET (19)
+#define TOSA_GPIO_CF_IRQ (21) /* CF slot0 Ready */
+#define TOSA_GPIO_CF_CD (13)
+#define TOSA_GPIO_TC6393XB_INT (15)
+#define TOSA_GPIO_JC_CF_IRQ (36) /* CF slot1 Ready */
+
+#define TOSA_SCOOP_GPIO_BASE 1
+#define TOSA_GPIO_IR_POWERDWN (TOSA_SCOOP_GPIO_BASE + 2)
+#define TOSA_GPIO_SD_WP (TOSA_SCOOP_GPIO_BASE + 3)
+#define TOSA_GPIO_PWR_ON (TOSA_SCOOP_GPIO_BASE + 4)
+
+#define TOSA_SCOOP_JC_GPIO_BASE 1
+#define TOSA_GPIO_BT_LED (TOSA_SCOOP_JC_GPIO_BASE + 0)
+#define TOSA_GPIO_NOTE_LED (TOSA_SCOOP_JC_GPIO_BASE + 1)
+#define TOSA_GPIO_CHRG_ERR_LED (TOSA_SCOOP_JC_GPIO_BASE + 2)
+#define TOSA_GPIO_TC6393XB_L3V_ON (TOSA_SCOOP_JC_GPIO_BASE + 5)
+#define TOSA_GPIO_WLAN_LED (TOSA_SCOOP_JC_GPIO_BASE + 7)
+
+#define DAC_BASE 0x4e
+#define DAC_CH1 0
+#define DAC_CH2 1
+
+static void tosa_microdrive_attach(PXA2xxState *cpu)
+{
+ PCMCIACardState *md;
+ DriveInfo *dinfo;
+
+ dinfo = drive_get(IF_IDE, 0, 0);
+ if (!dinfo || dinfo->media_cd)
+ return;
+ md = dscm1xxxx_init(dinfo);
+ pxa2xx_pcmcia_attach(cpu->pcmcia[0], md);
+}
+
+static void tosa_out_switch(void *opaque, int line, int level)
+{
+ switch (line) {
+ case 0:
+ fprintf(stderr, "blue LED %s.\n", level ? "on" : "off");
+ break;
+ case 1:
+ fprintf(stderr, "green LED %s.\n", level ? "on" : "off");
+ break;
+ case 2:
+ fprintf(stderr, "amber LED %s.\n", level ? "on" : "off");
+ break;
+ case 3:
+ fprintf(stderr, "wlan LED %s.\n", level ? "on" : "off");
+ break;
+ default:
+ fprintf(stderr, "Uhandled out event: %d = %d\n", line, level);
+ break;
+ }
+}
+
+
+static void tosa_gpio_setup(PXA2xxState *cpu,
+ DeviceState *scp0,
+ DeviceState *scp1,
+ TC6393xbState *tmio)
+{
+ qemu_irq *outsignals = qemu_allocate_irqs(tosa_out_switch, cpu, 4);
+ /* MMC/SD host */
+ pxa2xx_mmci_handlers(cpu->mmc,
+ qdev_get_gpio_in(scp0, TOSA_GPIO_SD_WP),
+ qemu_irq_invert(qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_nSD_DETECT)));
+
+ /* Handle reset */
+ qdev_connect_gpio_out(cpu->gpio, TOSA_GPIO_ON_RESET, cpu->reset);
+
+ /* PCMCIA signals: card's IRQ and Card-Detect */
+ pxa2xx_pcmcia_set_irq_cb(cpu->pcmcia[0],
+ qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_CF_IRQ),
+ qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_CF_CD));
+
+ pxa2xx_pcmcia_set_irq_cb(cpu->pcmcia[1],
+ qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_JC_CF_IRQ),
+ NULL);
+
+ qdev_connect_gpio_out(scp1, TOSA_GPIO_BT_LED, outsignals[0]);
+ qdev_connect_gpio_out(scp1, TOSA_GPIO_NOTE_LED, outsignals[1]);
+ qdev_connect_gpio_out(scp1, TOSA_GPIO_CHRG_ERR_LED, outsignals[2]);
+ qdev_connect_gpio_out(scp1, TOSA_GPIO_WLAN_LED, outsignals[3]);
+
+ qdev_connect_gpio_out(scp1, TOSA_GPIO_TC6393XB_L3V_ON, tc6393xb_l3v_get(tmio));
+
+ /* UDC Vbus */
+ qemu_irq_raise(qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_USB_IN));
+}
+
+static uint32_t tosa_ssp_tansfer(SSISlave *dev, uint32_t value)
+{
+ fprintf(stderr, "TG: %d %02x\n", value >> 5, value & 0x1f);
+ return 0;
+}
+
+static int tosa_ssp_init(SSISlave *dev)
+{
+ /* Nothing to do. */
+ return 0;
+}
+
+#define TYPE_TOSA_DAC "tosa_dac"
+#define TOSA_DAC(obj) OBJECT_CHECK(TosaDACState, (obj), TYPE_TOSA_DAC)
+
+typedef struct {
+ I2CSlave parent_obj;
+
+ int len;
+ char buf[3];
+} TosaDACState;
+
+static int tosa_dac_send(I2CSlave *i2c, uint8_t data)
+{
+ TosaDACState *s = TOSA_DAC(i2c);
+
+ s->buf[s->len] = data;
+ if (s->len ++ > 2) {
+#ifdef VERBOSE
+ fprintf(stderr, "%s: message too long (%i bytes)\n", __FUNCTION__, s->len);
+#endif
+ return 1;
+ }
+
+ if (s->len == 2) {
+ fprintf(stderr, "dac: channel %d value 0x%02x\n",
+ s->buf[0], s->buf[1]);
+ }
+
+ return 0;
+}
+
+static void tosa_dac_event(I2CSlave *i2c, enum i2c_event event)
+{
+ TosaDACState *s = TOSA_DAC(i2c);
+
+ s->len = 0;
+ switch (event) {
+ case I2C_START_SEND:
+ break;
+ case I2C_START_RECV:
+ printf("%s: recv not supported!!!\n", __FUNCTION__);
+ break;
+ case I2C_FINISH:
+#ifdef VERBOSE
+ if (s->len < 2)
+ printf("%s: message too short (%i bytes)\n", __FUNCTION__, s->len);
+ if (s->len > 2)
+ printf("%s: message too long\n", __FUNCTION__);
+#endif
+ break;
+ default:
+ break;
+ }
+}
+
+static int tosa_dac_recv(I2CSlave *s)
+{
+ printf("%s: recv not supported!!!\n", __FUNCTION__);
+ return -1;
+}
+
+static int tosa_dac_init(I2CSlave *i2c)
+{
+ /* Nothing to do. */
+ return 0;
+}
+
+static void tosa_tg_init(PXA2xxState *cpu)
+{
+ I2CBus *bus = pxa2xx_i2c_bus(cpu->i2c[0]);
+ i2c_create_slave(bus, TYPE_TOSA_DAC, DAC_BASE);
+ ssi_create_slave(cpu->ssp[1], "tosa-ssp");
+}
+
+
+static struct arm_boot_info tosa_binfo = {
+ .loader_start = PXA2XX_SDRAM_BASE,
+ .ram_size = 0x04000000,
+};
+
+static void tosa_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *rom = g_new(MemoryRegion, 1);
+ PXA2xxState *mpu;
+ TC6393xbState *tmio;
+ DeviceState *scp0, *scp1;
+
+ if (!cpu_model)
+ cpu_model = "pxa255";
+
+ mpu = pxa255_init(address_space_mem, tosa_binfo.ram_size);
+
+ memory_region_init_ram(rom, NULL, "tosa.rom", TOSA_ROM, &error_abort);
+ vmstate_register_ram_global(rom);
+ memory_region_set_readonly(rom, true);
+ memory_region_add_subregion(address_space_mem, 0, rom);
+
+ tmio = tc6393xb_init(address_space_mem, 0x10000000,
+ qdev_get_gpio_in(mpu->gpio, TOSA_GPIO_TC6393XB_INT));
+
+ scp0 = sysbus_create_simple("scoop", 0x08800000, NULL);
+ scp1 = sysbus_create_simple("scoop", 0x14800040, NULL);
+
+ tosa_gpio_setup(mpu, scp0, scp1, tmio);
+
+ tosa_microdrive_attach(mpu);
+
+ tosa_tg_init(mpu);
+
+ tosa_binfo.kernel_filename = kernel_filename;
+ tosa_binfo.kernel_cmdline = kernel_cmdline;
+ tosa_binfo.initrd_filename = initrd_filename;
+ tosa_binfo.board_id = 0x208;
+ arm_load_kernel(mpu->cpu, &tosa_binfo);
+ sl_bootparam_write(SL_PXA_PARAM_BASE);
+}
+
+static QEMUMachine tosapda_machine = {
+ .name = "tosa",
+ .desc = "Tosa PDA (PXA255)",
+ .init = tosa_init,
+};
+
+static void tosapda_machine_init(void)
+{
+ qemu_register_machine(&tosapda_machine);
+}
+
+machine_init(tosapda_machine_init);
+
+static void tosa_dac_class_init(ObjectClass *klass, void *data)
+{
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = tosa_dac_init;
+ k->event = tosa_dac_event;
+ k->recv = tosa_dac_recv;
+ k->send = tosa_dac_send;
+}
+
+static const TypeInfo tosa_dac_info = {
+ .name = TYPE_TOSA_DAC,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(TosaDACState),
+ .class_init = tosa_dac_class_init,
+};
+
+static void tosa_ssp_class_init(ObjectClass *klass, void *data)
+{
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = tosa_ssp_init;
+ k->transfer = tosa_ssp_tansfer;
+}
+
+static const TypeInfo tosa_ssp_info = {
+ .name = "tosa-ssp",
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(SSISlave),
+ .class_init = tosa_ssp_class_init,
+};
+
+static void tosa_register_types(void)
+{
+ type_register_static(&tosa_dac_info);
+ type_register_static(&tosa_ssp_info);
+}
+
+type_init(tosa_register_types)
diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c
new file mode 100644
index 00000000..6c69f4ea
--- /dev/null
+++ b/hw/arm/versatilepb.c
@@ -0,0 +1,437 @@
+/*
+ * ARM Versatile Platform/Application Baseboard System emulation.
+ *
+ * Copyright (c) 2005-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/pci/pci.h"
+#include "hw/i2c/i2c.h"
+#include "hw/boards.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "hw/block/flash.h"
+#include "qemu/error-report.h"
+
+#define VERSATILE_FLASH_ADDR 0x34000000
+#define VERSATILE_FLASH_SIZE (64 * 1024 * 1024)
+#define VERSATILE_FLASH_SECT_SIZE (256 * 1024)
+
+/* Primary interrupt controller. */
+
+#define TYPE_VERSATILE_PB_SIC "versatilepb_sic"
+#define VERSATILE_PB_SIC(obj) \
+ OBJECT_CHECK(vpb_sic_state, (obj), TYPE_VERSATILE_PB_SIC)
+
+typedef struct vpb_sic_state {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t level;
+ uint32_t mask;
+ uint32_t pic_enable;
+ qemu_irq parent[32];
+ int irq;
+} vpb_sic_state;
+
+static const VMStateDescription vmstate_vpb_sic = {
+ .name = "versatilepb_sic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(level, vpb_sic_state),
+ VMSTATE_UINT32(mask, vpb_sic_state),
+ VMSTATE_UINT32(pic_enable, vpb_sic_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void vpb_sic_update(vpb_sic_state *s)
+{
+ uint32_t flags;
+
+ flags = s->level & s->mask;
+ qemu_set_irq(s->parent[s->irq], flags != 0);
+}
+
+static void vpb_sic_update_pic(vpb_sic_state *s)
+{
+ int i;
+ uint32_t mask;
+
+ for (i = 21; i <= 30; i++) {
+ mask = 1u << i;
+ if (!(s->pic_enable & mask))
+ continue;
+ qemu_set_irq(s->parent[i], (s->level & mask) != 0);
+ }
+}
+
+static void vpb_sic_set_irq(void *opaque, int irq, int level)
+{
+ vpb_sic_state *s = (vpb_sic_state *)opaque;
+ if (level)
+ s->level |= 1u << irq;
+ else
+ s->level &= ~(1u << irq);
+ if (s->pic_enable & (1u << irq))
+ qemu_set_irq(s->parent[irq], level);
+ vpb_sic_update(s);
+}
+
+static uint64_t vpb_sic_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ vpb_sic_state *s = (vpb_sic_state *)opaque;
+
+ switch (offset >> 2) {
+ case 0: /* STATUS */
+ return s->level & s->mask;
+ case 1: /* RAWSTAT */
+ return s->level;
+ case 2: /* ENABLE */
+ return s->mask;
+ case 4: /* SOFTINT */
+ return s->level & 1;
+ case 8: /* PICENABLE */
+ return s->pic_enable;
+ default:
+ printf ("vpb_sic_read: Bad register offset 0x%x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void vpb_sic_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ vpb_sic_state *s = (vpb_sic_state *)opaque;
+
+ switch (offset >> 2) {
+ case 2: /* ENSET */
+ s->mask |= value;
+ break;
+ case 3: /* ENCLR */
+ s->mask &= ~value;
+ break;
+ case 4: /* SOFTINTSET */
+ if (value)
+ s->mask |= 1;
+ break;
+ case 5: /* SOFTINTCLR */
+ if (value)
+ s->mask &= ~1u;
+ break;
+ case 8: /* PICENSET */
+ s->pic_enable |= (value & 0x7fe00000);
+ vpb_sic_update_pic(s);
+ break;
+ case 9: /* PICENCLR */
+ s->pic_enable &= ~value;
+ vpb_sic_update_pic(s);
+ break;
+ default:
+ printf ("vpb_sic_write: Bad register offset 0x%x\n", (int)offset);
+ return;
+ }
+ vpb_sic_update(s);
+}
+
+static const MemoryRegionOps vpb_sic_ops = {
+ .read = vpb_sic_read,
+ .write = vpb_sic_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int vpb_sic_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ vpb_sic_state *s = VERSATILE_PB_SIC(dev);
+ int i;
+
+ qdev_init_gpio_in(dev, vpb_sic_set_irq, 32);
+ for (i = 0; i < 32; i++) {
+ sysbus_init_irq(sbd, &s->parent[i]);
+ }
+ s->irq = 31;
+ memory_region_init_io(&s->iomem, OBJECT(s), &vpb_sic_ops, s,
+ "vpb-sic", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ return 0;
+}
+
+/* Board init. */
+
+/* The AB and PB boards both use the same core, just with different
+ peripherals and expansion busses. For now we emulate a subset of the
+ PB peripherals and just change the board ID. */
+
+static struct arm_boot_info versatile_binfo;
+
+static void versatile_init(MachineState *machine, int board_id)
+{
+ ObjectClass *cpu_oc;
+ Object *cpuobj;
+ ARMCPU *cpu;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ qemu_irq pic[32];
+ qemu_irq sic[32];
+ DeviceState *dev, *sysctl;
+ SysBusDevice *busdev;
+ DeviceState *pl041;
+ PCIBus *pci_bus;
+ NICInfo *nd;
+ I2CBus *i2c;
+ int n;
+ int done_smc = 0;
+ DriveInfo *dinfo;
+ Error *err = NULL;
+
+ if (!machine->cpu_model) {
+ machine->cpu_model = "arm926";
+ }
+
+ cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, machine->cpu_model);
+ if (!cpu_oc) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+
+ cpuobj = object_new(object_class_get_name(cpu_oc));
+
+ /* By default ARM1176 CPUs have EL3 enabled. This board does not
+ * currently support EL3 so the CPU EL3 property is disabled before
+ * realization.
+ */
+ if (object_property_find(cpuobj, "has_el3", NULL)) {
+ object_property_set_bool(cpuobj, false, "has_el3", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ object_property_set_bool(cpuobj, true, "realized", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+
+ cpu = ARM_CPU(cpuobj);
+
+ memory_region_allocate_system_memory(ram, NULL, "versatile.ram",
+ machine->ram_size);
+ /* ??? RAM should repeat to fill physical memory space. */
+ /* SDRAM at address zero. */
+ memory_region_add_subregion(sysmem, 0, ram);
+
+ sysctl = qdev_create(NULL, "realview_sysctl");
+ qdev_prop_set_uint32(sysctl, "sys_id", 0x41007004);
+ qdev_prop_set_uint32(sysctl, "proc_id", 0x02000000);
+ qdev_init_nofail(sysctl);
+ sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, 0x10000000);
+
+ dev = sysbus_create_varargs("pl190", 0x10140000,
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ),
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ),
+ NULL);
+ for (n = 0; n < 32; n++) {
+ pic[n] = qdev_get_gpio_in(dev, n);
+ }
+ dev = sysbus_create_simple(TYPE_VERSATILE_PB_SIC, 0x10003000, NULL);
+ for (n = 0; n < 32; n++) {
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), n, pic[n]);
+ sic[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ sysbus_create_simple("pl050_keyboard", 0x10006000, sic[3]);
+ sysbus_create_simple("pl050_mouse", 0x10007000, sic[4]);
+
+ dev = qdev_create(NULL, "versatile_pci");
+ busdev = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(busdev, 0, 0x10001000); /* PCI controller regs */
+ sysbus_mmio_map(busdev, 1, 0x41000000); /* PCI self-config */
+ sysbus_mmio_map(busdev, 2, 0x42000000); /* PCI config */
+ sysbus_mmio_map(busdev, 3, 0x43000000); /* PCI I/O */
+ sysbus_mmio_map(busdev, 4, 0x44000000); /* PCI memory window 1 */
+ sysbus_mmio_map(busdev, 5, 0x50000000); /* PCI memory window 2 */
+ sysbus_mmio_map(busdev, 6, 0x60000000); /* PCI memory window 3 */
+ sysbus_connect_irq(busdev, 0, sic[27]);
+ sysbus_connect_irq(busdev, 1, sic[28]);
+ sysbus_connect_irq(busdev, 2, sic[29]);
+ sysbus_connect_irq(busdev, 3, sic[30]);
+ pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci");
+
+ for(n = 0; n < nb_nics; n++) {
+ nd = &nd_table[n];
+
+ if (!done_smc && (!nd->model || strcmp(nd->model, "smc91c111") == 0)) {
+ smc91c111_init(nd, 0x10010000, sic[25]);
+ done_smc = 1;
+ } else {
+ pci_nic_init_nofail(nd, pci_bus, "rtl8139", NULL);
+ }
+ }
+ if (usb_enabled()) {
+ pci_create_simple(pci_bus, -1, "pci-ohci");
+ }
+ n = drive_get_max_bus(IF_SCSI);
+ while (n >= 0) {
+ pci_create_simple(pci_bus, -1, "lsi53c895a");
+ n--;
+ }
+
+ sysbus_create_simple("pl011", 0x101f1000, pic[12]);
+ sysbus_create_simple("pl011", 0x101f2000, pic[13]);
+ sysbus_create_simple("pl011", 0x101f3000, pic[14]);
+ sysbus_create_simple("pl011", 0x10009000, sic[6]);
+
+ sysbus_create_simple("pl080", 0x10130000, pic[17]);
+ sysbus_create_simple("sp804", 0x101e2000, pic[4]);
+ sysbus_create_simple("sp804", 0x101e3000, pic[5]);
+
+ sysbus_create_simple("pl061", 0x101e4000, pic[6]);
+ sysbus_create_simple("pl061", 0x101e5000, pic[7]);
+ sysbus_create_simple("pl061", 0x101e6000, pic[8]);
+ sysbus_create_simple("pl061", 0x101e7000, pic[9]);
+
+ /* The versatile/PB actually has a modified Color LCD controller
+ that includes hardware cursor support from the PL111. */
+ dev = sysbus_create_simple("pl110_versatile", 0x10120000, pic[16]);
+ /* Wire up the mux control signals from the SYS_CLCD register */
+ qdev_connect_gpio_out(sysctl, 0, qdev_get_gpio_in(dev, 0));
+
+ sysbus_create_varargs("pl181", 0x10005000, sic[22], sic[1], NULL);
+ sysbus_create_varargs("pl181", 0x1000b000, sic[23], sic[2], NULL);
+
+ /* Add PL031 Real Time Clock. */
+ sysbus_create_simple("pl031", 0x101e8000, pic[10]);
+
+ dev = sysbus_create_simple("versatile_i2c", 0x10002000, NULL);
+ i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
+ i2c_create_slave(i2c, "ds1338", 0x68);
+
+ /* Add PL041 AACI Interface to the LM4549 codec */
+ pl041 = qdev_create(NULL, "pl041");
+ qdev_prop_set_uint32(pl041, "nc_fifo_depth", 512);
+ qdev_init_nofail(pl041);
+ sysbus_mmio_map(SYS_BUS_DEVICE(pl041), 0, 0x10004000);
+ sysbus_connect_irq(SYS_BUS_DEVICE(pl041), 0, sic[24]);
+
+ /* Memory map for Versatile/PB: */
+ /* 0x10000000 System registers. */
+ /* 0x10001000 PCI controller config registers. */
+ /* 0x10002000 Serial bus interface. */
+ /* 0x10003000 Secondary interrupt controller. */
+ /* 0x10004000 AACI (audio). */
+ /* 0x10005000 MMCI0. */
+ /* 0x10006000 KMI0 (keyboard). */
+ /* 0x10007000 KMI1 (mouse). */
+ /* 0x10008000 Character LCD Interface. */
+ /* 0x10009000 UART3. */
+ /* 0x1000a000 Smart card 1. */
+ /* 0x1000b000 MMCI1. */
+ /* 0x10010000 Ethernet. */
+ /* 0x10020000 USB. */
+ /* 0x10100000 SSMC. */
+ /* 0x10110000 MPMC. */
+ /* 0x10120000 CLCD Controller. */
+ /* 0x10130000 DMA Controller. */
+ /* 0x10140000 Vectored interrupt controller. */
+ /* 0x101d0000 AHB Monitor Interface. */
+ /* 0x101e0000 System Controller. */
+ /* 0x101e1000 Watchdog Interface. */
+ /* 0x101e2000 Timer 0/1. */
+ /* 0x101e3000 Timer 2/3. */
+ /* 0x101e4000 GPIO port 0. */
+ /* 0x101e5000 GPIO port 1. */
+ /* 0x101e6000 GPIO port 2. */
+ /* 0x101e7000 GPIO port 3. */
+ /* 0x101e8000 RTC. */
+ /* 0x101f0000 Smart card 0. */
+ /* 0x101f1000 UART0. */
+ /* 0x101f2000 UART1. */
+ /* 0x101f3000 UART2. */
+ /* 0x101f4000 SSPI. */
+ /* 0x34000000 NOR Flash */
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ if (!pflash_cfi01_register(VERSATILE_FLASH_ADDR, NULL, "versatile.flash",
+ VERSATILE_FLASH_SIZE,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ VERSATILE_FLASH_SECT_SIZE,
+ VERSATILE_FLASH_SIZE / VERSATILE_FLASH_SECT_SIZE,
+ 4, 0x0089, 0x0018, 0x0000, 0x0, 0)) {
+ fprintf(stderr, "qemu: Error registering flash memory.\n");
+ }
+
+ versatile_binfo.ram_size = machine->ram_size;
+ versatile_binfo.kernel_filename = machine->kernel_filename;
+ versatile_binfo.kernel_cmdline = machine->kernel_cmdline;
+ versatile_binfo.initrd_filename = machine->initrd_filename;
+ versatile_binfo.board_id = board_id;
+ arm_load_kernel(cpu, &versatile_binfo);
+}
+
+static void vpb_init(MachineState *machine)
+{
+ versatile_init(machine, 0x183);
+}
+
+static void vab_init(MachineState *machine)
+{
+ versatile_init(machine, 0x25e);
+}
+
+static QEMUMachine versatilepb_machine = {
+ .name = "versatilepb",
+ .desc = "ARM Versatile/PB (ARM926EJ-S)",
+ .init = vpb_init,
+ .block_default_type = IF_SCSI,
+};
+
+static QEMUMachine versatileab_machine = {
+ .name = "versatileab",
+ .desc = "ARM Versatile/AB (ARM926EJ-S)",
+ .init = vab_init,
+ .block_default_type = IF_SCSI,
+};
+
+static void versatile_machine_init(void)
+{
+ qemu_register_machine(&versatilepb_machine);
+ qemu_register_machine(&versatileab_machine);
+}
+
+machine_init(versatile_machine_init);
+
+static void vpb_sic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = vpb_sic_init;
+ dc->vmsd = &vmstate_vpb_sic;
+}
+
+static const TypeInfo vpb_sic_info = {
+ .name = TYPE_VERSATILE_PB_SIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(vpb_sic_state),
+ .class_init = vpb_sic_class_init,
+};
+
+static void versatilepb_register_types(void)
+{
+ type_register_static(&vpb_sic_info);
+}
+
+type_init(versatilepb_register_types)
diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c
new file mode 100644
index 00000000..da217884
--- /dev/null
+++ b/hw/arm/vexpress.c
@@ -0,0 +1,808 @@
+/*
+ * ARM Versatile Express emulation.
+ *
+ * Copyright (c) 2010 - 2011 B Labs Ltd.
+ * Copyright (c) 2011 Linaro Limited
+ * Written by Bahadir Balban, Amit Mahajan, Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/arm/primecell.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "exec/address-spaces.h"
+#include "sysemu/block-backend.h"
+#include "hw/block/flash.h"
+#include "sysemu/device_tree.h"
+#include "qemu/error-report.h"
+#include <libfdt.h>
+
+#define VEXPRESS_BOARD_ID 0x8e0
+#define VEXPRESS_FLASH_SIZE (64 * 1024 * 1024)
+#define VEXPRESS_FLASH_SECT_SIZE (256 * 1024)
+
+/* Number of virtio transports to create (0..8; limited by
+ * number of available IRQ lines).
+ */
+#define NUM_VIRTIO_TRANSPORTS 4
+
+/* Address maps for peripherals:
+ * the Versatile Express motherboard has two possible maps,
+ * the "legacy" one (used for A9) and the "Cortex-A Series"
+ * map (used for newer cores).
+ * Individual daughterboards can also have different maps for
+ * their peripherals.
+ */
+
+enum {
+ VE_SYSREGS,
+ VE_SP810,
+ VE_SERIALPCI,
+ VE_PL041,
+ VE_MMCI,
+ VE_KMI0,
+ VE_KMI1,
+ VE_UART0,
+ VE_UART1,
+ VE_UART2,
+ VE_UART3,
+ VE_WDT,
+ VE_TIMER01,
+ VE_TIMER23,
+ VE_SERIALDVI,
+ VE_RTC,
+ VE_COMPACTFLASH,
+ VE_CLCD,
+ VE_NORFLASH0,
+ VE_NORFLASH1,
+ VE_NORFLASHALIAS,
+ VE_SRAM,
+ VE_VIDEORAM,
+ VE_ETHERNET,
+ VE_USB,
+ VE_DAPROM,
+ VE_VIRTIO,
+};
+
+static hwaddr motherboard_legacy_map[] = {
+ [VE_NORFLASHALIAS] = 0,
+ /* CS7: 0x10000000 .. 0x10020000 */
+ [VE_SYSREGS] = 0x10000000,
+ [VE_SP810] = 0x10001000,
+ [VE_SERIALPCI] = 0x10002000,
+ [VE_PL041] = 0x10004000,
+ [VE_MMCI] = 0x10005000,
+ [VE_KMI0] = 0x10006000,
+ [VE_KMI1] = 0x10007000,
+ [VE_UART0] = 0x10009000,
+ [VE_UART1] = 0x1000a000,
+ [VE_UART2] = 0x1000b000,
+ [VE_UART3] = 0x1000c000,
+ [VE_WDT] = 0x1000f000,
+ [VE_TIMER01] = 0x10011000,
+ [VE_TIMER23] = 0x10012000,
+ [VE_VIRTIO] = 0x10013000,
+ [VE_SERIALDVI] = 0x10016000,
+ [VE_RTC] = 0x10017000,
+ [VE_COMPACTFLASH] = 0x1001a000,
+ [VE_CLCD] = 0x1001f000,
+ /* CS0: 0x40000000 .. 0x44000000 */
+ [VE_NORFLASH0] = 0x40000000,
+ /* CS1: 0x44000000 .. 0x48000000 */
+ [VE_NORFLASH1] = 0x44000000,
+ /* CS2: 0x48000000 .. 0x4a000000 */
+ [VE_SRAM] = 0x48000000,
+ /* CS3: 0x4c000000 .. 0x50000000 */
+ [VE_VIDEORAM] = 0x4c000000,
+ [VE_ETHERNET] = 0x4e000000,
+ [VE_USB] = 0x4f000000,
+};
+
+static hwaddr motherboard_aseries_map[] = {
+ [VE_NORFLASHALIAS] = 0,
+ /* CS0: 0x08000000 .. 0x0c000000 */
+ [VE_NORFLASH0] = 0x08000000,
+ /* CS4: 0x0c000000 .. 0x10000000 */
+ [VE_NORFLASH1] = 0x0c000000,
+ /* CS5: 0x10000000 .. 0x14000000 */
+ /* CS1: 0x14000000 .. 0x18000000 */
+ [VE_SRAM] = 0x14000000,
+ /* CS2: 0x18000000 .. 0x1c000000 */
+ [VE_VIDEORAM] = 0x18000000,
+ [VE_ETHERNET] = 0x1a000000,
+ [VE_USB] = 0x1b000000,
+ /* CS3: 0x1c000000 .. 0x20000000 */
+ [VE_DAPROM] = 0x1c000000,
+ [VE_SYSREGS] = 0x1c010000,
+ [VE_SP810] = 0x1c020000,
+ [VE_SERIALPCI] = 0x1c030000,
+ [VE_PL041] = 0x1c040000,
+ [VE_MMCI] = 0x1c050000,
+ [VE_KMI0] = 0x1c060000,
+ [VE_KMI1] = 0x1c070000,
+ [VE_UART0] = 0x1c090000,
+ [VE_UART1] = 0x1c0a0000,
+ [VE_UART2] = 0x1c0b0000,
+ [VE_UART3] = 0x1c0c0000,
+ [VE_WDT] = 0x1c0f0000,
+ [VE_TIMER01] = 0x1c110000,
+ [VE_TIMER23] = 0x1c120000,
+ [VE_VIRTIO] = 0x1c130000,
+ [VE_SERIALDVI] = 0x1c160000,
+ [VE_RTC] = 0x1c170000,
+ [VE_COMPACTFLASH] = 0x1c1a0000,
+ [VE_CLCD] = 0x1c1f0000,
+};
+
+/* Structure defining the peculiarities of a specific daughterboard */
+
+typedef struct VEDBoardInfo VEDBoardInfo;
+
+typedef struct {
+ MachineClass parent;
+ VEDBoardInfo *daughterboard;
+} VexpressMachineClass;
+
+typedef struct {
+ MachineState parent;
+ bool secure;
+} VexpressMachineState;
+
+#define TYPE_VEXPRESS_MACHINE "vexpress"
+#define TYPE_VEXPRESS_A9_MACHINE "vexpress-a9"
+#define TYPE_VEXPRESS_A15_MACHINE "vexpress-a15"
+#define VEXPRESS_MACHINE(obj) \
+ OBJECT_CHECK(VexpressMachineState, (obj), TYPE_VEXPRESS_MACHINE)
+#define VEXPRESS_MACHINE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VexpressMachineClass, obj, TYPE_VEXPRESS_MACHINE)
+#define VEXPRESS_MACHINE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VexpressMachineClass, klass, TYPE_VEXPRESS_MACHINE)
+
+typedef void DBoardInitFn(const VexpressMachineState *machine,
+ ram_addr_t ram_size,
+ const char *cpu_model,
+ qemu_irq *pic);
+
+struct VEDBoardInfo {
+ struct arm_boot_info bootinfo;
+ const hwaddr *motherboard_map;
+ hwaddr loader_start;
+ const hwaddr gic_cpu_if_addr;
+ uint32_t proc_id;
+ uint32_t num_voltage_sensors;
+ const uint32_t *voltages;
+ uint32_t num_clocks;
+ const uint32_t *clocks;
+ DBoardInitFn *init;
+};
+
+static void init_cpus(const char *cpu_model, const char *privdev,
+ hwaddr periphbase, qemu_irq *pic, bool secure)
+{
+ ObjectClass *cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model);
+ DeviceState *dev;
+ SysBusDevice *busdev;
+ int n;
+
+ if (!cpu_oc) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+
+ /* Create the actual CPUs */
+ for (n = 0; n < smp_cpus; n++) {
+ Object *cpuobj = object_new(object_class_get_name(cpu_oc));
+ Error *err = NULL;
+
+ if (!secure) {
+ object_property_set_bool(cpuobj, false, "has_el3", NULL);
+ }
+
+ if (object_property_find(cpuobj, "reset-cbar", NULL)) {
+ object_property_set_int(cpuobj, periphbase,
+ "reset-cbar", &error_abort);
+ }
+ object_property_set_bool(cpuobj, true, "realized", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ /* Create the private peripheral devices (including the GIC);
+ * this must happen after the CPUs are created because a15mpcore_priv
+ * wires itself up to the CPU's generic_timer gpio out lines.
+ */
+ dev = qdev_create(NULL, privdev);
+ qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, periphbase);
+
+ /* Interrupts [42:0] are from the motherboard;
+ * [47:43] are reserved; [63:48] are daughterboard
+ * peripherals. Note that some documentation numbers
+ * external interrupts starting from 32 (because there
+ * are internal interrupts 0..31).
+ */
+ for (n = 0; n < 64; n++) {
+ pic[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ /* Connect the CPUs to the GIC */
+ for (n = 0; n < smp_cpus; n++) {
+ DeviceState *cpudev = DEVICE(qemu_get_cpu(n));
+
+ sysbus_connect_irq(busdev, n, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
+ sysbus_connect_irq(busdev, n + smp_cpus,
+ qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
+ }
+}
+
+static void a9_daughterboard_init(const VexpressMachineState *vms,
+ ram_addr_t ram_size,
+ const char *cpu_model,
+ qemu_irq *pic)
+{
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *lowram = g_new(MemoryRegion, 1);
+ ram_addr_t low_ram_size;
+
+ if (!cpu_model) {
+ cpu_model = "cortex-a9";
+ }
+
+ if (ram_size > 0x40000000) {
+ /* 1GB is the maximum the address space permits */
+ fprintf(stderr, "vexpress-a9: cannot model more than 1GB RAM\n");
+ exit(1);
+ }
+
+ memory_region_allocate_system_memory(ram, NULL, "vexpress.highmem",
+ ram_size);
+ low_ram_size = ram_size;
+ if (low_ram_size > 0x4000000) {
+ low_ram_size = 0x4000000;
+ }
+ /* RAM is from 0x60000000 upwards. The bottom 64MB of the
+ * address space should in theory be remappable to various
+ * things including ROM or RAM; we always map the RAM there.
+ */
+ memory_region_init_alias(lowram, NULL, "vexpress.lowmem", ram, 0, low_ram_size);
+ memory_region_add_subregion(sysmem, 0x0, lowram);
+ memory_region_add_subregion(sysmem, 0x60000000, ram);
+
+ /* 0x1e000000 A9MPCore (SCU) private memory region */
+ init_cpus(cpu_model, "a9mpcore_priv", 0x1e000000, pic, vms->secure);
+
+ /* Daughterboard peripherals : 0x10020000 .. 0x20000000 */
+
+ /* 0x10020000 PL111 CLCD (daughterboard) */
+ sysbus_create_simple("pl111", 0x10020000, pic[44]);
+
+ /* 0x10060000 AXI RAM */
+ /* 0x100e0000 PL341 Dynamic Memory Controller */
+ /* 0x100e1000 PL354 Static Memory Controller */
+ /* 0x100e2000 System Configuration Controller */
+
+ sysbus_create_simple("sp804", 0x100e4000, pic[48]);
+ /* 0x100e5000 SP805 Watchdog module */
+ /* 0x100e6000 BP147 TrustZone Protection Controller */
+ /* 0x100e9000 PL301 'Fast' AXI matrix */
+ /* 0x100ea000 PL301 'Slow' AXI matrix */
+ /* 0x100ec000 TrustZone Address Space Controller */
+ /* 0x10200000 CoreSight debug APB */
+ /* 0x1e00a000 PL310 L2 Cache Controller */
+ sysbus_create_varargs("l2x0", 0x1e00a000, NULL);
+}
+
+/* Voltage values for SYS_CFG_VOLT daughterboard registers;
+ * values are in microvolts.
+ */
+static const uint32_t a9_voltages[] = {
+ 1000000, /* VD10 : 1.0V : SoC internal logic voltage */
+ 1000000, /* VD10_S2 : 1.0V : PL310, L2 cache, RAM, non-PL310 logic */
+ 1000000, /* VD10_S3 : 1.0V : Cortex-A9, cores, MPEs, SCU, PL310 logic */
+ 1800000, /* VCC1V8 : 1.8V : DDR2 SDRAM, test chip DDR2 I/O supply */
+ 900000, /* DDR2VTT : 0.9V : DDR2 SDRAM VTT termination voltage */
+ 3300000, /* VCC3V3 : 3.3V : local board supply for misc external logic */
+};
+
+/* Reset values for daughterboard oscillators (in Hz) */
+static const uint32_t a9_clocks[] = {
+ 45000000, /* AMBA AXI ACLK: 45MHz */
+ 23750000, /* daughterboard CLCD clock: 23.75MHz */
+ 66670000, /* Test chip reference clock: 66.67MHz */
+};
+
+static VEDBoardInfo a9_daughterboard = {
+ .motherboard_map = motherboard_legacy_map,
+ .loader_start = 0x60000000,
+ .gic_cpu_if_addr = 0x1e000100,
+ .proc_id = 0x0c000191,
+ .num_voltage_sensors = ARRAY_SIZE(a9_voltages),
+ .voltages = a9_voltages,
+ .num_clocks = ARRAY_SIZE(a9_clocks),
+ .clocks = a9_clocks,
+ .init = a9_daughterboard_init,
+};
+
+static void a15_daughterboard_init(const VexpressMachineState *vms,
+ ram_addr_t ram_size,
+ const char *cpu_model,
+ qemu_irq *pic)
+{
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+
+ if (!cpu_model) {
+ cpu_model = "cortex-a15";
+ }
+
+ {
+ /* We have to use a separate 64 bit variable here to avoid the gcc
+ * "comparison is always false due to limited range of data type"
+ * warning if we are on a host where ram_addr_t is 32 bits.
+ */
+ uint64_t rsz = ram_size;
+ if (rsz > (30ULL * 1024 * 1024 * 1024)) {
+ fprintf(stderr, "vexpress-a15: cannot model more than 30GB RAM\n");
+ exit(1);
+ }
+ }
+
+ memory_region_allocate_system_memory(ram, NULL, "vexpress.highmem",
+ ram_size);
+ /* RAM is from 0x80000000 upwards; there is no low-memory alias for it. */
+ memory_region_add_subregion(sysmem, 0x80000000, ram);
+
+ /* 0x2c000000 A15MPCore private memory region (GIC) */
+ init_cpus(cpu_model, "a15mpcore_priv", 0x2c000000, pic, vms->secure);
+
+ /* A15 daughterboard peripherals: */
+
+ /* 0x20000000: CoreSight interfaces: not modelled */
+ /* 0x2a000000: PL301 AXI interconnect: not modelled */
+ /* 0x2a420000: SCC: not modelled */
+ /* 0x2a430000: system counter: not modelled */
+ /* 0x2b000000: HDLCD controller: not modelled */
+ /* 0x2b060000: SP805 watchdog: not modelled */
+ /* 0x2b0a0000: PL341 dynamic memory controller: not modelled */
+ /* 0x2e000000: system SRAM */
+ memory_region_init_ram(sram, NULL, "vexpress.a15sram", 0x10000,
+ &error_abort);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(sysmem, 0x2e000000, sram);
+
+ /* 0x7ffb0000: DMA330 DMA controller: not modelled */
+ /* 0x7ffd0000: PL354 static memory controller: not modelled */
+}
+
+static const uint32_t a15_voltages[] = {
+ 900000, /* Vcore: 0.9V : CPU core voltage */
+};
+
+static const uint32_t a15_clocks[] = {
+ 60000000, /* OSCCLK0: 60MHz : CPU_CLK reference */
+ 0, /* OSCCLK1: reserved */
+ 0, /* OSCCLK2: reserved */
+ 0, /* OSCCLK3: reserved */
+ 40000000, /* OSCCLK4: 40MHz : external AXI master clock */
+ 23750000, /* OSCCLK5: 23.75MHz : HDLCD PLL reference */
+ 50000000, /* OSCCLK6: 50MHz : static memory controller clock */
+ 60000000, /* OSCCLK7: 60MHz : SYSCLK reference */
+ 40000000, /* OSCCLK8: 40MHz : DDR2 PLL reference */
+};
+
+static VEDBoardInfo a15_daughterboard = {
+ .motherboard_map = motherboard_aseries_map,
+ .loader_start = 0x80000000,
+ .gic_cpu_if_addr = 0x2c002000,
+ .proc_id = 0x14000237,
+ .num_voltage_sensors = ARRAY_SIZE(a15_voltages),
+ .voltages = a15_voltages,
+ .num_clocks = ARRAY_SIZE(a15_clocks),
+ .clocks = a15_clocks,
+ .init = a15_daughterboard_init,
+};
+
+static int add_virtio_mmio_node(void *fdt, uint32_t acells, uint32_t scells,
+ hwaddr addr, hwaddr size, uint32_t intc,
+ int irq)
+{
+ /* Add a virtio_mmio node to the device tree blob:
+ * virtio_mmio@ADDRESS {
+ * compatible = "virtio,mmio";
+ * reg = <ADDRESS, SIZE>;
+ * interrupt-parent = <&intc>;
+ * interrupts = <0, irq, 1>;
+ * }
+ * (Note that the format of the interrupts property is dependent on the
+ * interrupt controller that interrupt-parent points to; these are for
+ * the ARM GIC and indicate an SPI interrupt, rising-edge-triggered.)
+ */
+ int rc;
+ char *nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, addr);
+
+ rc = qemu_fdt_add_subnode(fdt, nodename);
+ rc |= qemu_fdt_setprop_string(fdt, nodename,
+ "compatible", "virtio,mmio");
+ rc |= qemu_fdt_setprop_sized_cells(fdt, nodename, "reg",
+ acells, addr, scells, size);
+ qemu_fdt_setprop_cells(fdt, nodename, "interrupt-parent", intc);
+ qemu_fdt_setprop_cells(fdt, nodename, "interrupts", 0, irq, 1);
+ g_free(nodename);
+ if (rc) {
+ return -1;
+ }
+ return 0;
+}
+
+static uint32_t find_int_controller(void *fdt)
+{
+ /* Find the FDT node corresponding to the interrupt controller
+ * for virtio-mmio devices. We do this by scanning the fdt for
+ * a node with the right compatibility, since we know there is
+ * only one GIC on a vexpress board.
+ * We return the phandle of the node, or 0 if none was found.
+ */
+ const char *compat = "arm,cortex-a9-gic";
+ int offset;
+
+ offset = fdt_node_offset_by_compatible(fdt, -1, compat);
+ if (offset >= 0) {
+ return fdt_get_phandle(fdt, offset);
+ }
+ return 0;
+}
+
+static void vexpress_modify_dtb(const struct arm_boot_info *info, void *fdt)
+{
+ uint32_t acells, scells, intc;
+ const VEDBoardInfo *daughterboard = (const VEDBoardInfo *)info;
+
+ acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells");
+ scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells");
+ intc = find_int_controller(fdt);
+ if (!intc) {
+ /* Not fatal, we just won't provide virtio. This will
+ * happen with older device tree blobs.
+ */
+ fprintf(stderr, "QEMU: warning: couldn't find interrupt controller in "
+ "dtb; will not include virtio-mmio devices in the dtb.\n");
+ } else {
+ int i;
+ const hwaddr *map = daughterboard->motherboard_map;
+
+ /* We iterate backwards here because adding nodes
+ * to the dtb puts them in last-first.
+ */
+ for (i = NUM_VIRTIO_TRANSPORTS - 1; i >= 0; i--) {
+ add_virtio_mmio_node(fdt, acells, scells,
+ map[VE_VIRTIO] + 0x200 * i,
+ 0x200, intc, 40 + i);
+ }
+ }
+}
+
+
+/* Open code a private version of pflash registration since we
+ * need to set non-default device width for VExpress platform.
+ */
+static pflash_t *ve_pflash_cfi01_register(hwaddr base, const char *name,
+ DriveInfo *di)
+{
+ DeviceState *dev = qdev_create(NULL, "cfi.pflash01");
+
+ if (di) {
+ qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(di),
+ &error_abort);
+ }
+
+ qdev_prop_set_uint32(dev, "num-blocks",
+ VEXPRESS_FLASH_SIZE / VEXPRESS_FLASH_SECT_SIZE);
+ qdev_prop_set_uint64(dev, "sector-length", VEXPRESS_FLASH_SECT_SIZE);
+ qdev_prop_set_uint8(dev, "width", 4);
+ qdev_prop_set_uint8(dev, "device-width", 2);
+ qdev_prop_set_bit(dev, "big-endian", false);
+ qdev_prop_set_uint16(dev, "id0", 0x89);
+ qdev_prop_set_uint16(dev, "id1", 0x18);
+ qdev_prop_set_uint16(dev, "id2", 0x00);
+ qdev_prop_set_uint16(dev, "id3", 0x00);
+ qdev_prop_set_string(dev, "name", name);
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ return OBJECT_CHECK(pflash_t, (dev), "cfi.pflash01");
+}
+
+static void vexpress_common_init(MachineState *machine)
+{
+ VexpressMachineState *vms = VEXPRESS_MACHINE(machine);
+ VexpressMachineClass *vmc = VEXPRESS_MACHINE_GET_CLASS(machine);
+ VEDBoardInfo *daughterboard = vmc->daughterboard;;
+ DeviceState *dev, *sysctl, *pl041;
+ qemu_irq pic[64];
+ uint32_t sys_id;
+ DriveInfo *dinfo;
+ pflash_t *pflash0;
+ ram_addr_t vram_size, sram_size;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *vram = g_new(MemoryRegion, 1);
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+ MemoryRegion *flashalias = g_new(MemoryRegion, 1);
+ MemoryRegion *flash0mem;
+ const hwaddr *map = daughterboard->motherboard_map;
+ int i;
+
+ daughterboard->init(vms, machine->ram_size, machine->cpu_model, pic);
+
+ /*
+ * If a bios file was provided, attempt to map it into memory
+ */
+ if (bios_name) {
+ char *fn;
+ int image_size;
+
+ if (drive_get(IF_PFLASH, 0, 0)) {
+ error_report("The contents of the first flash device may be "
+ "specified with -bios or with -drive if=pflash... "
+ "but you cannot use both options at once");
+ exit(1);
+ }
+ fn = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (!fn) {
+ error_report("Could not find ROM image '%s'", bios_name);
+ exit(1);
+ }
+ image_size = load_image_targphys(fn, map[VE_NORFLASH0],
+ VEXPRESS_FLASH_SIZE);
+ g_free(fn);
+ if (image_size < 0) {
+ error_report("Could not load ROM image '%s'", bios_name);
+ exit(1);
+ }
+ }
+
+ /* Motherboard peripherals: the wiring is the same but the
+ * addresses vary between the legacy and A-Series memory maps.
+ */
+
+ sys_id = 0x1190f500;
+
+ sysctl = qdev_create(NULL, "realview_sysctl");
+ qdev_prop_set_uint32(sysctl, "sys_id", sys_id);
+ qdev_prop_set_uint32(sysctl, "proc_id", daughterboard->proc_id);
+ qdev_prop_set_uint32(sysctl, "len-db-voltage",
+ daughterboard->num_voltage_sensors);
+ for (i = 0; i < daughterboard->num_voltage_sensors; i++) {
+ char *propname = g_strdup_printf("db-voltage[%d]", i);
+ qdev_prop_set_uint32(sysctl, propname, daughterboard->voltages[i]);
+ g_free(propname);
+ }
+ qdev_prop_set_uint32(sysctl, "len-db-clock",
+ daughterboard->num_clocks);
+ for (i = 0; i < daughterboard->num_clocks; i++) {
+ char *propname = g_strdup_printf("db-clock[%d]", i);
+ qdev_prop_set_uint32(sysctl, propname, daughterboard->clocks[i]);
+ g_free(propname);
+ }
+ qdev_init_nofail(sysctl);
+ sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, map[VE_SYSREGS]);
+
+ /* VE_SP810: not modelled */
+ /* VE_SERIALPCI: not modelled */
+
+ pl041 = qdev_create(NULL, "pl041");
+ qdev_prop_set_uint32(pl041, "nc_fifo_depth", 512);
+ qdev_init_nofail(pl041);
+ sysbus_mmio_map(SYS_BUS_DEVICE(pl041), 0, map[VE_PL041]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(pl041), 0, pic[11]);
+
+ dev = sysbus_create_varargs("pl181", map[VE_MMCI], pic[9], pic[10], NULL);
+ /* Wire up MMC card detect and read-only signals */
+ qdev_connect_gpio_out(dev, 0,
+ qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_WPROT));
+ qdev_connect_gpio_out(dev, 1,
+ qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_CARDIN));
+
+ sysbus_create_simple("pl050_keyboard", map[VE_KMI0], pic[12]);
+ sysbus_create_simple("pl050_mouse", map[VE_KMI1], pic[13]);
+
+ sysbus_create_simple("pl011", map[VE_UART0], pic[5]);
+ sysbus_create_simple("pl011", map[VE_UART1], pic[6]);
+ sysbus_create_simple("pl011", map[VE_UART2], pic[7]);
+ sysbus_create_simple("pl011", map[VE_UART3], pic[8]);
+
+ sysbus_create_simple("sp804", map[VE_TIMER01], pic[2]);
+ sysbus_create_simple("sp804", map[VE_TIMER23], pic[3]);
+
+ /* VE_SERIALDVI: not modelled */
+
+ sysbus_create_simple("pl031", map[VE_RTC], pic[4]); /* RTC */
+
+ /* VE_COMPACTFLASH: not modelled */
+
+ sysbus_create_simple("pl111", map[VE_CLCD], pic[14]);
+
+ dinfo = drive_get_next(IF_PFLASH);
+ pflash0 = ve_pflash_cfi01_register(map[VE_NORFLASH0], "vexpress.flash0",
+ dinfo);
+ if (!pflash0) {
+ fprintf(stderr, "vexpress: error registering flash 0.\n");
+ exit(1);
+ }
+
+ if (map[VE_NORFLASHALIAS] != -1) {
+ /* Map flash 0 as an alias into low memory */
+ flash0mem = sysbus_mmio_get_region(SYS_BUS_DEVICE(pflash0), 0);
+ memory_region_init_alias(flashalias, NULL, "vexpress.flashalias",
+ flash0mem, 0, VEXPRESS_FLASH_SIZE);
+ memory_region_add_subregion(sysmem, map[VE_NORFLASHALIAS], flashalias);
+ }
+
+ dinfo = drive_get_next(IF_PFLASH);
+ if (!ve_pflash_cfi01_register(map[VE_NORFLASH1], "vexpress.flash1",
+ dinfo)) {
+ fprintf(stderr, "vexpress: error registering flash 1.\n");
+ exit(1);
+ }
+
+ sram_size = 0x2000000;
+ memory_region_init_ram(sram, NULL, "vexpress.sram", sram_size,
+ &error_abort);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(sysmem, map[VE_SRAM], sram);
+
+ vram_size = 0x800000;
+ memory_region_init_ram(vram, NULL, "vexpress.vram", vram_size,
+ &error_abort);
+ vmstate_register_ram_global(vram);
+ memory_region_add_subregion(sysmem, map[VE_VIDEORAM], vram);
+
+ /* 0x4e000000 LAN9118 Ethernet */
+ if (nd_table[0].used) {
+ lan9118_init(&nd_table[0], map[VE_ETHERNET], pic[15]);
+ }
+
+ /* VE_USB: not modelled */
+
+ /* VE_DAPROM: not modelled */
+
+ /* Create mmio transports, so the user can create virtio backends
+ * (which will be automatically plugged in to the transports). If
+ * no backend is created the transport will just sit harmlessly idle.
+ */
+ for (i = 0; i < NUM_VIRTIO_TRANSPORTS; i++) {
+ sysbus_create_simple("virtio-mmio", map[VE_VIRTIO] + 0x200 * i,
+ pic[40 + i]);
+ }
+
+ daughterboard->bootinfo.ram_size = machine->ram_size;
+ daughterboard->bootinfo.kernel_filename = machine->kernel_filename;
+ daughterboard->bootinfo.kernel_cmdline = machine->kernel_cmdline;
+ daughterboard->bootinfo.initrd_filename = machine->initrd_filename;
+ daughterboard->bootinfo.nb_cpus = smp_cpus;
+ daughterboard->bootinfo.board_id = VEXPRESS_BOARD_ID;
+ daughterboard->bootinfo.loader_start = daughterboard->loader_start;
+ daughterboard->bootinfo.smp_loader_start = map[VE_SRAM];
+ daughterboard->bootinfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30;
+ daughterboard->bootinfo.gic_cpu_if_addr = daughterboard->gic_cpu_if_addr;
+ daughterboard->bootinfo.modify_dtb = vexpress_modify_dtb;
+ /* Indicate that when booting Linux we should be in secure state */
+ daughterboard->bootinfo.secure_boot = true;
+ arm_load_kernel(ARM_CPU(first_cpu), &daughterboard->bootinfo);
+}
+
+static bool vexpress_get_secure(Object *obj, Error **errp)
+{
+ VexpressMachineState *vms = VEXPRESS_MACHINE(obj);
+
+ return vms->secure;
+}
+
+static void vexpress_set_secure(Object *obj, bool value, Error **errp)
+{
+ VexpressMachineState *vms = VEXPRESS_MACHINE(obj);
+
+ vms->secure = value;
+}
+
+static void vexpress_instance_init(Object *obj)
+{
+ VexpressMachineState *vms = VEXPRESS_MACHINE(obj);
+
+ /* EL3 is enabled by default on vexpress */
+ vms->secure = true;
+ object_property_add_bool(obj, "secure", vexpress_get_secure,
+ vexpress_set_secure, NULL);
+ object_property_set_description(obj, "secure",
+ "Set on/off to enable/disable the ARM "
+ "Security Extensions (TrustZone)",
+ NULL);
+}
+
+static void vexpress_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->name = TYPE_VEXPRESS_MACHINE;
+ mc->desc = "ARM Versatile Express";
+ mc->init = vexpress_common_init;
+ mc->block_default_type = IF_SCSI;
+ mc->max_cpus = 4;
+}
+
+static void vexpress_a9_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ VexpressMachineClass *vmc = VEXPRESS_MACHINE_CLASS(oc);
+
+ mc->name = TYPE_VEXPRESS_A9_MACHINE;
+ mc->desc = "ARM Versatile Express for Cortex-A9";
+
+ vmc->daughterboard = &a9_daughterboard;;
+}
+
+static void vexpress_a15_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ VexpressMachineClass *vmc = VEXPRESS_MACHINE_CLASS(oc);
+
+ mc->name = TYPE_VEXPRESS_A15_MACHINE;
+ mc->desc = "ARM Versatile Express for Cortex-A15";
+
+ vmc->daughterboard = &a15_daughterboard;
+}
+
+static const TypeInfo vexpress_info = {
+ .name = TYPE_VEXPRESS_MACHINE,
+ .parent = TYPE_MACHINE,
+ .abstract = true,
+ .instance_size = sizeof(VexpressMachineState),
+ .instance_init = vexpress_instance_init,
+ .class_size = sizeof(VexpressMachineClass),
+ .class_init = vexpress_class_init,
+};
+
+static const TypeInfo vexpress_a9_info = {
+ .name = TYPE_VEXPRESS_A9_MACHINE,
+ .parent = TYPE_VEXPRESS_MACHINE,
+ .class_init = vexpress_a9_class_init,
+};
+
+static const TypeInfo vexpress_a15_info = {
+ .name = TYPE_VEXPRESS_A15_MACHINE,
+ .parent = TYPE_VEXPRESS_MACHINE,
+ .class_init = vexpress_a15_class_init,
+};
+
+static void vexpress_machine_init(void)
+{
+ type_register_static(&vexpress_info);
+ type_register_static(&vexpress_a9_info);
+ type_register_static(&vexpress_a15_info);
+}
+
+machine_init(vexpress_machine_init);
diff --git a/hw/arm/virt-acpi-build.c b/hw/arm/virt-acpi-build.c
new file mode 100644
index 00000000..f3651403
--- /dev/null
+++ b/hw/arm/virt-acpi-build.c
@@ -0,0 +1,697 @@
+/* Support for generating ACPI tables and passing them to Guests
+ *
+ * ARM virt ACPI generation
+ *
+ * Copyright (C) 2008-2010 Kevin O'Connor <kevin@koconnor.net>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2013 Red Hat Inc
+ *
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ *
+ * Copyright (c) 2015 HUAWEI TECHNOLOGIES CO.,LTD.
+ *
+ * Author: Shannon Zhao <zhaoshenglong@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "hw/arm/virt-acpi-build.h"
+#include "qemu/bitmap.h"
+#include "trace.h"
+#include "qom/cpu.h"
+#include "target-arm/cpu.h"
+#include "hw/acpi/acpi-defs.h"
+#include "hw/acpi/acpi.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/acpi/bios-linker-loader.h"
+#include "hw/loader.h"
+#include "hw/hw.h"
+#include "hw/acpi/aml-build.h"
+#include "hw/pci/pcie_host.h"
+#include "hw/pci/pci.h"
+
+#define ARM_SPI_BASE 32
+
+typedef struct VirtAcpiCpuInfo {
+ DECLARE_BITMAP(found_cpus, VIRT_ACPI_CPU_ID_LIMIT);
+} VirtAcpiCpuInfo;
+
+static void virt_acpi_get_cpu_info(VirtAcpiCpuInfo *cpuinfo)
+{
+ CPUState *cpu;
+
+ memset(cpuinfo->found_cpus, 0, sizeof cpuinfo->found_cpus);
+ CPU_FOREACH(cpu) {
+ set_bit(cpu->cpu_index, cpuinfo->found_cpus);
+ }
+}
+
+static void acpi_dsdt_add_cpus(Aml *scope, int smp_cpus)
+{
+ uint16_t i;
+
+ for (i = 0; i < smp_cpus; i++) {
+ Aml *dev = aml_device("C%03x", i);
+ aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0007")));
+ aml_append(dev, aml_name_decl("_UID", aml_int(i)));
+ aml_append(scope, dev);
+ }
+}
+
+static void acpi_dsdt_add_uart(Aml *scope, const MemMapEntry *uart_memmap,
+ int uart_irq)
+{
+ Aml *dev = aml_device("COM0");
+ aml_append(dev, aml_name_decl("_HID", aml_string("ARMH0011")));
+ aml_append(dev, aml_name_decl("_UID", aml_int(0)));
+
+ Aml *crs = aml_resource_template();
+ aml_append(crs, aml_memory32_fixed(uart_memmap->base,
+ uart_memmap->size, AML_READ_WRITE));
+ aml_append(crs,
+ aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
+ AML_EXCLUSIVE, uart_irq));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+
+ /* The _ADR entry is used to link this device to the UART described
+ * in the SPCR table, i.e. SPCR.base_address.address == _ADR.
+ */
+ aml_append(dev, aml_name_decl("_ADR", aml_int(uart_memmap->base)));
+
+ aml_append(scope, dev);
+}
+
+static void acpi_dsdt_add_rtc(Aml *scope, const MemMapEntry *rtc_memmap,
+ int rtc_irq)
+{
+ Aml *dev = aml_device("RTC0");
+ aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0013")));
+ aml_append(dev, aml_name_decl("_UID", aml_int(0)));
+
+ Aml *crs = aml_resource_template();
+ aml_append(crs, aml_memory32_fixed(rtc_memmap->base,
+ rtc_memmap->size, AML_READ_WRITE));
+ aml_append(crs,
+ aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
+ AML_EXCLUSIVE, rtc_irq));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(scope, dev);
+}
+
+static void acpi_dsdt_add_flash(Aml *scope, const MemMapEntry *flash_memmap)
+{
+ Aml *dev, *crs;
+ hwaddr base = flash_memmap->base;
+ hwaddr size = flash_memmap->size;
+
+ dev = aml_device("FLS0");
+ aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0015")));
+ aml_append(dev, aml_name_decl("_UID", aml_int(0)));
+
+ crs = aml_resource_template();
+ aml_append(crs, aml_memory32_fixed(base, size, AML_READ_WRITE));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(scope, dev);
+
+ dev = aml_device("FLS1");
+ aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0015")));
+ aml_append(dev, aml_name_decl("_UID", aml_int(1)));
+ crs = aml_resource_template();
+ aml_append(crs, aml_memory32_fixed(base + size, size, AML_READ_WRITE));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(scope, dev);
+}
+
+static void acpi_dsdt_add_virtio(Aml *scope,
+ const MemMapEntry *virtio_mmio_memmap,
+ int mmio_irq, int num)
+{
+ hwaddr base = virtio_mmio_memmap->base;
+ hwaddr size = virtio_mmio_memmap->size;
+ int irq = mmio_irq;
+ int i;
+
+ for (i = 0; i < num; i++) {
+ Aml *dev = aml_device("VR%02u", i);
+ aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0005")));
+ aml_append(dev, aml_name_decl("_UID", aml_int(i)));
+
+ Aml *crs = aml_resource_template();
+ aml_append(crs, aml_memory32_fixed(base, size, AML_READ_WRITE));
+ aml_append(crs,
+ aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
+ AML_EXCLUSIVE, irq + i));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(scope, dev);
+ base += size;
+ }
+}
+
+static void acpi_dsdt_add_pci(Aml *scope, const MemMapEntry *memmap, int irq)
+{
+ Aml *method, *crs, *ifctx, *UUID, *ifctx1, *elsectx, *buf;
+ int i, bus_no;
+ hwaddr base_mmio = memmap[VIRT_PCIE_MMIO].base;
+ hwaddr size_mmio = memmap[VIRT_PCIE_MMIO].size;
+ hwaddr base_pio = memmap[VIRT_PCIE_PIO].base;
+ hwaddr size_pio = memmap[VIRT_PCIE_PIO].size;
+ hwaddr base_ecam = memmap[VIRT_PCIE_ECAM].base;
+ hwaddr size_ecam = memmap[VIRT_PCIE_ECAM].size;
+ int nr_pcie_buses = size_ecam / PCIE_MMCFG_SIZE_MIN;
+
+ Aml *dev = aml_device("%s", "PCI0");
+ aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A08")));
+ aml_append(dev, aml_name_decl("_CID", aml_string("PNP0A03")));
+ aml_append(dev, aml_name_decl("_SEG", aml_int(0)));
+ aml_append(dev, aml_name_decl("_BBN", aml_int(0)));
+ aml_append(dev, aml_name_decl("_ADR", aml_int(0)));
+ aml_append(dev, aml_name_decl("_UID", aml_string("PCI0")));
+ aml_append(dev, aml_name_decl("_STR", aml_unicode("PCIe 0 Device")));
+
+ /* Declare the PCI Routing Table. */
+ Aml *rt_pkg = aml_package(nr_pcie_buses * PCI_NUM_PINS);
+ for (bus_no = 0; bus_no < nr_pcie_buses; bus_no++) {
+ for (i = 0; i < PCI_NUM_PINS; i++) {
+ int gsi = (i + bus_no) % PCI_NUM_PINS;
+ Aml *pkg = aml_package(4);
+ aml_append(pkg, aml_int((bus_no << 16) | 0xFFFF));
+ aml_append(pkg, aml_int(i));
+ aml_append(pkg, aml_name("GSI%d", gsi));
+ aml_append(pkg, aml_int(0));
+ aml_append(rt_pkg, pkg);
+ }
+ }
+ aml_append(dev, aml_name_decl("_PRT", rt_pkg));
+
+ /* Create GSI link device */
+ for (i = 0; i < PCI_NUM_PINS; i++) {
+ Aml *dev_gsi = aml_device("GSI%d", i);
+ aml_append(dev_gsi, aml_name_decl("_HID", aml_string("PNP0C0F")));
+ aml_append(dev_gsi, aml_name_decl("_UID", aml_int(0)));
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
+ AML_EXCLUSIVE, irq + i));
+ aml_append(dev_gsi, aml_name_decl("_PRS", crs));
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
+ AML_EXCLUSIVE, irq + i));
+ aml_append(dev_gsi, aml_name_decl("_CRS", crs));
+ method = aml_method("_SRS", 1);
+ aml_append(dev_gsi, method);
+ aml_append(dev, dev_gsi);
+ }
+
+ method = aml_method("_CBA", 0);
+ aml_append(method, aml_return(aml_int(base_ecam)));
+ aml_append(dev, method);
+
+ method = aml_method("_CRS", 0);
+ Aml *rbuf = aml_resource_template();
+ aml_append(rbuf,
+ aml_word_bus_number(AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE,
+ 0x0000, 0x0000, nr_pcie_buses - 1, 0x0000,
+ nr_pcie_buses));
+ aml_append(rbuf,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_NON_CACHEABLE, AML_READ_WRITE, 0x0000, base_mmio,
+ base_mmio + size_mmio - 1, 0x0000, size_mmio));
+ aml_append(rbuf,
+ aml_dword_io(AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE,
+ AML_ENTIRE_RANGE, 0x0000, 0x0000, size_pio - 1, base_pio,
+ size_pio));
+
+ aml_append(method, aml_name_decl("RBUF", rbuf));
+ aml_append(method, aml_return(rbuf));
+ aml_append(dev, method);
+
+ /* Declare an _OSC (OS Control Handoff) method */
+ aml_append(dev, aml_name_decl("SUPP", aml_int(0)));
+ aml_append(dev, aml_name_decl("CTRL", aml_int(0)));
+ method = aml_method("_OSC", 4);
+ aml_append(method,
+ aml_create_dword_field(aml_arg(3), aml_int(0), "CDW1"));
+
+ /* PCI Firmware Specification 3.0
+ * 4.5.1. _OSC Interface for PCI Host Bridge Devices
+ * The _OSC interface for a PCI/PCI-X/PCI Express hierarchy is
+ * identified by the Universal Unique IDentifier (UUID)
+ * 33DB4D5B-1FF7-401C-9657-7441C03DD766
+ */
+ UUID = aml_touuid("33DB4D5B-1FF7-401C-9657-7441C03DD766");
+ ifctx = aml_if(aml_equal(aml_arg(0), UUID));
+ aml_append(ifctx,
+ aml_create_dword_field(aml_arg(3), aml_int(4), "CDW2"));
+ aml_append(ifctx,
+ aml_create_dword_field(aml_arg(3), aml_int(8), "CDW3"));
+ aml_append(ifctx, aml_store(aml_name("CDW2"), aml_name("SUPP")));
+ aml_append(ifctx, aml_store(aml_name("CDW3"), aml_name("CTRL")));
+ aml_append(ifctx, aml_store(aml_and(aml_name("CTRL"), aml_int(0x1D)),
+ aml_name("CTRL")));
+
+ ifctx1 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(0x1))));
+ aml_append(ifctx1, aml_store(aml_or(aml_name("CDW1"), aml_int(0x08)),
+ aml_name("CDW1")));
+ aml_append(ifctx, ifctx1);
+
+ ifctx1 = aml_if(aml_lnot(aml_equal(aml_name("CDW3"), aml_name("CTRL"))));
+ aml_append(ifctx1, aml_store(aml_or(aml_name("CDW1"), aml_int(0x10)),
+ aml_name("CDW1")));
+ aml_append(ifctx, ifctx1);
+
+ aml_append(ifctx, aml_store(aml_name("CTRL"), aml_name("CDW3")));
+ aml_append(ifctx, aml_return(aml_arg(3)));
+ aml_append(method, ifctx);
+
+ elsectx = aml_else();
+ aml_append(elsectx, aml_store(aml_or(aml_name("CDW1"), aml_int(4)),
+ aml_name("CDW1")));
+ aml_append(elsectx, aml_return(aml_arg(3)));
+ aml_append(method, elsectx);
+ aml_append(dev, method);
+
+ method = aml_method("_DSM", 4);
+
+ /* PCI Firmware Specification 3.0
+ * 4.6.1. _DSM for PCI Express Slot Information
+ * The UUID in _DSM in this context is
+ * {E5C937D0-3553-4D7A-9117-EA4D19C3434D}
+ */
+ UUID = aml_touuid("E5C937D0-3553-4D7A-9117-EA4D19C3434D");
+ ifctx = aml_if(aml_equal(aml_arg(0), UUID));
+ ifctx1 = aml_if(aml_equal(aml_arg(2), aml_int(0)));
+ uint8_t byte_list[1] = {1};
+ buf = aml_buffer(1, byte_list);
+ aml_append(ifctx1, aml_return(buf));
+ aml_append(ifctx, ifctx1);
+ aml_append(method, ifctx);
+
+ byte_list[0] = 0;
+ buf = aml_buffer(1, byte_list);
+ aml_append(method, aml_return(buf));
+ aml_append(dev, method);
+
+ Aml *dev_rp0 = aml_device("%s", "RP0");
+ aml_append(dev_rp0, aml_name_decl("_ADR", aml_int(0)));
+ aml_append(dev, dev_rp0);
+ aml_append(scope, dev);
+}
+
+/* RSDP */
+static GArray *
+build_rsdp(GArray *rsdp_table, GArray *linker, unsigned rsdt)
+{
+ AcpiRsdpDescriptor *rsdp = acpi_data_push(rsdp_table, sizeof *rsdp);
+
+ bios_linker_loader_alloc(linker, ACPI_BUILD_RSDP_FILE, 16,
+ true /* fseg memory */);
+
+ memcpy(&rsdp->signature, "RSD PTR ", sizeof(rsdp->signature));
+ memcpy(rsdp->oem_id, ACPI_BUILD_APPNAME6, sizeof(rsdp->oem_id));
+ rsdp->length = cpu_to_le32(sizeof(*rsdp));
+ rsdp->revision = 0x02;
+
+ /* Point to RSDT */
+ rsdp->rsdt_physical_address = cpu_to_le32(rsdt);
+ /* Address to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_RSDP_FILE,
+ ACPI_BUILD_TABLE_FILE,
+ rsdp_table, &rsdp->rsdt_physical_address,
+ sizeof rsdp->rsdt_physical_address);
+ rsdp->checksum = 0;
+ /* Checksum to be filled by Guest linker */
+ bios_linker_loader_add_checksum(linker, ACPI_BUILD_RSDP_FILE,
+ rsdp, rsdp, sizeof *rsdp, &rsdp->checksum);
+
+ return rsdp_table;
+}
+
+static void
+build_spcr(GArray *table_data, GArray *linker, VirtGuestInfo *guest_info)
+{
+ AcpiSerialPortConsoleRedirection *spcr;
+ const MemMapEntry *uart_memmap = &guest_info->memmap[VIRT_UART];
+ int irq = guest_info->irqmap[VIRT_UART] + ARM_SPI_BASE;
+
+ spcr = acpi_data_push(table_data, sizeof(*spcr));
+
+ spcr->interface_type = 0x3; /* ARM PL011 UART */
+
+ spcr->base_address.space_id = AML_SYSTEM_MEMORY;
+ spcr->base_address.bit_width = 8;
+ spcr->base_address.bit_offset = 0;
+ spcr->base_address.access_width = 1;
+ spcr->base_address.address = cpu_to_le64(uart_memmap->base);
+
+ spcr->interrupt_types = (1 << 3); /* Bit[3] ARMH GIC interrupt */
+ spcr->gsi = cpu_to_le32(irq); /* Global System Interrupt */
+
+ spcr->baud = 3; /* Baud Rate: 3 = 9600 */
+ spcr->parity = 0; /* No Parity */
+ spcr->stopbits = 1; /* 1 Stop bit */
+ spcr->flowctrl = (1 << 1); /* Bit[1] = RTS/CTS hardware flow control */
+ spcr->term_type = 0; /* Terminal Type: 0 = VT100 */
+
+ spcr->pci_device_id = 0xffff; /* PCI Device ID: not a PCI device */
+ spcr->pci_vendor_id = 0xffff; /* PCI Vendor ID: not a PCI device */
+
+ build_header(linker, table_data, (void *)spcr, "SPCR", sizeof(*spcr), 2);
+}
+
+static void
+build_mcfg(GArray *table_data, GArray *linker, VirtGuestInfo *guest_info)
+{
+ AcpiTableMcfg *mcfg;
+ const MemMapEntry *memmap = guest_info->memmap;
+ int len = sizeof(*mcfg) + sizeof(mcfg->allocation[0]);
+
+ mcfg = acpi_data_push(table_data, len);
+ mcfg->allocation[0].address = cpu_to_le64(memmap[VIRT_PCIE_ECAM].base);
+
+ /* Only a single allocation so no need to play with segments */
+ mcfg->allocation[0].pci_segment = cpu_to_le16(0);
+ mcfg->allocation[0].start_bus_number = 0;
+ mcfg->allocation[0].end_bus_number = (memmap[VIRT_PCIE_ECAM].size
+ / PCIE_MMCFG_SIZE_MIN) - 1;
+
+ build_header(linker, table_data, (void *)mcfg, "MCFG", len, 1);
+}
+
+/* GTDT */
+static void
+build_gtdt(GArray *table_data, GArray *linker)
+{
+ int gtdt_start = table_data->len;
+ AcpiGenericTimerTable *gtdt;
+
+ gtdt = acpi_data_push(table_data, sizeof *gtdt);
+ /* The interrupt values are the same with the device tree when adding 16 */
+ gtdt->secure_el1_interrupt = ARCH_TIMER_S_EL1_IRQ + 16;
+ gtdt->secure_el1_flags = ACPI_EDGE_SENSITIVE;
+
+ gtdt->non_secure_el1_interrupt = ARCH_TIMER_NS_EL1_IRQ + 16;
+ gtdt->non_secure_el1_flags = ACPI_EDGE_SENSITIVE;
+
+ gtdt->virtual_timer_interrupt = ARCH_TIMER_VIRT_IRQ + 16;
+ gtdt->virtual_timer_flags = ACPI_EDGE_SENSITIVE;
+
+ gtdt->non_secure_el2_interrupt = ARCH_TIMER_NS_EL2_IRQ + 16;
+ gtdt->non_secure_el2_flags = ACPI_EDGE_SENSITIVE;
+
+ build_header(linker, table_data,
+ (void *)(table_data->data + gtdt_start), "GTDT",
+ table_data->len - gtdt_start, 2);
+}
+
+/* MADT */
+static void
+build_madt(GArray *table_data, GArray *linker, VirtGuestInfo *guest_info,
+ VirtAcpiCpuInfo *cpuinfo)
+{
+ int madt_start = table_data->len;
+ const MemMapEntry *memmap = guest_info->memmap;
+ const int *irqmap = guest_info->irqmap;
+ AcpiMultipleApicTable *madt;
+ AcpiMadtGenericDistributor *gicd;
+ AcpiMadtGenericMsiFrame *gic_msi;
+ int i;
+
+ madt = acpi_data_push(table_data, sizeof *madt);
+
+ for (i = 0; i < guest_info->smp_cpus; i++) {
+ AcpiMadtGenericInterrupt *gicc = acpi_data_push(table_data,
+ sizeof *gicc);
+ gicc->type = ACPI_APIC_GENERIC_INTERRUPT;
+ gicc->length = sizeof(*gicc);
+ gicc->base_address = memmap[VIRT_GIC_CPU].base;
+ gicc->cpu_interface_number = i;
+ gicc->arm_mpidr = i;
+ gicc->uid = i;
+ if (test_bit(i, cpuinfo->found_cpus)) {
+ gicc->flags = cpu_to_le32(ACPI_GICC_ENABLED);
+ }
+ }
+
+ gicd = acpi_data_push(table_data, sizeof *gicd);
+ gicd->type = ACPI_APIC_GENERIC_DISTRIBUTOR;
+ gicd->length = sizeof(*gicd);
+ gicd->base_address = memmap[VIRT_GIC_DIST].base;
+
+ gic_msi = acpi_data_push(table_data, sizeof *gic_msi);
+ gic_msi->type = ACPI_APIC_GENERIC_MSI_FRAME;
+ gic_msi->length = sizeof(*gic_msi);
+ gic_msi->gic_msi_frame_id = 0;
+ gic_msi->base_address = cpu_to_le64(memmap[VIRT_GIC_V2M].base);
+ gic_msi->flags = cpu_to_le32(1);
+ gic_msi->spi_count = cpu_to_le16(NUM_GICV2M_SPIS);
+ gic_msi->spi_base = cpu_to_le16(irqmap[VIRT_GIC_V2M] + ARM_SPI_BASE);
+
+ build_header(linker, table_data,
+ (void *)(table_data->data + madt_start), "APIC",
+ table_data->len - madt_start, 3);
+}
+
+/* FADT */
+static void
+build_fadt(GArray *table_data, GArray *linker, unsigned dsdt)
+{
+ AcpiFadtDescriptorRev5_1 *fadt = acpi_data_push(table_data, sizeof(*fadt));
+
+ /* Hardware Reduced = 1 and use PSCI 0.2+ and with HVC */
+ fadt->flags = cpu_to_le32(1 << ACPI_FADT_F_HW_REDUCED_ACPI);
+ fadt->arm_boot_flags = cpu_to_le16((1 << ACPI_FADT_ARM_USE_PSCI_G_0_2) |
+ (1 << ACPI_FADT_ARM_PSCI_USE_HVC));
+
+ /* ACPI v5.1 (fadt->revision.fadt->minor_revision) */
+ fadt->minor_revision = 0x1;
+
+ fadt->dsdt = cpu_to_le32(dsdt);
+ /* DSDT address to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE,
+ ACPI_BUILD_TABLE_FILE,
+ table_data, &fadt->dsdt,
+ sizeof fadt->dsdt);
+
+ build_header(linker, table_data,
+ (void *)fadt, "FACP", sizeof(*fadt), 5);
+}
+
+/* DSDT */
+static void
+build_dsdt(GArray *table_data, GArray *linker, VirtGuestInfo *guest_info)
+{
+ Aml *scope, *dsdt;
+ const MemMapEntry *memmap = guest_info->memmap;
+ const int *irqmap = guest_info->irqmap;
+
+ dsdt = init_aml_allocator();
+ /* Reserve space for header */
+ acpi_data_push(dsdt->buf, sizeof(AcpiTableHeader));
+
+ scope = aml_scope("\\_SB");
+ acpi_dsdt_add_cpus(scope, guest_info->smp_cpus);
+ acpi_dsdt_add_uart(scope, &memmap[VIRT_UART],
+ (irqmap[VIRT_UART] + ARM_SPI_BASE));
+ acpi_dsdt_add_rtc(scope, &memmap[VIRT_RTC],
+ (irqmap[VIRT_RTC] + ARM_SPI_BASE));
+ acpi_dsdt_add_flash(scope, &memmap[VIRT_FLASH]);
+ acpi_dsdt_add_virtio(scope, &memmap[VIRT_MMIO],
+ (irqmap[VIRT_MMIO] + ARM_SPI_BASE), NUM_VIRTIO_TRANSPORTS);
+ acpi_dsdt_add_pci(scope, memmap, (irqmap[VIRT_PCIE] + ARM_SPI_BASE));
+
+ aml_append(dsdt, scope);
+
+ /* copy AML table into ACPI tables blob and patch header there */
+ g_array_append_vals(table_data, dsdt->buf->data, dsdt->buf->len);
+ build_header(linker, table_data,
+ (void *)(table_data->data + table_data->len - dsdt->buf->len),
+ "DSDT", dsdt->buf->len, 2);
+ free_aml_allocator();
+}
+
+typedef
+struct AcpiBuildState {
+ /* Copy of table in RAM (for patching). */
+ MemoryRegion *table_mr;
+ MemoryRegion *rsdp_mr;
+ MemoryRegion *linker_mr;
+ /* Is table patched? */
+ bool patched;
+ VirtGuestInfo *guest_info;
+} AcpiBuildState;
+
+static
+void virt_acpi_build(VirtGuestInfo *guest_info, AcpiBuildTables *tables)
+{
+ GArray *table_offsets;
+ unsigned dsdt, rsdt;
+ VirtAcpiCpuInfo cpuinfo;
+ GArray *tables_blob = tables->table_data;
+
+ virt_acpi_get_cpu_info(&cpuinfo);
+
+ table_offsets = g_array_new(false, true /* clear */,
+ sizeof(uint32_t));
+
+ bios_linker_loader_alloc(tables->linker, ACPI_BUILD_TABLE_FILE,
+ 64, false /* high memory */);
+
+ /*
+ * The ACPI v5.1 tables for Hardware-reduced ACPI platform are:
+ * RSDP
+ * RSDT
+ * FADT
+ * GTDT
+ * MADT
+ * MCFG
+ * DSDT
+ */
+
+ /* DSDT is pointed to by FADT */
+ dsdt = tables_blob->len;
+ build_dsdt(tables_blob, tables->linker, guest_info);
+
+ /* FADT MADT GTDT MCFG SPCR pointed to by RSDT */
+ acpi_add_table(table_offsets, tables_blob);
+ build_fadt(tables_blob, tables->linker, dsdt);
+
+ acpi_add_table(table_offsets, tables_blob);
+ build_madt(tables_blob, tables->linker, guest_info, &cpuinfo);
+
+ acpi_add_table(table_offsets, tables_blob);
+ build_gtdt(tables_blob, tables->linker);
+
+ acpi_add_table(table_offsets, tables_blob);
+ build_mcfg(tables_blob, tables->linker, guest_info);
+
+ acpi_add_table(table_offsets, tables_blob);
+ build_spcr(tables_blob, tables->linker, guest_info);
+
+ /* RSDT is pointed to by RSDP */
+ rsdt = tables_blob->len;
+ build_rsdt(tables_blob, tables->linker, table_offsets);
+
+ /* RSDP is in FSEG memory, so allocate it separately */
+ build_rsdp(tables->rsdp, tables->linker, rsdt);
+
+ /* Cleanup memory that's no longer used. */
+ g_array_free(table_offsets, true);
+}
+
+static void acpi_ram_update(MemoryRegion *mr, GArray *data)
+{
+ uint32_t size = acpi_data_len(data);
+
+ /* Make sure RAM size is correct - in case it got changed
+ * e.g. by migration */
+ memory_region_ram_resize(mr, size, &error_abort);
+
+ memcpy(memory_region_get_ram_ptr(mr), data->data, size);
+ memory_region_set_dirty(mr, 0, size);
+}
+
+static void virt_acpi_build_update(void *build_opaque, uint32_t offset)
+{
+ AcpiBuildState *build_state = build_opaque;
+ AcpiBuildTables tables;
+
+ /* No state to update or already patched? Nothing to do. */
+ if (!build_state || build_state->patched) {
+ return;
+ }
+ build_state->patched = true;
+
+ acpi_build_tables_init(&tables);
+
+ virt_acpi_build(build_state->guest_info, &tables);
+
+ acpi_ram_update(build_state->table_mr, tables.table_data);
+ acpi_ram_update(build_state->rsdp_mr, tables.rsdp);
+ acpi_ram_update(build_state->linker_mr, tables.linker);
+
+
+ acpi_build_tables_cleanup(&tables, true);
+}
+
+static void virt_acpi_build_reset(void *build_opaque)
+{
+ AcpiBuildState *build_state = build_opaque;
+ build_state->patched = false;
+}
+
+static MemoryRegion *acpi_add_rom_blob(AcpiBuildState *build_state,
+ GArray *blob, const char *name,
+ uint64_t max_size)
+{
+ return rom_add_blob(name, blob->data, acpi_data_len(blob), max_size, -1,
+ name, virt_acpi_build_update, build_state);
+}
+
+static const VMStateDescription vmstate_virt_acpi_build = {
+ .name = "virt_acpi_build",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(patched, AcpiBuildState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+void virt_acpi_setup(VirtGuestInfo *guest_info)
+{
+ AcpiBuildTables tables;
+ AcpiBuildState *build_state;
+
+ if (!guest_info->fw_cfg) {
+ trace_virt_acpi_setup();
+ return;
+ }
+
+ if (!acpi_enabled) {
+ trace_virt_acpi_setup();
+ return;
+ }
+
+ build_state = g_malloc0(sizeof *build_state);
+ build_state->guest_info = guest_info;
+
+ acpi_build_tables_init(&tables);
+ virt_acpi_build(build_state->guest_info, &tables);
+
+ /* Now expose it all to Guest */
+ build_state->table_mr = acpi_add_rom_blob(build_state, tables.table_data,
+ ACPI_BUILD_TABLE_FILE,
+ ACPI_BUILD_TABLE_MAX_SIZE);
+ assert(build_state->table_mr != NULL);
+
+ build_state->linker_mr =
+ acpi_add_rom_blob(build_state, tables.linker, "etc/table-loader", 0);
+
+ fw_cfg_add_file(guest_info->fw_cfg, ACPI_BUILD_TPMLOG_FILE,
+ tables.tcpalog->data, acpi_data_len(tables.tcpalog));
+
+ build_state->rsdp_mr = acpi_add_rom_blob(build_state, tables.rsdp,
+ ACPI_BUILD_RSDP_FILE, 0);
+
+ qemu_register_reset(virt_acpi_build_reset, build_state);
+ virt_acpi_build_reset(build_state);
+ vmstate_register(NULL, 0, &vmstate_virt_acpi_build, build_state);
+
+ /* Cleanup tables but don't free the memory: we track it
+ * in build_state.
+ */
+ acpi_build_tables_cleanup(&tables, false);
+}
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
new file mode 100644
index 00000000..48468926
--- /dev/null
+++ b/hw/arm/virt.c
@@ -0,0 +1,977 @@
+/*
+ * ARM mach-virt emulation
+ *
+ * Copyright (c) 2013 Linaro Limited
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Emulate a virtual board which works by passing Linux all the information
+ * it needs about what devices are present via the device tree.
+ * There are some restrictions about what we can do here:
+ * + we can only present devices whose Linux drivers will work based
+ * purely on the device tree with no platform data at all
+ * + we want to present a very stripped-down minimalist platform,
+ * both because this reduces the security attack surface from the guest
+ * and also because it reduces our exposure to being broken when
+ * the kernel updates its device tree bindings and requires further
+ * information in a device binding that we aren't providing.
+ * This is essentially the same approach kvmtool uses.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/arm/primecell.h"
+#include "hw/arm/virt.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/device_tree.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/kvm.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "exec/address-spaces.h"
+#include "qemu/bitops.h"
+#include "qemu/error-report.h"
+#include "hw/pci-host/gpex.h"
+#include "hw/arm/virt-acpi-build.h"
+#include "hw/arm/sysbus-fdt.h"
+#include "hw/platform-bus.h"
+#include "hw/arm/fdt.h"
+
+/* Number of external interrupt lines to configure the GIC with */
+#define NUM_IRQS 256
+
+#define PLATFORM_BUS_NUM_IRQS 64
+
+static ARMPlatformBusSystemParams platform_bus_params;
+
+typedef struct VirtBoardInfo {
+ struct arm_boot_info bootinfo;
+ const char *cpu_model;
+ const MemMapEntry *memmap;
+ const int *irqmap;
+ int smp_cpus;
+ void *fdt;
+ int fdt_size;
+ uint32_t clock_phandle;
+ uint32_t gic_phandle;
+ uint32_t v2m_phandle;
+} VirtBoardInfo;
+
+typedef struct {
+ MachineClass parent;
+ VirtBoardInfo *daughterboard;
+} VirtMachineClass;
+
+typedef struct {
+ MachineState parent;
+ bool secure;
+} VirtMachineState;
+
+#define TYPE_VIRT_MACHINE "virt"
+#define VIRT_MACHINE(obj) \
+ OBJECT_CHECK(VirtMachineState, (obj), TYPE_VIRT_MACHINE)
+#define VIRT_MACHINE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VirtMachineClass, obj, TYPE_VIRT_MACHINE)
+#define VIRT_MACHINE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtMachineClass, klass, TYPE_VIRT_MACHINE)
+
+/* Addresses and sizes of our components.
+ * 0..128MB is space for a flash device so we can run bootrom code such as UEFI.
+ * 128MB..256MB is used for miscellaneous device I/O.
+ * 256MB..1GB is reserved for possible future PCI support (ie where the
+ * PCI memory window will go if we add a PCI host controller).
+ * 1GB and up is RAM (which may happily spill over into the
+ * high memory region beyond 4GB).
+ * This represents a compromise between how much RAM can be given to
+ * a 32 bit VM and leaving space for expansion and in particular for PCI.
+ * Note that devices should generally be placed at multiples of 0x10000,
+ * to accommodate guests using 64K pages.
+ */
+static const MemMapEntry a15memmap[] = {
+ /* Space up to 0x8000000 is reserved for a boot ROM */
+ [VIRT_FLASH] = { 0, 0x08000000 },
+ [VIRT_CPUPERIPHS] = { 0x08000000, 0x00020000 },
+ /* GIC distributor and CPU interfaces sit inside the CPU peripheral space */
+ [VIRT_GIC_DIST] = { 0x08000000, 0x00010000 },
+ [VIRT_GIC_CPU] = { 0x08010000, 0x00010000 },
+ [VIRT_GIC_V2M] = { 0x08020000, 0x00001000 },
+ [VIRT_UART] = { 0x09000000, 0x00001000 },
+ [VIRT_RTC] = { 0x09010000, 0x00001000 },
+ [VIRT_FW_CFG] = { 0x09020000, 0x0000000a },
+ [VIRT_MMIO] = { 0x0a000000, 0x00000200 },
+ /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */
+ [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 },
+ [VIRT_PCIE_MMIO] = { 0x10000000, 0x2eff0000 },
+ [VIRT_PCIE_PIO] = { 0x3eff0000, 0x00010000 },
+ [VIRT_PCIE_ECAM] = { 0x3f000000, 0x01000000 },
+ [VIRT_MEM] = { 0x40000000, 30ULL * 1024 * 1024 * 1024 },
+};
+
+static const int a15irqmap[] = {
+ [VIRT_UART] = 1,
+ [VIRT_RTC] = 2,
+ [VIRT_PCIE] = 3, /* ... to 6 */
+ [VIRT_MMIO] = 16, /* ...to 16 + NUM_VIRTIO_TRANSPORTS - 1 */
+ [VIRT_GIC_V2M] = 48, /* ...to 48 + NUM_GICV2M_SPIS - 1 */
+ [VIRT_PLATFORM_BUS] = 112, /* ...to 112 + PLATFORM_BUS_NUM_IRQS -1 */
+};
+
+static VirtBoardInfo machines[] = {
+ {
+ .cpu_model = "cortex-a15",
+ .memmap = a15memmap,
+ .irqmap = a15irqmap,
+ },
+ {
+ .cpu_model = "cortex-a53",
+ .memmap = a15memmap,
+ .irqmap = a15irqmap,
+ },
+ {
+ .cpu_model = "cortex-a57",
+ .memmap = a15memmap,
+ .irqmap = a15irqmap,
+ },
+ {
+ .cpu_model = "host",
+ .memmap = a15memmap,
+ .irqmap = a15irqmap,
+ },
+};
+
+static VirtBoardInfo *find_machine_info(const char *cpu)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(machines); i++) {
+ if (strcmp(cpu, machines[i].cpu_model) == 0) {
+ return &machines[i];
+ }
+ }
+ return NULL;
+}
+
+static void create_fdt(VirtBoardInfo *vbi)
+{
+ void *fdt = create_device_tree(&vbi->fdt_size);
+
+ if (!fdt) {
+ error_report("create_device_tree() failed");
+ exit(1);
+ }
+
+ vbi->fdt = fdt;
+
+ /* Header */
+ qemu_fdt_setprop_string(fdt, "/", "compatible", "linux,dummy-virt");
+ qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2);
+ qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x2);
+
+ /*
+ * /chosen and /memory nodes must exist for load_dtb
+ * to fill in necessary properties later
+ */
+ qemu_fdt_add_subnode(fdt, "/chosen");
+ qemu_fdt_add_subnode(fdt, "/memory");
+ qemu_fdt_setprop_string(fdt, "/memory", "device_type", "memory");
+
+ /* Clock node, for the benefit of the UART. The kernel device tree
+ * binding documentation claims the PL011 node clock properties are
+ * optional but in practice if you omit them the kernel refuses to
+ * probe for the device.
+ */
+ vbi->clock_phandle = qemu_fdt_alloc_phandle(fdt);
+ qemu_fdt_add_subnode(fdt, "/apb-pclk");
+ qemu_fdt_setprop_string(fdt, "/apb-pclk", "compatible", "fixed-clock");
+ qemu_fdt_setprop_cell(fdt, "/apb-pclk", "#clock-cells", 0x0);
+ qemu_fdt_setprop_cell(fdt, "/apb-pclk", "clock-frequency", 24000000);
+ qemu_fdt_setprop_string(fdt, "/apb-pclk", "clock-output-names",
+ "clk24mhz");
+ qemu_fdt_setprop_cell(fdt, "/apb-pclk", "phandle", vbi->clock_phandle);
+
+}
+
+static void fdt_add_psci_node(const VirtBoardInfo *vbi)
+{
+ uint32_t cpu_suspend_fn;
+ uint32_t cpu_off_fn;
+ uint32_t cpu_on_fn;
+ uint32_t migrate_fn;
+ void *fdt = vbi->fdt;
+ ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(0));
+
+ qemu_fdt_add_subnode(fdt, "/psci");
+ if (armcpu->psci_version == 2) {
+ const char comp[] = "arm,psci-0.2\0arm,psci";
+ qemu_fdt_setprop(fdt, "/psci", "compatible", comp, sizeof(comp));
+
+ cpu_off_fn = QEMU_PSCI_0_2_FN_CPU_OFF;
+ if (arm_feature(&armcpu->env, ARM_FEATURE_AARCH64)) {
+ cpu_suspend_fn = QEMU_PSCI_0_2_FN64_CPU_SUSPEND;
+ cpu_on_fn = QEMU_PSCI_0_2_FN64_CPU_ON;
+ migrate_fn = QEMU_PSCI_0_2_FN64_MIGRATE;
+ } else {
+ cpu_suspend_fn = QEMU_PSCI_0_2_FN_CPU_SUSPEND;
+ cpu_on_fn = QEMU_PSCI_0_2_FN_CPU_ON;
+ migrate_fn = QEMU_PSCI_0_2_FN_MIGRATE;
+ }
+ } else {
+ qemu_fdt_setprop_string(fdt, "/psci", "compatible", "arm,psci");
+
+ cpu_suspend_fn = QEMU_PSCI_0_1_FN_CPU_SUSPEND;
+ cpu_off_fn = QEMU_PSCI_0_1_FN_CPU_OFF;
+ cpu_on_fn = QEMU_PSCI_0_1_FN_CPU_ON;
+ migrate_fn = QEMU_PSCI_0_1_FN_MIGRATE;
+ }
+
+ /* We adopt the PSCI spec's nomenclature, and use 'conduit' to refer
+ * to the instruction that should be used to invoke PSCI functions.
+ * However, the device tree binding uses 'method' instead, so that is
+ * what we should use here.
+ */
+ qemu_fdt_setprop_string(fdt, "/psci", "method", "hvc");
+
+ qemu_fdt_setprop_cell(fdt, "/psci", "cpu_suspend", cpu_suspend_fn);
+ qemu_fdt_setprop_cell(fdt, "/psci", "cpu_off", cpu_off_fn);
+ qemu_fdt_setprop_cell(fdt, "/psci", "cpu_on", cpu_on_fn);
+ qemu_fdt_setprop_cell(fdt, "/psci", "migrate", migrate_fn);
+}
+
+static void fdt_add_timer_nodes(const VirtBoardInfo *vbi)
+{
+ /* Note that on A15 h/w these interrupts are level-triggered,
+ * but for the GIC implementation provided by both QEMU and KVM
+ * they are edge-triggered.
+ */
+ ARMCPU *armcpu;
+ uint32_t irqflags = GIC_FDT_IRQ_FLAGS_EDGE_LO_HI;
+
+ irqflags = deposit32(irqflags, GIC_FDT_IRQ_PPI_CPU_START,
+ GIC_FDT_IRQ_PPI_CPU_WIDTH, (1 << vbi->smp_cpus) - 1);
+
+ qemu_fdt_add_subnode(vbi->fdt, "/timer");
+
+ armcpu = ARM_CPU(qemu_get_cpu(0));
+ if (arm_feature(&armcpu->env, ARM_FEATURE_V8)) {
+ const char compat[] = "arm,armv8-timer\0arm,armv7-timer";
+ qemu_fdt_setprop(vbi->fdt, "/timer", "compatible",
+ compat, sizeof(compat));
+ } else {
+ qemu_fdt_setprop_string(vbi->fdt, "/timer", "compatible",
+ "arm,armv7-timer");
+ }
+ qemu_fdt_setprop_cells(vbi->fdt, "/timer", "interrupts",
+ GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_S_EL1_IRQ, irqflags,
+ GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_NS_EL1_IRQ, irqflags,
+ GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_VIRT_IRQ, irqflags,
+ GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_NS_EL2_IRQ, irqflags);
+}
+
+static void fdt_add_cpu_nodes(const VirtBoardInfo *vbi)
+{
+ int cpu;
+
+ qemu_fdt_add_subnode(vbi->fdt, "/cpus");
+ qemu_fdt_setprop_cell(vbi->fdt, "/cpus", "#address-cells", 0x1);
+ qemu_fdt_setprop_cell(vbi->fdt, "/cpus", "#size-cells", 0x0);
+
+ for (cpu = vbi->smp_cpus - 1; cpu >= 0; cpu--) {
+ char *nodename = g_strdup_printf("/cpus/cpu@%d", cpu);
+ ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(cpu));
+
+ qemu_fdt_add_subnode(vbi->fdt, nodename);
+ qemu_fdt_setprop_string(vbi->fdt, nodename, "device_type", "cpu");
+ qemu_fdt_setprop_string(vbi->fdt, nodename, "compatible",
+ armcpu->dtb_compatible);
+
+ if (vbi->smp_cpus > 1) {
+ qemu_fdt_setprop_string(vbi->fdt, nodename,
+ "enable-method", "psci");
+ }
+
+ qemu_fdt_setprop_cell(vbi->fdt, nodename, "reg", armcpu->mp_affinity);
+ g_free(nodename);
+ }
+}
+
+static void fdt_add_v2m_gic_node(VirtBoardInfo *vbi)
+{
+ vbi->v2m_phandle = qemu_fdt_alloc_phandle(vbi->fdt);
+ qemu_fdt_add_subnode(vbi->fdt, "/intc/v2m");
+ qemu_fdt_setprop_string(vbi->fdt, "/intc/v2m", "compatible",
+ "arm,gic-v2m-frame");
+ qemu_fdt_setprop(vbi->fdt, "/intc/v2m", "msi-controller", NULL, 0);
+ qemu_fdt_setprop_sized_cells(vbi->fdt, "/intc/v2m", "reg",
+ 2, vbi->memmap[VIRT_GIC_V2M].base,
+ 2, vbi->memmap[VIRT_GIC_V2M].size);
+ qemu_fdt_setprop_cell(vbi->fdt, "/intc/v2m", "phandle", vbi->v2m_phandle);
+}
+
+static void fdt_add_gic_node(VirtBoardInfo *vbi)
+{
+ vbi->gic_phandle = qemu_fdt_alloc_phandle(vbi->fdt);
+ qemu_fdt_setprop_cell(vbi->fdt, "/", "interrupt-parent", vbi->gic_phandle);
+
+ qemu_fdt_add_subnode(vbi->fdt, "/intc");
+ /* 'cortex-a15-gic' means 'GIC v2' */
+ qemu_fdt_setprop_string(vbi->fdt, "/intc", "compatible",
+ "arm,cortex-a15-gic");
+ qemu_fdt_setprop_cell(vbi->fdt, "/intc", "#interrupt-cells", 3);
+ qemu_fdt_setprop(vbi->fdt, "/intc", "interrupt-controller", NULL, 0);
+ qemu_fdt_setprop_sized_cells(vbi->fdt, "/intc", "reg",
+ 2, vbi->memmap[VIRT_GIC_DIST].base,
+ 2, vbi->memmap[VIRT_GIC_DIST].size,
+ 2, vbi->memmap[VIRT_GIC_CPU].base,
+ 2, vbi->memmap[VIRT_GIC_CPU].size);
+ qemu_fdt_setprop_cell(vbi->fdt, "/intc", "#address-cells", 0x2);
+ qemu_fdt_setprop_cell(vbi->fdt, "/intc", "#size-cells", 0x2);
+ qemu_fdt_setprop(vbi->fdt, "/intc", "ranges", NULL, 0);
+ qemu_fdt_setprop_cell(vbi->fdt, "/intc", "phandle", vbi->gic_phandle);
+}
+
+static void create_v2m(VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ int i;
+ int irq = vbi->irqmap[VIRT_GIC_V2M];
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "arm-gicv2m");
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, vbi->memmap[VIRT_GIC_V2M].base);
+ qdev_prop_set_uint32(dev, "base-spi", irq);
+ qdev_prop_set_uint32(dev, "num-spi", NUM_GICV2M_SPIS);
+ qdev_init_nofail(dev);
+
+ for (i = 0; i < NUM_GICV2M_SPIS; i++) {
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, pic[irq + i]);
+ }
+
+ fdt_add_v2m_gic_node(vbi);
+}
+
+static void create_gic(VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ /* We create a standalone GIC v2 */
+ DeviceState *gicdev;
+ SysBusDevice *gicbusdev;
+ const char *gictype = "arm_gic";
+ int i;
+
+ if (kvm_irqchip_in_kernel()) {
+ gictype = "kvm-arm-gic";
+ }
+
+ gicdev = qdev_create(NULL, gictype);
+ qdev_prop_set_uint32(gicdev, "revision", 2);
+ qdev_prop_set_uint32(gicdev, "num-cpu", smp_cpus);
+ /* Note that the num-irq property counts both internal and external
+ * interrupts; there are always 32 of the former (mandated by GIC spec).
+ */
+ qdev_prop_set_uint32(gicdev, "num-irq", NUM_IRQS + 32);
+ qdev_init_nofail(gicdev);
+ gicbusdev = SYS_BUS_DEVICE(gicdev);
+ sysbus_mmio_map(gicbusdev, 0, vbi->memmap[VIRT_GIC_DIST].base);
+ sysbus_mmio_map(gicbusdev, 1, vbi->memmap[VIRT_GIC_CPU].base);
+
+ /* Wire the outputs from each CPU's generic timer to the
+ * appropriate GIC PPI inputs, and the GIC's IRQ output to
+ * the CPU's IRQ input.
+ */
+ for (i = 0; i < smp_cpus; i++) {
+ DeviceState *cpudev = DEVICE(qemu_get_cpu(i));
+ int ppibase = NUM_IRQS + i * 32;
+ /* physical timer; we wire it up to the non-secure timer's ID,
+ * since a real A15 always has TrustZone but QEMU doesn't.
+ */
+ qdev_connect_gpio_out(cpudev, 0,
+ qdev_get_gpio_in(gicdev, ppibase + 30));
+ /* virtual timer */
+ qdev_connect_gpio_out(cpudev, 1,
+ qdev_get_gpio_in(gicdev, ppibase + 27));
+
+ sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
+ sysbus_connect_irq(gicbusdev, i + smp_cpus,
+ qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
+ }
+
+ for (i = 0; i < NUM_IRQS; i++) {
+ pic[i] = qdev_get_gpio_in(gicdev, i);
+ }
+
+ fdt_add_gic_node(vbi);
+
+ create_v2m(vbi, pic);
+}
+
+static void create_uart(const VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ char *nodename;
+ hwaddr base = vbi->memmap[VIRT_UART].base;
+ hwaddr size = vbi->memmap[VIRT_UART].size;
+ int irq = vbi->irqmap[VIRT_UART];
+ const char compat[] = "arm,pl011\0arm,primecell";
+ const char clocknames[] = "uartclk\0apb_pclk";
+
+ sysbus_create_simple("pl011", base, pic[irq]);
+
+ nodename = g_strdup_printf("/pl011@%" PRIx64, base);
+ qemu_fdt_add_subnode(vbi->fdt, nodename);
+ /* Note that we can't use setprop_string because of the embedded NUL */
+ qemu_fdt_setprop(vbi->fdt, nodename, "compatible",
+ compat, sizeof(compat));
+ qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg",
+ 2, base, 2, size);
+ qemu_fdt_setprop_cells(vbi->fdt, nodename, "interrupts",
+ GIC_FDT_IRQ_TYPE_SPI, irq,
+ GIC_FDT_IRQ_FLAGS_LEVEL_HI);
+ qemu_fdt_setprop_cells(vbi->fdt, nodename, "clocks",
+ vbi->clock_phandle, vbi->clock_phandle);
+ qemu_fdt_setprop(vbi->fdt, nodename, "clock-names",
+ clocknames, sizeof(clocknames));
+
+ qemu_fdt_setprop_string(vbi->fdt, "/chosen", "stdout-path", nodename);
+ g_free(nodename);
+}
+
+static void create_rtc(const VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ char *nodename;
+ hwaddr base = vbi->memmap[VIRT_RTC].base;
+ hwaddr size = vbi->memmap[VIRT_RTC].size;
+ int irq = vbi->irqmap[VIRT_RTC];
+ const char compat[] = "arm,pl031\0arm,primecell";
+
+ sysbus_create_simple("pl031", base, pic[irq]);
+
+ nodename = g_strdup_printf("/pl031@%" PRIx64, base);
+ qemu_fdt_add_subnode(vbi->fdt, nodename);
+ qemu_fdt_setprop(vbi->fdt, nodename, "compatible", compat, sizeof(compat));
+ qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg",
+ 2, base, 2, size);
+ qemu_fdt_setprop_cells(vbi->fdt, nodename, "interrupts",
+ GIC_FDT_IRQ_TYPE_SPI, irq,
+ GIC_FDT_IRQ_FLAGS_LEVEL_HI);
+ qemu_fdt_setprop_cell(vbi->fdt, nodename, "clocks", vbi->clock_phandle);
+ qemu_fdt_setprop_string(vbi->fdt, nodename, "clock-names", "apb_pclk");
+ g_free(nodename);
+}
+
+static void create_virtio_devices(const VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ int i;
+ hwaddr size = vbi->memmap[VIRT_MMIO].size;
+
+ /* We create the transports in forwards order. Since qbus_realize()
+ * prepends (not appends) new child buses, the incrementing loop below will
+ * create a list of virtio-mmio buses with decreasing base addresses.
+ *
+ * When a -device option is processed from the command line,
+ * qbus_find_recursive() picks the next free virtio-mmio bus in forwards
+ * order. The upshot is that -device options in increasing command line
+ * order are mapped to virtio-mmio buses with decreasing base addresses.
+ *
+ * When this code was originally written, that arrangement ensured that the
+ * guest Linux kernel would give the lowest "name" (/dev/vda, eth0, etc) to
+ * the first -device on the command line. (The end-to-end order is a
+ * function of this loop, qbus_realize(), qbus_find_recursive(), and the
+ * guest kernel's name-to-address assignment strategy.)
+ *
+ * Meanwhile, the kernel's traversal seems to have been reversed; see eg.
+ * the message, if not necessarily the code, of commit 70161ff336.
+ * Therefore the loop now establishes the inverse of the original intent.
+ *
+ * Unfortunately, we can't counteract the kernel change by reversing the
+ * loop; it would break existing command lines.
+ *
+ * In any case, the kernel makes no guarantee about the stability of
+ * enumeration order of virtio devices (as demonstrated by it changing
+ * between kernel versions). For reliable and stable identification
+ * of disks users must use UUIDs or similar mechanisms.
+ */
+ for (i = 0; i < NUM_VIRTIO_TRANSPORTS; i++) {
+ int irq = vbi->irqmap[VIRT_MMIO] + i;
+ hwaddr base = vbi->memmap[VIRT_MMIO].base + i * size;
+
+ sysbus_create_simple("virtio-mmio", base, pic[irq]);
+ }
+
+ /* We add dtb nodes in reverse order so that they appear in the finished
+ * device tree lowest address first.
+ *
+ * Note that this mapping is independent of the loop above. The previous
+ * loop influences virtio device to virtio transport assignment, whereas
+ * this loop controls how virtio transports are laid out in the dtb.
+ */
+ for (i = NUM_VIRTIO_TRANSPORTS - 1; i >= 0; i--) {
+ char *nodename;
+ int irq = vbi->irqmap[VIRT_MMIO] + i;
+ hwaddr base = vbi->memmap[VIRT_MMIO].base + i * size;
+
+ nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, base);
+ qemu_fdt_add_subnode(vbi->fdt, nodename);
+ qemu_fdt_setprop_string(vbi->fdt, nodename,
+ "compatible", "virtio,mmio");
+ qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg",
+ 2, base, 2, size);
+ qemu_fdt_setprop_cells(vbi->fdt, nodename, "interrupts",
+ GIC_FDT_IRQ_TYPE_SPI, irq,
+ GIC_FDT_IRQ_FLAGS_EDGE_LO_HI);
+ g_free(nodename);
+ }
+}
+
+static void create_one_flash(const char *name, hwaddr flashbase,
+ hwaddr flashsize)
+{
+ /* Create and map a single flash device. We use the same
+ * parameters as the flash devices on the Versatile Express board.
+ */
+ DriveInfo *dinfo = drive_get_next(IF_PFLASH);
+ DeviceState *dev = qdev_create(NULL, "cfi.pflash01");
+ const uint64_t sectorlength = 256 * 1024;
+
+ if (dinfo) {
+ qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(dinfo),
+ &error_abort);
+ }
+
+ qdev_prop_set_uint32(dev, "num-blocks", flashsize / sectorlength);
+ qdev_prop_set_uint64(dev, "sector-length", sectorlength);
+ qdev_prop_set_uint8(dev, "width", 4);
+ qdev_prop_set_uint8(dev, "device-width", 2);
+ qdev_prop_set_bit(dev, "big-endian", false);
+ qdev_prop_set_uint16(dev, "id0", 0x89);
+ qdev_prop_set_uint16(dev, "id1", 0x18);
+ qdev_prop_set_uint16(dev, "id2", 0x00);
+ qdev_prop_set_uint16(dev, "id3", 0x00);
+ qdev_prop_set_string(dev, "name", name);
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, flashbase);
+}
+
+static void create_flash(const VirtBoardInfo *vbi)
+{
+ /* Create two flash devices to fill the VIRT_FLASH space in the memmap.
+ * Any file passed via -bios goes in the first of these.
+ */
+ hwaddr flashsize = vbi->memmap[VIRT_FLASH].size / 2;
+ hwaddr flashbase = vbi->memmap[VIRT_FLASH].base;
+ char *nodename;
+
+ if (bios_name) {
+ char *fn;
+ int image_size;
+
+ if (drive_get(IF_PFLASH, 0, 0)) {
+ error_report("The contents of the first flash device may be "
+ "specified with -bios or with -drive if=pflash... "
+ "but you cannot use both options at once");
+ exit(1);
+ }
+ fn = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (!fn) {
+ error_report("Could not find ROM image '%s'", bios_name);
+ exit(1);
+ }
+ image_size = load_image_targphys(fn, flashbase, flashsize);
+ g_free(fn);
+ if (image_size < 0) {
+ error_report("Could not load ROM image '%s'", bios_name);
+ exit(1);
+ }
+ }
+
+ create_one_flash("virt.flash0", flashbase, flashsize);
+ create_one_flash("virt.flash1", flashbase + flashsize, flashsize);
+
+ nodename = g_strdup_printf("/flash@%" PRIx64, flashbase);
+ qemu_fdt_add_subnode(vbi->fdt, nodename);
+ qemu_fdt_setprop_string(vbi->fdt, nodename, "compatible", "cfi-flash");
+ qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg",
+ 2, flashbase, 2, flashsize,
+ 2, flashbase + flashsize, 2, flashsize);
+ qemu_fdt_setprop_cell(vbi->fdt, nodename, "bank-width", 4);
+ g_free(nodename);
+}
+
+static void create_fw_cfg(const VirtBoardInfo *vbi)
+{
+ hwaddr base = vbi->memmap[VIRT_FW_CFG].base;
+ hwaddr size = vbi->memmap[VIRT_FW_CFG].size;
+ char *nodename;
+
+ fw_cfg_init_mem_wide(base + 8, base, 8);
+
+ nodename = g_strdup_printf("/fw-cfg@%" PRIx64, base);
+ qemu_fdt_add_subnode(vbi->fdt, nodename);
+ qemu_fdt_setprop_string(vbi->fdt, nodename,
+ "compatible", "qemu,fw-cfg-mmio");
+ qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg",
+ 2, base, 2, size);
+ g_free(nodename);
+}
+
+static void create_pcie_irq_map(const VirtBoardInfo *vbi, uint32_t gic_phandle,
+ int first_irq, const char *nodename)
+{
+ int devfn, pin;
+ uint32_t full_irq_map[4 * 4 * 10] = { 0 };
+ uint32_t *irq_map = full_irq_map;
+
+ for (devfn = 0; devfn <= 0x18; devfn += 0x8) {
+ for (pin = 0; pin < 4; pin++) {
+ int irq_type = GIC_FDT_IRQ_TYPE_SPI;
+ int irq_nr = first_irq + ((pin + PCI_SLOT(devfn)) % PCI_NUM_PINS);
+ int irq_level = GIC_FDT_IRQ_FLAGS_LEVEL_HI;
+ int i;
+
+ uint32_t map[] = {
+ devfn << 8, 0, 0, /* devfn */
+ pin + 1, /* PCI pin */
+ gic_phandle, 0, 0, irq_type, irq_nr, irq_level }; /* GIC irq */
+
+ /* Convert map to big endian */
+ for (i = 0; i < 10; i++) {
+ irq_map[i] = cpu_to_be32(map[i]);
+ }
+ irq_map += 10;
+ }
+ }
+
+ qemu_fdt_setprop(vbi->fdt, nodename, "interrupt-map",
+ full_irq_map, sizeof(full_irq_map));
+
+ qemu_fdt_setprop_cells(vbi->fdt, nodename, "interrupt-map-mask",
+ 0x1800, 0, 0, /* devfn (PCI_SLOT(3)) */
+ 0x7 /* PCI irq */);
+}
+
+static void create_pcie(const VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ hwaddr base_mmio = vbi->memmap[VIRT_PCIE_MMIO].base;
+ hwaddr size_mmio = vbi->memmap[VIRT_PCIE_MMIO].size;
+ hwaddr base_pio = vbi->memmap[VIRT_PCIE_PIO].base;
+ hwaddr size_pio = vbi->memmap[VIRT_PCIE_PIO].size;
+ hwaddr base_ecam = vbi->memmap[VIRT_PCIE_ECAM].base;
+ hwaddr size_ecam = vbi->memmap[VIRT_PCIE_ECAM].size;
+ hwaddr base = base_mmio;
+ int nr_pcie_buses = size_ecam / PCIE_MMCFG_SIZE_MIN;
+ int irq = vbi->irqmap[VIRT_PCIE];
+ MemoryRegion *mmio_alias;
+ MemoryRegion *mmio_reg;
+ MemoryRegion *ecam_alias;
+ MemoryRegion *ecam_reg;
+ DeviceState *dev;
+ char *nodename;
+ int i;
+
+ dev = qdev_create(NULL, TYPE_GPEX_HOST);
+ qdev_init_nofail(dev);
+
+ /* Map only the first size_ecam bytes of ECAM space */
+ ecam_alias = g_new0(MemoryRegion, 1);
+ ecam_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
+ memory_region_init_alias(ecam_alias, OBJECT(dev), "pcie-ecam",
+ ecam_reg, 0, size_ecam);
+ memory_region_add_subregion(get_system_memory(), base_ecam, ecam_alias);
+
+ /* Map the MMIO window into system address space so as to expose
+ * the section of PCI MMIO space which starts at the same base address
+ * (ie 1:1 mapping for that part of PCI MMIO space visible through
+ * the window).
+ */
+ mmio_alias = g_new0(MemoryRegion, 1);
+ mmio_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1);
+ memory_region_init_alias(mmio_alias, OBJECT(dev), "pcie-mmio",
+ mmio_reg, base_mmio, size_mmio);
+ memory_region_add_subregion(get_system_memory(), base_mmio, mmio_alias);
+
+ /* Map IO port space */
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, base_pio);
+
+ for (i = 0; i < GPEX_NUM_IRQS; i++) {
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, pic[irq + i]);
+ }
+
+ nodename = g_strdup_printf("/pcie@%" PRIx64, base);
+ qemu_fdt_add_subnode(vbi->fdt, nodename);
+ qemu_fdt_setprop_string(vbi->fdt, nodename,
+ "compatible", "pci-host-ecam-generic");
+ qemu_fdt_setprop_string(vbi->fdt, nodename, "device_type", "pci");
+ qemu_fdt_setprop_cell(vbi->fdt, nodename, "#address-cells", 3);
+ qemu_fdt_setprop_cell(vbi->fdt, nodename, "#size-cells", 2);
+ qemu_fdt_setprop_cells(vbi->fdt, nodename, "bus-range", 0,
+ nr_pcie_buses - 1);
+
+ qemu_fdt_setprop_cells(vbi->fdt, nodename, "msi-parent", vbi->v2m_phandle);
+
+ qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg",
+ 2, base_ecam, 2, size_ecam);
+ qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "ranges",
+ 1, FDT_PCI_RANGE_IOPORT, 2, 0,
+ 2, base_pio, 2, size_pio,
+ 1, FDT_PCI_RANGE_MMIO, 2, base_mmio,
+ 2, base_mmio, 2, size_mmio);
+
+ qemu_fdt_setprop_cell(vbi->fdt, nodename, "#interrupt-cells", 1);
+ create_pcie_irq_map(vbi, vbi->gic_phandle, irq, nodename);
+
+ g_free(nodename);
+}
+
+static void create_platform_bus(VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ int i;
+ ARMPlatformBusFDTParams *fdt_params = g_new(ARMPlatformBusFDTParams, 1);
+ MemoryRegion *sysmem = get_system_memory();
+
+ platform_bus_params.platform_bus_base = vbi->memmap[VIRT_PLATFORM_BUS].base;
+ platform_bus_params.platform_bus_size = vbi->memmap[VIRT_PLATFORM_BUS].size;
+ platform_bus_params.platform_bus_first_irq = vbi->irqmap[VIRT_PLATFORM_BUS];
+ platform_bus_params.platform_bus_num_irqs = PLATFORM_BUS_NUM_IRQS;
+
+ fdt_params->system_params = &platform_bus_params;
+ fdt_params->binfo = &vbi->bootinfo;
+ fdt_params->intc = "/intc";
+ /*
+ * register a machine init done notifier that creates the device tree
+ * nodes of the platform bus and its children dynamic sysbus devices
+ */
+ arm_register_platform_bus_fdt_creator(fdt_params);
+
+ dev = qdev_create(NULL, TYPE_PLATFORM_BUS_DEVICE);
+ dev->id = TYPE_PLATFORM_BUS_DEVICE;
+ qdev_prop_set_uint32(dev, "num_irqs",
+ platform_bus_params.platform_bus_num_irqs);
+ qdev_prop_set_uint32(dev, "mmio_size",
+ platform_bus_params.platform_bus_size);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ for (i = 0; i < platform_bus_params.platform_bus_num_irqs; i++) {
+ int irqn = platform_bus_params.platform_bus_first_irq + i;
+ sysbus_connect_irq(s, i, pic[irqn]);
+ }
+
+ memory_region_add_subregion(sysmem,
+ platform_bus_params.platform_bus_base,
+ sysbus_mmio_get_region(s, 0));
+}
+
+static void *machvirt_dtb(const struct arm_boot_info *binfo, int *fdt_size)
+{
+ const VirtBoardInfo *board = (const VirtBoardInfo *)binfo;
+
+ *fdt_size = board->fdt_size;
+ return board->fdt;
+}
+
+static
+void virt_guest_info_machine_done(Notifier *notifier, void *data)
+{
+ VirtGuestInfoState *guest_info_state = container_of(notifier,
+ VirtGuestInfoState, machine_done);
+ virt_acpi_setup(&guest_info_state->info);
+}
+
+static void machvirt_init(MachineState *machine)
+{
+ VirtMachineState *vms = VIRT_MACHINE(machine);
+ qemu_irq pic[NUM_IRQS];
+ MemoryRegion *sysmem = get_system_memory();
+ int n;
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ const char *cpu_model = machine->cpu_model;
+ VirtBoardInfo *vbi;
+ VirtGuestInfoState *guest_info_state = g_malloc0(sizeof *guest_info_state);
+ VirtGuestInfo *guest_info = &guest_info_state->info;
+ char **cpustr;
+
+ if (!cpu_model) {
+ cpu_model = "cortex-a15";
+ }
+
+ /* Separate the actual CPU model name from any appended features */
+ cpustr = g_strsplit(cpu_model, ",", 2);
+
+ vbi = find_machine_info(cpustr[0]);
+
+ if (!vbi) {
+ error_report("mach-virt: CPU %s not supported", cpustr[0]);
+ exit(1);
+ }
+
+ vbi->smp_cpus = smp_cpus;
+
+ if (machine->ram_size > vbi->memmap[VIRT_MEM].size) {
+ error_report("mach-virt: cannot model more than 30GB RAM");
+ exit(1);
+ }
+
+ create_fdt(vbi);
+
+ for (n = 0; n < smp_cpus; n++) {
+ ObjectClass *oc = cpu_class_by_name(TYPE_ARM_CPU, cpustr[0]);
+ CPUClass *cc = CPU_CLASS(oc);
+ Object *cpuobj;
+ Error *err = NULL;
+ char *cpuopts = g_strdup(cpustr[1]);
+
+ if (!oc) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ cpuobj = object_new(object_class_get_name(oc));
+
+ /* Handle any CPU options specified by the user */
+ cc->parse_features(CPU(cpuobj), cpuopts, &err);
+ g_free(cpuopts);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+
+ if (!vms->secure) {
+ object_property_set_bool(cpuobj, false, "has_el3", NULL);
+ }
+
+ object_property_set_int(cpuobj, QEMU_PSCI_CONDUIT_HVC, "psci-conduit",
+ NULL);
+
+ /* Secondary CPUs start in PSCI powered-down state */
+ if (n > 0) {
+ object_property_set_bool(cpuobj, true, "start-powered-off", NULL);
+ }
+
+ if (object_property_find(cpuobj, "reset-cbar", NULL)) {
+ object_property_set_int(cpuobj, vbi->memmap[VIRT_CPUPERIPHS].base,
+ "reset-cbar", &error_abort);
+ }
+
+ object_property_set_bool(cpuobj, true, "realized", NULL);
+ }
+ g_strfreev(cpustr);
+ fdt_add_timer_nodes(vbi);
+ fdt_add_cpu_nodes(vbi);
+ fdt_add_psci_node(vbi);
+
+ memory_region_allocate_system_memory(ram, NULL, "mach-virt.ram",
+ machine->ram_size);
+ memory_region_add_subregion(sysmem, vbi->memmap[VIRT_MEM].base, ram);
+
+ create_flash(vbi);
+
+ create_gic(vbi, pic);
+
+ create_uart(vbi, pic);
+
+ create_rtc(vbi, pic);
+
+ create_pcie(vbi, pic);
+
+ /* Create mmio transports, so the user can create virtio backends
+ * (which will be automatically plugged in to the transports). If
+ * no backend is created the transport will just sit harmlessly idle.
+ */
+ create_virtio_devices(vbi, pic);
+
+ create_fw_cfg(vbi);
+ rom_set_fw(fw_cfg_find());
+
+ guest_info->smp_cpus = smp_cpus;
+ guest_info->fw_cfg = fw_cfg_find();
+ guest_info->memmap = vbi->memmap;
+ guest_info->irqmap = vbi->irqmap;
+ guest_info_state->machine_done.notify = virt_guest_info_machine_done;
+ qemu_add_machine_init_done_notifier(&guest_info_state->machine_done);
+
+ vbi->bootinfo.ram_size = machine->ram_size;
+ vbi->bootinfo.kernel_filename = machine->kernel_filename;
+ vbi->bootinfo.kernel_cmdline = machine->kernel_cmdline;
+ vbi->bootinfo.initrd_filename = machine->initrd_filename;
+ vbi->bootinfo.nb_cpus = smp_cpus;
+ vbi->bootinfo.board_id = -1;
+ vbi->bootinfo.loader_start = vbi->memmap[VIRT_MEM].base;
+ vbi->bootinfo.get_dtb = machvirt_dtb;
+ vbi->bootinfo.firmware_loaded = bios_name || drive_get(IF_PFLASH, 0, 0);
+ arm_load_kernel(ARM_CPU(first_cpu), &vbi->bootinfo);
+
+ /*
+ * arm_load_kernel machine init done notifier registration must
+ * happen before the platform_bus_create call. In this latter,
+ * another notifier is registered which adds platform bus nodes.
+ * Notifiers are executed in registration reverse order.
+ */
+ create_platform_bus(vbi, pic);
+}
+
+static bool virt_get_secure(Object *obj, Error **errp)
+{
+ VirtMachineState *vms = VIRT_MACHINE(obj);
+
+ return vms->secure;
+}
+
+static void virt_set_secure(Object *obj, bool value, Error **errp)
+{
+ VirtMachineState *vms = VIRT_MACHINE(obj);
+
+ vms->secure = value;
+}
+
+static void virt_instance_init(Object *obj)
+{
+ VirtMachineState *vms = VIRT_MACHINE(obj);
+
+ /* EL3 is enabled by default on virt */
+ vms->secure = true;
+ object_property_add_bool(obj, "secure", virt_get_secure,
+ virt_set_secure, NULL);
+ object_property_set_description(obj, "secure",
+ "Set on/off to enable/disable the ARM "
+ "Security Extensions (TrustZone)",
+ NULL);
+}
+
+static void virt_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->name = TYPE_VIRT_MACHINE;
+ mc->desc = "ARM Virtual Machine",
+ mc->init = machvirt_init;
+ mc->max_cpus = 8;
+ mc->has_dynamic_sysbus = true;
+ mc->block_default_type = IF_VIRTIO;
+ mc->no_cdrom = 1;
+}
+
+static const TypeInfo machvirt_info = {
+ .name = TYPE_VIRT_MACHINE,
+ .parent = TYPE_MACHINE,
+ .instance_size = sizeof(VirtMachineState),
+ .instance_init = virt_instance_init,
+ .class_size = sizeof(VirtMachineClass),
+ .class_init = virt_class_init,
+};
+
+static void machvirt_machine_init(void)
+{
+ type_register_static(&machvirt_info);
+}
+
+machine_init(machvirt_machine_init);
diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c
new file mode 100644
index 00000000..a4e7b5c6
--- /dev/null
+++ b/hw/arm/xilinx_zynq.c
@@ -0,0 +1,272 @@
+/*
+ * Xilinx Zynq Baseboard System emulation.
+ *
+ * Copyright (c) 2010 Xilinx.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.croshtwaite@petalogix.com)
+ * Copyright (c) 2012 Petalogix Pty Ltd.
+ * Written by Haibing Ma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "net/net.h"
+#include "exec/address-spaces.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "hw/loader.h"
+#include "hw/ssi.h"
+#include "qemu/error-report.h"
+
+#define NUM_SPI_FLASHES 4
+#define NUM_QSPI_FLASHES 2
+#define NUM_QSPI_BUSSES 2
+
+#define FLASH_SIZE (64 * 1024 * 1024)
+#define FLASH_SECTOR_SIZE (128 * 1024)
+
+#define IRQ_OFFSET 32 /* pic interrupts start from index 32 */
+
+#define MPCORE_PERIPHBASE 0xF8F00000
+#define ZYNQ_BOARD_MIDR 0x413FC090
+
+static const int dma_irqs[8] = {
+ 46, 47, 48, 49, 72, 73, 74, 75
+};
+
+static struct arm_boot_info zynq_binfo = {};
+
+static void gem_init(NICInfo *nd, uint32_t base, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "cadence_gem");
+ if (nd->used) {
+ qemu_check_nic_model(nd, "cadence_gem");
+ qdev_set_nic_properties(dev, nd);
+ }
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, base);
+ sysbus_connect_irq(s, 0, irq);
+}
+
+static inline void zynq_init_spi_flashes(uint32_t base_addr, qemu_irq irq,
+ bool is_qspi)
+{
+ DeviceState *dev;
+ SysBusDevice *busdev;
+ SSIBus *spi;
+ DeviceState *flash_dev;
+ int i, j;
+ int num_busses = is_qspi ? NUM_QSPI_BUSSES : 1;
+ int num_ss = is_qspi ? NUM_QSPI_FLASHES : NUM_SPI_FLASHES;
+
+ dev = qdev_create(NULL, is_qspi ? "xlnx.ps7-qspi" : "xlnx.ps7-spi");
+ qdev_prop_set_uint8(dev, "num-txrx-bytes", is_qspi ? 4 : 1);
+ qdev_prop_set_uint8(dev, "num-ss-bits", num_ss);
+ qdev_prop_set_uint8(dev, "num-busses", num_busses);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, base_addr);
+ if (is_qspi) {
+ sysbus_mmio_map(busdev, 1, 0xFC000000);
+ }
+ sysbus_connect_irq(busdev, 0, irq);
+
+ for (i = 0; i < num_busses; ++i) {
+ char bus_name[16];
+ qemu_irq cs_line;
+
+ snprintf(bus_name, 16, "spi%d", i);
+ spi = (SSIBus *)qdev_get_child_bus(dev, bus_name);
+
+ for (j = 0; j < num_ss; ++j) {
+ flash_dev = ssi_create_slave(spi, "n25q128");
+
+ cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0);
+ sysbus_connect_irq(busdev, i * num_ss + j + 1, cs_line);
+ }
+ }
+
+}
+
+static void zynq_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ ObjectClass *cpu_oc;
+ ARMCPU *cpu;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ext_ram = g_new(MemoryRegion, 1);
+ MemoryRegion *ocm_ram = g_new(MemoryRegion, 1);
+ DeviceState *dev;
+ SysBusDevice *busdev;
+ qemu_irq pic[64];
+ Error *err = NULL;
+ int n;
+
+ if (!cpu_model) {
+ cpu_model = "cortex-a9";
+ }
+ cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model);
+
+ cpu = ARM_CPU(object_new(object_class_get_name(cpu_oc)));
+
+ /* By default A9 CPUs have EL3 enabled. This board does not
+ * currently support EL3 so the CPU EL3 property is disabled before
+ * realization.
+ */
+ if (object_property_find(OBJECT(cpu), "has_el3", NULL)) {
+ object_property_set_bool(OBJECT(cpu), false, "has_el3", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ }
+
+ object_property_set_int(OBJECT(cpu), ZYNQ_BOARD_MIDR, "midr", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+
+ object_property_set_int(OBJECT(cpu), MPCORE_PERIPHBASE, "reset-cbar", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+ object_property_set_bool(OBJECT(cpu), true, "realized", &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+
+ /* max 2GB ram */
+ if (ram_size > 0x80000000) {
+ ram_size = 0x80000000;
+ }
+
+ /* DDR remapped to address zero. */
+ memory_region_allocate_system_memory(ext_ram, NULL, "zynq.ext_ram",
+ ram_size);
+ memory_region_add_subregion(address_space_mem, 0, ext_ram);
+
+ /* 256K of on-chip memory */
+ memory_region_init_ram(ocm_ram, NULL, "zynq.ocm_ram", 256 << 10,
+ &error_abort);
+ vmstate_register_ram_global(ocm_ram);
+ memory_region_add_subregion(address_space_mem, 0xFFFC0000, ocm_ram);
+
+ DriveInfo *dinfo = drive_get(IF_PFLASH, 0, 0);
+
+ /* AMD */
+ pflash_cfi02_register(0xe2000000, NULL, "zynq.pflash", FLASH_SIZE,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ FLASH_SECTOR_SIZE,
+ FLASH_SIZE/FLASH_SECTOR_SIZE, 1,
+ 1, 0x0066, 0x0022, 0x0000, 0x0000, 0x0555, 0x2aa,
+ 0);
+
+ dev = qdev_create(NULL, "xilinx,zynq_slcr");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xF8000000);
+
+ dev = qdev_create(NULL, "a9mpcore_priv");
+ qdev_prop_set_uint32(dev, "num-cpu", 1);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, MPCORE_PERIPHBASE);
+ sysbus_connect_irq(busdev, 0,
+ qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ));
+
+ for (n = 0; n < 64; n++) {
+ pic[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ zynq_init_spi_flashes(0xE0006000, pic[58-IRQ_OFFSET], false);
+ zynq_init_spi_flashes(0xE0007000, pic[81-IRQ_OFFSET], false);
+ zynq_init_spi_flashes(0xE000D000, pic[51-IRQ_OFFSET], true);
+
+ sysbus_create_simple("xlnx,ps7-usb", 0xE0002000, pic[53-IRQ_OFFSET]);
+ sysbus_create_simple("xlnx,ps7-usb", 0xE0003000, pic[76-IRQ_OFFSET]);
+
+ sysbus_create_simple("cadence_uart", 0xE0000000, pic[59-IRQ_OFFSET]);
+ sysbus_create_simple("cadence_uart", 0xE0001000, pic[82-IRQ_OFFSET]);
+
+ sysbus_create_varargs("cadence_ttc", 0xF8001000,
+ pic[42-IRQ_OFFSET], pic[43-IRQ_OFFSET], pic[44-IRQ_OFFSET], NULL);
+ sysbus_create_varargs("cadence_ttc", 0xF8002000,
+ pic[69-IRQ_OFFSET], pic[70-IRQ_OFFSET], pic[71-IRQ_OFFSET], NULL);
+
+ gem_init(&nd_table[0], 0xE000B000, pic[54-IRQ_OFFSET]);
+ gem_init(&nd_table[1], 0xE000C000, pic[77-IRQ_OFFSET]);
+
+ dev = qdev_create(NULL, "generic-sdhci");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xE0100000);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[56-IRQ_OFFSET]);
+
+ dev = qdev_create(NULL, "generic-sdhci");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xE0101000);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[79-IRQ_OFFSET]);
+
+ dev = qdev_create(NULL, "pl330");
+ qdev_prop_set_uint8(dev, "num_chnls", 8);
+ qdev_prop_set_uint8(dev, "num_periph_req", 4);
+ qdev_prop_set_uint8(dev, "num_events", 16);
+
+ qdev_prop_set_uint8(dev, "data_width", 64);
+ qdev_prop_set_uint8(dev, "wr_cap", 8);
+ qdev_prop_set_uint8(dev, "wr_q_dep", 16);
+ qdev_prop_set_uint8(dev, "rd_cap", 8);
+ qdev_prop_set_uint8(dev, "rd_q_dep", 16);
+ qdev_prop_set_uint16(dev, "data_buffer_dep", 256);
+
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, 0xF8003000);
+ sysbus_connect_irq(busdev, 0, pic[45-IRQ_OFFSET]); /* abort irq line */
+ for (n = 0; n < 8; ++n) { /* event irqs */
+ sysbus_connect_irq(busdev, n + 1, pic[dma_irqs[n] - IRQ_OFFSET]);
+ }
+
+ zynq_binfo.ram_size = ram_size;
+ zynq_binfo.kernel_filename = kernel_filename;
+ zynq_binfo.kernel_cmdline = kernel_cmdline;
+ zynq_binfo.initrd_filename = initrd_filename;
+ zynq_binfo.nb_cpus = 1;
+ zynq_binfo.board_id = 0xd32;
+ zynq_binfo.loader_start = 0;
+ arm_load_kernel(ARM_CPU(first_cpu), &zynq_binfo);
+}
+
+static QEMUMachine zynq_machine = {
+ .name = "xilinx-zynq-a9",
+ .desc = "Xilinx Zynq Platform Baseboard for Cortex-A9",
+ .init = zynq_init,
+ .block_default_type = IF_SCSI,
+ .max_cpus = 1,
+ .no_sdcard = 1,
+};
+
+static void zynq_machine_init(void)
+{
+ qemu_register_machine(&zynq_machine);
+}
+
+machine_init(zynq_machine_init);
diff --git a/hw/arm/xlnx-ep108.c b/hw/arm/xlnx-ep108.c
new file mode 100644
index 00000000..f94da86c
--- /dev/null
+++ b/hw/arm/xlnx-ep108.c
@@ -0,0 +1,82 @@
+/*
+ * Xilinx ZynqMP EP108 board
+ *
+ * Copyright (C) 2015 Xilinx Inc
+ * Written by Peter Crosthwaite <peter.crosthwaite@xilinx.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "hw/arm/xlnx-zynqmp.h"
+#include "hw/boards.h"
+#include "qemu/error-report.h"
+#include "exec/address-spaces.h"
+
+typedef struct XlnxEP108 {
+ XlnxZynqMPState soc;
+ MemoryRegion ddr_ram;
+} XlnxEP108;
+
+/* Max 2GB RAM */
+#define EP108_MAX_RAM_SIZE 0x80000000ull
+
+static struct arm_boot_info xlnx_ep108_binfo;
+
+static void xlnx_ep108_init(MachineState *machine)
+{
+ XlnxEP108 *s = g_new0(XlnxEP108, 1);
+ Error *err = NULL;
+
+ object_initialize(&s->soc, sizeof(s->soc), TYPE_XLNX_ZYNQMP);
+ object_property_add_child(OBJECT(machine), "soc", OBJECT(&s->soc),
+ &error_abort);
+
+ object_property_set_bool(OBJECT(&s->soc), true, "realized", &err);
+ if (err) {
+ error_report("%s", error_get_pretty(err));
+ exit(1);
+ }
+
+ if (machine->ram_size > EP108_MAX_RAM_SIZE) {
+ error_report("WARNING: RAM size " RAM_ADDR_FMT " above max supported, "
+ "reduced to %llx", machine->ram_size, EP108_MAX_RAM_SIZE);
+ machine->ram_size = EP108_MAX_RAM_SIZE;
+ }
+
+ if (machine->ram_size <= 0x08000000) {
+ qemu_log("WARNING: RAM size " RAM_ADDR_FMT " is small for EP108",
+ machine->ram_size);
+ }
+
+ memory_region_allocate_system_memory(&s->ddr_ram, NULL, "ddr-ram",
+ machine->ram_size);
+ memory_region_add_subregion(get_system_memory(), 0, &s->ddr_ram);
+
+ xlnx_ep108_binfo.ram_size = machine->ram_size;
+ xlnx_ep108_binfo.kernel_filename = machine->kernel_filename;
+ xlnx_ep108_binfo.kernel_cmdline = machine->kernel_cmdline;
+ xlnx_ep108_binfo.initrd_filename = machine->initrd_filename;
+ xlnx_ep108_binfo.loader_start = 0;
+ arm_load_kernel(s->soc.boot_cpu_ptr, &xlnx_ep108_binfo);
+}
+
+static QEMUMachine xlnx_ep108_machine = {
+ .name = "xlnx-ep108",
+ .desc = "Xilinx ZynqMP EP108 board",
+ .init = xlnx_ep108_init,
+};
+
+static void xlnx_ep108_machine_init(void)
+{
+ qemu_register_machine(&xlnx_ep108_machine);
+}
+
+machine_init(xlnx_ep108_machine_init);
diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c
new file mode 100644
index 00000000..5157565f
--- /dev/null
+++ b/hw/arm/xlnx-zynqmp.c
@@ -0,0 +1,272 @@
+/*
+ * Xilinx Zynq MPSoC emulation
+ *
+ * Copyright (C) 2015 Xilinx Inc
+ * Written by Peter Crosthwaite <peter.crosthwaite@xilinx.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "hw/arm/xlnx-zynqmp.h"
+#include "hw/intc/arm_gic_common.h"
+#include "exec/address-spaces.h"
+
+#define GIC_NUM_SPI_INTR 160
+
+#define ARM_PHYS_TIMER_PPI 30
+#define ARM_VIRT_TIMER_PPI 27
+
+#define GIC_BASE_ADDR 0xf9000000
+#define GIC_DIST_ADDR 0xf9010000
+#define GIC_CPU_ADDR 0xf9020000
+
+static const uint64_t gem_addr[XLNX_ZYNQMP_NUM_GEMS] = {
+ 0xFF0B0000, 0xFF0C0000, 0xFF0D0000, 0xFF0E0000,
+};
+
+static const int gem_intr[XLNX_ZYNQMP_NUM_GEMS] = {
+ 57, 59, 61, 63,
+};
+
+static const uint64_t uart_addr[XLNX_ZYNQMP_NUM_UARTS] = {
+ 0xFF000000, 0xFF010000,
+};
+
+static const int uart_intr[XLNX_ZYNQMP_NUM_UARTS] = {
+ 21, 22,
+};
+
+typedef struct XlnxZynqMPGICRegion {
+ int region_index;
+ uint32_t address;
+} XlnxZynqMPGICRegion;
+
+static const XlnxZynqMPGICRegion xlnx_zynqmp_gic_regions[] = {
+ { .region_index = 0, .address = GIC_DIST_ADDR, },
+ { .region_index = 1, .address = GIC_CPU_ADDR, },
+};
+
+static inline int arm_gic_ppi_index(int cpu_nr, int ppi_index)
+{
+ return GIC_NUM_SPI_INTR + cpu_nr * GIC_INTERNAL + ppi_index;
+}
+
+static void xlnx_zynqmp_init(Object *obj)
+{
+ XlnxZynqMPState *s = XLNX_ZYNQMP(obj);
+ int i;
+
+ for (i = 0; i < XLNX_ZYNQMP_NUM_APU_CPUS; i++) {
+ object_initialize(&s->apu_cpu[i], sizeof(s->apu_cpu[i]),
+ "cortex-a53-" TYPE_ARM_CPU);
+ object_property_add_child(obj, "apu-cpu[*]", OBJECT(&s->apu_cpu[i]),
+ &error_abort);
+ }
+
+ for (i = 0; i < XLNX_ZYNQMP_NUM_RPU_CPUS; i++) {
+ object_initialize(&s->rpu_cpu[i], sizeof(s->rpu_cpu[i]),
+ "cortex-r5-" TYPE_ARM_CPU);
+ object_property_add_child(obj, "rpu-cpu[*]", OBJECT(&s->rpu_cpu[i]),
+ &error_abort);
+ }
+
+ object_initialize(&s->gic, sizeof(s->gic), TYPE_ARM_GIC);
+ qdev_set_parent_bus(DEVICE(&s->gic), sysbus_get_default());
+
+ for (i = 0; i < XLNX_ZYNQMP_NUM_GEMS; i++) {
+ object_initialize(&s->gem[i], sizeof(s->gem[i]), TYPE_CADENCE_GEM);
+ qdev_set_parent_bus(DEVICE(&s->gem[i]), sysbus_get_default());
+ }
+
+ for (i = 0; i < XLNX_ZYNQMP_NUM_UARTS; i++) {
+ object_initialize(&s->uart[i], sizeof(s->uart[i]), TYPE_CADENCE_UART);
+ qdev_set_parent_bus(DEVICE(&s->uart[i]), sysbus_get_default());
+ }
+}
+
+static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp)
+{
+ XlnxZynqMPState *s = XLNX_ZYNQMP(dev);
+ MemoryRegion *system_memory = get_system_memory();
+ uint8_t i;
+ const char *boot_cpu = s->boot_cpu ? s->boot_cpu : "apu-cpu[0]";
+ qemu_irq gic_spi[GIC_NUM_SPI_INTR];
+ Error *err = NULL;
+
+ qdev_prop_set_uint32(DEVICE(&s->gic), "num-irq", GIC_NUM_SPI_INTR + 32);
+ qdev_prop_set_uint32(DEVICE(&s->gic), "revision", 2);
+ qdev_prop_set_uint32(DEVICE(&s->gic), "num-cpu", XLNX_ZYNQMP_NUM_APU_CPUS);
+ object_property_set_bool(OBJECT(&s->gic), true, "realized", &err);
+ if (err) {
+ error_propagate((errp), (err));
+ return;
+ }
+ assert(ARRAY_SIZE(xlnx_zynqmp_gic_regions) == XLNX_ZYNQMP_GIC_REGIONS);
+ for (i = 0; i < XLNX_ZYNQMP_GIC_REGIONS; i++) {
+ SysBusDevice *gic = SYS_BUS_DEVICE(&s->gic);
+ const XlnxZynqMPGICRegion *r = &xlnx_zynqmp_gic_regions[i];
+ MemoryRegion *mr = sysbus_mmio_get_region(gic, r->region_index);
+ uint32_t addr = r->address;
+ int j;
+
+ sysbus_mmio_map(gic, r->region_index, addr);
+
+ for (j = 0; j < XLNX_ZYNQMP_GIC_ALIASES; j++) {
+ MemoryRegion *alias = &s->gic_mr[i][j];
+
+ addr += XLNX_ZYNQMP_GIC_REGION_SIZE;
+ memory_region_init_alias(alias, OBJECT(s), "zynqmp-gic-alias", mr,
+ 0, XLNX_ZYNQMP_GIC_REGION_SIZE);
+ memory_region_add_subregion(system_memory, addr, alias);
+ }
+ }
+
+ for (i = 0; i < XLNX_ZYNQMP_NUM_APU_CPUS; i++) {
+ qemu_irq irq;
+ char *name;
+
+ object_property_set_int(OBJECT(&s->apu_cpu[i]), QEMU_PSCI_CONDUIT_SMC,
+ "psci-conduit", &error_abort);
+
+ name = object_get_canonical_path_component(OBJECT(&s->apu_cpu[i]));
+ if (strcmp(name, boot_cpu)) {
+ /* Secondary CPUs start in PSCI powered-down state */
+ object_property_set_bool(OBJECT(&s->apu_cpu[i]), true,
+ "start-powered-off", &error_abort);
+ } else {
+ s->boot_cpu_ptr = &s->apu_cpu[i];
+ }
+ g_free(name);
+
+ object_property_set_int(OBJECT(&s->apu_cpu[i]), GIC_BASE_ADDR,
+ "reset-cbar", &err);
+ if (err) {
+ error_propagate((errp), (err));
+ return;
+ }
+
+ object_property_set_bool(OBJECT(&s->apu_cpu[i]), true, "realized",
+ &err);
+ if (err) {
+ error_propagate((errp), (err));
+ return;
+ }
+
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i,
+ qdev_get_gpio_in(DEVICE(&s->apu_cpu[i]),
+ ARM_CPU_IRQ));
+ irq = qdev_get_gpio_in(DEVICE(&s->gic),
+ arm_gic_ppi_index(i, ARM_PHYS_TIMER_PPI));
+ qdev_connect_gpio_out(DEVICE(&s->apu_cpu[i]), 0, irq);
+ irq = qdev_get_gpio_in(DEVICE(&s->gic),
+ arm_gic_ppi_index(i, ARM_VIRT_TIMER_PPI));
+ qdev_connect_gpio_out(DEVICE(&s->apu_cpu[i]), 1, irq);
+ }
+
+ for (i = 0; i < XLNX_ZYNQMP_NUM_RPU_CPUS; i++) {
+ char *name;
+
+ name = object_get_canonical_path_component(OBJECT(&s->rpu_cpu[i]));
+ if (strcmp(name, boot_cpu)) {
+ /* Secondary CPUs start in PSCI powered-down state */
+ object_property_set_bool(OBJECT(&s->rpu_cpu[i]), true,
+ "start-powered-off", &error_abort);
+ } else {
+ s->boot_cpu_ptr = &s->rpu_cpu[i];
+ }
+ g_free(name);
+
+ object_property_set_bool(OBJECT(&s->rpu_cpu[i]), true, "reset-hivecs",
+ &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ object_property_set_bool(OBJECT(&s->rpu_cpu[i]), true, "realized",
+ &err);
+ if (err) {
+ error_propagate((errp), (err));
+ return;
+ }
+ }
+
+ if (!s->boot_cpu_ptr) {
+ error_setg(errp, "ZynqMP Boot cpu %s not found\n", boot_cpu);
+ return;
+ }
+
+ for (i = 0; i < GIC_NUM_SPI_INTR; i++) {
+ gic_spi[i] = qdev_get_gpio_in(DEVICE(&s->gic), i);
+ }
+
+ for (i = 0; i < XLNX_ZYNQMP_NUM_GEMS; i++) {
+ NICInfo *nd = &nd_table[i];
+
+ if (nd->used) {
+ qemu_check_nic_model(nd, TYPE_CADENCE_GEM);
+ qdev_set_nic_properties(DEVICE(&s->gem[i]), nd);
+ }
+ object_property_set_bool(OBJECT(&s->gem[i]), true, "realized", &err);
+ if (err) {
+ error_propagate((errp), (err));
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->gem[i]), 0, gem_addr[i]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->gem[i]), 0,
+ gic_spi[gem_intr[i]]);
+ }
+
+ for (i = 0; i < XLNX_ZYNQMP_NUM_UARTS; i++) {
+ object_property_set_bool(OBJECT(&s->uart[i]), true, "realized", &err);
+ if (err) {
+ error_propagate((errp), (err));
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_addr[i]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
+ gic_spi[uart_intr[i]]);
+ }
+}
+
+static Property xlnx_zynqmp_props[] = {
+ DEFINE_PROP_STRING("boot-cpu", XlnxZynqMPState, boot_cpu),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void xlnx_zynqmp_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->props = xlnx_zynqmp_props;
+ dc->realize = xlnx_zynqmp_realize;
+
+ /*
+ * Reason: creates an ARM CPU, thus use after free(), see
+ * arm_cpu_class_init()
+ */
+ dc->cannot_destroy_with_object_finalize_yet = true;
+}
+
+static const TypeInfo xlnx_zynqmp_type_info = {
+ .name = TYPE_XLNX_ZYNQMP,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(XlnxZynqMPState),
+ .instance_init = xlnx_zynqmp_init,
+ .class_init = xlnx_zynqmp_class_init,
+};
+
+static void xlnx_zynqmp_register_types(void)
+{
+ type_register_static(&xlnx_zynqmp_type_info);
+}
+
+type_init(xlnx_zynqmp_register_types)
diff --git a/hw/arm/z2.c b/hw/arm/z2.c
new file mode 100644
index 00000000..17355479
--- /dev/null
+++ b/hw/arm/z2.c
@@ -0,0 +1,386 @@
+/*
+ * PXA270-based Zipit Z2 device
+ *
+ * Copyright (c) 2011 by Vasily Khoruzhick <anarsoul@gmail.com>
+ *
+ * Code is based on mainstone platform.
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "hw/i2c/i2c.h"
+#include "hw/ssi.h"
+#include "hw/boards.h"
+#include "sysemu/sysemu.h"
+#include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "ui/console.h"
+#include "audio/audio.h"
+#include "exec/address-spaces.h"
+#include "sysemu/qtest.h"
+
+#ifdef DEBUG_Z2
+#define DPRINTF(fmt, ...) \
+ printf(fmt, ## __VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+static const struct keymap map[0x100] = {
+ [0 ... 0xff] = { -1, -1 },
+ [0x3b] = {0, 0}, /* Option = F1 */
+ [0xc8] = {0, 1}, /* Up */
+ [0xd0] = {0, 2}, /* Down */
+ [0xcb] = {0, 3}, /* Left */
+ [0xcd] = {0, 4}, /* Right */
+ [0xcf] = {0, 5}, /* End */
+ [0x0d] = {0, 6}, /* KPPLUS */
+ [0xc7] = {1, 0}, /* Home */
+ [0x10] = {1, 1}, /* Q */
+ [0x17] = {1, 2}, /* I */
+ [0x22] = {1, 3}, /* G */
+ [0x2d] = {1, 4}, /* X */
+ [0x1c] = {1, 5}, /* Enter */
+ [0x0c] = {1, 6}, /* KPMINUS */
+ [0xc9] = {2, 0}, /* PageUp */
+ [0x11] = {2, 1}, /* W */
+ [0x18] = {2, 2}, /* O */
+ [0x23] = {2, 3}, /* H */
+ [0x2e] = {2, 4}, /* C */
+ [0x38] = {2, 5}, /* LeftAlt */
+ [0xd1] = {3, 0}, /* PageDown */
+ [0x12] = {3, 1}, /* E */
+ [0x19] = {3, 2}, /* P */
+ [0x24] = {3, 3}, /* J */
+ [0x2f] = {3, 4}, /* V */
+ [0x2a] = {3, 5}, /* LeftShift */
+ [0x01] = {4, 0}, /* Esc */
+ [0x13] = {4, 1}, /* R */
+ [0x1e] = {4, 2}, /* A */
+ [0x25] = {4, 3}, /* K */
+ [0x30] = {4, 4}, /* B */
+ [0x1d] = {4, 5}, /* LeftCtrl */
+ [0x0f] = {5, 0}, /* Tab */
+ [0x14] = {5, 1}, /* T */
+ [0x1f] = {5, 2}, /* S */
+ [0x26] = {5, 3}, /* L */
+ [0x31] = {5, 4}, /* N */
+ [0x39] = {5, 5}, /* Space */
+ [0x3c] = {6, 0}, /* Stop = F2 */
+ [0x15] = {6, 1}, /* Y */
+ [0x20] = {6, 2}, /* D */
+ [0x0e] = {6, 3}, /* Backspace */
+ [0x32] = {6, 4}, /* M */
+ [0x33] = {6, 5}, /* Comma */
+ [0x3d] = {7, 0}, /* Play = F3 */
+ [0x16] = {7, 1}, /* U */
+ [0x21] = {7, 2}, /* F */
+ [0x2c] = {7, 3}, /* Z */
+ [0x27] = {7, 4}, /* Semicolon */
+ [0x34] = {7, 5}, /* Dot */
+};
+
+#define Z2_RAM_SIZE 0x02000000
+#define Z2_FLASH_BASE 0x00000000
+#define Z2_FLASH_SIZE 0x00800000
+
+static struct arm_boot_info z2_binfo = {
+ .loader_start = PXA2XX_SDRAM_BASE,
+ .ram_size = Z2_RAM_SIZE,
+};
+
+#define Z2_GPIO_SD_DETECT 96
+#define Z2_GPIO_AC_IN 0
+#define Z2_GPIO_KEY_ON 1
+#define Z2_GPIO_LCD_CS 88
+
+typedef struct {
+ SSISlave ssidev;
+ int32_t selected;
+ int32_t enabled;
+ uint8_t buf[3];
+ uint32_t cur_reg;
+ int pos;
+} ZipitLCD;
+
+static uint32_t zipit_lcd_transfer(SSISlave *dev, uint32_t value)
+{
+ ZipitLCD *z = FROM_SSI_SLAVE(ZipitLCD, dev);
+ uint16_t val;
+ if (z->selected) {
+ z->buf[z->pos] = value & 0xff;
+ z->pos++;
+ }
+ if (z->pos == 3) {
+ switch (z->buf[0]) {
+ case 0x74:
+ DPRINTF("%s: reg: 0x%.2x\n", __func__, z->buf[2]);
+ z->cur_reg = z->buf[2];
+ break;
+ case 0x76:
+ val = z->buf[1] << 8 | z->buf[2];
+ DPRINTF("%s: value: 0x%.4x\n", __func__, val);
+ if (z->cur_reg == 0x22 && val == 0x0000) {
+ z->enabled = 1;
+ printf("%s: LCD enabled\n", __func__);
+ } else if (z->cur_reg == 0x10 && val == 0x0000) {
+ z->enabled = 0;
+ printf("%s: LCD disabled\n", __func__);
+ }
+ break;
+ default:
+ DPRINTF("%s: unknown command!\n", __func__);
+ break;
+ }
+ z->pos = 0;
+ }
+ return 0;
+}
+
+static void z2_lcd_cs(void *opaque, int line, int level)
+{
+ ZipitLCD *z2_lcd = opaque;
+ z2_lcd->selected = !level;
+}
+
+static int zipit_lcd_init(SSISlave *dev)
+{
+ ZipitLCD *z = FROM_SSI_SLAVE(ZipitLCD, dev);
+ z->selected = 0;
+ z->enabled = 0;
+ z->pos = 0;
+
+ return 0;
+}
+
+static VMStateDescription vmstate_zipit_lcd_state = {
+ .name = "zipit-lcd",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_SSI_SLAVE(ssidev, ZipitLCD),
+ VMSTATE_INT32(selected, ZipitLCD),
+ VMSTATE_INT32(enabled, ZipitLCD),
+ VMSTATE_BUFFER(buf, ZipitLCD),
+ VMSTATE_UINT32(cur_reg, ZipitLCD),
+ VMSTATE_INT32(pos, ZipitLCD),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static void zipit_lcd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = zipit_lcd_init;
+ k->transfer = zipit_lcd_transfer;
+ dc->vmsd = &vmstate_zipit_lcd_state;
+}
+
+static const TypeInfo zipit_lcd_info = {
+ .name = "zipit-lcd",
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(ZipitLCD),
+ .class_init = zipit_lcd_class_init,
+};
+
+#define TYPE_AER915 "aer915"
+#define AER915(obj) OBJECT_CHECK(AER915State, (obj), TYPE_AER915)
+
+typedef struct AER915State {
+ I2CSlave parent_obj;
+
+ int len;
+ uint8_t buf[3];
+} AER915State;
+
+static int aer915_send(I2CSlave *i2c, uint8_t data)
+{
+ AER915State *s = AER915(i2c);
+
+ s->buf[s->len] = data;
+ if (s->len++ > 2) {
+ DPRINTF("%s: message too long (%i bytes)\n",
+ __func__, s->len);
+ return 1;
+ }
+
+ if (s->len == 2) {
+ DPRINTF("%s: reg %d value 0x%02x\n", __func__,
+ s->buf[0], s->buf[1]);
+ }
+
+ return 0;
+}
+
+static void aer915_event(I2CSlave *i2c, enum i2c_event event)
+{
+ AER915State *s = AER915(i2c);
+
+ switch (event) {
+ case I2C_START_SEND:
+ s->len = 0;
+ break;
+ case I2C_START_RECV:
+ if (s->len != 1) {
+ DPRINTF("%s: short message!?\n", __func__);
+ }
+ break;
+ case I2C_FINISH:
+ break;
+ default:
+ break;
+ }
+}
+
+static int aer915_recv(I2CSlave *slave)
+{
+ AER915State *s = AER915(slave);
+ int retval = 0x00;
+
+ switch (s->buf[0]) {
+ /* Return hardcoded battery voltage,
+ * 0xf0 means ~4.1V
+ */
+ case 0x02:
+ retval = 0xf0;
+ break;
+ /* Return 0x00 for other regs,
+ * we don't know what they are for,
+ * anyway they return 0x00 on real hardware.
+ */
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+static int aer915_init(I2CSlave *i2c)
+{
+ /* Nothing to do. */
+ return 0;
+}
+
+static VMStateDescription vmstate_aer915_state = {
+ .name = "aer915",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(len, AER915State),
+ VMSTATE_BUFFER(buf, AER915State),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static void aer915_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = aer915_init;
+ k->event = aer915_event;
+ k->recv = aer915_recv;
+ k->send = aer915_send;
+ dc->vmsd = &vmstate_aer915_state;
+}
+
+static const TypeInfo aer915_info = {
+ .name = TYPE_AER915,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(AER915State),
+ .class_init = aer915_class_init,
+};
+
+static void z2_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ MemoryRegion *address_space_mem = get_system_memory();
+ uint32_t sector_len = 0x10000;
+ PXA2xxState *mpu;
+ DriveInfo *dinfo;
+ int be;
+ void *z2_lcd;
+ I2CBus *bus;
+ DeviceState *wm;
+
+ if (!cpu_model) {
+ cpu_model = "pxa270-c5";
+ }
+
+ /* Setup CPU & memory */
+ mpu = pxa270_init(address_space_mem, z2_binfo.ram_size, cpu_model);
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ be = 1;
+#else
+ be = 0;
+#endif
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ if (!dinfo && !qtest_enabled()) {
+ fprintf(stderr, "Flash image must be given with the "
+ "'pflash' parameter\n");
+ exit(1);
+ }
+
+ if (!pflash_cfi01_register(Z2_FLASH_BASE,
+ NULL, "z2.flash0", Z2_FLASH_SIZE,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ sector_len, Z2_FLASH_SIZE / sector_len,
+ 4, 0, 0, 0, 0, be)) {
+ fprintf(stderr, "qemu: Error registering flash memory.\n");
+ exit(1);
+ }
+
+ /* setup keypad */
+ pxa27x_register_keypad(mpu->kp, map, 0x100);
+
+ /* MMC/SD host */
+ pxa2xx_mmci_handlers(mpu->mmc,
+ NULL,
+ qdev_get_gpio_in(mpu->gpio, Z2_GPIO_SD_DETECT));
+
+ type_register_static(&zipit_lcd_info);
+ type_register_static(&aer915_info);
+ z2_lcd = ssi_create_slave(mpu->ssp[1], "zipit-lcd");
+ bus = pxa2xx_i2c_bus(mpu->i2c[0]);
+ i2c_create_slave(bus, TYPE_AER915, 0x55);
+ wm = i2c_create_slave(bus, "wm8750", 0x1b);
+ mpu->i2s->opaque = wm;
+ mpu->i2s->codec_out = wm8750_dac_dat;
+ mpu->i2s->codec_in = wm8750_adc_dat;
+ wm8750_data_req_set(wm, mpu->i2s->data_req, mpu->i2s);
+
+ qdev_connect_gpio_out(mpu->gpio, Z2_GPIO_LCD_CS,
+ qemu_allocate_irq(z2_lcd_cs, z2_lcd, 0));
+
+ z2_binfo.kernel_filename = kernel_filename;
+ z2_binfo.kernel_cmdline = kernel_cmdline;
+ z2_binfo.initrd_filename = initrd_filename;
+ z2_binfo.board_id = 0x6dd;
+ arm_load_kernel(mpu->cpu, &z2_binfo);
+}
+
+static QEMUMachine z2_machine = {
+ .name = "z2",
+ .desc = "Zipit Z2 (PXA27x)",
+ .init = z2_init,
+};
+
+static void z2_machine_init(void)
+{
+ qemu_register_machine(&z2_machine);
+}
+
+machine_init(z2_machine_init);
diff --git a/hw/audio/Makefile.objs b/hw/audio/Makefile.objs
new file mode 100644
index 00000000..7ce85a2e
--- /dev/null
+++ b/hw/audio/Makefile.objs
@@ -0,0 +1,18 @@
+# Sound
+common-obj-$(CONFIG_SB16) += sb16.o
+common-obj-$(CONFIG_ES1370) += es1370.o
+common-obj-$(CONFIG_AC97) += ac97.o
+common-obj-$(CONFIG_ADLIB) += fmopl.o adlib.o
+common-obj-$(CONFIG_GUS) += gus.o gusemu_hal.o gusemu_mixer.o
+common-obj-$(CONFIG_CS4231A) += cs4231a.o
+common-obj-$(CONFIG_HDA) += intel-hda.o hda-codec.o
+
+common-obj-$(CONFIG_PCSPK) += pcspk.o
+common-obj-$(CONFIG_WM8750) += wm8750.o
+common-obj-$(CONFIG_PL041) += pl041.o lm4549.o
+
+common-obj-$(CONFIG_CS4231) += cs4231.o
+common-obj-$(CONFIG_MARVELL_88W8618) += marvell_88w8618.o
+common-obj-$(CONFIG_MILKYMIST) += milkymist-ac97.o
+
+$(obj)/adlib.o $(obj)/fmopl.o: QEMU_CFLAGS += -DBUILD_Y8950=0
diff --git a/hw/audio/ac97.c b/hw/audio/ac97.c
new file mode 100644
index 00000000..b1738355
--- /dev/null
+++ b/hw/audio/ac97.c
@@ -0,0 +1,1430 @@
+/*
+ * Copyright (C) 2006 InnoTek Systemberatung GmbH
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation,
+ * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
+ * distribution. VirtualBox OSE is distributed in the hope that it will
+ * be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * If you received this file as part of a commercial VirtualBox
+ * distribution, then only the terms of your commercial VirtualBox
+ * license agreement apply instead of the previous paragraph.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/audio/audio.h"
+#include "audio/audio.h"
+#include "hw/pci/pci.h"
+#include "sysemu/dma.h"
+
+enum {
+ AC97_Reset = 0x00,
+ AC97_Master_Volume_Mute = 0x02,
+ AC97_Headphone_Volume_Mute = 0x04,
+ AC97_Master_Volume_Mono_Mute = 0x06,
+ AC97_Master_Tone_RL = 0x08,
+ AC97_PC_BEEP_Volume_Mute = 0x0A,
+ AC97_Phone_Volume_Mute = 0x0C,
+ AC97_Mic_Volume_Mute = 0x0E,
+ AC97_Line_In_Volume_Mute = 0x10,
+ AC97_CD_Volume_Mute = 0x12,
+ AC97_Video_Volume_Mute = 0x14,
+ AC97_Aux_Volume_Mute = 0x16,
+ AC97_PCM_Out_Volume_Mute = 0x18,
+ AC97_Record_Select = 0x1A,
+ AC97_Record_Gain_Mute = 0x1C,
+ AC97_Record_Gain_Mic_Mute = 0x1E,
+ AC97_General_Purpose = 0x20,
+ AC97_3D_Control = 0x22,
+ AC97_AC_97_RESERVED = 0x24,
+ AC97_Powerdown_Ctrl_Stat = 0x26,
+ AC97_Extended_Audio_ID = 0x28,
+ AC97_Extended_Audio_Ctrl_Stat = 0x2A,
+ AC97_PCM_Front_DAC_Rate = 0x2C,
+ AC97_PCM_Surround_DAC_Rate = 0x2E,
+ AC97_PCM_LFE_DAC_Rate = 0x30,
+ AC97_PCM_LR_ADC_Rate = 0x32,
+ AC97_MIC_ADC_Rate = 0x34,
+ AC97_6Ch_Vol_C_LFE_Mute = 0x36,
+ AC97_6Ch_Vol_L_R_Surround_Mute = 0x38,
+ AC97_Vendor_Reserved = 0x58,
+ AC97_Sigmatel_Analog = 0x6c, /* We emulate a Sigmatel codec */
+ AC97_Sigmatel_Dac2Invert = 0x6e, /* We emulate a Sigmatel codec */
+ AC97_Vendor_ID1 = 0x7c,
+ AC97_Vendor_ID2 = 0x7e
+};
+
+#define SOFT_VOLUME
+#define SR_FIFOE 16 /* rwc */
+#define SR_BCIS 8 /* rwc */
+#define SR_LVBCI 4 /* rwc */
+#define SR_CELV 2 /* ro */
+#define SR_DCH 1 /* ro */
+#define SR_VALID_MASK ((1 << 5) - 1)
+#define SR_WCLEAR_MASK (SR_FIFOE | SR_BCIS | SR_LVBCI)
+#define SR_RO_MASK (SR_DCH | SR_CELV)
+#define SR_INT_MASK (SR_FIFOE | SR_BCIS | SR_LVBCI)
+
+#define CR_IOCE 16 /* rw */
+#define CR_FEIE 8 /* rw */
+#define CR_LVBIE 4 /* rw */
+#define CR_RR 2 /* rw */
+#define CR_RPBM 1 /* rw */
+#define CR_VALID_MASK ((1 << 5) - 1)
+#define CR_DONT_CLEAR_MASK (CR_IOCE | CR_FEIE | CR_LVBIE)
+
+#define GC_WR 4 /* rw */
+#define GC_CR 2 /* rw */
+#define GC_VALID_MASK ((1 << 6) - 1)
+
+#define GS_MD3 (1<<17) /* rw */
+#define GS_AD3 (1<<16) /* rw */
+#define GS_RCS (1<<15) /* rwc */
+#define GS_B3S12 (1<<14) /* ro */
+#define GS_B2S12 (1<<13) /* ro */
+#define GS_B1S12 (1<<12) /* ro */
+#define GS_S1R1 (1<<11) /* rwc */
+#define GS_S0R1 (1<<10) /* rwc */
+#define GS_S1CR (1<<9) /* ro */
+#define GS_S0CR (1<<8) /* ro */
+#define GS_MINT (1<<7) /* ro */
+#define GS_POINT (1<<6) /* ro */
+#define GS_PIINT (1<<5) /* ro */
+#define GS_RSRVD ((1<<4)|(1<<3))
+#define GS_MOINT (1<<2) /* ro */
+#define GS_MIINT (1<<1) /* ro */
+#define GS_GSCI 1 /* rwc */
+#define GS_RO_MASK (GS_B3S12| \
+ GS_B2S12| \
+ GS_B1S12| \
+ GS_S1CR| \
+ GS_S0CR| \
+ GS_MINT| \
+ GS_POINT| \
+ GS_PIINT| \
+ GS_RSRVD| \
+ GS_MOINT| \
+ GS_MIINT)
+#define GS_VALID_MASK ((1 << 18) - 1)
+#define GS_WCLEAR_MASK (GS_RCS|GS_S1R1|GS_S0R1|GS_GSCI)
+
+#define BD_IOC (1<<31)
+#define BD_BUP (1<<30)
+
+#define EACS_VRA 1
+#define EACS_VRM 8
+
+#define MUTE_SHIFT 15
+
+#define REC_MASK 7
+enum {
+ REC_MIC = 0,
+ REC_CD,
+ REC_VIDEO,
+ REC_AUX,
+ REC_LINE_IN,
+ REC_STEREO_MIX,
+ REC_MONO_MIX,
+ REC_PHONE
+};
+
+typedef struct BD {
+ uint32_t addr;
+ uint32_t ctl_len;
+} BD;
+
+typedef struct AC97BusMasterRegs {
+ uint32_t bdbar; /* rw 0 */
+ uint8_t civ; /* ro 0 */
+ uint8_t lvi; /* rw 0 */
+ uint16_t sr; /* rw 1 */
+ uint16_t picb; /* ro 0 */
+ uint8_t piv; /* ro 0 */
+ uint8_t cr; /* rw 0 */
+ unsigned int bd_valid;
+ BD bd;
+} AC97BusMasterRegs;
+
+typedef struct AC97LinkState {
+ PCIDevice dev;
+ QEMUSoundCard card;
+ uint32_t use_broken_id;
+ uint32_t glob_cnt;
+ uint32_t glob_sta;
+ uint32_t cas;
+ uint32_t last_samp;
+ AC97BusMasterRegs bm_regs[3];
+ uint8_t mixer_data[256];
+ SWVoiceIn *voice_pi;
+ SWVoiceOut *voice_po;
+ SWVoiceIn *voice_mc;
+ int invalid_freq[3];
+ uint8_t silence[128];
+ int bup_flag;
+ MemoryRegion io_nam;
+ MemoryRegion io_nabm;
+} AC97LinkState;
+
+enum {
+ BUP_SET = 1,
+ BUP_LAST = 2
+};
+
+#ifdef DEBUG_AC97
+#define dolog(...) AUD_log ("ac97", __VA_ARGS__)
+#else
+#define dolog(...)
+#endif
+
+#define MKREGS(prefix, start) \
+enum { \
+ prefix ## _BDBAR = start, \
+ prefix ## _CIV = start + 4, \
+ prefix ## _LVI = start + 5, \
+ prefix ## _SR = start + 6, \
+ prefix ## _PICB = start + 8, \
+ prefix ## _PIV = start + 10, \
+ prefix ## _CR = start + 11 \
+}
+
+enum {
+ PI_INDEX = 0,
+ PO_INDEX,
+ MC_INDEX,
+ LAST_INDEX
+};
+
+MKREGS (PI, PI_INDEX * 16);
+MKREGS (PO, PO_INDEX * 16);
+MKREGS (MC, MC_INDEX * 16);
+
+enum {
+ GLOB_CNT = 0x2c,
+ GLOB_STA = 0x30,
+ CAS = 0x34
+};
+
+#define GET_BM(index) (((index) >> 4) & 3)
+
+static void po_callback (void *opaque, int free);
+static void pi_callback (void *opaque, int avail);
+static void mc_callback (void *opaque, int avail);
+
+static void warm_reset (AC97LinkState *s)
+{
+ (void) s;
+}
+
+static void cold_reset (AC97LinkState * s)
+{
+ (void) s;
+}
+
+static void fetch_bd (AC97LinkState *s, AC97BusMasterRegs *r)
+{
+ uint8_t b[8];
+
+ pci_dma_read (&s->dev, r->bdbar + r->civ * 8, b, 8);
+ r->bd_valid = 1;
+ r->bd.addr = le32_to_cpu (*(uint32_t *) &b[0]) & ~3;
+ r->bd.ctl_len = le32_to_cpu (*(uint32_t *) &b[4]);
+ r->picb = r->bd.ctl_len & 0xffff;
+ dolog ("bd %2d addr=%#x ctl=%#06x len=%#x(%d bytes)\n",
+ r->civ, r->bd.addr, r->bd.ctl_len >> 16,
+ r->bd.ctl_len & 0xffff,
+ (r->bd.ctl_len & 0xffff) << 1);
+}
+
+static void update_sr (AC97LinkState *s, AC97BusMasterRegs *r, uint32_t new_sr)
+{
+ int event = 0;
+ int level = 0;
+ uint32_t new_mask = new_sr & SR_INT_MASK;
+ uint32_t old_mask = r->sr & SR_INT_MASK;
+ uint32_t masks[] = {GS_PIINT, GS_POINT, GS_MINT};
+
+ if (new_mask ^ old_mask) {
+ /** @todo is IRQ deasserted when only one of status bits is cleared? */
+ if (!new_mask) {
+ event = 1;
+ level = 0;
+ }
+ else {
+ if ((new_mask & SR_LVBCI) && (r->cr & CR_LVBIE)) {
+ event = 1;
+ level = 1;
+ }
+ if ((new_mask & SR_BCIS) && (r->cr & CR_IOCE)) {
+ event = 1;
+ level = 1;
+ }
+ }
+ }
+
+ r->sr = new_sr;
+
+ dolog ("IOC%d LVB%d sr=%#x event=%d level=%d\n",
+ r->sr & SR_BCIS, r->sr & SR_LVBCI,
+ r->sr,
+ event, level);
+
+ if (!event)
+ return;
+
+ if (level) {
+ s->glob_sta |= masks[r - s->bm_regs];
+ dolog ("set irq level=1\n");
+ pci_irq_assert(&s->dev);
+ }
+ else {
+ s->glob_sta &= ~masks[r - s->bm_regs];
+ dolog ("set irq level=0\n");
+ pci_irq_deassert(&s->dev);
+ }
+}
+
+static void voice_set_active (AC97LinkState *s, int bm_index, int on)
+{
+ switch (bm_index) {
+ case PI_INDEX:
+ AUD_set_active_in (s->voice_pi, on);
+ break;
+
+ case PO_INDEX:
+ AUD_set_active_out (s->voice_po, on);
+ break;
+
+ case MC_INDEX:
+ AUD_set_active_in (s->voice_mc, on);
+ break;
+
+ default:
+ AUD_log ("ac97", "invalid bm_index(%d) in voice_set_active", bm_index);
+ break;
+ }
+}
+
+static void reset_bm_regs (AC97LinkState *s, AC97BusMasterRegs *r)
+{
+ dolog ("reset_bm_regs\n");
+ r->bdbar = 0;
+ r->civ = 0;
+ r->lvi = 0;
+ /** todo do we need to do that? */
+ update_sr (s, r, SR_DCH);
+ r->picb = 0;
+ r->piv = 0;
+ r->cr = r->cr & CR_DONT_CLEAR_MASK;
+ r->bd_valid = 0;
+
+ voice_set_active (s, r - s->bm_regs, 0);
+ memset (s->silence, 0, sizeof (s->silence));
+}
+
+static void mixer_store (AC97LinkState *s, uint32_t i, uint16_t v)
+{
+ if (i + 2 > sizeof (s->mixer_data)) {
+ dolog ("mixer_store: index %d out of bounds %zd\n",
+ i, sizeof (s->mixer_data));
+ return;
+ }
+
+ s->mixer_data[i + 0] = v & 0xff;
+ s->mixer_data[i + 1] = v >> 8;
+}
+
+static uint16_t mixer_load (AC97LinkState *s, uint32_t i)
+{
+ uint16_t val = 0xffff;
+
+ if (i + 2 > sizeof (s->mixer_data)) {
+ dolog ("mixer_load: index %d out of bounds %zd\n",
+ i, sizeof (s->mixer_data));
+ }
+ else {
+ val = s->mixer_data[i + 0] | (s->mixer_data[i + 1] << 8);
+ }
+
+ return val;
+}
+
+static void open_voice (AC97LinkState *s, int index, int freq)
+{
+ struct audsettings as;
+
+ as.freq = freq;
+ as.nchannels = 2;
+ as.fmt = AUD_FMT_S16;
+ as.endianness = 0;
+
+ if (freq > 0) {
+ s->invalid_freq[index] = 0;
+ switch (index) {
+ case PI_INDEX:
+ s->voice_pi = AUD_open_in (
+ &s->card,
+ s->voice_pi,
+ "ac97.pi",
+ s,
+ pi_callback,
+ &as
+ );
+ break;
+
+ case PO_INDEX:
+ s->voice_po = AUD_open_out (
+ &s->card,
+ s->voice_po,
+ "ac97.po",
+ s,
+ po_callback,
+ &as
+ );
+ break;
+
+ case MC_INDEX:
+ s->voice_mc = AUD_open_in (
+ &s->card,
+ s->voice_mc,
+ "ac97.mc",
+ s,
+ mc_callback,
+ &as
+ );
+ break;
+ }
+ }
+ else {
+ s->invalid_freq[index] = freq;
+ switch (index) {
+ case PI_INDEX:
+ AUD_close_in (&s->card, s->voice_pi);
+ s->voice_pi = NULL;
+ break;
+
+ case PO_INDEX:
+ AUD_close_out (&s->card, s->voice_po);
+ s->voice_po = NULL;
+ break;
+
+ case MC_INDEX:
+ AUD_close_in (&s->card, s->voice_mc);
+ s->voice_mc = NULL;
+ break;
+ }
+ }
+}
+
+static void reset_voices (AC97LinkState *s, uint8_t active[LAST_INDEX])
+{
+ uint16_t freq;
+
+ freq = mixer_load (s, AC97_PCM_LR_ADC_Rate);
+ open_voice (s, PI_INDEX, freq);
+ AUD_set_active_in (s->voice_pi, active[PI_INDEX]);
+
+ freq = mixer_load (s, AC97_PCM_Front_DAC_Rate);
+ open_voice (s, PO_INDEX, freq);
+ AUD_set_active_out (s->voice_po, active[PO_INDEX]);
+
+ freq = mixer_load (s, AC97_MIC_ADC_Rate);
+ open_voice (s, MC_INDEX, freq);
+ AUD_set_active_in (s->voice_mc, active[MC_INDEX]);
+}
+
+static void get_volume (uint16_t vol, uint16_t mask, int inverse,
+ int *mute, uint8_t *lvol, uint8_t *rvol)
+{
+ *mute = (vol >> MUTE_SHIFT) & 1;
+ *rvol = (255 * (vol & mask)) / mask;
+ *lvol = (255 * ((vol >> 8) & mask)) / mask;
+
+ if (inverse) {
+ *rvol = 255 - *rvol;
+ *lvol = 255 - *lvol;
+ }
+}
+
+static void update_combined_volume_out (AC97LinkState *s)
+{
+ uint8_t lvol, rvol, plvol, prvol;
+ int mute, pmute;
+
+ get_volume (mixer_load (s, AC97_Master_Volume_Mute), 0x3f, 1,
+ &mute, &lvol, &rvol);
+ get_volume (mixer_load (s, AC97_PCM_Out_Volume_Mute), 0x1f, 1,
+ &pmute, &plvol, &prvol);
+
+ mute = mute | pmute;
+ lvol = (lvol * plvol) / 255;
+ rvol = (rvol * prvol) / 255;
+
+ AUD_set_volume_out (s->voice_po, mute, lvol, rvol);
+}
+
+static void update_volume_in (AC97LinkState *s)
+{
+ uint8_t lvol, rvol;
+ int mute;
+
+ get_volume (mixer_load (s, AC97_Record_Gain_Mute), 0x0f, 0,
+ &mute, &lvol, &rvol);
+
+ AUD_set_volume_in (s->voice_pi, mute, lvol, rvol);
+}
+
+static void set_volume (AC97LinkState *s, int index, uint32_t val)
+{
+ switch (index) {
+ case AC97_Master_Volume_Mute:
+ val &= 0xbf3f;
+ mixer_store (s, index, val);
+ update_combined_volume_out (s);
+ break;
+ case AC97_PCM_Out_Volume_Mute:
+ val &= 0x9f1f;
+ mixer_store (s, index, val);
+ update_combined_volume_out (s);
+ break;
+ case AC97_Record_Gain_Mute:
+ val &= 0x8f0f;
+ mixer_store (s, index, val);
+ update_volume_in (s);
+ break;
+ }
+}
+
+static void record_select (AC97LinkState *s, uint32_t val)
+{
+ uint8_t rs = val & REC_MASK;
+ uint8_t ls = (val >> 8) & REC_MASK;
+ mixer_store (s, AC97_Record_Select, rs | (ls << 8));
+}
+
+static void mixer_reset (AC97LinkState *s)
+{
+ uint8_t active[LAST_INDEX];
+
+ dolog ("mixer_reset\n");
+ memset (s->mixer_data, 0, sizeof (s->mixer_data));
+ memset (active, 0, sizeof (active));
+ mixer_store (s, AC97_Reset , 0x0000); /* 6940 */
+ mixer_store (s, AC97_Headphone_Volume_Mute , 0x0000);
+ mixer_store (s, AC97_Master_Volume_Mono_Mute , 0x0000);
+ mixer_store (s, AC97_Master_Tone_RL, 0x0000);
+ mixer_store (s, AC97_PC_BEEP_Volume_Mute , 0x0000);
+ mixer_store (s, AC97_Phone_Volume_Mute , 0x0000);
+ mixer_store (s, AC97_Mic_Volume_Mute , 0x0000);
+ mixer_store (s, AC97_Line_In_Volume_Mute , 0x0000);
+ mixer_store (s, AC97_CD_Volume_Mute , 0x0000);
+ mixer_store (s, AC97_Video_Volume_Mute , 0x0000);
+ mixer_store (s, AC97_Aux_Volume_Mute , 0x0000);
+ mixer_store (s, AC97_Record_Gain_Mic_Mute , 0x0000);
+ mixer_store (s, AC97_General_Purpose , 0x0000);
+ mixer_store (s, AC97_3D_Control , 0x0000);
+ mixer_store (s, AC97_Powerdown_Ctrl_Stat , 0x000f);
+
+ /*
+ * Sigmatel 9700 (STAC9700)
+ */
+ mixer_store (s, AC97_Vendor_ID1 , 0x8384);
+ mixer_store (s, AC97_Vendor_ID2 , 0x7600); /* 7608 */
+
+ mixer_store (s, AC97_Extended_Audio_ID , 0x0809);
+ mixer_store (s, AC97_Extended_Audio_Ctrl_Stat, 0x0009);
+ mixer_store (s, AC97_PCM_Front_DAC_Rate , 0xbb80);
+ mixer_store (s, AC97_PCM_Surround_DAC_Rate , 0xbb80);
+ mixer_store (s, AC97_PCM_LFE_DAC_Rate , 0xbb80);
+ mixer_store (s, AC97_PCM_LR_ADC_Rate , 0xbb80);
+ mixer_store (s, AC97_MIC_ADC_Rate , 0xbb80);
+
+ record_select (s, 0);
+ set_volume (s, AC97_Master_Volume_Mute, 0x8000);
+ set_volume (s, AC97_PCM_Out_Volume_Mute, 0x8808);
+ set_volume (s, AC97_Record_Gain_Mute, 0x8808);
+
+ reset_voices (s, active);
+}
+
+/**
+ * Native audio mixer
+ * I/O Reads
+ */
+static uint32_t nam_readb (void *opaque, uint32_t addr)
+{
+ AC97LinkState *s = opaque;
+ dolog ("U nam readb %#x\n", addr);
+ s->cas = 0;
+ return ~0U;
+}
+
+static uint32_t nam_readw (void *opaque, uint32_t addr)
+{
+ AC97LinkState *s = opaque;
+ uint32_t val = ~0U;
+ uint32_t index = addr;
+ s->cas = 0;
+ val = mixer_load (s, index);
+ return val;
+}
+
+static uint32_t nam_readl (void *opaque, uint32_t addr)
+{
+ AC97LinkState *s = opaque;
+ dolog ("U nam readl %#x\n", addr);
+ s->cas = 0;
+ return ~0U;
+}
+
+/**
+ * Native audio mixer
+ * I/O Writes
+ */
+static void nam_writeb (void *opaque, uint32_t addr, uint32_t val)
+{
+ AC97LinkState *s = opaque;
+ dolog ("U nam writeb %#x <- %#x\n", addr, val);
+ s->cas = 0;
+}
+
+static void nam_writew (void *opaque, uint32_t addr, uint32_t val)
+{
+ AC97LinkState *s = opaque;
+ uint32_t index = addr;
+ s->cas = 0;
+ switch (index) {
+ case AC97_Reset:
+ mixer_reset (s);
+ break;
+ case AC97_Powerdown_Ctrl_Stat:
+ val &= ~0x800f;
+ val |= mixer_load (s, index) & 0xf;
+ mixer_store (s, index, val);
+ break;
+ case AC97_PCM_Out_Volume_Mute:
+ case AC97_Master_Volume_Mute:
+ case AC97_Record_Gain_Mute:
+ set_volume (s, index, val);
+ break;
+ case AC97_Record_Select:
+ record_select (s, val);
+ break;
+ case AC97_Vendor_ID1:
+ case AC97_Vendor_ID2:
+ dolog ("Attempt to write vendor ID to %#x\n", val);
+ break;
+ case AC97_Extended_Audio_ID:
+ dolog ("Attempt to write extended audio ID to %#x\n", val);
+ break;
+ case AC97_Extended_Audio_Ctrl_Stat:
+ if (!(val & EACS_VRA)) {
+ mixer_store (s, AC97_PCM_Front_DAC_Rate, 0xbb80);
+ mixer_store (s, AC97_PCM_LR_ADC_Rate, 0xbb80);
+ open_voice (s, PI_INDEX, 48000);
+ open_voice (s, PO_INDEX, 48000);
+ }
+ if (!(val & EACS_VRM)) {
+ mixer_store (s, AC97_MIC_ADC_Rate, 0xbb80);
+ open_voice (s, MC_INDEX, 48000);
+ }
+ dolog ("Setting extended audio control to %#x\n", val);
+ mixer_store (s, AC97_Extended_Audio_Ctrl_Stat, val);
+ break;
+ case AC97_PCM_Front_DAC_Rate:
+ if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) {
+ mixer_store (s, index, val);
+ dolog ("Set front DAC rate to %d\n", val);
+ open_voice (s, PO_INDEX, val);
+ }
+ else {
+ dolog ("Attempt to set front DAC rate to %d, "
+ "but VRA is not set\n",
+ val);
+ }
+ break;
+ case AC97_MIC_ADC_Rate:
+ if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRM) {
+ mixer_store (s, index, val);
+ dolog ("Set MIC ADC rate to %d\n", val);
+ open_voice (s, MC_INDEX, val);
+ }
+ else {
+ dolog ("Attempt to set MIC ADC rate to %d, "
+ "but VRM is not set\n",
+ val);
+ }
+ break;
+ case AC97_PCM_LR_ADC_Rate:
+ if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) {
+ mixer_store (s, index, val);
+ dolog ("Set front LR ADC rate to %d\n", val);
+ open_voice (s, PI_INDEX, val);
+ }
+ else {
+ dolog ("Attempt to set LR ADC rate to %d, but VRA is not set\n",
+ val);
+ }
+ break;
+ case AC97_Headphone_Volume_Mute:
+ case AC97_Master_Volume_Mono_Mute:
+ case AC97_Master_Tone_RL:
+ case AC97_PC_BEEP_Volume_Mute:
+ case AC97_Phone_Volume_Mute:
+ case AC97_Mic_Volume_Mute:
+ case AC97_Line_In_Volume_Mute:
+ case AC97_CD_Volume_Mute:
+ case AC97_Video_Volume_Mute:
+ case AC97_Aux_Volume_Mute:
+ case AC97_Record_Gain_Mic_Mute:
+ case AC97_General_Purpose:
+ case AC97_3D_Control:
+ case AC97_Sigmatel_Analog:
+ case AC97_Sigmatel_Dac2Invert:
+ /* None of the features in these regs are emulated, so they are RO */
+ break;
+ default:
+ dolog ("U nam writew %#x <- %#x\n", addr, val);
+ mixer_store (s, index, val);
+ break;
+ }
+}
+
+static void nam_writel (void *opaque, uint32_t addr, uint32_t val)
+{
+ AC97LinkState *s = opaque;
+ dolog ("U nam writel %#x <- %#x\n", addr, val);
+ s->cas = 0;
+}
+
+/**
+ * Native audio bus master
+ * I/O Reads
+ */
+static uint32_t nabm_readb (void *opaque, uint32_t addr)
+{
+ AC97LinkState *s = opaque;
+ AC97BusMasterRegs *r = NULL;
+ uint32_t index = addr;
+ uint32_t val = ~0U;
+
+ switch (index) {
+ case CAS:
+ dolog ("CAS %d\n", s->cas);
+ val = s->cas;
+ s->cas = 1;
+ break;
+ case PI_CIV:
+ case PO_CIV:
+ case MC_CIV:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->civ;
+ dolog ("CIV[%d] -> %#x\n", GET_BM (index), val);
+ break;
+ case PI_LVI:
+ case PO_LVI:
+ case MC_LVI:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->lvi;
+ dolog ("LVI[%d] -> %#x\n", GET_BM (index), val);
+ break;
+ case PI_PIV:
+ case PO_PIV:
+ case MC_PIV:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->piv;
+ dolog ("PIV[%d] -> %#x\n", GET_BM (index), val);
+ break;
+ case PI_CR:
+ case PO_CR:
+ case MC_CR:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->cr;
+ dolog ("CR[%d] -> %#x\n", GET_BM (index), val);
+ break;
+ case PI_SR:
+ case PO_SR:
+ case MC_SR:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->sr & 0xff;
+ dolog ("SRb[%d] -> %#x\n", GET_BM (index), val);
+ break;
+ default:
+ dolog ("U nabm readb %#x -> %#x\n", addr, val);
+ break;
+ }
+ return val;
+}
+
+static uint32_t nabm_readw (void *opaque, uint32_t addr)
+{
+ AC97LinkState *s = opaque;
+ AC97BusMasterRegs *r = NULL;
+ uint32_t index = addr;
+ uint32_t val = ~0U;
+
+ switch (index) {
+ case PI_SR:
+ case PO_SR:
+ case MC_SR:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->sr;
+ dolog ("SR[%d] -> %#x\n", GET_BM (index), val);
+ break;
+ case PI_PICB:
+ case PO_PICB:
+ case MC_PICB:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->picb;
+ dolog ("PICB[%d] -> %#x\n", GET_BM (index), val);
+ break;
+ default:
+ dolog ("U nabm readw %#x -> %#x\n", addr, val);
+ break;
+ }
+ return val;
+}
+
+static uint32_t nabm_readl (void *opaque, uint32_t addr)
+{
+ AC97LinkState *s = opaque;
+ AC97BusMasterRegs *r = NULL;
+ uint32_t index = addr;
+ uint32_t val = ~0U;
+
+ switch (index) {
+ case PI_BDBAR:
+ case PO_BDBAR:
+ case MC_BDBAR:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->bdbar;
+ dolog ("BMADDR[%d] -> %#x\n", GET_BM (index), val);
+ break;
+ case PI_CIV:
+ case PO_CIV:
+ case MC_CIV:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->civ | (r->lvi << 8) | (r->sr << 16);
+ dolog ("CIV LVI SR[%d] -> %#x, %#x, %#x\n", GET_BM (index),
+ r->civ, r->lvi, r->sr);
+ break;
+ case PI_PICB:
+ case PO_PICB:
+ case MC_PICB:
+ r = &s->bm_regs[GET_BM (index)];
+ val = r->picb | (r->piv << 16) | (r->cr << 24);
+ dolog ("PICB PIV CR[%d] -> %#x %#x %#x %#x\n", GET_BM (index),
+ val, r->picb, r->piv, r->cr);
+ break;
+ case GLOB_CNT:
+ val = s->glob_cnt;
+ dolog ("glob_cnt -> %#x\n", val);
+ break;
+ case GLOB_STA:
+ val = s->glob_sta | GS_S0CR;
+ dolog ("glob_sta -> %#x\n", val);
+ break;
+ default:
+ dolog ("U nabm readl %#x -> %#x\n", addr, val);
+ break;
+ }
+ return val;
+}
+
+/**
+ * Native audio bus master
+ * I/O Writes
+ */
+static void nabm_writeb (void *opaque, uint32_t addr, uint32_t val)
+{
+ AC97LinkState *s = opaque;
+ AC97BusMasterRegs *r = NULL;
+ uint32_t index = addr;
+ switch (index) {
+ case PI_LVI:
+ case PO_LVI:
+ case MC_LVI:
+ r = &s->bm_regs[GET_BM (index)];
+ if ((r->cr & CR_RPBM) && (r->sr & SR_DCH)) {
+ r->sr &= ~(SR_DCH | SR_CELV);
+ r->civ = r->piv;
+ r->piv = (r->piv + 1) % 32;
+ fetch_bd (s, r);
+ }
+ r->lvi = val % 32;
+ dolog ("LVI[%d] <- %#x\n", GET_BM (index), val);
+ break;
+ case PI_CR:
+ case PO_CR:
+ case MC_CR:
+ r = &s->bm_regs[GET_BM (index)];
+ if (val & CR_RR) {
+ reset_bm_regs (s, r);
+ }
+ else {
+ r->cr = val & CR_VALID_MASK;
+ if (!(r->cr & CR_RPBM)) {
+ voice_set_active (s, r - s->bm_regs, 0);
+ r->sr |= SR_DCH;
+ }
+ else {
+ r->civ = r->piv;
+ r->piv = (r->piv + 1) % 32;
+ fetch_bd (s, r);
+ r->sr &= ~SR_DCH;
+ voice_set_active (s, r - s->bm_regs, 1);
+ }
+ }
+ dolog ("CR[%d] <- %#x (cr %#x)\n", GET_BM (index), val, r->cr);
+ break;
+ case PI_SR:
+ case PO_SR:
+ case MC_SR:
+ r = &s->bm_regs[GET_BM (index)];
+ r->sr |= val & ~(SR_RO_MASK | SR_WCLEAR_MASK);
+ update_sr (s, r, r->sr & ~(val & SR_WCLEAR_MASK));
+ dolog ("SR[%d] <- %#x (sr %#x)\n", GET_BM (index), val, r->sr);
+ break;
+ default:
+ dolog ("U nabm writeb %#x <- %#x\n", addr, val);
+ break;
+ }
+}
+
+static void nabm_writew (void *opaque, uint32_t addr, uint32_t val)
+{
+ AC97LinkState *s = opaque;
+ AC97BusMasterRegs *r = NULL;
+ uint32_t index = addr;
+ switch (index) {
+ case PI_SR:
+ case PO_SR:
+ case MC_SR:
+ r = &s->bm_regs[GET_BM (index)];
+ r->sr |= val & ~(SR_RO_MASK | SR_WCLEAR_MASK);
+ update_sr (s, r, r->sr & ~(val & SR_WCLEAR_MASK));
+ dolog ("SR[%d] <- %#x (sr %#x)\n", GET_BM (index), val, r->sr);
+ break;
+ default:
+ dolog ("U nabm writew %#x <- %#x\n", addr, val);
+ break;
+ }
+}
+
+static void nabm_writel (void *opaque, uint32_t addr, uint32_t val)
+{
+ AC97LinkState *s = opaque;
+ AC97BusMasterRegs *r = NULL;
+ uint32_t index = addr;
+ switch (index) {
+ case PI_BDBAR:
+ case PO_BDBAR:
+ case MC_BDBAR:
+ r = &s->bm_regs[GET_BM (index)];
+ r->bdbar = val & ~3;
+ dolog ("BDBAR[%d] <- %#x (bdbar %#x)\n",
+ GET_BM (index), val, r->bdbar);
+ break;
+ case GLOB_CNT:
+ if (val & GC_WR)
+ warm_reset (s);
+ if (val & GC_CR)
+ cold_reset (s);
+ if (!(val & (GC_WR | GC_CR)))
+ s->glob_cnt = val & GC_VALID_MASK;
+ dolog ("glob_cnt <- %#x (glob_cnt %#x)\n", val, s->glob_cnt);
+ break;
+ case GLOB_STA:
+ s->glob_sta &= ~(val & GS_WCLEAR_MASK);
+ s->glob_sta |= (val & ~(GS_WCLEAR_MASK | GS_RO_MASK)) & GS_VALID_MASK;
+ dolog ("glob_sta <- %#x (glob_sta %#x)\n", val, s->glob_sta);
+ break;
+ default:
+ dolog ("U nabm writel %#x <- %#x\n", addr, val);
+ break;
+ }
+}
+
+static int write_audio (AC97LinkState *s, AC97BusMasterRegs *r,
+ int max, int *stop)
+{
+ uint8_t tmpbuf[4096];
+ uint32_t addr = r->bd.addr;
+ uint32_t temp = r->picb << 1;
+ uint32_t written = 0;
+ int to_copy = 0;
+ temp = audio_MIN (temp, max);
+
+ if (!temp) {
+ *stop = 1;
+ return 0;
+ }
+
+ while (temp) {
+ int copied;
+ to_copy = audio_MIN (temp, sizeof (tmpbuf));
+ pci_dma_read (&s->dev, addr, tmpbuf, to_copy);
+ copied = AUD_write (s->voice_po, tmpbuf, to_copy);
+ dolog ("write_audio max=%x to_copy=%x copied=%x\n",
+ max, to_copy, copied);
+ if (!copied) {
+ *stop = 1;
+ break;
+ }
+ temp -= copied;
+ addr += copied;
+ written += copied;
+ }
+
+ if (!temp) {
+ if (to_copy < 4) {
+ dolog ("whoops\n");
+ s->last_samp = 0;
+ }
+ else {
+ s->last_samp = *(uint32_t *) &tmpbuf[to_copy - 4];
+ }
+ }
+
+ r->bd.addr = addr;
+ return written;
+}
+
+static void write_bup (AC97LinkState *s, int elapsed)
+{
+ dolog ("write_bup\n");
+ if (!(s->bup_flag & BUP_SET)) {
+ if (s->bup_flag & BUP_LAST) {
+ int i;
+ uint8_t *p = s->silence;
+ for (i = 0; i < sizeof (s->silence) / 4; i++, p += 4) {
+ *(uint32_t *) p = s->last_samp;
+ }
+ }
+ else {
+ memset (s->silence, 0, sizeof (s->silence));
+ }
+ s->bup_flag |= BUP_SET;
+ }
+
+ while (elapsed) {
+ int temp = audio_MIN (elapsed, sizeof (s->silence));
+ while (temp) {
+ int copied = AUD_write (s->voice_po, s->silence, temp);
+ if (!copied)
+ return;
+ temp -= copied;
+ elapsed -= copied;
+ }
+ }
+}
+
+static int read_audio (AC97LinkState *s, AC97BusMasterRegs *r,
+ int max, int *stop)
+{
+ uint8_t tmpbuf[4096];
+ uint32_t addr = r->bd.addr;
+ uint32_t temp = r->picb << 1;
+ uint32_t nread = 0;
+ int to_copy = 0;
+ SWVoiceIn *voice = (r - s->bm_regs) == MC_INDEX ? s->voice_mc : s->voice_pi;
+
+ temp = audio_MIN (temp, max);
+
+ if (!temp) {
+ *stop = 1;
+ return 0;
+ }
+
+ while (temp) {
+ int acquired;
+ to_copy = audio_MIN (temp, sizeof (tmpbuf));
+ acquired = AUD_read (voice, tmpbuf, to_copy);
+ if (!acquired) {
+ *stop = 1;
+ break;
+ }
+ pci_dma_write (&s->dev, addr, tmpbuf, acquired);
+ temp -= acquired;
+ addr += acquired;
+ nread += acquired;
+ }
+
+ r->bd.addr = addr;
+ return nread;
+}
+
+static void transfer_audio (AC97LinkState *s, int index, int elapsed)
+{
+ AC97BusMasterRegs *r = &s->bm_regs[index];
+ int stop = 0;
+
+ if (s->invalid_freq[index]) {
+ AUD_log ("ac97", "attempt to use voice %d with invalid frequency %d\n",
+ index, s->invalid_freq[index]);
+ return;
+ }
+
+ if (r->sr & SR_DCH) {
+ if (r->cr & CR_RPBM) {
+ switch (index) {
+ case PO_INDEX:
+ write_bup (s, elapsed);
+ break;
+ }
+ }
+ return;
+ }
+
+ while ((elapsed >> 1) && !stop) {
+ int temp;
+
+ if (!r->bd_valid) {
+ dolog ("invalid bd\n");
+ fetch_bd (s, r);
+ }
+
+ if (!r->picb) {
+ dolog ("fresh bd %d is empty %#x %#x\n",
+ r->civ, r->bd.addr, r->bd.ctl_len);
+ if (r->civ == r->lvi) {
+ r->sr |= SR_DCH; /* CELV? */
+ s->bup_flag = 0;
+ break;
+ }
+ r->sr &= ~SR_CELV;
+ r->civ = r->piv;
+ r->piv = (r->piv + 1) % 32;
+ fetch_bd (s, r);
+ return;
+ }
+
+ switch (index) {
+ case PO_INDEX:
+ temp = write_audio (s, r, elapsed, &stop);
+ elapsed -= temp;
+ r->picb -= (temp >> 1);
+ break;
+
+ case PI_INDEX:
+ case MC_INDEX:
+ temp = read_audio (s, r, elapsed, &stop);
+ elapsed -= temp;
+ r->picb -= (temp >> 1);
+ break;
+ }
+
+ if (!r->picb) {
+ uint32_t new_sr = r->sr & ~SR_CELV;
+
+ if (r->bd.ctl_len & BD_IOC) {
+ new_sr |= SR_BCIS;
+ }
+
+ if (r->civ == r->lvi) {
+ dolog ("Underrun civ (%d) == lvi (%d)\n", r->civ, r->lvi);
+
+ new_sr |= SR_LVBCI | SR_DCH | SR_CELV;
+ stop = 1;
+ s->bup_flag = (r->bd.ctl_len & BD_BUP) ? BUP_LAST : 0;
+ }
+ else {
+ r->civ = r->piv;
+ r->piv = (r->piv + 1) % 32;
+ fetch_bd (s, r);
+ }
+
+ update_sr (s, r, new_sr);
+ }
+ }
+}
+
+static void pi_callback (void *opaque, int avail)
+{
+ transfer_audio (opaque, PI_INDEX, avail);
+}
+
+static void mc_callback (void *opaque, int avail)
+{
+ transfer_audio (opaque, MC_INDEX, avail);
+}
+
+static void po_callback (void *opaque, int free)
+{
+ transfer_audio (opaque, PO_INDEX, free);
+}
+
+static const VMStateDescription vmstate_ac97_bm_regs = {
+ .name = "ac97_bm_regs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32 (bdbar, AC97BusMasterRegs),
+ VMSTATE_UINT8 (civ, AC97BusMasterRegs),
+ VMSTATE_UINT8 (lvi, AC97BusMasterRegs),
+ VMSTATE_UINT16 (sr, AC97BusMasterRegs),
+ VMSTATE_UINT16 (picb, AC97BusMasterRegs),
+ VMSTATE_UINT8 (piv, AC97BusMasterRegs),
+ VMSTATE_UINT8 (cr, AC97BusMasterRegs),
+ VMSTATE_UINT32 (bd_valid, AC97BusMasterRegs),
+ VMSTATE_UINT32 (bd.addr, AC97BusMasterRegs),
+ VMSTATE_UINT32 (bd.ctl_len, AC97BusMasterRegs),
+ VMSTATE_END_OF_LIST ()
+ }
+};
+
+static int ac97_post_load (void *opaque, int version_id)
+{
+ uint8_t active[LAST_INDEX];
+ AC97LinkState *s = opaque;
+
+ record_select (s, mixer_load (s, AC97_Record_Select));
+ set_volume (s, AC97_Master_Volume_Mute,
+ mixer_load (s, AC97_Master_Volume_Mute));
+ set_volume (s, AC97_PCM_Out_Volume_Mute,
+ mixer_load (s, AC97_PCM_Out_Volume_Mute));
+ set_volume (s, AC97_Record_Gain_Mute,
+ mixer_load (s, AC97_Record_Gain_Mute));
+
+ active[PI_INDEX] = !!(s->bm_regs[PI_INDEX].cr & CR_RPBM);
+ active[PO_INDEX] = !!(s->bm_regs[PO_INDEX].cr & CR_RPBM);
+ active[MC_INDEX] = !!(s->bm_regs[MC_INDEX].cr & CR_RPBM);
+ reset_voices (s, active);
+
+ s->bup_flag = 0;
+ s->last_samp = 0;
+ return 0;
+}
+
+static bool is_version_2 (void *opaque, int version_id)
+{
+ return version_id == 2;
+}
+
+static const VMStateDescription vmstate_ac97 = {
+ .name = "ac97",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .post_load = ac97_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE (dev, AC97LinkState),
+ VMSTATE_UINT32 (glob_cnt, AC97LinkState),
+ VMSTATE_UINT32 (glob_sta, AC97LinkState),
+ VMSTATE_UINT32 (cas, AC97LinkState),
+ VMSTATE_STRUCT_ARRAY (bm_regs, AC97LinkState, 3, 1,
+ vmstate_ac97_bm_regs, AC97BusMasterRegs),
+ VMSTATE_BUFFER (mixer_data, AC97LinkState),
+ VMSTATE_UNUSED_TEST (is_version_2, 3),
+ VMSTATE_END_OF_LIST ()
+ }
+};
+
+static uint64_t nam_read(void *opaque, hwaddr addr, unsigned size)
+{
+ if ((addr / size) > 256) {
+ return -1;
+ }
+
+ switch (size) {
+ case 1:
+ return nam_readb(opaque, addr);
+ case 2:
+ return nam_readw(opaque, addr);
+ case 4:
+ return nam_readl(opaque, addr);
+ default:
+ return -1;
+ }
+}
+
+static void nam_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ if ((addr / size) > 256) {
+ return;
+ }
+
+ switch (size) {
+ case 1:
+ nam_writeb(opaque, addr, val);
+ break;
+ case 2:
+ nam_writew(opaque, addr, val);
+ break;
+ case 4:
+ nam_writel(opaque, addr, val);
+ break;
+ }
+}
+
+static const MemoryRegionOps ac97_io_nam_ops = {
+ .read = nam_read,
+ .write = nam_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t nabm_read(void *opaque, hwaddr addr, unsigned size)
+{
+ if ((addr / size) > 64) {
+ return -1;
+ }
+
+ switch (size) {
+ case 1:
+ return nabm_readb(opaque, addr);
+ case 2:
+ return nabm_readw(opaque, addr);
+ case 4:
+ return nabm_readl(opaque, addr);
+ default:
+ return -1;
+ }
+}
+
+static void nabm_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ if ((addr / size) > 64) {
+ return;
+ }
+
+ switch (size) {
+ case 1:
+ nabm_writeb(opaque, addr, val);
+ break;
+ case 2:
+ nabm_writew(opaque, addr, val);
+ break;
+ case 4:
+ nabm_writel(opaque, addr, val);
+ break;
+ }
+}
+
+
+static const MemoryRegionOps ac97_io_nabm_ops = {
+ .read = nabm_read,
+ .write = nabm_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ac97_on_reset (DeviceState *dev)
+{
+ AC97LinkState *s = container_of(dev, AC97LinkState, dev.qdev);
+
+ reset_bm_regs (s, &s->bm_regs[0]);
+ reset_bm_regs (s, &s->bm_regs[1]);
+ reset_bm_regs (s, &s->bm_regs[2]);
+
+ /*
+ * Reset the mixer too. The Windows XP driver seems to rely on
+ * this. At least it wants to read the vendor id before it resets
+ * the codec manually.
+ */
+ mixer_reset (s);
+}
+
+static void ac97_realize(PCIDevice *dev, Error **errp)
+{
+ AC97LinkState *s = DO_UPCAST (AC97LinkState, dev, dev);
+ uint8_t *c = s->dev.config;
+
+ /* TODO: no need to override */
+ c[PCI_COMMAND] = 0x00; /* pcicmd pci command rw, ro */
+ c[PCI_COMMAND + 1] = 0x00;
+
+ /* TODO: */
+ c[PCI_STATUS] = PCI_STATUS_FAST_BACK; /* pcists pci status rwc, ro */
+ c[PCI_STATUS + 1] = PCI_STATUS_DEVSEL_MEDIUM >> 8;
+
+ c[PCI_CLASS_PROG] = 0x00; /* pi programming interface ro */
+
+ /* TODO set when bar is registered. no need to override. */
+ /* nabmar native audio mixer base address rw */
+ c[PCI_BASE_ADDRESS_0] = PCI_BASE_ADDRESS_SPACE_IO;
+ c[PCI_BASE_ADDRESS_0 + 1] = 0x00;
+ c[PCI_BASE_ADDRESS_0 + 2] = 0x00;
+ c[PCI_BASE_ADDRESS_0 + 3] = 0x00;
+
+ /* TODO set when bar is registered. no need to override. */
+ /* nabmbar native audio bus mastering base address rw */
+ c[PCI_BASE_ADDRESS_0 + 4] = PCI_BASE_ADDRESS_SPACE_IO;
+ c[PCI_BASE_ADDRESS_0 + 5] = 0x00;
+ c[PCI_BASE_ADDRESS_0 + 6] = 0x00;
+ c[PCI_BASE_ADDRESS_0 + 7] = 0x00;
+
+ if (s->use_broken_id) {
+ c[PCI_SUBSYSTEM_VENDOR_ID] = 0x86;
+ c[PCI_SUBSYSTEM_VENDOR_ID + 1] = 0x80;
+ c[PCI_SUBSYSTEM_ID] = 0x00;
+ c[PCI_SUBSYSTEM_ID + 1] = 0x00;
+ }
+
+ c[PCI_INTERRUPT_LINE] = 0x00; /* intr_ln interrupt line rw */
+ c[PCI_INTERRUPT_PIN] = 0x01; /* intr_pn interrupt pin ro */
+
+ memory_region_init_io (&s->io_nam, OBJECT(s), &ac97_io_nam_ops, s,
+ "ac97-nam", 1024);
+ memory_region_init_io (&s->io_nabm, OBJECT(s), &ac97_io_nabm_ops, s,
+ "ac97-nabm", 256);
+ pci_register_bar (&s->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io_nam);
+ pci_register_bar (&s->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io_nabm);
+ AUD_register_card ("ac97", &s->card);
+ ac97_on_reset (&s->dev.qdev);
+}
+
+static int ac97_init (PCIBus *bus)
+{
+ pci_create_simple (bus, -1, "AC97");
+ return 0;
+}
+
+static Property ac97_properties[] = {
+ DEFINE_PROP_UINT32 ("use_broken_id", AC97LinkState, use_broken_id, 0),
+ DEFINE_PROP_END_OF_LIST (),
+};
+
+static void ac97_class_init (ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS (klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS (klass);
+
+ k->realize = ac97_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82801AA_5;
+ k->revision = 0x01;
+ k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = "Intel 82801AA AC97 Audio";
+ dc->vmsd = &vmstate_ac97;
+ dc->props = ac97_properties;
+ dc->reset = ac97_on_reset;
+}
+
+static const TypeInfo ac97_info = {
+ .name = "AC97",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof (AC97LinkState),
+ .class_init = ac97_class_init,
+};
+
+static void ac97_register_types (void)
+{
+ type_register_static (&ac97_info);
+ pci_register_soundhw("ac97", "Intel 82801AA AC97 Audio", ac97_init);
+}
+
+type_init (ac97_register_types)
diff --git a/hw/audio/adlib.c b/hw/audio/adlib.c
new file mode 100644
index 00000000..656eb377
--- /dev/null
+++ b/hw/audio/adlib.c
@@ -0,0 +1,389 @@
+/*
+ * QEMU Proxy for OPL2/3 emulation by MAME team
+ *
+ * Copyright (c) 2004-2005 Vassili Karpov (malc)
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/audio/audio.h"
+#include "audio/audio.h"
+#include "hw/isa/isa.h"
+
+//#define DEBUG
+
+#define ADLIB_KILL_TIMERS 1
+
+#ifdef HAS_YMF262
+#define ADLIB_DESC "Yamaha YMF262 (OPL3)"
+#else
+#define ADLIB_DESC "Yamaha YM3812 (OPL2)"
+#endif
+
+#ifdef DEBUG
+#include "qemu/timer.h"
+#endif
+
+#define dolog(...) AUD_log ("adlib", __VA_ARGS__)
+#ifdef DEBUG
+#define ldebug(...) dolog (__VA_ARGS__)
+#else
+#define ldebug(...)
+#endif
+
+#ifdef HAS_YMF262
+#include "ymf262.h"
+void YMF262UpdateOneQEMU (int which, INT16 *dst, int length);
+#define SHIFT 2
+#else
+#include "fmopl.h"
+#define SHIFT 1
+#endif
+
+#define IO_READ_PROTO(name) \
+ uint32_t name (void *opaque, uint32_t nport)
+#define IO_WRITE_PROTO(name) \
+ void name (void *opaque, uint32_t nport, uint32_t val)
+
+#define TYPE_ADLIB "adlib"
+#define ADLIB(obj) OBJECT_CHECK(AdlibState, (obj), TYPE_ADLIB)
+
+typedef struct {
+ ISADevice parent_obj;
+
+ QEMUSoundCard card;
+ uint32_t freq;
+ uint32_t port;
+ int ticking[2];
+ int enabled;
+ int active;
+ int bufpos;
+#ifdef DEBUG
+ int64_t exp[2];
+#endif
+ int16_t *mixbuf;
+ uint64_t dexp[2];
+ SWVoiceOut *voice;
+ int left, pos, samples;
+ QEMUAudioTimeStamp ats;
+#ifndef HAS_YMF262
+ FM_OPL *opl;
+#endif
+ PortioList port_list;
+} AdlibState;
+
+static AdlibState *glob_adlib;
+
+static void adlib_stop_opl_timer (AdlibState *s, size_t n)
+{
+#ifdef HAS_YMF262
+ YMF262TimerOver (0, n);
+#else
+ OPLTimerOver (s->opl, n);
+#endif
+ s->ticking[n] = 0;
+}
+
+static void adlib_kill_timers (AdlibState *s)
+{
+ size_t i;
+
+ for (i = 0; i < 2; ++i) {
+ if (s->ticking[i]) {
+ uint64_t delta;
+
+ delta = AUD_get_elapsed_usec_out (s->voice, &s->ats);
+ ldebug (
+ "delta = %f dexp = %f expired => %d\n",
+ delta / 1000000.0,
+ s->dexp[i] / 1000000.0,
+ delta >= s->dexp[i]
+ );
+ if (ADLIB_KILL_TIMERS || delta >= s->dexp[i]) {
+ adlib_stop_opl_timer (s, i);
+ AUD_init_time_stamp_out (s->voice, &s->ats);
+ }
+ }
+ }
+}
+
+static IO_WRITE_PROTO (adlib_write)
+{
+ AdlibState *s = opaque;
+ int a = nport & 3;
+
+ s->active = 1;
+ AUD_set_active_out (s->voice, 1);
+
+ adlib_kill_timers (s);
+
+#ifdef HAS_YMF262
+ YMF262Write (0, a, val);
+#else
+ OPLWrite (s->opl, a, val);
+#endif
+}
+
+static IO_READ_PROTO (adlib_read)
+{
+ AdlibState *s = opaque;
+ uint8_t data;
+ int a = nport & 3;
+
+ adlib_kill_timers (s);
+
+#ifdef HAS_YMF262
+ data = YMF262Read (0, a);
+#else
+ data = OPLRead (s->opl, a);
+#endif
+ return data;
+}
+
+static void timer_handler (int c, double interval_Sec)
+{
+ AdlibState *s = glob_adlib;
+ unsigned n = c & 1;
+#ifdef DEBUG
+ double interval;
+ int64_t exp;
+#endif
+
+ if (interval_Sec == 0.0) {
+ s->ticking[n] = 0;
+ return;
+ }
+
+ s->ticking[n] = 1;
+#ifdef DEBUG
+ interval = get_ticks_per_sec () * interval_Sec;
+ exp = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + interval;
+ s->exp[n] = exp;
+#endif
+
+ s->dexp[n] = interval_Sec * 1000000.0;
+ AUD_init_time_stamp_out (s->voice, &s->ats);
+}
+
+static int write_audio (AdlibState *s, int samples)
+{
+ int net = 0;
+ int pos = s->pos;
+
+ while (samples) {
+ int nbytes, wbytes, wsampl;
+
+ nbytes = samples << SHIFT;
+ wbytes = AUD_write (
+ s->voice,
+ s->mixbuf + (pos << (SHIFT - 1)),
+ nbytes
+ );
+
+ if (wbytes) {
+ wsampl = wbytes >> SHIFT;
+
+ samples -= wsampl;
+ pos = (pos + wsampl) % s->samples;
+
+ net += wsampl;
+ }
+ else {
+ break;
+ }
+ }
+
+ return net;
+}
+
+static void adlib_callback (void *opaque, int free)
+{
+ AdlibState *s = opaque;
+ int samples, net = 0, to_play, written;
+
+ samples = free >> SHIFT;
+ if (!(s->active && s->enabled) || !samples) {
+ return;
+ }
+
+ to_play = audio_MIN (s->left, samples);
+ while (to_play) {
+ written = write_audio (s, to_play);
+
+ if (written) {
+ s->left -= written;
+ samples -= written;
+ to_play -= written;
+ s->pos = (s->pos + written) % s->samples;
+ }
+ else {
+ return;
+ }
+ }
+
+ samples = audio_MIN (samples, s->samples - s->pos);
+ if (!samples) {
+ return;
+ }
+
+#ifdef HAS_YMF262
+ YMF262UpdateOneQEMU (0, s->mixbuf + s->pos * 2, samples);
+#else
+ YM3812UpdateOne (s->opl, s->mixbuf + s->pos, samples);
+#endif
+
+ while (samples) {
+ written = write_audio (s, samples);
+
+ if (written) {
+ net += written;
+ samples -= written;
+ s->pos = (s->pos + written) % s->samples;
+ }
+ else {
+ s->left = samples;
+ return;
+ }
+ }
+}
+
+static void Adlib_fini (AdlibState *s)
+{
+#ifdef HAS_YMF262
+ YMF262Shutdown ();
+#else
+ if (s->opl) {
+ OPLDestroy (s->opl);
+ s->opl = NULL;
+ }
+#endif
+
+ g_free(s->mixbuf);
+
+ s->active = 0;
+ s->enabled = 0;
+ AUD_remove_card (&s->card);
+}
+
+static MemoryRegionPortio adlib_portio_list[] = {
+ { 0, 4, 1, .read = adlib_read, .write = adlib_write, },
+ { 0, 2, 1, .read = adlib_read, .write = adlib_write, },
+ { 0x388, 4, 1, .read = adlib_read, .write = adlib_write, },
+ PORTIO_END_OF_LIST(),
+};
+
+static void adlib_realizefn (DeviceState *dev, Error **errp)
+{
+ AdlibState *s = ADLIB(dev);
+ struct audsettings as;
+
+ if (glob_adlib) {
+ error_setg (errp, "Cannot create more than 1 adlib device");
+ return;
+ }
+ glob_adlib = s;
+
+#ifdef HAS_YMF262
+ if (YMF262Init (1, 14318180, s->freq)) {
+ error_setg (errp, "YMF262Init %d failed", s->freq);
+ return;
+ }
+ else {
+ YMF262SetTimerHandler (0, timer_handler, 0);
+ s->enabled = 1;
+ }
+#else
+ s->opl = OPLCreate (OPL_TYPE_YM3812, 3579545, s->freq);
+ if (!s->opl) {
+ error_setg (errp, "OPLCreate %d failed", s->freq);
+ return;
+ }
+ else {
+ OPLSetTimerHandler (s->opl, timer_handler, 0);
+ s->enabled = 1;
+ }
+#endif
+
+ as.freq = s->freq;
+ as.nchannels = SHIFT;
+ as.fmt = AUD_FMT_S16;
+ as.endianness = AUDIO_HOST_ENDIANNESS;
+
+ AUD_register_card ("adlib", &s->card);
+
+ s->voice = AUD_open_out (
+ &s->card,
+ s->voice,
+ "adlib",
+ s,
+ adlib_callback,
+ &as
+ );
+ if (!s->voice) {
+ Adlib_fini (s);
+ error_setg (errp, "Initializing audio voice failed");
+ return;
+ }
+
+ s->samples = AUD_get_buffer_size_out (s->voice) >> SHIFT;
+ s->mixbuf = g_malloc0 (s->samples << SHIFT);
+
+ adlib_portio_list[0].offset = s->port;
+ adlib_portio_list[1].offset = s->port + 8;
+ portio_list_init (&s->port_list, OBJECT(s), adlib_portio_list, s, "adlib");
+ portio_list_add (&s->port_list, isa_address_space_io(&s->parent_obj), 0);
+}
+
+static Property adlib_properties[] = {
+ DEFINE_PROP_UINT32 ("iobase", AdlibState, port, 0x220),
+ DEFINE_PROP_UINT32 ("freq", AdlibState, freq, 44100),
+ DEFINE_PROP_END_OF_LIST (),
+};
+
+static void adlib_class_initfn (ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS (klass);
+
+ dc->realize = adlib_realizefn;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = ADLIB_DESC;
+ dc->props = adlib_properties;
+}
+
+static const TypeInfo adlib_info = {
+ .name = TYPE_ADLIB,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof (AdlibState),
+ .class_init = adlib_class_initfn,
+};
+
+static int Adlib_init (ISABus *bus)
+{
+ isa_create_simple (bus, TYPE_ADLIB);
+ return 0;
+}
+
+static void adlib_register_types (void)
+{
+ type_register_static (&adlib_info);
+ isa_register_soundhw("adlib", ADLIB_DESC, Adlib_init);
+}
+
+type_init (adlib_register_types)
diff --git a/hw/audio/cs4231.c b/hw/audio/cs4231.c
new file mode 100644
index 00000000..6325a8ce
--- /dev/null
+++ b/hw/audio/cs4231.c
@@ -0,0 +1,186 @@
+/*
+ * QEMU Crystal CS4231 audio chip emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "trace.h"
+
+/*
+ * In addition to Crystal CS4231 there is a DMA controller on Sparc.
+ */
+#define CS_SIZE 0x40
+#define CS_REGS 16
+#define CS_DREGS 32
+#define CS_MAXDREG (CS_DREGS - 1)
+
+#define TYPE_CS4231 "SUNW,CS4231"
+#define CS4231(obj) \
+ OBJECT_CHECK(CSState, (obj), TYPE_CS4231)
+
+typedef struct CSState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ uint32_t regs[CS_REGS];
+ uint8_t dregs[CS_DREGS];
+} CSState;
+
+#define CS_RAP(s) ((s)->regs[0] & CS_MAXDREG)
+#define CS_VER 0xa0
+#define CS_CDC_VER 0x8a
+
+static void cs_reset(DeviceState *d)
+{
+ CSState *s = CS4231(d);
+
+ memset(s->regs, 0, CS_REGS * 4);
+ memset(s->dregs, 0, CS_DREGS);
+ s->dregs[12] = CS_CDC_VER;
+ s->dregs[25] = CS_VER;
+}
+
+static uint64_t cs_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CSState *s = opaque;
+ uint32_t saddr, ret;
+
+ saddr = addr >> 2;
+ switch (saddr) {
+ case 1:
+ switch (CS_RAP(s)) {
+ case 3: // Write only
+ ret = 0;
+ break;
+ default:
+ ret = s->dregs[CS_RAP(s)];
+ break;
+ }
+ trace_cs4231_mem_readl_dreg(CS_RAP(s), ret);
+ break;
+ default:
+ ret = s->regs[saddr];
+ trace_cs4231_mem_readl_reg(saddr, ret);
+ break;
+ }
+ return ret;
+}
+
+static void cs_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ CSState *s = opaque;
+ uint32_t saddr;
+
+ saddr = addr >> 2;
+ trace_cs4231_mem_writel_reg(saddr, s->regs[saddr], val);
+ switch (saddr) {
+ case 1:
+ trace_cs4231_mem_writel_dreg(CS_RAP(s), s->dregs[CS_RAP(s)], val);
+ switch(CS_RAP(s)) {
+ case 11:
+ case 25: // Read only
+ break;
+ case 12:
+ val &= 0x40;
+ val |= CS_CDC_VER; // Codec version
+ s->dregs[CS_RAP(s)] = val;
+ break;
+ default:
+ s->dregs[CS_RAP(s)] = val;
+ break;
+ }
+ break;
+ case 2: // Read only
+ break;
+ case 4:
+ if (val & 1) {
+ cs_reset(DEVICE(s));
+ }
+ val &= 0x7f;
+ s->regs[saddr] = val;
+ break;
+ default:
+ s->regs[saddr] = val;
+ break;
+ }
+}
+
+static const MemoryRegionOps cs_mem_ops = {
+ .read = cs_mem_read,
+ .write = cs_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_cs4231 = {
+ .name ="cs4231",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, CSState, CS_REGS),
+ VMSTATE_UINT8_ARRAY(dregs, CSState, CS_DREGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int cs4231_init1(SysBusDevice *dev)
+{
+ CSState *s = CS4231(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &cs_mem_ops, s, "cs4321",
+ CS_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+
+ return 0;
+}
+
+static Property cs4231_properties[] = {
+ {.name = NULL},
+};
+
+static void cs4231_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = cs4231_init1;
+ dc->reset = cs_reset;
+ dc->vmsd = &vmstate_cs4231;
+ dc->props = cs4231_properties;
+}
+
+static const TypeInfo cs4231_info = {
+ .name = TYPE_CS4231,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CSState),
+ .class_init = cs4231_class_init,
+};
+
+static void cs4231_register_types(void)
+{
+ type_register_static(&cs4231_info);
+}
+
+type_init(cs4231_register_types)
diff --git a/hw/audio/cs4231a.c b/hw/audio/cs4231a.c
new file mode 100644
index 00000000..f96f561c
--- /dev/null
+++ b/hw/audio/cs4231a.c
@@ -0,0 +1,707 @@
+/*
+ * QEMU Crystal CS4231 audio chip emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/audio/audio.h"
+#include "audio/audio.h"
+#include "hw/isa/isa.h"
+#include "hw/qdev.h"
+#include "qemu/timer.h"
+
+/*
+ Missing features:
+ ADC
+ Loopback
+ Timer
+ ADPCM
+ More...
+*/
+
+/* #define DEBUG */
+/* #define DEBUG_XLAW */
+
+static struct {
+ int aci_counter;
+} conf = {1};
+
+#ifdef DEBUG
+#define dolog(...) AUD_log ("cs4231a", __VA_ARGS__)
+#else
+#define dolog(...)
+#endif
+
+#define lwarn(...) AUD_log ("cs4231a", "warning: " __VA_ARGS__)
+#define lerr(...) AUD_log ("cs4231a", "error: " __VA_ARGS__)
+
+#define CS_REGS 16
+#define CS_DREGS 32
+
+#define TYPE_CS4231A "cs4231a"
+#define CS4231A(obj) OBJECT_CHECK (CSState, (obj), TYPE_CS4231A)
+
+typedef struct CSState {
+ ISADevice dev;
+ QEMUSoundCard card;
+ MemoryRegion ioports;
+ qemu_irq pic;
+ uint32_t regs[CS_REGS];
+ uint8_t dregs[CS_DREGS];
+ uint32_t irq;
+ uint32_t dma;
+ uint32_t port;
+ int shift;
+ int dma_running;
+ int audio_free;
+ int transferred;
+ int aci_counter;
+ SWVoiceOut *voice;
+ int16_t *tab;
+} CSState;
+
+#define MODE2 (1 << 6)
+#define MCE (1 << 6)
+#define PMCE (1 << 4)
+#define CMCE (1 << 5)
+#define TE (1 << 6)
+#define PEN (1 << 0)
+#define INT (1 << 0)
+#define IEN (1 << 1)
+#define PPIO (1 << 6)
+#define PI (1 << 4)
+#define CI (1 << 5)
+#define TI (1 << 6)
+
+enum {
+ Index_Address,
+ Index_Data,
+ Status,
+ PIO_Data
+};
+
+enum {
+ Left_ADC_Input_Control,
+ Right_ADC_Input_Control,
+ Left_AUX1_Input_Control,
+ Right_AUX1_Input_Control,
+ Left_AUX2_Input_Control,
+ Right_AUX2_Input_Control,
+ Left_DAC_Output_Control,
+ Right_DAC_Output_Control,
+ FS_And_Playback_Data_Format,
+ Interface_Configuration,
+ Pin_Control,
+ Error_Status_And_Initialization,
+ MODE_And_ID,
+ Loopback_Control,
+ Playback_Upper_Base_Count,
+ Playback_Lower_Base_Count,
+ Alternate_Feature_Enable_I,
+ Alternate_Feature_Enable_II,
+ Left_Line_Input_Control,
+ Right_Line_Input_Control,
+ Timer_Low_Base,
+ Timer_High_Base,
+ RESERVED,
+ Alternate_Feature_Enable_III,
+ Alternate_Feature_Status,
+ Version_Chip_ID,
+ Mono_Input_And_Output_Control,
+ RESERVED_2,
+ Capture_Data_Format,
+ RESERVED_3,
+ Capture_Upper_Base_Count,
+ Capture_Lower_Base_Count
+};
+
+static int freqs[2][8] = {
+ { 8000, 16000, 27420, 32000, -1, -1, 48000, 9000 },
+ { 5510, 11025, 18900, 22050, 37800, 44100, 33075, 6620 }
+};
+
+/* Tables courtesy http://hazelware.luggle.com/tutorials/mulawcompression.html */
+static int16_t MuLawDecompressTable[256] =
+{
+ -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
+ -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
+ -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
+ -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
+ -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
+ -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
+ -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
+ -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
+ -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
+ -1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
+ -876, -844, -812, -780, -748, -716, -684, -652,
+ -620, -588, -556, -524, -492, -460, -428, -396,
+ -372, -356, -340, -324, -308, -292, -276, -260,
+ -244, -228, -212, -196, -180, -164, -148, -132,
+ -120, -112, -104, -96, -88, -80, -72, -64,
+ -56, -48, -40, -32, -24, -16, -8, 0,
+ 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
+ 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
+ 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
+ 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
+ 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
+ 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
+ 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
+ 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
+ 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
+ 1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
+ 876, 844, 812, 780, 748, 716, 684, 652,
+ 620, 588, 556, 524, 492, 460, 428, 396,
+ 372, 356, 340, 324, 308, 292, 276, 260,
+ 244, 228, 212, 196, 180, 164, 148, 132,
+ 120, 112, 104, 96, 88, 80, 72, 64,
+ 56, 48, 40, 32, 24, 16, 8, 0
+};
+
+static int16_t ALawDecompressTable[256] =
+{
+ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
+ -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
+ -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
+ -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
+ -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
+ -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
+ -11008,-10496,-12032,-11520,-8960, -8448, -9984, -9472,
+ -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
+ -344, -328, -376, -360, -280, -264, -312, -296,
+ -472, -456, -504, -488, -408, -392, -440, -424,
+ -88, -72, -120, -104, -24, -8, -56, -40,
+ -216, -200, -248, -232, -152, -136, -184, -168,
+ -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
+ -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
+ -688, -656, -752, -720, -560, -528, -624, -592,
+ -944, -912, -1008, -976, -816, -784, -880, -848,
+ 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
+ 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
+ 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
+ 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
+ 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
+ 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
+ 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
+ 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
+ 344, 328, 376, 360, 280, 264, 312, 296,
+ 472, 456, 504, 488, 408, 392, 440, 424,
+ 88, 72, 120, 104, 24, 8, 56, 40,
+ 216, 200, 248, 232, 152, 136, 184, 168,
+ 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
+ 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
+ 688, 656, 752, 720, 560, 528, 624, 592,
+ 944, 912, 1008, 976, 816, 784, 880, 848
+};
+
+static void cs4231a_reset (DeviceState *dev)
+{
+ CSState *s = CS4231A (dev);
+
+ s->regs[Index_Address] = 0x40;
+ s->regs[Index_Data] = 0x00;
+ s->regs[Status] = 0x00;
+ s->regs[PIO_Data] = 0x00;
+
+ s->dregs[Left_ADC_Input_Control] = 0x00;
+ s->dregs[Right_ADC_Input_Control] = 0x00;
+ s->dregs[Left_AUX1_Input_Control] = 0x88;
+ s->dregs[Right_AUX1_Input_Control] = 0x88;
+ s->dregs[Left_AUX2_Input_Control] = 0x88;
+ s->dregs[Right_AUX2_Input_Control] = 0x88;
+ s->dregs[Left_DAC_Output_Control] = 0x80;
+ s->dregs[Right_DAC_Output_Control] = 0x80;
+ s->dregs[FS_And_Playback_Data_Format] = 0x00;
+ s->dregs[Interface_Configuration] = 0x08;
+ s->dregs[Pin_Control] = 0x00;
+ s->dregs[Error_Status_And_Initialization] = 0x00;
+ s->dregs[MODE_And_ID] = 0x8a;
+ s->dregs[Loopback_Control] = 0x00;
+ s->dregs[Playback_Upper_Base_Count] = 0x00;
+ s->dregs[Playback_Lower_Base_Count] = 0x00;
+ s->dregs[Alternate_Feature_Enable_I] = 0x00;
+ s->dregs[Alternate_Feature_Enable_II] = 0x00;
+ s->dregs[Left_Line_Input_Control] = 0x88;
+ s->dregs[Right_Line_Input_Control] = 0x88;
+ s->dregs[Timer_Low_Base] = 0x00;
+ s->dregs[Timer_High_Base] = 0x00;
+ s->dregs[RESERVED] = 0x00;
+ s->dregs[Alternate_Feature_Enable_III] = 0x00;
+ s->dregs[Alternate_Feature_Status] = 0x00;
+ s->dregs[Version_Chip_ID] = 0xa0;
+ s->dregs[Mono_Input_And_Output_Control] = 0xa0;
+ s->dregs[RESERVED_2] = 0x00;
+ s->dregs[Capture_Data_Format] = 0x00;
+ s->dregs[RESERVED_3] = 0x00;
+ s->dregs[Capture_Upper_Base_Count] = 0x00;
+ s->dregs[Capture_Lower_Base_Count] = 0x00;
+}
+
+static void cs_audio_callback (void *opaque, int free)
+{
+ CSState *s = opaque;
+ s->audio_free = free;
+}
+
+static void cs_reset_voices (CSState *s, uint32_t val)
+{
+ int xtal;
+ struct audsettings as;
+
+#ifdef DEBUG_XLAW
+ if (val == 0 || val == 32)
+ val = (1 << 4) | (1 << 5);
+#endif
+
+ xtal = val & 1;
+ as.freq = freqs[xtal][(val >> 1) & 7];
+
+ if (as.freq == -1) {
+ lerr ("unsupported frequency (val=%#x)\n", val);
+ goto error;
+ }
+
+ as.nchannels = (val & (1 << 4)) ? 2 : 1;
+ as.endianness = 0;
+ s->tab = NULL;
+
+ switch ((val >> 5) & ((s->dregs[MODE_And_ID] & MODE2) ? 7 : 3)) {
+ case 0:
+ as.fmt = AUD_FMT_U8;
+ s->shift = as.nchannels == 2;
+ break;
+
+ case 1:
+ s->tab = MuLawDecompressTable;
+ goto x_law;
+ case 3:
+ s->tab = ALawDecompressTable;
+ x_law:
+ as.fmt = AUD_FMT_S16;
+ as.endianness = AUDIO_HOST_ENDIANNESS;
+ s->shift = as.nchannels == 2;
+ break;
+
+ case 6:
+ as.endianness = 1;
+ case 2:
+ as.fmt = AUD_FMT_S16;
+ s->shift = as.nchannels;
+ break;
+
+ case 7:
+ case 4:
+ lerr ("attempt to use reserved format value (%#x)\n", val);
+ goto error;
+
+ case 5:
+ lerr ("ADPCM 4 bit IMA compatible format is not supported\n");
+ goto error;
+ }
+
+ s->voice = AUD_open_out (
+ &s->card,
+ s->voice,
+ "cs4231a",
+ s,
+ cs_audio_callback,
+ &as
+ );
+
+ if (s->dregs[Interface_Configuration] & PEN) {
+ if (!s->dma_running) {
+ DMA_hold_DREQ (s->dma);
+ AUD_set_active_out (s->voice, 1);
+ s->transferred = 0;
+ }
+ s->dma_running = 1;
+ }
+ else {
+ if (s->dma_running) {
+ DMA_release_DREQ (s->dma);
+ AUD_set_active_out (s->voice, 0);
+ }
+ s->dma_running = 0;
+ }
+ return;
+
+ error:
+ if (s->dma_running) {
+ DMA_release_DREQ (s->dma);
+ AUD_set_active_out (s->voice, 0);
+ }
+}
+
+static uint64_t cs_read (void *opaque, hwaddr addr, unsigned size)
+{
+ CSState *s = opaque;
+ uint32_t saddr, iaddr, ret;
+
+ saddr = addr;
+ iaddr = ~0U;
+
+ switch (saddr) {
+ case Index_Address:
+ ret = s->regs[saddr] & ~0x80;
+ break;
+
+ case Index_Data:
+ if (!(s->dregs[MODE_And_ID] & MODE2))
+ iaddr = s->regs[Index_Address] & 0x0f;
+ else
+ iaddr = s->regs[Index_Address] & 0x1f;
+
+ ret = s->dregs[iaddr];
+ if (iaddr == Error_Status_And_Initialization) {
+ /* keep SEAL happy */
+ if (s->aci_counter) {
+ ret |= 1 << 5;
+ s->aci_counter -= 1;
+ }
+ }
+ break;
+
+ default:
+ ret = s->regs[saddr];
+ break;
+ }
+ dolog ("read %d:%d -> %d\n", saddr, iaddr, ret);
+ return ret;
+}
+
+static void cs_write (void *opaque, hwaddr addr,
+ uint64_t val64, unsigned size)
+{
+ CSState *s = opaque;
+ uint32_t saddr, iaddr, val;
+
+ saddr = addr;
+ val = val64;
+
+ switch (saddr) {
+ case Index_Address:
+ if (!(s->regs[Index_Address] & MCE) && (val & MCE)
+ && (s->dregs[Interface_Configuration] & (3 << 3)))
+ s->aci_counter = conf.aci_counter;
+
+ s->regs[Index_Address] = val & ~(1 << 7);
+ break;
+
+ case Index_Data:
+ if (!(s->dregs[MODE_And_ID] & MODE2))
+ iaddr = s->regs[Index_Address] & 0x0f;
+ else
+ iaddr = s->regs[Index_Address] & 0x1f;
+
+ switch (iaddr) {
+ case RESERVED:
+ case RESERVED_2:
+ case RESERVED_3:
+ lwarn ("attempt to write %#x to reserved indirect register %d\n",
+ val, iaddr);
+ break;
+
+ case FS_And_Playback_Data_Format:
+ if (s->regs[Index_Address] & MCE) {
+ cs_reset_voices (s, val);
+ }
+ else {
+ if (s->dregs[Alternate_Feature_Status] & PMCE) {
+ val = (val & ~0x0f) | (s->dregs[iaddr] & 0x0f);
+ cs_reset_voices (s, val);
+ }
+ else {
+ lwarn ("[P]MCE(%#x, %#x) is not set, val=%#x\n",
+ s->regs[Index_Address],
+ s->dregs[Alternate_Feature_Status],
+ val);
+ break;
+ }
+ }
+ s->dregs[iaddr] = val;
+ break;
+
+ case Interface_Configuration:
+ val &= ~(1 << 5); /* D5 is reserved */
+ s->dregs[iaddr] = val;
+ if (val & PPIO) {
+ lwarn ("PIO is not supported (%#x)\n", val);
+ break;
+ }
+ if (val & PEN) {
+ if (!s->dma_running) {
+ cs_reset_voices (s, s->dregs[FS_And_Playback_Data_Format]);
+ }
+ }
+ else {
+ if (s->dma_running) {
+ DMA_release_DREQ (s->dma);
+ AUD_set_active_out (s->voice, 0);
+ s->dma_running = 0;
+ }
+ }
+ break;
+
+ case Error_Status_And_Initialization:
+ lwarn ("attempt to write to read only register %d\n", iaddr);
+ break;
+
+ case MODE_And_ID:
+ dolog ("val=%#x\n", val);
+ if (val & MODE2)
+ s->dregs[iaddr] |= MODE2;
+ else
+ s->dregs[iaddr] &= ~MODE2;
+ break;
+
+ case Alternate_Feature_Enable_I:
+ if (val & TE)
+ lerr ("timer is not yet supported\n");
+ s->dregs[iaddr] = val;
+ break;
+
+ case Alternate_Feature_Status:
+ if ((s->dregs[iaddr] & PI) && !(val & PI)) {
+ /* XXX: TI CI */
+ qemu_irq_lower (s->pic);
+ s->regs[Status] &= ~INT;
+ }
+ s->dregs[iaddr] = val;
+ break;
+
+ case Version_Chip_ID:
+ lwarn ("write to Version_Chip_ID register %#x\n", val);
+ s->dregs[iaddr] = val;
+ break;
+
+ default:
+ s->dregs[iaddr] = val;
+ break;
+ }
+ dolog ("written value %#x to indirect register %d\n", val, iaddr);
+ break;
+
+ case Status:
+ if (s->regs[Status] & INT) {
+ qemu_irq_lower (s->pic);
+ }
+ s->regs[Status] &= ~INT;
+ s->dregs[Alternate_Feature_Status] &= ~(PI | CI | TI);
+ break;
+
+ case PIO_Data:
+ lwarn ("attempt to write value %#x to PIO register\n", val);
+ break;
+ }
+}
+
+static int cs_write_audio (CSState *s, int nchan, int dma_pos,
+ int dma_len, int len)
+{
+ int temp, net;
+ uint8_t tmpbuf[4096];
+
+ temp = len;
+ net = 0;
+
+ while (temp) {
+ int left = dma_len - dma_pos;
+ int copied;
+ size_t to_copy;
+
+ to_copy = audio_MIN (temp, left);
+ if (to_copy > sizeof (tmpbuf)) {
+ to_copy = sizeof (tmpbuf);
+ }
+
+ copied = DMA_read_memory (nchan, tmpbuf, dma_pos, to_copy);
+ if (s->tab) {
+ int i;
+ int16_t linbuf[4096];
+
+ for (i = 0; i < copied; ++i)
+ linbuf[i] = s->tab[tmpbuf[i]];
+ copied = AUD_write (s->voice, linbuf, copied << 1);
+ copied >>= 1;
+ }
+ else {
+ copied = AUD_write (s->voice, tmpbuf, copied);
+ }
+
+ temp -= copied;
+ dma_pos = (dma_pos + copied) % dma_len;
+ net += copied;
+
+ if (!copied) {
+ break;
+ }
+ }
+
+ return net;
+}
+
+static int cs_dma_read (void *opaque, int nchan, int dma_pos, int dma_len)
+{
+ CSState *s = opaque;
+ int copy, written;
+ int till = -1;
+
+ copy = s->voice ? (s->audio_free >> (s->tab != NULL)) : dma_len;
+
+ if (s->dregs[Pin_Control] & IEN) {
+ till = (s->dregs[Playback_Lower_Base_Count]
+ | (s->dregs[Playback_Upper_Base_Count] << 8)) << s->shift;
+ till -= s->transferred;
+ copy = audio_MIN (till, copy);
+ }
+
+ if ((copy <= 0) || (dma_len <= 0)) {
+ return dma_pos;
+ }
+
+ written = cs_write_audio (s, nchan, dma_pos, dma_len, copy);
+
+ dma_pos = (dma_pos + written) % dma_len;
+ s->audio_free -= (written << (s->tab != NULL));
+
+ if (written == till) {
+ s->regs[Status] |= INT;
+ s->dregs[Alternate_Feature_Status] |= PI;
+ s->transferred = 0;
+ qemu_irq_raise (s->pic);
+ }
+ else {
+ s->transferred += written;
+ }
+
+ return dma_pos;
+}
+
+static int cs4231a_pre_load (void *opaque)
+{
+ CSState *s = opaque;
+
+ if (s->dma_running) {
+ DMA_release_DREQ (s->dma);
+ AUD_set_active_out (s->voice, 0);
+ }
+ s->dma_running = 0;
+ return 0;
+}
+
+static int cs4231a_post_load (void *opaque, int version_id)
+{
+ CSState *s = opaque;
+
+ if (s->dma_running && (s->dregs[Interface_Configuration] & PEN)) {
+ s->dma_running = 0;
+ cs_reset_voices (s, s->dregs[FS_And_Playback_Data_Format]);
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_cs4231a = {
+ .name = "cs4231a",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_load = cs4231a_pre_load,
+ .post_load = cs4231a_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY (regs, CSState, CS_REGS),
+ VMSTATE_BUFFER (dregs, CSState),
+ VMSTATE_INT32 (dma_running, CSState),
+ VMSTATE_INT32 (audio_free, CSState),
+ VMSTATE_INT32 (transferred, CSState),
+ VMSTATE_INT32 (aci_counter, CSState),
+ VMSTATE_END_OF_LIST ()
+ }
+};
+
+static const MemoryRegionOps cs_ioport_ops = {
+ .read = cs_read,
+ .write = cs_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ }
+};
+
+static void cs4231a_initfn (Object *obj)
+{
+ CSState *s = CS4231A (obj);
+
+ memory_region_init_io (&s->ioports, OBJECT(s), &cs_ioport_ops, s,
+ "cs4231a", 4);
+}
+
+static void cs4231a_realizefn (DeviceState *dev, Error **errp)
+{
+ ISADevice *d = ISA_DEVICE (dev);
+ CSState *s = CS4231A (dev);
+
+ isa_init_irq (d, &s->pic, s->irq);
+
+ isa_register_ioport (d, &s->ioports, s->port);
+
+ DMA_register_channel (s->dma, cs_dma_read, s);
+
+ AUD_register_card ("cs4231a", &s->card);
+}
+
+static int cs4231a_init (ISABus *bus)
+{
+ isa_create_simple (bus, TYPE_CS4231A);
+ return 0;
+}
+
+static Property cs4231a_properties[] = {
+ DEFINE_PROP_UINT32 ("iobase", CSState, port, 0x534),
+ DEFINE_PROP_UINT32 ("irq", CSState, irq, 9),
+ DEFINE_PROP_UINT32 ("dma", CSState, dma, 3),
+ DEFINE_PROP_END_OF_LIST (),
+};
+
+static void cs4231a_class_initfn (ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS (klass);
+
+ dc->realize = cs4231a_realizefn;
+ dc->reset = cs4231a_reset;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = "Crystal Semiconductor CS4231A";
+ dc->vmsd = &vmstate_cs4231a;
+ dc->props = cs4231a_properties;
+}
+
+static const TypeInfo cs4231a_info = {
+ .name = TYPE_CS4231A,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof (CSState),
+ .instance_init = cs4231a_initfn,
+ .class_init = cs4231a_class_initfn,
+};
+
+static void cs4231a_register_types (void)
+{
+ type_register_static (&cs4231a_info);
+ isa_register_soundhw("cs4231a", "CS4231A", cs4231a_init);
+}
+
+type_init (cs4231a_register_types)
diff --git a/hw/audio/es1370.c b/hw/audio/es1370.c
new file mode 100644
index 00000000..8e7bcf50
--- /dev/null
+++ b/hw/audio/es1370.c
@@ -0,0 +1,1080 @@
+/*
+ * QEMU ES1370 emulation
+ *
+ * Copyright (c) 2005 Vassili Karpov (malc)
+ *
+ * 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.
+ */
+
+/* #define DEBUG_ES1370 */
+/* #define VERBOSE_ES1370 */
+#define SILENT_ES1370
+
+#include "hw/hw.h"
+#include "hw/audio/audio.h"
+#include "audio/audio.h"
+#include "hw/pci/pci.h"
+#include "sysemu/dma.h"
+
+/* Missing stuff:
+ SCTRL_P[12](END|ST)INC
+ SCTRL_P1SCTRLD
+ SCTRL_P2DACSEN
+ CTRL_DAC_SYNC
+ MIDI
+ non looped mode
+ surely more
+*/
+
+/*
+ Following macros and samplerate array were copied verbatim from
+ Linux kernel 2.4.30: drivers/sound/es1370.c
+
+ Copyright (C) 1998-2001, 2003 Thomas Sailer (t.sailer@alumni.ethz.ch)
+*/
+
+/* Start blatant GPL violation */
+
+#define ES1370_REG_CONTROL 0x00
+#define ES1370_REG_STATUS 0x04
+#define ES1370_REG_UART_DATA 0x08
+#define ES1370_REG_UART_STATUS 0x09
+#define ES1370_REG_UART_CONTROL 0x09
+#define ES1370_REG_UART_TEST 0x0a
+#define ES1370_REG_MEMPAGE 0x0c
+#define ES1370_REG_CODEC 0x10
+#define ES1370_REG_SERIAL_CONTROL 0x20
+#define ES1370_REG_DAC1_SCOUNT 0x24
+#define ES1370_REG_DAC2_SCOUNT 0x28
+#define ES1370_REG_ADC_SCOUNT 0x2c
+
+#define ES1370_REG_DAC1_FRAMEADR 0xc30
+#define ES1370_REG_DAC1_FRAMECNT 0xc34
+#define ES1370_REG_DAC2_FRAMEADR 0xc38
+#define ES1370_REG_DAC2_FRAMECNT 0xc3c
+#define ES1370_REG_ADC_FRAMEADR 0xd30
+#define ES1370_REG_ADC_FRAMECNT 0xd34
+#define ES1370_REG_PHANTOM_FRAMEADR 0xd38
+#define ES1370_REG_PHANTOM_FRAMECNT 0xd3c
+
+static const unsigned dac1_samplerate[] = { 5512, 11025, 22050, 44100 };
+
+#define DAC2_SRTODIV(x) (((1411200+(x)/2)/(x))-2)
+#define DAC2_DIVTOSR(x) (1411200/((x)+2))
+
+#define CTRL_ADC_STOP 0x80000000 /* 1 = ADC stopped */
+#define CTRL_XCTL1 0x40000000 /* electret mic bias */
+#define CTRL_OPEN 0x20000000 /* no function, can be read and written */
+#define CTRL_PCLKDIV 0x1fff0000 /* ADC/DAC2 clock divider */
+#define CTRL_SH_PCLKDIV 16
+#define CTRL_MSFMTSEL 0x00008000 /* MPEG serial data fmt: 0 = Sony, 1 = I2S */
+#define CTRL_M_SBB 0x00004000 /* DAC2 clock: 0 = PCLKDIV, 1 = MPEG */
+#define CTRL_WTSRSEL 0x00003000 /* DAC1 clock freq: 0=5512, 1=11025, 2=22050, 3=44100 */
+#define CTRL_SH_WTSRSEL 12
+#define CTRL_DAC_SYNC 0x00000800 /* 1 = DAC2 runs off DAC1 clock */
+#define CTRL_CCB_INTRM 0x00000400 /* 1 = CCB "voice" ints enabled */
+#define CTRL_M_CB 0x00000200 /* recording source: 0 = ADC, 1 = MPEG */
+#define CTRL_XCTL0 0x00000100 /* 0 = Line in, 1 = Line out */
+#define CTRL_BREQ 0x00000080 /* 1 = test mode (internal mem test) */
+#define CTRL_DAC1_EN 0x00000040 /* enable DAC1 */
+#define CTRL_DAC2_EN 0x00000020 /* enable DAC2 */
+#define CTRL_ADC_EN 0x00000010 /* enable ADC */
+#define CTRL_UART_EN 0x00000008 /* enable MIDI uart */
+#define CTRL_JYSTK_EN 0x00000004 /* enable Joystick port (presumably at address 0x200) */
+#define CTRL_CDC_EN 0x00000002 /* enable serial (CODEC) interface */
+#define CTRL_SERR_DIS 0x00000001 /* 1 = disable PCI SERR signal */
+
+#define STAT_INTR 0x80000000 /* wired or of all interrupt bits */
+#define STAT_CSTAT 0x00000400 /* 1 = codec busy or codec write in progress */
+#define STAT_CBUSY 0x00000200 /* 1 = codec busy */
+#define STAT_CWRIP 0x00000100 /* 1 = codec write in progress */
+#define STAT_VC 0x00000060 /* CCB int source, 0=DAC1, 1=DAC2, 2=ADC, 3=undef */
+#define STAT_SH_VC 5
+#define STAT_MCCB 0x00000010 /* CCB int pending */
+#define STAT_UART 0x00000008 /* UART int pending */
+#define STAT_DAC1 0x00000004 /* DAC1 int pending */
+#define STAT_DAC2 0x00000002 /* DAC2 int pending */
+#define STAT_ADC 0x00000001 /* ADC int pending */
+
+#define USTAT_RXINT 0x80 /* UART rx int pending */
+#define USTAT_TXINT 0x04 /* UART tx int pending */
+#define USTAT_TXRDY 0x02 /* UART tx ready */
+#define USTAT_RXRDY 0x01 /* UART rx ready */
+
+#define UCTRL_RXINTEN 0x80 /* 1 = enable RX ints */
+#define UCTRL_TXINTEN 0x60 /* TX int enable field mask */
+#define UCTRL_ENA_TXINT 0x20 /* enable TX int */
+#define UCTRL_CNTRL 0x03 /* control field */
+#define UCTRL_CNTRL_SWR 0x03 /* software reset command */
+
+#define SCTRL_P2ENDINC 0x00380000 /* */
+#define SCTRL_SH_P2ENDINC 19
+#define SCTRL_P2STINC 0x00070000 /* */
+#define SCTRL_SH_P2STINC 16
+#define SCTRL_R1LOOPSEL 0x00008000 /* 0 = loop mode */
+#define SCTRL_P2LOOPSEL 0x00004000 /* 0 = loop mode */
+#define SCTRL_P1LOOPSEL 0x00002000 /* 0 = loop mode */
+#define SCTRL_P2PAUSE 0x00001000 /* 1 = pause mode */
+#define SCTRL_P1PAUSE 0x00000800 /* 1 = pause mode */
+#define SCTRL_R1INTEN 0x00000400 /* enable interrupt */
+#define SCTRL_P2INTEN 0x00000200 /* enable interrupt */
+#define SCTRL_P1INTEN 0x00000100 /* enable interrupt */
+#define SCTRL_P1SCTRLD 0x00000080 /* reload sample count register for DAC1 */
+#define SCTRL_P2DACSEN 0x00000040 /* 1 = DAC2 play back last sample when disabled */
+#define SCTRL_R1SEB 0x00000020 /* 1 = 16bit */
+#define SCTRL_R1SMB 0x00000010 /* 1 = stereo */
+#define SCTRL_R1FMT 0x00000030 /* format mask */
+#define SCTRL_SH_R1FMT 4
+#define SCTRL_P2SEB 0x00000008 /* 1 = 16bit */
+#define SCTRL_P2SMB 0x00000004 /* 1 = stereo */
+#define SCTRL_P2FMT 0x0000000c /* format mask */
+#define SCTRL_SH_P2FMT 2
+#define SCTRL_P1SEB 0x00000002 /* 1 = 16bit */
+#define SCTRL_P1SMB 0x00000001 /* 1 = stereo */
+#define SCTRL_P1FMT 0x00000003 /* format mask */
+#define SCTRL_SH_P1FMT 0
+
+/* End blatant GPL violation */
+
+#define NB_CHANNELS 3
+#define DAC1_CHANNEL 0
+#define DAC2_CHANNEL 1
+#define ADC_CHANNEL 2
+
+#define IO_READ_PROTO(n) \
+static uint32_t n (void *opaque, uint32_t addr)
+#define IO_WRITE_PROTO(n) \
+static void n (void *opaque, uint32_t addr, uint32_t val)
+
+static void es1370_dac1_callback (void *opaque, int free);
+static void es1370_dac2_callback (void *opaque, int free);
+static void es1370_adc_callback (void *opaque, int avail);
+
+#ifdef DEBUG_ES1370
+
+#define ldebug(...) AUD_log ("es1370", __VA_ARGS__)
+
+static void print_ctl (uint32_t val)
+{
+ char buf[1024];
+
+ buf[0] = '\0';
+#define a(n) if (val & CTRL_##n) strcat (buf, " "#n)
+ a (ADC_STOP);
+ a (XCTL1);
+ a (OPEN);
+ a (MSFMTSEL);
+ a (M_SBB);
+ a (DAC_SYNC);
+ a (CCB_INTRM);
+ a (M_CB);
+ a (XCTL0);
+ a (BREQ);
+ a (DAC1_EN);
+ a (DAC2_EN);
+ a (ADC_EN);
+ a (UART_EN);
+ a (JYSTK_EN);
+ a (CDC_EN);
+ a (SERR_DIS);
+#undef a
+ AUD_log ("es1370", "ctl - PCLKDIV %d(DAC2 freq %d), freq %d,%s\n",
+ (val & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV,
+ DAC2_DIVTOSR ((val & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV),
+ dac1_samplerate[(val & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL],
+ buf);
+}
+
+static void print_sctl (uint32_t val)
+{
+ static const char *fmt_names[] = {"8M", "8S", "16M", "16S"};
+ char buf[1024];
+
+ buf[0] = '\0';
+
+#define a(n) if (val & SCTRL_##n) strcat (buf, " "#n)
+#define b(n) if (!(val & SCTRL_##n)) strcat (buf, " "#n)
+ b (R1LOOPSEL);
+ b (P2LOOPSEL);
+ b (P1LOOPSEL);
+ a (P2PAUSE);
+ a (P1PAUSE);
+ a (R1INTEN);
+ a (P2INTEN);
+ a (P1INTEN);
+ a (P1SCTRLD);
+ a (P2DACSEN);
+ if (buf[0]) {
+ strcat (buf, "\n ");
+ }
+ else {
+ buf[0] = ' ';
+ buf[1] = '\0';
+ }
+#undef b
+#undef a
+ AUD_log ("es1370",
+ "%s"
+ "p2_end_inc %d, p2_st_inc %d, r1_fmt %s, p2_fmt %s, p1_fmt %s\n",
+ buf,
+ (val & SCTRL_P2ENDINC) >> SCTRL_SH_P2ENDINC,
+ (val & SCTRL_P2STINC) >> SCTRL_SH_P2STINC,
+ fmt_names [(val >> SCTRL_SH_R1FMT) & 3],
+ fmt_names [(val >> SCTRL_SH_P2FMT) & 3],
+ fmt_names [(val >> SCTRL_SH_P1FMT) & 3]
+ );
+}
+#else
+#define ldebug(...)
+#define print_ctl(...)
+#define print_sctl(...)
+#endif
+
+#ifdef VERBOSE_ES1370
+#define dolog(...) AUD_log ("es1370", __VA_ARGS__)
+#else
+#define dolog(...)
+#endif
+
+#ifndef SILENT_ES1370
+#define lwarn(...) AUD_log ("es1370: warning", __VA_ARGS__)
+#else
+#define lwarn(...)
+#endif
+
+struct chan {
+ uint32_t shift;
+ uint32_t leftover;
+ uint32_t scount;
+ uint32_t frame_addr;
+ uint32_t frame_cnt;
+};
+
+typedef struct ES1370State {
+ PCIDevice dev;
+ QEMUSoundCard card;
+ MemoryRegion io;
+ struct chan chan[NB_CHANNELS];
+ SWVoiceOut *dac_voice[2];
+ SWVoiceIn *adc_voice;
+
+ uint32_t ctl;
+ uint32_t status;
+ uint32_t mempage;
+ uint32_t codec;
+ uint32_t sctl;
+} ES1370State;
+
+struct chan_bits {
+ uint32_t ctl_en;
+ uint32_t stat_int;
+ uint32_t sctl_pause;
+ uint32_t sctl_inten;
+ uint32_t sctl_fmt;
+ uint32_t sctl_sh_fmt;
+ uint32_t sctl_loopsel;
+ void (*calc_freq) (ES1370State *s, uint32_t ctl,
+ uint32_t *old_freq, uint32_t *new_freq);
+};
+
+static void es1370_dac1_calc_freq (ES1370State *s, uint32_t ctl,
+ uint32_t *old_freq, uint32_t *new_freq);
+static void es1370_dac2_and_adc_calc_freq (ES1370State *s, uint32_t ctl,
+ uint32_t *old_freq,
+ uint32_t *new_freq);
+
+static const struct chan_bits es1370_chan_bits[] = {
+ {CTRL_DAC1_EN, STAT_DAC1, SCTRL_P1PAUSE, SCTRL_P1INTEN,
+ SCTRL_P1FMT, SCTRL_SH_P1FMT, SCTRL_P1LOOPSEL,
+ es1370_dac1_calc_freq},
+
+ {CTRL_DAC2_EN, STAT_DAC2, SCTRL_P2PAUSE, SCTRL_P2INTEN,
+ SCTRL_P2FMT, SCTRL_SH_P2FMT, SCTRL_P2LOOPSEL,
+ es1370_dac2_and_adc_calc_freq},
+
+ {CTRL_ADC_EN, STAT_ADC, 0, SCTRL_R1INTEN,
+ SCTRL_R1FMT, SCTRL_SH_R1FMT, SCTRL_R1LOOPSEL,
+ es1370_dac2_and_adc_calc_freq}
+};
+
+static void es1370_update_status (ES1370State *s, uint32_t new_status)
+{
+ uint32_t level = new_status & (STAT_DAC1 | STAT_DAC2 | STAT_ADC);
+
+ if (level) {
+ s->status = new_status | STAT_INTR;
+ }
+ else {
+ s->status = new_status & ~STAT_INTR;
+ }
+ pci_set_irq(&s->dev, !!level);
+}
+
+static void es1370_reset (ES1370State *s)
+{
+ size_t i;
+
+ s->ctl = 1;
+ s->status = 0x60;
+ s->mempage = 0;
+ s->codec = 0;
+ s->sctl = 0;
+
+ for (i = 0; i < NB_CHANNELS; ++i) {
+ struct chan *d = &s->chan[i];
+ d->scount = 0;
+ d->leftover = 0;
+ if (i == ADC_CHANNEL) {
+ AUD_close_in (&s->card, s->adc_voice);
+ s->adc_voice = NULL;
+ }
+ else {
+ AUD_close_out (&s->card, s->dac_voice[i]);
+ s->dac_voice[i] = NULL;
+ }
+ }
+ pci_irq_deassert(&s->dev);
+}
+
+static void es1370_maybe_lower_irq (ES1370State *s, uint32_t sctl)
+{
+ uint32_t new_status = s->status;
+
+ if (!(sctl & SCTRL_P1INTEN) && (s->sctl & SCTRL_P1INTEN)) {
+ new_status &= ~STAT_DAC1;
+ }
+
+ if (!(sctl & SCTRL_P2INTEN) && (s->sctl & SCTRL_P2INTEN)) {
+ new_status &= ~STAT_DAC2;
+ }
+
+ if (!(sctl & SCTRL_R1INTEN) && (s->sctl & SCTRL_R1INTEN)) {
+ new_status &= ~STAT_ADC;
+ }
+
+ if (new_status != s->status) {
+ es1370_update_status (s, new_status);
+ }
+}
+
+static void es1370_dac1_calc_freq (ES1370State *s, uint32_t ctl,
+ uint32_t *old_freq, uint32_t *new_freq)
+
+{
+ *old_freq = dac1_samplerate[(s->ctl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL];
+ *new_freq = dac1_samplerate[(ctl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL];
+}
+
+static void es1370_dac2_and_adc_calc_freq (ES1370State *s, uint32_t ctl,
+ uint32_t *old_freq,
+ uint32_t *new_freq)
+
+{
+ uint32_t old_pclkdiv, new_pclkdiv;
+
+ new_pclkdiv = (ctl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV;
+ old_pclkdiv = (s->ctl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV;
+ *new_freq = DAC2_DIVTOSR (new_pclkdiv);
+ *old_freq = DAC2_DIVTOSR (old_pclkdiv);
+}
+
+static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl)
+{
+ size_t i;
+ uint32_t old_freq, new_freq, old_fmt, new_fmt;
+
+ for (i = 0; i < NB_CHANNELS; ++i) {
+ struct chan *d = &s->chan[i];
+ const struct chan_bits *b = &es1370_chan_bits[i];
+
+ new_fmt = (sctl & b->sctl_fmt) >> b->sctl_sh_fmt;
+ old_fmt = (s->sctl & b->sctl_fmt) >> b->sctl_sh_fmt;
+
+ b->calc_freq (s, ctl, &old_freq, &new_freq);
+
+ if ((old_fmt != new_fmt) || (old_freq != new_freq)) {
+ d->shift = (new_fmt & 1) + (new_fmt >> 1);
+ ldebug ("channel %zu, freq = %d, nchannels %d, fmt %d, shift %d\n",
+ i,
+ new_freq,
+ 1 << (new_fmt & 1),
+ (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8,
+ d->shift);
+ if (new_freq) {
+ struct audsettings as;
+
+ as.freq = new_freq;
+ as.nchannels = 1 << (new_fmt & 1);
+ as.fmt = (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8;
+ as.endianness = 0;
+
+ if (i == ADC_CHANNEL) {
+ s->adc_voice =
+ AUD_open_in (
+ &s->card,
+ s->adc_voice,
+ "es1370.adc",
+ s,
+ es1370_adc_callback,
+ &as
+ );
+ }
+ else {
+ s->dac_voice[i] =
+ AUD_open_out (
+ &s->card,
+ s->dac_voice[i],
+ i ? "es1370.dac2" : "es1370.dac1",
+ s,
+ i ? es1370_dac2_callback : es1370_dac1_callback,
+ &as
+ );
+ }
+ }
+ }
+
+ if (((ctl ^ s->ctl) & b->ctl_en)
+ || ((sctl ^ s->sctl) & b->sctl_pause)) {
+ int on = (ctl & b->ctl_en) && !(sctl & b->sctl_pause);
+
+ if (i == ADC_CHANNEL) {
+ AUD_set_active_in (s->adc_voice, on);
+ }
+ else {
+ AUD_set_active_out (s->dac_voice[i], on);
+ }
+ }
+ }
+
+ s->ctl = ctl;
+ s->sctl = sctl;
+}
+
+static inline uint32_t es1370_fixup (ES1370State *s, uint32_t addr)
+{
+ addr &= 0xff;
+ if (addr >= 0x30 && addr <= 0x3f)
+ addr |= s->mempage << 8;
+ return addr;
+}
+
+IO_WRITE_PROTO (es1370_writeb)
+{
+ ES1370State *s = opaque;
+ uint32_t shift, mask;
+
+ addr = es1370_fixup (s, addr);
+
+ switch (addr) {
+ case ES1370_REG_CONTROL:
+ case ES1370_REG_CONTROL + 1:
+ case ES1370_REG_CONTROL + 2:
+ case ES1370_REG_CONTROL + 3:
+ shift = (addr - ES1370_REG_CONTROL) << 3;
+ mask = 0xff << shift;
+ val = (s->ctl & ~mask) | ((val & 0xff) << shift);
+ es1370_update_voices (s, val, s->sctl);
+ print_ctl (val);
+ break;
+ case ES1370_REG_MEMPAGE:
+ s->mempage = val;
+ break;
+ case ES1370_REG_SERIAL_CONTROL:
+ case ES1370_REG_SERIAL_CONTROL + 1:
+ case ES1370_REG_SERIAL_CONTROL + 2:
+ case ES1370_REG_SERIAL_CONTROL + 3:
+ shift = (addr - ES1370_REG_SERIAL_CONTROL) << 3;
+ mask = 0xff << shift;
+ val = (s->sctl & ~mask) | ((val & 0xff) << shift);
+ es1370_maybe_lower_irq (s, val);
+ es1370_update_voices (s, s->ctl, val);
+ print_sctl (val);
+ break;
+ default:
+ lwarn ("writeb %#x <- %#x\n", addr, val);
+ break;
+ }
+}
+
+IO_WRITE_PROTO (es1370_writew)
+{
+ ES1370State *s = opaque;
+ addr = es1370_fixup (s, addr);
+ uint32_t shift, mask;
+ struct chan *d = &s->chan[0];
+
+ switch (addr) {
+ case ES1370_REG_CODEC:
+ dolog ("ignored codec write address %#x, data %#x\n",
+ (val >> 8) & 0xff, val & 0xff);
+ s->codec = val;
+ break;
+
+ case ES1370_REG_CONTROL:
+ case ES1370_REG_CONTROL + 2:
+ shift = (addr != ES1370_REG_CONTROL) << 4;
+ mask = 0xffff << shift;
+ val = (s->ctl & ~mask) | ((val & 0xffff) << shift);
+ es1370_update_voices (s, val, s->sctl);
+ print_ctl (val);
+ break;
+
+ case ES1370_REG_ADC_SCOUNT:
+ d++;
+ case ES1370_REG_DAC2_SCOUNT:
+ d++;
+ case ES1370_REG_DAC1_SCOUNT:
+ d->scount = (d->scount & ~0xffff) | (val & 0xffff);
+ break;
+
+ default:
+ lwarn ("writew %#x <- %#x\n", addr, val);
+ break;
+ }
+}
+
+IO_WRITE_PROTO (es1370_writel)
+{
+ ES1370State *s = opaque;
+ struct chan *d = &s->chan[0];
+
+ addr = es1370_fixup (s, addr);
+
+ switch (addr) {
+ case ES1370_REG_CONTROL:
+ es1370_update_voices (s, val, s->sctl);
+ print_ctl (val);
+ break;
+
+ case ES1370_REG_MEMPAGE:
+ s->mempage = val & 0xf;
+ break;
+
+ case ES1370_REG_SERIAL_CONTROL:
+ es1370_maybe_lower_irq (s, val);
+ es1370_update_voices (s, s->ctl, val);
+ print_sctl (val);
+ break;
+
+ case ES1370_REG_ADC_SCOUNT:
+ d++;
+ case ES1370_REG_DAC2_SCOUNT:
+ d++;
+ case ES1370_REG_DAC1_SCOUNT:
+ d->scount = (val & 0xffff) | (d->scount & ~0xffff);
+ ldebug ("chan %td CURR_SAMP_CT %d, SAMP_CT %d\n",
+ d - &s->chan[0], val >> 16, (val & 0xffff));
+ break;
+
+ case ES1370_REG_ADC_FRAMEADR:
+ d++;
+ case ES1370_REG_DAC2_FRAMEADR:
+ d++;
+ case ES1370_REG_DAC1_FRAMEADR:
+ d->frame_addr = val;
+ ldebug ("chan %td frame address %#x\n", d - &s->chan[0], val);
+ break;
+
+ case ES1370_REG_PHANTOM_FRAMECNT:
+ lwarn ("writing to phantom frame count %#x\n", val);
+ break;
+ case ES1370_REG_PHANTOM_FRAMEADR:
+ lwarn ("writing to phantom frame address %#x\n", val);
+ break;
+
+ case ES1370_REG_ADC_FRAMECNT:
+ d++;
+ case ES1370_REG_DAC2_FRAMECNT:
+ d++;
+ case ES1370_REG_DAC1_FRAMECNT:
+ d->frame_cnt = val;
+ d->leftover = 0;
+ ldebug ("chan %td frame count %d, buffer size %d\n",
+ d - &s->chan[0], val >> 16, val & 0xffff);
+ break;
+
+ default:
+ lwarn ("writel %#x <- %#x\n", addr, val);
+ break;
+ }
+}
+
+IO_READ_PROTO (es1370_readb)
+{
+ ES1370State *s = opaque;
+ uint32_t val;
+
+ addr = es1370_fixup (s, addr);
+
+ switch (addr) {
+ case 0x1b: /* Legacy */
+ lwarn ("Attempt to read from legacy register\n");
+ val = 5;
+ break;
+ case ES1370_REG_MEMPAGE:
+ val = s->mempage;
+ break;
+ case ES1370_REG_CONTROL + 0:
+ case ES1370_REG_CONTROL + 1:
+ case ES1370_REG_CONTROL + 2:
+ case ES1370_REG_CONTROL + 3:
+ val = s->ctl >> ((addr - ES1370_REG_CONTROL) << 3);
+ break;
+ case ES1370_REG_STATUS + 0:
+ case ES1370_REG_STATUS + 1:
+ case ES1370_REG_STATUS + 2:
+ case ES1370_REG_STATUS + 3:
+ val = s->status >> ((addr - ES1370_REG_STATUS) << 3);
+ break;
+ default:
+ val = ~0;
+ lwarn ("readb %#x -> %#x\n", addr, val);
+ break;
+ }
+ return val;
+}
+
+IO_READ_PROTO (es1370_readw)
+{
+ ES1370State *s = opaque;
+ struct chan *d = &s->chan[0];
+ uint32_t val;
+
+ addr = es1370_fixup (s, addr);
+
+ switch (addr) {
+ case ES1370_REG_ADC_SCOUNT + 2:
+ d++;
+ case ES1370_REG_DAC2_SCOUNT + 2:
+ d++;
+ case ES1370_REG_DAC1_SCOUNT + 2:
+ val = d->scount >> 16;
+ break;
+
+ case ES1370_REG_ADC_FRAMECNT:
+ d++;
+ case ES1370_REG_DAC2_FRAMECNT:
+ d++;
+ case ES1370_REG_DAC1_FRAMECNT:
+ val = d->frame_cnt & 0xffff;
+ break;
+
+ case ES1370_REG_ADC_FRAMECNT + 2:
+ d++;
+ case ES1370_REG_DAC2_FRAMECNT + 2:
+ d++;
+ case ES1370_REG_DAC1_FRAMECNT + 2:
+ val = d->frame_cnt >> 16;
+ break;
+
+ default:
+ val = ~0;
+ lwarn ("readw %#x -> %#x\n", addr, val);
+ break;
+ }
+
+ return val;
+}
+
+IO_READ_PROTO (es1370_readl)
+{
+ ES1370State *s = opaque;
+ uint32_t val;
+ struct chan *d = &s->chan[0];
+
+ addr = es1370_fixup (s, addr);
+
+ switch (addr) {
+ case ES1370_REG_CONTROL:
+ val = s->ctl;
+ break;
+ case ES1370_REG_STATUS:
+ val = s->status;
+ break;
+ case ES1370_REG_MEMPAGE:
+ val = s->mempage;
+ break;
+ case ES1370_REG_CODEC:
+ val = s->codec;
+ break;
+ case ES1370_REG_SERIAL_CONTROL:
+ val = s->sctl;
+ break;
+
+ case ES1370_REG_ADC_SCOUNT:
+ d++;
+ case ES1370_REG_DAC2_SCOUNT:
+ d++;
+ case ES1370_REG_DAC1_SCOUNT:
+ val = d->scount;
+#ifdef DEBUG_ES1370
+ {
+ uint32_t curr_count = d->scount >> 16;
+ uint32_t count = d->scount & 0xffff;
+
+ curr_count <<= d->shift;
+ count <<= d->shift;
+ dolog ("read scount curr %d, total %d\n", curr_count, count);
+ }
+#endif
+ break;
+
+ case ES1370_REG_ADC_FRAMECNT:
+ d++;
+ case ES1370_REG_DAC2_FRAMECNT:
+ d++;
+ case ES1370_REG_DAC1_FRAMECNT:
+ val = d->frame_cnt;
+#ifdef DEBUG_ES1370
+ {
+ uint32_t size = ((d->frame_cnt & 0xffff) + 1) << 2;
+ uint32_t curr = ((d->frame_cnt >> 16) + 1) << 2;
+ if (curr > size) {
+ dolog ("read framecnt curr %d, size %d %d\n", curr, size,
+ curr > size);
+ }
+ }
+#endif
+ break;
+
+ case ES1370_REG_ADC_FRAMEADR:
+ d++;
+ case ES1370_REG_DAC2_FRAMEADR:
+ d++;
+ case ES1370_REG_DAC1_FRAMEADR:
+ val = d->frame_addr;
+ break;
+
+ case ES1370_REG_PHANTOM_FRAMECNT:
+ val = ~0U;
+ lwarn ("reading from phantom frame count\n");
+ break;
+ case ES1370_REG_PHANTOM_FRAMEADR:
+ val = ~0U;
+ lwarn ("reading from phantom frame address\n");
+ break;
+
+ default:
+ val = ~0U;
+ lwarn ("readl %#x -> %#x\n", addr, val);
+ break;
+ }
+ return val;
+}
+
+static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel,
+ int max, int *irq)
+{
+ uint8_t tmpbuf[4096];
+ uint32_t addr = d->frame_addr;
+ int sc = d->scount & 0xffff;
+ int csc = d->scount >> 16;
+ int csc_bytes = (csc + 1) << d->shift;
+ int cnt = d->frame_cnt >> 16;
+ int size = d->frame_cnt & 0xffff;
+ int left = ((size - cnt + 1) << 2) + d->leftover;
+ int transferred = 0;
+ int temp = audio_MIN (max, audio_MIN (left, csc_bytes));
+ int index = d - &s->chan[0];
+
+ addr += (cnt << 2) + d->leftover;
+
+ if (index == ADC_CHANNEL) {
+ while (temp) {
+ int acquired, to_copy;
+
+ to_copy = audio_MIN ((size_t) temp, sizeof (tmpbuf));
+ acquired = AUD_read (s->adc_voice, tmpbuf, to_copy);
+ if (!acquired)
+ break;
+
+ pci_dma_write (&s->dev, addr, tmpbuf, acquired);
+
+ temp -= acquired;
+ addr += acquired;
+ transferred += acquired;
+ }
+ }
+ else {
+ SWVoiceOut *voice = s->dac_voice[index];
+
+ while (temp) {
+ int copied, to_copy;
+
+ to_copy = audio_MIN ((size_t) temp, sizeof (tmpbuf));
+ pci_dma_read (&s->dev, addr, tmpbuf, to_copy);
+ copied = AUD_write (voice, tmpbuf, to_copy);
+ if (!copied)
+ break;
+ temp -= copied;
+ addr += copied;
+ transferred += copied;
+ }
+ }
+
+ if (csc_bytes == transferred) {
+ *irq = 1;
+ d->scount = sc | (sc << 16);
+ ldebug ("sc = %d, rate = %f\n",
+ (sc + 1) << d->shift,
+ (sc + 1) / (double) 44100);
+ }
+ else {
+ *irq = 0;
+ d->scount = sc | (((csc_bytes - transferred - 1) >> d->shift) << 16);
+ }
+
+ cnt += (transferred + d->leftover) >> 2;
+
+ if (s->sctl & loop_sel) {
+ /* Bah, how stupid is that having a 0 represent true value?
+ i just spent few hours on this shit */
+ AUD_log ("es1370: warning", "non looping mode\n");
+ }
+ else {
+ d->frame_cnt = size;
+
+ if ((uint32_t) cnt <= d->frame_cnt)
+ d->frame_cnt |= cnt << 16;
+ }
+
+ d->leftover = (transferred + d->leftover) & 3;
+}
+
+static void es1370_run_channel (ES1370State *s, size_t chan, int free_or_avail)
+{
+ uint32_t new_status = s->status;
+ int max_bytes, irq;
+ struct chan *d = &s->chan[chan];
+ const struct chan_bits *b = &es1370_chan_bits[chan];
+
+ if (!(s->ctl & b->ctl_en) || (s->sctl & b->sctl_pause)) {
+ return;
+ }
+
+ max_bytes = free_or_avail;
+ max_bytes &= ~((1 << d->shift) - 1);
+ if (!max_bytes) {
+ return;
+ }
+
+ es1370_transfer_audio (s, d, b->sctl_loopsel, max_bytes, &irq);
+
+ if (irq) {
+ if (s->sctl & b->sctl_inten) {
+ new_status |= b->stat_int;
+ }
+ }
+
+ if (new_status != s->status) {
+ es1370_update_status (s, new_status);
+ }
+}
+
+static void es1370_dac1_callback (void *opaque, int free)
+{
+ ES1370State *s = opaque;
+
+ es1370_run_channel (s, DAC1_CHANNEL, free);
+}
+
+static void es1370_dac2_callback (void *opaque, int free)
+{
+ ES1370State *s = opaque;
+
+ es1370_run_channel (s, DAC2_CHANNEL, free);
+}
+
+static void es1370_adc_callback (void *opaque, int avail)
+{
+ ES1370State *s = opaque;
+
+ es1370_run_channel (s, ADC_CHANNEL, avail);
+}
+
+static uint64_t es1370_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ switch (size) {
+ case 1:
+ return es1370_readb(opaque, addr);
+ case 2:
+ return es1370_readw(opaque, addr);
+ case 4:
+ return es1370_readl(opaque, addr);
+ default:
+ return -1;
+ }
+}
+
+static void es1370_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ switch (size) {
+ case 1:
+ es1370_writeb(opaque, addr, val);
+ break;
+ case 2:
+ es1370_writew(opaque, addr, val);
+ break;
+ case 4:
+ es1370_writel(opaque, addr, val);
+ break;
+ }
+}
+
+static const MemoryRegionOps es1370_io_ops = {
+ .read = es1370_read,
+ .write = es1370_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_es1370_channel = {
+ .name = "es1370_channel",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32 (shift, struct chan),
+ VMSTATE_UINT32 (leftover, struct chan),
+ VMSTATE_UINT32 (scount, struct chan),
+ VMSTATE_UINT32 (frame_addr, struct chan),
+ VMSTATE_UINT32 (frame_cnt, struct chan),
+ VMSTATE_END_OF_LIST ()
+ }
+};
+
+static int es1370_post_load (void *opaque, int version_id)
+{
+ uint32_t ctl, sctl;
+ ES1370State *s = opaque;
+ size_t i;
+
+ for (i = 0; i < NB_CHANNELS; ++i) {
+ if (i == ADC_CHANNEL) {
+ if (s->adc_voice) {
+ AUD_close_in (&s->card, s->adc_voice);
+ s->adc_voice = NULL;
+ }
+ }
+ else {
+ if (s->dac_voice[i]) {
+ AUD_close_out (&s->card, s->dac_voice[i]);
+ s->dac_voice[i] = NULL;
+ }
+ }
+ }
+
+ ctl = s->ctl;
+ sctl = s->sctl;
+ s->ctl = 0;
+ s->sctl = 0;
+ es1370_update_voices (s, ctl, sctl);
+ return 0;
+}
+
+static const VMStateDescription vmstate_es1370 = {
+ .name = "es1370",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = es1370_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE (dev, ES1370State),
+ VMSTATE_STRUCT_ARRAY (chan, ES1370State, NB_CHANNELS, 2,
+ vmstate_es1370_channel, struct chan),
+ VMSTATE_UINT32 (ctl, ES1370State),
+ VMSTATE_UINT32 (status, ES1370State),
+ VMSTATE_UINT32 (mempage, ES1370State),
+ VMSTATE_UINT32 (codec, ES1370State),
+ VMSTATE_UINT32 (sctl, ES1370State),
+ VMSTATE_END_OF_LIST ()
+ }
+};
+
+static void es1370_on_reset (void *opaque)
+{
+ ES1370State *s = opaque;
+ es1370_reset (s);
+}
+
+static void es1370_realize(PCIDevice *dev, Error **errp)
+{
+ ES1370State *s = DO_UPCAST (ES1370State, dev, dev);
+ uint8_t *c = s->dev.config;
+
+ c[PCI_STATUS + 1] = PCI_STATUS_DEVSEL_SLOW >> 8;
+
+#if 0
+ c[PCI_CAPABILITY_LIST] = 0xdc;
+ c[PCI_INTERRUPT_LINE] = 10;
+ c[0xdc] = 0x00;
+#endif
+
+ c[PCI_INTERRUPT_PIN] = 1;
+ c[PCI_MIN_GNT] = 0x0c;
+ c[PCI_MAX_LAT] = 0x80;
+
+ memory_region_init_io (&s->io, OBJECT(s), &es1370_io_ops, s, "es1370", 256);
+ pci_register_bar (&s->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+ qemu_register_reset (es1370_on_reset, s);
+
+ AUD_register_card ("es1370", &s->card);
+ es1370_reset (s);
+}
+
+static int es1370_init (PCIBus *bus)
+{
+ pci_create_simple (bus, -1, "ES1370");
+ return 0;
+}
+
+static void es1370_class_init (ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS (klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS (klass);
+
+ k->realize = es1370_realize;
+ k->vendor_id = PCI_VENDOR_ID_ENSONIQ;
+ k->device_id = PCI_DEVICE_ID_ENSONIQ_ES1370;
+ k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
+ k->subsystem_vendor_id = 0x4942;
+ k->subsystem_id = 0x4c4c;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = "ENSONIQ AudioPCI ES1370";
+ dc->vmsd = &vmstate_es1370;
+}
+
+static const TypeInfo es1370_info = {
+ .name = "ES1370",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof (ES1370State),
+ .class_init = es1370_class_init,
+};
+
+static void es1370_register_types (void)
+{
+ type_register_static (&es1370_info);
+ pci_register_soundhw("es1370", "ENSONIQ AudioPCI ES1370", es1370_init);
+}
+
+type_init (es1370_register_types)
+
diff --git a/hw/audio/fmopl.c b/hw/audio/fmopl.c
new file mode 100644
index 00000000..adcef2d3
--- /dev/null
+++ b/hw/audio/fmopl.c
@@ -0,0 +1,1394 @@
+/*
+**
+** File: fmopl.c -- software implementation of FM sound generator
+**
+** Copyright (C) 1999,2000 Tatsuyuki Satoh , MultiArcadeMachineEmurator development
+**
+** Version 0.37a
+**
+*/
+
+/*
+ preliminary :
+ Problem :
+ note:
+*/
+
+/* This version of fmopl.c is a fork of the MAME one, relicensed under the LGPL.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define HAS_YM3812 1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <math.h>
+//#include "driver.h" /* use M.A.M.E. */
+#include "fmopl.h"
+
+#ifndef PI
+#define PI 3.14159265358979323846
+#endif
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+
+/* -------------------- for debug --------------------- */
+/* #define OPL_OUTPUT_LOG */
+#ifdef OPL_OUTPUT_LOG
+static FILE *opl_dbg_fp = NULL;
+static FM_OPL *opl_dbg_opl[16];
+static int opl_dbg_maxchip,opl_dbg_chip;
+#endif
+
+/* -------------------- preliminary define section --------------------- */
+/* attack/decay rate time rate */
+#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */
+#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */
+
+#define DELTAT_MIXING_LEVEL (1) /* DELTA-T ADPCM MIXING LEVEL */
+
+#define FREQ_BITS 24 /* frequency turn */
+
+/* counter bits = 20 , octerve 7 */
+#define FREQ_RATE (1<<(FREQ_BITS-20))
+#define TL_BITS (FREQ_BITS+2)
+
+/* final output shift , limit minimum and maximum */
+#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */
+#define OPL_MAXOUT (0x7fff<<OPL_OUTSB)
+#define OPL_MINOUT (-0x8000<<OPL_OUTSB)
+
+/* -------------------- quality selection --------------------- */
+
+/* sinwave entries */
+/* used static memory = SIN_ENT * 4 (byte) */
+#define SIN_ENT 2048
+
+/* output level entries (envelope,sinwave) */
+/* envelope counter lower bits */
+#define ENV_BITS 16
+/* envelope output entries */
+#define EG_ENT 4096
+/* used dynamic memory = EG_ENT*4*4(byte)or EG_ENT*6*4(byte) */
+/* used static memory = EG_ENT*4 (byte) */
+
+#define EG_OFF ((2*EG_ENT)<<ENV_BITS) /* OFF */
+#define EG_DED EG_OFF
+#define EG_DST (EG_ENT<<ENV_BITS) /* DECAY START */
+#define EG_AED EG_DST
+#define EG_AST 0 /* ATTACK START */
+
+#define EG_STEP (96.0/EG_ENT) /* OPL is 0.1875 dB step */
+
+/* LFO table entries */
+#define VIB_ENT 512
+#define VIB_SHIFT (32-9)
+#define AMS_ENT 512
+#define AMS_SHIFT (32-9)
+
+#define VIB_RATE 256
+
+/* -------------------- local defines , macros --------------------- */
+
+/* register number to channel number , slot offset */
+#define SLOT1 0
+#define SLOT2 1
+
+/* envelope phase */
+#define ENV_MOD_RR 0x00
+#define ENV_MOD_DR 0x01
+#define ENV_MOD_AR 0x02
+
+/* -------------------- tables --------------------- */
+static const int slot_array[32]=
+{
+ 0, 2, 4, 1, 3, 5,-1,-1,
+ 6, 8,10, 7, 9,11,-1,-1,
+ 12,14,16,13,15,17,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1
+};
+
+/* key scale level */
+/* table is 3dB/OCT , DV converts this in TL step at 6dB/OCT */
+#define DV (EG_STEP/2)
+static const UINT32 KSL_TABLE[8*16]=
+{
+ /* OCT 0 */
+ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+ /* OCT 1 */
+ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+ 0.000/DV, 0.750/DV, 1.125/DV, 1.500/DV,
+ 1.875/DV, 2.250/DV, 2.625/DV, 3.000/DV,
+ /* OCT 2 */
+ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+ 0.000/DV, 1.125/DV, 1.875/DV, 2.625/DV,
+ 3.000/DV, 3.750/DV, 4.125/DV, 4.500/DV,
+ 4.875/DV, 5.250/DV, 5.625/DV, 6.000/DV,
+ /* OCT 3 */
+ 0.000/DV, 0.000/DV, 0.000/DV, 1.875/DV,
+ 3.000/DV, 4.125/DV, 4.875/DV, 5.625/DV,
+ 6.000/DV, 6.750/DV, 7.125/DV, 7.500/DV,
+ 7.875/DV, 8.250/DV, 8.625/DV, 9.000/DV,
+ /* OCT 4 */
+ 0.000/DV, 0.000/DV, 3.000/DV, 4.875/DV,
+ 6.000/DV, 7.125/DV, 7.875/DV, 8.625/DV,
+ 9.000/DV, 9.750/DV,10.125/DV,10.500/DV,
+ 10.875/DV,11.250/DV,11.625/DV,12.000/DV,
+ /* OCT 5 */
+ 0.000/DV, 3.000/DV, 6.000/DV, 7.875/DV,
+ 9.000/DV,10.125/DV,10.875/DV,11.625/DV,
+ 12.000/DV,12.750/DV,13.125/DV,13.500/DV,
+ 13.875/DV,14.250/DV,14.625/DV,15.000/DV,
+ /* OCT 6 */
+ 0.000/DV, 6.000/DV, 9.000/DV,10.875/DV,
+ 12.000/DV,13.125/DV,13.875/DV,14.625/DV,
+ 15.000/DV,15.750/DV,16.125/DV,16.500/DV,
+ 16.875/DV,17.250/DV,17.625/DV,18.000/DV,
+ /* OCT 7 */
+ 0.000/DV, 9.000/DV,12.000/DV,13.875/DV,
+ 15.000/DV,16.125/DV,16.875/DV,17.625/DV,
+ 18.000/DV,18.750/DV,19.125/DV,19.500/DV,
+ 19.875/DV,20.250/DV,20.625/DV,21.000/DV
+};
+#undef DV
+
+/* sustain lebel table (3db per step) */
+/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/
+#define SC(db) (db*((3/EG_STEP)*(1<<ENV_BITS)))+EG_DST
+static const INT32 SL_TABLE[16]={
+ SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7),
+ SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31)
+};
+#undef SC
+
+#define TL_MAX (EG_ENT*2) /* limit(tl + ksr + envelope) + sinwave */
+/* TotalLevel : 48 24 12 6 3 1.5 0.75 (dB) */
+/* TL_TABLE[ 0 to TL_MAX ] : plus section */
+/* TL_TABLE[ TL_MAX to TL_MAX+TL_MAX-1 ] : minus section */
+static INT32 *TL_TABLE;
+
+/* pointers to TL_TABLE with sinwave output offset */
+static INT32 **SIN_TABLE;
+
+/* LFO table */
+static INT32 *AMS_TABLE;
+static INT32 *VIB_TABLE;
+
+/* envelope output curve table */
+/* attack + decay + OFF */
+static INT32 ENV_CURVE[2*EG_ENT+1];
+
+/* multiple table */
+#define ML 2
+static const UINT32 MUL_TABLE[16]= {
+/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */
+ 0.50*ML, 1.00*ML, 2.00*ML, 3.00*ML, 4.00*ML, 5.00*ML, 6.00*ML, 7.00*ML,
+ 8.00*ML, 9.00*ML,10.00*ML,10.00*ML,12.00*ML,12.00*ML,15.00*ML,15.00*ML
+};
+#undef ML
+
+/* dummy attack / decay rate ( when rate == 0 ) */
+static INT32 RATE_0[16]=
+{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+
+/* -------------------- static state --------------------- */
+
+/* lock level of common table */
+static int num_lock = 0;
+
+/* work table */
+static void *cur_chip = NULL; /* current chip point */
+/* currenct chip state */
+/* static OPLSAMPLE *bufL,*bufR; */
+static OPL_CH *S_CH;
+static OPL_CH *E_CH;
+static OPL_SLOT *SLOT7_1, *SLOT7_2, *SLOT8_1, *SLOT8_2;
+
+static INT32 outd[1];
+static INT32 ams;
+static INT32 vib;
+static INT32 *ams_table;
+static INT32 *vib_table;
+static INT32 amsIncr;
+static INT32 vibIncr;
+static INT32 feedback2; /* connect for SLOT 2 */
+
+/* log output level */
+#define LOG_ERR 3 /* ERROR */
+#define LOG_WAR 2 /* WARNING */
+#define LOG_INF 1 /* INFORMATION */
+
+//#define LOG_LEVEL LOG_INF
+#define LOG_LEVEL LOG_ERR
+
+//#define LOG(n,x) if( (n)>=LOG_LEVEL ) logerror x
+#define LOG(n,x)
+
+/* --------------------- subroutines --------------------- */
+
+static inline int Limit( int val, int max, int min ) {
+ if ( val > max )
+ val = max;
+ else if ( val < min )
+ val = min;
+
+ return val;
+}
+
+/* status set and IRQ handling */
+static inline void OPL_STATUS_SET(FM_OPL *OPL,int flag)
+{
+ /* set status flag */
+ OPL->status |= flag;
+ if(!(OPL->status & 0x80))
+ {
+ if(OPL->status & OPL->statusmask)
+ { /* IRQ on */
+ OPL->status |= 0x80;
+ /* callback user interrupt handler (IRQ is OFF to ON) */
+ if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1);
+ }
+ }
+}
+
+/* status reset and IRQ handling */
+static inline void OPL_STATUS_RESET(FM_OPL *OPL,int flag)
+{
+ /* reset status flag */
+ OPL->status &=~flag;
+ if((OPL->status & 0x80))
+ {
+ if (!(OPL->status & OPL->statusmask) )
+ {
+ OPL->status &= 0x7f;
+ /* callback user interrupt handler (IRQ is ON to OFF) */
+ if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0);
+ }
+ }
+}
+
+/* IRQ mask set */
+static inline void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag)
+{
+ OPL->statusmask = flag;
+ /* IRQ handling check */
+ OPL_STATUS_SET(OPL,0);
+ OPL_STATUS_RESET(OPL,0);
+}
+
+/* ----- key on ----- */
+static inline void OPL_KEYON(OPL_SLOT *SLOT)
+{
+ /* sin wave restart */
+ SLOT->Cnt = 0;
+ /* set attack */
+ SLOT->evm = ENV_MOD_AR;
+ SLOT->evs = SLOT->evsa;
+ SLOT->evc = EG_AST;
+ SLOT->eve = EG_AED;
+}
+/* ----- key off ----- */
+static inline void OPL_KEYOFF(OPL_SLOT *SLOT)
+{
+ if( SLOT->evm > ENV_MOD_RR)
+ {
+ /* set envelope counter from envleope output */
+ SLOT->evm = ENV_MOD_RR;
+ if( !(SLOT->evc&EG_DST) )
+ //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<<ENV_BITS) + EG_DST;
+ SLOT->evc = EG_DST;
+ SLOT->eve = EG_DED;
+ SLOT->evs = SLOT->evsr;
+ }
+}
+
+/* ---------- calcrate Envelope Generator & Phase Generator ---------- */
+/* return : envelope output */
+static inline UINT32 OPL_CALC_SLOT( OPL_SLOT *SLOT )
+{
+ /* calcrate envelope generator */
+ if( (SLOT->evc+=SLOT->evs) >= SLOT->eve )
+ {
+ switch( SLOT->evm ){
+ case ENV_MOD_AR: /* ATTACK -> DECAY1 */
+ /* next DR */
+ SLOT->evm = ENV_MOD_DR;
+ SLOT->evc = EG_DST;
+ SLOT->eve = SLOT->SL;
+ SLOT->evs = SLOT->evsd;
+ break;
+ case ENV_MOD_DR: /* DECAY -> SL or RR */
+ SLOT->evc = SLOT->SL;
+ SLOT->eve = EG_DED;
+ if(SLOT->eg_typ)
+ {
+ SLOT->evs = 0;
+ }
+ else
+ {
+ SLOT->evm = ENV_MOD_RR;
+ SLOT->evs = SLOT->evsr;
+ }
+ break;
+ case ENV_MOD_RR: /* RR -> OFF */
+ SLOT->evc = EG_OFF;
+ SLOT->eve = EG_OFF+1;
+ SLOT->evs = 0;
+ break;
+ }
+ }
+ /* calcrate envelope */
+ return SLOT->TLL+ENV_CURVE[SLOT->evc>>ENV_BITS]+(SLOT->ams ? ams : 0);
+}
+
+/* set algorithm connection */
+static void set_algorithm( OPL_CH *CH)
+{
+ INT32 *carrier = &outd[0];
+ CH->connect1 = CH->CON ? carrier : &feedback2;
+ CH->connect2 = carrier;
+}
+
+/* ---------- frequency counter for operater update ---------- */
+static inline void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT)
+{
+ int ksr;
+
+ /* frequency step counter */
+ SLOT->Incr = CH->fc * SLOT->mul;
+ ksr = CH->kcode >> SLOT->KSR;
+
+ if( SLOT->ksr != ksr )
+ {
+ SLOT->ksr = ksr;
+ /* attack , decay rate recalcration */
+ SLOT->evsa = SLOT->AR[ksr];
+ SLOT->evsd = SLOT->DR[ksr];
+ SLOT->evsr = SLOT->RR[ksr];
+ }
+ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl);
+}
+
+/* set multi,am,vib,EG-TYP,KSR,mul */
+static inline void set_mul(FM_OPL *OPL,int slot,int v)
+{
+ OPL_CH *CH = &OPL->P_CH[slot/2];
+ OPL_SLOT *SLOT = &CH->SLOT[slot&1];
+
+ SLOT->mul = MUL_TABLE[v&0x0f];
+ SLOT->KSR = (v&0x10) ? 0 : 2;
+ SLOT->eg_typ = (v&0x20)>>5;
+ SLOT->vib = (v&0x40);
+ SLOT->ams = (v&0x80);
+ CALC_FCSLOT(CH,SLOT);
+}
+
+/* set ksl & tl */
+static inline void set_ksl_tl(FM_OPL *OPL,int slot,int v)
+{
+ OPL_CH *CH = &OPL->P_CH[slot/2];
+ OPL_SLOT *SLOT = &CH->SLOT[slot&1];
+ int ksl = v>>6; /* 0 / 1.5 / 3 / 6 db/OCT */
+
+ SLOT->ksl = ksl ? 3-ksl : 31;
+ SLOT->TL = (v&0x3f)*(0.75/EG_STEP); /* 0.75db step */
+
+ if( !(OPL->mode&0x80) )
+ { /* not CSM latch total level */
+ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl);
+ }
+}
+
+/* set attack rate & decay rate */
+static inline void set_ar_dr(FM_OPL *OPL,int slot,int v)
+{
+ OPL_CH *CH = &OPL->P_CH[slot/2];
+ OPL_SLOT *SLOT = &CH->SLOT[slot&1];
+ int ar = v>>4;
+ int dr = v&0x0f;
+
+ SLOT->AR = ar ? &OPL->AR_TABLE[ar<<2] : RATE_0;
+ SLOT->evsa = SLOT->AR[SLOT->ksr];
+ if( SLOT->evm == ENV_MOD_AR ) SLOT->evs = SLOT->evsa;
+
+ SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0;
+ SLOT->evsd = SLOT->DR[SLOT->ksr];
+ if( SLOT->evm == ENV_MOD_DR ) SLOT->evs = SLOT->evsd;
+}
+
+/* set sustain level & release rate */
+static inline void set_sl_rr(FM_OPL *OPL,int slot,int v)
+{
+ OPL_CH *CH = &OPL->P_CH[slot/2];
+ OPL_SLOT *SLOT = &CH->SLOT[slot&1];
+ int sl = v>>4;
+ int rr = v & 0x0f;
+
+ SLOT->SL = SL_TABLE[sl];
+ if( SLOT->evm == ENV_MOD_DR ) SLOT->eve = SLOT->SL;
+ SLOT->RR = &OPL->DR_TABLE[rr<<2];
+ SLOT->evsr = SLOT->RR[SLOT->ksr];
+ if( SLOT->evm == ENV_MOD_RR ) SLOT->evs = SLOT->evsr;
+}
+
+/* operator output calcrator */
+#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt+con)/(0x1000000/SIN_ENT))&(SIN_ENT-1)][env]
+/* ---------- calcrate one of channel ---------- */
+static inline void OPL_CALC_CH( OPL_CH *CH )
+{
+ UINT32 env_out;
+ OPL_SLOT *SLOT;
+
+ feedback2 = 0;
+ /* SLOT 1 */
+ SLOT = &CH->SLOT[SLOT1];
+ env_out=OPL_CALC_SLOT(SLOT);
+ if( env_out < EG_ENT-1 )
+ {
+ /* PG */
+ if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE);
+ else SLOT->Cnt += SLOT->Incr;
+ /* connectoion */
+ if(CH->FB)
+ {
+ int feedback1 = (CH->op1_out[0]+CH->op1_out[1])>>CH->FB;
+ CH->op1_out[1] = CH->op1_out[0];
+ *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT,env_out,feedback1);
+ }
+ else
+ {
+ *CH->connect1 += OP_OUT(SLOT,env_out,0);
+ }
+ }else
+ {
+ CH->op1_out[1] = CH->op1_out[0];
+ CH->op1_out[0] = 0;
+ }
+ /* SLOT 2 */
+ SLOT = &CH->SLOT[SLOT2];
+ env_out=OPL_CALC_SLOT(SLOT);
+ if( env_out < EG_ENT-1 )
+ {
+ /* PG */
+ if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE);
+ else SLOT->Cnt += SLOT->Incr;
+ /* connectoion */
+ outd[0] += OP_OUT(SLOT,env_out, feedback2);
+ }
+}
+
+/* ---------- calcrate rhythm block ---------- */
+#define WHITE_NOISE_db 6.0
+static inline void OPL_CALC_RH( OPL_CH *CH )
+{
+ UINT32 env_tam,env_sd,env_top,env_hh;
+ int whitenoise = (rand()&1)*(WHITE_NOISE_db/EG_STEP);
+ INT32 tone8;
+
+ OPL_SLOT *SLOT;
+ int env_out;
+
+ /* BD : same as FM serial mode and output level is large */
+ feedback2 = 0;
+ /* SLOT 1 */
+ SLOT = &CH[6].SLOT[SLOT1];
+ env_out=OPL_CALC_SLOT(SLOT);
+ if( env_out < EG_ENT-1 )
+ {
+ /* PG */
+ if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE);
+ else SLOT->Cnt += SLOT->Incr;
+ /* connectoion */
+ if(CH[6].FB)
+ {
+ int feedback1 = (CH[6].op1_out[0]+CH[6].op1_out[1])>>CH[6].FB;
+ CH[6].op1_out[1] = CH[6].op1_out[0];
+ feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT,env_out,feedback1);
+ }
+ else
+ {
+ feedback2 = OP_OUT(SLOT,env_out,0);
+ }
+ }else
+ {
+ feedback2 = 0;
+ CH[6].op1_out[1] = CH[6].op1_out[0];
+ CH[6].op1_out[0] = 0;
+ }
+ /* SLOT 2 */
+ SLOT = &CH[6].SLOT[SLOT2];
+ env_out=OPL_CALC_SLOT(SLOT);
+ if( env_out < EG_ENT-1 )
+ {
+ /* PG */
+ if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE);
+ else SLOT->Cnt += SLOT->Incr;
+ /* connectoion */
+ outd[0] += OP_OUT(SLOT,env_out, feedback2)*2;
+ }
+
+ // SD (17) = mul14[fnum7] + white noise
+ // TAM (15) = mul15[fnum8]
+ // TOP (18) = fnum6(mul18[fnum8]+whitenoise)
+ // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise
+ env_sd =OPL_CALC_SLOT(SLOT7_2) + whitenoise;
+ env_tam=OPL_CALC_SLOT(SLOT8_1);
+ env_top=OPL_CALC_SLOT(SLOT8_2);
+ env_hh =OPL_CALC_SLOT(SLOT7_1) + whitenoise;
+
+ /* PG */
+ if(SLOT7_1->vib) SLOT7_1->Cnt += (2*SLOT7_1->Incr*vib/VIB_RATE);
+ else SLOT7_1->Cnt += 2*SLOT7_1->Incr;
+ if(SLOT7_2->vib) SLOT7_2->Cnt += ((CH[7].fc*8)*vib/VIB_RATE);
+ else SLOT7_2->Cnt += (CH[7].fc*8);
+ if(SLOT8_1->vib) SLOT8_1->Cnt += (SLOT8_1->Incr*vib/VIB_RATE);
+ else SLOT8_1->Cnt += SLOT8_1->Incr;
+ if(SLOT8_2->vib) SLOT8_2->Cnt += ((CH[8].fc*48)*vib/VIB_RATE);
+ else SLOT8_2->Cnt += (CH[8].fc*48);
+
+ tone8 = OP_OUT(SLOT8_2,whitenoise,0 );
+
+ /* SD */
+ if( env_sd < EG_ENT-1 )
+ outd[0] += OP_OUT(SLOT7_1,env_sd, 0)*8;
+ /* TAM */
+ if( env_tam < EG_ENT-1 )
+ outd[0] += OP_OUT(SLOT8_1,env_tam, 0)*2;
+ /* TOP-CY */
+ if( env_top < EG_ENT-1 )
+ outd[0] += OP_OUT(SLOT7_2,env_top,tone8)*2;
+ /* HH */
+ if( env_hh < EG_ENT-1 )
+ outd[0] += OP_OUT(SLOT7_2,env_hh,tone8)*2;
+}
+
+/* ----------- initialize time tabls ----------- */
+static void init_timetables( FM_OPL *OPL , int ARRATE , int DRRATE )
+{
+ int i;
+ double rate;
+
+ /* make attack rate & decay rate tables */
+ for (i = 0;i < 4;i++) OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0;
+ for (i = 4;i <= 60;i++){
+ rate = OPL->freqbase; /* frequency rate */
+ if( i < 60 ) rate *= 1.0+(i&3)*0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */
+ rate *= 1<<((i>>2)-1); /* b2-5 : shift bit */
+ rate *= (double)(EG_ENT<<ENV_BITS);
+ OPL->AR_TABLE[i] = rate / ARRATE;
+ OPL->DR_TABLE[i] = rate / DRRATE;
+ }
+ for (i = 60; i < ARRAY_SIZE(OPL->AR_TABLE); i++)
+ {
+ OPL->AR_TABLE[i] = EG_AED-1;
+ OPL->DR_TABLE[i] = OPL->DR_TABLE[60];
+ }
+#if 0
+ for (i = 0;i < 64 ;i++){ /* make for overflow area */
+ LOG(LOG_WAR, ("rate %2d , ar %f ms , dr %f ms\n", i,
+ ((double)(EG_ENT<<ENV_BITS) / OPL->AR_TABLE[i]) * (1000.0 / OPL->rate),
+ ((double)(EG_ENT<<ENV_BITS) / OPL->DR_TABLE[i]) * (1000.0 / OPL->rate) ));
+ }
+#endif
+}
+
+/* ---------- generic table initialize ---------- */
+static int OPLOpenTable( void )
+{
+ int s,t;
+ double rate;
+ int i,j;
+ double pom;
+
+ /* allocate dynamic tables */
+ if( (TL_TABLE = malloc(TL_MAX*2*sizeof(INT32))) == NULL)
+ return 0;
+ if( (SIN_TABLE = malloc(SIN_ENT*4 *sizeof(INT32 *))) == NULL)
+ {
+ free(TL_TABLE);
+ return 0;
+ }
+ if( (AMS_TABLE = malloc(AMS_ENT*2 *sizeof(INT32))) == NULL)
+ {
+ free(TL_TABLE);
+ free(SIN_TABLE);
+ return 0;
+ }
+ if( (VIB_TABLE = malloc(VIB_ENT*2 *sizeof(INT32))) == NULL)
+ {
+ free(TL_TABLE);
+ free(SIN_TABLE);
+ free(AMS_TABLE);
+ return 0;
+ }
+ /* make total level table */
+ for (t = 0;t < EG_ENT-1 ;t++){
+ rate = ((1<<TL_BITS)-1)/pow(10,EG_STEP*t/20); /* dB -> voltage */
+ TL_TABLE[ t] = (int)rate;
+ TL_TABLE[TL_MAX+t] = -TL_TABLE[t];
+/* LOG(LOG_INF,("TotalLevel(%3d) = %x\n",t,TL_TABLE[t]));*/
+ }
+ /* fill volume off area */
+ for ( t = EG_ENT-1; t < TL_MAX ;t++){
+ TL_TABLE[t] = TL_TABLE[TL_MAX+t] = 0;
+ }
+
+ /* make sinwave table (total level offet) */
+ /* degree 0 = degree 180 = off */
+ SIN_TABLE[0] = SIN_TABLE[SIN_ENT/2] = &TL_TABLE[EG_ENT-1];
+ for (s = 1;s <= SIN_ENT/4;s++){
+ pom = sin(2*PI*s/SIN_ENT); /* sin */
+ pom = 20*log10(1/pom); /* decibel */
+ j = pom / EG_STEP; /* TL_TABLE steps */
+
+ /* degree 0 - 90 , degree 180 - 90 : plus section */
+ SIN_TABLE[ s] = SIN_TABLE[SIN_ENT/2-s] = &TL_TABLE[j];
+ /* degree 180 - 270 , degree 360 - 270 : minus section */
+ SIN_TABLE[SIN_ENT/2+s] = SIN_TABLE[SIN_ENT -s] = &TL_TABLE[TL_MAX+j];
+/* LOG(LOG_INF,("sin(%3d) = %f:%f db\n",s,pom,(double)j * EG_STEP));*/
+ }
+ for (s = 0;s < SIN_ENT;s++)
+ {
+ SIN_TABLE[SIN_ENT*1+s] = s<(SIN_ENT/2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT];
+ SIN_TABLE[SIN_ENT*2+s] = SIN_TABLE[s % (SIN_ENT/2)];
+ SIN_TABLE[SIN_ENT*3+s] = (s/(SIN_ENT/4))&1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT*2+s];
+ }
+
+ /* envelope counter -> envelope output table */
+ for (i=0; i<EG_ENT; i++)
+ {
+ /* ATTACK curve */
+ pom = pow( ((double)(EG_ENT-1-i)/EG_ENT) , 8 ) * EG_ENT;
+ /* if( pom >= EG_ENT ) pom = EG_ENT-1; */
+ ENV_CURVE[i] = (int)pom;
+ /* DECAY ,RELEASE curve */
+ ENV_CURVE[(EG_DST>>ENV_BITS)+i]= i;
+ }
+ /* off */
+ ENV_CURVE[EG_OFF>>ENV_BITS]= EG_ENT-1;
+ /* make LFO ams table */
+ for (i=0; i<AMS_ENT; i++)
+ {
+ pom = (1.0+sin(2*PI*i/AMS_ENT))/2; /* sin */
+ AMS_TABLE[i] = (1.0/EG_STEP)*pom; /* 1dB */
+ AMS_TABLE[AMS_ENT+i] = (4.8/EG_STEP)*pom; /* 4.8dB */
+ }
+ /* make LFO vibrate table */
+ for (i=0; i<VIB_ENT; i++)
+ {
+ /* 100cent = 1seminote = 6% ?? */
+ pom = (double)VIB_RATE*0.06*sin(2*PI*i/VIB_ENT); /* +-100sect step */
+ VIB_TABLE[i] = VIB_RATE + (pom*0.07); /* +- 7cent */
+ VIB_TABLE[VIB_ENT+i] = VIB_RATE + (pom*0.14); /* +-14cent */
+ /* LOG(LOG_INF,("vib %d=%d\n",i,VIB_TABLE[VIB_ENT+i])); */
+ }
+ return 1;
+}
+
+
+static void OPLCloseTable( void )
+{
+ free(TL_TABLE);
+ free(SIN_TABLE);
+ free(AMS_TABLE);
+ free(VIB_TABLE);
+}
+
+/* CSM Key Control */
+static inline void CSMKeyControll(OPL_CH *CH)
+{
+ OPL_SLOT *slot1 = &CH->SLOT[SLOT1];
+ OPL_SLOT *slot2 = &CH->SLOT[SLOT2];
+ /* all key off */
+ OPL_KEYOFF(slot1);
+ OPL_KEYOFF(slot2);
+ /* total level latch */
+ slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl);
+ slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl);
+ /* key on */
+ CH->op1_out[0] = CH->op1_out[1] = 0;
+ OPL_KEYON(slot1);
+ OPL_KEYON(slot2);
+}
+
+/* ---------- opl initialize ---------- */
+static void OPL_initialize(FM_OPL *OPL)
+{
+ int fn;
+
+ /* frequency base */
+ OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0;
+ /* Timer base time */
+ OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 );
+ /* make time tables */
+ init_timetables( OPL , OPL_ARRATE , OPL_DRRATE );
+ /* make fnumber -> increment counter table */
+ for( fn=0 ; fn < 1024 ; fn++ )
+ {
+ OPL->FN_TABLE[fn] = OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2;
+ }
+ /* LFO freq.table */
+ OPL->amsIncr = OPL->rate ? (double)AMS_ENT*(1<<AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0;
+ OPL->vibIncr = OPL->rate ? (double)VIB_ENT*(1<<VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0;
+}
+
+/* ---------- write a OPL registers ---------- */
+static void OPLWriteReg(FM_OPL *OPL, int r, int v)
+{
+ OPL_CH *CH;
+ int slot;
+ int block_fnum;
+
+ switch(r&0xe0)
+ {
+ case 0x00: /* 00-1f:control */
+ switch(r&0x1f)
+ {
+ case 0x01:
+ /* wave selector enable */
+ if(OPL->type&OPL_TYPE_WAVESEL)
+ {
+ OPL->wavesel = v&0x20;
+ if(!OPL->wavesel)
+ {
+ /* preset compatible mode */
+ int c;
+ for(c=0;c<OPL->max_ch;c++)
+ {
+ OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0];
+ OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0];
+ }
+ }
+ }
+ return;
+ case 0x02: /* Timer 1 */
+ OPL->T[0] = (256-v)*4;
+ break;
+ case 0x03: /* Timer 2 */
+ OPL->T[1] = (256-v)*16;
+ return;
+ case 0x04: /* IRQ clear / mask and Timer enable */
+ if(v&0x80)
+ { /* IRQ flag clear */
+ OPL_STATUS_RESET(OPL,0x7f);
+ }
+ else
+ { /* set IRQ mask ,timer enable*/
+ UINT8 st1 = v&1;
+ UINT8 st2 = (v>>1)&1;
+ /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */
+ OPL_STATUS_RESET(OPL,v&0x78);
+ OPL_STATUSMASK_SET(OPL,((~v)&0x78)|0x01);
+ /* timer 2 */
+ if(OPL->st[1] != st2)
+ {
+ double interval = st2 ? (double)OPL->T[1]*OPL->TimerBase : 0.0;
+ OPL->st[1] = st2;
+ if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+1,interval);
+ }
+ /* timer 1 */
+ if(OPL->st[0] != st1)
+ {
+ double interval = st1 ? (double)OPL->T[0]*OPL->TimerBase : 0.0;
+ OPL->st[0] = st1;
+ if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+0,interval);
+ }
+ }
+ return;
+#if BUILD_Y8950
+ case 0x06: /* Key Board OUT */
+ if(OPL->type&OPL_TYPE_KEYBOARD)
+ {
+ if(OPL->keyboardhandler_w)
+ OPL->keyboardhandler_w(OPL->keyboard_param,v);
+ else
+ LOG(LOG_WAR,("OPL:write unmapped KEYBOARD port\n"));
+ }
+ return;
+ case 0x07: /* DELTA-T control : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */
+ if(OPL->type&OPL_TYPE_ADPCM)
+ YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v);
+ return;
+ case 0x08: /* MODE,DELTA-T : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */
+ OPL->mode = v;
+ v&=0x1f; /* for DELTA-T unit */
+ case 0x09: /* START ADD */
+ case 0x0a:
+ case 0x0b: /* STOP ADD */
+ case 0x0c:
+ case 0x0d: /* PRESCALE */
+ case 0x0e:
+ case 0x0f: /* ADPCM data */
+ case 0x10: /* DELTA-N */
+ case 0x11: /* DELTA-N */
+ case 0x12: /* EG-CTRL */
+ if(OPL->type&OPL_TYPE_ADPCM)
+ YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v);
+ return;
+#if 0
+ case 0x15: /* DAC data */
+ case 0x16:
+ case 0x17: /* SHIFT */
+ return;
+ case 0x18: /* I/O CTRL (Direction) */
+ if(OPL->type&OPL_TYPE_IO)
+ OPL->portDirection = v&0x0f;
+ return;
+ case 0x19: /* I/O DATA */
+ if(OPL->type&OPL_TYPE_IO)
+ {
+ OPL->portLatch = v;
+ if(OPL->porthandler_w)
+ OPL->porthandler_w(OPL->port_param,v&OPL->portDirection);
+ }
+ return;
+ case 0x1a: /* PCM data */
+ return;
+#endif
+#endif
+ }
+ break;
+ case 0x20: /* am,vib,ksr,eg type,mul */
+ slot = slot_array[r&0x1f];
+ if(slot == -1) return;
+ set_mul(OPL,slot,v);
+ return;
+ case 0x40:
+ slot = slot_array[r&0x1f];
+ if(slot == -1) return;
+ set_ksl_tl(OPL,slot,v);
+ return;
+ case 0x60:
+ slot = slot_array[r&0x1f];
+ if(slot == -1) return;
+ set_ar_dr(OPL,slot,v);
+ return;
+ case 0x80:
+ slot = slot_array[r&0x1f];
+ if(slot == -1) return;
+ set_sl_rr(OPL,slot,v);
+ return;
+ case 0xa0:
+ switch(r)
+ {
+ case 0xbd:
+ /* amsep,vibdep,r,bd,sd,tom,tc,hh */
+ {
+ UINT8 rkey = OPL->rhythm^v;
+ OPL->ams_table = &AMS_TABLE[v&0x80 ? AMS_ENT : 0];
+ OPL->vib_table = &VIB_TABLE[v&0x40 ? VIB_ENT : 0];
+ OPL->rhythm = v&0x3f;
+ if(OPL->rhythm&0x20)
+ {
+#if 0
+ usrintf_showmessage("OPL Rhythm mode select");
+#endif
+ /* BD key on/off */
+ if(rkey&0x10)
+ {
+ if(v&0x10)
+ {
+ OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0;
+ OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]);
+ OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]);
+ }
+ else
+ {
+ OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]);
+ OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]);
+ }
+ }
+ /* SD key on/off */
+ if(rkey&0x08)
+ {
+ if(v&0x08) OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]);
+ else OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]);
+ }/* TAM key on/off */
+ if(rkey&0x04)
+ {
+ if(v&0x04) OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]);
+ else OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]);
+ }
+ /* TOP-CY key on/off */
+ if(rkey&0x02)
+ {
+ if(v&0x02) OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]);
+ else OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]);
+ }
+ /* HH key on/off */
+ if(rkey&0x01)
+ {
+ if(v&0x01) OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]);
+ else OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]);
+ }
+ }
+ }
+ return;
+ }
+ /* keyon,block,fnum */
+ if( (r&0x0f) > 8) return;
+ CH = &OPL->P_CH[r&0x0f];
+ if(!(r&0x10))
+ { /* a0-a8 */
+ block_fnum = (CH->block_fnum&0x1f00) | v;
+ }
+ else
+ { /* b0-b8 */
+ int keyon = (v>>5)&1;
+ block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff);
+ if(CH->keyon != keyon)
+ {
+ if( (CH->keyon=keyon) )
+ {
+ CH->op1_out[0] = CH->op1_out[1] = 0;
+ OPL_KEYON(&CH->SLOT[SLOT1]);
+ OPL_KEYON(&CH->SLOT[SLOT2]);
+ }
+ else
+ {
+ OPL_KEYOFF(&CH->SLOT[SLOT1]);
+ OPL_KEYOFF(&CH->SLOT[SLOT2]);
+ }
+ }
+ }
+ /* update */
+ if(CH->block_fnum != block_fnum)
+ {
+ int blockRv = 7-(block_fnum>>10);
+ int fnum = block_fnum&0x3ff;
+ CH->block_fnum = block_fnum;
+
+ CH->ksl_base = KSL_TABLE[block_fnum>>6];
+ CH->fc = OPL->FN_TABLE[fnum]>>blockRv;
+ CH->kcode = CH->block_fnum>>9;
+ if( (OPL->mode&0x40) && CH->block_fnum&0x100) CH->kcode |=1;
+ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]);
+ CALC_FCSLOT(CH,&CH->SLOT[SLOT2]);
+ }
+ return;
+ case 0xc0:
+ /* FB,C */
+ if( (r&0x0f) > 8) return;
+ CH = &OPL->P_CH[r&0x0f];
+ {
+ int feedback = (v>>1)&7;
+ CH->FB = feedback ? (8+1) - feedback : 0;
+ CH->CON = v&1;
+ set_algorithm(CH);
+ }
+ return;
+ case 0xe0: /* wave type */
+ slot = slot_array[r&0x1f];
+ if(slot == -1) return;
+ CH = &OPL->P_CH[slot/2];
+ if(OPL->wavesel)
+ {
+ /* LOG(LOG_INF,("OPL SLOT %d wave select %d\n",slot,v&3)); */
+ CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v&0x03)*SIN_ENT];
+ }
+ return;
+ }
+}
+
+/* lock/unlock for common table */
+static int OPL_LockTable(void)
+{
+ num_lock++;
+ if(num_lock>1) return 0;
+ /* first time */
+ cur_chip = NULL;
+ /* allocate total level table (128kb space) */
+ if( !OPLOpenTable() )
+ {
+ num_lock--;
+ return -1;
+ }
+ return 0;
+}
+
+static void OPL_UnLockTable(void)
+{
+ if(num_lock) num_lock--;
+ if(num_lock) return;
+ /* last time */
+ cur_chip = NULL;
+ OPLCloseTable();
+}
+
+#if (BUILD_YM3812 || BUILD_YM3526)
+/*******************************************************************************/
+/* YM3812 local section */
+/*******************************************************************************/
+
+/* ---------- update one of chip ----------- */
+void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length)
+{
+ int i;
+ int data;
+ OPLSAMPLE *buf = buffer;
+ UINT32 amsCnt = OPL->amsCnt;
+ UINT32 vibCnt = OPL->vibCnt;
+ UINT8 rhythm = OPL->rhythm&0x20;
+ OPL_CH *CH,*R_CH;
+
+ if( (void *)OPL != cur_chip ){
+ cur_chip = (void *)OPL;
+ /* channel pointers */
+ S_CH = OPL->P_CH;
+ E_CH = &S_CH[9];
+ /* rhythm slot */
+ SLOT7_1 = &S_CH[7].SLOT[SLOT1];
+ SLOT7_2 = &S_CH[7].SLOT[SLOT2];
+ SLOT8_1 = &S_CH[8].SLOT[SLOT1];
+ SLOT8_2 = &S_CH[8].SLOT[SLOT2];
+ /* LFO state */
+ amsIncr = OPL->amsIncr;
+ vibIncr = OPL->vibIncr;
+ ams_table = OPL->ams_table;
+ vib_table = OPL->vib_table;
+ }
+ R_CH = rhythm ? &S_CH[6] : E_CH;
+ for( i=0; i < length ; i++ )
+ {
+ /* channel A channel B channel C */
+ /* LFO */
+ ams = ams_table[(amsCnt+=amsIncr)>>AMS_SHIFT];
+ vib = vib_table[(vibCnt+=vibIncr)>>VIB_SHIFT];
+ outd[0] = 0;
+ /* FM part */
+ for(CH=S_CH ; CH < R_CH ; CH++)
+ OPL_CALC_CH(CH);
+ /* Rythn part */
+ if(rhythm)
+ OPL_CALC_RH(S_CH);
+ /* limit check */
+ data = Limit( outd[0] , OPL_MAXOUT, OPL_MINOUT );
+ /* store to sound buffer */
+ buf[i] = data >> OPL_OUTSB;
+ }
+
+ OPL->amsCnt = amsCnt;
+ OPL->vibCnt = vibCnt;
+#ifdef OPL_OUTPUT_LOG
+ if(opl_dbg_fp)
+ {
+ for(opl_dbg_chip=0;opl_dbg_chip<opl_dbg_maxchip;opl_dbg_chip++)
+ if( opl_dbg_opl[opl_dbg_chip] == OPL) break;
+ fprintf(opl_dbg_fp,"%c%c%c",0x20+opl_dbg_chip,length&0xff,length/256);
+ }
+#endif
+}
+#endif /* (BUILD_YM3812 || BUILD_YM3526) */
+
+#if BUILD_Y8950
+
+void Y8950UpdateOne(FM_OPL *OPL, INT16 *buffer, int length)
+{
+ int i;
+ int data;
+ OPLSAMPLE *buf = buffer;
+ UINT32 amsCnt = OPL->amsCnt;
+ UINT32 vibCnt = OPL->vibCnt;
+ UINT8 rhythm = OPL->rhythm&0x20;
+ OPL_CH *CH,*R_CH;
+ YM_DELTAT *DELTAT = OPL->deltat;
+
+ /* setup DELTA-T unit */
+ YM_DELTAT_DECODE_PRESET(DELTAT);
+
+ if( (void *)OPL != cur_chip ){
+ cur_chip = (void *)OPL;
+ /* channel pointers */
+ S_CH = OPL->P_CH;
+ E_CH = &S_CH[9];
+ /* rhythm slot */
+ SLOT7_1 = &S_CH[7].SLOT[SLOT1];
+ SLOT7_2 = &S_CH[7].SLOT[SLOT2];
+ SLOT8_1 = &S_CH[8].SLOT[SLOT1];
+ SLOT8_2 = &S_CH[8].SLOT[SLOT2];
+ /* LFO state */
+ amsIncr = OPL->amsIncr;
+ vibIncr = OPL->vibIncr;
+ ams_table = OPL->ams_table;
+ vib_table = OPL->vib_table;
+ }
+ R_CH = rhythm ? &S_CH[6] : E_CH;
+ for( i=0; i < length ; i++ )
+ {
+ /* channel A channel B channel C */
+ /* LFO */
+ ams = ams_table[(amsCnt+=amsIncr)>>AMS_SHIFT];
+ vib = vib_table[(vibCnt+=vibIncr)>>VIB_SHIFT];
+ outd[0] = 0;
+ /* deltaT ADPCM */
+ if( DELTAT->portstate )
+ YM_DELTAT_ADPCM_CALC(DELTAT);
+ /* FM part */
+ for(CH=S_CH ; CH < R_CH ; CH++)
+ OPL_CALC_CH(CH);
+ /* Rythn part */
+ if(rhythm)
+ OPL_CALC_RH(S_CH);
+ /* limit check */
+ data = Limit( outd[0] , OPL_MAXOUT, OPL_MINOUT );
+ /* store to sound buffer */
+ buf[i] = data >> OPL_OUTSB;
+ }
+ OPL->amsCnt = amsCnt;
+ OPL->vibCnt = vibCnt;
+ /* deltaT START flag */
+ if( !DELTAT->portstate )
+ OPL->status &= 0xfe;
+}
+#endif
+
+/* ---------- reset one of chip ---------- */
+void OPLResetChip(FM_OPL *OPL)
+{
+ int c,s;
+ int i;
+
+ /* reset chip */
+ OPL->mode = 0; /* normal mode */
+ OPL_STATUS_RESET(OPL,0x7f);
+ /* reset with register write */
+ OPLWriteReg(OPL,0x01,0); /* wabesel disable */
+ OPLWriteReg(OPL,0x02,0); /* Timer1 */
+ OPLWriteReg(OPL,0x03,0); /* Timer2 */
+ OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */
+ for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0);
+ /* reset OPerator paramater */
+ for( c = 0 ; c < OPL->max_ch ; c++ )
+ {
+ OPL_CH *CH = &OPL->P_CH[c];
+ /* OPL->P_CH[c].PAN = OPN_CENTER; */
+ for(s = 0 ; s < 2 ; s++ )
+ {
+ /* wave table */
+ CH->SLOT[s].wavetable = &SIN_TABLE[0];
+ /* CH->SLOT[s].evm = ENV_MOD_RR; */
+ CH->SLOT[s].evc = EG_OFF;
+ CH->SLOT[s].eve = EG_OFF+1;
+ CH->SLOT[s].evs = 0;
+ }
+ }
+#if BUILD_Y8950
+ if(OPL->type&OPL_TYPE_ADPCM)
+ {
+ YM_DELTAT *DELTAT = OPL->deltat;
+
+ DELTAT->freqbase = OPL->freqbase;
+ DELTAT->output_pointer = outd;
+ DELTAT->portshift = 5;
+ DELTAT->output_range = DELTAT_MIXING_LEVEL<<TL_BITS;
+ YM_DELTAT_ADPCM_Reset(DELTAT,0);
+ }
+#endif
+}
+
+/* ---------- Create one of vietual YM3812 ---------- */
+/* 'rate' is sampling rate and 'bufsiz' is the size of the */
+FM_OPL *OPLCreate(int type, int clock, int rate)
+{
+ char *ptr;
+ FM_OPL *OPL;
+ int state_size;
+ int max_ch = 9; /* normaly 9 channels */
+
+ if( OPL_LockTable() ==-1) return NULL;
+ /* allocate OPL state space */
+ state_size = sizeof(FM_OPL);
+ state_size += sizeof(OPL_CH)*max_ch;
+#if BUILD_Y8950
+ if(type&OPL_TYPE_ADPCM) state_size+= sizeof(YM_DELTAT);
+#endif
+ /* allocate memory block */
+ ptr = malloc(state_size);
+ if(ptr==NULL) return NULL;
+ /* clear */
+ memset(ptr,0,state_size);
+ OPL = (FM_OPL *)ptr; ptr+=sizeof(FM_OPL);
+ OPL->P_CH = (OPL_CH *)ptr; ptr+=sizeof(OPL_CH)*max_ch;
+#if BUILD_Y8950
+ if(type&OPL_TYPE_ADPCM) OPL->deltat = (YM_DELTAT *)ptr; ptr+=sizeof(YM_DELTAT);
+#endif
+ /* set channel state pointer */
+ OPL->type = type;
+ OPL->clock = clock;
+ OPL->rate = rate;
+ OPL->max_ch = max_ch;
+ /* init grobal tables */
+ OPL_initialize(OPL);
+ /* reset chip */
+ OPLResetChip(OPL);
+#ifdef OPL_OUTPUT_LOG
+ if(!opl_dbg_fp)
+ {
+ opl_dbg_fp = fopen("opllog.opl","wb");
+ opl_dbg_maxchip = 0;
+ }
+ if(opl_dbg_fp)
+ {
+ opl_dbg_opl[opl_dbg_maxchip] = OPL;
+ fprintf(opl_dbg_fp,"%c%c%c%c%c%c",0x00+opl_dbg_maxchip,
+ type,
+ clock&0xff,
+ (clock/0x100)&0xff,
+ (clock/0x10000)&0xff,
+ (clock/0x1000000)&0xff);
+ opl_dbg_maxchip++;
+ }
+#endif
+ return OPL;
+}
+
+/* ---------- Destroy one of vietual YM3812 ---------- */
+void OPLDestroy(FM_OPL *OPL)
+{
+#ifdef OPL_OUTPUT_LOG
+ if(opl_dbg_fp)
+ {
+ fclose(opl_dbg_fp);
+ opl_dbg_fp = NULL;
+ }
+#endif
+ OPL_UnLockTable();
+ free(OPL);
+}
+
+/* ---------- Option handlers ---------- */
+
+void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset)
+{
+ OPL->TimerHandler = TimerHandler;
+ OPL->TimerParam = channelOffset;
+}
+void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param)
+{
+ OPL->IRQHandler = IRQHandler;
+ OPL->IRQParam = param;
+}
+void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param)
+{
+ OPL->UpdateHandler = UpdateHandler;
+ OPL->UpdateParam = param;
+}
+#if BUILD_Y8950
+void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param)
+{
+ OPL->porthandler_w = PortHandler_w;
+ OPL->porthandler_r = PortHandler_r;
+ OPL->port_param = param;
+}
+
+void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param)
+{
+ OPL->keyboardhandler_w = KeyboardHandler_w;
+ OPL->keyboardhandler_r = KeyboardHandler_r;
+ OPL->keyboard_param = param;
+}
+#endif
+/* ---------- YM3812 I/O interface ---------- */
+int OPLWrite(FM_OPL *OPL,int a,int v)
+{
+ if( !(a&1) )
+ { /* address port */
+ OPL->address = v & 0xff;
+ }
+ else
+ { /* data port */
+ if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0);
+#ifdef OPL_OUTPUT_LOG
+ if(opl_dbg_fp)
+ {
+ for(opl_dbg_chip=0;opl_dbg_chip<opl_dbg_maxchip;opl_dbg_chip++)
+ if( opl_dbg_opl[opl_dbg_chip] == OPL) break;
+ fprintf(opl_dbg_fp,"%c%c%c",0x10+opl_dbg_chip,OPL->address,v);
+ }
+#endif
+ OPLWriteReg(OPL,OPL->address,v);
+ }
+ return OPL->status>>7;
+}
+
+unsigned char OPLRead(FM_OPL *OPL,int a)
+{
+ if( !(a&1) )
+ { /* status port */
+ return OPL->status & (OPL->statusmask|0x80);
+ }
+ /* data port */
+ switch(OPL->address)
+ {
+ case 0x05: /* KeyBoard IN */
+ if(OPL->type&OPL_TYPE_KEYBOARD)
+ {
+ if(OPL->keyboardhandler_r)
+ return OPL->keyboardhandler_r(OPL->keyboard_param);
+ else {
+ LOG(LOG_WAR,("OPL:read unmapped KEYBOARD port\n"));
+ }
+ }
+ return 0;
+#if 0
+ case 0x0f: /* ADPCM-DATA */
+ return 0;
+#endif
+ case 0x19: /* I/O DATA */
+ if(OPL->type&OPL_TYPE_IO)
+ {
+ if(OPL->porthandler_r)
+ return OPL->porthandler_r(OPL->port_param);
+ else {
+ LOG(LOG_WAR,("OPL:read unmapped I/O port\n"));
+ }
+ }
+ return 0;
+ case 0x1a: /* PCM-DATA */
+ return 0;
+ }
+ return 0;
+}
+
+int OPLTimerOver(FM_OPL *OPL,int c)
+{
+ if( c )
+ { /* Timer B */
+ OPL_STATUS_SET(OPL,0x20);
+ }
+ else
+ { /* Timer A */
+ OPL_STATUS_SET(OPL,0x40);
+ /* CSM mode key,TL control */
+ if( OPL->mode & 0x80 )
+ { /* CSM mode total level latch and auto key on */
+ int ch;
+ if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0);
+ for(ch=0;ch<9;ch++)
+ CSMKeyControll( &OPL->P_CH[ch] );
+ }
+ }
+ /* reload timer */
+ if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+c,(double)OPL->T[c]*OPL->TimerBase);
+ return OPL->status>>7;
+}
diff --git a/hw/audio/fmopl.h b/hw/audio/fmopl.h
new file mode 100644
index 00000000..24ba5f48
--- /dev/null
+++ b/hw/audio/fmopl.h
@@ -0,0 +1,174 @@
+#ifndef __FMOPL_H_
+#define __FMOPL_H_
+
+/* --- select emulation chips --- */
+#define BUILD_YM3812 (HAS_YM3812)
+//#define BUILD_YM3526 (HAS_YM3526)
+//#define BUILD_Y8950 (HAS_Y8950)
+
+/* --- system optimize --- */
+/* select bit size of output : 8 or 16 */
+#define OPL_OUTPUT_BIT 16
+
+/* compiler dependence */
+#ifndef OSD_CPU_H
+#define OSD_CPU_H
+typedef unsigned char UINT8; /* unsigned 8bit */
+typedef unsigned short UINT16; /* unsigned 16bit */
+typedef unsigned int UINT32; /* unsigned 32bit */
+typedef signed char INT8; /* signed 8bit */
+typedef signed short INT16; /* signed 16bit */
+typedef signed int INT32; /* signed 32bit */
+#endif
+
+#if (OPL_OUTPUT_BIT==16)
+typedef INT16 OPLSAMPLE;
+#endif
+#if (OPL_OUTPUT_BIT==8)
+typedef unsigned char OPLSAMPLE;
+#endif
+
+
+#if BUILD_Y8950
+#include "ymdeltat.h"
+#endif
+
+typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec);
+typedef void (*OPL_IRQHANDLER)(int param,int irq);
+typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us);
+typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data);
+typedef unsigned char (*OPL_PORTHANDLER_R)(int param);
+
+/* !!!!! here is private section , do not access there member direct !!!!! */
+
+#define OPL_TYPE_WAVESEL 0x01 /* waveform select */
+#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */
+#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */
+#define OPL_TYPE_IO 0x08 /* I/O port */
+
+/* Saving is necessary for member of the 'R' mark for suspend/resume */
+/* ---------- OPL one of slot ---------- */
+typedef struct fm_opl_slot {
+ INT32 TL; /* total level :TL << 8 */
+ INT32 TLL; /* adjusted now TL */
+ UINT8 KSR; /* key scale rate :(shift down bit) */
+ INT32 *AR; /* attack rate :&AR_TABLE[AR<<2] */
+ INT32 *DR; /* decay rate :&DR_TALBE[DR<<2] */
+ INT32 SL; /* sustin level :SL_TALBE[SL] */
+ INT32 *RR; /* release rate :&DR_TABLE[RR<<2] */
+ UINT8 ksl; /* keyscale level :(shift down bits) */
+ UINT8 ksr; /* key scale rate :kcode>>KSR */
+ UINT32 mul; /* multiple :ML_TABLE[ML] */
+ UINT32 Cnt; /* frequency count : */
+ UINT32 Incr; /* frequency step : */
+ /* envelope generator state */
+ UINT8 eg_typ; /* envelope type flag */
+ UINT8 evm; /* envelope phase */
+ INT32 evc; /* envelope counter */
+ INT32 eve; /* envelope counter end point */
+ INT32 evs; /* envelope counter step */
+ INT32 evsa; /* envelope step for AR :AR[ksr] */
+ INT32 evsd; /* envelope step for DR :DR[ksr] */
+ INT32 evsr; /* envelope step for RR :RR[ksr] */
+ /* LFO */
+ UINT8 ams; /* ams flag */
+ UINT8 vib; /* vibrate flag */
+ /* wave selector */
+ INT32 **wavetable;
+}OPL_SLOT;
+
+/* ---------- OPL one of channel ---------- */
+typedef struct fm_opl_channel {
+ OPL_SLOT SLOT[2];
+ UINT8 CON; /* connection type */
+ UINT8 FB; /* feed back :(shift down bit) */
+ INT32 *connect1; /* slot1 output pointer */
+ INT32 *connect2; /* slot2 output pointer */
+ INT32 op1_out[2]; /* slot1 output for selfeedback */
+ /* phase generator state */
+ UINT32 block_fnum; /* block+fnum : */
+ UINT8 kcode; /* key code : KeyScaleCode */
+ UINT32 fc; /* Freq. Increment base */
+ UINT32 ksl_base; /* KeyScaleLevel Base step */
+ UINT8 keyon; /* key on/off flag */
+} OPL_CH;
+
+/* OPL state */
+typedef struct fm_opl_f {
+ UINT8 type; /* chip type */
+ int clock; /* master clock (Hz) */
+ int rate; /* sampling rate (Hz) */
+ double freqbase; /* frequency base */
+ double TimerBase; /* Timer base time (==sampling time) */
+ UINT8 address; /* address register */
+ UINT8 status; /* status flag */
+ UINT8 statusmask; /* status mask */
+ UINT32 mode; /* Reg.08 : CSM , notesel,etc. */
+ /* Timer */
+ int T[2]; /* timer counter */
+ UINT8 st[2]; /* timer enable */
+ /* FM channel slots */
+ OPL_CH *P_CH; /* pointer of CH */
+ int max_ch; /* maximum channel */
+ /* Rhythm sention */
+ UINT8 rhythm; /* Rhythm mode , key flag */
+#if BUILD_Y8950
+ /* Delta-T ADPCM unit (Y8950) */
+ YM_DELTAT *deltat; /* DELTA-T ADPCM */
+#endif
+ /* Keyboard / I/O interface unit (Y8950) */
+ UINT8 portDirection;
+ UINT8 portLatch;
+ OPL_PORTHANDLER_R porthandler_r;
+ OPL_PORTHANDLER_W porthandler_w;
+ int port_param;
+ OPL_PORTHANDLER_R keyboardhandler_r;
+ OPL_PORTHANDLER_W keyboardhandler_w;
+ int keyboard_param;
+ /* time tables */
+ INT32 AR_TABLE[75]; /* atttack rate tables */
+ INT32 DR_TABLE[75]; /* decay rate tables */
+ UINT32 FN_TABLE[1024]; /* fnumber -> increment counter */
+ /* LFO */
+ INT32 *ams_table;
+ INT32 *vib_table;
+ INT32 amsCnt;
+ INT32 amsIncr;
+ INT32 vibCnt;
+ INT32 vibIncr;
+ /* wave selector enable flag */
+ UINT8 wavesel;
+ /* external event callback handler */
+ OPL_TIMERHANDLER TimerHandler; /* TIMER handler */
+ int TimerParam; /* TIMER parameter */
+ OPL_IRQHANDLER IRQHandler; /* IRQ handler */
+ int IRQParam; /* IRQ parameter */
+ OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */
+ int UpdateParam; /* stream update parameter */
+} FM_OPL;
+
+/* ---------- Generic interface section ---------- */
+#define OPL_TYPE_YM3526 (0)
+#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL)
+#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO)
+
+FM_OPL *OPLCreate(int type, int clock, int rate);
+void OPLDestroy(FM_OPL *OPL);
+void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset);
+void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param);
+void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param);
+/* Y8950 port handlers */
+void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param);
+void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param);
+
+void OPLResetChip(FM_OPL *OPL);
+int OPLWrite(FM_OPL *OPL,int a,int v);
+unsigned char OPLRead(FM_OPL *OPL,int a);
+int OPLTimerOver(FM_OPL *OPL,int c);
+
+/* YM3626/YM3812 local section */
+void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length);
+
+void Y8950UpdateOne(FM_OPL *OPL, INT16 *buffer, int length);
+
+#endif
diff --git a/hw/audio/gus.c b/hw/audio/gus.c
new file mode 100644
index 00000000..86223a95
--- /dev/null
+++ b/hw/audio/gus.c
@@ -0,0 +1,318 @@
+/*
+ * QEMU Proxy for Gravis Ultrasound GF1 emulation by Tibor "TS" Schütz
+ *
+ * Copyright (c) 2002-2005 Vassili Karpov (malc)
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/audio/audio.h"
+#include "audio/audio.h"
+#include "hw/isa/isa.h"
+#include "gusemu.h"
+#include "gustate.h"
+
+#define dolog(...) AUD_log ("audio", __VA_ARGS__)
+#ifdef DEBUG
+#define ldebug(...) dolog (__VA_ARGS__)
+#else
+#define ldebug(...)
+#endif
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define GUS_ENDIANNESS 1
+#else
+#define GUS_ENDIANNESS 0
+#endif
+
+#define IO_READ_PROTO(name) \
+ static uint32_t name (void *opaque, uint32_t nport)
+#define IO_WRITE_PROTO(name) \
+ static void name (void *opaque, uint32_t nport, uint32_t val)
+
+#define TYPE_GUS "gus"
+#define GUS(obj) OBJECT_CHECK (GUSState, (obj), TYPE_GUS)
+
+typedef struct GUSState {
+ ISADevice dev;
+ GUSEmuState emu;
+ QEMUSoundCard card;
+ uint32_t freq;
+ uint32_t port;
+ int pos, left, shift, irqs;
+ GUSsample *mixbuf;
+ uint8_t himem[1024 * 1024 + 32 + 4096];
+ int samples;
+ SWVoiceOut *voice;
+ int64_t last_ticks;
+ qemu_irq pic;
+} GUSState;
+
+IO_READ_PROTO (gus_readb)
+{
+ GUSState *s = opaque;
+
+ return gus_read (&s->emu, nport, 1);
+}
+
+IO_WRITE_PROTO (gus_writeb)
+{
+ GUSState *s = opaque;
+
+ gus_write (&s->emu, nport, 1, val);
+}
+
+static int write_audio (GUSState *s, int samples)
+{
+ int net = 0;
+ int pos = s->pos;
+
+ while (samples) {
+ int nbytes, wbytes, wsampl;
+
+ nbytes = samples << s->shift;
+ wbytes = AUD_write (
+ s->voice,
+ s->mixbuf + (pos << (s->shift - 1)),
+ nbytes
+ );
+
+ if (wbytes) {
+ wsampl = wbytes >> s->shift;
+
+ samples -= wsampl;
+ pos = (pos + wsampl) % s->samples;
+
+ net += wsampl;
+ }
+ else {
+ break;
+ }
+ }
+
+ return net;
+}
+
+static void GUS_callback (void *opaque, int free)
+{
+ int samples, to_play, net = 0;
+ GUSState *s = opaque;
+
+ samples = free >> s->shift;
+ to_play = audio_MIN (samples, s->left);
+
+ while (to_play) {
+ int written = write_audio (s, to_play);
+
+ if (!written) {
+ goto reset;
+ }
+
+ s->left -= written;
+ to_play -= written;
+ samples -= written;
+ net += written;
+ }
+
+ samples = audio_MIN (samples, s->samples);
+ if (samples) {
+ gus_mixvoices (&s->emu, s->freq, samples, s->mixbuf);
+
+ while (samples) {
+ int written = write_audio (s, samples);
+ if (!written) {
+ break;
+ }
+ samples -= written;
+ net += written;
+ }
+ }
+ s->left = samples;
+
+ reset:
+ gus_irqgen (&s->emu, muldiv64 (net, 1000000, s->freq));
+}
+
+int GUS_irqrequest (GUSEmuState *emu, int hwirq, int n)
+{
+ GUSState *s = emu->opaque;
+ /* qemu_irq_lower (s->pic); */
+ qemu_irq_raise (s->pic);
+ s->irqs += n;
+ ldebug ("irqrequest %d %d %d\n", hwirq, n, s->irqs);
+ return n;
+}
+
+void GUS_irqclear (GUSEmuState *emu, int hwirq)
+{
+ GUSState *s = emu->opaque;
+ ldebug ("irqclear %d %d\n", hwirq, s->irqs);
+ qemu_irq_lower (s->pic);
+ s->irqs -= 1;
+#ifdef IRQ_STORM
+ if (s->irqs > 0) {
+ qemu_irq_raise (s->pic[hwirq]);
+ }
+#endif
+}
+
+void GUS_dmarequest (GUSEmuState *der)
+{
+ /* GUSState *s = (GUSState *) der; */
+ ldebug ("dma request %d\n", der->gusdma);
+ DMA_hold_DREQ (der->gusdma);
+}
+
+static int GUS_read_DMA (void *opaque, int nchan, int dma_pos, int dma_len)
+{
+ GUSState *s = opaque;
+ char tmpbuf[4096];
+ int pos = dma_pos, mode, left = dma_len - dma_pos;
+
+ ldebug ("read DMA %#x %d\n", dma_pos, dma_len);
+ mode = DMA_get_channel_mode (s->emu.gusdma);
+ while (left) {
+ int to_copy = audio_MIN ((size_t) left, sizeof (tmpbuf));
+ int copied;
+
+ ldebug ("left=%d to_copy=%d pos=%d\n", left, to_copy, pos);
+ copied = DMA_read_memory (nchan, tmpbuf, pos, to_copy);
+ gus_dma_transferdata (&s->emu, tmpbuf, copied, left == copied);
+ left -= copied;
+ pos += copied;
+ }
+
+ if (((mode >> 4) & 1) == 0) {
+ DMA_release_DREQ (s->emu.gusdma);
+ }
+ return dma_len;
+}
+
+static const VMStateDescription vmstate_gus = {
+ .name = "gus",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32 (pos, GUSState),
+ VMSTATE_INT32 (left, GUSState),
+ VMSTATE_INT32 (shift, GUSState),
+ VMSTATE_INT32 (irqs, GUSState),
+ VMSTATE_INT32 (samples, GUSState),
+ VMSTATE_INT64 (last_ticks, GUSState),
+ VMSTATE_BUFFER (himem, GUSState),
+ VMSTATE_END_OF_LIST ()
+ }
+};
+
+static const MemoryRegionPortio gus_portio_list1[] = {
+ {0x000, 1, 1, .write = gus_writeb },
+ {0x006, 10, 1, .read = gus_readb, .write = gus_writeb },
+ {0x100, 8, 1, .read = gus_readb, .write = gus_writeb },
+ PORTIO_END_OF_LIST (),
+};
+
+static const MemoryRegionPortio gus_portio_list2[] = {
+ {0, 2, 1, .read = gus_readb },
+ PORTIO_END_OF_LIST (),
+};
+
+static void gus_realizefn (DeviceState *dev, Error **errp)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+ GUSState *s = GUS (dev);
+ struct audsettings as;
+
+ AUD_register_card ("gus", &s->card);
+
+ as.freq = s->freq;
+ as.nchannels = 2;
+ as.fmt = AUD_FMT_S16;
+ as.endianness = GUS_ENDIANNESS;
+
+ s->voice = AUD_open_out (
+ &s->card,
+ NULL,
+ "gus",
+ s,
+ GUS_callback,
+ &as
+ );
+
+ if (!s->voice) {
+ AUD_remove_card (&s->card);
+ error_setg(errp, "No voice");
+ return;
+ }
+
+ s->shift = 2;
+ s->samples = AUD_get_buffer_size_out (s->voice) >> s->shift;
+ s->mixbuf = g_malloc0 (s->samples << s->shift);
+
+ isa_register_portio_list (d, s->port, gus_portio_list1, s, "gus");
+ isa_register_portio_list (d, (s->port + 0x100) & 0xf00,
+ gus_portio_list2, s, "gus");
+
+ DMA_register_channel (s->emu.gusdma, GUS_read_DMA, s);
+ s->emu.himemaddr = s->himem;
+ s->emu.gusdatapos = s->emu.himemaddr + 1024 * 1024 + 32;
+ s->emu.opaque = s;
+ isa_init_irq (d, &s->pic, s->emu.gusirq);
+
+ AUD_set_active_out (s->voice, 1);
+}
+
+static int GUS_init (ISABus *bus)
+{
+ isa_create_simple (bus, TYPE_GUS);
+ return 0;
+}
+
+static Property gus_properties[] = {
+ DEFINE_PROP_UINT32 ("freq", GUSState, freq, 44100),
+ DEFINE_PROP_UINT32 ("iobase", GUSState, port, 0x240),
+ DEFINE_PROP_UINT32 ("irq", GUSState, emu.gusirq, 7),
+ DEFINE_PROP_UINT32 ("dma", GUSState, emu.gusdma, 3),
+ DEFINE_PROP_END_OF_LIST (),
+};
+
+static void gus_class_initfn (ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS (klass);
+
+ dc->realize = gus_realizefn;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = "Gravis Ultrasound GF1";
+ dc->vmsd = &vmstate_gus;
+ dc->props = gus_properties;
+}
+
+static const TypeInfo gus_info = {
+ .name = TYPE_GUS,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof (GUSState),
+ .class_init = gus_class_initfn,
+};
+
+static void gus_register_types (void)
+{
+ type_register_static (&gus_info);
+ isa_register_soundhw("gus", "Gravis Ultrasound GF1", GUS_init);
+}
+
+type_init (gus_register_types)
diff --git a/hw/audio/gusemu.h b/hw/audio/gusemu.h
new file mode 100644
index 00000000..331bb6fe
--- /dev/null
+++ b/hw/audio/gusemu.h
@@ -0,0 +1,105 @@
+/*
+ * GUSEMU32 - API
+ *
+ * Copyright (C) 2000-2007 Tibor "TS" Schütz
+ *
+ * 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.
+ */
+
+#ifndef GUSEMU_H
+#define GUSEMU_H
+
+/* data types (need to be adjusted if neither a VC6 nor a C99 compatible compiler is used) */
+
+#if defined _WIN32 && defined _MSC_VER /* doesn't support other win32 compilers yet, do it yourself... */
+ typedef unsigned char GUSbyte;
+ typedef unsigned short GUSword;
+ typedef unsigned int GUSdword;
+ typedef signed char GUSchar;
+ typedef signed short GUSsample;
+#else
+ #include <stdint.h>
+ typedef int8_t GUSchar;
+ typedef uint8_t GUSbyte;
+ typedef uint16_t GUSword;
+ typedef uint32_t GUSdword;
+ typedef int16_t GUSsample;
+#endif
+
+typedef struct _GUSEmuState
+{
+ GUSbyte *himemaddr; /* 1024*1024 bytes used for storing uploaded samples (+32 additional bytes for read padding) */
+ GUSbyte *gusdatapos; /* (gusdataend-gusdata) bytes used for storing emulated GF1/mixer register states (32*32+4 bytes in initial GUSemu32 version) */
+ uint32_t gusirq;
+ uint32_t gusdma;
+ unsigned int timer1fraction;
+ unsigned int timer2fraction;
+ void *opaque;
+} GUSEmuState;
+
+/* ** Callback functions needed: */
+/* NMI is defined as hwirq=-1 (not supported (yet?)) */
+/* GUS_irqrequest returns the number of IRQs actually scheduled into the virtual machine */
+/* Level triggered IRQ simulations normally return 1 */
+/* Event triggered IRQ simulation can safely ignore GUS_irqclear calls */
+int GUS_irqrequest(GUSEmuState *state, int hwirq, int num);/* needed in both mixer and bus emulation functions. */
+void GUS_irqclear( GUSEmuState *state, int hwirq); /* used by gus_write() only - can be left empty for mixer functions */
+void GUS_dmarequest(GUSEmuState *state); /* used by gus_write() only - can be left empty for mixer functions */
+
+/* ** ISA bus interface functions: */
+
+/* Port I/O handlers */
+/* support the following ports: */
+/* 2x0,2x6,2x8...2xF,3x0...3x7; */
+/* optional: 388,389 (at least writes should be forwarded or some GUS detection algorithms will fail) */
+/* data is passed in host byte order */
+unsigned int gus_read( GUSEmuState *state, int port, int size);
+void gus_write(GUSEmuState *state, int port, int size, unsigned int data);
+/* size is given in bytes (1 for byte, 2 for word) */
+
+/* DMA data transfer function */
+/* data pointed to is passed in native x86 order */
+void gus_dma_transferdata(GUSEmuState *state, char *dma_addr, unsigned int count, int TC);
+/* Called back by GUS_start_DMA as soon as the emulated DMA controller is ready for a transfer to or from GUS */
+/* (might be immediately if the DMA controller was programmed first) */
+/* dma_addr is an already translated address directly pointing to the beginning of the memory block */
+/* do not forget to update DMA states after the call, including the DREQ and TC flags */
+/* it is possible to break down a single transfer into multiple ones, but take care that: */
+/* -dma_count is actually count-1 */
+/* -before and during a transfer, DREQ is set and TC cleared */
+/* -when calling gus_dma_transferdata(), TC is only set true for call transferring the last byte */
+/* -after the last transfer, DREQ is cleared and TC is set */
+
+/* ** GF1 mixer emulation functions: */
+/* Usually, gus_irqgen should be called directly after gus_mixvoices if you can meet the recommended ranges. */
+/* If the interrupts are executed immediately (i.e., are synchronous), it may be useful to break this */
+/* down into a sequence of gus_mixvoice();gus_irqgen(); calls while mixing an audio block. */
+/* If the interrupts are asynchronous, it may be needed to use a separate thread mixing into a temporary */
+/* audio buffer in order to avoid quality loss caused by large numsamples and elapsed_time values. */
+
+void gus_mixvoices(GUSEmuState *state, unsigned int playback_freq, unsigned int numsamples, GUSsample *bufferpos);
+/* recommended range: 10 < numsamples < 100 */
+/* lower values may result in increased rounding error, higher values often cause audible timing delays */
+
+void gus_irqgen(GUSEmuState *state, unsigned int elapsed_time);
+/* recommended range: 80us < elapsed_time < max(1000us, numsamples/playback_freq) */
+/* lower values won´t provide any benefit at all, higher values can cause audible timing delays */
+/* note: masked timers are also calculated by this function, thus it might be needed even without any IRQs in use! */
+
+#endif /* gusemu.h */
diff --git a/hw/audio/gusemu_hal.c b/hw/audio/gusemu_hal.c
new file mode 100644
index 00000000..60966907
--- /dev/null
+++ b/hw/audio/gusemu_hal.c
@@ -0,0 +1,554 @@
+/*
+ * GUSEMU32 - bus interface part
+ *
+ * Copyright (C) 2000-2007 Tibor "TS" Schütz
+ *
+ * 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.
+ */
+
+/*
+ * TODO: check mixer: see 7.20 of sdk for panning pos (applies to all gus models?)?
+ */
+
+#include "gustate.h"
+#include "gusemu.h"
+
+#define GUSregb(position) (* (gusptr+(position)))
+#define GUSregw(position) (*(GUSword *) (gusptr+(position)))
+#define GUSregd(position) (*(GUSdword *)(gusptr+(position)))
+
+/* size given in bytes */
+unsigned int gus_read(GUSEmuState * state, int port, int size)
+{
+ int value_read = 0;
+
+ GUSbyte *gusptr;
+ gusptr = state->gusdatapos;
+ GUSregd(portaccesses)++;
+
+ switch (port & 0xff0f)
+ {
+ /* MixerCtrlReg (read not supported on GUS classic) */
+ /* case 0x200: return GUSregb(MixerCtrlReg2x0); */
+ case 0x206: /* IRQstatReg / SB2x6IRQ */
+ /* adlib/sb bits set in port handlers */
+ /* timer/voice bits set in gus_irqgen() */
+ /* dma bit set in gus_dma_transferdata */
+ /* midi not implemented yet */
+ return GUSregb(IRQStatReg2x6);
+ /* case 0x308: */ /* AdLib388 */
+ case 0x208:
+ if (GUSregb(GUS45TimerCtrl) & 1)
+ return GUSregb(TimerStatus2x8);
+ return GUSregb(AdLibStatus2x8); /* AdLibStatus */
+ case 0x309: /* AdLib389 */
+ case 0x209:
+ return GUSregb(AdLibData2x9); /* AdLibData */
+ case 0x20A:
+ return GUSregb(AdLibCommand2xA); /* AdLib2x8_2xA */
+
+#if 0
+ case 0x20B: /* GUS hidden registers (read not supported on GUS classic) */
+ switch (GUSregb(RegCtrl_2xF) & 0x07)
+ {
+ case 0: /* IRQ/DMA select */
+ if (GUSregb(MixerCtrlReg2x0) & 0x40)
+ return GUSregb(IRQ_2xB); /* control register select bit */
+ else
+ return GUSregb(DMA_2xB);
+ /* case 1-5: */ /* general purpose emulation regs */
+ /* return ... */ /* + status reset reg (write only) */
+ case 6:
+ return GUSregb(Jumper_2xB); /* Joystick/MIDI enable (JumperReg) */
+ default:;
+ }
+ break;
+#endif
+
+ case 0x20C: /* SB2xCd */
+ value_read = GUSregb(SB2xCd);
+ if (GUSregb(StatRead_2xF) & 0x20)
+ GUSregb(SB2xCd) ^= 0x80; /* toggle MSB on read */
+ return value_read;
+ /* case 0x20D: */ /* SB2xD is write only -> 2xE writes to it*/
+ case 0x20E:
+ if (GUSregb(RegCtrl_2xF) & 0x80) /* 2xE read IRQ enabled? */
+ {
+ GUSregb(StatRead_2xF) |= 0x80;
+ GUS_irqrequest(state, state->gusirq, 1);
+ }
+ return GUSregb(SB2xE); /* SB2xE */
+ case 0x20F: /* StatRead_2xF */
+ /*set/clear fixed bits */
+ /*value_read = (GUSregb(StatRead_2xF) & 0xf9)|1; */ /*(LSB not set on GUS classic!)*/
+ value_read = (GUSregb(StatRead_2xF) & 0xf9);
+ if (GUSregb(MixerCtrlReg2x0) & 0x08)
+ value_read |= 2; /* DMA/IRQ enabled flag */
+ return value_read;
+ /* case 0x300: */ /* MIDI (not implemented) */
+ /* case 0x301: */ /* MIDI (not implemented) */
+ case 0x302:
+ return GUSregb(VoiceSelReg3x2); /* VoiceSelReg */
+ case 0x303:
+ return GUSregb(FunkSelReg3x3); /* FunkSelReg */
+ case 0x304: /* DataRegLoByte3x4 + DataRegWord3x4 */
+ case 0x305: /* DataRegHiByte3x5 */
+ switch (GUSregb(FunkSelReg3x3))
+ {
+ /* common functions */
+ case 0x41: /* DramDMAContrReg */
+ value_read = GUSregb(GUS41DMACtrl); /* &0xfb */
+ GUSregb(GUS41DMACtrl) &= 0xbb;
+ if (state->gusdma >= 4)
+ value_read |= 0x04;
+ if (GUSregb(IRQStatReg2x6) & 0x80)
+ {
+ value_read |= 0x40;
+ GUSregb(IRQStatReg2x6) &= 0x7f;
+ if (!GUSregb(IRQStatReg2x6))
+ GUS_irqclear(state, state->gusirq);
+ }
+ return (GUSbyte) value_read;
+ /* DramDMAmemPosReg */
+ /* case 0x42: value_read=GUSregw(GUS42DMAStart); break;*/
+ /* 43h+44h write only */
+ case 0x45:
+ return GUSregb(GUS45TimerCtrl); /* TimerCtrlReg */
+ /* 46h+47h write only */
+ /* 48h: samp freq - write only */
+ case 0x49:
+ return GUSregb(GUS49SampCtrl) & 0xbf; /* SampCtrlReg */
+ /* case 4bh: */ /* joystick trim not supported */
+ /* case 0x4c: return GUSregb(GUS4cReset); */ /* GUSreset: write only*/
+ /* voice specific functions */
+ case 0x80:
+ case 0x81:
+ case 0x82:
+ case 0x83:
+ case 0x84:
+ case 0x85:
+ case 0x86:
+ case 0x87:
+ case 0x88:
+ case 0x89:
+ case 0x8a:
+ case 0x8b:
+ case 0x8c:
+ case 0x8d:
+ {
+ int offset = 2 * (GUSregb(FunkSelReg3x3) & 0x0f);
+ offset += ((int) GUSregb(VoiceSelReg3x2) & 0x1f) << 5; /* = Voice*32 + Funktion*2 */
+ value_read = GUSregw(offset);
+ }
+ break;
+ /* voice unspecific functions */
+ case 0x8e: /* NumVoice */
+ return GUSregb(NumVoices);
+ case 0x8f: /* irqstatreg */
+ /* (pseudo IRQ-FIFO is processed during a gus_write(0x3X3,0x8f)) */
+ return GUSregb(SynVoiceIRQ8f);
+ default:
+ return 0xffff;
+ }
+ if (size == 1)
+ {
+ if ((port & 0xff0f) == 0x305)
+ value_read = value_read >> 8;
+ value_read &= 0xff;
+ }
+ return (GUSword) value_read;
+ /* case 0x306: */ /* Mixer/Version info */
+ /* return 0xff; */ /* Pre 3.6 boards, ICS mixer NOT present */
+ case 0x307: /* DRAMaccess */
+ {
+ GUSbyte *adr;
+ adr = state->himemaddr + (GUSregd(GUSDRAMPOS24bit) & 0xfffff);
+ return *adr;
+ }
+ default:;
+ }
+ return 0xffff;
+}
+
+void gus_write(GUSEmuState * state, int port, int size, unsigned int data)
+{
+ GUSbyte *gusptr;
+ gusptr = state->gusdatapos;
+ GUSregd(portaccesses)++;
+
+ switch (port & 0xff0f)
+ {
+ case 0x200: /* MixerCtrlReg */
+ GUSregb(MixerCtrlReg2x0) = (GUSbyte) data;
+ break;
+ case 0x206: /* IRQstatReg / SB2x6IRQ */
+ if (GUSregb(GUS45TimerCtrl) & 0x20) /* SB IRQ enabled? -> set 2x6IRQ bit */
+ {
+ GUSregb(TimerStatus2x8) |= 0x08;
+ GUSregb(IRQStatReg2x6) = 0x10;
+ GUS_irqrequest(state, state->gusirq, 1);
+ }
+ break;
+ case 0x308: /* AdLib 388h */
+ case 0x208: /* AdLibCommandReg */
+ GUSregb(AdLibCommand2xA) = (GUSbyte) data;
+ break;
+ case 0x309: /* AdLib 389h */
+ case 0x209: /* AdLibDataReg */
+ if ((GUSregb(AdLibCommand2xA) == 0x04) && (!(GUSregb(GUS45TimerCtrl) & 1))) /* GUS auto timer mode enabled? */
+ {
+ if (data & 0x80)
+ GUSregb(TimerStatus2x8) &= 0x1f; /* AdLib IRQ reset? -> clear maskable adl. timer int regs */
+ else
+ GUSregb(TimerDataReg2x9) = (GUSbyte) data;
+ }
+ else
+ {
+ GUSregb(AdLibData2x9) = (GUSbyte) data;
+ if (GUSregb(GUS45TimerCtrl) & 0x02)
+ {
+ GUSregb(TimerStatus2x8) |= 0x01;
+ GUSregb(IRQStatReg2x6) = 0x10;
+ GUS_irqrequest(state, state->gusirq, 1);
+ }
+ }
+ break;
+ case 0x20A:
+ GUSregb(AdLibStatus2x8) = (GUSbyte) data;
+ break; /* AdLibStatus2x8 */
+ case 0x20B: /* GUS hidden registers */
+ switch (GUSregb(RegCtrl_2xF) & 0x7)
+ {
+ case 0:
+ if (GUSregb(MixerCtrlReg2x0) & 0x40)
+ GUSregb(IRQ_2xB) = (GUSbyte) data; /* control register select bit */
+ else
+ GUSregb(DMA_2xB) = (GUSbyte) data;
+ break;
+ /* case 1-4: general purpose emulation regs */
+ case 5: /* clear stat reg 2xF */
+ GUSregb(StatRead_2xF) = 0; /* ToDo: is this identical with GUS classic? */
+ if (!GUSregb(IRQStatReg2x6))
+ GUS_irqclear(state, state->gusirq);
+ break;
+ case 6: /* Jumper reg (Joystick/MIDI enable) */
+ GUSregb(Jumper_2xB) = (GUSbyte) data;
+ break;
+ default:;
+ }
+ break;
+ case 0x20C: /* SB2xCd */
+ if (GUSregb(GUS45TimerCtrl) & 0x20)
+ {
+ GUSregb(TimerStatus2x8) |= 0x10; /* SB IRQ enabled? -> set 2xCIRQ bit */
+ GUSregb(IRQStatReg2x6) = 0x10;
+ GUS_irqrequest(state, state->gusirq, 1);
+ }
+ case 0x20D: /* SB2xCd no IRQ */
+ GUSregb(SB2xCd) = (GUSbyte) data;
+ break;
+ case 0x20E: /* SB2xE */
+ GUSregb(SB2xE) = (GUSbyte) data;
+ break;
+ case 0x20F:
+ GUSregb(RegCtrl_2xF) = (GUSbyte) data;
+ break; /* CtrlReg2xF */
+ case 0x302: /* VoiceSelReg */
+ GUSregb(VoiceSelReg3x2) = (GUSbyte) data;
+ break;
+ case 0x303: /* FunkSelReg */
+ GUSregb(FunkSelReg3x3) = (GUSbyte) data;
+ if ((GUSbyte) data == 0x8f) /* set irqstatreg, get voicereg and clear IRQ */
+ {
+ int voice;
+ if (GUSregd(voicewavetableirq)) /* WavetableIRQ */
+ {
+ for (voice = 0; voice < 31; voice++)
+ {
+ if (GUSregd(voicewavetableirq) & (1 << voice))
+ {
+ GUSregd(voicewavetableirq) ^= (1 << voice); /* clear IRQ bit */
+ GUSregb(voice << 5) &= 0x7f; /* clear voice reg irq bit */
+ if (!GUSregd(voicewavetableirq))
+ GUSregb(IRQStatReg2x6) &= 0xdf;
+ if (!GUSregb(IRQStatReg2x6))
+ GUS_irqclear(state, state->gusirq);
+ GUSregb(SynVoiceIRQ8f) = voice | 0x60; /* (bit==0 => IRQ wartend) */
+ return;
+ }
+ }
+ }
+ else if (GUSregd(voicevolrampirq)) /* VolRamp IRQ */
+ {
+ for (voice = 0; voice < 31; voice++)
+ {
+ if (GUSregd(voicevolrampirq) & (1 << voice))
+ {
+ GUSregd(voicevolrampirq) ^= (1 << voice); /* clear IRQ bit */
+ GUSregb((voice << 5) + VSRVolRampControl) &= 0x7f; /* clear voice volume reg irq bit */
+ if (!GUSregd(voicevolrampirq))
+ GUSregb(IRQStatReg2x6) &= 0xbf;
+ if (!GUSregb(IRQStatReg2x6))
+ GUS_irqclear(state, state->gusirq);
+ GUSregb(SynVoiceIRQ8f) = voice | 0x80; /* (bit==0 => IRQ wartend) */
+ return;
+ }
+ }
+ }
+ GUSregb(SynVoiceIRQ8f) = 0xe8; /* kein IRQ wartet */
+ }
+ break;
+ case 0x304:
+ case 0x305:
+ {
+ GUSword writedata = (GUSword) data;
+ GUSword readmask = 0x0000;
+ if (size == 1)
+ {
+ readmask = 0xff00;
+ writedata &= 0xff;
+ if ((port & 0xff0f) == 0x305)
+ {
+ writedata = (GUSword) (writedata << 8);
+ readmask = 0x00ff;
+ }
+ }
+ switch (GUSregb(FunkSelReg3x3))
+ {
+ /* voice specific functions */
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ {
+ int offset;
+ if (!(GUSregb(GUS4cReset) & 0x01))
+ break; /* reset flag active? */
+ offset = 2 * (GUSregb(FunkSelReg3x3) & 0x0f);
+ offset += (GUSregb(VoiceSelReg3x2) & 0x1f) << 5; /* = Voice*32 + Funktion*2 */
+ GUSregw(offset) = (GUSword) ((GUSregw(offset) & readmask) | writedata);
+ }
+ break;
+ /* voice unspecific functions */
+ case 0x0e: /* NumVoices */
+ GUSregb(NumVoices) = (GUSbyte) data;
+ break;
+ /* case 0x0f: */ /* read only */
+ /* common functions */
+ case 0x41: /* DramDMAContrReg */
+ GUSregb(GUS41DMACtrl) = (GUSbyte) data;
+ if (data & 0x01)
+ GUS_dmarequest(state);
+ break;
+ case 0x42: /* DramDMAmemPosReg */
+ GUSregw(GUS42DMAStart) = (GUSregw(GUS42DMAStart) & readmask) | writedata;
+ GUSregb(GUS50DMAHigh) &= 0xf; /* compatibility stuff... */
+ break;
+ case 0x43: /* DRAMaddrLo */
+ GUSregd(GUSDRAMPOS24bit) =
+ (GUSregd(GUSDRAMPOS24bit) & (readmask | 0xff0000)) | writedata;
+ break;
+ case 0x44: /* DRAMaddrHi */
+ GUSregd(GUSDRAMPOS24bit) =
+ (GUSregd(GUSDRAMPOS24bit) & 0xffff) | ((data & 0x0f) << 16);
+ break;
+ case 0x45: /* TCtrlReg */
+ GUSregb(GUS45TimerCtrl) = (GUSbyte) data;
+ if (!(data & 0x20))
+ GUSregb(TimerStatus2x8) &= 0xe7; /* sb IRQ dis? -> clear 2x8/2xC sb IRQ flags */
+ if (!(data & 0x02))
+ GUSregb(TimerStatus2x8) &= 0xfe; /* adlib data IRQ dis? -> clear 2x8 adlib IRQ flag */
+ if (!(GUSregb(TimerStatus2x8) & 0x19))
+ GUSregb(IRQStatReg2x6) &= 0xef; /* 0xe6; $$clear IRQ if both IRQ bits are inactive or cleared */
+ /* catch up delayed timer IRQs: */
+ if ((GUSregw(TimerIRQs) > 1) && (GUSregb(TimerDataReg2x9) & 3))
+ {
+ if (GUSregb(TimerDataReg2x9) & 1) /* start timer 1 (80us decrement rate) */
+ {
+ if (!(GUSregb(TimerDataReg2x9) & 0x40))
+ GUSregb(TimerStatus2x8) |= 0xc0; /* maskable bits */
+ if (data & 4) /* timer1 irq enable */
+ {
+ GUSregb(TimerStatus2x8) |= 4; /* nonmaskable bit */
+ GUSregb(IRQStatReg2x6) |= 4; /* timer 1 irq pending */
+ }
+ }
+ if (GUSregb(TimerDataReg2x9) & 2) /* start timer 2 (320us decrement rate) */
+ {
+ if (!(GUSregb(TimerDataReg2x9) & 0x20))
+ GUSregb(TimerStatus2x8) |= 0xa0; /* maskable bits */
+ if (data & 8) /* timer2 irq enable */
+ {
+ GUSregb(TimerStatus2x8) |= 2; /* nonmaskable bit */
+ GUSregb(IRQStatReg2x6) |= 8; /* timer 2 irq pending */
+ }
+ }
+ GUSregw(TimerIRQs)--;
+ if (GUSregw(BusyTimerIRQs) > 1)
+ GUSregw(BusyTimerIRQs)--;
+ else
+ GUSregw(BusyTimerIRQs) =
+ GUS_irqrequest(state, state->gusirq, GUSregw(TimerIRQs));
+ }
+ else
+ GUSregw(TimerIRQs) = 0;
+
+ if (!(data & 0x04))
+ {
+ GUSregb(TimerStatus2x8) &= 0xfb; /* clear non-maskable timer1 bit */
+ GUSregb(IRQStatReg2x6) &= 0xfb;
+ }
+ if (!(data & 0x08))
+ {
+ GUSregb(TimerStatus2x8) &= 0xfd; /* clear non-maskable timer2 bit */
+ GUSregb(IRQStatReg2x6) &= 0xf7;
+ }
+ if (!GUSregb(IRQStatReg2x6))
+ GUS_irqclear(state, state->gusirq);
+ break;
+ case 0x46: /* Counter1 */
+ GUSregb(GUS46Counter1) = (GUSbyte) data;
+ break;
+ case 0x47: /* Counter2 */
+ GUSregb(GUS47Counter2) = (GUSbyte) data;
+ break;
+ /* case 0x48: */ /* sampling freq reg not emulated (same as interwave) */
+ case 0x49: /* SampCtrlReg */
+ GUSregb(GUS49SampCtrl) = (GUSbyte) data;
+ break;
+ /* case 0x4b: */ /* joystick trim not emulated */
+ case 0x4c: /* GUSreset */
+ GUSregb(GUS4cReset) = (GUSbyte) data;
+ if (!(GUSregb(GUS4cReset) & 1)) /* reset... */
+ {
+ GUSregd(voicewavetableirq) = 0;
+ GUSregd(voicevolrampirq) = 0;
+ GUSregw(TimerIRQs) = 0;
+ GUSregw(BusyTimerIRQs) = 0;
+ GUSregb(NumVoices) = 0xcd;
+ GUSregb(IRQStatReg2x6) = 0;
+ GUSregb(TimerStatus2x8) = 0;
+ GUSregb(AdLibData2x9) = 0;
+ GUSregb(TimerDataReg2x9) = 0;
+ GUSregb(GUS41DMACtrl) = 0;
+ GUSregb(GUS45TimerCtrl) = 0;
+ GUSregb(GUS49SampCtrl) = 0;
+ GUSregb(GUS4cReset) &= 0xf9; /* clear IRQ and DAC enable bits */
+ GUS_irqclear(state, state->gusirq);
+ }
+ /* IRQ enable bit checked elsewhere */
+ /* EnableDAC bit may be used by external callers */
+ break;
+ }
+ }
+ break;
+ case 0x307: /* DRAMaccess */
+ {
+ GUSbyte *adr;
+ adr = state->himemaddr + (GUSregd(GUSDRAMPOS24bit) & 0xfffff);
+ *adr = (GUSbyte) data;
+ }
+ break;
+ }
+}
+
+/* Attention when breaking up a single DMA transfer to multiple ones:
+ * it may lead to multiple terminal count interrupts and broken transfers:
+ *
+ * 1. Whenever you transfer a piece of data, the gusemu callback is invoked
+ * 2. The callback may generate a TC irq (if the register was set up to do so)
+ * 3. The irq may result in the program using the GUS to reprogram the GUS
+ *
+ * Some programs also decide to upload by just checking if TC occurs
+ * (via interrupt or a cleared GUS dma flag)
+ * and then start the next transfer, without checking DMA state
+ *
+ * Thus: Always make sure to set the TC flag correctly!
+ *
+ * Note that the genuine GUS had a granularity of 16 bytes/words for low/high DMA
+ * while later cards had atomic granularity provided by an additional GUS50DMAHigh register
+ * GUSemu also uses this register to support byte-granular transfers for better compatibility
+ * with emulators other than GUSemu32
+ */
+
+void gus_dma_transferdata(GUSEmuState * state, char *dma_addr, unsigned int count, int TC)
+{
+ /* this function gets called by the callback function as soon as a DMA transfer is about to start
+ * dma_addr is a translated address within accessible memory, not the physical one,
+ * count is (real dma count register)+1
+ * note that the amount of bytes transferred is fully determined by values in the DMA registers
+ * do not forget to update DMA states after transferring the entire block:
+ * DREQ cleared & TC asserted after the _whole_ transfer */
+
+ char *srcaddr;
+ char *destaddr;
+ char msbmask = 0;
+ GUSbyte *gusptr;
+ gusptr = state->gusdatapos;
+
+ srcaddr = dma_addr; /* system memory address */
+ {
+ int offset = (GUSregw(GUS42DMAStart) << 4) + (GUSregb(GUS50DMAHigh) & 0xf);
+ if (state->gusdma >= 4)
+ offset = (offset & 0xc0000) + (2 * (offset & 0x1fff0)); /* 16 bit address translation */
+ destaddr = (char *) state->himemaddr + offset; /* wavetable RAM address */
+ }
+
+ GUSregw(GUS42DMAStart) += (GUSword) (count >> 4); /* ToDo: add 16bit GUS page limit? */
+ GUSregb(GUS50DMAHigh) = (GUSbyte) ((count + GUSregb(GUS50DMAHigh)) & 0xf); /* ToDo: add 16bit GUS page limit? */
+
+ if (GUSregb(GUS41DMACtrl) & 0x02) /* direction, 0 := sysram->gusram */
+ {
+ char *tmpaddr = destaddr;
+ destaddr = srcaddr;
+ srcaddr = tmpaddr;
+ }
+
+ if ((GUSregb(GUS41DMACtrl) & 0x80) && (!(GUSregb(GUS41DMACtrl) & 0x02)))
+ msbmask = (const char) 0x80; /* invert MSB */
+ for (; count > 0; count--)
+ {
+ if (GUSregb(GUS41DMACtrl) & 0x40)
+ *(destaddr++) = *(srcaddr++); /* 16 bit lobyte */
+ else
+ *(destaddr++) = (msbmask ^ (*(srcaddr++))); /* 8 bit */
+ if (state->gusdma >= 4)
+ *(destaddr++) = (msbmask ^ (*(srcaddr++))); /* 16 bit hibyte */
+ }
+
+ if (TC)
+ {
+ (GUSregb(GUS41DMACtrl)) &= 0xfe; /* clear DMA request bit */
+ if (GUSregb(GUS41DMACtrl) & 0x20) /* DMA terminal count IRQ */
+ {
+ GUSregb(IRQStatReg2x6) |= 0x80;
+ GUS_irqrequest(state, state->gusirq, 1);
+ }
+ }
+}
diff --git a/hw/audio/gusemu_mixer.c b/hw/audio/gusemu_mixer.c
new file mode 100644
index 00000000..6d8d9ced
--- /dev/null
+++ b/hw/audio/gusemu_mixer.c
@@ -0,0 +1,240 @@
+/*
+ * GUSEMU32 - mixing engine (similar to Interwave GF1 compatibility)
+ *
+ * Copyright (C) 2000-2007 Tibor "TS" Schütz
+ *
+ * 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.
+ */
+
+#include "gusemu.h"
+#include "gustate.h"
+
+#define GUSregb(position) (* (gusptr+(position)))
+#define GUSregw(position) (*(GUSword *) (gusptr+(position)))
+#define GUSregd(position) (*(GUSdword *)(gusptr+(position)))
+
+#define GUSvoice(position) (*(GUSword *)(voiceptr+(position)))
+
+/* samples are always 16bit stereo (4 bytes each, first right then left interleaved) */
+void gus_mixvoices(GUSEmuState * state, unsigned int playback_freq, unsigned int numsamples,
+ GUSsample *bufferpos)
+{
+ /* note that byte registers are stored in the upper half of each voice register! */
+ GUSbyte *gusptr;
+ int Voice;
+ GUSword *voiceptr;
+
+ unsigned int count;
+ for (count = 0; count < numsamples * 2; count++)
+ *(bufferpos + count) = 0; /* clear */
+
+ gusptr = state->gusdatapos;
+ voiceptr = (GUSword *) gusptr;
+ if (!(GUSregb(GUS4cReset) & 0x01)) /* reset flag active? */
+ return;
+
+ for (Voice = 0; Voice <= (GUSregb(NumVoices) & 31); Voice++)
+ {
+ if (GUSvoice(wVSRControl) & 0x200)
+ GUSvoice(wVSRControl) |= 0x100; /* voice stop request */
+ if (GUSvoice(wVSRVolRampControl) & 0x200)
+ GUSvoice(wVSRVolRampControl) |= 0x100; /* Volume ramp stop request */
+ if (!(GUSvoice(wVSRControl) & GUSvoice(wVSRVolRampControl) & 0x100)) /* neither voice nor volume calculation active - save some time here ;) */
+ {
+ unsigned int sample;
+
+ unsigned int LoopStart = (GUSvoice(wVSRLoopStartHi) << 16) | GUSvoice(wVSRLoopStartLo); /* 23.9 format */
+ unsigned int LoopEnd = (GUSvoice(wVSRLoopEndHi) << 16) | GUSvoice(wVSRLoopEndLo); /* 23.9 format */
+ unsigned int CurrPos = (GUSvoice(wVSRCurrPosHi) << 16) | GUSvoice(wVSRCurrPosLo); /* 23.9 format */
+ int VoiceIncrement = ((((unsigned long) GUSvoice(wVSRFreq) * 44100) / playback_freq) * (14 >> 1)) /
+ ((GUSregb(NumVoices) & 31) + 1); /* 6.10 increment/frame to 23.9 increment/sample */
+
+ int PanningPos = (GUSvoice(wVSRPanning) >> 8) & 0xf;
+
+ unsigned int Volume32 = 32 * GUSvoice(wVSRCurrVol); /* 32 times larger than original gus for maintaining precision while ramping */
+ unsigned int StartVol32 = (GUSvoice(wVSRVolRampStartVol) & 0xff00) * 32;
+ unsigned int EndVol32 = (GUSvoice(wVSRVolRampEndVol) & 0xff00) * 32;
+ int VolumeIncrement32 = (32 * 16 * (GUSvoice(wVSRVolRampRate) & 0x3f00) >> 8) >> ((((GUSvoice(wVSRVolRampRate) & 0xc000) >> 8) >> 6) * 3); /* including 1/8/64/512 volume speed divisor */
+ VolumeIncrement32 = (((VolumeIncrement32 * 44100 / 2) / playback_freq) * 14) / ((GUSregb(NumVoices) & 31) + 1); /* adjust ramping speed to playback speed */
+
+ if (GUSvoice(wVSRControl) & 0x4000)
+ VoiceIncrement = -VoiceIncrement; /* reverse playback */
+ if (GUSvoice(wVSRVolRampControl) & 0x4000)
+ VolumeIncrement32 = -VolumeIncrement32; /* reverse ramping */
+
+ for (sample = 0; sample < numsamples; sample++)
+ {
+ int sample1, sample2, Volume;
+ if (GUSvoice(wVSRControl) & 0x400) /* 16bit */
+ {
+ int offset = ((CurrPos >> 9) & 0xc0000) + (((CurrPos >> 9) & 0x1ffff) << 1);
+ GUSchar *adr;
+ adr = (GUSchar *) state->himemaddr + offset;
+ sample1 = (*adr & 0xff) + (*(adr + 1) * 256);
+ sample2 = (*(adr + 2) & 0xff) + (*(adr + 2 + 1) * 256);
+ }
+ else /* 8bit */
+ {
+ int offset = (CurrPos >> 9) & 0xfffff;
+ GUSchar *adr;
+ adr = (GUSchar *) state->himemaddr + offset;
+ sample1 = (*adr) * 256;
+ sample2 = (*(adr + 1)) * 256;
+ }
+
+ Volume = ((((Volume32 >> (4 + 5)) & 0xff) + 256) << (Volume32 >> ((4 + 8) + 5))) / 512; /* semi-logarithmic volume, +5 due to additional precision */
+ sample1 = (((sample1 * Volume) >> 16) * (512 - (CurrPos % 512))) / 512;
+ sample2 = (((sample2 * Volume) >> 16) * (CurrPos % 512)) / 512;
+ sample1 += sample2;
+
+ if (!(GUSvoice(wVSRVolRampControl) & 0x100))
+ {
+ Volume32 += VolumeIncrement32;
+ if ((GUSvoice(wVSRVolRampControl) & 0x4000) ? (Volume32 <= StartVol32) : (Volume32 >= EndVol32)) /* ramp up boundary cross */
+ {
+ if (GUSvoice(wVSRVolRampControl) & 0x2000)
+ GUSvoice(wVSRVolRampControl) |= 0x8000; /* volramp IRQ enabled? -> IRQ wait flag */
+ if (GUSvoice(wVSRVolRampControl) & 0x800) /* loop enabled */
+ {
+ if (GUSvoice(wVSRVolRampControl) & 0x1000) /* bidir. loop */
+ {
+ GUSvoice(wVSRVolRampControl) ^= 0x4000; /* toggle dir */
+ VolumeIncrement32 = -VolumeIncrement32;
+ }
+ else
+ Volume32 = (GUSvoice(wVSRVolRampControl) & 0x4000) ? EndVol32 : StartVol32; /* unidir. loop ramp */
+ }
+ else
+ {
+ GUSvoice(wVSRVolRampControl) |= 0x100;
+ Volume32 =
+ (GUSvoice(wVSRVolRampControl) & 0x4000) ? StartVol32 : EndVol32;
+ }
+ }
+ }
+ if ((GUSvoice(wVSRVolRampControl) & 0xa000) == 0xa000) /* volramp IRQ set and enabled? */
+ {
+ GUSregd(voicevolrampirq) |= 1 << Voice; /* set irq slot */
+ }
+ else
+ {
+ GUSregd(voicevolrampirq) &= (~(1 << Voice)); /* clear irq slot */
+ GUSvoice(wVSRVolRampControl) &= 0x7f00;
+ }
+
+ if (!(GUSvoice(wVSRControl) & 0x100))
+ {
+ CurrPos += VoiceIncrement;
+ if ((GUSvoice(wVSRControl) & 0x4000) ? (CurrPos <= LoopStart) : (CurrPos >= LoopEnd)) /* playback boundary cross */
+ {
+ if (GUSvoice(wVSRControl) & 0x2000)
+ GUSvoice(wVSRControl) |= 0x8000; /* voice IRQ enabled -> IRQ wait flag */
+ if (GUSvoice(wVSRControl) & 0x800) /* loop enabled */
+ {
+ if (GUSvoice(wVSRControl) & 0x1000) /* pingpong loop */
+ {
+ GUSvoice(wVSRControl) ^= 0x4000; /* toggle dir */
+ VoiceIncrement = -VoiceIncrement;
+ }
+ else
+ CurrPos = (GUSvoice(wVSRControl) & 0x4000) ? LoopEnd : LoopStart; /* unidir. loop */
+ }
+ else if (!(GUSvoice(wVSRVolRampControl) & 0x400))
+ GUSvoice(wVSRControl) |= 0x100; /* loop disabled, rollover check */
+ }
+ }
+ if ((GUSvoice(wVSRControl) & 0xa000) == 0xa000) /* wavetable IRQ set and enabled? */
+ {
+ GUSregd(voicewavetableirq) |= 1 << Voice; /* set irq slot */
+ }
+ else
+ {
+ GUSregd(voicewavetableirq) &= (~(1 << Voice)); /* clear irq slot */
+ GUSvoice(wVSRControl) &= 0x7f00;
+ }
+
+ /* mix samples into buffer */
+ *(bufferpos + 2 * sample) += (GUSsample) ((sample1 * PanningPos) >> 4); /* right */
+ *(bufferpos + 2 * sample + 1) += (GUSsample) ((sample1 * (15 - PanningPos)) >> 4); /* left */
+ }
+ /* write back voice and volume */
+ GUSvoice(wVSRCurrVol) = Volume32 / 32;
+ GUSvoice(wVSRCurrPosHi) = CurrPos >> 16;
+ GUSvoice(wVSRCurrPosLo) = CurrPos & 0xffff;
+ }
+ voiceptr += 16; /* next voice */
+ }
+}
+
+void gus_irqgen(GUSEmuState * state, unsigned int elapsed_time)
+/* time given in microseconds */
+{
+ int requestedIRQs = 0;
+ GUSbyte *gusptr;
+ gusptr = state->gusdatapos;
+ if (GUSregb(TimerDataReg2x9) & 1) /* start timer 1 (80us decrement rate) */
+ {
+ unsigned int timer1fraction = state->timer1fraction;
+ int newtimerirqs;
+ newtimerirqs = (elapsed_time + timer1fraction) / (80 * (256 - GUSregb(GUS46Counter1)));
+ state->timer1fraction = (elapsed_time + timer1fraction) % (80 * (256 - GUSregb(GUS46Counter1)));
+ if (newtimerirqs)
+ {
+ if (!(GUSregb(TimerDataReg2x9) & 0x40))
+ GUSregb(TimerStatus2x8) |= 0xc0; /* maskable bits */
+ if (GUSregb(GUS45TimerCtrl) & 4) /* timer1 irq enable */
+ {
+ GUSregb(TimerStatus2x8) |= 4; /* nonmaskable bit */
+ GUSregb(IRQStatReg2x6) |= 4; /* timer 1 irq pending */
+ GUSregw(TimerIRQs) += newtimerirqs;
+ requestedIRQs += newtimerirqs;
+ }
+ }
+ }
+ if (GUSregb(TimerDataReg2x9) & 2) /* start timer 2 (320us decrement rate) */
+ {
+ unsigned int timer2fraction = state->timer2fraction;
+ int newtimerirqs;
+ newtimerirqs = (elapsed_time + timer2fraction) / (320 * (256 - GUSregb(GUS47Counter2)));
+ state->timer2fraction = (elapsed_time + timer2fraction) % (320 * (256 - GUSregb(GUS47Counter2)));
+ if (newtimerirqs)
+ {
+ if (!(GUSregb(TimerDataReg2x9) & 0x20))
+ GUSregb(TimerStatus2x8) |= 0xa0; /* maskable bits */
+ if (GUSregb(GUS45TimerCtrl) & 8) /* timer2 irq enable */
+ {
+ GUSregb(TimerStatus2x8) |= 2; /* nonmaskable bit */
+ GUSregb(IRQStatReg2x6) |= 8; /* timer 2 irq pending */
+ GUSregw(TimerIRQs) += newtimerirqs;
+ requestedIRQs += newtimerirqs;
+ }
+ }
+ }
+ if (GUSregb(GUS4cReset) & 0x4) /* synth IRQ enable */
+ {
+ if (GUSregd(voicewavetableirq))
+ GUSregb(IRQStatReg2x6) |= 0x20;
+ if (GUSregd(voicevolrampirq))
+ GUSregb(IRQStatReg2x6) |= 0x40;
+ }
+ if ((!requestedIRQs) && GUSregb(IRQStatReg2x6))
+ requestedIRQs++;
+ if (GUSregb(IRQStatReg2x6))
+ GUSregw(BusyTimerIRQs) = GUS_irqrequest(state, state->gusirq, requestedIRQs);
+}
diff --git a/hw/audio/gustate.h b/hw/audio/gustate.h
new file mode 100644
index 00000000..ece903ab
--- /dev/null
+++ b/hw/audio/gustate.h
@@ -0,0 +1,132 @@
+/*
+ * GUSEMU32 - persistent GUS register state
+ *
+ * Copyright (C) 2000-2007 Tibor "TS" Schütz
+ *
+ * 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.
+ */
+
+#ifndef GUSTATE_H
+#define GUSTATE_H
+
+/*state block offset*/
+#define gusdata (0)
+
+/* data stored using this structure is in host byte order! */
+
+/*access type*/
+#define PortRead (0)
+#define PortWrite (1)
+
+#define Port8Bitacc (0)
+#define Port16Bitacc (1)
+
+/*voice register offsets (in bytes)*/
+#define VSRegs (0)
+#define VSRControl (0)
+#define VSRegsEnd (VSRControl+VSRegs + 32*(16*2))
+#define VSRFreq (2)
+#define VSRLoopStartHi (4)
+#define VSRLoopStartLo (6)
+#define VSRLoopEndHi (8)
+#define VSRLoopEndLo (10)
+#define VSRVolRampRate (12)
+#define VSRVolRampStartVol (14)
+#define VSRVolRampEndVol (16)
+#define VSRCurrVol (18)
+#define VSRCurrPosHi (20)
+#define VSRCurrPosLo (22)
+#define VSRPanning (24)
+#define VSRVolRampControl (26)
+
+/*voice register offsets (in words)*/
+#define wVSRegs (0)
+#define wVSRControl (0)
+#define wVSRegsEnd (wVSRControl+wVSRegs + 32*(16))
+#define wVSRFreq (1)
+#define wVSRLoopStartHi (2)
+#define wVSRLoopStartLo (3)
+#define wVSRLoopEndHi (4)
+#define wVSRLoopEndLo (5)
+#define wVSRVolRampRate (6)
+#define wVSRVolRampStartVol (7)
+#define wVSRVolRampEndVol (8)
+#define wVSRCurrVol (9)
+#define wVSRCurrPosHi (10)
+#define wVSRCurrPosLo (11)
+#define wVSRPanning (12)
+#define wVSRVolRampControl (13)
+
+/*GUS register state block: 32 voices, padding filled with remaining registers*/
+#define DataRegLoByte3x4 (VSRVolRampControl+2)
+#define DataRegWord3x4 (DataRegLoByte3x4)
+#define DataRegHiByte3x5 (VSRVolRampControl+2 +1)
+#define DMA_2xB (VSRVolRampControl+2+2)
+#define IRQ_2xB (VSRVolRampControl+2+3)
+
+#define RegCtrl_2xF (VSRVolRampControl+2+(16*2))
+#define Jumper_2xB (VSRVolRampControl+2+(16*2)+1)
+#define GUS42DMAStart (VSRVolRampControl+2+(16*2)+2)
+
+#define GUS43DRAMIOlo (VSRVolRampControl+2+(16*2)*2)
+#define GUSDRAMPOS24bit (GUS43DRAMIOlo)
+#define GUS44DRAMIOhi (VSRVolRampControl+2+(16*2)*2+2)
+
+#define voicewavetableirq (VSRVolRampControl+2+(16*2)*3) /* voice IRQ pseudoqueue: 1 bit per voice */
+
+#define voicevolrampirq (VSRVolRampControl+2+(16*2)*4) /* voice IRQ pseudoqueue: 1 bit per voice */
+
+#define startvoices (VSRVolRampControl+2+(16*2)*5) /* statistics / optimizations */
+
+#define IRQStatReg2x6 (VSRVolRampControl+2+(16*2)*6)
+#define TimerStatus2x8 (VSRVolRampControl+2+(16*2)*6+1)
+#define TimerDataReg2x9 (VSRVolRampControl+2+(16*2)*6+2)
+#define MixerCtrlReg2x0 (VSRVolRampControl+2+(16*2)*6+3)
+
+#define VoiceSelReg3x2 (VSRVolRampControl+2+(16*2)*7)
+#define FunkSelReg3x3 (VSRVolRampControl+2+(16*2)*7+1)
+#define AdLibStatus2x8 (VSRVolRampControl+2+(16*2)*7+2)
+#define StatRead_2xF (VSRVolRampControl+2+(16*2)*7+3)
+
+#define GUS48SampSpeed (VSRVolRampControl+2+(16*2)*8)
+#define GUS41DMACtrl (VSRVolRampControl+2+(16*2)*8+1)
+#define GUS45TimerCtrl (VSRVolRampControl+2+(16*2)*8+2)
+#define GUS46Counter1 (VSRVolRampControl+2+(16*2)*8+3)
+
+#define GUS47Counter2 (VSRVolRampControl+2+(16*2)*9)
+#define GUS49SampCtrl (VSRVolRampControl+2+(16*2)*9+1)
+#define GUS4cReset (VSRVolRampControl+2+(16*2)*9+2)
+#define NumVoices (VSRVolRampControl+2+(16*2)*9+3)
+
+#define TimerIRQs (VSRVolRampControl+2+(16*2)*10) /* delayed IRQ, statistics */
+#define BusyTimerIRQs (VSRVolRampControl+2+(16*2)*10+2) /* delayed IRQ, statistics */
+
+#define AdLibCommand2xA (VSRVolRampControl+2+(16*2)*11)
+#define AdLibData2x9 (VSRVolRampControl+2+(16*2)*11+1)
+#define SB2xCd (VSRVolRampControl+2+(16*2)*11+2)
+#define SB2xE (VSRVolRampControl+2+(16*2)*11+3)
+
+#define SynVoiceIRQ8f (VSRVolRampControl+2+(16*2)*12)
+#define GUS50DMAHigh (VSRVolRampControl+2+(16*2)*12+1)
+
+#define portaccesses (VSRegsEnd) /* statistics / suspend mode */
+
+#define gusdataend (VSRegsEnd+4)
+
+#endif /* gustate.h */
diff --git a/hw/audio/hda-codec-common.h b/hw/audio/hda-codec-common.h
new file mode 100644
index 00000000..b4fdb51e
--- /dev/null
+++ b/hw/audio/hda-codec-common.h
@@ -0,0 +1,456 @@
+/*
+ * Common code to disable/enable mixer emulation at run time
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * Written by Bandan Das <bsd@redhat.com>
+ * with important bits picked up from hda-codec.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * HDA codec descriptions
+ */
+
+#ifdef HDA_MIXER
+#define QEMU_HDA_ID_OUTPUT ((QEMU_HDA_ID_VENDOR << 16) | 0x12)
+#define QEMU_HDA_ID_DUPLEX ((QEMU_HDA_ID_VENDOR << 16) | 0x22)
+#define QEMU_HDA_ID_MICRO ((QEMU_HDA_ID_VENDOR << 16) | 0x32)
+#define QEMU_HDA_AMP_CAPS \
+ (AC_AMPCAP_MUTE | \
+ (QEMU_HDA_AMP_STEPS << AC_AMPCAP_OFFSET_SHIFT) | \
+ (QEMU_HDA_AMP_STEPS << AC_AMPCAP_NUM_STEPS_SHIFT) | \
+ (3 << AC_AMPCAP_STEP_SIZE_SHIFT))
+#else
+#define QEMU_HDA_ID_OUTPUT ((QEMU_HDA_ID_VENDOR << 16) | 0x11)
+#define QEMU_HDA_ID_DUPLEX ((QEMU_HDA_ID_VENDOR << 16) | 0x21)
+#define QEMU_HDA_ID_MICRO ((QEMU_HDA_ID_VENDOR << 16) | 0x31)
+#define QEMU_HDA_AMP_CAPS QEMU_HDA_AMP_NONE
+#endif
+
+
+/* common: audio output widget */
+static const desc_param glue(common_params_audio_dac_, PARAM)[] = {
+ {
+ .id = AC_PAR_AUDIO_WIDGET_CAP,
+ .val = ((AC_WID_AUD_OUT << AC_WCAP_TYPE_SHIFT) |
+ AC_WCAP_FORMAT_OVRD |
+ AC_WCAP_AMP_OVRD |
+ AC_WCAP_OUT_AMP |
+ AC_WCAP_STEREO),
+ },{
+ .id = AC_PAR_PCM,
+ .val = QEMU_HDA_PCM_FORMATS,
+ },{
+ .id = AC_PAR_STREAM,
+ .val = AC_SUPFMT_PCM,
+ },{
+ .id = AC_PAR_AMP_IN_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_AMP_OUT_CAP,
+ .val = QEMU_HDA_AMP_CAPS,
+ },
+};
+
+/* common: audio input widget */
+static const desc_param glue(common_params_audio_adc_, PARAM)[] = {
+ {
+ .id = AC_PAR_AUDIO_WIDGET_CAP,
+ .val = ((AC_WID_AUD_IN << AC_WCAP_TYPE_SHIFT) |
+ AC_WCAP_CONN_LIST |
+ AC_WCAP_FORMAT_OVRD |
+ AC_WCAP_AMP_OVRD |
+ AC_WCAP_IN_AMP |
+ AC_WCAP_STEREO),
+ },{
+ .id = AC_PAR_CONNLIST_LEN,
+ .val = 1,
+ },{
+ .id = AC_PAR_PCM,
+ .val = QEMU_HDA_PCM_FORMATS,
+ },{
+ .id = AC_PAR_STREAM,
+ .val = AC_SUPFMT_PCM,
+ },{
+ .id = AC_PAR_AMP_IN_CAP,
+ .val = QEMU_HDA_AMP_CAPS,
+ },{
+ .id = AC_PAR_AMP_OUT_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },
+};
+
+/* common: pin widget (line-out) */
+static const desc_param glue(common_params_audio_lineout_, PARAM)[] = {
+ {
+ .id = AC_PAR_AUDIO_WIDGET_CAP,
+ .val = ((AC_WID_PIN << AC_WCAP_TYPE_SHIFT) |
+ AC_WCAP_CONN_LIST |
+ AC_WCAP_STEREO),
+ },{
+ .id = AC_PAR_PIN_CAP,
+ .val = AC_PINCAP_OUT,
+ },{
+ .id = AC_PAR_CONNLIST_LEN,
+ .val = 1,
+ },{
+ .id = AC_PAR_AMP_IN_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_AMP_OUT_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },
+};
+
+/* common: pin widget (line-in) */
+static const desc_param glue(common_params_audio_linein_, PARAM)[] = {
+ {
+ .id = AC_PAR_AUDIO_WIDGET_CAP,
+ .val = ((AC_WID_PIN << AC_WCAP_TYPE_SHIFT) |
+ AC_WCAP_STEREO),
+ },{
+ .id = AC_PAR_PIN_CAP,
+ .val = AC_PINCAP_IN,
+ },{
+ .id = AC_PAR_AMP_IN_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_AMP_OUT_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },
+};
+
+/* output: root node */
+static const desc_param glue(output_params_root_, PARAM)[] = {
+ {
+ .id = AC_PAR_VENDOR_ID,
+ .val = QEMU_HDA_ID_OUTPUT,
+ },{
+ .id = AC_PAR_SUBSYSTEM_ID,
+ .val = QEMU_HDA_ID_OUTPUT,
+ },{
+ .id = AC_PAR_REV_ID,
+ .val = 0x00100101,
+ },{
+ .id = AC_PAR_NODE_COUNT,
+ .val = 0x00010001,
+ },
+};
+
+/* output: audio function */
+static const desc_param glue(output_params_audio_func_, PARAM)[] = {
+ {
+ .id = AC_PAR_FUNCTION_TYPE,
+ .val = AC_GRP_AUDIO_FUNCTION,
+ },{
+ .id = AC_PAR_SUBSYSTEM_ID,
+ .val = QEMU_HDA_ID_OUTPUT,
+ },{
+ .id = AC_PAR_NODE_COUNT,
+ .val = 0x00020002,
+ },{
+ .id = AC_PAR_PCM,
+ .val = QEMU_HDA_PCM_FORMATS,
+ },{
+ .id = AC_PAR_STREAM,
+ .val = AC_SUPFMT_PCM,
+ },{
+ .id = AC_PAR_AMP_IN_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_AMP_OUT_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_GPIO_CAP,
+ .val = 0,
+ },{
+ .id = AC_PAR_AUDIO_FG_CAP,
+ .val = 0x00000808,
+ },{
+ .id = AC_PAR_POWER_STATE,
+ .val = 0,
+ },
+};
+
+/* output: nodes */
+static const desc_node glue(output_nodes_, PARAM)[] = {
+ {
+ .nid = AC_NODE_ROOT,
+ .name = "root",
+ .params = glue(output_params_root_, PARAM),
+ .nparams = ARRAY_SIZE(glue(output_params_root_, PARAM)),
+ },{
+ .nid = 1,
+ .name = "func",
+ .params = glue(output_params_audio_func_, PARAM),
+ .nparams = ARRAY_SIZE(glue(output_params_audio_func_, PARAM)),
+ },{
+ .nid = 2,
+ .name = "dac",
+ .params = glue(common_params_audio_dac_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_dac_, PARAM)),
+ .stindex = 0,
+ },{
+ .nid = 3,
+ .name = "out",
+ .params = glue(common_params_audio_lineout_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_lineout_, PARAM)),
+ .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) |
+ (AC_JACK_LINE_OUT << AC_DEFCFG_DEVICE_SHIFT) |
+ (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) |
+ (AC_JACK_COLOR_GREEN << AC_DEFCFG_COLOR_SHIFT) |
+ 0x10),
+ .pinctl = AC_PINCTL_OUT_EN,
+ .conn = (uint32_t[]) { 2 },
+ }
+};
+
+/* output: codec */
+static const desc_codec glue(output_, PARAM) = {
+ .name = "output",
+ .iid = QEMU_HDA_ID_OUTPUT,
+ .nodes = glue(output_nodes_, PARAM),
+ .nnodes = ARRAY_SIZE(glue(output_nodes_, PARAM)),
+};
+
+/* duplex: root node */
+static const desc_param glue(duplex_params_root_, PARAM)[] = {
+ {
+ .id = AC_PAR_VENDOR_ID,
+ .val = QEMU_HDA_ID_DUPLEX,
+ },{
+ .id = AC_PAR_SUBSYSTEM_ID,
+ .val = QEMU_HDA_ID_DUPLEX,
+ },{
+ .id = AC_PAR_REV_ID,
+ .val = 0x00100101,
+ },{
+ .id = AC_PAR_NODE_COUNT,
+ .val = 0x00010001,
+ },
+};
+
+/* duplex: audio function */
+static const desc_param glue(duplex_params_audio_func_, PARAM)[] = {
+ {
+ .id = AC_PAR_FUNCTION_TYPE,
+ .val = AC_GRP_AUDIO_FUNCTION,
+ },{
+ .id = AC_PAR_SUBSYSTEM_ID,
+ .val = QEMU_HDA_ID_DUPLEX,
+ },{
+ .id = AC_PAR_NODE_COUNT,
+ .val = 0x00020004,
+ },{
+ .id = AC_PAR_PCM,
+ .val = QEMU_HDA_PCM_FORMATS,
+ },{
+ .id = AC_PAR_STREAM,
+ .val = AC_SUPFMT_PCM,
+ },{
+ .id = AC_PAR_AMP_IN_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_AMP_OUT_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_GPIO_CAP,
+ .val = 0,
+ },{
+ .id = AC_PAR_AUDIO_FG_CAP,
+ .val = 0x00000808,
+ },{
+ .id = AC_PAR_POWER_STATE,
+ .val = 0,
+ },
+};
+
+/* duplex: nodes */
+static const desc_node glue(duplex_nodes_, PARAM)[] = {
+ {
+ .nid = AC_NODE_ROOT,
+ .name = "root",
+ .params = glue(duplex_params_root_, PARAM),
+ .nparams = ARRAY_SIZE(glue(duplex_params_root_, PARAM)),
+ },{
+ .nid = 1,
+ .name = "func",
+ .params = glue(duplex_params_audio_func_, PARAM),
+ .nparams = ARRAY_SIZE(glue(duplex_params_audio_func_, PARAM)),
+ },{
+ .nid = 2,
+ .name = "dac",
+ .params = glue(common_params_audio_dac_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_dac_, PARAM)),
+ .stindex = 0,
+ },{
+ .nid = 3,
+ .name = "out",
+ .params = glue(common_params_audio_lineout_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_lineout_, PARAM)),
+ .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) |
+ (AC_JACK_LINE_OUT << AC_DEFCFG_DEVICE_SHIFT) |
+ (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) |
+ (AC_JACK_COLOR_GREEN << AC_DEFCFG_COLOR_SHIFT) |
+ 0x10),
+ .pinctl = AC_PINCTL_OUT_EN,
+ .conn = (uint32_t[]) { 2 },
+ },{
+ .nid = 4,
+ .name = "adc",
+ .params = glue(common_params_audio_adc_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_adc_, PARAM)),
+ .stindex = 1,
+ .conn = (uint32_t[]) { 5 },
+ },{
+ .nid = 5,
+ .name = "in",
+ .params = glue(common_params_audio_linein_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_linein_, PARAM)),
+ .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) |
+ (AC_JACK_LINE_IN << AC_DEFCFG_DEVICE_SHIFT) |
+ (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) |
+ (AC_JACK_COLOR_RED << AC_DEFCFG_COLOR_SHIFT) |
+ 0x20),
+ .pinctl = AC_PINCTL_IN_EN,
+ }
+};
+
+/* duplex: codec */
+static const desc_codec glue(duplex_, PARAM) = {
+ .name = "duplex",
+ .iid = QEMU_HDA_ID_DUPLEX,
+ .nodes = glue(duplex_nodes_, PARAM),
+ .nnodes = ARRAY_SIZE(glue(duplex_nodes_, PARAM)),
+};
+
+/* micro: root node */
+static const desc_param glue(micro_params_root_, PARAM)[] = {
+ {
+ .id = AC_PAR_VENDOR_ID,
+ .val = QEMU_HDA_ID_MICRO,
+ },{
+ .id = AC_PAR_SUBSYSTEM_ID,
+ .val = QEMU_HDA_ID_MICRO,
+ },{
+ .id = AC_PAR_REV_ID,
+ .val = 0x00100101,
+ },{
+ .id = AC_PAR_NODE_COUNT,
+ .val = 0x00010001,
+ },
+};
+
+/* micro: audio function */
+static const desc_param glue(micro_params_audio_func_, PARAM)[] = {
+ {
+ .id = AC_PAR_FUNCTION_TYPE,
+ .val = AC_GRP_AUDIO_FUNCTION,
+ },{
+ .id = AC_PAR_SUBSYSTEM_ID,
+ .val = QEMU_HDA_ID_MICRO,
+ },{
+ .id = AC_PAR_NODE_COUNT,
+ .val = 0x00020004,
+ },{
+ .id = AC_PAR_PCM,
+ .val = QEMU_HDA_PCM_FORMATS,
+ },{
+ .id = AC_PAR_STREAM,
+ .val = AC_SUPFMT_PCM,
+ },{
+ .id = AC_PAR_AMP_IN_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_AMP_OUT_CAP,
+ .val = QEMU_HDA_AMP_NONE,
+ },{
+ .id = AC_PAR_GPIO_CAP,
+ .val = 0,
+ },{
+ .id = AC_PAR_AUDIO_FG_CAP,
+ .val = 0x00000808,
+ },{
+ .id = AC_PAR_POWER_STATE,
+ .val = 0,
+ },
+};
+
+/* micro: nodes */
+static const desc_node glue(micro_nodes_, PARAM)[] = {
+ {
+ .nid = AC_NODE_ROOT,
+ .name = "root",
+ .params = glue(micro_params_root_, PARAM),
+ .nparams = ARRAY_SIZE(glue(micro_params_root_, PARAM)),
+ },{
+ .nid = 1,
+ .name = "func",
+ .params = glue(micro_params_audio_func_, PARAM),
+ .nparams = ARRAY_SIZE(glue(micro_params_audio_func_, PARAM)),
+ },{
+ .nid = 2,
+ .name = "dac",
+ .params = glue(common_params_audio_dac_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_dac_, PARAM)),
+ .stindex = 0,
+ },{
+ .nid = 3,
+ .name = "out",
+ .params = glue(common_params_audio_lineout_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_lineout_, PARAM)),
+ .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) |
+ (AC_JACK_SPEAKER << AC_DEFCFG_DEVICE_SHIFT) |
+ (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) |
+ (AC_JACK_COLOR_GREEN << AC_DEFCFG_COLOR_SHIFT) |
+ 0x10),
+ .pinctl = AC_PINCTL_OUT_EN,
+ .conn = (uint32_t[]) { 2 },
+ },{
+ .nid = 4,
+ .name = "adc",
+ .params = glue(common_params_audio_adc_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_adc_, PARAM)),
+ .stindex = 1,
+ .conn = (uint32_t[]) { 5 },
+ },{
+ .nid = 5,
+ .name = "in",
+ .params = glue(common_params_audio_linein_, PARAM),
+ .nparams = ARRAY_SIZE(glue(common_params_audio_linein_, PARAM)),
+ .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) |
+ (AC_JACK_MIC_IN << AC_DEFCFG_DEVICE_SHIFT) |
+ (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) |
+ (AC_JACK_COLOR_RED << AC_DEFCFG_COLOR_SHIFT) |
+ 0x20),
+ .pinctl = AC_PINCTL_IN_EN,
+ }
+};
+
+/* micro: codec */
+static const desc_codec glue(micro_, PARAM) = {
+ .name = "micro",
+ .iid = QEMU_HDA_ID_MICRO,
+ .nodes = glue(micro_nodes_, PARAM),
+ .nnodes = ARRAY_SIZE(glue(micro_nodes_, PARAM)),
+};
+
+#undef PARAM
+#undef HDA_MIXER
+#undef QEMU_HDA_ID_OUTPUT
+#undef QEMU_HDA_ID_DUPLEX
+#undef QEMU_HDA_ID_MICRO
+#undef QEMU_HDA_AMP_CAPS
diff --git a/hw/audio/hda-codec.c b/hw/audio/hda-codec.c
new file mode 100644
index 00000000..3c03ff56
--- /dev/null
+++ b/hw/audio/hda-codec.c
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * written by Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "intel-hda.h"
+#include "intel-hda-defs.h"
+#include "audio/audio.h"
+
+/* -------------------------------------------------------------------------- */
+
+typedef struct desc_param {
+ uint32_t id;
+ uint32_t val;
+} desc_param;
+
+typedef struct desc_node {
+ uint32_t nid;
+ const char *name;
+ const desc_param *params;
+ uint32_t nparams;
+ uint32_t config;
+ uint32_t pinctl;
+ uint32_t *conn;
+ uint32_t stindex;
+} desc_node;
+
+typedef struct desc_codec {
+ const char *name;
+ uint32_t iid;
+ const desc_node *nodes;
+ uint32_t nnodes;
+} desc_codec;
+
+static const desc_param* hda_codec_find_param(const desc_node *node, uint32_t id)
+{
+ int i;
+
+ for (i = 0; i < node->nparams; i++) {
+ if (node->params[i].id == id) {
+ return &node->params[i];
+ }
+ }
+ return NULL;
+}
+
+static const desc_node* hda_codec_find_node(const desc_codec *codec, uint32_t nid)
+{
+ int i;
+
+ for (i = 0; i < codec->nnodes; i++) {
+ if (codec->nodes[i].nid == nid) {
+ return &codec->nodes[i];
+ }
+ }
+ return NULL;
+}
+
+static void hda_codec_parse_fmt(uint32_t format, struct audsettings *as)
+{
+ if (format & AC_FMT_TYPE_NON_PCM) {
+ return;
+ }
+
+ as->freq = (format & AC_FMT_BASE_44K) ? 44100 : 48000;
+
+ switch ((format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT) {
+ case 1: as->freq *= 2; break;
+ case 2: as->freq *= 3; break;
+ case 3: as->freq *= 4; break;
+ }
+
+ switch ((format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT) {
+ case 1: as->freq /= 2; break;
+ case 2: as->freq /= 3; break;
+ case 3: as->freq /= 4; break;
+ case 4: as->freq /= 5; break;
+ case 5: as->freq /= 6; break;
+ case 6: as->freq /= 7; break;
+ case 7: as->freq /= 8; break;
+ }
+
+ switch (format & AC_FMT_BITS_MASK) {
+ case AC_FMT_BITS_8: as->fmt = AUD_FMT_S8; break;
+ case AC_FMT_BITS_16: as->fmt = AUD_FMT_S16; break;
+ case AC_FMT_BITS_32: as->fmt = AUD_FMT_S32; break;
+ }
+
+ as->nchannels = ((format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT) + 1;
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * HDA codec descriptions
+ */
+
+/* some defines */
+
+#define QEMU_HDA_ID_VENDOR 0x1af4
+#define QEMU_HDA_PCM_FORMATS (AC_SUPPCM_BITS_16 | \
+ 0x1fc /* 16 -> 96 kHz */)
+#define QEMU_HDA_AMP_NONE (0)
+#define QEMU_HDA_AMP_STEPS 0x4a
+
+#define PARAM mixemu
+#define HDA_MIXER
+#include "hda-codec-common.h"
+
+#define PARAM nomixemu
+#include "hda-codec-common.h"
+
+/* -------------------------------------------------------------------------- */
+
+static const char *fmt2name[] = {
+ [ AUD_FMT_U8 ] = "PCM-U8",
+ [ AUD_FMT_S8 ] = "PCM-S8",
+ [ AUD_FMT_U16 ] = "PCM-U16",
+ [ AUD_FMT_S16 ] = "PCM-S16",
+ [ AUD_FMT_U32 ] = "PCM-U32",
+ [ AUD_FMT_S32 ] = "PCM-S32",
+};
+
+typedef struct HDAAudioState HDAAudioState;
+typedef struct HDAAudioStream HDAAudioStream;
+
+struct HDAAudioStream {
+ HDAAudioState *state;
+ const desc_node *node;
+ bool output, running;
+ uint32_t stream;
+ uint32_t channel;
+ uint32_t format;
+ uint32_t gain_left, gain_right;
+ bool mute_left, mute_right;
+ struct audsettings as;
+ union {
+ SWVoiceIn *in;
+ SWVoiceOut *out;
+ } voice;
+ uint8_t buf[HDA_BUFFER_SIZE];
+ uint32_t bpos;
+};
+
+#define TYPE_HDA_AUDIO "hda-audio"
+#define HDA_AUDIO(obj) OBJECT_CHECK(HDAAudioState, (obj), TYPE_HDA_AUDIO)
+
+struct HDAAudioState {
+ HDACodecDevice hda;
+ const char *name;
+
+ QEMUSoundCard card;
+ const desc_codec *desc;
+ HDAAudioStream st[4];
+ bool running_compat[16];
+ bool running_real[2 * 16];
+
+ /* properties */
+ uint32_t debug;
+ bool mixer;
+};
+
+static void hda_audio_input_cb(void *opaque, int avail)
+{
+ HDAAudioStream *st = opaque;
+ int recv = 0;
+ int len;
+ bool rc;
+
+ while (avail - recv >= sizeof(st->buf)) {
+ if (st->bpos != sizeof(st->buf)) {
+ len = AUD_read(st->voice.in, st->buf + st->bpos,
+ sizeof(st->buf) - st->bpos);
+ st->bpos += len;
+ recv += len;
+ if (st->bpos != sizeof(st->buf)) {
+ break;
+ }
+ }
+ rc = hda_codec_xfer(&st->state->hda, st->stream, false,
+ st->buf, sizeof(st->buf));
+ if (!rc) {
+ break;
+ }
+ st->bpos = 0;
+ }
+}
+
+static void hda_audio_output_cb(void *opaque, int avail)
+{
+ HDAAudioStream *st = opaque;
+ int sent = 0;
+ int len;
+ bool rc;
+
+ while (avail - sent >= sizeof(st->buf)) {
+ if (st->bpos == sizeof(st->buf)) {
+ rc = hda_codec_xfer(&st->state->hda, st->stream, true,
+ st->buf, sizeof(st->buf));
+ if (!rc) {
+ break;
+ }
+ st->bpos = 0;
+ }
+ len = AUD_write(st->voice.out, st->buf + st->bpos,
+ sizeof(st->buf) - st->bpos);
+ st->bpos += len;
+ sent += len;
+ if (st->bpos != sizeof(st->buf)) {
+ break;
+ }
+ }
+}
+
+static void hda_audio_set_running(HDAAudioStream *st, bool running)
+{
+ if (st->node == NULL) {
+ return;
+ }
+ if (st->running == running) {
+ return;
+ }
+ st->running = running;
+ dprint(st->state, 1, "%s: %s (stream %d)\n", st->node->name,
+ st->running ? "on" : "off", st->stream);
+ if (st->output) {
+ AUD_set_active_out(st->voice.out, st->running);
+ } else {
+ AUD_set_active_in(st->voice.in, st->running);
+ }
+}
+
+static void hda_audio_set_amp(HDAAudioStream *st)
+{
+ bool muted;
+ uint32_t left, right;
+
+ if (st->node == NULL) {
+ return;
+ }
+
+ muted = st->mute_left && st->mute_right;
+ left = st->mute_left ? 0 : st->gain_left;
+ right = st->mute_right ? 0 : st->gain_right;
+
+ left = left * 255 / QEMU_HDA_AMP_STEPS;
+ right = right * 255 / QEMU_HDA_AMP_STEPS;
+
+ if (!st->state->mixer) {
+ return;
+ }
+ if (st->output) {
+ AUD_set_volume_out(st->voice.out, muted, left, right);
+ } else {
+ AUD_set_volume_in(st->voice.in, muted, left, right);
+ }
+}
+
+static void hda_audio_setup(HDAAudioStream *st)
+{
+ if (st->node == NULL) {
+ return;
+ }
+
+ dprint(st->state, 1, "%s: format: %d x %s @ %d Hz\n",
+ st->node->name, st->as.nchannels,
+ fmt2name[st->as.fmt], st->as.freq);
+
+ if (st->output) {
+ st->voice.out = AUD_open_out(&st->state->card, st->voice.out,
+ st->node->name, st,
+ hda_audio_output_cb, &st->as);
+ } else {
+ st->voice.in = AUD_open_in(&st->state->card, st->voice.in,
+ st->node->name, st,
+ hda_audio_input_cb, &st->as);
+ }
+}
+
+static void hda_audio_command(HDACodecDevice *hda, uint32_t nid, uint32_t data)
+{
+ HDAAudioState *a = HDA_AUDIO(hda);
+ HDAAudioStream *st;
+ const desc_node *node = NULL;
+ const desc_param *param;
+ uint32_t verb, payload, response, count, shift;
+
+ if ((data & 0x70000) == 0x70000) {
+ /* 12/8 id/payload */
+ verb = (data >> 8) & 0xfff;
+ payload = data & 0x00ff;
+ } else {
+ /* 4/16 id/payload */
+ verb = (data >> 8) & 0xf00;
+ payload = data & 0xffff;
+ }
+
+ node = hda_codec_find_node(a->desc, nid);
+ if (node == NULL) {
+ goto fail;
+ }
+ dprint(a, 2, "%s: nid %d (%s), verb 0x%x, payload 0x%x\n",
+ __FUNCTION__, nid, node->name, verb, payload);
+
+ switch (verb) {
+ /* all nodes */
+ case AC_VERB_PARAMETERS:
+ param = hda_codec_find_param(node, payload);
+ if (param == NULL) {
+ goto fail;
+ }
+ hda_codec_response(hda, true, param->val);
+ break;
+ case AC_VERB_GET_SUBSYSTEM_ID:
+ hda_codec_response(hda, true, a->desc->iid);
+ break;
+
+ /* all functions */
+ case AC_VERB_GET_CONNECT_LIST:
+ param = hda_codec_find_param(node, AC_PAR_CONNLIST_LEN);
+ count = param ? param->val : 0;
+ response = 0;
+ shift = 0;
+ while (payload < count && shift < 32) {
+ response |= node->conn[payload] << shift;
+ payload++;
+ shift += 8;
+ }
+ hda_codec_response(hda, true, response);
+ break;
+
+ /* pin widget */
+ case AC_VERB_GET_CONFIG_DEFAULT:
+ hda_codec_response(hda, true, node->config);
+ break;
+ case AC_VERB_GET_PIN_WIDGET_CONTROL:
+ hda_codec_response(hda, true, node->pinctl);
+ break;
+ case AC_VERB_SET_PIN_WIDGET_CONTROL:
+ if (node->pinctl != payload) {
+ dprint(a, 1, "unhandled pin control bit\n");
+ }
+ hda_codec_response(hda, true, 0);
+ break;
+
+ /* audio in/out widget */
+ case AC_VERB_SET_CHANNEL_STREAMID:
+ st = a->st + node->stindex;
+ if (st->node == NULL) {
+ goto fail;
+ }
+ hda_audio_set_running(st, false);
+ st->stream = (payload >> 4) & 0x0f;
+ st->channel = payload & 0x0f;
+ dprint(a, 2, "%s: stream %d, channel %d\n",
+ st->node->name, st->stream, st->channel);
+ hda_audio_set_running(st, a->running_real[st->output * 16 + st->stream]);
+ hda_codec_response(hda, true, 0);
+ break;
+ case AC_VERB_GET_CONV:
+ st = a->st + node->stindex;
+ if (st->node == NULL) {
+ goto fail;
+ }
+ response = st->stream << 4 | st->channel;
+ hda_codec_response(hda, true, response);
+ break;
+ case AC_VERB_SET_STREAM_FORMAT:
+ st = a->st + node->stindex;
+ if (st->node == NULL) {
+ goto fail;
+ }
+ st->format = payload;
+ hda_codec_parse_fmt(st->format, &st->as);
+ hda_audio_setup(st);
+ hda_codec_response(hda, true, 0);
+ break;
+ case AC_VERB_GET_STREAM_FORMAT:
+ st = a->st + node->stindex;
+ if (st->node == NULL) {
+ goto fail;
+ }
+ hda_codec_response(hda, true, st->format);
+ break;
+ case AC_VERB_GET_AMP_GAIN_MUTE:
+ st = a->st + node->stindex;
+ if (st->node == NULL) {
+ goto fail;
+ }
+ if (payload & AC_AMP_GET_LEFT) {
+ response = st->gain_left | (st->mute_left ? AC_AMP_MUTE : 0);
+ } else {
+ response = st->gain_right | (st->mute_right ? AC_AMP_MUTE : 0);
+ }
+ hda_codec_response(hda, true, response);
+ break;
+ case AC_VERB_SET_AMP_GAIN_MUTE:
+ st = a->st + node->stindex;
+ if (st->node == NULL) {
+ goto fail;
+ }
+ dprint(a, 1, "amp (%s): %s%s%s%s index %d gain %3d %s\n",
+ st->node->name,
+ (payload & AC_AMP_SET_OUTPUT) ? "o" : "-",
+ (payload & AC_AMP_SET_INPUT) ? "i" : "-",
+ (payload & AC_AMP_SET_LEFT) ? "l" : "-",
+ (payload & AC_AMP_SET_RIGHT) ? "r" : "-",
+ (payload & AC_AMP_SET_INDEX) >> AC_AMP_SET_INDEX_SHIFT,
+ (payload & AC_AMP_GAIN),
+ (payload & AC_AMP_MUTE) ? "muted" : "");
+ if (payload & AC_AMP_SET_LEFT) {
+ st->gain_left = payload & AC_AMP_GAIN;
+ st->mute_left = payload & AC_AMP_MUTE;
+ }
+ if (payload & AC_AMP_SET_RIGHT) {
+ st->gain_right = payload & AC_AMP_GAIN;
+ st->mute_right = payload & AC_AMP_MUTE;
+ }
+ hda_audio_set_amp(st);
+ hda_codec_response(hda, true, 0);
+ break;
+
+ /* not supported */
+ case AC_VERB_SET_POWER_STATE:
+ case AC_VERB_GET_POWER_STATE:
+ case AC_VERB_GET_SDI_SELECT:
+ hda_codec_response(hda, true, 0);
+ break;
+ default:
+ goto fail;
+ }
+ return;
+
+fail:
+ dprint(a, 1, "%s: not handled: nid %d (%s), verb 0x%x, payload 0x%x\n",
+ __FUNCTION__, nid, node ? node->name : "?", verb, payload);
+ hda_codec_response(hda, true, 0);
+}
+
+static void hda_audio_stream(HDACodecDevice *hda, uint32_t stnr, bool running, bool output)
+{
+ HDAAudioState *a = HDA_AUDIO(hda);
+ int s;
+
+ a->running_compat[stnr] = running;
+ a->running_real[output * 16 + stnr] = running;
+ for (s = 0; s < ARRAY_SIZE(a->st); s++) {
+ if (a->st[s].node == NULL) {
+ continue;
+ }
+ if (a->st[s].output != output) {
+ continue;
+ }
+ if (a->st[s].stream != stnr) {
+ continue;
+ }
+ hda_audio_set_running(&a->st[s], running);
+ }
+}
+
+static int hda_audio_init(HDACodecDevice *hda, const struct desc_codec *desc)
+{
+ HDAAudioState *a = HDA_AUDIO(hda);
+ HDAAudioStream *st;
+ const desc_node *node;
+ const desc_param *param;
+ uint32_t i, type;
+
+ a->desc = desc;
+ a->name = object_get_typename(OBJECT(a));
+ dprint(a, 1, "%s: cad %d\n", __FUNCTION__, a->hda.cad);
+
+ AUD_register_card("hda", &a->card);
+ for (i = 0; i < a->desc->nnodes; i++) {
+ node = a->desc->nodes + i;
+ param = hda_codec_find_param(node, AC_PAR_AUDIO_WIDGET_CAP);
+ if (param == NULL) {
+ continue;
+ }
+ type = (param->val & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+ switch (type) {
+ case AC_WID_AUD_OUT:
+ case AC_WID_AUD_IN:
+ assert(node->stindex < ARRAY_SIZE(a->st));
+ st = a->st + node->stindex;
+ st->state = a;
+ st->node = node;
+ if (type == AC_WID_AUD_OUT) {
+ /* unmute output by default */
+ st->gain_left = QEMU_HDA_AMP_STEPS;
+ st->gain_right = QEMU_HDA_AMP_STEPS;
+ st->bpos = sizeof(st->buf);
+ st->output = true;
+ } else {
+ st->output = false;
+ }
+ st->format = AC_FMT_TYPE_PCM | AC_FMT_BITS_16 |
+ (1 << AC_FMT_CHAN_SHIFT);
+ hda_codec_parse_fmt(st->format, &st->as);
+ hda_audio_setup(st);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int hda_audio_exit(HDACodecDevice *hda)
+{
+ HDAAudioState *a = HDA_AUDIO(hda);
+ HDAAudioStream *st;
+ int i;
+
+ dprint(a, 1, "%s\n", __FUNCTION__);
+ for (i = 0; i < ARRAY_SIZE(a->st); i++) {
+ st = a->st + i;
+ if (st->node == NULL) {
+ continue;
+ }
+ if (st->output) {
+ AUD_close_out(&a->card, st->voice.out);
+ } else {
+ AUD_close_in(&a->card, st->voice.in);
+ }
+ }
+ AUD_remove_card(&a->card);
+ return 0;
+}
+
+static int hda_audio_post_load(void *opaque, int version)
+{
+ HDAAudioState *a = opaque;
+ HDAAudioStream *st;
+ int i;
+
+ dprint(a, 1, "%s\n", __FUNCTION__);
+ if (version == 1) {
+ /* assume running_compat[] is for output streams */
+ for (i = 0; i < ARRAY_SIZE(a->running_compat); i++)
+ a->running_real[16 + i] = a->running_compat[i];
+ }
+
+ for (i = 0; i < ARRAY_SIZE(a->st); i++) {
+ st = a->st + i;
+ if (st->node == NULL)
+ continue;
+ hda_codec_parse_fmt(st->format, &st->as);
+ hda_audio_setup(st);
+ hda_audio_set_amp(st);
+ hda_audio_set_running(st, a->running_real[st->output * 16 + st->stream]);
+ }
+ return 0;
+}
+
+static void hda_audio_reset(DeviceState *dev)
+{
+ HDAAudioState *a = HDA_AUDIO(dev);
+ HDAAudioStream *st;
+ int i;
+
+ dprint(a, 1, "%s\n", __func__);
+ for (i = 0; i < ARRAY_SIZE(a->st); i++) {
+ st = a->st + i;
+ if (st->node != NULL) {
+ hda_audio_set_running(st, false);
+ }
+ }
+}
+
+static const VMStateDescription vmstate_hda_audio_stream = {
+ .name = "hda-audio-stream",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(stream, HDAAudioStream),
+ VMSTATE_UINT32(channel, HDAAudioStream),
+ VMSTATE_UINT32(format, HDAAudioStream),
+ VMSTATE_UINT32(gain_left, HDAAudioStream),
+ VMSTATE_UINT32(gain_right, HDAAudioStream),
+ VMSTATE_BOOL(mute_left, HDAAudioStream),
+ VMSTATE_BOOL(mute_right, HDAAudioStream),
+ VMSTATE_UINT32(bpos, HDAAudioStream),
+ VMSTATE_BUFFER(buf, HDAAudioStream),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_hda_audio = {
+ .name = "hda-audio",
+ .version_id = 2,
+ .post_load = hda_audio_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(st, HDAAudioState, 4, 0,
+ vmstate_hda_audio_stream,
+ HDAAudioStream),
+ VMSTATE_BOOL_ARRAY(running_compat, HDAAudioState, 16),
+ VMSTATE_BOOL_ARRAY_V(running_real, HDAAudioState, 2 * 16, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property hda_audio_properties[] = {
+ DEFINE_PROP_UINT32("debug", HDAAudioState, debug, 0),
+ DEFINE_PROP_BOOL("mixer", HDAAudioState, mixer, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static int hda_audio_init_output(HDACodecDevice *hda)
+{
+ HDAAudioState *a = HDA_AUDIO(hda);
+
+ if (!a->mixer) {
+ return hda_audio_init(hda, &output_nomixemu);
+ } else {
+ return hda_audio_init(hda, &output_mixemu);
+ }
+}
+
+static int hda_audio_init_duplex(HDACodecDevice *hda)
+{
+ HDAAudioState *a = HDA_AUDIO(hda);
+
+ if (!a->mixer) {
+ return hda_audio_init(hda, &duplex_nomixemu);
+ } else {
+ return hda_audio_init(hda, &duplex_mixemu);
+ }
+}
+
+static int hda_audio_init_micro(HDACodecDevice *hda)
+{
+ HDAAudioState *a = HDA_AUDIO(hda);
+
+ if (!a->mixer) {
+ return hda_audio_init(hda, &micro_nomixemu);
+ } else {
+ return hda_audio_init(hda, &micro_mixemu);
+ }
+}
+
+static void hda_audio_base_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
+
+ k->exit = hda_audio_exit;
+ k->command = hda_audio_command;
+ k->stream = hda_audio_stream;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->reset = hda_audio_reset;
+ dc->vmsd = &vmstate_hda_audio;
+ dc->props = hda_audio_properties;
+}
+
+static const TypeInfo hda_audio_info = {
+ .name = TYPE_HDA_AUDIO,
+ .parent = TYPE_HDA_CODEC_DEVICE,
+ .class_init = hda_audio_base_class_init,
+ .abstract = true,
+};
+
+static void hda_audio_output_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
+
+ k->init = hda_audio_init_output;
+ dc->desc = "HDA Audio Codec, output-only (line-out)";
+}
+
+static const TypeInfo hda_audio_output_info = {
+ .name = "hda-output",
+ .parent = TYPE_HDA_AUDIO,
+ .instance_size = sizeof(HDAAudioState),
+ .class_init = hda_audio_output_class_init,
+};
+
+static void hda_audio_duplex_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
+
+ k->init = hda_audio_init_duplex;
+ dc->desc = "HDA Audio Codec, duplex (line-out, line-in)";
+}
+
+static const TypeInfo hda_audio_duplex_info = {
+ .name = "hda-duplex",
+ .parent = TYPE_HDA_AUDIO,
+ .instance_size = sizeof(HDAAudioState),
+ .class_init = hda_audio_duplex_class_init,
+};
+
+static void hda_audio_micro_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
+
+ k->init = hda_audio_init_micro;
+ dc->desc = "HDA Audio Codec, duplex (speaker, microphone)";
+}
+
+static const TypeInfo hda_audio_micro_info = {
+ .name = "hda-micro",
+ .parent = TYPE_HDA_AUDIO,
+ .instance_size = sizeof(HDAAudioState),
+ .class_init = hda_audio_micro_class_init,
+};
+
+static void hda_audio_register_types(void)
+{
+ type_register_static(&hda_audio_info);
+ type_register_static(&hda_audio_output_info);
+ type_register_static(&hda_audio_duplex_info);
+ type_register_static(&hda_audio_micro_info);
+}
+
+type_init(hda_audio_register_types)
diff --git a/hw/audio/intel-hda-defs.h b/hw/audio/intel-hda-defs.h
new file mode 100644
index 00000000..2e37e5b8
--- /dev/null
+++ b/hw/audio/intel-hda-defs.h
@@ -0,0 +1,717 @@
+#ifndef HW_INTEL_HDA_DEFS_H
+#define HW_INTEL_HDA_DEFS_H
+
+/* qemu */
+#define HDA_BUFFER_SIZE 256
+
+/* --------------------------------------------------------------------- */
+/* from linux/sound/pci/hda/hda_intel.c */
+
+/*
+ * registers
+ */
+#define ICH6_REG_GCAP 0x00
+#define ICH6_GCAP_64OK (1 << 0) /* 64bit address support */
+#define ICH6_GCAP_NSDO (3 << 1) /* # of serial data out signals */
+#define ICH6_GCAP_BSS (31 << 3) /* # of bidirectional streams */
+#define ICH6_GCAP_ISS (15 << 8) /* # of input streams */
+#define ICH6_GCAP_OSS (15 << 12) /* # of output streams */
+#define ICH6_REG_VMIN 0x02
+#define ICH6_REG_VMAJ 0x03
+#define ICH6_REG_OUTPAY 0x04
+#define ICH6_REG_INPAY 0x06
+#define ICH6_REG_GCTL 0x08
+#define ICH6_GCTL_RESET (1 << 0) /* controller reset */
+#define ICH6_GCTL_FCNTRL (1 << 1) /* flush control */
+#define ICH6_GCTL_UNSOL (1 << 8) /* accept unsol. response enable */
+#define ICH6_REG_WAKEEN 0x0c
+#define ICH6_REG_STATESTS 0x0e
+#define ICH6_REG_GSTS 0x10
+#define ICH6_GSTS_FSTS (1 << 1) /* flush status */
+#define ICH6_REG_INTCTL 0x20
+#define ICH6_REG_INTSTS 0x24
+#define ICH6_REG_WALLCLK 0x30 /* 24Mhz source */
+#define ICH6_REG_SYNC 0x34
+#define ICH6_REG_CORBLBASE 0x40
+#define ICH6_REG_CORBUBASE 0x44
+#define ICH6_REG_CORBWP 0x48
+#define ICH6_REG_CORBRP 0x4a
+#define ICH6_CORBRP_RST (1 << 15) /* read pointer reset */
+#define ICH6_REG_CORBCTL 0x4c
+#define ICH6_CORBCTL_RUN (1 << 1) /* enable DMA */
+#define ICH6_CORBCTL_CMEIE (1 << 0) /* enable memory error irq */
+#define ICH6_REG_CORBSTS 0x4d
+#define ICH6_CORBSTS_CMEI (1 << 0) /* memory error indication */
+#define ICH6_REG_CORBSIZE 0x4e
+
+#define ICH6_REG_RIRBLBASE 0x50
+#define ICH6_REG_RIRBUBASE 0x54
+#define ICH6_REG_RIRBWP 0x58
+#define ICH6_RIRBWP_RST (1 << 15) /* write pointer reset */
+#define ICH6_REG_RINTCNT 0x5a
+#define ICH6_REG_RIRBCTL 0x5c
+#define ICH6_RBCTL_IRQ_EN (1 << 0) /* enable IRQ */
+#define ICH6_RBCTL_DMA_EN (1 << 1) /* enable DMA */
+#define ICH6_RBCTL_OVERRUN_EN (1 << 2) /* enable overrun irq */
+#define ICH6_REG_RIRBSTS 0x5d
+#define ICH6_RBSTS_IRQ (1 << 0) /* response irq */
+#define ICH6_RBSTS_OVERRUN (1 << 2) /* overrun irq */
+#define ICH6_REG_RIRBSIZE 0x5e
+
+#define ICH6_REG_IC 0x60
+#define ICH6_REG_IR 0x64
+#define ICH6_REG_IRS 0x68
+#define ICH6_IRS_VALID (1<<1)
+#define ICH6_IRS_BUSY (1<<0)
+
+#define ICH6_REG_DPLBASE 0x70
+#define ICH6_REG_DPUBASE 0x74
+#define ICH6_DPLBASE_ENABLE 0x1 /* Enable position buffer */
+
+/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
+enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
+
+/* stream register offsets from stream base */
+#define ICH6_REG_SD_CTL 0x00
+#define ICH6_REG_SD_STS 0x03
+#define ICH6_REG_SD_LPIB 0x04
+#define ICH6_REG_SD_CBL 0x08
+#define ICH6_REG_SD_LVI 0x0c
+#define ICH6_REG_SD_FIFOW 0x0e
+#define ICH6_REG_SD_FIFOSIZE 0x10
+#define ICH6_REG_SD_FORMAT 0x12
+#define ICH6_REG_SD_BDLPL 0x18
+#define ICH6_REG_SD_BDLPU 0x1c
+
+/* PCI space */
+#define ICH6_PCIREG_TCSEL 0x44
+
+/*
+ * other constants
+ */
+
+/* max number of SDs */
+/* ICH, ATI and VIA have 4 playback and 4 capture */
+#define ICH6_NUM_CAPTURE 4
+#define ICH6_NUM_PLAYBACK 4
+
+/* ULI has 6 playback and 5 capture */
+#define ULI_NUM_CAPTURE 5
+#define ULI_NUM_PLAYBACK 6
+
+/* ATI HDMI has 1 playback and 0 capture */
+#define ATIHDMI_NUM_CAPTURE 0
+#define ATIHDMI_NUM_PLAYBACK 1
+
+/* TERA has 4 playback and 3 capture */
+#define TERA_NUM_CAPTURE 3
+#define TERA_NUM_PLAYBACK 4
+
+/* this number is statically defined for simplicity */
+#define MAX_AZX_DEV 16
+
+/* max number of fragments - we may use more if allocating more pages for BDL */
+#define BDL_SIZE 4096
+#define AZX_MAX_BDL_ENTRIES (BDL_SIZE / 16)
+#define AZX_MAX_FRAG 32
+/* max buffer size - no h/w limit, you can increase as you like */
+#define AZX_MAX_BUF_SIZE (1024*1024*1024)
+
+/* RIRB int mask: overrun[2], response[0] */
+#define RIRB_INT_RESPONSE 0x01
+#define RIRB_INT_OVERRUN 0x04
+#define RIRB_INT_MASK 0x05
+
+/* STATESTS int mask: S3,SD2,SD1,SD0 */
+#define AZX_MAX_CODECS 8
+#define AZX_DEFAULT_CODECS 4
+#define STATESTS_INT_MASK ((1 << AZX_MAX_CODECS) - 1)
+
+/* SD_CTL bits */
+#define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */
+#define SD_CTL_DMA_START 0x02 /* stream DMA start bit */
+#define SD_CTL_STRIPE (3 << 16) /* stripe control */
+#define SD_CTL_TRAFFIC_PRIO (1 << 18) /* traffic priority */
+#define SD_CTL_DIR (1 << 19) /* bi-directional stream */
+#define SD_CTL_STREAM_TAG_MASK (0xf << 20)
+#define SD_CTL_STREAM_TAG_SHIFT 20
+
+/* SD_CTL and SD_STS */
+#define SD_INT_DESC_ERR 0x10 /* descriptor error interrupt */
+#define SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */
+#define SD_INT_COMPLETE 0x04 /* completion interrupt */
+#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|\
+ SD_INT_COMPLETE)
+
+/* SD_STS */
+#define SD_STS_FIFO_READY 0x20 /* FIFO ready */
+
+/* INTCTL and INTSTS */
+#define ICH6_INT_ALL_STREAM 0xff /* all stream interrupts */
+#define ICH6_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */
+#define ICH6_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */
+
+/* below are so far hardcoded - should read registers in future */
+#define ICH6_MAX_CORB_ENTRIES 256
+#define ICH6_MAX_RIRB_ENTRIES 256
+
+/* position fix mode */
+enum {
+ POS_FIX_AUTO,
+ POS_FIX_LPIB,
+ POS_FIX_POSBUF,
+};
+
+/* Defines for ATI HD Audio support in SB450 south bridge */
+#define ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR 0x42
+#define ATI_SB450_HDAUDIO_ENABLE_SNOOP 0x02
+
+/* Defines for Nvidia HDA support */
+#define NVIDIA_HDA_TRANSREG_ADDR 0x4e
+#define NVIDIA_HDA_ENABLE_COHBITS 0x0f
+#define NVIDIA_HDA_ISTRM_COH 0x4d
+#define NVIDIA_HDA_OSTRM_COH 0x4c
+#define NVIDIA_HDA_ENABLE_COHBIT 0x01
+
+/* Defines for Intel SCH HDA snoop control */
+#define INTEL_SCH_HDA_DEVC 0x78
+#define INTEL_SCH_HDA_DEVC_NOSNOOP (0x1<<11)
+
+/* Define IN stream 0 FIFO size offset in VIA controller */
+#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET 0x90
+/* Define VIA HD Audio Device ID*/
+#define VIA_HDAC_DEVICE_ID 0x3288
+
+/* HD Audio class code */
+#define PCI_CLASS_MULTIMEDIA_HD_AUDIO 0x0403
+
+/* --------------------------------------------------------------------- */
+/* from linux/sound/pci/hda/hda_codec.h */
+
+/*
+ * nodes
+ */
+#define AC_NODE_ROOT 0x00
+
+/*
+ * function group types
+ */
+enum {
+ AC_GRP_AUDIO_FUNCTION = 0x01,
+ AC_GRP_MODEM_FUNCTION = 0x02,
+};
+
+/*
+ * widget types
+ */
+enum {
+ AC_WID_AUD_OUT, /* Audio Out */
+ AC_WID_AUD_IN, /* Audio In */
+ AC_WID_AUD_MIX, /* Audio Mixer */
+ AC_WID_AUD_SEL, /* Audio Selector */
+ AC_WID_PIN, /* Pin Complex */
+ AC_WID_POWER, /* Power */
+ AC_WID_VOL_KNB, /* Volume Knob */
+ AC_WID_BEEP, /* Beep Generator */
+ AC_WID_VENDOR = 0x0f /* Vendor specific */
+};
+
+/*
+ * GET verbs
+ */
+#define AC_VERB_GET_STREAM_FORMAT 0x0a00
+#define AC_VERB_GET_AMP_GAIN_MUTE 0x0b00
+#define AC_VERB_GET_PROC_COEF 0x0c00
+#define AC_VERB_GET_COEF_INDEX 0x0d00
+#define AC_VERB_PARAMETERS 0x0f00
+#define AC_VERB_GET_CONNECT_SEL 0x0f01
+#define AC_VERB_GET_CONNECT_LIST 0x0f02
+#define AC_VERB_GET_PROC_STATE 0x0f03
+#define AC_VERB_GET_SDI_SELECT 0x0f04
+#define AC_VERB_GET_POWER_STATE 0x0f05
+#define AC_VERB_GET_CONV 0x0f06
+#define AC_VERB_GET_PIN_WIDGET_CONTROL 0x0f07
+#define AC_VERB_GET_UNSOLICITED_RESPONSE 0x0f08
+#define AC_VERB_GET_PIN_SENSE 0x0f09
+#define AC_VERB_GET_BEEP_CONTROL 0x0f0a
+#define AC_VERB_GET_EAPD_BTLENABLE 0x0f0c
+#define AC_VERB_GET_DIGI_CONVERT_1 0x0f0d
+#define AC_VERB_GET_DIGI_CONVERT_2 0x0f0e /* unused */
+#define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f
+/* f10-f1a: GPIO */
+#define AC_VERB_GET_GPIO_DATA 0x0f15
+#define AC_VERB_GET_GPIO_MASK 0x0f16
+#define AC_VERB_GET_GPIO_DIRECTION 0x0f17
+#define AC_VERB_GET_GPIO_WAKE_MASK 0x0f18
+#define AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK 0x0f19
+#define AC_VERB_GET_GPIO_STICKY_MASK 0x0f1a
+#define AC_VERB_GET_CONFIG_DEFAULT 0x0f1c
+/* f20: AFG/MFG */
+#define AC_VERB_GET_SUBSYSTEM_ID 0x0f20
+#define AC_VERB_GET_CVT_CHAN_COUNT 0x0f2d
+#define AC_VERB_GET_HDMI_DIP_SIZE 0x0f2e
+#define AC_VERB_GET_HDMI_ELDD 0x0f2f
+#define AC_VERB_GET_HDMI_DIP_INDEX 0x0f30
+#define AC_VERB_GET_HDMI_DIP_DATA 0x0f31
+#define AC_VERB_GET_HDMI_DIP_XMIT 0x0f32
+#define AC_VERB_GET_HDMI_CP_CTRL 0x0f33
+#define AC_VERB_GET_HDMI_CHAN_SLOT 0x0f34
+
+/*
+ * SET verbs
+ */
+#define AC_VERB_SET_STREAM_FORMAT 0x200
+#define AC_VERB_SET_AMP_GAIN_MUTE 0x300
+#define AC_VERB_SET_PROC_COEF 0x400
+#define AC_VERB_SET_COEF_INDEX 0x500
+#define AC_VERB_SET_CONNECT_SEL 0x701
+#define AC_VERB_SET_PROC_STATE 0x703
+#define AC_VERB_SET_SDI_SELECT 0x704
+#define AC_VERB_SET_POWER_STATE 0x705
+#define AC_VERB_SET_CHANNEL_STREAMID 0x706
+#define AC_VERB_SET_PIN_WIDGET_CONTROL 0x707
+#define AC_VERB_SET_UNSOLICITED_ENABLE 0x708
+#define AC_VERB_SET_PIN_SENSE 0x709
+#define AC_VERB_SET_BEEP_CONTROL 0x70a
+#define AC_VERB_SET_EAPD_BTLENABLE 0x70c
+#define AC_VERB_SET_DIGI_CONVERT_1 0x70d
+#define AC_VERB_SET_DIGI_CONVERT_2 0x70e
+#define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f
+#define AC_VERB_SET_GPIO_DATA 0x715
+#define AC_VERB_SET_GPIO_MASK 0x716
+#define AC_VERB_SET_GPIO_DIRECTION 0x717
+#define AC_VERB_SET_GPIO_WAKE_MASK 0x718
+#define AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK 0x719
+#define AC_VERB_SET_GPIO_STICKY_MASK 0x71a
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 0x71c
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_1 0x71d
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_2 0x71e
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_3 0x71f
+#define AC_VERB_SET_EAPD 0x788
+#define AC_VERB_SET_CODEC_RESET 0x7ff
+#define AC_VERB_SET_CVT_CHAN_COUNT 0x72d
+#define AC_VERB_SET_HDMI_DIP_INDEX 0x730
+#define AC_VERB_SET_HDMI_DIP_DATA 0x731
+#define AC_VERB_SET_HDMI_DIP_XMIT 0x732
+#define AC_VERB_SET_HDMI_CP_CTRL 0x733
+#define AC_VERB_SET_HDMI_CHAN_SLOT 0x734
+
+/*
+ * Parameter IDs
+ */
+#define AC_PAR_VENDOR_ID 0x00
+#define AC_PAR_SUBSYSTEM_ID 0x01
+#define AC_PAR_REV_ID 0x02
+#define AC_PAR_NODE_COUNT 0x04
+#define AC_PAR_FUNCTION_TYPE 0x05
+#define AC_PAR_AUDIO_FG_CAP 0x08
+#define AC_PAR_AUDIO_WIDGET_CAP 0x09
+#define AC_PAR_PCM 0x0a
+#define AC_PAR_STREAM 0x0b
+#define AC_PAR_PIN_CAP 0x0c
+#define AC_PAR_AMP_IN_CAP 0x0d
+#define AC_PAR_CONNLIST_LEN 0x0e
+#define AC_PAR_POWER_STATE 0x0f
+#define AC_PAR_PROC_CAP 0x10
+#define AC_PAR_GPIO_CAP 0x11
+#define AC_PAR_AMP_OUT_CAP 0x12
+#define AC_PAR_VOL_KNB_CAP 0x13
+#define AC_PAR_HDMI_LPCM_CAP 0x20
+
+/*
+ * AC_VERB_PARAMETERS results (32bit)
+ */
+
+/* Function Group Type */
+#define AC_FGT_TYPE (0xff<<0)
+#define AC_FGT_TYPE_SHIFT 0
+#define AC_FGT_UNSOL_CAP (1<<8)
+
+/* Audio Function Group Capabilities */
+#define AC_AFG_OUT_DELAY (0xf<<0)
+#define AC_AFG_IN_DELAY (0xf<<8)
+#define AC_AFG_BEEP_GEN (1<<16)
+
+/* Audio Widget Capabilities */
+#define AC_WCAP_STEREO (1<<0) /* stereo I/O */
+#define AC_WCAP_IN_AMP (1<<1) /* AMP-in present */
+#define AC_WCAP_OUT_AMP (1<<2) /* AMP-out present */
+#define AC_WCAP_AMP_OVRD (1<<3) /* AMP-parameter override */
+#define AC_WCAP_FORMAT_OVRD (1<<4) /* format override */
+#define AC_WCAP_STRIPE (1<<5) /* stripe */
+#define AC_WCAP_PROC_WID (1<<6) /* Proc Widget */
+#define AC_WCAP_UNSOL_CAP (1<<7) /* Unsol capable */
+#define AC_WCAP_CONN_LIST (1<<8) /* connection list */
+#define AC_WCAP_DIGITAL (1<<9) /* digital I/O */
+#define AC_WCAP_POWER (1<<10) /* power control */
+#define AC_WCAP_LR_SWAP (1<<11) /* L/R swap */
+#define AC_WCAP_CP_CAPS (1<<12) /* content protection */
+#define AC_WCAP_CHAN_CNT_EXT (7<<13) /* channel count ext */
+#define AC_WCAP_DELAY (0xf<<16)
+#define AC_WCAP_DELAY_SHIFT 16
+#define AC_WCAP_TYPE (0xf<<20)
+#define AC_WCAP_TYPE_SHIFT 20
+
+/* supported PCM rates and bits */
+#define AC_SUPPCM_RATES (0xfff << 0)
+#define AC_SUPPCM_BITS_8 (1<<16)
+#define AC_SUPPCM_BITS_16 (1<<17)
+#define AC_SUPPCM_BITS_20 (1<<18)
+#define AC_SUPPCM_BITS_24 (1<<19)
+#define AC_SUPPCM_BITS_32 (1<<20)
+
+/* supported PCM stream format */
+#define AC_SUPFMT_PCM (1<<0)
+#define AC_SUPFMT_FLOAT32 (1<<1)
+#define AC_SUPFMT_AC3 (1<<2)
+
+/* GP I/O count */
+#define AC_GPIO_IO_COUNT (0xff<<0)
+#define AC_GPIO_O_COUNT (0xff<<8)
+#define AC_GPIO_O_COUNT_SHIFT 8
+#define AC_GPIO_I_COUNT (0xff<<16)
+#define AC_GPIO_I_COUNT_SHIFT 16
+#define AC_GPIO_UNSOLICITED (1<<30)
+#define AC_GPIO_WAKE (1<<31)
+
+/* Converter stream, channel */
+#define AC_CONV_CHANNEL (0xf<<0)
+#define AC_CONV_STREAM (0xf<<4)
+#define AC_CONV_STREAM_SHIFT 4
+
+/* Input converter SDI select */
+#define AC_SDI_SELECT (0xf<<0)
+
+/* stream format id */
+#define AC_FMT_CHAN_SHIFT 0
+#define AC_FMT_CHAN_MASK (0x0f << 0)
+#define AC_FMT_BITS_SHIFT 4
+#define AC_FMT_BITS_MASK (7 << 4)
+#define AC_FMT_BITS_8 (0 << 4)
+#define AC_FMT_BITS_16 (1 << 4)
+#define AC_FMT_BITS_20 (2 << 4)
+#define AC_FMT_BITS_24 (3 << 4)
+#define AC_FMT_BITS_32 (4 << 4)
+#define AC_FMT_DIV_SHIFT 8
+#define AC_FMT_DIV_MASK (7 << 8)
+#define AC_FMT_MULT_SHIFT 11
+#define AC_FMT_MULT_MASK (7 << 11)
+#define AC_FMT_BASE_SHIFT 14
+#define AC_FMT_BASE_48K (0 << 14)
+#define AC_FMT_BASE_44K (1 << 14)
+#define AC_FMT_TYPE_SHIFT 15
+#define AC_FMT_TYPE_PCM (0 << 15)
+#define AC_FMT_TYPE_NON_PCM (1 << 15)
+
+/* Unsolicited response control */
+#define AC_UNSOL_TAG (0x3f<<0)
+#define AC_UNSOL_ENABLED (1<<7)
+#define AC_USRSP_EN AC_UNSOL_ENABLED
+
+/* Unsolicited responses */
+#define AC_UNSOL_RES_TAG (0x3f<<26)
+#define AC_UNSOL_RES_TAG_SHIFT 26
+#define AC_UNSOL_RES_SUBTAG (0x1f<<21)
+#define AC_UNSOL_RES_SUBTAG_SHIFT 21
+#define AC_UNSOL_RES_ELDV (1<<1) /* ELD Data valid (for HDMI) */
+#define AC_UNSOL_RES_PD (1<<0) /* pinsense detect */
+#define AC_UNSOL_RES_CP_STATE (1<<1) /* content protection */
+#define AC_UNSOL_RES_CP_READY (1<<0) /* content protection */
+
+/* Pin widget capabilies */
+#define AC_PINCAP_IMP_SENSE (1<<0) /* impedance sense capable */
+#define AC_PINCAP_TRIG_REQ (1<<1) /* trigger required */
+#define AC_PINCAP_PRES_DETECT (1<<2) /* presence detect capable */
+#define AC_PINCAP_HP_DRV (1<<3) /* headphone drive capable */
+#define AC_PINCAP_OUT (1<<4) /* output capable */
+#define AC_PINCAP_IN (1<<5) /* input capable */
+#define AC_PINCAP_BALANCE (1<<6) /* balanced I/O capable */
+/* Note: This LR_SWAP pincap is defined in the Realtek ALC883 specification,
+ * but is marked reserved in the Intel HDA specification.
+ */
+#define AC_PINCAP_LR_SWAP (1<<7) /* L/R swap */
+/* Note: The same bit as LR_SWAP is newly defined as HDMI capability
+ * in HD-audio specification
+ */
+#define AC_PINCAP_HDMI (1<<7) /* HDMI pin */
+#define AC_PINCAP_DP (1<<24) /* DisplayPort pin, can
+ * coexist with AC_PINCAP_HDMI
+ */
+#define AC_PINCAP_VREF (0x37<<8)
+#define AC_PINCAP_VREF_SHIFT 8
+#define AC_PINCAP_EAPD (1<<16) /* EAPD capable */
+#define AC_PINCAP_HBR (1<<27) /* High Bit Rate */
+/* Vref status (used in pin cap) */
+#define AC_PINCAP_VREF_HIZ (1<<0) /* Hi-Z */
+#define AC_PINCAP_VREF_50 (1<<1) /* 50% */
+#define AC_PINCAP_VREF_GRD (1<<2) /* ground */
+#define AC_PINCAP_VREF_80 (1<<4) /* 80% */
+#define AC_PINCAP_VREF_100 (1<<5) /* 100% */
+
+/* Amplifier capabilities */
+#define AC_AMPCAP_OFFSET (0x7f<<0) /* 0dB offset */
+#define AC_AMPCAP_OFFSET_SHIFT 0
+#define AC_AMPCAP_NUM_STEPS (0x7f<<8) /* number of steps */
+#define AC_AMPCAP_NUM_STEPS_SHIFT 8
+#define AC_AMPCAP_STEP_SIZE (0x7f<<16) /* step size 0-32dB
+ * in 0.25dB
+ */
+#define AC_AMPCAP_STEP_SIZE_SHIFT 16
+#define AC_AMPCAP_MUTE (1<<31) /* mute capable */
+#define AC_AMPCAP_MUTE_SHIFT 31
+
+/* Connection list */
+#define AC_CLIST_LENGTH (0x7f<<0)
+#define AC_CLIST_LONG (1<<7)
+
+/* Supported power status */
+#define AC_PWRST_D0SUP (1<<0)
+#define AC_PWRST_D1SUP (1<<1)
+#define AC_PWRST_D2SUP (1<<2)
+#define AC_PWRST_D3SUP (1<<3)
+#define AC_PWRST_D3COLDSUP (1<<4)
+#define AC_PWRST_S3D3COLDSUP (1<<29)
+#define AC_PWRST_CLKSTOP (1<<30)
+#define AC_PWRST_EPSS (1U<<31)
+
+/* Power state values */
+#define AC_PWRST_SETTING (0xf<<0)
+#define AC_PWRST_ACTUAL (0xf<<4)
+#define AC_PWRST_ACTUAL_SHIFT 4
+#define AC_PWRST_D0 0x00
+#define AC_PWRST_D1 0x01
+#define AC_PWRST_D2 0x02
+#define AC_PWRST_D3 0x03
+
+/* Processing capabilies */
+#define AC_PCAP_BENIGN (1<<0)
+#define AC_PCAP_NUM_COEF (0xff<<8)
+#define AC_PCAP_NUM_COEF_SHIFT 8
+
+/* Volume knobs capabilities */
+#define AC_KNBCAP_NUM_STEPS (0x7f<<0)
+#define AC_KNBCAP_DELTA (1<<7)
+
+/* HDMI LPCM capabilities */
+#define AC_LPCMCAP_48K_CP_CHNS (0x0f<<0) /* max channels w/ CP-on */
+#define AC_LPCMCAP_48K_NO_CHNS (0x0f<<4) /* max channels w/o CP-on */
+#define AC_LPCMCAP_48K_20BIT (1<<8) /* 20b bitrate supported */
+#define AC_LPCMCAP_48K_24BIT (1<<9) /* 24b bitrate supported */
+#define AC_LPCMCAP_96K_CP_CHNS (0x0f<<10) /* max channels w/ CP-on */
+#define AC_LPCMCAP_96K_NO_CHNS (0x0f<<14) /* max channels w/o CP-on */
+#define AC_LPCMCAP_96K_20BIT (1<<18) /* 20b bitrate supported */
+#define AC_LPCMCAP_96K_24BIT (1<<19) /* 24b bitrate supported */
+#define AC_LPCMCAP_192K_CP_CHNS (0x0f<<20) /* max channels w/ CP-on */
+#define AC_LPCMCAP_192K_NO_CHNS (0x0f<<24) /* max channels w/o CP-on */
+#define AC_LPCMCAP_192K_20BIT (1<<28) /* 20b bitrate supported */
+#define AC_LPCMCAP_192K_24BIT (1<<29) /* 24b bitrate supported */
+#define AC_LPCMCAP_44K (1<<30) /* 44.1kHz support */
+#define AC_LPCMCAP_44K_MS (1<<31) /* 44.1kHz-multiplies support */
+
+/*
+ * Control Parameters
+ */
+
+/* Amp gain/mute */
+#define AC_AMP_MUTE (1<<7)
+#define AC_AMP_GAIN (0x7f)
+#define AC_AMP_GET_INDEX (0xf<<0)
+
+#define AC_AMP_GET_LEFT (1<<13)
+#define AC_AMP_GET_RIGHT (0<<13)
+#define AC_AMP_GET_OUTPUT (1<<15)
+#define AC_AMP_GET_INPUT (0<<15)
+
+#define AC_AMP_SET_INDEX (0xf<<8)
+#define AC_AMP_SET_INDEX_SHIFT 8
+#define AC_AMP_SET_RIGHT (1<<12)
+#define AC_AMP_SET_LEFT (1<<13)
+#define AC_AMP_SET_INPUT (1<<14)
+#define AC_AMP_SET_OUTPUT (1<<15)
+
+/* DIGITAL1 bits */
+#define AC_DIG1_ENABLE (1<<0)
+#define AC_DIG1_V (1<<1)
+#define AC_DIG1_VCFG (1<<2)
+#define AC_DIG1_EMPHASIS (1<<3)
+#define AC_DIG1_COPYRIGHT (1<<4)
+#define AC_DIG1_NONAUDIO (1<<5)
+#define AC_DIG1_PROFESSIONAL (1<<6)
+#define AC_DIG1_LEVEL (1<<7)
+
+/* DIGITAL2 bits */
+#define AC_DIG2_CC (0x7f<<0)
+
+/* Pin widget control - 8bit */
+#define AC_PINCTL_EPT (0x3<<0)
+#define AC_PINCTL_EPT_NATIVE 0
+#define AC_PINCTL_EPT_HBR 3
+#define AC_PINCTL_VREFEN (0x7<<0)
+#define AC_PINCTL_VREF_HIZ 0 /* Hi-Z */
+#define AC_PINCTL_VREF_50 1 /* 50% */
+#define AC_PINCTL_VREF_GRD 2 /* ground */
+#define AC_PINCTL_VREF_80 4 /* 80% */
+#define AC_PINCTL_VREF_100 5 /* 100% */
+#define AC_PINCTL_IN_EN (1<<5)
+#define AC_PINCTL_OUT_EN (1<<6)
+#define AC_PINCTL_HP_EN (1<<7)
+
+/* Pin sense - 32bit */
+#define AC_PINSENSE_IMPEDANCE_MASK (0x7fffffff)
+#define AC_PINSENSE_PRESENCE (1<<31)
+#define AC_PINSENSE_ELDV (1<<30) /* ELD valid (HDMI) */
+
+/* EAPD/BTL enable - 32bit */
+#define AC_EAPDBTL_BALANCED (1<<0)
+#define AC_EAPDBTL_EAPD (1<<1)
+#define AC_EAPDBTL_LR_SWAP (1<<2)
+
+/* HDMI ELD data */
+#define AC_ELDD_ELD_VALID (1<<31)
+#define AC_ELDD_ELD_DATA 0xff
+
+/* HDMI DIP size */
+#define AC_DIPSIZE_ELD_BUF (1<<3) /* ELD buf size of packet size */
+#define AC_DIPSIZE_PACK_IDX (0x07<<0) /* packet index */
+
+/* HDMI DIP index */
+#define AC_DIPIDX_PACK_IDX (0x07<<5) /* packet idnex */
+#define AC_DIPIDX_BYTE_IDX (0x1f<<0) /* byte index */
+
+/* HDMI DIP xmit (transmit) control */
+#define AC_DIPXMIT_MASK (0x3<<6)
+#define AC_DIPXMIT_DISABLE (0x0<<6) /* disable xmit */
+#define AC_DIPXMIT_ONCE (0x2<<6) /* xmit once then disable */
+#define AC_DIPXMIT_BEST (0x3<<6) /* best effort */
+
+/* HDMI content protection (CP) control */
+#define AC_CPCTRL_CES (1<<9) /* current encryption state */
+#define AC_CPCTRL_READY (1<<8) /* ready bit */
+#define AC_CPCTRL_SUBTAG (0x1f<<3) /* subtag for unsol-resp */
+#define AC_CPCTRL_STATE (3<<0) /* current CP request state */
+
+/* Converter channel <-> HDMI slot mapping */
+#define AC_CVTMAP_HDMI_SLOT (0xf<<0) /* HDMI slot number */
+#define AC_CVTMAP_CHAN (0xf<<4) /* converter channel number */
+
+/* configuration default - 32bit */
+#define AC_DEFCFG_SEQUENCE (0xf<<0)
+#define AC_DEFCFG_DEF_ASSOC (0xf<<4)
+#define AC_DEFCFG_ASSOC_SHIFT 4
+#define AC_DEFCFG_MISC (0xf<<8)
+#define AC_DEFCFG_MISC_SHIFT 8
+#define AC_DEFCFG_MISC_NO_PRESENCE (1<<0)
+#define AC_DEFCFG_COLOR (0xf<<12)
+#define AC_DEFCFG_COLOR_SHIFT 12
+#define AC_DEFCFG_CONN_TYPE (0xf<<16)
+#define AC_DEFCFG_CONN_TYPE_SHIFT 16
+#define AC_DEFCFG_DEVICE (0xf<<20)
+#define AC_DEFCFG_DEVICE_SHIFT 20
+#define AC_DEFCFG_LOCATION (0x3f<<24)
+#define AC_DEFCFG_LOCATION_SHIFT 24
+#define AC_DEFCFG_PORT_CONN (0x3<<30)
+#define AC_DEFCFG_PORT_CONN_SHIFT 30
+
+/* device device types (0x0-0xf) */
+enum {
+ AC_JACK_LINE_OUT,
+ AC_JACK_SPEAKER,
+ AC_JACK_HP_OUT,
+ AC_JACK_CD,
+ AC_JACK_SPDIF_OUT,
+ AC_JACK_DIG_OTHER_OUT,
+ AC_JACK_MODEM_LINE_SIDE,
+ AC_JACK_MODEM_HAND_SIDE,
+ AC_JACK_LINE_IN,
+ AC_JACK_AUX,
+ AC_JACK_MIC_IN,
+ AC_JACK_TELEPHONY,
+ AC_JACK_SPDIF_IN,
+ AC_JACK_DIG_OTHER_IN,
+ AC_JACK_OTHER = 0xf,
+};
+
+/* jack connection types (0x0-0xf) */
+enum {
+ AC_JACK_CONN_UNKNOWN,
+ AC_JACK_CONN_1_8,
+ AC_JACK_CONN_1_4,
+ AC_JACK_CONN_ATAPI,
+ AC_JACK_CONN_RCA,
+ AC_JACK_CONN_OPTICAL,
+ AC_JACK_CONN_OTHER_DIGITAL,
+ AC_JACK_CONN_OTHER_ANALOG,
+ AC_JACK_CONN_DIN,
+ AC_JACK_CONN_XLR,
+ AC_JACK_CONN_RJ11,
+ AC_JACK_CONN_COMB,
+ AC_JACK_CONN_OTHER = 0xf,
+};
+
+/* jack colors (0x0-0xf) */
+enum {
+ AC_JACK_COLOR_UNKNOWN,
+ AC_JACK_COLOR_BLACK,
+ AC_JACK_COLOR_GREY,
+ AC_JACK_COLOR_BLUE,
+ AC_JACK_COLOR_GREEN,
+ AC_JACK_COLOR_RED,
+ AC_JACK_COLOR_ORANGE,
+ AC_JACK_COLOR_YELLOW,
+ AC_JACK_COLOR_PURPLE,
+ AC_JACK_COLOR_PINK,
+ AC_JACK_COLOR_WHITE = 0xe,
+ AC_JACK_COLOR_OTHER,
+};
+
+/* Jack location (0x0-0x3f) */
+/* common case */
+enum {
+ AC_JACK_LOC_NONE,
+ AC_JACK_LOC_REAR,
+ AC_JACK_LOC_FRONT,
+ AC_JACK_LOC_LEFT,
+ AC_JACK_LOC_RIGHT,
+ AC_JACK_LOC_TOP,
+ AC_JACK_LOC_BOTTOM,
+};
+/* bits 4-5 */
+enum {
+ AC_JACK_LOC_EXTERNAL = 0x00,
+ AC_JACK_LOC_INTERNAL = 0x10,
+ AC_JACK_LOC_SEPARATE = 0x20,
+ AC_JACK_LOC_OTHER = 0x30,
+};
+enum {
+ /* external on primary chasis */
+ AC_JACK_LOC_REAR_PANEL = 0x07,
+ AC_JACK_LOC_DRIVE_BAY,
+ /* internal */
+ AC_JACK_LOC_RISER = 0x17,
+ AC_JACK_LOC_HDMI,
+ AC_JACK_LOC_ATAPI,
+ /* others */
+ AC_JACK_LOC_MOBILE_IN = 0x37,
+ AC_JACK_LOC_MOBILE_OUT,
+};
+
+/* Port connectivity (0-3) */
+enum {
+ AC_JACK_PORT_COMPLEX,
+ AC_JACK_PORT_NONE,
+ AC_JACK_PORT_FIXED,
+ AC_JACK_PORT_BOTH,
+};
+
+/* max. connections to a widget */
+#define HDA_MAX_CONNECTIONS 32
+
+/* max. codec address */
+#define HDA_MAX_CODEC_ADDRESS 0x0f
+
+/* max number of PCM devics per card */
+#define HDA_MAX_PCMS 10
+
+/* --------------------------------------------------------------------- */
+
+#endif
diff --git a/hw/audio/intel-hda.c b/hw/audio/intel-hda.c
new file mode 100644
index 00000000..433463e9
--- /dev/null
+++ b/hw/audio/intel-hda.c
@@ -0,0 +1,1343 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * written by Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/msi.h"
+#include "qemu/timer.h"
+#include "hw/audio/audio.h"
+#include "intel-hda.h"
+#include "intel-hda-defs.h"
+#include "sysemu/dma.h"
+
+/* --------------------------------------------------------------------- */
+/* hda bus */
+
+static Property hda_props[] = {
+ DEFINE_PROP_UINT32("cad", HDACodecDevice, cad, -1),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static const TypeInfo hda_codec_bus_info = {
+ .name = TYPE_HDA_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(HDACodecBus),
+};
+
+void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus, size_t bus_size,
+ hda_codec_response_func response,
+ hda_codec_xfer_func xfer)
+{
+ qbus_create_inplace(bus, bus_size, TYPE_HDA_BUS, dev, NULL);
+ bus->response = response;
+ bus->xfer = xfer;
+}
+
+static int hda_codec_dev_init(DeviceState *qdev)
+{
+ HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, qdev->parent_bus);
+ HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev);
+ HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev);
+
+ if (dev->cad == -1) {
+ dev->cad = bus->next_cad;
+ }
+ if (dev->cad >= 15) {
+ return -1;
+ }
+ bus->next_cad = dev->cad + 1;
+ return cdc->init(dev);
+}
+
+static int hda_codec_dev_exit(DeviceState *qdev)
+{
+ HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev);
+ HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev);
+
+ if (cdc->exit) {
+ cdc->exit(dev);
+ }
+ return 0;
+}
+
+HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad)
+{
+ BusChild *kid;
+ HDACodecDevice *cdev;
+
+ QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
+ if (cdev->cad == cad) {
+ return cdev;
+ }
+ }
+ return NULL;
+}
+
+void hda_codec_response(HDACodecDevice *dev, bool solicited, uint32_t response)
+{
+ HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
+ bus->response(dev, solicited, response);
+}
+
+bool hda_codec_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
+ uint8_t *buf, uint32_t len)
+{
+ HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
+ return bus->xfer(dev, stnr, output, buf, len);
+}
+
+/* --------------------------------------------------------------------- */
+/* intel hda emulation */
+
+typedef struct IntelHDAStream IntelHDAStream;
+typedef struct IntelHDAState IntelHDAState;
+typedef struct IntelHDAReg IntelHDAReg;
+
+typedef struct bpl {
+ uint64_t addr;
+ uint32_t len;
+ uint32_t flags;
+} bpl;
+
+struct IntelHDAStream {
+ /* registers */
+ uint32_t ctl;
+ uint32_t lpib;
+ uint32_t cbl;
+ uint32_t lvi;
+ uint32_t fmt;
+ uint32_t bdlp_lbase;
+ uint32_t bdlp_ubase;
+
+ /* state */
+ bpl *bpl;
+ uint32_t bentries;
+ uint32_t bsize, be, bp;
+};
+
+struct IntelHDAState {
+ PCIDevice pci;
+ const char *name;
+ HDACodecBus codecs;
+
+ /* registers */
+ uint32_t g_ctl;
+ uint32_t wake_en;
+ uint32_t state_sts;
+ uint32_t int_ctl;
+ uint32_t int_sts;
+ uint32_t wall_clk;
+
+ uint32_t corb_lbase;
+ uint32_t corb_ubase;
+ uint32_t corb_rp;
+ uint32_t corb_wp;
+ uint32_t corb_ctl;
+ uint32_t corb_sts;
+ uint32_t corb_size;
+
+ uint32_t rirb_lbase;
+ uint32_t rirb_ubase;
+ uint32_t rirb_wp;
+ uint32_t rirb_cnt;
+ uint32_t rirb_ctl;
+ uint32_t rirb_sts;
+ uint32_t rirb_size;
+
+ uint32_t dp_lbase;
+ uint32_t dp_ubase;
+
+ uint32_t icw;
+ uint32_t irr;
+ uint32_t ics;
+
+ /* streams */
+ IntelHDAStream st[8];
+
+ /* state */
+ MemoryRegion mmio;
+ uint32_t rirb_count;
+ int64_t wall_base_ns;
+
+ /* debug logging */
+ const IntelHDAReg *last_reg;
+ uint32_t last_val;
+ uint32_t last_write;
+ uint32_t last_sec;
+ uint32_t repeat_count;
+
+ /* properties */
+ uint32_t debug;
+ uint32_t msi;
+ bool old_msi_addr;
+};
+
+#define TYPE_INTEL_HDA_GENERIC "intel-hda-generic"
+
+#define INTEL_HDA(obj) \
+ OBJECT_CHECK(IntelHDAState, (obj), TYPE_INTEL_HDA_GENERIC)
+
+struct IntelHDAReg {
+ const char *name; /* register name */
+ uint32_t size; /* size in bytes */
+ uint32_t reset; /* reset value */
+ uint32_t wmask; /* write mask */
+ uint32_t wclear; /* write 1 to clear bits */
+ uint32_t offset; /* location in IntelHDAState */
+ uint32_t shift; /* byte access entries for dwords */
+ uint32_t stream;
+ void (*whandler)(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old);
+ void (*rhandler)(IntelHDAState *d, const IntelHDAReg *reg);
+};
+
+static void intel_hda_reset(DeviceState *dev);
+
+/* --------------------------------------------------------------------- */
+
+static hwaddr intel_hda_addr(uint32_t lbase, uint32_t ubase)
+{
+ hwaddr addr;
+
+ addr = ((uint64_t)ubase << 32) | lbase;
+ return addr;
+}
+
+static void intel_hda_update_int_sts(IntelHDAState *d)
+{
+ uint32_t sts = 0;
+ uint32_t i;
+
+ /* update controller status */
+ if (d->rirb_sts & ICH6_RBSTS_IRQ) {
+ sts |= (1 << 30);
+ }
+ if (d->rirb_sts & ICH6_RBSTS_OVERRUN) {
+ sts |= (1 << 30);
+ }
+ if (d->state_sts & d->wake_en) {
+ sts |= (1 << 30);
+ }
+
+ /* update stream status */
+ for (i = 0; i < 8; i++) {
+ /* buffer completion interrupt */
+ if (d->st[i].ctl & (1 << 26)) {
+ sts |= (1 << i);
+ }
+ }
+
+ /* update global status */
+ if (sts & d->int_ctl) {
+ sts |= (1U << 31);
+ }
+
+ d->int_sts = sts;
+}
+
+static void intel_hda_update_irq(IntelHDAState *d)
+{
+ int msi = d->msi && msi_enabled(&d->pci);
+ int level;
+
+ intel_hda_update_int_sts(d);
+ if (d->int_sts & (1U << 31) && d->int_ctl & (1U << 31)) {
+ level = 1;
+ } else {
+ level = 0;
+ }
+ dprint(d, 2, "%s: level %d [%s]\n", __FUNCTION__,
+ level, msi ? "msi" : "intx");
+ if (msi) {
+ if (level) {
+ msi_notify(&d->pci, 0);
+ }
+ } else {
+ pci_set_irq(&d->pci, level);
+ }
+}
+
+static int intel_hda_send_command(IntelHDAState *d, uint32_t verb)
+{
+ uint32_t cad, nid, data;
+ HDACodecDevice *codec;
+ HDACodecDeviceClass *cdc;
+
+ cad = (verb >> 28) & 0x0f;
+ if (verb & (1 << 27)) {
+ /* indirect node addressing, not specified in HDA 1.0 */
+ dprint(d, 1, "%s: indirect node addressing (guest bug?)\n", __FUNCTION__);
+ return -1;
+ }
+ nid = (verb >> 20) & 0x7f;
+ data = verb & 0xfffff;
+
+ codec = hda_codec_find(&d->codecs, cad);
+ if (codec == NULL) {
+ dprint(d, 1, "%s: addressed non-existing codec\n", __FUNCTION__);
+ return -1;
+ }
+ cdc = HDA_CODEC_DEVICE_GET_CLASS(codec);
+ cdc->command(codec, nid, data);
+ return 0;
+}
+
+static void intel_hda_corb_run(IntelHDAState *d)
+{
+ hwaddr addr;
+ uint32_t rp, verb;
+
+ if (d->ics & ICH6_IRS_BUSY) {
+ dprint(d, 2, "%s: [icw] verb 0x%08x\n", __FUNCTION__, d->icw);
+ intel_hda_send_command(d, d->icw);
+ return;
+ }
+
+ for (;;) {
+ if (!(d->corb_ctl & ICH6_CORBCTL_RUN)) {
+ dprint(d, 2, "%s: !run\n", __FUNCTION__);
+ return;
+ }
+ if ((d->corb_rp & 0xff) == d->corb_wp) {
+ dprint(d, 2, "%s: corb ring empty\n", __FUNCTION__);
+ return;
+ }
+ if (d->rirb_count == d->rirb_cnt) {
+ dprint(d, 2, "%s: rirb count reached\n", __FUNCTION__);
+ return;
+ }
+
+ rp = (d->corb_rp + 1) & 0xff;
+ addr = intel_hda_addr(d->corb_lbase, d->corb_ubase);
+ verb = ldl_le_pci_dma(&d->pci, addr + 4*rp);
+ d->corb_rp = rp;
+
+ dprint(d, 2, "%s: [rp 0x%x] verb 0x%08x\n", __FUNCTION__, rp, verb);
+ intel_hda_send_command(d, verb);
+ }
+}
+
+static void intel_hda_response(HDACodecDevice *dev, bool solicited, uint32_t response)
+{
+ HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
+ IntelHDAState *d = container_of(bus, IntelHDAState, codecs);
+ hwaddr addr;
+ uint32_t wp, ex;
+
+ if (d->ics & ICH6_IRS_BUSY) {
+ dprint(d, 2, "%s: [irr] response 0x%x, cad 0x%x\n",
+ __FUNCTION__, response, dev->cad);
+ d->irr = response;
+ d->ics &= ~(ICH6_IRS_BUSY | 0xf0);
+ d->ics |= (ICH6_IRS_VALID | (dev->cad << 4));
+ return;
+ }
+
+ if (!(d->rirb_ctl & ICH6_RBCTL_DMA_EN)) {
+ dprint(d, 1, "%s: rirb dma disabled, drop codec response\n", __FUNCTION__);
+ return;
+ }
+
+ ex = (solicited ? 0 : (1 << 4)) | dev->cad;
+ wp = (d->rirb_wp + 1) & 0xff;
+ addr = intel_hda_addr(d->rirb_lbase, d->rirb_ubase);
+ stl_le_pci_dma(&d->pci, addr + 8*wp, response);
+ stl_le_pci_dma(&d->pci, addr + 8*wp + 4, ex);
+ d->rirb_wp = wp;
+
+ dprint(d, 2, "%s: [wp 0x%x] response 0x%x, extra 0x%x\n",
+ __FUNCTION__, wp, response, ex);
+
+ d->rirb_count++;
+ if (d->rirb_count == d->rirb_cnt) {
+ dprint(d, 2, "%s: rirb count reached (%d)\n", __FUNCTION__, d->rirb_count);
+ if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) {
+ d->rirb_sts |= ICH6_RBSTS_IRQ;
+ intel_hda_update_irq(d);
+ }
+ } else if ((d->corb_rp & 0xff) == d->corb_wp) {
+ dprint(d, 2, "%s: corb ring empty (%d/%d)\n", __FUNCTION__,
+ d->rirb_count, d->rirb_cnt);
+ if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) {
+ d->rirb_sts |= ICH6_RBSTS_IRQ;
+ intel_hda_update_irq(d);
+ }
+ }
+}
+
+static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
+ uint8_t *buf, uint32_t len)
+{
+ HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
+ IntelHDAState *d = container_of(bus, IntelHDAState, codecs);
+ hwaddr addr;
+ uint32_t s, copy, left;
+ IntelHDAStream *st;
+ bool irq = false;
+
+ st = output ? d->st + 4 : d->st;
+ for (s = 0; s < 4; s++) {
+ if (stnr == ((st[s].ctl >> 20) & 0x0f)) {
+ st = st + s;
+ break;
+ }
+ }
+ if (s == 4) {
+ return false;
+ }
+ if (st->bpl == NULL) {
+ return false;
+ }
+ if (st->ctl & (1 << 26)) {
+ /*
+ * Wait with the next DMA xfer until the guest
+ * has acked the buffer completion interrupt
+ */
+ return false;
+ }
+
+ left = len;
+ while (left > 0) {
+ copy = left;
+ if (copy > st->bsize - st->lpib)
+ copy = st->bsize - st->lpib;
+ if (copy > st->bpl[st->be].len - st->bp)
+ copy = st->bpl[st->be].len - st->bp;
+
+ dprint(d, 3, "dma: entry %d, pos %d/%d, copy %d\n",
+ st->be, st->bp, st->bpl[st->be].len, copy);
+
+ pci_dma_rw(&d->pci, st->bpl[st->be].addr + st->bp, buf, copy, !output);
+ st->lpib += copy;
+ st->bp += copy;
+ buf += copy;
+ left -= copy;
+
+ if (st->bpl[st->be].len == st->bp) {
+ /* bpl entry filled */
+ if (st->bpl[st->be].flags & 0x01) {
+ irq = true;
+ }
+ st->bp = 0;
+ st->be++;
+ if (st->be == st->bentries) {
+ /* bpl wrap around */
+ st->be = 0;
+ st->lpib = 0;
+ }
+ }
+ }
+ if (d->dp_lbase & 0x01) {
+ s = st - d->st;
+ addr = intel_hda_addr(d->dp_lbase & ~0x01, d->dp_ubase);
+ stl_le_pci_dma(&d->pci, addr + 8*s, st->lpib);
+ }
+ dprint(d, 3, "dma: --\n");
+
+ if (irq) {
+ st->ctl |= (1 << 26); /* buffer completion interrupt */
+ intel_hda_update_irq(d);
+ }
+ return true;
+}
+
+static void intel_hda_parse_bdl(IntelHDAState *d, IntelHDAStream *st)
+{
+ hwaddr addr;
+ uint8_t buf[16];
+ uint32_t i;
+
+ addr = intel_hda_addr(st->bdlp_lbase, st->bdlp_ubase);
+ st->bentries = st->lvi +1;
+ g_free(st->bpl);
+ st->bpl = g_malloc(sizeof(bpl) * st->bentries);
+ for (i = 0; i < st->bentries; i++, addr += 16) {
+ pci_dma_read(&d->pci, addr, buf, 16);
+ st->bpl[i].addr = le64_to_cpu(*(uint64_t *)buf);
+ st->bpl[i].len = le32_to_cpu(*(uint32_t *)(buf + 8));
+ st->bpl[i].flags = le32_to_cpu(*(uint32_t *)(buf + 12));
+ dprint(d, 1, "bdl/%d: 0x%" PRIx64 " +0x%x, 0x%x\n",
+ i, st->bpl[i].addr, st->bpl[i].len, st->bpl[i].flags);
+ }
+
+ st->bsize = st->cbl;
+ st->lpib = 0;
+ st->be = 0;
+ st->bp = 0;
+}
+
+static void intel_hda_notify_codecs(IntelHDAState *d, uint32_t stream, bool running, bool output)
+{
+ BusChild *kid;
+ HDACodecDevice *cdev;
+
+ QTAILQ_FOREACH(kid, &d->codecs.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ HDACodecDeviceClass *cdc;
+
+ cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
+ cdc = HDA_CODEC_DEVICE_GET_CLASS(cdev);
+ if (cdc->stream) {
+ cdc->stream(cdev, stream, running, output);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------- */
+
+static void intel_hda_set_g_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ if ((d->g_ctl & ICH6_GCTL_RESET) == 0) {
+ intel_hda_reset(DEVICE(d));
+ }
+}
+
+static void intel_hda_set_wake_en(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ intel_hda_update_irq(d);
+}
+
+static void intel_hda_set_state_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ intel_hda_update_irq(d);
+}
+
+static void intel_hda_set_int_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ intel_hda_update_irq(d);
+}
+
+static void intel_hda_get_wall_clk(IntelHDAState *d, const IntelHDAReg *reg)
+{
+ int64_t ns;
+
+ ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - d->wall_base_ns;
+ d->wall_clk = (uint32_t)(ns * 24 / 1000); /* 24 MHz */
+}
+
+static void intel_hda_set_corb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ intel_hda_corb_run(d);
+}
+
+static void intel_hda_set_corb_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ intel_hda_corb_run(d);
+}
+
+static void intel_hda_set_rirb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ if (d->rirb_wp & ICH6_RIRBWP_RST) {
+ d->rirb_wp = 0;
+ }
+}
+
+static void intel_hda_set_rirb_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ intel_hda_update_irq(d);
+
+ if ((old & ICH6_RBSTS_IRQ) && !(d->rirb_sts & ICH6_RBSTS_IRQ)) {
+ /* cleared ICH6_RBSTS_IRQ */
+ d->rirb_count = 0;
+ intel_hda_corb_run(d);
+ }
+}
+
+static void intel_hda_set_ics(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ if (d->ics & ICH6_IRS_BUSY) {
+ intel_hda_corb_run(d);
+ }
+}
+
+static void intel_hda_set_st_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
+{
+ bool output = reg->stream >= 4;
+ IntelHDAStream *st = d->st + reg->stream;
+
+ if (st->ctl & 0x01) {
+ /* reset */
+ dprint(d, 1, "st #%d: reset\n", reg->stream);
+ st->ctl = SD_STS_FIFO_READY << 24;
+ }
+ if ((st->ctl & 0x02) != (old & 0x02)) {
+ uint32_t stnr = (st->ctl >> 20) & 0x0f;
+ /* run bit flipped */
+ if (st->ctl & 0x02) {
+ /* start */
+ dprint(d, 1, "st #%d: start %d (ring buf %d bytes)\n",
+ reg->stream, stnr, st->cbl);
+ intel_hda_parse_bdl(d, st);
+ intel_hda_notify_codecs(d, stnr, true, output);
+ } else {
+ /* stop */
+ dprint(d, 1, "st #%d: stop %d\n", reg->stream, stnr);
+ intel_hda_notify_codecs(d, stnr, false, output);
+ }
+ }
+ intel_hda_update_irq(d);
+}
+
+/* --------------------------------------------------------------------- */
+
+#define ST_REG(_n, _o) (0x80 + (_n) * 0x20 + (_o))
+
+static const struct IntelHDAReg regtab[] = {
+ /* global */
+ [ ICH6_REG_GCAP ] = {
+ .name = "GCAP",
+ .size = 2,
+ .reset = 0x4401,
+ },
+ [ ICH6_REG_VMIN ] = {
+ .name = "VMIN",
+ .size = 1,
+ },
+ [ ICH6_REG_VMAJ ] = {
+ .name = "VMAJ",
+ .size = 1,
+ .reset = 1,
+ },
+ [ ICH6_REG_OUTPAY ] = {
+ .name = "OUTPAY",
+ .size = 2,
+ .reset = 0x3c,
+ },
+ [ ICH6_REG_INPAY ] = {
+ .name = "INPAY",
+ .size = 2,
+ .reset = 0x1d,
+ },
+ [ ICH6_REG_GCTL ] = {
+ .name = "GCTL",
+ .size = 4,
+ .wmask = 0x0103,
+ .offset = offsetof(IntelHDAState, g_ctl),
+ .whandler = intel_hda_set_g_ctl,
+ },
+ [ ICH6_REG_WAKEEN ] = {
+ .name = "WAKEEN",
+ .size = 2,
+ .wmask = 0x7fff,
+ .offset = offsetof(IntelHDAState, wake_en),
+ .whandler = intel_hda_set_wake_en,
+ },
+ [ ICH6_REG_STATESTS ] = {
+ .name = "STATESTS",
+ .size = 2,
+ .wmask = 0x7fff,
+ .wclear = 0x7fff,
+ .offset = offsetof(IntelHDAState, state_sts),
+ .whandler = intel_hda_set_state_sts,
+ },
+
+ /* interrupts */
+ [ ICH6_REG_INTCTL ] = {
+ .name = "INTCTL",
+ .size = 4,
+ .wmask = 0xc00000ff,
+ .offset = offsetof(IntelHDAState, int_ctl),
+ .whandler = intel_hda_set_int_ctl,
+ },
+ [ ICH6_REG_INTSTS ] = {
+ .name = "INTSTS",
+ .size = 4,
+ .wmask = 0xc00000ff,
+ .wclear = 0xc00000ff,
+ .offset = offsetof(IntelHDAState, int_sts),
+ },
+
+ /* misc */
+ [ ICH6_REG_WALLCLK ] = {
+ .name = "WALLCLK",
+ .size = 4,
+ .offset = offsetof(IntelHDAState, wall_clk),
+ .rhandler = intel_hda_get_wall_clk,
+ },
+ [ ICH6_REG_WALLCLK + 0x2000 ] = {
+ .name = "WALLCLK(alias)",
+ .size = 4,
+ .offset = offsetof(IntelHDAState, wall_clk),
+ .rhandler = intel_hda_get_wall_clk,
+ },
+
+ /* dma engine */
+ [ ICH6_REG_CORBLBASE ] = {
+ .name = "CORBLBASE",
+ .size = 4,
+ .wmask = 0xffffff80,
+ .offset = offsetof(IntelHDAState, corb_lbase),
+ },
+ [ ICH6_REG_CORBUBASE ] = {
+ .name = "CORBUBASE",
+ .size = 4,
+ .wmask = 0xffffffff,
+ .offset = offsetof(IntelHDAState, corb_ubase),
+ },
+ [ ICH6_REG_CORBWP ] = {
+ .name = "CORBWP",
+ .size = 2,
+ .wmask = 0xff,
+ .offset = offsetof(IntelHDAState, corb_wp),
+ .whandler = intel_hda_set_corb_wp,
+ },
+ [ ICH6_REG_CORBRP ] = {
+ .name = "CORBRP",
+ .size = 2,
+ .wmask = 0x80ff,
+ .offset = offsetof(IntelHDAState, corb_rp),
+ },
+ [ ICH6_REG_CORBCTL ] = {
+ .name = "CORBCTL",
+ .size = 1,
+ .wmask = 0x03,
+ .offset = offsetof(IntelHDAState, corb_ctl),
+ .whandler = intel_hda_set_corb_ctl,
+ },
+ [ ICH6_REG_CORBSTS ] = {
+ .name = "CORBSTS",
+ .size = 1,
+ .wmask = 0x01,
+ .wclear = 0x01,
+ .offset = offsetof(IntelHDAState, corb_sts),
+ },
+ [ ICH6_REG_CORBSIZE ] = {
+ .name = "CORBSIZE",
+ .size = 1,
+ .reset = 0x42,
+ .offset = offsetof(IntelHDAState, corb_size),
+ },
+ [ ICH6_REG_RIRBLBASE ] = {
+ .name = "RIRBLBASE",
+ .size = 4,
+ .wmask = 0xffffff80,
+ .offset = offsetof(IntelHDAState, rirb_lbase),
+ },
+ [ ICH6_REG_RIRBUBASE ] = {
+ .name = "RIRBUBASE",
+ .size = 4,
+ .wmask = 0xffffffff,
+ .offset = offsetof(IntelHDAState, rirb_ubase),
+ },
+ [ ICH6_REG_RIRBWP ] = {
+ .name = "RIRBWP",
+ .size = 2,
+ .wmask = 0x8000,
+ .offset = offsetof(IntelHDAState, rirb_wp),
+ .whandler = intel_hda_set_rirb_wp,
+ },
+ [ ICH6_REG_RINTCNT ] = {
+ .name = "RINTCNT",
+ .size = 2,
+ .wmask = 0xff,
+ .offset = offsetof(IntelHDAState, rirb_cnt),
+ },
+ [ ICH6_REG_RIRBCTL ] = {
+ .name = "RIRBCTL",
+ .size = 1,
+ .wmask = 0x07,
+ .offset = offsetof(IntelHDAState, rirb_ctl),
+ },
+ [ ICH6_REG_RIRBSTS ] = {
+ .name = "RIRBSTS",
+ .size = 1,
+ .wmask = 0x05,
+ .wclear = 0x05,
+ .offset = offsetof(IntelHDAState, rirb_sts),
+ .whandler = intel_hda_set_rirb_sts,
+ },
+ [ ICH6_REG_RIRBSIZE ] = {
+ .name = "RIRBSIZE",
+ .size = 1,
+ .reset = 0x42,
+ .offset = offsetof(IntelHDAState, rirb_size),
+ },
+
+ [ ICH6_REG_DPLBASE ] = {
+ .name = "DPLBASE",
+ .size = 4,
+ .wmask = 0xffffff81,
+ .offset = offsetof(IntelHDAState, dp_lbase),
+ },
+ [ ICH6_REG_DPUBASE ] = {
+ .name = "DPUBASE",
+ .size = 4,
+ .wmask = 0xffffffff,
+ .offset = offsetof(IntelHDAState, dp_ubase),
+ },
+
+ [ ICH6_REG_IC ] = {
+ .name = "ICW",
+ .size = 4,
+ .wmask = 0xffffffff,
+ .offset = offsetof(IntelHDAState, icw),
+ },
+ [ ICH6_REG_IR ] = {
+ .name = "IRR",
+ .size = 4,
+ .offset = offsetof(IntelHDAState, irr),
+ },
+ [ ICH6_REG_IRS ] = {
+ .name = "ICS",
+ .size = 2,
+ .wmask = 0x0003,
+ .wclear = 0x0002,
+ .offset = offsetof(IntelHDAState, ics),
+ .whandler = intel_hda_set_ics,
+ },
+
+#define HDA_STREAM(_t, _i) \
+ [ ST_REG(_i, ICH6_REG_SD_CTL) ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " CTL", \
+ .size = 4, \
+ .wmask = 0x1cff001f, \
+ .offset = offsetof(IntelHDAState, st[_i].ctl), \
+ .whandler = intel_hda_set_st_ctl, \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_CTL) + 2] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " CTL(stnr)", \
+ .size = 1, \
+ .shift = 16, \
+ .wmask = 0x00ff0000, \
+ .offset = offsetof(IntelHDAState, st[_i].ctl), \
+ .whandler = intel_hda_set_st_ctl, \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_STS)] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " CTL(sts)", \
+ .size = 1, \
+ .shift = 24, \
+ .wmask = 0x1c000000, \
+ .wclear = 0x1c000000, \
+ .offset = offsetof(IntelHDAState, st[_i].ctl), \
+ .whandler = intel_hda_set_st_ctl, \
+ .reset = SD_STS_FIFO_READY << 24 \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_LPIB) ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " LPIB", \
+ .size = 4, \
+ .offset = offsetof(IntelHDAState, st[_i].lpib), \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_LPIB) + 0x2000 ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " LPIB(alias)", \
+ .size = 4, \
+ .offset = offsetof(IntelHDAState, st[_i].lpib), \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_CBL) ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " CBL", \
+ .size = 4, \
+ .wmask = 0xffffffff, \
+ .offset = offsetof(IntelHDAState, st[_i].cbl), \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_LVI) ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " LVI", \
+ .size = 2, \
+ .wmask = 0x00ff, \
+ .offset = offsetof(IntelHDAState, st[_i].lvi), \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_FIFOSIZE) ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " FIFOS", \
+ .size = 2, \
+ .reset = HDA_BUFFER_SIZE, \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_FORMAT) ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " FMT", \
+ .size = 2, \
+ .wmask = 0x7f7f, \
+ .offset = offsetof(IntelHDAState, st[_i].fmt), \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_BDLPL) ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " BDLPL", \
+ .size = 4, \
+ .wmask = 0xffffff80, \
+ .offset = offsetof(IntelHDAState, st[_i].bdlp_lbase), \
+ }, \
+ [ ST_REG(_i, ICH6_REG_SD_BDLPU) ] = { \
+ .stream = _i, \
+ .name = _t stringify(_i) " BDLPU", \
+ .size = 4, \
+ .wmask = 0xffffffff, \
+ .offset = offsetof(IntelHDAState, st[_i].bdlp_ubase), \
+ }, \
+
+ HDA_STREAM("IN", 0)
+ HDA_STREAM("IN", 1)
+ HDA_STREAM("IN", 2)
+ HDA_STREAM("IN", 3)
+
+ HDA_STREAM("OUT", 4)
+ HDA_STREAM("OUT", 5)
+ HDA_STREAM("OUT", 6)
+ HDA_STREAM("OUT", 7)
+
+};
+
+static const IntelHDAReg *intel_hda_reg_find(IntelHDAState *d, hwaddr addr)
+{
+ const IntelHDAReg *reg;
+
+ if (addr >= ARRAY_SIZE(regtab)) {
+ goto noreg;
+ }
+ reg = regtab+addr;
+ if (reg->name == NULL) {
+ goto noreg;
+ }
+ return reg;
+
+noreg:
+ dprint(d, 1, "unknown register, addr 0x%x\n", (int) addr);
+ return NULL;
+}
+
+static uint32_t *intel_hda_reg_addr(IntelHDAState *d, const IntelHDAReg *reg)
+{
+ uint8_t *addr = (void*)d;
+
+ addr += reg->offset;
+ return (uint32_t*)addr;
+}
+
+static void intel_hda_reg_write(IntelHDAState *d, const IntelHDAReg *reg, uint32_t val,
+ uint32_t wmask)
+{
+ uint32_t *addr;
+ uint32_t old;
+
+ if (!reg) {
+ return;
+ }
+
+ if (d->debug) {
+ time_t now = time(NULL);
+ if (d->last_write && d->last_reg == reg && d->last_val == val) {
+ d->repeat_count++;
+ if (d->last_sec != now) {
+ dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
+ d->last_sec = now;
+ d->repeat_count = 0;
+ }
+ } else {
+ if (d->repeat_count) {
+ dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
+ }
+ dprint(d, 2, "write %-16s: 0x%x (%x)\n", reg->name, val, wmask);
+ d->last_write = 1;
+ d->last_reg = reg;
+ d->last_val = val;
+ d->last_sec = now;
+ d->repeat_count = 0;
+ }
+ }
+ assert(reg->offset != 0);
+
+ addr = intel_hda_reg_addr(d, reg);
+ old = *addr;
+
+ if (reg->shift) {
+ val <<= reg->shift;
+ wmask <<= reg->shift;
+ }
+ wmask &= reg->wmask;
+ *addr &= ~wmask;
+ *addr |= wmask & val;
+ *addr &= ~(val & reg->wclear);
+
+ if (reg->whandler) {
+ reg->whandler(d, reg, old);
+ }
+}
+
+static uint32_t intel_hda_reg_read(IntelHDAState *d, const IntelHDAReg *reg,
+ uint32_t rmask)
+{
+ uint32_t *addr, ret;
+
+ if (!reg) {
+ return 0;
+ }
+
+ if (reg->rhandler) {
+ reg->rhandler(d, reg);
+ }
+
+ if (reg->offset == 0) {
+ /* constant read-only register */
+ ret = reg->reset;
+ } else {
+ addr = intel_hda_reg_addr(d, reg);
+ ret = *addr;
+ if (reg->shift) {
+ ret >>= reg->shift;
+ }
+ ret &= rmask;
+ }
+ if (d->debug) {
+ time_t now = time(NULL);
+ if (!d->last_write && d->last_reg == reg && d->last_val == ret) {
+ d->repeat_count++;
+ if (d->last_sec != now) {
+ dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
+ d->last_sec = now;
+ d->repeat_count = 0;
+ }
+ } else {
+ if (d->repeat_count) {
+ dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
+ }
+ dprint(d, 2, "read %-16s: 0x%x (%x)\n", reg->name, ret, rmask);
+ d->last_write = 0;
+ d->last_reg = reg;
+ d->last_val = ret;
+ d->last_sec = now;
+ d->repeat_count = 0;
+ }
+ }
+ return ret;
+}
+
+static void intel_hda_regs_reset(IntelHDAState *d)
+{
+ uint32_t *addr;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(regtab); i++) {
+ if (regtab[i].name == NULL) {
+ continue;
+ }
+ if (regtab[i].offset == 0) {
+ continue;
+ }
+ addr = intel_hda_reg_addr(d, regtab + i);
+ *addr = regtab[i].reset;
+ }
+}
+
+/* --------------------------------------------------------------------- */
+
+static void intel_hda_mmio_writeb(void *opaque, hwaddr addr, uint32_t val)
+{
+ IntelHDAState *d = opaque;
+ const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
+
+ intel_hda_reg_write(d, reg, val, 0xff);
+}
+
+static void intel_hda_mmio_writew(void *opaque, hwaddr addr, uint32_t val)
+{
+ IntelHDAState *d = opaque;
+ const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
+
+ intel_hda_reg_write(d, reg, val, 0xffff);
+}
+
+static void intel_hda_mmio_writel(void *opaque, hwaddr addr, uint32_t val)
+{
+ IntelHDAState *d = opaque;
+ const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
+
+ intel_hda_reg_write(d, reg, val, 0xffffffff);
+}
+
+static uint32_t intel_hda_mmio_readb(void *opaque, hwaddr addr)
+{
+ IntelHDAState *d = opaque;
+ const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
+
+ return intel_hda_reg_read(d, reg, 0xff);
+}
+
+static uint32_t intel_hda_mmio_readw(void *opaque, hwaddr addr)
+{
+ IntelHDAState *d = opaque;
+ const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
+
+ return intel_hda_reg_read(d, reg, 0xffff);
+}
+
+static uint32_t intel_hda_mmio_readl(void *opaque, hwaddr addr)
+{
+ IntelHDAState *d = opaque;
+ const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
+
+ return intel_hda_reg_read(d, reg, 0xffffffff);
+}
+
+static const MemoryRegionOps intel_hda_mmio_ops = {
+ .old_mmio = {
+ .read = {
+ intel_hda_mmio_readb,
+ intel_hda_mmio_readw,
+ intel_hda_mmio_readl,
+ },
+ .write = {
+ intel_hda_mmio_writeb,
+ intel_hda_mmio_writew,
+ intel_hda_mmio_writel,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* --------------------------------------------------------------------- */
+
+static void intel_hda_reset(DeviceState *dev)
+{
+ BusChild *kid;
+ IntelHDAState *d = INTEL_HDA(dev);
+ HDACodecDevice *cdev;
+
+ intel_hda_regs_reset(d);
+ d->wall_base_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ /* reset codecs */
+ QTAILQ_FOREACH(kid, &d->codecs.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
+ device_reset(DEVICE(cdev));
+ d->state_sts |= (1 << cdev->cad);
+ }
+ intel_hda_update_irq(d);
+}
+
+static void intel_hda_realize(PCIDevice *pci, Error **errp)
+{
+ IntelHDAState *d = INTEL_HDA(pci);
+ uint8_t *conf = d->pci.config;
+
+ d->name = object_get_typename(OBJECT(d));
+
+ pci_config_set_interrupt_pin(conf, 1);
+
+ /* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */
+ conf[0x40] = 0x01;
+
+ memory_region_init_io(&d->mmio, OBJECT(d), &intel_hda_mmio_ops, d,
+ "intel-hda", 0x4000);
+ pci_register_bar(&d->pci, 0, 0, &d->mmio);
+ if (d->msi) {
+ msi_init(&d->pci, d->old_msi_addr ? 0x50 : 0x60, 1, true, false);
+ }
+
+ hda_codec_bus_init(DEVICE(pci), &d->codecs, sizeof(d->codecs),
+ intel_hda_response, intel_hda_xfer);
+}
+
+static void intel_hda_exit(PCIDevice *pci)
+{
+ IntelHDAState *d = INTEL_HDA(pci);
+
+ msi_uninit(&d->pci);
+}
+
+static int intel_hda_post_load(void *opaque, int version)
+{
+ IntelHDAState* d = opaque;
+ int i;
+
+ dprint(d, 1, "%s\n", __FUNCTION__);
+ for (i = 0; i < ARRAY_SIZE(d->st); i++) {
+ if (d->st[i].ctl & 0x02) {
+ intel_hda_parse_bdl(d, &d->st[i]);
+ }
+ }
+ intel_hda_update_irq(d);
+ return 0;
+}
+
+static const VMStateDescription vmstate_intel_hda_stream = {
+ .name = "intel-hda-stream",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ctl, IntelHDAStream),
+ VMSTATE_UINT32(lpib, IntelHDAStream),
+ VMSTATE_UINT32(cbl, IntelHDAStream),
+ VMSTATE_UINT32(lvi, IntelHDAStream),
+ VMSTATE_UINT32(fmt, IntelHDAStream),
+ VMSTATE_UINT32(bdlp_lbase, IntelHDAStream),
+ VMSTATE_UINT32(bdlp_ubase, IntelHDAStream),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_intel_hda = {
+ .name = "intel-hda",
+ .version_id = 1,
+ .post_load = intel_hda_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(pci, IntelHDAState),
+
+ /* registers */
+ VMSTATE_UINT32(g_ctl, IntelHDAState),
+ VMSTATE_UINT32(wake_en, IntelHDAState),
+ VMSTATE_UINT32(state_sts, IntelHDAState),
+ VMSTATE_UINT32(int_ctl, IntelHDAState),
+ VMSTATE_UINT32(int_sts, IntelHDAState),
+ VMSTATE_UINT32(wall_clk, IntelHDAState),
+ VMSTATE_UINT32(corb_lbase, IntelHDAState),
+ VMSTATE_UINT32(corb_ubase, IntelHDAState),
+ VMSTATE_UINT32(corb_rp, IntelHDAState),
+ VMSTATE_UINT32(corb_wp, IntelHDAState),
+ VMSTATE_UINT32(corb_ctl, IntelHDAState),
+ VMSTATE_UINT32(corb_sts, IntelHDAState),
+ VMSTATE_UINT32(corb_size, IntelHDAState),
+ VMSTATE_UINT32(rirb_lbase, IntelHDAState),
+ VMSTATE_UINT32(rirb_ubase, IntelHDAState),
+ VMSTATE_UINT32(rirb_wp, IntelHDAState),
+ VMSTATE_UINT32(rirb_cnt, IntelHDAState),
+ VMSTATE_UINT32(rirb_ctl, IntelHDAState),
+ VMSTATE_UINT32(rirb_sts, IntelHDAState),
+ VMSTATE_UINT32(rirb_size, IntelHDAState),
+ VMSTATE_UINT32(dp_lbase, IntelHDAState),
+ VMSTATE_UINT32(dp_ubase, IntelHDAState),
+ VMSTATE_UINT32(icw, IntelHDAState),
+ VMSTATE_UINT32(irr, IntelHDAState),
+ VMSTATE_UINT32(ics, IntelHDAState),
+ VMSTATE_STRUCT_ARRAY(st, IntelHDAState, 8, 0,
+ vmstate_intel_hda_stream,
+ IntelHDAStream),
+
+ /* additional state info */
+ VMSTATE_UINT32(rirb_count, IntelHDAState),
+ VMSTATE_INT64(wall_base_ns, IntelHDAState),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property intel_hda_properties[] = {
+ DEFINE_PROP_UINT32("debug", IntelHDAState, debug, 0),
+ DEFINE_PROP_UINT32("msi", IntelHDAState, msi, 1),
+ DEFINE_PROP_BOOL("old_msi_addr", IntelHDAState, old_msi_addr, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void intel_hda_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = intel_hda_realize;
+ k->exit = intel_hda_exit;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->class_id = PCI_CLASS_MULTIMEDIA_HD_AUDIO;
+ dc->reset = intel_hda_reset;
+ dc->vmsd = &vmstate_intel_hda;
+ dc->props = intel_hda_properties;
+}
+
+static void intel_hda_class_init_ich6(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->device_id = 0x2668;
+ k->revision = 1;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = "Intel HD Audio Controller (ich6)";
+}
+
+static void intel_hda_class_init_ich9(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->device_id = 0x293e;
+ k->revision = 3;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = "Intel HD Audio Controller (ich9)";
+}
+
+static const TypeInfo intel_hda_info = {
+ .name = TYPE_INTEL_HDA_GENERIC,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(IntelHDAState),
+ .class_init = intel_hda_class_init,
+ .abstract = true,
+};
+
+static const TypeInfo intel_hda_info_ich6 = {
+ .name = "intel-hda",
+ .parent = TYPE_INTEL_HDA_GENERIC,
+ .class_init = intel_hda_class_init_ich6,
+};
+
+static const TypeInfo intel_hda_info_ich9 = {
+ .name = "ich9-intel-hda",
+ .parent = TYPE_INTEL_HDA_GENERIC,
+ .class_init = intel_hda_class_init_ich9,
+};
+
+static void hda_codec_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->init = hda_codec_dev_init;
+ k->exit = hda_codec_dev_exit;
+ set_bit(DEVICE_CATEGORY_SOUND, k->categories);
+ k->bus_type = TYPE_HDA_BUS;
+ k->props = hda_props;
+}
+
+static const TypeInfo hda_codec_device_type_info = {
+ .name = TYPE_HDA_CODEC_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(HDACodecDevice),
+ .abstract = true,
+ .class_size = sizeof(HDACodecDeviceClass),
+ .class_init = hda_codec_device_class_init,
+};
+
+/*
+ * create intel hda controller with codec attached to it,
+ * so '-soundhw hda' works.
+ */
+static int intel_hda_and_codec_init(PCIBus *bus)
+{
+ DeviceState *controller;
+ BusState *hdabus;
+ DeviceState *codec;
+
+ controller = DEVICE(pci_create_simple(bus, -1, "intel-hda"));
+ hdabus = QLIST_FIRST(&controller->child_bus);
+ codec = qdev_create(hdabus, "hda-duplex");
+ qdev_init_nofail(codec);
+ return 0;
+}
+
+static void intel_hda_register_types(void)
+{
+ type_register_static(&hda_codec_bus_info);
+ type_register_static(&intel_hda_info);
+ type_register_static(&intel_hda_info_ich6);
+ type_register_static(&intel_hda_info_ich9);
+ type_register_static(&hda_codec_device_type_info);
+ pci_register_soundhw("hda", "Intel HD Audio", intel_hda_and_codec_init);
+}
+
+type_init(intel_hda_register_types)
diff --git a/hw/audio/intel-hda.h b/hw/audio/intel-hda.h
new file mode 100644
index 00000000..d784bcf5
--- /dev/null
+++ b/hw/audio/intel-hda.h
@@ -0,0 +1,72 @@
+#ifndef HW_INTEL_HDA_H
+#define HW_INTEL_HDA_H
+
+#include "hw/qdev.h"
+
+/* --------------------------------------------------------------------- */
+/* hda bus */
+
+#define TYPE_HDA_CODEC_DEVICE "hda-codec"
+#define HDA_CODEC_DEVICE(obj) \
+ OBJECT_CHECK(HDACodecDevice, (obj), TYPE_HDA_CODEC_DEVICE)
+#define HDA_CODEC_DEVICE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(HDACodecDeviceClass, (klass), TYPE_HDA_CODEC_DEVICE)
+#define HDA_CODEC_DEVICE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(HDACodecDeviceClass, (obj), TYPE_HDA_CODEC_DEVICE)
+
+#define TYPE_HDA_BUS "HDA"
+#define HDA_BUS(obj) OBJECT_CHECK(HDACodecBus, (obj), TYPE_HDA_BUS)
+
+typedef struct HDACodecBus HDACodecBus;
+typedef struct HDACodecDevice HDACodecDevice;
+
+typedef void (*hda_codec_response_func)(HDACodecDevice *dev,
+ bool solicited, uint32_t response);
+typedef bool (*hda_codec_xfer_func)(HDACodecDevice *dev,
+ uint32_t stnr, bool output,
+ uint8_t *buf, uint32_t len);
+
+struct HDACodecBus {
+ BusState qbus;
+ uint32_t next_cad;
+ hda_codec_response_func response;
+ hda_codec_xfer_func xfer;
+};
+
+typedef struct HDACodecDeviceClass
+{
+ DeviceClass parent_class;
+
+ int (*init)(HDACodecDevice *dev);
+ int (*exit)(HDACodecDevice *dev);
+ void (*command)(HDACodecDevice *dev, uint32_t nid, uint32_t data);
+ void (*stream)(HDACodecDevice *dev, uint32_t stnr, bool running, bool output);
+} HDACodecDeviceClass;
+
+struct HDACodecDevice {
+ DeviceState qdev;
+ uint32_t cad; /* codec address */
+};
+
+void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus, size_t bus_size,
+ hda_codec_response_func response,
+ hda_codec_xfer_func xfer);
+HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad);
+
+void hda_codec_response(HDACodecDevice *dev, bool solicited, uint32_t response);
+bool hda_codec_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
+ uint8_t *buf, uint32_t len);
+
+/* --------------------------------------------------------------------- */
+
+#define dprint(_dev, _level, _fmt, ...) \
+ do { \
+ if (_dev->debug >= _level) { \
+ fprintf(stderr, "%s: ", _dev->name); \
+ fprintf(stderr, _fmt, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+/* --------------------------------------------------------------------- */
+
+#endif
diff --git a/hw/audio/lm4549.c b/hw/audio/lm4549.c
new file mode 100644
index 00000000..380ef603
--- /dev/null
+++ b/hw/audio/lm4549.c
@@ -0,0 +1,335 @@
+/*
+ * LM4549 Audio Codec Interface
+ *
+ * Copyright (c) 2011
+ * Written by Mathieu Sonet - www.elasticsheep.com
+ *
+ * This code is licensed under the GPL.
+ *
+ * *****************************************************************
+ *
+ * This driver emulates the LM4549 codec.
+ *
+ * It supports only one playback voice and no record voice.
+ */
+
+#include "hw/hw.h"
+#include "audio/audio.h"
+#include "lm4549.h"
+
+#if 0
+#define LM4549_DEBUG 1
+#endif
+
+#if 0
+#define LM4549_DUMP_DAC_INPUT 1
+#endif
+
+#ifdef LM4549_DEBUG
+#define DPRINTF(fmt, ...) \
+do { printf("lm4549: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#if defined(LM4549_DUMP_DAC_INPUT)
+#include <stdio.h>
+static FILE *fp_dac_input;
+#endif
+
+/* LM4549 register list */
+enum {
+ LM4549_Reset = 0x00,
+ LM4549_Master_Volume = 0x02,
+ LM4549_Line_Out_Volume = 0x04,
+ LM4549_Master_Volume_Mono = 0x06,
+ LM4549_PC_Beep_Volume = 0x0A,
+ LM4549_Phone_Volume = 0x0C,
+ LM4549_Mic_Volume = 0x0E,
+ LM4549_Line_In_Volume = 0x10,
+ LM4549_CD_Volume = 0x12,
+ LM4549_Video_Volume = 0x14,
+ LM4549_Aux_Volume = 0x16,
+ LM4549_PCM_Out_Volume = 0x18,
+ LM4549_Record_Select = 0x1A,
+ LM4549_Record_Gain = 0x1C,
+ LM4549_General_Purpose = 0x20,
+ LM4549_3D_Control = 0x22,
+ LM4549_Powerdown_Ctrl_Stat = 0x26,
+ LM4549_Ext_Audio_ID = 0x28,
+ LM4549_Ext_Audio_Stat_Ctrl = 0x2A,
+ LM4549_PCM_Front_DAC_Rate = 0x2C,
+ LM4549_PCM_ADC_Rate = 0x32,
+ LM4549_Vendor_ID1 = 0x7C,
+ LM4549_Vendor_ID2 = 0x7E
+};
+
+static void lm4549_reset(lm4549_state *s)
+{
+ uint16_t *regfile = s->regfile;
+
+ regfile[LM4549_Reset] = 0x0d50;
+ regfile[LM4549_Master_Volume] = 0x8008;
+ regfile[LM4549_Line_Out_Volume] = 0x8000;
+ regfile[LM4549_Master_Volume_Mono] = 0x8000;
+ regfile[LM4549_PC_Beep_Volume] = 0x0000;
+ regfile[LM4549_Phone_Volume] = 0x8008;
+ regfile[LM4549_Mic_Volume] = 0x8008;
+ regfile[LM4549_Line_In_Volume] = 0x8808;
+ regfile[LM4549_CD_Volume] = 0x8808;
+ regfile[LM4549_Video_Volume] = 0x8808;
+ regfile[LM4549_Aux_Volume] = 0x8808;
+ regfile[LM4549_PCM_Out_Volume] = 0x8808;
+ regfile[LM4549_Record_Select] = 0x0000;
+ regfile[LM4549_Record_Gain] = 0x8000;
+ regfile[LM4549_General_Purpose] = 0x0000;
+ regfile[LM4549_3D_Control] = 0x0101;
+ regfile[LM4549_Powerdown_Ctrl_Stat] = 0x000f;
+ regfile[LM4549_Ext_Audio_ID] = 0x0001;
+ regfile[LM4549_Ext_Audio_Stat_Ctrl] = 0x0000;
+ regfile[LM4549_PCM_Front_DAC_Rate] = 0xbb80;
+ regfile[LM4549_PCM_ADC_Rate] = 0xbb80;
+ regfile[LM4549_Vendor_ID1] = 0x4e53;
+ regfile[LM4549_Vendor_ID2] = 0x4331;
+}
+
+static void lm4549_audio_transfer(lm4549_state *s)
+{
+ uint32_t written_bytes, written_samples;
+ uint32_t i;
+
+ /* Activate the voice */
+ AUD_set_active_out(s->voice, 1);
+ s->voice_is_active = 1;
+
+ /* Try to write the buffer content */
+ written_bytes = AUD_write(s->voice, s->buffer,
+ s->buffer_level * sizeof(uint16_t));
+ written_samples = written_bytes >> 1;
+
+#if defined(LM4549_DUMP_DAC_INPUT)
+ fwrite(s->buffer, sizeof(uint8_t), written_bytes, fp_dac_input);
+#endif
+
+ s->buffer_level -= written_samples;
+
+ if (s->buffer_level > 0) {
+ /* Move the data back to the start of the buffer */
+ for (i = 0; i < s->buffer_level; i++) {
+ s->buffer[i] = s->buffer[i + written_samples];
+ }
+ }
+}
+
+static void lm4549_audio_out_callback(void *opaque, int free)
+{
+ lm4549_state *s = (lm4549_state *)opaque;
+ static uint32_t prev_buffer_level;
+
+#ifdef LM4549_DEBUG
+ int size = AUD_get_buffer_size_out(s->voice);
+ DPRINTF("audio_out_callback size = %i free = %i\n", size, free);
+#endif
+
+ /* Detect that no data are consumed
+ => disable the voice */
+ if (s->buffer_level == prev_buffer_level) {
+ AUD_set_active_out(s->voice, 0);
+ s->voice_is_active = 0;
+ }
+ prev_buffer_level = s->buffer_level;
+
+ /* Check if a buffer transfer is pending */
+ if (s->buffer_level == LM4549_BUFFER_SIZE) {
+ lm4549_audio_transfer(s);
+
+ /* Request more data */
+ if (s->data_req_cb != NULL) {
+ (s->data_req_cb)(s->opaque);
+ }
+ }
+}
+
+uint32_t lm4549_read(lm4549_state *s, hwaddr offset)
+{
+ uint16_t *regfile = s->regfile;
+ uint32_t value = 0;
+
+ /* Read the stored value */
+ assert(offset < 128);
+ value = regfile[offset];
+
+ DPRINTF("read [0x%02x] = 0x%04x\n", offset, value);
+
+ return value;
+}
+
+void lm4549_write(lm4549_state *s,
+ hwaddr offset, uint32_t value)
+{
+ uint16_t *regfile = s->regfile;
+
+ assert(offset < 128);
+ DPRINTF("write [0x%02x] = 0x%04x\n", offset, value);
+
+ switch (offset) {
+ case LM4549_Reset:
+ lm4549_reset(s);
+ break;
+
+ case LM4549_PCM_Front_DAC_Rate:
+ regfile[LM4549_PCM_Front_DAC_Rate] = value;
+ DPRINTF("DAC rate change = %i\n", value);
+
+ /* Re-open a voice with the new sample rate */
+ struct audsettings as;
+ as.freq = value;
+ as.nchannels = 2;
+ as.fmt = AUD_FMT_S16;
+ as.endianness = 0;
+
+ s->voice = AUD_open_out(
+ &s->card,
+ s->voice,
+ "lm4549.out",
+ s,
+ lm4549_audio_out_callback,
+ &as
+ );
+ break;
+
+ case LM4549_Powerdown_Ctrl_Stat:
+ value &= ~0xf;
+ value |= regfile[LM4549_Powerdown_Ctrl_Stat] & 0xf;
+ regfile[LM4549_Powerdown_Ctrl_Stat] = value;
+ break;
+
+ case LM4549_Ext_Audio_ID:
+ case LM4549_Vendor_ID1:
+ case LM4549_Vendor_ID2:
+ DPRINTF("Write to read-only register 0x%x\n", (int)offset);
+ break;
+
+ default:
+ /* Store the new value */
+ regfile[offset] = value;
+ break;
+ }
+}
+
+uint32_t lm4549_write_samples(lm4549_state *s, uint32_t left, uint32_t right)
+{
+ /* The left and right samples are in 20-bit resolution.
+ The LM4549 has 18-bit resolution and only uses the bits [19:2].
+ This model supports 16-bit playback.
+ */
+
+ if (s->buffer_level > LM4549_BUFFER_SIZE - 2) {
+ DPRINTF("write_sample Buffer full\n");
+ return 0;
+ }
+
+ /* Store 16-bit samples in the buffer */
+ s->buffer[s->buffer_level++] = (left >> 4);
+ s->buffer[s->buffer_level++] = (right >> 4);
+
+ if (s->buffer_level == LM4549_BUFFER_SIZE) {
+ /* Trigger the transfer of the buffer to the audio host */
+ lm4549_audio_transfer(s);
+ }
+
+ return 1;
+}
+
+static int lm4549_post_load(void *opaque, int version_id)
+{
+ lm4549_state *s = (lm4549_state *)opaque;
+ uint16_t *regfile = s->regfile;
+
+ /* Re-open a voice with the current sample rate */
+ uint32_t freq = regfile[LM4549_PCM_Front_DAC_Rate];
+
+ DPRINTF("post_load freq = %i\n", freq);
+ DPRINTF("post_load voice_is_active = %i\n", s->voice_is_active);
+
+ struct audsettings as;
+ as.freq = freq;
+ as.nchannels = 2;
+ as.fmt = AUD_FMT_S16;
+ as.endianness = 0;
+
+ s->voice = AUD_open_out(
+ &s->card,
+ s->voice,
+ "lm4549.out",
+ s,
+ lm4549_audio_out_callback,
+ &as
+ );
+
+ /* Request data */
+ if (s->voice_is_active == 1) {
+ lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->voice));
+ }
+
+ return 0;
+}
+
+void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque)
+{
+ struct audsettings as;
+
+ /* Store the callback and opaque pointer */
+ s->data_req_cb = data_req_cb;
+ s->opaque = opaque;
+
+ /* Init the registers */
+ lm4549_reset(s);
+
+ /* Register an audio card */
+ AUD_register_card("lm4549", &s->card);
+
+ /* Open a default voice */
+ as.freq = 48000;
+ as.nchannels = 2;
+ as.fmt = AUD_FMT_S16;
+ as.endianness = 0;
+
+ s->voice = AUD_open_out(
+ &s->card,
+ s->voice,
+ "lm4549.out",
+ s,
+ lm4549_audio_out_callback,
+ &as
+ );
+
+ AUD_set_volume_out(s->voice, 0, 255, 255);
+
+ s->voice_is_active = 0;
+
+ /* Reset the input buffer */
+ memset(s->buffer, 0x00, sizeof(s->buffer));
+ s->buffer_level = 0;
+
+#if defined(LM4549_DUMP_DAC_INPUT)
+ fp_dac_input = fopen("lm4549_dac_input.pcm", "wb");
+ if (!fp_dac_input) {
+ hw_error("Unable to open lm4549_dac_input.pcm for writing\n");
+ }
+#endif
+}
+
+const VMStateDescription vmstate_lm4549_state = {
+ .name = "lm4549_state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = lm4549_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(voice_is_active, lm4549_state),
+ VMSTATE_UINT16_ARRAY(regfile, lm4549_state, 128),
+ VMSTATE_UINT16_ARRAY(buffer, lm4549_state, LM4549_BUFFER_SIZE),
+ VMSTATE_UINT32(buffer_level, lm4549_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
diff --git a/hw/audio/lm4549.h b/hw/audio/lm4549.h
new file mode 100644
index 00000000..812a7a44
--- /dev/null
+++ b/hw/audio/lm4549.h
@@ -0,0 +1,43 @@
+/*
+ * LM4549 Audio Codec Interface
+ *
+ * Copyright (c) 2011
+ * Written by Mathieu Sonet - www.elasticsheep.com
+ *
+ * This code is licensed under the GPL.
+ *
+ * *****************************************************************
+ */
+
+#ifndef HW_LM4549_H
+#define HW_LM4549_H
+
+#include "audio/audio.h"
+
+typedef void (*lm4549_callback)(void *opaque);
+
+#define LM4549_BUFFER_SIZE (512 * 2) /* 512 16-bit stereo samples */
+
+
+typedef struct {
+ QEMUSoundCard card;
+ SWVoiceOut *voice;
+ uint32_t voice_is_active;
+
+ uint16_t regfile[128];
+ lm4549_callback data_req_cb;
+ void *opaque;
+
+ uint16_t buffer[LM4549_BUFFER_SIZE];
+ uint32_t buffer_level;
+} lm4549_state;
+
+extern const VMStateDescription vmstate_lm4549_state;
+
+
+void lm4549_init(lm4549_state *s, lm4549_callback data_req, void *opaque);
+uint32_t lm4549_read(lm4549_state *s, hwaddr offset);
+void lm4549_write(lm4549_state *s, hwaddr offset, uint32_t value);
+uint32_t lm4549_write_samples(lm4549_state *s, uint32_t left, uint32_t right);
+
+#endif /* #ifndef HW_LM4549_H */
diff --git a/hw/audio/marvell_88w8618.c b/hw/audio/marvell_88w8618.c
new file mode 100644
index 00000000..86992677
--- /dev/null
+++ b/hw/audio/marvell_88w8618.c
@@ -0,0 +1,306 @@
+/*
+ * Marvell 88w8618 audio emulation extracted from
+ * Marvell MV88w8618 / Freecom MusicPal emulation.
+ *
+ * Copyright (c) 2008 Jan Kiszka
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "audio/audio.h"
+
+#define MP_AUDIO_SIZE 0x00001000
+
+/* Audio register offsets */
+#define MP_AUDIO_PLAYBACK_MODE 0x00
+#define MP_AUDIO_CLOCK_DIV 0x18
+#define MP_AUDIO_IRQ_STATUS 0x20
+#define MP_AUDIO_IRQ_ENABLE 0x24
+#define MP_AUDIO_TX_START_LO 0x28
+#define MP_AUDIO_TX_THRESHOLD 0x2C
+#define MP_AUDIO_TX_STATUS 0x38
+#define MP_AUDIO_TX_START_HI 0x40
+
+/* Status register and IRQ enable bits */
+#define MP_AUDIO_TX_HALF (1 << 6)
+#define MP_AUDIO_TX_FULL (1 << 7)
+
+/* Playback mode bits */
+#define MP_AUDIO_16BIT_SAMPLE (1 << 0)
+#define MP_AUDIO_PLAYBACK_EN (1 << 7)
+#define MP_AUDIO_CLOCK_24MHZ (1 << 9)
+#define MP_AUDIO_MONO (1 << 14)
+
+#define TYPE_MV88W8618_AUDIO "mv88w8618_audio"
+#define MV88W8618_AUDIO(obj) \
+ OBJECT_CHECK(mv88w8618_audio_state, (obj), TYPE_MV88W8618_AUDIO)
+
+typedef struct mv88w8618_audio_state {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ uint32_t playback_mode;
+ uint32_t status;
+ uint32_t irq_enable;
+ uint32_t phys_buf;
+ uint32_t target_buffer;
+ uint32_t threshold;
+ uint32_t play_pos;
+ uint32_t last_free;
+ uint32_t clock_div;
+ void *wm;
+} mv88w8618_audio_state;
+
+static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
+{
+ mv88w8618_audio_state *s = opaque;
+ int16_t *codec_buffer;
+ int8_t buf[4096];
+ int8_t *mem_buffer;
+ int pos, block_size;
+
+ if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
+ return;
+ }
+ if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
+ free_out <<= 1;
+ }
+ if (!(s->playback_mode & MP_AUDIO_MONO)) {
+ free_out <<= 1;
+ }
+ block_size = s->threshold / 2;
+ if (free_out - s->last_free < block_size) {
+ return;
+ }
+ if (block_size > 4096) {
+ return;
+ }
+ cpu_physical_memory_read(s->target_buffer + s->play_pos, buf, block_size);
+ mem_buffer = buf;
+ if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
+ if (s->playback_mode & MP_AUDIO_MONO) {
+ codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
+ for (pos = 0; pos < block_size; pos += 2) {
+ *codec_buffer++ = *(int16_t *)mem_buffer;
+ *codec_buffer++ = *(int16_t *)mem_buffer;
+ mem_buffer += 2;
+ }
+ } else {
+ memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
+ (uint32_t *)mem_buffer, block_size);
+ }
+ } else {
+ if (s->playback_mode & MP_AUDIO_MONO) {
+ codec_buffer = wm8750_dac_buffer(s->wm, block_size);
+ for (pos = 0; pos < block_size; pos++) {
+ *codec_buffer++ = cpu_to_le16(256 * *mem_buffer);
+ *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
+ }
+ } else {
+ codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
+ for (pos = 0; pos < block_size; pos += 2) {
+ *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
+ *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
+ }
+ }
+ }
+ wm8750_dac_commit(s->wm);
+
+ s->last_free = free_out - block_size;
+
+ if (s->play_pos == 0) {
+ s->status |= MP_AUDIO_TX_HALF;
+ s->play_pos = block_size;
+ } else {
+ s->status |= MP_AUDIO_TX_FULL;
+ s->play_pos = 0;
+ }
+
+ if (s->status & s->irq_enable) {
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
+{
+ int rate;
+
+ if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
+ rate = 24576000 / 64; /* 24.576MHz */
+ } else {
+ rate = 11289600 / 64; /* 11.2896MHz */
+ }
+ rate /= ((s->clock_div >> 8) & 0xff) + 1;
+
+ wm8750_set_bclk_in(s->wm, rate);
+}
+
+static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ mv88w8618_audio_state *s = opaque;
+
+ switch (offset) {
+ case MP_AUDIO_PLAYBACK_MODE:
+ return s->playback_mode;
+
+ case MP_AUDIO_CLOCK_DIV:
+ return s->clock_div;
+
+ case MP_AUDIO_IRQ_STATUS:
+ return s->status;
+
+ case MP_AUDIO_IRQ_ENABLE:
+ return s->irq_enable;
+
+ case MP_AUDIO_TX_STATUS:
+ return s->play_pos >> 2;
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_audio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ mv88w8618_audio_state *s = opaque;
+
+ switch (offset) {
+ case MP_AUDIO_PLAYBACK_MODE:
+ if (value & MP_AUDIO_PLAYBACK_EN &&
+ !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
+ s->status = 0;
+ s->last_free = 0;
+ s->play_pos = 0;
+ }
+ s->playback_mode = value;
+ mv88w8618_audio_clock_update(s);
+ break;
+
+ case MP_AUDIO_CLOCK_DIV:
+ s->clock_div = value;
+ s->last_free = 0;
+ s->play_pos = 0;
+ mv88w8618_audio_clock_update(s);
+ break;
+
+ case MP_AUDIO_IRQ_STATUS:
+ s->status &= ~value;
+ break;
+
+ case MP_AUDIO_IRQ_ENABLE:
+ s->irq_enable = value;
+ if (s->status & s->irq_enable) {
+ qemu_irq_raise(s->irq);
+ }
+ break;
+
+ case MP_AUDIO_TX_START_LO:
+ s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
+ s->target_buffer = s->phys_buf;
+ s->play_pos = 0;
+ s->last_free = 0;
+ break;
+
+ case MP_AUDIO_TX_THRESHOLD:
+ s->threshold = (value + 1) * 4;
+ break;
+
+ case MP_AUDIO_TX_START_HI:
+ s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
+ s->target_buffer = s->phys_buf;
+ s->play_pos = 0;
+ s->last_free = 0;
+ break;
+ }
+}
+
+static void mv88w8618_audio_reset(DeviceState *d)
+{
+ mv88w8618_audio_state *s = MV88W8618_AUDIO(d);
+
+ s->playback_mode = 0;
+ s->status = 0;
+ s->irq_enable = 0;
+ s->clock_div = 0;
+ s->threshold = 0;
+ s->phys_buf = 0;
+}
+
+static const MemoryRegionOps mv88w8618_audio_ops = {
+ .read = mv88w8618_audio_read,
+ .write = mv88w8618_audio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int mv88w8618_audio_init(SysBusDevice *dev)
+{
+ mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_audio_ops, s,
+ "audio", MP_AUDIO_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static const VMStateDescription mv88w8618_audio_vmsd = {
+ .name = "mv88w8618_audio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(playback_mode, mv88w8618_audio_state),
+ VMSTATE_UINT32(status, mv88w8618_audio_state),
+ VMSTATE_UINT32(irq_enable, mv88w8618_audio_state),
+ VMSTATE_UINT32(phys_buf, mv88w8618_audio_state),
+ VMSTATE_UINT32(target_buffer, mv88w8618_audio_state),
+ VMSTATE_UINT32(threshold, mv88w8618_audio_state),
+ VMSTATE_UINT32(play_pos, mv88w8618_audio_state),
+ VMSTATE_UINT32(last_free, mv88w8618_audio_state),
+ VMSTATE_UINT32(clock_div, mv88w8618_audio_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property mv88w8618_audio_properties[] = {
+ DEFINE_PROP_PTR("wm8750", mv88w8618_audio_state, wm),
+ {/* end of list */},
+};
+
+static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mv88w8618_audio_init;
+ dc->reset = mv88w8618_audio_reset;
+ dc->vmsd = &mv88w8618_audio_vmsd;
+ dc->props = mv88w8618_audio_properties;
+ /* Reason: pointer property "wm8750" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo mv88w8618_audio_info = {
+ .name = TYPE_MV88W8618_AUDIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(mv88w8618_audio_state),
+ .class_init = mv88w8618_audio_class_init,
+};
+
+static void mv88w8618_register_types(void)
+{
+ type_register_static(&mv88w8618_audio_info);
+}
+
+type_init(mv88w8618_register_types)
diff --git a/hw/audio/milkymist-ac97.c b/hw/audio/milkymist-ac97.c
new file mode 100644
index 00000000..28f55e85
--- /dev/null
+++ b/hw/audio/milkymist-ac97.c
@@ -0,0 +1,348 @@
+/*
+ * QEMU model of the Milkymist System Controller.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/ac97.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "audio/audio.h"
+#include "qemu/error-report.h"
+
+enum {
+ R_AC97_CTRL = 0,
+ R_AC97_ADDR,
+ R_AC97_DATAOUT,
+ R_AC97_DATAIN,
+ R_D_CTRL,
+ R_D_ADDR,
+ R_D_REMAINING,
+ R_RESERVED,
+ R_U_CTRL,
+ R_U_ADDR,
+ R_U_REMAINING,
+ R_MAX
+};
+
+enum {
+ AC97_CTRL_RQEN = (1<<0),
+ AC97_CTRL_WRITE = (1<<1),
+};
+
+enum {
+ CTRL_EN = (1<<0),
+};
+
+#define TYPE_MILKYMIST_AC97 "milkymist-ac97"
+#define MILKYMIST_AC97(obj) \
+ OBJECT_CHECK(MilkymistAC97State, (obj), TYPE_MILKYMIST_AC97)
+
+struct MilkymistAC97State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+
+ QEMUSoundCard card;
+ SWVoiceIn *voice_in;
+ SWVoiceOut *voice_out;
+
+ uint32_t regs[R_MAX];
+
+ qemu_irq crrequest_irq;
+ qemu_irq crreply_irq;
+ qemu_irq dmar_irq;
+ qemu_irq dmaw_irq;
+};
+typedef struct MilkymistAC97State MilkymistAC97State;
+
+static void update_voices(MilkymistAC97State *s)
+{
+ if (s->regs[R_D_CTRL] & CTRL_EN) {
+ AUD_set_active_out(s->voice_out, 1);
+ } else {
+ AUD_set_active_out(s->voice_out, 0);
+ }
+
+ if (s->regs[R_U_CTRL] & CTRL_EN) {
+ AUD_set_active_in(s->voice_in, 1);
+ } else {
+ AUD_set_active_in(s->voice_in, 0);
+ }
+}
+
+static uint64_t ac97_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistAC97State *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_AC97_CTRL:
+ case R_AC97_ADDR:
+ case R_AC97_DATAOUT:
+ case R_AC97_DATAIN:
+ case R_D_CTRL:
+ case R_D_ADDR:
+ case R_D_REMAINING:
+ case R_U_CTRL:
+ case R_U_ADDR:
+ case R_U_REMAINING:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_ac97: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_ac97_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void ac97_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistAC97State *s = opaque;
+
+ trace_milkymist_ac97_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_AC97_CTRL:
+ /* always raise an IRQ according to the direction */
+ if (value & AC97_CTRL_RQEN) {
+ if (value & AC97_CTRL_WRITE) {
+ trace_milkymist_ac97_pulse_irq_crrequest();
+ qemu_irq_pulse(s->crrequest_irq);
+ } else {
+ trace_milkymist_ac97_pulse_irq_crreply();
+ qemu_irq_pulse(s->crreply_irq);
+ }
+ }
+
+ /* RQEN is self clearing */
+ s->regs[addr] = value & ~AC97_CTRL_RQEN;
+ break;
+ case R_D_CTRL:
+ case R_U_CTRL:
+ s->regs[addr] = value;
+ update_voices(s);
+ break;
+ case R_AC97_ADDR:
+ case R_AC97_DATAOUT:
+ case R_AC97_DATAIN:
+ case R_D_ADDR:
+ case R_D_REMAINING:
+ case R_U_ADDR:
+ case R_U_REMAINING:
+ s->regs[addr] = value;
+ break;
+
+ default:
+ error_report("milkymist_ac97: write access to unknown register 0x"
+ TARGET_FMT_plx, addr);
+ break;
+ }
+
+}
+
+static const MemoryRegionOps ac97_mmio_ops = {
+ .read = ac97_read,
+ .write = ac97_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ac97_in_cb(void *opaque, int avail_b)
+{
+ MilkymistAC97State *s = opaque;
+ uint8_t buf[4096];
+ uint32_t remaining = s->regs[R_U_REMAINING];
+ int temp = audio_MIN(remaining, avail_b);
+ uint32_t addr = s->regs[R_U_ADDR];
+ int transferred = 0;
+
+ trace_milkymist_ac97_in_cb(avail_b, remaining);
+
+ /* prevent from raising an IRQ */
+ if (temp == 0) {
+ return;
+ }
+
+ while (temp) {
+ int acquired, to_copy;
+
+ to_copy = audio_MIN(temp, sizeof(buf));
+ acquired = AUD_read(s->voice_in, buf, to_copy);
+ if (!acquired) {
+ break;
+ }
+
+ cpu_physical_memory_write(addr, buf, acquired);
+
+ temp -= acquired;
+ addr += acquired;
+ transferred += acquired;
+ }
+
+ trace_milkymist_ac97_in_cb_transferred(transferred);
+
+ s->regs[R_U_ADDR] = addr;
+ s->regs[R_U_REMAINING] -= transferred;
+
+ if ((s->regs[R_U_CTRL] & CTRL_EN) && (s->regs[R_U_REMAINING] == 0)) {
+ trace_milkymist_ac97_pulse_irq_dmaw();
+ qemu_irq_pulse(s->dmaw_irq);
+ }
+}
+
+static void ac97_out_cb(void *opaque, int free_b)
+{
+ MilkymistAC97State *s = opaque;
+ uint8_t buf[4096];
+ uint32_t remaining = s->regs[R_D_REMAINING];
+ int temp = audio_MIN(remaining, free_b);
+ uint32_t addr = s->regs[R_D_ADDR];
+ int transferred = 0;
+
+ trace_milkymist_ac97_out_cb(free_b, remaining);
+
+ /* prevent from raising an IRQ */
+ if (temp == 0) {
+ return;
+ }
+
+ while (temp) {
+ int copied, to_copy;
+
+ to_copy = audio_MIN(temp, sizeof(buf));
+ cpu_physical_memory_read(addr, buf, to_copy);
+ copied = AUD_write(s->voice_out, buf, to_copy);
+ if (!copied) {
+ break;
+ }
+ temp -= copied;
+ addr += copied;
+ transferred += copied;
+ }
+
+ trace_milkymist_ac97_out_cb_transferred(transferred);
+
+ s->regs[R_D_ADDR] = addr;
+ s->regs[R_D_REMAINING] -= transferred;
+
+ if ((s->regs[R_D_CTRL] & CTRL_EN) && (s->regs[R_D_REMAINING] == 0)) {
+ trace_milkymist_ac97_pulse_irq_dmar();
+ qemu_irq_pulse(s->dmar_irq);
+ }
+}
+
+static void milkymist_ac97_reset(DeviceState *d)
+{
+ MilkymistAC97State *s = MILKYMIST_AC97(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ AUD_set_active_in(s->voice_in, 0);
+ AUD_set_active_out(s->voice_out, 0);
+}
+
+static int ac97_post_load(void *opaque, int version_id)
+{
+ MilkymistAC97State *s = opaque;
+
+ update_voices(s);
+
+ return 0;
+}
+
+static int milkymist_ac97_init(SysBusDevice *dev)
+{
+ MilkymistAC97State *s = MILKYMIST_AC97(dev);
+
+ struct audsettings as;
+ sysbus_init_irq(dev, &s->crrequest_irq);
+ sysbus_init_irq(dev, &s->crreply_irq);
+ sysbus_init_irq(dev, &s->dmar_irq);
+ sysbus_init_irq(dev, &s->dmaw_irq);
+
+ AUD_register_card("Milkymist AC'97", &s->card);
+
+ as.freq = 48000;
+ as.nchannels = 2;
+ as.fmt = AUD_FMT_S16;
+ as.endianness = 1;
+
+ s->voice_in = AUD_open_in(&s->card, s->voice_in,
+ "mm_ac97.in", s, ac97_in_cb, &as);
+ s->voice_out = AUD_open_out(&s->card, s->voice_out,
+ "mm_ac97.out", s, ac97_out_cb, &as);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &ac97_mmio_ops, s,
+ "milkymist-ac97", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_ac97 = {
+ .name = "milkymist-ac97",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ac97_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistAC97State, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void milkymist_ac97_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_ac97_init;
+ dc->reset = milkymist_ac97_reset;
+ dc->vmsd = &vmstate_milkymist_ac97;
+}
+
+static const TypeInfo milkymist_ac97_info = {
+ .name = TYPE_MILKYMIST_AC97,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistAC97State),
+ .class_init = milkymist_ac97_class_init,
+};
+
+static void milkymist_ac97_register_types(void)
+{
+ type_register_static(&milkymist_ac97_info);
+}
+
+type_init(milkymist_ac97_register_types)
diff --git a/hw/audio/pcspk.c b/hw/audio/pcspk.c
new file mode 100644
index 00000000..5266fb54
--- /dev/null
+++ b/hw/audio/pcspk.c
@@ -0,0 +1,213 @@
+/*
+ * QEMU PC speaker emulation
+ *
+ * Copyright (c) 2006 Joachim Henke
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/isa.h"
+#include "hw/audio/audio.h"
+#include "audio/audio.h"
+#include "qemu/timer.h"
+#include "hw/timer/i8254.h"
+#include "hw/audio/pcspk.h"
+
+#define PCSPK_BUF_LEN 1792
+#define PCSPK_SAMPLE_RATE 32000
+#define PCSPK_MAX_FREQ (PCSPK_SAMPLE_RATE >> 1)
+#define PCSPK_MIN_COUNT ((PIT_FREQ + PCSPK_MAX_FREQ - 1) / PCSPK_MAX_FREQ)
+
+#define PC_SPEAKER(obj) OBJECT_CHECK(PCSpkState, (obj), TYPE_PC_SPEAKER)
+
+typedef struct {
+ ISADevice parent_obj;
+
+ MemoryRegion ioport;
+ uint32_t iobase;
+ uint8_t sample_buf[PCSPK_BUF_LEN];
+ QEMUSoundCard card;
+ SWVoiceOut *voice;
+ void *pit;
+ unsigned int pit_count;
+ unsigned int samples;
+ unsigned int play_pos;
+ int data_on;
+ int dummy_refresh_clock;
+} PCSpkState;
+
+static const char *s_spk = "pcspk";
+static PCSpkState *pcspk_state;
+
+static inline void generate_samples(PCSpkState *s)
+{
+ unsigned int i;
+
+ if (s->pit_count) {
+ const uint32_t m = PCSPK_SAMPLE_RATE * s->pit_count;
+ const uint32_t n = ((uint64_t)PIT_FREQ << 32) / m;
+
+ /* multiple of wavelength for gapless looping */
+ s->samples = (PCSPK_BUF_LEN * PIT_FREQ / m * m / (PIT_FREQ >> 1) + 1) >> 1;
+ for (i = 0; i < s->samples; ++i)
+ s->sample_buf[i] = (64 & (n * i >> 25)) - 32;
+ } else {
+ s->samples = PCSPK_BUF_LEN;
+ for (i = 0; i < PCSPK_BUF_LEN; ++i)
+ s->sample_buf[i] = 128; /* silence */
+ }
+}
+
+static void pcspk_callback(void *opaque, int free)
+{
+ PCSpkState *s = opaque;
+ PITChannelInfo ch;
+ unsigned int n;
+
+ pit_get_channel_info(s->pit, 2, &ch);
+
+ if (ch.mode != 3) {
+ return;
+ }
+
+ n = ch.initial_count;
+ /* avoid frequencies that are not reproducible with sample rate */
+ if (n < PCSPK_MIN_COUNT)
+ n = 0;
+
+ if (s->pit_count != n) {
+ s->pit_count = n;
+ s->play_pos = 0;
+ generate_samples(s);
+ }
+
+ while (free > 0) {
+ n = audio_MIN(s->samples - s->play_pos, (unsigned int)free);
+ n = AUD_write(s->voice, &s->sample_buf[s->play_pos], n);
+ if (!n)
+ break;
+ s->play_pos = (s->play_pos + n) % s->samples;
+ free -= n;
+ }
+}
+
+static int pcspk_audio_init(ISABus *bus)
+{
+ PCSpkState *s = pcspk_state;
+ struct audsettings as = {PCSPK_SAMPLE_RATE, 1, AUD_FMT_U8, 0};
+
+ AUD_register_card(s_spk, &s->card);
+
+ s->voice = AUD_open_out(&s->card, s->voice, s_spk, s, pcspk_callback, &as);
+ if (!s->voice) {
+ AUD_log(s_spk, "Could not open voice\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static uint64_t pcspk_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCSpkState *s = opaque;
+ PITChannelInfo ch;
+
+ pit_get_channel_info(s->pit, 2, &ch);
+
+ s->dummy_refresh_clock ^= (1 << 4);
+
+ return ch.gate | (s->data_on << 1) | s->dummy_refresh_clock |
+ (ch.out << 5);
+}
+
+static void pcspk_io_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ PCSpkState *s = opaque;
+ const int gate = val & 1;
+
+ s->data_on = (val >> 1) & 1;
+ pit_set_gate(s->pit, 2, gate);
+ if (s->voice) {
+ if (gate) /* restart */
+ s->play_pos = 0;
+ AUD_set_active_out(s->voice, gate & s->data_on);
+ }
+}
+
+static const MemoryRegionOps pcspk_io_ops = {
+ .read = pcspk_io_read,
+ .write = pcspk_io_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void pcspk_initfn(Object *obj)
+{
+ PCSpkState *s = PC_SPEAKER(obj);
+
+ memory_region_init_io(&s->ioport, OBJECT(s), &pcspk_io_ops, s, "pcspk", 1);
+}
+
+static void pcspk_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ PCSpkState *s = PC_SPEAKER(dev);
+
+ isa_register_ioport(isadev, &s->ioport, s->iobase);
+
+ pcspk_state = s;
+}
+
+static Property pcspk_properties[] = {
+ DEFINE_PROP_UINT32("iobase", PCSpkState, iobase, -1),
+ DEFINE_PROP_PTR("pit", PCSpkState, pit),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pcspk_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pcspk_realizefn;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->props = pcspk_properties;
+ /* Reason: pointer property "pit", realize sets global pcspk_state */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo pcspk_info = {
+ .name = TYPE_PC_SPEAKER,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(PCSpkState),
+ .instance_init = pcspk_initfn,
+ .class_init = pcspk_class_initfn,
+};
+
+static void pcspk_register(void)
+{
+ type_register_static(&pcspk_info);
+ isa_register_soundhw("pcspk", "PC speaker", pcspk_audio_init);
+}
+type_init(pcspk_register)
diff --git a/hw/audio/pl041.c b/hw/audio/pl041.c
new file mode 100644
index 00000000..19982f24
--- /dev/null
+++ b/hw/audio/pl041.c
@@ -0,0 +1,649 @@
+/*
+ * Arm PrimeCell PL041 Advanced Audio Codec Interface
+ *
+ * Copyright (c) 2011
+ * Written by Mathieu Sonet - www.elasticsheep.com
+ *
+ * This code is licensed under the GPL.
+ *
+ * *****************************************************************
+ *
+ * This driver emulates the ARM AACI interface
+ * connected to a LM4549 codec.
+ *
+ * Limitations:
+ * - Supports only a playback on one channel (Versatile/Vexpress)
+ * - Supports only one TX FIFO in compact-mode or non-compact mode.
+ * - Supports playback of 12, 16, 18 and 20 bits samples.
+ * - Record is not supported.
+ * - The PL041 is hardwired to a LM4549 codec.
+ *
+ */
+
+#include "hw/sysbus.h"
+
+#include "pl041.h"
+#include "lm4549.h"
+
+#if 0
+#define PL041_DEBUG_LEVEL 1
+#endif
+
+#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 1)
+#define DBG_L1(fmt, ...) \
+do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DBG_L1(fmt, ...) \
+do { } while (0)
+#endif
+
+#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 2)
+#define DBG_L2(fmt, ...) \
+do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DBG_L2(fmt, ...) \
+do { } while (0)
+#endif
+
+
+#define MAX_FIFO_DEPTH (1024)
+#define DEFAULT_FIFO_DEPTH (8)
+
+#define SLOT1_RW (1 << 19)
+
+/* This FIFO only stores 20-bit samples on 32-bit words.
+ So its level is independent of the selected mode */
+typedef struct {
+ uint32_t level;
+ uint32_t data[MAX_FIFO_DEPTH];
+} pl041_fifo;
+
+typedef struct {
+ pl041_fifo tx_fifo;
+ uint8_t tx_enabled;
+ uint8_t tx_compact_mode;
+ uint8_t tx_sample_size;
+
+ pl041_fifo rx_fifo;
+ uint8_t rx_enabled;
+ uint8_t rx_compact_mode;
+ uint8_t rx_sample_size;
+} pl041_channel;
+
+#define TYPE_PL041 "pl041"
+#define PL041(obj) OBJECT_CHECK(PL041State, (obj), TYPE_PL041)
+
+typedef struct PL041State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ uint32_t fifo_depth; /* FIFO depth in non-compact mode */
+
+ pl041_regfile regs;
+ pl041_channel fifo1;
+ lm4549_state codec;
+} PL041State;
+
+
+static const unsigned char pl041_default_id[8] = {
+ 0x41, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1
+};
+
+#if defined(PL041_DEBUG_LEVEL)
+#define REGISTER(name, offset) #name,
+static const char *pl041_regs_name[] = {
+ #include "pl041.hx"
+};
+#undef REGISTER
+#endif
+
+
+#if defined(PL041_DEBUG_LEVEL)
+static const char *get_reg_name(hwaddr offset)
+{
+ if (offset <= PL041_dr1_7) {
+ return pl041_regs_name[offset >> 2];
+ }
+
+ return "unknown";
+}
+#endif
+
+static uint8_t pl041_compute_periphid3(PL041State *s)
+{
+ uint8_t id3 = 1; /* One channel */
+
+ /* Add the fifo depth information */
+ switch (s->fifo_depth) {
+ case 8:
+ id3 |= 0 << 3;
+ break;
+ case 32:
+ id3 |= 1 << 3;
+ break;
+ case 64:
+ id3 |= 2 << 3;
+ break;
+ case 128:
+ id3 |= 3 << 3;
+ break;
+ case 256:
+ id3 |= 4 << 3;
+ break;
+ case 512:
+ id3 |= 5 << 3;
+ break;
+ case 1024:
+ id3 |= 6 << 3;
+ break;
+ case 2048:
+ id3 |= 7 << 3;
+ break;
+ }
+
+ return id3;
+}
+
+static void pl041_reset(PL041State *s)
+{
+ DBG_L1("pl041_reset\n");
+
+ memset(&s->regs, 0x00, sizeof(pl041_regfile));
+
+ s->regs.slfr = SL1TXEMPTY | SL2TXEMPTY | SL12TXEMPTY;
+ s->regs.sr1 = TXFE | RXFE | TXHE;
+ s->regs.isr1 = 0;
+
+ memset(&s->fifo1, 0x00, sizeof(s->fifo1));
+}
+
+
+static void pl041_fifo1_write(PL041State *s, uint32_t value)
+{
+ pl041_channel *channel = &s->fifo1;
+ pl041_fifo *fifo = &s->fifo1.tx_fifo;
+
+ /* Push the value in the FIFO */
+ if (channel->tx_compact_mode == 0) {
+ /* Non-compact mode */
+
+ if (fifo->level < s->fifo_depth) {
+ /* Pad the value with 0 to obtain a 20-bit sample */
+ switch (channel->tx_sample_size) {
+ case 12:
+ value = (value << 8) & 0xFFFFF;
+ break;
+ case 16:
+ value = (value << 4) & 0xFFFFF;
+ break;
+ case 18:
+ value = (value << 2) & 0xFFFFF;
+ break;
+ case 20:
+ default:
+ break;
+ }
+
+ /* Store the sample in the FIFO */
+ fifo->data[fifo->level++] = value;
+ }
+#if defined(PL041_DEBUG_LEVEL)
+ else {
+ DBG_L1("fifo1 write: overrun\n");
+ }
+#endif
+ } else {
+ /* Compact mode */
+
+ if ((fifo->level + 2) < s->fifo_depth) {
+ uint32_t i = 0;
+ uint32_t sample = 0;
+
+ for (i = 0; i < 2; i++) {
+ sample = value & 0xFFFF;
+ value = value >> 16;
+
+ /* Pad each sample with 0 to obtain a 20-bit sample */
+ switch (channel->tx_sample_size) {
+ case 12:
+ sample = sample << 8;
+ break;
+ case 16:
+ default:
+ sample = sample << 4;
+ break;
+ }
+
+ /* Store the sample in the FIFO */
+ fifo->data[fifo->level++] = sample;
+ }
+ }
+#if defined(PL041_DEBUG_LEVEL)
+ else {
+ DBG_L1("fifo1 write: overrun\n");
+ }
+#endif
+ }
+
+ /* Update the status register */
+ if (fifo->level > 0) {
+ s->regs.sr1 &= ~(TXUNDERRUN | TXFE);
+ }
+
+ if (fifo->level >= (s->fifo_depth / 2)) {
+ s->regs.sr1 &= ~TXHE;
+ }
+
+ if (fifo->level >= s->fifo_depth) {
+ s->regs.sr1 |= TXFF;
+ }
+
+ DBG_L2("fifo1_push sr1 = 0x%08x\n", s->regs.sr1);
+}
+
+static void pl041_fifo1_transmit(PL041State *s)
+{
+ pl041_channel *channel = &s->fifo1;
+ pl041_fifo *fifo = &s->fifo1.tx_fifo;
+ uint32_t slots = s->regs.txcr1 & TXSLOT_MASK;
+ uint32_t written_samples;
+
+ /* Check if FIFO1 transmit is enabled */
+ if ((channel->tx_enabled) && (slots & (TXSLOT3 | TXSLOT4))) {
+ if (fifo->level >= (s->fifo_depth / 2)) {
+ int i;
+
+ DBG_L1("Transfer FIFO level = %i\n", fifo->level);
+
+ /* Try to transfer the whole FIFO */
+ for (i = 0; i < (fifo->level / 2); i++) {
+ uint32_t left = fifo->data[i * 2];
+ uint32_t right = fifo->data[i * 2 + 1];
+
+ /* Transmit two 20-bit samples to the codec */
+ if (lm4549_write_samples(&s->codec, left, right) == 0) {
+ DBG_L1("Codec buffer full\n");
+ break;
+ }
+ }
+
+ written_samples = i * 2;
+ if (written_samples > 0) {
+ /* Update the FIFO level */
+ fifo->level -= written_samples;
+
+ /* Move back the pending samples to the start of the FIFO */
+ for (i = 0; i < fifo->level; i++) {
+ fifo->data[i] = fifo->data[written_samples + i];
+ }
+
+ /* Update the status register */
+ s->regs.sr1 &= ~TXFF;
+
+ if (fifo->level <= (s->fifo_depth / 2)) {
+ s->regs.sr1 |= TXHE;
+ }
+
+ if (fifo->level == 0) {
+ s->regs.sr1 |= TXFE | TXUNDERRUN;
+ DBG_L1("Empty FIFO\n");
+ }
+ }
+ }
+ }
+}
+
+static void pl041_isr1_update(PL041State *s)
+{
+ /* Update ISR1 */
+ if (s->regs.sr1 & TXUNDERRUN) {
+ s->regs.isr1 |= URINTR;
+ } else {
+ s->regs.isr1 &= ~URINTR;
+ }
+
+ if (s->regs.sr1 & TXHE) {
+ s->regs.isr1 |= TXINTR;
+ } else {
+ s->regs.isr1 &= ~TXINTR;
+ }
+
+ if (!(s->regs.sr1 & TXBUSY) && (s->regs.sr1 & TXFE)) {
+ s->regs.isr1 |= TXCINTR;
+ } else {
+ s->regs.isr1 &= ~TXCINTR;
+ }
+
+ /* Update the irq state */
+ qemu_set_irq(s->irq, ((s->regs.isr1 & s->regs.ie1) > 0) ? 1 : 0);
+ DBG_L2("Set interrupt sr1 = 0x%08x isr1 = 0x%08x masked = 0x%08x\n",
+ s->regs.sr1, s->regs.isr1, s->regs.isr1 & s->regs.ie1);
+}
+
+static void pl041_request_data(void *opaque)
+{
+ PL041State *s = (PL041State *)opaque;
+
+ /* Trigger pending transfers */
+ pl041_fifo1_transmit(s);
+ pl041_isr1_update(s);
+}
+
+static uint64_t pl041_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL041State *s = (PL041State *)opaque;
+ int value;
+
+ if ((offset >= PL041_periphid0) && (offset <= PL041_pcellid3)) {
+ if (offset == PL041_periphid3) {
+ value = pl041_compute_periphid3(s);
+ } else {
+ value = pl041_default_id[(offset - PL041_periphid0) >> 2];
+ }
+
+ DBG_L1("pl041_read [0x%08x] => 0x%08x\n", offset, value);
+ return value;
+ } else if (offset <= PL041_dr4_7) {
+ value = *((uint32_t *)&s->regs + (offset >> 2));
+ } else {
+ DBG_L1("pl041_read: Reserved offset %x\n", (int)offset);
+ return 0;
+ }
+
+ switch (offset) {
+ case PL041_allints:
+ value = s->regs.isr1 & 0x7F;
+ break;
+ }
+
+ DBG_L1("pl041_read [0x%08x] %s => 0x%08x\n", offset,
+ get_reg_name(offset), value);
+
+ return value;
+}
+
+static void pl041_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL041State *s = (PL041State *)opaque;
+ uint16_t control, data;
+ uint32_t result;
+
+ DBG_L1("pl041_write [0x%08x] %s <= 0x%08x\n", offset,
+ get_reg_name(offset), (unsigned int)value);
+
+ /* Write the register */
+ if (offset <= PL041_dr4_7) {
+ *((uint32_t *)&s->regs + (offset >> 2)) = value;
+ } else {
+ DBG_L1("pl041_write: Reserved offset %x\n", (int)offset);
+ return;
+ }
+
+ /* Execute the actions */
+ switch (offset) {
+ case PL041_txcr1:
+ {
+ pl041_channel *channel = &s->fifo1;
+
+ uint32_t txen = s->regs.txcr1 & TXEN;
+ uint32_t tsize = (s->regs.txcr1 & TSIZE_MASK) >> TSIZE_MASK_BIT;
+ uint32_t compact_mode = (s->regs.txcr1 & TXCOMPACT) ? 1 : 0;
+#if defined(PL041_DEBUG_LEVEL)
+ uint32_t slots = (s->regs.txcr1 & TXSLOT_MASK) >> TXSLOT_MASK_BIT;
+ uint32_t txfen = (s->regs.txcr1 & TXFEN) > 0 ? 1 : 0;
+#endif
+
+ DBG_L1("=> txen = %i slots = 0x%01x tsize = %i compact = %i "
+ "txfen = %i\n", txen, slots, tsize, compact_mode, txfen);
+
+ channel->tx_enabled = txen;
+ channel->tx_compact_mode = compact_mode;
+
+ switch (tsize) {
+ case 0:
+ channel->tx_sample_size = 16;
+ break;
+ case 1:
+ channel->tx_sample_size = 18;
+ break;
+ case 2:
+ channel->tx_sample_size = 20;
+ break;
+ case 3:
+ channel->tx_sample_size = 12;
+ break;
+ }
+
+ DBG_L1("TX enabled = %i\n", channel->tx_enabled);
+ DBG_L1("TX compact mode = %i\n", channel->tx_compact_mode);
+ DBG_L1("TX sample width = %i\n", channel->tx_sample_size);
+
+ /* Check if compact mode is allowed with selected tsize */
+ if (channel->tx_compact_mode == 1) {
+ if ((channel->tx_sample_size == 18) ||
+ (channel->tx_sample_size == 20)) {
+ channel->tx_compact_mode = 0;
+ DBG_L1("Compact mode not allowed with 18/20-bit sample size\n");
+ }
+ }
+
+ break;
+ }
+ case PL041_sl1tx:
+ s->regs.slfr &= ~SL1TXEMPTY;
+
+ control = (s->regs.sl1tx >> 12) & 0x7F;
+ data = (s->regs.sl2tx >> 4) & 0xFFFF;
+
+ if ((s->regs.sl1tx & SLOT1_RW) == 0) {
+ /* Write operation */
+ lm4549_write(&s->codec, control, data);
+ } else {
+ /* Read operation */
+ result = lm4549_read(&s->codec, control);
+
+ /* Store the returned value */
+ s->regs.sl1rx = s->regs.sl1tx & ~SLOT1_RW;
+ s->regs.sl2rx = result << 4;
+
+ s->regs.slfr &= ~(SL1RXBUSY | SL2RXBUSY);
+ s->regs.slfr |= SL1RXVALID | SL2RXVALID;
+ }
+ break;
+
+ case PL041_sl2tx:
+ s->regs.sl2tx = value;
+ s->regs.slfr &= ~SL2TXEMPTY;
+ break;
+
+ case PL041_intclr:
+ DBG_L1("=> Clear interrupt intclr = 0x%08x isr1 = 0x%08x\n",
+ s->regs.intclr, s->regs.isr1);
+
+ if (s->regs.intclr & TXUEC1) {
+ s->regs.sr1 &= ~TXUNDERRUN;
+ }
+ break;
+
+ case PL041_maincr:
+ {
+#if defined(PL041_DEBUG_LEVEL)
+ char debug[] = " AACIFE SL1RXEN SL1TXEN";
+ if (!(value & AACIFE)) {
+ debug[0] = '!';
+ }
+ if (!(value & SL1RXEN)) {
+ debug[8] = '!';
+ }
+ if (!(value & SL1TXEN)) {
+ debug[17] = '!';
+ }
+ DBG_L1("%s\n", debug);
+#endif
+
+ if ((s->regs.maincr & AACIFE) == 0) {
+ pl041_reset(s);
+ }
+ break;
+ }
+
+ case PL041_dr1_0:
+ case PL041_dr1_1:
+ case PL041_dr1_2:
+ case PL041_dr1_3:
+ pl041_fifo1_write(s, value);
+ break;
+ }
+
+ /* Transmit the FIFO content */
+ pl041_fifo1_transmit(s);
+
+ /* Update the ISR1 register */
+ pl041_isr1_update(s);
+}
+
+static void pl041_device_reset(DeviceState *d)
+{
+ PL041State *s = PL041(d);
+
+ pl041_reset(s);
+}
+
+static const MemoryRegionOps pl041_ops = {
+ .read = pl041_read,
+ .write = pl041_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pl041_init(SysBusDevice *dev)
+{
+ PL041State *s = PL041(dev);
+
+ DBG_L1("pl041_init 0x%08x\n", (uint32_t)s);
+
+ /* Check the device properties */
+ switch (s->fifo_depth) {
+ case 8:
+ case 32:
+ case 64:
+ case 128:
+ case 256:
+ case 512:
+ case 1024:
+ case 2048:
+ break;
+ case 16:
+ default:
+ /* NC FIFO depth of 16 is not allowed because its id bits in
+ AACIPERIPHID3 overlap with the id for the default NC FIFO depth */
+ qemu_log_mask(LOG_UNIMP,
+ "pl041: unsupported non-compact fifo depth [%i]\n",
+ s->fifo_depth);
+ return -1;
+ }
+
+ /* Connect the device to the sysbus */
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl041_ops, s, "pl041", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+
+ /* Init the codec */
+ lm4549_init(&s->codec, &pl041_request_data, (void *)s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_pl041_regfile = {
+ .name = "pl041_regfile",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+#define REGISTER(name, offset) VMSTATE_UINT32(name, pl041_regfile),
+ #include "pl041.hx"
+#undef REGISTER
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pl041_fifo = {
+ .name = "pl041_fifo",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(level, pl041_fifo),
+ VMSTATE_UINT32_ARRAY(data, pl041_fifo, MAX_FIFO_DEPTH),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pl041_channel = {
+ .name = "pl041_channel",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(tx_fifo, pl041_channel, 0,
+ vmstate_pl041_fifo, pl041_fifo),
+ VMSTATE_UINT8(tx_enabled, pl041_channel),
+ VMSTATE_UINT8(tx_compact_mode, pl041_channel),
+ VMSTATE_UINT8(tx_sample_size, pl041_channel),
+ VMSTATE_STRUCT(rx_fifo, pl041_channel, 0,
+ vmstate_pl041_fifo, pl041_fifo),
+ VMSTATE_UINT8(rx_enabled, pl041_channel),
+ VMSTATE_UINT8(rx_compact_mode, pl041_channel),
+ VMSTATE_UINT8(rx_sample_size, pl041_channel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pl041 = {
+ .name = "pl041",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(fifo_depth, PL041State),
+ VMSTATE_STRUCT(regs, PL041State, 0,
+ vmstate_pl041_regfile, pl041_regfile),
+ VMSTATE_STRUCT(fifo1, PL041State, 0,
+ vmstate_pl041_channel, pl041_channel),
+ VMSTATE_STRUCT(codec, PL041State, 0,
+ vmstate_lm4549_state, lm4549_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property pl041_device_properties[] = {
+ /* Non-compact FIFO depth property */
+ DEFINE_PROP_UINT32("nc_fifo_depth", PL041State, fifo_depth,
+ DEFAULT_FIFO_DEPTH),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pl041_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pl041_init;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->reset = pl041_device_reset;
+ dc->vmsd = &vmstate_pl041;
+ dc->props = pl041_device_properties;
+}
+
+static const TypeInfo pl041_device_info = {
+ .name = TYPE_PL041,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL041State),
+ .class_init = pl041_device_class_init,
+};
+
+static void pl041_register_types(void)
+{
+ type_register_static(&pl041_device_info);
+}
+
+type_init(pl041_register_types)
diff --git a/hw/audio/pl041.h b/hw/audio/pl041.h
new file mode 100644
index 00000000..427ab6d6
--- /dev/null
+++ b/hw/audio/pl041.h
@@ -0,0 +1,135 @@
+/*
+ * Arm PrimeCell PL041 Advanced Audio Codec Interface
+ *
+ * Copyright (c) 2011
+ * Written by Mathieu Sonet - www.elasticsheep.com
+ *
+ * This code is licensed under the GPL.
+ *
+ * *****************************************************************
+ */
+
+#ifndef HW_PL041_H
+#define HW_PL041_H
+
+/* Register file */
+#define REGISTER(name, offset) uint32_t name;
+typedef struct {
+ #include "pl041.hx"
+} pl041_regfile;
+#undef REGISTER
+
+/* Register addresses */
+#define REGISTER(name, offset) PL041_##name = offset,
+enum {
+ #include "pl041.hx"
+
+ PL041_periphid0 = 0xFE0,
+ PL041_periphid1 = 0xFE4,
+ PL041_periphid2 = 0xFE8,
+ PL041_periphid3 = 0xFEC,
+ PL041_pcellid0 = 0xFF0,
+ PL041_pcellid1 = 0xFF4,
+ PL041_pcellid2 = 0xFF8,
+ PL041_pcellid3 = 0xFFC,
+};
+#undef REGISTER
+
+/* Register bits */
+
+/* IEx */
+#define TXCIE (1 << 0)
+#define RXTIE (1 << 1)
+#define TXIE (1 << 2)
+#define RXIE (1 << 3)
+#define RXOIE (1 << 4)
+#define TXUIE (1 << 5)
+#define RXTOIE (1 << 6)
+
+/* TXCRx */
+#define TXEN (1 << 0)
+#define TXSLOT1 (1 << 1)
+#define TXSLOT2 (1 << 2)
+#define TXSLOT3 (1 << 3)
+#define TXSLOT4 (1 << 4)
+#define TXCOMPACT (1 << 15)
+#define TXFEN (1 << 16)
+
+#define TXSLOT_MASK_BIT (1)
+#define TXSLOT_MASK (0xFFF << TXSLOT_MASK_BIT)
+
+#define TSIZE_MASK_BIT (13)
+#define TSIZE_MASK (0x3 << TSIZE_MASK_BIT)
+
+#define TSIZE_16BITS (0x0 << TSIZE_MASK_BIT)
+#define TSIZE_18BITS (0x1 << TSIZE_MASK_BIT)
+#define TSIZE_20BITS (0x2 << TSIZE_MASK_BIT)
+#define TSIZE_12BITS (0x3 << TSIZE_MASK_BIT)
+
+/* SRx */
+#define RXFE (1 << 0)
+#define TXFE (1 << 1)
+#define RXHF (1 << 2)
+#define TXHE (1 << 3)
+#define RXFF (1 << 4)
+#define TXFF (1 << 5)
+#define RXBUSY (1 << 6)
+#define TXBUSY (1 << 7)
+#define RXOVERRUN (1 << 8)
+#define TXUNDERRUN (1 << 9)
+#define RXTIMEOUT (1 << 10)
+#define RXTOFE (1 << 11)
+
+/* ISRx */
+#define TXCINTR (1 << 0)
+#define RXTOINTR (1 << 1)
+#define TXINTR (1 << 2)
+#define RXINTR (1 << 3)
+#define ORINTR (1 << 4)
+#define URINTR (1 << 5)
+#define RXTOFEINTR (1 << 6)
+
+/* SLFR */
+#define SL1RXBUSY (1 << 0)
+#define SL1TXBUSY (1 << 1)
+#define SL2RXBUSY (1 << 2)
+#define SL2TXBUSY (1 << 3)
+#define SL12RXBUSY (1 << 4)
+#define SL12TXBUSY (1 << 5)
+#define SL1RXVALID (1 << 6)
+#define SL1TXEMPTY (1 << 7)
+#define SL2RXVALID (1 << 8)
+#define SL2TXEMPTY (1 << 9)
+#define SL12RXVALID (1 << 10)
+#define SL12TXEMPTY (1 << 11)
+#define RAWGPIOINT (1 << 12)
+#define RWIS (1 << 13)
+
+/* MAINCR */
+#define AACIFE (1 << 0)
+#define LOOPBACK (1 << 1)
+#define LOWPOWER (1 << 2)
+#define SL1RXEN (1 << 3)
+#define SL1TXEN (1 << 4)
+#define SL2RXEN (1 << 5)
+#define SL2TXEN (1 << 6)
+#define SL12RXEN (1 << 7)
+#define SL12TXEN (1 << 8)
+#define DMAENABLE (1 << 9)
+
+/* INTCLR */
+#define WISC (1 << 0)
+#define RXOEC1 (1 << 1)
+#define RXOEC2 (1 << 2)
+#define RXOEC3 (1 << 3)
+#define RXOEC4 (1 << 4)
+#define TXUEC1 (1 << 5)
+#define TXUEC2 (1 << 6)
+#define TXUEC3 (1 << 7)
+#define TXUEC4 (1 << 8)
+#define RXTOFEC1 (1 << 9)
+#define RXTOFEC2 (1 << 10)
+#define RXTOFEC3 (1 << 11)
+#define RXTOFEC4 (1 << 12)
+
+#endif /* #ifndef HW_PL041_H */
diff --git a/hw/audio/pl041.hx b/hw/audio/pl041.hx
new file mode 100644
index 00000000..dd7188cb
--- /dev/null
+++ b/hw/audio/pl041.hx
@@ -0,0 +1,81 @@
+/*
+ * Arm PrimeCell PL041 Advanced Audio Codec Interface
+ *
+ * Copyright (c) 2011
+ * Written by Mathieu Sonet - www.elasticsheep.com
+ *
+ * This code is licensed under the GPL.
+ *
+ * *****************************************************************
+ */
+
+/* PL041 register file description */
+
+REGISTER( rxcr1, 0x00 )
+REGISTER( txcr1, 0x04 )
+REGISTER( sr1, 0x08 )
+REGISTER( isr1, 0x0C )
+REGISTER( ie1, 0x10 )
+REGISTER( rxcr2, 0x14 )
+REGISTER( txcr2, 0x18 )
+REGISTER( sr2, 0x1C )
+REGISTER( isr2, 0x20 )
+REGISTER( ie2, 0x24 )
+REGISTER( rxcr3, 0x28 )
+REGISTER( txcr3, 0x2C )
+REGISTER( sr3, 0x30 )
+REGISTER( isr3, 0x34 )
+REGISTER( ie3, 0x38 )
+REGISTER( rxcr4, 0x3C )
+REGISTER( txcr4, 0x40 )
+REGISTER( sr4, 0x44 )
+REGISTER( isr4, 0x48 )
+REGISTER( ie4, 0x4C )
+REGISTER( sl1rx, 0x50 )
+REGISTER( sl1tx, 0x54 )
+REGISTER( sl2rx, 0x58 )
+REGISTER( sl2tx, 0x5C )
+REGISTER( sl12rx, 0x60 )
+REGISTER( sl12tx, 0x64 )
+REGISTER( slfr, 0x68 )
+REGISTER( slistat, 0x6C )
+REGISTER( slien, 0x70 )
+REGISTER( intclr, 0x74 )
+REGISTER( maincr, 0x78 )
+REGISTER( reset, 0x7C )
+REGISTER( sync, 0x80 )
+REGISTER( allints, 0x84 )
+REGISTER( mainfr, 0x88 )
+REGISTER( unused, 0x8C )
+REGISTER( dr1_0, 0x90 )
+REGISTER( dr1_1, 0x94 )
+REGISTER( dr1_2, 0x98 )
+REGISTER( dr1_3, 0x9C )
+REGISTER( dr1_4, 0xA0 )
+REGISTER( dr1_5, 0xA4 )
+REGISTER( dr1_6, 0xA8 )
+REGISTER( dr1_7, 0xAC )
+REGISTER( dr2_0, 0xB0 )
+REGISTER( dr2_1, 0xB4 )
+REGISTER( dr2_2, 0xB8 )
+REGISTER( dr2_3, 0xBC )
+REGISTER( dr2_4, 0xC0 )
+REGISTER( dr2_5, 0xC4 )
+REGISTER( dr2_6, 0xC8 )
+REGISTER( dr2_7, 0xCC )
+REGISTER( dr3_0, 0xD0 )
+REGISTER( dr3_1, 0xD4 )
+REGISTER( dr3_2, 0xD8 )
+REGISTER( dr3_3, 0xDC )
+REGISTER( dr3_4, 0xE0 )
+REGISTER( dr3_5, 0xE4 )
+REGISTER( dr3_6, 0xE8 )
+REGISTER( dr3_7, 0xEC )
+REGISTER( dr4_0, 0xF0 )
+REGISTER( dr4_1, 0xF4 )
+REGISTER( dr4_2, 0xF8 )
+REGISTER( dr4_3, 0xFC )
+REGISTER( dr4_4, 0x100 )
+REGISTER( dr4_5, 0x104 )
+REGISTER( dr4_6, 0x108 )
+REGISTER( dr4_7, 0x10C )
diff --git a/hw/audio/sb16.c b/hw/audio/sb16.c
new file mode 100644
index 00000000..b052de5f
--- /dev/null
+++ b/hw/audio/sb16.c
@@ -0,0 +1,1427 @@
+/*
+ * QEMU Soundblaster 16 emulation
+ *
+ * Copyright (c) 2003-2005 Vassili Karpov (malc)
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/audio/audio.h"
+#include "audio/audio.h"
+#include "hw/isa/isa.h"
+#include "hw/qdev.h"
+#include "qemu/timer.h"
+#include "qemu/host-utils.h"
+
+#define dolog(...) AUD_log ("sb16", __VA_ARGS__)
+
+/* #define DEBUG */
+/* #define DEBUG_SB16_MOST */
+
+#ifdef DEBUG
+#define ldebug(...) dolog (__VA_ARGS__)
+#else
+#define ldebug(...)
+#endif
+
+#define IO_READ_PROTO(name) \
+ uint32_t name (void *opaque, uint32_t nport)
+#define IO_WRITE_PROTO(name) \
+ void name (void *opaque, uint32_t nport, uint32_t val)
+
+static const char e3[] = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.";
+
+#define TYPE_SB16 "sb16"
+#define SB16(obj) OBJECT_CHECK (SB16State, (obj), TYPE_SB16)
+
+typedef struct SB16State {
+ ISADevice parent_obj;
+
+ QEMUSoundCard card;
+ qemu_irq pic;
+ uint32_t irq;
+ uint32_t dma;
+ uint32_t hdma;
+ uint32_t port;
+ uint32_t ver;
+
+ int in_index;
+ int out_data_len;
+ int fmt_stereo;
+ int fmt_signed;
+ int fmt_bits;
+ audfmt_e fmt;
+ int dma_auto;
+ int block_size;
+ int fifo;
+ int freq;
+ int time_const;
+ int speaker;
+ int needed_bytes;
+ int cmd;
+ int use_hdma;
+ int highspeed;
+ int can_write;
+
+ int v2x6;
+
+ uint8_t csp_param;
+ uint8_t csp_value;
+ uint8_t csp_mode;
+ uint8_t csp_regs[256];
+ uint8_t csp_index;
+ uint8_t csp_reg83[4];
+ int csp_reg83r;
+ int csp_reg83w;
+
+ uint8_t in2_data[10];
+ uint8_t out_data[50];
+ uint8_t test_reg;
+ uint8_t last_read_byte;
+ int nzero;
+
+ int left_till_irq;
+
+ int dma_running;
+ int bytes_per_second;
+ int align;
+ int audio_free;
+ SWVoiceOut *voice;
+
+ QEMUTimer *aux_ts;
+ /* mixer state */
+ int mixer_nreg;
+ uint8_t mixer_regs[256];
+} SB16State;
+
+static void SB_audio_callback (void *opaque, int free);
+
+static int magic_of_irq (int irq)
+{
+ switch (irq) {
+ case 5:
+ return 2;
+ case 7:
+ return 4;
+ case 9:
+ return 1;
+ case 10:
+ return 8;
+ default:
+ dolog ("bad irq %d\n", irq);
+ return 2;
+ }
+}
+
+static int irq_of_magic (int magic)
+{
+ switch (magic) {
+ case 1:
+ return 9;
+ case 2:
+ return 5;
+ case 4:
+ return 7;
+ case 8:
+ return 10;
+ default:
+ dolog ("bad irq magic %d\n", magic);
+ return -1;
+ }
+}
+
+#if 0
+static void log_dsp (SB16State *dsp)
+{
+ ldebug ("%s:%s:%d:%s:dmasize=%d:freq=%d:const=%d:speaker=%d\n",
+ dsp->fmt_stereo ? "Stereo" : "Mono",
+ dsp->fmt_signed ? "Signed" : "Unsigned",
+ dsp->fmt_bits,
+ dsp->dma_auto ? "Auto" : "Single",
+ dsp->block_size,
+ dsp->freq,
+ dsp->time_const,
+ dsp->speaker);
+}
+#endif
+
+static void speaker (SB16State *s, int on)
+{
+ s->speaker = on;
+ /* AUD_enable (s->voice, on); */
+}
+
+static void control (SB16State *s, int hold)
+{
+ int dma = s->use_hdma ? s->hdma : s->dma;
+ s->dma_running = hold;
+
+ ldebug ("hold %d high %d dma %d\n", hold, s->use_hdma, dma);
+
+ if (hold) {
+ DMA_hold_DREQ (dma);
+ AUD_set_active_out (s->voice, 1);
+ }
+ else {
+ DMA_release_DREQ (dma);
+ AUD_set_active_out (s->voice, 0);
+ }
+}
+
+static void aux_timer (void *opaque)
+{
+ SB16State *s = opaque;
+ s->can_write = 1;
+ qemu_irq_raise (s->pic);
+}
+
+#define DMA8_AUTO 1
+#define DMA8_HIGH 2
+
+static void continue_dma8 (SB16State *s)
+{
+ if (s->freq > 0) {
+ struct audsettings as;
+
+ s->audio_free = 0;
+
+ as.freq = s->freq;
+ as.nchannels = 1 << s->fmt_stereo;
+ as.fmt = s->fmt;
+ as.endianness = 0;
+
+ s->voice = AUD_open_out (
+ &s->card,
+ s->voice,
+ "sb16",
+ s,
+ SB_audio_callback,
+ &as
+ );
+ }
+
+ control (s, 1);
+}
+
+static void dma_cmd8 (SB16State *s, int mask, int dma_len)
+{
+ s->fmt = AUD_FMT_U8;
+ s->use_hdma = 0;
+ s->fmt_bits = 8;
+ s->fmt_signed = 0;
+ s->fmt_stereo = (s->mixer_regs[0x0e] & 2) != 0;
+ if (-1 == s->time_const) {
+ if (s->freq <= 0)
+ s->freq = 11025;
+ }
+ else {
+ int tmp = (256 - s->time_const);
+ s->freq = (1000000 + (tmp / 2)) / tmp;
+ }
+
+ if (dma_len != -1) {
+ s->block_size = dma_len << s->fmt_stereo;
+ }
+ else {
+ /* This is apparently the only way to make both Act1/PL
+ and SecondReality/FC work
+
+ Act1 sets block size via command 0x48 and it's an odd number
+ SR does the same with even number
+ Both use stereo, and Creatives own documentation states that
+ 0x48 sets block size in bytes less one.. go figure */
+ s->block_size &= ~s->fmt_stereo;
+ }
+
+ s->freq >>= s->fmt_stereo;
+ s->left_till_irq = s->block_size;
+ s->bytes_per_second = (s->freq << s->fmt_stereo);
+ /* s->highspeed = (mask & DMA8_HIGH) != 0; */
+ s->dma_auto = (mask & DMA8_AUTO) != 0;
+ s->align = (1 << s->fmt_stereo) - 1;
+
+ if (s->block_size & s->align) {
+ dolog ("warning: misaligned block size %d, alignment %d\n",
+ s->block_size, s->align + 1);
+ }
+
+ ldebug ("freq %d, stereo %d, sign %d, bits %d, "
+ "dma %d, auto %d, fifo %d, high %d\n",
+ s->freq, s->fmt_stereo, s->fmt_signed, s->fmt_bits,
+ s->block_size, s->dma_auto, s->fifo, s->highspeed);
+
+ continue_dma8 (s);
+ speaker (s, 1);
+}
+
+static void dma_cmd (SB16State *s, uint8_t cmd, uint8_t d0, int dma_len)
+{
+ s->use_hdma = cmd < 0xc0;
+ s->fifo = (cmd >> 1) & 1;
+ s->dma_auto = (cmd >> 2) & 1;
+ s->fmt_signed = (d0 >> 4) & 1;
+ s->fmt_stereo = (d0 >> 5) & 1;
+
+ switch (cmd >> 4) {
+ case 11:
+ s->fmt_bits = 16;
+ break;
+
+ case 12:
+ s->fmt_bits = 8;
+ break;
+ }
+
+ if (-1 != s->time_const) {
+#if 1
+ int tmp = 256 - s->time_const;
+ s->freq = (1000000 + (tmp / 2)) / tmp;
+#else
+ /* s->freq = 1000000 / ((255 - s->time_const) << s->fmt_stereo); */
+ s->freq = 1000000 / ((255 - s->time_const));
+#endif
+ s->time_const = -1;
+ }
+
+ s->block_size = dma_len + 1;
+ s->block_size <<= (s->fmt_bits == 16);
+ if (!s->dma_auto) {
+ /* It is clear that for DOOM and auto-init this value
+ shouldn't take stereo into account, while Miles Sound Systems
+ setsound.exe with single transfer mode wouldn't work without it
+ wonders of SB16 yet again */
+ s->block_size <<= s->fmt_stereo;
+ }
+
+ ldebug ("freq %d, stereo %d, sign %d, bits %d, "
+ "dma %d, auto %d, fifo %d, high %d\n",
+ s->freq, s->fmt_stereo, s->fmt_signed, s->fmt_bits,
+ s->block_size, s->dma_auto, s->fifo, s->highspeed);
+
+ if (16 == s->fmt_bits) {
+ if (s->fmt_signed) {
+ s->fmt = AUD_FMT_S16;
+ }
+ else {
+ s->fmt = AUD_FMT_U16;
+ }
+ }
+ else {
+ if (s->fmt_signed) {
+ s->fmt = AUD_FMT_S8;
+ }
+ else {
+ s->fmt = AUD_FMT_U8;
+ }
+ }
+
+ s->left_till_irq = s->block_size;
+
+ s->bytes_per_second = (s->freq << s->fmt_stereo) << (s->fmt_bits == 16);
+ s->highspeed = 0;
+ s->align = (1 << (s->fmt_stereo + (s->fmt_bits == 16))) - 1;
+ if (s->block_size & s->align) {
+ dolog ("warning: misaligned block size %d, alignment %d\n",
+ s->block_size, s->align + 1);
+ }
+
+ if (s->freq) {
+ struct audsettings as;
+
+ s->audio_free = 0;
+
+ as.freq = s->freq;
+ as.nchannels = 1 << s->fmt_stereo;
+ as.fmt = s->fmt;
+ as.endianness = 0;
+
+ s->voice = AUD_open_out (
+ &s->card,
+ s->voice,
+ "sb16",
+ s,
+ SB_audio_callback,
+ &as
+ );
+ }
+
+ control (s, 1);
+ speaker (s, 1);
+}
+
+static inline void dsp_out_data (SB16State *s, uint8_t val)
+{
+ ldebug ("outdata %#x\n", val);
+ if ((size_t) s->out_data_len < sizeof (s->out_data)) {
+ s->out_data[s->out_data_len++] = val;
+ }
+}
+
+static inline uint8_t dsp_get_data (SB16State *s)
+{
+ if (s->in_index) {
+ return s->in2_data[--s->in_index];
+ }
+ else {
+ dolog ("buffer underflow\n");
+ return 0;
+ }
+}
+
+static void command (SB16State *s, uint8_t cmd)
+{
+ ldebug ("command %#x\n", cmd);
+
+ if (cmd > 0xaf && cmd < 0xd0) {
+ if (cmd & 8) {
+ dolog ("ADC not yet supported (command %#x)\n", cmd);
+ }
+
+ switch (cmd >> 4) {
+ case 11:
+ case 12:
+ break;
+ default:
+ dolog ("%#x wrong bits\n", cmd);
+ }
+ s->needed_bytes = 3;
+ }
+ else {
+ s->needed_bytes = 0;
+
+ switch (cmd) {
+ case 0x03:
+ dsp_out_data (s, 0x10); /* s->csp_param); */
+ goto warn;
+
+ case 0x04:
+ s->needed_bytes = 1;
+ goto warn;
+
+ case 0x05:
+ s->needed_bytes = 2;
+ goto warn;
+
+ case 0x08:
+ /* __asm__ ("int3"); */
+ goto warn;
+
+ case 0x0e:
+ s->needed_bytes = 2;
+ goto warn;
+
+ case 0x09:
+ dsp_out_data (s, 0xf8);
+ goto warn;
+
+ case 0x0f:
+ s->needed_bytes = 1;
+ goto warn;
+
+ case 0x10:
+ s->needed_bytes = 1;
+ goto warn;
+
+ case 0x14:
+ s->needed_bytes = 2;
+ s->block_size = 0;
+ break;
+
+ case 0x1c: /* Auto-Initialize DMA DAC, 8-bit */
+ dma_cmd8 (s, DMA8_AUTO, -1);
+ break;
+
+ case 0x20: /* Direct ADC, Juice/PL */
+ dsp_out_data (s, 0xff);
+ goto warn;
+
+ case 0x35:
+ dolog ("0x35 - MIDI command not implemented\n");
+ break;
+
+ case 0x40:
+ s->freq = -1;
+ s->time_const = -1;
+ s->needed_bytes = 1;
+ break;
+
+ case 0x41:
+ s->freq = -1;
+ s->time_const = -1;
+ s->needed_bytes = 2;
+ break;
+
+ case 0x42:
+ s->freq = -1;
+ s->time_const = -1;
+ s->needed_bytes = 2;
+ goto warn;
+
+ case 0x45:
+ dsp_out_data (s, 0xaa);
+ goto warn;
+
+ case 0x47: /* Continue Auto-Initialize DMA 16bit */
+ break;
+
+ case 0x48:
+ s->needed_bytes = 2;
+ break;
+
+ case 0x74:
+ s->needed_bytes = 2; /* DMA DAC, 4-bit ADPCM */
+ dolog ("0x75 - DMA DAC, 4-bit ADPCM not implemented\n");
+ break;
+
+ case 0x75: /* DMA DAC, 4-bit ADPCM Reference */
+ s->needed_bytes = 2;
+ dolog ("0x74 - DMA DAC, 4-bit ADPCM Reference not implemented\n");
+ break;
+
+ case 0x76: /* DMA DAC, 2.6-bit ADPCM */
+ s->needed_bytes = 2;
+ dolog ("0x74 - DMA DAC, 2.6-bit ADPCM not implemented\n");
+ break;
+
+ case 0x77: /* DMA DAC, 2.6-bit ADPCM Reference */
+ s->needed_bytes = 2;
+ dolog ("0x74 - DMA DAC, 2.6-bit ADPCM Reference not implemented\n");
+ break;
+
+ case 0x7d:
+ dolog ("0x7d - Autio-Initialize DMA DAC, 4-bit ADPCM Reference\n");
+ dolog ("not implemented\n");
+ break;
+
+ case 0x7f:
+ dolog (
+ "0x7d - Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference\n"
+ );
+ dolog ("not implemented\n");
+ break;
+
+ case 0x80:
+ s->needed_bytes = 2;
+ break;
+
+ case 0x90:
+ case 0x91:
+ dma_cmd8 (s, ((cmd & 1) == 0) | DMA8_HIGH, -1);
+ break;
+
+ case 0xd0: /* halt DMA operation. 8bit */
+ control (s, 0);
+ break;
+
+ case 0xd1: /* speaker on */
+ speaker (s, 1);
+ break;
+
+ case 0xd3: /* speaker off */
+ speaker (s, 0);
+ break;
+
+ case 0xd4: /* continue DMA operation. 8bit */
+ /* KQ6 (or maybe Sierras audblst.drv in general) resets
+ the frequency between halt/continue */
+ continue_dma8 (s);
+ break;
+
+ case 0xd5: /* halt DMA operation. 16bit */
+ control (s, 0);
+ break;
+
+ case 0xd6: /* continue DMA operation. 16bit */
+ control (s, 1);
+ break;
+
+ case 0xd9: /* exit auto-init DMA after this block. 16bit */
+ s->dma_auto = 0;
+ break;
+
+ case 0xda: /* exit auto-init DMA after this block. 8bit */
+ s->dma_auto = 0;
+ break;
+
+ case 0xe0: /* DSP identification */
+ s->needed_bytes = 1;
+ break;
+
+ case 0xe1:
+ dsp_out_data (s, s->ver & 0xff);
+ dsp_out_data (s, s->ver >> 8);
+ break;
+
+ case 0xe2:
+ s->needed_bytes = 1;
+ goto warn;
+
+ case 0xe3:
+ {
+ int i;
+ for (i = sizeof (e3) - 1; i >= 0; --i)
+ dsp_out_data (s, e3[i]);
+ }
+ break;
+
+ case 0xe4: /* write test reg */
+ s->needed_bytes = 1;
+ break;
+
+ case 0xe7:
+ dolog ("Attempt to probe for ESS (0xe7)?\n");
+ break;
+
+ case 0xe8: /* read test reg */
+ dsp_out_data (s, s->test_reg);
+ break;
+
+ case 0xf2:
+ case 0xf3:
+ dsp_out_data (s, 0xaa);
+ s->mixer_regs[0x82] |= (cmd == 0xf2) ? 1 : 2;
+ qemu_irq_raise (s->pic);
+ break;
+
+ case 0xf9:
+ s->needed_bytes = 1;
+ goto warn;
+
+ case 0xfa:
+ dsp_out_data (s, 0);
+ goto warn;
+
+ case 0xfc: /* FIXME */
+ dsp_out_data (s, 0);
+ goto warn;
+
+ default:
+ dolog ("Unrecognized command %#x\n", cmd);
+ break;
+ }
+ }
+
+ if (!s->needed_bytes) {
+ ldebug ("\n");
+ }
+
+ exit:
+ if (!s->needed_bytes) {
+ s->cmd = -1;
+ }
+ else {
+ s->cmd = cmd;
+ }
+ return;
+
+ warn:
+ dolog ("warning: command %#x,%d is not truly understood yet\n",
+ cmd, s->needed_bytes);
+ goto exit;
+
+}
+
+static uint16_t dsp_get_lohi (SB16State *s)
+{
+ uint8_t hi = dsp_get_data (s);
+ uint8_t lo = dsp_get_data (s);
+ return (hi << 8) | lo;
+}
+
+static uint16_t dsp_get_hilo (SB16State *s)
+{
+ uint8_t lo = dsp_get_data (s);
+ uint8_t hi = dsp_get_data (s);
+ return (hi << 8) | lo;
+}
+
+static void complete (SB16State *s)
+{
+ int d0, d1, d2;
+ ldebug ("complete command %#x, in_index %d, needed_bytes %d\n",
+ s->cmd, s->in_index, s->needed_bytes);
+
+ if (s->cmd > 0xaf && s->cmd < 0xd0) {
+ d2 = dsp_get_data (s);
+ d1 = dsp_get_data (s);
+ d0 = dsp_get_data (s);
+
+ if (s->cmd & 8) {
+ dolog ("ADC params cmd = %#x d0 = %d, d1 = %d, d2 = %d\n",
+ s->cmd, d0, d1, d2);
+ }
+ else {
+ ldebug ("cmd = %#x d0 = %d, d1 = %d, d2 = %d\n",
+ s->cmd, d0, d1, d2);
+ dma_cmd (s, s->cmd, d0, d1 + (d2 << 8));
+ }
+ }
+ else {
+ switch (s->cmd) {
+ case 0x04:
+ s->csp_mode = dsp_get_data (s);
+ s->csp_reg83r = 0;
+ s->csp_reg83w = 0;
+ ldebug ("CSP command 0x04: mode=%#x\n", s->csp_mode);
+ break;
+
+ case 0x05:
+ s->csp_param = dsp_get_data (s);
+ s->csp_value = dsp_get_data (s);
+ ldebug ("CSP command 0x05: param=%#x value=%#x\n",
+ s->csp_param,
+ s->csp_value);
+ break;
+
+ case 0x0e:
+ d0 = dsp_get_data (s);
+ d1 = dsp_get_data (s);
+ ldebug ("write CSP register %d <- %#x\n", d1, d0);
+ if (d1 == 0x83) {
+ ldebug ("0x83[%d] <- %#x\n", s->csp_reg83r, d0);
+ s->csp_reg83[s->csp_reg83r % 4] = d0;
+ s->csp_reg83r += 1;
+ }
+ else {
+ s->csp_regs[d1] = d0;
+ }
+ break;
+
+ case 0x0f:
+ d0 = dsp_get_data (s);
+ ldebug ("read CSP register %#x -> %#x, mode=%#x\n",
+ d0, s->csp_regs[d0], s->csp_mode);
+ if (d0 == 0x83) {
+ ldebug ("0x83[%d] -> %#x\n",
+ s->csp_reg83w,
+ s->csp_reg83[s->csp_reg83w % 4]);
+ dsp_out_data (s, s->csp_reg83[s->csp_reg83w % 4]);
+ s->csp_reg83w += 1;
+ }
+ else {
+ dsp_out_data (s, s->csp_regs[d0]);
+ }
+ break;
+
+ case 0x10:
+ d0 = dsp_get_data (s);
+ dolog ("cmd 0x10 d0=%#x\n", d0);
+ break;
+
+ case 0x14:
+ dma_cmd8 (s, 0, dsp_get_lohi (s) + 1);
+ break;
+
+ case 0x40:
+ s->time_const = dsp_get_data (s);
+ ldebug ("set time const %d\n", s->time_const);
+ break;
+
+ case 0x42: /* FT2 sets output freq with this, go figure */
+#if 0
+ dolog ("cmd 0x42 might not do what it think it should\n");
+#endif
+ case 0x41:
+ s->freq = dsp_get_hilo (s);
+ ldebug ("set freq %d\n", s->freq);
+ break;
+
+ case 0x48:
+ s->block_size = dsp_get_lohi (s) + 1;
+ ldebug ("set dma block len %d\n", s->block_size);
+ break;
+
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ /* ADPCM stuff, ignore */
+ break;
+
+ case 0x80:
+ {
+ int freq, samples, bytes;
+ int64_t ticks;
+
+ freq = s->freq > 0 ? s->freq : 11025;
+ samples = dsp_get_lohi (s) + 1;
+ bytes = samples << s->fmt_stereo << (s->fmt_bits == 16);
+ ticks = muldiv64 (bytes, get_ticks_per_sec (), freq);
+ if (ticks < get_ticks_per_sec () / 1024) {
+ qemu_irq_raise (s->pic);
+ }
+ else {
+ if (s->aux_ts) {
+ timer_mod (
+ s->aux_ts,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ticks
+ );
+ }
+ }
+ ldebug ("mix silence %d %d %" PRId64 "\n", samples, bytes, ticks);
+ }
+ break;
+
+ case 0xe0:
+ d0 = dsp_get_data (s);
+ s->out_data_len = 0;
+ ldebug ("E0 data = %#x\n", d0);
+ dsp_out_data (s, ~d0);
+ break;
+
+ case 0xe2:
+#ifdef DEBUG
+ d0 = dsp_get_data (s);
+ dolog ("E2 = %#x\n", d0);
+#endif
+ break;
+
+ case 0xe4:
+ s->test_reg = dsp_get_data (s);
+ break;
+
+ case 0xf9:
+ d0 = dsp_get_data (s);
+ ldebug ("command 0xf9 with %#x\n", d0);
+ switch (d0) {
+ case 0x0e:
+ dsp_out_data (s, 0xff);
+ break;
+
+ case 0x0f:
+ dsp_out_data (s, 0x07);
+ break;
+
+ case 0x37:
+ dsp_out_data (s, 0x38);
+ break;
+
+ default:
+ dsp_out_data (s, 0x00);
+ break;
+ }
+ break;
+
+ default:
+ dolog ("complete: unrecognized command %#x\n", s->cmd);
+ return;
+ }
+ }
+
+ ldebug ("\n");
+ s->cmd = -1;
+}
+
+static void legacy_reset (SB16State *s)
+{
+ struct audsettings as;
+
+ s->freq = 11025;
+ s->fmt_signed = 0;
+ s->fmt_bits = 8;
+ s->fmt_stereo = 0;
+
+ as.freq = s->freq;
+ as.nchannels = 1;
+ as.fmt = AUD_FMT_U8;
+ as.endianness = 0;
+
+ s->voice = AUD_open_out (
+ &s->card,
+ s->voice,
+ "sb16",
+ s,
+ SB_audio_callback,
+ &as
+ );
+
+ /* Not sure about that... */
+ /* AUD_set_active_out (s->voice, 1); */
+}
+
+static void reset (SB16State *s)
+{
+ qemu_irq_lower (s->pic);
+ if (s->dma_auto) {
+ qemu_irq_raise (s->pic);
+ qemu_irq_lower (s->pic);
+ }
+
+ s->mixer_regs[0x82] = 0;
+ s->dma_auto = 0;
+ s->in_index = 0;
+ s->out_data_len = 0;
+ s->left_till_irq = 0;
+ s->needed_bytes = 0;
+ s->block_size = -1;
+ s->nzero = 0;
+ s->highspeed = 0;
+ s->v2x6 = 0;
+ s->cmd = -1;
+
+ dsp_out_data (s, 0xaa);
+ speaker (s, 0);
+ control (s, 0);
+ legacy_reset (s);
+}
+
+static IO_WRITE_PROTO (dsp_write)
+{
+ SB16State *s = opaque;
+ int iport;
+
+ iport = nport - s->port;
+
+ ldebug ("write %#x <- %#x\n", nport, val);
+ switch (iport) {
+ case 0x06:
+ switch (val) {
+ case 0x00:
+ if (s->v2x6 == 1) {
+ reset (s);
+ }
+ s->v2x6 = 0;
+ break;
+
+ case 0x01:
+ case 0x03: /* FreeBSD kludge */
+ s->v2x6 = 1;
+ break;
+
+ case 0xc6:
+ s->v2x6 = 0; /* Prince of Persia, csp.sys, diagnose.exe */
+ break;
+
+ case 0xb8: /* Panic */
+ reset (s);
+ break;
+
+ case 0x39:
+ dsp_out_data (s, 0x38);
+ reset (s);
+ s->v2x6 = 0x39;
+ break;
+
+ default:
+ s->v2x6 = val;
+ break;
+ }
+ break;
+
+ case 0x0c: /* write data or command | write status */
+/* if (s->highspeed) */
+/* break; */
+
+ if (s->needed_bytes == 0) {
+ command (s, val);
+#if 0
+ if (0 == s->needed_bytes) {
+ log_dsp (s);
+ }
+#endif
+ }
+ else {
+ if (s->in_index == sizeof (s->in2_data)) {
+ dolog ("in data overrun\n");
+ }
+ else {
+ s->in2_data[s->in_index++] = val;
+ if (s->in_index == s->needed_bytes) {
+ s->needed_bytes = 0;
+ complete (s);
+#if 0
+ log_dsp (s);
+#endif
+ }
+ }
+ }
+ break;
+
+ default:
+ ldebug ("(nport=%#x, val=%#x)\n", nport, val);
+ break;
+ }
+}
+
+static IO_READ_PROTO (dsp_read)
+{
+ SB16State *s = opaque;
+ int iport, retval, ack = 0;
+
+ iport = nport - s->port;
+
+ switch (iport) {
+ case 0x06: /* reset */
+ retval = 0xff;
+ break;
+
+ case 0x0a: /* read data */
+ if (s->out_data_len) {
+ retval = s->out_data[--s->out_data_len];
+ s->last_read_byte = retval;
+ }
+ else {
+ if (s->cmd != -1) {
+ dolog ("empty output buffer for command %#x\n",
+ s->cmd);
+ }
+ retval = s->last_read_byte;
+ /* goto error; */
+ }
+ break;
+
+ case 0x0c: /* 0 can write */
+ retval = s->can_write ? 0 : 0x80;
+ break;
+
+ case 0x0d: /* timer interrupt clear */
+ /* dolog ("timer interrupt clear\n"); */
+ retval = 0;
+ break;
+
+ case 0x0e: /* data available status | irq 8 ack */
+ retval = (!s->out_data_len || s->highspeed) ? 0 : 0x80;
+ if (s->mixer_regs[0x82] & 1) {
+ ack = 1;
+ s->mixer_regs[0x82] &= ~1;
+ qemu_irq_lower (s->pic);
+ }
+ break;
+
+ case 0x0f: /* irq 16 ack */
+ retval = 0xff;
+ if (s->mixer_regs[0x82] & 2) {
+ ack = 1;
+ s->mixer_regs[0x82] &= ~2;
+ qemu_irq_lower (s->pic);
+ }
+ break;
+
+ default:
+ goto error;
+ }
+
+ if (!ack) {
+ ldebug ("read %#x -> %#x\n", nport, retval);
+ }
+
+ return retval;
+
+ error:
+ dolog ("warning: dsp_read %#x error\n", nport);
+ return 0xff;
+}
+
+static void reset_mixer (SB16State *s)
+{
+ int i;
+
+ memset (s->mixer_regs, 0xff, 0x7f);
+ memset (s->mixer_regs + 0x83, 0xff, sizeof (s->mixer_regs) - 0x83);
+
+ s->mixer_regs[0x02] = 4; /* master volume 3bits */
+ s->mixer_regs[0x06] = 4; /* MIDI volume 3bits */
+ s->mixer_regs[0x08] = 0; /* CD volume 3bits */
+ s->mixer_regs[0x0a] = 0; /* voice volume 2bits */
+
+ /* d5=input filt, d3=lowpass filt, d1,d2=input source */
+ s->mixer_regs[0x0c] = 0;
+
+ /* d5=output filt, d1=stereo switch */
+ s->mixer_regs[0x0e] = 0;
+
+ /* voice volume L d5,d7, R d1,d3 */
+ s->mixer_regs[0x04] = (4 << 5) | (4 << 1);
+ /* master ... */
+ s->mixer_regs[0x22] = (4 << 5) | (4 << 1);
+ /* MIDI ... */
+ s->mixer_regs[0x26] = (4 << 5) | (4 << 1);
+
+ for (i = 0x30; i < 0x48; i++) {
+ s->mixer_regs[i] = 0x20;
+ }
+}
+
+static IO_WRITE_PROTO (mixer_write_indexb)
+{
+ SB16State *s = opaque;
+ (void) nport;
+ s->mixer_nreg = val;
+}
+
+static IO_WRITE_PROTO (mixer_write_datab)
+{
+ SB16State *s = opaque;
+
+ (void) nport;
+ ldebug ("mixer_write [%#x] <- %#x\n", s->mixer_nreg, val);
+
+ switch (s->mixer_nreg) {
+ case 0x00:
+ reset_mixer (s);
+ break;
+
+ case 0x80:
+ {
+ int irq = irq_of_magic (val);
+ ldebug ("setting irq to %d (val=%#x)\n", irq, val);
+ if (irq > 0) {
+ s->irq = irq;
+ }
+ }
+ break;
+
+ case 0x81:
+ {
+ int dma, hdma;
+
+ dma = ctz32 (val & 0xf);
+ hdma = ctz32 (val & 0xf0);
+ if (dma != s->dma || hdma != s->hdma) {
+ dolog (
+ "attempt to change DMA "
+ "8bit %d(%d), 16bit %d(%d) (val=%#x)\n",
+ dma, s->dma, hdma, s->hdma, val);
+ }
+#if 0
+ s->dma = dma;
+ s->hdma = hdma;
+#endif
+ }
+ break;
+
+ case 0x82:
+ dolog ("attempt to write into IRQ status register (val=%#x)\n",
+ val);
+ return;
+
+ default:
+ if (s->mixer_nreg >= 0x80) {
+ ldebug ("attempt to write mixer[%#x] <- %#x\n", s->mixer_nreg, val);
+ }
+ break;
+ }
+
+ s->mixer_regs[s->mixer_nreg] = val;
+}
+
+static IO_READ_PROTO (mixer_read)
+{
+ SB16State *s = opaque;
+
+ (void) nport;
+#ifndef DEBUG_SB16_MOST
+ if (s->mixer_nreg != 0x82) {
+ ldebug ("mixer_read[%#x] -> %#x\n",
+ s->mixer_nreg, s->mixer_regs[s->mixer_nreg]);
+ }
+#else
+ ldebug ("mixer_read[%#x] -> %#x\n",
+ s->mixer_nreg, s->mixer_regs[s->mixer_nreg]);
+#endif
+ return s->mixer_regs[s->mixer_nreg];
+}
+
+static int write_audio (SB16State *s, int nchan, int dma_pos,
+ int dma_len, int len)
+{
+ int temp, net;
+ uint8_t tmpbuf[4096];
+
+ temp = len;
+ net = 0;
+
+ while (temp) {
+ int left = dma_len - dma_pos;
+ int copied;
+ size_t to_copy;
+
+ to_copy = audio_MIN (temp, left);
+ if (to_copy > sizeof (tmpbuf)) {
+ to_copy = sizeof (tmpbuf);
+ }
+
+ copied = DMA_read_memory (nchan, tmpbuf, dma_pos, to_copy);
+ copied = AUD_write (s->voice, tmpbuf, copied);
+
+ temp -= copied;
+ dma_pos = (dma_pos + copied) % dma_len;
+ net += copied;
+
+ if (!copied) {
+ break;
+ }
+ }
+
+ return net;
+}
+
+static int SB_read_DMA (void *opaque, int nchan, int dma_pos, int dma_len)
+{
+ SB16State *s = opaque;
+ int till, copy, written, free;
+
+ if (s->block_size <= 0) {
+ dolog ("invalid block size=%d nchan=%d dma_pos=%d dma_len=%d\n",
+ s->block_size, nchan, dma_pos, dma_len);
+ return dma_pos;
+ }
+
+ if (s->left_till_irq < 0) {
+ s->left_till_irq = s->block_size;
+ }
+
+ if (s->voice) {
+ free = s->audio_free & ~s->align;
+ if ((free <= 0) || !dma_len) {
+ return dma_pos;
+ }
+ }
+ else {
+ free = dma_len;
+ }
+
+ copy = free;
+ till = s->left_till_irq;
+
+#ifdef DEBUG_SB16_MOST
+ dolog ("pos:%06d %d till:%d len:%d\n",
+ dma_pos, free, till, dma_len);
+#endif
+
+ if (till <= copy) {
+ if (s->dma_auto == 0) {
+ copy = till;
+ }
+ }
+
+ written = write_audio (s, nchan, dma_pos, dma_len, copy);
+ dma_pos = (dma_pos + written) % dma_len;
+ s->left_till_irq -= written;
+
+ if (s->left_till_irq <= 0) {
+ s->mixer_regs[0x82] |= (nchan & 4) ? 2 : 1;
+ qemu_irq_raise (s->pic);
+ if (s->dma_auto == 0) {
+ control (s, 0);
+ speaker (s, 0);
+ }
+ }
+
+#ifdef DEBUG_SB16_MOST
+ ldebug ("pos %5d free %5d size %5d till % 5d copy %5d written %5d size %5d\n",
+ dma_pos, free, dma_len, s->left_till_irq, copy, written,
+ s->block_size);
+#endif
+
+ while (s->left_till_irq <= 0) {
+ s->left_till_irq = s->block_size + s->left_till_irq;
+ }
+
+ return dma_pos;
+}
+
+static void SB_audio_callback (void *opaque, int free)
+{
+ SB16State *s = opaque;
+ s->audio_free = free;
+}
+
+static int sb16_post_load (void *opaque, int version_id)
+{
+ SB16State *s = opaque;
+
+ if (s->voice) {
+ AUD_close_out (&s->card, s->voice);
+ s->voice = NULL;
+ }
+
+ if (s->dma_running) {
+ if (s->freq) {
+ struct audsettings as;
+
+ s->audio_free = 0;
+
+ as.freq = s->freq;
+ as.nchannels = 1 << s->fmt_stereo;
+ as.fmt = s->fmt;
+ as.endianness = 0;
+
+ s->voice = AUD_open_out (
+ &s->card,
+ s->voice,
+ "sb16",
+ s,
+ SB_audio_callback,
+ &as
+ );
+ }
+
+ control (s, 1);
+ speaker (s, s->speaker);
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_sb16 = {
+ .name = "sb16",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = sb16_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32 (irq, SB16State),
+ VMSTATE_UINT32 (dma, SB16State),
+ VMSTATE_UINT32 (hdma, SB16State),
+ VMSTATE_UINT32 (port, SB16State),
+ VMSTATE_UINT32 (ver, SB16State),
+ VMSTATE_INT32 (in_index, SB16State),
+ VMSTATE_INT32 (out_data_len, SB16State),
+ VMSTATE_INT32 (fmt_stereo, SB16State),
+ VMSTATE_INT32 (fmt_signed, SB16State),
+ VMSTATE_INT32 (fmt_bits, SB16State),
+ VMSTATE_UINT32 (fmt, SB16State),
+ VMSTATE_INT32 (dma_auto, SB16State),
+ VMSTATE_INT32 (block_size, SB16State),
+ VMSTATE_INT32 (fifo, SB16State),
+ VMSTATE_INT32 (freq, SB16State),
+ VMSTATE_INT32 (time_const, SB16State),
+ VMSTATE_INT32 (speaker, SB16State),
+ VMSTATE_INT32 (needed_bytes, SB16State),
+ VMSTATE_INT32 (cmd, SB16State),
+ VMSTATE_INT32 (use_hdma, SB16State),
+ VMSTATE_INT32 (highspeed, SB16State),
+ VMSTATE_INT32 (can_write, SB16State),
+ VMSTATE_INT32 (v2x6, SB16State),
+
+ VMSTATE_UINT8 (csp_param, SB16State),
+ VMSTATE_UINT8 (csp_value, SB16State),
+ VMSTATE_UINT8 (csp_mode, SB16State),
+ VMSTATE_UINT8 (csp_param, SB16State),
+ VMSTATE_BUFFER (csp_regs, SB16State),
+ VMSTATE_UINT8 (csp_index, SB16State),
+ VMSTATE_BUFFER (csp_reg83, SB16State),
+ VMSTATE_INT32 (csp_reg83r, SB16State),
+ VMSTATE_INT32 (csp_reg83w, SB16State),
+
+ VMSTATE_BUFFER (in2_data, SB16State),
+ VMSTATE_BUFFER (out_data, SB16State),
+ VMSTATE_UINT8 (test_reg, SB16State),
+ VMSTATE_UINT8 (last_read_byte, SB16State),
+
+ VMSTATE_INT32 (nzero, SB16State),
+ VMSTATE_INT32 (left_till_irq, SB16State),
+ VMSTATE_INT32 (dma_running, SB16State),
+ VMSTATE_INT32 (bytes_per_second, SB16State),
+ VMSTATE_INT32 (align, SB16State),
+
+ VMSTATE_INT32 (mixer_nreg, SB16State),
+ VMSTATE_BUFFER (mixer_regs, SB16State),
+
+ VMSTATE_END_OF_LIST ()
+ }
+};
+
+static const MemoryRegionPortio sb16_ioport_list[] = {
+ { 4, 1, 1, .write = mixer_write_indexb },
+ { 5, 1, 1, .read = mixer_read, .write = mixer_write_datab },
+ { 6, 1, 1, .read = dsp_read, .write = dsp_write },
+ { 10, 1, 1, .read = dsp_read },
+ { 12, 1, 1, .write = dsp_write },
+ { 12, 4, 1, .read = dsp_read },
+ PORTIO_END_OF_LIST (),
+};
+
+
+static void sb16_initfn (Object *obj)
+{
+ SB16State *s = SB16 (obj);
+
+ s->cmd = -1;
+}
+
+static void sb16_realizefn (DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE (dev);
+ SB16State *s = SB16 (dev);
+
+ isa_init_irq (isadev, &s->pic, s->irq);
+
+ s->mixer_regs[0x80] = magic_of_irq (s->irq);
+ s->mixer_regs[0x81] = (1 << s->dma) | (1 << s->hdma);
+ s->mixer_regs[0x82] = 2 << 5;
+
+ s->csp_regs[5] = 1;
+ s->csp_regs[9] = 0xf8;
+
+ reset_mixer (s);
+ s->aux_ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, aux_timer, s);
+ if (!s->aux_ts) {
+ dolog ("warning: Could not create auxiliary timer\n");
+ }
+
+ isa_register_portio_list (isadev, s->port, sb16_ioport_list, s, "sb16");
+
+ DMA_register_channel (s->hdma, SB_read_DMA, s);
+ DMA_register_channel (s->dma, SB_read_DMA, s);
+ s->can_write = 1;
+
+ AUD_register_card ("sb16", &s->card);
+}
+
+static int SB16_init (ISABus *bus)
+{
+ isa_create_simple (bus, TYPE_SB16);
+ return 0;
+}
+
+static Property sb16_properties[] = {
+ DEFINE_PROP_UINT32 ("version", SB16State, ver, 0x0405), /* 4.5 */
+ DEFINE_PROP_UINT32 ("iobase", SB16State, port, 0x220),
+ DEFINE_PROP_UINT32 ("irq", SB16State, irq, 5),
+ DEFINE_PROP_UINT32 ("dma", SB16State, dma, 1),
+ DEFINE_PROP_UINT32 ("dma16", SB16State, hdma, 5),
+ DEFINE_PROP_END_OF_LIST (),
+};
+
+static void sb16_class_initfn (ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS (klass);
+
+ dc->realize = sb16_realizefn;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = "Creative Sound Blaster 16";
+ dc->vmsd = &vmstate_sb16;
+ dc->props = sb16_properties;
+}
+
+static const TypeInfo sb16_info = {
+ .name = TYPE_SB16,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof (SB16State),
+ .instance_init = sb16_initfn,
+ .class_init = sb16_class_initfn,
+};
+
+static void sb16_register_types (void)
+{
+ type_register_static (&sb16_info);
+ isa_register_soundhw("sb16", "Creative Sound Blaster 16", SB16_init);
+}
+
+type_init (sb16_register_types)
diff --git a/hw/audio/wm8750.c b/hw/audio/wm8750.c
new file mode 100644
index 00000000..b50b3314
--- /dev/null
+++ b/hw/audio/wm8750.c
@@ -0,0 +1,722 @@
+/*
+ * WM8750 audio CODEC.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This file is licensed under GNU GPL.
+ */
+
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "audio/audio.h"
+
+#define IN_PORT_N 3
+#define OUT_PORT_N 3
+
+#define CODEC "wm8750"
+
+typedef struct {
+ int adc;
+ int adc_hz;
+ int dac;
+ int dac_hz;
+} WMRate;
+
+#define TYPE_WM8750 "wm8750"
+#define WM8750(obj) OBJECT_CHECK(WM8750State, (obj), TYPE_WM8750)
+
+typedef struct WM8750State {
+ I2CSlave parent_obj;
+
+ uint8_t i2c_data[2];
+ int i2c_len;
+ QEMUSoundCard card;
+ SWVoiceIn *adc_voice[IN_PORT_N];
+ SWVoiceOut *dac_voice[OUT_PORT_N];
+ int enable;
+ void (*data_req)(void *, int, int);
+ void *opaque;
+ uint8_t data_in[4096];
+ uint8_t data_out[4096];
+ int idx_in, req_in;
+ int idx_out, req_out;
+
+ SWVoiceOut **out[2];
+ uint8_t outvol[7], outmute[2];
+ SWVoiceIn **in[2];
+ uint8_t invol[4], inmute[2];
+
+ uint8_t diff[2], pol, ds, monomix[2], alc, mute;
+ uint8_t path[4], mpath[2], power, format;
+ const WMRate *rate;
+ uint8_t rate_vmstate;
+ int adc_hz, dac_hz, ext_adc_hz, ext_dac_hz, master;
+} WM8750State;
+
+/* pow(10.0, -i / 20.0) * 255, i = 0..42 */
+static const uint8_t wm8750_vol_db_table[] = {
+ 255, 227, 203, 181, 161, 143, 128, 114, 102, 90, 81, 72, 64, 57, 51, 45,
+ 40, 36, 32, 29, 26, 23, 20, 18, 16, 14, 13, 11, 10, 9, 8, 7, 6, 6, 5, 5,
+ 4, 4, 3, 3, 3, 2, 2
+};
+
+#define WM8750_OUTVOL_TRANSFORM(x) wm8750_vol_db_table[(0x7f - x) / 3]
+#define WM8750_INVOL_TRANSFORM(x) (x << 2)
+
+static inline void wm8750_in_load(WM8750State *s)
+{
+ if (s->idx_in + s->req_in <= sizeof(s->data_in))
+ return;
+ s->idx_in = audio_MAX(0, (int) sizeof(s->data_in) - s->req_in);
+ AUD_read(*s->in[0], s->data_in + s->idx_in,
+ sizeof(s->data_in) - s->idx_in);
+}
+
+static inline void wm8750_out_flush(WM8750State *s)
+{
+ int sent = 0;
+ while (sent < s->idx_out)
+ sent += AUD_write(*s->out[0], s->data_out + sent, s->idx_out - sent)
+ ?: s->idx_out;
+ s->idx_out = 0;
+}
+
+static void wm8750_audio_in_cb(void *opaque, int avail_b)
+{
+ WM8750State *s = (WM8750State *) opaque;
+ s->req_in = avail_b;
+ s->data_req(s->opaque, s->req_out >> 2, avail_b >> 2);
+}
+
+static void wm8750_audio_out_cb(void *opaque, int free_b)
+{
+ WM8750State *s = (WM8750State *) opaque;
+
+ if (s->idx_out >= free_b) {
+ s->idx_out = free_b;
+ s->req_out = 0;
+ wm8750_out_flush(s);
+ } else
+ s->req_out = free_b - s->idx_out;
+
+ s->data_req(s->opaque, s->req_out >> 2, s->req_in >> 2);
+}
+
+static const WMRate wm_rate_table[] = {
+ { 256, 48000, 256, 48000 }, /* SR: 00000 */
+ { 384, 48000, 384, 48000 }, /* SR: 00001 */
+ { 256, 48000, 1536, 8000 }, /* SR: 00010 */
+ { 384, 48000, 2304, 8000 }, /* SR: 00011 */
+ { 1536, 8000, 256, 48000 }, /* SR: 00100 */
+ { 2304, 8000, 384, 48000 }, /* SR: 00101 */
+ { 1536, 8000, 1536, 8000 }, /* SR: 00110 */
+ { 2304, 8000, 2304, 8000 }, /* SR: 00111 */
+ { 1024, 12000, 1024, 12000 }, /* SR: 01000 */
+ { 1526, 12000, 1536, 12000 }, /* SR: 01001 */
+ { 768, 16000, 768, 16000 }, /* SR: 01010 */
+ { 1152, 16000, 1152, 16000 }, /* SR: 01011 */
+ { 384, 32000, 384, 32000 }, /* SR: 01100 */
+ { 576, 32000, 576, 32000 }, /* SR: 01101 */
+ { 128, 96000, 128, 96000 }, /* SR: 01110 */
+ { 192, 96000, 192, 96000 }, /* SR: 01111 */
+ { 256, 44100, 256, 44100 }, /* SR: 10000 */
+ { 384, 44100, 384, 44100 }, /* SR: 10001 */
+ { 256, 44100, 1408, 8018 }, /* SR: 10010 */
+ { 384, 44100, 2112, 8018 }, /* SR: 10011 */
+ { 1408, 8018, 256, 44100 }, /* SR: 10100 */
+ { 2112, 8018, 384, 44100 }, /* SR: 10101 */
+ { 1408, 8018, 1408, 8018 }, /* SR: 10110 */
+ { 2112, 8018, 2112, 8018 }, /* SR: 10111 */
+ { 1024, 11025, 1024, 11025 }, /* SR: 11000 */
+ { 1536, 11025, 1536, 11025 }, /* SR: 11001 */
+ { 512, 22050, 512, 22050 }, /* SR: 11010 */
+ { 768, 22050, 768, 22050 }, /* SR: 11011 */
+ { 512, 24000, 512, 24000 }, /* SR: 11100 */
+ { 768, 24000, 768, 24000 }, /* SR: 11101 */
+ { 128, 88200, 128, 88200 }, /* SR: 11110 */
+ { 192, 88200, 192, 88200 }, /* SR: 11111 */
+};
+
+static void wm8750_vol_update(WM8750State *s)
+{
+ /* FIXME: multiply all volumes by s->invol[2], s->invol[3] */
+
+ AUD_set_volume_in(s->adc_voice[0], s->mute,
+ s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
+ s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
+ AUD_set_volume_in(s->adc_voice[1], s->mute,
+ s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
+ s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
+ AUD_set_volume_in(s->adc_voice[2], s->mute,
+ s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
+ s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
+
+ /* FIXME: multiply all volumes by s->outvol[0], s->outvol[1] */
+
+ /* Speaker: LOUT2VOL ROUT2VOL */
+ AUD_set_volume_out(s->dac_voice[0], s->mute,
+ s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[4]),
+ s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[5]));
+
+ /* Headphone: LOUT1VOL ROUT1VOL */
+ AUD_set_volume_out(s->dac_voice[1], s->mute,
+ s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[2]),
+ s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[3]));
+
+ /* MONOOUT: MONOVOL MONOVOL */
+ AUD_set_volume_out(s->dac_voice[2], s->mute,
+ s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6]),
+ s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6]));
+}
+
+static void wm8750_set_format(WM8750State *s)
+{
+ int i;
+ struct audsettings in_fmt;
+ struct audsettings out_fmt;
+
+ wm8750_out_flush(s);
+
+ if (s->in[0] && *s->in[0])
+ AUD_set_active_in(*s->in[0], 0);
+ if (s->out[0] && *s->out[0])
+ AUD_set_active_out(*s->out[0], 0);
+
+ for (i = 0; i < IN_PORT_N; i ++)
+ if (s->adc_voice[i]) {
+ AUD_close_in(&s->card, s->adc_voice[i]);
+ s->adc_voice[i] = NULL;
+ }
+ for (i = 0; i < OUT_PORT_N; i ++)
+ if (s->dac_voice[i]) {
+ AUD_close_out(&s->card, s->dac_voice[i]);
+ s->dac_voice[i] = NULL;
+ }
+
+ if (!s->enable)
+ return;
+
+ /* Setup input */
+ in_fmt.endianness = 0;
+ in_fmt.nchannels = 2;
+ in_fmt.freq = s->adc_hz;
+ in_fmt.fmt = AUD_FMT_S16;
+
+ s->adc_voice[0] = AUD_open_in(&s->card, s->adc_voice[0],
+ CODEC ".input1", s, wm8750_audio_in_cb, &in_fmt);
+ s->adc_voice[1] = AUD_open_in(&s->card, s->adc_voice[1],
+ CODEC ".input2", s, wm8750_audio_in_cb, &in_fmt);
+ s->adc_voice[2] = AUD_open_in(&s->card, s->adc_voice[2],
+ CODEC ".input3", s, wm8750_audio_in_cb, &in_fmt);
+
+ /* Setup output */
+ out_fmt.endianness = 0;
+ out_fmt.nchannels = 2;
+ out_fmt.freq = s->dac_hz;
+ out_fmt.fmt = AUD_FMT_S16;
+
+ s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0],
+ CODEC ".speaker", s, wm8750_audio_out_cb, &out_fmt);
+ s->dac_voice[1] = AUD_open_out(&s->card, s->dac_voice[1],
+ CODEC ".headphone", s, wm8750_audio_out_cb, &out_fmt);
+ /* MONOMIX is also in stereo for simplicity */
+ s->dac_voice[2] = AUD_open_out(&s->card, s->dac_voice[2],
+ CODEC ".monomix", s, wm8750_audio_out_cb, &out_fmt);
+ /* no sense emulating OUT3 which is a mix of other outputs */
+
+ wm8750_vol_update(s);
+
+ /* We should connect the left and right channels to their
+ * respective inputs/outputs but we have completely no need
+ * for mixing or combining paths to different ports, so we
+ * connect both channels to where the left channel is routed. */
+ if (s->in[0] && *s->in[0])
+ AUD_set_active_in(*s->in[0], 1);
+ if (s->out[0] && *s->out[0])
+ AUD_set_active_out(*s->out[0], 1);
+}
+
+static void wm8750_clk_update(WM8750State *s, int ext)
+{
+ if (s->master || !s->ext_dac_hz)
+ s->dac_hz = s->rate->dac_hz;
+ else
+ s->dac_hz = s->ext_dac_hz;
+
+ if (s->master || !s->ext_adc_hz)
+ s->adc_hz = s->rate->adc_hz;
+ else
+ s->adc_hz = s->ext_adc_hz;
+
+ if (s->master || (!s->ext_dac_hz && !s->ext_adc_hz)) {
+ if (!ext)
+ wm8750_set_format(s);
+ } else {
+ if (ext)
+ wm8750_set_format(s);
+ }
+}
+
+static void wm8750_reset(I2CSlave *i2c)
+{
+ WM8750State *s = WM8750(i2c);
+
+ s->rate = &wm_rate_table[0];
+ s->enable = 0;
+ wm8750_clk_update(s, 1);
+ s->diff[0] = 0;
+ s->diff[1] = 0;
+ s->ds = 0;
+ s->alc = 0;
+ s->in[0] = &s->adc_voice[0];
+ s->invol[0] = 0x17;
+ s->invol[1] = 0x17;
+ s->invol[2] = 0xc3;
+ s->invol[3] = 0xc3;
+ s->out[0] = &s->dac_voice[0];
+ s->outvol[0] = 0xff;
+ s->outvol[1] = 0xff;
+ s->outvol[2] = 0x79;
+ s->outvol[3] = 0x79;
+ s->outvol[4] = 0x79;
+ s->outvol[5] = 0x79;
+ s->outvol[6] = 0x79;
+ s->inmute[0] = 0;
+ s->inmute[1] = 0;
+ s->outmute[0] = 0;
+ s->outmute[1] = 0;
+ s->mute = 1;
+ s->path[0] = 0;
+ s->path[1] = 0;
+ s->path[2] = 0;
+ s->path[3] = 0;
+ s->mpath[0] = 0;
+ s->mpath[1] = 0;
+ s->format = 0x0a;
+ s->idx_in = sizeof(s->data_in);
+ s->req_in = 0;
+ s->idx_out = 0;
+ s->req_out = 0;
+ wm8750_vol_update(s);
+ s->i2c_len = 0;
+}
+
+static void wm8750_event(I2CSlave *i2c, enum i2c_event event)
+{
+ WM8750State *s = WM8750(i2c);
+
+ switch (event) {
+ case I2C_START_SEND:
+ s->i2c_len = 0;
+ break;
+ case I2C_FINISH:
+#ifdef VERBOSE
+ if (s->i2c_len < 2)
+ printf("%s: message too short (%i bytes)\n",
+ __FUNCTION__, s->i2c_len);
+#endif
+ break;
+ default:
+ break;
+ }
+}
+
+#define WM8750_LINVOL 0x00
+#define WM8750_RINVOL 0x01
+#define WM8750_LOUT1V 0x02
+#define WM8750_ROUT1V 0x03
+#define WM8750_ADCDAC 0x05
+#define WM8750_IFACE 0x07
+#define WM8750_SRATE 0x08
+#define WM8750_LDAC 0x0a
+#define WM8750_RDAC 0x0b
+#define WM8750_BASS 0x0c
+#define WM8750_TREBLE 0x0d
+#define WM8750_RESET 0x0f
+#define WM8750_3D 0x10
+#define WM8750_ALC1 0x11
+#define WM8750_ALC2 0x12
+#define WM8750_ALC3 0x13
+#define WM8750_NGATE 0x14
+#define WM8750_LADC 0x15
+#define WM8750_RADC 0x16
+#define WM8750_ADCTL1 0x17
+#define WM8750_ADCTL2 0x18
+#define WM8750_PWR1 0x19
+#define WM8750_PWR2 0x1a
+#define WM8750_ADCTL3 0x1b
+#define WM8750_ADCIN 0x1f
+#define WM8750_LADCIN 0x20
+#define WM8750_RADCIN 0x21
+#define WM8750_LOUTM1 0x22
+#define WM8750_LOUTM2 0x23
+#define WM8750_ROUTM1 0x24
+#define WM8750_ROUTM2 0x25
+#define WM8750_MOUTM1 0x26
+#define WM8750_MOUTM2 0x27
+#define WM8750_LOUT2V 0x28
+#define WM8750_ROUT2V 0x29
+#define WM8750_MOUTV 0x2a
+
+static int wm8750_tx(I2CSlave *i2c, uint8_t data)
+{
+ WM8750State *s = WM8750(i2c);
+ uint8_t cmd;
+ uint16_t value;
+
+ if (s->i2c_len >= 2) {
+#ifdef VERBOSE
+ printf("%s: long message (%i bytes)\n", __func__, s->i2c_len);
+#endif
+ return 1;
+ }
+ s->i2c_data[s->i2c_len ++] = data;
+ if (s->i2c_len != 2)
+ return 0;
+
+ cmd = s->i2c_data[0] >> 1;
+ value = ((s->i2c_data[0] << 8) | s->i2c_data[1]) & 0x1ff;
+
+ switch (cmd) {
+ case WM8750_LADCIN: /* ADC Signal Path Control (Left) */
+ s->diff[0] = (((value >> 6) & 3) == 3); /* LINSEL */
+ if (s->diff[0])
+ s->in[0] = &s->adc_voice[0 + s->ds * 1];
+ else
+ s->in[0] = &s->adc_voice[((value >> 6) & 3) * 1 + 0];
+ break;
+
+ case WM8750_RADCIN: /* ADC Signal Path Control (Right) */
+ s->diff[1] = (((value >> 6) & 3) == 3); /* RINSEL */
+ if (s->diff[1])
+ s->in[1] = &s->adc_voice[0 + s->ds * 1];
+ else
+ s->in[1] = &s->adc_voice[((value >> 6) & 3) * 1 + 0];
+ break;
+
+ case WM8750_ADCIN: /* ADC Input Mode */
+ s->ds = (value >> 8) & 1; /* DS */
+ if (s->diff[0])
+ s->in[0] = &s->adc_voice[0 + s->ds * 1];
+ if (s->diff[1])
+ s->in[1] = &s->adc_voice[0 + s->ds * 1];
+ s->monomix[0] = (value >> 6) & 3; /* MONOMIX */
+ break;
+
+ case WM8750_ADCTL1: /* Additional Control (1) */
+ s->monomix[1] = (value >> 1) & 1; /* DMONOMIX */
+ break;
+
+ case WM8750_PWR1: /* Power Management (1) */
+ s->enable = ((value >> 6) & 7) == 3; /* VMIDSEL, VREF */
+ wm8750_set_format(s);
+ break;
+
+ case WM8750_LINVOL: /* Left Channel PGA */
+ s->invol[0] = value & 0x3f; /* LINVOL */
+ s->inmute[0] = (value >> 7) & 1; /* LINMUTE */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_RINVOL: /* Right Channel PGA */
+ s->invol[1] = value & 0x3f; /* RINVOL */
+ s->inmute[1] = (value >> 7) & 1; /* RINMUTE */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_ADCDAC: /* ADC and DAC Control */
+ s->pol = (value >> 5) & 3; /* ADCPOL */
+ s->mute = (value >> 3) & 1; /* DACMU */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_ADCTL3: /* Additional Control (3) */
+ break;
+
+ case WM8750_LADC: /* Left ADC Digital Volume */
+ s->invol[2] = value & 0xff; /* LADCVOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_RADC: /* Right ADC Digital Volume */
+ s->invol[3] = value & 0xff; /* RADCVOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_ALC1: /* ALC Control (1) */
+ s->alc = (value >> 7) & 3; /* ALCSEL */
+ break;
+
+ case WM8750_NGATE: /* Noise Gate Control */
+ case WM8750_3D: /* 3D enhance */
+ break;
+
+ case WM8750_LDAC: /* Left Channel Digital Volume */
+ s->outvol[0] = value & 0xff; /* LDACVOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_RDAC: /* Right Channel Digital Volume */
+ s->outvol[1] = value & 0xff; /* RDACVOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_BASS: /* Bass Control */
+ break;
+
+ case WM8750_LOUTM1: /* Left Mixer Control (1) */
+ s->path[0] = (value >> 8) & 1; /* LD2LO */
+ /* TODO: mute/unmute respective paths */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_LOUTM2: /* Left Mixer Control (2) */
+ s->path[1] = (value >> 8) & 1; /* RD2LO */
+ /* TODO: mute/unmute respective paths */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_ROUTM1: /* Right Mixer Control (1) */
+ s->path[2] = (value >> 8) & 1; /* LD2RO */
+ /* TODO: mute/unmute respective paths */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_ROUTM2: /* Right Mixer Control (2) */
+ s->path[3] = (value >> 8) & 1; /* RD2RO */
+ /* TODO: mute/unmute respective paths */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_MOUTM1: /* Mono Mixer Control (1) */
+ s->mpath[0] = (value >> 8) & 1; /* LD2MO */
+ /* TODO: mute/unmute respective paths */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_MOUTM2: /* Mono Mixer Control (2) */
+ s->mpath[1] = (value >> 8) & 1; /* RD2MO */
+ /* TODO: mute/unmute respective paths */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_LOUT1V: /* LOUT1 Volume */
+ s->outvol[2] = value & 0x7f; /* LOUT1VOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_LOUT2V: /* LOUT2 Volume */
+ s->outvol[4] = value & 0x7f; /* LOUT2VOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_ROUT1V: /* ROUT1 Volume */
+ s->outvol[3] = value & 0x7f; /* ROUT1VOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_ROUT2V: /* ROUT2 Volume */
+ s->outvol[5] = value & 0x7f; /* ROUT2VOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_MOUTV: /* MONOOUT Volume */
+ s->outvol[6] = value & 0x7f; /* MONOOUTVOL */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_ADCTL2: /* Additional Control (2) */
+ break;
+
+ case WM8750_PWR2: /* Power Management (2) */
+ s->power = value & 0x7e;
+ /* TODO: mute/unmute respective paths */
+ wm8750_vol_update(s);
+ break;
+
+ case WM8750_IFACE: /* Digital Audio Interface Format */
+ s->format = value;
+ s->master = (value >> 6) & 1; /* MS */
+ wm8750_clk_update(s, s->master);
+ break;
+
+ case WM8750_SRATE: /* Clocking and Sample Rate Control */
+ s->rate = &wm_rate_table[(value >> 1) & 0x1f];
+ wm8750_clk_update(s, 0);
+ break;
+
+ case WM8750_RESET: /* Reset */
+ wm8750_reset(I2C_SLAVE(s));
+ break;
+
+#ifdef VERBOSE
+ default:
+ printf("%s: unknown register %02x\n", __FUNCTION__, cmd);
+#endif
+ }
+
+ return 0;
+}
+
+static int wm8750_rx(I2CSlave *i2c)
+{
+ return 0x00;
+}
+
+static void wm8750_pre_save(void *opaque)
+{
+ WM8750State *s = opaque;
+
+ s->rate_vmstate = s->rate - wm_rate_table;
+}
+
+static int wm8750_post_load(void *opaque, int version_id)
+{
+ WM8750State *s = opaque;
+
+ s->rate = &wm_rate_table[s->rate_vmstate & 0x1f];
+ return 0;
+}
+
+static const VMStateDescription vmstate_wm8750 = {
+ .name = CODEC,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_save = wm8750_pre_save,
+ .post_load = wm8750_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(i2c_data, WM8750State, 2),
+ VMSTATE_INT32(i2c_len, WM8750State),
+ VMSTATE_INT32(enable, WM8750State),
+ VMSTATE_INT32(idx_in, WM8750State),
+ VMSTATE_INT32(req_in, WM8750State),
+ VMSTATE_INT32(idx_out, WM8750State),
+ VMSTATE_INT32(req_out, WM8750State),
+ VMSTATE_UINT8_ARRAY(outvol, WM8750State, 7),
+ VMSTATE_UINT8_ARRAY(outmute, WM8750State, 2),
+ VMSTATE_UINT8_ARRAY(invol, WM8750State, 4),
+ VMSTATE_UINT8_ARRAY(inmute, WM8750State, 2),
+ VMSTATE_UINT8_ARRAY(diff, WM8750State, 2),
+ VMSTATE_UINT8(pol, WM8750State),
+ VMSTATE_UINT8(ds, WM8750State),
+ VMSTATE_UINT8_ARRAY(monomix, WM8750State, 2),
+ VMSTATE_UINT8(alc, WM8750State),
+ VMSTATE_UINT8(mute, WM8750State),
+ VMSTATE_UINT8_ARRAY(path, WM8750State, 4),
+ VMSTATE_UINT8_ARRAY(mpath, WM8750State, 2),
+ VMSTATE_UINT8(format, WM8750State),
+ VMSTATE_UINT8(power, WM8750State),
+ VMSTATE_UINT8(rate_vmstate, WM8750State),
+ VMSTATE_I2C_SLAVE(parent_obj, WM8750State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int wm8750_init(I2CSlave *i2c)
+{
+ WM8750State *s = WM8750(i2c);
+
+ AUD_register_card(CODEC, &s->card);
+ wm8750_reset(I2C_SLAVE(s));
+
+ return 0;
+}
+
+#if 0
+static void wm8750_fini(I2CSlave *i2c)
+{
+ WM8750State *s = WM8750(i2c);
+
+ wm8750_reset(I2C_SLAVE(s));
+ AUD_remove_card(&s->card);
+ g_free(s);
+}
+#endif
+
+void wm8750_data_req_set(DeviceState *dev,
+ void (*data_req)(void *, int, int), void *opaque)
+{
+ WM8750State *s = WM8750(dev);
+
+ s->data_req = data_req;
+ s->opaque = opaque;
+}
+
+void wm8750_dac_dat(void *opaque, uint32_t sample)
+{
+ WM8750State *s = (WM8750State *) opaque;
+
+ *(uint32_t *) &s->data_out[s->idx_out] = sample;
+ s->req_out -= 4;
+ s->idx_out += 4;
+ if (s->idx_out >= sizeof(s->data_out) || s->req_out <= 0)
+ wm8750_out_flush(s);
+}
+
+void *wm8750_dac_buffer(void *opaque, int samples)
+{
+ WM8750State *s = (WM8750State *) opaque;
+ /* XXX: Should check if there are <i>samples</i> free samples available */
+ void *ret = s->data_out + s->idx_out;
+
+ s->idx_out += samples << 2;
+ s->req_out -= samples << 2;
+ return ret;
+}
+
+void wm8750_dac_commit(void *opaque)
+{
+ WM8750State *s = (WM8750State *) opaque;
+
+ wm8750_out_flush(s);
+}
+
+uint32_t wm8750_adc_dat(void *opaque)
+{
+ WM8750State *s = (WM8750State *) opaque;
+ uint32_t *data;
+
+ if (s->idx_in >= sizeof(s->data_in))
+ wm8750_in_load(s);
+
+ data = (uint32_t *) &s->data_in[s->idx_in];
+ s->req_in -= 4;
+ s->idx_in += 4;
+ return *data;
+}
+
+void wm8750_set_bclk_in(void *opaque, int new_hz)
+{
+ WM8750State *s = (WM8750State *) opaque;
+
+ s->ext_adc_hz = new_hz;
+ s->ext_dac_hz = new_hz;
+ wm8750_clk_update(s, 1);
+}
+
+static void wm8750_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
+
+ sc->init = wm8750_init;
+ sc->event = wm8750_event;
+ sc->recv = wm8750_rx;
+ sc->send = wm8750_tx;
+ dc->vmsd = &vmstate_wm8750;
+}
+
+static const TypeInfo wm8750_info = {
+ .name = TYPE_WM8750,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(WM8750State),
+ .class_init = wm8750_class_init,
+};
+
+static void wm8750_register_types(void)
+{
+ type_register_static(&wm8750_info);
+}
+
+type_init(wm8750_register_types)
diff --git a/hw/block/Makefile.objs b/hw/block/Makefile.objs
new file mode 100644
index 00000000..d4c3ab75
--- /dev/null
+++ b/hw/block/Makefile.objs
@@ -0,0 +1,15 @@
+common-obj-y += block.o cdrom.o hd-geometry.o
+common-obj-$(CONFIG_FDC) += fdc.o
+common-obj-$(CONFIG_SSI_M25P80) += m25p80.o
+common-obj-$(CONFIG_NAND) += nand.o
+common-obj-$(CONFIG_PFLASH_CFI01) += pflash_cfi01.o
+common-obj-$(CONFIG_PFLASH_CFI02) += pflash_cfi02.o
+common-obj-$(CONFIG_XEN_BACKEND) += xen_disk.o
+common-obj-$(CONFIG_ECC) += ecc.o
+common-obj-$(CONFIG_ONENAND) += onenand.o
+common-obj-$(CONFIG_NVME_PCI) += nvme.o
+
+obj-$(CONFIG_SH4) += tc58128.o
+
+obj-$(CONFIG_VIRTIO) += virtio-blk.o
+obj-$(CONFIG_VIRTIO) += dataplane/
diff --git a/hw/block/block.c b/hw/block/block.c
new file mode 100644
index 00000000..f7243e5b
--- /dev/null
+++ b/hw/block/block.c
@@ -0,0 +1,91 @@
+/*
+ * Common code for block device models
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+
+#include "sysemu/blockdev.h"
+#include "sysemu/block-backend.h"
+#include "hw/block/block.h"
+#include "qemu/error-report.h"
+
+void blkconf_serial(BlockConf *conf, char **serial)
+{
+ DriveInfo *dinfo;
+
+ if (!*serial) {
+ /* try to fall back to value set with legacy -drive serial=... */
+ dinfo = blk_legacy_dinfo(conf->blk);
+ if (dinfo) {
+ *serial = g_strdup(dinfo->serial);
+ }
+ }
+}
+
+void blkconf_blocksizes(BlockConf *conf)
+{
+ BlockBackend *blk = conf->blk;
+ BlockSizes blocksizes;
+ int backend_ret;
+
+ backend_ret = blk_probe_blocksizes(blk, &blocksizes);
+ /* fill in detected values if they are not defined via qemu command line */
+ if (!conf->physical_block_size) {
+ if (!backend_ret) {
+ conf->physical_block_size = blocksizes.phys;
+ } else {
+ conf->physical_block_size = BDRV_SECTOR_SIZE;
+ }
+ }
+ if (!conf->logical_block_size) {
+ if (!backend_ret) {
+ conf->logical_block_size = blocksizes.log;
+ } else {
+ conf->logical_block_size = BDRV_SECTOR_SIZE;
+ }
+ }
+}
+
+void blkconf_geometry(BlockConf *conf, int *ptrans,
+ unsigned cyls_max, unsigned heads_max, unsigned secs_max,
+ Error **errp)
+{
+ DriveInfo *dinfo;
+
+ if (!conf->cyls && !conf->heads && !conf->secs) {
+ /* try to fall back to value set with legacy -drive cyls=... */
+ dinfo = blk_legacy_dinfo(conf->blk);
+ if (dinfo) {
+ conf->cyls = dinfo->cyls;
+ conf->heads = dinfo->heads;
+ conf->secs = dinfo->secs;
+ if (ptrans) {
+ *ptrans = dinfo->trans;
+ }
+ }
+ }
+ if (!conf->cyls && !conf->heads && !conf->secs) {
+ hd_geometry_guess(conf->blk,
+ &conf->cyls, &conf->heads, &conf->secs,
+ ptrans);
+ } else if (ptrans && *ptrans == BIOS_ATA_TRANSLATION_AUTO) {
+ *ptrans = hd_bios_chs_auto_trans(conf->cyls, conf->heads, conf->secs);
+ }
+ if (conf->cyls || conf->heads || conf->secs) {
+ if (conf->cyls < 1 || conf->cyls > cyls_max) {
+ error_setg(errp, "cyls must be between 1 and %u", cyls_max);
+ return;
+ }
+ if (conf->heads < 1 || conf->heads > heads_max) {
+ error_setg(errp, "heads must be between 1 and %u", heads_max);
+ return;
+ }
+ if (conf->secs < 1 || conf->secs > secs_max) {
+ error_setg(errp, "secs must be between 1 and %u", secs_max);
+ return;
+ }
+ }
+}
diff --git a/hw/block/cdrom.c b/hw/block/cdrom.c
new file mode 100644
index 00000000..4e1019c8
--- /dev/null
+++ b/hw/block/cdrom.c
@@ -0,0 +1,155 @@
+/*
+ * QEMU ATAPI CD-ROM Emulator
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * 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.
+ */
+
+/* ??? Most of the ATAPI emulation is still in ide.c. It should be moved
+ here. */
+
+#include "qemu-common.h"
+#include "hw/scsi/scsi.h"
+
+static void lba_to_msf(uint8_t *buf, int lba)
+{
+ lba += 150;
+ buf[0] = (lba / 75) / 60;
+ buf[1] = (lba / 75) % 60;
+ buf[2] = lba % 75;
+}
+
+/* same toc as bochs. Return -1 if error or the toc length */
+/* XXX: check this */
+int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track)
+{
+ uint8_t *q;
+ int len;
+
+ if (start_track > 1 && start_track != 0xaa)
+ return -1;
+ q = buf + 2;
+ *q++ = 1; /* first session */
+ *q++ = 1; /* last session */
+ if (start_track <= 1) {
+ *q++ = 0; /* reserved */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 1; /* track number */
+ *q++ = 0; /* reserved */
+ if (msf) {
+ *q++ = 0; /* reserved */
+ lba_to_msf(q, 0);
+ q += 3;
+ } else {
+ /* sector 0 */
+ stl_be_p(q, 0);
+ q += 4;
+ }
+ }
+ /* lead out track */
+ *q++ = 0; /* reserved */
+ *q++ = 0x16; /* ADR, control */
+ *q++ = 0xaa; /* track number */
+ *q++ = 0; /* reserved */
+ if (msf) {
+ *q++ = 0; /* reserved */
+ lba_to_msf(q, nb_sectors);
+ q += 3;
+ } else {
+ stl_be_p(q, nb_sectors);
+ q += 4;
+ }
+ len = q - buf;
+ stw_be_p(buf, len - 2);
+ return len;
+}
+
+/* mostly same info as PearPc */
+int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num)
+{
+ uint8_t *q;
+ int len;
+
+ q = buf + 2;
+ *q++ = 1; /* first session */
+ *q++ = 1; /* last session */
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa0; /* lead-in */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* first track */
+ *q++ = 0x00; /* disk type */
+ *q++ = 0x00;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa1;
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* last track */
+ *q++ = 0x00;
+ *q++ = 0x00;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa2; /* lead-out */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (msf) {
+ *q++ = 0; /* reserved */
+ lba_to_msf(q, nb_sectors);
+ q += 3;
+ } else {
+ stl_be_p(q, nb_sectors);
+ q += 4;
+ }
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0; /* track number */
+ *q++ = 1; /* point */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (msf) {
+ *q++ = 0;
+ lba_to_msf(q, 0);
+ q += 3;
+ } else {
+ *q++ = 0;
+ *q++ = 0;
+ *q++ = 0;
+ *q++ = 0;
+ }
+
+ len = q - buf;
+ stw_be_p(buf, len - 2);
+ return len;
+}
diff --git a/hw/block/dataplane/Makefile.objs b/hw/block/dataplane/Makefile.objs
new file mode 100644
index 00000000..e786f664
--- /dev/null
+++ b/hw/block/dataplane/Makefile.objs
@@ -0,0 +1 @@
+obj-y += virtio-blk.o
diff --git a/hw/block/dataplane/virtio-blk.c b/hw/block/dataplane/virtio-blk.c
new file mode 100644
index 00000000..6106e461
--- /dev/null
+++ b/hw/block/dataplane/virtio-blk.c
@@ -0,0 +1,341 @@
+/*
+ * Dedicated thread for virtio-blk I/O processing
+ *
+ * Copyright 2012 IBM, Corp.
+ * Copyright 2012 Red Hat, Inc. and/or its affiliates
+ *
+ * Authors:
+ * Stefan Hajnoczi <stefanha@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "trace.h"
+#include "qemu/iov.h"
+#include "qemu/thread.h"
+#include "qemu/error-report.h"
+#include "hw/virtio/virtio-access.h"
+#include "hw/virtio/dataplane/vring.h"
+#include "hw/virtio/dataplane/vring-accessors.h"
+#include "sysemu/block-backend.h"
+#include "hw/virtio/virtio-blk.h"
+#include "virtio-blk.h"
+#include "block/aio.h"
+#include "hw/virtio/virtio-bus.h"
+#include "qom/object_interfaces.h"
+
+struct VirtIOBlockDataPlane {
+ bool started;
+ bool starting;
+ bool stopping;
+ bool disabled;
+
+ VirtIOBlkConf *conf;
+
+ VirtIODevice *vdev;
+ Vring vring; /* virtqueue vring */
+ EventNotifier *guest_notifier; /* irq */
+ QEMUBH *bh; /* bh for guest notification */
+
+ /* Note that these EventNotifiers are assigned by value. This is
+ * fine as long as you do not call event_notifier_cleanup on them
+ * (because you don't own the file descriptor or handle; you just
+ * use it).
+ */
+ IOThread *iothread;
+ IOThread internal_iothread_obj;
+ AioContext *ctx;
+ EventNotifier host_notifier; /* doorbell */
+
+ /* Operation blocker on BDS */
+ Error *blocker;
+ void (*saved_complete_request)(struct VirtIOBlockReq *req,
+ unsigned char status);
+};
+
+/* Raise an interrupt to signal guest, if necessary */
+static void notify_guest(VirtIOBlockDataPlane *s)
+{
+ if (!vring_should_notify(s->vdev, &s->vring)) {
+ return;
+ }
+
+ event_notifier_set(s->guest_notifier);
+}
+
+static void notify_guest_bh(void *opaque)
+{
+ VirtIOBlockDataPlane *s = opaque;
+
+ notify_guest(s);
+}
+
+static void complete_request_vring(VirtIOBlockReq *req, unsigned char status)
+{
+ VirtIOBlockDataPlane *s = req->dev->dataplane;
+ stb_p(&req->in->status, status);
+
+ vring_push(s->vdev, &req->dev->dataplane->vring, &req->elem, req->in_len);
+
+ /* Suppress notification to guest by BH and its scheduled
+ * flag because requests are completed as a batch after io
+ * plug & unplug is introduced, and the BH can still be
+ * executed in dataplane aio context even after it is
+ * stopped, so needn't worry about notification loss with BH.
+ */
+ qemu_bh_schedule(s->bh);
+}
+
+static void handle_notify(EventNotifier *e)
+{
+ VirtIOBlockDataPlane *s = container_of(e, VirtIOBlockDataPlane,
+ host_notifier);
+ VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
+
+ event_notifier_test_and_clear(&s->host_notifier);
+ blk_io_plug(s->conf->conf.blk);
+ for (;;) {
+ MultiReqBuffer mrb = {};
+ int ret;
+
+ /* Disable guest->host notifies to avoid unnecessary vmexits */
+ vring_disable_notification(s->vdev, &s->vring);
+
+ for (;;) {
+ VirtIOBlockReq *req = virtio_blk_alloc_request(vblk);
+
+ ret = vring_pop(s->vdev, &s->vring, &req->elem);
+ if (ret < 0) {
+ virtio_blk_free_request(req);
+ break; /* no more requests */
+ }
+
+ trace_virtio_blk_data_plane_process_request(s, req->elem.out_num,
+ req->elem.in_num,
+ req->elem.index);
+
+ virtio_blk_handle_request(req, &mrb);
+ }
+
+ if (mrb.num_reqs) {
+ virtio_blk_submit_multireq(s->conf->conf.blk, &mrb);
+ }
+
+ if (likely(ret == -EAGAIN)) { /* vring emptied */
+ /* Re-enable guest->host notifies and stop processing the vring.
+ * But if the guest has snuck in more descriptors, keep processing.
+ */
+ if (vring_enable_notification(s->vdev, &s->vring)) {
+ break;
+ }
+ } else { /* fatal error */
+ break;
+ }
+ }
+ blk_io_unplug(s->conf->conf.blk);
+}
+
+/* Context: QEMU global mutex held */
+void virtio_blk_data_plane_create(VirtIODevice *vdev, VirtIOBlkConf *conf,
+ VirtIOBlockDataPlane **dataplane,
+ Error **errp)
+{
+ VirtIOBlockDataPlane *s;
+ Error *local_err = NULL;
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+
+ *dataplane = NULL;
+
+ if (!conf->data_plane && !conf->iothread) {
+ return;
+ }
+
+ /* Don't try if transport does not support notifiers. */
+ if (!k->set_guest_notifiers || !k->set_host_notifier) {
+ error_setg(errp,
+ "device is incompatible with x-data-plane "
+ "(transport does not support notifiers)");
+ return;
+ }
+
+ /* If dataplane is (re-)enabled while the guest is running there could be
+ * block jobs that can conflict.
+ */
+ if (blk_op_is_blocked(conf->conf.blk, BLOCK_OP_TYPE_DATAPLANE,
+ &local_err)) {
+ error_setg(errp, "cannot start dataplane thread: %s",
+ error_get_pretty(local_err));
+ error_free(local_err);
+ return;
+ }
+
+ s = g_new0(VirtIOBlockDataPlane, 1);
+ s->vdev = vdev;
+ s->conf = conf;
+
+ if (conf->iothread) {
+ s->iothread = conf->iothread;
+ object_ref(OBJECT(s->iothread));
+ } else {
+ /* Create per-device IOThread if none specified. This is for
+ * x-data-plane option compatibility. If x-data-plane is removed we
+ * can drop this.
+ */
+ object_initialize(&s->internal_iothread_obj,
+ sizeof(s->internal_iothread_obj),
+ TYPE_IOTHREAD);
+ user_creatable_complete(OBJECT(&s->internal_iothread_obj), &error_abort);
+ s->iothread = &s->internal_iothread_obj;
+ }
+ s->ctx = iothread_get_aio_context(s->iothread);
+ s->bh = aio_bh_new(s->ctx, notify_guest_bh, s);
+
+ error_setg(&s->blocker, "block device is in use by data plane");
+ blk_op_block_all(conf->conf.blk, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_RESIZE, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_DRIVE_DEL, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_CHANGE, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_COMMIT_SOURCE, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_COMMIT_TARGET, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_EJECT, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT_DELETE,
+ s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_MIRROR, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_STREAM, s->blocker);
+ blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_REPLACE, s->blocker);
+
+ *dataplane = s;
+}
+
+/* Context: QEMU global mutex held */
+void virtio_blk_data_plane_destroy(VirtIOBlockDataPlane *s)
+{
+ if (!s) {
+ return;
+ }
+
+ virtio_blk_data_plane_stop(s);
+ blk_op_unblock_all(s->conf->conf.blk, s->blocker);
+ error_free(s->blocker);
+ qemu_bh_delete(s->bh);
+ object_unref(OBJECT(s->iothread));
+ g_free(s);
+}
+
+/* Context: QEMU global mutex held */
+void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
+ VirtQueue *vq;
+ int r;
+
+ if (s->started || s->disabled) {
+ return;
+ }
+
+ if (s->starting) {
+ return;
+ }
+
+ s->starting = true;
+
+ vq = virtio_get_queue(s->vdev, 0);
+ if (!vring_setup(&s->vring, s->vdev, 0)) {
+ goto fail_vring;
+ }
+
+ /* Set up guest notifier (irq) */
+ r = k->set_guest_notifiers(qbus->parent, 1, true);
+ if (r != 0) {
+ fprintf(stderr, "virtio-blk failed to set guest notifier (%d), "
+ "ensure -enable-kvm is set\n", r);
+ goto fail_guest_notifiers;
+ }
+ s->guest_notifier = virtio_queue_get_guest_notifier(vq);
+
+ /* Set up virtqueue notify */
+ r = k->set_host_notifier(qbus->parent, 0, true);
+ if (r != 0) {
+ fprintf(stderr, "virtio-blk failed to set host notifier (%d)\n", r);
+ goto fail_host_notifier;
+ }
+ s->host_notifier = *virtio_queue_get_host_notifier(vq);
+
+ s->saved_complete_request = vblk->complete_request;
+ vblk->complete_request = complete_request_vring;
+
+ s->starting = false;
+ s->started = true;
+ trace_virtio_blk_data_plane_start(s);
+
+ blk_set_aio_context(s->conf->conf.blk, s->ctx);
+
+ /* Kick right away to begin processing requests already in vring */
+ event_notifier_set(virtio_queue_get_host_notifier(vq));
+
+ /* Get this show started by hooking up our callbacks */
+ aio_context_acquire(s->ctx);
+ aio_set_event_notifier(s->ctx, &s->host_notifier, handle_notify);
+ aio_context_release(s->ctx);
+ return;
+
+ fail_host_notifier:
+ k->set_guest_notifiers(qbus->parent, 1, false);
+ fail_guest_notifiers:
+ vring_teardown(&s->vring, s->vdev, 0);
+ s->disabled = true;
+ fail_vring:
+ s->starting = false;
+}
+
+/* Context: QEMU global mutex held */
+void virtio_blk_data_plane_stop(VirtIOBlockDataPlane *s)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
+
+
+ /* Better luck next time. */
+ if (s->disabled) {
+ s->disabled = false;
+ return;
+ }
+ if (!s->started || s->stopping) {
+ return;
+ }
+ s->stopping = true;
+ vblk->complete_request = s->saved_complete_request;
+ trace_virtio_blk_data_plane_stop(s);
+
+ aio_context_acquire(s->ctx);
+
+ /* Stop notifications for new requests from guest */
+ aio_set_event_notifier(s->ctx, &s->host_notifier, NULL);
+
+ /* Drain and switch bs back to the QEMU main loop */
+ blk_set_aio_context(s->conf->conf.blk, qemu_get_aio_context());
+
+ aio_context_release(s->ctx);
+
+ /* Sync vring state back to virtqueue so that non-dataplane request
+ * processing can continue when we disable the host notifier below.
+ */
+ vring_teardown(&s->vring, s->vdev, 0);
+
+ k->set_host_notifier(qbus->parent, 0, false);
+
+ /* Clean up guest notifier (irq) */
+ k->set_guest_notifiers(qbus->parent, 1, false);
+
+ s->started = false;
+ s->stopping = false;
+}
diff --git a/hw/block/dataplane/virtio-blk.h b/hw/block/dataplane/virtio-blk.h
new file mode 100644
index 00000000..c88d40e7
--- /dev/null
+++ b/hw/block/dataplane/virtio-blk.h
@@ -0,0 +1,30 @@
+/*
+ * Dedicated thread for virtio-blk I/O processing
+ *
+ * Copyright 2012 IBM, Corp.
+ * Copyright 2012 Red Hat, Inc. and/or its affiliates
+ *
+ * Authors:
+ * Stefan Hajnoczi <stefanha@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef HW_DATAPLANE_VIRTIO_BLK_H
+#define HW_DATAPLANE_VIRTIO_BLK_H
+
+#include "hw/virtio/virtio.h"
+
+typedef struct VirtIOBlockDataPlane VirtIOBlockDataPlane;
+
+void virtio_blk_data_plane_create(VirtIODevice *vdev, VirtIOBlkConf *conf,
+ VirtIOBlockDataPlane **dataplane,
+ Error **errp);
+void virtio_blk_data_plane_destroy(VirtIOBlockDataPlane *s);
+void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s);
+void virtio_blk_data_plane_stop(VirtIOBlockDataPlane *s);
+void virtio_blk_data_plane_drain(VirtIOBlockDataPlane *s);
+
+#endif /* HW_DATAPLANE_VIRTIO_BLK_H */
diff --git a/hw/block/ecc.c b/hw/block/ecc.c
new file mode 100644
index 00000000..10bb2330
--- /dev/null
+++ b/hw/block/ecc.c
@@ -0,0 +1,90 @@
+/*
+ * Calculate Error-correcting Codes. Used by NAND Flash controllers
+ * (not by NAND chips).
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/block/flash.h"
+
+/*
+ * Pre-calculated 256-way 1 byte column parity. Table borrowed from Linux.
+ */
+static const uint8_t nand_ecc_precalc_table[] = {
+ 0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a,
+ 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,
+ 0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f,
+ 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
+ 0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c,
+ 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
+ 0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59,
+ 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
+ 0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33,
+ 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
+ 0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56,
+ 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
+ 0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55,
+ 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
+ 0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30,
+ 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
+ 0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30,
+ 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
+ 0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55,
+ 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
+ 0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56,
+ 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
+ 0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33,
+ 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
+ 0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59,
+ 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
+ 0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c,
+ 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
+ 0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f,
+ 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
+ 0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a,
+ 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,
+};
+
+/* Update ECC parity count. */
+uint8_t ecc_digest(ECCState *s, uint8_t sample)
+{
+ uint8_t idx = nand_ecc_precalc_table[sample];
+
+ s->cp ^= idx & 0x3f;
+ if (idx & 0x40) {
+ s->lp[0] ^= ~s->count;
+ s->lp[1] ^= s->count;
+ }
+ s->count ++;
+
+ return sample;
+}
+
+/* Reinitialise the counters. */
+void ecc_reset(ECCState *s)
+{
+ s->lp[0] = 0x0000;
+ s->lp[1] = 0x0000;
+ s->cp = 0x00;
+ s->count = 0;
+}
+
+/* Save/restore */
+VMStateDescription vmstate_ecc_state = {
+ .name = "ecc-state",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(cp, ECCState),
+ VMSTATE_UINT16_ARRAY(lp, ECCState, 2),
+ VMSTATE_UINT16(count, ECCState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
diff --git a/hw/block/fdc.c b/hw/block/fdc.c
new file mode 100644
index 00000000..5e1b67ee
--- /dev/null
+++ b/hw/block/fdc.c
@@ -0,0 +1,2529 @@
+/*
+ * 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 "hw/hw.h"
+#include "hw/block/fdc.h"
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "hw/isa/isa.h"
+#include "hw/sysbus.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/sysemu.h"
+#include "qemu/log.h"
+
+/********************************************************/
+/* debug Floppy devices */
+//#define DEBUG_FLOPPY
+
+#ifdef DEBUG_FLOPPY
+#define FLOPPY_DPRINTF(fmt, ...) \
+ do { printf("FLOPPY: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define FLOPPY_DPRINTF(fmt, ...)
+#endif
+
+/********************************************************/
+/* Floppy drive emulation */
+
+typedef enum FDriveRate {
+ FDRIVE_RATE_500K = 0x00, /* 500 Kbps */
+ FDRIVE_RATE_300K = 0x01, /* 300 Kbps */
+ FDRIVE_RATE_250K = 0x02, /* 250 Kbps */
+ FDRIVE_RATE_1M = 0x03, /* 1 Mbps */
+} FDriveRate;
+
+typedef struct FDFormat {
+ FDriveType drive;
+ uint8_t last_sect;
+ uint8_t max_track;
+ uint8_t max_head;
+ FDriveRate rate;
+} FDFormat;
+
+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, },
+ /* 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, },
+ /* 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, },
+ /* 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, },
+ /* 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, },
+ /* 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, },
+ /* 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, },
+ /* 360 kB must match 5"1/4 better than 3"1/2... */
+ { FDRIVE_DRV_144, 9, 80, 0, FDRIVE_RATE_250K, },
+ /* end */
+ { FDRIVE_DRV_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)
+{
+ 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];
+ }
+ *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 SET_CUR_DRV(fdctrl, drive) ((fdctrl)->cur_drv = (drive))
+
+/* Will always be a fixed parameter for us */
+#define FD_SECTOR_LEN 512
+#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 {
+ FDCtrl *fdctrl;
+ BlockBackend *blk;
+ /* Drive status */
+ FDriveType drive;
+ uint8_t perpendicular; /* 2.88 MB access mode */
+ /* Position */
+ uint8_t head;
+ uint8_t track;
+ uint8_t sect;
+ /* Media */
+ FDiskFlags flags;
+ uint8_t last_sect; /* Nb sector per track */
+ uint8_t max_track; /* Nb of tracks */
+ uint16_t bps; /* Bytes per sector */
+ uint8_t ro; /* Is read-only */
+ uint8_t media_changed; /* Is media changed */
+ uint8_t media_rate; /* Data rate of medium */
+} FDrive;
+
+static void fd_init(FDrive *drv)
+{
+ /* Drive */
+ drv->drive = FDRIVE_DRV_NONE;
+ drv->perpendicular = 0;
+ /* Disk */
+ drv->last_sect = 0;
+ drv->max_track = 0;
+}
+
+#define NUM_SIDES(drv) ((drv)->flags & FDISK_DBL_SIDES ? 2 : 1)
+
+static int fd_sector_calc(uint8_t head, uint8_t track, uint8_t sect,
+ uint8_t last_sect, uint8_t num_sides)
+{
+ return (((track * num_sides) + head) * last_sect) + sect - 1;
+}
+
+/* Returns current position, in sectors, for given drive */
+static int fd_sector(FDrive *drv)
+{
+ return fd_sector_calc(drv->head, drv->track, drv->sect, drv->last_sect,
+ NUM_SIDES(drv));
+}
+
+/* Seek to a new position:
+ * returns 0 if already on right track
+ * returns 1 if track changed
+ * returns 2 if track is invalid
+ * returns 3 if sector is invalid
+ * returns 4 if seek is disabled
+ */
+static int fd_seek(FDrive *drv, uint8_t head, uint8_t track, uint8_t sect,
+ int enable_seek)
+{
+ uint32_t sector;
+ int ret;
+
+ if (track > drv->max_track ||
+ (head != 0 && (drv->flags & FDISK_DBL_SIDES) == 0)) {
+ FLOPPY_DPRINTF("try to read %d %02x %02x (max=%d %d %02x %02x)\n",
+ head, track, sect, 1,
+ (drv->flags & FDISK_DBL_SIDES) == 0 ? 0 : 1,
+ drv->max_track, drv->last_sect);
+ return 2;
+ }
+ if (sect > drv->last_sect) {
+ FLOPPY_DPRINTF("try to read %d %02x %02x (max=%d %d %02x %02x)\n",
+ head, track, sect, 1,
+ (drv->flags & FDISK_DBL_SIDES) == 0 ? 0 : 1,
+ drv->max_track, drv->last_sect);
+ return 3;
+ }
+ sector = fd_sector_calc(head, track, sect, drv->last_sect, NUM_SIDES(drv));
+ ret = 0;
+ if (sector != fd_sector(drv)) {
+#if 0
+ if (!enable_seek) {
+ FLOPPY_DPRINTF("error: no implicit seek %d %02x %02x"
+ " (max=%d %02x %02x)\n",
+ head, track, sect, 1, drv->max_track,
+ drv->last_sect);
+ return 4;
+ }
+#endif
+ drv->head = head;
+ if (drv->track != track) {
+ if (drv->blk != NULL && blk_is_inserted(drv->blk)) {
+ drv->media_changed = 0;
+ }
+ ret = 1;
+ }
+ drv->track = track;
+ drv->sect = sect;
+ }
+
+ if (drv->blk == NULL || !blk_is_inserted(drv->blk)) {
+ ret = 2;
+ }
+
+ return ret;
+}
+
+/* Set drive back to track 0 */
+static void fd_recalibrate(FDrive *drv)
+{
+ FLOPPY_DPRINTF("recalibrate\n");
+ fd_seek(drv, 0, 0, 1, 1);
+}
+
+/* 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;
+
+ 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);
+ 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->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;
+ }
+}
+
+/********************************************************/
+/* Intel 82078 floppy disk controller emulation */
+
+static void fdctrl_reset(FDCtrl *fdctrl, int do_irq);
+static void fdctrl_to_command_phase(FDCtrl *fdctrl);
+static int fdctrl_transfer_handler (void *opaque, int nchan,
+ int dma_pos, int dma_len);
+static void fdctrl_raise_irq(FDCtrl *fdctrl);
+static FDrive *get_cur_drv(FDCtrl *fdctrl);
+
+static uint32_t fdctrl_read_statusA(FDCtrl *fdctrl);
+static uint32_t fdctrl_read_statusB(FDCtrl *fdctrl);
+static uint32_t fdctrl_read_dor(FDCtrl *fdctrl);
+static void fdctrl_write_dor(FDCtrl *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_tape(FDCtrl *fdctrl);
+static void fdctrl_write_tape(FDCtrl *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_main_status(FDCtrl *fdctrl);
+static void fdctrl_write_rate(FDCtrl *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_data(FDCtrl *fdctrl);
+static void fdctrl_write_data(FDCtrl *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_dir(FDCtrl *fdctrl);
+static void fdctrl_write_ccr(FDCtrl *fdctrl, uint32_t value);
+
+enum {
+ FD_DIR_WRITE = 0,
+ FD_DIR_READ = 1,
+ FD_DIR_SCANE = 2,
+ FD_DIR_SCANL = 3,
+ FD_DIR_SCANH = 4,
+ FD_DIR_VERIFY = 5,
+};
+
+enum {
+ FD_STATE_MULTI = 0x01, /* multi track flag */
+ FD_STATE_FORMAT = 0x02, /* format flag */
+};
+
+enum {
+ FD_REG_SRA = 0x00,
+ FD_REG_SRB = 0x01,
+ FD_REG_DOR = 0x02,
+ FD_REG_TDR = 0x03,
+ FD_REG_MSR = 0x04,
+ FD_REG_DSR = 0x04,
+ FD_REG_FIFO = 0x05,
+ FD_REG_DIR = 0x07,
+ FD_REG_CCR = 0x07,
+};
+
+enum {
+ FD_CMD_READ_TRACK = 0x02,
+ FD_CMD_SPECIFY = 0x03,
+ FD_CMD_SENSE_DRIVE_STATUS = 0x04,
+ FD_CMD_WRITE = 0x05,
+ FD_CMD_READ = 0x06,
+ FD_CMD_RECALIBRATE = 0x07,
+ FD_CMD_SENSE_INTERRUPT_STATUS = 0x08,
+ FD_CMD_WRITE_DELETED = 0x09,
+ FD_CMD_READ_ID = 0x0a,
+ FD_CMD_READ_DELETED = 0x0c,
+ FD_CMD_FORMAT_TRACK = 0x0d,
+ FD_CMD_DUMPREG = 0x0e,
+ FD_CMD_SEEK = 0x0f,
+ FD_CMD_VERSION = 0x10,
+ FD_CMD_SCAN_EQUAL = 0x11,
+ FD_CMD_PERPENDICULAR_MODE = 0x12,
+ FD_CMD_CONFIGURE = 0x13,
+ FD_CMD_LOCK = 0x14,
+ FD_CMD_VERIFY = 0x16,
+ FD_CMD_POWERDOWN_MODE = 0x17,
+ FD_CMD_PART_ID = 0x18,
+ FD_CMD_SCAN_LOW_OR_EQUAL = 0x19,
+ FD_CMD_SCAN_HIGH_OR_EQUAL = 0x1d,
+ FD_CMD_SAVE = 0x2e,
+ FD_CMD_OPTION = 0x33,
+ FD_CMD_RESTORE = 0x4e,
+ FD_CMD_DRIVE_SPECIFICATION_COMMAND = 0x8e,
+ FD_CMD_RELATIVE_SEEK_OUT = 0x8f,
+ FD_CMD_FORMAT_AND_WRITE = 0xcd,
+ FD_CMD_RELATIVE_SEEK_IN = 0xcf,
+};
+
+enum {
+ FD_CONFIG_PRETRK = 0xff, /* Pre-compensation set to track 0 */
+ FD_CONFIG_FIFOTHR = 0x0f, /* FIFO threshold set to 1 byte */
+ FD_CONFIG_POLL = 0x10, /* Poll enabled */
+ FD_CONFIG_EFIFO = 0x20, /* FIFO disabled */
+ FD_CONFIG_EIS = 0x40, /* No implied seeks */
+};
+
+enum {
+ FD_SR0_DS0 = 0x01,
+ FD_SR0_DS1 = 0x02,
+ FD_SR0_HEAD = 0x04,
+ FD_SR0_EQPMT = 0x10,
+ FD_SR0_SEEK = 0x20,
+ FD_SR0_ABNTERM = 0x40,
+ FD_SR0_INVCMD = 0x80,
+ FD_SR0_RDYCHG = 0xc0,
+};
+
+enum {
+ FD_SR1_MA = 0x01, /* Missing address mark */
+ FD_SR1_NW = 0x02, /* Not writable */
+ FD_SR1_EC = 0x80, /* End of cylinder */
+};
+
+enum {
+ FD_SR2_SNS = 0x04, /* Scan not satisfied */
+ FD_SR2_SEH = 0x08, /* Scan equal hit */
+};
+
+enum {
+ FD_SRA_DIR = 0x01,
+ FD_SRA_nWP = 0x02,
+ FD_SRA_nINDX = 0x04,
+ FD_SRA_HDSEL = 0x08,
+ FD_SRA_nTRK0 = 0x10,
+ FD_SRA_STEP = 0x20,
+ FD_SRA_nDRV2 = 0x40,
+ FD_SRA_INTPEND = 0x80,
+};
+
+enum {
+ FD_SRB_MTR0 = 0x01,
+ FD_SRB_MTR1 = 0x02,
+ FD_SRB_WGATE = 0x04,
+ FD_SRB_RDATA = 0x08,
+ FD_SRB_WDATA = 0x10,
+ FD_SRB_DR0 = 0x20,
+};
+
+enum {
+#if MAX_FD == 4
+ FD_DOR_SELMASK = 0x03,
+#else
+ FD_DOR_SELMASK = 0x01,
+#endif
+ FD_DOR_nRESET = 0x04,
+ FD_DOR_DMAEN = 0x08,
+ FD_DOR_MOTEN0 = 0x10,
+ FD_DOR_MOTEN1 = 0x20,
+ FD_DOR_MOTEN2 = 0x40,
+ FD_DOR_MOTEN3 = 0x80,
+};
+
+enum {
+#if MAX_FD == 4
+ FD_TDR_BOOTSEL = 0x0c,
+#else
+ FD_TDR_BOOTSEL = 0x04,
+#endif
+};
+
+enum {
+ FD_DSR_DRATEMASK= 0x03,
+ FD_DSR_PWRDOWN = 0x40,
+ FD_DSR_SWRESET = 0x80,
+};
+
+enum {
+ FD_MSR_DRV0BUSY = 0x01,
+ FD_MSR_DRV1BUSY = 0x02,
+ FD_MSR_DRV2BUSY = 0x04,
+ FD_MSR_DRV3BUSY = 0x08,
+ FD_MSR_CMDBUSY = 0x10,
+ FD_MSR_NONDMA = 0x20,
+ FD_MSR_DIO = 0x40,
+ FD_MSR_RQM = 0x80,
+};
+
+enum {
+ FD_DIR_DSKCHG = 0x80,
+};
+
+/*
+ * See chapter 5.0 "Controller phases" of the spec:
+ *
+ * Command phase:
+ * The host writes a command and its parameters into the FIFO. The command
+ * phase is completed when all parameters for the command have been supplied,
+ * and execution phase is entered.
+ *
+ * Execution phase:
+ * Data transfers, either DMA or non-DMA. For non-DMA transfers, the FIFO
+ * contains the payload now, otherwise it's unused. When all bytes of the
+ * required data have been transferred, the state is switched to either result
+ * phase (if the command produces status bytes) or directly back into the
+ * command phase for the next command.
+ *
+ * Result phase:
+ * The host reads out the FIFO, which contains one or more result bytes now.
+ */
+enum {
+ /* Only for migration: reconstruct phase from registers like qemu 2.3 */
+ FD_PHASE_RECONSTRUCT = 0,
+
+ FD_PHASE_COMMAND = 1,
+ FD_PHASE_EXECUTION = 2,
+ FD_PHASE_RESULT = 3,
+};
+
+#define FD_MULTI_TRACK(state) ((state) & FD_STATE_MULTI)
+#define FD_FORMAT_CMD(state) ((state) & FD_STATE_FORMAT)
+
+struct FDCtrl {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ /* Controller state */
+ QEMUTimer *result_timer;
+ int dma_chann;
+ uint8_t phase;
+ /* Controller's identification */
+ uint8_t version;
+ /* HW */
+ uint8_t sra;
+ uint8_t srb;
+ uint8_t dor;
+ uint8_t dor_vmstate; /* only used as temp during vmstate */
+ uint8_t tdr;
+ uint8_t dsr;
+ uint8_t msr;
+ uint8_t cur_drv;
+ uint8_t status0;
+ uint8_t status1;
+ uint8_t status2;
+ /* Command FIFO */
+ uint8_t *fifo;
+ int32_t fifo_size;
+ uint32_t data_pos;
+ uint32_t data_len;
+ uint8_t data_state;
+ uint8_t data_dir;
+ uint8_t eot; /* last wanted sector */
+ /* States kept only to be returned back */
+ /* precompensation */
+ uint8_t precomp_trk;
+ uint8_t config;
+ uint8_t lock;
+ /* Power down config (also with status regB access mode */
+ uint8_t pwrd;
+ /* Floppy drives */
+ uint8_t num_floppies;
+ FDrive drives[MAX_FD];
+ int reset_sensei;
+ uint32_t check_media_rate;
+ /* Timers state */
+ uint8_t timer0;
+ uint8_t timer1;
+};
+
+#define TYPE_SYSBUS_FDC "base-sysbus-fdc"
+#define SYSBUS_FDC(obj) OBJECT_CHECK(FDCtrlSysBus, (obj), TYPE_SYSBUS_FDC)
+
+typedef struct FDCtrlSysBus {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ struct FDCtrl state;
+} FDCtrlSysBus;
+
+#define ISA_FDC(obj) OBJECT_CHECK(FDCtrlISABus, (obj), TYPE_ISA_FDC)
+
+typedef struct FDCtrlISABus {
+ ISADevice parent_obj;
+
+ uint32_t iobase;
+ uint32_t irq;
+ uint32_t dma;
+ struct FDCtrl state;
+ int32_t bootindexA;
+ int32_t bootindexB;
+} FDCtrlISABus;
+
+static uint32_t fdctrl_read (void *opaque, uint32_t reg)
+{
+ FDCtrl *fdctrl = opaque;
+ uint32_t retval;
+
+ reg &= 7;
+ switch (reg) {
+ case FD_REG_SRA:
+ retval = fdctrl_read_statusA(fdctrl);
+ break;
+ case FD_REG_SRB:
+ retval = fdctrl_read_statusB(fdctrl);
+ break;
+ case FD_REG_DOR:
+ retval = fdctrl_read_dor(fdctrl);
+ break;
+ case FD_REG_TDR:
+ retval = fdctrl_read_tape(fdctrl);
+ break;
+ case FD_REG_MSR:
+ retval = fdctrl_read_main_status(fdctrl);
+ break;
+ case FD_REG_FIFO:
+ retval = fdctrl_read_data(fdctrl);
+ break;
+ case FD_REG_DIR:
+ retval = fdctrl_read_dir(fdctrl);
+ break;
+ default:
+ retval = (uint32_t)(-1);
+ break;
+ }
+ FLOPPY_DPRINTF("read reg%d: 0x%02x\n", reg & 7, retval);
+
+ return retval;
+}
+
+static void fdctrl_write (void *opaque, uint32_t reg, uint32_t value)
+{
+ FDCtrl *fdctrl = opaque;
+
+ FLOPPY_DPRINTF("write reg%d: 0x%02x\n", reg & 7, value);
+
+ reg &= 7;
+ switch (reg) {
+ case FD_REG_DOR:
+ fdctrl_write_dor(fdctrl, value);
+ break;
+ case FD_REG_TDR:
+ fdctrl_write_tape(fdctrl, value);
+ break;
+ case FD_REG_DSR:
+ fdctrl_write_rate(fdctrl, value);
+ break;
+ case FD_REG_FIFO:
+ fdctrl_write_data(fdctrl, value);
+ break;
+ case FD_REG_CCR:
+ fdctrl_write_ccr(fdctrl, value);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t fdctrl_read_mem (void *opaque, hwaddr reg,
+ unsigned ize)
+{
+ return fdctrl_read(opaque, (uint32_t)reg);
+}
+
+static void fdctrl_write_mem (void *opaque, hwaddr reg,
+ uint64_t value, unsigned size)
+{
+ fdctrl_write(opaque, (uint32_t)reg, value);
+}
+
+static const MemoryRegionOps fdctrl_mem_ops = {
+ .read = fdctrl_read_mem,
+ .write = fdctrl_write_mem,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps fdctrl_mem_strict_ops = {
+ .read = fdctrl_read_mem,
+ .write = fdctrl_write_mem,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static bool fdrive_media_changed_needed(void *opaque)
+{
+ FDrive *drive = opaque;
+
+ return (drive->blk != NULL && drive->media_changed != 1);
+}
+
+static const VMStateDescription vmstate_fdrive_media_changed = {
+ .name = "fdrive/media_changed",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = fdrive_media_changed_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(media_changed, FDrive),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool fdrive_media_rate_needed(void *opaque)
+{
+ FDrive *drive = opaque;
+
+ return drive->fdctrl->check_media_rate;
+}
+
+static const VMStateDescription vmstate_fdrive_media_rate = {
+ .name = "fdrive/media_rate",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = fdrive_media_rate_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(media_rate, FDrive),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool fdrive_perpendicular_needed(void *opaque)
+{
+ FDrive *drive = opaque;
+
+ return drive->perpendicular != 0;
+}
+
+static const VMStateDescription vmstate_fdrive_perpendicular = {
+ .name = "fdrive/perpendicular",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = fdrive_perpendicular_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(perpendicular, FDrive),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int fdrive_post_load(void *opaque, int version_id)
+{
+ fd_revalidate(opaque);
+ return 0;
+}
+
+static const VMStateDescription vmstate_fdrive = {
+ .name = "fdrive",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = fdrive_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(head, FDrive),
+ VMSTATE_UINT8(track, FDrive),
+ VMSTATE_UINT8(sect, FDrive),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_fdrive_media_changed,
+ &vmstate_fdrive_media_rate,
+ &vmstate_fdrive_perpendicular,
+ NULL
+ }
+};
+
+/*
+ * Reconstructs the phase from register values according to the logic that was
+ * implemented in qemu 2.3. This is the default value that is used if the phase
+ * subsection is not present on migration.
+ *
+ * Don't change this function to reflect newer qemu versions, it is part of
+ * the migration ABI.
+ */
+static int reconstruct_phase(FDCtrl *fdctrl)
+{
+ if (fdctrl->msr & FD_MSR_NONDMA) {
+ return FD_PHASE_EXECUTION;
+ } else if ((fdctrl->msr & FD_MSR_RQM) == 0) {
+ /* qemu 2.3 disabled RQM only during DMA transfers */
+ return FD_PHASE_EXECUTION;
+ } else if (fdctrl->msr & FD_MSR_DIO) {
+ return FD_PHASE_RESULT;
+ } else {
+ return FD_PHASE_COMMAND;
+ }
+}
+
+static void fdc_pre_save(void *opaque)
+{
+ FDCtrl *s = opaque;
+
+ s->dor_vmstate = s->dor | GET_CUR_DRV(s);
+}
+
+static int fdc_pre_load(void *opaque)
+{
+ FDCtrl *s = opaque;
+ s->phase = FD_PHASE_RECONSTRUCT;
+ return 0;
+}
+
+static int fdc_post_load(void *opaque, int version_id)
+{
+ FDCtrl *s = opaque;
+
+ SET_CUR_DRV(s, s->dor_vmstate & FD_DOR_SELMASK);
+ s->dor = s->dor_vmstate & ~FD_DOR_SELMASK;
+
+ if (s->phase == FD_PHASE_RECONSTRUCT) {
+ s->phase = reconstruct_phase(s);
+ }
+
+ return 0;
+}
+
+static bool fdc_reset_sensei_needed(void *opaque)
+{
+ FDCtrl *s = opaque;
+
+ return s->reset_sensei != 0;
+}
+
+static const VMStateDescription vmstate_fdc_reset_sensei = {
+ .name = "fdc/reset_sensei",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = fdc_reset_sensei_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(reset_sensei, FDCtrl),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool fdc_result_timer_needed(void *opaque)
+{
+ FDCtrl *s = opaque;
+
+ return timer_pending(s->result_timer);
+}
+
+static const VMStateDescription vmstate_fdc_result_timer = {
+ .name = "fdc/result_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = fdc_result_timer_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(result_timer, FDCtrl),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool fdc_phase_needed(void *opaque)
+{
+ FDCtrl *fdctrl = opaque;
+
+ return reconstruct_phase(fdctrl) != fdctrl->phase;
+}
+
+static const VMStateDescription vmstate_fdc_phase = {
+ .name = "fdc/phase",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = fdc_phase_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(phase, FDCtrl),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_fdc = {
+ .name = "fdc",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .pre_save = fdc_pre_save,
+ .pre_load = fdc_pre_load,
+ .post_load = fdc_post_load,
+ .fields = (VMStateField[]) {
+ /* Controller State */
+ VMSTATE_UINT8(sra, FDCtrl),
+ VMSTATE_UINT8(srb, FDCtrl),
+ VMSTATE_UINT8(dor_vmstate, FDCtrl),
+ VMSTATE_UINT8(tdr, FDCtrl),
+ VMSTATE_UINT8(dsr, FDCtrl),
+ VMSTATE_UINT8(msr, FDCtrl),
+ VMSTATE_UINT8(status0, FDCtrl),
+ VMSTATE_UINT8(status1, FDCtrl),
+ VMSTATE_UINT8(status2, FDCtrl),
+ /* Command FIFO */
+ VMSTATE_VARRAY_INT32(fifo, FDCtrl, fifo_size, 0, vmstate_info_uint8,
+ uint8_t),
+ VMSTATE_UINT32(data_pos, FDCtrl),
+ VMSTATE_UINT32(data_len, FDCtrl),
+ VMSTATE_UINT8(data_state, FDCtrl),
+ VMSTATE_UINT8(data_dir, FDCtrl),
+ VMSTATE_UINT8(eot, FDCtrl),
+ /* States kept only to be returned back */
+ VMSTATE_UINT8(timer0, FDCtrl),
+ VMSTATE_UINT8(timer1, FDCtrl),
+ VMSTATE_UINT8(precomp_trk, FDCtrl),
+ VMSTATE_UINT8(config, FDCtrl),
+ VMSTATE_UINT8(lock, FDCtrl),
+ VMSTATE_UINT8(pwrd, FDCtrl),
+ VMSTATE_UINT8_EQUAL(num_floppies, FDCtrl),
+ VMSTATE_STRUCT_ARRAY(drives, FDCtrl, MAX_FD, 1,
+ vmstate_fdrive, FDrive),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_fdc_reset_sensei,
+ &vmstate_fdc_result_timer,
+ &vmstate_fdc_phase,
+ NULL
+ }
+};
+
+static void fdctrl_external_reset_sysbus(DeviceState *d)
+{
+ FDCtrlSysBus *sys = SYSBUS_FDC(d);
+ FDCtrl *s = &sys->state;
+
+ fdctrl_reset(s, 0);
+}
+
+static void fdctrl_external_reset_isa(DeviceState *d)
+{
+ FDCtrlISABus *isa = ISA_FDC(d);
+ FDCtrl *s = &isa->state;
+
+ fdctrl_reset(s, 0);
+}
+
+static void fdctrl_handle_tc(void *opaque, int irq, int level)
+{
+ //FDCtrl *s = opaque;
+
+ if (level) {
+ // XXX
+ FLOPPY_DPRINTF("TC pulsed\n");
+ }
+}
+
+/* Change IRQ state */
+static void fdctrl_reset_irq(FDCtrl *fdctrl)
+{
+ fdctrl->status0 = 0;
+ if (!(fdctrl->sra & FD_SRA_INTPEND))
+ return;
+ FLOPPY_DPRINTF("Reset interrupt\n");
+ qemu_set_irq(fdctrl->irq, 0);
+ fdctrl->sra &= ~FD_SRA_INTPEND;
+}
+
+static void fdctrl_raise_irq(FDCtrl *fdctrl)
+{
+ if (!(fdctrl->sra & FD_SRA_INTPEND)) {
+ qemu_set_irq(fdctrl->irq, 1);
+ fdctrl->sra |= FD_SRA_INTPEND;
+ }
+
+ fdctrl->reset_sensei = 0;
+ FLOPPY_DPRINTF("Set interrupt status to 0x%02x\n", fdctrl->status0);
+}
+
+/* Reset controller */
+static void fdctrl_reset(FDCtrl *fdctrl, int do_irq)
+{
+ int i;
+
+ FLOPPY_DPRINTF("reset controller\n");
+ fdctrl_reset_irq(fdctrl);
+ /* Initialise controller */
+ fdctrl->sra = 0;
+ fdctrl->srb = 0xc0;
+ if (!fdctrl->drives[1].blk) {
+ fdctrl->sra |= FD_SRA_nDRV2;
+ }
+ fdctrl->cur_drv = 0;
+ fdctrl->dor = FD_DOR_nRESET;
+ fdctrl->dor |= (fdctrl->dma_chann != -1) ? FD_DOR_DMAEN : 0;
+ fdctrl->msr = FD_MSR_RQM;
+ fdctrl->reset_sensei = 0;
+ timer_del(fdctrl->result_timer);
+ /* FIFO state */
+ fdctrl->data_pos = 0;
+ fdctrl->data_len = 0;
+ fdctrl->data_state = 0;
+ fdctrl->data_dir = FD_DIR_WRITE;
+ for (i = 0; i < MAX_FD; i++)
+ fd_recalibrate(&fdctrl->drives[i]);
+ fdctrl_to_command_phase(fdctrl);
+ if (do_irq) {
+ fdctrl->status0 |= FD_SR0_RDYCHG;
+ fdctrl_raise_irq(fdctrl);
+ fdctrl->reset_sensei = FD_RESET_SENSEI_COUNT;
+ }
+}
+
+static inline FDrive *drv0(FDCtrl *fdctrl)
+{
+ return &fdctrl->drives[(fdctrl->tdr & FD_TDR_BOOTSEL) >> 2];
+}
+
+static inline FDrive *drv1(FDCtrl *fdctrl)
+{
+ if ((fdctrl->tdr & FD_TDR_BOOTSEL) < (1 << 2))
+ return &fdctrl->drives[1];
+ else
+ return &fdctrl->drives[0];
+}
+
+#if MAX_FD == 4
+static inline FDrive *drv2(FDCtrl *fdctrl)
+{
+ if ((fdctrl->tdr & FD_TDR_BOOTSEL) < (2 << 2))
+ return &fdctrl->drives[2];
+ else
+ return &fdctrl->drives[1];
+}
+
+static inline FDrive *drv3(FDCtrl *fdctrl)
+{
+ if ((fdctrl->tdr & FD_TDR_BOOTSEL) < (3 << 2))
+ return &fdctrl->drives[3];
+ else
+ return &fdctrl->drives[2];
+}
+#endif
+
+static FDrive *get_cur_drv(FDCtrl *fdctrl)
+{
+ switch (fdctrl->cur_drv) {
+ case 0: return drv0(fdctrl);
+ case 1: return drv1(fdctrl);
+#if MAX_FD == 4
+ case 2: return drv2(fdctrl);
+ case 3: return drv3(fdctrl);
+#endif
+ default: return NULL;
+ }
+}
+
+/* Status A register : 0x00 (read-only) */
+static uint32_t fdctrl_read_statusA(FDCtrl *fdctrl)
+{
+ uint32_t retval = fdctrl->sra;
+
+ FLOPPY_DPRINTF("status register A: 0x%02x\n", retval);
+
+ return retval;
+}
+
+/* Status B register : 0x01 (read-only) */
+static uint32_t fdctrl_read_statusB(FDCtrl *fdctrl)
+{
+ uint32_t retval = fdctrl->srb;
+
+ FLOPPY_DPRINTF("status register B: 0x%02x\n", retval);
+
+ return retval;
+}
+
+/* Digital output register : 0x02 */
+static uint32_t fdctrl_read_dor(FDCtrl *fdctrl)
+{
+ uint32_t retval = fdctrl->dor;
+
+ /* Selected drive */
+ retval |= fdctrl->cur_drv;
+ FLOPPY_DPRINTF("digital output register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_write_dor(FDCtrl *fdctrl, uint32_t value)
+{
+ FLOPPY_DPRINTF("digital output register set to 0x%02x\n", value);
+
+ /* Motors */
+ if (value & FD_DOR_MOTEN0)
+ fdctrl->srb |= FD_SRB_MTR0;
+ else
+ fdctrl->srb &= ~FD_SRB_MTR0;
+ if (value & FD_DOR_MOTEN1)
+ fdctrl->srb |= FD_SRB_MTR1;
+ else
+ fdctrl->srb &= ~FD_SRB_MTR1;
+
+ /* Drive */
+ if (value & 1)
+ fdctrl->srb |= FD_SRB_DR0;
+ else
+ fdctrl->srb &= ~FD_SRB_DR0;
+
+ /* Reset */
+ if (!(value & FD_DOR_nRESET)) {
+ if (fdctrl->dor & FD_DOR_nRESET) {
+ FLOPPY_DPRINTF("controller enter RESET state\n");
+ }
+ } else {
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("controller out of RESET state\n");
+ fdctrl_reset(fdctrl, 1);
+ fdctrl->dsr &= ~FD_DSR_PWRDOWN;
+ }
+ }
+ /* Selected drive */
+ fdctrl->cur_drv = value & FD_DOR_SELMASK;
+
+ fdctrl->dor = value;
+}
+
+/* Tape drive register : 0x03 */
+static uint32_t fdctrl_read_tape(FDCtrl *fdctrl)
+{
+ uint32_t retval = fdctrl->tdr;
+
+ FLOPPY_DPRINTF("tape drive register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_write_tape(FDCtrl *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ FLOPPY_DPRINTF("tape drive register set to 0x%02x\n", value);
+ /* Disk boot selection indicator */
+ fdctrl->tdr = value & FD_TDR_BOOTSEL;
+ /* Tape indicators: never allow */
+}
+
+/* Main status register : 0x04 (read) */
+static uint32_t fdctrl_read_main_status(FDCtrl *fdctrl)
+{
+ uint32_t retval = fdctrl->msr;
+
+ fdctrl->dsr &= ~FD_DSR_PWRDOWN;
+ fdctrl->dor |= FD_DOR_nRESET;
+
+ FLOPPY_DPRINTF("main status register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+/* Data select rate register : 0x04 (write) */
+static void fdctrl_write_rate(FDCtrl *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ FLOPPY_DPRINTF("select rate register set to 0x%02x\n", value);
+ /* Reset: autoclear */
+ if (value & FD_DSR_SWRESET) {
+ fdctrl->dor &= ~FD_DOR_nRESET;
+ fdctrl_reset(fdctrl, 1);
+ fdctrl->dor |= FD_DOR_nRESET;
+ }
+ if (value & FD_DSR_PWRDOWN) {
+ fdctrl_reset(fdctrl, 1);
+ }
+ fdctrl->dsr = value;
+}
+
+/* Configuration control register: 0x07 (write) */
+static void fdctrl_write_ccr(FDCtrl *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ FLOPPY_DPRINTF("configuration control register set to 0x%02x\n", value);
+
+ /* Only the rate selection bits used in AT mode, and we
+ * store those in the DSR.
+ */
+ fdctrl->dsr = (fdctrl->dsr & ~FD_DSR_DRATEMASK) |
+ (value & FD_DSR_DRATEMASK);
+}
+
+static int fdctrl_media_changed(FDrive *drv)
+{
+ return drv->media_changed;
+}
+
+/* Digital input register : 0x07 (read-only) */
+static uint32_t fdctrl_read_dir(FDCtrl *fdctrl)
+{
+ uint32_t retval = 0;
+
+ if (fdctrl_media_changed(get_cur_drv(fdctrl))) {
+ retval |= FD_DIR_DSKCHG;
+ }
+ if (retval != 0) {
+ FLOPPY_DPRINTF("Floppy digital input register: 0x%02x\n", retval);
+ }
+
+ return retval;
+}
+
+/* Clear the FIFO and update the state for receiving the next command */
+static void fdctrl_to_command_phase(FDCtrl *fdctrl)
+{
+ fdctrl->phase = FD_PHASE_COMMAND;
+ fdctrl->data_dir = FD_DIR_WRITE;
+ fdctrl->data_pos = 0;
+ fdctrl->data_len = 1; /* Accept command byte, adjust for params later */
+ fdctrl->msr &= ~(FD_MSR_CMDBUSY | FD_MSR_DIO);
+ fdctrl->msr |= FD_MSR_RQM;
+}
+
+/* Update the state to allow the guest to read out the command status.
+ * @fifo_len is the number of result bytes to be read out. */
+static void fdctrl_to_result_phase(FDCtrl *fdctrl, int fifo_len)
+{
+ fdctrl->phase = FD_PHASE_RESULT;
+ fdctrl->data_dir = FD_DIR_READ;
+ fdctrl->data_len = fifo_len;
+ fdctrl->data_pos = 0;
+ fdctrl->msr |= FD_MSR_CMDBUSY | FD_MSR_RQM | FD_MSR_DIO;
+}
+
+/* Set an error: unimplemented/unknown command */
+static void fdctrl_unimplemented(FDCtrl *fdctrl, int direction)
+{
+ qemu_log_mask(LOG_UNIMP, "fdc: unimplemented command 0x%02x\n",
+ fdctrl->fifo[0]);
+ fdctrl->fifo[0] = FD_SR0_INVCMD;
+ fdctrl_to_result_phase(fdctrl, 1);
+}
+
+/* Seek to next sector
+ * returns 0 when end of track reached (for DBL_SIDES on head 1)
+ * otherwise returns 1
+ */
+static int fdctrl_seek_to_next_sect(FDCtrl *fdctrl, FDrive *cur_drv)
+{
+ FLOPPY_DPRINTF("seek to next sector (%d %02x %02x => %d)\n",
+ cur_drv->head, cur_drv->track, cur_drv->sect,
+ fd_sector(cur_drv));
+ /* XXX: cur_drv->sect >= cur_drv->last_sect should be an
+ error in fact */
+ uint8_t new_head = cur_drv->head;
+ uint8_t new_track = cur_drv->track;
+ uint8_t new_sect = cur_drv->sect;
+
+ int ret = 1;
+
+ if (new_sect >= cur_drv->last_sect ||
+ new_sect == fdctrl->eot) {
+ new_sect = 1;
+ if (FD_MULTI_TRACK(fdctrl->data_state)) {
+ if (new_head == 0 &&
+ (cur_drv->flags & FDISK_DBL_SIDES) != 0) {
+ new_head = 1;
+ } else {
+ new_head = 0;
+ new_track++;
+ fdctrl->status0 |= FD_SR0_SEEK;
+ if ((cur_drv->flags & FDISK_DBL_SIDES) == 0) {
+ ret = 0;
+ }
+ }
+ } else {
+ fdctrl->status0 |= FD_SR0_SEEK;
+ new_track++;
+ ret = 0;
+ }
+ if (ret == 1) {
+ FLOPPY_DPRINTF("seek to next track (%d %02x %02x => %d)\n",
+ new_head, new_track, new_sect, fd_sector(cur_drv));
+ }
+ } else {
+ new_sect++;
+ }
+ fd_seek(cur_drv, new_head, new_track, new_sect, 1);
+ return ret;
+}
+
+/* Callback for transfer end (stop or abort) */
+static void fdctrl_stop_transfer(FDCtrl *fdctrl, uint8_t status0,
+ uint8_t status1, uint8_t status2)
+{
+ FDrive *cur_drv;
+ cur_drv = get_cur_drv(fdctrl);
+
+ fdctrl->status0 &= ~(FD_SR0_DS0 | FD_SR0_DS1 | FD_SR0_HEAD);
+ fdctrl->status0 |= GET_CUR_DRV(fdctrl);
+ if (cur_drv->head) {
+ fdctrl->status0 |= FD_SR0_HEAD;
+ }
+ fdctrl->status0 |= status0;
+
+ FLOPPY_DPRINTF("transfer status: %02x %02x %02x (%02x)\n",
+ status0, status1, status2, fdctrl->status0);
+ fdctrl->fifo[0] = fdctrl->status0;
+ fdctrl->fifo[1] = status1;
+ fdctrl->fifo[2] = status2;
+ fdctrl->fifo[3] = cur_drv->track;
+ fdctrl->fifo[4] = cur_drv->head;
+ fdctrl->fifo[5] = cur_drv->sect;
+ fdctrl->fifo[6] = FD_SECTOR_SC;
+ fdctrl->data_dir = FD_DIR_READ;
+ if (!(fdctrl->msr & FD_MSR_NONDMA)) {
+ DMA_release_DREQ(fdctrl->dma_chann);
+ }
+ fdctrl->msr |= FD_MSR_RQM | FD_MSR_DIO;
+ fdctrl->msr &= ~FD_MSR_NONDMA;
+
+ fdctrl_to_result_phase(fdctrl, 7);
+ fdctrl_raise_irq(fdctrl);
+}
+
+/* Prepare a data transfer (either DMA or FIFO) */
+static void fdctrl_start_transfer(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv;
+ uint8_t kh, kt, ks;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ kt = fdctrl->fifo[2];
+ kh = fdctrl->fifo[3];
+ ks = fdctrl->fifo[4];
+ FLOPPY_DPRINTF("Start transfer at %d %d %02x %02x (%d)\n",
+ GET_CUR_DRV(fdctrl), kh, kt, ks,
+ fd_sector_calc(kh, kt, ks, cur_drv->last_sect,
+ NUM_SIDES(cur_drv)));
+ switch (fd_seek(cur_drv, kh, kt, ks, fdctrl->config & FD_CONFIG_EIS)) {
+ case 2:
+ /* sect too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 3:
+ /* track too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_EC, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 4:
+ /* No seek enabled */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 1:
+ fdctrl->status0 |= FD_SR0_SEEK;
+ break;
+ default:
+ break;
+ }
+
+ /* Check the data rate. If the programmed data rate does not match
+ * the currently inserted medium, the operation has to fail. */
+ if (fdctrl->check_media_rate &&
+ (fdctrl->dsr & FD_DSR_DRATEMASK) != cur_drv->media_rate) {
+ FLOPPY_DPRINTF("data rate mismatch (fdc=%d, media=%d)\n",
+ fdctrl->dsr & FD_DSR_DRATEMASK, cur_drv->media_rate);
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ }
+
+ /* Set the FIFO state */
+ fdctrl->data_dir = direction;
+ fdctrl->data_pos = 0;
+ assert(fdctrl->msr & FD_MSR_CMDBUSY);
+ if (fdctrl->fifo[0] & 0x80)
+ fdctrl->data_state |= FD_STATE_MULTI;
+ else
+ fdctrl->data_state &= ~FD_STATE_MULTI;
+ if (fdctrl->fifo[5] == 0) {
+ fdctrl->data_len = fdctrl->fifo[8];
+ } else {
+ int tmp;
+ fdctrl->data_len = 128 << (fdctrl->fifo[5] > 7 ? 7 : fdctrl->fifo[5]);
+ tmp = (fdctrl->fifo[6] - ks + 1);
+ if (fdctrl->fifo[0] & 0x80)
+ tmp += fdctrl->fifo[6];
+ fdctrl->data_len *= tmp;
+ }
+ fdctrl->eot = fdctrl->fifo[6];
+ if (fdctrl->dor & FD_DOR_DMAEN) {
+ int dma_mode;
+ /* 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;
+ 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)) {
+ /* 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);
+ } else {
+ /* Start transfer */
+ fdctrl_transfer_handler(fdctrl, fdctrl->dma_chann, 0,
+ fdctrl->data_len);
+ }
+ return;
+ } else {
+ FLOPPY_DPRINTF("bad dma_mode=%d direction=%d\n", dma_mode,
+ direction);
+ }
+ }
+ FLOPPY_DPRINTF("start non-DMA transfer\n");
+ fdctrl->msr |= FD_MSR_NONDMA | FD_MSR_RQM;
+ if (direction != FD_DIR_WRITE)
+ fdctrl->msr |= FD_MSR_DIO;
+ /* IO based transfer: calculate len */
+ fdctrl_raise_irq(fdctrl);
+}
+
+/* Prepare a transfer of deleted data */
+static void fdctrl_start_transfer_del(FDCtrl *fdctrl, int direction)
+{
+ qemu_log_mask(LOG_UNIMP, "fdctrl_start_transfer_del() unimplemented\n");
+
+ /* We don't handle deleted data,
+ * so we don't return *ANYTHING*
+ */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+}
+
+/* handlers for DMA transfers */
+static int fdctrl_transfer_handler (void *opaque, int nchan,
+ int dma_pos, int dma_len)
+{
+ FDCtrl *fdctrl;
+ FDrive *cur_drv;
+ int len, start_pos, rel_pos;
+ uint8_t status0 = 0x00, status1 = 0x00, status2 = 0x00;
+
+ fdctrl = opaque;
+ if (fdctrl->msr & FD_MSR_RQM) {
+ FLOPPY_DPRINTF("Not in DMA transfer mode !\n");
+ return 0;
+ }
+ 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)
+ status2 = FD_SR2_SNS;
+ if (dma_len > fdctrl->data_len)
+ dma_len = fdctrl->data_len;
+ if (cur_drv->blk == NULL) {
+ if (fdctrl->data_dir == FD_DIR_WRITE)
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ else
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ len = 0;
+ goto transfer_error;
+ }
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ for (start_pos = fdctrl->data_pos; fdctrl->data_pos < dma_len;) {
+ len = dma_len - fdctrl->data_pos;
+ if (len + rel_pos > FD_SECTOR_LEN)
+ len = FD_SECTOR_LEN - rel_pos;
+ FLOPPY_DPRINTF("copy %d bytes (%d %d %d) %d pos %d %02x "
+ "(%d-0x%08x 0x%08x)\n", len, dma_len, fdctrl->data_pos,
+ fdctrl->data_len, GET_CUR_DRV(fdctrl), cur_drv->head,
+ cur_drv->track, cur_drv->sect, fd_sector(cur_drv),
+ fd_sector(cur_drv) * FD_SECTOR_LEN);
+ 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) {
+ FLOPPY_DPRINTF("Floppy: error getting sector %d\n",
+ fd_sector(cur_drv));
+ /* Sure, image size is too small... */
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+ }
+ }
+ switch (fdctrl->data_dir) {
+ case FD_DIR_READ:
+ /* READ commands */
+ DMA_write_memory (nchan, fdctrl->fifo + rel_pos,
+ fdctrl->data_pos, len);
+ break;
+ case FD_DIR_WRITE:
+ /* WRITE commands */
+ if (cur_drv->ro) {
+ /* Handle readonly medium early, no need to do DMA, touch the
+ * LED or attempt any writes. A real floppy doesn't attempt
+ * to write to readonly media either. */
+ fdctrl_stop_transfer(fdctrl,
+ FD_SR0_ABNTERM | FD_SR0_SEEK, FD_SR1_NW,
+ 0x00);
+ 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) {
+ FLOPPY_DPRINTF("error writing sector %d\n",
+ fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ goto transfer_error;
+ }
+ break;
+ case FD_DIR_VERIFY:
+ /* VERIFY commands */
+ break;
+ default:
+ /* SCAN commands */
+ {
+ uint8_t tmpbuf[FD_SECTOR_LEN];
+ int ret;
+ DMA_read_memory (nchan, tmpbuf, fdctrl->data_pos, len);
+ ret = memcmp(tmpbuf, fdctrl->fifo + rel_pos, len);
+ if (ret == 0) {
+ status2 = FD_SR2_SEH;
+ goto end_transfer;
+ }
+ if ((ret < 0 && fdctrl->data_dir == FD_DIR_SCANL) ||
+ (ret > 0 && fdctrl->data_dir == FD_DIR_SCANH)) {
+ status2 = 0x00;
+ goto end_transfer;
+ }
+ }
+ break;
+ }
+ fdctrl->data_pos += len;
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ if (rel_pos == 0) {
+ /* Seek to next sector */
+ if (!fdctrl_seek_to_next_sect(fdctrl, cur_drv))
+ break;
+ }
+ }
+ end_transfer:
+ len = fdctrl->data_pos - start_pos;
+ FLOPPY_DPRINTF("end transfer %d %d %d\n",
+ fdctrl->data_pos, len, fdctrl->data_len);
+ if (fdctrl->data_dir == FD_DIR_SCANE ||
+ fdctrl->data_dir == FD_DIR_SCANL ||
+ fdctrl->data_dir == FD_DIR_SCANH)
+ status2 = FD_SR2_SEH;
+ fdctrl->data_len -= len;
+ fdctrl_stop_transfer(fdctrl, status0, status1, status2);
+ transfer_error:
+
+ return len;
+}
+
+/* Data register : 0x05 */
+static uint32_t fdctrl_read_data(FDCtrl *fdctrl)
+{
+ FDrive *cur_drv;
+ uint32_t retval = 0;
+ uint32_t pos;
+
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl->dsr &= ~FD_DSR_PWRDOWN;
+ if (!(fdctrl->msr & FD_MSR_RQM) || !(fdctrl->msr & FD_MSR_DIO)) {
+ FLOPPY_DPRINTF("error: controller not ready for reading\n");
+ return 0;
+ }
+
+ /* If data_len spans multiple sectors, the current position in the FIFO
+ * wraps around while fdctrl->data_pos is the real position in the whole
+ * request. */
+ pos = fdctrl->data_pos;
+ pos %= FD_SECTOR_LEN;
+
+ switch (fdctrl->phase) {
+ case FD_PHASE_EXECUTION:
+ assert(fdctrl->msr & FD_MSR_NONDMA);
+ if (pos == 0) {
+ if (fdctrl->data_pos != 0)
+ if (!fdctrl_seek_to_next_sect(fdctrl, cur_drv)) {
+ FLOPPY_DPRINTF("error seeking to next sector %d\n",
+ fd_sector(cur_drv));
+ return 0;
+ }
+ if (blk_read(cur_drv->blk, fd_sector(cur_drv), fdctrl->fifo, 1)
+ < 0) {
+ FLOPPY_DPRINTF("error getting sector %d\n",
+ fd_sector(cur_drv));
+ /* Sure, image size is too small... */
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+ }
+ }
+
+ if (++fdctrl->data_pos == fdctrl->data_len) {
+ fdctrl->msr &= ~FD_MSR_RQM;
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+ }
+ break;
+
+ case FD_PHASE_RESULT:
+ assert(!(fdctrl->msr & FD_MSR_NONDMA));
+ if (++fdctrl->data_pos == fdctrl->data_len) {
+ fdctrl->msr &= ~FD_MSR_RQM;
+ fdctrl_to_command_phase(fdctrl);
+ fdctrl_reset_irq(fdctrl);
+ }
+ break;
+
+ case FD_PHASE_COMMAND:
+ default:
+ abort();
+ }
+
+ retval = fdctrl->fifo[pos];
+ FLOPPY_DPRINTF("data register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_format_sector(FDCtrl *fdctrl)
+{
+ FDrive *cur_drv;
+ uint8_t kh, kt, ks;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ kt = fdctrl->fifo[6];
+ kh = fdctrl->fifo[7];
+ ks = fdctrl->fifo[8];
+ FLOPPY_DPRINTF("format sector at %d %d %02x %02x (%d)\n",
+ GET_CUR_DRV(fdctrl), kh, kt, ks,
+ fd_sector_calc(kh, kt, ks, cur_drv->last_sect,
+ NUM_SIDES(cur_drv)));
+ switch (fd_seek(cur_drv, kh, kt, ks, fdctrl->config & FD_CONFIG_EIS)) {
+ case 2:
+ /* sect too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 3:
+ /* track too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_EC, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 4:
+ /* No seek enabled */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 1:
+ fdctrl->status0 |= FD_SR0_SEEK;
+ break;
+ default:
+ break;
+ }
+ 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) {
+ FLOPPY_DPRINTF("error formatting sector %d\n", fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ } else {
+ if (cur_drv->sect == cur_drv->last_sect) {
+ fdctrl->data_state &= ~FD_STATE_FORMAT;
+ /* Last sector done */
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+ } else {
+ /* More to do */
+ fdctrl->data_pos = 0;
+ fdctrl->data_len = 4;
+ }
+ }
+}
+
+static void fdctrl_handle_lock(FDCtrl *fdctrl, int direction)
+{
+ fdctrl->lock = (fdctrl->fifo[0] & 0x80) ? 1 : 0;
+ fdctrl->fifo[0] = fdctrl->lock << 4;
+ fdctrl_to_result_phase(fdctrl, 1);
+}
+
+static void fdctrl_handle_dumpreg(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv = get_cur_drv(fdctrl);
+
+ /* Drives position */
+ fdctrl->fifo[0] = drv0(fdctrl)->track;
+ fdctrl->fifo[1] = drv1(fdctrl)->track;
+#if MAX_FD == 4
+ fdctrl->fifo[2] = drv2(fdctrl)->track;
+ fdctrl->fifo[3] = drv3(fdctrl)->track;
+#else
+ fdctrl->fifo[2] = 0;
+ fdctrl->fifo[3] = 0;
+#endif
+ /* timers */
+ fdctrl->fifo[4] = fdctrl->timer0;
+ fdctrl->fifo[5] = (fdctrl->timer1 << 1) | (fdctrl->dor & FD_DOR_DMAEN ? 1 : 0);
+ fdctrl->fifo[6] = cur_drv->last_sect;
+ fdctrl->fifo[7] = (fdctrl->lock << 7) |
+ (cur_drv->perpendicular << 2);
+ fdctrl->fifo[8] = fdctrl->config;
+ fdctrl->fifo[9] = fdctrl->precomp_trk;
+ fdctrl_to_result_phase(fdctrl, 10);
+}
+
+static void fdctrl_handle_version(FDCtrl *fdctrl, int direction)
+{
+ /* Controller's version */
+ fdctrl->fifo[0] = fdctrl->version;
+ fdctrl_to_result_phase(fdctrl, 1);
+}
+
+static void fdctrl_handle_partid(FDCtrl *fdctrl, int direction)
+{
+ fdctrl->fifo[0] = 0x41; /* Stepping 1 */
+ fdctrl_to_result_phase(fdctrl, 1);
+}
+
+static void fdctrl_handle_restore(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv = get_cur_drv(fdctrl);
+
+ /* Drives position */
+ drv0(fdctrl)->track = fdctrl->fifo[3];
+ drv1(fdctrl)->track = fdctrl->fifo[4];
+#if MAX_FD == 4
+ drv2(fdctrl)->track = fdctrl->fifo[5];
+ drv3(fdctrl)->track = fdctrl->fifo[6];
+#endif
+ /* timers */
+ fdctrl->timer0 = fdctrl->fifo[7];
+ fdctrl->timer1 = fdctrl->fifo[8];
+ cur_drv->last_sect = fdctrl->fifo[9];
+ fdctrl->lock = fdctrl->fifo[10] >> 7;
+ cur_drv->perpendicular = (fdctrl->fifo[10] >> 2) & 0xF;
+ fdctrl->config = fdctrl->fifo[11];
+ fdctrl->precomp_trk = fdctrl->fifo[12];
+ fdctrl->pwrd = fdctrl->fifo[13];
+ fdctrl_to_command_phase(fdctrl);
+}
+
+static void fdctrl_handle_save(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv = get_cur_drv(fdctrl);
+
+ fdctrl->fifo[0] = 0;
+ fdctrl->fifo[1] = 0;
+ /* Drives position */
+ fdctrl->fifo[2] = drv0(fdctrl)->track;
+ fdctrl->fifo[3] = drv1(fdctrl)->track;
+#if MAX_FD == 4
+ fdctrl->fifo[4] = drv2(fdctrl)->track;
+ fdctrl->fifo[5] = drv3(fdctrl)->track;
+#else
+ fdctrl->fifo[4] = 0;
+ fdctrl->fifo[5] = 0;
+#endif
+ /* timers */
+ fdctrl->fifo[6] = fdctrl->timer0;
+ fdctrl->fifo[7] = fdctrl->timer1;
+ fdctrl->fifo[8] = cur_drv->last_sect;
+ fdctrl->fifo[9] = (fdctrl->lock << 7) |
+ (cur_drv->perpendicular << 2);
+ fdctrl->fifo[10] = fdctrl->config;
+ fdctrl->fifo[11] = fdctrl->precomp_trk;
+ fdctrl->fifo[12] = fdctrl->pwrd;
+ fdctrl->fifo[13] = 0;
+ fdctrl->fifo[14] = 0;
+ fdctrl_to_result_phase(fdctrl, 15);
+}
+
+static void fdctrl_handle_readid(FDCtrl *fdctrl, int direction)
+{
+ 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));
+}
+
+static void fdctrl_handle_format_track(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl->data_state |= FD_STATE_FORMAT;
+ if (fdctrl->fifo[0] & 0x80)
+ fdctrl->data_state |= FD_STATE_MULTI;
+ else
+ fdctrl->data_state &= ~FD_STATE_MULTI;
+ cur_drv->bps =
+ fdctrl->fifo[2] > 7 ? 16384 : 128 << fdctrl->fifo[2];
+#if 0
+ cur_drv->last_sect =
+ cur_drv->flags & FDISK_DBL_SIDES ? fdctrl->fifo[3] :
+ fdctrl->fifo[3] / 2;
+#else
+ cur_drv->last_sect = fdctrl->fifo[3];
+#endif
+ /* TODO: implement format using DMA expected by the Bochs BIOS
+ * and Linux fdformat (read 3 bytes per sector via DMA and fill
+ * the sector with the specified fill byte
+ */
+ fdctrl->data_state &= ~FD_STATE_FORMAT;
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+}
+
+static void fdctrl_handle_specify(FDCtrl *fdctrl, int direction)
+{
+ fdctrl->timer0 = (fdctrl->fifo[1] >> 4) & 0xF;
+ fdctrl->timer1 = fdctrl->fifo[2] >> 1;
+ if (fdctrl->fifo[2] & 1)
+ fdctrl->dor &= ~FD_DOR_DMAEN;
+ else
+ fdctrl->dor |= FD_DOR_DMAEN;
+ /* No result back */
+ fdctrl_to_command_phase(fdctrl);
+}
+
+static void fdctrl_handle_sense_drive_status(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ cur_drv->head = (fdctrl->fifo[1] >> 2) & 1;
+ /* 1 Byte status back */
+ fdctrl->fifo[0] = (cur_drv->ro << 6) |
+ (cur_drv->track == 0 ? 0x10 : 0x00) |
+ (cur_drv->head << 2) |
+ GET_CUR_DRV(fdctrl) |
+ 0x28;
+ fdctrl_to_result_phase(fdctrl, 1);
+}
+
+static void fdctrl_handle_recalibrate(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ fd_recalibrate(cur_drv);
+ fdctrl_to_command_phase(fdctrl);
+ /* Raise Interrupt */
+ fdctrl->status0 |= FD_SR0_SEEK;
+ fdctrl_raise_irq(fdctrl);
+}
+
+static void fdctrl_handle_sense_interrupt_status(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv = get_cur_drv(fdctrl);
+
+ if (fdctrl->reset_sensei > 0) {
+ fdctrl->fifo[0] =
+ FD_SR0_RDYCHG + FD_RESET_SENSEI_COUNT - fdctrl->reset_sensei;
+ fdctrl->reset_sensei--;
+ } else if (!(fdctrl->sra & FD_SRA_INTPEND)) {
+ fdctrl->fifo[0] = FD_SR0_INVCMD;
+ fdctrl_to_result_phase(fdctrl, 1);
+ return;
+ } else {
+ fdctrl->fifo[0] =
+ (fdctrl->status0 & ~(FD_SR0_HEAD | FD_SR0_DS1 | FD_SR0_DS0))
+ | GET_CUR_DRV(fdctrl);
+ }
+
+ fdctrl->fifo[1] = cur_drv->track;
+ fdctrl_to_result_phase(fdctrl, 2);
+ fdctrl_reset_irq(fdctrl);
+ fdctrl->status0 = FD_SR0_RDYCHG;
+}
+
+static void fdctrl_handle_seek(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl_to_command_phase(fdctrl);
+ /* The seek command just sends step pulses to the drive and doesn't care if
+ * there is a medium inserted of if it's banging the head against the drive.
+ */
+ fd_seek(cur_drv, cur_drv->head, fdctrl->fifo[2], cur_drv->sect, 1);
+ /* Raise Interrupt */
+ fdctrl->status0 |= FD_SR0_SEEK;
+ fdctrl_raise_irq(fdctrl);
+}
+
+static void fdctrl_handle_perpendicular_mode(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv = get_cur_drv(fdctrl);
+
+ if (fdctrl->fifo[1] & 0x80)
+ cur_drv->perpendicular = fdctrl->fifo[1] & 0x7;
+ /* No result back */
+ fdctrl_to_command_phase(fdctrl);
+}
+
+static void fdctrl_handle_configure(FDCtrl *fdctrl, int direction)
+{
+ fdctrl->config = fdctrl->fifo[2];
+ fdctrl->precomp_trk = fdctrl->fifo[3];
+ /* No result back */
+ fdctrl_to_command_phase(fdctrl);
+}
+
+static void fdctrl_handle_powerdown_mode(FDCtrl *fdctrl, int direction)
+{
+ fdctrl->pwrd = fdctrl->fifo[1];
+ fdctrl->fifo[0] = fdctrl->fifo[1];
+ fdctrl_to_result_phase(fdctrl, 1);
+}
+
+static void fdctrl_handle_option(FDCtrl *fdctrl, int direction)
+{
+ /* No result back */
+ fdctrl_to_command_phase(fdctrl);
+}
+
+static void fdctrl_handle_drive_specification_command(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv = get_cur_drv(fdctrl);
+ uint32_t pos;
+
+ pos = fdctrl->data_pos - 1;
+ pos %= FD_SECTOR_LEN;
+ if (fdctrl->fifo[pos] & 0x80) {
+ /* Command parameters done */
+ if (fdctrl->fifo[pos] & 0x40) {
+ fdctrl->fifo[0] = fdctrl->fifo[1];
+ fdctrl->fifo[2] = 0;
+ fdctrl->fifo[3] = 0;
+ fdctrl_to_result_phase(fdctrl, 4);
+ } else {
+ fdctrl_to_command_phase(fdctrl);
+ }
+ } else if (fdctrl->data_len > 7) {
+ /* ERROR */
+ fdctrl->fifo[0] = 0x80 |
+ (cur_drv->head << 2) | GET_CUR_DRV(fdctrl);
+ fdctrl_to_result_phase(fdctrl, 1);
+ }
+}
+
+static void fdctrl_handle_relative_seek_in(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ if (fdctrl->fifo[2] + cur_drv->track >= cur_drv->max_track) {
+ fd_seek(cur_drv, cur_drv->head, cur_drv->max_track - 1,
+ cur_drv->sect, 1);
+ } else {
+ fd_seek(cur_drv, cur_drv->head,
+ cur_drv->track + fdctrl->fifo[2], cur_drv->sect, 1);
+ }
+ fdctrl_to_command_phase(fdctrl);
+ /* Raise Interrupt */
+ fdctrl->status0 |= FD_SR0_SEEK;
+ fdctrl_raise_irq(fdctrl);
+}
+
+static void fdctrl_handle_relative_seek_out(FDCtrl *fdctrl, int direction)
+{
+ FDrive *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ if (fdctrl->fifo[2] > cur_drv->track) {
+ fd_seek(cur_drv, cur_drv->head, 0, cur_drv->sect, 1);
+ } else {
+ fd_seek(cur_drv, cur_drv->head,
+ cur_drv->track - fdctrl->fifo[2], cur_drv->sect, 1);
+ }
+ fdctrl_to_command_phase(fdctrl);
+ /* Raise Interrupt */
+ fdctrl->status0 |= FD_SR0_SEEK;
+ fdctrl_raise_irq(fdctrl);
+}
+
+/*
+ * Handlers for the execution phase of each command
+ */
+typedef struct FDCtrlCommand {
+ uint8_t value;
+ uint8_t mask;
+ const char* name;
+ int parameters;
+ void (*handler)(FDCtrl *fdctrl, int direction);
+ int direction;
+} FDCtrlCommand;
+
+static const FDCtrlCommand handlers[] = {
+ { FD_CMD_READ, 0x1f, "READ", 8, fdctrl_start_transfer, FD_DIR_READ },
+ { FD_CMD_WRITE, 0x3f, "WRITE", 8, fdctrl_start_transfer, FD_DIR_WRITE },
+ { FD_CMD_SEEK, 0xff, "SEEK", 2, fdctrl_handle_seek },
+ { FD_CMD_SENSE_INTERRUPT_STATUS, 0xff, "SENSE INTERRUPT STATUS", 0, fdctrl_handle_sense_interrupt_status },
+ { FD_CMD_RECALIBRATE, 0xff, "RECALIBRATE", 1, fdctrl_handle_recalibrate },
+ { FD_CMD_FORMAT_TRACK, 0xbf, "FORMAT TRACK", 5, fdctrl_handle_format_track },
+ { FD_CMD_READ_TRACK, 0xbf, "READ TRACK", 8, fdctrl_start_transfer, FD_DIR_READ },
+ { FD_CMD_RESTORE, 0xff, "RESTORE", 17, fdctrl_handle_restore }, /* part of READ DELETED DATA */
+ { FD_CMD_SAVE, 0xff, "SAVE", 0, fdctrl_handle_save }, /* part of READ DELETED DATA */
+ { FD_CMD_READ_DELETED, 0x1f, "READ DELETED DATA", 8, fdctrl_start_transfer_del, FD_DIR_READ },
+ { FD_CMD_SCAN_EQUAL, 0x1f, "SCAN EQUAL", 8, fdctrl_start_transfer, FD_DIR_SCANE },
+ { FD_CMD_VERIFY, 0x1f, "VERIFY", 8, fdctrl_start_transfer, FD_DIR_VERIFY },
+ { FD_CMD_SCAN_LOW_OR_EQUAL, 0x1f, "SCAN LOW OR EQUAL", 8, fdctrl_start_transfer, FD_DIR_SCANL },
+ { FD_CMD_SCAN_HIGH_OR_EQUAL, 0x1f, "SCAN HIGH OR EQUAL", 8, fdctrl_start_transfer, FD_DIR_SCANH },
+ { FD_CMD_WRITE_DELETED, 0x3f, "WRITE DELETED DATA", 8, fdctrl_start_transfer_del, FD_DIR_WRITE },
+ { FD_CMD_READ_ID, 0xbf, "READ ID", 1, fdctrl_handle_readid },
+ { FD_CMD_SPECIFY, 0xff, "SPECIFY", 2, fdctrl_handle_specify },
+ { FD_CMD_SENSE_DRIVE_STATUS, 0xff, "SENSE DRIVE STATUS", 1, fdctrl_handle_sense_drive_status },
+ { FD_CMD_PERPENDICULAR_MODE, 0xff, "PERPENDICULAR MODE", 1, fdctrl_handle_perpendicular_mode },
+ { FD_CMD_CONFIGURE, 0xff, "CONFIGURE", 3, fdctrl_handle_configure },
+ { FD_CMD_POWERDOWN_MODE, 0xff, "POWERDOWN MODE", 2, fdctrl_handle_powerdown_mode },
+ { FD_CMD_OPTION, 0xff, "OPTION", 1, fdctrl_handle_option },
+ { FD_CMD_DRIVE_SPECIFICATION_COMMAND, 0xff, "DRIVE SPECIFICATION COMMAND", 5, fdctrl_handle_drive_specification_command },
+ { FD_CMD_RELATIVE_SEEK_OUT, 0xff, "RELATIVE SEEK OUT", 2, fdctrl_handle_relative_seek_out },
+ { FD_CMD_FORMAT_AND_WRITE, 0xff, "FORMAT AND WRITE", 10, fdctrl_unimplemented },
+ { FD_CMD_RELATIVE_SEEK_IN, 0xff, "RELATIVE SEEK IN", 2, fdctrl_handle_relative_seek_in },
+ { FD_CMD_LOCK, 0x7f, "LOCK", 0, fdctrl_handle_lock },
+ { FD_CMD_DUMPREG, 0xff, "DUMPREG", 0, fdctrl_handle_dumpreg },
+ { FD_CMD_VERSION, 0xff, "VERSION", 0, fdctrl_handle_version },
+ { FD_CMD_PART_ID, 0xff, "PART ID", 0, fdctrl_handle_partid },
+ { FD_CMD_WRITE, 0x1f, "WRITE (BeOS)", 8, fdctrl_start_transfer, FD_DIR_WRITE }, /* not in specification ; BeOS 4.5 bug */
+ { 0, 0, "unknown", 0, fdctrl_unimplemented }, /* default handler */
+};
+/* Associate command to an index in the 'handlers' array */
+static uint8_t command_to_handler[256];
+
+static const FDCtrlCommand *get_command(uint8_t cmd)
+{
+ int idx;
+
+ idx = command_to_handler[cmd];
+ FLOPPY_DPRINTF("%s command\n", handlers[idx].name);
+ return &handlers[idx];
+}
+
+static void fdctrl_write_data(FDCtrl *fdctrl, uint32_t value)
+{
+ FDrive *cur_drv;
+ const FDCtrlCommand *cmd;
+ uint32_t pos;
+
+ /* Reset mode */
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ if (!(fdctrl->msr & FD_MSR_RQM) || (fdctrl->msr & FD_MSR_DIO)) {
+ FLOPPY_DPRINTF("error: controller not ready for writing\n");
+ return;
+ }
+ fdctrl->dsr &= ~FD_DSR_PWRDOWN;
+
+ FLOPPY_DPRINTF("%s: %02x\n", __func__, value);
+
+ /* If data_len spans multiple sectors, the current position in the FIFO
+ * wraps around while fdctrl->data_pos is the real position in the whole
+ * request. */
+ pos = fdctrl->data_pos++;
+ pos %= FD_SECTOR_LEN;
+ fdctrl->fifo[pos] = value;
+
+ if (fdctrl->data_pos == fdctrl->data_len) {
+ fdctrl->msr &= ~FD_MSR_RQM;
+ }
+
+ switch (fdctrl->phase) {
+ case FD_PHASE_EXECUTION:
+ /* For DMA requests, RQM should be cleared during execution phase, so
+ * we would have errored out above. */
+ assert(fdctrl->msr & FD_MSR_NONDMA);
+
+ /* FIFO data write */
+ 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) {
+ FLOPPY_DPRINTF("error writing sector %d\n",
+ fd_sector(cur_drv));
+ break;
+ }
+ if (!fdctrl_seek_to_next_sect(fdctrl, cur_drv)) {
+ FLOPPY_DPRINTF("error seeking to next sector %d\n",
+ fd_sector(cur_drv));
+ break;
+ }
+ }
+
+ /* Switch to result phase when done with the transfer */
+ if (fdctrl->data_pos == fdctrl->data_len) {
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+ }
+ break;
+
+ case FD_PHASE_COMMAND:
+ assert(!(fdctrl->msr & FD_MSR_NONDMA));
+ assert(fdctrl->data_pos < FD_SECTOR_LEN);
+
+ if (pos == 0) {
+ /* The first byte specifies the command. Now we start reading
+ * as many parameters as this command requires. */
+ cmd = get_command(value);
+ fdctrl->data_len = cmd->parameters + 1;
+ if (cmd->parameters) {
+ fdctrl->msr |= FD_MSR_RQM;
+ }
+ fdctrl->msr |= FD_MSR_CMDBUSY;
+ }
+
+ if (fdctrl->data_pos == fdctrl->data_len) {
+ /* We have all parameters now, execute the command */
+ fdctrl->phase = FD_PHASE_EXECUTION;
+
+ if (fdctrl->data_state & FD_STATE_FORMAT) {
+ fdctrl_format_sector(fdctrl);
+ break;
+ }
+
+ cmd = get_command(fdctrl->fifo[0]);
+ FLOPPY_DPRINTF("Calling handler for '%s'\n", cmd->name);
+ cmd->handler(fdctrl, cmd->direction);
+ }
+ break;
+
+ case FD_PHASE_RESULT:
+ default:
+ abort();
+ }
+}
+
+static void fdctrl_result_timer(void *opaque)
+{
+ FDCtrl *fdctrl = opaque;
+ FDrive *cur_drv = get_cur_drv(fdctrl);
+
+ /* Pretend we are spinning.
+ * This is needed for Coherent, which uses READ ID to check for
+ * sector interleaving.
+ */
+ if (cur_drv->last_sect != 0) {
+ cur_drv->sect = (cur_drv->sect % cur_drv->last_sect) + 1;
+ }
+ /* READ_ID can't automatically succeed! */
+ if (fdctrl->check_media_rate &&
+ (fdctrl->dsr & FD_DSR_DRATEMASK) != cur_drv->media_rate) {
+ FLOPPY_DPRINTF("read id rate mismatch (fdc=%d, media=%d)\n",
+ fdctrl->dsr & FD_DSR_DRATEMASK, cur_drv->media_rate);
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA, 0x00);
+ } else {
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+ }
+}
+
+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;
+
+ 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;
+ }
+ }
+
+ fd_init(drive);
+ fdctrl_change_cb(drive, 0);
+ if (drive->blk) {
+ blk_set_dev_ops(drive->blk, &fdctrl_block_ops, drive);
+ }
+ }
+}
+
+ISADevice *fdctrl_init_isa(ISABus *bus, DriveInfo **fds)
+{
+ DeviceState *dev;
+ ISADevice *isadev;
+
+ isadev = isa_try_create(bus, TYPE_ISA_FDC);
+ if (!isadev) {
+ return NULL;
+ }
+ dev = DEVICE(isadev);
+
+ if (fds[0]) {
+ qdev_prop_set_drive_nofail(dev, "driveA", blk_by_legacy_dinfo(fds[0]));
+ }
+ if (fds[1]) {
+ qdev_prop_set_drive_nofail(dev, "driveB", blk_by_legacy_dinfo(fds[1]));
+ }
+ qdev_init_nofail(dev);
+
+ return isadev;
+}
+
+void fdctrl_init_sysbus(qemu_irq irq, int dma_chann,
+ hwaddr mmio_base, DriveInfo **fds)
+{
+ FDCtrl *fdctrl;
+ DeviceState *dev;
+ SysBusDevice *sbd;
+ FDCtrlSysBus *sys;
+
+ dev = qdev_create(NULL, "sysbus-fdc");
+ sys = SYSBUS_FDC(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]));
+ }
+ if (fds[1]) {
+ qdev_prop_set_drive_nofail(dev, "driveB", blk_by_legacy_dinfo(fds[1]));
+ }
+ qdev_init_nofail(dev);
+ sbd = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(sbd, 0, irq);
+ sysbus_mmio_map(sbd, 0, mmio_base);
+}
+
+void sun4m_fdctrl_init(qemu_irq irq, hwaddr io_base,
+ DriveInfo **fds, qemu_irq *fdc_tc)
+{
+ DeviceState *dev;
+ FDCtrlSysBus *sys;
+
+ dev = qdev_create(NULL, "SUNW,fdtwo");
+ if (fds[0]) {
+ qdev_prop_set_drive_nofail(dev, "drive", blk_by_legacy_dinfo(fds[0]));
+ }
+ qdev_init_nofail(dev);
+ sys = SYSBUS_FDC(dev);
+ sysbus_connect_irq(SYS_BUS_DEVICE(sys), 0, irq);
+ sysbus_mmio_map(SYS_BUS_DEVICE(sys), 0, io_base);
+ *fdc_tc = qdev_get_gpio_in(dev, 0);
+}
+
+static void fdctrl_realize_common(FDCtrl *fdctrl, Error **errp)
+{
+ int i, j;
+ static int command_tables_inited = 0;
+
+ /* Fill 'command_to_handler' lookup table */
+ if (!command_tables_inited) {
+ command_tables_inited = 1;
+ for (i = ARRAY_SIZE(handlers) - 1; i >= 0; i--) {
+ for (j = 0; j < sizeof(command_to_handler); j++) {
+ if ((j & handlers[i].mask) == handlers[i].value) {
+ command_to_handler[j] = i;
+ }
+ }
+ }
+ }
+
+ FLOPPY_DPRINTF("init controller\n");
+ fdctrl->fifo = qemu_memalign(512, FD_SECTOR_LEN);
+ fdctrl->fifo_size = 512;
+ fdctrl->result_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ fdctrl_result_timer, fdctrl);
+
+ fdctrl->version = 0x90; /* Intel 82078 controller */
+ fdctrl->config = FD_CONFIG_EIS | FD_CONFIG_EFIFO; /* Implicit seek, polling & FIFO enabled */
+ fdctrl->num_floppies = MAX_FD;
+
+ if (fdctrl->dma_chann != -1) {
+ DMA_register_channel(fdctrl->dma_chann, &fdctrl_transfer_handler, fdctrl);
+ }
+ fdctrl_connect_drives(fdctrl, errp);
+}
+
+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);
+ FDCtrlISABus *isa = ISA_FDC(dev);
+ FDCtrl *fdctrl = &isa->state;
+ Error *err = NULL;
+
+ isa_register_portio_list(isadev, isa->iobase, fdc_portio_list, fdctrl,
+ "fdc");
+
+ isa_init_irq(isadev, &fdctrl->irq, isa->irq);
+ fdctrl->dma_chann = isa->dma;
+
+ qdev_set_legacy_instance_id(dev, isa->iobase, 2);
+ fdctrl_realize_common(fdctrl, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+}
+
+static void sysbus_fdc_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ FDCtrlSysBus *sys = SYSBUS_FDC(obj);
+ FDCtrl *fdctrl = &sys->state;
+
+ fdctrl->dma_chann = -1;
+
+ memory_region_init_io(&fdctrl->iomem, obj, &fdctrl_mem_ops, fdctrl,
+ "fdc", 0x08);
+ sysbus_init_mmio(sbd, &fdctrl->iomem);
+}
+
+static void sun4m_fdc_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ FDCtrlSysBus *sys = SYSBUS_FDC(obj);
+ FDCtrl *fdctrl = &sys->state;
+
+ memory_region_init_io(&fdctrl->iomem, obj, &fdctrl_mem_strict_ops,
+ fdctrl, "fdctrl", 0x08);
+ sysbus_init_mmio(sbd, &fdctrl->iomem);
+}
+
+static void sysbus_fdc_common_initfn(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ FDCtrlSysBus *sys = SYSBUS_FDC(obj);
+ FDCtrl *fdctrl = &sys->state;
+
+ qdev_set_legacy_instance_id(dev, 0 /* io */, 2); /* FIXME */
+
+ sysbus_init_irq(sbd, &fdctrl->irq);
+ qdev_init_gpio_in(dev, fdctrl_handle_tc, 1);
+}
+
+static void sysbus_fdc_common_realize(DeviceState *dev, Error **errp)
+{
+ FDCtrlSysBus *sys = SYSBUS_FDC(dev);
+ FDCtrl *fdctrl = &sys->state;
+
+ fdctrl_realize_common(fdctrl, errp);
+}
+
+FDriveType isa_fdc_get_drive_type(ISADevice *fdc, int i)
+{
+ FDCtrlISABus *isa = ISA_FDC(fdc);
+
+ return isa->state.drives[i].drive;
+}
+
+static const VMStateDescription vmstate_isa_fdc ={
+ .name = "fdc",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (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_DRIVE("driveA", FDCtrlISABus, state.drives[0].blk),
+ DEFINE_PROP_DRIVE("driveB", FDCtrlISABus, state.drives[1].blk),
+ DEFINE_PROP_BIT("check_media_rate", FDCtrlISABus, state.check_media_rate,
+ 0, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void isabus_fdc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = isabus_fdc_realize;
+ dc->fw_name = "fdc";
+ dc->reset = fdctrl_external_reset_isa;
+ dc->vmsd = &vmstate_isa_fdc;
+ dc->props = 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), NULL);
+ device_add_bootindex_property(obj, &isa->bootindexB,
+ "bootindexB", "/floppy@1",
+ DEVICE(obj), NULL);
+}
+
+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,
+};
+
+static const VMStateDescription vmstate_sysbus_fdc ={
+ .name = "fdc",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, FDCtrlSysBus, 0, vmstate_fdc, FDCtrl),
+ VMSTATE_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_END_OF_LIST(),
+};
+
+static void sysbus_fdc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->props = sysbus_fdc_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo sysbus_fdc_info = {
+ .name = "sysbus-fdc",
+ .parent = TYPE_SYSBUS_FDC,
+ .instance_init = sysbus_fdc_initfn,
+ .class_init = sysbus_fdc_class_init,
+};
+
+static Property sun4m_fdc_properties[] = {
+ DEFINE_PROP_DRIVE("drive", FDCtrlSysBus, state.drives[0].blk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sun4m_fdc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->props = sun4m_fdc_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo sun4m_fdc_info = {
+ .name = "SUNW,fdtwo",
+ .parent = TYPE_SYSBUS_FDC,
+ .instance_init = sun4m_fdc_initfn,
+ .class_init = sun4m_fdc_class_init,
+};
+
+static void sysbus_fdc_common_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = sysbus_fdc_common_realize;
+ dc->reset = fdctrl_external_reset_sysbus;
+ dc->vmsd = &vmstate_sysbus_fdc;
+}
+
+static const TypeInfo sysbus_fdc_type_info = {
+ .name = TYPE_SYSBUS_FDC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(FDCtrlSysBus),
+ .instance_init = sysbus_fdc_common_initfn,
+ .abstract = true,
+ .class_init = sysbus_fdc_common_class_init,
+};
+
+static void fdc_register_types(void)
+{
+ type_register_static(&isa_fdc_info);
+ type_register_static(&sysbus_fdc_type_info);
+ type_register_static(&sysbus_fdc_info);
+ type_register_static(&sun4m_fdc_info);
+}
+
+type_init(fdc_register_types)
diff --git a/hw/block/hd-geometry.c b/hw/block/hd-geometry.c
new file mode 100644
index 00000000..b187878f
--- /dev/null
+++ b/hw/block/hd-geometry.c
@@ -0,0 +1,165 @@
+/*
+ * Hard disk geometry utilities
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "sysemu/block-backend.h"
+#include "hw/block/block.h"
+#include "trace.h"
+
+struct partition {
+ uint8_t boot_ind; /* 0x80 - active */
+ uint8_t head; /* starting head */
+ uint8_t sector; /* starting sector */
+ uint8_t cyl; /* starting cylinder */
+ uint8_t sys_ind; /* What partition type */
+ uint8_t end_head; /* end head */
+ uint8_t end_sector; /* end sector */
+ uint8_t end_cyl; /* end cylinder */
+ uint32_t start_sect; /* starting sector counting from 0 */
+ uint32_t nr_sects; /* nr of sectors in partition */
+} QEMU_PACKED;
+
+/* try to guess the disk logical geometry from the MSDOS partition table.
+ Return 0 if OK, -1 if could not guess */
+static int guess_disk_lchs(BlockBackend *blk,
+ int *pcylinders, int *pheads, int *psectors)
+{
+ uint8_t buf[BDRV_SECTOR_SIZE];
+ int i, heads, sectors, cylinders;
+ struct partition *p;
+ uint32_t nr_sects;
+ uint64_t nb_sectors;
+
+ blk_get_geometry(blk, &nb_sectors);
+
+ /**
+ * The function will be invoked during startup not only in sync I/O mode,
+ * but also in async I/O mode. So the I/O throttling function has to
+ * be disabled temporarily here, not permanently.
+ */
+ if (blk_read_unthrottled(blk, 0, buf, 1) < 0) {
+ return -1;
+ }
+ /* test msdos magic */
+ if (buf[510] != 0x55 || buf[511] != 0xaa) {
+ return -1;
+ }
+ for (i = 0; i < 4; i++) {
+ p = ((struct partition *)(buf + 0x1be)) + i;
+ nr_sects = le32_to_cpu(p->nr_sects);
+ if (nr_sects && p->end_head) {
+ /* We make the assumption that the partition terminates on
+ a cylinder boundary */
+ heads = p->end_head + 1;
+ sectors = p->end_sector & 63;
+ if (sectors == 0) {
+ continue;
+ }
+ cylinders = nb_sectors / (heads * sectors);
+ if (cylinders < 1 || cylinders > 16383) {
+ continue;
+ }
+ *pheads = heads;
+ *psectors = sectors;
+ *pcylinders = cylinders;
+ trace_hd_geometry_lchs_guess(blk, cylinders, heads, sectors);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void guess_chs_for_size(BlockBackend *blk,
+ uint32_t *pcyls, uint32_t *pheads, uint32_t *psecs)
+{
+ uint64_t nb_sectors;
+ int cylinders;
+
+ blk_get_geometry(blk, &nb_sectors);
+
+ cylinders = nb_sectors / (16 * 63);
+ if (cylinders > 16383) {
+ cylinders = 16383;
+ } else if (cylinders < 2) {
+ cylinders = 2;
+ }
+ *pcyls = cylinders;
+ *pheads = 16;
+ *psecs = 63;
+}
+
+void hd_geometry_guess(BlockBackend *blk,
+ uint32_t *pcyls, uint32_t *pheads, uint32_t *psecs,
+ int *ptrans)
+{
+ int cylinders, heads, secs, translation;
+ HDGeometry geo;
+
+ /* Try to probe the backing device geometry, otherwise fallback
+ to the old logic. (as of 12/2014 probing only succeeds on DASDs) */
+ if (blk_probe_geometry(blk, &geo) == 0) {
+ *pcyls = geo.cylinders;
+ *psecs = geo.sectors;
+ *pheads = geo.heads;
+ translation = BIOS_ATA_TRANSLATION_NONE;
+ } else if (guess_disk_lchs(blk, &cylinders, &heads, &secs) < 0) {
+ /* no LCHS guess: use a standard physical disk geometry */
+ guess_chs_for_size(blk, pcyls, pheads, psecs);
+ translation = hd_bios_chs_auto_trans(*pcyls, *pheads, *psecs);
+ } else if (heads > 16) {
+ /* LCHS guess with heads > 16 means that a BIOS LBA
+ translation was active, so a standard physical disk
+ geometry is OK */
+ guess_chs_for_size(blk, pcyls, pheads, psecs);
+ translation = *pcyls * *pheads <= 131072
+ ? BIOS_ATA_TRANSLATION_LARGE
+ : BIOS_ATA_TRANSLATION_LBA;
+ } else {
+ /* LCHS guess with heads <= 16: use as physical geometry */
+ *pcyls = cylinders;
+ *pheads = heads;
+ *psecs = secs;
+ /* disable any translation to be in sync with
+ the logical geometry */
+ translation = BIOS_ATA_TRANSLATION_NONE;
+ }
+ if (ptrans) {
+ *ptrans = translation;
+ }
+ trace_hd_geometry_guess(blk, *pcyls, *pheads, *psecs, translation);
+}
+
+int hd_bios_chs_auto_trans(uint32_t cyls, uint32_t heads, uint32_t secs)
+{
+ return cyls <= 1024 && heads <= 16 && secs <= 63
+ ? BIOS_ATA_TRANSLATION_NONE
+ : BIOS_ATA_TRANSLATION_LBA;
+}
diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
new file mode 100644
index 00000000..efc43dde
--- /dev/null
+++ b/hw/block/m25p80.c
@@ -0,0 +1,711 @@
+/*
+ * ST M25P80 emulator. Emulate all SPI flash devices based on the m25p80 command
+ * set. Known devices table current as of Jun/2012 and taken from linux.
+ * See drivers/mtd/devices/m25p80.c.
+ *
+ * Copyright (C) 2011 Edgar E. Iglesias <edgar.iglesias@gmail.com>
+ * Copyright (C) 2012 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
+ * Copyright (C) 2012 PetaLogix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) a later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/ssi.h"
+
+#ifndef M25P80_ERR_DEBUG
+#define M25P80_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(level, ...) do { \
+ if (M25P80_ERR_DEBUG > (level)) { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } \
+} while (0);
+
+/* Fields for FlashPartInfo->flags */
+
+/* erase capabilities */
+#define ER_4K 1
+#define ER_32K 2
+/* set to allow the page program command to write 0s back to 1. Useful for
+ * modelling EEPROM with SPI flash command set
+ */
+#define WR_1 0x100
+
+typedef struct FlashPartInfo {
+ const char *part_name;
+ /* jedec code. (jedec >> 16) & 0xff is the 1st byte, >> 8 the 2nd etc */
+ uint32_t jedec;
+ /* extended jedec code */
+ uint16_t ext_jedec;
+ /* there is confusion between manufacturers as to what a sector is. In this
+ * device model, a "sector" is the size that is erased by the ERASE_SECTOR
+ * command (opcode 0xd8).
+ */
+ uint32_t sector_size;
+ uint32_t n_sectors;
+ uint32_t page_size;
+ uint8_t flags;
+} FlashPartInfo;
+
+/* adapted from linux */
+
+#define INFO(_part_name, _jedec, _ext_jedec, _sector_size, _n_sectors, _flags)\
+ .part_name = (_part_name),\
+ .jedec = (_jedec),\
+ .ext_jedec = (_ext_jedec),\
+ .sector_size = (_sector_size),\
+ .n_sectors = (_n_sectors),\
+ .page_size = 256,\
+ .flags = (_flags),\
+
+#define JEDEC_NUMONYX 0x20
+#define JEDEC_WINBOND 0xEF
+#define JEDEC_SPANSION 0x01
+
+static const FlashPartInfo known_devices[] = {
+ /* Atmel -- some are (confusingly) marketed as "DataFlash" */
+ { INFO("at25fs010", 0x1f6601, 0, 32 << 10, 4, ER_4K) },
+ { INFO("at25fs040", 0x1f6604, 0, 64 << 10, 8, ER_4K) },
+
+ { INFO("at25df041a", 0x1f4401, 0, 64 << 10, 8, ER_4K) },
+ { INFO("at25df321a", 0x1f4701, 0, 64 << 10, 64, ER_4K) },
+ { INFO("at25df641", 0x1f4800, 0, 64 << 10, 128, ER_4K) },
+
+ { INFO("at26f004", 0x1f0400, 0, 64 << 10, 8, ER_4K) },
+ { INFO("at26df081a", 0x1f4501, 0, 64 << 10, 16, ER_4K) },
+ { INFO("at26df161a", 0x1f4601, 0, 64 << 10, 32, ER_4K) },
+ { INFO("at26df321", 0x1f4700, 0, 64 << 10, 64, ER_4K) },
+
+ { INFO("at45db081d", 0x1f2500, 0, 64 << 10, 16, ER_4K) },
+
+ /* EON -- en25xxx */
+ { INFO("en25f32", 0x1c3116, 0, 64 << 10, 64, ER_4K) },
+ { INFO("en25p32", 0x1c2016, 0, 64 << 10, 64, 0) },
+ { INFO("en25q32b", 0x1c3016, 0, 64 << 10, 64, 0) },
+ { INFO("en25p64", 0x1c2017, 0, 64 << 10, 128, 0) },
+ { INFO("en25q64", 0x1c3017, 0, 64 << 10, 128, ER_4K) },
+
+ /* GigaDevice */
+ { INFO("gd25q32", 0xc84016, 0, 64 << 10, 64, ER_4K) },
+ { INFO("gd25q64", 0xc84017, 0, 64 << 10, 128, ER_4K) },
+
+ /* Intel/Numonyx -- xxxs33b */
+ { INFO("160s33b", 0x898911, 0, 64 << 10, 32, 0) },
+ { INFO("320s33b", 0x898912, 0, 64 << 10, 64, 0) },
+ { INFO("640s33b", 0x898913, 0, 64 << 10, 128, 0) },
+ { INFO("n25q064", 0x20ba17, 0, 64 << 10, 128, 0) },
+
+ /* Macronix */
+ { INFO("mx25l2005a", 0xc22012, 0, 64 << 10, 4, ER_4K) },
+ { INFO("mx25l4005a", 0xc22013, 0, 64 << 10, 8, ER_4K) },
+ { INFO("mx25l8005", 0xc22014, 0, 64 << 10, 16, 0) },
+ { INFO("mx25l1606e", 0xc22015, 0, 64 << 10, 32, ER_4K) },
+ { INFO("mx25l3205d", 0xc22016, 0, 64 << 10, 64, 0) },
+ { INFO("mx25l6405d", 0xc22017, 0, 64 << 10, 128, 0) },
+ { INFO("mx25l12805d", 0xc22018, 0, 64 << 10, 256, 0) },
+ { INFO("mx25l12855e", 0xc22618, 0, 64 << 10, 256, 0) },
+ { INFO("mx25l25635e", 0xc22019, 0, 64 << 10, 512, 0) },
+ { INFO("mx25l25655e", 0xc22619, 0, 64 << 10, 512, 0) },
+
+ /* Micron */
+ { INFO("n25q032a11", 0x20bb16, 0, 64 << 10, 64, ER_4K) },
+ { INFO("n25q032a13", 0x20ba16, 0, 64 << 10, 64, ER_4K) },
+ { INFO("n25q064a11", 0x20bb17, 0, 64 << 10, 128, ER_4K) },
+ { INFO("n25q064a13", 0x20ba17, 0, 64 << 10, 128, ER_4K) },
+ { INFO("n25q128a11", 0x20bb18, 0, 64 << 10, 256, ER_4K) },
+ { INFO("n25q128a13", 0x20ba18, 0, 64 << 10, 256, ER_4K) },
+ { INFO("n25q256a11", 0x20bb19, 0, 64 << 10, 512, ER_4K) },
+ { INFO("n25q256a13", 0x20ba19, 0, 64 << 10, 512, ER_4K) },
+
+ /* Spansion -- single (large) sector size only, at least
+ * for the chips listed here (without boot sectors).
+ */
+ { INFO("s25sl032p", 0x010215, 0x4d00, 64 << 10, 64, ER_4K) },
+ { INFO("s25sl064p", 0x010216, 0x4d00, 64 << 10, 128, ER_4K) },
+ { INFO("s25fl256s0", 0x010219, 0x4d00, 256 << 10, 128, 0) },
+ { INFO("s25fl256s1", 0x010219, 0x4d01, 64 << 10, 512, 0) },
+ { INFO("s25fl512s", 0x010220, 0x4d00, 256 << 10, 256, 0) },
+ { INFO("s70fl01gs", 0x010221, 0x4d00, 256 << 10, 256, 0) },
+ { INFO("s25sl12800", 0x012018, 0x0300, 256 << 10, 64, 0) },
+ { INFO("s25sl12801", 0x012018, 0x0301, 64 << 10, 256, 0) },
+ { INFO("s25fl129p0", 0x012018, 0x4d00, 256 << 10, 64, 0) },
+ { INFO("s25fl129p1", 0x012018, 0x4d01, 64 << 10, 256, 0) },
+ { INFO("s25sl004a", 0x010212, 0, 64 << 10, 8, 0) },
+ { INFO("s25sl008a", 0x010213, 0, 64 << 10, 16, 0) },
+ { INFO("s25sl016a", 0x010214, 0, 64 << 10, 32, 0) },
+ { INFO("s25sl032a", 0x010215, 0, 64 << 10, 64, 0) },
+ { INFO("s25sl064a", 0x010216, 0, 64 << 10, 128, 0) },
+ { INFO("s25fl016k", 0xef4015, 0, 64 << 10, 32, ER_4K | ER_32K) },
+ { INFO("s25fl064k", 0xef4017, 0, 64 << 10, 128, ER_4K | ER_32K) },
+
+ /* SST -- large erase sizes are "overlays", "sectors" are 4<< 10 */
+ { INFO("sst25vf040b", 0xbf258d, 0, 64 << 10, 8, ER_4K) },
+ { INFO("sst25vf080b", 0xbf258e, 0, 64 << 10, 16, ER_4K) },
+ { INFO("sst25vf016b", 0xbf2541, 0, 64 << 10, 32, ER_4K) },
+ { INFO("sst25vf032b", 0xbf254a, 0, 64 << 10, 64, ER_4K) },
+ { INFO("sst25wf512", 0xbf2501, 0, 64 << 10, 1, ER_4K) },
+ { INFO("sst25wf010", 0xbf2502, 0, 64 << 10, 2, ER_4K) },
+ { INFO("sst25wf020", 0xbf2503, 0, 64 << 10, 4, ER_4K) },
+ { INFO("sst25wf040", 0xbf2504, 0, 64 << 10, 8, ER_4K) },
+
+ /* ST Microelectronics -- newer production may have feature updates */
+ { INFO("m25p05", 0x202010, 0, 32 << 10, 2, 0) },
+ { INFO("m25p10", 0x202011, 0, 32 << 10, 4, 0) },
+ { INFO("m25p20", 0x202012, 0, 64 << 10, 4, 0) },
+ { INFO("m25p40", 0x202013, 0, 64 << 10, 8, 0) },
+ { INFO("m25p80", 0x202014, 0, 64 << 10, 16, 0) },
+ { INFO("m25p16", 0x202015, 0, 64 << 10, 32, 0) },
+ { INFO("m25p32", 0x202016, 0, 64 << 10, 64, 0) },
+ { INFO("m25p64", 0x202017, 0, 64 << 10, 128, 0) },
+ { INFO("m25p128", 0x202018, 0, 256 << 10, 64, 0) },
+ { INFO("n25q032", 0x20ba16, 0, 64 << 10, 64, 0) },
+
+ { INFO("m45pe10", 0x204011, 0, 64 << 10, 2, 0) },
+ { INFO("m45pe80", 0x204014, 0, 64 << 10, 16, 0) },
+ { INFO("m45pe16", 0x204015, 0, 64 << 10, 32, 0) },
+
+ { INFO("m25pe20", 0x208012, 0, 64 << 10, 4, 0) },
+ { INFO("m25pe80", 0x208014, 0, 64 << 10, 16, 0) },
+ { INFO("m25pe16", 0x208015, 0, 64 << 10, 32, ER_4K) },
+
+ { INFO("m25px32", 0x207116, 0, 64 << 10, 64, ER_4K) },
+ { INFO("m25px32-s0", 0x207316, 0, 64 << 10, 64, ER_4K) },
+ { INFO("m25px32-s1", 0x206316, 0, 64 << 10, 64, ER_4K) },
+ { INFO("m25px64", 0x207117, 0, 64 << 10, 128, 0) },
+
+ /* Winbond -- w25x "blocks" are 64k, "sectors" are 4KiB */
+ { INFO("w25x10", 0xef3011, 0, 64 << 10, 2, ER_4K) },
+ { INFO("w25x20", 0xef3012, 0, 64 << 10, 4, ER_4K) },
+ { INFO("w25x40", 0xef3013, 0, 64 << 10, 8, ER_4K) },
+ { INFO("w25x80", 0xef3014, 0, 64 << 10, 16, ER_4K) },
+ { INFO("w25x16", 0xef3015, 0, 64 << 10, 32, ER_4K) },
+ { INFO("w25x32", 0xef3016, 0, 64 << 10, 64, ER_4K) },
+ { INFO("w25q32", 0xef4016, 0, 64 << 10, 64, ER_4K) },
+ { INFO("w25q32dw", 0xef6016, 0, 64 << 10, 64, ER_4K) },
+ { INFO("w25x64", 0xef3017, 0, 64 << 10, 128, ER_4K) },
+ { INFO("w25q64", 0xef4017, 0, 64 << 10, 128, ER_4K) },
+ { INFO("w25q80", 0xef5014, 0, 64 << 10, 16, ER_4K) },
+ { INFO("w25q80bl", 0xef4014, 0, 64 << 10, 16, ER_4K) },
+ { INFO("w25q256", 0xef4019, 0, 64 << 10, 512, ER_4K) },
+
+ /* Numonyx -- n25q128 */
+ { INFO("n25q128", 0x20ba18, 0, 64 << 10, 256, 0) },
+};
+
+typedef enum {
+ NOP = 0,
+ WRSR = 0x1,
+ WRDI = 0x4,
+ RDSR = 0x5,
+ WREN = 0x6,
+ JEDEC_READ = 0x9f,
+ BULK_ERASE = 0xc7,
+
+ READ = 0x3,
+ FAST_READ = 0xb,
+ DOR = 0x3b,
+ QOR = 0x6b,
+ DIOR = 0xbb,
+ QIOR = 0xeb,
+
+ PP = 0x2,
+ DPP = 0xa2,
+ QPP = 0x32,
+
+ ERASE_4K = 0x20,
+ ERASE_32K = 0x52,
+ ERASE_SECTOR = 0xd8,
+} FlashCMD;
+
+typedef enum {
+ STATE_IDLE,
+ STATE_PAGE_PROGRAM,
+ STATE_READ,
+ STATE_COLLECTING_DATA,
+ STATE_READING_DATA,
+} CMDState;
+
+typedef struct Flash {
+ SSISlave parent_obj;
+
+ uint32_t r;
+
+ BlockBackend *blk;
+
+ uint8_t *storage;
+ uint32_t size;
+ int page_size;
+
+ uint8_t state;
+ uint8_t data[16];
+ uint32_t len;
+ uint32_t pos;
+ uint8_t needed_bytes;
+ uint8_t cmd_in_progress;
+ uint64_t cur_addr;
+ bool write_enable;
+
+ int64_t dirty_page;
+
+ const FlashPartInfo *pi;
+
+} Flash;
+
+typedef struct M25P80Class {
+ SSISlaveClass parent_class;
+ FlashPartInfo *pi;
+} M25P80Class;
+
+#define TYPE_M25P80 "m25p80-generic"
+#define M25P80(obj) \
+ OBJECT_CHECK(Flash, (obj), TYPE_M25P80)
+#define M25P80_CLASS(klass) \
+ OBJECT_CLASS_CHECK(M25P80Class, (klass), TYPE_M25P80)
+#define M25P80_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(M25P80Class, (obj), TYPE_M25P80)
+
+static void blk_sync_complete(void *opaque, int ret)
+{
+ /* do nothing. Masters do not directly interact with the backing store,
+ * only the working copy so no mutexing required.
+ */
+}
+
+static void flash_sync_page(Flash *s, int page)
+{
+ int blk_sector, nb_sectors;
+ QEMUIOVector iov;
+
+ if (!s->blk || blk_is_read_only(s->blk)) {
+ return;
+ }
+
+ blk_sector = (page * s->pi->page_size) / BDRV_SECTOR_SIZE;
+ nb_sectors = DIV_ROUND_UP(s->pi->page_size, BDRV_SECTOR_SIZE);
+ qemu_iovec_init(&iov, 1);
+ qemu_iovec_add(&iov, s->storage + blk_sector * BDRV_SECTOR_SIZE,
+ nb_sectors * BDRV_SECTOR_SIZE);
+ blk_aio_writev(s->blk, blk_sector, &iov, nb_sectors, blk_sync_complete,
+ NULL);
+}
+
+static inline void flash_sync_area(Flash *s, int64_t off, int64_t len)
+{
+ int64_t start, end, nb_sectors;
+ QEMUIOVector iov;
+
+ if (!s->blk || blk_is_read_only(s->blk)) {
+ return;
+ }
+
+ assert(!(len % BDRV_SECTOR_SIZE));
+ start = off / BDRV_SECTOR_SIZE;
+ end = (off + len) / BDRV_SECTOR_SIZE;
+ nb_sectors = end - start;
+ qemu_iovec_init(&iov, 1);
+ qemu_iovec_add(&iov, s->storage + (start * BDRV_SECTOR_SIZE),
+ nb_sectors * BDRV_SECTOR_SIZE);
+ blk_aio_writev(s->blk, start, &iov, nb_sectors, blk_sync_complete, NULL);
+}
+
+static void flash_erase(Flash *s, int offset, FlashCMD cmd)
+{
+ uint32_t len;
+ uint8_t capa_to_assert = 0;
+
+ switch (cmd) {
+ case ERASE_4K:
+ len = 4 << 10;
+ capa_to_assert = ER_4K;
+ break;
+ case ERASE_32K:
+ len = 32 << 10;
+ capa_to_assert = ER_32K;
+ break;
+ case ERASE_SECTOR:
+ len = s->pi->sector_size;
+ break;
+ case BULK_ERASE:
+ len = s->size;
+ break;
+ default:
+ abort();
+ }
+
+ DB_PRINT_L(0, "offset = %#x, len = %d\n", offset, len);
+ if ((s->pi->flags & capa_to_assert) != capa_to_assert) {
+ qemu_log_mask(LOG_GUEST_ERROR, "M25P80: %d erase size not supported by"
+ " device\n", len);
+ }
+
+ if (!s->write_enable) {
+ qemu_log_mask(LOG_GUEST_ERROR, "M25P80: erase with write protect!\n");
+ return;
+ }
+ memset(s->storage + offset, 0xff, len);
+ flash_sync_area(s, offset, len);
+}
+
+static inline void flash_sync_dirty(Flash *s, int64_t newpage)
+{
+ if (s->dirty_page >= 0 && s->dirty_page != newpage) {
+ flash_sync_page(s, s->dirty_page);
+ s->dirty_page = newpage;
+ }
+}
+
+static inline
+void flash_write8(Flash *s, uint64_t addr, uint8_t data)
+{
+ int64_t page = addr / s->pi->page_size;
+ uint8_t prev = s->storage[s->cur_addr];
+
+ if (!s->write_enable) {
+ qemu_log_mask(LOG_GUEST_ERROR, "M25P80: write with write protect!\n");
+ }
+
+ if ((prev ^ data) & data) {
+ DB_PRINT_L(1, "programming zero to one! addr=%" PRIx64 " %" PRIx8
+ " -> %" PRIx8 "\n", addr, prev, data);
+ }
+
+ if (s->pi->flags & WR_1) {
+ s->storage[s->cur_addr] = data;
+ } else {
+ s->storage[s->cur_addr] &= data;
+ }
+
+ flash_sync_dirty(s, page);
+ s->dirty_page = page;
+}
+
+static void complete_collecting_data(Flash *s)
+{
+ s->cur_addr = s->data[0] << 16;
+ s->cur_addr |= s->data[1] << 8;
+ s->cur_addr |= s->data[2];
+
+ s->state = STATE_IDLE;
+
+ switch (s->cmd_in_progress) {
+ case DPP:
+ case QPP:
+ case PP:
+ s->state = STATE_PAGE_PROGRAM;
+ break;
+ case READ:
+ case FAST_READ:
+ case DOR:
+ case QOR:
+ case DIOR:
+ case QIOR:
+ s->state = STATE_READ;
+ break;
+ case ERASE_4K:
+ case ERASE_32K:
+ case ERASE_SECTOR:
+ flash_erase(s, s->cur_addr, s->cmd_in_progress);
+ break;
+ case WRSR:
+ if (s->write_enable) {
+ s->write_enable = false;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void decode_new_cmd(Flash *s, uint32_t value)
+{
+ s->cmd_in_progress = value;
+ DB_PRINT_L(0, "decoded new command:%x\n", value);
+
+ switch (value) {
+
+ case ERASE_4K:
+ case ERASE_32K:
+ case ERASE_SECTOR:
+ case READ:
+ case DPP:
+ case QPP:
+ case PP:
+ s->needed_bytes = 3;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ break;
+
+ case FAST_READ:
+ case DOR:
+ case QOR:
+ s->needed_bytes = 4;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ break;
+
+ case DIOR:
+ switch ((s->pi->jedec >> 16) & 0xFF) {
+ case JEDEC_WINBOND:
+ case JEDEC_SPANSION:
+ s->needed_bytes = 4;
+ break;
+ case JEDEC_NUMONYX:
+ default:
+ s->needed_bytes = 5;
+ }
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ break;
+
+ case QIOR:
+ switch ((s->pi->jedec >> 16) & 0xFF) {
+ case JEDEC_WINBOND:
+ case JEDEC_SPANSION:
+ s->needed_bytes = 6;
+ break;
+ case JEDEC_NUMONYX:
+ default:
+ s->needed_bytes = 8;
+ }
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ break;
+
+ case WRSR:
+ if (s->write_enable) {
+ s->needed_bytes = 1;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ }
+ break;
+
+ case WRDI:
+ s->write_enable = false;
+ break;
+ case WREN:
+ s->write_enable = true;
+ break;
+
+ case RDSR:
+ s->data[0] = (!!s->write_enable) << 1;
+ s->pos = 0;
+ s->len = 1;
+ s->state = STATE_READING_DATA;
+ break;
+
+ case JEDEC_READ:
+ DB_PRINT_L(0, "populated jedec code\n");
+ s->data[0] = (s->pi->jedec >> 16) & 0xff;
+ s->data[1] = (s->pi->jedec >> 8) & 0xff;
+ s->data[2] = s->pi->jedec & 0xff;
+ if (s->pi->ext_jedec) {
+ s->data[3] = (s->pi->ext_jedec >> 8) & 0xff;
+ s->data[4] = s->pi->ext_jedec & 0xff;
+ s->len = 5;
+ } else {
+ s->len = 3;
+ }
+ s->pos = 0;
+ s->state = STATE_READING_DATA;
+ break;
+
+ case BULK_ERASE:
+ if (s->write_enable) {
+ DB_PRINT_L(0, "chip erase\n");
+ flash_erase(s, 0, BULK_ERASE);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "M25P80: chip erase with write "
+ "protect!\n");
+ }
+ break;
+ case NOP:
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "M25P80: Unknown cmd %x\n", value);
+ break;
+ }
+}
+
+static int m25p80_cs(SSISlave *ss, bool select)
+{
+ Flash *s = M25P80(ss);
+
+ if (select) {
+ s->len = 0;
+ s->pos = 0;
+ s->state = STATE_IDLE;
+ flash_sync_dirty(s, -1);
+ }
+
+ DB_PRINT_L(0, "%sselect\n", select ? "de" : "");
+
+ return 0;
+}
+
+static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx)
+{
+ Flash *s = M25P80(ss);
+ uint32_t r = 0;
+
+ switch (s->state) {
+
+ case STATE_PAGE_PROGRAM:
+ DB_PRINT_L(1, "page program cur_addr=%#" PRIx64 " data=%" PRIx8 "\n",
+ s->cur_addr, (uint8_t)tx);
+ flash_write8(s, s->cur_addr, (uint8_t)tx);
+ s->cur_addr++;
+ break;
+
+ case STATE_READ:
+ r = s->storage[s->cur_addr];
+ DB_PRINT_L(1, "READ 0x%" PRIx64 "=%" PRIx8 "\n", s->cur_addr,
+ (uint8_t)r);
+ s->cur_addr = (s->cur_addr + 1) % s->size;
+ break;
+
+ case STATE_COLLECTING_DATA:
+ s->data[s->len] = (uint8_t)tx;
+ s->len++;
+
+ if (s->len == s->needed_bytes) {
+ complete_collecting_data(s);
+ }
+ break;
+
+ case STATE_READING_DATA:
+ r = s->data[s->pos];
+ s->pos++;
+ if (s->pos == s->len) {
+ s->pos = 0;
+ s->state = STATE_IDLE;
+ }
+ break;
+
+ default:
+ case STATE_IDLE:
+ decode_new_cmd(s, (uint8_t)tx);
+ break;
+ }
+
+ return r;
+}
+
+static int m25p80_init(SSISlave *ss)
+{
+ DriveInfo *dinfo;
+ Flash *s = M25P80(ss);
+ M25P80Class *mc = M25P80_GET_CLASS(s);
+
+ s->pi = mc->pi;
+
+ s->size = s->pi->sector_size * s->pi->n_sectors;
+ s->dirty_page = -1;
+
+ /* FIXME use a qdev drive property instead of drive_get_next() */
+ dinfo = drive_get_next(IF_MTD);
+
+ if (dinfo) {
+ DB_PRINT_L(0, "Binding to IF_MTD drive\n");
+ s->blk = blk_by_legacy_dinfo(dinfo);
+ blk_attach_dev_nofail(s->blk, s);
+
+ s->storage = blk_blockalign(s->blk, s->size);
+
+ /* FIXME: Move to late init */
+ if (blk_read(s->blk, 0, s->storage,
+ DIV_ROUND_UP(s->size, BDRV_SECTOR_SIZE))) {
+ fprintf(stderr, "Failed to initialize SPI flash!\n");
+ return 1;
+ }
+ } else {
+ DB_PRINT_L(0, "No BDRV - binding to RAM\n");
+ s->storage = blk_blockalign(NULL, s->size);
+ memset(s->storage, 0xFF, s->size);
+ }
+
+ return 0;
+}
+
+static void m25p80_pre_save(void *opaque)
+{
+ flash_sync_dirty((Flash *)opaque, -1);
+}
+
+static const VMStateDescription vmstate_m25p80 = {
+ .name = "xilinx_spi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = m25p80_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(state, Flash),
+ VMSTATE_UINT8_ARRAY(data, Flash, 16),
+ VMSTATE_UINT32(len, Flash),
+ VMSTATE_UINT32(pos, Flash),
+ VMSTATE_UINT8(needed_bytes, Flash),
+ VMSTATE_UINT8(cmd_in_progress, Flash),
+ VMSTATE_UINT64(cur_addr, Flash),
+ VMSTATE_BOOL(write_enable, Flash),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void m25p80_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+ M25P80Class *mc = M25P80_CLASS(klass);
+
+ k->init = m25p80_init;
+ k->transfer = m25p80_transfer8;
+ k->set_cs = m25p80_cs;
+ k->cs_polarity = SSI_CS_LOW;
+ dc->vmsd = &vmstate_m25p80;
+ mc->pi = data;
+}
+
+static const TypeInfo m25p80_info = {
+ .name = TYPE_M25P80,
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(Flash),
+ .class_size = sizeof(M25P80Class),
+ .abstract = true,
+};
+
+static void m25p80_register_types(void)
+{
+ int i;
+
+ type_register_static(&m25p80_info);
+ for (i = 0; i < ARRAY_SIZE(known_devices); ++i) {
+ TypeInfo ti = {
+ .name = known_devices[i].part_name,
+ .parent = TYPE_M25P80,
+ .class_init = m25p80_class_init,
+ .class_data = (void *)&known_devices[i],
+ };
+ type_register(&ti);
+ }
+}
+
+type_init(m25p80_register_types)
diff --git a/hw/block/nand.c b/hw/block/nand.c
new file mode 100644
index 00000000..61d2cec0
--- /dev/null
+++ b/hw/block/nand.c
@@ -0,0 +1,799 @@
+/*
+ * Flash NAND memory emulation. Based on "16M x 8 Bit NAND Flash
+ * Memory" datasheet for the KM29U128AT / K9F2808U0A chips from
+ * Samsung Electronic.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * Support for additional features based on "MT29F2G16ABCWP 2Gx16"
+ * datasheet from Micron Technology and "NAND02G-B2C" datasheet
+ * from ST Microelectronics.
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#ifndef NAND_IO
+
+# include "hw/hw.h"
+# include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "hw/qdev.h"
+#include "qemu/error-report.h"
+
+# define NAND_CMD_READ0 0x00
+# define NAND_CMD_READ1 0x01
+# define NAND_CMD_READ2 0x50
+# define NAND_CMD_LPREAD2 0x30
+# define NAND_CMD_NOSERIALREAD2 0x35
+# define NAND_CMD_RANDOMREAD1 0x05
+# define NAND_CMD_RANDOMREAD2 0xe0
+# define NAND_CMD_READID 0x90
+# define NAND_CMD_RESET 0xff
+# define NAND_CMD_PAGEPROGRAM1 0x80
+# define NAND_CMD_PAGEPROGRAM2 0x10
+# define NAND_CMD_CACHEPROGRAM2 0x15
+# define NAND_CMD_BLOCKERASE1 0x60
+# define NAND_CMD_BLOCKERASE2 0xd0
+# define NAND_CMD_READSTATUS 0x70
+# define NAND_CMD_COPYBACKPRG1 0x85
+
+# define NAND_IOSTATUS_ERROR (1 << 0)
+# define NAND_IOSTATUS_PLANE0 (1 << 1)
+# define NAND_IOSTATUS_PLANE1 (1 << 2)
+# define NAND_IOSTATUS_PLANE2 (1 << 3)
+# define NAND_IOSTATUS_PLANE3 (1 << 4)
+# define NAND_IOSTATUS_READY (1 << 6)
+# define NAND_IOSTATUS_UNPROTCT (1 << 7)
+
+# define MAX_PAGE 0x800
+# define MAX_OOB 0x40
+
+typedef struct NANDFlashState NANDFlashState;
+struct NANDFlashState {
+ DeviceState parent_obj;
+
+ uint8_t manf_id, chip_id;
+ uint8_t buswidth; /* in BYTES */
+ int size, pages;
+ int page_shift, oob_shift, erase_shift, addr_shift;
+ uint8_t *storage;
+ BlockBackend *blk;
+ int mem_oob;
+
+ uint8_t cle, ale, ce, wp, gnd;
+
+ uint8_t io[MAX_PAGE + MAX_OOB + 0x400];
+ uint8_t *ioaddr;
+ int iolen;
+
+ uint32_t cmd;
+ uint64_t addr;
+ int addrlen;
+ int status;
+ int offset;
+
+ void (*blk_write)(NANDFlashState *s);
+ void (*blk_erase)(NANDFlashState *s);
+ void (*blk_load)(NANDFlashState *s, uint64_t addr, int offset);
+
+ uint32_t ioaddr_vmstate;
+};
+
+#define TYPE_NAND "nand"
+
+#define NAND(obj) \
+ OBJECT_CHECK(NANDFlashState, (obj), TYPE_NAND)
+
+static void mem_and(uint8_t *dest, const uint8_t *src, size_t n)
+{
+ /* Like memcpy() but we logical-AND the data into the destination */
+ int i;
+ for (i = 0; i < n; i++) {
+ dest[i] &= src[i];
+ }
+}
+
+# define NAND_NO_AUTOINCR 0x00000001
+# define NAND_BUSWIDTH_16 0x00000002
+# define NAND_NO_PADDING 0x00000004
+# define NAND_CACHEPRG 0x00000008
+# define NAND_COPYBACK 0x00000010
+# define NAND_IS_AND 0x00000020
+# define NAND_4PAGE_ARRAY 0x00000040
+# define NAND_NO_READRDY 0x00000100
+# define NAND_SAMSUNG_LP (NAND_NO_PADDING | NAND_COPYBACK)
+
+# define NAND_IO
+
+# define PAGE(addr) ((addr) >> ADDR_SHIFT)
+# define PAGE_START(page) (PAGE(page) * (PAGE_SIZE + OOB_SIZE))
+# define PAGE_MASK ((1 << ADDR_SHIFT) - 1)
+# define OOB_SHIFT (PAGE_SHIFT - 5)
+# define OOB_SIZE (1 << OOB_SHIFT)
+# define SECTOR(addr) ((addr) >> (9 + ADDR_SHIFT - PAGE_SHIFT))
+# define SECTOR_OFFSET(addr) ((addr) & ((511 >> PAGE_SHIFT) << 8))
+
+# define PAGE_SIZE 256
+# define PAGE_SHIFT 8
+# define PAGE_SECTORS 1
+# define ADDR_SHIFT 8
+# include "nand.c"
+# define PAGE_SIZE 512
+# define PAGE_SHIFT 9
+# define PAGE_SECTORS 1
+# define ADDR_SHIFT 8
+# include "nand.c"
+# define PAGE_SIZE 2048
+# define PAGE_SHIFT 11
+# define PAGE_SECTORS 4
+# define ADDR_SHIFT 16
+# include "nand.c"
+
+/* Information based on Linux drivers/mtd/nand/nand_ids.c */
+static const struct {
+ int size;
+ int width;
+ int page_shift;
+ int erase_shift;
+ uint32_t options;
+} nand_flash_ids[0x100] = {
+ [0 ... 0xff] = { 0 },
+
+ [0x6e] = { 1, 8, 8, 4, 0 },
+ [0x64] = { 2, 8, 8, 4, 0 },
+ [0x6b] = { 4, 8, 9, 4, 0 },
+ [0xe8] = { 1, 8, 8, 4, 0 },
+ [0xec] = { 1, 8, 8, 4, 0 },
+ [0xea] = { 2, 8, 8, 4, 0 },
+ [0xd5] = { 4, 8, 9, 4, 0 },
+ [0xe3] = { 4, 8, 9, 4, 0 },
+ [0xe5] = { 4, 8, 9, 4, 0 },
+ [0xd6] = { 8, 8, 9, 4, 0 },
+
+ [0x39] = { 8, 8, 9, 4, 0 },
+ [0xe6] = { 8, 8, 9, 4, 0 },
+ [0x49] = { 8, 16, 9, 4, NAND_BUSWIDTH_16 },
+ [0x59] = { 8, 16, 9, 4, NAND_BUSWIDTH_16 },
+
+ [0x33] = { 16, 8, 9, 5, 0 },
+ [0x73] = { 16, 8, 9, 5, 0 },
+ [0x43] = { 16, 16, 9, 5, NAND_BUSWIDTH_16 },
+ [0x53] = { 16, 16, 9, 5, NAND_BUSWIDTH_16 },
+
+ [0x35] = { 32, 8, 9, 5, 0 },
+ [0x75] = { 32, 8, 9, 5, 0 },
+ [0x45] = { 32, 16, 9, 5, NAND_BUSWIDTH_16 },
+ [0x55] = { 32, 16, 9, 5, NAND_BUSWIDTH_16 },
+
+ [0x36] = { 64, 8, 9, 5, 0 },
+ [0x76] = { 64, 8, 9, 5, 0 },
+ [0x46] = { 64, 16, 9, 5, NAND_BUSWIDTH_16 },
+ [0x56] = { 64, 16, 9, 5, NAND_BUSWIDTH_16 },
+
+ [0x78] = { 128, 8, 9, 5, 0 },
+ [0x39] = { 128, 8, 9, 5, 0 },
+ [0x79] = { 128, 8, 9, 5, 0 },
+ [0x72] = { 128, 16, 9, 5, NAND_BUSWIDTH_16 },
+ [0x49] = { 128, 16, 9, 5, NAND_BUSWIDTH_16 },
+ [0x74] = { 128, 16, 9, 5, NAND_BUSWIDTH_16 },
+ [0x59] = { 128, 16, 9, 5, NAND_BUSWIDTH_16 },
+
+ [0x71] = { 256, 8, 9, 5, 0 },
+
+ /*
+ * These are the new chips with large page size. The pagesize and the
+ * erasesize is determined from the extended id bytes
+ */
+# define LP_OPTIONS (NAND_SAMSUNG_LP | NAND_NO_READRDY | NAND_NO_AUTOINCR)
+# define LP_OPTIONS16 (LP_OPTIONS | NAND_BUSWIDTH_16)
+
+ /* 512 Megabit */
+ [0xa2] = { 64, 8, 0, 0, LP_OPTIONS },
+ [0xf2] = { 64, 8, 0, 0, LP_OPTIONS },
+ [0xb2] = { 64, 16, 0, 0, LP_OPTIONS16 },
+ [0xc2] = { 64, 16, 0, 0, LP_OPTIONS16 },
+
+ /* 1 Gigabit */
+ [0xa1] = { 128, 8, 0, 0, LP_OPTIONS },
+ [0xf1] = { 128, 8, 0, 0, LP_OPTIONS },
+ [0xb1] = { 128, 16, 0, 0, LP_OPTIONS16 },
+ [0xc1] = { 128, 16, 0, 0, LP_OPTIONS16 },
+
+ /* 2 Gigabit */
+ [0xaa] = { 256, 8, 0, 0, LP_OPTIONS },
+ [0xda] = { 256, 8, 0, 0, LP_OPTIONS },
+ [0xba] = { 256, 16, 0, 0, LP_OPTIONS16 },
+ [0xca] = { 256, 16, 0, 0, LP_OPTIONS16 },
+
+ /* 4 Gigabit */
+ [0xac] = { 512, 8, 0, 0, LP_OPTIONS },
+ [0xdc] = { 512, 8, 0, 0, LP_OPTIONS },
+ [0xbc] = { 512, 16, 0, 0, LP_OPTIONS16 },
+ [0xcc] = { 512, 16, 0, 0, LP_OPTIONS16 },
+
+ /* 8 Gigabit */
+ [0xa3] = { 1024, 8, 0, 0, LP_OPTIONS },
+ [0xd3] = { 1024, 8, 0, 0, LP_OPTIONS },
+ [0xb3] = { 1024, 16, 0, 0, LP_OPTIONS16 },
+ [0xc3] = { 1024, 16, 0, 0, LP_OPTIONS16 },
+
+ /* 16 Gigabit */
+ [0xa5] = { 2048, 8, 0, 0, LP_OPTIONS },
+ [0xd5] = { 2048, 8, 0, 0, LP_OPTIONS },
+ [0xb5] = { 2048, 16, 0, 0, LP_OPTIONS16 },
+ [0xc5] = { 2048, 16, 0, 0, LP_OPTIONS16 },
+};
+
+static void nand_reset(DeviceState *dev)
+{
+ NANDFlashState *s = NAND(dev);
+ s->cmd = NAND_CMD_READ0;
+ s->addr = 0;
+ s->addrlen = 0;
+ s->iolen = 0;
+ s->offset = 0;
+ s->status &= NAND_IOSTATUS_UNPROTCT;
+ s->status |= NAND_IOSTATUS_READY;
+}
+
+static inline void nand_pushio_byte(NANDFlashState *s, uint8_t value)
+{
+ s->ioaddr[s->iolen++] = value;
+ for (value = s->buswidth; --value;) {
+ s->ioaddr[s->iolen++] = 0;
+ }
+}
+
+static void nand_command(NANDFlashState *s)
+{
+ unsigned int offset;
+ switch (s->cmd) {
+ case NAND_CMD_READ0:
+ s->iolen = 0;
+ break;
+
+ case NAND_CMD_READID:
+ s->ioaddr = s->io;
+ s->iolen = 0;
+ nand_pushio_byte(s, s->manf_id);
+ nand_pushio_byte(s, s->chip_id);
+ nand_pushio_byte(s, 'Q'); /* Don't-care byte (often 0xa5) */
+ if (nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) {
+ /* Page Size, Block Size, Spare Size; bit 6 indicates
+ * 8 vs 16 bit width NAND.
+ */
+ nand_pushio_byte(s, (s->buswidth == 2) ? 0x55 : 0x15);
+ } else {
+ nand_pushio_byte(s, 0xc0); /* Multi-plane */
+ }
+ break;
+
+ case NAND_CMD_RANDOMREAD2:
+ case NAND_CMD_NOSERIALREAD2:
+ if (!(nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP))
+ break;
+ offset = s->addr & ((1 << s->addr_shift) - 1);
+ s->blk_load(s, s->addr, offset);
+ if (s->gnd)
+ s->iolen = (1 << s->page_shift) - offset;
+ else
+ s->iolen = (1 << s->page_shift) + (1 << s->oob_shift) - offset;
+ break;
+
+ case NAND_CMD_RESET:
+ nand_reset(DEVICE(s));
+ break;
+
+ case NAND_CMD_PAGEPROGRAM1:
+ s->ioaddr = s->io;
+ s->iolen = 0;
+ break;
+
+ case NAND_CMD_PAGEPROGRAM2:
+ if (s->wp) {
+ s->blk_write(s);
+ }
+ break;
+
+ case NAND_CMD_BLOCKERASE1:
+ break;
+
+ case NAND_CMD_BLOCKERASE2:
+ s->addr &= (1ull << s->addrlen * 8) - 1;
+ s->addr <<= nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP ?
+ 16 : 8;
+
+ if (s->wp) {
+ s->blk_erase(s);
+ }
+ break;
+
+ case NAND_CMD_READSTATUS:
+ s->ioaddr = s->io;
+ s->iolen = 0;
+ nand_pushio_byte(s, s->status);
+ break;
+
+ default:
+ printf("%s: Unknown NAND command 0x%02x\n", __FUNCTION__, s->cmd);
+ }
+}
+
+static void nand_pre_save(void *opaque)
+{
+ NANDFlashState *s = NAND(opaque);
+
+ s->ioaddr_vmstate = s->ioaddr - s->io;
+}
+
+static int nand_post_load(void *opaque, int version_id)
+{
+ NANDFlashState *s = NAND(opaque);
+
+ if (s->ioaddr_vmstate > sizeof(s->io)) {
+ return -EINVAL;
+ }
+ s->ioaddr = s->io + s->ioaddr_vmstate;
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_nand = {
+ .name = "nand",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = nand_pre_save,
+ .post_load = nand_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(cle, NANDFlashState),
+ VMSTATE_UINT8(ale, NANDFlashState),
+ VMSTATE_UINT8(ce, NANDFlashState),
+ VMSTATE_UINT8(wp, NANDFlashState),
+ VMSTATE_UINT8(gnd, NANDFlashState),
+ VMSTATE_BUFFER(io, NANDFlashState),
+ VMSTATE_UINT32(ioaddr_vmstate, NANDFlashState),
+ VMSTATE_INT32(iolen, NANDFlashState),
+ VMSTATE_UINT32(cmd, NANDFlashState),
+ VMSTATE_UINT64(addr, NANDFlashState),
+ VMSTATE_INT32(addrlen, NANDFlashState),
+ VMSTATE_INT32(status, NANDFlashState),
+ VMSTATE_INT32(offset, NANDFlashState),
+ /* XXX: do we want to save s->storage too? */
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void nand_realize(DeviceState *dev, Error **errp)
+{
+ int pagesize;
+ NANDFlashState *s = NAND(dev);
+
+ s->buswidth = nand_flash_ids[s->chip_id].width >> 3;
+ s->size = nand_flash_ids[s->chip_id].size << 20;
+ if (nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) {
+ s->page_shift = 11;
+ s->erase_shift = 6;
+ } else {
+ s->page_shift = nand_flash_ids[s->chip_id].page_shift;
+ s->erase_shift = nand_flash_ids[s->chip_id].erase_shift;
+ }
+
+ switch (1 << s->page_shift) {
+ case 256:
+ nand_init_256(s);
+ break;
+ case 512:
+ nand_init_512(s);
+ break;
+ case 2048:
+ nand_init_2048(s);
+ break;
+ default:
+ error_setg(errp, "Unsupported NAND block size %#x",
+ 1 << s->page_shift);
+ return;
+ }
+
+ pagesize = 1 << s->oob_shift;
+ s->mem_oob = 1;
+ if (s->blk) {
+ if (blk_is_read_only(s->blk)) {
+ error_setg(errp, "Can't use a read-only drive");
+ return;
+ }
+ if (blk_getlength(s->blk) >=
+ (s->pages << s->page_shift) + (s->pages << s->oob_shift)) {
+ pagesize = 0;
+ s->mem_oob = 0;
+ }
+ } else {
+ pagesize += 1 << s->page_shift;
+ }
+ if (pagesize) {
+ s->storage = (uint8_t *) memset(g_malloc(s->pages * pagesize),
+ 0xff, s->pages * pagesize);
+ }
+ /* Give s->ioaddr a sane value in case we save state before it is used. */
+ s->ioaddr = s->io;
+}
+
+static Property nand_properties[] = {
+ DEFINE_PROP_UINT8("manufacturer_id", NANDFlashState, manf_id, 0),
+ DEFINE_PROP_UINT8("chip_id", NANDFlashState, chip_id, 0),
+ DEFINE_PROP_DRIVE("drive", NANDFlashState, blk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void nand_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = nand_realize;
+ dc->reset = nand_reset;
+ dc->vmsd = &vmstate_nand;
+ dc->props = nand_properties;
+}
+
+static const TypeInfo nand_info = {
+ .name = TYPE_NAND,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(NANDFlashState),
+ .class_init = nand_class_init,
+};
+
+static void nand_register_types(void)
+{
+ type_register_static(&nand_info);
+}
+
+/*
+ * Chip inputs are CLE, ALE, CE, WP, GND and eight I/O pins. Chip
+ * outputs are R/B and eight I/O pins.
+ *
+ * CE, WP and R/B are active low.
+ */
+void nand_setpins(DeviceState *dev, uint8_t cle, uint8_t ale,
+ uint8_t ce, uint8_t wp, uint8_t gnd)
+{
+ NANDFlashState *s = NAND(dev);
+
+ s->cle = cle;
+ s->ale = ale;
+ s->ce = ce;
+ s->wp = wp;
+ s->gnd = gnd;
+ if (wp) {
+ s->status |= NAND_IOSTATUS_UNPROTCT;
+ } else {
+ s->status &= ~NAND_IOSTATUS_UNPROTCT;
+ }
+}
+
+void nand_getpins(DeviceState *dev, int *rb)
+{
+ *rb = 1;
+}
+
+void nand_setio(DeviceState *dev, uint32_t value)
+{
+ int i;
+ NANDFlashState *s = NAND(dev);
+
+ if (!s->ce && s->cle) {
+ if (nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) {
+ if (s->cmd == NAND_CMD_READ0 && value == NAND_CMD_LPREAD2)
+ return;
+ if (value == NAND_CMD_RANDOMREAD1) {
+ s->addr &= ~((1 << s->addr_shift) - 1);
+ s->addrlen = 0;
+ return;
+ }
+ }
+ if (value == NAND_CMD_READ0) {
+ s->offset = 0;
+ } else if (value == NAND_CMD_READ1) {
+ s->offset = 0x100;
+ value = NAND_CMD_READ0;
+ } else if (value == NAND_CMD_READ2) {
+ s->offset = 1 << s->page_shift;
+ value = NAND_CMD_READ0;
+ }
+
+ s->cmd = value;
+
+ if (s->cmd == NAND_CMD_READSTATUS ||
+ s->cmd == NAND_CMD_PAGEPROGRAM2 ||
+ s->cmd == NAND_CMD_BLOCKERASE1 ||
+ s->cmd == NAND_CMD_BLOCKERASE2 ||
+ s->cmd == NAND_CMD_NOSERIALREAD2 ||
+ s->cmd == NAND_CMD_RANDOMREAD2 ||
+ s->cmd == NAND_CMD_RESET) {
+ nand_command(s);
+ }
+
+ if (s->cmd != NAND_CMD_RANDOMREAD2) {
+ s->addrlen = 0;
+ }
+ }
+
+ if (s->ale) {
+ unsigned int shift = s->addrlen * 8;
+ unsigned int mask = ~(0xff << shift);
+ unsigned int v = value << shift;
+
+ s->addr = (s->addr & mask) | v;
+ s->addrlen ++;
+
+ switch (s->addrlen) {
+ case 1:
+ if (s->cmd == NAND_CMD_READID) {
+ nand_command(s);
+ }
+ break;
+ case 2: /* fix cache address as a byte address */
+ s->addr <<= (s->buswidth - 1);
+ break;
+ case 3:
+ if (!(nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) &&
+ (s->cmd == NAND_CMD_READ0 ||
+ s->cmd == NAND_CMD_PAGEPROGRAM1)) {
+ nand_command(s);
+ }
+ break;
+ case 4:
+ if ((nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) &&
+ nand_flash_ids[s->chip_id].size < 256 && /* 1Gb or less */
+ (s->cmd == NAND_CMD_READ0 ||
+ s->cmd == NAND_CMD_PAGEPROGRAM1)) {
+ nand_command(s);
+ }
+ break;
+ case 5:
+ if ((nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) &&
+ nand_flash_ids[s->chip_id].size >= 256 && /* 2Gb or more */
+ (s->cmd == NAND_CMD_READ0 ||
+ s->cmd == NAND_CMD_PAGEPROGRAM1)) {
+ nand_command(s);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!s->cle && !s->ale && s->cmd == NAND_CMD_PAGEPROGRAM1) {
+ if (s->iolen < (1 << s->page_shift) + (1 << s->oob_shift)) {
+ for (i = s->buswidth; i--; value >>= 8) {
+ s->io[s->iolen ++] = (uint8_t) (value & 0xff);
+ }
+ }
+ } else if (!s->cle && !s->ale && s->cmd == NAND_CMD_COPYBACKPRG1) {
+ if ((s->addr & ((1 << s->addr_shift) - 1)) <
+ (1 << s->page_shift) + (1 << s->oob_shift)) {
+ for (i = s->buswidth; i--; s->addr++, value >>= 8) {
+ s->io[s->iolen + (s->addr & ((1 << s->addr_shift) - 1))] =
+ (uint8_t) (value & 0xff);
+ }
+ }
+ }
+}
+
+uint32_t nand_getio(DeviceState *dev)
+{
+ int offset;
+ uint32_t x = 0;
+ NANDFlashState *s = NAND(dev);
+
+ /* Allow sequential reading */
+ if (!s->iolen && s->cmd == NAND_CMD_READ0) {
+ offset = (int) (s->addr & ((1 << s->addr_shift) - 1)) + s->offset;
+ s->offset = 0;
+
+ s->blk_load(s, s->addr, offset);
+ if (s->gnd)
+ s->iolen = (1 << s->page_shift) - offset;
+ else
+ s->iolen = (1 << s->page_shift) + (1 << s->oob_shift) - offset;
+ }
+
+ if (s->ce || s->iolen <= 0) {
+ return 0;
+ }
+
+ for (offset = s->buswidth; offset--;) {
+ x |= s->ioaddr[offset] << (offset << 3);
+ }
+ /* after receiving READ STATUS command all subsequent reads will
+ * return the status register value until another command is issued
+ */
+ if (s->cmd != NAND_CMD_READSTATUS) {
+ s->addr += s->buswidth;
+ s->ioaddr += s->buswidth;
+ s->iolen -= s->buswidth;
+ }
+ return x;
+}
+
+uint32_t nand_getbuswidth(DeviceState *dev)
+{
+ NANDFlashState *s = (NANDFlashState *) dev;
+ return s->buswidth << 3;
+}
+
+DeviceState *nand_init(BlockBackend *blk, int manf_id, int chip_id)
+{
+ DeviceState *dev;
+
+ if (nand_flash_ids[chip_id].size == 0) {
+ hw_error("%s: Unsupported NAND chip ID.\n", __FUNCTION__);
+ }
+ dev = DEVICE(object_new(TYPE_NAND));
+ qdev_prop_set_uint8(dev, "manufacturer_id", manf_id);
+ qdev_prop_set_uint8(dev, "chip_id", chip_id);
+ if (blk) {
+ qdev_prop_set_drive_nofail(dev, "drive", blk);
+ }
+
+ qdev_init_nofail(dev);
+ return dev;
+}
+
+type_init(nand_register_types)
+
+#else
+
+/* Program a single page */
+static void glue(nand_blk_write_, PAGE_SIZE)(NANDFlashState *s)
+{
+ uint64_t off, page, sector, soff;
+ uint8_t iobuf[(PAGE_SECTORS + 2) * 0x200];
+ if (PAGE(s->addr) >= s->pages)
+ return;
+
+ if (!s->blk) {
+ mem_and(s->storage + PAGE_START(s->addr) + (s->addr & PAGE_MASK) +
+ s->offset, s->io, s->iolen);
+ } else if (s->mem_oob) {
+ sector = SECTOR(s->addr);
+ off = (s->addr & PAGE_MASK) + s->offset;
+ soff = SECTOR_OFFSET(s->addr);
+ if (blk_read(s->blk, sector, iobuf, PAGE_SECTORS) < 0) {
+ printf("%s: read error in sector %" PRIu64 "\n", __func__, sector);
+ return;
+ }
+
+ mem_and(iobuf + (soff | off), s->io, MIN(s->iolen, PAGE_SIZE - off));
+ if (off + s->iolen > PAGE_SIZE) {
+ page = PAGE(s->addr);
+ mem_and(s->storage + (page << OOB_SHIFT), s->io + PAGE_SIZE - off,
+ MIN(OOB_SIZE, off + s->iolen - PAGE_SIZE));
+ }
+
+ if (blk_write(s->blk, sector, iobuf, PAGE_SECTORS) < 0) {
+ printf("%s: write error in sector %" PRIu64 "\n", __func__, sector);
+ }
+ } else {
+ off = PAGE_START(s->addr) + (s->addr & PAGE_MASK) + s->offset;
+ sector = off >> 9;
+ soff = off & 0x1ff;
+ if (blk_read(s->blk, sector, iobuf, PAGE_SECTORS + 2) < 0) {
+ printf("%s: read error in sector %" PRIu64 "\n", __func__, sector);
+ return;
+ }
+
+ mem_and(iobuf + soff, s->io, s->iolen);
+
+ if (blk_write(s->blk, sector, iobuf, PAGE_SECTORS + 2) < 0) {
+ printf("%s: write error in sector %" PRIu64 "\n", __func__, sector);
+ }
+ }
+ s->offset = 0;
+}
+
+/* Erase a single block */
+static void glue(nand_blk_erase_, PAGE_SIZE)(NANDFlashState *s)
+{
+ uint64_t i, page, addr;
+ uint8_t iobuf[0x200] = { [0 ... 0x1ff] = 0xff, };
+ addr = s->addr & ~((1 << (ADDR_SHIFT + s->erase_shift)) - 1);
+
+ if (PAGE(addr) >= s->pages) {
+ return;
+ }
+
+ if (!s->blk) {
+ memset(s->storage + PAGE_START(addr),
+ 0xff, (PAGE_SIZE + OOB_SIZE) << s->erase_shift);
+ } else if (s->mem_oob) {
+ memset(s->storage + (PAGE(addr) << OOB_SHIFT),
+ 0xff, OOB_SIZE << s->erase_shift);
+ i = SECTOR(addr);
+ page = SECTOR(addr + (ADDR_SHIFT + s->erase_shift));
+ for (; i < page; i ++)
+ if (blk_write(s->blk, i, iobuf, 1) < 0) {
+ printf("%s: write error in sector %" PRIu64 "\n", __func__, i);
+ }
+ } else {
+ addr = PAGE_START(addr);
+ page = addr >> 9;
+ if (blk_read(s->blk, page, iobuf, 1) < 0) {
+ printf("%s: read error in sector %" PRIu64 "\n", __func__, page);
+ }
+ memset(iobuf + (addr & 0x1ff), 0xff, (~addr & 0x1ff) + 1);
+ if (blk_write(s->blk, page, iobuf, 1) < 0) {
+ printf("%s: write error in sector %" PRIu64 "\n", __func__, page);
+ }
+
+ memset(iobuf, 0xff, 0x200);
+ i = (addr & ~0x1ff) + 0x200;
+ for (addr += ((PAGE_SIZE + OOB_SIZE) << s->erase_shift) - 0x200;
+ i < addr; i += 0x200) {
+ if (blk_write(s->blk, i >> 9, iobuf, 1) < 0) {
+ printf("%s: write error in sector %" PRIu64 "\n",
+ __func__, i >> 9);
+ }
+ }
+
+ page = i >> 9;
+ if (blk_read(s->blk, page, iobuf, 1) < 0) {
+ printf("%s: read error in sector %" PRIu64 "\n", __func__, page);
+ }
+ memset(iobuf, 0xff, ((addr - 1) & 0x1ff) + 1);
+ if (blk_write(s->blk, page, iobuf, 1) < 0) {
+ printf("%s: write error in sector %" PRIu64 "\n", __func__, page);
+ }
+ }
+}
+
+static void glue(nand_blk_load_, PAGE_SIZE)(NANDFlashState *s,
+ uint64_t addr, int offset)
+{
+ if (PAGE(addr) >= s->pages) {
+ return;
+ }
+
+ if (s->blk) {
+ if (s->mem_oob) {
+ if (blk_read(s->blk, SECTOR(addr), s->io, PAGE_SECTORS) < 0) {
+ printf("%s: read error in sector %" PRIu64 "\n",
+ __func__, SECTOR(addr));
+ }
+ memcpy(s->io + SECTOR_OFFSET(s->addr) + PAGE_SIZE,
+ s->storage + (PAGE(s->addr) << OOB_SHIFT),
+ OOB_SIZE);
+ s->ioaddr = s->io + SECTOR_OFFSET(s->addr) + offset;
+ } else {
+ if (blk_read(s->blk, PAGE_START(addr) >> 9,
+ s->io, (PAGE_SECTORS + 2)) < 0) {
+ printf("%s: read error in sector %" PRIu64 "\n",
+ __func__, PAGE_START(addr) >> 9);
+ }
+ s->ioaddr = s->io + (PAGE_START(addr) & 0x1ff) + offset;
+ }
+ } else {
+ memcpy(s->io, s->storage + PAGE_START(s->addr) +
+ offset, PAGE_SIZE + OOB_SIZE - offset);
+ s->ioaddr = s->io;
+ }
+}
+
+static void glue(nand_init_, PAGE_SIZE)(NANDFlashState *s)
+{
+ s->oob_shift = PAGE_SHIFT - 5;
+ s->pages = s->size >> PAGE_SHIFT;
+ s->addr_shift = ADDR_SHIFT;
+
+ s->blk_erase = glue(nand_blk_erase_, PAGE_SIZE);
+ s->blk_write = glue(nand_blk_write_, PAGE_SIZE);
+ s->blk_load = glue(nand_blk_load_, PAGE_SIZE);
+}
+
+# undef PAGE_SIZE
+# undef PAGE_SHIFT
+# undef PAGE_SECTORS
+# undef ADDR_SHIFT
+#endif /* NAND_IO */
diff --git a/hw/block/nvme.c b/hw/block/nvme.c
new file mode 100644
index 00000000..40d48803
--- /dev/null
+++ b/hw/block/nvme.c
@@ -0,0 +1,967 @@
+/*
+ * QEMU NVM Express Controller
+ *
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Written by Keith Busch <keith.busch@intel.com>
+ *
+ * This code is licensed under the GNU GPL v2 or later.
+ */
+
+/**
+ * Reference Specs: http://www.nvmexpress.org, 1.1, 1.0e
+ *
+ * http://www.nvmexpress.org/resources/
+ */
+
+/**
+ * Usage: add options:
+ * -drive file=<file>,if=none,id=<drive_id>
+ * -device nvme,drive=<drive_id>,serial=<serial>,id=<id[optional]>
+ */
+
+#include <hw/block/block.h>
+#include <hw/hw.h>
+#include <hw/pci/msix.h>
+#include <hw/pci/pci.h>
+#include "sysemu/sysemu.h"
+#include "qapi/visitor.h"
+#include "sysemu/block-backend.h"
+
+#include "nvme.h"
+
+static void nvme_process_sq(void *opaque);
+
+static int nvme_check_sqid(NvmeCtrl *n, uint16_t sqid)
+{
+ return sqid < n->num_queues && n->sq[sqid] != NULL ? 0 : -1;
+}
+
+static int nvme_check_cqid(NvmeCtrl *n, uint16_t cqid)
+{
+ return cqid < n->num_queues && n->cq[cqid] != NULL ? 0 : -1;
+}
+
+static void nvme_inc_cq_tail(NvmeCQueue *cq)
+{
+ cq->tail++;
+ if (cq->tail >= cq->size) {
+ cq->tail = 0;
+ cq->phase = !cq->phase;
+ }
+}
+
+static void nvme_inc_sq_head(NvmeSQueue *sq)
+{
+ sq->head = (sq->head + 1) % sq->size;
+}
+
+static uint8_t nvme_cq_full(NvmeCQueue *cq)
+{
+ return (cq->tail + 1) % cq->size == cq->head;
+}
+
+static uint8_t nvme_sq_empty(NvmeSQueue *sq)
+{
+ return sq->head == sq->tail;
+}
+
+static void nvme_isr_notify(NvmeCtrl *n, NvmeCQueue *cq)
+{
+ if (cq->irq_enabled) {
+ if (msix_enabled(&(n->parent_obj))) {
+ msix_notify(&(n->parent_obj), cq->vector);
+ } else {
+ pci_irq_pulse(&n->parent_obj);
+ }
+ }
+}
+
+static uint16_t nvme_map_prp(QEMUSGList *qsg, uint64_t prp1, uint64_t prp2,
+ uint32_t len, NvmeCtrl *n)
+{
+ hwaddr trans_len = n->page_size - (prp1 % n->page_size);
+ trans_len = MIN(len, trans_len);
+ int num_prps = (len >> n->page_bits) + 1;
+
+ if (!prp1) {
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+
+ pci_dma_sglist_init(qsg, &n->parent_obj, num_prps);
+ qemu_sglist_add(qsg, prp1, trans_len);
+ len -= trans_len;
+ if (len) {
+ if (!prp2) {
+ goto unmap;
+ }
+ if (len > n->page_size) {
+ uint64_t prp_list[n->max_prp_ents];
+ uint32_t nents, prp_trans;
+ int i = 0;
+
+ nents = (len + n->page_size - 1) >> n->page_bits;
+ prp_trans = MIN(n->max_prp_ents, nents) * sizeof(uint64_t);
+ pci_dma_read(&n->parent_obj, prp2, (void *)prp_list, prp_trans);
+ while (len != 0) {
+ uint64_t prp_ent = le64_to_cpu(prp_list[i]);
+
+ if (i == n->max_prp_ents - 1 && len > n->page_size) {
+ if (!prp_ent || prp_ent & (n->page_size - 1)) {
+ goto unmap;
+ }
+
+ i = 0;
+ nents = (len + n->page_size - 1) >> n->page_bits;
+ prp_trans = MIN(n->max_prp_ents, nents) * sizeof(uint64_t);
+ pci_dma_read(&n->parent_obj, prp_ent, (void *)prp_list,
+ prp_trans);
+ prp_ent = le64_to_cpu(prp_list[i]);
+ }
+
+ if (!prp_ent || prp_ent & (n->page_size - 1)) {
+ goto unmap;
+ }
+
+ trans_len = MIN(len, n->page_size);
+ qemu_sglist_add(qsg, prp_ent, trans_len);
+ len -= trans_len;
+ i++;
+ }
+ } else {
+ if (prp2 & (n->page_size - 1)) {
+ goto unmap;
+ }
+ qemu_sglist_add(qsg, prp2, len);
+ }
+ }
+ return NVME_SUCCESS;
+
+ unmap:
+ qemu_sglist_destroy(qsg);
+ return NVME_INVALID_FIELD | NVME_DNR;
+}
+
+static uint16_t nvme_dma_read_prp(NvmeCtrl *n, uint8_t *ptr, uint32_t len,
+ uint64_t prp1, uint64_t prp2)
+{
+ QEMUSGList qsg;
+
+ if (nvme_map_prp(&qsg, prp1, prp2, len, n)) {
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+ if (dma_buf_read(ptr, len, &qsg)) {
+ qemu_sglist_destroy(&qsg);
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+ qemu_sglist_destroy(&qsg);
+ return NVME_SUCCESS;
+}
+
+static void nvme_post_cqes(void *opaque)
+{
+ NvmeCQueue *cq = opaque;
+ NvmeCtrl *n = cq->ctrl;
+ NvmeRequest *req, *next;
+
+ QTAILQ_FOREACH_SAFE(req, &cq->req_list, entry, next) {
+ NvmeSQueue *sq;
+ hwaddr addr;
+
+ if (nvme_cq_full(cq)) {
+ break;
+ }
+
+ QTAILQ_REMOVE(&cq->req_list, req, entry);
+ sq = req->sq;
+ req->cqe.status = cpu_to_le16((req->status << 1) | cq->phase);
+ req->cqe.sq_id = cpu_to_le16(sq->sqid);
+ req->cqe.sq_head = cpu_to_le16(sq->head);
+ addr = cq->dma_addr + cq->tail * n->cqe_size;
+ nvme_inc_cq_tail(cq);
+ pci_dma_write(&n->parent_obj, addr, (void *)&req->cqe,
+ sizeof(req->cqe));
+ QTAILQ_INSERT_TAIL(&sq->req_list, req, entry);
+ }
+ nvme_isr_notify(n, cq);
+}
+
+static void nvme_enqueue_req_completion(NvmeCQueue *cq, NvmeRequest *req)
+{
+ assert(cq->cqid == req->sq->cqid);
+ QTAILQ_REMOVE(&req->sq->out_req_list, req, entry);
+ QTAILQ_INSERT_TAIL(&cq->req_list, req, entry);
+ timer_mod(cq->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 500);
+}
+
+static void nvme_rw_cb(void *opaque, int ret)
+{
+ NvmeRequest *req = opaque;
+ NvmeSQueue *sq = req->sq;
+ NvmeCtrl *n = sq->ctrl;
+ NvmeCQueue *cq = n->cq[sq->cqid];
+
+ block_acct_done(blk_get_stats(n->conf.blk), &req->acct);
+ if (!ret) {
+ req->status = NVME_SUCCESS;
+ } else {
+ req->status = NVME_INTERNAL_DEV_ERROR;
+ }
+ if (req->has_sg) {
+ qemu_sglist_destroy(&req->qsg);
+ }
+ nvme_enqueue_req_completion(cq, req);
+}
+
+static uint16_t nvme_flush(NvmeCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd,
+ NvmeRequest *req)
+{
+ req->has_sg = false;
+ block_acct_start(blk_get_stats(n->conf.blk), &req->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ req->aiocb = blk_aio_flush(n->conf.blk, nvme_rw_cb, req);
+
+ return NVME_NO_COMPLETE;
+}
+
+static uint16_t nvme_rw(NvmeCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd,
+ NvmeRequest *req)
+{
+ NvmeRwCmd *rw = (NvmeRwCmd *)cmd;
+ uint32_t nlb = le32_to_cpu(rw->nlb) + 1;
+ uint64_t slba = le64_to_cpu(rw->slba);
+ uint64_t prp1 = le64_to_cpu(rw->prp1);
+ uint64_t prp2 = le64_to_cpu(rw->prp2);
+
+ uint8_t lba_index = NVME_ID_NS_FLBAS_INDEX(ns->id_ns.flbas);
+ uint8_t data_shift = ns->id_ns.lbaf[lba_index].ds;
+ uint64_t data_size = (uint64_t)nlb << data_shift;
+ uint64_t aio_slba = slba << (data_shift - BDRV_SECTOR_BITS);
+ int is_write = rw->opcode == NVME_CMD_WRITE ? 1 : 0;
+
+ if ((slba + nlb) > ns->id_ns.nsze) {
+ return NVME_LBA_RANGE | NVME_DNR;
+ }
+ if (nvme_map_prp(&req->qsg, prp1, prp2, data_size, n)) {
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+ assert((nlb << data_shift) == req->qsg.size);
+
+ req->has_sg = true;
+ dma_acct_start(n->conf.blk, &req->acct, &req->qsg,
+ is_write ? BLOCK_ACCT_WRITE : BLOCK_ACCT_READ);
+ req->aiocb = is_write ?
+ dma_blk_write(n->conf.blk, &req->qsg, aio_slba, nvme_rw_cb, req) :
+ dma_blk_read(n->conf.blk, &req->qsg, aio_slba, nvme_rw_cb, req);
+
+ return NVME_NO_COMPLETE;
+}
+
+static uint16_t nvme_io_cmd(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req)
+{
+ NvmeNamespace *ns;
+ uint32_t nsid = le32_to_cpu(cmd->nsid);
+
+ if (nsid == 0 || nsid > n->num_namespaces) {
+ return NVME_INVALID_NSID | NVME_DNR;
+ }
+
+ ns = &n->namespaces[nsid - 1];
+ switch (cmd->opcode) {
+ case NVME_CMD_FLUSH:
+ return nvme_flush(n, ns, cmd, req);
+ case NVME_CMD_WRITE:
+ case NVME_CMD_READ:
+ return nvme_rw(n, ns, cmd, req);
+ default:
+ return NVME_INVALID_OPCODE | NVME_DNR;
+ }
+}
+
+static void nvme_free_sq(NvmeSQueue *sq, NvmeCtrl *n)
+{
+ n->sq[sq->sqid] = NULL;
+ timer_del(sq->timer);
+ timer_free(sq->timer);
+ g_free(sq->io_req);
+ if (sq->sqid) {
+ g_free(sq);
+ }
+}
+
+static uint16_t nvme_del_sq(NvmeCtrl *n, NvmeCmd *cmd)
+{
+ NvmeDeleteQ *c = (NvmeDeleteQ *)cmd;
+ NvmeRequest *req, *next;
+ NvmeSQueue *sq;
+ NvmeCQueue *cq;
+ uint16_t qid = le16_to_cpu(c->qid);
+
+ if (!qid || nvme_check_sqid(n, qid)) {
+ return NVME_INVALID_QID | NVME_DNR;
+ }
+
+ sq = n->sq[qid];
+ while (!QTAILQ_EMPTY(&sq->out_req_list)) {
+ req = QTAILQ_FIRST(&sq->out_req_list);
+ assert(req->aiocb);
+ blk_aio_cancel(req->aiocb);
+ }
+ if (!nvme_check_cqid(n, sq->cqid)) {
+ cq = n->cq[sq->cqid];
+ QTAILQ_REMOVE(&cq->sq_list, sq, entry);
+
+ nvme_post_cqes(cq);
+ QTAILQ_FOREACH_SAFE(req, &cq->req_list, entry, next) {
+ if (req->sq == sq) {
+ QTAILQ_REMOVE(&cq->req_list, req, entry);
+ QTAILQ_INSERT_TAIL(&sq->req_list, req, entry);
+ }
+ }
+ }
+
+ nvme_free_sq(sq, n);
+ return NVME_SUCCESS;
+}
+
+static void nvme_init_sq(NvmeSQueue *sq, NvmeCtrl *n, uint64_t dma_addr,
+ uint16_t sqid, uint16_t cqid, uint16_t size)
+{
+ int i;
+ NvmeCQueue *cq;
+
+ sq->ctrl = n;
+ sq->dma_addr = dma_addr;
+ sq->sqid = sqid;
+ sq->size = size;
+ sq->cqid = cqid;
+ sq->head = sq->tail = 0;
+ sq->io_req = g_new(NvmeRequest, sq->size);
+
+ QTAILQ_INIT(&sq->req_list);
+ QTAILQ_INIT(&sq->out_req_list);
+ for (i = 0; i < sq->size; i++) {
+ sq->io_req[i].sq = sq;
+ QTAILQ_INSERT_TAIL(&(sq->req_list), &sq->io_req[i], entry);
+ }
+ sq->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, nvme_process_sq, sq);
+
+ assert(n->cq[cqid]);
+ cq = n->cq[cqid];
+ QTAILQ_INSERT_TAIL(&(cq->sq_list), sq, entry);
+ n->sq[sqid] = sq;
+}
+
+static uint16_t nvme_create_sq(NvmeCtrl *n, NvmeCmd *cmd)
+{
+ NvmeSQueue *sq;
+ NvmeCreateSq *c = (NvmeCreateSq *)cmd;
+
+ uint16_t cqid = le16_to_cpu(c->cqid);
+ uint16_t sqid = le16_to_cpu(c->sqid);
+ uint16_t qsize = le16_to_cpu(c->qsize);
+ uint16_t qflags = le16_to_cpu(c->sq_flags);
+ uint64_t prp1 = le64_to_cpu(c->prp1);
+
+ if (!cqid || nvme_check_cqid(n, cqid)) {
+ return NVME_INVALID_CQID | NVME_DNR;
+ }
+ if (!sqid || (sqid && !nvme_check_sqid(n, sqid))) {
+ return NVME_INVALID_QID | NVME_DNR;
+ }
+ if (!qsize || qsize > NVME_CAP_MQES(n->bar.cap)) {
+ return NVME_MAX_QSIZE_EXCEEDED | NVME_DNR;
+ }
+ if (!prp1 || prp1 & (n->page_size - 1)) {
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+ if (!(NVME_SQ_FLAGS_PC(qflags))) {
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+ sq = g_malloc0(sizeof(*sq));
+ nvme_init_sq(sq, n, prp1, sqid, cqid, qsize + 1);
+ return NVME_SUCCESS;
+}
+
+static void nvme_free_cq(NvmeCQueue *cq, NvmeCtrl *n)
+{
+ n->cq[cq->cqid] = NULL;
+ timer_del(cq->timer);
+ timer_free(cq->timer);
+ msix_vector_unuse(&n->parent_obj, cq->vector);
+ if (cq->cqid) {
+ g_free(cq);
+ }
+}
+
+static uint16_t nvme_del_cq(NvmeCtrl *n, NvmeCmd *cmd)
+{
+ NvmeDeleteQ *c = (NvmeDeleteQ *)cmd;
+ NvmeCQueue *cq;
+ uint16_t qid = le16_to_cpu(c->qid);
+
+ if (!qid || nvme_check_cqid(n, qid)) {
+ return NVME_INVALID_CQID | NVME_DNR;
+ }
+
+ cq = n->cq[qid];
+ if (!QTAILQ_EMPTY(&cq->sq_list)) {
+ return NVME_INVALID_QUEUE_DEL;
+ }
+ nvme_free_cq(cq, n);
+ return NVME_SUCCESS;
+}
+
+static void nvme_init_cq(NvmeCQueue *cq, NvmeCtrl *n, uint64_t dma_addr,
+ uint16_t cqid, uint16_t vector, uint16_t size, uint16_t irq_enabled)
+{
+ cq->ctrl = n;
+ cq->cqid = cqid;
+ cq->size = size;
+ cq->dma_addr = dma_addr;
+ cq->phase = 1;
+ cq->irq_enabled = irq_enabled;
+ cq->vector = vector;
+ cq->head = cq->tail = 0;
+ QTAILQ_INIT(&cq->req_list);
+ QTAILQ_INIT(&cq->sq_list);
+ msix_vector_use(&n->parent_obj, cq->vector);
+ n->cq[cqid] = cq;
+ cq->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, nvme_post_cqes, cq);
+}
+
+static uint16_t nvme_create_cq(NvmeCtrl *n, NvmeCmd *cmd)
+{
+ NvmeCQueue *cq;
+ NvmeCreateCq *c = (NvmeCreateCq *)cmd;
+ uint16_t cqid = le16_to_cpu(c->cqid);
+ uint16_t vector = le16_to_cpu(c->irq_vector);
+ uint16_t qsize = le16_to_cpu(c->qsize);
+ uint16_t qflags = le16_to_cpu(c->cq_flags);
+ uint64_t prp1 = le64_to_cpu(c->prp1);
+
+ if (!cqid || (cqid && !nvme_check_cqid(n, cqid))) {
+ return NVME_INVALID_CQID | NVME_DNR;
+ }
+ if (!qsize || qsize > NVME_CAP_MQES(n->bar.cap)) {
+ return NVME_MAX_QSIZE_EXCEEDED | NVME_DNR;
+ }
+ if (!prp1) {
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+ if (vector > n->num_queues) {
+ return NVME_INVALID_IRQ_VECTOR | NVME_DNR;
+ }
+ if (!(NVME_CQ_FLAGS_PC(qflags))) {
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+
+ cq = g_malloc0(sizeof(*cq));
+ nvme_init_cq(cq, n, prp1, cqid, vector, qsize + 1,
+ NVME_CQ_FLAGS_IEN(qflags));
+ return NVME_SUCCESS;
+}
+
+static uint16_t nvme_identify(NvmeCtrl *n, NvmeCmd *cmd)
+{
+ NvmeNamespace *ns;
+ NvmeIdentify *c = (NvmeIdentify *)cmd;
+ uint32_t cns = le32_to_cpu(c->cns);
+ uint32_t nsid = le32_to_cpu(c->nsid);
+ uint64_t prp1 = le64_to_cpu(c->prp1);
+ uint64_t prp2 = le64_to_cpu(c->prp2);
+
+ if (cns) {
+ return nvme_dma_read_prp(n, (uint8_t *)&n->id_ctrl, sizeof(n->id_ctrl),
+ prp1, prp2);
+ }
+ if (nsid == 0 || nsid > n->num_namespaces) {
+ return NVME_INVALID_NSID | NVME_DNR;
+ }
+
+ ns = &n->namespaces[nsid - 1];
+ return nvme_dma_read_prp(n, (uint8_t *)&ns->id_ns, sizeof(ns->id_ns),
+ prp1, prp2);
+}
+
+static uint16_t nvme_get_feature(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req)
+{
+ uint32_t dw10 = le32_to_cpu(cmd->cdw10);
+ uint32_t result;
+
+ switch (dw10) {
+ case NVME_VOLATILE_WRITE_CACHE:
+ result = blk_enable_write_cache(n->conf.blk);
+ break;
+ case NVME_NUMBER_OF_QUEUES:
+ result = cpu_to_le32((n->num_queues - 1) | ((n->num_queues - 1) << 16));
+ break;
+ default:
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+
+ req->cqe.result = result;
+ return NVME_SUCCESS;
+}
+
+static uint16_t nvme_set_feature(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req)
+{
+ uint32_t dw10 = le32_to_cpu(cmd->cdw10);
+ uint32_t dw11 = le32_to_cpu(cmd->cdw11);
+
+ switch (dw10) {
+ case NVME_VOLATILE_WRITE_CACHE:
+ blk_set_enable_write_cache(n->conf.blk, dw11 & 1);
+ break;
+ case NVME_NUMBER_OF_QUEUES:
+ req->cqe.result =
+ cpu_to_le32((n->num_queues - 1) | ((n->num_queues - 1) << 16));
+ break;
+ default:
+ return NVME_INVALID_FIELD | NVME_DNR;
+ }
+ return NVME_SUCCESS;
+}
+
+static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req)
+{
+ switch (cmd->opcode) {
+ case NVME_ADM_CMD_DELETE_SQ:
+ return nvme_del_sq(n, cmd);
+ case NVME_ADM_CMD_CREATE_SQ:
+ return nvme_create_sq(n, cmd);
+ case NVME_ADM_CMD_DELETE_CQ:
+ return nvme_del_cq(n, cmd);
+ case NVME_ADM_CMD_CREATE_CQ:
+ return nvme_create_cq(n, cmd);
+ case NVME_ADM_CMD_IDENTIFY:
+ return nvme_identify(n, cmd);
+ case NVME_ADM_CMD_SET_FEATURES:
+ return nvme_set_feature(n, cmd, req);
+ case NVME_ADM_CMD_GET_FEATURES:
+ return nvme_get_feature(n, cmd, req);
+ default:
+ return NVME_INVALID_OPCODE | NVME_DNR;
+ }
+}
+
+static void nvme_process_sq(void *opaque)
+{
+ NvmeSQueue *sq = opaque;
+ NvmeCtrl *n = sq->ctrl;
+ NvmeCQueue *cq = n->cq[sq->cqid];
+
+ uint16_t status;
+ hwaddr addr;
+ NvmeCmd cmd;
+ NvmeRequest *req;
+
+ while (!(nvme_sq_empty(sq) || QTAILQ_EMPTY(&sq->req_list))) {
+ addr = sq->dma_addr + sq->head * n->sqe_size;
+ pci_dma_read(&n->parent_obj, addr, (void *)&cmd, sizeof(cmd));
+ nvme_inc_sq_head(sq);
+
+ req = QTAILQ_FIRST(&sq->req_list);
+ QTAILQ_REMOVE(&sq->req_list, req, entry);
+ QTAILQ_INSERT_TAIL(&sq->out_req_list, req, entry);
+ memset(&req->cqe, 0, sizeof(req->cqe));
+ req->cqe.cid = cmd.cid;
+
+ status = sq->sqid ? nvme_io_cmd(n, &cmd, req) :
+ nvme_admin_cmd(n, &cmd, req);
+ if (status != NVME_NO_COMPLETE) {
+ req->status = status;
+ nvme_enqueue_req_completion(cq, req);
+ }
+ }
+}
+
+static void nvme_clear_ctrl(NvmeCtrl *n)
+{
+ int i;
+
+ for (i = 0; i < n->num_queues; i++) {
+ if (n->sq[i] != NULL) {
+ nvme_free_sq(n->sq[i], n);
+ }
+ }
+ for (i = 0; i < n->num_queues; i++) {
+ if (n->cq[i] != NULL) {
+ nvme_free_cq(n->cq[i], n);
+ }
+ }
+
+ blk_flush(n->conf.blk);
+ n->bar.cc = 0;
+}
+
+static int nvme_start_ctrl(NvmeCtrl *n)
+{
+ uint32_t page_bits = NVME_CC_MPS(n->bar.cc) + 12;
+ uint32_t page_size = 1 << page_bits;
+
+ if (n->cq[0] || n->sq[0] || !n->bar.asq || !n->bar.acq ||
+ n->bar.asq & (page_size - 1) || n->bar.acq & (page_size - 1) ||
+ NVME_CC_MPS(n->bar.cc) < NVME_CAP_MPSMIN(n->bar.cap) ||
+ NVME_CC_MPS(n->bar.cc) > NVME_CAP_MPSMAX(n->bar.cap) ||
+ NVME_CC_IOCQES(n->bar.cc) < NVME_CTRL_CQES_MIN(n->id_ctrl.cqes) ||
+ NVME_CC_IOCQES(n->bar.cc) > NVME_CTRL_CQES_MAX(n->id_ctrl.cqes) ||
+ NVME_CC_IOSQES(n->bar.cc) < NVME_CTRL_SQES_MIN(n->id_ctrl.sqes) ||
+ NVME_CC_IOSQES(n->bar.cc) > NVME_CTRL_SQES_MAX(n->id_ctrl.sqes) ||
+ !NVME_AQA_ASQS(n->bar.aqa) || !NVME_AQA_ACQS(n->bar.aqa)) {
+ return -1;
+ }
+
+ n->page_bits = page_bits;
+ n->page_size = page_size;
+ n->max_prp_ents = n->page_size / sizeof(uint64_t);
+ n->cqe_size = 1 << NVME_CC_IOCQES(n->bar.cc);
+ n->sqe_size = 1 << NVME_CC_IOSQES(n->bar.cc);
+ nvme_init_cq(&n->admin_cq, n, n->bar.acq, 0, 0,
+ NVME_AQA_ACQS(n->bar.aqa) + 1, 1);
+ nvme_init_sq(&n->admin_sq, n, n->bar.asq, 0, 0,
+ NVME_AQA_ASQS(n->bar.aqa) + 1);
+
+ return 0;
+}
+
+static void nvme_write_bar(NvmeCtrl *n, hwaddr offset, uint64_t data,
+ unsigned size)
+{
+ switch (offset) {
+ case 0xc:
+ n->bar.intms |= data & 0xffffffff;
+ n->bar.intmc = n->bar.intms;
+ break;
+ case 0x10:
+ n->bar.intms &= ~(data & 0xffffffff);
+ n->bar.intmc = n->bar.intms;
+ break;
+ case 0x14:
+ /* Windows first sends data, then sends enable bit */
+ if (!NVME_CC_EN(data) && !NVME_CC_EN(n->bar.cc) &&
+ !NVME_CC_SHN(data) && !NVME_CC_SHN(n->bar.cc))
+ {
+ n->bar.cc = data;
+ }
+
+ if (NVME_CC_EN(data) && !NVME_CC_EN(n->bar.cc)) {
+ n->bar.cc = data;
+ if (nvme_start_ctrl(n)) {
+ n->bar.csts = NVME_CSTS_FAILED;
+ } else {
+ n->bar.csts = NVME_CSTS_READY;
+ }
+ } else if (!NVME_CC_EN(data) && NVME_CC_EN(n->bar.cc)) {
+ nvme_clear_ctrl(n);
+ n->bar.csts &= ~NVME_CSTS_READY;
+ }
+ if (NVME_CC_SHN(data) && !(NVME_CC_SHN(n->bar.cc))) {
+ nvme_clear_ctrl(n);
+ n->bar.cc = data;
+ n->bar.csts |= NVME_CSTS_SHST_COMPLETE;
+ } else if (!NVME_CC_SHN(data) && NVME_CC_SHN(n->bar.cc)) {
+ n->bar.csts &= ~NVME_CSTS_SHST_COMPLETE;
+ n->bar.cc = data;
+ }
+ break;
+ case 0x24:
+ n->bar.aqa = data & 0xffffffff;
+ break;
+ case 0x28:
+ n->bar.asq = data;
+ break;
+ case 0x2c:
+ n->bar.asq |= data << 32;
+ break;
+ case 0x30:
+ n->bar.acq = data;
+ break;
+ case 0x34:
+ n->bar.acq |= data << 32;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t nvme_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ NvmeCtrl *n = (NvmeCtrl *)opaque;
+ uint8_t *ptr = (uint8_t *)&n->bar;
+ uint64_t val = 0;
+
+ if (addr < sizeof(n->bar)) {
+ memcpy(&val, ptr + addr, size);
+ }
+ return val;
+}
+
+static void nvme_process_db(NvmeCtrl *n, hwaddr addr, int val)
+{
+ uint32_t qid;
+
+ if (addr & ((1 << 2) - 1)) {
+ return;
+ }
+
+ if (((addr - 0x1000) >> 2) & 1) {
+ uint16_t new_head = val & 0xffff;
+ int start_sqs;
+ NvmeCQueue *cq;
+
+ qid = (addr - (0x1000 + (1 << 2))) >> 3;
+ if (nvme_check_cqid(n, qid)) {
+ return;
+ }
+
+ cq = n->cq[qid];
+ if (new_head >= cq->size) {
+ return;
+ }
+
+ start_sqs = nvme_cq_full(cq) ? 1 : 0;
+ cq->head = new_head;
+ if (start_sqs) {
+ NvmeSQueue *sq;
+ QTAILQ_FOREACH(sq, &cq->sq_list, entry) {
+ timer_mod(sq->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 500);
+ }
+ timer_mod(cq->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 500);
+ }
+
+ if (cq->tail != cq->head) {
+ nvme_isr_notify(n, cq);
+ }
+ } else {
+ uint16_t new_tail = val & 0xffff;
+ NvmeSQueue *sq;
+
+ qid = (addr - 0x1000) >> 3;
+ if (nvme_check_sqid(n, qid)) {
+ return;
+ }
+
+ sq = n->sq[qid];
+ if (new_tail >= sq->size) {
+ return;
+ }
+
+ sq->tail = new_tail;
+ timer_mod(sq->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 500);
+ }
+}
+
+static void nvme_mmio_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned size)
+{
+ NvmeCtrl *n = (NvmeCtrl *)opaque;
+ if (addr < sizeof(n->bar)) {
+ nvme_write_bar(n, addr, data, size);
+ } else if (addr >= 0x1000) {
+ nvme_process_db(n, addr, data);
+ }
+}
+
+static const MemoryRegionOps nvme_mmio_ops = {
+ .read = nvme_mmio_read,
+ .write = nvme_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 2,
+ .max_access_size = 8,
+ },
+};
+
+static int nvme_init(PCIDevice *pci_dev)
+{
+ NvmeCtrl *n = NVME(pci_dev);
+ NvmeIdCtrl *id = &n->id_ctrl;
+
+ int i;
+ int64_t bs_size;
+ uint8_t *pci_conf;
+
+ if (!n->conf.blk) {
+ return -1;
+ }
+
+ bs_size = blk_getlength(n->conf.blk);
+ if (bs_size < 0) {
+ return -1;
+ }
+
+ blkconf_serial(&n->conf, &n->serial);
+ if (!n->serial) {
+ return -1;
+ }
+ blkconf_blocksizes(&n->conf);
+
+ pci_conf = pci_dev->config;
+ pci_conf[PCI_INTERRUPT_PIN] = 1;
+ pci_config_set_prog_interface(pci_dev->config, 0x2);
+ pci_config_set_class(pci_dev->config, PCI_CLASS_STORAGE_EXPRESS);
+ pcie_endpoint_cap_init(&n->parent_obj, 0x80);
+
+ n->num_namespaces = 1;
+ n->num_queues = 64;
+ n->reg_size = 1 << qemu_fls(0x1004 + 2 * (n->num_queues + 1) * 4);
+ n->ns_size = bs_size / (uint64_t)n->num_namespaces;
+
+ n->namespaces = g_new0(NvmeNamespace, n->num_namespaces);
+ n->sq = g_new0(NvmeSQueue *, n->num_queues);
+ n->cq = g_new0(NvmeCQueue *, n->num_queues);
+
+ memory_region_init_io(&n->iomem, OBJECT(n), &nvme_mmio_ops, n,
+ "nvme", n->reg_size);
+ pci_register_bar(&n->parent_obj, 0,
+ PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64,
+ &n->iomem);
+ msix_init_exclusive_bar(&n->parent_obj, n->num_queues, 4);
+
+ id->vid = cpu_to_le16(pci_get_word(pci_conf + PCI_VENDOR_ID));
+ id->ssvid = cpu_to_le16(pci_get_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID));
+ strpadcpy((char *)id->mn, sizeof(id->mn), "QEMU NVMe Ctrl", ' ');
+ strpadcpy((char *)id->fr, sizeof(id->fr), "1.0", ' ');
+ strpadcpy((char *)id->sn, sizeof(id->sn), n->serial, ' ');
+ id->rab = 6;
+ id->ieee[0] = 0x00;
+ id->ieee[1] = 0x02;
+ id->ieee[2] = 0xb3;
+ id->oacs = cpu_to_le16(0);
+ id->frmw = 7 << 1;
+ id->lpa = 1 << 0;
+ id->sqes = (0x6 << 4) | 0x6;
+ id->cqes = (0x4 << 4) | 0x4;
+ id->nn = cpu_to_le32(n->num_namespaces);
+ id->psd[0].mp = cpu_to_le16(0x9c4);
+ id->psd[0].enlat = cpu_to_le32(0x10);
+ id->psd[0].exlat = cpu_to_le32(0x4);
+ if (blk_enable_write_cache(n->conf.blk)) {
+ id->vwc = 1;
+ }
+
+ n->bar.cap = 0;
+ NVME_CAP_SET_MQES(n->bar.cap, 0x7ff);
+ NVME_CAP_SET_CQR(n->bar.cap, 1);
+ NVME_CAP_SET_AMS(n->bar.cap, 1);
+ NVME_CAP_SET_TO(n->bar.cap, 0xf);
+ NVME_CAP_SET_CSS(n->bar.cap, 1);
+ NVME_CAP_SET_MPSMAX(n->bar.cap, 4);
+
+ n->bar.vs = 0x00010100;
+ n->bar.intmc = n->bar.intms = 0;
+
+ for (i = 0; i < n->num_namespaces; i++) {
+ NvmeNamespace *ns = &n->namespaces[i];
+ NvmeIdNs *id_ns = &ns->id_ns;
+ id_ns->nsfeat = 0;
+ id_ns->nlbaf = 0;
+ id_ns->flbas = 0;
+ id_ns->mc = 0;
+ id_ns->dpc = 0;
+ id_ns->dps = 0;
+ id_ns->lbaf[0].ds = BDRV_SECTOR_BITS;
+ id_ns->ncap = id_ns->nuse = id_ns->nsze =
+ cpu_to_le64(n->ns_size >>
+ id_ns->lbaf[NVME_ID_NS_FLBAS_INDEX(ns->id_ns.flbas)].ds);
+ }
+ return 0;
+}
+
+static void nvme_exit(PCIDevice *pci_dev)
+{
+ NvmeCtrl *n = NVME(pci_dev);
+
+ nvme_clear_ctrl(n);
+ g_free(n->namespaces);
+ g_free(n->cq);
+ g_free(n->sq);
+ msix_uninit_exclusive_bar(pci_dev);
+}
+
+static Property nvme_props[] = {
+ DEFINE_BLOCK_PROPERTIES(NvmeCtrl, conf),
+ DEFINE_PROP_STRING("serial", NvmeCtrl, serial),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription nvme_vmstate = {
+ .name = "nvme",
+ .unmigratable = 1,
+};
+
+static void nvme_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc);
+
+ pc->init = nvme_init;
+ pc->exit = nvme_exit;
+ pc->class_id = PCI_CLASS_STORAGE_EXPRESS;
+ pc->vendor_id = PCI_VENDOR_ID_INTEL;
+ pc->device_id = 0x5845;
+ pc->revision = 1;
+ pc->is_express = 1;
+
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->desc = "Non-Volatile Memory Express";
+ dc->props = nvme_props;
+ dc->vmsd = &nvme_vmstate;
+}
+
+static void nvme_get_bootindex(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ NvmeCtrl *s = NVME(obj);
+
+ visit_type_int32(v, &s->conf.bootindex, name, errp);
+}
+
+static void nvme_set_bootindex(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ NvmeCtrl *s = NVME(obj);
+ int32_t boot_index;
+ Error *local_err = NULL;
+
+ visit_type_int32(v, &boot_index, name, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ /* check whether bootindex is present in fw_boot_order list */
+ check_boot_index(boot_index, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ /* change bootindex to a new one */
+ s->conf.bootindex = boot_index;
+
+out:
+ if (local_err) {
+ error_propagate(errp, local_err);
+ }
+}
+
+static void nvme_instance_init(Object *obj)
+{
+ object_property_add(obj, "bootindex", "int32",
+ nvme_get_bootindex,
+ nvme_set_bootindex, NULL, NULL, NULL);
+ object_property_set_int(obj, -1, "bootindex", NULL);
+}
+
+static const TypeInfo nvme_info = {
+ .name = "nvme",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(NvmeCtrl),
+ .class_init = nvme_class_init,
+ .instance_init = nvme_instance_init,
+};
+
+static void nvme_register_types(void)
+{
+ type_register_static(&nvme_info);
+}
+
+type_init(nvme_register_types)
diff --git a/hw/block/nvme.h b/hw/block/nvme.h
new file mode 100644
index 00000000..bf3a3cca
--- /dev/null
+++ b/hw/block/nvme.h
@@ -0,0 +1,712 @@
+#ifndef HW_NVME_H
+#define HW_NVME_H
+
+typedef struct NvmeBar {
+ uint64_t cap;
+ uint32_t vs;
+ uint32_t intms;
+ uint32_t intmc;
+ uint32_t cc;
+ uint32_t rsvd1;
+ uint32_t csts;
+ uint32_t nssrc;
+ uint32_t aqa;
+ uint64_t asq;
+ uint64_t acq;
+} NvmeBar;
+
+enum NvmeCapShift {
+ CAP_MQES_SHIFT = 0,
+ CAP_CQR_SHIFT = 16,
+ CAP_AMS_SHIFT = 17,
+ CAP_TO_SHIFT = 24,
+ CAP_DSTRD_SHIFT = 32,
+ CAP_NSSRS_SHIFT = 33,
+ CAP_CSS_SHIFT = 37,
+ CAP_MPSMIN_SHIFT = 48,
+ CAP_MPSMAX_SHIFT = 52,
+};
+
+enum NvmeCapMask {
+ CAP_MQES_MASK = 0xffff,
+ CAP_CQR_MASK = 0x1,
+ CAP_AMS_MASK = 0x3,
+ CAP_TO_MASK = 0xff,
+ CAP_DSTRD_MASK = 0xf,
+ CAP_NSSRS_MASK = 0x1,
+ CAP_CSS_MASK = 0xff,
+ CAP_MPSMIN_MASK = 0xf,
+ CAP_MPSMAX_MASK = 0xf,
+};
+
+#define NVME_CAP_MQES(cap) (((cap) >> CAP_MQES_SHIFT) & CAP_MQES_MASK)
+#define NVME_CAP_CQR(cap) (((cap) >> CAP_CQR_SHIFT) & CAP_CQR_MASK)
+#define NVME_CAP_AMS(cap) (((cap) >> CAP_AMS_SHIFT) & CAP_AMS_MASK)
+#define NVME_CAP_TO(cap) (((cap) >> CAP_TO_SHIFT) & CAP_TO_MASK)
+#define NVME_CAP_DSTRD(cap) (((cap) >> CAP_DSTRD_SHIFT) & CAP_DSTRD_MASK)
+#define NVME_CAP_NSSRS(cap) (((cap) >> CAP_NSSRS_SHIFT) & CAP_NSSRS_MASK)
+#define NVME_CAP_CSS(cap) (((cap) >> CAP_CSS_SHIFT) & CAP_CSS_MASK)
+#define NVME_CAP_MPSMIN(cap)(((cap) >> CAP_MPSMIN_SHIFT) & CAP_MPSMIN_MASK)
+#define NVME_CAP_MPSMAX(cap)(((cap) >> CAP_MPSMAX_SHIFT) & CAP_MPSMAX_MASK)
+
+#define NVME_CAP_SET_MQES(cap, val) (cap |= (uint64_t)(val & CAP_MQES_MASK) \
+ << CAP_MQES_SHIFT)
+#define NVME_CAP_SET_CQR(cap, val) (cap |= (uint64_t)(val & CAP_CQR_MASK) \
+ << CAP_CQR_SHIFT)
+#define NVME_CAP_SET_AMS(cap, val) (cap |= (uint64_t)(val & CAP_AMS_MASK) \
+ << CAP_AMS_SHIFT)
+#define NVME_CAP_SET_TO(cap, val) (cap |= (uint64_t)(val & CAP_TO_MASK) \
+ << CAP_TO_SHIFT)
+#define NVME_CAP_SET_DSTRD(cap, val) (cap |= (uint64_t)(val & CAP_DSTRD_MASK) \
+ << CAP_DSTRD_SHIFT)
+#define NVME_CAP_SET_NSSRS(cap, val) (cap |= (uint64_t)(val & CAP_NSSRS_MASK) \
+ << CAP_NSSRS_SHIFT)
+#define NVME_CAP_SET_CSS(cap, val) (cap |= (uint64_t)(val & CAP_CSS_MASK) \
+ << CAP_CSS_SHIFT)
+#define NVME_CAP_SET_MPSMIN(cap, val) (cap |= (uint64_t)(val & CAP_MPSMIN_MASK)\
+ << CAP_MPSMIN_SHIFT)
+#define NVME_CAP_SET_MPSMAX(cap, val) (cap |= (uint64_t)(val & CAP_MPSMAX_MASK)\
+ << CAP_MPSMAX_SHIFT)
+
+enum NvmeCcShift {
+ CC_EN_SHIFT = 0,
+ CC_CSS_SHIFT = 4,
+ CC_MPS_SHIFT = 7,
+ CC_AMS_SHIFT = 11,
+ CC_SHN_SHIFT = 14,
+ CC_IOSQES_SHIFT = 16,
+ CC_IOCQES_SHIFT = 20,
+};
+
+enum NvmeCcMask {
+ CC_EN_MASK = 0x1,
+ CC_CSS_MASK = 0x7,
+ CC_MPS_MASK = 0xf,
+ CC_AMS_MASK = 0x7,
+ CC_SHN_MASK = 0x3,
+ CC_IOSQES_MASK = 0xf,
+ CC_IOCQES_MASK = 0xf,
+};
+
+#define NVME_CC_EN(cc) ((cc >> CC_EN_SHIFT) & CC_EN_MASK)
+#define NVME_CC_CSS(cc) ((cc >> CC_CSS_SHIFT) & CC_CSS_MASK)
+#define NVME_CC_MPS(cc) ((cc >> CC_MPS_SHIFT) & CC_MPS_MASK)
+#define NVME_CC_AMS(cc) ((cc >> CC_AMS_SHIFT) & CC_AMS_MASK)
+#define NVME_CC_SHN(cc) ((cc >> CC_SHN_SHIFT) & CC_SHN_MASK)
+#define NVME_CC_IOSQES(cc) ((cc >> CC_IOSQES_SHIFT) & CC_IOSQES_MASK)
+#define NVME_CC_IOCQES(cc) ((cc >> CC_IOCQES_SHIFT) & CC_IOCQES_MASK)
+
+enum NvmeCstsShift {
+ CSTS_RDY_SHIFT = 0,
+ CSTS_CFS_SHIFT = 1,
+ CSTS_SHST_SHIFT = 2,
+ CSTS_NSSRO_SHIFT = 4,
+};
+
+enum NvmeCstsMask {
+ CSTS_RDY_MASK = 0x1,
+ CSTS_CFS_MASK = 0x1,
+ CSTS_SHST_MASK = 0x3,
+ CSTS_NSSRO_MASK = 0x1,
+};
+
+enum NvmeCsts {
+ NVME_CSTS_READY = 1 << CSTS_RDY_SHIFT,
+ NVME_CSTS_FAILED = 1 << CSTS_CFS_SHIFT,
+ NVME_CSTS_SHST_NORMAL = 0 << CSTS_SHST_SHIFT,
+ NVME_CSTS_SHST_PROGRESS = 1 << CSTS_SHST_SHIFT,
+ NVME_CSTS_SHST_COMPLETE = 2 << CSTS_SHST_SHIFT,
+ NVME_CSTS_NSSRO = 1 << CSTS_NSSRO_SHIFT,
+};
+
+#define NVME_CSTS_RDY(csts) ((csts >> CSTS_RDY_SHIFT) & CSTS_RDY_MASK)
+#define NVME_CSTS_CFS(csts) ((csts >> CSTS_CFS_SHIFT) & CSTS_CFS_MASK)
+#define NVME_CSTS_SHST(csts) ((csts >> CSTS_SHST_SHIFT) & CSTS_SHST_MASK)
+#define NVME_CSTS_NSSRO(csts) ((csts >> CSTS_NSSRO_SHIFT) & CSTS_NSSRO_MASK)
+
+enum NvmeAqaShift {
+ AQA_ASQS_SHIFT = 0,
+ AQA_ACQS_SHIFT = 16,
+};
+
+enum NvmeAqaMask {
+ AQA_ASQS_MASK = 0xfff,
+ AQA_ACQS_MASK = 0xfff,
+};
+
+#define NVME_AQA_ASQS(aqa) ((aqa >> AQA_ASQS_SHIFT) & AQA_ASQS_MASK)
+#define NVME_AQA_ACQS(aqa) ((aqa >> AQA_ACQS_SHIFT) & AQA_ACQS_MASK)
+
+typedef struct NvmeCmd {
+ uint8_t opcode;
+ uint8_t fuse;
+ uint16_t cid;
+ uint32_t nsid;
+ uint64_t res1;
+ uint64_t mptr;
+ uint64_t prp1;
+ uint64_t prp2;
+ uint32_t cdw10;
+ uint32_t cdw11;
+ uint32_t cdw12;
+ uint32_t cdw13;
+ uint32_t cdw14;
+ uint32_t cdw15;
+} NvmeCmd;
+
+enum NvmeAdminCommands {
+ NVME_ADM_CMD_DELETE_SQ = 0x00,
+ NVME_ADM_CMD_CREATE_SQ = 0x01,
+ NVME_ADM_CMD_GET_LOG_PAGE = 0x02,
+ NVME_ADM_CMD_DELETE_CQ = 0x04,
+ NVME_ADM_CMD_CREATE_CQ = 0x05,
+ NVME_ADM_CMD_IDENTIFY = 0x06,
+ NVME_ADM_CMD_ABORT = 0x08,
+ NVME_ADM_CMD_SET_FEATURES = 0x09,
+ NVME_ADM_CMD_GET_FEATURES = 0x0a,
+ NVME_ADM_CMD_ASYNC_EV_REQ = 0x0c,
+ NVME_ADM_CMD_ACTIVATE_FW = 0x10,
+ NVME_ADM_CMD_DOWNLOAD_FW = 0x11,
+ NVME_ADM_CMD_FORMAT_NVM = 0x80,
+ NVME_ADM_CMD_SECURITY_SEND = 0x81,
+ NVME_ADM_CMD_SECURITY_RECV = 0x82,
+};
+
+enum NvmeIoCommands {
+ NVME_CMD_FLUSH = 0x00,
+ NVME_CMD_WRITE = 0x01,
+ NVME_CMD_READ = 0x02,
+ NVME_CMD_WRITE_UNCOR = 0x04,
+ NVME_CMD_COMPARE = 0x05,
+ NVME_CMD_DSM = 0x09,
+};
+
+typedef struct NvmeDeleteQ {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t cid;
+ uint32_t rsvd1[9];
+ uint16_t qid;
+ uint16_t rsvd10;
+ uint32_t rsvd11[5];
+} NvmeDeleteQ;
+
+typedef struct NvmeCreateCq {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t cid;
+ uint32_t rsvd1[5];
+ uint64_t prp1;
+ uint64_t rsvd8;
+ uint16_t cqid;
+ uint16_t qsize;
+ uint16_t cq_flags;
+ uint16_t irq_vector;
+ uint32_t rsvd12[4];
+} NvmeCreateCq;
+
+#define NVME_CQ_FLAGS_PC(cq_flags) (cq_flags & 0x1)
+#define NVME_CQ_FLAGS_IEN(cq_flags) ((cq_flags >> 1) & 0x1)
+
+typedef struct NvmeCreateSq {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t cid;
+ uint32_t rsvd1[5];
+ uint64_t prp1;
+ uint64_t rsvd8;
+ uint16_t sqid;
+ uint16_t qsize;
+ uint16_t sq_flags;
+ uint16_t cqid;
+ uint32_t rsvd12[4];
+} NvmeCreateSq;
+
+#define NVME_SQ_FLAGS_PC(sq_flags) (sq_flags & 0x1)
+#define NVME_SQ_FLAGS_QPRIO(sq_flags) ((sq_flags >> 1) & 0x3)
+
+enum NvmeQueueFlags {
+ NVME_Q_PC = 1,
+ NVME_Q_PRIO_URGENT = 0,
+ NVME_Q_PRIO_HIGH = 1,
+ NVME_Q_PRIO_NORMAL = 2,
+ NVME_Q_PRIO_LOW = 3,
+};
+
+typedef struct NvmeIdentify {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t cid;
+ uint32_t nsid;
+ uint64_t rsvd2[2];
+ uint64_t prp1;
+ uint64_t prp2;
+ uint32_t cns;
+ uint32_t rsvd11[5];
+} NvmeIdentify;
+
+typedef struct NvmeRwCmd {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t cid;
+ uint32_t nsid;
+ uint64_t rsvd2;
+ uint64_t mptr;
+ uint64_t prp1;
+ uint64_t prp2;
+ uint64_t slba;
+ uint16_t nlb;
+ uint16_t control;
+ uint32_t dsmgmt;
+ uint32_t reftag;
+ uint16_t apptag;
+ uint16_t appmask;
+} NvmeRwCmd;
+
+enum {
+ NVME_RW_LR = 1 << 15,
+ NVME_RW_FUA = 1 << 14,
+ NVME_RW_DSM_FREQ_UNSPEC = 0,
+ NVME_RW_DSM_FREQ_TYPICAL = 1,
+ NVME_RW_DSM_FREQ_RARE = 2,
+ NVME_RW_DSM_FREQ_READS = 3,
+ NVME_RW_DSM_FREQ_WRITES = 4,
+ NVME_RW_DSM_FREQ_RW = 5,
+ NVME_RW_DSM_FREQ_ONCE = 6,
+ NVME_RW_DSM_FREQ_PREFETCH = 7,
+ NVME_RW_DSM_FREQ_TEMP = 8,
+ NVME_RW_DSM_LATENCY_NONE = 0 << 4,
+ NVME_RW_DSM_LATENCY_IDLE = 1 << 4,
+ NVME_RW_DSM_LATENCY_NORM = 2 << 4,
+ NVME_RW_DSM_LATENCY_LOW = 3 << 4,
+ NVME_RW_DSM_SEQ_REQ = 1 << 6,
+ NVME_RW_DSM_COMPRESSED = 1 << 7,
+ NVME_RW_PRINFO_PRACT = 1 << 13,
+ NVME_RW_PRINFO_PRCHK_GUARD = 1 << 12,
+ NVME_RW_PRINFO_PRCHK_APP = 1 << 11,
+ NVME_RW_PRINFO_PRCHK_REF = 1 << 10,
+};
+
+typedef struct NvmeDsmCmd {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t cid;
+ uint32_t nsid;
+ uint64_t rsvd2[2];
+ uint64_t prp1;
+ uint64_t prp2;
+ uint32_t nr;
+ uint32_t attributes;
+ uint32_t rsvd12[4];
+} NvmeDsmCmd;
+
+enum {
+ NVME_DSMGMT_IDR = 1 << 0,
+ NVME_DSMGMT_IDW = 1 << 1,
+ NVME_DSMGMT_AD = 1 << 2,
+};
+
+typedef struct NvmeDsmRange {
+ uint32_t cattr;
+ uint32_t nlb;
+ uint64_t slba;
+} NvmeDsmRange;
+
+enum NvmeAsyncEventRequest {
+ NVME_AER_TYPE_ERROR = 0,
+ NVME_AER_TYPE_SMART = 1,
+ NVME_AER_TYPE_IO_SPECIFIC = 6,
+ NVME_AER_TYPE_VENDOR_SPECIFIC = 7,
+ NVME_AER_INFO_ERR_INVALID_SQ = 0,
+ NVME_AER_INFO_ERR_INVALID_DB = 1,
+ NVME_AER_INFO_ERR_DIAG_FAIL = 2,
+ NVME_AER_INFO_ERR_PERS_INTERNAL_ERR = 3,
+ NVME_AER_INFO_ERR_TRANS_INTERNAL_ERR = 4,
+ NVME_AER_INFO_ERR_FW_IMG_LOAD_ERR = 5,
+ NVME_AER_INFO_SMART_RELIABILITY = 0,
+ NVME_AER_INFO_SMART_TEMP_THRESH = 1,
+ NVME_AER_INFO_SMART_SPARE_THRESH = 2,
+};
+
+typedef struct NvmeAerResult {
+ uint8_t event_type;
+ uint8_t event_info;
+ uint8_t log_page;
+ uint8_t resv;
+} NvmeAerResult;
+
+typedef struct NvmeCqe {
+ uint32_t result;
+ uint32_t rsvd;
+ uint16_t sq_head;
+ uint16_t sq_id;
+ uint16_t cid;
+ uint16_t status;
+} NvmeCqe;
+
+enum NvmeStatusCodes {
+ NVME_SUCCESS = 0x0000,
+ NVME_INVALID_OPCODE = 0x0001,
+ NVME_INVALID_FIELD = 0x0002,
+ NVME_CID_CONFLICT = 0x0003,
+ NVME_DATA_TRAS_ERROR = 0x0004,
+ NVME_POWER_LOSS_ABORT = 0x0005,
+ NVME_INTERNAL_DEV_ERROR = 0x0006,
+ NVME_CMD_ABORT_REQ = 0x0007,
+ NVME_CMD_ABORT_SQ_DEL = 0x0008,
+ NVME_CMD_ABORT_FAILED_FUSE = 0x0009,
+ NVME_CMD_ABORT_MISSING_FUSE = 0x000a,
+ NVME_INVALID_NSID = 0x000b,
+ NVME_CMD_SEQ_ERROR = 0x000c,
+ NVME_LBA_RANGE = 0x0080,
+ NVME_CAP_EXCEEDED = 0x0081,
+ NVME_NS_NOT_READY = 0x0082,
+ NVME_NS_RESV_CONFLICT = 0x0083,
+ NVME_INVALID_CQID = 0x0100,
+ NVME_INVALID_QID = 0x0101,
+ NVME_MAX_QSIZE_EXCEEDED = 0x0102,
+ NVME_ACL_EXCEEDED = 0x0103,
+ NVME_RESERVED = 0x0104,
+ NVME_AER_LIMIT_EXCEEDED = 0x0105,
+ NVME_INVALID_FW_SLOT = 0x0106,
+ NVME_INVALID_FW_IMAGE = 0x0107,
+ NVME_INVALID_IRQ_VECTOR = 0x0108,
+ NVME_INVALID_LOG_ID = 0x0109,
+ NVME_INVALID_FORMAT = 0x010a,
+ NVME_FW_REQ_RESET = 0x010b,
+ NVME_INVALID_QUEUE_DEL = 0x010c,
+ NVME_FID_NOT_SAVEABLE = 0x010d,
+ NVME_FID_NOT_NSID_SPEC = 0x010f,
+ NVME_FW_REQ_SUSYSTEM_RESET = 0x0110,
+ NVME_CONFLICTING_ATTRS = 0x0180,
+ NVME_INVALID_PROT_INFO = 0x0181,
+ NVME_WRITE_TO_RO = 0x0182,
+ NVME_WRITE_FAULT = 0x0280,
+ NVME_UNRECOVERED_READ = 0x0281,
+ NVME_E2E_GUARD_ERROR = 0x0282,
+ NVME_E2E_APP_ERROR = 0x0283,
+ NVME_E2E_REF_ERROR = 0x0284,
+ NVME_CMP_FAILURE = 0x0285,
+ NVME_ACCESS_DENIED = 0x0286,
+ NVME_MORE = 0x2000,
+ NVME_DNR = 0x4000,
+ NVME_NO_COMPLETE = 0xffff,
+};
+
+typedef struct NvmeFwSlotInfoLog {
+ uint8_t afi;
+ uint8_t reserved1[7];
+ uint8_t frs1[8];
+ uint8_t frs2[8];
+ uint8_t frs3[8];
+ uint8_t frs4[8];
+ uint8_t frs5[8];
+ uint8_t frs6[8];
+ uint8_t frs7[8];
+ uint8_t reserved2[448];
+} NvmeFwSlotInfoLog;
+
+typedef struct NvmeErrorLog {
+ uint64_t error_count;
+ uint16_t sqid;
+ uint16_t cid;
+ uint16_t status_field;
+ uint16_t param_error_location;
+ uint64_t lba;
+ uint32_t nsid;
+ uint8_t vs;
+ uint8_t resv[35];
+} NvmeErrorLog;
+
+typedef struct NvmeSmartLog {
+ uint8_t critical_warning;
+ uint8_t temperature[2];
+ uint8_t available_spare;
+ uint8_t available_spare_threshold;
+ uint8_t percentage_used;
+ uint8_t reserved1[26];
+ uint64_t data_units_read[2];
+ uint64_t data_units_written[2];
+ uint64_t host_read_commands[2];
+ uint64_t host_write_commands[2];
+ uint64_t controller_busy_time[2];
+ uint64_t power_cycles[2];
+ uint64_t power_on_hours[2];
+ uint64_t unsafe_shutdowns[2];
+ uint64_t media_errors[2];
+ uint64_t number_of_error_log_entries[2];
+ uint8_t reserved2[320];
+} NvmeSmartLog;
+
+enum NvmeSmartWarn {
+ NVME_SMART_SPARE = 1 << 0,
+ NVME_SMART_TEMPERATURE = 1 << 1,
+ NVME_SMART_RELIABILITY = 1 << 2,
+ NVME_SMART_MEDIA_READ_ONLY = 1 << 3,
+ NVME_SMART_FAILED_VOLATILE_MEDIA = 1 << 4,
+};
+
+enum LogIdentifier {
+ NVME_LOG_ERROR_INFO = 0x01,
+ NVME_LOG_SMART_INFO = 0x02,
+ NVME_LOG_FW_SLOT_INFO = 0x03,
+};
+
+typedef struct NvmePSD {
+ uint16_t mp;
+ uint16_t reserved;
+ uint32_t enlat;
+ uint32_t exlat;
+ uint8_t rrt;
+ uint8_t rrl;
+ uint8_t rwt;
+ uint8_t rwl;
+ uint8_t resv[16];
+} NvmePSD;
+
+typedef struct NvmeIdCtrl {
+ uint16_t vid;
+ uint16_t ssvid;
+ uint8_t sn[20];
+ uint8_t mn[40];
+ uint8_t fr[8];
+ uint8_t rab;
+ uint8_t ieee[3];
+ uint8_t cmic;
+ uint8_t mdts;
+ uint8_t rsvd255[178];
+ uint16_t oacs;
+ uint8_t acl;
+ uint8_t aerl;
+ uint8_t frmw;
+ uint8_t lpa;
+ uint8_t elpe;
+ uint8_t npss;
+ uint8_t rsvd511[248];
+ uint8_t sqes;
+ uint8_t cqes;
+ uint16_t rsvd515;
+ uint32_t nn;
+ uint16_t oncs;
+ uint16_t fuses;
+ uint8_t fna;
+ uint8_t vwc;
+ uint16_t awun;
+ uint16_t awupf;
+ uint8_t rsvd703[174];
+ uint8_t rsvd2047[1344];
+ NvmePSD psd[32];
+ uint8_t vs[1024];
+} NvmeIdCtrl;
+
+enum NvmeIdCtrlOacs {
+ NVME_OACS_SECURITY = 1 << 0,
+ NVME_OACS_FORMAT = 1 << 1,
+ NVME_OACS_FW = 1 << 2,
+};
+
+enum NvmeIdCtrlOncs {
+ NVME_ONCS_COMPARE = 1 << 0,
+ NVME_ONCS_WRITE_UNCORR = 1 << 1,
+ NVME_ONCS_DSM = 1 << 2,
+ NVME_ONCS_WRITE_ZEROS = 1 << 3,
+ NVME_ONCS_FEATURES = 1 << 4,
+ NVME_ONCS_RESRVATIONS = 1 << 5,
+};
+
+#define NVME_CTRL_SQES_MIN(sqes) ((sqes) & 0xf)
+#define NVME_CTRL_SQES_MAX(sqes) (((sqes) >> 4) & 0xf)
+#define NVME_CTRL_CQES_MIN(cqes) ((cqes) & 0xf)
+#define NVME_CTRL_CQES_MAX(cqes) (((cqes) >> 4) & 0xf)
+
+typedef struct NvmeFeatureVal {
+ uint32_t arbitration;
+ uint32_t power_mgmt;
+ uint32_t temp_thresh;
+ uint32_t err_rec;
+ uint32_t volatile_wc;
+ uint32_t num_queues;
+ uint32_t int_coalescing;
+ uint32_t *int_vector_config;
+ uint32_t write_atomicity;
+ uint32_t async_config;
+ uint32_t sw_prog_marker;
+} NvmeFeatureVal;
+
+#define NVME_ARB_AB(arb) (arb & 0x7)
+#define NVME_ARB_LPW(arb) ((arb >> 8) & 0xff)
+#define NVME_ARB_MPW(arb) ((arb >> 16) & 0xff)
+#define NVME_ARB_HPW(arb) ((arb >> 24) & 0xff)
+
+#define NVME_INTC_THR(intc) (intc & 0xff)
+#define NVME_INTC_TIME(intc) ((intc >> 8) & 0xff)
+
+enum NvmeFeatureIds {
+ NVME_ARBITRATION = 0x1,
+ NVME_POWER_MANAGEMENT = 0x2,
+ NVME_LBA_RANGE_TYPE = 0x3,
+ NVME_TEMPERATURE_THRESHOLD = 0x4,
+ NVME_ERROR_RECOVERY = 0x5,
+ NVME_VOLATILE_WRITE_CACHE = 0x6,
+ NVME_NUMBER_OF_QUEUES = 0x7,
+ NVME_INTERRUPT_COALESCING = 0x8,
+ NVME_INTERRUPT_VECTOR_CONF = 0x9,
+ NVME_WRITE_ATOMICITY = 0xa,
+ NVME_ASYNCHRONOUS_EVENT_CONF = 0xb,
+ NVME_SOFTWARE_PROGRESS_MARKER = 0x80
+};
+
+typedef struct NvmeRangeType {
+ uint8_t type;
+ uint8_t attributes;
+ uint8_t rsvd2[14];
+ uint64_t slba;
+ uint64_t nlb;
+ uint8_t guid[16];
+ uint8_t rsvd48[16];
+} NvmeRangeType;
+
+typedef struct NvmeLBAF {
+ uint16_t ms;
+ uint8_t ds;
+ uint8_t rp;
+} NvmeLBAF;
+
+typedef struct NvmeIdNs {
+ uint64_t nsze;
+ uint64_t ncap;
+ uint64_t nuse;
+ uint8_t nsfeat;
+ uint8_t nlbaf;
+ uint8_t flbas;
+ uint8_t mc;
+ uint8_t dpc;
+ uint8_t dps;
+ uint8_t res30[98];
+ NvmeLBAF lbaf[16];
+ uint8_t res192[192];
+ uint8_t vs[3712];
+} NvmeIdNs;
+
+#define NVME_ID_NS_NSFEAT_THIN(nsfeat) ((nsfeat & 0x1))
+#define NVME_ID_NS_FLBAS_EXTENDED(flbas) ((flbas >> 4) & 0x1)
+#define NVME_ID_NS_FLBAS_INDEX(flbas) ((flbas & 0xf))
+#define NVME_ID_NS_MC_SEPARATE(mc) ((mc >> 1) & 0x1)
+#define NVME_ID_NS_MC_EXTENDED(mc) ((mc & 0x1))
+#define NVME_ID_NS_DPC_LAST_EIGHT(dpc) ((dpc >> 4) & 0x1)
+#define NVME_ID_NS_DPC_FIRST_EIGHT(dpc) ((dpc >> 3) & 0x1)
+#define NVME_ID_NS_DPC_TYPE_3(dpc) ((dpc >> 2) & 0x1)
+#define NVME_ID_NS_DPC_TYPE_2(dpc) ((dpc >> 1) & 0x1)
+#define NVME_ID_NS_DPC_TYPE_1(dpc) ((dpc & 0x1))
+#define NVME_ID_NS_DPC_TYPE_MASK 0x7
+
+enum NvmeIdNsDps {
+ DPS_TYPE_NONE = 0,
+ DPS_TYPE_1 = 1,
+ DPS_TYPE_2 = 2,
+ DPS_TYPE_3 = 3,
+ DPS_TYPE_MASK = 0x7,
+ DPS_FIRST_EIGHT = 8,
+};
+
+static inline void _nvme_check_size(void)
+{
+ QEMU_BUILD_BUG_ON(sizeof(NvmeAerResult) != 4);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeCqe) != 16);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeDsmRange) != 16);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeCmd) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeDeleteQ) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeCreateCq) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeCreateSq) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeIdentify) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeRwCmd) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeDsmCmd) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeRangeType) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeErrorLog) != 64);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeFwSlotInfoLog) != 512);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeSmartLog) != 512);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeIdCtrl) != 4096);
+ QEMU_BUILD_BUG_ON(sizeof(NvmeIdNs) != 4096);
+}
+
+typedef struct NvmeAsyncEvent {
+ QSIMPLEQ_ENTRY(NvmeAsyncEvent) entry;
+ NvmeAerResult result;
+} NvmeAsyncEvent;
+
+typedef struct NvmeRequest {
+ struct NvmeSQueue *sq;
+ BlockAIOCB *aiocb;
+ uint16_t status;
+ bool has_sg;
+ NvmeCqe cqe;
+ BlockAcctCookie acct;
+ QEMUSGList qsg;
+ QTAILQ_ENTRY(NvmeRequest)entry;
+} NvmeRequest;
+
+typedef struct NvmeSQueue {
+ struct NvmeCtrl *ctrl;
+ uint16_t sqid;
+ uint16_t cqid;
+ uint32_t head;
+ uint32_t tail;
+ uint32_t size;
+ uint64_t dma_addr;
+ QEMUTimer *timer;
+ NvmeRequest *io_req;
+ QTAILQ_HEAD(sq_req_list, NvmeRequest) req_list;
+ QTAILQ_HEAD(out_req_list, NvmeRequest) out_req_list;
+ QTAILQ_ENTRY(NvmeSQueue) entry;
+} NvmeSQueue;
+
+typedef struct NvmeCQueue {
+ struct NvmeCtrl *ctrl;
+ uint8_t phase;
+ uint16_t cqid;
+ uint16_t irq_enabled;
+ uint32_t head;
+ uint32_t tail;
+ uint32_t vector;
+ uint32_t size;
+ uint64_t dma_addr;
+ QEMUTimer *timer;
+ QTAILQ_HEAD(sq_list, NvmeSQueue) sq_list;
+ QTAILQ_HEAD(cq_req_list, NvmeRequest) req_list;
+} NvmeCQueue;
+
+typedef struct NvmeNamespace {
+ NvmeIdNs id_ns;
+} NvmeNamespace;
+
+#define TYPE_NVME "nvme"
+#define NVME(obj) \
+ OBJECT_CHECK(NvmeCtrl, (obj), TYPE_NVME)
+
+typedef struct NvmeCtrl {
+ PCIDevice parent_obj;
+ MemoryRegion iomem;
+ NvmeBar bar;
+ BlockConf conf;
+
+ uint32_t page_size;
+ uint16_t page_bits;
+ uint16_t max_prp_ents;
+ uint16_t cqe_size;
+ uint16_t sqe_size;
+ uint32_t reg_size;
+ uint32_t num_namespaces;
+ uint32_t num_queues;
+ uint32_t max_q_ents;
+ uint64_t ns_size;
+
+ char *serial;
+ NvmeNamespace *namespaces;
+ NvmeSQueue **sq;
+ NvmeCQueue **cq;
+ NvmeSQueue admin_sq;
+ NvmeCQueue admin_cq;
+ NvmeIdCtrl id_ctrl;
+} NvmeCtrl;
+
+#endif /* HW_NVME_H */
diff --git a/hw/block/onenand.c b/hw/block/onenand.c
new file mode 100644
index 00000000..1b2c8937
--- /dev/null
+++ b/hw/block/onenand.c
@@ -0,0 +1,848 @@
+/*
+ * OneNAND flash memories emulation.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "hw/hw.h"
+#include "hw/block/flash.h"
+#include "hw/irq.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+#include "hw/sysbus.h"
+#include "qemu/error-report.h"
+
+/* 11 for 2kB-page OneNAND ("2nd generation") and 10 for 1kB-page chips */
+#define PAGE_SHIFT 11
+
+/* Fixed */
+#define BLOCK_SHIFT (PAGE_SHIFT + 6)
+
+#define TYPE_ONE_NAND "onenand"
+#define ONE_NAND(obj) OBJECT_CHECK(OneNANDState, (obj), TYPE_ONE_NAND)
+
+typedef struct OneNANDState {
+ SysBusDevice parent_obj;
+
+ struct {
+ uint16_t man;
+ uint16_t dev;
+ uint16_t ver;
+ } id;
+ int shift;
+ hwaddr base;
+ qemu_irq intr;
+ qemu_irq rdy;
+ BlockBackend *blk;
+ BlockBackend *blk_cur;
+ uint8_t *image;
+ uint8_t *otp;
+ uint8_t *current;
+ MemoryRegion ram;
+ MemoryRegion mapped_ram;
+ uint8_t current_direction;
+ uint8_t *boot[2];
+ uint8_t *data[2][2];
+ MemoryRegion iomem;
+ MemoryRegion container;
+ int cycle;
+ int otpmode;
+
+ uint16_t addr[8];
+ uint16_t unladdr[8];
+ int bufaddr;
+ int count;
+ uint16_t command;
+ uint16_t config[2];
+ uint16_t status;
+ uint16_t intstatus;
+ uint16_t wpstatus;
+
+ ECCState ecc;
+
+ int density_mask;
+ int secs;
+ int secs_cur;
+ int blocks;
+ uint8_t *blockwp;
+} OneNANDState;
+
+enum {
+ ONEN_BUF_BLOCK = 0,
+ ONEN_BUF_BLOCK2 = 1,
+ ONEN_BUF_DEST_BLOCK = 2,
+ ONEN_BUF_DEST_PAGE = 3,
+ ONEN_BUF_PAGE = 7,
+};
+
+enum {
+ ONEN_ERR_CMD = 1 << 10,
+ ONEN_ERR_ERASE = 1 << 11,
+ ONEN_ERR_PROG = 1 << 12,
+ ONEN_ERR_LOAD = 1 << 13,
+};
+
+enum {
+ ONEN_INT_RESET = 1 << 4,
+ ONEN_INT_ERASE = 1 << 5,
+ ONEN_INT_PROG = 1 << 6,
+ ONEN_INT_LOAD = 1 << 7,
+ ONEN_INT = 1 << 15,
+};
+
+enum {
+ ONEN_LOCK_LOCKTIGHTEN = 1 << 0,
+ ONEN_LOCK_LOCKED = 1 << 1,
+ ONEN_LOCK_UNLOCKED = 1 << 2,
+};
+
+static void onenand_mem_setup(OneNANDState *s)
+{
+ /* XXX: We should use IO_MEM_ROMD but we broke it earlier...
+ * Both 0x0000 ... 0x01ff and 0x8000 ... 0x800f can be used to
+ * write boot commands. Also take note of the BWPS bit. */
+ memory_region_init(&s->container, OBJECT(s), "onenand",
+ 0x10000 << s->shift);
+ memory_region_add_subregion(&s->container, 0, &s->iomem);
+ memory_region_init_alias(&s->mapped_ram, OBJECT(s), "onenand-mapped-ram",
+ &s->ram, 0x0200 << s->shift,
+ 0xbe00 << s->shift);
+ memory_region_add_subregion_overlap(&s->container,
+ 0x0200 << s->shift,
+ &s->mapped_ram,
+ 1);
+}
+
+static void onenand_intr_update(OneNANDState *s)
+{
+ qemu_set_irq(s->intr, ((s->intstatus >> 15) ^ (~s->config[0] >> 6)) & 1);
+}
+
+static void onenand_pre_save(void *opaque)
+{
+ OneNANDState *s = opaque;
+ if (s->current == s->otp) {
+ s->current_direction = 1;
+ } else if (s->current == s->image) {
+ s->current_direction = 2;
+ } else {
+ s->current_direction = 0;
+ }
+}
+
+static int onenand_post_load(void *opaque, int version_id)
+{
+ OneNANDState *s = opaque;
+ switch (s->current_direction) {
+ case 0:
+ break;
+ case 1:
+ s->current = s->otp;
+ break;
+ case 2:
+ s->current = s->image;
+ break;
+ default:
+ return -1;
+ }
+ onenand_intr_update(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_onenand = {
+ .name = "onenand",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = onenand_pre_save,
+ .post_load = onenand_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(current_direction, OneNANDState),
+ VMSTATE_INT32(cycle, OneNANDState),
+ VMSTATE_INT32(otpmode, OneNANDState),
+ VMSTATE_UINT16_ARRAY(addr, OneNANDState, 8),
+ VMSTATE_UINT16_ARRAY(unladdr, OneNANDState, 8),
+ VMSTATE_INT32(bufaddr, OneNANDState),
+ VMSTATE_INT32(count, OneNANDState),
+ VMSTATE_UINT16(command, OneNANDState),
+ VMSTATE_UINT16_ARRAY(config, OneNANDState, 2),
+ VMSTATE_UINT16(status, OneNANDState),
+ VMSTATE_UINT16(intstatus, OneNANDState),
+ VMSTATE_UINT16(wpstatus, OneNANDState),
+ VMSTATE_INT32(secs_cur, OneNANDState),
+ VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, blocks),
+ VMSTATE_UINT8(ecc.cp, OneNANDState),
+ VMSTATE_UINT16_ARRAY(ecc.lp, OneNANDState, 2),
+ VMSTATE_UINT16(ecc.count, OneNANDState),
+ VMSTATE_BUFFER_POINTER_UNSAFE(otp, OneNANDState, 0,
+ ((64 + 2) << PAGE_SHIFT)),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* Hot reset (Reset OneNAND command) or warm reset (RP pin low) */
+static void onenand_reset(OneNANDState *s, int cold)
+{
+ memset(&s->addr, 0, sizeof(s->addr));
+ s->command = 0;
+ s->count = 1;
+ s->bufaddr = 0;
+ s->config[0] = 0x40c0;
+ s->config[1] = 0x0000;
+ onenand_intr_update(s);
+ qemu_irq_raise(s->rdy);
+ s->status = 0x0000;
+ s->intstatus = cold ? 0x8080 : 0x8010;
+ s->unladdr[0] = 0;
+ s->unladdr[1] = 0;
+ s->wpstatus = 0x0002;
+ s->cycle = 0;
+ s->otpmode = 0;
+ s->blk_cur = s->blk;
+ s->current = s->image;
+ s->secs_cur = s->secs;
+
+ if (cold) {
+ /* Lock the whole flash */
+ memset(s->blockwp, ONEN_LOCK_LOCKED, s->blocks);
+
+ if (s->blk_cur && blk_read(s->blk_cur, 0, s->boot[0], 8) < 0) {
+ hw_error("%s: Loading the BootRAM failed.\n", __func__);
+ }
+ }
+}
+
+static void onenand_system_reset(DeviceState *dev)
+{
+ OneNANDState *s = ONE_NAND(dev);
+
+ onenand_reset(s, 1);
+}
+
+static inline int onenand_load_main(OneNANDState *s, int sec, int secn,
+ void *dest)
+{
+ if (s->blk_cur) {
+ return blk_read(s->blk_cur, sec, dest, secn) < 0;
+ } else if (sec + secn > s->secs_cur) {
+ return 1;
+ }
+
+ memcpy(dest, s->current + (sec << 9), secn << 9);
+
+ return 0;
+}
+
+static inline int onenand_prog_main(OneNANDState *s, int sec, int secn,
+ void *src)
+{
+ int result = 0;
+
+ if (secn > 0) {
+ uint32_t size = (uint32_t)secn * 512;
+ const uint8_t *sp = (const uint8_t *)src;
+ uint8_t *dp = 0;
+ if (s->blk_cur) {
+ dp = g_malloc(size);
+ if (!dp || blk_read(s->blk_cur, sec, dp, secn) < 0) {
+ result = 1;
+ }
+ } else {
+ if (sec + secn > s->secs_cur) {
+ result = 1;
+ } else {
+ dp = (uint8_t *)s->current + (sec << 9);
+ }
+ }
+ if (!result) {
+ uint32_t i;
+ for (i = 0; i < size; i++) {
+ dp[i] &= sp[i];
+ }
+ if (s->blk_cur) {
+ result = blk_write(s->blk_cur, sec, dp, secn) < 0;
+ }
+ }
+ if (dp && s->blk_cur) {
+ g_free(dp);
+ }
+ }
+
+ return result;
+}
+
+static inline int onenand_load_spare(OneNANDState *s, int sec, int secn,
+ void *dest)
+{
+ uint8_t buf[512];
+
+ if (s->blk_cur) {
+ if (blk_read(s->blk_cur, s->secs_cur + (sec >> 5), buf, 1) < 0) {
+ return 1;
+ }
+ memcpy(dest, buf + ((sec & 31) << 4), secn << 4);
+ } else if (sec + secn > s->secs_cur) {
+ return 1;
+ } else {
+ memcpy(dest, s->current + (s->secs_cur << 9) + (sec << 4), secn << 4);
+ }
+
+ return 0;
+}
+
+static inline int onenand_prog_spare(OneNANDState *s, int sec, int secn,
+ void *src)
+{
+ int result = 0;
+ if (secn > 0) {
+ const uint8_t *sp = (const uint8_t *)src;
+ uint8_t *dp = 0, *dpp = 0;
+ if (s->blk_cur) {
+ dp = g_malloc(512);
+ if (!dp
+ || blk_read(s->blk_cur, s->secs_cur + (sec >> 5), dp, 1) < 0) {
+ result = 1;
+ } else {
+ dpp = dp + ((sec & 31) << 4);
+ }
+ } else {
+ if (sec + secn > s->secs_cur) {
+ result = 1;
+ } else {
+ dpp = s->current + (s->secs_cur << 9) + (sec << 4);
+ }
+ }
+ if (!result) {
+ uint32_t i;
+ for (i = 0; i < (secn << 4); i++) {
+ dpp[i] &= sp[i];
+ }
+ if (s->blk_cur) {
+ result = blk_write(s->blk_cur, s->secs_cur + (sec >> 5),
+ dp, 1) < 0;
+ }
+ }
+ g_free(dp);
+ }
+ return result;
+}
+
+static inline int onenand_erase(OneNANDState *s, int sec, int num)
+{
+ uint8_t *blankbuf, *tmpbuf;
+
+ blankbuf = g_malloc(512);
+ tmpbuf = g_malloc(512);
+ memset(blankbuf, 0xff, 512);
+ for (; num > 0; num--, sec++) {
+ if (s->blk_cur) {
+ int erasesec = s->secs_cur + (sec >> 5);
+ if (blk_write(s->blk_cur, sec, blankbuf, 1) < 0) {
+ goto fail;
+ }
+ if (blk_read(s->blk_cur, erasesec, tmpbuf, 1) < 0) {
+ goto fail;
+ }
+ memcpy(tmpbuf + ((sec & 31) << 4), blankbuf, 1 << 4);
+ if (blk_write(s->blk_cur, erasesec, tmpbuf, 1) < 0) {
+ goto fail;
+ }
+ } else {
+ if (sec + 1 > s->secs_cur) {
+ goto fail;
+ }
+ memcpy(s->current + (sec << 9), blankbuf, 512);
+ memcpy(s->current + (s->secs_cur << 9) + (sec << 4),
+ blankbuf, 1 << 4);
+ }
+ }
+
+ g_free(tmpbuf);
+ g_free(blankbuf);
+ return 0;
+
+fail:
+ g_free(tmpbuf);
+ g_free(blankbuf);
+ return 1;
+}
+
+static void onenand_command(OneNANDState *s)
+{
+ int b;
+ int sec;
+ void *buf;
+#define SETADDR(block, page) \
+ sec = (s->addr[page] & 3) + \
+ ((((s->addr[page] >> 2) & 0x3f) + \
+ (((s->addr[block] & 0xfff) | \
+ (s->addr[block] >> 15 ? \
+ s->density_mask : 0)) << 6)) << (PAGE_SHIFT - 9));
+#define SETBUF_M() \
+ buf = (s->bufaddr & 8) ? \
+ s->data[(s->bufaddr >> 2) & 1][0] : s->boot[0]; \
+ buf += (s->bufaddr & 3) << 9;
+#define SETBUF_S() \
+ buf = (s->bufaddr & 8) ? \
+ s->data[(s->bufaddr >> 2) & 1][1] : s->boot[1]; \
+ buf += (s->bufaddr & 3) << 4;
+
+ switch (s->command) {
+ case 0x00: /* Load single/multiple sector data unit into buffer */
+ SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+
+ SETBUF_M()
+ if (onenand_load_main(s, sec, s->count, buf))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
+
+#if 0
+ SETBUF_S()
+ if (onenand_load_spare(s, sec, s->count, buf))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
+#endif
+
+ /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
+ * or if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
+ * then we need two split the read/write into two chunks.
+ */
+ s->intstatus |= ONEN_INT | ONEN_INT_LOAD;
+ break;
+ case 0x13: /* Load single/multiple spare sector into buffer */
+ SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+
+ SETBUF_S()
+ if (onenand_load_spare(s, sec, s->count, buf))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
+
+ /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
+ * or if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
+ * then we need two split the read/write into two chunks.
+ */
+ s->intstatus |= ONEN_INT | ONEN_INT_LOAD;
+ break;
+ case 0x80: /* Program single/multiple sector data unit from buffer */
+ SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+
+ SETBUF_M()
+ if (onenand_prog_main(s, sec, s->count, buf))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+
+#if 0
+ SETBUF_S()
+ if (onenand_prog_spare(s, sec, s->count, buf))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+#endif
+
+ /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
+ * or if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
+ * then we need two split the read/write into two chunks.
+ */
+ s->intstatus |= ONEN_INT | ONEN_INT_PROG;
+ break;
+ case 0x1a: /* Program single/multiple spare area sector from buffer */
+ SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+
+ SETBUF_S()
+ if (onenand_prog_spare(s, sec, s->count, buf))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+
+ /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
+ * or if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
+ * then we need two split the read/write into two chunks.
+ */
+ s->intstatus |= ONEN_INT | ONEN_INT_PROG;
+ break;
+ case 0x1b: /* Copy-back program */
+ SETBUF_S()
+
+ SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+ if (onenand_load_main(s, sec, s->count, buf))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+
+ SETADDR(ONEN_BUF_DEST_BLOCK, ONEN_BUF_DEST_PAGE)
+ if (onenand_prog_main(s, sec, s->count, buf))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+
+ /* TODO: spare areas */
+
+ s->intstatus |= ONEN_INT | ONEN_INT_PROG;
+ break;
+
+ case 0x23: /* Unlock NAND array block(s) */
+ s->intstatus |= ONEN_INT;
+
+ /* XXX the previous (?) area should be locked automatically */
+ for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
+ if (b >= s->blocks) {
+ s->status |= ONEN_ERR_CMD;
+ break;
+ }
+ if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN)
+ break;
+
+ s->wpstatus = s->blockwp[b] = ONEN_LOCK_UNLOCKED;
+ }
+ break;
+ case 0x27: /* Unlock All NAND array blocks */
+ s->intstatus |= ONEN_INT;
+
+ for (b = 0; b < s->blocks; b ++) {
+ if (b >= s->blocks) {
+ s->status |= ONEN_ERR_CMD;
+ break;
+ }
+ if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN)
+ break;
+
+ s->wpstatus = s->blockwp[b] = ONEN_LOCK_UNLOCKED;
+ }
+ break;
+
+ case 0x2a: /* Lock NAND array block(s) */
+ s->intstatus |= ONEN_INT;
+
+ for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
+ if (b >= s->blocks) {
+ s->status |= ONEN_ERR_CMD;
+ break;
+ }
+ if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN)
+ break;
+
+ s->wpstatus = s->blockwp[b] = ONEN_LOCK_LOCKED;
+ }
+ break;
+ case 0x2c: /* Lock-tight NAND array block(s) */
+ s->intstatus |= ONEN_INT;
+
+ for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
+ if (b >= s->blocks) {
+ s->status |= ONEN_ERR_CMD;
+ break;
+ }
+ if (s->blockwp[b] == ONEN_LOCK_UNLOCKED)
+ continue;
+
+ s->wpstatus = s->blockwp[b] = ONEN_LOCK_LOCKTIGHTEN;
+ }
+ break;
+
+ case 0x71: /* Erase-Verify-Read */
+ s->intstatus |= ONEN_INT;
+ break;
+ case 0x95: /* Multi-block erase */
+ qemu_irq_pulse(s->intr);
+ /* Fall through. */
+ case 0x94: /* Block erase */
+ sec = ((s->addr[ONEN_BUF_BLOCK] & 0xfff) |
+ (s->addr[ONEN_BUF_BLOCK] >> 15 ? s->density_mask : 0))
+ << (BLOCK_SHIFT - 9);
+ if (onenand_erase(s, sec, 1 << (BLOCK_SHIFT - 9)))
+ s->status |= ONEN_ERR_CMD | ONEN_ERR_ERASE;
+
+ s->intstatus |= ONEN_INT | ONEN_INT_ERASE;
+ break;
+ case 0xb0: /* Erase suspend */
+ break;
+ case 0x30: /* Erase resume */
+ s->intstatus |= ONEN_INT | ONEN_INT_ERASE;
+ break;
+
+ case 0xf0: /* Reset NAND Flash core */
+ onenand_reset(s, 0);
+ break;
+ case 0xf3: /* Reset OneNAND */
+ onenand_reset(s, 0);
+ break;
+
+ case 0x65: /* OTP Access */
+ s->intstatus |= ONEN_INT;
+ s->blk_cur = NULL;
+ s->current = s->otp;
+ s->secs_cur = 1 << (BLOCK_SHIFT - 9);
+ s->addr[ONEN_BUF_BLOCK] = 0;
+ s->otpmode = 1;
+ break;
+
+ default:
+ s->status |= ONEN_ERR_CMD;
+ s->intstatus |= ONEN_INT;
+ fprintf(stderr, "%s: unknown OneNAND command %x\n",
+ __func__, s->command);
+ }
+
+ onenand_intr_update(s);
+}
+
+static uint64_t onenand_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ OneNANDState *s = (OneNANDState *) opaque;
+ int offset = addr >> s->shift;
+
+ switch (offset) {
+ case 0x0000 ... 0xc000:
+ return lduw_le_p(s->boot[0] + addr);
+
+ case 0xf000: /* Manufacturer ID */
+ return s->id.man;
+ case 0xf001: /* Device ID */
+ return s->id.dev;
+ case 0xf002: /* Version ID */
+ return s->id.ver;
+ /* TODO: get the following values from a real chip! */
+ case 0xf003: /* Data Buffer size */
+ return 1 << PAGE_SHIFT;
+ case 0xf004: /* Boot Buffer size */
+ return 0x200;
+ case 0xf005: /* Amount of buffers */
+ return 1 | (2 << 8);
+ case 0xf006: /* Technology */
+ return 0;
+
+ case 0xf100 ... 0xf107: /* Start addresses */
+ return s->addr[offset - 0xf100];
+
+ case 0xf200: /* Start buffer */
+ return (s->bufaddr << 8) | ((s->count - 1) & (1 << (PAGE_SHIFT - 10)));
+
+ case 0xf220: /* Command */
+ return s->command;
+ case 0xf221: /* System Configuration 1 */
+ return s->config[0] & 0xffe0;
+ case 0xf222: /* System Configuration 2 */
+ return s->config[1];
+
+ case 0xf240: /* Controller Status */
+ return s->status;
+ case 0xf241: /* Interrupt */
+ return s->intstatus;
+ case 0xf24c: /* Unlock Start Block Address */
+ return s->unladdr[0];
+ case 0xf24d: /* Unlock End Block Address */
+ return s->unladdr[1];
+ case 0xf24e: /* Write Protection Status */
+ return s->wpstatus;
+
+ case 0xff00: /* ECC Status */
+ return 0x00;
+ case 0xff01: /* ECC Result of main area data */
+ case 0xff02: /* ECC Result of spare area data */
+ case 0xff03: /* ECC Result of main area data */
+ case 0xff04: /* ECC Result of spare area data */
+ hw_error("%s: imeplement ECC\n", __FUNCTION__);
+ return 0x0000;
+ }
+
+ fprintf(stderr, "%s: unknown OneNAND register %x\n",
+ __FUNCTION__, offset);
+ return 0;
+}
+
+static void onenand_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ OneNANDState *s = (OneNANDState *) opaque;
+ int offset = addr >> s->shift;
+ int sec;
+
+ switch (offset) {
+ case 0x0000 ... 0x01ff:
+ case 0x8000 ... 0x800f:
+ if (s->cycle) {
+ s->cycle = 0;
+
+ if (value == 0x0000) {
+ SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+ onenand_load_main(s, sec,
+ 1 << (PAGE_SHIFT - 9), s->data[0][0]);
+ s->addr[ONEN_BUF_PAGE] += 4;
+ s->addr[ONEN_BUF_PAGE] &= 0xff;
+ }
+ break;
+ }
+
+ switch (value) {
+ case 0x00f0: /* Reset OneNAND */
+ onenand_reset(s, 0);
+ break;
+
+ case 0x00e0: /* Load Data into Buffer */
+ s->cycle = 1;
+ break;
+
+ case 0x0090: /* Read Identification Data */
+ memset(s->boot[0], 0, 3 << s->shift);
+ s->boot[0][0 << s->shift] = s->id.man & 0xff;
+ s->boot[0][1 << s->shift] = s->id.dev & 0xff;
+ s->boot[0][2 << s->shift] = s->wpstatus & 0xff;
+ break;
+
+ default:
+ fprintf(stderr, "%s: unknown OneNAND boot command %"PRIx64"\n",
+ __FUNCTION__, value);
+ }
+ break;
+
+ case 0xf100 ... 0xf107: /* Start addresses */
+ s->addr[offset - 0xf100] = value;
+ break;
+
+ case 0xf200: /* Start buffer */
+ s->bufaddr = (value >> 8) & 0xf;
+ if (PAGE_SHIFT == 11)
+ s->count = (value & 3) ?: 4;
+ else if (PAGE_SHIFT == 10)
+ s->count = (value & 1) ?: 2;
+ break;
+
+ case 0xf220: /* Command */
+ if (s->intstatus & (1 << 15))
+ break;
+ s->command = value;
+ onenand_command(s);
+ break;
+ case 0xf221: /* System Configuration 1 */
+ s->config[0] = value;
+ onenand_intr_update(s);
+ qemu_set_irq(s->rdy, (s->config[0] >> 7) & 1);
+ break;
+ case 0xf222: /* System Configuration 2 */
+ s->config[1] = value;
+ break;
+
+ case 0xf241: /* Interrupt */
+ s->intstatus &= value;
+ if ((1 << 15) & ~s->intstatus)
+ s->status &= ~(ONEN_ERR_CMD | ONEN_ERR_ERASE |
+ ONEN_ERR_PROG | ONEN_ERR_LOAD);
+ onenand_intr_update(s);
+ break;
+ case 0xf24c: /* Unlock Start Block Address */
+ s->unladdr[0] = value & (s->blocks - 1);
+ /* For some reason we have to set the end address to by default
+ * be same as start because the software forgets to write anything
+ * in there. */
+ s->unladdr[1] = value & (s->blocks - 1);
+ break;
+ case 0xf24d: /* Unlock End Block Address */
+ s->unladdr[1] = value & (s->blocks - 1);
+ break;
+
+ default:
+ fprintf(stderr, "%s: unknown OneNAND register %x\n",
+ __FUNCTION__, offset);
+ }
+}
+
+static const MemoryRegionOps onenand_ops = {
+ .read = onenand_read,
+ .write = onenand_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int onenand_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ OneNANDState *s = ONE_NAND(dev);
+ uint32_t size = 1 << (24 + ((s->id.dev >> 4) & 7));
+ void *ram;
+
+ s->base = (hwaddr)-1;
+ s->rdy = NULL;
+ s->blocks = size >> BLOCK_SHIFT;
+ s->secs = size >> 9;
+ s->blockwp = g_malloc(s->blocks);
+ s->density_mask = (s->id.dev & 0x08)
+ ? (1 << (6 + ((s->id.dev >> 4) & 7))) : 0;
+ memory_region_init_io(&s->iomem, OBJECT(s), &onenand_ops, s, "onenand",
+ 0x10000 << s->shift);
+ if (!s->blk) {
+ s->image = memset(g_malloc(size + (size >> 5)),
+ 0xff, size + (size >> 5));
+ } else {
+ if (blk_is_read_only(s->blk)) {
+ error_report("Can't use a read-only drive");
+ return -1;
+ }
+ s->blk_cur = s->blk;
+ }
+ s->otp = memset(g_malloc((64 + 2) << PAGE_SHIFT),
+ 0xff, (64 + 2) << PAGE_SHIFT);
+ memory_region_init_ram(&s->ram, OBJECT(s), "onenand.ram",
+ 0xc000 << s->shift, &error_abort);
+ vmstate_register_ram_global(&s->ram);
+ ram = memory_region_get_ram_ptr(&s->ram);
+ s->boot[0] = ram + (0x0000 << s->shift);
+ s->boot[1] = ram + (0x8000 << s->shift);
+ s->data[0][0] = ram + ((0x0200 + (0 << (PAGE_SHIFT - 1))) << s->shift);
+ s->data[0][1] = ram + ((0x8010 + (0 << (PAGE_SHIFT - 6))) << s->shift);
+ s->data[1][0] = ram + ((0x0200 + (1 << (PAGE_SHIFT - 1))) << s->shift);
+ s->data[1][1] = ram + ((0x8010 + (1 << (PAGE_SHIFT - 6))) << s->shift);
+ onenand_mem_setup(s);
+ sysbus_init_irq(sbd, &s->intr);
+ sysbus_init_mmio(sbd, &s->container);
+ vmstate_register(dev,
+ ((s->shift & 0x7f) << 24)
+ | ((s->id.man & 0xff) << 16)
+ | ((s->id.dev & 0xff) << 8)
+ | (s->id.ver & 0xff),
+ &vmstate_onenand, s);
+ return 0;
+}
+
+static Property onenand_properties[] = {
+ DEFINE_PROP_UINT16("manufacturer_id", OneNANDState, id.man, 0),
+ DEFINE_PROP_UINT16("device_id", OneNANDState, id.dev, 0),
+ DEFINE_PROP_UINT16("version_id", OneNANDState, id.ver, 0),
+ DEFINE_PROP_INT32("shift", OneNANDState, shift, 0),
+ DEFINE_PROP_DRIVE("drive", OneNANDState, blk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void onenand_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = onenand_initfn;
+ dc->reset = onenand_system_reset;
+ dc->props = onenand_properties;
+}
+
+static const TypeInfo onenand_info = {
+ .name = TYPE_ONE_NAND,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OneNANDState),
+ .class_init = onenand_class_init,
+};
+
+static void onenand_register_types(void)
+{
+ type_register_static(&onenand_info);
+}
+
+void *onenand_raw_otp(DeviceState *onenand_device)
+{
+ OneNANDState *s = ONE_NAND(onenand_device);
+
+ return s->otp;
+}
+
+type_init(onenand_register_types)
diff --git a/hw/block/pflash_cfi01.c b/hw/block/pflash_cfi01.c
new file mode 100644
index 00000000..2ba6c772
--- /dev/null
+++ b/hw/block/pflash_cfi01.c
@@ -0,0 +1,954 @@
+/*
+ * CFI parallel flash with Intel command set emulation
+ *
+ * Copyright (c) 2006 Thorsten Zitterell
+ * Copyright (c) 2005 Jocelyn Mayer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * For now, this code can emulate flashes of 1, 2 or 4 bytes width.
+ * Supported commands/modes are:
+ * - flash read
+ * - flash write
+ * - flash ID read
+ * - sector erase
+ * - CFI queries
+ *
+ * It does not support timings
+ * It does not support flash interleaving
+ * It does not implement software data protection as found in many real chips
+ * It does not implement erase suspend/resume commands
+ * It does not implement multiple sectors erase
+ *
+ * It does not implement much more ...
+ */
+
+#include "hw/hw.h"
+#include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "qemu/timer.h"
+#include "qemu/bitops.h"
+#include "exec/address-spaces.h"
+#include "qemu/host-utils.h"
+#include "hw/sysbus.h"
+
+#define PFLASH_BUG(fmt, ...) \
+do { \
+ fprintf(stderr, "PFLASH: Possible BUG - " fmt, ## __VA_ARGS__); \
+ exit(1); \
+} while(0)
+
+/* #define PFLASH_DEBUG */
+#ifdef PFLASH_DEBUG
+#define DPRINTF(fmt, ...) \
+do { \
+ fprintf(stderr, "PFLASH: " fmt , ## __VA_ARGS__); \
+} while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+#define TYPE_CFI_PFLASH01 "cfi.pflash01"
+#define CFI_PFLASH01(obj) OBJECT_CHECK(pflash_t, (obj), TYPE_CFI_PFLASH01)
+
+#define PFLASH_BE 0
+#define PFLASH_SECURE 1
+
+struct pflash_t {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ BlockBackend *blk;
+ uint32_t nb_blocs;
+ uint64_t sector_len;
+ uint8_t bank_width;
+ uint8_t device_width; /* If 0, device width not specified. */
+ uint8_t max_device_width; /* max device width in bytes */
+ uint32_t features;
+ uint8_t wcycle; /* if 0, the flash is read normally */
+ int ro;
+ uint8_t cmd;
+ uint8_t status;
+ uint16_t ident0;
+ uint16_t ident1;
+ uint16_t ident2;
+ uint16_t ident3;
+ uint8_t cfi_len;
+ uint8_t cfi_table[0x52];
+ uint64_t counter;
+ unsigned int writeblock_size;
+ QEMUTimer *timer;
+ MemoryRegion mem;
+ char *name;
+ void *storage;
+};
+
+static int pflash_post_load(void *opaque, int version_id);
+
+static const VMStateDescription vmstate_pflash = {
+ .name = "pflash_cfi01",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pflash_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(wcycle, pflash_t),
+ VMSTATE_UINT8(cmd, pflash_t),
+ VMSTATE_UINT8(status, pflash_t),
+ VMSTATE_UINT64(counter, pflash_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pflash_timer (void *opaque)
+{
+ pflash_t *pfl = opaque;
+
+ DPRINTF("%s: command %02x done\n", __func__, pfl->cmd);
+ /* Reset flash */
+ pfl->status ^= 0x80;
+ memory_region_rom_device_set_romd(&pfl->mem, true);
+ pfl->wcycle = 0;
+ pfl->cmd = 0;
+}
+
+/* Perform a CFI query based on the bank width of the flash.
+ * If this code is called we know we have a device_width set for
+ * this flash.
+ */
+static uint32_t pflash_cfi_query(pflash_t *pfl, hwaddr offset)
+{
+ int i;
+ uint32_t resp = 0;
+ hwaddr boff;
+
+ /* Adjust incoming offset to match expected device-width
+ * addressing. CFI query addresses are always specified in terms of
+ * the maximum supported width of the device. This means that x8
+ * devices and x8/x16 devices in x8 mode behave differently. For
+ * devices that are not used at their max width, we will be
+ * provided with addresses that use higher address bits than
+ * expected (based on the max width), so we will shift them lower
+ * so that they will match the addresses used when
+ * device_width==max_device_width.
+ */
+ boff = offset >> (ctz32(pfl->bank_width) +
+ ctz32(pfl->max_device_width) - ctz32(pfl->device_width));
+
+ if (boff > pfl->cfi_len) {
+ return 0;
+ }
+ /* Now we will construct the CFI response generated by a single
+ * device, then replicate that for all devices that make up the
+ * bus. For wide parts used in x8 mode, CFI query responses
+ * are different than native byte-wide parts.
+ */
+ resp = pfl->cfi_table[boff];
+ if (pfl->device_width != pfl->max_device_width) {
+ /* The only case currently supported is x8 mode for a
+ * wider part.
+ */
+ if (pfl->device_width != 1 || pfl->bank_width > 4) {
+ DPRINTF("%s: Unsupported device configuration: "
+ "device_width=%d, max_device_width=%d\n",
+ __func__, pfl->device_width,
+ pfl->max_device_width);
+ return 0;
+ }
+ /* CFI query data is repeated, rather than zero padded for
+ * wide devices used in x8 mode.
+ */
+ for (i = 1; i < pfl->max_device_width; i++) {
+ resp = deposit32(resp, 8 * i, 8, pfl->cfi_table[boff]);
+ }
+ }
+ /* Replicate responses for each device in bank. */
+ if (pfl->device_width < pfl->bank_width) {
+ for (i = pfl->device_width;
+ i < pfl->bank_width; i += pfl->device_width) {
+ resp = deposit32(resp, 8 * i, 8 * pfl->device_width, resp);
+ }
+ }
+
+ return resp;
+}
+
+
+
+/* Perform a device id query based on the bank width of the flash. */
+static uint32_t pflash_devid_query(pflash_t *pfl, hwaddr offset)
+{
+ int i;
+ uint32_t resp;
+ hwaddr boff;
+
+ /* Adjust incoming offset to match expected device-width
+ * addressing. Device ID read addresses are always specified in
+ * terms of the maximum supported width of the device. This means
+ * that x8 devices and x8/x16 devices in x8 mode behave
+ * differently. For devices that are not used at their max width,
+ * we will be provided with addresses that use higher address bits
+ * than expected (based on the max width), so we will shift them
+ * lower so that they will match the addresses used when
+ * device_width==max_device_width.
+ */
+ boff = offset >> (ctz32(pfl->bank_width) +
+ ctz32(pfl->max_device_width) - ctz32(pfl->device_width));
+
+ /* Mask off upper bits which may be used in to query block
+ * or sector lock status at other addresses.
+ * Offsets 2/3 are block lock status, is not emulated.
+ */
+ switch (boff & 0xFF) {
+ case 0:
+ resp = pfl->ident0;
+ DPRINTF("%s: Manufacturer Code %04x\n", __func__, resp);
+ break;
+ case 1:
+ resp = pfl->ident1;
+ DPRINTF("%s: Device ID Code %04x\n", __func__, resp);
+ break;
+ default:
+ DPRINTF("%s: Read Device Information offset=%x\n", __func__,
+ (unsigned)offset);
+ return 0;
+ break;
+ }
+ /* Replicate responses for each device in bank. */
+ if (pfl->device_width < pfl->bank_width) {
+ for (i = pfl->device_width;
+ i < pfl->bank_width; i += pfl->device_width) {
+ resp = deposit32(resp, 8 * i, 8 * pfl->device_width, resp);
+ }
+ }
+
+ return resp;
+}
+
+static uint32_t pflash_data_read(pflash_t *pfl, hwaddr offset,
+ int width, int be)
+{
+ uint8_t *p;
+ uint32_t ret;
+
+ p = pfl->storage;
+ switch (width) {
+ case 1:
+ ret = p[offset];
+ DPRINTF("%s: data offset " TARGET_FMT_plx " %02x\n",
+ __func__, offset, ret);
+ break;
+ case 2:
+ if (be) {
+ ret = p[offset] << 8;
+ ret |= p[offset + 1];
+ } else {
+ ret = p[offset];
+ ret |= p[offset + 1] << 8;
+ }
+ DPRINTF("%s: data offset " TARGET_FMT_plx " %04x\n",
+ __func__, offset, ret);
+ break;
+ case 4:
+ if (be) {
+ ret = p[offset] << 24;
+ ret |= p[offset + 1] << 16;
+ ret |= p[offset + 2] << 8;
+ ret |= p[offset + 3];
+ } else {
+ ret = p[offset];
+ ret |= p[offset + 1] << 8;
+ ret |= p[offset + 2] << 16;
+ ret |= p[offset + 3] << 24;
+ }
+ DPRINTF("%s: data offset " TARGET_FMT_plx " %08x\n",
+ __func__, offset, ret);
+ break;
+ default:
+ DPRINTF("BUG in %s\n", __func__);
+ abort();
+ }
+ return ret;
+}
+
+static uint32_t pflash_read (pflash_t *pfl, hwaddr offset,
+ int width, int be)
+{
+ hwaddr boff;
+ uint32_t ret;
+
+ ret = -1;
+
+#if 0
+ DPRINTF("%s: reading offset " TARGET_FMT_plx " under cmd %02x width %d\n",
+ __func__, offset, pfl->cmd, width);
+#endif
+ switch (pfl->cmd) {
+ default:
+ /* This should never happen : reset state & treat it as a read */
+ DPRINTF("%s: unknown command state: %x\n", __func__, pfl->cmd);
+ pfl->wcycle = 0;
+ pfl->cmd = 0;
+ /* fall through to read code */
+ case 0x00:
+ /* Flash area read */
+ ret = pflash_data_read(pfl, offset, width, be);
+ break;
+ case 0x10: /* Single byte program */
+ case 0x20: /* Block erase */
+ case 0x28: /* Block erase */
+ case 0x40: /* single byte program */
+ case 0x50: /* Clear status register */
+ case 0x60: /* Block /un)lock */
+ case 0x70: /* Status Register */
+ case 0xe8: /* Write block */
+ /* Status register read. Return status from each device in
+ * bank.
+ */
+ ret = pfl->status;
+ if (pfl->device_width && width > pfl->device_width) {
+ int shift = pfl->device_width * 8;
+ while (shift + pfl->device_width * 8 <= width * 8) {
+ ret |= pfl->status << shift;
+ shift += pfl->device_width * 8;
+ }
+ } else if (!pfl->device_width && width > 2) {
+ /* Handle 32 bit flash cases where device width is not
+ * set. (Existing behavior before device width added.)
+ */
+ ret |= pfl->status << 16;
+ }
+ DPRINTF("%s: status %x\n", __func__, ret);
+ break;
+ case 0x90:
+ if (!pfl->device_width) {
+ /* Preserve old behavior if device width not specified */
+ boff = offset & 0xFF;
+ if (pfl->bank_width == 2) {
+ boff = boff >> 1;
+ } else if (pfl->bank_width == 4) {
+ boff = boff >> 2;
+ }
+
+ switch (boff) {
+ case 0:
+ ret = pfl->ident0 << 8 | pfl->ident1;
+ DPRINTF("%s: Manufacturer Code %04x\n", __func__, ret);
+ break;
+ case 1:
+ ret = pfl->ident2 << 8 | pfl->ident3;
+ DPRINTF("%s: Device ID Code %04x\n", __func__, ret);
+ break;
+ default:
+ DPRINTF("%s: Read Device Information boff=%x\n", __func__,
+ (unsigned)boff);
+ ret = 0;
+ break;
+ }
+ } else {
+ /* If we have a read larger than the bank_width, combine multiple
+ * manufacturer/device ID queries into a single response.
+ */
+ int i;
+ for (i = 0; i < width; i += pfl->bank_width) {
+ ret = deposit32(ret, i * 8, pfl->bank_width * 8,
+ pflash_devid_query(pfl,
+ offset + i * pfl->bank_width));
+ }
+ }
+ break;
+ case 0x98: /* Query mode */
+ if (!pfl->device_width) {
+ /* Preserve old behavior if device width not specified */
+ boff = offset & 0xFF;
+ if (pfl->bank_width == 2) {
+ boff = boff >> 1;
+ } else if (pfl->bank_width == 4) {
+ boff = boff >> 2;
+ }
+
+ if (boff > pfl->cfi_len) {
+ ret = 0;
+ } else {
+ ret = pfl->cfi_table[boff];
+ }
+ } else {
+ /* If we have a read larger than the bank_width, combine multiple
+ * CFI queries into a single response.
+ */
+ int i;
+ for (i = 0; i < width; i += pfl->bank_width) {
+ ret = deposit32(ret, i * 8, pfl->bank_width * 8,
+ pflash_cfi_query(pfl,
+ offset + i * pfl->bank_width));
+ }
+ }
+
+ break;
+ }
+ return ret;
+}
+
+/* update flash content on disk */
+static void pflash_update(pflash_t *pfl, int offset,
+ int size)
+{
+ int offset_end;
+ if (pfl->blk) {
+ offset_end = offset + size;
+ /* round to sectors */
+ offset = offset >> 9;
+ offset_end = (offset_end + 511) >> 9;
+ blk_write(pfl->blk, offset, pfl->storage + (offset << 9),
+ offset_end - offset);
+ }
+}
+
+static inline void pflash_data_write(pflash_t *pfl, hwaddr offset,
+ uint32_t value, int width, int be)
+{
+ uint8_t *p = pfl->storage;
+
+ DPRINTF("%s: block write offset " TARGET_FMT_plx
+ " value %x counter %016" PRIx64 "\n",
+ __func__, offset, value, pfl->counter);
+ switch (width) {
+ case 1:
+ p[offset] = value;
+ break;
+ case 2:
+ if (be) {
+ p[offset] = value >> 8;
+ p[offset + 1] = value;
+ } else {
+ p[offset] = value;
+ p[offset + 1] = value >> 8;
+ }
+ break;
+ case 4:
+ if (be) {
+ p[offset] = value >> 24;
+ p[offset + 1] = value >> 16;
+ p[offset + 2] = value >> 8;
+ p[offset + 3] = value;
+ } else {
+ p[offset] = value;
+ p[offset + 1] = value >> 8;
+ p[offset + 2] = value >> 16;
+ p[offset + 3] = value >> 24;
+ }
+ break;
+ }
+
+}
+
+static void pflash_write(pflash_t *pfl, hwaddr offset,
+ uint32_t value, int width, int be)
+{
+ uint8_t *p;
+ uint8_t cmd;
+
+ cmd = value;
+
+ DPRINTF("%s: writing offset " TARGET_FMT_plx " value %08x width %d wcycle 0x%x\n",
+ __func__, offset, value, width, pfl->wcycle);
+
+ if (!pfl->wcycle) {
+ /* Set the device in I/O access mode */
+ memory_region_rom_device_set_romd(&pfl->mem, false);
+ }
+
+ switch (pfl->wcycle) {
+ case 0:
+ /* read mode */
+ switch (cmd) {
+ case 0x00: /* ??? */
+ goto reset_flash;
+ case 0x10: /* Single Byte Program */
+ case 0x40: /* Single Byte Program */
+ DPRINTF("%s: Single Byte Program\n", __func__);
+ break;
+ case 0x20: /* Block erase */
+ p = pfl->storage;
+ offset &= ~(pfl->sector_len - 1);
+
+ DPRINTF("%s: block erase at " TARGET_FMT_plx " bytes %x\n",
+ __func__, offset, (unsigned)pfl->sector_len);
+
+ if (!pfl->ro) {
+ memset(p + offset, 0xff, pfl->sector_len);
+ pflash_update(pfl, offset, pfl->sector_len);
+ } else {
+ pfl->status |= 0x20; /* Block erase error */
+ }
+ pfl->status |= 0x80; /* Ready! */
+ break;
+ case 0x50: /* Clear status bits */
+ DPRINTF("%s: Clear status bits\n", __func__);
+ pfl->status = 0x0;
+ goto reset_flash;
+ case 0x60: /* Block (un)lock */
+ DPRINTF("%s: Block unlock\n", __func__);
+ break;
+ case 0x70: /* Status Register */
+ DPRINTF("%s: Read status register\n", __func__);
+ pfl->cmd = cmd;
+ return;
+ case 0x90: /* Read Device ID */
+ DPRINTF("%s: Read Device information\n", __func__);
+ pfl->cmd = cmd;
+ return;
+ case 0x98: /* CFI query */
+ DPRINTF("%s: CFI query\n", __func__);
+ break;
+ case 0xe8: /* Write to buffer */
+ DPRINTF("%s: Write to buffer\n", __func__);
+ pfl->status |= 0x80; /* Ready! */
+ break;
+ case 0xf0: /* Probe for AMD flash */
+ DPRINTF("%s: Probe for AMD flash\n", __func__);
+ goto reset_flash;
+ case 0xff: /* Read array mode */
+ DPRINTF("%s: Read array mode\n", __func__);
+ goto reset_flash;
+ default:
+ goto error_flash;
+ }
+ pfl->wcycle++;
+ pfl->cmd = cmd;
+ break;
+ case 1:
+ switch (pfl->cmd) {
+ case 0x10: /* Single Byte Program */
+ case 0x40: /* Single Byte Program */
+ DPRINTF("%s: Single Byte Program\n", __func__);
+ if (!pfl->ro) {
+ pflash_data_write(pfl, offset, value, width, be);
+ pflash_update(pfl, offset, width);
+ } else {
+ pfl->status |= 0x10; /* Programming error */
+ }
+ pfl->status |= 0x80; /* Ready! */
+ pfl->wcycle = 0;
+ break;
+ case 0x20: /* Block erase */
+ case 0x28:
+ if (cmd == 0xd0) { /* confirm */
+ pfl->wcycle = 0;
+ pfl->status |= 0x80;
+ } else if (cmd == 0xff) { /* read array mode */
+ goto reset_flash;
+ } else
+ goto error_flash;
+
+ break;
+ case 0xe8:
+ /* Mask writeblock size based on device width, or bank width if
+ * device width not specified.
+ */
+ if (pfl->device_width) {
+ value = extract32(value, 0, pfl->device_width * 8);
+ } else {
+ value = extract32(value, 0, pfl->bank_width * 8);
+ }
+ DPRINTF("%s: block write of %x bytes\n", __func__, value);
+ pfl->counter = value;
+ pfl->wcycle++;
+ break;
+ case 0x60:
+ if (cmd == 0xd0) {
+ pfl->wcycle = 0;
+ pfl->status |= 0x80;
+ } else if (cmd == 0x01) {
+ pfl->wcycle = 0;
+ pfl->status |= 0x80;
+ } else if (cmd == 0xff) {
+ goto reset_flash;
+ } else {
+ DPRINTF("%s: Unknown (un)locking command\n", __func__);
+ goto reset_flash;
+ }
+ break;
+ case 0x98:
+ if (cmd == 0xff) {
+ goto reset_flash;
+ } else {
+ DPRINTF("%s: leaving query mode\n", __func__);
+ }
+ break;
+ default:
+ goto error_flash;
+ }
+ break;
+ case 2:
+ switch (pfl->cmd) {
+ case 0xe8: /* Block write */
+ if (!pfl->ro) {
+ pflash_data_write(pfl, offset, value, width, be);
+ } else {
+ pfl->status |= 0x10; /* Programming error */
+ }
+
+ pfl->status |= 0x80;
+
+ if (!pfl->counter) {
+ hwaddr mask = pfl->writeblock_size - 1;
+ mask = ~mask;
+
+ DPRINTF("%s: block write finished\n", __func__);
+ pfl->wcycle++;
+ if (!pfl->ro) {
+ /* Flush the entire write buffer onto backing storage. */
+ pflash_update(pfl, offset & mask, pfl->writeblock_size);
+ } else {
+ pfl->status |= 0x10; /* Programming error */
+ }
+ }
+
+ pfl->counter--;
+ break;
+ default:
+ goto error_flash;
+ }
+ break;
+ case 3: /* Confirm mode */
+ switch (pfl->cmd) {
+ case 0xe8: /* Block write */
+ if (cmd == 0xd0) {
+ pfl->wcycle = 0;
+ pfl->status |= 0x80;
+ } else {
+ DPRINTF("%s: unknown command for \"write block\"\n", __func__);
+ PFLASH_BUG("Write block confirm");
+ goto reset_flash;
+ }
+ break;
+ default:
+ goto error_flash;
+ }
+ break;
+ default:
+ /* Should never happen */
+ DPRINTF("%s: invalid write state\n", __func__);
+ goto reset_flash;
+ }
+ return;
+
+ error_flash:
+ qemu_log_mask(LOG_UNIMP, "%s: Unimplemented flash cmd sequence "
+ "(offset " TARGET_FMT_plx ", wcycle 0x%x cmd 0x%x value 0x%x)"
+ "\n", __func__, offset, pfl->wcycle, pfl->cmd, value);
+
+ reset_flash:
+ memory_region_rom_device_set_romd(&pfl->mem, true);
+
+ pfl->wcycle = 0;
+ pfl->cmd = 0;
+}
+
+
+static MemTxResult pflash_mem_read_with_attrs(void *opaque, hwaddr addr, uint64_t *value,
+ unsigned len, MemTxAttrs attrs)
+{
+ pflash_t *pfl = opaque;
+ bool be = !!(pfl->features & (1 << PFLASH_BE));
+
+ if ((pfl->features & (1 << PFLASH_SECURE)) && !attrs.secure) {
+ *value = pflash_data_read(opaque, addr, len, be);
+ } else {
+ *value = pflash_read(opaque, addr, len, be);
+ }
+ return MEMTX_OK;
+}
+
+static MemTxResult pflash_mem_write_with_attrs(void *opaque, hwaddr addr, uint64_t value,
+ unsigned len, MemTxAttrs attrs)
+{
+ pflash_t *pfl = opaque;
+ bool be = !!(pfl->features & (1 << PFLASH_BE));
+
+ if ((pfl->features & (1 << PFLASH_SECURE)) && !attrs.secure) {
+ return MEMTX_ERROR;
+ } else {
+ pflash_write(opaque, addr, value, len, be);
+ return MEMTX_OK;
+ }
+}
+
+static const MemoryRegionOps pflash_cfi01_ops = {
+ .read_with_attrs = pflash_mem_read_with_attrs,
+ .write_with_attrs = pflash_mem_write_with_attrs,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pflash_cfi01_realize(DeviceState *dev, Error **errp)
+{
+ pflash_t *pfl = CFI_PFLASH01(dev);
+ uint64_t total_len;
+ int ret;
+ uint64_t blocks_per_device, device_len;
+ int num_devices;
+ Error *local_err = NULL;
+
+ total_len = pfl->sector_len * pfl->nb_blocs;
+
+ /* These are only used to expose the parameters of each device
+ * in the cfi_table[].
+ */
+ num_devices = pfl->device_width ? (pfl->bank_width / pfl->device_width) : 1;
+ blocks_per_device = pfl->nb_blocs / num_devices;
+ device_len = pfl->sector_len * blocks_per_device;
+
+ /* XXX: to be fixed */
+#if 0
+ if (total_len != (8 * 1024 * 1024) && total_len != (16 * 1024 * 1024) &&
+ total_len != (32 * 1024 * 1024) && total_len != (64 * 1024 * 1024))
+ return NULL;
+#endif
+
+ memory_region_init_rom_device(
+ &pfl->mem, OBJECT(dev),
+ &pflash_cfi01_ops,
+ pfl,
+ pfl->name, total_len, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ vmstate_register_ram(&pfl->mem, DEVICE(pfl));
+ pfl->storage = memory_region_get_ram_ptr(&pfl->mem);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &pfl->mem);
+
+ if (pfl->blk) {
+ /* read the initial flash content */
+ ret = blk_read(pfl->blk, 0, pfl->storage, total_len >> 9);
+
+ if (ret < 0) {
+ vmstate_unregister_ram(&pfl->mem, DEVICE(pfl));
+ error_setg(errp, "failed to read the initial flash content");
+ return;
+ }
+ }
+
+ if (pfl->blk) {
+ pfl->ro = blk_is_read_only(pfl->blk);
+ } else {
+ pfl->ro = 0;
+ }
+
+ /* Default to devices being used at their maximum device width. This was
+ * assumed before the device_width support was added.
+ */
+ if (!pfl->max_device_width) {
+ pfl->max_device_width = pfl->device_width;
+ }
+
+ pfl->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, pflash_timer, pfl);
+ pfl->wcycle = 0;
+ pfl->cmd = 0;
+ pfl->status = 0;
+ /* Hardcoded CFI table */
+ pfl->cfi_len = 0x52;
+ /* Standard "QRY" string */
+ pfl->cfi_table[0x10] = 'Q';
+ pfl->cfi_table[0x11] = 'R';
+ pfl->cfi_table[0x12] = 'Y';
+ /* Command set (Intel) */
+ pfl->cfi_table[0x13] = 0x01;
+ pfl->cfi_table[0x14] = 0x00;
+ /* Primary extended table address (none) */
+ pfl->cfi_table[0x15] = 0x31;
+ pfl->cfi_table[0x16] = 0x00;
+ /* Alternate command set (none) */
+ pfl->cfi_table[0x17] = 0x00;
+ pfl->cfi_table[0x18] = 0x00;
+ /* Alternate extended table (none) */
+ pfl->cfi_table[0x19] = 0x00;
+ pfl->cfi_table[0x1A] = 0x00;
+ /* Vcc min */
+ pfl->cfi_table[0x1B] = 0x45;
+ /* Vcc max */
+ pfl->cfi_table[0x1C] = 0x55;
+ /* Vpp min (no Vpp pin) */
+ pfl->cfi_table[0x1D] = 0x00;
+ /* Vpp max (no Vpp pin) */
+ pfl->cfi_table[0x1E] = 0x00;
+ /* Reserved */
+ pfl->cfi_table[0x1F] = 0x07;
+ /* Timeout for min size buffer write */
+ pfl->cfi_table[0x20] = 0x07;
+ /* Typical timeout for block erase */
+ pfl->cfi_table[0x21] = 0x0a;
+ /* Typical timeout for full chip erase (4096 ms) */
+ pfl->cfi_table[0x22] = 0x00;
+ /* Reserved */
+ pfl->cfi_table[0x23] = 0x04;
+ /* Max timeout for buffer write */
+ pfl->cfi_table[0x24] = 0x04;
+ /* Max timeout for block erase */
+ pfl->cfi_table[0x25] = 0x04;
+ /* Max timeout for chip erase */
+ pfl->cfi_table[0x26] = 0x00;
+ /* Device size */
+ pfl->cfi_table[0x27] = ctz32(device_len); /* + 1; */
+ /* Flash device interface (8 & 16 bits) */
+ pfl->cfi_table[0x28] = 0x02;
+ pfl->cfi_table[0x29] = 0x00;
+ /* Max number of bytes in multi-bytes write */
+ if (pfl->bank_width == 1) {
+ pfl->cfi_table[0x2A] = 0x08;
+ } else {
+ pfl->cfi_table[0x2A] = 0x0B;
+ }
+ pfl->writeblock_size = 1 << pfl->cfi_table[0x2A];
+
+ pfl->cfi_table[0x2B] = 0x00;
+ /* Number of erase block regions (uniform) */
+ pfl->cfi_table[0x2C] = 0x01;
+ /* Erase block region 1 */
+ pfl->cfi_table[0x2D] = blocks_per_device - 1;
+ pfl->cfi_table[0x2E] = (blocks_per_device - 1) >> 8;
+ pfl->cfi_table[0x2F] = pfl->sector_len >> 8;
+ pfl->cfi_table[0x30] = pfl->sector_len >> 16;
+
+ /* Extended */
+ pfl->cfi_table[0x31] = 'P';
+ pfl->cfi_table[0x32] = 'R';
+ pfl->cfi_table[0x33] = 'I';
+
+ pfl->cfi_table[0x34] = '1';
+ pfl->cfi_table[0x35] = '0';
+
+ pfl->cfi_table[0x36] = 0x00;
+ pfl->cfi_table[0x37] = 0x00;
+ pfl->cfi_table[0x38] = 0x00;
+ pfl->cfi_table[0x39] = 0x00;
+
+ pfl->cfi_table[0x3a] = 0x00;
+
+ pfl->cfi_table[0x3b] = 0x00;
+ pfl->cfi_table[0x3c] = 0x00;
+
+ pfl->cfi_table[0x3f] = 0x01; /* Number of protection fields */
+}
+
+static Property pflash_cfi01_properties[] = {
+ DEFINE_PROP_DRIVE("drive", struct pflash_t, blk),
+ /* num-blocks is the number of blocks actually visible to the guest,
+ * ie the total size of the device divided by the sector length.
+ * If we're emulating flash devices wired in parallel the actual
+ * number of blocks per indvidual device will differ.
+ */
+ DEFINE_PROP_UINT32("num-blocks", struct pflash_t, nb_blocs, 0),
+ DEFINE_PROP_UINT64("sector-length", struct pflash_t, sector_len, 0),
+ /* width here is the overall width of this QEMU device in bytes.
+ * The QEMU device may be emulating a number of flash devices
+ * wired up in parallel; the width of each individual flash
+ * device should be specified via device-width. If the individual
+ * devices have a maximum width which is greater than the width
+ * they are being used for, this maximum width should be set via
+ * max-device-width (which otherwise defaults to device-width).
+ * So for instance a 32-bit wide QEMU flash device made from four
+ * 16-bit flash devices used in 8-bit wide mode would be configured
+ * with width = 4, device-width = 1, max-device-width = 2.
+ *
+ * If device-width is not specified we default to backwards
+ * compatible behaviour which is a bad emulation of two
+ * 16 bit devices making up a 32 bit wide QEMU device. This
+ * is deprecated for new uses of this device.
+ */
+ DEFINE_PROP_UINT8("width", struct pflash_t, bank_width, 0),
+ DEFINE_PROP_UINT8("device-width", struct pflash_t, device_width, 0),
+ DEFINE_PROP_UINT8("max-device-width", struct pflash_t, max_device_width, 0),
+ DEFINE_PROP_BIT("big-endian", struct pflash_t, features, PFLASH_BE, 0),
+ DEFINE_PROP_BIT("secure", struct pflash_t, features, PFLASH_SECURE, 0),
+ DEFINE_PROP_UINT16("id0", struct pflash_t, ident0, 0),
+ DEFINE_PROP_UINT16("id1", struct pflash_t, ident1, 0),
+ DEFINE_PROP_UINT16("id2", struct pflash_t, ident2, 0),
+ DEFINE_PROP_UINT16("id3", struct pflash_t, ident3, 0),
+ DEFINE_PROP_STRING("name", struct pflash_t, name),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pflash_cfi01_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pflash_cfi01_realize;
+ dc->props = pflash_cfi01_properties;
+ dc->vmsd = &vmstate_pflash;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+
+static const TypeInfo pflash_cfi01_info = {
+ .name = TYPE_CFI_PFLASH01,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct pflash_t),
+ .class_init = pflash_cfi01_class_init,
+};
+
+static void pflash_cfi01_register_types(void)
+{
+ type_register_static(&pflash_cfi01_info);
+}
+
+type_init(pflash_cfi01_register_types)
+
+pflash_t *pflash_cfi01_register(hwaddr base,
+ DeviceState *qdev, const char *name,
+ hwaddr size,
+ BlockBackend *blk,
+ uint32_t sector_len, int nb_blocs,
+ int bank_width, uint16_t id0, uint16_t id1,
+ uint16_t id2, uint16_t id3, int be)
+{
+ DeviceState *dev = qdev_create(NULL, TYPE_CFI_PFLASH01);
+
+ if (blk) {
+ qdev_prop_set_drive(dev, "drive", blk, &error_abort);
+ }
+ qdev_prop_set_uint32(dev, "num-blocks", nb_blocs);
+ qdev_prop_set_uint64(dev, "sector-length", sector_len);
+ qdev_prop_set_uint8(dev, "width", bank_width);
+ qdev_prop_set_bit(dev, "big-endian", !!be);
+ qdev_prop_set_uint16(dev, "id0", id0);
+ qdev_prop_set_uint16(dev, "id1", id1);
+ qdev_prop_set_uint16(dev, "id2", id2);
+ qdev_prop_set_uint16(dev, "id3", id3);
+ qdev_prop_set_string(dev, "name", name);
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ return CFI_PFLASH01(dev);
+}
+
+MemoryRegion *pflash_cfi01_get_memory(pflash_t *fl)
+{
+ return &fl->mem;
+}
+
+static int pflash_post_load(void *opaque, int version_id)
+{
+ pflash_t *pfl = opaque;
+
+ if (!pfl->ro) {
+ DPRINTF("%s: updating bdrv for %s\n", __func__, pfl->name);
+ pflash_update(pfl, 0, pfl->sector_len * pfl->nb_blocs);
+ }
+ return 0;
+}
diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
new file mode 100644
index 00000000..074a005f
--- /dev/null
+++ b/hw/block/pflash_cfi02.c
@@ -0,0 +1,795 @@
+/*
+ * CFI parallel flash with AMD command set emulation
+ *
+ * Copyright (c) 2005 Jocelyn Mayer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * For now, this code can emulate flashes of 1, 2 or 4 bytes width.
+ * Supported commands/modes are:
+ * - flash read
+ * - flash write
+ * - flash ID read
+ * - sector erase
+ * - chip erase
+ * - unlock bypass command
+ * - CFI queries
+ *
+ * It does not support flash interleaving.
+ * It does not implement boot blocs with reduced size
+ * It does not implement software data protection as found in many real chips
+ * It does not implement erase suspend/resume commands
+ * It does not implement multiple sectors erase
+ */
+
+#include "hw/hw.h"
+#include "hw/block/flash.h"
+#include "qemu/timer.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "qemu/host-utils.h"
+#include "hw/sysbus.h"
+
+//#define PFLASH_DEBUG
+#ifdef PFLASH_DEBUG
+#define DPRINTF(fmt, ...) \
+do { \
+ fprintf(stderr, "PFLASH: " fmt , ## __VA_ARGS__); \
+} while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+#define PFLASH_LAZY_ROMD_THRESHOLD 42
+
+#define TYPE_CFI_PFLASH02 "cfi.pflash02"
+#define CFI_PFLASH02(obj) OBJECT_CHECK(pflash_t, (obj), TYPE_CFI_PFLASH02)
+
+struct pflash_t {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ BlockBackend *blk;
+ uint32_t sector_len;
+ uint32_t nb_blocs;
+ uint32_t chip_len;
+ uint8_t mappings;
+ uint8_t width;
+ uint8_t be;
+ int wcycle; /* if 0, the flash is read normally */
+ int bypass;
+ int ro;
+ uint8_t cmd;
+ uint8_t status;
+ /* FIXME: implement array device properties */
+ uint16_t ident0;
+ uint16_t ident1;
+ uint16_t ident2;
+ uint16_t ident3;
+ uint16_t unlock_addr0;
+ uint16_t unlock_addr1;
+ uint8_t cfi_len;
+ uint8_t cfi_table[0x52];
+ QEMUTimer *timer;
+ /* The device replicates the flash memory across its memory space. Emulate
+ * that by having a container (.mem) filled with an array of aliases
+ * (.mem_mappings) pointing to the flash memory (.orig_mem).
+ */
+ MemoryRegion mem;
+ MemoryRegion *mem_mappings; /* array; one per mapping */
+ MemoryRegion orig_mem;
+ int rom_mode;
+ int read_counter; /* used for lazy switch-back to rom mode */
+ char *name;
+ void *storage;
+};
+
+/*
+ * Set up replicated mappings of the same region.
+ */
+static void pflash_setup_mappings(pflash_t *pfl)
+{
+ unsigned i;
+ hwaddr size = memory_region_size(&pfl->orig_mem);
+
+ memory_region_init(&pfl->mem, OBJECT(pfl), "pflash", pfl->mappings * size);
+ pfl->mem_mappings = g_new(MemoryRegion, pfl->mappings);
+ for (i = 0; i < pfl->mappings; ++i) {
+ memory_region_init_alias(&pfl->mem_mappings[i], OBJECT(pfl),
+ "pflash-alias", &pfl->orig_mem, 0, size);
+ memory_region_add_subregion(&pfl->mem, i * size, &pfl->mem_mappings[i]);
+ }
+}
+
+static void pflash_register_memory(pflash_t *pfl, int rom_mode)
+{
+ memory_region_rom_device_set_romd(&pfl->orig_mem, rom_mode);
+ pfl->rom_mode = rom_mode;
+}
+
+static void pflash_timer (void *opaque)
+{
+ pflash_t *pfl = opaque;
+
+ DPRINTF("%s: command %02x done\n", __func__, pfl->cmd);
+ /* Reset flash */
+ pfl->status ^= 0x80;
+ if (pfl->bypass) {
+ pfl->wcycle = 2;
+ } else {
+ pflash_register_memory(pfl, 1);
+ pfl->wcycle = 0;
+ }
+ pfl->cmd = 0;
+}
+
+static uint32_t pflash_read (pflash_t *pfl, hwaddr offset,
+ int width, int be)
+{
+ hwaddr boff;
+ uint32_t ret;
+ uint8_t *p;
+
+ DPRINTF("%s: offset " TARGET_FMT_plx "\n", __func__, offset);
+ ret = -1;
+ /* Lazy reset to ROMD mode after a certain amount of read accesses */
+ if (!pfl->rom_mode && pfl->wcycle == 0 &&
+ ++pfl->read_counter > PFLASH_LAZY_ROMD_THRESHOLD) {
+ pflash_register_memory(pfl, 1);
+ }
+ offset &= pfl->chip_len - 1;
+ boff = offset & 0xFF;
+ if (pfl->width == 2)
+ boff = boff >> 1;
+ else if (pfl->width == 4)
+ boff = boff >> 2;
+ switch (pfl->cmd) {
+ default:
+ /* This should never happen : reset state & treat it as a read*/
+ DPRINTF("%s: unknown command state: %x\n", __func__, pfl->cmd);
+ pfl->wcycle = 0;
+ pfl->cmd = 0;
+ /* fall through to the read code */
+ case 0x80:
+ /* We accept reads during second unlock sequence... */
+ case 0x00:
+ flash_read:
+ /* Flash area read */
+ p = pfl->storage;
+ switch (width) {
+ case 1:
+ ret = p[offset];
+// DPRINTF("%s: data offset %08x %02x\n", __func__, offset, ret);
+ break;
+ case 2:
+ if (be) {
+ ret = p[offset] << 8;
+ ret |= p[offset + 1];
+ } else {
+ ret = p[offset];
+ ret |= p[offset + 1] << 8;
+ }
+// DPRINTF("%s: data offset %08x %04x\n", __func__, offset, ret);
+ break;
+ case 4:
+ if (be) {
+ ret = p[offset] << 24;
+ ret |= p[offset + 1] << 16;
+ ret |= p[offset + 2] << 8;
+ ret |= p[offset + 3];
+ } else {
+ ret = p[offset];
+ ret |= p[offset + 1] << 8;
+ ret |= p[offset + 2] << 16;
+ ret |= p[offset + 3] << 24;
+ }
+// DPRINTF("%s: data offset %08x %08x\n", __func__, offset, ret);
+ break;
+ }
+ break;
+ case 0x90:
+ /* flash ID read */
+ switch (boff) {
+ case 0x00:
+ case 0x01:
+ ret = boff & 0x01 ? pfl->ident1 : pfl->ident0;
+ break;
+ case 0x02:
+ ret = 0x00; /* Pretend all sectors are unprotected */
+ break;
+ case 0x0E:
+ case 0x0F:
+ ret = boff & 0x01 ? pfl->ident3 : pfl->ident2;
+ if (ret == (uint8_t)-1) {
+ goto flash_read;
+ }
+ break;
+ default:
+ goto flash_read;
+ }
+ DPRINTF("%s: ID " TARGET_FMT_plx " %x\n", __func__, boff, ret);
+ break;
+ case 0xA0:
+ case 0x10:
+ case 0x30:
+ /* Status register read */
+ ret = pfl->status;
+ DPRINTF("%s: status %x\n", __func__, ret);
+ /* Toggle bit 6 */
+ pfl->status ^= 0x40;
+ break;
+ case 0x98:
+ /* CFI query mode */
+ if (boff > pfl->cfi_len)
+ ret = 0;
+ else
+ ret = pfl->cfi_table[boff];
+ break;
+ }
+
+ return ret;
+}
+
+/* update flash content on disk */
+static void pflash_update(pflash_t *pfl, int offset,
+ int size)
+{
+ int offset_end;
+ if (pfl->blk) {
+ offset_end = offset + size;
+ /* round to sectors */
+ offset = offset >> 9;
+ offset_end = (offset_end + 511) >> 9;
+ blk_write(pfl->blk, offset, pfl->storage + (offset << 9),
+ offset_end - offset);
+ }
+}
+
+static void pflash_write (pflash_t *pfl, hwaddr offset,
+ uint32_t value, int width, int be)
+{
+ hwaddr boff;
+ uint8_t *p;
+ uint8_t cmd;
+
+ cmd = value;
+ if (pfl->cmd != 0xA0 && cmd == 0xF0) {
+#if 0
+ DPRINTF("%s: flash reset asked (%02x %02x)\n",
+ __func__, pfl->cmd, cmd);
+#endif
+ goto reset_flash;
+ }
+ DPRINTF("%s: offset " TARGET_FMT_plx " %08x %d %d\n", __func__,
+ offset, value, width, pfl->wcycle);
+ offset &= pfl->chip_len - 1;
+
+ DPRINTF("%s: offset " TARGET_FMT_plx " %08x %d\n", __func__,
+ offset, value, width);
+ boff = offset & (pfl->sector_len - 1);
+ if (pfl->width == 2)
+ boff = boff >> 1;
+ else if (pfl->width == 4)
+ boff = boff >> 2;
+ switch (pfl->wcycle) {
+ case 0:
+ /* Set the device in I/O access mode if required */
+ if (pfl->rom_mode)
+ pflash_register_memory(pfl, 0);
+ pfl->read_counter = 0;
+ /* We're in read mode */
+ check_unlock0:
+ if (boff == 0x55 && cmd == 0x98) {
+ enter_CFI_mode:
+ /* Enter CFI query mode */
+ pfl->wcycle = 7;
+ pfl->cmd = 0x98;
+ return;
+ }
+ if (boff != pfl->unlock_addr0 || cmd != 0xAA) {
+ DPRINTF("%s: unlock0 failed " TARGET_FMT_plx " %02x %04x\n",
+ __func__, boff, cmd, pfl->unlock_addr0);
+ goto reset_flash;
+ }
+ DPRINTF("%s: unlock sequence started\n", __func__);
+ break;
+ case 1:
+ /* We started an unlock sequence */
+ check_unlock1:
+ if (boff != pfl->unlock_addr1 || cmd != 0x55) {
+ DPRINTF("%s: unlock1 failed " TARGET_FMT_plx " %02x\n", __func__,
+ boff, cmd);
+ goto reset_flash;
+ }
+ DPRINTF("%s: unlock sequence done\n", __func__);
+ break;
+ case 2:
+ /* We finished an unlock sequence */
+ if (!pfl->bypass && boff != pfl->unlock_addr0) {
+ DPRINTF("%s: command failed " TARGET_FMT_plx " %02x\n", __func__,
+ boff, cmd);
+ goto reset_flash;
+ }
+ switch (cmd) {
+ case 0x20:
+ pfl->bypass = 1;
+ goto do_bypass;
+ case 0x80:
+ case 0x90:
+ case 0xA0:
+ pfl->cmd = cmd;
+ DPRINTF("%s: starting command %02x\n", __func__, cmd);
+ break;
+ default:
+ DPRINTF("%s: unknown command %02x\n", __func__, cmd);
+ goto reset_flash;
+ }
+ break;
+ case 3:
+ switch (pfl->cmd) {
+ case 0x80:
+ /* We need another unlock sequence */
+ goto check_unlock0;
+ case 0xA0:
+ DPRINTF("%s: write data offset " TARGET_FMT_plx " %08x %d\n",
+ __func__, offset, value, width);
+ p = pfl->storage;
+ if (!pfl->ro) {
+ switch (width) {
+ case 1:
+ p[offset] &= value;
+ pflash_update(pfl, offset, 1);
+ break;
+ case 2:
+ if (be) {
+ p[offset] &= value >> 8;
+ p[offset + 1] &= value;
+ } else {
+ p[offset] &= value;
+ p[offset + 1] &= value >> 8;
+ }
+ pflash_update(pfl, offset, 2);
+ break;
+ case 4:
+ if (be) {
+ p[offset] &= value >> 24;
+ p[offset + 1] &= value >> 16;
+ p[offset + 2] &= value >> 8;
+ p[offset + 3] &= value;
+ } else {
+ p[offset] &= value;
+ p[offset + 1] &= value >> 8;
+ p[offset + 2] &= value >> 16;
+ p[offset + 3] &= value >> 24;
+ }
+ pflash_update(pfl, offset, 4);
+ break;
+ }
+ }
+ pfl->status = 0x00 | ~(value & 0x80);
+ /* Let's pretend write is immediate */
+ if (pfl->bypass)
+ goto do_bypass;
+ goto reset_flash;
+ case 0x90:
+ if (pfl->bypass && cmd == 0x00) {
+ /* Unlock bypass reset */
+ goto reset_flash;
+ }
+ /* We can enter CFI query mode from autoselect mode */
+ if (boff == 0x55 && cmd == 0x98)
+ goto enter_CFI_mode;
+ /* No break here */
+ default:
+ DPRINTF("%s: invalid write for command %02x\n",
+ __func__, pfl->cmd);
+ goto reset_flash;
+ }
+ case 4:
+ switch (pfl->cmd) {
+ case 0xA0:
+ /* Ignore writes while flash data write is occurring */
+ /* As we suppose write is immediate, this should never happen */
+ return;
+ case 0x80:
+ goto check_unlock1;
+ default:
+ /* Should never happen */
+ DPRINTF("%s: invalid command state %02x (wc 4)\n",
+ __func__, pfl->cmd);
+ goto reset_flash;
+ }
+ break;
+ case 5:
+ switch (cmd) {
+ case 0x10:
+ if (boff != pfl->unlock_addr0) {
+ DPRINTF("%s: chip erase: invalid address " TARGET_FMT_plx "\n",
+ __func__, offset);
+ goto reset_flash;
+ }
+ /* Chip erase */
+ DPRINTF("%s: start chip erase\n", __func__);
+ if (!pfl->ro) {
+ memset(pfl->storage, 0xFF, pfl->chip_len);
+ pflash_update(pfl, 0, pfl->chip_len);
+ }
+ pfl->status = 0x00;
+ /* Let's wait 5 seconds before chip erase is done */
+ timer_mod(pfl->timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() * 5));
+ break;
+ case 0x30:
+ /* Sector erase */
+ p = pfl->storage;
+ offset &= ~(pfl->sector_len - 1);
+ DPRINTF("%s: start sector erase at " TARGET_FMT_plx "\n", __func__,
+ offset);
+ if (!pfl->ro) {
+ memset(p + offset, 0xFF, pfl->sector_len);
+ pflash_update(pfl, offset, pfl->sector_len);
+ }
+ pfl->status = 0x00;
+ /* Let's wait 1/2 second before sector erase is done */
+ timer_mod(pfl->timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() / 2));
+ break;
+ default:
+ DPRINTF("%s: invalid command %02x (wc 5)\n", __func__, cmd);
+ goto reset_flash;
+ }
+ pfl->cmd = cmd;
+ break;
+ case 6:
+ switch (pfl->cmd) {
+ case 0x10:
+ /* Ignore writes during chip erase */
+ return;
+ case 0x30:
+ /* Ignore writes during sector erase */
+ return;
+ default:
+ /* Should never happen */
+ DPRINTF("%s: invalid command state %02x (wc 6)\n",
+ __func__, pfl->cmd);
+ goto reset_flash;
+ }
+ break;
+ case 7: /* Special value for CFI queries */
+ DPRINTF("%s: invalid write in CFI query mode\n", __func__);
+ goto reset_flash;
+ default:
+ /* Should never happen */
+ DPRINTF("%s: invalid write state (wc 7)\n", __func__);
+ goto reset_flash;
+ }
+ pfl->wcycle++;
+
+ return;
+
+ /* Reset flash */
+ reset_flash:
+ pfl->bypass = 0;
+ pfl->wcycle = 0;
+ pfl->cmd = 0;
+ return;
+
+ do_bypass:
+ pfl->wcycle = 2;
+ pfl->cmd = 0;
+}
+
+
+static uint32_t pflash_readb_be(void *opaque, hwaddr addr)
+{
+ return pflash_read(opaque, addr, 1, 1);
+}
+
+static uint32_t pflash_readb_le(void *opaque, hwaddr addr)
+{
+ return pflash_read(opaque, addr, 1, 0);
+}
+
+static uint32_t pflash_readw_be(void *opaque, hwaddr addr)
+{
+ pflash_t *pfl = opaque;
+
+ return pflash_read(pfl, addr, 2, 1);
+}
+
+static uint32_t pflash_readw_le(void *opaque, hwaddr addr)
+{
+ pflash_t *pfl = opaque;
+
+ return pflash_read(pfl, addr, 2, 0);
+}
+
+static uint32_t pflash_readl_be(void *opaque, hwaddr addr)
+{
+ pflash_t *pfl = opaque;
+
+ return pflash_read(pfl, addr, 4, 1);
+}
+
+static uint32_t pflash_readl_le(void *opaque, hwaddr addr)
+{
+ pflash_t *pfl = opaque;
+
+ return pflash_read(pfl, addr, 4, 0);
+}
+
+static void pflash_writeb_be(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ pflash_write(opaque, addr, value, 1, 1);
+}
+
+static void pflash_writeb_le(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ pflash_write(opaque, addr, value, 1, 0);
+}
+
+static void pflash_writew_be(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ pflash_t *pfl = opaque;
+
+ pflash_write(pfl, addr, value, 2, 1);
+}
+
+static void pflash_writew_le(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ pflash_t *pfl = opaque;
+
+ pflash_write(pfl, addr, value, 2, 0);
+}
+
+static void pflash_writel_be(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ pflash_t *pfl = opaque;
+
+ pflash_write(pfl, addr, value, 4, 1);
+}
+
+static void pflash_writel_le(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ pflash_t *pfl = opaque;
+
+ pflash_write(pfl, addr, value, 4, 0);
+}
+
+static const MemoryRegionOps pflash_cfi02_ops_be = {
+ .old_mmio = {
+ .read = { pflash_readb_be, pflash_readw_be, pflash_readl_be, },
+ .write = { pflash_writeb_be, pflash_writew_be, pflash_writel_be, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps pflash_cfi02_ops_le = {
+ .old_mmio = {
+ .read = { pflash_readb_le, pflash_readw_le, pflash_readl_le, },
+ .write = { pflash_writeb_le, pflash_writew_le, pflash_writel_le, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
+{
+ pflash_t *pfl = CFI_PFLASH02(dev);
+ uint32_t chip_len;
+ int ret;
+ Error *local_err = NULL;
+
+ chip_len = pfl->sector_len * pfl->nb_blocs;
+ /* XXX: to be fixed */
+#if 0
+ if (total_len != (8 * 1024 * 1024) && total_len != (16 * 1024 * 1024) &&
+ total_len != (32 * 1024 * 1024) && total_len != (64 * 1024 * 1024))
+ return NULL;
+#endif
+
+ memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl), pfl->be ?
+ &pflash_cfi02_ops_be : &pflash_cfi02_ops_le,
+ pfl, pfl->name, chip_len, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ vmstate_register_ram(&pfl->orig_mem, DEVICE(pfl));
+ pfl->storage = memory_region_get_ram_ptr(&pfl->orig_mem);
+ pfl->chip_len = chip_len;
+ if (pfl->blk) {
+ /* read the initial flash content */
+ ret = blk_read(pfl->blk, 0, pfl->storage, chip_len >> 9);
+ if (ret < 0) {
+ vmstate_unregister_ram(&pfl->orig_mem, DEVICE(pfl));
+ error_setg(errp, "failed to read the initial flash content");
+ return;
+ }
+ }
+
+ pflash_setup_mappings(pfl);
+ pfl->rom_mode = 1;
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &pfl->mem);
+
+ if (pfl->blk) {
+ pfl->ro = blk_is_read_only(pfl->blk);
+ } else {
+ pfl->ro = 0;
+ }
+
+ pfl->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, pflash_timer, pfl);
+ pfl->wcycle = 0;
+ pfl->cmd = 0;
+ pfl->status = 0;
+ /* Hardcoded CFI table (mostly from SG29 Spansion flash) */
+ pfl->cfi_len = 0x52;
+ /* Standard "QRY" string */
+ pfl->cfi_table[0x10] = 'Q';
+ pfl->cfi_table[0x11] = 'R';
+ pfl->cfi_table[0x12] = 'Y';
+ /* Command set (AMD/Fujitsu) */
+ pfl->cfi_table[0x13] = 0x02;
+ pfl->cfi_table[0x14] = 0x00;
+ /* Primary extended table address */
+ pfl->cfi_table[0x15] = 0x31;
+ pfl->cfi_table[0x16] = 0x00;
+ /* Alternate command set (none) */
+ pfl->cfi_table[0x17] = 0x00;
+ pfl->cfi_table[0x18] = 0x00;
+ /* Alternate extended table (none) */
+ pfl->cfi_table[0x19] = 0x00;
+ pfl->cfi_table[0x1A] = 0x00;
+ /* Vcc min */
+ pfl->cfi_table[0x1B] = 0x27;
+ /* Vcc max */
+ pfl->cfi_table[0x1C] = 0x36;
+ /* Vpp min (no Vpp pin) */
+ pfl->cfi_table[0x1D] = 0x00;
+ /* Vpp max (no Vpp pin) */
+ pfl->cfi_table[0x1E] = 0x00;
+ /* Reserved */
+ pfl->cfi_table[0x1F] = 0x07;
+ /* Timeout for min size buffer write (NA) */
+ pfl->cfi_table[0x20] = 0x00;
+ /* Typical timeout for block erase (512 ms) */
+ pfl->cfi_table[0x21] = 0x09;
+ /* Typical timeout for full chip erase (4096 ms) */
+ pfl->cfi_table[0x22] = 0x0C;
+ /* Reserved */
+ pfl->cfi_table[0x23] = 0x01;
+ /* Max timeout for buffer write (NA) */
+ pfl->cfi_table[0x24] = 0x00;
+ /* Max timeout for block erase */
+ pfl->cfi_table[0x25] = 0x0A;
+ /* Max timeout for chip erase */
+ pfl->cfi_table[0x26] = 0x0D;
+ /* Device size */
+ pfl->cfi_table[0x27] = ctz32(chip_len);
+ /* Flash device interface (8 & 16 bits) */
+ pfl->cfi_table[0x28] = 0x02;
+ pfl->cfi_table[0x29] = 0x00;
+ /* Max number of bytes in multi-bytes write */
+ /* XXX: disable buffered write as it's not supported */
+ // pfl->cfi_table[0x2A] = 0x05;
+ pfl->cfi_table[0x2A] = 0x00;
+ pfl->cfi_table[0x2B] = 0x00;
+ /* Number of erase block regions (uniform) */
+ pfl->cfi_table[0x2C] = 0x01;
+ /* Erase block region 1 */
+ pfl->cfi_table[0x2D] = pfl->nb_blocs - 1;
+ pfl->cfi_table[0x2E] = (pfl->nb_blocs - 1) >> 8;
+ pfl->cfi_table[0x2F] = pfl->sector_len >> 8;
+ pfl->cfi_table[0x30] = pfl->sector_len >> 16;
+
+ /* Extended */
+ pfl->cfi_table[0x31] = 'P';
+ pfl->cfi_table[0x32] = 'R';
+ pfl->cfi_table[0x33] = 'I';
+
+ pfl->cfi_table[0x34] = '1';
+ pfl->cfi_table[0x35] = '0';
+
+ pfl->cfi_table[0x36] = 0x00;
+ pfl->cfi_table[0x37] = 0x00;
+ pfl->cfi_table[0x38] = 0x00;
+ pfl->cfi_table[0x39] = 0x00;
+
+ pfl->cfi_table[0x3a] = 0x00;
+
+ pfl->cfi_table[0x3b] = 0x00;
+ pfl->cfi_table[0x3c] = 0x00;
+}
+
+static Property pflash_cfi02_properties[] = {
+ DEFINE_PROP_DRIVE("drive", struct pflash_t, blk),
+ DEFINE_PROP_UINT32("num-blocks", struct pflash_t, nb_blocs, 0),
+ DEFINE_PROP_UINT32("sector-length", struct pflash_t, sector_len, 0),
+ DEFINE_PROP_UINT8("width", struct pflash_t, width, 0),
+ DEFINE_PROP_UINT8("mappings", struct pflash_t, mappings, 0),
+ DEFINE_PROP_UINT8("big-endian", struct pflash_t, be, 0),
+ DEFINE_PROP_UINT16("id0", struct pflash_t, ident0, 0),
+ DEFINE_PROP_UINT16("id1", struct pflash_t, ident1, 0),
+ DEFINE_PROP_UINT16("id2", struct pflash_t, ident2, 0),
+ DEFINE_PROP_UINT16("id3", struct pflash_t, ident3, 0),
+ DEFINE_PROP_UINT16("unlock-addr0", struct pflash_t, unlock_addr0, 0),
+ DEFINE_PROP_UINT16("unlock-addr1", struct pflash_t, unlock_addr1, 0),
+ DEFINE_PROP_STRING("name", struct pflash_t, name),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pflash_cfi02_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pflash_cfi02_realize;
+ dc->props = pflash_cfi02_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo pflash_cfi02_info = {
+ .name = TYPE_CFI_PFLASH02,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct pflash_t),
+ .class_init = pflash_cfi02_class_init,
+};
+
+static void pflash_cfi02_register_types(void)
+{
+ type_register_static(&pflash_cfi02_info);
+}
+
+type_init(pflash_cfi02_register_types)
+
+pflash_t *pflash_cfi02_register(hwaddr base,
+ DeviceState *qdev, const char *name,
+ hwaddr size,
+ BlockBackend *blk, uint32_t sector_len,
+ int nb_blocs, int nb_mappings, int width,
+ uint16_t id0, uint16_t id1,
+ uint16_t id2, uint16_t id3,
+ uint16_t unlock_addr0, uint16_t unlock_addr1,
+ int be)
+{
+ DeviceState *dev = qdev_create(NULL, TYPE_CFI_PFLASH02);
+
+ if (blk) {
+ qdev_prop_set_drive(dev, "drive", blk, &error_abort);
+ }
+ qdev_prop_set_uint32(dev, "num-blocks", nb_blocs);
+ qdev_prop_set_uint32(dev, "sector-length", sector_len);
+ qdev_prop_set_uint8(dev, "width", width);
+ qdev_prop_set_uint8(dev, "mappings", nb_mappings);
+ qdev_prop_set_uint8(dev, "big-endian", !!be);
+ qdev_prop_set_uint16(dev, "id0", id0);
+ qdev_prop_set_uint16(dev, "id1", id1);
+ qdev_prop_set_uint16(dev, "id2", id2);
+ qdev_prop_set_uint16(dev, "id3", id3);
+ qdev_prop_set_uint16(dev, "unlock-addr0", unlock_addr0);
+ qdev_prop_set_uint16(dev, "unlock-addr1", unlock_addr1);
+ qdev_prop_set_string(dev, "name", name);
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ return CFI_PFLASH02(dev);
+}
diff --git a/hw/block/tc58128.c b/hw/block/tc58128.c
new file mode 100644
index 00000000..728f1c3b
--- /dev/null
+++ b/hw/block/tc58128.c
@@ -0,0 +1,180 @@
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "hw/loader.h"
+#include "sysemu/qtest.h"
+#include "qemu/error-report.h"
+
+#define CE1 0x0100
+#define CE2 0x0200
+#define RE 0x0400
+#define WE 0x0800
+#define ALE 0x1000
+#define CLE 0x2000
+#define RDY1 0x4000
+#define RDY2 0x8000
+#define RDY(n) ((n) == 0 ? RDY1 : RDY2)
+
+typedef enum { WAIT, READ1, READ2, READ3 } state_t;
+
+typedef struct {
+ uint8_t *flash_contents;
+ state_t state;
+ uint32_t address;
+ uint8_t address_cycle;
+} tc58128_dev;
+
+static tc58128_dev tc58128_devs[2];
+
+#define FLASH_SIZE (16*1024*1024)
+
+static void init_dev(tc58128_dev * dev, const char *filename)
+{
+ int ret, blocks;
+
+ dev->state = WAIT;
+ dev->flash_contents = g_malloc(FLASH_SIZE);
+ memset(dev->flash_contents, 0xff, FLASH_SIZE);
+ if (filename) {
+ /* Load flash image skipping the first block */
+ ret = load_image(filename, dev->flash_contents + 528 * 32);
+ if (ret < 0) {
+ if (!qtest_enabled()) {
+ error_report("Could not load flash image %s", filename);
+ exit(1);
+ }
+ } else {
+ /* Build first block with number of blocks */
+ blocks = (ret + 528 * 32 - 1) / (528 * 32);
+ dev->flash_contents[0] = blocks & 0xff;
+ dev->flash_contents[1] = (blocks >> 8) & 0xff;
+ dev->flash_contents[2] = (blocks >> 16) & 0xff;
+ dev->flash_contents[3] = (blocks >> 24) & 0xff;
+ fprintf(stderr, "loaded %d bytes for %s into flash\n", ret,
+ filename);
+ }
+ }
+}
+
+static void handle_command(tc58128_dev * dev, uint8_t command)
+{
+ switch (command) {
+ case 0xff:
+ fprintf(stderr, "reset flash device\n");
+ dev->state = WAIT;
+ break;
+ case 0x00:
+ fprintf(stderr, "read mode 1\n");
+ dev->state = READ1;
+ dev->address_cycle = 0;
+ break;
+ case 0x01:
+ fprintf(stderr, "read mode 2\n");
+ dev->state = READ2;
+ dev->address_cycle = 0;
+ break;
+ case 0x50:
+ fprintf(stderr, "read mode 3\n");
+ dev->state = READ3;
+ dev->address_cycle = 0;
+ break;
+ default:
+ fprintf(stderr, "unknown flash command 0x%02x\n", command);
+ abort();
+ }
+}
+
+static void handle_address(tc58128_dev * dev, uint8_t data)
+{
+ switch (dev->state) {
+ case READ1:
+ case READ2:
+ case READ3:
+ switch (dev->address_cycle) {
+ case 0:
+ dev->address = data;
+ if (dev->state == READ2)
+ dev->address |= 0x100;
+ else if (dev->state == READ3)
+ dev->address |= 0x200;
+ break;
+ case 1:
+ dev->address += data * 528 * 0x100;
+ break;
+ case 2:
+ dev->address += data * 528;
+ fprintf(stderr, "address pointer in flash: 0x%08x\n",
+ dev->address);
+ break;
+ default:
+ /* Invalid data */
+ abort();
+ }
+ dev->address_cycle++;
+ break;
+ default:
+ abort();
+ }
+}
+
+static uint8_t handle_read(tc58128_dev * dev)
+{
+#if 0
+ if (dev->address % 0x100000 == 0)
+ fprintf(stderr, "reading flash at address 0x%08x\n", dev->address);
+#endif
+ return dev->flash_contents[dev->address++];
+}
+
+/* We never mark the device as busy, so interrupts cannot be triggered
+ XXXXX */
+
+static int tc58128_cb(uint16_t porta, uint16_t portb,
+ uint16_t * periph_pdtra, uint16_t * periph_portadir,
+ uint16_t * periph_pdtrb, uint16_t * periph_portbdir)
+{
+ int dev;
+
+ if ((porta & CE1) == 0)
+ dev = 0;
+ else if ((porta & CE2) == 0)
+ dev = 1;
+ else
+ return 0; /* No device selected */
+
+ if ((porta & RE) && (porta & WE)) {
+ /* Nothing to do, assert ready and return to input state */
+ *periph_portadir &= 0xff00;
+ *periph_portadir |= RDY(dev);
+ *periph_pdtra |= RDY(dev);
+ return 1;
+ }
+
+ if (porta & CLE) {
+ /* Command */
+ assert((porta & WE) == 0);
+ handle_command(&tc58128_devs[dev], porta & 0x00ff);
+ } else if (porta & ALE) {
+ assert((porta & WE) == 0);
+ handle_address(&tc58128_devs[dev], porta & 0x00ff);
+ } else if ((porta & RE) == 0) {
+ *periph_portadir |= 0x00ff;
+ *periph_pdtra &= 0xff00;
+ *periph_pdtra |= handle_read(&tc58128_devs[dev]);
+ } else {
+ abort();
+ }
+ return 1;
+}
+
+static sh7750_io_device tc58128 = {
+ RE | WE, /* Port A triggers */
+ 0, /* Port B triggers */
+ tc58128_cb /* Callback */
+};
+
+int tc58128_init(struct SH7750State *s, const char *zone1, const char *zone2)
+{
+ init_dev(&tc58128_devs[0], zone1);
+ init_dev(&tc58128_devs[1], zone2);
+ return sh7750_register_io_device(s, &tc58128);
+}
diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
new file mode 100644
index 00000000..5625a9fa
--- /dev/null
+++ b/hw/block/virtio-blk.c
@@ -0,0 +1,1018 @@
+/*
+ * Virtio Block Device
+ *
+ * Copyright IBM, Corp. 2007
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu-common.h"
+#include "qemu/iov.h"
+#include "qemu/error-report.h"
+#include "trace.h"
+#include "hw/block/block.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/virtio/virtio-blk.h"
+#include "dataplane/virtio-blk.h"
+#include "migration/migration.h"
+#include "block/scsi.h"
+#ifdef __linux__
+# include <scsi/sg.h>
+#endif
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+
+VirtIOBlockReq *virtio_blk_alloc_request(VirtIOBlock *s)
+{
+ VirtIOBlockReq *req = g_slice_new(VirtIOBlockReq);
+ req->dev = s;
+ req->qiov.size = 0;
+ req->in_len = 0;
+ req->next = NULL;
+ req->mr_next = NULL;
+ return req;
+}
+
+void virtio_blk_free_request(VirtIOBlockReq *req)
+{
+ if (req) {
+ g_slice_free(VirtIOBlockReq, req);
+ }
+}
+
+static void virtio_blk_complete_request(VirtIOBlockReq *req,
+ unsigned char status)
+{
+ VirtIOBlock *s = req->dev;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ trace_virtio_blk_req_complete(req, status);
+
+ stb_p(&req->in->status, status);
+ virtqueue_push(s->vq, &req->elem, req->in_len);
+ virtio_notify(vdev, s->vq);
+}
+
+static void virtio_blk_req_complete(VirtIOBlockReq *req, unsigned char status)
+{
+ req->dev->complete_request(req, status);
+}
+
+static int virtio_blk_handle_rw_error(VirtIOBlockReq *req, int error,
+ bool is_read)
+{
+ BlockErrorAction action = blk_get_error_action(req->dev->blk,
+ is_read, error);
+ VirtIOBlock *s = req->dev;
+
+ if (action == BLOCK_ERROR_ACTION_STOP) {
+ req->next = s->rq;
+ s->rq = req;
+ } else if (action == BLOCK_ERROR_ACTION_REPORT) {
+ virtio_blk_req_complete(req, VIRTIO_BLK_S_IOERR);
+ block_acct_done(blk_get_stats(s->blk), &req->acct);
+ virtio_blk_free_request(req);
+ }
+
+ blk_error_action(s->blk, action, is_read, error);
+ return action != BLOCK_ERROR_ACTION_IGNORE;
+}
+
+static void virtio_blk_rw_complete(void *opaque, int ret)
+{
+ VirtIOBlockReq *next = opaque;
+
+ while (next) {
+ VirtIOBlockReq *req = next;
+ next = req->mr_next;
+ trace_virtio_blk_rw_complete(req, ret);
+
+ if (req->qiov.nalloc != -1) {
+ /* If nalloc is != 1 req->qiov is a local copy of the original
+ * external iovec. It was allocated in submit_merged_requests
+ * to be able to merge requests. */
+ qemu_iovec_destroy(&req->qiov);
+ }
+
+ if (ret) {
+ int p = virtio_ldl_p(VIRTIO_DEVICE(req->dev), &req->out.type);
+ bool is_read = !(p & VIRTIO_BLK_T_OUT);
+ /* Note that memory may be dirtied on read failure. If the
+ * virtio request is not completed here, as is the case for
+ * BLOCK_ERROR_ACTION_STOP, the memory may not be copied
+ * correctly during live migration. While this is ugly,
+ * it is acceptable because the device is free to write to
+ * the memory until the request is completed (which will
+ * happen on the other side of the migration).
+ */
+ if (virtio_blk_handle_rw_error(req, -ret, is_read)) {
+ continue;
+ }
+ }
+
+ virtio_blk_req_complete(req, VIRTIO_BLK_S_OK);
+ block_acct_done(blk_get_stats(req->dev->blk), &req->acct);
+ virtio_blk_free_request(req);
+ }
+}
+
+static void virtio_blk_flush_complete(void *opaque, int ret)
+{
+ VirtIOBlockReq *req = opaque;
+
+ if (ret) {
+ if (virtio_blk_handle_rw_error(req, -ret, 0)) {
+ return;
+ }
+ }
+
+ virtio_blk_req_complete(req, VIRTIO_BLK_S_OK);
+ block_acct_done(blk_get_stats(req->dev->blk), &req->acct);
+ virtio_blk_free_request(req);
+}
+
+#ifdef __linux__
+
+typedef struct {
+ VirtIOBlockReq *req;
+ struct sg_io_hdr hdr;
+} VirtIOBlockIoctlReq;
+
+static void virtio_blk_ioctl_complete(void *opaque, int status)
+{
+ VirtIOBlockIoctlReq *ioctl_req = opaque;
+ VirtIOBlockReq *req = ioctl_req->req;
+ VirtIODevice *vdev = VIRTIO_DEVICE(req->dev);
+ struct virtio_scsi_inhdr *scsi;
+ struct sg_io_hdr *hdr;
+
+ scsi = (void *)req->elem.in_sg[req->elem.in_num - 2].iov_base;
+
+ if (status) {
+ status = VIRTIO_BLK_S_UNSUPP;
+ virtio_stl_p(vdev, &scsi->errors, 255);
+ goto out;
+ }
+
+ hdr = &ioctl_req->hdr;
+ /*
+ * From SCSI-Generic-HOWTO: "Some lower level drivers (e.g. ide-scsi)
+ * clear the masked_status field [hence status gets cleared too, see
+ * block/scsi_ioctl.c] even when a CHECK_CONDITION or COMMAND_TERMINATED
+ * status has occurred. However they do set DRIVER_SENSE in driver_status
+ * field. Also a (sb_len_wr > 0) indicates there is a sense buffer.
+ */
+ if (hdr->status == 0 && hdr->sb_len_wr > 0) {
+ hdr->status = CHECK_CONDITION;
+ }
+
+ virtio_stl_p(vdev, &scsi->errors,
+ hdr->status | (hdr->msg_status << 8) |
+ (hdr->host_status << 16) | (hdr->driver_status << 24));
+ virtio_stl_p(vdev, &scsi->residual, hdr->resid);
+ virtio_stl_p(vdev, &scsi->sense_len, hdr->sb_len_wr);
+ virtio_stl_p(vdev, &scsi->data_len, hdr->dxfer_len);
+
+out:
+ virtio_blk_req_complete(req, status);
+ virtio_blk_free_request(req);
+ g_free(ioctl_req);
+}
+
+#endif
+
+static VirtIOBlockReq *virtio_blk_get_request(VirtIOBlock *s)
+{
+ VirtIOBlockReq *req = virtio_blk_alloc_request(s);
+
+ if (!virtqueue_pop(s->vq, &req->elem)) {
+ virtio_blk_free_request(req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static int virtio_blk_handle_scsi_req(VirtIOBlockReq *req)
+{
+ int status = VIRTIO_BLK_S_OK;
+ struct virtio_scsi_inhdr *scsi = NULL;
+ VirtIODevice *vdev = VIRTIO_DEVICE(req->dev);
+ VirtQueueElement *elem = &req->elem;
+ VirtIOBlock *blk = req->dev;
+
+#ifdef __linux__
+ int i;
+ VirtIOBlockIoctlReq *ioctl_req;
+ BlockAIOCB *acb;
+#endif
+
+ /*
+ * We require at least one output segment each for the virtio_blk_outhdr
+ * and the SCSI command block.
+ *
+ * We also at least require the virtio_blk_inhdr, the virtio_scsi_inhdr
+ * and the sense buffer pointer in the input segments.
+ */
+ if (elem->out_num < 2 || elem->in_num < 3) {
+ status = VIRTIO_BLK_S_IOERR;
+ goto fail;
+ }
+
+ /*
+ * The scsi inhdr is placed in the second-to-last input segment, just
+ * before the regular inhdr.
+ */
+ scsi = (void *)elem->in_sg[elem->in_num - 2].iov_base;
+
+ if (!blk->conf.scsi) {
+ status = VIRTIO_BLK_S_UNSUPP;
+ goto fail;
+ }
+
+ /*
+ * No support for bidirection commands yet.
+ */
+ if (elem->out_num > 2 && elem->in_num > 3) {
+ status = VIRTIO_BLK_S_UNSUPP;
+ goto fail;
+ }
+
+#ifdef __linux__
+ ioctl_req = g_new0(VirtIOBlockIoctlReq, 1);
+ ioctl_req->req = req;
+ ioctl_req->hdr.interface_id = 'S';
+ ioctl_req->hdr.cmd_len = elem->out_sg[1].iov_len;
+ ioctl_req->hdr.cmdp = elem->out_sg[1].iov_base;
+ ioctl_req->hdr.dxfer_len = 0;
+
+ if (elem->out_num > 2) {
+ /*
+ * If there are more than the minimally required 2 output segments
+ * there is write payload starting from the third iovec.
+ */
+ ioctl_req->hdr.dxfer_direction = SG_DXFER_TO_DEV;
+ ioctl_req->hdr.iovec_count = elem->out_num - 2;
+
+ for (i = 0; i < ioctl_req->hdr.iovec_count; i++) {
+ ioctl_req->hdr.dxfer_len += elem->out_sg[i + 2].iov_len;
+ }
+
+ ioctl_req->hdr.dxferp = elem->out_sg + 2;
+
+ } else if (elem->in_num > 3) {
+ /*
+ * If we have more than 3 input segments the guest wants to actually
+ * read data.
+ */
+ ioctl_req->hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ ioctl_req->hdr.iovec_count = elem->in_num - 3;
+ for (i = 0; i < ioctl_req->hdr.iovec_count; i++) {
+ ioctl_req->hdr.dxfer_len += elem->in_sg[i].iov_len;
+ }
+
+ ioctl_req->hdr.dxferp = elem->in_sg;
+ } else {
+ /*
+ * Some SCSI commands don't actually transfer any data.
+ */
+ ioctl_req->hdr.dxfer_direction = SG_DXFER_NONE;
+ }
+
+ ioctl_req->hdr.sbp = elem->in_sg[elem->in_num - 3].iov_base;
+ ioctl_req->hdr.mx_sb_len = elem->in_sg[elem->in_num - 3].iov_len;
+
+ acb = blk_aio_ioctl(blk->blk, SG_IO, &ioctl_req->hdr,
+ virtio_blk_ioctl_complete, ioctl_req);
+ if (!acb) {
+ g_free(ioctl_req);
+ status = VIRTIO_BLK_S_UNSUPP;
+ goto fail;
+ }
+ return -EINPROGRESS;
+#else
+ abort();
+#endif
+
+fail:
+ /* Just put anything nonzero so that the ioctl fails in the guest. */
+ if (scsi) {
+ virtio_stl_p(vdev, &scsi->errors, 255);
+ }
+ return status;
+}
+
+static void virtio_blk_handle_scsi(VirtIOBlockReq *req)
+{
+ int status;
+
+ status = virtio_blk_handle_scsi_req(req);
+ if (status != -EINPROGRESS) {
+ virtio_blk_req_complete(req, status);
+ virtio_blk_free_request(req);
+ }
+}
+
+static inline void submit_requests(BlockBackend *blk, MultiReqBuffer *mrb,
+ int start, int num_reqs, int niov)
+{
+ QEMUIOVector *qiov = &mrb->reqs[start]->qiov;
+ int64_t sector_num = mrb->reqs[start]->sector_num;
+ int nb_sectors = mrb->reqs[start]->qiov.size / BDRV_SECTOR_SIZE;
+ bool is_write = mrb->is_write;
+
+ if (num_reqs > 1) {
+ int i;
+ struct iovec *tmp_iov = qiov->iov;
+ int tmp_niov = qiov->niov;
+
+ /* mrb->reqs[start]->qiov was initialized from external so we can't
+ * modifiy it here. We need to initialize it locally and then add the
+ * external iovecs. */
+ qemu_iovec_init(qiov, niov);
+
+ for (i = 0; i < tmp_niov; i++) {
+ qemu_iovec_add(qiov, tmp_iov[i].iov_base, tmp_iov[i].iov_len);
+ }
+
+ for (i = start + 1; i < start + num_reqs; i++) {
+ qemu_iovec_concat(qiov, &mrb->reqs[i]->qiov, 0,
+ mrb->reqs[i]->qiov.size);
+ mrb->reqs[i - 1]->mr_next = mrb->reqs[i];
+ nb_sectors += mrb->reqs[i]->qiov.size / BDRV_SECTOR_SIZE;
+ }
+ assert(nb_sectors == qiov->size / BDRV_SECTOR_SIZE);
+
+ trace_virtio_blk_submit_multireq(mrb, start, num_reqs, sector_num,
+ nb_sectors, is_write);
+ block_acct_merge_done(blk_get_stats(blk),
+ is_write ? BLOCK_ACCT_WRITE : BLOCK_ACCT_READ,
+ num_reqs - 1);
+ }
+
+ if (is_write) {
+ blk_aio_writev(blk, sector_num, qiov, nb_sectors,
+ virtio_blk_rw_complete, mrb->reqs[start]);
+ } else {
+ blk_aio_readv(blk, sector_num, qiov, nb_sectors,
+ virtio_blk_rw_complete, mrb->reqs[start]);
+ }
+}
+
+static int multireq_compare(const void *a, const void *b)
+{
+ const VirtIOBlockReq *req1 = *(VirtIOBlockReq **)a,
+ *req2 = *(VirtIOBlockReq **)b;
+
+ /*
+ * Note that we can't simply subtract sector_num1 from sector_num2
+ * here as that could overflow the return value.
+ */
+ if (req1->sector_num > req2->sector_num) {
+ return 1;
+ } else if (req1->sector_num < req2->sector_num) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+void virtio_blk_submit_multireq(BlockBackend *blk, MultiReqBuffer *mrb)
+{
+ int i = 0, start = 0, num_reqs = 0, niov = 0, nb_sectors = 0;
+ int max_xfer_len = 0;
+ int64_t sector_num = 0;
+
+ if (mrb->num_reqs == 1) {
+ submit_requests(blk, mrb, 0, 1, -1);
+ mrb->num_reqs = 0;
+ return;
+ }
+
+ max_xfer_len = blk_get_max_transfer_length(mrb->reqs[0]->dev->blk);
+ max_xfer_len = MIN_NON_ZERO(max_xfer_len, BDRV_REQUEST_MAX_SECTORS);
+
+ qsort(mrb->reqs, mrb->num_reqs, sizeof(*mrb->reqs),
+ &multireq_compare);
+
+ for (i = 0; i < mrb->num_reqs; i++) {
+ VirtIOBlockReq *req = mrb->reqs[i];
+ if (num_reqs > 0) {
+ bool merge = true;
+
+ /* merge would exceed maximum number of IOVs */
+ if (niov + req->qiov.niov > IOV_MAX) {
+ merge = false;
+ }
+
+ /* merge would exceed maximum transfer length of backend device */
+ if (req->qiov.size / BDRV_SECTOR_SIZE + nb_sectors > max_xfer_len) {
+ merge = false;
+ }
+
+ /* requests are not sequential */
+ if (sector_num + nb_sectors != req->sector_num) {
+ merge = false;
+ }
+
+ if (!merge) {
+ submit_requests(blk, mrb, start, num_reqs, niov);
+ num_reqs = 0;
+ }
+ }
+
+ if (num_reqs == 0) {
+ sector_num = req->sector_num;
+ nb_sectors = niov = 0;
+ start = i;
+ }
+
+ nb_sectors += req->qiov.size / BDRV_SECTOR_SIZE;
+ niov += req->qiov.niov;
+ num_reqs++;
+ }
+
+ submit_requests(blk, mrb, start, num_reqs, niov);
+ mrb->num_reqs = 0;
+}
+
+static void virtio_blk_handle_flush(VirtIOBlockReq *req, MultiReqBuffer *mrb)
+{
+ block_acct_start(blk_get_stats(req->dev->blk), &req->acct, 0,
+ BLOCK_ACCT_FLUSH);
+
+ /*
+ * Make sure all outstanding writes are posted to the backing device.
+ */
+ if (mrb->is_write && mrb->num_reqs > 0) {
+ virtio_blk_submit_multireq(req->dev->blk, mrb);
+ }
+ blk_aio_flush(req->dev->blk, virtio_blk_flush_complete, req);
+}
+
+static bool virtio_blk_sect_range_ok(VirtIOBlock *dev,
+ uint64_t sector, size_t size)
+{
+ uint64_t nb_sectors = size >> BDRV_SECTOR_BITS;
+ uint64_t total_sectors;
+
+ if (nb_sectors > BDRV_REQUEST_MAX_SECTORS) {
+ return false;
+ }
+ if (sector & dev->sector_mask) {
+ return false;
+ }
+ if (size % dev->conf.conf.logical_block_size) {
+ return false;
+ }
+ blk_get_geometry(dev->blk, &total_sectors);
+ if (sector > total_sectors || nb_sectors > total_sectors - sector) {
+ return false;
+ }
+ return true;
+}
+
+void virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb)
+{
+ uint32_t type;
+ struct iovec *in_iov = req->elem.in_sg;
+ struct iovec *iov = req->elem.out_sg;
+ unsigned in_num = req->elem.in_num;
+ unsigned out_num = req->elem.out_num;
+
+ if (req->elem.out_num < 1 || req->elem.in_num < 1) {
+ error_report("virtio-blk missing headers");
+ exit(1);
+ }
+
+ if (unlikely(iov_to_buf(iov, out_num, 0, &req->out,
+ sizeof(req->out)) != sizeof(req->out))) {
+ error_report("virtio-blk request outhdr too short");
+ exit(1);
+ }
+
+ iov_discard_front(&iov, &out_num, sizeof(req->out));
+
+ if (in_iov[in_num - 1].iov_len < sizeof(struct virtio_blk_inhdr)) {
+ error_report("virtio-blk request inhdr too short");
+ exit(1);
+ }
+
+ /* We always touch the last byte, so just see how big in_iov is. */
+ req->in_len = iov_size(in_iov, in_num);
+ req->in = (void *)in_iov[in_num - 1].iov_base
+ + in_iov[in_num - 1].iov_len
+ - sizeof(struct virtio_blk_inhdr);
+ iov_discard_back(in_iov, &in_num, sizeof(struct virtio_blk_inhdr));
+
+ type = virtio_ldl_p(VIRTIO_DEVICE(req->dev), &req->out.type);
+
+ /* VIRTIO_BLK_T_OUT defines the command direction. VIRTIO_BLK_T_BARRIER
+ * is an optional flag. Although a guest should not send this flag if
+ * not negotiated we ignored it in the past. So keep ignoring it. */
+ switch (type & ~(VIRTIO_BLK_T_OUT | VIRTIO_BLK_T_BARRIER)) {
+ case VIRTIO_BLK_T_IN:
+ {
+ bool is_write = type & VIRTIO_BLK_T_OUT;
+ req->sector_num = virtio_ldq_p(VIRTIO_DEVICE(req->dev),
+ &req->out.sector);
+
+ if (is_write) {
+ qemu_iovec_init_external(&req->qiov, iov, out_num);
+ trace_virtio_blk_handle_write(req, req->sector_num,
+ req->qiov.size / BDRV_SECTOR_SIZE);
+ } else {
+ qemu_iovec_init_external(&req->qiov, in_iov, in_num);
+ trace_virtio_blk_handle_read(req, req->sector_num,
+ req->qiov.size / BDRV_SECTOR_SIZE);
+ }
+
+ if (!virtio_blk_sect_range_ok(req->dev, req->sector_num,
+ req->qiov.size)) {
+ virtio_blk_req_complete(req, VIRTIO_BLK_S_IOERR);
+ virtio_blk_free_request(req);
+ return;
+ }
+
+ block_acct_start(blk_get_stats(req->dev->blk),
+ &req->acct, req->qiov.size,
+ is_write ? BLOCK_ACCT_WRITE : BLOCK_ACCT_READ);
+
+ /* merge would exceed maximum number of requests or IO direction
+ * changes */
+ if (mrb->num_reqs > 0 && (mrb->num_reqs == VIRTIO_BLK_MAX_MERGE_REQS ||
+ is_write != mrb->is_write ||
+ !req->dev->conf.request_merging)) {
+ virtio_blk_submit_multireq(req->dev->blk, mrb);
+ }
+
+ assert(mrb->num_reqs < VIRTIO_BLK_MAX_MERGE_REQS);
+ mrb->reqs[mrb->num_reqs++] = req;
+ mrb->is_write = is_write;
+ break;
+ }
+ case VIRTIO_BLK_T_FLUSH:
+ virtio_blk_handle_flush(req, mrb);
+ break;
+ case VIRTIO_BLK_T_SCSI_CMD:
+ virtio_blk_handle_scsi(req);
+ break;
+ case VIRTIO_BLK_T_GET_ID:
+ {
+ VirtIOBlock *s = req->dev;
+
+ /*
+ * NB: per existing s/n string convention the string is
+ * terminated by '\0' only when shorter than buffer.
+ */
+ const char *serial = s->conf.serial ? s->conf.serial : "";
+ size_t size = MIN(strlen(serial) + 1,
+ MIN(iov_size(in_iov, in_num),
+ VIRTIO_BLK_ID_BYTES));
+ iov_from_buf(in_iov, in_num, 0, serial, size);
+ virtio_blk_req_complete(req, VIRTIO_BLK_S_OK);
+ virtio_blk_free_request(req);
+ break;
+ }
+ default:
+ virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP);
+ virtio_blk_free_request(req);
+ }
+}
+
+static void virtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+ VirtIOBlockReq *req;
+ MultiReqBuffer mrb = {};
+
+ /* Some guests kick before setting VIRTIO_CONFIG_S_DRIVER_OK so start
+ * dataplane here instead of waiting for .set_status().
+ */
+ if (s->dataplane) {
+ virtio_blk_data_plane_start(s->dataplane);
+ return;
+ }
+
+ while ((req = virtio_blk_get_request(s))) {
+ virtio_blk_handle_request(req, &mrb);
+ }
+
+ if (mrb.num_reqs) {
+ virtio_blk_submit_multireq(s->blk, &mrb);
+ }
+}
+
+static void virtio_blk_dma_restart_bh(void *opaque)
+{
+ VirtIOBlock *s = opaque;
+ VirtIOBlockReq *req = s->rq;
+ MultiReqBuffer mrb = {};
+
+ qemu_bh_delete(s->bh);
+ s->bh = NULL;
+
+ s->rq = NULL;
+
+ while (req) {
+ VirtIOBlockReq *next = req->next;
+ virtio_blk_handle_request(req, &mrb);
+ req = next;
+ }
+
+ if (mrb.num_reqs) {
+ virtio_blk_submit_multireq(s->blk, &mrb);
+ }
+}
+
+static void virtio_blk_dma_restart_cb(void *opaque, int running,
+ RunState state)
+{
+ VirtIOBlock *s = opaque;
+
+ if (!running) {
+ return;
+ }
+
+ if (!s->bh) {
+ s->bh = aio_bh_new(blk_get_aio_context(s->conf.conf.blk),
+ virtio_blk_dma_restart_bh, s);
+ qemu_bh_schedule(s->bh);
+ }
+}
+
+static void virtio_blk_reset(VirtIODevice *vdev)
+{
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+ AioContext *ctx;
+
+ /*
+ * This should cancel pending requests, but can't do nicely until there
+ * are per-device request lists.
+ */
+ ctx = blk_get_aio_context(s->blk);
+ aio_context_acquire(ctx);
+ blk_drain(s->blk);
+
+ if (s->dataplane) {
+ virtio_blk_data_plane_stop(s->dataplane);
+ }
+ aio_context_release(ctx);
+
+ blk_set_enable_write_cache(s->blk, s->original_wce);
+}
+
+/* coalesce internal state, copy to pci i/o region 0
+ */
+static void virtio_blk_update_config(VirtIODevice *vdev, uint8_t *config)
+{
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+ BlockConf *conf = &s->conf.conf;
+ struct virtio_blk_config blkcfg;
+ uint64_t capacity;
+ int blk_size = conf->logical_block_size;
+
+ blk_get_geometry(s->blk, &capacity);
+ memset(&blkcfg, 0, sizeof(blkcfg));
+ virtio_stq_p(vdev, &blkcfg.capacity, capacity);
+ virtio_stl_p(vdev, &blkcfg.seg_max, 128 - 2);
+ virtio_stw_p(vdev, &blkcfg.geometry.cylinders, conf->cyls);
+ virtio_stl_p(vdev, &blkcfg.blk_size, blk_size);
+ virtio_stw_p(vdev, &blkcfg.min_io_size, conf->min_io_size / blk_size);
+ virtio_stw_p(vdev, &blkcfg.opt_io_size, conf->opt_io_size / blk_size);
+ blkcfg.geometry.heads = conf->heads;
+ /*
+ * We must ensure that the block device capacity is a multiple of
+ * the logical block size. If that is not the case, let's use
+ * sector_mask to adopt the geometry to have a correct picture.
+ * For those devices where the capacity is ok for the given geometry
+ * we don't touch the sector value of the geometry, since some devices
+ * (like s390 dasd) need a specific value. Here the capacity is already
+ * cyls*heads*secs*blk_size and the sector value is not block size
+ * divided by 512 - instead it is the amount of blk_size blocks
+ * per track (cylinder).
+ */
+ if (blk_getlength(s->blk) / conf->heads / conf->secs % blk_size) {
+ blkcfg.geometry.sectors = conf->secs & ~s->sector_mask;
+ } else {
+ blkcfg.geometry.sectors = conf->secs;
+ }
+ blkcfg.size_max = 0;
+ blkcfg.physical_block_exp = get_physical_block_exp(conf);
+ blkcfg.alignment_offset = 0;
+ blkcfg.wce = blk_enable_write_cache(s->blk);
+ memcpy(config, &blkcfg, sizeof(struct virtio_blk_config));
+}
+
+static void virtio_blk_set_config(VirtIODevice *vdev, const uint8_t *config)
+{
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+ struct virtio_blk_config blkcfg;
+
+ memcpy(&blkcfg, config, sizeof(blkcfg));
+
+ aio_context_acquire(blk_get_aio_context(s->blk));
+ blk_set_enable_write_cache(s->blk, blkcfg.wce != 0);
+ aio_context_release(blk_get_aio_context(s->blk));
+}
+
+static uint64_t virtio_blk_get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
+{
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+
+ virtio_add_feature(&features, VIRTIO_BLK_F_SEG_MAX);
+ virtio_add_feature(&features, VIRTIO_BLK_F_GEOMETRY);
+ virtio_add_feature(&features, VIRTIO_BLK_F_TOPOLOGY);
+ virtio_add_feature(&features, VIRTIO_BLK_F_BLK_SIZE);
+ if (virtio_has_feature(features, VIRTIO_F_VERSION_1)) {
+ if (s->conf.scsi) {
+ error_setg(errp, "Please set scsi=off for virtio-blk devices in order to use virtio 1.0");
+ return 0;
+ }
+ } else {
+ virtio_clear_feature(&features, VIRTIO_F_ANY_LAYOUT);
+ virtio_add_feature(&features, VIRTIO_BLK_F_SCSI);
+ }
+
+ if (s->conf.config_wce) {
+ virtio_add_feature(&features, VIRTIO_BLK_F_CONFIG_WCE);
+ }
+ if (blk_enable_write_cache(s->blk)) {
+ virtio_add_feature(&features, VIRTIO_BLK_F_WCE);
+ }
+ if (blk_is_read_only(s->blk)) {
+ virtio_add_feature(&features, VIRTIO_BLK_F_RO);
+ }
+
+ return features;
+}
+
+static void virtio_blk_set_status(VirtIODevice *vdev, uint8_t status)
+{
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+
+ if (s->dataplane && !(status & (VIRTIO_CONFIG_S_DRIVER |
+ VIRTIO_CONFIG_S_DRIVER_OK))) {
+ virtio_blk_data_plane_stop(s->dataplane);
+ }
+
+ if (!(status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ return;
+ }
+
+ /* A guest that supports VIRTIO_BLK_F_CONFIG_WCE must be able to send
+ * cache flushes. Thus, the "auto writethrough" behavior is never
+ * necessary for guests that support the VIRTIO_BLK_F_CONFIG_WCE feature.
+ * Leaving it enabled would break the following sequence:
+ *
+ * Guest started with "-drive cache=writethrough"
+ * Guest sets status to 0
+ * Guest sets DRIVER bit in status field
+ * Guest reads host features (WCE=0, CONFIG_WCE=1)
+ * Guest writes guest features (WCE=0, CONFIG_WCE=1)
+ * Guest writes 1 to the WCE configuration field (writeback mode)
+ * Guest sets DRIVER_OK bit in status field
+ *
+ * s->blk would erroneously be placed in writethrough mode.
+ */
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_BLK_F_CONFIG_WCE)) {
+ aio_context_acquire(blk_get_aio_context(s->blk));
+ blk_set_enable_write_cache(s->blk,
+ virtio_vdev_has_feature(vdev,
+ VIRTIO_BLK_F_WCE));
+ aio_context_release(blk_get_aio_context(s->blk));
+ }
+}
+
+static void virtio_blk_save(QEMUFile *f, void *opaque)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+
+ if (s->dataplane) {
+ virtio_blk_data_plane_stop(s->dataplane);
+ }
+
+ virtio_save(vdev, f);
+}
+
+static void virtio_blk_save_device(VirtIODevice *vdev, QEMUFile *f)
+{
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+ VirtIOBlockReq *req = s->rq;
+
+ while (req) {
+ qemu_put_sbyte(f, 1);
+ qemu_put_buffer(f, (unsigned char *)&req->elem,
+ sizeof(VirtQueueElement));
+ req = req->next;
+ }
+ qemu_put_sbyte(f, 0);
+}
+
+static int virtio_blk_load(QEMUFile *f, void *opaque, int version_id)
+{
+ VirtIOBlock *s = opaque;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ if (version_id != 2)
+ return -EINVAL;
+
+ return virtio_load(vdev, f, version_id);
+}
+
+static int virtio_blk_load_device(VirtIODevice *vdev, QEMUFile *f,
+ int version_id)
+{
+ VirtIOBlock *s = VIRTIO_BLK(vdev);
+
+ while (qemu_get_sbyte(f)) {
+ VirtIOBlockReq *req = virtio_blk_alloc_request(s);
+ qemu_get_buffer(f, (unsigned char *)&req->elem,
+ sizeof(VirtQueueElement));
+ req->next = s->rq;
+ s->rq = req;
+
+ virtqueue_map_sg(req->elem.in_sg, req->elem.in_addr,
+ req->elem.in_num, 1);
+ virtqueue_map_sg(req->elem.out_sg, req->elem.out_addr,
+ req->elem.out_num, 0);
+ }
+
+ return 0;
+}
+
+static void virtio_blk_resize(void *opaque)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
+
+ virtio_notify_config(vdev);
+}
+
+static const BlockDevOps virtio_block_ops = {
+ .resize_cb = virtio_blk_resize,
+};
+
+/* Disable dataplane thread during live migration since it does not
+ * update the dirty memory bitmap yet.
+ */
+static void virtio_blk_migration_state_changed(Notifier *notifier, void *data)
+{
+ VirtIOBlock *s = container_of(notifier, VirtIOBlock,
+ migration_state_notifier);
+ MigrationState *mig = data;
+ Error *err = NULL;
+
+ if (migration_in_setup(mig)) {
+ if (!s->dataplane) {
+ return;
+ }
+ virtio_blk_data_plane_destroy(s->dataplane);
+ s->dataplane = NULL;
+ } else if (migration_has_finished(mig) ||
+ migration_has_failed(mig)) {
+ if (s->dataplane) {
+ return;
+ }
+ blk_drain_all(); /* complete in-flight non-dataplane requests */
+ virtio_blk_data_plane_create(VIRTIO_DEVICE(s), &s->conf,
+ &s->dataplane, &err);
+ if (err != NULL) {
+ error_report_err(err);
+ }
+ }
+}
+
+static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOBlock *s = VIRTIO_BLK(dev);
+ VirtIOBlkConf *conf = &s->conf;
+ Error *err = NULL;
+ static int virtio_blk_id;
+
+ if (!conf->conf.blk) {
+ error_setg(errp, "drive property not set");
+ return;
+ }
+ if (!blk_is_inserted(conf->conf.blk)) {
+ error_setg(errp, "Device needs media, but drive is empty");
+ return;
+ }
+
+ blkconf_serial(&conf->conf, &conf->serial);
+ s->original_wce = blk_enable_write_cache(conf->conf.blk);
+ blkconf_geometry(&conf->conf, NULL, 65535, 255, 255, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ blkconf_blocksizes(&conf->conf);
+
+ virtio_init(vdev, "virtio-blk", VIRTIO_ID_BLOCK,
+ sizeof(struct virtio_blk_config));
+
+ s->blk = conf->conf.blk;
+ s->rq = NULL;
+ s->sector_mask = (s->conf.conf.logical_block_size / BDRV_SECTOR_SIZE) - 1;
+
+ s->vq = virtio_add_queue(vdev, 128, virtio_blk_handle_output);
+ s->complete_request = virtio_blk_complete_request;
+ virtio_blk_data_plane_create(vdev, conf, &s->dataplane, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ virtio_cleanup(vdev);
+ return;
+ }
+ s->migration_state_notifier.notify = virtio_blk_migration_state_changed;
+ add_migration_state_change_notifier(&s->migration_state_notifier);
+
+ s->change = qemu_add_vm_change_state_handler(virtio_blk_dma_restart_cb, s);
+ register_savevm(dev, "virtio-blk", virtio_blk_id++, 2,
+ virtio_blk_save, virtio_blk_load, s);
+ blk_set_dev_ops(s->blk, &virtio_block_ops, s);
+ blk_set_guest_block_size(s->blk, s->conf.conf.logical_block_size);
+
+ blk_iostatus_enable(s->blk);
+}
+
+static void virtio_blk_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOBlock *s = VIRTIO_BLK(dev);
+
+ remove_migration_state_change_notifier(&s->migration_state_notifier);
+ virtio_blk_data_plane_destroy(s->dataplane);
+ s->dataplane = NULL;
+ qemu_del_vm_change_state_handler(s->change);
+ unregister_savevm(dev, "virtio-blk", s);
+ blockdev_mark_auto_del(s->blk);
+ virtio_cleanup(vdev);
+}
+
+static void virtio_blk_instance_init(Object *obj)
+{
+ VirtIOBlock *s = VIRTIO_BLK(obj);
+
+ object_property_add_link(obj, "iothread", TYPE_IOTHREAD,
+ (Object **)&s->conf.iothread,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE, NULL);
+ device_add_bootindex_property(obj, &s->conf.conf.bootindex,
+ "bootindex", "/disk@0,0",
+ DEVICE(obj), NULL);
+}
+
+static Property virtio_blk_properties[] = {
+ DEFINE_BLOCK_PROPERTIES(VirtIOBlock, conf.conf),
+ DEFINE_BLOCK_CHS_PROPERTIES(VirtIOBlock, conf.conf),
+ DEFINE_PROP_STRING("serial", VirtIOBlock, conf.serial),
+ DEFINE_PROP_BIT("config-wce", VirtIOBlock, conf.config_wce, 0, true),
+#ifdef __linux__
+ DEFINE_PROP_BIT("scsi", VirtIOBlock, conf.scsi, 0, true),
+#endif
+ DEFINE_PROP_BIT("request-merging", VirtIOBlock, conf.request_merging, 0,
+ true),
+ DEFINE_PROP_BIT("x-data-plane", VirtIOBlock, conf.data_plane, 0, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_blk_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ dc->props = virtio_blk_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ vdc->realize = virtio_blk_device_realize;
+ vdc->unrealize = virtio_blk_device_unrealize;
+ vdc->get_config = virtio_blk_update_config;
+ vdc->set_config = virtio_blk_set_config;
+ vdc->get_features = virtio_blk_get_features;
+ vdc->set_status = virtio_blk_set_status;
+ vdc->reset = virtio_blk_reset;
+ vdc->save = virtio_blk_save_device;
+ vdc->load = virtio_blk_load_device;
+}
+
+static const TypeInfo virtio_device_info = {
+ .name = TYPE_VIRTIO_BLK,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOBlock),
+ .instance_init = virtio_blk_instance_init,
+ .class_init = virtio_blk_class_init,
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_device_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/block/xen_blkif.h b/hw/block/xen_blkif.h
new file mode 100644
index 00000000..711b6927
--- /dev/null
+++ b/hw/block/xen_blkif.h
@@ -0,0 +1,115 @@
+#ifndef __XEN_BLKIF_H__
+#define __XEN_BLKIF_H__
+
+#include <xen/io/ring.h>
+#include <xen/io/blkif.h>
+#include <xen/io/protocols.h>
+
+/* Not a real protocol. Used to generate ring structs which contain
+ * the elements common to all protocols only. This way we get a
+ * compiler-checkable way to use common struct elements, so we can
+ * avoid using switch(protocol) in a number of places. */
+struct blkif_common_request {
+ char dummy;
+};
+struct blkif_common_response {
+ char dummy;
+};
+
+/* i386 protocol version */
+#pragma pack(push, 4)
+struct blkif_x86_32_request {
+ uint8_t operation; /* BLKIF_OP_??? */
+ uint8_t nr_segments; /* number of segments */
+ blkif_vdev_t handle; /* only for read/write requests */
+ uint64_t id; /* private guest value, echoed in resp */
+ blkif_sector_t sector_number;/* start sector idx on disk (r/w only) */
+ struct blkif_request_segment seg[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+};
+struct blkif_x86_32_response {
+ uint64_t id; /* copied from request */
+ uint8_t operation; /* copied from request */
+ int16_t status; /* BLKIF_RSP_??? */
+};
+typedef struct blkif_x86_32_request blkif_x86_32_request_t;
+typedef struct blkif_x86_32_response blkif_x86_32_response_t;
+#pragma pack(pop)
+
+/* x86_64 protocol version */
+struct blkif_x86_64_request {
+ uint8_t operation; /* BLKIF_OP_??? */
+ uint8_t nr_segments; /* number of segments */
+ blkif_vdev_t handle; /* only for read/write requests */
+ uint64_t __attribute__((__aligned__(8))) id;
+ blkif_sector_t sector_number;/* start sector idx on disk (r/w only) */
+ struct blkif_request_segment seg[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+};
+struct blkif_x86_64_response {
+ uint64_t __attribute__((__aligned__(8))) id;
+ uint8_t operation; /* copied from request */
+ int16_t status; /* BLKIF_RSP_??? */
+};
+typedef struct blkif_x86_64_request blkif_x86_64_request_t;
+typedef struct blkif_x86_64_response blkif_x86_64_response_t;
+
+DEFINE_RING_TYPES(blkif_common, struct blkif_common_request, struct blkif_common_response);
+DEFINE_RING_TYPES(blkif_x86_32, struct blkif_x86_32_request, struct blkif_x86_32_response);
+DEFINE_RING_TYPES(blkif_x86_64, struct blkif_x86_64_request, struct blkif_x86_64_response);
+
+union blkif_back_rings {
+ blkif_back_ring_t native;
+ blkif_common_back_ring_t common;
+ blkif_x86_32_back_ring_t x86_32_part;
+ blkif_x86_64_back_ring_t x86_64_part;
+};
+typedef union blkif_back_rings blkif_back_rings_t;
+
+enum blkif_protocol {
+ BLKIF_PROTOCOL_NATIVE = 1,
+ BLKIF_PROTOCOL_X86_32 = 2,
+ BLKIF_PROTOCOL_X86_64 = 3,
+};
+
+static inline void blkif_get_x86_32_req(blkif_request_t *dst, blkif_x86_32_request_t *src)
+{
+ int i, n = BLKIF_MAX_SEGMENTS_PER_REQUEST;
+
+ dst->operation = src->operation;
+ dst->nr_segments = src->nr_segments;
+ dst->handle = src->handle;
+ dst->id = src->id;
+ dst->sector_number = src->sector_number;
+ if (src->operation == BLKIF_OP_DISCARD) {
+ struct blkif_request_discard *s = (void *)src;
+ struct blkif_request_discard *d = (void *)dst;
+ d->nr_sectors = s->nr_sectors;
+ return;
+ }
+ if (n > src->nr_segments)
+ n = src->nr_segments;
+ for (i = 0; i < n; i++)
+ dst->seg[i] = src->seg[i];
+}
+
+static inline void blkif_get_x86_64_req(blkif_request_t *dst, blkif_x86_64_request_t *src)
+{
+ int i, n = BLKIF_MAX_SEGMENTS_PER_REQUEST;
+
+ dst->operation = src->operation;
+ dst->nr_segments = src->nr_segments;
+ dst->handle = src->handle;
+ dst->id = src->id;
+ dst->sector_number = src->sector_number;
+ if (src->operation == BLKIF_OP_DISCARD) {
+ struct blkif_request_discard *s = (void *)src;
+ struct blkif_request_discard *d = (void *)dst;
+ d->nr_sectors = s->nr_sectors;
+ return;
+ }
+ if (n > src->nr_segments)
+ n = src->nr_segments;
+ for (i = 0; i < n; i++)
+ dst->seg[i] = src->seg[i];
+}
+
+#endif /* __XEN_BLKIF_H__ */
diff --git a/hw/block/xen_disk.c b/hw/block/xen_disk.c
new file mode 100644
index 00000000..267d8a8c
--- /dev/null
+++ b/hw/block/xen_disk.c
@@ -0,0 +1,1106 @@
+/*
+ * xen paravirt block device backend
+ *
+ * (c) Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <inttypes.h>
+#include <time.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+
+#include "hw/hw.h"
+#include "hw/xen/xen_backend.h"
+#include "xen_blkif.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/block-backend.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qstring.h"
+
+/* ------------------------------------------------------------- */
+
+static int batch_maps = 0;
+
+static int max_requests = 32;
+
+/* ------------------------------------------------------------- */
+
+#define BLOCK_SIZE 512
+#define IOCB_COUNT (BLKIF_MAX_SEGMENTS_PER_REQUEST + 2)
+
+struct PersistentGrant {
+ void *page;
+ struct XenBlkDev *blkdev;
+};
+
+typedef struct PersistentGrant PersistentGrant;
+
+struct PersistentRegion {
+ void *addr;
+ int num;
+};
+
+typedef struct PersistentRegion PersistentRegion;
+
+struct ioreq {
+ blkif_request_t req;
+ int16_t status;
+
+ /* parsed request */
+ off_t start;
+ QEMUIOVector v;
+ int presync;
+ int postsync;
+ uint8_t mapped;
+
+ /* grant mapping */
+ uint32_t domids[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+ uint32_t refs[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+ int prot;
+ void *page[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+ void *pages;
+ int num_unmap;
+
+ /* aio status */
+ int aio_inflight;
+ int aio_errors;
+
+ struct XenBlkDev *blkdev;
+ QLIST_ENTRY(ioreq) list;
+ BlockAcctCookie acct;
+};
+
+struct XenBlkDev {
+ struct XenDevice xendev; /* must be first */
+ char *params;
+ char *mode;
+ char *type;
+ char *dev;
+ char *devtype;
+ bool directiosafe;
+ const char *fileproto;
+ const char *filename;
+ int ring_ref;
+ void *sring;
+ int64_t file_blk;
+ int64_t file_size;
+ int protocol;
+ blkif_back_rings_t rings;
+ int more_work;
+ int cnt_map;
+
+ /* request lists */
+ QLIST_HEAD(inflight_head, ioreq) inflight;
+ QLIST_HEAD(finished_head, ioreq) finished;
+ QLIST_HEAD(freelist_head, ioreq) freelist;
+ int requests_total;
+ int requests_inflight;
+ int requests_finished;
+
+ /* Persistent grants extension */
+ gboolean feature_discard;
+ gboolean feature_persistent;
+ GTree *persistent_gnts;
+ GSList *persistent_regions;
+ unsigned int persistent_gnt_count;
+ unsigned int max_grants;
+
+ /* qemu block driver */
+ DriveInfo *dinfo;
+ BlockBackend *blk;
+ QEMUBH *bh;
+};
+
+/* ------------------------------------------------------------- */
+
+static void ioreq_reset(struct ioreq *ioreq)
+{
+ memset(&ioreq->req, 0, sizeof(ioreq->req));
+ ioreq->status = 0;
+ ioreq->start = 0;
+ ioreq->presync = 0;
+ ioreq->postsync = 0;
+ ioreq->mapped = 0;
+
+ memset(ioreq->domids, 0, sizeof(ioreq->domids));
+ memset(ioreq->refs, 0, sizeof(ioreq->refs));
+ ioreq->prot = 0;
+ memset(ioreq->page, 0, sizeof(ioreq->page));
+ ioreq->pages = NULL;
+
+ ioreq->aio_inflight = 0;
+ ioreq->aio_errors = 0;
+
+ ioreq->blkdev = NULL;
+ memset(&ioreq->list, 0, sizeof(ioreq->list));
+ memset(&ioreq->acct, 0, sizeof(ioreq->acct));
+
+ qemu_iovec_reset(&ioreq->v);
+}
+
+static gint int_cmp(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+ uint ua = GPOINTER_TO_UINT(a);
+ uint ub = GPOINTER_TO_UINT(b);
+ return (ua > ub) - (ua < ub);
+}
+
+static void destroy_grant(gpointer pgnt)
+{
+ PersistentGrant *grant = pgnt;
+ XenGnttab gnt = grant->blkdev->xendev.gnttabdev;
+
+ if (xc_gnttab_munmap(gnt, grant->page, 1) != 0) {
+ xen_be_printf(&grant->blkdev->xendev, 0,
+ "xc_gnttab_munmap failed: %s\n",
+ strerror(errno));
+ }
+ grant->blkdev->persistent_gnt_count--;
+ xen_be_printf(&grant->blkdev->xendev, 3,
+ "unmapped grant %p\n", grant->page);
+ g_free(grant);
+}
+
+static void remove_persistent_region(gpointer data, gpointer dev)
+{
+ PersistentRegion *region = data;
+ struct XenBlkDev *blkdev = dev;
+ XenGnttab gnt = blkdev->xendev.gnttabdev;
+
+ if (xc_gnttab_munmap(gnt, region->addr, region->num) != 0) {
+ xen_be_printf(&blkdev->xendev, 0,
+ "xc_gnttab_munmap region %p failed: %s\n",
+ region->addr, strerror(errno));
+ }
+ xen_be_printf(&blkdev->xendev, 3,
+ "unmapped grant region %p with %d pages\n",
+ region->addr, region->num);
+ g_free(region);
+}
+
+static struct ioreq *ioreq_start(struct XenBlkDev *blkdev)
+{
+ struct ioreq *ioreq = NULL;
+
+ if (QLIST_EMPTY(&blkdev->freelist)) {
+ if (blkdev->requests_total >= max_requests) {
+ goto out;
+ }
+ /* allocate new struct */
+ ioreq = g_malloc0(sizeof(*ioreq));
+ ioreq->blkdev = blkdev;
+ blkdev->requests_total++;
+ qemu_iovec_init(&ioreq->v, BLKIF_MAX_SEGMENTS_PER_REQUEST);
+ } else {
+ /* get one from freelist */
+ ioreq = QLIST_FIRST(&blkdev->freelist);
+ QLIST_REMOVE(ioreq, list);
+ }
+ QLIST_INSERT_HEAD(&blkdev->inflight, ioreq, list);
+ blkdev->requests_inflight++;
+
+out:
+ return ioreq;
+}
+
+static void ioreq_finish(struct ioreq *ioreq)
+{
+ struct XenBlkDev *blkdev = ioreq->blkdev;
+
+ QLIST_REMOVE(ioreq, list);
+ QLIST_INSERT_HEAD(&blkdev->finished, ioreq, list);
+ blkdev->requests_inflight--;
+ blkdev->requests_finished++;
+}
+
+static void ioreq_release(struct ioreq *ioreq, bool finish)
+{
+ struct XenBlkDev *blkdev = ioreq->blkdev;
+
+ QLIST_REMOVE(ioreq, list);
+ ioreq_reset(ioreq);
+ ioreq->blkdev = blkdev;
+ QLIST_INSERT_HEAD(&blkdev->freelist, ioreq, list);
+ if (finish) {
+ blkdev->requests_finished--;
+ } else {
+ blkdev->requests_inflight--;
+ }
+}
+
+/*
+ * translate request into iovec + start offset
+ * do sanity checks along the way
+ */
+static int ioreq_parse(struct ioreq *ioreq)
+{
+ struct XenBlkDev *blkdev = ioreq->blkdev;
+ uintptr_t mem;
+ size_t len;
+ int i;
+
+ xen_be_printf(&blkdev->xendev, 3,
+ "op %d, nr %d, handle %d, id %" PRId64 ", sector %" PRId64 "\n",
+ ioreq->req.operation, ioreq->req.nr_segments,
+ ioreq->req.handle, ioreq->req.id, ioreq->req.sector_number);
+ switch (ioreq->req.operation) {
+ case BLKIF_OP_READ:
+ ioreq->prot = PROT_WRITE; /* to memory */
+ break;
+ case BLKIF_OP_FLUSH_DISKCACHE:
+ ioreq->presync = 1;
+ if (!ioreq->req.nr_segments) {
+ return 0;
+ }
+ /* fall through */
+ case BLKIF_OP_WRITE:
+ ioreq->prot = PROT_READ; /* from memory */
+ break;
+ case BLKIF_OP_DISCARD:
+ return 0;
+ default:
+ xen_be_printf(&blkdev->xendev, 0, "error: unknown operation (%d)\n",
+ ioreq->req.operation);
+ goto err;
+ };
+
+ if (ioreq->req.operation != BLKIF_OP_READ && blkdev->mode[0] != 'w') {
+ xen_be_printf(&blkdev->xendev, 0, "error: write req for ro device\n");
+ goto err;
+ }
+
+ ioreq->start = ioreq->req.sector_number * blkdev->file_blk;
+ for (i = 0; i < ioreq->req.nr_segments; i++) {
+ if (i == BLKIF_MAX_SEGMENTS_PER_REQUEST) {
+ xen_be_printf(&blkdev->xendev, 0, "error: nr_segments too big\n");
+ goto err;
+ }
+ if (ioreq->req.seg[i].first_sect > ioreq->req.seg[i].last_sect) {
+ xen_be_printf(&blkdev->xendev, 0, "error: first > last sector\n");
+ goto err;
+ }
+ if (ioreq->req.seg[i].last_sect * BLOCK_SIZE >= XC_PAGE_SIZE) {
+ xen_be_printf(&blkdev->xendev, 0, "error: page crossing\n");
+ goto err;
+ }
+
+ ioreq->domids[i] = blkdev->xendev.dom;
+ ioreq->refs[i] = ioreq->req.seg[i].gref;
+
+ mem = ioreq->req.seg[i].first_sect * blkdev->file_blk;
+ len = (ioreq->req.seg[i].last_sect - ioreq->req.seg[i].first_sect + 1) * blkdev->file_blk;
+ qemu_iovec_add(&ioreq->v, (void*)mem, len);
+ }
+ if (ioreq->start + ioreq->v.size > blkdev->file_size) {
+ xen_be_printf(&blkdev->xendev, 0, "error: access beyond end of file\n");
+ goto err;
+ }
+ return 0;
+
+err:
+ ioreq->status = BLKIF_RSP_ERROR;
+ return -1;
+}
+
+static void ioreq_unmap(struct ioreq *ioreq)
+{
+ XenGnttab gnt = ioreq->blkdev->xendev.gnttabdev;
+ int i;
+
+ if (ioreq->num_unmap == 0 || ioreq->mapped == 0) {
+ return;
+ }
+ if (batch_maps) {
+ if (!ioreq->pages) {
+ return;
+ }
+ if (xc_gnttab_munmap(gnt, ioreq->pages, ioreq->num_unmap) != 0) {
+ xen_be_printf(&ioreq->blkdev->xendev, 0, "xc_gnttab_munmap failed: %s\n",
+ strerror(errno));
+ }
+ ioreq->blkdev->cnt_map -= ioreq->num_unmap;
+ ioreq->pages = NULL;
+ } else {
+ for (i = 0; i < ioreq->num_unmap; i++) {
+ if (!ioreq->page[i]) {
+ continue;
+ }
+ if (xc_gnttab_munmap(gnt, ioreq->page[i], 1) != 0) {
+ xen_be_printf(&ioreq->blkdev->xendev, 0, "xc_gnttab_munmap failed: %s\n",
+ strerror(errno));
+ }
+ ioreq->blkdev->cnt_map--;
+ ioreq->page[i] = NULL;
+ }
+ }
+ ioreq->mapped = 0;
+}
+
+static int ioreq_map(struct ioreq *ioreq)
+{
+ XenGnttab gnt = ioreq->blkdev->xendev.gnttabdev;
+ uint32_t domids[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+ uint32_t refs[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+ void *page[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+ int i, j, new_maps = 0;
+ PersistentGrant *grant;
+ PersistentRegion *region;
+ /* domids and refs variables will contain the information necessary
+ * to map the grants that are needed to fulfill this request.
+ *
+ * After mapping the needed grants, the page array will contain the
+ * memory address of each granted page in the order specified in ioreq
+ * (disregarding if it's a persistent grant or not).
+ */
+
+ if (ioreq->v.niov == 0 || ioreq->mapped == 1) {
+ return 0;
+ }
+ if (ioreq->blkdev->feature_persistent) {
+ for (i = 0; i < ioreq->v.niov; i++) {
+ grant = g_tree_lookup(ioreq->blkdev->persistent_gnts,
+ GUINT_TO_POINTER(ioreq->refs[i]));
+
+ if (grant != NULL) {
+ page[i] = grant->page;
+ xen_be_printf(&ioreq->blkdev->xendev, 3,
+ "using persistent-grant %" PRIu32 "\n",
+ ioreq->refs[i]);
+ } else {
+ /* Add the grant to the list of grants that
+ * should be mapped
+ */
+ domids[new_maps] = ioreq->domids[i];
+ refs[new_maps] = ioreq->refs[i];
+ page[i] = NULL;
+ new_maps++;
+ }
+ }
+ /* Set the protection to RW, since grants may be reused later
+ * with a different protection than the one needed for this request
+ */
+ ioreq->prot = PROT_WRITE | PROT_READ;
+ } else {
+ /* All grants in the request should be mapped */
+ memcpy(refs, ioreq->refs, sizeof(refs));
+ memcpy(domids, ioreq->domids, sizeof(domids));
+ memset(page, 0, sizeof(page));
+ new_maps = ioreq->v.niov;
+ }
+
+ if (batch_maps && new_maps) {
+ ioreq->pages = xc_gnttab_map_grant_refs
+ (gnt, new_maps, domids, refs, ioreq->prot);
+ if (ioreq->pages == NULL) {
+ xen_be_printf(&ioreq->blkdev->xendev, 0,
+ "can't map %d grant refs (%s, %d maps)\n",
+ new_maps, strerror(errno), ioreq->blkdev->cnt_map);
+ return -1;
+ }
+ for (i = 0, j = 0; i < ioreq->v.niov; i++) {
+ if (page[i] == NULL) {
+ page[i] = ioreq->pages + (j++) * XC_PAGE_SIZE;
+ }
+ }
+ ioreq->blkdev->cnt_map += new_maps;
+ } else if (new_maps) {
+ for (i = 0; i < new_maps; i++) {
+ ioreq->page[i] = xc_gnttab_map_grant_ref
+ (gnt, domids[i], refs[i], ioreq->prot);
+ if (ioreq->page[i] == NULL) {
+ xen_be_printf(&ioreq->blkdev->xendev, 0,
+ "can't map grant ref %d (%s, %d maps)\n",
+ refs[i], strerror(errno), ioreq->blkdev->cnt_map);
+ ioreq->mapped = 1;
+ ioreq_unmap(ioreq);
+ return -1;
+ }
+ ioreq->blkdev->cnt_map++;
+ }
+ for (i = 0, j = 0; i < ioreq->v.niov; i++) {
+ if (page[i] == NULL) {
+ page[i] = ioreq->page[j++];
+ }
+ }
+ }
+ if (ioreq->blkdev->feature_persistent && new_maps != 0 &&
+ (!batch_maps || (ioreq->blkdev->persistent_gnt_count + new_maps <=
+ ioreq->blkdev->max_grants))) {
+ /*
+ * If we are using persistent grants and batch mappings only
+ * add the new maps to the list of persistent grants if the whole
+ * area can be persistently mapped.
+ */
+ if (batch_maps) {
+ region = g_malloc0(sizeof(*region));
+ region->addr = ioreq->pages;
+ region->num = new_maps;
+ ioreq->blkdev->persistent_regions = g_slist_append(
+ ioreq->blkdev->persistent_regions,
+ region);
+ }
+ while ((ioreq->blkdev->persistent_gnt_count < ioreq->blkdev->max_grants)
+ && new_maps) {
+ /* Go through the list of newly mapped grants and add as many
+ * as possible to the list of persistently mapped grants.
+ *
+ * Since we start at the end of ioreq->page(s), we only need
+ * to decrease new_maps to prevent this granted pages from
+ * being unmapped in ioreq_unmap.
+ */
+ grant = g_malloc0(sizeof(*grant));
+ new_maps--;
+ if (batch_maps) {
+ grant->page = ioreq->pages + (new_maps) * XC_PAGE_SIZE;
+ } else {
+ grant->page = ioreq->page[new_maps];
+ }
+ grant->blkdev = ioreq->blkdev;
+ xen_be_printf(&ioreq->blkdev->xendev, 3,
+ "adding grant %" PRIu32 " page: %p\n",
+ refs[new_maps], grant->page);
+ g_tree_insert(ioreq->blkdev->persistent_gnts,
+ GUINT_TO_POINTER(refs[new_maps]),
+ grant);
+ ioreq->blkdev->persistent_gnt_count++;
+ }
+ assert(!batch_maps || new_maps == 0);
+ }
+ for (i = 0; i < ioreq->v.niov; i++) {
+ ioreq->v.iov[i].iov_base += (uintptr_t)page[i];
+ }
+ ioreq->mapped = 1;
+ ioreq->num_unmap = new_maps;
+ return 0;
+}
+
+static int ioreq_runio_qemu_aio(struct ioreq *ioreq);
+
+static void qemu_aio_complete(void *opaque, int ret)
+{
+ struct ioreq *ioreq = opaque;
+
+ if (ret != 0) {
+ xen_be_printf(&ioreq->blkdev->xendev, 0, "%s I/O error\n",
+ ioreq->req.operation == BLKIF_OP_READ ? "read" : "write");
+ ioreq->aio_errors++;
+ }
+
+ ioreq->aio_inflight--;
+ if (ioreq->presync) {
+ ioreq->presync = 0;
+ ioreq_runio_qemu_aio(ioreq);
+ return;
+ }
+ if (ioreq->aio_inflight > 0) {
+ return;
+ }
+ if (ioreq->postsync) {
+ ioreq->postsync = 0;
+ ioreq->aio_inflight++;
+ blk_aio_flush(ioreq->blkdev->blk, qemu_aio_complete, ioreq);
+ return;
+ }
+
+ ioreq->status = ioreq->aio_errors ? BLKIF_RSP_ERROR : BLKIF_RSP_OKAY;
+ ioreq_unmap(ioreq);
+ ioreq_finish(ioreq);
+ switch (ioreq->req.operation) {
+ case BLKIF_OP_WRITE:
+ case BLKIF_OP_FLUSH_DISKCACHE:
+ if (!ioreq->req.nr_segments) {
+ break;
+ }
+ case BLKIF_OP_READ:
+ block_acct_done(blk_get_stats(ioreq->blkdev->blk), &ioreq->acct);
+ break;
+ case BLKIF_OP_DISCARD:
+ default:
+ break;
+ }
+ qemu_bh_schedule(ioreq->blkdev->bh);
+}
+
+static int ioreq_runio_qemu_aio(struct ioreq *ioreq)
+{
+ struct XenBlkDev *blkdev = ioreq->blkdev;
+
+ if (ioreq->req.nr_segments && ioreq_map(ioreq) == -1) {
+ goto err_no_map;
+ }
+
+ ioreq->aio_inflight++;
+ if (ioreq->presync) {
+ blk_aio_flush(ioreq->blkdev->blk, qemu_aio_complete, ioreq);
+ return 0;
+ }
+
+ switch (ioreq->req.operation) {
+ case BLKIF_OP_READ:
+ block_acct_start(blk_get_stats(blkdev->blk), &ioreq->acct,
+ ioreq->v.size, BLOCK_ACCT_READ);
+ ioreq->aio_inflight++;
+ blk_aio_readv(blkdev->blk, ioreq->start / BLOCK_SIZE,
+ &ioreq->v, ioreq->v.size / BLOCK_SIZE,
+ qemu_aio_complete, ioreq);
+ break;
+ case BLKIF_OP_WRITE:
+ case BLKIF_OP_FLUSH_DISKCACHE:
+ if (!ioreq->req.nr_segments) {
+ break;
+ }
+
+ block_acct_start(blk_get_stats(blkdev->blk), &ioreq->acct,
+ ioreq->v.size, BLOCK_ACCT_WRITE);
+ ioreq->aio_inflight++;
+ blk_aio_writev(blkdev->blk, ioreq->start / BLOCK_SIZE,
+ &ioreq->v, ioreq->v.size / BLOCK_SIZE,
+ qemu_aio_complete, ioreq);
+ break;
+ case BLKIF_OP_DISCARD:
+ {
+ struct blkif_request_discard *discard_req = (void *)&ioreq->req;
+ ioreq->aio_inflight++;
+ blk_aio_discard(blkdev->blk,
+ discard_req->sector_number, discard_req->nr_sectors,
+ qemu_aio_complete, ioreq);
+ break;
+ }
+ default:
+ /* unknown operation (shouldn't happen -- parse catches this) */
+ goto err;
+ }
+
+ qemu_aio_complete(ioreq, 0);
+
+ return 0;
+
+err:
+ ioreq_unmap(ioreq);
+err_no_map:
+ ioreq_finish(ioreq);
+ ioreq->status = BLKIF_RSP_ERROR;
+ return -1;
+}
+
+static int blk_send_response_one(struct ioreq *ioreq)
+{
+ struct XenBlkDev *blkdev = ioreq->blkdev;
+ int send_notify = 0;
+ int have_requests = 0;
+ blkif_response_t resp;
+ void *dst;
+
+ resp.id = ioreq->req.id;
+ resp.operation = ioreq->req.operation;
+ resp.status = ioreq->status;
+
+ /* Place on the response ring for the relevant domain. */
+ switch (blkdev->protocol) {
+ case BLKIF_PROTOCOL_NATIVE:
+ dst = RING_GET_RESPONSE(&blkdev->rings.native, blkdev->rings.native.rsp_prod_pvt);
+ break;
+ case BLKIF_PROTOCOL_X86_32:
+ dst = RING_GET_RESPONSE(&blkdev->rings.x86_32_part,
+ blkdev->rings.x86_32_part.rsp_prod_pvt);
+ break;
+ case BLKIF_PROTOCOL_X86_64:
+ dst = RING_GET_RESPONSE(&blkdev->rings.x86_64_part,
+ blkdev->rings.x86_64_part.rsp_prod_pvt);
+ break;
+ default:
+ dst = NULL;
+ return 0;
+ }
+ memcpy(dst, &resp, sizeof(resp));
+ blkdev->rings.common.rsp_prod_pvt++;
+
+ RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&blkdev->rings.common, send_notify);
+ if (blkdev->rings.common.rsp_prod_pvt == blkdev->rings.common.req_cons) {
+ /*
+ * Tail check for pending requests. Allows frontend to avoid
+ * notifications if requests are already in flight (lower
+ * overheads and promotes batching).
+ */
+ RING_FINAL_CHECK_FOR_REQUESTS(&blkdev->rings.common, have_requests);
+ } else if (RING_HAS_UNCONSUMED_REQUESTS(&blkdev->rings.common)) {
+ have_requests = 1;
+ }
+
+ if (have_requests) {
+ blkdev->more_work++;
+ }
+ return send_notify;
+}
+
+/* walk finished list, send outstanding responses, free requests */
+static void blk_send_response_all(struct XenBlkDev *blkdev)
+{
+ struct ioreq *ioreq;
+ int send_notify = 0;
+
+ while (!QLIST_EMPTY(&blkdev->finished)) {
+ ioreq = QLIST_FIRST(&blkdev->finished);
+ send_notify += blk_send_response_one(ioreq);
+ ioreq_release(ioreq, true);
+ }
+ if (send_notify) {
+ xen_be_send_notify(&blkdev->xendev);
+ }
+}
+
+static int blk_get_request(struct XenBlkDev *blkdev, struct ioreq *ioreq, RING_IDX rc)
+{
+ switch (blkdev->protocol) {
+ case BLKIF_PROTOCOL_NATIVE:
+ memcpy(&ioreq->req, RING_GET_REQUEST(&blkdev->rings.native, rc),
+ sizeof(ioreq->req));
+ break;
+ case BLKIF_PROTOCOL_X86_32:
+ blkif_get_x86_32_req(&ioreq->req,
+ RING_GET_REQUEST(&blkdev->rings.x86_32_part, rc));
+ break;
+ case BLKIF_PROTOCOL_X86_64:
+ blkif_get_x86_64_req(&ioreq->req,
+ RING_GET_REQUEST(&blkdev->rings.x86_64_part, rc));
+ break;
+ }
+ return 0;
+}
+
+static void blk_handle_requests(struct XenBlkDev *blkdev)
+{
+ RING_IDX rc, rp;
+ struct ioreq *ioreq;
+
+ blkdev->more_work = 0;
+
+ rc = blkdev->rings.common.req_cons;
+ rp = blkdev->rings.common.sring->req_prod;
+ xen_rmb(); /* Ensure we see queued requests up to 'rp'. */
+
+ blk_send_response_all(blkdev);
+ while (rc != rp) {
+ /* pull request from ring */
+ if (RING_REQUEST_CONS_OVERFLOW(&blkdev->rings.common, rc)) {
+ break;
+ }
+ ioreq = ioreq_start(blkdev);
+ if (ioreq == NULL) {
+ blkdev->more_work++;
+ break;
+ }
+ blk_get_request(blkdev, ioreq, rc);
+ blkdev->rings.common.req_cons = ++rc;
+
+ /* parse them */
+ if (ioreq_parse(ioreq) != 0) {
+ if (blk_send_response_one(ioreq)) {
+ xen_be_send_notify(&blkdev->xendev);
+ }
+ ioreq_release(ioreq, false);
+ continue;
+ }
+
+ ioreq_runio_qemu_aio(ioreq);
+ }
+
+ if (blkdev->more_work && blkdev->requests_inflight < max_requests) {
+ qemu_bh_schedule(blkdev->bh);
+ }
+}
+
+/* ------------------------------------------------------------- */
+
+static void blk_bh(void *opaque)
+{
+ struct XenBlkDev *blkdev = opaque;
+ blk_handle_requests(blkdev);
+}
+
+/*
+ * We need to account for the grant allocations requiring contiguous
+ * chunks; the worst case number would be
+ * max_req * max_seg + (max_req - 1) * (max_seg - 1) + 1,
+ * but in order to keep things simple just use
+ * 2 * max_req * max_seg.
+ */
+#define MAX_GRANTS(max_req, max_seg) (2 * (max_req) * (max_seg))
+
+static void blk_alloc(struct XenDevice *xendev)
+{
+ struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev);
+
+ QLIST_INIT(&blkdev->inflight);
+ QLIST_INIT(&blkdev->finished);
+ QLIST_INIT(&blkdev->freelist);
+ blkdev->bh = qemu_bh_new(blk_bh, blkdev);
+ if (xen_mode != XEN_EMULATE) {
+ batch_maps = 1;
+ }
+ if (xc_gnttab_set_max_grants(xendev->gnttabdev,
+ MAX_GRANTS(max_requests, BLKIF_MAX_SEGMENTS_PER_REQUEST)) < 0) {
+ xen_be_printf(xendev, 0, "xc_gnttab_set_max_grants failed: %s\n",
+ strerror(errno));
+ }
+}
+
+static void blk_parse_discard(struct XenBlkDev *blkdev)
+{
+ int enable;
+
+ blkdev->feature_discard = true;
+
+ if (xenstore_read_be_int(&blkdev->xendev, "discard-enable", &enable) == 0) {
+ blkdev->feature_discard = !!enable;
+ }
+
+ if (blkdev->feature_discard) {
+ xenstore_write_be_int(&blkdev->xendev, "feature-discard", 1);
+ }
+}
+
+static int blk_init(struct XenDevice *xendev)
+{
+ struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev);
+ int info = 0;
+ char *directiosafe = NULL;
+
+ /* read xenstore entries */
+ if (blkdev->params == NULL) {
+ char *h = NULL;
+ blkdev->params = xenstore_read_be_str(&blkdev->xendev, "params");
+ if (blkdev->params != NULL) {
+ h = strchr(blkdev->params, ':');
+ }
+ if (h != NULL) {
+ blkdev->fileproto = blkdev->params;
+ blkdev->filename = h+1;
+ *h = 0;
+ } else {
+ blkdev->fileproto = "<unset>";
+ blkdev->filename = blkdev->params;
+ }
+ }
+ if (!strcmp("aio", blkdev->fileproto)) {
+ blkdev->fileproto = "raw";
+ }
+ if (blkdev->mode == NULL) {
+ blkdev->mode = xenstore_read_be_str(&blkdev->xendev, "mode");
+ }
+ if (blkdev->type == NULL) {
+ blkdev->type = xenstore_read_be_str(&blkdev->xendev, "type");
+ }
+ if (blkdev->dev == NULL) {
+ blkdev->dev = xenstore_read_be_str(&blkdev->xendev, "dev");
+ }
+ if (blkdev->devtype == NULL) {
+ blkdev->devtype = xenstore_read_be_str(&blkdev->xendev, "device-type");
+ }
+ directiosafe = xenstore_read_be_str(&blkdev->xendev, "direct-io-safe");
+ blkdev->directiosafe = (directiosafe && atoi(directiosafe));
+
+ /* do we have all we need? */
+ if (blkdev->params == NULL ||
+ blkdev->mode == NULL ||
+ blkdev->type == NULL ||
+ blkdev->dev == NULL) {
+ goto out_error;
+ }
+
+ /* read-only ? */
+ if (strcmp(blkdev->mode, "w")) {
+ info |= VDISK_READONLY;
+ }
+
+ /* cdrom ? */
+ if (blkdev->devtype && !strcmp(blkdev->devtype, "cdrom")) {
+ info |= VDISK_CDROM;
+ }
+
+ blkdev->file_blk = BLOCK_SIZE;
+
+ /* fill info
+ * blk_connect supplies sector-size and sectors
+ */
+ xenstore_write_be_int(&blkdev->xendev, "feature-flush-cache", 1);
+ xenstore_write_be_int(&blkdev->xendev, "feature-persistent", 1);
+ xenstore_write_be_int(&blkdev->xendev, "info", info);
+
+ blk_parse_discard(blkdev);
+
+ g_free(directiosafe);
+ return 0;
+
+out_error:
+ g_free(blkdev->params);
+ blkdev->params = NULL;
+ g_free(blkdev->mode);
+ blkdev->mode = NULL;
+ g_free(blkdev->type);
+ blkdev->type = NULL;
+ g_free(blkdev->dev);
+ blkdev->dev = NULL;
+ g_free(blkdev->devtype);
+ blkdev->devtype = NULL;
+ g_free(directiosafe);
+ blkdev->directiosafe = false;
+ return -1;
+}
+
+static int blk_connect(struct XenDevice *xendev)
+{
+ struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev);
+ int pers, index, qflags;
+ bool readonly = true;
+
+ /* read-only ? */
+ if (blkdev->directiosafe) {
+ qflags = BDRV_O_NOCACHE | BDRV_O_NATIVE_AIO;
+ } else {
+ qflags = BDRV_O_CACHE_WB;
+ }
+ if (strcmp(blkdev->mode, "w") == 0) {
+ qflags |= BDRV_O_RDWR;
+ readonly = false;
+ }
+ if (blkdev->feature_discard) {
+ qflags |= BDRV_O_UNMAP;
+ }
+
+ /* init qemu block driver */
+ index = (blkdev->xendev.dev - 202 * 256) / 16;
+ blkdev->dinfo = drive_get(IF_XEN, 0, index);
+ if (!blkdev->dinfo) {
+ Error *local_err = NULL;
+ QDict *options = NULL;
+
+ if (strcmp(blkdev->fileproto, "<unset>")) {
+ options = qdict_new();
+ qdict_put(options, "driver", qstring_from_str(blkdev->fileproto));
+ }
+
+ /* setup via xenbus -> create new block driver instance */
+ xen_be_printf(&blkdev->xendev, 2, "create new bdrv (xenbus setup)\n");
+ blkdev->blk = blk_new_open(blkdev->dev, blkdev->filename, NULL, options,
+ qflags, &local_err);
+ if (!blkdev->blk) {
+ xen_be_printf(&blkdev->xendev, 0, "error: %s\n",
+ error_get_pretty(local_err));
+ error_free(local_err);
+ return -1;
+ }
+ } else {
+ /* setup via qemu cmdline -> already setup for us */
+ xen_be_printf(&blkdev->xendev, 2, "get configured bdrv (cmdline setup)\n");
+ blkdev->blk = blk_by_legacy_dinfo(blkdev->dinfo);
+ if (blk_is_read_only(blkdev->blk) && !readonly) {
+ xen_be_printf(&blkdev->xendev, 0, "Unexpected read-only drive");
+ blkdev->blk = NULL;
+ return -1;
+ }
+ /* blkdev->blk is not create by us, we get a reference
+ * so we can blk_unref() unconditionally */
+ blk_ref(blkdev->blk);
+ }
+ blk_attach_dev_nofail(blkdev->blk, blkdev);
+ blkdev->file_size = blk_getlength(blkdev->blk);
+ if (blkdev->file_size < 0) {
+ xen_be_printf(&blkdev->xendev, 1, "blk_getlength: %d (%s) | drv %s\n",
+ (int)blkdev->file_size, strerror(-blkdev->file_size),
+ bdrv_get_format_name(blk_bs(blkdev->blk)) ?: "-");
+ blkdev->file_size = 0;
+ }
+
+ xen_be_printf(xendev, 1, "type \"%s\", fileproto \"%s\", filename \"%s\","
+ " size %" PRId64 " (%" PRId64 " MB)\n",
+ blkdev->type, blkdev->fileproto, blkdev->filename,
+ blkdev->file_size, blkdev->file_size >> 20);
+
+ /* Fill in number of sector size and number of sectors */
+ xenstore_write_be_int(&blkdev->xendev, "sector-size", blkdev->file_blk);
+ xenstore_write_be_int64(&blkdev->xendev, "sectors",
+ blkdev->file_size / blkdev->file_blk);
+
+ if (xenstore_read_fe_int(&blkdev->xendev, "ring-ref", &blkdev->ring_ref) == -1) {
+ return -1;
+ }
+ if (xenstore_read_fe_int(&blkdev->xendev, "event-channel",
+ &blkdev->xendev.remote_port) == -1) {
+ return -1;
+ }
+ if (xenstore_read_fe_int(&blkdev->xendev, "feature-persistent", &pers)) {
+ blkdev->feature_persistent = FALSE;
+ } else {
+ blkdev->feature_persistent = !!pers;
+ }
+
+ blkdev->protocol = BLKIF_PROTOCOL_NATIVE;
+ if (blkdev->xendev.protocol) {
+ if (strcmp(blkdev->xendev.protocol, XEN_IO_PROTO_ABI_X86_32) == 0) {
+ blkdev->protocol = BLKIF_PROTOCOL_X86_32;
+ }
+ if (strcmp(blkdev->xendev.protocol, XEN_IO_PROTO_ABI_X86_64) == 0) {
+ blkdev->protocol = BLKIF_PROTOCOL_X86_64;
+ }
+ }
+
+ blkdev->sring = xc_gnttab_map_grant_ref(blkdev->xendev.gnttabdev,
+ blkdev->xendev.dom,
+ blkdev->ring_ref,
+ PROT_READ | PROT_WRITE);
+ if (!blkdev->sring) {
+ return -1;
+ }
+ blkdev->cnt_map++;
+
+ switch (blkdev->protocol) {
+ case BLKIF_PROTOCOL_NATIVE:
+ {
+ blkif_sring_t *sring_native = blkdev->sring;
+ BACK_RING_INIT(&blkdev->rings.native, sring_native, XC_PAGE_SIZE);
+ break;
+ }
+ case BLKIF_PROTOCOL_X86_32:
+ {
+ blkif_x86_32_sring_t *sring_x86_32 = blkdev->sring;
+
+ BACK_RING_INIT(&blkdev->rings.x86_32_part, sring_x86_32, XC_PAGE_SIZE);
+ break;
+ }
+ case BLKIF_PROTOCOL_X86_64:
+ {
+ blkif_x86_64_sring_t *sring_x86_64 = blkdev->sring;
+
+ BACK_RING_INIT(&blkdev->rings.x86_64_part, sring_x86_64, XC_PAGE_SIZE);
+ break;
+ }
+ }
+
+ if (blkdev->feature_persistent) {
+ /* Init persistent grants */
+ blkdev->max_grants = max_requests * BLKIF_MAX_SEGMENTS_PER_REQUEST;
+ blkdev->persistent_gnts = g_tree_new_full((GCompareDataFunc)int_cmp,
+ NULL, NULL,
+ batch_maps ?
+ (GDestroyNotify)g_free :
+ (GDestroyNotify)destroy_grant);
+ blkdev->persistent_regions = NULL;
+ blkdev->persistent_gnt_count = 0;
+ }
+
+ xen_be_bind_evtchn(&blkdev->xendev);
+
+ xen_be_printf(&blkdev->xendev, 1, "ok: proto %s, ring-ref %d, "
+ "remote port %d, local port %d\n",
+ blkdev->xendev.protocol, blkdev->ring_ref,
+ blkdev->xendev.remote_port, blkdev->xendev.local_port);
+ return 0;
+}
+
+static void blk_disconnect(struct XenDevice *xendev)
+{
+ struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev);
+
+ if (blkdev->blk) {
+ blk_detach_dev(blkdev->blk, blkdev);
+ blk_unref(blkdev->blk);
+ blkdev->blk = NULL;
+ }
+ xen_be_unbind_evtchn(&blkdev->xendev);
+
+ if (blkdev->sring) {
+ xc_gnttab_munmap(blkdev->xendev.gnttabdev, blkdev->sring, 1);
+ blkdev->cnt_map--;
+ blkdev->sring = NULL;
+ }
+
+ /*
+ * Unmap persistent grants before switching to the closed state
+ * so the frontend can free them.
+ *
+ * In the !batch_maps case g_tree_destroy will take care of unmapping
+ * the grant, but in the batch_maps case we need to iterate over every
+ * region in persistent_regions and unmap it.
+ */
+ if (blkdev->feature_persistent) {
+ g_tree_destroy(blkdev->persistent_gnts);
+ assert(batch_maps || blkdev->persistent_gnt_count == 0);
+ if (batch_maps) {
+ blkdev->persistent_gnt_count = 0;
+ g_slist_foreach(blkdev->persistent_regions,
+ (GFunc)remove_persistent_region, blkdev);
+ g_slist_free(blkdev->persistent_regions);
+ }
+ blkdev->feature_persistent = false;
+ }
+}
+
+static int blk_free(struct XenDevice *xendev)
+{
+ struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev);
+ struct ioreq *ioreq;
+
+ if (blkdev->blk || blkdev->sring) {
+ blk_disconnect(xendev);
+ }
+
+ while (!QLIST_EMPTY(&blkdev->freelist)) {
+ ioreq = QLIST_FIRST(&blkdev->freelist);
+ QLIST_REMOVE(ioreq, list);
+ qemu_iovec_destroy(&ioreq->v);
+ g_free(ioreq);
+ }
+
+ g_free(blkdev->params);
+ g_free(blkdev->mode);
+ g_free(blkdev->type);
+ g_free(blkdev->dev);
+ g_free(blkdev->devtype);
+ qemu_bh_delete(blkdev->bh);
+ return 0;
+}
+
+static void blk_event(struct XenDevice *xendev)
+{
+ struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev);
+
+ qemu_bh_schedule(blkdev->bh);
+}
+
+struct XenDevOps xen_blkdev_ops = {
+ .size = sizeof(struct XenBlkDev),
+ .flags = DEVOPS_FLAG_NEED_GNTDEV,
+ .alloc = blk_alloc,
+ .init = blk_init,
+ .initialise = blk_connect,
+ .disconnect = blk_disconnect,
+ .event = blk_event,
+ .free = blk_free,
+};
diff --git a/hw/bt/Makefile.objs b/hw/bt/Makefile.objs
new file mode 100644
index 00000000..867a7d2e
--- /dev/null
+++ b/hw/bt/Makefile.objs
@@ -0,0 +1,3 @@
+common-obj-y += core.o l2cap.o sdp.o hci.o hid.o
+common-obj-y += hci-csr.o
+
diff --git a/hw/bt/core.c b/hw/bt/core.c
new file mode 100644
index 00000000..0ffc9488
--- /dev/null
+++ b/hw/bt/core.c
@@ -0,0 +1,144 @@
+/*
+ * Convenience functions for bluetooth.
+ *
+ * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "sysemu/bt.h"
+#include "hw/bt.h"
+
+/* Slave implementations can ignore this */
+static void bt_dummy_lmp_mode_change(struct bt_link_s *link)
+{
+}
+
+/* Slaves should never receive these PDUs */
+static void bt_dummy_lmp_connection_complete(struct bt_link_s *link)
+{
+ if (link->slave->reject_reason)
+ fprintf(stderr, "%s: stray LMP_not_accepted received, fixme\n",
+ __FUNCTION__);
+ else
+ fprintf(stderr, "%s: stray LMP_accepted received, fixme\n",
+ __FUNCTION__);
+ exit(-1);
+}
+
+static void bt_dummy_lmp_disconnect_master(struct bt_link_s *link)
+{
+ fprintf(stderr, "%s: stray LMP_detach received, fixme\n", __FUNCTION__);
+ exit(-1);
+}
+
+static void bt_dummy_lmp_acl_resp(struct bt_link_s *link,
+ const uint8_t *data, int start, int len)
+{
+ fprintf(stderr, "%s: stray ACL response PDU, fixme\n", __FUNCTION__);
+ exit(-1);
+}
+
+/* Slaves that don't hold any additional per link state can use these */
+static void bt_dummy_lmp_connection_request(struct bt_link_s *req)
+{
+ struct bt_link_s *link = g_malloc0(sizeof(struct bt_link_s));
+
+ link->slave = req->slave;
+ link->host = req->host;
+
+ req->host->reject_reason = 0;
+ req->host->lmp_connection_complete(link);
+}
+
+static void bt_dummy_lmp_disconnect_slave(struct bt_link_s *link)
+{
+ g_free(link);
+}
+
+static void bt_dummy_destroy(struct bt_device_s *device)
+{
+ bt_device_done(device);
+ g_free(device);
+}
+
+static int bt_dev_idx = 0;
+
+void bt_device_init(struct bt_device_s *dev, struct bt_scatternet_s *net)
+{
+ memset(dev, 0, sizeof(*dev));
+ dev->inquiry_scan = 1;
+ dev->page_scan = 1;
+
+ dev->bd_addr.b[0] = bt_dev_idx & 0xff;
+ dev->bd_addr.b[1] = bt_dev_idx >> 8;
+ dev->bd_addr.b[2] = 0xd0;
+ dev->bd_addr.b[3] = 0xba;
+ dev->bd_addr.b[4] = 0xbe;
+ dev->bd_addr.b[5] = 0xba;
+ bt_dev_idx ++;
+
+ /* Simple slave-only devices need to implement only .lmp_acl_data */
+ dev->lmp_connection_complete = bt_dummy_lmp_connection_complete;
+ dev->lmp_disconnect_master = bt_dummy_lmp_disconnect_master;
+ dev->lmp_acl_resp = bt_dummy_lmp_acl_resp;
+ dev->lmp_mode_change = bt_dummy_lmp_mode_change;
+ dev->lmp_connection_request = bt_dummy_lmp_connection_request;
+ dev->lmp_disconnect_slave = bt_dummy_lmp_disconnect_slave;
+
+ dev->handle_destroy = bt_dummy_destroy;
+
+ dev->net = net;
+ dev->next = net->slave;
+ net->slave = dev;
+}
+
+void bt_device_done(struct bt_device_s *dev)
+{
+ struct bt_device_s **p = &dev->net->slave;
+
+ while (*p && *p != dev)
+ p = &(*p)->next;
+ if (*p != dev) {
+ fprintf(stderr, "%s: bad bt device \"%s\"\n", __FUNCTION__,
+ dev->lmp_name ?: "(null)");
+ exit(-1);
+ }
+
+ *p = dev->next;
+}
+
+static struct bt_vlan_s {
+ struct bt_scatternet_s net;
+ int id;
+ struct bt_vlan_s *next;
+} *first_bt_vlan;
+
+/* find or alloc a new bluetooth "VLAN" */
+struct bt_scatternet_s *qemu_find_bt_vlan(int id)
+{
+ struct bt_vlan_s **pvlan, *vlan;
+ for (vlan = first_bt_vlan; vlan != NULL; vlan = vlan->next) {
+ if (vlan->id == id)
+ return &vlan->net;
+ }
+ vlan = g_malloc0(sizeof(struct bt_vlan_s));
+ vlan->id = id;
+ pvlan = &first_bt_vlan;
+ while (*pvlan != NULL)
+ pvlan = &(*pvlan)->next;
+ *pvlan = vlan;
+ return &vlan->net;
+}
diff --git a/hw/bt/hci-csr.c b/hw/bt/hci-csr.c
new file mode 100644
index 00000000..7b9b9160
--- /dev/null
+++ b/hw/bt/hci-csr.c
@@ -0,0 +1,454 @@
+/*
+ * Bluetooth serial HCI transport.
+ * CSR41814 HCI with H4p vendor extensions.
+ *
+ * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "sysemu/char.h"
+#include "qemu/timer.h"
+#include "hw/irq.h"
+#include "sysemu/bt.h"
+#include "hw/bt.h"
+
+struct csrhci_s {
+ int enable;
+ qemu_irq *pins;
+ int pin_state;
+ int modem_state;
+ CharDriverState chr;
+#define FIFO_LEN 4096
+ int out_start;
+ int out_len;
+ int out_size;
+ uint8_t outfifo[FIFO_LEN * 2];
+ uint8_t inpkt[FIFO_LEN];
+ int in_len;
+ int in_hdr;
+ int in_data;
+ QEMUTimer *out_tm;
+ int64_t baud_delay;
+
+ bdaddr_t bd_addr;
+ struct HCIInfo *hci;
+};
+
+/* H4+ packet types */
+enum {
+ H4_CMD_PKT = 1,
+ H4_ACL_PKT = 2,
+ H4_SCO_PKT = 3,
+ H4_EVT_PKT = 4,
+ H4_NEG_PKT = 6,
+ H4_ALIVE_PKT = 7,
+};
+
+/* CSR41814 negotiation start magic packet */
+static const uint8_t csrhci_neg_packet[] = {
+ H4_NEG_PKT, 10,
+ 0x00, 0xa0, 0x01, 0x00, 0x00,
+ 0x4c, 0x00, 0x96, 0x00, 0x00,
+};
+
+/* CSR41814 vendor-specific command OCFs */
+enum {
+ OCF_CSR_SEND_FIRMWARE = 0x000,
+};
+
+static inline void csrhci_fifo_wake(struct csrhci_s *s)
+{
+ if (!s->enable || !s->out_len)
+ return;
+
+ /* XXX: Should wait for s->modem_state & CHR_TIOCM_RTS? */
+ if (s->chr.chr_can_read && s->chr.chr_can_read(s->chr.handler_opaque) &&
+ s->chr.chr_read) {
+ s->chr.chr_read(s->chr.handler_opaque,
+ s->outfifo + s->out_start ++, 1);
+ s->out_len --;
+ if (s->out_start >= s->out_size) {
+ s->out_start = 0;
+ s->out_size = FIFO_LEN;
+ }
+ }
+
+ if (s->out_len)
+ timer_mod(s->out_tm, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->baud_delay);
+}
+
+#define csrhci_out_packetz(s, len) memset(csrhci_out_packet(s, len), 0, len)
+static uint8_t *csrhci_out_packet(struct csrhci_s *s, int len)
+{
+ int off = s->out_start + s->out_len;
+
+ /* TODO: do the padding here, i.e. align len */
+ s->out_len += len;
+
+ if (off < FIFO_LEN) {
+ if (off + len > FIFO_LEN && (s->out_size = off + len) > FIFO_LEN * 2) {
+ fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len);
+ exit(-1);
+ }
+ return s->outfifo + off;
+ }
+
+ if (s->out_len > s->out_size) {
+ fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len);
+ exit(-1);
+ }
+
+ return s->outfifo + off - s->out_size;
+}
+
+static inline uint8_t *csrhci_out_packet_csr(struct csrhci_s *s,
+ int type, int len)
+{
+ uint8_t *ret = csrhci_out_packetz(s, len + 2);
+
+ *ret ++ = type;
+ *ret ++ = len;
+
+ return ret;
+}
+
+static inline uint8_t *csrhci_out_packet_event(struct csrhci_s *s,
+ int evt, int len)
+{
+ uint8_t *ret = csrhci_out_packetz(s,
+ len + 1 + sizeof(struct hci_event_hdr));
+
+ *ret ++ = H4_EVT_PKT;
+ ((struct hci_event_hdr *) ret)->evt = evt;
+ ((struct hci_event_hdr *) ret)->plen = len;
+
+ return ret + sizeof(struct hci_event_hdr);
+}
+
+static void csrhci_in_packet_vendor(struct csrhci_s *s, int ocf,
+ uint8_t *data, int len)
+{
+ int offset;
+ uint8_t *rpkt;
+
+ switch (ocf) {
+ case OCF_CSR_SEND_FIRMWARE:
+ /* Check if this is the bd_address packet */
+ if (len >= 18 + 8 && data[12] == 0x01 && data[13] == 0x00) {
+ offset = 18;
+ s->bd_addr.b[0] = data[offset + 7]; /* Beyond cmd packet end(!?) */
+ s->bd_addr.b[1] = data[offset + 6];
+ s->bd_addr.b[2] = data[offset + 4];
+ s->bd_addr.b[3] = data[offset + 0];
+ s->bd_addr.b[4] = data[offset + 3];
+ s->bd_addr.b[5] = data[offset + 2];
+
+ s->hci->bdaddr_set(s->hci, s->bd_addr.b);
+ fprintf(stderr, "%s: bd_address loaded from firmware: "
+ "%02x:%02x:%02x:%02x:%02x:%02x\n", __FUNCTION__,
+ s->bd_addr.b[0], s->bd_addr.b[1], s->bd_addr.b[2],
+ s->bd_addr.b[3], s->bd_addr.b[4], s->bd_addr.b[5]);
+ }
+
+ rpkt = csrhci_out_packet_event(s, EVT_VENDOR, 11);
+ /* Status bytes: no error */
+ rpkt[9] = 0x00;
+ rpkt[10] = 0x00;
+ break;
+
+ default:
+ fprintf(stderr, "%s: got a bad CMD packet\n", __FUNCTION__);
+ return;
+ }
+
+ csrhci_fifo_wake(s);
+}
+
+static void csrhci_in_packet(struct csrhci_s *s, uint8_t *pkt)
+{
+ uint8_t *rpkt;
+ int opc;
+
+ switch (*pkt ++) {
+ case H4_CMD_PKT:
+ opc = le16_to_cpu(((struct hci_command_hdr *) pkt)->opcode);
+ if (cmd_opcode_ogf(opc) == OGF_VENDOR_CMD) {
+ csrhci_in_packet_vendor(s, cmd_opcode_ocf(opc),
+ pkt + sizeof(struct hci_command_hdr),
+ s->in_len - sizeof(struct hci_command_hdr) - 1);
+ return;
+ }
+
+ /* TODO: if the command is OCF_READ_LOCAL_COMMANDS or the likes,
+ * we need to send it to the HCI layer and then add our supported
+ * commands to the returned mask (such as OGF_VENDOR_CMD). With
+ * bt-hci.c we could just have hooks for this kind of commands but
+ * we can't with bt-host.c. */
+
+ s->hci->cmd_send(s->hci, pkt, s->in_len - 1);
+ break;
+
+ case H4_EVT_PKT:
+ goto bad_pkt;
+
+ case H4_ACL_PKT:
+ s->hci->acl_send(s->hci, pkt, s->in_len - 1);
+ break;
+
+ case H4_SCO_PKT:
+ s->hci->sco_send(s->hci, pkt, s->in_len - 1);
+ break;
+
+ case H4_NEG_PKT:
+ if (s->in_hdr != sizeof(csrhci_neg_packet) ||
+ memcmp(pkt - 1, csrhci_neg_packet, s->in_hdr)) {
+ fprintf(stderr, "%s: got a bad NEG packet\n", __FUNCTION__);
+ return;
+ }
+ pkt += 2;
+
+ rpkt = csrhci_out_packet_csr(s, H4_NEG_PKT, 10);
+
+ *rpkt ++ = 0x20; /* Operational settings negotiation Ok */
+ memcpy(rpkt, pkt, 7); rpkt += 7;
+ *rpkt ++ = 0xff;
+ *rpkt = 0xff;
+ break;
+
+ case H4_ALIVE_PKT:
+ if (s->in_hdr != 4 || pkt[1] != 0x55 || pkt[2] != 0x00) {
+ fprintf(stderr, "%s: got a bad ALIVE packet\n", __FUNCTION__);
+ return;
+ }
+
+ rpkt = csrhci_out_packet_csr(s, H4_ALIVE_PKT, 2);
+
+ *rpkt ++ = 0xcc;
+ *rpkt = 0x00;
+ break;
+
+ default:
+ bad_pkt:
+ /* TODO: error out */
+ fprintf(stderr, "%s: got a bad packet\n", __FUNCTION__);
+ break;
+ }
+
+ csrhci_fifo_wake(s);
+}
+
+static int csrhci_header_len(const uint8_t *pkt)
+{
+ switch (pkt[0]) {
+ case H4_CMD_PKT:
+ return HCI_COMMAND_HDR_SIZE;
+ case H4_EVT_PKT:
+ return HCI_EVENT_HDR_SIZE;
+ case H4_ACL_PKT:
+ return HCI_ACL_HDR_SIZE;
+ case H4_SCO_PKT:
+ return HCI_SCO_HDR_SIZE;
+ case H4_NEG_PKT:
+ return pkt[1] + 1;
+ case H4_ALIVE_PKT:
+ return 3;
+ }
+
+ exit(-1);
+}
+
+static int csrhci_data_len(const uint8_t *pkt)
+{
+ switch (*pkt ++) {
+ case H4_CMD_PKT:
+ /* It seems that vendor-specific command packets for H4+ are all
+ * one byte longer than indicated in the standard header. */
+ if (le16_to_cpu(((struct hci_command_hdr *) pkt)->opcode) == 0xfc00)
+ return (((struct hci_command_hdr *) pkt)->plen + 1) & ~1;
+
+ return ((struct hci_command_hdr *) pkt)->plen;
+ case H4_EVT_PKT:
+ return ((struct hci_event_hdr *) pkt)->plen;
+ case H4_ACL_PKT:
+ return le16_to_cpu(((struct hci_acl_hdr *) pkt)->dlen);
+ case H4_SCO_PKT:
+ return ((struct hci_sco_hdr *) pkt)->dlen;
+ case H4_NEG_PKT:
+ case H4_ALIVE_PKT:
+ return 0;
+ }
+
+ exit(-1);
+}
+
+static int csrhci_write(struct CharDriverState *chr,
+ const uint8_t *buf, int len)
+{
+ struct csrhci_s *s = (struct csrhci_s *) chr->opaque;
+ int plen = s->in_len;
+
+ if (!s->enable)
+ return 0;
+
+ s->in_len += len;
+ memcpy(s->inpkt + plen, buf, len);
+
+ while (1) {
+ if (s->in_len >= 2 && plen < 2)
+ s->in_hdr = csrhci_header_len(s->inpkt) + 1;
+
+ if (s->in_len >= s->in_hdr && plen < s->in_hdr)
+ s->in_data = csrhci_data_len(s->inpkt) + s->in_hdr;
+
+ if (s->in_len >= s->in_data) {
+ csrhci_in_packet(s, s->inpkt);
+
+ memmove(s->inpkt, s->inpkt + s->in_len, s->in_len - s->in_data);
+ s->in_len -= s->in_data;
+ s->in_hdr = INT_MAX;
+ s->in_data = INT_MAX;
+ plen = 0;
+ } else
+ break;
+ }
+
+ return len;
+}
+
+static void csrhci_out_hci_packet_event(void *opaque,
+ const uint8_t *data, int len)
+{
+ struct csrhci_s *s = (struct csrhci_s *) opaque;
+ uint8_t *pkt = csrhci_out_packet(s, (len + 2) & ~1); /* Align */
+
+ *pkt ++ = H4_EVT_PKT;
+ memcpy(pkt, data, len);
+
+ csrhci_fifo_wake(s);
+}
+
+static void csrhci_out_hci_packet_acl(void *opaque,
+ const uint8_t *data, int len)
+{
+ struct csrhci_s *s = (struct csrhci_s *) opaque;
+ uint8_t *pkt = csrhci_out_packet(s, (len + 2) & ~1); /* Align */
+
+ *pkt ++ = H4_ACL_PKT;
+ pkt[len & ~1] = 0;
+ memcpy(pkt, data, len);
+
+ csrhci_fifo_wake(s);
+}
+
+static int csrhci_ioctl(struct CharDriverState *chr, int cmd, void *arg)
+{
+ QEMUSerialSetParams *ssp;
+ struct csrhci_s *s = (struct csrhci_s *) chr->opaque;
+ int prev_state = s->modem_state;
+
+ switch (cmd) {
+ case CHR_IOCTL_SERIAL_SET_PARAMS:
+ ssp = (QEMUSerialSetParams *) arg;
+ s->baud_delay = get_ticks_per_sec() / ssp->speed;
+ /* Moments later... (but shorter than 100ms) */
+ s->modem_state |= CHR_TIOCM_CTS;
+ break;
+
+ case CHR_IOCTL_SERIAL_GET_TIOCM:
+ *(int *) arg = s->modem_state;
+ break;
+
+ case CHR_IOCTL_SERIAL_SET_TIOCM:
+ s->modem_state = *(int *) arg;
+ if (~s->modem_state & prev_state & CHR_TIOCM_RTS)
+ s->modem_state &= ~CHR_TIOCM_CTS;
+ break;
+
+ default:
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+static void csrhci_reset(struct csrhci_s *s)
+{
+ s->out_len = 0;
+ s->out_size = FIFO_LEN;
+ s->in_len = 0;
+ s->baud_delay = get_ticks_per_sec();
+ s->enable = 0;
+ s->in_hdr = INT_MAX;
+ s->in_data = INT_MAX;
+
+ s->modem_state = 0;
+ /* After a while... (but sooner than 10ms) */
+ s->modem_state |= CHR_TIOCM_CTS;
+
+ memset(&s->bd_addr, 0, sizeof(bdaddr_t));
+}
+
+static void csrhci_out_tick(void *opaque)
+{
+ csrhci_fifo_wake((struct csrhci_s *) opaque);
+}
+
+static void csrhci_pins(void *opaque, int line, int level)
+{
+ struct csrhci_s *s = (struct csrhci_s *) opaque;
+ int state = s->pin_state;
+
+ s->pin_state &= ~(1 << line);
+ s->pin_state |= (!!level) << line;
+
+ if ((state & ~s->pin_state) & (1 << csrhci_pin_reset)) {
+ /* TODO: Disappear from lower layers */
+ csrhci_reset(s);
+ }
+
+ if (s->pin_state == 3 && state != 3) {
+ s->enable = 1;
+ /* TODO: Wake lower layers up */
+ }
+}
+
+qemu_irq *csrhci_pins_get(CharDriverState *chr)
+{
+ struct csrhci_s *s = (struct csrhci_s *) chr->opaque;
+
+ return s->pins;
+}
+
+CharDriverState *uart_hci_init(qemu_irq wakeup)
+{
+ struct csrhci_s *s = (struct csrhci_s *)
+ g_malloc0(sizeof(struct csrhci_s));
+
+ s->chr.opaque = s;
+ s->chr.chr_write = csrhci_write;
+ s->chr.chr_ioctl = csrhci_ioctl;
+ s->chr.avail_connections = 1;
+
+ s->hci = qemu_next_hci();
+ s->hci->opaque = s;
+ s->hci->evt_recv = csrhci_out_hci_packet_event;
+ s->hci->acl_recv = csrhci_out_hci_packet_acl;
+
+ s->out_tm = timer_new_ns(QEMU_CLOCK_VIRTUAL, csrhci_out_tick, s);
+ s->pins = qemu_allocate_irqs(csrhci_pins, s, __csrhci_pins);
+ csrhci_reset(s);
+
+ return &s->chr;
+}
diff --git a/hw/bt/hci.c b/hw/bt/hci.c
new file mode 100644
index 00000000..7ea3dc6b
--- /dev/null
+++ b/hw/bt/hci.c
@@ -0,0 +1,2265 @@
+/*
+ * QEMU Bluetooth HCI logic.
+ *
+ * Copyright (C) 2007 OpenMoko, Inc.
+ * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "hw/usb.h"
+#include "sysemu/bt.h"
+#include "hw/bt.h"
+
+struct bt_hci_s {
+ uint8_t *(*evt_packet)(void *opaque);
+ void (*evt_submit)(void *opaque, int len);
+ void *opaque;
+ uint8_t evt_buf[256];
+
+ uint8_t acl_buf[4096];
+ int acl_len;
+
+ uint16_t asb_handle;
+ uint16_t psb_handle;
+
+ int last_cmd; /* Note: Always little-endian */
+
+ struct bt_device_s *conn_req_host;
+
+ struct {
+ int inquire;
+ int periodic;
+ int responses_left;
+ int responses;
+ QEMUTimer *inquiry_done;
+ QEMUTimer *inquiry_next;
+ int inquiry_length;
+ int inquiry_period;
+ int inquiry_mode;
+
+#define HCI_HANDLE_OFFSET 0x20
+#define HCI_HANDLES_MAX 0x10
+ struct bt_hci_master_link_s {
+ struct bt_link_s *link;
+ void (*lmp_acl_data)(struct bt_link_s *link,
+ const uint8_t *data, int start, int len);
+ QEMUTimer *acl_mode_timer;
+ } handle[HCI_HANDLES_MAX];
+ uint32_t role_bmp;
+ int last_handle;
+ int connecting;
+ bdaddr_t awaiting_bdaddr[HCI_HANDLES_MAX];
+ } lm;
+
+ uint8_t event_mask[8];
+ uint16_t voice_setting; /* Notw: Always little-endian */
+ uint16_t conn_accept_tout;
+ QEMUTimer *conn_accept_timer;
+
+ struct HCIInfo info;
+ struct bt_device_s device;
+};
+
+#define DEFAULT_RSSI_DBM 20
+
+#define hci_from_info(ptr) container_of((ptr), struct bt_hci_s, info)
+#define hci_from_device(ptr) container_of((ptr), struct bt_hci_s, device)
+
+struct bt_hci_link_s {
+ struct bt_link_s btlink;
+ uint16_t handle; /* Local */
+};
+
+/* LMP layer emulation */
+#if 0
+static void bt_submit_lmp(struct bt_device_s *bt, int length, uint8_t *data)
+{
+ int resp, resplen, error, op, tr;
+ uint8_t respdata[17];
+
+ if (length < 1)
+ return;
+
+ tr = *data & 1;
+ op = *(data ++) >> 1;
+ resp = LMP_ACCEPTED;
+ resplen = 2;
+ respdata[1] = op;
+ error = 0;
+ length --;
+
+ if (op >= 0x7c) { /* Extended opcode */
+ op |= *(data ++) << 8;
+ resp = LMP_ACCEPTED_EXT;
+ resplen = 4;
+ respdata[0] = op >> 8;
+ respdata[1] = op & 0xff;
+ length --;
+ }
+
+ switch (op) {
+ case LMP_ACCEPTED:
+ /* data[0] Op code
+ */
+ if (length < 1) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ resp = 0;
+ break;
+
+ case LMP_ACCEPTED_EXT:
+ /* data[0] Escape op code
+ * data[1] Extended op code
+ */
+ if (length < 2) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ resp = 0;
+ break;
+
+ case LMP_NOT_ACCEPTED:
+ /* data[0] Op code
+ * data[1] Error code
+ */
+ if (length < 2) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ resp = 0;
+ break;
+
+ case LMP_NOT_ACCEPTED_EXT:
+ /* data[0] Op code
+ * data[1] Extended op code
+ * data[2] Error code
+ */
+ if (length < 3) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ resp = 0;
+ break;
+
+ case LMP_HOST_CONNECTION_REQ:
+ break;
+
+ case LMP_SETUP_COMPLETE:
+ resp = LMP_SETUP_COMPLETE;
+ resplen = 1;
+ bt->setup = 1;
+ break;
+
+ case LMP_DETACH:
+ /* data[0] Error code
+ */
+ if (length < 1) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ bt->setup = 0;
+ resp = 0;
+ break;
+
+ case LMP_SUPERVISION_TIMEOUT:
+ /* data[0,1] Supervision timeout
+ */
+ if (length < 2) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ resp = 0;
+ break;
+
+ case LMP_QUALITY_OF_SERVICE:
+ resp = 0;
+ /* Fall through */
+ case LMP_QOS_REQ:
+ /* data[0,1] Poll interval
+ * data[2] N(BC)
+ */
+ if (length < 3) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ break;
+
+ case LMP_MAX_SLOT:
+ resp = 0;
+ /* Fall through */
+ case LMP_MAX_SLOT_REQ:
+ /* data[0] Max slots
+ */
+ if (length < 1) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ break;
+
+ case LMP_AU_RAND:
+ case LMP_IN_RAND:
+ case LMP_COMB_KEY:
+ /* data[0-15] Random number
+ */
+ if (length < 16) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ if (op == LMP_AU_RAND) {
+ if (bt->key_present) {
+ resp = LMP_SRES;
+ resplen = 5;
+ /* XXX: [Part H] Section 6.1 on page 801 */
+ } else {
+ error = HCI_PIN_OR_KEY_MISSING;
+ goto not_accepted;
+ }
+ } else if (op == LMP_IN_RAND) {
+ error = HCI_PAIRING_NOT_ALLOWED;
+ goto not_accepted;
+ } else {
+ /* XXX: [Part H] Section 3.2 on page 779 */
+ resp = LMP_UNIT_KEY;
+ resplen = 17;
+ memcpy(respdata + 1, bt->key, 16);
+
+ error = HCI_UNIT_LINK_KEY_USED;
+ goto not_accepted;
+ }
+ break;
+
+ case LMP_UNIT_KEY:
+ /* data[0-15] Key
+ */
+ if (length < 16) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ memcpy(bt->key, data, 16);
+ bt->key_present = 1;
+ break;
+
+ case LMP_SRES:
+ /* data[0-3] Authentication response
+ */
+ if (length < 4) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ break;
+
+ case LMP_CLKOFFSET_REQ:
+ resp = LMP_CLKOFFSET_RES;
+ resplen = 3;
+ respdata[1] = 0x33;
+ respdata[2] = 0x33;
+ break;
+
+ case LMP_CLKOFFSET_RES:
+ /* data[0,1] Clock offset
+ * (Slave to master only)
+ */
+ if (length < 2) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ break;
+
+ case LMP_VERSION_REQ:
+ case LMP_VERSION_RES:
+ /* data[0] VersNr
+ * data[1,2] CompId
+ * data[3,4] SubVersNr
+ */
+ if (length < 5) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ if (op == LMP_VERSION_REQ) {
+ resp = LMP_VERSION_RES;
+ resplen = 6;
+ respdata[1] = 0x20;
+ respdata[2] = 0xff;
+ respdata[3] = 0xff;
+ respdata[4] = 0xff;
+ respdata[5] = 0xff;
+ } else
+ resp = 0;
+ break;
+
+ case LMP_FEATURES_REQ:
+ case LMP_FEATURES_RES:
+ /* data[0-7] Features
+ */
+ if (length < 8) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ if (op == LMP_FEATURES_REQ) {
+ resp = LMP_FEATURES_RES;
+ resplen = 9;
+ respdata[1] = (bt->lmp_caps >> 0) & 0xff;
+ respdata[2] = (bt->lmp_caps >> 8) & 0xff;
+ respdata[3] = (bt->lmp_caps >> 16) & 0xff;
+ respdata[4] = (bt->lmp_caps >> 24) & 0xff;
+ respdata[5] = (bt->lmp_caps >> 32) & 0xff;
+ respdata[6] = (bt->lmp_caps >> 40) & 0xff;
+ respdata[7] = (bt->lmp_caps >> 48) & 0xff;
+ respdata[8] = (bt->lmp_caps >> 56) & 0xff;
+ } else
+ resp = 0;
+ break;
+
+ case LMP_NAME_REQ:
+ /* data[0] Name offset
+ */
+ if (length < 1) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ resp = LMP_NAME_RES;
+ resplen = 17;
+ respdata[1] = data[0];
+ respdata[2] = strlen(bt->lmp_name);
+ memset(respdata + 3, 0x00, 14);
+ if (respdata[2] > respdata[1])
+ memcpy(respdata + 3, bt->lmp_name + respdata[1],
+ respdata[2] - respdata[1]);
+ break;
+
+ case LMP_NAME_RES:
+ /* data[0] Name offset
+ * data[1] Name length
+ * data[2-15] Name fragment
+ */
+ if (length < 16) {
+ error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE;
+ goto not_accepted;
+ }
+ resp = 0;
+ break;
+
+ default:
+ error = HCI_UNKNOWN_LMP_PDU;
+ /* Fall through */
+ not_accepted:
+ if (op >> 8) {
+ resp = LMP_NOT_ACCEPTED_EXT;
+ resplen = 5;
+ respdata[0] = op >> 8;
+ respdata[1] = op & 0xff;
+ respdata[2] = error;
+ } else {
+ resp = LMP_NOT_ACCEPTED;
+ resplen = 3;
+ respdata[0] = op & 0xff;
+ respdata[1] = error;
+ }
+ }
+
+ if (resp == 0)
+ return;
+
+ if (resp >> 8) {
+ respdata[0] = resp >> 8;
+ respdata[1] = resp & 0xff;
+ } else
+ respdata[0] = resp & 0xff;
+
+ respdata[0] <<= 1;
+ respdata[0] |= tr;
+}
+
+static void bt_submit_raw_acl(struct bt_piconet_s *net, int length, uint8_t *data)
+{
+ struct bt_device_s *slave;
+ if (length < 1)
+ return;
+
+ slave = 0;
+#if 0
+ slave = net->slave;
+#endif
+
+ switch (data[0] & 3) {
+ case LLID_ACLC:
+ bt_submit_lmp(slave, length - 1, data + 1);
+ break;
+ case LLID_ACLU_START:
+#if 0
+ bt_sumbit_l2cap(slave, length - 1, data + 1, (data[0] >> 2) & 1);
+ breka;
+#endif
+ default:
+ case LLID_ACLU_CONT:
+ break;
+ }
+}
+#endif
+
+/* HCI layer emulation */
+
+/* Note: we could ignore endiannes because unswapped handles will still
+ * be valid as connection identifiers for the guest - they don't have to
+ * be continuously allocated. We do it though, to preserve similar
+ * behaviour between hosts. Some things, like the BD_ADDR cannot be
+ * preserved though (for example if a real hci is used). */
+#ifdef HOST_WORDS_BIGENDIAN
+# define HNDL(raw) bswap16(raw)
+#else
+# define HNDL(raw) (raw)
+#endif
+
+static const uint8_t bt_event_reserved_mask[8] = {
+ 0xff, 0x9f, 0xfb, 0xff, 0x07, 0x18, 0x00, 0x00,
+};
+
+
+static void null_hci_send(struct HCIInfo *hci, const uint8_t *data, int len)
+{
+}
+
+static int null_hci_addr_set(struct HCIInfo *hci, const uint8_t *bd_addr)
+{
+ return -ENOTSUP;
+}
+
+struct HCIInfo null_hci = {
+ .cmd_send = null_hci_send,
+ .sco_send = null_hci_send,
+ .acl_send = null_hci_send,
+ .bdaddr_set = null_hci_addr_set,
+};
+
+
+static inline uint8_t *bt_hci_event_start(struct bt_hci_s *hci,
+ int evt, int len)
+{
+ uint8_t *packet, mask;
+ int mask_byte;
+
+ if (len > 255) {
+ fprintf(stderr, "%s: HCI event params too long (%ib)\n",
+ __FUNCTION__, len);
+ exit(-1);
+ }
+
+ mask_byte = (evt - 1) >> 3;
+ mask = 1 << ((evt - 1) & 3);
+ if (mask & bt_event_reserved_mask[mask_byte] & ~hci->event_mask[mask_byte])
+ return NULL;
+
+ packet = hci->evt_packet(hci->opaque);
+ packet[0] = evt;
+ packet[1] = len;
+
+ return &packet[2];
+}
+
+static inline void bt_hci_event(struct bt_hci_s *hci, int evt,
+ void *params, int len)
+{
+ uint8_t *packet = bt_hci_event_start(hci, evt, len);
+
+ if (!packet)
+ return;
+
+ if (len)
+ memcpy(packet, params, len);
+
+ hci->evt_submit(hci->opaque, len + 2);
+}
+
+static inline void bt_hci_event_status(struct bt_hci_s *hci, int status)
+{
+ evt_cmd_status params = {
+ .status = status,
+ .ncmd = 1,
+ .opcode = hci->last_cmd,
+ };
+
+ bt_hci_event(hci, EVT_CMD_STATUS, &params, EVT_CMD_STATUS_SIZE);
+}
+
+static inline void bt_hci_event_complete(struct bt_hci_s *hci,
+ void *ret, int len)
+{
+ uint8_t *packet = bt_hci_event_start(hci, EVT_CMD_COMPLETE,
+ len + EVT_CMD_COMPLETE_SIZE);
+ evt_cmd_complete *params = (evt_cmd_complete *) packet;
+
+ if (!packet)
+ return;
+
+ params->ncmd = 1;
+ params->opcode = hci->last_cmd;
+ if (len)
+ memcpy(&packet[EVT_CMD_COMPLETE_SIZE], ret, len);
+
+ hci->evt_submit(hci->opaque, len + EVT_CMD_COMPLETE_SIZE + 2);
+}
+
+static void bt_hci_inquiry_done(void *opaque)
+{
+ struct bt_hci_s *hci = (struct bt_hci_s *) opaque;
+ uint8_t status = HCI_SUCCESS;
+
+ if (!hci->lm.periodic)
+ hci->lm.inquire = 0;
+
+ /* The specification is inconsistent about this one. Page 565 reads
+ * "The event parameters of Inquiry Complete event will have a summary
+ * of the result from the Inquiry process, which reports the number of
+ * nearby Bluetooth devices that responded [so hci->responses].", but
+ * Event Parameters (see page 729) has only Status. */
+ bt_hci_event(hci, EVT_INQUIRY_COMPLETE, &status, 1);
+}
+
+static void bt_hci_inquiry_result_standard(struct bt_hci_s *hci,
+ struct bt_device_s *slave)
+{
+ inquiry_info params = {
+ .num_responses = 1,
+ .bdaddr = BAINIT(&slave->bd_addr),
+ .pscan_rep_mode = 0x00, /* R0 */
+ .pscan_period_mode = 0x00, /* P0 - deprecated */
+ .pscan_mode = 0x00, /* Standard scan - deprecated */
+ .dev_class[0] = slave->class[0],
+ .dev_class[1] = slave->class[1],
+ .dev_class[2] = slave->class[2],
+ /* TODO: return the clkoff *differenece* */
+ .clock_offset = slave->clkoff, /* Note: no swapping */
+ };
+
+ bt_hci_event(hci, EVT_INQUIRY_RESULT, &params, INQUIRY_INFO_SIZE);
+}
+
+static void bt_hci_inquiry_result_with_rssi(struct bt_hci_s *hci,
+ struct bt_device_s *slave)
+{
+ inquiry_info_with_rssi params = {
+ .num_responses = 1,
+ .bdaddr = BAINIT(&slave->bd_addr),
+ .pscan_rep_mode = 0x00, /* R0 */
+ .pscan_period_mode = 0x00, /* P0 - deprecated */
+ .dev_class[0] = slave->class[0],
+ .dev_class[1] = slave->class[1],
+ .dev_class[2] = slave->class[2],
+ /* TODO: return the clkoff *differenece* */
+ .clock_offset = slave->clkoff, /* Note: no swapping */
+ .rssi = DEFAULT_RSSI_DBM,
+ };
+
+ bt_hci_event(hci, EVT_INQUIRY_RESULT_WITH_RSSI,
+ &params, INQUIRY_INFO_WITH_RSSI_SIZE);
+}
+
+static void bt_hci_inquiry_result(struct bt_hci_s *hci,
+ struct bt_device_s *slave)
+{
+ if (!slave->inquiry_scan || !hci->lm.responses_left)
+ return;
+
+ hci->lm.responses_left --;
+ hci->lm.responses ++;
+
+ switch (hci->lm.inquiry_mode) {
+ case 0x00:
+ bt_hci_inquiry_result_standard(hci, slave);
+ return;
+ case 0x01:
+ bt_hci_inquiry_result_with_rssi(hci, slave);
+ return;
+ default:
+ fprintf(stderr, "%s: bad inquiry mode %02x\n", __FUNCTION__,
+ hci->lm.inquiry_mode);
+ exit(-1);
+ }
+}
+
+static void bt_hci_mod_timer_1280ms(QEMUTimer *timer, int period)
+{
+ timer_mod(timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ muldiv64(period << 7, get_ticks_per_sec(), 100));
+}
+
+static void bt_hci_inquiry_start(struct bt_hci_s *hci, int length)
+{
+ struct bt_device_s *slave;
+
+ hci->lm.inquiry_length = length;
+ for (slave = hci->device.net->slave; slave; slave = slave->next)
+ /* Don't uncover ourselves. */
+ if (slave != &hci->device)
+ bt_hci_inquiry_result(hci, slave);
+
+ /* TODO: register for a callback on a new device's addition to the
+ * scatternet so that if it's added before inquiry_length expires,
+ * an Inquiry Result is generated immediately. Alternatively re-loop
+ * through the devices on the inquiry_length expiration and report
+ * devices not seen before. */
+ if (hci->lm.responses_left)
+ bt_hci_mod_timer_1280ms(hci->lm.inquiry_done, hci->lm.inquiry_length);
+ else
+ bt_hci_inquiry_done(hci);
+
+ if (hci->lm.periodic)
+ bt_hci_mod_timer_1280ms(hci->lm.inquiry_next, hci->lm.inquiry_period);
+}
+
+static void bt_hci_inquiry_next(void *opaque)
+{
+ struct bt_hci_s *hci = (struct bt_hci_s *) opaque;
+
+ hci->lm.responses_left += hci->lm.responses;
+ hci->lm.responses = 0;
+ bt_hci_inquiry_start(hci, hci->lm.inquiry_length);
+}
+
+static inline int bt_hci_handle_bad(struct bt_hci_s *hci, uint16_t handle)
+{
+ return !(handle & HCI_HANDLE_OFFSET) ||
+ handle >= (HCI_HANDLE_OFFSET | HCI_HANDLES_MAX) ||
+ !hci->lm.handle[handle & ~HCI_HANDLE_OFFSET].link;
+}
+
+static inline int bt_hci_role_master(struct bt_hci_s *hci, uint16_t handle)
+{
+ return !!(hci->lm.role_bmp & (1 << (handle & ~HCI_HANDLE_OFFSET)));
+}
+
+static inline struct bt_device_s *bt_hci_remote_dev(struct bt_hci_s *hci,
+ uint16_t handle)
+{
+ struct bt_link_s *link = hci->lm.handle[handle & ~HCI_HANDLE_OFFSET].link;
+
+ return bt_hci_role_master(hci, handle) ? link->slave : link->host;
+}
+
+static void bt_hci_mode_tick(void *opaque);
+static void bt_hci_lmp_link_establish(struct bt_hci_s *hci,
+ struct bt_link_s *link, int master)
+{
+ hci->lm.handle[hci->lm.last_handle].link = link;
+
+ if (master) {
+ /* We are the master side of an ACL link */
+ hci->lm.role_bmp |= 1 << hci->lm.last_handle;
+
+ hci->lm.handle[hci->lm.last_handle].lmp_acl_data =
+ link->slave->lmp_acl_data;
+ } else {
+ /* We are the slave side of an ACL link */
+ hci->lm.role_bmp &= ~(1 << hci->lm.last_handle);
+
+ hci->lm.handle[hci->lm.last_handle].lmp_acl_data =
+ link->host->lmp_acl_resp;
+ }
+
+ /* Mode */
+ if (master) {
+ link->acl_mode = acl_active;
+ hci->lm.handle[hci->lm.last_handle].acl_mode_timer =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, bt_hci_mode_tick, link);
+ }
+}
+
+static void bt_hci_lmp_link_teardown(struct bt_hci_s *hci, uint16_t handle)
+{
+ handle &= ~HCI_HANDLE_OFFSET;
+ hci->lm.handle[handle].link = NULL;
+
+ if (bt_hci_role_master(hci, handle)) {
+ timer_del(hci->lm.handle[handle].acl_mode_timer);
+ timer_free(hci->lm.handle[handle].acl_mode_timer);
+ }
+}
+
+static int bt_hci_connect(struct bt_hci_s *hci, bdaddr_t *bdaddr)
+{
+ struct bt_device_s *slave;
+ struct bt_link_s link;
+
+ for (slave = hci->device.net->slave; slave; slave = slave->next)
+ if (slave->page_scan && !bacmp(&slave->bd_addr, bdaddr))
+ break;
+ if (!slave || slave == &hci->device)
+ return -ENODEV;
+
+ bacpy(&hci->lm.awaiting_bdaddr[hci->lm.connecting ++], &slave->bd_addr);
+
+ link.slave = slave;
+ link.host = &hci->device;
+ link.slave->lmp_connection_request(&link); /* Always last */
+
+ return 0;
+}
+
+static void bt_hci_connection_reject(struct bt_hci_s *hci,
+ struct bt_device_s *host, uint8_t because)
+{
+ struct bt_link_s link = {
+ .slave = &hci->device,
+ .host = host,
+ /* Rest uninitialised */
+ };
+
+ host->reject_reason = because;
+ host->lmp_connection_complete(&link);
+}
+
+static void bt_hci_connection_reject_event(struct bt_hci_s *hci,
+ bdaddr_t *bdaddr)
+{
+ evt_conn_complete params;
+
+ params.status = HCI_NO_CONNECTION;
+ params.handle = 0;
+ bacpy(&params.bdaddr, bdaddr);
+ params.link_type = ACL_LINK;
+ params.encr_mode = 0x00; /* Encryption not required */
+ bt_hci_event(hci, EVT_CONN_COMPLETE, &params, EVT_CONN_COMPLETE_SIZE);
+}
+
+static void bt_hci_connection_accept(struct bt_hci_s *hci,
+ struct bt_device_s *host)
+{
+ struct bt_hci_link_s *link = g_malloc0(sizeof(struct bt_hci_link_s));
+ evt_conn_complete params;
+ uint16_t handle;
+ uint8_t status = HCI_SUCCESS;
+ int tries = HCI_HANDLES_MAX;
+
+ /* Make a connection handle */
+ do {
+ while (hci->lm.handle[++ hci->lm.last_handle].link && -- tries)
+ hci->lm.last_handle &= HCI_HANDLES_MAX - 1;
+ handle = hci->lm.last_handle | HCI_HANDLE_OFFSET;
+ } while ((handle == hci->asb_handle || handle == hci->psb_handle) &&
+ tries);
+
+ if (!tries) {
+ g_free(link);
+ bt_hci_connection_reject(hci, host, HCI_REJECTED_LIMITED_RESOURCES);
+ status = HCI_NO_CONNECTION;
+ goto complete;
+ }
+
+ link->btlink.slave = &hci->device;
+ link->btlink.host = host;
+ link->handle = handle;
+
+ /* Link established */
+ bt_hci_lmp_link_establish(hci, &link->btlink, 0);
+
+complete:
+ params.status = status;
+ params.handle = HNDL(handle);
+ bacpy(&params.bdaddr, &host->bd_addr);
+ params.link_type = ACL_LINK;
+ params.encr_mode = 0x00; /* Encryption not required */
+ bt_hci_event(hci, EVT_CONN_COMPLETE, &params, EVT_CONN_COMPLETE_SIZE);
+
+ /* Neets to be done at the very end because it can trigger a (nested)
+ * disconnected, in case the other and had cancelled the request
+ * locally. */
+ if (status == HCI_SUCCESS) {
+ host->reject_reason = 0;
+ host->lmp_connection_complete(&link->btlink);
+ }
+}
+
+static void bt_hci_lmp_connection_request(struct bt_link_s *link)
+{
+ struct bt_hci_s *hci = hci_from_device(link->slave);
+ evt_conn_request params;
+
+ if (hci->conn_req_host) {
+ bt_hci_connection_reject(hci, link->host,
+ HCI_REJECTED_LIMITED_RESOURCES);
+ return;
+ }
+ hci->conn_req_host = link->host;
+ /* TODO: if masked and auto-accept, then auto-accept,
+ * if masked and not auto-accept, then auto-reject */
+ /* TODO: kick the hci->conn_accept_timer, timeout after
+ * hci->conn_accept_tout * 0.625 msec */
+
+ bacpy(&params.bdaddr, &link->host->bd_addr);
+ memcpy(&params.dev_class, &link->host->class, sizeof(params.dev_class));
+ params.link_type = ACL_LINK;
+ bt_hci_event(hci, EVT_CONN_REQUEST, &params, EVT_CONN_REQUEST_SIZE);
+}
+
+static void bt_hci_conn_accept_timeout(void *opaque)
+{
+ struct bt_hci_s *hci = (struct bt_hci_s *) opaque;
+
+ if (!hci->conn_req_host)
+ /* Already accepted or rejected. If the other end cancelled the
+ * connection request then we still have to reject or accept it
+ * and then we'll get a disconnect. */
+ return;
+
+ /* TODO */
+}
+
+/* Remove from the list of devices which we wanted to connect to and
+ * are awaiting a response from. If the callback sees a response from
+ * a device which is not on the list it will assume it's a connection
+ * that's been cancelled by the host in the meantime and immediately
+ * try to detach the link and send a Connection Complete. */
+static int bt_hci_lmp_connection_ready(struct bt_hci_s *hci,
+ bdaddr_t *bdaddr)
+{
+ int i;
+
+ for (i = 0; i < hci->lm.connecting; i ++)
+ if (!bacmp(&hci->lm.awaiting_bdaddr[i], bdaddr)) {
+ if (i < -- hci->lm.connecting)
+ bacpy(&hci->lm.awaiting_bdaddr[i],
+ &hci->lm.awaiting_bdaddr[hci->lm.connecting]);
+ return 0;
+ }
+
+ return 1;
+}
+
+static void bt_hci_lmp_connection_complete(struct bt_link_s *link)
+{
+ struct bt_hci_s *hci = hci_from_device(link->host);
+ evt_conn_complete params;
+ uint16_t handle;
+ uint8_t status = HCI_SUCCESS;
+ int tries = HCI_HANDLES_MAX;
+
+ if (bt_hci_lmp_connection_ready(hci, &link->slave->bd_addr)) {
+ if (!hci->device.reject_reason)
+ link->slave->lmp_disconnect_slave(link);
+ handle = 0;
+ status = HCI_NO_CONNECTION;
+ goto complete;
+ }
+
+ if (hci->device.reject_reason) {
+ handle = 0;
+ status = hci->device.reject_reason;
+ goto complete;
+ }
+
+ /* Make a connection handle */
+ do {
+ while (hci->lm.handle[++ hci->lm.last_handle].link && -- tries)
+ hci->lm.last_handle &= HCI_HANDLES_MAX - 1;
+ handle = hci->lm.last_handle | HCI_HANDLE_OFFSET;
+ } while ((handle == hci->asb_handle || handle == hci->psb_handle) &&
+ tries);
+
+ if (!tries) {
+ link->slave->lmp_disconnect_slave(link);
+ status = HCI_NO_CONNECTION;
+ goto complete;
+ }
+
+ /* Link established */
+ link->handle = handle;
+ bt_hci_lmp_link_establish(hci, link, 1);
+
+complete:
+ params.status = status;
+ params.handle = HNDL(handle);
+ params.link_type = ACL_LINK;
+ bacpy(&params.bdaddr, &link->slave->bd_addr);
+ params.encr_mode = 0x00; /* Encryption not required */
+ bt_hci_event(hci, EVT_CONN_COMPLETE, &params, EVT_CONN_COMPLETE_SIZE);
+}
+
+static void bt_hci_disconnect(struct bt_hci_s *hci,
+ uint16_t handle, int reason)
+{
+ struct bt_link_s *btlink =
+ hci->lm.handle[handle & ~HCI_HANDLE_OFFSET].link;
+ struct bt_hci_link_s *link;
+ evt_disconn_complete params;
+
+ if (bt_hci_role_master(hci, handle)) {
+ btlink->slave->reject_reason = reason;
+ btlink->slave->lmp_disconnect_slave(btlink);
+ /* The link pointer is invalid from now on */
+
+ goto complete;
+ }
+
+ btlink->host->reject_reason = reason;
+ btlink->host->lmp_disconnect_master(btlink);
+
+ /* We are the slave, we get to clean this burden */
+ link = (struct bt_hci_link_s *) btlink;
+ g_free(link);
+
+complete:
+ bt_hci_lmp_link_teardown(hci, handle);
+
+ params.status = HCI_SUCCESS;
+ params.handle = HNDL(handle);
+ params.reason = HCI_CONNECTION_TERMINATED;
+ bt_hci_event(hci, EVT_DISCONN_COMPLETE,
+ &params, EVT_DISCONN_COMPLETE_SIZE);
+}
+
+/* TODO: use only one function */
+static void bt_hci_lmp_disconnect_host(struct bt_link_s *link)
+{
+ struct bt_hci_s *hci = hci_from_device(link->host);
+ uint16_t handle = link->handle;
+ evt_disconn_complete params;
+
+ bt_hci_lmp_link_teardown(hci, handle);
+
+ params.status = HCI_SUCCESS;
+ params.handle = HNDL(handle);
+ params.reason = hci->device.reject_reason;
+ bt_hci_event(hci, EVT_DISCONN_COMPLETE,
+ &params, EVT_DISCONN_COMPLETE_SIZE);
+}
+
+static void bt_hci_lmp_disconnect_slave(struct bt_link_s *btlink)
+{
+ struct bt_hci_link_s *link = (struct bt_hci_link_s *) btlink;
+ struct bt_hci_s *hci = hci_from_device(btlink->slave);
+ uint16_t handle = link->handle;
+ evt_disconn_complete params;
+
+ g_free(link);
+
+ bt_hci_lmp_link_teardown(hci, handle);
+
+ params.status = HCI_SUCCESS;
+ params.handle = HNDL(handle);
+ params.reason = hci->device.reject_reason;
+ bt_hci_event(hci, EVT_DISCONN_COMPLETE,
+ &params, EVT_DISCONN_COMPLETE_SIZE);
+}
+
+static int bt_hci_name_req(struct bt_hci_s *hci, bdaddr_t *bdaddr)
+{
+ struct bt_device_s *slave;
+ evt_remote_name_req_complete params;
+
+ for (slave = hci->device.net->slave; slave; slave = slave->next)
+ if (slave->page_scan && !bacmp(&slave->bd_addr, bdaddr))
+ break;
+ if (!slave)
+ return -ENODEV;
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+
+ params.status = HCI_SUCCESS;
+ bacpy(&params.bdaddr, &slave->bd_addr);
+ pstrcpy(params.name, sizeof(params.name), slave->lmp_name ?: "");
+ bt_hci_event(hci, EVT_REMOTE_NAME_REQ_COMPLETE,
+ &params, EVT_REMOTE_NAME_REQ_COMPLETE_SIZE);
+
+ return 0;
+}
+
+static int bt_hci_features_req(struct bt_hci_s *hci, uint16_t handle)
+{
+ struct bt_device_s *slave;
+ evt_read_remote_features_complete params;
+
+ if (bt_hci_handle_bad(hci, handle))
+ return -ENODEV;
+
+ slave = bt_hci_remote_dev(hci, handle);
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+
+ params.status = HCI_SUCCESS;
+ params.handle = HNDL(handle);
+ params.features[0] = (slave->lmp_caps >> 0) & 0xff;
+ params.features[1] = (slave->lmp_caps >> 8) & 0xff;
+ params.features[2] = (slave->lmp_caps >> 16) & 0xff;
+ params.features[3] = (slave->lmp_caps >> 24) & 0xff;
+ params.features[4] = (slave->lmp_caps >> 32) & 0xff;
+ params.features[5] = (slave->lmp_caps >> 40) & 0xff;
+ params.features[6] = (slave->lmp_caps >> 48) & 0xff;
+ params.features[7] = (slave->lmp_caps >> 56) & 0xff;
+ bt_hci_event(hci, EVT_READ_REMOTE_FEATURES_COMPLETE,
+ &params, EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE);
+
+ return 0;
+}
+
+static int bt_hci_version_req(struct bt_hci_s *hci, uint16_t handle)
+{
+ evt_read_remote_version_complete params;
+
+ if (bt_hci_handle_bad(hci, handle))
+ return -ENODEV;
+
+ bt_hci_remote_dev(hci, handle);
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+
+ params.status = HCI_SUCCESS;
+ params.handle = HNDL(handle);
+ params.lmp_ver = 0x03;
+ params.manufacturer = cpu_to_le16(0xa000);
+ params.lmp_subver = cpu_to_le16(0xa607);
+ bt_hci_event(hci, EVT_READ_REMOTE_VERSION_COMPLETE,
+ &params, EVT_READ_REMOTE_VERSION_COMPLETE_SIZE);
+
+ return 0;
+}
+
+static int bt_hci_clkoffset_req(struct bt_hci_s *hci, uint16_t handle)
+{
+ struct bt_device_s *slave;
+ evt_read_clock_offset_complete params;
+
+ if (bt_hci_handle_bad(hci, handle))
+ return -ENODEV;
+
+ slave = bt_hci_remote_dev(hci, handle);
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+
+ params.status = HCI_SUCCESS;
+ params.handle = HNDL(handle);
+ /* TODO: return the clkoff *differenece* */
+ params.clock_offset = slave->clkoff; /* Note: no swapping */
+ bt_hci_event(hci, EVT_READ_CLOCK_OFFSET_COMPLETE,
+ &params, EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE);
+
+ return 0;
+}
+
+static void bt_hci_event_mode(struct bt_hci_s *hci, struct bt_link_s *link,
+ uint16_t handle)
+{
+ evt_mode_change params = {
+ .status = HCI_SUCCESS,
+ .handle = HNDL(handle),
+ .mode = link->acl_mode,
+ .interval = cpu_to_le16(link->acl_interval),
+ };
+
+ bt_hci_event(hci, EVT_MODE_CHANGE, &params, EVT_MODE_CHANGE_SIZE);
+}
+
+static void bt_hci_lmp_mode_change_master(struct bt_hci_s *hci,
+ struct bt_link_s *link, int mode, uint16_t interval)
+{
+ link->acl_mode = mode;
+ link->acl_interval = interval;
+
+ bt_hci_event_mode(hci, link, link->handle);
+
+ link->slave->lmp_mode_change(link);
+}
+
+static void bt_hci_lmp_mode_change_slave(struct bt_link_s *btlink)
+{
+ struct bt_hci_link_s *link = (struct bt_hci_link_s *) btlink;
+ struct bt_hci_s *hci = hci_from_device(btlink->slave);
+
+ bt_hci_event_mode(hci, btlink, link->handle);
+}
+
+static int bt_hci_mode_change(struct bt_hci_s *hci, uint16_t handle,
+ int interval, int mode)
+{
+ struct bt_hci_master_link_s *link;
+
+ if (bt_hci_handle_bad(hci, handle) || !bt_hci_role_master(hci, handle))
+ return -ENODEV;
+
+ link = &hci->lm.handle[handle & ~HCI_HANDLE_OFFSET];
+ if (link->link->acl_mode != acl_active) {
+ bt_hci_event_status(hci, HCI_COMMAND_DISALLOWED);
+ return 0;
+ }
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+
+ timer_mod(link->acl_mode_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ muldiv64(interval * 625, get_ticks_per_sec(), 1000000));
+ bt_hci_lmp_mode_change_master(hci, link->link, mode, interval);
+
+ return 0;
+}
+
+static int bt_hci_mode_cancel(struct bt_hci_s *hci, uint16_t handle, int mode)
+{
+ struct bt_hci_master_link_s *link;
+
+ if (bt_hci_handle_bad(hci, handle) || !bt_hci_role_master(hci, handle))
+ return -ENODEV;
+
+ link = &hci->lm.handle[handle & ~HCI_HANDLE_OFFSET];
+ if (link->link->acl_mode != mode) {
+ bt_hci_event_status(hci, HCI_COMMAND_DISALLOWED);
+
+ return 0;
+ }
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+
+ timer_del(link->acl_mode_timer);
+ bt_hci_lmp_mode_change_master(hci, link->link, acl_active, 0);
+
+ return 0;
+}
+
+static void bt_hci_mode_tick(void *opaque)
+{
+ struct bt_link_s *link = opaque;
+ struct bt_hci_s *hci = hci_from_device(link->host);
+
+ bt_hci_lmp_mode_change_master(hci, link, acl_active, 0);
+}
+
+static void bt_hci_reset(struct bt_hci_s *hci)
+{
+ hci->acl_len = 0;
+ hci->last_cmd = 0;
+ hci->lm.connecting = 0;
+
+ hci->event_mask[0] = 0xff;
+ hci->event_mask[1] = 0xff;
+ hci->event_mask[2] = 0xff;
+ hci->event_mask[3] = 0xff;
+ hci->event_mask[4] = 0xff;
+ hci->event_mask[5] = 0x1f;
+ hci->event_mask[6] = 0x00;
+ hci->event_mask[7] = 0x00;
+ hci->device.inquiry_scan = 0;
+ hci->device.page_scan = 0;
+ if (hci->device.lmp_name)
+ g_free((void *) hci->device.lmp_name);
+ hci->device.lmp_name = NULL;
+ hci->device.class[0] = 0x00;
+ hci->device.class[1] = 0x00;
+ hci->device.class[2] = 0x00;
+ hci->voice_setting = 0x0000;
+ hci->conn_accept_tout = 0x1f40;
+ hci->lm.inquiry_mode = 0x00;
+
+ hci->psb_handle = 0x000;
+ hci->asb_handle = 0x000;
+
+ /* XXX: timer_del(sl->acl_mode_timer); for all links */
+ timer_del(hci->lm.inquiry_done);
+ timer_del(hci->lm.inquiry_next);
+ timer_del(hci->conn_accept_timer);
+}
+
+static void bt_hci_read_local_version_rp(struct bt_hci_s *hci)
+{
+ read_local_version_rp lv = {
+ .status = HCI_SUCCESS,
+ .hci_ver = 0x03,
+ .hci_rev = cpu_to_le16(0xa607),
+ .lmp_ver = 0x03,
+ .manufacturer = cpu_to_le16(0xa000),
+ .lmp_subver = cpu_to_le16(0xa607),
+ };
+
+ bt_hci_event_complete(hci, &lv, READ_LOCAL_VERSION_RP_SIZE);
+}
+
+static void bt_hci_read_local_commands_rp(struct bt_hci_s *hci)
+{
+ read_local_commands_rp lc = {
+ .status = HCI_SUCCESS,
+ .commands = {
+ /* Keep updated! */
+ /* Also, keep in sync with hci->device.lmp_caps in bt_new_hci */
+ 0xbf, 0x80, 0xf9, 0x03, 0xb2, 0xc0, 0x03, 0xc3,
+ 0x00, 0x0f, 0x80, 0x00, 0xc0, 0x00, 0xe8, 0x13,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ };
+
+ bt_hci_event_complete(hci, &lc, READ_LOCAL_COMMANDS_RP_SIZE);
+}
+
+static void bt_hci_read_local_features_rp(struct bt_hci_s *hci)
+{
+ read_local_features_rp lf = {
+ .status = HCI_SUCCESS,
+ .features = {
+ (hci->device.lmp_caps >> 0) & 0xff,
+ (hci->device.lmp_caps >> 8) & 0xff,
+ (hci->device.lmp_caps >> 16) & 0xff,
+ (hci->device.lmp_caps >> 24) & 0xff,
+ (hci->device.lmp_caps >> 32) & 0xff,
+ (hci->device.lmp_caps >> 40) & 0xff,
+ (hci->device.lmp_caps >> 48) & 0xff,
+ (hci->device.lmp_caps >> 56) & 0xff,
+ },
+ };
+
+ bt_hci_event_complete(hci, &lf, READ_LOCAL_FEATURES_RP_SIZE);
+}
+
+static void bt_hci_read_local_ext_features_rp(struct bt_hci_s *hci, int page)
+{
+ read_local_ext_features_rp lef = {
+ .status = HCI_SUCCESS,
+ .page_num = page,
+ .max_page_num = 0x00,
+ .features = {
+ /* Keep updated! */
+ 0x5f, 0x35, 0x85, 0x7e, 0x9b, 0x19, 0x00, 0x80,
+ },
+ };
+ if (page)
+ memset(lef.features, 0, sizeof(lef.features));
+
+ bt_hci_event_complete(hci, &lef, READ_LOCAL_EXT_FEATURES_RP_SIZE);
+}
+
+static void bt_hci_read_buffer_size_rp(struct bt_hci_s *hci)
+{
+ read_buffer_size_rp bs = {
+ /* This can be made configurable, for one standard USB dongle HCI
+ * the four values are cpu_to_le16(0x0180), 0x40,
+ * cpu_to_le16(0x0008), cpu_to_le16(0x0008). */
+ .status = HCI_SUCCESS,
+ .acl_mtu = cpu_to_le16(0x0200),
+ .sco_mtu = 0,
+ .acl_max_pkt = cpu_to_le16(0x0001),
+ .sco_max_pkt = cpu_to_le16(0x0000),
+ };
+
+ bt_hci_event_complete(hci, &bs, READ_BUFFER_SIZE_RP_SIZE);
+}
+
+/* Deprecated in V2.0 (page 661) */
+static void bt_hci_read_country_code_rp(struct bt_hci_s *hci)
+{
+ read_country_code_rp cc ={
+ .status = HCI_SUCCESS,
+ .country_code = 0x00, /* North America & Europe^1 and Japan */
+ };
+
+ bt_hci_event_complete(hci, &cc, READ_COUNTRY_CODE_RP_SIZE);
+
+ /* ^1. Except France, sorry */
+}
+
+static void bt_hci_read_bd_addr_rp(struct bt_hci_s *hci)
+{
+ read_bd_addr_rp ba = {
+ .status = HCI_SUCCESS,
+ .bdaddr = BAINIT(&hci->device.bd_addr),
+ };
+
+ bt_hci_event_complete(hci, &ba, READ_BD_ADDR_RP_SIZE);
+}
+
+static int bt_hci_link_quality_rp(struct bt_hci_s *hci, uint16_t handle)
+{
+ read_link_quality_rp lq = {
+ .status = HCI_SUCCESS,
+ .handle = HNDL(handle),
+ .link_quality = 0xff,
+ };
+
+ if (bt_hci_handle_bad(hci, handle))
+ lq.status = HCI_NO_CONNECTION;
+
+ bt_hci_event_complete(hci, &lq, READ_LINK_QUALITY_RP_SIZE);
+ return 0;
+}
+
+/* Generate a Command Complete event with only the Status parameter */
+static inline void bt_hci_event_complete_status(struct bt_hci_s *hci,
+ uint8_t status)
+{
+ bt_hci_event_complete(hci, &status, 1);
+}
+
+static inline void bt_hci_event_complete_conn_cancel(struct bt_hci_s *hci,
+ uint8_t status, bdaddr_t *bd_addr)
+{
+ create_conn_cancel_rp params = {
+ .status = status,
+ .bdaddr = BAINIT(bd_addr),
+ };
+
+ bt_hci_event_complete(hci, &params, CREATE_CONN_CANCEL_RP_SIZE);
+}
+
+static inline void bt_hci_event_auth_complete(struct bt_hci_s *hci,
+ uint16_t handle)
+{
+ evt_auth_complete params = {
+ .status = HCI_SUCCESS,
+ .handle = HNDL(handle),
+ };
+
+ bt_hci_event(hci, EVT_AUTH_COMPLETE, &params, EVT_AUTH_COMPLETE_SIZE);
+}
+
+static inline void bt_hci_event_encrypt_change(struct bt_hci_s *hci,
+ uint16_t handle, uint8_t mode)
+{
+ evt_encrypt_change params = {
+ .status = HCI_SUCCESS,
+ .handle = HNDL(handle),
+ .encrypt = mode,
+ };
+
+ bt_hci_event(hci, EVT_ENCRYPT_CHANGE, &params, EVT_ENCRYPT_CHANGE_SIZE);
+}
+
+static inline void bt_hci_event_complete_name_cancel(struct bt_hci_s *hci,
+ bdaddr_t *bd_addr)
+{
+ remote_name_req_cancel_rp params = {
+ .status = HCI_INVALID_PARAMETERS,
+ .bdaddr = BAINIT(bd_addr),
+ };
+
+ bt_hci_event_complete(hci, &params, REMOTE_NAME_REQ_CANCEL_RP_SIZE);
+}
+
+static inline void bt_hci_event_read_remote_ext_features(struct bt_hci_s *hci,
+ uint16_t handle)
+{
+ evt_read_remote_ext_features_complete params = {
+ .status = HCI_UNSUPPORTED_FEATURE,
+ .handle = HNDL(handle),
+ /* Rest uninitialised */
+ };
+
+ bt_hci_event(hci, EVT_READ_REMOTE_EXT_FEATURES_COMPLETE,
+ &params, EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE);
+}
+
+static inline void bt_hci_event_complete_lmp_handle(struct bt_hci_s *hci,
+ uint16_t handle)
+{
+ read_lmp_handle_rp params = {
+ .status = HCI_NO_CONNECTION,
+ .handle = HNDL(handle),
+ .reserved = 0,
+ /* Rest uninitialised */
+ };
+
+ bt_hci_event_complete(hci, &params, READ_LMP_HANDLE_RP_SIZE);
+}
+
+static inline void bt_hci_event_complete_role_discovery(struct bt_hci_s *hci,
+ int status, uint16_t handle, int master)
+{
+ role_discovery_rp params = {
+ .status = status,
+ .handle = HNDL(handle),
+ .role = master ? 0x00 : 0x01,
+ };
+
+ bt_hci_event_complete(hci, &params, ROLE_DISCOVERY_RP_SIZE);
+}
+
+static inline void bt_hci_event_complete_flush(struct bt_hci_s *hci,
+ int status, uint16_t handle)
+{
+ flush_rp params = {
+ .status = status,
+ .handle = HNDL(handle),
+ };
+
+ bt_hci_event_complete(hci, &params, FLUSH_RP_SIZE);
+}
+
+static inline void bt_hci_event_complete_read_local_name(struct bt_hci_s *hci)
+{
+ read_local_name_rp params;
+ params.status = HCI_SUCCESS;
+ memset(params.name, 0, sizeof(params.name));
+ if (hci->device.lmp_name)
+ pstrcpy(params.name, sizeof(params.name), hci->device.lmp_name);
+
+ bt_hci_event_complete(hci, &params, READ_LOCAL_NAME_RP_SIZE);
+}
+
+static inline void bt_hci_event_complete_read_conn_accept_timeout(
+ struct bt_hci_s *hci)
+{
+ read_conn_accept_timeout_rp params = {
+ .status = HCI_SUCCESS,
+ .timeout = cpu_to_le16(hci->conn_accept_tout),
+ };
+
+ bt_hci_event_complete(hci, &params, READ_CONN_ACCEPT_TIMEOUT_RP_SIZE);
+}
+
+static inline void bt_hci_event_complete_read_scan_enable(struct bt_hci_s *hci)
+{
+ read_scan_enable_rp params = {
+ .status = HCI_SUCCESS,
+ .enable =
+ (hci->device.inquiry_scan ? SCAN_INQUIRY : 0) |
+ (hci->device.page_scan ? SCAN_PAGE : 0),
+ };
+
+ bt_hci_event_complete(hci, &params, READ_SCAN_ENABLE_RP_SIZE);
+}
+
+static inline void bt_hci_event_complete_read_local_class(struct bt_hci_s *hci)
+{
+ read_class_of_dev_rp params;
+
+ params.status = HCI_SUCCESS;
+ memcpy(params.dev_class, hci->device.class, sizeof(params.dev_class));
+
+ bt_hci_event_complete(hci, &params, READ_CLASS_OF_DEV_RP_SIZE);
+}
+
+static inline void bt_hci_event_complete_voice_setting(struct bt_hci_s *hci)
+{
+ read_voice_setting_rp params = {
+ .status = HCI_SUCCESS,
+ .voice_setting = hci->voice_setting, /* Note: no swapping */
+ };
+
+ bt_hci_event_complete(hci, &params, READ_VOICE_SETTING_RP_SIZE);
+}
+
+static inline void bt_hci_event_complete_read_inquiry_mode(
+ struct bt_hci_s *hci)
+{
+ read_inquiry_mode_rp params = {
+ .status = HCI_SUCCESS,
+ .mode = hci->lm.inquiry_mode,
+ };
+
+ bt_hci_event_complete(hci, &params, READ_INQUIRY_MODE_RP_SIZE);
+}
+
+static inline void bt_hci_event_num_comp_pkts(struct bt_hci_s *hci,
+ uint16_t handle, int packets)
+{
+ uint16_t buf[EVT_NUM_COMP_PKTS_SIZE(1) / 2 + 1];
+ evt_num_comp_pkts *params = (void *) ((uint8_t *) buf + 1);
+
+ params->num_hndl = 1;
+ params->connection->handle = HNDL(handle);
+ params->connection->num_packets = cpu_to_le16(packets);
+
+ bt_hci_event(hci, EVT_NUM_COMP_PKTS, params, EVT_NUM_COMP_PKTS_SIZE(1));
+}
+
+static void bt_submit_hci(struct HCIInfo *info,
+ const uint8_t *data, int length)
+{
+ struct bt_hci_s *hci = hci_from_info(info);
+ uint16_t cmd;
+ int paramlen, i;
+
+ if (length < HCI_COMMAND_HDR_SIZE)
+ goto short_hci;
+
+ memcpy(&hci->last_cmd, data, 2);
+
+ cmd = (data[1] << 8) | data[0];
+ paramlen = data[2];
+ if (cmd_opcode_ogf(cmd) == 0 || cmd_opcode_ocf(cmd) == 0) /* NOP */
+ return;
+
+ data += HCI_COMMAND_HDR_SIZE;
+ length -= HCI_COMMAND_HDR_SIZE;
+
+ if (paramlen > length)
+ return;
+
+#define PARAM(cmd, param) (((cmd##_cp *) data)->param)
+#define PARAM16(cmd, param) le16_to_cpup(&PARAM(cmd, param))
+#define PARAMHANDLE(cmd) HNDL(PARAM(cmd, handle))
+#define LENGTH_CHECK(cmd) if (length < sizeof(cmd##_cp)) goto short_hci
+ /* Note: the supported commands bitmask in bt_hci_read_local_commands_rp
+ * needs to be updated every time a command is implemented here! */
+ switch (cmd) {
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY):
+ LENGTH_CHECK(inquiry);
+
+ if (PARAM(inquiry, length) < 1) {
+ bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+
+ hci->lm.inquire = 1;
+ hci->lm.periodic = 0;
+ hci->lm.responses_left = PARAM(inquiry, num_rsp) ?: INT_MAX;
+ hci->lm.responses = 0;
+ bt_hci_event_status(hci, HCI_SUCCESS);
+ bt_hci_inquiry_start(hci, PARAM(inquiry, length));
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY_CANCEL):
+ if (!hci->lm.inquire || hci->lm.periodic) {
+ fprintf(stderr, "%s: Inquiry Cancel should only be issued after "
+ "the Inquiry command has been issued, a Command "
+ "Status event has been received for the Inquiry "
+ "command, and before the Inquiry Complete event "
+ "occurs", __FUNCTION__);
+ bt_hci_event_complete_status(hci, HCI_COMMAND_DISALLOWED);
+ break;
+ }
+
+ hci->lm.inquire = 0;
+ timer_del(hci->lm.inquiry_done);
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_PERIODIC_INQUIRY):
+ LENGTH_CHECK(periodic_inquiry);
+
+ if (!(PARAM(periodic_inquiry, length) <
+ PARAM16(periodic_inquiry, min_period) &&
+ PARAM16(periodic_inquiry, min_period) <
+ PARAM16(periodic_inquiry, max_period)) ||
+ PARAM(periodic_inquiry, length) < 1 ||
+ PARAM16(periodic_inquiry, min_period) < 2 ||
+ PARAM16(periodic_inquiry, max_period) < 3) {
+ bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+
+ hci->lm.inquire = 1;
+ hci->lm.periodic = 1;
+ hci->lm.responses_left = PARAM(periodic_inquiry, num_rsp);
+ hci->lm.responses = 0;
+ hci->lm.inquiry_period = PARAM16(periodic_inquiry, max_period);
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ bt_hci_inquiry_start(hci, PARAM(periodic_inquiry, length));
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_EXIT_PERIODIC_INQUIRY):
+ if (!hci->lm.inquire || !hci->lm.periodic) {
+ fprintf(stderr, "%s: Inquiry Cancel should only be issued after "
+ "the Inquiry command has been issued, a Command "
+ "Status event has been received for the Inquiry "
+ "command, and before the Inquiry Complete event "
+ "occurs", __FUNCTION__);
+ bt_hci_event_complete_status(hci, HCI_COMMAND_DISALLOWED);
+ break;
+ }
+ hci->lm.inquire = 0;
+ timer_del(hci->lm.inquiry_done);
+ timer_del(hci->lm.inquiry_next);
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_CREATE_CONN):
+ LENGTH_CHECK(create_conn);
+
+ if (hci->lm.connecting >= HCI_HANDLES_MAX) {
+ bt_hci_event_status(hci, HCI_REJECTED_LIMITED_RESOURCES);
+ break;
+ }
+ bt_hci_event_status(hci, HCI_SUCCESS);
+
+ if (bt_hci_connect(hci, &PARAM(create_conn, bdaddr)))
+ bt_hci_connection_reject_event(hci, &PARAM(create_conn, bdaddr));
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_DISCONNECT):
+ LENGTH_CHECK(disconnect);
+
+ if (bt_hci_handle_bad(hci, PARAMHANDLE(disconnect))) {
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ break;
+ }
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+ bt_hci_disconnect(hci, PARAMHANDLE(disconnect),
+ PARAM(disconnect, reason));
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_CREATE_CONN_CANCEL):
+ LENGTH_CHECK(create_conn_cancel);
+
+ if (bt_hci_lmp_connection_ready(hci,
+ &PARAM(create_conn_cancel, bdaddr))) {
+ for (i = 0; i < HCI_HANDLES_MAX; i ++)
+ if (bt_hci_role_master(hci, i) && hci->lm.handle[i].link &&
+ !bacmp(&hci->lm.handle[i].link->slave->bd_addr,
+ &PARAM(create_conn_cancel, bdaddr)))
+ break;
+
+ bt_hci_event_complete_conn_cancel(hci, i < HCI_HANDLES_MAX ?
+ HCI_ACL_CONNECTION_EXISTS : HCI_NO_CONNECTION,
+ &PARAM(create_conn_cancel, bdaddr));
+ } else
+ bt_hci_event_complete_conn_cancel(hci, HCI_SUCCESS,
+ &PARAM(create_conn_cancel, bdaddr));
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_ACCEPT_CONN_REQ):
+ LENGTH_CHECK(accept_conn_req);
+
+ if (!hci->conn_req_host ||
+ bacmp(&PARAM(accept_conn_req, bdaddr),
+ &hci->conn_req_host->bd_addr)) {
+ bt_hci_event_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+ bt_hci_connection_accept(hci, hci->conn_req_host);
+ hci->conn_req_host = NULL;
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_REJECT_CONN_REQ):
+ LENGTH_CHECK(reject_conn_req);
+
+ if (!hci->conn_req_host ||
+ bacmp(&PARAM(reject_conn_req, bdaddr),
+ &hci->conn_req_host->bd_addr)) {
+ bt_hci_event_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+
+ bt_hci_event_status(hci, HCI_SUCCESS);
+ bt_hci_connection_reject(hci, hci->conn_req_host,
+ PARAM(reject_conn_req, reason));
+ bt_hci_connection_reject_event(hci, &hci->conn_req_host->bd_addr);
+ hci->conn_req_host = NULL;
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_AUTH_REQUESTED):
+ LENGTH_CHECK(auth_requested);
+
+ if (bt_hci_handle_bad(hci, PARAMHANDLE(auth_requested)))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ else {
+ bt_hci_event_status(hci, HCI_SUCCESS);
+ bt_hci_event_auth_complete(hci, PARAMHANDLE(auth_requested));
+ }
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT):
+ LENGTH_CHECK(set_conn_encrypt);
+
+ if (bt_hci_handle_bad(hci, PARAMHANDLE(set_conn_encrypt)))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ else {
+ bt_hci_event_status(hci, HCI_SUCCESS);
+ bt_hci_event_encrypt_change(hci,
+ PARAMHANDLE(set_conn_encrypt),
+ PARAM(set_conn_encrypt, encrypt));
+ }
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_REMOTE_NAME_REQ):
+ LENGTH_CHECK(remote_name_req);
+
+ if (bt_hci_name_req(hci, &PARAM(remote_name_req, bdaddr)))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_REMOTE_NAME_REQ_CANCEL):
+ LENGTH_CHECK(remote_name_req_cancel);
+
+ bt_hci_event_complete_name_cancel(hci,
+ &PARAM(remote_name_req_cancel, bdaddr));
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_REMOTE_FEATURES):
+ LENGTH_CHECK(read_remote_features);
+
+ if (bt_hci_features_req(hci, PARAMHANDLE(read_remote_features)))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_REMOTE_EXT_FEATURES):
+ LENGTH_CHECK(read_remote_ext_features);
+
+ if (bt_hci_handle_bad(hci, PARAMHANDLE(read_remote_ext_features)))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ else {
+ bt_hci_event_status(hci, HCI_SUCCESS);
+ bt_hci_event_read_remote_ext_features(hci,
+ PARAMHANDLE(read_remote_ext_features));
+ }
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_REMOTE_VERSION):
+ LENGTH_CHECK(read_remote_version);
+
+ if (bt_hci_version_req(hci, PARAMHANDLE(read_remote_version)))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_CLOCK_OFFSET):
+ LENGTH_CHECK(read_clock_offset);
+
+ if (bt_hci_clkoffset_req(hci, PARAMHANDLE(read_clock_offset)))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_LMP_HANDLE):
+ LENGTH_CHECK(read_lmp_handle);
+
+ /* TODO: */
+ bt_hci_event_complete_lmp_handle(hci, PARAMHANDLE(read_lmp_handle));
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_POLICY, OCF_HOLD_MODE):
+ LENGTH_CHECK(hold_mode);
+
+ if (PARAM16(hold_mode, min_interval) >
+ PARAM16(hold_mode, max_interval) ||
+ PARAM16(hold_mode, min_interval) < 0x0002 ||
+ PARAM16(hold_mode, max_interval) > 0xff00 ||
+ (PARAM16(hold_mode, min_interval) & 1) ||
+ (PARAM16(hold_mode, max_interval) & 1)) {
+ bt_hci_event_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+
+ if (bt_hci_mode_change(hci, PARAMHANDLE(hold_mode),
+ PARAM16(hold_mode, max_interval),
+ acl_hold))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_POLICY, OCF_PARK_MODE):
+ LENGTH_CHECK(park_mode);
+
+ if (PARAM16(park_mode, min_interval) >
+ PARAM16(park_mode, max_interval) ||
+ PARAM16(park_mode, min_interval) < 0x000e ||
+ (PARAM16(park_mode, min_interval) & 1) ||
+ (PARAM16(park_mode, max_interval) & 1)) {
+ bt_hci_event_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+
+ if (bt_hci_mode_change(hci, PARAMHANDLE(park_mode),
+ PARAM16(park_mode, max_interval),
+ acl_parked))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_POLICY, OCF_EXIT_PARK_MODE):
+ LENGTH_CHECK(exit_park_mode);
+
+ if (bt_hci_mode_cancel(hci, PARAMHANDLE(exit_park_mode),
+ acl_parked))
+ bt_hci_event_status(hci, HCI_NO_CONNECTION);
+ break;
+
+ case cmd_opcode_pack(OGF_LINK_POLICY, OCF_ROLE_DISCOVERY):
+ LENGTH_CHECK(role_discovery);
+
+ if (bt_hci_handle_bad(hci, PARAMHANDLE(role_discovery)))
+ bt_hci_event_complete_role_discovery(hci,
+ HCI_NO_CONNECTION, PARAMHANDLE(role_discovery), 0);
+ else
+ bt_hci_event_complete_role_discovery(hci,
+ HCI_SUCCESS, PARAMHANDLE(role_discovery),
+ bt_hci_role_master(hci,
+ PARAMHANDLE(role_discovery)));
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_SET_EVENT_MASK):
+ LENGTH_CHECK(set_event_mask);
+
+ memcpy(hci->event_mask, PARAM(set_event_mask, mask), 8);
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_RESET):
+ bt_hci_reset(hci);
+ bt_hci_event_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_SET_EVENT_FLT):
+ if (length >= 1 && PARAM(set_event_flt, flt_type) == FLT_CLEAR_ALL)
+ /* No length check */;
+ else
+ LENGTH_CHECK(set_event_flt);
+
+ /* Filters are not implemented */
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_FLUSH):
+ LENGTH_CHECK(flush);
+
+ if (bt_hci_handle_bad(hci, PARAMHANDLE(flush)))
+ bt_hci_event_complete_flush(hci,
+ HCI_NO_CONNECTION, PARAMHANDLE(flush));
+ else {
+ /* TODO: ordering? */
+ bt_hci_event(hci, EVT_FLUSH_OCCURRED,
+ &PARAM(flush, handle),
+ EVT_FLUSH_OCCURRED_SIZE);
+ bt_hci_event_complete_flush(hci,
+ HCI_SUCCESS, PARAMHANDLE(flush));
+ }
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME):
+ LENGTH_CHECK(change_local_name);
+
+ if (hci->device.lmp_name)
+ g_free((void *) hci->device.lmp_name);
+ hci->device.lmp_name = g_strndup(PARAM(change_local_name, name),
+ sizeof(PARAM(change_local_name, name)));
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_LOCAL_NAME):
+ bt_hci_event_complete_read_local_name(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_CONN_ACCEPT_TIMEOUT):
+ bt_hci_event_complete_read_conn_accept_timeout(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_CONN_ACCEPT_TIMEOUT):
+ /* TODO */
+ LENGTH_CHECK(write_conn_accept_timeout);
+
+ if (PARAM16(write_conn_accept_timeout, timeout) < 0x0001 ||
+ PARAM16(write_conn_accept_timeout, timeout) > 0xb540) {
+ bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+
+ hci->conn_accept_tout = PARAM16(write_conn_accept_timeout, timeout);
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_SCAN_ENABLE):
+ bt_hci_event_complete_read_scan_enable(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE):
+ LENGTH_CHECK(write_scan_enable);
+
+ /* TODO: check that the remaining bits are all 0 */
+ hci->device.inquiry_scan =
+ !!(PARAM(write_scan_enable, scan_enable) & SCAN_INQUIRY);
+ hci->device.page_scan =
+ !!(PARAM(write_scan_enable, scan_enable) & SCAN_PAGE);
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_CLASS_OF_DEV):
+ bt_hci_event_complete_read_local_class(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV):
+ LENGTH_CHECK(write_class_of_dev);
+
+ memcpy(hci->device.class, PARAM(write_class_of_dev, dev_class),
+ sizeof(PARAM(write_class_of_dev, dev_class)));
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_VOICE_SETTING):
+ bt_hci_event_complete_voice_setting(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_VOICE_SETTING):
+ LENGTH_CHECK(write_voice_setting);
+
+ hci->voice_setting = PARAM(write_voice_setting, voice_setting);
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_HOST_NUMBER_OF_COMPLETED_PACKETS):
+ if (length < data[0] * 2 + 1)
+ goto short_hci;
+
+ for (i = 0; i < data[0]; i ++)
+ if (bt_hci_handle_bad(hci,
+ data[i * 2 + 1] | (data[i * 2 + 2] << 8)))
+ bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_INQUIRY_MODE):
+ /* Only if (local_features[3] & 0x40) && (local_commands[12] & 0x40)
+ * else
+ * goto unknown_command */
+ bt_hci_event_complete_read_inquiry_mode(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_INQUIRY_MODE):
+ /* Only if (local_features[3] & 0x40) && (local_commands[12] & 0x80)
+ * else
+ * goto unknown_command */
+ LENGTH_CHECK(write_inquiry_mode);
+
+ if (PARAM(write_inquiry_mode, mode) > 0x01) {
+ bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+
+ hci->lm.inquiry_mode = PARAM(write_inquiry_mode, mode);
+ bt_hci_event_complete_status(hci, HCI_SUCCESS);
+ break;
+
+ case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_VERSION):
+ bt_hci_read_local_version_rp(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_COMMANDS):
+ bt_hci_read_local_commands_rp(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_FEATURES):
+ bt_hci_read_local_features_rp(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_EXT_FEATURES):
+ LENGTH_CHECK(read_local_ext_features);
+
+ bt_hci_read_local_ext_features_rp(hci,
+ PARAM(read_local_ext_features, page_num));
+ break;
+
+ case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_BUFFER_SIZE):
+ bt_hci_read_buffer_size_rp(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_COUNTRY_CODE):
+ bt_hci_read_country_code_rp(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_BD_ADDR):
+ bt_hci_read_bd_addr_rp(hci);
+ break;
+
+ case cmd_opcode_pack(OGF_STATUS_PARAM, OCF_READ_LINK_QUALITY):
+ LENGTH_CHECK(read_link_quality);
+
+ bt_hci_link_quality_rp(hci, PARAMHANDLE(read_link_quality));
+ break;
+
+ default:
+ bt_hci_event_status(hci, HCI_UNKNOWN_COMMAND);
+ break;
+
+ short_hci:
+ fprintf(stderr, "%s: HCI packet too short (%iB)\n",
+ __FUNCTION__, length);
+ bt_hci_event_status(hci, HCI_INVALID_PARAMETERS);
+ break;
+ }
+}
+
+/* We could perform fragmentation here, we can't do "recombination" because
+ * at this layer the length of the payload is not know ahead, so we only
+ * know that a packet contained the last fragment of the SDU when the next
+ * SDU starts. */
+static inline void bt_hci_lmp_acl_data(struct bt_hci_s *hci, uint16_t handle,
+ const uint8_t *data, int start, int len)
+{
+ struct hci_acl_hdr *pkt = (void *) hci->acl_buf;
+
+ /* TODO: packet flags */
+ /* TODO: avoid memcpy'ing */
+
+ if (len + HCI_ACL_HDR_SIZE > sizeof(hci->acl_buf)) {
+ fprintf(stderr, "%s: can't take ACL packets %i bytes long\n",
+ __FUNCTION__, len);
+ return;
+ }
+ memcpy(hci->acl_buf + HCI_ACL_HDR_SIZE, data, len);
+
+ pkt->handle = cpu_to_le16(
+ acl_handle_pack(handle, start ? ACL_START : ACL_CONT));
+ pkt->dlen = cpu_to_le16(len);
+ hci->info.acl_recv(hci->info.opaque,
+ hci->acl_buf, len + HCI_ACL_HDR_SIZE);
+}
+
+static void bt_hci_lmp_acl_data_slave(struct bt_link_s *btlink,
+ const uint8_t *data, int start, int len)
+{
+ struct bt_hci_link_s *link = (struct bt_hci_link_s *) btlink;
+
+ bt_hci_lmp_acl_data(hci_from_device(btlink->slave),
+ link->handle, data, start, len);
+}
+
+static void bt_hci_lmp_acl_data_host(struct bt_link_s *link,
+ const uint8_t *data, int start, int len)
+{
+ bt_hci_lmp_acl_data(hci_from_device(link->host),
+ link->handle, data, start, len);
+}
+
+static void bt_submit_acl(struct HCIInfo *info,
+ const uint8_t *data, int length)
+{
+ struct bt_hci_s *hci = hci_from_info(info);
+ uint16_t handle;
+ int datalen, flags;
+ struct bt_link_s *link;
+
+ if (length < HCI_ACL_HDR_SIZE) {
+ fprintf(stderr, "%s: ACL packet too short (%iB)\n",
+ __FUNCTION__, length);
+ return;
+ }
+
+ handle = acl_handle((data[1] << 8) | data[0]);
+ flags = acl_flags((data[1] << 8) | data[0]);
+ datalen = (data[3] << 8) | data[2];
+ data += HCI_ACL_HDR_SIZE;
+ length -= HCI_ACL_HDR_SIZE;
+
+ if (bt_hci_handle_bad(hci, handle)) {
+ fprintf(stderr, "%s: invalid ACL handle %03x\n",
+ __FUNCTION__, handle);
+ /* TODO: signal an error */
+ return;
+ }
+ handle &= ~HCI_HANDLE_OFFSET;
+
+ if (datalen > length) {
+ fprintf(stderr, "%s: ACL packet too short (%iB < %iB)\n",
+ __FUNCTION__, length, datalen);
+ return;
+ }
+
+ link = hci->lm.handle[handle].link;
+
+ if ((flags & ~3) == ACL_ACTIVE_BCAST) {
+ if (!hci->asb_handle)
+ hci->asb_handle = handle;
+ else if (handle != hci->asb_handle) {
+ fprintf(stderr, "%s: Bad handle %03x in Active Slave Broadcast\n",
+ __FUNCTION__, handle);
+ /* TODO: signal an error */
+ return;
+ }
+
+ /* TODO */
+ }
+
+ if ((flags & ~3) == ACL_PICO_BCAST) {
+ if (!hci->psb_handle)
+ hci->psb_handle = handle;
+ else if (handle != hci->psb_handle) {
+ fprintf(stderr, "%s: Bad handle %03x in Parked Slave Broadcast\n",
+ __FUNCTION__, handle);
+ /* TODO: signal an error */
+ return;
+ }
+
+ /* TODO */
+ }
+
+ /* TODO: increase counter and send EVT_NUM_COMP_PKTS */
+ bt_hci_event_num_comp_pkts(hci, handle | HCI_HANDLE_OFFSET, 1);
+
+ /* Do this last as it can trigger further events even in this HCI */
+ hci->lm.handle[handle].lmp_acl_data(link, data,
+ (flags & 3) == ACL_START, length);
+}
+
+static void bt_submit_sco(struct HCIInfo *info,
+ const uint8_t *data, int length)
+{
+ struct bt_hci_s *hci = hci_from_info(info);
+ uint16_t handle;
+ int datalen;
+
+ if (length < 3)
+ return;
+
+ handle = acl_handle((data[1] << 8) | data[0]);
+ datalen = data[2];
+ length -= 3;
+
+ if (bt_hci_handle_bad(hci, handle)) {
+ fprintf(stderr, "%s: invalid SCO handle %03x\n",
+ __FUNCTION__, handle);
+ return;
+ }
+
+ if (datalen > length) {
+ fprintf(stderr, "%s: SCO packet too short (%iB < %iB)\n",
+ __FUNCTION__, length, datalen);
+ return;
+ }
+
+ /* TODO */
+
+ /* TODO: increase counter and send EVT_NUM_COMP_PKTS if synchronous
+ * Flow Control is enabled.
+ * (See Read/Write_Synchronous_Flow_Control_Enable on page 513 and
+ * page 514.) */
+}
+
+static uint8_t *bt_hci_evt_packet(void *opaque)
+{
+ /* TODO: allocate a packet from upper layer */
+ struct bt_hci_s *s = opaque;
+
+ return s->evt_buf;
+}
+
+static void bt_hci_evt_submit(void *opaque, int len)
+{
+ /* TODO: notify upper layer */
+ struct bt_hci_s *s = opaque;
+
+ s->info.evt_recv(s->info.opaque, s->evt_buf, len);
+}
+
+static int bt_hci_bdaddr_set(struct HCIInfo *info, const uint8_t *bd_addr)
+{
+ struct bt_hci_s *hci = hci_from_info(info);
+
+ bacpy(&hci->device.bd_addr, (const bdaddr_t *) bd_addr);
+ return 0;
+}
+
+static void bt_hci_done(struct HCIInfo *info);
+static void bt_hci_destroy(struct bt_device_s *dev)
+{
+ struct bt_hci_s *hci = hci_from_device(dev);
+
+ bt_hci_done(&hci->info);
+}
+
+struct HCIInfo *bt_new_hci(struct bt_scatternet_s *net)
+{
+ struct bt_hci_s *s = g_malloc0(sizeof(struct bt_hci_s));
+
+ s->lm.inquiry_done = timer_new_ns(QEMU_CLOCK_VIRTUAL, bt_hci_inquiry_done, s);
+ s->lm.inquiry_next = timer_new_ns(QEMU_CLOCK_VIRTUAL, bt_hci_inquiry_next, s);
+ s->conn_accept_timer =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, bt_hci_conn_accept_timeout, s);
+
+ s->evt_packet = bt_hci_evt_packet;
+ s->evt_submit = bt_hci_evt_submit;
+ s->opaque = s;
+
+ bt_device_init(&s->device, net);
+ s->device.lmp_connection_request = bt_hci_lmp_connection_request;
+ s->device.lmp_connection_complete = bt_hci_lmp_connection_complete;
+ s->device.lmp_disconnect_master = bt_hci_lmp_disconnect_host;
+ s->device.lmp_disconnect_slave = bt_hci_lmp_disconnect_slave;
+ s->device.lmp_acl_data = bt_hci_lmp_acl_data_slave;
+ s->device.lmp_acl_resp = bt_hci_lmp_acl_data_host;
+ s->device.lmp_mode_change = bt_hci_lmp_mode_change_slave;
+
+ /* Keep updated! */
+ /* Also keep in sync with supported commands bitmask in
+ * bt_hci_read_local_commands_rp */
+ s->device.lmp_caps = 0x8000199b7e85355fll;
+
+ bt_hci_reset(s);
+
+ s->info.cmd_send = bt_submit_hci;
+ s->info.sco_send = bt_submit_sco;
+ s->info.acl_send = bt_submit_acl;
+ s->info.bdaddr_set = bt_hci_bdaddr_set;
+
+ s->device.handle_destroy = bt_hci_destroy;
+
+ return &s->info;
+}
+
+struct HCIInfo *hci_init(const char *str)
+{
+ char *endp;
+ struct bt_scatternet_s *vlan = 0;
+
+ if (!strcmp(str, "null"))
+ /* null */
+ return &null_hci;
+ else if (!strncmp(str, "host", 4) && (str[4] == '\0' || str[4] == ':'))
+ /* host[:hciN] */
+ return bt_host_hci(str[4] ? str + 5 : "hci0");
+ else if (!strncmp(str, "hci", 3)) {
+ /* hci[,vlan=n] */
+ if (str[3]) {
+ if (!strncmp(str + 3, ",vlan=", 6)) {
+ vlan = qemu_find_bt_vlan(strtol(str + 9, &endp, 0));
+ if (*endp)
+ vlan = 0;
+ }
+ } else
+ vlan = qemu_find_bt_vlan(0);
+ if (vlan)
+ return bt_new_hci(vlan);
+ }
+
+ fprintf(stderr, "qemu: Unknown bluetooth HCI `%s'.\n", str);
+
+ return 0;
+}
+
+static void bt_hci_done(struct HCIInfo *info)
+{
+ struct bt_hci_s *hci = hci_from_info(info);
+ int handle;
+
+ bt_device_done(&hci->device);
+
+ if (hci->device.lmp_name)
+ g_free((void *) hci->device.lmp_name);
+
+ /* Be gentle and send DISCONNECT to all connected peers and those
+ * currently waiting for us to accept or reject a connection request.
+ * This frees the links. */
+ if (hci->conn_req_host) {
+ bt_hci_connection_reject(hci,
+ hci->conn_req_host, HCI_OE_POWER_OFF);
+ return;
+ }
+
+ for (handle = HCI_HANDLE_OFFSET;
+ handle < (HCI_HANDLE_OFFSET | HCI_HANDLES_MAX); handle ++)
+ if (!bt_hci_handle_bad(hci, handle))
+ bt_hci_disconnect(hci, handle, HCI_OE_POWER_OFF);
+
+ /* TODO: this is not enough actually, there may be slaves from whom
+ * we have requested a connection who will soon (or not) respond with
+ * an accept or a reject, so we should also check if hci->lm.connecting
+ * is non-zero and if so, avoid freeing the hci but otherwise disappear
+ * from all qemu social life (e.g. stop scanning and request to be
+ * removed from s->device.net) and arrange for
+ * s->device.lmp_connection_complete to free the remaining bits once
+ * hci->lm.awaiting_bdaddr[] is empty. */
+
+ timer_free(hci->lm.inquiry_done);
+ timer_free(hci->lm.inquiry_next);
+ timer_free(hci->conn_accept_timer);
+
+ g_free(hci);
+}
diff --git a/hw/bt/hid.c b/hw/bt/hid.c
new file mode 100644
index 00000000..af494e1e
--- /dev/null
+++ b/hw/bt/hid.c
@@ -0,0 +1,553 @@
+/*
+ * QEMU Bluetooth HID Profile wrapper for USB HID.
+ *
+ * Copyright (C) 2007-2008 OpenMoko, Inc.
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "ui/console.h"
+#include "hw/input/hid.h"
+#include "hw/bt.h"
+
+enum hid_transaction_req {
+ BT_HANDSHAKE = 0x0,
+ BT_HID_CONTROL = 0x1,
+ BT_GET_REPORT = 0x4,
+ BT_SET_REPORT = 0x5,
+ BT_GET_PROTOCOL = 0x6,
+ BT_SET_PROTOCOL = 0x7,
+ BT_GET_IDLE = 0x8,
+ BT_SET_IDLE = 0x9,
+ BT_DATA = 0xa,
+ BT_DATC = 0xb,
+};
+
+enum hid_transaction_handshake {
+ BT_HS_SUCCESSFUL = 0x0,
+ BT_HS_NOT_READY = 0x1,
+ BT_HS_ERR_INVALID_REPORT_ID = 0x2,
+ BT_HS_ERR_UNSUPPORTED_REQUEST = 0x3,
+ BT_HS_ERR_INVALID_PARAMETER = 0x4,
+ BT_HS_ERR_UNKNOWN = 0xe,
+ BT_HS_ERR_FATAL = 0xf,
+};
+
+enum hid_transaction_control {
+ BT_HC_NOP = 0x0,
+ BT_HC_HARD_RESET = 0x1,
+ BT_HC_SOFT_RESET = 0x2,
+ BT_HC_SUSPEND = 0x3,
+ BT_HC_EXIT_SUSPEND = 0x4,
+ BT_HC_VIRTUAL_CABLE_UNPLUG = 0x5,
+};
+
+enum hid_protocol {
+ BT_HID_PROTO_BOOT = 0,
+ BT_HID_PROTO_REPORT = 1,
+};
+
+enum hid_boot_reportid {
+ BT_HID_BOOT_INVALID = 0,
+ BT_HID_BOOT_KEYBOARD,
+ BT_HID_BOOT_MOUSE,
+};
+
+enum hid_data_pkt {
+ BT_DATA_OTHER = 0,
+ BT_DATA_INPUT,
+ BT_DATA_OUTPUT,
+ BT_DATA_FEATURE,
+};
+
+#define BT_HID_MTU 48
+
+/* HID interface requests */
+#define GET_REPORT 0xa101
+#define GET_IDLE 0xa102
+#define GET_PROTOCOL 0xa103
+#define SET_REPORT 0x2109
+#define SET_IDLE 0x210a
+#define SET_PROTOCOL 0x210b
+
+struct bt_hid_device_s {
+ struct bt_l2cap_device_s btdev;
+ struct bt_l2cap_conn_params_s *control;
+ struct bt_l2cap_conn_params_s *interrupt;
+ HIDState hid;
+
+ int proto;
+ int connected;
+ int data_type;
+ int intr_state;
+ struct {
+ int len;
+ uint8_t buffer[1024];
+ } dataother, datain, dataout, feature, intrdataout;
+ enum {
+ bt_state_ready,
+ bt_state_transaction,
+ bt_state_suspend,
+ } state;
+};
+
+static void bt_hid_reset(struct bt_hid_device_s *s)
+{
+ struct bt_scatternet_s *net = s->btdev.device.net;
+
+ /* Go as far as... */
+ bt_l2cap_device_done(&s->btdev);
+ bt_l2cap_device_init(&s->btdev, net);
+
+ hid_reset(&s->hid);
+ s->proto = BT_HID_PROTO_REPORT;
+ s->state = bt_state_ready;
+ s->dataother.len = 0;
+ s->datain.len = 0;
+ s->dataout.len = 0;
+ s->feature.len = 0;
+ s->intrdataout.len = 0;
+ s->intr_state = 0;
+}
+
+static int bt_hid_out(struct bt_hid_device_s *s)
+{
+ if (s->data_type == BT_DATA_OUTPUT) {
+ /* nothing */
+ ;
+ }
+
+ if (s->data_type == BT_DATA_FEATURE) {
+ /* XXX:
+ * does this send a USB_REQ_CLEAR_FEATURE/USB_REQ_SET_FEATURE
+ * or a SET_REPORT? */
+ ;
+ }
+
+ return -1;
+}
+
+static int bt_hid_in(struct bt_hid_device_s *s)
+{
+ s->datain.len = hid_keyboard_poll(&s->hid, s->datain.buffer,
+ sizeof(s->datain.buffer));
+ return s->datain.len;
+}
+
+static void bt_hid_send_handshake(struct bt_hid_device_s *s, int result)
+{
+ *s->control->sdu_out(s->control, 1) =
+ (BT_HANDSHAKE << 4) | result;
+ s->control->sdu_submit(s->control);
+}
+
+static void bt_hid_send_control(struct bt_hid_device_s *s, int operation)
+{
+ *s->control->sdu_out(s->control, 1) =
+ (BT_HID_CONTROL << 4) | operation;
+ s->control->sdu_submit(s->control);
+}
+
+static void bt_hid_disconnect(struct bt_hid_device_s *s)
+{
+ /* Disconnect s->control and s->interrupt */
+}
+
+static void bt_hid_send_data(struct bt_l2cap_conn_params_s *ch, int type,
+ const uint8_t *data, int len)
+{
+ uint8_t *pkt, hdr = (BT_DATA << 4) | type;
+ int plen;
+
+ do {
+ plen = MIN(len, ch->remote_mtu - 1);
+ pkt = ch->sdu_out(ch, plen + 1);
+
+ pkt[0] = hdr;
+ if (plen)
+ memcpy(pkt + 1, data, plen);
+ ch->sdu_submit(ch);
+
+ len -= plen;
+ data += plen;
+ hdr = (BT_DATC << 4) | type;
+ } while (plen == ch->remote_mtu - 1);
+}
+
+static void bt_hid_control_transaction(struct bt_hid_device_s *s,
+ const uint8_t *data, int len)
+{
+ uint8_t type, parameter;
+ int rlen, ret = -1;
+ if (len < 1)
+ return;
+
+ type = data[0] >> 4;
+ parameter = data[0] & 0xf;
+
+ switch (type) {
+ case BT_HANDSHAKE:
+ case BT_DATA:
+ switch (parameter) {
+ default:
+ /* These are not expected to be sent this direction. */
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ }
+ break;
+
+ case BT_HID_CONTROL:
+ if (len != 1 || (parameter != BT_HC_VIRTUAL_CABLE_UNPLUG &&
+ s->state == bt_state_transaction)) {
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ }
+ switch (parameter) {
+ case BT_HC_NOP:
+ break;
+ case BT_HC_HARD_RESET:
+ case BT_HC_SOFT_RESET:
+ bt_hid_reset(s);
+ break;
+ case BT_HC_SUSPEND:
+ if (s->state == bt_state_ready)
+ s->state = bt_state_suspend;
+ else
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ case BT_HC_EXIT_SUSPEND:
+ if (s->state == bt_state_suspend)
+ s->state = bt_state_ready;
+ else
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ case BT_HC_VIRTUAL_CABLE_UNPLUG:
+ bt_hid_disconnect(s);
+ break;
+ default:
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ }
+ break;
+
+ case BT_GET_REPORT:
+ /* No ReportIDs declared. */
+ if (((parameter & 8) && len != 3) ||
+ (!(parameter & 8) && len != 1) ||
+ s->state != bt_state_ready) {
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ }
+ if (parameter & 8)
+ rlen = data[2] | (data[3] << 8);
+ else
+ rlen = INT_MAX;
+ switch (parameter & 3) {
+ case BT_DATA_OTHER:
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ case BT_DATA_INPUT:
+ /* Here we can as well poll s->usbdev */
+ bt_hid_send_data(s->control, BT_DATA_INPUT,
+ s->datain.buffer, MIN(rlen, s->datain.len));
+ break;
+ case BT_DATA_OUTPUT:
+ bt_hid_send_data(s->control, BT_DATA_OUTPUT,
+ s->dataout.buffer, MIN(rlen, s->dataout.len));
+ break;
+ case BT_DATA_FEATURE:
+ bt_hid_send_data(s->control, BT_DATA_FEATURE,
+ s->feature.buffer, MIN(rlen, s->feature.len));
+ break;
+ }
+ break;
+
+ case BT_SET_REPORT:
+ if (len < 2 || len > BT_HID_MTU || s->state != bt_state_ready ||
+ (parameter & 3) == BT_DATA_OTHER ||
+ (parameter & 3) == BT_DATA_INPUT) {
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ }
+ s->data_type = parameter & 3;
+ if (s->data_type == BT_DATA_OUTPUT) {
+ s->dataout.len = len - 1;
+ memcpy(s->dataout.buffer, data + 1, s->dataout.len);
+ } else {
+ s->feature.len = len - 1;
+ memcpy(s->feature.buffer, data + 1, s->feature.len);
+ }
+ if (len == BT_HID_MTU)
+ s->state = bt_state_transaction;
+ else
+ bt_hid_out(s);
+ break;
+
+ case BT_GET_PROTOCOL:
+ if (len != 1 || s->state == bt_state_transaction) {
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ }
+ *s->control->sdu_out(s->control, 1) = s->proto;
+ s->control->sdu_submit(s->control);
+ break;
+
+ case BT_SET_PROTOCOL:
+ if (len != 1 || s->state == bt_state_transaction ||
+ (parameter != BT_HID_PROTO_BOOT &&
+ parameter != BT_HID_PROTO_REPORT)) {
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ }
+ s->proto = parameter;
+ s->hid.protocol = parameter;
+ ret = BT_HS_SUCCESSFUL;
+ break;
+
+ case BT_GET_IDLE:
+ if (len != 1 || s->state == bt_state_transaction) {
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ }
+ *s->control->sdu_out(s->control, 1) = s->hid.idle;
+ s->control->sdu_submit(s->control);
+ break;
+
+ case BT_SET_IDLE:
+ if (len != 2 || s->state == bt_state_transaction) {
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ }
+
+ s->hid.idle = data[1];
+ /* XXX: Does this generate a handshake? */
+ break;
+
+ case BT_DATC:
+ if (len > BT_HID_MTU || s->state != bt_state_transaction) {
+ ret = BT_HS_ERR_INVALID_PARAMETER;
+ break;
+ }
+ if (s->data_type == BT_DATA_OUTPUT) {
+ memcpy(s->dataout.buffer + s->dataout.len, data + 1, len - 1);
+ s->dataout.len += len - 1;
+ } else {
+ memcpy(s->feature.buffer + s->feature.len, data + 1, len - 1);
+ s->feature.len += len - 1;
+ }
+ if (len < BT_HID_MTU) {
+ bt_hid_out(s);
+ s->state = bt_state_ready;
+ }
+ break;
+
+ default:
+ ret = BT_HS_ERR_UNSUPPORTED_REQUEST;
+ }
+
+ if (ret != -1)
+ bt_hid_send_handshake(s, ret);
+}
+
+static void bt_hid_control_sdu(void *opaque, const uint8_t *data, int len)
+{
+ struct bt_hid_device_s *hid = opaque;
+
+ bt_hid_control_transaction(hid, data, len);
+}
+
+static void bt_hid_datain(HIDState *hs)
+{
+ struct bt_hid_device_s *hid =
+ container_of(hs, struct bt_hid_device_s, hid);
+
+ /* If suspended, wake-up and send a wake-up event first. We might
+ * want to also inspect the input report and ignore event like
+ * mouse movements until a button event occurs. */
+ if (hid->state == bt_state_suspend) {
+ hid->state = bt_state_ready;
+ }
+
+ if (bt_hid_in(hid) > 0)
+ /* TODO: when in boot-mode precede any Input reports with the ReportID
+ * byte, here and in GetReport/SetReport on the Control channel. */
+ bt_hid_send_data(hid->interrupt, BT_DATA_INPUT,
+ hid->datain.buffer, hid->datain.len);
+}
+
+static void bt_hid_interrupt_sdu(void *opaque, const uint8_t *data, int len)
+{
+ struct bt_hid_device_s *hid = opaque;
+
+ if (len > BT_HID_MTU || len < 1)
+ goto bad;
+ if ((data[0] & 3) != BT_DATA_OUTPUT)
+ goto bad;
+ if ((data[0] >> 4) == BT_DATA) {
+ if (hid->intr_state)
+ goto bad;
+
+ hid->data_type = BT_DATA_OUTPUT;
+ hid->intrdataout.len = 0;
+ } else if ((data[0] >> 4) == BT_DATC) {
+ if (!hid->intr_state)
+ goto bad;
+ } else
+ goto bad;
+
+ memcpy(hid->intrdataout.buffer + hid->intrdataout.len, data + 1, len - 1);
+ hid->intrdataout.len += len - 1;
+ hid->intr_state = (len == BT_HID_MTU);
+ if (!hid->intr_state) {
+ memcpy(hid->dataout.buffer, hid->intrdataout.buffer,
+ hid->dataout.len = hid->intrdataout.len);
+ bt_hid_out(hid);
+ }
+
+ return;
+bad:
+ fprintf(stderr, "%s: bad transaction on Interrupt channel.\n",
+ __FUNCTION__);
+}
+
+/* "Virtual cable" plug/unplug event. */
+static void bt_hid_connected_update(struct bt_hid_device_s *hid)
+{
+ int prev = hid->connected;
+
+ hid->connected = hid->control && hid->interrupt;
+
+ /* Stop page-/inquiry-scanning when a host is connected. */
+ hid->btdev.device.page_scan = !hid->connected;
+ hid->btdev.device.inquiry_scan = !hid->connected;
+
+ if (hid->connected && !prev) {
+ hid_reset(&hid->hid);
+ hid->proto = BT_HID_PROTO_REPORT;
+ }
+
+ /* Should set HIDVirtualCable in SDP (possibly need to check that SDP
+ * isn't destroyed yet, in case we're being called from handle_destroy) */
+}
+
+static void bt_hid_close_control(void *opaque)
+{
+ struct bt_hid_device_s *hid = opaque;
+
+ hid->control = NULL;
+ bt_hid_connected_update(hid);
+}
+
+static void bt_hid_close_interrupt(void *opaque)
+{
+ struct bt_hid_device_s *hid = opaque;
+
+ hid->interrupt = NULL;
+ bt_hid_connected_update(hid);
+}
+
+static int bt_hid_new_control_ch(struct bt_l2cap_device_s *dev,
+ struct bt_l2cap_conn_params_s *params)
+{
+ struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
+
+ if (hid->control)
+ return 1;
+
+ hid->control = params;
+ hid->control->opaque = hid;
+ hid->control->close = bt_hid_close_control;
+ hid->control->sdu_in = bt_hid_control_sdu;
+
+ bt_hid_connected_update(hid);
+
+ return 0;
+}
+
+static int bt_hid_new_interrupt_ch(struct bt_l2cap_device_s *dev,
+ struct bt_l2cap_conn_params_s *params)
+{
+ struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
+
+ if (hid->interrupt)
+ return 1;
+
+ hid->interrupt = params;
+ hid->interrupt->opaque = hid;
+ hid->interrupt->close = bt_hid_close_interrupt;
+ hid->interrupt->sdu_in = bt_hid_interrupt_sdu;
+
+ bt_hid_connected_update(hid);
+
+ return 0;
+}
+
+static void bt_hid_destroy(struct bt_device_s *dev)
+{
+ struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
+
+ if (hid->connected)
+ bt_hid_send_control(hid, BT_HC_VIRTUAL_CABLE_UNPLUG);
+ bt_l2cap_device_done(&hid->btdev);
+
+ hid_free(&hid->hid);
+
+ g_free(hid);
+}
+
+enum peripheral_minor_class {
+ class_other = 0 << 4,
+ class_keyboard = 1 << 4,
+ class_pointing = 2 << 4,
+ class_combo = 3 << 4,
+};
+
+static struct bt_device_s *bt_hid_init(struct bt_scatternet_s *net,
+ enum peripheral_minor_class minor)
+{
+ struct bt_hid_device_s *s = g_malloc0(sizeof(*s));
+ uint32_t class =
+ /* Format type */
+ (0 << 0) |
+ /* Device class */
+ (minor << 2) |
+ (5 << 8) | /* "Peripheral" */
+ /* Service classes */
+ (1 << 13) | /* Limited discoverable mode */
+ (1 << 19); /* Capturing device (?) */
+
+ bt_l2cap_device_init(&s->btdev, net);
+ bt_l2cap_sdp_init(&s->btdev);
+ bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_CTRL,
+ BT_HID_MTU, bt_hid_new_control_ch);
+ bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_INTR,
+ BT_HID_MTU, bt_hid_new_interrupt_ch);
+
+ hid_init(&s->hid, HID_KEYBOARD, bt_hid_datain);
+ s->btdev.device.lmp_name = "BT Keyboard";
+
+ s->btdev.device.handle_destroy = bt_hid_destroy;
+
+ s->btdev.device.class[0] = (class >> 0) & 0xff;
+ s->btdev.device.class[1] = (class >> 8) & 0xff;
+ s->btdev.device.class[2] = (class >> 16) & 0xff;
+
+ return &s->btdev.device;
+}
+
+struct bt_device_s *bt_keyboard_init(struct bt_scatternet_s *net)
+{
+ return bt_hid_init(net, class_keyboard);
+}
diff --git a/hw/bt/l2cap.c b/hw/bt/l2cap.c
new file mode 100644
index 00000000..591e0477
--- /dev/null
+++ b/hw/bt/l2cap.c
@@ -0,0 +1,1365 @@
+/*
+ * QEMU Bluetooth L2CAP logic.
+ *
+ * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "hw/bt.h"
+
+#define L2CAP_CID_MAX 0x100 /* Between 0x40 and 0x10000 */
+
+struct l2cap_instance_s {
+ struct bt_link_s *link;
+ struct bt_l2cap_device_s *dev;
+ int role;
+
+ uint8_t frame_in[65535 + L2CAP_HDR_SIZE] __attribute__ ((aligned (4)));
+ int frame_in_len;
+
+ uint8_t frame_out[65535 + L2CAP_HDR_SIZE] __attribute__ ((aligned (4)));
+ int frame_out_len;
+
+ /* Signalling channel timers. They exist per-request but we can make
+ * sure we have no more than one outstanding request at any time. */
+ QEMUTimer *rtx;
+ QEMUTimer *ertx;
+
+ int last_id;
+ int next_id;
+
+ struct l2cap_chan_s {
+ struct bt_l2cap_conn_params_s params;
+
+ void (*frame_in)(struct l2cap_chan_s *chan, uint16_t cid,
+ const l2cap_hdr *hdr, int len);
+ int mps;
+ int min_mtu;
+
+ struct l2cap_instance_s *l2cap;
+
+ /* Only allocated channels */
+ uint16_t remote_cid;
+#define L2CAP_CFG_INIT 2
+#define L2CAP_CFG_ACC 1
+ int config_req_id; /* TODO: handle outgoing requests generically */
+ int config;
+
+ /* Only connection-oriented channels. Note: if we allow the tx and
+ * rx traffic to be in different modes at any time, we need two. */
+ int mode;
+
+ /* Only flow-controlled, connection-oriented channels */
+ uint8_t sdu[65536]; /* TODO: dynamically allocate */
+ int len_cur, len_total;
+ int rexmit;
+ int monitor_timeout;
+ QEMUTimer *monitor_timer;
+ QEMUTimer *retransmission_timer;
+ } *cid[L2CAP_CID_MAX];
+ /* The channel state machine states map as following:
+ * CLOSED -> !cid[N]
+ * WAIT_CONNECT -> never occurs
+ * WAIT_CONNECT_RSP -> never occurs
+ * CONFIG -> cid[N] && config < 3
+ * WAIT_CONFIG -> never occurs, cid[N] && config == 0 && !config_r
+ * WAIT_SEND_CONFIG -> never occurs, cid[N] && config == 1 && !config_r
+ * WAIT_CONFIG_REQ_RSP -> cid[N] && config == 0 && config_req_id
+ * WAIT_CONFIG_RSP -> cid[N] && config == 1 && config_req_id
+ * WAIT_CONFIG_REQ -> cid[N] && config == 2
+ * OPEN -> cid[N] && config == 3
+ * WAIT_DISCONNECT -> never occurs
+ */
+
+ struct l2cap_chan_s signalling_ch;
+ struct l2cap_chan_s group_ch;
+};
+
+struct slave_l2cap_instance_s {
+ struct bt_link_s link; /* Underlying logical link (ACL) */
+ struct l2cap_instance_s l2cap;
+};
+
+struct bt_l2cap_psm_s {
+ int psm;
+ int min_mtu;
+ int (*new_channel)(struct bt_l2cap_device_s *device,
+ struct bt_l2cap_conn_params_s *params);
+ struct bt_l2cap_psm_s *next;
+};
+
+static const uint16_t l2cap_fcs16_table[256] = {
+ 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
+ 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
+ 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
+ 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
+ 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
+ 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
+ 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
+ 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
+ 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
+ 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
+ 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
+ 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
+ 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
+ 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
+ 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
+ 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
+ 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
+ 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
+ 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
+ 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
+ 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
+ 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
+ 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
+ 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
+ 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
+ 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
+ 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
+ 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
+ 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
+ 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
+ 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
+ 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040,
+};
+
+static uint16_t l2cap_fcs16(const uint8_t *message, int len)
+{
+ uint16_t fcs = 0x0000;
+
+ while (len --)
+#if 0
+ {
+ int i;
+
+ fcs ^= *message ++;
+ for (i = 8; i; -- i)
+ if (fcs & 1)
+ fcs = (fcs >> 1) ^ 0xa001;
+ else
+ fcs = (fcs >> 1);
+ }
+#else
+ fcs = (fcs >> 8) ^ l2cap_fcs16_table[(fcs ^ *message ++) & 0xff];
+#endif
+
+ return fcs;
+}
+
+/* L2CAP layer logic (protocol) */
+
+static void l2cap_retransmission_timer_update(struct l2cap_chan_s *ch)
+{
+#if 0
+ if (ch->mode != L2CAP_MODE_BASIC && ch->rexmit)
+ timer_mod(ch->retransmission_timer);
+ else
+ timer_del(ch->retransmission_timer);
+#endif
+}
+
+static void l2cap_monitor_timer_update(struct l2cap_chan_s *ch)
+{
+#if 0
+ if (ch->mode != L2CAP_MODE_BASIC && !ch->rexmit)
+ timer_mod(ch->monitor_timer);
+ else
+ timer_del(ch->monitor_timer);
+#endif
+}
+
+static void l2cap_command_reject(struct l2cap_instance_s *l2cap, int id,
+ uint16_t reason, const void *data, int plen)
+{
+ uint8_t *pkt;
+ l2cap_cmd_hdr *hdr;
+ l2cap_cmd_rej *params;
+ uint16_t len;
+
+ reason = cpu_to_le16(reason);
+ len = cpu_to_le16(L2CAP_CMD_REJ_SIZE + plen);
+
+ pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params,
+ L2CAP_CMD_HDR_SIZE + L2CAP_CMD_REJ_SIZE + plen);
+ hdr = (void *) (pkt + 0);
+ params = (void *) (pkt + L2CAP_CMD_HDR_SIZE);
+
+ hdr->code = L2CAP_COMMAND_REJ;
+ hdr->ident = id;
+ memcpy(&hdr->len, &len, sizeof(hdr->len));
+ memcpy(&params->reason, &reason, sizeof(reason));
+ if (plen)
+ memcpy(pkt + L2CAP_CMD_HDR_SIZE + L2CAP_CMD_REJ_SIZE, data, plen);
+
+ l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params);
+}
+
+static void l2cap_command_reject_cid(struct l2cap_instance_s *l2cap, int id,
+ uint16_t reason, uint16_t dcid, uint16_t scid)
+{
+ l2cap_cmd_rej_cid params = {
+ .dcid = dcid,
+ .scid = scid,
+ };
+
+ l2cap_command_reject(l2cap, id, reason, &params, L2CAP_CMD_REJ_CID_SIZE);
+}
+
+static void l2cap_connection_response(struct l2cap_instance_s *l2cap,
+ int dcid, int scid, int result, int status)
+{
+ uint8_t *pkt;
+ l2cap_cmd_hdr *hdr;
+ l2cap_conn_rsp *params;
+
+ pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params,
+ L2CAP_CMD_HDR_SIZE + L2CAP_CONN_RSP_SIZE);
+ hdr = (void *) (pkt + 0);
+ params = (void *) (pkt + L2CAP_CMD_HDR_SIZE);
+
+ hdr->code = L2CAP_CONN_RSP;
+ hdr->ident = l2cap->last_id;
+ hdr->len = cpu_to_le16(L2CAP_CONN_RSP_SIZE);
+
+ params->dcid = cpu_to_le16(dcid);
+ params->scid = cpu_to_le16(scid);
+ params->result = cpu_to_le16(result);
+ params->status = cpu_to_le16(status);
+
+ l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params);
+}
+
+static void l2cap_configuration_request(struct l2cap_instance_s *l2cap,
+ int dcid, int flag, const uint8_t *data, int len)
+{
+ uint8_t *pkt;
+ l2cap_cmd_hdr *hdr;
+ l2cap_conf_req *params;
+
+ pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params,
+ L2CAP_CMD_HDR_SIZE + L2CAP_CONF_REQ_SIZE(len));
+ hdr = (void *) (pkt + 0);
+ params = (void *) (pkt + L2CAP_CMD_HDR_SIZE);
+
+ /* TODO: unify the id sequencing */
+ l2cap->last_id = l2cap->next_id;
+ l2cap->next_id = l2cap->next_id == 255 ? 1 : l2cap->next_id + 1;
+
+ hdr->code = L2CAP_CONF_REQ;
+ hdr->ident = l2cap->last_id;
+ hdr->len = cpu_to_le16(L2CAP_CONF_REQ_SIZE(len));
+
+ params->dcid = cpu_to_le16(dcid);
+ params->flags = cpu_to_le16(flag);
+ if (len)
+ memcpy(params->data, data, len);
+
+ l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params);
+}
+
+static void l2cap_configuration_response(struct l2cap_instance_s *l2cap,
+ int scid, int flag, int result, const uint8_t *data, int len)
+{
+ uint8_t *pkt;
+ l2cap_cmd_hdr *hdr;
+ l2cap_conf_rsp *params;
+
+ pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params,
+ L2CAP_CMD_HDR_SIZE + L2CAP_CONF_RSP_SIZE(len));
+ hdr = (void *) (pkt + 0);
+ params = (void *) (pkt + L2CAP_CMD_HDR_SIZE);
+
+ hdr->code = L2CAP_CONF_RSP;
+ hdr->ident = l2cap->last_id;
+ hdr->len = cpu_to_le16(L2CAP_CONF_RSP_SIZE(len));
+
+ params->scid = cpu_to_le16(scid);
+ params->flags = cpu_to_le16(flag);
+ params->result = cpu_to_le16(result);
+ if (len)
+ memcpy(params->data, data, len);
+
+ l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params);
+}
+
+static void l2cap_disconnection_response(struct l2cap_instance_s *l2cap,
+ int dcid, int scid)
+{
+ uint8_t *pkt;
+ l2cap_cmd_hdr *hdr;
+ l2cap_disconn_rsp *params;
+
+ pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params,
+ L2CAP_CMD_HDR_SIZE + L2CAP_DISCONN_RSP_SIZE);
+ hdr = (void *) (pkt + 0);
+ params = (void *) (pkt + L2CAP_CMD_HDR_SIZE);
+
+ hdr->code = L2CAP_DISCONN_RSP;
+ hdr->ident = l2cap->last_id;
+ hdr->len = cpu_to_le16(L2CAP_DISCONN_RSP_SIZE);
+
+ params->dcid = cpu_to_le16(dcid);
+ params->scid = cpu_to_le16(scid);
+
+ l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params);
+}
+
+static void l2cap_echo_response(struct l2cap_instance_s *l2cap,
+ const uint8_t *data, int len)
+{
+ uint8_t *pkt;
+ l2cap_cmd_hdr *hdr;
+ uint8_t *params;
+
+ pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params,
+ L2CAP_CMD_HDR_SIZE + len);
+ hdr = (void *) (pkt + 0);
+ params = (void *) (pkt + L2CAP_CMD_HDR_SIZE);
+
+ hdr->code = L2CAP_ECHO_RSP;
+ hdr->ident = l2cap->last_id;
+ hdr->len = cpu_to_le16(len);
+
+ memcpy(params, data, len);
+
+ l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params);
+}
+
+static void l2cap_info_response(struct l2cap_instance_s *l2cap, int type,
+ int result, const uint8_t *data, int len)
+{
+ uint8_t *pkt;
+ l2cap_cmd_hdr *hdr;
+ l2cap_info_rsp *params;
+
+ pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params,
+ L2CAP_CMD_HDR_SIZE + L2CAP_INFO_RSP_SIZE + len);
+ hdr = (void *) (pkt + 0);
+ params = (void *) (pkt + L2CAP_CMD_HDR_SIZE);
+
+ hdr->code = L2CAP_INFO_RSP;
+ hdr->ident = l2cap->last_id;
+ hdr->len = cpu_to_le16(L2CAP_INFO_RSP_SIZE + len);
+
+ params->type = cpu_to_le16(type);
+ params->result = cpu_to_le16(result);
+ if (len)
+ memcpy(params->data, data, len);
+
+ l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params);
+}
+
+static uint8_t *l2cap_bframe_out(struct bt_l2cap_conn_params_s *parm, int len);
+static void l2cap_bframe_submit(struct bt_l2cap_conn_params_s *parms);
+#if 0
+static uint8_t *l2cap_iframe_out(struct bt_l2cap_conn_params_s *parm, int len);
+static void l2cap_iframe_submit(struct bt_l2cap_conn_params_s *parm);
+#endif
+static void l2cap_bframe_in(struct l2cap_chan_s *ch, uint16_t cid,
+ const l2cap_hdr *hdr, int len);
+static void l2cap_iframe_in(struct l2cap_chan_s *ch, uint16_t cid,
+ const l2cap_hdr *hdr, int len);
+
+static int l2cap_cid_new(struct l2cap_instance_s *l2cap)
+{
+ int i;
+
+ for (i = L2CAP_CID_ALLOC; i < L2CAP_CID_MAX; i ++)
+ if (!l2cap->cid[i])
+ return i;
+
+ return L2CAP_CID_INVALID;
+}
+
+static inline struct bt_l2cap_psm_s *l2cap_psm(
+ struct bt_l2cap_device_s *device, int psm)
+{
+ struct bt_l2cap_psm_s *ret = device->first_psm;
+
+ while (ret && ret->psm != psm)
+ ret = ret->next;
+
+ return ret;
+}
+
+static struct l2cap_chan_s *l2cap_channel_open(struct l2cap_instance_s *l2cap,
+ int psm, int source_cid)
+{
+ struct l2cap_chan_s *ch = NULL;
+ struct bt_l2cap_psm_s *psm_info;
+ int result, status;
+ int cid = l2cap_cid_new(l2cap);
+
+ if (cid) {
+ /* See what the channel is to be used for.. */
+ psm_info = l2cap_psm(l2cap->dev, psm);
+
+ if (psm_info) {
+ /* Device supports this use-case. */
+ ch = g_malloc0(sizeof(*ch));
+ ch->params.sdu_out = l2cap_bframe_out;
+ ch->params.sdu_submit = l2cap_bframe_submit;
+ ch->frame_in = l2cap_bframe_in;
+ ch->mps = 65536;
+ ch->min_mtu = MAX(48, psm_info->min_mtu);
+ ch->params.remote_mtu = MAX(672, ch->min_mtu);
+ ch->remote_cid = source_cid;
+ ch->mode = L2CAP_MODE_BASIC;
+ ch->l2cap = l2cap;
+
+ /* Does it feel like opening yet another channel though? */
+ if (!psm_info->new_channel(l2cap->dev, &ch->params)) {
+ l2cap->cid[cid] = ch;
+
+ result = L2CAP_CR_SUCCESS;
+ status = L2CAP_CS_NO_INFO;
+ } else {
+ g_free(ch);
+ ch = NULL;
+ result = L2CAP_CR_NO_MEM;
+ status = L2CAP_CS_NO_INFO;
+ }
+ } else {
+ result = L2CAP_CR_BAD_PSM;
+ status = L2CAP_CS_NO_INFO;
+ }
+ } else {
+ result = L2CAP_CR_NO_MEM;
+ status = L2CAP_CS_NO_INFO;
+ }
+
+ l2cap_connection_response(l2cap, cid, source_cid, result, status);
+
+ return ch;
+}
+
+static void l2cap_channel_close(struct l2cap_instance_s *l2cap,
+ int cid, int source_cid)
+{
+ struct l2cap_chan_s *ch = NULL;
+
+ /* According to Volume 3, section 6.1.1, pg 1048 of BT Core V2.0, a
+ * connection in CLOSED state still responds with a L2CAP_DisconnectRsp
+ * message on an L2CAP_DisconnectReq event. */
+ if (unlikely(cid < L2CAP_CID_ALLOC)) {
+ l2cap_command_reject_cid(l2cap, l2cap->last_id, L2CAP_REJ_CID_INVAL,
+ cid, source_cid);
+ return;
+ }
+ if (likely(cid >= L2CAP_CID_ALLOC && cid < L2CAP_CID_MAX))
+ ch = l2cap->cid[cid];
+
+ if (likely(ch)) {
+ if (ch->remote_cid != source_cid) {
+ fprintf(stderr, "%s: Ignoring a Disconnection Request with the "
+ "invalid SCID %04x.\n", __FUNCTION__, source_cid);
+ return;
+ }
+
+ l2cap->cid[cid] = NULL;
+
+ ch->params.close(ch->params.opaque);
+ g_free(ch);
+ }
+
+ l2cap_disconnection_response(l2cap, cid, source_cid);
+}
+
+static void l2cap_channel_config_null(struct l2cap_instance_s *l2cap,
+ struct l2cap_chan_s *ch)
+{
+ l2cap_configuration_request(l2cap, ch->remote_cid, 0, NULL, 0);
+ ch->config_req_id = l2cap->last_id;
+ ch->config &= ~L2CAP_CFG_INIT;
+}
+
+static void l2cap_channel_config_req_event(struct l2cap_instance_s *l2cap,
+ struct l2cap_chan_s *ch)
+{
+ /* Use all default channel options and terminate negotiation. */
+ l2cap_channel_config_null(l2cap, ch);
+}
+
+static int l2cap_channel_config(struct l2cap_instance_s *l2cap,
+ struct l2cap_chan_s *ch, int flag,
+ const uint8_t *data, int len)
+{
+ l2cap_conf_opt *opt;
+ l2cap_conf_opt_qos *qos;
+ uint32_t val;
+ uint8_t rsp[len];
+ int result = L2CAP_CONF_SUCCESS;
+
+ data = memcpy(rsp, data, len);
+ while (len) {
+ opt = (void *) data;
+
+ if (len < L2CAP_CONF_OPT_SIZE ||
+ len < L2CAP_CONF_OPT_SIZE + opt->len) {
+ result = L2CAP_CONF_REJECT;
+ break;
+ }
+ data += L2CAP_CONF_OPT_SIZE + opt->len;
+ len -= L2CAP_CONF_OPT_SIZE + opt->len;
+
+ switch (opt->type & 0x7f) {
+ case L2CAP_CONF_MTU:
+ if (opt->len != 2) {
+ result = L2CAP_CONF_REJECT;
+ break;
+ }
+
+ /* MTU */
+ val = le16_to_cpup((void *) opt->val);
+ if (val < ch->min_mtu) {
+ cpu_to_le16w((void *) opt->val, ch->min_mtu);
+ result = L2CAP_CONF_UNACCEPT;
+ break;
+ }
+
+ ch->params.remote_mtu = val;
+ break;
+
+ case L2CAP_CONF_FLUSH_TO:
+ if (opt->len != 2) {
+ result = L2CAP_CONF_REJECT;
+ break;
+ }
+
+ /* Flush Timeout */
+ val = le16_to_cpup((void *) opt->val);
+ if (val < 0x0001) {
+ opt->val[0] = 0xff;
+ opt->val[1] = 0xff;
+ result = L2CAP_CONF_UNACCEPT;
+ break;
+ }
+ break;
+
+ case L2CAP_CONF_QOS:
+ if (opt->len != L2CAP_CONF_OPT_QOS_SIZE) {
+ result = L2CAP_CONF_REJECT;
+ break;
+ }
+ qos = (void *) opt->val;
+
+ /* Flags */
+ val = qos->flags;
+ if (val) {
+ qos->flags = 0;
+ result = L2CAP_CONF_UNACCEPT;
+ }
+
+ /* Service type */
+ val = qos->service_type;
+ if (val != L2CAP_CONF_QOS_BEST_EFFORT &&
+ val != L2CAP_CONF_QOS_NO_TRAFFIC) {
+ qos->service_type = L2CAP_CONF_QOS_BEST_EFFORT;
+ result = L2CAP_CONF_UNACCEPT;
+ }
+
+ if (val != L2CAP_CONF_QOS_NO_TRAFFIC) {
+ /* XXX: These values should possibly be calculated
+ * based on LM / baseband properties also. */
+
+ /* Token rate */
+ val = le32_to_cpu(qos->token_rate);
+ if (val == L2CAP_CONF_QOS_WILDCARD)
+ qos->token_rate = cpu_to_le32(0x100000);
+
+ /* Token bucket size */
+ val = le32_to_cpu(qos->token_bucket_size);
+ if (val == L2CAP_CONF_QOS_WILDCARD)
+ qos->token_bucket_size = cpu_to_le32(65500);
+
+ /* Any Peak bandwidth value is correct to return as-is */
+ /* Any Access latency value is correct to return as-is */
+ /* Any Delay variation value is correct to return as-is */
+ }
+ break;
+
+ case L2CAP_CONF_RFC:
+ if (opt->len != 9) {
+ result = L2CAP_CONF_REJECT;
+ break;
+ }
+
+ /* Mode */
+ val = opt->val[0];
+ switch (val) {
+ case L2CAP_MODE_BASIC:
+ ch->mode = val;
+ ch->frame_in = l2cap_bframe_in;
+
+ /* All other parameters shall be ignored */
+ break;
+
+ case L2CAP_MODE_RETRANS:
+ case L2CAP_MODE_FLOWCTL:
+ ch->mode = val;
+ ch->frame_in = l2cap_iframe_in;
+ /* Note: most of these parameters refer to incoming traffic
+ * so we don't need to save them as long as we can accept
+ * incoming PDUs at any values of the parameters. */
+
+ /* TxWindow size */
+ val = opt->val[1];
+ if (val < 1 || val > 32) {
+ opt->val[1] = 32;
+ result = L2CAP_CONF_UNACCEPT;
+ break;
+ }
+
+ /* MaxTransmit */
+ val = opt->val[2];
+ if (val < 1) {
+ opt->val[2] = 1;
+ result = L2CAP_CONF_UNACCEPT;
+ break;
+ }
+
+ /* Remote Retransmission time-out shouldn't affect local
+ * operation (?) */
+
+ /* The Monitor time-out drives the local Monitor timer (?),
+ * so save the value. */
+ val = (opt->val[6] << 8) | opt->val[5];
+ if (val < 30) {
+ opt->val[5] = 100 & 0xff;
+ opt->val[6] = 100 >> 8;
+ result = L2CAP_CONF_UNACCEPT;
+ break;
+ }
+ ch->monitor_timeout = val;
+ l2cap_monitor_timer_update(ch);
+
+ /* MPS */
+ val = (opt->val[8] << 8) | opt->val[7];
+ if (val < ch->min_mtu) {
+ opt->val[7] = ch->min_mtu & 0xff;
+ opt->val[8] = ch->min_mtu >> 8;
+ result = L2CAP_CONF_UNACCEPT;
+ break;
+ }
+ ch->mps = val;
+ break;
+
+ default:
+ result = L2CAP_CONF_UNACCEPT;
+ break;
+ }
+ break;
+
+ default:
+ if (!(opt->type >> 7))
+ result = L2CAP_CONF_UNKNOWN;
+ break;
+ }
+
+ if (result != L2CAP_CONF_SUCCESS)
+ break; /* XXX: should continue? */
+ }
+
+ l2cap_configuration_response(l2cap, ch->remote_cid,
+ flag, result, rsp, len);
+
+ return result == L2CAP_CONF_SUCCESS && !flag;
+}
+
+static void l2cap_channel_config_req_msg(struct l2cap_instance_s *l2cap,
+ int flag, int cid, const uint8_t *data, int len)
+{
+ struct l2cap_chan_s *ch;
+
+ if (unlikely(cid >= L2CAP_CID_MAX || !l2cap->cid[cid])) {
+ l2cap_command_reject_cid(l2cap, l2cap->last_id, L2CAP_REJ_CID_INVAL,
+ cid, 0x0000);
+ return;
+ }
+ ch = l2cap->cid[cid];
+
+ /* From OPEN go to WAIT_CONFIG_REQ and from WAIT_CONFIG_REQ_RSP to
+ * WAIT_CONFIG_REQ_RSP. This is assuming the transition chart for OPEN
+ * on pg 1053, section 6.1.5, volume 3 of BT Core V2.0 has a mistake
+ * and on options-acceptable we go back to OPEN and otherwise to
+ * WAIT_CONFIG_REQ and not the other way. */
+ ch->config &= ~L2CAP_CFG_ACC;
+
+ if (l2cap_channel_config(l2cap, ch, flag, data, len))
+ /* Go to OPEN or WAIT_CONFIG_RSP */
+ ch->config |= L2CAP_CFG_ACC;
+
+ /* TODO: if the incoming traffic flow control or retransmission mode
+ * changed then we probably need to also generate the
+ * ConfigureChannel_Req event and set the outgoing traffic to the same
+ * mode. */
+ if (!(ch->config & L2CAP_CFG_INIT) && (ch->config & L2CAP_CFG_ACC) &&
+ !ch->config_req_id)
+ l2cap_channel_config_req_event(l2cap, ch);
+}
+
+static int l2cap_channel_config_rsp_msg(struct l2cap_instance_s *l2cap,
+ int result, int flag, int cid, const uint8_t *data, int len)
+{
+ struct l2cap_chan_s *ch;
+
+ if (unlikely(cid >= L2CAP_CID_MAX || !l2cap->cid[cid])) {
+ l2cap_command_reject_cid(l2cap, l2cap->last_id, L2CAP_REJ_CID_INVAL,
+ cid, 0x0000);
+ return 0;
+ }
+ ch = l2cap->cid[cid];
+
+ if (ch->config_req_id != l2cap->last_id)
+ return 1;
+ ch->config_req_id = 0;
+
+ if (result == L2CAP_CONF_SUCCESS) {
+ if (!flag)
+ ch->config |= L2CAP_CFG_INIT;
+ else
+ l2cap_channel_config_null(l2cap, ch);
+ } else
+ /* Retry until we succeed */
+ l2cap_channel_config_req_event(l2cap, ch);
+
+ return 0;
+}
+
+static void l2cap_channel_open_req_msg(struct l2cap_instance_s *l2cap,
+ int psm, int source_cid)
+{
+ struct l2cap_chan_s *ch = l2cap_channel_open(l2cap, psm, source_cid);
+
+ if (!ch)
+ return;
+
+ /* Optional */
+ if (!(ch->config & L2CAP_CFG_INIT) && !ch->config_req_id)
+ l2cap_channel_config_req_event(l2cap, ch);
+}
+
+static void l2cap_info(struct l2cap_instance_s *l2cap, int type)
+{
+ uint8_t data[4];
+ int len = 0;
+ int result = L2CAP_IR_SUCCESS;
+
+ switch (type) {
+ case L2CAP_IT_CL_MTU:
+ data[len ++] = l2cap->group_ch.mps & 0xff;
+ data[len ++] = l2cap->group_ch.mps >> 8;
+ break;
+
+ case L2CAP_IT_FEAT_MASK:
+ /* (Prematurely) report Flow control and Retransmission modes. */
+ data[len ++] = 0x03;
+ data[len ++] = 0x00;
+ data[len ++] = 0x00;
+ data[len ++] = 0x00;
+ break;
+
+ default:
+ result = L2CAP_IR_NOTSUPP;
+ }
+
+ l2cap_info_response(l2cap, type, result, data, len);
+}
+
+static void l2cap_command(struct l2cap_instance_s *l2cap, int code, int id,
+ const uint8_t *params, int len)
+{
+ int err;
+
+#if 0
+ /* TODO: do the IDs really have to be in sequence? */
+ if (!id || (id != l2cap->last_id && id != l2cap->next_id)) {
+ fprintf(stderr, "%s: out of sequence command packet ignored.\n",
+ __FUNCTION__);
+ return;
+ }
+#else
+ l2cap->next_id = id;
+#endif
+ if (id == l2cap->next_id) {
+ l2cap->last_id = l2cap->next_id;
+ l2cap->next_id = l2cap->next_id == 255 ? 1 : l2cap->next_id + 1;
+ } else {
+ /* TODO: Need to re-send the same response, without re-executing
+ * the corresponding command! */
+ }
+
+ switch (code) {
+ case L2CAP_COMMAND_REJ:
+ if (unlikely(len != 2 && len != 4 && len != 6)) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ /* We never issue commands other than Command Reject currently. */
+ fprintf(stderr, "%s: stray Command Reject (%02x, %04x) "
+ "packet, ignoring.\n", __FUNCTION__, id,
+ le16_to_cpu(((l2cap_cmd_rej *) params)->reason));
+ break;
+
+ case L2CAP_CONN_REQ:
+ if (unlikely(len != L2CAP_CONN_REQ_SIZE)) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ l2cap_channel_open_req_msg(l2cap,
+ le16_to_cpu(((l2cap_conn_req *) params)->psm),
+ le16_to_cpu(((l2cap_conn_req *) params)->scid));
+ break;
+
+ case L2CAP_CONN_RSP:
+ if (unlikely(len != L2CAP_CONN_RSP_SIZE)) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ /* We never issue Connection Requests currently. TODO */
+ fprintf(stderr, "%s: unexpected Connection Response (%02x) "
+ "packet, ignoring.\n", __FUNCTION__, id);
+ break;
+
+ case L2CAP_CONF_REQ:
+ if (unlikely(len < L2CAP_CONF_REQ_SIZE(0))) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ l2cap_channel_config_req_msg(l2cap,
+ le16_to_cpu(((l2cap_conf_req *) params)->flags) & 1,
+ le16_to_cpu(((l2cap_conf_req *) params)->dcid),
+ ((l2cap_conf_req *) params)->data,
+ len - L2CAP_CONF_REQ_SIZE(0));
+ break;
+
+ case L2CAP_CONF_RSP:
+ if (unlikely(len < L2CAP_CONF_RSP_SIZE(0))) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ if (l2cap_channel_config_rsp_msg(l2cap,
+ le16_to_cpu(((l2cap_conf_rsp *) params)->result),
+ le16_to_cpu(((l2cap_conf_rsp *) params)->flags) & 1,
+ le16_to_cpu(((l2cap_conf_rsp *) params)->scid),
+ ((l2cap_conf_rsp *) params)->data,
+ len - L2CAP_CONF_RSP_SIZE(0)))
+ fprintf(stderr, "%s: unexpected Configure Response (%02x) "
+ "packet, ignoring.\n", __FUNCTION__, id);
+ break;
+
+ case L2CAP_DISCONN_REQ:
+ if (unlikely(len != L2CAP_DISCONN_REQ_SIZE)) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ l2cap_channel_close(l2cap,
+ le16_to_cpu(((l2cap_disconn_req *) params)->dcid),
+ le16_to_cpu(((l2cap_disconn_req *) params)->scid));
+ break;
+
+ case L2CAP_DISCONN_RSP:
+ if (unlikely(len != L2CAP_DISCONN_RSP_SIZE)) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ /* We never issue Disconnection Requests currently. TODO */
+ fprintf(stderr, "%s: unexpected Disconnection Response (%02x) "
+ "packet, ignoring.\n", __FUNCTION__, id);
+ break;
+
+ case L2CAP_ECHO_REQ:
+ l2cap_echo_response(l2cap, params, len);
+ break;
+
+ case L2CAP_ECHO_RSP:
+ /* We never issue Echo Requests currently. TODO */
+ fprintf(stderr, "%s: unexpected Echo Response (%02x) "
+ "packet, ignoring.\n", __FUNCTION__, id);
+ break;
+
+ case L2CAP_INFO_REQ:
+ if (unlikely(len != L2CAP_INFO_REQ_SIZE)) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ l2cap_info(l2cap, le16_to_cpu(((l2cap_info_req *) params)->type));
+ break;
+
+ case L2CAP_INFO_RSP:
+ if (unlikely(len != L2CAP_INFO_RSP_SIZE)) {
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ goto reject;
+ }
+
+ /* We never issue Information Requests currently. TODO */
+ fprintf(stderr, "%s: unexpected Information Response (%02x) "
+ "packet, ignoring.\n", __FUNCTION__, id);
+ break;
+
+ default:
+ err = L2CAP_REJ_CMD_NOT_UNDERSTOOD;
+ reject:
+ l2cap_command_reject(l2cap, id, err, 0, 0);
+ break;
+ }
+}
+
+static void l2cap_rexmit_enable(struct l2cap_chan_s *ch, int enable)
+{
+ ch->rexmit = enable;
+
+ l2cap_retransmission_timer_update(ch);
+ l2cap_monitor_timer_update(ch);
+}
+
+/* Command frame SDU */
+static void l2cap_cframe_in(void *opaque, const uint8_t *data, int len)
+{
+ struct l2cap_instance_s *l2cap = opaque;
+ const l2cap_cmd_hdr *hdr;
+ int clen;
+
+ while (len) {
+ hdr = (void *) data;
+ if (len < L2CAP_CMD_HDR_SIZE)
+ /* TODO: signal an error */
+ return;
+ len -= L2CAP_CMD_HDR_SIZE;
+ data += L2CAP_CMD_HDR_SIZE;
+
+ clen = le16_to_cpu(hdr->len);
+ if (len < clen) {
+ l2cap_command_reject(l2cap, hdr->ident,
+ L2CAP_REJ_CMD_NOT_UNDERSTOOD, 0, 0);
+ break;
+ }
+
+ l2cap_command(l2cap, hdr->code, hdr->ident, data, clen);
+ len -= clen;
+ data += clen;
+ }
+}
+
+/* Group frame SDU */
+static void l2cap_gframe_in(void *opaque, const uint8_t *data, int len)
+{
+}
+
+/* Supervisory frame */
+static void l2cap_sframe_in(struct l2cap_chan_s *ch, uint16_t ctrl)
+{
+}
+
+/* Basic L2CAP mode Information frame */
+static void l2cap_bframe_in(struct l2cap_chan_s *ch, uint16_t cid,
+ const l2cap_hdr *hdr, int len)
+{
+ /* We have a full SDU, no further processing */
+ ch->params.sdu_in(ch->params.opaque, hdr->data, len);
+}
+
+/* Flow Control and Retransmission mode frame */
+static void l2cap_iframe_in(struct l2cap_chan_s *ch, uint16_t cid,
+ const l2cap_hdr *hdr, int len)
+{
+ uint16_t fcs = le16_to_cpup((void *) (hdr->data + len - 2));
+
+ if (len < 4)
+ goto len_error;
+ if (l2cap_fcs16((const uint8_t *) hdr, L2CAP_HDR_SIZE + len - 2) != fcs)
+ goto fcs_error;
+
+ if ((hdr->data[0] >> 7) == ch->rexmit)
+ l2cap_rexmit_enable(ch, !(hdr->data[0] >> 7));
+
+ if (hdr->data[0] & 1) {
+ if (len != 4) {
+ /* TODO: Signal an error? */
+ return;
+ }
+ l2cap_sframe_in(ch, le16_to_cpup((void *) hdr->data));
+ return;
+ }
+
+ switch (hdr->data[1] >> 6) { /* SAR */
+ case L2CAP_SAR_NO_SEG:
+ if (ch->len_total)
+ goto seg_error;
+ if (len - 4 > ch->mps)
+ goto len_error;
+
+ ch->params.sdu_in(ch->params.opaque, hdr->data + 2, len - 4);
+ break;
+
+ case L2CAP_SAR_START:
+ if (ch->len_total || len < 6)
+ goto seg_error;
+ if (len - 6 > ch->mps)
+ goto len_error;
+
+ ch->len_total = le16_to_cpup((void *) (hdr->data + 2));
+ if (len >= 6 + ch->len_total)
+ goto seg_error;
+
+ ch->len_cur = len - 6;
+ memcpy(ch->sdu, hdr->data + 4, ch->len_cur);
+ break;
+
+ case L2CAP_SAR_END:
+ if (!ch->len_total || ch->len_cur + len - 4 < ch->len_total)
+ goto seg_error;
+ if (len - 4 > ch->mps)
+ goto len_error;
+
+ memcpy(ch->sdu + ch->len_cur, hdr->data + 2, len - 4);
+ ch->params.sdu_in(ch->params.opaque, ch->sdu, ch->len_total);
+ break;
+
+ case L2CAP_SAR_CONT:
+ if (!ch->len_total || ch->len_cur + len - 4 >= ch->len_total)
+ goto seg_error;
+ if (len - 4 > ch->mps)
+ goto len_error;
+
+ memcpy(ch->sdu + ch->len_cur, hdr->data + 2, len - 4);
+ ch->len_cur += len - 4;
+ break;
+
+ seg_error:
+ len_error: /* TODO */
+ fcs_error: /* TODO */
+ ch->len_cur = 0;
+ ch->len_total = 0;
+ break;
+ }
+}
+
+static void l2cap_frame_in(struct l2cap_instance_s *l2cap,
+ const l2cap_hdr *frame)
+{
+ uint16_t cid = le16_to_cpu(frame->cid);
+ uint16_t len = le16_to_cpu(frame->len);
+
+ if (unlikely(cid >= L2CAP_CID_MAX || !l2cap->cid[cid])) {
+ fprintf(stderr, "%s: frame addressed to a non-existent L2CAP "
+ "channel %04x received.\n", __FUNCTION__, cid);
+ return;
+ }
+
+ l2cap->cid[cid]->frame_in(l2cap->cid[cid], cid, frame, len);
+}
+
+/* "Recombination" */
+static void l2cap_pdu_in(struct l2cap_instance_s *l2cap,
+ const uint8_t *data, int len)
+{
+ const l2cap_hdr *hdr = (void *) l2cap->frame_in;
+
+ if (unlikely(len + l2cap->frame_in_len > sizeof(l2cap->frame_in))) {
+ if (l2cap->frame_in_len < sizeof(l2cap->frame_in)) {
+ memcpy(l2cap->frame_in + l2cap->frame_in_len, data,
+ sizeof(l2cap->frame_in) - l2cap->frame_in_len);
+ l2cap->frame_in_len = sizeof(l2cap->frame_in);
+ /* TODO: truncate */
+ l2cap_frame_in(l2cap, hdr);
+ }
+
+ return;
+ }
+
+ memcpy(l2cap->frame_in + l2cap->frame_in_len, data, len);
+ l2cap->frame_in_len += len;
+
+ if (len >= L2CAP_HDR_SIZE)
+ if (len >= L2CAP_HDR_SIZE + le16_to_cpu(hdr->len))
+ l2cap_frame_in(l2cap, hdr);
+ /* There is never a start of a new PDU in the same ACL packet, so
+ * no need to memmove the remaining payload and loop. */
+}
+
+static inline uint8_t *l2cap_pdu_out(struct l2cap_instance_s *l2cap,
+ uint16_t cid, uint16_t len)
+{
+ l2cap_hdr *hdr = (void *) l2cap->frame_out;
+
+ l2cap->frame_out_len = len + L2CAP_HDR_SIZE;
+
+ hdr->cid = cpu_to_le16(cid);
+ hdr->len = cpu_to_le16(len);
+
+ return l2cap->frame_out + L2CAP_HDR_SIZE;
+}
+
+static inline void l2cap_pdu_submit(struct l2cap_instance_s *l2cap)
+{
+ /* TODO: Fragmentation */
+ (l2cap->role ?
+ l2cap->link->slave->lmp_acl_data : l2cap->link->host->lmp_acl_resp)
+ (l2cap->link, l2cap->frame_out, 1, l2cap->frame_out_len);
+}
+
+static uint8_t *l2cap_bframe_out(struct bt_l2cap_conn_params_s *parm, int len)
+{
+ struct l2cap_chan_s *chan = (struct l2cap_chan_s *) parm;
+
+ if (len > chan->params.remote_mtu) {
+ fprintf(stderr, "%s: B-Frame for CID %04x longer than %i octets.\n",
+ __FUNCTION__,
+ chan->remote_cid, chan->params.remote_mtu);
+ exit(-1);
+ }
+
+ return l2cap_pdu_out(chan->l2cap, chan->remote_cid, len);
+}
+
+static void l2cap_bframe_submit(struct bt_l2cap_conn_params_s *parms)
+{
+ struct l2cap_chan_s *chan = (struct l2cap_chan_s *) parms;
+
+ l2cap_pdu_submit(chan->l2cap);
+}
+
+#if 0
+/* Stub: Only used if an emulated device requests outgoing flow control */
+static uint8_t *l2cap_iframe_out(struct bt_l2cap_conn_params_s *parm, int len)
+{
+ struct l2cap_chan_s *chan = (struct l2cap_chan_s *) parm;
+
+ if (len > chan->params.remote_mtu) {
+ /* TODO: slice into segments and queue each segment as a separate
+ * I-Frame in a FIFO of I-Frames, local to the CID. */
+ } else {
+ /* TODO: add to the FIFO of I-Frames, local to the CID. */
+ /* Possibly we need to return a pointer to a contiguous buffer
+ * for now and then memcpy from it into FIFOs in l2cap_iframe_submit
+ * while segmenting at the same time. */
+ }
+ return 0;
+}
+
+static void l2cap_iframe_submit(struct bt_l2cap_conn_params_s *parm)
+{
+ /* TODO: If flow control indicates clear to send, start submitting the
+ * invidual I-Frames from the FIFO, but don't remove them from there.
+ * Kick the appropriate timer until we get an S-Frame, and only then
+ * remove from FIFO or resubmit and re-kick the timer if the timer
+ * expired. */
+}
+#endif
+
+static void l2cap_init(struct l2cap_instance_s *l2cap,
+ struct bt_link_s *link, int role)
+{
+ l2cap->link = link;
+ l2cap->role = role;
+ l2cap->dev = (struct bt_l2cap_device_s *)
+ (role ? link->host : link->slave);
+
+ l2cap->next_id = 1;
+
+ /* Establish the signalling channel */
+ l2cap->signalling_ch.params.sdu_in = l2cap_cframe_in;
+ l2cap->signalling_ch.params.sdu_out = l2cap_bframe_out;
+ l2cap->signalling_ch.params.sdu_submit = l2cap_bframe_submit;
+ l2cap->signalling_ch.params.opaque = l2cap;
+ l2cap->signalling_ch.params.remote_mtu = 48;
+ l2cap->signalling_ch.remote_cid = L2CAP_CID_SIGNALLING;
+ l2cap->signalling_ch.frame_in = l2cap_bframe_in;
+ l2cap->signalling_ch.mps = 65536;
+ l2cap->signalling_ch.min_mtu = 48;
+ l2cap->signalling_ch.mode = L2CAP_MODE_BASIC;
+ l2cap->signalling_ch.l2cap = l2cap;
+ l2cap->cid[L2CAP_CID_SIGNALLING] = &l2cap->signalling_ch;
+
+ /* Establish the connection-less data channel */
+ l2cap->group_ch.params.sdu_in = l2cap_gframe_in;
+ l2cap->group_ch.params.opaque = l2cap;
+ l2cap->group_ch.frame_in = l2cap_bframe_in;
+ l2cap->group_ch.mps = 65533;
+ l2cap->group_ch.l2cap = l2cap;
+ l2cap->group_ch.remote_cid = L2CAP_CID_INVALID;
+ l2cap->cid[L2CAP_CID_GROUP] = &l2cap->group_ch;
+}
+
+static void l2cap_teardown(struct l2cap_instance_s *l2cap, int send_disconnect)
+{
+ int cid;
+
+ /* Don't send DISCONNECT if we are currently handling a DISCONNECT
+ * sent from the other side. */
+ if (send_disconnect) {
+ if (l2cap->role)
+ l2cap->dev->device.lmp_disconnect_slave(l2cap->link);
+ /* l2cap->link is invalid from now on. */
+ else
+ l2cap->dev->device.lmp_disconnect_master(l2cap->link);
+ }
+
+ for (cid = L2CAP_CID_ALLOC; cid < L2CAP_CID_MAX; cid ++)
+ if (l2cap->cid[cid]) {
+ l2cap->cid[cid]->params.close(l2cap->cid[cid]->params.opaque);
+ g_free(l2cap->cid[cid]);
+ }
+
+ if (l2cap->role)
+ g_free(l2cap);
+ else
+ g_free(l2cap->link);
+}
+
+/* L2CAP glue to lower layers in bluetooth stack (LMP) */
+
+static void l2cap_lmp_connection_request(struct bt_link_s *link)
+{
+ struct bt_l2cap_device_s *dev = (struct bt_l2cap_device_s *) link->slave;
+ struct slave_l2cap_instance_s *l2cap;
+
+ /* Always accept - we only get called if (dev->device->page_scan). */
+
+ l2cap = g_malloc0(sizeof(struct slave_l2cap_instance_s));
+ l2cap->link.slave = &dev->device;
+ l2cap->link.host = link->host;
+ l2cap_init(&l2cap->l2cap, &l2cap->link, 0);
+
+ /* Always at the end */
+ link->host->reject_reason = 0;
+ link->host->lmp_connection_complete(&l2cap->link);
+}
+
+/* Stub */
+static void l2cap_lmp_connection_complete(struct bt_link_s *link)
+{
+ struct bt_l2cap_device_s *dev = (struct bt_l2cap_device_s *) link->host;
+ struct l2cap_instance_s *l2cap;
+
+ if (dev->device.reject_reason) {
+ /* Signal to upper layer */
+ return;
+ }
+
+ l2cap = g_malloc0(sizeof(struct l2cap_instance_s));
+ l2cap_init(l2cap, link, 1);
+
+ link->acl_mode = acl_active;
+
+ /* Signal to upper layer */
+}
+
+/* Stub */
+static void l2cap_lmp_disconnect_host(struct bt_link_s *link)
+{
+ struct bt_l2cap_device_s *dev = (struct bt_l2cap_device_s *) link->host;
+ struct l2cap_instance_s *l2cap =
+ /* TODO: Retrieve from upper layer */ (void *) dev;
+
+ /* Signal to upper layer */
+
+ l2cap_teardown(l2cap, 0);
+}
+
+static void l2cap_lmp_disconnect_slave(struct bt_link_s *link)
+{
+ struct slave_l2cap_instance_s *l2cap =
+ (struct slave_l2cap_instance_s *) link;
+
+ l2cap_teardown(&l2cap->l2cap, 0);
+}
+
+static void l2cap_lmp_acl_data_slave(struct bt_link_s *link,
+ const uint8_t *data, int start, int len)
+{
+ struct slave_l2cap_instance_s *l2cap =
+ (struct slave_l2cap_instance_s *) link;
+
+ if (start)
+ l2cap->l2cap.frame_in_len = 0;
+
+ l2cap_pdu_in(&l2cap->l2cap, data, len);
+}
+
+/* Stub */
+static void l2cap_lmp_acl_data_host(struct bt_link_s *link,
+ const uint8_t *data, int start, int len)
+{
+ struct bt_l2cap_device_s *dev = (struct bt_l2cap_device_s *) link->host;
+ struct l2cap_instance_s *l2cap =
+ /* TODO: Retrieve from upper layer */ (void *) dev;
+
+ if (start)
+ l2cap->frame_in_len = 0;
+
+ l2cap_pdu_in(l2cap, data, len);
+}
+
+static void l2cap_dummy_destroy(struct bt_device_s *dev)
+{
+ struct bt_l2cap_device_s *l2cap_dev = (struct bt_l2cap_device_s *) dev;
+
+ bt_l2cap_device_done(l2cap_dev);
+}
+
+void bt_l2cap_device_init(struct bt_l2cap_device_s *dev,
+ struct bt_scatternet_s *net)
+{
+ bt_device_init(&dev->device, net);
+
+ dev->device.lmp_connection_request = l2cap_lmp_connection_request;
+ dev->device.lmp_connection_complete = l2cap_lmp_connection_complete;
+ dev->device.lmp_disconnect_master = l2cap_lmp_disconnect_host;
+ dev->device.lmp_disconnect_slave = l2cap_lmp_disconnect_slave;
+ dev->device.lmp_acl_data = l2cap_lmp_acl_data_slave;
+ dev->device.lmp_acl_resp = l2cap_lmp_acl_data_host;
+
+ dev->device.handle_destroy = l2cap_dummy_destroy;
+}
+
+void bt_l2cap_device_done(struct bt_l2cap_device_s *dev)
+{
+ bt_device_done(&dev->device);
+
+ /* Should keep a list of all instances and go through it and
+ * invoke l2cap_teardown() for each. */
+}
+
+void bt_l2cap_psm_register(struct bt_l2cap_device_s *dev, int psm, int min_mtu,
+ int (*new_channel)(struct bt_l2cap_device_s *dev,
+ struct bt_l2cap_conn_params_s *params))
+{
+ struct bt_l2cap_psm_s *new_psm = l2cap_psm(dev, psm);
+
+ if (new_psm) {
+ fprintf(stderr, "%s: PSM %04x already registered for device `%s'.\n",
+ __FUNCTION__, psm, dev->device.lmp_name);
+ exit(-1);
+ }
+
+ new_psm = g_malloc0(sizeof(*new_psm));
+ new_psm->psm = psm;
+ new_psm->min_mtu = min_mtu;
+ new_psm->new_channel = new_channel;
+ new_psm->next = dev->first_psm;
+ dev->first_psm = new_psm;
+}
diff --git a/hw/bt/sdp.c b/hw/bt/sdp.c
new file mode 100644
index 00000000..c9037479
--- /dev/null
+++ b/hw/bt/sdp.c
@@ -0,0 +1,967 @@
+/*
+ * Service Discover Protocol server for QEMU L2CAP devices
+ *
+ * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "hw/bt.h"
+
+struct bt_l2cap_sdp_state_s {
+ struct bt_l2cap_conn_params_s *channel;
+
+ struct sdp_service_record_s {
+ int match;
+
+ int *uuid;
+ int uuids;
+ struct sdp_service_attribute_s {
+ int match;
+
+ int attribute_id;
+ int len;
+ void *pair;
+ } *attribute_list;
+ int attributes;
+ } *service_list;
+ int services;
+};
+
+static ssize_t sdp_datalen(const uint8_t **element, ssize_t *left)
+{
+ size_t len = *(*element) ++ & SDP_DSIZE_MASK;
+
+ if (!*left)
+ return -1;
+ (*left) --;
+
+ if (len < SDP_DSIZE_NEXT1)
+ return 1 << len;
+ else if (len == SDP_DSIZE_NEXT1) {
+ if (*left < 1)
+ return -1;
+ (*left) --;
+
+ return *(*element) ++;
+ } else if (len == SDP_DSIZE_NEXT2) {
+ if (*left < 2)
+ return -1;
+ (*left) -= 2;
+
+ len = (*(*element) ++) << 8;
+ return len | (*(*element) ++);
+ } else {
+ if (*left < 4)
+ return -1;
+ (*left) -= 4;
+
+ len = (*(*element) ++) << 24;
+ len |= (*(*element) ++) << 16;
+ len |= (*(*element) ++) << 8;
+ return len | (*(*element) ++);
+ }
+}
+
+static const uint8_t bt_base_uuid[12] = {
+ 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
+};
+
+static int sdp_uuid_match(struct sdp_service_record_s *record,
+ const uint8_t *uuid, ssize_t datalen)
+{
+ int *lo, hi, val;
+
+ if (datalen == 16 || datalen == 4) {
+ if (datalen == 16 && memcmp(uuid + 4, bt_base_uuid, 12))
+ return 0;
+
+ if (uuid[0] | uuid[1])
+ return 0;
+ uuid += 2;
+ }
+
+ val = (uuid[0] << 8) | uuid[1];
+ lo = record->uuid;
+ hi = record->uuids;
+ while (hi >>= 1)
+ if (lo[hi] <= val)
+ lo += hi;
+
+ return *lo == val;
+}
+
+#define CONTINUATION_PARAM_SIZE (1 + sizeof(int))
+#define MAX_PDU_OUT_SIZE 96 /* Arbitrary */
+#define PDU_HEADER_SIZE 5
+#define MAX_RSP_PARAM_SIZE (MAX_PDU_OUT_SIZE - PDU_HEADER_SIZE - \
+ CONTINUATION_PARAM_SIZE)
+
+static int sdp_svc_match(struct bt_l2cap_sdp_state_s *sdp,
+ const uint8_t **req, ssize_t *len)
+{
+ size_t datalen;
+ int i;
+
+ if ((**req & ~SDP_DSIZE_MASK) != SDP_DTYPE_UUID)
+ return 1;
+
+ datalen = sdp_datalen(req, len);
+ if (datalen != 2 && datalen != 4 && datalen != 16)
+ return 1;
+
+ for (i = 0; i < sdp->services; i ++)
+ if (sdp_uuid_match(&sdp->service_list[i], *req, datalen))
+ sdp->service_list[i].match = 1;
+
+ (*req) += datalen;
+ (*len) -= datalen;
+
+ return 0;
+}
+
+static ssize_t sdp_svc_search(struct bt_l2cap_sdp_state_s *sdp,
+ uint8_t *rsp, const uint8_t *req, ssize_t len)
+{
+ ssize_t seqlen;
+ int i, count, start, end, max;
+ int32_t handle;
+
+ /* Perform the search */
+ for (i = 0; i < sdp->services; i ++)
+ sdp->service_list[i].match = 0;
+
+ if (len < 1)
+ return -SDP_INVALID_SYNTAX;
+ if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) {
+ seqlen = sdp_datalen(&req, &len);
+ if (seqlen < 3 || len < seqlen)
+ return -SDP_INVALID_SYNTAX;
+ len -= seqlen;
+
+ while (seqlen)
+ if (sdp_svc_match(sdp, &req, &seqlen))
+ return -SDP_INVALID_SYNTAX;
+ } else if (sdp_svc_match(sdp, &req, &seqlen))
+ return -SDP_INVALID_SYNTAX;
+
+ if (len < 3)
+ return -SDP_INVALID_SYNTAX;
+ max = (req[0] << 8) | req[1];
+ req += 2;
+ len -= 2;
+
+ if (*req) {
+ if (len <= sizeof(int))
+ return -SDP_INVALID_SYNTAX;
+ len -= sizeof(int);
+ memcpy(&start, req + 1, sizeof(int));
+ } else
+ start = 0;
+
+ if (len > 1)
+ return -SDP_INVALID_SYNTAX;
+
+ /* Output the results */
+ len = 4;
+ count = 0;
+ end = start;
+ for (i = 0; i < sdp->services; i ++)
+ if (sdp->service_list[i].match) {
+ if (count >= start && count < max && len + 4 < MAX_RSP_PARAM_SIZE) {
+ handle = i;
+ memcpy(rsp + len, &handle, 4);
+ len += 4;
+ end = count + 1;
+ }
+
+ count ++;
+ }
+
+ rsp[0] = count >> 8;
+ rsp[1] = count & 0xff;
+ rsp[2] = (end - start) >> 8;
+ rsp[3] = (end - start) & 0xff;
+
+ if (end < count) {
+ rsp[len ++] = sizeof(int);
+ memcpy(rsp + len, &end, sizeof(int));
+ len += 4;
+ } else
+ rsp[len ++] = 0;
+
+ return len;
+}
+
+static int sdp_attr_match(struct sdp_service_record_s *record,
+ const uint8_t **req, ssize_t *len)
+{
+ int i, start, end;
+
+ if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_2)) {
+ (*req) ++;
+ if (*len < 3)
+ return 1;
+
+ start = (*(*req) ++) << 8;
+ start |= *(*req) ++;
+ end = start;
+ *len -= 3;
+ } else if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_4)) {
+ (*req) ++;
+ if (*len < 5)
+ return 1;
+
+ start = (*(*req) ++) << 8;
+ start |= *(*req) ++;
+ end = (*(*req) ++) << 8;
+ end |= *(*req) ++;
+ *len -= 5;
+ } else
+ return 1;
+
+ for (i = 0; i < record->attributes; i ++)
+ if (record->attribute_list[i].attribute_id >= start &&
+ record->attribute_list[i].attribute_id <= end)
+ record->attribute_list[i].match = 1;
+
+ return 0;
+}
+
+static ssize_t sdp_attr_get(struct bt_l2cap_sdp_state_s *sdp,
+ uint8_t *rsp, const uint8_t *req, ssize_t len)
+{
+ ssize_t seqlen;
+ int i, start, end, max;
+ int32_t handle;
+ struct sdp_service_record_s *record;
+ uint8_t *lst;
+
+ /* Perform the search */
+ if (len < 7)
+ return -SDP_INVALID_SYNTAX;
+ memcpy(&handle, req, 4);
+ req += 4;
+ len -= 4;
+
+ if (handle < 0 || handle > sdp->services)
+ return -SDP_INVALID_RECORD_HANDLE;
+ record = &sdp->service_list[handle];
+
+ for (i = 0; i < record->attributes; i ++)
+ record->attribute_list[i].match = 0;
+
+ max = (req[0] << 8) | req[1];
+ req += 2;
+ len -= 2;
+ if (max < 0x0007)
+ return -SDP_INVALID_SYNTAX;
+
+ if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) {
+ seqlen = sdp_datalen(&req, &len);
+ if (seqlen < 3 || len < seqlen)
+ return -SDP_INVALID_SYNTAX;
+ len -= seqlen;
+
+ while (seqlen)
+ if (sdp_attr_match(record, &req, &seqlen))
+ return -SDP_INVALID_SYNTAX;
+ } else if (sdp_attr_match(record, &req, &seqlen))
+ return -SDP_INVALID_SYNTAX;
+
+ if (len < 1)
+ return -SDP_INVALID_SYNTAX;
+
+ if (*req) {
+ if (len <= sizeof(int))
+ return -SDP_INVALID_SYNTAX;
+ len -= sizeof(int);
+ memcpy(&start, req + 1, sizeof(int));
+ } else
+ start = 0;
+
+ if (len > 1)
+ return -SDP_INVALID_SYNTAX;
+
+ /* Output the results */
+ lst = rsp + 2;
+ max = MIN(max, MAX_RSP_PARAM_SIZE);
+ len = 3 - start;
+ end = 0;
+ for (i = 0; i < record->attributes; i ++)
+ if (record->attribute_list[i].match) {
+ if (len >= 0 && len + record->attribute_list[i].len < max) {
+ memcpy(lst + len, record->attribute_list[i].pair,
+ record->attribute_list[i].len);
+ end = len + record->attribute_list[i].len;
+ }
+ len += record->attribute_list[i].len;
+ }
+ if (0 >= start) {
+ lst[0] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2;
+ lst[1] = (len + start - 3) >> 8;
+ lst[2] = (len + start - 3) & 0xff;
+ }
+
+ rsp[0] = end >> 8;
+ rsp[1] = end & 0xff;
+
+ if (end < len) {
+ len = end + start;
+ lst[end ++] = sizeof(int);
+ memcpy(lst + end, &len, sizeof(int));
+ end += sizeof(int);
+ } else
+ lst[end ++] = 0;
+
+ return end + 2;
+}
+
+static int sdp_svc_attr_match(struct bt_l2cap_sdp_state_s *sdp,
+ const uint8_t **req, ssize_t *len)
+{
+ int i, j, start, end;
+ struct sdp_service_record_s *record;
+
+ if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_2)) {
+ (*req) ++;
+ if (*len < 3)
+ return 1;
+
+ start = (*(*req) ++) << 8;
+ start |= *(*req) ++;
+ end = start;
+ *len -= 3;
+ } else if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_4)) {
+ (*req) ++;
+ if (*len < 5)
+ return 1;
+
+ start = (*(*req) ++) << 8;
+ start |= *(*req) ++;
+ end = (*(*req) ++) << 8;
+ end |= *(*req) ++;
+ *len -= 5;
+ } else
+ return 1;
+
+ for (i = 0; i < sdp->services; i ++)
+ if ((record = &sdp->service_list[i])->match)
+ for (j = 0; j < record->attributes; j ++)
+ if (record->attribute_list[j].attribute_id >= start &&
+ record->attribute_list[j].attribute_id <= end)
+ record->attribute_list[j].match = 1;
+
+ return 0;
+}
+
+static ssize_t sdp_svc_search_attr_get(struct bt_l2cap_sdp_state_s *sdp,
+ uint8_t *rsp, const uint8_t *req, ssize_t len)
+{
+ ssize_t seqlen;
+ int i, j, start, end, max;
+ struct sdp_service_record_s *record;
+ uint8_t *lst;
+
+ /* Perform the search */
+ for (i = 0; i < sdp->services; i ++) {
+ sdp->service_list[i].match = 0;
+ for (j = 0; j < sdp->service_list[i].attributes; j ++)
+ sdp->service_list[i].attribute_list[j].match = 0;
+ }
+
+ if (len < 1)
+ return -SDP_INVALID_SYNTAX;
+ if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) {
+ seqlen = sdp_datalen(&req, &len);
+ if (seqlen < 3 || len < seqlen)
+ return -SDP_INVALID_SYNTAX;
+ len -= seqlen;
+
+ while (seqlen)
+ if (sdp_svc_match(sdp, &req, &seqlen))
+ return -SDP_INVALID_SYNTAX;
+ } else if (sdp_svc_match(sdp, &req, &seqlen))
+ return -SDP_INVALID_SYNTAX;
+
+ if (len < 3)
+ return -SDP_INVALID_SYNTAX;
+ max = (req[0] << 8) | req[1];
+ req += 2;
+ len -= 2;
+ if (max < 0x0007)
+ return -SDP_INVALID_SYNTAX;
+
+ if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) {
+ seqlen = sdp_datalen(&req, &len);
+ if (seqlen < 3 || len < seqlen)
+ return -SDP_INVALID_SYNTAX;
+ len -= seqlen;
+
+ while (seqlen)
+ if (sdp_svc_attr_match(sdp, &req, &seqlen))
+ return -SDP_INVALID_SYNTAX;
+ } else if (sdp_svc_attr_match(sdp, &req, &seqlen))
+ return -SDP_INVALID_SYNTAX;
+
+ if (len < 1)
+ return -SDP_INVALID_SYNTAX;
+
+ if (*req) {
+ if (len <= sizeof(int))
+ return -SDP_INVALID_SYNTAX;
+ len -= sizeof(int);
+ memcpy(&start, req + 1, sizeof(int));
+ } else
+ start = 0;
+
+ if (len > 1)
+ return -SDP_INVALID_SYNTAX;
+
+ /* Output the results */
+ /* This assumes empty attribute lists are never to be returned even
+ * for matching Service Records. In practice this shouldn't happen
+ * as the requestor will usually include the always present
+ * ServiceRecordHandle AttributeID in AttributeIDList. */
+ lst = rsp + 2;
+ max = MIN(max, MAX_RSP_PARAM_SIZE);
+ len = 3 - start;
+ end = 0;
+ for (i = 0; i < sdp->services; i ++)
+ if ((record = &sdp->service_list[i])->match) {
+ len += 3;
+ seqlen = len;
+ for (j = 0; j < record->attributes; j ++)
+ if (record->attribute_list[j].match) {
+ if (len >= 0)
+ if (len + record->attribute_list[j].len < max) {
+ memcpy(lst + len, record->attribute_list[j].pair,
+ record->attribute_list[j].len);
+ end = len + record->attribute_list[j].len;
+ }
+ len += record->attribute_list[j].len;
+ }
+ if (seqlen == len)
+ len -= 3;
+ else if (seqlen >= 3 && seqlen < max) {
+ lst[seqlen - 3] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2;
+ lst[seqlen - 2] = (len - seqlen) >> 8;
+ lst[seqlen - 1] = (len - seqlen) & 0xff;
+ }
+ }
+ if (len == 3 - start)
+ len -= 3;
+ else if (0 >= start) {
+ lst[0] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2;
+ lst[1] = (len + start - 3) >> 8;
+ lst[2] = (len + start - 3) & 0xff;
+ }
+
+ rsp[0] = end >> 8;
+ rsp[1] = end & 0xff;
+
+ if (end < len) {
+ len = end + start;
+ lst[end ++] = sizeof(int);
+ memcpy(lst + end, &len, sizeof(int));
+ end += sizeof(int);
+ } else
+ lst[end ++] = 0;
+
+ return end + 2;
+}
+
+static void bt_l2cap_sdp_sdu_in(void *opaque, const uint8_t *data, int len)
+{
+ struct bt_l2cap_sdp_state_s *sdp = opaque;
+ enum bt_sdp_cmd pdu_id;
+ uint8_t rsp[MAX_PDU_OUT_SIZE - PDU_HEADER_SIZE], *sdu_out;
+ int transaction_id, plen;
+ int err = 0;
+ int rsp_len = 0;
+
+ if (len < 5) {
+ fprintf(stderr, "%s: short SDP PDU (%iB).\n", __FUNCTION__, len);
+ return;
+ }
+
+ pdu_id = *data ++;
+ transaction_id = (data[0] << 8) | data[1];
+ plen = (data[2] << 8) | data[3];
+ data += 4;
+ len -= 5;
+
+ if (len != plen) {
+ fprintf(stderr, "%s: wrong SDP PDU length (%iB != %iB).\n",
+ __FUNCTION__, plen, len);
+ err = SDP_INVALID_PDU_SIZE;
+ goto respond;
+ }
+
+ switch (pdu_id) {
+ case SDP_SVC_SEARCH_REQ:
+ rsp_len = sdp_svc_search(sdp, rsp, data, len);
+ pdu_id = SDP_SVC_SEARCH_RSP;
+ break;
+
+ case SDP_SVC_ATTR_REQ:
+ rsp_len = sdp_attr_get(sdp, rsp, data, len);
+ pdu_id = SDP_SVC_ATTR_RSP;
+ break;
+
+ case SDP_SVC_SEARCH_ATTR_REQ:
+ rsp_len = sdp_svc_search_attr_get(sdp, rsp, data, len);
+ pdu_id = SDP_SVC_SEARCH_ATTR_RSP;
+ break;
+
+ case SDP_ERROR_RSP:
+ case SDP_SVC_ATTR_RSP:
+ case SDP_SVC_SEARCH_RSP:
+ case SDP_SVC_SEARCH_ATTR_RSP:
+ default:
+ fprintf(stderr, "%s: unexpected SDP PDU ID %02x.\n",
+ __FUNCTION__, pdu_id);
+ err = SDP_INVALID_SYNTAX;
+ break;
+ }
+
+ if (rsp_len < 0) {
+ err = -rsp_len;
+ rsp_len = 0;
+ }
+
+respond:
+ if (err) {
+ pdu_id = SDP_ERROR_RSP;
+ rsp[rsp_len ++] = err >> 8;
+ rsp[rsp_len ++] = err & 0xff;
+ }
+
+ sdu_out = sdp->channel->sdu_out(sdp->channel, rsp_len + PDU_HEADER_SIZE);
+
+ sdu_out[0] = pdu_id;
+ sdu_out[1] = transaction_id >> 8;
+ sdu_out[2] = transaction_id & 0xff;
+ sdu_out[3] = rsp_len >> 8;
+ sdu_out[4] = rsp_len & 0xff;
+ memcpy(sdu_out + PDU_HEADER_SIZE, rsp, rsp_len);
+
+ sdp->channel->sdu_submit(sdp->channel);
+}
+
+static void bt_l2cap_sdp_close_ch(void *opaque)
+{
+ struct bt_l2cap_sdp_state_s *sdp = opaque;
+ int i;
+
+ for (i = 0; i < sdp->services; i ++) {
+ g_free(sdp->service_list[i].attribute_list->pair);
+ g_free(sdp->service_list[i].attribute_list);
+ g_free(sdp->service_list[i].uuid);
+ }
+ g_free(sdp->service_list);
+ g_free(sdp);
+}
+
+struct sdp_def_service_s {
+ uint16_t class_uuid;
+ struct sdp_def_attribute_s {
+ uint16_t id;
+ struct sdp_def_data_element_s {
+ uint8_t type;
+ union {
+ uint32_t uint;
+ const char *str;
+ struct sdp_def_data_element_s *list;
+ } value;
+ } data;
+ } attributes[];
+};
+
+/* Calculate a safe byte count to allocate that will store the given
+ * element, at the same time count elements of a UUID type. */
+static int sdp_attr_max_size(struct sdp_def_data_element_s *element,
+ int *uuids)
+{
+ int type = element->type & ~SDP_DSIZE_MASK;
+ int len;
+
+ if (type == SDP_DTYPE_UINT || type == SDP_DTYPE_UUID ||
+ type == SDP_DTYPE_BOOL) {
+ if (type == SDP_DTYPE_UUID)
+ (*uuids) ++;
+ return 1 + (1 << (element->type & SDP_DSIZE_MASK));
+ }
+
+ if (type == SDP_DTYPE_STRING || type == SDP_DTYPE_URL) {
+ if (element->type & SDP_DSIZE_MASK) {
+ for (len = 0; element->value.str[len] |
+ element->value.str[len + 1]; len ++);
+ return len;
+ } else
+ return 2 + strlen(element->value.str);
+ }
+
+ if (type != SDP_DTYPE_SEQ)
+ exit(-1);
+ len = 2;
+ element = element->value.list;
+ while (element->type)
+ len += sdp_attr_max_size(element ++, uuids);
+ if (len > 255)
+ exit (-1);
+
+ return len;
+}
+
+static int sdp_attr_write(uint8_t *data,
+ struct sdp_def_data_element_s *element, int **uuid)
+{
+ int type = element->type & ~SDP_DSIZE_MASK;
+ int len = 0;
+
+ if (type == SDP_DTYPE_UINT || type == SDP_DTYPE_BOOL) {
+ data[len ++] = element->type;
+ if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_1)
+ data[len ++] = (element->value.uint >> 0) & 0xff;
+ else if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_2) {
+ data[len ++] = (element->value.uint >> 8) & 0xff;
+ data[len ++] = (element->value.uint >> 0) & 0xff;
+ } else if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_4) {
+ data[len ++] = (element->value.uint >> 24) & 0xff;
+ data[len ++] = (element->value.uint >> 16) & 0xff;
+ data[len ++] = (element->value.uint >> 8) & 0xff;
+ data[len ++] = (element->value.uint >> 0) & 0xff;
+ }
+
+ return len;
+ }
+
+ if (type == SDP_DTYPE_UUID) {
+ *(*uuid) ++ = element->value.uint;
+
+ data[len ++] = element->type;
+ data[len ++] = (element->value.uint >> 24) & 0xff;
+ data[len ++] = (element->value.uint >> 16) & 0xff;
+ data[len ++] = (element->value.uint >> 8) & 0xff;
+ data[len ++] = (element->value.uint >> 0) & 0xff;
+ memcpy(data + len, bt_base_uuid, 12);
+
+ return len + 12;
+ }
+
+ data[0] = type | SDP_DSIZE_NEXT1;
+ if (type == SDP_DTYPE_STRING || type == SDP_DTYPE_URL) {
+ if (element->type & SDP_DSIZE_MASK)
+ for (len = 0; element->value.str[len] |
+ element->value.str[len + 1]; len ++);
+ else
+ len = strlen(element->value.str);
+ memcpy(data + 2, element->value.str, data[1] = len);
+
+ return len + 2;
+ }
+
+ len = 2;
+ element = element->value.list;
+ while (element->type)
+ len += sdp_attr_write(data + len, element ++, uuid);
+ data[1] = len - 2;
+
+ return len;
+}
+
+static int sdp_attributeid_compare(const struct sdp_service_attribute_s *a,
+ const struct sdp_service_attribute_s *b)
+{
+ return (int) b->attribute_id - a->attribute_id;
+}
+
+static int sdp_uuid_compare(const int *a, const int *b)
+{
+ return *a - *b;
+}
+
+static void sdp_service_record_build(struct sdp_service_record_s *record,
+ struct sdp_def_service_s *def, int handle)
+{
+ int len = 0;
+ uint8_t *data;
+ int *uuid;
+
+ record->uuids = 0;
+ while (def->attributes[record->attributes].data.type) {
+ len += 3;
+ len += sdp_attr_max_size(&def->attributes[record->attributes ++].data,
+ &record->uuids);
+ }
+ record->uuids = pow2ceil(record->uuids);
+ record->attribute_list =
+ g_malloc0(record->attributes * sizeof(*record->attribute_list));
+ record->uuid =
+ g_malloc0(record->uuids * sizeof(*record->uuid));
+ data = g_malloc(len);
+
+ record->attributes = 0;
+ uuid = record->uuid;
+ while (def->attributes[record->attributes].data.type) {
+ record->attribute_list[record->attributes].pair = data;
+
+ len = 0;
+ data[len ++] = SDP_DTYPE_UINT | SDP_DSIZE_2;
+ data[len ++] = def->attributes[record->attributes].id >> 8;
+ data[len ++] = def->attributes[record->attributes].id & 0xff;
+ len += sdp_attr_write(data + len,
+ &def->attributes[record->attributes].data, &uuid);
+
+ /* Special case: assign a ServiceRecordHandle in sequence */
+ if (def->attributes[record->attributes].id == SDP_ATTR_RECORD_HANDLE)
+ def->attributes[record->attributes].data.value.uint = handle;
+ /* Note: we could also assign a ServiceDescription based on
+ * sdp->device.device->lmp_name. */
+
+ record->attribute_list[record->attributes ++].len = len;
+ data += len;
+ }
+
+ /* Sort the attribute list by the AttributeID */
+ qsort(record->attribute_list, record->attributes,
+ sizeof(*record->attribute_list),
+ (void *) sdp_attributeid_compare);
+ /* Sort the searchable UUIDs list for bisection */
+ qsort(record->uuid, record->uuids,
+ sizeof(*record->uuid),
+ (void *) sdp_uuid_compare);
+}
+
+static void sdp_service_db_build(struct bt_l2cap_sdp_state_s *sdp,
+ struct sdp_def_service_s **service)
+{
+ sdp->services = 0;
+ while (service[sdp->services])
+ sdp->services ++;
+ sdp->service_list =
+ g_malloc0(sdp->services * sizeof(*sdp->service_list));
+
+ sdp->services = 0;
+ while (*service) {
+ sdp_service_record_build(&sdp->service_list[sdp->services],
+ *service, sdp->services);
+ service ++;
+ sdp->services ++;
+ }
+}
+
+#define LAST { .type = 0 }
+#define SERVICE(name, attrs) \
+ static struct sdp_def_service_s glue(glue(sdp_service_, name), _s) = { \
+ .attributes = { attrs { .data = LAST } }, \
+ };
+#define ATTRIBUTE(attrid, val) { .id = glue(SDP_ATTR_, attrid), .data = val },
+#define UINT8(val) { \
+ .type = SDP_DTYPE_UINT | SDP_DSIZE_1, \
+ .value.uint = val, \
+ },
+#define UINT16(val) { \
+ .type = SDP_DTYPE_UINT | SDP_DSIZE_2, \
+ .value.uint = val, \
+ },
+#define UINT32(val) { \
+ .type = SDP_DTYPE_UINT | SDP_DSIZE_4, \
+ .value.uint = val, \
+ },
+#define UUID128(val) { \
+ .type = SDP_DTYPE_UUID | SDP_DSIZE_16, \
+ .value.uint = val, \
+ },
+#define SDP_TRUE { \
+ .type = SDP_DTYPE_BOOL | SDP_DSIZE_1, \
+ .value.uint = 1, \
+ },
+#define SDP_FALSE { \
+ .type = SDP_DTYPE_BOOL | SDP_DSIZE_1, \
+ .value.uint = 0, \
+ },
+#define STRING(val) { \
+ .type = SDP_DTYPE_STRING, \
+ .value.str = val, \
+ },
+#define ARRAY(...) { \
+ .type = SDP_DTYPE_STRING | SDP_DSIZE_2, \
+ .value.str = (char []) { __VA_ARGS__, 0, 0 }, \
+ },
+#define URL(val) { \
+ .type = SDP_DTYPE_URL, \
+ .value.str = val, \
+ },
+#if 1
+#define LIST(val) { \
+ .type = SDP_DTYPE_SEQ, \
+ .value.list = (struct sdp_def_data_element_s []) { val LAST }, \
+ },
+#endif
+
+/* Try to keep each single attribute below MAX_PDU_OUT_SIZE bytes
+ * in resulting SDP data representation size. */
+
+SERVICE(hid,
+ ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */
+ ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(HID_SVCLASS_ID)))
+ ATTRIBUTE(RECORD_STATE, UINT32(1))
+ ATTRIBUTE(PROTO_DESC_LIST, LIST(
+ LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_HID_CTRL))
+ LIST(UUID128(HIDP_UUID))
+ ))
+ ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002)))
+ ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST(
+ UINT16(0x656e) UINT16(0x006a) UINT16(0x0100)
+ ))
+ ATTRIBUTE(PFILE_DESC_LIST, LIST(
+ LIST(UUID128(HID_PROFILE_ID) UINT16(0x0100))
+ ))
+ ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html"))
+ ATTRIBUTE(SVCNAME_PRIMARY, STRING("QEMU Bluetooth HID"))
+ ATTRIBUTE(SVCDESC_PRIMARY, STRING("QEMU Keyboard/Mouse"))
+ ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU"))
+
+ /* Profile specific */
+ ATTRIBUTE(DEVICE_RELEASE_NUMBER, UINT16(0x0091)) /* Deprecated, remove */
+ ATTRIBUTE(PARSER_VERSION, UINT16(0x0111))
+ /* TODO: extract from l2cap_device->device.class[0] */
+ ATTRIBUTE(DEVICE_SUBCLASS, UINT8(0x40))
+ ATTRIBUTE(COUNTRY_CODE, UINT8(0x15))
+ ATTRIBUTE(VIRTUAL_CABLE, SDP_TRUE)
+ ATTRIBUTE(RECONNECT_INITIATE, SDP_FALSE)
+ /* TODO: extract from hid->usbdev->report_desc */
+ ATTRIBUTE(DESCRIPTOR_LIST, LIST(
+ LIST(UINT8(0x22) ARRAY(
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x06, /* Usage (Keyboard) */
+ 0xa1, 0x01, /* Collection (Application) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x95, 0x08, /* Report Count (8) */
+ 0x05, 0x07, /* Usage Page (Key Codes) */
+ 0x19, 0xe0, /* Usage Minimum (224) */
+ 0x29, 0xe7, /* Usage Maximum (231) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x81, 0x02, /* Input (Data, Variable, Absolute) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x81, 0x01, /* Input (Constant) */
+ 0x95, 0x05, /* Report Count (5) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x05, 0x08, /* Usage Page (LEDs) */
+ 0x19, 0x01, /* Usage Minimum (1) */
+ 0x29, 0x05, /* Usage Maximum (5) */
+ 0x91, 0x02, /* Output (Data, Variable, Absolute) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x03, /* Report Size (3) */
+ 0x91, 0x01, /* Output (Constant) */
+ 0x95, 0x06, /* Report Count (6) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0xff, /* Logical Maximum (255) */
+ 0x05, 0x07, /* Usage Page (Key Codes) */
+ 0x19, 0x00, /* Usage Minimum (0) */
+ 0x29, 0xff, /* Usage Maximum (255) */
+ 0x81, 0x00, /* Input (Data, Array) */
+ 0xc0 /* End Collection */
+ ))))
+ ATTRIBUTE(LANG_ID_BASE_LIST, LIST(
+ LIST(UINT16(0x0409) UINT16(0x0100))
+ ))
+ ATTRIBUTE(SDP_DISABLE, SDP_FALSE)
+ ATTRIBUTE(BATTERY_POWER, SDP_TRUE)
+ ATTRIBUTE(REMOTE_WAKEUP, SDP_TRUE)
+ ATTRIBUTE(BOOT_DEVICE, SDP_TRUE) /* XXX: untested */
+ ATTRIBUTE(SUPERVISION_TIMEOUT, UINT16(0x0c80))
+ ATTRIBUTE(NORMALLY_CONNECTABLE, SDP_TRUE)
+ ATTRIBUTE(PROFILE_VERSION, UINT16(0x0100))
+)
+
+SERVICE(sdp,
+ ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */
+ ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(SDP_SERVER_SVCLASS_ID)))
+ ATTRIBUTE(RECORD_STATE, UINT32(1))
+ ATTRIBUTE(PROTO_DESC_LIST, LIST(
+ LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_SDP))
+ LIST(UUID128(SDP_UUID))
+ ))
+ ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002)))
+ ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST(
+ UINT16(0x656e) UINT16(0x006a) UINT16(0x0100)
+ ))
+ ATTRIBUTE(PFILE_DESC_LIST, LIST(
+ LIST(UUID128(SDP_SERVER_PROFILE_ID) UINT16(0x0100))
+ ))
+ ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html"))
+ ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU"))
+
+ /* Profile specific */
+ ATTRIBUTE(VERSION_NUM_LIST, LIST(UINT16(0x0100)))
+ ATTRIBUTE(SVCDB_STATE , UINT32(1))
+)
+
+SERVICE(pnp,
+ ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */
+ ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(PNP_INFO_SVCLASS_ID)))
+ ATTRIBUTE(RECORD_STATE, UINT32(1))
+ ATTRIBUTE(PROTO_DESC_LIST, LIST(
+ LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_SDP))
+ LIST(UUID128(SDP_UUID))
+ ))
+ ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002)))
+ ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST(
+ UINT16(0x656e) UINT16(0x006a) UINT16(0x0100)
+ ))
+ ATTRIBUTE(PFILE_DESC_LIST, LIST(
+ LIST(UUID128(PNP_INFO_PROFILE_ID) UINT16(0x0100))
+ ))
+ ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html"))
+ ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU"))
+
+ /* Profile specific */
+ ATTRIBUTE(SPECIFICATION_ID, UINT16(0x0100))
+ ATTRIBUTE(VERSION, UINT16(0x0100))
+ ATTRIBUTE(PRIMARY_RECORD, SDP_TRUE)
+)
+
+static int bt_l2cap_sdp_new_ch(struct bt_l2cap_device_s *dev,
+ struct bt_l2cap_conn_params_s *params)
+{
+ struct bt_l2cap_sdp_state_s *sdp = g_malloc0(sizeof(*sdp));
+ struct sdp_def_service_s *services[] = {
+ &sdp_service_sdp_s,
+ &sdp_service_hid_s,
+ &sdp_service_pnp_s,
+ NULL,
+ };
+
+ sdp->channel = params;
+ sdp->channel->opaque = sdp;
+ sdp->channel->close = bt_l2cap_sdp_close_ch;
+ sdp->channel->sdu_in = bt_l2cap_sdp_sdu_in;
+
+ sdp_service_db_build(sdp, services);
+
+ return 0;
+}
+
+void bt_l2cap_sdp_init(struct bt_l2cap_device_s *dev)
+{
+ bt_l2cap_psm_register(dev, BT_PSM_SDP,
+ MAX_PDU_OUT_SIZE, bt_l2cap_sdp_new_ch);
+}
diff --git a/hw/char/Makefile.objs b/hw/char/Makefile.objs
new file mode 100644
index 00000000..5931cc84
--- /dev/null
+++ b/hw/char/Makefile.objs
@@ -0,0 +1,29 @@
+common-obj-$(CONFIG_IPACK) += ipoctal232.o
+common-obj-$(CONFIG_ESCC) += escc.o
+common-obj-$(CONFIG_PARALLEL) += parallel.o
+common-obj-$(CONFIG_PL011) += pl011.o
+common-obj-$(CONFIG_SERIAL) += serial.o serial-isa.o
+common-obj-$(CONFIG_SERIAL_PCI) += serial-pci.o
+common-obj-$(CONFIG_VIRTIO) += virtio-console.o
+common-obj-$(CONFIG_XILINX) += xilinx_uartlite.o
+common-obj-$(CONFIG_XEN_BACKEND) += xen_console.o
+common-obj-$(CONFIG_CADENCE) += cadence_uart.o
+
+obj-$(CONFIG_EXYNOS4) += exynos4210_uart.o
+obj-$(CONFIG_COLDFIRE) += mcf_uart.o
+obj-$(CONFIG_OMAP) += omap_uart.o
+obj-$(CONFIG_SH4) += sh_serial.o
+obj-$(CONFIG_PSERIES) += spapr_vty.o
+obj-$(CONFIG_DIGIC) += digic-uart.o
+obj-$(CONFIG_STM32F2XX_USART) += stm32f2xx_usart.o
+
+common-obj-$(CONFIG_ETRAXFS) += etraxfs_ser.o
+common-obj-$(CONFIG_ISA_DEBUG) += debugcon.o
+common-obj-$(CONFIG_GRLIB) += grlib_apbuart.o
+common-obj-$(CONFIG_IMX) += imx_serial.o
+common-obj-$(CONFIG_LM32) += lm32_juart.o
+common-obj-$(CONFIG_LM32) += lm32_uart.o
+common-obj-$(CONFIG_MILKYMIST) += milkymist-uart.o
+common-obj-$(CONFIG_SCLPCONSOLE) += sclpconsole.o sclpconsole-lm.o
+
+obj-$(CONFIG_VIRTIO) += virtio-serial-bus.o
diff --git a/hw/char/cadence_uart.c b/hw/char/cadence_uart.c
new file mode 100644
index 00000000..9d379e5b
--- /dev/null
+++ b/hw/char/cadence_uart.c
@@ -0,0 +1,536 @@
+/*
+ * Device model for Cadence UART
+ *
+ * Copyright (c) 2010 Xilinx Inc.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ * Written by Haibing Ma
+ * M.Habib
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/char/cadence_uart.h"
+
+#ifdef CADENCE_UART_ERR_DEBUG
+#define DB_PRINT(...) do { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } while (0);
+#else
+ #define DB_PRINT(...)
+#endif
+
+#define UART_SR_INTR_RTRIG 0x00000001
+#define UART_SR_INTR_REMPTY 0x00000002
+#define UART_SR_INTR_RFUL 0x00000004
+#define UART_SR_INTR_TEMPTY 0x00000008
+#define UART_SR_INTR_TFUL 0x00000010
+/* somewhat awkwardly, TTRIG is misaligned between SR and ISR */
+#define UART_SR_TTRIG 0x00002000
+#define UART_INTR_TTRIG 0x00000400
+/* bits fields in CSR that correlate to CISR. If any of these bits are set in
+ * SR, then the same bit in CISR is set high too */
+#define UART_SR_TO_CISR_MASK 0x0000001F
+
+#define UART_INTR_ROVR 0x00000020
+#define UART_INTR_FRAME 0x00000040
+#define UART_INTR_PARE 0x00000080
+#define UART_INTR_TIMEOUT 0x00000100
+#define UART_INTR_DMSI 0x00000200
+#define UART_INTR_TOVR 0x00001000
+
+#define UART_SR_RACTIVE 0x00000400
+#define UART_SR_TACTIVE 0x00000800
+#define UART_SR_FDELT 0x00001000
+
+#define UART_CR_RXRST 0x00000001
+#define UART_CR_TXRST 0x00000002
+#define UART_CR_RX_EN 0x00000004
+#define UART_CR_RX_DIS 0x00000008
+#define UART_CR_TX_EN 0x00000010
+#define UART_CR_TX_DIS 0x00000020
+#define UART_CR_RST_TO 0x00000040
+#define UART_CR_STARTBRK 0x00000080
+#define UART_CR_STOPBRK 0x00000100
+
+#define UART_MR_CLKS 0x00000001
+#define UART_MR_CHRL 0x00000006
+#define UART_MR_CHRL_SH 1
+#define UART_MR_PAR 0x00000038
+#define UART_MR_PAR_SH 3
+#define UART_MR_NBSTOP 0x000000C0
+#define UART_MR_NBSTOP_SH 6
+#define UART_MR_CHMODE 0x00000300
+#define UART_MR_CHMODE_SH 8
+#define UART_MR_UCLKEN 0x00000400
+#define UART_MR_IRMODE 0x00000800
+
+#define UART_DATA_BITS_6 (0x3 << UART_MR_CHRL_SH)
+#define UART_DATA_BITS_7 (0x2 << UART_MR_CHRL_SH)
+#define UART_PARITY_ODD (0x1 << UART_MR_PAR_SH)
+#define UART_PARITY_EVEN (0x0 << UART_MR_PAR_SH)
+#define UART_STOP_BITS_1 (0x3 << UART_MR_NBSTOP_SH)
+#define UART_STOP_BITS_2 (0x2 << UART_MR_NBSTOP_SH)
+#define NORMAL_MODE (0x0 << UART_MR_CHMODE_SH)
+#define ECHO_MODE (0x1 << UART_MR_CHMODE_SH)
+#define LOCAL_LOOPBACK (0x2 << UART_MR_CHMODE_SH)
+#define REMOTE_LOOPBACK (0x3 << UART_MR_CHMODE_SH)
+
+#define UART_INPUT_CLK 50000000
+
+#define R_CR (0x00/4)
+#define R_MR (0x04/4)
+#define R_IER (0x08/4)
+#define R_IDR (0x0C/4)
+#define R_IMR (0x10/4)
+#define R_CISR (0x14/4)
+#define R_BRGR (0x18/4)
+#define R_RTOR (0x1C/4)
+#define R_RTRIG (0x20/4)
+#define R_MCR (0x24/4)
+#define R_MSR (0x28/4)
+#define R_SR (0x2C/4)
+#define R_TX_RX (0x30/4)
+#define R_BDIV (0x34/4)
+#define R_FDEL (0x38/4)
+#define R_PMIN (0x3C/4)
+#define R_PWID (0x40/4)
+#define R_TTRIG (0x44/4)
+
+
+static void uart_update_status(CadenceUARTState *s)
+{
+ s->r[R_SR] = 0;
+
+ s->r[R_SR] |= s->rx_count == CADENCE_UART_RX_FIFO_SIZE ? UART_SR_INTR_RFUL
+ : 0;
+ s->r[R_SR] |= !s->rx_count ? UART_SR_INTR_REMPTY : 0;
+ s->r[R_SR] |= s->rx_count >= s->r[R_RTRIG] ? UART_SR_INTR_RTRIG : 0;
+
+ s->r[R_SR] |= s->tx_count == CADENCE_UART_TX_FIFO_SIZE ? UART_SR_INTR_TFUL
+ : 0;
+ s->r[R_SR] |= !s->tx_count ? UART_SR_INTR_TEMPTY : 0;
+ s->r[R_SR] |= s->tx_count >= s->r[R_TTRIG] ? UART_SR_TTRIG : 0;
+
+ s->r[R_CISR] |= s->r[R_SR] & UART_SR_TO_CISR_MASK;
+ s->r[R_CISR] |= s->r[R_SR] & UART_SR_TTRIG ? UART_INTR_TTRIG : 0;
+ qemu_set_irq(s->irq, !!(s->r[R_IMR] & s->r[R_CISR]));
+}
+
+static void fifo_trigger_update(void *opaque)
+{
+ CadenceUARTState *s = opaque;
+
+ s->r[R_CISR] |= UART_INTR_TIMEOUT;
+
+ uart_update_status(s);
+}
+
+static void uart_rx_reset(CadenceUARTState *s)
+{
+ s->rx_wpos = 0;
+ s->rx_count = 0;
+ if (s->chr) {
+ qemu_chr_accept_input(s->chr);
+ }
+}
+
+static void uart_tx_reset(CadenceUARTState *s)
+{
+ s->tx_count = 0;
+}
+
+static void uart_send_breaks(CadenceUARTState *s)
+{
+ int break_enabled = 1;
+
+ if (s->chr) {
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
+ &break_enabled);
+ }
+}
+
+static void uart_parameters_setup(CadenceUARTState *s)
+{
+ QEMUSerialSetParams ssp;
+ unsigned int baud_rate, packet_size;
+
+ baud_rate = (s->r[R_MR] & UART_MR_CLKS) ?
+ UART_INPUT_CLK / 8 : UART_INPUT_CLK;
+
+ ssp.speed = baud_rate / (s->r[R_BRGR] * (s->r[R_BDIV] + 1));
+ packet_size = 1;
+
+ switch (s->r[R_MR] & UART_MR_PAR) {
+ case UART_PARITY_EVEN:
+ ssp.parity = 'E';
+ packet_size++;
+ break;
+ case UART_PARITY_ODD:
+ ssp.parity = 'O';
+ packet_size++;
+ break;
+ default:
+ ssp.parity = 'N';
+ break;
+ }
+
+ switch (s->r[R_MR] & UART_MR_CHRL) {
+ case UART_DATA_BITS_6:
+ ssp.data_bits = 6;
+ break;
+ case UART_DATA_BITS_7:
+ ssp.data_bits = 7;
+ break;
+ default:
+ ssp.data_bits = 8;
+ break;
+ }
+
+ switch (s->r[R_MR] & UART_MR_NBSTOP) {
+ case UART_STOP_BITS_1:
+ ssp.stop_bits = 1;
+ break;
+ default:
+ ssp.stop_bits = 2;
+ break;
+ }
+
+ packet_size += ssp.data_bits + ssp.stop_bits;
+ s->char_tx_time = (get_ticks_per_sec() / ssp.speed) * packet_size;
+ if (s->chr) {
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+ }
+}
+
+static int uart_can_receive(void *opaque)
+{
+ CadenceUARTState *s = opaque;
+ int ret = MAX(CADENCE_UART_RX_FIFO_SIZE, CADENCE_UART_TX_FIFO_SIZE);
+ uint32_t ch_mode = s->r[R_MR] & UART_MR_CHMODE;
+
+ if (ch_mode == NORMAL_MODE || ch_mode == ECHO_MODE) {
+ ret = MIN(ret, CADENCE_UART_RX_FIFO_SIZE - s->rx_count);
+ }
+ if (ch_mode == REMOTE_LOOPBACK || ch_mode == ECHO_MODE) {
+ ret = MIN(ret, CADENCE_UART_TX_FIFO_SIZE - s->tx_count);
+ }
+ return ret;
+}
+
+static void uart_ctrl_update(CadenceUARTState *s)
+{
+ if (s->r[R_CR] & UART_CR_TXRST) {
+ uart_tx_reset(s);
+ }
+
+ if (s->r[R_CR] & UART_CR_RXRST) {
+ uart_rx_reset(s);
+ }
+
+ s->r[R_CR] &= ~(UART_CR_TXRST | UART_CR_RXRST);
+
+ if (s->r[R_CR] & UART_CR_STARTBRK && !(s->r[R_CR] & UART_CR_STOPBRK)) {
+ uart_send_breaks(s);
+ }
+}
+
+static void uart_write_rx_fifo(void *opaque, const uint8_t *buf, int size)
+{
+ CadenceUARTState *s = opaque;
+ uint64_t new_rx_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ int i;
+
+ if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) {
+ return;
+ }
+
+ if (s->rx_count == CADENCE_UART_RX_FIFO_SIZE) {
+ s->r[R_CISR] |= UART_INTR_ROVR;
+ } else {
+ for (i = 0; i < size; i++) {
+ s->rx_fifo[s->rx_wpos] = buf[i];
+ s->rx_wpos = (s->rx_wpos + 1) % CADENCE_UART_RX_FIFO_SIZE;
+ s->rx_count++;
+ }
+ timer_mod(s->fifo_trigger_handle, new_rx_time +
+ (s->char_tx_time * 4));
+ }
+ uart_update_status(s);
+}
+
+static gboolean cadence_uart_xmit(GIOChannel *chan, GIOCondition cond,
+ void *opaque)
+{
+ CadenceUARTState *s = opaque;
+ int ret;
+
+ /* instant drain the fifo when there's no back-end */
+ if (!s->chr) {
+ s->tx_count = 0;
+ return FALSE;
+ }
+
+ if (!s->tx_count) {
+ return FALSE;
+ }
+
+ ret = qemu_chr_fe_write(s->chr, s->tx_fifo, s->tx_count);
+ s->tx_count -= ret;
+ memmove(s->tx_fifo, s->tx_fifo + ret, s->tx_count);
+
+ if (s->tx_count) {
+ int r = qemu_chr_fe_add_watch(s->chr, G_IO_OUT|G_IO_HUP,
+ cadence_uart_xmit, s);
+ assert(r);
+ }
+
+ uart_update_status(s);
+ return FALSE;
+}
+
+static void uart_write_tx_fifo(CadenceUARTState *s, const uint8_t *buf,
+ int size)
+{
+ if ((s->r[R_CR] & UART_CR_TX_DIS) || !(s->r[R_CR] & UART_CR_TX_EN)) {
+ return;
+ }
+
+ if (size > CADENCE_UART_TX_FIFO_SIZE - s->tx_count) {
+ size = CADENCE_UART_TX_FIFO_SIZE - s->tx_count;
+ /*
+ * This can only be a guest error via a bad tx fifo register push,
+ * as can_receive() should stop remote loop and echo modes ever getting
+ * us to here.
+ */
+ qemu_log_mask(LOG_GUEST_ERROR, "cadence_uart: TxFIFO overflow");
+ s->r[R_CISR] |= UART_INTR_ROVR;
+ }
+
+ memcpy(s->tx_fifo + s->tx_count, buf, size);
+ s->tx_count += size;
+
+ cadence_uart_xmit(NULL, G_IO_OUT, s);
+}
+
+static void uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ CadenceUARTState *s = opaque;
+ uint32_t ch_mode = s->r[R_MR] & UART_MR_CHMODE;
+
+ if (ch_mode == NORMAL_MODE || ch_mode == ECHO_MODE) {
+ uart_write_rx_fifo(opaque, buf, size);
+ }
+ if (ch_mode == REMOTE_LOOPBACK || ch_mode == ECHO_MODE) {
+ uart_write_tx_fifo(s, buf, size);
+ }
+}
+
+static void uart_event(void *opaque, int event)
+{
+ CadenceUARTState *s = opaque;
+ uint8_t buf = '\0';
+
+ if (event == CHR_EVENT_BREAK) {
+ uart_write_rx_fifo(opaque, &buf, 1);
+ }
+
+ uart_update_status(s);
+}
+
+static void uart_read_rx_fifo(CadenceUARTState *s, uint32_t *c)
+{
+ if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) {
+ return;
+ }
+
+ if (s->rx_count) {
+ uint32_t rx_rpos = (CADENCE_UART_RX_FIFO_SIZE + s->rx_wpos -
+ s->rx_count) % CADENCE_UART_RX_FIFO_SIZE;
+ *c = s->rx_fifo[rx_rpos];
+ s->rx_count--;
+
+ if (s->chr) {
+ qemu_chr_accept_input(s->chr);
+ }
+ } else {
+ *c = 0;
+ }
+
+ uart_update_status(s);
+}
+
+static void uart_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ CadenceUARTState *s = opaque;
+
+ DB_PRINT(" offset:%x data:%08x\n", (unsigned)offset, (unsigned)value);
+ offset >>= 2;
+ switch (offset) {
+ case R_IER: /* ier (wts imr) */
+ s->r[R_IMR] |= value;
+ break;
+ case R_IDR: /* idr (wtc imr) */
+ s->r[R_IMR] &= ~value;
+ break;
+ case R_IMR: /* imr (read only) */
+ break;
+ case R_CISR: /* cisr (wtc) */
+ s->r[R_CISR] &= ~value;
+ break;
+ case R_TX_RX: /* UARTDR */
+ switch (s->r[R_MR] & UART_MR_CHMODE) {
+ case NORMAL_MODE:
+ uart_write_tx_fifo(s, (uint8_t *) &value, 1);
+ break;
+ case LOCAL_LOOPBACK:
+ uart_write_rx_fifo(opaque, (uint8_t *) &value, 1);
+ break;
+ }
+ break;
+ default:
+ s->r[offset] = value;
+ }
+
+ switch (offset) {
+ case R_CR:
+ uart_ctrl_update(s);
+ break;
+ case R_MR:
+ uart_parameters_setup(s);
+ break;
+ }
+ uart_update_status(s);
+}
+
+static uint64_t uart_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ CadenceUARTState *s = opaque;
+ uint32_t c = 0;
+
+ offset >>= 2;
+ if (offset >= CADENCE_UART_R_MAX) {
+ c = 0;
+ } else if (offset == R_TX_RX) {
+ uart_read_rx_fifo(s, &c);
+ } else {
+ c = s->r[offset];
+ }
+
+ DB_PRINT(" offset:%x data:%08x\n", (unsigned)(offset << 2), (unsigned)c);
+ return c;
+}
+
+static const MemoryRegionOps uart_ops = {
+ .read = uart_read,
+ .write = uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void cadence_uart_reset(DeviceState *dev)
+{
+ CadenceUARTState *s = CADENCE_UART(dev);
+
+ s->r[R_CR] = 0x00000128;
+ s->r[R_IMR] = 0;
+ s->r[R_CISR] = 0;
+ s->r[R_RTRIG] = 0x00000020;
+ s->r[R_BRGR] = 0x0000000F;
+ s->r[R_TTRIG] = 0x00000020;
+
+ uart_rx_reset(s);
+ uart_tx_reset(s);
+
+ uart_update_status(s);
+}
+
+static void cadence_uart_realize(DeviceState *dev, Error **errp)
+{
+ CadenceUARTState *s = CADENCE_UART(dev);
+
+ s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ fifo_trigger_update, s);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, uart_can_receive, uart_receive,
+ uart_event, s);
+ }
+}
+
+static void cadence_uart_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ CadenceUARTState *s = CADENCE_UART(obj);
+
+ memory_region_init_io(&s->iomem, obj, &uart_ops, s, "uart", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->char_tx_time = (get_ticks_per_sec() / 9600) * 10;
+}
+
+static int cadence_uart_post_load(void *opaque, int version_id)
+{
+ CadenceUARTState *s = opaque;
+
+ uart_parameters_setup(s);
+ uart_update_status(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_cadence_uart = {
+ .name = "cadence_uart",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = cadence_uart_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(r, CadenceUARTState, CADENCE_UART_R_MAX),
+ VMSTATE_UINT8_ARRAY(rx_fifo, CadenceUARTState,
+ CADENCE_UART_RX_FIFO_SIZE),
+ VMSTATE_UINT8_ARRAY(tx_fifo, CadenceUARTState,
+ CADENCE_UART_TX_FIFO_SIZE),
+ VMSTATE_UINT32(rx_count, CadenceUARTState),
+ VMSTATE_UINT32(tx_count, CadenceUARTState),
+ VMSTATE_UINT32(rx_wpos, CadenceUARTState),
+ VMSTATE_TIMER_PTR(fifo_trigger_handle, CadenceUARTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cadence_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = cadence_uart_realize;
+ dc->vmsd = &vmstate_cadence_uart;
+ dc->reset = cadence_uart_reset;
+ /* Reason: realize() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo cadence_uart_info = {
+ .name = TYPE_CADENCE_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CadenceUARTState),
+ .instance_init = cadence_uart_init,
+ .class_init = cadence_uart_class_init,
+};
+
+static void cadence_uart_register_types(void)
+{
+ type_register_static(&cadence_uart_info);
+}
+
+type_init(cadence_uart_register_types)
diff --git a/hw/char/debugcon.c b/hw/char/debugcon.c
new file mode 100644
index 00000000..36f1c4ad
--- /dev/null
+++ b/hw/char/debugcon.c
@@ -0,0 +1,140 @@
+/*
+ * QEMU Bochs-style debug console ("port E9") emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ * Copyright (c) Intel Corporation; author: H. Peter Anvin
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/char.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+
+#define TYPE_ISA_DEBUGCON_DEVICE "isa-debugcon"
+#define ISA_DEBUGCON_DEVICE(obj) \
+ OBJECT_CHECK(ISADebugconState, (obj), TYPE_ISA_DEBUGCON_DEVICE)
+
+//#define DEBUG_DEBUGCON
+
+typedef struct DebugconState {
+ MemoryRegion io;
+ CharDriverState *chr;
+ uint32_t readback;
+} DebugconState;
+
+typedef struct ISADebugconState {
+ ISADevice parent_obj;
+
+ uint32_t iobase;
+ DebugconState state;
+} ISADebugconState;
+
+static void debugcon_ioport_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ DebugconState *s = opaque;
+ unsigned char ch = val;
+
+#ifdef DEBUG_DEBUGCON
+ printf(" [debugcon: write addr=0x%04" HWADDR_PRIx " val=0x%02" PRIx64 "]\n", addr, val);
+#endif
+
+ qemu_chr_fe_write(s->chr, &ch, 1);
+}
+
+
+static uint64_t debugcon_ioport_read(void *opaque, hwaddr addr, unsigned width)
+{
+ DebugconState *s = opaque;
+
+#ifdef DEBUG_DEBUGCON
+ printf("debugcon: read addr=0x%04" HWADDR_PRIx "\n", addr);
+#endif
+
+ return s->readback;
+}
+
+static const MemoryRegionOps debugcon_ops = {
+ .read = debugcon_ioport_read,
+ .write = debugcon_ioport_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void debugcon_realize_core(DebugconState *s, Error **errp)
+{
+ if (!s->chr) {
+ error_setg(errp, "Can't create debugcon device, empty char device");
+ return;
+ }
+
+ qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, s);
+}
+
+static void debugcon_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+ ISADebugconState *isa = ISA_DEBUGCON_DEVICE(dev);
+ DebugconState *s = &isa->state;
+ Error *err = NULL;
+
+ debugcon_realize_core(s, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ memory_region_init_io(&s->io, OBJECT(dev), &debugcon_ops, s,
+ TYPE_ISA_DEBUGCON_DEVICE, 1);
+ memory_region_add_subregion(isa_address_space_io(d),
+ isa->iobase, &s->io);
+}
+
+static Property debugcon_isa_properties[] = {
+ DEFINE_PROP_UINT32("iobase", ISADebugconState, iobase, 0xe9),
+ DEFINE_PROP_CHR("chardev", ISADebugconState, state.chr),
+ DEFINE_PROP_UINT32("readback", ISADebugconState, state.readback, 0xe9),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void debugcon_isa_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = debugcon_isa_realizefn;
+ dc->props = debugcon_isa_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo debugcon_isa_info = {
+ .name = TYPE_ISA_DEBUGCON_DEVICE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISADebugconState),
+ .class_init = debugcon_isa_class_initfn,
+};
+
+static void debugcon_register_types(void)
+{
+ type_register_static(&debugcon_isa_info);
+}
+
+type_init(debugcon_register_types)
diff --git a/hw/char/digic-uart.c b/hw/char/digic-uart.c
new file mode 100644
index 00000000..6d44576f
--- /dev/null
+++ b/hw/char/digic-uart.c
@@ -0,0 +1,197 @@
+/*
+ * QEMU model of the Canon DIGIC UART block.
+ *
+ * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com>
+ *
+ * This model is based on reverse engineering efforts
+ * made by CHDK (http://chdk.wikia.com) and
+ * Magic Lantern (http://www.magiclantern.fm) projects
+ * contributors.
+ *
+ * See "Serial terminal" docs here:
+ * http://magiclantern.wikia.com/wiki/Register_Map#Misc_Registers
+ *
+ * The QEMU model of the Milkymist UART block by Michael Walle
+ * is used as a template.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+
+#include "hw/char/digic-uart.h"
+
+enum {
+ ST_RX_RDY = (1 << 0),
+ ST_TX_RDY = (1 << 1),
+};
+
+static uint64_t digic_uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ DigicUartState *s = opaque;
+ uint64_t ret = 0;
+
+ addr >>= 2;
+
+ switch (addr) {
+ case R_RX:
+ s->reg_st &= ~(ST_RX_RDY);
+ ret = s->reg_rx;
+ break;
+
+ case R_ST:
+ ret = s->reg_st;
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "digic-uart: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ }
+
+ return ret;
+}
+
+static void digic_uart_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ DigicUartState *s = opaque;
+ unsigned char ch = value;
+
+ addr >>= 2;
+
+ switch (addr) {
+ case R_TX:
+ if (s->chr) {
+ qemu_chr_fe_write_all(s->chr, &ch, 1);
+ }
+ break;
+
+ case R_ST:
+ /*
+ * Ignore write to R_ST.
+ *
+ * The point is that this register is actively used
+ * during receiving and transmitting symbols,
+ * but we don't know the function of most of bits.
+ *
+ * Ignoring writes to R_ST is only a simplification
+ * of the model. It has no perceptible side effects
+ * for existing guests.
+ */
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "digic-uart: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ }
+}
+
+static const MemoryRegionOps uart_mmio_ops = {
+ .read = digic_uart_read,
+ .write = digic_uart_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int uart_can_rx(void *opaque)
+{
+ DigicUartState *s = opaque;
+
+ return !(s->reg_st & ST_RX_RDY);
+}
+
+static void uart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ DigicUartState *s = opaque;
+
+ assert(uart_can_rx(opaque));
+
+ s->reg_st |= ST_RX_RDY;
+ s->reg_rx = *buf;
+}
+
+static void uart_event(void *opaque, int event)
+{
+}
+
+static void digic_uart_reset(DeviceState *d)
+{
+ DigicUartState *s = DIGIC_UART(d);
+
+ s->reg_rx = 0;
+ s->reg_st = ST_TX_RDY;
+}
+
+static void digic_uart_realize(DeviceState *dev, Error **errp)
+{
+ DigicUartState *s = DIGIC_UART(dev);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s);
+ }
+}
+
+static void digic_uart_init(Object *obj)
+{
+ DigicUartState *s = DIGIC_UART(obj);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &uart_mmio_ops, s,
+ TYPE_DIGIC_UART, 0x18);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->regs_region);
+}
+
+static const VMStateDescription vmstate_digic_uart = {
+ .name = "digic-uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg_rx, DigicUartState),
+ VMSTATE_UINT32(reg_st, DigicUartState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void digic_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = digic_uart_realize;
+ dc->reset = digic_uart_reset;
+ dc->vmsd = &vmstate_digic_uart;
+ /* Reason: realize() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo digic_uart_info = {
+ .name = TYPE_DIGIC_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(DigicUartState),
+ .instance_init = digic_uart_init,
+ .class_init = digic_uart_class_init,
+};
+
+static void digic_uart_register_types(void)
+{
+ type_register_static(&digic_uart_info);
+}
+
+type_init(digic_uart_register_types)
diff --git a/hw/char/escc.c b/hw/char/escc.c
new file mode 100644
index 00000000..ba653efd
--- /dev/null
+++ b/hw/char/escc.c
@@ -0,0 +1,1052 @@
+/*
+ * QEMU ESCC (Z8030/Z8530/Z85C30/SCC/ESCC) serial port emulation
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/char/escc.h"
+#include "sysemu/char.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "trace.h"
+
+/*
+ * Chipset docs:
+ * "Z80C30/Z85C30/Z80230/Z85230/Z85233 SCC/ESCC User Manual",
+ * http://www.zilog.com/docs/serial/scc_escc_um.pdf
+ *
+ * On Sparc32 this is the serial port, mouse and keyboard part of chip STP2001
+ * (Slave I/O), also produced as NCR89C105. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
+ *
+ * The serial ports implement full AMD AM8530 or Zilog Z8530 chips,
+ * mouse and keyboard ports don't implement all functions and they are
+ * only asynchronous. There is no DMA.
+ *
+ * Z85C30 is also used on PowerMacs. There are some small differences
+ * between Sparc version (sunzilog) and PowerMac (pmac):
+ * Offset between control and data registers
+ * There is some kind of lockup bug, but we can ignore it
+ * CTS is inverted
+ * DMA on pmac using DBDMA chip
+ * pmac can do IRDA and faster rates, sunzilog can only do 38400
+ * pmac baud rate generator clock is 3.6864 MHz, sunzilog 4.9152 MHz
+ */
+
+/*
+ * Modifications:
+ * 2006-Aug-10 Igor Kovalenko : Renamed KBDQueue to SERIOQueue, implemented
+ * serial mouse queue.
+ * Implemented serial mouse protocol.
+ *
+ * 2010-May-23 Artyom Tarasenko: Reworked IUS logic
+ */
+
+typedef enum {
+ chn_a, chn_b,
+} ChnID;
+
+#define CHN_C(s) ((s)->chn == chn_b? 'b' : 'a')
+
+typedef enum {
+ ser, kbd, mouse,
+} ChnType;
+
+#define SERIO_QUEUE_SIZE 256
+
+typedef struct {
+ uint8_t data[SERIO_QUEUE_SIZE];
+ int rptr, wptr, count;
+} SERIOQueue;
+
+#define SERIAL_REGS 16
+typedef struct ChannelState {
+ qemu_irq irq;
+ uint32_t rxint, txint, rxint_under_svc, txint_under_svc;
+ struct ChannelState *otherchn;
+ uint32_t reg;
+ uint8_t wregs[SERIAL_REGS], rregs[SERIAL_REGS];
+ SERIOQueue queue;
+ CharDriverState *chr;
+ int e0_mode, led_mode, caps_lock_mode, num_lock_mode;
+ int disabled;
+ int clock;
+ uint32_t vmstate_dummy;
+ ChnID chn; // this channel, A (base+4) or B (base+0)
+ ChnType type;
+ uint8_t rx, tx;
+ QemuInputHandlerState *hs;
+} ChannelState;
+
+#define ESCC(obj) OBJECT_CHECK(ESCCState, (obj), TYPE_ESCC)
+
+typedef struct ESCCState {
+ SysBusDevice parent_obj;
+
+ struct ChannelState chn[2];
+ uint32_t it_shift;
+ MemoryRegion mmio;
+ uint32_t disabled;
+ uint32_t frequency;
+} ESCCState;
+
+#define SERIAL_CTRL 0
+#define SERIAL_DATA 1
+
+#define W_CMD 0
+#define CMD_PTR_MASK 0x07
+#define CMD_CMD_MASK 0x38
+#define CMD_HI 0x08
+#define CMD_CLR_TXINT 0x28
+#define CMD_CLR_IUS 0x38
+#define W_INTR 1
+#define INTR_INTALL 0x01
+#define INTR_TXINT 0x02
+#define INTR_RXMODEMSK 0x18
+#define INTR_RXINT1ST 0x08
+#define INTR_RXINTALL 0x10
+#define W_IVEC 2
+#define W_RXCTRL 3
+#define RXCTRL_RXEN 0x01
+#define W_TXCTRL1 4
+#define TXCTRL1_PAREN 0x01
+#define TXCTRL1_PAREV 0x02
+#define TXCTRL1_1STOP 0x04
+#define TXCTRL1_1HSTOP 0x08
+#define TXCTRL1_2STOP 0x0c
+#define TXCTRL1_STPMSK 0x0c
+#define TXCTRL1_CLK1X 0x00
+#define TXCTRL1_CLK16X 0x40
+#define TXCTRL1_CLK32X 0x80
+#define TXCTRL1_CLK64X 0xc0
+#define TXCTRL1_CLKMSK 0xc0
+#define W_TXCTRL2 5
+#define TXCTRL2_TXEN 0x08
+#define TXCTRL2_BITMSK 0x60
+#define TXCTRL2_5BITS 0x00
+#define TXCTRL2_7BITS 0x20
+#define TXCTRL2_6BITS 0x40
+#define TXCTRL2_8BITS 0x60
+#define W_SYNC1 6
+#define W_SYNC2 7
+#define W_TXBUF 8
+#define W_MINTR 9
+#define MINTR_STATUSHI 0x10
+#define MINTR_RST_MASK 0xc0
+#define MINTR_RST_B 0x40
+#define MINTR_RST_A 0x80
+#define MINTR_RST_ALL 0xc0
+#define W_MISC1 10
+#define W_CLOCK 11
+#define CLOCK_TRXC 0x08
+#define W_BRGLO 12
+#define W_BRGHI 13
+#define W_MISC2 14
+#define MISC2_PLLDIS 0x30
+#define W_EXTINT 15
+#define EXTINT_DCD 0x08
+#define EXTINT_SYNCINT 0x10
+#define EXTINT_CTSINT 0x20
+#define EXTINT_TXUNDRN 0x40
+#define EXTINT_BRKINT 0x80
+
+#define R_STATUS 0
+#define STATUS_RXAV 0x01
+#define STATUS_ZERO 0x02
+#define STATUS_TXEMPTY 0x04
+#define STATUS_DCD 0x08
+#define STATUS_SYNC 0x10
+#define STATUS_CTS 0x20
+#define STATUS_TXUNDRN 0x40
+#define STATUS_BRK 0x80
+#define R_SPEC 1
+#define SPEC_ALLSENT 0x01
+#define SPEC_BITS8 0x06
+#define R_IVEC 2
+#define IVEC_TXINTB 0x00
+#define IVEC_LONOINT 0x06
+#define IVEC_LORXINTA 0x0c
+#define IVEC_LORXINTB 0x04
+#define IVEC_LOTXINTA 0x08
+#define IVEC_HINOINT 0x60
+#define IVEC_HIRXINTA 0x30
+#define IVEC_HIRXINTB 0x20
+#define IVEC_HITXINTA 0x10
+#define R_INTR 3
+#define INTR_EXTINTB 0x01
+#define INTR_TXINTB 0x02
+#define INTR_RXINTB 0x04
+#define INTR_EXTINTA 0x08
+#define INTR_TXINTA 0x10
+#define INTR_RXINTA 0x20
+#define R_IPEN 4
+#define R_TXCTRL1 5
+#define R_TXCTRL2 6
+#define R_BC 7
+#define R_RXBUF 8
+#define R_RXCTRL 9
+#define R_MISC 10
+#define R_MISC1 11
+#define R_BRGLO 12
+#define R_BRGHI 13
+#define R_MISC1I 14
+#define R_EXTINT 15
+
+static void handle_kbd_command(ChannelState *s, int val);
+static int serial_can_receive(void *opaque);
+static void serial_receive_byte(ChannelState *s, int ch);
+
+static void clear_queue(void *opaque)
+{
+ ChannelState *s = opaque;
+ SERIOQueue *q = &s->queue;
+ q->rptr = q->wptr = q->count = 0;
+}
+
+static void put_queue(void *opaque, int b)
+{
+ ChannelState *s = opaque;
+ SERIOQueue *q = &s->queue;
+
+ trace_escc_put_queue(CHN_C(s), b);
+ if (q->count >= SERIO_QUEUE_SIZE)
+ return;
+ q->data[q->wptr] = b;
+ if (++q->wptr == SERIO_QUEUE_SIZE)
+ q->wptr = 0;
+ q->count++;
+ serial_receive_byte(s, 0);
+}
+
+static uint32_t get_queue(void *opaque)
+{
+ ChannelState *s = opaque;
+ SERIOQueue *q = &s->queue;
+ int val;
+
+ if (q->count == 0) {
+ return 0;
+ } else {
+ val = q->data[q->rptr];
+ if (++q->rptr == SERIO_QUEUE_SIZE)
+ q->rptr = 0;
+ q->count--;
+ }
+ trace_escc_get_queue(CHN_C(s), val);
+ if (q->count > 0)
+ serial_receive_byte(s, 0);
+ return val;
+}
+
+static int escc_update_irq_chn(ChannelState *s)
+{
+ if ((((s->wregs[W_INTR] & INTR_TXINT) && (s->txint == 1)) ||
+ // tx ints enabled, pending
+ ((((s->wregs[W_INTR] & INTR_RXMODEMSK) == INTR_RXINT1ST) ||
+ ((s->wregs[W_INTR] & INTR_RXMODEMSK) == INTR_RXINTALL)) &&
+ s->rxint == 1) || // rx ints enabled, pending
+ ((s->wregs[W_EXTINT] & EXTINT_BRKINT) &&
+ (s->rregs[R_STATUS] & STATUS_BRK)))) { // break int e&p
+ return 1;
+ }
+ return 0;
+}
+
+static void escc_update_irq(ChannelState *s)
+{
+ int irq;
+
+ irq = escc_update_irq_chn(s);
+ irq |= escc_update_irq_chn(s->otherchn);
+
+ trace_escc_update_irq(irq);
+ qemu_set_irq(s->irq, irq);
+}
+
+static void escc_reset_chn(ChannelState *s)
+{
+ int i;
+
+ s->reg = 0;
+ for (i = 0; i < SERIAL_REGS; i++) {
+ s->rregs[i] = 0;
+ s->wregs[i] = 0;
+ }
+ s->wregs[W_TXCTRL1] = TXCTRL1_1STOP; // 1X divisor, 1 stop bit, no parity
+ s->wregs[W_MINTR] = MINTR_RST_ALL;
+ s->wregs[W_CLOCK] = CLOCK_TRXC; // Synch mode tx clock = TRxC
+ s->wregs[W_MISC2] = MISC2_PLLDIS; // PLL disabled
+ s->wregs[W_EXTINT] = EXTINT_DCD | EXTINT_SYNCINT | EXTINT_CTSINT |
+ EXTINT_TXUNDRN | EXTINT_BRKINT; // Enable most interrupts
+ if (s->disabled)
+ s->rregs[R_STATUS] = STATUS_TXEMPTY | STATUS_DCD | STATUS_SYNC |
+ STATUS_CTS | STATUS_TXUNDRN;
+ else
+ s->rregs[R_STATUS] = STATUS_TXEMPTY | STATUS_TXUNDRN;
+ s->rregs[R_SPEC] = SPEC_BITS8 | SPEC_ALLSENT;
+
+ s->rx = s->tx = 0;
+ s->rxint = s->txint = 0;
+ s->rxint_under_svc = s->txint_under_svc = 0;
+ s->e0_mode = s->led_mode = s->caps_lock_mode = s->num_lock_mode = 0;
+ clear_queue(s);
+}
+
+static void escc_reset(DeviceState *d)
+{
+ ESCCState *s = ESCC(d);
+
+ escc_reset_chn(&s->chn[0]);
+ escc_reset_chn(&s->chn[1]);
+}
+
+static inline void set_rxint(ChannelState *s)
+{
+ s->rxint = 1;
+ /* XXX: missing daisy chainnig: chn_b rx should have a lower priority
+ than chn_a rx/tx/special_condition service*/
+ s->rxint_under_svc = 1;
+ if (s->chn == chn_a) {
+ s->rregs[R_INTR] |= INTR_RXINTA;
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->otherchn->rregs[R_IVEC] = IVEC_HIRXINTA;
+ else
+ s->otherchn->rregs[R_IVEC] = IVEC_LORXINTA;
+ } else {
+ s->otherchn->rregs[R_INTR] |= INTR_RXINTB;
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->rregs[R_IVEC] = IVEC_HIRXINTB;
+ else
+ s->rregs[R_IVEC] = IVEC_LORXINTB;
+ }
+ escc_update_irq(s);
+}
+
+static inline void set_txint(ChannelState *s)
+{
+ s->txint = 1;
+ if (!s->rxint_under_svc) {
+ s->txint_under_svc = 1;
+ if (s->chn == chn_a) {
+ if (s->wregs[W_INTR] & INTR_TXINT) {
+ s->rregs[R_INTR] |= INTR_TXINTA;
+ }
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->otherchn->rregs[R_IVEC] = IVEC_HITXINTA;
+ else
+ s->otherchn->rregs[R_IVEC] = IVEC_LOTXINTA;
+ } else {
+ s->rregs[R_IVEC] = IVEC_TXINTB;
+ if (s->wregs[W_INTR] & INTR_TXINT) {
+ s->otherchn->rregs[R_INTR] |= INTR_TXINTB;
+ }
+ }
+ escc_update_irq(s);
+ }
+}
+
+static inline void clr_rxint(ChannelState *s)
+{
+ s->rxint = 0;
+ s->rxint_under_svc = 0;
+ if (s->chn == chn_a) {
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->otherchn->rregs[R_IVEC] = IVEC_HINOINT;
+ else
+ s->otherchn->rregs[R_IVEC] = IVEC_LONOINT;
+ s->rregs[R_INTR] &= ~INTR_RXINTA;
+ } else {
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->rregs[R_IVEC] = IVEC_HINOINT;
+ else
+ s->rregs[R_IVEC] = IVEC_LONOINT;
+ s->otherchn->rregs[R_INTR] &= ~INTR_RXINTB;
+ }
+ if (s->txint)
+ set_txint(s);
+ escc_update_irq(s);
+}
+
+static inline void clr_txint(ChannelState *s)
+{
+ s->txint = 0;
+ s->txint_under_svc = 0;
+ if (s->chn == chn_a) {
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->otherchn->rregs[R_IVEC] = IVEC_HINOINT;
+ else
+ s->otherchn->rregs[R_IVEC] = IVEC_LONOINT;
+ s->rregs[R_INTR] &= ~INTR_TXINTA;
+ } else {
+ s->otherchn->rregs[R_INTR] &= ~INTR_TXINTB;
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->rregs[R_IVEC] = IVEC_HINOINT;
+ else
+ s->rregs[R_IVEC] = IVEC_LONOINT;
+ s->otherchn->rregs[R_INTR] &= ~INTR_TXINTB;
+ }
+ if (s->rxint)
+ set_rxint(s);
+ escc_update_irq(s);
+}
+
+static void escc_update_parameters(ChannelState *s)
+{
+ int speed, parity, data_bits, stop_bits;
+ QEMUSerialSetParams ssp;
+
+ if (!s->chr || s->type != ser)
+ return;
+
+ if (s->wregs[W_TXCTRL1] & TXCTRL1_PAREN) {
+ if (s->wregs[W_TXCTRL1] & TXCTRL1_PAREV)
+ parity = 'E';
+ else
+ parity = 'O';
+ } else {
+ parity = 'N';
+ }
+ if ((s->wregs[W_TXCTRL1] & TXCTRL1_STPMSK) == TXCTRL1_2STOP)
+ stop_bits = 2;
+ else
+ stop_bits = 1;
+ switch (s->wregs[W_TXCTRL2] & TXCTRL2_BITMSK) {
+ case TXCTRL2_5BITS:
+ data_bits = 5;
+ break;
+ case TXCTRL2_7BITS:
+ data_bits = 7;
+ break;
+ case TXCTRL2_6BITS:
+ data_bits = 6;
+ break;
+ default:
+ case TXCTRL2_8BITS:
+ data_bits = 8;
+ break;
+ }
+ speed = s->clock / ((s->wregs[W_BRGLO] | (s->wregs[W_BRGHI] << 8)) + 2);
+ switch (s->wregs[W_TXCTRL1] & TXCTRL1_CLKMSK) {
+ case TXCTRL1_CLK1X:
+ break;
+ case TXCTRL1_CLK16X:
+ speed /= 16;
+ break;
+ case TXCTRL1_CLK32X:
+ speed /= 32;
+ break;
+ default:
+ case TXCTRL1_CLK64X:
+ speed /= 64;
+ break;
+ }
+ ssp.speed = speed;
+ ssp.parity = parity;
+ ssp.data_bits = data_bits;
+ ssp.stop_bits = stop_bits;
+ trace_escc_update_parameters(CHN_C(s), speed, parity, data_bits, stop_bits);
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+}
+
+static void escc_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ ESCCState *serial = opaque;
+ ChannelState *s;
+ uint32_t saddr;
+ int newreg, channel;
+
+ val &= 0xff;
+ saddr = (addr >> serial->it_shift) & 1;
+ channel = (addr >> (serial->it_shift + 1)) & 1;
+ s = &serial->chn[channel];
+ switch (saddr) {
+ case SERIAL_CTRL:
+ trace_escc_mem_writeb_ctrl(CHN_C(s), s->reg, val & 0xff);
+ newreg = 0;
+ switch (s->reg) {
+ case W_CMD:
+ newreg = val & CMD_PTR_MASK;
+ val &= CMD_CMD_MASK;
+ switch (val) {
+ case CMD_HI:
+ newreg |= CMD_HI;
+ break;
+ case CMD_CLR_TXINT:
+ clr_txint(s);
+ break;
+ case CMD_CLR_IUS:
+ if (s->rxint_under_svc) {
+ s->rxint_under_svc = 0;
+ if (s->txint) {
+ set_txint(s);
+ }
+ } else if (s->txint_under_svc) {
+ s->txint_under_svc = 0;
+ }
+ escc_update_irq(s);
+ break;
+ default:
+ break;
+ }
+ break;
+ case W_INTR ... W_RXCTRL:
+ case W_SYNC1 ... W_TXBUF:
+ case W_MISC1 ... W_CLOCK:
+ case W_MISC2 ... W_EXTINT:
+ s->wregs[s->reg] = val;
+ break;
+ case W_TXCTRL1:
+ case W_TXCTRL2:
+ s->wregs[s->reg] = val;
+ escc_update_parameters(s);
+ break;
+ case W_BRGLO:
+ case W_BRGHI:
+ s->wregs[s->reg] = val;
+ s->rregs[s->reg] = val;
+ escc_update_parameters(s);
+ break;
+ case W_MINTR:
+ switch (val & MINTR_RST_MASK) {
+ case 0:
+ default:
+ break;
+ case MINTR_RST_B:
+ escc_reset_chn(&serial->chn[0]);
+ return;
+ case MINTR_RST_A:
+ escc_reset_chn(&serial->chn[1]);
+ return;
+ case MINTR_RST_ALL:
+ escc_reset(DEVICE(serial));
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ if (s->reg == 0)
+ s->reg = newreg;
+ else
+ s->reg = 0;
+ break;
+ case SERIAL_DATA:
+ trace_escc_mem_writeb_data(CHN_C(s), val);
+ s->tx = val;
+ if (s->wregs[W_TXCTRL2] & TXCTRL2_TXEN) { // tx enabled
+ if (s->chr)
+ qemu_chr_fe_write(s->chr, &s->tx, 1);
+ else if (s->type == kbd && !s->disabled) {
+ handle_kbd_command(s, val);
+ }
+ }
+ s->rregs[R_STATUS] |= STATUS_TXEMPTY; // Tx buffer empty
+ s->rregs[R_SPEC] |= SPEC_ALLSENT; // All sent
+ set_txint(s);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t escc_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ESCCState *serial = opaque;
+ ChannelState *s;
+ uint32_t saddr;
+ uint32_t ret;
+ int channel;
+
+ saddr = (addr >> serial->it_shift) & 1;
+ channel = (addr >> (serial->it_shift + 1)) & 1;
+ s = &serial->chn[channel];
+ switch (saddr) {
+ case SERIAL_CTRL:
+ trace_escc_mem_readb_ctrl(CHN_C(s), s->reg, s->rregs[s->reg]);
+ ret = s->rregs[s->reg];
+ s->reg = 0;
+ return ret;
+ case SERIAL_DATA:
+ s->rregs[R_STATUS] &= ~STATUS_RXAV;
+ clr_rxint(s);
+ if (s->type == kbd || s->type == mouse)
+ ret = get_queue(s);
+ else
+ ret = s->rx;
+ trace_escc_mem_readb_data(CHN_C(s), ret);
+ if (s->chr)
+ qemu_chr_accept_input(s->chr);
+ return ret;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const MemoryRegionOps escc_mem_ops = {
+ .read = escc_mem_read,
+ .write = escc_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static int serial_can_receive(void *opaque)
+{
+ ChannelState *s = opaque;
+ int ret;
+
+ if (((s->wregs[W_RXCTRL] & RXCTRL_RXEN) == 0) // Rx not enabled
+ || ((s->rregs[R_STATUS] & STATUS_RXAV) == STATUS_RXAV))
+ // char already available
+ ret = 0;
+ else
+ ret = 1;
+ return ret;
+}
+
+static void serial_receive_byte(ChannelState *s, int ch)
+{
+ trace_escc_serial_receive_byte(CHN_C(s), ch);
+ s->rregs[R_STATUS] |= STATUS_RXAV;
+ s->rx = ch;
+ set_rxint(s);
+}
+
+static void serial_receive_break(ChannelState *s)
+{
+ s->rregs[R_STATUS] |= STATUS_BRK;
+ escc_update_irq(s);
+}
+
+static void serial_receive1(void *opaque, const uint8_t *buf, int size)
+{
+ ChannelState *s = opaque;
+ serial_receive_byte(s, buf[0]);
+}
+
+static void serial_event(void *opaque, int event)
+{
+ ChannelState *s = opaque;
+ if (event == CHR_EVENT_BREAK)
+ serial_receive_break(s);
+}
+
+static const VMStateDescription vmstate_escc_chn = {
+ .name ="escc_chn",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(vmstate_dummy, ChannelState),
+ VMSTATE_UINT32(reg, ChannelState),
+ VMSTATE_UINT32(rxint, ChannelState),
+ VMSTATE_UINT32(txint, ChannelState),
+ VMSTATE_UINT32(rxint_under_svc, ChannelState),
+ VMSTATE_UINT32(txint_under_svc, ChannelState),
+ VMSTATE_UINT8(rx, ChannelState),
+ VMSTATE_UINT8(tx, ChannelState),
+ VMSTATE_BUFFER(wregs, ChannelState),
+ VMSTATE_BUFFER(rregs, ChannelState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_escc = {
+ .name ="escc",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(chn, ESCCState, 2, 2, vmstate_escc_chn,
+ ChannelState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+MemoryRegion *escc_init(hwaddr base, qemu_irq irqA, qemu_irq irqB,
+ CharDriverState *chrA, CharDriverState *chrB,
+ int clock, int it_shift)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ ESCCState *d;
+
+ dev = qdev_create(NULL, TYPE_ESCC);
+ qdev_prop_set_uint32(dev, "disabled", 0);
+ qdev_prop_set_uint32(dev, "frequency", clock);
+ qdev_prop_set_uint32(dev, "it_shift", it_shift);
+ qdev_prop_set_chr(dev, "chrB", chrB);
+ qdev_prop_set_chr(dev, "chrA", chrA);
+ qdev_prop_set_uint32(dev, "chnBtype", ser);
+ qdev_prop_set_uint32(dev, "chnAtype", ser);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irqB);
+ sysbus_connect_irq(s, 1, irqA);
+ if (base) {
+ sysbus_mmio_map(s, 0, base);
+ }
+
+ d = ESCC(s);
+ return &d->mmio;
+}
+
+static const uint8_t qcode_to_keycode[Q_KEY_CODE_MAX] = {
+ [Q_KEY_CODE_SHIFT] = 99,
+ [Q_KEY_CODE_SHIFT_R] = 110,
+ [Q_KEY_CODE_ALT] = 19,
+ [Q_KEY_CODE_ALT_R] = 13,
+ [Q_KEY_CODE_ALTGR] = 13,
+ [Q_KEY_CODE_CTRL] = 76,
+ [Q_KEY_CODE_CTRL_R] = 76,
+ [Q_KEY_CODE_ESC] = 29,
+ [Q_KEY_CODE_1] = 30,
+ [Q_KEY_CODE_2] = 31,
+ [Q_KEY_CODE_3] = 32,
+ [Q_KEY_CODE_4] = 33,
+ [Q_KEY_CODE_5] = 34,
+ [Q_KEY_CODE_6] = 35,
+ [Q_KEY_CODE_7] = 36,
+ [Q_KEY_CODE_8] = 37,
+ [Q_KEY_CODE_9] = 38,
+ [Q_KEY_CODE_0] = 39,
+ [Q_KEY_CODE_MINUS] = 40,
+ [Q_KEY_CODE_EQUAL] = 41,
+ [Q_KEY_CODE_BACKSPACE] = 43,
+ [Q_KEY_CODE_TAB] = 53,
+ [Q_KEY_CODE_Q] = 54,
+ [Q_KEY_CODE_W] = 55,
+ [Q_KEY_CODE_E] = 56,
+ [Q_KEY_CODE_R] = 57,
+ [Q_KEY_CODE_T] = 58,
+ [Q_KEY_CODE_Y] = 59,
+ [Q_KEY_CODE_U] = 60,
+ [Q_KEY_CODE_I] = 61,
+ [Q_KEY_CODE_O] = 62,
+ [Q_KEY_CODE_P] = 63,
+ [Q_KEY_CODE_BRACKET_LEFT] = 64,
+ [Q_KEY_CODE_BRACKET_RIGHT] = 65,
+ [Q_KEY_CODE_RET] = 89,
+ [Q_KEY_CODE_A] = 77,
+ [Q_KEY_CODE_S] = 78,
+ [Q_KEY_CODE_D] = 79,
+ [Q_KEY_CODE_F] = 80,
+ [Q_KEY_CODE_G] = 81,
+ [Q_KEY_CODE_H] = 82,
+ [Q_KEY_CODE_J] = 83,
+ [Q_KEY_CODE_K] = 84,
+ [Q_KEY_CODE_L] = 85,
+ [Q_KEY_CODE_SEMICOLON] = 86,
+ [Q_KEY_CODE_APOSTROPHE] = 87,
+ [Q_KEY_CODE_GRAVE_ACCENT] = 42,
+ [Q_KEY_CODE_BACKSLASH] = 88,
+ [Q_KEY_CODE_Z] = 100,
+ [Q_KEY_CODE_X] = 101,
+ [Q_KEY_CODE_C] = 102,
+ [Q_KEY_CODE_V] = 103,
+ [Q_KEY_CODE_B] = 104,
+ [Q_KEY_CODE_N] = 105,
+ [Q_KEY_CODE_M] = 106,
+ [Q_KEY_CODE_COMMA] = 107,
+ [Q_KEY_CODE_DOT] = 108,
+ [Q_KEY_CODE_SLASH] = 109,
+ [Q_KEY_CODE_ASTERISK] = 47,
+ [Q_KEY_CODE_SPC] = 121,
+ [Q_KEY_CODE_CAPS_LOCK] = 119,
+ [Q_KEY_CODE_F1] = 5,
+ [Q_KEY_CODE_F2] = 6,
+ [Q_KEY_CODE_F3] = 8,
+ [Q_KEY_CODE_F4] = 10,
+ [Q_KEY_CODE_F5] = 12,
+ [Q_KEY_CODE_F6] = 14,
+ [Q_KEY_CODE_F7] = 16,
+ [Q_KEY_CODE_F8] = 17,
+ [Q_KEY_CODE_F9] = 18,
+ [Q_KEY_CODE_F10] = 7,
+ [Q_KEY_CODE_NUM_LOCK] = 98,
+ [Q_KEY_CODE_SCROLL_LOCK] = 23,
+ [Q_KEY_CODE_KP_DIVIDE] = 46,
+ [Q_KEY_CODE_KP_MULTIPLY] = 47,
+ [Q_KEY_CODE_KP_SUBTRACT] = 71,
+ [Q_KEY_CODE_KP_ADD] = 125,
+ [Q_KEY_CODE_KP_ENTER] = 90,
+ [Q_KEY_CODE_KP_DECIMAL] = 50,
+ [Q_KEY_CODE_KP_0] = 94,
+ [Q_KEY_CODE_KP_1] = 112,
+ [Q_KEY_CODE_KP_2] = 113,
+ [Q_KEY_CODE_KP_3] = 114,
+ [Q_KEY_CODE_KP_4] = 91,
+ [Q_KEY_CODE_KP_5] = 92,
+ [Q_KEY_CODE_KP_6] = 93,
+ [Q_KEY_CODE_KP_7] = 68,
+ [Q_KEY_CODE_KP_8] = 69,
+ [Q_KEY_CODE_KP_9] = 70,
+ [Q_KEY_CODE_LESS] = 124,
+ [Q_KEY_CODE_F11] = 9,
+ [Q_KEY_CODE_F12] = 11,
+ [Q_KEY_CODE_HOME] = 52,
+ [Q_KEY_CODE_PGUP] = 96,
+ [Q_KEY_CODE_PGDN] = 123,
+ [Q_KEY_CODE_END] = 74,
+ [Q_KEY_CODE_LEFT] = 24,
+ [Q_KEY_CODE_UP] = 20,
+ [Q_KEY_CODE_DOWN] = 27,
+ [Q_KEY_CODE_RIGHT] = 28,
+ [Q_KEY_CODE_INSERT] = 44,
+ [Q_KEY_CODE_DELETE] = 66,
+ [Q_KEY_CODE_STOP] = 1,
+ [Q_KEY_CODE_AGAIN] = 3,
+ [Q_KEY_CODE_PROPS] = 25,
+ [Q_KEY_CODE_UNDO] = 26,
+ [Q_KEY_CODE_FRONT] = 49,
+ [Q_KEY_CODE_COPY] = 51,
+ [Q_KEY_CODE_OPEN] = 72,
+ [Q_KEY_CODE_PASTE] = 73,
+ [Q_KEY_CODE_FIND] = 95,
+ [Q_KEY_CODE_CUT] = 97,
+ [Q_KEY_CODE_LF] = 111,
+ [Q_KEY_CODE_HELP] = 118,
+ [Q_KEY_CODE_META_L] = 120,
+ [Q_KEY_CODE_META_R] = 122,
+ [Q_KEY_CODE_COMPOSE] = 67,
+ [Q_KEY_CODE_PRINT] = 22,
+ [Q_KEY_CODE_SYSRQ] = 21,
+};
+
+static void sunkbd_handle_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ ChannelState *s = (ChannelState *)dev;
+ int qcode, keycode;
+
+ assert(evt->kind == INPUT_EVENT_KIND_KEY);
+ qcode = qemu_input_key_value_to_qcode(evt->key->key);
+ trace_escc_sunkbd_event_in(qcode, QKeyCode_lookup[qcode],
+ evt->key->down);
+
+ if (qcode == Q_KEY_CODE_CAPS_LOCK) {
+ if (evt->key->down) {
+ s->caps_lock_mode ^= 1;
+ if (s->caps_lock_mode == 2) {
+ return; /* Drop second press */
+ }
+ } else {
+ s->caps_lock_mode ^= 2;
+ if (s->caps_lock_mode == 3) {
+ return; /* Drop first release */
+ }
+ }
+ }
+
+ if (qcode == Q_KEY_CODE_NUM_LOCK) {
+ if (evt->key->down) {
+ s->num_lock_mode ^= 1;
+ if (s->num_lock_mode == 2) {
+ return; /* Drop second press */
+ }
+ } else {
+ s->num_lock_mode ^= 2;
+ if (s->num_lock_mode == 3) {
+ return; /* Drop first release */
+ }
+ }
+ }
+
+ keycode = qcode_to_keycode[qcode];
+ if (!evt->key->down) {
+ keycode |= 0x80;
+ }
+ trace_escc_sunkbd_event_out(keycode);
+ put_queue(s, keycode);
+}
+
+static QemuInputHandler sunkbd_handler = {
+ .name = "sun keyboard",
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = sunkbd_handle_event,
+};
+
+static void handle_kbd_command(ChannelState *s, int val)
+{
+ trace_escc_kbd_command(val);
+ if (s->led_mode) { // Ignore led byte
+ s->led_mode = 0;
+ return;
+ }
+ switch (val) {
+ case 1: // Reset, return type code
+ clear_queue(s);
+ put_queue(s, 0xff);
+ put_queue(s, 4); // Type 4
+ put_queue(s, 0x7f);
+ break;
+ case 0xe: // Set leds
+ s->led_mode = 1;
+ break;
+ case 7: // Query layout
+ case 0xf:
+ clear_queue(s);
+ put_queue(s, 0xfe);
+ put_queue(s, 0x21); /* en-us layout */
+ break;
+ default:
+ break;
+ }
+}
+
+static void sunmouse_event(void *opaque,
+ int dx, int dy, int dz, int buttons_state)
+{
+ ChannelState *s = opaque;
+ int ch;
+
+ trace_escc_sunmouse_event(dx, dy, buttons_state);
+ ch = 0x80 | 0x7; /* protocol start byte, no buttons pressed */
+
+ if (buttons_state & MOUSE_EVENT_LBUTTON)
+ ch ^= 0x4;
+ if (buttons_state & MOUSE_EVENT_MBUTTON)
+ ch ^= 0x2;
+ if (buttons_state & MOUSE_EVENT_RBUTTON)
+ ch ^= 0x1;
+
+ put_queue(s, ch);
+
+ ch = dx;
+
+ if (ch > 127)
+ ch = 127;
+ else if (ch < -127)
+ ch = -127;
+
+ put_queue(s, ch & 0xff);
+
+ ch = -dy;
+
+ if (ch > 127)
+ ch = 127;
+ else if (ch < -127)
+ ch = -127;
+
+ put_queue(s, ch & 0xff);
+
+ // MSC protocol specify two extra motion bytes
+
+ put_queue(s, 0);
+ put_queue(s, 0);
+}
+
+void slavio_serial_ms_kbd_init(hwaddr base, qemu_irq irq,
+ int disabled, int clock, int it_shift)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, TYPE_ESCC);
+ qdev_prop_set_uint32(dev, "disabled", disabled);
+ qdev_prop_set_uint32(dev, "frequency", clock);
+ qdev_prop_set_uint32(dev, "it_shift", it_shift);
+ qdev_prop_set_chr(dev, "chrB", NULL);
+ qdev_prop_set_chr(dev, "chrA", NULL);
+ qdev_prop_set_uint32(dev, "chnBtype", mouse);
+ qdev_prop_set_uint32(dev, "chnAtype", kbd);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irq);
+ sysbus_connect_irq(s, 1, irq);
+ sysbus_mmio_map(s, 0, base);
+}
+
+static int escc_init1(SysBusDevice *dev)
+{
+ ESCCState *s = ESCC(dev);
+ unsigned int i;
+
+ s->chn[0].disabled = s->disabled;
+ s->chn[1].disabled = s->disabled;
+ for (i = 0; i < 2; i++) {
+ sysbus_init_irq(dev, &s->chn[i].irq);
+ s->chn[i].chn = 1 - i;
+ s->chn[i].clock = s->frequency / 2;
+ if (s->chn[i].chr) {
+ qemu_chr_add_handlers(s->chn[i].chr, serial_can_receive,
+ serial_receive1, serial_event, &s->chn[i]);
+ }
+ }
+ s->chn[0].otherchn = &s->chn[1];
+ s->chn[1].otherchn = &s->chn[0];
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &escc_mem_ops, s, "escc",
+ ESCC_SIZE << s->it_shift);
+ sysbus_init_mmio(dev, &s->mmio);
+
+ if (s->chn[0].type == mouse) {
+ qemu_add_mouse_event_handler(sunmouse_event, &s->chn[0], 0,
+ "QEMU Sun Mouse");
+ }
+ if (s->chn[1].type == kbd) {
+ s->chn[1].hs = qemu_input_handler_register((DeviceState *)(&s->chn[1]),
+ &sunkbd_handler);
+ }
+
+ return 0;
+}
+
+static Property escc_properties[] = {
+ DEFINE_PROP_UINT32("frequency", ESCCState, frequency, 0),
+ DEFINE_PROP_UINT32("it_shift", ESCCState, it_shift, 0),
+ DEFINE_PROP_UINT32("disabled", ESCCState, disabled, 0),
+ DEFINE_PROP_UINT32("chnBtype", ESCCState, chn[0].type, 0),
+ DEFINE_PROP_UINT32("chnAtype", ESCCState, chn[1].type, 0),
+ DEFINE_PROP_CHR("chrB", ESCCState, chn[0].chr),
+ DEFINE_PROP_CHR("chrA", ESCCState, chn[1].chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void escc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = escc_init1;
+ dc->reset = escc_reset;
+ dc->vmsd = &vmstate_escc;
+ dc->props = escc_properties;
+}
+
+static const TypeInfo escc_info = {
+ .name = TYPE_ESCC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ESCCState),
+ .class_init = escc_class_init,
+};
+
+static void escc_register_types(void)
+{
+ type_register_static(&escc_info);
+}
+
+type_init(escc_register_types)
diff --git a/hw/char/etraxfs_ser.c b/hw/char/etraxfs_ser.c
new file mode 100644
index 00000000..857c1362
--- /dev/null
+++ b/hw/char/etraxfs_ser.c
@@ -0,0 +1,255 @@
+/*
+ * QEMU ETRAX System Emulator
+ *
+ * Copyright (c) 2007 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+#include "qemu/log.h"
+
+#define D(x)
+
+#define RW_TR_CTRL (0x00 / 4)
+#define RW_TR_DMA_EN (0x04 / 4)
+#define RW_REC_CTRL (0x08 / 4)
+#define RW_DOUT (0x1c / 4)
+#define RS_STAT_DIN (0x20 / 4)
+#define R_STAT_DIN (0x24 / 4)
+#define RW_INTR_MASK (0x2c / 4)
+#define RW_ACK_INTR (0x30 / 4)
+#define R_INTR (0x34 / 4)
+#define R_MASKED_INTR (0x38 / 4)
+#define R_MAX (0x3c / 4)
+
+#define STAT_DAV 16
+#define STAT_TR_IDLE 22
+#define STAT_TR_RDY 24
+
+#define TYPE_ETRAX_FS_SERIAL "etraxfs,serial"
+#define ETRAX_SERIAL(obj) \
+ OBJECT_CHECK(ETRAXSerial, (obj), TYPE_ETRAX_FS_SERIAL)
+
+typedef struct ETRAXSerial {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ int pending_tx;
+
+ uint8_t rx_fifo[16];
+ unsigned int rx_fifo_pos;
+ unsigned int rx_fifo_len;
+
+ /* Control registers. */
+ uint32_t regs[R_MAX];
+} ETRAXSerial;
+
+static void ser_update_irq(ETRAXSerial *s)
+{
+
+ if (s->rx_fifo_len) {
+ s->regs[R_INTR] |= 8;
+ } else {
+ s->regs[R_INTR] &= ~8;
+ }
+
+ s->regs[R_MASKED_INTR] = s->regs[R_INTR] & s->regs[RW_INTR_MASK];
+ qemu_set_irq(s->irq, !!s->regs[R_MASKED_INTR]);
+}
+
+static uint64_t
+ser_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ ETRAXSerial *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr)
+ {
+ case R_STAT_DIN:
+ r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 15];
+ if (s->rx_fifo_len) {
+ r |= 1 << STAT_DAV;
+ }
+ r |= 1 << STAT_TR_RDY;
+ r |= 1 << STAT_TR_IDLE;
+ break;
+ case RS_STAT_DIN:
+ r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 15];
+ if (s->rx_fifo_len) {
+ r |= 1 << STAT_DAV;
+ s->rx_fifo_len--;
+ }
+ r |= 1 << STAT_TR_RDY;
+ r |= 1 << STAT_TR_IDLE;
+ break;
+ default:
+ r = s->regs[addr];
+ D(qemu_log("%s " TARGET_FMT_plx "=%x\n", __func__, addr, r));
+ break;
+ }
+ return r;
+}
+
+static void
+ser_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ ETRAXSerial *s = opaque;
+ uint32_t value = val64;
+ unsigned char ch = val64;
+
+ D(qemu_log("%s " TARGET_FMT_plx "=%x\n", __func__, addr, value));
+ addr >>= 2;
+ switch (addr)
+ {
+ case RW_DOUT:
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ s->regs[R_INTR] |= 3;
+ s->pending_tx = 1;
+ s->regs[addr] = value;
+ break;
+ case RW_ACK_INTR:
+ if (s->pending_tx) {
+ value &= ~1;
+ s->pending_tx = 0;
+ D(qemu_log("fixedup value=%x r_intr=%x\n",
+ value, s->regs[R_INTR]));
+ }
+ s->regs[addr] = value;
+ s->regs[R_INTR] &= ~value;
+ D(printf("r_intr=%x\n", s->regs[R_INTR]));
+ break;
+ default:
+ s->regs[addr] = value;
+ break;
+ }
+ ser_update_irq(s);
+}
+
+static const MemoryRegionOps ser_ops = {
+ .read = ser_read,
+ .write = ser_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void serial_receive(void *opaque, const uint8_t *buf, int size)
+{
+ ETRAXSerial *s = opaque;
+ int i;
+
+ /* Got a byte. */
+ if (s->rx_fifo_len >= 16) {
+ qemu_log("WARNING: UART dropped char.\n");
+ return;
+ }
+
+ for (i = 0; i < size; i++) {
+ s->rx_fifo[s->rx_fifo_pos] = buf[i];
+ s->rx_fifo_pos++;
+ s->rx_fifo_pos &= 15;
+ s->rx_fifo_len++;
+ }
+
+ ser_update_irq(s);
+}
+
+static int serial_can_receive(void *opaque)
+{
+ ETRAXSerial *s = opaque;
+ int r;
+
+ /* Is the receiver enabled? */
+ if (!(s->regs[RW_REC_CTRL] & (1 << 3))) {
+ return 0;
+ }
+
+ r = sizeof(s->rx_fifo) - s->rx_fifo_len;
+ return r;
+}
+
+static void serial_event(void *opaque, int event)
+{
+
+}
+
+static void etraxfs_ser_reset(DeviceState *d)
+{
+ ETRAXSerial *s = ETRAX_SERIAL(d);
+
+ /* transmitter begins ready and idle. */
+ s->regs[RS_STAT_DIN] |= (1 << STAT_TR_RDY);
+ s->regs[RS_STAT_DIN] |= (1 << STAT_TR_IDLE);
+
+ s->regs[RW_REC_CTRL] = 0x10000;
+
+}
+
+static int etraxfs_ser_init(SysBusDevice *dev)
+{
+ ETRAXSerial *s = ETRAX_SERIAL(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+ memory_region_init_io(&s->mmio, OBJECT(s), &ser_ops, s,
+ "etraxfs-serial", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->mmio);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr,
+ serial_can_receive, serial_receive,
+ serial_event, s);
+ }
+ return 0;
+}
+
+static void etraxfs_ser_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = etraxfs_ser_init;
+ dc->reset = etraxfs_ser_reset;
+ /* Reason: init() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo etraxfs_ser_info = {
+ .name = TYPE_ETRAX_FS_SERIAL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ETRAXSerial),
+ .class_init = etraxfs_ser_class_init,
+};
+
+static void etraxfs_serial_register_types(void)
+{
+ type_register_static(&etraxfs_ser_info);
+}
+
+type_init(etraxfs_serial_register_types)
diff --git a/hw/char/exynos4210_uart.c b/hw/char/exynos4210_uart.c
new file mode 100644
index 00000000..7614e586
--- /dev/null
+++ b/hw/char/exynos4210_uart.c
@@ -0,0 +1,676 @@
+/*
+ * Exynos4210 UART Emulation
+ *
+ * Copyright (C) 2011 Samsung Electronics Co Ltd.
+ * Maksim Kozlov, <m.kozlov@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/char.h"
+
+#include "hw/arm/exynos4210.h"
+
+#undef DEBUG_UART
+#undef DEBUG_UART_EXTEND
+#undef DEBUG_IRQ
+#undef DEBUG_Rx_DATA
+#undef DEBUG_Tx_DATA
+
+#define DEBUG_UART 0
+#define DEBUG_UART_EXTEND 0
+#define DEBUG_IRQ 0
+#define DEBUG_Rx_DATA 0
+#define DEBUG_Tx_DATA 0
+
+#if DEBUG_UART
+#define PRINT_DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+#if DEBUG_UART_EXTEND
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+#else
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do {} while (0)
+#endif /* EXTEND */
+
+#else
+#define PRINT_DEBUG(fmt, args...) \
+ do {} while (0)
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do {} while (0)
+#endif
+
+#define PRINT_ERROR(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+/*
+ * Offsets for UART registers relative to SFR base address
+ * for UARTn
+ *
+ */
+#define ULCON 0x0000 /* Line Control */
+#define UCON 0x0004 /* Control */
+#define UFCON 0x0008 /* FIFO Control */
+#define UMCON 0x000C /* Modem Control */
+#define UTRSTAT 0x0010 /* Tx/Rx Status */
+#define UERSTAT 0x0014 /* UART Error Status */
+#define UFSTAT 0x0018 /* FIFO Status */
+#define UMSTAT 0x001C /* Modem Status */
+#define UTXH 0x0020 /* Transmit Buffer */
+#define URXH 0x0024 /* Receive Buffer */
+#define UBRDIV 0x0028 /* Baud Rate Divisor */
+#define UFRACVAL 0x002C /* Divisor Fractional Value */
+#define UINTP 0x0030 /* Interrupt Pending */
+#define UINTSP 0x0034 /* Interrupt Source Pending */
+#define UINTM 0x0038 /* Interrupt Mask */
+
+/*
+ * for indexing register in the uint32_t array
+ *
+ * 'reg' - register offset (see offsets definitions above)
+ *
+ */
+#define I_(reg) (reg / sizeof(uint32_t))
+
+typedef struct Exynos4210UartReg {
+ const char *name; /* the only reason is the debug output */
+ hwaddr offset;
+ uint32_t reset_value;
+} Exynos4210UartReg;
+
+static Exynos4210UartReg exynos4210_uart_regs[] = {
+ {"ULCON", ULCON, 0x00000000},
+ {"UCON", UCON, 0x00003000},
+ {"UFCON", UFCON, 0x00000000},
+ {"UMCON", UMCON, 0x00000000},
+ {"UTRSTAT", UTRSTAT, 0x00000006}, /* RO */
+ {"UERSTAT", UERSTAT, 0x00000000}, /* RO */
+ {"UFSTAT", UFSTAT, 0x00000000}, /* RO */
+ {"UMSTAT", UMSTAT, 0x00000000}, /* RO */
+ {"UTXH", UTXH, 0x5c5c5c5c}, /* WO, undefined reset value*/
+ {"URXH", URXH, 0x00000000}, /* RO */
+ {"UBRDIV", UBRDIV, 0x00000000},
+ {"UFRACVAL", UFRACVAL, 0x00000000},
+ {"UINTP", UINTP, 0x00000000},
+ {"UINTSP", UINTSP, 0x00000000},
+ {"UINTM", UINTM, 0x00000000},
+};
+
+#define EXYNOS4210_UART_REGS_MEM_SIZE 0x3C
+
+/* UART FIFO Control */
+#define UFCON_FIFO_ENABLE 0x1
+#define UFCON_Rx_FIFO_RESET 0x2
+#define UFCON_Tx_FIFO_RESET 0x4
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT 8
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT)
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT 4
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT)
+
+/* Uart FIFO Status */
+#define UFSTAT_Rx_FIFO_COUNT 0xff
+#define UFSTAT_Rx_FIFO_FULL 0x100
+#define UFSTAT_Rx_FIFO_ERROR 0x200
+#define UFSTAT_Tx_FIFO_COUNT_SHIFT 16
+#define UFSTAT_Tx_FIFO_COUNT (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT)
+#define UFSTAT_Tx_FIFO_FULL_SHIFT 24
+#define UFSTAT_Tx_FIFO_FULL (1 << UFSTAT_Tx_FIFO_FULL_SHIFT)
+
+/* UART Interrupt Source Pending */
+#define UINTSP_RXD 0x1 /* Receive interrupt */
+#define UINTSP_ERROR 0x2 /* Error interrupt */
+#define UINTSP_TXD 0x4 /* Transmit interrupt */
+#define UINTSP_MODEM 0x8 /* Modem interrupt */
+
+/* UART Line Control */
+#define ULCON_IR_MODE_SHIFT 6
+#define ULCON_PARITY_SHIFT 3
+#define ULCON_STOP_BIT_SHIFT 1
+
+/* UART Tx/Rx Status */
+#define UTRSTAT_TRANSMITTER_EMPTY 0x4
+#define UTRSTAT_Tx_BUFFER_EMPTY 0x2
+#define UTRSTAT_Rx_BUFFER_DATA_READY 0x1
+
+/* UART Error Status */
+#define UERSTAT_OVERRUN 0x1
+#define UERSTAT_PARITY 0x2
+#define UERSTAT_FRAME 0x4
+#define UERSTAT_BREAK 0x8
+
+typedef struct {
+ uint8_t *data;
+ uint32_t sp, rp; /* store and retrieve pointers */
+ uint32_t size;
+} Exynos4210UartFIFO;
+
+#define TYPE_EXYNOS4210_UART "exynos4210.uart"
+#define EXYNOS4210_UART(obj) \
+ OBJECT_CHECK(Exynos4210UartState, (obj), TYPE_EXYNOS4210_UART)
+
+typedef struct Exynos4210UartState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t reg[EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)];
+ Exynos4210UartFIFO rx;
+ Exynos4210UartFIFO tx;
+
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t channel;
+
+} Exynos4210UartState;
+
+
+#if DEBUG_UART
+/* Used only for debugging inside PRINT_DEBUG_... macros */
+static const char *exynos4210_uart_regname(hwaddr offset)
+{
+
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(exynos4210_uart_regs); i++) {
+ if (offset == exynos4210_uart_regs[i].offset) {
+ return exynos4210_uart_regs[i].name;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+
+static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch)
+{
+ q->data[q->sp] = ch;
+ q->sp = (q->sp + 1) % q->size;
+}
+
+static uint8_t fifo_retrieve(Exynos4210UartFIFO *q)
+{
+ uint8_t ret = q->data[q->rp];
+ q->rp = (q->rp + 1) % q->size;
+ return ret;
+}
+
+static int fifo_elements_number(Exynos4210UartFIFO *q)
+{
+ if (q->sp < q->rp) {
+ return q->size - q->rp + q->sp;
+ }
+
+ return q->sp - q->rp;
+}
+
+static int fifo_empty_elements_number(Exynos4210UartFIFO *q)
+{
+ return q->size - fifo_elements_number(q);
+}
+
+static void fifo_reset(Exynos4210UartFIFO *q)
+{
+ if (q->data != NULL) {
+ g_free(q->data);
+ q->data = NULL;
+ }
+
+ q->data = (uint8_t *)g_malloc0(q->size);
+
+ q->sp = 0;
+ q->rp = 0;
+}
+
+static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(Exynos4210UartState *s)
+{
+ uint32_t level = 0;
+ uint32_t reg;
+
+ reg = (s->reg[I_(UFCON)] & UFCON_Tx_FIFO_TRIGGER_LEVEL) >>
+ UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT;
+
+ switch (s->channel) {
+ case 0:
+ level = reg * 32;
+ break;
+ case 1:
+ case 4:
+ level = reg * 8;
+ break;
+ case 2:
+ case 3:
+ level = reg * 2;
+ break;
+ default:
+ level = 0;
+ PRINT_ERROR("Wrong UART channel number: %d\n", s->channel);
+ }
+
+ return level;
+}
+
+static void exynos4210_uart_update_irq(Exynos4210UartState *s)
+{
+ /*
+ * The Tx interrupt is always requested if the number of data in the
+ * transmit FIFO is smaller than the trigger level.
+ */
+ if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+
+ uint32_t count = (s->reg[I_(UFSTAT)] & UFSTAT_Tx_FIFO_COUNT) >>
+ UFSTAT_Tx_FIFO_COUNT_SHIFT;
+
+ if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) {
+ s->reg[I_(UINTSP)] |= UINTSP_TXD;
+ }
+ }
+
+ s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)];
+
+ if (s->reg[I_(UINTP)]) {
+ qemu_irq_raise(s->irq);
+
+#if DEBUG_IRQ
+ fprintf(stderr, "UART%d: IRQ has been raised: %08x\n",
+ s->channel, s->reg[I_(UINTP)]);
+#endif
+
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
+{
+ int speed, parity, data_bits, stop_bits, frame_size;
+ QEMUSerialSetParams ssp;
+ uint64_t uclk_rate;
+
+ if (s->reg[I_(UBRDIV)] == 0) {
+ return;
+ }
+
+ frame_size = 1; /* start bit */
+ if (s->reg[I_(ULCON)] & 0x20) {
+ frame_size++; /* parity bit */
+ if (s->reg[I_(ULCON)] & 0x28) {
+ parity = 'E';
+ } else {
+ parity = 'O';
+ }
+ } else {
+ parity = 'N';
+ }
+
+ if (s->reg[I_(ULCON)] & 0x4) {
+ stop_bits = 2;
+ } else {
+ stop_bits = 1;
+ }
+
+ data_bits = (s->reg[I_(ULCON)] & 0x3) + 5;
+
+ frame_size += data_bits + stop_bits;
+
+ uclk_rate = 24000000;
+
+ speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) +
+ (s->reg[I_(UFRACVAL)] & 0x7) + 16);
+
+ ssp.speed = speed;
+ ssp.parity = parity;
+ ssp.data_bits = data_bits;
+ ssp.stop_bits = stop_bits;
+
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+
+ PRINT_DEBUG("UART%d: speed: %d, parity: %c, data: %d, stop: %d\n",
+ s->channel, speed, parity, data_bits, stop_bits);
+}
+
+static void exynos4210_uart_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ uint8_t ch;
+
+ PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n", s->channel,
+ offset, exynos4210_uart_regname(offset), (long long unsigned int)val);
+
+ switch (offset) {
+ case ULCON:
+ case UBRDIV:
+ case UFRACVAL:
+ s->reg[I_(offset)] = val;
+ exynos4210_uart_update_parameters(s);
+ break;
+ case UFCON:
+ s->reg[I_(UFCON)] = val;
+ if (val & UFCON_Rx_FIFO_RESET) {
+ fifo_reset(&s->rx);
+ s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET;
+ PRINT_DEBUG("UART%d: Rx FIFO Reset\n", s->channel);
+ }
+ if (val & UFCON_Tx_FIFO_RESET) {
+ fifo_reset(&s->tx);
+ s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET;
+ PRINT_DEBUG("UART%d: Tx FIFO Reset\n", s->channel);
+ }
+ break;
+
+ case UTXH:
+ if (s->chr) {
+ s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY |
+ UTRSTAT_Tx_BUFFER_EMPTY);
+ ch = (uint8_t)val;
+ qemu_chr_fe_write(s->chr, &ch, 1);
+#if DEBUG_Tx_DATA
+ fprintf(stderr, "%c", ch);
+#endif
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY |
+ UTRSTAT_Tx_BUFFER_EMPTY;
+ s->reg[I_(UINTSP)] |= UINTSP_TXD;
+ exynos4210_uart_update_irq(s);
+ }
+ break;
+
+ case UINTP:
+ s->reg[I_(UINTP)] &= ~val;
+ s->reg[I_(UINTSP)] &= ~val;
+ PRINT_DEBUG("UART%d: UINTP [%04x] have been cleared: %08x\n",
+ s->channel, offset, s->reg[I_(UINTP)]);
+ exynos4210_uart_update_irq(s);
+ break;
+ case UTRSTAT:
+ case UERSTAT:
+ case UFSTAT:
+ case UMSTAT:
+ case URXH:
+ PRINT_DEBUG("UART%d: Trying to write into RO register: %s [%04x]\n",
+ s->channel, exynos4210_uart_regname(offset), offset);
+ break;
+ case UINTSP:
+ s->reg[I_(UINTSP)] &= ~val;
+ break;
+ case UINTM:
+ s->reg[I_(UINTM)] = val;
+ exynos4210_uart_update_irq(s);
+ break;
+ case UCON:
+ case UMCON:
+ default:
+ s->reg[I_(offset)] = val;
+ break;
+ }
+}
+static uint64_t exynos4210_uart_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ uint32_t res;
+
+ switch (offset) {
+ case UERSTAT: /* Read Only */
+ res = s->reg[I_(UERSTAT)];
+ s->reg[I_(UERSTAT)] = 0;
+ return res;
+ case UFSTAT: /* Read Only */
+ s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff;
+ if (fifo_empty_elements_number(&s->rx) == 0) {
+ s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL;
+ s->reg[I_(UFSTAT)] &= ~0xff;
+ }
+ return s->reg[I_(UFSTAT)];
+ case URXH:
+ if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+ if (fifo_elements_number(&s->rx)) {
+ res = fifo_retrieve(&s->rx);
+#if DEBUG_Rx_DATA
+ fprintf(stderr, "%c", res);
+#endif
+ if (!fifo_elements_number(&s->rx)) {
+ s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+ } else {
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+ } else {
+ s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+ exynos4210_uart_update_irq(s);
+ res = 0;
+ }
+ } else {
+ s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+ res = s->reg[I_(URXH)];
+ }
+ return res;
+ case UTXH:
+ PRINT_DEBUG("UART%d: Trying to read from WO register: %s [%04x]\n",
+ s->channel, exynos4210_uart_regname(offset), offset);
+ break;
+ default:
+ return s->reg[I_(offset)];
+ }
+
+ return 0;
+}
+
+static const MemoryRegionOps exynos4210_uart_ops = {
+ .read = exynos4210_uart_read,
+ .write = exynos4210_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .max_access_size = 4,
+ .unaligned = false
+ },
+};
+
+static int exynos4210_uart_can_receive(void *opaque)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+ return fifo_empty_elements_number(&s->rx);
+}
+
+
+static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ int i;
+
+ if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+ if (fifo_empty_elements_number(&s->rx) < size) {
+ for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) {
+ fifo_store(&s->rx, buf[i]);
+ }
+ s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ } else {
+ for (i = 0; i < size; i++) {
+ fifo_store(&s->rx, buf[i]);
+ }
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+ /* XXX: Around here we maybe should check Rx trigger level */
+ s->reg[I_(UINTSP)] |= UINTSP_RXD;
+ } else {
+ s->reg[I_(URXH)] = buf[0];
+ s->reg[I_(UINTSP)] |= UINTSP_RXD;
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+
+ exynos4210_uart_update_irq(s);
+}
+
+
+static void exynos4210_uart_event(void *opaque, int event)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+ if (event == CHR_EVENT_BREAK) {
+ /* When the RxDn is held in logic 0, then a null byte is pushed into the
+ * fifo */
+ fifo_store(&s->rx, '\0');
+ s->reg[I_(UERSTAT)] |= UERSTAT_BREAK;
+ exynos4210_uart_update_irq(s);
+ }
+}
+
+
+static void exynos4210_uart_reset(DeviceState *dev)
+{
+ Exynos4210UartState *s = EXYNOS4210_UART(dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(exynos4210_uart_regs); i++) {
+ s->reg[I_(exynos4210_uart_regs[i].offset)] =
+ exynos4210_uart_regs[i].reset_value;
+ }
+
+ fifo_reset(&s->rx);
+ fifo_reset(&s->tx);
+
+ PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size);
+}
+
+static const VMStateDescription vmstate_exynos4210_uart_fifo = {
+ .name = "exynos4210.uart.fifo",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(sp, Exynos4210UartFIFO),
+ VMSTATE_UINT32(rp, Exynos4210UartFIFO),
+ VMSTATE_VBUFFER_UINT32(data, Exynos4210UartFIFO, 1, NULL, 0, size),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_uart = {
+ .name = "exynos4210.uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(rx, Exynos4210UartState, 1,
+ vmstate_exynos4210_uart_fifo, Exynos4210UartFIFO),
+ VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState,
+ EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+DeviceState *exynos4210_uart_create(hwaddr addr,
+ int fifo_size,
+ int channel,
+ CharDriverState *chr,
+ qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *bus;
+
+ const char chr_name[] = "serial";
+ char label[ARRAY_SIZE(chr_name) + 1];
+
+ dev = qdev_create(NULL, TYPE_EXYNOS4210_UART);
+
+ if (!chr) {
+ if (channel >= MAX_SERIAL_PORTS) {
+ hw_error("Only %d serial ports are supported by QEMU.\n",
+ MAX_SERIAL_PORTS);
+ }
+ chr = serial_hds[channel];
+ if (!chr) {
+ snprintf(label, ARRAY_SIZE(label), "%s%d", chr_name, channel);
+ chr = qemu_chr_new(label, "null", NULL);
+ if (!(chr)) {
+ hw_error("Can't assign serial port to UART%d.\n", channel);
+ }
+ }
+ }
+
+ qdev_prop_set_chr(dev, "chardev", chr);
+ qdev_prop_set_uint32(dev, "channel", channel);
+ qdev_prop_set_uint32(dev, "rx-size", fifo_size);
+ qdev_prop_set_uint32(dev, "tx-size", fifo_size);
+
+ bus = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ if (addr != (hwaddr)-1) {
+ sysbus_mmio_map(bus, 0, addr);
+ }
+ sysbus_connect_irq(bus, 0, irq);
+
+ return dev;
+}
+
+static int exynos4210_uart_init(SysBusDevice *dev)
+{
+ Exynos4210UartState *s = EXYNOS4210_UART(dev);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_uart_ops, s,
+ "exynos4210.uart", EXYNOS4210_UART_REGS_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ qemu_chr_add_handlers(s->chr, exynos4210_uart_can_receive,
+ exynos4210_uart_receive, exynos4210_uart_event, s);
+
+ return 0;
+}
+
+static Property exynos4210_uart_properties[] = {
+ DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr),
+ DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0),
+ DEFINE_PROP_UINT32("rx-size", Exynos4210UartState, rx.size, 16),
+ DEFINE_PROP_UINT32("tx-size", Exynos4210UartState, tx.size, 16),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void exynos4210_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_uart_init;
+ dc->reset = exynos4210_uart_reset;
+ dc->props = exynos4210_uart_properties;
+ dc->vmsd = &vmstate_exynos4210_uart;
+}
+
+static const TypeInfo exynos4210_uart_info = {
+ .name = TYPE_EXYNOS4210_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210UartState),
+ .class_init = exynos4210_uart_class_init,
+};
+
+static void exynos4210_uart_register(void)
+{
+ type_register_static(&exynos4210_uart_info);
+}
+
+type_init(exynos4210_uart_register)
diff --git a/hw/char/grlib_apbuart.c b/hw/char/grlib_apbuart.c
new file mode 100644
index 00000000..35ef6617
--- /dev/null
+++ b/hw/char/grlib_apbuart.c
@@ -0,0 +1,298 @@
+/*
+ * QEMU GRLIB APB UART Emulator
+ *
+ * Copyright (c) 2010-2011 AdaCore
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+
+#include "trace.h"
+
+#define UART_REG_SIZE 20 /* Size of memory mapped registers */
+
+/* UART status register fields */
+#define UART_DATA_READY (1 << 0)
+#define UART_TRANSMIT_SHIFT_EMPTY (1 << 1)
+#define UART_TRANSMIT_FIFO_EMPTY (1 << 2)
+#define UART_BREAK_RECEIVED (1 << 3)
+#define UART_OVERRUN (1 << 4)
+#define UART_PARITY_ERROR (1 << 5)
+#define UART_FRAMING_ERROR (1 << 6)
+#define UART_TRANSMIT_FIFO_HALF (1 << 7)
+#define UART_RECEIVE_FIFO_HALF (1 << 8)
+#define UART_TRANSMIT_FIFO_FULL (1 << 9)
+#define UART_RECEIVE_FIFO_FULL (1 << 10)
+
+/* UART control register fields */
+#define UART_RECEIVE_ENABLE (1 << 0)
+#define UART_TRANSMIT_ENABLE (1 << 1)
+#define UART_RECEIVE_INTERRUPT (1 << 2)
+#define UART_TRANSMIT_INTERRUPT (1 << 3)
+#define UART_PARITY_SELECT (1 << 4)
+#define UART_PARITY_ENABLE (1 << 5)
+#define UART_FLOW_CONTROL (1 << 6)
+#define UART_LOOPBACK (1 << 7)
+#define UART_EXTERNAL_CLOCK (1 << 8)
+#define UART_RECEIVE_FIFO_INTERRUPT (1 << 9)
+#define UART_TRANSMIT_FIFO_INTERRUPT (1 << 10)
+#define UART_FIFO_DEBUG_MODE (1 << 11)
+#define UART_OUTPUT_ENABLE (1 << 12)
+#define UART_FIFO_AVAILABLE (1 << 31)
+
+/* Memory mapped register offsets */
+#define DATA_OFFSET 0x00
+#define STATUS_OFFSET 0x04
+#define CONTROL_OFFSET 0x08
+#define SCALER_OFFSET 0x0C /* not supported */
+#define FIFO_DEBUG_OFFSET 0x10 /* not supported */
+
+#define FIFO_LENGTH 1024
+
+#define TYPE_GRLIB_APB_UART "grlib,apbuart"
+#define GRLIB_APB_UART(obj) \
+ OBJECT_CHECK(UART, (obj), TYPE_GRLIB_APB_UART)
+
+typedef struct UART {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ CharDriverState *chr;
+
+ /* registers */
+ uint32_t status;
+ uint32_t control;
+
+ /* FIFO */
+ char buffer[FIFO_LENGTH];
+ int len;
+ int current;
+} UART;
+
+static int uart_data_to_read(UART *uart)
+{
+ return uart->current < uart->len;
+}
+
+static char uart_pop(UART *uart)
+{
+ char ret;
+
+ if (uart->len == 0) {
+ uart->status &= ~UART_DATA_READY;
+ return 0;
+ }
+
+ ret = uart->buffer[uart->current++];
+
+ if (uart->current >= uart->len) {
+ /* Flush */
+ uart->len = 0;
+ uart->current = 0;
+ }
+
+ if (!uart_data_to_read(uart)) {
+ uart->status &= ~UART_DATA_READY;
+ }
+
+ return ret;
+}
+
+static void uart_add_to_fifo(UART *uart,
+ const uint8_t *buffer,
+ int length)
+{
+ if (uart->len + length > FIFO_LENGTH) {
+ abort();
+ }
+ memcpy(uart->buffer + uart->len, buffer, length);
+ uart->len += length;
+}
+
+static int grlib_apbuart_can_receive(void *opaque)
+{
+ UART *uart = opaque;
+
+ return FIFO_LENGTH - uart->len;
+}
+
+static void grlib_apbuart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ UART *uart = opaque;
+
+ if (uart->control & UART_RECEIVE_ENABLE) {
+ uart_add_to_fifo(uart, buf, size);
+
+ uart->status |= UART_DATA_READY;
+
+ if (uart->control & UART_RECEIVE_INTERRUPT) {
+ qemu_irq_pulse(uart->irq);
+ }
+ }
+}
+
+static void grlib_apbuart_event(void *opaque, int event)
+{
+ trace_grlib_apbuart_event(event);
+}
+
+
+static uint64_t grlib_apbuart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ UART *uart = opaque;
+
+ addr &= 0xff;
+
+ /* Unit registers */
+ switch (addr) {
+ case DATA_OFFSET:
+ case DATA_OFFSET + 3: /* when only one byte read */
+ return uart_pop(uart);
+
+ case STATUS_OFFSET:
+ /* Read Only */
+ return uart->status;
+
+ case CONTROL_OFFSET:
+ return uart->control;
+
+ case SCALER_OFFSET:
+ /* Not supported */
+ return 0;
+
+ default:
+ trace_grlib_apbuart_readl_unknown(addr);
+ return 0;
+ }
+}
+
+static void grlib_apbuart_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ UART *uart = opaque;
+ unsigned char c = 0;
+
+ addr &= 0xff;
+
+ /* Unit registers */
+ switch (addr) {
+ case DATA_OFFSET:
+ case DATA_OFFSET + 3: /* When only one byte write */
+ /* Transmit when character device available and transmitter enabled */
+ if ((uart->chr) && (uart->control & UART_TRANSMIT_ENABLE)) {
+ c = value & 0xFF;
+ qemu_chr_fe_write(uart->chr, &c, 1);
+ /* Generate interrupt */
+ if (uart->control & UART_TRANSMIT_INTERRUPT) {
+ qemu_irq_pulse(uart->irq);
+ }
+ }
+ return;
+
+ case STATUS_OFFSET:
+ /* Read Only */
+ return;
+
+ case CONTROL_OFFSET:
+ uart->control = value;
+ return;
+
+ case SCALER_OFFSET:
+ /* Not supported */
+ return;
+
+ default:
+ break;
+ }
+
+ trace_grlib_apbuart_writel_unknown(addr, value);
+}
+
+static const MemoryRegionOps grlib_apbuart_ops = {
+ .write = grlib_apbuart_write,
+ .read = grlib_apbuart_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int grlib_apbuart_init(SysBusDevice *dev)
+{
+ UART *uart = GRLIB_APB_UART(dev);
+
+ qemu_chr_add_handlers(uart->chr,
+ grlib_apbuart_can_receive,
+ grlib_apbuart_receive,
+ grlib_apbuart_event,
+ uart);
+
+ sysbus_init_irq(dev, &uart->irq);
+
+ memory_region_init_io(&uart->iomem, OBJECT(uart), &grlib_apbuart_ops, uart,
+ "uart", UART_REG_SIZE);
+
+ sysbus_init_mmio(dev, &uart->iomem);
+
+ return 0;
+}
+
+static void grlib_apbuart_reset(DeviceState *d)
+{
+ UART *uart = GRLIB_APB_UART(d);
+
+ /* Transmitter FIFO and shift registers are always empty in QEMU */
+ uart->status = UART_TRANSMIT_FIFO_EMPTY | UART_TRANSMIT_SHIFT_EMPTY;
+ /* Everything is off */
+ uart->control = 0;
+ /* Flush receive FIFO */
+ uart->len = 0;
+ uart->current = 0;
+}
+
+static Property grlib_apbuart_properties[] = {
+ DEFINE_PROP_CHR("chrdev", UART, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void grlib_apbuart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = grlib_apbuart_init;
+ dc->reset = grlib_apbuart_reset;
+ dc->props = grlib_apbuart_properties;
+}
+
+static const TypeInfo grlib_apbuart_info = {
+ .name = TYPE_GRLIB_APB_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(UART),
+ .class_init = grlib_apbuart_class_init,
+};
+
+static void grlib_apbuart_register_types(void)
+{
+ type_register_static(&grlib_apbuart_info);
+}
+
+type_init(grlib_apbuart_register_types)
diff --git a/hw/char/imx_serial.c b/hw/char/imx_serial.c
new file mode 100644
index 00000000..f3fbc776
--- /dev/null
+++ b/hw/char/imx_serial.c
@@ -0,0 +1,472 @@
+/*
+ * IMX31 UARTS
+ *
+ * Copyright (c) 2008 OKL
+ * Originally Written by Hans Jiang
+ * Copyright (c) 2011 NICTA Pty Ltd.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * This is a `bare-bones' implementation of the IMX series serial ports.
+ * TODO:
+ * -- implement FIFOs. The real hardware has 32 word transmit
+ * and receive FIFOs; we currently use a 1-char buffer
+ * -- implement DMA
+ * -- implement BAUD-rate and modem lines, for when the backend
+ * is a real serial device.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/char.h"
+#include "hw/arm/imx.h"
+
+//#define DEBUG_SERIAL 1
+#ifdef DEBUG_SERIAL
+#define DPRINTF(fmt, args...) \
+do { printf("imx_serial: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Define to 1 for messages about attempts to
+ * access unimplemented registers or similar.
+ */
+//#define DEBUG_IMPLEMENTATION 1
+#ifdef DEBUG_IMPLEMENTATION
+# define IPRINTF(fmt, args...) \
+ do { fprintf(stderr, "imx_serial: " fmt, ##args); } while (0)
+#else
+# define IPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define TYPE_IMX_SERIAL "imx-serial"
+#define IMX_SERIAL(obj) OBJECT_CHECK(IMXSerialState, (obj), TYPE_IMX_SERIAL)
+
+typedef struct IMXSerialState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ int32_t readbuff;
+
+ uint32_t usr1;
+ uint32_t usr2;
+ uint32_t ucr1;
+ uint32_t ucr2;
+ uint32_t uts1;
+
+ /*
+ * The registers below are implemented just so that the
+ * guest OS sees what it has written
+ */
+ uint32_t onems;
+ uint32_t ufcr;
+ uint32_t ubmr;
+ uint32_t ubrc;
+ uint32_t ucr3;
+
+ qemu_irq irq;
+ CharDriverState *chr;
+} IMXSerialState;
+
+static const VMStateDescription vmstate_imx_serial = {
+ .name = "imx-serial",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(readbuff, IMXSerialState),
+ VMSTATE_UINT32(usr1, IMXSerialState),
+ VMSTATE_UINT32(usr2, IMXSerialState),
+ VMSTATE_UINT32(ucr1, IMXSerialState),
+ VMSTATE_UINT32(uts1, IMXSerialState),
+ VMSTATE_UINT32(onems, IMXSerialState),
+ VMSTATE_UINT32(ufcr, IMXSerialState),
+ VMSTATE_UINT32(ubmr, IMXSerialState),
+ VMSTATE_UINT32(ubrc, IMXSerialState),
+ VMSTATE_UINT32(ucr3, IMXSerialState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+
+#define URXD_CHARRDY (1<<15) /* character read is valid */
+#define URXD_ERR (1<<14) /* Character has error */
+#define URXD_BRK (1<<11) /* Break received */
+
+#define USR1_PARTYER (1<<15) /* Parity Error */
+#define USR1_RTSS (1<<14) /* RTS pin status */
+#define USR1_TRDY (1<<13) /* Tx ready */
+#define USR1_RTSD (1<<12) /* RTS delta: pin changed state */
+#define USR1_ESCF (1<<11) /* Escape sequence interrupt */
+#define USR1_FRAMERR (1<<10) /* Framing error */
+#define USR1_RRDY (1<<9) /* receiver ready */
+#define USR1_AGTIM (1<<8) /* Aging timer interrupt */
+#define USR1_DTRD (1<<7) /* DTR changed */
+#define USR1_RXDS (1<<6) /* Receiver is idle */
+#define USR1_AIRINT (1<<5) /* Aysnch IR interrupt */
+#define USR1_AWAKE (1<<4) /* Falling edge detected on RXd pin */
+
+#define USR2_ADET (1<<15) /* Autobaud complete */
+#define USR2_TXFE (1<<14) /* Transmit FIFO empty */
+#define USR2_DTRF (1<<13) /* DTR/DSR transition */
+#define USR2_IDLE (1<<12) /* UART has been idle for too long */
+#define USR2_ACST (1<<11) /* Autobaud counter stopped */
+#define USR2_RIDELT (1<<10) /* Ring Indicator delta */
+#define USR2_RIIN (1<<9) /* Ring Indicator Input */
+#define USR2_IRINT (1<<8) /* Serial Infrared Interrupt */
+#define USR2_WAKE (1<<7) /* Start bit detected */
+#define USR2_DCDDELT (1<<6) /* Data Carrier Detect delta */
+#define USR2_DCDIN (1<<5) /* Data Carrier Detect Input */
+#define USR2_RTSF (1<<4) /* RTS transition */
+#define USR2_TXDC (1<<3) /* Transmission complete */
+#define USR2_BRCD (1<<2) /* Break condition detected */
+#define USR2_ORE (1<<1) /* Overrun error */
+#define USR2_RDR (1<<0) /* Receive data ready */
+
+#define UCR1_TRDYEN (1<<13) /* Tx Ready Interrupt Enable */
+#define UCR1_RRDYEN (1<<9) /* Rx Ready Interrupt Enable */
+#define UCR1_TXMPTYEN (1<<6) /* Tx Empty Interrupt Enable */
+#define UCR1_UARTEN (1<<0) /* UART Enable */
+
+#define UCR2_TXEN (1<<2) /* Transmitter enable */
+#define UCR2_RXEN (1<<1) /* Receiver enable */
+#define UCR2_SRST (1<<0) /* Reset complete */
+
+#define UTS1_TXEMPTY (1<<6)
+#define UTS1_RXEMPTY (1<<5)
+#define UTS1_TXFULL (1<<4)
+#define UTS1_RXFULL (1<<3)
+
+static void imx_update(IMXSerialState *s)
+{
+ uint32_t flags;
+
+ flags = (s->usr1 & s->ucr1) & (USR1_TRDY|USR1_RRDY);
+ if (!(s->ucr1 & UCR1_TXMPTYEN)) {
+ flags &= ~USR1_TRDY;
+ }
+
+ qemu_set_irq(s->irq, !!flags);
+}
+
+static void imx_serial_reset(IMXSerialState *s)
+{
+
+ s->usr1 = USR1_TRDY | USR1_RXDS;
+ /*
+ * Fake attachment of a terminal: assert RTS.
+ */
+ s->usr1 |= USR1_RTSS;
+ s->usr2 = USR2_TXFE | USR2_TXDC | USR2_DCDIN;
+ s->uts1 = UTS1_RXEMPTY | UTS1_TXEMPTY;
+ s->ucr1 = 0;
+ s->ucr2 = UCR2_SRST;
+ s->ucr3 = 0x700;
+ s->ubmr = 0;
+ s->ubrc = 4;
+ s->readbuff = URXD_ERR;
+}
+
+static void imx_serial_reset_at_boot(DeviceState *dev)
+{
+ IMXSerialState *s = IMX_SERIAL(dev);
+
+ imx_serial_reset(s);
+
+ /*
+ * enable the uart on boot, so messages from the linux decompresser
+ * are visible. On real hardware this is done by the boot rom
+ * before anything else is loaded.
+ */
+ s->ucr1 = UCR1_UARTEN;
+ s->ucr2 = UCR2_TXEN;
+
+}
+
+static uint64_t imx_serial_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ IMXSerialState *s = (IMXSerialState *)opaque;
+ uint32_t c;
+
+ DPRINTF("read(offset=%x)\n", offset >> 2);
+ switch (offset >> 2) {
+ case 0x0: /* URXD */
+ c = s->readbuff;
+ if (!(s->uts1 & UTS1_RXEMPTY)) {
+ /* Character is valid */
+ c |= URXD_CHARRDY;
+ s->usr1 &= ~USR1_RRDY;
+ s->usr2 &= ~USR2_RDR;
+ s->uts1 |= UTS1_RXEMPTY;
+ imx_update(s);
+ qemu_chr_accept_input(s->chr);
+ }
+ return c;
+
+ case 0x20: /* UCR1 */
+ return s->ucr1;
+
+ case 0x21: /* UCR2 */
+ return s->ucr2;
+
+ case 0x25: /* USR1 */
+ return s->usr1;
+
+ case 0x26: /* USR2 */
+ return s->usr2;
+
+ case 0x2A: /* BRM Modulator */
+ return s->ubmr;
+
+ case 0x2B: /* Baud Rate Count */
+ return s->ubrc;
+
+ case 0x2d: /* Test register */
+ return s->uts1;
+
+ case 0x24: /* UFCR */
+ return s->ufcr;
+
+ case 0x2c:
+ return s->onems;
+
+ case 0x22: /* UCR3 */
+ return s->ucr3;
+
+ case 0x23: /* UCR4 */
+ case 0x29: /* BRM Incremental */
+ return 0x0; /* TODO */
+
+ default:
+ IPRINTF("imx_serial_read: bad offset: 0x%x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void imx_serial_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IMXSerialState *s = (IMXSerialState *)opaque;
+ unsigned char ch;
+
+ DPRINTF("write(offset=%x, value = %x) to %s\n",
+ offset >> 2,
+ (unsigned int)value, s->chr ? s->chr->label : "NODEV");
+
+ switch (offset >> 2) {
+ case 0x10: /* UTXD */
+ ch = value;
+ if (s->ucr2 & UCR2_TXEN) {
+ if (s->chr) {
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ }
+ s->usr1 &= ~USR1_TRDY;
+ imx_update(s);
+ s->usr1 |= USR1_TRDY;
+ imx_update(s);
+ }
+ break;
+
+ case 0x20: /* UCR1 */
+ s->ucr1 = value & 0xffff;
+ DPRINTF("write(ucr1=%x)\n", (unsigned int)value);
+ imx_update(s);
+ break;
+
+ case 0x21: /* UCR2 */
+ /*
+ * Only a few bits in control register 2 are implemented as yet.
+ * If it's intended to use a real serial device as a back-end, this
+ * register will have to be implemented more fully.
+ */
+ if (!(value & UCR2_SRST)) {
+ imx_serial_reset(s);
+ imx_update(s);
+ value |= UCR2_SRST;
+ }
+ if (value & UCR2_RXEN) {
+ if (!(s->ucr2 & UCR2_RXEN)) {
+ qemu_chr_accept_input(s->chr);
+ }
+ }
+ s->ucr2 = value & 0xffff;
+ break;
+
+ case 0x25: /* USR1 */
+ value &= USR1_AWAKE | USR1_AIRINT | USR1_DTRD | USR1_AGTIM |
+ USR1_FRAMERR | USR1_ESCF | USR1_RTSD | USR1_PARTYER;
+ s->usr1 &= ~value;
+ break;
+
+ case 0x26: /* USR2 */
+ /*
+ * Writing 1 to some bits clears them; all other
+ * values are ignored
+ */
+ value &= USR2_ADET | USR2_DTRF | USR2_IDLE | USR2_ACST |
+ USR2_RIDELT | USR2_IRINT | USR2_WAKE |
+ USR2_DCDDELT | USR2_RTSF | USR2_BRCD | USR2_ORE;
+ s->usr2 &= ~value;
+ break;
+
+ /*
+ * Linux expects to see what it writes to these registers
+ * We don't currently alter the baud rate
+ */
+ case 0x29: /* UBIR */
+ s->ubrc = value & 0xffff;
+ break;
+
+ case 0x2a: /* UBMR */
+ s->ubmr = value & 0xffff;
+ break;
+
+ case 0x2c: /* One ms reg */
+ s->onems = value & 0xffff;
+ break;
+
+ case 0x24: /* FIFO control register */
+ s->ufcr = value & 0xffff;
+ break;
+
+ case 0x22: /* UCR3 */
+ s->ucr3 = value & 0xffff;
+ break;
+
+ case 0x2d: /* UTS1 */
+ case 0x23: /* UCR4 */
+ IPRINTF("Unimplemented Register %x written to\n", offset >> 2);
+ /* TODO */
+ break;
+
+ default:
+ IPRINTF("imx_serial_write: Bad offset 0x%x\n", (int)offset);
+ }
+}
+
+static int imx_can_receive(void *opaque)
+{
+ IMXSerialState *s = (IMXSerialState *)opaque;
+ return !(s->usr1 & USR1_RRDY);
+}
+
+static void imx_put_data(void *opaque, uint32_t value)
+{
+ IMXSerialState *s = (IMXSerialState *)opaque;
+ DPRINTF("received char\n");
+ s->usr1 |= USR1_RRDY;
+ s->usr2 |= USR2_RDR;
+ s->uts1 &= ~UTS1_RXEMPTY;
+ s->readbuff = value;
+ imx_update(s);
+}
+
+static void imx_receive(void *opaque, const uint8_t *buf, int size)
+{
+ imx_put_data(opaque, *buf);
+}
+
+static void imx_event(void *opaque, int event)
+{
+ if (event == CHR_EVENT_BREAK) {
+ imx_put_data(opaque, URXD_BRK);
+ }
+}
+
+
+static const struct MemoryRegionOps imx_serial_ops = {
+ .read = imx_serial_read,
+ .write = imx_serial_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int imx_serial_init(SysBusDevice *dev)
+{
+ IMXSerialState *s = IMX_SERIAL(dev);
+
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &imx_serial_ops, s,
+ "imx-serial", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, imx_can_receive, imx_receive,
+ imx_event, s);
+ } else {
+ DPRINTF("No char dev for uart at 0x%lx\n",
+ (unsigned long)s->iomem.ram_addr);
+ }
+
+ return 0;
+}
+
+void imx_serial_create(int uart, const hwaddr addr, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *bus;
+ CharDriverState *chr;
+ const char chr_name[] = "serial";
+ char label[ARRAY_SIZE(chr_name) + 1];
+
+ dev = qdev_create(NULL, TYPE_IMX_SERIAL);
+
+ if (uart >= MAX_SERIAL_PORTS) {
+ hw_error("Cannot assign uart %d: QEMU supports only %d ports\n",
+ uart, MAX_SERIAL_PORTS);
+ }
+ chr = serial_hds[uart];
+ if (!chr) {
+ snprintf(label, ARRAY_SIZE(label), "%s%d", chr_name, uart);
+ chr = qemu_chr_new(label, "null", NULL);
+ if (!(chr)) {
+ hw_error("Can't assign serial port to imx-uart%d.\n", uart);
+ }
+ }
+
+ qdev_prop_set_chr(dev, "chardev", chr);
+ bus = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ if (addr != (hwaddr)-1) {
+ sysbus_mmio_map(bus, 0, addr);
+ }
+ sysbus_connect_irq(bus, 0, irq);
+
+}
+
+
+static Property imx32_serial_properties[] = {
+ DEFINE_PROP_CHR("chardev", IMXSerialState, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void imx_serial_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = imx_serial_init;
+ dc->vmsd = &vmstate_imx_serial;
+ dc->reset = imx_serial_reset_at_boot;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "i.MX series UART";
+ dc->props = imx32_serial_properties;
+}
+
+static const TypeInfo imx_serial_info = {
+ .name = TYPE_IMX_SERIAL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXSerialState),
+ .class_init = imx_serial_class_init,
+};
+
+static void imx_serial_register_types(void)
+{
+ type_register_static(&imx_serial_info);
+}
+
+type_init(imx_serial_register_types)
diff --git a/hw/char/ipoctal232.c b/hw/char/ipoctal232.c
new file mode 100644
index 00000000..c8d5cdb3
--- /dev/null
+++ b/hw/char/ipoctal232.c
@@ -0,0 +1,603 @@
+/*
+ * QEMU GE IP-Octal 232 IndustryPack emulation
+ *
+ * Copyright (C) 2012 Igalia, S.L.
+ * Author: Alberto Garcia <agarcia@igalia.com>
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any
+ * later version.
+ */
+
+#include "hw/ipack/ipack.h"
+#include "qemu/bitops.h"
+#include "sysemu/char.h"
+
+/* #define DEBUG_IPOCTAL */
+
+#ifdef DEBUG_IPOCTAL
+#define DPRINTF2(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF2(fmt, ...) do { } while (0)
+#endif
+
+#define DPRINTF(fmt, ...) DPRINTF2("IP-Octal: " fmt, ## __VA_ARGS__)
+
+#define RX_FIFO_SIZE 3
+
+/* The IP-Octal has 8 channels (a-h)
+ divided into 4 blocks (A-D) */
+#define N_CHANNELS 8
+#define N_BLOCKS 4
+
+#define REG_MRa 0x01
+#define REG_MRb 0x11
+#define REG_SRa 0x03
+#define REG_SRb 0x13
+#define REG_CSRa 0x03
+#define REG_CSRb 0x13
+#define REG_CRa 0x05
+#define REG_CRb 0x15
+#define REG_RHRa 0x07
+#define REG_RHRb 0x17
+#define REG_THRa 0x07
+#define REG_THRb 0x17
+#define REG_ACR 0x09
+#define REG_ISR 0x0B
+#define REG_IMR 0x0B
+#define REG_OPCR 0x1B
+
+#define CR_ENABLE_RX BIT(0)
+#define CR_DISABLE_RX BIT(1)
+#define CR_ENABLE_TX BIT(2)
+#define CR_DISABLE_TX BIT(3)
+#define CR_CMD(cr) ((cr) >> 4)
+#define CR_NO_OP 0
+#define CR_RESET_MR 1
+#define CR_RESET_RX 2
+#define CR_RESET_TX 3
+#define CR_RESET_ERR 4
+#define CR_RESET_BRKINT 5
+#define CR_START_BRK 6
+#define CR_STOP_BRK 7
+#define CR_ASSERT_RTSN 8
+#define CR_NEGATE_RTSN 9
+#define CR_TIMEOUT_ON 10
+#define CR_TIMEOUT_OFF 12
+
+#define SR_RXRDY BIT(0)
+#define SR_FFULL BIT(1)
+#define SR_TXRDY BIT(2)
+#define SR_TXEMT BIT(3)
+#define SR_OVERRUN BIT(4)
+#define SR_PARITY BIT(5)
+#define SR_FRAMING BIT(6)
+#define SR_BREAK BIT(7)
+
+#define ISR_TXRDYA BIT(0)
+#define ISR_RXRDYA BIT(1)
+#define ISR_BREAKA BIT(2)
+#define ISR_CNTRDY BIT(3)
+#define ISR_TXRDYB BIT(4)
+#define ISR_RXRDYB BIT(5)
+#define ISR_BREAKB BIT(6)
+#define ISR_MPICHG BIT(7)
+#define ISR_TXRDY(CH) (((CH) & 1) ? BIT(4) : BIT(0))
+#define ISR_RXRDY(CH) (((CH) & 1) ? BIT(5) : BIT(1))
+#define ISR_BREAK(CH) (((CH) & 1) ? BIT(6) : BIT(2))
+
+typedef struct IPOctalState IPOctalState;
+typedef struct SCC2698Channel SCC2698Channel;
+typedef struct SCC2698Block SCC2698Block;
+
+struct SCC2698Channel {
+ IPOctalState *ipoctal;
+ CharDriverState *dev;
+ bool rx_enabled;
+ uint8_t mr[2];
+ uint8_t mr_idx;
+ uint8_t sr;
+ uint8_t rhr[RX_FIFO_SIZE];
+ uint8_t rhr_idx;
+ uint8_t rx_pending;
+};
+
+struct SCC2698Block {
+ uint8_t imr;
+ uint8_t isr;
+};
+
+struct IPOctalState {
+ IPackDevice parent_obj;
+
+ SCC2698Channel ch[N_CHANNELS];
+ SCC2698Block blk[N_BLOCKS];
+ uint8_t irq_vector;
+};
+
+#define TYPE_IPOCTAL "ipoctal232"
+
+#define IPOCTAL(obj) \
+ OBJECT_CHECK(IPOctalState, (obj), TYPE_IPOCTAL)
+
+static const VMStateDescription vmstate_scc2698_channel = {
+ .name = "scc2698_channel",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(rx_enabled, SCC2698Channel),
+ VMSTATE_UINT8_ARRAY(mr, SCC2698Channel, 2),
+ VMSTATE_UINT8(mr_idx, SCC2698Channel),
+ VMSTATE_UINT8(sr, SCC2698Channel),
+ VMSTATE_UINT8_ARRAY(rhr, SCC2698Channel, RX_FIFO_SIZE),
+ VMSTATE_UINT8(rhr_idx, SCC2698Channel),
+ VMSTATE_UINT8(rx_pending, SCC2698Channel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_scc2698_block = {
+ .name = "scc2698_block",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(imr, SCC2698Block),
+ VMSTATE_UINT8(isr, SCC2698Block),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ipoctal = {
+ .name = "ipoctal232",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_IPACK_DEVICE(parent_obj, IPOctalState),
+ VMSTATE_STRUCT_ARRAY(ch, IPOctalState, N_CHANNELS, 1,
+ vmstate_scc2698_channel, SCC2698Channel),
+ VMSTATE_STRUCT_ARRAY(blk, IPOctalState, N_BLOCKS, 1,
+ vmstate_scc2698_block, SCC2698Block),
+ VMSTATE_UINT8(irq_vector, IPOctalState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* data[10] is 0x0C, not 0x0B as the doc says */
+static const uint8_t id_prom_data[] = {
+ 0x49, 0x50, 0x41, 0x43, 0xF0, 0x22,
+ 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xCC
+};
+
+static void update_irq(IPOctalState *dev, unsigned block)
+{
+ IPackDevice *idev = IPACK_DEVICE(dev);
+ /* Blocks A and B interrupt on INT0#, C and D on INT1#.
+ Thus, to get the status we have to check two blocks. */
+ SCC2698Block *blk0 = &dev->blk[block];
+ SCC2698Block *blk1 = &dev->blk[block^1];
+ unsigned intno = block / 2;
+
+ if ((blk0->isr & blk0->imr) || (blk1->isr & blk1->imr)) {
+ qemu_irq_raise(idev->irq[intno]);
+ } else {
+ qemu_irq_lower(idev->irq[intno]);
+ }
+}
+
+static void write_cr(IPOctalState *dev, unsigned channel, uint8_t val)
+{
+ SCC2698Channel *ch = &dev->ch[channel];
+ SCC2698Block *blk = &dev->blk[channel / 2];
+
+ DPRINTF("Write CR%c %u: ", channel + 'a', val);
+
+ /* The lower 4 bits are used to enable and disable Tx and Rx */
+ if (val & CR_ENABLE_RX) {
+ DPRINTF2("Rx on, ");
+ ch->rx_enabled = true;
+ }
+ if (val & CR_DISABLE_RX) {
+ DPRINTF2("Rx off, ");
+ ch->rx_enabled = false;
+ }
+ if (val & CR_ENABLE_TX) {
+ DPRINTF2("Tx on, ");
+ ch->sr |= SR_TXRDY | SR_TXEMT;
+ blk->isr |= ISR_TXRDY(channel);
+ }
+ if (val & CR_DISABLE_TX) {
+ DPRINTF2("Tx off, ");
+ ch->sr &= ~(SR_TXRDY | SR_TXEMT);
+ blk->isr &= ~ISR_TXRDY(channel);
+ }
+
+ DPRINTF2("cmd: ");
+
+ /* The rest of the bits implement different commands */
+ switch (CR_CMD(val)) {
+ case CR_NO_OP:
+ DPRINTF2("none");
+ break;
+ case CR_RESET_MR:
+ DPRINTF2("reset MR");
+ ch->mr_idx = 0;
+ break;
+ case CR_RESET_RX:
+ DPRINTF2("reset Rx");
+ ch->rx_enabled = false;
+ ch->rx_pending = 0;
+ ch->sr &= ~SR_RXRDY;
+ blk->isr &= ~ISR_RXRDY(channel);
+ break;
+ case CR_RESET_TX:
+ DPRINTF2("reset Tx");
+ ch->sr &= ~(SR_TXRDY | SR_TXEMT);
+ blk->isr &= ~ISR_TXRDY(channel);
+ break;
+ case CR_RESET_ERR:
+ DPRINTF2("reset err");
+ ch->sr &= ~(SR_OVERRUN | SR_PARITY | SR_FRAMING | SR_BREAK);
+ break;
+ case CR_RESET_BRKINT:
+ DPRINTF2("reset brk ch int");
+ blk->isr &= ~(ISR_BREAKA | ISR_BREAKB);
+ break;
+ default:
+ DPRINTF2("unsupported 0x%x", CR_CMD(val));
+ }
+
+ DPRINTF2("\n");
+}
+
+static uint16_t io_read(IPackDevice *ip, uint8_t addr)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ uint16_t ret = 0;
+ /* addr[7:6]: block (A-D)
+ addr[7:5]: channel (a-h)
+ addr[5:0]: register */
+ unsigned block = addr >> 5;
+ unsigned channel = addr >> 4;
+ /* Big endian, accessed using 8-bit bytes at odd locations */
+ unsigned offset = (addr & 0x1F) ^ 1;
+ SCC2698Channel *ch = &dev->ch[channel];
+ SCC2698Block *blk = &dev->blk[block];
+ uint8_t old_isr = blk->isr;
+
+ switch (offset) {
+
+ case REG_MRa:
+ case REG_MRb:
+ ret = ch->mr[ch->mr_idx];
+ DPRINTF("Read MR%u%c: 0x%x\n", ch->mr_idx + 1, channel + 'a', ret);
+ ch->mr_idx = 1;
+ break;
+
+ case REG_SRa:
+ case REG_SRb:
+ ret = ch->sr;
+ DPRINTF("Read SR%c: 0x%x\n", channel + 'a', ret);
+ break;
+
+ case REG_RHRa:
+ case REG_RHRb:
+ ret = ch->rhr[ch->rhr_idx];
+ if (ch->rx_pending > 0) {
+ ch->rx_pending--;
+ if (ch->rx_pending == 0) {
+ ch->sr &= ~SR_RXRDY;
+ blk->isr &= ~ISR_RXRDY(channel);
+ if (ch->dev) {
+ qemu_chr_accept_input(ch->dev);
+ }
+ } else {
+ ch->rhr_idx = (ch->rhr_idx + 1) % RX_FIFO_SIZE;
+ }
+ if (ch->sr & SR_BREAK) {
+ ch->sr &= ~SR_BREAK;
+ blk->isr |= ISR_BREAK(channel);
+ }
+ }
+ DPRINTF("Read RHR%c (0x%x)\n", channel + 'a', ret);
+ break;
+
+ case REG_ISR:
+ ret = blk->isr;
+ DPRINTF("Read ISR%c: 0x%x\n", block + 'A', ret);
+ break;
+
+ default:
+ DPRINTF("Read unknown/unsupported register 0x%02x\n", offset);
+ }
+
+ if (old_isr != blk->isr) {
+ update_irq(dev, block);
+ }
+
+ return ret;
+}
+
+static void io_write(IPackDevice *ip, uint8_t addr, uint16_t val)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ unsigned reg = val & 0xFF;
+ /* addr[7:6]: block (A-D)
+ addr[7:5]: channel (a-h)
+ addr[5:0]: register */
+ unsigned block = addr >> 5;
+ unsigned channel = addr >> 4;
+ /* Big endian, accessed using 8-bit bytes at odd locations */
+ unsigned offset = (addr & 0x1F) ^ 1;
+ SCC2698Channel *ch = &dev->ch[channel];
+ SCC2698Block *blk = &dev->blk[block];
+ uint8_t old_isr = blk->isr;
+ uint8_t old_imr = blk->imr;
+
+ switch (offset) {
+
+ case REG_MRa:
+ case REG_MRb:
+ ch->mr[ch->mr_idx] = reg;
+ DPRINTF("Write MR%u%c 0x%x\n", ch->mr_idx + 1, channel + 'a', reg);
+ ch->mr_idx = 1;
+ break;
+
+ /* Not implemented */
+ case REG_CSRa:
+ case REG_CSRb:
+ DPRINTF("Write CSR%c: 0x%x\n", channel + 'a', reg);
+ break;
+
+ case REG_CRa:
+ case REG_CRb:
+ write_cr(dev, channel, reg);
+ break;
+
+ case REG_THRa:
+ case REG_THRb:
+ if (ch->sr & SR_TXRDY) {
+ DPRINTF("Write THR%c (0x%x)\n", channel + 'a', reg);
+ if (ch->dev) {
+ uint8_t thr = reg;
+ qemu_chr_fe_write(ch->dev, &thr, 1);
+ }
+ } else {
+ DPRINTF("Write THR%c (0x%x), Tx disabled\n", channel + 'a', reg);
+ }
+ break;
+
+ /* Not implemented */
+ case REG_ACR:
+ DPRINTF("Write ACR%c 0x%x\n", block + 'A', val);
+ break;
+
+ case REG_IMR:
+ DPRINTF("Write IMR%c 0x%x\n", block + 'A', val);
+ blk->imr = reg;
+ break;
+
+ /* Not implemented */
+ case REG_OPCR:
+ DPRINTF("Write OPCR%c 0x%x\n", block + 'A', val);
+ break;
+
+ default:
+ DPRINTF("Write unknown/unsupported register 0x%02x %u\n", offset, val);
+ }
+
+ if (old_isr != blk->isr || old_imr != blk->imr) {
+ update_irq(dev, block);
+ }
+}
+
+static uint16_t id_read(IPackDevice *ip, uint8_t addr)
+{
+ uint16_t ret = 0;
+ unsigned pos = addr / 2; /* The ID PROM data is stored every other byte */
+
+ if (pos < ARRAY_SIZE(id_prom_data)) {
+ ret = id_prom_data[pos];
+ } else {
+ DPRINTF("Attempt to read unavailable PROM data at 0x%x\n", addr);
+ }
+
+ return ret;
+}
+
+static void id_write(IPackDevice *ip, uint8_t addr, uint16_t val)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ if (addr == 1) {
+ DPRINTF("Write IRQ vector: %u\n", (unsigned) val);
+ dev->irq_vector = val; /* Undocumented, but the hw works like that */
+ } else {
+ DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
+ }
+}
+
+static uint16_t int_read(IPackDevice *ip, uint8_t addr)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ /* Read address 0 to ACK INT0# and address 2 to ACK INT1# */
+ if (addr != 0 && addr != 2) {
+ DPRINTF("Attempt to read from 0x%x\n", addr);
+ return 0;
+ } else {
+ /* Update interrupts if necessary */
+ update_irq(dev, addr);
+ return dev->irq_vector;
+ }
+}
+
+static void int_write(IPackDevice *ip, uint8_t addr, uint16_t val)
+{
+ DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
+}
+
+static uint16_t mem_read16(IPackDevice *ip, uint32_t addr)
+{
+ DPRINTF("Attempt to read from 0x%x\n", addr);
+ return 0;
+}
+
+static void mem_write16(IPackDevice *ip, uint32_t addr, uint16_t val)
+{
+ DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
+}
+
+static uint8_t mem_read8(IPackDevice *ip, uint32_t addr)
+{
+ DPRINTF("Attempt to read from 0x%x\n", addr);
+ return 0;
+}
+
+static void mem_write8(IPackDevice *ip, uint32_t addr, uint8_t val)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ if (addr == 1) {
+ DPRINTF("Write IRQ vector: %u\n", (unsigned) val);
+ dev->irq_vector = val;
+ } else {
+ DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
+ }
+}
+
+static int hostdev_can_receive(void *opaque)
+{
+ SCC2698Channel *ch = opaque;
+ int available_bytes = RX_FIFO_SIZE - ch->rx_pending;
+ return ch->rx_enabled ? available_bytes : 0;
+}
+
+static void hostdev_receive(void *opaque, const uint8_t *buf, int size)
+{
+ SCC2698Channel *ch = opaque;
+ IPOctalState *dev = ch->ipoctal;
+ unsigned pos = ch->rhr_idx + ch->rx_pending;
+ int i;
+
+ assert(size + ch->rx_pending <= RX_FIFO_SIZE);
+
+ /* Copy data to the RxFIFO */
+ for (i = 0; i < size; i++) {
+ pos %= RX_FIFO_SIZE;
+ ch->rhr[pos++] = buf[i];
+ }
+
+ ch->rx_pending += size;
+
+ /* If the RxFIFO was empty raise an interrupt */
+ if (!(ch->sr & SR_RXRDY)) {
+ unsigned block, channel = 0;
+ /* Find channel number to update the ISR register */
+ while (&dev->ch[channel] != ch) {
+ channel++;
+ }
+ block = channel / 2;
+ dev->blk[block].isr |= ISR_RXRDY(channel);
+ ch->sr |= SR_RXRDY;
+ update_irq(dev, block);
+ }
+}
+
+static void hostdev_event(void *opaque, int event)
+{
+ SCC2698Channel *ch = opaque;
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ DPRINTF("Device %s opened\n", ch->dev->label);
+ break;
+ case CHR_EVENT_BREAK: {
+ uint8_t zero = 0;
+ DPRINTF("Device %s received break\n", ch->dev->label);
+
+ if (!(ch->sr & SR_BREAK)) {
+ IPOctalState *dev = ch->ipoctal;
+ unsigned block, channel = 0;
+
+ while (&dev->ch[channel] != ch) {
+ channel++;
+ }
+ block = channel / 2;
+
+ ch->sr |= SR_BREAK;
+ dev->blk[block].isr |= ISR_BREAK(channel);
+ }
+
+ /* Put a zero character in the buffer */
+ hostdev_receive(ch, &zero, 1);
+ }
+ break;
+ default:
+ DPRINTF("Device %s received event %d\n", ch->dev->label, event);
+ }
+}
+
+static void ipoctal_realize(DeviceState *dev, Error **errp)
+{
+ IPOctalState *s = IPOCTAL(dev);
+ unsigned i;
+
+ for (i = 0; i < N_CHANNELS; i++) {
+ SCC2698Channel *ch = &s->ch[i];
+ ch->ipoctal = s;
+
+ /* Redirect IP-Octal channels to host character devices */
+ if (ch->dev) {
+ qemu_chr_add_handlers(ch->dev, hostdev_can_receive,
+ hostdev_receive, hostdev_event, ch);
+ DPRINTF("Redirecting channel %u to %s\n", i, ch->dev->label);
+ } else {
+ DPRINTF("Could not redirect channel %u, no chardev set\n", i);
+ }
+ }
+}
+
+static Property ipoctal_properties[] = {
+ DEFINE_PROP_CHR("chardev0", IPOctalState, ch[0].dev),
+ DEFINE_PROP_CHR("chardev1", IPOctalState, ch[1].dev),
+ DEFINE_PROP_CHR("chardev2", IPOctalState, ch[2].dev),
+ DEFINE_PROP_CHR("chardev3", IPOctalState, ch[3].dev),
+ DEFINE_PROP_CHR("chardev4", IPOctalState, ch[4].dev),
+ DEFINE_PROP_CHR("chardev5", IPOctalState, ch[5].dev),
+ DEFINE_PROP_CHR("chardev6", IPOctalState, ch[6].dev),
+ DEFINE_PROP_CHR("chardev7", IPOctalState, ch[7].dev),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ipoctal_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IPackDeviceClass *ic = IPACK_DEVICE_CLASS(klass);
+
+ ic->realize = ipoctal_realize;
+ ic->io_read = io_read;
+ ic->io_write = io_write;
+ ic->id_read = id_read;
+ ic->id_write = id_write;
+ ic->int_read = int_read;
+ ic->int_write = int_write;
+ ic->mem_read16 = mem_read16;
+ ic->mem_write16 = mem_write16;
+ ic->mem_read8 = mem_read8;
+ ic->mem_write8 = mem_write8;
+
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "GE IP-Octal 232 8-channel RS-232 IndustryPack";
+ dc->props = ipoctal_properties;
+ dc->vmsd = &vmstate_ipoctal;
+}
+
+static const TypeInfo ipoctal_info = {
+ .name = TYPE_IPOCTAL,
+ .parent = TYPE_IPACK_DEVICE,
+ .instance_size = sizeof(IPOctalState),
+ .class_init = ipoctal_class_init,
+};
+
+static void ipoctal_register_types(void)
+{
+ type_register_static(&ipoctal_info);
+}
+
+type_init(ipoctal_register_types)
diff --git a/hw/char/lm32_juart.c b/hw/char/lm32_juart.c
new file mode 100644
index 00000000..62763f2f
--- /dev/null
+++ b/hw/char/lm32_juart.c
@@ -0,0 +1,164 @@
+/*
+ * LatticeMico32 JTAG UART model.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "sysemu/char.h"
+
+#include "hw/char/lm32_juart.h"
+
+enum {
+ LM32_JUART_MIN_SAVE_VERSION = 0,
+ LM32_JUART_CURRENT_SAVE_VERSION = 0,
+ LM32_JUART_MAX_SAVE_VERSION = 0,
+};
+
+enum {
+ JTX_FULL = (1<<8),
+};
+
+enum {
+ JRX_FULL = (1<<8),
+};
+
+#define LM32_JUART(obj) OBJECT_CHECK(LM32JuartState, (obj), TYPE_LM32_JUART)
+
+struct LM32JuartState {
+ SysBusDevice parent_obj;
+
+ CharDriverState *chr;
+
+ uint32_t jtx;
+ uint32_t jrx;
+};
+typedef struct LM32JuartState LM32JuartState;
+
+uint32_t lm32_juart_get_jtx(DeviceState *d)
+{
+ LM32JuartState *s = LM32_JUART(d);
+
+ trace_lm32_juart_get_jtx(s->jtx);
+ return s->jtx;
+}
+
+uint32_t lm32_juart_get_jrx(DeviceState *d)
+{
+ LM32JuartState *s = LM32_JUART(d);
+
+ trace_lm32_juart_get_jrx(s->jrx);
+ return s->jrx;
+}
+
+void lm32_juart_set_jtx(DeviceState *d, uint32_t jtx)
+{
+ LM32JuartState *s = LM32_JUART(d);
+ unsigned char ch = jtx & 0xff;
+
+ trace_lm32_juart_set_jtx(s->jtx);
+
+ s->jtx = jtx;
+ if (s->chr) {
+ qemu_chr_fe_write_all(s->chr, &ch, 1);
+ }
+}
+
+void lm32_juart_set_jrx(DeviceState *d, uint32_t jtx)
+{
+ LM32JuartState *s = LM32_JUART(d);
+
+ trace_lm32_juart_set_jrx(s->jrx);
+ s->jrx &= ~JRX_FULL;
+}
+
+static void juart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ LM32JuartState *s = opaque;
+
+ s->jrx = *buf | JRX_FULL;
+}
+
+static int juart_can_rx(void *opaque)
+{
+ LM32JuartState *s = opaque;
+
+ return !(s->jrx & JRX_FULL);
+}
+
+static void juart_event(void *opaque, int event)
+{
+}
+
+static void juart_reset(DeviceState *d)
+{
+ LM32JuartState *s = LM32_JUART(d);
+
+ s->jtx = 0;
+ s->jrx = 0;
+}
+
+static int lm32_juart_init(SysBusDevice *dev)
+{
+ LM32JuartState *s = LM32_JUART(dev);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, juart_can_rx, juart_rx, juart_event, s);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm32_juart = {
+ .name = "lm32-juart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(jtx, LM32JuartState),
+ VMSTATE_UINT32(jrx, LM32JuartState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void lm32_juart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lm32_juart_init;
+ dc->reset = juart_reset;
+ dc->vmsd = &vmstate_lm32_juart;
+ /* Reason: init() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo lm32_juart_info = {
+ .name = TYPE_LM32_JUART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LM32JuartState),
+ .class_init = lm32_juart_class_init,
+};
+
+static void lm32_juart_register_types(void)
+{
+ type_register_static(&lm32_juart_info);
+}
+
+type_init(lm32_juart_register_types)
diff --git a/hw/char/lm32_uart.c b/hw/char/lm32_uart.c
new file mode 100644
index 00000000..837a46e8
--- /dev/null
+++ b/hw/char/lm32_uart.c
@@ -0,0 +1,304 @@
+/*
+ * QEMU model of the LatticeMico32 UART block.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.latticesemi.com/documents/mico32uart.pdf
+ */
+
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "sysemu/char.h"
+#include "qemu/error-report.h"
+
+enum {
+ R_RXTX = 0,
+ R_IER,
+ R_IIR,
+ R_LCR,
+ R_MCR,
+ R_LSR,
+ R_MSR,
+ R_DIV,
+ R_MAX
+};
+
+enum {
+ IER_RBRI = (1<<0),
+ IER_THRI = (1<<1),
+ IER_RLSI = (1<<2),
+ IER_MSI = (1<<3),
+};
+
+enum {
+ IIR_STAT = (1<<0),
+ IIR_ID0 = (1<<1),
+ IIR_ID1 = (1<<2),
+};
+
+enum {
+ LCR_WLS0 = (1<<0),
+ LCR_WLS1 = (1<<1),
+ LCR_STB = (1<<2),
+ LCR_PEN = (1<<3),
+ LCR_EPS = (1<<4),
+ LCR_SP = (1<<5),
+ LCR_SB = (1<<6),
+};
+
+enum {
+ MCR_DTR = (1<<0),
+ MCR_RTS = (1<<1),
+};
+
+enum {
+ LSR_DR = (1<<0),
+ LSR_OE = (1<<1),
+ LSR_PE = (1<<2),
+ LSR_FE = (1<<3),
+ LSR_BI = (1<<4),
+ LSR_THRE = (1<<5),
+ LSR_TEMT = (1<<6),
+};
+
+enum {
+ MSR_DCTS = (1<<0),
+ MSR_DDSR = (1<<1),
+ MSR_TERI = (1<<2),
+ MSR_DDCD = (1<<3),
+ MSR_CTS = (1<<4),
+ MSR_DSR = (1<<5),
+ MSR_RI = (1<<6),
+ MSR_DCD = (1<<7),
+};
+
+#define TYPE_LM32_UART "lm32-uart"
+#define LM32_UART(obj) OBJECT_CHECK(LM32UartState, (obj), TYPE_LM32_UART)
+
+struct LM32UartState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t regs[R_MAX];
+};
+typedef struct LM32UartState LM32UartState;
+
+static void uart_update_irq(LM32UartState *s)
+{
+ unsigned int irq;
+
+ if ((s->regs[R_LSR] & (LSR_OE | LSR_PE | LSR_FE | LSR_BI))
+ && (s->regs[R_IER] & IER_RLSI)) {
+ irq = 1;
+ s->regs[R_IIR] = IIR_ID1 | IIR_ID0;
+ } else if ((s->regs[R_LSR] & LSR_DR) && (s->regs[R_IER] & IER_RBRI)) {
+ irq = 1;
+ s->regs[R_IIR] = IIR_ID1;
+ } else if ((s->regs[R_LSR] & LSR_THRE) && (s->regs[R_IER] & IER_THRI)) {
+ irq = 1;
+ s->regs[R_IIR] = IIR_ID0;
+ } else if ((s->regs[R_MSR] & 0x0f) && (s->regs[R_IER] & IER_MSI)) {
+ irq = 1;
+ s->regs[R_IIR] = 0;
+ } else {
+ irq = 0;
+ s->regs[R_IIR] = IIR_STAT;
+ }
+
+ trace_lm32_uart_irq_state(irq);
+ qemu_set_irq(s->irq, irq);
+}
+
+static uint64_t uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LM32UartState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RXTX:
+ r = s->regs[R_RXTX];
+ s->regs[R_LSR] &= ~LSR_DR;
+ uart_update_irq(s);
+ qemu_chr_accept_input(s->chr);
+ break;
+ case R_IIR:
+ case R_LSR:
+ case R_MSR:
+ r = s->regs[addr];
+ break;
+ case R_IER:
+ case R_LCR:
+ case R_MCR:
+ case R_DIV:
+ error_report("lm32_uart: read access to write only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ default:
+ error_report("lm32_uart: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_lm32_uart_memory_read(addr << 2, r);
+ return r;
+}
+
+static void uart_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ LM32UartState *s = opaque;
+ unsigned char ch = value;
+
+ trace_lm32_uart_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RXTX:
+ if (s->chr) {
+ qemu_chr_fe_write_all(s->chr, &ch, 1);
+ }
+ break;
+ case R_IER:
+ case R_LCR:
+ case R_MCR:
+ case R_DIV:
+ s->regs[addr] = value;
+ break;
+ case R_IIR:
+ case R_LSR:
+ case R_MSR:
+ error_report("lm32_uart: write access to read only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ default:
+ error_report("lm32_uart: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+ uart_update_irq(s);
+}
+
+static const MemoryRegionOps uart_ops = {
+ .read = uart_read,
+ .write = uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void uart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ LM32UartState *s = opaque;
+
+ if (s->regs[R_LSR] & LSR_DR) {
+ s->regs[R_LSR] |= LSR_OE;
+ }
+
+ s->regs[R_LSR] |= LSR_DR;
+ s->regs[R_RXTX] = *buf;
+
+ uart_update_irq(s);
+}
+
+static int uart_can_rx(void *opaque)
+{
+ LM32UartState *s = opaque;
+
+ return !(s->regs[R_LSR] & LSR_DR);
+}
+
+static void uart_event(void *opaque, int event)
+{
+}
+
+static void uart_reset(DeviceState *d)
+{
+ LM32UartState *s = LM32_UART(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ /* defaults */
+ s->regs[R_LSR] = LSR_THRE | LSR_TEMT;
+}
+
+static int lm32_uart_init(SysBusDevice *dev)
+{
+ LM32UartState *s = LM32_UART(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &uart_ops, s,
+ "uart", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm32_uart = {
+ .name = "lm32-uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, LM32UartState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void lm32_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lm32_uart_init;
+ dc->reset = uart_reset;
+ dc->vmsd = &vmstate_lm32_uart;
+ /* Reason: init() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo lm32_uart_info = {
+ .name = TYPE_LM32_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LM32UartState),
+ .class_init = lm32_uart_class_init,
+};
+
+static void lm32_uart_register_types(void)
+{
+ type_register_static(&lm32_uart_info);
+}
+
+type_init(lm32_uart_register_types)
diff --git a/hw/char/mcf_uart.c b/hw/char/mcf_uart.c
new file mode 100644
index 00000000..98fd44e6
--- /dev/null
+++ b/hw/char/mcf_uart.c
@@ -0,0 +1,307 @@
+/*
+ * ColdFire UART emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+#include "hw/hw.h"
+#include "hw/m68k/mcf.h"
+#include "sysemu/char.h"
+#include "exec/address-spaces.h"
+
+typedef struct {
+ MemoryRegion iomem;
+ uint8_t mr[2];
+ uint8_t sr;
+ uint8_t isr;
+ uint8_t imr;
+ uint8_t bg1;
+ uint8_t bg2;
+ uint8_t fifo[4];
+ uint8_t tb;
+ int current_mr;
+ int fifo_len;
+ int tx_enabled;
+ int rx_enabled;
+ qemu_irq irq;
+ CharDriverState *chr;
+} mcf_uart_state;
+
+/* UART Status Register bits. */
+#define MCF_UART_RxRDY 0x01
+#define MCF_UART_FFULL 0x02
+#define MCF_UART_TxRDY 0x04
+#define MCF_UART_TxEMP 0x08
+#define MCF_UART_OE 0x10
+#define MCF_UART_PE 0x20
+#define MCF_UART_FE 0x40
+#define MCF_UART_RB 0x80
+
+/* Interrupt flags. */
+#define MCF_UART_TxINT 0x01
+#define MCF_UART_RxINT 0x02
+#define MCF_UART_DBINT 0x04
+#define MCF_UART_COSINT 0x80
+
+/* UMR1 flags. */
+#define MCF_UART_BC0 0x01
+#define MCF_UART_BC1 0x02
+#define MCF_UART_PT 0x04
+#define MCF_UART_PM0 0x08
+#define MCF_UART_PM1 0x10
+#define MCF_UART_ERR 0x20
+#define MCF_UART_RxIRQ 0x40
+#define MCF_UART_RxRTS 0x80
+
+static void mcf_uart_update(mcf_uart_state *s)
+{
+ s->isr &= ~(MCF_UART_TxINT | MCF_UART_RxINT);
+ if (s->sr & MCF_UART_TxRDY)
+ s->isr |= MCF_UART_TxINT;
+ if ((s->sr & ((s->mr[0] & MCF_UART_RxIRQ)
+ ? MCF_UART_FFULL : MCF_UART_RxRDY)) != 0)
+ s->isr |= MCF_UART_RxINT;
+
+ qemu_set_irq(s->irq, (s->isr & s->imr) != 0);
+}
+
+uint64_t mcf_uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+ switch (addr & 0x3f) {
+ case 0x00:
+ return s->mr[s->current_mr];
+ case 0x04:
+ return s->sr;
+ case 0x0c:
+ {
+ uint8_t val;
+ int i;
+
+ if (s->fifo_len == 0)
+ return 0;
+
+ val = s->fifo[0];
+ s->fifo_len--;
+ for (i = 0; i < s->fifo_len; i++)
+ s->fifo[i] = s->fifo[i + 1];
+ s->sr &= ~MCF_UART_FFULL;
+ if (s->fifo_len == 0)
+ s->sr &= ~MCF_UART_RxRDY;
+ mcf_uart_update(s);
+ qemu_chr_accept_input(s->chr);
+ return val;
+ }
+ case 0x10:
+ /* TODO: Implement IPCR. */
+ return 0;
+ case 0x14:
+ return s->isr;
+ case 0x18:
+ return s->bg1;
+ case 0x1c:
+ return s->bg2;
+ default:
+ return 0;
+ }
+}
+
+/* Update TxRDY flag and set data if present and enabled. */
+static void mcf_uart_do_tx(mcf_uart_state *s)
+{
+ if (s->tx_enabled && (s->sr & MCF_UART_TxEMP) == 0) {
+ if (s->chr)
+ qemu_chr_fe_write(s->chr, (unsigned char *)&s->tb, 1);
+ s->sr |= MCF_UART_TxEMP;
+ }
+ if (s->tx_enabled) {
+ s->sr |= MCF_UART_TxRDY;
+ } else {
+ s->sr &= ~MCF_UART_TxRDY;
+ }
+}
+
+static void mcf_do_command(mcf_uart_state *s, uint8_t cmd)
+{
+ /* Misc command. */
+ switch ((cmd >> 4) & 3) {
+ case 0: /* No-op. */
+ break;
+ case 1: /* Reset mode register pointer. */
+ s->current_mr = 0;
+ break;
+ case 2: /* Reset receiver. */
+ s->rx_enabled = 0;
+ s->fifo_len = 0;
+ s->sr &= ~(MCF_UART_RxRDY | MCF_UART_FFULL);
+ break;
+ case 3: /* Reset transmitter. */
+ s->tx_enabled = 0;
+ s->sr |= MCF_UART_TxEMP;
+ s->sr &= ~MCF_UART_TxRDY;
+ break;
+ case 4: /* Reset error status. */
+ break;
+ case 5: /* Reset break-change interrupt. */
+ s->isr &= ~MCF_UART_DBINT;
+ break;
+ case 6: /* Start break. */
+ case 7: /* Stop break. */
+ break;
+ }
+
+ /* Transmitter command. */
+ switch ((cmd >> 2) & 3) {
+ case 0: /* No-op. */
+ break;
+ case 1: /* Enable. */
+ s->tx_enabled = 1;
+ mcf_uart_do_tx(s);
+ break;
+ case 2: /* Disable. */
+ s->tx_enabled = 0;
+ mcf_uart_do_tx(s);
+ break;
+ case 3: /* Reserved. */
+ fprintf(stderr, "mcf_uart: Bad TX command\n");
+ break;
+ }
+
+ /* Receiver command. */
+ switch (cmd & 3) {
+ case 0: /* No-op. */
+ break;
+ case 1: /* Enable. */
+ s->rx_enabled = 1;
+ break;
+ case 2:
+ s->rx_enabled = 0;
+ break;
+ case 3: /* Reserved. */
+ fprintf(stderr, "mcf_uart: Bad RX command\n");
+ break;
+ }
+}
+
+void mcf_uart_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+ switch (addr & 0x3f) {
+ case 0x00:
+ s->mr[s->current_mr] = val;
+ s->current_mr = 1;
+ break;
+ case 0x04:
+ /* CSR is ignored. */
+ break;
+ case 0x08: /* Command Register. */
+ mcf_do_command(s, val);
+ break;
+ case 0x0c: /* Transmit Buffer. */
+ s->sr &= ~MCF_UART_TxEMP;
+ s->tb = val;
+ mcf_uart_do_tx(s);
+ break;
+ case 0x10:
+ /* ACR is ignored. */
+ break;
+ case 0x14:
+ s->imr = val;
+ break;
+ default:
+ break;
+ }
+ mcf_uart_update(s);
+}
+
+static void mcf_uart_reset(mcf_uart_state *s)
+{
+ s->fifo_len = 0;
+ s->mr[0] = 0;
+ s->mr[1] = 0;
+ s->sr = MCF_UART_TxEMP;
+ s->tx_enabled = 0;
+ s->rx_enabled = 0;
+ s->isr = 0;
+ s->imr = 0;
+}
+
+static void mcf_uart_push_byte(mcf_uart_state *s, uint8_t data)
+{
+ /* Break events overwrite the last byte if the fifo is full. */
+ if (s->fifo_len == 4)
+ s->fifo_len--;
+
+ s->fifo[s->fifo_len] = data;
+ s->fifo_len++;
+ s->sr |= MCF_UART_RxRDY;
+ if (s->fifo_len == 4)
+ s->sr |= MCF_UART_FFULL;
+
+ mcf_uart_update(s);
+}
+
+static void mcf_uart_event(void *opaque, int event)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+
+ switch (event) {
+ case CHR_EVENT_BREAK:
+ s->isr |= MCF_UART_DBINT;
+ mcf_uart_push_byte(s, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+static int mcf_uart_can_receive(void *opaque)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+
+ return s->rx_enabled && (s->sr & MCF_UART_FFULL) == 0;
+}
+
+static void mcf_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+
+ mcf_uart_push_byte(s, buf[0]);
+}
+
+void *mcf_uart_init(qemu_irq irq, CharDriverState *chr)
+{
+ mcf_uart_state *s;
+
+ s = g_malloc0(sizeof(mcf_uart_state));
+ s->chr = chr;
+ s->irq = irq;
+ if (chr) {
+ qemu_chr_fe_claim_no_fail(chr);
+ qemu_chr_add_handlers(chr, mcf_uart_can_receive, mcf_uart_receive,
+ mcf_uart_event, s);
+ }
+ mcf_uart_reset(s);
+ return s;
+}
+
+static const MemoryRegionOps mcf_uart_ops = {
+ .read = mcf_uart_read,
+ .write = mcf_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void mcf_uart_mm_init(MemoryRegion *sysmem,
+ hwaddr base,
+ qemu_irq irq,
+ CharDriverState *chr)
+{
+ mcf_uart_state *s;
+
+ s = mcf_uart_init(irq, chr);
+ memory_region_init_io(&s->iomem, NULL, &mcf_uart_ops, s, "uart", 0x40);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+}
diff --git a/hw/char/milkymist-uart.c b/hw/char/milkymist-uart.c
new file mode 100644
index 00000000..9b89b7e6
--- /dev/null
+++ b/hw/char/milkymist-uart.c
@@ -0,0 +1,255 @@
+/*
+ * QEMU model of the Milkymist UART block.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/uart.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "sysemu/char.h"
+#include "qemu/error-report.h"
+
+enum {
+ R_RXTX = 0,
+ R_DIV,
+ R_STAT,
+ R_CTRL,
+ R_DBG,
+ R_MAX
+};
+
+enum {
+ STAT_THRE = (1<<0),
+ STAT_RX_EVT = (1<<1),
+ STAT_TX_EVT = (1<<2),
+};
+
+enum {
+ CTRL_RX_IRQ_EN = (1<<0),
+ CTRL_TX_IRQ_EN = (1<<1),
+ CTRL_THRU_EN = (1<<2),
+};
+
+enum {
+ DBG_BREAK_EN = (1<<0),
+};
+
+#define TYPE_MILKYMIST_UART "milkymist-uart"
+#define MILKYMIST_UART(obj) \
+ OBJECT_CHECK(MilkymistUartState, (obj), TYPE_MILKYMIST_UART)
+
+struct MilkymistUartState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t regs[R_MAX];
+};
+typedef struct MilkymistUartState MilkymistUartState;
+
+static void uart_update_irq(MilkymistUartState *s)
+{
+ int rx_event = s->regs[R_STAT] & STAT_RX_EVT;
+ int tx_event = s->regs[R_STAT] & STAT_TX_EVT;
+ int rx_irq_en = s->regs[R_CTRL] & CTRL_RX_IRQ_EN;
+ int tx_irq_en = s->regs[R_CTRL] & CTRL_TX_IRQ_EN;
+
+ if ((rx_irq_en && rx_event) || (tx_irq_en && tx_event)) {
+ trace_milkymist_uart_raise_irq();
+ qemu_irq_raise(s->irq);
+ } else {
+ trace_milkymist_uart_lower_irq();
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static uint64_t uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistUartState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RXTX:
+ r = s->regs[addr];
+ break;
+ case R_DIV:
+ case R_STAT:
+ case R_CTRL:
+ case R_DBG:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_uart: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_uart_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void uart_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistUartState *s = opaque;
+ unsigned char ch = value;
+
+ trace_milkymist_uart_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RXTX:
+ if (s->chr) {
+ qemu_chr_fe_write_all(s->chr, &ch, 1);
+ }
+ s->regs[R_STAT] |= STAT_TX_EVT;
+ break;
+ case R_DIV:
+ case R_CTRL:
+ case R_DBG:
+ s->regs[addr] = value;
+ break;
+
+ case R_STAT:
+ /* write one to clear bits */
+ s->regs[addr] &= ~(value & (STAT_RX_EVT | STAT_TX_EVT));
+ qemu_chr_accept_input(s->chr);
+ break;
+
+ default:
+ error_report("milkymist_uart: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ uart_update_irq(s);
+}
+
+static const MemoryRegionOps uart_mmio_ops = {
+ .read = uart_read,
+ .write = uart_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void uart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ MilkymistUartState *s = opaque;
+
+ assert(!(s->regs[R_STAT] & STAT_RX_EVT));
+
+ s->regs[R_STAT] |= STAT_RX_EVT;
+ s->regs[R_RXTX] = *buf;
+
+ uart_update_irq(s);
+}
+
+static int uart_can_rx(void *opaque)
+{
+ MilkymistUartState *s = opaque;
+
+ return !(s->regs[R_STAT] & STAT_RX_EVT);
+}
+
+static void uart_event(void *opaque, int event)
+{
+}
+
+static void milkymist_uart_reset(DeviceState *d)
+{
+ MilkymistUartState *s = MILKYMIST_UART(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ /* THRE is always set */
+ s->regs[R_STAT] = STAT_THRE;
+}
+
+static void milkymist_uart_realize(DeviceState *dev, Error **errp)
+{
+ MilkymistUartState *s = MILKYMIST_UART(dev);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s);
+ }
+}
+
+static void milkymist_uart_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MilkymistUartState *s = MILKYMIST_UART(obj);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &uart_mmio_ops, s,
+ "milkymist-uart", R_MAX * 4);
+ sysbus_init_mmio(sbd, &s->regs_region);
+}
+
+static const VMStateDescription vmstate_milkymist_uart = {
+ .name = "milkymist-uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistUartState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void milkymist_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = milkymist_uart_realize;
+ dc->reset = milkymist_uart_reset;
+ dc->vmsd = &vmstate_milkymist_uart;
+ /* Reason: realize() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo milkymist_uart_info = {
+ .name = TYPE_MILKYMIST_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistUartState),
+ .instance_init = milkymist_uart_init,
+ .class_init = milkymist_uart_class_init,
+};
+
+static void milkymist_uart_register_types(void)
+{
+ type_register_static(&milkymist_uart_info);
+}
+
+type_init(milkymist_uart_register_types)
diff --git a/hw/char/omap_uart.c b/hw/char/omap_uart.c
new file mode 100644
index 00000000..88f20943
--- /dev/null
+++ b/hw/char/omap_uart.c
@@ -0,0 +1,188 @@
+/*
+ * TI OMAP processors UART emulation.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2007-2009 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "sysemu/char.h"
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+#include "hw/char/serial.h"
+#include "exec/address-spaces.h"
+
+/* UARTs */
+struct omap_uart_s {
+ MemoryRegion iomem;
+ hwaddr base;
+ SerialState *serial; /* TODO */
+ struct omap_target_agent_s *ta;
+ omap_clk fclk;
+ qemu_irq irq;
+
+ uint8_t eblr;
+ uint8_t syscontrol;
+ uint8_t wkup;
+ uint8_t cfps;
+ uint8_t mdr[2];
+ uint8_t scr;
+ uint8_t clksel;
+};
+
+void omap_uart_reset(struct omap_uart_s *s)
+{
+ s->eblr = 0x00;
+ s->syscontrol = 0;
+ s->wkup = 0x3f;
+ s->cfps = 0x69;
+ s->clksel = 0;
+}
+
+struct omap_uart_s *omap_uart_init(hwaddr base,
+ qemu_irq irq, omap_clk fclk, omap_clk iclk,
+ qemu_irq txdma, qemu_irq rxdma,
+ const char *label, CharDriverState *chr)
+{
+ struct omap_uart_s *s = (struct omap_uart_s *)
+ g_malloc0(sizeof(struct omap_uart_s));
+
+ s->base = base;
+ s->fclk = fclk;
+ s->irq = irq;
+ s->serial = serial_mm_init(get_system_memory(), base, 2, irq,
+ omap_clk_getrate(fclk)/16,
+ chr ?: qemu_chr_new(label, "null", NULL),
+ DEVICE_NATIVE_ENDIAN);
+ return s;
+}
+
+static uint64_t omap_uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_uart_s *s = (struct omap_uart_s *) opaque;
+
+ if (size == 4) {
+ return omap_badwidth_read8(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x20: /* MDR1 */
+ return s->mdr[0];
+ case 0x24: /* MDR2 */
+ return s->mdr[1];
+ case 0x40: /* SCR */
+ return s->scr;
+ case 0x44: /* SSR */
+ return 0x0;
+ case 0x48: /* EBLR (OMAP2) */
+ return s->eblr;
+ case 0x4C: /* OSC_12M_SEL (OMAP1) */
+ return s->clksel;
+ case 0x50: /* MVR */
+ return 0x30;
+ case 0x54: /* SYSC (OMAP2) */
+ return s->syscontrol;
+ case 0x58: /* SYSS (OMAP2) */
+ return 1;
+ case 0x5c: /* WER (OMAP2) */
+ return s->wkup;
+ case 0x60: /* CFPS (OMAP2) */
+ return s->cfps;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_uart_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_uart_s *s = (struct omap_uart_s *) opaque;
+
+ if (size == 4) {
+ omap_badwidth_write8(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x20: /* MDR1 */
+ s->mdr[0] = value & 0x7f;
+ break;
+ case 0x24: /* MDR2 */
+ s->mdr[1] = value & 0xff;
+ break;
+ case 0x40: /* SCR */
+ s->scr = value & 0xff;
+ break;
+ case 0x48: /* EBLR (OMAP2) */
+ s->eblr = value & 0xff;
+ break;
+ case 0x4C: /* OSC_12M_SEL (OMAP1) */
+ s->clksel = value & 1;
+ break;
+ case 0x44: /* SSR */
+ case 0x50: /* MVR */
+ case 0x58: /* SYSS (OMAP2) */
+ OMAP_RO_REG(addr);
+ break;
+ case 0x54: /* SYSC (OMAP2) */
+ s->syscontrol = value & 0x1d;
+ if (value & 2)
+ omap_uart_reset(s);
+ break;
+ case 0x5c: /* WER (OMAP2) */
+ s->wkup = value & 0x7f;
+ break;
+ case 0x60: /* CFPS (OMAP2) */
+ s->cfps = value & 0xff;
+ break;
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_uart_ops = {
+ .read = omap_uart_read,
+ .write = omap_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_uart_s *omap2_uart_init(MemoryRegion *sysmem,
+ struct omap_target_agent_s *ta,
+ qemu_irq irq, omap_clk fclk, omap_clk iclk,
+ qemu_irq txdma, qemu_irq rxdma,
+ const char *label, CharDriverState *chr)
+{
+ hwaddr base = omap_l4_attach(ta, 0, NULL);
+ struct omap_uart_s *s = omap_uart_init(base, irq,
+ fclk, iclk, txdma, rxdma, label, chr);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_uart_ops, s, "omap.uart", 0x100);
+
+ s->ta = ta;
+
+ memory_region_add_subregion(sysmem, base + 0x20, &s->iomem);
+
+ return s;
+}
+
+void omap_uart_attach(struct omap_uart_s *s, CharDriverState *chr)
+{
+ /* TODO: Should reuse or destroy current s->serial */
+ s->serial = serial_mm_init(get_system_memory(), s->base, 2, s->irq,
+ omap_clk_getrate(s->fclk) / 16,
+ chr ?: qemu_chr_new("null", "null", NULL),
+ DEVICE_NATIVE_ENDIAN);
+}
diff --git a/hw/char/parallel.c b/hw/char/parallel.c
new file mode 100644
index 00000000..c2b553f0
--- /dev/null
+++ b/hw/char/parallel.c
@@ -0,0 +1,643 @@
+/*
+ * QEMU Parallel PORT emulation
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ * Copyright (c) 2007 Marko Kohtala
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "sysemu/char.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+#include "sysemu/sysemu.h"
+
+//#define DEBUG_PARALLEL
+
+#ifdef DEBUG_PARALLEL
+#define pdebug(fmt, ...) printf("pp: " fmt, ## __VA_ARGS__)
+#else
+#define pdebug(fmt, ...) ((void)0)
+#endif
+
+#define PARA_REG_DATA 0
+#define PARA_REG_STS 1
+#define PARA_REG_CTR 2
+#define PARA_REG_EPP_ADDR 3
+#define PARA_REG_EPP_DATA 4
+
+/*
+ * These are the definitions for the Printer Status Register
+ */
+#define PARA_STS_BUSY 0x80 /* Busy complement */
+#define PARA_STS_ACK 0x40 /* Acknowledge */
+#define PARA_STS_PAPER 0x20 /* Out of paper */
+#define PARA_STS_ONLINE 0x10 /* Online */
+#define PARA_STS_ERROR 0x08 /* Error complement */
+#define PARA_STS_TMOUT 0x01 /* EPP timeout */
+
+/*
+ * These are the definitions for the Printer Control Register
+ */
+#define PARA_CTR_DIR 0x20 /* Direction (1=read, 0=write) */
+#define PARA_CTR_INTEN 0x10 /* IRQ Enable */
+#define PARA_CTR_SELECT 0x08 /* Select In complement */
+#define PARA_CTR_INIT 0x04 /* Initialize Printer complement */
+#define PARA_CTR_AUTOLF 0x02 /* Auto linefeed complement */
+#define PARA_CTR_STROBE 0x01 /* Strobe complement */
+
+#define PARA_CTR_SIGNAL (PARA_CTR_SELECT|PARA_CTR_INIT|PARA_CTR_AUTOLF|PARA_CTR_STROBE)
+
+typedef struct ParallelState {
+ MemoryRegion iomem;
+ uint8_t dataw;
+ uint8_t datar;
+ uint8_t status;
+ uint8_t control;
+ qemu_irq irq;
+ int irq_pending;
+ CharDriverState *chr;
+ int hw_driver;
+ int epp_timeout;
+ uint32_t last_read_offset; /* For debugging */
+ /* Memory-mapped interface */
+ int it_shift;
+} ParallelState;
+
+#define TYPE_ISA_PARALLEL "isa-parallel"
+#define ISA_PARALLEL(obj) \
+ OBJECT_CHECK(ISAParallelState, (obj), TYPE_ISA_PARALLEL)
+
+typedef struct ISAParallelState {
+ ISADevice parent_obj;
+
+ uint32_t index;
+ uint32_t iobase;
+ uint32_t isairq;
+ ParallelState state;
+} ISAParallelState;
+
+static void parallel_update_irq(ParallelState *s)
+{
+ if (s->irq_pending)
+ qemu_irq_raise(s->irq);
+ else
+ qemu_irq_lower(s->irq);
+}
+
+static void
+parallel_ioport_write_sw(void *opaque, uint32_t addr, uint32_t val)
+{
+ ParallelState *s = opaque;
+
+ pdebug("write addr=0x%02x val=0x%02x\n", addr, val);
+
+ addr &= 7;
+ switch(addr) {
+ case PARA_REG_DATA:
+ s->dataw = val;
+ parallel_update_irq(s);
+ break;
+ case PARA_REG_CTR:
+ val |= 0xc0;
+ if ((val & PARA_CTR_INIT) == 0 ) {
+ s->status = PARA_STS_BUSY;
+ s->status |= PARA_STS_ACK;
+ s->status |= PARA_STS_ONLINE;
+ s->status |= PARA_STS_ERROR;
+ }
+ else if (val & PARA_CTR_SELECT) {
+ if (val & PARA_CTR_STROBE) {
+ s->status &= ~PARA_STS_BUSY;
+ if ((s->control & PARA_CTR_STROBE) == 0)
+ qemu_chr_fe_write(s->chr, &s->dataw, 1);
+ } else {
+ if (s->control & PARA_CTR_INTEN) {
+ s->irq_pending = 1;
+ }
+ }
+ }
+ parallel_update_irq(s);
+ s->control = val;
+ break;
+ }
+}
+
+static void parallel_ioport_write_hw(void *opaque, uint32_t addr, uint32_t val)
+{
+ ParallelState *s = opaque;
+ uint8_t parm = val;
+ int dir;
+
+ /* Sometimes programs do several writes for timing purposes on old
+ HW. Take care not to waste time on writes that do nothing. */
+
+ s->last_read_offset = ~0U;
+
+ addr &= 7;
+ switch(addr) {
+ case PARA_REG_DATA:
+ if (s->dataw == val)
+ return;
+ pdebug("wd%02x\n", val);
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_WRITE_DATA, &parm);
+ s->dataw = val;
+ break;
+ case PARA_REG_STS:
+ pdebug("ws%02x\n", val);
+ if (val & PARA_STS_TMOUT)
+ s->epp_timeout = 0;
+ break;
+ case PARA_REG_CTR:
+ val |= 0xc0;
+ if (s->control == val)
+ return;
+ pdebug("wc%02x\n", val);
+
+ if ((val & PARA_CTR_DIR) != (s->control & PARA_CTR_DIR)) {
+ if (val & PARA_CTR_DIR) {
+ dir = 1;
+ } else {
+ dir = 0;
+ }
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_DATA_DIR, &dir);
+ parm &= ~PARA_CTR_DIR;
+ }
+
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_WRITE_CONTROL, &parm);
+ s->control = val;
+ break;
+ case PARA_REG_EPP_ADDR:
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT)
+ /* Controls not correct for EPP address cycle, so do nothing */
+ pdebug("wa%02x s\n", val);
+ else {
+ struct ParallelIOArg ioarg = { .buffer = &parm, .count = 1 };
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE_ADDR, &ioarg)) {
+ s->epp_timeout = 1;
+ pdebug("wa%02x t\n", val);
+ }
+ else
+ pdebug("wa%02x\n", val);
+ }
+ break;
+ case PARA_REG_EPP_DATA:
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT)
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("we%02x s\n", val);
+ else {
+ struct ParallelIOArg ioarg = { .buffer = &parm, .count = 1 };
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg)) {
+ s->epp_timeout = 1;
+ pdebug("we%02x t\n", val);
+ }
+ else
+ pdebug("we%02x\n", val);
+ }
+ break;
+ }
+}
+
+static void
+parallel_ioport_eppdata_write_hw2(void *opaque, uint32_t addr, uint32_t val)
+{
+ ParallelState *s = opaque;
+ uint16_t eppdata = cpu_to_le16(val);
+ int err;
+ struct ParallelIOArg ioarg = {
+ .buffer = &eppdata, .count = sizeof(eppdata)
+ };
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT) {
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("we%04x s\n", val);
+ return;
+ }
+ err = qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg);
+ if (err) {
+ s->epp_timeout = 1;
+ pdebug("we%04x t\n", val);
+ }
+ else
+ pdebug("we%04x\n", val);
+}
+
+static void
+parallel_ioport_eppdata_write_hw4(void *opaque, uint32_t addr, uint32_t val)
+{
+ ParallelState *s = opaque;
+ uint32_t eppdata = cpu_to_le32(val);
+ int err;
+ struct ParallelIOArg ioarg = {
+ .buffer = &eppdata, .count = sizeof(eppdata)
+ };
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT) {
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("we%08x s\n", val);
+ return;
+ }
+ err = qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg);
+ if (err) {
+ s->epp_timeout = 1;
+ pdebug("we%08x t\n", val);
+ }
+ else
+ pdebug("we%08x\n", val);
+}
+
+static uint32_t parallel_ioport_read_sw(void *opaque, uint32_t addr)
+{
+ ParallelState *s = opaque;
+ uint32_t ret = 0xff;
+
+ addr &= 7;
+ switch(addr) {
+ case PARA_REG_DATA:
+ if (s->control & PARA_CTR_DIR)
+ ret = s->datar;
+ else
+ ret = s->dataw;
+ break;
+ case PARA_REG_STS:
+ ret = s->status;
+ s->irq_pending = 0;
+ if ((s->status & PARA_STS_BUSY) == 0 && (s->control & PARA_CTR_STROBE) == 0) {
+ /* XXX Fixme: wait 5 microseconds */
+ if (s->status & PARA_STS_ACK)
+ s->status &= ~PARA_STS_ACK;
+ else {
+ /* XXX Fixme: wait 5 microseconds */
+ s->status |= PARA_STS_ACK;
+ s->status |= PARA_STS_BUSY;
+ }
+ }
+ parallel_update_irq(s);
+ break;
+ case PARA_REG_CTR:
+ ret = s->control;
+ break;
+ }
+ pdebug("read addr=0x%02x val=0x%02x\n", addr, ret);
+ return ret;
+}
+
+static uint32_t parallel_ioport_read_hw(void *opaque, uint32_t addr)
+{
+ ParallelState *s = opaque;
+ uint8_t ret = 0xff;
+ addr &= 7;
+ switch(addr) {
+ case PARA_REG_DATA:
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_READ_DATA, &ret);
+ if (s->last_read_offset != addr || s->datar != ret)
+ pdebug("rd%02x\n", ret);
+ s->datar = ret;
+ break;
+ case PARA_REG_STS:
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_READ_STATUS, &ret);
+ ret &= ~PARA_STS_TMOUT;
+ if (s->epp_timeout)
+ ret |= PARA_STS_TMOUT;
+ if (s->last_read_offset != addr || s->status != ret)
+ pdebug("rs%02x\n", ret);
+ s->status = ret;
+ break;
+ case PARA_REG_CTR:
+ /* s->control has some bits fixed to 1. It is zero only when
+ it has not been yet written to. */
+ if (s->control == 0) {
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_READ_CONTROL, &ret);
+ if (s->last_read_offset != addr)
+ pdebug("rc%02x\n", ret);
+ s->control = ret;
+ }
+ else {
+ ret = s->control;
+ if (s->last_read_offset != addr)
+ pdebug("rc%02x\n", ret);
+ }
+ break;
+ case PARA_REG_EPP_ADDR:
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT))
+ /* Controls not correct for EPP addr cycle, so do nothing */
+ pdebug("ra%02x s\n", ret);
+ else {
+ struct ParallelIOArg ioarg = { .buffer = &ret, .count = 1 };
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ_ADDR, &ioarg)) {
+ s->epp_timeout = 1;
+ pdebug("ra%02x t\n", ret);
+ }
+ else
+ pdebug("ra%02x\n", ret);
+ }
+ break;
+ case PARA_REG_EPP_DATA:
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT))
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("re%02x s\n", ret);
+ else {
+ struct ParallelIOArg ioarg = { .buffer = &ret, .count = 1 };
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg)) {
+ s->epp_timeout = 1;
+ pdebug("re%02x t\n", ret);
+ }
+ else
+ pdebug("re%02x\n", ret);
+ }
+ break;
+ }
+ s->last_read_offset = addr;
+ return ret;
+}
+
+static uint32_t
+parallel_ioport_eppdata_read_hw2(void *opaque, uint32_t addr)
+{
+ ParallelState *s = opaque;
+ uint32_t ret;
+ uint16_t eppdata = ~0;
+ int err;
+ struct ParallelIOArg ioarg = {
+ .buffer = &eppdata, .count = sizeof(eppdata)
+ };
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT)) {
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("re%04x s\n", eppdata);
+ return eppdata;
+ }
+ err = qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg);
+ ret = le16_to_cpu(eppdata);
+
+ if (err) {
+ s->epp_timeout = 1;
+ pdebug("re%04x t\n", ret);
+ }
+ else
+ pdebug("re%04x\n", ret);
+ return ret;
+}
+
+static uint32_t
+parallel_ioport_eppdata_read_hw4(void *opaque, uint32_t addr)
+{
+ ParallelState *s = opaque;
+ uint32_t ret;
+ uint32_t eppdata = ~0U;
+ int err;
+ struct ParallelIOArg ioarg = {
+ .buffer = &eppdata, .count = sizeof(eppdata)
+ };
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT)) {
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("re%08x s\n", eppdata);
+ return eppdata;
+ }
+ err = qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg);
+ ret = le32_to_cpu(eppdata);
+
+ if (err) {
+ s->epp_timeout = 1;
+ pdebug("re%08x t\n", ret);
+ }
+ else
+ pdebug("re%08x\n", ret);
+ return ret;
+}
+
+static void parallel_ioport_ecp_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ pdebug("wecp%d=%02x\n", addr & 7, val);
+}
+
+static uint32_t parallel_ioport_ecp_read(void *opaque, uint32_t addr)
+{
+ uint8_t ret = 0xff;
+
+ pdebug("recp%d:%02x\n", addr & 7, ret);
+ return ret;
+}
+
+static void parallel_reset(void *opaque)
+{
+ ParallelState *s = opaque;
+
+ s->datar = ~0;
+ s->dataw = ~0;
+ s->status = PARA_STS_BUSY;
+ s->status |= PARA_STS_ACK;
+ s->status |= PARA_STS_ONLINE;
+ s->status |= PARA_STS_ERROR;
+ s->status |= PARA_STS_TMOUT;
+ s->control = PARA_CTR_SELECT;
+ s->control |= PARA_CTR_INIT;
+ s->control |= 0xc0;
+ s->irq_pending = 0;
+ s->hw_driver = 0;
+ s->epp_timeout = 0;
+ s->last_read_offset = ~0U;
+}
+
+static const int isa_parallel_io[MAX_PARALLEL_PORTS] = { 0x378, 0x278, 0x3bc };
+
+static const MemoryRegionPortio isa_parallel_portio_hw_list[] = {
+ { 0, 8, 1,
+ .read = parallel_ioport_read_hw,
+ .write = parallel_ioport_write_hw },
+ { 4, 1, 2,
+ .read = parallel_ioport_eppdata_read_hw2,
+ .write = parallel_ioport_eppdata_write_hw2 },
+ { 4, 1, 4,
+ .read = parallel_ioport_eppdata_read_hw4,
+ .write = parallel_ioport_eppdata_write_hw4 },
+ { 0x400, 8, 1,
+ .read = parallel_ioport_ecp_read,
+ .write = parallel_ioport_ecp_write },
+ PORTIO_END_OF_LIST(),
+};
+
+static const MemoryRegionPortio isa_parallel_portio_sw_list[] = {
+ { 0, 8, 1,
+ .read = parallel_ioport_read_sw,
+ .write = parallel_ioport_write_sw },
+ PORTIO_END_OF_LIST(),
+};
+
+
+static const VMStateDescription vmstate_parallel_isa = {
+ .name = "parallel_isa",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(state.dataw, ISAParallelState),
+ VMSTATE_UINT8(state.datar, ISAParallelState),
+ VMSTATE_UINT8(state.status, ISAParallelState),
+ VMSTATE_UINT8(state.control, ISAParallelState),
+ VMSTATE_INT32(state.irq_pending, ISAParallelState),
+ VMSTATE_INT32(state.epp_timeout, ISAParallelState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+static void parallel_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ static int index;
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISAParallelState *isa = ISA_PARALLEL(dev);
+ ParallelState *s = &isa->state;
+ int base;
+ uint8_t dummy;
+
+ if (!s->chr) {
+ error_setg(errp, "Can't create parallel device, empty char device");
+ return;
+ }
+
+ if (isa->index == -1) {
+ isa->index = index;
+ }
+ if (isa->index >= MAX_PARALLEL_PORTS) {
+ error_setg(errp, "Max. supported number of parallel ports is %d.",
+ MAX_PARALLEL_PORTS);
+ return;
+ }
+ if (isa->iobase == -1) {
+ isa->iobase = isa_parallel_io[isa->index];
+ }
+ index++;
+
+ base = isa->iobase;
+ isa_init_irq(isadev, &s->irq, isa->isairq);
+ qemu_register_reset(parallel_reset, s);
+
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_READ_STATUS, &dummy) == 0) {
+ s->hw_driver = 1;
+ s->status = dummy;
+ }
+
+ isa_register_portio_list(isadev, base,
+ (s->hw_driver
+ ? &isa_parallel_portio_hw_list[0]
+ : &isa_parallel_portio_sw_list[0]),
+ s, "parallel");
+}
+
+/* Memory mapped interface */
+static uint32_t parallel_mm_readb (void *opaque, hwaddr addr)
+{
+ ParallelState *s = opaque;
+
+ return parallel_ioport_read_sw(s, addr >> s->it_shift) & 0xFF;
+}
+
+static void parallel_mm_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ParallelState *s = opaque;
+
+ parallel_ioport_write_sw(s, addr >> s->it_shift, value & 0xFF);
+}
+
+static uint32_t parallel_mm_readw (void *opaque, hwaddr addr)
+{
+ ParallelState *s = opaque;
+
+ return parallel_ioport_read_sw(s, addr >> s->it_shift) & 0xFFFF;
+}
+
+static void parallel_mm_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ParallelState *s = opaque;
+
+ parallel_ioport_write_sw(s, addr >> s->it_shift, value & 0xFFFF);
+}
+
+static uint32_t parallel_mm_readl (void *opaque, hwaddr addr)
+{
+ ParallelState *s = opaque;
+
+ return parallel_ioport_read_sw(s, addr >> s->it_shift);
+}
+
+static void parallel_mm_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ParallelState *s = opaque;
+
+ parallel_ioport_write_sw(s, addr >> s->it_shift, value);
+}
+
+static const MemoryRegionOps parallel_mm_ops = {
+ .old_mmio = {
+ .read = { parallel_mm_readb, parallel_mm_readw, parallel_mm_readl },
+ .write = { parallel_mm_writeb, parallel_mm_writew, parallel_mm_writel },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* If fd is zero, it means that the parallel device uses the console */
+bool parallel_mm_init(MemoryRegion *address_space,
+ hwaddr base, int it_shift, qemu_irq irq,
+ CharDriverState *chr)
+{
+ ParallelState *s;
+
+ s = g_malloc0(sizeof(ParallelState));
+ s->irq = irq;
+ s->chr = chr;
+ s->it_shift = it_shift;
+ qemu_register_reset(parallel_reset, s);
+
+ memory_region_init_io(&s->iomem, NULL, &parallel_mm_ops, s,
+ "parallel", 8 << it_shift);
+ memory_region_add_subregion(address_space, base, &s->iomem);
+ return true;
+}
+
+static Property parallel_isa_properties[] = {
+ DEFINE_PROP_UINT32("index", ISAParallelState, index, -1),
+ DEFINE_PROP_UINT32("iobase", ISAParallelState, iobase, -1),
+ DEFINE_PROP_UINT32("irq", ISAParallelState, isairq, 7),
+ DEFINE_PROP_CHR("chardev", ISAParallelState, state.chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void parallel_isa_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = parallel_isa_realizefn;
+ dc->vmsd = &vmstate_parallel_isa;
+ dc->props = parallel_isa_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo parallel_isa_info = {
+ .name = TYPE_ISA_PARALLEL,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISAParallelState),
+ .class_init = parallel_isa_class_initfn,
+};
+
+static void parallel_register_types(void)
+{
+ type_register_static(&parallel_isa_info);
+}
+
+type_init(parallel_register_types)
diff --git a/hw/char/pl011.c b/hw/char/pl011.c
new file mode 100644
index 00000000..eac6fac0
--- /dev/null
+++ b/hw/char/pl011.c
@@ -0,0 +1,342 @@
+/*
+ * Arm PrimeCell PL011 UART
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+
+#define TYPE_PL011 "pl011"
+#define PL011(obj) OBJECT_CHECK(PL011State, (obj), TYPE_PL011)
+
+typedef struct PL011State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t readbuff;
+ uint32_t flags;
+ uint32_t lcr;
+ uint32_t rsr;
+ uint32_t cr;
+ uint32_t dmacr;
+ uint32_t int_enabled;
+ uint32_t int_level;
+ uint32_t read_fifo[16];
+ uint32_t ilpr;
+ uint32_t ibrd;
+ uint32_t fbrd;
+ uint32_t ifl;
+ int read_pos;
+ int read_count;
+ int read_trigger;
+ CharDriverState *chr;
+ qemu_irq irq;
+ const unsigned char *id;
+} PL011State;
+
+#define PL011_INT_TX 0x20
+#define PL011_INT_RX 0x10
+
+#define PL011_FLAG_TXFE 0x80
+#define PL011_FLAG_RXFF 0x40
+#define PL011_FLAG_TXFF 0x20
+#define PL011_FLAG_RXFE 0x10
+
+static const unsigned char pl011_id_arm[8] =
+ { 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+static const unsigned char pl011_id_luminary[8] =
+ { 0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static void pl011_update(PL011State *s)
+{
+ uint32_t flags;
+
+ flags = s->int_level & s->int_enabled;
+ qemu_set_irq(s->irq, flags != 0);
+}
+
+static uint64_t pl011_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL011State *s = (PL011State *)opaque;
+ uint32_t c;
+
+ if (offset >= 0xfe0 && offset < 0x1000) {
+ return s->id[(offset - 0xfe0) >> 2];
+ }
+ switch (offset >> 2) {
+ case 0: /* UARTDR */
+ s->flags &= ~PL011_FLAG_RXFF;
+ c = s->read_fifo[s->read_pos];
+ if (s->read_count > 0) {
+ s->read_count--;
+ if (++s->read_pos == 16)
+ s->read_pos = 0;
+ }
+ if (s->read_count == 0) {
+ s->flags |= PL011_FLAG_RXFE;
+ }
+ if (s->read_count == s->read_trigger - 1)
+ s->int_level &= ~ PL011_INT_RX;
+ s->rsr = c >> 8;
+ pl011_update(s);
+ if (s->chr) {
+ qemu_chr_accept_input(s->chr);
+ }
+ return c;
+ case 1: /* UARTRSR */
+ return s->rsr;
+ case 6: /* UARTFR */
+ return s->flags;
+ case 8: /* UARTILPR */
+ return s->ilpr;
+ case 9: /* UARTIBRD */
+ return s->ibrd;
+ case 10: /* UARTFBRD */
+ return s->fbrd;
+ case 11: /* UARTLCR_H */
+ return s->lcr;
+ case 12: /* UARTCR */
+ return s->cr;
+ case 13: /* UARTIFLS */
+ return s->ifl;
+ case 14: /* UARTIMSC */
+ return s->int_enabled;
+ case 15: /* UARTRIS */
+ return s->int_level;
+ case 16: /* UARTMIS */
+ return s->int_level & s->int_enabled;
+ case 18: /* UARTDMACR */
+ return s->dmacr;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl011_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl011_set_read_trigger(PL011State *s)
+{
+#if 0
+ /* The docs say the RX interrupt is triggered when the FIFO exceeds
+ the threshold. However linux only reads the FIFO in response to an
+ interrupt. Triggering the interrupt when the FIFO is non-empty seems
+ to make things work. */
+ if (s->lcr & 0x10)
+ s->read_trigger = (s->ifl >> 1) & 0x1c;
+ else
+#endif
+ s->read_trigger = 1;
+}
+
+static void pl011_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL011State *s = (PL011State *)opaque;
+ unsigned char ch;
+
+ switch (offset >> 2) {
+ case 0: /* UARTDR */
+ /* ??? Check if transmitter is enabled. */
+ ch = value;
+ if (s->chr)
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ s->int_level |= PL011_INT_TX;
+ pl011_update(s);
+ break;
+ case 1: /* UARTRSR/UARTECR */
+ s->rsr = 0;
+ break;
+ case 6: /* UARTFR */
+ /* Writes to Flag register are ignored. */
+ break;
+ case 8: /* UARTUARTILPR */
+ s->ilpr = value;
+ break;
+ case 9: /* UARTIBRD */
+ s->ibrd = value;
+ break;
+ case 10: /* UARTFBRD */
+ s->fbrd = value;
+ break;
+ case 11: /* UARTLCR_H */
+ /* Reset the FIFO state on FIFO enable or disable */
+ if ((s->lcr ^ value) & 0x10) {
+ s->read_count = 0;
+ s->read_pos = 0;
+ }
+ s->lcr = value;
+ pl011_set_read_trigger(s);
+ break;
+ case 12: /* UARTCR */
+ /* ??? Need to implement the enable and loopback bits. */
+ s->cr = value;
+ break;
+ case 13: /* UARTIFS */
+ s->ifl = value;
+ pl011_set_read_trigger(s);
+ break;
+ case 14: /* UARTIMSC */
+ s->int_enabled = value;
+ pl011_update(s);
+ break;
+ case 17: /* UARTICR */
+ s->int_level &= ~value;
+ pl011_update(s);
+ break;
+ case 18: /* UARTDMACR */
+ s->dmacr = value;
+ if (value & 3) {
+ qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl011_write: Bad offset %x\n", (int)offset);
+ }
+}
+
+static int pl011_can_receive(void *opaque)
+{
+ PL011State *s = (PL011State *)opaque;
+
+ if (s->lcr & 0x10)
+ return s->read_count < 16;
+ else
+ return s->read_count < 1;
+}
+
+static void pl011_put_fifo(void *opaque, uint32_t value)
+{
+ PL011State *s = (PL011State *)opaque;
+ int slot;
+
+ slot = s->read_pos + s->read_count;
+ if (slot >= 16)
+ slot -= 16;
+ s->read_fifo[slot] = value;
+ s->read_count++;
+ s->flags &= ~PL011_FLAG_RXFE;
+ if (!(s->lcr & 0x10) || s->read_count == 16) {
+ s->flags |= PL011_FLAG_RXFF;
+ }
+ if (s->read_count == s->read_trigger) {
+ s->int_level |= PL011_INT_RX;
+ pl011_update(s);
+ }
+}
+
+static void pl011_receive(void *opaque, const uint8_t *buf, int size)
+{
+ pl011_put_fifo(opaque, *buf);
+}
+
+static void pl011_event(void *opaque, int event)
+{
+ if (event == CHR_EVENT_BREAK)
+ pl011_put_fifo(opaque, 0x400);
+}
+
+static const MemoryRegionOps pl011_ops = {
+ .read = pl011_read,
+ .write = pl011_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pl011 = {
+ .name = "pl011",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(readbuff, PL011State),
+ VMSTATE_UINT32(flags, PL011State),
+ VMSTATE_UINT32(lcr, PL011State),
+ VMSTATE_UINT32(rsr, PL011State),
+ VMSTATE_UINT32(cr, PL011State),
+ VMSTATE_UINT32(dmacr, PL011State),
+ VMSTATE_UINT32(int_enabled, PL011State),
+ VMSTATE_UINT32(int_level, PL011State),
+ VMSTATE_UINT32_ARRAY(read_fifo, PL011State, 16),
+ VMSTATE_UINT32(ilpr, PL011State),
+ VMSTATE_UINT32(ibrd, PL011State),
+ VMSTATE_UINT32(fbrd, PL011State),
+ VMSTATE_UINT32(ifl, PL011State),
+ VMSTATE_INT32(read_pos, PL011State),
+ VMSTATE_INT32(read_count, PL011State),
+ VMSTATE_INT32(read_trigger, PL011State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pl011_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ PL011State *s = PL011(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->read_trigger = 1;
+ s->ifl = 0x12;
+ s->cr = 0x300;
+ s->flags = 0x90;
+
+ s->id = pl011_id_arm;
+}
+
+static void pl011_realize(DeviceState *dev, Error **errp)
+{
+ PL011State *s = PL011(dev);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, pl011_can_receive, pl011_receive,
+ pl011_event, s);
+ }
+}
+
+static void pl011_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = pl011_realize;
+ dc->vmsd = &vmstate_pl011;
+ /* Reason: realize() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo pl011_arm_info = {
+ .name = TYPE_PL011,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL011State),
+ .instance_init = pl011_init,
+ .class_init = pl011_class_init,
+};
+
+static void pl011_luminary_init(Object *obj)
+{
+ PL011State *s = PL011(obj);
+
+ s->id = pl011_id_luminary;
+}
+
+static const TypeInfo pl011_luminary_info = {
+ .name = "pl011_luminary",
+ .parent = TYPE_PL011,
+ .instance_init = pl011_luminary_init,
+};
+
+static void pl011_register_types(void)
+{
+ type_register_static(&pl011_arm_info);
+ type_register_static(&pl011_luminary_info);
+}
+
+type_init(pl011_register_types)
diff --git a/hw/char/sclpconsole-lm.c b/hw/char/sclpconsole-lm.c
new file mode 100644
index 00000000..02ac80b6
--- /dev/null
+++ b/hw/char/sclpconsole-lm.c
@@ -0,0 +1,383 @@
+/*
+ * SCLP event types
+ * Operations Command - Line Mode input
+ * Message - Line Mode output
+ *
+ * Copyright IBM, Corp. 2013
+ *
+ * Authors:
+ * Heinz Graalfs <graalfs@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/qdev.h"
+#include "qemu/thread.h"
+#include "qemu/error-report.h"
+#include "sysemu/char.h"
+
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/event-facility.h"
+#include "hw/s390x/ebcdic.h"
+
+#define SIZE_BUFFER 4096
+#define NEWLINE "\n"
+
+typedef struct OprtnsCommand {
+ EventBufferHeader header;
+ MDMSU message_unit;
+ char data[0];
+} QEMU_PACKED OprtnsCommand;
+
+/* max size for line-mode data in 4K SCCB page */
+#define SIZE_CONSOLE_BUFFER (SCCB_DATA_LEN - sizeof(OprtnsCommand))
+
+typedef struct SCLPConsoleLM {
+ SCLPEvent event;
+ CharDriverState *chr;
+ bool echo; /* immediate echo of input if true */
+ uint32_t write_errors; /* errors writing to char layer */
+ uint32_t length; /* length of byte stream in buffer */
+ uint8_t buf[SIZE_CONSOLE_BUFFER];
+} SCLPConsoleLM;
+
+/*
+* Character layer call-back functions
+ *
+ * Allow 1 character at a time
+ *
+ * Accumulate bytes from character layer in console buffer,
+ * event_pending is set when a newline character is encountered
+ *
+ * The maximum command line length is limited by the maximum
+ * space available in an SCCB. Line mode console input is sent
+ * truncated to the guest in case it doesn't fit into the SCCB.
+ */
+
+static int chr_can_read(void *opaque)
+{
+ SCLPConsoleLM *scon = opaque;
+
+ if (scon->event.event_pending) {
+ return 0;
+ }
+ return 1;
+}
+
+static void chr_read(void *opaque, const uint8_t *buf, int size)
+{
+ SCLPConsoleLM *scon = opaque;
+
+ assert(size == 1);
+
+ if (*buf == '\r' || *buf == '\n') {
+ scon->event.event_pending = true;
+ sclp_service_interrupt(0);
+ return;
+ }
+ if (scon->length == SIZE_CONSOLE_BUFFER) {
+ /* Eat the character, but still process CR and LF. */
+ return;
+ }
+ scon->buf[scon->length] = *buf;
+ scon->length += 1;
+ if (scon->echo) {
+ qemu_chr_fe_write(scon->chr, buf, size);
+ }
+}
+
+/* functions to be called by event facility */
+
+static bool can_handle_event(uint8_t type)
+{
+ return type == SCLP_EVENT_MESSAGE || type == SCLP_EVENT_PMSGCMD;
+}
+
+static unsigned int send_mask(void)
+{
+ return SCLP_EVENT_MASK_OP_CMD | SCLP_EVENT_MASK_PMSGCMD;
+}
+
+static unsigned int receive_mask(void)
+{
+ return SCLP_EVENT_MASK_MSG | SCLP_EVENT_MASK_PMSGCMD;
+}
+
+/*
+ * Triggered by SCLP's read_event_data
+ * - convert ASCII byte stream to EBCDIC and
+ * - copy converted data into provided (SCLP) buffer
+ */
+static int get_console_data(SCLPEvent *event, uint8_t *buf, size_t *size,
+ int avail)
+{
+ int len;
+
+ SCLPConsoleLM *cons = DO_UPCAST(SCLPConsoleLM, event, event);
+
+ len = cons->length;
+ /* data need to fit into provided SCLP buffer */
+ if (len > avail) {
+ return 1;
+ }
+
+ ebcdic_put(buf, (char *)&cons->buf, len);
+ *size = len;
+ cons->length = 0;
+ /* data provided and no more data pending */
+ event->event_pending = false;
+ qemu_notify_event();
+ return 0;
+}
+
+static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
+ int *slen)
+{
+ int avail, rc;
+ size_t src_len;
+ uint8_t *to;
+ OprtnsCommand *oc = (OprtnsCommand *) evt_buf_hdr;
+
+ if (!event->event_pending) {
+ /* no data pending */
+ return 0;
+ }
+
+ to = (uint8_t *)&oc->data;
+ avail = *slen - sizeof(OprtnsCommand);
+ rc = get_console_data(event, to, &src_len, avail);
+ if (rc) {
+ /* data didn't fit, try next SCCB */
+ return 1;
+ }
+
+ oc->message_unit.mdmsu.gds_id = GDS_ID_MDSMU;
+ oc->message_unit.mdmsu.length = cpu_to_be16(sizeof(struct MDMSU));
+
+ oc->message_unit.cpmsu.gds_id = GDS_ID_CPMSU;
+ oc->message_unit.cpmsu.length =
+ cpu_to_be16(sizeof(struct MDMSU) - sizeof(GdsVector));
+
+ oc->message_unit.text_command.gds_id = GDS_ID_TEXTCMD;
+ oc->message_unit.text_command.length =
+ cpu_to_be16(sizeof(struct MDMSU) - (2 * sizeof(GdsVector)));
+
+ oc->message_unit.self_def_text_message.key = GDS_KEY_SELFDEFTEXTMSG;
+ oc->message_unit.self_def_text_message.length =
+ cpu_to_be16(sizeof(struct MDMSU) - (3 * sizeof(GdsVector)));
+
+ oc->message_unit.text_message.key = GDS_KEY_TEXTMSG;
+ oc->message_unit.text_message.length =
+ cpu_to_be16(sizeof(GdsSubvector) + src_len);
+
+ oc->header.length = cpu_to_be16(sizeof(OprtnsCommand) + src_len);
+ oc->header.type = SCLP_EVENT_OPRTNS_COMMAND;
+ *slen = avail - src_len;
+
+ return 1;
+}
+
+/*
+ * Triggered by SCLP's write_event_data
+ * - write console data to character layer
+ * returns < 0 if an error occurred
+ */
+static int write_console_data(SCLPEvent *event, const uint8_t *buf, int len)
+{
+ int ret = 0;
+ const uint8_t *buf_offset;
+
+ SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event);
+
+ if (!scon->chr) {
+ /* If there's no backend, we can just say we consumed all data. */
+ return len;
+ }
+
+ buf_offset = buf;
+ while (len > 0) {
+ ret = qemu_chr_fe_write(scon->chr, buf, len);
+ if (ret == 0) {
+ /* a pty doesn't seem to be connected - no error */
+ len = 0;
+ } else if (ret == -EAGAIN || (ret > 0 && ret < len)) {
+ len -= ret;
+ buf_offset += ret;
+ } else {
+ len = 0;
+ }
+ }
+
+ return ret;
+}
+
+static int process_mdb(SCLPEvent *event, MDBO *mdbo)
+{
+ int rc;
+ int len;
+ uint8_t buffer[SIZE_BUFFER];
+
+ len = be16_to_cpu(mdbo->length);
+ len -= sizeof(mdbo->length) + sizeof(mdbo->type)
+ + sizeof(mdbo->mto.line_type_flags)
+ + sizeof(mdbo->mto.alarm_control)
+ + sizeof(mdbo->mto._reserved);
+
+ assert(len <= SIZE_BUFFER);
+
+ /* convert EBCDIC SCLP contents to ASCII console message */
+ ascii_put(buffer, mdbo->mto.message, len);
+ rc = write_console_data(event, (uint8_t *)NEWLINE, 1);
+ if (rc < 0) {
+ return rc;
+ }
+ return write_console_data(event, buffer, len);
+}
+
+static int write_event_data(SCLPEvent *event, EventBufferHeader *ebh)
+{
+ int len;
+ int written;
+ int errors = 0;
+ MDBO *mdbo;
+ SclpMsg *data = (SclpMsg *) ebh;
+ SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event);
+
+ len = be16_to_cpu(data->mdb.header.length);
+ if (len < sizeof(data->mdb.header)) {
+ return SCLP_RC_INCONSISTENT_LENGTHS;
+ }
+ len -= sizeof(data->mdb.header);
+
+ /* first check message buffers */
+ mdbo = data->mdb.mdbo;
+ while (len > 0) {
+ if (be16_to_cpu(mdbo->length) > len
+ || be16_to_cpu(mdbo->length) == 0) {
+ return SCLP_RC_INCONSISTENT_LENGTHS;
+ }
+ len -= be16_to_cpu(mdbo->length);
+ mdbo = (void *) mdbo + be16_to_cpu(mdbo->length);
+ }
+
+ /* then execute */
+ len = be16_to_cpu(data->mdb.header.length) - sizeof(data->mdb.header);
+ mdbo = data->mdb.mdbo;
+ while (len > 0) {
+ switch (be16_to_cpu(mdbo->type)) {
+ case MESSAGE_TEXT:
+ /* message text object */
+ written = process_mdb(event, mdbo);
+ if (written < 0) {
+ /* character layer error */
+ errors++;
+ }
+ break;
+ default: /* ignore */
+ break;
+ }
+ len -= be16_to_cpu(mdbo->length);
+ mdbo = (void *) mdbo + be16_to_cpu(mdbo->length);
+ }
+ if (errors) {
+ scon->write_errors += errors;
+ }
+ data->header.flags = SCLP_EVENT_BUFFER_ACCEPTED;
+
+ return SCLP_RC_NORMAL_COMPLETION;
+}
+
+/* functions for live migration */
+
+static const VMStateDescription vmstate_sclplmconsole = {
+ .name = "sclplmconsole",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(event.event_pending, SCLPConsoleLM),
+ VMSTATE_UINT32(write_errors, SCLPConsoleLM),
+ VMSTATE_UINT32(length, SCLPConsoleLM),
+ VMSTATE_UINT8_ARRAY(buf, SCLPConsoleLM, SIZE_CONSOLE_BUFFER),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* qemu object creation and initialization functions */
+
+/* tell character layer our call-back functions */
+
+static int console_init(SCLPEvent *event)
+{
+ static bool console_available;
+
+ SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event);
+
+ if (console_available) {
+ error_report("Multiple line-mode operator consoles are not supported");
+ return -1;
+ }
+ console_available = true;
+
+ if (scon->chr) {
+ qemu_chr_add_handlers(scon->chr, chr_can_read, chr_read, NULL, scon);
+ }
+
+ return 0;
+}
+
+static int console_exit(SCLPEvent *event)
+{
+ return 0;
+}
+
+static void console_reset(DeviceState *dev)
+{
+ SCLPEvent *event = SCLP_EVENT(dev);
+ SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event);
+
+ event->event_pending = false;
+ scon->length = 0;
+ scon->write_errors = 0;
+}
+
+static Property console_properties[] = {
+ DEFINE_PROP_CHR("chardev", SCLPConsoleLM, chr),
+ DEFINE_PROP_UINT32("write_errors", SCLPConsoleLM, write_errors, 0),
+ DEFINE_PROP_BOOL("echo", SCLPConsoleLM, echo, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void console_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCLPEventClass *ec = SCLP_EVENT_CLASS(klass);
+
+ dc->props = console_properties;
+ dc->reset = console_reset;
+ dc->vmsd = &vmstate_sclplmconsole;
+ ec->init = console_init;
+ ec->exit = console_exit;
+ ec->get_send_mask = send_mask;
+ ec->get_receive_mask = receive_mask;
+ ec->can_handle_event = can_handle_event;
+ ec->read_event_data = read_event_data;
+ ec->write_event_data = write_event_data;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo sclp_console_info = {
+ .name = "sclplmconsole",
+ .parent = TYPE_SCLP_EVENT,
+ .instance_size = sizeof(SCLPConsoleLM),
+ .class_init = console_class_init,
+ .class_size = sizeof(SCLPEventClass),
+};
+
+static void register_types(void)
+{
+ type_register_static(&sclp_console_info);
+}
+
+type_init(register_types)
diff --git a/hw/char/sclpconsole.c b/hw/char/sclpconsole.c
new file mode 100644
index 00000000..b014c7f5
--- /dev/null
+++ b/hw/char/sclpconsole.c
@@ -0,0 +1,285 @@
+/*
+ * SCLP event type
+ * Ascii Console Data (VT220 Console)
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Heinz Graalfs <graalfs@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include <hw/qdev.h>
+#include "qemu/thread.h"
+#include "qemu/error-report.h"
+
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/event-facility.h"
+#include "sysemu/char.h"
+
+typedef struct ASCIIConsoleData {
+ EventBufferHeader ebh;
+ char data[0];
+} QEMU_PACKED ASCIIConsoleData;
+
+/* max size for ASCII data in 4K SCCB page */
+#define SIZE_BUFFER_VT220 4080
+
+typedef struct SCLPConsole {
+ SCLPEvent event;
+ CharDriverState *chr;
+ uint8_t iov[SIZE_BUFFER_VT220];
+ uint32_t iov_sclp; /* offset in buf for SCLP read operation */
+ uint32_t iov_bs; /* offset in buf for char layer read operation */
+ uint32_t iov_data_len; /* length of byte stream in buffer */
+ uint32_t iov_sclp_rest; /* length of byte stream not read via SCLP */
+ bool notify; /* qemu_notify_event() req'd if true */
+} SCLPConsole;
+
+/* character layer call-back functions */
+
+/* Return number of bytes that fit into iov buffer */
+static int chr_can_read(void *opaque)
+{
+ SCLPConsole *scon = opaque;
+ int avail = SIZE_BUFFER_VT220 - scon->iov_data_len;
+
+ if (avail == 0) {
+ scon->notify = true;
+ }
+ return avail;
+}
+
+/* Send data from a char device over to the guest */
+static void chr_read(void *opaque, const uint8_t *buf, int size)
+{
+ SCLPConsole *scon = opaque;
+
+ assert(scon);
+ /* read data must fit into current buffer */
+ assert(size <= SIZE_BUFFER_VT220 - scon->iov_data_len);
+
+ /* put byte-stream from character layer into buffer */
+ memcpy(&scon->iov[scon->iov_bs], buf, size);
+ scon->iov_data_len += size;
+ scon->iov_sclp_rest += size;
+ scon->iov_bs += size;
+ scon->event.event_pending = true;
+ sclp_service_interrupt(0);
+}
+
+/* functions to be called by event facility */
+
+static bool can_handle_event(uint8_t type)
+{
+ return type == SCLP_EVENT_ASCII_CONSOLE_DATA;
+}
+
+static unsigned int send_mask(void)
+{
+ return SCLP_EVENT_MASK_MSG_ASCII;
+}
+
+static unsigned int receive_mask(void)
+{
+ return SCLP_EVENT_MASK_MSG_ASCII;
+}
+
+/* triggered by SCLP's read_event_data -
+ * copy console data byte-stream into provided (SCLP) buffer
+ */
+static void get_console_data(SCLPEvent *event, uint8_t *buf, size_t *size,
+ int avail)
+{
+ SCLPConsole *cons = DO_UPCAST(SCLPConsole, event, event);
+
+ /* first byte is hex 0 saying an ascii string follows */
+ *buf++ = '\0';
+ avail--;
+ /* if all data fit into provided SCLP buffer */
+ if (avail >= cons->iov_sclp_rest) {
+ /* copy character byte-stream to SCLP buffer */
+ memcpy(buf, &cons->iov[cons->iov_sclp], cons->iov_sclp_rest);
+ *size = cons->iov_sclp_rest + 1;
+ cons->iov_sclp = 0;
+ cons->iov_bs = 0;
+ cons->iov_data_len = 0;
+ cons->iov_sclp_rest = 0;
+ event->event_pending = false;
+ /* data provided and no more data pending */
+ } else {
+ /* if provided buffer is too small, just copy part */
+ memcpy(buf, &cons->iov[cons->iov_sclp], avail);
+ *size = avail + 1;
+ cons->iov_sclp_rest -= avail;
+ cons->iov_sclp += avail;
+ /* more data pending */
+ }
+ if (cons->notify) {
+ cons->notify = false;
+ qemu_notify_event();
+ }
+}
+
+static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
+ int *slen)
+{
+ int avail;
+ size_t src_len;
+ uint8_t *to;
+ ASCIIConsoleData *acd = (ASCIIConsoleData *) evt_buf_hdr;
+
+ if (!event->event_pending) {
+ /* no data pending */
+ return 0;
+ }
+
+ to = (uint8_t *)&acd->data;
+ avail = *slen - sizeof(ASCIIConsoleData);
+ get_console_data(event, to, &src_len, avail);
+
+ acd->ebh.length = cpu_to_be16(sizeof(ASCIIConsoleData) + src_len);
+ acd->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA;
+ acd->ebh.flags |= SCLP_EVENT_BUFFER_ACCEPTED;
+ *slen = avail - src_len;
+
+ return 1;
+}
+
+/* triggered by SCLP's write_event_data
+ * - write console data to character layer
+ * returns < 0 if an error occurred
+ */
+static ssize_t write_console_data(SCLPEvent *event, const uint8_t *buf,
+ size_t len)
+{
+ SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event);
+
+ if (!scon->chr) {
+ /* If there's no backend, we can just say we consumed all data. */
+ return len;
+ }
+
+ return qemu_chr_fe_write_all(scon->chr, buf, len);
+}
+
+static int write_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr)
+{
+ int rc;
+ int length;
+ ssize_t written;
+ ASCIIConsoleData *acd = (ASCIIConsoleData *) evt_buf_hdr;
+
+ length = be16_to_cpu(evt_buf_hdr->length) - sizeof(EventBufferHeader);
+ written = write_console_data(event, (uint8_t *)acd->data, length);
+
+ rc = SCLP_RC_NORMAL_COMPLETION;
+ /* set event buffer accepted flag */
+ evt_buf_hdr->flags |= SCLP_EVENT_BUFFER_ACCEPTED;
+
+ /* written will be zero if a pty is not connected - don't treat as error */
+ if (written < 0) {
+ /* event buffer not accepted due to error in character layer */
+ evt_buf_hdr->flags &= ~(SCLP_EVENT_BUFFER_ACCEPTED);
+ rc = SCLP_RC_CONTAINED_EQUIPMENT_CHECK;
+ }
+
+ return rc;
+}
+
+static const VMStateDescription vmstate_sclpconsole = {
+ .name = "sclpconsole",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(event.event_pending, SCLPConsole),
+ VMSTATE_UINT8_ARRAY(iov, SCLPConsole, SIZE_BUFFER_VT220),
+ VMSTATE_UINT32(iov_sclp, SCLPConsole),
+ VMSTATE_UINT32(iov_bs, SCLPConsole),
+ VMSTATE_UINT32(iov_data_len, SCLPConsole),
+ VMSTATE_UINT32(iov_sclp_rest, SCLPConsole),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* qemu object creation and initialization functions */
+
+/* tell character layer our call-back functions */
+
+static int console_init(SCLPEvent *event)
+{
+ static bool console_available;
+
+ SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event);
+
+ if (console_available) {
+ error_report("Multiple VT220 operator consoles are not supported");
+ return -1;
+ }
+ console_available = true;
+ if (scon->chr) {
+ qemu_chr_add_handlers(scon->chr, chr_can_read,
+ chr_read, NULL, scon);
+ }
+
+ return 0;
+}
+
+static void console_reset(DeviceState *dev)
+{
+ SCLPEvent *event = SCLP_EVENT(dev);
+ SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event);
+
+ event->event_pending = false;
+ scon->iov_sclp = 0;
+ scon->iov_bs = 0;
+ scon->iov_data_len = 0;
+ scon->iov_sclp_rest = 0;
+ scon->notify = false;
+}
+
+static int console_exit(SCLPEvent *event)
+{
+ return 0;
+}
+
+static Property console_properties[] = {
+ DEFINE_PROP_CHR("chardev", SCLPConsole, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void console_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCLPEventClass *ec = SCLP_EVENT_CLASS(klass);
+
+ dc->props = console_properties;
+ dc->reset = console_reset;
+ dc->vmsd = &vmstate_sclpconsole;
+ ec->init = console_init;
+ ec->exit = console_exit;
+ ec->get_send_mask = send_mask;
+ ec->get_receive_mask = receive_mask;
+ ec->can_handle_event = can_handle_event;
+ ec->read_event_data = read_event_data;
+ ec->write_event_data = write_event_data;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo sclp_console_info = {
+ .name = "sclpconsole",
+ .parent = TYPE_SCLP_EVENT,
+ .instance_size = sizeof(SCLPConsole),
+ .class_init = console_class_init,
+ .class_size = sizeof(SCLPEventClass),
+};
+
+static void register_types(void)
+{
+ type_register_static(&sclp_console_info);
+}
+
+type_init(register_types)
diff --git a/hw/char/serial-isa.c b/hw/char/serial-isa.c
new file mode 100644
index 00000000..f3db024d
--- /dev/null
+++ b/hw/char/serial-isa.c
@@ -0,0 +1,145 @@
+/*
+ * QEMU 16550A UART emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ *
+ * 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.
+ */
+
+#include "hw/char/serial.h"
+#include "hw/isa/isa.h"
+
+#define ISA_SERIAL(obj) OBJECT_CHECK(ISASerialState, (obj), TYPE_ISA_SERIAL)
+
+typedef struct ISASerialState {
+ ISADevice parent_obj;
+
+ uint32_t index;
+ uint32_t iobase;
+ uint32_t isairq;
+ SerialState state;
+} ISASerialState;
+
+static const int isa_serial_io[MAX_SERIAL_PORTS] = {
+ 0x3f8, 0x2f8, 0x3e8, 0x2e8
+};
+static const int isa_serial_irq[MAX_SERIAL_PORTS] = {
+ 4, 3, 4, 3
+};
+
+static void serial_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ static int index;
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISASerialState *isa = ISA_SERIAL(dev);
+ SerialState *s = &isa->state;
+
+ if (isa->index == -1) {
+ isa->index = index;
+ }
+ if (isa->index >= MAX_SERIAL_PORTS) {
+ error_setg(errp, "Max. supported number of ISA serial ports is %d.",
+ MAX_SERIAL_PORTS);
+ return;
+ }
+ if (isa->iobase == -1) {
+ isa->iobase = isa_serial_io[isa->index];
+ }
+ if (isa->isairq == -1) {
+ isa->isairq = isa_serial_irq[isa->index];
+ }
+ index++;
+
+ s->baudbase = 115200;
+ isa_init_irq(isadev, &s->irq, isa->isairq);
+ serial_realize_core(s, errp);
+ qdev_set_legacy_instance_id(dev, isa->iobase, 3);
+
+ memory_region_init_io(&s->io, OBJECT(isa), &serial_io_ops, s, "serial", 8);
+ isa_register_ioport(isadev, &s->io, isa->iobase);
+}
+
+static const VMStateDescription vmstate_isa_serial = {
+ .name = "serial",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, ISASerialState, 0, vmstate_serial, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property serial_isa_properties[] = {
+ DEFINE_PROP_UINT32("index", ISASerialState, index, -1),
+ DEFINE_PROP_UINT32("iobase", ISASerialState, iobase, -1),
+ DEFINE_PROP_UINT32("irq", ISASerialState, isairq, -1),
+ DEFINE_PROP_CHR("chardev", ISASerialState, state.chr),
+ DEFINE_PROP_UINT32("wakeup", ISASerialState, state.wakeup, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void serial_isa_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = serial_isa_realizefn;
+ dc->vmsd = &vmstate_isa_serial;
+ dc->props = serial_isa_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo serial_isa_info = {
+ .name = TYPE_ISA_SERIAL,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISASerialState),
+ .class_init = serial_isa_class_initfn,
+};
+
+static void serial_register_types(void)
+{
+ type_register_static(&serial_isa_info);
+}
+
+type_init(serial_register_types)
+
+static void serial_isa_init(ISABus *bus, int index, CharDriverState *chr)
+{
+ DeviceState *dev;
+ ISADevice *isadev;
+
+ isadev = isa_create(bus, TYPE_ISA_SERIAL);
+ dev = DEVICE(isadev);
+ qdev_prop_set_uint32(dev, "index", index);
+ qdev_prop_set_chr(dev, "chardev", chr);
+ qdev_init_nofail(dev);
+}
+
+void serial_hds_isa_init(ISABus *bus, int n)
+{
+ int i;
+
+ assert(n <= MAX_SERIAL_PORTS);
+
+ for (i = 0; i < n; ++i) {
+ if (serial_hds[i]) {
+ serial_isa_init(bus, i, serial_hds[i]);
+ }
+ }
+}
diff --git a/hw/char/serial-pci.c b/hw/char/serial-pci.c
new file mode 100644
index 00000000..1c8b9be5
--- /dev/null
+++ b/hw/char/serial-pci.c
@@ -0,0 +1,274 @@
+/*
+ * QEMU 16550A UART emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ *
+ * 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.
+ */
+
+/* see docs/specs/pci-serial.txt */
+
+#include "hw/char/serial.h"
+#include "hw/pci/pci.h"
+
+#define PCI_SERIAL_MAX_PORTS 4
+
+typedef struct PCISerialState {
+ PCIDevice dev;
+ SerialState state;
+ uint8_t prog_if;
+} PCISerialState;
+
+typedef struct PCIMultiSerialState {
+ PCIDevice dev;
+ MemoryRegion iobar;
+ uint32_t ports;
+ char *name[PCI_SERIAL_MAX_PORTS];
+ SerialState state[PCI_SERIAL_MAX_PORTS];
+ uint32_t level[PCI_SERIAL_MAX_PORTS];
+ qemu_irq *irqs;
+ uint8_t prog_if;
+} PCIMultiSerialState;
+
+static void multi_serial_pci_exit(PCIDevice *dev);
+
+static void serial_pci_realize(PCIDevice *dev, Error **errp)
+{
+ PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev);
+ SerialState *s = &pci->state;
+ Error *err = NULL;
+
+ s->baudbase = 115200;
+ serial_realize_core(s, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ pci->dev.config[PCI_CLASS_PROG] = pci->prog_if;
+ pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
+ s->irq = pci_allocate_irq(&pci->dev);
+
+ memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s, "serial", 8);
+ pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+}
+
+static void multi_serial_irq_mux(void *opaque, int n, int level)
+{
+ PCIMultiSerialState *pci = opaque;
+ int i, pending = 0;
+
+ pci->level[n] = level;
+ for (i = 0; i < pci->ports; i++) {
+ if (pci->level[i]) {
+ pending = 1;
+ }
+ }
+ pci_set_irq(&pci->dev, pending);
+}
+
+static void multi_serial_pci_realize(PCIDevice *dev, Error **errp)
+{
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+ PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev);
+ SerialState *s;
+ Error *err = NULL;
+ int i, nr_ports = 0;
+
+ switch (pc->device_id) {
+ case 0x0003:
+ nr_ports = 2;
+ break;
+ case 0x0004:
+ nr_ports = 4;
+ break;
+ }
+ assert(nr_ports > 0);
+ assert(nr_ports <= PCI_SERIAL_MAX_PORTS);
+
+ pci->dev.config[PCI_CLASS_PROG] = pci->prog_if;
+ pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
+ memory_region_init(&pci->iobar, OBJECT(pci), "multiserial", 8 * nr_ports);
+ pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->iobar);
+ pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci,
+ nr_ports);
+
+ for (i = 0; i < nr_ports; i++) {
+ s = pci->state + i;
+ s->baudbase = 115200;
+ serial_realize_core(s, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ multi_serial_pci_exit(dev);
+ return;
+ }
+ s->irq = pci->irqs[i];
+ pci->name[i] = g_strdup_printf("uart #%d", i+1);
+ memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s,
+ pci->name[i], 8);
+ memory_region_add_subregion(&pci->iobar, 8 * i, &s->io);
+ pci->ports++;
+ }
+}
+
+static void serial_pci_exit(PCIDevice *dev)
+{
+ PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev);
+ SerialState *s = &pci->state;
+
+ serial_exit_core(s);
+ qemu_free_irq(s->irq);
+}
+
+static void multi_serial_pci_exit(PCIDevice *dev)
+{
+ PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev);
+ SerialState *s;
+ int i;
+
+ for (i = 0; i < pci->ports; i++) {
+ s = pci->state + i;
+ serial_exit_core(s);
+ memory_region_del_subregion(&pci->iobar, &s->io);
+ g_free(pci->name[i]);
+ }
+ qemu_free_irqs(pci->irqs, pci->ports);
+}
+
+static const VMStateDescription vmstate_pci_serial = {
+ .name = "pci-serial",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCISerialState),
+ VMSTATE_STRUCT(state, PCISerialState, 0, vmstate_serial, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_multi_serial = {
+ .name = "pci-serial-multi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCIMultiSerialState),
+ VMSTATE_STRUCT_ARRAY(state, PCIMultiSerialState, PCI_SERIAL_MAX_PORTS,
+ 0, vmstate_serial, SerialState),
+ VMSTATE_UINT32_ARRAY(level, PCIMultiSerialState, PCI_SERIAL_MAX_PORTS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property serial_pci_properties[] = {
+ DEFINE_PROP_CHR("chardev", PCISerialState, state.chr),
+ DEFINE_PROP_UINT8("prog_if", PCISerialState, prog_if, 0x02),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property multi_2x_serial_pci_properties[] = {
+ DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr),
+ DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr),
+ DEFINE_PROP_UINT8("prog_if", PCIMultiSerialState, prog_if, 0x02),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property multi_4x_serial_pci_properties[] = {
+ DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr),
+ DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr),
+ DEFINE_PROP_CHR("chardev3", PCIMultiSerialState, state[2].chr),
+ DEFINE_PROP_CHR("chardev4", PCIMultiSerialState, state[3].chr),
+ DEFINE_PROP_UINT8("prog_if", PCIMultiSerialState, prog_if, 0x02),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void serial_pci_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+ pc->realize = serial_pci_realize;
+ pc->exit = serial_pci_exit;
+ pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+ pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL;
+ pc->revision = 1;
+ pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
+ dc->vmsd = &vmstate_pci_serial;
+ dc->props = serial_pci_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static void multi_2x_serial_pci_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+ pc->realize = multi_serial_pci_realize;
+ pc->exit = multi_serial_pci_exit;
+ pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+ pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL2;
+ pc->revision = 1;
+ pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
+ dc->vmsd = &vmstate_pci_multi_serial;
+ dc->props = multi_2x_serial_pci_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static void multi_4x_serial_pci_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+ pc->realize = multi_serial_pci_realize;
+ pc->exit = multi_serial_pci_exit;
+ pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+ pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL4;
+ pc->revision = 1;
+ pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
+ dc->vmsd = &vmstate_pci_multi_serial;
+ dc->props = multi_4x_serial_pci_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo serial_pci_info = {
+ .name = "pci-serial",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCISerialState),
+ .class_init = serial_pci_class_initfn,
+};
+
+static const TypeInfo multi_2x_serial_pci_info = {
+ .name = "pci-serial-2x",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIMultiSerialState),
+ .class_init = multi_2x_serial_pci_class_initfn,
+};
+
+static const TypeInfo multi_4x_serial_pci_info = {
+ .name = "pci-serial-4x",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIMultiSerialState),
+ .class_init = multi_4x_serial_pci_class_initfn,
+};
+
+static void serial_pci_register_types(void)
+{
+ type_register_static(&serial_pci_info);
+ type_register_static(&multi_2x_serial_pci_info);
+ type_register_static(&multi_4x_serial_pci_info);
+}
+
+type_init(serial_pci_register_types)
diff --git a/hw/char/serial.c b/hw/char/serial.c
new file mode 100644
index 00000000..513d73c2
--- /dev/null
+++ b/hw/char/serial.c
@@ -0,0 +1,972 @@
+/*
+ * QEMU 16550A UART emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ *
+ * 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.
+ */
+
+#include "hw/char/serial.h"
+#include "sysemu/char.h"
+#include "qemu/timer.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+
+//#define DEBUG_SERIAL
+
+#define UART_LCR_DLAB 0x80 /* Divisor latch access bit */
+
+#define UART_IER_MSI 0x08 /* Enable Modem status interrupt */
+#define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */
+#define UART_IER_THRI 0x02 /* Enable Transmitter holding register int. */
+#define UART_IER_RDI 0x01 /* Enable receiver data interrupt */
+
+#define UART_IIR_NO_INT 0x01 /* No interrupts pending */
+#define UART_IIR_ID 0x06 /* Mask for the interrupt ID */
+
+#define UART_IIR_MSI 0x00 /* Modem status interrupt */
+#define UART_IIR_THRI 0x02 /* Transmitter holding register empty */
+#define UART_IIR_RDI 0x04 /* Receiver data interrupt */
+#define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */
+#define UART_IIR_CTI 0x0C /* Character Timeout Indication */
+
+#define UART_IIR_FENF 0x80 /* Fifo enabled, but not functionning */
+#define UART_IIR_FE 0xC0 /* Fifo enabled */
+
+/*
+ * These are the definitions for the Modem Control Register
+ */
+#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */
+#define UART_MCR_OUT2 0x08 /* Out2 complement */
+#define UART_MCR_OUT1 0x04 /* Out1 complement */
+#define UART_MCR_RTS 0x02 /* RTS complement */
+#define UART_MCR_DTR 0x01 /* DTR complement */
+
+/*
+ * These are the definitions for the Modem Status Register
+ */
+#define UART_MSR_DCD 0x80 /* Data Carrier Detect */
+#define UART_MSR_RI 0x40 /* Ring Indicator */
+#define UART_MSR_DSR 0x20 /* Data Set Ready */
+#define UART_MSR_CTS 0x10 /* Clear to Send */
+#define UART_MSR_DDCD 0x08 /* Delta DCD */
+#define UART_MSR_TERI 0x04 /* Trailing edge ring indicator */
+#define UART_MSR_DDSR 0x02 /* Delta DSR */
+#define UART_MSR_DCTS 0x01 /* Delta CTS */
+#define UART_MSR_ANY_DELTA 0x0F /* Any of the delta bits! */
+
+#define UART_LSR_TEMT 0x40 /* Transmitter empty */
+#define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */
+#define UART_LSR_BI 0x10 /* Break interrupt indicator */
+#define UART_LSR_FE 0x08 /* Frame error indicator */
+#define UART_LSR_PE 0x04 /* Parity error indicator */
+#define UART_LSR_OE 0x02 /* Overrun error indicator */
+#define UART_LSR_DR 0x01 /* Receiver data ready */
+#define UART_LSR_INT_ANY 0x1E /* Any of the lsr-interrupt-triggering status bits */
+
+/* Interrupt trigger levels. The byte-counts are for 16550A - in newer UARTs the byte-count for each ITL is higher. */
+
+#define UART_FCR_ITL_1 0x00 /* 1 byte ITL */
+#define UART_FCR_ITL_2 0x40 /* 4 bytes ITL */
+#define UART_FCR_ITL_3 0x80 /* 8 bytes ITL */
+#define UART_FCR_ITL_4 0xC0 /* 14 bytes ITL */
+
+#define UART_FCR_DMS 0x08 /* DMA Mode Select */
+#define UART_FCR_XFR 0x04 /* XMIT Fifo Reset */
+#define UART_FCR_RFR 0x02 /* RCVR Fifo Reset */
+#define UART_FCR_FE 0x01 /* FIFO Enable */
+
+#define MAX_XMIT_RETRY 4
+
+#ifdef DEBUG_SERIAL
+#define DPRINTF(fmt, ...) \
+do { fprintf(stderr, "serial: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+do {} while (0)
+#endif
+
+static void serial_receive1(void *opaque, const uint8_t *buf, int size);
+
+static inline void recv_fifo_put(SerialState *s, uint8_t chr)
+{
+ /* Receive overruns do not overwrite FIFO contents. */
+ if (!fifo8_is_full(&s->recv_fifo)) {
+ fifo8_push(&s->recv_fifo, chr);
+ } else {
+ s->lsr |= UART_LSR_OE;
+ }
+}
+
+static void serial_update_irq(SerialState *s)
+{
+ uint8_t tmp_iir = UART_IIR_NO_INT;
+
+ if ((s->ier & UART_IER_RLSI) && (s->lsr & UART_LSR_INT_ANY)) {
+ tmp_iir = UART_IIR_RLSI;
+ } else if ((s->ier & UART_IER_RDI) && s->timeout_ipending) {
+ /* Note that(s->ier & UART_IER_RDI) can mask this interrupt,
+ * this is not in the specification but is observed on existing
+ * hardware. */
+ tmp_iir = UART_IIR_CTI;
+ } else if ((s->ier & UART_IER_RDI) && (s->lsr & UART_LSR_DR) &&
+ (!(s->fcr & UART_FCR_FE) ||
+ s->recv_fifo.num >= s->recv_fifo_itl)) {
+ tmp_iir = UART_IIR_RDI;
+ } else if ((s->ier & UART_IER_THRI) && s->thr_ipending) {
+ tmp_iir = UART_IIR_THRI;
+ } else if ((s->ier & UART_IER_MSI) && (s->msr & UART_MSR_ANY_DELTA)) {
+ tmp_iir = UART_IIR_MSI;
+ }
+
+ s->iir = tmp_iir | (s->iir & 0xF0);
+
+ if (tmp_iir != UART_IIR_NO_INT) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void serial_update_parameters(SerialState *s)
+{
+ int speed, parity, data_bits, stop_bits, frame_size;
+ QEMUSerialSetParams ssp;
+
+ if (s->divider == 0)
+ return;
+
+ /* Start bit. */
+ frame_size = 1;
+ if (s->lcr & 0x08) {
+ /* Parity bit. */
+ frame_size++;
+ if (s->lcr & 0x10)
+ parity = 'E';
+ else
+ parity = 'O';
+ } else {
+ parity = 'N';
+ }
+ if (s->lcr & 0x04)
+ stop_bits = 2;
+ else
+ stop_bits = 1;
+
+ data_bits = (s->lcr & 0x03) + 5;
+ frame_size += data_bits + stop_bits;
+ speed = s->baudbase / s->divider;
+ ssp.speed = speed;
+ ssp.parity = parity;
+ ssp.data_bits = data_bits;
+ ssp.stop_bits = stop_bits;
+ s->char_transmit_time = (get_ticks_per_sec() / speed) * frame_size;
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+
+ DPRINTF("speed=%d parity=%c data=%d stop=%d\n",
+ speed, parity, data_bits, stop_bits);
+}
+
+static void serial_update_msl(SerialState *s)
+{
+ uint8_t omsr;
+ int flags;
+
+ timer_del(s->modem_status_poll);
+
+ if (qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP) {
+ s->poll_msl = -1;
+ return;
+ }
+
+ omsr = s->msr;
+
+ s->msr = (flags & CHR_TIOCM_CTS) ? s->msr | UART_MSR_CTS : s->msr & ~UART_MSR_CTS;
+ s->msr = (flags & CHR_TIOCM_DSR) ? s->msr | UART_MSR_DSR : s->msr & ~UART_MSR_DSR;
+ s->msr = (flags & CHR_TIOCM_CAR) ? s->msr | UART_MSR_DCD : s->msr & ~UART_MSR_DCD;
+ s->msr = (flags & CHR_TIOCM_RI) ? s->msr | UART_MSR_RI : s->msr & ~UART_MSR_RI;
+
+ if (s->msr != omsr) {
+ /* Set delta bits */
+ s->msr = s->msr | ((s->msr >> 4) ^ (omsr >> 4));
+ /* UART_MSR_TERI only if change was from 1 -> 0 */
+ if ((s->msr & UART_MSR_TERI) && !(omsr & UART_MSR_RI))
+ s->msr &= ~UART_MSR_TERI;
+ serial_update_irq(s);
+ }
+
+ /* The real 16550A apparently has a 250ns response latency to line status changes.
+ We'll be lazy and poll only every 10ms, and only poll it at all if MSI interrupts are turned on */
+
+ if (s->poll_msl)
+ timer_mod(s->modem_status_poll, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + get_ticks_per_sec() / 100);
+}
+
+static gboolean serial_xmit(GIOChannel *chan, GIOCondition cond, void *opaque)
+{
+ SerialState *s = opaque;
+
+ do {
+ assert(!(s->lsr & UART_LSR_TEMT));
+ if (s->tsr_retry <= 0) {
+ assert(!(s->lsr & UART_LSR_THRE));
+
+ if (s->fcr & UART_FCR_FE) {
+ assert(!fifo8_is_empty(&s->xmit_fifo));
+ s->tsr = fifo8_pop(&s->xmit_fifo);
+ if (!s->xmit_fifo.num) {
+ s->lsr |= UART_LSR_THRE;
+ }
+ } else {
+ s->tsr = s->thr;
+ s->lsr |= UART_LSR_THRE;
+ }
+ if ((s->lsr & UART_LSR_THRE) && !s->thr_ipending) {
+ s->thr_ipending = 1;
+ serial_update_irq(s);
+ }
+ }
+
+ if (s->mcr & UART_MCR_LOOP) {
+ /* in loopback mode, say that we just received a char */
+ serial_receive1(s, &s->tsr, 1);
+ } else if (qemu_chr_fe_write(s->chr, &s->tsr, 1) != 1) {
+ if (s->tsr_retry >= 0 && s->tsr_retry < MAX_XMIT_RETRY &&
+ qemu_chr_fe_add_watch(s->chr, G_IO_OUT|G_IO_HUP,
+ serial_xmit, s) > 0) {
+ s->tsr_retry++;
+ return FALSE;
+ }
+ s->tsr_retry = 0;
+ } else {
+ s->tsr_retry = 0;
+ }
+
+ /* Transmit another byte if it is already available. It is only
+ possible when FIFO is enabled and not empty. */
+ } while (!(s->lsr & UART_LSR_THRE));
+
+ s->last_xmit_ts = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->lsr |= UART_LSR_TEMT;
+
+ return FALSE;
+}
+
+
+/* Setter for FCR.
+ is_load flag means, that value is set while loading VM state
+ and interrupt should not be invoked */
+static void serial_write_fcr(SerialState *s, uint8_t val)
+{
+ /* Set fcr - val only has the bits that are supposed to "stick" */
+ s->fcr = val;
+
+ if (val & UART_FCR_FE) {
+ s->iir |= UART_IIR_FE;
+ /* Set recv_fifo trigger Level */
+ switch (val & 0xC0) {
+ case UART_FCR_ITL_1:
+ s->recv_fifo_itl = 1;
+ break;
+ case UART_FCR_ITL_2:
+ s->recv_fifo_itl = 4;
+ break;
+ case UART_FCR_ITL_3:
+ s->recv_fifo_itl = 8;
+ break;
+ case UART_FCR_ITL_4:
+ s->recv_fifo_itl = 14;
+ break;
+ }
+ } else {
+ s->iir &= ~UART_IIR_FE;
+ }
+}
+
+static void serial_ioport_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ SerialState *s = opaque;
+
+ addr &= 7;
+ DPRINTF("write addr=0x%" HWADDR_PRIx " val=0x%" PRIx64 "\n", addr, val);
+ switch(addr) {
+ default:
+ case 0:
+ if (s->lcr & UART_LCR_DLAB) {
+ s->divider = (s->divider & 0xff00) | val;
+ serial_update_parameters(s);
+ } else {
+ s->thr = (uint8_t) val;
+ if(s->fcr & UART_FCR_FE) {
+ /* xmit overruns overwrite data, so make space if needed */
+ if (fifo8_is_full(&s->xmit_fifo)) {
+ fifo8_pop(&s->xmit_fifo);
+ }
+ fifo8_push(&s->xmit_fifo, s->thr);
+ }
+ s->thr_ipending = 0;
+ s->lsr &= ~UART_LSR_THRE;
+ s->lsr &= ~UART_LSR_TEMT;
+ serial_update_irq(s);
+ if (s->tsr_retry <= 0) {
+ serial_xmit(NULL, G_IO_OUT, s);
+ }
+ }
+ break;
+ case 1:
+ if (s->lcr & UART_LCR_DLAB) {
+ s->divider = (s->divider & 0x00ff) | (val << 8);
+ serial_update_parameters(s);
+ } else {
+ uint8_t changed = (s->ier ^ val) & 0x0f;
+ s->ier = val & 0x0f;
+ /* If the backend device is a real serial port, turn polling of the modem
+ * status lines on physical port on or off depending on UART_IER_MSI state.
+ */
+ if ((changed & UART_IER_MSI) && s->poll_msl >= 0) {
+ if (s->ier & UART_IER_MSI) {
+ s->poll_msl = 1;
+ serial_update_msl(s);
+ } else {
+ timer_del(s->modem_status_poll);
+ s->poll_msl = 0;
+ }
+ }
+
+ /* Turning on the THRE interrupt on IER can trigger the interrupt
+ * if LSR.THRE=1, even if it had been masked before by reading IIR.
+ * This is not in the datasheet, but Windows relies on it. It is
+ * unclear if THRE has to be resampled every time THRI becomes
+ * 1, or only on the rising edge. Bochs does the latter, and Windows
+ * always toggles IER to all zeroes and back to all ones, so do the
+ * same.
+ *
+ * If IER.THRI is zero, thr_ipending is not used. Set it to zero
+ * so that the thr_ipending subsection is not migrated.
+ */
+ if (changed & UART_IER_THRI) {
+ if ((s->ier & UART_IER_THRI) && (s->lsr & UART_LSR_THRE)) {
+ s->thr_ipending = 1;
+ } else {
+ s->thr_ipending = 0;
+ }
+ }
+
+ if (changed) {
+ serial_update_irq(s);
+ }
+ }
+ break;
+ case 2:
+ /* Did the enable/disable flag change? If so, make sure FIFOs get flushed */
+ if ((val ^ s->fcr) & UART_FCR_FE) {
+ val |= UART_FCR_XFR | UART_FCR_RFR;
+ }
+
+ /* FIFO clear */
+
+ if (val & UART_FCR_RFR) {
+ s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
+ timer_del(s->fifo_timeout_timer);
+ s->timeout_ipending = 0;
+ fifo8_reset(&s->recv_fifo);
+ }
+
+ if (val & UART_FCR_XFR) {
+ s->lsr |= UART_LSR_THRE;
+ s->thr_ipending = 1;
+ fifo8_reset(&s->xmit_fifo);
+ }
+
+ serial_write_fcr(s, val & 0xC9);
+ serial_update_irq(s);
+ break;
+ case 3:
+ {
+ int break_enable;
+ s->lcr = val;
+ serial_update_parameters(s);
+ break_enable = (val >> 6) & 1;
+ if (break_enable != s->last_break_enable) {
+ s->last_break_enable = break_enable;
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
+ &break_enable);
+ }
+ }
+ break;
+ case 4:
+ {
+ int flags;
+ int old_mcr = s->mcr;
+ s->mcr = val & 0x1f;
+ if (val & UART_MCR_LOOP)
+ break;
+
+ if (s->poll_msl >= 0 && old_mcr != s->mcr) {
+
+ qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_GET_TIOCM, &flags);
+
+ flags &= ~(CHR_TIOCM_RTS | CHR_TIOCM_DTR);
+
+ if (val & UART_MCR_RTS)
+ flags |= CHR_TIOCM_RTS;
+ if (val & UART_MCR_DTR)
+ flags |= CHR_TIOCM_DTR;
+
+ qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_SET_TIOCM, &flags);
+ /* Update the modem status after a one-character-send wait-time, since there may be a response
+ from the device/computer at the other end of the serial line */
+ timer_mod(s->modem_status_poll, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time);
+ }
+ }
+ break;
+ case 5:
+ break;
+ case 6:
+ break;
+ case 7:
+ s->scr = val;
+ break;
+ }
+}
+
+static uint64_t serial_ioport_read(void *opaque, hwaddr addr, unsigned size)
+{
+ SerialState *s = opaque;
+ uint32_t ret;
+
+ addr &= 7;
+ switch(addr) {
+ default:
+ case 0:
+ if (s->lcr & UART_LCR_DLAB) {
+ ret = s->divider & 0xff;
+ } else {
+ if(s->fcr & UART_FCR_FE) {
+ ret = fifo8_is_empty(&s->recv_fifo) ?
+ 0 : fifo8_pop(&s->recv_fifo);
+ if (s->recv_fifo.num == 0) {
+ s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
+ } else {
+ timer_mod(s->fifo_timeout_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 4);
+ }
+ s->timeout_ipending = 0;
+ } else {
+ ret = s->rbr;
+ s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
+ }
+ serial_update_irq(s);
+ if (!(s->mcr & UART_MCR_LOOP)) {
+ /* in loopback mode, don't receive any data */
+ qemu_chr_accept_input(s->chr);
+ }
+ }
+ break;
+ case 1:
+ if (s->lcr & UART_LCR_DLAB) {
+ ret = (s->divider >> 8) & 0xff;
+ } else {
+ ret = s->ier;
+ }
+ break;
+ case 2:
+ ret = s->iir;
+ if ((ret & UART_IIR_ID) == UART_IIR_THRI) {
+ s->thr_ipending = 0;
+ serial_update_irq(s);
+ }
+ break;
+ case 3:
+ ret = s->lcr;
+ break;
+ case 4:
+ ret = s->mcr;
+ break;
+ case 5:
+ ret = s->lsr;
+ /* Clear break and overrun interrupts */
+ if (s->lsr & (UART_LSR_BI|UART_LSR_OE)) {
+ s->lsr &= ~(UART_LSR_BI|UART_LSR_OE);
+ serial_update_irq(s);
+ }
+ break;
+ case 6:
+ if (s->mcr & UART_MCR_LOOP) {
+ /* in loopback, the modem output pins are connected to the
+ inputs */
+ ret = (s->mcr & 0x0c) << 4;
+ ret |= (s->mcr & 0x02) << 3;
+ ret |= (s->mcr & 0x01) << 5;
+ } else {
+ if (s->poll_msl >= 0)
+ serial_update_msl(s);
+ ret = s->msr;
+ /* Clear delta bits & msr int after read, if they were set */
+ if (s->msr & UART_MSR_ANY_DELTA) {
+ s->msr &= 0xF0;
+ serial_update_irq(s);
+ }
+ }
+ break;
+ case 7:
+ ret = s->scr;
+ break;
+ }
+ DPRINTF("read addr=0x%" HWADDR_PRIx " val=0x%02x\n", addr, ret);
+ return ret;
+}
+
+static int serial_can_receive(SerialState *s)
+{
+ if(s->fcr & UART_FCR_FE) {
+ if (s->recv_fifo.num < UART_FIFO_LENGTH) {
+ /*
+ * Advertise (fifo.itl - fifo.count) bytes when count < ITL, and 1
+ * if above. If UART_FIFO_LENGTH - fifo.count is advertised the
+ * effect will be to almost always fill the fifo completely before
+ * the guest has a chance to respond, effectively overriding the ITL
+ * that the guest has set.
+ */
+ return (s->recv_fifo.num <= s->recv_fifo_itl) ?
+ s->recv_fifo_itl - s->recv_fifo.num : 1;
+ } else {
+ return 0;
+ }
+ } else {
+ return !(s->lsr & UART_LSR_DR);
+ }
+}
+
+static void serial_receive_break(SerialState *s)
+{
+ s->rbr = 0;
+ /* When the LSR_DR is set a null byte is pushed into the fifo */
+ recv_fifo_put(s, '\0');
+ s->lsr |= UART_LSR_BI | UART_LSR_DR;
+ serial_update_irq(s);
+}
+
+/* There's data in recv_fifo and s->rbr has not been read for 4 char transmit times */
+static void fifo_timeout_int (void *opaque) {
+ SerialState *s = opaque;
+ if (s->recv_fifo.num) {
+ s->timeout_ipending = 1;
+ serial_update_irq(s);
+ }
+}
+
+static int serial_can_receive1(void *opaque)
+{
+ SerialState *s = opaque;
+ return serial_can_receive(s);
+}
+
+static void serial_receive1(void *opaque, const uint8_t *buf, int size)
+{
+ SerialState *s = opaque;
+
+ if (s->wakeup) {
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
+ }
+ if(s->fcr & UART_FCR_FE) {
+ int i;
+ for (i = 0; i < size; i++) {
+ recv_fifo_put(s, buf[i]);
+ }
+ s->lsr |= UART_LSR_DR;
+ /* call the timeout receive callback in 4 char transmit time */
+ timer_mod(s->fifo_timeout_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 4);
+ } else {
+ if (s->lsr & UART_LSR_DR)
+ s->lsr |= UART_LSR_OE;
+ s->rbr = buf[0];
+ s->lsr |= UART_LSR_DR;
+ }
+ serial_update_irq(s);
+}
+
+static void serial_event(void *opaque, int event)
+{
+ SerialState *s = opaque;
+ DPRINTF("event %x\n", event);
+ if (event == CHR_EVENT_BREAK)
+ serial_receive_break(s);
+}
+
+static void serial_pre_save(void *opaque)
+{
+ SerialState *s = opaque;
+ s->fcr_vmstate = s->fcr;
+}
+
+static int serial_pre_load(void *opaque)
+{
+ SerialState *s = opaque;
+ s->thr_ipending = -1;
+ s->poll_msl = -1;
+ return 0;
+}
+
+static int serial_post_load(void *opaque, int version_id)
+{
+ SerialState *s = opaque;
+
+ if (version_id < 3) {
+ s->fcr_vmstate = 0;
+ }
+ if (s->thr_ipending == -1) {
+ s->thr_ipending = ((s->iir & UART_IIR_ID) == UART_IIR_THRI);
+ }
+ s->last_break_enable = (s->lcr >> 6) & 1;
+ /* Initialize fcr via setter to perform essential side-effects */
+ serial_write_fcr(s, s->fcr_vmstate);
+ serial_update_parameters(s);
+ return 0;
+}
+
+static bool serial_thr_ipending_needed(void *opaque)
+{
+ SerialState *s = opaque;
+
+ if (s->ier & UART_IER_THRI) {
+ bool expected_value = ((s->iir & UART_IIR_ID) == UART_IIR_THRI);
+ return s->thr_ipending != expected_value;
+ } else {
+ /* LSR.THRE will be sampled again when the interrupt is
+ * enabled. thr_ipending is not used in this case, do
+ * not migrate it.
+ */
+ return false;
+ }
+}
+
+static const VMStateDescription vmstate_serial_thr_ipending = {
+ .name = "serial/thr_ipending",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = serial_thr_ipending_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(thr_ipending, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool serial_tsr_needed(void *opaque)
+{
+ SerialState *s = (SerialState *)opaque;
+ return s->tsr_retry != 0;
+}
+
+static const VMStateDescription vmstate_serial_tsr = {
+ .name = "serial/tsr",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = serial_tsr_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(tsr_retry, SerialState),
+ VMSTATE_UINT8(thr, SerialState),
+ VMSTATE_UINT8(tsr, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool serial_recv_fifo_needed(void *opaque)
+{
+ SerialState *s = (SerialState *)opaque;
+ return !fifo8_is_empty(&s->recv_fifo);
+
+}
+
+static const VMStateDescription vmstate_serial_recv_fifo = {
+ .name = "serial/recv_fifo",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = serial_recv_fifo_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(recv_fifo, SerialState, 1, vmstate_fifo8, Fifo8),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool serial_xmit_fifo_needed(void *opaque)
+{
+ SerialState *s = (SerialState *)opaque;
+ return !fifo8_is_empty(&s->xmit_fifo);
+}
+
+static const VMStateDescription vmstate_serial_xmit_fifo = {
+ .name = "serial/xmit_fifo",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = serial_xmit_fifo_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(xmit_fifo, SerialState, 1, vmstate_fifo8, Fifo8),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool serial_fifo_timeout_timer_needed(void *opaque)
+{
+ SerialState *s = (SerialState *)opaque;
+ return timer_pending(s->fifo_timeout_timer);
+}
+
+static const VMStateDescription vmstate_serial_fifo_timeout_timer = {
+ .name = "serial/fifo_timeout_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = serial_fifo_timeout_timer_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(fifo_timeout_timer, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool serial_timeout_ipending_needed(void *opaque)
+{
+ SerialState *s = (SerialState *)opaque;
+ return s->timeout_ipending != 0;
+}
+
+static const VMStateDescription vmstate_serial_timeout_ipending = {
+ .name = "serial/timeout_ipending",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = serial_timeout_ipending_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(timeout_ipending, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool serial_poll_needed(void *opaque)
+{
+ SerialState *s = (SerialState *)opaque;
+ return s->poll_msl >= 0;
+}
+
+static const VMStateDescription vmstate_serial_poll = {
+ .name = "serial/poll",
+ .version_id = 1,
+ .needed = serial_poll_needed,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(poll_msl, SerialState),
+ VMSTATE_TIMER_PTR(modem_status_poll, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_serial = {
+ .name = "serial",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .pre_save = serial_pre_save,
+ .pre_load = serial_pre_load,
+ .post_load = serial_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16_V(divider, SerialState, 2),
+ VMSTATE_UINT8(rbr, SerialState),
+ VMSTATE_UINT8(ier, SerialState),
+ VMSTATE_UINT8(iir, SerialState),
+ VMSTATE_UINT8(lcr, SerialState),
+ VMSTATE_UINT8(mcr, SerialState),
+ VMSTATE_UINT8(lsr, SerialState),
+ VMSTATE_UINT8(msr, SerialState),
+ VMSTATE_UINT8(scr, SerialState),
+ VMSTATE_UINT8_V(fcr_vmstate, SerialState, 3),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_serial_thr_ipending,
+ &vmstate_serial_tsr,
+ &vmstate_serial_recv_fifo,
+ &vmstate_serial_xmit_fifo,
+ &vmstate_serial_fifo_timeout_timer,
+ &vmstate_serial_timeout_ipending,
+ &vmstate_serial_poll,
+ NULL
+ }
+};
+
+static void serial_reset(void *opaque)
+{
+ SerialState *s = opaque;
+
+ s->rbr = 0;
+ s->ier = 0;
+ s->iir = UART_IIR_NO_INT;
+ s->lcr = 0;
+ s->lsr = UART_LSR_TEMT | UART_LSR_THRE;
+ s->msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS;
+ /* Default to 9600 baud, 1 start bit, 8 data bits, 1 stop bit, no parity. */
+ s->divider = 0x0C;
+ s->mcr = UART_MCR_OUT2;
+ s->scr = 0;
+ s->tsr_retry = 0;
+ s->char_transmit_time = (get_ticks_per_sec() / 9600) * 10;
+ s->poll_msl = 0;
+
+ s->timeout_ipending = 0;
+ timer_del(s->fifo_timeout_timer);
+ timer_del(s->modem_status_poll);
+
+ fifo8_reset(&s->recv_fifo);
+ fifo8_reset(&s->xmit_fifo);
+
+ s->last_xmit_ts = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ s->thr_ipending = 0;
+ s->last_break_enable = 0;
+ qemu_irq_lower(s->irq);
+
+ serial_update_msl(s);
+ s->msr &= ~UART_MSR_ANY_DELTA;
+}
+
+void serial_realize_core(SerialState *s, Error **errp)
+{
+ if (!s->chr) {
+ error_setg(errp, "Can't create serial device, empty char device");
+ return;
+ }
+
+ s->modem_status_poll = timer_new_ns(QEMU_CLOCK_VIRTUAL, (QEMUTimerCB *) serial_update_msl, s);
+
+ s->fifo_timeout_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, (QEMUTimerCB *) fifo_timeout_int, s);
+ qemu_register_reset(serial_reset, s);
+
+ qemu_chr_add_handlers(s->chr, serial_can_receive1, serial_receive1,
+ serial_event, s);
+ fifo8_create(&s->recv_fifo, UART_FIFO_LENGTH);
+ fifo8_create(&s->xmit_fifo, UART_FIFO_LENGTH);
+ serial_reset(s);
+}
+
+void serial_exit_core(SerialState *s)
+{
+ qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, NULL);
+ qemu_unregister_reset(serial_reset, s);
+}
+
+/* Change the main reference oscillator frequency. */
+void serial_set_frequency(SerialState *s, uint32_t frequency)
+{
+ s->baudbase = frequency;
+ serial_update_parameters(s);
+}
+
+const MemoryRegionOps serial_io_ops = {
+ .read = serial_ioport_read,
+ .write = serial_ioport_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+SerialState *serial_init(int base, qemu_irq irq, int baudbase,
+ CharDriverState *chr, MemoryRegion *system_io)
+{
+ SerialState *s;
+ Error *err = NULL;
+
+ s = g_malloc0(sizeof(SerialState));
+
+ s->irq = irq;
+ s->baudbase = baudbase;
+ s->chr = chr;
+ serial_realize_core(s, &err);
+ if (err != NULL) {
+ error_report_err(err);
+ exit(1);
+ }
+
+ vmstate_register(NULL, base, &vmstate_serial, s);
+
+ memory_region_init_io(&s->io, NULL, &serial_io_ops, s, "serial", 8);
+ memory_region_add_subregion(system_io, base, &s->io);
+
+ return s;
+}
+
+/* Memory mapped interface */
+static uint64_t serial_mm_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SerialState *s = opaque;
+ return serial_ioport_read(s, addr >> s->it_shift, 1);
+}
+
+static void serial_mm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SerialState *s = opaque;
+ value &= ~0u >> (32 - (size * 8));
+ serial_ioport_write(s, addr >> s->it_shift, value, 1);
+}
+
+static const MemoryRegionOps serial_mm_ops[3] = {
+ [DEVICE_NATIVE_ENDIAN] = {
+ .read = serial_mm_read,
+ .write = serial_mm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ },
+ [DEVICE_LITTLE_ENDIAN] = {
+ .read = serial_mm_read,
+ .write = serial_mm_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ },
+ [DEVICE_BIG_ENDIAN] = {
+ .read = serial_mm_read,
+ .write = serial_mm_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ },
+};
+
+SerialState *serial_mm_init(MemoryRegion *address_space,
+ hwaddr base, int it_shift,
+ qemu_irq irq, int baudbase,
+ CharDriverState *chr, enum device_endian end)
+{
+ SerialState *s;
+ Error *err = NULL;
+
+ s = g_malloc0(sizeof(SerialState));
+
+ s->it_shift = it_shift;
+ s->irq = irq;
+ s->baudbase = baudbase;
+ s->chr = chr;
+
+ serial_realize_core(s, &err);
+ if (err != NULL) {
+ error_report_err(err);
+ exit(1);
+ }
+ vmstate_register(NULL, base, &vmstate_serial, s);
+
+ memory_region_init_io(&s->io, NULL, &serial_mm_ops[end], s,
+ "serial", 8 << it_shift);
+ memory_region_add_subregion(address_space, base, &s->io);
+ return s;
+}
diff --git a/hw/char/sh_serial.c b/hw/char/sh_serial.c
new file mode 100644
index 00000000..9328dd1b
--- /dev/null
+++ b/hw/char/sh_serial.c
@@ -0,0 +1,408 @@
+/*
+ * QEMU SCI/SCIF serial port emulation
+ *
+ * Copyright (c) 2007 Magnus Damm
+ *
+ * Based on serial.c - QEMU 16450 UART emulation
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "sysemu/char.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_SERIAL
+
+#define SH_SERIAL_FLAG_TEND (1 << 0)
+#define SH_SERIAL_FLAG_TDE (1 << 1)
+#define SH_SERIAL_FLAG_RDF (1 << 2)
+#define SH_SERIAL_FLAG_BRK (1 << 3)
+#define SH_SERIAL_FLAG_DR (1 << 4)
+
+#define SH_RX_FIFO_LENGTH (16)
+
+typedef struct {
+ MemoryRegion iomem;
+ MemoryRegion iomem_p4;
+ MemoryRegion iomem_a7;
+ uint8_t smr;
+ uint8_t brr;
+ uint8_t scr;
+ uint8_t dr; /* ftdr / tdr */
+ uint8_t sr; /* fsr / ssr */
+ uint16_t fcr;
+ uint8_t sptr;
+
+ uint8_t rx_fifo[SH_RX_FIFO_LENGTH]; /* frdr / rdr */
+ uint8_t rx_cnt;
+ uint8_t rx_tail;
+ uint8_t rx_head;
+
+ int freq;
+ int feat;
+ int flags;
+ int rtrg;
+
+ CharDriverState *chr;
+
+ qemu_irq eri;
+ qemu_irq rxi;
+ qemu_irq txi;
+ qemu_irq tei;
+ qemu_irq bri;
+} sh_serial_state;
+
+static void sh_serial_clear_fifo(sh_serial_state * s)
+{
+ memset(s->rx_fifo, 0, SH_RX_FIFO_LENGTH);
+ s->rx_cnt = 0;
+ s->rx_head = 0;
+ s->rx_tail = 0;
+}
+
+static void sh_serial_write(void *opaque, hwaddr offs,
+ uint64_t val, unsigned size)
+{
+ sh_serial_state *s = opaque;
+ unsigned char ch;
+
+#ifdef DEBUG_SERIAL
+ printf("sh_serial: write offs=0x%02x val=0x%02x\n",
+ offs, val);
+#endif
+ switch(offs) {
+ case 0x00: /* SMR */
+ s->smr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0x7b : 0xff);
+ return;
+ case 0x04: /* BRR */
+ s->brr = val;
+ return;
+ case 0x08: /* SCR */
+ /* TODO : For SH7751, SCIF mask should be 0xfb. */
+ s->scr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0xfa : 0xff);
+ if (!(val & (1 << 5)))
+ s->flags |= SH_SERIAL_FLAG_TEND;
+ if ((s->feat & SH_SERIAL_FEAT_SCIF) && s->txi) {
+ qemu_set_irq(s->txi, val & (1 << 7));
+ }
+ if (!(val & (1 << 6))) {
+ qemu_set_irq(s->rxi, 0);
+ }
+ return;
+ case 0x0c: /* FTDR / TDR */
+ if (s->chr) {
+ ch = val;
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ }
+ s->dr = val;
+ s->flags &= ~SH_SERIAL_FLAG_TDE;
+ return;
+#if 0
+ case 0x14: /* FRDR / RDR */
+ ret = 0;
+ break;
+#endif
+ }
+ if (s->feat & SH_SERIAL_FEAT_SCIF) {
+ switch(offs) {
+ case 0x10: /* FSR */
+ if (!(val & (1 << 6)))
+ s->flags &= ~SH_SERIAL_FLAG_TEND;
+ if (!(val & (1 << 5)))
+ s->flags &= ~SH_SERIAL_FLAG_TDE;
+ if (!(val & (1 << 4)))
+ s->flags &= ~SH_SERIAL_FLAG_BRK;
+ if (!(val & (1 << 1)))
+ s->flags &= ~SH_SERIAL_FLAG_RDF;
+ if (!(val & (1 << 0)))
+ s->flags &= ~SH_SERIAL_FLAG_DR;
+
+ if (!(val & (1 << 1)) || !(val & (1 << 0))) {
+ if (s->rxi) {
+ qemu_set_irq(s->rxi, 0);
+ }
+ }
+ return;
+ case 0x18: /* FCR */
+ s->fcr = val;
+ switch ((val >> 6) & 3) {
+ case 0:
+ s->rtrg = 1;
+ break;
+ case 1:
+ s->rtrg = 4;
+ break;
+ case 2:
+ s->rtrg = 8;
+ break;
+ case 3:
+ s->rtrg = 14;
+ break;
+ }
+ if (val & (1 << 1)) {
+ sh_serial_clear_fifo(s);
+ s->sr &= ~(1 << 1);
+ }
+
+ return;
+ case 0x20: /* SPTR */
+ s->sptr = val & 0xf3;
+ return;
+ case 0x24: /* LSR */
+ return;
+ }
+ }
+ else {
+ switch(offs) {
+#if 0
+ case 0x0c:
+ ret = s->dr;
+ break;
+ case 0x10:
+ ret = 0;
+ break;
+#endif
+ case 0x1c:
+ s->sptr = val & 0x8f;
+ return;
+ }
+ }
+
+ fprintf(stderr, "sh_serial: unsupported write to 0x%02"
+ HWADDR_PRIx "\n", offs);
+ abort();
+}
+
+static uint64_t sh_serial_read(void *opaque, hwaddr offs,
+ unsigned size)
+{
+ sh_serial_state *s = opaque;
+ uint32_t ret = ~0;
+
+#if 0
+ switch(offs) {
+ case 0x00:
+ ret = s->smr;
+ break;
+ case 0x04:
+ ret = s->brr;
+ break;
+ case 0x08:
+ ret = s->scr;
+ break;
+ case 0x14:
+ ret = 0;
+ break;
+ }
+#endif
+ if (s->feat & SH_SERIAL_FEAT_SCIF) {
+ switch(offs) {
+ case 0x00: /* SMR */
+ ret = s->smr;
+ break;
+ case 0x08: /* SCR */
+ ret = s->scr;
+ break;
+ case 0x10: /* FSR */
+ ret = 0;
+ if (s->flags & SH_SERIAL_FLAG_TEND)
+ ret |= (1 << 6);
+ if (s->flags & SH_SERIAL_FLAG_TDE)
+ ret |= (1 << 5);
+ if (s->flags & SH_SERIAL_FLAG_BRK)
+ ret |= (1 << 4);
+ if (s->flags & SH_SERIAL_FLAG_RDF)
+ ret |= (1 << 1);
+ if (s->flags & SH_SERIAL_FLAG_DR)
+ ret |= (1 << 0);
+
+ if (s->scr & (1 << 5))
+ s->flags |= SH_SERIAL_FLAG_TDE | SH_SERIAL_FLAG_TEND;
+
+ break;
+ case 0x14:
+ if (s->rx_cnt > 0) {
+ ret = s->rx_fifo[s->rx_tail++];
+ s->rx_cnt--;
+ if (s->rx_tail == SH_RX_FIFO_LENGTH)
+ s->rx_tail = 0;
+ if (s->rx_cnt < s->rtrg)
+ s->flags &= ~SH_SERIAL_FLAG_RDF;
+ }
+ break;
+ case 0x18:
+ ret = s->fcr;
+ break;
+ case 0x1c:
+ ret = s->rx_cnt;
+ break;
+ case 0x20:
+ ret = s->sptr;
+ break;
+ case 0x24:
+ ret = 0;
+ break;
+ }
+ }
+ else {
+ switch(offs) {
+#if 0
+ case 0x0c:
+ ret = s->dr;
+ break;
+ case 0x10:
+ ret = 0;
+ break;
+ case 0x14:
+ ret = s->rx_fifo[0];
+ break;
+#endif
+ case 0x1c:
+ ret = s->sptr;
+ break;
+ }
+ }
+#ifdef DEBUG_SERIAL
+ printf("sh_serial: read offs=0x%02x val=0x%x\n",
+ offs, ret);
+#endif
+
+ if (ret & ~((1 << 16) - 1)) {
+ fprintf(stderr, "sh_serial: unsupported read from 0x%02"
+ HWADDR_PRIx "\n", offs);
+ abort();
+ }
+
+ return ret;
+}
+
+static int sh_serial_can_receive(sh_serial_state *s)
+{
+ return s->scr & (1 << 4);
+}
+
+static void sh_serial_receive_break(sh_serial_state *s)
+{
+ if (s->feat & SH_SERIAL_FEAT_SCIF)
+ s->sr |= (1 << 4);
+}
+
+static int sh_serial_can_receive1(void *opaque)
+{
+ sh_serial_state *s = opaque;
+ return sh_serial_can_receive(s);
+}
+
+static void sh_serial_receive1(void *opaque, const uint8_t *buf, int size)
+{
+ sh_serial_state *s = opaque;
+
+ if (s->feat & SH_SERIAL_FEAT_SCIF) {
+ int i;
+ for (i = 0; i < size; i++) {
+ if (s->rx_cnt < SH_RX_FIFO_LENGTH) {
+ s->rx_fifo[s->rx_head++] = buf[i];
+ if (s->rx_head == SH_RX_FIFO_LENGTH) {
+ s->rx_head = 0;
+ }
+ s->rx_cnt++;
+ if (s->rx_cnt >= s->rtrg) {
+ s->flags |= SH_SERIAL_FLAG_RDF;
+ if (s->scr & (1 << 6) && s->rxi) {
+ qemu_set_irq(s->rxi, 1);
+ }
+ }
+ }
+ }
+ } else {
+ s->rx_fifo[0] = buf[0];
+ }
+}
+
+static void sh_serial_event(void *opaque, int event)
+{
+ sh_serial_state *s = opaque;
+ if (event == CHR_EVENT_BREAK)
+ sh_serial_receive_break(s);
+}
+
+static const MemoryRegionOps sh_serial_ops = {
+ .read = sh_serial_read,
+ .write = sh_serial_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void sh_serial_init(MemoryRegion *sysmem,
+ hwaddr base, int feat,
+ uint32_t freq, CharDriverState *chr,
+ qemu_irq eri_source,
+ qemu_irq rxi_source,
+ qemu_irq txi_source,
+ qemu_irq tei_source,
+ qemu_irq bri_source)
+{
+ sh_serial_state *s;
+
+ s = g_malloc0(sizeof(sh_serial_state));
+
+ s->feat = feat;
+ s->flags = SH_SERIAL_FLAG_TEND | SH_SERIAL_FLAG_TDE;
+ s->rtrg = 1;
+
+ s->smr = 0;
+ s->brr = 0xff;
+ s->scr = 1 << 5; /* pretend that TX is enabled so early printk works */
+ s->sptr = 0;
+
+ if (feat & SH_SERIAL_FEAT_SCIF) {
+ s->fcr = 0;
+ }
+ else {
+ s->dr = 0xff;
+ }
+
+ sh_serial_clear_fifo(s);
+
+ memory_region_init_io(&s->iomem, NULL, &sh_serial_ops, s,
+ "serial", 0x100000000ULL);
+
+ memory_region_init_alias(&s->iomem_p4, NULL, "serial-p4", &s->iomem,
+ 0, 0x28);
+ memory_region_add_subregion(sysmem, P4ADDR(base), &s->iomem_p4);
+
+ memory_region_init_alias(&s->iomem_a7, NULL, "serial-a7", &s->iomem,
+ 0, 0x28);
+ memory_region_add_subregion(sysmem, A7ADDR(base), &s->iomem_a7);
+
+ s->chr = chr;
+
+ if (chr) {
+ qemu_chr_fe_claim_no_fail(chr);
+ qemu_chr_add_handlers(chr, sh_serial_can_receive1, sh_serial_receive1,
+ sh_serial_event, s);
+ }
+
+ s->eri = eri_source;
+ s->rxi = rxi_source;
+ s->txi = txi_source;
+ s->tei = tei_source;
+ s->bri = bri_source;
+}
diff --git a/hw/char/spapr_vty.c b/hw/char/spapr_vty.c
new file mode 100644
index 00000000..36b328b9
--- /dev/null
+++ b/hw/char/spapr_vty.c
@@ -0,0 +1,245 @@
+#include "hw/qdev.h"
+#include "sysemu/char.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+
+#define VTERM_BUFSIZE 16
+
+typedef struct VIOsPAPRVTYDevice {
+ VIOsPAPRDevice sdev;
+ CharDriverState *chardev;
+ uint32_t in, out;
+ uint8_t buf[VTERM_BUFSIZE];
+} VIOsPAPRVTYDevice;
+
+#define TYPE_VIO_SPAPR_VTY_DEVICE "spapr-vty"
+#define VIO_SPAPR_VTY_DEVICE(obj) \
+ OBJECT_CHECK(VIOsPAPRVTYDevice, (obj), TYPE_VIO_SPAPR_VTY_DEVICE)
+
+static int vty_can_receive(void *opaque)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(opaque);
+
+ return (dev->in - dev->out) < VTERM_BUFSIZE;
+}
+
+static void vty_receive(void *opaque, const uint8_t *buf, int size)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(opaque);
+ int i;
+
+ if ((dev->in == dev->out) && size) {
+ /* toggle line to simulate edge interrupt */
+ qemu_irq_pulse(spapr_vio_qirq(&dev->sdev));
+ }
+ for (i = 0; i < size; i++) {
+ assert((dev->in - dev->out) < VTERM_BUFSIZE);
+ dev->buf[dev->in++ % VTERM_BUFSIZE] = buf[i];
+ }
+}
+
+static int vty_getchars(VIOsPAPRDevice *sdev, uint8_t *buf, int max)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(sdev);
+ int n = 0;
+
+ while ((n < max) && (dev->out != dev->in)) {
+ buf[n++] = dev->buf[dev->out++ % VTERM_BUFSIZE];
+ }
+
+ qemu_chr_accept_input(dev->chardev);
+
+ return n;
+}
+
+void vty_putchars(VIOsPAPRDevice *sdev, uint8_t *buf, int len)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(sdev);
+
+ /* FIXME: should check the qemu_chr_fe_write() return value */
+ qemu_chr_fe_write(dev->chardev, buf, len);
+}
+
+static void spapr_vty_realize(VIOsPAPRDevice *sdev, Error **errp)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(sdev);
+
+ if (!dev->chardev) {
+ error_setg(errp, "chardev property not set");
+ return;
+ }
+
+ qemu_chr_add_handlers(dev->chardev, vty_can_receive,
+ vty_receive, NULL, dev);
+}
+
+/* Forward declaration */
+static target_ulong h_put_term_char(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong len = args[1];
+ target_ulong char0_7 = args[2];
+ target_ulong char8_15 = args[3];
+ VIOsPAPRDevice *sdev;
+ uint8_t buf[16];
+
+ sdev = vty_lookup(spapr, reg);
+ if (!sdev) {
+ return H_PARAMETER;
+ }
+
+ if (len > 16) {
+ return H_PARAMETER;
+ }
+
+ *((uint64_t *)buf) = cpu_to_be64(char0_7);
+ *((uint64_t *)buf + 1) = cpu_to_be64(char8_15);
+
+ vty_putchars(sdev, buf, len);
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_get_term_char(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong *len = args + 0;
+ target_ulong *char0_7 = args + 1;
+ target_ulong *char8_15 = args + 2;
+ VIOsPAPRDevice *sdev;
+ uint8_t buf[16];
+
+ sdev = vty_lookup(spapr, reg);
+ if (!sdev) {
+ return H_PARAMETER;
+ }
+
+ *len = vty_getchars(sdev, buf, sizeof(buf));
+ if (*len < 16) {
+ memset(buf + *len, 0, 16 - *len);
+ }
+
+ *char0_7 = be64_to_cpu(*((uint64_t *)buf));
+ *char8_15 = be64_to_cpu(*((uint64_t *)buf + 1));
+
+ return H_SUCCESS;
+}
+
+void spapr_vty_create(VIOsPAPRBus *bus, CharDriverState *chardev)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->bus, "spapr-vty");
+ qdev_prop_set_chr(dev, "chardev", chardev);
+ qdev_init_nofail(dev);
+}
+
+static Property spapr_vty_properties[] = {
+ DEFINE_SPAPR_PROPERTIES(VIOsPAPRVTYDevice, sdev),
+ DEFINE_PROP_CHR("chardev", VIOsPAPRVTYDevice, chardev),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_spapr_vty = {
+ .name = "spapr_vty",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_SPAPR_VIO(sdev, VIOsPAPRVTYDevice),
+
+ VMSTATE_UINT32(in, VIOsPAPRVTYDevice),
+ VMSTATE_UINT32(out, VIOsPAPRVTYDevice),
+ VMSTATE_BUFFER(buf, VIOsPAPRVTYDevice),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void spapr_vty_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VIOsPAPRDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
+
+ k->realize = spapr_vty_realize;
+ k->dt_name = "vty";
+ k->dt_type = "serial";
+ k->dt_compatible = "hvterm1";
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->props = spapr_vty_properties;
+ dc->vmsd = &vmstate_spapr_vty;
+}
+
+static const TypeInfo spapr_vty_info = {
+ .name = TYPE_VIO_SPAPR_VTY_DEVICE,
+ .parent = TYPE_VIO_SPAPR_DEVICE,
+ .instance_size = sizeof(VIOsPAPRVTYDevice),
+ .class_init = spapr_vty_class_init,
+};
+
+VIOsPAPRDevice *spapr_vty_get_default(VIOsPAPRBus *bus)
+{
+ VIOsPAPRDevice *sdev, *selected;
+ BusChild *kid;
+
+ /*
+ * To avoid the console bouncing around we want one VTY to be
+ * the "default". We haven't really got anything to go on, so
+ * arbitrarily choose the one with the lowest reg value.
+ */
+
+ selected = NULL;
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ DeviceState *iter = kid->child;
+
+ /* Only look at VTY devices */
+ if (!object_dynamic_cast(OBJECT(iter), TYPE_VIO_SPAPR_VTY_DEVICE)) {
+ continue;
+ }
+
+ sdev = VIO_SPAPR_DEVICE(iter);
+
+ /* First VTY we've found, so it is selected for now */
+ if (!selected) {
+ selected = sdev;
+ continue;
+ }
+
+ /* Choose VTY with lowest reg value */
+ if (sdev->reg < selected->reg) {
+ selected = sdev;
+ }
+ }
+
+ return selected;
+}
+
+VIOsPAPRDevice *vty_lookup(sPAPRMachineState *spapr, target_ulong reg)
+{
+ VIOsPAPRDevice *sdev;
+
+ sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ if (!sdev && reg == 0) {
+ /* Hack for kernel early debug, which always specifies reg==0.
+ * We search all VIO devices, and grab the vty with the lowest
+ * reg. This attempts to mimic existing PowerVM behaviour
+ * (early debug does work there, despite having no vty with
+ * reg==0. */
+ return spapr_vty_get_default(spapr->vio_bus);
+ }
+
+ if (!object_dynamic_cast(OBJECT(sdev), TYPE_VIO_SPAPR_VTY_DEVICE)) {
+ return NULL;
+ }
+
+ return sdev;
+}
+
+static void spapr_vty_register_types(void)
+{
+ spapr_register_hypercall(H_PUT_TERM_CHAR, h_put_term_char);
+ spapr_register_hypercall(H_GET_TERM_CHAR, h_get_term_char);
+ type_register_static(&spapr_vty_info);
+}
+
+type_init(spapr_vty_register_types)
diff --git a/hw/char/stm32f2xx_usart.c b/hw/char/stm32f2xx_usart.c
new file mode 100644
index 00000000..c9d3a1be
--- /dev/null
+++ b/hw/char/stm32f2xx_usart.c
@@ -0,0 +1,232 @@
+/*
+ * STM32F2XX USART
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * 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.
+ */
+
+#include "hw/char/stm32f2xx_usart.h"
+
+#ifndef STM_USART_ERR_DEBUG
+#define STM_USART_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(lvl, fmt, args...) do { \
+ if (STM_USART_ERR_DEBUG >= lvl) { \
+ qemu_log("%s: " fmt, __func__, ## args); \
+ } \
+} while (0);
+
+#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
+
+static int stm32f2xx_usart_can_receive(void *opaque)
+{
+ STM32F2XXUsartState *s = opaque;
+
+ if (!(s->usart_sr & USART_SR_RXNE)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void stm32f2xx_usart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ STM32F2XXUsartState *s = opaque;
+
+ s->usart_dr = *buf;
+
+ if (!(s->usart_cr1 & USART_CR1_UE && s->usart_cr1 & USART_CR1_RE)) {
+ /* USART not enabled - drop the chars */
+ DB_PRINT("Dropping the chars\n");
+ return;
+ }
+
+ s->usart_sr |= USART_SR_RXNE;
+
+ if (s->usart_cr1 & USART_CR1_RXNEIE) {
+ qemu_set_irq(s->irq, 1);
+ }
+
+ DB_PRINT("Receiving: %c\n", s->usart_dr);
+}
+
+static void stm32f2xx_usart_reset(DeviceState *dev)
+{
+ STM32F2XXUsartState *s = STM32F2XX_USART(dev);
+
+ s->usart_sr = USART_SR_RESET;
+ s->usart_dr = 0x00000000;
+ s->usart_brr = 0x00000000;
+ s->usart_cr1 = 0x00000000;
+ s->usart_cr2 = 0x00000000;
+ s->usart_cr3 = 0x00000000;
+ s->usart_gtpr = 0x00000000;
+
+ qemu_set_irq(s->irq, 0);
+}
+
+static uint64_t stm32f2xx_usart_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ STM32F2XXUsartState *s = opaque;
+ uint64_t retvalue;
+
+ DB_PRINT("Read 0x%"HWADDR_PRIx"\n", addr);
+
+ switch (addr) {
+ case USART_SR:
+ retvalue = s->usart_sr;
+ s->usart_sr &= ~USART_SR_TC;
+ if (s->chr) {
+ qemu_chr_accept_input(s->chr);
+ }
+ return retvalue;
+ case USART_DR:
+ DB_PRINT("Value: 0x%" PRIx32 ", %c\n", s->usart_dr, (char) s->usart_dr);
+ s->usart_sr |= USART_SR_TXE;
+ s->usart_sr &= ~USART_SR_RXNE;
+ if (s->chr) {
+ qemu_chr_accept_input(s->chr);
+ }
+ qemu_set_irq(s->irq, 0);
+ return s->usart_dr & 0x3FF;
+ case USART_BRR:
+ return s->usart_brr;
+ case USART_CR1:
+ return s->usart_cr1;
+ case USART_CR2:
+ return s->usart_cr2;
+ case USART_CR3:
+ return s->usart_cr3;
+ case USART_GTPR:
+ return s->usart_gtpr;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+ return 0;
+ }
+
+ return 0;
+}
+
+static void stm32f2xx_usart_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ STM32F2XXUsartState *s = opaque;
+ uint32_t value = val64;
+ unsigned char ch;
+
+ DB_PRINT("Write 0x%" PRIx32 ", 0x%"HWADDR_PRIx"\n", value, addr);
+
+ switch (addr) {
+ case USART_SR:
+ if (value <= 0x3FF) {
+ s->usart_sr = value;
+ } else {
+ s->usart_sr &= value;
+ }
+ if (!(s->usart_sr & USART_SR_RXNE)) {
+ qemu_set_irq(s->irq, 0);
+ }
+ return;
+ case USART_DR:
+ if (value < 0xF000) {
+ ch = value;
+ if (s->chr) {
+ qemu_chr_fe_write_all(s->chr, &ch, 1);
+ }
+ s->usart_sr |= USART_SR_TC;
+ s->usart_sr &= ~USART_SR_TXE;
+ }
+ return;
+ case USART_BRR:
+ s->usart_brr = value;
+ return;
+ case USART_CR1:
+ s->usart_cr1 = value;
+ if (s->usart_cr1 & USART_CR1_RXNEIE &&
+ s->usart_sr & USART_SR_RXNE) {
+ qemu_set_irq(s->irq, 1);
+ }
+ return;
+ case USART_CR2:
+ s->usart_cr2 = value;
+ return;
+ case USART_CR3:
+ s->usart_cr3 = value;
+ return;
+ case USART_GTPR:
+ s->usart_gtpr = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+ }
+}
+
+static const MemoryRegionOps stm32f2xx_usart_ops = {
+ .read = stm32f2xx_usart_read,
+ .write = stm32f2xx_usart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void stm32f2xx_usart_init(Object *obj)
+{
+ STM32F2XXUsartState *s = STM32F2XX_USART(obj);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+ memory_region_init_io(&s->mmio, obj, &stm32f2xx_usart_ops, s,
+ TYPE_STM32F2XX_USART, 0x2000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, stm32f2xx_usart_can_receive,
+ stm32f2xx_usart_receive, NULL, s);
+ }
+}
+
+static void stm32f2xx_usart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = stm32f2xx_usart_reset;
+ /* Reason: instance_init() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo stm32f2xx_usart_info = {
+ .name = TYPE_STM32F2XX_USART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F2XXUsartState),
+ .instance_init = stm32f2xx_usart_init,
+ .class_init = stm32f2xx_usart_class_init,
+};
+
+static void stm32f2xx_usart_register_types(void)
+{
+ type_register_static(&stm32f2xx_usart_info);
+}
+
+type_init(stm32f2xx_usart_register_types)
diff --git a/hw/char/virtio-console.c b/hw/char/virtio-console.c
new file mode 100644
index 00000000..2a867cb4
--- /dev/null
+++ b/hw/char/virtio-console.c
@@ -0,0 +1,217 @@
+/*
+ * Virtio Console and Generic Serial Port Devices
+ *
+ * Copyright Red Hat, Inc. 2009, 2010
+ *
+ * Authors:
+ * Amit Shah <amit.shah@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "sysemu/char.h"
+#include "qemu/error-report.h"
+#include "trace.h"
+#include "hw/virtio/virtio-serial.h"
+#include "qapi-event.h"
+
+#define TYPE_VIRTIO_CONSOLE_SERIAL_PORT "virtserialport"
+#define VIRTIO_CONSOLE(obj) \
+ OBJECT_CHECK(VirtConsole, (obj), TYPE_VIRTIO_CONSOLE_SERIAL_PORT)
+
+typedef struct VirtConsole {
+ VirtIOSerialPort parent_obj;
+
+ CharDriverState *chr;
+ guint watch;
+} VirtConsole;
+
+/*
+ * Callback function that's called from chardevs when backend becomes
+ * writable.
+ */
+static gboolean chr_write_unblocked(GIOChannel *chan, GIOCondition cond,
+ void *opaque)
+{
+ VirtConsole *vcon = opaque;
+
+ vcon->watch = 0;
+ virtio_serial_throttle_port(VIRTIO_SERIAL_PORT(vcon), false);
+ return FALSE;
+}
+
+/* Callback function that's called when the guest sends us data */
+static ssize_t flush_buf(VirtIOSerialPort *port,
+ const uint8_t *buf, ssize_t len)
+{
+ VirtConsole *vcon = VIRTIO_CONSOLE(port);
+ ssize_t ret;
+
+ if (!vcon->chr) {
+ /* If there's no backend, we can just say we consumed all data. */
+ return len;
+ }
+
+ ret = qemu_chr_fe_write(vcon->chr, buf, len);
+ trace_virtio_console_flush_buf(port->id, len, ret);
+
+ if (ret < len) {
+ VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ /*
+ * Ideally we'd get a better error code than just -1, but
+ * that's what the chardev interface gives us right now. If
+ * we had a finer-grained message, like -EPIPE, we could close
+ * this connection.
+ */
+ if (ret < 0)
+ ret = 0;
+ if (!k->is_console) {
+ virtio_serial_throttle_port(port, true);
+ if (!vcon->watch) {
+ vcon->watch = qemu_chr_fe_add_watch(vcon->chr,
+ G_IO_OUT|G_IO_HUP,
+ chr_write_unblocked, vcon);
+ }
+ }
+ }
+ return ret;
+}
+
+/* Callback function that's called when the guest opens/closes the port */
+static void set_guest_connected(VirtIOSerialPort *port, int guest_connected)
+{
+ VirtConsole *vcon = VIRTIO_CONSOLE(port);
+ DeviceState *dev = DEVICE(port);
+
+ if (vcon->chr) {
+ qemu_chr_fe_set_open(vcon->chr, guest_connected);
+ }
+
+ if (dev->id) {
+ qapi_event_send_vserport_change(dev->id, guest_connected,
+ &error_abort);
+ }
+}
+
+static void guest_writable(VirtIOSerialPort *port)
+{
+ VirtConsole *vcon = VIRTIO_CONSOLE(port);
+
+ if (vcon->chr) {
+ qemu_chr_accept_input(vcon->chr);
+ }
+}
+
+/* Readiness of the guest to accept data on a port */
+static int chr_can_read(void *opaque)
+{
+ VirtConsole *vcon = opaque;
+
+ return virtio_serial_guest_ready(VIRTIO_SERIAL_PORT(vcon));
+}
+
+/* Send data from a char device over to the guest */
+static void chr_read(void *opaque, const uint8_t *buf, int size)
+{
+ VirtConsole *vcon = opaque;
+ VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(vcon);
+
+ trace_virtio_console_chr_read(port->id, size);
+ virtio_serial_write(port, buf, size);
+}
+
+static void chr_event(void *opaque, int event)
+{
+ VirtConsole *vcon = opaque;
+ VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(vcon);
+
+ trace_virtio_console_chr_event(port->id, event);
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ virtio_serial_open(port);
+ break;
+ case CHR_EVENT_CLOSED:
+ if (vcon->watch) {
+ g_source_remove(vcon->watch);
+ vcon->watch = 0;
+ }
+ virtio_serial_close(port);
+ break;
+ }
+}
+
+static void virtconsole_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev);
+ VirtConsole *vcon = VIRTIO_CONSOLE(dev);
+ VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(dev);
+
+ if (port->id == 0 && !k->is_console) {
+ error_setg(errp, "Port number 0 on virtio-serial devices reserved "
+ "for virtconsole devices for backward compatibility.");
+ return;
+ }
+
+ if (vcon->chr) {
+ vcon->chr->explicit_fe_open = 1;
+ qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event,
+ vcon);
+ }
+}
+
+static void virtconsole_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtConsole *vcon = VIRTIO_CONSOLE(dev);
+
+ if (vcon->watch) {
+ g_source_remove(vcon->watch);
+ }
+}
+
+static void virtconsole_class_init(ObjectClass *klass, void *data)
+{
+ VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
+
+ k->is_console = true;
+}
+
+static const TypeInfo virtconsole_info = {
+ .name = "virtconsole",
+ .parent = TYPE_VIRTIO_CONSOLE_SERIAL_PORT,
+ .class_init = virtconsole_class_init,
+};
+
+static Property virtserialport_properties[] = {
+ DEFINE_PROP_CHR("chardev", VirtConsole, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtserialport_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
+
+ k->realize = virtconsole_realize;
+ k->unrealize = virtconsole_unrealize;
+ k->have_data = flush_buf;
+ k->set_guest_connected = set_guest_connected;
+ k->guest_writable = guest_writable;
+ dc->props = virtserialport_properties;
+}
+
+static const TypeInfo virtserialport_info = {
+ .name = TYPE_VIRTIO_CONSOLE_SERIAL_PORT,
+ .parent = TYPE_VIRTIO_SERIAL_PORT,
+ .instance_size = sizeof(VirtConsole),
+ .class_init = virtserialport_class_init,
+};
+
+static void virtconsole_register_types(void)
+{
+ type_register_static(&virtserialport_info);
+ type_register_static(&virtconsole_info);
+}
+
+type_init(virtconsole_register_types)
diff --git a/hw/char/virtio-serial-bus.c b/hw/char/virtio-serial-bus.c
new file mode 100644
index 00000000..be970587
--- /dev/null
+++ b/hw/char/virtio-serial-bus.c
@@ -0,0 +1,1134 @@
+/*
+ * A bus for connecting virtio serial and console ports
+ *
+ * Copyright (C) 2009, 2010 Red Hat, Inc.
+ *
+ * Author(s):
+ * Amit Shah <amit.shah@redhat.com>
+ *
+ * Some earlier parts are:
+ * Copyright IBM, Corp. 2008
+ * authored by
+ * Christian Ehrhardt <ehrhardt@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/iov.h"
+#include "monitor/monitor.h"
+#include "qemu/error-report.h"
+#include "qemu/queue.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "hw/virtio/virtio-serial.h"
+#include "hw/virtio/virtio-access.h"
+
+static struct VirtIOSerialDevices {
+ QLIST_HEAD(, VirtIOSerial) devices;
+} vserdevices;
+
+static VirtIOSerialPort *find_port_by_id(VirtIOSerial *vser, uint32_t id)
+{
+ VirtIOSerialPort *port;
+
+ if (id == VIRTIO_CONSOLE_BAD_ID) {
+ return NULL;
+ }
+
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ if (port->id == id)
+ return port;
+ }
+ return NULL;
+}
+
+static VirtIOSerialPort *find_port_by_vq(VirtIOSerial *vser, VirtQueue *vq)
+{
+ VirtIOSerialPort *port;
+
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ if (port->ivq == vq || port->ovq == vq)
+ return port;
+ }
+ return NULL;
+}
+
+static VirtIOSerialPort *find_port_by_name(char *name)
+{
+ VirtIOSerial *vser;
+
+ QLIST_FOREACH(vser, &vserdevices.devices, next) {
+ VirtIOSerialPort *port;
+
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ if (port->name && !strcmp(port->name, name)) {
+ return port;
+ }
+ }
+ }
+ return NULL;
+}
+
+static bool use_multiport(VirtIOSerial *vser)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(vser);
+ return virtio_vdev_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT);
+}
+
+static size_t write_to_port(VirtIOSerialPort *port,
+ const uint8_t *buf, size_t size)
+{
+ VirtQueueElement elem;
+ VirtQueue *vq;
+ size_t offset;
+
+ vq = port->ivq;
+ if (!virtio_queue_ready(vq)) {
+ return 0;
+ }
+
+ offset = 0;
+ while (offset < size) {
+ size_t len;
+
+ if (!virtqueue_pop(vq, &elem)) {
+ break;
+ }
+
+ len = iov_from_buf(elem.in_sg, elem.in_num, 0,
+ buf + offset, size - offset);
+ offset += len;
+
+ virtqueue_push(vq, &elem, len);
+ }
+
+ virtio_notify(VIRTIO_DEVICE(port->vser), vq);
+ return offset;
+}
+
+static void discard_vq_data(VirtQueue *vq, VirtIODevice *vdev)
+{
+ VirtQueueElement elem;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+ while (virtqueue_pop(vq, &elem)) {
+ virtqueue_push(vq, &elem, 0);
+ }
+ virtio_notify(vdev, vq);
+}
+
+static void do_flush_queued_data(VirtIOSerialPort *port, VirtQueue *vq,
+ VirtIODevice *vdev)
+{
+ VirtIOSerialPortClass *vsc;
+
+ assert(port);
+ assert(virtio_queue_ready(vq));
+
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ while (!port->throttled) {
+ unsigned int i;
+
+ /* Pop an elem only if we haven't left off a previous one mid-way */
+ if (!port->elem.out_num) {
+ if (!virtqueue_pop(vq, &port->elem)) {
+ break;
+ }
+ port->iov_idx = 0;
+ port->iov_offset = 0;
+ }
+
+ for (i = port->iov_idx; i < port->elem.out_num; i++) {
+ size_t buf_size;
+ ssize_t ret;
+
+ buf_size = port->elem.out_sg[i].iov_len - port->iov_offset;
+ ret = vsc->have_data(port,
+ port->elem.out_sg[i].iov_base
+ + port->iov_offset,
+ buf_size);
+ if (port->throttled) {
+ port->iov_idx = i;
+ if (ret > 0) {
+ port->iov_offset += ret;
+ }
+ break;
+ }
+ port->iov_offset = 0;
+ }
+ if (port->throttled) {
+ break;
+ }
+ virtqueue_push(vq, &port->elem, 0);
+ port->elem.out_num = 0;
+ }
+ virtio_notify(vdev, vq);
+}
+
+static void flush_queued_data(VirtIOSerialPort *port)
+{
+ assert(port);
+
+ if (!virtio_queue_ready(port->ovq)) {
+ return;
+ }
+ do_flush_queued_data(port, port->ovq, VIRTIO_DEVICE(port->vser));
+}
+
+static size_t send_control_msg(VirtIOSerial *vser, void *buf, size_t len)
+{
+ VirtQueueElement elem;
+ VirtQueue *vq;
+
+ vq = vser->c_ivq;
+ if (!virtio_queue_ready(vq)) {
+ return 0;
+ }
+ if (!virtqueue_pop(vq, &elem)) {
+ return 0;
+ }
+
+ /* TODO: detect a buffer that's too short, set NEEDS_RESET */
+ iov_from_buf(elem.in_sg, elem.in_num, 0, buf, len);
+
+ virtqueue_push(vq, &elem, len);
+ virtio_notify(VIRTIO_DEVICE(vser), vq);
+ return len;
+}
+
+static size_t send_control_event(VirtIOSerial *vser, uint32_t port_id,
+ uint16_t event, uint16_t value)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(vser);
+ struct virtio_console_control cpkt;
+
+ virtio_stl_p(vdev, &cpkt.id, port_id);
+ virtio_stw_p(vdev, &cpkt.event, event);
+ virtio_stw_p(vdev, &cpkt.value, value);
+
+ trace_virtio_serial_send_control_event(port_id, event, value);
+ return send_control_msg(vser, &cpkt, sizeof(cpkt));
+}
+
+/* Functions for use inside qemu to open and read from/write to ports */
+int virtio_serial_open(VirtIOSerialPort *port)
+{
+ /* Don't allow opening an already-open port */
+ if (port->host_connected) {
+ return 0;
+ }
+ /* Send port open notification to the guest */
+ port->host_connected = true;
+ send_control_event(port->vser, port->id, VIRTIO_CONSOLE_PORT_OPEN, 1);
+
+ return 0;
+}
+
+int virtio_serial_close(VirtIOSerialPort *port)
+{
+ port->host_connected = false;
+ /*
+ * If there's any data the guest sent which the app didn't
+ * consume, reset the throttling flag and discard the data.
+ */
+ port->throttled = false;
+ discard_vq_data(port->ovq, VIRTIO_DEVICE(port->vser));
+
+ send_control_event(port->vser, port->id, VIRTIO_CONSOLE_PORT_OPEN, 0);
+
+ return 0;
+}
+
+/* Individual ports/apps call this function to write to the guest. */
+ssize_t virtio_serial_write(VirtIOSerialPort *port, const uint8_t *buf,
+ size_t size)
+{
+ if (!port || !port->host_connected || !port->guest_connected) {
+ return 0;
+ }
+ return write_to_port(port, buf, size);
+}
+
+/*
+ * Readiness of the guest to accept data on a port.
+ * Returns max. data the guest can receive
+ */
+size_t virtio_serial_guest_ready(VirtIOSerialPort *port)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(port->vser);
+ VirtQueue *vq = port->ivq;
+ unsigned int bytes;
+
+ if (!virtio_queue_ready(vq) ||
+ !(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK) ||
+ virtio_queue_empty(vq)) {
+ return 0;
+ }
+ if (use_multiport(port->vser) && !port->guest_connected) {
+ return 0;
+ }
+ virtqueue_get_avail_bytes(vq, &bytes, NULL, 4096, 0);
+ return bytes;
+}
+
+static void flush_queued_data_bh(void *opaque)
+{
+ VirtIOSerialPort *port = opaque;
+
+ flush_queued_data(port);
+}
+
+void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle)
+{
+ if (!port) {
+ return;
+ }
+
+ trace_virtio_serial_throttle_port(port->id, throttle);
+ port->throttled = throttle;
+ if (throttle) {
+ return;
+ }
+ qemu_bh_schedule(port->bh);
+}
+
+/* Guest wants to notify us of some event */
+static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(vser);
+ struct VirtIOSerialPort *port;
+ VirtIOSerialPortClass *vsc;
+ struct virtio_console_control cpkt, *gcpkt;
+ uint8_t *buffer;
+ size_t buffer_len;
+
+ gcpkt = buf;
+
+ if (len < sizeof(cpkt)) {
+ /* The guest sent an invalid control packet */
+ return;
+ }
+
+ cpkt.event = virtio_lduw_p(vdev, &gcpkt->event);
+ cpkt.value = virtio_lduw_p(vdev, &gcpkt->value);
+
+ trace_virtio_serial_handle_control_message(cpkt.event, cpkt.value);
+
+ if (cpkt.event == VIRTIO_CONSOLE_DEVICE_READY) {
+ if (!cpkt.value) {
+ error_report("virtio-serial-bus: Guest failure in adding device %s",
+ vser->bus.qbus.name);
+ return;
+ }
+ /*
+ * The device is up, we can now tell the device about all the
+ * ports we have here.
+ */
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ send_control_event(vser, port->id, VIRTIO_CONSOLE_PORT_ADD, 1);
+ }
+ return;
+ }
+
+ port = find_port_by_id(vser, virtio_ldl_p(vdev, &gcpkt->id));
+ if (!port) {
+ error_report("virtio-serial-bus: Unexpected port id %u for device %s",
+ virtio_ldl_p(vdev, &gcpkt->id), vser->bus.qbus.name);
+ return;
+ }
+
+ trace_virtio_serial_handle_control_message_port(port->id);
+
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ switch(cpkt.event) {
+ case VIRTIO_CONSOLE_PORT_READY:
+ if (!cpkt.value) {
+ error_report("virtio-serial-bus: Guest failure in adding port %u for device %s",
+ port->id, vser->bus.qbus.name);
+ break;
+ }
+ /*
+ * Now that we know the guest asked for the port name, we're
+ * sure the guest has initialised whatever state is necessary
+ * for this port. Now's a good time to let the guest know if
+ * this port is a console port so that the guest can hook it
+ * up to hvc.
+ */
+ if (vsc->is_console) {
+ send_control_event(vser, port->id, VIRTIO_CONSOLE_CONSOLE_PORT, 1);
+ }
+
+ if (port->name) {
+ virtio_stl_p(vdev, &cpkt.id, port->id);
+ virtio_stw_p(vdev, &cpkt.event, VIRTIO_CONSOLE_PORT_NAME);
+ virtio_stw_p(vdev, &cpkt.value, 1);
+
+ buffer_len = sizeof(cpkt) + strlen(port->name) + 1;
+ buffer = g_malloc(buffer_len);
+
+ memcpy(buffer, &cpkt, sizeof(cpkt));
+ memcpy(buffer + sizeof(cpkt), port->name, strlen(port->name));
+ buffer[buffer_len - 1] = 0;
+
+ send_control_msg(vser, buffer, buffer_len);
+ g_free(buffer);
+ }
+
+ if (port->host_connected) {
+ send_control_event(vser, port->id, VIRTIO_CONSOLE_PORT_OPEN, 1);
+ }
+
+ /*
+ * When the guest has asked us for this information it means
+ * the guest is all setup and has its virtqueues
+ * initialised. If some app is interested in knowing about
+ * this event, let it know.
+ */
+ if (vsc->guest_ready) {
+ vsc->guest_ready(port);
+ }
+ break;
+
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->guest_connected = cpkt.value;
+ if (vsc->set_guest_connected) {
+ /* Send the guest opened notification if an app is interested */
+ vsc->set_guest_connected(port, cpkt.value);
+ }
+ break;
+ }
+}
+
+static void control_in(VirtIODevice *vdev, VirtQueue *vq)
+{
+}
+
+static void control_out(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtQueueElement elem;
+ VirtIOSerial *vser;
+ uint8_t *buf;
+ size_t len;
+
+ vser = VIRTIO_SERIAL(vdev);
+
+ len = 0;
+ buf = NULL;
+ while (virtqueue_pop(vq, &elem)) {
+ size_t cur_len;
+
+ cur_len = iov_size(elem.out_sg, elem.out_num);
+ /*
+ * Allocate a new buf only if we didn't have one previously or
+ * if the size of the buf differs
+ */
+ if (cur_len > len) {
+ g_free(buf);
+
+ buf = g_malloc(cur_len);
+ len = cur_len;
+ }
+ iov_to_buf(elem.out_sg, elem.out_num, 0, buf, cur_len);
+
+ handle_control_message(vser, buf, cur_len);
+ virtqueue_push(vq, &elem, 0);
+ }
+ g_free(buf);
+ virtio_notify(vdev, vq);
+}
+
+/* Guest wrote something to some port. */
+static void handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSerial *vser;
+ VirtIOSerialPort *port;
+
+ vser = VIRTIO_SERIAL(vdev);
+ port = find_port_by_vq(vser, vq);
+
+ if (!port || !port->host_connected) {
+ discard_vq_data(vq, vdev);
+ return;
+ }
+
+ if (!port->throttled) {
+ do_flush_queued_data(port, vq, vdev);
+ return;
+ }
+}
+
+static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
+{
+ /*
+ * Users of virtio-serial would like to know when guest becomes
+ * writable again -- i.e. if a vq had stuff queued up and the
+ * guest wasn't reading at all, the host would not be able to
+ * write to the vq anymore. Once the guest reads off something,
+ * we can start queueing things up again. However, this call is
+ * made for each buffer addition by the guest -- even though free
+ * buffers existed prior to the current buffer addition. This is
+ * done so as not to maintain previous state, which will need
+ * additional live-migration-related changes.
+ */
+ VirtIOSerial *vser;
+ VirtIOSerialPort *port;
+ VirtIOSerialPortClass *vsc;
+
+ vser = VIRTIO_SERIAL(vdev);
+ port = find_port_by_vq(vser, vq);
+
+ if (!port) {
+ return;
+ }
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ /*
+ * If guest_connected is false, this call is being made by the
+ * early-boot queueing up of descriptors, which is just noise for
+ * the host apps -- don't disturb them in that case.
+ */
+ if (port->guest_connected && port->host_connected && vsc->guest_writable) {
+ vsc->guest_writable(port);
+ }
+}
+
+static uint64_t get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
+{
+ VirtIOSerial *vser;
+
+ vser = VIRTIO_SERIAL(vdev);
+
+ if (vser->bus.max_nr_ports > 1) {
+ virtio_add_feature(&features, VIRTIO_CONSOLE_F_MULTIPORT);
+ }
+ return features;
+}
+
+/* Guest requested config info */
+static void get_config(VirtIODevice *vdev, uint8_t *config_data)
+{
+ VirtIOSerial *vser = VIRTIO_SERIAL(vdev);
+ struct virtio_console_config *config =
+ (struct virtio_console_config *)config_data;
+
+ config->cols = 0;
+ config->rows = 0;
+ config->max_nr_ports = virtio_tswap32(vdev,
+ vser->serial.max_virtserial_ports);
+}
+
+static void guest_reset(VirtIOSerial *vser)
+{
+ VirtIOSerialPort *port;
+ VirtIOSerialPortClass *vsc;
+
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+ if (port->guest_connected) {
+ port->guest_connected = false;
+ if (vsc->set_guest_connected) {
+ vsc->set_guest_connected(port, false);
+ }
+ }
+ }
+}
+
+static void set_status(VirtIODevice *vdev, uint8_t status)
+{
+ VirtIOSerial *vser;
+ VirtIOSerialPort *port;
+
+ vser = VIRTIO_SERIAL(vdev);
+ port = find_port_by_id(vser, 0);
+
+ if (port && !use_multiport(port->vser)
+ && (status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ /*
+ * Non-multiport guests won't be able to tell us guest
+ * open/close status. Such guests can only have a port at id
+ * 0, so set guest_connected for such ports as soon as guest
+ * is up.
+ */
+ port->guest_connected = true;
+ }
+ if (!(status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ guest_reset(vser);
+ }
+}
+
+static void vser_reset(VirtIODevice *vdev)
+{
+ VirtIOSerial *vser;
+
+ vser = VIRTIO_SERIAL(vdev);
+ guest_reset(vser);
+}
+
+static void virtio_serial_save(QEMUFile *f, void *opaque)
+{
+ /* The virtio device */
+ virtio_save(VIRTIO_DEVICE(opaque), f);
+}
+
+static void virtio_serial_save_device(VirtIODevice *vdev, QEMUFile *f)
+{
+ VirtIOSerial *s = VIRTIO_SERIAL(vdev);
+ VirtIOSerialPort *port;
+ uint32_t nr_active_ports;
+ unsigned int i, max_nr_ports;
+ struct virtio_console_config config;
+
+ /* The config space (ignored on the far end in current versions) */
+ get_config(vdev, (uint8_t *)&config);
+ qemu_put_be16s(f, &config.cols);
+ qemu_put_be16s(f, &config.rows);
+ qemu_put_be32s(f, &config.max_nr_ports);
+
+ /* The ports map */
+ max_nr_ports = s->serial.max_virtserial_ports;
+ for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
+ qemu_put_be32s(f, &s->ports_map[i]);
+ }
+
+ /* Ports */
+
+ nr_active_ports = 0;
+ QTAILQ_FOREACH(port, &s->ports, next) {
+ nr_active_ports++;
+ }
+
+ qemu_put_be32s(f, &nr_active_ports);
+
+ /*
+ * Items in struct VirtIOSerialPort.
+ */
+ QTAILQ_FOREACH(port, &s->ports, next) {
+ uint32_t elem_popped;
+
+ qemu_put_be32s(f, &port->id);
+ qemu_put_byte(f, port->guest_connected);
+ qemu_put_byte(f, port->host_connected);
+
+ elem_popped = 0;
+ if (port->elem.out_num) {
+ elem_popped = 1;
+ }
+ qemu_put_be32s(f, &elem_popped);
+ if (elem_popped) {
+ qemu_put_be32s(f, &port->iov_idx);
+ qemu_put_be64s(f, &port->iov_offset);
+
+ qemu_put_buffer(f, (unsigned char *)&port->elem,
+ sizeof(port->elem));
+ }
+ }
+}
+
+static void virtio_serial_post_load_timer_cb(void *opaque)
+{
+ uint32_t i;
+ VirtIOSerial *s = VIRTIO_SERIAL(opaque);
+ VirtIOSerialPort *port;
+ uint8_t host_connected;
+ VirtIOSerialPortClass *vsc;
+
+ if (!s->post_load) {
+ return;
+ }
+ for (i = 0 ; i < s->post_load->nr_active_ports; ++i) {
+ port = s->post_load->connected[i].port;
+ host_connected = s->post_load->connected[i].host_connected;
+ if (host_connected != port->host_connected) {
+ /*
+ * We have to let the guest know of the host connection
+ * status change
+ */
+ send_control_event(s, port->id, VIRTIO_CONSOLE_PORT_OPEN,
+ port->host_connected);
+ }
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+ if (vsc->set_guest_connected) {
+ vsc->set_guest_connected(port, port->guest_connected);
+ }
+ }
+ g_free(s->post_load->connected);
+ timer_free(s->post_load->timer);
+ g_free(s->post_load);
+ s->post_load = NULL;
+}
+
+static int fetch_active_ports_list(QEMUFile *f, int version_id,
+ VirtIOSerial *s, uint32_t nr_active_ports)
+{
+ uint32_t i;
+
+ s->post_load = g_malloc0(sizeof(*s->post_load));
+ s->post_load->nr_active_ports = nr_active_ports;
+ s->post_load->connected =
+ g_malloc0(sizeof(*s->post_load->connected) * nr_active_ports);
+
+ s->post_load->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ virtio_serial_post_load_timer_cb,
+ s);
+
+ /* Items in struct VirtIOSerialPort */
+ for (i = 0; i < nr_active_ports; i++) {
+ VirtIOSerialPort *port;
+ uint32_t id;
+
+ id = qemu_get_be32(f);
+ port = find_port_by_id(s, id);
+ if (!port) {
+ return -EINVAL;
+ }
+
+ port->guest_connected = qemu_get_byte(f);
+ s->post_load->connected[i].port = port;
+ s->post_load->connected[i].host_connected = qemu_get_byte(f);
+
+ if (version_id > 2) {
+ uint32_t elem_popped;
+
+ qemu_get_be32s(f, &elem_popped);
+ if (elem_popped) {
+ qemu_get_be32s(f, &port->iov_idx);
+ qemu_get_be64s(f, &port->iov_offset);
+
+ qemu_get_buffer(f, (unsigned char *)&port->elem,
+ sizeof(port->elem));
+ virtqueue_map_sg(port->elem.in_sg, port->elem.in_addr,
+ port->elem.in_num, 1);
+ virtqueue_map_sg(port->elem.out_sg, port->elem.out_addr,
+ port->elem.out_num, 1);
+
+ /*
+ * Port was throttled on source machine. Let's
+ * unthrottle it here so data starts flowing again.
+ */
+ virtio_serial_throttle_port(port, false);
+ }
+ }
+ }
+ timer_mod(s->post_load->timer, 1);
+ return 0;
+}
+
+static int virtio_serial_load(QEMUFile *f, void *opaque, int version_id)
+{
+ if (version_id > 3) {
+ return -EINVAL;
+ }
+
+ /* The virtio device */
+ return virtio_load(VIRTIO_DEVICE(opaque), f, version_id);
+}
+
+static int virtio_serial_load_device(VirtIODevice *vdev, QEMUFile *f,
+ int version_id)
+{
+ VirtIOSerial *s = VIRTIO_SERIAL(vdev);
+ uint32_t max_nr_ports, nr_active_ports, ports_map;
+ unsigned int i;
+ int ret;
+ uint32_t tmp;
+
+ if (version_id < 2) {
+ return 0;
+ }
+
+ /* Unused */
+ qemu_get_be16s(f, (uint16_t *) &tmp);
+ qemu_get_be16s(f, (uint16_t *) &tmp);
+ qemu_get_be32s(f, &tmp);
+
+ max_nr_ports = s->serial.max_virtserial_ports;
+ for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
+ qemu_get_be32s(f, &ports_map);
+
+ if (ports_map != s->ports_map[i]) {
+ /*
+ * Ports active on source and destination don't
+ * match. Fail migration.
+ */
+ return -EINVAL;
+ }
+ }
+
+ qemu_get_be32s(f, &nr_active_ports);
+
+ if (nr_active_ports) {
+ ret = fetch_active_ports_list(f, version_id, s, nr_active_ports);
+ if (ret) {
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void virtser_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent);
+
+static Property virtser_props[] = {
+ DEFINE_PROP_UINT32("nr", VirtIOSerialPort, id, VIRTIO_CONSOLE_BAD_ID),
+ DEFINE_PROP_STRING("name", VirtIOSerialPort, name),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+#define TYPE_VIRTIO_SERIAL_BUS "virtio-serial-bus"
+#define VIRTIO_SERIAL_BUS(obj) \
+ OBJECT_CHECK(VirtIOSerialBus, (obj), TYPE_VIRTIO_SERIAL_BUS)
+
+static void virtser_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+ k->print_dev = virtser_bus_dev_print;
+}
+
+static const TypeInfo virtser_bus_info = {
+ .name = TYPE_VIRTIO_SERIAL_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(VirtIOSerialBus),
+ .class_init = virtser_bus_class_init,
+};
+
+static void virtser_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent)
+{
+ VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, qdev);
+
+ monitor_printf(mon, "%*sport %d, guest %s, host %s, throttle %s\n",
+ indent, "", port->id,
+ port->guest_connected ? "on" : "off",
+ port->host_connected ? "on" : "off",
+ port->throttled ? "on" : "off");
+}
+
+/* This function is only used if a port id is not provided by the user */
+static uint32_t find_free_port_id(VirtIOSerial *vser)
+{
+ unsigned int i, max_nr_ports;
+
+ max_nr_ports = vser->serial.max_virtserial_ports;
+ for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
+ uint32_t map, zeroes;
+
+ map = vser->ports_map[i];
+ zeroes = ctz32(~map);
+ if (zeroes != 32) {
+ return zeroes + i * 32;
+ }
+ }
+ return VIRTIO_CONSOLE_BAD_ID;
+}
+
+static void mark_port_added(VirtIOSerial *vser, uint32_t port_id)
+{
+ unsigned int i;
+
+ i = port_id / 32;
+ vser->ports_map[i] |= 1U << (port_id % 32);
+}
+
+static void add_port(VirtIOSerial *vser, uint32_t port_id)
+{
+ mark_port_added(vser, port_id);
+ send_control_event(vser, port_id, VIRTIO_CONSOLE_PORT_ADD, 1);
+}
+
+static void remove_port(VirtIOSerial *vser, uint32_t port_id)
+{
+ VirtIOSerialPort *port;
+
+ /*
+ * Don't mark port 0 removed -- we explicitly reserve it for
+ * backward compat with older guests, ensure a virtconsole device
+ * unplug retains the reservation.
+ */
+ if (port_id) {
+ unsigned int i;
+
+ i = port_id / 32;
+ vser->ports_map[i] &= ~(1U << (port_id % 32));
+ }
+
+ port = find_port_by_id(vser, port_id);
+ /*
+ * This function is only called from qdev's unplug callback; if we
+ * get a NULL port here, we're in trouble.
+ */
+ assert(port);
+
+ /* Flush out any unconsumed buffers first */
+ discard_vq_data(port->ovq, VIRTIO_DEVICE(port->vser));
+
+ send_control_event(vser, port->id, VIRTIO_CONSOLE_PORT_REMOVE, 1);
+}
+
+static void virtser_port_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev);
+ VirtIOSerialPortClass *vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+ VirtIOSerialBus *bus = VIRTIO_SERIAL_BUS(qdev_get_parent_bus(dev));
+ int max_nr_ports;
+ bool plugging_port0;
+ Error *err = NULL;
+
+ port->vser = bus->vser;
+ port->bh = qemu_bh_new(flush_queued_data_bh, port);
+
+ assert(vsc->have_data);
+
+ /*
+ * Is the first console port we're seeing? If so, put it up at
+ * location 0. This is done for backward compatibility (old
+ * kernel, new qemu).
+ */
+ plugging_port0 = vsc->is_console && !find_port_by_id(port->vser, 0);
+
+ if (find_port_by_id(port->vser, port->id)) {
+ error_setg(errp, "virtio-serial-bus: A port already exists at id %u",
+ port->id);
+ return;
+ }
+
+ if (port->name != NULL && find_port_by_name(port->name)) {
+ error_setg(errp, "virtio-serial-bus: A port already exists by name %s",
+ port->name);
+ return;
+ }
+
+ if (port->id == VIRTIO_CONSOLE_BAD_ID) {
+ if (plugging_port0) {
+ port->id = 0;
+ } else {
+ port->id = find_free_port_id(port->vser);
+ if (port->id == VIRTIO_CONSOLE_BAD_ID) {
+ error_setg(errp, "virtio-serial-bus: Maximum port limit for "
+ "this device reached");
+ return;
+ }
+ }
+ }
+
+ max_nr_ports = port->vser->serial.max_virtserial_ports;
+ if (port->id >= max_nr_ports) {
+ error_setg(errp, "virtio-serial-bus: Out-of-range port id specified, "
+ "max. allowed: %u", max_nr_ports - 1);
+ return;
+ }
+
+ vsc->realize(dev, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ port->elem.out_num = 0;
+}
+
+static void virtser_port_device_plug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev);
+
+ QTAILQ_INSERT_TAIL(&port->vser->ports, port, next);
+ port->ivq = port->vser->ivqs[port->id];
+ port->ovq = port->vser->ovqs[port->id];
+
+ add_port(port->vser, port->id);
+
+ /* Send an update to the guest about this new port added */
+ virtio_notify_config(VIRTIO_DEVICE(hotplug_dev));
+}
+
+static void virtser_port_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev);
+ VirtIOSerialPortClass *vsc = VIRTIO_SERIAL_PORT_GET_CLASS(dev);
+ VirtIOSerial *vser = port->vser;
+
+ qemu_bh_delete(port->bh);
+ remove_port(port->vser, port->id);
+
+ QTAILQ_REMOVE(&vser->ports, port, next);
+
+ if (vsc->unrealize) {
+ vsc->unrealize(dev, errp);
+ }
+}
+
+static void virtio_serial_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSerial *vser = VIRTIO_SERIAL(dev);
+ uint32_t i, max_supported_ports;
+
+ if (!vser->serial.max_virtserial_ports) {
+ error_setg(errp, "Maximum number of serial ports not specified");
+ return;
+ }
+
+ /* Each port takes 2 queues, and one pair is for the control queue */
+ max_supported_ports = VIRTIO_QUEUE_MAX / 2 - 1;
+
+ if (vser->serial.max_virtserial_ports > max_supported_ports) {
+ error_setg(errp, "maximum ports supported: %u", max_supported_ports);
+ return;
+ }
+
+ /* We don't support emergency write, skip it for now. */
+ /* TODO: cleaner fix, depending on host features. */
+ virtio_init(vdev, "virtio-serial", VIRTIO_ID_CONSOLE,
+ offsetof(struct virtio_console_config, emerg_wr));
+
+ /* Spawn a new virtio-serial bus on which the ports will ride as devices */
+ qbus_create_inplace(&vser->bus, sizeof(vser->bus), TYPE_VIRTIO_SERIAL_BUS,
+ dev, vdev->bus_name);
+ qbus_set_hotplug_handler(BUS(&vser->bus), DEVICE(vser), errp);
+ vser->bus.vser = vser;
+ QTAILQ_INIT(&vser->ports);
+
+ vser->bus.max_nr_ports = vser->serial.max_virtserial_ports;
+ vser->ivqs = g_malloc(vser->serial.max_virtserial_ports
+ * sizeof(VirtQueue *));
+ vser->ovqs = g_malloc(vser->serial.max_virtserial_ports
+ * sizeof(VirtQueue *));
+
+ /* Add a queue for host to guest transfers for port 0 (backward compat) */
+ vser->ivqs[0] = virtio_add_queue(vdev, 128, handle_input);
+ /* Add a queue for guest to host transfers for port 0 (backward compat) */
+ vser->ovqs[0] = virtio_add_queue(vdev, 128, handle_output);
+
+ /* TODO: host to guest notifications can get dropped
+ * if the queue fills up. Implement queueing in host,
+ * this might also make it possible to reduce the control
+ * queue size: as guest preposts buffers there,
+ * this will save 4Kbyte of guest memory per entry. */
+
+ /* control queue: host to guest */
+ vser->c_ivq = virtio_add_queue(vdev, 32, control_in);
+ /* control queue: guest to host */
+ vser->c_ovq = virtio_add_queue(vdev, 32, control_out);
+
+ for (i = 1; i < vser->bus.max_nr_ports; i++) {
+ /* Add a per-port queue for host to guest transfers */
+ vser->ivqs[i] = virtio_add_queue(vdev, 128, handle_input);
+ /* Add a per-per queue for guest to host transfers */
+ vser->ovqs[i] = virtio_add_queue(vdev, 128, handle_output);
+ }
+
+ vser->ports_map = g_malloc0(((vser->serial.max_virtserial_ports + 31) / 32)
+ * sizeof(vser->ports_map[0]));
+ /*
+ * Reserve location 0 for a console port for backward compat
+ * (old kernel, new qemu)
+ */
+ mark_port_added(vser, 0);
+
+ vser->post_load = NULL;
+
+ /*
+ * Register for the savevm section with the virtio-console name
+ * to preserve backward compat
+ */
+ register_savevm(dev, "virtio-console", -1, 3, virtio_serial_save,
+ virtio_serial_load, vser);
+
+ QLIST_INSERT_HEAD(&vserdevices.devices, vser, next);
+}
+
+static void virtio_serial_port_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_INPUT, k->categories);
+ k->bus_type = TYPE_VIRTIO_SERIAL_BUS;
+ k->realize = virtser_port_device_realize;
+ k->unrealize = virtser_port_device_unrealize;
+ k->props = virtser_props;
+}
+
+static const TypeInfo virtio_serial_port_type_info = {
+ .name = TYPE_VIRTIO_SERIAL_PORT,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(VirtIOSerialPort),
+ .abstract = true,
+ .class_size = sizeof(VirtIOSerialPortClass),
+ .class_init = virtio_serial_port_class_init,
+};
+
+static void virtio_serial_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSerial *vser = VIRTIO_SERIAL(dev);
+
+ QLIST_REMOVE(vser, next);
+
+ unregister_savevm(dev, "virtio-console", vser);
+
+ g_free(vser->ivqs);
+ g_free(vser->ovqs);
+ g_free(vser->ports_map);
+ if (vser->post_load) {
+ g_free(vser->post_load->connected);
+ timer_del(vser->post_load->timer);
+ timer_free(vser->post_load->timer);
+ g_free(vser->post_load);
+ }
+ virtio_cleanup(vdev);
+}
+
+static Property virtio_serial_properties[] = {
+ DEFINE_PROP_UINT32("max_ports", VirtIOSerial, serial.max_virtserial_ports,
+ 31),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_serial_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ QLIST_INIT(&vserdevices.devices);
+
+ dc->props = virtio_serial_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ vdc->realize = virtio_serial_device_realize;
+ vdc->unrealize = virtio_serial_device_unrealize;
+ vdc->get_features = get_features;
+ vdc->get_config = get_config;
+ vdc->set_status = set_status;
+ vdc->reset = vser_reset;
+ vdc->save = virtio_serial_save_device;
+ vdc->load = virtio_serial_load_device;
+ hc->plug = virtser_port_device_plug;
+ hc->unplug = qdev_simple_device_unplug_cb;
+}
+
+static const TypeInfo virtio_device_info = {
+ .name = TYPE_VIRTIO_SERIAL,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOSerial),
+ .class_init = virtio_serial_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static void virtio_serial_register_types(void)
+{
+ type_register_static(&virtser_bus_info);
+ type_register_static(&virtio_serial_port_type_info);
+ type_register_static(&virtio_device_info);
+}
+
+type_init(virtio_serial_register_types)
diff --git a/hw/char/xen_console.c b/hw/char/xen_console.c
new file mode 100644
index 00000000..eb7f450a
--- /dev/null
+++ b/hw/char/xen_console.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) International Business Machines Corp., 2005
+ * Author(s): Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * Copyright (C) Red Hat 2007
+ *
+ * Xen Console
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <sys/mman.h>
+
+#include "hw/hw.h"
+#include "sysemu/char.h"
+#include "hw/xen/xen_backend.h"
+
+#include <xen/io/console.h>
+
+struct buffer {
+ uint8_t *data;
+ size_t consumed;
+ size_t size;
+ size_t capacity;
+ size_t max_capacity;
+};
+
+struct XenConsole {
+ struct XenDevice xendev; /* must be first */
+ struct buffer buffer;
+ char console[XEN_BUFSIZE];
+ int ring_ref;
+ void *sring;
+ CharDriverState *chr;
+ int backlog;
+};
+
+static void buffer_append(struct XenConsole *con)
+{
+ struct buffer *buffer = &con->buffer;
+ XENCONS_RING_IDX cons, prod, size;
+ struct xencons_interface *intf = con->sring;
+
+ cons = intf->out_cons;
+ prod = intf->out_prod;
+ xen_mb();
+
+ size = prod - cons;
+ if ((size == 0) || (size > sizeof(intf->out)))
+ return;
+
+ if ((buffer->capacity - buffer->size) < size) {
+ buffer->capacity += (size + 1024);
+ buffer->data = g_realloc(buffer->data, buffer->capacity);
+ }
+
+ while (cons != prod)
+ buffer->data[buffer->size++] = intf->out[
+ MASK_XENCONS_IDX(cons++, intf->out)];
+
+ xen_mb();
+ intf->out_cons = cons;
+ xen_be_send_notify(&con->xendev);
+
+ if (buffer->max_capacity &&
+ buffer->size > buffer->max_capacity) {
+ /* Discard the middle of the data. */
+
+ size_t over = buffer->size - buffer->max_capacity;
+ uint8_t *maxpos = buffer->data + buffer->max_capacity;
+
+ memmove(maxpos - over, maxpos, over);
+ buffer->data = g_realloc(buffer->data, buffer->max_capacity);
+ buffer->size = buffer->capacity = buffer->max_capacity;
+
+ if (buffer->consumed > buffer->max_capacity - over)
+ buffer->consumed = buffer->max_capacity - over;
+ }
+}
+
+static void buffer_advance(struct buffer *buffer, size_t len)
+{
+ buffer->consumed += len;
+ if (buffer->consumed == buffer->size) {
+ buffer->consumed = 0;
+ buffer->size = 0;
+ }
+}
+
+static int ring_free_bytes(struct XenConsole *con)
+{
+ struct xencons_interface *intf = con->sring;
+ XENCONS_RING_IDX cons, prod, space;
+
+ cons = intf->in_cons;
+ prod = intf->in_prod;
+ xen_mb();
+
+ space = prod - cons;
+ if (space > sizeof(intf->in))
+ return 0; /* ring is screwed: ignore it */
+
+ return (sizeof(intf->in) - space);
+}
+
+static int xencons_can_receive(void *opaque)
+{
+ struct XenConsole *con = opaque;
+ return ring_free_bytes(con);
+}
+
+static void xencons_receive(void *opaque, const uint8_t *buf, int len)
+{
+ struct XenConsole *con = opaque;
+ struct xencons_interface *intf = con->sring;
+ XENCONS_RING_IDX prod;
+ int i, max;
+
+ max = ring_free_bytes(con);
+ /* The can_receive() func limits this, but check again anyway */
+ if (max < len)
+ len = max;
+
+ prod = intf->in_prod;
+ for (i = 0; i < len; i++) {
+ intf->in[MASK_XENCONS_IDX(prod++, intf->in)] =
+ buf[i];
+ }
+ xen_wmb();
+ intf->in_prod = prod;
+ xen_be_send_notify(&con->xendev);
+}
+
+static void xencons_send(struct XenConsole *con)
+{
+ ssize_t len, size;
+
+ size = con->buffer.size - con->buffer.consumed;
+ if (con->chr)
+ len = qemu_chr_fe_write(con->chr, con->buffer.data + con->buffer.consumed,
+ size);
+ else
+ len = size;
+ if (len < 1) {
+ if (!con->backlog) {
+ con->backlog = 1;
+ xen_be_printf(&con->xendev, 1, "backlog piling up, nobody listening?\n");
+ }
+ } else {
+ buffer_advance(&con->buffer, len);
+ if (con->backlog && len == size) {
+ con->backlog = 0;
+ xen_be_printf(&con->xendev, 1, "backlog is gone\n");
+ }
+ }
+}
+
+/* -------------------------------------------------------------------- */
+
+static int con_init(struct XenDevice *xendev)
+{
+ struct XenConsole *con = container_of(xendev, struct XenConsole, xendev);
+ char *type, *dom, label[32];
+ int ret = 0;
+ const char *output;
+
+ /* setup */
+ dom = xs_get_domain_path(xenstore, con->xendev.dom);
+ if (!xendev->dev) {
+ snprintf(con->console, sizeof(con->console), "%s/console", dom);
+ } else {
+ snprintf(con->console, sizeof(con->console), "%s/device/console/%d", dom, xendev->dev);
+ }
+ free(dom);
+
+ type = xenstore_read_str(con->console, "type");
+ if (!type || strcmp(type, "ioemu") != 0) {
+ xen_be_printf(xendev, 1, "not for me (type=%s)\n", type);
+ ret = -1;
+ goto out;
+ }
+
+ output = xenstore_read_str(con->console, "output");
+
+ /* no Xen override, use qemu output device */
+ if (output == NULL) {
+ con->chr = serial_hds[con->xendev.dev];
+ } else {
+ snprintf(label, sizeof(label), "xencons%d", con->xendev.dev);
+ con->chr = qemu_chr_new(label, output, NULL);
+ }
+
+ xenstore_store_pv_console_info(con->xendev.dev, con->chr);
+
+out:
+ g_free(type);
+ return ret;
+}
+
+static int con_initialise(struct XenDevice *xendev)
+{
+ struct XenConsole *con = container_of(xendev, struct XenConsole, xendev);
+ int limit;
+
+ if (xenstore_read_int(con->console, "ring-ref", &con->ring_ref) == -1)
+ return -1;
+ if (xenstore_read_int(con->console, "port", &con->xendev.remote_port) == -1)
+ return -1;
+ if (xenstore_read_int(con->console, "limit", &limit) == 0)
+ con->buffer.max_capacity = limit;
+
+ if (!xendev->dev) {
+ con->sring = xc_map_foreign_range(xen_xc, con->xendev.dom,
+ XC_PAGE_SIZE,
+ PROT_READ|PROT_WRITE,
+ con->ring_ref);
+ } else {
+ con->sring = xc_gnttab_map_grant_ref(xendev->gnttabdev, con->xendev.dom,
+ con->ring_ref,
+ PROT_READ|PROT_WRITE);
+ }
+ if (!con->sring)
+ return -1;
+
+ xen_be_bind_evtchn(&con->xendev);
+ if (con->chr) {
+ if (qemu_chr_fe_claim(con->chr) == 0) {
+ qemu_chr_add_handlers(con->chr, xencons_can_receive,
+ xencons_receive, NULL, con);
+ } else {
+ xen_be_printf(xendev, 0,
+ "xen_console_init error chardev %s already used\n",
+ con->chr->label);
+ con->chr = NULL;
+ }
+ }
+
+ xen_be_printf(xendev, 1, "ring mfn %d, remote port %d, local port %d, limit %zd\n",
+ con->ring_ref,
+ con->xendev.remote_port,
+ con->xendev.local_port,
+ con->buffer.max_capacity);
+ return 0;
+}
+
+static void con_disconnect(struct XenDevice *xendev)
+{
+ struct XenConsole *con = container_of(xendev, struct XenConsole, xendev);
+
+ if (!xendev->dev) {
+ return;
+ }
+ if (con->chr) {
+ qemu_chr_add_handlers(con->chr, NULL, NULL, NULL, NULL);
+ qemu_chr_fe_release(con->chr);
+ }
+ xen_be_unbind_evtchn(&con->xendev);
+
+ if (con->sring) {
+ if (!xendev->gnttabdev) {
+ munmap(con->sring, XC_PAGE_SIZE);
+ } else {
+ xc_gnttab_munmap(xendev->gnttabdev, con->sring, 1);
+ }
+ con->sring = NULL;
+ }
+}
+
+static void con_event(struct XenDevice *xendev)
+{
+ struct XenConsole *con = container_of(xendev, struct XenConsole, xendev);
+
+ buffer_append(con);
+ if (con->buffer.size - con->buffer.consumed)
+ xencons_send(con);
+}
+
+/* -------------------------------------------------------------------- */
+
+struct XenDevOps xen_console_ops = {
+ .size = sizeof(struct XenConsole),
+ .flags = DEVOPS_FLAG_IGNORE_STATE|DEVOPS_FLAG_NEED_GNTDEV,
+ .init = con_init,
+ .initialise = con_initialise,
+ .event = con_event,
+ .disconnect = con_disconnect,
+};
diff --git a/hw/char/xilinx_uartlite.c b/hw/char/xilinx_uartlite.c
new file mode 100644
index 00000000..ef883a89
--- /dev/null
+++ b/hw/char/xilinx_uartlite.c
@@ -0,0 +1,248 @@
+/*
+ * QEMU model of Xilinx uartlite.
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+
+#define DUART(x)
+
+#define R_RX 0
+#define R_TX 1
+#define R_STATUS 2
+#define R_CTRL 3
+#define R_MAX 4
+
+#define STATUS_RXVALID 0x01
+#define STATUS_RXFULL 0x02
+#define STATUS_TXEMPTY 0x04
+#define STATUS_TXFULL 0x08
+#define STATUS_IE 0x10
+#define STATUS_OVERRUN 0x20
+#define STATUS_FRAME 0x40
+#define STATUS_PARITY 0x80
+
+#define CONTROL_RST_TX 0x01
+#define CONTROL_RST_RX 0x02
+#define CONTROL_IE 0x10
+
+#define TYPE_XILINX_UARTLITE "xlnx.xps-uartlite"
+#define XILINX_UARTLITE(obj) \
+ OBJECT_CHECK(XilinxUARTLite, (obj), TYPE_XILINX_UARTLITE)
+
+typedef struct XilinxUARTLite {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint8_t rx_fifo[8];
+ unsigned int rx_fifo_pos;
+ unsigned int rx_fifo_len;
+
+ uint32_t regs[R_MAX];
+} XilinxUARTLite;
+
+static void uart_update_irq(XilinxUARTLite *s)
+{
+ unsigned int irq;
+
+ if (s->rx_fifo_len)
+ s->regs[R_STATUS] |= STATUS_IE;
+
+ irq = (s->regs[R_STATUS] & STATUS_IE) && (s->regs[R_CTRL] & CONTROL_IE);
+ qemu_set_irq(s->irq, irq);
+}
+
+static void uart_update_status(XilinxUARTLite *s)
+{
+ uint32_t r;
+
+ r = s->regs[R_STATUS];
+ r &= ~7;
+ r |= 1 << 2; /* Tx fifo is always empty. We are fast :) */
+ r |= (s->rx_fifo_len == sizeof (s->rx_fifo)) << 1;
+ r |= (!!s->rx_fifo_len);
+ s->regs[R_STATUS] = r;
+}
+
+static void xilinx_uartlite_reset(DeviceState *dev)
+{
+ uart_update_status(XILINX_UARTLITE(dev));
+}
+
+static uint64_t
+uart_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ XilinxUARTLite *s = opaque;
+ uint32_t r = 0;
+ addr >>= 2;
+ switch (addr)
+ {
+ case R_RX:
+ r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 7];
+ if (s->rx_fifo_len)
+ s->rx_fifo_len--;
+ uart_update_status(s);
+ uart_update_irq(s);
+ qemu_chr_accept_input(s->chr);
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(s->regs))
+ r = s->regs[addr];
+ DUART(qemu_log("%s addr=%x v=%x\n", __func__, addr, r));
+ break;
+ }
+ return r;
+}
+
+static void
+uart_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ XilinxUARTLite *s = opaque;
+ uint32_t value = val64;
+ unsigned char ch = value;
+
+ addr >>= 2;
+ switch (addr)
+ {
+ case R_STATUS:
+ hw_error("write to UART STATUS?\n");
+ break;
+
+ case R_CTRL:
+ if (value & CONTROL_RST_RX) {
+ s->rx_fifo_pos = 0;
+ s->rx_fifo_len = 0;
+ }
+ s->regs[addr] = value;
+ break;
+
+ case R_TX:
+ if (s->chr)
+ qemu_chr_fe_write(s->chr, &ch, 1);
+
+ s->regs[addr] = value;
+
+ /* hax. */
+ s->regs[R_STATUS] |= STATUS_IE;
+ break;
+
+ default:
+ DUART(printf("%s addr=%x v=%x\n", __func__, addr, value));
+ if (addr < ARRAY_SIZE(s->regs))
+ s->regs[addr] = value;
+ break;
+ }
+ uart_update_status(s);
+ uart_update_irq(s);
+}
+
+static const MemoryRegionOps uart_ops = {
+ .read = uart_read,
+ .write = uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4
+ }
+};
+
+static void uart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ XilinxUARTLite *s = opaque;
+
+ /* Got a byte. */
+ if (s->rx_fifo_len >= 8) {
+ printf("WARNING: UART dropped char.\n");
+ return;
+ }
+ s->rx_fifo[s->rx_fifo_pos] = *buf;
+ s->rx_fifo_pos++;
+ s->rx_fifo_pos &= 0x7;
+ s->rx_fifo_len++;
+
+ uart_update_status(s);
+ uart_update_irq(s);
+}
+
+static int uart_can_rx(void *opaque)
+{
+ XilinxUARTLite *s = opaque;
+
+ return s->rx_fifo_len < sizeof(s->rx_fifo);
+}
+
+static void uart_event(void *opaque, int event)
+{
+
+}
+
+static void xilinx_uartlite_realize(DeviceState *dev, Error **errp)
+{
+ XilinxUARTLite *s = XILINX_UARTLITE(dev);
+
+ /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr)
+ qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s);
+}
+
+static void xilinx_uartlite_init(Object *obj)
+{
+ XilinxUARTLite *s = XILINX_UARTLITE(obj);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+ memory_region_init_io(&s->mmio, obj, &uart_ops, s,
+ "xlnx.xps-uartlite", R_MAX * 4);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static void xilinx_uartlite_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = xilinx_uartlite_reset;
+ dc->realize = xilinx_uartlite_realize;
+ /* Reason: realize() method uses qemu_char_get_next_serial() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo xilinx_uartlite_info = {
+ .name = TYPE_XILINX_UARTLITE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XilinxUARTLite),
+ .instance_init = xilinx_uartlite_init,
+ .class_init = xilinx_uartlite_class_init,
+};
+
+static void xilinx_uart_register_types(void)
+{
+ type_register_static(&xilinx_uartlite_info);
+}
+
+type_init(xilinx_uart_register_types)
diff --git a/hw/core/Makefile.objs b/hw/core/Makefile.objs
new file mode 100644
index 00000000..abb3560b
--- /dev/null
+++ b/hw/core/Makefile.objs
@@ -0,0 +1,17 @@
+# core qdev-related obj files, also used by *-user:
+common-obj-y += qdev.o qdev-properties.o
+common-obj-y += fw-path-provider.o
+# irq.o needed for qdev GPIO handling:
+common-obj-y += irq.o
+common-obj-y += hotplug.o
+common-obj-y += nmi.o
+
+common-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o
+common-obj-$(CONFIG_XILINX_AXI) += stream.o
+common-obj-$(CONFIG_PTIMER) += ptimer.o
+common-obj-$(CONFIG_SOFTMMU) += sysbus.o
+common-obj-$(CONFIG_SOFTMMU) += machine.o
+common-obj-$(CONFIG_SOFTMMU) += null-machine.o
+common-obj-$(CONFIG_SOFTMMU) += loader.o
+common-obj-$(CONFIG_SOFTMMU) += qdev-properties-system.o
+common-obj-$(CONFIG_PLATFORM_BUS) += platform-bus.o
diff --git a/hw/core/empty_slot.c b/hw/core/empty_slot.c
new file mode 100644
index 00000000..612b1093
--- /dev/null
+++ b/hw/core/empty_slot.c
@@ -0,0 +1,102 @@
+/*
+ * QEMU Empty Slot
+ *
+ * The empty_slot device emulates known to a bus but not connected devices.
+ *
+ * Copyright (c) 2010 Artyom Tarasenko
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any later
+ * version.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/empty_slot.h"
+
+//#define DEBUG_EMPTY_SLOT
+
+#ifdef DEBUG_EMPTY_SLOT
+#define DPRINTF(fmt, ...) \
+ do { printf("empty_slot: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define TYPE_EMPTY_SLOT "empty_slot"
+#define EMPTY_SLOT(obj) OBJECT_CHECK(EmptySlot, (obj), TYPE_EMPTY_SLOT)
+
+typedef struct EmptySlot {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint64_t size;
+} EmptySlot;
+
+static uint64_t empty_slot_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ DPRINTF("read from " TARGET_FMT_plx "\n", addr);
+ return 0;
+}
+
+static void empty_slot_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ DPRINTF("write 0x%x to " TARGET_FMT_plx "\n", (unsigned)val, addr);
+}
+
+static const MemoryRegionOps empty_slot_ops = {
+ .read = empty_slot_read,
+ .write = empty_slot_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void empty_slot_init(hwaddr addr, uint64_t slot_size)
+{
+ if (slot_size > 0) {
+ /* Only empty slots larger than 0 byte need handling. */
+ DeviceState *dev;
+ SysBusDevice *s;
+ EmptySlot *e;
+
+ dev = qdev_create(NULL, TYPE_EMPTY_SLOT);
+ s = SYS_BUS_DEVICE(dev);
+ e = EMPTY_SLOT(dev);
+ e->size = slot_size;
+
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(s, 0, addr);
+ }
+}
+
+static int empty_slot_init1(SysBusDevice *dev)
+{
+ EmptySlot *s = EMPTY_SLOT(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &empty_slot_ops, s,
+ "empty-slot", s->size);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static void empty_slot_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = empty_slot_init1;
+}
+
+static const TypeInfo empty_slot_info = {
+ .name = TYPE_EMPTY_SLOT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(EmptySlot),
+ .class_init = empty_slot_class_init,
+};
+
+static void empty_slot_register_types(void)
+{
+ type_register_static(&empty_slot_info);
+}
+
+type_init(empty_slot_register_types)
diff --git a/hw/core/fw-path-provider.c b/hw/core/fw-path-provider.c
new file mode 100644
index 00000000..7442d322
--- /dev/null
+++ b/hw/core/fw-path-provider.c
@@ -0,0 +1,52 @@
+/*
+ * Firmware path provider class and helpers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/fw-path-provider.h"
+
+char *fw_path_provider_get_dev_path(FWPathProvider *p, BusState *bus,
+ DeviceState *dev)
+{
+ FWPathProviderClass *k = FW_PATH_PROVIDER_GET_CLASS(p);
+
+ return k->get_dev_path(p, bus, dev);
+}
+
+char *fw_path_provider_try_get_dev_path(Object *o, BusState *bus,
+ DeviceState *dev)
+{
+ FWPathProvider *p = (FWPathProvider *)
+ object_dynamic_cast(o, TYPE_FW_PATH_PROVIDER);
+
+ if (p) {
+ return fw_path_provider_get_dev_path(p, bus, dev);
+ }
+
+ return NULL;
+}
+
+static const TypeInfo fw_path_provider_info = {
+ .name = TYPE_FW_PATH_PROVIDER,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(FWPathProviderClass),
+};
+
+static void fw_path_provider_register_types(void)
+{
+ type_register_static(&fw_path_provider_info);
+}
+
+type_init(fw_path_provider_register_types)
diff --git a/hw/core/hotplug.c b/hw/core/hotplug.c
new file mode 100644
index 00000000..4e010745
--- /dev/null
+++ b/hw/core/hotplug.c
@@ -0,0 +1,59 @@
+/*
+ * Hotplug handler interface.
+ *
+ * Copyright (c) 2014 Red Hat Inc.
+ *
+ * Authors:
+ * Igor Mammedov <imammedo@redhat.com>,
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/hotplug.h"
+#include "qemu/module.h"
+
+void hotplug_handler_plug(HotplugHandler *plug_handler,
+ DeviceState *plugged_dev,
+ Error **errp)
+{
+ HotplugHandlerClass *hdc = HOTPLUG_HANDLER_GET_CLASS(plug_handler);
+
+ if (hdc->plug) {
+ hdc->plug(plug_handler, plugged_dev, errp);
+ }
+}
+
+void hotplug_handler_unplug_request(HotplugHandler *plug_handler,
+ DeviceState *plugged_dev,
+ Error **errp)
+{
+ HotplugHandlerClass *hdc = HOTPLUG_HANDLER_GET_CLASS(plug_handler);
+
+ if (hdc->unplug_request) {
+ hdc->unplug_request(plug_handler, plugged_dev, errp);
+ }
+}
+
+void hotplug_handler_unplug(HotplugHandler *plug_handler,
+ DeviceState *plugged_dev,
+ Error **errp)
+{
+ HotplugHandlerClass *hdc = HOTPLUG_HANDLER_GET_CLASS(plug_handler);
+
+ if (hdc->unplug) {
+ hdc->unplug(plug_handler, plugged_dev, errp);
+ }
+}
+
+static const TypeInfo hotplug_handler_info = {
+ .name = TYPE_HOTPLUG_HANDLER,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(HotplugHandlerClass),
+};
+
+static void hotplug_handler_register_types(void)
+{
+ type_register_static(&hotplug_handler_info);
+}
+
+type_init(hotplug_handler_register_types)
diff --git a/hw/core/irq.c b/hw/core/irq.c
new file mode 100644
index 00000000..8a62a36d
--- /dev/null
+++ b/hw/core/irq.c
@@ -0,0 +1,158 @@
+/*
+ * QEMU IRQ/GPIO common code.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * 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.
+ */
+#include "qemu-common.h"
+#include "hw/irq.h"
+#include "qom/object.h"
+
+#define IRQ(obj) OBJECT_CHECK(struct IRQState, (obj), TYPE_IRQ)
+
+struct IRQState {
+ Object parent_obj;
+
+ qemu_irq_handler handler;
+ void *opaque;
+ int n;
+};
+
+void qemu_set_irq(qemu_irq irq, int level)
+{
+ if (!irq)
+ return;
+
+ irq->handler(irq->opaque, irq->n, level);
+}
+
+qemu_irq *qemu_extend_irqs(qemu_irq *old, int n_old, qemu_irq_handler handler,
+ void *opaque, int n)
+{
+ qemu_irq *s;
+ int i;
+
+ if (!old) {
+ n_old = 0;
+ }
+ s = old ? g_renew(qemu_irq, old, n + n_old) : g_new(qemu_irq, n);
+ for (i = n_old; i < n + n_old; i++) {
+ s[i] = qemu_allocate_irq(handler, opaque, i);
+ }
+ return s;
+}
+
+qemu_irq *qemu_allocate_irqs(qemu_irq_handler handler, void *opaque, int n)
+{
+ return qemu_extend_irqs(NULL, 0, handler, opaque, n);
+}
+
+qemu_irq qemu_allocate_irq(qemu_irq_handler handler, void *opaque, int n)
+{
+ struct IRQState *irq;
+
+ irq = IRQ(object_new(TYPE_IRQ));
+ irq->handler = handler;
+ irq->opaque = opaque;
+ irq->n = n;
+
+ return irq;
+}
+
+void qemu_free_irqs(qemu_irq *s, int n)
+{
+ int i;
+ for (i = 0; i < n; i++) {
+ qemu_free_irq(s[i]);
+ }
+ g_free(s);
+}
+
+void qemu_free_irq(qemu_irq irq)
+{
+ object_unref(OBJECT(irq));
+}
+
+static void qemu_notirq(void *opaque, int line, int level)
+{
+ struct IRQState *irq = opaque;
+
+ irq->handler(irq->opaque, irq->n, !level);
+}
+
+qemu_irq qemu_irq_invert(qemu_irq irq)
+{
+ /* The default state for IRQs is low, so raise the output now. */
+ qemu_irq_raise(irq);
+ return qemu_allocate_irq(qemu_notirq, irq, 0);
+}
+
+static void qemu_splitirq(void *opaque, int line, int level)
+{
+ struct IRQState **irq = opaque;
+ irq[0]->handler(irq[0]->opaque, irq[0]->n, level);
+ irq[1]->handler(irq[1]->opaque, irq[1]->n, level);
+}
+
+qemu_irq qemu_irq_split(qemu_irq irq1, qemu_irq irq2)
+{
+ qemu_irq *s = g_malloc0(2 * sizeof(qemu_irq));
+ s[0] = irq1;
+ s[1] = irq2;
+ return qemu_allocate_irq(qemu_splitirq, s, 0);
+}
+
+static void proxy_irq_handler(void *opaque, int n, int level)
+{
+ qemu_irq **target = opaque;
+
+ if (*target) {
+ qemu_set_irq((*target)[n], level);
+ }
+}
+
+qemu_irq *qemu_irq_proxy(qemu_irq **target, int n)
+{
+ return qemu_allocate_irqs(proxy_irq_handler, target, n);
+}
+
+void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n)
+{
+ int i;
+ qemu_irq *old_irqs = qemu_allocate_irqs(NULL, NULL, n);
+ for (i = 0; i < n; i++) {
+ *old_irqs[i] = *gpio_in[i];
+ gpio_in[i]->handler = handler;
+ gpio_in[i]->opaque = &old_irqs[i];
+ }
+}
+
+static const TypeInfo irq_type_info = {
+ .name = TYPE_IRQ,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(struct IRQState),
+};
+
+static void irq_register_types(void)
+{
+ type_register_static(&irq_type_info);
+}
+
+type_init(irq_register_types)
diff --git a/hw/core/loader.c b/hw/core/loader.c
new file mode 100644
index 00000000..216eeeb9
--- /dev/null
+++ b/hw/core/loader.c
@@ -0,0 +1,1089 @@
+/*
+ * QEMU Executable loader
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * 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.
+ *
+ * Gunzip functionality in this file is derived from u-boot:
+ *
+ * (C) Copyright 2008 Semihalf
+ *
+ * (C) Copyright 2000-2005
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "disas/disas.h"
+#include "monitor/monitor.h"
+#include "sysemu/sysemu.h"
+#include "uboot_image.h"
+#include "hw/loader.h"
+#include "hw/nvram/fw_cfg.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+
+#include <zlib.h>
+
+bool option_rom_has_mr = false;
+bool rom_file_has_mr = true;
+
+static int roms_loaded;
+
+/* return the size or -1 if error */
+int get_image_size(const char *filename)
+{
+ int fd, size;
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return -1;
+ size = lseek(fd, 0, SEEK_END);
+ close(fd);
+ return size;
+}
+
+/* return the size or -1 if error */
+/* deprecated, because caller does not specify buffer size! */
+int load_image(const char *filename, uint8_t *addr)
+{
+ int fd, size;
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return -1;
+ size = lseek(fd, 0, SEEK_END);
+ if (size == -1) {
+ fprintf(stderr, "file %-20s: get size error: %s\n",
+ filename, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ lseek(fd, 0, SEEK_SET);
+ if (read(fd, addr, size) != size) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return size;
+}
+
+/* return the size or -1 if error */
+ssize_t load_image_size(const char *filename, void *addr, size_t size)
+{
+ int fd;
+ ssize_t actsize;
+
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if (fd < 0) {
+ return -1;
+ }
+
+ actsize = read(fd, addr, size);
+ if (actsize < 0) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ return actsize;
+}
+
+/* read()-like version */
+ssize_t read_targphys(const char *name,
+ int fd, hwaddr dst_addr, size_t nbytes)
+{
+ uint8_t *buf;
+ ssize_t did;
+
+ buf = g_malloc(nbytes);
+ did = read(fd, buf, nbytes);
+ if (did > 0)
+ rom_add_blob_fixed("read", buf, did, dst_addr);
+ g_free(buf);
+ return did;
+}
+
+/* return the size or -1 if error */
+int load_image_targphys(const char *filename,
+ hwaddr addr, uint64_t max_sz)
+{
+ int size;
+
+ size = get_image_size(filename);
+ if (size > max_sz) {
+ return -1;
+ }
+ if (size > 0) {
+ rom_add_file_fixed(filename, addr, -1);
+ }
+ return size;
+}
+
+void pstrcpy_targphys(const char *name, hwaddr dest, int buf_size,
+ const char *source)
+{
+ const char *nulp;
+ char *ptr;
+
+ if (buf_size <= 0) return;
+ nulp = memchr(source, 0, buf_size);
+ if (nulp) {
+ rom_add_blob_fixed(name, source, (nulp - source) + 1, dest);
+ } else {
+ rom_add_blob_fixed(name, source, buf_size, dest);
+ ptr = rom_ptr(dest + buf_size - 1);
+ *ptr = 0;
+ }
+}
+
+/* A.OUT loader */
+
+struct exec
+{
+ uint32_t a_info; /* Use macros N_MAGIC, etc for access */
+ uint32_t a_text; /* length of text, in bytes */
+ uint32_t a_data; /* length of data, in bytes */
+ uint32_t a_bss; /* length of uninitialized data area, in bytes */
+ uint32_t a_syms; /* length of symbol table data in file, in bytes */
+ uint32_t a_entry; /* start address */
+ uint32_t a_trsize; /* length of relocation info for text, in bytes */
+ uint32_t a_drsize; /* length of relocation info for data, in bytes */
+};
+
+static void bswap_ahdr(struct exec *e)
+{
+ bswap32s(&e->a_info);
+ bswap32s(&e->a_text);
+ bswap32s(&e->a_data);
+ bswap32s(&e->a_bss);
+ bswap32s(&e->a_syms);
+ bswap32s(&e->a_entry);
+ bswap32s(&e->a_trsize);
+ bswap32s(&e->a_drsize);
+}
+
+#define N_MAGIC(exec) ((exec).a_info & 0xffff)
+#define OMAGIC 0407
+#define NMAGIC 0410
+#define ZMAGIC 0413
+#define QMAGIC 0314
+#define _N_HDROFF(x) (1024 - sizeof (struct exec))
+#define N_TXTOFF(x) \
+ (N_MAGIC(x) == ZMAGIC ? _N_HDROFF((x)) + sizeof (struct exec) : \
+ (N_MAGIC(x) == QMAGIC ? 0 : sizeof (struct exec)))
+#define N_TXTADDR(x, target_page_size) (N_MAGIC(x) == QMAGIC ? target_page_size : 0)
+#define _N_SEGMENT_ROUND(x, target_page_size) (((x) + target_page_size - 1) & ~(target_page_size - 1))
+
+#define _N_TXTENDADDR(x, target_page_size) (N_TXTADDR(x, target_page_size)+(x).a_text)
+
+#define N_DATADDR(x, target_page_size) \
+ (N_MAGIC(x)==OMAGIC? (_N_TXTENDADDR(x, target_page_size)) \
+ : (_N_SEGMENT_ROUND (_N_TXTENDADDR(x, target_page_size), target_page_size)))
+
+
+int load_aout(const char *filename, hwaddr addr, int max_sz,
+ int bswap_needed, hwaddr target_page_size)
+{
+ int fd;
+ ssize_t size, ret;
+ struct exec e;
+ uint32_t magic;
+
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return -1;
+
+ size = read(fd, &e, sizeof(e));
+ if (size < 0)
+ goto fail;
+
+ if (bswap_needed) {
+ bswap_ahdr(&e);
+ }
+
+ magic = N_MAGIC(e);
+ switch (magic) {
+ case ZMAGIC:
+ case QMAGIC:
+ case OMAGIC:
+ if (e.a_text + e.a_data > max_sz)
+ goto fail;
+ lseek(fd, N_TXTOFF(e), SEEK_SET);
+ size = read_targphys(filename, fd, addr, e.a_text + e.a_data);
+ if (size < 0)
+ goto fail;
+ break;
+ case NMAGIC:
+ if (N_DATADDR(e, target_page_size) + e.a_data > max_sz)
+ goto fail;
+ lseek(fd, N_TXTOFF(e), SEEK_SET);
+ size = read_targphys(filename, fd, addr, e.a_text);
+ if (size < 0)
+ goto fail;
+ ret = read_targphys(filename, fd, addr + N_DATADDR(e, target_page_size),
+ e.a_data);
+ if (ret < 0)
+ goto fail;
+ size += ret;
+ break;
+ default:
+ goto fail;
+ }
+ close(fd);
+ return size;
+ fail:
+ close(fd);
+ return -1;
+}
+
+/* ELF loader */
+
+static void *load_at(int fd, off_t offset, size_t size)
+{
+ void *ptr;
+ if (lseek(fd, offset, SEEK_SET) < 0)
+ return NULL;
+ ptr = g_malloc(size);
+ if (read(fd, ptr, size) != size) {
+ g_free(ptr);
+ return NULL;
+ }
+ return ptr;
+}
+
+#ifdef ELF_CLASS
+#undef ELF_CLASS
+#endif
+
+#define ELF_CLASS ELFCLASS32
+#include "elf.h"
+
+#define SZ 32
+#define elf_word uint32_t
+#define elf_sword int32_t
+#define bswapSZs bswap32s
+#include "hw/elf_ops.h"
+
+#undef elfhdr
+#undef elf_phdr
+#undef elf_shdr
+#undef elf_sym
+#undef elf_rela
+#undef elf_note
+#undef elf_word
+#undef elf_sword
+#undef bswapSZs
+#undef SZ
+#define elfhdr elf64_hdr
+#define elf_phdr elf64_phdr
+#define elf_note elf64_note
+#define elf_shdr elf64_shdr
+#define elf_sym elf64_sym
+#define elf_rela elf64_rela
+#define elf_word uint64_t
+#define elf_sword int64_t
+#define bswapSZs bswap64s
+#define SZ 64
+#include "hw/elf_ops.h"
+
+const char *load_elf_strerror(int error)
+{
+ switch (error) {
+ case 0:
+ return "No error";
+ case ELF_LOAD_FAILED:
+ return "Failed to load ELF";
+ case ELF_LOAD_NOT_ELF:
+ return "The image is not ELF";
+ case ELF_LOAD_WRONG_ARCH:
+ return "The image is from incompatible architecture";
+ case ELF_LOAD_WRONG_ENDIAN:
+ return "The image has incorrect endianness";
+ default:
+ return "Unknown error";
+ }
+}
+
+/* return < 0 if error, otherwise the number of bytes loaded in memory */
+int load_elf(const char *filename, uint64_t (*translate_fn)(void *, uint64_t),
+ void *translate_opaque, uint64_t *pentry, uint64_t *lowaddr,
+ uint64_t *highaddr, int big_endian, int elf_machine, int clear_lsb)
+{
+ int fd, data_order, target_data_order, must_swab, ret = ELF_LOAD_FAILED;
+ uint8_t e_ident[EI_NIDENT];
+
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if (fd < 0) {
+ perror(filename);
+ return -1;
+ }
+ if (read(fd, e_ident, sizeof(e_ident)) != sizeof(e_ident))
+ goto fail;
+ if (e_ident[0] != ELFMAG0 ||
+ e_ident[1] != ELFMAG1 ||
+ e_ident[2] != ELFMAG2 ||
+ e_ident[3] != ELFMAG3) {
+ ret = ELF_LOAD_NOT_ELF;
+ goto fail;
+ }
+#ifdef HOST_WORDS_BIGENDIAN
+ data_order = ELFDATA2MSB;
+#else
+ data_order = ELFDATA2LSB;
+#endif
+ must_swab = data_order != e_ident[EI_DATA];
+ if (big_endian) {
+ target_data_order = ELFDATA2MSB;
+ } else {
+ target_data_order = ELFDATA2LSB;
+ }
+
+ if (target_data_order != e_ident[EI_DATA]) {
+ ret = ELF_LOAD_WRONG_ENDIAN;
+ goto fail;
+ }
+
+ lseek(fd, 0, SEEK_SET);
+ if (e_ident[EI_CLASS] == ELFCLASS64) {
+ ret = load_elf64(filename, fd, translate_fn, translate_opaque, must_swab,
+ pentry, lowaddr, highaddr, elf_machine, clear_lsb);
+ } else {
+ ret = load_elf32(filename, fd, translate_fn, translate_opaque, must_swab,
+ pentry, lowaddr, highaddr, elf_machine, clear_lsb);
+ }
+
+ fail:
+ close(fd);
+ return ret;
+}
+
+static void bswap_uboot_header(uboot_image_header_t *hdr)
+{
+#ifndef HOST_WORDS_BIGENDIAN
+ bswap32s(&hdr->ih_magic);
+ bswap32s(&hdr->ih_hcrc);
+ bswap32s(&hdr->ih_time);
+ bswap32s(&hdr->ih_size);
+ bswap32s(&hdr->ih_load);
+ bswap32s(&hdr->ih_ep);
+ bswap32s(&hdr->ih_dcrc);
+#endif
+}
+
+
+#define ZALLOC_ALIGNMENT 16
+
+static void *zalloc(void *x, unsigned items, unsigned size)
+{
+ void *p;
+
+ size *= items;
+ size = (size + ZALLOC_ALIGNMENT - 1) & ~(ZALLOC_ALIGNMENT - 1);
+
+ p = g_malloc(size);
+
+ return (p);
+}
+
+static void zfree(void *x, void *addr)
+{
+ g_free(addr);
+}
+
+
+#define HEAD_CRC 2
+#define EXTRA_FIELD 4
+#define ORIG_NAME 8
+#define COMMENT 0x10
+#define RESERVED 0xe0
+
+#define DEFLATED 8
+
+/* This is the usual maximum in uboot, so if a uImage overflows this, it would
+ * overflow on real hardware too. */
+#define UBOOT_MAX_GUNZIP_BYTES (64 << 20)
+
+static ssize_t gunzip(void *dst, size_t dstlen, uint8_t *src,
+ size_t srclen)
+{
+ z_stream s;
+ ssize_t dstbytes;
+ int r, i, flags;
+
+ /* skip header */
+ i = 10;
+ flags = src[3];
+ if (src[2] != DEFLATED || (flags & RESERVED) != 0) {
+ puts ("Error: Bad gzipped data\n");
+ return -1;
+ }
+ if ((flags & EXTRA_FIELD) != 0)
+ i = 12 + src[10] + (src[11] << 8);
+ if ((flags & ORIG_NAME) != 0)
+ while (src[i++] != 0)
+ ;
+ if ((flags & COMMENT) != 0)
+ while (src[i++] != 0)
+ ;
+ if ((flags & HEAD_CRC) != 0)
+ i += 2;
+ if (i >= srclen) {
+ puts ("Error: gunzip out of data in header\n");
+ return -1;
+ }
+
+ s.zalloc = zalloc;
+ s.zfree = zfree;
+
+ r = inflateInit2(&s, -MAX_WBITS);
+ if (r != Z_OK) {
+ printf ("Error: inflateInit2() returned %d\n", r);
+ return (-1);
+ }
+ s.next_in = src + i;
+ s.avail_in = srclen - i;
+ s.next_out = dst;
+ s.avail_out = dstlen;
+ r = inflate(&s, Z_FINISH);
+ if (r != Z_OK && r != Z_STREAM_END) {
+ printf ("Error: inflate() returned %d\n", r);
+ return -1;
+ }
+ dstbytes = s.next_out - (unsigned char *) dst;
+ inflateEnd(&s);
+
+ return dstbytes;
+}
+
+/* Load a U-Boot image. */
+static int load_uboot_image(const char *filename, hwaddr *ep, hwaddr *loadaddr,
+ int *is_linux, uint8_t image_type,
+ uint64_t (*translate_fn)(void *, uint64_t),
+ void *translate_opaque)
+{
+ int fd;
+ int size;
+ hwaddr address;
+ uboot_image_header_t h;
+ uboot_image_header_t *hdr = &h;
+ uint8_t *data = NULL;
+ int ret = -1;
+ int do_uncompress = 0;
+
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return -1;
+
+ size = read(fd, hdr, sizeof(uboot_image_header_t));
+ if (size < 0)
+ goto out;
+
+ bswap_uboot_header(hdr);
+
+ if (hdr->ih_magic != IH_MAGIC)
+ goto out;
+
+ if (hdr->ih_type != image_type) {
+ fprintf(stderr, "Wrong image type %d, expected %d\n", hdr->ih_type,
+ image_type);
+ goto out;
+ }
+
+ /* TODO: Implement other image types. */
+ switch (hdr->ih_type) {
+ case IH_TYPE_KERNEL:
+ address = hdr->ih_load;
+ if (translate_fn) {
+ address = translate_fn(translate_opaque, address);
+ }
+ if (loadaddr) {
+ *loadaddr = hdr->ih_load;
+ }
+
+ switch (hdr->ih_comp) {
+ case IH_COMP_NONE:
+ break;
+ case IH_COMP_GZIP:
+ do_uncompress = 1;
+ break;
+ default:
+ fprintf(stderr,
+ "Unable to load u-boot images with compression type %d\n",
+ hdr->ih_comp);
+ goto out;
+ }
+
+ if (ep) {
+ *ep = hdr->ih_ep;
+ }
+
+ /* TODO: Check CPU type. */
+ if (is_linux) {
+ if (hdr->ih_os == IH_OS_LINUX) {
+ *is_linux = 1;
+ } else {
+ *is_linux = 0;
+ }
+ }
+
+ break;
+ case IH_TYPE_RAMDISK:
+ address = *loadaddr;
+ break;
+ default:
+ fprintf(stderr, "Unsupported u-boot image type %d\n", hdr->ih_type);
+ goto out;
+ }
+
+ data = g_malloc(hdr->ih_size);
+
+ if (read(fd, data, hdr->ih_size) != hdr->ih_size) {
+ fprintf(stderr, "Error reading file\n");
+ goto out;
+ }
+
+ if (do_uncompress) {
+ uint8_t *compressed_data;
+ size_t max_bytes;
+ ssize_t bytes;
+
+ compressed_data = data;
+ max_bytes = UBOOT_MAX_GUNZIP_BYTES;
+ data = g_malloc(max_bytes);
+
+ bytes = gunzip(data, max_bytes, compressed_data, hdr->ih_size);
+ g_free(compressed_data);
+ if (bytes < 0) {
+ fprintf(stderr, "Unable to decompress gzipped image!\n");
+ goto out;
+ }
+ hdr->ih_size = bytes;
+ }
+
+ rom_add_blob_fixed(filename, data, hdr->ih_size, address);
+
+ ret = hdr->ih_size;
+
+out:
+ if (data)
+ g_free(data);
+ close(fd);
+ return ret;
+}
+
+int load_uimage(const char *filename, hwaddr *ep, hwaddr *loadaddr,
+ int *is_linux,
+ uint64_t (*translate_fn)(void *, uint64_t),
+ void *translate_opaque)
+{
+ return load_uboot_image(filename, ep, loadaddr, is_linux, IH_TYPE_KERNEL,
+ translate_fn, translate_opaque);
+}
+
+/* Load a ramdisk. */
+int load_ramdisk(const char *filename, hwaddr addr, uint64_t max_sz)
+{
+ return load_uboot_image(filename, NULL, &addr, NULL, IH_TYPE_RAMDISK,
+ NULL, NULL);
+}
+
+/* Load a gzip-compressed kernel to a dynamically allocated buffer. */
+int load_image_gzipped_buffer(const char *filename, uint64_t max_sz,
+ uint8_t **buffer)
+{
+ uint8_t *compressed_data = NULL;
+ uint8_t *data = NULL;
+ gsize len;
+ ssize_t bytes;
+ int ret = -1;
+
+ if (!g_file_get_contents(filename, (char **) &compressed_data, &len,
+ NULL)) {
+ goto out;
+ }
+
+ /* Is it a gzip-compressed file? */
+ if (len < 2 ||
+ compressed_data[0] != 0x1f ||
+ compressed_data[1] != 0x8b) {
+ goto out;
+ }
+
+ if (max_sz > LOAD_IMAGE_MAX_GUNZIP_BYTES) {
+ max_sz = LOAD_IMAGE_MAX_GUNZIP_BYTES;
+ }
+
+ data = g_malloc(max_sz);
+ bytes = gunzip(data, max_sz, compressed_data, len);
+ if (bytes < 0) {
+ fprintf(stderr, "%s: unable to decompress gzipped kernel file\n",
+ filename);
+ goto out;
+ }
+
+ /* trim to actual size and return to caller */
+ *buffer = g_realloc(data, bytes);
+ ret = bytes;
+ /* ownership has been transferred to caller */
+ data = NULL;
+
+ out:
+ g_free(compressed_data);
+ g_free(data);
+ return ret;
+}
+
+/* Load a gzip-compressed kernel. */
+int load_image_gzipped(const char *filename, hwaddr addr, uint64_t max_sz)
+{
+ int bytes;
+ uint8_t *data;
+
+ bytes = load_image_gzipped_buffer(filename, max_sz, &data);
+ if (bytes != -1) {
+ rom_add_blob_fixed(filename, data, bytes, addr);
+ g_free(data);
+ }
+ return bytes;
+}
+
+/*
+ * Functions for reboot-persistent memory regions.
+ * - used for vga bios and option roms.
+ * - also linux kernel (-kernel / -initrd).
+ */
+
+typedef struct Rom Rom;
+
+struct Rom {
+ char *name;
+ char *path;
+
+ /* datasize is the amount of memory allocated in "data". If datasize is less
+ * than romsize, it means that the area from datasize to romsize is filled
+ * with zeros.
+ */
+ size_t romsize;
+ size_t datasize;
+
+ uint8_t *data;
+ MemoryRegion *mr;
+ int isrom;
+ char *fw_dir;
+ char *fw_file;
+
+ hwaddr addr;
+ QTAILQ_ENTRY(Rom) next;
+};
+
+static FWCfgState *fw_cfg;
+static QTAILQ_HEAD(, Rom) roms = QTAILQ_HEAD_INITIALIZER(roms);
+
+static void rom_insert(Rom *rom)
+{
+ Rom *item;
+
+ if (roms_loaded) {
+ hw_error ("ROM images must be loaded at startup\n");
+ }
+
+ /* list is ordered by load address */
+ QTAILQ_FOREACH(item, &roms, next) {
+ if (rom->addr >= item->addr)
+ continue;
+ QTAILQ_INSERT_BEFORE(item, rom, next);
+ return;
+ }
+ QTAILQ_INSERT_TAIL(&roms, rom, next);
+}
+
+static void fw_cfg_resized(const char *id, uint64_t length, void *host)
+{
+ if (fw_cfg) {
+ fw_cfg_modify_file(fw_cfg, id + strlen("/rom@"), host, length);
+ }
+}
+
+static void *rom_set_mr(Rom *rom, Object *owner, const char *name)
+{
+ void *data;
+
+ rom->mr = g_malloc(sizeof(*rom->mr));
+ memory_region_init_resizeable_ram(rom->mr, owner, name,
+ rom->datasize, rom->romsize,
+ fw_cfg_resized,
+ &error_abort);
+ memory_region_set_readonly(rom->mr, true);
+ vmstate_register_ram_global(rom->mr);
+
+ data = memory_region_get_ram_ptr(rom->mr);
+ memcpy(data, rom->data, rom->datasize);
+
+ return data;
+}
+
+int rom_add_file(const char *file, const char *fw_dir,
+ hwaddr addr, int32_t bootindex,
+ bool option_rom)
+{
+ Rom *rom;
+ int rc, fd = -1;
+ char devpath[100];
+
+ rom = g_malloc0(sizeof(*rom));
+ rom->name = g_strdup(file);
+ rom->path = qemu_find_file(QEMU_FILE_TYPE_BIOS, rom->name);
+ if (rom->path == NULL) {
+ rom->path = g_strdup(file);
+ }
+
+ fd = open(rom->path, O_RDONLY | O_BINARY);
+ if (fd == -1) {
+ fprintf(stderr, "Could not open option rom '%s': %s\n",
+ rom->path, strerror(errno));
+ goto err;
+ }
+
+ if (fw_dir) {
+ rom->fw_dir = g_strdup(fw_dir);
+ rom->fw_file = g_strdup(file);
+ }
+ rom->addr = addr;
+ rom->romsize = lseek(fd, 0, SEEK_END);
+ if (rom->romsize == -1) {
+ fprintf(stderr, "rom: file %-20s: get size error: %s\n",
+ rom->name, strerror(errno));
+ goto err;
+ }
+
+ rom->datasize = rom->romsize;
+ rom->data = g_malloc0(rom->datasize);
+ lseek(fd, 0, SEEK_SET);
+ rc = read(fd, rom->data, rom->datasize);
+ if (rc != rom->datasize) {
+ fprintf(stderr, "rom: file %-20s: read error: rc=%d (expected %zd)\n",
+ rom->name, rc, rom->datasize);
+ goto err;
+ }
+ close(fd);
+ rom_insert(rom);
+ if (rom->fw_file && fw_cfg) {
+ const char *basename;
+ char fw_file_name[FW_CFG_MAX_FILE_PATH];
+ void *data;
+
+ basename = strrchr(rom->fw_file, '/');
+ if (basename) {
+ basename++;
+ } else {
+ basename = rom->fw_file;
+ }
+ snprintf(fw_file_name, sizeof(fw_file_name), "%s/%s", rom->fw_dir,
+ basename);
+ snprintf(devpath, sizeof(devpath), "/rom@%s", fw_file_name);
+
+ if ((!option_rom || option_rom_has_mr) && rom_file_has_mr) {
+ data = rom_set_mr(rom, OBJECT(fw_cfg), devpath);
+ } else {
+ data = rom->data;
+ }
+
+ fw_cfg_add_file(fw_cfg, fw_file_name, data, rom->romsize);
+ } else {
+ snprintf(devpath, sizeof(devpath), "/rom@" TARGET_FMT_plx, addr);
+ }
+
+ add_boot_device_path(bootindex, NULL, devpath);
+ return 0;
+
+err:
+ if (fd != -1)
+ close(fd);
+ g_free(rom->data);
+ g_free(rom->path);
+ g_free(rom->name);
+ g_free(rom);
+ return -1;
+}
+
+MemoryRegion *rom_add_blob(const char *name, const void *blob, size_t len,
+ size_t max_len, hwaddr addr, const char *fw_file_name,
+ FWCfgReadCallback fw_callback, void *callback_opaque)
+{
+ Rom *rom;
+ MemoryRegion *mr = NULL;
+
+ rom = g_malloc0(sizeof(*rom));
+ rom->name = g_strdup(name);
+ rom->addr = addr;
+ rom->romsize = max_len ? max_len : len;
+ rom->datasize = len;
+ rom->data = g_malloc0(rom->datasize);
+ memcpy(rom->data, blob, len);
+ rom_insert(rom);
+ if (fw_file_name && fw_cfg) {
+ char devpath[100];
+ void *data;
+
+ snprintf(devpath, sizeof(devpath), "/rom@%s", fw_file_name);
+
+ if (rom_file_has_mr) {
+ data = rom_set_mr(rom, OBJECT(fw_cfg), devpath);
+ mr = rom->mr;
+ } else {
+ data = rom->data;
+ }
+
+ fw_cfg_add_file_callback(fw_cfg, fw_file_name,
+ fw_callback, callback_opaque,
+ data, rom->datasize);
+ }
+ return mr;
+}
+
+/* This function is specific for elf program because we don't need to allocate
+ * all the rom. We just allocate the first part and the rest is just zeros. This
+ * is why romsize and datasize are different. Also, this function seize the
+ * memory ownership of "data", so we don't have to allocate and copy the buffer.
+ */
+int rom_add_elf_program(const char *name, void *data, size_t datasize,
+ size_t romsize, hwaddr addr)
+{
+ Rom *rom;
+
+ rom = g_malloc0(sizeof(*rom));
+ rom->name = g_strdup(name);
+ rom->addr = addr;
+ rom->datasize = datasize;
+ rom->romsize = romsize;
+ rom->data = data;
+ rom_insert(rom);
+ return 0;
+}
+
+int rom_add_vga(const char *file)
+{
+ return rom_add_file(file, "vgaroms", 0, -1, true);
+}
+
+int rom_add_option(const char *file, int32_t bootindex)
+{
+ return rom_add_file(file, "genroms", 0, bootindex, true);
+}
+
+static void rom_reset(void *unused)
+{
+ Rom *rom;
+
+ QTAILQ_FOREACH(rom, &roms, next) {
+ if (rom->fw_file) {
+ continue;
+ }
+ if (rom->data == NULL) {
+ continue;
+ }
+ if (rom->mr) {
+ void *host = memory_region_get_ram_ptr(rom->mr);
+ memcpy(host, rom->data, rom->datasize);
+ } else {
+ cpu_physical_memory_write_rom(&address_space_memory,
+ rom->addr, rom->data, rom->datasize);
+ }
+ if (rom->isrom) {
+ /* rom needs to be written only once */
+ g_free(rom->data);
+ rom->data = NULL;
+ }
+ /*
+ * The rom loader is really on the same level as firmware in the guest
+ * shadowing a ROM into RAM. Such a shadowing mechanism needs to ensure
+ * that the instruction cache for that new region is clear, so that the
+ * CPU definitely fetches its instructions from the just written data.
+ */
+ cpu_flush_icache_range(rom->addr, rom->datasize);
+ }
+}
+
+int rom_check_and_register_reset(void)
+{
+ hwaddr addr = 0;
+ MemoryRegionSection section;
+ Rom *rom;
+
+ QTAILQ_FOREACH(rom, &roms, next) {
+ if (rom->fw_file) {
+ continue;
+ }
+ if (addr > rom->addr) {
+ fprintf(stderr, "rom: requested regions overlap "
+ "(rom %s. free=0x" TARGET_FMT_plx
+ ", addr=0x" TARGET_FMT_plx ")\n",
+ rom->name, addr, rom->addr);
+ return -1;
+ }
+ addr = rom->addr;
+ addr += rom->romsize;
+ section = memory_region_find(get_system_memory(), rom->addr, 1);
+ rom->isrom = int128_nz(section.size) && memory_region_is_rom(section.mr);
+ memory_region_unref(section.mr);
+ }
+ qemu_register_reset(rom_reset, NULL);
+ roms_loaded = 1;
+ return 0;
+}
+
+void rom_set_fw(FWCfgState *f)
+{
+ fw_cfg = f;
+}
+
+static Rom *find_rom(hwaddr addr)
+{
+ Rom *rom;
+
+ QTAILQ_FOREACH(rom, &roms, next) {
+ if (rom->fw_file) {
+ continue;
+ }
+ if (rom->mr) {
+ continue;
+ }
+ if (rom->addr > addr) {
+ continue;
+ }
+ if (rom->addr + rom->romsize < addr) {
+ continue;
+ }
+ return rom;
+ }
+ return NULL;
+}
+
+/*
+ * Copies memory from registered ROMs to dest. Any memory that is contained in
+ * a ROM between addr and addr + size is copied. Note that this can involve
+ * multiple ROMs, which need not start at addr and need not end at addr + size.
+ */
+int rom_copy(uint8_t *dest, hwaddr addr, size_t size)
+{
+ hwaddr end = addr + size;
+ uint8_t *s, *d = dest;
+ size_t l = 0;
+ Rom *rom;
+
+ QTAILQ_FOREACH(rom, &roms, next) {
+ if (rom->fw_file) {
+ continue;
+ }
+ if (rom->mr) {
+ continue;
+ }
+ if (rom->addr + rom->romsize < addr) {
+ continue;
+ }
+ if (rom->addr > end) {
+ break;
+ }
+
+ d = dest + (rom->addr - addr);
+ s = rom->data;
+ l = rom->datasize;
+
+ if ((d + l) > (dest + size)) {
+ l = dest - d;
+ }
+
+ if (l > 0) {
+ memcpy(d, s, l);
+ }
+
+ if (rom->romsize > rom->datasize) {
+ /* If datasize is less than romsize, it means that we didn't
+ * allocate all the ROM because the trailing data are only zeros.
+ */
+
+ d += l;
+ l = rom->romsize - rom->datasize;
+
+ if ((d + l) > (dest + size)) {
+ /* Rom size doesn't fit in the destination area. Adjust to avoid
+ * overflow.
+ */
+ l = dest - d;
+ }
+
+ if (l > 0) {
+ memset(d, 0x0, l);
+ }
+ }
+ }
+
+ return (d + l) - dest;
+}
+
+void *rom_ptr(hwaddr addr)
+{
+ Rom *rom;
+
+ rom = find_rom(addr);
+ if (!rom || !rom->data)
+ return NULL;
+ return rom->data + (addr - rom->addr);
+}
+
+void hmp_info_roms(Monitor *mon, const QDict *qdict)
+{
+ Rom *rom;
+
+ QTAILQ_FOREACH(rom, &roms, next) {
+ if (rom->mr) {
+ monitor_printf(mon, "%s"
+ " size=0x%06zx name=\"%s\"\n",
+ memory_region_name(rom->mr),
+ rom->romsize,
+ rom->name);
+ } else if (!rom->fw_file) {
+ monitor_printf(mon, "addr=" TARGET_FMT_plx
+ " size=0x%06zx mem=%s name=\"%s\"\n",
+ rom->addr, rom->romsize,
+ rom->isrom ? "rom" : "ram",
+ rom->name);
+ } else {
+ monitor_printf(mon, "fw=%s/%s"
+ " size=0x%06zx name=\"%s\"\n",
+ rom->fw_dir,
+ rom->fw_file,
+ rom->romsize,
+ rom->name);
+ }
+ }
+}
diff --git a/hw/core/machine.c b/hw/core/machine.c
new file mode 100644
index 00000000..ac4654e9
--- /dev/null
+++ b/hw/core/machine.c
@@ -0,0 +1,485 @@
+/*
+ * QEMU Machine
+ *
+ * Copyright (C) 2014 Red Hat Inc
+ *
+ * Authors:
+ * Marcel Apfelbaum <marcel.a@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "hw/boards.h"
+#include "qapi/visitor.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "qemu/error-report.h"
+
+static char *machine_get_accel(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return g_strdup(ms->accel);
+}
+
+static void machine_set_accel(Object *obj, const char *value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->accel);
+ ms->accel = g_strdup(value);
+}
+
+static void machine_set_kernel_irqchip(Object *obj, bool value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ ms->kernel_irqchip_allowed = value;
+ ms->kernel_irqchip_required = value;
+}
+
+static void machine_get_kvm_shadow_mem(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+ int64_t value = ms->kvm_shadow_mem;
+
+ visit_type_int(v, &value, name, errp);
+}
+
+static void machine_set_kvm_shadow_mem(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+ Error *error = NULL;
+ int64_t value;
+
+ visit_type_int(v, &value, name, &error);
+ if (error) {
+ error_propagate(errp, error);
+ return;
+ }
+
+ ms->kvm_shadow_mem = value;
+}
+
+static char *machine_get_kernel(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return g_strdup(ms->kernel_filename);
+}
+
+static void machine_set_kernel(Object *obj, const char *value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->kernel_filename);
+ ms->kernel_filename = g_strdup(value);
+}
+
+static char *machine_get_initrd(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return g_strdup(ms->initrd_filename);
+}
+
+static void machine_set_initrd(Object *obj, const char *value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->initrd_filename);
+ ms->initrd_filename = g_strdup(value);
+}
+
+static char *machine_get_append(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return g_strdup(ms->kernel_cmdline);
+}
+
+static void machine_set_append(Object *obj, const char *value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->kernel_cmdline);
+ ms->kernel_cmdline = g_strdup(value);
+}
+
+static char *machine_get_dtb(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return g_strdup(ms->dtb);
+}
+
+static void machine_set_dtb(Object *obj, const char *value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->dtb);
+ ms->dtb = g_strdup(value);
+}
+
+static char *machine_get_dumpdtb(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return g_strdup(ms->dumpdtb);
+}
+
+static void machine_set_dumpdtb(Object *obj, const char *value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->dumpdtb);
+ ms->dumpdtb = g_strdup(value);
+}
+
+static void machine_get_phandle_start(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+ int64_t value = ms->phandle_start;
+
+ visit_type_int(v, &value, name, errp);
+}
+
+static void machine_set_phandle_start(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+ Error *error = NULL;
+ int64_t value;
+
+ visit_type_int(v, &value, name, &error);
+ if (error) {
+ error_propagate(errp, error);
+ return;
+ }
+
+ ms->phandle_start = value;
+}
+
+static char *machine_get_dt_compatible(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return g_strdup(ms->dt_compatible);
+}
+
+static void machine_set_dt_compatible(Object *obj, const char *value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->dt_compatible);
+ ms->dt_compatible = g_strdup(value);
+}
+
+static bool machine_get_dump_guest_core(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return ms->dump_guest_core;
+}
+
+static void machine_set_dump_guest_core(Object *obj, bool value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ ms->dump_guest_core = value;
+}
+
+static bool machine_get_mem_merge(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return ms->mem_merge;
+}
+
+static void machine_set_mem_merge(Object *obj, bool value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ ms->mem_merge = value;
+}
+
+static bool machine_get_usb(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return ms->usb;
+}
+
+static void machine_set_usb(Object *obj, bool value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ ms->usb = value;
+ ms->usb_disabled = !value;
+}
+
+static char *machine_get_firmware(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return g_strdup(ms->firmware);
+}
+
+static void machine_set_firmware(Object *obj, const char *value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->firmware);
+ ms->firmware = g_strdup(value);
+}
+
+static bool machine_get_iommu(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return ms->iommu;
+}
+
+static void machine_set_iommu(Object *obj, bool value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ ms->iommu = value;
+}
+
+static void machine_set_suppress_vmdesc(Object *obj, bool value, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ ms->suppress_vmdesc = value;
+}
+
+static bool machine_get_suppress_vmdesc(Object *obj, Error **errp)
+{
+ MachineState *ms = MACHINE(obj);
+
+ return ms->suppress_vmdesc;
+}
+
+static int error_on_sysbus_device(SysBusDevice *sbdev, void *opaque)
+{
+ error_report("Option '-device %s' cannot be handled by this machine",
+ object_class_get_name(object_get_class(OBJECT(sbdev))));
+ exit(1);
+}
+
+static void machine_init_notify(Notifier *notifier, void *data)
+{
+ Object *machine = qdev_get_machine();
+ ObjectClass *oc = object_get_class(machine);
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ if (mc->has_dynamic_sysbus) {
+ /* Our machine can handle dynamic sysbus devices, we're all good */
+ return;
+ }
+
+ /*
+ * Loop through all dynamically created devices and check whether there
+ * are sysbus devices among them. If there are, error out.
+ */
+ foreach_dynamic_sysbus_device(error_on_sysbus_device, NULL);
+}
+
+static void machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ /* Default 128 MB as guest ram size */
+ mc->default_ram_size = 128 * M_BYTE;
+}
+
+static void machine_initfn(Object *obj)
+{
+ MachineState *ms = MACHINE(obj);
+
+ ms->kernel_irqchip_allowed = true;
+ ms->kvm_shadow_mem = -1;
+ ms->dump_guest_core = true;
+ ms->mem_merge = true;
+
+ object_property_add_str(obj, "accel",
+ machine_get_accel, machine_set_accel, NULL);
+ object_property_set_description(obj, "accel",
+ "Accelerator list",
+ NULL);
+ object_property_add_bool(obj, "kernel-irqchip",
+ NULL,
+ machine_set_kernel_irqchip,
+ NULL);
+ object_property_set_description(obj, "kernel-irqchip",
+ "Use KVM in-kernel irqchip",
+ NULL);
+ object_property_add(obj, "kvm-shadow-mem", "int",
+ machine_get_kvm_shadow_mem,
+ machine_set_kvm_shadow_mem,
+ NULL, NULL, NULL);
+ object_property_set_description(obj, "kvm-shadow-mem",
+ "KVM shadow MMU size",
+ NULL);
+ object_property_add_str(obj, "kernel",
+ machine_get_kernel, machine_set_kernel, NULL);
+ object_property_set_description(obj, "kernel",
+ "Linux kernel image file",
+ NULL);
+ object_property_add_str(obj, "initrd",
+ machine_get_initrd, machine_set_initrd, NULL);
+ object_property_set_description(obj, "initrd",
+ "Linux initial ramdisk file",
+ NULL);
+ object_property_add_str(obj, "append",
+ machine_get_append, machine_set_append, NULL);
+ object_property_set_description(obj, "append",
+ "Linux kernel command line",
+ NULL);
+ object_property_add_str(obj, "dtb",
+ machine_get_dtb, machine_set_dtb, NULL);
+ object_property_set_description(obj, "dtb",
+ "Linux kernel device tree file",
+ NULL);
+ object_property_add_str(obj, "dumpdtb",
+ machine_get_dumpdtb, machine_set_dumpdtb, NULL);
+ object_property_set_description(obj, "dumpdtb",
+ "Dump current dtb to a file and quit",
+ NULL);
+ object_property_add(obj, "phandle-start", "int",
+ machine_get_phandle_start,
+ machine_set_phandle_start,
+ NULL, NULL, NULL);
+ object_property_set_description(obj, "phandle-start",
+ "The first phandle ID we may generate dynamically",
+ NULL);
+ object_property_add_str(obj, "dt-compatible",
+ machine_get_dt_compatible,
+ machine_set_dt_compatible,
+ NULL);
+ object_property_set_description(obj, "dt-compatible",
+ "Overrides the \"compatible\" property of the dt root node",
+ NULL);
+ object_property_add_bool(obj, "dump-guest-core",
+ machine_get_dump_guest_core,
+ machine_set_dump_guest_core,
+ NULL);
+ object_property_set_description(obj, "dump-guest-core",
+ "Include guest memory in a core dump",
+ NULL);
+ object_property_add_bool(obj, "mem-merge",
+ machine_get_mem_merge,
+ machine_set_mem_merge, NULL);
+ object_property_set_description(obj, "mem-merge",
+ "Enable/disable memory merge support",
+ NULL);
+ object_property_add_bool(obj, "usb",
+ machine_get_usb,
+ machine_set_usb, NULL);
+ object_property_set_description(obj, "usb",
+ "Set on/off to enable/disable usb",
+ NULL);
+ object_property_add_str(obj, "firmware",
+ machine_get_firmware,
+ machine_set_firmware, NULL);
+ object_property_set_description(obj, "firmware",
+ "Firmware image",
+ NULL);
+ object_property_add_bool(obj, "iommu",
+ machine_get_iommu,
+ machine_set_iommu, NULL);
+ object_property_set_description(obj, "iommu",
+ "Set on/off to enable/disable Intel IOMMU (VT-d)",
+ NULL);
+ object_property_add_bool(obj, "suppress-vmdesc",
+ machine_get_suppress_vmdesc,
+ machine_set_suppress_vmdesc, NULL);
+ object_property_set_description(obj, "suppress-vmdesc",
+ "Set on to disable self-describing migration",
+ NULL);
+
+ /* Register notifier when init is done for sysbus sanity checks */
+ ms->sysbus_notifier.notify = machine_init_notify;
+ qemu_add_machine_init_done_notifier(&ms->sysbus_notifier);
+}
+
+static void machine_finalize(Object *obj)
+{
+ MachineState *ms = MACHINE(obj);
+
+ g_free(ms->accel);
+ g_free(ms->kernel_filename);
+ g_free(ms->initrd_filename);
+ g_free(ms->kernel_cmdline);
+ g_free(ms->dtb);
+ g_free(ms->dumpdtb);
+ g_free(ms->dt_compatible);
+ g_free(ms->firmware);
+}
+
+bool machine_usb(MachineState *machine)
+{
+ return machine->usb;
+}
+
+bool machine_iommu(MachineState *machine)
+{
+ return machine->iommu;
+}
+
+bool machine_kernel_irqchip_allowed(MachineState *machine)
+{
+ return machine->kernel_irqchip_allowed;
+}
+
+bool machine_kernel_irqchip_required(MachineState *machine)
+{
+ return machine->kernel_irqchip_required;
+}
+
+int machine_kvm_shadow_mem(MachineState *machine)
+{
+ return machine->kvm_shadow_mem;
+}
+
+int machine_phandle_start(MachineState *machine)
+{
+ return machine->phandle_start;
+}
+
+bool machine_dump_guest_core(MachineState *machine)
+{
+ return machine->dump_guest_core;
+}
+
+bool machine_mem_merge(MachineState *machine)
+{
+ return machine->mem_merge;
+}
+
+static const TypeInfo machine_info = {
+ .name = TYPE_MACHINE,
+ .parent = TYPE_OBJECT,
+ .abstract = true,
+ .class_size = sizeof(MachineClass),
+ .class_init = machine_class_init,
+ .instance_size = sizeof(MachineState),
+ .instance_init = machine_initfn,
+ .instance_finalize = machine_finalize,
+};
+
+static void machine_register_types(void)
+{
+ type_register_static(&machine_info);
+}
+
+type_init(machine_register_types)
diff --git a/hw/core/nmi.c b/hw/core/nmi.c
new file mode 100644
index 00000000..de1d1f8c
--- /dev/null
+++ b/hw/core/nmi.c
@@ -0,0 +1,104 @@
+/*
+ * NMI monitor handler class and helpers.
+ *
+ * Copyright IBM Corp., 2014
+ *
+ * Author: Alexey Kardashevskiy <aik@ozlabs.ru>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/nmi.h"
+#include "qapi/qmp/qerror.h"
+#include "monitor/monitor.h"
+
+struct do_nmi_s {
+ int cpu_index;
+ Error *errp;
+ bool handled;
+};
+
+static void nmi_children(Object *o, struct do_nmi_s *ns);
+
+static int do_nmi(Object *o, void *opaque)
+{
+ struct do_nmi_s *ns = opaque;
+ NMIState *n = (NMIState *) object_dynamic_cast(o, TYPE_NMI);
+
+ if (n) {
+ NMIClass *nc = NMI_GET_CLASS(n);
+
+ ns->handled = true;
+ nc->nmi_monitor_handler(n, ns->cpu_index, &ns->errp);
+ if (ns->errp) {
+ return -1;
+ }
+ }
+ nmi_children(o, ns);
+
+ return 0;
+}
+
+static void nmi_children(Object *o, struct do_nmi_s *ns)
+{
+ object_child_foreach(o, do_nmi, ns);
+}
+
+void nmi_monitor_handle(int cpu_index, Error **errp)
+{
+ struct do_nmi_s ns = {
+ .cpu_index = cpu_index,
+ .errp = NULL,
+ .handled = false
+ };
+
+ nmi_children(object_get_root(), &ns);
+ if (ns.handled) {
+ error_propagate(errp, ns.errp);
+ } else {
+ error_setg(errp, QERR_UNSUPPORTED);
+ }
+}
+
+void inject_nmi(void)
+{
+#if defined(TARGET_I386)
+ CPUState *cs;
+
+ CPU_FOREACH(cs) {
+ X86CPU *cpu = X86_CPU(cs);
+
+ if (!cpu->apic_state) {
+ cpu_interrupt(cs, CPU_INTERRUPT_NMI);
+ } else {
+ apic_deliver_nmi(cpu->apic_state);
+ }
+ }
+#else
+ nmi_monitor_handle(0, NULL);
+#endif
+}
+
+static const TypeInfo nmi_info = {
+ .name = TYPE_NMI,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(NMIClass),
+};
+
+static void nmi_register_types(void)
+{
+ type_register_static(&nmi_info);
+}
+
+type_init(nmi_register_types)
diff --git a/hw/core/null-machine.c b/hw/core/null-machine.c
new file mode 100644
index 00000000..1ec7c3bb
--- /dev/null
+++ b/hw/core/null-machine.c
@@ -0,0 +1,35 @@
+/*
+ * Empty machine
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu-common.h"
+#include "hw/hw.h"
+#include "hw/boards.h"
+
+static void machine_none_init(MachineState *machine)
+{
+}
+
+static QEMUMachine machine_none = {
+ .name = "none",
+ .desc = "empty machine",
+ .init = machine_none_init,
+ .max_cpus = 0,
+};
+
+static void register_machines(void)
+{
+ qemu_register_machine(&machine_none);
+}
+
+machine_init(register_machines);
+
diff --git a/hw/core/platform-bus.c b/hw/core/platform-bus.c
new file mode 100644
index 00000000..70e05189
--- /dev/null
+++ b/hw/core/platform-bus.c
@@ -0,0 +1,252 @@
+/*
+ * Platform Bus device to support dynamic Sysbus devices
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/platform-bus.h"
+#include "exec/address-spaces.h"
+#include "sysemu/sysemu.h"
+
+
+/*
+ * Returns the PlatformBus IRQ number for a SysBusDevice irq number or -1 if
+ * the IRQ is not mapped on this Platform bus.
+ */
+int platform_bus_get_irqn(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+ int n)
+{
+ qemu_irq sbirq = sysbus_get_connected_irq(sbdev, n);
+ int i;
+
+ for (i = 0; i < pbus->num_irqs; i++) {
+ if (pbus->irqs[i] == sbirq) {
+ return i;
+ }
+ }
+
+ /* IRQ not mapped on platform bus */
+ return -1;
+}
+
+/*
+ * Returns the PlatformBus MMIO region offset for Region n of a SysBusDevice or
+ * -1 if the region is not mapped on this Platform bus.
+ */
+hwaddr platform_bus_get_mmio_addr(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+ int n)
+{
+ MemoryRegion *pbus_mr = &pbus->mmio;
+ MemoryRegion *sbdev_mr = sysbus_mmio_get_region(sbdev, n);
+ Object *pbus_mr_obj = OBJECT(pbus_mr);
+ Object *parent_mr;
+
+ if (!memory_region_is_mapped(sbdev_mr)) {
+ /* Region is not mapped? */
+ return -1;
+ }
+
+ parent_mr = object_property_get_link(OBJECT(sbdev_mr), "container", NULL);
+
+ assert(parent_mr);
+ if (parent_mr != pbus_mr_obj) {
+ /* MMIO region is not mapped on platform bus */
+ return -1;
+ }
+
+ return object_property_get_int(OBJECT(sbdev_mr), "addr", NULL);
+}
+
+static int platform_bus_count_irqs(SysBusDevice *sbdev, void *opaque)
+{
+ PlatformBusDevice *pbus = opaque;
+ qemu_irq sbirq;
+ int n, i;
+
+ for (n = 0; ; n++) {
+ if (!sysbus_has_irq(sbdev, n)) {
+ break;
+ }
+
+ sbirq = sysbus_get_connected_irq(sbdev, n);
+ for (i = 0; i < pbus->num_irqs; i++) {
+ if (pbus->irqs[i] == sbirq) {
+ bitmap_set(pbus->used_irqs, i, 1);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Loop through all sysbus devices and look for unassigned IRQ lines as well as
+ * unassociated MMIO regions. Connect them to the platform bus if available.
+ */
+static void plaform_bus_refresh_irqs(PlatformBusDevice *pbus)
+{
+ bitmap_zero(pbus->used_irqs, pbus->num_irqs);
+ foreach_dynamic_sysbus_device(platform_bus_count_irqs, pbus);
+ pbus->done_gathering = true;
+}
+
+static int platform_bus_map_irq(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+ int n)
+{
+ int max_irqs = pbus->num_irqs;
+ int irqn;
+
+ if (sysbus_is_irq_connected(sbdev, n)) {
+ /* IRQ is already mapped, nothing to do */
+ return 0;
+ }
+
+ irqn = find_first_zero_bit(pbus->used_irqs, max_irqs);
+ if (irqn >= max_irqs) {
+ hw_error("Platform Bus: Can not fit IRQ line");
+ return -1;
+ }
+
+ set_bit(irqn, pbus->used_irqs);
+ sysbus_connect_irq(sbdev, n, pbus->irqs[irqn]);
+
+ return 0;
+}
+
+static int platform_bus_map_mmio(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+ int n)
+{
+ MemoryRegion *sbdev_mr = sysbus_mmio_get_region(sbdev, n);
+ uint64_t size = memory_region_size(sbdev_mr);
+ uint64_t alignment = (1ULL << (63 - clz64(size + size - 1)));
+ uint64_t off;
+ bool found_region = false;
+
+ if (memory_region_is_mapped(sbdev_mr)) {
+ /* Region is already mapped, nothing to do */
+ return 0;
+ }
+
+ /*
+ * Look for empty space in the MMIO space that is naturally aligned with
+ * the target device's memory region
+ */
+ for (off = 0; off < pbus->mmio_size; off += alignment) {
+ if (!memory_region_find(&pbus->mmio, off, size).mr) {
+ found_region = true;
+ break;
+ }
+ }
+
+ if (!found_region) {
+ hw_error("Platform Bus: Can not fit MMIO region of size %"PRIx64, size);
+ }
+
+ /* Map the device's region into our Platform Bus MMIO space */
+ memory_region_add_subregion(&pbus->mmio, off, sbdev_mr);
+
+ return 0;
+}
+
+/*
+ * For each sysbus device, look for unassigned IRQ lines as well as
+ * unassociated MMIO regions. Connect them to the platform bus if available.
+ */
+static int link_sysbus_device(SysBusDevice *sbdev, void *opaque)
+{
+ PlatformBusDevice *pbus = opaque;
+ int i;
+
+ for (i = 0; sysbus_has_irq(sbdev, i); i++) {
+ platform_bus_map_irq(pbus, sbdev, i);
+ }
+
+ for (i = 0; sysbus_has_mmio(sbdev, i); i++) {
+ platform_bus_map_mmio(pbus, sbdev, i);
+ }
+
+ return 0;
+}
+
+static void platform_bus_init_notify(Notifier *notifier, void *data)
+{
+ PlatformBusDevice *pb = container_of(notifier, PlatformBusDevice, notifier);
+
+ /*
+ * Generate a bitmap of used IRQ lines, as the user might have specified
+ * them on the command line.
+ */
+ plaform_bus_refresh_irqs(pb);
+
+ foreach_dynamic_sysbus_device(link_sysbus_device, pb);
+}
+
+static void platform_bus_realize(DeviceState *dev, Error **errp)
+{
+ PlatformBusDevice *pbus;
+ SysBusDevice *d;
+ int i;
+
+ d = SYS_BUS_DEVICE(dev);
+ pbus = PLATFORM_BUS_DEVICE(dev);
+
+ memory_region_init(&pbus->mmio, NULL, "platform bus", pbus->mmio_size);
+ sysbus_init_mmio(d, &pbus->mmio);
+
+ pbus->used_irqs = bitmap_new(pbus->num_irqs);
+ pbus->irqs = g_new0(qemu_irq, pbus->num_irqs);
+ for (i = 0; i < pbus->num_irqs; i++) {
+ sysbus_init_irq(d, &pbus->irqs[i]);
+ }
+
+ /*
+ * Register notifier that allows us to gather dangling devices once the
+ * machine is completely assembled
+ */
+ pbus->notifier.notify = platform_bus_init_notify;
+ qemu_add_machine_init_done_notifier(&pbus->notifier);
+}
+
+static Property platform_bus_properties[] = {
+ DEFINE_PROP_UINT32("num_irqs", PlatformBusDevice, num_irqs, 0),
+ DEFINE_PROP_UINT32("mmio_size", PlatformBusDevice, mmio_size, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void platform_bus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = platform_bus_realize;
+ dc->props = platform_bus_properties;
+}
+
+static const TypeInfo platform_bus_info = {
+ .name = TYPE_PLATFORM_BUS_DEVICE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PlatformBusDevice),
+ .class_init = platform_bus_class_init,
+};
+
+static void platform_bus_register_types(void)
+{
+ type_register_static(&platform_bus_info);
+}
+
+type_init(platform_bus_register_types)
diff --git a/hw/core/ptimer.c b/hw/core/ptimer.c
new file mode 100644
index 00000000..8437bd6e
--- /dev/null
+++ b/hw/core/ptimer.c
@@ -0,0 +1,230 @@
+/*
+ * General purpose implementation of a simple periodic countdown timer.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GNU LGPL.
+ */
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "qemu/host-utils.h"
+
+struct ptimer_state
+{
+ uint8_t enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot. */
+ uint64_t limit;
+ uint64_t delta;
+ uint32_t period_frac;
+ int64_t period;
+ int64_t last_event;
+ int64_t next_event;
+ QEMUBH *bh;
+ QEMUTimer *timer;
+};
+
+/* Use a bottom-half routine to avoid reentrancy issues. */
+static void ptimer_trigger(ptimer_state *s)
+{
+ if (s->bh) {
+ qemu_bh_schedule(s->bh);
+ }
+}
+
+static void ptimer_reload(ptimer_state *s)
+{
+ if (s->delta == 0) {
+ ptimer_trigger(s);
+ s->delta = s->limit;
+ }
+ if (s->delta == 0 || s->period == 0) {
+ fprintf(stderr, "Timer with period zero, disabling\n");
+ s->enabled = 0;
+ return;
+ }
+
+ s->last_event = s->next_event;
+ s->next_event = s->last_event + s->delta * s->period;
+ if (s->period_frac) {
+ s->next_event += ((int64_t)s->period_frac * s->delta) >> 32;
+ }
+ timer_mod(s->timer, s->next_event);
+}
+
+static void ptimer_tick(void *opaque)
+{
+ ptimer_state *s = (ptimer_state *)opaque;
+ ptimer_trigger(s);
+ s->delta = 0;
+ if (s->enabled == 2) {
+ s->enabled = 0;
+ } else {
+ ptimer_reload(s);
+ }
+}
+
+uint64_t ptimer_get_count(ptimer_state *s)
+{
+ int64_t now;
+ uint64_t counter;
+
+ if (s->enabled) {
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ /* Figure out the current counter value. */
+ if (now - s->next_event > 0
+ || s->period == 0) {
+ /* Prevent timer underflowing if it should already have
+ triggered. */
+ counter = 0;
+ } else {
+ uint64_t rem;
+ uint64_t div;
+ int clz1, clz2;
+ int shift;
+
+ /* We need to divide time by period, where time is stored in
+ rem (64-bit integer) and period is stored in period/period_frac
+ (64.32 fixed point).
+
+ Doing full precision division is hard, so scale values and
+ do a 64-bit division. The result should be rounded down,
+ so that the rounding error never causes the timer to go
+ backwards.
+ */
+
+ rem = s->next_event - now;
+ div = s->period;
+
+ clz1 = clz64(rem);
+ clz2 = clz64(div);
+ shift = clz1 < clz2 ? clz1 : clz2;
+
+ rem <<= shift;
+ div <<= shift;
+ if (shift >= 32) {
+ div |= ((uint64_t)s->period_frac << (shift - 32));
+ } else {
+ if (shift != 0)
+ div |= (s->period_frac >> (32 - shift));
+ /* Look at remaining bits of period_frac and round div up if
+ necessary. */
+ if ((uint32_t)(s->period_frac << shift))
+ div += 1;
+ }
+ counter = rem / div;
+ }
+ } else {
+ counter = s->delta;
+ }
+ return counter;
+}
+
+void ptimer_set_count(ptimer_state *s, uint64_t count)
+{
+ s->delta = count;
+ if (s->enabled) {
+ s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ptimer_reload(s);
+ }
+}
+
+void ptimer_run(ptimer_state *s, int oneshot)
+{
+ if (s->enabled) {
+ return;
+ }
+ if (s->period == 0) {
+ fprintf(stderr, "Timer with period zero, disabling\n");
+ return;
+ }
+ s->enabled = oneshot ? 2 : 1;
+ s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ptimer_reload(s);
+}
+
+/* Pause a timer. Note that this may cause it to "lose" time, even if it
+ is immediately restarted. */
+void ptimer_stop(ptimer_state *s)
+{
+ if (!s->enabled)
+ return;
+
+ s->delta = ptimer_get_count(s);
+ timer_del(s->timer);
+ s->enabled = 0;
+}
+
+/* Set counter increment interval in nanoseconds. */
+void ptimer_set_period(ptimer_state *s, int64_t period)
+{
+ s->period = period;
+ s->period_frac = 0;
+ if (s->enabled) {
+ s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ptimer_reload(s);
+ }
+}
+
+/* Set counter frequency in Hz. */
+void ptimer_set_freq(ptimer_state *s, uint32_t freq)
+{
+ s->period = 1000000000ll / freq;
+ s->period_frac = (1000000000ll << 32) / freq;
+ if (s->enabled) {
+ s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ptimer_reload(s);
+ }
+}
+
+/* Set the initial countdown value. If reload is nonzero then also set
+ count = limit. */
+void ptimer_set_limit(ptimer_state *s, uint64_t limit, int reload)
+{
+ /*
+ * Artificially limit timeout rate to something
+ * achievable under QEMU. Otherwise, QEMU spends all
+ * its time generating timer interrupts, and there
+ * is no forward progress.
+ * About ten microseconds is the fastest that really works
+ * on the current generation of host machines.
+ */
+
+ if (!use_icount && limit * s->period < 10000 && s->period) {
+ limit = 10000 / s->period;
+ }
+
+ s->limit = limit;
+ if (reload)
+ s->delta = limit;
+ if (s->enabled && reload) {
+ s->next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ptimer_reload(s);
+ }
+}
+
+const VMStateDescription vmstate_ptimer = {
+ .name = "ptimer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(enabled, ptimer_state),
+ VMSTATE_UINT64(limit, ptimer_state),
+ VMSTATE_UINT64(delta, ptimer_state),
+ VMSTATE_UINT32(period_frac, ptimer_state),
+ VMSTATE_INT64(period, ptimer_state),
+ VMSTATE_INT64(last_event, ptimer_state),
+ VMSTATE_INT64(next_event, ptimer_state),
+ VMSTATE_TIMER_PTR(timer, ptimer_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+ptimer_state *ptimer_init(QEMUBH *bh)
+{
+ ptimer_state *s;
+
+ s = (ptimer_state *)g_malloc0(sizeof(ptimer_state));
+ s->bh = bh;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ptimer_tick, s);
+ return s;
+}
diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c
new file mode 100644
index 00000000..921e799d
--- /dev/null
+++ b/hw/core/qdev-properties-system.c
@@ -0,0 +1,425 @@
+/*
+ * qdev property parsing and global properties
+ * (parts specific for qemu-system-*)
+ *
+ * This file is based on code from hw/qdev-properties.c from
+ * commit 074a86fccd185616469dfcdc0e157f438aebba18,
+ * Copyright (c) Gerd Hoffmann <kraxel@redhat.com> and other contributors.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "net/net.h"
+#include "hw/qdev.h"
+#include "qapi/qmp/qerror.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/block/block.h"
+#include "net/hub.h"
+#include "qapi/visitor.h"
+#include "sysemu/char.h"
+#include "sysemu/iothread.h"
+
+static void get_pointer(Object *obj, Visitor *v, Property *prop,
+ char *(*print)(void *ptr),
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ void **ptr = qdev_get_prop_ptr(dev, prop);
+ char *p;
+
+ p = *ptr ? print(*ptr) : g_strdup("");
+ visit_type_str(v, &p, name, errp);
+ g_free(p);
+}
+
+static void set_pointer(Object *obj, Visitor *v, Property *prop,
+ void (*parse)(DeviceState *dev, const char *str,
+ void **ptr, const char *propname,
+ Error **errp),
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Error *local_err = NULL;
+ void **ptr = qdev_get_prop_ptr(dev, prop);
+ char *str;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_str(v, &str, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ if (!*str) {
+ g_free(str);
+ *ptr = NULL;
+ return;
+ }
+ parse(dev, str, ptr, prop->name, errp);
+ g_free(str);
+}
+
+/* --- drive --- */
+
+static void parse_drive(DeviceState *dev, const char *str, void **ptr,
+ const char *propname, Error **errp)
+{
+ BlockBackend *blk;
+
+ blk = blk_by_name(str);
+ if (!blk) {
+ error_setg(errp, "Property '%s.%s' can't find value '%s'",
+ object_get_typename(OBJECT(dev)), propname, str);
+ return;
+ }
+ if (blk_attach_dev(blk, dev) < 0) {
+ DriveInfo *dinfo = blk_legacy_dinfo(blk);
+
+ if (dinfo->type != IF_NONE) {
+ error_setg(errp, "Drive '%s' is already in use because "
+ "it has been automatically connected to another "
+ "device (did you need 'if=none' in the drive options?)",
+ str);
+ } else {
+ error_setg(errp, "Drive '%s' is already in use by another device",
+ str);
+ }
+ return;
+ }
+ *ptr = blk;
+}
+
+static void release_drive(Object *obj, const char *name, void *opaque)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ BlockBackend **ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (*ptr) {
+ blk_detach_dev(*ptr, dev);
+ blockdev_auto_del(*ptr);
+ }
+}
+
+static char *print_drive(void *ptr)
+{
+ return g_strdup(blk_name(ptr));
+}
+
+static void get_drive(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ get_pointer(obj, v, opaque, print_drive, name, errp);
+}
+
+static void set_drive(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ set_pointer(obj, v, opaque, parse_drive, name, errp);
+}
+
+PropertyInfo qdev_prop_drive = {
+ .name = "str",
+ .description = "ID of a drive to use as a backend",
+ .get = get_drive,
+ .set = set_drive,
+ .release = release_drive,
+};
+
+/* --- character device --- */
+
+static void parse_chr(DeviceState *dev, const char *str, void **ptr,
+ const char *propname, Error **errp)
+{
+ CharDriverState *chr = qemu_chr_find(str);
+ if (chr == NULL) {
+ error_setg(errp, "Property '%s.%s' can't find value '%s'",
+ object_get_typename(OBJECT(dev)), propname, str);
+ return;
+ }
+ if (qemu_chr_fe_claim(chr) != 0) {
+ error_setg(errp, "Property '%s.%s' can't take value '%s', it's in use",
+ object_get_typename(OBJECT(dev)), propname, str);
+ return;
+ }
+ *ptr = chr;
+}
+
+static void release_chr(Object *obj, const char *name, void *opaque)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ CharDriverState **ptr = qdev_get_prop_ptr(dev, prop);
+ CharDriverState *chr = *ptr;
+
+ if (chr) {
+ qemu_chr_add_handlers(chr, NULL, NULL, NULL, NULL);
+ qemu_chr_fe_release(chr);
+ }
+}
+
+
+static char *print_chr(void *ptr)
+{
+ CharDriverState *chr = ptr;
+ const char *val = chr->label ? chr->label : "";
+
+ return g_strdup(val);
+}
+
+static void get_chr(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ get_pointer(obj, v, opaque, print_chr, name, errp);
+}
+
+static void set_chr(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ set_pointer(obj, v, opaque, parse_chr, name, errp);
+}
+
+PropertyInfo qdev_prop_chr = {
+ .name = "str",
+ .description = "ID of a chardev to use as a backend",
+ .get = get_chr,
+ .set = set_chr,
+ .release = release_chr,
+};
+
+/* --- netdev device --- */
+static void get_netdev(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ NICPeers *peers_ptr = qdev_get_prop_ptr(dev, prop);
+ char *p = g_strdup(peers_ptr->ncs[0] ? peers_ptr->ncs[0]->name : "");
+
+ visit_type_str(v, &p, name, errp);
+ g_free(p);
+}
+
+static void set_netdev(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ NICPeers *peers_ptr = qdev_get_prop_ptr(dev, prop);
+ NetClientState **ncs = peers_ptr->ncs;
+ NetClientState *peers[MAX_QUEUE_NUM];
+ Error *local_err = NULL;
+ int queues, err = 0, i = 0;
+ char *str;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_str(v, &str, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ queues = qemu_find_net_clients_except(str, peers,
+ NET_CLIENT_OPTIONS_KIND_NIC,
+ MAX_QUEUE_NUM);
+ if (queues == 0) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ if (queues > MAX_QUEUE_NUM) {
+ error_setg(errp, "queues of backend '%s'(%d) exceeds QEMU limitation(%d)",
+ str, queues, MAX_QUEUE_NUM);
+ goto out;
+ }
+
+ for (i = 0; i < queues; i++) {
+ if (peers[i] == NULL) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ if (peers[i]->peer) {
+ err = -EEXIST;
+ goto out;
+ }
+
+ if (ncs[i]) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ ncs[i] = peers[i];
+ ncs[i]->queue_index = i;
+ }
+
+ peers_ptr->queues = queues;
+
+out:
+ error_set_from_qdev_prop_error(errp, err, dev, prop, str);
+ g_free(str);
+}
+
+PropertyInfo qdev_prop_netdev = {
+ .name = "str",
+ .description = "ID of a netdev to use as a backend",
+ .get = get_netdev,
+ .set = set_netdev,
+};
+
+/* --- vlan --- */
+
+static int print_vlan(DeviceState *dev, Property *prop, char *dest, size_t len)
+{
+ NetClientState **ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (*ptr) {
+ int id;
+ if (!net_hub_id_for_client(*ptr, &id)) {
+ return snprintf(dest, len, "%d", id);
+ }
+ }
+
+ return snprintf(dest, len, "<null>");
+}
+
+static void get_vlan(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ NetClientState **ptr = qdev_get_prop_ptr(dev, prop);
+ int32_t id = -1;
+
+ if (*ptr) {
+ int hub_id;
+ if (!net_hub_id_for_client(*ptr, &hub_id)) {
+ id = hub_id;
+ }
+ }
+
+ visit_type_int32(v, &id, name, errp);
+}
+
+static void set_vlan(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ NICPeers *peers_ptr = qdev_get_prop_ptr(dev, prop);
+ NetClientState **ptr = &peers_ptr->ncs[0];
+ Error *local_err = NULL;
+ int32_t id;
+ NetClientState *hubport;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_int32(v, &id, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ if (id == -1) {
+ *ptr = NULL;
+ return;
+ }
+ if (*ptr) {
+ error_set_from_qdev_prop_error(errp, -EINVAL, dev, prop, name);
+ return;
+ }
+
+ hubport = net_hub_port_find(id);
+ if (!hubport) {
+ error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+ name, prop->info->name);
+ return;
+ }
+ *ptr = hubport;
+}
+
+PropertyInfo qdev_prop_vlan = {
+ .name = "int32",
+ .description = "Integer VLAN id to connect to",
+ .print = print_vlan,
+ .get = get_vlan,
+ .set = set_vlan,
+};
+
+void qdev_prop_set_drive(DeviceState *dev, const char *name,
+ BlockBackend *value, Error **errp)
+{
+ object_property_set_str(OBJECT(dev), value ? blk_name(value) : "",
+ name, errp);
+}
+
+void qdev_prop_set_drive_nofail(DeviceState *dev, const char *name,
+ BlockBackend *value)
+{
+ Error *err = NULL;
+
+ qdev_prop_set_drive(dev, name, value, &err);
+ if (err) {
+ error_report_err(err);
+ exit(1);
+ }
+}
+
+void qdev_prop_set_chr(DeviceState *dev, const char *name,
+ CharDriverState *value)
+{
+ assert(!value || value->label);
+ object_property_set_str(OBJECT(dev),
+ value ? value->label : "", name, &error_abort);
+}
+
+void qdev_prop_set_netdev(DeviceState *dev, const char *name,
+ NetClientState *value)
+{
+ assert(!value || value->name);
+ object_property_set_str(OBJECT(dev),
+ value ? value->name : "", name, &error_abort);
+}
+
+void qdev_set_nic_properties(DeviceState *dev, NICInfo *nd)
+{
+ qdev_prop_set_macaddr(dev, "mac", nd->macaddr.a);
+ if (nd->netdev) {
+ qdev_prop_set_netdev(dev, "netdev", nd->netdev);
+ }
+ if (nd->nvectors != DEV_NVECTORS_UNSPECIFIED &&
+ object_property_find(OBJECT(dev), "vectors", NULL)) {
+ qdev_prop_set_uint32(dev, "vectors", nd->nvectors);
+ }
+ nd->instantiated = 1;
+}
+
+static int qdev_add_one_global(void *opaque, QemuOpts *opts, Error **errp)
+{
+ GlobalProperty *g;
+
+ g = g_malloc0(sizeof(*g));
+ g->driver = qemu_opt_get(opts, "driver");
+ g->property = qemu_opt_get(opts, "property");
+ g->value = qemu_opt_get(opts, "value");
+ g->user_provided = true;
+ qdev_prop_register_global(g);
+ return 0;
+}
+
+void qemu_add_globals(void)
+{
+ qemu_opts_foreach(qemu_find_opts("global"),
+ qdev_add_one_global, NULL, NULL);
+}
diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c
new file mode 100644
index 00000000..04fd80a4
--- /dev/null
+++ b/hw/core/qdev-properties.c
@@ -0,0 +1,1113 @@
+#include "net/net.h"
+#include "hw/qdev.h"
+#include "qapi/qmp/qerror.h"
+#include "qemu/error-report.h"
+#include "sysemu/block-backend.h"
+#include "hw/block/block.h"
+#include "net/hub.h"
+#include "qapi/visitor.h"
+#include "sysemu/char.h"
+
+void qdev_prop_set_after_realize(DeviceState *dev, const char *name,
+ Error **errp)
+{
+ if (dev->id) {
+ error_setg(errp, "Attempt to set property '%s' on device '%s' "
+ "(type '%s') after it was realized", name, dev->id,
+ object_get_typename(OBJECT(dev)));
+ } else {
+ error_setg(errp, "Attempt to set property '%s' on anonymous device "
+ "(type '%s') after it was realized", name,
+ object_get_typename(OBJECT(dev)));
+ }
+}
+
+void qdev_prop_allow_set_link_before_realize(Object *obj, const char *name,
+ Object *val, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+
+ if (dev->realized) {
+ error_setg(errp, "Attempt to set link property '%s' on device '%s' "
+ "(type '%s') after it was realized",
+ name, dev->id, object_get_typename(obj));
+ }
+}
+
+void *qdev_get_prop_ptr(DeviceState *dev, Property *prop)
+{
+ void *ptr = dev;
+ ptr += prop->offset;
+ return ptr;
+}
+
+static void get_enum(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ int *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_enum(v, ptr, prop->info->enum_table,
+ prop->info->name, prop->name, errp);
+}
+
+static void set_enum(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ int *ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_enum(v, ptr, prop->info->enum_table,
+ prop->info->name, prop->name, errp);
+}
+
+/* Bit */
+
+static uint32_t qdev_get_prop_mask(Property *prop)
+{
+ assert(prop->info == &qdev_prop_bit);
+ return 0x1 << prop->bitnr;
+}
+
+static void bit_prop_set(DeviceState *dev, Property *props, bool val)
+{
+ uint32_t *p = qdev_get_prop_ptr(dev, props);
+ uint32_t mask = qdev_get_prop_mask(props);
+ if (val) {
+ *p |= mask;
+ } else {
+ *p &= ~mask;
+ }
+}
+
+static void prop_get_bit(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint32_t *p = qdev_get_prop_ptr(dev, prop);
+ bool value = (*p & qdev_get_prop_mask(prop)) != 0;
+
+ visit_type_bool(v, &value, name, errp);
+}
+
+static void prop_set_bit(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ Error *local_err = NULL;
+ bool value;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_bool(v, &value, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ bit_prop_set(dev, prop, value);
+}
+
+PropertyInfo qdev_prop_bit = {
+ .name = "bool",
+ .description = "on/off",
+ .get = prop_get_bit,
+ .set = prop_set_bit,
+};
+
+/* Bit64 */
+
+static uint64_t qdev_get_prop_mask64(Property *prop)
+{
+ assert(prop->info == &qdev_prop_bit64);
+ return 0x1ull << prop->bitnr;
+}
+
+static void bit64_prop_set(DeviceState *dev, Property *props, bool val)
+{
+ uint64_t *p = qdev_get_prop_ptr(dev, props);
+ uint64_t mask = qdev_get_prop_mask64(props);
+ if (val) {
+ *p |= mask;
+ } else {
+ *p &= ~mask;
+ }
+}
+
+static void prop_get_bit64(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint64_t *p = qdev_get_prop_ptr(dev, prop);
+ bool value = (*p & qdev_get_prop_mask64(prop)) != 0;
+
+ visit_type_bool(v, &value, name, errp);
+}
+
+static void prop_set_bit64(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ Error *local_err = NULL;
+ bool value;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_bool(v, &value, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ bit64_prop_set(dev, prop, value);
+}
+
+PropertyInfo qdev_prop_bit64 = {
+ .name = "bool",
+ .description = "on/off",
+ .get = prop_get_bit64,
+ .set = prop_set_bit64,
+};
+
+/* --- bool --- */
+
+static void get_bool(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ bool *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_bool(v, ptr, name, errp);
+}
+
+static void set_bool(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ bool *ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_bool(v, ptr, name, errp);
+}
+
+PropertyInfo qdev_prop_bool = {
+ .name = "bool",
+ .get = get_bool,
+ .set = set_bool,
+};
+
+/* --- 8bit integer --- */
+
+static void get_uint8(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint8_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_uint8(v, ptr, name, errp);
+}
+
+static void set_uint8(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint8_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_uint8(v, ptr, name, errp);
+}
+
+PropertyInfo qdev_prop_uint8 = {
+ .name = "uint8",
+ .get = get_uint8,
+ .set = set_uint8,
+};
+
+/* --- 16bit integer --- */
+
+static void get_uint16(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint16_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_uint16(v, ptr, name, errp);
+}
+
+static void set_uint16(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint16_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_uint16(v, ptr, name, errp);
+}
+
+PropertyInfo qdev_prop_uint16 = {
+ .name = "uint16",
+ .get = get_uint16,
+ .set = set_uint16,
+};
+
+/* --- 32bit integer --- */
+
+static void get_uint32(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint32_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_uint32(v, ptr, name, errp);
+}
+
+static void set_uint32(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint32_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_uint32(v, ptr, name, errp);
+}
+
+static void get_int32(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ int32_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_int32(v, ptr, name, errp);
+}
+
+static void set_int32(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ int32_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_int32(v, ptr, name, errp);
+}
+
+PropertyInfo qdev_prop_uint32 = {
+ .name = "uint32",
+ .get = get_uint32,
+ .set = set_uint32,
+};
+
+PropertyInfo qdev_prop_int32 = {
+ .name = "int32",
+ .get = get_int32,
+ .set = set_int32,
+};
+
+/* --- 64bit integer --- */
+
+static void get_uint64(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint64_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_uint64(v, ptr, name, errp);
+}
+
+static void set_uint64(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint64_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_uint64(v, ptr, name, errp);
+}
+
+PropertyInfo qdev_prop_uint64 = {
+ .name = "uint64",
+ .get = get_uint64,
+ .set = set_uint64,
+};
+
+/* --- string --- */
+
+static void release_string(Object *obj, const char *name, void *opaque)
+{
+ Property *prop = opaque;
+ g_free(*(char **)qdev_get_prop_ptr(DEVICE(obj), prop));
+}
+
+static void get_string(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ char **ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (!*ptr) {
+ char *str = (char *)"";
+ visit_type_str(v, &str, name, errp);
+ } else {
+ visit_type_str(v, ptr, name, errp);
+ }
+}
+
+static void set_string(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ char **ptr = qdev_get_prop_ptr(dev, prop);
+ Error *local_err = NULL;
+ char *str;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_str(v, &str, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ if (*ptr) {
+ g_free(*ptr);
+ }
+ *ptr = str;
+}
+
+PropertyInfo qdev_prop_string = {
+ .name = "str",
+ .release = release_string,
+ .get = get_string,
+ .set = set_string,
+};
+
+/* --- pointer --- */
+
+/* Not a proper property, just for dirty hacks. TODO Remove it! */
+PropertyInfo qdev_prop_ptr = {
+ .name = "ptr",
+};
+
+/* --- mac address --- */
+
+/*
+ * accepted syntax versions:
+ * 01:02:03:04:05:06
+ * 01-02-03-04-05-06
+ */
+static void get_mac(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ MACAddr *mac = qdev_get_prop_ptr(dev, prop);
+ char buffer[2 * 6 + 5 + 1];
+ char *p = buffer;
+
+ snprintf(buffer, sizeof(buffer), "%02x:%02x:%02x:%02x:%02x:%02x",
+ mac->a[0], mac->a[1], mac->a[2],
+ mac->a[3], mac->a[4], mac->a[5]);
+
+ visit_type_str(v, &p, name, errp);
+}
+
+static void set_mac(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ MACAddr *mac = qdev_get_prop_ptr(dev, prop);
+ Error *local_err = NULL;
+ int i, pos;
+ char *str, *p;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_str(v, &str, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ for (i = 0, pos = 0; i < 6; i++, pos += 3) {
+ if (!qemu_isxdigit(str[pos])) {
+ goto inval;
+ }
+ if (!qemu_isxdigit(str[pos+1])) {
+ goto inval;
+ }
+ if (i == 5) {
+ if (str[pos+2] != '\0') {
+ goto inval;
+ }
+ } else {
+ if (str[pos+2] != ':' && str[pos+2] != '-') {
+ goto inval;
+ }
+ }
+ mac->a[i] = strtol(str+pos, &p, 16);
+ }
+ g_free(str);
+ return;
+
+inval:
+ error_set_from_qdev_prop_error(errp, EINVAL, dev, prop, str);
+ g_free(str);
+}
+
+PropertyInfo qdev_prop_macaddr = {
+ .name = "str",
+ .description = "Ethernet 6-byte MAC Address, example: 52:54:00:12:34:56",
+ .get = get_mac,
+ .set = set_mac,
+};
+
+/* --- lost tick policy --- */
+
+QEMU_BUILD_BUG_ON(sizeof(LostTickPolicy) != sizeof(int));
+
+PropertyInfo qdev_prop_losttickpolicy = {
+ .name = "LostTickPolicy",
+ .enum_table = LostTickPolicy_lookup,
+ .get = get_enum,
+ .set = set_enum,
+};
+
+/* --- BIOS CHS translation */
+
+QEMU_BUILD_BUG_ON(sizeof(BiosAtaTranslation) != sizeof(int));
+
+PropertyInfo qdev_prop_bios_chs_trans = {
+ .name = "BiosAtaTranslation",
+ .description = "Logical CHS translation algorithm, "
+ "auto/none/lba/large/rechs",
+ .enum_table = BiosAtaTranslation_lookup,
+ .get = get_enum,
+ .set = set_enum,
+};
+
+/* --- pci address --- */
+
+/*
+ * bus-local address, i.e. "$slot" or "$slot.$fn"
+ */
+static void set_pci_devfn(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ int32_t value, *ptr = qdev_get_prop_ptr(dev, prop);
+ unsigned int slot, fn, n;
+ Error *local_err = NULL;
+ char *str;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_str(v, &str, name, &local_err);
+ if (local_err) {
+ error_free(local_err);
+ local_err = NULL;
+ visit_type_int32(v, &value, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ } else if (value < -1 || value > 255) {
+ error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+ name ? name : "null", "pci_devfn");
+ } else {
+ *ptr = value;
+ }
+ return;
+ }
+
+ if (sscanf(str, "%x.%x%n", &slot, &fn, &n) != 2) {
+ fn = 0;
+ if (sscanf(str, "%x%n", &slot, &n) != 1) {
+ goto invalid;
+ }
+ }
+ if (str[n] != '\0' || fn > 7 || slot > 31) {
+ goto invalid;
+ }
+ *ptr = slot << 3 | fn;
+ g_free(str);
+ return;
+
+invalid:
+ error_set_from_qdev_prop_error(errp, EINVAL, dev, prop, str);
+ g_free(str);
+}
+
+static int print_pci_devfn(DeviceState *dev, Property *prop, char *dest,
+ size_t len)
+{
+ int32_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ if (*ptr == -1) {
+ return snprintf(dest, len, "<unset>");
+ } else {
+ return snprintf(dest, len, "%02x.%x", *ptr >> 3, *ptr & 7);
+ }
+}
+
+PropertyInfo qdev_prop_pci_devfn = {
+ .name = "int32",
+ .description = "Slot and optional function number, example: 06.0 or 06",
+ .print = print_pci_devfn,
+ .get = get_int32,
+ .set = set_pci_devfn,
+};
+
+/* --- blocksize --- */
+
+static void set_blocksize(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint16_t value, *ptr = qdev_get_prop_ptr(dev, prop);
+ Error *local_err = NULL;
+ const int64_t min = 512;
+ const int64_t max = 32768;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_uint16(v, &value, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ /* value of 0 means "unset" */
+ if (value && (value < min || value > max)) {
+ error_setg(errp, QERR_PROPERTY_VALUE_OUT_OF_RANGE,
+ dev->id ? : "", name, (int64_t)value, min, max);
+ return;
+ }
+
+ /* We rely on power-of-2 blocksizes for bitmasks */
+ if ((value & (value - 1)) != 0) {
+ error_setg(errp,
+ "Property %s.%s doesn't take value '%" PRId64 "', it's not a power of 2",
+ dev->id ?: "", name, (int64_t)value);
+ return;
+ }
+
+ *ptr = value;
+}
+
+PropertyInfo qdev_prop_blocksize = {
+ .name = "uint16",
+ .description = "A power of two between 512 and 32768",
+ .get = get_uint16,
+ .set = set_blocksize,
+};
+
+/* --- pci host address --- */
+
+static void get_pci_host_devaddr(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ PCIHostDeviceAddress *addr = qdev_get_prop_ptr(dev, prop);
+ char buffer[] = "xxxx:xx:xx.x";
+ char *p = buffer;
+ int rc = 0;
+
+ rc = snprintf(buffer, sizeof(buffer), "%04x:%02x:%02x.%d",
+ addr->domain, addr->bus, addr->slot, addr->function);
+ assert(rc == sizeof(buffer) - 1);
+
+ visit_type_str(v, &p, name, errp);
+}
+
+/*
+ * Parse [<domain>:]<bus>:<slot>.<func>
+ * if <domain> is not supplied, it's assumed to be 0.
+ */
+static void set_pci_host_devaddr(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ PCIHostDeviceAddress *addr = qdev_get_prop_ptr(dev, prop);
+ Error *local_err = NULL;
+ char *str, *p;
+ char *e;
+ unsigned long val;
+ unsigned long dom = 0, bus = 0;
+ unsigned int slot = 0, func = 0;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+
+ visit_type_str(v, &str, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ p = str;
+ val = strtoul(p, &e, 16);
+ if (e == p || *e != ':') {
+ goto inval;
+ }
+ bus = val;
+
+ p = e + 1;
+ val = strtoul(p, &e, 16);
+ if (e == p) {
+ goto inval;
+ }
+ if (*e == ':') {
+ dom = bus;
+ bus = val;
+ p = e + 1;
+ val = strtoul(p, &e, 16);
+ if (e == p) {
+ goto inval;
+ }
+ }
+ slot = val;
+
+ if (*e != '.') {
+ goto inval;
+ }
+ p = e + 1;
+ val = strtoul(p, &e, 10);
+ if (e == p) {
+ goto inval;
+ }
+ func = val;
+
+ if (dom > 0xffff || bus > 0xff || slot > 0x1f || func > 7) {
+ goto inval;
+ }
+
+ if (*e) {
+ goto inval;
+ }
+
+ addr->domain = dom;
+ addr->bus = bus;
+ addr->slot = slot;
+ addr->function = func;
+
+ g_free(str);
+ return;
+
+inval:
+ error_set_from_qdev_prop_error(errp, EINVAL, dev, prop, str);
+ g_free(str);
+}
+
+PropertyInfo qdev_prop_pci_host_devaddr = {
+ .name = "str",
+ .description = "Address (bus/device/function) of "
+ "the host device, example: 04:10.0",
+ .get = get_pci_host_devaddr,
+ .set = set_pci_host_devaddr,
+};
+
+/* --- support for array properties --- */
+
+/* Used as an opaque for the object properties we add for each
+ * array element. Note that the struct Property must be first
+ * in the struct so that a pointer to this works as the opaque
+ * for the underlying element's property hooks as well as for
+ * our own release callback.
+ */
+typedef struct {
+ struct Property prop;
+ char *propname;
+ ObjectPropertyRelease *release;
+} ArrayElementProperty;
+
+/* object property release callback for array element properties:
+ * we call the underlying element's property release hook, and
+ * then free the memory we allocated when we added the property.
+ */
+static void array_element_release(Object *obj, const char *name, void *opaque)
+{
+ ArrayElementProperty *p = opaque;
+ if (p->release) {
+ p->release(obj, name, opaque);
+ }
+ g_free(p->propname);
+ g_free(p);
+}
+
+static void set_prop_arraylen(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ /* Setter for the property which defines the length of a
+ * variable-sized property array. As well as actually setting the
+ * array-length field in the device struct, we have to create the
+ * array itself and dynamically add the corresponding properties.
+ */
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint32_t *alenptr = qdev_get_prop_ptr(dev, prop);
+ void **arrayptr = (void *)dev + prop->arrayoffset;
+ Error *local_err = NULL;
+ void *eltptr;
+ const char *arrayname;
+ int i;
+
+ if (dev->realized) {
+ qdev_prop_set_after_realize(dev, name, errp);
+ return;
+ }
+ if (*alenptr) {
+ error_setg(errp, "array size property %s may not be set more than once",
+ name);
+ return;
+ }
+ visit_type_uint32(v, alenptr, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ if (!*alenptr) {
+ return;
+ }
+
+ /* DEFINE_PROP_ARRAY guarantees that name should start with this prefix;
+ * strip it off so we can get the name of the array itself.
+ */
+ assert(strncmp(name, PROP_ARRAY_LEN_PREFIX,
+ strlen(PROP_ARRAY_LEN_PREFIX)) == 0);
+ arrayname = name + strlen(PROP_ARRAY_LEN_PREFIX);
+
+ /* Note that it is the responsibility of the individual device's deinit
+ * to free the array proper.
+ */
+ *arrayptr = eltptr = g_malloc0(*alenptr * prop->arrayfieldsize);
+ for (i = 0; i < *alenptr; i++, eltptr += prop->arrayfieldsize) {
+ char *propname = g_strdup_printf("%s[%d]", arrayname, i);
+ ArrayElementProperty *arrayprop = g_new0(ArrayElementProperty, 1);
+ arrayprop->release = prop->arrayinfo->release;
+ arrayprop->propname = propname;
+ arrayprop->prop.info = prop->arrayinfo;
+ arrayprop->prop.name = propname;
+ /* This ugly piece of pointer arithmetic sets up the offset so
+ * that when the underlying get/set hooks call qdev_get_prop_ptr
+ * they get the right answer despite the array element not actually
+ * being inside the device struct.
+ */
+ arrayprop->prop.offset = eltptr - (void *)dev;
+ assert(qdev_get_prop_ptr(dev, &arrayprop->prop) == eltptr);
+ object_property_add(obj, propname,
+ arrayprop->prop.info->name,
+ arrayprop->prop.info->get,
+ arrayprop->prop.info->set,
+ array_element_release,
+ arrayprop, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ }
+}
+
+PropertyInfo qdev_prop_arraylen = {
+ .name = "uint32",
+ .get = get_uint32,
+ .set = set_prop_arraylen,
+};
+
+/* --- public helpers --- */
+
+static Property *qdev_prop_walk(Property *props, const char *name)
+{
+ if (!props) {
+ return NULL;
+ }
+ while (props->name) {
+ if (strcmp(props->name, name) == 0) {
+ return props;
+ }
+ props++;
+ }
+ return NULL;
+}
+
+static Property *qdev_prop_find(DeviceState *dev, const char *name)
+{
+ ObjectClass *class;
+ Property *prop;
+
+ /* device properties */
+ class = object_get_class(OBJECT(dev));
+ do {
+ prop = qdev_prop_walk(DEVICE_CLASS(class)->props, name);
+ if (prop) {
+ return prop;
+ }
+ class = object_class_get_parent(class);
+ } while (class != object_class_by_name(TYPE_DEVICE));
+
+ return NULL;
+}
+
+void error_set_from_qdev_prop_error(Error **errp, int ret, DeviceState *dev,
+ Property *prop, const char *value)
+{
+ switch (ret) {
+ case -EEXIST:
+ error_setg(errp, "Property '%s.%s' can't take value '%s', it's in use",
+ object_get_typename(OBJECT(dev)), prop->name, value);
+ break;
+ default:
+ case -EINVAL:
+ error_setg(errp, QERR_PROPERTY_VALUE_BAD,
+ object_get_typename(OBJECT(dev)), prop->name, value);
+ break;
+ case -ENOENT:
+ error_setg(errp, "Property '%s.%s' can't find value '%s'",
+ object_get_typename(OBJECT(dev)), prop->name, value);
+ break;
+ case 0:
+ break;
+ }
+}
+
+void qdev_prop_set_bit(DeviceState *dev, const char *name, bool value)
+{
+ object_property_set_bool(OBJECT(dev), value, name, &error_abort);
+}
+
+void qdev_prop_set_uint8(DeviceState *dev, const char *name, uint8_t value)
+{
+ object_property_set_int(OBJECT(dev), value, name, &error_abort);
+}
+
+void qdev_prop_set_uint16(DeviceState *dev, const char *name, uint16_t value)
+{
+ object_property_set_int(OBJECT(dev), value, name, &error_abort);
+}
+
+void qdev_prop_set_uint32(DeviceState *dev, const char *name, uint32_t value)
+{
+ object_property_set_int(OBJECT(dev), value, name, &error_abort);
+}
+
+void qdev_prop_set_int32(DeviceState *dev, const char *name, int32_t value)
+{
+ object_property_set_int(OBJECT(dev), value, name, &error_abort);
+}
+
+void qdev_prop_set_uint64(DeviceState *dev, const char *name, uint64_t value)
+{
+ object_property_set_int(OBJECT(dev), value, name, &error_abort);
+}
+
+void qdev_prop_set_string(DeviceState *dev, const char *name, const char *value)
+{
+ object_property_set_str(OBJECT(dev), value, name, &error_abort);
+}
+
+void qdev_prop_set_macaddr(DeviceState *dev, const char *name, uint8_t *value)
+{
+ char str[2 * 6 + 5 + 1];
+ snprintf(str, sizeof(str), "%02x:%02x:%02x:%02x:%02x:%02x",
+ value[0], value[1], value[2], value[3], value[4], value[5]);
+
+ object_property_set_str(OBJECT(dev), str, name, &error_abort);
+}
+
+void qdev_prop_set_enum(DeviceState *dev, const char *name, int value)
+{
+ Property *prop;
+
+ prop = qdev_prop_find(dev, name);
+ object_property_set_str(OBJECT(dev), prop->info->enum_table[value],
+ name, &error_abort);
+}
+
+void qdev_prop_set_ptr(DeviceState *dev, const char *name, void *value)
+{
+ Property *prop;
+ void **ptr;
+
+ prop = qdev_prop_find(dev, name);
+ assert(prop && prop->info == &qdev_prop_ptr);
+ ptr = qdev_get_prop_ptr(dev, prop);
+ *ptr = value;
+}
+
+static QTAILQ_HEAD(, GlobalProperty) global_props =
+ QTAILQ_HEAD_INITIALIZER(global_props);
+
+void qdev_prop_register_global(GlobalProperty *prop)
+{
+ QTAILQ_INSERT_TAIL(&global_props, prop, next);
+}
+
+void qdev_prop_register_global_list(GlobalProperty *props)
+{
+ int i;
+
+ for (i = 0; props[i].driver != NULL; i++) {
+ qdev_prop_register_global(props+i);
+ }
+}
+
+int qdev_prop_check_globals(void)
+{
+ GlobalProperty *prop;
+ int ret = 0;
+
+ QTAILQ_FOREACH(prop, &global_props, next) {
+ ObjectClass *oc;
+ DeviceClass *dc;
+ if (prop->used) {
+ continue;
+ }
+ if (!prop->user_provided) {
+ continue;
+ }
+ oc = object_class_by_name(prop->driver);
+ oc = object_class_dynamic_cast(oc, TYPE_DEVICE);
+ if (!oc) {
+ error_report("Warning: global %s.%s has invalid class name",
+ prop->driver, prop->property);
+ ret = 1;
+ continue;
+ }
+ dc = DEVICE_CLASS(oc);
+ if (!dc->hotpluggable && !prop->used) {
+ error_report("Warning: global %s.%s=%s not used",
+ prop->driver, prop->property, prop->value);
+ ret = 1;
+ continue;
+ }
+ }
+ return ret;
+}
+
+static void qdev_prop_set_globals_for_type(DeviceState *dev,
+ const char *typename)
+{
+ GlobalProperty *prop;
+
+ QTAILQ_FOREACH(prop, &global_props, next) {
+ Error *err = NULL;
+
+ if (strcmp(typename, prop->driver) != 0) {
+ continue;
+ }
+ prop->used = true;
+ object_property_parse(OBJECT(dev), prop->value, prop->property, &err);
+ if (err != NULL) {
+ assert(prop->user_provided);
+ error_report("Warning: global %s.%s=%s ignored (%s)",
+ prop->driver, prop->property, prop->value,
+ error_get_pretty(err));
+ error_free(err);
+ return;
+ }
+ }
+}
+
+void qdev_prop_set_globals(DeviceState *dev)
+{
+ ObjectClass *class = object_get_class(OBJECT(dev));
+
+ do {
+ qdev_prop_set_globals_for_type(dev, object_class_get_name(class));
+ class = object_class_get_parent(class);
+ } while (class);
+}
+
+/* --- 64bit unsigned int 'size' type --- */
+
+static void get_size(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint64_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_size(v, ptr, name, errp);
+}
+
+static void set_size(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+ uint64_t *ptr = qdev_get_prop_ptr(dev, prop);
+
+ visit_type_size(v, ptr, name, errp);
+}
+
+PropertyInfo qdev_prop_size = {
+ .name = "size",
+ .get = get_size,
+ .set = set_size,
+};
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
new file mode 100644
index 00000000..b2f404a7
--- /dev/null
+++ b/hw/core/qdev.c
@@ -0,0 +1,1349 @@
+/*
+ * Dynamic device configuration and creation.
+ *
+ * Copyright (c) 2009 CodeSourcery
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* The theory here is that it should be possible to create a machine without
+ knowledge of specific devices. Historically board init routines have
+ passed a bunch of arguments to each device, requiring the board know
+ exactly which device it is dealing with. This file provides an abstract
+ API for device configuration and initialization. Devices will generally
+ inherit from a particular bus (e.g. PCI or I2C) rather than
+ this API directly. */
+
+#include "hw/qdev.h"
+#include "hw/fw-path-provider.h"
+#include "sysemu/sysemu.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qerror.h"
+#include "qapi/visitor.h"
+#include "qapi/qmp/qjson.h"
+#include "qemu/error-report.h"
+#include "hw/hotplug.h"
+#include "hw/boards.h"
+#include "qapi-event.h"
+
+int qdev_hotplug = 0;
+static bool qdev_hot_added = false;
+static bool qdev_hot_removed = false;
+
+const VMStateDescription *qdev_get_vmsd(DeviceState *dev)
+{
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+ return dc->vmsd;
+}
+
+const char *qdev_fw_name(DeviceState *dev)
+{
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+
+ if (dc->fw_name) {
+ return dc->fw_name;
+ }
+
+ return object_get_typename(OBJECT(dev));
+}
+
+static void qdev_property_add_legacy(DeviceState *dev, Property *prop,
+ Error **errp);
+
+static void bus_remove_child(BusState *bus, DeviceState *child)
+{
+ BusChild *kid;
+
+ QTAILQ_FOREACH(kid, &bus->children, sibling) {
+ if (kid->child == child) {
+ char name[32];
+
+ snprintf(name, sizeof(name), "child[%d]", kid->index);
+ QTAILQ_REMOVE(&bus->children, kid, sibling);
+
+ /* This gives back ownership of kid->child back to us. */
+ object_property_del(OBJECT(bus), name, NULL);
+ object_unref(OBJECT(kid->child));
+ g_free(kid);
+ return;
+ }
+ }
+}
+
+static void bus_add_child(BusState *bus, DeviceState *child)
+{
+ char name[32];
+ BusChild *kid = g_malloc0(sizeof(*kid));
+
+ kid->index = bus->max_index++;
+ kid->child = child;
+ object_ref(OBJECT(kid->child));
+
+ QTAILQ_INSERT_HEAD(&bus->children, kid, sibling);
+
+ /* This transfers ownership of kid->child to the property. */
+ snprintf(name, sizeof(name), "child[%d]", kid->index);
+ object_property_add_link(OBJECT(bus), name,
+ object_get_typename(OBJECT(child)),
+ (Object **)&kid->child,
+ NULL, /* read-only property */
+ 0, /* return ownership on prop deletion */
+ NULL);
+}
+
+void qdev_set_parent_bus(DeviceState *dev, BusState *bus)
+{
+ dev->parent_bus = bus;
+ object_ref(OBJECT(bus));
+ bus_add_child(bus, dev);
+}
+
+static void qbus_set_hotplug_handler_internal(BusState *bus, Object *handler,
+ Error **errp)
+{
+
+ object_property_set_link(OBJECT(bus), OBJECT(handler),
+ QDEV_HOTPLUG_HANDLER_PROPERTY, errp);
+}
+
+void qbus_set_hotplug_handler(BusState *bus, DeviceState *handler, Error **errp)
+{
+ qbus_set_hotplug_handler_internal(bus, OBJECT(handler), errp);
+}
+
+void qbus_set_bus_hotplug_handler(BusState *bus, Error **errp)
+{
+ qbus_set_hotplug_handler_internal(bus, OBJECT(bus), errp);
+}
+
+/* Create a new device. This only initializes the device state
+ structure and allows properties to be set. The device still needs
+ to be realized. See qdev-core.h. */
+DeviceState *qdev_create(BusState *bus, const char *name)
+{
+ DeviceState *dev;
+
+ dev = qdev_try_create(bus, name);
+ if (!dev) {
+ if (bus) {
+ error_report("Unknown device '%s' for bus '%s'", name,
+ object_get_typename(OBJECT(bus)));
+ } else {
+ error_report("Unknown device '%s' for default sysbus", name);
+ }
+ abort();
+ }
+
+ return dev;
+}
+
+DeviceState *qdev_try_create(BusState *bus, const char *type)
+{
+ DeviceState *dev;
+
+ if (object_class_by_name(type) == NULL) {
+ return NULL;
+ }
+ dev = DEVICE(object_new(type));
+ if (!dev) {
+ return NULL;
+ }
+
+ if (!bus) {
+ bus = sysbus_get_default();
+ }
+
+ qdev_set_parent_bus(dev, bus);
+ object_unref(OBJECT(dev));
+ return dev;
+}
+
+static QTAILQ_HEAD(device_listeners, DeviceListener) device_listeners
+ = QTAILQ_HEAD_INITIALIZER(device_listeners);
+
+enum ListenerDirection { Forward, Reverse };
+
+#define DEVICE_LISTENER_CALL(_callback, _direction, _args...) \
+ do { \
+ DeviceListener *_listener; \
+ \
+ switch (_direction) { \
+ case Forward: \
+ QTAILQ_FOREACH(_listener, &device_listeners, link) { \
+ if (_listener->_callback) { \
+ _listener->_callback(_listener, ##_args); \
+ } \
+ } \
+ break; \
+ case Reverse: \
+ QTAILQ_FOREACH_REVERSE(_listener, &device_listeners, \
+ device_listeners, link) { \
+ if (_listener->_callback) { \
+ _listener->_callback(_listener, ##_args); \
+ } \
+ } \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static int device_listener_add(DeviceState *dev, void *opaque)
+{
+ DEVICE_LISTENER_CALL(realize, Forward, dev);
+
+ return 0;
+}
+
+void device_listener_register(DeviceListener *listener)
+{
+ QTAILQ_INSERT_TAIL(&device_listeners, listener, link);
+
+ qbus_walk_children(sysbus_get_default(), NULL, NULL, device_listener_add,
+ NULL, NULL);
+}
+
+void device_listener_unregister(DeviceListener *listener)
+{
+ QTAILQ_REMOVE(&device_listeners, listener, link);
+}
+
+static void device_realize(DeviceState *dev, Error **errp)
+{
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+
+ if (dc->init) {
+ int rc = dc->init(dev);
+ if (rc < 0) {
+ error_setg(errp, "Device initialization failed.");
+ return;
+ }
+ }
+}
+
+static void device_unrealize(DeviceState *dev, Error **errp)
+{
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+
+ if (dc->exit) {
+ int rc = dc->exit(dev);
+ if (rc < 0) {
+ error_setg(errp, "Device exit failed.");
+ return;
+ }
+ }
+}
+
+void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id,
+ int required_for_version)
+{
+ assert(!dev->realized);
+ dev->instance_id_alias = alias_id;
+ dev->alias_required_for_version = required_for_version;
+}
+
+HotplugHandler *qdev_get_hotplug_handler(DeviceState *dev)
+{
+ HotplugHandler *hotplug_ctrl = NULL;
+
+ if (dev->parent_bus && dev->parent_bus->hotplug_handler) {
+ hotplug_ctrl = dev->parent_bus->hotplug_handler;
+ } else if (object_dynamic_cast(qdev_get_machine(), TYPE_MACHINE)) {
+ MachineState *machine = MACHINE(qdev_get_machine());
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+
+ if (mc->get_hotplug_handler) {
+ hotplug_ctrl = mc->get_hotplug_handler(machine, dev);
+ }
+ }
+ return hotplug_ctrl;
+}
+
+void qdev_unplug(DeviceState *dev, Error **errp)
+{
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+ HotplugHandler *hotplug_ctrl;
+ HotplugHandlerClass *hdc;
+
+ if (dev->parent_bus && !qbus_is_hotpluggable(dev->parent_bus)) {
+ error_setg(errp, QERR_BUS_NO_HOTPLUG, dev->parent_bus->name);
+ return;
+ }
+
+ if (!dc->hotpluggable) {
+ error_setg(errp, QERR_DEVICE_NO_HOTPLUG,
+ object_get_typename(OBJECT(dev)));
+ return;
+ }
+
+ qdev_hot_removed = true;
+
+ hotplug_ctrl = qdev_get_hotplug_handler(dev);
+ /* hotpluggable device MUST have HotplugHandler, if it doesn't
+ * then something is very wrong with it */
+ g_assert(hotplug_ctrl);
+
+ /* If device supports async unplug just request it to be done,
+ * otherwise just remove it synchronously */
+ hdc = HOTPLUG_HANDLER_GET_CLASS(hotplug_ctrl);
+ if (hdc->unplug_request) {
+ hotplug_handler_unplug_request(hotplug_ctrl, dev, errp);
+ } else {
+ hotplug_handler_unplug(hotplug_ctrl, dev, errp);
+ }
+}
+
+static int qdev_reset_one(DeviceState *dev, void *opaque)
+{
+ device_reset(dev);
+
+ return 0;
+}
+
+static int qbus_reset_one(BusState *bus, void *opaque)
+{
+ BusClass *bc = BUS_GET_CLASS(bus);
+ if (bc->reset) {
+ bc->reset(bus);
+ }
+ return 0;
+}
+
+void qdev_reset_all(DeviceState *dev)
+{
+ qdev_walk_children(dev, NULL, NULL, qdev_reset_one, qbus_reset_one, NULL);
+}
+
+void qbus_reset_all(BusState *bus)
+{
+ qbus_walk_children(bus, NULL, NULL, qdev_reset_one, qbus_reset_one, NULL);
+}
+
+void qbus_reset_all_fn(void *opaque)
+{
+ BusState *bus = opaque;
+ qbus_reset_all(bus);
+}
+
+/* can be used as ->unplug() callback for the simple cases */
+void qdev_simple_device_unplug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ /* just zap it */
+ object_unparent(OBJECT(dev));
+}
+
+/*
+ * Realize @dev.
+ * Device properties should be set before calling this function. IRQs
+ * and MMIO regions should be connected/mapped after calling this
+ * function.
+ * On failure, report an error with error_report() and terminate the
+ * program. This is okay during machine creation. Don't use for
+ * hotplug, because there callers need to recover from failure.
+ * Exception: if you know the device's init() callback can't fail,
+ * then qdev_init_nofail() can't fail either, and is therefore usable
+ * even then. But relying on the device implementation that way is
+ * somewhat unclean, and best avoided.
+ */
+void qdev_init_nofail(DeviceState *dev)
+{
+ Error *err = NULL;
+
+ assert(!dev->realized);
+
+ object_property_set_bool(OBJECT(dev), true, "realized", &err);
+ if (err) {
+ error_report("Initialization of device %s failed: %s",
+ object_get_typename(OBJECT(dev)),
+ error_get_pretty(err));
+ exit(1);
+ }
+}
+
+void qdev_machine_creation_done(void)
+{
+ /*
+ * ok, initial machine setup is done, starting from now we can
+ * only create hotpluggable devices
+ */
+ qdev_hotplug = 1;
+}
+
+bool qdev_machine_modified(void)
+{
+ return qdev_hot_added || qdev_hot_removed;
+}
+
+BusState *qdev_get_parent_bus(DeviceState *dev)
+{
+ return dev->parent_bus;
+}
+
+static NamedGPIOList *qdev_get_named_gpio_list(DeviceState *dev,
+ const char *name)
+{
+ NamedGPIOList *ngl;
+
+ QLIST_FOREACH(ngl, &dev->gpios, node) {
+ /* NULL is a valid and matchable name, otherwise do a normal
+ * strcmp match.
+ */
+ if ((!ngl->name && !name) ||
+ (name && ngl->name && strcmp(name, ngl->name) == 0)) {
+ return ngl;
+ }
+ }
+
+ ngl = g_malloc0(sizeof(*ngl));
+ ngl->name = g_strdup(name);
+ QLIST_INSERT_HEAD(&dev->gpios, ngl, node);
+ return ngl;
+}
+
+void qdev_init_gpio_in_named(DeviceState *dev, qemu_irq_handler handler,
+ const char *name, int n)
+{
+ int i;
+ NamedGPIOList *gpio_list = qdev_get_named_gpio_list(dev, name);
+ char *propname = g_strdup_printf("%s[*]", name ? name : "unnamed-gpio-in");
+
+ assert(gpio_list->num_out == 0 || !name);
+ gpio_list->in = qemu_extend_irqs(gpio_list->in, gpio_list->num_in, handler,
+ dev, n);
+
+ for (i = gpio_list->num_in; i < gpio_list->num_in + n; i++) {
+ object_property_add_child(OBJECT(dev), propname,
+ OBJECT(gpio_list->in[i]), &error_abort);
+ }
+ g_free(propname);
+
+ gpio_list->num_in += n;
+}
+
+void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n)
+{
+ qdev_init_gpio_in_named(dev, handler, NULL, n);
+}
+
+void qdev_init_gpio_out_named(DeviceState *dev, qemu_irq *pins,
+ const char *name, int n)
+{
+ int i;
+ NamedGPIOList *gpio_list = qdev_get_named_gpio_list(dev, name);
+ char *propname = g_strdup_printf("%s[*]", name ? name : "unnamed-gpio-out");
+
+ assert(gpio_list->num_in == 0 || !name);
+ gpio_list->num_out += n;
+
+ for (i = 0; i < n; ++i) {
+ memset(&pins[i], 0, sizeof(*pins));
+ object_property_add_link(OBJECT(dev), propname, TYPE_IRQ,
+ (Object **)&pins[i],
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &error_abort);
+ }
+ g_free(propname);
+}
+
+void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n)
+{
+ qdev_init_gpio_out_named(dev, pins, NULL, n);
+}
+
+qemu_irq qdev_get_gpio_in_named(DeviceState *dev, const char *name, int n)
+{
+ NamedGPIOList *gpio_list = qdev_get_named_gpio_list(dev, name);
+
+ assert(n >= 0 && n < gpio_list->num_in);
+ return gpio_list->in[n];
+}
+
+qemu_irq qdev_get_gpio_in(DeviceState *dev, int n)
+{
+ return qdev_get_gpio_in_named(dev, NULL, n);
+}
+
+void qdev_connect_gpio_out_named(DeviceState *dev, const char *name, int n,
+ qemu_irq pin)
+{
+ char *propname = g_strdup_printf("%s[%d]",
+ name ? name : "unnamed-gpio-out", n);
+ if (pin) {
+ /* We need a name for object_property_set_link to work. If the
+ * object has a parent, object_property_add_child will come back
+ * with an error without doing anything. If it has none, it will
+ * never fail. So we can just call it with a NULL Error pointer.
+ */
+ object_property_add_child(container_get(qdev_get_machine(),
+ "/unattached"),
+ "non-qdev-gpio[*]", OBJECT(pin), NULL);
+ }
+ object_property_set_link(OBJECT(dev), OBJECT(pin), propname, &error_abort);
+ g_free(propname);
+}
+
+qemu_irq qdev_get_gpio_out_connector(DeviceState *dev, const char *name, int n)
+{
+ char *propname = g_strdup_printf("%s[%d]",
+ name ? name : "unnamed-gpio-out", n);
+
+ qemu_irq ret = (qemu_irq)object_property_get_link(OBJECT(dev), propname,
+ NULL);
+
+ return ret;
+}
+
+/* disconnect a GPIO ouput, returning the disconnected input (if any) */
+
+static qemu_irq qdev_disconnect_gpio_out_named(DeviceState *dev,
+ const char *name, int n)
+{
+ char *propname = g_strdup_printf("%s[%d]",
+ name ? name : "unnamed-gpio-out", n);
+
+ qemu_irq ret = (qemu_irq)object_property_get_link(OBJECT(dev), propname,
+ NULL);
+ if (ret) {
+ object_property_set_link(OBJECT(dev), NULL, propname, NULL);
+ }
+ g_free(propname);
+ return ret;
+}
+
+qemu_irq qdev_intercept_gpio_out(DeviceState *dev, qemu_irq icpt,
+ const char *name, int n)
+{
+ qemu_irq disconnected = qdev_disconnect_gpio_out_named(dev, name, n);
+ qdev_connect_gpio_out_named(dev, name, n, icpt);
+ return disconnected;
+}
+
+void qdev_connect_gpio_out(DeviceState * dev, int n, qemu_irq pin)
+{
+ qdev_connect_gpio_out_named(dev, NULL, n, pin);
+}
+
+void qdev_pass_gpios(DeviceState *dev, DeviceState *container,
+ const char *name)
+{
+ int i;
+ NamedGPIOList *ngl = qdev_get_named_gpio_list(dev, name);
+
+ for (i = 0; i < ngl->num_in; i++) {
+ const char *nm = ngl->name ? ngl->name : "unnamed-gpio-in";
+ char *propname = g_strdup_printf("%s[%d]", nm, i);
+
+ object_property_add_alias(OBJECT(container), propname,
+ OBJECT(dev), propname,
+ &error_abort);
+ g_free(propname);
+ }
+ for (i = 0; i < ngl->num_out; i++) {
+ const char *nm = ngl->name ? ngl->name : "unnamed-gpio-out";
+ char *propname = g_strdup_printf("%s[%d]", nm, i);
+
+ object_property_add_alias(OBJECT(container), propname,
+ OBJECT(dev), propname,
+ &error_abort);
+ g_free(propname);
+ }
+ QLIST_REMOVE(ngl, node);
+ QLIST_INSERT_HEAD(&container->gpios, ngl, node);
+}
+
+BusState *qdev_get_child_bus(DeviceState *dev, const char *name)
+{
+ BusState *bus;
+
+ QLIST_FOREACH(bus, &dev->child_bus, sibling) {
+ if (strcmp(name, bus->name) == 0) {
+ return bus;
+ }
+ }
+ return NULL;
+}
+
+int qbus_walk_children(BusState *bus,
+ qdev_walkerfn *pre_devfn, qbus_walkerfn *pre_busfn,
+ qdev_walkerfn *post_devfn, qbus_walkerfn *post_busfn,
+ void *opaque)
+{
+ BusChild *kid;
+ int err;
+
+ if (pre_busfn) {
+ err = pre_busfn(bus, opaque);
+ if (err) {
+ return err;
+ }
+ }
+
+ QTAILQ_FOREACH(kid, &bus->children, sibling) {
+ err = qdev_walk_children(kid->child,
+ pre_devfn, pre_busfn,
+ post_devfn, post_busfn, opaque);
+ if (err < 0) {
+ return err;
+ }
+ }
+
+ if (post_busfn) {
+ err = post_busfn(bus, opaque);
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int qdev_walk_children(DeviceState *dev,
+ qdev_walkerfn *pre_devfn, qbus_walkerfn *pre_busfn,
+ qdev_walkerfn *post_devfn, qbus_walkerfn *post_busfn,
+ void *opaque)
+{
+ BusState *bus;
+ int err;
+
+ if (pre_devfn) {
+ err = pre_devfn(dev, opaque);
+ if (err) {
+ return err;
+ }
+ }
+
+ QLIST_FOREACH(bus, &dev->child_bus, sibling) {
+ err = qbus_walk_children(bus, pre_devfn, pre_busfn,
+ post_devfn, post_busfn, opaque);
+ if (err < 0) {
+ return err;
+ }
+ }
+
+ if (post_devfn) {
+ err = post_devfn(dev, opaque);
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+DeviceState *qdev_find_recursive(BusState *bus, const char *id)
+{
+ BusChild *kid;
+ DeviceState *ret;
+ BusState *child;
+
+ QTAILQ_FOREACH(kid, &bus->children, sibling) {
+ DeviceState *dev = kid->child;
+
+ if (dev->id && strcmp(dev->id, id) == 0) {
+ return dev;
+ }
+
+ QLIST_FOREACH(child, &dev->child_bus, sibling) {
+ ret = qdev_find_recursive(child, id);
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+ return NULL;
+}
+
+static void qbus_realize(BusState *bus, DeviceState *parent, const char *name)
+{
+ const char *typename = object_get_typename(OBJECT(bus));
+ BusClass *bc;
+ char *buf;
+ int i, len, bus_id;
+
+ bus->parent = parent;
+
+ if (name) {
+ bus->name = g_strdup(name);
+ } else if (bus->parent && bus->parent->id) {
+ /* parent device has id -> use it plus parent-bus-id for bus name */
+ bus_id = bus->parent->num_child_bus;
+
+ len = strlen(bus->parent->id) + 16;
+ buf = g_malloc(len);
+ snprintf(buf, len, "%s.%d", bus->parent->id, bus_id);
+ bus->name = buf;
+ } else {
+ /* no id -> use lowercase bus type plus global bus-id for bus name */
+ bc = BUS_GET_CLASS(bus);
+ bus_id = bc->automatic_ids++;
+
+ len = strlen(typename) + 16;
+ buf = g_malloc(len);
+ len = snprintf(buf, len, "%s.%d", typename, bus_id);
+ for (i = 0; i < len; i++) {
+ buf[i] = qemu_tolower(buf[i]);
+ }
+ bus->name = buf;
+ }
+
+ if (bus->parent) {
+ QLIST_INSERT_HEAD(&bus->parent->child_bus, bus, sibling);
+ bus->parent->num_child_bus++;
+ object_property_add_child(OBJECT(bus->parent), bus->name, OBJECT(bus), NULL);
+ object_unref(OBJECT(bus));
+ } else if (bus != sysbus_get_default()) {
+ /* TODO: once all bus devices are qdevified,
+ only reset handler for main_system_bus should be registered here. */
+ qemu_register_reset(qbus_reset_all_fn, bus);
+ }
+}
+
+static void bus_unparent(Object *obj)
+{
+ BusState *bus = BUS(obj);
+ BusChild *kid;
+
+ while ((kid = QTAILQ_FIRST(&bus->children)) != NULL) {
+ DeviceState *dev = kid->child;
+ object_unparent(OBJECT(dev));
+ }
+ if (bus->parent) {
+ QLIST_REMOVE(bus, sibling);
+ bus->parent->num_child_bus--;
+ bus->parent = NULL;
+ } else {
+ assert(bus != sysbus_get_default()); /* main_system_bus is never freed */
+ qemu_unregister_reset(qbus_reset_all_fn, bus);
+ }
+}
+
+static bool bus_get_realized(Object *obj, Error **errp)
+{
+ BusState *bus = BUS(obj);
+
+ return bus->realized;
+}
+
+static void bus_set_realized(Object *obj, bool value, Error **errp)
+{
+ BusState *bus = BUS(obj);
+ BusClass *bc = BUS_GET_CLASS(bus);
+ BusChild *kid;
+ Error *local_err = NULL;
+
+ if (value && !bus->realized) {
+ if (bc->realize) {
+ bc->realize(bus, &local_err);
+ }
+
+ /* TODO: recursive realization */
+ } else if (!value && bus->realized) {
+ QTAILQ_FOREACH(kid, &bus->children, sibling) {
+ DeviceState *dev = kid->child;
+ object_property_set_bool(OBJECT(dev), false, "realized",
+ &local_err);
+ if (local_err != NULL) {
+ break;
+ }
+ }
+ if (bc->unrealize && local_err == NULL) {
+ bc->unrealize(bus, &local_err);
+ }
+ }
+
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ bus->realized = value;
+}
+
+void qbus_create_inplace(void *bus, size_t size, const char *typename,
+ DeviceState *parent, const char *name)
+{
+ object_initialize(bus, size, typename);
+ qbus_realize(bus, parent, name);
+}
+
+BusState *qbus_create(const char *typename, DeviceState *parent, const char *name)
+{
+ BusState *bus;
+
+ bus = BUS(object_new(typename));
+ qbus_realize(bus, parent, name);
+
+ return bus;
+}
+
+static char *bus_get_fw_dev_path(BusState *bus, DeviceState *dev)
+{
+ BusClass *bc = BUS_GET_CLASS(bus);
+
+ if (bc->get_fw_dev_path) {
+ return bc->get_fw_dev_path(dev);
+ }
+
+ return NULL;
+}
+
+static char *qdev_get_fw_dev_path_from_handler(BusState *bus, DeviceState *dev)
+{
+ Object *obj = OBJECT(dev);
+ char *d = NULL;
+
+ while (!d && obj->parent) {
+ obj = obj->parent;
+ d = fw_path_provider_try_get_dev_path(obj, bus, dev);
+ }
+ return d;
+}
+
+char *qdev_get_own_fw_dev_path_from_handler(BusState *bus, DeviceState *dev)
+{
+ Object *obj = OBJECT(dev);
+
+ return fw_path_provider_try_get_dev_path(obj, bus, dev);
+}
+
+static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
+{
+ int l = 0;
+
+ if (dev && dev->parent_bus) {
+ char *d;
+ l = qdev_get_fw_dev_path_helper(dev->parent_bus->parent, p, size);
+ d = qdev_get_fw_dev_path_from_handler(dev->parent_bus, dev);
+ if (!d) {
+ d = bus_get_fw_dev_path(dev->parent_bus, dev);
+ }
+ if (d) {
+ l += snprintf(p + l, size - l, "%s", d);
+ g_free(d);
+ } else {
+ return l;
+ }
+ }
+ l += snprintf(p + l , size - l, "/");
+
+ return l;
+}
+
+char* qdev_get_fw_dev_path(DeviceState *dev)
+{
+ char path[128];
+ int l;
+
+ l = qdev_get_fw_dev_path_helper(dev, path, 128);
+
+ path[l-1] = '\0';
+
+ return g_strdup(path);
+}
+
+char *qdev_get_dev_path(DeviceState *dev)
+{
+ BusClass *bc;
+
+ if (!dev || !dev->parent_bus) {
+ return NULL;
+ }
+
+ bc = BUS_GET_CLASS(dev->parent_bus);
+ if (bc->get_dev_path) {
+ return bc->get_dev_path(dev);
+ }
+
+ return NULL;
+}
+
+/**
+ * Legacy property handling
+ */
+
+static void qdev_get_legacy_property(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ Property *prop = opaque;
+
+ char buffer[1024];
+ char *ptr = buffer;
+
+ prop->info->print(dev, prop, buffer, sizeof(buffer));
+ visit_type_str(v, &ptr, name, errp);
+}
+
+/**
+ * @qdev_add_legacy_property - adds a legacy property
+ *
+ * Do not use this is new code! Properties added through this interface will
+ * be given names and types in the "legacy" namespace.
+ *
+ * Legacy properties are string versions of other OOM properties. The format
+ * of the string depends on the property type.
+ */
+static void qdev_property_add_legacy(DeviceState *dev, Property *prop,
+ Error **errp)
+{
+ gchar *name;
+
+ /* Register pointer properties as legacy properties */
+ if (!prop->info->print && prop->info->get) {
+ return;
+ }
+
+ name = g_strdup_printf("legacy-%s", prop->name);
+ object_property_add(OBJECT(dev), name, "str",
+ prop->info->print ? qdev_get_legacy_property : prop->info->get,
+ NULL,
+ NULL,
+ prop, errp);
+
+ g_free(name);
+}
+
+/**
+ * @qdev_property_add_static - add a @Property to a device.
+ *
+ * Static properties access data in a struct. The actual type of the
+ * property and the field depends on the property type.
+ */
+void qdev_property_add_static(DeviceState *dev, Property *prop,
+ Error **errp)
+{
+ Error *local_err = NULL;
+ Object *obj = OBJECT(dev);
+
+ /*
+ * TODO qdev_prop_ptr does not have getters or setters. It must
+ * go now that it can be replaced with links. The test should be
+ * removed along with it: all static properties are read/write.
+ */
+ if (!prop->info->get && !prop->info->set) {
+ return;
+ }
+
+ object_property_add(obj, prop->name, prop->info->name,
+ prop->info->get, prop->info->set,
+ prop->info->release,
+ prop, &local_err);
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ object_property_set_description(obj, prop->name,
+ prop->info->description,
+ &error_abort);
+
+ if (prop->qtype == QTYPE_NONE) {
+ return;
+ }
+
+ if (prop->qtype == QTYPE_QBOOL) {
+ object_property_set_bool(obj, prop->defval, prop->name, &error_abort);
+ } else if (prop->info->enum_table) {
+ object_property_set_str(obj, prop->info->enum_table[prop->defval],
+ prop->name, &error_abort);
+ } else if (prop->qtype == QTYPE_QINT) {
+ object_property_set_int(obj, prop->defval, prop->name, &error_abort);
+ }
+}
+
+/* @qdev_alias_all_properties - Add alias properties to the source object for
+ * all qdev properties on the target DeviceState.
+ */
+void qdev_alias_all_properties(DeviceState *target, Object *source)
+{
+ ObjectClass *class;
+ Property *prop;
+
+ class = object_get_class(OBJECT(target));
+ do {
+ DeviceClass *dc = DEVICE_CLASS(class);
+
+ for (prop = dc->props; prop && prop->name; prop++) {
+ object_property_add_alias(source, prop->name,
+ OBJECT(target), prop->name,
+ &error_abort);
+ }
+ class = object_class_get_parent(class);
+ } while (class != object_class_by_name(TYPE_DEVICE));
+}
+
+static int qdev_add_hotpluggable_device(Object *obj, void *opaque)
+{
+ GSList **list = opaque;
+ DeviceState *dev = (DeviceState *)object_dynamic_cast(OBJECT(obj),
+ TYPE_DEVICE);
+
+ if (dev == NULL) {
+ return 0;
+ }
+
+ if (dev->realized && object_property_get_bool(obj, "hotpluggable", NULL)) {
+ *list = g_slist_append(*list, dev);
+ }
+
+ return 0;
+}
+
+GSList *qdev_build_hotpluggable_device_list(Object *peripheral)
+{
+ GSList *list = NULL;
+
+ object_child_foreach(peripheral, qdev_add_hotpluggable_device, &list);
+
+ return list;
+}
+
+static bool device_get_realized(Object *obj, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ return dev->realized;
+}
+
+static void device_set_realized(Object *obj, bool value, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+ HotplugHandler *hotplug_ctrl;
+ BusState *bus;
+ Error *local_err = NULL;
+
+ if (dev->hotplugged && !dc->hotpluggable) {
+ error_setg(errp, QERR_DEVICE_NO_HOTPLUG, object_get_typename(obj));
+ return;
+ }
+
+ if (value && !dev->realized) {
+ if (!obj->parent) {
+ static int unattached_count;
+ gchar *name = g_strdup_printf("device[%d]", unattached_count++);
+
+ object_property_add_child(container_get(qdev_get_machine(),
+ "/unattached"),
+ name, obj, &error_abort);
+ g_free(name);
+ }
+
+ if (dc->realize) {
+ dc->realize(dev, &local_err);
+ }
+
+ if (local_err != NULL) {
+ goto fail;
+ }
+
+ DEVICE_LISTENER_CALL(realize, Forward, dev);
+
+ hotplug_ctrl = qdev_get_hotplug_handler(dev);
+ if (hotplug_ctrl) {
+ hotplug_handler_plug(hotplug_ctrl, dev, &local_err);
+ }
+
+ if (local_err != NULL) {
+ goto post_realize_fail;
+ }
+
+ if (qdev_get_vmsd(dev)) {
+ vmstate_register_with_alias_id(dev, -1, qdev_get_vmsd(dev), dev,
+ dev->instance_id_alias,
+ dev->alias_required_for_version);
+ }
+
+ QLIST_FOREACH(bus, &dev->child_bus, sibling) {
+ object_property_set_bool(OBJECT(bus), true, "realized",
+ &local_err);
+ if (local_err != NULL) {
+ goto child_realize_fail;
+ }
+ }
+ if (dev->hotplugged) {
+ device_reset(dev);
+ }
+ dev->pending_deleted_event = false;
+ } else if (!value && dev->realized) {
+ Error **local_errp = NULL;
+ QLIST_FOREACH(bus, &dev->child_bus, sibling) {
+ local_errp = local_err ? NULL : &local_err;
+ object_property_set_bool(OBJECT(bus), false, "realized",
+ local_errp);
+ }
+ if (qdev_get_vmsd(dev)) {
+ vmstate_unregister(dev, qdev_get_vmsd(dev), dev);
+ }
+ if (dc->unrealize) {
+ local_errp = local_err ? NULL : &local_err;
+ dc->unrealize(dev, local_errp);
+ }
+ dev->pending_deleted_event = true;
+ DEVICE_LISTENER_CALL(unrealize, Reverse, dev);
+ }
+
+ if (local_err != NULL) {
+ goto fail;
+ }
+
+ dev->realized = value;
+ return;
+
+child_realize_fail:
+ QLIST_FOREACH(bus, &dev->child_bus, sibling) {
+ object_property_set_bool(OBJECT(bus), false, "realized",
+ NULL);
+ }
+
+ if (qdev_get_vmsd(dev)) {
+ vmstate_unregister(dev, qdev_get_vmsd(dev), dev);
+ }
+
+post_realize_fail:
+ if (dc->unrealize) {
+ dc->unrealize(dev, NULL);
+ }
+
+fail:
+ error_propagate(errp, local_err);
+ return;
+}
+
+static bool device_get_hotpluggable(Object *obj, Error **errp)
+{
+ DeviceClass *dc = DEVICE_GET_CLASS(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ return dc->hotpluggable && (dev->parent_bus == NULL ||
+ qbus_is_hotpluggable(dev->parent_bus));
+}
+
+static bool device_get_hotplugged(Object *obj, Error **err)
+{
+ DeviceState *dev = DEVICE(obj);
+
+ return dev->hotplugged;
+}
+
+static void device_set_hotplugged(Object *obj, bool value, Error **err)
+{
+ DeviceState *dev = DEVICE(obj);
+
+ dev->hotplugged = value;
+}
+
+static void device_initfn(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ ObjectClass *class;
+ Property *prop;
+
+ if (qdev_hotplug) {
+ dev->hotplugged = 1;
+ qdev_hot_added = true;
+ }
+
+ dev->instance_id_alias = -1;
+ dev->realized = false;
+
+ object_property_add_bool(obj, "realized",
+ device_get_realized, device_set_realized, NULL);
+ object_property_add_bool(obj, "hotpluggable",
+ device_get_hotpluggable, NULL, NULL);
+ object_property_add_bool(obj, "hotplugged",
+ device_get_hotplugged, device_set_hotplugged,
+ &error_abort);
+
+ class = object_get_class(OBJECT(dev));
+ do {
+ for (prop = DEVICE_CLASS(class)->props; prop && prop->name; prop++) {
+ qdev_property_add_legacy(dev, prop, &error_abort);
+ qdev_property_add_static(dev, prop, &error_abort);
+ }
+ class = object_class_get_parent(class);
+ } while (class != object_class_by_name(TYPE_DEVICE));
+
+ object_property_add_link(OBJECT(dev), "parent_bus", TYPE_BUS,
+ (Object **)&dev->parent_bus, NULL, 0,
+ &error_abort);
+ QLIST_INIT(&dev->gpios);
+}
+
+static void device_post_init(Object *obj)
+{
+ qdev_prop_set_globals(DEVICE(obj));
+}
+
+/* Unlink device from bus and free the structure. */
+static void device_finalize(Object *obj)
+{
+ NamedGPIOList *ngl, *next;
+
+ DeviceState *dev = DEVICE(obj);
+ qemu_opts_del(dev->opts);
+
+ QLIST_FOREACH_SAFE(ngl, &dev->gpios, node, next) {
+ QLIST_REMOVE(ngl, node);
+ qemu_free_irqs(ngl->in, ngl->num_in);
+ g_free(ngl->name);
+ g_free(ngl);
+ /* ngl->out irqs are owned by the other end and should not be freed
+ * here
+ */
+ }
+}
+
+static void device_class_base_init(ObjectClass *class, void *data)
+{
+ DeviceClass *klass = DEVICE_CLASS(class);
+
+ /* We explicitly look up properties in the superclasses,
+ * so do not propagate them to the subclasses.
+ */
+ klass->props = NULL;
+}
+
+static void device_unparent(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ BusState *bus;
+
+ if (dev->realized) {
+ object_property_set_bool(obj, false, "realized", NULL);
+ }
+ while (dev->num_child_bus) {
+ bus = QLIST_FIRST(&dev->child_bus);
+ object_unparent(OBJECT(bus));
+ }
+ if (dev->parent_bus) {
+ bus_remove_child(dev->parent_bus, dev);
+ object_unref(OBJECT(dev->parent_bus));
+ dev->parent_bus = NULL;
+ }
+
+ /* Only send event if the device had been completely realized */
+ if (dev->pending_deleted_event) {
+ gchar *path = object_get_canonical_path(OBJECT(dev));
+
+ qapi_event_send_device_deleted(!!dev->id, dev->id, path, &error_abort);
+ g_free(path);
+ }
+}
+
+static void device_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+
+ class->unparent = device_unparent;
+ dc->realize = device_realize;
+ dc->unrealize = device_unrealize;
+
+ /* by default all devices were considered as hotpluggable,
+ * so with intent to check it in generic qdev_unplug() /
+ * device_set_realized() functions make every device
+ * hotpluggable. Devices that shouldn't be hotpluggable,
+ * should override it in their class_init()
+ */
+ dc->hotpluggable = true;
+}
+
+void device_reset(DeviceState *dev)
+{
+ DeviceClass *klass = DEVICE_GET_CLASS(dev);
+
+ if (klass->reset) {
+ klass->reset(dev);
+ }
+}
+
+Object *qdev_get_machine(void)
+{
+ static Object *dev;
+
+ if (dev == NULL) {
+ dev = container_get(object_get_root(), "/machine");
+ }
+
+ return dev;
+}
+
+static const TypeInfo device_type_info = {
+ .name = TYPE_DEVICE,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(DeviceState),
+ .instance_init = device_initfn,
+ .instance_post_init = device_post_init,
+ .instance_finalize = device_finalize,
+ .class_base_init = device_class_base_init,
+ .class_init = device_class_init,
+ .abstract = true,
+ .class_size = sizeof(DeviceClass),
+};
+
+static void qbus_initfn(Object *obj)
+{
+ BusState *bus = BUS(obj);
+
+ QTAILQ_INIT(&bus->children);
+ object_property_add_link(obj, QDEV_HOTPLUG_HANDLER_PROPERTY,
+ TYPE_HOTPLUG_HANDLER,
+ (Object **)&bus->hotplug_handler,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ NULL);
+ object_property_add_bool(obj, "realized",
+ bus_get_realized, bus_set_realized, NULL);
+}
+
+static char *default_bus_get_fw_dev_path(DeviceState *dev)
+{
+ return g_strdup(object_get_typename(OBJECT(dev)));
+}
+
+static void bus_class_init(ObjectClass *class, void *data)
+{
+ BusClass *bc = BUS_CLASS(class);
+
+ class->unparent = bus_unparent;
+ bc->get_fw_dev_path = default_bus_get_fw_dev_path;
+}
+
+static void qbus_finalize(Object *obj)
+{
+ BusState *bus = BUS(obj);
+
+ g_free((char *)bus->name);
+}
+
+static const TypeInfo bus_info = {
+ .name = TYPE_BUS,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(BusState),
+ .abstract = true,
+ .class_size = sizeof(BusClass),
+ .instance_init = qbus_initfn,
+ .instance_finalize = qbus_finalize,
+ .class_init = bus_class_init,
+};
+
+static void qdev_register_types(void)
+{
+ type_register_static(&bus_info);
+ type_register_static(&device_type_info);
+}
+
+type_init(qdev_register_types)
diff --git a/hw/core/stream.c b/hw/core/stream.c
new file mode 100644
index 00000000..e6a05a54
--- /dev/null
+++ b/hw/core/stream.c
@@ -0,0 +1,32 @@
+#include "hw/stream.h"
+
+size_t
+stream_push(StreamSlave *sink, uint8_t *buf, size_t len)
+{
+ StreamSlaveClass *k = STREAM_SLAVE_GET_CLASS(sink);
+
+ return k->push(sink, buf, len);
+}
+
+bool
+stream_can_push(StreamSlave *sink, StreamCanPushNotifyFn notify,
+ void *notify_opaque)
+{
+ StreamSlaveClass *k = STREAM_SLAVE_GET_CLASS(sink);
+
+ return k->can_push ? k->can_push(sink, notify, notify_opaque) : true;
+}
+
+static const TypeInfo stream_slave_info = {
+ .name = TYPE_STREAM_SLAVE,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(StreamSlaveClass),
+};
+
+
+static void stream_slave_register_types(void)
+{
+ type_register_static(&stream_slave_info);
+}
+
+type_init(stream_slave_register_types)
diff --git a/hw/core/sysbus.c b/hw/core/sysbus.c
new file mode 100644
index 00000000..3c586298
--- /dev/null
+++ b/hw/core/sysbus.c
@@ -0,0 +1,369 @@
+/*
+ * System (CPU) Bus device support code
+ *
+ * Copyright (c) 2009 CodeSourcery
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "monitor/monitor.h"
+#include "exec/address-spaces.h"
+
+static void sysbus_dev_print(Monitor *mon, DeviceState *dev, int indent);
+static char *sysbus_get_fw_dev_path(DeviceState *dev);
+
+typedef struct SysBusFind {
+ void *opaque;
+ FindSysbusDeviceFunc *func;
+} SysBusFind;
+
+/* Run func() for every sysbus device, traverse the tree for everything else */
+static int find_sysbus_device(Object *obj, void *opaque)
+{
+ SysBusFind *find = opaque;
+ Object *dev;
+ SysBusDevice *sbdev;
+
+ dev = object_dynamic_cast(obj, TYPE_SYS_BUS_DEVICE);
+ sbdev = (SysBusDevice *)dev;
+
+ if (!sbdev) {
+ /* Container, traverse it for children */
+ return object_child_foreach(obj, find_sysbus_device, opaque);
+ }
+
+ find->func(sbdev, find->opaque);
+
+ return 0;
+}
+
+/*
+ * Loop through all dynamically created sysbus devices and call
+ * func() for each instance.
+ */
+void foreach_dynamic_sysbus_device(FindSysbusDeviceFunc *func, void *opaque)
+{
+ Object *container;
+ SysBusFind find = {
+ .func = func,
+ .opaque = opaque,
+ };
+
+ /* Loop through all sysbus devices that were spawened outside the machine */
+ container = container_get(qdev_get_machine(), "/peripheral");
+ find_sysbus_device(container, &find);
+ container = container_get(qdev_get_machine(), "/peripheral-anon");
+ find_sysbus_device(container, &find);
+}
+
+
+static void system_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+
+ k->print_dev = sysbus_dev_print;
+ k->get_fw_dev_path = sysbus_get_fw_dev_path;
+}
+
+static const TypeInfo system_bus_info = {
+ .name = TYPE_SYSTEM_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(BusState),
+ .class_init = system_bus_class_init,
+};
+
+/* Check whether an IRQ source exists */
+bool sysbus_has_irq(SysBusDevice *dev, int n)
+{
+ char *prop = g_strdup_printf("%s[%d]", SYSBUS_DEVICE_GPIO_IRQ, n);
+ ObjectProperty *r;
+
+ r = object_property_find(OBJECT(dev), prop, NULL);
+ g_free(prop);
+
+ return (r != NULL);
+}
+
+bool sysbus_is_irq_connected(SysBusDevice *dev, int n)
+{
+ return !!sysbus_get_connected_irq(dev, n);
+}
+
+qemu_irq sysbus_get_connected_irq(SysBusDevice *dev, int n)
+{
+ DeviceState *d = DEVICE(dev);
+ return qdev_get_gpio_out_connector(d, SYSBUS_DEVICE_GPIO_IRQ, n);
+}
+
+void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq)
+{
+ SysBusDeviceClass *sbd = SYS_BUS_DEVICE_GET_CLASS(dev);
+
+ qdev_connect_gpio_out_named(DEVICE(dev), SYSBUS_DEVICE_GPIO_IRQ, n, irq);
+
+ if (sbd->connect_irq_notifier) {
+ sbd->connect_irq_notifier(dev, irq);
+ }
+}
+
+/* Check whether an MMIO region exists */
+bool sysbus_has_mmio(SysBusDevice *dev, unsigned int n)
+{
+ return (n < dev->num_mmio);
+}
+
+static void sysbus_mmio_map_common(SysBusDevice *dev, int n, hwaddr addr,
+ bool may_overlap, int priority)
+{
+ assert(n >= 0 && n < dev->num_mmio);
+
+ if (dev->mmio[n].addr == addr) {
+ /* ??? region already mapped here. */
+ return;
+ }
+ if (dev->mmio[n].addr != (hwaddr)-1) {
+ /* Unregister previous mapping. */
+ memory_region_del_subregion(get_system_memory(), dev->mmio[n].memory);
+ }
+ dev->mmio[n].addr = addr;
+ if (may_overlap) {
+ memory_region_add_subregion_overlap(get_system_memory(),
+ addr,
+ dev->mmio[n].memory,
+ priority);
+ }
+ else {
+ memory_region_add_subregion(get_system_memory(),
+ addr,
+ dev->mmio[n].memory);
+ }
+}
+
+void sysbus_mmio_map(SysBusDevice *dev, int n, hwaddr addr)
+{
+ sysbus_mmio_map_common(dev, n, addr, false, 0);
+}
+
+void sysbus_mmio_map_overlap(SysBusDevice *dev, int n, hwaddr addr,
+ int priority)
+{
+ sysbus_mmio_map_common(dev, n, addr, true, priority);
+}
+
+/* Request an IRQ source. The actual IRQ object may be populated later. */
+void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p)
+{
+ qdev_init_gpio_out_named(DEVICE(dev), p, SYSBUS_DEVICE_GPIO_IRQ, 1);
+}
+
+/* Pass IRQs from a target device. */
+void sysbus_pass_irq(SysBusDevice *dev, SysBusDevice *target)
+{
+ qdev_pass_gpios(DEVICE(target), DEVICE(dev), SYSBUS_DEVICE_GPIO_IRQ);
+}
+
+void sysbus_init_mmio(SysBusDevice *dev, MemoryRegion *memory)
+{
+ int n;
+
+ assert(dev->num_mmio < QDEV_MAX_MMIO);
+ n = dev->num_mmio++;
+ dev->mmio[n].addr = -1;
+ dev->mmio[n].memory = memory;
+}
+
+MemoryRegion *sysbus_mmio_get_region(SysBusDevice *dev, int n)
+{
+ return dev->mmio[n].memory;
+}
+
+void sysbus_init_ioports(SysBusDevice *dev, pio_addr_t ioport, pio_addr_t size)
+{
+ pio_addr_t i;
+
+ for (i = 0; i < size; i++) {
+ assert(dev->num_pio < QDEV_MAX_PIO);
+ dev->pio[dev->num_pio++] = ioport++;
+ }
+}
+
+static int sysbus_device_init(DeviceState *dev)
+{
+ SysBusDevice *sd = SYS_BUS_DEVICE(dev);
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_GET_CLASS(sd);
+
+ if (!sbc->init) {
+ return 0;
+ }
+ return sbc->init(sd);
+}
+
+DeviceState *sysbus_create_varargs(const char *name,
+ hwaddr addr, ...)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ va_list va;
+ qemu_irq irq;
+ int n;
+
+ dev = qdev_create(NULL, name);
+ s = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ if (addr != (hwaddr)-1) {
+ sysbus_mmio_map(s, 0, addr);
+ }
+ va_start(va, addr);
+ n = 0;
+ while (1) {
+ irq = va_arg(va, qemu_irq);
+ if (!irq) {
+ break;
+ }
+ sysbus_connect_irq(s, n, irq);
+ n++;
+ }
+ va_end(va);
+ return dev;
+}
+
+DeviceState *sysbus_try_create_varargs(const char *name,
+ hwaddr addr, ...)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ va_list va;
+ qemu_irq irq;
+ int n;
+
+ dev = qdev_try_create(NULL, name);
+ if (!dev) {
+ return NULL;
+ }
+ s = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ if (addr != (hwaddr)-1) {
+ sysbus_mmio_map(s, 0, addr);
+ }
+ va_start(va, addr);
+ n = 0;
+ while (1) {
+ irq = va_arg(va, qemu_irq);
+ if (!irq) {
+ break;
+ }
+ sysbus_connect_irq(s, n, irq);
+ n++;
+ }
+ va_end(va);
+ return dev;
+}
+
+static void sysbus_dev_print(Monitor *mon, DeviceState *dev, int indent)
+{
+ SysBusDevice *s = SYS_BUS_DEVICE(dev);
+ hwaddr size;
+ int i;
+
+ for (i = 0; i < s->num_mmio; i++) {
+ size = memory_region_size(s->mmio[i].memory);
+ monitor_printf(mon, "%*smmio " TARGET_FMT_plx "/" TARGET_FMT_plx "\n",
+ indent, "", s->mmio[i].addr, size);
+ }
+}
+
+static char *sysbus_get_fw_dev_path(DeviceState *dev)
+{
+ SysBusDevice *s = SYS_BUS_DEVICE(dev);
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_GET_CLASS(s);
+ /* for the explicit unit address fallback case: */
+ char *addr, *fw_dev_path;
+
+ if (s->num_mmio) {
+ return g_strdup_printf("%s@" TARGET_FMT_plx, qdev_fw_name(dev),
+ s->mmio[0].addr);
+ }
+ if (s->num_pio) {
+ return g_strdup_printf("%s@i%04x", qdev_fw_name(dev), s->pio[0]);
+ }
+ if (sbc->explicit_ofw_unit_address) {
+ addr = sbc->explicit_ofw_unit_address(s);
+ if (addr) {
+ fw_dev_path = g_strdup_printf("%s@%s", qdev_fw_name(dev), addr);
+ g_free(addr);
+ return fw_dev_path;
+ }
+ }
+ return g_strdup(qdev_fw_name(dev));
+}
+
+void sysbus_add_io(SysBusDevice *dev, hwaddr addr,
+ MemoryRegion *mem)
+{
+ memory_region_add_subregion(get_system_io(), addr, mem);
+}
+
+MemoryRegion *sysbus_address_space(SysBusDevice *dev)
+{
+ return get_system_memory();
+}
+
+static void sysbus_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->init = sysbus_device_init;
+ k->bus_type = TYPE_SYSTEM_BUS;
+}
+
+static const TypeInfo sysbus_device_type_info = {
+ .name = TYPE_SYS_BUS_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .abstract = true,
+ .class_size = sizeof(SysBusDeviceClass),
+ .class_init = sysbus_device_class_init,
+};
+
+/* This is a nasty hack to allow passing a NULL bus to qdev_create. */
+static BusState *main_system_bus;
+
+static void main_system_bus_create(void)
+{
+ /* assign main_system_bus before qbus_create_inplace()
+ * in order to make "if (bus != sysbus_get_default())" work */
+ main_system_bus = g_malloc0(system_bus_info.instance_size);
+ qbus_create_inplace(main_system_bus, system_bus_info.instance_size,
+ TYPE_SYSTEM_BUS, NULL, "main-system-bus");
+ OBJECT(main_system_bus)->free = g_free;
+ object_property_add_child(container_get(qdev_get_machine(),
+ "/unattached"),
+ "sysbus", OBJECT(main_system_bus), NULL);
+}
+
+BusState *sysbus_get_default(void)
+{
+ if (!main_system_bus) {
+ main_system_bus_create();
+ }
+ return main_system_bus;
+}
+
+static void sysbus_register_types(void)
+{
+ type_register_static(&system_bus_info);
+ type_register_static(&sysbus_device_type_info);
+}
+
+type_init(sysbus_register_types)
diff --git a/hw/core/uboot_image.h b/hw/core/uboot_image.h
new file mode 100644
index 00000000..9fc2760b
--- /dev/null
+++ b/hw/core/uboot_image.h
@@ -0,0 +1,158 @@
+/*
+ * (C) Copyright 2000-2005
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ ********************************************************************
+ * NOTE: This header file defines an interface to U-Boot. Including
+ * this (unmodified) header file in another file is considered normal
+ * use of U-Boot, and does *not* fall under the heading of "derived
+ * work".
+ ********************************************************************
+ */
+
+#ifndef __UBOOT_IMAGE_H__
+#define __UBOOT_IMAGE_H__
+
+/*
+ * Operating System Codes
+ */
+#define IH_OS_INVALID 0 /* Invalid OS */
+#define IH_OS_OPENBSD 1 /* OpenBSD */
+#define IH_OS_NETBSD 2 /* NetBSD */
+#define IH_OS_FREEBSD 3 /* FreeBSD */
+#define IH_OS_4_4BSD 4 /* 4.4BSD */
+#define IH_OS_LINUX 5 /* Linux */
+#define IH_OS_SVR4 6 /* SVR4 */
+#define IH_OS_ESIX 7 /* Esix */
+#define IH_OS_SOLARIS 8 /* Solaris */
+#define IH_OS_IRIX 9 /* Irix */
+#define IH_OS_SCO 10 /* SCO */
+#define IH_OS_DELL 11 /* Dell */
+#define IH_OS_NCR 12 /* NCR */
+#define IH_OS_LYNXOS 13 /* LynxOS */
+#define IH_OS_VXWORKS 14 /* VxWorks */
+#define IH_OS_PSOS 15 /* pSOS */
+#define IH_OS_QNX 16 /* QNX */
+#define IH_OS_U_BOOT 17 /* Firmware */
+#define IH_OS_RTEMS 18 /* RTEMS */
+#define IH_OS_ARTOS 19 /* ARTOS */
+#define IH_OS_UNITY 20 /* Unity OS */
+
+/*
+ * CPU Architecture Codes (supported by Linux)
+ */
+#define IH_CPU_INVALID 0 /* Invalid CPU */
+#define IH_CPU_ALPHA 1 /* Alpha */
+#define IH_CPU_ARM 2 /* ARM */
+#define IH_CPU_I386 3 /* Intel x86 */
+#define IH_CPU_IA64 4 /* IA64 */
+#define IH_CPU_MIPS 5 /* MIPS */
+#define IH_CPU_MIPS64 6 /* MIPS 64 Bit */
+#define IH_CPU_PPC 7 /* PowerPC */
+#define IH_CPU_S390 8 /* IBM S390 */
+#define IH_CPU_SH 9 /* SuperH */
+#define IH_CPU_SPARC 10 /* Sparc */
+#define IH_CPU_SPARC64 11 /* Sparc 64 Bit */
+#define IH_CPU_M68K 12 /* M68K */
+#define IH_CPU_NIOS 13 /* Nios-32 */
+#define IH_CPU_MICROBLAZE 14 /* MicroBlaze */
+#define IH_CPU_NIOS2 15 /* Nios-II */
+#define IH_CPU_BLACKFIN 16 /* Blackfin */
+#define IH_CPU_AVR32 17 /* AVR32 */
+
+/*
+ * Image Types
+ *
+ * "Standalone Programs" are directly runnable in the environment
+ * provided by U-Boot; it is expected that (if they behave
+ * well) you can continue to work in U-Boot after return from
+ * the Standalone Program.
+ * "OS Kernel Images" are usually images of some Embedded OS which
+ * will take over control completely. Usually these programs
+ * will install their own set of exception handlers, device
+ * drivers, set up the MMU, etc. - this means, that you cannot
+ * expect to re-enter U-Boot except by resetting the CPU.
+ * "RAMDisk Images" are more or less just data blocks, and their
+ * parameters (address, size) are passed to an OS kernel that is
+ * being started.
+ * "Multi-File Images" contain several images, typically an OS
+ * (Linux) kernel image and one or more data images like
+ * RAMDisks. This construct is useful for instance when you want
+ * to boot over the network using BOOTP etc., where the boot
+ * server provides just a single image file, but you want to get
+ * for instance an OS kernel and a RAMDisk image.
+ *
+ * "Multi-File Images" start with a list of image sizes, each
+ * image size (in bytes) specified by an "uint32_t" in network
+ * byte order. This list is terminated by an "(uint32_t)0".
+ * Immediately after the terminating 0 follow the images, one by
+ * one, all aligned on "uint32_t" boundaries (size rounded up to
+ * a multiple of 4 bytes - except for the last file).
+ *
+ * "Firmware Images" are binary images containing firmware (like
+ * U-Boot or FPGA images) which usually will be programmed to
+ * flash memory.
+ *
+ * "Script files" are command sequences that will be executed by
+ * U-Boot's command interpreter; this feature is especially
+ * useful when you configure U-Boot to use a real shell (hush)
+ * as command interpreter (=> Shell Scripts).
+ */
+
+#define IH_TYPE_INVALID 0 /* Invalid Image */
+#define IH_TYPE_STANDALONE 1 /* Standalone Program */
+#define IH_TYPE_KERNEL 2 /* OS Kernel Image */
+#define IH_TYPE_RAMDISK 3 /* RAMDisk Image */
+#define IH_TYPE_MULTI 4 /* Multi-File Image */
+#define IH_TYPE_FIRMWARE 5 /* Firmware Image */
+#define IH_TYPE_SCRIPT 6 /* Script file */
+#define IH_TYPE_FILESYSTEM 7 /* Filesystem Image (any type) */
+#define IH_TYPE_FLATDT 8 /* Binary Flat Device Tree Blob */
+
+/*
+ * Compression Types
+ */
+#define IH_COMP_NONE 0 /* No Compression Used */
+#define IH_COMP_GZIP 1 /* gzip Compression Used */
+#define IH_COMP_BZIP2 2 /* bzip2 Compression Used */
+
+#define IH_MAGIC 0x27051956 /* Image Magic Number */
+#define IH_NMLEN 32 /* Image Name Length */
+
+/*
+ * all data in network byte order (aka natural aka bigendian)
+ */
+
+typedef struct uboot_image_header {
+ uint32_t ih_magic; /* Image Header Magic Number */
+ uint32_t ih_hcrc; /* Image Header CRC Checksum */
+ uint32_t ih_time; /* Image Creation Timestamp */
+ uint32_t ih_size; /* Image Data Size */
+ uint32_t ih_load; /* Data Load Address */
+ uint32_t ih_ep; /* Entry Point Address */
+ uint32_t ih_dcrc; /* Image Data CRC Checksum */
+ uint8_t ih_os; /* Operating System */
+ uint8_t ih_arch; /* CPU architecture */
+ uint8_t ih_type; /* Image Type */
+ uint8_t ih_comp; /* Compression Type */
+ uint8_t ih_name[IH_NMLEN]; /* Image Name */
+} uboot_image_header_t;
+
+
+#endif /* __IMAGE_H__ */
diff --git a/hw/cpu/Makefile.objs b/hw/cpu/Makefile.objs
new file mode 100644
index 00000000..6381238c
--- /dev/null
+++ b/hw/cpu/Makefile.objs
@@ -0,0 +1,6 @@
+obj-$(CONFIG_ARM11MPCORE) += arm11mpcore.o
+obj-$(CONFIG_REALVIEW) += realview_mpcore.o
+obj-$(CONFIG_A9MPCORE) += a9mpcore.o
+obj-$(CONFIG_A15MPCORE) += a15mpcore.o
+obj-$(CONFIG_ICC_BUS) += icc_bus.o
+
diff --git a/hw/cpu/a15mpcore.c b/hw/cpu/a15mpcore.c
new file mode 100644
index 00000000..acc419e1
--- /dev/null
+++ b/hw/cpu/a15mpcore.c
@@ -0,0 +1,140 @@
+/*
+ * Cortex-A15MPCore internal peripheral emulation.
+ *
+ * Copyright (c) 2012 Linaro Limited.
+ * Written by Peter Maydell.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/cpu/a15mpcore.h"
+#include "sysemu/kvm.h"
+
+static void a15mp_priv_set_irq(void *opaque, int irq, int level)
+{
+ A15MPPrivState *s = (A15MPPrivState *)opaque;
+
+ qemu_set_irq(qdev_get_gpio_in(DEVICE(&s->gic), irq), level);
+}
+
+static void a15mp_priv_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ A15MPPrivState *s = A15MPCORE_PRIV(obj);
+ DeviceState *gicdev;
+ const char *gictype = "arm_gic";
+
+ if (kvm_irqchip_in_kernel()) {
+ gictype = "kvm-arm-gic";
+ }
+
+ memory_region_init(&s->container, obj, "a15mp-priv-container", 0x8000);
+ sysbus_init_mmio(sbd, &s->container);
+
+ object_initialize(&s->gic, sizeof(s->gic), gictype);
+ gicdev = DEVICE(&s->gic);
+ qdev_set_parent_bus(gicdev, sysbus_get_default());
+ qdev_prop_set_uint32(gicdev, "revision", 2);
+}
+
+static void a15mp_priv_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ A15MPPrivState *s = A15MPCORE_PRIV(dev);
+ DeviceState *gicdev;
+ SysBusDevice *busdev;
+ int i;
+ Error *err = NULL;
+
+ gicdev = DEVICE(&s->gic);
+ qdev_prop_set_uint32(gicdev, "num-cpu", s->num_cpu);
+ qdev_prop_set_uint32(gicdev, "num-irq", s->num_irq);
+ object_property_set_bool(OBJECT(&s->gic), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ busdev = SYS_BUS_DEVICE(&s->gic);
+
+ /* Pass through outbound IRQ lines from the GIC */
+ sysbus_pass_irq(sbd, busdev);
+
+ /* Pass through inbound GPIO lines to the GIC */
+ qdev_init_gpio_in(dev, a15mp_priv_set_irq, s->num_irq - 32);
+
+ /* Wire the outputs from each CPU's generic timer to the
+ * appropriate GIC PPI inputs
+ */
+ for (i = 0; i < s->num_cpu; i++) {
+ DeviceState *cpudev = DEVICE(qemu_get_cpu(i));
+ int ppibase = s->num_irq - 32 + i * 32;
+ /* physical timer; we wire it up to the non-secure timer's ID,
+ * since a real A15 always has TrustZone but QEMU doesn't.
+ */
+ qdev_connect_gpio_out(cpudev, 0,
+ qdev_get_gpio_in(gicdev, ppibase + 30));
+ /* virtual timer */
+ qdev_connect_gpio_out(cpudev, 1,
+ qdev_get_gpio_in(gicdev, ppibase + 27));
+ }
+
+ /* Memory map (addresses are offsets from PERIPHBASE):
+ * 0x0000-0x0fff -- reserved
+ * 0x1000-0x1fff -- GIC Distributor
+ * 0x2000-0x2fff -- GIC CPU interface
+ * 0x4000-0x4fff -- GIC virtual interface control (not modelled)
+ * 0x5000-0x5fff -- GIC virtual interface control (not modelled)
+ * 0x6000-0x7fff -- GIC virtual CPU interface (not modelled)
+ */
+ memory_region_add_subregion(&s->container, 0x1000,
+ sysbus_mmio_get_region(busdev, 0));
+ memory_region_add_subregion(&s->container, 0x2000,
+ sysbus_mmio_get_region(busdev, 1));
+}
+
+static Property a15mp_priv_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", A15MPPrivState, num_cpu, 1),
+ /* The Cortex-A15MP may have anything from 0 to 224 external interrupt
+ * IRQ lines (with another 32 internal). We default to 128+32, which
+ * is the number provided by the Cortex-A15MP test chip in the
+ * Versatile Express A15 development board.
+ * Other boards may differ and should set this property appropriately.
+ */
+ DEFINE_PROP_UINT32("num-irq", A15MPPrivState, num_irq, 160),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void a15mp_priv_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = a15mp_priv_realize;
+ dc->props = a15mp_priv_properties;
+ /* We currently have no savable state */
+}
+
+static const TypeInfo a15mp_priv_info = {
+ .name = TYPE_A15MPCORE_PRIV,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(A15MPPrivState),
+ .instance_init = a15mp_priv_initfn,
+ .class_init = a15mp_priv_class_init,
+};
+
+static void a15mp_register_types(void)
+{
+ type_register_static(&a15mp_priv_info);
+}
+
+type_init(a15mp_register_types)
diff --git a/hw/cpu/a9mpcore.c b/hw/cpu/a9mpcore.c
new file mode 100644
index 00000000..c09358c6
--- /dev/null
+++ b/hw/cpu/a9mpcore.c
@@ -0,0 +1,179 @@
+/*
+ * Cortex-A9MPCore internal peripheral emulation.
+ *
+ * Copyright (c) 2009 CodeSourcery.
+ * Copyright (c) 2011 Linaro Limited.
+ * Written by Paul Brook, Peter Maydell.
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/cpu/a9mpcore.h"
+
+static void a9mp_priv_set_irq(void *opaque, int irq, int level)
+{
+ A9MPPrivState *s = (A9MPPrivState *)opaque;
+
+ qemu_set_irq(qdev_get_gpio_in(DEVICE(&s->gic), irq), level);
+}
+
+static void a9mp_priv_initfn(Object *obj)
+{
+ A9MPPrivState *s = A9MPCORE_PRIV(obj);
+
+ memory_region_init(&s->container, obj, "a9mp-priv-container", 0x2000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->container);
+
+ object_initialize(&s->scu, sizeof(s->scu), TYPE_A9_SCU);
+ qdev_set_parent_bus(DEVICE(&s->scu), sysbus_get_default());
+
+ object_initialize(&s->gic, sizeof(s->gic), TYPE_ARM_GIC);
+ qdev_set_parent_bus(DEVICE(&s->gic), sysbus_get_default());
+
+ object_initialize(&s->gtimer, sizeof(s->gtimer), TYPE_A9_GTIMER);
+ qdev_set_parent_bus(DEVICE(&s->gtimer), sysbus_get_default());
+
+ object_initialize(&s->mptimer, sizeof(s->mptimer), TYPE_ARM_MPTIMER);
+ qdev_set_parent_bus(DEVICE(&s->mptimer), sysbus_get_default());
+
+ object_initialize(&s->wdt, sizeof(s->wdt), TYPE_ARM_MPTIMER);
+ qdev_set_parent_bus(DEVICE(&s->wdt), sysbus_get_default());
+}
+
+static void a9mp_priv_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ A9MPPrivState *s = A9MPCORE_PRIV(dev);
+ DeviceState *scudev, *gicdev, *gtimerdev, *mptimerdev, *wdtdev;
+ SysBusDevice *scubusdev, *gicbusdev, *gtimerbusdev, *mptimerbusdev,
+ *wdtbusdev;
+ Error *err = NULL;
+ int i;
+
+ scudev = DEVICE(&s->scu);
+ qdev_prop_set_uint32(scudev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->scu), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ scubusdev = SYS_BUS_DEVICE(&s->scu);
+
+ gicdev = DEVICE(&s->gic);
+ qdev_prop_set_uint32(gicdev, "num-cpu", s->num_cpu);
+ qdev_prop_set_uint32(gicdev, "num-irq", s->num_irq);
+ object_property_set_bool(OBJECT(&s->gic), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ gicbusdev = SYS_BUS_DEVICE(&s->gic);
+
+ /* Pass through outbound IRQ lines from the GIC */
+ sysbus_pass_irq(sbd, gicbusdev);
+
+ /* Pass through inbound GPIO lines to the GIC */
+ qdev_init_gpio_in(dev, a9mp_priv_set_irq, s->num_irq - 32);
+
+ gtimerdev = DEVICE(&s->gtimer);
+ qdev_prop_set_uint32(gtimerdev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->gtimer), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ gtimerbusdev = SYS_BUS_DEVICE(&s->gtimer);
+
+ mptimerdev = DEVICE(&s->mptimer);
+ qdev_prop_set_uint32(mptimerdev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->mptimer), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ mptimerbusdev = SYS_BUS_DEVICE(&s->mptimer);
+
+ wdtdev = DEVICE(&s->wdt);
+ qdev_prop_set_uint32(wdtdev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->wdt), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ wdtbusdev = SYS_BUS_DEVICE(&s->wdt);
+
+ /* Memory map (addresses are offsets from PERIPHBASE):
+ * 0x0000-0x00ff -- Snoop Control Unit
+ * 0x0100-0x01ff -- GIC CPU interface
+ * 0x0200-0x02ff -- Global Timer
+ * 0x0300-0x05ff -- nothing
+ * 0x0600-0x06ff -- private timers and watchdogs
+ * 0x0700-0x0fff -- nothing
+ * 0x1000-0x1fff -- GIC Distributor
+ */
+ memory_region_add_subregion(&s->container, 0,
+ sysbus_mmio_get_region(scubusdev, 0));
+ /* GIC CPU interface */
+ memory_region_add_subregion(&s->container, 0x100,
+ sysbus_mmio_get_region(gicbusdev, 1));
+ memory_region_add_subregion(&s->container, 0x200,
+ sysbus_mmio_get_region(gtimerbusdev, 0));
+ /* Note that the A9 exposes only the "timer/watchdog for this core"
+ * memory region, not the "timer/watchdog for core X" ones 11MPcore has.
+ */
+ memory_region_add_subregion(&s->container, 0x600,
+ sysbus_mmio_get_region(mptimerbusdev, 0));
+ memory_region_add_subregion(&s->container, 0x620,
+ sysbus_mmio_get_region(wdtbusdev, 0));
+ memory_region_add_subregion(&s->container, 0x1000,
+ sysbus_mmio_get_region(gicbusdev, 0));
+
+ /* Wire up the interrupt from each watchdog and timer.
+ * For each core the global timer is PPI 27, the private
+ * timer is PPI 29 and the watchdog PPI 30.
+ */
+ for (i = 0; i < s->num_cpu; i++) {
+ int ppibase = (s->num_irq - 32) + i * 32;
+ sysbus_connect_irq(gtimerbusdev, i,
+ qdev_get_gpio_in(gicdev, ppibase + 27));
+ sysbus_connect_irq(mptimerbusdev, i,
+ qdev_get_gpio_in(gicdev, ppibase + 29));
+ sysbus_connect_irq(wdtbusdev, i,
+ qdev_get_gpio_in(gicdev, ppibase + 30));
+ }
+}
+
+static Property a9mp_priv_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", A9MPPrivState, num_cpu, 1),
+ /* The Cortex-A9MP may have anything from 0 to 224 external interrupt
+ * IRQ lines (with another 32 internal). We default to 64+32, which
+ * is the number provided by the Cortex-A9MP test chip in the
+ * Realview PBX-A9 and Versatile Express A9 development boards.
+ * Other boards may differ and should set this property appropriately.
+ */
+ DEFINE_PROP_UINT32("num-irq", A9MPPrivState, num_irq, 96),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void a9mp_priv_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = a9mp_priv_realize;
+ dc->props = a9mp_priv_properties;
+}
+
+static const TypeInfo a9mp_priv_info = {
+ .name = TYPE_A9MPCORE_PRIV,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(A9MPPrivState),
+ .instance_init = a9mp_priv_initfn,
+ .class_init = a9mp_priv_class_init,
+};
+
+static void a9mp_register_types(void)
+{
+ type_register_static(&a9mp_priv_info);
+}
+
+type_init(a9mp_register_types)
diff --git a/hw/cpu/arm11mpcore.c b/hw/cpu/arm11mpcore.c
new file mode 100644
index 00000000..717d3e4f
--- /dev/null
+++ b/hw/cpu/arm11mpcore.c
@@ -0,0 +1,172 @@
+/*
+ * ARM11MPCore internal peripheral emulation.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/cpu/arm11mpcore.h"
+#include "hw/intc/realview_gic.h"
+
+
+static void mpcore_priv_set_irq(void *opaque, int irq, int level)
+{
+ ARM11MPCorePriveState *s = (ARM11MPCorePriveState *)opaque;
+
+ qemu_set_irq(qdev_get_gpio_in(DEVICE(&s->gic), irq), level);
+}
+
+static void mpcore_priv_map_setup(ARM11MPCorePriveState *s)
+{
+ int i;
+ SysBusDevice *scubusdev = SYS_BUS_DEVICE(&s->scu);
+ DeviceState *gicdev = DEVICE(&s->gic);
+ SysBusDevice *gicbusdev = SYS_BUS_DEVICE(&s->gic);
+ SysBusDevice *timerbusdev = SYS_BUS_DEVICE(&s->mptimer);
+ SysBusDevice *wdtbusdev = SYS_BUS_DEVICE(&s->wdtimer);
+
+ memory_region_add_subregion(&s->container, 0,
+ sysbus_mmio_get_region(scubusdev, 0));
+ /* GIC CPU interfaces: "current CPU" at 0x100, then specific CPUs
+ * at 0x200, 0x300...
+ */
+ for (i = 0; i < (s->num_cpu + 1); i++) {
+ hwaddr offset = 0x100 + (i * 0x100);
+ memory_region_add_subregion(&s->container, offset,
+ sysbus_mmio_get_region(gicbusdev, i + 1));
+ }
+ /* Add the regions for timer and watchdog for "current CPU" and
+ * for each specific CPU.
+ */
+ for (i = 0; i < (s->num_cpu + 1); i++) {
+ /* Timers at 0x600, 0x700, ...; watchdogs at 0x620, 0x720, ... */
+ hwaddr offset = 0x600 + i * 0x100;
+ memory_region_add_subregion(&s->container, offset,
+ sysbus_mmio_get_region(timerbusdev, i));
+ memory_region_add_subregion(&s->container, offset + 0x20,
+ sysbus_mmio_get_region(wdtbusdev, i));
+ }
+ memory_region_add_subregion(&s->container, 0x1000,
+ sysbus_mmio_get_region(gicbusdev, 0));
+ /* Wire up the interrupt from each watchdog and timer.
+ * For each core the timer is PPI 29 and the watchdog PPI 30.
+ */
+ for (i = 0; i < s->num_cpu; i++) {
+ int ppibase = (s->num_irq - 32) + i * 32;
+ sysbus_connect_irq(timerbusdev, i,
+ qdev_get_gpio_in(gicdev, ppibase + 29));
+ sysbus_connect_irq(wdtbusdev, i,
+ qdev_get_gpio_in(gicdev, ppibase + 30));
+ }
+}
+
+static void mpcore_priv_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ ARM11MPCorePriveState *s = ARM11MPCORE_PRIV(dev);
+ DeviceState *scudev = DEVICE(&s->scu);
+ DeviceState *gicdev = DEVICE(&s->gic);
+ DeviceState *mptimerdev = DEVICE(&s->mptimer);
+ DeviceState *wdtimerdev = DEVICE(&s->wdtimer);
+ Error *err = NULL;
+
+ qdev_prop_set_uint32(scudev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->scu), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ qdev_prop_set_uint32(gicdev, "num-cpu", s->num_cpu);
+ qdev_prop_set_uint32(gicdev, "num-irq", s->num_irq);
+ object_property_set_bool(OBJECT(&s->gic), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* Pass through outbound IRQ lines from the GIC */
+ sysbus_pass_irq(sbd, SYS_BUS_DEVICE(&s->gic));
+
+ /* Pass through inbound GPIO lines to the GIC */
+ qdev_init_gpio_in(dev, mpcore_priv_set_irq, s->num_irq - 32);
+
+ qdev_prop_set_uint32(mptimerdev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->mptimer), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ qdev_prop_set_uint32(wdtimerdev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->wdtimer), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ mpcore_priv_map_setup(s);
+}
+
+static void mpcore_priv_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARM11MPCorePriveState *s = ARM11MPCORE_PRIV(obj);
+
+ memory_region_init(&s->container, OBJECT(s),
+ "mpcore-priv-container", 0x2000);
+ sysbus_init_mmio(sbd, &s->container);
+
+ object_initialize(&s->scu, sizeof(s->scu), TYPE_ARM11_SCU);
+ qdev_set_parent_bus(DEVICE(&s->scu), sysbus_get_default());
+
+ object_initialize(&s->gic, sizeof(s->gic), TYPE_ARM_GIC);
+ qdev_set_parent_bus(DEVICE(&s->gic), sysbus_get_default());
+ /* Request the legacy 11MPCore GIC behaviour: */
+ qdev_prop_set_uint32(DEVICE(&s->gic), "revision", 0);
+
+ object_initialize(&s->mptimer, sizeof(s->mptimer), TYPE_ARM_MPTIMER);
+ qdev_set_parent_bus(DEVICE(&s->mptimer), sysbus_get_default());
+
+ object_initialize(&s->wdtimer, sizeof(s->wdtimer), TYPE_ARM_MPTIMER);
+ qdev_set_parent_bus(DEVICE(&s->wdtimer), sysbus_get_default());
+}
+
+static Property mpcore_priv_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", ARM11MPCorePriveState, num_cpu, 1),
+ /* The ARM11 MPCORE TRM says the on-chip controller may have
+ * anything from 0 to 224 external interrupt IRQ lines (with another
+ * 32 internal). We default to 32+32, which is the number provided by
+ * the ARM11 MPCore test chip in the Realview Versatile Express
+ * coretile. Other boards may differ and should set this property
+ * appropriately. Some Linux kernels may not boot if the hardware
+ * has more IRQ lines than the kernel expects.
+ */
+ DEFINE_PROP_UINT32("num-irq", ARM11MPCorePriveState, num_irq, 64),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mpcore_priv_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = mpcore_priv_realize;
+ dc->props = mpcore_priv_properties;
+}
+
+static const TypeInfo mpcore_priv_info = {
+ .name = TYPE_ARM11MPCORE_PRIV,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARM11MPCorePriveState),
+ .instance_init = mpcore_priv_initfn,
+ .class_init = mpcore_priv_class_init,
+};
+
+static void arm11mpcore_register_types(void)
+{
+ type_register_static(&mpcore_priv_info);
+}
+
+type_init(arm11mpcore_register_types)
diff --git a/hw/cpu/icc_bus.c b/hw/cpu/icc_bus.c
new file mode 100644
index 00000000..6646ea2b
--- /dev/null
+++ b/hw/cpu/icc_bus.c
@@ -0,0 +1,118 @@
+/* icc_bus.c
+ * emulate x86 ICC (Interrupt Controller Communications) bus
+ *
+ * Copyright (c) 2013 Red Hat, Inc
+ *
+ * Authors:
+ * Igor Mammedov <imammedo@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+#include "hw/cpu/icc_bus.h"
+#include "hw/sysbus.h"
+
+/* icc-bridge implementation */
+
+static const TypeInfo icc_bus_info = {
+ .name = TYPE_ICC_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(ICCBus),
+};
+
+
+/* icc-device implementation */
+
+static void icc_device_realize(DeviceState *dev, Error **errp)
+{
+ ICCDeviceClass *idc = ICC_DEVICE_GET_CLASS(dev);
+
+ /* convert to QOM */
+ if (idc->realize) {
+ idc->realize(dev, errp);
+ }
+
+}
+
+static void icc_device_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = icc_device_realize;
+ dc->bus_type = TYPE_ICC_BUS;
+}
+
+static const TypeInfo icc_device_info = {
+ .name = TYPE_ICC_DEVICE,
+ .parent = TYPE_DEVICE,
+ .abstract = true,
+ .instance_size = sizeof(ICCDevice),
+ .class_size = sizeof(ICCDeviceClass),
+ .class_init = icc_device_class_init,
+};
+
+
+/* icc-bridge implementation */
+
+typedef struct ICCBridgeState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ ICCBus icc_bus;
+ MemoryRegion apic_container;
+} ICCBridgeState;
+
+#define ICC_BRIDGE(obj) OBJECT_CHECK(ICCBridgeState, (obj), TYPE_ICC_BRIDGE)
+
+static void icc_bridge_init(Object *obj)
+{
+ ICCBridgeState *s = ICC_BRIDGE(obj);
+ SysBusDevice *sb = SYS_BUS_DEVICE(obj);
+
+ qbus_create_inplace(&s->icc_bus, sizeof(s->icc_bus), TYPE_ICC_BUS,
+ DEVICE(s), "icc");
+
+ /* Do not change order of registering regions,
+ * APIC must be first registered region, board maps it by 0 index
+ */
+ memory_region_init(&s->apic_container, obj, "icc-apic-container",
+ APIC_SPACE_SIZE);
+ sysbus_init_mmio(sb, &s->apic_container);
+ s->icc_bus.apic_address_space = &s->apic_container;
+}
+
+static void icc_bridge_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo icc_bridge_info = {
+ .name = TYPE_ICC_BRIDGE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = icc_bridge_init,
+ .instance_size = sizeof(ICCBridgeState),
+ .class_init = icc_bridge_class_init,
+};
+
+
+static void icc_bus_register_types(void)
+{
+ type_register_static(&icc_bus_info);
+ type_register_static(&icc_device_info);
+ type_register_static(&icc_bridge_info);
+}
+
+type_init(icc_bus_register_types)
diff --git a/hw/cpu/realview_mpcore.c b/hw/cpu/realview_mpcore.c
new file mode 100644
index 00000000..c39a2da4
--- /dev/null
+++ b/hw/cpu/realview_mpcore.c
@@ -0,0 +1,139 @@
+/*
+ * RealView ARM11MPCore internal peripheral emulation
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Copyright (c) 2013 SUSE LINUX Products GmbH
+ * Written by Paul Brook and Andreas Färber
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/cpu/arm11mpcore.h"
+#include "hw/intc/realview_gic.h"
+
+#define TYPE_REALVIEW_MPCORE_RIRQ "realview_mpcore"
+#define REALVIEW_MPCORE_RIRQ(obj) \
+ OBJECT_CHECK(mpcore_rirq_state, (obj), TYPE_REALVIEW_MPCORE_RIRQ)
+
+/* Dummy PIC to route IRQ lines. The baseboard has 4 independent IRQ
+ controllers. The output of these, plus some of the raw input lines
+ are fed into a single SMP-aware interrupt controller on the CPU. */
+typedef struct {
+ SysBusDevice parent_obj;
+
+ qemu_irq cpuic[32];
+ qemu_irq rvic[4][64];
+ uint32_t num_cpu;
+
+ ARM11MPCorePriveState priv;
+ RealViewGICState gic[4];
+} mpcore_rirq_state;
+
+/* Map baseboard IRQs onto CPU IRQ lines. */
+static const int mpcore_irq_map[32] = {
+ -1, -1, -1, -1, 1, 2, -1, -1,
+ -1, -1, 6, -1, 4, 5, -1, -1,
+ -1, 14, 15, 0, 7, 8, -1, -1,
+ -1, -1, -1, -1, 9, 3, -1, -1,
+};
+
+static void mpcore_rirq_set_irq(void *opaque, int irq, int level)
+{
+ mpcore_rirq_state *s = (mpcore_rirq_state *)opaque;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ qemu_set_irq(s->rvic[i][irq], level);
+ }
+ if (irq < 32) {
+ irq = mpcore_irq_map[irq];
+ if (irq >= 0) {
+ qemu_set_irq(s->cpuic[irq], level);
+ }
+ }
+}
+
+static void realview_mpcore_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ mpcore_rirq_state *s = REALVIEW_MPCORE_RIRQ(dev);
+ DeviceState *priv = DEVICE(&s->priv);
+ DeviceState *gic;
+ SysBusDevice *gicbusdev;
+ Error *err = NULL;
+ int n;
+ int i;
+
+ qdev_prop_set_uint32(priv, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->priv), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbus_pass_irq(sbd, SYS_BUS_DEVICE(&s->priv));
+ for (i = 0; i < 32; i++) {
+ s->cpuic[i] = qdev_get_gpio_in(priv, i);
+ }
+ /* ??? IRQ routing is hardcoded to "normal" mode. */
+ for (n = 0; n < 4; n++) {
+ object_property_set_bool(OBJECT(&s->gic[n]), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ gic = DEVICE(&s->gic[n]);
+ gicbusdev = SYS_BUS_DEVICE(&s->gic[n]);
+ sysbus_mmio_map(gicbusdev, 0, 0x10040000 + n * 0x10000);
+ sysbus_connect_irq(gicbusdev, 0, s->cpuic[10 + n]);
+ for (i = 0; i < 64; i++) {
+ s->rvic[n][i] = qdev_get_gpio_in(gic, i);
+ }
+ }
+ qdev_init_gpio_in(dev, mpcore_rirq_set_irq, 64);
+}
+
+static void mpcore_rirq_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ mpcore_rirq_state *s = REALVIEW_MPCORE_RIRQ(obj);
+ SysBusDevice *privbusdev;
+ int i;
+
+ object_initialize(&s->priv, sizeof(s->priv), TYPE_ARM11MPCORE_PRIV);
+ qdev_set_parent_bus(DEVICE(&s->priv), sysbus_get_default());
+ privbusdev = SYS_BUS_DEVICE(&s->priv);
+ sysbus_init_mmio(sbd, sysbus_mmio_get_region(privbusdev, 0));
+
+ for (i = 0; i < 4; i++) {
+ object_initialize(&s->gic[i], sizeof(s->gic[i]), TYPE_REALVIEW_GIC);
+ qdev_set_parent_bus(DEVICE(&s->gic[i]), sysbus_get_default());
+ }
+}
+
+static Property mpcore_rirq_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", mpcore_rirq_state, num_cpu, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mpcore_rirq_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = realview_mpcore_realize;
+ dc->props = mpcore_rirq_properties;
+}
+
+static const TypeInfo mpcore_rirq_info = {
+ .name = TYPE_REALVIEW_MPCORE_RIRQ,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(mpcore_rirq_state),
+ .instance_init = mpcore_rirq_init,
+ .class_init = mpcore_rirq_class_init,
+};
+
+static void realview_mpcore_register_types(void)
+{
+ type_register_static(&mpcore_rirq_info);
+}
+
+type_init(realview_mpcore_register_types)
diff --git a/hw/cris/Makefile.objs b/hw/cris/Makefile.objs
new file mode 100644
index 00000000..7624173f
--- /dev/null
+++ b/hw/cris/Makefile.objs
@@ -0,0 +1,2 @@
+obj-y += boot.o
+obj-y += axis_dev88.o
diff --git a/hw/cris/axis_dev88.c b/hw/cris/axis_dev88.c
new file mode 100644
index 00000000..3cae480f
--- /dev/null
+++ b/hw/cris/axis_dev88.c
@@ -0,0 +1,366 @@
+/*
+ * QEMU model for the AXIS devboard 88.
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "hw/block/flash.h"
+#include "hw/boards.h"
+#include "hw/cris/etraxfs.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "boot.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "sysemu/qtest.h"
+
+#define D(x)
+#define DNAND(x)
+
+struct nand_state_t
+{
+ DeviceState *nand;
+ MemoryRegion iomem;
+ unsigned int rdy:1;
+ unsigned int ale:1;
+ unsigned int cle:1;
+ unsigned int ce:1;
+};
+
+static struct nand_state_t nand_state;
+static uint64_t nand_read(void *opaque, hwaddr addr, unsigned size)
+{
+ struct nand_state_t *s = opaque;
+ uint32_t r;
+ int rdy;
+
+ r = nand_getio(s->nand);
+ nand_getpins(s->nand, &rdy);
+ s->rdy = rdy;
+
+ DNAND(printf("%s addr=%x r=%x\n", __func__, addr, r));
+ return r;
+}
+
+static void
+nand_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ struct nand_state_t *s = opaque;
+ int rdy;
+
+ DNAND(printf("%s addr=%x v=%x\n", __func__, addr, (unsigned)value));
+ nand_setpins(s->nand, s->cle, s->ale, s->ce, 1, 0);
+ nand_setio(s->nand, value);
+ nand_getpins(s->nand, &rdy);
+ s->rdy = rdy;
+}
+
+static const MemoryRegionOps nand_ops = {
+ .read = nand_read,
+ .write = nand_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct tempsensor_t
+{
+ unsigned int shiftreg;
+ unsigned int count;
+ enum {
+ ST_OUT, ST_IN, ST_Z
+ } state;
+
+ uint16_t regs[3];
+};
+
+static void tempsensor_clkedge(struct tempsensor_t *s,
+ unsigned int clk, unsigned int data_in)
+{
+ D(printf("%s clk=%d state=%d sr=%x\n", __func__,
+ clk, s->state, s->shiftreg));
+ if (s->count == 0) {
+ s->count = 16;
+ s->state = ST_OUT;
+ }
+ switch (s->state) {
+ case ST_OUT:
+ /* Output reg is clocked at negedge. */
+ if (!clk) {
+ s->count--;
+ s->shiftreg <<= 1;
+ if (s->count == 0) {
+ s->shiftreg = 0;
+ s->state = ST_IN;
+ s->count = 16;
+ }
+ }
+ break;
+ case ST_Z:
+ if (clk) {
+ s->count--;
+ if (s->count == 0) {
+ s->shiftreg = 0;
+ s->state = ST_OUT;
+ s->count = 16;
+ }
+ }
+ break;
+ case ST_IN:
+ /* Indata is sampled at posedge. */
+ if (clk) {
+ s->count--;
+ s->shiftreg <<= 1;
+ s->shiftreg |= data_in & 1;
+ if (s->count == 0) {
+ D(printf("%s cfgreg=%x\n", __func__, s->shiftreg));
+ s->regs[0] = s->shiftreg;
+ s->state = ST_OUT;
+ s->count = 16;
+
+ if ((s->regs[0] & 0xff) == 0) {
+ /* 25 degrees celcius. */
+ s->shiftreg = 0x0b9f;
+ } else if ((s->regs[0] & 0xff) == 0xff) {
+ /* Sensor ID, 0x8100 LM70. */
+ s->shiftreg = 0x8100;
+ } else
+ printf("Invalid tempsens state %x\n", s->regs[0]);
+ }
+ }
+ break;
+ }
+}
+
+
+#define RW_PA_DOUT 0x00
+#define R_PA_DIN 0x01
+#define RW_PA_OE 0x02
+#define RW_PD_DOUT 0x10
+#define R_PD_DIN 0x11
+#define RW_PD_OE 0x12
+
+static struct gpio_state_t
+{
+ MemoryRegion iomem;
+ struct nand_state_t *nand;
+ struct tempsensor_t tempsensor;
+ uint32_t regs[0x5c / 4];
+} gpio_state;
+
+static uint64_t gpio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ struct gpio_state_t *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr)
+ {
+ case R_PA_DIN:
+ r = s->regs[RW_PA_DOUT] & s->regs[RW_PA_OE];
+
+ /* Encode pins from the nand. */
+ r |= s->nand->rdy << 7;
+ break;
+ case R_PD_DIN:
+ r = s->regs[RW_PD_DOUT] & s->regs[RW_PD_OE];
+
+ /* Encode temp sensor pins. */
+ r |= (!!(s->tempsensor.shiftreg & 0x10000)) << 4;
+ break;
+
+ default:
+ r = s->regs[addr];
+ break;
+ }
+ return r;
+ D(printf("%s %x=%x\n", __func__, addr, r));
+}
+
+static void gpio_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ struct gpio_state_t *s = opaque;
+ D(printf("%s %x=%x\n", __func__, addr, (unsigned)value));
+
+ addr >>= 2;
+ switch (addr)
+ {
+ case RW_PA_DOUT:
+ /* Decode nand pins. */
+ s->nand->ale = !!(value & (1 << 6));
+ s->nand->cle = !!(value & (1 << 5));
+ s->nand->ce = !!(value & (1 << 4));
+
+ s->regs[addr] = value;
+ break;
+
+ case RW_PD_DOUT:
+ /* Temp sensor clk. */
+ if ((s->regs[addr] ^ value) & 2)
+ tempsensor_clkedge(&s->tempsensor, !!(value & 2),
+ !!(value & 16));
+ s->regs[addr] = value;
+ break;
+
+ default:
+ s->regs[addr] = value;
+ break;
+ }
+}
+
+static const MemoryRegionOps gpio_ops = {
+ .read = gpio_read,
+ .write = gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+#define INTMEM_SIZE (128 * 1024)
+
+static struct cris_load_info li;
+
+static
+void axisdev88_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ CRISCPU *cpu;
+ CPUCRISState *env;
+ DeviceState *dev;
+ SysBusDevice *s;
+ DriveInfo *nand;
+ qemu_irq irq[30], nmi[2];
+ void *etraxfs_dmac;
+ struct etraxfs_dma_client *dma_eth;
+ int i;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *phys_ram = g_new(MemoryRegion, 1);
+ MemoryRegion *phys_intmem = g_new(MemoryRegion, 1);
+
+ /* init CPUs */
+ if (cpu_model == NULL) {
+ cpu_model = "crisv32";
+ }
+ cpu = cpu_cris_init(cpu_model);
+ env = &cpu->env;
+
+ /* allocate RAM */
+ memory_region_allocate_system_memory(phys_ram, NULL, "axisdev88.ram",
+ ram_size);
+ memory_region_add_subregion(address_space_mem, 0x40000000, phys_ram);
+
+ /* The ETRAX-FS has 128Kb on chip ram, the docs refer to it as the
+ internal memory. */
+ memory_region_init_ram(phys_intmem, NULL, "axisdev88.chipram", INTMEM_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(phys_intmem);
+ memory_region_add_subregion(address_space_mem, 0x38000000, phys_intmem);
+
+ /* Attach a NAND flash to CS1. */
+ nand = drive_get(IF_MTD, 0, 0);
+ nand_state.nand = nand_init(nand ? blk_by_legacy_dinfo(nand) : NULL,
+ NAND_MFR_STMICRO, 0x39);
+ memory_region_init_io(&nand_state.iomem, NULL, &nand_ops, &nand_state,
+ "nand", 0x05000000);
+ memory_region_add_subregion(address_space_mem, 0x10000000,
+ &nand_state.iomem);
+
+ gpio_state.nand = &nand_state;
+ memory_region_init_io(&gpio_state.iomem, NULL, &gpio_ops, &gpio_state,
+ "gpio", 0x5c);
+ memory_region_add_subregion(address_space_mem, 0x3001a000,
+ &gpio_state.iomem);
+
+
+ dev = qdev_create(NULL, "etraxfs,pic");
+ /* FIXME: Is there a proper way to signal vectors to the CPU core? */
+ qdev_prop_set_ptr(dev, "interrupt_vector", &env->interrupt_vector);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, 0x3001c000);
+ sysbus_connect_irq(s, 0, qdev_get_gpio_in(DEVICE(cpu), CRIS_CPU_IRQ));
+ sysbus_connect_irq(s, 1, qdev_get_gpio_in(DEVICE(cpu), CRIS_CPU_NMI));
+ for (i = 0; i < 30; i++) {
+ irq[i] = qdev_get_gpio_in(dev, i);
+ }
+ nmi[0] = qdev_get_gpio_in(dev, 30);
+ nmi[1] = qdev_get_gpio_in(dev, 31);
+
+ etraxfs_dmac = etraxfs_dmac_init(0x30000000, 10);
+ for (i = 0; i < 10; i++) {
+ /* On ETRAX, odd numbered channels are inputs. */
+ etraxfs_dmac_connect(etraxfs_dmac, i, irq + 7 + i, i & 1);
+ }
+
+ /* Add the two ethernet blocks. */
+ dma_eth = g_malloc0(sizeof dma_eth[0] * 4); /* Allocate 4 channels. */
+ etraxfs_eth_init(&nd_table[0], 0x30034000, 1, &dma_eth[0], &dma_eth[1]);
+ if (nb_nics > 1) {
+ etraxfs_eth_init(&nd_table[1], 0x30036000, 2, &dma_eth[2], &dma_eth[3]);
+ }
+
+ /* The DMA Connector block is missing, hardwire things for now. */
+ etraxfs_dmac_connect_client(etraxfs_dmac, 0, &dma_eth[0]);
+ etraxfs_dmac_connect_client(etraxfs_dmac, 1, &dma_eth[1]);
+ if (nb_nics > 1) {
+ etraxfs_dmac_connect_client(etraxfs_dmac, 6, &dma_eth[2]);
+ etraxfs_dmac_connect_client(etraxfs_dmac, 7, &dma_eth[3]);
+ }
+
+ /* 2 timers. */
+ sysbus_create_varargs("etraxfs,timer", 0x3001e000, irq[0x1b], nmi[1], NULL);
+ sysbus_create_varargs("etraxfs,timer", 0x3005e000, irq[0x1b], nmi[1], NULL);
+
+ for (i = 0; i < 4; i++) {
+ sysbus_create_simple("etraxfs,serial", 0x30026000 + i * 0x2000,
+ irq[0x14 + i]);
+ }
+
+ if (kernel_filename) {
+ li.image_filename = kernel_filename;
+ li.cmdline = kernel_cmdline;
+ cris_load_image(cpu, &li);
+ } else if (!qtest_enabled()) {
+ fprintf(stderr, "Kernel image must be specified\n");
+ exit(1);
+ }
+}
+
+static QEMUMachine axisdev88_machine = {
+ .name = "axis-dev88",
+ .desc = "AXIS devboard 88",
+ .init = axisdev88_init,
+ .is_default = 1,
+};
+
+static void axisdev88_machine_init(void)
+{
+ qemu_register_machine(&axisdev88_machine);
+}
+
+machine_init(axisdev88_machine_init);
diff --git a/hw/cris/boot.c b/hw/cris/boot.c
new file mode 100644
index 00000000..622f353c
--- /dev/null
+++ b/hw/cris/boot.c
@@ -0,0 +1,98 @@
+/*
+ * CRIS image loading.
+ *
+ * Copyright (c) 2010 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "boot.h"
+
+static void main_cpu_reset(void *opaque)
+{
+ CRISCPU *cpu = opaque;
+ CPUCRISState *env = &cpu->env;
+ struct cris_load_info *li;
+
+ li = env->load_info;
+
+ cpu_reset(CPU(cpu));
+
+ if (!li) {
+ /* nothing more to do. */
+ return;
+ }
+
+ env->pc = li->entry;
+
+ if (li->image_filename) {
+ env->regs[8] = 0x56902387; /* RAM boot magic. */
+ env->regs[9] = 0x40004000 + li->image_size;
+ }
+
+ if (li->cmdline) {
+ /* Let the kernel know we are modifying the cmdline. */
+ env->regs[10] = 0x87109563;
+ env->regs[11] = 0x40000000;
+ }
+}
+
+static uint64_t translate_kernel_address(void *opaque, uint64_t addr)
+{
+ return addr - 0x80000000LL;
+}
+
+void cris_load_image(CRISCPU *cpu, struct cris_load_info *li)
+{
+ CPUCRISState *env = &cpu->env;
+ uint64_t entry, high;
+ int kcmdline_len;
+ int image_size;
+
+ env->load_info = li;
+ /* Boots a kernel elf binary, os/linux-2.6/vmlinux from the axis
+ devboard SDK. */
+ image_size = load_elf(li->image_filename, translate_kernel_address, NULL,
+ &entry, NULL, &high, 0, ELF_MACHINE, 0);
+ li->entry = entry;
+ if (image_size < 0) {
+ /* Takes a kimage from the axis devboard SDK. */
+ image_size = load_image_targphys(li->image_filename, 0x40004000,
+ ram_size);
+ li->entry = 0x40004000;
+ }
+
+ if (image_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ li->image_filename);
+ exit(1);
+ }
+
+ if (li->cmdline && (kcmdline_len = strlen(li->cmdline))) {
+ if (kcmdline_len > 256) {
+ fprintf(stderr, "Too long CRIS kernel cmdline (max 256)\n");
+ exit(1);
+ }
+ pstrcpy_targphys("cmdline", 0x40000000, 256, li->cmdline);
+ }
+ qemu_register_reset(main_cpu_reset, cpu);
+}
diff --git a/hw/cris/boot.h b/hw/cris/boot.h
new file mode 100644
index 00000000..c4d3fa6f
--- /dev/null
+++ b/hw/cris/boot.h
@@ -0,0 +1,15 @@
+#ifndef _CRIS_BOOT_H
+#define HW_CRIS_BOOT_H 1
+
+struct cris_load_info
+{
+ const char *image_filename;
+ const char *cmdline;
+ int image_size;
+
+ hwaddr entry;
+};
+
+void cris_load_image(CRISCPU *cpu, struct cris_load_info *li);
+
+#endif
diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
new file mode 100644
index 00000000..dd8ea76d
--- /dev/null
+++ b/hw/display/Makefile.objs
@@ -0,0 +1,40 @@
+common-obj-$(CONFIG_ADS7846) += ads7846.o
+common-obj-$(CONFIG_VGA_CIRRUS) += cirrus_vga.o
+common-obj-$(CONFIG_G364FB) += g364fb.o
+common-obj-$(CONFIG_JAZZ_LED) += jazz_led.o
+common-obj-$(CONFIG_PL110) += pl110.o
+common-obj-$(CONFIG_SSD0303) += ssd0303.o
+common-obj-$(CONFIG_SSD0323) += ssd0323.o
+common-obj-$(CONFIG_XEN_BACKEND) += xenfb.o
+
+common-obj-$(CONFIG_VGA_PCI) += vga-pci.o
+common-obj-$(CONFIG_VGA_ISA) += vga-isa.o
+common-obj-$(CONFIG_VGA_ISA_MM) += vga-isa-mm.o
+common-obj-$(CONFIG_VMWARE_VGA) += vmware_vga.o
+
+common-obj-$(CONFIG_BLIZZARD) += blizzard.o
+common-obj-$(CONFIG_EXYNOS4) += exynos4210_fimd.o
+common-obj-$(CONFIG_FRAMEBUFFER) += framebuffer.o
+common-obj-$(CONFIG_MILKYMIST) += milkymist-vgafb.o
+common-obj-$(CONFIG_ZAURUS) += tc6393xb.o
+
+ifeq ($(CONFIG_MILKYMIST_TMU2),y)
+common-obj-y += milkymist-tmu2.o
+milkymist-tmu2.o-cflags := $(OPENGL_CFLAGS)
+milkymist-tmu2.o-libs += $(OPENGL_LIBS)
+endif
+
+obj-$(CONFIG_OMAP) += omap_dss.o
+obj-$(CONFIG_OMAP) += omap_lcdc.o
+obj-$(CONFIG_PXA2XX) += pxa2xx_lcd.o
+obj-$(CONFIG_SM501) += sm501.o
+obj-$(CONFIG_TCX) += tcx.o
+obj-$(CONFIG_CG3) += cg3.o
+
+obj-$(CONFIG_VGA) += vga.o
+
+common-obj-$(CONFIG_QXL) += qxl.o qxl-logger.o qxl-render.o
+
+obj-$(CONFIG_VIRTIO) += virtio-gpu.o
+obj-$(CONFIG_VIRTIO_PCI) += virtio-gpu-pci.o
+obj-$(CONFIG_VIRTIO_VGA) += virtio-vga.o
diff --git a/hw/display/ads7846.c b/hw/display/ads7846.c
new file mode 100644
index 00000000..3f35369b
--- /dev/null
+++ b/hw/display/ads7846.c
@@ -0,0 +1,177 @@
+/*
+ * TI ADS7846 / TSC2046 chip emulation.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/ssi.h"
+#include "ui/console.h"
+
+typedef struct {
+ SSISlave ssidev;
+ qemu_irq interrupt;
+
+ int input[8];
+ int pressure;
+ int noise;
+
+ int cycle;
+ int output;
+} ADS7846State;
+
+/* Control-byte bitfields */
+#define CB_PD0 (1 << 0)
+#define CB_PD1 (1 << 1)
+#define CB_SER (1 << 2)
+#define CB_MODE (1 << 3)
+#define CB_A0 (1 << 4)
+#define CB_A1 (1 << 5)
+#define CB_A2 (1 << 6)
+#define CB_START (1 << 7)
+
+#define X_AXIS_DMAX 3470
+#define X_AXIS_MIN 290
+#define Y_AXIS_DMAX 3450
+#define Y_AXIS_MIN 200
+
+#define ADS_VBAT 2000
+#define ADS_VAUX 2000
+#define ADS_TEMP0 2000
+#define ADS_TEMP1 3000
+#define ADS_XPOS(x, y) (X_AXIS_MIN + ((X_AXIS_DMAX * (x)) >> 15))
+#define ADS_YPOS(x, y) (Y_AXIS_MIN + ((Y_AXIS_DMAX * (y)) >> 15))
+#define ADS_Z1POS(x, y) 600
+#define ADS_Z2POS(x, y) (600 + 6000 / ADS_XPOS(x, y))
+
+static void ads7846_int_update(ADS7846State *s)
+{
+ if (s->interrupt)
+ qemu_set_irq(s->interrupt, s->pressure == 0);
+}
+
+static uint32_t ads7846_transfer(SSISlave *dev, uint32_t value)
+{
+ ADS7846State *s = FROM_SSI_SLAVE(ADS7846State, dev);
+
+ switch (s->cycle ++) {
+ case 0:
+ if (!(value & CB_START)) {
+ s->cycle = 0;
+ break;
+ }
+
+ s->output = s->input[(value >> 4) & 7];
+
+ /* Imitate the ADC noise, some drivers expect this. */
+ s->noise = (s->noise + 3) & 7;
+ switch ((value >> 4) & 7) {
+ case 1: s->output += s->noise ^ 2; break;
+ case 3: s->output += s->noise ^ 0; break;
+ case 4: s->output += s->noise ^ 7; break;
+ case 5: s->output += s->noise ^ 5; break;
+ }
+
+ if (value & CB_MODE)
+ s->output >>= 4; /* 8 bits instead of 12 */
+
+ break;
+ case 1:
+ s->cycle = 0;
+ break;
+ }
+ return s->output;
+}
+
+static void ads7846_ts_event(void *opaque,
+ int x, int y, int z, int buttons_state)
+{
+ ADS7846State *s = opaque;
+
+ if (buttons_state) {
+ x = 0x7fff - x;
+ s->input[1] = ADS_XPOS(x, y);
+ s->input[3] = ADS_Z1POS(x, y);
+ s->input[4] = ADS_Z2POS(x, y);
+ s->input[5] = ADS_YPOS(x, y);
+ }
+
+ if (s->pressure == !buttons_state) {
+ s->pressure = !!buttons_state;
+
+ ads7846_int_update(s);
+ }
+}
+
+static int ads7856_post_load(void *opaque, int version_id)
+{
+ ADS7846State *s = opaque;
+
+ s->pressure = 0;
+ ads7846_int_update(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_ads7846 = {
+ .name = "ads7846",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ads7856_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_SSI_SLAVE(ssidev, ADS7846State),
+ VMSTATE_INT32_ARRAY(input, ADS7846State, 8),
+ VMSTATE_INT32(noise, ADS7846State),
+ VMSTATE_INT32(cycle, ADS7846State),
+ VMSTATE_INT32(output, ADS7846State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int ads7846_init(SSISlave *d)
+{
+ DeviceState *dev = DEVICE(d);
+ ADS7846State *s = FROM_SSI_SLAVE(ADS7846State, d);
+
+ qdev_init_gpio_out(dev, &s->interrupt, 1);
+
+ s->input[0] = ADS_TEMP0; /* TEMP0 */
+ s->input[2] = ADS_VBAT; /* VBAT */
+ s->input[6] = ADS_VAUX; /* VAUX */
+ s->input[7] = ADS_TEMP1; /* TEMP1 */
+
+ /* We want absolute coordinates */
+ qemu_add_mouse_event_handler(ads7846_ts_event, s, 1,
+ "QEMU ADS7846-driven Touchscreen");
+
+ ads7846_int_update(s);
+
+ vmstate_register(NULL, -1, &vmstate_ads7846, s);
+ return 0;
+}
+
+static void ads7846_class_init(ObjectClass *klass, void *data)
+{
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = ads7846_init;
+ k->transfer = ads7846_transfer;
+}
+
+static const TypeInfo ads7846_info = {
+ .name = "ads7846",
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(ADS7846State),
+ .class_init = ads7846_class_init,
+};
+
+static void ads7846_register_types(void)
+{
+ type_register_static(&ads7846_info);
+}
+
+type_init(ads7846_register_types)
diff --git a/hw/display/blizzard.c b/hw/display/blizzard.c
new file mode 100644
index 00000000..5019bbbe
--- /dev/null
+++ b/hw/display/blizzard.c
@@ -0,0 +1,986 @@
+/*
+ * Epson S1D13744/S1D13745 (Blizzard/Hailstorm/Tornado) LCD/TV controller.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "ui/console.h"
+#include "hw/devices.h"
+#include "ui/pixel_ops.h"
+
+typedef void (*blizzard_fn_t)(uint8_t *, const uint8_t *, unsigned int);
+
+typedef struct {
+ uint8_t reg;
+ uint32_t addr;
+ int swallow;
+
+ int pll;
+ int pll_range;
+ int pll_ctrl;
+ uint8_t pll_mode;
+ uint8_t clksel;
+ int memenable;
+ int memrefresh;
+ uint8_t timing[3];
+ int priority;
+
+ uint8_t lcd_config;
+ int x;
+ int y;
+ int skipx;
+ int skipy;
+ uint8_t hndp;
+ uint8_t vndp;
+ uint8_t hsync;
+ uint8_t vsync;
+ uint8_t pclk;
+ uint8_t u;
+ uint8_t v;
+ uint8_t yrc[2];
+ int ix[2];
+ int iy[2];
+ int ox[2];
+ int oy[2];
+
+ int enable;
+ int blank;
+ int bpp;
+ int invalidate;
+ int mx[2];
+ int my[2];
+ uint8_t mode;
+ uint8_t effect;
+ uint8_t iformat;
+ uint8_t source;
+ QemuConsole *con;
+ blizzard_fn_t *line_fn_tab[2];
+ void *fb;
+
+ uint8_t hssi_config[3];
+ uint8_t tv_config;
+ uint8_t tv_timing[4];
+ uint8_t vbi;
+ uint8_t tv_x;
+ uint8_t tv_y;
+ uint8_t tv_test;
+ uint8_t tv_filter_config;
+ uint8_t tv_filter_idx;
+ uint8_t tv_filter_coeff[0x20];
+ uint8_t border_r;
+ uint8_t border_g;
+ uint8_t border_b;
+ uint8_t gamma_config;
+ uint8_t gamma_idx;
+ uint8_t gamma_lut[0x100];
+ uint8_t matrix_ena;
+ uint8_t matrix_coeff[0x12];
+ uint8_t matrix_r;
+ uint8_t matrix_g;
+ uint8_t matrix_b;
+ uint8_t pm;
+ uint8_t status;
+ uint8_t rgbgpio_dir;
+ uint8_t rgbgpio;
+ uint8_t gpio_dir;
+ uint8_t gpio;
+ uint8_t gpio_edge[2];
+ uint8_t gpio_irq;
+ uint8_t gpio_pdown;
+
+ struct {
+ int x;
+ int y;
+ int dx;
+ int dy;
+ int len;
+ int buflen;
+ void *buf;
+ void *data;
+ uint16_t *ptr;
+ int angle;
+ int pitch;
+ blizzard_fn_t line_fn;
+ } data;
+} BlizzardState;
+
+/* Bytes(!) per pixel */
+static const int blizzard_iformat_bpp[0x10] = {
+ 0,
+ 2, /* RGB 5:6:5*/
+ 3, /* RGB 6:6:6 mode 1 */
+ 3, /* RGB 8:8:8 mode 1 */
+ 0, 0,
+ 4, /* RGB 6:6:6 mode 2 */
+ 4, /* RGB 8:8:8 mode 2 */
+ 0, /* YUV 4:2:2 */
+ 0, /* YUV 4:2:0 */
+ 0, 0, 0, 0, 0, 0,
+};
+
+static void blizzard_window(BlizzardState *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint8_t *src, *dst;
+ int bypp[2];
+ int bypl[3];
+ int y;
+ blizzard_fn_t fn = s->data.line_fn;
+
+ if (!fn)
+ return;
+ if (s->mx[0] > s->data.x)
+ s->mx[0] = s->data.x;
+ if (s->my[0] > s->data.y)
+ s->my[0] = s->data.y;
+ if (s->mx[1] < s->data.x + s->data.dx)
+ s->mx[1] = s->data.x + s->data.dx;
+ if (s->my[1] < s->data.y + s->data.dy)
+ s->my[1] = s->data.y + s->data.dy;
+
+ bypp[0] = s->bpp;
+ bypp[1] = surface_bytes_per_pixel(surface);
+ bypl[0] = bypp[0] * s->data.pitch;
+ bypl[1] = bypp[1] * s->x;
+ bypl[2] = bypp[0] * s->data.dx;
+
+ src = s->data.data;
+ dst = s->fb + bypl[1] * s->data.y + bypp[1] * s->data.x;
+ for (y = s->data.dy; y > 0; y --, src += bypl[0], dst += bypl[1])
+ fn(dst, src, bypl[2]);
+}
+
+static int blizzard_transfer_setup(BlizzardState *s)
+{
+ if (s->source > 3 || !s->bpp ||
+ s->ix[1] < s->ix[0] || s->iy[1] < s->iy[0])
+ return 0;
+
+ s->data.angle = s->effect & 3;
+ s->data.line_fn = s->line_fn_tab[!!s->data.angle][s->iformat];
+ s->data.x = s->ix[0];
+ s->data.y = s->iy[0];
+ s->data.dx = s->ix[1] - s->ix[0] + 1;
+ s->data.dy = s->iy[1] - s->iy[0] + 1;
+ s->data.len = s->bpp * s->data.dx * s->data.dy;
+ s->data.pitch = s->data.dx;
+ if (s->data.len > s->data.buflen) {
+ s->data.buf = g_realloc(s->data.buf, s->data.len);
+ s->data.buflen = s->data.len;
+ }
+ s->data.ptr = s->data.buf;
+ s->data.data = s->data.buf;
+ s->data.len /= 2;
+ return 1;
+}
+
+static void blizzard_reset(BlizzardState *s)
+{
+ s->reg = 0;
+ s->swallow = 0;
+
+ s->pll = 9;
+ s->pll_range = 1;
+ s->pll_ctrl = 0x14;
+ s->pll_mode = 0x32;
+ s->clksel = 0x00;
+ s->memenable = 0;
+ s->memrefresh = 0x25c;
+ s->timing[0] = 0x3f;
+ s->timing[1] = 0x13;
+ s->timing[2] = 0x21;
+ s->priority = 0;
+
+ s->lcd_config = 0x74;
+ s->x = 8;
+ s->y = 1;
+ s->skipx = 0;
+ s->skipy = 0;
+ s->hndp = 3;
+ s->vndp = 2;
+ s->hsync = 1;
+ s->vsync = 1;
+ s->pclk = 0x80;
+
+ s->ix[0] = 0;
+ s->ix[1] = 0;
+ s->iy[0] = 0;
+ s->iy[1] = 0;
+ s->ox[0] = 0;
+ s->ox[1] = 0;
+ s->oy[0] = 0;
+ s->oy[1] = 0;
+
+ s->yrc[0] = 0x00;
+ s->yrc[1] = 0x30;
+ s->u = 0;
+ s->v = 0;
+
+ s->iformat = 3;
+ s->source = 0;
+ s->bpp = blizzard_iformat_bpp[s->iformat];
+
+ s->hssi_config[0] = 0x00;
+ s->hssi_config[1] = 0x00;
+ s->hssi_config[2] = 0x01;
+ s->tv_config = 0x00;
+ s->tv_timing[0] = 0x00;
+ s->tv_timing[1] = 0x00;
+ s->tv_timing[2] = 0x00;
+ s->tv_timing[3] = 0x00;
+ s->vbi = 0x10;
+ s->tv_x = 0x14;
+ s->tv_y = 0x03;
+ s->tv_test = 0x00;
+ s->tv_filter_config = 0x80;
+ s->tv_filter_idx = 0x00;
+ s->border_r = 0x10;
+ s->border_g = 0x80;
+ s->border_b = 0x80;
+ s->gamma_config = 0x00;
+ s->gamma_idx = 0x00;
+ s->matrix_ena = 0x00;
+ memset(&s->matrix_coeff, 0, sizeof(s->matrix_coeff));
+ s->matrix_r = 0x00;
+ s->matrix_g = 0x00;
+ s->matrix_b = 0x00;
+ s->pm = 0x02;
+ s->status = 0x00;
+ s->rgbgpio_dir = 0x00;
+ s->gpio_dir = 0x00;
+ s->gpio_edge[0] = 0x00;
+ s->gpio_edge[1] = 0x00;
+ s->gpio_irq = 0x00;
+ s->gpio_pdown = 0xff;
+}
+
+static inline void blizzard_invalidate_display(void *opaque) {
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ s->invalidate = 1;
+}
+
+static uint16_t blizzard_reg_read(void *opaque, uint8_t reg)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ switch (reg) {
+ case 0x00: /* Revision Code */
+ return 0xa5;
+
+ case 0x02: /* Configuration Readback */
+ return 0x83; /* Macrovision OK, CNF[2:0] = 3 */
+
+ case 0x04: /* PLL M-Divider */
+ return (s->pll - 1) | (1 << 7);
+ case 0x06: /* PLL Lock Range Control */
+ return s->pll_range;
+ case 0x08: /* PLL Lock Synthesis Control 0 */
+ return s->pll_ctrl & 0xff;
+ case 0x0a: /* PLL Lock Synthesis Control 1 */
+ return s->pll_ctrl >> 8;
+ case 0x0c: /* PLL Mode Control 0 */
+ return s->pll_mode;
+
+ case 0x0e: /* Clock-Source Select */
+ return s->clksel;
+
+ case 0x10: /* Memory Controller Activate */
+ case 0x14: /* Memory Controller Bank 0 Status Flag */
+ return s->memenable;
+
+ case 0x18: /* Auto-Refresh Interval Setting 0 */
+ return s->memrefresh & 0xff;
+ case 0x1a: /* Auto-Refresh Interval Setting 1 */
+ return s->memrefresh >> 8;
+
+ case 0x1c: /* Power-On Sequence Timing Control */
+ return s->timing[0];
+ case 0x1e: /* Timing Control 0 */
+ return s->timing[1];
+ case 0x20: /* Timing Control 1 */
+ return s->timing[2];
+
+ case 0x24: /* Arbitration Priority Control */
+ return s->priority;
+
+ case 0x28: /* LCD Panel Configuration */
+ return s->lcd_config;
+
+ case 0x2a: /* LCD Horizontal Display Width */
+ return s->x >> 3;
+ case 0x2c: /* LCD Horizontal Non-display Period */
+ return s->hndp;
+ case 0x2e: /* LCD Vertical Display Height 0 */
+ return s->y & 0xff;
+ case 0x30: /* LCD Vertical Display Height 1 */
+ return s->y >> 8;
+ case 0x32: /* LCD Vertical Non-display Period */
+ return s->vndp;
+ case 0x34: /* LCD HS Pulse-width */
+ return s->hsync;
+ case 0x36: /* LCd HS Pulse Start Position */
+ return s->skipx >> 3;
+ case 0x38: /* LCD VS Pulse-width */
+ return s->vsync;
+ case 0x3a: /* LCD VS Pulse Start Position */
+ return s->skipy;
+
+ case 0x3c: /* PCLK Polarity */
+ return s->pclk;
+
+ case 0x3e: /* High-speed Serial Interface Tx Configuration Port 0 */
+ return s->hssi_config[0];
+ case 0x40: /* High-speed Serial Interface Tx Configuration Port 1 */
+ return s->hssi_config[1];
+ case 0x42: /* High-speed Serial Interface Tx Mode */
+ return s->hssi_config[2];
+ case 0x44: /* TV Display Configuration */
+ return s->tv_config;
+ case 0x46 ... 0x4c: /* TV Vertical Blanking Interval Data bits */
+ return s->tv_timing[(reg - 0x46) >> 1];
+ case 0x4e: /* VBI: Closed Caption / XDS Control / Status */
+ return s->vbi;
+ case 0x50: /* TV Horizontal Start Position */
+ return s->tv_x;
+ case 0x52: /* TV Vertical Start Position */
+ return s->tv_y;
+ case 0x54: /* TV Test Pattern Setting */
+ return s->tv_test;
+ case 0x56: /* TV Filter Setting */
+ return s->tv_filter_config;
+ case 0x58: /* TV Filter Coefficient Index */
+ return s->tv_filter_idx;
+ case 0x5a: /* TV Filter Coefficient Data */
+ if (s->tv_filter_idx < 0x20)
+ return s->tv_filter_coeff[s->tv_filter_idx ++];
+ return 0;
+
+ case 0x60: /* Input YUV/RGB Translate Mode 0 */
+ return s->yrc[0];
+ case 0x62: /* Input YUV/RGB Translate Mode 1 */
+ return s->yrc[1];
+ case 0x64: /* U Data Fix */
+ return s->u;
+ case 0x66: /* V Data Fix */
+ return s->v;
+
+ case 0x68: /* Display Mode */
+ return s->mode;
+
+ case 0x6a: /* Special Effects */
+ return s->effect;
+
+ case 0x6c: /* Input Window X Start Position 0 */
+ return s->ix[0] & 0xff;
+ case 0x6e: /* Input Window X Start Position 1 */
+ return s->ix[0] >> 3;
+ case 0x70: /* Input Window Y Start Position 0 */
+ return s->ix[0] & 0xff;
+ case 0x72: /* Input Window Y Start Position 1 */
+ return s->ix[0] >> 3;
+ case 0x74: /* Input Window X End Position 0 */
+ return s->ix[1] & 0xff;
+ case 0x76: /* Input Window X End Position 1 */
+ return s->ix[1] >> 3;
+ case 0x78: /* Input Window Y End Position 0 */
+ return s->ix[1] & 0xff;
+ case 0x7a: /* Input Window Y End Position 1 */
+ return s->ix[1] >> 3;
+ case 0x7c: /* Output Window X Start Position 0 */
+ return s->ox[0] & 0xff;
+ case 0x7e: /* Output Window X Start Position 1 */
+ return s->ox[0] >> 3;
+ case 0x80: /* Output Window Y Start Position 0 */
+ return s->oy[0] & 0xff;
+ case 0x82: /* Output Window Y Start Position 1 */
+ return s->oy[0] >> 3;
+ case 0x84: /* Output Window X End Position 0 */
+ return s->ox[1] & 0xff;
+ case 0x86: /* Output Window X End Position 1 */
+ return s->ox[1] >> 3;
+ case 0x88: /* Output Window Y End Position 0 */
+ return s->oy[1] & 0xff;
+ case 0x8a: /* Output Window Y End Position 1 */
+ return s->oy[1] >> 3;
+
+ case 0x8c: /* Input Data Format */
+ return s->iformat;
+ case 0x8e: /* Data Source Select */
+ return s->source;
+ case 0x90: /* Display Memory Data Port */
+ return 0;
+
+ case 0xa8: /* Border Color 0 */
+ return s->border_r;
+ case 0xaa: /* Border Color 1 */
+ return s->border_g;
+ case 0xac: /* Border Color 2 */
+ return s->border_b;
+
+ case 0xb4: /* Gamma Correction Enable */
+ return s->gamma_config;
+ case 0xb6: /* Gamma Correction Table Index */
+ return s->gamma_idx;
+ case 0xb8: /* Gamma Correction Table Data */
+ return s->gamma_lut[s->gamma_idx ++];
+
+ case 0xba: /* 3x3 Matrix Enable */
+ return s->matrix_ena;
+ case 0xbc ... 0xde: /* Coefficient Registers */
+ return s->matrix_coeff[(reg - 0xbc) >> 1];
+ case 0xe0: /* 3x3 Matrix Red Offset */
+ return s->matrix_r;
+ case 0xe2: /* 3x3 Matrix Green Offset */
+ return s->matrix_g;
+ case 0xe4: /* 3x3 Matrix Blue Offset */
+ return s->matrix_b;
+
+ case 0xe6: /* Power-save */
+ return s->pm;
+ case 0xe8: /* Non-display Period Control / Status */
+ return s->status | (1 << 5);
+ case 0xea: /* RGB Interface Control */
+ return s->rgbgpio_dir;
+ case 0xec: /* RGB Interface Status */
+ return s->rgbgpio;
+ case 0xee: /* General-purpose IO Pins Configuration */
+ return s->gpio_dir;
+ case 0xf0: /* General-purpose IO Pins Status / Control */
+ return s->gpio;
+ case 0xf2: /* GPIO Positive Edge Interrupt Trigger */
+ return s->gpio_edge[0];
+ case 0xf4: /* GPIO Negative Edge Interrupt Trigger */
+ return s->gpio_edge[1];
+ case 0xf6: /* GPIO Interrupt Status */
+ return s->gpio_irq;
+ case 0xf8: /* GPIO Pull-down Control */
+ return s->gpio_pdown;
+
+ default:
+ fprintf(stderr, "%s: unknown register %02x\n", __FUNCTION__, reg);
+ return 0;
+ }
+}
+
+static void blizzard_reg_write(void *opaque, uint8_t reg, uint16_t value)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ switch (reg) {
+ case 0x04: /* PLL M-Divider */
+ s->pll = (value & 0x3f) + 1;
+ break;
+ case 0x06: /* PLL Lock Range Control */
+ s->pll_range = value & 3;
+ break;
+ case 0x08: /* PLL Lock Synthesis Control 0 */
+ s->pll_ctrl &= 0xf00;
+ s->pll_ctrl |= (value << 0) & 0x0ff;
+ break;
+ case 0x0a: /* PLL Lock Synthesis Control 1 */
+ s->pll_ctrl &= 0x0ff;
+ s->pll_ctrl |= (value << 8) & 0xf00;
+ break;
+ case 0x0c: /* PLL Mode Control 0 */
+ s->pll_mode = value & 0x77;
+ if ((value & 3) == 0 || (value & 3) == 3)
+ fprintf(stderr, "%s: wrong PLL Control bits (%i)\n",
+ __FUNCTION__, value & 3);
+ break;
+
+ case 0x0e: /* Clock-Source Select */
+ s->clksel = value & 0xff;
+ break;
+
+ case 0x10: /* Memory Controller Activate */
+ s->memenable = value & 1;
+ break;
+ case 0x14: /* Memory Controller Bank 0 Status Flag */
+ break;
+
+ case 0x18: /* Auto-Refresh Interval Setting 0 */
+ s->memrefresh &= 0xf00;
+ s->memrefresh |= (value << 0) & 0x0ff;
+ break;
+ case 0x1a: /* Auto-Refresh Interval Setting 1 */
+ s->memrefresh &= 0x0ff;
+ s->memrefresh |= (value << 8) & 0xf00;
+ break;
+
+ case 0x1c: /* Power-On Sequence Timing Control */
+ s->timing[0] = value & 0x7f;
+ break;
+ case 0x1e: /* Timing Control 0 */
+ s->timing[1] = value & 0x17;
+ break;
+ case 0x20: /* Timing Control 1 */
+ s->timing[2] = value & 0x35;
+ break;
+
+ case 0x24: /* Arbitration Priority Control */
+ s->priority = value & 1;
+ break;
+
+ case 0x28: /* LCD Panel Configuration */
+ s->lcd_config = value & 0xff;
+ if (value & (1 << 7))
+ fprintf(stderr, "%s: data swap not supported!\n", __FUNCTION__);
+ break;
+
+ case 0x2a: /* LCD Horizontal Display Width */
+ s->x = value << 3;
+ break;
+ case 0x2c: /* LCD Horizontal Non-display Period */
+ s->hndp = value & 0xff;
+ break;
+ case 0x2e: /* LCD Vertical Display Height 0 */
+ s->y &= 0x300;
+ s->y |= (value << 0) & 0x0ff;
+ break;
+ case 0x30: /* LCD Vertical Display Height 1 */
+ s->y &= 0x0ff;
+ s->y |= (value << 8) & 0x300;
+ break;
+ case 0x32: /* LCD Vertical Non-display Period */
+ s->vndp = value & 0xff;
+ break;
+ case 0x34: /* LCD HS Pulse-width */
+ s->hsync = value & 0xff;
+ break;
+ case 0x36: /* LCD HS Pulse Start Position */
+ s->skipx = value & 0xff;
+ break;
+ case 0x38: /* LCD VS Pulse-width */
+ s->vsync = value & 0xbf;
+ break;
+ case 0x3a: /* LCD VS Pulse Start Position */
+ s->skipy = value & 0xff;
+ break;
+
+ case 0x3c: /* PCLK Polarity */
+ s->pclk = value & 0x82;
+ /* Affects calculation of s->hndp, s->hsync and s->skipx. */
+ break;
+
+ case 0x3e: /* High-speed Serial Interface Tx Configuration Port 0 */
+ s->hssi_config[0] = value;
+ break;
+ case 0x40: /* High-speed Serial Interface Tx Configuration Port 1 */
+ s->hssi_config[1] = value;
+ if (((value >> 4) & 3) == 3)
+ fprintf(stderr, "%s: Illegal active-data-links value\n",
+ __FUNCTION__);
+ break;
+ case 0x42: /* High-speed Serial Interface Tx Mode */
+ s->hssi_config[2] = value & 0xbd;
+ break;
+
+ case 0x44: /* TV Display Configuration */
+ s->tv_config = value & 0xfe;
+ break;
+ case 0x46 ... 0x4c: /* TV Vertical Blanking Interval Data bits 0 */
+ s->tv_timing[(reg - 0x46) >> 1] = value;
+ break;
+ case 0x4e: /* VBI: Closed Caption / XDS Control / Status */
+ s->vbi = value;
+ break;
+ case 0x50: /* TV Horizontal Start Position */
+ s->tv_x = value;
+ break;
+ case 0x52: /* TV Vertical Start Position */
+ s->tv_y = value & 0x7f;
+ break;
+ case 0x54: /* TV Test Pattern Setting */
+ s->tv_test = value;
+ break;
+ case 0x56: /* TV Filter Setting */
+ s->tv_filter_config = value & 0xbf;
+ break;
+ case 0x58: /* TV Filter Coefficient Index */
+ s->tv_filter_idx = value & 0x1f;
+ break;
+ case 0x5a: /* TV Filter Coefficient Data */
+ if (s->tv_filter_idx < 0x20)
+ s->tv_filter_coeff[s->tv_filter_idx ++] = value;
+ break;
+
+ case 0x60: /* Input YUV/RGB Translate Mode 0 */
+ s->yrc[0] = value & 0xb0;
+ break;
+ case 0x62: /* Input YUV/RGB Translate Mode 1 */
+ s->yrc[1] = value & 0x30;
+ break;
+ case 0x64: /* U Data Fix */
+ s->u = value & 0xff;
+ break;
+ case 0x66: /* V Data Fix */
+ s->v = value & 0xff;
+ break;
+
+ case 0x68: /* Display Mode */
+ if ((s->mode ^ value) & 3)
+ s->invalidate = 1;
+ s->mode = value & 0xb7;
+ s->enable = value & 1;
+ s->blank = (value >> 1) & 1;
+ if (value & (1 << 4))
+ fprintf(stderr, "%s: Macrovision enable attempt!\n", __FUNCTION__);
+ break;
+
+ case 0x6a: /* Special Effects */
+ s->effect = value & 0xfb;
+ break;
+
+ case 0x6c: /* Input Window X Start Position 0 */
+ s->ix[0] &= 0x300;
+ s->ix[0] |= (value << 0) & 0x0ff;
+ break;
+ case 0x6e: /* Input Window X Start Position 1 */
+ s->ix[0] &= 0x0ff;
+ s->ix[0] |= (value << 8) & 0x300;
+ break;
+ case 0x70: /* Input Window Y Start Position 0 */
+ s->iy[0] &= 0x300;
+ s->iy[0] |= (value << 0) & 0x0ff;
+ break;
+ case 0x72: /* Input Window Y Start Position 1 */
+ s->iy[0] &= 0x0ff;
+ s->iy[0] |= (value << 8) & 0x300;
+ break;
+ case 0x74: /* Input Window X End Position 0 */
+ s->ix[1] &= 0x300;
+ s->ix[1] |= (value << 0) & 0x0ff;
+ break;
+ case 0x76: /* Input Window X End Position 1 */
+ s->ix[1] &= 0x0ff;
+ s->ix[1] |= (value << 8) & 0x300;
+ break;
+ case 0x78: /* Input Window Y End Position 0 */
+ s->iy[1] &= 0x300;
+ s->iy[1] |= (value << 0) & 0x0ff;
+ break;
+ case 0x7a: /* Input Window Y End Position 1 */
+ s->iy[1] &= 0x0ff;
+ s->iy[1] |= (value << 8) & 0x300;
+ break;
+ case 0x7c: /* Output Window X Start Position 0 */
+ s->ox[0] &= 0x300;
+ s->ox[0] |= (value << 0) & 0x0ff;
+ break;
+ case 0x7e: /* Output Window X Start Position 1 */
+ s->ox[0] &= 0x0ff;
+ s->ox[0] |= (value << 8) & 0x300;
+ break;
+ case 0x80: /* Output Window Y Start Position 0 */
+ s->oy[0] &= 0x300;
+ s->oy[0] |= (value << 0) & 0x0ff;
+ break;
+ case 0x82: /* Output Window Y Start Position 1 */
+ s->oy[0] &= 0x0ff;
+ s->oy[0] |= (value << 8) & 0x300;
+ break;
+ case 0x84: /* Output Window X End Position 0 */
+ s->ox[1] &= 0x300;
+ s->ox[1] |= (value << 0) & 0x0ff;
+ break;
+ case 0x86: /* Output Window X End Position 1 */
+ s->ox[1] &= 0x0ff;
+ s->ox[1] |= (value << 8) & 0x300;
+ break;
+ case 0x88: /* Output Window Y End Position 0 */
+ s->oy[1] &= 0x300;
+ s->oy[1] |= (value << 0) & 0x0ff;
+ break;
+ case 0x8a: /* Output Window Y End Position 1 */
+ s->oy[1] &= 0x0ff;
+ s->oy[1] |= (value << 8) & 0x300;
+ break;
+
+ case 0x8c: /* Input Data Format */
+ s->iformat = value & 0xf;
+ s->bpp = blizzard_iformat_bpp[s->iformat];
+ if (!s->bpp)
+ fprintf(stderr, "%s: Illegal or unsupported input format %x\n",
+ __FUNCTION__, s->iformat);
+ break;
+ case 0x8e: /* Data Source Select */
+ s->source = value & 7;
+ /* Currently all windows will be "destructive overlays". */
+ if ((!(s->effect & (1 << 3)) && (s->ix[0] != s->ox[0] ||
+ s->iy[0] != s->oy[0] ||
+ s->ix[1] != s->ox[1] ||
+ s->iy[1] != s->oy[1])) ||
+ !((s->ix[1] - s->ix[0]) & (s->iy[1] - s->iy[0]) &
+ (s->ox[1] - s->ox[0]) & (s->oy[1] - s->oy[0]) & 1))
+ fprintf(stderr, "%s: Illegal input/output window positions\n",
+ __FUNCTION__);
+
+ blizzard_transfer_setup(s);
+ break;
+
+ case 0x90: /* Display Memory Data Port */
+ if (!s->data.len && !blizzard_transfer_setup(s))
+ break;
+
+ *s->data.ptr ++ = value;
+ if (-- s->data.len == 0)
+ blizzard_window(s);
+ break;
+
+ case 0xa8: /* Border Color 0 */
+ s->border_r = value;
+ break;
+ case 0xaa: /* Border Color 1 */
+ s->border_g = value;
+ break;
+ case 0xac: /* Border Color 2 */
+ s->border_b = value;
+ break;
+
+ case 0xb4: /* Gamma Correction Enable */
+ s->gamma_config = value & 0x87;
+ break;
+ case 0xb6: /* Gamma Correction Table Index */
+ s->gamma_idx = value;
+ break;
+ case 0xb8: /* Gamma Correction Table Data */
+ s->gamma_lut[s->gamma_idx ++] = value;
+ break;
+
+ case 0xba: /* 3x3 Matrix Enable */
+ s->matrix_ena = value & 1;
+ break;
+ case 0xbc ... 0xde: /* Coefficient Registers */
+ s->matrix_coeff[(reg - 0xbc) >> 1] = value & ((reg & 2) ? 0x80 : 0xff);
+ break;
+ case 0xe0: /* 3x3 Matrix Red Offset */
+ s->matrix_r = value;
+ break;
+ case 0xe2: /* 3x3 Matrix Green Offset */
+ s->matrix_g = value;
+ break;
+ case 0xe4: /* 3x3 Matrix Blue Offset */
+ s->matrix_b = value;
+ break;
+
+ case 0xe6: /* Power-save */
+ s->pm = value & 0x83;
+ if (value & s->mode & 1)
+ fprintf(stderr, "%s: The display must be disabled before entering "
+ "Standby Mode\n", __FUNCTION__);
+ break;
+ case 0xe8: /* Non-display Period Control / Status */
+ s->status = value & 0x1b;
+ break;
+ case 0xea: /* RGB Interface Control */
+ s->rgbgpio_dir = value & 0x8f;
+ break;
+ case 0xec: /* RGB Interface Status */
+ s->rgbgpio = value & 0xcf;
+ break;
+ case 0xee: /* General-purpose IO Pins Configuration */
+ s->gpio_dir = value;
+ break;
+ case 0xf0: /* General-purpose IO Pins Status / Control */
+ s->gpio = value;
+ break;
+ case 0xf2: /* GPIO Positive Edge Interrupt Trigger */
+ s->gpio_edge[0] = value;
+ break;
+ case 0xf4: /* GPIO Negative Edge Interrupt Trigger */
+ s->gpio_edge[1] = value;
+ break;
+ case 0xf6: /* GPIO Interrupt Status */
+ s->gpio_irq &= value;
+ break;
+ case 0xf8: /* GPIO Pull-down Control */
+ s->gpio_pdown = value;
+ break;
+
+ default:
+ fprintf(stderr, "%s: unknown register %02x\n", __FUNCTION__, reg);
+ break;
+ }
+}
+
+uint16_t s1d13745_read(void *opaque, int dc)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+ uint16_t value = blizzard_reg_read(s, s->reg);
+
+ if (s->swallow -- > 0)
+ return 0;
+ if (dc)
+ s->reg ++;
+
+ return value;
+}
+
+void s1d13745_write(void *opaque, int dc, uint16_t value)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ if (s->swallow -- > 0)
+ return;
+ if (dc) {
+ blizzard_reg_write(s, s->reg, value);
+
+ if (s->reg != 0x90 && s->reg != 0x5a && s->reg != 0xb8)
+ s->reg += 2;
+ } else
+ s->reg = value & 0xff;
+}
+
+void s1d13745_write_block(void *opaque, int dc,
+ void *buf, size_t len, int pitch)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ while (len > 0) {
+ if (s->reg == 0x90 && dc &&
+ (s->data.len || blizzard_transfer_setup(s)) &&
+ len >= (s->data.len << 1)) {
+ len -= s->data.len << 1;
+ s->data.len = 0;
+ s->data.data = buf;
+ if (pitch)
+ s->data.pitch = pitch;
+ blizzard_window(s);
+ s->data.data = s->data.buf;
+ continue;
+ }
+
+ s1d13745_write(opaque, dc, *(uint16_t *) buf);
+ len -= 2;
+ buf += 2;
+ }
+}
+
+static void blizzard_update_display(void *opaque)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int y, bypp, bypl, bwidth;
+ uint8_t *src, *dst;
+
+ if (!s->enable)
+ return;
+
+ if (s->x != surface_width(surface) || s->y != surface_height(surface)) {
+ s->invalidate = 1;
+ qemu_console_resize(s->con, s->x, s->y);
+ surface = qemu_console_surface(s->con);
+ }
+
+ if (s->invalidate) {
+ s->invalidate = 0;
+
+ if (s->blank) {
+ bypp = surface_bytes_per_pixel(surface);
+ memset(surface_data(surface), 0, bypp * s->x * s->y);
+ return;
+ }
+
+ s->mx[0] = 0;
+ s->mx[1] = s->x;
+ s->my[0] = 0;
+ s->my[1] = s->y;
+ }
+
+ if (s->mx[1] <= s->mx[0])
+ return;
+
+ bypp = surface_bytes_per_pixel(surface);
+ bypl = bypp * s->x;
+ bwidth = bypp * (s->mx[1] - s->mx[0]);
+ y = s->my[0];
+ src = s->fb + bypl * y + bypp * s->mx[0];
+ dst = surface_data(surface) + bypl * y + bypp * s->mx[0];
+ for (; y < s->my[1]; y ++, src += bypl, dst += bypl)
+ memcpy(dst, src, bwidth);
+
+ dpy_gfx_update(s->con, s->mx[0], s->my[0],
+ s->mx[1] - s->mx[0], y - s->my[0]);
+
+ s->mx[0] = s->x;
+ s->mx[1] = 0;
+ s->my[0] = s->y;
+ s->my[1] = 0;
+}
+
+#define DEPTH 8
+#include "blizzard_template.h"
+#define DEPTH 15
+#include "blizzard_template.h"
+#define DEPTH 16
+#include "blizzard_template.h"
+#define DEPTH 24
+#include "blizzard_template.h"
+#define DEPTH 32
+#include "blizzard_template.h"
+
+static const GraphicHwOps blizzard_ops = {
+ .invalidate = blizzard_invalidate_display,
+ .gfx_update = blizzard_update_display,
+};
+
+void *s1d13745_init(qemu_irq gpio_int)
+{
+ BlizzardState *s = (BlizzardState *) g_malloc0(sizeof(*s));
+ DisplaySurface *surface;
+
+ s->fb = g_malloc(0x180000);
+
+ s->con = graphic_console_init(NULL, 0, &blizzard_ops, s);
+ surface = qemu_console_surface(s->con);
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ s->line_fn_tab[0] = s->line_fn_tab[1] =
+ g_malloc0(sizeof(blizzard_fn_t) * 0x10);
+ break;
+ case 8:
+ s->line_fn_tab[0] = blizzard_draw_fn_8;
+ s->line_fn_tab[1] = blizzard_draw_fn_r_8;
+ break;
+ case 15:
+ s->line_fn_tab[0] = blizzard_draw_fn_15;
+ s->line_fn_tab[1] = blizzard_draw_fn_r_15;
+ break;
+ case 16:
+ s->line_fn_tab[0] = blizzard_draw_fn_16;
+ s->line_fn_tab[1] = blizzard_draw_fn_r_16;
+ break;
+ case 24:
+ s->line_fn_tab[0] = blizzard_draw_fn_24;
+ s->line_fn_tab[1] = blizzard_draw_fn_r_24;
+ break;
+ case 32:
+ s->line_fn_tab[0] = blizzard_draw_fn_32;
+ s->line_fn_tab[1] = blizzard_draw_fn_r_32;
+ break;
+ default:
+ fprintf(stderr, "%s: Bad color depth\n", __FUNCTION__);
+ exit(1);
+ }
+
+ blizzard_reset(s);
+
+ return s;
+}
diff --git a/hw/display/blizzard_template.h b/hw/display/blizzard_template.h
new file mode 100644
index 00000000..b7ef27c8
--- /dev/null
+++ b/hw/display/blizzard_template.h
@@ -0,0 +1,146 @@
+/*
+ * QEMU Epson S1D13744/S1D13745 templates
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define SKIP_PIXEL(to) (to += deststep)
+#if DEPTH == 8
+# define PIXEL_TYPE uint8_t
+# define COPY_PIXEL(to, from) do { *to = from; SKIP_PIXEL(to); } while (0)
+# define COPY_PIXEL1(to, from) (*to++ = from)
+#elif DEPTH == 15 || DEPTH == 16
+# define PIXEL_TYPE uint16_t
+# define COPY_PIXEL(to, from) do { *to = from; SKIP_PIXEL(to); } while (0)
+# define COPY_PIXEL1(to, from) (*to++ = from)
+#elif DEPTH == 24
+# define PIXEL_TYPE uint8_t
+# define COPY_PIXEL(to, from) \
+ do { \
+ to[0] = from; \
+ to[1] = (from) >> 8; \
+ to[2] = (from) >> 16; \
+ SKIP_PIXEL(to); \
+ } while (0)
+
+# define COPY_PIXEL1(to, from) \
+ do { \
+ *to++ = from; \
+ *to++ = (from) >> 8; \
+ *to++ = (from) >> 16; \
+ } while (0)
+#elif DEPTH == 32
+# define PIXEL_TYPE uint32_t
+# define COPY_PIXEL(to, from) do { *to = from; SKIP_PIXEL(to); } while (0)
+# define COPY_PIXEL1(to, from) (*to++ = from)
+#else
+# error unknown bit depth
+#endif
+
+#ifdef HOST_WORDS_BIGENDIAN
+# define SWAP_WORDS 1
+#endif
+
+static void glue(blizzard_draw_line16_, DEPTH)(PIXEL_TYPE *dest,
+ const uint16_t *src, unsigned int width)
+{
+#if !defined(SWAP_WORDS) && DEPTH == 16
+ memcpy(dest, src, width);
+#else
+ uint16_t data;
+ unsigned int r, g, b;
+ const uint16_t *end = (const void *) src + width;
+ while (src < end) {
+ data = *src ++;
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x1f) << 3;
+ data >>= 5;
+ COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r, g, b));
+ }
+#endif
+}
+
+static void glue(blizzard_draw_line24mode1_, DEPTH)(PIXEL_TYPE *dest,
+ const uint8_t *src, unsigned int width)
+{
+ /* TODO: check if SDL 24-bit planes are not in the same format and
+ * if so, use memcpy */
+ unsigned int r[2], g[2], b[2];
+ const uint8_t *end = src + width;
+ while (src < end) {
+ g[0] = *src ++;
+ r[0] = *src ++;
+ r[1] = *src ++;
+ b[0] = *src ++;
+ COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r[0], g[0], b[0]));
+ b[1] = *src ++;
+ g[1] = *src ++;
+ COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r[1], g[1], b[1]));
+ }
+}
+
+static void glue(blizzard_draw_line24mode2_, DEPTH)(PIXEL_TYPE *dest,
+ const uint8_t *src, unsigned int width)
+{
+ unsigned int r, g, b;
+ const uint8_t *end = src + width;
+ while (src < end) {
+ r = *src ++;
+ src ++;
+ b = *src ++;
+ g = *src ++;
+ COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r, g, b));
+ }
+}
+
+/* No rotation */
+static blizzard_fn_t glue(blizzard_draw_fn_, DEPTH)[0x10] = {
+ NULL,
+ /* RGB 5:6:5*/
+ (blizzard_fn_t) glue(blizzard_draw_line16_, DEPTH),
+ /* RGB 6:6:6 mode 1 */
+ (blizzard_fn_t) glue(blizzard_draw_line24mode1_, DEPTH),
+ /* RGB 8:8:8 mode 1 */
+ (blizzard_fn_t) glue(blizzard_draw_line24mode1_, DEPTH),
+ NULL, NULL,
+ /* RGB 6:6:6 mode 2 */
+ (blizzard_fn_t) glue(blizzard_draw_line24mode2_, DEPTH),
+ /* RGB 8:8:8 mode 2 */
+ (blizzard_fn_t) glue(blizzard_draw_line24mode2_, DEPTH),
+ /* YUV 4:2:2 */
+ NULL,
+ /* YUV 4:2:0 */
+ NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+};
+
+/* 90deg, 180deg and 270deg rotation */
+static blizzard_fn_t glue(blizzard_draw_fn_r_, DEPTH)[0x10] = {
+ /* TODO */
+ [0 ... 0xf] = NULL,
+};
+
+#undef DEPTH
+#undef SKIP_PIXEL
+#undef COPY_PIXEL
+#undef COPY_PIXEL1
+#undef PIXEL_TYPE
+
+#undef SWAP_WORDS
diff --git a/hw/display/cg3.c b/hw/display/cg3.c
new file mode 100644
index 00000000..2d3bd708
--- /dev/null
+++ b/hw/display/cg3.c
@@ -0,0 +1,397 @@
+/*
+ * QEMU CG3 Frame buffer
+ *
+ * Copyright (c) 2012 Bob Breuer
+ * Copyright (c) 2013 Mark Cave-Ayland
+ *
+ * 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.
+ */
+
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "ui/console.h"
+#include "hw/sysbus.h"
+#include "hw/loader.h"
+
+/* Change to 1 to enable debugging */
+#define DEBUG_CG3 0
+
+#define CG3_ROM_FILE "QEMU,cgthree.bin"
+#define FCODE_MAX_ROM_SIZE 0x10000
+
+#define CG3_REG_SIZE 0x20
+
+#define CG3_REG_BT458_ADDR 0x0
+#define CG3_REG_BT458_COLMAP 0x4
+#define CG3_REG_FBC_CTRL 0x10
+#define CG3_REG_FBC_STATUS 0x11
+#define CG3_REG_FBC_CURSTART 0x12
+#define CG3_REG_FBC_CUREND 0x13
+#define CG3_REG_FBC_VCTRL 0x14
+
+/* Control register flags */
+#define CG3_CR_ENABLE_INTS 0x80
+
+/* Status register flags */
+#define CG3_SR_PENDING_INT 0x80
+#define CG3_SR_1152_900_76_B 0x60
+#define CG3_SR_ID_COLOR 0x01
+
+#define CG3_VRAM_SIZE 0x100000
+#define CG3_VRAM_OFFSET 0x800000
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_CG3) { \
+ printf("CG3: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0);
+
+#define TYPE_CG3 "cgthree"
+#define CG3(obj) OBJECT_CHECK(CG3State, (obj), TYPE_CG3)
+
+typedef struct CG3State {
+ SysBusDevice parent_obj;
+
+ QemuConsole *con;
+ qemu_irq irq;
+ hwaddr prom_addr;
+ MemoryRegion vram_mem;
+ MemoryRegion rom;
+ MemoryRegion reg;
+ uint32_t vram_size;
+ int full_update;
+ uint8_t regs[16];
+ uint8_t r[256], g[256], b[256];
+ uint16_t width, height, depth;
+ uint8_t dac_index, dac_state;
+} CG3State;
+
+static void cg3_update_display(void *opaque)
+{
+ CG3State *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ const uint8_t *pix;
+ uint32_t *data;
+ uint32_t dval;
+ int x, y, y_start;
+ unsigned int width, height;
+ ram_addr_t page, page_min, page_max;
+
+ if (surface_bits_per_pixel(surface) != 32) {
+ return;
+ }
+ width = s->width;
+ height = s->height;
+
+ y_start = -1;
+ page_min = -1;
+ page_max = 0;
+ page = 0;
+ pix = memory_region_get_ram_ptr(&s->vram_mem);
+ data = (uint32_t *)surface_data(surface);
+
+ memory_region_sync_dirty_bitmap(&s->vram_mem);
+ for (y = 0; y < height; y++) {
+ int update = s->full_update;
+
+ page = (y * width) & TARGET_PAGE_MASK;
+ update |= memory_region_get_dirty(&s->vram_mem, page, page + width,
+ DIRTY_MEMORY_VGA);
+ if (update) {
+ if (y_start < 0) {
+ y_start = y;
+ }
+ if (page < page_min) {
+ page_min = page;
+ }
+ if (page > page_max) {
+ page_max = page;
+ }
+
+ for (x = 0; x < width; x++) {
+ dval = *pix++;
+ dval = (s->r[dval] << 16) | (s->g[dval] << 8) | s->b[dval];
+ *data++ = dval;
+ }
+ } else {
+ if (y_start >= 0) {
+ dpy_gfx_update(s->con, 0, y_start, s->width, y - y_start);
+ y_start = -1;
+ }
+ pix += width;
+ data += width;
+ }
+ }
+ s->full_update = 0;
+ if (y_start >= 0) {
+ dpy_gfx_update(s->con, 0, y_start, s->width, y - y_start);
+ }
+ if (page_max >= page_min) {
+ memory_region_reset_dirty(&s->vram_mem,
+ page_min, page_max - page_min + TARGET_PAGE_SIZE,
+ DIRTY_MEMORY_VGA);
+ }
+ /* vsync interrupt? */
+ if (s->regs[0] & CG3_CR_ENABLE_INTS) {
+ s->regs[1] |= CG3_SR_PENDING_INT;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static void cg3_invalidate_display(void *opaque)
+{
+ CG3State *s = opaque;
+
+ memory_region_set_dirty(&s->vram_mem, 0, CG3_VRAM_SIZE);
+}
+
+static uint64_t cg3_reg_read(void *opaque, hwaddr addr, unsigned size)
+{
+ CG3State *s = opaque;
+ int val;
+
+ switch (addr) {
+ case CG3_REG_BT458_ADDR:
+ case CG3_REG_BT458_COLMAP:
+ val = 0;
+ break;
+ case CG3_REG_FBC_CTRL:
+ val = s->regs[0];
+ break;
+ case CG3_REG_FBC_STATUS:
+ /* monitor ID 6, board type = 1 (color) */
+ val = s->regs[1] | CG3_SR_1152_900_76_B | CG3_SR_ID_COLOR;
+ break;
+ case CG3_REG_FBC_CURSTART ... CG3_REG_SIZE - 1:
+ val = s->regs[addr - 0x10];
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "cg3: Unimplemented register read "
+ "reg 0x%" HWADDR_PRIx " size 0x%x\n",
+ addr, size);
+ val = 0;
+ break;
+ }
+ DPRINTF("read %02x from reg %" HWADDR_PRIx "\n", val, addr);
+ return val;
+}
+
+static void cg3_reg_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ CG3State *s = opaque;
+ uint8_t regval;
+ int i;
+
+ DPRINTF("write %" PRIx64 " to reg %" HWADDR_PRIx " size %d\n",
+ val, addr, size);
+
+ switch (addr) {
+ case CG3_REG_BT458_ADDR:
+ s->dac_index = val;
+ s->dac_state = 0;
+ break;
+ case CG3_REG_BT458_COLMAP:
+ /* This register can be written to as either a long word or a byte */
+ if (size == 1) {
+ val <<= 24;
+ }
+
+ for (i = 0; i < size; i++) {
+ regval = val >> 24;
+
+ switch (s->dac_state) {
+ case 0:
+ s->r[s->dac_index] = regval;
+ s->dac_state++;
+ break;
+ case 1:
+ s->g[s->dac_index] = regval;
+ s->dac_state++;
+ break;
+ case 2:
+ s->b[s->dac_index] = regval;
+ /* Index autoincrement */
+ s->dac_index = (s->dac_index + 1) & 0xff;
+ default:
+ s->dac_state = 0;
+ break;
+ }
+ val <<= 8;
+ }
+ s->full_update = 1;
+ break;
+ case CG3_REG_FBC_CTRL:
+ s->regs[0] = val;
+ break;
+ case CG3_REG_FBC_STATUS:
+ if (s->regs[1] & CG3_SR_PENDING_INT) {
+ /* clear interrupt */
+ s->regs[1] &= ~CG3_SR_PENDING_INT;
+ qemu_irq_lower(s->irq);
+ }
+ break;
+ case CG3_REG_FBC_CURSTART ... CG3_REG_SIZE - 1:
+ s->regs[addr - 0x10] = val;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "cg3: Unimplemented register write "
+ "reg 0x%" HWADDR_PRIx " size 0x%x value 0x%" PRIx64 "\n",
+ addr, size, val);
+ break;
+ }
+}
+
+static const MemoryRegionOps cg3_reg_ops = {
+ .read = cg3_reg_read,
+ .write = cg3_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static const GraphicHwOps cg3_ops = {
+ .invalidate = cg3_invalidate_display,
+ .gfx_update = cg3_update_display,
+};
+
+static void cg3_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ CG3State *s = CG3(obj);
+
+ memory_region_init_ram(&s->rom, obj, "cg3.prom", FCODE_MAX_ROM_SIZE,
+ &error_abort);
+ memory_region_set_readonly(&s->rom, true);
+ sysbus_init_mmio(sbd, &s->rom);
+
+ memory_region_init_io(&s->reg, obj, &cg3_reg_ops, s, "cg3.reg",
+ CG3_REG_SIZE);
+ sysbus_init_mmio(sbd, &s->reg);
+}
+
+static void cg3_realizefn(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ CG3State *s = CG3(dev);
+ int ret;
+ char *fcode_filename;
+
+ /* FCode ROM */
+ vmstate_register_ram_global(&s->rom);
+ fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, CG3_ROM_FILE);
+ if (fcode_filename) {
+ ret = load_image_targphys(fcode_filename, s->prom_addr,
+ FCODE_MAX_ROM_SIZE);
+ g_free(fcode_filename);
+ if (ret < 0 || ret > FCODE_MAX_ROM_SIZE) {
+ error_report("cg3: could not load prom '%s'", CG3_ROM_FILE);
+ }
+ }
+
+ memory_region_init_ram(&s->vram_mem, NULL, "cg3.vram", s->vram_size,
+ &error_abort);
+ memory_region_set_log(&s->vram_mem, true, DIRTY_MEMORY_VGA);
+ vmstate_register_ram_global(&s->vram_mem);
+ sysbus_init_mmio(sbd, &s->vram_mem);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->con = graphic_console_init(DEVICE(dev), 0, &cg3_ops, s);
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static int vmstate_cg3_post_load(void *opaque, int version_id)
+{
+ CG3State *s = opaque;
+
+ cg3_invalidate_display(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_cg3 = {
+ .name = "cg3",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = vmstate_cg3_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(height, CG3State),
+ VMSTATE_UINT16(width, CG3State),
+ VMSTATE_UINT16(depth, CG3State),
+ VMSTATE_BUFFER(r, CG3State),
+ VMSTATE_BUFFER(g, CG3State),
+ VMSTATE_BUFFER(b, CG3State),
+ VMSTATE_UINT8(dac_index, CG3State),
+ VMSTATE_UINT8(dac_state, CG3State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cg3_reset(DeviceState *d)
+{
+ CG3State *s = CG3(d);
+
+ /* Initialize palette */
+ memset(s->r, 0, 256);
+ memset(s->g, 0, 256);
+ memset(s->b, 0, 256);
+
+ s->dac_state = 0;
+ s->full_update = 1;
+ qemu_irq_lower(s->irq);
+}
+
+static Property cg3_properties[] = {
+ DEFINE_PROP_UINT32("vram-size", CG3State, vram_size, -1),
+ DEFINE_PROP_UINT16("width", CG3State, width, -1),
+ DEFINE_PROP_UINT16("height", CG3State, height, -1),
+ DEFINE_PROP_UINT16("depth", CG3State, depth, -1),
+ DEFINE_PROP_UINT64("prom-addr", CG3State, prom_addr, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void cg3_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = cg3_realizefn;
+ dc->reset = cg3_reset;
+ dc->vmsd = &vmstate_cg3;
+ dc->props = cg3_properties;
+}
+
+static const TypeInfo cg3_info = {
+ .name = TYPE_CG3,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CG3State),
+ .instance_init = cg3_initfn,
+ .class_init = cg3_class_init,
+};
+
+static void cg3_register_types(void)
+{
+ type_register_static(&cg3_info);
+}
+
+type_init(cg3_register_types)
diff --git a/hw/display/cirrus_vga.c b/hw/display/cirrus_vga.c
new file mode 100644
index 00000000..5198037d
--- /dev/null
+++ b/hw/display/cirrus_vga.c
@@ -0,0 +1,3089 @@
+/*
+ * QEMU Cirrus CLGD 54xx VGA Emulator.
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ * Copyright (c) 2004 Makoto Suzuki (suzu)
+ *
+ * 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.
+ */
+/*
+ * Reference: Finn Thogersons' VGADOC4b
+ * available at http://home.worldonline.dk/~finth/
+ */
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "vga_int.h"
+#include "hw/loader.h"
+
+/*
+ * TODO:
+ * - destination write mask support not complete (bits 5..7)
+ * - optimize linear mappings
+ * - optimize bitblt functions
+ */
+
+//#define DEBUG_CIRRUS
+//#define DEBUG_BITBLT
+
+/***************************************
+ *
+ * definitions
+ *
+ ***************************************/
+
+// ID
+#define CIRRUS_ID_CLGD5422 (0x23<<2)
+#define CIRRUS_ID_CLGD5426 (0x24<<2)
+#define CIRRUS_ID_CLGD5424 (0x25<<2)
+#define CIRRUS_ID_CLGD5428 (0x26<<2)
+#define CIRRUS_ID_CLGD5430 (0x28<<2)
+#define CIRRUS_ID_CLGD5434 (0x2A<<2)
+#define CIRRUS_ID_CLGD5436 (0x2B<<2)
+#define CIRRUS_ID_CLGD5446 (0x2E<<2)
+
+// sequencer 0x07
+#define CIRRUS_SR7_BPP_VGA 0x00
+#define CIRRUS_SR7_BPP_SVGA 0x01
+#define CIRRUS_SR7_BPP_MASK 0x0e
+#define CIRRUS_SR7_BPP_8 0x00
+#define CIRRUS_SR7_BPP_16_DOUBLEVCLK 0x02
+#define CIRRUS_SR7_BPP_24 0x04
+#define CIRRUS_SR7_BPP_16 0x06
+#define CIRRUS_SR7_BPP_32 0x08
+#define CIRRUS_SR7_ISAADDR_MASK 0xe0
+
+// sequencer 0x0f
+#define CIRRUS_MEMSIZE_512k 0x08
+#define CIRRUS_MEMSIZE_1M 0x10
+#define CIRRUS_MEMSIZE_2M 0x18
+#define CIRRUS_MEMFLAGS_BANKSWITCH 0x80 // bank switching is enabled.
+
+// sequencer 0x12
+#define CIRRUS_CURSOR_SHOW 0x01
+#define CIRRUS_CURSOR_HIDDENPEL 0x02
+#define CIRRUS_CURSOR_LARGE 0x04 // 64x64 if set, 32x32 if clear
+
+// sequencer 0x17
+#define CIRRUS_BUSTYPE_VLBFAST 0x10
+#define CIRRUS_BUSTYPE_PCI 0x20
+#define CIRRUS_BUSTYPE_VLBSLOW 0x30
+#define CIRRUS_BUSTYPE_ISA 0x38
+#define CIRRUS_MMIO_ENABLE 0x04
+#define CIRRUS_MMIO_USE_PCIADDR 0x40 // 0xb8000 if cleared.
+#define CIRRUS_MEMSIZEEXT_DOUBLE 0x80
+
+// control 0x0b
+#define CIRRUS_BANKING_DUAL 0x01
+#define CIRRUS_BANKING_GRANULARITY_16K 0x20 // set:16k, clear:4k
+
+// control 0x30
+#define CIRRUS_BLTMODE_BACKWARDS 0x01
+#define CIRRUS_BLTMODE_MEMSYSDEST 0x02
+#define CIRRUS_BLTMODE_MEMSYSSRC 0x04
+#define CIRRUS_BLTMODE_TRANSPARENTCOMP 0x08
+#define CIRRUS_BLTMODE_PATTERNCOPY 0x40
+#define CIRRUS_BLTMODE_COLOREXPAND 0x80
+#define CIRRUS_BLTMODE_PIXELWIDTHMASK 0x30
+#define CIRRUS_BLTMODE_PIXELWIDTH8 0x00
+#define CIRRUS_BLTMODE_PIXELWIDTH16 0x10
+#define CIRRUS_BLTMODE_PIXELWIDTH24 0x20
+#define CIRRUS_BLTMODE_PIXELWIDTH32 0x30
+
+// control 0x31
+#define CIRRUS_BLT_BUSY 0x01
+#define CIRRUS_BLT_START 0x02
+#define CIRRUS_BLT_RESET 0x04
+#define CIRRUS_BLT_FIFOUSED 0x10
+#define CIRRUS_BLT_AUTOSTART 0x80
+
+// control 0x32
+#define CIRRUS_ROP_0 0x00
+#define CIRRUS_ROP_SRC_AND_DST 0x05
+#define CIRRUS_ROP_NOP 0x06
+#define CIRRUS_ROP_SRC_AND_NOTDST 0x09
+#define CIRRUS_ROP_NOTDST 0x0b
+#define CIRRUS_ROP_SRC 0x0d
+#define CIRRUS_ROP_1 0x0e
+#define CIRRUS_ROP_NOTSRC_AND_DST 0x50
+#define CIRRUS_ROP_SRC_XOR_DST 0x59
+#define CIRRUS_ROP_SRC_OR_DST 0x6d
+#define CIRRUS_ROP_NOTSRC_OR_NOTDST 0x90
+#define CIRRUS_ROP_SRC_NOTXOR_DST 0x95
+#define CIRRUS_ROP_SRC_OR_NOTDST 0xad
+#define CIRRUS_ROP_NOTSRC 0xd0
+#define CIRRUS_ROP_NOTSRC_OR_DST 0xd6
+#define CIRRUS_ROP_NOTSRC_AND_NOTDST 0xda
+
+#define CIRRUS_ROP_NOP_INDEX 2
+#define CIRRUS_ROP_SRC_INDEX 5
+
+// control 0x33
+#define CIRRUS_BLTMODEEXT_SOLIDFILL 0x04
+#define CIRRUS_BLTMODEEXT_COLOREXPINV 0x02
+#define CIRRUS_BLTMODEEXT_DWORDGRANULARITY 0x01
+
+// memory-mapped IO
+#define CIRRUS_MMIO_BLTBGCOLOR 0x00 // dword
+#define CIRRUS_MMIO_BLTFGCOLOR 0x04 // dword
+#define CIRRUS_MMIO_BLTWIDTH 0x08 // word
+#define CIRRUS_MMIO_BLTHEIGHT 0x0a // word
+#define CIRRUS_MMIO_BLTDESTPITCH 0x0c // word
+#define CIRRUS_MMIO_BLTSRCPITCH 0x0e // word
+#define CIRRUS_MMIO_BLTDESTADDR 0x10 // dword
+#define CIRRUS_MMIO_BLTSRCADDR 0x14 // dword
+#define CIRRUS_MMIO_BLTWRITEMASK 0x17 // byte
+#define CIRRUS_MMIO_BLTMODE 0x18 // byte
+#define CIRRUS_MMIO_BLTROP 0x1a // byte
+#define CIRRUS_MMIO_BLTMODEEXT 0x1b // byte
+#define CIRRUS_MMIO_BLTTRANSPARENTCOLOR 0x1c // word?
+#define CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK 0x20 // word?
+#define CIRRUS_MMIO_LINEARDRAW_START_X 0x24 // word
+#define CIRRUS_MMIO_LINEARDRAW_START_Y 0x26 // word
+#define CIRRUS_MMIO_LINEARDRAW_END_X 0x28 // word
+#define CIRRUS_MMIO_LINEARDRAW_END_Y 0x2a // word
+#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_INC 0x2c // byte
+#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_ROLLOVER 0x2d // byte
+#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_MASK 0x2e // byte
+#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_ACCUM 0x2f // byte
+#define CIRRUS_MMIO_BRESENHAM_K1 0x30 // word
+#define CIRRUS_MMIO_BRESENHAM_K3 0x32 // word
+#define CIRRUS_MMIO_BRESENHAM_ERROR 0x34 // word
+#define CIRRUS_MMIO_BRESENHAM_DELTA_MAJOR 0x36 // word
+#define CIRRUS_MMIO_BRESENHAM_DIRECTION 0x38 // byte
+#define CIRRUS_MMIO_LINEDRAW_MODE 0x39 // byte
+#define CIRRUS_MMIO_BLTSTATUS 0x40 // byte
+
+#define CIRRUS_PNPMMIO_SIZE 0x1000
+
+struct CirrusVGAState;
+typedef void (*cirrus_bitblt_rop_t) (struct CirrusVGAState *s,
+ uint8_t * dst, const uint8_t * src,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight);
+typedef void (*cirrus_fill_t)(struct CirrusVGAState *s,
+ uint8_t *dst, int dst_pitch, int width, int height);
+
+typedef struct CirrusVGAState {
+ VGACommonState vga;
+
+ MemoryRegion cirrus_vga_io;
+ MemoryRegion cirrus_linear_io;
+ MemoryRegion cirrus_linear_bitblt_io;
+ MemoryRegion cirrus_mmio_io;
+ MemoryRegion pci_bar;
+ bool linear_vram; /* vga.vram mapped over cirrus_linear_io */
+ MemoryRegion low_mem_container; /* container for 0xa0000-0xc0000 */
+ MemoryRegion low_mem; /* always mapped, overridden by: */
+ MemoryRegion cirrus_bank[2]; /* aliases at 0xa0000-0xb0000 */
+ uint32_t cirrus_addr_mask;
+ uint32_t linear_mmio_mask;
+ uint8_t cirrus_shadow_gr0;
+ uint8_t cirrus_shadow_gr1;
+ uint8_t cirrus_hidden_dac_lockindex;
+ uint8_t cirrus_hidden_dac_data;
+ uint32_t cirrus_bank_base[2];
+ uint32_t cirrus_bank_limit[2];
+ uint8_t cirrus_hidden_palette[48];
+ int cirrus_blt_pixelwidth;
+ int cirrus_blt_width;
+ int cirrus_blt_height;
+ int cirrus_blt_dstpitch;
+ int cirrus_blt_srcpitch;
+ uint32_t cirrus_blt_fgcol;
+ uint32_t cirrus_blt_bgcol;
+ uint32_t cirrus_blt_dstaddr;
+ uint32_t cirrus_blt_srcaddr;
+ uint8_t cirrus_blt_mode;
+ uint8_t cirrus_blt_modeext;
+ cirrus_bitblt_rop_t cirrus_rop;
+#define CIRRUS_BLTBUFSIZE (2048 * 4) /* one line width */
+ uint8_t cirrus_bltbuf[CIRRUS_BLTBUFSIZE];
+ uint8_t *cirrus_srcptr;
+ uint8_t *cirrus_srcptr_end;
+ uint32_t cirrus_srccounter;
+ /* hwcursor display state */
+ int last_hw_cursor_size;
+ int last_hw_cursor_x;
+ int last_hw_cursor_y;
+ int last_hw_cursor_y_start;
+ int last_hw_cursor_y_end;
+ int real_vram_size; /* XXX: suppress that */
+ int device_id;
+ int bustype;
+} CirrusVGAState;
+
+typedef struct PCICirrusVGAState {
+ PCIDevice dev;
+ CirrusVGAState cirrus_vga;
+} PCICirrusVGAState;
+
+#define TYPE_PCI_CIRRUS_VGA "cirrus-vga"
+#define PCI_CIRRUS_VGA(obj) \
+ OBJECT_CHECK(PCICirrusVGAState, (obj), TYPE_PCI_CIRRUS_VGA)
+
+#define TYPE_ISA_CIRRUS_VGA "isa-cirrus-vga"
+#define ISA_CIRRUS_VGA(obj) \
+ OBJECT_CHECK(ISACirrusVGAState, (obj), TYPE_ISA_CIRRUS_VGA)
+
+typedef struct ISACirrusVGAState {
+ ISADevice parent_obj;
+
+ CirrusVGAState cirrus_vga;
+} ISACirrusVGAState;
+
+static uint8_t rop_to_index[256];
+
+/***************************************
+ *
+ * prototypes.
+ *
+ ***************************************/
+
+
+static void cirrus_bitblt_reset(CirrusVGAState *s);
+static void cirrus_update_memory_access(CirrusVGAState *s);
+
+/***************************************
+ *
+ * raster operations
+ *
+ ***************************************/
+
+static bool blit_region_is_unsafe(struct CirrusVGAState *s,
+ int32_t pitch, int32_t addr)
+{
+ if (pitch < 0) {
+ int64_t min = addr
+ + ((int64_t)s->cirrus_blt_height-1) * pitch;
+ int32_t max = addr
+ + s->cirrus_blt_width;
+ if (min < 0 || max >= s->vga.vram_size) {
+ return true;
+ }
+ } else {
+ int64_t max = addr
+ + ((int64_t)s->cirrus_blt_height-1) * pitch
+ + s->cirrus_blt_width;
+ if (max >= s->vga.vram_size) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool blit_is_unsafe(struct CirrusVGAState *s)
+{
+ /* should be the case, see cirrus_bitblt_start */
+ assert(s->cirrus_blt_width > 0);
+ assert(s->cirrus_blt_height > 0);
+
+ if (s->cirrus_blt_width > CIRRUS_BLTBUFSIZE) {
+ return true;
+ }
+
+ if (blit_region_is_unsafe(s, s->cirrus_blt_dstpitch,
+ s->cirrus_blt_dstaddr & s->cirrus_addr_mask)) {
+ return true;
+ }
+ if (blit_region_is_unsafe(s, s->cirrus_blt_srcpitch,
+ s->cirrus_blt_srcaddr & s->cirrus_addr_mask)) {
+ return true;
+ }
+
+ return false;
+}
+
+static void cirrus_bitblt_rop_nop(CirrusVGAState *s,
+ uint8_t *dst,const uint8_t *src,
+ int dstpitch,int srcpitch,
+ int bltwidth,int bltheight)
+{
+}
+
+static void cirrus_bitblt_fill_nop(CirrusVGAState *s,
+ uint8_t *dst,
+ int dstpitch, int bltwidth,int bltheight)
+{
+}
+
+#define ROP_NAME 0
+#define ROP_FN(d, s) 0
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_and_dst
+#define ROP_FN(d, s) (s) & (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_and_notdst
+#define ROP_FN(d, s) (s) & (~(d))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notdst
+#define ROP_FN(d, s) ~(d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src
+#define ROP_FN(d, s) s
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME 1
+#define ROP_FN(d, s) ~0
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc_and_dst
+#define ROP_FN(d, s) (~(s)) & (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_xor_dst
+#define ROP_FN(d, s) (s) ^ (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_or_dst
+#define ROP_FN(d, s) (s) | (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc_or_notdst
+#define ROP_FN(d, s) (~(s)) | (~(d))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_notxor_dst
+#define ROP_FN(d, s) ~((s) ^ (d))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_or_notdst
+#define ROP_FN(d, s) (s) | (~(d))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc
+#define ROP_FN(d, s) (~(s))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc_or_dst
+#define ROP_FN(d, s) (~(s)) | (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc_and_notdst
+#define ROP_FN(d, s) (~(s)) & (~(d))
+#include "cirrus_vga_rop.h"
+
+static const cirrus_bitblt_rop_t cirrus_fwd_rop[16] = {
+ cirrus_bitblt_rop_fwd_0,
+ cirrus_bitblt_rop_fwd_src_and_dst,
+ cirrus_bitblt_rop_nop,
+ cirrus_bitblt_rop_fwd_src_and_notdst,
+ cirrus_bitblt_rop_fwd_notdst,
+ cirrus_bitblt_rop_fwd_src,
+ cirrus_bitblt_rop_fwd_1,
+ cirrus_bitblt_rop_fwd_notsrc_and_dst,
+ cirrus_bitblt_rop_fwd_src_xor_dst,
+ cirrus_bitblt_rop_fwd_src_or_dst,
+ cirrus_bitblt_rop_fwd_notsrc_or_notdst,
+ cirrus_bitblt_rop_fwd_src_notxor_dst,
+ cirrus_bitblt_rop_fwd_src_or_notdst,
+ cirrus_bitblt_rop_fwd_notsrc,
+ cirrus_bitblt_rop_fwd_notsrc_or_dst,
+ cirrus_bitblt_rop_fwd_notsrc_and_notdst,
+};
+
+static const cirrus_bitblt_rop_t cirrus_bkwd_rop[16] = {
+ cirrus_bitblt_rop_bkwd_0,
+ cirrus_bitblt_rop_bkwd_src_and_dst,
+ cirrus_bitblt_rop_nop,
+ cirrus_bitblt_rop_bkwd_src_and_notdst,
+ cirrus_bitblt_rop_bkwd_notdst,
+ cirrus_bitblt_rop_bkwd_src,
+ cirrus_bitblt_rop_bkwd_1,
+ cirrus_bitblt_rop_bkwd_notsrc_and_dst,
+ cirrus_bitblt_rop_bkwd_src_xor_dst,
+ cirrus_bitblt_rop_bkwd_src_or_dst,
+ cirrus_bitblt_rop_bkwd_notsrc_or_notdst,
+ cirrus_bitblt_rop_bkwd_src_notxor_dst,
+ cirrus_bitblt_rop_bkwd_src_or_notdst,
+ cirrus_bitblt_rop_bkwd_notsrc,
+ cirrus_bitblt_rop_bkwd_notsrc_or_dst,
+ cirrus_bitblt_rop_bkwd_notsrc_and_notdst,
+};
+
+#define TRANSP_ROP(name) {\
+ name ## _8,\
+ name ## _16,\
+ }
+#define TRANSP_NOP(func) {\
+ func,\
+ func,\
+ }
+
+static const cirrus_bitblt_rop_t cirrus_fwd_transp_rop[16][2] = {
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_0),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_and_dst),
+ TRANSP_NOP(cirrus_bitblt_rop_nop),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_and_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_1),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_and_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_xor_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_or_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_or_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_notxor_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_or_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_or_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_bkwd_transp_rop[16][2] = {
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_0),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_and_dst),
+ TRANSP_NOP(cirrus_bitblt_rop_nop),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_and_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_1),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_and_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_xor_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_or_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_or_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_notxor_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_or_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_or_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_and_notdst),
+};
+
+#define ROP2(name) {\
+ name ## _8,\
+ name ## _16,\
+ name ## _24,\
+ name ## _32,\
+ }
+
+#define ROP_NOP2(func) {\
+ func,\
+ func,\
+ func,\
+ func,\
+ }
+
+static const cirrus_bitblt_rop_t cirrus_patternfill[16][4] = {
+ ROP2(cirrus_patternfill_0),
+ ROP2(cirrus_patternfill_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_patternfill_src_and_notdst),
+ ROP2(cirrus_patternfill_notdst),
+ ROP2(cirrus_patternfill_src),
+ ROP2(cirrus_patternfill_1),
+ ROP2(cirrus_patternfill_notsrc_and_dst),
+ ROP2(cirrus_patternfill_src_xor_dst),
+ ROP2(cirrus_patternfill_src_or_dst),
+ ROP2(cirrus_patternfill_notsrc_or_notdst),
+ ROP2(cirrus_patternfill_src_notxor_dst),
+ ROP2(cirrus_patternfill_src_or_notdst),
+ ROP2(cirrus_patternfill_notsrc),
+ ROP2(cirrus_patternfill_notsrc_or_dst),
+ ROP2(cirrus_patternfill_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_colorexpand_transp[16][4] = {
+ ROP2(cirrus_colorexpand_transp_0),
+ ROP2(cirrus_colorexpand_transp_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_colorexpand_transp_src_and_notdst),
+ ROP2(cirrus_colorexpand_transp_notdst),
+ ROP2(cirrus_colorexpand_transp_src),
+ ROP2(cirrus_colorexpand_transp_1),
+ ROP2(cirrus_colorexpand_transp_notsrc_and_dst),
+ ROP2(cirrus_colorexpand_transp_src_xor_dst),
+ ROP2(cirrus_colorexpand_transp_src_or_dst),
+ ROP2(cirrus_colorexpand_transp_notsrc_or_notdst),
+ ROP2(cirrus_colorexpand_transp_src_notxor_dst),
+ ROP2(cirrus_colorexpand_transp_src_or_notdst),
+ ROP2(cirrus_colorexpand_transp_notsrc),
+ ROP2(cirrus_colorexpand_transp_notsrc_or_dst),
+ ROP2(cirrus_colorexpand_transp_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_colorexpand[16][4] = {
+ ROP2(cirrus_colorexpand_0),
+ ROP2(cirrus_colorexpand_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_colorexpand_src_and_notdst),
+ ROP2(cirrus_colorexpand_notdst),
+ ROP2(cirrus_colorexpand_src),
+ ROP2(cirrus_colorexpand_1),
+ ROP2(cirrus_colorexpand_notsrc_and_dst),
+ ROP2(cirrus_colorexpand_src_xor_dst),
+ ROP2(cirrus_colorexpand_src_or_dst),
+ ROP2(cirrus_colorexpand_notsrc_or_notdst),
+ ROP2(cirrus_colorexpand_src_notxor_dst),
+ ROP2(cirrus_colorexpand_src_or_notdst),
+ ROP2(cirrus_colorexpand_notsrc),
+ ROP2(cirrus_colorexpand_notsrc_or_dst),
+ ROP2(cirrus_colorexpand_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_colorexpand_pattern_transp[16][4] = {
+ ROP2(cirrus_colorexpand_pattern_transp_0),
+ ROP2(cirrus_colorexpand_pattern_transp_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_colorexpand_pattern_transp_src_and_notdst),
+ ROP2(cirrus_colorexpand_pattern_transp_notdst),
+ ROP2(cirrus_colorexpand_pattern_transp_src),
+ ROP2(cirrus_colorexpand_pattern_transp_1),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc_and_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_src_xor_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_src_or_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc_or_notdst),
+ ROP2(cirrus_colorexpand_pattern_transp_src_notxor_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_src_or_notdst),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc_or_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_colorexpand_pattern[16][4] = {
+ ROP2(cirrus_colorexpand_pattern_0),
+ ROP2(cirrus_colorexpand_pattern_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_colorexpand_pattern_src_and_notdst),
+ ROP2(cirrus_colorexpand_pattern_notdst),
+ ROP2(cirrus_colorexpand_pattern_src),
+ ROP2(cirrus_colorexpand_pattern_1),
+ ROP2(cirrus_colorexpand_pattern_notsrc_and_dst),
+ ROP2(cirrus_colorexpand_pattern_src_xor_dst),
+ ROP2(cirrus_colorexpand_pattern_src_or_dst),
+ ROP2(cirrus_colorexpand_pattern_notsrc_or_notdst),
+ ROP2(cirrus_colorexpand_pattern_src_notxor_dst),
+ ROP2(cirrus_colorexpand_pattern_src_or_notdst),
+ ROP2(cirrus_colorexpand_pattern_notsrc),
+ ROP2(cirrus_colorexpand_pattern_notsrc_or_dst),
+ ROP2(cirrus_colorexpand_pattern_notsrc_and_notdst),
+};
+
+static const cirrus_fill_t cirrus_fill[16][4] = {
+ ROP2(cirrus_fill_0),
+ ROP2(cirrus_fill_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_fill_nop),
+ ROP2(cirrus_fill_src_and_notdst),
+ ROP2(cirrus_fill_notdst),
+ ROP2(cirrus_fill_src),
+ ROP2(cirrus_fill_1),
+ ROP2(cirrus_fill_notsrc_and_dst),
+ ROP2(cirrus_fill_src_xor_dst),
+ ROP2(cirrus_fill_src_or_dst),
+ ROP2(cirrus_fill_notsrc_or_notdst),
+ ROP2(cirrus_fill_src_notxor_dst),
+ ROP2(cirrus_fill_src_or_notdst),
+ ROP2(cirrus_fill_notsrc),
+ ROP2(cirrus_fill_notsrc_or_dst),
+ ROP2(cirrus_fill_notsrc_and_notdst),
+};
+
+static inline void cirrus_bitblt_fgcol(CirrusVGAState *s)
+{
+ unsigned int color;
+ switch (s->cirrus_blt_pixelwidth) {
+ case 1:
+ s->cirrus_blt_fgcol = s->cirrus_shadow_gr1;
+ break;
+ case 2:
+ color = s->cirrus_shadow_gr1 | (s->vga.gr[0x11] << 8);
+ s->cirrus_blt_fgcol = le16_to_cpu(color);
+ break;
+ case 3:
+ s->cirrus_blt_fgcol = s->cirrus_shadow_gr1 |
+ (s->vga.gr[0x11] << 8) | (s->vga.gr[0x13] << 16);
+ break;
+ default:
+ case 4:
+ color = s->cirrus_shadow_gr1 | (s->vga.gr[0x11] << 8) |
+ (s->vga.gr[0x13] << 16) | (s->vga.gr[0x15] << 24);
+ s->cirrus_blt_fgcol = le32_to_cpu(color);
+ break;
+ }
+}
+
+static inline void cirrus_bitblt_bgcol(CirrusVGAState *s)
+{
+ unsigned int color;
+ switch (s->cirrus_blt_pixelwidth) {
+ case 1:
+ s->cirrus_blt_bgcol = s->cirrus_shadow_gr0;
+ break;
+ case 2:
+ color = s->cirrus_shadow_gr0 | (s->vga.gr[0x10] << 8);
+ s->cirrus_blt_bgcol = le16_to_cpu(color);
+ break;
+ case 3:
+ s->cirrus_blt_bgcol = s->cirrus_shadow_gr0 |
+ (s->vga.gr[0x10] << 8) | (s->vga.gr[0x12] << 16);
+ break;
+ default:
+ case 4:
+ color = s->cirrus_shadow_gr0 | (s->vga.gr[0x10] << 8) |
+ (s->vga.gr[0x12] << 16) | (s->vga.gr[0x14] << 24);
+ s->cirrus_blt_bgcol = le32_to_cpu(color);
+ break;
+ }
+}
+
+static void cirrus_invalidate_region(CirrusVGAState * s, int off_begin,
+ int off_pitch, int bytesperline,
+ int lines)
+{
+ int y;
+ int off_cur;
+ int off_cur_end;
+
+ for (y = 0; y < lines; y++) {
+ off_cur = off_begin;
+ off_cur_end = (off_cur + bytesperline) & s->cirrus_addr_mask;
+ memory_region_set_dirty(&s->vga.vram, off_cur, off_cur_end - off_cur);
+ off_begin += off_pitch;
+ }
+}
+
+static int cirrus_bitblt_common_patterncopy(CirrusVGAState * s,
+ const uint8_t * src)
+{
+ uint8_t *dst;
+
+ dst = s->vga.vram_ptr + (s->cirrus_blt_dstaddr & s->cirrus_addr_mask);
+
+ if (blit_is_unsafe(s))
+ return 0;
+
+ (*s->cirrus_rop) (s, dst, src,
+ s->cirrus_blt_dstpitch, 0,
+ s->cirrus_blt_width, s->cirrus_blt_height);
+ cirrus_invalidate_region(s, s->cirrus_blt_dstaddr,
+ s->cirrus_blt_dstpitch, s->cirrus_blt_width,
+ s->cirrus_blt_height);
+ return 1;
+}
+
+/* fill */
+
+static int cirrus_bitblt_solidfill(CirrusVGAState *s, int blt_rop)
+{
+ cirrus_fill_t rop_func;
+
+ if (blit_is_unsafe(s)) {
+ return 0;
+ }
+ rop_func = cirrus_fill[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ rop_func(s, s->vga.vram_ptr + (s->cirrus_blt_dstaddr & s->cirrus_addr_mask),
+ s->cirrus_blt_dstpitch,
+ s->cirrus_blt_width, s->cirrus_blt_height);
+ cirrus_invalidate_region(s, s->cirrus_blt_dstaddr,
+ s->cirrus_blt_dstpitch, s->cirrus_blt_width,
+ s->cirrus_blt_height);
+ cirrus_bitblt_reset(s);
+ return 1;
+}
+
+/***************************************
+ *
+ * bitblt (video-to-video)
+ *
+ ***************************************/
+
+static int cirrus_bitblt_videotovideo_patterncopy(CirrusVGAState * s)
+{
+ return cirrus_bitblt_common_patterncopy(s,
+ s->vga.vram_ptr + ((s->cirrus_blt_srcaddr & ~7) &
+ s->cirrus_addr_mask));
+}
+
+static void cirrus_do_copy(CirrusVGAState *s, int dst, int src, int w, int h)
+{
+ int sx = 0, sy = 0;
+ int dx = 0, dy = 0;
+ int depth = 0;
+ int notify = 0;
+
+ /* make sure to only copy if it's a plain copy ROP */
+ if (*s->cirrus_rop == cirrus_bitblt_rop_fwd_src ||
+ *s->cirrus_rop == cirrus_bitblt_rop_bkwd_src) {
+
+ int width, height;
+
+ depth = s->vga.get_bpp(&s->vga) / 8;
+ s->vga.get_resolution(&s->vga, &width, &height);
+
+ /* extra x, y */
+ sx = (src % ABS(s->cirrus_blt_srcpitch)) / depth;
+ sy = (src / ABS(s->cirrus_blt_srcpitch));
+ dx = (dst % ABS(s->cirrus_blt_dstpitch)) / depth;
+ dy = (dst / ABS(s->cirrus_blt_dstpitch));
+
+ /* normalize width */
+ w /= depth;
+
+ /* if we're doing a backward copy, we have to adjust
+ our x/y to be the upper left corner (instead of the lower
+ right corner) */
+ if (s->cirrus_blt_dstpitch < 0) {
+ sx -= (s->cirrus_blt_width / depth) - 1;
+ dx -= (s->cirrus_blt_width / depth) - 1;
+ sy -= s->cirrus_blt_height - 1;
+ dy -= s->cirrus_blt_height - 1;
+ }
+
+ /* are we in the visible portion of memory? */
+ if (sx >= 0 && sy >= 0 && dx >= 0 && dy >= 0 &&
+ (sx + w) <= width && (sy + h) <= height &&
+ (dx + w) <= width && (dy + h) <= height) {
+ notify = 1;
+ }
+ }
+
+ /* we have to flush all pending changes so that the copy
+ is generated at the appropriate moment in time */
+ if (notify)
+ graphic_hw_update(s->vga.con);
+
+ (*s->cirrus_rop) (s, s->vga.vram_ptr +
+ (s->cirrus_blt_dstaddr & s->cirrus_addr_mask),
+ s->vga.vram_ptr +
+ (s->cirrus_blt_srcaddr & s->cirrus_addr_mask),
+ s->cirrus_blt_dstpitch, s->cirrus_blt_srcpitch,
+ s->cirrus_blt_width, s->cirrus_blt_height);
+
+ if (notify) {
+ qemu_console_copy(s->vga.con,
+ sx, sy, dx, dy,
+ s->cirrus_blt_width / depth,
+ s->cirrus_blt_height);
+ }
+
+ /* we don't have to notify the display that this portion has
+ changed since qemu_console_copy implies this */
+
+ cirrus_invalidate_region(s, s->cirrus_blt_dstaddr,
+ s->cirrus_blt_dstpitch, s->cirrus_blt_width,
+ s->cirrus_blt_height);
+}
+
+static int cirrus_bitblt_videotovideo_copy(CirrusVGAState * s)
+{
+ if (blit_is_unsafe(s))
+ return 0;
+
+ cirrus_do_copy(s, s->cirrus_blt_dstaddr - s->vga.start_addr,
+ s->cirrus_blt_srcaddr - s->vga.start_addr,
+ s->cirrus_blt_width, s->cirrus_blt_height);
+
+ return 1;
+}
+
+/***************************************
+ *
+ * bitblt (cpu-to-video)
+ *
+ ***************************************/
+
+static void cirrus_bitblt_cputovideo_next(CirrusVGAState * s)
+{
+ int copy_count;
+ uint8_t *end_ptr;
+
+ if (s->cirrus_srccounter > 0) {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) {
+ cirrus_bitblt_common_patterncopy(s, s->cirrus_bltbuf);
+ the_end:
+ s->cirrus_srccounter = 0;
+ cirrus_bitblt_reset(s);
+ } else {
+ /* at least one scan line */
+ do {
+ (*s->cirrus_rop)(s, s->vga.vram_ptr +
+ (s->cirrus_blt_dstaddr & s->cirrus_addr_mask),
+ s->cirrus_bltbuf, 0, 0, s->cirrus_blt_width, 1);
+ cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, 0,
+ s->cirrus_blt_width, 1);
+ s->cirrus_blt_dstaddr += s->cirrus_blt_dstpitch;
+ s->cirrus_srccounter -= s->cirrus_blt_srcpitch;
+ if (s->cirrus_srccounter <= 0)
+ goto the_end;
+ /* more bytes than needed can be transferred because of
+ word alignment, so we keep them for the next line */
+ /* XXX: keep alignment to speed up transfer */
+ end_ptr = s->cirrus_bltbuf + s->cirrus_blt_srcpitch;
+ copy_count = s->cirrus_srcptr_end - end_ptr;
+ memmove(s->cirrus_bltbuf, end_ptr, copy_count);
+ s->cirrus_srcptr = s->cirrus_bltbuf + copy_count;
+ s->cirrus_srcptr_end = s->cirrus_bltbuf + s->cirrus_blt_srcpitch;
+ } while (s->cirrus_srcptr >= s->cirrus_srcptr_end);
+ }
+ }
+}
+
+/***************************************
+ *
+ * bitblt wrapper
+ *
+ ***************************************/
+
+static void cirrus_bitblt_reset(CirrusVGAState * s)
+{
+ int need_update;
+
+ s->vga.gr[0x31] &=
+ ~(CIRRUS_BLT_START | CIRRUS_BLT_BUSY | CIRRUS_BLT_FIFOUSED);
+ need_update = s->cirrus_srcptr != &s->cirrus_bltbuf[0]
+ || s->cirrus_srcptr_end != &s->cirrus_bltbuf[0];
+ s->cirrus_srcptr = &s->cirrus_bltbuf[0];
+ s->cirrus_srcptr_end = &s->cirrus_bltbuf[0];
+ s->cirrus_srccounter = 0;
+ if (!need_update)
+ return;
+ cirrus_update_memory_access(s);
+}
+
+static int cirrus_bitblt_cputovideo(CirrusVGAState * s)
+{
+ int w;
+
+ s->cirrus_blt_mode &= ~CIRRUS_BLTMODE_MEMSYSSRC;
+ s->cirrus_srcptr = &s->cirrus_bltbuf[0];
+ s->cirrus_srcptr_end = &s->cirrus_bltbuf[0];
+
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) {
+ s->cirrus_blt_srcpitch = 8;
+ } else {
+ /* XXX: check for 24 bpp */
+ s->cirrus_blt_srcpitch = 8 * 8 * s->cirrus_blt_pixelwidth;
+ }
+ s->cirrus_srccounter = s->cirrus_blt_srcpitch;
+ } else {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) {
+ w = s->cirrus_blt_width / s->cirrus_blt_pixelwidth;
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_DWORDGRANULARITY)
+ s->cirrus_blt_srcpitch = ((w + 31) >> 5);
+ else
+ s->cirrus_blt_srcpitch = ((w + 7) >> 3);
+ } else {
+ /* always align input size to 32 bits */
+ s->cirrus_blt_srcpitch = (s->cirrus_blt_width + 3) & ~3;
+ }
+ s->cirrus_srccounter = s->cirrus_blt_srcpitch * s->cirrus_blt_height;
+ }
+ s->cirrus_srcptr = s->cirrus_bltbuf;
+ s->cirrus_srcptr_end = s->cirrus_bltbuf + s->cirrus_blt_srcpitch;
+ cirrus_update_memory_access(s);
+ return 1;
+}
+
+static int cirrus_bitblt_videotocpu(CirrusVGAState * s)
+{
+ /* XXX */
+#ifdef DEBUG_BITBLT
+ printf("cirrus: bitblt (video to cpu) is not implemented yet\n");
+#endif
+ return 0;
+}
+
+static int cirrus_bitblt_videotovideo(CirrusVGAState * s)
+{
+ int ret;
+
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) {
+ ret = cirrus_bitblt_videotovideo_patterncopy(s);
+ } else {
+ ret = cirrus_bitblt_videotovideo_copy(s);
+ }
+ if (ret)
+ cirrus_bitblt_reset(s);
+ return ret;
+}
+
+static void cirrus_bitblt_start(CirrusVGAState * s)
+{
+ uint8_t blt_rop;
+
+ s->vga.gr[0x31] |= CIRRUS_BLT_BUSY;
+
+ s->cirrus_blt_width = (s->vga.gr[0x20] | (s->vga.gr[0x21] << 8)) + 1;
+ s->cirrus_blt_height = (s->vga.gr[0x22] | (s->vga.gr[0x23] << 8)) + 1;
+ s->cirrus_blt_dstpitch = (s->vga.gr[0x24] | (s->vga.gr[0x25] << 8));
+ s->cirrus_blt_srcpitch = (s->vga.gr[0x26] | (s->vga.gr[0x27] << 8));
+ s->cirrus_blt_dstaddr =
+ (s->vga.gr[0x28] | (s->vga.gr[0x29] << 8) | (s->vga.gr[0x2a] << 16));
+ s->cirrus_blt_srcaddr =
+ (s->vga.gr[0x2c] | (s->vga.gr[0x2d] << 8) | (s->vga.gr[0x2e] << 16));
+ s->cirrus_blt_mode = s->vga.gr[0x30];
+ s->cirrus_blt_modeext = s->vga.gr[0x33];
+ blt_rop = s->vga.gr[0x32];
+
+#ifdef DEBUG_BITBLT
+ printf("rop=0x%02x mode=0x%02x modeext=0x%02x w=%d h=%d dpitch=%d spitch=%d daddr=0x%08x saddr=0x%08x writemask=0x%02x\n",
+ blt_rop,
+ s->cirrus_blt_mode,
+ s->cirrus_blt_modeext,
+ s->cirrus_blt_width,
+ s->cirrus_blt_height,
+ s->cirrus_blt_dstpitch,
+ s->cirrus_blt_srcpitch,
+ s->cirrus_blt_dstaddr,
+ s->cirrus_blt_srcaddr,
+ s->vga.gr[0x2f]);
+#endif
+
+ switch (s->cirrus_blt_mode & CIRRUS_BLTMODE_PIXELWIDTHMASK) {
+ case CIRRUS_BLTMODE_PIXELWIDTH8:
+ s->cirrus_blt_pixelwidth = 1;
+ break;
+ case CIRRUS_BLTMODE_PIXELWIDTH16:
+ s->cirrus_blt_pixelwidth = 2;
+ break;
+ case CIRRUS_BLTMODE_PIXELWIDTH24:
+ s->cirrus_blt_pixelwidth = 3;
+ break;
+ case CIRRUS_BLTMODE_PIXELWIDTH32:
+ s->cirrus_blt_pixelwidth = 4;
+ break;
+ default:
+#ifdef DEBUG_BITBLT
+ printf("cirrus: bitblt - pixel width is unknown\n");
+#endif
+ goto bitblt_ignore;
+ }
+ s->cirrus_blt_mode &= ~CIRRUS_BLTMODE_PIXELWIDTHMASK;
+
+ if ((s->
+ cirrus_blt_mode & (CIRRUS_BLTMODE_MEMSYSSRC |
+ CIRRUS_BLTMODE_MEMSYSDEST))
+ == (CIRRUS_BLTMODE_MEMSYSSRC | CIRRUS_BLTMODE_MEMSYSDEST)) {
+#ifdef DEBUG_BITBLT
+ printf("cirrus: bitblt - memory-to-memory copy is requested\n");
+#endif
+ goto bitblt_ignore;
+ }
+
+ if ((s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_SOLIDFILL) &&
+ (s->cirrus_blt_mode & (CIRRUS_BLTMODE_MEMSYSDEST |
+ CIRRUS_BLTMODE_TRANSPARENTCOMP |
+ CIRRUS_BLTMODE_PATTERNCOPY |
+ CIRRUS_BLTMODE_COLOREXPAND)) ==
+ (CIRRUS_BLTMODE_PATTERNCOPY | CIRRUS_BLTMODE_COLOREXPAND)) {
+ cirrus_bitblt_fgcol(s);
+ cirrus_bitblt_solidfill(s, blt_rop);
+ } else {
+ if ((s->cirrus_blt_mode & (CIRRUS_BLTMODE_COLOREXPAND |
+ CIRRUS_BLTMODE_PATTERNCOPY)) ==
+ CIRRUS_BLTMODE_COLOREXPAND) {
+
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) {
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV)
+ cirrus_bitblt_bgcol(s);
+ else
+ cirrus_bitblt_fgcol(s);
+ s->cirrus_rop = cirrus_colorexpand_transp[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ } else {
+ cirrus_bitblt_fgcol(s);
+ cirrus_bitblt_bgcol(s);
+ s->cirrus_rop = cirrus_colorexpand[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ }
+ } else if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) {
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV)
+ cirrus_bitblt_bgcol(s);
+ else
+ cirrus_bitblt_fgcol(s);
+ s->cirrus_rop = cirrus_colorexpand_pattern_transp[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ } else {
+ cirrus_bitblt_fgcol(s);
+ cirrus_bitblt_bgcol(s);
+ s->cirrus_rop = cirrus_colorexpand_pattern[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ }
+ } else {
+ s->cirrus_rop = cirrus_patternfill[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ }
+ } else {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) {
+ if (s->cirrus_blt_pixelwidth > 2) {
+ printf("src transparent without colorexpand must be 8bpp or 16bpp\n");
+ goto bitblt_ignore;
+ }
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_BACKWARDS) {
+ s->cirrus_blt_dstpitch = -s->cirrus_blt_dstpitch;
+ s->cirrus_blt_srcpitch = -s->cirrus_blt_srcpitch;
+ s->cirrus_rop = cirrus_bkwd_transp_rop[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ } else {
+ s->cirrus_rop = cirrus_fwd_transp_rop[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ }
+ } else {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_BACKWARDS) {
+ s->cirrus_blt_dstpitch = -s->cirrus_blt_dstpitch;
+ s->cirrus_blt_srcpitch = -s->cirrus_blt_srcpitch;
+ s->cirrus_rop = cirrus_bkwd_rop[rop_to_index[blt_rop]];
+ } else {
+ s->cirrus_rop = cirrus_fwd_rop[rop_to_index[blt_rop]];
+ }
+ }
+ }
+ // setup bitblt engine.
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_MEMSYSSRC) {
+ if (!cirrus_bitblt_cputovideo(s))
+ goto bitblt_ignore;
+ } else if (s->cirrus_blt_mode & CIRRUS_BLTMODE_MEMSYSDEST) {
+ if (!cirrus_bitblt_videotocpu(s))
+ goto bitblt_ignore;
+ } else {
+ if (!cirrus_bitblt_videotovideo(s))
+ goto bitblt_ignore;
+ }
+ }
+ return;
+ bitblt_ignore:;
+ cirrus_bitblt_reset(s);
+}
+
+static void cirrus_write_bitblt(CirrusVGAState * s, unsigned reg_value)
+{
+ unsigned old_value;
+
+ old_value = s->vga.gr[0x31];
+ s->vga.gr[0x31] = reg_value;
+
+ if (((old_value & CIRRUS_BLT_RESET) != 0) &&
+ ((reg_value & CIRRUS_BLT_RESET) == 0)) {
+ cirrus_bitblt_reset(s);
+ } else if (((old_value & CIRRUS_BLT_START) == 0) &&
+ ((reg_value & CIRRUS_BLT_START) != 0)) {
+ cirrus_bitblt_start(s);
+ }
+}
+
+
+/***************************************
+ *
+ * basic parameters
+ *
+ ***************************************/
+
+static void cirrus_get_offsets(VGACommonState *s1,
+ uint32_t *pline_offset,
+ uint32_t *pstart_addr,
+ uint32_t *pline_compare)
+{
+ CirrusVGAState * s = container_of(s1, CirrusVGAState, vga);
+ uint32_t start_addr, line_offset, line_compare;
+
+ line_offset = s->vga.cr[0x13]
+ | ((s->vga.cr[0x1b] & 0x10) << 4);
+ line_offset <<= 3;
+ *pline_offset = line_offset;
+
+ start_addr = (s->vga.cr[0x0c] << 8)
+ | s->vga.cr[0x0d]
+ | ((s->vga.cr[0x1b] & 0x01) << 16)
+ | ((s->vga.cr[0x1b] & 0x0c) << 15)
+ | ((s->vga.cr[0x1d] & 0x80) << 12);
+ *pstart_addr = start_addr;
+
+ line_compare = s->vga.cr[0x18] |
+ ((s->vga.cr[0x07] & 0x10) << 4) |
+ ((s->vga.cr[0x09] & 0x40) << 3);
+ *pline_compare = line_compare;
+}
+
+static uint32_t cirrus_get_bpp16_depth(CirrusVGAState * s)
+{
+ uint32_t ret = 16;
+
+ switch (s->cirrus_hidden_dac_data & 0xf) {
+ case 0:
+ ret = 15;
+ break; /* Sierra HiColor */
+ case 1:
+ ret = 16;
+ break; /* XGA HiColor */
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: invalid DAC value %x in 16bpp\n",
+ (s->cirrus_hidden_dac_data & 0xf));
+#endif
+ ret = 15; /* XXX */
+ break;
+ }
+ return ret;
+}
+
+static int cirrus_get_bpp(VGACommonState *s1)
+{
+ CirrusVGAState * s = container_of(s1, CirrusVGAState, vga);
+ uint32_t ret = 8;
+
+ if ((s->vga.sr[0x07] & 0x01) != 0) {
+ /* Cirrus SVGA */
+ switch (s->vga.sr[0x07] & CIRRUS_SR7_BPP_MASK) {
+ case CIRRUS_SR7_BPP_8:
+ ret = 8;
+ break;
+ case CIRRUS_SR7_BPP_16_DOUBLEVCLK:
+ ret = cirrus_get_bpp16_depth(s);
+ break;
+ case CIRRUS_SR7_BPP_24:
+ ret = 24;
+ break;
+ case CIRRUS_SR7_BPP_16:
+ ret = cirrus_get_bpp16_depth(s);
+ break;
+ case CIRRUS_SR7_BPP_32:
+ ret = 32;
+ break;
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: unknown bpp - sr7=%x\n", s->vga.sr[0x7]);
+#endif
+ ret = 8;
+ break;
+ }
+ } else {
+ /* VGA */
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void cirrus_get_resolution(VGACommonState *s, int *pwidth, int *pheight)
+{
+ int width, height;
+
+ width = (s->cr[0x01] + 1) * 8;
+ height = s->cr[0x12] |
+ ((s->cr[0x07] & 0x02) << 7) |
+ ((s->cr[0x07] & 0x40) << 3);
+ height = (height + 1);
+ /* interlace support */
+ if (s->cr[0x1a] & 0x01)
+ height = height * 2;
+ *pwidth = width;
+ *pheight = height;
+}
+
+/***************************************
+ *
+ * bank memory
+ *
+ ***************************************/
+
+static void cirrus_update_bank_ptr(CirrusVGAState * s, unsigned bank_index)
+{
+ unsigned offset;
+ unsigned limit;
+
+ if ((s->vga.gr[0x0b] & 0x01) != 0) /* dual bank */
+ offset = s->vga.gr[0x09 + bank_index];
+ else /* single bank */
+ offset = s->vga.gr[0x09];
+
+ if ((s->vga.gr[0x0b] & 0x20) != 0)
+ offset <<= 14;
+ else
+ offset <<= 12;
+
+ if (s->real_vram_size <= offset)
+ limit = 0;
+ else
+ limit = s->real_vram_size - offset;
+
+ if (((s->vga.gr[0x0b] & 0x01) == 0) && (bank_index != 0)) {
+ if (limit > 0x8000) {
+ offset += 0x8000;
+ limit -= 0x8000;
+ } else {
+ limit = 0;
+ }
+ }
+
+ if (limit > 0) {
+ s->cirrus_bank_base[bank_index] = offset;
+ s->cirrus_bank_limit[bank_index] = limit;
+ } else {
+ s->cirrus_bank_base[bank_index] = 0;
+ s->cirrus_bank_limit[bank_index] = 0;
+ }
+}
+
+/***************************************
+ *
+ * I/O access between 0x3c4-0x3c5
+ *
+ ***************************************/
+
+static int cirrus_vga_read_sr(CirrusVGAState * s)
+{
+ switch (s->vga.sr_index) {
+ case 0x00: // Standard VGA
+ case 0x01: // Standard VGA
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ return s->vga.sr[s->vga.sr_index];
+ case 0x06: // Unlock Cirrus extensions
+ return s->vga.sr[s->vga.sr_index];
+ case 0x10:
+ case 0x30:
+ case 0x50:
+ case 0x70: // Graphics Cursor X
+ case 0x90:
+ case 0xb0:
+ case 0xd0:
+ case 0xf0: // Graphics Cursor X
+ return s->vga.sr[0x10];
+ case 0x11:
+ case 0x31:
+ case 0x51:
+ case 0x71: // Graphics Cursor Y
+ case 0x91:
+ case 0xb1:
+ case 0xd1:
+ case 0xf1: // Graphics Cursor Y
+ return s->vga.sr[0x11];
+ case 0x05: // ???
+ case 0x07: // Extended Sequencer Mode
+ case 0x08: // EEPROM Control
+ case 0x09: // Scratch Register 0
+ case 0x0a: // Scratch Register 1
+ case 0x0b: // VCLK 0
+ case 0x0c: // VCLK 1
+ case 0x0d: // VCLK 2
+ case 0x0e: // VCLK 3
+ case 0x0f: // DRAM Control
+ case 0x12: // Graphics Cursor Attribute
+ case 0x13: // Graphics Cursor Pattern Address
+ case 0x14: // Scratch Register 2
+ case 0x15: // Scratch Register 3
+ case 0x16: // Performance Tuning Register
+ case 0x17: // Configuration Readback and Extended Control
+ case 0x18: // Signature Generator Control
+ case 0x19: // Signal Generator Result
+ case 0x1a: // Signal Generator Result
+ case 0x1b: // VCLK 0 Denominator & Post
+ case 0x1c: // VCLK 1 Denominator & Post
+ case 0x1d: // VCLK 2 Denominator & Post
+ case 0x1e: // VCLK 3 Denominator & Post
+ case 0x1f: // BIOS Write Enable and MCLK select
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: handled inport sr_index %02x\n", s->vga.sr_index);
+#endif
+ return s->vga.sr[s->vga.sr_index];
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: inport sr_index %02x\n", s->vga.sr_index);
+#endif
+ return 0xff;
+ break;
+ }
+}
+
+static void cirrus_vga_write_sr(CirrusVGAState * s, uint32_t val)
+{
+ switch (s->vga.sr_index) {
+ case 0x00: // Standard VGA
+ case 0x01: // Standard VGA
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ s->vga.sr[s->vga.sr_index] = val & sr_mask[s->vga.sr_index];
+ if (s->vga.sr_index == 1)
+ s->vga.update_retrace_info(&s->vga);
+ break;
+ case 0x06: // Unlock Cirrus extensions
+ val &= 0x17;
+ if (val == 0x12) {
+ s->vga.sr[s->vga.sr_index] = 0x12;
+ } else {
+ s->vga.sr[s->vga.sr_index] = 0x0f;
+ }
+ break;
+ case 0x10:
+ case 0x30:
+ case 0x50:
+ case 0x70: // Graphics Cursor X
+ case 0x90:
+ case 0xb0:
+ case 0xd0:
+ case 0xf0: // Graphics Cursor X
+ s->vga.sr[0x10] = val;
+ s->vga.hw_cursor_x = (val << 3) | (s->vga.sr_index >> 5);
+ break;
+ case 0x11:
+ case 0x31:
+ case 0x51:
+ case 0x71: // Graphics Cursor Y
+ case 0x91:
+ case 0xb1:
+ case 0xd1:
+ case 0xf1: // Graphics Cursor Y
+ s->vga.sr[0x11] = val;
+ s->vga.hw_cursor_y = (val << 3) | (s->vga.sr_index >> 5);
+ break;
+ case 0x07: // Extended Sequencer Mode
+ cirrus_update_memory_access(s);
+ case 0x08: // EEPROM Control
+ case 0x09: // Scratch Register 0
+ case 0x0a: // Scratch Register 1
+ case 0x0b: // VCLK 0
+ case 0x0c: // VCLK 1
+ case 0x0d: // VCLK 2
+ case 0x0e: // VCLK 3
+ case 0x0f: // DRAM Control
+ case 0x13: // Graphics Cursor Pattern Address
+ case 0x14: // Scratch Register 2
+ case 0x15: // Scratch Register 3
+ case 0x16: // Performance Tuning Register
+ case 0x18: // Signature Generator Control
+ case 0x19: // Signature Generator Result
+ case 0x1a: // Signature Generator Result
+ case 0x1b: // VCLK 0 Denominator & Post
+ case 0x1c: // VCLK 1 Denominator & Post
+ case 0x1d: // VCLK 2 Denominator & Post
+ case 0x1e: // VCLK 3 Denominator & Post
+ case 0x1f: // BIOS Write Enable and MCLK select
+ s->vga.sr[s->vga.sr_index] = val;
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: handled outport sr_index %02x, sr_value %02x\n",
+ s->vga.sr_index, val);
+#endif
+ break;
+ case 0x12: // Graphics Cursor Attribute
+ s->vga.sr[0x12] = val;
+ s->vga.force_shadow = !!(val & CIRRUS_CURSOR_SHOW);
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: cursor ctl SR12=%02x (force shadow: %d)\n",
+ val, s->vga.force_shadow);
+#endif
+ break;
+ case 0x17: // Configuration Readback and Extended Control
+ s->vga.sr[s->vga.sr_index] = (s->vga.sr[s->vga.sr_index] & 0x38)
+ | (val & 0xc7);
+ cirrus_update_memory_access(s);
+ break;
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: outport sr_index %02x, sr_value %02x\n",
+ s->vga.sr_index, val);
+#endif
+ break;
+ }
+}
+
+/***************************************
+ *
+ * I/O access at 0x3c6
+ *
+ ***************************************/
+
+static int cirrus_read_hidden_dac(CirrusVGAState * s)
+{
+ if (++s->cirrus_hidden_dac_lockindex == 5) {
+ s->cirrus_hidden_dac_lockindex = 0;
+ return s->cirrus_hidden_dac_data;
+ }
+ return 0xff;
+}
+
+static void cirrus_write_hidden_dac(CirrusVGAState * s, int reg_value)
+{
+ if (s->cirrus_hidden_dac_lockindex == 4) {
+ s->cirrus_hidden_dac_data = reg_value;
+#if defined(DEBUG_CIRRUS)
+ printf("cirrus: outport hidden DAC, value %02x\n", reg_value);
+#endif
+ }
+ s->cirrus_hidden_dac_lockindex = 0;
+}
+
+/***************************************
+ *
+ * I/O access at 0x3c9
+ *
+ ***************************************/
+
+static int cirrus_vga_read_palette(CirrusVGAState * s)
+{
+ int val;
+
+ if ((s->vga.sr[0x12] & CIRRUS_CURSOR_HIDDENPEL)) {
+ val = s->cirrus_hidden_palette[(s->vga.dac_read_index & 0x0f) * 3 +
+ s->vga.dac_sub_index];
+ } else {
+ val = s->vga.palette[s->vga.dac_read_index * 3 + s->vga.dac_sub_index];
+ }
+ if (++s->vga.dac_sub_index == 3) {
+ s->vga.dac_sub_index = 0;
+ s->vga.dac_read_index++;
+ }
+ return val;
+}
+
+static void cirrus_vga_write_palette(CirrusVGAState * s, int reg_value)
+{
+ s->vga.dac_cache[s->vga.dac_sub_index] = reg_value;
+ if (++s->vga.dac_sub_index == 3) {
+ if ((s->vga.sr[0x12] & CIRRUS_CURSOR_HIDDENPEL)) {
+ memcpy(&s->cirrus_hidden_palette[(s->vga.dac_write_index & 0x0f) * 3],
+ s->vga.dac_cache, 3);
+ } else {
+ memcpy(&s->vga.palette[s->vga.dac_write_index * 3], s->vga.dac_cache, 3);
+ }
+ /* XXX update cursor */
+ s->vga.dac_sub_index = 0;
+ s->vga.dac_write_index++;
+ }
+}
+
+/***************************************
+ *
+ * I/O access between 0x3ce-0x3cf
+ *
+ ***************************************/
+
+static int cirrus_vga_read_gr(CirrusVGAState * s, unsigned reg_index)
+{
+ switch (reg_index) {
+ case 0x00: // Standard VGA, BGCOLOR 0x000000ff
+ return s->cirrus_shadow_gr0;
+ case 0x01: // Standard VGA, FGCOLOR 0x000000ff
+ return s->cirrus_shadow_gr1;
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ case 0x06: // Standard VGA
+ case 0x07: // Standard VGA
+ case 0x08: // Standard VGA
+ return s->vga.gr[s->vga.gr_index];
+ case 0x05: // Standard VGA, Cirrus extended mode
+ default:
+ break;
+ }
+
+ if (reg_index < 0x3a) {
+ return s->vga.gr[reg_index];
+ } else {
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: inport gr_index %02x\n", reg_index);
+#endif
+ return 0xff;
+ }
+}
+
+static void
+cirrus_vga_write_gr(CirrusVGAState * s, unsigned reg_index, int reg_value)
+{
+#if defined(DEBUG_BITBLT) && 0
+ printf("gr%02x: %02x\n", reg_index, reg_value);
+#endif
+ switch (reg_index) {
+ case 0x00: // Standard VGA, BGCOLOR 0x000000ff
+ s->vga.gr[reg_index] = reg_value & gr_mask[reg_index];
+ s->cirrus_shadow_gr0 = reg_value;
+ break;
+ case 0x01: // Standard VGA, FGCOLOR 0x000000ff
+ s->vga.gr[reg_index] = reg_value & gr_mask[reg_index];
+ s->cirrus_shadow_gr1 = reg_value;
+ break;
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ case 0x06: // Standard VGA
+ case 0x07: // Standard VGA
+ case 0x08: // Standard VGA
+ s->vga.gr[reg_index] = reg_value & gr_mask[reg_index];
+ break;
+ case 0x05: // Standard VGA, Cirrus extended mode
+ s->vga.gr[reg_index] = reg_value & 0x7f;
+ cirrus_update_memory_access(s);
+ break;
+ case 0x09: // bank offset #0
+ case 0x0A: // bank offset #1
+ s->vga.gr[reg_index] = reg_value;
+ cirrus_update_bank_ptr(s, 0);
+ cirrus_update_bank_ptr(s, 1);
+ cirrus_update_memory_access(s);
+ break;
+ case 0x0B:
+ s->vga.gr[reg_index] = reg_value;
+ cirrus_update_bank_ptr(s, 0);
+ cirrus_update_bank_ptr(s, 1);
+ cirrus_update_memory_access(s);
+ break;
+ case 0x10: // BGCOLOR 0x0000ff00
+ case 0x11: // FGCOLOR 0x0000ff00
+ case 0x12: // BGCOLOR 0x00ff0000
+ case 0x13: // FGCOLOR 0x00ff0000
+ case 0x14: // BGCOLOR 0xff000000
+ case 0x15: // FGCOLOR 0xff000000
+ case 0x20: // BLT WIDTH 0x0000ff
+ case 0x22: // BLT HEIGHT 0x0000ff
+ case 0x24: // BLT DEST PITCH 0x0000ff
+ case 0x26: // BLT SRC PITCH 0x0000ff
+ case 0x28: // BLT DEST ADDR 0x0000ff
+ case 0x29: // BLT DEST ADDR 0x00ff00
+ case 0x2c: // BLT SRC ADDR 0x0000ff
+ case 0x2d: // BLT SRC ADDR 0x00ff00
+ case 0x2f: // BLT WRITEMASK
+ case 0x30: // BLT MODE
+ case 0x32: // RASTER OP
+ case 0x33: // BLT MODEEXT
+ case 0x34: // BLT TRANSPARENT COLOR 0x00ff
+ case 0x35: // BLT TRANSPARENT COLOR 0xff00
+ case 0x38: // BLT TRANSPARENT COLOR MASK 0x00ff
+ case 0x39: // BLT TRANSPARENT COLOR MASK 0xff00
+ s->vga.gr[reg_index] = reg_value;
+ break;
+ case 0x21: // BLT WIDTH 0x001f00
+ case 0x23: // BLT HEIGHT 0x001f00
+ case 0x25: // BLT DEST PITCH 0x001f00
+ case 0x27: // BLT SRC PITCH 0x001f00
+ s->vga.gr[reg_index] = reg_value & 0x1f;
+ break;
+ case 0x2a: // BLT DEST ADDR 0x3f0000
+ s->vga.gr[reg_index] = reg_value & 0x3f;
+ /* if auto start mode, starts bit blt now */
+ if (s->vga.gr[0x31] & CIRRUS_BLT_AUTOSTART) {
+ cirrus_bitblt_start(s);
+ }
+ break;
+ case 0x2e: // BLT SRC ADDR 0x3f0000
+ s->vga.gr[reg_index] = reg_value & 0x3f;
+ break;
+ case 0x31: // BLT STATUS/START
+ cirrus_write_bitblt(s, reg_value);
+ break;
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: outport gr_index %02x, gr_value %02x\n", reg_index,
+ reg_value);
+#endif
+ break;
+ }
+}
+
+/***************************************
+ *
+ * I/O access between 0x3d4-0x3d5
+ *
+ ***************************************/
+
+static int cirrus_vga_read_cr(CirrusVGAState * s, unsigned reg_index)
+{
+ switch (reg_index) {
+ case 0x00: // Standard VGA
+ case 0x01: // Standard VGA
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ case 0x05: // Standard VGA
+ case 0x06: // Standard VGA
+ case 0x07: // Standard VGA
+ case 0x08: // Standard VGA
+ case 0x09: // Standard VGA
+ case 0x0a: // Standard VGA
+ case 0x0b: // Standard VGA
+ case 0x0c: // Standard VGA
+ case 0x0d: // Standard VGA
+ case 0x0e: // Standard VGA
+ case 0x0f: // Standard VGA
+ case 0x10: // Standard VGA
+ case 0x11: // Standard VGA
+ case 0x12: // Standard VGA
+ case 0x13: // Standard VGA
+ case 0x14: // Standard VGA
+ case 0x15: // Standard VGA
+ case 0x16: // Standard VGA
+ case 0x17: // Standard VGA
+ case 0x18: // Standard VGA
+ return s->vga.cr[s->vga.cr_index];
+ case 0x24: // Attribute Controller Toggle Readback (R)
+ return (s->vga.ar_flip_flop << 7);
+ case 0x19: // Interlace End
+ case 0x1a: // Miscellaneous Control
+ case 0x1b: // Extended Display Control
+ case 0x1c: // Sync Adjust and Genlock
+ case 0x1d: // Overlay Extended Control
+ case 0x22: // Graphics Data Latches Readback (R)
+ case 0x25: // Part Status
+ case 0x27: // Part ID (R)
+ return s->vga.cr[s->vga.cr_index];
+ case 0x26: // Attribute Controller Index Readback (R)
+ return s->vga.ar_index & 0x3f;
+ break;
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: inport cr_index %02x\n", reg_index);
+#endif
+ return 0xff;
+ }
+}
+
+static void cirrus_vga_write_cr(CirrusVGAState * s, int reg_value)
+{
+ switch (s->vga.cr_index) {
+ case 0x00: // Standard VGA
+ case 0x01: // Standard VGA
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ case 0x05: // Standard VGA
+ case 0x06: // Standard VGA
+ case 0x07: // Standard VGA
+ case 0x08: // Standard VGA
+ case 0x09: // Standard VGA
+ case 0x0a: // Standard VGA
+ case 0x0b: // Standard VGA
+ case 0x0c: // Standard VGA
+ case 0x0d: // Standard VGA
+ case 0x0e: // Standard VGA
+ case 0x0f: // Standard VGA
+ case 0x10: // Standard VGA
+ case 0x11: // Standard VGA
+ case 0x12: // Standard VGA
+ case 0x13: // Standard VGA
+ case 0x14: // Standard VGA
+ case 0x15: // Standard VGA
+ case 0x16: // Standard VGA
+ case 0x17: // Standard VGA
+ case 0x18: // Standard VGA
+ /* handle CR0-7 protection */
+ if ((s->vga.cr[0x11] & 0x80) && s->vga.cr_index <= 7) {
+ /* can always write bit 4 of CR7 */
+ if (s->vga.cr_index == 7)
+ s->vga.cr[7] = (s->vga.cr[7] & ~0x10) | (reg_value & 0x10);
+ return;
+ }
+ s->vga.cr[s->vga.cr_index] = reg_value;
+ switch(s->vga.cr_index) {
+ case 0x00:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x11:
+ case 0x17:
+ s->vga.update_retrace_info(&s->vga);
+ break;
+ }
+ break;
+ case 0x19: // Interlace End
+ case 0x1a: // Miscellaneous Control
+ case 0x1b: // Extended Display Control
+ case 0x1c: // Sync Adjust and Genlock
+ case 0x1d: // Overlay Extended Control
+ s->vga.cr[s->vga.cr_index] = reg_value;
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: handled outport cr_index %02x, cr_value %02x\n",
+ s->vga.cr_index, reg_value);
+#endif
+ break;
+ case 0x22: // Graphics Data Latches Readback (R)
+ case 0x24: // Attribute Controller Toggle Readback (R)
+ case 0x26: // Attribute Controller Index Readback (R)
+ case 0x27: // Part ID (R)
+ break;
+ case 0x25: // Part Status
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: outport cr_index %02x, cr_value %02x\n",
+ s->vga.cr_index, reg_value);
+#endif
+ break;
+ }
+}
+
+/***************************************
+ *
+ * memory-mapped I/O (bitblt)
+ *
+ ***************************************/
+
+static uint8_t cirrus_mmio_blt_read(CirrusVGAState * s, unsigned address)
+{
+ int value = 0xff;
+
+ switch (address) {
+ case (CIRRUS_MMIO_BLTBGCOLOR + 0):
+ value = cirrus_vga_read_gr(s, 0x00);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 1):
+ value = cirrus_vga_read_gr(s, 0x10);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 2):
+ value = cirrus_vga_read_gr(s, 0x12);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 3):
+ value = cirrus_vga_read_gr(s, 0x14);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 0):
+ value = cirrus_vga_read_gr(s, 0x01);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 1):
+ value = cirrus_vga_read_gr(s, 0x11);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 2):
+ value = cirrus_vga_read_gr(s, 0x13);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 3):
+ value = cirrus_vga_read_gr(s, 0x15);
+ break;
+ case (CIRRUS_MMIO_BLTWIDTH + 0):
+ value = cirrus_vga_read_gr(s, 0x20);
+ break;
+ case (CIRRUS_MMIO_BLTWIDTH + 1):
+ value = cirrus_vga_read_gr(s, 0x21);
+ break;
+ case (CIRRUS_MMIO_BLTHEIGHT + 0):
+ value = cirrus_vga_read_gr(s, 0x22);
+ break;
+ case (CIRRUS_MMIO_BLTHEIGHT + 1):
+ value = cirrus_vga_read_gr(s, 0x23);
+ break;
+ case (CIRRUS_MMIO_BLTDESTPITCH + 0):
+ value = cirrus_vga_read_gr(s, 0x24);
+ break;
+ case (CIRRUS_MMIO_BLTDESTPITCH + 1):
+ value = cirrus_vga_read_gr(s, 0x25);
+ break;
+ case (CIRRUS_MMIO_BLTSRCPITCH + 0):
+ value = cirrus_vga_read_gr(s, 0x26);
+ break;
+ case (CIRRUS_MMIO_BLTSRCPITCH + 1):
+ value = cirrus_vga_read_gr(s, 0x27);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 0):
+ value = cirrus_vga_read_gr(s, 0x28);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 1):
+ value = cirrus_vga_read_gr(s, 0x29);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 2):
+ value = cirrus_vga_read_gr(s, 0x2a);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 0):
+ value = cirrus_vga_read_gr(s, 0x2c);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 1):
+ value = cirrus_vga_read_gr(s, 0x2d);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 2):
+ value = cirrus_vga_read_gr(s, 0x2e);
+ break;
+ case CIRRUS_MMIO_BLTWRITEMASK:
+ value = cirrus_vga_read_gr(s, 0x2f);
+ break;
+ case CIRRUS_MMIO_BLTMODE:
+ value = cirrus_vga_read_gr(s, 0x30);
+ break;
+ case CIRRUS_MMIO_BLTROP:
+ value = cirrus_vga_read_gr(s, 0x32);
+ break;
+ case CIRRUS_MMIO_BLTMODEEXT:
+ value = cirrus_vga_read_gr(s, 0x33);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 0):
+ value = cirrus_vga_read_gr(s, 0x34);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 1):
+ value = cirrus_vga_read_gr(s, 0x35);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 0):
+ value = cirrus_vga_read_gr(s, 0x38);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 1):
+ value = cirrus_vga_read_gr(s, 0x39);
+ break;
+ case CIRRUS_MMIO_BLTSTATUS:
+ value = cirrus_vga_read_gr(s, 0x31);
+ break;
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: mmio read - address 0x%04x\n", address);
+#endif
+ break;
+ }
+
+ return (uint8_t) value;
+}
+
+static void cirrus_mmio_blt_write(CirrusVGAState * s, unsigned address,
+ uint8_t value)
+{
+ switch (address) {
+ case (CIRRUS_MMIO_BLTBGCOLOR + 0):
+ cirrus_vga_write_gr(s, 0x00, value);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 1):
+ cirrus_vga_write_gr(s, 0x10, value);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 2):
+ cirrus_vga_write_gr(s, 0x12, value);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 3):
+ cirrus_vga_write_gr(s, 0x14, value);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 0):
+ cirrus_vga_write_gr(s, 0x01, value);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 1):
+ cirrus_vga_write_gr(s, 0x11, value);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 2):
+ cirrus_vga_write_gr(s, 0x13, value);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 3):
+ cirrus_vga_write_gr(s, 0x15, value);
+ break;
+ case (CIRRUS_MMIO_BLTWIDTH + 0):
+ cirrus_vga_write_gr(s, 0x20, value);
+ break;
+ case (CIRRUS_MMIO_BLTWIDTH + 1):
+ cirrus_vga_write_gr(s, 0x21, value);
+ break;
+ case (CIRRUS_MMIO_BLTHEIGHT + 0):
+ cirrus_vga_write_gr(s, 0x22, value);
+ break;
+ case (CIRRUS_MMIO_BLTHEIGHT + 1):
+ cirrus_vga_write_gr(s, 0x23, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTPITCH + 0):
+ cirrus_vga_write_gr(s, 0x24, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTPITCH + 1):
+ cirrus_vga_write_gr(s, 0x25, value);
+ break;
+ case (CIRRUS_MMIO_BLTSRCPITCH + 0):
+ cirrus_vga_write_gr(s, 0x26, value);
+ break;
+ case (CIRRUS_MMIO_BLTSRCPITCH + 1):
+ cirrus_vga_write_gr(s, 0x27, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 0):
+ cirrus_vga_write_gr(s, 0x28, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 1):
+ cirrus_vga_write_gr(s, 0x29, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 2):
+ cirrus_vga_write_gr(s, 0x2a, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 3):
+ /* ignored */
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 0):
+ cirrus_vga_write_gr(s, 0x2c, value);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 1):
+ cirrus_vga_write_gr(s, 0x2d, value);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 2):
+ cirrus_vga_write_gr(s, 0x2e, value);
+ break;
+ case CIRRUS_MMIO_BLTWRITEMASK:
+ cirrus_vga_write_gr(s, 0x2f, value);
+ break;
+ case CIRRUS_MMIO_BLTMODE:
+ cirrus_vga_write_gr(s, 0x30, value);
+ break;
+ case CIRRUS_MMIO_BLTROP:
+ cirrus_vga_write_gr(s, 0x32, value);
+ break;
+ case CIRRUS_MMIO_BLTMODEEXT:
+ cirrus_vga_write_gr(s, 0x33, value);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 0):
+ cirrus_vga_write_gr(s, 0x34, value);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 1):
+ cirrus_vga_write_gr(s, 0x35, value);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 0):
+ cirrus_vga_write_gr(s, 0x38, value);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 1):
+ cirrus_vga_write_gr(s, 0x39, value);
+ break;
+ case CIRRUS_MMIO_BLTSTATUS:
+ cirrus_vga_write_gr(s, 0x31, value);
+ break;
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: mmio write - addr 0x%04x val 0x%02x (ignored)\n",
+ address, value);
+#endif
+ break;
+ }
+}
+
+/***************************************
+ *
+ * write mode 4/5
+ *
+ ***************************************/
+
+static void cirrus_mem_writeb_mode4and5_8bpp(CirrusVGAState * s,
+ unsigned mode,
+ unsigned offset,
+ uint32_t mem_value)
+{
+ int x;
+ unsigned val = mem_value;
+ uint8_t *dst;
+
+ dst = s->vga.vram_ptr + (offset &= s->cirrus_addr_mask);
+ for (x = 0; x < 8; x++) {
+ if (val & 0x80) {
+ *dst = s->cirrus_shadow_gr1;
+ } else if (mode == 5) {
+ *dst = s->cirrus_shadow_gr0;
+ }
+ val <<= 1;
+ dst++;
+ }
+ memory_region_set_dirty(&s->vga.vram, offset, 8);
+}
+
+static void cirrus_mem_writeb_mode4and5_16bpp(CirrusVGAState * s,
+ unsigned mode,
+ unsigned offset,
+ uint32_t mem_value)
+{
+ int x;
+ unsigned val = mem_value;
+ uint8_t *dst;
+
+ dst = s->vga.vram_ptr + (offset &= s->cirrus_addr_mask);
+ for (x = 0; x < 8; x++) {
+ if (val & 0x80) {
+ *dst = s->cirrus_shadow_gr1;
+ *(dst + 1) = s->vga.gr[0x11];
+ } else if (mode == 5) {
+ *dst = s->cirrus_shadow_gr0;
+ *(dst + 1) = s->vga.gr[0x10];
+ }
+ val <<= 1;
+ dst += 2;
+ }
+ memory_region_set_dirty(&s->vga.vram, offset, 16);
+}
+
+/***************************************
+ *
+ * memory access between 0xa0000-0xbffff
+ *
+ ***************************************/
+
+static uint64_t cirrus_vga_mem_read(void *opaque,
+ hwaddr addr,
+ uint32_t size)
+{
+ CirrusVGAState *s = opaque;
+ unsigned bank_index;
+ unsigned bank_offset;
+ uint32_t val;
+
+ if ((s->vga.sr[0x07] & 0x01) == 0) {
+ return vga_mem_readb(&s->vga, addr);
+ }
+
+ if (addr < 0x10000) {
+ /* XXX handle bitblt */
+ /* video memory */
+ bank_index = addr >> 15;
+ bank_offset = addr & 0x7fff;
+ if (bank_offset < s->cirrus_bank_limit[bank_index]) {
+ bank_offset += s->cirrus_bank_base[bank_index];
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ bank_offset <<= 4;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ bank_offset <<= 3;
+ }
+ bank_offset &= s->cirrus_addr_mask;
+ val = *(s->vga.vram_ptr + bank_offset);
+ } else
+ val = 0xff;
+ } else if (addr >= 0x18000 && addr < 0x18100) {
+ /* memory-mapped I/O */
+ val = 0xff;
+ if ((s->vga.sr[0x17] & 0x44) == 0x04) {
+ val = cirrus_mmio_blt_read(s, addr & 0xff);
+ }
+ } else {
+ val = 0xff;
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: mem_readb " TARGET_FMT_plx "\n", addr);
+#endif
+ }
+ return val;
+}
+
+static void cirrus_vga_mem_write(void *opaque,
+ hwaddr addr,
+ uint64_t mem_value,
+ uint32_t size)
+{
+ CirrusVGAState *s = opaque;
+ unsigned bank_index;
+ unsigned bank_offset;
+ unsigned mode;
+
+ if ((s->vga.sr[0x07] & 0x01) == 0) {
+ vga_mem_writeb(&s->vga, addr, mem_value);
+ return;
+ }
+
+ if (addr < 0x10000) {
+ if (s->cirrus_srcptr != s->cirrus_srcptr_end) {
+ /* bitblt */
+ *s->cirrus_srcptr++ = (uint8_t) mem_value;
+ if (s->cirrus_srcptr >= s->cirrus_srcptr_end) {
+ cirrus_bitblt_cputovideo_next(s);
+ }
+ } else {
+ /* video memory */
+ bank_index = addr >> 15;
+ bank_offset = addr & 0x7fff;
+ if (bank_offset < s->cirrus_bank_limit[bank_index]) {
+ bank_offset += s->cirrus_bank_base[bank_index];
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ bank_offset <<= 4;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ bank_offset <<= 3;
+ }
+ bank_offset &= s->cirrus_addr_mask;
+ mode = s->vga.gr[0x05] & 0x7;
+ if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) {
+ *(s->vga.vram_ptr + bank_offset) = mem_value;
+ memory_region_set_dirty(&s->vga.vram, bank_offset,
+ sizeof(mem_value));
+ } else {
+ if ((s->vga.gr[0x0B] & 0x14) != 0x14) {
+ cirrus_mem_writeb_mode4and5_8bpp(s, mode,
+ bank_offset,
+ mem_value);
+ } else {
+ cirrus_mem_writeb_mode4and5_16bpp(s, mode,
+ bank_offset,
+ mem_value);
+ }
+ }
+ }
+ }
+ } else if (addr >= 0x18000 && addr < 0x18100) {
+ /* memory-mapped I/O */
+ if ((s->vga.sr[0x17] & 0x44) == 0x04) {
+ cirrus_mmio_blt_write(s, addr & 0xff, mem_value);
+ }
+ } else {
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: mem_writeb " TARGET_FMT_plx " value 0x%02" PRIu64 "\n", addr,
+ mem_value);
+#endif
+ }
+}
+
+static const MemoryRegionOps cirrus_vga_mem_ops = {
+ .read = cirrus_vga_mem_read,
+ .write = cirrus_vga_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+/***************************************
+ *
+ * hardware cursor
+ *
+ ***************************************/
+
+static inline void invalidate_cursor1(CirrusVGAState *s)
+{
+ if (s->last_hw_cursor_size) {
+ vga_invalidate_scanlines(&s->vga,
+ s->last_hw_cursor_y + s->last_hw_cursor_y_start,
+ s->last_hw_cursor_y + s->last_hw_cursor_y_end);
+ }
+}
+
+static inline void cirrus_cursor_compute_yrange(CirrusVGAState *s)
+{
+ const uint8_t *src;
+ uint32_t content;
+ int y, y_min, y_max;
+
+ src = s->vga.vram_ptr + s->real_vram_size - 16 * 1024;
+ if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) {
+ src += (s->vga.sr[0x13] & 0x3c) * 256;
+ y_min = 64;
+ y_max = -1;
+ for(y = 0; y < 64; y++) {
+ content = ((uint32_t *)src)[0] |
+ ((uint32_t *)src)[1] |
+ ((uint32_t *)src)[2] |
+ ((uint32_t *)src)[3];
+ if (content) {
+ if (y < y_min)
+ y_min = y;
+ if (y > y_max)
+ y_max = y;
+ }
+ src += 16;
+ }
+ } else {
+ src += (s->vga.sr[0x13] & 0x3f) * 256;
+ y_min = 32;
+ y_max = -1;
+ for(y = 0; y < 32; y++) {
+ content = ((uint32_t *)src)[0] |
+ ((uint32_t *)(src + 128))[0];
+ if (content) {
+ if (y < y_min)
+ y_min = y;
+ if (y > y_max)
+ y_max = y;
+ }
+ src += 4;
+ }
+ }
+ if (y_min > y_max) {
+ s->last_hw_cursor_y_start = 0;
+ s->last_hw_cursor_y_end = 0;
+ } else {
+ s->last_hw_cursor_y_start = y_min;
+ s->last_hw_cursor_y_end = y_max + 1;
+ }
+}
+
+/* NOTE: we do not currently handle the cursor bitmap change, so we
+ update the cursor only if it moves. */
+static void cirrus_cursor_invalidate(VGACommonState *s1)
+{
+ CirrusVGAState *s = container_of(s1, CirrusVGAState, vga);
+ int size;
+
+ if (!(s->vga.sr[0x12] & CIRRUS_CURSOR_SHOW)) {
+ size = 0;
+ } else {
+ if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE)
+ size = 64;
+ else
+ size = 32;
+ }
+ /* invalidate last cursor and new cursor if any change */
+ if (s->last_hw_cursor_size != size ||
+ s->last_hw_cursor_x != s->vga.hw_cursor_x ||
+ s->last_hw_cursor_y != s->vga.hw_cursor_y) {
+
+ invalidate_cursor1(s);
+
+ s->last_hw_cursor_size = size;
+ s->last_hw_cursor_x = s->vga.hw_cursor_x;
+ s->last_hw_cursor_y = s->vga.hw_cursor_y;
+ /* compute the real cursor min and max y */
+ cirrus_cursor_compute_yrange(s);
+ invalidate_cursor1(s);
+ }
+}
+
+static void vga_draw_cursor_line(uint8_t *d1,
+ const uint8_t *src1,
+ int poffset, int w,
+ unsigned int color0,
+ unsigned int color1,
+ unsigned int color_xor)
+{
+ const uint8_t *plane0, *plane1;
+ int x, b0, b1;
+ uint8_t *d;
+
+ d = d1;
+ plane0 = src1;
+ plane1 = src1 + poffset;
+ for (x = 0; x < w; x++) {
+ b0 = (plane0[x >> 3] >> (7 - (x & 7))) & 1;
+ b1 = (plane1[x >> 3] >> (7 - (x & 7))) & 1;
+ switch (b0 | (b1 << 1)) {
+ case 0:
+ break;
+ case 1:
+ ((uint32_t *)d)[0] ^= color_xor;
+ break;
+ case 2:
+ ((uint32_t *)d)[0] = color0;
+ break;
+ case 3:
+ ((uint32_t *)d)[0] = color1;
+ break;
+ }
+ d += 4;
+ }
+}
+
+static void cirrus_cursor_draw_line(VGACommonState *s1, uint8_t *d1, int scr_y)
+{
+ CirrusVGAState *s = container_of(s1, CirrusVGAState, vga);
+ int w, h, x1, x2, poffset;
+ unsigned int color0, color1;
+ const uint8_t *palette, *src;
+ uint32_t content;
+
+ if (!(s->vga.sr[0x12] & CIRRUS_CURSOR_SHOW))
+ return;
+ /* fast test to see if the cursor intersects with the scan line */
+ if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) {
+ h = 64;
+ } else {
+ h = 32;
+ }
+ if (scr_y < s->vga.hw_cursor_y ||
+ scr_y >= (s->vga.hw_cursor_y + h)) {
+ return;
+ }
+
+ src = s->vga.vram_ptr + s->real_vram_size - 16 * 1024;
+ if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) {
+ src += (s->vga.sr[0x13] & 0x3c) * 256;
+ src += (scr_y - s->vga.hw_cursor_y) * 16;
+ poffset = 8;
+ content = ((uint32_t *)src)[0] |
+ ((uint32_t *)src)[1] |
+ ((uint32_t *)src)[2] |
+ ((uint32_t *)src)[3];
+ } else {
+ src += (s->vga.sr[0x13] & 0x3f) * 256;
+ src += (scr_y - s->vga.hw_cursor_y) * 4;
+
+
+ poffset = 128;
+ content = ((uint32_t *)src)[0] |
+ ((uint32_t *)(src + 128))[0];
+ }
+ /* if nothing to draw, no need to continue */
+ if (!content)
+ return;
+ w = h;
+
+ x1 = s->vga.hw_cursor_x;
+ if (x1 >= s->vga.last_scr_width)
+ return;
+ x2 = s->vga.hw_cursor_x + w;
+ if (x2 > s->vga.last_scr_width)
+ x2 = s->vga.last_scr_width;
+ w = x2 - x1;
+ palette = s->cirrus_hidden_palette;
+ color0 = rgb_to_pixel32(c6_to_8(palette[0x0 * 3]),
+ c6_to_8(palette[0x0 * 3 + 1]),
+ c6_to_8(palette[0x0 * 3 + 2]));
+ color1 = rgb_to_pixel32(c6_to_8(palette[0xf * 3]),
+ c6_to_8(palette[0xf * 3 + 1]),
+ c6_to_8(palette[0xf * 3 + 2]));
+ d1 += x1 * 4;
+ vga_draw_cursor_line(d1, src, poffset, w, color0, color1, 0xffffff);
+}
+
+/***************************************
+ *
+ * LFB memory access
+ *
+ ***************************************/
+
+static uint64_t cirrus_linear_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CirrusVGAState *s = opaque;
+ uint32_t ret;
+
+ addr &= s->cirrus_addr_mask;
+
+ if (((s->vga.sr[0x17] & 0x44) == 0x44) &&
+ ((addr & s->linear_mmio_mask) == s->linear_mmio_mask)) {
+ /* memory-mapped I/O */
+ ret = cirrus_mmio_blt_read(s, addr & 0xff);
+ } else if (0) {
+ /* XXX handle bitblt */
+ ret = 0xff;
+ } else {
+ /* video memory */
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ addr <<= 4;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ addr <<= 3;
+ }
+ addr &= s->cirrus_addr_mask;
+ ret = *(s->vga.vram_ptr + addr);
+ }
+
+ return ret;
+}
+
+static void cirrus_linear_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ CirrusVGAState *s = opaque;
+ unsigned mode;
+
+ addr &= s->cirrus_addr_mask;
+
+ if (((s->vga.sr[0x17] & 0x44) == 0x44) &&
+ ((addr & s->linear_mmio_mask) == s->linear_mmio_mask)) {
+ /* memory-mapped I/O */
+ cirrus_mmio_blt_write(s, addr & 0xff, val);
+ } else if (s->cirrus_srcptr != s->cirrus_srcptr_end) {
+ /* bitblt */
+ *s->cirrus_srcptr++ = (uint8_t) val;
+ if (s->cirrus_srcptr >= s->cirrus_srcptr_end) {
+ cirrus_bitblt_cputovideo_next(s);
+ }
+ } else {
+ /* video memory */
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ addr <<= 4;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ addr <<= 3;
+ }
+ addr &= s->cirrus_addr_mask;
+
+ mode = s->vga.gr[0x05] & 0x7;
+ if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) {
+ *(s->vga.vram_ptr + addr) = (uint8_t) val;
+ memory_region_set_dirty(&s->vga.vram, addr, 1);
+ } else {
+ if ((s->vga.gr[0x0B] & 0x14) != 0x14) {
+ cirrus_mem_writeb_mode4and5_8bpp(s, mode, addr, val);
+ } else {
+ cirrus_mem_writeb_mode4and5_16bpp(s, mode, addr, val);
+ }
+ }
+ }
+}
+
+/***************************************
+ *
+ * system to screen memory access
+ *
+ ***************************************/
+
+
+static uint64_t cirrus_linear_bitblt_read(void *opaque,
+ hwaddr addr,
+ unsigned size)
+{
+ CirrusVGAState *s = opaque;
+ uint32_t ret;
+
+ /* XXX handle bitblt */
+ (void)s;
+ ret = 0xff;
+ return ret;
+}
+
+static void cirrus_linear_bitblt_write(void *opaque,
+ hwaddr addr,
+ uint64_t val,
+ unsigned size)
+{
+ CirrusVGAState *s = opaque;
+
+ if (s->cirrus_srcptr != s->cirrus_srcptr_end) {
+ /* bitblt */
+ *s->cirrus_srcptr++ = (uint8_t) val;
+ if (s->cirrus_srcptr >= s->cirrus_srcptr_end) {
+ cirrus_bitblt_cputovideo_next(s);
+ }
+ }
+}
+
+static const MemoryRegionOps cirrus_linear_bitblt_io_ops = {
+ .read = cirrus_linear_bitblt_read,
+ .write = cirrus_linear_bitblt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void map_linear_vram_bank(CirrusVGAState *s, unsigned bank)
+{
+ MemoryRegion *mr = &s->cirrus_bank[bank];
+ bool enabled = !(s->cirrus_srcptr != s->cirrus_srcptr_end)
+ && !((s->vga.sr[0x07] & 0x01) == 0)
+ && !((s->vga.gr[0x0B] & 0x14) == 0x14)
+ && !(s->vga.gr[0x0B] & 0x02);
+
+ memory_region_set_enabled(mr, enabled);
+ memory_region_set_alias_offset(mr, s->cirrus_bank_base[bank]);
+}
+
+static void map_linear_vram(CirrusVGAState *s)
+{
+ if (s->bustype == CIRRUS_BUSTYPE_PCI && !s->linear_vram) {
+ s->linear_vram = true;
+ memory_region_add_subregion_overlap(&s->pci_bar, 0, &s->vga.vram, 1);
+ }
+ map_linear_vram_bank(s, 0);
+ map_linear_vram_bank(s, 1);
+}
+
+static void unmap_linear_vram(CirrusVGAState *s)
+{
+ if (s->bustype == CIRRUS_BUSTYPE_PCI && s->linear_vram) {
+ s->linear_vram = false;
+ memory_region_del_subregion(&s->pci_bar, &s->vga.vram);
+ }
+ memory_region_set_enabled(&s->cirrus_bank[0], false);
+ memory_region_set_enabled(&s->cirrus_bank[1], false);
+}
+
+/* Compute the memory access functions */
+static void cirrus_update_memory_access(CirrusVGAState *s)
+{
+ unsigned mode;
+
+ memory_region_transaction_begin();
+ if ((s->vga.sr[0x17] & 0x44) == 0x44) {
+ goto generic_io;
+ } else if (s->cirrus_srcptr != s->cirrus_srcptr_end) {
+ goto generic_io;
+ } else {
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ goto generic_io;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ goto generic_io;
+ }
+
+ mode = s->vga.gr[0x05] & 0x7;
+ if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) {
+ map_linear_vram(s);
+ } else {
+ generic_io:
+ unmap_linear_vram(s);
+ }
+ }
+ memory_region_transaction_commit();
+}
+
+
+/* I/O ports */
+
+static uint64_t cirrus_vga_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CirrusVGAState *c = opaque;
+ VGACommonState *s = &c->vga;
+ int val, index;
+
+ addr += 0x3b0;
+
+ if (vga_ioport_invalid(s, addr)) {
+ val = 0xff;
+ } else {
+ switch (addr) {
+ case 0x3c0:
+ if (s->ar_flip_flop == 0) {
+ val = s->ar_index;
+ } else {
+ val = 0;
+ }
+ break;
+ case 0x3c1:
+ index = s->ar_index & 0x1f;
+ if (index < 21)
+ val = s->ar[index];
+ else
+ val = 0;
+ break;
+ case 0x3c2:
+ val = s->st00;
+ break;
+ case 0x3c4:
+ val = s->sr_index;
+ break;
+ case 0x3c5:
+ val = cirrus_vga_read_sr(c);
+ break;
+#ifdef DEBUG_VGA_REG
+ printf("vga: read SR%x = 0x%02x\n", s->sr_index, val);
+#endif
+ break;
+ case 0x3c6:
+ val = cirrus_read_hidden_dac(c);
+ break;
+ case 0x3c7:
+ val = s->dac_state;
+ break;
+ case 0x3c8:
+ val = s->dac_write_index;
+ c->cirrus_hidden_dac_lockindex = 0;
+ break;
+ case 0x3c9:
+ val = cirrus_vga_read_palette(c);
+ break;
+ case 0x3ca:
+ val = s->fcr;
+ break;
+ case 0x3cc:
+ val = s->msr;
+ break;
+ case 0x3ce:
+ val = s->gr_index;
+ break;
+ case 0x3cf:
+ val = cirrus_vga_read_gr(c, s->gr_index);
+#ifdef DEBUG_VGA_REG
+ printf("vga: read GR%x = 0x%02x\n", s->gr_index, val);
+#endif
+ break;
+ case 0x3b4:
+ case 0x3d4:
+ val = s->cr_index;
+ break;
+ case 0x3b5:
+ case 0x3d5:
+ val = cirrus_vga_read_cr(c, s->cr_index);
+#ifdef DEBUG_VGA_REG
+ printf("vga: read CR%x = 0x%02x\n", s->cr_index, val);
+#endif
+ break;
+ case 0x3ba:
+ case 0x3da:
+ /* just toggle to fool polling */
+ val = s->st01 = s->retrace(s);
+ s->ar_flip_flop = 0;
+ break;
+ default:
+ val = 0x00;
+ break;
+ }
+ }
+#if defined(DEBUG_VGA)
+ printf("VGA: read addr=0x%04x data=0x%02x\n", addr, val);
+#endif
+ return val;
+}
+
+static void cirrus_vga_ioport_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ CirrusVGAState *c = opaque;
+ VGACommonState *s = &c->vga;
+ int index;
+
+ addr += 0x3b0;
+
+ /* check port range access depending on color/monochrome mode */
+ if (vga_ioport_invalid(s, addr)) {
+ return;
+ }
+#ifdef DEBUG_VGA
+ printf("VGA: write addr=0x%04x data=0x%02x\n", addr, val);
+#endif
+
+ switch (addr) {
+ case 0x3c0:
+ if (s->ar_flip_flop == 0) {
+ val &= 0x3f;
+ s->ar_index = val;
+ } else {
+ index = s->ar_index & 0x1f;
+ switch (index) {
+ case 0x00 ... 0x0f:
+ s->ar[index] = val & 0x3f;
+ break;
+ case 0x10:
+ s->ar[index] = val & ~0x10;
+ break;
+ case 0x11:
+ s->ar[index] = val;
+ break;
+ case 0x12:
+ s->ar[index] = val & ~0xc0;
+ break;
+ case 0x13:
+ s->ar[index] = val & ~0xf0;
+ break;
+ case 0x14:
+ s->ar[index] = val & ~0xf0;
+ break;
+ default:
+ break;
+ }
+ }
+ s->ar_flip_flop ^= 1;
+ break;
+ case 0x3c2:
+ s->msr = val & ~0x10;
+ s->update_retrace_info(s);
+ break;
+ case 0x3c4:
+ s->sr_index = val;
+ break;
+ case 0x3c5:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write SR%x = 0x%02" PRIu64 "\n", s->sr_index, val);
+#endif
+ cirrus_vga_write_sr(c, val);
+ break;
+ case 0x3c6:
+ cirrus_write_hidden_dac(c, val);
+ break;
+ case 0x3c7:
+ s->dac_read_index = val;
+ s->dac_sub_index = 0;
+ s->dac_state = 3;
+ break;
+ case 0x3c8:
+ s->dac_write_index = val;
+ s->dac_sub_index = 0;
+ s->dac_state = 0;
+ break;
+ case 0x3c9:
+ cirrus_vga_write_palette(c, val);
+ break;
+ case 0x3ce:
+ s->gr_index = val;
+ break;
+ case 0x3cf:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write GR%x = 0x%02" PRIu64 "\n", s->gr_index, val);
+#endif
+ cirrus_vga_write_gr(c, s->gr_index, val);
+ break;
+ case 0x3b4:
+ case 0x3d4:
+ s->cr_index = val;
+ break;
+ case 0x3b5:
+ case 0x3d5:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write CR%x = 0x%02"PRIu64"\n", s->cr_index, val);
+#endif
+ cirrus_vga_write_cr(c, val);
+ break;
+ case 0x3ba:
+ case 0x3da:
+ s->fcr = val & 0x10;
+ break;
+ }
+}
+
+/***************************************
+ *
+ * memory-mapped I/O access
+ *
+ ***************************************/
+
+static uint64_t cirrus_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CirrusVGAState *s = opaque;
+
+ if (addr >= 0x100) {
+ return cirrus_mmio_blt_read(s, addr - 0x100);
+ } else {
+ return cirrus_vga_ioport_read(s, addr + 0x10, size);
+ }
+}
+
+static void cirrus_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ CirrusVGAState *s = opaque;
+
+ if (addr >= 0x100) {
+ cirrus_mmio_blt_write(s, addr - 0x100, val);
+ } else {
+ cirrus_vga_ioport_write(s, addr + 0x10, val, size);
+ }
+}
+
+static const MemoryRegionOps cirrus_mmio_io_ops = {
+ .read = cirrus_mmio_read,
+ .write = cirrus_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+/* load/save state */
+
+static int cirrus_post_load(void *opaque, int version_id)
+{
+ CirrusVGAState *s = opaque;
+
+ s->vga.gr[0x00] = s->cirrus_shadow_gr0 & 0x0f;
+ s->vga.gr[0x01] = s->cirrus_shadow_gr1 & 0x0f;
+
+ cirrus_update_memory_access(s);
+ /* force refresh */
+ s->vga.graphic_mode = -1;
+ cirrus_update_bank_ptr(s, 0);
+ cirrus_update_bank_ptr(s, 1);
+ return 0;
+}
+
+static const VMStateDescription vmstate_cirrus_vga = {
+ .name = "cirrus_vga",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .post_load = cirrus_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(vga.latch, CirrusVGAState),
+ VMSTATE_UINT8(vga.sr_index, CirrusVGAState),
+ VMSTATE_BUFFER(vga.sr, CirrusVGAState),
+ VMSTATE_UINT8(vga.gr_index, CirrusVGAState),
+ VMSTATE_UINT8(cirrus_shadow_gr0, CirrusVGAState),
+ VMSTATE_UINT8(cirrus_shadow_gr1, CirrusVGAState),
+ VMSTATE_BUFFER_START_MIDDLE(vga.gr, CirrusVGAState, 2),
+ VMSTATE_UINT8(vga.ar_index, CirrusVGAState),
+ VMSTATE_BUFFER(vga.ar, CirrusVGAState),
+ VMSTATE_INT32(vga.ar_flip_flop, CirrusVGAState),
+ VMSTATE_UINT8(vga.cr_index, CirrusVGAState),
+ VMSTATE_BUFFER(vga.cr, CirrusVGAState),
+ VMSTATE_UINT8(vga.msr, CirrusVGAState),
+ VMSTATE_UINT8(vga.fcr, CirrusVGAState),
+ VMSTATE_UINT8(vga.st00, CirrusVGAState),
+ VMSTATE_UINT8(vga.st01, CirrusVGAState),
+ VMSTATE_UINT8(vga.dac_state, CirrusVGAState),
+ VMSTATE_UINT8(vga.dac_sub_index, CirrusVGAState),
+ VMSTATE_UINT8(vga.dac_read_index, CirrusVGAState),
+ VMSTATE_UINT8(vga.dac_write_index, CirrusVGAState),
+ VMSTATE_BUFFER(vga.dac_cache, CirrusVGAState),
+ VMSTATE_BUFFER(vga.palette, CirrusVGAState),
+ VMSTATE_INT32(vga.bank_offset, CirrusVGAState),
+ VMSTATE_UINT8(cirrus_hidden_dac_lockindex, CirrusVGAState),
+ VMSTATE_UINT8(cirrus_hidden_dac_data, CirrusVGAState),
+ VMSTATE_UINT32(vga.hw_cursor_x, CirrusVGAState),
+ VMSTATE_UINT32(vga.hw_cursor_y, CirrusVGAState),
+ /* XXX: we do not save the bitblt state - we assume we do not save
+ the state when the blitter is active */
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_cirrus_vga = {
+ .name = "cirrus_vga",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCICirrusVGAState),
+ VMSTATE_STRUCT(cirrus_vga, PCICirrusVGAState, 0,
+ vmstate_cirrus_vga, CirrusVGAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/***************************************
+ *
+ * initialize
+ *
+ ***************************************/
+
+static void cirrus_reset(void *opaque)
+{
+ CirrusVGAState *s = opaque;
+
+ vga_common_reset(&s->vga);
+ unmap_linear_vram(s);
+ s->vga.sr[0x06] = 0x0f;
+ if (s->device_id == CIRRUS_ID_CLGD5446) {
+ /* 4MB 64 bit memory config, always PCI */
+ s->vga.sr[0x1F] = 0x2d; // MemClock
+ s->vga.gr[0x18] = 0x0f; // fastest memory configuration
+ s->vga.sr[0x0f] = 0x98;
+ s->vga.sr[0x17] = 0x20;
+ s->vga.sr[0x15] = 0x04; /* memory size, 3=2MB, 4=4MB */
+ } else {
+ s->vga.sr[0x1F] = 0x22; // MemClock
+ s->vga.sr[0x0F] = CIRRUS_MEMSIZE_2M;
+ s->vga.sr[0x17] = s->bustype;
+ s->vga.sr[0x15] = 0x03; /* memory size, 3=2MB, 4=4MB */
+ }
+ s->vga.cr[0x27] = s->device_id;
+
+ s->cirrus_hidden_dac_lockindex = 5;
+ s->cirrus_hidden_dac_data = 0;
+}
+
+static const MemoryRegionOps cirrus_linear_io_ops = {
+ .read = cirrus_linear_read,
+ .write = cirrus_linear_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps cirrus_vga_io_ops = {
+ .read = cirrus_vga_ioport_read,
+ .write = cirrus_vga_ioport_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void cirrus_init_common(CirrusVGAState *s, Object *owner,
+ int device_id, int is_pci,
+ MemoryRegion *system_memory,
+ MemoryRegion *system_io)
+{
+ int i;
+ static int inited;
+
+ if (!inited) {
+ inited = 1;
+ for(i = 0;i < 256; i++)
+ rop_to_index[i] = CIRRUS_ROP_NOP_INDEX; /* nop rop */
+ rop_to_index[CIRRUS_ROP_0] = 0;
+ rop_to_index[CIRRUS_ROP_SRC_AND_DST] = 1;
+ rop_to_index[CIRRUS_ROP_NOP] = 2;
+ rop_to_index[CIRRUS_ROP_SRC_AND_NOTDST] = 3;
+ rop_to_index[CIRRUS_ROP_NOTDST] = 4;
+ rop_to_index[CIRRUS_ROP_SRC] = 5;
+ rop_to_index[CIRRUS_ROP_1] = 6;
+ rop_to_index[CIRRUS_ROP_NOTSRC_AND_DST] = 7;
+ rop_to_index[CIRRUS_ROP_SRC_XOR_DST] = 8;
+ rop_to_index[CIRRUS_ROP_SRC_OR_DST] = 9;
+ rop_to_index[CIRRUS_ROP_NOTSRC_OR_NOTDST] = 10;
+ rop_to_index[CIRRUS_ROP_SRC_NOTXOR_DST] = 11;
+ rop_to_index[CIRRUS_ROP_SRC_OR_NOTDST] = 12;
+ rop_to_index[CIRRUS_ROP_NOTSRC] = 13;
+ rop_to_index[CIRRUS_ROP_NOTSRC_OR_DST] = 14;
+ rop_to_index[CIRRUS_ROP_NOTSRC_AND_NOTDST] = 15;
+ s->device_id = device_id;
+ if (is_pci)
+ s->bustype = CIRRUS_BUSTYPE_PCI;
+ else
+ s->bustype = CIRRUS_BUSTYPE_ISA;
+ }
+
+ /* Register ioport 0x3b0 - 0x3df */
+ memory_region_init_io(&s->cirrus_vga_io, owner, &cirrus_vga_io_ops, s,
+ "cirrus-io", 0x30);
+ memory_region_set_flush_coalesced(&s->cirrus_vga_io);
+ memory_region_add_subregion(system_io, 0x3b0, &s->cirrus_vga_io);
+
+ memory_region_init(&s->low_mem_container, owner,
+ "cirrus-lowmem-container",
+ 0x20000);
+
+ memory_region_init_io(&s->low_mem, owner, &cirrus_vga_mem_ops, s,
+ "cirrus-low-memory", 0x20000);
+ memory_region_add_subregion(&s->low_mem_container, 0, &s->low_mem);
+ for (i = 0; i < 2; ++i) {
+ static const char *names[] = { "vga.bank0", "vga.bank1" };
+ MemoryRegion *bank = &s->cirrus_bank[i];
+ memory_region_init_alias(bank, owner, names[i], &s->vga.vram,
+ 0, 0x8000);
+ memory_region_set_enabled(bank, false);
+ memory_region_add_subregion_overlap(&s->low_mem_container, i * 0x8000,
+ bank, 1);
+ }
+ memory_region_add_subregion_overlap(system_memory,
+ 0x000a0000,
+ &s->low_mem_container,
+ 1);
+ memory_region_set_coalescing(&s->low_mem);
+
+ /* I/O handler for LFB */
+ memory_region_init_io(&s->cirrus_linear_io, owner, &cirrus_linear_io_ops, s,
+ "cirrus-linear-io", s->vga.vram_size_mb
+ * 1024 * 1024);
+ memory_region_set_flush_coalesced(&s->cirrus_linear_io);
+
+ /* I/O handler for LFB */
+ memory_region_init_io(&s->cirrus_linear_bitblt_io, owner,
+ &cirrus_linear_bitblt_io_ops,
+ s,
+ "cirrus-bitblt-mmio",
+ 0x400000);
+ memory_region_set_flush_coalesced(&s->cirrus_linear_bitblt_io);
+
+ /* I/O handler for memory-mapped I/O */
+ memory_region_init_io(&s->cirrus_mmio_io, owner, &cirrus_mmio_io_ops, s,
+ "cirrus-mmio", CIRRUS_PNPMMIO_SIZE);
+ memory_region_set_flush_coalesced(&s->cirrus_mmio_io);
+
+ s->real_vram_size =
+ (s->device_id == CIRRUS_ID_CLGD5446) ? 4096 * 1024 : 2048 * 1024;
+
+ /* XXX: s->vga.vram_size must be a power of two */
+ s->cirrus_addr_mask = s->real_vram_size - 1;
+ s->linear_mmio_mask = s->real_vram_size - 256;
+
+ s->vga.get_bpp = cirrus_get_bpp;
+ s->vga.get_offsets = cirrus_get_offsets;
+ s->vga.get_resolution = cirrus_get_resolution;
+ s->vga.cursor_invalidate = cirrus_cursor_invalidate;
+ s->vga.cursor_draw_line = cirrus_cursor_draw_line;
+
+ qemu_register_reset(cirrus_reset, s);
+}
+
+/***************************************
+ *
+ * ISA bus support
+ *
+ ***************************************/
+
+static void isa_cirrus_vga_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISACirrusVGAState *d = ISA_CIRRUS_VGA(dev);
+ VGACommonState *s = &d->cirrus_vga.vga;
+
+ /* follow real hardware, cirrus card emulated has 4 MB video memory.
+ Also accept 8 MB/16 MB for backward compatibility. */
+ if (s->vram_size_mb != 4 && s->vram_size_mb != 8 &&
+ s->vram_size_mb != 16) {
+ error_setg(errp, "Invalid cirrus_vga ram size '%u'",
+ s->vram_size_mb);
+ return;
+ }
+ vga_common_init(s, OBJECT(dev), true);
+ cirrus_init_common(&d->cirrus_vga, OBJECT(dev), CIRRUS_ID_CLGD5430, 0,
+ isa_address_space(isadev),
+ isa_address_space_io(isadev));
+ s->con = graphic_console_init(dev, 0, s->hw_ops, s);
+ rom_add_vga(VGABIOS_CIRRUS_FILENAME);
+ /* XXX ISA-LFB support */
+ /* FIXME not qdev yet */
+}
+
+static Property isa_cirrus_vga_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", struct ISACirrusVGAState,
+ cirrus_vga.vga.vram_size_mb, 8),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void isa_cirrus_vga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_cirrus_vga;
+ dc->realize = isa_cirrus_vga_realizefn;
+ dc->props = isa_cirrus_vga_properties;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo isa_cirrus_vga_info = {
+ .name = TYPE_ISA_CIRRUS_VGA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISACirrusVGAState),
+ .class_init = isa_cirrus_vga_class_init,
+};
+
+/***************************************
+ *
+ * PCI bus support
+ *
+ ***************************************/
+
+static void pci_cirrus_vga_realize(PCIDevice *dev, Error **errp)
+{
+ PCICirrusVGAState *d = PCI_CIRRUS_VGA(dev);
+ CirrusVGAState *s = &d->cirrus_vga;
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+ int16_t device_id = pc->device_id;
+
+ /* follow real hardware, cirrus card emulated has 4 MB video memory.
+ Also accept 8 MB/16 MB for backward compatibility. */
+ if (s->vga.vram_size_mb != 4 && s->vga.vram_size_mb != 8 &&
+ s->vga.vram_size_mb != 16) {
+ error_setg(errp, "Invalid cirrus_vga ram size '%u'",
+ s->vga.vram_size_mb);
+ return;
+ }
+ /* setup VGA */
+ vga_common_init(&s->vga, OBJECT(dev), true);
+ cirrus_init_common(s, OBJECT(dev), device_id, 1, pci_address_space(dev),
+ pci_address_space_io(dev));
+ s->vga.con = graphic_console_init(DEVICE(dev), 0, s->vga.hw_ops, &s->vga);
+
+ /* setup PCI */
+
+ memory_region_init(&s->pci_bar, OBJECT(dev), "cirrus-pci-bar0", 0x2000000);
+
+ /* XXX: add byte swapping apertures */
+ memory_region_add_subregion(&s->pci_bar, 0, &s->cirrus_linear_io);
+ memory_region_add_subregion(&s->pci_bar, 0x1000000,
+ &s->cirrus_linear_bitblt_io);
+
+ /* setup memory space */
+ /* memory #0 LFB */
+ /* memory #1 memory-mapped I/O */
+ /* XXX: s->vga.vram_size must be a power of two */
+ pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->pci_bar);
+ if (device_id == CIRRUS_ID_CLGD5446) {
+ pci_register_bar(&d->dev, 1, 0, &s->cirrus_mmio_io);
+ }
+}
+
+static Property pci_vga_cirrus_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", struct PCICirrusVGAState,
+ cirrus_vga.vga.vram_size_mb, 8),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void cirrus_vga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_cirrus_vga_realize;
+ k->romfile = VGABIOS_CIRRUS_FILENAME;
+ k->vendor_id = PCI_VENDOR_ID_CIRRUS;
+ k->device_id = CIRRUS_ID_CLGD5446;
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->desc = "Cirrus CLGD 54xx VGA";
+ dc->vmsd = &vmstate_pci_cirrus_vga;
+ dc->props = pci_vga_cirrus_properties;
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo cirrus_vga_info = {
+ .name = TYPE_PCI_CIRRUS_VGA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCICirrusVGAState),
+ .class_init = cirrus_vga_class_init,
+};
+
+static void cirrus_vga_register_types(void)
+{
+ type_register_static(&isa_cirrus_vga_info);
+ type_register_static(&cirrus_vga_info);
+}
+
+type_init(cirrus_vga_register_types)
diff --git a/hw/display/cirrus_vga_rop.h b/hw/display/cirrus_vga_rop.h
new file mode 100644
index 00000000..0925a009
--- /dev/null
+++ b/hw/display/cirrus_vga_rop.h
@@ -0,0 +1,207 @@
+/*
+ * QEMU Cirrus CLGD 54xx VGA Emulator.
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * 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.
+ */
+
+static inline void glue(rop_8_,ROP_NAME)(uint8_t *dst, uint8_t src)
+{
+ *dst = ROP_FN(*dst, src);
+}
+
+static inline void glue(rop_16_,ROP_NAME)(uint16_t *dst, uint16_t src)
+{
+ *dst = ROP_FN(*dst, src);
+}
+
+static inline void glue(rop_32_,ROP_NAME)(uint32_t *dst, uint32_t src)
+{
+ *dst = ROP_FN(*dst, src);
+}
+
+#define ROP_OP(d, s) glue(rop_8_,ROP_NAME)(d, s)
+#define ROP_OP_16(d, s) glue(rop_16_,ROP_NAME)(d, s)
+#define ROP_OP_32(d, s) glue(rop_32_,ROP_NAME)(d, s)
+#undef ROP_FN
+
+static void
+glue(cirrus_bitblt_rop_fwd_, ROP_NAME)(CirrusVGAState *s,
+ uint8_t *dst,const uint8_t *src,
+ int dstpitch,int srcpitch,
+ int bltwidth,int bltheight)
+{
+ int x,y;
+ dstpitch -= bltwidth;
+ srcpitch -= bltwidth;
+
+ if (bltheight > 1 && (dstpitch < 0 || srcpitch < 0)) {
+ return;
+ }
+
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x++) {
+ ROP_OP(dst, *src);
+ dst++;
+ src++;
+ }
+ dst += dstpitch;
+ src += srcpitch;
+ }
+}
+
+static void
+glue(cirrus_bitblt_rop_bkwd_, ROP_NAME)(CirrusVGAState *s,
+ uint8_t *dst,const uint8_t *src,
+ int dstpitch,int srcpitch,
+ int bltwidth,int bltheight)
+{
+ int x,y;
+ dstpitch += bltwidth;
+ srcpitch += bltwidth;
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x++) {
+ ROP_OP(dst, *src);
+ dst--;
+ src--;
+ }
+ dst += dstpitch;
+ src += srcpitch;
+ }
+}
+
+static void
+glue(glue(cirrus_bitblt_rop_fwd_transp_, ROP_NAME),_8)(CirrusVGAState *s,
+ uint8_t *dst,const uint8_t *src,
+ int dstpitch,int srcpitch,
+ int bltwidth,int bltheight)
+{
+ int x,y;
+ uint8_t p;
+ dstpitch -= bltwidth;
+ srcpitch -= bltwidth;
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x++) {
+ p = *dst;
+ ROP_OP(&p, *src);
+ if (p != s->vga.gr[0x34]) *dst = p;
+ dst++;
+ src++;
+ }
+ dst += dstpitch;
+ src += srcpitch;
+ }
+}
+
+static void
+glue(glue(cirrus_bitblt_rop_bkwd_transp_, ROP_NAME),_8)(CirrusVGAState *s,
+ uint8_t *dst,const uint8_t *src,
+ int dstpitch,int srcpitch,
+ int bltwidth,int bltheight)
+{
+ int x,y;
+ uint8_t p;
+ dstpitch += bltwidth;
+ srcpitch += bltwidth;
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x++) {
+ p = *dst;
+ ROP_OP(&p, *src);
+ if (p != s->vga.gr[0x34]) *dst = p;
+ dst--;
+ src--;
+ }
+ dst += dstpitch;
+ src += srcpitch;
+ }
+}
+
+static void
+glue(glue(cirrus_bitblt_rop_fwd_transp_, ROP_NAME),_16)(CirrusVGAState *s,
+ uint8_t *dst,const uint8_t *src,
+ int dstpitch,int srcpitch,
+ int bltwidth,int bltheight)
+{
+ int x,y;
+ uint8_t p1, p2;
+ dstpitch -= bltwidth;
+ srcpitch -= bltwidth;
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x+=2) {
+ p1 = *dst;
+ p2 = *(dst+1);
+ ROP_OP(&p1, *src);
+ ROP_OP(&p2, *(src + 1));
+ if ((p1 != s->vga.gr[0x34]) || (p2 != s->vga.gr[0x35])) {
+ *dst = p1;
+ *(dst+1) = p2;
+ }
+ dst+=2;
+ src+=2;
+ }
+ dst += dstpitch;
+ src += srcpitch;
+ }
+}
+
+static void
+glue(glue(cirrus_bitblt_rop_bkwd_transp_, ROP_NAME),_16)(CirrusVGAState *s,
+ uint8_t *dst,const uint8_t *src,
+ int dstpitch,int srcpitch,
+ int bltwidth,int bltheight)
+{
+ int x,y;
+ uint8_t p1, p2;
+ dstpitch += bltwidth;
+ srcpitch += bltwidth;
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x+=2) {
+ p1 = *(dst-1);
+ p2 = *dst;
+ ROP_OP(&p1, *(src - 1));
+ ROP_OP(&p2, *src);
+ if ((p1 != s->vga.gr[0x34]) || (p2 != s->vga.gr[0x35])) {
+ *(dst-1) = p1;
+ *dst = p2;
+ }
+ dst-=2;
+ src-=2;
+ }
+ dst += dstpitch;
+ src += srcpitch;
+ }
+}
+
+#define DEPTH 8
+#include "cirrus_vga_rop2.h"
+
+#define DEPTH 16
+#include "cirrus_vga_rop2.h"
+
+#define DEPTH 24
+#include "cirrus_vga_rop2.h"
+
+#define DEPTH 32
+#include "cirrus_vga_rop2.h"
+
+#undef ROP_NAME
+#undef ROP_OP
+#undef ROP_OP_16
+#undef ROP_OP_32
diff --git a/hw/display/cirrus_vga_rop2.h b/hw/display/cirrus_vga_rop2.h
new file mode 100644
index 00000000..d28bcc6f
--- /dev/null
+++ b/hw/display/cirrus_vga_rop2.h
@@ -0,0 +1,281 @@
+/*
+ * QEMU Cirrus CLGD 54xx VGA Emulator.
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#if DEPTH == 8
+#define PUTPIXEL() ROP_OP(&d[0], col)
+#elif DEPTH == 16
+#define PUTPIXEL() ROP_OP_16((uint16_t *)&d[0], col)
+#elif DEPTH == 24
+#define PUTPIXEL() ROP_OP(&d[0], col); \
+ ROP_OP(&d[1], (col >> 8)); \
+ ROP_OP(&d[2], (col >> 16))
+#elif DEPTH == 32
+#define PUTPIXEL() ROP_OP_32(((uint32_t *)&d[0]), col)
+#else
+#error unsupported DEPTH
+#endif
+
+static void
+glue(glue(glue(cirrus_patternfill_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState * s, uint8_t * dst,
+ const uint8_t * src,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint8_t *d;
+ int x, y, pattern_y, pattern_pitch, pattern_x;
+ unsigned int col;
+ const uint8_t *src1;
+#if DEPTH == 24
+ int skipleft = s->vga.gr[0x2f] & 0x1f;
+#else
+ int skipleft = (s->vga.gr[0x2f] & 0x07) * (DEPTH / 8);
+#endif
+
+#if DEPTH == 8
+ pattern_pitch = 8;
+#elif DEPTH == 16
+ pattern_pitch = 16;
+#else
+ pattern_pitch = 32;
+#endif
+ pattern_y = s->cirrus_blt_srcaddr & 7;
+ for(y = 0; y < bltheight; y++) {
+ pattern_x = skipleft;
+ d = dst + skipleft;
+ src1 = src + pattern_y * pattern_pitch;
+ for (x = skipleft; x < bltwidth; x += (DEPTH / 8)) {
+#if DEPTH == 8
+ col = src1[pattern_x];
+ pattern_x = (pattern_x + 1) & 7;
+#elif DEPTH == 16
+ col = ((uint16_t *)(src1 + pattern_x))[0];
+ pattern_x = (pattern_x + 2) & 15;
+#elif DEPTH == 24
+ {
+ const uint8_t *src2 = src1 + pattern_x * 3;
+ col = src2[0] | (src2[1] << 8) | (src2[2] << 16);
+ pattern_x = (pattern_x + 1) & 7;
+ }
+#else
+ col = ((uint32_t *)(src1 + pattern_x))[0];
+ pattern_x = (pattern_x + 4) & 31;
+#endif
+ PUTPIXEL();
+ d += (DEPTH / 8);
+ }
+ pattern_y = (pattern_y + 1) & 7;
+ dst += dstpitch;
+ }
+}
+
+/* NOTE: srcpitch is ignored */
+static void
+glue(glue(glue(cirrus_colorexpand_transp_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState * s, uint8_t * dst,
+ const uint8_t * src,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint8_t *d;
+ int x, y;
+ unsigned bits, bits_xor;
+ unsigned int col;
+ unsigned bitmask;
+ unsigned index;
+#if DEPTH == 24
+ int dstskipleft = s->vga.gr[0x2f] & 0x1f;
+ int srcskipleft = dstskipleft / 3;
+#else
+ int srcskipleft = s->vga.gr[0x2f] & 0x07;
+ int dstskipleft = srcskipleft * (DEPTH / 8);
+#endif
+
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV) {
+ bits_xor = 0xff;
+ col = s->cirrus_blt_bgcol;
+ } else {
+ bits_xor = 0x00;
+ col = s->cirrus_blt_fgcol;
+ }
+
+ for(y = 0; y < bltheight; y++) {
+ bitmask = 0x80 >> srcskipleft;
+ bits = *src++ ^ bits_xor;
+ d = dst + dstskipleft;
+ for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) {
+ if ((bitmask & 0xff) == 0) {
+ bitmask = 0x80;
+ bits = *src++ ^ bits_xor;
+ }
+ index = (bits & bitmask);
+ if (index) {
+ PUTPIXEL();
+ }
+ d += (DEPTH / 8);
+ bitmask >>= 1;
+ }
+ dst += dstpitch;
+ }
+}
+
+static void
+glue(glue(glue(cirrus_colorexpand_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState * s, uint8_t * dst,
+ const uint8_t * src,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint32_t colors[2];
+ uint8_t *d;
+ int x, y;
+ unsigned bits;
+ unsigned int col;
+ unsigned bitmask;
+ int srcskipleft = s->vga.gr[0x2f] & 0x07;
+ int dstskipleft = srcskipleft * (DEPTH / 8);
+
+ colors[0] = s->cirrus_blt_bgcol;
+ colors[1] = s->cirrus_blt_fgcol;
+ for(y = 0; y < bltheight; y++) {
+ bitmask = 0x80 >> srcskipleft;
+ bits = *src++;
+ d = dst + dstskipleft;
+ for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) {
+ if ((bitmask & 0xff) == 0) {
+ bitmask = 0x80;
+ bits = *src++;
+ }
+ col = colors[!!(bits & bitmask)];
+ PUTPIXEL();
+ d += (DEPTH / 8);
+ bitmask >>= 1;
+ }
+ dst += dstpitch;
+ }
+}
+
+static void
+glue(glue(glue(cirrus_colorexpand_pattern_transp_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState * s, uint8_t * dst,
+ const uint8_t * src,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint8_t *d;
+ int x, y, bitpos, pattern_y;
+ unsigned int bits, bits_xor;
+ unsigned int col;
+#if DEPTH == 24
+ int dstskipleft = s->vga.gr[0x2f] & 0x1f;
+ int srcskipleft = dstskipleft / 3;
+#else
+ int srcskipleft = s->vga.gr[0x2f] & 0x07;
+ int dstskipleft = srcskipleft * (DEPTH / 8);
+#endif
+
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV) {
+ bits_xor = 0xff;
+ col = s->cirrus_blt_bgcol;
+ } else {
+ bits_xor = 0x00;
+ col = s->cirrus_blt_fgcol;
+ }
+ pattern_y = s->cirrus_blt_srcaddr & 7;
+
+ for(y = 0; y < bltheight; y++) {
+ bits = src[pattern_y] ^ bits_xor;
+ bitpos = 7 - srcskipleft;
+ d = dst + dstskipleft;
+ for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) {
+ if ((bits >> bitpos) & 1) {
+ PUTPIXEL();
+ }
+ d += (DEPTH / 8);
+ bitpos = (bitpos - 1) & 7;
+ }
+ pattern_y = (pattern_y + 1) & 7;
+ dst += dstpitch;
+ }
+}
+
+static void
+glue(glue(glue(cirrus_colorexpand_pattern_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState * s, uint8_t * dst,
+ const uint8_t * src,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint32_t colors[2];
+ uint8_t *d;
+ int x, y, bitpos, pattern_y;
+ unsigned int bits;
+ unsigned int col;
+ int srcskipleft = s->vga.gr[0x2f] & 0x07;
+ int dstskipleft = srcskipleft * (DEPTH / 8);
+
+ colors[0] = s->cirrus_blt_bgcol;
+ colors[1] = s->cirrus_blt_fgcol;
+ pattern_y = s->cirrus_blt_srcaddr & 7;
+
+ for(y = 0; y < bltheight; y++) {
+ bits = src[pattern_y];
+ bitpos = 7 - srcskipleft;
+ d = dst + dstskipleft;
+ for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) {
+ col = colors[(bits >> bitpos) & 1];
+ PUTPIXEL();
+ d += (DEPTH / 8);
+ bitpos = (bitpos - 1) & 7;
+ }
+ pattern_y = (pattern_y + 1) & 7;
+ dst += dstpitch;
+ }
+}
+
+static void
+glue(glue(glue(cirrus_fill_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState *s,
+ uint8_t *dst, int dst_pitch,
+ int width, int height)
+{
+ uint8_t *d, *d1;
+ uint32_t col;
+ int x, y;
+
+ col = s->cirrus_blt_fgcol;
+
+ d1 = dst;
+ for(y = 0; y < height; y++) {
+ d = d1;
+ for(x = 0; x < width; x += (DEPTH / 8)) {
+ PUTPIXEL();
+ d += (DEPTH / 8);
+ }
+ d1 += dst_pitch;
+ }
+}
+
+#undef DEPTH
+#undef PUTPIXEL
diff --git a/hw/display/exynos4210_fimd.c b/hw/display/exynos4210_fimd.c
new file mode 100644
index 00000000..603ef505
--- /dev/null
+++ b/hw/display/exynos4210_fimd.c
@@ -0,0 +1,1953 @@
+/*
+ * Samsung exynos4210 Display Controller (FIMD)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Based on LCD controller for Samsung S5PC1xx-based board emulation
+ * by Kirill Batuzov <batuzovk@ispras.ru>
+ *
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "hw/sysbus.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "qemu/bswap.h"
+
+/* Debug messages configuration */
+#define EXYNOS4210_FIMD_DEBUG 0
+#define EXYNOS4210_FIMD_MODE_TRACE 0
+
+#if EXYNOS4210_FIMD_DEBUG == 0
+ #define DPRINT_L1(fmt, args...) do { } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+ #define DPRINT_ERROR(fmt, args...) do { } while (0)
+#elif EXYNOS4210_FIMD_DEBUG == 1
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+ #define DPRINT_ERROR(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#else
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define DPRINT_ERROR(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#endif
+
+#if EXYNOS4210_FIMD_MODE_TRACE == 0
+ #define DPRINT_TRACE(fmt, args...) do { } while (0)
+#else
+ #define DPRINT_TRACE(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+#endif
+
+#define NUM_OF_WINDOWS 5
+#define FIMD_REGS_SIZE 0x4114
+
+/* Video main control registers */
+#define FIMD_VIDCON0 0x0000
+#define FIMD_VIDCON1 0x0004
+#define FIMD_VIDCON2 0x0008
+#define FIMD_VIDCON3 0x000C
+#define FIMD_VIDCON0_ENVID_F (1 << 0)
+#define FIMD_VIDCON0_ENVID (1 << 1)
+#define FIMD_VIDCON0_ENVID_MASK ((1 << 0) | (1 << 1))
+#define FIMD_VIDCON1_ROMASK 0x07FFE000
+
+/* Video time control registers */
+#define FIMD_VIDTCON_START 0x10
+#define FIMD_VIDTCON_END 0x1C
+#define FIMD_VIDTCON2_SIZE_MASK 0x07FF
+#define FIMD_VIDTCON2_HOR_SHIFT 0
+#define FIMD_VIDTCON2_VER_SHIFT 11
+
+/* Window control registers */
+#define FIMD_WINCON_START 0x0020
+#define FIMD_WINCON_END 0x0030
+#define FIMD_WINCON_ROMASK 0x82200000
+#define FIMD_WINCON_ENWIN (1 << 0)
+#define FIMD_WINCON_BLD_PIX (1 << 6)
+#define FIMD_WINCON_ALPHA_MUL (1 << 7)
+#define FIMD_WINCON_ALPHA_SEL (1 << 1)
+#define FIMD_WINCON_SWAP 0x078000
+#define FIMD_WINCON_SWAP_SHIFT 15
+#define FIMD_WINCON_SWAP_WORD 0x1
+#define FIMD_WINCON_SWAP_HWORD 0x2
+#define FIMD_WINCON_SWAP_BYTE 0x4
+#define FIMD_WINCON_SWAP_BITS 0x8
+#define FIMD_WINCON_BUFSTAT_L (1 << 21)
+#define FIMD_WINCON_BUFSTAT_H (1 << 31)
+#define FIMD_WINCON_BUFSTATUS ((1 << 21) | (1 << 31))
+#define FIMD_WINCON_BUF0_STAT ((0 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF1_STAT ((1 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF2_STAT ((0 << 21) | (1 << 31))
+#define FIMD_WINCON_BUFSELECT ((1 << 20) | (1 << 30))
+#define FIMD_WINCON_BUF0_SEL ((0 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF1_SEL ((1 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF2_SEL ((0 << 20) | (1 << 30))
+#define FIMD_WINCON_BUFMODE (1 << 14)
+#define IS_PALETTIZED_MODE(w) (w->wincon & 0xC)
+#define PAL_MODE_WITH_ALPHA(x) ((x) == 7)
+#define WIN_BPP_MODE(w) ((w->wincon >> 2) & 0xF)
+#define WIN_BPP_MODE_WITH_ALPHA(w) \
+ (WIN_BPP_MODE(w) == 0xD || WIN_BPP_MODE(w) == 0xE)
+
+/* Shadow control register */
+#define FIMD_SHADOWCON 0x0034
+#define FIMD_WINDOW_PROTECTED(s, w) ((s) & (1 << (10 + (w))))
+/* Channel mapping control register */
+#define FIMD_WINCHMAP 0x003C
+
+/* Window position control registers */
+#define FIMD_VIDOSD_START 0x0040
+#define FIMD_VIDOSD_END 0x0088
+#define FIMD_VIDOSD_COORD_MASK 0x07FF
+#define FIMD_VIDOSD_HOR_SHIFT 11
+#define FIMD_VIDOSD_VER_SHIFT 0
+#define FIMD_VIDOSD_ALPHA_AEN0 0xFFF000
+#define FIMD_VIDOSD_AEN0_SHIFT 12
+#define FIMD_VIDOSD_ALPHA_AEN1 0x000FFF
+
+/* Frame buffer address registers */
+#define FIMD_VIDWADD0_START 0x00A0
+#define FIMD_VIDWADD0_END 0x00C4
+#define FIMD_VIDWADD0_END 0x00C4
+#define FIMD_VIDWADD1_START 0x00D0
+#define FIMD_VIDWADD1_END 0x00F4
+#define FIMD_VIDWADD2_START 0x0100
+#define FIMD_VIDWADD2_END 0x0110
+#define FIMD_VIDWADD2_PAGEWIDTH 0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE 0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13
+#define FIMD_VIDW0ADD0_B2 0x20A0
+#define FIMD_VIDW4ADD0_B2 0x20C0
+
+/* Video interrupt control registers */
+#define FIMD_VIDINTCON0 0x130
+#define FIMD_VIDINTCON1 0x134
+
+/* Window color key registers */
+#define FIMD_WKEYCON_START 0x140
+#define FIMD_WKEYCON_END 0x15C
+#define FIMD_WKEYCON0_COMPKEY 0x00FFFFFF
+#define FIMD_WKEYCON0_CTL_SHIFT 24
+#define FIMD_WKEYCON0_DIRCON (1 << 24)
+#define FIMD_WKEYCON0_KEYEN (1 << 25)
+#define FIMD_WKEYCON0_KEYBLEN (1 << 26)
+/* Window color key alpha control register */
+#define FIMD_WKEYALPHA_START 0x160
+#define FIMD_WKEYALPHA_END 0x16C
+
+/* Dithering control register */
+#define FIMD_DITHMODE 0x170
+
+/* Window alpha control registers */
+#define FIMD_VIDALPHA_ALPHA_LOWER 0x000F0F0F
+#define FIMD_VIDALPHA_ALPHA_UPPER 0x00F0F0F0
+#define FIMD_VIDWALPHA_START 0x21C
+#define FIMD_VIDWALPHA_END 0x240
+
+/* Window color map registers */
+#define FIMD_WINMAP_START 0x180
+#define FIMD_WINMAP_END 0x190
+#define FIMD_WINMAP_EN (1 << 24)
+#define FIMD_WINMAP_COLOR_MASK 0x00FFFFFF
+
+/* Window palette control registers */
+#define FIMD_WPALCON_HIGH 0x019C
+#define FIMD_WPALCON_LOW 0x01A0
+#define FIMD_WPALCON_UPDATEEN (1 << 9)
+#define FIMD_WPAL_W0PAL_L 0x07
+#define FIMD_WPAL_W0PAL_L_SHT 0
+#define FIMD_WPAL_W1PAL_L 0x07
+#define FIMD_WPAL_W1PAL_L_SHT 3
+#define FIMD_WPAL_W2PAL_L 0x01
+#define FIMD_WPAL_W2PAL_L_SHT 6
+#define FIMD_WPAL_W2PAL_H 0x06
+#define FIMD_WPAL_W2PAL_H_SHT 8
+#define FIMD_WPAL_W3PAL_L 0x01
+#define FIMD_WPAL_W3PAL_L_SHT 7
+#define FIMD_WPAL_W3PAL_H 0x06
+#define FIMD_WPAL_W3PAL_H_SHT 12
+#define FIMD_WPAL_W4PAL_L 0x01
+#define FIMD_WPAL_W4PAL_L_SHT 8
+#define FIMD_WPAL_W4PAL_H 0x06
+#define FIMD_WPAL_W4PAL_H_SHT 16
+
+/* Trigger control registers */
+#define FIMD_TRIGCON 0x01A4
+#define FIMD_TRIGCON_ROMASK 0x00000004
+
+/* LCD I80 Interface Control */
+#define FIMD_I80IFCON_START 0x01B0
+#define FIMD_I80IFCON_END 0x01BC
+/* Color gain control register */
+#define FIMD_COLORGAINCON 0x01C0
+/* LCD i80 Interface Command Control */
+#define FIMD_LDI_CMDCON0 0x01D0
+#define FIMD_LDI_CMDCON1 0x01D4
+/* I80 System Interface Manual Command Control */
+#define FIMD_SIFCCON0 0x01E0
+#define FIMD_SIFCCON2 0x01E8
+
+/* Hue Control Registers */
+#define FIMD_HUECOEFCR_START 0x01EC
+#define FIMD_HUECOEFCR_END 0x01F4
+#define FIMD_HUECOEFCB_START 0x01FC
+#define FIMD_HUECOEFCB_END 0x0208
+#define FIMD_HUEOFFSET 0x020C
+
+/* Video interrupt control registers */
+#define FIMD_VIDINT_INTFIFOPEND (1 << 0)
+#define FIMD_VIDINT_INTFRMPEND (1 << 1)
+#define FIMD_VIDINT_INTI80PEND (1 << 2)
+#define FIMD_VIDINT_INTEN (1 << 0)
+#define FIMD_VIDINT_INTFIFOEN (1 << 1)
+#define FIMD_VIDINT_INTFRMEN (1 << 12)
+#define FIMD_VIDINT_I80IFDONE (1 << 17)
+
+/* Window blend equation control registers */
+#define FIMD_BLENDEQ_START 0x0244
+#define FIMD_BLENDEQ_END 0x0250
+#define FIMD_BLENDCON 0x0260
+#define FIMD_ALPHA_8BIT (1 << 0)
+#define FIMD_BLENDEQ_COEF_MASK 0xF
+
+/* Window RTQOS Control Registers */
+#define FIMD_WRTQOSCON_START 0x0264
+#define FIMD_WRTQOSCON_END 0x0274
+
+/* LCD I80 Interface Command */
+#define FIMD_I80IFCMD_START 0x0280
+#define FIMD_I80IFCMD_END 0x02AC
+
+/* Shadow windows control registers */
+#define FIMD_SHD_ADD0_START 0x40A0
+#define FIMD_SHD_ADD0_END 0x40C0
+#define FIMD_SHD_ADD1_START 0x40D0
+#define FIMD_SHD_ADD1_END 0x40F0
+#define FIMD_SHD_ADD2_START 0x4100
+#define FIMD_SHD_ADD2_END 0x4110
+
+/* Palette memory */
+#define FIMD_PAL_MEM_START 0x2400
+#define FIMD_PAL_MEM_END 0x37FC
+/* Palette memory aliases for windows 0 and 1 */
+#define FIMD_PALMEM_AL_START 0x0400
+#define FIMD_PALMEM_AL_END 0x0BFC
+
+typedef struct {
+ uint8_t r, g, b;
+ /* D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */
+ uint32_t a;
+} rgba;
+#define RGBA_SIZE 7
+
+typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p);
+typedef struct Exynos4210fimdWindow Exynos4210fimdWindow;
+
+struct Exynos4210fimdWindow {
+ uint32_t wincon; /* Window control register */
+ uint32_t buf_start[3]; /* Start address for video frame buffer */
+ uint32_t buf_end[3]; /* End address for video frame buffer */
+ uint32_t keycon[2]; /* Window color key registers */
+ uint32_t keyalpha; /* Color key alpha control register */
+ uint32_t winmap; /* Window color map register */
+ uint32_t blendeq; /* Window blending equation control register */
+ uint32_t rtqoscon; /* Window RTQOS Control Registers */
+ uint32_t palette[256]; /* Palette RAM */
+ uint32_t shadow_buf_start; /* Start address of shadow frame buffer */
+ uint32_t shadow_buf_end; /* End address of shadow frame buffer */
+ uint32_t shadow_buf_size; /* Virtual shadow screen width */
+
+ pixel_to_rgb_func *pixel_to_rgb;
+ void (*draw_line)(Exynos4210fimdWindow *w, uint8_t *src, uint8_t *dst,
+ bool blend);
+ uint32_t (*get_alpha)(Exynos4210fimdWindow *w, uint32_t pix_a);
+ uint16_t lefttop_x, lefttop_y; /* VIDOSD0 register */
+ uint16_t rightbot_x, rightbot_y; /* VIDOSD1 register */
+ uint32_t osdsize; /* VIDOSD2&3 register */
+ uint32_t alpha_val[2]; /* VIDOSD2&3, VIDWALPHA registers */
+ uint16_t virtpage_width; /* VIDWADD2 register */
+ uint16_t virtpage_offsize; /* VIDWADD2 register */
+ MemoryRegionSection mem_section; /* RAM fragment containing framebuffer */
+ uint8_t *host_fb_addr; /* Host pointer to window's framebuffer */
+ hwaddr fb_len; /* Framebuffer length */
+};
+
+#define TYPE_EXYNOS4210_FIMD "exynos4210.fimd"
+#define EXYNOS4210_FIMD(obj) \
+ OBJECT_CHECK(Exynos4210fimdState, (obj), TYPE_EXYNOS4210_FIMD)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ QemuConsole *console;
+ qemu_irq irq[3];
+
+ uint32_t vidcon[4]; /* Video main control registers 0-3 */
+ uint32_t vidtcon[4]; /* Video time control registers 0-3 */
+ uint32_t shadowcon; /* Window shadow control register */
+ uint32_t winchmap; /* Channel mapping control register */
+ uint32_t vidintcon[2]; /* Video interrupt control registers */
+ uint32_t dithmode; /* Dithering control register */
+ uint32_t wpalcon[2]; /* Window palette control registers */
+ uint32_t trigcon; /* Trigger control register */
+ uint32_t i80ifcon[4]; /* I80 interface control registers */
+ uint32_t colorgaincon; /* Color gain control register */
+ uint32_t ldi_cmdcon[2]; /* LCD I80 interface command control */
+ uint32_t sifccon[3]; /* I80 System Interface Manual Command Control */
+ uint32_t huecoef_cr[4]; /* Hue control registers */
+ uint32_t huecoef_cb[4]; /* Hue control registers */
+ uint32_t hueoffset; /* Hue offset control register */
+ uint32_t blendcon; /* Blending control register */
+ uint32_t i80ifcmd[12]; /* LCD I80 Interface Command */
+
+ Exynos4210fimdWindow window[5]; /* Window-specific registers */
+ uint8_t *ifb; /* Internal frame buffer */
+ bool invalidate; /* Image needs to be redrawn */
+ bool enabled; /* Display controller is enabled */
+} Exynos4210fimdState;
+
+/* Perform byte/halfword/word swap of data according to WINCON */
+static inline void fimd_swap_data(unsigned int swap_ctl, uint64_t *data)
+{
+ int i;
+ uint64_t res;
+ uint64_t x = *data;
+
+ if (swap_ctl & FIMD_WINCON_SWAP_BITS) {
+ res = 0;
+ for (i = 0; i < 64; i++) {
+ if (x & (1ULL << (63 - i))) {
+ res |= (1ULL << i);
+ }
+ }
+ x = res;
+ }
+
+ if (swap_ctl & FIMD_WINCON_SWAP_BYTE) {
+ x = bswap64(x);
+ }
+
+ if (swap_ctl & FIMD_WINCON_SWAP_HWORD) {
+ x = ((x & 0x000000000000FFFFULL) << 48) |
+ ((x & 0x00000000FFFF0000ULL) << 16) |
+ ((x & 0x0000FFFF00000000ULL) >> 16) |
+ ((x & 0xFFFF000000000000ULL) >> 48);
+ }
+
+ if (swap_ctl & FIMD_WINCON_SWAP_WORD) {
+ x = ((x & 0x00000000FFFFFFFFULL) << 32) |
+ ((x & 0xFFFFFFFF00000000ULL) >> 32);
+ }
+
+ *data = x;
+}
+
+/* Conversion routines of Pixel data from frame buffer area to internal RGBA
+ * pixel representation.
+ * Every color component internally represented as 8-bit value. If original
+ * data has less than 8 bit for component, data is extended to 8 bit. For
+ * example, if blue component has only two possible values 0 and 1 it will be
+ * extended to 0 and 0xFF */
+
+/* One bit for alpha representation */
+#define DEF_PIXEL_TO_RGB_A1(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ pixel >>= (R); \
+ p->a = (pixel & 0x1); \
+}
+
+DEF_PIXEL_TO_RGB_A1(pixel_a444_to_rgb, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A1(pixel_a555_to_rgb, 5, 5, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a666_to_rgb, 6, 6, 6)
+DEF_PIXEL_TO_RGB_A1(pixel_a665_to_rgb, 6, 6, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a888_to_rgb, 8, 8, 8)
+DEF_PIXEL_TO_RGB_A1(pixel_a887_to_rgb, 8, 8, 7)
+
+/* Alpha component is always zero */
+#define DEF_PIXEL_TO_RGB_A0(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ p->a = 0x0; \
+}
+
+DEF_PIXEL_TO_RGB_A0(pixel_565_to_rgb, 5, 6, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_555_to_rgb, 5, 5, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_666_to_rgb, 6, 6, 6)
+DEF_PIXEL_TO_RGB_A0(pixel_888_to_rgb, 8, 8, 8)
+
+/* Alpha component has some meaningful value */
+#define DEF_PIXEL_TO_RGB_A(N, R, G, B, A) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ pixel >>= (R); \
+ p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)) | \
+ ((pixel >> (2 * (A) - 8)) & ((1 << (8 - (A))) - 1)); \
+ p->a = p->a | (p->a << 8) | (p->a << 16); \
+}
+
+DEF_PIXEL_TO_RGB_A(pixel_4444_to_rgb, 4, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A(pixel_8888_to_rgb, 8, 8, 8, 8)
+
+/* Lookup table to extent 2-bit color component to 8 bit */
+static const uint8_t pixel_lutable_2b[4] = {
+ 0x0, 0x55, 0xAA, 0xFF
+};
+/* Lookup table to extent 3-bit color component to 8 bit */
+static const uint8_t pixel_lutable_3b[8] = {
+ 0x0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF
+};
+/* Special case for a232 bpp mode */
+static void pixel_a232_to_rgb(uint32_t pixel, rgba *p)
+{
+ p->b = pixel_lutable_2b[(pixel & 0x3)];
+ pixel >>= 2;
+ p->g = pixel_lutable_3b[(pixel & 0x7)];
+ pixel >>= 3;
+ p->r = pixel_lutable_2b[(pixel & 0x3)];
+ pixel >>= 2;
+ p->a = (pixel & 0x1);
+}
+
+/* Special case for (5+1, 5+1, 5+1) mode. Data bit 15 is common LSB
+ * for all three color components */
+static void pixel_1555_to_rgb(uint32_t pixel, rgba *p)
+{
+ uint8_t comm = (pixel >> 15) & 1;
+ p->b = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ pixel >>= 5;
+ p->g = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ pixel >>= 5;
+ p->r = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ p->a = 0x0;
+}
+
+/* Put/get pixel to/from internal LCD Controller framebuffer */
+
+static int put_pixel_ifb(const rgba p, uint8_t *d)
+{
+ *(uint8_t *)d++ = p.r;
+ *(uint8_t *)d++ = p.g;
+ *(uint8_t *)d++ = p.b;
+ *(uint32_t *)d = p.a;
+ return RGBA_SIZE;
+}
+
+static int get_pixel_ifb(const uint8_t *s, rgba *p)
+{
+ p->r = *(uint8_t *)s++;
+ p->g = *(uint8_t *)s++;
+ p->b = *(uint8_t *)s++;
+ p->a = (*(uint32_t *)s) & 0x00FFFFFF;
+ return RGBA_SIZE;
+}
+
+static pixel_to_rgb_func *palette_data_format[8] = {
+ [0] = pixel_565_to_rgb,
+ [1] = pixel_a555_to_rgb,
+ [2] = pixel_666_to_rgb,
+ [3] = pixel_a665_to_rgb,
+ [4] = pixel_a666_to_rgb,
+ [5] = pixel_888_to_rgb,
+ [6] = pixel_a888_to_rgb,
+ [7] = pixel_8888_to_rgb
+};
+
+/* Returns Index in palette data formats table for given window number WINDOW */
+static uint32_t
+exynos4210_fimd_palette_format(Exynos4210fimdState *s, int window)
+{
+ uint32_t ret;
+
+ switch (window) {
+ case 0:
+ ret = (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SHT) & FIMD_WPAL_W0PAL_L;
+ if (ret != 7) {
+ ret = 6 - ret;
+ }
+ break;
+ case 1:
+ ret = (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SHT) & FIMD_WPAL_W1PAL_L;
+ if (ret != 7) {
+ ret = 6 - ret;
+ }
+ break;
+ case 2:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SHT) & FIMD_WPAL_W2PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SHT) & FIMD_WPAL_W2PAL_L);
+ break;
+ case 3:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SHT) & FIMD_WPAL_W3PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SHT) & FIMD_WPAL_W3PAL_L);
+ break;
+ case 4:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SHT) & FIMD_WPAL_W4PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SHT) & FIMD_WPAL_W4PAL_L);
+ break;
+ default:
+ hw_error("exynos4210.fimd: incorrect window number %d\n", window);
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+#define FIMD_1_MINUS_COLOR(x) \
+ ((0xFF - ((x) & 0xFF)) | (0xFF00 - ((x) & 0xFF00)) | \
+ (0xFF0000 - ((x) & 0xFF0000)))
+#define EXTEND_LOWER_HALFBYTE(x) (((x) & 0xF0F0F) | (((x) << 4) & 0xF0F0F0))
+#define EXTEND_UPPER_HALFBYTE(x) (((x) & 0xF0F0F0) | (((x) >> 4) & 0xF0F0F))
+
+/* Multiply three lower bytes of two 32-bit words with each other.
+ * Each byte with values 0-255 is considered as a number with possible values
+ * in a range [0 - 1] */
+static inline uint32_t fimd_mult_each_byte(uint32_t a, uint32_t b)
+{
+ uint32_t tmp;
+ uint32_t ret;
+
+ ret = ((tmp = (((a & 0xFF) * (b & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF : tmp;
+ ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF00 : tmp << 8;
+ ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF0000 : tmp << 16;
+ return ret;
+}
+
+/* For each corresponding bytes of two 32-bit words: (a*b + c*d)
+ * Byte values 0-255 are mapped to a range [0 .. 1] */
+static inline uint32_t
+fimd_mult_and_sum_each_byte(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+{
+ uint32_t tmp;
+ uint32_t ret;
+
+ ret = ((tmp = (((a & 0xFF) * (b & 0xFF) + (c & 0xFF) * (d & 0xFF)) / 0xFF))
+ > 0xFF) ? 0xFF : tmp;
+ ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF) + ((c >> 8) & 0xFF) *
+ ((d >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF00 : tmp << 8;
+ ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF) +
+ ((c >> 16) & 0xFF) * ((d >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF0000 : tmp << 16;
+ return ret;
+}
+
+/* These routines cover all possible sources of window's transparent factor
+ * used in blending equation. Choice of routine is affected by WPALCON
+ * registers, BLENDCON register and window's WINCON register */
+
+static uint32_t fimd_get_alpha_pix(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return pix_a;
+}
+
+static uint32_t
+fimd_get_alpha_pix_extlow(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return EXTEND_LOWER_HALFBYTE(pix_a);
+}
+
+static uint32_t
+fimd_get_alpha_pix_exthigh(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return EXTEND_UPPER_HALFBYTE(pix_a);
+}
+
+static uint32_t fimd_get_alpha_mult(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return fimd_mult_each_byte(pix_a, w->alpha_val[0]);
+}
+
+static uint32_t fimd_get_alpha_mult_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return fimd_mult_each_byte(EXTEND_LOWER_HALFBYTE(pix_a),
+ EXTEND_UPPER_HALFBYTE(w->alpha_val[0]));
+}
+
+static uint32_t fimd_get_alpha_aen(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return w->alpha_val[pix_a];
+}
+
+static uint32_t fimd_get_alpha_aen_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return EXTEND_UPPER_HALFBYTE(w->alpha_val[pix_a]);
+}
+
+static uint32_t fimd_get_alpha_sel(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return w->alpha_val[(w->wincon & FIMD_WINCON_ALPHA_SEL) ? 1 : 0];
+}
+
+static uint32_t fimd_get_alpha_sel_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return EXTEND_UPPER_HALFBYTE(w->alpha_val[(w->wincon &
+ FIMD_WINCON_ALPHA_SEL) ? 1 : 0]);
+}
+
+/* Updates currently active alpha value get function for specified window */
+static void fimd_update_get_alpha(Exynos4210fimdState *s, int win)
+{
+ Exynos4210fimdWindow *w = &s->window[win];
+ const bool alpha_is_8bit = s->blendcon & FIMD_ALPHA_8BIT;
+
+ if (w->wincon & FIMD_WINCON_BLD_PIX) {
+ if ((w->wincon & FIMD_WINCON_ALPHA_SEL) && WIN_BPP_MODE_WITH_ALPHA(w)) {
+ /* In this case, alpha component contains meaningful value */
+ if (w->wincon & FIMD_WINCON_ALPHA_MUL) {
+ w->get_alpha = alpha_is_8bit ?
+ fimd_get_alpha_mult : fimd_get_alpha_mult_ext;
+ } else {
+ w->get_alpha = alpha_is_8bit ?
+ fimd_get_alpha_pix : fimd_get_alpha_pix_extlow;
+ }
+ } else {
+ if (IS_PALETTIZED_MODE(w) &&
+ PAL_MODE_WITH_ALPHA(exynos4210_fimd_palette_format(s, win))) {
+ /* Alpha component has 8-bit numeric value */
+ w->get_alpha = alpha_is_8bit ?
+ fimd_get_alpha_pix : fimd_get_alpha_pix_exthigh;
+ } else {
+ /* Alpha has only two possible values (AEN) */
+ w->get_alpha = alpha_is_8bit ?
+ fimd_get_alpha_aen : fimd_get_alpha_aen_ext;
+ }
+ }
+ } else {
+ w->get_alpha = alpha_is_8bit ? fimd_get_alpha_sel :
+ fimd_get_alpha_sel_ext;
+ }
+}
+
+/* Blends current window's (w) pixel (foreground pixel *ret) with background
+ * window (w_blend) pixel p_bg according to formula:
+ * NEW_COLOR = a_coef x FG_PIXEL_COLOR + b_coef x BG_PIXEL_COLOR
+ * NEW_ALPHA = p_coef x FG_ALPHA + q_coef x BG_ALPHA
+ */
+static void
+exynos4210_fimd_blend_pixel(Exynos4210fimdWindow *w, rgba p_bg, rgba *ret)
+{
+ rgba p_fg = *ret;
+ uint32_t bg_color = ((p_bg.r & 0xFF) << 16) | ((p_bg.g & 0xFF) << 8) |
+ (p_bg.b & 0xFF);
+ uint32_t fg_color = ((p_fg.r & 0xFF) << 16) | ((p_fg.g & 0xFF) << 8) |
+ (p_fg.b & 0xFF);
+ uint32_t alpha_fg = p_fg.a;
+ int i;
+ /* It is possible that blending equation parameters a and b do not
+ * depend on window BLENEQ register. Account for this with first_coef */
+ enum { A_COEF = 0, B_COEF = 1, P_COEF = 2, Q_COEF = 3, COEF_NUM = 4};
+ uint32_t first_coef = A_COEF;
+ uint32_t blend_param[COEF_NUM];
+
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYEN) {
+ uint32_t colorkey = (w->keycon[1] &
+ ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) & FIMD_WKEYCON0_COMPKEY;
+
+ if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) &&
+ (bg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+ /* Foreground pixel is displayed */
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+ alpha_fg = w->keyalpha;
+ blend_param[A_COEF] = alpha_fg;
+ blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+ } else {
+ alpha_fg = 0;
+ blend_param[A_COEF] = 0xFFFFFF;
+ blend_param[B_COEF] = 0x0;
+ }
+ first_coef = P_COEF;
+ } else if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) == 0 &&
+ (fg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+ /* Background pixel is displayed */
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+ alpha_fg = w->keyalpha;
+ blend_param[A_COEF] = alpha_fg;
+ blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+ } else {
+ alpha_fg = 0;
+ blend_param[A_COEF] = 0x0;
+ blend_param[B_COEF] = 0xFFFFFF;
+ }
+ first_coef = P_COEF;
+ }
+ }
+
+ for (i = first_coef; i < COEF_NUM; i++) {
+ switch ((w->blendeq >> i * 6) & FIMD_BLENDEQ_COEF_MASK) {
+ case 0:
+ blend_param[i] = 0;
+ break;
+ case 1:
+ blend_param[i] = 0xFFFFFF;
+ break;
+ case 2:
+ blend_param[i] = alpha_fg;
+ break;
+ case 3:
+ blend_param[i] = FIMD_1_MINUS_COLOR(alpha_fg);
+ break;
+ case 4:
+ blend_param[i] = p_bg.a;
+ break;
+ case 5:
+ blend_param[i] = FIMD_1_MINUS_COLOR(p_bg.a);
+ break;
+ case 6:
+ blend_param[i] = w->alpha_val[0];
+ break;
+ case 10:
+ blend_param[i] = fg_color;
+ break;
+ case 11:
+ blend_param[i] = FIMD_1_MINUS_COLOR(fg_color);
+ break;
+ case 12:
+ blend_param[i] = bg_color;
+ break;
+ case 13:
+ blend_param[i] = FIMD_1_MINUS_COLOR(bg_color);
+ break;
+ default:
+ hw_error("exynos4210.fimd: blend equation coef illegal value\n");
+ break;
+ }
+ }
+
+ fg_color = fimd_mult_and_sum_each_byte(bg_color, blend_param[B_COEF],
+ fg_color, blend_param[A_COEF]);
+ ret->b = fg_color & 0xFF;
+ fg_color >>= 8;
+ ret->g = fg_color & 0xFF;
+ fg_color >>= 8;
+ ret->r = fg_color & 0xFF;
+ ret->a = fimd_mult_and_sum_each_byte(alpha_fg, blend_param[P_COEF],
+ p_bg.a, blend_param[Q_COEF]);
+}
+
+/* These routines read data from video frame buffer in system RAM, convert
+ * this data to display controller internal representation, if necessary,
+ * perform pixel blending with data, currently presented in internal buffer.
+ * Result is stored in display controller internal frame buffer. */
+
+/* Draw line with index in palette table in RAM frame buffer data */
+#define DEF_DRAW_LINE_PALETTE(N) \
+static void glue(draw_line_palette_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+ uint8_t *dst, bool blend) \
+{ \
+ int width = w->rightbot_x - w->lefttop_x + 1; \
+ uint8_t *ifb = dst; \
+ uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+ uint64_t data; \
+ rgba p, p_old; \
+ int i; \
+ do { \
+ memcpy(&data, src, sizeof(data)); \
+ src += 8; \
+ fimd_swap_data(swap, &data); \
+ for (i = (64 / (N) - 1); i >= 0; i--) { \
+ w->pixel_to_rgb(w->palette[(data >> ((N) * i)) & \
+ ((1ULL << (N)) - 1)], &p); \
+ p.a = w->get_alpha(w, p.a); \
+ if (blend) { \
+ ifb += get_pixel_ifb(ifb, &p_old); \
+ exynos4210_fimd_blend_pixel(w, p_old, &p); \
+ } \
+ dst += put_pixel_ifb(p, dst); \
+ } \
+ width -= (64 / (N)); \
+ } while (width > 0); \
+}
+
+/* Draw line with direct color value in RAM frame buffer data */
+#define DEF_DRAW_LINE_NOPALETTE(N) \
+static void glue(draw_line_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+ uint8_t *dst, bool blend) \
+{ \
+ int width = w->rightbot_x - w->lefttop_x + 1; \
+ uint8_t *ifb = dst; \
+ uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+ uint64_t data; \
+ rgba p, p_old; \
+ int i; \
+ do { \
+ memcpy(&data, src, sizeof(data)); \
+ src += 8; \
+ fimd_swap_data(swap, &data); \
+ for (i = (64 / (N) - 1); i >= 0; i--) { \
+ w->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \
+ p.a = w->get_alpha(w, p.a); \
+ if (blend) { \
+ ifb += get_pixel_ifb(ifb, &p_old); \
+ exynos4210_fimd_blend_pixel(w, p_old, &p); \
+ } \
+ dst += put_pixel_ifb(p, dst); \
+ } \
+ width -= (64 / (N)); \
+ } while (width > 0); \
+}
+
+DEF_DRAW_LINE_PALETTE(1)
+DEF_DRAW_LINE_PALETTE(2)
+DEF_DRAW_LINE_PALETTE(4)
+DEF_DRAW_LINE_PALETTE(8)
+DEF_DRAW_LINE_NOPALETTE(8) /* 8bpp mode has palette and non-palette versions */
+DEF_DRAW_LINE_NOPALETTE(16)
+DEF_DRAW_LINE_NOPALETTE(32)
+
+/* Special draw line routine for window color map case */
+static void draw_line_mapcolor(Exynos4210fimdWindow *w, uint8_t *src,
+ uint8_t *dst, bool blend)
+{
+ rgba p, p_old;
+ uint8_t *ifb = dst;
+ int width = w->rightbot_x - w->lefttop_x + 1;
+ uint32_t map_color = w->winmap & FIMD_WINMAP_COLOR_MASK;
+
+ do {
+ pixel_888_to_rgb(map_color, &p);
+ p.a = w->get_alpha(w, p.a);
+ if (blend) {
+ ifb += get_pixel_ifb(ifb, &p_old);
+ exynos4210_fimd_blend_pixel(w, p_old, &p);
+ }
+ dst += put_pixel_ifb(p, dst);
+ } while (--width);
+}
+
+/* Write RGB to QEMU's GraphicConsole framebuffer */
+
+static int put_to_qemufb_pixel8(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel8(p.r, p.g, p.b);
+ *(uint8_t *)d = pixel;
+ return 1;
+}
+
+static int put_to_qemufb_pixel15(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel15(p.r, p.g, p.b);
+ *(uint16_t *)d = pixel;
+ return 2;
+}
+
+static int put_to_qemufb_pixel16(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel16(p.r, p.g, p.b);
+ *(uint16_t *)d = pixel;
+ return 2;
+}
+
+static int put_to_qemufb_pixel24(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+ *(uint8_t *)d++ = (pixel >> 0) & 0xFF;
+ *(uint8_t *)d++ = (pixel >> 8) & 0xFF;
+ *(uint8_t *)d++ = (pixel >> 16) & 0xFF;
+ return 3;
+}
+
+static int put_to_qemufb_pixel32(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+ *(uint32_t *)d = pixel;
+ return 4;
+}
+
+/* Routine to copy pixel from internal buffer to QEMU buffer */
+static int (*put_pixel_toqemu)(const rgba p, uint8_t *pixel);
+static inline void fimd_update_putpix_qemu(int bpp)
+{
+ switch (bpp) {
+ case 8:
+ put_pixel_toqemu = put_to_qemufb_pixel8;
+ break;
+ case 15:
+ put_pixel_toqemu = put_to_qemufb_pixel15;
+ break;
+ case 16:
+ put_pixel_toqemu = put_to_qemufb_pixel16;
+ break;
+ case 24:
+ put_pixel_toqemu = put_to_qemufb_pixel24;
+ break;
+ case 32:
+ put_pixel_toqemu = put_to_qemufb_pixel32;
+ break;
+ default:
+ hw_error("exynos4210.fimd: unsupported BPP (%d)", bpp);
+ break;
+ }
+}
+
+/* Routine to copy a line from internal frame buffer to QEMU display */
+static void fimd_copy_line_toqemu(int width, uint8_t *src, uint8_t *dst)
+{
+ rgba p;
+
+ do {
+ src += get_pixel_ifb(src, &p);
+ dst += put_pixel_toqemu(p, dst);
+ } while (--width);
+}
+
+/* Parse BPPMODE_F = WINCON1[5:2] bits */
+static void exynos4210_fimd_update_win_bppmode(Exynos4210fimdState *s, int win)
+{
+ Exynos4210fimdWindow *w = &s->window[win];
+
+ if (w->winmap & FIMD_WINMAP_EN) {
+ w->draw_line = draw_line_mapcolor;
+ return;
+ }
+
+ switch (WIN_BPP_MODE(w)) {
+ case 0:
+ w->draw_line = draw_line_palette_1;
+ w->pixel_to_rgb =
+ palette_data_format[exynos4210_fimd_palette_format(s, win)];
+ break;
+ case 1:
+ w->draw_line = draw_line_palette_2;
+ w->pixel_to_rgb =
+ palette_data_format[exynos4210_fimd_palette_format(s, win)];
+ break;
+ case 2:
+ w->draw_line = draw_line_palette_4;
+ w->pixel_to_rgb =
+ palette_data_format[exynos4210_fimd_palette_format(s, win)];
+ break;
+ case 3:
+ w->draw_line = draw_line_palette_8;
+ w->pixel_to_rgb =
+ palette_data_format[exynos4210_fimd_palette_format(s, win)];
+ break;
+ case 4:
+ w->draw_line = draw_line_8;
+ w->pixel_to_rgb = pixel_a232_to_rgb;
+ break;
+ case 5:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_565_to_rgb;
+ break;
+ case 6:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_a555_to_rgb;
+ break;
+ case 7:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_1555_to_rgb;
+ break;
+ case 8:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_666_to_rgb;
+ break;
+ case 9:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a665_to_rgb;
+ break;
+ case 10:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a666_to_rgb;
+ break;
+ case 11:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_888_to_rgb;
+ break;
+ case 12:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a887_to_rgb;
+ break;
+ case 13:
+ w->draw_line = draw_line_32;
+ if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+ FIMD_WINCON_ALPHA_SEL)) {
+ w->pixel_to_rgb = pixel_8888_to_rgb;
+ } else {
+ w->pixel_to_rgb = pixel_a888_to_rgb;
+ }
+ break;
+ case 14:
+ w->draw_line = draw_line_16;
+ if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+ FIMD_WINCON_ALPHA_SEL)) {
+ w->pixel_to_rgb = pixel_4444_to_rgb;
+ } else {
+ w->pixel_to_rgb = pixel_a444_to_rgb;
+ }
+ break;
+ case 15:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_555_to_rgb;
+ break;
+ }
+}
+
+#if EXYNOS4210_FIMD_MODE_TRACE > 0
+static const char *exynos4210_fimd_get_bppmode(int mode_code)
+{
+ switch (mode_code) {
+ case 0:
+ return "1 bpp";
+ case 1:
+ return "2 bpp";
+ case 2:
+ return "4 bpp";
+ case 3:
+ return "8 bpp (palettized)";
+ case 4:
+ return "8 bpp (non-palettized, A: 1-R:2-G:3-B:2)";
+ case 5:
+ return "16 bpp (non-palettized, R:5-G:6-B:5)";
+ case 6:
+ return "16 bpp (non-palettized, A:1-R:5-G:5-B:5)";
+ case 7:
+ return "16 bpp (non-palettized, I :1-R:5-G:5-B:5)";
+ case 8:
+ return "Unpacked 18 bpp (non-palettized, R:6-G:6-B:6)";
+ case 9:
+ return "Unpacked 18bpp (non-palettized,A:1-R:6-G:6-B:5)";
+ case 10:
+ return "Unpacked 19bpp (non-palettized,A:1-R:6-G:6-B:6)";
+ case 11:
+ return "Unpacked 24 bpp (non-palettized R:8-G:8-B:8)";
+ case 12:
+ return "Unpacked 24 bpp (non-palettized A:1-R:8-G:8-B:7)";
+ case 13:
+ return "Unpacked 25 bpp (non-palettized A:1-R:8-G:8-B:8)";
+ case 14:
+ return "Unpacked 13 bpp (non-palettized A:1-R:4-G:4-B:4)";
+ case 15:
+ return "Unpacked 15 bpp (non-palettized R:5-G:5-B:5)";
+ default:
+ return "Non-existing bpp mode";
+ }
+}
+
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s,
+ int win_num, uint32_t val)
+{
+ Exynos4210fimdWindow *w = &s->window[win_num];
+
+ if (w->winmap & FIMD_WINMAP_EN) {
+ printf("QEMU FIMD: Window %d is mapped with MAPCOLOR=0x%x\n",
+ win_num, w->winmap & 0xFFFFFF);
+ return;
+ }
+
+ if ((val != 0xFFFFFFFF) && ((w->wincon >> 2) & 0xF) == ((val >> 2) & 0xF)) {
+ return;
+ }
+ printf("QEMU FIMD: Window %d BPP mode set to %s\n", win_num,
+ exynos4210_fimd_get_bppmode((val >> 2) & 0xF));
+}
+#else
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s,
+ int win_num, uint32_t val)
+{
+
+}
+#endif
+
+static inline int fimd_get_buffer_id(Exynos4210fimdWindow *w)
+{
+ switch (w->wincon & FIMD_WINCON_BUFSTATUS) {
+ case FIMD_WINCON_BUF0_STAT:
+ return 0;
+ case FIMD_WINCON_BUF1_STAT:
+ return 1;
+ case FIMD_WINCON_BUF2_STAT:
+ return 2;
+ default:
+ DPRINT_ERROR("Non-existent buffer index\n");
+ return 0;
+ }
+}
+
+static void exynos4210_fimd_invalidate(void *opaque)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ s->invalidate = true;
+}
+
+/* Updates specified window's MemorySection based on values of WINCON,
+ * VIDOSDA, VIDOSDB, VIDWADDx and SHADOWCON registers */
+static void fimd_update_memory_section(Exynos4210fimdState *s, unsigned win)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(s);
+ Exynos4210fimdWindow *w = &s->window[win];
+ hwaddr fb_start_addr, fb_mapped_len;
+
+ if (!s->enabled || !(w->wincon & FIMD_WINCON_ENWIN) ||
+ FIMD_WINDOW_PROTECTED(s->shadowcon, win)) {
+ return;
+ }
+
+ if (w->host_fb_addr) {
+ cpu_physical_memory_unmap(w->host_fb_addr, w->fb_len, 0, 0);
+ w->host_fb_addr = NULL;
+ w->fb_len = 0;
+ }
+
+ fb_start_addr = w->buf_start[fimd_get_buffer_id(w)];
+ /* Total number of bytes of virtual screen used by current window */
+ w->fb_len = fb_mapped_len = (w->virtpage_width + w->virtpage_offsize) *
+ (w->rightbot_y - w->lefttop_y + 1);
+
+ /* TODO: add .exit and unref the region there. Not needed yet since sysbus
+ * does not support hot-unplug.
+ */
+ if (w->mem_section.mr) {
+ memory_region_set_log(w->mem_section.mr, false, DIRTY_MEMORY_VGA);
+ memory_region_unref(w->mem_section.mr);
+ }
+
+ w->mem_section = memory_region_find(sysbus_address_space(sbd),
+ fb_start_addr, w->fb_len);
+ assert(w->mem_section.mr);
+ assert(w->mem_section.offset_within_address_space == fb_start_addr);
+ DPRINT_TRACE("Window %u framebuffer changed: address=0x%08x, len=0x%x\n",
+ win, fb_start_addr, w->fb_len);
+
+ if (int128_get64(w->mem_section.size) != w->fb_len ||
+ !memory_region_is_ram(w->mem_section.mr)) {
+ DPRINT_ERROR("Failed to find window %u framebuffer region\n", win);
+ goto error_return;
+ }
+
+ w->host_fb_addr = cpu_physical_memory_map(fb_start_addr, &fb_mapped_len, 0);
+ if (!w->host_fb_addr) {
+ DPRINT_ERROR("Failed to map window %u framebuffer\n", win);
+ goto error_return;
+ }
+
+ if (fb_mapped_len != w->fb_len) {
+ DPRINT_ERROR("Window %u mapped framebuffer length is less then "
+ "expected\n", win);
+ cpu_physical_memory_unmap(w->host_fb_addr, fb_mapped_len, 0, 0);
+ goto error_return;
+ }
+ memory_region_set_log(w->mem_section.mr, true, DIRTY_MEMORY_VGA);
+ exynos4210_fimd_invalidate(s);
+ return;
+
+error_return:
+ memory_region_unref(w->mem_section.mr);
+ w->mem_section.mr = NULL;
+ w->mem_section.size = int128_zero();
+ w->host_fb_addr = NULL;
+ w->fb_len = 0;
+}
+
+static void exynos4210_fimd_enable(Exynos4210fimdState *s, bool enabled)
+{
+ if (enabled && !s->enabled) {
+ unsigned w;
+ s->enabled = true;
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ fimd_update_memory_section(s, w);
+ }
+ }
+ s->enabled = enabled;
+ DPRINT_TRACE("display controller %s\n", enabled ? "enabled" : "disabled");
+}
+
+static inline uint32_t unpack_upper_4(uint32_t x)
+{
+ return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4);
+}
+
+static inline uint32_t pack_upper_4(uint32_t x)
+{
+ return (((x & 0xF00000) >> 12) | ((x & 0xF000) >> 8) |
+ ((x & 0xF0) >> 4)) & 0xFFF;
+}
+
+static void exynos4210_fimd_update_irq(Exynos4210fimdState *s)
+{
+ if (!(s->vidintcon[0] & FIMD_VIDINT_INTEN)) {
+ qemu_irq_lower(s->irq[0]);
+ qemu_irq_lower(s->irq[1]);
+ qemu_irq_lower(s->irq[2]);
+ return;
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_INTFIFOEN) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTFIFOPEND)) {
+ qemu_irq_raise(s->irq[0]);
+ } else {
+ qemu_irq_lower(s->irq[0]);
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_INTFRMEN) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTFRMPEND)) {
+ qemu_irq_raise(s->irq[1]);
+ } else {
+ qemu_irq_lower(s->irq[1]);
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_I80IFDONE) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTI80PEND)) {
+ qemu_irq_raise(s->irq[2]);
+ } else {
+ qemu_irq_lower(s->irq[2]);
+ }
+}
+
+static void exynos4210_update_resolution(Exynos4210fimdState *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->console);
+
+ /* LCD resolution is stored in VIDEO TIME CONTROL REGISTER 2 */
+ uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) &
+ FIMD_VIDTCON2_SIZE_MASK) + 1;
+ uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+ FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+ if (s->ifb == NULL || surface_width(surface) != width ||
+ surface_height(surface) != height) {
+ DPRINT_L1("Resolution changed from %ux%u to %ux%u\n",
+ surface_width(surface), surface_height(surface), width, height);
+ qemu_console_resize(s->console, width, height);
+ s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE + 1);
+ memset(s->ifb, 0, width * height * RGBA_SIZE + 1);
+ exynos4210_fimd_invalidate(s);
+ }
+}
+
+static void exynos4210_fimd_update(void *opaque)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ DisplaySurface *surface;
+ Exynos4210fimdWindow *w;
+ int i, line;
+ hwaddr fb_line_addr, inc_size;
+ int scrn_height;
+ int first_line = -1, last_line = -1, scrn_width;
+ bool blend = false;
+ uint8_t *host_fb_addr;
+ bool is_dirty = false;
+ const int global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1;
+ const int global_height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+ FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+ if (!s || !s->console || !s->enabled ||
+ surface_bits_per_pixel(qemu_console_surface(s->console)) == 0) {
+ return;
+ }
+ exynos4210_update_resolution(s);
+ surface = qemu_console_surface(s->console);
+
+ for (i = 0; i < NUM_OF_WINDOWS; i++) {
+ w = &s->window[i];
+ if ((w->wincon & FIMD_WINCON_ENWIN) && w->host_fb_addr) {
+ scrn_height = w->rightbot_y - w->lefttop_y + 1;
+ scrn_width = w->virtpage_width;
+ /* Total width of virtual screen page in bytes */
+ inc_size = scrn_width + w->virtpage_offsize;
+ memory_region_sync_dirty_bitmap(w->mem_section.mr);
+ host_fb_addr = w->host_fb_addr;
+ fb_line_addr = w->mem_section.offset_within_region;
+
+ for (line = 0; line < scrn_height; line++) {
+ is_dirty = memory_region_get_dirty(w->mem_section.mr,
+ fb_line_addr, scrn_width, DIRTY_MEMORY_VGA);
+
+ if (s->invalidate || is_dirty) {
+ if (first_line == -1) {
+ first_line = line;
+ }
+ last_line = line;
+ w->draw_line(w, host_fb_addr, s->ifb +
+ w->lefttop_x * RGBA_SIZE + (w->lefttop_y + line) *
+ global_width * RGBA_SIZE, blend);
+ }
+ host_fb_addr += inc_size;
+ fb_line_addr += inc_size;
+ is_dirty = false;
+ }
+ memory_region_reset_dirty(w->mem_section.mr,
+ w->mem_section.offset_within_region,
+ w->fb_len, DIRTY_MEMORY_VGA);
+ blend = true;
+ }
+ }
+
+ /* Copy resulting image to QEMU_CONSOLE. */
+ if (first_line >= 0) {
+ uint8_t *d;
+ int bpp;
+
+ bpp = surface_bits_per_pixel(surface);
+ fimd_update_putpix_qemu(bpp);
+ bpp = (bpp + 1) >> 3;
+ d = surface_data(surface);
+ for (line = first_line; line <= last_line; line++) {
+ fimd_copy_line_toqemu(global_width, s->ifb + global_width * line *
+ RGBA_SIZE, d + global_width * line * bpp);
+ }
+ dpy_gfx_update(s->console, 0, 0, global_width, global_height);
+ }
+ s->invalidate = false;
+ s->vidintcon[1] |= FIMD_VIDINT_INTFRMPEND;
+ if ((s->vidcon[0] & FIMD_VIDCON0_ENVID_F) == 0) {
+ exynos4210_fimd_enable(s, false);
+ }
+ exynos4210_fimd_update_irq(s);
+}
+
+static void exynos4210_fimd_reset(DeviceState *d)
+{
+ Exynos4210fimdState *s = EXYNOS4210_FIMD(d);
+ unsigned w;
+
+ DPRINT_TRACE("Display controller reset\n");
+ /* Set all display controller registers to 0 */
+ memset(&s->vidcon, 0, (uint8_t *)&s->window - (uint8_t *)&s->vidcon);
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ memset(&s->window[w], 0, sizeof(Exynos4210fimdWindow));
+ s->window[w].blendeq = 0xC2;
+ exynos4210_fimd_update_win_bppmode(s, w);
+ exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF);
+ fimd_update_get_alpha(s, w);
+ }
+
+ if (s->ifb != NULL) {
+ g_free(s->ifb);
+ }
+ s->ifb = NULL;
+
+ exynos4210_fimd_invalidate(s);
+ exynos4210_fimd_enable(s, false);
+ /* Some registers have non-zero initial values */
+ s->winchmap = 0x7D517D51;
+ s->colorgaincon = 0x10040100;
+ s->huecoef_cr[0] = s->huecoef_cr[3] = 0x01000100;
+ s->huecoef_cb[0] = s->huecoef_cb[3] = 0x01000100;
+ s->hueoffset = 0x01800080;
+}
+
+static void exynos4210_fimd_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ unsigned w, i;
+ uint32_t old_value;
+
+ DPRINT_L2("write offset 0x%08x, value=%llu(0x%08llx)\n", offset,
+ (long long unsigned int)val, (long long unsigned int)val);
+
+ switch (offset) {
+ case FIMD_VIDCON0:
+ if ((val & FIMD_VIDCON0_ENVID_MASK) == FIMD_VIDCON0_ENVID_MASK) {
+ exynos4210_fimd_enable(s, true);
+ } else {
+ if ((val & FIMD_VIDCON0_ENVID) == 0) {
+ exynos4210_fimd_enable(s, false);
+ }
+ }
+ s->vidcon[0] = val;
+ break;
+ case FIMD_VIDCON1:
+ /* Leave read-only bits as is */
+ val = (val & (~FIMD_VIDCON1_ROMASK)) |
+ (s->vidcon[1] & FIMD_VIDCON1_ROMASK);
+ s->vidcon[1] = val;
+ break;
+ case FIMD_VIDCON2 ... FIMD_VIDCON3:
+ s->vidcon[(offset) >> 2] = val;
+ break;
+ case FIMD_VIDTCON_START ... FIMD_VIDTCON_END:
+ s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2] = val;
+ break;
+ case FIMD_WINCON_START ... FIMD_WINCON_END:
+ w = (offset - FIMD_WINCON_START) >> 2;
+ /* Window's current buffer ID */
+ i = fimd_get_buffer_id(&s->window[w]);
+ old_value = s->window[w].wincon;
+ val = (val & ~FIMD_WINCON_ROMASK) |
+ (s->window[w].wincon & FIMD_WINCON_ROMASK);
+ if (w == 0) {
+ /* Window 0 wincon ALPHA_MUL bit must always be 0 */
+ val &= ~FIMD_WINCON_ALPHA_MUL;
+ }
+ exynos4210_fimd_trace_bppmode(s, w, val);
+ switch (val & FIMD_WINCON_BUFSELECT) {
+ case FIMD_WINCON_BUF0_SEL:
+ val &= ~FIMD_WINCON_BUFSTATUS;
+ break;
+ case FIMD_WINCON_BUF1_SEL:
+ val = (val & ~FIMD_WINCON_BUFSTAT_H) | FIMD_WINCON_BUFSTAT_L;
+ break;
+ case FIMD_WINCON_BUF2_SEL:
+ if (val & FIMD_WINCON_BUFMODE) {
+ val = (val & ~FIMD_WINCON_BUFSTAT_L) | FIMD_WINCON_BUFSTAT_H;
+ }
+ break;
+ default:
+ break;
+ }
+ s->window[w].wincon = val;
+ exynos4210_fimd_update_win_bppmode(s, w);
+ fimd_update_get_alpha(s, w);
+ if ((i != fimd_get_buffer_id(&s->window[w])) ||
+ (!(old_value & FIMD_WINCON_ENWIN) && (s->window[w].wincon &
+ FIMD_WINCON_ENWIN))) {
+ fimd_update_memory_section(s, w);
+ }
+ break;
+ case FIMD_SHADOWCON:
+ old_value = s->shadowcon;
+ s->shadowcon = val;
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ if (FIMD_WINDOW_PROTECTED(old_value, w) &&
+ !FIMD_WINDOW_PROTECTED(s->shadowcon, w)) {
+ fimd_update_memory_section(s, w);
+ }
+ }
+ break;
+ case FIMD_WINCHMAP:
+ s->winchmap = val;
+ break;
+ case FIMD_VIDOSD_START ... FIMD_VIDOSD_END:
+ w = (offset - FIMD_VIDOSD_START) >> 4;
+ i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+ switch (i) {
+ case 0:
+ old_value = s->window[w].lefttop_y;
+ s->window[w].lefttop_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ s->window[w].lefttop_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ if (s->window[w].lefttop_y != old_value) {
+ fimd_update_memory_section(s, w);
+ }
+ break;
+ case 1:
+ old_value = s->window[w].rightbot_y;
+ s->window[w].rightbot_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ s->window[w].rightbot_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ if (s->window[w].rightbot_y != old_value) {
+ fimd_update_memory_section(s, w);
+ }
+ break;
+ case 2:
+ if (w == 0) {
+ s->window[w].osdsize = val;
+ } else {
+ s->window[w].alpha_val[0] =
+ unpack_upper_4((val & FIMD_VIDOSD_ALPHA_AEN0) >>
+ FIMD_VIDOSD_AEN0_SHIFT) |
+ (s->window[w].alpha_val[0] & FIMD_VIDALPHA_ALPHA_LOWER);
+ s->window[w].alpha_val[1] =
+ unpack_upper_4(val & FIMD_VIDOSD_ALPHA_AEN1) |
+ (s->window[w].alpha_val[1] & FIMD_VIDALPHA_ALPHA_LOWER);
+ }
+ break;
+ case 3:
+ if (w != 1 && w != 2) {
+ DPRINT_ERROR("Bad write offset 0x%08x\n", offset);
+ return;
+ }
+ s->window[w].osdsize = val;
+ break;
+ }
+ break;
+ case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END:
+ w = (offset - FIMD_VIDWADD0_START) >> 3;
+ i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+ if (i == fimd_get_buffer_id(&s->window[w]) &&
+ s->window[w].buf_start[i] != val) {
+ s->window[w].buf_start[i] = val;
+ fimd_update_memory_section(s, w);
+ break;
+ }
+ s->window[w].buf_start[i] = val;
+ break;
+ case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END:
+ w = (offset - FIMD_VIDWADD1_START) >> 3;
+ i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+ s->window[w].buf_end[i] = val;
+ break;
+ case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END:
+ w = (offset - FIMD_VIDWADD2_START) >> 2;
+ if (((val & FIMD_VIDWADD2_PAGEWIDTH) != s->window[w].virtpage_width) ||
+ (((val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE) !=
+ s->window[w].virtpage_offsize)) {
+ s->window[w].virtpage_width = val & FIMD_VIDWADD2_PAGEWIDTH;
+ s->window[w].virtpage_offsize =
+ (val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE;
+ fimd_update_memory_section(s, w);
+ }
+ break;
+ case FIMD_VIDINTCON0:
+ s->vidintcon[0] = val;
+ break;
+ case FIMD_VIDINTCON1:
+ s->vidintcon[1] &= ~(val & 7);
+ exynos4210_fimd_update_irq(s);
+ break;
+ case FIMD_WKEYCON_START ... FIMD_WKEYCON_END:
+ w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+ i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+ s->window[w].keycon[i] = val;
+ break;
+ case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END:
+ w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+ s->window[w].keyalpha = val;
+ break;
+ case FIMD_DITHMODE:
+ s->dithmode = val;
+ break;
+ case FIMD_WINMAP_START ... FIMD_WINMAP_END:
+ w = (offset - FIMD_WINMAP_START) >> 2;
+ old_value = s->window[w].winmap;
+ s->window[w].winmap = val;
+ if ((val & FIMD_WINMAP_EN) ^ (old_value & FIMD_WINMAP_EN)) {
+ exynos4210_fimd_invalidate(s);
+ exynos4210_fimd_update_win_bppmode(s, w);
+ exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF);
+ exynos4210_fimd_update(s);
+ }
+ break;
+ case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW:
+ i = (offset - FIMD_WPALCON_HIGH) >> 2;
+ s->wpalcon[i] = val;
+ if (s->wpalcon[1] & FIMD_WPALCON_UPDATEEN) {
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ exynos4210_fimd_update_win_bppmode(s, w);
+ fimd_update_get_alpha(s, w);
+ }
+ }
+ break;
+ case FIMD_TRIGCON:
+ val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK);
+ s->trigcon = val;
+ break;
+ case FIMD_I80IFCON_START ... FIMD_I80IFCON_END:
+ s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2] = val;
+ break;
+ case FIMD_COLORGAINCON:
+ s->colorgaincon = val;
+ break;
+ case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1:
+ s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2] = val;
+ break;
+ case FIMD_SIFCCON0 ... FIMD_SIFCCON2:
+ i = (offset - FIMD_SIFCCON0) >> 2;
+ if (i != 2) {
+ s->sifccon[i] = val;
+ }
+ break;
+ case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END:
+ i = (offset - FIMD_HUECOEFCR_START) >> 2;
+ s->huecoef_cr[i] = val;
+ break;
+ case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END:
+ i = (offset - FIMD_HUECOEFCB_START) >> 2;
+ s->huecoef_cb[i] = val;
+ break;
+ case FIMD_HUEOFFSET:
+ s->hueoffset = val;
+ break;
+ case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END:
+ w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+ i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+ if (w == 0) {
+ s->window[w].alpha_val[i] = val;
+ } else {
+ s->window[w].alpha_val[i] = (val & FIMD_VIDALPHA_ALPHA_LOWER) |
+ (s->window[w].alpha_val[i] & FIMD_VIDALPHA_ALPHA_UPPER);
+ }
+ break;
+ case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END:
+ s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq = val;
+ break;
+ case FIMD_BLENDCON:
+ old_value = s->blendcon;
+ s->blendcon = val;
+ if ((s->blendcon & FIMD_ALPHA_8BIT) != (old_value & FIMD_ALPHA_8BIT)) {
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ fimd_update_get_alpha(s, w);
+ }
+ }
+ break;
+ case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END:
+ s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon = val;
+ break;
+ case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END:
+ s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2] = val;
+ break;
+ case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2:
+ if (offset & 0x0004) {
+ DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+ break;
+ }
+ w = (offset - FIMD_VIDW0ADD0_B2) >> 3;
+ if (fimd_get_buffer_id(&s->window[w]) == 2 &&
+ s->window[w].buf_start[2] != val) {
+ s->window[w].buf_start[2] = val;
+ fimd_update_memory_section(s, w);
+ break;
+ }
+ s->window[w].buf_start[2] = val;
+ break;
+ case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END:
+ if (offset & 0x0004) {
+ DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+ break;
+ }
+ s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start = val;
+ break;
+ case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END:
+ if (offset & 0x0004) {
+ DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+ break;
+ }
+ s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end = val;
+ break;
+ case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END:
+ s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size = val;
+ break;
+ case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END:
+ w = (offset - FIMD_PAL_MEM_START) >> 10;
+ i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+ s->window[w].palette[i] = val;
+ break;
+ case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END:
+ /* Palette memory aliases for windows 0 and 1 */
+ w = (offset - FIMD_PALMEM_AL_START) >> 10;
+ i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+ s->window[w].palette[i] = val;
+ break;
+ default:
+ DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+ break;
+ }
+}
+
+static uint64_t exynos4210_fimd_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ int w, i;
+ uint32_t ret = 0;
+
+ DPRINT_L2("read offset 0x%08x\n", offset);
+
+ switch (offset) {
+ case FIMD_VIDCON0 ... FIMD_VIDCON3:
+ return s->vidcon[(offset - FIMD_VIDCON0) >> 2];
+ case FIMD_VIDTCON_START ... FIMD_VIDTCON_END:
+ return s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2];
+ case FIMD_WINCON_START ... FIMD_WINCON_END:
+ return s->window[(offset - FIMD_WINCON_START) >> 2].wincon;
+ case FIMD_SHADOWCON:
+ return s->shadowcon;
+ case FIMD_WINCHMAP:
+ return s->winchmap;
+ case FIMD_VIDOSD_START ... FIMD_VIDOSD_END:
+ w = (offset - FIMD_VIDOSD_START) >> 4;
+ i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+ switch (i) {
+ case 0:
+ ret = ((s->window[w].lefttop_x & FIMD_VIDOSD_COORD_MASK) <<
+ FIMD_VIDOSD_HOR_SHIFT) |
+ (s->window[w].lefttop_y & FIMD_VIDOSD_COORD_MASK);
+ break;
+ case 1:
+ ret = ((s->window[w].rightbot_x & FIMD_VIDOSD_COORD_MASK) <<
+ FIMD_VIDOSD_HOR_SHIFT) |
+ (s->window[w].rightbot_y & FIMD_VIDOSD_COORD_MASK);
+ break;
+ case 2:
+ if (w == 0) {
+ ret = s->window[w].osdsize;
+ } else {
+ ret = (pack_upper_4(s->window[w].alpha_val[0]) <<
+ FIMD_VIDOSD_AEN0_SHIFT) |
+ pack_upper_4(s->window[w].alpha_val[1]);
+ }
+ break;
+ case 3:
+ if (w != 1 && w != 2) {
+ DPRINT_ERROR("bad read offset 0x%08x\n", offset);
+ return 0xBAADBAAD;
+ }
+ ret = s->window[w].osdsize;
+ break;
+ }
+ return ret;
+ case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END:
+ w = (offset - FIMD_VIDWADD0_START) >> 3;
+ i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+ return s->window[w].buf_start[i];
+ case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END:
+ w = (offset - FIMD_VIDWADD1_START) >> 3;
+ i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+ return s->window[w].buf_end[i];
+ case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END:
+ w = (offset - FIMD_VIDWADD2_START) >> 2;
+ return s->window[w].virtpage_width | (s->window[w].virtpage_offsize <<
+ FIMD_VIDWADD2_OFFSIZE_SHIFT);
+ case FIMD_VIDINTCON0 ... FIMD_VIDINTCON1:
+ return s->vidintcon[(offset - FIMD_VIDINTCON0) >> 2];
+ case FIMD_WKEYCON_START ... FIMD_WKEYCON_END:
+ w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+ i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+ return s->window[w].keycon[i];
+ case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END:
+ w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+ return s->window[w].keyalpha;
+ case FIMD_DITHMODE:
+ return s->dithmode;
+ case FIMD_WINMAP_START ... FIMD_WINMAP_END:
+ return s->window[(offset - FIMD_WINMAP_START) >> 2].winmap;
+ case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW:
+ return s->wpalcon[(offset - FIMD_WPALCON_HIGH) >> 2];
+ case FIMD_TRIGCON:
+ return s->trigcon;
+ case FIMD_I80IFCON_START ... FIMD_I80IFCON_END:
+ return s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2];
+ case FIMD_COLORGAINCON:
+ return s->colorgaincon;
+ case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1:
+ return s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2];
+ case FIMD_SIFCCON0 ... FIMD_SIFCCON2:
+ i = (offset - FIMD_SIFCCON0) >> 2;
+ return s->sifccon[i];
+ case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END:
+ i = (offset - FIMD_HUECOEFCR_START) >> 2;
+ return s->huecoef_cr[i];
+ case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END:
+ i = (offset - FIMD_HUECOEFCB_START) >> 2;
+ return s->huecoef_cb[i];
+ case FIMD_HUEOFFSET:
+ return s->hueoffset;
+ case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END:
+ w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+ i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+ return s->window[w].alpha_val[i] &
+ (w == 0 ? 0xFFFFFF : FIMD_VIDALPHA_ALPHA_LOWER);
+ case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END:
+ return s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq;
+ case FIMD_BLENDCON:
+ return s->blendcon;
+ case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END:
+ return s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon;
+ case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END:
+ return s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2];
+ case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2:
+ if (offset & 0x0004) {
+ break;
+ }
+ return s->window[(offset - FIMD_VIDW0ADD0_B2) >> 3].buf_start[2];
+ case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END:
+ if (offset & 0x0004) {
+ break;
+ }
+ return s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start;
+ case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END:
+ if (offset & 0x0004) {
+ break;
+ }
+ return s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end;
+ case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END:
+ return s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size;
+ case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END:
+ w = (offset - FIMD_PAL_MEM_START) >> 10;
+ i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+ return s->window[w].palette[i];
+ case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END:
+ /* Palette aliases for win 0,1 */
+ w = (offset - FIMD_PALMEM_AL_START) >> 10;
+ i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+ return s->window[w].palette[i];
+ }
+
+ DPRINT_ERROR("bad read offset 0x%08x\n", offset);
+ return 0xBAADBAAD;
+}
+
+static const MemoryRegionOps exynos4210_fimd_mmio_ops = {
+ .read = exynos4210_fimd_read,
+ .write = exynos4210_fimd_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_fimd_load(void *opaque, int version_id)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ int w;
+
+ if (version_id != 1) {
+ return -EINVAL;
+ }
+
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ exynos4210_fimd_update_win_bppmode(s, w);
+ fimd_update_get_alpha(s, w);
+ fimd_update_memory_section(s, w);
+ }
+
+ /* Redraw the whole screen */
+ exynos4210_update_resolution(s);
+ exynos4210_fimd_invalidate(s);
+ exynos4210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) ==
+ FIMD_VIDCON0_ENVID_MASK);
+ return 0;
+}
+
+static const VMStateDescription exynos4210_fimd_window_vmstate = {
+ .name = "exynos4210.fimd_window",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(wincon, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(buf_start, Exynos4210fimdWindow, 3),
+ VMSTATE_UINT32_ARRAY(buf_end, Exynos4210fimdWindow, 3),
+ VMSTATE_UINT32_ARRAY(keycon, Exynos4210fimdWindow, 2),
+ VMSTATE_UINT32(keyalpha, Exynos4210fimdWindow),
+ VMSTATE_UINT32(winmap, Exynos4210fimdWindow),
+ VMSTATE_UINT32(blendeq, Exynos4210fimdWindow),
+ VMSTATE_UINT32(rtqoscon, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(palette, Exynos4210fimdWindow, 256),
+ VMSTATE_UINT32(shadow_buf_start, Exynos4210fimdWindow),
+ VMSTATE_UINT32(shadow_buf_end, Exynos4210fimdWindow),
+ VMSTATE_UINT32(shadow_buf_size, Exynos4210fimdWindow),
+ VMSTATE_UINT16(lefttop_x, Exynos4210fimdWindow),
+ VMSTATE_UINT16(lefttop_y, Exynos4210fimdWindow),
+ VMSTATE_UINT16(rightbot_x, Exynos4210fimdWindow),
+ VMSTATE_UINT16(rightbot_y, Exynos4210fimdWindow),
+ VMSTATE_UINT32(osdsize, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(alpha_val, Exynos4210fimdWindow, 2),
+ VMSTATE_UINT16(virtpage_width, Exynos4210fimdWindow),
+ VMSTATE_UINT16(virtpage_offsize, Exynos4210fimdWindow),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription exynos4210_fimd_vmstate = {
+ .name = "exynos4210.fimd",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = exynos4210_fimd_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(vidcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32_ARRAY(vidtcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(shadowcon, Exynos4210fimdState),
+ VMSTATE_UINT32(winchmap, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(vidintcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32(dithmode, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(wpalcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32(trigcon, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(i80ifcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(colorgaincon, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(ldi_cmdcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32_ARRAY(sifccon, Exynos4210fimdState, 3),
+ VMSTATE_UINT32_ARRAY(huecoef_cr, Exynos4210fimdState, 4),
+ VMSTATE_UINT32_ARRAY(huecoef_cb, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(hueoffset, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(i80ifcmd, Exynos4210fimdState, 12),
+ VMSTATE_UINT32(blendcon, Exynos4210fimdState),
+ VMSTATE_STRUCT_ARRAY(window, Exynos4210fimdState, 5, 1,
+ exynos4210_fimd_window_vmstate, Exynos4210fimdWindow),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps exynos4210_fimd_ops = {
+ .invalidate = exynos4210_fimd_invalidate,
+ .gfx_update = exynos4210_fimd_update,
+};
+
+static int exynos4210_fimd_init(SysBusDevice *dev)
+{
+ Exynos4210fimdState *s = EXYNOS4210_FIMD(dev);
+
+ s->ifb = NULL;
+
+ sysbus_init_irq(dev, &s->irq[0]);
+ sysbus_init_irq(dev, &s->irq[1]);
+ sysbus_init_irq(dev, &s->irq[2]);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_fimd_mmio_ops, s,
+ "exynos4210.fimd", FIMD_REGS_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ s->console = graphic_console_init(DEVICE(dev), 0, &exynos4210_fimd_ops, s);
+
+ return 0;
+}
+
+static void exynos4210_fimd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ dc->vmsd = &exynos4210_fimd_vmstate;
+ dc->reset = exynos4210_fimd_reset;
+ k->init = exynos4210_fimd_init;
+}
+
+static const TypeInfo exynos4210_fimd_info = {
+ .name = TYPE_EXYNOS4210_FIMD,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210fimdState),
+ .class_init = exynos4210_fimd_class_init,
+};
+
+static void exynos4210_fimd_register_types(void)
+{
+ type_register_static(&exynos4210_fimd_info);
+}
+
+type_init(exynos4210_fimd_register_types)
diff --git a/hw/display/framebuffer.c b/hw/display/framebuffer.c
new file mode 100644
index 00000000..7f075ce7
--- /dev/null
+++ b/hw/display/framebuffer.c
@@ -0,0 +1,124 @@
+/*
+ * Framebuffer device helper routines
+ *
+ * Copyright (c) 2009 CodeSourcery
+ * Written by Paul Brook <paul@codesourcery.com>
+ *
+ * This code is licensed under the GNU GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+/* TODO:
+ - Do something similar for framebuffers with local ram
+ - Handle rotation here instead of hacking dest_pitch
+ - Use common pixel conversion routines instead of per-device drawfn
+ - Remove all DisplayState knowledge from devices.
+ */
+
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "framebuffer.h"
+
+void framebuffer_update_memory_section(
+ MemoryRegionSection *mem_section,
+ MemoryRegion *root,
+ hwaddr base,
+ unsigned rows,
+ unsigned src_width)
+{
+ hwaddr src_len = (hwaddr)rows * src_width;
+
+ if (mem_section->mr) {
+ memory_region_set_log(mem_section->mr, false, DIRTY_MEMORY_VGA);
+ memory_region_unref(mem_section->mr);
+ mem_section->mr = NULL;
+ }
+
+ *mem_section = memory_region_find(root, base, src_len);
+ if (!mem_section->mr) {
+ return;
+ }
+
+ if (int128_get64(mem_section->size) < src_len ||
+ !memory_region_is_ram(mem_section->mr)) {
+ memory_region_unref(mem_section->mr);
+ mem_section->mr = NULL;
+ return;
+ }
+
+ memory_region_set_log(mem_section->mr, true, DIRTY_MEMORY_VGA);
+}
+
+/* Render an image from a shared memory framebuffer. */
+void framebuffer_update_display(
+ DisplaySurface *ds,
+ MemoryRegionSection *mem_section,
+ int cols, /* Width in pixels. */
+ int rows, /* Height in pixels. */
+ int src_width, /* Length of source line, in bytes. */
+ int dest_row_pitch, /* Bytes between adjacent horizontal output pixels. */
+ int dest_col_pitch, /* Bytes between adjacent vertical output pixels. */
+ int invalidate, /* nonzero to redraw the whole image. */
+ drawfn fn,
+ void *opaque,
+ int *first_row, /* Input and output. */
+ int *last_row /* Output only */)
+{
+ hwaddr src_len;
+ uint8_t *dest;
+ uint8_t *src;
+ int first, last = 0;
+ int dirty;
+ int i;
+ ram_addr_t addr;
+ MemoryRegion *mem;
+
+ i = *first_row;
+ *first_row = -1;
+ src_len = src_width * rows;
+
+ mem = mem_section->mr;
+ if (!mem) {
+ return;
+ }
+ memory_region_sync_dirty_bitmap(mem);
+
+ addr = mem_section->offset_within_region;
+ src = memory_region_get_ram_ptr(mem) + addr;
+
+ dest = surface_data(ds);
+ if (dest_col_pitch < 0) {
+ dest -= dest_col_pitch * (cols - 1);
+ }
+ if (dest_row_pitch < 0) {
+ dest -= dest_row_pitch * (rows - 1);
+ }
+ first = -1;
+
+ addr += i * src_width;
+ src += i * src_width;
+ dest += i * dest_row_pitch;
+
+ for (; i < rows; i++) {
+ dirty = memory_region_get_dirty(mem, addr, src_width,
+ DIRTY_MEMORY_VGA);
+ if (dirty || invalidate) {
+ fn(opaque, dest, src, cols, dest_col_pitch);
+ if (first == -1)
+ first = i;
+ last = i;
+ }
+ addr += src_width;
+ src += src_width;
+ dest += dest_row_pitch;
+ }
+ if (first < 0) {
+ return;
+ }
+ memory_region_reset_dirty(mem, mem_section->offset_within_region, src_len,
+ DIRTY_MEMORY_VGA);
+ *first_row = first;
+ *last_row = last;
+}
diff --git a/hw/display/framebuffer.h b/hw/display/framebuffer.h
new file mode 100644
index 00000000..38fa0dce
--- /dev/null
+++ b/hw/display/framebuffer.h
@@ -0,0 +1,65 @@
+#ifndef QEMU_FRAMEBUFFER_H
+#define QEMU_FRAMEBUFFER_H
+
+#include "exec/memory.h"
+
+/* Framebuffer device helper routines. */
+
+typedef void (*drawfn)(void *, uint8_t *, const uint8_t *, int, int);
+
+/* framebuffer_update_memory_section: Update framebuffer
+ * #MemoryRegionSection, for example if the framebuffer is switched to
+ * a different memory area.
+ *
+ * @mem_section: Output #MemoryRegionSection, to be passed to
+ * framebuffer_update_display().
+ * @root: #MemoryRegion within which the framebuffer lies
+ * @base: Base address of the framebuffer within @root.
+ * @rows: Height of the screen.
+ * @src_width: Number of bytes in framebuffer memory between two rows.
+ */
+void framebuffer_update_memory_section(
+ MemoryRegionSection *mem_section,
+ MemoryRegion *root,
+ hwaddr base,
+ unsigned rows,
+ unsigned src_width);
+
+/* framebuffer_update_display: Draw the framebuffer on a surface.
+ *
+ * @ds: #DisplaySurface to draw to.
+ * @mem_section: #MemoryRegionSection provided by
+ * framebuffer_update_memory_section().
+ * @cols: Width the screen.
+ * @rows: Height of the screen.
+ * @src_width: Number of bytes in framebuffer memory between two rows.
+ * @dest_row_pitch: Number of bytes in the surface data between two rows.
+ * Negative if the framebuffer is stored in the opposite order (e.g.
+ * bottom-to-top) compared to the framebuffer.
+ * @dest_col_pitch: Number of bytes in the surface data between two pixels.
+ * Negative if the framebuffer is stored in the opposite order (e.g.
+ * right-to-left) compared to the framebuffer.
+ * @invalidate: True if the function should redraw the whole screen
+ * without checking the DIRTY_MEMORY_VGA dirty bitmap.
+ * @fn: Drawing function to be called for each row that has to be drawn.
+ * @opaque: Opaque pointer passed to @fn.
+ * @first_row: Pointer to an integer, receives the number of the first row
+ * that was drawn (either the first dirty row, or 0 if @invalidate is true).
+ * @last_row: Pointer to an integer, receives the number of the last row that
+ * was drawn (either the last dirty row, or @rows-1 if @invalidate is true).
+ */
+void framebuffer_update_display(
+ DisplaySurface *ds,
+ MemoryRegionSection *mem_section,
+ int cols,
+ int rows,
+ int src_width,
+ int dest_row_pitch,
+ int dest_col_pitch,
+ int invalidate,
+ drawfn fn,
+ void *opaque,
+ int *first_row,
+ int *last_row);
+
+#endif
diff --git a/hw/display/g364fb.c b/hw/display/g364fb.c
new file mode 100644
index 00000000..7f83a007
--- /dev/null
+++ b/hw/display/g364fb.c
@@ -0,0 +1,558 @@
+/*
+ * QEMU G364 framebuffer Emulator.
+ *
+ * Copyright (c) 2007-2011 Herve Poussineau
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "qemu/error-report.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+
+typedef struct G364State {
+ /* hardware */
+ uint8_t *vram;
+ uint32_t vram_size;
+ qemu_irq irq;
+ MemoryRegion mem_vram;
+ MemoryRegion mem_ctrl;
+ /* registers */
+ uint8_t color_palette[256][3];
+ uint8_t cursor_palette[3][3];
+ uint16_t cursor[512];
+ uint32_t cursor_position;
+ uint32_t ctla;
+ uint32_t top_of_screen;
+ uint32_t width, height; /* in pixels */
+ /* display refresh support */
+ QemuConsole *con;
+ int depth;
+ int blanked;
+} G364State;
+
+#define REG_BOOT 0x000000
+#define REG_DISPLAY 0x000118
+#define REG_VDISPLAY 0x000150
+#define REG_CTLA 0x000300
+#define REG_TOP 0x000400
+#define REG_CURS_PAL 0x000508
+#define REG_CURS_POS 0x000638
+#define REG_CLR_PAL 0x000800
+#define REG_CURS_PAT 0x001000
+#define REG_RESET 0x100000
+
+#define CTLA_FORCE_BLANK 0x00000400
+#define CTLA_NO_CURSOR 0x00800000
+
+#define G364_PAGE_SIZE 4096
+
+static inline int check_dirty(G364State *s, ram_addr_t page)
+{
+ return memory_region_get_dirty(&s->mem_vram, page, G364_PAGE_SIZE,
+ DIRTY_MEMORY_VGA);
+}
+
+static inline void reset_dirty(G364State *s,
+ ram_addr_t page_min, ram_addr_t page_max)
+{
+ memory_region_reset_dirty(&s->mem_vram,
+ page_min,
+ page_max + G364_PAGE_SIZE - page_min - 1,
+ DIRTY_MEMORY_VGA);
+}
+
+static void g364fb_draw_graphic8(G364State *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, w;
+ uint8_t *vram;
+ uint8_t *data_display, *dd;
+ ram_addr_t page, page_min, page_max;
+ int x, y;
+ int xmin, xmax;
+ int ymin, ymax;
+ int xcursor, ycursor;
+ unsigned int (*rgb_to_pixel)(unsigned int r, unsigned int g, unsigned int b);
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ rgb_to_pixel = rgb_to_pixel8;
+ w = 1;
+ break;
+ case 15:
+ rgb_to_pixel = rgb_to_pixel15;
+ w = 2;
+ break;
+ case 16:
+ rgb_to_pixel = rgb_to_pixel16;
+ w = 2;
+ break;
+ case 32:
+ rgb_to_pixel = rgb_to_pixel32;
+ w = 4;
+ break;
+ default:
+ hw_error("g364: unknown host depth %d",
+ surface_bits_per_pixel(surface));
+ return;
+ }
+
+ page = 0;
+ page_min = (ram_addr_t)-1;
+ page_max = 0;
+
+ x = y = 0;
+ xmin = s->width;
+ xmax = 0;
+ ymin = s->height;
+ ymax = 0;
+
+ if (!(s->ctla & CTLA_NO_CURSOR)) {
+ xcursor = s->cursor_position >> 12;
+ ycursor = s->cursor_position & 0xfff;
+ } else {
+ xcursor = ycursor = -65;
+ }
+
+ vram = s->vram + s->top_of_screen;
+ /* XXX: out of range in vram? */
+ data_display = dd = surface_data(surface);
+ while (y < s->height) {
+ if (check_dirty(s, page)) {
+ if (y < ymin)
+ ymin = ymax = y;
+ if (page_min == (ram_addr_t)-1)
+ page_min = page;
+ page_max = page;
+ if (x < xmin)
+ xmin = x;
+ for (i = 0; i < G364_PAGE_SIZE; i++) {
+ uint8_t index;
+ unsigned int color;
+ if (unlikely((y >= ycursor && y < ycursor + 64) &&
+ (x >= xcursor && x < xcursor + 64))) {
+ /* pointer area */
+ int xdiff = x - xcursor;
+ uint16_t curs = s->cursor[(y - ycursor) * 8 + xdiff / 8];
+ int op = (curs >> ((xdiff & 7) * 2)) & 3;
+ if (likely(op == 0)) {
+ /* transparent */
+ index = *vram;
+ color = (*rgb_to_pixel)(
+ s->color_palette[index][0],
+ s->color_palette[index][1],
+ s->color_palette[index][2]);
+ } else {
+ /* get cursor color */
+ index = op - 1;
+ color = (*rgb_to_pixel)(
+ s->cursor_palette[index][0],
+ s->cursor_palette[index][1],
+ s->cursor_palette[index][2]);
+ }
+ } else {
+ /* normal area */
+ index = *vram;
+ color = (*rgb_to_pixel)(
+ s->color_palette[index][0],
+ s->color_palette[index][1],
+ s->color_palette[index][2]);
+ }
+ memcpy(dd, &color, w);
+ dd += w;
+ x++;
+ vram++;
+ if (x == s->width) {
+ xmax = s->width - 1;
+ y++;
+ if (y == s->height) {
+ ymax = s->height - 1;
+ goto done;
+ }
+ data_display = dd = data_display + surface_stride(surface);
+ xmin = 0;
+ x = 0;
+ }
+ }
+ if (x > xmax)
+ xmax = x;
+ if (y > ymax)
+ ymax = y;
+ } else {
+ int dy;
+ if (page_min != (ram_addr_t)-1) {
+ reset_dirty(s, page_min, page_max);
+ page_min = (ram_addr_t)-1;
+ page_max = 0;
+ dpy_gfx_update(s->con, xmin, ymin,
+ xmax - xmin + 1, ymax - ymin + 1);
+ xmin = s->width;
+ xmax = 0;
+ ymin = s->height;
+ ymax = 0;
+ }
+ x += G364_PAGE_SIZE;
+ dy = x / s->width;
+ x = x % s->width;
+ y += dy;
+ vram += G364_PAGE_SIZE;
+ data_display += dy * surface_stride(surface);
+ dd = data_display + x * w;
+ }
+ page += G364_PAGE_SIZE;
+ }
+
+done:
+ if (page_min != (ram_addr_t)-1) {
+ dpy_gfx_update(s->con, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1);
+ reset_dirty(s, page_min, page_max);
+ }
+}
+
+static void g364fb_draw_blank(G364State *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, w;
+ uint8_t *d;
+
+ if (s->blanked) {
+ /* Screen is already blank. No need to redraw it */
+ return;
+ }
+
+ w = s->width * surface_bytes_per_pixel(surface);
+ d = surface_data(surface);
+ for (i = 0; i < s->height; i++) {
+ memset(d, 0, w);
+ d += surface_stride(surface);
+ }
+
+ dpy_gfx_update(s->con, 0, 0, s->width, s->height);
+ s->blanked = 1;
+}
+
+static void g364fb_update_display(void *opaque)
+{
+ G364State *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+
+ qemu_flush_coalesced_mmio_buffer();
+
+ if (s->width == 0 || s->height == 0)
+ return;
+
+ if (s->width != surface_width(surface) ||
+ s->height != surface_height(surface)) {
+ qemu_console_resize(s->con, s->width, s->height);
+ }
+
+ memory_region_sync_dirty_bitmap(&s->mem_vram);
+ if (s->ctla & CTLA_FORCE_BLANK) {
+ g364fb_draw_blank(s);
+ } else if (s->depth == 8) {
+ g364fb_draw_graphic8(s);
+ } else {
+ error_report("g364: unknown guest depth %d", s->depth);
+ }
+
+ qemu_irq_raise(s->irq);
+}
+
+static inline void g364fb_invalidate_display(void *opaque)
+{
+ G364State *s = opaque;
+
+ s->blanked = 0;
+ memory_region_set_dirty(&s->mem_vram, 0, s->vram_size);
+}
+
+static void g364fb_reset(G364State *s)
+{
+ qemu_irq_lower(s->irq);
+
+ memset(s->color_palette, 0, sizeof(s->color_palette));
+ memset(s->cursor_palette, 0, sizeof(s->cursor_palette));
+ memset(s->cursor, 0, sizeof(s->cursor));
+ s->cursor_position = 0;
+ s->ctla = 0;
+ s->top_of_screen = 0;
+ s->width = s->height = 0;
+ memset(s->vram, 0, s->vram_size);
+ g364fb_invalidate_display(s);
+}
+
+/* called for accesses to io ports */
+static uint64_t g364fb_ctrl_read(void *opaque,
+ hwaddr addr,
+ unsigned int size)
+{
+ G364State *s = opaque;
+ uint32_t val;
+
+ if (addr >= REG_CURS_PAT && addr < REG_CURS_PAT + 0x1000) {
+ /* cursor pattern */
+ int idx = (addr - REG_CURS_PAT) >> 3;
+ val = s->cursor[idx];
+ } else if (addr >= REG_CURS_PAL && addr < REG_CURS_PAL + 0x18) {
+ /* cursor palette */
+ int idx = (addr - REG_CURS_PAL) >> 3;
+ val = ((uint32_t)s->cursor_palette[idx][0] << 16);
+ val |= ((uint32_t)s->cursor_palette[idx][1] << 8);
+ val |= ((uint32_t)s->cursor_palette[idx][2] << 0);
+ } else {
+ switch (addr) {
+ case REG_DISPLAY:
+ val = s->width / 4;
+ break;
+ case REG_VDISPLAY:
+ val = s->height * 2;
+ break;
+ case REG_CTLA:
+ val = s->ctla;
+ break;
+ default:
+ {
+ error_report("g364: invalid read at [" TARGET_FMT_plx "]",
+ addr);
+ val = 0;
+ break;
+ }
+ }
+ }
+
+ trace_g364fb_read(addr, val);
+
+ return val;
+}
+
+static void g364fb_update_depth(G364State *s)
+{
+ static const int depths[8] = { 1, 2, 4, 8, 15, 16, 0 };
+ s->depth = depths[(s->ctla & 0x00700000) >> 20];
+}
+
+static void g364_invalidate_cursor_position(G364State *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int ymin, ymax, start, end;
+
+ /* invalidate only near the cursor */
+ ymin = s->cursor_position & 0xfff;
+ ymax = MIN(s->height, ymin + 64);
+ start = ymin * surface_stride(surface);
+ end = (ymax + 1) * surface_stride(surface);
+
+ memory_region_set_dirty(&s->mem_vram, start, end - start);
+}
+
+static void g364fb_ctrl_write(void *opaque,
+ hwaddr addr,
+ uint64_t val,
+ unsigned int size)
+{
+ G364State *s = opaque;
+
+ trace_g364fb_write(addr, val);
+
+ if (addr >= REG_CLR_PAL && addr < REG_CLR_PAL + 0x800) {
+ /* color palette */
+ int idx = (addr - REG_CLR_PAL) >> 3;
+ s->color_palette[idx][0] = (val >> 16) & 0xff;
+ s->color_palette[idx][1] = (val >> 8) & 0xff;
+ s->color_palette[idx][2] = val & 0xff;
+ g364fb_invalidate_display(s);
+ } else if (addr >= REG_CURS_PAT && addr < REG_CURS_PAT + 0x1000) {
+ /* cursor pattern */
+ int idx = (addr - REG_CURS_PAT) >> 3;
+ s->cursor[idx] = val;
+ g364fb_invalidate_display(s);
+ } else if (addr >= REG_CURS_PAL && addr < REG_CURS_PAL + 0x18) {
+ /* cursor palette */
+ int idx = (addr - REG_CURS_PAL) >> 3;
+ s->cursor_palette[idx][0] = (val >> 16) & 0xff;
+ s->cursor_palette[idx][1] = (val >> 8) & 0xff;
+ s->cursor_palette[idx][2] = val & 0xff;
+ g364fb_invalidate_display(s);
+ } else {
+ switch (addr) {
+ case REG_BOOT: /* Boot timing */
+ case 0x00108: /* Line timing: half sync */
+ case 0x00110: /* Line timing: back porch */
+ case 0x00120: /* Line timing: short display */
+ case 0x00128: /* Frame timing: broad pulse */
+ case 0x00130: /* Frame timing: v sync */
+ case 0x00138: /* Frame timing: v preequalise */
+ case 0x00140: /* Frame timing: v postequalise */
+ case 0x00148: /* Frame timing: v blank */
+ case 0x00158: /* Line timing: line time */
+ case 0x00160: /* Frame store: line start */
+ case 0x00168: /* vram cycle: mem init */
+ case 0x00170: /* vram cycle: transfer delay */
+ case 0x00200: /* vram cycle: mask register */
+ /* ignore */
+ break;
+ case REG_TOP:
+ s->top_of_screen = val;
+ g364fb_invalidate_display(s);
+ break;
+ case REG_DISPLAY:
+ s->width = val * 4;
+ break;
+ case REG_VDISPLAY:
+ s->height = val / 2;
+ break;
+ case REG_CTLA:
+ s->ctla = val;
+ g364fb_update_depth(s);
+ g364fb_invalidate_display(s);
+ break;
+ case REG_CURS_POS:
+ g364_invalidate_cursor_position(s);
+ s->cursor_position = val;
+ g364_invalidate_cursor_position(s);
+ break;
+ case REG_RESET:
+ g364fb_reset(s);
+ break;
+ default:
+ error_report("g364: invalid write of 0x%" PRIx64
+ " at [" TARGET_FMT_plx "]", val, addr);
+ break;
+ }
+ }
+ qemu_irq_lower(s->irq);
+}
+
+static const MemoryRegionOps g364fb_ctrl_ops = {
+ .read = g364fb_ctrl_read,
+ .write = g364fb_ctrl_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static int g364fb_post_load(void *opaque, int version_id)
+{
+ G364State *s = opaque;
+
+ /* force refresh */
+ g364fb_update_depth(s);
+ g364fb_invalidate_display(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_g364fb = {
+ .name = "g364fb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = g364fb_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_VBUFFER_UINT32(vram, G364State, 1, NULL, 0, vram_size),
+ VMSTATE_BUFFER_UNSAFE(color_palette, G364State, 0, 256 * 3),
+ VMSTATE_BUFFER_UNSAFE(cursor_palette, G364State, 0, 9),
+ VMSTATE_UINT16_ARRAY(cursor, G364State, 512),
+ VMSTATE_UINT32(cursor_position, G364State),
+ VMSTATE_UINT32(ctla, G364State),
+ VMSTATE_UINT32(top_of_screen, G364State),
+ VMSTATE_UINT32(width, G364State),
+ VMSTATE_UINT32(height, G364State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps g364fb_ops = {
+ .invalidate = g364fb_invalidate_display,
+ .gfx_update = g364fb_update_display,
+};
+
+static void g364fb_init(DeviceState *dev, G364State *s)
+{
+ s->vram = g_malloc0(s->vram_size);
+
+ s->con = graphic_console_init(dev, 0, &g364fb_ops, s);
+
+ memory_region_init_io(&s->mem_ctrl, NULL, &g364fb_ctrl_ops, s, "ctrl", 0x180000);
+ memory_region_init_ram_ptr(&s->mem_vram, NULL, "vram",
+ s->vram_size, s->vram);
+ vmstate_register_ram(&s->mem_vram, dev);
+ memory_region_set_log(&s->mem_vram, true, DIRTY_MEMORY_VGA);
+}
+
+#define TYPE_G364 "sysbus-g364"
+#define G364(obj) OBJECT_CHECK(G364SysBusState, (obj), TYPE_G364)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ G364State g364;
+} G364SysBusState;
+
+static int g364fb_sysbus_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ G364SysBusState *sbs = G364(dev);
+ G364State *s = &sbs->g364;
+
+ g364fb_init(dev, s);
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_mmio(sbd, &s->mem_ctrl);
+ sysbus_init_mmio(sbd, &s->mem_vram);
+
+ return 0;
+}
+
+static void g364fb_sysbus_reset(DeviceState *d)
+{
+ G364SysBusState *s = G364(d);
+
+ g364fb_reset(&s->g364);
+}
+
+static Property g364fb_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("vram_size", G364SysBusState, g364.vram_size,
+ 8 * 1024 * 1024),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void g364fb_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = g364fb_sysbus_init;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->desc = "G364 framebuffer";
+ dc->reset = g364fb_sysbus_reset;
+ dc->vmsd = &vmstate_g364fb;
+ dc->props = g364fb_sysbus_properties;
+}
+
+static const TypeInfo g364fb_sysbus_info = {
+ .name = TYPE_G364,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(G364SysBusState),
+ .class_init = g364fb_sysbus_class_init,
+};
+
+static void g364fb_register_types(void)
+{
+ type_register_static(&g364fb_sysbus_info);
+}
+
+type_init(g364fb_register_types)
diff --git a/hw/display/jazz_led.c b/hw/display/jazz_led.c
new file mode 100644
index 00000000..12b1707c
--- /dev/null
+++ b/hw/display/jazz_led.c
@@ -0,0 +1,311 @@
+/*
+ * QEMU JAZZ LED emulator.
+ *
+ * Copyright (c) 2007-2012 Herve 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.
+ */
+
+#include "qemu-common.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+
+typedef enum {
+ REDRAW_NONE = 0, REDRAW_SEGMENTS = 1, REDRAW_BACKGROUND = 2,
+} screen_state_t;
+
+#define TYPE_JAZZ_LED "jazz-led"
+#define JAZZ_LED(obj) OBJECT_CHECK(LedState, (obj), TYPE_JAZZ_LED)
+
+typedef struct LedState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint8_t segments;
+ QemuConsole *con;
+ screen_state_t state;
+} LedState;
+
+static uint64_t jazz_led_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ LedState *s = opaque;
+ uint8_t val;
+
+ val = s->segments;
+ trace_jazz_led_read(addr, val);
+
+ return val;
+}
+
+static void jazz_led_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ LedState *s = opaque;
+ uint8_t new_val = val & 0xff;
+
+ trace_jazz_led_write(addr, new_val);
+
+ s->segments = new_val;
+ s->state |= REDRAW_SEGMENTS;
+}
+
+static const MemoryRegionOps led_ops = {
+ .read = jazz_led_read,
+ .write = jazz_led_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+};
+
+/***********************************************************/
+/* jazz_led display */
+
+static void draw_horizontal_line(DisplaySurface *ds,
+ int posy, int posx1, int posx2,
+ uint32_t color)
+{
+ uint8_t *d;
+ int x, bpp;
+
+ bpp = (surface_bits_per_pixel(ds) + 7) >> 3;
+ d = surface_data(ds) + surface_stride(ds) * posy + bpp * posx1;
+ switch(bpp) {
+ case 1:
+ for (x = posx1; x <= posx2; x++) {
+ *((uint8_t *)d) = color;
+ d++;
+ }
+ break;
+ case 2:
+ for (x = posx1; x <= posx2; x++) {
+ *((uint16_t *)d) = color;
+ d += 2;
+ }
+ break;
+ case 4:
+ for (x = posx1; x <= posx2; x++) {
+ *((uint32_t *)d) = color;
+ d += 4;
+ }
+ break;
+ }
+}
+
+static void draw_vertical_line(DisplaySurface *ds,
+ int posx, int posy1, int posy2,
+ uint32_t color)
+{
+ uint8_t *d;
+ int y, bpp;
+
+ bpp = (surface_bits_per_pixel(ds) + 7) >> 3;
+ d = surface_data(ds) + surface_stride(ds) * posy1 + bpp * posx;
+ switch(bpp) {
+ case 1:
+ for (y = posy1; y <= posy2; y++) {
+ *((uint8_t *)d) = color;
+ d += surface_stride(ds);
+ }
+ break;
+ case 2:
+ for (y = posy1; y <= posy2; y++) {
+ *((uint16_t *)d) = color;
+ d += surface_stride(ds);
+ }
+ break;
+ case 4:
+ for (y = posy1; y <= posy2; y++) {
+ *((uint32_t *)d) = color;
+ d += surface_stride(ds);
+ }
+ break;
+ }
+}
+
+static void jazz_led_update_display(void *opaque)
+{
+ LedState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint8_t *d1;
+ uint32_t color_segment, color_led;
+ int y, bpp;
+
+ if (s->state & REDRAW_BACKGROUND) {
+ /* clear screen */
+ bpp = (surface_bits_per_pixel(surface) + 7) >> 3;
+ d1 = surface_data(surface);
+ for (y = 0; y < surface_height(surface); y++) {
+ memset(d1, 0x00, surface_width(surface) * bpp);
+ d1 += surface_stride(surface);
+ }
+ }
+
+ if (s->state & REDRAW_SEGMENTS) {
+ /* set colors according to bpp */
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ color_segment = rgb_to_pixel8(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel8(0x00, 0xff, 0x00);
+ break;
+ case 15:
+ color_segment = rgb_to_pixel15(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel15(0x00, 0xff, 0x00);
+ break;
+ case 16:
+ color_segment = rgb_to_pixel16(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel16(0x00, 0xff, 0x00);
+ break;
+ case 24:
+ color_segment = rgb_to_pixel24(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel24(0x00, 0xff, 0x00);
+ break;
+ case 32:
+ color_segment = rgb_to_pixel32(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel32(0x00, 0xff, 0x00);
+ break;
+ default:
+ return;
+ }
+
+ /* display segments */
+ draw_horizontal_line(surface, 40, 10, 40,
+ (s->segments & 0x02) ? color_segment : 0);
+ draw_vertical_line(surface, 10, 10, 40,
+ (s->segments & 0x04) ? color_segment : 0);
+ draw_vertical_line(surface, 10, 40, 70,
+ (s->segments & 0x08) ? color_segment : 0);
+ draw_horizontal_line(surface, 70, 10, 40,
+ (s->segments & 0x10) ? color_segment : 0);
+ draw_vertical_line(surface, 40, 40, 70,
+ (s->segments & 0x20) ? color_segment : 0);
+ draw_vertical_line(surface, 40, 10, 40,
+ (s->segments & 0x40) ? color_segment : 0);
+ draw_horizontal_line(surface, 10, 10, 40,
+ (s->segments & 0x80) ? color_segment : 0);
+
+ /* display led */
+ if (!(s->segments & 0x01))
+ color_led = 0; /* black */
+ draw_horizontal_line(surface, 68, 50, 50, color_led);
+ draw_horizontal_line(surface, 69, 49, 51, color_led);
+ draw_horizontal_line(surface, 70, 48, 52, color_led);
+ draw_horizontal_line(surface, 71, 49, 51, color_led);
+ draw_horizontal_line(surface, 72, 50, 50, color_led);
+ }
+
+ s->state = REDRAW_NONE;
+ dpy_gfx_update(s->con, 0, 0,
+ surface_width(surface), surface_height(surface));
+}
+
+static void jazz_led_invalidate_display(void *opaque)
+{
+ LedState *s = opaque;
+ s->state |= REDRAW_SEGMENTS | REDRAW_BACKGROUND;
+}
+
+static void jazz_led_text_update(void *opaque, console_ch_t *chardata)
+{
+ LedState *s = opaque;
+ char buf[2];
+
+ dpy_text_cursor(s->con, -1, -1);
+ qemu_console_resize(s->con, 2, 1);
+
+ /* TODO: draw the segments */
+ snprintf(buf, 2, "%02hhx\n", s->segments);
+ console_write_ch(chardata++, 0x00200100 | buf[0]);
+ console_write_ch(chardata++, 0x00200100 | buf[1]);
+
+ dpy_text_update(s->con, 0, 0, 2, 1);
+}
+
+static int jazz_led_post_load(void *opaque, int version_id)
+{
+ /* force refresh */
+ jazz_led_invalidate_display(opaque);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_jazz_led = {
+ .name = "jazz-led",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = jazz_led_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(segments, LedState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps jazz_led_ops = {
+ .invalidate = jazz_led_invalidate_display,
+ .gfx_update = jazz_led_update_display,
+ .text_update = jazz_led_text_update,
+};
+
+static int jazz_led_init(SysBusDevice *dev)
+{
+ LedState *s = JAZZ_LED(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &led_ops, s, "led", 1);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ s->con = graphic_console_init(DEVICE(dev), 0, &jazz_led_ops, s);
+
+ return 0;
+}
+
+static void jazz_led_reset(DeviceState *d)
+{
+ LedState *s = JAZZ_LED(d);
+
+ s->segments = 0;
+ s->state = REDRAW_SEGMENTS | REDRAW_BACKGROUND;
+ qemu_console_resize(s->con, 60, 80);
+}
+
+static void jazz_led_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = jazz_led_init;
+ dc->desc = "Jazz LED display",
+ dc->vmsd = &vmstate_jazz_led;
+ dc->reset = jazz_led_reset;
+}
+
+static const TypeInfo jazz_led_info = {
+ .name = TYPE_JAZZ_LED,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LedState),
+ .class_init = jazz_led_class_init,
+};
+
+static void jazz_led_register(void)
+{
+ type_register_static(&jazz_led_info);
+}
+
+type_init(jazz_led_register);
diff --git a/hw/display/milkymist-tmu2.c b/hw/display/milkymist-tmu2.c
new file mode 100644
index 00000000..3e1d0b9c
--- /dev/null
+++ b/hw/display/milkymist-tmu2.c
@@ -0,0 +1,494 @@
+/*
+ * QEMU model of the Milkymist texture mapping unit.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ * Copyright (c) 2010 Sebastien Bourdeauducq
+ * <sebastien.bourdeauducq@lekernel.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/tmu2.pdf
+ *
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "qemu/error-report.h"
+
+#include <X11/Xlib.h>
+#include <GL/gl.h>
+#include <GL/glx.h>
+
+enum {
+ R_CTL = 0,
+ R_HMESHLAST,
+ R_VMESHLAST,
+ R_BRIGHTNESS,
+ R_CHROMAKEY,
+ R_VERTICESADDR,
+ R_TEXFBUF,
+ R_TEXHRES,
+ R_TEXVRES,
+ R_TEXHMASK,
+ R_TEXVMASK,
+ R_DSTFBUF,
+ R_DSTHRES,
+ R_DSTVRES,
+ R_DSTHOFFSET,
+ R_DSTVOFFSET,
+ R_DSTSQUAREW,
+ R_DSTSQUAREH,
+ R_ALPHA,
+ R_MAX
+};
+
+enum {
+ CTL_START_BUSY = (1<<0),
+ CTL_CHROMAKEY = (1<<1),
+};
+
+enum {
+ MAX_BRIGHTNESS = 63,
+ MAX_ALPHA = 63,
+};
+
+enum {
+ MESH_MAXSIZE = 128,
+};
+
+struct vertex {
+ int x;
+ int y;
+} QEMU_PACKED;
+
+#define TYPE_MILKYMIST_TMU2 "milkymist-tmu2"
+#define MILKYMIST_TMU2(obj) \
+ OBJECT_CHECK(MilkymistTMU2State, (obj), TYPE_MILKYMIST_TMU2)
+
+struct MilkymistTMU2State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t regs[R_MAX];
+
+ Display *dpy;
+ GLXFBConfig glx_fb_config;
+ GLXContext glx_context;
+};
+typedef struct MilkymistTMU2State MilkymistTMU2State;
+
+static const int glx_fbconfig_attr[] = {
+ GLX_GREEN_SIZE, 5,
+ GLX_GREEN_SIZE, 6,
+ GLX_BLUE_SIZE, 5,
+ None
+};
+
+static int tmu2_glx_init(MilkymistTMU2State *s)
+{
+ GLXFBConfig *configs;
+ int nelements;
+
+ s->dpy = XOpenDisplay(NULL); /* FIXME: call XCloseDisplay() */
+ if (s->dpy == NULL) {
+ return 1;
+ }
+
+ configs = glXChooseFBConfig(s->dpy, 0, glx_fbconfig_attr, &nelements);
+ if (configs == NULL) {
+ return 1;
+ }
+
+ s->glx_fb_config = *configs;
+ XFree(configs);
+
+ /* FIXME: call glXDestroyContext() */
+ s->glx_context = glXCreateNewContext(s->dpy, s->glx_fb_config,
+ GLX_RGBA_TYPE, NULL, 1);
+ if (s->glx_context == NULL) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void tmu2_gl_map(struct vertex *mesh, int texhres, int texvres,
+ int hmeshlast, int vmeshlast, int ho, int vo, int sw, int sh)
+{
+ int x, y;
+ int x0, y0, x1, y1;
+ int u0, v0, u1, v1, u2, v2, u3, v3;
+ double xscale = 1.0 / ((double)(64 * texhres));
+ double yscale = 1.0 / ((double)(64 * texvres));
+
+ glLoadIdentity();
+ glTranslatef(ho, vo, 0);
+ glEnable(GL_TEXTURE_2D);
+ glBegin(GL_QUADS);
+
+ for (y = 0; y < vmeshlast; y++) {
+ y0 = y * sh;
+ y1 = y0 + sh;
+ for (x = 0; x < hmeshlast; x++) {
+ x0 = x * sw;
+ x1 = x0 + sw;
+
+ u0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].x);
+ v0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].y);
+ u1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].x);
+ v1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].y);
+ u2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].x);
+ v2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].y);
+ u3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].x);
+ v3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].y);
+
+ glTexCoord2d(((double)u0) * xscale, ((double)v0) * yscale);
+ glVertex3i(x0, y0, 0);
+ glTexCoord2d(((double)u1) * xscale, ((double)v1) * yscale);
+ glVertex3i(x1, y0, 0);
+ glTexCoord2d(((double)u2) * xscale, ((double)v2) * yscale);
+ glVertex3i(x1, y1, 0);
+ glTexCoord2d(((double)u3) * xscale, ((double)v3) * yscale);
+ glVertex3i(x0, y1, 0);
+ }
+ }
+
+ glEnd();
+}
+
+static void tmu2_start(MilkymistTMU2State *s)
+{
+ int pbuffer_attrib[6] = {
+ GLX_PBUFFER_WIDTH,
+ 0,
+ GLX_PBUFFER_HEIGHT,
+ 0,
+ GLX_PRESERVED_CONTENTS,
+ True
+ };
+
+ GLXPbuffer pbuffer;
+ GLuint texture;
+ void *fb;
+ hwaddr fb_len;
+ void *mesh;
+ hwaddr mesh_len;
+ float m;
+
+ trace_milkymist_tmu2_start();
+
+ /* Create and set up a suitable OpenGL context */
+ pbuffer_attrib[1] = s->regs[R_DSTHRES];
+ pbuffer_attrib[3] = s->regs[R_DSTVRES];
+ pbuffer = glXCreatePbuffer(s->dpy, s->glx_fb_config, pbuffer_attrib);
+ glXMakeContextCurrent(s->dpy, pbuffer, pbuffer, s->glx_context);
+
+ /* Fixup endianness. TODO: would it work on BE hosts? */
+ glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
+ glPixelStorei(GL_PACK_SWAP_BYTES, 1);
+
+ /* Row alignment */
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
+ glPixelStorei(GL_PACK_ALIGNMENT, 2);
+
+ /* Read the QEMU source framebuffer into an OpenGL texture */
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ fb_len = 2*s->regs[R_TEXHRES]*s->regs[R_TEXVRES];
+ fb = cpu_physical_memory_map(s->regs[R_TEXFBUF], &fb_len, 0);
+ if (fb == NULL) {
+ glDeleteTextures(1, &texture);
+ glXMakeContextCurrent(s->dpy, None, None, NULL);
+ glXDestroyPbuffer(s->dpy, pbuffer);
+ return;
+ }
+ glTexImage2D(GL_TEXTURE_2D, 0, 3, s->regs[R_TEXHRES], s->regs[R_TEXVRES],
+ 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fb);
+ cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
+
+ /* Set up texturing options */
+ /* WARNING:
+ * Many cases of TMU2 masking are not supported by OpenGL.
+ * We only implement the most common ones:
+ * - full bilinear filtering vs. nearest texel
+ * - texture clamping vs. texture wrapping
+ */
+ if ((s->regs[R_TEXHMASK] & 0x3f) > 0x20) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ }
+ if ((s->regs[R_TEXHMASK] >> 6) & s->regs[R_TEXHRES]) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ }
+ if ((s->regs[R_TEXVMASK] >> 6) & s->regs[R_TEXVRES]) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ }
+
+ /* Translucency and decay */
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ m = (float)(s->regs[R_BRIGHTNESS] + 1) / 64.0f;
+ glColor4f(m, m, m, (float)(s->regs[R_ALPHA] + 1) / 64.0f);
+
+ /* Read the QEMU dest. framebuffer into the OpenGL framebuffer */
+ fb_len = 2 * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
+ fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 0);
+ if (fb == NULL) {
+ glDeleteTextures(1, &texture);
+ glXMakeContextCurrent(s->dpy, None, None, NULL);
+ glXDestroyPbuffer(s->dpy, pbuffer);
+ return;
+ }
+
+ glDrawPixels(s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
+ GL_UNSIGNED_SHORT_5_6_5, fb);
+ cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
+ glViewport(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES]);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0.0, s->regs[R_DSTHRES], 0.0, s->regs[R_DSTVRES], -1.0, 1.0);
+ glMatrixMode(GL_MODELVIEW);
+
+ /* Map the texture */
+ mesh_len = MESH_MAXSIZE*MESH_MAXSIZE*sizeof(struct vertex);
+ mesh = cpu_physical_memory_map(s->regs[R_VERTICESADDR], &mesh_len, 0);
+ if (mesh == NULL) {
+ glDeleteTextures(1, &texture);
+ glXMakeContextCurrent(s->dpy, None, None, NULL);
+ glXDestroyPbuffer(s->dpy, pbuffer);
+ return;
+ }
+
+ tmu2_gl_map((struct vertex *)mesh,
+ s->regs[R_TEXHRES], s->regs[R_TEXVRES],
+ s->regs[R_HMESHLAST], s->regs[R_VMESHLAST],
+ s->regs[R_DSTHOFFSET], s->regs[R_DSTVOFFSET],
+ s->regs[R_DSTSQUAREW], s->regs[R_DSTSQUAREH]);
+ cpu_physical_memory_unmap(mesh, mesh_len, 0, mesh_len);
+
+ /* Write back the OpenGL framebuffer to the QEMU framebuffer */
+ fb_len = 2 * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
+ fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 1);
+ if (fb == NULL) {
+ glDeleteTextures(1, &texture);
+ glXMakeContextCurrent(s->dpy, None, None, NULL);
+ glXDestroyPbuffer(s->dpy, pbuffer);
+ return;
+ }
+
+ glReadPixels(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
+ GL_UNSIGNED_SHORT_5_6_5, fb);
+ cpu_physical_memory_unmap(fb, fb_len, 1, fb_len);
+
+ /* Free OpenGL allocs */
+ glDeleteTextures(1, &texture);
+ glXMakeContextCurrent(s->dpy, None, None, NULL);
+ glXDestroyPbuffer(s->dpy, pbuffer);
+
+ s->regs[R_CTL] &= ~CTL_START_BUSY;
+
+ trace_milkymist_tmu2_pulse_irq();
+ qemu_irq_pulse(s->irq);
+}
+
+static uint64_t tmu2_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistTMU2State *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTL:
+ case R_HMESHLAST:
+ case R_VMESHLAST:
+ case R_BRIGHTNESS:
+ case R_CHROMAKEY:
+ case R_VERTICESADDR:
+ case R_TEXFBUF:
+ case R_TEXHRES:
+ case R_TEXVRES:
+ case R_TEXHMASK:
+ case R_TEXVMASK:
+ case R_DSTFBUF:
+ case R_DSTHRES:
+ case R_DSTVRES:
+ case R_DSTHOFFSET:
+ case R_DSTVOFFSET:
+ case R_DSTSQUAREW:
+ case R_DSTSQUAREH:
+ case R_ALPHA:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_tmu2: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_tmu2_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void tmu2_check_registers(MilkymistTMU2State *s)
+{
+ if (s->regs[R_BRIGHTNESS] > MAX_BRIGHTNESS) {
+ error_report("milkymist_tmu2: max brightness is %d", MAX_BRIGHTNESS);
+ }
+
+ if (s->regs[R_ALPHA] > MAX_ALPHA) {
+ error_report("milkymist_tmu2: max alpha is %d", MAX_ALPHA);
+ }
+
+ if (s->regs[R_VERTICESADDR] & 0x07) {
+ error_report("milkymist_tmu2: vertex mesh address has to be 64-bit "
+ "aligned");
+ }
+
+ if (s->regs[R_TEXFBUF] & 0x01) {
+ error_report("milkymist_tmu2: texture buffer address has to be "
+ "16-bit aligned");
+ }
+}
+
+static void tmu2_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistTMU2State *s = opaque;
+
+ trace_milkymist_tmu2_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTL:
+ s->regs[addr] = value;
+ if (value & CTL_START_BUSY) {
+ tmu2_start(s);
+ }
+ break;
+ case R_BRIGHTNESS:
+ case R_HMESHLAST:
+ case R_VMESHLAST:
+ case R_CHROMAKEY:
+ case R_VERTICESADDR:
+ case R_TEXFBUF:
+ case R_TEXHRES:
+ case R_TEXVRES:
+ case R_TEXHMASK:
+ case R_TEXVMASK:
+ case R_DSTFBUF:
+ case R_DSTHRES:
+ case R_DSTVRES:
+ case R_DSTHOFFSET:
+ case R_DSTVOFFSET:
+ case R_DSTSQUAREW:
+ case R_DSTSQUAREH:
+ case R_ALPHA:
+ s->regs[addr] = value;
+ break;
+
+ default:
+ error_report("milkymist_tmu2: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ tmu2_check_registers(s);
+}
+
+static const MemoryRegionOps tmu2_mmio_ops = {
+ .read = tmu2_read,
+ .write = tmu2_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void milkymist_tmu2_reset(DeviceState *d)
+{
+ MilkymistTMU2State *s = MILKYMIST_TMU2(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+}
+
+static int milkymist_tmu2_init(SysBusDevice *dev)
+{
+ MilkymistTMU2State *s = MILKYMIST_TMU2(dev);
+
+ if (tmu2_glx_init(s)) {
+ return 1;
+ }
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &tmu2_mmio_ops, s,
+ "milkymist-tmu2", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_tmu2 = {
+ .name = "milkymist-tmu2",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistTMU2State, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void milkymist_tmu2_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_tmu2_init;
+ dc->reset = milkymist_tmu2_reset;
+ dc->vmsd = &vmstate_milkymist_tmu2;
+}
+
+static const TypeInfo milkymist_tmu2_info = {
+ .name = TYPE_MILKYMIST_TMU2,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistTMU2State),
+ .class_init = milkymist_tmu2_class_init,
+};
+
+static void milkymist_tmu2_register_types(void)
+{
+ type_register_static(&milkymist_tmu2_info);
+}
+
+type_init(milkymist_tmu2_register_types)
diff --git a/hw/display/milkymist-vgafb.c b/hw/display/milkymist-vgafb.c
new file mode 100644
index 00000000..ab3074fa
--- /dev/null
+++ b/hw/display/milkymist-vgafb.c
@@ -0,0 +1,353 @@
+
+/*
+ * QEMU model of the Milkymist VGA framebuffer.
+ *
+ * Copyright (c) 2010-2012 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/vgafb.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "ui/console.h"
+#include "framebuffer.h"
+#include "ui/pixel_ops.h"
+#include "qemu/error-report.h"
+
+#define BITS 8
+#include "milkymist-vgafb_template.h"
+#define BITS 15
+#include "milkymist-vgafb_template.h"
+#define BITS 16
+#include "milkymist-vgafb_template.h"
+#define BITS 24
+#include "milkymist-vgafb_template.h"
+#define BITS 32
+#include "milkymist-vgafb_template.h"
+
+enum {
+ R_CTRL = 0,
+ R_HRES,
+ R_HSYNC_START,
+ R_HSYNC_END,
+ R_HSCAN,
+ R_VRES,
+ R_VSYNC_START,
+ R_VSYNC_END,
+ R_VSCAN,
+ R_BASEADDRESS,
+ R_BASEADDRESS_ACT,
+ R_BURST_COUNT,
+ R_DDC,
+ R_SOURCE_CLOCK,
+ R_MAX
+};
+
+enum {
+ CTRL_RESET = (1<<0),
+};
+
+#define TYPE_MILKYMIST_VGAFB "milkymist-vgafb"
+#define MILKYMIST_VGAFB(obj) \
+ OBJECT_CHECK(MilkymistVgafbState, (obj), TYPE_MILKYMIST_VGAFB)
+
+struct MilkymistVgafbState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+ MemoryRegionSection fbsection;
+ QemuConsole *con;
+
+ int invalidate;
+ uint32_t fb_offset;
+ uint32_t fb_mask;
+
+ uint32_t regs[R_MAX];
+};
+typedef struct MilkymistVgafbState MilkymistVgafbState;
+
+static int vgafb_enabled(MilkymistVgafbState *s)
+{
+ return !(s->regs[R_CTRL] & CTRL_RESET);
+}
+
+static void vgafb_update_display(void *opaque)
+{
+ MilkymistVgafbState *s = opaque;
+ SysBusDevice *sbd;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width;
+ int first = 0;
+ int last = 0;
+ drawfn fn;
+
+ if (!vgafb_enabled(s)) {
+ return;
+ }
+
+ sbd = SYS_BUS_DEVICE(s);
+ int dest_width = s->regs[R_HRES];
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ return;
+ case 8:
+ fn = draw_line_8;
+ break;
+ case 15:
+ fn = draw_line_15;
+ dest_width *= 2;
+ break;
+ case 16:
+ fn = draw_line_16;
+ dest_width *= 2;
+ break;
+ case 24:
+ fn = draw_line_24;
+ dest_width *= 3;
+ break;
+ case 32:
+ fn = draw_line_32;
+ dest_width *= 4;
+ break;
+ default:
+ hw_error("milkymist_vgafb: bad color depth\n");
+ break;
+ }
+
+ src_width = s->regs[R_HRES] * 2;
+ if (s->invalidate) {
+ framebuffer_update_memory_section(&s->fbsection,
+ sysbus_address_space(sbd),
+ s->regs[R_BASEADDRESS] + s->fb_offset,
+ s->regs[R_VRES], src_width);
+ }
+
+ framebuffer_update_display(surface, &s->fbsection,
+ s->regs[R_HRES],
+ s->regs[R_VRES],
+ src_width,
+ dest_width,
+ 0,
+ s->invalidate,
+ fn,
+ NULL,
+ &first, &last);
+
+ if (first >= 0) {
+ dpy_gfx_update(s->con, 0, first, s->regs[R_HRES], last - first + 1);
+ }
+ s->invalidate = 0;
+}
+
+static void vgafb_invalidate_display(void *opaque)
+{
+ MilkymistVgafbState *s = opaque;
+ s->invalidate = 1;
+}
+
+static void vgafb_resize(MilkymistVgafbState *s)
+{
+ if (!vgafb_enabled(s)) {
+ return;
+ }
+
+ qemu_console_resize(s->con, s->regs[R_HRES], s->regs[R_VRES]);
+ s->invalidate = 1;
+}
+
+static uint64_t vgafb_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistVgafbState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTRL:
+ case R_HRES:
+ case R_HSYNC_START:
+ case R_HSYNC_END:
+ case R_HSCAN:
+ case R_VRES:
+ case R_VSYNC_START:
+ case R_VSYNC_END:
+ case R_VSCAN:
+ case R_BASEADDRESS:
+ case R_BURST_COUNT:
+ case R_DDC:
+ case R_SOURCE_CLOCK:
+ r = s->regs[addr];
+ break;
+ case R_BASEADDRESS_ACT:
+ r = s->regs[R_BASEADDRESS];
+ break;
+
+ default:
+ error_report("milkymist_vgafb: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_vgafb_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void vgafb_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistVgafbState *s = opaque;
+
+ trace_milkymist_vgafb_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTRL:
+ s->regs[addr] = value;
+ vgafb_resize(s);
+ break;
+ case R_HSYNC_START:
+ case R_HSYNC_END:
+ case R_HSCAN:
+ case R_VSYNC_START:
+ case R_VSYNC_END:
+ case R_VSCAN:
+ case R_BURST_COUNT:
+ case R_DDC:
+ case R_SOURCE_CLOCK:
+ s->regs[addr] = value;
+ break;
+ case R_BASEADDRESS:
+ if (value & 0x1f) {
+ error_report("milkymist_vgafb: framebuffer base address have to "
+ "be 32 byte aligned");
+ break;
+ }
+ s->regs[addr] = value & s->fb_mask;
+ s->invalidate = 1;
+ break;
+ case R_HRES:
+ case R_VRES:
+ s->regs[addr] = value;
+ vgafb_resize(s);
+ break;
+ case R_BASEADDRESS_ACT:
+ error_report("milkymist_vgafb: write to read-only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+
+ default:
+ error_report("milkymist_vgafb: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps vgafb_mmio_ops = {
+ .read = vgafb_read,
+ .write = vgafb_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void milkymist_vgafb_reset(DeviceState *d)
+{
+ MilkymistVgafbState *s = MILKYMIST_VGAFB(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ /* defaults */
+ s->regs[R_CTRL] = CTRL_RESET;
+ s->regs[R_HRES] = 640;
+ s->regs[R_VRES] = 480;
+ s->regs[R_BASEADDRESS] = 0;
+}
+
+static const GraphicHwOps vgafb_ops = {
+ .invalidate = vgafb_invalidate_display,
+ .gfx_update = vgafb_update_display,
+};
+
+static int milkymist_vgafb_init(SysBusDevice *dev)
+{
+ MilkymistVgafbState *s = MILKYMIST_VGAFB(dev);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &vgafb_mmio_ops, s,
+ "milkymist-vgafb", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ s->con = graphic_console_init(DEVICE(dev), 0, &vgafb_ops, s);
+
+ return 0;
+}
+
+static int vgafb_post_load(void *opaque, int version_id)
+{
+ vgafb_invalidate_display(opaque);
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_vgafb = {
+ .name = "milkymist-vgafb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = vgafb_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistVgafbState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property milkymist_vgafb_properties[] = {
+ DEFINE_PROP_UINT32("fb_offset", MilkymistVgafbState, fb_offset, 0x0),
+ DEFINE_PROP_UINT32("fb_mask", MilkymistVgafbState, fb_mask, 0xffffffff),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void milkymist_vgafb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_vgafb_init;
+ dc->reset = milkymist_vgafb_reset;
+ dc->vmsd = &vmstate_milkymist_vgafb;
+ dc->props = milkymist_vgafb_properties;
+}
+
+static const TypeInfo milkymist_vgafb_info = {
+ .name = TYPE_MILKYMIST_VGAFB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistVgafbState),
+ .class_init = milkymist_vgafb_class_init,
+};
+
+static void milkymist_vgafb_register_types(void)
+{
+ type_register_static(&milkymist_vgafb_info);
+}
+
+type_init(milkymist_vgafb_register_types)
diff --git a/hw/display/milkymist-vgafb_template.h b/hw/display/milkymist-vgafb_template.h
new file mode 100644
index 00000000..48837809
--- /dev/null
+++ b/hw/display/milkymist-vgafb_template.h
@@ -0,0 +1,74 @@
+/*
+ * QEMU model of the Milkymist VGA framebuffer.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if BITS == 8
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ *to = rgb_to_pixel8(r, g, b); \
+ to += 1; \
+ } while (0)
+#elif BITS == 15
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ *(uint16_t *)to = rgb_to_pixel15(r, g, b); \
+ to += 2; \
+ } while (0)
+#elif BITS == 16
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ *(uint16_t *)to = rgb_to_pixel16(r, g, b); \
+ to += 2; \
+ } while (0)
+#elif BITS == 24
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ uint32_t tmp = rgb_to_pixel24(r, g, b); \
+ *(to++) = tmp & 0xff; \
+ *(to++) = (tmp >> 8) & 0xff; \
+ *(to++) = (tmp >> 16) & 0xff; \
+ } while (0)
+#elif BITS == 32
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ *(uint32_t *)to = rgb_to_pixel32(r, g, b); \
+ to += 4; \
+ } while (0)
+#else
+#error unknown bit depth
+#endif
+
+static void glue(draw_line_, BITS)(void *opaque, uint8_t *d, const uint8_t *s,
+ int width, int deststep)
+{
+ uint16_t rgb565;
+ uint8_t r, g, b;
+
+ while (width--) {
+ rgb565 = lduw_be_p(s);
+ r = ((rgb565 >> 11) & 0x1f) << 3;
+ g = ((rgb565 >> 5) & 0x3f) << 2;
+ b = ((rgb565 >> 0) & 0x1f) << 3;
+ COPY_PIXEL(d, r, g, b);
+ s += 2;
+ }
+}
+
+#undef BITS
+#undef COPY_PIXEL
diff --git a/hw/display/omap_dss.c b/hw/display/omap_dss.c
new file mode 100644
index 00000000..f1fef276
--- /dev/null
+++ b/hw/display/omap_dss.c
@@ -0,0 +1,1091 @@
+/*
+ * OMAP2 Display Subsystem.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/arm/omap.h"
+
+struct omap_dss_s {
+ qemu_irq irq;
+ qemu_irq drq;
+ DisplayState *state;
+ MemoryRegion iomem_diss1, iomem_disc1, iomem_rfbi1, iomem_venc1, iomem_im3;
+
+ int autoidle;
+ int control;
+ int enable;
+
+ struct omap_dss_panel_s {
+ int enable;
+ int nx;
+ int ny;
+
+ int x;
+ int y;
+ } dig, lcd;
+
+ struct {
+ uint32_t idlemode;
+ uint32_t irqst;
+ uint32_t irqen;
+ uint32_t control;
+ uint32_t config;
+ uint32_t capable;
+ uint32_t timing[4];
+ int line;
+ uint32_t bg[2];
+ uint32_t trans[2];
+
+ struct omap_dss_plane_s {
+ int enable;
+ int bpp;
+ int posx;
+ int posy;
+ int nx;
+ int ny;
+
+ hwaddr addr[3];
+
+ uint32_t attr;
+ uint32_t tresh;
+ int rowinc;
+ int colinc;
+ int wininc;
+ } l[3];
+
+ int invalidate;
+ uint16_t palette[256];
+ } dispc;
+
+ struct {
+ int idlemode;
+ uint32_t control;
+ int enable;
+ int pixels;
+ int busy;
+ int skiplines;
+ uint16_t rxbuf;
+ uint32_t config[2];
+ uint32_t time[4];
+ uint32_t data[6];
+ uint16_t vsync;
+ uint16_t hsync;
+ struct rfbi_chip_s *chip[2];
+ } rfbi;
+};
+
+static void omap_dispc_interrupt_update(struct omap_dss_s *s)
+{
+ qemu_set_irq(s->irq, s->dispc.irqst & s->dispc.irqen);
+}
+
+static void omap_rfbi_reset(struct omap_dss_s *s)
+{
+ s->rfbi.idlemode = 0;
+ s->rfbi.control = 2;
+ s->rfbi.enable = 0;
+ s->rfbi.pixels = 0;
+ s->rfbi.skiplines = 0;
+ s->rfbi.busy = 0;
+ s->rfbi.config[0] = 0x00310000;
+ s->rfbi.config[1] = 0x00310000;
+ s->rfbi.time[0] = 0;
+ s->rfbi.time[1] = 0;
+ s->rfbi.time[2] = 0;
+ s->rfbi.time[3] = 0;
+ s->rfbi.data[0] = 0;
+ s->rfbi.data[1] = 0;
+ s->rfbi.data[2] = 0;
+ s->rfbi.data[3] = 0;
+ s->rfbi.data[4] = 0;
+ s->rfbi.data[5] = 0;
+ s->rfbi.vsync = 0;
+ s->rfbi.hsync = 0;
+}
+
+void omap_dss_reset(struct omap_dss_s *s)
+{
+ s->autoidle = 0;
+ s->control = 0;
+ s->enable = 0;
+
+ s->dig.enable = 0;
+ s->dig.nx = 1;
+ s->dig.ny = 1;
+
+ s->lcd.enable = 0;
+ s->lcd.nx = 1;
+ s->lcd.ny = 1;
+
+ s->dispc.idlemode = 0;
+ s->dispc.irqst = 0;
+ s->dispc.irqen = 0;
+ s->dispc.control = 0;
+ s->dispc.config = 0;
+ s->dispc.capable = 0x161;
+ s->dispc.timing[0] = 0;
+ s->dispc.timing[1] = 0;
+ s->dispc.timing[2] = 0;
+ s->dispc.timing[3] = 0;
+ s->dispc.line = 0;
+ s->dispc.bg[0] = 0;
+ s->dispc.bg[1] = 0;
+ s->dispc.trans[0] = 0;
+ s->dispc.trans[1] = 0;
+
+ s->dispc.l[0].enable = 0;
+ s->dispc.l[0].bpp = 0;
+ s->dispc.l[0].addr[0] = 0;
+ s->dispc.l[0].addr[1] = 0;
+ s->dispc.l[0].addr[2] = 0;
+ s->dispc.l[0].posx = 0;
+ s->dispc.l[0].posy = 0;
+ s->dispc.l[0].nx = 1;
+ s->dispc.l[0].ny = 1;
+ s->dispc.l[0].attr = 0;
+ s->dispc.l[0].tresh = 0;
+ s->dispc.l[0].rowinc = 1;
+ s->dispc.l[0].colinc = 1;
+ s->dispc.l[0].wininc = 0;
+
+ omap_rfbi_reset(s);
+ omap_dispc_interrupt_update(s);
+}
+
+static uint64_t omap_diss_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* DSS_REVISIONNUMBER */
+ return 0x20;
+
+ case 0x10: /* DSS_SYSCONFIG */
+ return s->autoidle;
+
+ case 0x14: /* DSS_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x40: /* DSS_CONTROL */
+ return s->control;
+
+ case 0x50: /* DSS_PSA_LCD_REG_1 */
+ case 0x54: /* DSS_PSA_LCD_REG_2 */
+ case 0x58: /* DSS_PSA_VIDEO_REG */
+ /* TODO: fake some values when appropriate s->control bits are set */
+ return 0;
+
+ case 0x5c: /* DSS_STATUS */
+ return 1 + (s->control & 1);
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_diss_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* DSS_REVISIONNUMBER */
+ case 0x14: /* DSS_SYSSTATUS */
+ case 0x50: /* DSS_PSA_LCD_REG_1 */
+ case 0x54: /* DSS_PSA_LCD_REG_2 */
+ case 0x58: /* DSS_PSA_VIDEO_REG */
+ case 0x5c: /* DSS_STATUS */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* DSS_SYSCONFIG */
+ if (value & 2) /* SOFTRESET */
+ omap_dss_reset(s);
+ s->autoidle = value & 1;
+ break;
+
+ case 0x40: /* DSS_CONTROL */
+ s->control = value & 0x3dd;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_diss_ops = {
+ .read = omap_diss_read,
+ .write = omap_diss_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t omap_disc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x000: /* DISPC_REVISION */
+ return 0x20;
+
+ case 0x010: /* DISPC_SYSCONFIG */
+ return s->dispc.idlemode;
+
+ case 0x014: /* DISPC_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x018: /* DISPC_IRQSTATUS */
+ return s->dispc.irqst;
+
+ case 0x01c: /* DISPC_IRQENABLE */
+ return s->dispc.irqen;
+
+ case 0x040: /* DISPC_CONTROL */
+ return s->dispc.control;
+
+ case 0x044: /* DISPC_CONFIG */
+ return s->dispc.config;
+
+ case 0x048: /* DISPC_CAPABLE */
+ return s->dispc.capable;
+
+ case 0x04c: /* DISPC_DEFAULT_COLOR0 */
+ return s->dispc.bg[0];
+ case 0x050: /* DISPC_DEFAULT_COLOR1 */
+ return s->dispc.bg[1];
+ case 0x054: /* DISPC_TRANS_COLOR0 */
+ return s->dispc.trans[0];
+ case 0x058: /* DISPC_TRANS_COLOR1 */
+ return s->dispc.trans[1];
+
+ case 0x05c: /* DISPC_LINE_STATUS */
+ return 0x7ff;
+ case 0x060: /* DISPC_LINE_NUMBER */
+ return s->dispc.line;
+
+ case 0x064: /* DISPC_TIMING_H */
+ return s->dispc.timing[0];
+ case 0x068: /* DISPC_TIMING_V */
+ return s->dispc.timing[1];
+ case 0x06c: /* DISPC_POL_FREQ */
+ return s->dispc.timing[2];
+ case 0x070: /* DISPC_DIVISOR */
+ return s->dispc.timing[3];
+
+ case 0x078: /* DISPC_SIZE_DIG */
+ return ((s->dig.ny - 1) << 16) | (s->dig.nx - 1);
+ case 0x07c: /* DISPC_SIZE_LCD */
+ return ((s->lcd.ny - 1) << 16) | (s->lcd.nx - 1);
+
+ case 0x080: /* DISPC_GFX_BA0 */
+ return s->dispc.l[0].addr[0];
+ case 0x084: /* DISPC_GFX_BA1 */
+ return s->dispc.l[0].addr[1];
+ case 0x088: /* DISPC_GFX_POSITION */
+ return (s->dispc.l[0].posy << 16) | s->dispc.l[0].posx;
+ case 0x08c: /* DISPC_GFX_SIZE */
+ return ((s->dispc.l[0].ny - 1) << 16) | (s->dispc.l[0].nx - 1);
+ case 0x0a0: /* DISPC_GFX_ATTRIBUTES */
+ return s->dispc.l[0].attr;
+ case 0x0a4: /* DISPC_GFX_FIFO_TRESHOLD */
+ return s->dispc.l[0].tresh;
+ case 0x0a8: /* DISPC_GFX_FIFO_SIZE_STATUS */
+ return 256;
+ case 0x0ac: /* DISPC_GFX_ROW_INC */
+ return s->dispc.l[0].rowinc;
+ case 0x0b0: /* DISPC_GFX_PIXEL_INC */
+ return s->dispc.l[0].colinc;
+ case 0x0b4: /* DISPC_GFX_WINDOW_SKIP */
+ return s->dispc.l[0].wininc;
+ case 0x0b8: /* DISPC_GFX_TABLE_BA */
+ return s->dispc.l[0].addr[2];
+
+ case 0x0bc: /* DISPC_VID1_BA0 */
+ case 0x0c0: /* DISPC_VID1_BA1 */
+ case 0x0c4: /* DISPC_VID1_POSITION */
+ case 0x0c8: /* DISPC_VID1_SIZE */
+ case 0x0cc: /* DISPC_VID1_ATTRIBUTES */
+ case 0x0d0: /* DISPC_VID1_FIFO_TRESHOLD */
+ case 0x0d4: /* DISPC_VID1_FIFO_SIZE_STATUS */
+ case 0x0d8: /* DISPC_VID1_ROW_INC */
+ case 0x0dc: /* DISPC_VID1_PIXEL_INC */
+ case 0x0e0: /* DISPC_VID1_FIR */
+ case 0x0e4: /* DISPC_VID1_PICTURE_SIZE */
+ case 0x0e8: /* DISPC_VID1_ACCU0 */
+ case 0x0ec: /* DISPC_VID1_ACCU1 */
+ case 0x0f0 ... 0x140: /* DISPC_VID1_FIR_COEF, DISPC_VID1_CONV_COEF */
+ case 0x14c: /* DISPC_VID2_BA0 */
+ case 0x150: /* DISPC_VID2_BA1 */
+ case 0x154: /* DISPC_VID2_POSITION */
+ case 0x158: /* DISPC_VID2_SIZE */
+ case 0x15c: /* DISPC_VID2_ATTRIBUTES */
+ case 0x160: /* DISPC_VID2_FIFO_TRESHOLD */
+ case 0x164: /* DISPC_VID2_FIFO_SIZE_STATUS */
+ case 0x168: /* DISPC_VID2_ROW_INC */
+ case 0x16c: /* DISPC_VID2_PIXEL_INC */
+ case 0x170: /* DISPC_VID2_FIR */
+ case 0x174: /* DISPC_VID2_PICTURE_SIZE */
+ case 0x178: /* DISPC_VID2_ACCU0 */
+ case 0x17c: /* DISPC_VID2_ACCU1 */
+ case 0x180 ... 0x1d0: /* DISPC_VID2_FIR_COEF, DISPC_VID2_CONV_COEF */
+ case 0x1d4: /* DISPC_DATA_CYCLE1 */
+ case 0x1d8: /* DISPC_DATA_CYCLE2 */
+ case 0x1dc: /* DISPC_DATA_CYCLE3 */
+ return 0;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_disc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x010: /* DISPC_SYSCONFIG */
+ if (value & 2) /* SOFTRESET */
+ omap_dss_reset(s);
+ s->dispc.idlemode = value & 0x301b;
+ break;
+
+ case 0x018: /* DISPC_IRQSTATUS */
+ s->dispc.irqst &= ~value;
+ omap_dispc_interrupt_update(s);
+ break;
+
+ case 0x01c: /* DISPC_IRQENABLE */
+ s->dispc.irqen = value & 0xffff;
+ omap_dispc_interrupt_update(s);
+ break;
+
+ case 0x040: /* DISPC_CONTROL */
+ s->dispc.control = value & 0x07ff9fff;
+ s->dig.enable = (value >> 1) & 1;
+ s->lcd.enable = (value >> 0) & 1;
+ if (value & (1 << 12)) /* OVERLAY_OPTIMIZATION */
+ if (!((s->dispc.l[1].attr | s->dispc.l[2].attr) & 1)) {
+ fprintf(stderr, "%s: Overlay Optimization when no overlay "
+ "region effectively exists leads to "
+ "unpredictable behaviour!\n", __func__);
+ }
+ if (value & (1 << 6)) { /* GODIGITAL */
+ /* XXX: Shadowed fields are:
+ * s->dispc.config
+ * s->dispc.capable
+ * s->dispc.bg[0]
+ * s->dispc.bg[1]
+ * s->dispc.trans[0]
+ * s->dispc.trans[1]
+ * s->dispc.line
+ * s->dispc.timing[0]
+ * s->dispc.timing[1]
+ * s->dispc.timing[2]
+ * s->dispc.timing[3]
+ * s->lcd.nx
+ * s->lcd.ny
+ * s->dig.nx
+ * s->dig.ny
+ * s->dispc.l[0].addr[0]
+ * s->dispc.l[0].addr[1]
+ * s->dispc.l[0].addr[2]
+ * s->dispc.l[0].posx
+ * s->dispc.l[0].posy
+ * s->dispc.l[0].nx
+ * s->dispc.l[0].ny
+ * s->dispc.l[0].tresh
+ * s->dispc.l[0].rowinc
+ * s->dispc.l[0].colinc
+ * s->dispc.l[0].wininc
+ * All they need to be loaded here from their shadow registers.
+ */
+ }
+ if (value & (1 << 5)) { /* GOLCD */
+ /* XXX: Likewise for LCD here. */
+ }
+ s->dispc.invalidate = 1;
+ break;
+
+ case 0x044: /* DISPC_CONFIG */
+ s->dispc.config = value & 0x3fff;
+ /* XXX:
+ * bits 2:1 (LOADMODE) reset to 0 after set to 1 and palette loaded
+ * bits 2:1 (LOADMODE) reset to 2 after set to 3 and palette loaded
+ */
+ s->dispc.invalidate = 1;
+ break;
+
+ case 0x048: /* DISPC_CAPABLE */
+ s->dispc.capable = value & 0x3ff;
+ break;
+
+ case 0x04c: /* DISPC_DEFAULT_COLOR0 */
+ s->dispc.bg[0] = value & 0xffffff;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x050: /* DISPC_DEFAULT_COLOR1 */
+ s->dispc.bg[1] = value & 0xffffff;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x054: /* DISPC_TRANS_COLOR0 */
+ s->dispc.trans[0] = value & 0xffffff;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x058: /* DISPC_TRANS_COLOR1 */
+ s->dispc.trans[1] = value & 0xffffff;
+ s->dispc.invalidate = 1;
+ break;
+
+ case 0x060: /* DISPC_LINE_NUMBER */
+ s->dispc.line = value & 0x7ff;
+ break;
+
+ case 0x064: /* DISPC_TIMING_H */
+ s->dispc.timing[0] = value & 0x0ff0ff3f;
+ break;
+ case 0x068: /* DISPC_TIMING_V */
+ s->dispc.timing[1] = value & 0x0ff0ff3f;
+ break;
+ case 0x06c: /* DISPC_POL_FREQ */
+ s->dispc.timing[2] = value & 0x0003ffff;
+ break;
+ case 0x070: /* DISPC_DIVISOR */
+ s->dispc.timing[3] = value & 0x00ff00ff;
+ break;
+
+ case 0x078: /* DISPC_SIZE_DIG */
+ s->dig.nx = ((value >> 0) & 0x7ff) + 1; /* PPL */
+ s->dig.ny = ((value >> 16) & 0x7ff) + 1; /* LPP */
+ s->dispc.invalidate = 1;
+ break;
+ case 0x07c: /* DISPC_SIZE_LCD */
+ s->lcd.nx = ((value >> 0) & 0x7ff) + 1; /* PPL */
+ s->lcd.ny = ((value >> 16) & 0x7ff) + 1; /* LPP */
+ s->dispc.invalidate = 1;
+ break;
+ case 0x080: /* DISPC_GFX_BA0 */
+ s->dispc.l[0].addr[0] = (hwaddr) value;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x084: /* DISPC_GFX_BA1 */
+ s->dispc.l[0].addr[1] = (hwaddr) value;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x088: /* DISPC_GFX_POSITION */
+ s->dispc.l[0].posx = ((value >> 0) & 0x7ff); /* GFXPOSX */
+ s->dispc.l[0].posy = ((value >> 16) & 0x7ff); /* GFXPOSY */
+ s->dispc.invalidate = 1;
+ break;
+ case 0x08c: /* DISPC_GFX_SIZE */
+ s->dispc.l[0].nx = ((value >> 0) & 0x7ff) + 1; /* GFXSIZEX */
+ s->dispc.l[0].ny = ((value >> 16) & 0x7ff) + 1; /* GFXSIZEY */
+ s->dispc.invalidate = 1;
+ break;
+ case 0x0a0: /* DISPC_GFX_ATTRIBUTES */
+ s->dispc.l[0].attr = value & 0x7ff;
+ if (value & (3 << 9))
+ fprintf(stderr, "%s: Big-endian pixel format not supported\n",
+ __FUNCTION__);
+ s->dispc.l[0].enable = value & 1;
+ s->dispc.l[0].bpp = (value >> 1) & 0xf;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x0a4: /* DISPC_GFX_FIFO_TRESHOLD */
+ s->dispc.l[0].tresh = value & 0x01ff01ff;
+ break;
+ case 0x0ac: /* DISPC_GFX_ROW_INC */
+ s->dispc.l[0].rowinc = value;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x0b0: /* DISPC_GFX_PIXEL_INC */
+ s->dispc.l[0].colinc = value;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x0b4: /* DISPC_GFX_WINDOW_SKIP */
+ s->dispc.l[0].wininc = value;
+ break;
+ case 0x0b8: /* DISPC_GFX_TABLE_BA */
+ s->dispc.l[0].addr[2] = (hwaddr) value;
+ s->dispc.invalidate = 1;
+ break;
+
+ case 0x0bc: /* DISPC_VID1_BA0 */
+ case 0x0c0: /* DISPC_VID1_BA1 */
+ case 0x0c4: /* DISPC_VID1_POSITION */
+ case 0x0c8: /* DISPC_VID1_SIZE */
+ case 0x0cc: /* DISPC_VID1_ATTRIBUTES */
+ case 0x0d0: /* DISPC_VID1_FIFO_TRESHOLD */
+ case 0x0d8: /* DISPC_VID1_ROW_INC */
+ case 0x0dc: /* DISPC_VID1_PIXEL_INC */
+ case 0x0e0: /* DISPC_VID1_FIR */
+ case 0x0e4: /* DISPC_VID1_PICTURE_SIZE */
+ case 0x0e8: /* DISPC_VID1_ACCU0 */
+ case 0x0ec: /* DISPC_VID1_ACCU1 */
+ case 0x0f0 ... 0x140: /* DISPC_VID1_FIR_COEF, DISPC_VID1_CONV_COEF */
+ case 0x14c: /* DISPC_VID2_BA0 */
+ case 0x150: /* DISPC_VID2_BA1 */
+ case 0x154: /* DISPC_VID2_POSITION */
+ case 0x158: /* DISPC_VID2_SIZE */
+ case 0x15c: /* DISPC_VID2_ATTRIBUTES */
+ case 0x160: /* DISPC_VID2_FIFO_TRESHOLD */
+ case 0x168: /* DISPC_VID2_ROW_INC */
+ case 0x16c: /* DISPC_VID2_PIXEL_INC */
+ case 0x170: /* DISPC_VID2_FIR */
+ case 0x174: /* DISPC_VID2_PICTURE_SIZE */
+ case 0x178: /* DISPC_VID2_ACCU0 */
+ case 0x17c: /* DISPC_VID2_ACCU1 */
+ case 0x180 ... 0x1d0: /* DISPC_VID2_FIR_COEF, DISPC_VID2_CONV_COEF */
+ case 0x1d4: /* DISPC_DATA_CYCLE1 */
+ case 0x1d8: /* DISPC_DATA_CYCLE2 */
+ case 0x1dc: /* DISPC_DATA_CYCLE3 */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_disc_ops = {
+ .read = omap_disc_read,
+ .write = omap_disc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_rfbi_transfer_stop(struct omap_dss_s *s)
+{
+ if (!s->rfbi.busy)
+ return;
+
+ /* TODO: in non-Bypass mode we probably need to just deassert the DRQ. */
+
+ s->rfbi.busy = 0;
+}
+
+static void omap_rfbi_transfer_start(struct omap_dss_s *s)
+{
+ void *data;
+ hwaddr len;
+ hwaddr data_addr;
+ int pitch;
+ static void *bounce_buffer;
+ static hwaddr bounce_len;
+
+ if (!s->rfbi.enable || s->rfbi.busy)
+ return;
+
+ if (s->rfbi.control & (1 << 1)) { /* BYPASS */
+ /* TODO: in non-Bypass mode we probably need to just assert the
+ * DRQ and wait for DMA to write the pixels. */
+ fprintf(stderr, "%s: Bypass mode unimplemented\n", __FUNCTION__);
+ return;
+ }
+
+ if (!(s->dispc.control & (1 << 11))) /* RFBIMODE */
+ return;
+ /* TODO: check that LCD output is enabled in DISPC. */
+
+ s->rfbi.busy = 1;
+
+ len = s->rfbi.pixels * 2;
+
+ data_addr = s->dispc.l[0].addr[0];
+ data = cpu_physical_memory_map(data_addr, &len, 0);
+ if (data && len != s->rfbi.pixels * 2) {
+ cpu_physical_memory_unmap(data, len, 0, 0);
+ data = NULL;
+ len = s->rfbi.pixels * 2;
+ }
+ if (!data) {
+ if (len > bounce_len) {
+ bounce_buffer = g_realloc(bounce_buffer, len);
+ }
+ data = bounce_buffer;
+ cpu_physical_memory_read(data_addr, data, len);
+ }
+
+ /* TODO bpp */
+ s->rfbi.pixels = 0;
+
+ /* TODO: negative values */
+ pitch = s->dispc.l[0].nx + (s->dispc.l[0].rowinc - 1) / 2;
+
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.chip[0]->block(s->rfbi.chip[0]->opaque, 1, data, len, pitch);
+ if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.chip[1]->block(s->rfbi.chip[1]->opaque, 1, data, len, pitch);
+
+ if (data != bounce_buffer) {
+ cpu_physical_memory_unmap(data, len, 0, len);
+ }
+
+ omap_rfbi_transfer_stop(s);
+
+ /* TODO */
+ s->dispc.irqst |= 1; /* FRAMEDONE */
+ omap_dispc_interrupt_update(s);
+}
+
+static uint64_t omap_rfbi_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* RFBI_REVISION */
+ return 0x10;
+
+ case 0x10: /* RFBI_SYSCONFIG */
+ return s->rfbi.idlemode;
+
+ case 0x14: /* RFBI_SYSSTATUS */
+ return 1 | (s->rfbi.busy << 8); /* RESETDONE */
+
+ case 0x40: /* RFBI_CONTROL */
+ return s->rfbi.control;
+
+ case 0x44: /* RFBI_PIXELCNT */
+ return s->rfbi.pixels;
+
+ case 0x48: /* RFBI_LINE_NUMBER */
+ return s->rfbi.skiplines;
+
+ case 0x58: /* RFBI_READ */
+ case 0x5c: /* RFBI_STATUS */
+ return s->rfbi.rxbuf;
+
+ case 0x60: /* RFBI_CONFIG0 */
+ return s->rfbi.config[0];
+ case 0x64: /* RFBI_ONOFF_TIME0 */
+ return s->rfbi.time[0];
+ case 0x68: /* RFBI_CYCLE_TIME0 */
+ return s->rfbi.time[1];
+ case 0x6c: /* RFBI_DATA_CYCLE1_0 */
+ return s->rfbi.data[0];
+ case 0x70: /* RFBI_DATA_CYCLE2_0 */
+ return s->rfbi.data[1];
+ case 0x74: /* RFBI_DATA_CYCLE3_0 */
+ return s->rfbi.data[2];
+
+ case 0x78: /* RFBI_CONFIG1 */
+ return s->rfbi.config[1];
+ case 0x7c: /* RFBI_ONOFF_TIME1 */
+ return s->rfbi.time[2];
+ case 0x80: /* RFBI_CYCLE_TIME1 */
+ return s->rfbi.time[3];
+ case 0x84: /* RFBI_DATA_CYCLE1_1 */
+ return s->rfbi.data[3];
+ case 0x88: /* RFBI_DATA_CYCLE2_1 */
+ return s->rfbi.data[4];
+ case 0x8c: /* RFBI_DATA_CYCLE3_1 */
+ return s->rfbi.data[5];
+
+ case 0x90: /* RFBI_VSYNC_WIDTH */
+ return s->rfbi.vsync;
+ case 0x94: /* RFBI_HSYNC_WIDTH */
+ return s->rfbi.hsync;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_rfbi_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x10: /* RFBI_SYSCONFIG */
+ if (value & 2) /* SOFTRESET */
+ omap_rfbi_reset(s);
+ s->rfbi.idlemode = value & 0x19;
+ break;
+
+ case 0x40: /* RFBI_CONTROL */
+ s->rfbi.control = value & 0xf;
+ s->rfbi.enable = value & 1;
+ if (value & (1 << 4) && /* ITE */
+ !(s->rfbi.config[0] & s->rfbi.config[1] & 0xc))
+ omap_rfbi_transfer_start(s);
+ break;
+
+ case 0x44: /* RFBI_PIXELCNT */
+ s->rfbi.pixels = value;
+ break;
+
+ case 0x48: /* RFBI_LINE_NUMBER */
+ s->rfbi.skiplines = value & 0x7ff;
+ break;
+
+ case 0x4c: /* RFBI_CMD */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 0, value & 0xffff);
+ if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 0, value & 0xffff);
+ break;
+ case 0x50: /* RFBI_PARAM */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value & 0xffff);
+ if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value & 0xffff);
+ break;
+ case 0x54: /* RFBI_DATA */
+ /* TODO: take into account the format set up in s->rfbi.config[?] and
+ * s->rfbi.data[?], but special-case the most usual scenario so that
+ * speed doesn't suffer. */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) {
+ s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value & 0xffff);
+ s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value >> 16);
+ }
+ if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) {
+ s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value & 0xffff);
+ s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value >> 16);
+ }
+ if (!-- s->rfbi.pixels)
+ omap_rfbi_transfer_stop(s);
+ break;
+ case 0x58: /* RFBI_READ */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 1);
+ else if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.rxbuf = s->rfbi.chip[1]->read(s->rfbi.chip[1]->opaque, 1);
+ if (!-- s->rfbi.pixels)
+ omap_rfbi_transfer_stop(s);
+ break;
+
+ case 0x5c: /* RFBI_STATUS */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 0);
+ else if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.rxbuf = s->rfbi.chip[1]->read(s->rfbi.chip[1]->opaque, 0);
+ if (!-- s->rfbi.pixels)
+ omap_rfbi_transfer_stop(s);
+ break;
+
+ case 0x60: /* RFBI_CONFIG0 */
+ s->rfbi.config[0] = value & 0x003f1fff;
+ break;
+
+ case 0x64: /* RFBI_ONOFF_TIME0 */
+ s->rfbi.time[0] = value & 0x3fffffff;
+ break;
+ case 0x68: /* RFBI_CYCLE_TIME0 */
+ s->rfbi.time[1] = value & 0x0fffffff;
+ break;
+ case 0x6c: /* RFBI_DATA_CYCLE1_0 */
+ s->rfbi.data[0] = value & 0x0f1f0f1f;
+ break;
+ case 0x70: /* RFBI_DATA_CYCLE2_0 */
+ s->rfbi.data[1] = value & 0x0f1f0f1f;
+ break;
+ case 0x74: /* RFBI_DATA_CYCLE3_0 */
+ s->rfbi.data[2] = value & 0x0f1f0f1f;
+ break;
+ case 0x78: /* RFBI_CONFIG1 */
+ s->rfbi.config[1] = value & 0x003f1fff;
+ break;
+
+ case 0x7c: /* RFBI_ONOFF_TIME1 */
+ s->rfbi.time[2] = value & 0x3fffffff;
+ break;
+ case 0x80: /* RFBI_CYCLE_TIME1 */
+ s->rfbi.time[3] = value & 0x0fffffff;
+ break;
+ case 0x84: /* RFBI_DATA_CYCLE1_1 */
+ s->rfbi.data[3] = value & 0x0f1f0f1f;
+ break;
+ case 0x88: /* RFBI_DATA_CYCLE2_1 */
+ s->rfbi.data[4] = value & 0x0f1f0f1f;
+ break;
+ case 0x8c: /* RFBI_DATA_CYCLE3_1 */
+ s->rfbi.data[5] = value & 0x0f1f0f1f;
+ break;
+
+ case 0x90: /* RFBI_VSYNC_WIDTH */
+ s->rfbi.vsync = value & 0xffff;
+ break;
+ case 0x94: /* RFBI_HSYNC_WIDTH */
+ s->rfbi.hsync = value & 0xffff;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_rfbi_ops = {
+ .read = omap_rfbi_read,
+ .write = omap_rfbi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t omap_venc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* REV_ID */
+ case 0x04: /* STATUS */
+ case 0x08: /* F_CONTROL */
+ case 0x10: /* VIDOUT_CTRL */
+ case 0x14: /* SYNC_CTRL */
+ case 0x1c: /* LLEN */
+ case 0x20: /* FLENS */
+ case 0x24: /* HFLTR_CTRL */
+ case 0x28: /* CC_CARR_WSS_CARR */
+ case 0x2c: /* C_PHASE */
+ case 0x30: /* GAIN_U */
+ case 0x34: /* GAIN_V */
+ case 0x38: /* GAIN_Y */
+ case 0x3c: /* BLACK_LEVEL */
+ case 0x40: /* BLANK_LEVEL */
+ case 0x44: /* X_COLOR */
+ case 0x48: /* M_CONTROL */
+ case 0x4c: /* BSTAMP_WSS_DATA */
+ case 0x50: /* S_CARR */
+ case 0x54: /* LINE21 */
+ case 0x58: /* LN_SEL */
+ case 0x5c: /* L21__WC_CTL */
+ case 0x60: /* HTRIGGER_VTRIGGER */
+ case 0x64: /* SAVID__EAVID */
+ case 0x68: /* FLEN__FAL */
+ case 0x6c: /* LAL__PHASE_RESET */
+ case 0x70: /* HS_INT_START_STOP_X */
+ case 0x74: /* HS_EXT_START_STOP_X */
+ case 0x78: /* VS_INT_START_X */
+ case 0x7c: /* VS_INT_STOP_X__VS_INT_START_Y */
+ case 0x80: /* VS_INT_STOP_Y__VS_INT_START_X */
+ case 0x84: /* VS_EXT_STOP_X__VS_EXT_START_Y */
+ case 0x88: /* VS_EXT_STOP_Y */
+ case 0x90: /* AVID_START_STOP_X */
+ case 0x94: /* AVID_START_STOP_Y */
+ case 0xa0: /* FID_INT_START_X__FID_INT_START_Y */
+ case 0xa4: /* FID_INT_OFFSET_Y__FID_EXT_START_X */
+ case 0xa8: /* FID_EXT_START_Y__FID_EXT_OFFSET_Y */
+ case 0xb0: /* TVDETGP_INT_START_STOP_X */
+ case 0xb4: /* TVDETGP_INT_START_STOP_Y */
+ case 0xb8: /* GEN_CTRL */
+ case 0xc4: /* DAC_TST__DAC_A */
+ case 0xc8: /* DAC_B__DAC_C */
+ return 0;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_venc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, size);
+ return;
+ }
+
+ switch (addr) {
+ case 0x08: /* F_CONTROL */
+ case 0x10: /* VIDOUT_CTRL */
+ case 0x14: /* SYNC_CTRL */
+ case 0x1c: /* LLEN */
+ case 0x20: /* FLENS */
+ case 0x24: /* HFLTR_CTRL */
+ case 0x28: /* CC_CARR_WSS_CARR */
+ case 0x2c: /* C_PHASE */
+ case 0x30: /* GAIN_U */
+ case 0x34: /* GAIN_V */
+ case 0x38: /* GAIN_Y */
+ case 0x3c: /* BLACK_LEVEL */
+ case 0x40: /* BLANK_LEVEL */
+ case 0x44: /* X_COLOR */
+ case 0x48: /* M_CONTROL */
+ case 0x4c: /* BSTAMP_WSS_DATA */
+ case 0x50: /* S_CARR */
+ case 0x54: /* LINE21 */
+ case 0x58: /* LN_SEL */
+ case 0x5c: /* L21__WC_CTL */
+ case 0x60: /* HTRIGGER_VTRIGGER */
+ case 0x64: /* SAVID__EAVID */
+ case 0x68: /* FLEN__FAL */
+ case 0x6c: /* LAL__PHASE_RESET */
+ case 0x70: /* HS_INT_START_STOP_X */
+ case 0x74: /* HS_EXT_START_STOP_X */
+ case 0x78: /* VS_INT_START_X */
+ case 0x7c: /* VS_INT_STOP_X__VS_INT_START_Y */
+ case 0x80: /* VS_INT_STOP_Y__VS_INT_START_X */
+ case 0x84: /* VS_EXT_STOP_X__VS_EXT_START_Y */
+ case 0x88: /* VS_EXT_STOP_Y */
+ case 0x90: /* AVID_START_STOP_X */
+ case 0x94: /* AVID_START_STOP_Y */
+ case 0xa0: /* FID_INT_START_X__FID_INT_START_Y */
+ case 0xa4: /* FID_INT_OFFSET_Y__FID_EXT_START_X */
+ case 0xa8: /* FID_EXT_START_Y__FID_EXT_OFFSET_Y */
+ case 0xb0: /* TVDETGP_INT_START_STOP_X */
+ case 0xb4: /* TVDETGP_INT_START_STOP_Y */
+ case 0xb8: /* GEN_CTRL */
+ case 0xc4: /* DAC_TST__DAC_A */
+ case 0xc8: /* DAC_B__DAC_C */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_venc_ops = {
+ .read = omap_venc_read,
+ .write = omap_venc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t omap_im3_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x0a8: /* SBIMERRLOGA */
+ case 0x0b0: /* SBIMERRLOG */
+ case 0x190: /* SBIMSTATE */
+ case 0x198: /* SBTMSTATE_L */
+ case 0x19c: /* SBTMSTATE_H */
+ case 0x1a8: /* SBIMCONFIG_L */
+ case 0x1ac: /* SBIMCONFIG_H */
+ case 0x1f8: /* SBID_L */
+ case 0x1fc: /* SBID_H */
+ return 0;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_im3_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x0b0: /* SBIMERRLOG */
+ case 0x190: /* SBIMSTATE */
+ case 0x198: /* SBTMSTATE_L */
+ case 0x19c: /* SBTMSTATE_H */
+ case 0x1a8: /* SBIMCONFIG_L */
+ case 0x1ac: /* SBIMCONFIG_H */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_im3_ops = {
+ .read = omap_im3_read,
+ .write = omap_im3_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_dss_s *omap_dss_init(struct omap_target_agent_s *ta,
+ MemoryRegion *sysmem,
+ hwaddr l3_base,
+ qemu_irq irq, qemu_irq drq,
+ omap_clk fck1, omap_clk fck2, omap_clk ck54m,
+ omap_clk ick1, omap_clk ick2)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *)
+ g_malloc0(sizeof(struct omap_dss_s));
+
+ s->irq = irq;
+ s->drq = drq;
+ omap_dss_reset(s);
+
+ memory_region_init_io(&s->iomem_diss1, NULL, &omap_diss_ops, s, "omap.diss1",
+ omap_l4_region_size(ta, 0));
+ memory_region_init_io(&s->iomem_disc1, NULL, &omap_disc_ops, s, "omap.disc1",
+ omap_l4_region_size(ta, 1));
+ memory_region_init_io(&s->iomem_rfbi1, NULL, &omap_rfbi_ops, s, "omap.rfbi1",
+ omap_l4_region_size(ta, 2));
+ memory_region_init_io(&s->iomem_venc1, NULL, &omap_venc_ops, s, "omap.venc1",
+ omap_l4_region_size(ta, 3));
+ memory_region_init_io(&s->iomem_im3, NULL, &omap_im3_ops, s,
+ "omap.im3", 0x1000);
+
+ omap_l4_attach(ta, 0, &s->iomem_diss1);
+ omap_l4_attach(ta, 1, &s->iomem_disc1);
+ omap_l4_attach(ta, 2, &s->iomem_rfbi1);
+ omap_l4_attach(ta, 3, &s->iomem_venc1);
+ memory_region_add_subregion(sysmem, l3_base, &s->iomem_im3);
+
+#if 0
+ s->state = graphic_console_init(omap_update_display,
+ omap_invalidate_display, omap_screen_dump, s);
+#endif
+
+ return s;
+}
+
+void omap_rfbi_attach(struct omap_dss_s *s, int cs, struct rfbi_chip_s *chip)
+{
+ if (cs < 0 || cs > 1)
+ hw_error("%s: wrong CS %i\n", __FUNCTION__, cs);
+ s->rfbi.chip[cs] = chip;
+}
diff --git a/hw/display/omap_lcd_template.h b/hw/display/omap_lcd_template.h
new file mode 100644
index 00000000..e5dd4471
--- /dev/null
+++ b/hw/display/omap_lcd_template.h
@@ -0,0 +1,175 @@
+/*
+ * QEMU OMAP LCD Emulator templates
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if DEPTH == 8
+# define BPP 1
+# define PIXEL_TYPE uint8_t
+#elif DEPTH == 15 || DEPTH == 16
+# define BPP 2
+# define PIXEL_TYPE uint16_t
+#elif DEPTH == 32
+# define BPP 4
+# define PIXEL_TYPE uint32_t
+#else
+# error unsupport depth
+#endif
+
+/*
+ * 2-bit colour
+ */
+static void glue(draw_line2_, DEPTH)(void *opaque,
+ uint8_t *d, const uint8_t *s, int width, int deststep)
+{
+ uint16_t *pal = opaque;
+ uint8_t v, r, g, b;
+
+ do {
+ v = ldub_p((void *) s);
+ r = (pal[v & 3] >> 4) & 0xf0;
+ g = pal[v & 3] & 0xf0;
+ b = (pal[v & 3] << 4) & 0xf0;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ d += BPP;
+ v >>= 2;
+ r = (pal[v & 3] >> 4) & 0xf0;
+ g = pal[v & 3] & 0xf0;
+ b = (pal[v & 3] << 4) & 0xf0;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ d += BPP;
+ v >>= 2;
+ r = (pal[v & 3] >> 4) & 0xf0;
+ g = pal[v & 3] & 0xf0;
+ b = (pal[v & 3] << 4) & 0xf0;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ d += BPP;
+ v >>= 2;
+ r = (pal[v & 3] >> 4) & 0xf0;
+ g = pal[v & 3] & 0xf0;
+ b = (pal[v & 3] << 4) & 0xf0;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ d += BPP;
+ s ++;
+ width -= 4;
+ } while (width > 0);
+}
+
+/*
+ * 4-bit colour
+ */
+static void glue(draw_line4_, DEPTH)(void *opaque,
+ uint8_t *d, const uint8_t *s, int width, int deststep)
+{
+ uint16_t *pal = opaque;
+ uint8_t v, r, g, b;
+
+ do {
+ v = ldub_p((void *) s);
+ r = (pal[v & 0xf] >> 4) & 0xf0;
+ g = pal[v & 0xf] & 0xf0;
+ b = (pal[v & 0xf] << 4) & 0xf0;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ d += BPP;
+ v >>= 4;
+ r = (pal[v & 0xf] >> 4) & 0xf0;
+ g = pal[v & 0xf] & 0xf0;
+ b = (pal[v & 0xf] << 4) & 0xf0;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ d += BPP;
+ s ++;
+ width -= 2;
+ } while (width > 0);
+}
+
+/*
+ * 8-bit colour
+ */
+static void glue(draw_line8_, DEPTH)(void *opaque,
+ uint8_t *d, const uint8_t *s, int width, int deststep)
+{
+ uint16_t *pal = opaque;
+ uint8_t v, r, g, b;
+
+ do {
+ v = ldub_p((void *) s);
+ r = (pal[v] >> 4) & 0xf0;
+ g = pal[v] & 0xf0;
+ b = (pal[v] << 4) & 0xf0;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ s ++;
+ d += BPP;
+ } while (-- width != 0);
+}
+
+/*
+ * 12-bit colour
+ */
+static void glue(draw_line12_, DEPTH)(void *opaque,
+ uint8_t *d, const uint8_t *s, int width, int deststep)
+{
+ uint16_t v;
+ uint8_t r, g, b;
+
+ do {
+ v = lduw_p((void *) s);
+ r = (v >> 4) & 0xf0;
+ g = v & 0xf0;
+ b = (v << 4) & 0xf0;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ s += 2;
+ d += BPP;
+ } while (-- width != 0);
+}
+
+/*
+ * 16-bit colour
+ */
+static void glue(draw_line16_, DEPTH)(void *opaque,
+ uint8_t *d, const uint8_t *s, int width, int deststep)
+{
+#if DEPTH == 16 && defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN)
+ memcpy(d, s, width * 2);
+#else
+ uint16_t v;
+ uint8_t r, g, b;
+
+ do {
+ v = lduw_p((void *) s);
+ r = (v >> 8) & 0xf8;
+ g = (v >> 3) & 0xfc;
+ b = (v << 3) & 0xf8;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b);
+ s += 2;
+ d += BPP;
+ } while (-- width != 0);
+#endif
+}
+
+#undef DEPTH
+#undef BPP
+#undef PIXEL_TYPE
diff --git a/hw/display/omap_lcdc.c b/hw/display/omap_lcdc.c
new file mode 100644
index 00000000..a7c6cd79
--- /dev/null
+++ b/hw/display/omap_lcdc.c
@@ -0,0 +1,420 @@
+/*
+ * OMAP LCD controller.
+ *
+ * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/arm/omap.h"
+#include "framebuffer.h"
+#include "ui/pixel_ops.h"
+
+struct omap_lcd_panel_s {
+ MemoryRegion *sysmem;
+ MemoryRegion iomem;
+ MemoryRegionSection fbsection;
+ qemu_irq irq;
+ QemuConsole *con;
+
+ int plm;
+ int tft;
+ int mono;
+ int enable;
+ int width;
+ int height;
+ int interrupts;
+ uint32_t timing[3];
+ uint32_t subpanel;
+ uint32_t ctrl;
+
+ struct omap_dma_lcd_channel_s *dma;
+ uint16_t palette[256];
+ int palette_done;
+ int frame_done;
+ int invalidate;
+ int sync_error;
+};
+
+static void omap_lcd_interrupts(struct omap_lcd_panel_s *s)
+{
+ if (s->frame_done && (s->interrupts & 1)) {
+ qemu_irq_raise(s->irq);
+ return;
+ }
+
+ if (s->palette_done && (s->interrupts & 2)) {
+ qemu_irq_raise(s->irq);
+ return;
+ }
+
+ if (s->sync_error) {
+ qemu_irq_raise(s->irq);
+ return;
+ }
+
+ qemu_irq_lower(s->irq);
+}
+
+#define draw_line_func drawfn
+
+#define DEPTH 8
+#include "omap_lcd_template.h"
+#define DEPTH 15
+#include "omap_lcd_template.h"
+#define DEPTH 16
+#include "omap_lcd_template.h"
+#define DEPTH 32
+#include "omap_lcd_template.h"
+
+static draw_line_func draw_line_table2[33] = {
+ [0 ... 32] = NULL,
+ [8] = draw_line2_8,
+ [15] = draw_line2_15,
+ [16] = draw_line2_16,
+ [32] = draw_line2_32,
+}, draw_line_table4[33] = {
+ [0 ... 32] = NULL,
+ [8] = draw_line4_8,
+ [15] = draw_line4_15,
+ [16] = draw_line4_16,
+ [32] = draw_line4_32,
+}, draw_line_table8[33] = {
+ [0 ... 32] = NULL,
+ [8] = draw_line8_8,
+ [15] = draw_line8_15,
+ [16] = draw_line8_16,
+ [32] = draw_line8_32,
+}, draw_line_table12[33] = {
+ [0 ... 32] = NULL,
+ [8] = draw_line12_8,
+ [15] = draw_line12_15,
+ [16] = draw_line12_16,
+ [32] = draw_line12_32,
+}, draw_line_table16[33] = {
+ [0 ... 32] = NULL,
+ [8] = draw_line16_8,
+ [15] = draw_line16_15,
+ [16] = draw_line16_16,
+ [32] = draw_line16_32,
+};
+
+static void omap_update_display(void *opaque)
+{
+ struct omap_lcd_panel_s *omap_lcd = (struct omap_lcd_panel_s *) opaque;
+ DisplaySurface *surface = qemu_console_surface(omap_lcd->con);
+ draw_line_func draw_line;
+ int size, height, first, last;
+ int width, linesize, step, bpp, frame_offset;
+ hwaddr frame_base;
+
+ if (!omap_lcd || omap_lcd->plm == 1 || !omap_lcd->enable ||
+ !surface_bits_per_pixel(surface)) {
+ return;
+ }
+
+ frame_offset = 0;
+ if (omap_lcd->plm != 2) {
+ cpu_physical_memory_read(omap_lcd->dma->phys_framebuffer[
+ omap_lcd->dma->current_frame],
+ (void *)omap_lcd->palette, 0x200);
+ switch (omap_lcd->palette[0] >> 12 & 7) {
+ case 3 ... 7:
+ frame_offset += 0x200;
+ break;
+ default:
+ frame_offset += 0x20;
+ }
+ }
+
+ /* Colour depth */
+ switch ((omap_lcd->palette[0] >> 12) & 7) {
+ case 1:
+ draw_line = draw_line_table2[surface_bits_per_pixel(surface)];
+ bpp = 2;
+ break;
+
+ case 2:
+ draw_line = draw_line_table4[surface_bits_per_pixel(surface)];
+ bpp = 4;
+ break;
+
+ case 3:
+ draw_line = draw_line_table8[surface_bits_per_pixel(surface)];
+ bpp = 8;
+ break;
+
+ case 4 ... 7:
+ if (!omap_lcd->tft)
+ draw_line = draw_line_table12[surface_bits_per_pixel(surface)];
+ else
+ draw_line = draw_line_table16[surface_bits_per_pixel(surface)];
+ bpp = 16;
+ break;
+
+ default:
+ /* Unsupported at the moment. */
+ return;
+ }
+
+ /* Resolution */
+ width = omap_lcd->width;
+ if (width != surface_width(surface) ||
+ omap_lcd->height != surface_height(surface)) {
+ qemu_console_resize(omap_lcd->con,
+ omap_lcd->width, omap_lcd->height);
+ surface = qemu_console_surface(omap_lcd->con);
+ omap_lcd->invalidate = 1;
+ }
+
+ if (omap_lcd->dma->current_frame == 0)
+ size = omap_lcd->dma->src_f1_bottom - omap_lcd->dma->src_f1_top;
+ else
+ size = omap_lcd->dma->src_f2_bottom - omap_lcd->dma->src_f2_top;
+
+ if (frame_offset + ((width * omap_lcd->height * bpp) >> 3) > size + 2) {
+ omap_lcd->sync_error = 1;
+ omap_lcd_interrupts(omap_lcd);
+ omap_lcd->enable = 0;
+ return;
+ }
+
+ /* Content */
+ frame_base = omap_lcd->dma->phys_framebuffer[
+ omap_lcd->dma->current_frame] + frame_offset;
+ omap_lcd->dma->condition |= 1 << omap_lcd->dma->current_frame;
+ if (omap_lcd->dma->interrupts & 1)
+ qemu_irq_raise(omap_lcd->dma->irq);
+ if (omap_lcd->dma->dual)
+ omap_lcd->dma->current_frame ^= 1;
+
+ if (!surface_bits_per_pixel(surface)) {
+ return;
+ }
+
+ first = 0;
+ height = omap_lcd->height;
+ if (omap_lcd->subpanel & (1 << 31)) {
+ if (omap_lcd->subpanel & (1 << 29))
+ first = (omap_lcd->subpanel >> 16) & 0x3ff;
+ else
+ height = (omap_lcd->subpanel >> 16) & 0x3ff;
+ /* TODO: fill the rest of the panel with DPD */
+ }
+
+ step = width * bpp >> 3;
+ linesize = surface_stride(surface);
+ if (omap_lcd->invalidate) {
+ framebuffer_update_memory_section(&omap_lcd->fbsection,
+ omap_lcd->sysmem, frame_base,
+ height, step);
+ }
+
+ framebuffer_update_display(surface, &omap_lcd->fbsection,
+ width, height,
+ step, linesize, 0,
+ omap_lcd->invalidate,
+ draw_line, omap_lcd->palette,
+ &first, &last);
+
+ if (first >= 0) {
+ dpy_gfx_update(omap_lcd->con, 0, first, width, last - first + 1);
+ }
+ omap_lcd->invalidate = 0;
+}
+
+static void omap_invalidate_display(void *opaque) {
+ struct omap_lcd_panel_s *omap_lcd = opaque;
+ omap_lcd->invalidate = 1;
+}
+
+static void omap_lcd_update(struct omap_lcd_panel_s *s) {
+ if (!s->enable) {
+ s->dma->current_frame = -1;
+ s->sync_error = 0;
+ if (s->plm != 1)
+ s->frame_done = 1;
+ omap_lcd_interrupts(s);
+ return;
+ }
+
+ if (s->dma->current_frame == -1) {
+ s->frame_done = 0;
+ s->palette_done = 0;
+ s->dma->current_frame = 0;
+ }
+
+ if (!s->dma->mpu->port[s->dma->src].addr_valid(s->dma->mpu,
+ s->dma->src_f1_top) ||
+ !s->dma->mpu->port[
+ s->dma->src].addr_valid(s->dma->mpu,
+ s->dma->src_f1_bottom) ||
+ (s->dma->dual &&
+ (!s->dma->mpu->port[
+ s->dma->src].addr_valid(s->dma->mpu,
+ s->dma->src_f2_top) ||
+ !s->dma->mpu->port[
+ s->dma->src].addr_valid(s->dma->mpu,
+ s->dma->src_f2_bottom)))) {
+ s->dma->condition |= 1 << 2;
+ if (s->dma->interrupts & (1 << 1))
+ qemu_irq_raise(s->dma->irq);
+ s->enable = 0;
+ return;
+ }
+
+ s->dma->phys_framebuffer[0] = s->dma->src_f1_top;
+ s->dma->phys_framebuffer[1] = s->dma->src_f2_top;
+
+ if (s->plm != 2 && !s->palette_done) {
+ cpu_physical_memory_read(
+ s->dma->phys_framebuffer[s->dma->current_frame],
+ (void *)s->palette, 0x200);
+ s->palette_done = 1;
+ omap_lcd_interrupts(s);
+ }
+}
+
+static uint64_t omap_lcdc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* LCD_CONTROL */
+ return (s->tft << 23) | (s->plm << 20) |
+ (s->tft << 7) | (s->interrupts << 3) |
+ (s->mono << 1) | s->enable | s->ctrl | 0xfe000c34;
+
+ case 0x04: /* LCD_TIMING0 */
+ return (s->timing[0] << 10) | (s->width - 1) | 0x0000000f;
+
+ case 0x08: /* LCD_TIMING1 */
+ return (s->timing[1] << 10) | (s->height - 1);
+
+ case 0x0c: /* LCD_TIMING2 */
+ return s->timing[2] | 0xfc000000;
+
+ case 0x10: /* LCD_STATUS */
+ return (s->palette_done << 6) | (s->sync_error << 2) | s->frame_done;
+
+ case 0x14: /* LCD_SUBPANEL */
+ return s->subpanel;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_lcdc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* LCD_CONTROL */
+ s->plm = (value >> 20) & 3;
+ s->tft = (value >> 7) & 1;
+ s->interrupts = (value >> 3) & 3;
+ s->mono = (value >> 1) & 1;
+ s->ctrl = value & 0x01cff300;
+ if (s->enable != (value & 1)) {
+ s->enable = value & 1;
+ omap_lcd_update(s);
+ }
+ break;
+
+ case 0x04: /* LCD_TIMING0 */
+ s->timing[0] = value >> 10;
+ s->width = (value & 0x3ff) + 1;
+ break;
+
+ case 0x08: /* LCD_TIMING1 */
+ s->timing[1] = value >> 10;
+ s->height = (value & 0x3ff) + 1;
+ break;
+
+ case 0x0c: /* LCD_TIMING2 */
+ s->timing[2] = value;
+ break;
+
+ case 0x10: /* LCD_STATUS */
+ break;
+
+ case 0x14: /* LCD_SUBPANEL */
+ s->subpanel = value & 0xa1ffffff;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_lcdc_ops = {
+ .read = omap_lcdc_read,
+ .write = omap_lcdc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void omap_lcdc_reset(struct omap_lcd_panel_s *s)
+{
+ s->dma->current_frame = -1;
+ s->plm = 0;
+ s->tft = 0;
+ s->mono = 0;
+ s->enable = 0;
+ s->width = 0;
+ s->height = 0;
+ s->interrupts = 0;
+ s->timing[0] = 0;
+ s->timing[1] = 0;
+ s->timing[2] = 0;
+ s->subpanel = 0;
+ s->palette_done = 0;
+ s->frame_done = 0;
+ s->sync_error = 0;
+ s->invalidate = 1;
+ s->subpanel = 0;
+ s->ctrl = 0;
+}
+
+static const GraphicHwOps omap_ops = {
+ .invalidate = omap_invalidate_display,
+ .gfx_update = omap_update_display,
+};
+
+struct omap_lcd_panel_s *omap_lcdc_init(MemoryRegion *sysmem,
+ hwaddr base,
+ qemu_irq irq,
+ struct omap_dma_lcd_channel_s *dma,
+ omap_clk clk)
+{
+ struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *)
+ g_malloc0(sizeof(struct omap_lcd_panel_s));
+
+ s->irq = irq;
+ s->dma = dma;
+ s->sysmem = sysmem;
+ omap_lcdc_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_lcdc_ops, s, "omap.lcdc", 0x100);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ s->con = graphic_console_init(NULL, 0, &omap_ops, s);
+
+ return s;
+}
diff --git a/hw/display/pl110.c b/hw/display/pl110.c
new file mode 100644
index 00000000..ef1a7b1a
--- /dev/null
+++ b/hw/display/pl110.c
@@ -0,0 +1,538 @@
+/*
+ * Arm PrimeCell PL110 Color LCD Controller
+ *
+ * Copyright (c) 2005-2009 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU LGPL
+ */
+
+#include "hw/sysbus.h"
+#include "ui/console.h"
+#include "framebuffer.h"
+#include "ui/pixel_ops.h"
+
+#define PL110_CR_EN 0x001
+#define PL110_CR_BGR 0x100
+#define PL110_CR_BEBO 0x200
+#define PL110_CR_BEPO 0x400
+#define PL110_CR_PWR 0x800
+
+enum pl110_bppmode
+{
+ BPP_1,
+ BPP_2,
+ BPP_4,
+ BPP_8,
+ BPP_16,
+ BPP_32,
+ BPP_16_565, /* PL111 only */
+ BPP_12 /* PL111 only */
+};
+
+
+/* The Versatile/PB uses a slightly modified PL110 controller. */
+enum pl110_version
+{
+ PL110,
+ PL110_VERSATILE,
+ PL111
+};
+
+#define TYPE_PL110 "pl110"
+#define PL110(obj) OBJECT_CHECK(PL110State, (obj), TYPE_PL110)
+
+typedef struct PL110State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ MemoryRegionSection fbsection;
+ QemuConsole *con;
+
+ int version;
+ uint32_t timing[4];
+ uint32_t cr;
+ uint32_t upbase;
+ uint32_t lpbase;
+ uint32_t int_status;
+ uint32_t int_mask;
+ int cols;
+ int rows;
+ enum pl110_bppmode bpp;
+ int invalidate;
+ uint32_t mux_ctrl;
+ uint32_t palette[256];
+ uint32_t raw_palette[128];
+ qemu_irq irq;
+} PL110State;
+
+static int vmstate_pl110_post_load(void *opaque, int version_id);
+
+static const VMStateDescription vmstate_pl110 = {
+ .name = "pl110",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .post_load = vmstate_pl110_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(version, PL110State),
+ VMSTATE_UINT32_ARRAY(timing, PL110State, 4),
+ VMSTATE_UINT32(cr, PL110State),
+ VMSTATE_UINT32(upbase, PL110State),
+ VMSTATE_UINT32(lpbase, PL110State),
+ VMSTATE_UINT32(int_status, PL110State),
+ VMSTATE_UINT32(int_mask, PL110State),
+ VMSTATE_INT32(cols, PL110State),
+ VMSTATE_INT32(rows, PL110State),
+ VMSTATE_UINT32(bpp, PL110State),
+ VMSTATE_INT32(invalidate, PL110State),
+ VMSTATE_UINT32_ARRAY(palette, PL110State, 256),
+ VMSTATE_UINT32_ARRAY(raw_palette, PL110State, 128),
+ VMSTATE_UINT32_V(mux_ctrl, PL110State, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const unsigned char pl110_id[] =
+{ 0x10, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static const unsigned char pl111_id[] = {
+ 0x11, 0x11, 0x24, 0x00, 0x0d, 0xf0, 0x05, 0xb1
+};
+
+
+/* Indexed by pl110_version */
+static const unsigned char *idregs[] = {
+ pl110_id,
+ /* The ARM documentation (DDI0224C) says the CLCDC on the Versatile board
+ * has a different ID (0x93, 0x10, 0x04, 0x00, ...). However the hardware
+ * itself has the same ID values as a stock PL110, and guests (in
+ * particular Linux) rely on this. We emulate what the hardware does,
+ * rather than what the docs claim it ought to do.
+ */
+ pl110_id,
+ pl111_id
+};
+
+#define BITS 8
+#include "pl110_template.h"
+#define BITS 15
+#include "pl110_template.h"
+#define BITS 16
+#include "pl110_template.h"
+#define BITS 24
+#include "pl110_template.h"
+#define BITS 32
+#include "pl110_template.h"
+
+static int pl110_enabled(PL110State *s)
+{
+ return (s->cr & PL110_CR_EN) && (s->cr & PL110_CR_PWR);
+}
+
+static void pl110_update_display(void *opaque)
+{
+ PL110State *s = (PL110State *)opaque;
+ SysBusDevice *sbd;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ drawfn* fntable;
+ drawfn fn;
+ int dest_width;
+ int src_width;
+ int bpp_offset;
+ int first;
+ int last;
+
+ if (!pl110_enabled(s)) {
+ return;
+ }
+
+ sbd = SYS_BUS_DEVICE(s);
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ return;
+ case 8:
+ fntable = pl110_draw_fn_8;
+ dest_width = 1;
+ break;
+ case 15:
+ fntable = pl110_draw_fn_15;
+ dest_width = 2;
+ break;
+ case 16:
+ fntable = pl110_draw_fn_16;
+ dest_width = 2;
+ break;
+ case 24:
+ fntable = pl110_draw_fn_24;
+ dest_width = 3;
+ break;
+ case 32:
+ fntable = pl110_draw_fn_32;
+ dest_width = 4;
+ break;
+ default:
+ fprintf(stderr, "pl110: Bad color depth\n");
+ exit(1);
+ }
+ if (s->cr & PL110_CR_BGR)
+ bpp_offset = 0;
+ else
+ bpp_offset = 24;
+
+ if ((s->version != PL111) && (s->bpp == BPP_16)) {
+ /* The PL110's native 16 bit mode is 5551; however
+ * most boards with a PL110 implement an external
+ * mux which allows bits to be reshuffled to give
+ * 565 format. The mux is typically controlled by
+ * an external system register.
+ * This is controlled by a GPIO input pin
+ * so boards can wire it up to their register.
+ *
+ * The PL111 straightforwardly implements both
+ * 5551 and 565 under control of the bpp field
+ * in the LCDControl register.
+ */
+ switch (s->mux_ctrl) {
+ case 3: /* 565 BGR */
+ bpp_offset = (BPP_16_565 - BPP_16);
+ break;
+ case 1: /* 5551 */
+ break;
+ case 0: /* 888; also if we have loaded vmstate from an old version */
+ case 2: /* 565 RGB */
+ default:
+ /* treat as 565 but honour BGR bit */
+ bpp_offset += (BPP_16_565 - BPP_16);
+ break;
+ }
+ }
+
+ if (s->cr & PL110_CR_BEBO)
+ fn = fntable[s->bpp + 8 + bpp_offset];
+ else if (s->cr & PL110_CR_BEPO)
+ fn = fntable[s->bpp + 16 + bpp_offset];
+ else
+ fn = fntable[s->bpp + bpp_offset];
+
+ src_width = s->cols;
+ switch (s->bpp) {
+ case BPP_1:
+ src_width >>= 3;
+ break;
+ case BPP_2:
+ src_width >>= 2;
+ break;
+ case BPP_4:
+ src_width >>= 1;
+ break;
+ case BPP_8:
+ break;
+ case BPP_16:
+ case BPP_16_565:
+ case BPP_12:
+ src_width <<= 1;
+ break;
+ case BPP_32:
+ src_width <<= 2;
+ break;
+ }
+ dest_width *= s->cols;
+ first = 0;
+ if (s->invalidate) {
+ framebuffer_update_memory_section(&s->fbsection,
+ sysbus_address_space(sbd),
+ s->upbase,
+ s->rows, src_width);
+ }
+
+ framebuffer_update_display(surface, &s->fbsection,
+ s->cols, s->rows,
+ src_width, dest_width, 0,
+ s->invalidate,
+ fn, s->palette,
+ &first, &last);
+
+ if (first >= 0) {
+ dpy_gfx_update(s->con, 0, first, s->cols, last - first + 1);
+ }
+ s->invalidate = 0;
+}
+
+static void pl110_invalidate_display(void * opaque)
+{
+ PL110State *s = (PL110State *)opaque;
+ s->invalidate = 1;
+ if (pl110_enabled(s)) {
+ qemu_console_resize(s->con, s->cols, s->rows);
+ }
+}
+
+static void pl110_update_palette(PL110State *s, int n)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i;
+ uint32_t raw;
+ unsigned int r, g, b;
+
+ raw = s->raw_palette[n];
+ n <<= 1;
+ for (i = 0; i < 2; i++) {
+ r = (raw & 0x1f) << 3;
+ raw >>= 5;
+ g = (raw & 0x1f) << 3;
+ raw >>= 5;
+ b = (raw & 0x1f) << 3;
+ /* The I bit is ignored. */
+ raw >>= 6;
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ s->palette[n] = rgb_to_pixel8(r, g, b);
+ break;
+ case 15:
+ s->palette[n] = rgb_to_pixel15(r, g, b);
+ break;
+ case 16:
+ s->palette[n] = rgb_to_pixel16(r, g, b);
+ break;
+ case 24:
+ case 32:
+ s->palette[n] = rgb_to_pixel32(r, g, b);
+ break;
+ }
+ n++;
+ }
+}
+
+static void pl110_resize(PL110State *s, int width, int height)
+{
+ if (width != s->cols || height != s->rows) {
+ if (pl110_enabled(s)) {
+ qemu_console_resize(s->con, width, height);
+ }
+ }
+ s->cols = width;
+ s->rows = height;
+}
+
+/* Update interrupts. */
+static void pl110_update(PL110State *s)
+{
+ /* TODO: Implement interrupts. */
+}
+
+static uint64_t pl110_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL110State *s = (PL110State *)opaque;
+
+ if (offset >= 0xfe0 && offset < 0x1000) {
+ return idregs[s->version][(offset - 0xfe0) >> 2];
+ }
+ if (offset >= 0x200 && offset < 0x400) {
+ return s->raw_palette[(offset - 0x200) >> 2];
+ }
+ switch (offset >> 2) {
+ case 0: /* LCDTiming0 */
+ return s->timing[0];
+ case 1: /* LCDTiming1 */
+ return s->timing[1];
+ case 2: /* LCDTiming2 */
+ return s->timing[2];
+ case 3: /* LCDTiming3 */
+ return s->timing[3];
+ case 4: /* LCDUPBASE */
+ return s->upbase;
+ case 5: /* LCDLPBASE */
+ return s->lpbase;
+ case 6: /* LCDIMSC */
+ if (s->version != PL110) {
+ return s->cr;
+ }
+ return s->int_mask;
+ case 7: /* LCDControl */
+ if (s->version != PL110) {
+ return s->int_mask;
+ }
+ return s->cr;
+ case 8: /* LCDRIS */
+ return s->int_status;
+ case 9: /* LCDMIS */
+ return s->int_status & s->int_mask;
+ case 11: /* LCDUPCURR */
+ /* TODO: Implement vertical refresh. */
+ return s->upbase;
+ case 12: /* LCDLPCURR */
+ return s->lpbase;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl110_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl110_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ PL110State *s = (PL110State *)opaque;
+ int n;
+
+ /* For simplicity invalidate the display whenever a control register
+ is written to. */
+ s->invalidate = 1;
+ if (offset >= 0x200 && offset < 0x400) {
+ /* Palette. */
+ n = (offset - 0x200) >> 2;
+ s->raw_palette[(offset - 0x200) >> 2] = val;
+ pl110_update_palette(s, n);
+ return;
+ }
+ switch (offset >> 2) {
+ case 0: /* LCDTiming0 */
+ s->timing[0] = val;
+ n = ((val & 0xfc) + 4) * 4;
+ pl110_resize(s, n, s->rows);
+ break;
+ case 1: /* LCDTiming1 */
+ s->timing[1] = val;
+ n = (val & 0x3ff) + 1;
+ pl110_resize(s, s->cols, n);
+ break;
+ case 2: /* LCDTiming2 */
+ s->timing[2] = val;
+ break;
+ case 3: /* LCDTiming3 */
+ s->timing[3] = val;
+ break;
+ case 4: /* LCDUPBASE */
+ s->upbase = val;
+ break;
+ case 5: /* LCDLPBASE */
+ s->lpbase = val;
+ break;
+ case 6: /* LCDIMSC */
+ if (s->version != PL110) {
+ goto control;
+ }
+ imsc:
+ s->int_mask = val;
+ pl110_update(s);
+ break;
+ case 7: /* LCDControl */
+ if (s->version != PL110) {
+ goto imsc;
+ }
+ control:
+ s->cr = val;
+ s->bpp = (val >> 1) & 7;
+ if (pl110_enabled(s)) {
+ qemu_console_resize(s->con, s->cols, s->rows);
+ }
+ break;
+ case 10: /* LCDICR */
+ s->int_status &= ~val;
+ pl110_update(s);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl110_write: Bad offset %x\n", (int)offset);
+ }
+}
+
+static const MemoryRegionOps pl110_ops = {
+ .read = pl110_read,
+ .write = pl110_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pl110_mux_ctrl_set(void *opaque, int line, int level)
+{
+ PL110State *s = (PL110State *)opaque;
+ s->mux_ctrl = level;
+}
+
+static int vmstate_pl110_post_load(void *opaque, int version_id)
+{
+ PL110State *s = opaque;
+ /* Make sure we redraw, and at the right size */
+ pl110_invalidate_display(s);
+ return 0;
+}
+
+static const GraphicHwOps pl110_gfx_ops = {
+ .invalidate = pl110_invalidate_display,
+ .gfx_update = pl110_update_display,
+};
+
+static int pl110_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PL110State *s = PL110(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl110_ops, s, "pl110", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_in(dev, pl110_mux_ctrl_set, 1);
+ s->con = graphic_console_init(dev, 0, &pl110_gfx_ops, s);
+ return 0;
+}
+
+static void pl110_init(Object *obj)
+{
+ PL110State *s = PL110(obj);
+
+ s->version = PL110;
+}
+
+static void pl110_versatile_init(Object *obj)
+{
+ PL110State *s = PL110(obj);
+
+ s->version = PL110_VERSATILE;
+}
+
+static void pl111_init(Object *obj)
+{
+ PL110State *s = PL110(obj);
+
+ s->version = PL111;
+}
+
+static void pl110_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pl110_initfn;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->vmsd = &vmstate_pl110;
+}
+
+static const TypeInfo pl110_info = {
+ .name = TYPE_PL110,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL110State),
+ .instance_init = pl110_init,
+ .class_init = pl110_class_init,
+};
+
+static const TypeInfo pl110_versatile_info = {
+ .name = "pl110_versatile",
+ .parent = TYPE_PL110,
+ .instance_init = pl110_versatile_init,
+};
+
+static const TypeInfo pl111_info = {
+ .name = "pl111",
+ .parent = TYPE_PL110,
+ .instance_init = pl111_init,
+};
+
+static void pl110_register_types(void)
+{
+ type_register_static(&pl110_info);
+ type_register_static(&pl110_versatile_info);
+ type_register_static(&pl111_info);
+}
+
+type_init(pl110_register_types)
diff --git a/hw/display/pl110_template.h b/hw/display/pl110_template.h
new file mode 100644
index 00000000..36ba791c
--- /dev/null
+++ b/hw/display/pl110_template.h
@@ -0,0 +1,399 @@
+/*
+ * Arm PrimeCell PL110 Color LCD Controller
+ *
+ * Copyright (c) 2005 CodeSourcery, LLC.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU LGPL
+ *
+ * Framebuffer format conversion routines.
+ */
+
+#ifndef ORDER
+
+#if BITS == 8
+#define COPY_PIXEL(to, from) *(to++) = from
+#elif BITS == 15 || BITS == 16
+#define COPY_PIXEL(to, from) do { *(uint16_t *)to = from; to += 2; } while (0)
+#elif BITS == 24
+#define COPY_PIXEL(to, from) \
+ do { \
+ *(to++) = from; \
+ *(to++) = (from) >> 8; \
+ *(to++) = (from) >> 16; \
+ } while (0)
+#elif BITS == 32
+#define COPY_PIXEL(to, from) do { *(uint32_t *)to = from; to += 4; } while (0)
+#else
+#error unknown bit depth
+#endif
+
+#undef RGB
+#define BORDER bgr
+#define ORDER 0
+#include "pl110_template.h"
+#define ORDER 1
+#include "pl110_template.h"
+#define ORDER 2
+#include "pl110_template.h"
+#undef BORDER
+#define RGB
+#define BORDER rgb
+#define ORDER 0
+#include "pl110_template.h"
+#define ORDER 1
+#include "pl110_template.h"
+#define ORDER 2
+#include "pl110_template.h"
+#undef BORDER
+
+static drawfn glue(pl110_draw_fn_,BITS)[48] =
+{
+ glue(pl110_draw_line1_lblp_bgr,BITS),
+ glue(pl110_draw_line2_lblp_bgr,BITS),
+ glue(pl110_draw_line4_lblp_bgr,BITS),
+ glue(pl110_draw_line8_lblp_bgr,BITS),
+ glue(pl110_draw_line16_555_lblp_bgr,BITS),
+ glue(pl110_draw_line32_lblp_bgr,BITS),
+ glue(pl110_draw_line16_lblp_bgr,BITS),
+ glue(pl110_draw_line12_lblp_bgr,BITS),
+
+ glue(pl110_draw_line1_bbbp_bgr,BITS),
+ glue(pl110_draw_line2_bbbp_bgr,BITS),
+ glue(pl110_draw_line4_bbbp_bgr,BITS),
+ glue(pl110_draw_line8_bbbp_bgr,BITS),
+ glue(pl110_draw_line16_555_bbbp_bgr,BITS),
+ glue(pl110_draw_line32_bbbp_bgr,BITS),
+ glue(pl110_draw_line16_bbbp_bgr,BITS),
+ glue(pl110_draw_line12_bbbp_bgr,BITS),
+
+ glue(pl110_draw_line1_lbbp_bgr,BITS),
+ glue(pl110_draw_line2_lbbp_bgr,BITS),
+ glue(pl110_draw_line4_lbbp_bgr,BITS),
+ glue(pl110_draw_line8_lbbp_bgr,BITS),
+ glue(pl110_draw_line16_555_lbbp_bgr,BITS),
+ glue(pl110_draw_line32_lbbp_bgr,BITS),
+ glue(pl110_draw_line16_lbbp_bgr,BITS),
+ glue(pl110_draw_line12_lbbp_bgr,BITS),
+
+ glue(pl110_draw_line1_lblp_rgb,BITS),
+ glue(pl110_draw_line2_lblp_rgb,BITS),
+ glue(pl110_draw_line4_lblp_rgb,BITS),
+ glue(pl110_draw_line8_lblp_rgb,BITS),
+ glue(pl110_draw_line16_555_lblp_rgb,BITS),
+ glue(pl110_draw_line32_lblp_rgb,BITS),
+ glue(pl110_draw_line16_lblp_rgb,BITS),
+ glue(pl110_draw_line12_lblp_rgb,BITS),
+
+ glue(pl110_draw_line1_bbbp_rgb,BITS),
+ glue(pl110_draw_line2_bbbp_rgb,BITS),
+ glue(pl110_draw_line4_bbbp_rgb,BITS),
+ glue(pl110_draw_line8_bbbp_rgb,BITS),
+ glue(pl110_draw_line16_555_bbbp_rgb,BITS),
+ glue(pl110_draw_line32_bbbp_rgb,BITS),
+ glue(pl110_draw_line16_bbbp_rgb,BITS),
+ glue(pl110_draw_line12_bbbp_rgb,BITS),
+
+ glue(pl110_draw_line1_lbbp_rgb,BITS),
+ glue(pl110_draw_line2_lbbp_rgb,BITS),
+ glue(pl110_draw_line4_lbbp_rgb,BITS),
+ glue(pl110_draw_line8_lbbp_rgb,BITS),
+ glue(pl110_draw_line16_555_lbbp_rgb,BITS),
+ glue(pl110_draw_line32_lbbp_rgb,BITS),
+ glue(pl110_draw_line16_lbbp_rgb,BITS),
+ glue(pl110_draw_line12_lbbp_rgb,BITS),
+};
+
+#undef BITS
+#undef COPY_PIXEL
+
+#else
+
+#if ORDER == 0
+#define NAME glue(glue(lblp_, BORDER), BITS)
+#ifdef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#elif ORDER == 1
+#define NAME glue(glue(bbbp_, BORDER), BITS)
+#ifndef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#else
+#define SWAP_PIXELS 1
+#define NAME glue(glue(lbbp_, BORDER), BITS)
+#ifdef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#endif
+
+#define FN_2(x, y) FN(x, y) FN(x+1, y)
+#define FN_4(x, y) FN_2(x, y) FN_2(x+2, y)
+#define FN_8(y) FN_4(0, y) FN_4(4, y)
+
+static void glue(pl110_draw_line1_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 7 - (x))) & 1]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x) + y)) & 1]);
+#endif
+#ifdef SWAP_WORDS
+ FN_8(24)
+ FN_8(16)
+ FN_8(8)
+ FN_8(0)
+#else
+ FN_8(0)
+ FN_8(8)
+ FN_8(16)
+ FN_8(24)
+#endif
+#undef FN
+ width -= 32;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line2_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 6 - (x)*2)) & 3]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x)*2 + y)) & 3]);
+#endif
+#ifdef SWAP_WORDS
+ FN_4(0, 24)
+ FN_4(0, 16)
+ FN_4(0, 8)
+ FN_4(0, 0)
+#else
+ FN_4(0, 0)
+ FN_4(0, 8)
+ FN_4(0, 16)
+ FN_4(0, 24)
+#endif
+#undef FN
+ width -= 16;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line4_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 4 - (x)*4)) & 0xf]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x)*4 + y)) & 0xf]);
+#endif
+#ifdef SWAP_WORDS
+ FN_2(0, 24)
+ FN_2(0, 16)
+ FN_2(0, 8)
+ FN_2(0, 0)
+#else
+ FN_2(0, 0)
+ FN_2(0, 8)
+ FN_2(0, 16)
+ FN_2(0, 24)
+#endif
+#undef FN
+ width -= 8;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line8_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#define FN(x) COPY_PIXEL(d, palette[(data >> (x)) & 0xff]);
+#ifdef SWAP_WORDS
+ FN(24)
+ FN(16)
+ FN(8)
+ FN(0)
+#else
+ FN(0)
+ FN(8)
+ FN(16)
+ FN(24)
+#endif
+#undef FN
+ width -= 4;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line16_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+#if 0
+ LSB = data & 0x1f;
+ data >>= 5;
+ g = data & 0x3f;
+ data >>= 6;
+ MSB = data & 0x1f;
+ data >>= 5;
+#else
+ LSB = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ MSB = (data & 0x1f) << 3;
+ data >>= 5;
+#endif
+ COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b));
+ LSB = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ MSB = (data & 0x1f) << 3;
+ data >>= 5;
+ COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b));
+#undef MSB
+#undef LSB
+ width -= 2;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line32_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+#ifndef SWAP_WORDS
+ LSB = data & 0xff;
+ g = (data >> 8) & 0xff;
+ MSB = (data >> 16) & 0xff;
+#else
+ LSB = (data >> 24) & 0xff;
+ g = (data >> 16) & 0xff;
+ MSB = (data >> 8) & 0xff;
+#endif
+ COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b));
+#undef MSB
+#undef LSB
+ width--;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line16_555_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ /* RGB 555 plus an intensity bit (which we ignore) */
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+ LSB = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x1f) << 3;
+ data >>= 5;
+ MSB = (data & 0x1f) << 3;
+ data >>= 5;
+ COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b));
+ LSB = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x1f) << 3;
+ data >>= 5;
+ MSB = (data & 0x1f) << 3;
+ data >>= 6;
+ COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b));
+#undef MSB
+#undef LSB
+ width -= 2;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line12_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ /* RGB 444 with 4 bits of zeroes at the top of each halfword */
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+ LSB = (data & 0xf) << 4;
+ data >>= 4;
+ g = (data & 0xf) << 4;
+ data >>= 4;
+ MSB = (data & 0xf) << 4;
+ data >>= 8;
+ COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b));
+ LSB = (data & 0xf) << 4;
+ data >>= 4;
+ g = (data & 0xf) << 4;
+ data >>= 4;
+ MSB = (data & 0xf) << 4;
+ data >>= 8;
+ COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b));
+#undef MSB
+#undef LSB
+ width -= 2;
+ src += 4;
+ }
+}
+
+#undef SWAP_PIXELS
+#undef NAME
+#undef SWAP_WORDS
+#undef ORDER
+
+#endif
diff --git a/hw/display/pxa2xx_lcd.c b/hw/display/pxa2xx_lcd.c
new file mode 100644
index 00000000..494700d0
--- /dev/null
+++ b/hw/display/pxa2xx_lcd.c
@@ -0,0 +1,1064 @@
+/*
+ * Intel XScale PXA255/270 LCDC emulation.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/arm/pxa.h"
+#include "ui/pixel_ops.h"
+/* FIXME: For graphic_rotate. Should probably be done in common code. */
+#include "sysemu/sysemu.h"
+#include "framebuffer.h"
+
+struct DMAChannel {
+ uint32_t branch;
+ uint8_t up;
+ uint8_t palette[1024];
+ uint8_t pbuffer[1024];
+ void (*redraw)(PXA2xxLCDState *s, hwaddr addr,
+ int *miny, int *maxy);
+
+ uint32_t descriptor;
+ uint32_t source;
+ uint32_t id;
+ uint32_t command;
+};
+
+struct PXA2xxLCDState {
+ MemoryRegion *sysmem;
+ MemoryRegion iomem;
+ MemoryRegionSection fbsection;
+ qemu_irq irq;
+ int irqlevel;
+
+ int invalidated;
+ QemuConsole *con;
+ drawfn *line_fn[2];
+ int dest_width;
+ int xres, yres;
+ int pal_for;
+ int transp;
+ enum {
+ pxa_lcdc_2bpp = 1,
+ pxa_lcdc_4bpp = 2,
+ pxa_lcdc_8bpp = 3,
+ pxa_lcdc_16bpp = 4,
+ pxa_lcdc_18bpp = 5,
+ pxa_lcdc_18pbpp = 6,
+ pxa_lcdc_19bpp = 7,
+ pxa_lcdc_19pbpp = 8,
+ pxa_lcdc_24bpp = 9,
+ pxa_lcdc_25bpp = 10,
+ } bpp;
+
+ uint32_t control[6];
+ uint32_t status[2];
+ uint32_t ovl1c[2];
+ uint32_t ovl2c[2];
+ uint32_t ccr;
+ uint32_t cmdcr;
+ uint32_t trgbr;
+ uint32_t tcr;
+ uint32_t liidr;
+ uint8_t bscntr;
+
+ struct DMAChannel dma_ch[7];
+
+ qemu_irq vsync_cb;
+ int orientation;
+};
+
+typedef struct QEMU_PACKED {
+ uint32_t fdaddr;
+ uint32_t fsaddr;
+ uint32_t fidr;
+ uint32_t ldcmd;
+} PXAFrameDescriptor;
+
+#define LCCR0 0x000 /* LCD Controller Control register 0 */
+#define LCCR1 0x004 /* LCD Controller Control register 1 */
+#define LCCR2 0x008 /* LCD Controller Control register 2 */
+#define LCCR3 0x00c /* LCD Controller Control register 3 */
+#define LCCR4 0x010 /* LCD Controller Control register 4 */
+#define LCCR5 0x014 /* LCD Controller Control register 5 */
+
+#define FBR0 0x020 /* DMA Channel 0 Frame Branch register */
+#define FBR1 0x024 /* DMA Channel 1 Frame Branch register */
+#define FBR2 0x028 /* DMA Channel 2 Frame Branch register */
+#define FBR3 0x02c /* DMA Channel 3 Frame Branch register */
+#define FBR4 0x030 /* DMA Channel 4 Frame Branch register */
+#define FBR5 0x110 /* DMA Channel 5 Frame Branch register */
+#define FBR6 0x114 /* DMA Channel 6 Frame Branch register */
+
+#define LCSR1 0x034 /* LCD Controller Status register 1 */
+#define LCSR0 0x038 /* LCD Controller Status register 0 */
+#define LIIDR 0x03c /* LCD Controller Interrupt ID register */
+
+#define TRGBR 0x040 /* TMED RGB Seed register */
+#define TCR 0x044 /* TMED Control register */
+
+#define OVL1C1 0x050 /* Overlay 1 Control register 1 */
+#define OVL1C2 0x060 /* Overlay 1 Control register 2 */
+#define OVL2C1 0x070 /* Overlay 2 Control register 1 */
+#define OVL2C2 0x080 /* Overlay 2 Control register 2 */
+#define CCR 0x090 /* Cursor Control register */
+
+#define CMDCR 0x100 /* Command Control register */
+#define PRSR 0x104 /* Panel Read Status register */
+
+#define PXA_LCDDMA_CHANS 7
+#define DMA_FDADR 0x00 /* Frame Descriptor Address register */
+#define DMA_FSADR 0x04 /* Frame Source Address register */
+#define DMA_FIDR 0x08 /* Frame ID register */
+#define DMA_LDCMD 0x0c /* Command register */
+
+/* LCD Buffer Strength Control register */
+#define BSCNTR 0x04000054
+
+/* Bitfield masks */
+#define LCCR0_ENB (1 << 0)
+#define LCCR0_CMS (1 << 1)
+#define LCCR0_SDS (1 << 2)
+#define LCCR0_LDM (1 << 3)
+#define LCCR0_SOFM0 (1 << 4)
+#define LCCR0_IUM (1 << 5)
+#define LCCR0_EOFM0 (1 << 6)
+#define LCCR0_PAS (1 << 7)
+#define LCCR0_DPD (1 << 9)
+#define LCCR0_DIS (1 << 10)
+#define LCCR0_QDM (1 << 11)
+#define LCCR0_PDD (0xff << 12)
+#define LCCR0_BSM0 (1 << 20)
+#define LCCR0_OUM (1 << 21)
+#define LCCR0_LCDT (1 << 22)
+#define LCCR0_RDSTM (1 << 23)
+#define LCCR0_CMDIM (1 << 24)
+#define LCCR0_OUC (1 << 25)
+#define LCCR0_LDDALT (1 << 26)
+#define LCCR1_PPL(x) ((x) & 0x3ff)
+#define LCCR2_LPP(x) ((x) & 0x3ff)
+#define LCCR3_API (15 << 16)
+#define LCCR3_BPP(x) ((((x) >> 24) & 7) | (((x) >> 26) & 8))
+#define LCCR3_PDFOR(x) (((x) >> 30) & 3)
+#define LCCR4_K1(x) (((x) >> 0) & 7)
+#define LCCR4_K2(x) (((x) >> 3) & 7)
+#define LCCR4_K3(x) (((x) >> 6) & 7)
+#define LCCR4_PALFOR(x) (((x) >> 15) & 3)
+#define LCCR5_SOFM(ch) (1 << (ch - 1))
+#define LCCR5_EOFM(ch) (1 << (ch + 7))
+#define LCCR5_BSM(ch) (1 << (ch + 15))
+#define LCCR5_IUM(ch) (1 << (ch + 23))
+#define OVLC1_EN (1 << 31)
+#define CCR_CEN (1 << 31)
+#define FBR_BRA (1 << 0)
+#define FBR_BINT (1 << 1)
+#define FBR_SRCADDR (0xfffffff << 4)
+#define LCSR0_LDD (1 << 0)
+#define LCSR0_SOF0 (1 << 1)
+#define LCSR0_BER (1 << 2)
+#define LCSR0_ABC (1 << 3)
+#define LCSR0_IU0 (1 << 4)
+#define LCSR0_IU1 (1 << 5)
+#define LCSR0_OU (1 << 6)
+#define LCSR0_QD (1 << 7)
+#define LCSR0_EOF0 (1 << 8)
+#define LCSR0_BS0 (1 << 9)
+#define LCSR0_SINT (1 << 10)
+#define LCSR0_RDST (1 << 11)
+#define LCSR0_CMDINT (1 << 12)
+#define LCSR0_BERCH(x) (((x) & 7) << 28)
+#define LCSR1_SOF(ch) (1 << (ch - 1))
+#define LCSR1_EOF(ch) (1 << (ch + 7))
+#define LCSR1_BS(ch) (1 << (ch + 15))
+#define LCSR1_IU(ch) (1 << (ch + 23))
+#define LDCMD_LENGTH(x) ((x) & 0x001ffffc)
+#define LDCMD_EOFINT (1 << 21)
+#define LDCMD_SOFINT (1 << 22)
+#define LDCMD_PAL (1 << 26)
+
+/* Route internal interrupt lines to the global IC */
+static void pxa2xx_lcdc_int_update(PXA2xxLCDState *s)
+{
+ int level = 0;
+ level |= (s->status[0] & LCSR0_LDD) && !(s->control[0] & LCCR0_LDM);
+ level |= (s->status[0] & LCSR0_SOF0) && !(s->control[0] & LCCR0_SOFM0);
+ level |= (s->status[0] & LCSR0_IU0) && !(s->control[0] & LCCR0_IUM);
+ level |= (s->status[0] & LCSR0_IU1) && !(s->control[5] & LCCR5_IUM(1));
+ level |= (s->status[0] & LCSR0_OU) && !(s->control[0] & LCCR0_OUM);
+ level |= (s->status[0] & LCSR0_QD) && !(s->control[0] & LCCR0_QDM);
+ level |= (s->status[0] & LCSR0_EOF0) && !(s->control[0] & LCCR0_EOFM0);
+ level |= (s->status[0] & LCSR0_BS0) && !(s->control[0] & LCCR0_BSM0);
+ level |= (s->status[0] & LCSR0_RDST) && !(s->control[0] & LCCR0_RDSTM);
+ level |= (s->status[0] & LCSR0_CMDINT) && !(s->control[0] & LCCR0_CMDIM);
+ level |= (s->status[1] & ~s->control[5]);
+
+ qemu_set_irq(s->irq, !!level);
+ s->irqlevel = level;
+}
+
+/* Set Branch Status interrupt high and poke associated registers */
+static inline void pxa2xx_dma_bs_set(PXA2xxLCDState *s, int ch)
+{
+ int unmasked;
+ if (ch == 0) {
+ s->status[0] |= LCSR0_BS0;
+ unmasked = !(s->control[0] & LCCR0_BSM0);
+ } else {
+ s->status[1] |= LCSR1_BS(ch);
+ unmasked = !(s->control[5] & LCCR5_BSM(ch));
+ }
+
+ if (unmasked) {
+ if (s->irqlevel)
+ s->status[0] |= LCSR0_SINT;
+ else
+ s->liidr = s->dma_ch[ch].id;
+ }
+}
+
+/* Set Start Of Frame Status interrupt high and poke associated registers */
+static inline void pxa2xx_dma_sof_set(PXA2xxLCDState *s, int ch)
+{
+ int unmasked;
+ if (!(s->dma_ch[ch].command & LDCMD_SOFINT))
+ return;
+
+ if (ch == 0) {
+ s->status[0] |= LCSR0_SOF0;
+ unmasked = !(s->control[0] & LCCR0_SOFM0);
+ } else {
+ s->status[1] |= LCSR1_SOF(ch);
+ unmasked = !(s->control[5] & LCCR5_SOFM(ch));
+ }
+
+ if (unmasked) {
+ if (s->irqlevel)
+ s->status[0] |= LCSR0_SINT;
+ else
+ s->liidr = s->dma_ch[ch].id;
+ }
+}
+
+/* Set End Of Frame Status interrupt high and poke associated registers */
+static inline void pxa2xx_dma_eof_set(PXA2xxLCDState *s, int ch)
+{
+ int unmasked;
+ if (!(s->dma_ch[ch].command & LDCMD_EOFINT))
+ return;
+
+ if (ch == 0) {
+ s->status[0] |= LCSR0_EOF0;
+ unmasked = !(s->control[0] & LCCR0_EOFM0);
+ } else {
+ s->status[1] |= LCSR1_EOF(ch);
+ unmasked = !(s->control[5] & LCCR5_EOFM(ch));
+ }
+
+ if (unmasked) {
+ if (s->irqlevel)
+ s->status[0] |= LCSR0_SINT;
+ else
+ s->liidr = s->dma_ch[ch].id;
+ }
+}
+
+/* Set Bus Error Status interrupt high and poke associated registers */
+static inline void pxa2xx_dma_ber_set(PXA2xxLCDState *s, int ch)
+{
+ s->status[0] |= LCSR0_BERCH(ch) | LCSR0_BER;
+ if (s->irqlevel)
+ s->status[0] |= LCSR0_SINT;
+ else
+ s->liidr = s->dma_ch[ch].id;
+}
+
+/* Load new Frame Descriptors from DMA */
+static void pxa2xx_descriptor_load(PXA2xxLCDState *s)
+{
+ PXAFrameDescriptor desc;
+ hwaddr descptr;
+ int i;
+
+ for (i = 0; i < PXA_LCDDMA_CHANS; i ++) {
+ s->dma_ch[i].source = 0;
+
+ if (!s->dma_ch[i].up)
+ continue;
+
+ if (s->dma_ch[i].branch & FBR_BRA) {
+ descptr = s->dma_ch[i].branch & FBR_SRCADDR;
+ if (s->dma_ch[i].branch & FBR_BINT)
+ pxa2xx_dma_bs_set(s, i);
+ s->dma_ch[i].branch &= ~FBR_BRA;
+ } else
+ descptr = s->dma_ch[i].descriptor;
+
+ if (!((descptr >= PXA2XX_SDRAM_BASE && descptr +
+ sizeof(desc) <= PXA2XX_SDRAM_BASE + ram_size) ||
+ (descptr >= PXA2XX_INTERNAL_BASE && descptr + sizeof(desc) <=
+ PXA2XX_INTERNAL_BASE + PXA2XX_INTERNAL_SIZE))) {
+ continue;
+ }
+
+ cpu_physical_memory_read(descptr, &desc, sizeof(desc));
+ s->dma_ch[i].descriptor = tswap32(desc.fdaddr);
+ s->dma_ch[i].source = tswap32(desc.fsaddr);
+ s->dma_ch[i].id = tswap32(desc.fidr);
+ s->dma_ch[i].command = tswap32(desc.ldcmd);
+ }
+}
+
+static uint64_t pxa2xx_lcdc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+ int ch;
+
+ switch (offset) {
+ case LCCR0:
+ return s->control[0];
+ case LCCR1:
+ return s->control[1];
+ case LCCR2:
+ return s->control[2];
+ case LCCR3:
+ return s->control[3];
+ case LCCR4:
+ return s->control[4];
+ case LCCR5:
+ return s->control[5];
+
+ case OVL1C1:
+ return s->ovl1c[0];
+ case OVL1C2:
+ return s->ovl1c[1];
+ case OVL2C1:
+ return s->ovl2c[0];
+ case OVL2C2:
+ return s->ovl2c[1];
+
+ case CCR:
+ return s->ccr;
+
+ case CMDCR:
+ return s->cmdcr;
+
+ case TRGBR:
+ return s->trgbr;
+ case TCR:
+ return s->tcr;
+
+ case 0x200 ... 0x1000: /* DMA per-channel registers */
+ ch = (offset - 0x200) >> 4;
+ if (!(ch >= 0 && ch < PXA_LCDDMA_CHANS))
+ goto fail;
+
+ switch (offset & 0xf) {
+ case DMA_FDADR:
+ return s->dma_ch[ch].descriptor;
+ case DMA_FSADR:
+ return s->dma_ch[ch].source;
+ case DMA_FIDR:
+ return s->dma_ch[ch].id;
+ case DMA_LDCMD:
+ return s->dma_ch[ch].command;
+ default:
+ goto fail;
+ }
+
+ case FBR0:
+ return s->dma_ch[0].branch;
+ case FBR1:
+ return s->dma_ch[1].branch;
+ case FBR2:
+ return s->dma_ch[2].branch;
+ case FBR3:
+ return s->dma_ch[3].branch;
+ case FBR4:
+ return s->dma_ch[4].branch;
+ case FBR5:
+ return s->dma_ch[5].branch;
+ case FBR6:
+ return s->dma_ch[6].branch;
+
+ case BSCNTR:
+ return s->bscntr;
+
+ case PRSR:
+ return 0;
+
+ case LCSR0:
+ return s->status[0];
+ case LCSR1:
+ return s->status[1];
+ case LIIDR:
+ return s->liidr;
+
+ default:
+ fail:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_lcdc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+ int ch;
+
+ switch (offset) {
+ case LCCR0:
+ /* ACK Quick Disable done */
+ if ((s->control[0] & LCCR0_ENB) && !(value & LCCR0_ENB))
+ s->status[0] |= LCSR0_QD;
+
+ if (!(s->control[0] & LCCR0_LCDT) && (value & LCCR0_LCDT))
+ printf("%s: internal frame buffer unsupported\n", __FUNCTION__);
+
+ if ((s->control[3] & LCCR3_API) &&
+ (value & LCCR0_ENB) && !(value & LCCR0_LCDT))
+ s->status[0] |= LCSR0_ABC;
+
+ s->control[0] = value & 0x07ffffff;
+ pxa2xx_lcdc_int_update(s);
+
+ s->dma_ch[0].up = !!(value & LCCR0_ENB);
+ s->dma_ch[1].up = (s->ovl1c[0] & OVLC1_EN) || (value & LCCR0_SDS);
+ break;
+
+ case LCCR1:
+ s->control[1] = value;
+ break;
+
+ case LCCR2:
+ s->control[2] = value;
+ break;
+
+ case LCCR3:
+ s->control[3] = value & 0xefffffff;
+ s->bpp = LCCR3_BPP(value);
+ break;
+
+ case LCCR4:
+ s->control[4] = value & 0x83ff81ff;
+ break;
+
+ case LCCR5:
+ s->control[5] = value & 0x3f3f3f3f;
+ break;
+
+ case OVL1C1:
+ if (!(s->ovl1c[0] & OVLC1_EN) && (value & OVLC1_EN))
+ printf("%s: Overlay 1 not supported\n", __FUNCTION__);
+
+ s->ovl1c[0] = value & 0x80ffffff;
+ s->dma_ch[1].up = (value & OVLC1_EN) || (s->control[0] & LCCR0_SDS);
+ break;
+
+ case OVL1C2:
+ s->ovl1c[1] = value & 0x000fffff;
+ break;
+
+ case OVL2C1:
+ if (!(s->ovl2c[0] & OVLC1_EN) && (value & OVLC1_EN))
+ printf("%s: Overlay 2 not supported\n", __FUNCTION__);
+
+ s->ovl2c[0] = value & 0x80ffffff;
+ s->dma_ch[2].up = !!(value & OVLC1_EN);
+ s->dma_ch[3].up = !!(value & OVLC1_EN);
+ s->dma_ch[4].up = !!(value & OVLC1_EN);
+ break;
+
+ case OVL2C2:
+ s->ovl2c[1] = value & 0x007fffff;
+ break;
+
+ case CCR:
+ if (!(s->ccr & CCR_CEN) && (value & CCR_CEN))
+ printf("%s: Hardware cursor unimplemented\n", __FUNCTION__);
+
+ s->ccr = value & 0x81ffffe7;
+ s->dma_ch[5].up = !!(value & CCR_CEN);
+ break;
+
+ case CMDCR:
+ s->cmdcr = value & 0xff;
+ break;
+
+ case TRGBR:
+ s->trgbr = value & 0x00ffffff;
+ break;
+
+ case TCR:
+ s->tcr = value & 0x7fff;
+ break;
+
+ case 0x200 ... 0x1000: /* DMA per-channel registers */
+ ch = (offset - 0x200) >> 4;
+ if (!(ch >= 0 && ch < PXA_LCDDMA_CHANS))
+ goto fail;
+
+ switch (offset & 0xf) {
+ case DMA_FDADR:
+ s->dma_ch[ch].descriptor = value & 0xfffffff0;
+ break;
+
+ default:
+ goto fail;
+ }
+ break;
+
+ case FBR0:
+ s->dma_ch[0].branch = value & 0xfffffff3;
+ break;
+ case FBR1:
+ s->dma_ch[1].branch = value & 0xfffffff3;
+ break;
+ case FBR2:
+ s->dma_ch[2].branch = value & 0xfffffff3;
+ break;
+ case FBR3:
+ s->dma_ch[3].branch = value & 0xfffffff3;
+ break;
+ case FBR4:
+ s->dma_ch[4].branch = value & 0xfffffff3;
+ break;
+ case FBR5:
+ s->dma_ch[5].branch = value & 0xfffffff3;
+ break;
+ case FBR6:
+ s->dma_ch[6].branch = value & 0xfffffff3;
+ break;
+
+ case BSCNTR:
+ s->bscntr = value & 0xf;
+ break;
+
+ case PRSR:
+ break;
+
+ case LCSR0:
+ s->status[0] &= ~(value & 0xfff);
+ if (value & LCSR0_BER)
+ s->status[0] &= ~LCSR0_BERCH(7);
+ break;
+
+ case LCSR1:
+ s->status[1] &= ~(value & 0x3e3f3f);
+ break;
+
+ default:
+ fail:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_lcdc_ops = {
+ .read = pxa2xx_lcdc_read,
+ .write = pxa2xx_lcdc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* Load new palette for a given DMA channel, convert to internal format */
+static void pxa2xx_palette_parse(PXA2xxLCDState *s, int ch, int bpp)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, n, format, r, g, b, alpha;
+ uint32_t *dest;
+ uint8_t *src;
+ s->pal_for = LCCR4_PALFOR(s->control[4]);
+ format = s->pal_for;
+
+ switch (bpp) {
+ case pxa_lcdc_2bpp:
+ n = 4;
+ break;
+ case pxa_lcdc_4bpp:
+ n = 16;
+ break;
+ case pxa_lcdc_8bpp:
+ n = 256;
+ break;
+ default:
+ format = 0;
+ return;
+ }
+
+ src = (uint8_t *) s->dma_ch[ch].pbuffer;
+ dest = (uint32_t *) s->dma_ch[ch].palette;
+ alpha = r = g = b = 0;
+
+ for (i = 0; i < n; i ++) {
+ switch (format) {
+ case 0: /* 16 bpp, no transparency */
+ alpha = 0;
+ if (s->control[0] & LCCR0_CMS) {
+ r = g = b = *(uint16_t *) src & 0xff;
+ }
+ else {
+ r = (*(uint16_t *) src & 0xf800) >> 8;
+ g = (*(uint16_t *) src & 0x07e0) >> 3;
+ b = (*(uint16_t *) src & 0x001f) << 3;
+ }
+ src += 2;
+ break;
+ case 1: /* 16 bpp plus transparency */
+ alpha = *(uint32_t *) src & (1 << 24);
+ if (s->control[0] & LCCR0_CMS)
+ r = g = b = *(uint32_t *) src & 0xff;
+ else {
+ r = (*(uint32_t *) src & 0xf80000) >> 16;
+ g = (*(uint32_t *) src & 0x00fc00) >> 8;
+ b = (*(uint32_t *) src & 0x0000f8);
+ }
+ src += 4;
+ break;
+ case 2: /* 18 bpp plus transparency */
+ alpha = *(uint32_t *) src & (1 << 24);
+ if (s->control[0] & LCCR0_CMS)
+ r = g = b = *(uint32_t *) src & 0xff;
+ else {
+ r = (*(uint32_t *) src & 0xfc0000) >> 16;
+ g = (*(uint32_t *) src & 0x00fc00) >> 8;
+ b = (*(uint32_t *) src & 0x0000fc);
+ }
+ src += 4;
+ break;
+ case 3: /* 24 bpp plus transparency */
+ alpha = *(uint32_t *) src & (1 << 24);
+ if (s->control[0] & LCCR0_CMS)
+ r = g = b = *(uint32_t *) src & 0xff;
+ else {
+ r = (*(uint32_t *) src & 0xff0000) >> 16;
+ g = (*(uint32_t *) src & 0x00ff00) >> 8;
+ b = (*(uint32_t *) src & 0x0000ff);
+ }
+ src += 4;
+ break;
+ }
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ *dest = rgb_to_pixel8(r, g, b) | alpha;
+ break;
+ case 15:
+ *dest = rgb_to_pixel15(r, g, b) | alpha;
+ break;
+ case 16:
+ *dest = rgb_to_pixel16(r, g, b) | alpha;
+ break;
+ case 24:
+ *dest = rgb_to_pixel24(r, g, b) | alpha;
+ break;
+ case 32:
+ *dest = rgb_to_pixel32(r, g, b) | alpha;
+ break;
+ }
+ dest ++;
+ }
+}
+
+static void pxa2xx_lcdc_dma0_redraw_rot0(PXA2xxLCDState *s,
+ hwaddr addr, int *miny, int *maxy)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width, dest_width;
+ drawfn fn = NULL;
+ if (s->dest_width)
+ fn = s->line_fn[s->transp][s->bpp];
+ if (!fn)
+ return;
+
+ src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */
+ if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp)
+ src_width *= 3;
+ else if (s->bpp > pxa_lcdc_16bpp)
+ src_width *= 4;
+ else if (s->bpp > pxa_lcdc_8bpp)
+ src_width *= 2;
+
+ dest_width = s->xres * s->dest_width;
+ *miny = 0;
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->fbsection, s->sysmem,
+ addr, s->yres, src_width);
+ }
+ framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres,
+ src_width, dest_width, s->dest_width,
+ s->invalidated,
+ fn, s->dma_ch[0].palette, miny, maxy);
+}
+
+static void pxa2xx_lcdc_dma0_redraw_rot90(PXA2xxLCDState *s,
+ hwaddr addr, int *miny, int *maxy)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width, dest_width;
+ drawfn fn = NULL;
+ if (s->dest_width)
+ fn = s->line_fn[s->transp][s->bpp];
+ if (!fn)
+ return;
+
+ src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */
+ if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp)
+ src_width *= 3;
+ else if (s->bpp > pxa_lcdc_16bpp)
+ src_width *= 4;
+ else if (s->bpp > pxa_lcdc_8bpp)
+ src_width *= 2;
+
+ dest_width = s->yres * s->dest_width;
+ *miny = 0;
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->fbsection, s->sysmem,
+ addr, s->yres, src_width);
+ }
+ framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres,
+ src_width, s->dest_width, -dest_width,
+ s->invalidated,
+ fn, s->dma_ch[0].palette,
+ miny, maxy);
+}
+
+static void pxa2xx_lcdc_dma0_redraw_rot180(PXA2xxLCDState *s,
+ hwaddr addr, int *miny, int *maxy)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width, dest_width;
+ drawfn fn = NULL;
+ if (s->dest_width) {
+ fn = s->line_fn[s->transp][s->bpp];
+ }
+ if (!fn) {
+ return;
+ }
+
+ src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */
+ if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp) {
+ src_width *= 3;
+ } else if (s->bpp > pxa_lcdc_16bpp) {
+ src_width *= 4;
+ } else if (s->bpp > pxa_lcdc_8bpp) {
+ src_width *= 2;
+ }
+
+ dest_width = s->xres * s->dest_width;
+ *miny = 0;
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->fbsection, s->sysmem,
+ addr, s->yres, src_width);
+ }
+ framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres,
+ src_width, -dest_width, -s->dest_width,
+ s->invalidated,
+ fn, s->dma_ch[0].palette, miny, maxy);
+}
+
+static void pxa2xx_lcdc_dma0_redraw_rot270(PXA2xxLCDState *s,
+ hwaddr addr, int *miny, int *maxy)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width, dest_width;
+ drawfn fn = NULL;
+ if (s->dest_width) {
+ fn = s->line_fn[s->transp][s->bpp];
+ }
+ if (!fn) {
+ return;
+ }
+
+ src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */
+ if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp) {
+ src_width *= 3;
+ } else if (s->bpp > pxa_lcdc_16bpp) {
+ src_width *= 4;
+ } else if (s->bpp > pxa_lcdc_8bpp) {
+ src_width *= 2;
+ }
+
+ dest_width = s->yres * s->dest_width;
+ *miny = 0;
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->fbsection, s->sysmem,
+ addr, s->yres, src_width);
+ }
+ framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres,
+ src_width, -s->dest_width, dest_width,
+ s->invalidated,
+ fn, s->dma_ch[0].palette,
+ miny, maxy);
+}
+
+static void pxa2xx_lcdc_resize(PXA2xxLCDState *s)
+{
+ int width, height;
+ if (!(s->control[0] & LCCR0_ENB))
+ return;
+
+ width = LCCR1_PPL(s->control[1]) + 1;
+ height = LCCR2_LPP(s->control[2]) + 1;
+
+ if (width != s->xres || height != s->yres) {
+ if (s->orientation == 90 || s->orientation == 270) {
+ qemu_console_resize(s->con, height, width);
+ } else {
+ qemu_console_resize(s->con, width, height);
+ }
+ s->invalidated = 1;
+ s->xres = width;
+ s->yres = height;
+ }
+}
+
+static void pxa2xx_update_display(void *opaque)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+ hwaddr fbptr;
+ int miny, maxy;
+ int ch;
+ if (!(s->control[0] & LCCR0_ENB))
+ return;
+
+ pxa2xx_descriptor_load(s);
+
+ pxa2xx_lcdc_resize(s);
+ miny = s->yres;
+ maxy = 0;
+ s->transp = s->dma_ch[2].up || s->dma_ch[3].up;
+ /* Note: With overlay planes the order depends on LCCR0 bit 25. */
+ for (ch = 0; ch < PXA_LCDDMA_CHANS; ch ++)
+ if (s->dma_ch[ch].up) {
+ if (!s->dma_ch[ch].source) {
+ pxa2xx_dma_ber_set(s, ch);
+ continue;
+ }
+ fbptr = s->dma_ch[ch].source;
+ if (!((fbptr >= PXA2XX_SDRAM_BASE &&
+ fbptr <= PXA2XX_SDRAM_BASE + ram_size) ||
+ (fbptr >= PXA2XX_INTERNAL_BASE &&
+ fbptr <= PXA2XX_INTERNAL_BASE + PXA2XX_INTERNAL_SIZE))) {
+ pxa2xx_dma_ber_set(s, ch);
+ continue;
+ }
+
+ if (s->dma_ch[ch].command & LDCMD_PAL) {
+ cpu_physical_memory_read(fbptr, s->dma_ch[ch].pbuffer,
+ MAX(LDCMD_LENGTH(s->dma_ch[ch].command),
+ sizeof(s->dma_ch[ch].pbuffer)));
+ pxa2xx_palette_parse(s, ch, s->bpp);
+ } else {
+ /* Do we need to reparse palette */
+ if (LCCR4_PALFOR(s->control[4]) != s->pal_for)
+ pxa2xx_palette_parse(s, ch, s->bpp);
+
+ /* ACK frame start */
+ pxa2xx_dma_sof_set(s, ch);
+
+ s->dma_ch[ch].redraw(s, fbptr, &miny, &maxy);
+ s->invalidated = 0;
+
+ /* ACK frame completed */
+ pxa2xx_dma_eof_set(s, ch);
+ }
+ }
+
+ if (s->control[0] & LCCR0_DIS) {
+ /* ACK last frame completed */
+ s->control[0] &= ~LCCR0_ENB;
+ s->status[0] |= LCSR0_LDD;
+ }
+
+ if (miny >= 0) {
+ switch (s->orientation) {
+ case 0:
+ dpy_gfx_update(s->con, 0, miny, s->xres, maxy - miny + 1);
+ break;
+ case 90:
+ dpy_gfx_update(s->con, miny, 0, maxy - miny + 1, s->xres);
+ break;
+ case 180:
+ maxy = s->yres - maxy - 1;
+ miny = s->yres - miny - 1;
+ dpy_gfx_update(s->con, 0, maxy, s->xres, miny - maxy + 1);
+ break;
+ case 270:
+ maxy = s->yres - maxy - 1;
+ miny = s->yres - miny - 1;
+ dpy_gfx_update(s->con, maxy, 0, miny - maxy + 1, s->xres);
+ break;
+ }
+ }
+ pxa2xx_lcdc_int_update(s);
+
+ qemu_irq_raise(s->vsync_cb);
+}
+
+static void pxa2xx_invalidate_display(void *opaque)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+ s->invalidated = 1;
+}
+
+static void pxa2xx_lcdc_orientation(void *opaque, int angle)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+
+ switch (angle) {
+ case 0:
+ s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot0;
+ break;
+ case 90:
+ s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot90;
+ break;
+ case 180:
+ s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot180;
+ break;
+ case 270:
+ s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot270;
+ break;
+ }
+
+ s->orientation = angle;
+ s->xres = s->yres = -1;
+ pxa2xx_lcdc_resize(s);
+}
+
+static const VMStateDescription vmstate_dma_channel = {
+ .name = "dma_channel",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(branch, struct DMAChannel),
+ VMSTATE_UINT8(up, struct DMAChannel),
+ VMSTATE_BUFFER(pbuffer, struct DMAChannel),
+ VMSTATE_UINT32(descriptor, struct DMAChannel),
+ VMSTATE_UINT32(source, struct DMAChannel),
+ VMSTATE_UINT32(id, struct DMAChannel),
+ VMSTATE_UINT32(command, struct DMAChannel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int pxa2xx_lcdc_post_load(void *opaque, int version_id)
+{
+ PXA2xxLCDState *s = opaque;
+
+ s->bpp = LCCR3_BPP(s->control[3]);
+ s->xres = s->yres = s->pal_for = -1;
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_pxa2xx_lcdc = {
+ .name = "pxa2xx_lcdc",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = pxa2xx_lcdc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(irqlevel, PXA2xxLCDState),
+ VMSTATE_INT32(transp, PXA2xxLCDState),
+ VMSTATE_UINT32_ARRAY(control, PXA2xxLCDState, 6),
+ VMSTATE_UINT32_ARRAY(status, PXA2xxLCDState, 2),
+ VMSTATE_UINT32_ARRAY(ovl1c, PXA2xxLCDState, 2),
+ VMSTATE_UINT32_ARRAY(ovl2c, PXA2xxLCDState, 2),
+ VMSTATE_UINT32(ccr, PXA2xxLCDState),
+ VMSTATE_UINT32(cmdcr, PXA2xxLCDState),
+ VMSTATE_UINT32(trgbr, PXA2xxLCDState),
+ VMSTATE_UINT32(tcr, PXA2xxLCDState),
+ VMSTATE_UINT32(liidr, PXA2xxLCDState),
+ VMSTATE_UINT8(bscntr, PXA2xxLCDState),
+ VMSTATE_STRUCT_ARRAY(dma_ch, PXA2xxLCDState, 7, 0,
+ vmstate_dma_channel, struct DMAChannel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define BITS 8
+#include "pxa2xx_template.h"
+#define BITS 15
+#include "pxa2xx_template.h"
+#define BITS 16
+#include "pxa2xx_template.h"
+#define BITS 24
+#include "pxa2xx_template.h"
+#define BITS 32
+#include "pxa2xx_template.h"
+
+static const GraphicHwOps pxa2xx_ops = {
+ .invalidate = pxa2xx_invalidate_display,
+ .gfx_update = pxa2xx_update_display,
+};
+
+PXA2xxLCDState *pxa2xx_lcdc_init(MemoryRegion *sysmem,
+ hwaddr base, qemu_irq irq)
+{
+ PXA2xxLCDState *s;
+ DisplaySurface *surface;
+
+ s = (PXA2xxLCDState *) g_malloc0(sizeof(PXA2xxLCDState));
+ s->invalidated = 1;
+ s->irq = irq;
+ s->sysmem = sysmem;
+
+ pxa2xx_lcdc_orientation(s, graphic_rotate);
+
+ memory_region_init_io(&s->iomem, NULL, &pxa2xx_lcdc_ops, s,
+ "pxa2xx-lcd-controller", 0x00100000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ s->con = graphic_console_init(NULL, 0, &pxa2xx_ops, s);
+ surface = qemu_console_surface(s->con);
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ s->dest_width = 0;
+ break;
+ case 8:
+ s->line_fn[0] = pxa2xx_draw_fn_8;
+ s->line_fn[1] = pxa2xx_draw_fn_8t;
+ s->dest_width = 1;
+ break;
+ case 15:
+ s->line_fn[0] = pxa2xx_draw_fn_15;
+ s->line_fn[1] = pxa2xx_draw_fn_15t;
+ s->dest_width = 2;
+ break;
+ case 16:
+ s->line_fn[0] = pxa2xx_draw_fn_16;
+ s->line_fn[1] = pxa2xx_draw_fn_16t;
+ s->dest_width = 2;
+ break;
+ case 24:
+ s->line_fn[0] = pxa2xx_draw_fn_24;
+ s->line_fn[1] = pxa2xx_draw_fn_24t;
+ s->dest_width = 3;
+ break;
+ case 32:
+ s->line_fn[0] = pxa2xx_draw_fn_32;
+ s->line_fn[1] = pxa2xx_draw_fn_32t;
+ s->dest_width = 4;
+ break;
+ default:
+ fprintf(stderr, "%s: Bad color depth\n", __FUNCTION__);
+ exit(1);
+ }
+
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_lcdc, s);
+
+ return s;
+}
+
+void pxa2xx_lcd_vsync_notifier(PXA2xxLCDState *s, qemu_irq handler)
+{
+ s->vsync_cb = handler;
+}
diff --git a/hw/display/pxa2xx_template.h b/hw/display/pxa2xx_template.h
new file mode 100644
index 00000000..c64eebc4
--- /dev/null
+++ b/hw/display/pxa2xx_template.h
@@ -0,0 +1,447 @@
+/*
+ * Intel XScale PXA255/270 LCDC emulation.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPLv2.
+ *
+ * Framebuffer format conversion routines.
+ */
+
+# define SKIP_PIXEL(to) to += deststep
+#if BITS == 8
+# define COPY_PIXEL(to, from) do { *to = from; SKIP_PIXEL(to); } while (0)
+#elif BITS == 15 || BITS == 16
+# define COPY_PIXEL(to, from) \
+ do { \
+ *(uint16_t *) to = from; \
+ SKIP_PIXEL(to); \
+ } while (0)
+#elif BITS == 24
+# define COPY_PIXEL(to, from) \
+ do { \
+ *(uint16_t *) to = from; \
+ *(to + 2) = (from) >> 16; \
+ SKIP_PIXEL(to); \
+ } while (0)
+#elif BITS == 32
+# define COPY_PIXEL(to, from) \
+ do { \
+ *(uint32_t *) to = from; \
+ SKIP_PIXEL(to); \
+ } while (0)
+#else
+# error unknown bit depth
+#endif
+
+#ifdef HOST_WORDS_BIGENDIAN
+# define SWAP_WORDS 1
+#endif
+
+#define FN_2(x) FN(x + 1) FN(x)
+#define FN_4(x) FN_2(x + 2) FN_2(x)
+
+static void glue(pxa2xx_draw_line2_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#define FN(x) COPY_PIXEL(dest, palette[(data >> ((x) * 2)) & 3]);
+#ifdef SWAP_WORDS
+ FN_4(12)
+ FN_4(8)
+ FN_4(4)
+ FN_4(0)
+#else
+ FN_4(0)
+ FN_4(4)
+ FN_4(8)
+ FN_4(12)
+#endif
+#undef FN
+ width -= 16;
+ src += 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line4_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#define FN(x) COPY_PIXEL(dest, palette[(data >> ((x) * 4)) & 0xf]);
+#ifdef SWAP_WORDS
+ FN_2(6)
+ FN_2(4)
+ FN_2(2)
+ FN_2(0)
+#else
+ FN_2(0)
+ FN_2(2)
+ FN_2(4)
+ FN_2(6)
+#endif
+#undef FN
+ width -= 8;
+ src += 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line8_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#define FN(x) COPY_PIXEL(dest, palette[(data >> (x)) & 0xff]);
+#ifdef SWAP_WORDS
+ FN(24)
+ FN(16)
+ FN(8)
+ FN(0)
+#else
+ FN(0)
+ FN(8)
+ FN(16)
+ FN(24)
+#endif
+#undef FN
+ width -= 4;
+ src += 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line16_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x1f) << 3;
+ data >>= 5;
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x1f) << 3;
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 2;
+ src += 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line16t_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x1f) << 3;
+ data >>= 5;
+ r = (data & 0x1f) << 3;
+ data >>= 5;
+ if (data & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ data >>= 1;
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x1f) << 3;
+ data >>= 5;
+ r = (data & 0x1f) << 3;
+ data >>= 5;
+ if (data & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 2;
+ src += 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line18_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x3f) << 2;
+ data >>= 6;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x3f) << 2;
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 1;
+ src += 4;
+ }
+}
+
+/* The wicked packed format */
+static void glue(pxa2xx_draw_line18p_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data[3];
+ unsigned int r, g, b;
+ while (width > 0) {
+ data[0] = *(uint32_t *) src;
+ src += 4;
+ data[1] = *(uint32_t *) src;
+ src += 4;
+ data[2] = *(uint32_t *) src;
+ src += 4;
+#ifdef SWAP_WORDS
+ data[0] = bswap32(data[0]);
+ data[1] = bswap32(data[1]);
+ data[2] = bswap32(data[2]);
+#endif
+ b = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ g = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ r = (data[0] & 0x3f) << 2;
+ data[0] >>= 12;
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ b = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ g = ((data[1] & 0xf) << 4) | (data[0] << 2);
+ data[1] >>= 4;
+ r = (data[1] & 0x3f) << 2;
+ data[1] >>= 12;
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ b = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ g = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ r = ((data[2] & 0x3) << 6) | (data[1] << 2);
+ data[2] >>= 8;
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ b = (data[2] & 0x3f) << 2;
+ data[2] >>= 6;
+ g = (data[2] & 0x3f) << 2;
+ data[2] >>= 6;
+ r = data[2] << 2;
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line19_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x3f) << 2;
+ data >>= 6;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x3f) << 2;
+ data >>= 6;
+ if (data & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 1;
+ src += 4;
+ }
+}
+
+/* The wicked packed format */
+static void glue(pxa2xx_draw_line19p_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data[3];
+ unsigned int r, g, b;
+ while (width > 0) {
+ data[0] = *(uint32_t *) src;
+ src += 4;
+ data[1] = *(uint32_t *) src;
+ src += 4;
+ data[2] = *(uint32_t *) src;
+ src += 4;
+# ifdef SWAP_WORDS
+ data[0] = bswap32(data[0]);
+ data[1] = bswap32(data[1]);
+ data[2] = bswap32(data[2]);
+# endif
+ b = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ g = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ r = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ if (data[0] & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ data[0] >>= 6;
+ b = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ g = ((data[1] & 0xf) << 4) | (data[0] << 2);
+ data[1] >>= 4;
+ r = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ if (data[1] & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ data[1] >>= 6;
+ b = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ g = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ r = ((data[2] & 0x3) << 6) | (data[1] << 2);
+ data[2] >>= 2;
+ if (data[2] & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ data[2] >>= 6;
+ b = (data[2] & 0x3f) << 2;
+ data[2] >>= 6;
+ g = (data[2] & 0x3f) << 2;
+ data[2] >>= 6;
+ r = data[2] << 2;
+ data[2] >>= 6;
+ if (data[2] & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line24_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = data & 0xff;
+ data >>= 8;
+ g = data & 0xff;
+ data >>= 8;
+ r = data & 0xff;
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 1;
+ src += 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line24t_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x7f) << 1;
+ data >>= 7;
+ g = data & 0xff;
+ data >>= 8;
+ r = data & 0xff;
+ data >>= 8;
+ if (data & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 1;
+ src += 4;
+ }
+}
+
+static void glue(pxa2xx_draw_line25_, BITS)(void *opaque,
+ uint8_t *dest, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = data & 0xff;
+ data >>= 8;
+ g = data & 0xff;
+ data >>= 8;
+ r = data & 0xff;
+ data >>= 8;
+ if (data & 1)
+ SKIP_PIXEL(dest);
+ else
+ COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b));
+ width -= 1;
+ src += 4;
+ }
+}
+
+/* Overlay planes disabled, no transparency */
+static drawfn glue(pxa2xx_draw_fn_, BITS)[16] =
+{
+ [0 ... 0xf] = NULL,
+ [pxa_lcdc_2bpp] = glue(pxa2xx_draw_line2_, BITS),
+ [pxa_lcdc_4bpp] = glue(pxa2xx_draw_line4_, BITS),
+ [pxa_lcdc_8bpp] = glue(pxa2xx_draw_line8_, BITS),
+ [pxa_lcdc_16bpp] = glue(pxa2xx_draw_line16_, BITS),
+ [pxa_lcdc_18bpp] = glue(pxa2xx_draw_line18_, BITS),
+ [pxa_lcdc_18pbpp] = glue(pxa2xx_draw_line18p_, BITS),
+ [pxa_lcdc_24bpp] = glue(pxa2xx_draw_line24_, BITS),
+};
+
+/* Overlay planes enabled, transparency used */
+static drawfn glue(glue(pxa2xx_draw_fn_, BITS), t)[16] =
+{
+ [0 ... 0xf] = NULL,
+ [pxa_lcdc_4bpp] = glue(pxa2xx_draw_line4_, BITS),
+ [pxa_lcdc_8bpp] = glue(pxa2xx_draw_line8_, BITS),
+ [pxa_lcdc_16bpp] = glue(pxa2xx_draw_line16t_, BITS),
+ [pxa_lcdc_19bpp] = glue(pxa2xx_draw_line19_, BITS),
+ [pxa_lcdc_19pbpp] = glue(pxa2xx_draw_line19p_, BITS),
+ [pxa_lcdc_24bpp] = glue(pxa2xx_draw_line24t_, BITS),
+ [pxa_lcdc_25bpp] = glue(pxa2xx_draw_line25_, BITS),
+};
+
+#undef BITS
+#undef COPY_PIXEL
+#undef SKIP_PIXEL
+
+#ifdef SWAP_WORDS
+# undef SWAP_WORDS
+#endif
diff --git a/hw/display/qxl-logger.c b/hw/display/qxl-logger.c
new file mode 100644
index 00000000..d944d3fd
--- /dev/null
+++ b/hw/display/qxl-logger.c
@@ -0,0 +1,275 @@
+/*
+ * qxl command logging -- for debug purposes
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * maintained by Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/timer.h"
+#include "qxl.h"
+
+static const char *const qxl_type[] = {
+ [ QXL_CMD_NOP ] = "nop",
+ [ QXL_CMD_DRAW ] = "draw",
+ [ QXL_CMD_UPDATE ] = "update",
+ [ QXL_CMD_CURSOR ] = "cursor",
+ [ QXL_CMD_MESSAGE ] = "message",
+ [ QXL_CMD_SURFACE ] = "surface",
+};
+
+static const char *const qxl_draw_type[] = {
+ [ QXL_DRAW_NOP ] = "nop",
+ [ QXL_DRAW_FILL ] = "fill",
+ [ QXL_DRAW_OPAQUE ] = "opaque",
+ [ QXL_DRAW_COPY ] = "copy",
+ [ QXL_COPY_BITS ] = "copy-bits",
+ [ QXL_DRAW_BLEND ] = "blend",
+ [ QXL_DRAW_BLACKNESS ] = "blackness",
+ [ QXL_DRAW_WHITENESS ] = "whitemess",
+ [ QXL_DRAW_INVERS ] = "invers",
+ [ QXL_DRAW_ROP3 ] = "rop3",
+ [ QXL_DRAW_STROKE ] = "stroke",
+ [ QXL_DRAW_TEXT ] = "text",
+ [ QXL_DRAW_TRANSPARENT ] = "transparent",
+ [ QXL_DRAW_ALPHA_BLEND ] = "alpha-blend",
+};
+
+static const char *const qxl_draw_effect[] = {
+ [ QXL_EFFECT_BLEND ] = "blend",
+ [ QXL_EFFECT_OPAQUE ] = "opaque",
+ [ QXL_EFFECT_REVERT_ON_DUP ] = "revert-on-dup",
+ [ QXL_EFFECT_BLACKNESS_ON_DUP ] = "blackness-on-dup",
+ [ QXL_EFFECT_WHITENESS_ON_DUP ] = "whiteness-on-dup",
+ [ QXL_EFFECT_NOP_ON_DUP ] = "nop-on-dup",
+ [ QXL_EFFECT_NOP ] = "nop",
+ [ QXL_EFFECT_OPAQUE_BRUSH ] = "opaque-brush",
+};
+
+static const char *const qxl_surface_cmd[] = {
+ [ QXL_SURFACE_CMD_CREATE ] = "create",
+ [ QXL_SURFACE_CMD_DESTROY ] = "destroy",
+};
+
+static const char *const spice_surface_fmt[] = {
+ [ SPICE_SURFACE_FMT_INVALID ] = "invalid",
+ [ SPICE_SURFACE_FMT_1_A ] = "alpha/1",
+ [ SPICE_SURFACE_FMT_8_A ] = "alpha/8",
+ [ SPICE_SURFACE_FMT_16_555 ] = "555/16",
+ [ SPICE_SURFACE_FMT_16_565 ] = "565/16",
+ [ SPICE_SURFACE_FMT_32_xRGB ] = "xRGB/32",
+ [ SPICE_SURFACE_FMT_32_ARGB ] = "ARGB/32",
+};
+
+static const char *const qxl_cursor_cmd[] = {
+ [ QXL_CURSOR_SET ] = "set",
+ [ QXL_CURSOR_MOVE ] = "move",
+ [ QXL_CURSOR_HIDE ] = "hide",
+ [ QXL_CURSOR_TRAIL ] = "trail",
+};
+
+static const char *const spice_cursor_type[] = {
+ [ SPICE_CURSOR_TYPE_ALPHA ] = "alpha",
+ [ SPICE_CURSOR_TYPE_MONO ] = "mono",
+ [ SPICE_CURSOR_TYPE_COLOR4 ] = "color4",
+ [ SPICE_CURSOR_TYPE_COLOR8 ] = "color8",
+ [ SPICE_CURSOR_TYPE_COLOR16 ] = "color16",
+ [ SPICE_CURSOR_TYPE_COLOR24 ] = "color24",
+ [ SPICE_CURSOR_TYPE_COLOR32 ] = "color32",
+};
+
+static const char *qxl_v2n(const char *const n[], size_t l, int v)
+{
+ if (v >= l || !n[v]) {
+ return "???";
+ }
+ return n[v];
+}
+#define qxl_name(_list, _value) qxl_v2n(_list, ARRAY_SIZE(_list), _value)
+
+static int qxl_log_image(PCIQXLDevice *qxl, QXLPHYSICAL addr, int group_id)
+{
+ QXLImage *image;
+ QXLImageDescriptor *desc;
+
+ image = qxl_phys2virt(qxl, addr, group_id);
+ if (!image) {
+ return 1;
+ }
+ desc = &image->descriptor;
+ fprintf(stderr, " (id %" PRIx64 " type %d flags %d width %d height %d",
+ desc->id, desc->type, desc->flags, desc->width, desc->height);
+ switch (desc->type) {
+ case SPICE_IMAGE_TYPE_BITMAP:
+ fprintf(stderr, ", fmt %d flags %d x %d y %d stride %d"
+ " palette %" PRIx64 " data %" PRIx64,
+ image->bitmap.format, image->bitmap.flags,
+ image->bitmap.x, image->bitmap.y,
+ image->bitmap.stride,
+ image->bitmap.palette, image->bitmap.data);
+ break;
+ }
+ fprintf(stderr, ")");
+ return 0;
+}
+
+static void qxl_log_rect(QXLRect *rect)
+{
+ fprintf(stderr, " %dx%d+%d+%d",
+ rect->right - rect->left,
+ rect->bottom - rect->top,
+ rect->left, rect->top);
+}
+
+static int qxl_log_cmd_draw_copy(PCIQXLDevice *qxl, QXLCopy *copy,
+ int group_id)
+{
+ int ret;
+
+ fprintf(stderr, " src %" PRIx64,
+ copy->src_bitmap);
+ ret = qxl_log_image(qxl, copy->src_bitmap, group_id);
+ if (ret != 0) {
+ return ret;
+ }
+ fprintf(stderr, " area");
+ qxl_log_rect(&copy->src_area);
+ fprintf(stderr, " rop %d", copy->rop_descriptor);
+ return 0;
+}
+
+static int qxl_log_cmd_draw(PCIQXLDevice *qxl, QXLDrawable *draw, int group_id)
+{
+ fprintf(stderr, ": surface_id %d type %s effect %s",
+ draw->surface_id,
+ qxl_name(qxl_draw_type, draw->type),
+ qxl_name(qxl_draw_effect, draw->effect));
+ switch (draw->type) {
+ case QXL_DRAW_COPY:
+ return qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id);
+ break;
+ }
+ return 0;
+}
+
+static int qxl_log_cmd_draw_compat(PCIQXLDevice *qxl, QXLCompatDrawable *draw,
+ int group_id)
+{
+ fprintf(stderr, ": type %s effect %s",
+ qxl_name(qxl_draw_type, draw->type),
+ qxl_name(qxl_draw_effect, draw->effect));
+ if (draw->bitmap_offset) {
+ fprintf(stderr, ": bitmap %d",
+ draw->bitmap_offset);
+ qxl_log_rect(&draw->bitmap_area);
+ }
+ switch (draw->type) {
+ case QXL_DRAW_COPY:
+ return qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id);
+ break;
+ }
+ return 0;
+}
+
+static void qxl_log_cmd_surface(PCIQXLDevice *qxl, QXLSurfaceCmd *cmd)
+{
+ fprintf(stderr, ": %s id %d",
+ qxl_name(qxl_surface_cmd, cmd->type),
+ cmd->surface_id);
+ if (cmd->type == QXL_SURFACE_CMD_CREATE) {
+ fprintf(stderr, " size %dx%d stride %d format %s (count %d, max %d)",
+ cmd->u.surface_create.width,
+ cmd->u.surface_create.height,
+ cmd->u.surface_create.stride,
+ qxl_name(spice_surface_fmt, cmd->u.surface_create.format),
+ qxl->guest_surfaces.count, qxl->guest_surfaces.max);
+ }
+ if (cmd->type == QXL_SURFACE_CMD_DESTROY) {
+ fprintf(stderr, " (count %d)", qxl->guest_surfaces.count);
+ }
+}
+
+int qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id)
+{
+ QXLCursor *cursor;
+
+ fprintf(stderr, ": %s",
+ qxl_name(qxl_cursor_cmd, cmd->type));
+ switch (cmd->type) {
+ case QXL_CURSOR_SET:
+ fprintf(stderr, " +%d+%d visible %s, shape @ 0x%" PRIx64,
+ cmd->u.set.position.x,
+ cmd->u.set.position.y,
+ cmd->u.set.visible ? "yes" : "no",
+ cmd->u.set.shape);
+ cursor = qxl_phys2virt(qxl, cmd->u.set.shape, group_id);
+ if (!cursor) {
+ return 1;
+ }
+ fprintf(stderr, " type %s size %dx%d hot-spot +%d+%d"
+ " unique 0x%" PRIx64 " data-size %d",
+ qxl_name(spice_cursor_type, cursor->header.type),
+ cursor->header.width, cursor->header.height,
+ cursor->header.hot_spot_x, cursor->header.hot_spot_y,
+ cursor->header.unique, cursor->data_size);
+ break;
+ case QXL_CURSOR_MOVE:
+ fprintf(stderr, " +%d+%d", cmd->u.position.x, cmd->u.position.y);
+ break;
+ }
+ return 0;
+}
+
+int qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext)
+{
+ bool compat = ext->flags & QXL_COMMAND_FLAG_COMPAT;
+ void *data;
+ int ret;
+
+ if (!qxl->cmdlog) {
+ return 0;
+ }
+ fprintf(stderr, "%" PRId64 " qxl-%d/%s:", qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ qxl->id, ring);
+ fprintf(stderr, " cmd @ 0x%" PRIx64 " %s%s", ext->cmd.data,
+ qxl_name(qxl_type, ext->cmd.type),
+ compat ? "(compat)" : "");
+
+ data = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
+ if (!data) {
+ return 1;
+ }
+ switch (ext->cmd.type) {
+ case QXL_CMD_DRAW:
+ if (!compat) {
+ ret = qxl_log_cmd_draw(qxl, data, ext->group_id);
+ } else {
+ ret = qxl_log_cmd_draw_compat(qxl, data, ext->group_id);
+ }
+ if (ret) {
+ return ret;
+ }
+ break;
+ case QXL_CMD_SURFACE:
+ qxl_log_cmd_surface(qxl, data);
+ break;
+ case QXL_CMD_CURSOR:
+ qxl_log_cmd_cursor(qxl, data, ext->group_id);
+ break;
+ }
+ fprintf(stderr, "\n");
+ return 0;
+}
diff --git a/hw/display/qxl-render.c b/hw/display/qxl-render.c
new file mode 100644
index 00000000..a542087f
--- /dev/null
+++ b/hw/display/qxl-render.c
@@ -0,0 +1,297 @@
+/*
+ * qxl local rendering (aka display on sdl/vnc)
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * maintained by Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qxl.h"
+#include "trace.h"
+
+static void qxl_blit(PCIQXLDevice *qxl, QXLRect *rect)
+{
+ DisplaySurface *surface = qemu_console_surface(qxl->vga.con);
+ uint8_t *dst = surface_data(surface);
+ uint8_t *src;
+ int len, i;
+
+ if (is_buffer_shared(surface)) {
+ return;
+ }
+ trace_qxl_render_blit(qxl->guest_primary.qxl_stride,
+ rect->left, rect->right, rect->top, rect->bottom);
+ src = qxl->guest_primary.data;
+ if (qxl->guest_primary.qxl_stride < 0) {
+ /* qxl surface is upside down, walk src scanlines
+ * in reverse order to flip it */
+ src += (qxl->guest_primary.surface.height - rect->top - 1) *
+ qxl->guest_primary.abs_stride;
+ } else {
+ src += rect->top * qxl->guest_primary.abs_stride;
+ }
+ dst += rect->top * qxl->guest_primary.abs_stride;
+ src += rect->left * qxl->guest_primary.bytes_pp;
+ dst += rect->left * qxl->guest_primary.bytes_pp;
+ len = (rect->right - rect->left) * qxl->guest_primary.bytes_pp;
+
+ for (i = rect->top; i < rect->bottom; i++) {
+ memcpy(dst, src, len);
+ dst += qxl->guest_primary.abs_stride;
+ src += qxl->guest_primary.qxl_stride;
+ }
+}
+
+void qxl_render_resize(PCIQXLDevice *qxl)
+{
+ QXLSurfaceCreate *sc = &qxl->guest_primary.surface;
+
+ qxl->guest_primary.qxl_stride = sc->stride;
+ qxl->guest_primary.abs_stride = abs(sc->stride);
+ qxl->guest_primary.resized++;
+ switch (sc->format) {
+ case SPICE_SURFACE_FMT_16_555:
+ qxl->guest_primary.bytes_pp = 2;
+ qxl->guest_primary.bits_pp = 15;
+ break;
+ case SPICE_SURFACE_FMT_16_565:
+ qxl->guest_primary.bytes_pp = 2;
+ qxl->guest_primary.bits_pp = 16;
+ break;
+ case SPICE_SURFACE_FMT_32_xRGB:
+ case SPICE_SURFACE_FMT_32_ARGB:
+ qxl->guest_primary.bytes_pp = 4;
+ qxl->guest_primary.bits_pp = 32;
+ break;
+ default:
+ fprintf(stderr, "%s: unhandled format: %x\n", __FUNCTION__,
+ qxl->guest_primary.surface.format);
+ qxl->guest_primary.bytes_pp = 4;
+ qxl->guest_primary.bits_pp = 32;
+ break;
+ }
+}
+
+static void qxl_set_rect_to_surface(PCIQXLDevice *qxl, QXLRect *area)
+{
+ area->left = 0;
+ area->right = qxl->guest_primary.surface.width;
+ area->top = 0;
+ area->bottom = qxl->guest_primary.surface.height;
+}
+
+static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
+{
+ VGACommonState *vga = &qxl->vga;
+ DisplaySurface *surface;
+ int i;
+
+ if (qxl->guest_primary.resized) {
+ qxl->guest_primary.resized = 0;
+ qxl->guest_primary.data = qxl_phys2virt(qxl,
+ qxl->guest_primary.surface.mem,
+ MEMSLOT_GROUP_GUEST);
+ if (!qxl->guest_primary.data) {
+ return;
+ }
+ qxl_set_rect_to_surface(qxl, &qxl->dirty[0]);
+ qxl->num_dirty_rects = 1;
+ trace_qxl_render_guest_primary_resized(
+ qxl->guest_primary.surface.width,
+ qxl->guest_primary.surface.height,
+ qxl->guest_primary.qxl_stride,
+ qxl->guest_primary.bytes_pp,
+ qxl->guest_primary.bits_pp);
+ if (qxl->guest_primary.qxl_stride > 0) {
+ pixman_format_code_t format =
+ qemu_default_pixman_format(qxl->guest_primary.bits_pp, true);
+ surface = qemu_create_displaysurface_from
+ (qxl->guest_primary.surface.width,
+ qxl->guest_primary.surface.height,
+ format,
+ qxl->guest_primary.abs_stride,
+ qxl->guest_primary.data);
+ } else {
+ surface = qemu_create_displaysurface
+ (qxl->guest_primary.surface.width,
+ qxl->guest_primary.surface.height);
+ }
+ dpy_gfx_replace_surface(vga->con, surface);
+ }
+
+ if (!qxl->guest_primary.data) {
+ return;
+ }
+ for (i = 0; i < qxl->num_dirty_rects; i++) {
+ if (qemu_spice_rect_is_empty(qxl->dirty+i)) {
+ break;
+ }
+ if (qxl->dirty[i].left < 0 ||
+ qxl->dirty[i].top < 0 ||
+ qxl->dirty[i].left > qxl->dirty[i].right ||
+ qxl->dirty[i].top > qxl->dirty[i].bottom ||
+ qxl->dirty[i].right > qxl->guest_primary.surface.width ||
+ qxl->dirty[i].bottom > qxl->guest_primary.surface.height) {
+ continue;
+ }
+ qxl_blit(qxl, qxl->dirty+i);
+ dpy_gfx_update(vga->con,
+ qxl->dirty[i].left, qxl->dirty[i].top,
+ qxl->dirty[i].right - qxl->dirty[i].left,
+ qxl->dirty[i].bottom - qxl->dirty[i].top);
+ }
+ qxl->num_dirty_rects = 0;
+}
+
+/*
+ * use ssd.lock to protect render_update_cookie_num.
+ * qxl_render_update is called by io thread or vcpu thread, and the completion
+ * callbacks are called by spice_server thread, defering to bh called from the
+ * io thread.
+ */
+void qxl_render_update(PCIQXLDevice *qxl)
+{
+ QXLCookie *cookie;
+
+ qemu_mutex_lock(&qxl->ssd.lock);
+
+ if (!runstate_is_running() || !qxl->guest_primary.commands) {
+ qxl_render_update_area_unlocked(qxl);
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ return;
+ }
+
+ qxl->guest_primary.commands = 0;
+ qxl->render_update_cookie_num++;
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ cookie = qxl_cookie_new(QXL_COOKIE_TYPE_RENDER_UPDATE_AREA,
+ 0);
+ qxl_set_rect_to_surface(qxl, &cookie->u.render.area);
+ qxl_spice_update_area(qxl, 0, &cookie->u.render.area, NULL,
+ 0, 1 /* clear_dirty_region */, QXL_ASYNC, cookie);
+}
+
+void qxl_render_update_area_bh(void *opaque)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ qemu_mutex_lock(&qxl->ssd.lock);
+ qxl_render_update_area_unlocked(qxl);
+ qemu_mutex_unlock(&qxl->ssd.lock);
+}
+
+void qxl_render_update_area_done(PCIQXLDevice *qxl, QXLCookie *cookie)
+{
+ qemu_mutex_lock(&qxl->ssd.lock);
+ trace_qxl_render_update_area_done(cookie);
+ qemu_bh_schedule(qxl->update_area_bh);
+ qxl->render_update_cookie_num--;
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ g_free(cookie);
+}
+
+static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor)
+{
+ QEMUCursor *c;
+ uint8_t *image, *mask;
+ size_t size;
+
+ c = cursor_alloc(cursor->header.width, cursor->header.height);
+ c->hot_x = cursor->header.hot_spot_x;
+ c->hot_y = cursor->header.hot_spot_y;
+ switch (cursor->header.type) {
+ case SPICE_CURSOR_TYPE_ALPHA:
+ size = sizeof(uint32_t) * cursor->header.width * cursor->header.height;
+ memcpy(c->data, cursor->chunk.data, size);
+ if (qxl->debug > 2) {
+ cursor_print_ascii_art(c, "qxl/alpha");
+ }
+ break;
+ case SPICE_CURSOR_TYPE_MONO:
+ mask = cursor->chunk.data;
+ image = mask + cursor_get_mono_bpl(c) * c->width;
+ cursor_set_mono(c, 0xffffff, 0x000000, image, 1, mask);
+ if (qxl->debug > 2) {
+ cursor_print_ascii_art(c, "qxl/mono");
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: not implemented: type %d\n",
+ __FUNCTION__, cursor->header.type);
+ goto fail;
+ }
+ return c;
+
+fail:
+ cursor_put(c);
+ return NULL;
+}
+
+
+/* called from spice server thread context only */
+int qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext)
+{
+ QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
+ QXLCursor *cursor;
+ QEMUCursor *c;
+
+ if (!cmd) {
+ return 1;
+ }
+
+ if (!dpy_cursor_define_supported(qxl->vga.con)) {
+ return 0;
+ }
+
+ if (qxl->debug > 1 && cmd->type != QXL_CURSOR_MOVE) {
+ fprintf(stderr, "%s", __FUNCTION__);
+ qxl_log_cmd_cursor(qxl, cmd, ext->group_id);
+ fprintf(stderr, "\n");
+ }
+ switch (cmd->type) {
+ case QXL_CURSOR_SET:
+ cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id);
+ if (!cursor) {
+ return 1;
+ }
+ if (cursor->chunk.data_size != cursor->data_size) {
+ fprintf(stderr, "%s: multiple chunks\n", __FUNCTION__);
+ return 1;
+ }
+ c = qxl_cursor(qxl, cursor);
+ if (c == NULL) {
+ c = cursor_builtin_left_ptr();
+ }
+ qemu_mutex_lock(&qxl->ssd.lock);
+ if (qxl->ssd.cursor) {
+ cursor_put(qxl->ssd.cursor);
+ }
+ qxl->ssd.cursor = c;
+ qxl->ssd.mouse_x = cmd->u.set.position.x;
+ qxl->ssd.mouse_y = cmd->u.set.position.y;
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ qemu_bh_schedule(qxl->ssd.cursor_bh);
+ break;
+ case QXL_CURSOR_MOVE:
+ qemu_mutex_lock(&qxl->ssd.lock);
+ qxl->ssd.mouse_x = cmd->u.position.x;
+ qxl->ssd.mouse_y = cmd->u.position.y;
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ qemu_bh_schedule(qxl->ssd.cursor_bh);
+ break;
+ }
+ return 0;
+}
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
new file mode 100644
index 00000000..2288238d
--- /dev/null
+++ b/hw/display/qxl.c
@@ -0,0 +1,2357 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * written by Yaniv Kamay, Izik Eidus, Gerd Hoffmann
+ * maintained by Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <zlib.h>
+#include <stdint.h>
+
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "qemu/queue.h"
+#include "qemu/atomic.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+
+#include "qxl.h"
+
+/*
+ * NOTE: SPICE_RING_PROD_ITEM accesses memory on the pci bar and as
+ * such can be changed by the guest, so to avoid a guest trigerrable
+ * abort we just qxl_set_guest_bug and set the return to NULL. Still
+ * it may happen as a result of emulator bug as well.
+ */
+#undef SPICE_RING_PROD_ITEM
+#define SPICE_RING_PROD_ITEM(qxl, r, ret) { \
+ uint32_t prod = (r)->prod & SPICE_RING_INDEX_MASK(r); \
+ if (prod >= ARRAY_SIZE((r)->items)) { \
+ qxl_set_guest_bug(qxl, "SPICE_RING_PROD_ITEM indices mismatch " \
+ "%u >= %zu", prod, ARRAY_SIZE((r)->items)); \
+ ret = NULL; \
+ } else { \
+ ret = &(r)->items[prod].el; \
+ } \
+ }
+
+#undef SPICE_RING_CONS_ITEM
+#define SPICE_RING_CONS_ITEM(qxl, r, ret) { \
+ uint32_t cons = (r)->cons & SPICE_RING_INDEX_MASK(r); \
+ if (cons >= ARRAY_SIZE((r)->items)) { \
+ qxl_set_guest_bug(qxl, "SPICE_RING_CONS_ITEM indices mismatch " \
+ "%u >= %zu", cons, ARRAY_SIZE((r)->items)); \
+ ret = NULL; \
+ } else { \
+ ret = &(r)->items[cons].el; \
+ } \
+ }
+
+#undef ALIGN
+#define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1))
+
+#define PIXEL_SIZE 0.2936875 //1280x1024 is 14.8" x 11.9"
+
+#define QXL_MODE(_x, _y, _b, _o) \
+ { .x_res = _x, \
+ .y_res = _y, \
+ .bits = _b, \
+ .stride = (_x) * (_b) / 8, \
+ .x_mili = PIXEL_SIZE * (_x), \
+ .y_mili = PIXEL_SIZE * (_y), \
+ .orientation = _o, \
+ }
+
+#define QXL_MODE_16_32(x_res, y_res, orientation) \
+ QXL_MODE(x_res, y_res, 16, orientation), \
+ QXL_MODE(x_res, y_res, 32, orientation)
+
+#define QXL_MODE_EX(x_res, y_res) \
+ QXL_MODE_16_32(x_res, y_res, 0), \
+ QXL_MODE_16_32(x_res, y_res, 1)
+
+static QXLMode qxl_modes[] = {
+ QXL_MODE_EX(640, 480),
+ QXL_MODE_EX(800, 480),
+ QXL_MODE_EX(800, 600),
+ QXL_MODE_EX(832, 624),
+ QXL_MODE_EX(960, 640),
+ QXL_MODE_EX(1024, 600),
+ QXL_MODE_EX(1024, 768),
+ QXL_MODE_EX(1152, 864),
+ QXL_MODE_EX(1152, 870),
+ QXL_MODE_EX(1280, 720),
+ QXL_MODE_EX(1280, 760),
+ QXL_MODE_EX(1280, 768),
+ QXL_MODE_EX(1280, 800),
+ QXL_MODE_EX(1280, 960),
+ QXL_MODE_EX(1280, 1024),
+ QXL_MODE_EX(1360, 768),
+ QXL_MODE_EX(1366, 768),
+ QXL_MODE_EX(1400, 1050),
+ QXL_MODE_EX(1440, 900),
+ QXL_MODE_EX(1600, 900),
+ QXL_MODE_EX(1600, 1200),
+ QXL_MODE_EX(1680, 1050),
+ QXL_MODE_EX(1920, 1080),
+ /* these modes need more than 8 MB video memory */
+ QXL_MODE_EX(1920, 1200),
+ QXL_MODE_EX(1920, 1440),
+ QXL_MODE_EX(2000, 2000),
+ QXL_MODE_EX(2048, 1536),
+ QXL_MODE_EX(2048, 2048),
+ QXL_MODE_EX(2560, 1440),
+ QXL_MODE_EX(2560, 1600),
+ /* these modes need more than 16 MB video memory */
+ QXL_MODE_EX(2560, 2048),
+ QXL_MODE_EX(2800, 2100),
+ QXL_MODE_EX(3200, 2400),
+ /* these modes need more than 32 MB video memory */
+ QXL_MODE_EX(3840, 2160), /* 4k mainstream */
+ QXL_MODE_EX(4096, 2160), /* 4k */
+ /* these modes need more than 64 MB video memory */
+ QXL_MODE_EX(7680, 4320), /* 8k mainstream */
+ /* these modes need more than 128 MB video memory */
+ QXL_MODE_EX(8192, 4320), /* 8k */
+};
+
+static void qxl_send_events(PCIQXLDevice *d, uint32_t events);
+static int qxl_destroy_primary(PCIQXLDevice *d, qxl_async_io async);
+static void qxl_reset_memslots(PCIQXLDevice *d);
+static void qxl_reset_surfaces(PCIQXLDevice *d);
+static void qxl_ring_set_dirty(PCIQXLDevice *qxl);
+
+static void qxl_hw_update(void *opaque);
+
+void qxl_set_guest_bug(PCIQXLDevice *qxl, const char *msg, ...)
+{
+ trace_qxl_set_guest_bug(qxl->id);
+ qxl_send_events(qxl, QXL_INTERRUPT_ERROR);
+ qxl->guest_bug = 1;
+ if (qxl->guestdebug) {
+ va_list ap;
+ va_start(ap, msg);
+ fprintf(stderr, "qxl-%d: guest bug: ", qxl->id);
+ vfprintf(stderr, msg, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ }
+}
+
+static void qxl_clear_guest_bug(PCIQXLDevice *qxl)
+{
+ qxl->guest_bug = 0;
+}
+
+void qxl_spice_update_area(PCIQXLDevice *qxl, uint32_t surface_id,
+ struct QXLRect *area, struct QXLRect *dirty_rects,
+ uint32_t num_dirty_rects,
+ uint32_t clear_dirty_region,
+ qxl_async_io async, struct QXLCookie *cookie)
+{
+ trace_qxl_spice_update_area(qxl->id, surface_id, area->left, area->right,
+ area->top, area->bottom);
+ trace_qxl_spice_update_area_rest(qxl->id, num_dirty_rects,
+ clear_dirty_region);
+ if (async == QXL_SYNC) {
+ spice_qxl_update_area(&qxl->ssd.qxl, surface_id, area,
+ dirty_rects, num_dirty_rects, clear_dirty_region);
+ } else {
+ assert(cookie != NULL);
+ spice_qxl_update_area_async(&qxl->ssd.qxl, surface_id, area,
+ clear_dirty_region, (uintptr_t)cookie);
+ }
+}
+
+static void qxl_spice_destroy_surface_wait_complete(PCIQXLDevice *qxl,
+ uint32_t id)
+{
+ trace_qxl_spice_destroy_surface_wait_complete(qxl->id, id);
+ qemu_mutex_lock(&qxl->track_lock);
+ qxl->guest_surfaces.cmds[id] = 0;
+ qxl->guest_surfaces.count--;
+ qemu_mutex_unlock(&qxl->track_lock);
+}
+
+static void qxl_spice_destroy_surface_wait(PCIQXLDevice *qxl, uint32_t id,
+ qxl_async_io async)
+{
+ QXLCookie *cookie;
+
+ trace_qxl_spice_destroy_surface_wait(qxl->id, id, async);
+ if (async) {
+ cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_DESTROY_SURFACE_ASYNC);
+ cookie->u.surface_id = id;
+ spice_qxl_destroy_surface_async(&qxl->ssd.qxl, id, (uintptr_t)cookie);
+ } else {
+ spice_qxl_destroy_surface_wait(&qxl->ssd.qxl, id);
+ qxl_spice_destroy_surface_wait_complete(qxl, id);
+ }
+}
+
+static void qxl_spice_flush_surfaces_async(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_flush_surfaces_async(qxl->id, qxl->guest_surfaces.count,
+ qxl->num_free_res);
+ spice_qxl_flush_surfaces_async(&qxl->ssd.qxl,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_FLUSH_SURFACES_ASYNC));
+}
+
+void qxl_spice_loadvm_commands(PCIQXLDevice *qxl, struct QXLCommandExt *ext,
+ uint32_t count)
+{
+ trace_qxl_spice_loadvm_commands(qxl->id, ext, count);
+ spice_qxl_loadvm_commands(&qxl->ssd.qxl, ext, count);
+}
+
+void qxl_spice_oom(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_oom(qxl->id);
+ spice_qxl_oom(&qxl->ssd.qxl);
+}
+
+void qxl_spice_reset_memslots(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_reset_memslots(qxl->id);
+ spice_qxl_reset_memslots(&qxl->ssd.qxl);
+}
+
+static void qxl_spice_destroy_surfaces_complete(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_destroy_surfaces_complete(qxl->id);
+ qemu_mutex_lock(&qxl->track_lock);
+ memset(qxl->guest_surfaces.cmds, 0,
+ sizeof(qxl->guest_surfaces.cmds[0]) * qxl->ssd.num_surfaces);
+ qxl->guest_surfaces.count = 0;
+ qemu_mutex_unlock(&qxl->track_lock);
+}
+
+static void qxl_spice_destroy_surfaces(PCIQXLDevice *qxl, qxl_async_io async)
+{
+ trace_qxl_spice_destroy_surfaces(qxl->id, async);
+ if (async) {
+ spice_qxl_destroy_surfaces_async(&qxl->ssd.qxl,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_DESTROY_ALL_SURFACES_ASYNC));
+ } else {
+ spice_qxl_destroy_surfaces(&qxl->ssd.qxl);
+ qxl_spice_destroy_surfaces_complete(qxl);
+ }
+}
+
+static void qxl_spice_monitors_config_async(PCIQXLDevice *qxl, int replay)
+{
+ trace_qxl_spice_monitors_config(qxl->id);
+ if (replay) {
+ /*
+ * don't use QXL_COOKIE_TYPE_IO:
+ * - we are not running yet (post_load), we will assert
+ * in send_events
+ * - this is not a guest io, but a reply, so async_io isn't set.
+ */
+ spice_qxl_monitors_config_async(&qxl->ssd.qxl,
+ qxl->guest_monitors_config,
+ MEMSLOT_GROUP_GUEST,
+ (uintptr_t)qxl_cookie_new(
+ QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG,
+ 0));
+ } else {
+#if SPICE_SERVER_VERSION >= 0x000c06 /* release 0.12.6 */
+ if (qxl->max_outputs) {
+ spice_qxl_set_max_monitors(&qxl->ssd.qxl, qxl->max_outputs);
+ }
+#endif
+ qxl->guest_monitors_config = qxl->ram->monitors_config;
+ spice_qxl_monitors_config_async(&qxl->ssd.qxl,
+ qxl->ram->monitors_config,
+ MEMSLOT_GROUP_GUEST,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_MONITORS_CONFIG_ASYNC));
+ }
+}
+
+void qxl_spice_reset_image_cache(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_reset_image_cache(qxl->id);
+ spice_qxl_reset_image_cache(&qxl->ssd.qxl);
+}
+
+void qxl_spice_reset_cursor(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_reset_cursor(qxl->id);
+ spice_qxl_reset_cursor(&qxl->ssd.qxl);
+ qemu_mutex_lock(&qxl->track_lock);
+ qxl->guest_cursor = 0;
+ qemu_mutex_unlock(&qxl->track_lock);
+ if (qxl->ssd.cursor) {
+ cursor_put(qxl->ssd.cursor);
+ }
+ qxl->ssd.cursor = cursor_builtin_hidden();
+}
+
+static ram_addr_t qxl_rom_size(void)
+{
+ uint32_t required_rom_size = sizeof(QXLRom) + sizeof(QXLModes) +
+ sizeof(qxl_modes);
+ uint32_t rom_size = 8192; /* two pages */
+
+ QEMU_BUILD_BUG_ON(required_rom_size > rom_size);
+ return rom_size;
+}
+
+static void init_qxl_rom(PCIQXLDevice *d)
+{
+ QXLRom *rom = memory_region_get_ram_ptr(&d->rom_bar);
+ QXLModes *modes = (QXLModes *)(rom + 1);
+ uint32_t ram_header_size;
+ uint32_t surface0_area_size;
+ uint32_t num_pages;
+ uint32_t fb;
+ int i, n;
+
+ memset(rom, 0, d->rom_size);
+
+ rom->magic = cpu_to_le32(QXL_ROM_MAGIC);
+ rom->id = cpu_to_le32(d->id);
+ rom->log_level = cpu_to_le32(d->guestdebug);
+ rom->modes_offset = cpu_to_le32(sizeof(QXLRom));
+
+ rom->slot_gen_bits = MEMSLOT_GENERATION_BITS;
+ rom->slot_id_bits = MEMSLOT_SLOT_BITS;
+ rom->slots_start = 1;
+ rom->slots_end = NUM_MEMSLOTS - 1;
+ rom->n_surfaces = cpu_to_le32(d->ssd.num_surfaces);
+
+ for (i = 0, n = 0; i < ARRAY_SIZE(qxl_modes); i++) {
+ fb = qxl_modes[i].y_res * qxl_modes[i].stride;
+ if (fb > d->vgamem_size) {
+ continue;
+ }
+ modes->modes[n].id = cpu_to_le32(i);
+ modes->modes[n].x_res = cpu_to_le32(qxl_modes[i].x_res);
+ modes->modes[n].y_res = cpu_to_le32(qxl_modes[i].y_res);
+ modes->modes[n].bits = cpu_to_le32(qxl_modes[i].bits);
+ modes->modes[n].stride = cpu_to_le32(qxl_modes[i].stride);
+ modes->modes[n].x_mili = cpu_to_le32(qxl_modes[i].x_mili);
+ modes->modes[n].y_mili = cpu_to_le32(qxl_modes[i].y_mili);
+ modes->modes[n].orientation = cpu_to_le32(qxl_modes[i].orientation);
+ n++;
+ }
+ modes->n_modes = cpu_to_le32(n);
+
+ ram_header_size = ALIGN(sizeof(QXLRam), 4096);
+ surface0_area_size = ALIGN(d->vgamem_size, 4096);
+ num_pages = d->vga.vram_size;
+ num_pages -= ram_header_size;
+ num_pages -= surface0_area_size;
+ num_pages = num_pages / QXL_PAGE_SIZE;
+
+ assert(ram_header_size + surface0_area_size <= d->vga.vram_size);
+
+ rom->draw_area_offset = cpu_to_le32(0);
+ rom->surface0_area_size = cpu_to_le32(surface0_area_size);
+ rom->pages_offset = cpu_to_le32(surface0_area_size);
+ rom->num_pages = cpu_to_le32(num_pages);
+ rom->ram_header_offset = cpu_to_le32(d->vga.vram_size - ram_header_size);
+
+ d->shadow_rom = *rom;
+ d->rom = rom;
+ d->modes = modes;
+}
+
+static void init_qxl_ram(PCIQXLDevice *d)
+{
+ uint8_t *buf;
+ uint64_t *item;
+
+ buf = d->vga.vram_ptr;
+ d->ram = (QXLRam *)(buf + le32_to_cpu(d->shadow_rom.ram_header_offset));
+ d->ram->magic = cpu_to_le32(QXL_RAM_MAGIC);
+ d->ram->int_pending = cpu_to_le32(0);
+ d->ram->int_mask = cpu_to_le32(0);
+ d->ram->update_surface = 0;
+ d->ram->monitors_config = 0;
+ SPICE_RING_INIT(&d->ram->cmd_ring);
+ SPICE_RING_INIT(&d->ram->cursor_ring);
+ SPICE_RING_INIT(&d->ram->release_ring);
+ SPICE_RING_PROD_ITEM(d, &d->ram->release_ring, item);
+ assert(item);
+ *item = 0;
+ qxl_ring_set_dirty(d);
+}
+
+/* can be called from spice server thread context */
+static void qxl_set_dirty(MemoryRegion *mr, ram_addr_t addr, ram_addr_t end)
+{
+ memory_region_set_dirty(mr, addr, end - addr);
+}
+
+static void qxl_rom_set_dirty(PCIQXLDevice *qxl)
+{
+ qxl_set_dirty(&qxl->rom_bar, 0, qxl->rom_size);
+}
+
+/* called from spice server thread context only */
+static void qxl_ram_set_dirty(PCIQXLDevice *qxl, void *ptr)
+{
+ void *base = qxl->vga.vram_ptr;
+ intptr_t offset;
+
+ offset = ptr - base;
+ assert(offset < qxl->vga.vram_size);
+ qxl_set_dirty(&qxl->vga.vram, offset, offset + 3);
+}
+
+/* can be called from spice server thread context */
+static void qxl_ring_set_dirty(PCIQXLDevice *qxl)
+{
+ ram_addr_t addr = qxl->shadow_rom.ram_header_offset;
+ ram_addr_t end = qxl->vga.vram_size;
+ qxl_set_dirty(&qxl->vga.vram, addr, end);
+}
+
+/*
+ * keep track of some command state, for savevm/loadvm.
+ * called from spice server thread context only
+ */
+static int qxl_track_command(PCIQXLDevice *qxl, struct QXLCommandExt *ext)
+{
+ switch (le32_to_cpu(ext->cmd.type)) {
+ case QXL_CMD_SURFACE:
+ {
+ QXLSurfaceCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
+
+ if (!cmd) {
+ return 1;
+ }
+ uint32_t id = le32_to_cpu(cmd->surface_id);
+
+ if (id >= qxl->ssd.num_surfaces) {
+ qxl_set_guest_bug(qxl, "QXL_CMD_SURFACE id %d >= %d", id,
+ qxl->ssd.num_surfaces);
+ return 1;
+ }
+ if (cmd->type == QXL_SURFACE_CMD_CREATE &&
+ (cmd->u.surface_create.stride & 0x03) != 0) {
+ qxl_set_guest_bug(qxl, "QXL_CMD_SURFACE stride = %d %% 4 != 0\n",
+ cmd->u.surface_create.stride);
+ return 1;
+ }
+ qemu_mutex_lock(&qxl->track_lock);
+ if (cmd->type == QXL_SURFACE_CMD_CREATE) {
+ qxl->guest_surfaces.cmds[id] = ext->cmd.data;
+ qxl->guest_surfaces.count++;
+ if (qxl->guest_surfaces.max < qxl->guest_surfaces.count)
+ qxl->guest_surfaces.max = qxl->guest_surfaces.count;
+ }
+ if (cmd->type == QXL_SURFACE_CMD_DESTROY) {
+ qxl->guest_surfaces.cmds[id] = 0;
+ qxl->guest_surfaces.count--;
+ }
+ qemu_mutex_unlock(&qxl->track_lock);
+ break;
+ }
+ case QXL_CMD_CURSOR:
+ {
+ QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
+
+ if (!cmd) {
+ return 1;
+ }
+ if (cmd->type == QXL_CURSOR_SET) {
+ qemu_mutex_lock(&qxl->track_lock);
+ qxl->guest_cursor = ext->cmd.data;
+ qemu_mutex_unlock(&qxl->track_lock);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+/* spice display interface callbacks */
+
+static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ trace_qxl_interface_attach_worker(qxl->id);
+ qxl->ssd.worker = qxl_worker;
+}
+
+static void interface_set_compression_level(QXLInstance *sin, int level)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ trace_qxl_interface_set_compression_level(qxl->id, level);
+ qxl->shadow_rom.compression_level = cpu_to_le32(level);
+ qxl->rom->compression_level = cpu_to_le32(level);
+ qxl_rom_set_dirty(qxl);
+}
+
+static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ if (!qemu_spice_display_is_running(&qxl->ssd)) {
+ return;
+ }
+
+ trace_qxl_interface_set_mm_time(qxl->id, mm_time);
+ qxl->shadow_rom.mm_clock = cpu_to_le32(mm_time);
+ qxl->rom->mm_clock = cpu_to_le32(mm_time);
+ qxl_rom_set_dirty(qxl);
+}
+
+static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ trace_qxl_interface_get_init_info(qxl->id);
+ info->memslot_gen_bits = MEMSLOT_GENERATION_BITS;
+ info->memslot_id_bits = MEMSLOT_SLOT_BITS;
+ info->num_memslots = NUM_MEMSLOTS;
+ info->num_memslots_groups = NUM_MEMSLOTS_GROUPS;
+ info->internal_groupslot_id = 0;
+ info->qxl_ram_size =
+ le32_to_cpu(qxl->shadow_rom.num_pages) << QXL_PAGE_BITS;
+ info->n_surfaces = qxl->ssd.num_surfaces;
+}
+
+static const char *qxl_mode_to_string(int mode)
+{
+ switch (mode) {
+ case QXL_MODE_COMPAT:
+ return "compat";
+ case QXL_MODE_NATIVE:
+ return "native";
+ case QXL_MODE_UNDEFINED:
+ return "undefined";
+ case QXL_MODE_VGA:
+ return "vga";
+ }
+ return "INVALID";
+}
+
+static const char *io_port_to_string(uint32_t io_port)
+{
+ if (io_port >= QXL_IO_RANGE_SIZE) {
+ return "out of range";
+ }
+ static const char *io_port_to_string[QXL_IO_RANGE_SIZE + 1] = {
+ [QXL_IO_NOTIFY_CMD] = "QXL_IO_NOTIFY_CMD",
+ [QXL_IO_NOTIFY_CURSOR] = "QXL_IO_NOTIFY_CURSOR",
+ [QXL_IO_UPDATE_AREA] = "QXL_IO_UPDATE_AREA",
+ [QXL_IO_UPDATE_IRQ] = "QXL_IO_UPDATE_IRQ",
+ [QXL_IO_NOTIFY_OOM] = "QXL_IO_NOTIFY_OOM",
+ [QXL_IO_RESET] = "QXL_IO_RESET",
+ [QXL_IO_SET_MODE] = "QXL_IO_SET_MODE",
+ [QXL_IO_LOG] = "QXL_IO_LOG",
+ [QXL_IO_MEMSLOT_ADD] = "QXL_IO_MEMSLOT_ADD",
+ [QXL_IO_MEMSLOT_DEL] = "QXL_IO_MEMSLOT_DEL",
+ [QXL_IO_DETACH_PRIMARY] = "QXL_IO_DETACH_PRIMARY",
+ [QXL_IO_ATTACH_PRIMARY] = "QXL_IO_ATTACH_PRIMARY",
+ [QXL_IO_CREATE_PRIMARY] = "QXL_IO_CREATE_PRIMARY",
+ [QXL_IO_DESTROY_PRIMARY] = "QXL_IO_DESTROY_PRIMARY",
+ [QXL_IO_DESTROY_SURFACE_WAIT] = "QXL_IO_DESTROY_SURFACE_WAIT",
+ [QXL_IO_DESTROY_ALL_SURFACES] = "QXL_IO_DESTROY_ALL_SURFACES",
+ [QXL_IO_UPDATE_AREA_ASYNC] = "QXL_IO_UPDATE_AREA_ASYNC",
+ [QXL_IO_MEMSLOT_ADD_ASYNC] = "QXL_IO_MEMSLOT_ADD_ASYNC",
+ [QXL_IO_CREATE_PRIMARY_ASYNC] = "QXL_IO_CREATE_PRIMARY_ASYNC",
+ [QXL_IO_DESTROY_PRIMARY_ASYNC] = "QXL_IO_DESTROY_PRIMARY_ASYNC",
+ [QXL_IO_DESTROY_SURFACE_ASYNC] = "QXL_IO_DESTROY_SURFACE_ASYNC",
+ [QXL_IO_DESTROY_ALL_SURFACES_ASYNC]
+ = "QXL_IO_DESTROY_ALL_SURFACES_ASYNC",
+ [QXL_IO_FLUSH_SURFACES_ASYNC] = "QXL_IO_FLUSH_SURFACES_ASYNC",
+ [QXL_IO_FLUSH_RELEASE] = "QXL_IO_FLUSH_RELEASE",
+ [QXL_IO_MONITORS_CONFIG_ASYNC] = "QXL_IO_MONITORS_CONFIG_ASYNC",
+ };
+ return io_port_to_string[io_port];
+}
+
+/* called from spice server thread context only */
+static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ SimpleSpiceUpdate *update;
+ QXLCommandRing *ring;
+ QXLCommand *cmd;
+ int notify, ret;
+
+ trace_qxl_ring_command_check(qxl->id, qxl_mode_to_string(qxl->mode));
+
+ switch (qxl->mode) {
+ case QXL_MODE_VGA:
+ ret = false;
+ qemu_mutex_lock(&qxl->ssd.lock);
+ update = QTAILQ_FIRST(&qxl->ssd.updates);
+ if (update != NULL) {
+ QTAILQ_REMOVE(&qxl->ssd.updates, update, next);
+ *ext = update->ext;
+ ret = true;
+ }
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ if (ret) {
+ trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->mode));
+ qxl_log_command(qxl, "vga", ext);
+ }
+ return ret;
+ case QXL_MODE_COMPAT:
+ case QXL_MODE_NATIVE:
+ case QXL_MODE_UNDEFINED:
+ ring = &qxl->ram->cmd_ring;
+ if (qxl->guest_bug || SPICE_RING_IS_EMPTY(ring)) {
+ return false;
+ }
+ SPICE_RING_CONS_ITEM(qxl, ring, cmd);
+ if (!cmd) {
+ return false;
+ }
+ ext->cmd = *cmd;
+ ext->group_id = MEMSLOT_GROUP_GUEST;
+ ext->flags = qxl->cmdflags;
+ SPICE_RING_POP(ring, notify);
+ qxl_ring_set_dirty(qxl);
+ if (notify) {
+ qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY);
+ }
+ qxl->guest_primary.commands++;
+ qxl_track_command(qxl, ext);
+ qxl_log_command(qxl, "cmd", ext);
+ trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->mode));
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* called from spice server thread context only */
+static int interface_req_cmd_notification(QXLInstance *sin)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ int wait = 1;
+
+ trace_qxl_ring_command_req_notification(qxl->id);
+ switch (qxl->mode) {
+ case QXL_MODE_COMPAT:
+ case QXL_MODE_NATIVE:
+ case QXL_MODE_UNDEFINED:
+ SPICE_RING_CONS_WAIT(&qxl->ram->cmd_ring, wait);
+ qxl_ring_set_dirty(qxl);
+ break;
+ default:
+ /* nothing */
+ break;
+ }
+ return wait;
+}
+
+/* called from spice server thread context only */
+static inline void qxl_push_free_res(PCIQXLDevice *d, int flush)
+{
+ QXLReleaseRing *ring = &d->ram->release_ring;
+ uint64_t *item;
+ int notify;
+
+#define QXL_FREE_BUNCH_SIZE 32
+
+ if (ring->prod - ring->cons + 1 == ring->num_items) {
+ /* ring full -- can't push */
+ return;
+ }
+ if (!flush && d->oom_running) {
+ /* collect everything from oom handler before pushing */
+ return;
+ }
+ if (!flush && d->num_free_res < QXL_FREE_BUNCH_SIZE) {
+ /* collect a bit more before pushing */
+ return;
+ }
+
+ SPICE_RING_PUSH(ring, notify);
+ trace_qxl_ring_res_push(d->id, qxl_mode_to_string(d->mode),
+ d->guest_surfaces.count, d->num_free_res,
+ d->last_release, notify ? "yes" : "no");
+ trace_qxl_ring_res_push_rest(d->id, ring->prod - ring->cons,
+ ring->num_items, ring->prod, ring->cons);
+ if (notify) {
+ qxl_send_events(d, QXL_INTERRUPT_DISPLAY);
+ }
+ SPICE_RING_PROD_ITEM(d, ring, item);
+ if (!item) {
+ return;
+ }
+ *item = 0;
+ d->num_free_res = 0;
+ d->last_release = NULL;
+ qxl_ring_set_dirty(d);
+}
+
+/* called from spice server thread context only */
+static void interface_release_resource(QXLInstance *sin,
+ QXLReleaseInfoExt ext)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ QXLReleaseRing *ring;
+ uint64_t *item, id;
+
+ if (ext.group_id == MEMSLOT_GROUP_HOST) {
+ /* host group -> vga mode update request */
+ QXLCommandExt *cmdext = (void *)(intptr_t)(ext.info->id);
+ SimpleSpiceUpdate *update;
+ g_assert(cmdext->cmd.type == QXL_CMD_DRAW);
+ update = container_of(cmdext, SimpleSpiceUpdate, ext);
+ qemu_spice_destroy_update(&qxl->ssd, update);
+ return;
+ }
+
+ /*
+ * ext->info points into guest-visible memory
+ * pci bar 0, $command.release_info
+ */
+ ring = &qxl->ram->release_ring;
+ SPICE_RING_PROD_ITEM(qxl, ring, item);
+ if (!item) {
+ return;
+ }
+ if (*item == 0) {
+ /* stick head into the ring */
+ id = ext.info->id;
+ ext.info->next = 0;
+ qxl_ram_set_dirty(qxl, &ext.info->next);
+ *item = id;
+ qxl_ring_set_dirty(qxl);
+ } else {
+ /* append item to the list */
+ qxl->last_release->next = ext.info->id;
+ qxl_ram_set_dirty(qxl, &qxl->last_release->next);
+ ext.info->next = 0;
+ qxl_ram_set_dirty(qxl, &ext.info->next);
+ }
+ qxl->last_release = ext.info;
+ qxl->num_free_res++;
+ trace_qxl_ring_res_put(qxl->id, qxl->num_free_res);
+ qxl_push_free_res(qxl, 0);
+}
+
+/* called from spice server thread context only */
+static int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ QXLCursorRing *ring;
+ QXLCommand *cmd;
+ int notify;
+
+ trace_qxl_ring_cursor_check(qxl->id, qxl_mode_to_string(qxl->mode));
+
+ switch (qxl->mode) {
+ case QXL_MODE_COMPAT:
+ case QXL_MODE_NATIVE:
+ case QXL_MODE_UNDEFINED:
+ ring = &qxl->ram->cursor_ring;
+ if (SPICE_RING_IS_EMPTY(ring)) {
+ return false;
+ }
+ SPICE_RING_CONS_ITEM(qxl, ring, cmd);
+ if (!cmd) {
+ return false;
+ }
+ ext->cmd = *cmd;
+ ext->group_id = MEMSLOT_GROUP_GUEST;
+ ext->flags = qxl->cmdflags;
+ SPICE_RING_POP(ring, notify);
+ qxl_ring_set_dirty(qxl);
+ if (notify) {
+ qxl_send_events(qxl, QXL_INTERRUPT_CURSOR);
+ }
+ qxl->guest_primary.commands++;
+ qxl_track_command(qxl, ext);
+ qxl_log_command(qxl, "csr", ext);
+ if (qxl->id == 0) {
+ qxl_render_cursor(qxl, ext);
+ }
+ trace_qxl_ring_cursor_get(qxl->id, qxl_mode_to_string(qxl->mode));
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* called from spice server thread context only */
+static int interface_req_cursor_notification(QXLInstance *sin)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ int wait = 1;
+
+ trace_qxl_ring_cursor_req_notification(qxl->id);
+ switch (qxl->mode) {
+ case QXL_MODE_COMPAT:
+ case QXL_MODE_NATIVE:
+ case QXL_MODE_UNDEFINED:
+ SPICE_RING_CONS_WAIT(&qxl->ram->cursor_ring, wait);
+ qxl_ring_set_dirty(qxl);
+ break;
+ default:
+ /* nothing */
+ break;
+ }
+ return wait;
+}
+
+/* called from spice server thread context */
+static void interface_notify_update(QXLInstance *sin, uint32_t update_id)
+{
+ /*
+ * Called by spice-server as a result of a QXL_CMD_UPDATE which is not in
+ * use by xf86-video-qxl and is defined out in the qxl windows driver.
+ * Probably was at some earlier version that is prior to git start (2009),
+ * and is still guest trigerrable.
+ */
+ fprintf(stderr, "%s: deprecated\n", __func__);
+}
+
+/* called from spice server thread context only */
+static int interface_flush_resources(QXLInstance *sin)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ int ret;
+
+ ret = qxl->num_free_res;
+ if (ret) {
+ qxl_push_free_res(qxl, 1);
+ }
+ return ret;
+}
+
+static void qxl_create_guest_primary_complete(PCIQXLDevice *d);
+
+/* called from spice server thread context only */
+static void interface_async_complete_io(PCIQXLDevice *qxl, QXLCookie *cookie)
+{
+ uint32_t current_async;
+
+ qemu_mutex_lock(&qxl->async_lock);
+ current_async = qxl->current_async;
+ qxl->current_async = QXL_UNDEFINED_IO;
+ qemu_mutex_unlock(&qxl->async_lock);
+
+ trace_qxl_interface_async_complete_io(qxl->id, current_async, cookie);
+ if (!cookie) {
+ fprintf(stderr, "qxl: %s: error, cookie is NULL\n", __func__);
+ return;
+ }
+ if (cookie && current_async != cookie->io) {
+ fprintf(stderr,
+ "qxl: %s: error: current_async = %d != %"
+ PRId64 " = cookie->io\n", __func__, current_async, cookie->io);
+ }
+ switch (current_async) {
+ case QXL_IO_MEMSLOT_ADD_ASYNC:
+ case QXL_IO_DESTROY_PRIMARY_ASYNC:
+ case QXL_IO_UPDATE_AREA_ASYNC:
+ case QXL_IO_FLUSH_SURFACES_ASYNC:
+ case QXL_IO_MONITORS_CONFIG_ASYNC:
+ break;
+ case QXL_IO_CREATE_PRIMARY_ASYNC:
+ qxl_create_guest_primary_complete(qxl);
+ break;
+ case QXL_IO_DESTROY_ALL_SURFACES_ASYNC:
+ qxl_spice_destroy_surfaces_complete(qxl);
+ break;
+ case QXL_IO_DESTROY_SURFACE_ASYNC:
+ qxl_spice_destroy_surface_wait_complete(qxl, cookie->u.surface_id);
+ break;
+ default:
+ fprintf(stderr, "qxl: %s: unexpected current_async %d\n", __func__,
+ current_async);
+ }
+ qxl_send_events(qxl, QXL_INTERRUPT_IO_CMD);
+}
+
+/* called from spice server thread context only */
+static void interface_update_area_complete(QXLInstance *sin,
+ uint32_t surface_id,
+ QXLRect *dirty, uint32_t num_updated_rects)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ int i;
+ int qxl_i;
+
+ qemu_mutex_lock(&qxl->ssd.lock);
+ if (surface_id != 0 || !qxl->render_update_cookie_num) {
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ return;
+ }
+ trace_qxl_interface_update_area_complete(qxl->id, surface_id, dirty->left,
+ dirty->right, dirty->top, dirty->bottom);
+ trace_qxl_interface_update_area_complete_rest(qxl->id, num_updated_rects);
+ if (qxl->num_dirty_rects + num_updated_rects > QXL_NUM_DIRTY_RECTS) {
+ /*
+ * overflow - treat this as a full update. Not expected to be common.
+ */
+ trace_qxl_interface_update_area_complete_overflow(qxl->id,
+ QXL_NUM_DIRTY_RECTS);
+ qxl->guest_primary.resized = 1;
+ }
+ if (qxl->guest_primary.resized) {
+ /*
+ * Don't bother copying or scheduling the bh since we will flip
+ * the whole area anyway on completion of the update_area async call
+ */
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ return;
+ }
+ qxl_i = qxl->num_dirty_rects;
+ for (i = 0; i < num_updated_rects; i++) {
+ qxl->dirty[qxl_i++] = dirty[i];
+ }
+ qxl->num_dirty_rects += num_updated_rects;
+ trace_qxl_interface_update_area_complete_schedule_bh(qxl->id,
+ qxl->num_dirty_rects);
+ qemu_bh_schedule(qxl->update_area_bh);
+ qemu_mutex_unlock(&qxl->ssd.lock);
+}
+
+/* called from spice server thread context only */
+static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ QXLCookie *cookie = (QXLCookie *)(uintptr_t)cookie_token;
+
+ switch (cookie->type) {
+ case QXL_COOKIE_TYPE_IO:
+ interface_async_complete_io(qxl, cookie);
+ g_free(cookie);
+ break;
+ case QXL_COOKIE_TYPE_RENDER_UPDATE_AREA:
+ qxl_render_update_area_done(qxl, cookie);
+ break;
+ case QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG:
+ break;
+ default:
+ fprintf(stderr, "qxl: %s: unexpected cookie type %d\n",
+ __func__, cookie->type);
+ g_free(cookie);
+ }
+}
+
+/* called from spice server thread context only */
+static void interface_set_client_capabilities(QXLInstance *sin,
+ uint8_t client_present,
+ uint8_t caps[58])
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ if (qxl->revision < 4) {
+ trace_qxl_set_client_capabilities_unsupported_by_revision(qxl->id,
+ qxl->revision);
+ return;
+ }
+
+ if (runstate_check(RUN_STATE_INMIGRATE) ||
+ runstate_check(RUN_STATE_POSTMIGRATE)) {
+ return;
+ }
+
+ qxl->shadow_rom.client_present = client_present;
+ memcpy(qxl->shadow_rom.client_capabilities, caps,
+ sizeof(qxl->shadow_rom.client_capabilities));
+ qxl->rom->client_present = client_present;
+ memcpy(qxl->rom->client_capabilities, caps,
+ sizeof(qxl->rom->client_capabilities));
+ qxl_rom_set_dirty(qxl);
+
+ qxl_send_events(qxl, QXL_INTERRUPT_CLIENT);
+}
+
+static uint32_t qxl_crc32(const uint8_t *p, unsigned len)
+{
+ /*
+ * zlib xors the seed with 0xffffffff, and xors the result
+ * again with 0xffffffff; Both are not done with linux's crc32,
+ * which we want to be compatible with, so undo that.
+ */
+ return crc32(0xffffffff, p, len) ^ 0xffffffff;
+}
+
+/* called from main context only */
+static int interface_client_monitors_config(QXLInstance *sin,
+ VDAgentMonitorsConfig *monitors_config)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ QXLRom *rom = memory_region_get_ram_ptr(&qxl->rom_bar);
+ int i;
+ unsigned max_outputs = ARRAY_SIZE(rom->client_monitors_config.heads);
+
+ if (qxl->revision < 4) {
+ trace_qxl_client_monitors_config_unsupported_by_device(qxl->id,
+ qxl->revision);
+ return 0;
+ }
+ /*
+ * Older windows drivers set int_mask to 0 when their ISR is called,
+ * then later set it to ~0. So it doesn't relate to the actual interrupts
+ * handled. However, they are old, so clearly they don't support this
+ * interrupt
+ */
+ if (qxl->ram->int_mask == 0 || qxl->ram->int_mask == ~0 ||
+ !(qxl->ram->int_mask & QXL_INTERRUPT_CLIENT_MONITORS_CONFIG)) {
+ trace_qxl_client_monitors_config_unsupported_by_guest(qxl->id,
+ qxl->ram->int_mask,
+ monitors_config);
+ return 0;
+ }
+ if (!monitors_config) {
+ return 1;
+ }
+
+#if SPICE_SERVER_VERSION >= 0x000c06 /* release 0.12.6 */
+ /* limit number of outputs based on setting limit */
+ if (qxl->max_outputs && qxl->max_outputs <= max_outputs) {
+ max_outputs = qxl->max_outputs;
+ }
+#endif
+
+ memset(&rom->client_monitors_config, 0,
+ sizeof(rom->client_monitors_config));
+ rom->client_monitors_config.count = monitors_config->num_of_monitors;
+ /* monitors_config->flags ignored */
+ if (rom->client_monitors_config.count >= max_outputs) {
+ trace_qxl_client_monitors_config_capped(qxl->id,
+ monitors_config->num_of_monitors,
+ max_outputs);
+ rom->client_monitors_config.count = max_outputs;
+ }
+ for (i = 0 ; i < rom->client_monitors_config.count ; ++i) {
+ VDAgentMonConfig *monitor = &monitors_config->monitors[i];
+ QXLURect *rect = &rom->client_monitors_config.heads[i];
+ /* monitor->depth ignored */
+ rect->left = monitor->x;
+ rect->top = monitor->y;
+ rect->right = monitor->x + monitor->width;
+ rect->bottom = monitor->y + monitor->height;
+ }
+ rom->client_monitors_config_crc = qxl_crc32(
+ (const uint8_t *)&rom->client_monitors_config,
+ sizeof(rom->client_monitors_config));
+ trace_qxl_client_monitors_config_crc(qxl->id,
+ sizeof(rom->client_monitors_config),
+ rom->client_monitors_config_crc);
+
+ trace_qxl_interrupt_client_monitors_config(qxl->id,
+ rom->client_monitors_config.count,
+ rom->client_monitors_config.heads);
+ qxl_send_events(qxl, QXL_INTERRUPT_CLIENT_MONITORS_CONFIG);
+ return 1;
+}
+
+static const QXLInterface qxl_interface = {
+ .base.type = SPICE_INTERFACE_QXL,
+ .base.description = "qxl gpu",
+ .base.major_version = SPICE_INTERFACE_QXL_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_QXL_MINOR,
+
+ .attache_worker = interface_attach_worker,
+ .set_compression_level = interface_set_compression_level,
+ .set_mm_time = interface_set_mm_time,
+ .get_init_info = interface_get_init_info,
+
+ /* the callbacks below are called from spice server thread context */
+ .get_command = interface_get_command,
+ .req_cmd_notification = interface_req_cmd_notification,
+ .release_resource = interface_release_resource,
+ .get_cursor_command = interface_get_cursor_command,
+ .req_cursor_notification = interface_req_cursor_notification,
+ .notify_update = interface_notify_update,
+ .flush_resources = interface_flush_resources,
+ .async_complete = interface_async_complete,
+ .update_area_complete = interface_update_area_complete,
+ .set_client_capabilities = interface_set_client_capabilities,
+ .client_monitors_config = interface_client_monitors_config,
+};
+
+static const GraphicHwOps qxl_ops = {
+ .gfx_update = qxl_hw_update,
+};
+
+static void qxl_enter_vga_mode(PCIQXLDevice *d)
+{
+ if (d->mode == QXL_MODE_VGA) {
+ return;
+ }
+ trace_qxl_enter_vga_mode(d->id);
+#if SPICE_SERVER_VERSION >= 0x000c03 /* release 0.12.3 */
+ spice_qxl_driver_unload(&d->ssd.qxl);
+#endif
+ graphic_console_set_hwops(d->ssd.dcl.con, d->vga.hw_ops, &d->vga);
+ update_displaychangelistener(&d->ssd.dcl, GUI_REFRESH_INTERVAL_DEFAULT);
+ qemu_spice_create_host_primary(&d->ssd);
+ d->mode = QXL_MODE_VGA;
+ vga_dirty_log_start(&d->vga);
+ graphic_hw_update(d->vga.con);
+}
+
+static void qxl_exit_vga_mode(PCIQXLDevice *d)
+{
+ if (d->mode != QXL_MODE_VGA) {
+ return;
+ }
+ trace_qxl_exit_vga_mode(d->id);
+ graphic_console_set_hwops(d->ssd.dcl.con, &qxl_ops, d);
+ update_displaychangelistener(&d->ssd.dcl, GUI_REFRESH_INTERVAL_IDLE);
+ vga_dirty_log_stop(&d->vga);
+ qxl_destroy_primary(d, QXL_SYNC);
+}
+
+static void qxl_update_irq(PCIQXLDevice *d)
+{
+ uint32_t pending = le32_to_cpu(d->ram->int_pending);
+ uint32_t mask = le32_to_cpu(d->ram->int_mask);
+ int level = !!(pending & mask);
+ pci_set_irq(&d->pci, level);
+ qxl_ring_set_dirty(d);
+}
+
+static void qxl_check_state(PCIQXLDevice *d)
+{
+ QXLRam *ram = d->ram;
+ int spice_display_running = qemu_spice_display_is_running(&d->ssd);
+
+ assert(!spice_display_running || SPICE_RING_IS_EMPTY(&ram->cmd_ring));
+ assert(!spice_display_running || SPICE_RING_IS_EMPTY(&ram->cursor_ring));
+}
+
+static void qxl_reset_state(PCIQXLDevice *d)
+{
+ QXLRom *rom = d->rom;
+
+ qxl_check_state(d);
+ d->shadow_rom.update_id = cpu_to_le32(0);
+ *rom = d->shadow_rom;
+ qxl_rom_set_dirty(d);
+ init_qxl_ram(d);
+ d->num_free_res = 0;
+ d->last_release = NULL;
+ memset(&d->ssd.dirty, 0, sizeof(d->ssd.dirty));
+ qxl_update_irq(d);
+}
+
+static void qxl_soft_reset(PCIQXLDevice *d)
+{
+ trace_qxl_soft_reset(d->id);
+ qxl_check_state(d);
+ qxl_clear_guest_bug(d);
+ d->current_async = QXL_UNDEFINED_IO;
+
+ if (d->id == 0) {
+ qxl_enter_vga_mode(d);
+ } else {
+ d->mode = QXL_MODE_UNDEFINED;
+ }
+}
+
+static void qxl_hard_reset(PCIQXLDevice *d, int loadvm)
+{
+ bool startstop = qemu_spice_display_is_running(&d->ssd);
+
+ trace_qxl_hard_reset(d->id, loadvm);
+
+ if (startstop) {
+ qemu_spice_display_stop();
+ }
+
+ qxl_spice_reset_cursor(d);
+ qxl_spice_reset_image_cache(d);
+ qxl_reset_surfaces(d);
+ qxl_reset_memslots(d);
+
+ /* pre loadvm reset must not touch QXLRam. This lives in
+ * device memory, is migrated together with RAM and thus
+ * already loaded at this point */
+ if (!loadvm) {
+ qxl_reset_state(d);
+ }
+ qemu_spice_create_host_memslot(&d->ssd);
+ qxl_soft_reset(d);
+
+ if (startstop) {
+ qemu_spice_display_start();
+ }
+}
+
+static void qxl_reset_handler(DeviceState *dev)
+{
+ PCIQXLDevice *d = PCI_QXL(PCI_DEVICE(dev));
+
+ qxl_hard_reset(d, 0);
+}
+
+static void qxl_vga_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ VGACommonState *vga = opaque;
+ PCIQXLDevice *qxl = container_of(vga, PCIQXLDevice, vga);
+
+ trace_qxl_io_write_vga(qxl->id, qxl_mode_to_string(qxl->mode), addr, val);
+ if (qxl->mode != QXL_MODE_VGA) {
+ qxl_destroy_primary(qxl, QXL_SYNC);
+ qxl_soft_reset(qxl);
+ }
+ vga_ioport_write(opaque, addr, val);
+}
+
+static const MemoryRegionPortio qxl_vga_portio_list[] = {
+ { 0x04, 2, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3b4 */
+ { 0x0a, 1, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3ba */
+ { 0x10, 16, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3c0 */
+ { 0x24, 2, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3d4 */
+ { 0x2a, 1, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3da */
+ PORTIO_END_OF_LIST(),
+};
+
+static int qxl_add_memslot(PCIQXLDevice *d, uint32_t slot_id, uint64_t delta,
+ qxl_async_io async)
+{
+ static const int regions[] = {
+ QXL_RAM_RANGE_INDEX,
+ QXL_VRAM_RANGE_INDEX,
+ QXL_VRAM64_RANGE_INDEX,
+ };
+ uint64_t guest_start;
+ uint64_t guest_end;
+ int pci_region;
+ pcibus_t pci_start;
+ pcibus_t pci_end;
+ intptr_t virt_start;
+ QXLDevMemSlot memslot;
+ int i;
+
+ guest_start = le64_to_cpu(d->guest_slots[slot_id].slot.mem_start);
+ guest_end = le64_to_cpu(d->guest_slots[slot_id].slot.mem_end);
+
+ trace_qxl_memslot_add_guest(d->id, slot_id, guest_start, guest_end);
+
+ if (slot_id >= NUM_MEMSLOTS) {
+ qxl_set_guest_bug(d, "%s: slot_id >= NUM_MEMSLOTS %d >= %d", __func__,
+ slot_id, NUM_MEMSLOTS);
+ return 1;
+ }
+ if (guest_start > guest_end) {
+ qxl_set_guest_bug(d, "%s: guest_start > guest_end 0x%" PRIx64
+ " > 0x%" PRIx64, __func__, guest_start, guest_end);
+ return 1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(regions); i++) {
+ pci_region = regions[i];
+ pci_start = d->pci.io_regions[pci_region].addr;
+ pci_end = pci_start + d->pci.io_regions[pci_region].size;
+ /* mapped? */
+ if (pci_start == -1) {
+ continue;
+ }
+ /* start address in range ? */
+ if (guest_start < pci_start || guest_start > pci_end) {
+ continue;
+ }
+ /* end address in range ? */
+ if (guest_end > pci_end) {
+ continue;
+ }
+ /* passed */
+ break;
+ }
+ if (i == ARRAY_SIZE(regions)) {
+ qxl_set_guest_bug(d, "%s: finished loop without match", __func__);
+ return 1;
+ }
+
+ switch (pci_region) {
+ case QXL_RAM_RANGE_INDEX:
+ virt_start = (intptr_t)memory_region_get_ram_ptr(&d->vga.vram);
+ break;
+ case QXL_VRAM_RANGE_INDEX:
+ case 4 /* vram 64bit */:
+ virt_start = (intptr_t)memory_region_get_ram_ptr(&d->vram_bar);
+ break;
+ default:
+ /* should not happen */
+ qxl_set_guest_bug(d, "%s: pci_region = %d", __func__, pci_region);
+ return 1;
+ }
+
+ memslot.slot_id = slot_id;
+ memslot.slot_group_id = MEMSLOT_GROUP_GUEST; /* guest group */
+ memslot.virt_start = virt_start + (guest_start - pci_start);
+ memslot.virt_end = virt_start + (guest_end - pci_start);
+ memslot.addr_delta = memslot.virt_start - delta;
+ memslot.generation = d->rom->slot_generation = 0;
+ qxl_rom_set_dirty(d);
+
+ qemu_spice_add_memslot(&d->ssd, &memslot, async);
+ d->guest_slots[slot_id].ptr = (void*)memslot.virt_start;
+ d->guest_slots[slot_id].size = memslot.virt_end - memslot.virt_start;
+ d->guest_slots[slot_id].delta = delta;
+ d->guest_slots[slot_id].active = 1;
+ return 0;
+}
+
+static void qxl_del_memslot(PCIQXLDevice *d, uint32_t slot_id)
+{
+ qemu_spice_del_memslot(&d->ssd, MEMSLOT_GROUP_HOST, slot_id);
+ d->guest_slots[slot_id].active = 0;
+}
+
+static void qxl_reset_memslots(PCIQXLDevice *d)
+{
+ qxl_spice_reset_memslots(d);
+ memset(&d->guest_slots, 0, sizeof(d->guest_slots));
+}
+
+static void qxl_reset_surfaces(PCIQXLDevice *d)
+{
+ trace_qxl_reset_surfaces(d->id);
+ d->mode = QXL_MODE_UNDEFINED;
+ qxl_spice_destroy_surfaces(d, QXL_SYNC);
+}
+
+/* can be also called from spice server thread context */
+void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL pqxl, int group_id)
+{
+ uint64_t phys = le64_to_cpu(pqxl);
+ uint32_t slot = (phys >> (64 - 8)) & 0xff;
+ uint64_t offset = phys & 0xffffffffffff;
+
+ switch (group_id) {
+ case MEMSLOT_GROUP_HOST:
+ return (void *)(intptr_t)offset;
+ case MEMSLOT_GROUP_GUEST:
+ if (slot >= NUM_MEMSLOTS) {
+ qxl_set_guest_bug(qxl, "slot too large %d >= %d", slot,
+ NUM_MEMSLOTS);
+ return NULL;
+ }
+ if (!qxl->guest_slots[slot].active) {
+ qxl_set_guest_bug(qxl, "inactive slot %d\n", slot);
+ return NULL;
+ }
+ if (offset < qxl->guest_slots[slot].delta) {
+ qxl_set_guest_bug(qxl,
+ "slot %d offset %"PRIu64" < delta %"PRIu64"\n",
+ slot, offset, qxl->guest_slots[slot].delta);
+ return NULL;
+ }
+ offset -= qxl->guest_slots[slot].delta;
+ if (offset > qxl->guest_slots[slot].size) {
+ qxl_set_guest_bug(qxl,
+ "slot %d offset %"PRIu64" > size %"PRIu64"\n",
+ slot, offset, qxl->guest_slots[slot].size);
+ return NULL;
+ }
+ return qxl->guest_slots[slot].ptr + offset;
+ }
+ return NULL;
+}
+
+static void qxl_create_guest_primary_complete(PCIQXLDevice *qxl)
+{
+ /* for local rendering */
+ qxl_render_resize(qxl);
+}
+
+static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm,
+ qxl_async_io async)
+{
+ QXLDevSurfaceCreate surface;
+ QXLSurfaceCreate *sc = &qxl->guest_primary.surface;
+ uint32_t requested_height = le32_to_cpu(sc->height);
+ int requested_stride = le32_to_cpu(sc->stride);
+
+ if (requested_stride == INT32_MIN ||
+ abs(requested_stride) * (uint64_t)requested_height
+ > qxl->vgamem_size) {
+ qxl_set_guest_bug(qxl, "%s: requested primary larger than framebuffer"
+ " stride %d x height %" PRIu32 " > %" PRIu32,
+ __func__, requested_stride, requested_height,
+ qxl->vgamem_size);
+ return;
+ }
+
+ if (qxl->mode == QXL_MODE_NATIVE) {
+ qxl_set_guest_bug(qxl, "%s: nop since already in QXL_MODE_NATIVE",
+ __func__);
+ }
+ qxl_exit_vga_mode(qxl);
+
+ surface.format = le32_to_cpu(sc->format);
+ surface.height = le32_to_cpu(sc->height);
+ surface.mem = le64_to_cpu(sc->mem);
+ surface.position = le32_to_cpu(sc->position);
+ surface.stride = le32_to_cpu(sc->stride);
+ surface.width = le32_to_cpu(sc->width);
+ surface.type = le32_to_cpu(sc->type);
+ surface.flags = le32_to_cpu(sc->flags);
+ trace_qxl_create_guest_primary(qxl->id, sc->width, sc->height, sc->mem,
+ sc->format, sc->position);
+ trace_qxl_create_guest_primary_rest(qxl->id, sc->stride, sc->type,
+ sc->flags);
+
+ if ((surface.stride & 0x3) != 0) {
+ qxl_set_guest_bug(qxl, "primary surface stride = %d %% 4 != 0",
+ surface.stride);
+ return;
+ }
+
+ surface.mouse_mode = true;
+ surface.group_id = MEMSLOT_GROUP_GUEST;
+ if (loadvm) {
+ surface.flags |= QXL_SURF_FLAG_KEEP_DATA;
+ }
+
+ qxl->mode = QXL_MODE_NATIVE;
+ qxl->cmdflags = 0;
+ qemu_spice_create_primary_surface(&qxl->ssd, 0, &surface, async);
+
+ if (async == QXL_SYNC) {
+ qxl_create_guest_primary_complete(qxl);
+ }
+}
+
+/* return 1 if surface destoy was initiated (in QXL_ASYNC case) or
+ * done (in QXL_SYNC case), 0 otherwise. */
+static int qxl_destroy_primary(PCIQXLDevice *d, qxl_async_io async)
+{
+ if (d->mode == QXL_MODE_UNDEFINED) {
+ return 0;
+ }
+ trace_qxl_destroy_primary(d->id);
+ d->mode = QXL_MODE_UNDEFINED;
+ qemu_spice_destroy_primary_surface(&d->ssd, 0, async);
+ qxl_spice_reset_cursor(d);
+ return 1;
+}
+
+static void qxl_set_mode(PCIQXLDevice *d, unsigned int modenr, int loadvm)
+{
+ pcibus_t start = d->pci.io_regions[QXL_RAM_RANGE_INDEX].addr;
+ pcibus_t end = d->pci.io_regions[QXL_RAM_RANGE_INDEX].size + start;
+ QXLMode *mode = d->modes->modes + modenr;
+ uint64_t devmem = d->pci.io_regions[QXL_RAM_RANGE_INDEX].addr;
+ QXLMemSlot slot = {
+ .mem_start = start,
+ .mem_end = end
+ };
+
+ if (modenr >= d->modes->n_modes) {
+ qxl_set_guest_bug(d, "mode number out of range");
+ return;
+ }
+
+ QXLSurfaceCreate surface = {
+ .width = mode->x_res,
+ .height = mode->y_res,
+ .stride = -mode->x_res * 4,
+ .format = SPICE_SURFACE_FMT_32_xRGB,
+ .flags = loadvm ? QXL_SURF_FLAG_KEEP_DATA : 0,
+ .mouse_mode = true,
+ .mem = devmem + d->shadow_rom.draw_area_offset,
+ };
+
+ trace_qxl_set_mode(d->id, modenr, mode->x_res, mode->y_res, mode->bits,
+ devmem);
+ if (!loadvm) {
+ qxl_hard_reset(d, 0);
+ }
+
+ d->guest_slots[0].slot = slot;
+ assert(qxl_add_memslot(d, 0, devmem, QXL_SYNC) == 0);
+
+ d->guest_primary.surface = surface;
+ qxl_create_guest_primary(d, 0, QXL_SYNC);
+
+ d->mode = QXL_MODE_COMPAT;
+ d->cmdflags = QXL_COMMAND_FLAG_COMPAT;
+ if (mode->bits == 16) {
+ d->cmdflags |= QXL_COMMAND_FLAG_COMPAT_16BPP;
+ }
+ d->shadow_rom.mode = cpu_to_le32(modenr);
+ d->rom->mode = cpu_to_le32(modenr);
+ qxl_rom_set_dirty(d);
+}
+
+static void ioport_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIQXLDevice *d = opaque;
+ uint32_t io_port = addr;
+ qxl_async_io async = QXL_SYNC;
+ uint32_t orig_io_port = io_port;
+
+ if (d->guest_bug && io_port != QXL_IO_RESET) {
+ return;
+ }
+
+ if (d->revision <= QXL_REVISION_STABLE_V10 &&
+ io_port > QXL_IO_FLUSH_RELEASE) {
+ qxl_set_guest_bug(d, "unsupported io %d for revision %d\n",
+ io_port, d->revision);
+ return;
+ }
+
+ switch (io_port) {
+ case QXL_IO_RESET:
+ case QXL_IO_SET_MODE:
+ case QXL_IO_MEMSLOT_ADD:
+ case QXL_IO_MEMSLOT_DEL:
+ case QXL_IO_CREATE_PRIMARY:
+ case QXL_IO_UPDATE_IRQ:
+ case QXL_IO_LOG:
+ case QXL_IO_MEMSLOT_ADD_ASYNC:
+ case QXL_IO_CREATE_PRIMARY_ASYNC:
+ break;
+ default:
+ if (d->mode != QXL_MODE_VGA) {
+ break;
+ }
+ trace_qxl_io_unexpected_vga_mode(d->id,
+ addr, val, io_port_to_string(io_port));
+ /* be nice to buggy guest drivers */
+ if (io_port >= QXL_IO_UPDATE_AREA_ASYNC &&
+ io_port < QXL_IO_RANGE_SIZE) {
+ qxl_send_events(d, QXL_INTERRUPT_IO_CMD);
+ }
+ return;
+ }
+
+ /* we change the io_port to avoid ifdeffery in the main switch */
+ orig_io_port = io_port;
+ switch (io_port) {
+ case QXL_IO_UPDATE_AREA_ASYNC:
+ io_port = QXL_IO_UPDATE_AREA;
+ goto async_common;
+ case QXL_IO_MEMSLOT_ADD_ASYNC:
+ io_port = QXL_IO_MEMSLOT_ADD;
+ goto async_common;
+ case QXL_IO_CREATE_PRIMARY_ASYNC:
+ io_port = QXL_IO_CREATE_PRIMARY;
+ goto async_common;
+ case QXL_IO_DESTROY_PRIMARY_ASYNC:
+ io_port = QXL_IO_DESTROY_PRIMARY;
+ goto async_common;
+ case QXL_IO_DESTROY_SURFACE_ASYNC:
+ io_port = QXL_IO_DESTROY_SURFACE_WAIT;
+ goto async_common;
+ case QXL_IO_DESTROY_ALL_SURFACES_ASYNC:
+ io_port = QXL_IO_DESTROY_ALL_SURFACES;
+ goto async_common;
+ case QXL_IO_FLUSH_SURFACES_ASYNC:
+ case QXL_IO_MONITORS_CONFIG_ASYNC:
+async_common:
+ async = QXL_ASYNC;
+ qemu_mutex_lock(&d->async_lock);
+ if (d->current_async != QXL_UNDEFINED_IO) {
+ qxl_set_guest_bug(d, "%d async started before last (%d) complete",
+ io_port, d->current_async);
+ qemu_mutex_unlock(&d->async_lock);
+ return;
+ }
+ d->current_async = orig_io_port;
+ qemu_mutex_unlock(&d->async_lock);
+ break;
+ default:
+ break;
+ }
+ trace_qxl_io_write(d->id, qxl_mode_to_string(d->mode),
+ addr, io_port_to_string(addr),
+ val, size, async);
+
+ switch (io_port) {
+ case QXL_IO_UPDATE_AREA:
+ {
+ QXLCookie *cookie = NULL;
+ QXLRect update = d->ram->update_area;
+
+ if (d->ram->update_surface > d->ssd.num_surfaces) {
+ qxl_set_guest_bug(d, "QXL_IO_UPDATE_AREA: invalid surface id %d\n",
+ d->ram->update_surface);
+ break;
+ }
+ if (update.left >= update.right || update.top >= update.bottom ||
+ update.left < 0 || update.top < 0) {
+ qxl_set_guest_bug(d,
+ "QXL_IO_UPDATE_AREA: invalid area (%ux%u)x(%ux%u)\n",
+ update.left, update.top, update.right, update.bottom);
+ if (update.left == update.right || update.top == update.bottom) {
+ /* old drivers may provide empty area, keep going */
+ qxl_clear_guest_bug(d);
+ goto cancel_async;
+ }
+ break;
+ }
+ if (async == QXL_ASYNC) {
+ cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_UPDATE_AREA_ASYNC);
+ cookie->u.area = update;
+ }
+ qxl_spice_update_area(d, d->ram->update_surface,
+ cookie ? &cookie->u.area : &update,
+ NULL, 0, 0, async, cookie);
+ break;
+ }
+ case QXL_IO_NOTIFY_CMD:
+ qemu_spice_wakeup(&d->ssd);
+ break;
+ case QXL_IO_NOTIFY_CURSOR:
+ qemu_spice_wakeup(&d->ssd);
+ break;
+ case QXL_IO_UPDATE_IRQ:
+ qxl_update_irq(d);
+ break;
+ case QXL_IO_NOTIFY_OOM:
+ if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) {
+ break;
+ }
+ d->oom_running = 1;
+ qxl_spice_oom(d);
+ d->oom_running = 0;
+ break;
+ case QXL_IO_SET_MODE:
+ qxl_set_mode(d, val, 0);
+ break;
+ case QXL_IO_LOG:
+ trace_qxl_io_log(d->id, d->ram->log_buf);
+ if (d->guestdebug) {
+ fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), d->ram->log_buf);
+ }
+ break;
+ case QXL_IO_RESET:
+ qxl_hard_reset(d, 0);
+ break;
+ case QXL_IO_MEMSLOT_ADD:
+ if (val >= NUM_MEMSLOTS) {
+ qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_ADD: val out of range");
+ break;
+ }
+ if (d->guest_slots[val].active) {
+ qxl_set_guest_bug(d,
+ "QXL_IO_MEMSLOT_ADD: memory slot already active");
+ break;
+ }
+ d->guest_slots[val].slot = d->ram->mem_slot;
+ qxl_add_memslot(d, val, 0, async);
+ break;
+ case QXL_IO_MEMSLOT_DEL:
+ if (val >= NUM_MEMSLOTS) {
+ qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_DEL: val out of range");
+ break;
+ }
+ qxl_del_memslot(d, val);
+ break;
+ case QXL_IO_CREATE_PRIMARY:
+ if (val != 0) {
+ qxl_set_guest_bug(d, "QXL_IO_CREATE_PRIMARY (async=%d): val != 0",
+ async);
+ goto cancel_async;
+ }
+ d->guest_primary.surface = d->ram->create_surface;
+ qxl_create_guest_primary(d, 0, async);
+ break;
+ case QXL_IO_DESTROY_PRIMARY:
+ if (val != 0) {
+ qxl_set_guest_bug(d, "QXL_IO_DESTROY_PRIMARY (async=%d): val != 0",
+ async);
+ goto cancel_async;
+ }
+ if (!qxl_destroy_primary(d, async)) {
+ trace_qxl_io_destroy_primary_ignored(d->id,
+ qxl_mode_to_string(d->mode));
+ goto cancel_async;
+ }
+ break;
+ case QXL_IO_DESTROY_SURFACE_WAIT:
+ if (val >= d->ssd.num_surfaces) {
+ qxl_set_guest_bug(d, "QXL_IO_DESTROY_SURFACE (async=%d):"
+ "%" PRIu64 " >= NUM_SURFACES", async, val);
+ goto cancel_async;
+ }
+ qxl_spice_destroy_surface_wait(d, val, async);
+ break;
+ case QXL_IO_FLUSH_RELEASE: {
+ QXLReleaseRing *ring = &d->ram->release_ring;
+ if (ring->prod - ring->cons + 1 == ring->num_items) {
+ fprintf(stderr,
+ "ERROR: no flush, full release ring [p%d,%dc]\n",
+ ring->prod, ring->cons);
+ }
+ qxl_push_free_res(d, 1 /* flush */);
+ break;
+ }
+ case QXL_IO_FLUSH_SURFACES_ASYNC:
+ qxl_spice_flush_surfaces_async(d);
+ break;
+ case QXL_IO_DESTROY_ALL_SURFACES:
+ d->mode = QXL_MODE_UNDEFINED;
+ qxl_spice_destroy_surfaces(d, async);
+ break;
+ case QXL_IO_MONITORS_CONFIG_ASYNC:
+ qxl_spice_monitors_config_async(d, 0);
+ break;
+ default:
+ qxl_set_guest_bug(d, "%s: unexpected ioport=0x%x\n", __func__, io_port);
+ }
+ return;
+cancel_async:
+ if (async) {
+ qxl_send_events(d, QXL_INTERRUPT_IO_CMD);
+ qemu_mutex_lock(&d->async_lock);
+ d->current_async = QXL_UNDEFINED_IO;
+ qemu_mutex_unlock(&d->async_lock);
+ }
+}
+
+static uint64_t ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ trace_qxl_io_read_unexpected(qxl->id);
+ return 0xff;
+}
+
+static const MemoryRegionOps qxl_io_ops = {
+ .read = ioport_read,
+ .write = ioport_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void qxl_update_irq_bh(void *opaque)
+{
+ PCIQXLDevice *d = opaque;
+ qxl_update_irq(d);
+}
+
+static void qxl_send_events(PCIQXLDevice *d, uint32_t events)
+{
+ uint32_t old_pending;
+ uint32_t le_events = cpu_to_le32(events);
+
+ trace_qxl_send_events(d->id, events);
+ if (!qemu_spice_display_is_running(&d->ssd)) {
+ /* spice-server tracks guest running state and should not do this */
+ fprintf(stderr, "%s: spice-server bug: guest stopped, ignoring\n",
+ __func__);
+ trace_qxl_send_events_vm_stopped(d->id, events);
+ return;
+ }
+ old_pending = atomic_fetch_or(&d->ram->int_pending, le_events);
+ if ((old_pending & le_events) == le_events) {
+ return;
+ }
+ qemu_bh_schedule(d->update_irq);
+}
+
+/* graphics console */
+
+static void qxl_hw_update(void *opaque)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ qxl_render_update(qxl);
+}
+
+static void qxl_dirty_surfaces(PCIQXLDevice *qxl)
+{
+ uintptr_t vram_start;
+ int i;
+
+ if (qxl->mode != QXL_MODE_NATIVE && qxl->mode != QXL_MODE_COMPAT) {
+ return;
+ }
+
+ /* dirty the primary surface */
+ qxl_set_dirty(&qxl->vga.vram, qxl->shadow_rom.draw_area_offset,
+ qxl->shadow_rom.surface0_area_size);
+
+ vram_start = (uintptr_t)memory_region_get_ram_ptr(&qxl->vram_bar);
+
+ /* dirty the off-screen surfaces */
+ for (i = 0; i < qxl->ssd.num_surfaces; i++) {
+ QXLSurfaceCmd *cmd;
+ intptr_t surface_offset;
+ int surface_size;
+
+ if (qxl->guest_surfaces.cmds[i] == 0) {
+ continue;
+ }
+
+ cmd = qxl_phys2virt(qxl, qxl->guest_surfaces.cmds[i],
+ MEMSLOT_GROUP_GUEST);
+ assert(cmd);
+ assert(cmd->type == QXL_SURFACE_CMD_CREATE);
+ surface_offset = (intptr_t)qxl_phys2virt(qxl,
+ cmd->u.surface_create.data,
+ MEMSLOT_GROUP_GUEST);
+ assert(surface_offset);
+ surface_offset -= vram_start;
+ surface_size = cmd->u.surface_create.height *
+ abs(cmd->u.surface_create.stride);
+ trace_qxl_surfaces_dirty(qxl->id, i, (int)surface_offset, surface_size);
+ qxl_set_dirty(&qxl->vram_bar, surface_offset, surface_size);
+ }
+}
+
+static void qxl_vm_change_state_handler(void *opaque, int running,
+ RunState state)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ if (running) {
+ /*
+ * if qxl_send_events was called from spice server context before
+ * migration ended, qxl_update_irq for these events might not have been
+ * called
+ */
+ qxl_update_irq(qxl);
+ } else {
+ /* make sure surfaces are saved before migration */
+ qxl_dirty_surfaces(qxl);
+ }
+}
+
+/* display change listener */
+
+static void display_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl);
+
+ if (qxl->mode == QXL_MODE_VGA) {
+ qemu_spice_display_update(&qxl->ssd, x, y, w, h);
+ }
+}
+
+static void display_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *surface)
+{
+ PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl);
+
+ qxl->ssd.ds = surface;
+ if (qxl->mode == QXL_MODE_VGA) {
+ qemu_spice_display_switch(&qxl->ssd, surface);
+ }
+}
+
+static void display_refresh(DisplayChangeListener *dcl)
+{
+ PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl);
+
+ if (qxl->mode == QXL_MODE_VGA) {
+ qemu_spice_display_refresh(&qxl->ssd);
+ }
+}
+
+static DisplayChangeListenerOps display_listener_ops = {
+ .dpy_name = "spice/qxl",
+ .dpy_gfx_update = display_update,
+ .dpy_gfx_switch = display_switch,
+ .dpy_refresh = display_refresh,
+};
+
+static void qxl_init_ramsize(PCIQXLDevice *qxl)
+{
+ /* vga mode framebuffer / primary surface (bar 0, first part) */
+ if (qxl->vgamem_size_mb < 8) {
+ qxl->vgamem_size_mb = 8;
+ }
+ /* XXX: we round vgamem_size_mb up to a nearest power of two and it must be
+ * less than vga_common_init()'s maximum on qxl->vga.vram_size (512 now).
+ */
+ if (qxl->vgamem_size_mb > 256) {
+ qxl->vgamem_size_mb = 256;
+ }
+ qxl->vgamem_size = qxl->vgamem_size_mb * 1024 * 1024;
+
+ /* vga ram (bar 0, total) */
+ if (qxl->ram_size_mb != -1) {
+ qxl->vga.vram_size = qxl->ram_size_mb * 1024 * 1024;
+ }
+ if (qxl->vga.vram_size < qxl->vgamem_size * 2) {
+ qxl->vga.vram_size = qxl->vgamem_size * 2;
+ }
+
+ /* vram32 (surfaces, 32bit, bar 1) */
+ if (qxl->vram32_size_mb != -1) {
+ qxl->vram32_size = qxl->vram32_size_mb * 1024 * 1024;
+ }
+ if (qxl->vram32_size < 4096) {
+ qxl->vram32_size = 4096;
+ }
+
+ /* vram (surfaces, 64bit, bar 4+5) */
+ if (qxl->vram_size_mb != -1) {
+ qxl->vram_size = qxl->vram_size_mb * 1024 * 1024;
+ }
+ if (qxl->vram_size < qxl->vram32_size) {
+ qxl->vram_size = qxl->vram32_size;
+ }
+
+ if (qxl->revision == 1) {
+ qxl->vram32_size = 4096;
+ qxl->vram_size = 4096;
+ }
+ qxl->vgamem_size = pow2ceil(qxl->vgamem_size);
+ qxl->vga.vram_size = pow2ceil(qxl->vga.vram_size);
+ qxl->vram32_size = pow2ceil(qxl->vram32_size);
+ qxl->vram_size = pow2ceil(qxl->vram_size);
+}
+
+static void qxl_realize_common(PCIQXLDevice *qxl, Error **errp)
+{
+ uint8_t* config = qxl->pci.config;
+ uint32_t pci_device_rev;
+ uint32_t io_size;
+
+ qxl->mode = QXL_MODE_UNDEFINED;
+ qxl->generation = 1;
+ qxl->num_memslots = NUM_MEMSLOTS;
+ qemu_mutex_init(&qxl->track_lock);
+ qemu_mutex_init(&qxl->async_lock);
+ qxl->current_async = QXL_UNDEFINED_IO;
+ qxl->guest_bug = 0;
+
+ switch (qxl->revision) {
+ case 1: /* spice 0.4 -- qxl-1 */
+ pci_device_rev = QXL_REVISION_STABLE_V04;
+ io_size = 8;
+ break;
+ case 2: /* spice 0.6 -- qxl-2 */
+ pci_device_rev = QXL_REVISION_STABLE_V06;
+ io_size = 16;
+ break;
+ case 3: /* qxl-3 */
+ pci_device_rev = QXL_REVISION_STABLE_V10;
+ io_size = 32; /* PCI region size must be pow2 */
+ break;
+ case 4: /* qxl-4 */
+ pci_device_rev = QXL_REVISION_STABLE_V12;
+ io_size = pow2ceil(QXL_IO_RANGE_SIZE);
+ break;
+ default:
+ error_setg(errp, "Invalid revision %d for qxl device (max %d)",
+ qxl->revision, QXL_DEFAULT_REVISION);
+ return;
+ }
+
+ pci_set_byte(&config[PCI_REVISION_ID], pci_device_rev);
+ pci_set_byte(&config[PCI_INTERRUPT_PIN], 1);
+
+ qxl->rom_size = qxl_rom_size();
+ memory_region_init_ram(&qxl->rom_bar, OBJECT(qxl), "qxl.vrom",
+ qxl->rom_size, &error_abort);
+ vmstate_register_ram(&qxl->rom_bar, &qxl->pci.qdev);
+ init_qxl_rom(qxl);
+ init_qxl_ram(qxl);
+
+ qxl->guest_surfaces.cmds = g_new0(QXLPHYSICAL, qxl->ssd.num_surfaces);
+ memory_region_init_ram(&qxl->vram_bar, OBJECT(qxl), "qxl.vram",
+ qxl->vram_size, &error_abort);
+ vmstate_register_ram(&qxl->vram_bar, &qxl->pci.qdev);
+ memory_region_init_alias(&qxl->vram32_bar, OBJECT(qxl), "qxl.vram32",
+ &qxl->vram_bar, 0, qxl->vram32_size);
+
+ memory_region_init_io(&qxl->io_bar, OBJECT(qxl), &qxl_io_ops, qxl,
+ "qxl-ioports", io_size);
+ if (qxl->id == 0) {
+ vga_dirty_log_start(&qxl->vga);
+ }
+ memory_region_set_flush_coalesced(&qxl->io_bar);
+
+
+ pci_register_bar(&qxl->pci, QXL_IO_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_IO, &qxl->io_bar);
+
+ pci_register_bar(&qxl->pci, QXL_ROM_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->rom_bar);
+
+ pci_register_bar(&qxl->pci, QXL_RAM_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->vga.vram);
+
+ pci_register_bar(&qxl->pci, QXL_VRAM_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->vram32_bar);
+
+ if (qxl->vram32_size < qxl->vram_size) {
+ /*
+ * Make the 64bit vram bar show up only in case it is
+ * configured to be larger than the 32bit vram bar.
+ */
+ pci_register_bar(&qxl->pci, QXL_VRAM64_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_TYPE_64 |
+ PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &qxl->vram_bar);
+ }
+
+ /* print pci bar details */
+ dprint(qxl, 1, "ram/%s: %d MB [region 0]\n",
+ qxl->id == 0 ? "pri" : "sec",
+ qxl->vga.vram_size / (1024*1024));
+ dprint(qxl, 1, "vram/32: %d MB [region 1]\n",
+ qxl->vram32_size / (1024*1024));
+ dprint(qxl, 1, "vram/64: %d MB %s\n",
+ qxl->vram_size / (1024*1024),
+ qxl->vram32_size < qxl->vram_size ? "[region 4]" : "[unmapped]");
+
+ qxl->ssd.qxl.base.sif = &qxl_interface.base;
+ if (qemu_spice_add_display_interface(&qxl->ssd.qxl, qxl->vga.con) != 0) {
+ error_setg(errp, "qxl interface %d.%d not supported by spice-server",
+ SPICE_INTERFACE_QXL_MAJOR, SPICE_INTERFACE_QXL_MINOR);
+ return;
+ }
+ qemu_add_vm_change_state_handler(qxl_vm_change_state_handler, qxl);
+
+ qxl->update_irq = qemu_bh_new(qxl_update_irq_bh, qxl);
+ qxl_reset_state(qxl);
+
+ qxl->update_area_bh = qemu_bh_new(qxl_render_update_area_bh, qxl);
+ qxl->ssd.cursor_bh = qemu_bh_new(qemu_spice_cursor_refresh_bh, &qxl->ssd);
+}
+
+static void qxl_realize_primary(PCIDevice *dev, Error **errp)
+{
+ PCIQXLDevice *qxl = PCI_QXL(dev);
+ VGACommonState *vga = &qxl->vga;
+ Error *local_err = NULL;
+
+ qxl->id = 0;
+ qxl_init_ramsize(qxl);
+ vga->vbe_size = qxl->vgamem_size;
+ vga->vram_size_mb = qxl->vga.vram_size >> 20;
+ vga_common_init(vga, OBJECT(dev), true);
+ vga_init(vga, OBJECT(dev),
+ pci_address_space(dev), pci_address_space_io(dev), false);
+ portio_list_init(&qxl->vga_port_list, OBJECT(dev), qxl_vga_portio_list,
+ vga, "vga");
+ portio_list_set_flush_coalesced(&qxl->vga_port_list);
+ portio_list_add(&qxl->vga_port_list, pci_address_space_io(dev), 0x3b0);
+
+ vga->con = graphic_console_init(DEVICE(dev), 0, &qxl_ops, qxl);
+ qemu_spice_display_init_common(&qxl->ssd);
+
+ qxl_realize_common(qxl, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ qxl->ssd.dcl.ops = &display_listener_ops;
+ qxl->ssd.dcl.con = vga->con;
+ register_displaychangelistener(&qxl->ssd.dcl);
+}
+
+static void qxl_realize_secondary(PCIDevice *dev, Error **errp)
+{
+ static int device_id = 1;
+ PCIQXLDevice *qxl = PCI_QXL(dev);
+
+ qxl->id = device_id++;
+ qxl_init_ramsize(qxl);
+ memory_region_init_ram(&qxl->vga.vram, OBJECT(dev), "qxl.vgavram",
+ qxl->vga.vram_size, &error_abort);
+ vmstate_register_ram(&qxl->vga.vram, &qxl->pci.qdev);
+ qxl->vga.vram_ptr = memory_region_get_ram_ptr(&qxl->vga.vram);
+ qxl->vga.con = graphic_console_init(DEVICE(dev), 0, &qxl_ops, qxl);
+
+ qxl_realize_common(qxl, errp);
+}
+
+static void qxl_pre_save(void *opaque)
+{
+ PCIQXLDevice* d = opaque;
+ uint8_t *ram_start = d->vga.vram_ptr;
+
+ trace_qxl_pre_save(d->id);
+ if (d->last_release == NULL) {
+ d->last_release_offset = 0;
+ } else {
+ d->last_release_offset = (uint8_t *)d->last_release - ram_start;
+ }
+ assert(d->last_release_offset < d->vga.vram_size);
+}
+
+static int qxl_pre_load(void *opaque)
+{
+ PCIQXLDevice* d = opaque;
+
+ trace_qxl_pre_load(d->id);
+ qxl_hard_reset(d, 1);
+ qxl_exit_vga_mode(d);
+ return 0;
+}
+
+static void qxl_create_memslots(PCIQXLDevice *d)
+{
+ int i;
+
+ for (i = 0; i < NUM_MEMSLOTS; i++) {
+ if (!d->guest_slots[i].active) {
+ continue;
+ }
+ qxl_add_memslot(d, i, 0, QXL_SYNC);
+ }
+}
+
+static int qxl_post_load(void *opaque, int version)
+{
+ PCIQXLDevice* d = opaque;
+ uint8_t *ram_start = d->vga.vram_ptr;
+ QXLCommandExt *cmds;
+ int in, out, newmode;
+
+ assert(d->last_release_offset < d->vga.vram_size);
+ if (d->last_release_offset == 0) {
+ d->last_release = NULL;
+ } else {
+ d->last_release = (QXLReleaseInfo *)(ram_start + d->last_release_offset);
+ }
+
+ d->modes = (QXLModes*)((uint8_t*)d->rom + d->rom->modes_offset);
+
+ trace_qxl_post_load(d->id, qxl_mode_to_string(d->mode));
+ newmode = d->mode;
+ d->mode = QXL_MODE_UNDEFINED;
+
+ switch (newmode) {
+ case QXL_MODE_UNDEFINED:
+ qxl_create_memslots(d);
+ break;
+ case QXL_MODE_VGA:
+ qxl_create_memslots(d);
+ qxl_enter_vga_mode(d);
+ break;
+ case QXL_MODE_NATIVE:
+ qxl_create_memslots(d);
+ qxl_create_guest_primary(d, 1, QXL_SYNC);
+
+ /* replay surface-create and cursor-set commands */
+ cmds = g_malloc0(sizeof(QXLCommandExt) * (d->ssd.num_surfaces + 1));
+ for (in = 0, out = 0; in < d->ssd.num_surfaces; in++) {
+ if (d->guest_surfaces.cmds[in] == 0) {
+ continue;
+ }
+ cmds[out].cmd.data = d->guest_surfaces.cmds[in];
+ cmds[out].cmd.type = QXL_CMD_SURFACE;
+ cmds[out].group_id = MEMSLOT_GROUP_GUEST;
+ out++;
+ }
+ if (d->guest_cursor) {
+ cmds[out].cmd.data = d->guest_cursor;
+ cmds[out].cmd.type = QXL_CMD_CURSOR;
+ cmds[out].group_id = MEMSLOT_GROUP_GUEST;
+ out++;
+ }
+ qxl_spice_loadvm_commands(d, cmds, out);
+ g_free(cmds);
+ if (d->guest_monitors_config) {
+ qxl_spice_monitors_config_async(d, 1);
+ }
+ break;
+ case QXL_MODE_COMPAT:
+ /* note: no need to call qxl_create_memslots, qxl_set_mode
+ * creates the mem slot. */
+ qxl_set_mode(d, d->shadow_rom.mode, 1);
+ break;
+ }
+ return 0;
+}
+
+#define QXL_SAVE_VERSION 21
+
+static bool qxl_monitors_config_needed(void *opaque)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ return qxl->guest_monitors_config != 0;
+}
+
+
+static VMStateDescription qxl_memslot = {
+ .name = "qxl-memslot",
+ .version_id = QXL_SAVE_VERSION,
+ .minimum_version_id = QXL_SAVE_VERSION,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(slot.mem_start, struct guest_slots),
+ VMSTATE_UINT64(slot.mem_end, struct guest_slots),
+ VMSTATE_UINT32(active, struct guest_slots),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static VMStateDescription qxl_surface = {
+ .name = "qxl-surface",
+ .version_id = QXL_SAVE_VERSION,
+ .minimum_version_id = QXL_SAVE_VERSION,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(width, QXLSurfaceCreate),
+ VMSTATE_UINT32(height, QXLSurfaceCreate),
+ VMSTATE_INT32(stride, QXLSurfaceCreate),
+ VMSTATE_UINT32(format, QXLSurfaceCreate),
+ VMSTATE_UINT32(position, QXLSurfaceCreate),
+ VMSTATE_UINT32(mouse_mode, QXLSurfaceCreate),
+ VMSTATE_UINT32(flags, QXLSurfaceCreate),
+ VMSTATE_UINT32(type, QXLSurfaceCreate),
+ VMSTATE_UINT64(mem, QXLSurfaceCreate),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static VMStateDescription qxl_vmstate_monitors_config = {
+ .name = "qxl/monitors-config",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = qxl_monitors_config_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(guest_monitors_config, PCIQXLDevice),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static VMStateDescription qxl_vmstate = {
+ .name = "qxl",
+ .version_id = QXL_SAVE_VERSION,
+ .minimum_version_id = QXL_SAVE_VERSION,
+ .pre_save = qxl_pre_save,
+ .pre_load = qxl_pre_load,
+ .post_load = qxl_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(pci, PCIQXLDevice),
+ VMSTATE_STRUCT(vga, PCIQXLDevice, 0, vmstate_vga_common, VGACommonState),
+ VMSTATE_UINT32(shadow_rom.mode, PCIQXLDevice),
+ VMSTATE_UINT32(num_free_res, PCIQXLDevice),
+ VMSTATE_UINT32(last_release_offset, PCIQXLDevice),
+ VMSTATE_UINT32(mode, PCIQXLDevice),
+ VMSTATE_UINT32(ssd.unique, PCIQXLDevice),
+ VMSTATE_INT32_EQUAL(num_memslots, PCIQXLDevice),
+ VMSTATE_STRUCT_ARRAY(guest_slots, PCIQXLDevice, NUM_MEMSLOTS, 0,
+ qxl_memslot, struct guest_slots),
+ VMSTATE_STRUCT(guest_primary.surface, PCIQXLDevice, 0,
+ qxl_surface, QXLSurfaceCreate),
+ VMSTATE_INT32_EQUAL(ssd.num_surfaces, PCIQXLDevice),
+ VMSTATE_VARRAY_INT32(guest_surfaces.cmds, PCIQXLDevice,
+ ssd.num_surfaces, 0,
+ vmstate_info_uint64, uint64_t),
+ VMSTATE_UINT64(guest_cursor, PCIQXLDevice),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &qxl_vmstate_monitors_config,
+ NULL
+ }
+};
+
+static Property qxl_properties[] = {
+ DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size,
+ 64 * 1024 * 1024),
+ DEFINE_PROP_UINT32("vram_size", PCIQXLDevice, vram32_size,
+ 64 * 1024 * 1024),
+ DEFINE_PROP_UINT32("revision", PCIQXLDevice, revision,
+ QXL_DEFAULT_REVISION),
+ DEFINE_PROP_UINT32("debug", PCIQXLDevice, debug, 0),
+ DEFINE_PROP_UINT32("guestdebug", PCIQXLDevice, guestdebug, 0),
+ DEFINE_PROP_UINT32("cmdlog", PCIQXLDevice, cmdlog, 0),
+ DEFINE_PROP_UINT32("ram_size_mb", PCIQXLDevice, ram_size_mb, -1),
+ DEFINE_PROP_UINT32("vram_size_mb", PCIQXLDevice, vram32_size_mb, -1),
+ DEFINE_PROP_UINT32("vram64_size_mb", PCIQXLDevice, vram_size_mb, -1),
+ DEFINE_PROP_UINT32("vgamem_mb", PCIQXLDevice, vgamem_size_mb, 16),
+ DEFINE_PROP_INT32("surfaces", PCIQXLDevice, ssd.num_surfaces, 1024),
+#if SPICE_SERVER_VERSION >= 0x000c06 /* release 0.12.6 */
+ DEFINE_PROP_UINT16("max_outputs", PCIQXLDevice, max_outputs, 0),
+#endif
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void qxl_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->vendor_id = REDHAT_PCI_VENDOR_ID;
+ k->device_id = QXL_DEVICE_ID_STABLE;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->reset = qxl_reset_handler;
+ dc->vmsd = &qxl_vmstate;
+ dc->props = qxl_properties;
+}
+
+static const TypeInfo qxl_pci_type_info = {
+ .name = TYPE_PCI_QXL,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIQXLDevice),
+ .abstract = true,
+ .class_init = qxl_pci_class_init,
+};
+
+static void qxl_primary_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = qxl_realize_primary;
+ k->romfile = "vgabios-qxl.bin";
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ dc->desc = "Spice QXL GPU (primary, vga compatible)";
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo qxl_primary_info = {
+ .name = "qxl-vga",
+ .parent = TYPE_PCI_QXL,
+ .class_init = qxl_primary_class_init,
+};
+
+static void qxl_secondary_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = qxl_realize_secondary;
+ k->class_id = PCI_CLASS_DISPLAY_OTHER;
+ dc->desc = "Spice QXL GPU (secondary)";
+}
+
+static const TypeInfo qxl_secondary_info = {
+ .name = "qxl",
+ .parent = TYPE_PCI_QXL,
+ .class_init = qxl_secondary_class_init,
+};
+
+static void qxl_register_types(void)
+{
+ type_register_static(&qxl_pci_type_info);
+ type_register_static(&qxl_primary_info);
+ type_register_static(&qxl_secondary_info);
+}
+
+type_init(qxl_register_types)
diff --git a/hw/display/qxl.h b/hw/display/qxl.h
new file mode 100644
index 00000000..2ddf065e
--- /dev/null
+++ b/hw/display/qxl.h
@@ -0,0 +1,174 @@
+#ifndef HW_QXL_H
+#define HW_QXL_H 1
+
+#include "qemu-common.h"
+
+#include "ui/console.h"
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "vga_int.h"
+#include "qemu/thread.h"
+
+#include "ui/qemu-spice.h"
+#include "ui/spice-display.h"
+
+enum qxl_mode {
+ QXL_MODE_UNDEFINED,
+ QXL_MODE_VGA,
+ QXL_MODE_COMPAT, /* spice 0.4.x */
+ QXL_MODE_NATIVE,
+};
+
+#ifndef QXL_VRAM64_RANGE_INDEX
+#define QXL_VRAM64_RANGE_INDEX 4
+#endif
+
+#define QXL_UNDEFINED_IO UINT32_MAX
+
+#define QXL_NUM_DIRTY_RECTS 64
+
+#define QXL_PAGE_BITS 12
+#define QXL_PAGE_SIZE (1 << QXL_PAGE_BITS);
+
+typedef struct PCIQXLDevice {
+ PCIDevice pci;
+ PortioList vga_port_list;
+ SimpleSpiceDisplay ssd;
+ int id;
+ uint32_t debug;
+ uint32_t guestdebug;
+ uint32_t cmdlog;
+
+ uint32_t guest_bug;
+
+ enum qxl_mode mode;
+ uint32_t cmdflags;
+ int generation;
+ uint32_t revision;
+
+ int32_t num_memslots;
+
+ uint32_t current_async;
+ QemuMutex async_lock;
+
+ struct guest_slots {
+ QXLMemSlot slot;
+ void *ptr;
+ uint64_t size;
+ uint64_t delta;
+ uint32_t active;
+ } guest_slots[NUM_MEMSLOTS];
+
+ struct guest_primary {
+ QXLSurfaceCreate surface;
+ uint32_t commands;
+ uint32_t resized;
+ int32_t qxl_stride;
+ uint32_t abs_stride;
+ uint32_t bits_pp;
+ uint32_t bytes_pp;
+ uint8_t *data;
+ } guest_primary;
+
+ struct surfaces {
+ QXLPHYSICAL *cmds;
+ uint32_t count;
+ uint32_t max;
+ } guest_surfaces;
+ QXLPHYSICAL guest_cursor;
+
+ QXLPHYSICAL guest_monitors_config;
+
+ QemuMutex track_lock;
+
+ /* thread signaling */
+ QEMUBH *update_irq;
+
+ /* ram pci bar */
+ QXLRam *ram;
+ VGACommonState vga;
+ uint32_t num_free_res;
+ QXLReleaseInfo *last_release;
+ uint32_t last_release_offset;
+ uint32_t oom_running;
+ uint32_t vgamem_size;
+
+ /* rom pci bar */
+ QXLRom shadow_rom;
+ QXLRom *rom;
+ QXLModes *modes;
+ uint32_t rom_size;
+ MemoryRegion rom_bar;
+#if SPICE_SERVER_VERSION >= 0x000c06 /* release 0.12.6 */
+ uint16_t max_outputs;
+#endif
+
+ /* vram pci bar */
+ uint32_t vram_size;
+ MemoryRegion vram_bar;
+ uint32_t vram32_size;
+ MemoryRegion vram32_bar;
+
+ /* io bar */
+ MemoryRegion io_bar;
+
+ /* user-friendly properties (in megabytes) */
+ uint32_t ram_size_mb;
+ uint32_t vram_size_mb;
+ uint32_t vram32_size_mb;
+ uint32_t vgamem_size_mb;
+
+ /* qxl_render_update state */
+ int render_update_cookie_num;
+ int num_dirty_rects;
+ QXLRect dirty[QXL_NUM_DIRTY_RECTS];
+ QEMUBH *update_area_bh;
+} PCIQXLDevice;
+
+#define TYPE_PCI_QXL "pci-qxl"
+#define PCI_QXL(obj) OBJECT_CHECK(PCIQXLDevice, (obj), TYPE_PCI_QXL)
+
+#define PANIC_ON(x) if ((x)) { \
+ printf("%s: PANIC %s failed\n", __FUNCTION__, #x); \
+ abort(); \
+}
+
+#define dprint(_qxl, _level, _fmt, ...) \
+ do { \
+ if (_qxl->debug >= _level) { \
+ fprintf(stderr, "qxl-%d: ", _qxl->id); \
+ fprintf(stderr, _fmt, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define QXL_DEFAULT_REVISION QXL_REVISION_STABLE_V12
+
+/* qxl.c */
+void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL phys, int group_id);
+void qxl_set_guest_bug(PCIQXLDevice *qxl, const char *msg, ...)
+ GCC_FMT_ATTR(2, 3);
+
+void qxl_spice_update_area(PCIQXLDevice *qxl, uint32_t surface_id,
+ struct QXLRect *area, struct QXLRect *dirty_rects,
+ uint32_t num_dirty_rects,
+ uint32_t clear_dirty_region,
+ qxl_async_io async, QXLCookie *cookie);
+void qxl_spice_loadvm_commands(PCIQXLDevice *qxl, struct QXLCommandExt *ext,
+ uint32_t count);
+void qxl_spice_oom(PCIQXLDevice *qxl);
+void qxl_spice_reset_memslots(PCIQXLDevice *qxl);
+void qxl_spice_reset_image_cache(PCIQXLDevice *qxl);
+void qxl_spice_reset_cursor(PCIQXLDevice *qxl);
+
+/* qxl-logger.c */
+int qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id);
+int qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext);
+
+/* qxl-render.c */
+void qxl_render_resize(PCIQXLDevice *qxl);
+void qxl_render_update(PCIQXLDevice *qxl);
+int qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext);
+void qxl_render_update_area_done(PCIQXLDevice *qxl, QXLCookie *cookie);
+void qxl_render_update_area_bh(void *opaque);
+
+#endif
diff --git a/hw/display/sm501.c b/hw/display/sm501.c
new file mode 100644
index 00000000..15a5ba80
--- /dev/null
+++ b/hw/display/sm501.c
@@ -0,0 +1,1455 @@
+/*
+ * QEMU SM501 Device
+ *
+ * Copyright (c) 2008 Shin-ichiro KAWASAKI
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include "hw/hw.h"
+#include "hw/char/serial.h"
+#include "ui/console.h"
+#include "hw/devices.h"
+#include "hw/sysbus.h"
+#include "qemu/range.h"
+#include "ui/pixel_ops.h"
+#include "exec/address-spaces.h"
+
+/*
+ * Status: 2010/05/07
+ * - Minimum implementation for Linux console : mmio regs and CRT layer.
+ * - 2D grapihcs acceleration partially supported : only fill rectangle.
+ *
+ * TODO:
+ * - Panel support
+ * - Touch panel support
+ * - USB support
+ * - UART support
+ * - More 2D graphics engine support
+ * - Performance tuning
+ */
+
+//#define DEBUG_SM501
+//#define DEBUG_BITBLT
+
+#ifdef DEBUG_SM501
+#define SM501_DPRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__)
+#else
+#define SM501_DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+
+#define MMIO_BASE_OFFSET 0x3e00000
+
+/* SM501 register definitions taken from "linux/include/linux/sm501-regs.h" */
+
+/* System Configuration area */
+/* System config base */
+#define SM501_SYS_CONFIG (0x000000)
+
+/* config 1 */
+#define SM501_SYSTEM_CONTROL (0x000000)
+
+#define SM501_SYSCTRL_PANEL_TRISTATE (1<<0)
+#define SM501_SYSCTRL_MEM_TRISTATE (1<<1)
+#define SM501_SYSCTRL_CRT_TRISTATE (1<<2)
+
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_MASK (3<<4)
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_1 (0<<4)
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_2 (1<<4)
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_4 (2<<4)
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_8 (3<<4)
+
+#define SM501_SYSCTRL_PCI_CLOCK_RUN_EN (1<<6)
+#define SM501_SYSCTRL_PCI_RETRY_DISABLE (1<<7)
+#define SM501_SYSCTRL_PCI_SUBSYS_LOCK (1<<11)
+#define SM501_SYSCTRL_PCI_BURST_READ_EN (1<<15)
+
+/* miscellaneous control */
+
+#define SM501_MISC_CONTROL (0x000004)
+
+#define SM501_MISC_BUS_SH (0x0)
+#define SM501_MISC_BUS_PCI (0x1)
+#define SM501_MISC_BUS_XSCALE (0x2)
+#define SM501_MISC_BUS_NEC (0x6)
+#define SM501_MISC_BUS_MASK (0x7)
+
+#define SM501_MISC_VR_62MB (1<<3)
+#define SM501_MISC_CDR_RESET (1<<7)
+#define SM501_MISC_USB_LB (1<<8)
+#define SM501_MISC_USB_SLAVE (1<<9)
+#define SM501_MISC_BL_1 (1<<10)
+#define SM501_MISC_MC (1<<11)
+#define SM501_MISC_DAC_POWER (1<<12)
+#define SM501_MISC_IRQ_INVERT (1<<16)
+#define SM501_MISC_SH (1<<17)
+
+#define SM501_MISC_HOLD_EMPTY (0<<18)
+#define SM501_MISC_HOLD_8 (1<<18)
+#define SM501_MISC_HOLD_16 (2<<18)
+#define SM501_MISC_HOLD_24 (3<<18)
+#define SM501_MISC_HOLD_32 (4<<18)
+#define SM501_MISC_HOLD_MASK (7<<18)
+
+#define SM501_MISC_FREQ_12 (1<<24)
+#define SM501_MISC_PNL_24BIT (1<<25)
+#define SM501_MISC_8051_LE (1<<26)
+
+
+
+#define SM501_GPIO31_0_CONTROL (0x000008)
+#define SM501_GPIO63_32_CONTROL (0x00000C)
+#define SM501_DRAM_CONTROL (0x000010)
+
+/* command list */
+#define SM501_ARBTRTN_CONTROL (0x000014)
+
+/* command list */
+#define SM501_COMMAND_LIST_STATUS (0x000024)
+
+/* interrupt debug */
+#define SM501_RAW_IRQ_STATUS (0x000028)
+#define SM501_RAW_IRQ_CLEAR (0x000028)
+#define SM501_IRQ_STATUS (0x00002C)
+#define SM501_IRQ_MASK (0x000030)
+#define SM501_DEBUG_CONTROL (0x000034)
+
+/* power management */
+#define SM501_POWERMODE_P2X_SRC (1<<29)
+#define SM501_POWERMODE_V2X_SRC (1<<20)
+#define SM501_POWERMODE_M_SRC (1<<12)
+#define SM501_POWERMODE_M1_SRC (1<<4)
+
+#define SM501_CURRENT_GATE (0x000038)
+#define SM501_CURRENT_CLOCK (0x00003C)
+#define SM501_POWER_MODE_0_GATE (0x000040)
+#define SM501_POWER_MODE_0_CLOCK (0x000044)
+#define SM501_POWER_MODE_1_GATE (0x000048)
+#define SM501_POWER_MODE_1_CLOCK (0x00004C)
+#define SM501_SLEEP_MODE_GATE (0x000050)
+#define SM501_POWER_MODE_CONTROL (0x000054)
+
+/* power gates for units within the 501 */
+#define SM501_GATE_HOST (0)
+#define SM501_GATE_MEMORY (1)
+#define SM501_GATE_DISPLAY (2)
+#define SM501_GATE_2D_ENGINE (3)
+#define SM501_GATE_CSC (4)
+#define SM501_GATE_ZVPORT (5)
+#define SM501_GATE_GPIO (6)
+#define SM501_GATE_UART0 (7)
+#define SM501_GATE_UART1 (8)
+#define SM501_GATE_SSP (10)
+#define SM501_GATE_USB_HOST (11)
+#define SM501_GATE_USB_GADGET (12)
+#define SM501_GATE_UCONTROLLER (17)
+#define SM501_GATE_AC97 (18)
+
+/* panel clock */
+#define SM501_CLOCK_P2XCLK (24)
+/* crt clock */
+#define SM501_CLOCK_V2XCLK (16)
+/* main clock */
+#define SM501_CLOCK_MCLK (8)
+/* SDRAM controller clock */
+#define SM501_CLOCK_M1XCLK (0)
+
+/* config 2 */
+#define SM501_PCI_MASTER_BASE (0x000058)
+#define SM501_ENDIAN_CONTROL (0x00005C)
+#define SM501_DEVICEID (0x000060)
+/* 0x050100A0 */
+
+#define SM501_DEVICEID_SM501 (0x05010000)
+#define SM501_DEVICEID_IDMASK (0xffff0000)
+#define SM501_DEVICEID_REVMASK (0x000000ff)
+
+#define SM501_PLLCLOCK_COUNT (0x000064)
+#define SM501_MISC_TIMING (0x000068)
+#define SM501_CURRENT_SDRAM_CLOCK (0x00006C)
+
+#define SM501_PROGRAMMABLE_PLL_CONTROL (0x000074)
+
+/* GPIO base */
+#define SM501_GPIO (0x010000)
+#define SM501_GPIO_DATA_LOW (0x00)
+#define SM501_GPIO_DATA_HIGH (0x04)
+#define SM501_GPIO_DDR_LOW (0x08)
+#define SM501_GPIO_DDR_HIGH (0x0C)
+#define SM501_GPIO_IRQ_SETUP (0x10)
+#define SM501_GPIO_IRQ_STATUS (0x14)
+#define SM501_GPIO_IRQ_RESET (0x14)
+
+/* I2C controller base */
+#define SM501_I2C (0x010040)
+#define SM501_I2C_BYTE_COUNT (0x00)
+#define SM501_I2C_CONTROL (0x01)
+#define SM501_I2C_STATUS (0x02)
+#define SM501_I2C_RESET (0x02)
+#define SM501_I2C_SLAVE_ADDRESS (0x03)
+#define SM501_I2C_DATA (0x04)
+
+/* SSP base */
+#define SM501_SSP (0x020000)
+
+/* Uart 0 base */
+#define SM501_UART0 (0x030000)
+
+/* Uart 1 base */
+#define SM501_UART1 (0x030020)
+
+/* USB host port base */
+#define SM501_USB_HOST (0x040000)
+
+/* USB slave/gadget base */
+#define SM501_USB_GADGET (0x060000)
+
+/* USB slave/gadget data port base */
+#define SM501_USB_GADGET_DATA (0x070000)
+
+/* Display controller/video engine base */
+#define SM501_DC (0x080000)
+
+/* common defines for the SM501 address registers */
+#define SM501_ADDR_FLIP (1<<31)
+#define SM501_ADDR_EXT (1<<27)
+#define SM501_ADDR_CS1 (1<<26)
+#define SM501_ADDR_MASK (0x3f << 26)
+
+#define SM501_FIFO_MASK (0x3 << 16)
+#define SM501_FIFO_1 (0x0 << 16)
+#define SM501_FIFO_3 (0x1 << 16)
+#define SM501_FIFO_7 (0x2 << 16)
+#define SM501_FIFO_11 (0x3 << 16)
+
+/* common registers for panel and the crt */
+#define SM501_OFF_DC_H_TOT (0x000)
+#define SM501_OFF_DC_V_TOT (0x008)
+#define SM501_OFF_DC_H_SYNC (0x004)
+#define SM501_OFF_DC_V_SYNC (0x00C)
+
+#define SM501_DC_PANEL_CONTROL (0x000)
+
+#define SM501_DC_PANEL_CONTROL_FPEN (1<<27)
+#define SM501_DC_PANEL_CONTROL_BIAS (1<<26)
+#define SM501_DC_PANEL_CONTROL_DATA (1<<25)
+#define SM501_DC_PANEL_CONTROL_VDD (1<<24)
+#define SM501_DC_PANEL_CONTROL_DP (1<<23)
+
+#define SM501_DC_PANEL_CONTROL_TFT_888 (0<<21)
+#define SM501_DC_PANEL_CONTROL_TFT_333 (1<<21)
+#define SM501_DC_PANEL_CONTROL_TFT_444 (2<<21)
+
+#define SM501_DC_PANEL_CONTROL_DE (1<<20)
+
+#define SM501_DC_PANEL_CONTROL_LCD_TFT (0<<18)
+#define SM501_DC_PANEL_CONTROL_LCD_STN8 (1<<18)
+#define SM501_DC_PANEL_CONTROL_LCD_STN12 (2<<18)
+
+#define SM501_DC_PANEL_CONTROL_CP (1<<14)
+#define SM501_DC_PANEL_CONTROL_VSP (1<<13)
+#define SM501_DC_PANEL_CONTROL_HSP (1<<12)
+#define SM501_DC_PANEL_CONTROL_CK (1<<9)
+#define SM501_DC_PANEL_CONTROL_TE (1<<8)
+#define SM501_DC_PANEL_CONTROL_VPD (1<<7)
+#define SM501_DC_PANEL_CONTROL_VP (1<<6)
+#define SM501_DC_PANEL_CONTROL_HPD (1<<5)
+#define SM501_DC_PANEL_CONTROL_HP (1<<4)
+#define SM501_DC_PANEL_CONTROL_GAMMA (1<<3)
+#define SM501_DC_PANEL_CONTROL_EN (1<<2)
+
+#define SM501_DC_PANEL_CONTROL_8BPP (0<<0)
+#define SM501_DC_PANEL_CONTROL_16BPP (1<<0)
+#define SM501_DC_PANEL_CONTROL_32BPP (2<<0)
+
+
+#define SM501_DC_PANEL_PANNING_CONTROL (0x004)
+#define SM501_DC_PANEL_COLOR_KEY (0x008)
+#define SM501_DC_PANEL_FB_ADDR (0x00C)
+#define SM501_DC_PANEL_FB_OFFSET (0x010)
+#define SM501_DC_PANEL_FB_WIDTH (0x014)
+#define SM501_DC_PANEL_FB_HEIGHT (0x018)
+#define SM501_DC_PANEL_TL_LOC (0x01C)
+#define SM501_DC_PANEL_BR_LOC (0x020)
+#define SM501_DC_PANEL_H_TOT (0x024)
+#define SM501_DC_PANEL_H_SYNC (0x028)
+#define SM501_DC_PANEL_V_TOT (0x02C)
+#define SM501_DC_PANEL_V_SYNC (0x030)
+#define SM501_DC_PANEL_CUR_LINE (0x034)
+
+#define SM501_DC_VIDEO_CONTROL (0x040)
+#define SM501_DC_VIDEO_FB0_ADDR (0x044)
+#define SM501_DC_VIDEO_FB_WIDTH (0x048)
+#define SM501_DC_VIDEO_FB0_LAST_ADDR (0x04C)
+#define SM501_DC_VIDEO_TL_LOC (0x050)
+#define SM501_DC_VIDEO_BR_LOC (0x054)
+#define SM501_DC_VIDEO_SCALE (0x058)
+#define SM501_DC_VIDEO_INIT_SCALE (0x05C)
+#define SM501_DC_VIDEO_YUV_CONSTANTS (0x060)
+#define SM501_DC_VIDEO_FB1_ADDR (0x064)
+#define SM501_DC_VIDEO_FB1_LAST_ADDR (0x068)
+
+#define SM501_DC_VIDEO_ALPHA_CONTROL (0x080)
+#define SM501_DC_VIDEO_ALPHA_FB_ADDR (0x084)
+#define SM501_DC_VIDEO_ALPHA_FB_OFFSET (0x088)
+#define SM501_DC_VIDEO_ALPHA_FB_LAST_ADDR (0x08C)
+#define SM501_DC_VIDEO_ALPHA_TL_LOC (0x090)
+#define SM501_DC_VIDEO_ALPHA_BR_LOC (0x094)
+#define SM501_DC_VIDEO_ALPHA_SCALE (0x098)
+#define SM501_DC_VIDEO_ALPHA_INIT_SCALE (0x09C)
+#define SM501_DC_VIDEO_ALPHA_CHROMA_KEY (0x0A0)
+#define SM501_DC_VIDEO_ALPHA_COLOR_LOOKUP (0x0A4)
+
+#define SM501_DC_PANEL_HWC_BASE (0x0F0)
+#define SM501_DC_PANEL_HWC_ADDR (0x0F0)
+#define SM501_DC_PANEL_HWC_LOC (0x0F4)
+#define SM501_DC_PANEL_HWC_COLOR_1_2 (0x0F8)
+#define SM501_DC_PANEL_HWC_COLOR_3 (0x0FC)
+
+#define SM501_HWC_EN (1<<31)
+
+#define SM501_OFF_HWC_ADDR (0x00)
+#define SM501_OFF_HWC_LOC (0x04)
+#define SM501_OFF_HWC_COLOR_1_2 (0x08)
+#define SM501_OFF_HWC_COLOR_3 (0x0C)
+
+#define SM501_DC_ALPHA_CONTROL (0x100)
+#define SM501_DC_ALPHA_FB_ADDR (0x104)
+#define SM501_DC_ALPHA_FB_OFFSET (0x108)
+#define SM501_DC_ALPHA_TL_LOC (0x10C)
+#define SM501_DC_ALPHA_BR_LOC (0x110)
+#define SM501_DC_ALPHA_CHROMA_KEY (0x114)
+#define SM501_DC_ALPHA_COLOR_LOOKUP (0x118)
+
+#define SM501_DC_CRT_CONTROL (0x200)
+
+#define SM501_DC_CRT_CONTROL_TVP (1<<15)
+#define SM501_DC_CRT_CONTROL_CP (1<<14)
+#define SM501_DC_CRT_CONTROL_VSP (1<<13)
+#define SM501_DC_CRT_CONTROL_HSP (1<<12)
+#define SM501_DC_CRT_CONTROL_VS (1<<11)
+#define SM501_DC_CRT_CONTROL_BLANK (1<<10)
+#define SM501_DC_CRT_CONTROL_SEL (1<<9)
+#define SM501_DC_CRT_CONTROL_TE (1<<8)
+#define SM501_DC_CRT_CONTROL_PIXEL_MASK (0xF << 4)
+#define SM501_DC_CRT_CONTROL_GAMMA (1<<3)
+#define SM501_DC_CRT_CONTROL_ENABLE (1<<2)
+
+#define SM501_DC_CRT_CONTROL_8BPP (0<<0)
+#define SM501_DC_CRT_CONTROL_16BPP (1<<0)
+#define SM501_DC_CRT_CONTROL_32BPP (2<<0)
+
+#define SM501_DC_CRT_FB_ADDR (0x204)
+#define SM501_DC_CRT_FB_OFFSET (0x208)
+#define SM501_DC_CRT_H_TOT (0x20C)
+#define SM501_DC_CRT_H_SYNC (0x210)
+#define SM501_DC_CRT_V_TOT (0x214)
+#define SM501_DC_CRT_V_SYNC (0x218)
+#define SM501_DC_CRT_SIGNATURE_ANALYZER (0x21C)
+#define SM501_DC_CRT_CUR_LINE (0x220)
+#define SM501_DC_CRT_MONITOR_DETECT (0x224)
+
+#define SM501_DC_CRT_HWC_BASE (0x230)
+#define SM501_DC_CRT_HWC_ADDR (0x230)
+#define SM501_DC_CRT_HWC_LOC (0x234)
+#define SM501_DC_CRT_HWC_COLOR_1_2 (0x238)
+#define SM501_DC_CRT_HWC_COLOR_3 (0x23C)
+
+#define SM501_DC_PANEL_PALETTE (0x400)
+
+#define SM501_DC_VIDEO_PALETTE (0x800)
+
+#define SM501_DC_CRT_PALETTE (0xC00)
+
+/* Zoom Video port base */
+#define SM501_ZVPORT (0x090000)
+
+/* AC97/I2S base */
+#define SM501_AC97 (0x0A0000)
+
+/* 8051 micro controller base */
+#define SM501_UCONTROLLER (0x0B0000)
+
+/* 8051 micro controller SRAM base */
+#define SM501_UCONTROLLER_SRAM (0x0C0000)
+
+/* DMA base */
+#define SM501_DMA (0x0D0000)
+
+/* 2d engine base */
+#define SM501_2D_ENGINE (0x100000)
+#define SM501_2D_SOURCE (0x00)
+#define SM501_2D_DESTINATION (0x04)
+#define SM501_2D_DIMENSION (0x08)
+#define SM501_2D_CONTROL (0x0C)
+#define SM501_2D_PITCH (0x10)
+#define SM501_2D_FOREGROUND (0x14)
+#define SM501_2D_BACKGROUND (0x18)
+#define SM501_2D_STRETCH (0x1C)
+#define SM501_2D_COLOR_COMPARE (0x20)
+#define SM501_2D_COLOR_COMPARE_MASK (0x24)
+#define SM501_2D_MASK (0x28)
+#define SM501_2D_CLIP_TL (0x2C)
+#define SM501_2D_CLIP_BR (0x30)
+#define SM501_2D_MONO_PATTERN_LOW (0x34)
+#define SM501_2D_MONO_PATTERN_HIGH (0x38)
+#define SM501_2D_WINDOW_WIDTH (0x3C)
+#define SM501_2D_SOURCE_BASE (0x40)
+#define SM501_2D_DESTINATION_BASE (0x44)
+#define SM501_2D_ALPHA (0x48)
+#define SM501_2D_WRAP (0x4C)
+#define SM501_2D_STATUS (0x50)
+
+#define SM501_CSC_Y_SOURCE_BASE (0xC8)
+#define SM501_CSC_CONSTANTS (0xCC)
+#define SM501_CSC_Y_SOURCE_X (0xD0)
+#define SM501_CSC_Y_SOURCE_Y (0xD4)
+#define SM501_CSC_U_SOURCE_BASE (0xD8)
+#define SM501_CSC_V_SOURCE_BASE (0xDC)
+#define SM501_CSC_SOURCE_DIMENSION (0xE0)
+#define SM501_CSC_SOURCE_PITCH (0xE4)
+#define SM501_CSC_DESTINATION (0xE8)
+#define SM501_CSC_DESTINATION_DIMENSION (0xEC)
+#define SM501_CSC_DESTINATION_PITCH (0xF0)
+#define SM501_CSC_SCALE_FACTOR (0xF4)
+#define SM501_CSC_DESTINATION_BASE (0xF8)
+#define SM501_CSC_CONTROL (0xFC)
+
+/* 2d engine data port base */
+#define SM501_2D_ENGINE_DATA (0x110000)
+
+/* end of register definitions */
+
+#define SM501_HWC_WIDTH (64)
+#define SM501_HWC_HEIGHT (64)
+
+/* SM501 local memory size taken from "linux/drivers/mfd/sm501.c" */
+static const uint32_t sm501_mem_local_size[] = {
+ [0] = 4*1024*1024,
+ [1] = 8*1024*1024,
+ [2] = 16*1024*1024,
+ [3] = 32*1024*1024,
+ [4] = 64*1024*1024,
+ [5] = 2*1024*1024,
+};
+#define get_local_mem_size(s) sm501_mem_local_size[(s)->local_mem_size_index]
+
+typedef struct SM501State {
+ /* graphic console status */
+ QemuConsole *con;
+
+ /* status & internal resources */
+ hwaddr base;
+ uint32_t local_mem_size_index;
+ uint8_t * local_mem;
+ MemoryRegion local_mem_region;
+ uint32_t last_width;
+ uint32_t last_height;
+
+ /* mmio registers */
+ uint32_t system_control;
+ uint32_t misc_control;
+ uint32_t gpio_31_0_control;
+ uint32_t gpio_63_32_control;
+ uint32_t dram_control;
+ uint32_t irq_mask;
+ uint32_t misc_timing;
+ uint32_t power_mode_control;
+
+ uint32_t uart0_ier;
+ uint32_t uart0_lcr;
+ uint32_t uart0_mcr;
+ uint32_t uart0_scr;
+
+ uint8_t dc_palette[0x400 * 3];
+
+ uint32_t dc_panel_control;
+ uint32_t dc_panel_panning_control;
+ uint32_t dc_panel_fb_addr;
+ uint32_t dc_panel_fb_offset;
+ uint32_t dc_panel_fb_width;
+ uint32_t dc_panel_fb_height;
+ uint32_t dc_panel_tl_location;
+ uint32_t dc_panel_br_location;
+ uint32_t dc_panel_h_total;
+ uint32_t dc_panel_h_sync;
+ uint32_t dc_panel_v_total;
+ uint32_t dc_panel_v_sync;
+
+ uint32_t dc_panel_hwc_addr;
+ uint32_t dc_panel_hwc_location;
+ uint32_t dc_panel_hwc_color_1_2;
+ uint32_t dc_panel_hwc_color_3;
+
+ uint32_t dc_crt_control;
+ uint32_t dc_crt_fb_addr;
+ uint32_t dc_crt_fb_offset;
+ uint32_t dc_crt_h_total;
+ uint32_t dc_crt_h_sync;
+ uint32_t dc_crt_v_total;
+ uint32_t dc_crt_v_sync;
+
+ uint32_t dc_crt_hwc_addr;
+ uint32_t dc_crt_hwc_location;
+ uint32_t dc_crt_hwc_color_1_2;
+ uint32_t dc_crt_hwc_color_3;
+
+ uint32_t twoD_source;
+ uint32_t twoD_destination;
+ uint32_t twoD_dimension;
+ uint32_t twoD_control;
+ uint32_t twoD_pitch;
+ uint32_t twoD_foreground;
+ uint32_t twoD_stretch;
+ uint32_t twoD_color_compare_mask;
+ uint32_t twoD_mask;
+ uint32_t twoD_window_width;
+ uint32_t twoD_source_base;
+ uint32_t twoD_destination_base;
+
+} SM501State;
+
+static uint32_t get_local_mem_size_index(uint32_t size)
+{
+ uint32_t norm_size = 0;
+ int i, index = 0;
+
+ for (i = 0; i < ARRAY_SIZE(sm501_mem_local_size); i++) {
+ uint32_t new_size = sm501_mem_local_size[i];
+ if (new_size >= size) {
+ if (norm_size == 0 || norm_size > new_size) {
+ norm_size = new_size;
+ index = i;
+ }
+ }
+ }
+
+ return index;
+}
+
+/**
+ * Check the availability of hardware cursor.
+ * @param crt 0 for PANEL, 1 for CRT.
+ */
+static inline int is_hwc_enabled(SM501State *state, int crt)
+{
+ uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr;
+ return addr & 0x80000000;
+}
+
+/**
+ * Get the address which holds cursor pattern data.
+ * @param crt 0 for PANEL, 1 for CRT.
+ */
+static inline uint32_t get_hwc_address(SM501State *state, int crt)
+{
+ uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr;
+ return (addr & 0x03FFFFF0)/* >> 4*/;
+}
+
+/**
+ * Get the cursor position in y coordinate.
+ * @param crt 0 for PANEL, 1 for CRT.
+ */
+static inline uint32_t get_hwc_y(SM501State *state, int crt)
+{
+ uint32_t location = crt ? state->dc_crt_hwc_location
+ : state->dc_panel_hwc_location;
+ return (location & 0x07FF0000) >> 16;
+}
+
+/**
+ * Get the cursor position in x coordinate.
+ * @param crt 0 for PANEL, 1 for CRT.
+ */
+static inline uint32_t get_hwc_x(SM501State *state, int crt)
+{
+ uint32_t location = crt ? state->dc_crt_hwc_location
+ : state->dc_panel_hwc_location;
+ return location & 0x000007FF;
+}
+
+/**
+ * Get the cursor position in x coordinate.
+ * @param crt 0 for PANEL, 1 for CRT.
+ * @param index 0, 1, 2 or 3 which specifies color of corsor dot.
+ */
+static inline uint16_t get_hwc_color(SM501State *state, int crt, int index)
+{
+ uint32_t color_reg = 0;
+ uint16_t color_565 = 0;
+
+ if (index == 0) {
+ return 0;
+ }
+
+ switch (index) {
+ case 1:
+ case 2:
+ color_reg = crt ? state->dc_crt_hwc_color_1_2
+ : state->dc_panel_hwc_color_1_2;
+ break;
+ case 3:
+ color_reg = crt ? state->dc_crt_hwc_color_3
+ : state->dc_panel_hwc_color_3;
+ break;
+ default:
+ printf("invalid hw cursor color.\n");
+ abort();
+ }
+
+ switch (index) {
+ case 1:
+ case 3:
+ color_565 = (uint16_t)(color_reg & 0xFFFF);
+ break;
+ case 2:
+ color_565 = (uint16_t)((color_reg >> 16) & 0xFFFF);
+ break;
+ }
+ return color_565;
+}
+
+static int within_hwc_y_range(SM501State *state, int y, int crt)
+{
+ int hwc_y = get_hwc_y(state, crt);
+ return (hwc_y <= y && y < hwc_y + SM501_HWC_HEIGHT);
+}
+
+static void sm501_2d_operation(SM501State * s)
+{
+ /* obtain operation parameters */
+ int operation = (s->twoD_control >> 16) & 0x1f;
+ int rtl = s->twoD_control & 0x8000000;
+ int src_x = (s->twoD_source >> 16) & 0x01FFF;
+ int src_y = s->twoD_source & 0xFFFF;
+ int dst_x = (s->twoD_destination >> 16) & 0x01FFF;
+ int dst_y = s->twoD_destination & 0xFFFF;
+ int operation_width = (s->twoD_dimension >> 16) & 0x1FFF;
+ int operation_height = s->twoD_dimension & 0xFFFF;
+ uint32_t color = s->twoD_foreground;
+ int format_flags = (s->twoD_stretch >> 20) & 0x3;
+ int addressing = (s->twoD_stretch >> 16) & 0xF;
+
+ /* get frame buffer info */
+ uint8_t * src = s->local_mem + (s->twoD_source_base & 0x03FFFFFF);
+ uint8_t * dst = s->local_mem + (s->twoD_destination_base & 0x03FFFFFF);
+ int src_width = (s->dc_crt_h_total & 0x00000FFF) + 1;
+ int dst_width = (s->dc_crt_h_total & 0x00000FFF) + 1;
+
+ if (addressing != 0x0) {
+ printf("%s: only XY addressing is supported.\n", __func__);
+ abort();
+ }
+
+ if ((s->twoD_source_base & 0x08000000) ||
+ (s->twoD_destination_base & 0x08000000)) {
+ printf("%s: only local memory is supported.\n", __func__);
+ abort();
+ }
+
+ switch (operation) {
+ case 0x00: /* copy area */
+#define COPY_AREA(_bpp, _pixel_type, rtl) { \
+ int y, x, index_d, index_s; \
+ for (y = 0; y < operation_height; y++) { \
+ for (x = 0; x < operation_width; x++) { \
+ if (rtl) { \
+ index_s = ((src_y - y) * src_width + src_x - x) * _bpp; \
+ index_d = ((dst_y - y) * dst_width + dst_x - x) * _bpp; \
+ } else { \
+ index_s = ((src_y + y) * src_width + src_x + x) * _bpp; \
+ index_d = ((dst_y + y) * dst_width + dst_x + x) * _bpp; \
+ } \
+ *(_pixel_type*)&dst[index_d] = *(_pixel_type*)&src[index_s];\
+ } \
+ } \
+ }
+ switch (format_flags) {
+ case 0:
+ COPY_AREA(1, uint8_t, rtl);
+ break;
+ case 1:
+ COPY_AREA(2, uint16_t, rtl);
+ break;
+ case 2:
+ COPY_AREA(4, uint32_t, rtl);
+ break;
+ }
+ break;
+
+ case 0x01: /* fill rectangle */
+#define FILL_RECT(_bpp, _pixel_type) { \
+ int y, x; \
+ for (y = 0; y < operation_height; y++) { \
+ for (x = 0; x < operation_width; x++) { \
+ int index = ((dst_y + y) * dst_width + dst_x + x) * _bpp; \
+ *(_pixel_type*)&dst[index] = (_pixel_type)color; \
+ } \
+ } \
+ }
+
+ switch (format_flags) {
+ case 0:
+ FILL_RECT(1, uint8_t);
+ break;
+ case 1:
+ FILL_RECT(2, uint16_t);
+ break;
+ case 2:
+ FILL_RECT(4, uint32_t);
+ break;
+ }
+ break;
+
+ default:
+ printf("non-implemented SM501 2D operation. %d\n", operation);
+ abort();
+ break;
+ }
+}
+
+static uint64_t sm501_system_config_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SM501State * s = (SM501State *)opaque;
+ uint32_t ret = 0;
+ SM501_DPRINTF("sm501 system config regs : read addr=%x\n", (int)addr);
+
+ switch(addr) {
+ case SM501_SYSTEM_CONTROL:
+ ret = s->system_control;
+ break;
+ case SM501_MISC_CONTROL:
+ ret = s->misc_control;
+ break;
+ case SM501_GPIO31_0_CONTROL:
+ ret = s->gpio_31_0_control;
+ break;
+ case SM501_GPIO63_32_CONTROL:
+ ret = s->gpio_63_32_control;
+ break;
+ case SM501_DEVICEID:
+ ret = 0x050100A0;
+ break;
+ case SM501_DRAM_CONTROL:
+ ret = (s->dram_control & 0x07F107C0) | s->local_mem_size_index << 13;
+ break;
+ case SM501_IRQ_MASK:
+ ret = s->irq_mask;
+ break;
+ case SM501_MISC_TIMING:
+ /* TODO : simulate gate control */
+ ret = s->misc_timing;
+ break;
+ case SM501_CURRENT_GATE:
+ /* TODO : simulate gate control */
+ ret = 0x00021807;
+ break;
+ case SM501_CURRENT_CLOCK:
+ ret = 0x2A1A0A09;
+ break;
+ case SM501_POWER_MODE_CONTROL:
+ ret = s->power_mode_control;
+ break;
+
+ default:
+ printf("sm501 system config : not implemented register read."
+ " addr=%x\n", (int)addr);
+ abort();
+ }
+
+ return ret;
+}
+
+static void sm501_system_config_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SM501State * s = (SM501State *)opaque;
+ SM501_DPRINTF("sm501 system config regs : write addr=%x, val=%x\n",
+ (uint32_t)addr, (uint32_t)value);
+
+ switch(addr) {
+ case SM501_SYSTEM_CONTROL:
+ s->system_control = value & 0xE300B8F7;
+ break;
+ case SM501_MISC_CONTROL:
+ s->misc_control = value & 0xFF7FFF20;
+ break;
+ case SM501_GPIO31_0_CONTROL:
+ s->gpio_31_0_control = value;
+ break;
+ case SM501_GPIO63_32_CONTROL:
+ s->gpio_63_32_control = value;
+ break;
+ case SM501_DRAM_CONTROL:
+ s->local_mem_size_index = (value >> 13) & 0x7;
+ /* rODO : check validity of size change */
+ s->dram_control |= value & 0x7FFFFFC3;
+ break;
+ case SM501_IRQ_MASK:
+ s->irq_mask = value;
+ break;
+ case SM501_MISC_TIMING:
+ s->misc_timing = value & 0xF31F1FFF;
+ break;
+ case SM501_POWER_MODE_0_GATE:
+ case SM501_POWER_MODE_1_GATE:
+ case SM501_POWER_MODE_0_CLOCK:
+ case SM501_POWER_MODE_1_CLOCK:
+ /* TODO : simulate gate & clock control */
+ break;
+ case SM501_POWER_MODE_CONTROL:
+ s->power_mode_control = value & 0x00000003;
+ break;
+
+ default:
+ printf("sm501 system config : not implemented register write."
+ " addr=%x, val=%x\n", (int)addr, (uint32_t)value);
+ abort();
+ }
+}
+
+static const MemoryRegionOps sm501_system_config_ops = {
+ .read = sm501_system_config_read,
+ .write = sm501_system_config_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint32_t sm501_palette_read(void *opaque, hwaddr addr)
+{
+ SM501State * s = (SM501State *)opaque;
+ SM501_DPRINTF("sm501 palette read addr=%x\n", (int)addr);
+
+ /* TODO : consider BYTE/WORD access */
+ /* TODO : consider endian */
+
+ assert(range_covers_byte(0, 0x400 * 3, addr));
+ return *(uint32_t*)&s->dc_palette[addr];
+}
+
+static void sm501_palette_write(void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ SM501State * s = (SM501State *)opaque;
+ SM501_DPRINTF("sm501 palette write addr=%x, val=%x\n",
+ (int)addr, value);
+
+ /* TODO : consider BYTE/WORD access */
+ /* TODO : consider endian */
+
+ assert(range_covers_byte(0, 0x400 * 3, addr));
+ *(uint32_t*)&s->dc_palette[addr] = value;
+}
+
+static uint64_t sm501_disp_ctrl_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SM501State * s = (SM501State *)opaque;
+ uint32_t ret = 0;
+ SM501_DPRINTF("sm501 disp ctrl regs : read addr=%x\n", (int)addr);
+
+ switch(addr) {
+
+ case SM501_DC_PANEL_CONTROL:
+ ret = s->dc_panel_control;
+ break;
+ case SM501_DC_PANEL_PANNING_CONTROL:
+ ret = s->dc_panel_panning_control;
+ break;
+ case SM501_DC_PANEL_FB_ADDR:
+ ret = s->dc_panel_fb_addr;
+ break;
+ case SM501_DC_PANEL_FB_OFFSET:
+ ret = s->dc_panel_fb_offset;
+ break;
+ case SM501_DC_PANEL_FB_WIDTH:
+ ret = s->dc_panel_fb_width;
+ break;
+ case SM501_DC_PANEL_FB_HEIGHT:
+ ret = s->dc_panel_fb_height;
+ break;
+ case SM501_DC_PANEL_TL_LOC:
+ ret = s->dc_panel_tl_location;
+ break;
+ case SM501_DC_PANEL_BR_LOC:
+ ret = s->dc_panel_br_location;
+ break;
+
+ case SM501_DC_PANEL_H_TOT:
+ ret = s->dc_panel_h_total;
+ break;
+ case SM501_DC_PANEL_H_SYNC:
+ ret = s->dc_panel_h_sync;
+ break;
+ case SM501_DC_PANEL_V_TOT:
+ ret = s->dc_panel_v_total;
+ break;
+ case SM501_DC_PANEL_V_SYNC:
+ ret = s->dc_panel_v_sync;
+ break;
+
+ case SM501_DC_CRT_CONTROL:
+ ret = s->dc_crt_control;
+ break;
+ case SM501_DC_CRT_FB_ADDR:
+ ret = s->dc_crt_fb_addr;
+ break;
+ case SM501_DC_CRT_FB_OFFSET:
+ ret = s->dc_crt_fb_offset;
+ break;
+ case SM501_DC_CRT_H_TOT:
+ ret = s->dc_crt_h_total;
+ break;
+ case SM501_DC_CRT_H_SYNC:
+ ret = s->dc_crt_h_sync;
+ break;
+ case SM501_DC_CRT_V_TOT:
+ ret = s->dc_crt_v_total;
+ break;
+ case SM501_DC_CRT_V_SYNC:
+ ret = s->dc_crt_v_sync;
+ break;
+
+ case SM501_DC_CRT_HWC_ADDR:
+ ret = s->dc_crt_hwc_addr;
+ break;
+ case SM501_DC_CRT_HWC_LOC:
+ ret = s->dc_crt_hwc_location;
+ break;
+ case SM501_DC_CRT_HWC_COLOR_1_2:
+ ret = s->dc_crt_hwc_color_1_2;
+ break;
+ case SM501_DC_CRT_HWC_COLOR_3:
+ ret = s->dc_crt_hwc_color_3;
+ break;
+
+ case SM501_DC_PANEL_PALETTE ... SM501_DC_PANEL_PALETTE + 0x400*3 - 4:
+ ret = sm501_palette_read(opaque, addr - SM501_DC_PANEL_PALETTE);
+ break;
+
+ default:
+ printf("sm501 disp ctrl : not implemented register read."
+ " addr=%x\n", (int)addr);
+ abort();
+ }
+
+ return ret;
+}
+
+static void sm501_disp_ctrl_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SM501State * s = (SM501State *)opaque;
+ SM501_DPRINTF("sm501 disp ctrl regs : write addr=%x, val=%x\n",
+ (unsigned)addr, (unsigned)value);
+
+ switch(addr) {
+ case SM501_DC_PANEL_CONTROL:
+ s->dc_panel_control = value & 0x0FFF73FF;
+ break;
+ case SM501_DC_PANEL_PANNING_CONTROL:
+ s->dc_panel_panning_control = value & 0xFF3FFF3F;
+ break;
+ case SM501_DC_PANEL_FB_ADDR:
+ s->dc_panel_fb_addr = value & 0x8FFFFFF0;
+ break;
+ case SM501_DC_PANEL_FB_OFFSET:
+ s->dc_panel_fb_offset = value & 0x3FF03FF0;
+ break;
+ case SM501_DC_PANEL_FB_WIDTH:
+ s->dc_panel_fb_width = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_FB_HEIGHT:
+ s->dc_panel_fb_height = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_TL_LOC:
+ s->dc_panel_tl_location = value & 0x07FF07FF;
+ break;
+ case SM501_DC_PANEL_BR_LOC:
+ s->dc_panel_br_location = value & 0x07FF07FF;
+ break;
+
+ case SM501_DC_PANEL_H_TOT:
+ s->dc_panel_h_total = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_H_SYNC:
+ s->dc_panel_h_sync = value & 0x00FF0FFF;
+ break;
+ case SM501_DC_PANEL_V_TOT:
+ s->dc_panel_v_total = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_V_SYNC:
+ s->dc_panel_v_sync = value & 0x003F0FFF;
+ break;
+
+ case SM501_DC_PANEL_HWC_ADDR:
+ s->dc_panel_hwc_addr = value & 0x8FFFFFF0;
+ break;
+ case SM501_DC_PANEL_HWC_LOC:
+ s->dc_panel_hwc_location = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_HWC_COLOR_1_2:
+ s->dc_panel_hwc_color_1_2 = value;
+ break;
+ case SM501_DC_PANEL_HWC_COLOR_3:
+ s->dc_panel_hwc_color_3 = value & 0x0000FFFF;
+ break;
+
+ case SM501_DC_CRT_CONTROL:
+ s->dc_crt_control = value & 0x0003FFFF;
+ break;
+ case SM501_DC_CRT_FB_ADDR:
+ s->dc_crt_fb_addr = value & 0x8FFFFFF0;
+ break;
+ case SM501_DC_CRT_FB_OFFSET:
+ s->dc_crt_fb_offset = value & 0x3FF03FF0;
+ break;
+ case SM501_DC_CRT_H_TOT:
+ s->dc_crt_h_total = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_CRT_H_SYNC:
+ s->dc_crt_h_sync = value & 0x00FF0FFF;
+ break;
+ case SM501_DC_CRT_V_TOT:
+ s->dc_crt_v_total = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_CRT_V_SYNC:
+ s->dc_crt_v_sync = value & 0x003F0FFF;
+ break;
+
+ case SM501_DC_CRT_HWC_ADDR:
+ s->dc_crt_hwc_addr = value & 0x8FFFFFF0;
+ break;
+ case SM501_DC_CRT_HWC_LOC:
+ s->dc_crt_hwc_location = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_CRT_HWC_COLOR_1_2:
+ s->dc_crt_hwc_color_1_2 = value;
+ break;
+ case SM501_DC_CRT_HWC_COLOR_3:
+ s->dc_crt_hwc_color_3 = value & 0x0000FFFF;
+ break;
+
+ case SM501_DC_PANEL_PALETTE ... SM501_DC_PANEL_PALETTE + 0x400*3 - 4:
+ sm501_palette_write(opaque, addr - SM501_DC_PANEL_PALETTE, value);
+ break;
+
+ default:
+ printf("sm501 disp ctrl : not implemented register write."
+ " addr=%x, val=%x\n", (int)addr, (unsigned)value);
+ abort();
+ }
+}
+
+static const MemoryRegionOps sm501_disp_ctrl_ops = {
+ .read = sm501_disp_ctrl_read,
+ .write = sm501_disp_ctrl_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t sm501_2d_engine_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SM501State * s = (SM501State *)opaque;
+ uint32_t ret = 0;
+ SM501_DPRINTF("sm501 2d engine regs : read addr=%x\n", (int)addr);
+
+ switch(addr) {
+ case SM501_2D_SOURCE_BASE:
+ ret = s->twoD_source_base;
+ break;
+ default:
+ printf("sm501 disp ctrl : not implemented register read."
+ " addr=%x\n", (int)addr);
+ abort();
+ }
+
+ return ret;
+}
+
+static void sm501_2d_engine_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SM501State * s = (SM501State *)opaque;
+ SM501_DPRINTF("sm501 2d engine regs : write addr=%x, val=%x\n",
+ (unsigned)addr, (unsigned)value);
+
+ switch(addr) {
+ case SM501_2D_SOURCE:
+ s->twoD_source = value;
+ break;
+ case SM501_2D_DESTINATION:
+ s->twoD_destination = value;
+ break;
+ case SM501_2D_DIMENSION:
+ s->twoD_dimension = value;
+ break;
+ case SM501_2D_CONTROL:
+ s->twoD_control = value;
+
+ /* do 2d operation if start flag is set. */
+ if (value & 0x80000000) {
+ sm501_2d_operation(s);
+ s->twoD_control &= ~0x80000000; /* start flag down */
+ }
+
+ break;
+ case SM501_2D_PITCH:
+ s->twoD_pitch = value;
+ break;
+ case SM501_2D_FOREGROUND:
+ s->twoD_foreground = value;
+ break;
+ case SM501_2D_STRETCH:
+ s->twoD_stretch = value;
+ break;
+ case SM501_2D_COLOR_COMPARE_MASK:
+ s->twoD_color_compare_mask = value;
+ break;
+ case SM501_2D_MASK:
+ s->twoD_mask = value;
+ break;
+ case SM501_2D_WINDOW_WIDTH:
+ s->twoD_window_width = value;
+ break;
+ case SM501_2D_SOURCE_BASE:
+ s->twoD_source_base = value;
+ break;
+ case SM501_2D_DESTINATION_BASE:
+ s->twoD_destination_base = value;
+ break;
+ default:
+ printf("sm501 2d engine : not implemented register write."
+ " addr=%x, val=%x\n", (int)addr, (unsigned)value);
+ abort();
+ }
+}
+
+static const MemoryRegionOps sm501_2d_engine_ops = {
+ .read = sm501_2d_engine_read,
+ .write = sm501_2d_engine_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* draw line functions for all console modes */
+
+typedef void draw_line_func(uint8_t *d, const uint8_t *s,
+ int width, const uint32_t *pal);
+
+typedef void draw_hwc_line_func(SM501State * s, int crt, uint8_t * palette,
+ int c_y, uint8_t *d, int width);
+
+#define DEPTH 8
+#include "sm501_template.h"
+
+#define DEPTH 15
+#include "sm501_template.h"
+
+#define BGR_FORMAT
+#define DEPTH 15
+#include "sm501_template.h"
+
+#define DEPTH 16
+#include "sm501_template.h"
+
+#define BGR_FORMAT
+#define DEPTH 16
+#include "sm501_template.h"
+
+#define DEPTH 32
+#include "sm501_template.h"
+
+#define BGR_FORMAT
+#define DEPTH 32
+#include "sm501_template.h"
+
+static draw_line_func * draw_line8_funcs[] = {
+ draw_line8_8,
+ draw_line8_15,
+ draw_line8_16,
+ draw_line8_32,
+ draw_line8_32bgr,
+ draw_line8_15bgr,
+ draw_line8_16bgr,
+};
+
+static draw_line_func * draw_line16_funcs[] = {
+ draw_line16_8,
+ draw_line16_15,
+ draw_line16_16,
+ draw_line16_32,
+ draw_line16_32bgr,
+ draw_line16_15bgr,
+ draw_line16_16bgr,
+};
+
+static draw_line_func * draw_line32_funcs[] = {
+ draw_line32_8,
+ draw_line32_15,
+ draw_line32_16,
+ draw_line32_32,
+ draw_line32_32bgr,
+ draw_line32_15bgr,
+ draw_line32_16bgr,
+};
+
+static draw_hwc_line_func * draw_hwc_line_funcs[] = {
+ draw_hwc_line_8,
+ draw_hwc_line_15,
+ draw_hwc_line_16,
+ draw_hwc_line_32,
+ draw_hwc_line_32bgr,
+ draw_hwc_line_15bgr,
+ draw_hwc_line_16bgr,
+};
+
+static inline int get_depth_index(DisplaySurface *surface)
+{
+ switch (surface_bits_per_pixel(surface)) {
+ default:
+ case 8:
+ return 0;
+ case 15:
+ return 1;
+ case 16:
+ return 2;
+ case 32:
+ if (is_surface_bgr(surface)) {
+ return 4;
+ } else {
+ return 3;
+ }
+ }
+}
+
+static void sm501_draw_crt(SM501State * s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int y;
+ int width = (s->dc_crt_h_total & 0x00000FFF) + 1;
+ int height = (s->dc_crt_v_total & 0x00000FFF) + 1;
+
+ uint8_t * src = s->local_mem;
+ int src_bpp = 0;
+ int dst_bpp = surface_bytes_per_pixel(surface);
+ uint32_t * palette = (uint32_t *)&s->dc_palette[SM501_DC_CRT_PALETTE
+ - SM501_DC_PANEL_PALETTE];
+ uint8_t hwc_palette[3 * 3];
+ int ds_depth_index = get_depth_index(surface);
+ draw_line_func * draw_line = NULL;
+ draw_hwc_line_func * draw_hwc_line = NULL;
+ int full_update = 0;
+ int y_start = -1;
+ ram_addr_t page_min = ~0l;
+ ram_addr_t page_max = 0l;
+ ram_addr_t offset = 0;
+
+ /* choose draw_line function */
+ switch (s->dc_crt_control & 3) {
+ case SM501_DC_CRT_CONTROL_8BPP:
+ src_bpp = 1;
+ draw_line = draw_line8_funcs[ds_depth_index];
+ break;
+ case SM501_DC_CRT_CONTROL_16BPP:
+ src_bpp = 2;
+ draw_line = draw_line16_funcs[ds_depth_index];
+ break;
+ case SM501_DC_CRT_CONTROL_32BPP:
+ src_bpp = 4;
+ draw_line = draw_line32_funcs[ds_depth_index];
+ break;
+ default:
+ printf("sm501 draw crt : invalid DC_CRT_CONTROL=%x.\n",
+ s->dc_crt_control);
+ abort();
+ break;
+ }
+
+ /* set up to draw hardware cursor */
+ if (is_hwc_enabled(s, 1)) {
+ int i;
+
+ /* get cursor palette */
+ for (i = 0; i < 3; i++) {
+ uint16_t rgb565 = get_hwc_color(s, 1, i + 1);
+ hwc_palette[i * 3 + 0] = (rgb565 & 0xf800) >> 8; /* red */
+ hwc_palette[i * 3 + 1] = (rgb565 & 0x07e0) >> 3; /* green */
+ hwc_palette[i * 3 + 2] = (rgb565 & 0x001f) << 3; /* blue */
+ }
+
+ /* choose cursor draw line function */
+ draw_hwc_line = draw_hwc_line_funcs[ds_depth_index];
+ }
+
+ /* adjust console size */
+ if (s->last_width != width || s->last_height != height) {
+ qemu_console_resize(s->con, width, height);
+ surface = qemu_console_surface(s->con);
+ s->last_width = width;
+ s->last_height = height;
+ full_update = 1;
+ }
+
+ /* draw each line according to conditions */
+ memory_region_sync_dirty_bitmap(&s->local_mem_region);
+ for (y = 0; y < height; y++) {
+ int update_hwc = draw_hwc_line ? within_hwc_y_range(s, y, 1) : 0;
+ int update = full_update || update_hwc;
+ ram_addr_t page0 = offset;
+ ram_addr_t page1 = offset + width * src_bpp - 1;
+
+ /* check dirty flags for each line */
+ update = memory_region_get_dirty(&s->local_mem_region, page0,
+ page1 - page0, DIRTY_MEMORY_VGA);
+
+ /* draw line and change status */
+ if (update) {
+ uint8_t *d = surface_data(surface);
+ d += y * width * dst_bpp;
+
+ /* draw graphics layer */
+ draw_line(d, src, width, palette);
+
+ /* draw haredware cursor */
+ if (update_hwc) {
+ draw_hwc_line(s, 1, hwc_palette, y - get_hwc_y(s, 1), d, width);
+ }
+
+ if (y_start < 0)
+ y_start = y;
+ if (page0 < page_min)
+ page_min = page0;
+ if (page1 > page_max)
+ page_max = page1;
+ } else {
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(s->con, 0, y_start, width, y - y_start);
+ y_start = -1;
+ }
+ }
+
+ src += width * src_bpp;
+ offset += width * src_bpp;
+ }
+
+ /* complete flush to display */
+ if (y_start >= 0)
+ dpy_gfx_update(s->con, 0, y_start, width, y - y_start);
+
+ /* clear dirty flags */
+ if (page_min != ~0l) {
+ memory_region_reset_dirty(&s->local_mem_region,
+ page_min, page_max + TARGET_PAGE_SIZE,
+ DIRTY_MEMORY_VGA);
+ }
+}
+
+static void sm501_update_display(void *opaque)
+{
+ SM501State * s = (SM501State *)opaque;
+
+ if (s->dc_crt_control & SM501_DC_CRT_CONTROL_ENABLE)
+ sm501_draw_crt(s);
+}
+
+static const GraphicHwOps sm501_ops = {
+ .gfx_update = sm501_update_display,
+};
+
+void sm501_init(MemoryRegion *address_space_mem, uint32_t base,
+ uint32_t local_mem_bytes, qemu_irq irq, CharDriverState *chr)
+{
+ SM501State * s;
+ DeviceState *dev;
+ MemoryRegion *sm501_system_config = g_new(MemoryRegion, 1);
+ MemoryRegion *sm501_disp_ctrl = g_new(MemoryRegion, 1);
+ MemoryRegion *sm501_2d_engine = g_new(MemoryRegion, 1);
+
+ /* allocate management data region */
+ s = (SM501State *)g_malloc0(sizeof(SM501State));
+ s->base = base;
+ s->local_mem_size_index
+ = get_local_mem_size_index(local_mem_bytes);
+ SM501_DPRINTF("local mem size=%x. index=%d\n", get_local_mem_size(s),
+ s->local_mem_size_index);
+ s->system_control = 0x00100000;
+ s->misc_control = 0x00001000; /* assumes SH, active=low */
+ s->dc_panel_control = 0x00010000;
+ s->dc_crt_control = 0x00010000;
+
+ /* allocate local memory */
+ memory_region_init_ram(&s->local_mem_region, NULL, "sm501.local",
+ local_mem_bytes, &error_abort);
+ vmstate_register_ram_global(&s->local_mem_region);
+ memory_region_set_log(&s->local_mem_region, true, DIRTY_MEMORY_VGA);
+ s->local_mem = memory_region_get_ram_ptr(&s->local_mem_region);
+ memory_region_add_subregion(address_space_mem, base, &s->local_mem_region);
+
+ /* map mmio */
+ memory_region_init_io(sm501_system_config, NULL, &sm501_system_config_ops, s,
+ "sm501-system-config", 0x6c);
+ memory_region_add_subregion(address_space_mem, base + MMIO_BASE_OFFSET,
+ sm501_system_config);
+ memory_region_init_io(sm501_disp_ctrl, NULL, &sm501_disp_ctrl_ops, s,
+ "sm501-disp-ctrl", 0x1000);
+ memory_region_add_subregion(address_space_mem,
+ base + MMIO_BASE_OFFSET + SM501_DC,
+ sm501_disp_ctrl);
+ memory_region_init_io(sm501_2d_engine, NULL, &sm501_2d_engine_ops, s,
+ "sm501-2d-engine", 0x54);
+ memory_region_add_subregion(address_space_mem,
+ base + MMIO_BASE_OFFSET + SM501_2D_ENGINE,
+ sm501_2d_engine);
+
+ /* bridge to usb host emulation module */
+ dev = qdev_create(NULL, "sysbus-ohci");
+ qdev_prop_set_uint32(dev, "num-ports", 2);
+ qdev_prop_set_uint64(dev, "dma-offset", base);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0,
+ base + MMIO_BASE_OFFSET + SM501_USB_HOST);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
+
+ /* bridge to serial emulation module */
+ if (chr) {
+ serial_mm_init(address_space_mem,
+ base + MMIO_BASE_OFFSET + SM501_UART0, 2,
+ NULL, /* TODO : chain irq to IRL */
+ 115200, chr, DEVICE_NATIVE_ENDIAN);
+ }
+
+ /* create qemu graphic console */
+ s->con = graphic_console_init(DEVICE(dev), 0, &sm501_ops, s);
+}
diff --git a/hw/display/sm501_template.h b/hw/display/sm501_template.h
new file mode 100644
index 00000000..f33e499b
--- /dev/null
+++ b/hw/display/sm501_template.h
@@ -0,0 +1,145 @@
+/*
+ * Pixel drawing function templates for QEMU SM501 Device
+ *
+ * Copyright (c) 2008 Shin-ichiro KAWASAKI
+ *
+ * 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.
+ */
+
+#if DEPTH == 8
+#define BPP 1
+#define PIXEL_TYPE uint8_t
+#elif DEPTH == 15 || DEPTH == 16
+#define BPP 2
+#define PIXEL_TYPE uint16_t
+#elif DEPTH == 32
+#define BPP 4
+#define PIXEL_TYPE uint32_t
+#else
+#error unsupport depth
+#endif
+
+#ifdef BGR_FORMAT
+#define PIXEL_NAME glue(DEPTH, bgr)
+#else
+#define PIXEL_NAME DEPTH
+#endif /* BGR_FORMAT */
+
+
+static void glue(draw_line8_, PIXEL_NAME)(
+ uint8_t *d, const uint8_t *s, int width, const uint32_t *pal)
+{
+ uint8_t v, r, g, b;
+ do {
+ v = ldub_p(s);
+ r = (pal[v] >> 16) & 0xff;
+ g = (pal[v] >> 8) & 0xff;
+ b = (pal[v] >> 0) & 0xff;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b);
+ s ++;
+ d += BPP;
+ } while (-- width != 0);
+}
+
+static void glue(draw_line16_, PIXEL_NAME)(
+ uint8_t *d, const uint8_t *s, int width, const uint32_t *pal)
+{
+ uint16_t rgb565;
+ uint8_t r, g, b;
+
+ do {
+ rgb565 = lduw_p(s);
+ r = ((rgb565 >> 11) & 0x1f) << 3;
+ g = ((rgb565 >> 5) & 0x3f) << 2;
+ b = ((rgb565 >> 0) & 0x1f) << 3;
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b);
+ s += 2;
+ d += BPP;
+ } while (-- width != 0);
+}
+
+static void glue(draw_line32_, PIXEL_NAME)(
+ uint8_t *d, const uint8_t *s, int width, const uint32_t *pal)
+{
+ uint8_t r, g, b;
+
+ do {
+ ldub_p(s);
+#if defined(TARGET_WORDS_BIGENDIAN)
+ r = s[1];
+ g = s[2];
+ b = s[3];
+#else
+ b = s[0];
+ g = s[1];
+ r = s[2];
+#endif
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b);
+ s += 4;
+ d += BPP;
+ } while (-- width != 0);
+}
+
+/**
+ * Draw hardware cursor image on the given line.
+ */
+static void glue(draw_hwc_line_, PIXEL_NAME)(SM501State * s, int crt,
+ uint8_t * palette, int c_y, uint8_t *d, int width)
+{
+ int x, i;
+ uint8_t bitset = 0;
+
+ /* get hardware cursor pattern */
+ uint32_t cursor_addr = get_hwc_address(s, crt);
+ assert(0 <= c_y && c_y < SM501_HWC_HEIGHT);
+ cursor_addr += 64 * c_y / 4; /* 4 pixels per byte */
+ cursor_addr += s->base;
+
+ /* get cursor position */
+ x = get_hwc_x(s, crt);
+ d += x * BPP;
+
+ for (i = 0; i < SM501_HWC_WIDTH && x + i < width; i++) {
+ uint8_t v;
+
+ /* get pixel value */
+ if (i % 4 == 0) {
+ bitset = ldub_phys(&address_space_memory, cursor_addr);
+ cursor_addr++;
+ }
+ v = bitset & 3;
+ bitset >>= 2;
+
+ /* write pixel */
+ if (v) {
+ v--;
+ uint8_t r = palette[v * 3 + 0];
+ uint8_t g = palette[v * 3 + 1];
+ uint8_t b = palette[v * 3 + 2];
+ ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b);
+ }
+ d += BPP;
+ }
+}
+
+#undef DEPTH
+#undef BPP
+#undef PIXEL_TYPE
+#undef PIXEL_NAME
+#undef BGR_FORMAT
diff --git a/hw/display/ssd0303.c b/hw/display/ssd0303.c
new file mode 100644
index 00000000..f6804fb5
--- /dev/null
+++ b/hw/display/ssd0303.c
@@ -0,0 +1,330 @@
+/*
+ * SSD0303 OLED controller with OSRAM Pictiva 96x16 display.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+/* The controller can support a variety of different displays, but we only
+ implement one. Most of the commends relating to brightness and geometry
+ setup are ignored. */
+#include "hw/i2c/i2c.h"
+#include "ui/console.h"
+
+//#define DEBUG_SSD0303 1
+
+#ifdef DEBUG_SSD0303
+#define DPRINTF(fmt, ...) \
+do { printf("ssd0303: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+/* Scaling factor for pixels. */
+#define MAGNIFY 4
+
+enum ssd0303_mode
+{
+ SSD0303_IDLE,
+ SSD0303_DATA,
+ SSD0303_CMD
+};
+
+enum ssd0303_cmd {
+ SSD0303_CMD_NONE,
+ SSD0303_CMD_SKIP1
+};
+
+#define TYPE_SSD0303 "ssd0303"
+#define SSD0303(obj) OBJECT_CHECK(ssd0303_state, (obj), TYPE_SSD0303)
+
+typedef struct {
+ I2CSlave parent_obj;
+
+ QemuConsole *con;
+ int row;
+ int col;
+ int start_line;
+ int mirror;
+ int flash;
+ int enabled;
+ int inverse;
+ int redraw;
+ enum ssd0303_mode mode;
+ enum ssd0303_cmd cmd_state;
+ uint8_t framebuffer[132*8];
+} ssd0303_state;
+
+static int ssd0303_recv(I2CSlave *i2c)
+{
+ BADF("Reads not implemented\n");
+ return -1;
+}
+
+static int ssd0303_send(I2CSlave *i2c, uint8_t data)
+{
+ ssd0303_state *s = SSD0303(i2c);
+ enum ssd0303_cmd old_cmd_state;
+
+ switch (s->mode) {
+ case SSD0303_IDLE:
+ DPRINTF("byte 0x%02x\n", data);
+ if (data == 0x80)
+ s->mode = SSD0303_CMD;
+ else if (data == 0x40)
+ s->mode = SSD0303_DATA;
+ else
+ BADF("Unexpected byte 0x%x\n", data);
+ break;
+ case SSD0303_DATA:
+ DPRINTF("data 0x%02x\n", data);
+ if (s->col < 132) {
+ s->framebuffer[s->col + s->row * 132] = data;
+ s->col++;
+ s->redraw = 1;
+ }
+ break;
+ case SSD0303_CMD:
+ old_cmd_state = s->cmd_state;
+ s->cmd_state = SSD0303_CMD_NONE;
+ switch (old_cmd_state) {
+ case SSD0303_CMD_NONE:
+ DPRINTF("cmd 0x%02x\n", data);
+ s->mode = SSD0303_IDLE;
+ switch (data) {
+ case 0x00 ... 0x0f: /* Set lower column address. */
+ s->col = (s->col & 0xf0) | (data & 0xf);
+ break;
+ case 0x10 ... 0x20: /* Set higher column address. */
+ s->col = (s->col & 0x0f) | ((data & 0xf) << 4);
+ break;
+ case 0x40 ... 0x7f: /* Set start line. */
+ s->start_line = 0;
+ break;
+ case 0x81: /* Set contrast (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xa0: /* Mirror off. */
+ s->mirror = 0;
+ break;
+ case 0xa1: /* Mirror off. */
+ s->mirror = 1;
+ break;
+ case 0xa4: /* Entire display off. */
+ s->flash = 0;
+ break;
+ case 0xa5: /* Entire display on. */
+ s->flash = 1;
+ break;
+ case 0xa6: /* Inverse off. */
+ s->inverse = 0;
+ break;
+ case 0xa7: /* Inverse on. */
+ s->inverse = 1;
+ break;
+ case 0xa8: /* Set multiplied ratio (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xad: /* DC-DC power control. */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xae: /* Display off. */
+ s->enabled = 0;
+ break;
+ case 0xaf: /* Display on. */
+ s->enabled = 1;
+ break;
+ case 0xb0 ... 0xbf: /* Set Page address. */
+ s->row = data & 7;
+ break;
+ case 0xc0 ... 0xc8: /* Set COM output direction (Ignored). */
+ break;
+ case 0xd3: /* Set display offset (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xd5: /* Set display clock (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xd8: /* Set color and power mode (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xd9: /* Set pre-charge period (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xda: /* Set COM pin configuration (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xdb: /* Set VCOM dselect level (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xe3: /* no-op. */
+ break;
+ default:
+ BADF("Unknown command: 0x%x\n", data);
+ }
+ break;
+ case SSD0303_CMD_SKIP1:
+ DPRINTF("skip 0x%02x\n", data);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static void ssd0303_event(I2CSlave *i2c, enum i2c_event event)
+{
+ ssd0303_state *s = SSD0303(i2c);
+
+ switch (event) {
+ case I2C_FINISH:
+ s->mode = SSD0303_IDLE;
+ break;
+ case I2C_START_RECV:
+ case I2C_START_SEND:
+ case I2C_NACK:
+ /* Nothing to do. */
+ break;
+ }
+}
+
+static void ssd0303_update_display(void *opaque)
+{
+ ssd0303_state *s = (ssd0303_state *)opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint8_t *dest;
+ uint8_t *src;
+ int x;
+ int y;
+ int line;
+ char *colors[2];
+ char colortab[MAGNIFY * 8];
+ int dest_width;
+ uint8_t mask;
+
+ if (!s->redraw)
+ return;
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ return;
+ case 15:
+ dest_width = 2;
+ break;
+ case 16:
+ dest_width = 2;
+ break;
+ case 24:
+ dest_width = 3;
+ break;
+ case 32:
+ dest_width = 4;
+ break;
+ default:
+ BADF("Bad color depth\n");
+ return;
+ }
+ dest_width *= MAGNIFY;
+ memset(colortab, 0xff, dest_width);
+ memset(colortab + dest_width, 0, dest_width);
+ if (s->flash) {
+ colors[0] = colortab;
+ colors[1] = colortab;
+ } else if (s->inverse) {
+ colors[0] = colortab;
+ colors[1] = colortab + dest_width;
+ } else {
+ colors[0] = colortab + dest_width;
+ colors[1] = colortab;
+ }
+ dest = surface_data(surface);
+ for (y = 0; y < 16; y++) {
+ line = (y + s->start_line) & 63;
+ src = s->framebuffer + 132 * (line >> 3) + 36;
+ mask = 1 << (line & 7);
+ for (x = 0; x < 96; x++) {
+ memcpy(dest, colors[(*src & mask) != 0], dest_width);
+ dest += dest_width;
+ src++;
+ }
+ for (x = 1; x < MAGNIFY; x++) {
+ memcpy(dest, dest - dest_width * 96, dest_width * 96);
+ dest += dest_width * 96;
+ }
+ }
+ s->redraw = 0;
+ dpy_gfx_update(s->con, 0, 0, 96 * MAGNIFY, 16 * MAGNIFY);
+}
+
+static void ssd0303_invalidate_display(void * opaque)
+{
+ ssd0303_state *s = (ssd0303_state *)opaque;
+ s->redraw = 1;
+}
+
+static const VMStateDescription vmstate_ssd0303 = {
+ .name = "ssd0303_oled",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(row, ssd0303_state),
+ VMSTATE_INT32(col, ssd0303_state),
+ VMSTATE_INT32(start_line, ssd0303_state),
+ VMSTATE_INT32(mirror, ssd0303_state),
+ VMSTATE_INT32(flash, ssd0303_state),
+ VMSTATE_INT32(enabled, ssd0303_state),
+ VMSTATE_INT32(inverse, ssd0303_state),
+ VMSTATE_INT32(redraw, ssd0303_state),
+ VMSTATE_UINT32(mode, ssd0303_state),
+ VMSTATE_UINT32(cmd_state, ssd0303_state),
+ VMSTATE_BUFFER(framebuffer, ssd0303_state),
+ VMSTATE_I2C_SLAVE(parent_obj, ssd0303_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps ssd0303_ops = {
+ .invalidate = ssd0303_invalidate_display,
+ .gfx_update = ssd0303_update_display,
+};
+
+static int ssd0303_init(I2CSlave *i2c)
+{
+ ssd0303_state *s = SSD0303(i2c);
+
+ s->con = graphic_console_init(DEVICE(i2c), 0, &ssd0303_ops, s);
+ qemu_console_resize(s->con, 96 * MAGNIFY, 16 * MAGNIFY);
+ return 0;
+}
+
+static void ssd0303_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = ssd0303_init;
+ k->event = ssd0303_event;
+ k->recv = ssd0303_recv;
+ k->send = ssd0303_send;
+ dc->vmsd = &vmstate_ssd0303;
+}
+
+static const TypeInfo ssd0303_info = {
+ .name = TYPE_SSD0303,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(ssd0303_state),
+ .class_init = ssd0303_class_init,
+};
+
+static void ssd0303_register_types(void)
+{
+ type_register_static(&ssd0303_info);
+}
+
+type_init(ssd0303_register_types)
diff --git a/hw/display/ssd0323.c b/hw/display/ssd0323.c
new file mode 100644
index 00000000..97270077
--- /dev/null
+++ b/hw/display/ssd0323.c
@@ -0,0 +1,401 @@
+/*
+ * SSD0323 OLED controller with OSRAM Pictiva 128x64 display.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+/* The controller can support a variety of different displays, but we only
+ implement one. Most of the commends relating to brightness and geometry
+ setup are ignored. */
+#include "hw/ssi.h"
+#include "ui/console.h"
+
+//#define DEBUG_SSD0323 1
+
+#ifdef DEBUG_SSD0323
+#define DPRINTF(fmt, ...) \
+do { printf("ssd0323: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { \
+ fprintf(stderr, "ssd0323: error: " fmt , ## __VA_ARGS__); abort(); \
+} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "ssd0323: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+/* Scaling factor for pixels. */
+#define MAGNIFY 4
+
+#define REMAP_SWAP_COLUMN 0x01
+#define REMAP_SWAP_NYBBLE 0x02
+#define REMAP_VERTICAL 0x04
+#define REMAP_SWAP_COM 0x10
+#define REMAP_SPLIT_COM 0x40
+
+enum ssd0323_mode
+{
+ SSD0323_CMD,
+ SSD0323_DATA
+};
+
+typedef struct {
+ SSISlave ssidev;
+ QemuConsole *con;
+
+ int cmd_len;
+ int cmd;
+ int cmd_data[8];
+ int row;
+ int row_start;
+ int row_end;
+ int col;
+ int col_start;
+ int col_end;
+ int redraw;
+ int remap;
+ enum ssd0323_mode mode;
+ uint8_t framebuffer[128 * 80 / 2];
+} ssd0323_state;
+
+static uint32_t ssd0323_transfer(SSISlave *dev, uint32_t data)
+{
+ ssd0323_state *s = FROM_SSI_SLAVE(ssd0323_state, dev);
+
+ switch (s->mode) {
+ case SSD0323_DATA:
+ DPRINTF("data 0x%02x\n", data);
+ s->framebuffer[s->col + s->row * 64] = data;
+ if (s->remap & REMAP_VERTICAL) {
+ s->row++;
+ if (s->row > s->row_end) {
+ s->row = s->row_start;
+ s->col++;
+ }
+ if (s->col > s->col_end) {
+ s->col = s->col_start;
+ }
+ } else {
+ s->col++;
+ if (s->col > s->col_end) {
+ s->row++;
+ s->col = s->col_start;
+ }
+ if (s->row > s->row_end) {
+ s->row = s->row_start;
+ }
+ }
+ s->redraw = 1;
+ break;
+ case SSD0323_CMD:
+ DPRINTF("cmd 0x%02x\n", data);
+ if (s->cmd_len == 0) {
+ s->cmd = data;
+ } else {
+ s->cmd_data[s->cmd_len - 1] = data;
+ }
+ s->cmd_len++;
+ switch (s->cmd) {
+#define DATA(x) if (s->cmd_len <= (x)) return 0
+ case 0x15: /* Set column. */
+ DATA(2);
+ s->col = s->col_start = s->cmd_data[0] % 64;
+ s->col_end = s->cmd_data[1] % 64;
+ break;
+ case 0x75: /* Set row. */
+ DATA(2);
+ s->row = s->row_start = s->cmd_data[0] % 80;
+ s->row_end = s->cmd_data[1] % 80;
+ break;
+ case 0x81: /* Set contrast */
+ DATA(1);
+ break;
+ case 0x84: case 0x85: case 0x86: /* Max current. */
+ DATA(0);
+ break;
+ case 0xa0: /* Set remapping. */
+ /* FIXME: Implement this. */
+ DATA(1);
+ s->remap = s->cmd_data[0];
+ break;
+ case 0xa1: /* Set display start line. */
+ case 0xa2: /* Set display offset. */
+ /* FIXME: Implement these. */
+ DATA(1);
+ break;
+ case 0xa4: /* Normal mode. */
+ case 0xa5: /* All on. */
+ case 0xa6: /* All off. */
+ case 0xa7: /* Inverse. */
+ /* FIXME: Implement these. */
+ DATA(0);
+ break;
+ case 0xa8: /* Set multiplex ratio. */
+ case 0xad: /* Set DC-DC converter. */
+ DATA(1);
+ /* Ignored. Don't care. */
+ break;
+ case 0xae: /* Display off. */
+ case 0xaf: /* Display on. */
+ DATA(0);
+ /* TODO: Implement power control. */
+ break;
+ case 0xb1: /* Set phase length. */
+ case 0xb2: /* Set row period. */
+ case 0xb3: /* Set clock rate. */
+ case 0xbc: /* Set precharge. */
+ case 0xbe: /* Set VCOMH. */
+ case 0xbf: /* Set segment low. */
+ DATA(1);
+ /* Ignored. Don't care. */
+ break;
+ case 0xb8: /* Set grey scale table. */
+ /* FIXME: Implement this. */
+ DATA(8);
+ break;
+ case 0xe3: /* NOP. */
+ DATA(0);
+ break;
+ case 0xff: /* Nasty hack because we don't handle chip selects
+ properly. */
+ break;
+ default:
+ BADF("Unknown command: 0x%x\n", data);
+ }
+ s->cmd_len = 0;
+ return 0;
+ }
+ return 0;
+}
+
+static void ssd0323_update_display(void *opaque)
+{
+ ssd0323_state *s = (ssd0323_state *)opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint8_t *dest;
+ uint8_t *src;
+ int x;
+ int y;
+ int i;
+ int line;
+ char *colors[16];
+ char colortab[MAGNIFY * 64];
+ char *p;
+ int dest_width;
+
+ if (!s->redraw)
+ return;
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ return;
+ case 15:
+ dest_width = 2;
+ break;
+ case 16:
+ dest_width = 2;
+ break;
+ case 24:
+ dest_width = 3;
+ break;
+ case 32:
+ dest_width = 4;
+ break;
+ default:
+ BADF("Bad color depth\n");
+ return;
+ }
+ p = colortab;
+ for (i = 0; i < 16; i++) {
+ int n;
+ colors[i] = p;
+ switch (surface_bits_per_pixel(surface)) {
+ case 15:
+ n = i * 2 + (i >> 3);
+ p[0] = n | (n << 5);
+ p[1] = (n << 2) | (n >> 3);
+ break;
+ case 16:
+ n = i * 2 + (i >> 3);
+ p[0] = n | (n << 6) | ((n << 1) & 0x20);
+ p[1] = (n << 3) | (n >> 2);
+ break;
+ case 24:
+ case 32:
+ n = (i << 4) | i;
+ p[0] = p[1] = p[2] = n;
+ break;
+ default:
+ BADF("Bad color depth\n");
+ return;
+ }
+ p += dest_width;
+ }
+ /* TODO: Implement row/column remapping. */
+ dest = surface_data(surface);
+ for (y = 0; y < 64; y++) {
+ line = y;
+ src = s->framebuffer + 64 * line;
+ for (x = 0; x < 64; x++) {
+ int val;
+ val = *src >> 4;
+ for (i = 0; i < MAGNIFY; i++) {
+ memcpy(dest, colors[val], dest_width);
+ dest += dest_width;
+ }
+ val = *src & 0xf;
+ for (i = 0; i < MAGNIFY; i++) {
+ memcpy(dest, colors[val], dest_width);
+ dest += dest_width;
+ }
+ src++;
+ }
+ for (i = 1; i < MAGNIFY; i++) {
+ memcpy(dest, dest - dest_width * MAGNIFY * 128,
+ dest_width * 128 * MAGNIFY);
+ dest += dest_width * 128 * MAGNIFY;
+ }
+ }
+ s->redraw = 0;
+ dpy_gfx_update(s->con, 0, 0, 128 * MAGNIFY, 64 * MAGNIFY);
+}
+
+static void ssd0323_invalidate_display(void * opaque)
+{
+ ssd0323_state *s = (ssd0323_state *)opaque;
+ s->redraw = 1;
+}
+
+/* Command/data input. */
+static void ssd0323_cd(void *opaque, int n, int level)
+{
+ ssd0323_state *s = (ssd0323_state *)opaque;
+ DPRINTF("%s mode\n", level ? "Data" : "Command");
+ s->mode = level ? SSD0323_DATA : SSD0323_CMD;
+}
+
+static void ssd0323_save(QEMUFile *f, void *opaque)
+{
+ SSISlave *ss = SSI_SLAVE(opaque);
+ ssd0323_state *s = (ssd0323_state *)opaque;
+ int i;
+
+ qemu_put_be32(f, s->cmd_len);
+ qemu_put_be32(f, s->cmd);
+ for (i = 0; i < 8; i++)
+ qemu_put_be32(f, s->cmd_data[i]);
+ qemu_put_be32(f, s->row);
+ qemu_put_be32(f, s->row_start);
+ qemu_put_be32(f, s->row_end);
+ qemu_put_be32(f, s->col);
+ qemu_put_be32(f, s->col_start);
+ qemu_put_be32(f, s->col_end);
+ qemu_put_be32(f, s->redraw);
+ qemu_put_be32(f, s->remap);
+ qemu_put_be32(f, s->mode);
+ qemu_put_buffer(f, s->framebuffer, sizeof(s->framebuffer));
+
+ qemu_put_be32(f, ss->cs);
+}
+
+static int ssd0323_load(QEMUFile *f, void *opaque, int version_id)
+{
+ SSISlave *ss = SSI_SLAVE(opaque);
+ ssd0323_state *s = (ssd0323_state *)opaque;
+ int i;
+
+ if (version_id != 1)
+ return -EINVAL;
+
+ s->cmd_len = qemu_get_be32(f);
+ if (s->cmd_len < 0 || s->cmd_len > ARRAY_SIZE(s->cmd_data)) {
+ return -EINVAL;
+ }
+ s->cmd = qemu_get_be32(f);
+ for (i = 0; i < 8; i++)
+ s->cmd_data[i] = qemu_get_be32(f);
+ s->row = qemu_get_be32(f);
+ if (s->row < 0 || s->row >= 80) {
+ return -EINVAL;
+ }
+ s->row_start = qemu_get_be32(f);
+ if (s->row_start < 0 || s->row_start >= 80) {
+ return -EINVAL;
+ }
+ s->row_end = qemu_get_be32(f);
+ if (s->row_end < 0 || s->row_end >= 80) {
+ return -EINVAL;
+ }
+ s->col = qemu_get_be32(f);
+ if (s->col < 0 || s->col >= 64) {
+ return -EINVAL;
+ }
+ s->col_start = qemu_get_be32(f);
+ if (s->col_start < 0 || s->col_start >= 64) {
+ return -EINVAL;
+ }
+ s->col_end = qemu_get_be32(f);
+ if (s->col_end < 0 || s->col_end >= 64) {
+ return -EINVAL;
+ }
+ s->redraw = qemu_get_be32(f);
+ s->remap = qemu_get_be32(f);
+ s->mode = qemu_get_be32(f);
+ if (s->mode != SSD0323_CMD && s->mode != SSD0323_DATA) {
+ return -EINVAL;
+ }
+ qemu_get_buffer(f, s->framebuffer, sizeof(s->framebuffer));
+
+ ss->cs = qemu_get_be32(f);
+
+ return 0;
+}
+
+static const GraphicHwOps ssd0323_ops = {
+ .invalidate = ssd0323_invalidate_display,
+ .gfx_update = ssd0323_update_display,
+};
+
+static int ssd0323_init(SSISlave *d)
+{
+ DeviceState *dev = DEVICE(d);
+ ssd0323_state *s = FROM_SSI_SLAVE(ssd0323_state, d);
+
+ s->col_end = 63;
+ s->row_end = 79;
+ s->con = graphic_console_init(dev, 0, &ssd0323_ops, s);
+ qemu_console_resize(s->con, 128 * MAGNIFY, 64 * MAGNIFY);
+
+ qdev_init_gpio_in(dev, ssd0323_cd, 1);
+
+ register_savevm(dev, "ssd0323_oled", -1, 1,
+ ssd0323_save, ssd0323_load, s);
+ return 0;
+}
+
+static void ssd0323_class_init(ObjectClass *klass, void *data)
+{
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = ssd0323_init;
+ k->transfer = ssd0323_transfer;
+ k->cs_polarity = SSI_CS_HIGH;
+}
+
+static const TypeInfo ssd0323_info = {
+ .name = "ssd0323",
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(ssd0323_state),
+ .class_init = ssd0323_class_init,
+};
+
+static void ssd03232_register_types(void)
+{
+ type_register_static(&ssd0323_info);
+}
+
+type_init(ssd03232_register_types)
diff --git a/hw/display/tc6393xb.c b/hw/display/tc6393xb.c
new file mode 100644
index 00000000..f5f3f3e6
--- /dev/null
+++ b/hw/display/tc6393xb.c
@@ -0,0 +1,596 @@
+/*
+ * Toshiba TC6393XB I/O Controller.
+ * Found in Sharp Zaurus SL-6000 (tosa) or some
+ * Toshiba e-Series PDAs.
+ *
+ * Most features are currently unsupported!!!
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/hw.h"
+#include "hw/devices.h"
+#include "hw/block/flash.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+
+#define IRQ_TC6393_NAND 0
+#define IRQ_TC6393_MMC 1
+#define IRQ_TC6393_OHCI 2
+#define IRQ_TC6393_SERIAL 3
+#define IRQ_TC6393_FB 4
+
+#define TC6393XB_NR_IRQS 8
+
+#define TC6393XB_GPIOS 16
+
+#define SCR_REVID 0x08 /* b Revision ID */
+#define SCR_ISR 0x50 /* b Interrupt Status */
+#define SCR_IMR 0x52 /* b Interrupt Mask */
+#define SCR_IRR 0x54 /* b Interrupt Routing */
+#define SCR_GPER 0x60 /* w GP Enable */
+#define SCR_GPI_SR(i) (0x64 + (i)) /* b3 GPI Status */
+#define SCR_GPI_IMR(i) (0x68 + (i)) /* b3 GPI INT Mask */
+#define SCR_GPI_EDER(i) (0x6c + (i)) /* b3 GPI Edge Detect Enable */
+#define SCR_GPI_LIR(i) (0x70 + (i)) /* b3 GPI Level Invert */
+#define SCR_GPO_DSR(i) (0x78 + (i)) /* b3 GPO Data Set */
+#define SCR_GPO_DOECR(i) (0x7c + (i)) /* b3 GPO Data OE Control */
+#define SCR_GP_IARCR(i) (0x80 + (i)) /* b3 GP Internal Active Register Control */
+#define SCR_GP_IARLCR(i) (0x84 + (i)) /* b3 GP INTERNAL Active Register Level Control */
+#define SCR_GPI_BCR(i) (0x88 + (i)) /* b3 GPI Buffer Control */
+#define SCR_GPA_IARCR 0x8c /* w GPa Internal Active Register Control */
+#define SCR_GPA_IARLCR 0x90 /* w GPa Internal Active Register Level Control */
+#define SCR_GPA_BCR 0x94 /* w GPa Buffer Control */
+#define SCR_CCR 0x98 /* w Clock Control */
+#define SCR_PLL2CR 0x9a /* w PLL2 Control */
+#define SCR_PLL1CR 0x9c /* l PLL1 Control */
+#define SCR_DIARCR 0xa0 /* b Device Internal Active Register Control */
+#define SCR_DBOCR 0xa1 /* b Device Buffer Off Control */
+#define SCR_FER 0xe0 /* b Function Enable */
+#define SCR_MCR 0xe4 /* w Mode Control */
+#define SCR_CONFIG 0xfc /* b Configuration Control */
+#define SCR_DEBUG 0xff /* b Debug */
+
+#define NAND_CFG_COMMAND 0x04 /* w Command */
+#define NAND_CFG_BASE 0x10 /* l Control Base Address */
+#define NAND_CFG_INTP 0x3d /* b Interrupt Pin */
+#define NAND_CFG_INTE 0x48 /* b Int Enable */
+#define NAND_CFG_EC 0x4a /* b Event Control */
+#define NAND_CFG_ICC 0x4c /* b Internal Clock Control */
+#define NAND_CFG_ECCC 0x5b /* b ECC Control */
+#define NAND_CFG_NFTC 0x60 /* b NAND Flash Transaction Control */
+#define NAND_CFG_NFM 0x61 /* b NAND Flash Monitor */
+#define NAND_CFG_NFPSC 0x62 /* b NAND Flash Power Supply Control */
+#define NAND_CFG_NFDC 0x63 /* b NAND Flash Detect Control */
+
+#define NAND_DATA 0x00 /* l Data */
+#define NAND_MODE 0x04 /* b Mode */
+#define NAND_STATUS 0x05 /* b Status */
+#define NAND_ISR 0x06 /* b Interrupt Status */
+#define NAND_IMR 0x07 /* b Interrupt Mask */
+
+#define NAND_MODE_WP 0x80
+#define NAND_MODE_CE 0x10
+#define NAND_MODE_ALE 0x02
+#define NAND_MODE_CLE 0x01
+#define NAND_MODE_ECC_MASK 0x60
+#define NAND_MODE_ECC_EN 0x20
+#define NAND_MODE_ECC_READ 0x40
+#define NAND_MODE_ECC_RST 0x60
+
+struct TC6393xbState {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq *sub_irqs;
+ struct {
+ uint8_t ISR;
+ uint8_t IMR;
+ uint8_t IRR;
+ uint16_t GPER;
+ uint8_t GPI_SR[3];
+ uint8_t GPI_IMR[3];
+ uint8_t GPI_EDER[3];
+ uint8_t GPI_LIR[3];
+ uint8_t GP_IARCR[3];
+ uint8_t GP_IARLCR[3];
+ uint8_t GPI_BCR[3];
+ uint16_t GPA_IARCR;
+ uint16_t GPA_IARLCR;
+ uint16_t CCR;
+ uint16_t PLL2CR;
+ uint32_t PLL1CR;
+ uint8_t DIARCR;
+ uint8_t DBOCR;
+ uint8_t FER;
+ uint16_t MCR;
+ uint8_t CONFIG;
+ uint8_t DEBUG;
+ } scr;
+ uint32_t gpio_dir;
+ uint32_t gpio_level;
+ uint32_t prev_level;
+ qemu_irq handler[TC6393XB_GPIOS];
+ qemu_irq *gpio_in;
+
+ struct {
+ uint8_t mode;
+ uint8_t isr;
+ uint8_t imr;
+ } nand;
+ int nand_enable;
+ uint32_t nand_phys;
+ DeviceState *flash;
+ ECCState ecc;
+
+ QemuConsole *con;
+ MemoryRegion vram;
+ uint16_t *vram_ptr;
+ uint32_t scr_width, scr_height; /* in pixels */
+ qemu_irq l3v;
+ unsigned blank : 1,
+ blanked : 1;
+};
+
+qemu_irq *tc6393xb_gpio_in_get(TC6393xbState *s)
+{
+ return s->gpio_in;
+}
+
+static void tc6393xb_gpio_set(void *opaque, int line, int level)
+{
+// TC6393xbState *s = opaque;
+
+ if (line > TC6393XB_GPIOS) {
+ printf("%s: No GPIO pin %i\n", __FUNCTION__, line);
+ return;
+ }
+
+ // FIXME: how does the chip reflect the GPIO input level change?
+}
+
+void tc6393xb_gpio_out_set(TC6393xbState *s, int line,
+ qemu_irq handler)
+{
+ if (line >= TC6393XB_GPIOS) {
+ fprintf(stderr, "TC6393xb: no GPIO pin %d\n", line);
+ return;
+ }
+
+ s->handler[line] = handler;
+}
+
+static void tc6393xb_gpio_handler_update(TC6393xbState *s)
+{
+ uint32_t level, diff;
+ int bit;
+
+ level = s->gpio_level & s->gpio_dir;
+
+ for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
+ bit = ctz32(diff);
+ qemu_set_irq(s->handler[bit], (level >> bit) & 1);
+ }
+
+ s->prev_level = level;
+}
+
+qemu_irq tc6393xb_l3v_get(TC6393xbState *s)
+{
+ return s->l3v;
+}
+
+static void tc6393xb_l3v(void *opaque, int line, int level)
+{
+ TC6393xbState *s = opaque;
+ s->blank = !level;
+ fprintf(stderr, "L3V: %d\n", level);
+}
+
+static void tc6393xb_sub_irq(void *opaque, int line, int level) {
+ TC6393xbState *s = opaque;
+ uint8_t isr = s->scr.ISR;
+ if (level)
+ isr |= 1 << line;
+ else
+ isr &= ~(1 << line);
+ s->scr.ISR = isr;
+ qemu_set_irq(s->irq, isr & s->scr.IMR);
+}
+
+#define SCR_REG_B(N) \
+ case SCR_ ##N: return s->scr.N
+#define SCR_REG_W(N) \
+ case SCR_ ##N: return s->scr.N; \
+ case SCR_ ##N + 1: return s->scr.N >> 8;
+#define SCR_REG_L(N) \
+ case SCR_ ##N: return s->scr.N; \
+ case SCR_ ##N + 1: return s->scr.N >> 8; \
+ case SCR_ ##N + 2: return s->scr.N >> 16; \
+ case SCR_ ##N + 3: return s->scr.N >> 24;
+#define SCR_REG_A(N) \
+ case SCR_ ##N(0): return s->scr.N[0]; \
+ case SCR_ ##N(1): return s->scr.N[1]; \
+ case SCR_ ##N(2): return s->scr.N[2]
+
+static uint32_t tc6393xb_scr_readb(TC6393xbState *s, hwaddr addr)
+{
+ switch (addr) {
+ case SCR_REVID:
+ return 3;
+ case SCR_REVID+1:
+ return 0;
+ SCR_REG_B(ISR);
+ SCR_REG_B(IMR);
+ SCR_REG_B(IRR);
+ SCR_REG_W(GPER);
+ SCR_REG_A(GPI_SR);
+ SCR_REG_A(GPI_IMR);
+ SCR_REG_A(GPI_EDER);
+ SCR_REG_A(GPI_LIR);
+ case SCR_GPO_DSR(0):
+ case SCR_GPO_DSR(1):
+ case SCR_GPO_DSR(2):
+ return (s->gpio_level >> ((addr - SCR_GPO_DSR(0)) * 8)) & 0xff;
+ case SCR_GPO_DOECR(0):
+ case SCR_GPO_DOECR(1):
+ case SCR_GPO_DOECR(2):
+ return (s->gpio_dir >> ((addr - SCR_GPO_DOECR(0)) * 8)) & 0xff;
+ SCR_REG_A(GP_IARCR);
+ SCR_REG_A(GP_IARLCR);
+ SCR_REG_A(GPI_BCR);
+ SCR_REG_W(GPA_IARCR);
+ SCR_REG_W(GPA_IARLCR);
+ SCR_REG_W(CCR);
+ SCR_REG_W(PLL2CR);
+ SCR_REG_L(PLL1CR);
+ SCR_REG_B(DIARCR);
+ SCR_REG_B(DBOCR);
+ SCR_REG_B(FER);
+ SCR_REG_W(MCR);
+ SCR_REG_B(CONFIG);
+ SCR_REG_B(DEBUG);
+ }
+ fprintf(stderr, "tc6393xb_scr: unhandled read at %08x\n", (uint32_t) addr);
+ return 0;
+}
+#undef SCR_REG_B
+#undef SCR_REG_W
+#undef SCR_REG_L
+#undef SCR_REG_A
+
+#define SCR_REG_B(N) \
+ case SCR_ ##N: s->scr.N = value; return;
+#define SCR_REG_W(N) \
+ case SCR_ ##N: s->scr.N = (s->scr.N & ~0xff) | (value & 0xff); return; \
+ case SCR_ ##N + 1: s->scr.N = (s->scr.N & 0xff) | (value << 8); return
+#define SCR_REG_L(N) \
+ case SCR_ ##N: s->scr.N = (s->scr.N & ~0xff) | (value & 0xff); return; \
+ case SCR_ ##N + 1: s->scr.N = (s->scr.N & ~(0xff << 8)) | (value & (0xff << 8)); return; \
+ case SCR_ ##N + 2: s->scr.N = (s->scr.N & ~(0xff << 16)) | (value & (0xff << 16)); return; \
+ case SCR_ ##N + 3: s->scr.N = (s->scr.N & ~(0xff << 24)) | (value & (0xff << 24)); return;
+#define SCR_REG_A(N) \
+ case SCR_ ##N(0): s->scr.N[0] = value; return; \
+ case SCR_ ##N(1): s->scr.N[1] = value; return; \
+ case SCR_ ##N(2): s->scr.N[2] = value; return
+
+static void tc6393xb_scr_writeb(TC6393xbState *s, hwaddr addr, uint32_t value)
+{
+ switch (addr) {
+ SCR_REG_B(ISR);
+ SCR_REG_B(IMR);
+ SCR_REG_B(IRR);
+ SCR_REG_W(GPER);
+ SCR_REG_A(GPI_SR);
+ SCR_REG_A(GPI_IMR);
+ SCR_REG_A(GPI_EDER);
+ SCR_REG_A(GPI_LIR);
+ case SCR_GPO_DSR(0):
+ case SCR_GPO_DSR(1):
+ case SCR_GPO_DSR(2):
+ s->gpio_level = (s->gpio_level & ~(0xff << ((addr - SCR_GPO_DSR(0))*8))) | ((value & 0xff) << ((addr - SCR_GPO_DSR(0))*8));
+ tc6393xb_gpio_handler_update(s);
+ return;
+ case SCR_GPO_DOECR(0):
+ case SCR_GPO_DOECR(1):
+ case SCR_GPO_DOECR(2):
+ s->gpio_dir = (s->gpio_dir & ~(0xff << ((addr - SCR_GPO_DOECR(0))*8))) | ((value & 0xff) << ((addr - SCR_GPO_DOECR(0))*8));
+ tc6393xb_gpio_handler_update(s);
+ return;
+ SCR_REG_A(GP_IARCR);
+ SCR_REG_A(GP_IARLCR);
+ SCR_REG_A(GPI_BCR);
+ SCR_REG_W(GPA_IARCR);
+ SCR_REG_W(GPA_IARLCR);
+ SCR_REG_W(CCR);
+ SCR_REG_W(PLL2CR);
+ SCR_REG_L(PLL1CR);
+ SCR_REG_B(DIARCR);
+ SCR_REG_B(DBOCR);
+ SCR_REG_B(FER);
+ SCR_REG_W(MCR);
+ SCR_REG_B(CONFIG);
+ SCR_REG_B(DEBUG);
+ }
+ fprintf(stderr, "tc6393xb_scr: unhandled write at %08x: %02x\n",
+ (uint32_t) addr, value & 0xff);
+}
+#undef SCR_REG_B
+#undef SCR_REG_W
+#undef SCR_REG_L
+#undef SCR_REG_A
+
+static void tc6393xb_nand_irq(TC6393xbState *s) {
+ qemu_set_irq(s->sub_irqs[IRQ_TC6393_NAND],
+ (s->nand.imr & 0x80) && (s->nand.imr & s->nand.isr));
+}
+
+static uint32_t tc6393xb_nand_cfg_readb(TC6393xbState *s, hwaddr addr) {
+ switch (addr) {
+ case NAND_CFG_COMMAND:
+ return s->nand_enable ? 2 : 0;
+ case NAND_CFG_BASE:
+ case NAND_CFG_BASE + 1:
+ case NAND_CFG_BASE + 2:
+ case NAND_CFG_BASE + 3:
+ return s->nand_phys >> (addr - NAND_CFG_BASE);
+ }
+ fprintf(stderr, "tc6393xb_nand_cfg: unhandled read at %08x\n", (uint32_t) addr);
+ return 0;
+}
+static void tc6393xb_nand_cfg_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) {
+ switch (addr) {
+ case NAND_CFG_COMMAND:
+ s->nand_enable = (value & 0x2);
+ return;
+ case NAND_CFG_BASE:
+ case NAND_CFG_BASE + 1:
+ case NAND_CFG_BASE + 2:
+ case NAND_CFG_BASE + 3:
+ s->nand_phys &= ~(0xff << ((addr - NAND_CFG_BASE) * 8));
+ s->nand_phys |= (value & 0xff) << ((addr - NAND_CFG_BASE) * 8);
+ return;
+ }
+ fprintf(stderr, "tc6393xb_nand_cfg: unhandled write at %08x: %02x\n",
+ (uint32_t) addr, value & 0xff);
+}
+
+static uint32_t tc6393xb_nand_readb(TC6393xbState *s, hwaddr addr) {
+ switch (addr) {
+ case NAND_DATA + 0:
+ case NAND_DATA + 1:
+ case NAND_DATA + 2:
+ case NAND_DATA + 3:
+ return nand_getio(s->flash);
+ case NAND_MODE:
+ return s->nand.mode;
+ case NAND_STATUS:
+ return 0x14;
+ case NAND_ISR:
+ return s->nand.isr;
+ case NAND_IMR:
+ return s->nand.imr;
+ }
+ fprintf(stderr, "tc6393xb_nand: unhandled read at %08x\n", (uint32_t) addr);
+ return 0;
+}
+static void tc6393xb_nand_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) {
+// fprintf(stderr, "tc6393xb_nand: write at %08x: %02x\n",
+// (uint32_t) addr, value & 0xff);
+ switch (addr) {
+ case NAND_DATA + 0:
+ case NAND_DATA + 1:
+ case NAND_DATA + 2:
+ case NAND_DATA + 3:
+ nand_setio(s->flash, value);
+ s->nand.isr |= 1;
+ tc6393xb_nand_irq(s);
+ return;
+ case NAND_MODE:
+ s->nand.mode = value;
+ nand_setpins(s->flash,
+ value & NAND_MODE_CLE,
+ value & NAND_MODE_ALE,
+ !(value & NAND_MODE_CE),
+ value & NAND_MODE_WP,
+ 0); // FIXME: gnd
+ switch (value & NAND_MODE_ECC_MASK) {
+ case NAND_MODE_ECC_RST:
+ ecc_reset(&s->ecc);
+ break;
+ case NAND_MODE_ECC_READ:
+ // FIXME
+ break;
+ case NAND_MODE_ECC_EN:
+ ecc_reset(&s->ecc);
+ }
+ return;
+ case NAND_ISR:
+ s->nand.isr = value;
+ tc6393xb_nand_irq(s);
+ return;
+ case NAND_IMR:
+ s->nand.imr = value;
+ tc6393xb_nand_irq(s);
+ return;
+ }
+ fprintf(stderr, "tc6393xb_nand: unhandled write at %08x: %02x\n",
+ (uint32_t) addr, value & 0xff);
+}
+
+#define BITS 8
+#include "tc6393xb_template.h"
+#define BITS 15
+#include "tc6393xb_template.h"
+#define BITS 16
+#include "tc6393xb_template.h"
+#define BITS 24
+#include "tc6393xb_template.h"
+#define BITS 32
+#include "tc6393xb_template.h"
+
+static void tc6393xb_draw_graphic(TC6393xbState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ tc6393xb_draw_graphic8(s);
+ break;
+ case 15:
+ tc6393xb_draw_graphic15(s);
+ break;
+ case 16:
+ tc6393xb_draw_graphic16(s);
+ break;
+ case 24:
+ tc6393xb_draw_graphic24(s);
+ break;
+ case 32:
+ tc6393xb_draw_graphic32(s);
+ break;
+ default:
+ printf("tc6393xb: unknown depth %d\n",
+ surface_bits_per_pixel(surface));
+ return;
+ }
+
+ dpy_gfx_update(s->con, 0, 0, s->scr_width, s->scr_height);
+}
+
+static void tc6393xb_draw_blank(TC6393xbState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, w;
+ uint8_t *d;
+
+ if (!full_update)
+ return;
+
+ w = s->scr_width * surface_bytes_per_pixel(surface);
+ d = surface_data(surface);
+ for(i = 0; i < s->scr_height; i++) {
+ memset(d, 0, w);
+ d += surface_stride(surface);
+ }
+
+ dpy_gfx_update(s->con, 0, 0, s->scr_width, s->scr_height);
+}
+
+static void tc6393xb_update_display(void *opaque)
+{
+ TC6393xbState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int full_update;
+
+ if (s->scr_width == 0 || s->scr_height == 0)
+ return;
+
+ full_update = 0;
+ if (s->blanked != s->blank) {
+ s->blanked = s->blank;
+ full_update = 1;
+ }
+ if (s->scr_width != surface_width(surface) ||
+ s->scr_height != surface_height(surface)) {
+ qemu_console_resize(s->con, s->scr_width, s->scr_height);
+ full_update = 1;
+ }
+ if (s->blanked)
+ tc6393xb_draw_blank(s, full_update);
+ else
+ tc6393xb_draw_graphic(s, full_update);
+}
+
+
+static uint64_t tc6393xb_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TC6393xbState *s = opaque;
+
+ switch (addr >> 8) {
+ case 0:
+ return tc6393xb_scr_readb(s, addr & 0xff);
+ case 1:
+ return tc6393xb_nand_cfg_readb(s, addr & 0xff);
+ };
+
+ if ((addr &~0xff) == s->nand_phys && s->nand_enable) {
+// return tc6393xb_nand_readb(s, addr & 0xff);
+ uint8_t d = tc6393xb_nand_readb(s, addr & 0xff);
+// fprintf(stderr, "tc6393xb_nand: read at %08x: %02hhx\n", (uint32_t) addr, d);
+ return d;
+ }
+
+// fprintf(stderr, "tc6393xb: unhandled read at %08x\n", (uint32_t) addr);
+ return 0;
+}
+
+static void tc6393xb_writeb(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size) {
+ TC6393xbState *s = opaque;
+
+ switch (addr >> 8) {
+ case 0:
+ tc6393xb_scr_writeb(s, addr & 0xff, value);
+ return;
+ case 1:
+ tc6393xb_nand_cfg_writeb(s, addr & 0xff, value);
+ return;
+ };
+
+ if ((addr &~0xff) == s->nand_phys && s->nand_enable)
+ tc6393xb_nand_writeb(s, addr & 0xff, value);
+ else
+ fprintf(stderr, "tc6393xb: unhandled write at %08x: %02x\n",
+ (uint32_t) addr, (int)value & 0xff);
+}
+
+static const GraphicHwOps tc6393xb_gfx_ops = {
+ .gfx_update = tc6393xb_update_display,
+};
+
+TC6393xbState *tc6393xb_init(MemoryRegion *sysmem, uint32_t base, qemu_irq irq)
+{
+ TC6393xbState *s;
+ DriveInfo *nand;
+ static const MemoryRegionOps tc6393xb_ops = {
+ .read = tc6393xb_readb,
+ .write = tc6393xb_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ };
+
+ s = (TC6393xbState *) g_malloc0(sizeof(TC6393xbState));
+ s->irq = irq;
+ s->gpio_in = qemu_allocate_irqs(tc6393xb_gpio_set, s, TC6393XB_GPIOS);
+
+ s->l3v = qemu_allocate_irq(tc6393xb_l3v, s, 0);
+ s->blanked = 1;
+
+ s->sub_irqs = qemu_allocate_irqs(tc6393xb_sub_irq, s, TC6393XB_NR_IRQS);
+
+ nand = drive_get(IF_MTD, 0, 0);
+ s->flash = nand_init(nand ? blk_by_legacy_dinfo(nand) : NULL,
+ NAND_MFR_TOSHIBA, 0x76);
+
+ memory_region_init_io(&s->iomem, NULL, &tc6393xb_ops, s, "tc6393xb", 0x10000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ memory_region_init_ram(&s->vram, NULL, "tc6393xb.vram", 0x100000,
+ &error_abort);
+ vmstate_register_ram_global(&s->vram);
+ s->vram_ptr = memory_region_get_ram_ptr(&s->vram);
+ memory_region_add_subregion(sysmem, base + 0x100000, &s->vram);
+ s->scr_width = 480;
+ s->scr_height = 640;
+ s->con = graphic_console_init(NULL, 0, &tc6393xb_gfx_ops, s);
+
+ return s;
+}
diff --git a/hw/display/tc6393xb_template.h b/hw/display/tc6393xb_template.h
new file mode 100644
index 00000000..78629c07
--- /dev/null
+++ b/hw/display/tc6393xb_template.h
@@ -0,0 +1,72 @@
+/*
+ * Toshiba TC6393XB I/O Controller.
+ * Found in Sharp Zaurus SL-6000 (tosa) or some
+ * Toshiba e-Series PDAs.
+ *
+ * FB support code. Based on G364 fb emulator
+ *
+ * Copyright (c) 2007 Hervé Poussineau
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if BITS == 8
+# define SET_PIXEL(addr, color) (*(uint8_t *)addr = color)
+#elif BITS == 15 || BITS == 16
+# define SET_PIXEL(addr, color) (*(uint16_t *)addr = color)
+#elif BITS == 24
+# define SET_PIXEL(addr, color) \
+ do { \
+ addr[0] = color; \
+ addr[1] = (color) >> 8; \
+ addr[2] = (color) >> 16; \
+ } while (0)
+#elif BITS == 32
+# define SET_PIXEL(addr, color) (*(uint32_t *)addr = color)
+#else
+# error unknown bit depth
+#endif
+
+
+static void glue(tc6393xb_draw_graphic, BITS)(TC6393xbState *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i;
+ uint16_t *data_buffer;
+ uint8_t *data_display;
+
+ data_buffer = s->vram_ptr;
+ data_display = surface_data(surface);
+ for(i = 0; i < s->scr_height; i++) {
+#if (BITS == 16)
+ memcpy(data_display, data_buffer, s->scr_width * 2);
+ data_buffer += s->scr_width;
+ data_display += surface_stride(surface);
+#else
+ int j;
+ for (j = 0; j < s->scr_width; j++, data_display += BITS / 8, data_buffer++) {
+ uint16_t color = *data_buffer;
+ uint32_t dest_color = glue(rgb_to_pixel, BITS)(
+ ((color & 0xf800) * 0x108) >> 11,
+ ((color & 0x7e0) * 0x41) >> 9,
+ ((color & 0x1f) * 0x21) >> 2
+ );
+ SET_PIXEL(data_display, dest_color);
+ }
+#endif
+ }
+}
+
+#undef BITS
+#undef SET_PIXEL
diff --git a/hw/display/tcx.c b/hw/display/tcx.c
new file mode 100644
index 00000000..327ce301
--- /dev/null
+++ b/hw/display/tcx.c
@@ -0,0 +1,1105 @@
+/*
+ * QEMU TCX Frame buffer
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "qemu-common.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "hw/loader.h"
+#include "hw/sysbus.h"
+#include "qemu/error-report.h"
+
+#define TCX_ROM_FILE "QEMU,tcx.bin"
+#define FCODE_MAX_ROM_SIZE 0x10000
+
+#define MAXX 1024
+#define MAXY 768
+#define TCX_DAC_NREGS 16
+#define TCX_THC_NREGS 0x1000
+#define TCX_DHC_NREGS 0x4000
+#define TCX_TEC_NREGS 0x1000
+#define TCX_ALT_NREGS 0x8000
+#define TCX_STIP_NREGS 0x800000
+#define TCX_BLIT_NREGS 0x800000
+#define TCX_RSTIP_NREGS 0x800000
+#define TCX_RBLIT_NREGS 0x800000
+
+#define TCX_THC_MISC 0x818
+#define TCX_THC_CURSXY 0x8fc
+#define TCX_THC_CURSMASK 0x900
+#define TCX_THC_CURSBITS 0x980
+
+#define TYPE_TCX "SUNW,tcx"
+#define TCX(obj) OBJECT_CHECK(TCXState, (obj), TYPE_TCX)
+
+typedef struct TCXState {
+ SysBusDevice parent_obj;
+
+ QemuConsole *con;
+ qemu_irq irq;
+ uint8_t *vram;
+ uint32_t *vram24, *cplane;
+ hwaddr prom_addr;
+ MemoryRegion rom;
+ MemoryRegion vram_mem;
+ MemoryRegion vram_8bit;
+ MemoryRegion vram_24bit;
+ MemoryRegion stip;
+ MemoryRegion blit;
+ MemoryRegion vram_cplane;
+ MemoryRegion rstip;
+ MemoryRegion rblit;
+ MemoryRegion tec;
+ MemoryRegion dac;
+ MemoryRegion thc;
+ MemoryRegion dhc;
+ MemoryRegion alt;
+ MemoryRegion thc24;
+
+ ram_addr_t vram24_offset, cplane_offset;
+ uint32_t tmpblit;
+ uint32_t vram_size;
+ uint32_t palette[260];
+ uint8_t r[260], g[260], b[260];
+ uint16_t width, height, depth;
+ uint8_t dac_index, dac_state;
+ uint32_t thcmisc;
+ uint32_t cursmask[32];
+ uint32_t cursbits[32];
+ uint16_t cursx;
+ uint16_t cursy;
+} TCXState;
+
+static void tcx_set_dirty(TCXState *s)
+{
+ memory_region_set_dirty(&s->vram_mem, 0, MAXX * MAXY);
+}
+
+static inline int tcx24_check_dirty(TCXState *s, ram_addr_t page,
+ ram_addr_t page24, ram_addr_t cpage)
+{
+ int ret;
+
+ ret = memory_region_get_dirty(&s->vram_mem, page, TARGET_PAGE_SIZE,
+ DIRTY_MEMORY_VGA);
+ ret |= memory_region_get_dirty(&s->vram_mem, page24, TARGET_PAGE_SIZE * 4,
+ DIRTY_MEMORY_VGA);
+ ret |= memory_region_get_dirty(&s->vram_mem, cpage, TARGET_PAGE_SIZE * 4,
+ DIRTY_MEMORY_VGA);
+ return ret;
+}
+
+static inline void tcx24_reset_dirty(TCXState *ts, ram_addr_t page_min,
+ ram_addr_t page_max, ram_addr_t page24,
+ ram_addr_t cpage)
+{
+ memory_region_reset_dirty(&ts->vram_mem,
+ page_min,
+ (page_max - page_min) + TARGET_PAGE_SIZE,
+ DIRTY_MEMORY_VGA);
+ memory_region_reset_dirty(&ts->vram_mem,
+ page24 + page_min * 4,
+ (page_max - page_min) * 4 + TARGET_PAGE_SIZE,
+ DIRTY_MEMORY_VGA);
+ memory_region_reset_dirty(&ts->vram_mem,
+ cpage + page_min * 4,
+ (page_max - page_min) * 4 + TARGET_PAGE_SIZE,
+ DIRTY_MEMORY_VGA);
+}
+
+static void update_palette_entries(TCXState *s, int start, int end)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i;
+
+ for (i = start; i < end; i++) {
+ switch (surface_bits_per_pixel(surface)) {
+ default:
+ case 8:
+ s->palette[i] = rgb_to_pixel8(s->r[i], s->g[i], s->b[i]);
+ break;
+ case 15:
+ s->palette[i] = rgb_to_pixel15(s->r[i], s->g[i], s->b[i]);
+ break;
+ case 16:
+ s->palette[i] = rgb_to_pixel16(s->r[i], s->g[i], s->b[i]);
+ break;
+ case 32:
+ if (is_surface_bgr(surface)) {
+ s->palette[i] = rgb_to_pixel32bgr(s->r[i], s->g[i], s->b[i]);
+ } else {
+ s->palette[i] = rgb_to_pixel32(s->r[i], s->g[i], s->b[i]);
+ }
+ break;
+ }
+ }
+ tcx_set_dirty(s);
+}
+
+static void tcx_draw_line32(TCXState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int x;
+ uint8_t val;
+ uint32_t *p = (uint32_t *)d;
+
+ for (x = 0; x < width; x++) {
+ val = *s++;
+ *p++ = s1->palette[val];
+ }
+}
+
+static void tcx_draw_line16(TCXState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int x;
+ uint8_t val;
+ uint16_t *p = (uint16_t *)d;
+
+ for (x = 0; x < width; x++) {
+ val = *s++;
+ *p++ = s1->palette[val];
+ }
+}
+
+static void tcx_draw_line8(TCXState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int x;
+ uint8_t val;
+
+ for(x = 0; x < width; x++) {
+ val = *s++;
+ *d++ = s1->palette[val];
+ }
+}
+
+static void tcx_draw_cursor32(TCXState *s1, uint8_t *d,
+ int y, int width)
+{
+ int x, len;
+ uint32_t mask, bits;
+ uint32_t *p = (uint32_t *)d;
+
+ y = y - s1->cursy;
+ mask = s1->cursmask[y];
+ bits = s1->cursbits[y];
+ len = MIN(width - s1->cursx, 32);
+ p = &p[s1->cursx];
+ for (x = 0; x < len; x++) {
+ if (mask & 0x80000000) {
+ if (bits & 0x80000000) {
+ *p = s1->palette[259];
+ } else {
+ *p = s1->palette[258];
+ }
+ }
+ p++;
+ mask <<= 1;
+ bits <<= 1;
+ }
+}
+
+static void tcx_draw_cursor16(TCXState *s1, uint8_t *d,
+ int y, int width)
+{
+ int x, len;
+ uint32_t mask, bits;
+ uint16_t *p = (uint16_t *)d;
+
+ y = y - s1->cursy;
+ mask = s1->cursmask[y];
+ bits = s1->cursbits[y];
+ len = MIN(width - s1->cursx, 32);
+ p = &p[s1->cursx];
+ for (x = 0; x < len; x++) {
+ if (mask & 0x80000000) {
+ if (bits & 0x80000000) {
+ *p = s1->palette[259];
+ } else {
+ *p = s1->palette[258];
+ }
+ }
+ p++;
+ mask <<= 1;
+ bits <<= 1;
+ }
+}
+
+static void tcx_draw_cursor8(TCXState *s1, uint8_t *d,
+ int y, int width)
+{
+ int x, len;
+ uint32_t mask, bits;
+
+ y = y - s1->cursy;
+ mask = s1->cursmask[y];
+ bits = s1->cursbits[y];
+ len = MIN(width - s1->cursx, 32);
+ d = &d[s1->cursx];
+ for (x = 0; x < len; x++) {
+ if (mask & 0x80000000) {
+ if (bits & 0x80000000) {
+ *d = s1->palette[259];
+ } else {
+ *d = s1->palette[258];
+ }
+ }
+ d++;
+ mask <<= 1;
+ bits <<= 1;
+ }
+}
+
+/*
+ XXX Could be much more optimal:
+ * detect if line/page/whole screen is in 24 bit mode
+ * if destination is also BGR, use memcpy
+ */
+static inline void tcx24_draw_line32(TCXState *s1, uint8_t *d,
+ const uint8_t *s, int width,
+ const uint32_t *cplane,
+ const uint32_t *s24)
+{
+ DisplaySurface *surface = qemu_console_surface(s1->con);
+ int x, bgr, r, g, b;
+ uint8_t val, *p8;
+ uint32_t *p = (uint32_t *)d;
+ uint32_t dval;
+ bgr = is_surface_bgr(surface);
+ for(x = 0; x < width; x++, s++, s24++) {
+ if (be32_to_cpu(*cplane) & 0x03000000) {
+ /* 24-bit direct, BGR order */
+ p8 = (uint8_t *)s24;
+ p8++;
+ b = *p8++;
+ g = *p8++;
+ r = *p8;
+ if (bgr)
+ dval = rgb_to_pixel32bgr(r, g, b);
+ else
+ dval = rgb_to_pixel32(r, g, b);
+ } else {
+ /* 8-bit pseudocolor */
+ val = *s;
+ dval = s1->palette[val];
+ }
+ *p++ = dval;
+ cplane++;
+ }
+}
+
+/* Fixed line length 1024 allows us to do nice tricks not possible on
+ VGA... */
+
+static void tcx_update_display(void *opaque)
+{
+ TCXState *ts = opaque;
+ DisplaySurface *surface = qemu_console_surface(ts->con);
+ ram_addr_t page, page_min, page_max;
+ int y, y_start, dd, ds;
+ uint8_t *d, *s;
+ void (*f)(TCXState *s1, uint8_t *dst, const uint8_t *src, int width);
+ void (*fc)(TCXState *s1, uint8_t *dst, int y, int width);
+
+ if (surface_bits_per_pixel(surface) == 0) {
+ return;
+ }
+
+ page = 0;
+ y_start = -1;
+ page_min = -1;
+ page_max = 0;
+ d = surface_data(surface);
+ s = ts->vram;
+ dd = surface_stride(surface);
+ ds = 1024;
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 32:
+ f = tcx_draw_line32;
+ fc = tcx_draw_cursor32;
+ break;
+ case 15:
+ case 16:
+ f = tcx_draw_line16;
+ fc = tcx_draw_cursor16;
+ break;
+ default:
+ case 8:
+ f = tcx_draw_line8;
+ fc = tcx_draw_cursor8;
+ break;
+ case 0:
+ return;
+ }
+
+ memory_region_sync_dirty_bitmap(&ts->vram_mem);
+ for (y = 0; y < ts->height; page += TARGET_PAGE_SIZE) {
+ if (memory_region_get_dirty(&ts->vram_mem, page, TARGET_PAGE_SIZE,
+ DIRTY_MEMORY_VGA)) {
+ if (y_start < 0)
+ y_start = y;
+ if (page < page_min)
+ page_min = page;
+ if (page > page_max)
+ page_max = page;
+
+ f(ts, d, s, ts->width);
+ if (y >= ts->cursy && y < ts->cursy + 32 && ts->cursx < ts->width) {
+ fc(ts, d, y, ts->width);
+ }
+ d += dd;
+ s += ds;
+ y++;
+
+ f(ts, d, s, ts->width);
+ if (y >= ts->cursy && y < ts->cursy + 32 && ts->cursx < ts->width) {
+ fc(ts, d, y, ts->width);
+ }
+ d += dd;
+ s += ds;
+ y++;
+
+ f(ts, d, s, ts->width);
+ if (y >= ts->cursy && y < ts->cursy + 32 && ts->cursx < ts->width) {
+ fc(ts, d, y, ts->width);
+ }
+ d += dd;
+ s += ds;
+ y++;
+
+ f(ts, d, s, ts->width);
+ if (y >= ts->cursy && y < ts->cursy + 32 && ts->cursx < ts->width) {
+ fc(ts, d, y, ts->width);
+ }
+ d += dd;
+ s += ds;
+ y++;
+ } else {
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(ts->con, 0, y_start,
+ ts->width, y - y_start);
+ y_start = -1;
+ }
+ d += dd * 4;
+ s += ds * 4;
+ y += 4;
+ }
+ }
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(ts->con, 0, y_start,
+ ts->width, y - y_start);
+ }
+ /* reset modified pages */
+ if (page_max >= page_min) {
+ memory_region_reset_dirty(&ts->vram_mem,
+ page_min,
+ (page_max - page_min) + TARGET_PAGE_SIZE,
+ DIRTY_MEMORY_VGA);
+ }
+}
+
+static void tcx24_update_display(void *opaque)
+{
+ TCXState *ts = opaque;
+ DisplaySurface *surface = qemu_console_surface(ts->con);
+ ram_addr_t page, page_min, page_max, cpage, page24;
+ int y, y_start, dd, ds;
+ uint8_t *d, *s;
+ uint32_t *cptr, *s24;
+
+ if (surface_bits_per_pixel(surface) != 32) {
+ return;
+ }
+
+ page = 0;
+ page24 = ts->vram24_offset;
+ cpage = ts->cplane_offset;
+ y_start = -1;
+ page_min = -1;
+ page_max = 0;
+ d = surface_data(surface);
+ s = ts->vram;
+ s24 = ts->vram24;
+ cptr = ts->cplane;
+ dd = surface_stride(surface);
+ ds = 1024;
+
+ memory_region_sync_dirty_bitmap(&ts->vram_mem);
+ for (y = 0; y < ts->height; page += TARGET_PAGE_SIZE,
+ page24 += TARGET_PAGE_SIZE, cpage += TARGET_PAGE_SIZE) {
+ if (tcx24_check_dirty(ts, page, page24, cpage)) {
+ if (y_start < 0)
+ y_start = y;
+ if (page < page_min)
+ page_min = page;
+ if (page > page_max)
+ page_max = page;
+ tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
+ if (y >= ts->cursy && y < ts->cursy+32 && ts->cursx < ts->width) {
+ tcx_draw_cursor32(ts, d, y, ts->width);
+ }
+ d += dd;
+ s += ds;
+ cptr += ds;
+ s24 += ds;
+ y++;
+ tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
+ if (y >= ts->cursy && y < ts->cursy+32 && ts->cursx < ts->width) {
+ tcx_draw_cursor32(ts, d, y, ts->width);
+ }
+ d += dd;
+ s += ds;
+ cptr += ds;
+ s24 += ds;
+ y++;
+ tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
+ if (y >= ts->cursy && y < ts->cursy+32 && ts->cursx < ts->width) {
+ tcx_draw_cursor32(ts, d, y, ts->width);
+ }
+ d += dd;
+ s += ds;
+ cptr += ds;
+ s24 += ds;
+ y++;
+ tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
+ if (y >= ts->cursy && y < ts->cursy+32 && ts->cursx < ts->width) {
+ tcx_draw_cursor32(ts, d, y, ts->width);
+ }
+ d += dd;
+ s += ds;
+ cptr += ds;
+ s24 += ds;
+ y++;
+ } else {
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(ts->con, 0, y_start,
+ ts->width, y - y_start);
+ y_start = -1;
+ }
+ d += dd * 4;
+ s += ds * 4;
+ cptr += ds * 4;
+ s24 += ds * 4;
+ y += 4;
+ }
+ }
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(ts->con, 0, y_start,
+ ts->width, y - y_start);
+ }
+ /* reset modified pages */
+ if (page_max >= page_min) {
+ tcx24_reset_dirty(ts, page_min, page_max, page24, cpage);
+ }
+}
+
+static void tcx_invalidate_display(void *opaque)
+{
+ TCXState *s = opaque;
+
+ tcx_set_dirty(s);
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static void tcx24_invalidate_display(void *opaque)
+{
+ TCXState *s = opaque;
+
+ tcx_set_dirty(s);
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static int vmstate_tcx_post_load(void *opaque, int version_id)
+{
+ TCXState *s = opaque;
+
+ update_palette_entries(s, 0, 256);
+ tcx_set_dirty(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_tcx = {
+ .name ="tcx",
+ .version_id = 4,
+ .minimum_version_id = 4,
+ .post_load = vmstate_tcx_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(height, TCXState),
+ VMSTATE_UINT16(width, TCXState),
+ VMSTATE_UINT16(depth, TCXState),
+ VMSTATE_BUFFER(r, TCXState),
+ VMSTATE_BUFFER(g, TCXState),
+ VMSTATE_BUFFER(b, TCXState),
+ VMSTATE_UINT8(dac_index, TCXState),
+ VMSTATE_UINT8(dac_state, TCXState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tcx_reset(DeviceState *d)
+{
+ TCXState *s = TCX(d);
+
+ /* Initialize palette */
+ memset(s->r, 0, 260);
+ memset(s->g, 0, 260);
+ memset(s->b, 0, 260);
+ s->r[255] = s->g[255] = s->b[255] = 255;
+ s->r[256] = s->g[256] = s->b[256] = 255;
+ s->r[258] = s->g[258] = s->b[258] = 255;
+ update_palette_entries(s, 0, 260);
+ memset(s->vram, 0, MAXX*MAXY);
+ memory_region_reset_dirty(&s->vram_mem, 0, MAXX * MAXY * (1 + 4 + 4),
+ DIRTY_MEMORY_VGA);
+ s->dac_index = 0;
+ s->dac_state = 0;
+ s->cursx = 0xf000; /* Put cursor off screen */
+ s->cursy = 0xf000;
+}
+
+static uint64_t tcx_dac_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TCXState *s = opaque;
+ uint32_t val = 0;
+
+ switch (s->dac_state) {
+ case 0:
+ val = s->r[s->dac_index] << 24;
+ s->dac_state++;
+ break;
+ case 1:
+ val = s->g[s->dac_index] << 24;
+ s->dac_state++;
+ break;
+ case 2:
+ val = s->b[s->dac_index] << 24;
+ s->dac_index = (s->dac_index + 1) & 0xff; /* Index autoincrement */
+ default:
+ s->dac_state = 0;
+ break;
+ }
+
+ return val;
+}
+
+static void tcx_dac_writel(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TCXState *s = opaque;
+ unsigned index;
+
+ switch (addr) {
+ case 0: /* Address */
+ s->dac_index = val >> 24;
+ s->dac_state = 0;
+ break;
+ case 4: /* Pixel colours */
+ case 12: /* Overlay (cursor) colours */
+ if (addr & 8) {
+ index = (s->dac_index & 3) + 256;
+ } else {
+ index = s->dac_index;
+ }
+ switch (s->dac_state) {
+ case 0:
+ s->r[index] = val >> 24;
+ update_palette_entries(s, index, index + 1);
+ s->dac_state++;
+ break;
+ case 1:
+ s->g[index] = val >> 24;
+ update_palette_entries(s, index, index + 1);
+ s->dac_state++;
+ break;
+ case 2:
+ s->b[index] = val >> 24;
+ update_palette_entries(s, index, index + 1);
+ s->dac_index = (s->dac_index + 1) & 0xff; /* Index autoincrement */
+ default:
+ s->dac_state = 0;
+ break;
+ }
+ break;
+ default: /* Control registers */
+ break;
+ }
+}
+
+static const MemoryRegionOps tcx_dac_ops = {
+ .read = tcx_dac_readl,
+ .write = tcx_dac_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t tcx_stip_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void tcx_stip_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+ int i;
+ uint32_t col;
+
+ if (!(addr & 4)) {
+ s->tmpblit = val;
+ } else {
+ addr = (addr >> 3) & 0xfffff;
+ col = cpu_to_be32(s->tmpblit);
+ if (s->depth == 24) {
+ for (i = 0; i < 32; i++) {
+ if (val & 0x80000000) {
+ s->vram[addr + i] = s->tmpblit;
+ s->vram24[addr + i] = col;
+ }
+ val <<= 1;
+ }
+ } else {
+ for (i = 0; i < 32; i++) {
+ if (val & 0x80000000) {
+ s->vram[addr + i] = s->tmpblit;
+ }
+ val <<= 1;
+ }
+ }
+ memory_region_set_dirty(&s->vram_mem, addr, 32);
+ }
+}
+
+static void tcx_rstip_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+ int i;
+ uint32_t col;
+
+ if (!(addr & 4)) {
+ s->tmpblit = val;
+ } else {
+ addr = (addr >> 3) & 0xfffff;
+ col = cpu_to_be32(s->tmpblit);
+ if (s->depth == 24) {
+ for (i = 0; i < 32; i++) {
+ if (val & 0x80000000) {
+ s->vram[addr + i] = s->tmpblit;
+ s->vram24[addr + i] = col;
+ s->cplane[addr + i] = col;
+ }
+ val <<= 1;
+ }
+ } else {
+ for (i = 0; i < 32; i++) {
+ if (val & 0x80000000) {
+ s->vram[addr + i] = s->tmpblit;
+ }
+ val <<= 1;
+ }
+ }
+ memory_region_set_dirty(&s->vram_mem, addr, 32);
+ }
+}
+
+static const MemoryRegionOps tcx_stip_ops = {
+ .read = tcx_stip_readl,
+ .write = tcx_stip_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps tcx_rstip_ops = {
+ .read = tcx_stip_readl,
+ .write = tcx_rstip_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t tcx_blit_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void tcx_blit_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+ uint32_t adsr, len;
+ int i;
+
+ if (!(addr & 4)) {
+ s->tmpblit = val;
+ } else {
+ addr = (addr >> 3) & 0xfffff;
+ adsr = val & 0xffffff;
+ len = ((val >> 24) & 0x1f) + 1;
+ if (adsr == 0xffffff) {
+ memset(&s->vram[addr], s->tmpblit, len);
+ if (s->depth == 24) {
+ val = s->tmpblit & 0xffffff;
+ val = cpu_to_be32(val);
+ for (i = 0; i < len; i++) {
+ s->vram24[addr + i] = val;
+ }
+ }
+ } else {
+ memcpy(&s->vram[addr], &s->vram[adsr], len);
+ if (s->depth == 24) {
+ memcpy(&s->vram24[addr], &s->vram24[adsr], len * 4);
+ }
+ }
+ memory_region_set_dirty(&s->vram_mem, addr, len);
+ }
+}
+
+static void tcx_rblit_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+ uint32_t adsr, len;
+ int i;
+
+ if (!(addr & 4)) {
+ s->tmpblit = val;
+ } else {
+ addr = (addr >> 3) & 0xfffff;
+ adsr = val & 0xffffff;
+ len = ((val >> 24) & 0x1f) + 1;
+ if (adsr == 0xffffff) {
+ memset(&s->vram[addr], s->tmpblit, len);
+ if (s->depth == 24) {
+ val = s->tmpblit & 0xffffff;
+ val = cpu_to_be32(val);
+ for (i = 0; i < len; i++) {
+ s->vram24[addr + i] = val;
+ s->cplane[addr + i] = val;
+ }
+ }
+ } else {
+ memcpy(&s->vram[addr], &s->vram[adsr], len);
+ if (s->depth == 24) {
+ memcpy(&s->vram24[addr], &s->vram24[adsr], len * 4);
+ memcpy(&s->cplane[addr], &s->cplane[adsr], len * 4);
+ }
+ }
+ memory_region_set_dirty(&s->vram_mem, addr, len);
+ }
+}
+
+static const MemoryRegionOps tcx_blit_ops = {
+ .read = tcx_blit_readl,
+ .write = tcx_blit_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps tcx_rblit_ops = {
+ .read = tcx_blit_readl,
+ .write = tcx_rblit_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void tcx_invalidate_cursor_position(TCXState *s)
+{
+ int ymin, ymax, start, end;
+
+ /* invalidate only near the cursor */
+ ymin = s->cursy;
+ if (ymin >= s->height) {
+ return;
+ }
+ ymax = MIN(s->height, ymin + 32);
+ start = ymin * 1024;
+ end = ymax * 1024;
+
+ memory_region_set_dirty(&s->vram_mem, start, end-start);
+}
+
+static uint64_t tcx_thc_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TCXState *s = opaque;
+ uint64_t val;
+
+ if (addr == TCX_THC_MISC) {
+ val = s->thcmisc | 0x02000000;
+ } else {
+ val = 0;
+ }
+ return val;
+}
+
+static void tcx_thc_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+
+ if (addr == TCX_THC_CURSXY) {
+ tcx_invalidate_cursor_position(s);
+ s->cursx = val >> 16;
+ s->cursy = val;
+ tcx_invalidate_cursor_position(s);
+ } else if (addr >= TCX_THC_CURSMASK && addr < TCX_THC_CURSMASK + 128) {
+ s->cursmask[(addr - TCX_THC_CURSMASK) >> 2] = val;
+ tcx_invalidate_cursor_position(s);
+ } else if (addr >= TCX_THC_CURSBITS && addr < TCX_THC_CURSBITS + 128) {
+ s->cursbits[(addr - TCX_THC_CURSBITS) >> 2] = val;
+ tcx_invalidate_cursor_position(s);
+ } else if (addr == TCX_THC_MISC) {
+ s->thcmisc = val;
+ }
+
+}
+
+static const MemoryRegionOps tcx_thc_ops = {
+ .read = tcx_thc_readl,
+ .write = tcx_thc_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t tcx_dummy_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void tcx_dummy_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ return;
+}
+
+static const MemoryRegionOps tcx_dummy_ops = {
+ .read = tcx_dummy_readl,
+ .write = tcx_dummy_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const GraphicHwOps tcx_ops = {
+ .invalidate = tcx_invalidate_display,
+ .gfx_update = tcx_update_display,
+};
+
+static const GraphicHwOps tcx24_ops = {
+ .invalidate = tcx24_invalidate_display,
+ .gfx_update = tcx24_update_display,
+};
+
+static void tcx_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ TCXState *s = TCX(obj);
+
+ memory_region_init_ram(&s->rom, OBJECT(s), "tcx.prom", FCODE_MAX_ROM_SIZE,
+ &error_abort);
+ memory_region_set_readonly(&s->rom, true);
+ sysbus_init_mmio(sbd, &s->rom);
+
+ /* 2/STIP : Stippler */
+ memory_region_init_io(&s->stip, OBJECT(s), &tcx_stip_ops, s, "tcx.stip",
+ TCX_STIP_NREGS);
+ sysbus_init_mmio(sbd, &s->stip);
+
+ /* 3/BLIT : Blitter */
+ memory_region_init_io(&s->blit, OBJECT(s), &tcx_blit_ops, s, "tcx.blit",
+ TCX_BLIT_NREGS);
+ sysbus_init_mmio(sbd, &s->blit);
+
+ /* 5/RSTIP : Raw Stippler */
+ memory_region_init_io(&s->rstip, OBJECT(s), &tcx_rstip_ops, s, "tcx.rstip",
+ TCX_RSTIP_NREGS);
+ sysbus_init_mmio(sbd, &s->rstip);
+
+ /* 6/RBLIT : Raw Blitter */
+ memory_region_init_io(&s->rblit, OBJECT(s), &tcx_rblit_ops, s, "tcx.rblit",
+ TCX_RBLIT_NREGS);
+ sysbus_init_mmio(sbd, &s->rblit);
+
+ /* 7/TEC : ??? */
+ memory_region_init_io(&s->tec, OBJECT(s), &tcx_dummy_ops, s,
+ "tcx.tec", TCX_TEC_NREGS);
+ sysbus_init_mmio(sbd, &s->tec);
+
+ /* 8/CMAP : DAC */
+ memory_region_init_io(&s->dac, OBJECT(s), &tcx_dac_ops, s,
+ "tcx.dac", TCX_DAC_NREGS);
+ sysbus_init_mmio(sbd, &s->dac);
+
+ /* 9/THC : Cursor */
+ memory_region_init_io(&s->thc, OBJECT(s), &tcx_thc_ops, s, "tcx.thc",
+ TCX_THC_NREGS);
+ sysbus_init_mmio(sbd, &s->thc);
+
+ /* 11/DHC : ??? */
+ memory_region_init_io(&s->dhc, OBJECT(s), &tcx_dummy_ops, s, "tcx.dhc",
+ TCX_DHC_NREGS);
+ sysbus_init_mmio(sbd, &s->dhc);
+
+ /* 12/ALT : ??? */
+ memory_region_init_io(&s->alt, OBJECT(s), &tcx_dummy_ops, s, "tcx.alt",
+ TCX_ALT_NREGS);
+ sysbus_init_mmio(sbd, &s->alt);
+
+ return;
+}
+
+static void tcx_realizefn(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ TCXState *s = TCX(dev);
+ ram_addr_t vram_offset = 0;
+ int size, ret;
+ uint8_t *vram_base;
+ char *fcode_filename;
+
+ memory_region_init_ram(&s->vram_mem, OBJECT(s), "tcx.vram",
+ s->vram_size * (1 + 4 + 4), &error_abort);
+ vmstate_register_ram_global(&s->vram_mem);
+ memory_region_set_log(&s->vram_mem, true, DIRTY_MEMORY_VGA);
+ vram_base = memory_region_get_ram_ptr(&s->vram_mem);
+
+ /* 10/ROM : FCode ROM */
+ vmstate_register_ram_global(&s->rom);
+ fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, TCX_ROM_FILE);
+ if (fcode_filename) {
+ ret = load_image_targphys(fcode_filename, s->prom_addr,
+ FCODE_MAX_ROM_SIZE);
+ g_free(fcode_filename);
+ if (ret < 0 || ret > FCODE_MAX_ROM_SIZE) {
+ error_report("tcx: could not load prom '%s'", TCX_ROM_FILE);
+ }
+ }
+
+ /* 0/DFB8 : 8-bit plane */
+ s->vram = vram_base;
+ size = s->vram_size;
+ memory_region_init_alias(&s->vram_8bit, OBJECT(s), "tcx.vram.8bit",
+ &s->vram_mem, vram_offset, size);
+ sysbus_init_mmio(sbd, &s->vram_8bit);
+ vram_offset += size;
+ vram_base += size;
+
+ /* 1/DFB24 : 24bit plane */
+ size = s->vram_size * 4;
+ s->vram24 = (uint32_t *)vram_base;
+ s->vram24_offset = vram_offset;
+ memory_region_init_alias(&s->vram_24bit, OBJECT(s), "tcx.vram.24bit",
+ &s->vram_mem, vram_offset, size);
+ sysbus_init_mmio(sbd, &s->vram_24bit);
+ vram_offset += size;
+ vram_base += size;
+
+ /* 4/RDFB32 : Raw Framebuffer */
+ size = s->vram_size * 4;
+ s->cplane = (uint32_t *)vram_base;
+ s->cplane_offset = vram_offset;
+ memory_region_init_alias(&s->vram_cplane, OBJECT(s), "tcx.vram.cplane",
+ &s->vram_mem, vram_offset, size);
+ sysbus_init_mmio(sbd, &s->vram_cplane);
+
+ /* 9/THC24bits : NetBSD writes here even with 8-bit display: dummy */
+ if (s->depth == 8) {
+ memory_region_init_io(&s->thc24, OBJECT(s), &tcx_dummy_ops, s,
+ "tcx.thc24", TCX_THC_NREGS);
+ sysbus_init_mmio(sbd, &s->thc24);
+ }
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ if (s->depth == 8) {
+ s->con = graphic_console_init(DEVICE(dev), 0, &tcx_ops, s);
+ } else {
+ s->con = graphic_console_init(DEVICE(dev), 0, &tcx24_ops, s);
+ }
+ s->thcmisc = 0;
+
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static Property tcx_properties[] = {
+ DEFINE_PROP_UINT32("vram_size", TCXState, vram_size, -1),
+ DEFINE_PROP_UINT16("width", TCXState, width, -1),
+ DEFINE_PROP_UINT16("height", TCXState, height, -1),
+ DEFINE_PROP_UINT16("depth", TCXState, depth, -1),
+ DEFINE_PROP_UINT64("prom_addr", TCXState, prom_addr, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tcx_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = tcx_realizefn;
+ dc->reset = tcx_reset;
+ dc->vmsd = &vmstate_tcx;
+ dc->props = tcx_properties;
+}
+
+static const TypeInfo tcx_info = {
+ .name = TYPE_TCX,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TCXState),
+ .instance_init = tcx_initfn,
+ .class_init = tcx_class_init,
+};
+
+static void tcx_register_types(void)
+{
+ type_register_static(&tcx_info);
+}
+
+type_init(tcx_register_types)
diff --git a/hw/display/vga-helpers.h b/hw/display/vga-helpers.h
new file mode 100644
index 00000000..94f6de20
--- /dev/null
+++ b/hw/display/vga-helpers.h
@@ -0,0 +1,439 @@
+/*
+ * QEMU VGA Emulator templates
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * 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.
+ */
+
+static inline void vga_draw_glyph_line(uint8_t *d, uint32_t font_data,
+ uint32_t xorcol, uint32_t bgcol)
+{
+ ((uint32_t *)d)[0] = (-((font_data >> 7)) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[1] = (-((font_data >> 6) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[2] = (-((font_data >> 5) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[3] = (-((font_data >> 4) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[4] = (-((font_data >> 3) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[5] = (-((font_data >> 2) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[6] = (-((font_data >> 1) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[7] = (-((font_data >> 0) & 1) & xorcol) ^ bgcol;
+}
+
+static void vga_draw_glyph8(uint8_t *d, int linesize,
+ const uint8_t *font_ptr, int h,
+ uint32_t fgcol, uint32_t bgcol)
+{
+ uint32_t font_data, xorcol;
+
+ xorcol = bgcol ^ fgcol;
+ do {
+ font_data = font_ptr[0];
+ vga_draw_glyph_line(d, font_data, xorcol, bgcol);
+ font_ptr += 4;
+ d += linesize;
+ } while (--h);
+}
+
+static void vga_draw_glyph16(uint8_t *d, int linesize,
+ const uint8_t *font_ptr, int h,
+ uint32_t fgcol, uint32_t bgcol)
+{
+ uint32_t font_data, xorcol;
+
+ xorcol = bgcol ^ fgcol;
+ do {
+ font_data = font_ptr[0];
+ vga_draw_glyph_line(d, expand4to8[font_data >> 4],
+ xorcol, bgcol);
+ vga_draw_glyph_line(d + 32, expand4to8[font_data & 0x0f],
+ xorcol, bgcol);
+ font_ptr += 4;
+ d += linesize;
+ } while (--h);
+}
+
+static void vga_draw_glyph9(uint8_t *d, int linesize,
+ const uint8_t *font_ptr, int h,
+ uint32_t fgcol, uint32_t bgcol, int dup9)
+{
+ uint32_t font_data, xorcol, v;
+
+ xorcol = bgcol ^ fgcol;
+ do {
+ font_data = font_ptr[0];
+ ((uint32_t *)d)[0] = (-((font_data >> 7)) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[1] = (-((font_data >> 6) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[2] = (-((font_data >> 5) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[3] = (-((font_data >> 4) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[4] = (-((font_data >> 3) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[5] = (-((font_data >> 2) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[6] = (-((font_data >> 1) & 1) & xorcol) ^ bgcol;
+ v = (-((font_data >> 0) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[7] = v;
+ if (dup9)
+ ((uint32_t *)d)[8] = v;
+ else
+ ((uint32_t *)d)[8] = bgcol;
+ font_ptr += 4;
+ d += linesize;
+ } while (--h);
+}
+
+/*
+ * 4 color mode
+ */
+static void vga_draw_line2(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ uint32_t plane_mask, *palette, data, v;
+ int x;
+
+ palette = s1->last_palette;
+ plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ data = ((uint32_t *)s)[0];
+ data &= plane_mask;
+ v = expand2[GET_PLANE(data, 0)];
+ v |= expand2[GET_PLANE(data, 2)] << 2;
+ ((uint32_t *)d)[0] = palette[v >> 12];
+ ((uint32_t *)d)[1] = palette[(v >> 8) & 0xf];
+ ((uint32_t *)d)[2] = palette[(v >> 4) & 0xf];
+ ((uint32_t *)d)[3] = palette[(v >> 0) & 0xf];
+
+ v = expand2[GET_PLANE(data, 1)];
+ v |= expand2[GET_PLANE(data, 3)] << 2;
+ ((uint32_t *)d)[4] = palette[v >> 12];
+ ((uint32_t *)d)[5] = palette[(v >> 8) & 0xf];
+ ((uint32_t *)d)[6] = palette[(v >> 4) & 0xf];
+ ((uint32_t *)d)[7] = palette[(v >> 0) & 0xf];
+ d += 32;
+ s += 4;
+ }
+}
+
+#define PUT_PIXEL2(d, n, v) \
+((uint32_t *)d)[2*(n)] = ((uint32_t *)d)[2*(n)+1] = (v)
+
+/*
+ * 4 color mode, dup2 horizontal
+ */
+static void vga_draw_line2d2(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ uint32_t plane_mask, *palette, data, v;
+ int x;
+
+ palette = s1->last_palette;
+ plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ data = ((uint32_t *)s)[0];
+ data &= plane_mask;
+ v = expand2[GET_PLANE(data, 0)];
+ v |= expand2[GET_PLANE(data, 2)] << 2;
+ PUT_PIXEL2(d, 0, palette[v >> 12]);
+ PUT_PIXEL2(d, 1, palette[(v >> 8) & 0xf]);
+ PUT_PIXEL2(d, 2, palette[(v >> 4) & 0xf]);
+ PUT_PIXEL2(d, 3, palette[(v >> 0) & 0xf]);
+
+ v = expand2[GET_PLANE(data, 1)];
+ v |= expand2[GET_PLANE(data, 3)] << 2;
+ PUT_PIXEL2(d, 4, palette[v >> 12]);
+ PUT_PIXEL2(d, 5, palette[(v >> 8) & 0xf]);
+ PUT_PIXEL2(d, 6, palette[(v >> 4) & 0xf]);
+ PUT_PIXEL2(d, 7, palette[(v >> 0) & 0xf]);
+ d += 64;
+ s += 4;
+ }
+}
+
+/*
+ * 16 color mode
+ */
+static void vga_draw_line4(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ uint32_t plane_mask, data, v, *palette;
+ int x;
+
+ palette = s1->last_palette;
+ plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ data = ((uint32_t *)s)[0];
+ data &= plane_mask;
+ v = expand4[GET_PLANE(data, 0)];
+ v |= expand4[GET_PLANE(data, 1)] << 1;
+ v |= expand4[GET_PLANE(data, 2)] << 2;
+ v |= expand4[GET_PLANE(data, 3)] << 3;
+ ((uint32_t *)d)[0] = palette[v >> 28];
+ ((uint32_t *)d)[1] = palette[(v >> 24) & 0xf];
+ ((uint32_t *)d)[2] = palette[(v >> 20) & 0xf];
+ ((uint32_t *)d)[3] = palette[(v >> 16) & 0xf];
+ ((uint32_t *)d)[4] = palette[(v >> 12) & 0xf];
+ ((uint32_t *)d)[5] = palette[(v >> 8) & 0xf];
+ ((uint32_t *)d)[6] = palette[(v >> 4) & 0xf];
+ ((uint32_t *)d)[7] = palette[(v >> 0) & 0xf];
+ d += 32;
+ s += 4;
+ }
+}
+
+/*
+ * 16 color mode, dup2 horizontal
+ */
+static void vga_draw_line4d2(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ uint32_t plane_mask, data, v, *palette;
+ int x;
+
+ palette = s1->last_palette;
+ plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ data = ((uint32_t *)s)[0];
+ data &= plane_mask;
+ v = expand4[GET_PLANE(data, 0)];
+ v |= expand4[GET_PLANE(data, 1)] << 1;
+ v |= expand4[GET_PLANE(data, 2)] << 2;
+ v |= expand4[GET_PLANE(data, 3)] << 3;
+ PUT_PIXEL2(d, 0, palette[v >> 28]);
+ PUT_PIXEL2(d, 1, palette[(v >> 24) & 0xf]);
+ PUT_PIXEL2(d, 2, palette[(v >> 20) & 0xf]);
+ PUT_PIXEL2(d, 3, palette[(v >> 16) & 0xf]);
+ PUT_PIXEL2(d, 4, palette[(v >> 12) & 0xf]);
+ PUT_PIXEL2(d, 5, palette[(v >> 8) & 0xf]);
+ PUT_PIXEL2(d, 6, palette[(v >> 4) & 0xf]);
+ PUT_PIXEL2(d, 7, palette[(v >> 0) & 0xf]);
+ d += 64;
+ s += 4;
+ }
+}
+
+/*
+ * 256 color mode, double pixels
+ *
+ * XXX: add plane_mask support (never used in standard VGA modes)
+ */
+static void vga_draw_line8d2(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ uint32_t *palette;
+ int x;
+
+ palette = s1->last_palette;
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ PUT_PIXEL2(d, 0, palette[s[0]]);
+ PUT_PIXEL2(d, 1, palette[s[1]]);
+ PUT_PIXEL2(d, 2, palette[s[2]]);
+ PUT_PIXEL2(d, 3, palette[s[3]]);
+ d += 32;
+ s += 4;
+ }
+}
+
+/*
+ * standard 256 color mode
+ *
+ * XXX: add plane_mask support (never used in standard VGA modes)
+ */
+static void vga_draw_line8(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ uint32_t *palette;
+ int x;
+
+ palette = s1->last_palette;
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ ((uint32_t *)d)[0] = palette[s[0]];
+ ((uint32_t *)d)[1] = palette[s[1]];
+ ((uint32_t *)d)[2] = palette[s[2]];
+ ((uint32_t *)d)[3] = palette[s[3]];
+ ((uint32_t *)d)[4] = palette[s[4]];
+ ((uint32_t *)d)[5] = palette[s[5]];
+ ((uint32_t *)d)[6] = palette[s[6]];
+ ((uint32_t *)d)[7] = palette[s[7]];
+ d += 32;
+ s += 8;
+ }
+}
+
+/*
+ * 15 bit color
+ */
+static void vga_draw_line15_le(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int w;
+ uint32_t v, r, g, b;
+
+ w = width;
+ do {
+ v = lduw_le_p((void *)s);
+ r = (v >> 7) & 0xf8;
+ g = (v >> 2) & 0xf8;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ s += 2;
+ d += 4;
+ } while (--w != 0);
+}
+
+static void vga_draw_line15_be(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int w;
+ uint32_t v, r, g, b;
+
+ w = width;
+ do {
+ v = lduw_be_p((void *)s);
+ r = (v >> 7) & 0xf8;
+ g = (v >> 2) & 0xf8;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ s += 2;
+ d += 4;
+ } while (--w != 0);
+}
+
+/*
+ * 16 bit color
+ */
+static void vga_draw_line16_le(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int w;
+ uint32_t v, r, g, b;
+
+ w = width;
+ do {
+ v = lduw_le_p((void *)s);
+ r = (v >> 8) & 0xf8;
+ g = (v >> 3) & 0xfc;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ s += 2;
+ d += 4;
+ } while (--w != 0);
+}
+
+static void vga_draw_line16_be(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int w;
+ uint32_t v, r, g, b;
+
+ w = width;
+ do {
+ v = lduw_be_p((void *)s);
+ r = (v >> 8) & 0xf8;
+ g = (v >> 3) & 0xfc;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ s += 2;
+ d += 4;
+ } while (--w != 0);
+}
+
+/*
+ * 24 bit color
+ */
+static void vga_draw_line24_le(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int w;
+ uint32_t r, g, b;
+
+ w = width;
+ do {
+ b = s[0];
+ g = s[1];
+ r = s[2];
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ s += 3;
+ d += 4;
+ } while (--w != 0);
+}
+
+static void vga_draw_line24_be(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int w;
+ uint32_t r, g, b;
+
+ w = width;
+ do {
+ r = s[0];
+ g = s[1];
+ b = s[2];
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ s += 3;
+ d += 4;
+ } while (--w != 0);
+}
+
+/*
+ * 32 bit color
+ */
+static void vga_draw_line32_le(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+#ifndef HOST_WORDS_BIGENDIAN
+ memcpy(d, s, width * 4);
+#else
+ int w;
+ uint32_t r, g, b;
+
+ w = width;
+ do {
+ b = s[0];
+ g = s[1];
+ r = s[2];
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ s += 4;
+ d += 4;
+ } while (--w != 0);
+#endif
+}
+
+static void vga_draw_line32_be(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+#ifdef HOST_WORDS_BIGENDIAN
+ memcpy(d, s, width * 4);
+#else
+ int w;
+ uint32_t r, g, b;
+
+ w = width;
+ do {
+ r = s[1];
+ g = s[2];
+ b = s[3];
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ s += 4;
+ d += 4;
+ } while (--w != 0);
+#endif
+}
diff --git a/hw/display/vga-isa-mm.c b/hw/display/vga-isa-mm.c
new file mode 100644
index 00000000..4efc2227
--- /dev/null
+++ b/hw/display/vga-isa-mm.c
@@ -0,0 +1,142 @@
+/*
+ * QEMU ISA MM VGA Emulator.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/i386/pc.h"
+#include "vga_int.h"
+#include "ui/pixel_ops.h"
+#include "qemu/timer.h"
+
+#define VGA_RAM_SIZE (8192 * 1024)
+
+typedef struct ISAVGAMMState {
+ VGACommonState vga;
+ int it_shift;
+} ISAVGAMMState;
+
+/* Memory mapped interface */
+static uint32_t vga_mm_readb (void *opaque, hwaddr addr)
+{
+ ISAVGAMMState *s = opaque;
+
+ return vga_ioport_read(&s->vga, addr >> s->it_shift) & 0xff;
+}
+
+static void vga_mm_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ISAVGAMMState *s = opaque;
+
+ vga_ioport_write(&s->vga, addr >> s->it_shift, value & 0xff);
+}
+
+static uint32_t vga_mm_readw (void *opaque, hwaddr addr)
+{
+ ISAVGAMMState *s = opaque;
+
+ return vga_ioport_read(&s->vga, addr >> s->it_shift) & 0xffff;
+}
+
+static void vga_mm_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ISAVGAMMState *s = opaque;
+
+ vga_ioport_write(&s->vga, addr >> s->it_shift, value & 0xffff);
+}
+
+static uint32_t vga_mm_readl (void *opaque, hwaddr addr)
+{
+ ISAVGAMMState *s = opaque;
+
+ return vga_ioport_read(&s->vga, addr >> s->it_shift);
+}
+
+static void vga_mm_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ISAVGAMMState *s = opaque;
+
+ vga_ioport_write(&s->vga, addr >> s->it_shift, value);
+}
+
+static const MemoryRegionOps vga_mm_ctrl_ops = {
+ .old_mmio = {
+ .read = {
+ vga_mm_readb,
+ vga_mm_readw,
+ vga_mm_readl,
+ },
+ .write = {
+ vga_mm_writeb,
+ vga_mm_writew,
+ vga_mm_writel,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void vga_mm_init(ISAVGAMMState *s, hwaddr vram_base,
+ hwaddr ctrl_base, int it_shift,
+ MemoryRegion *address_space)
+{
+ MemoryRegion *s_ioport_ctrl, *vga_io_memory;
+
+ s->it_shift = it_shift;
+ s_ioport_ctrl = g_malloc(sizeof(*s_ioport_ctrl));
+ memory_region_init_io(s_ioport_ctrl, NULL, &vga_mm_ctrl_ops, s,
+ "vga-mm-ctrl", 0x100000);
+ memory_region_set_flush_coalesced(s_ioport_ctrl);
+
+ vga_io_memory = g_malloc(sizeof(*vga_io_memory));
+ /* XXX: endianness? */
+ memory_region_init_io(vga_io_memory, NULL, &vga_mem_ops, &s->vga,
+ "vga-mem", 0x20000);
+
+ vmstate_register(NULL, 0, &vmstate_vga_common, s);
+
+ memory_region_add_subregion(address_space, ctrl_base, s_ioport_ctrl);
+ s->vga.bank_offset = 0;
+ memory_region_add_subregion(address_space,
+ vram_base + 0x000a0000, vga_io_memory);
+ memory_region_set_coalescing(vga_io_memory);
+}
+
+int isa_vga_mm_init(hwaddr vram_base,
+ hwaddr ctrl_base, int it_shift,
+ MemoryRegion *address_space)
+{
+ ISAVGAMMState *s;
+
+ s = g_malloc0(sizeof(*s));
+
+ s->vga.vram_size_mb = VGA_RAM_SIZE >> 20;
+ vga_common_init(&s->vga, NULL, true);
+ vga_mm_init(s, vram_base, ctrl_base, it_shift, address_space);
+
+ s->vga.con = graphic_console_init(NULL, 0, s->vga.hw_ops, s);
+
+ vga_init_vbe(&s->vga, NULL, address_space);
+ return 0;
+}
diff --git a/hw/display/vga-isa.c b/hw/display/vga-isa.c
new file mode 100644
index 00000000..7f3c9894
--- /dev/null
+++ b/hw/display/vga-isa.c
@@ -0,0 +1,105 @@
+/*
+ * QEMU ISA VGA Emulator.
+ *
+ * see docs/specs/standard-vga.txt for virtual hardware specs.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/i386/pc.h"
+#include "vga_int.h"
+#include "ui/pixel_ops.h"
+#include "qemu/timer.h"
+#include "hw/loader.h"
+
+#define TYPE_ISA_VGA "isa-vga"
+#define ISA_VGA(obj) OBJECT_CHECK(ISAVGAState, (obj), TYPE_ISA_VGA)
+
+typedef struct ISAVGAState {
+ ISADevice parent_obj;
+
+ struct VGACommonState state;
+} ISAVGAState;
+
+static void vga_isa_reset(DeviceState *dev)
+{
+ ISAVGAState *d = ISA_VGA(dev);
+ VGACommonState *s = &d->state;
+
+ vga_common_reset(s);
+}
+
+static void vga_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISAVGAState *d = ISA_VGA(dev);
+ VGACommonState *s = &d->state;
+ MemoryRegion *vga_io_memory;
+ const MemoryRegionPortio *vga_ports, *vbe_ports;
+
+ vga_common_init(s, OBJECT(dev), true);
+ s->legacy_address_space = isa_address_space(isadev);
+ vga_io_memory = vga_init_io(s, OBJECT(dev), &vga_ports, &vbe_ports);
+ isa_register_portio_list(isadev, 0x3b0, vga_ports, s, "vga");
+ if (vbe_ports) {
+ isa_register_portio_list(isadev, 0x1ce, vbe_ports, s, "vbe");
+ }
+ memory_region_add_subregion_overlap(isa_address_space(isadev),
+ 0x000a0000,
+ vga_io_memory, 1);
+ memory_region_set_coalescing(vga_io_memory);
+ s->con = graphic_console_init(DEVICE(dev), 0, s->hw_ops, s);
+
+ vga_init_vbe(s, OBJECT(dev), isa_address_space(isadev));
+ /* ROM BIOS */
+ rom_add_vga(VGABIOS_FILENAME);
+}
+
+static Property vga_isa_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", ISAVGAState, state.vram_size_mb, 8),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vga_isa_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = vga_isa_realizefn;
+ dc->reset = vga_isa_reset;
+ dc->vmsd = &vmstate_vga_common;
+ dc->props = vga_isa_properties;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo vga_isa_info = {
+ .name = TYPE_ISA_VGA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISAVGAState),
+ .class_init = vga_isa_class_initfn,
+};
+
+static void vga_isa_register_types(void)
+{
+ type_register_static(&vga_isa_info);
+}
+
+type_init(vga_isa_register_types)
diff --git a/hw/display/vga-pci.c b/hw/display/vga-pci.c
new file mode 100644
index 00000000..1dfa331e
--- /dev/null
+++ b/hw/display/vga-pci.c
@@ -0,0 +1,386 @@
+/*
+ * QEMU PCI VGA Emulator.
+ *
+ * see docs/specs/standard-vga.txt for virtual hardware specs.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/pci/pci.h"
+#include "vga_int.h"
+#include "ui/pixel_ops.h"
+#include "qemu/timer.h"
+#include "hw/loader.h"
+
+#define PCI_VGA_IOPORT_OFFSET 0x400
+#define PCI_VGA_IOPORT_SIZE (0x3e0 - 0x3c0)
+#define PCI_VGA_BOCHS_OFFSET 0x500
+#define PCI_VGA_BOCHS_SIZE (0x0b * 2)
+#define PCI_VGA_QEXT_OFFSET 0x600
+#define PCI_VGA_QEXT_SIZE (2 * 4)
+#define PCI_VGA_MMIO_SIZE 0x1000
+
+#define PCI_VGA_QEXT_REG_SIZE (0 * 4)
+#define PCI_VGA_QEXT_REG_BYTEORDER (1 * 4)
+#define PCI_VGA_QEXT_LITTLE_ENDIAN 0x1e1e1e1e
+#define PCI_VGA_QEXT_BIG_ENDIAN 0xbebebebe
+
+enum vga_pci_flags {
+ PCI_VGA_FLAG_ENABLE_MMIO = 1,
+ PCI_VGA_FLAG_ENABLE_QEXT = 2,
+};
+
+typedef struct PCIVGAState {
+ PCIDevice dev;
+ VGACommonState vga;
+ uint32_t flags;
+ MemoryRegion mmio;
+ MemoryRegion mrs[3];
+} PCIVGAState;
+
+#define TYPE_PCI_VGA "pci-vga"
+#define PCI_VGA(obj) OBJECT_CHECK(PCIVGAState, (obj), TYPE_PCI_VGA)
+
+static const VMStateDescription vmstate_vga_pci = {
+ .name = "vga",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCIVGAState),
+ VMSTATE_STRUCT(vga, PCIVGAState, 0, vmstate_vga_common, VGACommonState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint64_t pci_vga_ioport_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ VGACommonState *s = ptr;
+ uint64_t ret = 0;
+
+ switch (size) {
+ case 1:
+ ret = vga_ioport_read(s, addr + 0x3c0);
+ break;
+ case 2:
+ ret = vga_ioport_read(s, addr + 0x3c0);
+ ret |= vga_ioport_read(s, addr + 0x3c1) << 8;
+ break;
+ }
+ return ret;
+}
+
+static void pci_vga_ioport_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VGACommonState *s = ptr;
+
+ switch (size) {
+ case 1:
+ vga_ioport_write(s, addr + 0x3c0, val);
+ break;
+ case 2:
+ /*
+ * Update bytes in little endian order. Allows to update
+ * indexed registers with a single word write because the
+ * index byte is updated first.
+ */
+ vga_ioport_write(s, addr + 0x3c0, val & 0xff);
+ vga_ioport_write(s, addr + 0x3c1, (val >> 8) & 0xff);
+ break;
+ }
+}
+
+static const MemoryRegionOps pci_vga_ioport_ops = {
+ .read = pci_vga_ioport_read,
+ .write = pci_vga_ioport_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t pci_vga_bochs_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ VGACommonState *s = ptr;
+ int index = addr >> 1;
+
+ vbe_ioport_write_index(s, 0, index);
+ return vbe_ioport_read_data(s, 0);
+}
+
+static void pci_vga_bochs_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VGACommonState *s = ptr;
+ int index = addr >> 1;
+
+ vbe_ioport_write_index(s, 0, index);
+ vbe_ioport_write_data(s, 0, val);
+}
+
+static const MemoryRegionOps pci_vga_bochs_ops = {
+ .read = pci_vga_bochs_read,
+ .write = pci_vga_bochs_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 2,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t pci_vga_qext_read(void *ptr, hwaddr addr, unsigned size)
+{
+ VGACommonState *s = ptr;
+
+ switch (addr) {
+ case PCI_VGA_QEXT_REG_SIZE:
+ return PCI_VGA_QEXT_SIZE;
+ case PCI_VGA_QEXT_REG_BYTEORDER:
+ return s->big_endian_fb ?
+ PCI_VGA_QEXT_BIG_ENDIAN : PCI_VGA_QEXT_LITTLE_ENDIAN;
+ default:
+ return 0;
+ }
+}
+
+static void pci_vga_qext_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VGACommonState *s = ptr;
+
+ switch (addr) {
+ case PCI_VGA_QEXT_REG_BYTEORDER:
+ if (val == PCI_VGA_QEXT_BIG_ENDIAN) {
+ s->big_endian_fb = true;
+ }
+ if (val == PCI_VGA_QEXT_LITTLE_ENDIAN) {
+ s->big_endian_fb = false;
+ }
+ break;
+ }
+}
+
+static bool vga_get_big_endian_fb(Object *obj, Error **errp)
+{
+ PCIVGAState *d = PCI_VGA(PCI_DEVICE(obj));
+
+ return d->vga.big_endian_fb;
+}
+
+static void vga_set_big_endian_fb(Object *obj, bool value, Error **errp)
+{
+ PCIVGAState *d = PCI_VGA(PCI_DEVICE(obj));
+
+ d->vga.big_endian_fb = value;
+}
+
+static const MemoryRegionOps pci_vga_qext_ops = {
+ .read = pci_vga_qext_read,
+ .write = pci_vga_qext_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void pci_std_vga_mmio_region_init(VGACommonState *s,
+ MemoryRegion *parent,
+ MemoryRegion *subs,
+ bool qext)
+{
+ memory_region_init_io(&subs[0], NULL, &pci_vga_ioport_ops, s,
+ "vga ioports remapped", PCI_VGA_IOPORT_SIZE);
+ memory_region_add_subregion(parent, PCI_VGA_IOPORT_OFFSET,
+ &subs[0]);
+
+ memory_region_init_io(&subs[1], NULL, &pci_vga_bochs_ops, s,
+ "bochs dispi interface", PCI_VGA_BOCHS_SIZE);
+ memory_region_add_subregion(parent, PCI_VGA_BOCHS_OFFSET,
+ &subs[1]);
+
+ if (qext) {
+ memory_region_init_io(&subs[2], NULL, &pci_vga_qext_ops, s,
+ "qemu extended regs", PCI_VGA_QEXT_SIZE);
+ memory_region_add_subregion(parent, PCI_VGA_QEXT_OFFSET,
+ &subs[2]);
+ }
+}
+
+static void pci_std_vga_realize(PCIDevice *dev, Error **errp)
+{
+ PCIVGAState *d = PCI_VGA(dev);
+ VGACommonState *s = &d->vga;
+ bool qext = false;
+
+ /* vga + console init */
+ vga_common_init(s, OBJECT(dev), true);
+ vga_init(s, OBJECT(dev), pci_address_space(dev), pci_address_space_io(dev),
+ true);
+
+ s->con = graphic_console_init(DEVICE(dev), 0, s->hw_ops, s);
+
+ /* XXX: VGA_RAM_SIZE must be a power of two */
+ pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
+
+ /* mmio bar for vga register access */
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_MMIO)) {
+ memory_region_init(&d->mmio, NULL, "vga.mmio", 4096);
+
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_QEXT)) {
+ qext = true;
+ pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2);
+ }
+ pci_std_vga_mmio_region_init(s, &d->mmio, d->mrs, qext);
+
+ pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+ }
+
+ if (!dev->rom_bar) {
+ /* compatibility with pc-0.13 and older */
+ vga_init_vbe(s, OBJECT(dev), pci_address_space(dev));
+ }
+}
+
+static void pci_std_vga_init(Object *obj)
+{
+ /* Expose framebuffer byteorder via QOM */
+ object_property_add_bool(obj, "big-endian-framebuffer",
+ vga_get_big_endian_fb, vga_set_big_endian_fb, NULL);
+}
+
+static void pci_secondary_vga_realize(PCIDevice *dev, Error **errp)
+{
+ PCIVGAState *d = PCI_VGA(dev);
+ VGACommonState *s = &d->vga;
+ bool qext = false;
+
+ /* vga + console init */
+ vga_common_init(s, OBJECT(dev), false);
+ s->con = graphic_console_init(DEVICE(dev), 0, s->hw_ops, s);
+
+ /* mmio bar */
+ memory_region_init(&d->mmio, OBJECT(dev), "vga.mmio", 4096);
+
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_QEXT)) {
+ qext = true;
+ pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2);
+ }
+ pci_std_vga_mmio_region_init(s, &d->mmio, d->mrs, qext);
+
+ pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
+ pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+}
+
+static void pci_secondary_vga_init(Object *obj)
+{
+ /* Expose framebuffer byteorder via QOM */
+ object_property_add_bool(obj, "big-endian-framebuffer",
+ vga_get_big_endian_fb, vga_set_big_endian_fb, NULL);
+}
+
+static void pci_secondary_vga_reset(DeviceState *dev)
+{
+ PCIVGAState *d = PCI_VGA(PCI_DEVICE(dev));
+ vga_common_reset(&d->vga);
+}
+
+static Property vga_pci_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", PCIVGAState, vga.vram_size_mb, 16),
+ DEFINE_PROP_BIT("mmio", PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_MMIO, true),
+ DEFINE_PROP_BIT("qemu-extended-regs",
+ PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_QEXT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property secondary_pci_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", PCIVGAState, vga.vram_size_mb, 16),
+ DEFINE_PROP_BIT("qemu-extended-regs",
+ PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_QEXT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vga_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_QEMU;
+ k->device_id = PCI_DEVICE_ID_QEMU_VGA;
+ dc->vmsd = &vmstate_vga_pci;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo vga_pci_type_info = {
+ .name = TYPE_PCI_VGA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIVGAState),
+ .abstract = true,
+ .class_init = vga_pci_class_init,
+};
+
+static void vga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_std_vga_realize;
+ k->romfile = "vgabios-stdvga.bin";
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ dc->props = vga_pci_properties;
+ dc->hotpluggable = false;
+}
+
+static void secondary_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_secondary_vga_realize;
+ k->class_id = PCI_CLASS_DISPLAY_OTHER;
+ dc->props = secondary_pci_properties;
+ dc->reset = pci_secondary_vga_reset;
+}
+
+static const TypeInfo vga_info = {
+ .name = "VGA",
+ .parent = TYPE_PCI_VGA,
+ .instance_init = pci_std_vga_init,
+ .class_init = vga_class_init,
+};
+
+static const TypeInfo secondary_info = {
+ .name = "secondary-vga",
+ .parent = TYPE_PCI_VGA,
+ .instance_init = pci_secondary_vga_init,
+ .class_init = secondary_class_init,
+};
+
+static void vga_register_types(void)
+{
+ type_register_static(&vga_pci_type_info);
+ type_register_static(&vga_info);
+ type_register_static(&secondary_info);
+}
+
+type_init(vga_register_types)
diff --git a/hw/display/vga.c b/hw/display/vga.c
new file mode 100644
index 00000000..b35d523e
--- /dev/null
+++ b/hw/display/vga.c
@@ -0,0 +1,2252 @@
+/*
+ * QEMU VGA Emulator.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "vga.h"
+#include "ui/console.h"
+#include "hw/i386/pc.h"
+#include "hw/pci/pci.h"
+#include "vga_int.h"
+#include "ui/pixel_ops.h"
+#include "qemu/timer.h"
+#include "hw/xen/xen.h"
+#include "trace.h"
+
+//#define DEBUG_VGA
+//#define DEBUG_VGA_MEM
+//#define DEBUG_VGA_REG
+
+//#define DEBUG_BOCHS_VBE
+
+/* 16 state changes per vertical frame @60 Hz */
+#define VGA_TEXT_CURSOR_PERIOD_MS (1000 * 2 * 16 / 60)
+
+/*
+ * Video Graphics Array (VGA)
+ *
+ * Chipset docs for original IBM VGA:
+ * http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf
+ *
+ * FreeVGA site:
+ * http://www.osdever.net/FreeVGA/home.htm
+ *
+ * Standard VGA features and Bochs VBE extensions are implemented.
+ */
+
+/* force some bits to zero */
+const uint8_t sr_mask[8] = {
+ 0x03,
+ 0x3d,
+ 0x0f,
+ 0x3f,
+ 0x0e,
+ 0x00,
+ 0x00,
+ 0xff,
+};
+
+const uint8_t gr_mask[16] = {
+ 0x0f, /* 0x00 */
+ 0x0f, /* 0x01 */
+ 0x0f, /* 0x02 */
+ 0x1f, /* 0x03 */
+ 0x03, /* 0x04 */
+ 0x7b, /* 0x05 */
+ 0x0f, /* 0x06 */
+ 0x0f, /* 0x07 */
+ 0xff, /* 0x08 */
+ 0x00, /* 0x09 */
+ 0x00, /* 0x0a */
+ 0x00, /* 0x0b */
+ 0x00, /* 0x0c */
+ 0x00, /* 0x0d */
+ 0x00, /* 0x0e */
+ 0x00, /* 0x0f */
+};
+
+#define cbswap_32(__x) \
+((uint32_t)( \
+ (((uint32_t)(__x) & (uint32_t)0x000000ffUL) << 24) | \
+ (((uint32_t)(__x) & (uint32_t)0x0000ff00UL) << 8) | \
+ (((uint32_t)(__x) & (uint32_t)0x00ff0000UL) >> 8) | \
+ (((uint32_t)(__x) & (uint32_t)0xff000000UL) >> 24) ))
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define PAT(x) cbswap_32(x)
+#else
+#define PAT(x) (x)
+#endif
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define BIG 1
+#else
+#define BIG 0
+#endif
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define GET_PLANE(data, p) (((data) >> (24 - (p) * 8)) & 0xff)
+#else
+#define GET_PLANE(data, p) (((data) >> ((p) * 8)) & 0xff)
+#endif
+
+static const uint32_t mask16[16] = {
+ PAT(0x00000000),
+ PAT(0x000000ff),
+ PAT(0x0000ff00),
+ PAT(0x0000ffff),
+ PAT(0x00ff0000),
+ PAT(0x00ff00ff),
+ PAT(0x00ffff00),
+ PAT(0x00ffffff),
+ PAT(0xff000000),
+ PAT(0xff0000ff),
+ PAT(0xff00ff00),
+ PAT(0xff00ffff),
+ PAT(0xffff0000),
+ PAT(0xffff00ff),
+ PAT(0xffffff00),
+ PAT(0xffffffff),
+};
+
+#undef PAT
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define PAT(x) (x)
+#else
+#define PAT(x) cbswap_32(x)
+#endif
+
+static uint32_t expand4[256];
+static uint16_t expand2[256];
+static uint8_t expand4to8[16];
+
+static void vga_update_memory_access(VGACommonState *s)
+{
+ hwaddr base, offset, size;
+
+ if (s->legacy_address_space == NULL) {
+ return;
+ }
+
+ if (s->has_chain4_alias) {
+ memory_region_del_subregion(s->legacy_address_space, &s->chain4_alias);
+ object_unparent(OBJECT(&s->chain4_alias));
+ s->has_chain4_alias = false;
+ s->plane_updated = 0xf;
+ }
+ if ((s->sr[VGA_SEQ_PLANE_WRITE] & VGA_SR02_ALL_PLANES) ==
+ VGA_SR02_ALL_PLANES && s->sr[VGA_SEQ_MEMORY_MODE] & VGA_SR04_CHN_4M) {
+ offset = 0;
+ switch ((s->gr[VGA_GFX_MISC] >> 2) & 3) {
+ case 0:
+ base = 0xa0000;
+ size = 0x20000;
+ break;
+ case 1:
+ base = 0xa0000;
+ size = 0x10000;
+ offset = s->bank_offset;
+ break;
+ case 2:
+ base = 0xb0000;
+ size = 0x8000;
+ break;
+ case 3:
+ default:
+ base = 0xb8000;
+ size = 0x8000;
+ break;
+ }
+ memory_region_init_alias(&s->chain4_alias, memory_region_owner(&s->vram),
+ "vga.chain4", &s->vram, offset, size);
+ memory_region_add_subregion_overlap(s->legacy_address_space, base,
+ &s->chain4_alias, 2);
+ s->has_chain4_alias = true;
+ }
+}
+
+static void vga_dumb_update_retrace_info(VGACommonState *s)
+{
+ (void) s;
+}
+
+static void vga_precise_update_retrace_info(VGACommonState *s)
+{
+ int htotal_chars;
+ int hretr_start_char;
+ int hretr_skew_chars;
+ int hretr_end_char;
+
+ int vtotal_lines;
+ int vretr_start_line;
+ int vretr_end_line;
+
+ int dots;
+#if 0
+ int div2, sldiv2;
+#endif
+ int clocking_mode;
+ int clock_sel;
+ const int clk_hz[] = {25175000, 28322000, 25175000, 25175000};
+ int64_t chars_per_sec;
+ struct vga_precise_retrace *r = &s->retrace_info.precise;
+
+ htotal_chars = s->cr[VGA_CRTC_H_TOTAL] + 5;
+ hretr_start_char = s->cr[VGA_CRTC_H_SYNC_START];
+ hretr_skew_chars = (s->cr[VGA_CRTC_H_SYNC_END] >> 5) & 3;
+ hretr_end_char = s->cr[VGA_CRTC_H_SYNC_END] & 0x1f;
+
+ vtotal_lines = (s->cr[VGA_CRTC_V_TOTAL] |
+ (((s->cr[VGA_CRTC_OVERFLOW] & 1) |
+ ((s->cr[VGA_CRTC_OVERFLOW] >> 4) & 2)) << 8)) + 2;
+ vretr_start_line = s->cr[VGA_CRTC_V_SYNC_START] |
+ ((((s->cr[VGA_CRTC_OVERFLOW] >> 2) & 1) |
+ ((s->cr[VGA_CRTC_OVERFLOW] >> 6) & 2)) << 8);
+ vretr_end_line = s->cr[VGA_CRTC_V_SYNC_END] & 0xf;
+
+ clocking_mode = (s->sr[VGA_SEQ_CLOCK_MODE] >> 3) & 1;
+ clock_sel = (s->msr >> 2) & 3;
+ dots = (s->msr & 1) ? 8 : 9;
+
+ chars_per_sec = clk_hz[clock_sel] / dots;
+
+ htotal_chars <<= clocking_mode;
+
+ r->total_chars = vtotal_lines * htotal_chars;
+ if (r->freq) {
+ r->ticks_per_char = get_ticks_per_sec() / (r->total_chars * r->freq);
+ } else {
+ r->ticks_per_char = get_ticks_per_sec() / chars_per_sec;
+ }
+
+ r->vstart = vretr_start_line;
+ r->vend = r->vstart + vretr_end_line + 1;
+
+ r->hstart = hretr_start_char + hretr_skew_chars;
+ r->hend = r->hstart + hretr_end_char + 1;
+ r->htotal = htotal_chars;
+
+#if 0
+ div2 = (s->cr[VGA_CRTC_MODE] >> 2) & 1;
+ sldiv2 = (s->cr[VGA_CRTC_MODE] >> 3) & 1;
+ printf (
+ "hz=%f\n"
+ "htotal = %d\n"
+ "hretr_start = %d\n"
+ "hretr_skew = %d\n"
+ "hretr_end = %d\n"
+ "vtotal = %d\n"
+ "vretr_start = %d\n"
+ "vretr_end = %d\n"
+ "div2 = %d sldiv2 = %d\n"
+ "clocking_mode = %d\n"
+ "clock_sel = %d %d\n"
+ "dots = %d\n"
+ "ticks/char = %" PRId64 "\n"
+ "\n",
+ (double) get_ticks_per_sec() / (r->ticks_per_char * r->total_chars),
+ htotal_chars,
+ hretr_start_char,
+ hretr_skew_chars,
+ hretr_end_char,
+ vtotal_lines,
+ vretr_start_line,
+ vretr_end_line,
+ div2, sldiv2,
+ clocking_mode,
+ clock_sel,
+ clk_hz[clock_sel],
+ dots,
+ r->ticks_per_char
+ );
+#endif
+}
+
+static uint8_t vga_precise_retrace(VGACommonState *s)
+{
+ struct vga_precise_retrace *r = &s->retrace_info.precise;
+ uint8_t val = s->st01 & ~(ST01_V_RETRACE | ST01_DISP_ENABLE);
+
+ if (r->total_chars) {
+ int cur_line, cur_line_char, cur_char;
+ int64_t cur_tick;
+
+ cur_tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ cur_char = (cur_tick / r->ticks_per_char) % r->total_chars;
+ cur_line = cur_char / r->htotal;
+
+ if (cur_line >= r->vstart && cur_line <= r->vend) {
+ val |= ST01_V_RETRACE | ST01_DISP_ENABLE;
+ } else {
+ cur_line_char = cur_char % r->htotal;
+ if (cur_line_char >= r->hstart && cur_line_char <= r->hend) {
+ val |= ST01_DISP_ENABLE;
+ }
+ }
+
+ return val;
+ } else {
+ return s->st01 ^ (ST01_V_RETRACE | ST01_DISP_ENABLE);
+ }
+}
+
+static uint8_t vga_dumb_retrace(VGACommonState *s)
+{
+ return s->st01 ^ (ST01_V_RETRACE | ST01_DISP_ENABLE);
+}
+
+int vga_ioport_invalid(VGACommonState *s, uint32_t addr)
+{
+ if (s->msr & VGA_MIS_COLOR) {
+ /* Color */
+ return (addr >= 0x3b0 && addr <= 0x3bf);
+ } else {
+ /* Monochrome */
+ return (addr >= 0x3d0 && addr <= 0x3df);
+ }
+}
+
+uint32_t vga_ioport_read(void *opaque, uint32_t addr)
+{
+ VGACommonState *s = opaque;
+ int val, index;
+
+ if (vga_ioport_invalid(s, addr)) {
+ val = 0xff;
+ } else {
+ switch(addr) {
+ case VGA_ATT_W:
+ if (s->ar_flip_flop == 0) {
+ val = s->ar_index;
+ } else {
+ val = 0;
+ }
+ break;
+ case VGA_ATT_R:
+ index = s->ar_index & 0x1f;
+ if (index < VGA_ATT_C) {
+ val = s->ar[index];
+ } else {
+ val = 0;
+ }
+ break;
+ case VGA_MIS_W:
+ val = s->st00;
+ break;
+ case VGA_SEQ_I:
+ val = s->sr_index;
+ break;
+ case VGA_SEQ_D:
+ val = s->sr[s->sr_index];
+#ifdef DEBUG_VGA_REG
+ printf("vga: read SR%x = 0x%02x\n", s->sr_index, val);
+#endif
+ break;
+ case VGA_PEL_IR:
+ val = s->dac_state;
+ break;
+ case VGA_PEL_IW:
+ val = s->dac_write_index;
+ break;
+ case VGA_PEL_D:
+ val = s->palette[s->dac_read_index * 3 + s->dac_sub_index];
+ if (++s->dac_sub_index == 3) {
+ s->dac_sub_index = 0;
+ s->dac_read_index++;
+ }
+ break;
+ case VGA_FTC_R:
+ val = s->fcr;
+ break;
+ case VGA_MIS_R:
+ val = s->msr;
+ break;
+ case VGA_GFX_I:
+ val = s->gr_index;
+ break;
+ case VGA_GFX_D:
+ val = s->gr[s->gr_index];
+#ifdef DEBUG_VGA_REG
+ printf("vga: read GR%x = 0x%02x\n", s->gr_index, val);
+#endif
+ break;
+ case VGA_CRT_IM:
+ case VGA_CRT_IC:
+ val = s->cr_index;
+ break;
+ case VGA_CRT_DM:
+ case VGA_CRT_DC:
+ val = s->cr[s->cr_index];
+#ifdef DEBUG_VGA_REG
+ printf("vga: read CR%x = 0x%02x\n", s->cr_index, val);
+#endif
+ break;
+ case VGA_IS1_RM:
+ case VGA_IS1_RC:
+ /* just toggle to fool polling */
+ val = s->st01 = s->retrace(s);
+ s->ar_flip_flop = 0;
+ break;
+ default:
+ val = 0x00;
+ break;
+ }
+ }
+#if defined(DEBUG_VGA)
+ printf("VGA: read addr=0x%04x data=0x%02x\n", addr, val);
+#endif
+ return val;
+}
+
+void vga_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ VGACommonState *s = opaque;
+ int index;
+
+ /* check port range access depending on color/monochrome mode */
+ if (vga_ioport_invalid(s, addr)) {
+ return;
+ }
+#ifdef DEBUG_VGA
+ printf("VGA: write addr=0x%04x data=0x%02x\n", addr, val);
+#endif
+
+ switch(addr) {
+ case VGA_ATT_W:
+ if (s->ar_flip_flop == 0) {
+ val &= 0x3f;
+ s->ar_index = val;
+ } else {
+ index = s->ar_index & 0x1f;
+ switch(index) {
+ case VGA_ATC_PALETTE0 ... VGA_ATC_PALETTEF:
+ s->ar[index] = val & 0x3f;
+ break;
+ case VGA_ATC_MODE:
+ s->ar[index] = val & ~0x10;
+ break;
+ case VGA_ATC_OVERSCAN:
+ s->ar[index] = val;
+ break;
+ case VGA_ATC_PLANE_ENABLE:
+ s->ar[index] = val & ~0xc0;
+ break;
+ case VGA_ATC_PEL:
+ s->ar[index] = val & ~0xf0;
+ break;
+ case VGA_ATC_COLOR_PAGE:
+ s->ar[index] = val & ~0xf0;
+ break;
+ default:
+ break;
+ }
+ }
+ s->ar_flip_flop ^= 1;
+ break;
+ case VGA_MIS_W:
+ s->msr = val & ~0x10;
+ s->update_retrace_info(s);
+ break;
+ case VGA_SEQ_I:
+ s->sr_index = val & 7;
+ break;
+ case VGA_SEQ_D:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write SR%x = 0x%02x\n", s->sr_index, val);
+#endif
+ s->sr[s->sr_index] = val & sr_mask[s->sr_index];
+ if (s->sr_index == VGA_SEQ_CLOCK_MODE) {
+ s->update_retrace_info(s);
+ }
+ vga_update_memory_access(s);
+ break;
+ case VGA_PEL_IR:
+ s->dac_read_index = val;
+ s->dac_sub_index = 0;
+ s->dac_state = 3;
+ break;
+ case VGA_PEL_IW:
+ s->dac_write_index = val;
+ s->dac_sub_index = 0;
+ s->dac_state = 0;
+ break;
+ case VGA_PEL_D:
+ s->dac_cache[s->dac_sub_index] = val;
+ if (++s->dac_sub_index == 3) {
+ memcpy(&s->palette[s->dac_write_index * 3], s->dac_cache, 3);
+ s->dac_sub_index = 0;
+ s->dac_write_index++;
+ }
+ break;
+ case VGA_GFX_I:
+ s->gr_index = val & 0x0f;
+ break;
+ case VGA_GFX_D:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write GR%x = 0x%02x\n", s->gr_index, val);
+#endif
+ s->gr[s->gr_index] = val & gr_mask[s->gr_index];
+ vga_update_memory_access(s);
+ break;
+ case VGA_CRT_IM:
+ case VGA_CRT_IC:
+ s->cr_index = val;
+ break;
+ case VGA_CRT_DM:
+ case VGA_CRT_DC:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write CR%x = 0x%02x\n", s->cr_index, val);
+#endif
+ /* handle CR0-7 protection */
+ if ((s->cr[VGA_CRTC_V_SYNC_END] & VGA_CR11_LOCK_CR0_CR7) &&
+ s->cr_index <= VGA_CRTC_OVERFLOW) {
+ /* can always write bit 4 of CR7 */
+ if (s->cr_index == VGA_CRTC_OVERFLOW) {
+ s->cr[VGA_CRTC_OVERFLOW] = (s->cr[VGA_CRTC_OVERFLOW] & ~0x10) |
+ (val & 0x10);
+ }
+ return;
+ }
+ s->cr[s->cr_index] = val;
+
+ switch(s->cr_index) {
+ case VGA_CRTC_H_TOTAL:
+ case VGA_CRTC_H_SYNC_START:
+ case VGA_CRTC_H_SYNC_END:
+ case VGA_CRTC_V_TOTAL:
+ case VGA_CRTC_OVERFLOW:
+ case VGA_CRTC_V_SYNC_END:
+ case VGA_CRTC_MODE:
+ s->update_retrace_info(s);
+ break;
+ }
+ break;
+ case VGA_IS1_RM:
+ case VGA_IS1_RC:
+ s->fcr = val & 0x10;
+ break;
+ }
+}
+
+/*
+ * Sanity check vbe register writes.
+ *
+ * As we don't have a way to signal errors to the guest in the bochs
+ * dispi interface we'll go adjust the registers to the closest valid
+ * value.
+ */
+static void vbe_fixup_regs(VGACommonState *s)
+{
+ uint16_t *r = s->vbe_regs;
+ uint32_t bits, linelength, maxy, offset;
+
+ if (!(r[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) {
+ /* vbe is turned off -- nothing to do */
+ return;
+ }
+
+ /* check depth */
+ switch (r[VBE_DISPI_INDEX_BPP]) {
+ case 4:
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ bits = r[VBE_DISPI_INDEX_BPP];
+ break;
+ case 15:
+ bits = 16;
+ break;
+ default:
+ bits = r[VBE_DISPI_INDEX_BPP] = 8;
+ break;
+ }
+
+ /* check width */
+ r[VBE_DISPI_INDEX_XRES] &= ~7u;
+ if (r[VBE_DISPI_INDEX_XRES] == 0) {
+ r[VBE_DISPI_INDEX_XRES] = 8;
+ }
+ if (r[VBE_DISPI_INDEX_XRES] > VBE_DISPI_MAX_XRES) {
+ r[VBE_DISPI_INDEX_XRES] = VBE_DISPI_MAX_XRES;
+ }
+ r[VBE_DISPI_INDEX_VIRT_WIDTH] &= ~7u;
+ if (r[VBE_DISPI_INDEX_VIRT_WIDTH] > VBE_DISPI_MAX_XRES) {
+ r[VBE_DISPI_INDEX_VIRT_WIDTH] = VBE_DISPI_MAX_XRES;
+ }
+ if (r[VBE_DISPI_INDEX_VIRT_WIDTH] < r[VBE_DISPI_INDEX_XRES]) {
+ r[VBE_DISPI_INDEX_VIRT_WIDTH] = r[VBE_DISPI_INDEX_XRES];
+ }
+
+ /* check height */
+ linelength = r[VBE_DISPI_INDEX_VIRT_WIDTH] * bits / 8;
+ maxy = s->vbe_size / linelength;
+ if (r[VBE_DISPI_INDEX_YRES] == 0) {
+ r[VBE_DISPI_INDEX_YRES] = 1;
+ }
+ if (r[VBE_DISPI_INDEX_YRES] > VBE_DISPI_MAX_YRES) {
+ r[VBE_DISPI_INDEX_YRES] = VBE_DISPI_MAX_YRES;
+ }
+ if (r[VBE_DISPI_INDEX_YRES] > maxy) {
+ r[VBE_DISPI_INDEX_YRES] = maxy;
+ }
+
+ /* check offset */
+ if (r[VBE_DISPI_INDEX_X_OFFSET] > VBE_DISPI_MAX_XRES) {
+ r[VBE_DISPI_INDEX_X_OFFSET] = VBE_DISPI_MAX_XRES;
+ }
+ if (r[VBE_DISPI_INDEX_Y_OFFSET] > VBE_DISPI_MAX_YRES) {
+ r[VBE_DISPI_INDEX_Y_OFFSET] = VBE_DISPI_MAX_YRES;
+ }
+ offset = r[VBE_DISPI_INDEX_X_OFFSET] * bits / 8;
+ offset += r[VBE_DISPI_INDEX_Y_OFFSET] * linelength;
+ if (offset + r[VBE_DISPI_INDEX_YRES] * linelength > s->vbe_size) {
+ r[VBE_DISPI_INDEX_Y_OFFSET] = 0;
+ offset = r[VBE_DISPI_INDEX_X_OFFSET] * bits / 8;
+ if (offset + r[VBE_DISPI_INDEX_YRES] * linelength > s->vbe_size) {
+ r[VBE_DISPI_INDEX_X_OFFSET] = 0;
+ offset = 0;
+ }
+ }
+
+ /* update vga state */
+ r[VBE_DISPI_INDEX_VIRT_HEIGHT] = maxy;
+ s->vbe_line_offset = linelength;
+ s->vbe_start_addr = offset / 4;
+}
+
+static uint32_t vbe_ioport_read_index(void *opaque, uint32_t addr)
+{
+ VGACommonState *s = opaque;
+ uint32_t val;
+ val = s->vbe_index;
+ return val;
+}
+
+uint32_t vbe_ioport_read_data(void *opaque, uint32_t addr)
+{
+ VGACommonState *s = opaque;
+ uint32_t val;
+
+ if (s->vbe_index < VBE_DISPI_INDEX_NB) {
+ if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_GETCAPS) {
+ switch(s->vbe_index) {
+ /* XXX: do not hardcode ? */
+ case VBE_DISPI_INDEX_XRES:
+ val = VBE_DISPI_MAX_XRES;
+ break;
+ case VBE_DISPI_INDEX_YRES:
+ val = VBE_DISPI_MAX_YRES;
+ break;
+ case VBE_DISPI_INDEX_BPP:
+ val = VBE_DISPI_MAX_BPP;
+ break;
+ default:
+ val = s->vbe_regs[s->vbe_index];
+ break;
+ }
+ } else {
+ val = s->vbe_regs[s->vbe_index];
+ }
+ } else if (s->vbe_index == VBE_DISPI_INDEX_VIDEO_MEMORY_64K) {
+ val = s->vbe_size / (64 * 1024);
+ } else {
+ val = 0;
+ }
+#ifdef DEBUG_BOCHS_VBE
+ printf("VBE: read index=0x%x val=0x%x\n", s->vbe_index, val);
+#endif
+ return val;
+}
+
+void vbe_ioport_write_index(void *opaque, uint32_t addr, uint32_t val)
+{
+ VGACommonState *s = opaque;
+ s->vbe_index = val;
+}
+
+void vbe_ioport_write_data(void *opaque, uint32_t addr, uint32_t val)
+{
+ VGACommonState *s = opaque;
+
+ if (s->vbe_index <= VBE_DISPI_INDEX_NB) {
+#ifdef DEBUG_BOCHS_VBE
+ printf("VBE: write index=0x%x val=0x%x\n", s->vbe_index, val);
+#endif
+ switch(s->vbe_index) {
+ case VBE_DISPI_INDEX_ID:
+ if (val == VBE_DISPI_ID0 ||
+ val == VBE_DISPI_ID1 ||
+ val == VBE_DISPI_ID2 ||
+ val == VBE_DISPI_ID3 ||
+ val == VBE_DISPI_ID4) {
+ s->vbe_regs[s->vbe_index] = val;
+ }
+ break;
+ case VBE_DISPI_INDEX_XRES:
+ case VBE_DISPI_INDEX_YRES:
+ case VBE_DISPI_INDEX_BPP:
+ case VBE_DISPI_INDEX_VIRT_WIDTH:
+ case VBE_DISPI_INDEX_X_OFFSET:
+ case VBE_DISPI_INDEX_Y_OFFSET:
+ s->vbe_regs[s->vbe_index] = val;
+ vbe_fixup_regs(s);
+ break;
+ case VBE_DISPI_INDEX_BANK:
+ if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) {
+ val &= (s->vbe_bank_mask >> 2);
+ } else {
+ val &= s->vbe_bank_mask;
+ }
+ s->vbe_regs[s->vbe_index] = val;
+ s->bank_offset = (val << 16);
+ vga_update_memory_access(s);
+ break;
+ case VBE_DISPI_INDEX_ENABLE:
+ if ((val & VBE_DISPI_ENABLED) &&
+ !(s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) {
+ int h, shift_control;
+
+ s->vbe_regs[VBE_DISPI_INDEX_VIRT_WIDTH] = 0;
+ s->vbe_regs[VBE_DISPI_INDEX_X_OFFSET] = 0;
+ s->vbe_regs[VBE_DISPI_INDEX_Y_OFFSET] = 0;
+ s->vbe_regs[VBE_DISPI_INDEX_ENABLE] |= VBE_DISPI_ENABLED;
+ vbe_fixup_regs(s);
+
+ /* clear the screen */
+ if (!(val & VBE_DISPI_NOCLEARMEM)) {
+ memset(s->vram_ptr, 0,
+ s->vbe_regs[VBE_DISPI_INDEX_YRES] * s->vbe_line_offset);
+ }
+
+ /* we initialize the VGA graphic mode */
+ /* graphic mode + memory map 1 */
+ s->gr[VGA_GFX_MISC] = (s->gr[VGA_GFX_MISC] & ~0x0c) | 0x04 |
+ VGA_GR06_GRAPHICS_MODE;
+ s->cr[VGA_CRTC_MODE] |= 3; /* no CGA modes */
+ s->cr[VGA_CRTC_OFFSET] = s->vbe_line_offset >> 3;
+ /* width */
+ s->cr[VGA_CRTC_H_DISP] =
+ (s->vbe_regs[VBE_DISPI_INDEX_XRES] >> 3) - 1;
+ /* height (only meaningful if < 1024) */
+ h = s->vbe_regs[VBE_DISPI_INDEX_YRES] - 1;
+ s->cr[VGA_CRTC_V_DISP_END] = h;
+ s->cr[VGA_CRTC_OVERFLOW] = (s->cr[VGA_CRTC_OVERFLOW] & ~0x42) |
+ ((h >> 7) & 0x02) | ((h >> 3) & 0x40);
+ /* line compare to 1023 */
+ s->cr[VGA_CRTC_LINE_COMPARE] = 0xff;
+ s->cr[VGA_CRTC_OVERFLOW] |= 0x10;
+ s->cr[VGA_CRTC_MAX_SCAN] |= 0x40;
+
+ if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) {
+ shift_control = 0;
+ s->sr[VGA_SEQ_CLOCK_MODE] &= ~8; /* no double line */
+ } else {
+ shift_control = 2;
+ /* set chain 4 mode */
+ s->sr[VGA_SEQ_MEMORY_MODE] |= VGA_SR04_CHN_4M;
+ /* activate all planes */
+ s->sr[VGA_SEQ_PLANE_WRITE] |= VGA_SR02_ALL_PLANES;
+ }
+ s->gr[VGA_GFX_MODE] = (s->gr[VGA_GFX_MODE] & ~0x60) |
+ (shift_control << 5);
+ s->cr[VGA_CRTC_MAX_SCAN] &= ~0x9f; /* no double scan */
+ } else {
+ s->bank_offset = 0;
+ }
+ s->dac_8bit = (val & VBE_DISPI_8BIT_DAC) > 0;
+ s->vbe_regs[s->vbe_index] = val;
+ vga_update_memory_access(s);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/* called for accesses between 0xa0000 and 0xc0000 */
+uint32_t vga_mem_readb(VGACommonState *s, hwaddr addr)
+{
+ int memory_map_mode, plane;
+ uint32_t ret;
+
+ /* convert to VGA memory offset */
+ memory_map_mode = (s->gr[VGA_GFX_MISC] >> 2) & 3;
+ addr &= 0x1ffff;
+ switch(memory_map_mode) {
+ case 0:
+ break;
+ case 1:
+ if (addr >= 0x10000)
+ return 0xff;
+ addr += s->bank_offset;
+ break;
+ case 2:
+ addr -= 0x10000;
+ if (addr >= 0x8000)
+ return 0xff;
+ break;
+ default:
+ case 3:
+ addr -= 0x18000;
+ if (addr >= 0x8000)
+ return 0xff;
+ break;
+ }
+
+ if (s->sr[VGA_SEQ_MEMORY_MODE] & VGA_SR04_CHN_4M) {
+ /* chain 4 mode : simplest access */
+ ret = s->vram_ptr[addr];
+ } else if (s->gr[VGA_GFX_MODE] & 0x10) {
+ /* odd/even mode (aka text mode mapping) */
+ plane = (s->gr[VGA_GFX_PLANE_READ] & 2) | (addr & 1);
+ ret = s->vram_ptr[((addr & ~1) << 1) | plane];
+ } else {
+ /* standard VGA latched access */
+ s->latch = ((uint32_t *)s->vram_ptr)[addr];
+
+ if (!(s->gr[VGA_GFX_MODE] & 0x08)) {
+ /* read mode 0 */
+ plane = s->gr[VGA_GFX_PLANE_READ];
+ ret = GET_PLANE(s->latch, plane);
+ } else {
+ /* read mode 1 */
+ ret = (s->latch ^ mask16[s->gr[VGA_GFX_COMPARE_VALUE]]) &
+ mask16[s->gr[VGA_GFX_COMPARE_MASK]];
+ ret |= ret >> 16;
+ ret |= ret >> 8;
+ ret = (~ret) & 0xff;
+ }
+ }
+ return ret;
+}
+
+/* called for accesses between 0xa0000 and 0xc0000 */
+void vga_mem_writeb(VGACommonState *s, hwaddr addr, uint32_t val)
+{
+ int memory_map_mode, plane, write_mode, b, func_select, mask;
+ uint32_t write_mask, bit_mask, set_mask;
+
+#ifdef DEBUG_VGA_MEM
+ printf("vga: [0x" TARGET_FMT_plx "] = 0x%02x\n", addr, val);
+#endif
+ /* convert to VGA memory offset */
+ memory_map_mode = (s->gr[VGA_GFX_MISC] >> 2) & 3;
+ addr &= 0x1ffff;
+ switch(memory_map_mode) {
+ case 0:
+ break;
+ case 1:
+ if (addr >= 0x10000)
+ return;
+ addr += s->bank_offset;
+ break;
+ case 2:
+ addr -= 0x10000;
+ if (addr >= 0x8000)
+ return;
+ break;
+ default:
+ case 3:
+ addr -= 0x18000;
+ if (addr >= 0x8000)
+ return;
+ break;
+ }
+
+ if (s->sr[VGA_SEQ_MEMORY_MODE] & VGA_SR04_CHN_4M) {
+ /* chain 4 mode : simplest access */
+ plane = addr & 3;
+ mask = (1 << plane);
+ if (s->sr[VGA_SEQ_PLANE_WRITE] & mask) {
+ s->vram_ptr[addr] = val;
+#ifdef DEBUG_VGA_MEM
+ printf("vga: chain4: [0x" TARGET_FMT_plx "]\n", addr);
+#endif
+ s->plane_updated |= mask; /* only used to detect font change */
+ memory_region_set_dirty(&s->vram, addr, 1);
+ }
+ } else if (s->gr[VGA_GFX_MODE] & 0x10) {
+ /* odd/even mode (aka text mode mapping) */
+ plane = (s->gr[VGA_GFX_PLANE_READ] & 2) | (addr & 1);
+ mask = (1 << plane);
+ if (s->sr[VGA_SEQ_PLANE_WRITE] & mask) {
+ addr = ((addr & ~1) << 1) | plane;
+ s->vram_ptr[addr] = val;
+#ifdef DEBUG_VGA_MEM
+ printf("vga: odd/even: [0x" TARGET_FMT_plx "]\n", addr);
+#endif
+ s->plane_updated |= mask; /* only used to detect font change */
+ memory_region_set_dirty(&s->vram, addr, 1);
+ }
+ } else {
+ /* standard VGA latched access */
+ write_mode = s->gr[VGA_GFX_MODE] & 3;
+ switch(write_mode) {
+ default:
+ case 0:
+ /* rotate */
+ b = s->gr[VGA_GFX_DATA_ROTATE] & 7;
+ val = ((val >> b) | (val << (8 - b))) & 0xff;
+ val |= val << 8;
+ val |= val << 16;
+
+ /* apply set/reset mask */
+ set_mask = mask16[s->gr[VGA_GFX_SR_ENABLE]];
+ val = (val & ~set_mask) |
+ (mask16[s->gr[VGA_GFX_SR_VALUE]] & set_mask);
+ bit_mask = s->gr[VGA_GFX_BIT_MASK];
+ break;
+ case 1:
+ val = s->latch;
+ goto do_write;
+ case 2:
+ val = mask16[val & 0x0f];
+ bit_mask = s->gr[VGA_GFX_BIT_MASK];
+ break;
+ case 3:
+ /* rotate */
+ b = s->gr[VGA_GFX_DATA_ROTATE] & 7;
+ val = (val >> b) | (val << (8 - b));
+
+ bit_mask = s->gr[VGA_GFX_BIT_MASK] & val;
+ val = mask16[s->gr[VGA_GFX_SR_VALUE]];
+ break;
+ }
+
+ /* apply logical operation */
+ func_select = s->gr[VGA_GFX_DATA_ROTATE] >> 3;
+ switch(func_select) {
+ case 0:
+ default:
+ /* nothing to do */
+ break;
+ case 1:
+ /* and */
+ val &= s->latch;
+ break;
+ case 2:
+ /* or */
+ val |= s->latch;
+ break;
+ case 3:
+ /* xor */
+ val ^= s->latch;
+ break;
+ }
+
+ /* apply bit mask */
+ bit_mask |= bit_mask << 8;
+ bit_mask |= bit_mask << 16;
+ val = (val & bit_mask) | (s->latch & ~bit_mask);
+
+ do_write:
+ /* mask data according to sr[2] */
+ mask = s->sr[VGA_SEQ_PLANE_WRITE];
+ s->plane_updated |= mask; /* only used to detect font change */
+ write_mask = mask16[mask];
+ ((uint32_t *)s->vram_ptr)[addr] =
+ (((uint32_t *)s->vram_ptr)[addr] & ~write_mask) |
+ (val & write_mask);
+#ifdef DEBUG_VGA_MEM
+ printf("vga: latch: [0x" TARGET_FMT_plx "] mask=0x%08x val=0x%08x\n",
+ addr * 4, write_mask, val);
+#endif
+ memory_region_set_dirty(&s->vram, addr << 2, sizeof(uint32_t));
+ }
+}
+
+typedef void vga_draw_line_func(VGACommonState *s1, uint8_t *d,
+ const uint8_t *s, int width);
+
+#include "vga-helpers.h"
+
+/* return true if the palette was modified */
+static int update_palette16(VGACommonState *s)
+{
+ int full_update, i;
+ uint32_t v, col, *palette;
+
+ full_update = 0;
+ palette = s->last_palette;
+ for(i = 0; i < 16; i++) {
+ v = s->ar[i];
+ if (s->ar[VGA_ATC_MODE] & 0x80) {
+ v = ((s->ar[VGA_ATC_COLOR_PAGE] & 0xf) << 4) | (v & 0xf);
+ } else {
+ v = ((s->ar[VGA_ATC_COLOR_PAGE] & 0xc) << 4) | (v & 0x3f);
+ }
+ v = v * 3;
+ col = rgb_to_pixel32(c6_to_8(s->palette[v]),
+ c6_to_8(s->palette[v + 1]),
+ c6_to_8(s->palette[v + 2]));
+ if (col != palette[i]) {
+ full_update = 1;
+ palette[i] = col;
+ }
+ }
+ return full_update;
+}
+
+/* return true if the palette was modified */
+static int update_palette256(VGACommonState *s)
+{
+ int full_update, i;
+ uint32_t v, col, *palette;
+
+ full_update = 0;
+ palette = s->last_palette;
+ v = 0;
+ for(i = 0; i < 256; i++) {
+ if (s->dac_8bit) {
+ col = rgb_to_pixel32(s->palette[v],
+ s->palette[v + 1],
+ s->palette[v + 2]);
+ } else {
+ col = rgb_to_pixel32(c6_to_8(s->palette[v]),
+ c6_to_8(s->palette[v + 1]),
+ c6_to_8(s->palette[v + 2]));
+ }
+ if (col != palette[i]) {
+ full_update = 1;
+ palette[i] = col;
+ }
+ v += 3;
+ }
+ return full_update;
+}
+
+static void vga_get_offsets(VGACommonState *s,
+ uint32_t *pline_offset,
+ uint32_t *pstart_addr,
+ uint32_t *pline_compare)
+{
+ uint32_t start_addr, line_offset, line_compare;
+
+ if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED) {
+ line_offset = s->vbe_line_offset;
+ start_addr = s->vbe_start_addr;
+ line_compare = 65535;
+ } else {
+ /* compute line_offset in bytes */
+ line_offset = s->cr[VGA_CRTC_OFFSET];
+ line_offset <<= 3;
+
+ /* starting address */
+ start_addr = s->cr[VGA_CRTC_START_LO] |
+ (s->cr[VGA_CRTC_START_HI] << 8);
+
+ /* line compare */
+ line_compare = s->cr[VGA_CRTC_LINE_COMPARE] |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x10) << 4) |
+ ((s->cr[VGA_CRTC_MAX_SCAN] & 0x40) << 3);
+ }
+ *pline_offset = line_offset;
+ *pstart_addr = start_addr;
+ *pline_compare = line_compare;
+}
+
+/* update start_addr and line_offset. Return TRUE if modified */
+static int update_basic_params(VGACommonState *s)
+{
+ int full_update;
+ uint32_t start_addr, line_offset, line_compare;
+
+ full_update = 0;
+
+ s->get_offsets(s, &line_offset, &start_addr, &line_compare);
+
+ if (line_offset != s->line_offset ||
+ start_addr != s->start_addr ||
+ line_compare != s->line_compare) {
+ s->line_offset = line_offset;
+ s->start_addr = start_addr;
+ s->line_compare = line_compare;
+ full_update = 1;
+ }
+ return full_update;
+}
+
+
+static const uint8_t cursor_glyph[32 * 4] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+};
+
+static void vga_get_text_resolution(VGACommonState *s, int *pwidth, int *pheight,
+ int *pcwidth, int *pcheight)
+{
+ int width, cwidth, height, cheight;
+
+ /* total width & height */
+ cheight = (s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1;
+ cwidth = 8;
+ if (!(s->sr[VGA_SEQ_CLOCK_MODE] & VGA_SR01_CHAR_CLK_8DOTS)) {
+ cwidth = 9;
+ }
+ if (s->sr[VGA_SEQ_CLOCK_MODE] & 0x08) {
+ cwidth = 16; /* NOTE: no 18 pixel wide */
+ }
+ width = (s->cr[VGA_CRTC_H_DISP] + 1);
+ if (s->cr[VGA_CRTC_V_TOTAL] == 100) {
+ /* ugly hack for CGA 160x100x16 - explain me the logic */
+ height = 100;
+ } else {
+ height = s->cr[VGA_CRTC_V_DISP_END] |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3);
+ height = (height + 1) / cheight;
+ }
+
+ *pwidth = width;
+ *pheight = height;
+ *pcwidth = cwidth;
+ *pcheight = cheight;
+}
+
+/*
+ * Text mode update
+ * Missing:
+ * - double scan
+ * - double width
+ * - underline
+ * - flashing
+ */
+static void vga_draw_text(VGACommonState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int cx, cy, cheight, cw, ch, cattr, height, width, ch_attr;
+ int cx_min, cx_max, linesize, x_incr, line, line1;
+ uint32_t offset, fgcol, bgcol, v, cursor_offset;
+ uint8_t *d1, *d, *src, *dest, *cursor_ptr;
+ const uint8_t *font_ptr, *font_base[2];
+ int dup9, line_offset;
+ uint32_t *palette;
+ uint32_t *ch_attr_ptr;
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+
+ /* compute font data address (in plane 2) */
+ v = s->sr[VGA_SEQ_CHARACTER_MAP];
+ offset = (((v >> 4) & 1) | ((v << 1) & 6)) * 8192 * 4 + 2;
+ if (offset != s->font_offsets[0]) {
+ s->font_offsets[0] = offset;
+ full_update = 1;
+ }
+ font_base[0] = s->vram_ptr + offset;
+
+ offset = (((v >> 5) & 1) | ((v >> 1) & 6)) * 8192 * 4 + 2;
+ font_base[1] = s->vram_ptr + offset;
+ if (offset != s->font_offsets[1]) {
+ s->font_offsets[1] = offset;
+ full_update = 1;
+ }
+ if (s->plane_updated & (1 << 2) || s->has_chain4_alias) {
+ /* if the plane 2 was modified since the last display, it
+ indicates the font may have been modified */
+ s->plane_updated = 0;
+ full_update = 1;
+ }
+ full_update |= update_basic_params(s);
+
+ line_offset = s->line_offset;
+
+ vga_get_text_resolution(s, &width, &height, &cw, &cheight);
+ if ((height * width) <= 1) {
+ /* better than nothing: exit if transient size is too small */
+ return;
+ }
+ if ((height * width) > CH_ATTR_SIZE) {
+ /* better than nothing: exit if transient size is too big */
+ return;
+ }
+
+ if (width != s->last_width || height != s->last_height ||
+ cw != s->last_cw || cheight != s->last_ch || s->last_depth) {
+ s->last_scr_width = width * cw;
+ s->last_scr_height = height * cheight;
+ qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height);
+ surface = qemu_console_surface(s->con);
+ dpy_text_resize(s->con, width, height);
+ s->last_depth = 0;
+ s->last_width = width;
+ s->last_height = height;
+ s->last_ch = cheight;
+ s->last_cw = cw;
+ full_update = 1;
+ }
+ full_update |= update_palette16(s);
+ palette = s->last_palette;
+ x_incr = cw * surface_bytes_per_pixel(surface);
+
+ if (full_update) {
+ s->full_update_text = 1;
+ }
+ if (s->full_update_gfx) {
+ s->full_update_gfx = 0;
+ full_update |= 1;
+ }
+
+ cursor_offset = ((s->cr[VGA_CRTC_CURSOR_HI] << 8) |
+ s->cr[VGA_CRTC_CURSOR_LO]) - s->start_addr;
+ if (cursor_offset != s->cursor_offset ||
+ s->cr[VGA_CRTC_CURSOR_START] != s->cursor_start ||
+ s->cr[VGA_CRTC_CURSOR_END] != s->cursor_end) {
+ /* if the cursor position changed, we update the old and new
+ chars */
+ if (s->cursor_offset < CH_ATTR_SIZE)
+ s->last_ch_attr[s->cursor_offset] = -1;
+ if (cursor_offset < CH_ATTR_SIZE)
+ s->last_ch_attr[cursor_offset] = -1;
+ s->cursor_offset = cursor_offset;
+ s->cursor_start = s->cr[VGA_CRTC_CURSOR_START];
+ s->cursor_end = s->cr[VGA_CRTC_CURSOR_END];
+ }
+ cursor_ptr = s->vram_ptr + (s->start_addr + cursor_offset) * 4;
+ if (now >= s->cursor_blink_time) {
+ s->cursor_blink_time = now + VGA_TEXT_CURSOR_PERIOD_MS / 2;
+ s->cursor_visible_phase = !s->cursor_visible_phase;
+ }
+
+ dest = surface_data(surface);
+ linesize = surface_stride(surface);
+ ch_attr_ptr = s->last_ch_attr;
+ line = 0;
+ offset = s->start_addr * 4;
+ for(cy = 0; cy < height; cy++) {
+ d1 = dest;
+ src = s->vram_ptr + offset;
+ cx_min = width;
+ cx_max = -1;
+ for(cx = 0; cx < width; cx++) {
+ ch_attr = *(uint16_t *)src;
+ if (full_update || ch_attr != *ch_attr_ptr || src == cursor_ptr) {
+ if (cx < cx_min)
+ cx_min = cx;
+ if (cx > cx_max)
+ cx_max = cx;
+ *ch_attr_ptr = ch_attr;
+#ifdef HOST_WORDS_BIGENDIAN
+ ch = ch_attr >> 8;
+ cattr = ch_attr & 0xff;
+#else
+ ch = ch_attr & 0xff;
+ cattr = ch_attr >> 8;
+#endif
+ font_ptr = font_base[(cattr >> 3) & 1];
+ font_ptr += 32 * 4 * ch;
+ bgcol = palette[cattr >> 4];
+ fgcol = palette[cattr & 0x0f];
+ if (cw == 16) {
+ vga_draw_glyph16(d1, linesize,
+ font_ptr, cheight, fgcol, bgcol);
+ } else if (cw != 9) {
+ vga_draw_glyph8(d1, linesize,
+ font_ptr, cheight, fgcol, bgcol);
+ } else {
+ dup9 = 0;
+ if (ch >= 0xb0 && ch <= 0xdf &&
+ (s->ar[VGA_ATC_MODE] & 0x04)) {
+ dup9 = 1;
+ }
+ vga_draw_glyph9(d1, linesize,
+ font_ptr, cheight, fgcol, bgcol, dup9);
+ }
+ if (src == cursor_ptr &&
+ !(s->cr[VGA_CRTC_CURSOR_START] & 0x20) &&
+ s->cursor_visible_phase) {
+ int line_start, line_last, h;
+ /* draw the cursor */
+ line_start = s->cr[VGA_CRTC_CURSOR_START] & 0x1f;
+ line_last = s->cr[VGA_CRTC_CURSOR_END] & 0x1f;
+ /* XXX: check that */
+ if (line_last > cheight - 1)
+ line_last = cheight - 1;
+ if (line_last >= line_start && line_start < cheight) {
+ h = line_last - line_start + 1;
+ d = d1 + linesize * line_start;
+ if (cw == 16) {
+ vga_draw_glyph16(d, linesize,
+ cursor_glyph, h, fgcol, bgcol);
+ } else if (cw != 9) {
+ vga_draw_glyph8(d, linesize,
+ cursor_glyph, h, fgcol, bgcol);
+ } else {
+ vga_draw_glyph9(d, linesize,
+ cursor_glyph, h, fgcol, bgcol, 1);
+ }
+ }
+ }
+ }
+ d1 += x_incr;
+ src += 4;
+ ch_attr_ptr++;
+ }
+ if (cx_max != -1) {
+ dpy_gfx_update(s->con, cx_min * cw, cy * cheight,
+ (cx_max - cx_min + 1) * cw, cheight);
+ }
+ dest += linesize * cheight;
+ line1 = line + cheight;
+ offset += line_offset;
+ if (line < s->line_compare && line1 >= s->line_compare) {
+ offset = 0;
+ }
+ line = line1;
+ }
+}
+
+enum {
+ VGA_DRAW_LINE2,
+ VGA_DRAW_LINE2D2,
+ VGA_DRAW_LINE4,
+ VGA_DRAW_LINE4D2,
+ VGA_DRAW_LINE8D2,
+ VGA_DRAW_LINE8,
+ VGA_DRAW_LINE15_LE,
+ VGA_DRAW_LINE16_LE,
+ VGA_DRAW_LINE24_LE,
+ VGA_DRAW_LINE32_LE,
+ VGA_DRAW_LINE15_BE,
+ VGA_DRAW_LINE16_BE,
+ VGA_DRAW_LINE24_BE,
+ VGA_DRAW_LINE32_BE,
+ VGA_DRAW_LINE_NB,
+};
+
+static vga_draw_line_func * const vga_draw_line_table[VGA_DRAW_LINE_NB] = {
+ vga_draw_line2,
+ vga_draw_line2d2,
+ vga_draw_line4,
+ vga_draw_line4d2,
+ vga_draw_line8d2,
+ vga_draw_line8,
+ vga_draw_line15_le,
+ vga_draw_line16_le,
+ vga_draw_line24_le,
+ vga_draw_line32_le,
+ vga_draw_line15_be,
+ vga_draw_line16_be,
+ vga_draw_line24_be,
+ vga_draw_line32_be,
+};
+
+static int vga_get_bpp(VGACommonState *s)
+{
+ int ret;
+
+ if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED) {
+ ret = s->vbe_regs[VBE_DISPI_INDEX_BPP];
+ } else {
+ ret = 0;
+ }
+ return ret;
+}
+
+static void vga_get_resolution(VGACommonState *s, int *pwidth, int *pheight)
+{
+ int width, height;
+
+ if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED) {
+ width = s->vbe_regs[VBE_DISPI_INDEX_XRES];
+ height = s->vbe_regs[VBE_DISPI_INDEX_YRES];
+ } else {
+ width = (s->cr[VGA_CRTC_H_DISP] + 1) * 8;
+ height = s->cr[VGA_CRTC_V_DISP_END] |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3);
+ height = (height + 1);
+ }
+ *pwidth = width;
+ *pheight = height;
+}
+
+void vga_invalidate_scanlines(VGACommonState *s, int y1, int y2)
+{
+ int y;
+ if (y1 >= VGA_MAX_HEIGHT)
+ return;
+ if (y2 >= VGA_MAX_HEIGHT)
+ y2 = VGA_MAX_HEIGHT;
+ for(y = y1; y < y2; y++) {
+ s->invalidated_y_table[y >> 5] |= 1 << (y & 0x1f);
+ }
+}
+
+void vga_sync_dirty_bitmap(VGACommonState *s)
+{
+ memory_region_sync_dirty_bitmap(&s->vram);
+}
+
+void vga_dirty_log_start(VGACommonState *s)
+{
+ memory_region_set_log(&s->vram, true, DIRTY_MEMORY_VGA);
+}
+
+void vga_dirty_log_stop(VGACommonState *s)
+{
+ memory_region_set_log(&s->vram, false, DIRTY_MEMORY_VGA);
+}
+
+/*
+ * graphic modes
+ */
+static void vga_draw_graphic(VGACommonState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int y1, y, update, linesize, y_start, double_scan, mask, depth;
+ int width, height, shift_control, line_offset, bwidth, bits;
+ ram_addr_t page0, page1, page_min, page_max;
+ int disp_width, multi_scan, multi_run;
+ uint8_t *d;
+ uint32_t v, addr1, addr;
+ vga_draw_line_func *vga_draw_line = NULL;
+ bool share_surface;
+ pixman_format_code_t format;
+#ifdef HOST_WORDS_BIGENDIAN
+ bool byteswap = !s->big_endian_fb;
+#else
+ bool byteswap = s->big_endian_fb;
+#endif
+
+ full_update |= update_basic_params(s);
+
+ if (!full_update)
+ vga_sync_dirty_bitmap(s);
+
+ s->get_resolution(s, &width, &height);
+ disp_width = width;
+
+ shift_control = (s->gr[VGA_GFX_MODE] >> 5) & 3;
+ double_scan = (s->cr[VGA_CRTC_MAX_SCAN] >> 7);
+ if (shift_control != 1) {
+ multi_scan = (((s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1) << double_scan)
+ - 1;
+ } else {
+ /* in CGA modes, multi_scan is ignored */
+ /* XXX: is it correct ? */
+ multi_scan = double_scan;
+ }
+ multi_run = multi_scan;
+ if (shift_control != s->shift_control ||
+ double_scan != s->double_scan) {
+ full_update = 1;
+ s->shift_control = shift_control;
+ s->double_scan = double_scan;
+ }
+
+ if (shift_control == 0) {
+ if (s->sr[VGA_SEQ_CLOCK_MODE] & 8) {
+ disp_width <<= 1;
+ }
+ } else if (shift_control == 1) {
+ if (s->sr[VGA_SEQ_CLOCK_MODE] & 8) {
+ disp_width <<= 1;
+ }
+ }
+
+ depth = s->get_bpp(s);
+
+ /*
+ * Check whether we can share the surface with the backend
+ * or whether we need a shadow surface. We share native
+ * endian surfaces for 15bpp and above and byteswapped
+ * surfaces for 24bpp and above.
+ */
+ format = qemu_default_pixman_format(depth, !byteswap);
+ if (format) {
+ share_surface = dpy_gfx_check_format(s->con, format)
+ && !s->force_shadow;
+ } else {
+ share_surface = false;
+ }
+ if (s->line_offset != s->last_line_offset ||
+ disp_width != s->last_width ||
+ height != s->last_height ||
+ s->last_depth != depth ||
+ s->last_byteswap != byteswap ||
+ share_surface != is_buffer_shared(surface)) {
+ if (share_surface) {
+ surface = qemu_create_displaysurface_from(disp_width,
+ height, format, s->line_offset,
+ s->vram_ptr + (s->start_addr * 4));
+ dpy_gfx_replace_surface(s->con, surface);
+#ifdef DEBUG_VGA
+ printf("VGA: Using shared surface for depth=%d swap=%d\n",
+ depth, byteswap);
+#endif
+ } else {
+ qemu_console_resize(s->con, disp_width, height);
+ surface = qemu_console_surface(s->con);
+#ifdef DEBUG_VGA
+ printf("VGA: Using shadow surface for depth=%d swap=%d\n",
+ depth, byteswap);
+#endif
+ }
+ s->last_scr_width = disp_width;
+ s->last_scr_height = height;
+ s->last_width = disp_width;
+ s->last_height = height;
+ s->last_line_offset = s->line_offset;
+ s->last_depth = depth;
+ s->last_byteswap = byteswap;
+ full_update = 1;
+ } else if (is_buffer_shared(surface) &&
+ (full_update || surface_data(surface) != s->vram_ptr
+ + (s->start_addr * 4))) {
+ pixman_format_code_t format =
+ qemu_default_pixman_format(depth, !byteswap);
+ surface = qemu_create_displaysurface_from(disp_width,
+ height, format, s->line_offset,
+ s->vram_ptr + (s->start_addr * 4));
+ dpy_gfx_replace_surface(s->con, surface);
+ }
+
+ if (shift_control == 0) {
+ full_update |= update_palette16(s);
+ if (s->sr[VGA_SEQ_CLOCK_MODE] & 8) {
+ v = VGA_DRAW_LINE4D2;
+ } else {
+ v = VGA_DRAW_LINE4;
+ }
+ bits = 4;
+ } else if (shift_control == 1) {
+ full_update |= update_palette16(s);
+ if (s->sr[VGA_SEQ_CLOCK_MODE] & 8) {
+ v = VGA_DRAW_LINE2D2;
+ } else {
+ v = VGA_DRAW_LINE2;
+ }
+ bits = 4;
+ } else {
+ switch(s->get_bpp(s)) {
+ default:
+ case 0:
+ full_update |= update_palette256(s);
+ v = VGA_DRAW_LINE8D2;
+ bits = 4;
+ break;
+ case 8:
+ full_update |= update_palette256(s);
+ v = VGA_DRAW_LINE8;
+ bits = 8;
+ break;
+ case 15:
+ v = s->big_endian_fb ? VGA_DRAW_LINE15_BE : VGA_DRAW_LINE15_LE;
+ bits = 16;
+ break;
+ case 16:
+ v = s->big_endian_fb ? VGA_DRAW_LINE16_BE : VGA_DRAW_LINE16_LE;
+ bits = 16;
+ break;
+ case 24:
+ v = s->big_endian_fb ? VGA_DRAW_LINE24_BE : VGA_DRAW_LINE24_LE;
+ bits = 24;
+ break;
+ case 32:
+ v = s->big_endian_fb ? VGA_DRAW_LINE32_BE : VGA_DRAW_LINE32_LE;
+ bits = 32;
+ break;
+ }
+ }
+ vga_draw_line = vga_draw_line_table[v];
+
+ if (!is_buffer_shared(surface) && s->cursor_invalidate) {
+ s->cursor_invalidate(s);
+ }
+
+ line_offset = s->line_offset;
+#if 0
+ printf("w=%d h=%d v=%d line_offset=%d cr[0x09]=0x%02x cr[0x17]=0x%02x linecmp=%d sr[0x01]=0x%02x\n",
+ width, height, v, line_offset, s->cr[9], s->cr[VGA_CRTC_MODE],
+ s->line_compare, s->sr[VGA_SEQ_CLOCK_MODE]);
+#endif
+ addr1 = (s->start_addr * 4);
+ bwidth = (width * bits + 7) / 8;
+ y_start = -1;
+ page_min = -1;
+ page_max = 0;
+ d = surface_data(surface);
+ linesize = surface_stride(surface);
+ y1 = 0;
+ for(y = 0; y < height; y++) {
+ addr = addr1;
+ if (!(s->cr[VGA_CRTC_MODE] & 1)) {
+ int shift;
+ /* CGA compatibility handling */
+ shift = 14 + ((s->cr[VGA_CRTC_MODE] >> 6) & 1);
+ addr = (addr & ~(1 << shift)) | ((y1 & 1) << shift);
+ }
+ if (!(s->cr[VGA_CRTC_MODE] & 2)) {
+ addr = (addr & ~0x8000) | ((y1 & 2) << 14);
+ }
+ update = full_update;
+ page0 = addr;
+ page1 = addr + bwidth - 1;
+ update |= memory_region_get_dirty(&s->vram, page0, page1 - page0,
+ DIRTY_MEMORY_VGA);
+ /* explicit invalidation for the hardware cursor */
+ update |= (s->invalidated_y_table[y >> 5] >> (y & 0x1f)) & 1;
+ if (update) {
+ if (y_start < 0)
+ y_start = y;
+ if (page0 < page_min)
+ page_min = page0;
+ if (page1 > page_max)
+ page_max = page1;
+ if (!(is_buffer_shared(surface))) {
+ vga_draw_line(s, d, s->vram_ptr + addr, width);
+ if (s->cursor_draw_line)
+ s->cursor_draw_line(s, d, y);
+ }
+ } else {
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(s->con, 0, y_start,
+ disp_width, y - y_start);
+ y_start = -1;
+ }
+ }
+ if (!multi_run) {
+ mask = (s->cr[VGA_CRTC_MODE] & 3) ^ 3;
+ if ((y1 & mask) == mask)
+ addr1 += line_offset;
+ y1++;
+ multi_run = multi_scan;
+ } else {
+ multi_run--;
+ }
+ /* line compare acts on the displayed lines */
+ if (y == s->line_compare)
+ addr1 = 0;
+ d += linesize;
+ }
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(s->con, 0, y_start,
+ disp_width, y - y_start);
+ }
+ /* reset modified pages */
+ if (page_max >= page_min) {
+ memory_region_reset_dirty(&s->vram,
+ page_min,
+ page_max - page_min,
+ DIRTY_MEMORY_VGA);
+ }
+ memset(s->invalidated_y_table, 0, ((height + 31) >> 5) * 4);
+}
+
+static void vga_draw_blank(VGACommonState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, w;
+ uint8_t *d;
+
+ if (!full_update)
+ return;
+ if (s->last_scr_width <= 0 || s->last_scr_height <= 0)
+ return;
+
+ w = s->last_scr_width * surface_bytes_per_pixel(surface);
+ d = surface_data(surface);
+ for(i = 0; i < s->last_scr_height; i++) {
+ memset(d, 0, w);
+ d += surface_stride(surface);
+ }
+ dpy_gfx_update(s->con, 0, 0,
+ s->last_scr_width, s->last_scr_height);
+}
+
+#define GMODE_TEXT 0
+#define GMODE_GRAPH 1
+#define GMODE_BLANK 2
+
+static void vga_update_display(void *opaque)
+{
+ VGACommonState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int full_update, graphic_mode;
+
+ qemu_flush_coalesced_mmio_buffer();
+
+ if (surface_bits_per_pixel(surface) == 0) {
+ /* nothing to do */
+ } else {
+ full_update = 0;
+ if (!(s->ar_index & 0x20)) {
+ graphic_mode = GMODE_BLANK;
+ } else {
+ graphic_mode = s->gr[VGA_GFX_MISC] & VGA_GR06_GRAPHICS_MODE;
+ }
+ if (graphic_mode != s->graphic_mode) {
+ s->graphic_mode = graphic_mode;
+ s->cursor_blink_time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+ full_update = 1;
+ }
+ switch(graphic_mode) {
+ case GMODE_TEXT:
+ vga_draw_text(s, full_update);
+ break;
+ case GMODE_GRAPH:
+ vga_draw_graphic(s, full_update);
+ break;
+ case GMODE_BLANK:
+ default:
+ vga_draw_blank(s, full_update);
+ break;
+ }
+ }
+}
+
+/* force a full display refresh */
+static void vga_invalidate_display(void *opaque)
+{
+ VGACommonState *s = opaque;
+
+ s->last_width = -1;
+ s->last_height = -1;
+}
+
+void vga_common_reset(VGACommonState *s)
+{
+ s->sr_index = 0;
+ memset(s->sr, '\0', sizeof(s->sr));
+ s->gr_index = 0;
+ memset(s->gr, '\0', sizeof(s->gr));
+ s->ar_index = 0;
+ memset(s->ar, '\0', sizeof(s->ar));
+ s->ar_flip_flop = 0;
+ s->cr_index = 0;
+ memset(s->cr, '\0', sizeof(s->cr));
+ s->msr = 0;
+ s->fcr = 0;
+ s->st00 = 0;
+ s->st01 = 0;
+ s->dac_state = 0;
+ s->dac_sub_index = 0;
+ s->dac_read_index = 0;
+ s->dac_write_index = 0;
+ memset(s->dac_cache, '\0', sizeof(s->dac_cache));
+ s->dac_8bit = 0;
+ memset(s->palette, '\0', sizeof(s->palette));
+ s->bank_offset = 0;
+ s->vbe_index = 0;
+ memset(s->vbe_regs, '\0', sizeof(s->vbe_regs));
+ s->vbe_regs[VBE_DISPI_INDEX_ID] = VBE_DISPI_ID5;
+ s->vbe_start_addr = 0;
+ s->vbe_line_offset = 0;
+ s->vbe_bank_mask = (s->vram_size >> 16) - 1;
+ memset(s->font_offsets, '\0', sizeof(s->font_offsets));
+ s->graphic_mode = -1; /* force full update */
+ s->shift_control = 0;
+ s->double_scan = 0;
+ s->line_offset = 0;
+ s->line_compare = 0;
+ s->start_addr = 0;
+ s->plane_updated = 0;
+ s->last_cw = 0;
+ s->last_ch = 0;
+ s->last_width = 0;
+ s->last_height = 0;
+ s->last_scr_width = 0;
+ s->last_scr_height = 0;
+ s->cursor_start = 0;
+ s->cursor_end = 0;
+ s->cursor_offset = 0;
+ s->big_endian_fb = s->default_endian_fb;
+ memset(s->invalidated_y_table, '\0', sizeof(s->invalidated_y_table));
+ memset(s->last_palette, '\0', sizeof(s->last_palette));
+ memset(s->last_ch_attr, '\0', sizeof(s->last_ch_attr));
+ switch (vga_retrace_method) {
+ case VGA_RETRACE_DUMB:
+ break;
+ case VGA_RETRACE_PRECISE:
+ memset(&s->retrace_info, 0, sizeof (s->retrace_info));
+ break;
+ }
+ vga_update_memory_access(s);
+}
+
+static void vga_reset(void *opaque)
+{
+ VGACommonState *s = opaque;
+ vga_common_reset(s);
+}
+
+#define TEXTMODE_X(x) ((x) % width)
+#define TEXTMODE_Y(x) ((x) / width)
+#define VMEM2CHTYPE(v) ((v & 0xff0007ff) | \
+ ((v & 0x00000800) << 10) | ((v & 0x00007000) >> 1))
+/* relay text rendering to the display driver
+ * instead of doing a full vga_update_display() */
+static void vga_update_text(void *opaque, console_ch_t *chardata)
+{
+ VGACommonState *s = opaque;
+ int graphic_mode, i, cursor_offset, cursor_visible;
+ int cw, cheight, width, height, size, c_min, c_max;
+ uint32_t *src;
+ console_ch_t *dst, val;
+ char msg_buffer[80];
+ int full_update = 0;
+
+ qemu_flush_coalesced_mmio_buffer();
+
+ if (!(s->ar_index & 0x20)) {
+ graphic_mode = GMODE_BLANK;
+ } else {
+ graphic_mode = s->gr[VGA_GFX_MISC] & VGA_GR06_GRAPHICS_MODE;
+ }
+ if (graphic_mode != s->graphic_mode) {
+ s->graphic_mode = graphic_mode;
+ full_update = 1;
+ }
+ if (s->last_width == -1) {
+ s->last_width = 0;
+ full_update = 1;
+ }
+
+ switch (graphic_mode) {
+ case GMODE_TEXT:
+ /* TODO: update palette */
+ full_update |= update_basic_params(s);
+
+ /* total width & height */
+ cheight = (s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1;
+ cw = 8;
+ if (!(s->sr[VGA_SEQ_CLOCK_MODE] & VGA_SR01_CHAR_CLK_8DOTS)) {
+ cw = 9;
+ }
+ if (s->sr[VGA_SEQ_CLOCK_MODE] & 0x08) {
+ cw = 16; /* NOTE: no 18 pixel wide */
+ }
+ width = (s->cr[VGA_CRTC_H_DISP] + 1);
+ if (s->cr[VGA_CRTC_V_TOTAL] == 100) {
+ /* ugly hack for CGA 160x100x16 - explain me the logic */
+ height = 100;
+ } else {
+ height = s->cr[VGA_CRTC_V_DISP_END] |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3);
+ height = (height + 1) / cheight;
+ }
+
+ size = (height * width);
+ if (size > CH_ATTR_SIZE) {
+ if (!full_update)
+ return;
+
+ snprintf(msg_buffer, sizeof(msg_buffer), "%i x %i Text mode",
+ width, height);
+ break;
+ }
+
+ if (width != s->last_width || height != s->last_height ||
+ cw != s->last_cw || cheight != s->last_ch) {
+ s->last_scr_width = width * cw;
+ s->last_scr_height = height * cheight;
+ qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height);
+ dpy_text_resize(s->con, width, height);
+ s->last_depth = 0;
+ s->last_width = width;
+ s->last_height = height;
+ s->last_ch = cheight;
+ s->last_cw = cw;
+ full_update = 1;
+ }
+
+ if (full_update) {
+ s->full_update_gfx = 1;
+ }
+ if (s->full_update_text) {
+ s->full_update_text = 0;
+ full_update |= 1;
+ }
+
+ /* Update "hardware" cursor */
+ cursor_offset = ((s->cr[VGA_CRTC_CURSOR_HI] << 8) |
+ s->cr[VGA_CRTC_CURSOR_LO]) - s->start_addr;
+ if (cursor_offset != s->cursor_offset ||
+ s->cr[VGA_CRTC_CURSOR_START] != s->cursor_start ||
+ s->cr[VGA_CRTC_CURSOR_END] != s->cursor_end || full_update) {
+ cursor_visible = !(s->cr[VGA_CRTC_CURSOR_START] & 0x20);
+ if (cursor_visible && cursor_offset < size && cursor_offset >= 0)
+ dpy_text_cursor(s->con,
+ TEXTMODE_X(cursor_offset),
+ TEXTMODE_Y(cursor_offset));
+ else
+ dpy_text_cursor(s->con, -1, -1);
+ s->cursor_offset = cursor_offset;
+ s->cursor_start = s->cr[VGA_CRTC_CURSOR_START];
+ s->cursor_end = s->cr[VGA_CRTC_CURSOR_END];
+ }
+
+ src = (uint32_t *) s->vram_ptr + s->start_addr;
+ dst = chardata;
+
+ if (full_update) {
+ for (i = 0; i < size; src ++, dst ++, i ++)
+ console_write_ch(dst, VMEM2CHTYPE(le32_to_cpu(*src)));
+
+ dpy_text_update(s->con, 0, 0, width, height);
+ } else {
+ c_max = 0;
+
+ for (i = 0; i < size; src ++, dst ++, i ++) {
+ console_write_ch(&val, VMEM2CHTYPE(le32_to_cpu(*src)));
+ if (*dst != val) {
+ *dst = val;
+ c_max = i;
+ break;
+ }
+ }
+ c_min = i;
+ for (; i < size; src ++, dst ++, i ++) {
+ console_write_ch(&val, VMEM2CHTYPE(le32_to_cpu(*src)));
+ if (*dst != val) {
+ *dst = val;
+ c_max = i;
+ }
+ }
+
+ if (c_min <= c_max) {
+ i = TEXTMODE_Y(c_min);
+ dpy_text_update(s->con, 0, i, width, TEXTMODE_Y(c_max) - i + 1);
+ }
+ }
+
+ return;
+ case GMODE_GRAPH:
+ if (!full_update)
+ return;
+
+ s->get_resolution(s, &width, &height);
+ snprintf(msg_buffer, sizeof(msg_buffer), "%i x %i Graphic mode",
+ width, height);
+ break;
+ case GMODE_BLANK:
+ default:
+ if (!full_update)
+ return;
+
+ snprintf(msg_buffer, sizeof(msg_buffer), "VGA Blank mode");
+ break;
+ }
+
+ /* Display a message */
+ s->last_width = 60;
+ s->last_height = height = 3;
+ dpy_text_cursor(s->con, -1, -1);
+ dpy_text_resize(s->con, s->last_width, height);
+
+ for (dst = chardata, i = 0; i < s->last_width * height; i ++)
+ console_write_ch(dst ++, ' ');
+
+ size = strlen(msg_buffer);
+ width = (s->last_width - size) / 2;
+ dst = chardata + s->last_width + width;
+ for (i = 0; i < size; i ++)
+ console_write_ch(dst ++, 0x00200100 | msg_buffer[i]);
+
+ dpy_text_update(s->con, 0, 0, s->last_width, height);
+}
+
+static uint64_t vga_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ VGACommonState *s = opaque;
+
+ return vga_mem_readb(s, addr);
+}
+
+static void vga_mem_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VGACommonState *s = opaque;
+
+ vga_mem_writeb(s, addr, data);
+}
+
+const MemoryRegionOps vga_mem_ops = {
+ .read = vga_mem_read,
+ .write = vga_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static int vga_common_post_load(void *opaque, int version_id)
+{
+ VGACommonState *s = opaque;
+
+ /* force refresh */
+ s->graphic_mode = -1;
+ return 0;
+}
+
+static bool vga_endian_state_needed(void *opaque)
+{
+ VGACommonState *s = opaque;
+
+ /*
+ * Only send the endian state if it's different from the
+ * default one, thus ensuring backward compatibility for
+ * migration of the common case
+ */
+ return s->default_endian_fb != s->big_endian_fb;
+}
+
+static const VMStateDescription vmstate_vga_endian = {
+ .name = "vga.endian",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = vga_endian_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(big_endian_fb, VGACommonState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_vga_common = {
+ .name = "vga",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = vga_common_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(latch, VGACommonState),
+ VMSTATE_UINT8(sr_index, VGACommonState),
+ VMSTATE_PARTIAL_BUFFER(sr, VGACommonState, 8),
+ VMSTATE_UINT8(gr_index, VGACommonState),
+ VMSTATE_PARTIAL_BUFFER(gr, VGACommonState, 16),
+ VMSTATE_UINT8(ar_index, VGACommonState),
+ VMSTATE_BUFFER(ar, VGACommonState),
+ VMSTATE_INT32(ar_flip_flop, VGACommonState),
+ VMSTATE_UINT8(cr_index, VGACommonState),
+ VMSTATE_BUFFER(cr, VGACommonState),
+ VMSTATE_UINT8(msr, VGACommonState),
+ VMSTATE_UINT8(fcr, VGACommonState),
+ VMSTATE_UINT8(st00, VGACommonState),
+ VMSTATE_UINT8(st01, VGACommonState),
+
+ VMSTATE_UINT8(dac_state, VGACommonState),
+ VMSTATE_UINT8(dac_sub_index, VGACommonState),
+ VMSTATE_UINT8(dac_read_index, VGACommonState),
+ VMSTATE_UINT8(dac_write_index, VGACommonState),
+ VMSTATE_BUFFER(dac_cache, VGACommonState),
+ VMSTATE_BUFFER(palette, VGACommonState),
+
+ VMSTATE_INT32(bank_offset, VGACommonState),
+ VMSTATE_UINT8_EQUAL(is_vbe_vmstate, VGACommonState),
+ VMSTATE_UINT16(vbe_index, VGACommonState),
+ VMSTATE_UINT16_ARRAY(vbe_regs, VGACommonState, VBE_DISPI_INDEX_NB),
+ VMSTATE_UINT32(vbe_start_addr, VGACommonState),
+ VMSTATE_UINT32(vbe_line_offset, VGACommonState),
+ VMSTATE_UINT32(vbe_bank_mask, VGACommonState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_vga_endian,
+ NULL
+ }
+};
+
+static const GraphicHwOps vga_ops = {
+ .invalidate = vga_invalidate_display,
+ .gfx_update = vga_update_display,
+ .text_update = vga_update_text,
+};
+
+static inline uint32_t uint_clamp(uint32_t val, uint32_t vmin, uint32_t vmax)
+{
+ if (val < vmin) {
+ return vmin;
+ }
+ if (val > vmax) {
+ return vmax;
+ }
+ return val;
+}
+
+void vga_common_init(VGACommonState *s, Object *obj, bool global_vmstate)
+{
+ int i, j, v, b;
+
+ for(i = 0;i < 256; i++) {
+ v = 0;
+ for(j = 0; j < 8; j++) {
+ v |= ((i >> j) & 1) << (j * 4);
+ }
+ expand4[i] = v;
+
+ v = 0;
+ for(j = 0; j < 4; j++) {
+ v |= ((i >> (2 * j)) & 3) << (j * 4);
+ }
+ expand2[i] = v;
+ }
+ for(i = 0; i < 16; i++) {
+ v = 0;
+ for(j = 0; j < 4; j++) {
+ b = ((i >> j) & 1);
+ v |= b << (2 * j);
+ v |= b << (2 * j + 1);
+ }
+ expand4to8[i] = v;
+ }
+
+ s->vram_size_mb = uint_clamp(s->vram_size_mb, 1, 512);
+ s->vram_size_mb = pow2ceil(s->vram_size_mb);
+ s->vram_size = s->vram_size_mb << 20;
+
+ if (!s->vbe_size) {
+ s->vbe_size = s->vram_size;
+ }
+
+ s->is_vbe_vmstate = 1;
+ memory_region_init_ram(&s->vram, obj, "vga.vram", s->vram_size,
+ &error_abort);
+ vmstate_register_ram(&s->vram, global_vmstate ? NULL : DEVICE(obj));
+ xen_register_framebuffer(&s->vram);
+ s->vram_ptr = memory_region_get_ram_ptr(&s->vram);
+ s->get_bpp = vga_get_bpp;
+ s->get_offsets = vga_get_offsets;
+ s->get_resolution = vga_get_resolution;
+ s->hw_ops = &vga_ops;
+ switch (vga_retrace_method) {
+ case VGA_RETRACE_DUMB:
+ s->retrace = vga_dumb_retrace;
+ s->update_retrace_info = vga_dumb_update_retrace_info;
+ break;
+
+ case VGA_RETRACE_PRECISE:
+ s->retrace = vga_precise_retrace;
+ s->update_retrace_info = vga_precise_update_retrace_info;
+ break;
+ }
+
+ /*
+ * Set default fb endian based on target, could probably be turned
+ * into a device attribute set by the machine/platform to remove
+ * all target endian dependencies from this file.
+ */
+#ifdef TARGET_WORDS_BIGENDIAN
+ s->default_endian_fb = true;
+#else
+ s->default_endian_fb = false;
+#endif
+ vga_dirty_log_start(s);
+}
+
+static const MemoryRegionPortio vga_portio_list[] = {
+ { 0x04, 2, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3b4 */
+ { 0x0a, 1, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3ba */
+ { 0x10, 16, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3c0 */
+ { 0x24, 2, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3d4 */
+ { 0x2a, 1, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3da */
+ PORTIO_END_OF_LIST(),
+};
+
+static const MemoryRegionPortio vbe_portio_list[] = {
+ { 0, 1, 2, .read = vbe_ioport_read_index, .write = vbe_ioport_write_index },
+# ifdef TARGET_I386
+ { 1, 1, 2, .read = vbe_ioport_read_data, .write = vbe_ioport_write_data },
+# endif
+ { 2, 1, 2, .read = vbe_ioport_read_data, .write = vbe_ioport_write_data },
+ PORTIO_END_OF_LIST(),
+};
+
+/* Used by both ISA and PCI */
+MemoryRegion *vga_init_io(VGACommonState *s, Object *obj,
+ const MemoryRegionPortio **vga_ports,
+ const MemoryRegionPortio **vbe_ports)
+{
+ MemoryRegion *vga_mem;
+
+ *vga_ports = vga_portio_list;
+ *vbe_ports = vbe_portio_list;
+
+ vga_mem = g_malloc(sizeof(*vga_mem));
+ memory_region_init_io(vga_mem, obj, &vga_mem_ops, s,
+ "vga-lowmem", 0x20000);
+ memory_region_set_flush_coalesced(vga_mem);
+
+ return vga_mem;
+}
+
+void vga_init(VGACommonState *s, Object *obj, MemoryRegion *address_space,
+ MemoryRegion *address_space_io, bool init_vga_ports)
+{
+ MemoryRegion *vga_io_memory;
+ const MemoryRegionPortio *vga_ports, *vbe_ports;
+
+ qemu_register_reset(vga_reset, s);
+
+ s->bank_offset = 0;
+
+ s->legacy_address_space = address_space;
+
+ vga_io_memory = vga_init_io(s, obj, &vga_ports, &vbe_ports);
+ memory_region_add_subregion_overlap(address_space,
+ 0x000a0000,
+ vga_io_memory,
+ 1);
+ memory_region_set_coalescing(vga_io_memory);
+ if (init_vga_ports) {
+ portio_list_init(&s->vga_port_list, obj, vga_ports, s, "vga");
+ portio_list_set_flush_coalesced(&s->vga_port_list);
+ portio_list_add(&s->vga_port_list, address_space_io, 0x3b0);
+ }
+ if (vbe_ports) {
+ portio_list_init(&s->vbe_port_list, obj, vbe_ports, s, "vbe");
+ portio_list_add(&s->vbe_port_list, address_space_io, 0x1ce);
+ }
+}
+
+void vga_init_vbe(VGACommonState *s, Object *obj, MemoryRegion *system_memory)
+{
+ /* With pc-0.12 and below we map both the PCI BAR and the fixed VBE region,
+ * so use an alias to avoid double-mapping the same region.
+ */
+ memory_region_init_alias(&s->vram_vbe, obj, "vram.vbe",
+ &s->vram, 0, memory_region_size(&s->vram));
+ /* XXX: use optimized standard vga accesses */
+ memory_region_add_subregion(system_memory,
+ VBE_DISPI_LFB_PHYSICAL_ADDRESS,
+ &s->vram_vbe);
+ s->vbe_mapped = 1;
+}
diff --git a/hw/display/vga.h b/hw/display/vga.h
new file mode 100644
index 00000000..d917046d
--- /dev/null
+++ b/hw/display/vga.h
@@ -0,0 +1,159 @@
+/*
+ * linux/include/video/vga.h -- standard VGA chipset interaction
+ *
+ * Copyright 1999 Jeff Garzik <jgarzik@pobox.com>
+ *
+ * Copyright history from vga16fb.c:
+ * Copyright 1999 Ben Pfaff and Petr Vandrovec
+ * Based on VGA info at http://www.osdever.net/FreeVGA/home.htm
+ * Based on VESA framebuffer (c) 1998 Gerd Knorr
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file COPYING in the main directory of this
+ * archive for more details.
+ *
+ */
+
+#ifndef __linux_video_vga_h__
+#define __linux_video_vga_h__
+
+/* Some of the code below is taken from SVGAlib. The original,
+ unmodified copyright notice for that code is below. */
+/* VGAlib version 1.2 - (c) 1993 Tommy Frandsen */
+/* */
+/* This library is free software; you can redistribute it and/or */
+/* modify it without any restrictions. This library is distributed */
+/* in the hope that it will be useful, but without any warranty. */
+
+/* Multi-chipset support Copyright 1993 Harm Hanemaayer */
+/* partially copyrighted (C) 1993 by Hartmut Schirmer */
+
+/* VGA data register ports */
+#define VGA_CRT_DC 0x3D5 /* CRT Controller Data Register - color emulation */
+#define VGA_CRT_DM 0x3B5 /* CRT Controller Data Register - mono emulation */
+#define VGA_ATT_R 0x3C1 /* Attribute Controller Data Read Register */
+#define VGA_ATT_W 0x3C0 /* Attribute Controller Data Write Register */
+#define VGA_GFX_D 0x3CF /* Graphics Controller Data Register */
+#define VGA_SEQ_D 0x3C5 /* Sequencer Data Register */
+#define VGA_MIS_R 0x3CC /* Misc Output Read Register */
+#define VGA_MIS_W 0x3C2 /* Misc Output Write Register */
+#define VGA_FTC_R 0x3CA /* Feature Control Read Register */
+#define VGA_IS1_RC 0x3DA /* Input Status Register 1 - color emulation */
+#define VGA_IS1_RM 0x3BA /* Input Status Register 1 - mono emulation */
+#define VGA_PEL_D 0x3C9 /* PEL Data Register */
+#define VGA_PEL_MSK 0x3C6 /* PEL mask register */
+
+/* EGA-specific registers */
+#define EGA_GFX_E0 0x3CC /* Graphics enable processor 0 */
+#define EGA_GFX_E1 0x3CA /* Graphics enable processor 1 */
+
+/* VGA index register ports */
+#define VGA_CRT_IC 0x3D4 /* CRT Controller Index - color emulation */
+#define VGA_CRT_IM 0x3B4 /* CRT Controller Index - mono emulation */
+#define VGA_ATT_IW 0x3C0 /* Attribute Controller Index & Data Write Register */
+#define VGA_GFX_I 0x3CE /* Graphics Controller Index */
+#define VGA_SEQ_I 0x3C4 /* Sequencer Index */
+#define VGA_PEL_IW 0x3C8 /* PEL Write Index */
+#define VGA_PEL_IR 0x3C7 /* PEL Read Index */
+
+/* standard VGA indexes max counts */
+#define VGA_CRT_C 0x19 /* Number of CRT Controller Registers */
+#define VGA_ATT_C 0x15 /* Number of Attribute Controller Registers */
+#define VGA_GFX_C 0x09 /* Number of Graphics Controller Registers */
+#define VGA_SEQ_C 0x05 /* Number of Sequencer Registers */
+#define VGA_MIS_C 0x01 /* Number of Misc Output Register */
+
+/* VGA misc register bit masks */
+#define VGA_MIS_COLOR 0x01
+#define VGA_MIS_ENB_MEM_ACCESS 0x02
+#define VGA_MIS_DCLK_28322_720 0x04
+#define VGA_MIS_ENB_PLL_LOAD (0x04 | 0x08)
+#define VGA_MIS_SEL_HIGH_PAGE 0x20
+
+/* VGA CRT controller register indices */
+#define VGA_CRTC_H_TOTAL 0
+#define VGA_CRTC_H_DISP 1
+#define VGA_CRTC_H_BLANK_START 2
+#define VGA_CRTC_H_BLANK_END 3
+#define VGA_CRTC_H_SYNC_START 4
+#define VGA_CRTC_H_SYNC_END 5
+#define VGA_CRTC_V_TOTAL 6
+#define VGA_CRTC_OVERFLOW 7
+#define VGA_CRTC_PRESET_ROW 8
+#define VGA_CRTC_MAX_SCAN 9
+#define VGA_CRTC_CURSOR_START 0x0A
+#define VGA_CRTC_CURSOR_END 0x0B
+#define VGA_CRTC_START_HI 0x0C
+#define VGA_CRTC_START_LO 0x0D
+#define VGA_CRTC_CURSOR_HI 0x0E
+#define VGA_CRTC_CURSOR_LO 0x0F
+#define VGA_CRTC_V_SYNC_START 0x10
+#define VGA_CRTC_V_SYNC_END 0x11
+#define VGA_CRTC_V_DISP_END 0x12
+#define VGA_CRTC_OFFSET 0x13
+#define VGA_CRTC_UNDERLINE 0x14
+#define VGA_CRTC_V_BLANK_START 0x15
+#define VGA_CRTC_V_BLANK_END 0x16
+#define VGA_CRTC_MODE 0x17
+#define VGA_CRTC_LINE_COMPARE 0x18
+#define VGA_CRTC_REGS VGA_CRT_C
+
+/* VGA CRT controller bit masks */
+#define VGA_CR11_LOCK_CR0_CR7 0x80 /* lock writes to CR0 - CR7 */
+#define VGA_CR17_H_V_SIGNALS_ENABLED 0x80
+
+/* VGA attribute controller register indices */
+#define VGA_ATC_PALETTE0 0x00
+#define VGA_ATC_PALETTE1 0x01
+#define VGA_ATC_PALETTE2 0x02
+#define VGA_ATC_PALETTE3 0x03
+#define VGA_ATC_PALETTE4 0x04
+#define VGA_ATC_PALETTE5 0x05
+#define VGA_ATC_PALETTE6 0x06
+#define VGA_ATC_PALETTE7 0x07
+#define VGA_ATC_PALETTE8 0x08
+#define VGA_ATC_PALETTE9 0x09
+#define VGA_ATC_PALETTEA 0x0A
+#define VGA_ATC_PALETTEB 0x0B
+#define VGA_ATC_PALETTEC 0x0C
+#define VGA_ATC_PALETTED 0x0D
+#define VGA_ATC_PALETTEE 0x0E
+#define VGA_ATC_PALETTEF 0x0F
+#define VGA_ATC_MODE 0x10
+#define VGA_ATC_OVERSCAN 0x11
+#define VGA_ATC_PLANE_ENABLE 0x12
+#define VGA_ATC_PEL 0x13
+#define VGA_ATC_COLOR_PAGE 0x14
+
+#define VGA_AR_ENABLE_DISPLAY 0x20
+
+/* VGA sequencer register indices */
+#define VGA_SEQ_RESET 0x00
+#define VGA_SEQ_CLOCK_MODE 0x01
+#define VGA_SEQ_PLANE_WRITE 0x02
+#define VGA_SEQ_CHARACTER_MAP 0x03
+#define VGA_SEQ_MEMORY_MODE 0x04
+
+/* VGA sequencer register bit masks */
+#define VGA_SR01_CHAR_CLK_8DOTS 0x01 /* bit 0: character clocks 8 dots wide are generated */
+#define VGA_SR01_SCREEN_OFF 0x20 /* bit 5: Screen is off */
+#define VGA_SR02_ALL_PLANES 0x0F /* bits 3-0: enable access to all planes */
+#define VGA_SR04_EXT_MEM 0x02 /* bit 1: allows complete mem access to 256K */
+#define VGA_SR04_SEQ_MODE 0x04 /* bit 2: directs system to use a sequential addressing mode */
+#define VGA_SR04_CHN_4M 0x08 /* bit 3: selects modulo 4 addressing for CPU access to display memory */
+
+/* VGA graphics controller register indices */
+#define VGA_GFX_SR_VALUE 0x00
+#define VGA_GFX_SR_ENABLE 0x01
+#define VGA_GFX_COMPARE_VALUE 0x02
+#define VGA_GFX_DATA_ROTATE 0x03
+#define VGA_GFX_PLANE_READ 0x04
+#define VGA_GFX_MODE 0x05
+#define VGA_GFX_MISC 0x06
+#define VGA_GFX_COMPARE_MASK 0x07
+#define VGA_GFX_BIT_MASK 0x08
+
+/* VGA graphics controller bit masks */
+#define VGA_GR06_GRAPHICS_MODE 0x01
+
+#endif /* __linux_video_vga_h__ */
diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h
new file mode 100644
index 00000000..40ba6a42
--- /dev/null
+++ b/hw/display/vga_int.h
@@ -0,0 +1,228 @@
+/*
+ * QEMU internal VGA defines.
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#ifndef HW_VGA_INT_H
+#define HW_VGA_INT_H 1
+
+#include <hw/hw.h>
+#include "qapi/error.h"
+#include "exec/memory.h"
+
+#define ST01_V_RETRACE 0x08
+#define ST01_DISP_ENABLE 0x01
+
+#define VBE_DISPI_MAX_XRES 16000
+#define VBE_DISPI_MAX_YRES 12000
+#define VBE_DISPI_MAX_BPP 32
+
+#define VBE_DISPI_INDEX_ID 0x0
+#define VBE_DISPI_INDEX_XRES 0x1
+#define VBE_DISPI_INDEX_YRES 0x2
+#define VBE_DISPI_INDEX_BPP 0x3
+#define VBE_DISPI_INDEX_ENABLE 0x4
+#define VBE_DISPI_INDEX_BANK 0x5
+#define VBE_DISPI_INDEX_VIRT_WIDTH 0x6
+#define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7
+#define VBE_DISPI_INDEX_X_OFFSET 0x8
+#define VBE_DISPI_INDEX_Y_OFFSET 0x9
+#define VBE_DISPI_INDEX_NB 0xa /* size of vbe_regs[] */
+#define VBE_DISPI_INDEX_VIDEO_MEMORY_64K 0xa /* read-only, not in vbe_regs */
+
+#define VBE_DISPI_ID0 0xB0C0
+#define VBE_DISPI_ID1 0xB0C1
+#define VBE_DISPI_ID2 0xB0C2
+#define VBE_DISPI_ID3 0xB0C3
+#define VBE_DISPI_ID4 0xB0C4
+#define VBE_DISPI_ID5 0xB0C5
+
+#define VBE_DISPI_DISABLED 0x00
+#define VBE_DISPI_ENABLED 0x01
+#define VBE_DISPI_GETCAPS 0x02
+#define VBE_DISPI_8BIT_DAC 0x20
+#define VBE_DISPI_LFB_ENABLED 0x40
+#define VBE_DISPI_NOCLEARMEM 0x80
+
+#define VBE_DISPI_LFB_PHYSICAL_ADDRESS 0xE0000000
+
+#define CH_ATTR_SIZE (160 * 100)
+#define VGA_MAX_HEIGHT 2048
+
+struct vga_precise_retrace {
+ int64_t ticks_per_char;
+ int64_t total_chars;
+ int htotal;
+ int hstart;
+ int hend;
+ int vstart;
+ int vend;
+ int freq;
+};
+
+union vga_retrace {
+ struct vga_precise_retrace precise;
+};
+
+struct VGACommonState;
+typedef uint8_t (* vga_retrace_fn)(struct VGACommonState *s);
+typedef void (* vga_update_retrace_info_fn)(struct VGACommonState *s);
+
+typedef struct VGACommonState {
+ MemoryRegion *legacy_address_space;
+ uint8_t *vram_ptr;
+ MemoryRegion vram;
+ MemoryRegion vram_vbe;
+ uint32_t vram_size;
+ uint32_t vram_size_mb; /* property */
+ uint32_t vbe_size;
+ uint32_t latch;
+ bool has_chain4_alias;
+ MemoryRegion chain4_alias;
+ uint8_t sr_index;
+ uint8_t sr[256];
+ uint8_t gr_index;
+ uint8_t gr[256];
+ uint8_t ar_index;
+ uint8_t ar[21];
+ int ar_flip_flop;
+ uint8_t cr_index;
+ uint8_t cr[256]; /* CRT registers */
+ uint8_t msr; /* Misc Output Register */
+ uint8_t fcr; /* Feature Control Register */
+ uint8_t st00; /* status 0 */
+ uint8_t st01; /* status 1 */
+ uint8_t dac_state;
+ uint8_t dac_sub_index;
+ uint8_t dac_read_index;
+ uint8_t dac_write_index;
+ uint8_t dac_cache[3]; /* used when writing */
+ int dac_8bit;
+ uint8_t palette[768];
+ int32_t bank_offset;
+ int (*get_bpp)(struct VGACommonState *s);
+ void (*get_offsets)(struct VGACommonState *s,
+ uint32_t *pline_offset,
+ uint32_t *pstart_addr,
+ uint32_t *pline_compare);
+ void (*get_resolution)(struct VGACommonState *s,
+ int *pwidth,
+ int *pheight);
+ PortioList vga_port_list;
+ PortioList vbe_port_list;
+ /* bochs vbe state */
+ uint16_t vbe_index;
+ uint16_t vbe_regs[VBE_DISPI_INDEX_NB];
+ uint32_t vbe_start_addr;
+ uint32_t vbe_line_offset;
+ uint32_t vbe_bank_mask;
+ int vbe_mapped;
+ /* display refresh support */
+ QemuConsole *con;
+ uint32_t font_offsets[2];
+ int graphic_mode;
+ uint8_t shift_control;
+ uint8_t double_scan;
+ uint32_t line_offset;
+ uint32_t line_compare;
+ uint32_t start_addr;
+ uint32_t plane_updated;
+ uint32_t last_line_offset;
+ uint8_t last_cw, last_ch;
+ uint32_t last_width, last_height; /* in chars or pixels */
+ uint32_t last_scr_width, last_scr_height; /* in pixels */
+ uint32_t last_depth; /* in bits */
+ bool last_byteswap;
+ bool force_shadow;
+ uint8_t cursor_start, cursor_end;
+ bool cursor_visible_phase;
+ int64_t cursor_blink_time;
+ uint32_t cursor_offset;
+ const GraphicHwOps *hw_ops;
+ bool full_update_text;
+ bool full_update_gfx;
+ bool big_endian_fb;
+ bool default_endian_fb;
+ /* hardware mouse cursor support */
+ uint32_t invalidated_y_table[VGA_MAX_HEIGHT / 32];
+ uint32_t hw_cursor_x;
+ uint32_t hw_cursor_y;
+ void (*cursor_invalidate)(struct VGACommonState *s);
+ void (*cursor_draw_line)(struct VGACommonState *s, uint8_t *d, int y);
+ /* tell for each page if it has been updated since the last time */
+ uint32_t last_palette[256];
+ uint32_t last_ch_attr[CH_ATTR_SIZE]; /* XXX: make it dynamic */
+ /* retrace */
+ vga_retrace_fn retrace;
+ vga_update_retrace_info_fn update_retrace_info;
+ union vga_retrace retrace_info;
+ uint8_t is_vbe_vmstate;
+} VGACommonState;
+
+static inline int c6_to_8(int v)
+{
+ int b;
+ v &= 0x3f;
+ b = v & 1;
+ return (v << 2) | (b << 1) | b;
+}
+
+void vga_common_init(VGACommonState *s, Object *obj, bool global_vmstate);
+void vga_init(VGACommonState *s, Object *obj, MemoryRegion *address_space,
+ MemoryRegion *address_space_io, bool init_vga_ports);
+MemoryRegion *vga_init_io(VGACommonState *s, Object *obj,
+ const MemoryRegionPortio **vga_ports,
+ const MemoryRegionPortio **vbe_ports);
+void vga_common_reset(VGACommonState *s);
+
+void vga_sync_dirty_bitmap(VGACommonState *s);
+void vga_dirty_log_start(VGACommonState *s);
+void vga_dirty_log_stop(VGACommonState *s);
+
+extern const VMStateDescription vmstate_vga_common;
+uint32_t vga_ioport_read(void *opaque, uint32_t addr);
+void vga_ioport_write(void *opaque, uint32_t addr, uint32_t val);
+uint32_t vga_mem_readb(VGACommonState *s, hwaddr addr);
+void vga_mem_writeb(VGACommonState *s, hwaddr addr, uint32_t val);
+void vga_invalidate_scanlines(VGACommonState *s, int y1, int y2);
+
+int vga_ioport_invalid(VGACommonState *s, uint32_t addr);
+
+void vga_init_vbe(VGACommonState *s, Object *obj, MemoryRegion *address_space);
+uint32_t vbe_ioport_read_data(void *opaque, uint32_t addr);
+void vbe_ioport_write_index(void *opaque, uint32_t addr, uint32_t val);
+void vbe_ioport_write_data(void *opaque, uint32_t addr, uint32_t val);
+
+extern const uint8_t sr_mask[8];
+extern const uint8_t gr_mask[16];
+
+#define VGABIOS_FILENAME "vgabios.bin"
+#define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin"
+
+extern const MemoryRegionOps vga_mem_ops;
+
+/* vga-pci.c */
+void pci_std_vga_mmio_region_init(VGACommonState *s,
+ MemoryRegion *parent,
+ MemoryRegion *subs,
+ bool qext);
+
+#endif
diff --git a/hw/display/virtio-gpu-pci.c b/hw/display/virtio-gpu-pci.c
new file mode 100644
index 00000000..5bc62cf3
--- /dev/null
+++ b/hw/display/virtio-gpu-pci.c
@@ -0,0 +1,76 @@
+/*
+ * Virtio video device
+ *
+ * Copyright Red Hat
+ *
+ * Authors:
+ * Dave Airlie
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+#include "hw/pci/pci.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-pci.h"
+#include "hw/virtio/virtio-gpu.h"
+
+static Property virtio_gpu_pci_properties[] = {
+ DEFINE_VIRTIO_GPU_PCI_PROPERTIES(VirtIOPCIProxy),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_gpu_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOGPUPCI *vgpu = VIRTIO_GPU_PCI(vpci_dev);
+ VirtIOGPU *g = &vgpu->vdev;
+ DeviceState *vdev = DEVICE(&vgpu->vdev);
+ int i;
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ /* force virtio-1.0 */
+ vpci_dev->flags &= ~VIRTIO_PCI_FLAG_DISABLE_MODERN;
+ vpci_dev->flags |= VIRTIO_PCI_FLAG_DISABLE_LEGACY;
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ object_property_set_link(OBJECT(g->scanout[i].con),
+ OBJECT(vpci_dev),
+ "device", errp);
+ }
+}
+
+static void virtio_gpu_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->props = virtio_gpu_pci_properties;
+ k->realize = virtio_gpu_pci_realize;
+ pcidev_k->class_id = PCI_CLASS_DISPLAY_OTHER;
+}
+
+static void virtio_gpu_initfn(Object *obj)
+{
+ VirtIOGPUPCI *dev = VIRTIO_GPU_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_GPU);
+}
+
+static const TypeInfo virtio_gpu_pci_info = {
+ .name = TYPE_VIRTIO_GPU_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOGPUPCI),
+ .instance_init = virtio_gpu_initfn,
+ .class_init = virtio_gpu_pci_class_init,
+};
+
+static void virtio_gpu_pci_register_types(void)
+{
+ type_register_static(&virtio_gpu_pci_info);
+}
+type_init(virtio_gpu_pci_register_types)
diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
new file mode 100644
index 00000000..a67d927f
--- /dev/null
+++ b/hw/display/virtio-gpu.c
@@ -0,0 +1,919 @@
+/*
+ * Virtio GPU Device
+ *
+ * Copyright Red Hat, Inc. 2013-2014
+ *
+ * Authors:
+ * Dave Airlie <airlied@redhat.com>
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu-common.h"
+#include "qemu/iov.h"
+#include "ui/console.h"
+#include "trace.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-gpu.h"
+#include "hw/virtio/virtio-bus.h"
+
+static struct virtio_gpu_simple_resource*
+virtio_gpu_find_resource(VirtIOGPU *g, uint32_t resource_id);
+
+static void update_cursor_data_simple(VirtIOGPU *g,
+ struct virtio_gpu_scanout *s,
+ uint32_t resource_id)
+{
+ struct virtio_gpu_simple_resource *res;
+ uint32_t pixels;
+
+ res = virtio_gpu_find_resource(g, resource_id);
+ if (!res) {
+ return;
+ }
+
+ if (pixman_image_get_width(res->image) != s->current_cursor->width ||
+ pixman_image_get_height(res->image) != s->current_cursor->height) {
+ return;
+ }
+
+ pixels = s->current_cursor->width * s->current_cursor->height;
+ memcpy(s->current_cursor->data,
+ pixman_image_get_data(res->image),
+ pixels * sizeof(uint32_t));
+}
+
+static void update_cursor(VirtIOGPU *g, struct virtio_gpu_update_cursor *cursor)
+{
+ struct virtio_gpu_scanout *s;
+
+ if (cursor->pos.scanout_id >= g->conf.max_outputs) {
+ return;
+ }
+ s = &g->scanout[cursor->pos.scanout_id];
+
+ if (cursor->hdr.type != VIRTIO_GPU_CMD_MOVE_CURSOR) {
+ if (!s->current_cursor) {
+ s->current_cursor = cursor_alloc(64, 64);
+ }
+
+ s->current_cursor->hot_x = cursor->hot_x;
+ s->current_cursor->hot_y = cursor->hot_y;
+
+ if (cursor->resource_id > 0) {
+ update_cursor_data_simple(g, s, cursor->resource_id);
+ }
+ dpy_cursor_define(s->con, s->current_cursor);
+ }
+ dpy_mouse_set(s->con, cursor->pos.x, cursor->pos.y,
+ cursor->resource_id ? 1 : 0);
+}
+
+static void virtio_gpu_get_config(VirtIODevice *vdev, uint8_t *config)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ memcpy(config, &g->virtio_config, sizeof(g->virtio_config));
+}
+
+static void virtio_gpu_set_config(VirtIODevice *vdev, const uint8_t *config)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ struct virtio_gpu_config vgconfig;
+
+ memcpy(&vgconfig, config, sizeof(g->virtio_config));
+
+ if (vgconfig.events_clear) {
+ g->virtio_config.events_read &= ~vgconfig.events_clear;
+ }
+}
+
+static uint64_t virtio_gpu_get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
+{
+ return features;
+}
+
+static void virtio_gpu_notify_event(VirtIOGPU *g, uint32_t event_type)
+{
+ g->virtio_config.events_read |= event_type;
+ virtio_notify_config(&g->parent_obj);
+}
+
+static struct virtio_gpu_simple_resource *
+virtio_gpu_find_resource(VirtIOGPU *g, uint32_t resource_id)
+{
+ struct virtio_gpu_simple_resource *res;
+
+ QTAILQ_FOREACH(res, &g->reslist, next) {
+ if (res->resource_id == resource_id) {
+ return res;
+ }
+ }
+ return NULL;
+}
+
+void virtio_gpu_ctrl_response(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd,
+ struct virtio_gpu_ctrl_hdr *resp,
+ size_t resp_len)
+{
+ size_t s;
+
+ if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_FENCE) {
+ resp->flags |= VIRTIO_GPU_FLAG_FENCE;
+ resp->fence_id = cmd->cmd_hdr.fence_id;
+ resp->ctx_id = cmd->cmd_hdr.ctx_id;
+ }
+ s = iov_from_buf(cmd->elem.in_sg, cmd->elem.in_num, 0, resp, resp_len);
+ if (s != resp_len) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: response size incorrect %zu vs %zu\n",
+ __func__, s, resp_len);
+ }
+ virtqueue_push(cmd->vq, &cmd->elem, s);
+ virtio_notify(VIRTIO_DEVICE(g), cmd->vq);
+ cmd->finished = true;
+}
+
+void virtio_gpu_ctrl_response_nodata(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd,
+ enum virtio_gpu_ctrl_type type)
+{
+ struct virtio_gpu_ctrl_hdr resp;
+
+ memset(&resp, 0, sizeof(resp));
+ resp.type = type;
+ virtio_gpu_ctrl_response(g, cmd, &resp, sizeof(resp));
+}
+
+static void
+virtio_gpu_fill_display_info(VirtIOGPU *g,
+ struct virtio_gpu_resp_display_info *dpy_info)
+{
+ int i;
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ if (g->enabled_output_bitmask & (1 << i)) {
+ dpy_info->pmodes[i].enabled = 1;
+ dpy_info->pmodes[i].r.width = g->req_state[i].width;
+ dpy_info->pmodes[i].r.height = g->req_state[i].height;
+ }
+ }
+}
+
+void virtio_gpu_get_display_info(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resp_display_info display_info;
+
+ trace_virtio_gpu_cmd_get_display_info();
+ memset(&display_info, 0, sizeof(display_info));
+ display_info.hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO;
+ virtio_gpu_fill_display_info(g, &display_info);
+ virtio_gpu_ctrl_response(g, cmd, &display_info.hdr,
+ sizeof(display_info));
+}
+
+static pixman_format_code_t get_pixman_format(uint32_t virtio_gpu_format)
+{
+ switch (virtio_gpu_format) {
+#ifdef HOST_WORDS_BIGENDIAN
+ case VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM:
+ return PIXMAN_b8g8r8x8;
+ case VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM:
+ return PIXMAN_b8g8r8a8;
+ case VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM:
+ return PIXMAN_x8r8g8b8;
+ case VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM:
+ return PIXMAN_a8r8g8b8;
+ case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM:
+ return PIXMAN_r8g8b8x8;
+ case VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM:
+ return PIXMAN_r8g8b8a8;
+ case VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM:
+ return PIXMAN_x8b8g8r8;
+ case VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM:
+ return PIXMAN_a8b8g8r8;
+#else
+ case VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM:
+ return PIXMAN_x8r8g8b8;
+ case VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM:
+ return PIXMAN_a8r8g8b8;
+ case VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM:
+ return PIXMAN_b8g8r8x8;
+ case VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM:
+ return PIXMAN_b8g8r8a8;
+ case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM:
+ return PIXMAN_x8b8g8r8;
+ case VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM:
+ return PIXMAN_a8b8g8r8;
+ case VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM:
+ return PIXMAN_r8g8b8x8;
+ case VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM:
+ return PIXMAN_r8g8b8a8;
+#endif
+ default:
+ return 0;
+ }
+}
+
+static void virtio_gpu_resource_create_2d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ pixman_format_code_t pformat;
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_create_2d c2d;
+
+ VIRTIO_GPU_FILL_CMD(c2d);
+ trace_virtio_gpu_cmd_res_create_2d(c2d.resource_id, c2d.format,
+ c2d.width, c2d.height);
+
+ if (c2d.resource_id == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: resource id 0 is not allowed\n",
+ __func__);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ res = virtio_gpu_find_resource(g, c2d.resource_id);
+ if (res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: resource already exists %d\n",
+ __func__, c2d.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ res = g_new0(struct virtio_gpu_simple_resource, 1);
+
+ res->width = c2d.width;
+ res->height = c2d.height;
+ res->format = c2d.format;
+ res->resource_id = c2d.resource_id;
+
+ pformat = get_pixman_format(c2d.format);
+ if (!pformat) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: host couldn't handle guest format %d\n",
+ __func__, c2d.format);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+ res->image = pixman_image_create_bits(pformat,
+ c2d.width,
+ c2d.height,
+ NULL, 0);
+
+ if (!res->image) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: resource creation failed %d %d %d\n",
+ __func__, c2d.resource_id, c2d.width, c2d.height);
+ g_free(res);
+ cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY;
+ return;
+ }
+
+ QTAILQ_INSERT_HEAD(&g->reslist, res, next);
+}
+
+static void virtio_gpu_resource_destroy(VirtIOGPU *g,
+ struct virtio_gpu_simple_resource *res)
+{
+ pixman_image_unref(res->image);
+ QTAILQ_REMOVE(&g->reslist, res, next);
+ g_free(res);
+}
+
+static void virtio_gpu_resource_unref(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_unref unref;
+
+ VIRTIO_GPU_FILL_CMD(unref);
+ trace_virtio_gpu_cmd_res_unref(unref.resource_id);
+
+ res = virtio_gpu_find_resource(g, unref.resource_id);
+ if (!res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n",
+ __func__, unref.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+ virtio_gpu_resource_destroy(g, res);
+}
+
+static void virtio_gpu_transfer_to_host_2d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ int h;
+ uint32_t src_offset, dst_offset, stride;
+ int bpp;
+ pixman_format_code_t format;
+ struct virtio_gpu_transfer_to_host_2d t2d;
+
+ VIRTIO_GPU_FILL_CMD(t2d);
+ trace_virtio_gpu_cmd_res_xfer_toh_2d(t2d.resource_id);
+
+ res = virtio_gpu_find_resource(g, t2d.resource_id);
+ if (!res || !res->iov) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n",
+ __func__, t2d.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ if (t2d.r.x > res->width ||
+ t2d.r.y > res->height ||
+ t2d.r.width > res->width ||
+ t2d.r.height > res->height ||
+ t2d.r.x + t2d.r.width > res->width ||
+ t2d.r.y + t2d.r.height > res->height) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: transfer bounds outside resource"
+ " bounds for resource %d: %d %d %d %d vs %d %d\n",
+ __func__, t2d.resource_id, t2d.r.x, t2d.r.y,
+ t2d.r.width, t2d.r.height, res->width, res->height);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ format = pixman_image_get_format(res->image);
+ bpp = (PIXMAN_FORMAT_BPP(format) + 7) / 8;
+ stride = pixman_image_get_stride(res->image);
+
+ if (t2d.offset || t2d.r.x || t2d.r.y ||
+ t2d.r.width != pixman_image_get_width(res->image)) {
+ void *img_data = pixman_image_get_data(res->image);
+ for (h = 0; h < t2d.r.height; h++) {
+ src_offset = t2d.offset + stride * h;
+ dst_offset = (t2d.r.y + h) * stride + (t2d.r.x * bpp);
+
+ iov_to_buf(res->iov, res->iov_cnt, src_offset,
+ (uint8_t *)img_data
+ + dst_offset, t2d.r.width * bpp);
+ }
+ } else {
+ iov_to_buf(res->iov, res->iov_cnt, 0,
+ pixman_image_get_data(res->image),
+ pixman_image_get_stride(res->image)
+ * pixman_image_get_height(res->image));
+ }
+}
+
+static void virtio_gpu_resource_flush(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_flush rf;
+ pixman_region16_t flush_region;
+ int i;
+
+ VIRTIO_GPU_FILL_CMD(rf);
+ trace_virtio_gpu_cmd_res_flush(rf.resource_id,
+ rf.r.width, rf.r.height, rf.r.x, rf.r.y);
+
+ res = virtio_gpu_find_resource(g, rf.resource_id);
+ if (!res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n",
+ __func__, rf.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ if (rf.r.x > res->width ||
+ rf.r.y > res->height ||
+ rf.r.width > res->width ||
+ rf.r.height > res->height ||
+ rf.r.x + rf.r.width > res->width ||
+ rf.r.y + rf.r.height > res->height) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: flush bounds outside resource"
+ " bounds for resource %d: %d %d %d %d vs %d %d\n",
+ __func__, rf.resource_id, rf.r.x, rf.r.y,
+ rf.r.width, rf.r.height, res->width, res->height);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ pixman_region_init_rect(&flush_region,
+ rf.r.x, rf.r.y, rf.r.width, rf.r.height);
+ for (i = 0; i < VIRTIO_GPU_MAX_SCANOUT; i++) {
+ struct virtio_gpu_scanout *scanout;
+ pixman_region16_t region, finalregion;
+ pixman_box16_t *extents;
+
+ if (!(res->scanout_bitmask & (1 << i))) {
+ continue;
+ }
+ scanout = &g->scanout[i];
+
+ pixman_region_init(&finalregion);
+ pixman_region_init_rect(&region, scanout->x, scanout->y,
+ scanout->width, scanout->height);
+
+ pixman_region_intersect(&finalregion, &flush_region, &region);
+ pixman_region_translate(&finalregion, -scanout->x, -scanout->y);
+ extents = pixman_region_extents(&finalregion);
+ /* work out the area we need to update for each console */
+ dpy_gfx_update(g->scanout[i].con,
+ extents->x1, extents->y1,
+ extents->x2 - extents->x1,
+ extents->y2 - extents->y1);
+
+ pixman_region_fini(&region);
+ pixman_region_fini(&finalregion);
+ }
+ pixman_region_fini(&flush_region);
+}
+
+static void virtio_gpu_set_scanout(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_scanout *scanout;
+ pixman_format_code_t format;
+ uint32_t offset;
+ int bpp;
+ struct virtio_gpu_set_scanout ss;
+
+ VIRTIO_GPU_FILL_CMD(ss);
+ trace_virtio_gpu_cmd_set_scanout(ss.scanout_id, ss.resource_id,
+ ss.r.width, ss.r.height, ss.r.x, ss.r.y);
+
+ g->enable = 1;
+ if (ss.resource_id == 0) {
+ scanout = &g->scanout[ss.scanout_id];
+ if (scanout->resource_id) {
+ res = virtio_gpu_find_resource(g, scanout->resource_id);
+ if (res) {
+ res->scanout_bitmask &= ~(1 << ss.scanout_id);
+ }
+ }
+ if (ss.scanout_id == 0 ||
+ ss.scanout_id >= g->conf.max_outputs) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: illegal scanout id specified %d",
+ __func__, ss.scanout_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
+ return;
+ }
+ dpy_gfx_replace_surface(g->scanout[ss.scanout_id].con, NULL);
+ scanout->ds = NULL;
+ scanout->width = 0;
+ scanout->height = 0;
+ return;
+ }
+
+ /* create a surface for this scanout */
+ if (ss.scanout_id >= VIRTIO_GPU_MAX_SCANOUT ||
+ ss.scanout_id >= g->conf.max_outputs) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout id specified %d",
+ __func__, ss.scanout_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
+ return;
+ }
+
+ res = virtio_gpu_find_resource(g, ss.resource_id);
+ if (!res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n",
+ __func__, ss.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ if (ss.r.x > res->width ||
+ ss.r.y > res->height ||
+ ss.r.width > res->width ||
+ ss.r.height > res->height ||
+ ss.r.x + ss.r.width > res->width ||
+ ss.r.y + ss.r.height > res->height) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout %d bounds for"
+ " resource %d, (%d,%d)+%d,%d vs %d %d\n",
+ __func__, ss.scanout_id, ss.resource_id, ss.r.x, ss.r.y,
+ ss.r.width, ss.r.height, res->width, res->height);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ scanout = &g->scanout[ss.scanout_id];
+
+ format = pixman_image_get_format(res->image);
+ bpp = (PIXMAN_FORMAT_BPP(format) + 7) / 8;
+ offset = (ss.r.x * bpp) + ss.r.y * pixman_image_get_stride(res->image);
+ if (!scanout->ds || surface_data(scanout->ds)
+ != ((uint8_t *)pixman_image_get_data(res->image) + offset) ||
+ scanout->width != ss.r.width ||
+ scanout->height != ss.r.height) {
+ /* realloc the surface ptr */
+ scanout->ds = qemu_create_displaysurface_from
+ (ss.r.width, ss.r.height, format,
+ pixman_image_get_stride(res->image),
+ (uint8_t *)pixman_image_get_data(res->image) + offset);
+ if (!scanout->ds) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ return;
+ }
+ dpy_gfx_replace_surface(g->scanout[ss.scanout_id].con, scanout->ds);
+ }
+
+ res->scanout_bitmask |= (1 << ss.scanout_id);
+ scanout->resource_id = ss.resource_id;
+ scanout->x = ss.r.x;
+ scanout->y = ss.r.y;
+ scanout->width = ss.r.width;
+ scanout->height = ss.r.height;
+}
+
+int virtio_gpu_create_mapping_iov(struct virtio_gpu_resource_attach_backing *ab,
+ struct virtio_gpu_ctrl_command *cmd,
+ struct iovec **iov)
+{
+ struct virtio_gpu_mem_entry *ents;
+ size_t esize, s;
+ int i;
+
+ if (ab->nr_entries > 16384) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: nr_entries is too big (%d > 16384)\n",
+ __func__, ab->nr_entries);
+ return -1;
+ }
+
+ esize = sizeof(*ents) * ab->nr_entries;
+ ents = g_malloc(esize);
+ s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num,
+ sizeof(*ab), ents, esize);
+ if (s != esize) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: command data size incorrect %zu vs %zu\n",
+ __func__, s, esize);
+ g_free(ents);
+ return -1;
+ }
+
+ *iov = g_malloc0(sizeof(struct iovec) * ab->nr_entries);
+ for (i = 0; i < ab->nr_entries; i++) {
+ hwaddr len = ents[i].length;
+ (*iov)[i].iov_len = ents[i].length;
+ (*iov)[i].iov_base = cpu_physical_memory_map(ents[i].addr, &len, 1);
+ if (!(*iov)[i].iov_base || len != ents[i].length) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to map MMIO memory for"
+ " resource %d element %d\n",
+ __func__, ab->resource_id, i);
+ virtio_gpu_cleanup_mapping_iov(*iov, i);
+ g_free(ents);
+ g_free(*iov);
+ *iov = NULL;
+ return -1;
+ }
+ }
+ g_free(ents);
+ return 0;
+}
+
+void virtio_gpu_cleanup_mapping_iov(struct iovec *iov, uint32_t count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ cpu_physical_memory_unmap(iov[i].iov_base, iov[i].iov_len, 1,
+ iov[i].iov_len);
+ }
+}
+
+static void virtio_gpu_cleanup_mapping(struct virtio_gpu_simple_resource *res)
+{
+ virtio_gpu_cleanup_mapping_iov(res->iov, res->iov_cnt);
+ g_free(res->iov);
+ res->iov = NULL;
+ res->iov_cnt = 0;
+}
+
+static void
+virtio_gpu_resource_attach_backing(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_attach_backing ab;
+ int ret;
+
+ VIRTIO_GPU_FILL_CMD(ab);
+ trace_virtio_gpu_cmd_res_back_attach(ab.resource_id);
+
+ res = virtio_gpu_find_resource(g, ab.resource_id);
+ if (!res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n",
+ __func__, ab.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ ret = virtio_gpu_create_mapping_iov(&ab, cmd, &res->iov);
+ if (ret != 0) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ return;
+ }
+
+ res->iov_cnt = ab.nr_entries;
+}
+
+static void
+virtio_gpu_resource_detach_backing(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_detach_backing detach;
+
+ VIRTIO_GPU_FILL_CMD(detach);
+ trace_virtio_gpu_cmd_res_back_detach(detach.resource_id);
+
+ res = virtio_gpu_find_resource(g, detach.resource_id);
+ if (!res || !res->iov) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n",
+ __func__, detach.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+ virtio_gpu_cleanup_mapping(res);
+}
+
+static void virtio_gpu_simple_process_cmd(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ VIRTIO_GPU_FILL_CMD(cmd->cmd_hdr);
+
+ switch (cmd->cmd_hdr.type) {
+ case VIRTIO_GPU_CMD_GET_DISPLAY_INFO:
+ virtio_gpu_get_display_info(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D:
+ virtio_gpu_resource_create_2d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_UNREF:
+ virtio_gpu_resource_unref(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_FLUSH:
+ virtio_gpu_resource_flush(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D:
+ virtio_gpu_transfer_to_host_2d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_SET_SCANOUT:
+ virtio_gpu_set_scanout(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING:
+ virtio_gpu_resource_attach_backing(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING:
+ virtio_gpu_resource_detach_backing(g, cmd);
+ break;
+ default:
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ break;
+ }
+ if (!cmd->finished) {
+ virtio_gpu_ctrl_response_nodata(g, cmd, cmd->error ? cmd->error :
+ VIRTIO_GPU_RESP_OK_NODATA);
+ }
+}
+
+static void virtio_gpu_handle_ctrl_cb(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ qemu_bh_schedule(g->ctrl_bh);
+}
+
+static void virtio_gpu_handle_cursor_cb(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ qemu_bh_schedule(g->cursor_bh);
+}
+
+static void virtio_gpu_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ struct virtio_gpu_ctrl_command *cmd;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+
+ cmd = g_new(struct virtio_gpu_ctrl_command, 1);
+ while (virtqueue_pop(vq, &cmd->elem)) {
+ cmd->vq = vq;
+ cmd->error = 0;
+ cmd->finished = false;
+ g->stats.requests++;
+
+ virtio_gpu_simple_process_cmd(g, cmd);
+ if (!cmd->finished) {
+ QTAILQ_INSERT_TAIL(&g->fenceq, cmd, next);
+ g->stats.inflight++;
+ if (g->stats.max_inflight < g->stats.inflight) {
+ g->stats.max_inflight = g->stats.inflight;
+ }
+ fprintf(stderr, "inflight: %3d (+)\r", g->stats.inflight);
+ cmd = g_new(struct virtio_gpu_ctrl_command, 1);
+ }
+ }
+ g_free(cmd);
+}
+
+static void virtio_gpu_ctrl_bh(void *opaque)
+{
+ VirtIOGPU *g = opaque;
+ virtio_gpu_handle_ctrl(&g->parent_obj, g->ctrl_vq);
+}
+
+static void virtio_gpu_handle_cursor(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ VirtQueueElement elem;
+ size_t s;
+ struct virtio_gpu_update_cursor cursor_info;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+ while (virtqueue_pop(vq, &elem)) {
+ s = iov_to_buf(elem.out_sg, elem.out_num, 0,
+ &cursor_info, sizeof(cursor_info));
+ if (s != sizeof(cursor_info)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: cursor size incorrect %zu vs %zu\n",
+ __func__, s, sizeof(cursor_info));
+ } else {
+ update_cursor(g, &cursor_info);
+ }
+ virtqueue_push(vq, &elem, 0);
+ virtio_notify(vdev, vq);
+ }
+}
+
+static void virtio_gpu_cursor_bh(void *opaque)
+{
+ VirtIOGPU *g = opaque;
+ virtio_gpu_handle_cursor(&g->parent_obj, g->cursor_vq);
+}
+
+static void virtio_gpu_invalidate_display(void *opaque)
+{
+}
+
+static void virtio_gpu_update_display(void *opaque)
+{
+}
+
+static void virtio_gpu_text_update(void *opaque, console_ch_t *chardata)
+{
+}
+
+static int virtio_gpu_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
+{
+ VirtIOGPU *g = opaque;
+
+ if (idx > g->conf.max_outputs) {
+ return -1;
+ }
+
+ g->req_state[idx].x = info->xoff;
+ g->req_state[idx].y = info->yoff;
+ g->req_state[idx].width = info->width;
+ g->req_state[idx].height = info->height;
+
+ if (info->width && info->height) {
+ g->enabled_output_bitmask |= (1 << idx);
+ } else {
+ g->enabled_output_bitmask &= ~(1 << idx);
+ }
+
+ /* send event to guest */
+ virtio_gpu_notify_event(g, VIRTIO_GPU_EVENT_DISPLAY);
+ return 0;
+}
+
+const GraphicHwOps virtio_gpu_ops = {
+ .invalidate = virtio_gpu_invalidate_display,
+ .gfx_update = virtio_gpu_update_display,
+ .text_update = virtio_gpu_text_update,
+ .ui_info = virtio_gpu_ui_info,
+};
+
+static void virtio_gpu_device_realize(DeviceState *qdev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(qdev);
+ VirtIOGPU *g = VIRTIO_GPU(qdev);
+ int i;
+
+ g->config_size = sizeof(struct virtio_gpu_config);
+ g->virtio_config.num_scanouts = g->conf.max_outputs;
+ virtio_init(VIRTIO_DEVICE(g), "virtio-gpu", VIRTIO_ID_GPU,
+ g->config_size);
+
+ g->req_state[0].width = 1024;
+ g->req_state[0].height = 768;
+
+ g->ctrl_vq = virtio_add_queue(vdev, 64, virtio_gpu_handle_ctrl_cb);
+ g->cursor_vq = virtio_add_queue(vdev, 16, virtio_gpu_handle_cursor_cb);
+
+ g->ctrl_bh = qemu_bh_new(virtio_gpu_ctrl_bh, g);
+ g->cursor_bh = qemu_bh_new(virtio_gpu_cursor_bh, g);
+ QTAILQ_INIT(&g->reslist);
+ QTAILQ_INIT(&g->fenceq);
+
+ g->enabled_output_bitmask = 1;
+ g->qdev = qdev;
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ g->scanout[i].con =
+ graphic_console_init(DEVICE(g), i, &virtio_gpu_ops, g);
+ if (i > 0) {
+ dpy_gfx_replace_surface(g->scanout[i].con, NULL);
+ }
+ }
+}
+
+static void virtio_gpu_instance_init(Object *obj)
+{
+}
+
+static void virtio_gpu_reset(VirtIODevice *vdev)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ struct virtio_gpu_simple_resource *res, *tmp;
+ int i;
+
+ g->enable = 0;
+
+ QTAILQ_FOREACH_SAFE(res, &g->reslist, next, tmp) {
+ virtio_gpu_resource_destroy(g, res);
+ }
+ for (i = 0; i < g->conf.max_outputs; i++) {
+#if 0
+ g->req_state[i].x = 0;
+ g->req_state[i].y = 0;
+ if (i == 0) {
+ g->req_state[0].width = 1024;
+ g->req_state[0].height = 768;
+ } else {
+ g->req_state[i].width = 0;
+ g->req_state[i].height = 0;
+ }
+#endif
+ g->scanout[i].resource_id = 0;
+ g->scanout[i].width = 0;
+ g->scanout[i].height = 0;
+ g->scanout[i].x = 0;
+ g->scanout[i].y = 0;
+ g->scanout[i].ds = NULL;
+ }
+ g->enabled_output_bitmask = 1;
+}
+
+static Property virtio_gpu_properties[] = {
+ DEFINE_PROP_UINT32("max_outputs", VirtIOGPU, conf.max_outputs, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_gpu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ vdc->realize = virtio_gpu_device_realize;
+ vdc->get_config = virtio_gpu_get_config;
+ vdc->set_config = virtio_gpu_set_config;
+ vdc->get_features = virtio_gpu_get_features;
+
+ vdc->reset = virtio_gpu_reset;
+
+ dc->props = virtio_gpu_properties;
+}
+
+static const TypeInfo virtio_gpu_info = {
+ .name = TYPE_VIRTIO_GPU,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOGPU),
+ .instance_init = virtio_gpu_instance_init,
+ .class_init = virtio_gpu_class_init,
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_gpu_info);
+}
+
+type_init(virtio_register_types)
+
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_ctrl_hdr) != 24);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_update_cursor) != 56);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_unref) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_create_2d) != 40);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_set_scanout) != 48);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_flush) != 48);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_transfer_to_host_2d) != 56);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_mem_entry) != 16);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_attach_backing) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_detach_backing) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resp_display_info) != 408);
diff --git a/hw/display/virtio-vga.c b/hw/display/virtio-vga.c
new file mode 100644
index 00000000..f7e539fe
--- /dev/null
+++ b/hw/display/virtio-vga.c
@@ -0,0 +1,182 @@
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "ui/console.h"
+#include "vga_int.h"
+#include "hw/virtio/virtio-pci.h"
+
+/*
+ * virtio-vga: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_VGA "virtio-vga"
+#define VIRTIO_VGA(obj) \
+ OBJECT_CHECK(VirtIOVGA, (obj), TYPE_VIRTIO_VGA)
+
+typedef struct VirtIOVGA {
+ VirtIOPCIProxy parent_obj;
+ VirtIOGPU vdev;
+ VGACommonState vga;
+ MemoryRegion vga_mrs[3];
+} VirtIOVGA;
+
+static void virtio_vga_invalidate_display(void *opaque)
+{
+ VirtIOVGA *vvga = opaque;
+
+ if (vvga->vdev.enable) {
+ virtio_gpu_ops.invalidate(&vvga->vdev);
+ } else {
+ vvga->vga.hw_ops->invalidate(&vvga->vga);
+ }
+}
+
+static void virtio_vga_update_display(void *opaque)
+{
+ VirtIOVGA *vvga = opaque;
+
+ if (vvga->vdev.enable) {
+ virtio_gpu_ops.gfx_update(&vvga->vdev);
+ } else {
+ vvga->vga.hw_ops->gfx_update(&vvga->vga);
+ }
+}
+
+static void virtio_vga_text_update(void *opaque, console_ch_t *chardata)
+{
+ VirtIOVGA *vvga = opaque;
+
+ if (vvga->vdev.enable) {
+ if (virtio_gpu_ops.text_update) {
+ virtio_gpu_ops.text_update(&vvga->vdev, chardata);
+ }
+ } else {
+ if (vvga->vga.hw_ops->text_update) {
+ vvga->vga.hw_ops->text_update(&vvga->vga, chardata);
+ }
+ }
+}
+
+static int virtio_vga_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
+{
+ VirtIOVGA *vvga = opaque;
+
+ if (virtio_gpu_ops.ui_info) {
+ return virtio_gpu_ops.ui_info(&vvga->vdev, idx, info);
+ }
+ return -1;
+}
+
+static const GraphicHwOps virtio_vga_ops = {
+ .invalidate = virtio_vga_invalidate_display,
+ .gfx_update = virtio_vga_update_display,
+ .text_update = virtio_vga_text_update,
+ .ui_info = virtio_vga_ui_info,
+};
+
+/* VGA device wrapper around PCI device around virtio GPU */
+static void virtio_vga_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOVGA *vvga = VIRTIO_VGA(vpci_dev);
+ VirtIOGPU *g = &vvga->vdev;
+ VGACommonState *vga = &vvga->vga;
+ uint32_t offset;
+ int i;
+
+ /* init vga compat bits */
+ vga->vram_size_mb = 8;
+ vga_common_init(vga, OBJECT(vpci_dev), false);
+ vga_init(vga, OBJECT(vpci_dev), pci_address_space(&vpci_dev->pci_dev),
+ pci_address_space_io(&vpci_dev->pci_dev), true);
+ pci_register_bar(&vpci_dev->pci_dev, 0,
+ PCI_BASE_ADDRESS_MEM_PREFETCH, &vga->vram);
+
+ /*
+ * Configure virtio bar and regions
+ *
+ * We use bar #2 for the mmio regions, to be compatible with stdvga.
+ * virtio regions are moved to the end of bar #2, to make room for
+ * the stdvga mmio registers at the start of bar #2.
+ */
+ vpci_dev->modern_mem_bar = 2;
+ vpci_dev->msix_bar = 4;
+ offset = memory_region_size(&vpci_dev->modern_bar);
+ offset -= vpci_dev->notify.size;
+ vpci_dev->notify.offset = offset;
+ offset -= vpci_dev->device.size;
+ vpci_dev->device.offset = offset;
+ offset -= vpci_dev->isr.size;
+ vpci_dev->isr.offset = offset;
+ offset -= vpci_dev->common.size;
+ vpci_dev->common.offset = offset;
+
+ /* init virtio bits */
+ qdev_set_parent_bus(DEVICE(g), BUS(&vpci_dev->bus));
+ /* force virtio-1.0 */
+ vpci_dev->flags &= ~VIRTIO_PCI_FLAG_DISABLE_MODERN;
+ vpci_dev->flags |= VIRTIO_PCI_FLAG_DISABLE_LEGACY;
+ object_property_set_bool(OBJECT(g), true, "realized", errp);
+
+ /* add stdvga mmio regions */
+ pci_std_vga_mmio_region_init(vga, &vpci_dev->modern_bar,
+ vvga->vga_mrs, true);
+
+ vga->con = g->scanout[0].con;
+ graphic_console_set_hwops(vga->con, &virtio_vga_ops, vvga);
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ object_property_set_link(OBJECT(g->scanout[i].con),
+ OBJECT(vpci_dev),
+ "device", errp);
+ }
+}
+
+static void virtio_vga_reset(DeviceState *dev)
+{
+ VirtIOVGA *vvga = VIRTIO_VGA(dev);
+ vvga->vdev.enable = 0;
+
+ vga_dirty_log_start(&vvga->vga);
+}
+
+static Property virtio_vga_properties[] = {
+ DEFINE_VIRTIO_GPU_PCI_PROPERTIES(VirtIOPCIProxy),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_vga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->props = virtio_vga_properties;
+ dc->reset = virtio_vga_reset;
+ dc->hotpluggable = false;
+
+ k->realize = virtio_vga_realize;
+ pcidev_k->romfile = "vgabios-virtio.bin";
+ pcidev_k->class_id = PCI_CLASS_DISPLAY_VGA;
+}
+
+static void virtio_vga_inst_initfn(Object *obj)
+{
+ VirtIOVGA *dev = VIRTIO_VGA(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_GPU);
+}
+
+static TypeInfo virtio_vga_info = {
+ .name = TYPE_VIRTIO_VGA,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(struct VirtIOVGA),
+ .instance_init = virtio_vga_inst_initfn,
+ .class_init = virtio_vga_class_init,
+};
+
+static void virtio_vga_register_types(void)
+{
+ type_register_static(&virtio_vga_info);
+}
+
+type_init(virtio_vga_register_types)
diff --git a/hw/display/vmware_vga.c b/hw/display/vmware_vga.c
new file mode 100644
index 00000000..7f397d3c
--- /dev/null
+++ b/hw/display/vmware_vga.c
@@ -0,0 +1,1365 @@
+/*
+ * QEMU VMware-SVGA "chipset".
+ *
+ * Copyright (c) 2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/loader.h"
+#include "trace.h"
+#include "ui/console.h"
+#include "ui/vnc.h"
+#include "hw/pci/pci.h"
+
+#undef VERBOSE
+#define HW_RECT_ACCEL
+#define HW_FILL_ACCEL
+#define HW_MOUSE_ACCEL
+
+#include "vga_int.h"
+
+/* See http://vmware-svga.sf.net/ for some documentation on VMWare SVGA */
+
+struct vmsvga_state_s {
+ VGACommonState vga;
+
+ int invalidated;
+ int enable;
+ int config;
+ struct {
+ int id;
+ int x;
+ int y;
+ int on;
+ } cursor;
+
+ int index;
+ int scratch_size;
+ uint32_t *scratch;
+ int new_width;
+ int new_height;
+ int new_depth;
+ uint32_t guest;
+ uint32_t svgaid;
+ int syncing;
+
+ MemoryRegion fifo_ram;
+ uint8_t *fifo_ptr;
+ unsigned int fifo_size;
+
+ union {
+ uint32_t *fifo;
+ struct QEMU_PACKED {
+ uint32_t min;
+ uint32_t max;
+ uint32_t next_cmd;
+ uint32_t stop;
+ /* Add registers here when adding capabilities. */
+ uint32_t fifo[0];
+ } *cmd;
+ };
+
+#define REDRAW_FIFO_LEN 512
+ struct vmsvga_rect_s {
+ int x, y, w, h;
+ } redraw_fifo[REDRAW_FIFO_LEN];
+ int redraw_fifo_first, redraw_fifo_last;
+};
+
+#define TYPE_VMWARE_SVGA "vmware-svga"
+
+#define VMWARE_SVGA(obj) \
+ OBJECT_CHECK(struct pci_vmsvga_state_s, (obj), TYPE_VMWARE_SVGA)
+
+struct pci_vmsvga_state_s {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ struct vmsvga_state_s chip;
+ MemoryRegion io_bar;
+};
+
+#define SVGA_MAGIC 0x900000UL
+#define SVGA_MAKE_ID(ver) (SVGA_MAGIC << 8 | (ver))
+#define SVGA_ID_0 SVGA_MAKE_ID(0)
+#define SVGA_ID_1 SVGA_MAKE_ID(1)
+#define SVGA_ID_2 SVGA_MAKE_ID(2)
+
+#define SVGA_LEGACY_BASE_PORT 0x4560
+#define SVGA_INDEX_PORT 0x0
+#define SVGA_VALUE_PORT 0x1
+#define SVGA_BIOS_PORT 0x2
+
+#define SVGA_VERSION_2
+
+#ifdef SVGA_VERSION_2
+# define SVGA_ID SVGA_ID_2
+# define SVGA_IO_BASE SVGA_LEGACY_BASE_PORT
+# define SVGA_IO_MUL 1
+# define SVGA_FIFO_SIZE 0x10000
+# define SVGA_PCI_DEVICE_ID PCI_DEVICE_ID_VMWARE_SVGA2
+#else
+# define SVGA_ID SVGA_ID_1
+# define SVGA_IO_BASE SVGA_LEGACY_BASE_PORT
+# define SVGA_IO_MUL 4
+# define SVGA_FIFO_SIZE 0x10000
+# define SVGA_PCI_DEVICE_ID PCI_DEVICE_ID_VMWARE_SVGA
+#endif
+
+enum {
+ /* ID 0, 1 and 2 registers */
+ SVGA_REG_ID = 0,
+ SVGA_REG_ENABLE = 1,
+ SVGA_REG_WIDTH = 2,
+ SVGA_REG_HEIGHT = 3,
+ SVGA_REG_MAX_WIDTH = 4,
+ SVGA_REG_MAX_HEIGHT = 5,
+ SVGA_REG_DEPTH = 6,
+ SVGA_REG_BITS_PER_PIXEL = 7, /* Current bpp in the guest */
+ SVGA_REG_PSEUDOCOLOR = 8,
+ SVGA_REG_RED_MASK = 9,
+ SVGA_REG_GREEN_MASK = 10,
+ SVGA_REG_BLUE_MASK = 11,
+ SVGA_REG_BYTES_PER_LINE = 12,
+ SVGA_REG_FB_START = 13,
+ SVGA_REG_FB_OFFSET = 14,
+ SVGA_REG_VRAM_SIZE = 15,
+ SVGA_REG_FB_SIZE = 16,
+
+ /* ID 1 and 2 registers */
+ SVGA_REG_CAPABILITIES = 17,
+ SVGA_REG_MEM_START = 18, /* Memory for command FIFO */
+ SVGA_REG_MEM_SIZE = 19,
+ SVGA_REG_CONFIG_DONE = 20, /* Set when memory area configured */
+ SVGA_REG_SYNC = 21, /* Write to force synchronization */
+ SVGA_REG_BUSY = 22, /* Read to check if sync is done */
+ SVGA_REG_GUEST_ID = 23, /* Set guest OS identifier */
+ SVGA_REG_CURSOR_ID = 24, /* ID of cursor */
+ SVGA_REG_CURSOR_X = 25, /* Set cursor X position */
+ SVGA_REG_CURSOR_Y = 26, /* Set cursor Y position */
+ SVGA_REG_CURSOR_ON = 27, /* Turn cursor on/off */
+ SVGA_REG_HOST_BITS_PER_PIXEL = 28, /* Current bpp in the host */
+ SVGA_REG_SCRATCH_SIZE = 29, /* Number of scratch registers */
+ SVGA_REG_MEM_REGS = 30, /* Number of FIFO registers */
+ SVGA_REG_NUM_DISPLAYS = 31, /* Number of guest displays */
+ SVGA_REG_PITCHLOCK = 32, /* Fixed pitch for all modes */
+
+ SVGA_PALETTE_BASE = 1024, /* Base of SVGA color map */
+ SVGA_PALETTE_END = SVGA_PALETTE_BASE + 767,
+ SVGA_SCRATCH_BASE = SVGA_PALETTE_BASE + 768,
+};
+
+#define SVGA_CAP_NONE 0
+#define SVGA_CAP_RECT_FILL (1 << 0)
+#define SVGA_CAP_RECT_COPY (1 << 1)
+#define SVGA_CAP_RECT_PAT_FILL (1 << 2)
+#define SVGA_CAP_LEGACY_OFFSCREEN (1 << 3)
+#define SVGA_CAP_RASTER_OP (1 << 4)
+#define SVGA_CAP_CURSOR (1 << 5)
+#define SVGA_CAP_CURSOR_BYPASS (1 << 6)
+#define SVGA_CAP_CURSOR_BYPASS_2 (1 << 7)
+#define SVGA_CAP_8BIT_EMULATION (1 << 8)
+#define SVGA_CAP_ALPHA_CURSOR (1 << 9)
+#define SVGA_CAP_GLYPH (1 << 10)
+#define SVGA_CAP_GLYPH_CLIPPING (1 << 11)
+#define SVGA_CAP_OFFSCREEN_1 (1 << 12)
+#define SVGA_CAP_ALPHA_BLEND (1 << 13)
+#define SVGA_CAP_3D (1 << 14)
+#define SVGA_CAP_EXTENDED_FIFO (1 << 15)
+#define SVGA_CAP_MULTIMON (1 << 16)
+#define SVGA_CAP_PITCHLOCK (1 << 17)
+
+/*
+ * FIFO offsets (seen as an array of 32-bit words)
+ */
+enum {
+ /*
+ * The original defined FIFO offsets
+ */
+ SVGA_FIFO_MIN = 0,
+ SVGA_FIFO_MAX, /* The distance from MIN to MAX must be at least 10K */
+ SVGA_FIFO_NEXT_CMD,
+ SVGA_FIFO_STOP,
+
+ /*
+ * Additional offsets added as of SVGA_CAP_EXTENDED_FIFO
+ */
+ SVGA_FIFO_CAPABILITIES = 4,
+ SVGA_FIFO_FLAGS,
+ SVGA_FIFO_FENCE,
+ SVGA_FIFO_3D_HWVERSION,
+ SVGA_FIFO_PITCHLOCK,
+};
+
+#define SVGA_FIFO_CAP_NONE 0
+#define SVGA_FIFO_CAP_FENCE (1 << 0)
+#define SVGA_FIFO_CAP_ACCELFRONT (1 << 1)
+#define SVGA_FIFO_CAP_PITCHLOCK (1 << 2)
+
+#define SVGA_FIFO_FLAG_NONE 0
+#define SVGA_FIFO_FLAG_ACCELFRONT (1 << 0)
+
+/* These values can probably be changed arbitrarily. */
+#define SVGA_SCRATCH_SIZE 0x8000
+#define SVGA_MAX_WIDTH ROUND_UP(2360, VNC_DIRTY_PIXELS_PER_BIT)
+#define SVGA_MAX_HEIGHT 1770
+
+#ifdef VERBOSE
+# define GUEST_OS_BASE 0x5001
+static const char *vmsvga_guest_id[] = {
+ [0x00] = "Dos",
+ [0x01] = "Windows 3.1",
+ [0x02] = "Windows 95",
+ [0x03] = "Windows 98",
+ [0x04] = "Windows ME",
+ [0x05] = "Windows NT",
+ [0x06] = "Windows 2000",
+ [0x07] = "Linux",
+ [0x08] = "OS/2",
+ [0x09] = "an unknown OS",
+ [0x0a] = "BSD",
+ [0x0b] = "Whistler",
+ [0x0c] = "an unknown OS",
+ [0x0d] = "an unknown OS",
+ [0x0e] = "an unknown OS",
+ [0x0f] = "an unknown OS",
+ [0x10] = "an unknown OS",
+ [0x11] = "an unknown OS",
+ [0x12] = "an unknown OS",
+ [0x13] = "an unknown OS",
+ [0x14] = "an unknown OS",
+ [0x15] = "Windows 2003",
+};
+#endif
+
+enum {
+ SVGA_CMD_INVALID_CMD = 0,
+ SVGA_CMD_UPDATE = 1,
+ SVGA_CMD_RECT_FILL = 2,
+ SVGA_CMD_RECT_COPY = 3,
+ SVGA_CMD_DEFINE_BITMAP = 4,
+ SVGA_CMD_DEFINE_BITMAP_SCANLINE = 5,
+ SVGA_CMD_DEFINE_PIXMAP = 6,
+ SVGA_CMD_DEFINE_PIXMAP_SCANLINE = 7,
+ SVGA_CMD_RECT_BITMAP_FILL = 8,
+ SVGA_CMD_RECT_PIXMAP_FILL = 9,
+ SVGA_CMD_RECT_BITMAP_COPY = 10,
+ SVGA_CMD_RECT_PIXMAP_COPY = 11,
+ SVGA_CMD_FREE_OBJECT = 12,
+ SVGA_CMD_RECT_ROP_FILL = 13,
+ SVGA_CMD_RECT_ROP_COPY = 14,
+ SVGA_CMD_RECT_ROP_BITMAP_FILL = 15,
+ SVGA_CMD_RECT_ROP_PIXMAP_FILL = 16,
+ SVGA_CMD_RECT_ROP_BITMAP_COPY = 17,
+ SVGA_CMD_RECT_ROP_PIXMAP_COPY = 18,
+ SVGA_CMD_DEFINE_CURSOR = 19,
+ SVGA_CMD_DISPLAY_CURSOR = 20,
+ SVGA_CMD_MOVE_CURSOR = 21,
+ SVGA_CMD_DEFINE_ALPHA_CURSOR = 22,
+ SVGA_CMD_DRAW_GLYPH = 23,
+ SVGA_CMD_DRAW_GLYPH_CLIPPED = 24,
+ SVGA_CMD_UPDATE_VERBOSE = 25,
+ SVGA_CMD_SURFACE_FILL = 26,
+ SVGA_CMD_SURFACE_COPY = 27,
+ SVGA_CMD_SURFACE_ALPHA_BLEND = 28,
+ SVGA_CMD_FRONT_ROP_FILL = 29,
+ SVGA_CMD_FENCE = 30,
+};
+
+/* Legal values for the SVGA_REG_CURSOR_ON register in cursor bypass mode */
+enum {
+ SVGA_CURSOR_ON_HIDE = 0,
+ SVGA_CURSOR_ON_SHOW = 1,
+ SVGA_CURSOR_ON_REMOVE_FROM_FB = 2,
+ SVGA_CURSOR_ON_RESTORE_TO_FB = 3,
+};
+
+static inline bool vmsvga_verify_rect(DisplaySurface *surface,
+ const char *name,
+ int x, int y, int w, int h)
+{
+ if (x < 0) {
+ fprintf(stderr, "%s: x was < 0 (%d)\n", name, x);
+ return false;
+ }
+ if (x > SVGA_MAX_WIDTH) {
+ fprintf(stderr, "%s: x was > %d (%d)\n", name, SVGA_MAX_WIDTH, x);
+ return false;
+ }
+ if (w < 0) {
+ fprintf(stderr, "%s: w was < 0 (%d)\n", name, w);
+ return false;
+ }
+ if (w > SVGA_MAX_WIDTH) {
+ fprintf(stderr, "%s: w was > %d (%d)\n", name, SVGA_MAX_WIDTH, w);
+ return false;
+ }
+ if (x + w > surface_width(surface)) {
+ fprintf(stderr, "%s: width was > %d (x: %d, w: %d)\n",
+ name, surface_width(surface), x, w);
+ return false;
+ }
+
+ if (y < 0) {
+ fprintf(stderr, "%s: y was < 0 (%d)\n", name, y);
+ return false;
+ }
+ if (y > SVGA_MAX_HEIGHT) {
+ fprintf(stderr, "%s: y was > %d (%d)\n", name, SVGA_MAX_HEIGHT, y);
+ return false;
+ }
+ if (h < 0) {
+ fprintf(stderr, "%s: h was < 0 (%d)\n", name, h);
+ return false;
+ }
+ if (h > SVGA_MAX_HEIGHT) {
+ fprintf(stderr, "%s: h was > %d (%d)\n", name, SVGA_MAX_HEIGHT, h);
+ return false;
+ }
+ if (y + h > surface_height(surface)) {
+ fprintf(stderr, "%s: update height > %d (y: %d, h: %d)\n",
+ name, surface_height(surface), y, h);
+ return false;
+ }
+
+ return true;
+}
+
+static inline void vmsvga_update_rect(struct vmsvga_state_s *s,
+ int x, int y, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+ int line;
+ int bypl;
+ int width;
+ int start;
+ uint8_t *src;
+ uint8_t *dst;
+
+ if (!vmsvga_verify_rect(surface, __func__, x, y, w, h)) {
+ /* go for a fullscreen update as fallback */
+ x = 0;
+ y = 0;
+ w = surface_width(surface);
+ h = surface_height(surface);
+ }
+
+ bypl = surface_stride(surface);
+ width = surface_bytes_per_pixel(surface) * w;
+ start = surface_bytes_per_pixel(surface) * x + bypl * y;
+ src = s->vga.vram_ptr + start;
+ dst = surface_data(surface) + start;
+
+ for (line = h; line > 0; line--, src += bypl, dst += bypl) {
+ memcpy(dst, src, width);
+ }
+ dpy_gfx_update(s->vga.con, x, y, w, h);
+}
+
+static inline void vmsvga_update_rect_delayed(struct vmsvga_state_s *s,
+ int x, int y, int w, int h)
+{
+ struct vmsvga_rect_s *rect = &s->redraw_fifo[s->redraw_fifo_last++];
+
+ s->redraw_fifo_last &= REDRAW_FIFO_LEN - 1;
+ rect->x = x;
+ rect->y = y;
+ rect->w = w;
+ rect->h = h;
+}
+
+static inline void vmsvga_update_rect_flush(struct vmsvga_state_s *s)
+{
+ struct vmsvga_rect_s *rect;
+
+ if (s->invalidated) {
+ s->redraw_fifo_first = s->redraw_fifo_last;
+ return;
+ }
+ /* Overlapping region updates can be optimised out here - if someone
+ * knows a smart algorithm to do that, please share. */
+ while (s->redraw_fifo_first != s->redraw_fifo_last) {
+ rect = &s->redraw_fifo[s->redraw_fifo_first++];
+ s->redraw_fifo_first &= REDRAW_FIFO_LEN - 1;
+ vmsvga_update_rect(s, rect->x, rect->y, rect->w, rect->h);
+ }
+}
+
+#ifdef HW_RECT_ACCEL
+static inline int vmsvga_copy_rect(struct vmsvga_state_s *s,
+ int x0, int y0, int x1, int y1, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+ uint8_t *vram = s->vga.vram_ptr;
+ int bypl = surface_stride(surface);
+ int bypp = surface_bytes_per_pixel(surface);
+ int width = bypp * w;
+ int line = h;
+ uint8_t *ptr[2];
+
+ if (!vmsvga_verify_rect(surface, "vmsvga_copy_rect/src", x0, y0, w, h)) {
+ return -1;
+ }
+ if (!vmsvga_verify_rect(surface, "vmsvga_copy_rect/dst", x1, y1, w, h)) {
+ return -1;
+ }
+
+ if (y1 > y0) {
+ ptr[0] = vram + bypp * x0 + bypl * (y0 + h - 1);
+ ptr[1] = vram + bypp * x1 + bypl * (y1 + h - 1);
+ for (; line > 0; line --, ptr[0] -= bypl, ptr[1] -= bypl) {
+ memmove(ptr[1], ptr[0], width);
+ }
+ } else {
+ ptr[0] = vram + bypp * x0 + bypl * y0;
+ ptr[1] = vram + bypp * x1 + bypl * y1;
+ for (; line > 0; line --, ptr[0] += bypl, ptr[1] += bypl) {
+ memmove(ptr[1], ptr[0], width);
+ }
+ }
+
+ vmsvga_update_rect_delayed(s, x1, y1, w, h);
+ return 0;
+}
+#endif
+
+#ifdef HW_FILL_ACCEL
+static inline int vmsvga_fill_rect(struct vmsvga_state_s *s,
+ uint32_t c, int x, int y, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+ int bypl = surface_stride(surface);
+ int width = surface_bytes_per_pixel(surface) * w;
+ int line = h;
+ int column;
+ uint8_t *fst;
+ uint8_t *dst;
+ uint8_t *src;
+ uint8_t col[4];
+
+ if (!vmsvga_verify_rect(surface, __func__, x, y, w, h)) {
+ return -1;
+ }
+
+ col[0] = c;
+ col[1] = c >> 8;
+ col[2] = c >> 16;
+ col[3] = c >> 24;
+
+ fst = s->vga.vram_ptr + surface_bytes_per_pixel(surface) * x + bypl * y;
+
+ if (line--) {
+ dst = fst;
+ src = col;
+ for (column = width; column > 0; column--) {
+ *(dst++) = *(src++);
+ if (src - col == surface_bytes_per_pixel(surface)) {
+ src = col;
+ }
+ }
+ dst = fst;
+ for (; line > 0; line--) {
+ dst += bypl;
+ memcpy(dst, fst, width);
+ }
+ }
+
+ vmsvga_update_rect_delayed(s, x, y, w, h);
+ return 0;
+}
+#endif
+
+struct vmsvga_cursor_definition_s {
+ int width;
+ int height;
+ int id;
+ int bpp;
+ int hot_x;
+ int hot_y;
+ uint32_t mask[1024];
+ uint32_t image[4096];
+};
+
+#define SVGA_BITMAP_SIZE(w, h) ((((w) + 31) >> 5) * (h))
+#define SVGA_PIXMAP_SIZE(w, h, bpp) (((((w) * (bpp)) + 31) >> 5) * (h))
+
+#ifdef HW_MOUSE_ACCEL
+static inline void vmsvga_cursor_define(struct vmsvga_state_s *s,
+ struct vmsvga_cursor_definition_s *c)
+{
+ QEMUCursor *qc;
+ int i, pixels;
+
+ qc = cursor_alloc(c->width, c->height);
+ qc->hot_x = c->hot_x;
+ qc->hot_y = c->hot_y;
+ switch (c->bpp) {
+ case 1:
+ cursor_set_mono(qc, 0xffffff, 0x000000, (void *)c->image,
+ 1, (void *)c->mask);
+#ifdef DEBUG
+ cursor_print_ascii_art(qc, "vmware/mono");
+#endif
+ break;
+ case 32:
+ /* fill alpha channel from mask, set color to zero */
+ cursor_set_mono(qc, 0x000000, 0x000000, (void *)c->mask,
+ 1, (void *)c->mask);
+ /* add in rgb values */
+ pixels = c->width * c->height;
+ for (i = 0; i < pixels; i++) {
+ qc->data[i] |= c->image[i] & 0xffffff;
+ }
+#ifdef DEBUG
+ cursor_print_ascii_art(qc, "vmware/32bit");
+#endif
+ break;
+ default:
+ fprintf(stderr, "%s: unhandled bpp %d, using fallback cursor\n",
+ __func__, c->bpp);
+ cursor_put(qc);
+ qc = cursor_builtin_left_ptr();
+ }
+
+ dpy_cursor_define(s->vga.con, qc);
+ cursor_put(qc);
+}
+#endif
+
+#define CMD(f) le32_to_cpu(s->cmd->f)
+
+static inline int vmsvga_fifo_length(struct vmsvga_state_s *s)
+{
+ int num;
+
+ if (!s->config || !s->enable) {
+ return 0;
+ }
+ num = CMD(next_cmd) - CMD(stop);
+ if (num < 0) {
+ num += CMD(max) - CMD(min);
+ }
+ return num >> 2;
+}
+
+static inline uint32_t vmsvga_fifo_read_raw(struct vmsvga_state_s *s)
+{
+ uint32_t cmd = s->fifo[CMD(stop) >> 2];
+
+ s->cmd->stop = cpu_to_le32(CMD(stop) + 4);
+ if (CMD(stop) >= CMD(max)) {
+ s->cmd->stop = s->cmd->min;
+ }
+ return cmd;
+}
+
+static inline uint32_t vmsvga_fifo_read(struct vmsvga_state_s *s)
+{
+ return le32_to_cpu(vmsvga_fifo_read_raw(s));
+}
+
+static void vmsvga_fifo_run(struct vmsvga_state_s *s)
+{
+ uint32_t cmd, colour;
+ int args, len;
+ int x, y, dx, dy, width, height;
+ struct vmsvga_cursor_definition_s cursor;
+ uint32_t cmd_start;
+
+ len = vmsvga_fifo_length(s);
+ while (len > 0) {
+ /* May need to go back to the start of the command if incomplete */
+ cmd_start = s->cmd->stop;
+
+ switch (cmd = vmsvga_fifo_read(s)) {
+ case SVGA_CMD_UPDATE:
+ case SVGA_CMD_UPDATE_VERBOSE:
+ len -= 5;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ x = vmsvga_fifo_read(s);
+ y = vmsvga_fifo_read(s);
+ width = vmsvga_fifo_read(s);
+ height = vmsvga_fifo_read(s);
+ vmsvga_update_rect_delayed(s, x, y, width, height);
+ break;
+
+ case SVGA_CMD_RECT_FILL:
+ len -= 6;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ colour = vmsvga_fifo_read(s);
+ x = vmsvga_fifo_read(s);
+ y = vmsvga_fifo_read(s);
+ width = vmsvga_fifo_read(s);
+ height = vmsvga_fifo_read(s);
+#ifdef HW_FILL_ACCEL
+ if (vmsvga_fill_rect(s, colour, x, y, width, height) == 0) {
+ break;
+ }
+#endif
+ args = 0;
+ goto badcmd;
+
+ case SVGA_CMD_RECT_COPY:
+ len -= 7;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ x = vmsvga_fifo_read(s);
+ y = vmsvga_fifo_read(s);
+ dx = vmsvga_fifo_read(s);
+ dy = vmsvga_fifo_read(s);
+ width = vmsvga_fifo_read(s);
+ height = vmsvga_fifo_read(s);
+#ifdef HW_RECT_ACCEL
+ if (vmsvga_copy_rect(s, x, y, dx, dy, width, height) == 0) {
+ break;
+ }
+#endif
+ args = 0;
+ goto badcmd;
+
+ case SVGA_CMD_DEFINE_CURSOR:
+ len -= 8;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ cursor.id = vmsvga_fifo_read(s);
+ cursor.hot_x = vmsvga_fifo_read(s);
+ cursor.hot_y = vmsvga_fifo_read(s);
+ cursor.width = x = vmsvga_fifo_read(s);
+ cursor.height = y = vmsvga_fifo_read(s);
+ vmsvga_fifo_read(s);
+ cursor.bpp = vmsvga_fifo_read(s);
+
+ args = SVGA_BITMAP_SIZE(x, y) + SVGA_PIXMAP_SIZE(x, y, cursor.bpp);
+ if (SVGA_BITMAP_SIZE(x, y) > sizeof cursor.mask ||
+ SVGA_PIXMAP_SIZE(x, y, cursor.bpp) > sizeof cursor.image) {
+ goto badcmd;
+ }
+
+ len -= args;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ for (args = 0; args < SVGA_BITMAP_SIZE(x, y); args++) {
+ cursor.mask[args] = vmsvga_fifo_read_raw(s);
+ }
+ for (args = 0; args < SVGA_PIXMAP_SIZE(x, y, cursor.bpp); args++) {
+ cursor.image[args] = vmsvga_fifo_read_raw(s);
+ }
+#ifdef HW_MOUSE_ACCEL
+ vmsvga_cursor_define(s, &cursor);
+ break;
+#else
+ args = 0;
+ goto badcmd;
+#endif
+
+ /*
+ * Other commands that we at least know the number of arguments
+ * for so we can avoid FIFO desync if driver uses them illegally.
+ */
+ case SVGA_CMD_DEFINE_ALPHA_CURSOR:
+ len -= 6;
+ if (len < 0) {
+ goto rewind;
+ }
+ vmsvga_fifo_read(s);
+ vmsvga_fifo_read(s);
+ vmsvga_fifo_read(s);
+ x = vmsvga_fifo_read(s);
+ y = vmsvga_fifo_read(s);
+ args = x * y;
+ goto badcmd;
+ case SVGA_CMD_RECT_ROP_FILL:
+ args = 6;
+ goto badcmd;
+ case SVGA_CMD_RECT_ROP_COPY:
+ args = 7;
+ goto badcmd;
+ case SVGA_CMD_DRAW_GLYPH_CLIPPED:
+ len -= 4;
+ if (len < 0) {
+ goto rewind;
+ }
+ vmsvga_fifo_read(s);
+ vmsvga_fifo_read(s);
+ args = 7 + (vmsvga_fifo_read(s) >> 2);
+ goto badcmd;
+ case SVGA_CMD_SURFACE_ALPHA_BLEND:
+ args = 12;
+ goto badcmd;
+
+ /*
+ * Other commands that are not listed as depending on any
+ * CAPABILITIES bits, but are not described in the README either.
+ */
+ case SVGA_CMD_SURFACE_FILL:
+ case SVGA_CMD_SURFACE_COPY:
+ case SVGA_CMD_FRONT_ROP_FILL:
+ case SVGA_CMD_FENCE:
+ case SVGA_CMD_INVALID_CMD:
+ break; /* Nop */
+
+ default:
+ args = 0;
+ badcmd:
+ len -= args;
+ if (len < 0) {
+ goto rewind;
+ }
+ while (args--) {
+ vmsvga_fifo_read(s);
+ }
+ printf("%s: Unknown command 0x%02x in SVGA command FIFO\n",
+ __func__, cmd);
+ break;
+
+ rewind:
+ s->cmd->stop = cmd_start;
+ break;
+ }
+ }
+
+ s->syncing = 0;
+}
+
+static uint32_t vmsvga_index_read(void *opaque, uint32_t address)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ return s->index;
+}
+
+static void vmsvga_index_write(void *opaque, uint32_t address, uint32_t index)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ s->index = index;
+}
+
+static uint32_t vmsvga_value_read(void *opaque, uint32_t address)
+{
+ uint32_t caps;
+ struct vmsvga_state_s *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+ PixelFormat pf;
+ uint32_t ret;
+
+ switch (s->index) {
+ case SVGA_REG_ID:
+ ret = s->svgaid;
+ break;
+
+ case SVGA_REG_ENABLE:
+ ret = s->enable;
+ break;
+
+ case SVGA_REG_WIDTH:
+ ret = s->new_width ? s->new_width : surface_width(surface);
+ break;
+
+ case SVGA_REG_HEIGHT:
+ ret = s->new_height ? s->new_height : surface_height(surface);
+ break;
+
+ case SVGA_REG_MAX_WIDTH:
+ ret = SVGA_MAX_WIDTH;
+ break;
+
+ case SVGA_REG_MAX_HEIGHT:
+ ret = SVGA_MAX_HEIGHT;
+ break;
+
+ case SVGA_REG_DEPTH:
+ ret = (s->new_depth == 32) ? 24 : s->new_depth;
+ break;
+
+ case SVGA_REG_BITS_PER_PIXEL:
+ case SVGA_REG_HOST_BITS_PER_PIXEL:
+ ret = s->new_depth;
+ break;
+
+ case SVGA_REG_PSEUDOCOLOR:
+ ret = 0x0;
+ break;
+
+ case SVGA_REG_RED_MASK:
+ pf = qemu_default_pixelformat(s->new_depth);
+ ret = pf.rmask;
+ break;
+
+ case SVGA_REG_GREEN_MASK:
+ pf = qemu_default_pixelformat(s->new_depth);
+ ret = pf.gmask;
+ break;
+
+ case SVGA_REG_BLUE_MASK:
+ pf = qemu_default_pixelformat(s->new_depth);
+ ret = pf.bmask;
+ break;
+
+ case SVGA_REG_BYTES_PER_LINE:
+ if (s->new_width) {
+ ret = (s->new_depth * s->new_width) / 8;
+ } else {
+ ret = surface_stride(surface);
+ }
+ break;
+
+ case SVGA_REG_FB_START: {
+ struct pci_vmsvga_state_s *pci_vmsvga
+ = container_of(s, struct pci_vmsvga_state_s, chip);
+ ret = pci_get_bar_addr(PCI_DEVICE(pci_vmsvga), 1);
+ break;
+ }
+
+ case SVGA_REG_FB_OFFSET:
+ ret = 0x0;
+ break;
+
+ case SVGA_REG_VRAM_SIZE:
+ ret = s->vga.vram_size; /* No physical VRAM besides the framebuffer */
+ break;
+
+ case SVGA_REG_FB_SIZE:
+ ret = s->vga.vram_size;
+ break;
+
+ case SVGA_REG_CAPABILITIES:
+ caps = SVGA_CAP_NONE;
+#ifdef HW_RECT_ACCEL
+ caps |= SVGA_CAP_RECT_COPY;
+#endif
+#ifdef HW_FILL_ACCEL
+ caps |= SVGA_CAP_RECT_FILL;
+#endif
+#ifdef HW_MOUSE_ACCEL
+ if (dpy_cursor_define_supported(s->vga.con)) {
+ caps |= SVGA_CAP_CURSOR | SVGA_CAP_CURSOR_BYPASS_2 |
+ SVGA_CAP_CURSOR_BYPASS;
+ }
+#endif
+ ret = caps;
+ break;
+
+ case SVGA_REG_MEM_START: {
+ struct pci_vmsvga_state_s *pci_vmsvga
+ = container_of(s, struct pci_vmsvga_state_s, chip);
+ ret = pci_get_bar_addr(PCI_DEVICE(pci_vmsvga), 2);
+ break;
+ }
+
+ case SVGA_REG_MEM_SIZE:
+ ret = s->fifo_size;
+ break;
+
+ case SVGA_REG_CONFIG_DONE:
+ ret = s->config;
+ break;
+
+ case SVGA_REG_SYNC:
+ case SVGA_REG_BUSY:
+ ret = s->syncing;
+ break;
+
+ case SVGA_REG_GUEST_ID:
+ ret = s->guest;
+ break;
+
+ case SVGA_REG_CURSOR_ID:
+ ret = s->cursor.id;
+ break;
+
+ case SVGA_REG_CURSOR_X:
+ ret = s->cursor.x;
+ break;
+
+ case SVGA_REG_CURSOR_Y:
+ ret = s->cursor.y;
+ break;
+
+ case SVGA_REG_CURSOR_ON:
+ ret = s->cursor.on;
+ break;
+
+ case SVGA_REG_SCRATCH_SIZE:
+ ret = s->scratch_size;
+ break;
+
+ case SVGA_REG_MEM_REGS:
+ case SVGA_REG_NUM_DISPLAYS:
+ case SVGA_REG_PITCHLOCK:
+ case SVGA_PALETTE_BASE ... SVGA_PALETTE_END:
+ ret = 0;
+ break;
+
+ default:
+ if (s->index >= SVGA_SCRATCH_BASE &&
+ s->index < SVGA_SCRATCH_BASE + s->scratch_size) {
+ ret = s->scratch[s->index - SVGA_SCRATCH_BASE];
+ break;
+ }
+ printf("%s: Bad register %02x\n", __func__, s->index);
+ ret = 0;
+ break;
+ }
+
+ if (s->index >= SVGA_SCRATCH_BASE) {
+ trace_vmware_scratch_read(s->index, ret);
+ } else if (s->index >= SVGA_PALETTE_BASE) {
+ trace_vmware_palette_read(s->index, ret);
+ } else {
+ trace_vmware_value_read(s->index, ret);
+ }
+ return ret;
+}
+
+static void vmsvga_value_write(void *opaque, uint32_t address, uint32_t value)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ if (s->index >= SVGA_SCRATCH_BASE) {
+ trace_vmware_scratch_write(s->index, value);
+ } else if (s->index >= SVGA_PALETTE_BASE) {
+ trace_vmware_palette_write(s->index, value);
+ } else {
+ trace_vmware_value_write(s->index, value);
+ }
+ switch (s->index) {
+ case SVGA_REG_ID:
+ if (value == SVGA_ID_2 || value == SVGA_ID_1 || value == SVGA_ID_0) {
+ s->svgaid = value;
+ }
+ break;
+
+ case SVGA_REG_ENABLE:
+ s->enable = !!value;
+ s->invalidated = 1;
+ s->vga.hw_ops->invalidate(&s->vga);
+ if (s->enable && s->config) {
+ vga_dirty_log_stop(&s->vga);
+ } else {
+ vga_dirty_log_start(&s->vga);
+ }
+ break;
+
+ case SVGA_REG_WIDTH:
+ if (value <= SVGA_MAX_WIDTH) {
+ s->new_width = value;
+ s->invalidated = 1;
+ } else {
+ printf("%s: Bad width: %i\n", __func__, value);
+ }
+ break;
+
+ case SVGA_REG_HEIGHT:
+ if (value <= SVGA_MAX_HEIGHT) {
+ s->new_height = value;
+ s->invalidated = 1;
+ } else {
+ printf("%s: Bad height: %i\n", __func__, value);
+ }
+ break;
+
+ case SVGA_REG_BITS_PER_PIXEL:
+ if (value != 32) {
+ printf("%s: Bad bits per pixel: %i bits\n", __func__, value);
+ s->config = 0;
+ s->invalidated = 1;
+ }
+ break;
+
+ case SVGA_REG_CONFIG_DONE:
+ if (value) {
+ s->fifo = (uint32_t *) s->fifo_ptr;
+ /* Check range and alignment. */
+ if ((CMD(min) | CMD(max) | CMD(next_cmd) | CMD(stop)) & 3) {
+ break;
+ }
+ if (CMD(min) < (uint8_t *) s->cmd->fifo - (uint8_t *) s->fifo) {
+ break;
+ }
+ if (CMD(max) > SVGA_FIFO_SIZE) {
+ break;
+ }
+ if (CMD(max) < CMD(min) + 10 * 1024) {
+ break;
+ }
+ vga_dirty_log_stop(&s->vga);
+ }
+ s->config = !!value;
+ break;
+
+ case SVGA_REG_SYNC:
+ s->syncing = 1;
+ vmsvga_fifo_run(s); /* Or should we just wait for update_display? */
+ break;
+
+ case SVGA_REG_GUEST_ID:
+ s->guest = value;
+#ifdef VERBOSE
+ if (value >= GUEST_OS_BASE && value < GUEST_OS_BASE +
+ ARRAY_SIZE(vmsvga_guest_id)) {
+ printf("%s: guest runs %s.\n", __func__,
+ vmsvga_guest_id[value - GUEST_OS_BASE]);
+ }
+#endif
+ break;
+
+ case SVGA_REG_CURSOR_ID:
+ s->cursor.id = value;
+ break;
+
+ case SVGA_REG_CURSOR_X:
+ s->cursor.x = value;
+ break;
+
+ case SVGA_REG_CURSOR_Y:
+ s->cursor.y = value;
+ break;
+
+ case SVGA_REG_CURSOR_ON:
+ s->cursor.on |= (value == SVGA_CURSOR_ON_SHOW);
+ s->cursor.on &= (value != SVGA_CURSOR_ON_HIDE);
+#ifdef HW_MOUSE_ACCEL
+ if (value <= SVGA_CURSOR_ON_SHOW) {
+ dpy_mouse_set(s->vga.con, s->cursor.x, s->cursor.y, s->cursor.on);
+ }
+#endif
+ break;
+
+ case SVGA_REG_DEPTH:
+ case SVGA_REG_MEM_REGS:
+ case SVGA_REG_NUM_DISPLAYS:
+ case SVGA_REG_PITCHLOCK:
+ case SVGA_PALETTE_BASE ... SVGA_PALETTE_END:
+ break;
+
+ default:
+ if (s->index >= SVGA_SCRATCH_BASE &&
+ s->index < SVGA_SCRATCH_BASE + s->scratch_size) {
+ s->scratch[s->index - SVGA_SCRATCH_BASE] = value;
+ break;
+ }
+ printf("%s: Bad register %02x\n", __func__, s->index);
+ }
+}
+
+static uint32_t vmsvga_bios_read(void *opaque, uint32_t address)
+{
+ printf("%s: what are we supposed to return?\n", __func__);
+ return 0xcafe;
+}
+
+static void vmsvga_bios_write(void *opaque, uint32_t address, uint32_t data)
+{
+ printf("%s: what are we supposed to do with (%08x)?\n", __func__, data);
+}
+
+static inline void vmsvga_check_size(struct vmsvga_state_s *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+
+ if (s->new_width != surface_width(surface) ||
+ s->new_height != surface_height(surface) ||
+ s->new_depth != surface_bits_per_pixel(surface)) {
+ int stride = (s->new_depth * s->new_width) / 8;
+ pixman_format_code_t format =
+ qemu_default_pixman_format(s->new_depth, true);
+ trace_vmware_setmode(s->new_width, s->new_height, s->new_depth);
+ surface = qemu_create_displaysurface_from(s->new_width, s->new_height,
+ format, stride,
+ s->vga.vram_ptr);
+ dpy_gfx_replace_surface(s->vga.con, surface);
+ s->invalidated = 1;
+ }
+}
+
+static void vmsvga_update_display(void *opaque)
+{
+ struct vmsvga_state_s *s = opaque;
+ DisplaySurface *surface;
+ bool dirty = false;
+
+ if (!s->enable) {
+ s->vga.hw_ops->gfx_update(&s->vga);
+ return;
+ }
+
+ vmsvga_check_size(s);
+ surface = qemu_console_surface(s->vga.con);
+
+ vmsvga_fifo_run(s);
+ vmsvga_update_rect_flush(s);
+
+ /*
+ * Is it more efficient to look at vram VGA-dirty bits or wait
+ * for the driver to issue SVGA_CMD_UPDATE?
+ */
+ if (memory_region_is_logging(&s->vga.vram, DIRTY_MEMORY_VGA)) {
+ vga_sync_dirty_bitmap(&s->vga);
+ dirty = memory_region_get_dirty(&s->vga.vram, 0,
+ surface_stride(surface) * surface_height(surface),
+ DIRTY_MEMORY_VGA);
+ }
+ if (s->invalidated || dirty) {
+ s->invalidated = 0;
+ dpy_gfx_update(s->vga.con, 0, 0,
+ surface_width(surface), surface_height(surface));
+ }
+ if (dirty) {
+ memory_region_reset_dirty(&s->vga.vram, 0,
+ surface_stride(surface) * surface_height(surface),
+ DIRTY_MEMORY_VGA);
+ }
+}
+
+static void vmsvga_reset(DeviceState *dev)
+{
+ struct pci_vmsvga_state_s *pci = VMWARE_SVGA(dev);
+ struct vmsvga_state_s *s = &pci->chip;
+
+ s->index = 0;
+ s->enable = 0;
+ s->config = 0;
+ s->svgaid = SVGA_ID;
+ s->cursor.on = 0;
+ s->redraw_fifo_first = 0;
+ s->redraw_fifo_last = 0;
+ s->syncing = 0;
+
+ vga_dirty_log_start(&s->vga);
+}
+
+static void vmsvga_invalidate_display(void *opaque)
+{
+ struct vmsvga_state_s *s = opaque;
+ if (!s->enable) {
+ s->vga.hw_ops->invalidate(&s->vga);
+ return;
+ }
+
+ s->invalidated = 1;
+}
+
+static void vmsvga_text_update(void *opaque, console_ch_t *chardata)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ if (s->vga.hw_ops->text_update) {
+ s->vga.hw_ops->text_update(&s->vga, chardata);
+ }
+}
+
+static int vmsvga_post_load(void *opaque, int version_id)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ s->invalidated = 1;
+ if (s->config) {
+ s->fifo = (uint32_t *) s->fifo_ptr;
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_vmware_vga_internal = {
+ .name = "vmware_vga_internal",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = vmsvga_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_EQUAL(new_depth, struct vmsvga_state_s),
+ VMSTATE_INT32(enable, struct vmsvga_state_s),
+ VMSTATE_INT32(config, struct vmsvga_state_s),
+ VMSTATE_INT32(cursor.id, struct vmsvga_state_s),
+ VMSTATE_INT32(cursor.x, struct vmsvga_state_s),
+ VMSTATE_INT32(cursor.y, struct vmsvga_state_s),
+ VMSTATE_INT32(cursor.on, struct vmsvga_state_s),
+ VMSTATE_INT32(index, struct vmsvga_state_s),
+ VMSTATE_VARRAY_INT32(scratch, struct vmsvga_state_s,
+ scratch_size, 0, vmstate_info_uint32, uint32_t),
+ VMSTATE_INT32(new_width, struct vmsvga_state_s),
+ VMSTATE_INT32(new_height, struct vmsvga_state_s),
+ VMSTATE_UINT32(guest, struct vmsvga_state_s),
+ VMSTATE_UINT32(svgaid, struct vmsvga_state_s),
+ VMSTATE_INT32(syncing, struct vmsvga_state_s),
+ VMSTATE_UNUSED(4), /* was fb_size */
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_vmware_vga = {
+ .name = "vmware_vga",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, struct pci_vmsvga_state_s),
+ VMSTATE_STRUCT(chip, struct pci_vmsvga_state_s, 0,
+ vmstate_vmware_vga_internal, struct vmsvga_state_s),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps vmsvga_ops = {
+ .invalidate = vmsvga_invalidate_display,
+ .gfx_update = vmsvga_update_display,
+ .text_update = vmsvga_text_update,
+};
+
+static void vmsvga_init(DeviceState *dev, struct vmsvga_state_s *s,
+ MemoryRegion *address_space, MemoryRegion *io)
+{
+ s->scratch_size = SVGA_SCRATCH_SIZE;
+ s->scratch = g_malloc(s->scratch_size * 4);
+
+ s->vga.con = graphic_console_init(dev, 0, &vmsvga_ops, s);
+
+ s->fifo_size = SVGA_FIFO_SIZE;
+ memory_region_init_ram(&s->fifo_ram, NULL, "vmsvga.fifo", s->fifo_size,
+ &error_abort);
+ vmstate_register_ram_global(&s->fifo_ram);
+ s->fifo_ptr = memory_region_get_ram_ptr(&s->fifo_ram);
+
+ vga_common_init(&s->vga, OBJECT(dev), true);
+ vga_init(&s->vga, OBJECT(dev), address_space, io, true);
+ vmstate_register(NULL, 0, &vmstate_vga_common, &s->vga);
+ s->new_depth = 32;
+}
+
+static uint64_t vmsvga_io_read(void *opaque, hwaddr addr, unsigned size)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ switch (addr) {
+ case SVGA_IO_MUL * SVGA_INDEX_PORT: return vmsvga_index_read(s, addr);
+ case SVGA_IO_MUL * SVGA_VALUE_PORT: return vmsvga_value_read(s, addr);
+ case SVGA_IO_MUL * SVGA_BIOS_PORT: return vmsvga_bios_read(s, addr);
+ default: return -1u;
+ }
+}
+
+static void vmsvga_io_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ switch (addr) {
+ case SVGA_IO_MUL * SVGA_INDEX_PORT:
+ vmsvga_index_write(s, addr, data);
+ break;
+ case SVGA_IO_MUL * SVGA_VALUE_PORT:
+ vmsvga_value_write(s, addr, data);
+ break;
+ case SVGA_IO_MUL * SVGA_BIOS_PORT:
+ vmsvga_bios_write(s, addr, data);
+ break;
+ }
+}
+
+static const MemoryRegionOps vmsvga_io_ops = {
+ .read = vmsvga_io_read,
+ .write = vmsvga_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = true,
+ },
+ .impl = {
+ .unaligned = true,
+ },
+};
+
+static void pci_vmsvga_realize(PCIDevice *dev, Error **errp)
+{
+ struct pci_vmsvga_state_s *s = VMWARE_SVGA(dev);
+
+ dev->config[PCI_CACHE_LINE_SIZE] = 0x08;
+ dev->config[PCI_LATENCY_TIMER] = 0x40;
+ dev->config[PCI_INTERRUPT_LINE] = 0xff; /* End */
+
+ memory_region_init_io(&s->io_bar, NULL, &vmsvga_io_ops, &s->chip,
+ "vmsvga-io", 0x10);
+ memory_region_set_flush_coalesced(&s->io_bar);
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar);
+
+ vmsvga_init(DEVICE(dev), &s->chip,
+ pci_address_space(dev), pci_address_space_io(dev));
+
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &s->chip.vga.vram);
+ pci_register_bar(dev, 2, PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &s->chip.fifo_ram);
+
+ if (!dev->rom_bar) {
+ /* compatibility with pc-0.13 and older */
+ vga_init_vbe(&s->chip.vga, OBJECT(dev), pci_address_space(dev));
+ }
+}
+
+static Property vga_vmware_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", struct pci_vmsvga_state_s,
+ chip.vga.vram_size_mb, 16),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vmsvga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_vmsvga_realize;
+ k->romfile = "vgabios-vmware.bin";
+ k->vendor_id = PCI_VENDOR_ID_VMWARE;
+ k->device_id = SVGA_PCI_DEVICE_ID;
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ k->subsystem_vendor_id = PCI_VENDOR_ID_VMWARE;
+ k->subsystem_id = SVGA_PCI_DEVICE_ID;
+ dc->reset = vmsvga_reset;
+ dc->vmsd = &vmstate_vmware_vga;
+ dc->props = vga_vmware_properties;
+ dc->hotpluggable = false;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo vmsvga_info = {
+ .name = TYPE_VMWARE_SVGA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(struct pci_vmsvga_state_s),
+ .class_init = vmsvga_class_init,
+};
+
+static void vmsvga_register_types(void)
+{
+ type_register_static(&vmsvga_info);
+}
+
+type_init(vmsvga_register_types)
diff --git a/hw/display/xenfb.c b/hw/display/xenfb.c
new file mode 100644
index 00000000..5e324ef6
--- /dev/null
+++ b/hw/display/xenfb.c
@@ -0,0 +1,1014 @@
+/*
+ * xen paravirt framebuffer backend
+ *
+ * Copyright IBM, Corp. 2005-2006
+ * Copyright Red Hat, Inc. 2006-2008
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>,
+ * Markus Armbruster <armbru@redhat.com>,
+ * Daniel P. Berrange <berrange@redhat.com>,
+ * Pat Campbell <plc@novell.com>,
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "sysemu/char.h"
+#include "hw/xen/xen_backend.h"
+
+#include <xen/event_channel.h>
+#include <xen/io/fbif.h>
+#include <xen/io/kbdif.h>
+#include <xen/io/protocols.h>
+
+#include "trace.h"
+
+#ifndef BTN_LEFT
+#define BTN_LEFT 0x110 /* from <linux/input.h> */
+#endif
+
+/* -------------------------------------------------------------------- */
+
+struct common {
+ struct XenDevice xendev; /* must be first */
+ void *page;
+ QemuConsole *con;
+};
+
+struct XenInput {
+ struct common c;
+ int abs_pointer_wanted; /* Whether guest supports absolute pointer */
+ int button_state; /* Last seen pointer button state */
+ int extended;
+ QEMUPutMouseEntry *qmouse;
+};
+
+#define UP_QUEUE 8
+
+struct XenFB {
+ struct common c;
+ size_t fb_len;
+ int row_stride;
+ int depth;
+ int width;
+ int height;
+ int offset;
+ void *pixels;
+ int fbpages;
+ int feature_update;
+ int bug_trigger;
+ int have_console;
+ int do_resize;
+
+ struct {
+ int x,y,w,h;
+ } up_rects[UP_QUEUE];
+ int up_count;
+ int up_fullscreen;
+};
+
+/* -------------------------------------------------------------------- */
+
+static int common_bind(struct common *c)
+{
+ uint64_t mfn;
+
+ if (xenstore_read_fe_uint64(&c->xendev, "page-ref", &mfn) == -1)
+ return -1;
+ assert(mfn == (xen_pfn_t)mfn);
+
+ if (xenstore_read_fe_int(&c->xendev, "event-channel", &c->xendev.remote_port) == -1)
+ return -1;
+
+ c->page = xc_map_foreign_range(xen_xc, c->xendev.dom,
+ XC_PAGE_SIZE,
+ PROT_READ | PROT_WRITE, mfn);
+ if (c->page == NULL)
+ return -1;
+
+ xen_be_bind_evtchn(&c->xendev);
+ xen_be_printf(&c->xendev, 1, "ring mfn %"PRIx64", remote-port %d, local-port %d\n",
+ mfn, c->xendev.remote_port, c->xendev.local_port);
+
+ return 0;
+}
+
+static void common_unbind(struct common *c)
+{
+ xen_be_unbind_evtchn(&c->xendev);
+ if (c->page) {
+ munmap(c->page, XC_PAGE_SIZE);
+ c->page = NULL;
+ }
+}
+
+/* -------------------------------------------------------------------- */
+
+#if 0
+/*
+ * These two tables are not needed any more, but left in here
+ * intentionally as documentation, to show how scancode2linux[]
+ * was generated.
+ *
+ * Tables to map from scancode to Linux input layer keycode.
+ * Scancodes are hardware-specific. These maps assumes a
+ * standard AT or PS/2 keyboard which is what QEMU feeds us.
+ */
+const unsigned char atkbd_set2_keycode[512] = {
+
+ 0, 67, 65, 63, 61, 59, 60, 88, 0, 68, 66, 64, 62, 15, 41,117,
+ 0, 56, 42, 93, 29, 16, 2, 0, 0, 0, 44, 31, 30, 17, 3, 0,
+ 0, 46, 45, 32, 18, 5, 4, 95, 0, 57, 47, 33, 20, 19, 6,183,
+ 0, 49, 48, 35, 34, 21, 7,184, 0, 0, 50, 36, 22, 8, 9,185,
+ 0, 51, 37, 23, 24, 11, 10, 0, 0, 52, 53, 38, 39, 25, 12, 0,
+ 0, 89, 40, 0, 26, 13, 0, 0, 58, 54, 28, 27, 0, 43, 0, 85,
+ 0, 86, 91, 90, 92, 0, 14, 94, 0, 79,124, 75, 71,121, 0, 0,
+ 82, 83, 80, 76, 77, 72, 1, 69, 87, 78, 81, 74, 55, 73, 70, 99,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 217,100,255, 0, 97,165, 0, 0,156, 0, 0, 0, 0, 0, 0,125,
+ 173,114, 0,113, 0, 0, 0,126,128, 0, 0,140, 0, 0, 0,127,
+ 159, 0,115, 0,164, 0, 0,116,158, 0,150,166, 0, 0, 0,142,
+ 157, 0, 0, 0, 0, 0, 0, 0,155, 0, 98, 0, 0,163, 0, 0,
+ 226, 0, 0, 0, 0, 0, 0, 0, 0,255, 96, 0, 0, 0,143, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,107, 0,105,102, 0, 0,112,
+ 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0,
+
+};
+
+const unsigned char atkbd_unxlate_table[128] = {
+
+ 0,118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85,102, 13,
+ 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27,
+ 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42,
+ 50, 49, 58, 65, 73, 74, 89,124, 17, 41, 88, 5, 6, 4, 12, 3,
+ 11, 2, 10, 1, 9,119,126,108,117,125,123,107,115,116,121,105,
+ 114,122,112,113,127, 96, 97,120, 7, 15, 23, 31, 39, 47, 55, 63,
+ 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87,111,
+ 19, 25, 57, 81, 83, 92, 95, 98, 99,100,101,103,104,106,109,110
+
+};
+#endif
+
+/*
+ * for (i = 0; i < 128; i++) {
+ * scancode2linux[i] = atkbd_set2_keycode[atkbd_unxlate_table[i]];
+ * scancode2linux[i | 0x80] = atkbd_set2_keycode[atkbd_unxlate_table[i] | 0x80];
+ * }
+ */
+static const unsigned char scancode2linux[512] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 99, 0, 86, 87, 88,117, 0, 0, 95,183,184,185,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 93, 0, 0, 89, 0, 0, 85, 91, 90, 92, 0, 94, 0,124,121, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 165, 0, 0, 0, 0, 0, 0, 0, 0,163, 0, 0, 96, 97, 0, 0,
+ 113,140,164, 0,166, 0, 0, 0, 0, 0,255, 0, 0, 0,114, 0,
+ 115, 0,150, 0, 0, 98,255, 99,100, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,119,119,102,103,104, 0,105,112,106,118,107,
+ 108,109,110,111, 0, 0, 0, 0, 0, 0, 0,125,126,127,116,142,
+ 0, 0, 0,143, 0,217,156,173,128,159,158,157,155,226, 0,112,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+/* Send an event to the keyboard frontend driver */
+static int xenfb_kbd_event(struct XenInput *xenfb,
+ union xenkbd_in_event *event)
+{
+ struct xenkbd_page *page = xenfb->c.page;
+ uint32_t prod;
+
+ if (xenfb->c.xendev.be_state != XenbusStateConnected)
+ return 0;
+ if (!page)
+ return 0;
+
+ prod = page->in_prod;
+ if (prod - page->in_cons == XENKBD_IN_RING_LEN) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ xen_mb(); /* ensure ring space available */
+ XENKBD_IN_RING_REF(page, prod) = *event;
+ xen_wmb(); /* ensure ring contents visible */
+ page->in_prod = prod + 1;
+ return xen_be_send_notify(&xenfb->c.xendev);
+}
+
+/* Send a keyboard (or mouse button) event */
+static int xenfb_send_key(struct XenInput *xenfb, bool down, int keycode)
+{
+ union xenkbd_in_event event;
+
+ memset(&event, 0, XENKBD_IN_EVENT_SIZE);
+ event.type = XENKBD_TYPE_KEY;
+ event.key.pressed = down ? 1 : 0;
+ event.key.keycode = keycode;
+
+ return xenfb_kbd_event(xenfb, &event);
+}
+
+/* Send a relative mouse movement event */
+static int xenfb_send_motion(struct XenInput *xenfb,
+ int rel_x, int rel_y, int rel_z)
+{
+ union xenkbd_in_event event;
+
+ memset(&event, 0, XENKBD_IN_EVENT_SIZE);
+ event.type = XENKBD_TYPE_MOTION;
+ event.motion.rel_x = rel_x;
+ event.motion.rel_y = rel_y;
+#if __XEN_LATEST_INTERFACE_VERSION__ >= 0x00030207
+ event.motion.rel_z = rel_z;
+#endif
+
+ return xenfb_kbd_event(xenfb, &event);
+}
+
+/* Send an absolute mouse movement event */
+static int xenfb_send_position(struct XenInput *xenfb,
+ int abs_x, int abs_y, int z)
+{
+ union xenkbd_in_event event;
+
+ memset(&event, 0, XENKBD_IN_EVENT_SIZE);
+ event.type = XENKBD_TYPE_POS;
+ event.pos.abs_x = abs_x;
+ event.pos.abs_y = abs_y;
+#if __XEN_LATEST_INTERFACE_VERSION__ == 0x00030207
+ event.pos.abs_z = z;
+#endif
+#if __XEN_LATEST_INTERFACE_VERSION__ >= 0x00030208
+ event.pos.rel_z = z;
+#endif
+
+ return xenfb_kbd_event(xenfb, &event);
+}
+
+/*
+ * Send a key event from the client to the guest OS
+ * QEMU gives us a raw scancode from an AT / PS/2 style keyboard.
+ * We have to turn this into a Linux Input layer keycode.
+ *
+ * Extra complexity from the fact that with extended scancodes
+ * (like those produced by arrow keys) this method gets called
+ * twice, but we only want to send a single event. So we have to
+ * track the '0xe0' scancode state & collapse the extended keys
+ * as needed.
+ *
+ * Wish we could just send scancodes straight to the guest which
+ * already has code for dealing with this...
+ */
+static void xenfb_key_event(void *opaque, int scancode)
+{
+ struct XenInput *xenfb = opaque;
+ int down = 1;
+
+ if (scancode == 0xe0) {
+ xenfb->extended = 1;
+ return;
+ } else if (scancode & 0x80) {
+ scancode &= 0x7f;
+ down = 0;
+ }
+ if (xenfb->extended) {
+ scancode |= 0x80;
+ xenfb->extended = 0;
+ }
+ xenfb_send_key(xenfb, down, scancode2linux[scancode]);
+}
+
+/*
+ * Send a mouse event from the client to the guest OS
+ *
+ * The QEMU mouse can be in either relative, or absolute mode.
+ * Movement is sent separately from button state, which has to
+ * be encoded as virtual key events. We also don't actually get
+ * given any button up/down events, so have to track changes in
+ * the button state.
+ */
+static void xenfb_mouse_event(void *opaque,
+ int dx, int dy, int dz, int button_state)
+{
+ struct XenInput *xenfb = opaque;
+ DisplaySurface *surface = qemu_console_surface(xenfb->c.con);
+ int dw = surface_width(surface);
+ int dh = surface_height(surface);
+ int i;
+
+ trace_xenfb_mouse_event(opaque, dx, dy, dz, button_state,
+ xenfb->abs_pointer_wanted);
+ if (xenfb->abs_pointer_wanted)
+ xenfb_send_position(xenfb,
+ dx * (dw - 1) / 0x7fff,
+ dy * (dh - 1) / 0x7fff,
+ dz);
+ else
+ xenfb_send_motion(xenfb, dx, dy, dz);
+
+ for (i = 0 ; i < 8 ; i++) {
+ int lastDown = xenfb->button_state & (1 << i);
+ int down = button_state & (1 << i);
+ if (down == lastDown)
+ continue;
+
+ if (xenfb_send_key(xenfb, down, BTN_LEFT+i) < 0)
+ return;
+ }
+ xenfb->button_state = button_state;
+}
+
+static int input_init(struct XenDevice *xendev)
+{
+ xenstore_write_be_int(xendev, "feature-abs-pointer", 1);
+ return 0;
+}
+
+static int input_initialise(struct XenDevice *xendev)
+{
+ struct XenInput *in = container_of(xendev, struct XenInput, c.xendev);
+ int rc;
+
+ if (!in->c.con) {
+ xen_be_printf(xendev, 1, "ds not set (yet)\n");
+ return -1;
+ }
+
+ rc = common_bind(&in->c);
+ if (rc != 0)
+ return rc;
+
+ qemu_add_kbd_event_handler(xenfb_key_event, in);
+ return 0;
+}
+
+static void input_connected(struct XenDevice *xendev)
+{
+ struct XenInput *in = container_of(xendev, struct XenInput, c.xendev);
+
+ if (xenstore_read_fe_int(xendev, "request-abs-pointer",
+ &in->abs_pointer_wanted) == -1) {
+ in->abs_pointer_wanted = 0;
+ }
+
+ if (in->qmouse) {
+ qemu_remove_mouse_event_handler(in->qmouse);
+ }
+ trace_xenfb_input_connected(xendev, in->abs_pointer_wanted);
+ in->qmouse = qemu_add_mouse_event_handler(xenfb_mouse_event, in,
+ in->abs_pointer_wanted,
+ "Xen PVFB Mouse");
+}
+
+static void input_disconnect(struct XenDevice *xendev)
+{
+ struct XenInput *in = container_of(xendev, struct XenInput, c.xendev);
+
+ if (in->qmouse) {
+ qemu_remove_mouse_event_handler(in->qmouse);
+ in->qmouse = NULL;
+ }
+ qemu_add_kbd_event_handler(NULL, NULL);
+ common_unbind(&in->c);
+}
+
+static void input_event(struct XenDevice *xendev)
+{
+ struct XenInput *xenfb = container_of(xendev, struct XenInput, c.xendev);
+ struct xenkbd_page *page = xenfb->c.page;
+
+ /* We don't understand any keyboard events, so just ignore them. */
+ if (page->out_prod == page->out_cons)
+ return;
+ page->out_cons = page->out_prod;
+ xen_be_send_notify(&xenfb->c.xendev);
+}
+
+/* -------------------------------------------------------------------- */
+
+static void xenfb_copy_mfns(int mode, int count, xen_pfn_t *dst, void *src)
+{
+ uint32_t *src32 = src;
+ uint64_t *src64 = src;
+ int i;
+
+ for (i = 0; i < count; i++)
+ dst[i] = (mode == 32) ? src32[i] : src64[i];
+}
+
+static int xenfb_map_fb(struct XenFB *xenfb)
+{
+ struct xenfb_page *page = xenfb->c.page;
+ char *protocol = xenfb->c.xendev.protocol;
+ int n_fbdirs;
+ xen_pfn_t *pgmfns = NULL;
+ xen_pfn_t *fbmfns = NULL;
+ void *map, *pd;
+ int mode, ret = -1;
+
+ /* default to native */
+ pd = page->pd;
+ mode = sizeof(unsigned long) * 8;
+
+ if (!protocol) {
+ /*
+ * Undefined protocol, some guesswork needed.
+ *
+ * Old frontends which don't set the protocol use
+ * one page directory only, thus pd[1] must be zero.
+ * pd[1] of the 32bit struct layout and the lower
+ * 32 bits of pd[0] of the 64bit struct layout have
+ * the same location, so we can check that ...
+ */
+ uint32_t *ptr32 = NULL;
+ uint32_t *ptr64 = NULL;
+#if defined(__i386__)
+ ptr32 = (void*)page->pd;
+ ptr64 = ((void*)page->pd) + 4;
+#elif defined(__x86_64__)
+ ptr32 = ((void*)page->pd) - 4;
+ ptr64 = (void*)page->pd;
+#endif
+ if (ptr32) {
+ if (ptr32[1] == 0) {
+ mode = 32;
+ pd = ptr32;
+ } else {
+ mode = 64;
+ pd = ptr64;
+ }
+ }
+#if defined(__x86_64__)
+ } else if (strcmp(protocol, XEN_IO_PROTO_ABI_X86_32) == 0) {
+ /* 64bit dom0, 32bit domU */
+ mode = 32;
+ pd = ((void*)page->pd) - 4;
+#elif defined(__i386__)
+ } else if (strcmp(protocol, XEN_IO_PROTO_ABI_X86_64) == 0) {
+ /* 32bit dom0, 64bit domU */
+ mode = 64;
+ pd = ((void*)page->pd) + 4;
+#endif
+ }
+
+ if (xenfb->pixels) {
+ munmap(xenfb->pixels, xenfb->fbpages * XC_PAGE_SIZE);
+ xenfb->pixels = NULL;
+ }
+
+ xenfb->fbpages = (xenfb->fb_len + (XC_PAGE_SIZE - 1)) / XC_PAGE_SIZE;
+ n_fbdirs = xenfb->fbpages * mode / 8;
+ n_fbdirs = (n_fbdirs + (XC_PAGE_SIZE - 1)) / XC_PAGE_SIZE;
+
+ pgmfns = g_malloc0(sizeof(xen_pfn_t) * n_fbdirs);
+ fbmfns = g_malloc0(sizeof(xen_pfn_t) * xenfb->fbpages);
+
+ xenfb_copy_mfns(mode, n_fbdirs, pgmfns, pd);
+ map = xc_map_foreign_pages(xen_xc, xenfb->c.xendev.dom,
+ PROT_READ, pgmfns, n_fbdirs);
+ if (map == NULL)
+ goto out;
+ xenfb_copy_mfns(mode, xenfb->fbpages, fbmfns, map);
+ munmap(map, n_fbdirs * XC_PAGE_SIZE);
+
+ xenfb->pixels = xc_map_foreign_pages(xen_xc, xenfb->c.xendev.dom,
+ PROT_READ, fbmfns, xenfb->fbpages);
+ if (xenfb->pixels == NULL)
+ goto out;
+
+ ret = 0; /* all is fine */
+
+out:
+ g_free(pgmfns);
+ g_free(fbmfns);
+ return ret;
+}
+
+static int xenfb_configure_fb(struct XenFB *xenfb, size_t fb_len_lim,
+ int width, int height, int depth,
+ size_t fb_len, int offset, int row_stride)
+{
+ size_t mfn_sz = sizeof(*((struct xenfb_page *)0)->pd);
+ size_t pd_len = sizeof(((struct xenfb_page *)0)->pd) / mfn_sz;
+ size_t fb_pages = pd_len * XC_PAGE_SIZE / mfn_sz;
+ size_t fb_len_max = fb_pages * XC_PAGE_SIZE;
+ int max_width, max_height;
+
+ if (fb_len_lim > fb_len_max) {
+ xen_be_printf(&xenfb->c.xendev, 0, "fb size limit %zu exceeds %zu, corrected\n",
+ fb_len_lim, fb_len_max);
+ fb_len_lim = fb_len_max;
+ }
+ if (fb_len_lim && fb_len > fb_len_lim) {
+ xen_be_printf(&xenfb->c.xendev, 0, "frontend fb size %zu limited to %zu\n",
+ fb_len, fb_len_lim);
+ fb_len = fb_len_lim;
+ }
+ if (depth != 8 && depth != 16 && depth != 24 && depth != 32) {
+ xen_be_printf(&xenfb->c.xendev, 0, "can't handle frontend fb depth %d\n",
+ depth);
+ return -1;
+ }
+ if (row_stride <= 0 || row_stride > fb_len) {
+ xen_be_printf(&xenfb->c.xendev, 0, "invalid frontend stride %d\n", row_stride);
+ return -1;
+ }
+ max_width = row_stride / (depth / 8);
+ if (width < 0 || width > max_width) {
+ xen_be_printf(&xenfb->c.xendev, 0, "invalid frontend width %d limited to %d\n",
+ width, max_width);
+ width = max_width;
+ }
+ if (offset < 0 || offset >= fb_len) {
+ xen_be_printf(&xenfb->c.xendev, 0, "invalid frontend offset %d (max %zu)\n",
+ offset, fb_len - 1);
+ return -1;
+ }
+ max_height = (fb_len - offset) / row_stride;
+ if (height < 0 || height > max_height) {
+ xen_be_printf(&xenfb->c.xendev, 0, "invalid frontend height %d limited to %d\n",
+ height, max_height);
+ height = max_height;
+ }
+ xenfb->fb_len = fb_len;
+ xenfb->row_stride = row_stride;
+ xenfb->depth = depth;
+ xenfb->width = width;
+ xenfb->height = height;
+ xenfb->offset = offset;
+ xenfb->up_fullscreen = 1;
+ xenfb->do_resize = 1;
+ xen_be_printf(&xenfb->c.xendev, 1, "framebuffer %dx%dx%d offset %d stride %d\n",
+ width, height, depth, offset, row_stride);
+ return 0;
+}
+
+/* A convenient function for munging pixels between different depths */
+#define BLT(SRC_T,DST_T,RSB,GSB,BSB,RDB,GDB,BDB) \
+ for (line = y ; line < (y+h) ; line++) { \
+ SRC_T *src = (SRC_T *)(xenfb->pixels \
+ + xenfb->offset \
+ + (line * xenfb->row_stride) \
+ + (x * xenfb->depth / 8)); \
+ DST_T *dst = (DST_T *)(data \
+ + (line * linesize) \
+ + (x * bpp / 8)); \
+ int col; \
+ const int RSS = 32 - (RSB + GSB + BSB); \
+ const int GSS = 32 - (GSB + BSB); \
+ const int BSS = 32 - (BSB); \
+ const uint32_t RSM = (~0U) << (32 - RSB); \
+ const uint32_t GSM = (~0U) << (32 - GSB); \
+ const uint32_t BSM = (~0U) << (32 - BSB); \
+ const int RDS = 32 - (RDB + GDB + BDB); \
+ const int GDS = 32 - (GDB + BDB); \
+ const int BDS = 32 - (BDB); \
+ const uint32_t RDM = (~0U) << (32 - RDB); \
+ const uint32_t GDM = (~0U) << (32 - GDB); \
+ const uint32_t BDM = (~0U) << (32 - BDB); \
+ for (col = x ; col < (x+w) ; col++) { \
+ uint32_t spix = *src; \
+ *dst = (((spix << RSS) & RSM & RDM) >> RDS) | \
+ (((spix << GSS) & GSM & GDM) >> GDS) | \
+ (((spix << BSS) & BSM & BDM) >> BDS); \
+ src = (SRC_T *) ((unsigned long) src + xenfb->depth / 8); \
+ dst = (DST_T *) ((unsigned long) dst + bpp / 8); \
+ } \
+ }
+
+
+/*
+ * This copies data from the guest framebuffer region, into QEMU's
+ * displaysurface. qemu uses 16 or 32 bpp. In case the pv framebuffer
+ * uses something else we must convert and copy, otherwise we can
+ * supply the buffer directly and no thing here.
+ */
+static void xenfb_guest_copy(struct XenFB *xenfb, int x, int y, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(xenfb->c.con);
+ int line, oops = 0;
+ int bpp = surface_bits_per_pixel(surface);
+ int linesize = surface_stride(surface);
+ uint8_t *data = surface_data(surface);
+
+ if (!is_buffer_shared(surface)) {
+ switch (xenfb->depth) {
+ case 8:
+ if (bpp == 16) {
+ BLT(uint8_t, uint16_t, 3, 3, 2, 5, 6, 5);
+ } else if (bpp == 32) {
+ BLT(uint8_t, uint32_t, 3, 3, 2, 8, 8, 8);
+ } else {
+ oops = 1;
+ }
+ break;
+ case 24:
+ if (bpp == 16) {
+ BLT(uint32_t, uint16_t, 8, 8, 8, 5, 6, 5);
+ } else if (bpp == 32) {
+ BLT(uint32_t, uint32_t, 8, 8, 8, 8, 8, 8);
+ } else {
+ oops = 1;
+ }
+ break;
+ default:
+ oops = 1;
+ }
+ }
+ if (oops) /* should not happen */
+ xen_be_printf(&xenfb->c.xendev, 0, "%s: oops: convert %d -> %d bpp?\n",
+ __FUNCTION__, xenfb->depth, bpp);
+
+ dpy_gfx_update(xenfb->c.con, x, y, w, h);
+}
+
+#ifdef XENFB_TYPE_REFRESH_PERIOD
+static int xenfb_queue_full(struct XenFB *xenfb)
+{
+ struct xenfb_page *page = xenfb->c.page;
+ uint32_t cons, prod;
+
+ if (!page)
+ return 1;
+
+ prod = page->in_prod;
+ cons = page->in_cons;
+ return prod - cons == XENFB_IN_RING_LEN;
+}
+
+static void xenfb_send_event(struct XenFB *xenfb, union xenfb_in_event *event)
+{
+ uint32_t prod;
+ struct xenfb_page *page = xenfb->c.page;
+
+ prod = page->in_prod;
+ /* caller ensures !xenfb_queue_full() */
+ xen_mb(); /* ensure ring space available */
+ XENFB_IN_RING_REF(page, prod) = *event;
+ xen_wmb(); /* ensure ring contents visible */
+ page->in_prod = prod + 1;
+
+ xen_be_send_notify(&xenfb->c.xendev);
+}
+
+static void xenfb_send_refresh_period(struct XenFB *xenfb, int period)
+{
+ union xenfb_in_event event;
+
+ memset(&event, 0, sizeof(event));
+ event.type = XENFB_TYPE_REFRESH_PERIOD;
+ event.refresh_period.period = period;
+ xenfb_send_event(xenfb, &event);
+}
+#endif
+
+/*
+ * Periodic update of display.
+ * Also transmit the refresh interval to the frontend.
+ *
+ * Never ever do any qemu display operations
+ * (resize, screen update) outside this function.
+ * Our screen might be inactive. When asked for
+ * an update we know it is active.
+ */
+static void xenfb_update(void *opaque)
+{
+ struct XenFB *xenfb = opaque;
+ DisplaySurface *surface;
+ int i;
+
+ if (xenfb->c.xendev.be_state != XenbusStateConnected)
+ return;
+
+ if (!xenfb->feature_update) {
+ /* we don't get update notifications, thus use the
+ * sledge hammer approach ... */
+ xenfb->up_fullscreen = 1;
+ }
+
+ /* resize if needed */
+ if (xenfb->do_resize) {
+ pixman_format_code_t format;
+
+ xenfb->do_resize = 0;
+ switch (xenfb->depth) {
+ case 16:
+ case 32:
+ /* console.c supported depth -> buffer can be used directly */
+ format = qemu_default_pixman_format(xenfb->depth, true);
+ surface = qemu_create_displaysurface_from
+ (xenfb->width, xenfb->height, format,
+ xenfb->row_stride, xenfb->pixels + xenfb->offset);
+ break;
+ default:
+ /* we must convert stuff */
+ surface = qemu_create_displaysurface(xenfb->width, xenfb->height);
+ break;
+ }
+ dpy_gfx_replace_surface(xenfb->c.con, surface);
+ xen_be_printf(&xenfb->c.xendev, 1, "update: resizing: %dx%d @ %d bpp%s\n",
+ xenfb->width, xenfb->height, xenfb->depth,
+ is_buffer_shared(surface) ? " (shared)" : "");
+ xenfb->up_fullscreen = 1;
+ }
+
+ /* run queued updates */
+ if (xenfb->up_fullscreen) {
+ xen_be_printf(&xenfb->c.xendev, 3, "update: fullscreen\n");
+ xenfb_guest_copy(xenfb, 0, 0, xenfb->width, xenfb->height);
+ } else if (xenfb->up_count) {
+ xen_be_printf(&xenfb->c.xendev, 3, "update: %d rects\n", xenfb->up_count);
+ for (i = 0; i < xenfb->up_count; i++)
+ xenfb_guest_copy(xenfb,
+ xenfb->up_rects[i].x,
+ xenfb->up_rects[i].y,
+ xenfb->up_rects[i].w,
+ xenfb->up_rects[i].h);
+ } else {
+ xen_be_printf(&xenfb->c.xendev, 3, "update: nothing\n");
+ }
+ xenfb->up_count = 0;
+ xenfb->up_fullscreen = 0;
+}
+
+static void xenfb_update_interval(void *opaque, uint64_t interval)
+{
+ struct XenFB *xenfb = opaque;
+
+ if (xenfb->feature_update) {
+#ifdef XENFB_TYPE_REFRESH_PERIOD
+ if (xenfb_queue_full(xenfb)) {
+ return;
+ }
+ xenfb_send_refresh_period(xenfb, interval);
+#endif
+ }
+}
+
+/* QEMU display state changed, so refresh the framebuffer copy */
+static void xenfb_invalidate(void *opaque)
+{
+ struct XenFB *xenfb = opaque;
+ xenfb->up_fullscreen = 1;
+}
+
+static void xenfb_handle_events(struct XenFB *xenfb)
+{
+ uint32_t prod, cons;
+ struct xenfb_page *page = xenfb->c.page;
+
+ prod = page->out_prod;
+ if (prod == page->out_cons)
+ return;
+ xen_rmb(); /* ensure we see ring contents up to prod */
+ for (cons = page->out_cons; cons != prod; cons++) {
+ union xenfb_out_event *event = &XENFB_OUT_RING_REF(page, cons);
+ int x, y, w, h;
+
+ switch (event->type) {
+ case XENFB_TYPE_UPDATE:
+ if (xenfb->up_count == UP_QUEUE)
+ xenfb->up_fullscreen = 1;
+ if (xenfb->up_fullscreen)
+ break;
+ x = MAX(event->update.x, 0);
+ y = MAX(event->update.y, 0);
+ w = MIN(event->update.width, xenfb->width - x);
+ h = MIN(event->update.height, xenfb->height - y);
+ if (w < 0 || h < 0) {
+ xen_be_printf(&xenfb->c.xendev, 1, "bogus update ignored\n");
+ break;
+ }
+ if (x != event->update.x ||
+ y != event->update.y ||
+ w != event->update.width ||
+ h != event->update.height) {
+ xen_be_printf(&xenfb->c.xendev, 1, "bogus update clipped\n");
+ }
+ if (w == xenfb->width && h > xenfb->height / 2) {
+ /* scroll detector: updated more than 50% of the lines,
+ * don't bother keeping track of the rectangles then */
+ xenfb->up_fullscreen = 1;
+ } else {
+ xenfb->up_rects[xenfb->up_count].x = x;
+ xenfb->up_rects[xenfb->up_count].y = y;
+ xenfb->up_rects[xenfb->up_count].w = w;
+ xenfb->up_rects[xenfb->up_count].h = h;
+ xenfb->up_count++;
+ }
+ break;
+#ifdef XENFB_TYPE_RESIZE
+ case XENFB_TYPE_RESIZE:
+ if (xenfb_configure_fb(xenfb, xenfb->fb_len,
+ event->resize.width,
+ event->resize.height,
+ event->resize.depth,
+ xenfb->fb_len,
+ event->resize.offset,
+ event->resize.stride) < 0)
+ break;
+ xenfb_invalidate(xenfb);
+ break;
+#endif
+ }
+ }
+ xen_mb(); /* ensure we're done with ring contents */
+ page->out_cons = cons;
+}
+
+static int fb_init(struct XenDevice *xendev)
+{
+#ifdef XENFB_TYPE_RESIZE
+ xenstore_write_be_int(xendev, "feature-resize", 1);
+#endif
+ return 0;
+}
+
+static int fb_initialise(struct XenDevice *xendev)
+{
+ struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev);
+ struct xenfb_page *fb_page;
+ int videoram;
+ int rc;
+
+ if (xenstore_read_fe_int(xendev, "videoram", &videoram) == -1)
+ videoram = 0;
+
+ rc = common_bind(&fb->c);
+ if (rc != 0)
+ return rc;
+
+ fb_page = fb->c.page;
+ rc = xenfb_configure_fb(fb, videoram * 1024 * 1024U,
+ fb_page->width, fb_page->height, fb_page->depth,
+ fb_page->mem_length, 0, fb_page->line_length);
+ if (rc != 0)
+ return rc;
+
+ rc = xenfb_map_fb(fb);
+ if (rc != 0)
+ return rc;
+
+#if 0 /* handled in xen_init_display() for now */
+ if (!fb->have_console) {
+ fb->c.ds = graphic_console_init(xenfb_update,
+ xenfb_invalidate,
+ NULL,
+ NULL,
+ fb);
+ fb->have_console = 1;
+ }
+#endif
+
+ if (xenstore_read_fe_int(xendev, "feature-update", &fb->feature_update) == -1)
+ fb->feature_update = 0;
+ if (fb->feature_update)
+ xenstore_write_be_int(xendev, "request-update", 1);
+
+ xen_be_printf(xendev, 1, "feature-update=%d, videoram=%d\n",
+ fb->feature_update, videoram);
+ return 0;
+}
+
+static void fb_disconnect(struct XenDevice *xendev)
+{
+ struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev);
+
+ /*
+ * FIXME: qemu can't un-init gfx display (yet?).
+ * Replacing the framebuffer with anonymous shared memory
+ * instead. This releases the guest pages and keeps qemu happy.
+ */
+ fb->pixels = mmap(fb->pixels, fb->fbpages * XC_PAGE_SIZE,
+ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON,
+ -1, 0);
+ if (fb->pixels == MAP_FAILED) {
+ xen_be_printf(xendev, 0,
+ "Couldn't replace the framebuffer with anonymous memory errno=%d\n",
+ errno);
+ }
+ common_unbind(&fb->c);
+ fb->feature_update = 0;
+ fb->bug_trigger = 0;
+}
+
+static void fb_frontend_changed(struct XenDevice *xendev, const char *node)
+{
+ struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev);
+
+ /*
+ * Set state to Connected *again* once the frontend switched
+ * to connected. We must trigger the watch a second time to
+ * workaround a frontend bug.
+ */
+ if (fb->bug_trigger == 0 && strcmp(node, "state") == 0 &&
+ xendev->fe_state == XenbusStateConnected &&
+ xendev->be_state == XenbusStateConnected) {
+ xen_be_printf(xendev, 2, "re-trigger connected (frontend bug)\n");
+ xen_be_set_state(xendev, XenbusStateConnected);
+ fb->bug_trigger = 1; /* only once */
+ }
+}
+
+static void fb_event(struct XenDevice *xendev)
+{
+ struct XenFB *xenfb = container_of(xendev, struct XenFB, c.xendev);
+
+ xenfb_handle_events(xenfb);
+ xen_be_send_notify(&xenfb->c.xendev);
+}
+
+/* -------------------------------------------------------------------- */
+
+struct XenDevOps xen_kbdmouse_ops = {
+ .size = sizeof(struct XenInput),
+ .init = input_init,
+ .initialise = input_initialise,
+ .connected = input_connected,
+ .disconnect = input_disconnect,
+ .event = input_event,
+};
+
+struct XenDevOps xen_framebuffer_ops = {
+ .size = sizeof(struct XenFB),
+ .init = fb_init,
+ .initialise = fb_initialise,
+ .disconnect = fb_disconnect,
+ .event = fb_event,
+ .frontend_changed = fb_frontend_changed,
+};
+
+static const GraphicHwOps xenfb_ops = {
+ .invalidate = xenfb_invalidate,
+ .gfx_update = xenfb_update,
+ .update_interval = xenfb_update_interval,
+};
+
+/*
+ * FIXME/TODO: Kill this.
+ * Temporary needed while DisplayState reorganization is in flight.
+ */
+void xen_init_display(int domid)
+{
+ struct XenDevice *xfb, *xin;
+ struct XenFB *fb;
+ struct XenInput *in;
+ int i = 0;
+
+wait_more:
+ i++;
+ main_loop_wait(true);
+ xfb = xen_be_find_xendev("vfb", domid, 0);
+ xin = xen_be_find_xendev("vkbd", domid, 0);
+ if (!xfb || !xin) {
+ if (i < 256) {
+ usleep(10000);
+ goto wait_more;
+ }
+ xen_be_printf(NULL, 1, "displaystate setup failed\n");
+ return;
+ }
+
+ /* vfb */
+ fb = container_of(xfb, struct XenFB, c.xendev);
+ fb->c.con = graphic_console_init(NULL, 0, &xenfb_ops, fb);
+ fb->have_console = 1;
+
+ /* vkbd */
+ in = container_of(xin, struct XenInput, c.xendev);
+ in->c.con = fb->c.con;
+
+ /* retry ->init() */
+ xen_be_check_state(xin);
+ xen_be_check_state(xfb);
+}
diff --git a/hw/dma/Makefile.objs b/hw/dma/Makefile.objs
new file mode 100644
index 00000000..0e65ed0d
--- /dev/null
+++ b/hw/dma/Makefile.objs
@@ -0,0 +1,13 @@
+common-obj-$(CONFIG_PUV3) += puv3_dma.o
+common-obj-$(CONFIG_RC4030) += rc4030.o
+common-obj-$(CONFIG_PL080) += pl080.o
+common-obj-$(CONFIG_PL330) += pl330.o
+common-obj-$(CONFIG_I82374) += i82374.o
+common-obj-$(CONFIG_I8257) += i8257.o
+common-obj-$(CONFIG_XILINX_AXI) += xilinx_axidma.o
+common-obj-$(CONFIG_ETRAXFS) += etraxfs_dma.o
+common-obj-$(CONFIG_STP2000) += sparc32_dma.o
+common-obj-$(CONFIG_SUN4M) += sun4m_iommu.o
+
+obj-$(CONFIG_OMAP) += omap_dma.o soc_dma.o
+obj-$(CONFIG_PXA2XX) += pxa2xx_dma.o
diff --git a/hw/dma/etraxfs_dma.c b/hw/dma/etraxfs_dma.c
new file mode 100644
index 00000000..35995134
--- /dev/null
+++ b/hw/dma/etraxfs_dma.c
@@ -0,0 +1,781 @@
+/*
+ * QEMU ETRAX DMA Controller.
+ *
+ * Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * 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.
+ */
+#include <stdio.h>
+#include <sys/time.h>
+#include "hw/hw.h"
+#include "exec/address-spaces.h"
+#include "qemu-common.h"
+#include "sysemu/sysemu.h"
+
+#include "hw/cris/etraxfs_dma.h"
+
+#define D(x)
+
+#define RW_DATA (0x0 / 4)
+#define RW_SAVED_DATA (0x58 / 4)
+#define RW_SAVED_DATA_BUF (0x5c / 4)
+#define RW_GROUP (0x60 / 4)
+#define RW_GROUP_DOWN (0x7c / 4)
+#define RW_CMD (0x80 / 4)
+#define RW_CFG (0x84 / 4)
+#define RW_STAT (0x88 / 4)
+#define RW_INTR_MASK (0x8c / 4)
+#define RW_ACK_INTR (0x90 / 4)
+#define R_INTR (0x94 / 4)
+#define R_MASKED_INTR (0x98 / 4)
+#define RW_STREAM_CMD (0x9c / 4)
+
+#define DMA_REG_MAX (0x100 / 4)
+
+/* descriptors */
+
+// ------------------------------------------------------------ dma_descr_group
+typedef struct dma_descr_group {
+ uint32_t next;
+ unsigned eol : 1;
+ unsigned tol : 1;
+ unsigned bol : 1;
+ unsigned : 1;
+ unsigned intr : 1;
+ unsigned : 2;
+ unsigned en : 1;
+ unsigned : 7;
+ unsigned dis : 1;
+ unsigned md : 16;
+ struct dma_descr_group *up;
+ union {
+ struct dma_descr_context *context;
+ struct dma_descr_group *group;
+ } down;
+} dma_descr_group;
+
+// ---------------------------------------------------------- dma_descr_context
+typedef struct dma_descr_context {
+ uint32_t next;
+ unsigned eol : 1;
+ unsigned : 3;
+ unsigned intr : 1;
+ unsigned : 1;
+ unsigned store_mode : 1;
+ unsigned en : 1;
+ unsigned : 7;
+ unsigned dis : 1;
+ unsigned md0 : 16;
+ unsigned md1;
+ unsigned md2;
+ unsigned md3;
+ unsigned md4;
+ uint32_t saved_data;
+ uint32_t saved_data_buf;
+} dma_descr_context;
+
+// ------------------------------------------------------------- dma_descr_data
+typedef struct dma_descr_data {
+ uint32_t next;
+ uint32_t buf;
+ unsigned eol : 1;
+ unsigned : 2;
+ unsigned out_eop : 1;
+ unsigned intr : 1;
+ unsigned wait : 1;
+ unsigned : 2;
+ unsigned : 3;
+ unsigned in_eop : 1;
+ unsigned : 4;
+ unsigned md : 16;
+ uint32_t after;
+} dma_descr_data;
+
+/* Constants */
+enum {
+ regk_dma_ack_pkt = 0x00000100,
+ regk_dma_anytime = 0x00000001,
+ regk_dma_array = 0x00000008,
+ regk_dma_burst = 0x00000020,
+ regk_dma_client = 0x00000002,
+ regk_dma_copy_next = 0x00000010,
+ regk_dma_copy_up = 0x00000020,
+ regk_dma_data_at_eol = 0x00000001,
+ regk_dma_dis_c = 0x00000010,
+ regk_dma_dis_g = 0x00000020,
+ regk_dma_idle = 0x00000001,
+ regk_dma_intern = 0x00000004,
+ regk_dma_load_c = 0x00000200,
+ regk_dma_load_c_n = 0x00000280,
+ regk_dma_load_c_next = 0x00000240,
+ regk_dma_load_d = 0x00000140,
+ regk_dma_load_g = 0x00000300,
+ regk_dma_load_g_down = 0x000003c0,
+ regk_dma_load_g_next = 0x00000340,
+ regk_dma_load_g_up = 0x00000380,
+ regk_dma_next_en = 0x00000010,
+ regk_dma_next_pkt = 0x00000010,
+ regk_dma_no = 0x00000000,
+ regk_dma_only_at_wait = 0x00000000,
+ regk_dma_restore = 0x00000020,
+ regk_dma_rst = 0x00000001,
+ regk_dma_running = 0x00000004,
+ regk_dma_rw_cfg_default = 0x00000000,
+ regk_dma_rw_cmd_default = 0x00000000,
+ regk_dma_rw_intr_mask_default = 0x00000000,
+ regk_dma_rw_stat_default = 0x00000101,
+ regk_dma_rw_stream_cmd_default = 0x00000000,
+ regk_dma_save_down = 0x00000020,
+ regk_dma_save_up = 0x00000020,
+ regk_dma_set_reg = 0x00000050,
+ regk_dma_set_w_size1 = 0x00000190,
+ regk_dma_set_w_size2 = 0x000001a0,
+ regk_dma_set_w_size4 = 0x000001c0,
+ regk_dma_stopped = 0x00000002,
+ regk_dma_store_c = 0x00000002,
+ regk_dma_store_descr = 0x00000000,
+ regk_dma_store_g = 0x00000004,
+ regk_dma_store_md = 0x00000001,
+ regk_dma_sw = 0x00000008,
+ regk_dma_update_down = 0x00000020,
+ regk_dma_yes = 0x00000001
+};
+
+enum dma_ch_state
+{
+ RST = 1,
+ STOPPED = 2,
+ RUNNING = 4
+};
+
+struct fs_dma_channel
+{
+ qemu_irq irq;
+ struct etraxfs_dma_client *client;
+
+ /* Internal status. */
+ int stream_cmd_src;
+ enum dma_ch_state state;
+
+ unsigned int input : 1;
+ unsigned int eol : 1;
+
+ struct dma_descr_group current_g;
+ struct dma_descr_context current_c;
+ struct dma_descr_data current_d;
+
+ /* Control registers. */
+ uint32_t regs[DMA_REG_MAX];
+};
+
+struct fs_dma_ctrl
+{
+ MemoryRegion mmio;
+ int nr_channels;
+ struct fs_dma_channel *channels;
+
+ QEMUBH *bh;
+};
+
+static void DMA_run(void *opaque);
+static int channel_out_run(struct fs_dma_ctrl *ctrl, int c);
+
+static inline uint32_t channel_reg(struct fs_dma_ctrl *ctrl, int c, int reg)
+{
+ return ctrl->channels[c].regs[reg];
+}
+
+static inline int channel_stopped(struct fs_dma_ctrl *ctrl, int c)
+{
+ return channel_reg(ctrl, c, RW_CFG) & 2;
+}
+
+static inline int channel_en(struct fs_dma_ctrl *ctrl, int c)
+{
+ return (channel_reg(ctrl, c, RW_CFG) & 1)
+ && ctrl->channels[c].client;
+}
+
+static inline int fs_channel(hwaddr addr)
+{
+ /* Every channel has a 0x2000 ctrl register map. */
+ return addr >> 13;
+}
+
+#ifdef USE_THIS_DEAD_CODE
+static void channel_load_g(struct fs_dma_ctrl *ctrl, int c)
+{
+ hwaddr addr = channel_reg(ctrl, c, RW_GROUP);
+
+ /* Load and decode. FIXME: handle endianness. */
+ cpu_physical_memory_read (addr,
+ (void *) &ctrl->channels[c].current_g,
+ sizeof ctrl->channels[c].current_g);
+}
+
+static void dump_c(int ch, struct dma_descr_context *c)
+{
+ printf("%s ch=%d\n", __func__, ch);
+ printf("next=%x\n", c->next);
+ printf("saved_data=%x\n", c->saved_data);
+ printf("saved_data_buf=%x\n", c->saved_data_buf);
+ printf("eol=%x\n", (uint32_t) c->eol);
+}
+
+static void dump_d(int ch, struct dma_descr_data *d)
+{
+ printf("%s ch=%d\n", __func__, ch);
+ printf("next=%x\n", d->next);
+ printf("buf=%x\n", d->buf);
+ printf("after=%x\n", d->after);
+ printf("intr=%x\n", (uint32_t) d->intr);
+ printf("out_eop=%x\n", (uint32_t) d->out_eop);
+ printf("in_eop=%x\n", (uint32_t) d->in_eop);
+ printf("eol=%x\n", (uint32_t) d->eol);
+}
+#endif
+
+static void channel_load_c(struct fs_dma_ctrl *ctrl, int c)
+{
+ hwaddr addr = channel_reg(ctrl, c, RW_GROUP_DOWN);
+
+ /* Load and decode. FIXME: handle endianness. */
+ cpu_physical_memory_read (addr,
+ (void *) &ctrl->channels[c].current_c,
+ sizeof ctrl->channels[c].current_c);
+
+ D(dump_c(c, &ctrl->channels[c].current_c));
+ /* I guess this should update the current pos. */
+ ctrl->channels[c].regs[RW_SAVED_DATA] =
+ (uint32_t)(unsigned long)ctrl->channels[c].current_c.saved_data;
+ ctrl->channels[c].regs[RW_SAVED_DATA_BUF] =
+ (uint32_t)(unsigned long)ctrl->channels[c].current_c.saved_data_buf;
+}
+
+static void channel_load_d(struct fs_dma_ctrl *ctrl, int c)
+{
+ hwaddr addr = channel_reg(ctrl, c, RW_SAVED_DATA);
+
+ /* Load and decode. FIXME: handle endianness. */
+ D(printf("%s ch=%d addr=" TARGET_FMT_plx "\n", __func__, c, addr));
+ cpu_physical_memory_read (addr,
+ (void *) &ctrl->channels[c].current_d,
+ sizeof ctrl->channels[c].current_d);
+
+ D(dump_d(c, &ctrl->channels[c].current_d));
+ ctrl->channels[c].regs[RW_DATA] = addr;
+}
+
+static void channel_store_c(struct fs_dma_ctrl *ctrl, int c)
+{
+ hwaddr addr = channel_reg(ctrl, c, RW_GROUP_DOWN);
+
+ /* Encode and store. FIXME: handle endianness. */
+ D(printf("%s ch=%d addr=" TARGET_FMT_plx "\n", __func__, c, addr));
+ D(dump_d(c, &ctrl->channels[c].current_d));
+ cpu_physical_memory_write (addr,
+ (void *) &ctrl->channels[c].current_c,
+ sizeof ctrl->channels[c].current_c);
+}
+
+static void channel_store_d(struct fs_dma_ctrl *ctrl, int c)
+{
+ hwaddr addr = channel_reg(ctrl, c, RW_SAVED_DATA);
+
+ /* Encode and store. FIXME: handle endianness. */
+ D(printf("%s ch=%d addr=" TARGET_FMT_plx "\n", __func__, c, addr));
+ cpu_physical_memory_write (addr,
+ (void *) &ctrl->channels[c].current_d,
+ sizeof ctrl->channels[c].current_d);
+}
+
+static inline void channel_stop(struct fs_dma_ctrl *ctrl, int c)
+{
+ /* FIXME: */
+}
+
+static inline void channel_start(struct fs_dma_ctrl *ctrl, int c)
+{
+ if (ctrl->channels[c].client)
+ {
+ ctrl->channels[c].eol = 0;
+ ctrl->channels[c].state = RUNNING;
+ if (!ctrl->channels[c].input)
+ channel_out_run(ctrl, c);
+ } else
+ printf("WARNING: starting DMA ch %d with no client\n", c);
+
+ qemu_bh_schedule_idle(ctrl->bh);
+}
+
+static void channel_continue(struct fs_dma_ctrl *ctrl, int c)
+{
+ if (!channel_en(ctrl, c)
+ || channel_stopped(ctrl, c)
+ || ctrl->channels[c].state != RUNNING
+ /* Only reload the current data descriptor if it has eol set. */
+ || !ctrl->channels[c].current_d.eol) {
+ D(printf("continue failed ch=%d state=%d stopped=%d en=%d eol=%d\n",
+ c, ctrl->channels[c].state,
+ channel_stopped(ctrl, c),
+ channel_en(ctrl,c),
+ ctrl->channels[c].eol));
+ D(dump_d(c, &ctrl->channels[c].current_d));
+ return;
+ }
+
+ /* Reload the current descriptor. */
+ channel_load_d(ctrl, c);
+
+ /* If the current descriptor cleared the eol flag and we had already
+ reached eol state, do the continue. */
+ if (!ctrl->channels[c].current_d.eol && ctrl->channels[c].eol) {
+ D(printf("continue %d ok %x\n", c,
+ ctrl->channels[c].current_d.next));
+ ctrl->channels[c].regs[RW_SAVED_DATA] =
+ (uint32_t)(unsigned long)ctrl->channels[c].current_d.next;
+ channel_load_d(ctrl, c);
+ ctrl->channels[c].regs[RW_SAVED_DATA_BUF] =
+ (uint32_t)(unsigned long)ctrl->channels[c].current_d.buf;
+
+ channel_start(ctrl, c);
+ }
+ ctrl->channels[c].regs[RW_SAVED_DATA_BUF] =
+ (uint32_t)(unsigned long)ctrl->channels[c].current_d.buf;
+}
+
+static void channel_stream_cmd(struct fs_dma_ctrl *ctrl, int c, uint32_t v)
+{
+ unsigned int cmd = v & ((1 << 10) - 1);
+
+ D(printf("%s ch=%d cmd=%x\n",
+ __func__, c, cmd));
+ if (cmd & regk_dma_load_d) {
+ channel_load_d(ctrl, c);
+ if (cmd & regk_dma_burst)
+ channel_start(ctrl, c);
+ }
+
+ if (cmd & regk_dma_load_c) {
+ channel_load_c(ctrl, c);
+ }
+}
+
+static void channel_update_irq(struct fs_dma_ctrl *ctrl, int c)
+{
+ D(printf("%s %d\n", __func__, c));
+ ctrl->channels[c].regs[R_INTR] &=
+ ~(ctrl->channels[c].regs[RW_ACK_INTR]);
+
+ ctrl->channels[c].regs[R_MASKED_INTR] =
+ ctrl->channels[c].regs[R_INTR]
+ & ctrl->channels[c].regs[RW_INTR_MASK];
+
+ D(printf("%s: chan=%d masked_intr=%x\n", __func__,
+ c,
+ ctrl->channels[c].regs[R_MASKED_INTR]));
+
+ qemu_set_irq(ctrl->channels[c].irq,
+ !!ctrl->channels[c].regs[R_MASKED_INTR]);
+}
+
+static int channel_out_run(struct fs_dma_ctrl *ctrl, int c)
+{
+ uint32_t len;
+ uint32_t saved_data_buf;
+ unsigned char buf[2 * 1024];
+
+ struct dma_context_metadata meta;
+ bool send_context = true;
+
+ if (ctrl->channels[c].eol)
+ return 0;
+
+ do {
+ bool out_eop;
+ D(printf("ch=%d buf=%x after=%x\n",
+ c,
+ (uint32_t)ctrl->channels[c].current_d.buf,
+ (uint32_t)ctrl->channels[c].current_d.after));
+
+ if (send_context) {
+ if (ctrl->channels[c].client->client.metadata_push) {
+ meta.metadata = ctrl->channels[c].current_d.md;
+ ctrl->channels[c].client->client.metadata_push(
+ ctrl->channels[c].client->client.opaque,
+ &meta);
+ }
+ send_context = false;
+ }
+
+ channel_load_d(ctrl, c);
+ saved_data_buf = channel_reg(ctrl, c, RW_SAVED_DATA_BUF);
+ len = (uint32_t)(unsigned long)
+ ctrl->channels[c].current_d.after;
+ len -= saved_data_buf;
+
+ if (len > sizeof buf)
+ len = sizeof buf;
+ cpu_physical_memory_read (saved_data_buf, buf, len);
+
+ out_eop = ((saved_data_buf + len) ==
+ ctrl->channels[c].current_d.after) &&
+ ctrl->channels[c].current_d.out_eop;
+
+ D(printf("channel %d pushes %x %u bytes eop=%u\n", c,
+ saved_data_buf, len, out_eop));
+
+ if (ctrl->channels[c].client->client.push)
+ ctrl->channels[c].client->client.push(
+ ctrl->channels[c].client->client.opaque,
+ buf, len, out_eop);
+ else
+ printf("WARNING: DMA ch%d dataloss,"
+ " no attached client.\n", c);
+
+ saved_data_buf += len;
+
+ if (saved_data_buf == (uint32_t)(unsigned long)
+ ctrl->channels[c].current_d.after) {
+ /* Done. Step to next. */
+ if (ctrl->channels[c].current_d.out_eop) {
+ send_context = true;
+ }
+ if (ctrl->channels[c].current_d.intr) {
+ /* data intr. */
+ D(printf("signal intr %d eol=%d\n",
+ len, ctrl->channels[c].current_d.eol));
+ ctrl->channels[c].regs[R_INTR] |= (1 << 2);
+ channel_update_irq(ctrl, c);
+ }
+ channel_store_d(ctrl, c);
+ if (ctrl->channels[c].current_d.eol) {
+ D(printf("channel %d EOL\n", c));
+ ctrl->channels[c].eol = 1;
+
+ /* Mark the context as disabled. */
+ ctrl->channels[c].current_c.dis = 1;
+ channel_store_c(ctrl, c);
+
+ channel_stop(ctrl, c);
+ } else {
+ ctrl->channels[c].regs[RW_SAVED_DATA] =
+ (uint32_t)(unsigned long)ctrl->
+ channels[c].current_d.next;
+ /* Load new descriptor. */
+ channel_load_d(ctrl, c);
+ saved_data_buf = (uint32_t)(unsigned long)
+ ctrl->channels[c].current_d.buf;
+ }
+
+ ctrl->channels[c].regs[RW_SAVED_DATA_BUF] =
+ saved_data_buf;
+ D(dump_d(c, &ctrl->channels[c].current_d));
+ }
+ ctrl->channels[c].regs[RW_SAVED_DATA_BUF] = saved_data_buf;
+ } while (!ctrl->channels[c].eol);
+ return 1;
+}
+
+static int channel_in_process(struct fs_dma_ctrl *ctrl, int c,
+ unsigned char *buf, int buflen, int eop)
+{
+ uint32_t len;
+ uint32_t saved_data_buf;
+
+ if (ctrl->channels[c].eol == 1)
+ return 0;
+
+ channel_load_d(ctrl, c);
+ saved_data_buf = channel_reg(ctrl, c, RW_SAVED_DATA_BUF);
+ len = (uint32_t)(unsigned long)ctrl->channels[c].current_d.after;
+ len -= saved_data_buf;
+
+ if (len > buflen)
+ len = buflen;
+
+ cpu_physical_memory_write (saved_data_buf, buf, len);
+ saved_data_buf += len;
+
+ if (saved_data_buf ==
+ (uint32_t)(unsigned long)ctrl->channels[c].current_d.after
+ || eop) {
+ uint32_t r_intr = ctrl->channels[c].regs[R_INTR];
+
+ D(printf("in dscr end len=%d\n",
+ ctrl->channels[c].current_d.after
+ - ctrl->channels[c].current_d.buf));
+ ctrl->channels[c].current_d.after = saved_data_buf;
+
+ /* Done. Step to next. */
+ if (ctrl->channels[c].current_d.intr) {
+ /* TODO: signal eop to the client. */
+ /* data intr. */
+ ctrl->channels[c].regs[R_INTR] |= 3;
+ }
+ if (eop) {
+ ctrl->channels[c].current_d.in_eop = 1;
+ ctrl->channels[c].regs[R_INTR] |= 8;
+ }
+ if (r_intr != ctrl->channels[c].regs[R_INTR])
+ channel_update_irq(ctrl, c);
+
+ channel_store_d(ctrl, c);
+ D(dump_d(c, &ctrl->channels[c].current_d));
+
+ if (ctrl->channels[c].current_d.eol) {
+ D(printf("channel %d EOL\n", c));
+ ctrl->channels[c].eol = 1;
+
+ /* Mark the context as disabled. */
+ ctrl->channels[c].current_c.dis = 1;
+ channel_store_c(ctrl, c);
+
+ channel_stop(ctrl, c);
+ } else {
+ ctrl->channels[c].regs[RW_SAVED_DATA] =
+ (uint32_t)(unsigned long)ctrl->
+ channels[c].current_d.next;
+ /* Load new descriptor. */
+ channel_load_d(ctrl, c);
+ saved_data_buf = (uint32_t)(unsigned long)
+ ctrl->channels[c].current_d.buf;
+ }
+ }
+
+ ctrl->channels[c].regs[RW_SAVED_DATA_BUF] = saved_data_buf;
+ return len;
+}
+
+static inline int channel_in_run(struct fs_dma_ctrl *ctrl, int c)
+{
+ if (ctrl->channels[c].client->client.pull) {
+ ctrl->channels[c].client->client.pull(
+ ctrl->channels[c].client->client.opaque);
+ return 1;
+ } else
+ return 0;
+}
+
+static uint32_t dma_rinvalid (void *opaque, hwaddr addr)
+{
+ hw_error("Unsupported short raccess. reg=" TARGET_FMT_plx "\n", addr);
+ return 0;
+}
+
+static uint64_t
+dma_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ struct fs_dma_ctrl *ctrl = opaque;
+ int c;
+ uint32_t r = 0;
+
+ if (size != 4) {
+ dma_rinvalid(opaque, addr);
+ }
+
+ /* Make addr relative to this channel and bounded to nr regs. */
+ c = fs_channel(addr);
+ addr &= 0xff;
+ addr >>= 2;
+ switch (addr)
+ {
+ case RW_STAT:
+ r = ctrl->channels[c].state & 7;
+ r |= ctrl->channels[c].eol << 5;
+ r |= ctrl->channels[c].stream_cmd_src << 8;
+ break;
+
+ default:
+ r = ctrl->channels[c].regs[addr];
+ D(printf ("%s c=%d addr=" TARGET_FMT_plx "\n",
+ __func__, c, addr));
+ break;
+ }
+ return r;
+}
+
+static void
+dma_winvalid (void *opaque, hwaddr addr, uint32_t value)
+{
+ hw_error("Unsupported short waccess. reg=" TARGET_FMT_plx "\n", addr);
+}
+
+static void
+dma_update_state(struct fs_dma_ctrl *ctrl, int c)
+{
+ if (ctrl->channels[c].regs[RW_CFG] & 2)
+ ctrl->channels[c].state = STOPPED;
+ if (!(ctrl->channels[c].regs[RW_CFG] & 1))
+ ctrl->channels[c].state = RST;
+}
+
+static void
+dma_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ struct fs_dma_ctrl *ctrl = opaque;
+ uint32_t value = val64;
+ int c;
+
+ if (size != 4) {
+ dma_winvalid(opaque, addr, value);
+ }
+
+ /* Make addr relative to this channel and bounded to nr regs. */
+ c = fs_channel(addr);
+ addr &= 0xff;
+ addr >>= 2;
+ switch (addr)
+ {
+ case RW_DATA:
+ ctrl->channels[c].regs[addr] = value;
+ break;
+
+ case RW_CFG:
+ ctrl->channels[c].regs[addr] = value;
+ dma_update_state(ctrl, c);
+ break;
+ case RW_CMD:
+ /* continue. */
+ if (value & ~1)
+ printf("Invalid store to ch=%d RW_CMD %x\n",
+ c, value);
+ ctrl->channels[c].regs[addr] = value;
+ channel_continue(ctrl, c);
+ break;
+
+ case RW_SAVED_DATA:
+ case RW_SAVED_DATA_BUF:
+ case RW_GROUP:
+ case RW_GROUP_DOWN:
+ ctrl->channels[c].regs[addr] = value;
+ break;
+
+ case RW_ACK_INTR:
+ case RW_INTR_MASK:
+ ctrl->channels[c].regs[addr] = value;
+ channel_update_irq(ctrl, c);
+ if (addr == RW_ACK_INTR)
+ ctrl->channels[c].regs[RW_ACK_INTR] = 0;
+ break;
+
+ case RW_STREAM_CMD:
+ if (value & ~1023)
+ printf("Invalid store to ch=%d "
+ "RW_STREAMCMD %x\n",
+ c, value);
+ ctrl->channels[c].regs[addr] = value;
+ D(printf("stream_cmd ch=%d\n", c));
+ channel_stream_cmd(ctrl, c, value);
+ break;
+
+ default:
+ D(printf ("%s c=%d " TARGET_FMT_plx "\n",
+ __func__, c, addr));
+ break;
+ }
+}
+
+static const MemoryRegionOps dma_ops = {
+ .read = dma_read,
+ .write = dma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4
+ }
+};
+
+static int etraxfs_dmac_run(void *opaque)
+{
+ struct fs_dma_ctrl *ctrl = opaque;
+ int i;
+ int p = 0;
+
+ for (i = 0;
+ i < ctrl->nr_channels;
+ i++)
+ {
+ if (ctrl->channels[i].state == RUNNING)
+ {
+ if (ctrl->channels[i].input) {
+ p += channel_in_run(ctrl, i);
+ } else {
+ p += channel_out_run(ctrl, i);
+ }
+ }
+ }
+ return p;
+}
+
+int etraxfs_dmac_input(struct etraxfs_dma_client *client,
+ void *buf, int len, int eop)
+{
+ return channel_in_process(client->ctrl, client->channel,
+ buf, len, eop);
+}
+
+/* Connect an IRQ line with a channel. */
+void etraxfs_dmac_connect(void *opaque, int c, qemu_irq *line, int input)
+{
+ struct fs_dma_ctrl *ctrl = opaque;
+ ctrl->channels[c].irq = *line;
+ ctrl->channels[c].input = input;
+}
+
+void etraxfs_dmac_connect_client(void *opaque, int c,
+ struct etraxfs_dma_client *cl)
+{
+ struct fs_dma_ctrl *ctrl = opaque;
+ cl->ctrl = ctrl;
+ cl->channel = c;
+ ctrl->channels[c].client = cl;
+}
+
+
+static void DMA_run(void *opaque)
+{
+ struct fs_dma_ctrl *etraxfs_dmac = opaque;
+ int p = 1;
+
+ if (runstate_is_running())
+ p = etraxfs_dmac_run(etraxfs_dmac);
+
+ if (p)
+ qemu_bh_schedule_idle(etraxfs_dmac->bh);
+}
+
+void *etraxfs_dmac_init(hwaddr base, int nr_channels)
+{
+ struct fs_dma_ctrl *ctrl = NULL;
+
+ ctrl = g_malloc0(sizeof *ctrl);
+
+ ctrl->bh = qemu_bh_new(DMA_run, ctrl);
+
+ ctrl->nr_channels = nr_channels;
+ ctrl->channels = g_malloc0(sizeof ctrl->channels[0] * nr_channels);
+
+ memory_region_init_io(&ctrl->mmio, NULL, &dma_ops, ctrl, "etraxfs-dma",
+ nr_channels * 0x2000);
+ memory_region_add_subregion(get_system_memory(), base, &ctrl->mmio);
+
+ return ctrl;
+}
diff --git a/hw/dma/i82374.c b/hw/dma/i82374.c
new file mode 100644
index 00000000..b8ad2e64
--- /dev/null
+++ b/hw/dma/i82374.c
@@ -0,0 +1,178 @@
+/*
+ * QEMU Intel 82374 emulation (Enhanced DMA controller)
+ *
+ * Copyright (c) 2010 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.
+ */
+
+#include "hw/isa/isa.h"
+
+//#define DEBUG_I82374
+
+#ifdef DEBUG_I82374
+#define DPRINTF(fmt, ...) \
+do { fprintf(stderr, "i82374: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+do {} while (0)
+#endif
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "i82374 ERROR: " fmt , ## __VA_ARGS__); } while (0)
+
+typedef struct I82374State {
+ uint8_t commands[8];
+ qemu_irq out;
+ PortioList port_list;
+} I82374State;
+
+static const VMStateDescription vmstate_i82374 = {
+ .name = "i82374",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(commands, I82374State, 8),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static uint32_t i82374_read_isr(void *opaque, uint32_t nport)
+{
+ uint32_t val = 0;
+
+ BADF("%s: %08x\n", __func__, nport);
+
+ DPRINTF("%s: %08x=%08x\n", __func__, nport, val);
+ return val;
+}
+
+static void i82374_write_command(void *opaque, uint32_t nport, uint32_t data)
+{
+ DPRINTF("%s: %08x=%08x\n", __func__, nport, data);
+
+ if (data != 0x42) {
+ /* Not Stop S/G command */
+ BADF("%s: %08x=%08x\n", __func__, nport, data);
+ }
+}
+
+static uint32_t i82374_read_status(void *opaque, uint32_t nport)
+{
+ uint32_t val = 0;
+
+ BADF("%s: %08x\n", __func__, nport);
+
+ DPRINTF("%s: %08x=%08x\n", __func__, nport, val);
+ return val;
+}
+
+static void i82374_write_descriptor(void *opaque, uint32_t nport, uint32_t data)
+{
+ DPRINTF("%s: %08x=%08x\n", __func__, nport, data);
+
+ BADF("%s: %08x=%08x\n", __func__, nport, data);
+}
+
+static uint32_t i82374_read_descriptor(void *opaque, uint32_t nport)
+{
+ uint32_t val = 0;
+
+ BADF("%s: %08x\n", __func__, nport);
+
+ DPRINTF("%s: %08x=%08x\n", __func__, nport, val);
+ return val;
+}
+
+static void i82374_realize(I82374State *s, Error **errp)
+{
+ DMA_init(1, &s->out);
+ memset(s->commands, 0, sizeof(s->commands));
+}
+
+#define TYPE_I82374 "i82374"
+#define I82374(obj) OBJECT_CHECK(ISAi82374State, (obj), TYPE_I82374)
+
+typedef struct ISAi82374State {
+ ISADevice parent_obj;
+
+ uint32_t iobase;
+ I82374State state;
+} ISAi82374State;
+
+static const VMStateDescription vmstate_isa_i82374 = {
+ .name = "isa-i82374",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, ISAi82374State, 0, vmstate_i82374, I82374State),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const MemoryRegionPortio i82374_portio_list[] = {
+ { 0x0A, 1, 1, .read = i82374_read_isr, },
+ { 0x10, 8, 1, .write = i82374_write_command, },
+ { 0x18, 8, 1, .read = i82374_read_status, },
+ { 0x20, 0x20, 1,
+ .write = i82374_write_descriptor, .read = i82374_read_descriptor, },
+ PORTIO_END_OF_LIST(),
+};
+
+static void i82374_isa_realize(DeviceState *dev, Error **errp)
+{
+ ISAi82374State *isa = I82374(dev);
+ I82374State *s = &isa->state;
+
+ portio_list_init(&s->port_list, OBJECT(isa), i82374_portio_list, s,
+ "i82374");
+ portio_list_add(&s->port_list, isa_address_space_io(&isa->parent_obj),
+ isa->iobase);
+
+ i82374_realize(s, errp);
+
+ qdev_init_gpio_out(dev, &s->out, 1);
+}
+
+static Property i82374_properties[] = {
+ DEFINE_PROP_UINT32("iobase", ISAi82374State, iobase, 0x400),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void i82374_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = i82374_isa_realize;
+ dc->vmsd = &vmstate_isa_i82374;
+ dc->props = i82374_properties;
+}
+
+static const TypeInfo i82374_isa_info = {
+ .name = TYPE_I82374,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISAi82374State),
+ .class_init = i82374_class_init,
+};
+
+static void i82374_register_types(void)
+{
+ type_register_static(&i82374_isa_info);
+}
+
+type_init(i82374_register_types)
diff --git a/hw/dma/i8257.c b/hw/dma/i8257.c
new file mode 100644
index 00000000..a414029b
--- /dev/null
+++ b/hw/dma/i8257.c
@@ -0,0 +1,598 @@
+/*
+ * QEMU DMA emulation
+ *
+ * Copyright (c) 2003-2004 Vassili Karpov (malc)
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/isa/isa.h"
+#include "qemu/main-loop.h"
+#include "trace.h"
+
+/* #define DEBUG_DMA */
+
+#define dolog(...) fprintf (stderr, "dma: " __VA_ARGS__)
+#ifdef DEBUG_DMA
+#define linfo(...) fprintf (stderr, "dma: " __VA_ARGS__)
+#define ldebug(...) fprintf (stderr, "dma: " __VA_ARGS__)
+#else
+#define linfo(...)
+#define ldebug(...)
+#endif
+
+struct dma_regs {
+ int now[2];
+ uint16_t base[2];
+ uint8_t mode;
+ uint8_t page;
+ uint8_t pageh;
+ uint8_t dack;
+ uint8_t eop;
+ DMA_transfer_handler transfer_handler;
+ void *opaque;
+};
+
+#define ADDR 0
+#define COUNT 1
+
+static struct dma_cont {
+ uint8_t status;
+ uint8_t command;
+ uint8_t mask;
+ uint8_t flip_flop;
+ int dshift;
+ struct dma_regs regs[4];
+ qemu_irq *cpu_request_exit;
+ MemoryRegion channel_io;
+ MemoryRegion cont_io;
+} dma_controllers[2];
+
+enum {
+ CMD_MEMORY_TO_MEMORY = 0x01,
+ CMD_FIXED_ADDRESS = 0x02,
+ CMD_BLOCK_CONTROLLER = 0x04,
+ CMD_COMPRESSED_TIME = 0x08,
+ CMD_CYCLIC_PRIORITY = 0x10,
+ CMD_EXTENDED_WRITE = 0x20,
+ CMD_LOW_DREQ = 0x40,
+ CMD_LOW_DACK = 0x80,
+ CMD_NOT_SUPPORTED = CMD_MEMORY_TO_MEMORY | CMD_FIXED_ADDRESS
+ | CMD_COMPRESSED_TIME | CMD_CYCLIC_PRIORITY | CMD_EXTENDED_WRITE
+ | CMD_LOW_DREQ | CMD_LOW_DACK
+
+};
+
+static void DMA_run (void);
+
+static int channels[8] = {-1, 2, 3, 1, -1, -1, -1, 0};
+
+static void write_page (void *opaque, uint32_t nport, uint32_t data)
+{
+ struct dma_cont *d = opaque;
+ int ichan;
+
+ ichan = channels[nport & 7];
+ if (-1 == ichan) {
+ dolog ("invalid channel %#x %#x\n", nport, data);
+ return;
+ }
+ d->regs[ichan].page = data;
+}
+
+static void write_pageh (void *opaque, uint32_t nport, uint32_t data)
+{
+ struct dma_cont *d = opaque;
+ int ichan;
+
+ ichan = channels[nport & 7];
+ if (-1 == ichan) {
+ dolog ("invalid channel %#x %#x\n", nport, data);
+ return;
+ }
+ d->regs[ichan].pageh = data;
+}
+
+static uint32_t read_page (void *opaque, uint32_t nport)
+{
+ struct dma_cont *d = opaque;
+ int ichan;
+
+ ichan = channels[nport & 7];
+ if (-1 == ichan) {
+ dolog ("invalid channel read %#x\n", nport);
+ return 0;
+ }
+ return d->regs[ichan].page;
+}
+
+static uint32_t read_pageh (void *opaque, uint32_t nport)
+{
+ struct dma_cont *d = opaque;
+ int ichan;
+
+ ichan = channels[nport & 7];
+ if (-1 == ichan) {
+ dolog ("invalid channel read %#x\n", nport);
+ return 0;
+ }
+ return d->regs[ichan].pageh;
+}
+
+static inline void init_chan (struct dma_cont *d, int ichan)
+{
+ struct dma_regs *r;
+
+ r = d->regs + ichan;
+ r->now[ADDR] = r->base[ADDR] << d->dshift;
+ r->now[COUNT] = 0;
+}
+
+static inline int getff (struct dma_cont *d)
+{
+ int ff;
+
+ ff = d->flip_flop;
+ d->flip_flop = !ff;
+ return ff;
+}
+
+static uint64_t read_chan(void *opaque, hwaddr nport, unsigned size)
+{
+ struct dma_cont *d = opaque;
+ int ichan, nreg, iport, ff, val, dir;
+ struct dma_regs *r;
+
+ iport = (nport >> d->dshift) & 0x0f;
+ ichan = iport >> 1;
+ nreg = iport & 1;
+ r = d->regs + ichan;
+
+ dir = ((r->mode >> 5) & 1) ? -1 : 1;
+ ff = getff (d);
+ if (nreg)
+ val = (r->base[COUNT] << d->dshift) - r->now[COUNT];
+ else
+ val = r->now[ADDR] + r->now[COUNT] * dir;
+
+ ldebug ("read_chan %#x -> %d\n", iport, val);
+ return (val >> (d->dshift + (ff << 3))) & 0xff;
+}
+
+static void write_chan(void *opaque, hwaddr nport, uint64_t data,
+ unsigned size)
+{
+ struct dma_cont *d = opaque;
+ int iport, ichan, nreg;
+ struct dma_regs *r;
+
+ iport = (nport >> d->dshift) & 0x0f;
+ ichan = iport >> 1;
+ nreg = iport & 1;
+ r = d->regs + ichan;
+ if (getff (d)) {
+ r->base[nreg] = (r->base[nreg] & 0xff) | ((data << 8) & 0xff00);
+ init_chan (d, ichan);
+ } else {
+ r->base[nreg] = (r->base[nreg] & 0xff00) | (data & 0xff);
+ }
+}
+
+static void write_cont(void *opaque, hwaddr nport, uint64_t data,
+ unsigned size)
+{
+ struct dma_cont *d = opaque;
+ int iport, ichan = 0;
+
+ iport = (nport >> d->dshift) & 0x0f;
+ switch (iport) {
+ case 0x00: /* command */
+ if ((data != 0) && (data & CMD_NOT_SUPPORTED)) {
+ dolog("command %"PRIx64" not supported\n", data);
+ return;
+ }
+ d->command = data;
+ break;
+
+ case 0x01:
+ ichan = data & 3;
+ if (data & 4) {
+ d->status |= 1 << (ichan + 4);
+ }
+ else {
+ d->status &= ~(1 << (ichan + 4));
+ }
+ d->status &= ~(1 << ichan);
+ DMA_run();
+ break;
+
+ case 0x02: /* single mask */
+ if (data & 4)
+ d->mask |= 1 << (data & 3);
+ else
+ d->mask &= ~(1 << (data & 3));
+ DMA_run();
+ break;
+
+ case 0x03: /* mode */
+ {
+ ichan = data & 3;
+#ifdef DEBUG_DMA
+ {
+ int op, ai, dir, opmode;
+ op = (data >> 2) & 3;
+ ai = (data >> 4) & 1;
+ dir = (data >> 5) & 1;
+ opmode = (data >> 6) & 3;
+
+ linfo ("ichan %d, op %d, ai %d, dir %d, opmode %d\n",
+ ichan, op, ai, dir, opmode);
+ }
+#endif
+ d->regs[ichan].mode = data;
+ break;
+ }
+
+ case 0x04: /* clear flip flop */
+ d->flip_flop = 0;
+ break;
+
+ case 0x05: /* reset */
+ d->flip_flop = 0;
+ d->mask = ~0;
+ d->status = 0;
+ d->command = 0;
+ break;
+
+ case 0x06: /* clear mask for all channels */
+ d->mask = 0;
+ DMA_run();
+ break;
+
+ case 0x07: /* write mask for all channels */
+ d->mask = data;
+ DMA_run();
+ break;
+
+ default:
+ dolog ("unknown iport %#x\n", iport);
+ break;
+ }
+
+#ifdef DEBUG_DMA
+ if (0xc != iport) {
+ linfo ("write_cont: nport %#06x, ichan % 2d, val %#06x\n",
+ nport, ichan, data);
+ }
+#endif
+}
+
+static uint64_t read_cont(void *opaque, hwaddr nport, unsigned size)
+{
+ struct dma_cont *d = opaque;
+ int iport, val;
+
+ iport = (nport >> d->dshift) & 0x0f;
+ switch (iport) {
+ case 0x00: /* status */
+ val = d->status;
+ d->status &= 0xf0;
+ break;
+ case 0x01: /* mask */
+ val = d->mask;
+ break;
+ default:
+ val = 0;
+ break;
+ }
+
+ ldebug ("read_cont: nport %#06x, iport %#04x val %#x\n", nport, iport, val);
+ return val;
+}
+
+int DMA_get_channel_mode (int nchan)
+{
+ return dma_controllers[nchan > 3].regs[nchan & 3].mode;
+}
+
+void DMA_hold_DREQ (int nchan)
+{
+ int ncont, ichan;
+
+ ncont = nchan > 3;
+ ichan = nchan & 3;
+ linfo ("held cont=%d chan=%d\n", ncont, ichan);
+ dma_controllers[ncont].status |= 1 << (ichan + 4);
+ DMA_run();
+}
+
+void DMA_release_DREQ (int nchan)
+{
+ int ncont, ichan;
+
+ ncont = nchan > 3;
+ ichan = nchan & 3;
+ linfo ("released cont=%d chan=%d\n", ncont, ichan);
+ dma_controllers[ncont].status &= ~(1 << (ichan + 4));
+ DMA_run();
+}
+
+static void channel_run (int ncont, int ichan)
+{
+ int n;
+ struct dma_regs *r = &dma_controllers[ncont].regs[ichan];
+#ifdef DEBUG_DMA
+ int dir, opmode;
+
+ dir = (r->mode >> 5) & 1;
+ opmode = (r->mode >> 6) & 3;
+
+ if (dir) {
+ dolog ("DMA in address decrement mode\n");
+ }
+ if (opmode != 1) {
+ dolog ("DMA not in single mode select %#x\n", opmode);
+ }
+#endif
+
+ n = r->transfer_handler (r->opaque, ichan + (ncont << 2),
+ r->now[COUNT], (r->base[COUNT] + 1) << ncont);
+ r->now[COUNT] = n;
+ ldebug ("dma_pos %d size %d\n", n, (r->base[COUNT] + 1) << ncont);
+}
+
+static QEMUBH *dma_bh;
+
+static void DMA_run (void)
+{
+ struct dma_cont *d;
+ int icont, ichan;
+ int rearm = 0;
+ static int running = 0;
+
+ if (running) {
+ rearm = 1;
+ goto out;
+ } else {
+ running = 1;
+ }
+
+ d = dma_controllers;
+
+ for (icont = 0; icont < 2; icont++, d++) {
+ for (ichan = 0; ichan < 4; ichan++) {
+ int mask;
+
+ mask = 1 << ichan;
+
+ if ((0 == (d->mask & mask)) && (0 != (d->status & (mask << 4)))) {
+ channel_run (icont, ichan);
+ rearm = 1;
+ }
+ }
+ }
+
+ running = 0;
+out:
+ if (rearm)
+ qemu_bh_schedule_idle(dma_bh);
+}
+
+static void DMA_run_bh(void *unused)
+{
+ DMA_run();
+}
+
+void DMA_register_channel (int nchan,
+ DMA_transfer_handler transfer_handler,
+ void *opaque)
+{
+ struct dma_regs *r;
+ int ichan, ncont;
+
+ ncont = nchan > 3;
+ ichan = nchan & 3;
+
+ r = dma_controllers[ncont].regs + ichan;
+ r->transfer_handler = transfer_handler;
+ r->opaque = opaque;
+}
+
+int DMA_read_memory (int nchan, void *buf, int pos, int len)
+{
+ struct dma_regs *r = &dma_controllers[nchan > 3].regs[nchan & 3];
+ hwaddr addr = ((r->pageh & 0x7f) << 24) | (r->page << 16) | r->now[ADDR];
+
+ if (r->mode & 0x20) {
+ int i;
+ uint8_t *p = buf;
+
+ cpu_physical_memory_read (addr - pos - len, buf, len);
+ /* What about 16bit transfers? */
+ for (i = 0; i < len >> 1; i++) {
+ uint8_t b = p[len - i - 1];
+ p[i] = b;
+ }
+ }
+ else
+ cpu_physical_memory_read (addr + pos, buf, len);
+
+ return len;
+}
+
+int DMA_write_memory (int nchan, void *buf, int pos, int len)
+{
+ struct dma_regs *r = &dma_controllers[nchan > 3].regs[nchan & 3];
+ hwaddr addr = ((r->pageh & 0x7f) << 24) | (r->page << 16) | r->now[ADDR];
+
+ if (r->mode & 0x20) {
+ int i;
+ uint8_t *p = buf;
+
+ cpu_physical_memory_write (addr - pos - len, buf, len);
+ /* What about 16bit transfers? */
+ for (i = 0; i < len; i++) {
+ uint8_t b = p[len - i - 1];
+ p[i] = b;
+ }
+ }
+ else
+ cpu_physical_memory_write (addr + pos, buf, len);
+
+ return len;
+}
+
+/* request the emulator to transfer a new DMA memory block ASAP */
+void DMA_schedule(int nchan)
+{
+ struct dma_cont *d = &dma_controllers[nchan > 3];
+
+ qemu_irq_pulse(*d->cpu_request_exit);
+}
+
+static void dma_reset(void *opaque)
+{
+ struct dma_cont *d = opaque;
+ write_cont(d, (0x05 << d->dshift), 0, 1);
+}
+
+static int dma_phony_handler (void *opaque, int nchan, int dma_pos, int dma_len)
+{
+ trace_i8257_unregistered_dma(nchan, dma_pos, dma_len);
+ return dma_pos;
+}
+
+
+static const MemoryRegionOps channel_io_ops = {
+ .read = read_chan,
+ .write = write_chan,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+/* IOport from page_base */
+static const MemoryRegionPortio page_portio_list[] = {
+ { 0x01, 3, 1, .write = write_page, .read = read_page, },
+ { 0x07, 1, 1, .write = write_page, .read = read_page, },
+ PORTIO_END_OF_LIST(),
+};
+
+/* IOport from pageh_base */
+static const MemoryRegionPortio pageh_portio_list[] = {
+ { 0x01, 3, 1, .write = write_pageh, .read = read_pageh, },
+ { 0x07, 3, 1, .write = write_pageh, .read = read_pageh, },
+ PORTIO_END_OF_LIST(),
+};
+
+static const MemoryRegionOps cont_io_ops = {
+ .read = read_cont,
+ .write = write_cont,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+/* dshift = 0: 8 bit DMA, 1 = 16 bit DMA */
+static void dma_init2(struct dma_cont *d, int base, int dshift,
+ int page_base, int pageh_base,
+ qemu_irq *cpu_request_exit)
+{
+ int i;
+
+ d->dshift = dshift;
+ d->cpu_request_exit = cpu_request_exit;
+
+ memory_region_init_io(&d->channel_io, NULL, &channel_io_ops, d,
+ "dma-chan", 8 << d->dshift);
+ memory_region_add_subregion(isa_address_space_io(NULL),
+ base, &d->channel_io);
+
+ isa_register_portio_list(NULL, page_base, page_portio_list, d,
+ "dma-page");
+ if (pageh_base >= 0) {
+ isa_register_portio_list(NULL, pageh_base, pageh_portio_list, d,
+ "dma-pageh");
+ }
+
+ memory_region_init_io(&d->cont_io, NULL, &cont_io_ops, d, "dma-cont",
+ 8 << d->dshift);
+ memory_region_add_subregion(isa_address_space_io(NULL),
+ base + (8 << d->dshift), &d->cont_io);
+
+ qemu_register_reset(dma_reset, d);
+ dma_reset(d);
+ for (i = 0; i < ARRAY_SIZE (d->regs); ++i) {
+ d->regs[i].transfer_handler = dma_phony_handler;
+ }
+}
+
+static const VMStateDescription vmstate_dma_regs = {
+ .name = "dma_regs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_ARRAY(now, struct dma_regs, 2),
+ VMSTATE_UINT16_ARRAY(base, struct dma_regs, 2),
+ VMSTATE_UINT8(mode, struct dma_regs),
+ VMSTATE_UINT8(page, struct dma_regs),
+ VMSTATE_UINT8(pageh, struct dma_regs),
+ VMSTATE_UINT8(dack, struct dma_regs),
+ VMSTATE_UINT8(eop, struct dma_regs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int dma_post_load(void *opaque, int version_id)
+{
+ DMA_run();
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_dma = {
+ .name = "dma",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = dma_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(command, struct dma_cont),
+ VMSTATE_UINT8(mask, struct dma_cont),
+ VMSTATE_UINT8(flip_flop, struct dma_cont),
+ VMSTATE_INT32(dshift, struct dma_cont),
+ VMSTATE_STRUCT_ARRAY(regs, struct dma_cont, 4, 1, vmstate_dma_regs, struct dma_regs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+void DMA_init(int high_page_enable, qemu_irq *cpu_request_exit)
+{
+ dma_init2(&dma_controllers[0], 0x00, 0, 0x80,
+ high_page_enable ? 0x480 : -1, cpu_request_exit);
+ dma_init2(&dma_controllers[1], 0xc0, 1, 0x88,
+ high_page_enable ? 0x488 : -1, cpu_request_exit);
+ vmstate_register (NULL, 0, &vmstate_dma, &dma_controllers[0]);
+ vmstate_register (NULL, 1, &vmstate_dma, &dma_controllers[1]);
+
+ dma_bh = qemu_bh_new(DMA_run_bh, NULL);
+}
diff --git a/hw/dma/omap_dma.c b/hw/dma/omap_dma.c
new file mode 100644
index 00000000..97c57a03
--- /dev/null
+++ b/hw/dma/omap_dma.c
@@ -0,0 +1,2104 @@
+/*
+ * TI OMAP DMA gigacell.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2007-2008 Lauro Ramos Venancio <lauro.venancio@indt.org.br>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "hw/arm/omap.h"
+#include "hw/irq.h"
+#include "hw/arm/soc_dma.h"
+
+struct omap_dma_channel_s {
+ /* transfer data */
+ int burst[2];
+ int pack[2];
+ int endian[2];
+ int endian_lock[2];
+ int translate[2];
+ enum omap_dma_port port[2];
+ hwaddr addr[2];
+ omap_dma_addressing_t mode[2];
+ uint32_t elements;
+ uint16_t frames;
+ int32_t frame_index[2];
+ int16_t element_index[2];
+ int data_type;
+
+ /* transfer type */
+ int transparent_copy;
+ int constant_fill;
+ uint32_t color;
+ int prefetch;
+
+ /* auto init and linked channel data */
+ int end_prog;
+ int repeat;
+ int auto_init;
+ int link_enabled;
+ int link_next_ch;
+
+ /* interruption data */
+ int interrupts;
+ int status;
+ int cstatus;
+
+ /* state data */
+ int active;
+ int enable;
+ int sync;
+ int src_sync;
+ int pending_request;
+ int waiting_end_prog;
+ uint16_t cpc;
+ int set_update;
+
+ /* sync type */
+ int fs;
+ int bs;
+
+ /* compatibility */
+ int omap_3_1_compatible_disable;
+
+ qemu_irq irq;
+ struct omap_dma_channel_s *sibling;
+
+ struct omap_dma_reg_set_s {
+ hwaddr src, dest;
+ int frame;
+ int element;
+ int pck_element;
+ int frame_delta[2];
+ int elem_delta[2];
+ int frames;
+ int elements;
+ int pck_elements;
+ } active_set;
+
+ struct soc_dma_ch_s *dma;
+
+ /* unused parameters */
+ int write_mode;
+ int priority;
+ int interleave_disabled;
+ int type;
+ int suspend;
+ int buf_disable;
+};
+
+struct omap_dma_s {
+ struct soc_dma_s *dma;
+ MemoryRegion iomem;
+
+ struct omap_mpu_state_s *mpu;
+ omap_clk clk;
+ qemu_irq irq[4];
+ void (*intr_update)(struct omap_dma_s *s);
+ enum omap_dma_model model;
+ int omap_3_1_mapping_disabled;
+
+ uint32_t gcr;
+ uint32_t ocp;
+ uint32_t caps[5];
+ uint32_t irqen[4];
+ uint32_t irqstat[4];
+
+ int chans;
+ struct omap_dma_channel_s ch[32];
+ struct omap_dma_lcd_channel_s lcd_ch;
+};
+
+/* Interrupts */
+#define TIMEOUT_INTR (1 << 0)
+#define EVENT_DROP_INTR (1 << 1)
+#define HALF_FRAME_INTR (1 << 2)
+#define END_FRAME_INTR (1 << 3)
+#define LAST_FRAME_INTR (1 << 4)
+#define END_BLOCK_INTR (1 << 5)
+#define SYNC (1 << 6)
+#define END_PKT_INTR (1 << 7)
+#define TRANS_ERR_INTR (1 << 8)
+#define MISALIGN_INTR (1 << 11)
+
+static inline void omap_dma_interrupts_update(struct omap_dma_s *s)
+{
+ s->intr_update(s);
+}
+
+static void omap_dma_channel_load(struct omap_dma_channel_s *ch)
+{
+ struct omap_dma_reg_set_s *a = &ch->active_set;
+ int i, normal;
+ int omap_3_1 = !ch->omap_3_1_compatible_disable;
+
+ /*
+ * TODO: verify address ranges and alignment
+ * TODO: port endianness
+ */
+
+ a->src = ch->addr[0];
+ a->dest = ch->addr[1];
+ a->frames = ch->frames;
+ a->elements = ch->elements;
+ a->pck_elements = ch->frame_index[!ch->src_sync];
+ a->frame = 0;
+ a->element = 0;
+ a->pck_element = 0;
+
+ if (unlikely(!ch->elements || !ch->frames)) {
+ printf("%s: bad DMA request\n", __FUNCTION__);
+ return;
+ }
+
+ for (i = 0; i < 2; i ++)
+ switch (ch->mode[i]) {
+ case constant:
+ a->elem_delta[i] = 0;
+ a->frame_delta[i] = 0;
+ break;
+ case post_incremented:
+ a->elem_delta[i] = ch->data_type;
+ a->frame_delta[i] = 0;
+ break;
+ case single_index:
+ a->elem_delta[i] = ch->data_type +
+ ch->element_index[omap_3_1 ? 0 : i] - 1;
+ a->frame_delta[i] = 0;
+ break;
+ case double_index:
+ a->elem_delta[i] = ch->data_type +
+ ch->element_index[omap_3_1 ? 0 : i] - 1;
+ a->frame_delta[i] = ch->frame_index[omap_3_1 ? 0 : i] -
+ ch->element_index[omap_3_1 ? 0 : i];
+ break;
+ default:
+ break;
+ }
+
+ normal = !ch->transparent_copy && !ch->constant_fill &&
+ /* FIFO is big-endian so either (ch->endian[n] == 1) OR
+ * (ch->endian_lock[n] == 1) mean no endianism conversion. */
+ (ch->endian[0] | ch->endian_lock[0]) ==
+ (ch->endian[1] | ch->endian_lock[1]);
+ for (i = 0; i < 2; i ++) {
+ /* TODO: for a->frame_delta[i] > 0 still use the fast path, just
+ * limit min_elems in omap_dma_transfer_setup to the nearest frame
+ * end. */
+ if (!a->elem_delta[i] && normal &&
+ (a->frames == 1 || !a->frame_delta[i]))
+ ch->dma->type[i] = soc_dma_access_const;
+ else if (a->elem_delta[i] == ch->data_type && normal &&
+ (a->frames == 1 || !a->frame_delta[i]))
+ ch->dma->type[i] = soc_dma_access_linear;
+ else
+ ch->dma->type[i] = soc_dma_access_other;
+
+ ch->dma->vaddr[i] = ch->addr[i];
+ }
+ soc_dma_ch_update(ch->dma);
+}
+
+static void omap_dma_activate_channel(struct omap_dma_s *s,
+ struct omap_dma_channel_s *ch)
+{
+ if (!ch->active) {
+ if (ch->set_update) {
+ /* It's not clear when the active set is supposed to be
+ * loaded from registers. We're already loading it when the
+ * channel is enabled, and for some guests this is not enough
+ * but that may be also because of a race condition (no
+ * delays in qemu) in the guest code, which we're just
+ * working around here. */
+ omap_dma_channel_load(ch);
+ ch->set_update = 0;
+ }
+
+ ch->active = 1;
+ soc_dma_set_request(ch->dma, 1);
+ if (ch->sync)
+ ch->status |= SYNC;
+ }
+}
+
+static void omap_dma_deactivate_channel(struct omap_dma_s *s,
+ struct omap_dma_channel_s *ch)
+{
+ /* Update cpc */
+ ch->cpc = ch->active_set.dest & 0xffff;
+
+ if (ch->pending_request && !ch->waiting_end_prog && ch->enable) {
+ /* Don't deactivate the channel */
+ ch->pending_request = 0;
+ return;
+ }
+
+ /* Don't deactive the channel if it is synchronized and the DMA request is
+ active */
+ if (ch->sync && ch->enable && (s->dma->drqbmp & (1ULL << ch->sync)))
+ return;
+
+ if (ch->active) {
+ ch->active = 0;
+ ch->status &= ~SYNC;
+ soc_dma_set_request(ch->dma, 0);
+ }
+}
+
+static void omap_dma_enable_channel(struct omap_dma_s *s,
+ struct omap_dma_channel_s *ch)
+{
+ if (!ch->enable) {
+ ch->enable = 1;
+ ch->waiting_end_prog = 0;
+ omap_dma_channel_load(ch);
+ /* TODO: theoretically if ch->sync && ch->prefetch &&
+ * !s->dma->drqbmp[ch->sync], we should also activate and fetch
+ * from source and then stall until signalled. */
+ if ((!ch->sync) || (s->dma->drqbmp & (1ULL << ch->sync))) {
+ omap_dma_activate_channel(s, ch);
+ }
+ }
+}
+
+static void omap_dma_disable_channel(struct omap_dma_s *s,
+ struct omap_dma_channel_s *ch)
+{
+ if (ch->enable) {
+ ch->enable = 0;
+ /* Discard any pending request */
+ ch->pending_request = 0;
+ omap_dma_deactivate_channel(s, ch);
+ }
+}
+
+static void omap_dma_channel_end_prog(struct omap_dma_s *s,
+ struct omap_dma_channel_s *ch)
+{
+ if (ch->waiting_end_prog) {
+ ch->waiting_end_prog = 0;
+ if (!ch->sync || ch->pending_request) {
+ ch->pending_request = 0;
+ omap_dma_activate_channel(s, ch);
+ }
+ }
+}
+
+static void omap_dma_interrupts_3_1_update(struct omap_dma_s *s)
+{
+ struct omap_dma_channel_s *ch = s->ch;
+
+ /* First three interrupts are shared between two channels each. */
+ if (ch[0].status | ch[6].status)
+ qemu_irq_raise(ch[0].irq);
+ if (ch[1].status | ch[7].status)
+ qemu_irq_raise(ch[1].irq);
+ if (ch[2].status | ch[8].status)
+ qemu_irq_raise(ch[2].irq);
+ if (ch[3].status)
+ qemu_irq_raise(ch[3].irq);
+ if (ch[4].status)
+ qemu_irq_raise(ch[4].irq);
+ if (ch[5].status)
+ qemu_irq_raise(ch[5].irq);
+}
+
+static void omap_dma_interrupts_3_2_update(struct omap_dma_s *s)
+{
+ struct omap_dma_channel_s *ch = s->ch;
+ int i;
+
+ for (i = s->chans; i; ch ++, i --)
+ if (ch->status)
+ qemu_irq_raise(ch->irq);
+}
+
+static void omap_dma_enable_3_1_mapping(struct omap_dma_s *s)
+{
+ s->omap_3_1_mapping_disabled = 0;
+ s->chans = 9;
+ s->intr_update = omap_dma_interrupts_3_1_update;
+}
+
+static void omap_dma_disable_3_1_mapping(struct omap_dma_s *s)
+{
+ s->omap_3_1_mapping_disabled = 1;
+ s->chans = 16;
+ s->intr_update = omap_dma_interrupts_3_2_update;
+}
+
+static void omap_dma_process_request(struct omap_dma_s *s, int request)
+{
+ int channel;
+ int drop_event = 0;
+ struct omap_dma_channel_s *ch = s->ch;
+
+ for (channel = 0; channel < s->chans; channel ++, ch ++) {
+ if (ch->enable && ch->sync == request) {
+ if (!ch->active)
+ omap_dma_activate_channel(s, ch);
+ else if (!ch->pending_request)
+ ch->pending_request = 1;
+ else {
+ /* Request collision */
+ /* Second request received while processing other request */
+ ch->status |= EVENT_DROP_INTR;
+ drop_event = 1;
+ }
+ }
+ }
+
+ if (drop_event)
+ omap_dma_interrupts_update(s);
+}
+
+static void omap_dma_transfer_generic(struct soc_dma_ch_s *dma)
+{
+ uint8_t value[4];
+ struct omap_dma_channel_s *ch = dma->opaque;
+ struct omap_dma_reg_set_s *a = &ch->active_set;
+ int bytes = dma->bytes;
+#ifdef MULTI_REQ
+ uint16_t status = ch->status;
+#endif
+
+ do {
+ /* Transfer a single element */
+ /* FIXME: check the endianness */
+ if (!ch->constant_fill)
+ cpu_physical_memory_read(a->src, value, ch->data_type);
+ else
+ *(uint32_t *) value = ch->color;
+
+ if (!ch->transparent_copy || *(uint32_t *) value != ch->color)
+ cpu_physical_memory_write(a->dest, value, ch->data_type);
+
+ a->src += a->elem_delta[0];
+ a->dest += a->elem_delta[1];
+ a->element ++;
+
+#ifndef MULTI_REQ
+ if (a->element == a->elements) {
+ /* End of Frame */
+ a->element = 0;
+ a->src += a->frame_delta[0];
+ a->dest += a->frame_delta[1];
+ a->frame ++;
+
+ /* If the channel is async, update cpc */
+ if (!ch->sync)
+ ch->cpc = a->dest & 0xffff;
+ }
+ } while ((bytes -= ch->data_type));
+#else
+ /* If the channel is element synchronized, deactivate it */
+ if (ch->sync && !ch->fs && !ch->bs)
+ omap_dma_deactivate_channel(s, ch);
+
+ /* If it is the last frame, set the LAST_FRAME interrupt */
+ if (a->element == 1 && a->frame == a->frames - 1)
+ if (ch->interrupts & LAST_FRAME_INTR)
+ ch->status |= LAST_FRAME_INTR;
+
+ /* If the half of the frame was reached, set the HALF_FRAME
+ interrupt */
+ if (a->element == (a->elements >> 1))
+ if (ch->interrupts & HALF_FRAME_INTR)
+ ch->status |= HALF_FRAME_INTR;
+
+ if (ch->fs && ch->bs) {
+ a->pck_element ++;
+ /* Check if a full packet has beed transferred. */
+ if (a->pck_element == a->pck_elements) {
+ a->pck_element = 0;
+
+ /* Set the END_PKT interrupt */
+ if ((ch->interrupts & END_PKT_INTR) && !ch->src_sync)
+ ch->status |= END_PKT_INTR;
+
+ /* If the channel is packet-synchronized, deactivate it */
+ if (ch->sync)
+ omap_dma_deactivate_channel(s, ch);
+ }
+ }
+
+ if (a->element == a->elements) {
+ /* End of Frame */
+ a->element = 0;
+ a->src += a->frame_delta[0];
+ a->dest += a->frame_delta[1];
+ a->frame ++;
+
+ /* If the channel is frame synchronized, deactivate it */
+ if (ch->sync && ch->fs && !ch->bs)
+ omap_dma_deactivate_channel(s, ch);
+
+ /* If the channel is async, update cpc */
+ if (!ch->sync)
+ ch->cpc = a->dest & 0xffff;
+
+ /* Set the END_FRAME interrupt */
+ if (ch->interrupts & END_FRAME_INTR)
+ ch->status |= END_FRAME_INTR;
+
+ if (a->frame == a->frames) {
+ /* End of Block */
+ /* Disable the channel */
+
+ if (ch->omap_3_1_compatible_disable) {
+ omap_dma_disable_channel(s, ch);
+ if (ch->link_enabled)
+ omap_dma_enable_channel(s,
+ &s->ch[ch->link_next_ch]);
+ } else {
+ if (!ch->auto_init)
+ omap_dma_disable_channel(s, ch);
+ else if (ch->repeat || ch->end_prog)
+ omap_dma_channel_load(ch);
+ else {
+ ch->waiting_end_prog = 1;
+ omap_dma_deactivate_channel(s, ch);
+ }
+ }
+
+ if (ch->interrupts & END_BLOCK_INTR)
+ ch->status |= END_BLOCK_INTR;
+ }
+ }
+ } while (status == ch->status && ch->active);
+
+ omap_dma_interrupts_update(s);
+#endif
+}
+
+enum {
+ omap_dma_intr_element_sync,
+ omap_dma_intr_last_frame,
+ omap_dma_intr_half_frame,
+ omap_dma_intr_frame,
+ omap_dma_intr_frame_sync,
+ omap_dma_intr_packet,
+ omap_dma_intr_packet_sync,
+ omap_dma_intr_block,
+ __omap_dma_intr_last,
+};
+
+static void omap_dma_transfer_setup(struct soc_dma_ch_s *dma)
+{
+ struct omap_dma_port_if_s *src_p, *dest_p;
+ struct omap_dma_reg_set_s *a;
+ struct omap_dma_channel_s *ch = dma->opaque;
+ struct omap_dma_s *s = dma->dma->opaque;
+ int frames, min_elems, elements[__omap_dma_intr_last];
+
+ a = &ch->active_set;
+
+ src_p = &s->mpu->port[ch->port[0]];
+ dest_p = &s->mpu->port[ch->port[1]];
+ if ((!ch->constant_fill && !src_p->addr_valid(s->mpu, a->src)) ||
+ (!dest_p->addr_valid(s->mpu, a->dest))) {
+#if 0
+ /* Bus time-out */
+ if (ch->interrupts & TIMEOUT_INTR)
+ ch->status |= TIMEOUT_INTR;
+ omap_dma_deactivate_channel(s, ch);
+ continue;
+#endif
+ printf("%s: Bus time-out in DMA%i operation\n",
+ __FUNCTION__, dma->num);
+ }
+
+ min_elems = INT_MAX;
+
+ /* Check all the conditions that terminate the transfer starting
+ * with those that can occur the soonest. */
+#define INTR_CHECK(cond, id, nelements) \
+ if (cond) { \
+ elements[id] = nelements; \
+ if (elements[id] < min_elems) \
+ min_elems = elements[id]; \
+ } else \
+ elements[id] = INT_MAX;
+
+ /* Elements */
+ INTR_CHECK(
+ ch->sync && !ch->fs && !ch->bs,
+ omap_dma_intr_element_sync,
+ 1)
+
+ /* Frames */
+ /* TODO: for transfers where entire frames can be read and written
+ * using memcpy() but a->frame_delta is non-zero, try to still do
+ * transfers using soc_dma but limit min_elems to a->elements - ...
+ * See also the TODO in omap_dma_channel_load. */
+ INTR_CHECK(
+ (ch->interrupts & LAST_FRAME_INTR) &&
+ ((a->frame < a->frames - 1) || !a->element),
+ omap_dma_intr_last_frame,
+ (a->frames - a->frame - 2) * a->elements +
+ (a->elements - a->element + 1))
+ INTR_CHECK(
+ ch->interrupts & HALF_FRAME_INTR,
+ omap_dma_intr_half_frame,
+ (a->elements >> 1) +
+ (a->element >= (a->elements >> 1) ? a->elements : 0) -
+ a->element)
+ INTR_CHECK(
+ ch->sync && ch->fs && (ch->interrupts & END_FRAME_INTR),
+ omap_dma_intr_frame,
+ a->elements - a->element)
+ INTR_CHECK(
+ ch->sync && ch->fs && !ch->bs,
+ omap_dma_intr_frame_sync,
+ a->elements - a->element)
+
+ /* Packets */
+ INTR_CHECK(
+ ch->fs && ch->bs &&
+ (ch->interrupts & END_PKT_INTR) && !ch->src_sync,
+ omap_dma_intr_packet,
+ a->pck_elements - a->pck_element)
+ INTR_CHECK(
+ ch->fs && ch->bs && ch->sync,
+ omap_dma_intr_packet_sync,
+ a->pck_elements - a->pck_element)
+
+ /* Blocks */
+ INTR_CHECK(
+ 1,
+ omap_dma_intr_block,
+ (a->frames - a->frame - 1) * a->elements +
+ (a->elements - a->element))
+
+ dma->bytes = min_elems * ch->data_type;
+
+ /* Set appropriate interrupts and/or deactivate channels */
+
+#ifdef MULTI_REQ
+ /* TODO: should all of this only be done if dma->update, and otherwise
+ * inside omap_dma_transfer_generic below - check what's faster. */
+ if (dma->update) {
+#endif
+
+ /* If the channel is element synchronized, deactivate it */
+ if (min_elems == elements[omap_dma_intr_element_sync])
+ omap_dma_deactivate_channel(s, ch);
+
+ /* If it is the last frame, set the LAST_FRAME interrupt */
+ if (min_elems == elements[omap_dma_intr_last_frame])
+ ch->status |= LAST_FRAME_INTR;
+
+ /* If exactly half of the frame was reached, set the HALF_FRAME
+ interrupt */
+ if (min_elems == elements[omap_dma_intr_half_frame])
+ ch->status |= HALF_FRAME_INTR;
+
+ /* If a full packet has been transferred, set the END_PKT interrupt */
+ if (min_elems == elements[omap_dma_intr_packet])
+ ch->status |= END_PKT_INTR;
+
+ /* If the channel is packet-synchronized, deactivate it */
+ if (min_elems == elements[omap_dma_intr_packet_sync])
+ omap_dma_deactivate_channel(s, ch);
+
+ /* If the channel is frame synchronized, deactivate it */
+ if (min_elems == elements[omap_dma_intr_frame_sync])
+ omap_dma_deactivate_channel(s, ch);
+
+ /* Set the END_FRAME interrupt */
+ if (min_elems == elements[omap_dma_intr_frame])
+ ch->status |= END_FRAME_INTR;
+
+ if (min_elems == elements[omap_dma_intr_block]) {
+ /* End of Block */
+ /* Disable the channel */
+
+ if (ch->omap_3_1_compatible_disable) {
+ omap_dma_disable_channel(s, ch);
+ if (ch->link_enabled)
+ omap_dma_enable_channel(s, &s->ch[ch->link_next_ch]);
+ } else {
+ if (!ch->auto_init)
+ omap_dma_disable_channel(s, ch);
+ else if (ch->repeat || ch->end_prog)
+ omap_dma_channel_load(ch);
+ else {
+ ch->waiting_end_prog = 1;
+ omap_dma_deactivate_channel(s, ch);
+ }
+ }
+
+ if (ch->interrupts & END_BLOCK_INTR)
+ ch->status |= END_BLOCK_INTR;
+ }
+
+ /* Update packet number */
+ if (ch->fs && ch->bs) {
+ a->pck_element += min_elems;
+ a->pck_element %= a->pck_elements;
+ }
+
+ /* TODO: check if we really need to update anything here or perhaps we
+ * can skip part of this. */
+#ifndef MULTI_REQ
+ if (dma->update) {
+#endif
+ a->element += min_elems;
+
+ frames = a->element / a->elements;
+ a->element = a->element % a->elements;
+ a->frame += frames;
+ a->src += min_elems * a->elem_delta[0] + frames * a->frame_delta[0];
+ a->dest += min_elems * a->elem_delta[1] + frames * a->frame_delta[1];
+
+ /* If the channel is async, update cpc */
+ if (!ch->sync && frames)
+ ch->cpc = a->dest & 0xffff;
+
+ /* TODO: if the destination port is IMIF or EMIFF, set the dirty
+ * bits on it. */
+#ifndef MULTI_REQ
+ }
+#else
+ }
+#endif
+
+ omap_dma_interrupts_update(s);
+}
+
+void omap_dma_reset(struct soc_dma_s *dma)
+{
+ int i;
+ struct omap_dma_s *s = dma->opaque;
+
+ soc_dma_reset(s->dma);
+ if (s->model < omap_dma_4)
+ s->gcr = 0x0004;
+ else
+ s->gcr = 0x00010010;
+ s->ocp = 0x00000000;
+ memset(&s->irqstat, 0, sizeof(s->irqstat));
+ memset(&s->irqen, 0, sizeof(s->irqen));
+ s->lcd_ch.src = emiff;
+ s->lcd_ch.condition = 0;
+ s->lcd_ch.interrupts = 0;
+ s->lcd_ch.dual = 0;
+ if (s->model < omap_dma_4)
+ omap_dma_enable_3_1_mapping(s);
+ for (i = 0; i < s->chans; i ++) {
+ s->ch[i].suspend = 0;
+ s->ch[i].prefetch = 0;
+ s->ch[i].buf_disable = 0;
+ s->ch[i].src_sync = 0;
+ memset(&s->ch[i].burst, 0, sizeof(s->ch[i].burst));
+ memset(&s->ch[i].port, 0, sizeof(s->ch[i].port));
+ memset(&s->ch[i].mode, 0, sizeof(s->ch[i].mode));
+ memset(&s->ch[i].frame_index, 0, sizeof(s->ch[i].frame_index));
+ memset(&s->ch[i].element_index, 0, sizeof(s->ch[i].element_index));
+ memset(&s->ch[i].endian, 0, sizeof(s->ch[i].endian));
+ memset(&s->ch[i].endian_lock, 0, sizeof(s->ch[i].endian_lock));
+ memset(&s->ch[i].translate, 0, sizeof(s->ch[i].translate));
+ s->ch[i].write_mode = 0;
+ s->ch[i].data_type = 0;
+ s->ch[i].transparent_copy = 0;
+ s->ch[i].constant_fill = 0;
+ s->ch[i].color = 0x00000000;
+ s->ch[i].end_prog = 0;
+ s->ch[i].repeat = 0;
+ s->ch[i].auto_init = 0;
+ s->ch[i].link_enabled = 0;
+ if (s->model < omap_dma_4)
+ s->ch[i].interrupts = 0x0003;
+ else
+ s->ch[i].interrupts = 0x0000;
+ s->ch[i].status = 0;
+ s->ch[i].cstatus = 0;
+ s->ch[i].active = 0;
+ s->ch[i].enable = 0;
+ s->ch[i].sync = 0;
+ s->ch[i].pending_request = 0;
+ s->ch[i].waiting_end_prog = 0;
+ s->ch[i].cpc = 0x0000;
+ s->ch[i].fs = 0;
+ s->ch[i].bs = 0;
+ s->ch[i].omap_3_1_compatible_disable = 0;
+ memset(&s->ch[i].active_set, 0, sizeof(s->ch[i].active_set));
+ s->ch[i].priority = 0;
+ s->ch[i].interleave_disabled = 0;
+ s->ch[i].type = 0;
+ }
+}
+
+static int omap_dma_ch_reg_read(struct omap_dma_s *s,
+ struct omap_dma_channel_s *ch, int reg, uint16_t *value)
+{
+ switch (reg) {
+ case 0x00: /* SYS_DMA_CSDP_CH0 */
+ *value = (ch->burst[1] << 14) |
+ (ch->pack[1] << 13) |
+ (ch->port[1] << 9) |
+ (ch->burst[0] << 7) |
+ (ch->pack[0] << 6) |
+ (ch->port[0] << 2) |
+ (ch->data_type >> 1);
+ break;
+
+ case 0x02: /* SYS_DMA_CCR_CH0 */
+ if (s->model <= omap_dma_3_1)
+ *value = 0 << 10; /* FIFO_FLUSH reads as 0 */
+ else
+ *value = ch->omap_3_1_compatible_disable << 10;
+ *value |= (ch->mode[1] << 14) |
+ (ch->mode[0] << 12) |
+ (ch->end_prog << 11) |
+ (ch->repeat << 9) |
+ (ch->auto_init << 8) |
+ (ch->enable << 7) |
+ (ch->priority << 6) |
+ (ch->fs << 5) | ch->sync;
+ break;
+
+ case 0x04: /* SYS_DMA_CICR_CH0 */
+ *value = ch->interrupts;
+ break;
+
+ case 0x06: /* SYS_DMA_CSR_CH0 */
+ *value = ch->status;
+ ch->status &= SYNC;
+ if (!ch->omap_3_1_compatible_disable && ch->sibling) {
+ *value |= (ch->sibling->status & 0x3f) << 6;
+ ch->sibling->status &= SYNC;
+ }
+ qemu_irq_lower(ch->irq);
+ break;
+
+ case 0x08: /* SYS_DMA_CSSA_L_CH0 */
+ *value = ch->addr[0] & 0x0000ffff;
+ break;
+
+ case 0x0a: /* SYS_DMA_CSSA_U_CH0 */
+ *value = ch->addr[0] >> 16;
+ break;
+
+ case 0x0c: /* SYS_DMA_CDSA_L_CH0 */
+ *value = ch->addr[1] & 0x0000ffff;
+ break;
+
+ case 0x0e: /* SYS_DMA_CDSA_U_CH0 */
+ *value = ch->addr[1] >> 16;
+ break;
+
+ case 0x10: /* SYS_DMA_CEN_CH0 */
+ *value = ch->elements;
+ break;
+
+ case 0x12: /* SYS_DMA_CFN_CH0 */
+ *value = ch->frames;
+ break;
+
+ case 0x14: /* SYS_DMA_CFI_CH0 */
+ *value = ch->frame_index[0];
+ break;
+
+ case 0x16: /* SYS_DMA_CEI_CH0 */
+ *value = ch->element_index[0];
+ break;
+
+ case 0x18: /* SYS_DMA_CPC_CH0 or DMA_CSAC */
+ if (ch->omap_3_1_compatible_disable)
+ *value = ch->active_set.src & 0xffff; /* CSAC */
+ else
+ *value = ch->cpc;
+ break;
+
+ case 0x1a: /* DMA_CDAC */
+ *value = ch->active_set.dest & 0xffff; /* CDAC */
+ break;
+
+ case 0x1c: /* DMA_CDEI */
+ *value = ch->element_index[1];
+ break;
+
+ case 0x1e: /* DMA_CDFI */
+ *value = ch->frame_index[1];
+ break;
+
+ case 0x20: /* DMA_COLOR_L */
+ *value = ch->color & 0xffff;
+ break;
+
+ case 0x22: /* DMA_COLOR_U */
+ *value = ch->color >> 16;
+ break;
+
+ case 0x24: /* DMA_CCR2 */
+ *value = (ch->bs << 2) |
+ (ch->transparent_copy << 1) |
+ ch->constant_fill;
+ break;
+
+ case 0x28: /* DMA_CLNK_CTRL */
+ *value = (ch->link_enabled << 15) |
+ (ch->link_next_ch & 0xf);
+ break;
+
+ case 0x2a: /* DMA_LCH_CTRL */
+ *value = (ch->interleave_disabled << 15) |
+ ch->type;
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static int omap_dma_ch_reg_write(struct omap_dma_s *s,
+ struct omap_dma_channel_s *ch, int reg, uint16_t value)
+{
+ switch (reg) {
+ case 0x00: /* SYS_DMA_CSDP_CH0 */
+ ch->burst[1] = (value & 0xc000) >> 14;
+ ch->pack[1] = (value & 0x2000) >> 13;
+ ch->port[1] = (enum omap_dma_port) ((value & 0x1e00) >> 9);
+ ch->burst[0] = (value & 0x0180) >> 7;
+ ch->pack[0] = (value & 0x0040) >> 6;
+ ch->port[0] = (enum omap_dma_port) ((value & 0x003c) >> 2);
+ ch->data_type = 1 << (value & 3);
+ if (ch->port[0] >= __omap_dma_port_last)
+ printf("%s: invalid DMA port %i\n", __FUNCTION__,
+ ch->port[0]);
+ if (ch->port[1] >= __omap_dma_port_last)
+ printf("%s: invalid DMA port %i\n", __FUNCTION__,
+ ch->port[1]);
+ if ((value & 3) == 3)
+ printf("%s: bad data_type for DMA channel\n", __FUNCTION__);
+ break;
+
+ case 0x02: /* SYS_DMA_CCR_CH0 */
+ ch->mode[1] = (omap_dma_addressing_t) ((value & 0xc000) >> 14);
+ ch->mode[0] = (omap_dma_addressing_t) ((value & 0x3000) >> 12);
+ ch->end_prog = (value & 0x0800) >> 11;
+ if (s->model >= omap_dma_3_2)
+ ch->omap_3_1_compatible_disable = (value >> 10) & 0x1;
+ ch->repeat = (value & 0x0200) >> 9;
+ ch->auto_init = (value & 0x0100) >> 8;
+ ch->priority = (value & 0x0040) >> 6;
+ ch->fs = (value & 0x0020) >> 5;
+ ch->sync = value & 0x001f;
+
+ if (value & 0x0080)
+ omap_dma_enable_channel(s, ch);
+ else
+ omap_dma_disable_channel(s, ch);
+
+ if (ch->end_prog)
+ omap_dma_channel_end_prog(s, ch);
+
+ break;
+
+ case 0x04: /* SYS_DMA_CICR_CH0 */
+ ch->interrupts = value & 0x3f;
+ break;
+
+ case 0x06: /* SYS_DMA_CSR_CH0 */
+ OMAP_RO_REG((hwaddr) reg);
+ break;
+
+ case 0x08: /* SYS_DMA_CSSA_L_CH0 */
+ ch->addr[0] &= 0xffff0000;
+ ch->addr[0] |= value;
+ break;
+
+ case 0x0a: /* SYS_DMA_CSSA_U_CH0 */
+ ch->addr[0] &= 0x0000ffff;
+ ch->addr[0] |= (uint32_t) value << 16;
+ break;
+
+ case 0x0c: /* SYS_DMA_CDSA_L_CH0 */
+ ch->addr[1] &= 0xffff0000;
+ ch->addr[1] |= value;
+ break;
+
+ case 0x0e: /* SYS_DMA_CDSA_U_CH0 */
+ ch->addr[1] &= 0x0000ffff;
+ ch->addr[1] |= (uint32_t) value << 16;
+ break;
+
+ case 0x10: /* SYS_DMA_CEN_CH0 */
+ ch->elements = value;
+ break;
+
+ case 0x12: /* SYS_DMA_CFN_CH0 */
+ ch->frames = value;
+ break;
+
+ case 0x14: /* SYS_DMA_CFI_CH0 */
+ ch->frame_index[0] = (int16_t) value;
+ break;
+
+ case 0x16: /* SYS_DMA_CEI_CH0 */
+ ch->element_index[0] = (int16_t) value;
+ break;
+
+ case 0x18: /* SYS_DMA_CPC_CH0 or DMA_CSAC */
+ OMAP_RO_REG((hwaddr) reg);
+ break;
+
+ case 0x1c: /* DMA_CDEI */
+ ch->element_index[1] = (int16_t) value;
+ break;
+
+ case 0x1e: /* DMA_CDFI */
+ ch->frame_index[1] = (int16_t) value;
+ break;
+
+ case 0x20: /* DMA_COLOR_L */
+ ch->color &= 0xffff0000;
+ ch->color |= value;
+ break;
+
+ case 0x22: /* DMA_COLOR_U */
+ ch->color &= 0xffff;
+ ch->color |= (uint32_t)value << 16;
+ break;
+
+ case 0x24: /* DMA_CCR2 */
+ ch->bs = (value >> 2) & 0x1;
+ ch->transparent_copy = (value >> 1) & 0x1;
+ ch->constant_fill = value & 0x1;
+ break;
+
+ case 0x28: /* DMA_CLNK_CTRL */
+ ch->link_enabled = (value >> 15) & 0x1;
+ if (value & (1 << 14)) { /* Stop_Lnk */
+ ch->link_enabled = 0;
+ omap_dma_disable_channel(s, ch);
+ }
+ ch->link_next_ch = value & 0x1f;
+ break;
+
+ case 0x2a: /* DMA_LCH_CTRL */
+ ch->interleave_disabled = (value >> 15) & 0x1;
+ ch->type = value & 0xf;
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static int omap_dma_3_2_lcd_write(struct omap_dma_lcd_channel_s *s, int offset,
+ uint16_t value)
+{
+ switch (offset) {
+ case 0xbc0: /* DMA_LCD_CSDP */
+ s->brust_f2 = (value >> 14) & 0x3;
+ s->pack_f2 = (value >> 13) & 0x1;
+ s->data_type_f2 = (1 << ((value >> 11) & 0x3));
+ s->brust_f1 = (value >> 7) & 0x3;
+ s->pack_f1 = (value >> 6) & 0x1;
+ s->data_type_f1 = (1 << ((value >> 0) & 0x3));
+ break;
+
+ case 0xbc2: /* DMA_LCD_CCR */
+ s->mode_f2 = (value >> 14) & 0x3;
+ s->mode_f1 = (value >> 12) & 0x3;
+ s->end_prog = (value >> 11) & 0x1;
+ s->omap_3_1_compatible_disable = (value >> 10) & 0x1;
+ s->repeat = (value >> 9) & 0x1;
+ s->auto_init = (value >> 8) & 0x1;
+ s->running = (value >> 7) & 0x1;
+ s->priority = (value >> 6) & 0x1;
+ s->bs = (value >> 4) & 0x1;
+ break;
+
+ case 0xbc4: /* DMA_LCD_CTRL */
+ s->dst = (value >> 8) & 0x1;
+ s->src = ((value >> 6) & 0x3) << 1;
+ s->condition = 0;
+ /* Assume no bus errors and thus no BUS_ERROR irq bits. */
+ s->interrupts = (value >> 1) & 1;
+ s->dual = value & 1;
+ break;
+
+ case 0xbc8: /* TOP_B1_L */
+ s->src_f1_top &= 0xffff0000;
+ s->src_f1_top |= 0x0000ffff & value;
+ break;
+
+ case 0xbca: /* TOP_B1_U */
+ s->src_f1_top &= 0x0000ffff;
+ s->src_f1_top |= (uint32_t)value << 16;
+ break;
+
+ case 0xbcc: /* BOT_B1_L */
+ s->src_f1_bottom &= 0xffff0000;
+ s->src_f1_bottom |= 0x0000ffff & value;
+ break;
+
+ case 0xbce: /* BOT_B1_U */
+ s->src_f1_bottom &= 0x0000ffff;
+ s->src_f1_bottom |= (uint32_t) value << 16;
+ break;
+
+ case 0xbd0: /* TOP_B2_L */
+ s->src_f2_top &= 0xffff0000;
+ s->src_f2_top |= 0x0000ffff & value;
+ break;
+
+ case 0xbd2: /* TOP_B2_U */
+ s->src_f2_top &= 0x0000ffff;
+ s->src_f2_top |= (uint32_t) value << 16;
+ break;
+
+ case 0xbd4: /* BOT_B2_L */
+ s->src_f2_bottom &= 0xffff0000;
+ s->src_f2_bottom |= 0x0000ffff & value;
+ break;
+
+ case 0xbd6: /* BOT_B2_U */
+ s->src_f2_bottom &= 0x0000ffff;
+ s->src_f2_bottom |= (uint32_t) value << 16;
+ break;
+
+ case 0xbd8: /* DMA_LCD_SRC_EI_B1 */
+ s->element_index_f1 = value;
+ break;
+
+ case 0xbda: /* DMA_LCD_SRC_FI_B1_L */
+ s->frame_index_f1 &= 0xffff0000;
+ s->frame_index_f1 |= 0x0000ffff & value;
+ break;
+
+ case 0xbf4: /* DMA_LCD_SRC_FI_B1_U */
+ s->frame_index_f1 &= 0x0000ffff;
+ s->frame_index_f1 |= (uint32_t) value << 16;
+ break;
+
+ case 0xbdc: /* DMA_LCD_SRC_EI_B2 */
+ s->element_index_f2 = value;
+ break;
+
+ case 0xbde: /* DMA_LCD_SRC_FI_B2_L */
+ s->frame_index_f2 &= 0xffff0000;
+ s->frame_index_f2 |= 0x0000ffff & value;
+ break;
+
+ case 0xbf6: /* DMA_LCD_SRC_FI_B2_U */
+ s->frame_index_f2 &= 0x0000ffff;
+ s->frame_index_f2 |= (uint32_t) value << 16;
+ break;
+
+ case 0xbe0: /* DMA_LCD_SRC_EN_B1 */
+ s->elements_f1 = value;
+ break;
+
+ case 0xbe4: /* DMA_LCD_SRC_FN_B1 */
+ s->frames_f1 = value;
+ break;
+
+ case 0xbe2: /* DMA_LCD_SRC_EN_B2 */
+ s->elements_f2 = value;
+ break;
+
+ case 0xbe6: /* DMA_LCD_SRC_FN_B2 */
+ s->frames_f2 = value;
+ break;
+
+ case 0xbea: /* DMA_LCD_LCH_CTRL */
+ s->lch_type = value & 0xf;
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static int omap_dma_3_2_lcd_read(struct omap_dma_lcd_channel_s *s, int offset,
+ uint16_t *ret)
+{
+ switch (offset) {
+ case 0xbc0: /* DMA_LCD_CSDP */
+ *ret = (s->brust_f2 << 14) |
+ (s->pack_f2 << 13) |
+ ((s->data_type_f2 >> 1) << 11) |
+ (s->brust_f1 << 7) |
+ (s->pack_f1 << 6) |
+ ((s->data_type_f1 >> 1) << 0);
+ break;
+
+ case 0xbc2: /* DMA_LCD_CCR */
+ *ret = (s->mode_f2 << 14) |
+ (s->mode_f1 << 12) |
+ (s->end_prog << 11) |
+ (s->omap_3_1_compatible_disable << 10) |
+ (s->repeat << 9) |
+ (s->auto_init << 8) |
+ (s->running << 7) |
+ (s->priority << 6) |
+ (s->bs << 4);
+ break;
+
+ case 0xbc4: /* DMA_LCD_CTRL */
+ qemu_irq_lower(s->irq);
+ *ret = (s->dst << 8) |
+ ((s->src & 0x6) << 5) |
+ (s->condition << 3) |
+ (s->interrupts << 1) |
+ s->dual;
+ break;
+
+ case 0xbc8: /* TOP_B1_L */
+ *ret = s->src_f1_top & 0xffff;
+ break;
+
+ case 0xbca: /* TOP_B1_U */
+ *ret = s->src_f1_top >> 16;
+ break;
+
+ case 0xbcc: /* BOT_B1_L */
+ *ret = s->src_f1_bottom & 0xffff;
+ break;
+
+ case 0xbce: /* BOT_B1_U */
+ *ret = s->src_f1_bottom >> 16;
+ break;
+
+ case 0xbd0: /* TOP_B2_L */
+ *ret = s->src_f2_top & 0xffff;
+ break;
+
+ case 0xbd2: /* TOP_B2_U */
+ *ret = s->src_f2_top >> 16;
+ break;
+
+ case 0xbd4: /* BOT_B2_L */
+ *ret = s->src_f2_bottom & 0xffff;
+ break;
+
+ case 0xbd6: /* BOT_B2_U */
+ *ret = s->src_f2_bottom >> 16;
+ break;
+
+ case 0xbd8: /* DMA_LCD_SRC_EI_B1 */
+ *ret = s->element_index_f1;
+ break;
+
+ case 0xbda: /* DMA_LCD_SRC_FI_B1_L */
+ *ret = s->frame_index_f1 & 0xffff;
+ break;
+
+ case 0xbf4: /* DMA_LCD_SRC_FI_B1_U */
+ *ret = s->frame_index_f1 >> 16;
+ break;
+
+ case 0xbdc: /* DMA_LCD_SRC_EI_B2 */
+ *ret = s->element_index_f2;
+ break;
+
+ case 0xbde: /* DMA_LCD_SRC_FI_B2_L */
+ *ret = s->frame_index_f2 & 0xffff;
+ break;
+
+ case 0xbf6: /* DMA_LCD_SRC_FI_B2_U */
+ *ret = s->frame_index_f2 >> 16;
+ break;
+
+ case 0xbe0: /* DMA_LCD_SRC_EN_B1 */
+ *ret = s->elements_f1;
+ break;
+
+ case 0xbe4: /* DMA_LCD_SRC_FN_B1 */
+ *ret = s->frames_f1;
+ break;
+
+ case 0xbe2: /* DMA_LCD_SRC_EN_B2 */
+ *ret = s->elements_f2;
+ break;
+
+ case 0xbe6: /* DMA_LCD_SRC_FN_B2 */
+ *ret = s->frames_f2;
+ break;
+
+ case 0xbea: /* DMA_LCD_LCH_CTRL */
+ *ret = s->lch_type;
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static int omap_dma_3_1_lcd_write(struct omap_dma_lcd_channel_s *s, int offset,
+ uint16_t value)
+{
+ switch (offset) {
+ case 0x300: /* SYS_DMA_LCD_CTRL */
+ s->src = (value & 0x40) ? imif : emiff;
+ s->condition = 0;
+ /* Assume no bus errors and thus no BUS_ERROR irq bits. */
+ s->interrupts = (value >> 1) & 1;
+ s->dual = value & 1;
+ break;
+
+ case 0x302: /* SYS_DMA_LCD_TOP_F1_L */
+ s->src_f1_top &= 0xffff0000;
+ s->src_f1_top |= 0x0000ffff & value;
+ break;
+
+ case 0x304: /* SYS_DMA_LCD_TOP_F1_U */
+ s->src_f1_top &= 0x0000ffff;
+ s->src_f1_top |= (uint32_t)value << 16;
+ break;
+
+ case 0x306: /* SYS_DMA_LCD_BOT_F1_L */
+ s->src_f1_bottom &= 0xffff0000;
+ s->src_f1_bottom |= 0x0000ffff & value;
+ break;
+
+ case 0x308: /* SYS_DMA_LCD_BOT_F1_U */
+ s->src_f1_bottom &= 0x0000ffff;
+ s->src_f1_bottom |= (uint32_t)value << 16;
+ break;
+
+ case 0x30a: /* SYS_DMA_LCD_TOP_F2_L */
+ s->src_f2_top &= 0xffff0000;
+ s->src_f2_top |= 0x0000ffff & value;
+ break;
+
+ case 0x30c: /* SYS_DMA_LCD_TOP_F2_U */
+ s->src_f2_top &= 0x0000ffff;
+ s->src_f2_top |= (uint32_t)value << 16;
+ break;
+
+ case 0x30e: /* SYS_DMA_LCD_BOT_F2_L */
+ s->src_f2_bottom &= 0xffff0000;
+ s->src_f2_bottom |= 0x0000ffff & value;
+ break;
+
+ case 0x310: /* SYS_DMA_LCD_BOT_F2_U */
+ s->src_f2_bottom &= 0x0000ffff;
+ s->src_f2_bottom |= (uint32_t)value << 16;
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static int omap_dma_3_1_lcd_read(struct omap_dma_lcd_channel_s *s, int offset,
+ uint16_t *ret)
+{
+ int i;
+
+ switch (offset) {
+ case 0x300: /* SYS_DMA_LCD_CTRL */
+ i = s->condition;
+ s->condition = 0;
+ qemu_irq_lower(s->irq);
+ *ret = ((s->src == imif) << 6) | (i << 3) |
+ (s->interrupts << 1) | s->dual;
+ break;
+
+ case 0x302: /* SYS_DMA_LCD_TOP_F1_L */
+ *ret = s->src_f1_top & 0xffff;
+ break;
+
+ case 0x304: /* SYS_DMA_LCD_TOP_F1_U */
+ *ret = s->src_f1_top >> 16;
+ break;
+
+ case 0x306: /* SYS_DMA_LCD_BOT_F1_L */
+ *ret = s->src_f1_bottom & 0xffff;
+ break;
+
+ case 0x308: /* SYS_DMA_LCD_BOT_F1_U */
+ *ret = s->src_f1_bottom >> 16;
+ break;
+
+ case 0x30a: /* SYS_DMA_LCD_TOP_F2_L */
+ *ret = s->src_f2_top & 0xffff;
+ break;
+
+ case 0x30c: /* SYS_DMA_LCD_TOP_F2_U */
+ *ret = s->src_f2_top >> 16;
+ break;
+
+ case 0x30e: /* SYS_DMA_LCD_BOT_F2_L */
+ *ret = s->src_f2_bottom & 0xffff;
+ break;
+
+ case 0x310: /* SYS_DMA_LCD_BOT_F2_U */
+ *ret = s->src_f2_bottom >> 16;
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static int omap_dma_sys_write(struct omap_dma_s *s, int offset, uint16_t value)
+{
+ switch (offset) {
+ case 0x400: /* SYS_DMA_GCR */
+ s->gcr = value;
+ break;
+
+ case 0x404: /* DMA_GSCR */
+ if (value & 0x8)
+ omap_dma_disable_3_1_mapping(s);
+ else
+ omap_dma_enable_3_1_mapping(s);
+ break;
+
+ case 0x408: /* DMA_GRST */
+ if (value & 0x1)
+ omap_dma_reset(s->dma);
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static int omap_dma_sys_read(struct omap_dma_s *s, int offset,
+ uint16_t *ret)
+{
+ switch (offset) {
+ case 0x400: /* SYS_DMA_GCR */
+ *ret = s->gcr;
+ break;
+
+ case 0x404: /* DMA_GSCR */
+ *ret = s->omap_3_1_mapping_disabled << 3;
+ break;
+
+ case 0x408: /* DMA_GRST */
+ *ret = 0;
+ break;
+
+ case 0x442: /* DMA_HW_ID */
+ case 0x444: /* DMA_PCh2_ID */
+ case 0x446: /* DMA_PCh0_ID */
+ case 0x448: /* DMA_PCh1_ID */
+ case 0x44a: /* DMA_PChG_ID */
+ case 0x44c: /* DMA_PChD_ID */
+ *ret = 1;
+ break;
+
+ case 0x44e: /* DMA_CAPS_0_U */
+ *ret = (s->caps[0] >> 16) & 0xffff;
+ break;
+ case 0x450: /* DMA_CAPS_0_L */
+ *ret = (s->caps[0] >> 0) & 0xffff;
+ break;
+
+ case 0x452: /* DMA_CAPS_1_U */
+ *ret = (s->caps[1] >> 16) & 0xffff;
+ break;
+ case 0x454: /* DMA_CAPS_1_L */
+ *ret = (s->caps[1] >> 0) & 0xffff;
+ break;
+
+ case 0x456: /* DMA_CAPS_2 */
+ *ret = s->caps[2];
+ break;
+
+ case 0x458: /* DMA_CAPS_3 */
+ *ret = s->caps[3];
+ break;
+
+ case 0x45a: /* DMA_CAPS_4 */
+ *ret = s->caps[4];
+ break;
+
+ case 0x460: /* DMA_PCh2_SR */
+ case 0x480: /* DMA_PCh0_SR */
+ case 0x482: /* DMA_PCh1_SR */
+ case 0x4c0: /* DMA_PChD_SR_0 */
+ printf("%s: Physical Channel Status Registers not implemented.\n",
+ __FUNCTION__);
+ *ret = 0xff;
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static uint64_t omap_dma_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_dma_s *s = (struct omap_dma_s *) opaque;
+ int reg, ch;
+ uint16_t ret;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x300 ... 0x3fe:
+ if (s->model <= omap_dma_3_1 || !s->omap_3_1_mapping_disabled) {
+ if (omap_dma_3_1_lcd_read(&s->lcd_ch, addr, &ret))
+ break;
+ return ret;
+ }
+ /* Fall through. */
+ case 0x000 ... 0x2fe:
+ reg = addr & 0x3f;
+ ch = (addr >> 6) & 0x0f;
+ if (omap_dma_ch_reg_read(s, &s->ch[ch], reg, &ret))
+ break;
+ return ret;
+
+ case 0x404 ... 0x4fe:
+ if (s->model <= omap_dma_3_1)
+ break;
+ /* Fall through. */
+ case 0x400:
+ if (omap_dma_sys_read(s, addr, &ret))
+ break;
+ return ret;
+
+ case 0xb00 ... 0xbfe:
+ if (s->model == omap_dma_3_2 && s->omap_3_1_mapping_disabled) {
+ if (omap_dma_3_2_lcd_read(&s->lcd_ch, addr, &ret))
+ break;
+ return ret;
+ }
+ break;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_dma_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_dma_s *s = (struct omap_dma_s *) opaque;
+ int reg, ch;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x300 ... 0x3fe:
+ if (s->model <= omap_dma_3_1 || !s->omap_3_1_mapping_disabled) {
+ if (omap_dma_3_1_lcd_write(&s->lcd_ch, addr, value))
+ break;
+ return;
+ }
+ /* Fall through. */
+ case 0x000 ... 0x2fe:
+ reg = addr & 0x3f;
+ ch = (addr >> 6) & 0x0f;
+ if (omap_dma_ch_reg_write(s, &s->ch[ch], reg, value))
+ break;
+ return;
+
+ case 0x404 ... 0x4fe:
+ if (s->model <= omap_dma_3_1)
+ break;
+ case 0x400:
+ /* Fall through. */
+ if (omap_dma_sys_write(s, addr, value))
+ break;
+ return;
+
+ case 0xb00 ... 0xbfe:
+ if (s->model == omap_dma_3_2 && s->omap_3_1_mapping_disabled) {
+ if (omap_dma_3_2_lcd_write(&s->lcd_ch, addr, value))
+ break;
+ return;
+ }
+ break;
+ }
+
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap_dma_ops = {
+ .read = omap_dma_read,
+ .write = omap_dma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_dma_request(void *opaque, int drq, int req)
+{
+ struct omap_dma_s *s = (struct omap_dma_s *) opaque;
+ /* The request pins are level triggered in QEMU. */
+ if (req) {
+ if (~s->dma->drqbmp & (1ULL << drq)) {
+ s->dma->drqbmp |= 1ULL << drq;
+ omap_dma_process_request(s, drq);
+ }
+ } else
+ s->dma->drqbmp &= ~(1ULL << drq);
+}
+
+/* XXX: this won't be needed once soc_dma knows about clocks. */
+static void omap_dma_clk_update(void *opaque, int line, int on)
+{
+ struct omap_dma_s *s = (struct omap_dma_s *) opaque;
+ int i;
+
+ s->dma->freq = omap_clk_getrate(s->clk);
+
+ for (i = 0; i < s->chans; i ++)
+ if (s->ch[i].active)
+ soc_dma_set_request(s->ch[i].dma, on);
+}
+
+static void omap_dma_setcaps(struct omap_dma_s *s)
+{
+ switch (s->model) {
+ default:
+ case omap_dma_3_1:
+ break;
+ case omap_dma_3_2:
+ case omap_dma_4:
+ /* XXX Only available for sDMA */
+ s->caps[0] =
+ (1 << 19) | /* Constant Fill Capability */
+ (1 << 18); /* Transparent BLT Capability */
+ s->caps[1] =
+ (1 << 1); /* 1-bit palettized capability (DMA 3.2 only) */
+ s->caps[2] =
+ (1 << 8) | /* SEPARATE_SRC_AND_DST_INDEX_CPBLTY */
+ (1 << 7) | /* DST_DOUBLE_INDEX_ADRS_CPBLTY */
+ (1 << 6) | /* DST_SINGLE_INDEX_ADRS_CPBLTY */
+ (1 << 5) | /* DST_POST_INCRMNT_ADRS_CPBLTY */
+ (1 << 4) | /* DST_CONST_ADRS_CPBLTY */
+ (1 << 3) | /* SRC_DOUBLE_INDEX_ADRS_CPBLTY */
+ (1 << 2) | /* SRC_SINGLE_INDEX_ADRS_CPBLTY */
+ (1 << 1) | /* SRC_POST_INCRMNT_ADRS_CPBLTY */
+ (1 << 0); /* SRC_CONST_ADRS_CPBLTY */
+ s->caps[3] =
+ (1 << 6) | /* BLOCK_SYNCHR_CPBLTY (DMA 4 only) */
+ (1 << 7) | /* PKT_SYNCHR_CPBLTY (DMA 4 only) */
+ (1 << 5) | /* CHANNEL_CHAINING_CPBLTY */
+ (1 << 4) | /* LCh_INTERLEAVE_CPBLTY */
+ (1 << 3) | /* AUTOINIT_REPEAT_CPBLTY (DMA 3.2 only) */
+ (1 << 2) | /* AUTOINIT_ENDPROG_CPBLTY (DMA 3.2 only) */
+ (1 << 1) | /* FRAME_SYNCHR_CPBLTY */
+ (1 << 0); /* ELMNT_SYNCHR_CPBLTY */
+ s->caps[4] =
+ (1 << 7) | /* PKT_INTERRUPT_CPBLTY (DMA 4 only) */
+ (1 << 6) | /* SYNC_STATUS_CPBLTY */
+ (1 << 5) | /* BLOCK_INTERRUPT_CPBLTY */
+ (1 << 4) | /* LAST_FRAME_INTERRUPT_CPBLTY */
+ (1 << 3) | /* FRAME_INTERRUPT_CPBLTY */
+ (1 << 2) | /* HALF_FRAME_INTERRUPT_CPBLTY */
+ (1 << 1) | /* EVENT_DROP_INTERRUPT_CPBLTY */
+ (1 << 0); /* TIMEOUT_INTERRUPT_CPBLTY (DMA 3.2 only) */
+ break;
+ }
+}
+
+struct soc_dma_s *omap_dma_init(hwaddr base, qemu_irq *irqs,
+ MemoryRegion *sysmem,
+ qemu_irq lcd_irq, struct omap_mpu_state_s *mpu, omap_clk clk,
+ enum omap_dma_model model)
+{
+ int num_irqs, memsize, i;
+ struct omap_dma_s *s = (struct omap_dma_s *)
+ g_malloc0(sizeof(struct omap_dma_s));
+
+ if (model <= omap_dma_3_1) {
+ num_irqs = 6;
+ memsize = 0x800;
+ } else {
+ num_irqs = 16;
+ memsize = 0xc00;
+ }
+ s->model = model;
+ s->mpu = mpu;
+ s->clk = clk;
+ s->lcd_ch.irq = lcd_irq;
+ s->lcd_ch.mpu = mpu;
+
+ s->dma = soc_dma_init((model <= omap_dma_3_1) ? 9 : 16);
+ s->dma->freq = omap_clk_getrate(clk);
+ s->dma->transfer_fn = omap_dma_transfer_generic;
+ s->dma->setup_fn = omap_dma_transfer_setup;
+ s->dma->drq = qemu_allocate_irqs(omap_dma_request, s, 32);
+ s->dma->opaque = s;
+
+ while (num_irqs --)
+ s->ch[num_irqs].irq = irqs[num_irqs];
+ for (i = 0; i < 3; i ++) {
+ s->ch[i].sibling = &s->ch[i + 6];
+ s->ch[i + 6].sibling = &s->ch[i];
+ }
+ for (i = (model <= omap_dma_3_1) ? 8 : 15; i >= 0; i --) {
+ s->ch[i].dma = &s->dma->ch[i];
+ s->dma->ch[i].opaque = &s->ch[i];
+ }
+
+ omap_dma_setcaps(s);
+ omap_clk_adduser(s->clk, qemu_allocate_irq(omap_dma_clk_update, s, 0));
+ omap_dma_reset(s->dma);
+ omap_dma_clk_update(s, 0, 1);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_dma_ops, s, "omap.dma", memsize);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ mpu->drq = s->dma->drq;
+
+ return s->dma;
+}
+
+static void omap_dma_interrupts_4_update(struct omap_dma_s *s)
+{
+ struct omap_dma_channel_s *ch = s->ch;
+ uint32_t bmp, bit;
+
+ for (bmp = 0, bit = 1; bit; ch ++, bit <<= 1)
+ if (ch->status) {
+ bmp |= bit;
+ ch->cstatus |= ch->status;
+ ch->status = 0;
+ }
+ if ((s->irqstat[0] |= s->irqen[0] & bmp))
+ qemu_irq_raise(s->irq[0]);
+ if ((s->irqstat[1] |= s->irqen[1] & bmp))
+ qemu_irq_raise(s->irq[1]);
+ if ((s->irqstat[2] |= s->irqen[2] & bmp))
+ qemu_irq_raise(s->irq[2]);
+ if ((s->irqstat[3] |= s->irqen[3] & bmp))
+ qemu_irq_raise(s->irq[3]);
+}
+
+static uint64_t omap_dma4_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_dma_s *s = (struct omap_dma_s *) opaque;
+ int irqn = 0, chnum;
+ struct omap_dma_channel_s *ch;
+
+ if (size == 1) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* DMA4_REVISION */
+ return 0x40;
+
+ case 0x14: /* DMA4_IRQSTATUS_L3 */
+ irqn ++;
+ /* fall through */
+ case 0x10: /* DMA4_IRQSTATUS_L2 */
+ irqn ++;
+ /* fall through */
+ case 0x0c: /* DMA4_IRQSTATUS_L1 */
+ irqn ++;
+ /* fall through */
+ case 0x08: /* DMA4_IRQSTATUS_L0 */
+ return s->irqstat[irqn];
+
+ case 0x24: /* DMA4_IRQENABLE_L3 */
+ irqn ++;
+ /* fall through */
+ case 0x20: /* DMA4_IRQENABLE_L2 */
+ irqn ++;
+ /* fall through */
+ case 0x1c: /* DMA4_IRQENABLE_L1 */
+ irqn ++;
+ /* fall through */
+ case 0x18: /* DMA4_IRQENABLE_L0 */
+ return s->irqen[irqn];
+
+ case 0x28: /* DMA4_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x2c: /* DMA4_OCP_SYSCONFIG */
+ return s->ocp;
+
+ case 0x64: /* DMA4_CAPS_0 */
+ return s->caps[0];
+ case 0x6c: /* DMA4_CAPS_2 */
+ return s->caps[2];
+ case 0x70: /* DMA4_CAPS_3 */
+ return s->caps[3];
+ case 0x74: /* DMA4_CAPS_4 */
+ return s->caps[4];
+
+ case 0x78: /* DMA4_GCR */
+ return s->gcr;
+
+ case 0x80 ... 0xfff:
+ addr -= 0x80;
+ chnum = addr / 0x60;
+ ch = s->ch + chnum;
+ addr -= chnum * 0x60;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return 0;
+ }
+
+ /* Per-channel registers */
+ switch (addr) {
+ case 0x00: /* DMA4_CCR */
+ return (ch->buf_disable << 25) |
+ (ch->src_sync << 24) |
+ (ch->prefetch << 23) |
+ ((ch->sync & 0x60) << 14) |
+ (ch->bs << 18) |
+ (ch->transparent_copy << 17) |
+ (ch->constant_fill << 16) |
+ (ch->mode[1] << 14) |
+ (ch->mode[0] << 12) |
+ (0 << 10) | (0 << 9) |
+ (ch->suspend << 8) |
+ (ch->enable << 7) |
+ (ch->priority << 6) |
+ (ch->fs << 5) | (ch->sync & 0x1f);
+
+ case 0x04: /* DMA4_CLNK_CTRL */
+ return (ch->link_enabled << 15) | ch->link_next_ch;
+
+ case 0x08: /* DMA4_CICR */
+ return ch->interrupts;
+
+ case 0x0c: /* DMA4_CSR */
+ return ch->cstatus;
+
+ case 0x10: /* DMA4_CSDP */
+ return (ch->endian[0] << 21) |
+ (ch->endian_lock[0] << 20) |
+ (ch->endian[1] << 19) |
+ (ch->endian_lock[1] << 18) |
+ (ch->write_mode << 16) |
+ (ch->burst[1] << 14) |
+ (ch->pack[1] << 13) |
+ (ch->translate[1] << 9) |
+ (ch->burst[0] << 7) |
+ (ch->pack[0] << 6) |
+ (ch->translate[0] << 2) |
+ (ch->data_type >> 1);
+
+ case 0x14: /* DMA4_CEN */
+ return ch->elements;
+
+ case 0x18: /* DMA4_CFN */
+ return ch->frames;
+
+ case 0x1c: /* DMA4_CSSA */
+ return ch->addr[0];
+
+ case 0x20: /* DMA4_CDSA */
+ return ch->addr[1];
+
+ case 0x24: /* DMA4_CSEI */
+ return ch->element_index[0];
+
+ case 0x28: /* DMA4_CSFI */
+ return ch->frame_index[0];
+
+ case 0x2c: /* DMA4_CDEI */
+ return ch->element_index[1];
+
+ case 0x30: /* DMA4_CDFI */
+ return ch->frame_index[1];
+
+ case 0x34: /* DMA4_CSAC */
+ return ch->active_set.src & 0xffff;
+
+ case 0x38: /* DMA4_CDAC */
+ return ch->active_set.dest & 0xffff;
+
+ case 0x3c: /* DMA4_CCEN */
+ return ch->active_set.element;
+
+ case 0x40: /* DMA4_CCFN */
+ return ch->active_set.frame;
+
+ case 0x44: /* DMA4_COLOR */
+ /* XXX only in sDMA */
+ return ch->color;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return 0;
+ }
+}
+
+static void omap_dma4_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_dma_s *s = (struct omap_dma_s *) opaque;
+ int chnum, irqn = 0;
+ struct omap_dma_channel_s *ch;
+
+ if (size == 1) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x14: /* DMA4_IRQSTATUS_L3 */
+ irqn ++;
+ /* fall through */
+ case 0x10: /* DMA4_IRQSTATUS_L2 */
+ irqn ++;
+ /* fall through */
+ case 0x0c: /* DMA4_IRQSTATUS_L1 */
+ irqn ++;
+ /* fall through */
+ case 0x08: /* DMA4_IRQSTATUS_L0 */
+ s->irqstat[irqn] &= ~value;
+ if (!s->irqstat[irqn])
+ qemu_irq_lower(s->irq[irqn]);
+ return;
+
+ case 0x24: /* DMA4_IRQENABLE_L3 */
+ irqn ++;
+ /* fall through */
+ case 0x20: /* DMA4_IRQENABLE_L2 */
+ irqn ++;
+ /* fall through */
+ case 0x1c: /* DMA4_IRQENABLE_L1 */
+ irqn ++;
+ /* fall through */
+ case 0x18: /* DMA4_IRQENABLE_L0 */
+ s->irqen[irqn] = value;
+ return;
+
+ case 0x2c: /* DMA4_OCP_SYSCONFIG */
+ if (value & 2) /* SOFTRESET */
+ omap_dma_reset(s->dma);
+ s->ocp = value & 0x3321;
+ if (((s->ocp >> 12) & 3) == 3) /* MIDLEMODE */
+ fprintf(stderr, "%s: invalid DMA power mode\n", __FUNCTION__);
+ return;
+
+ case 0x78: /* DMA4_GCR */
+ s->gcr = value & 0x00ff00ff;
+ if ((value & 0xff) == 0x00) /* MAX_CHANNEL_FIFO_DEPTH */
+ fprintf(stderr, "%s: wrong FIFO depth in GCR\n", __FUNCTION__);
+ return;
+
+ case 0x80 ... 0xfff:
+ addr -= 0x80;
+ chnum = addr / 0x60;
+ ch = s->ch + chnum;
+ addr -= chnum * 0x60;
+ break;
+
+ case 0x00: /* DMA4_REVISION */
+ case 0x28: /* DMA4_SYSSTATUS */
+ case 0x64: /* DMA4_CAPS_0 */
+ case 0x6c: /* DMA4_CAPS_2 */
+ case 0x70: /* DMA4_CAPS_3 */
+ case 0x74: /* DMA4_CAPS_4 */
+ OMAP_RO_REG(addr);
+ return;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+
+ /* Per-channel registers */
+ switch (addr) {
+ case 0x00: /* DMA4_CCR */
+ ch->buf_disable = (value >> 25) & 1;
+ ch->src_sync = (value >> 24) & 1; /* XXX For CamDMA must be 1 */
+ if (ch->buf_disable && !ch->src_sync)
+ fprintf(stderr, "%s: Buffering disable is not allowed in "
+ "destination synchronised mode\n", __FUNCTION__);
+ ch->prefetch = (value >> 23) & 1;
+ ch->bs = (value >> 18) & 1;
+ ch->transparent_copy = (value >> 17) & 1;
+ ch->constant_fill = (value >> 16) & 1;
+ ch->mode[1] = (omap_dma_addressing_t) ((value & 0xc000) >> 14);
+ ch->mode[0] = (omap_dma_addressing_t) ((value & 0x3000) >> 12);
+ ch->suspend = (value & 0x0100) >> 8;
+ ch->priority = (value & 0x0040) >> 6;
+ ch->fs = (value & 0x0020) >> 5;
+ if (ch->fs && ch->bs && ch->mode[0] && ch->mode[1])
+ fprintf(stderr, "%s: For a packet transfer at least one port "
+ "must be constant-addressed\n", __FUNCTION__);
+ ch->sync = (value & 0x001f) | ((value >> 14) & 0x0060);
+ /* XXX must be 0x01 for CamDMA */
+
+ if (value & 0x0080)
+ omap_dma_enable_channel(s, ch);
+ else
+ omap_dma_disable_channel(s, ch);
+
+ break;
+
+ case 0x04: /* DMA4_CLNK_CTRL */
+ ch->link_enabled = (value >> 15) & 0x1;
+ ch->link_next_ch = value & 0x1f;
+ break;
+
+ case 0x08: /* DMA4_CICR */
+ ch->interrupts = value & 0x09be;
+ break;
+
+ case 0x0c: /* DMA4_CSR */
+ ch->cstatus &= ~value;
+ break;
+
+ case 0x10: /* DMA4_CSDP */
+ ch->endian[0] =(value >> 21) & 1;
+ ch->endian_lock[0] =(value >> 20) & 1;
+ ch->endian[1] =(value >> 19) & 1;
+ ch->endian_lock[1] =(value >> 18) & 1;
+ if (ch->endian[0] != ch->endian[1])
+ fprintf(stderr, "%s: DMA endiannes conversion enable attempt\n",
+ __FUNCTION__);
+ ch->write_mode = (value >> 16) & 3;
+ ch->burst[1] = (value & 0xc000) >> 14;
+ ch->pack[1] = (value & 0x2000) >> 13;
+ ch->translate[1] = (value & 0x1e00) >> 9;
+ ch->burst[0] = (value & 0x0180) >> 7;
+ ch->pack[0] = (value & 0x0040) >> 6;
+ ch->translate[0] = (value & 0x003c) >> 2;
+ if (ch->translate[0] | ch->translate[1])
+ fprintf(stderr, "%s: bad MReqAddressTranslate sideband signal\n",
+ __FUNCTION__);
+ ch->data_type = 1 << (value & 3);
+ if ((value & 3) == 3)
+ printf("%s: bad data_type for DMA channel\n", __FUNCTION__);
+ break;
+
+ case 0x14: /* DMA4_CEN */
+ ch->set_update = 1;
+ ch->elements = value & 0xffffff;
+ break;
+
+ case 0x18: /* DMA4_CFN */
+ ch->frames = value & 0xffff;
+ ch->set_update = 1;
+ break;
+
+ case 0x1c: /* DMA4_CSSA */
+ ch->addr[0] = (hwaddr) (uint32_t) value;
+ ch->set_update = 1;
+ break;
+
+ case 0x20: /* DMA4_CDSA */
+ ch->addr[1] = (hwaddr) (uint32_t) value;
+ ch->set_update = 1;
+ break;
+
+ case 0x24: /* DMA4_CSEI */
+ ch->element_index[0] = (int16_t) value;
+ ch->set_update = 1;
+ break;
+
+ case 0x28: /* DMA4_CSFI */
+ ch->frame_index[0] = (int32_t) value;
+ ch->set_update = 1;
+ break;
+
+ case 0x2c: /* DMA4_CDEI */
+ ch->element_index[1] = (int16_t) value;
+ ch->set_update = 1;
+ break;
+
+ case 0x30: /* DMA4_CDFI */
+ ch->frame_index[1] = (int32_t) value;
+ ch->set_update = 1;
+ break;
+
+ case 0x44: /* DMA4_COLOR */
+ /* XXX only in sDMA */
+ ch->color = value;
+ break;
+
+ case 0x34: /* DMA4_CSAC */
+ case 0x38: /* DMA4_CDAC */
+ case 0x3c: /* DMA4_CCEN */
+ case 0x40: /* DMA4_CCFN */
+ OMAP_RO_REG(addr);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_dma4_ops = {
+ .read = omap_dma4_read,
+ .write = omap_dma4_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct soc_dma_s *omap_dma4_init(hwaddr base, qemu_irq *irqs,
+ MemoryRegion *sysmem,
+ struct omap_mpu_state_s *mpu, int fifo,
+ int chans, omap_clk iclk, omap_clk fclk)
+{
+ int i;
+ struct omap_dma_s *s = (struct omap_dma_s *)
+ g_malloc0(sizeof(struct omap_dma_s));
+
+ s->model = omap_dma_4;
+ s->chans = chans;
+ s->mpu = mpu;
+ s->clk = fclk;
+
+ s->dma = soc_dma_init(s->chans);
+ s->dma->freq = omap_clk_getrate(fclk);
+ s->dma->transfer_fn = omap_dma_transfer_generic;
+ s->dma->setup_fn = omap_dma_transfer_setup;
+ s->dma->drq = qemu_allocate_irqs(omap_dma_request, s, 64);
+ s->dma->opaque = s;
+ for (i = 0; i < s->chans; i ++) {
+ s->ch[i].dma = &s->dma->ch[i];
+ s->dma->ch[i].opaque = &s->ch[i];
+ }
+
+ memcpy(&s->irq, irqs, sizeof(s->irq));
+ s->intr_update = omap_dma_interrupts_4_update;
+
+ omap_dma_setcaps(s);
+ omap_clk_adduser(s->clk, qemu_allocate_irq(omap_dma_clk_update, s, 0));
+ omap_dma_reset(s->dma);
+ omap_dma_clk_update(s, 0, !!s->dma->freq);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_dma4_ops, s, "omap.dma4", 0x1000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ mpu->drq = s->dma->drq;
+
+ return s->dma;
+}
+
+struct omap_dma_lcd_channel_s *omap_dma_get_lcdch(struct soc_dma_s *dma)
+{
+ struct omap_dma_s *s = dma->opaque;
+
+ return &s->lcd_ch;
+}
diff --git a/hw/dma/pl080.c b/hw/dma/pl080.c
new file mode 100644
index 00000000..b89b4744
--- /dev/null
+++ b/hw/dma/pl080.c
@@ -0,0 +1,422 @@
+/*
+ * Arm PrimeCell PL080/PL081 DMA controller
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+
+#define PL080_MAX_CHANNELS 8
+#define PL080_CONF_E 0x1
+#define PL080_CONF_M1 0x2
+#define PL080_CONF_M2 0x4
+
+#define PL080_CCONF_H 0x40000
+#define PL080_CCONF_A 0x20000
+#define PL080_CCONF_L 0x10000
+#define PL080_CCONF_ITC 0x08000
+#define PL080_CCONF_IE 0x04000
+#define PL080_CCONF_E 0x00001
+
+#define PL080_CCTRL_I 0x80000000
+#define PL080_CCTRL_DI 0x08000000
+#define PL080_CCTRL_SI 0x04000000
+#define PL080_CCTRL_D 0x02000000
+#define PL080_CCTRL_S 0x01000000
+
+typedef struct {
+ uint32_t src;
+ uint32_t dest;
+ uint32_t lli;
+ uint32_t ctrl;
+ uint32_t conf;
+} pl080_channel;
+
+#define TYPE_PL080 "pl080"
+#define PL080(obj) OBJECT_CHECK(PL080State, (obj), TYPE_PL080)
+
+typedef struct PL080State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint8_t tc_int;
+ uint8_t tc_mask;
+ uint8_t err_int;
+ uint8_t err_mask;
+ uint32_t conf;
+ uint32_t sync;
+ uint32_t req_single;
+ uint32_t req_burst;
+ pl080_channel chan[PL080_MAX_CHANNELS];
+ int nchannels;
+ /* Flag to avoid recursive DMA invocations. */
+ int running;
+ qemu_irq irq;
+} PL080State;
+
+static const VMStateDescription vmstate_pl080_channel = {
+ .name = "pl080_channel",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(src, pl080_channel),
+ VMSTATE_UINT32(dest, pl080_channel),
+ VMSTATE_UINT32(lli, pl080_channel),
+ VMSTATE_UINT32(ctrl, pl080_channel),
+ VMSTATE_UINT32(conf, pl080_channel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pl080 = {
+ .name = "pl080",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(tc_int, PL080State),
+ VMSTATE_UINT8(tc_mask, PL080State),
+ VMSTATE_UINT8(err_int, PL080State),
+ VMSTATE_UINT8(err_mask, PL080State),
+ VMSTATE_UINT32(conf, PL080State),
+ VMSTATE_UINT32(sync, PL080State),
+ VMSTATE_UINT32(req_single, PL080State),
+ VMSTATE_UINT32(req_burst, PL080State),
+ VMSTATE_UINT8(tc_int, PL080State),
+ VMSTATE_UINT8(tc_int, PL080State),
+ VMSTATE_UINT8(tc_int, PL080State),
+ VMSTATE_STRUCT_ARRAY(chan, PL080State, PL080_MAX_CHANNELS,
+ 1, vmstate_pl080_channel, pl080_channel),
+ VMSTATE_INT32(running, PL080State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const unsigned char pl080_id[] =
+{ 0x80, 0x10, 0x04, 0x0a, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static const unsigned char pl081_id[] =
+{ 0x81, 0x10, 0x04, 0x0a, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static void pl080_update(PL080State *s)
+{
+ if ((s->tc_int & s->tc_mask)
+ || (s->err_int & s->err_mask))
+ qemu_irq_raise(s->irq);
+ else
+ qemu_irq_lower(s->irq);
+}
+
+static void pl080_run(PL080State *s)
+{
+ int c;
+ int flow;
+ pl080_channel *ch;
+ int swidth;
+ int dwidth;
+ int xsize;
+ int n;
+ int src_id;
+ int dest_id;
+ int size;
+ uint8_t buff[4];
+ uint32_t req;
+
+ s->tc_mask = 0;
+ for (c = 0; c < s->nchannels; c++) {
+ if (s->chan[c].conf & PL080_CCONF_ITC)
+ s->tc_mask |= 1 << c;
+ if (s->chan[c].conf & PL080_CCONF_IE)
+ s->err_mask |= 1 << c;
+ }
+
+ if ((s->conf & PL080_CONF_E) == 0)
+ return;
+
+hw_error("DMA active\n");
+ /* If we are already in the middle of a DMA operation then indicate that
+ there may be new DMA requests and return immediately. */
+ if (s->running) {
+ s->running++;
+ return;
+ }
+ s->running = 1;
+ while (s->running) {
+ for (c = 0; c < s->nchannels; c++) {
+ ch = &s->chan[c];
+again:
+ /* Test if thiws channel has any pending DMA requests. */
+ if ((ch->conf & (PL080_CCONF_H | PL080_CCONF_E))
+ != PL080_CCONF_E)
+ continue;
+ flow = (ch->conf >> 11) & 7;
+ if (flow >= 4) {
+ hw_error(
+ "pl080_run: Peripheral flow control not implemented\n");
+ }
+ src_id = (ch->conf >> 1) & 0x1f;
+ dest_id = (ch->conf >> 6) & 0x1f;
+ size = ch->ctrl & 0xfff;
+ req = s->req_single | s->req_burst;
+ switch (flow) {
+ case 0:
+ break;
+ case 1:
+ if ((req & (1u << dest_id)) == 0)
+ size = 0;
+ break;
+ case 2:
+ if ((req & (1u << src_id)) == 0)
+ size = 0;
+ break;
+ case 3:
+ if ((req & (1u << src_id)) == 0
+ || (req & (1u << dest_id)) == 0)
+ size = 0;
+ break;
+ }
+ if (!size)
+ continue;
+
+ /* Transfer one element. */
+ /* ??? Should transfer multiple elements for a burst request. */
+ /* ??? Unclear what the proper behavior is when source and
+ destination widths are different. */
+ swidth = 1 << ((ch->ctrl >> 18) & 7);
+ dwidth = 1 << ((ch->ctrl >> 21) & 7);
+ for (n = 0; n < dwidth; n+= swidth) {
+ cpu_physical_memory_read(ch->src, buff + n, swidth);
+ if (ch->ctrl & PL080_CCTRL_SI)
+ ch->src += swidth;
+ }
+ xsize = (dwidth < swidth) ? swidth : dwidth;
+ /* ??? This may pad the value incorrectly for dwidth < 32. */
+ for (n = 0; n < xsize; n += dwidth) {
+ cpu_physical_memory_write(ch->dest + n, buff + n, dwidth);
+ if (ch->ctrl & PL080_CCTRL_DI)
+ ch->dest += swidth;
+ }
+
+ size--;
+ ch->ctrl = (ch->ctrl & 0xfffff000) | size;
+ if (size == 0) {
+ /* Transfer complete. */
+ if (ch->lli) {
+ ch->src = address_space_ldl_le(&address_space_memory,
+ ch->lli,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ ch->dest = address_space_ldl_le(&address_space_memory,
+ ch->lli + 4,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ ch->ctrl = address_space_ldl_le(&address_space_memory,
+ ch->lli + 12,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ ch->lli = address_space_ldl_le(&address_space_memory,
+ ch->lli + 8,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ } else {
+ ch->conf &= ~PL080_CCONF_E;
+ }
+ if (ch->ctrl & PL080_CCTRL_I) {
+ s->tc_int |= 1 << c;
+ }
+ }
+ goto again;
+ }
+ if (--s->running)
+ s->running = 1;
+ }
+}
+
+static uint64_t pl080_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL080State *s = (PL080State *)opaque;
+ uint32_t i;
+ uint32_t mask;
+
+ if (offset >= 0xfe0 && offset < 0x1000) {
+ if (s->nchannels == 8) {
+ return pl080_id[(offset - 0xfe0) >> 2];
+ } else {
+ return pl081_id[(offset - 0xfe0) >> 2];
+ }
+ }
+ if (offset >= 0x100 && offset < 0x200) {
+ i = (offset & 0xe0) >> 5;
+ if (i >= s->nchannels)
+ goto bad_offset;
+ switch (offset >> 2) {
+ case 0: /* SrcAddr */
+ return s->chan[i].src;
+ case 1: /* DestAddr */
+ return s->chan[i].dest;
+ case 2: /* LLI */
+ return s->chan[i].lli;
+ case 3: /* Control */
+ return s->chan[i].ctrl;
+ case 4: /* Configuration */
+ return s->chan[i].conf;
+ default:
+ goto bad_offset;
+ }
+ }
+ switch (offset >> 2) {
+ case 0: /* IntStatus */
+ return (s->tc_int & s->tc_mask) | (s->err_int & s->err_mask);
+ case 1: /* IntTCStatus */
+ return (s->tc_int & s->tc_mask);
+ case 3: /* IntErrorStatus */
+ return (s->err_int & s->err_mask);
+ case 5: /* RawIntTCStatus */
+ return s->tc_int;
+ case 6: /* RawIntErrorStatus */
+ return s->err_int;
+ case 7: /* EnbldChns */
+ mask = 0;
+ for (i = 0; i < s->nchannels; i++) {
+ if (s->chan[i].conf & PL080_CCONF_E)
+ mask |= 1 << i;
+ }
+ return mask;
+ case 8: /* SoftBReq */
+ case 9: /* SoftSReq */
+ case 10: /* SoftLBReq */
+ case 11: /* SoftLSReq */
+ /* ??? Implement these. */
+ return 0;
+ case 12: /* Configuration */
+ return s->conf;
+ case 13: /* Sync */
+ return s->sync;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl080_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl080_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL080State *s = (PL080State *)opaque;
+ int i;
+
+ if (offset >= 0x100 && offset < 0x200) {
+ i = (offset & 0xe0) >> 5;
+ if (i >= s->nchannels)
+ goto bad_offset;
+ switch (offset >> 2) {
+ case 0: /* SrcAddr */
+ s->chan[i].src = value;
+ break;
+ case 1: /* DestAddr */
+ s->chan[i].dest = value;
+ break;
+ case 2: /* LLI */
+ s->chan[i].lli = value;
+ break;
+ case 3: /* Control */
+ s->chan[i].ctrl = value;
+ break;
+ case 4: /* Configuration */
+ s->chan[i].conf = value;
+ pl080_run(s);
+ break;
+ }
+ }
+ switch (offset >> 2) {
+ case 2: /* IntTCClear */
+ s->tc_int &= ~value;
+ break;
+ case 4: /* IntErrorClear */
+ s->err_int &= ~value;
+ break;
+ case 8: /* SoftBReq */
+ case 9: /* SoftSReq */
+ case 10: /* SoftLBReq */
+ case 11: /* SoftLSReq */
+ /* ??? Implement these. */
+ qemu_log_mask(LOG_UNIMP, "pl080_write: Soft DMA not implemented\n");
+ break;
+ case 12: /* Configuration */
+ s->conf = value;
+ if (s->conf & (PL080_CONF_M1 | PL080_CONF_M1)) {
+ qemu_log_mask(LOG_UNIMP,
+ "pl080_write: Big-endian DMA not implemented\n");
+ }
+ pl080_run(s);
+ break;
+ case 13: /* Sync */
+ s->sync = value;
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl080_write: Bad offset %x\n", (int)offset);
+ }
+ pl080_update(s);
+}
+
+static const MemoryRegionOps pl080_ops = {
+ .read = pl080_read,
+ .write = pl080_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pl080_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ PL080State *s = PL080(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl080_ops, s, "pl080", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ s->nchannels = 8;
+}
+
+static void pl081_init(Object *obj)
+{
+ PL080State *s = PL080(obj);
+
+ s->nchannels = 2;
+}
+
+static void pl080_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->vmsd = &vmstate_pl080;
+}
+
+static const TypeInfo pl080_info = {
+ .name = TYPE_PL080,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL080State),
+ .instance_init = pl080_init,
+ .class_init = pl080_class_init,
+};
+
+static const TypeInfo pl081_info = {
+ .name = "pl081",
+ .parent = TYPE_PL080,
+ .instance_init = pl081_init,
+};
+
+/* The PL080 and PL081 are the same except for the number of channels
+ they implement (8 and 2 respectively). */
+static void pl080_register_types(void)
+{
+ type_register_static(&pl080_info);
+ type_register_static(&pl081_info);
+}
+
+type_init(pl080_register_types)
diff --git a/hw/dma/pl330.c b/hw/dma/pl330.c
new file mode 100644
index 00000000..5be3df52
--- /dev/null
+++ b/hw/dma/pl330.c
@@ -0,0 +1,1666 @@
+/*
+ * ARM PrimeCell PL330 DMA Controller
+ *
+ * Copyright (c) 2009 Samsung Electronics.
+ * Contributed by Kirill Batuzov <batuzovk@ispras.ru>
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 or later.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "sysemu/dma.h"
+
+#ifndef PL330_ERR_DEBUG
+#define PL330_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(lvl, fmt, args...) do {\
+ if (PL330_ERR_DEBUG >= lvl) {\
+ fprintf(stderr, "PL330: %s:" fmt, __func__, ## args);\
+ } \
+} while (0);
+
+#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
+
+#define PL330_PERIPH_NUM 32
+#define PL330_MAX_BURST_LEN 128
+#define PL330_INSN_MAXSIZE 6
+
+#define PL330_FIFO_OK 0
+#define PL330_FIFO_STALL 1
+#define PL330_FIFO_ERR (-1)
+
+#define PL330_FAULT_UNDEF_INSTR (1 << 0)
+#define PL330_FAULT_OPERAND_INVALID (1 << 1)
+#define PL330_FAULT_DMAGO_ERR (1 << 4)
+#define PL330_FAULT_EVENT_ERR (1 << 5)
+#define PL330_FAULT_CH_PERIPH_ERR (1 << 6)
+#define PL330_FAULT_CH_RDWR_ERR (1 << 7)
+#define PL330_FAULT_ST_DATA_UNAVAILABLE (1 << 12)
+#define PL330_FAULT_FIFOEMPTY_ERR (1 << 13)
+#define PL330_FAULT_INSTR_FETCH_ERR (1 << 16)
+#define PL330_FAULT_DATA_WRITE_ERR (1 << 17)
+#define PL330_FAULT_DATA_READ_ERR (1 << 18)
+#define PL330_FAULT_DBG_INSTR (1 << 30)
+#define PL330_FAULT_LOCKUP_ERR (1 << 31)
+
+#define PL330_UNTAGGED 0xff
+
+#define PL330_SINGLE 0x0
+#define PL330_BURST 0x1
+
+#define PL330_WATCHDOG_LIMIT 1024
+
+/* IOMEM mapped registers */
+#define PL330_REG_DSR 0x000
+#define PL330_REG_DPC 0x004
+#define PL330_REG_INTEN 0x020
+#define PL330_REG_INT_EVENT_RIS 0x024
+#define PL330_REG_INTMIS 0x028
+#define PL330_REG_INTCLR 0x02C
+#define PL330_REG_FSRD 0x030
+#define PL330_REG_FSRC 0x034
+#define PL330_REG_FTRD 0x038
+#define PL330_REG_FTR_BASE 0x040
+#define PL330_REG_CSR_BASE 0x100
+#define PL330_REG_CPC_BASE 0x104
+#define PL330_REG_CHANCTRL 0x400
+#define PL330_REG_DBGSTATUS 0xD00
+#define PL330_REG_DBGCMD 0xD04
+#define PL330_REG_DBGINST0 0xD08
+#define PL330_REG_DBGINST1 0xD0C
+#define PL330_REG_CR0_BASE 0xE00
+#define PL330_REG_PERIPH_ID 0xFE0
+
+#define PL330_IOMEM_SIZE 0x1000
+
+#define CFG_BOOT_ADDR 2
+#define CFG_INS 3
+#define CFG_PNS 4
+#define CFG_CRD 5
+
+static const uint32_t pl330_id[] = {
+ 0x30, 0x13, 0x24, 0x00, 0x0D, 0xF0, 0x05, 0xB1
+};
+
+/* DMA channel states as they are described in PL330 Technical Reference Manual
+ * Most of them will not be used in emulation.
+ */
+typedef enum {
+ pl330_chan_stopped = 0,
+ pl330_chan_executing = 1,
+ pl330_chan_cache_miss = 2,
+ pl330_chan_updating_pc = 3,
+ pl330_chan_waiting_event = 4,
+ pl330_chan_at_barrier = 5,
+ pl330_chan_queue_busy = 6,
+ pl330_chan_waiting_periph = 7,
+ pl330_chan_killing = 8,
+ pl330_chan_completing = 9,
+ pl330_chan_fault_completing = 14,
+ pl330_chan_fault = 15,
+} PL330ChanState;
+
+typedef struct PL330State PL330State;
+
+typedef struct PL330Chan {
+ uint32_t src;
+ uint32_t dst;
+ uint32_t pc;
+ uint32_t control;
+ uint32_t status;
+ uint32_t lc[2];
+ uint32_t fault_type;
+ uint32_t watchdog_timer;
+
+ bool ns;
+ uint8_t request_flag;
+ uint8_t wakeup;
+ uint8_t wfp_sbp;
+
+ uint8_t state;
+ uint8_t stall;
+
+ bool is_manager;
+ PL330State *parent;
+ uint8_t tag;
+} PL330Chan;
+
+static const VMStateDescription vmstate_pl330_chan = {
+ .name = "pl330_chan",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(src, PL330Chan),
+ VMSTATE_UINT32(dst, PL330Chan),
+ VMSTATE_UINT32(pc, PL330Chan),
+ VMSTATE_UINT32(control, PL330Chan),
+ VMSTATE_UINT32(status, PL330Chan),
+ VMSTATE_UINT32_ARRAY(lc, PL330Chan, 2),
+ VMSTATE_UINT32(fault_type, PL330Chan),
+ VMSTATE_UINT32(watchdog_timer, PL330Chan),
+ VMSTATE_BOOL(ns, PL330Chan),
+ VMSTATE_UINT8(request_flag, PL330Chan),
+ VMSTATE_UINT8(wakeup, PL330Chan),
+ VMSTATE_UINT8(wfp_sbp, PL330Chan),
+ VMSTATE_UINT8(state, PL330Chan),
+ VMSTATE_UINT8(stall, PL330Chan),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef struct PL330Fifo {
+ uint8_t *buf;
+ uint8_t *tag;
+ uint32_t head;
+ uint32_t num;
+ uint32_t buf_size;
+} PL330Fifo;
+
+static const VMStateDescription vmstate_pl330_fifo = {
+ .name = "pl330_chan",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_VBUFFER_UINT32(buf, PL330Fifo, 1, NULL, 0, buf_size),
+ VMSTATE_VBUFFER_UINT32(tag, PL330Fifo, 1, NULL, 0, buf_size),
+ VMSTATE_UINT32(head, PL330Fifo),
+ VMSTATE_UINT32(num, PL330Fifo),
+ VMSTATE_UINT32(buf_size, PL330Fifo),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef struct PL330QueueEntry {
+ uint32_t addr;
+ uint32_t len;
+ uint8_t n;
+ bool inc;
+ bool z;
+ uint8_t tag;
+ uint8_t seqn;
+} PL330QueueEntry;
+
+static const VMStateDescription vmstate_pl330_queue_entry = {
+ .name = "pl330_queue_entry",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(addr, PL330QueueEntry),
+ VMSTATE_UINT32(len, PL330QueueEntry),
+ VMSTATE_UINT8(n, PL330QueueEntry),
+ VMSTATE_BOOL(inc, PL330QueueEntry),
+ VMSTATE_BOOL(z, PL330QueueEntry),
+ VMSTATE_UINT8(tag, PL330QueueEntry),
+ VMSTATE_UINT8(seqn, PL330QueueEntry),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef struct PL330Queue {
+ PL330State *parent;
+ PL330QueueEntry *queue;
+ uint32_t queue_size;
+} PL330Queue;
+
+static const VMStateDescription vmstate_pl330_queue = {
+ .name = "pl330_queue",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_VARRAY_UINT32(queue, PL330Queue, queue_size, 1,
+ vmstate_pl330_queue_entry, PL330QueueEntry),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+struct PL330State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq_abort;
+ qemu_irq *irq;
+
+ /* Config registers. cfg[5] = CfgDn. */
+ uint32_t cfg[6];
+#define EVENT_SEC_STATE 3
+#define PERIPH_SEC_STATE 4
+ /* cfg 0 bits and pieces */
+ uint32_t num_chnls;
+ uint8_t num_periph_req;
+ uint8_t num_events;
+ uint8_t mgr_ns_at_rst;
+ /* cfg 1 bits and pieces */
+ uint8_t i_cache_len;
+ uint8_t num_i_cache_lines;
+ /* CRD bits and pieces */
+ uint8_t data_width;
+ uint8_t wr_cap;
+ uint8_t wr_q_dep;
+ uint8_t rd_cap;
+ uint8_t rd_q_dep;
+ uint16_t data_buffer_dep;
+
+ PL330Chan manager;
+ PL330Chan *chan;
+ PL330Fifo fifo;
+ PL330Queue read_queue;
+ PL330Queue write_queue;
+ uint8_t *lo_seqn;
+ uint8_t *hi_seqn;
+ QEMUTimer *timer; /* is used for restore dma. */
+
+ uint32_t inten;
+ uint32_t int_status;
+ uint32_t ev_status;
+ uint32_t dbg[2];
+ uint8_t debug_status;
+ uint8_t num_faulting;
+ uint8_t periph_busy[PL330_PERIPH_NUM];
+
+};
+
+#define TYPE_PL330 "pl330"
+#define PL330(obj) OBJECT_CHECK(PL330State, (obj), TYPE_PL330)
+
+static const VMStateDescription vmstate_pl330 = {
+ .name = "pl330",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(manager, PL330State, 0, vmstate_pl330_chan, PL330Chan),
+ VMSTATE_STRUCT_VARRAY_UINT32(chan, PL330State, num_chnls, 0,
+ vmstate_pl330_chan, PL330Chan),
+ VMSTATE_VBUFFER_UINT32(lo_seqn, PL330State, 1, NULL, 0, num_chnls),
+ VMSTATE_VBUFFER_UINT32(hi_seqn, PL330State, 1, NULL, 0, num_chnls),
+ VMSTATE_STRUCT(fifo, PL330State, 0, vmstate_pl330_fifo, PL330Fifo),
+ VMSTATE_STRUCT(read_queue, PL330State, 0, vmstate_pl330_queue,
+ PL330Queue),
+ VMSTATE_STRUCT(write_queue, PL330State, 0, vmstate_pl330_queue,
+ PL330Queue),
+ VMSTATE_TIMER_PTR(timer, PL330State),
+ VMSTATE_UINT32(inten, PL330State),
+ VMSTATE_UINT32(int_status, PL330State),
+ VMSTATE_UINT32(ev_status, PL330State),
+ VMSTATE_UINT32_ARRAY(dbg, PL330State, 2),
+ VMSTATE_UINT8(debug_status, PL330State),
+ VMSTATE_UINT8(num_faulting, PL330State),
+ VMSTATE_UINT8_ARRAY(periph_busy, PL330State, PL330_PERIPH_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef struct PL330InsnDesc {
+ /* OPCODE of the instruction */
+ uint8_t opcode;
+ /* Mask so we can select several sibling instructions, such as
+ DMALD, DMALDS and DMALDB */
+ uint8_t opmask;
+ /* Size of instruction in bytes */
+ uint8_t size;
+ /* Interpreter */
+ void (*exec)(PL330Chan *, uint8_t opcode, uint8_t *args, int len);
+} PL330InsnDesc;
+
+
+/* MFIFO Implementation
+ *
+ * MFIFO is implemented as a cyclic buffer of BUF_SIZE size. Tagged bytes are
+ * stored in this buffer. Data is stored in BUF field, tags - in the
+ * corresponding array elements of TAG field.
+ */
+
+/* Initialize queue. */
+
+static void pl330_fifo_init(PL330Fifo *s, uint32_t size)
+{
+ s->buf = g_malloc0(size);
+ s->tag = g_malloc0(size);
+ s->buf_size = size;
+}
+
+/* Cyclic increment */
+
+static inline int pl330_fifo_inc(PL330Fifo *s, int x)
+{
+ return (x + 1) % s->buf_size;
+}
+
+/* Number of empty bytes in MFIFO */
+
+static inline int pl330_fifo_num_free(PL330Fifo *s)
+{
+ return s->buf_size - s->num;
+}
+
+/* Push LEN bytes of data stored in BUF to MFIFO and tag it with TAG.
+ * Zero returned on success, PL330_FIFO_STALL if there is no enough free
+ * space in MFIFO to store requested amount of data. If push was unsuccessful
+ * no data is stored to MFIFO.
+ */
+
+static int pl330_fifo_push(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
+{
+ int i;
+
+ if (s->buf_size - s->num < len) {
+ return PL330_FIFO_STALL;
+ }
+ for (i = 0; i < len; i++) {
+ int push_idx = (s->head + s->num + i) % s->buf_size;
+ s->buf[push_idx] = buf[i];
+ s->tag[push_idx] = tag;
+ }
+ s->num += len;
+ return PL330_FIFO_OK;
+}
+
+/* Get LEN bytes of data from MFIFO and store it to BUF. Tag value of each
+ * byte is verified. Zero returned on success, PL330_FIFO_ERR on tag mismatch
+ * and PL330_FIFO_STALL if there is no enough data in MFIFO. If get was
+ * unsuccessful no data is removed from MFIFO.
+ */
+
+static int pl330_fifo_get(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
+{
+ int i;
+
+ if (s->num < len) {
+ return PL330_FIFO_STALL;
+ }
+ for (i = 0; i < len; i++) {
+ if (s->tag[s->head] == tag) {
+ int get_idx = (s->head + i) % s->buf_size;
+ buf[i] = s->buf[get_idx];
+ } else { /* Tag mismatch - Rollback transaction */
+ return PL330_FIFO_ERR;
+ }
+ }
+ s->head = (s->head + len) % s->buf_size;
+ s->num -= len;
+ return PL330_FIFO_OK;
+}
+
+/* Reset MFIFO. This completely erases all data in it. */
+
+static inline void pl330_fifo_reset(PL330Fifo *s)
+{
+ s->head = 0;
+ s->num = 0;
+}
+
+/* Return tag of the first byte stored in MFIFO. If MFIFO is empty
+ * PL330_UNTAGGED is returned.
+ */
+
+static inline uint8_t pl330_fifo_tag(PL330Fifo *s)
+{
+ return (!s->num) ? PL330_UNTAGGED : s->tag[s->head];
+}
+
+/* Returns non-zero if tag TAG is present in fifo or zero otherwise */
+
+static int pl330_fifo_has_tag(PL330Fifo *s, uint8_t tag)
+{
+ int i, n;
+
+ i = s->head;
+ for (n = 0; n < s->num; n++) {
+ if (s->tag[i] == tag) {
+ return 1;
+ }
+ i = pl330_fifo_inc(s, i);
+ }
+ return 0;
+}
+
+/* Remove all entry tagged with TAG from MFIFO */
+
+static void pl330_fifo_tagged_remove(PL330Fifo *s, uint8_t tag)
+{
+ int i, t, n;
+
+ t = i = s->head;
+ for (n = 0; n < s->num; n++) {
+ if (s->tag[i] != tag) {
+ s->buf[t] = s->buf[i];
+ s->tag[t] = s->tag[i];
+ t = pl330_fifo_inc(s, t);
+ } else {
+ s->num = s->num - 1;
+ }
+ i = pl330_fifo_inc(s, i);
+ }
+}
+
+/* Read-Write Queue implementation
+ *
+ * A Read-Write Queue stores up to QUEUE_SIZE instructions (loads or stores).
+ * Each instruction is described by source (for loads) or destination (for
+ * stores) address ADDR, width of data to be loaded/stored LEN, number of
+ * stores/loads to be performed N, INC bit, Z bit and TAG to identify channel
+ * this instruction belongs to. Queue does not store any information about
+ * nature of the instruction: is it load or store. PL330 has different queues
+ * for loads and stores so this is already known at the top level where it
+ * matters.
+ *
+ * Queue works as FIFO for instructions with equivalent tags, but can issue
+ * instructions with different tags in arbitrary order. SEQN field attached to
+ * each instruction helps to achieve this. For each TAG queue contains
+ * instructions with consecutive SEQN values ranging from LO_SEQN[TAG] to
+ * HI_SEQN[TAG]-1 inclusive. SEQN is 8-bit unsigned integer, so SEQN=255 is
+ * followed by SEQN=0.
+ *
+ * Z bit indicates that zeroes should be stored. No MFIFO fetches are performed
+ * in this case.
+ */
+
+static void pl330_queue_reset(PL330Queue *s)
+{
+ int i;
+
+ for (i = 0; i < s->queue_size; i++) {
+ s->queue[i].tag = PL330_UNTAGGED;
+ }
+}
+
+/* Initialize queue */
+static void pl330_queue_init(PL330Queue *s, int size, PL330State *parent)
+{
+ s->parent = parent;
+ s->queue = g_new0(PL330QueueEntry, size);
+ s->queue_size = size;
+}
+
+/* Returns pointer to an empty slot or NULL if queue is full */
+static PL330QueueEntry *pl330_queue_find_empty(PL330Queue *s)
+{
+ int i;
+
+ for (i = 0; i < s->queue_size; i++) {
+ if (s->queue[i].tag == PL330_UNTAGGED) {
+ return &s->queue[i];
+ }
+ }
+ return NULL;
+}
+
+/* Put instruction in queue.
+ * Return value:
+ * - zero - OK
+ * - non-zero - queue is full
+ */
+
+static int pl330_queue_put_insn(PL330Queue *s, uint32_t addr,
+ int len, int n, bool inc, bool z, uint8_t tag)
+{
+ PL330QueueEntry *entry = pl330_queue_find_empty(s);
+
+ if (!entry) {
+ return 1;
+ }
+ entry->tag = tag;
+ entry->addr = addr;
+ entry->len = len;
+ entry->n = n;
+ entry->z = z;
+ entry->inc = inc;
+ entry->seqn = s->parent->hi_seqn[tag];
+ s->parent->hi_seqn[tag]++;
+ return 0;
+}
+
+/* Returns a pointer to queue slot containing instruction which satisfies
+ * following conditions:
+ * - it has valid tag value (not PL330_UNTAGGED)
+ * - if enforce_seq is set it has to be issuable without violating queue
+ * logic (see above)
+ * - if TAG argument is not PL330_UNTAGGED this instruction has tag value
+ * equivalent to the argument TAG value.
+ * If such instruction cannot be found NULL is returned.
+ */
+
+static PL330QueueEntry *pl330_queue_find_insn(PL330Queue *s, uint8_t tag,
+ bool enforce_seq)
+{
+ int i;
+
+ for (i = 0; i < s->queue_size; i++) {
+ if (s->queue[i].tag != PL330_UNTAGGED) {
+ if ((!enforce_seq ||
+ s->queue[i].seqn == s->parent->lo_seqn[s->queue[i].tag]) &&
+ (s->queue[i].tag == tag || tag == PL330_UNTAGGED ||
+ s->queue[i].z)) {
+ return &s->queue[i];
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Removes instruction from queue. */
+
+static inline void pl330_queue_remove_insn(PL330Queue *s, PL330QueueEntry *e)
+{
+ s->parent->lo_seqn[e->tag]++;
+ e->tag = PL330_UNTAGGED;
+}
+
+/* Removes all instructions tagged with TAG from queue. */
+
+static inline void pl330_queue_remove_tagged(PL330Queue *s, uint8_t tag)
+{
+ int i;
+
+ for (i = 0; i < s->queue_size; i++) {
+ if (s->queue[i].tag == tag) {
+ s->queue[i].tag = PL330_UNTAGGED;
+ }
+ }
+}
+
+/* DMA instruction execution engine */
+
+/* Moves DMA channel to the FAULT state and updates it's status. */
+
+static inline void pl330_fault(PL330Chan *ch, uint32_t flags)
+{
+ DB_PRINT("ch: %p, flags: %" PRIx32 "\n", ch, flags);
+ ch->fault_type |= flags;
+ if (ch->state == pl330_chan_fault) {
+ return;
+ }
+ ch->state = pl330_chan_fault;
+ ch->parent->num_faulting++;
+ if (ch->parent->num_faulting == 1) {
+ DB_PRINT("abort interrupt raised\n");
+ qemu_irq_raise(ch->parent->irq_abort);
+ }
+}
+
+/*
+ * For information about instructions see PL330 Technical Reference Manual.
+ *
+ * Arguments:
+ * CH - channel executing the instruction
+ * OPCODE - opcode
+ * ARGS - array of 8-bit arguments
+ * LEN - number of elements in ARGS array
+ */
+
+static void pl330_dmaadxh(PL330Chan *ch, uint8_t *args, bool ra, bool neg)
+{
+ uint32_t im = (args[1] << 8) | args[0];
+ if (neg) {
+ im |= 0xffffu << 16;
+ }
+
+ if (ch->is_manager) {
+ pl330_fault(ch, PL330_FAULT_UNDEF_INSTR);
+ return;
+ }
+ if (ra) {
+ ch->dst += im;
+ } else {
+ ch->src += im;
+ }
+}
+
+static void pl330_dmaaddh(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ pl330_dmaadxh(ch, args, extract32(opcode, 1, 1), false);
+}
+
+static void pl330_dmaadnh(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ pl330_dmaadxh(ch, args, extract32(opcode, 1, 1), true);
+}
+
+static void pl330_dmaend(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ PL330State *s = ch->parent;
+
+ if (ch->state == pl330_chan_executing && !ch->is_manager) {
+ /* Wait for all transfers to complete */
+ if (pl330_fifo_has_tag(&s->fifo, ch->tag) ||
+ pl330_queue_find_insn(&s->read_queue, ch->tag, false) != NULL ||
+ pl330_queue_find_insn(&s->write_queue, ch->tag, false) != NULL) {
+
+ ch->stall = 1;
+ return;
+ }
+ }
+ DB_PRINT("DMA ending!\n");
+ pl330_fifo_tagged_remove(&s->fifo, ch->tag);
+ pl330_queue_remove_tagged(&s->read_queue, ch->tag);
+ pl330_queue_remove_tagged(&s->write_queue, ch->tag);
+ ch->state = pl330_chan_stopped;
+}
+
+static void pl330_dmaflushp(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ uint8_t periph_id;
+
+ if (args[0] & 7) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ periph_id = (args[0] >> 3) & 0x1f;
+ if (periph_id >= ch->parent->num_periph_req) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if (ch->ns && !(ch->parent->cfg[CFG_PNS] & (1 << periph_id))) {
+ pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
+ return;
+ }
+ /* Do nothing */
+}
+
+static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ uint8_t chan_id;
+ uint8_t ns;
+ uint32_t pc;
+ PL330Chan *s;
+
+ DB_PRINT("\n");
+
+ if (!ch->is_manager) {
+ pl330_fault(ch, PL330_FAULT_UNDEF_INSTR);
+ return;
+ }
+ ns = !!(opcode & 2);
+ chan_id = args[0] & 7;
+ if ((args[0] >> 3)) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if (chan_id >= ch->parent->num_chnls) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ pc = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
+ (((uint32_t)args[2]) << 8) | (((uint32_t)args[1]));
+ if (ch->parent->chan[chan_id].state != pl330_chan_stopped) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if (ch->ns && !ns) {
+ pl330_fault(ch, PL330_FAULT_DMAGO_ERR);
+ return;
+ }
+ s = &ch->parent->chan[chan_id];
+ s->ns = ns;
+ s->pc = pc;
+ s->state = pl330_chan_executing;
+}
+
+static void pl330_dmald(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ uint8_t bs = opcode & 3;
+ uint32_t size, num;
+ bool inc;
+
+ if (bs == 2) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if ((bs == 1 && ch->request_flag == PL330_BURST) ||
+ (bs == 3 && ch->request_flag == PL330_SINGLE)) {
+ /* Perform NOP */
+ return;
+ }
+ if (bs == 1 && ch->request_flag == PL330_SINGLE) {
+ num = 1;
+ } else {
+ num = ((ch->control >> 4) & 0xf) + 1;
+ }
+ size = (uint32_t)1 << ((ch->control >> 1) & 0x7);
+ inc = !!(ch->control & 1);
+ ch->stall = pl330_queue_put_insn(&ch->parent->read_queue, ch->src,
+ size, num, inc, 0, ch->tag);
+ if (!ch->stall) {
+ DB_PRINT("channel:%" PRId8 " address:%08" PRIx32 " size:%" PRIx32
+ " num:%" PRId32 " %c\n",
+ ch->tag, ch->src, size, num, inc ? 'Y' : 'N');
+ ch->src += inc ? size * num - (ch->src & (size - 1)) : 0;
+ }
+}
+
+static void pl330_dmaldp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ uint8_t periph_id;
+
+ if (args[0] & 7) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ periph_id = (args[0] >> 3) & 0x1f;
+ if (periph_id >= ch->parent->num_periph_req) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if (ch->ns && !(ch->parent->cfg[CFG_PNS] & (1 << periph_id))) {
+ pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
+ return;
+ }
+ pl330_dmald(ch, opcode, args, len);
+}
+
+static void pl330_dmalp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ uint8_t lc = (opcode & 2) >> 1;
+
+ ch->lc[lc] = args[0];
+}
+
+static void pl330_dmakill(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ if (ch->state == pl330_chan_fault ||
+ ch->state == pl330_chan_fault_completing) {
+ /* This is the only way for a channel to leave the faulting state */
+ ch->fault_type = 0;
+ ch->parent->num_faulting--;
+ if (ch->parent->num_faulting == 0) {
+ DB_PRINT("abort interrupt lowered\n");
+ qemu_irq_lower(ch->parent->irq_abort);
+ }
+ }
+ ch->state = pl330_chan_killing;
+ pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
+ pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
+ pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
+ ch->state = pl330_chan_stopped;
+}
+
+static void pl330_dmalpend(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ uint8_t nf = (opcode & 0x10) >> 4;
+ uint8_t bs = opcode & 3;
+ uint8_t lc = (opcode & 4) >> 2;
+
+ if (bs == 2) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if ((bs == 1 && ch->request_flag == PL330_BURST) ||
+ (bs == 3 && ch->request_flag == PL330_SINGLE)) {
+ /* Perform NOP */
+ return;
+ }
+ if (!nf || ch->lc[lc]) {
+ if (nf) {
+ ch->lc[lc]--;
+ }
+ DB_PRINT("loop reiteration\n");
+ ch->pc -= args[0];
+ ch->pc -= len + 1;
+ /* "ch->pc -= args[0] + len + 1" is incorrect when args[0] == 256 */
+ } else {
+ DB_PRINT("loop fallthrough\n");
+ }
+}
+
+
+static void pl330_dmamov(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ uint8_t rd = args[0] & 7;
+ uint32_t im;
+
+ if ((args[0] >> 3)) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ im = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
+ (((uint32_t)args[2]) << 8) | (((uint32_t)args[1]));
+ switch (rd) {
+ case 0:
+ ch->src = im;
+ break;
+ case 1:
+ ch->control = im;
+ break;
+ case 2:
+ ch->dst = im;
+ break;
+ default:
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+}
+
+static void pl330_dmanop(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ /* NOP is NOP. */
+}
+
+static void pl330_dmarmb(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ if (pl330_queue_find_insn(&ch->parent->read_queue, ch->tag, false)) {
+ ch->state = pl330_chan_at_barrier;
+ ch->stall = 1;
+ return;
+ } else {
+ ch->state = pl330_chan_executing;
+ }
+}
+
+static void pl330_dmasev(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ uint8_t ev_id;
+
+ if (args[0] & 7) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ ev_id = (args[0] >> 3) & 0x1f;
+ if (ev_id >= ch->parent->num_events) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if (ch->ns && !(ch->parent->cfg[CFG_INS] & (1 << ev_id))) {
+ pl330_fault(ch, PL330_FAULT_EVENT_ERR);
+ return;
+ }
+ if (ch->parent->inten & (1 << ev_id)) {
+ ch->parent->int_status |= (1 << ev_id);
+ DB_PRINT("event interrupt raised %" PRId8 "\n", ev_id);
+ qemu_irq_raise(ch->parent->irq[ev_id]);
+ }
+ DB_PRINT("event raised %" PRId8 "\n", ev_id);
+ ch->parent->ev_status |= (1 << ev_id);
+}
+
+static void pl330_dmast(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+ uint8_t bs = opcode & 3;
+ uint32_t size, num;
+ bool inc;
+
+ if (bs == 2) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if ((bs == 1 && ch->request_flag == PL330_BURST) ||
+ (bs == 3 && ch->request_flag == PL330_SINGLE)) {
+ /* Perform NOP */
+ return;
+ }
+ num = ((ch->control >> 18) & 0xf) + 1;
+ size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
+ inc = !!((ch->control >> 14) & 1);
+ ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
+ size, num, inc, 0, ch->tag);
+ if (!ch->stall) {
+ DB_PRINT("channel:%" PRId8 " address:%08" PRIx32 " size:%" PRIx32
+ " num:%" PRId32 " %c\n",
+ ch->tag, ch->dst, size, num, inc ? 'Y' : 'N');
+ ch->dst += inc ? size * num - (ch->dst & (size - 1)) : 0;
+ }
+}
+
+static void pl330_dmastp(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ uint8_t periph_id;
+
+ if (args[0] & 7) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ periph_id = (args[0] >> 3) & 0x1f;
+ if (periph_id >= ch->parent->num_periph_req) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if (ch->ns && !(ch->parent->cfg[CFG_PNS] & (1 << periph_id))) {
+ pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
+ return;
+ }
+ pl330_dmast(ch, opcode, args, len);
+}
+
+static void pl330_dmastz(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ uint32_t size, num;
+ bool inc;
+
+ num = ((ch->control >> 18) & 0xf) + 1;
+ size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
+ inc = !!((ch->control >> 14) & 1);
+ ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
+ size, num, inc, 1, ch->tag);
+ if (inc) {
+ ch->dst += size * num;
+ }
+}
+
+static void pl330_dmawfe(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ uint8_t ev_id;
+ int i;
+
+ if (args[0] & 5) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ ev_id = (args[0] >> 3) & 0x1f;
+ if (ev_id >= ch->parent->num_events) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if (ch->ns && !(ch->parent->cfg[CFG_INS] & (1 << ev_id))) {
+ pl330_fault(ch, PL330_FAULT_EVENT_ERR);
+ return;
+ }
+ ch->wakeup = ev_id;
+ ch->state = pl330_chan_waiting_event;
+ if (~ch->parent->inten & ch->parent->ev_status & 1 << ev_id) {
+ ch->state = pl330_chan_executing;
+ /* If anyone else is currently waiting on the same event, let them
+ * clear the ev_status so they pick up event as well
+ */
+ for (i = 0; i < ch->parent->num_chnls; ++i) {
+ PL330Chan *peer = &ch->parent->chan[i];
+ if (peer->state == pl330_chan_waiting_event &&
+ peer->wakeup == ev_id) {
+ return;
+ }
+ }
+ ch->parent->ev_status &= ~(1 << ev_id);
+ DB_PRINT("event lowered %" PRIx8 "\n", ev_id);
+ } else {
+ ch->stall = 1;
+ }
+}
+
+static void pl330_dmawfp(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ uint8_t bs = opcode & 3;
+ uint8_t periph_id;
+
+ if (args[0] & 7) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ periph_id = (args[0] >> 3) & 0x1f;
+ if (periph_id >= ch->parent->num_periph_req) {
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+ if (ch->ns && !(ch->parent->cfg[CFG_PNS] & (1 << periph_id))) {
+ pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
+ return;
+ }
+ switch (bs) {
+ case 0: /* S */
+ ch->request_flag = PL330_SINGLE;
+ ch->wfp_sbp = 0;
+ break;
+ case 1: /* P */
+ ch->request_flag = PL330_BURST;
+ ch->wfp_sbp = 2;
+ break;
+ case 2: /* B */
+ ch->request_flag = PL330_BURST;
+ ch->wfp_sbp = 1;
+ break;
+ default:
+ pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+ return;
+ }
+
+ if (ch->parent->periph_busy[periph_id]) {
+ ch->state = pl330_chan_waiting_periph;
+ ch->stall = 1;
+ } else if (ch->state == pl330_chan_waiting_periph) {
+ ch->state = pl330_chan_executing;
+ }
+}
+
+static void pl330_dmawmb(PL330Chan *ch, uint8_t opcode,
+ uint8_t *args, int len)
+{
+ if (pl330_queue_find_insn(&ch->parent->write_queue, ch->tag, false)) {
+ ch->state = pl330_chan_at_barrier;
+ ch->stall = 1;
+ return;
+ } else {
+ ch->state = pl330_chan_executing;
+ }
+}
+
+/* NULL terminated array of the instruction descriptions. */
+static const PL330InsnDesc insn_desc[] = {
+ { .opcode = 0x54, .opmask = 0xFD, .size = 3, .exec = pl330_dmaaddh, },
+ { .opcode = 0x5c, .opmask = 0xFD, .size = 3, .exec = pl330_dmaadnh, },
+ { .opcode = 0x00, .opmask = 0xFF, .size = 1, .exec = pl330_dmaend, },
+ { .opcode = 0x35, .opmask = 0xFF, .size = 2, .exec = pl330_dmaflushp, },
+ { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
+ { .opcode = 0x04, .opmask = 0xFC, .size = 1, .exec = pl330_dmald, },
+ { .opcode = 0x25, .opmask = 0xFD, .size = 2, .exec = pl330_dmaldp, },
+ { .opcode = 0x20, .opmask = 0xFD, .size = 2, .exec = pl330_dmalp, },
+ /* dmastp must be before dmalpend in this list, because their maps
+ * are overlapping
+ */
+ { .opcode = 0x29, .opmask = 0xFD, .size = 2, .exec = pl330_dmastp, },
+ { .opcode = 0x28, .opmask = 0xE8, .size = 2, .exec = pl330_dmalpend, },
+ { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
+ { .opcode = 0xBC, .opmask = 0xFF, .size = 6, .exec = pl330_dmamov, },
+ { .opcode = 0x18, .opmask = 0xFF, .size = 1, .exec = pl330_dmanop, },
+ { .opcode = 0x12, .opmask = 0xFF, .size = 1, .exec = pl330_dmarmb, },
+ { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
+ { .opcode = 0x08, .opmask = 0xFC, .size = 1, .exec = pl330_dmast, },
+ { .opcode = 0x0C, .opmask = 0xFF, .size = 1, .exec = pl330_dmastz, },
+ { .opcode = 0x36, .opmask = 0xFF, .size = 2, .exec = pl330_dmawfe, },
+ { .opcode = 0x30, .opmask = 0xFC, .size = 2, .exec = pl330_dmawfp, },
+ { .opcode = 0x13, .opmask = 0xFF, .size = 1, .exec = pl330_dmawmb, },
+ { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
+};
+
+/* Instructions which can be issued via debug registers. */
+static const PL330InsnDesc debug_insn_desc[] = {
+ { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
+ { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
+ { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
+ { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
+};
+
+static inline const PL330InsnDesc *pl330_fetch_insn(PL330Chan *ch)
+{
+ uint8_t opcode;
+ int i;
+
+ dma_memory_read(&address_space_memory, ch->pc, &opcode, 1);
+ for (i = 0; insn_desc[i].size; i++) {
+ if ((opcode & insn_desc[i].opmask) == insn_desc[i].opcode) {
+ return &insn_desc[i];
+ }
+ }
+ return NULL;
+}
+
+static inline void pl330_exec_insn(PL330Chan *ch, const PL330InsnDesc *insn)
+{
+ uint8_t buf[PL330_INSN_MAXSIZE];
+
+ assert(insn->size <= PL330_INSN_MAXSIZE);
+ dma_memory_read(&address_space_memory, ch->pc, buf, insn->size);
+ insn->exec(ch, buf[0], &buf[1], insn->size - 1);
+}
+
+static inline void pl330_update_pc(PL330Chan *ch,
+ const PL330InsnDesc *insn)
+{
+ ch->pc += insn->size;
+}
+
+/* Try to execute current instruction in channel CH. Number of executed
+ instructions is returned (0 or 1). */
+static int pl330_chan_exec(PL330Chan *ch)
+{
+ const PL330InsnDesc *insn;
+
+ if (ch->state != pl330_chan_executing &&
+ ch->state != pl330_chan_waiting_periph &&
+ ch->state != pl330_chan_at_barrier &&
+ ch->state != pl330_chan_waiting_event) {
+ return 0;
+ }
+ ch->stall = 0;
+ insn = pl330_fetch_insn(ch);
+ if (!insn) {
+ DB_PRINT("pl330 undefined instruction\n");
+ pl330_fault(ch, PL330_FAULT_UNDEF_INSTR);
+ return 0;
+ }
+ pl330_exec_insn(ch, insn);
+ if (!ch->stall) {
+ pl330_update_pc(ch, insn);
+ ch->watchdog_timer = 0;
+ return 1;
+ /* WDT only active in exec state */
+ } else if (ch->state == pl330_chan_executing) {
+ ch->watchdog_timer++;
+ if (ch->watchdog_timer >= PL330_WATCHDOG_LIMIT) {
+ pl330_fault(ch, PL330_FAULT_LOCKUP_ERR);
+ }
+ }
+ return 0;
+}
+
+/* Try to execute 1 instruction in each channel, one instruction from read
+ queue and one instruction from write queue. Number of successfully executed
+ instructions is returned. */
+static int pl330_exec_cycle(PL330Chan *channel)
+{
+ PL330State *s = channel->parent;
+ PL330QueueEntry *q;
+ int i;
+ int num_exec = 0;
+ int fifo_res = 0;
+ uint8_t buf[PL330_MAX_BURST_LEN];
+
+ /* Execute one instruction in each channel */
+ num_exec += pl330_chan_exec(channel);
+
+ /* Execute one instruction from read queue */
+ q = pl330_queue_find_insn(&s->read_queue, PL330_UNTAGGED, true);
+ if (q != NULL && q->len <= pl330_fifo_num_free(&s->fifo)) {
+ int len = q->len - (q->addr & (q->len - 1));
+
+ dma_memory_read(&address_space_memory, q->addr, buf, len);
+ if (PL330_ERR_DEBUG > 1) {
+ DB_PRINT("PL330 read from memory @%08" PRIx32 " (size = %08x):\n",
+ q->addr, len);
+ qemu_hexdump((char *)buf, stderr, "", len);
+ }
+ fifo_res = pl330_fifo_push(&s->fifo, buf, len, q->tag);
+ if (fifo_res == PL330_FIFO_OK) {
+ if (q->inc) {
+ q->addr += len;
+ }
+ q->n--;
+ if (!q->n) {
+ pl330_queue_remove_insn(&s->read_queue, q);
+ }
+ num_exec++;
+ }
+ }
+
+ /* Execute one instruction from write queue. */
+ q = pl330_queue_find_insn(&s->write_queue, pl330_fifo_tag(&s->fifo), true);
+ if (q != NULL) {
+ int len = q->len - (q->addr & (q->len - 1));
+
+ if (q->z) {
+ for (i = 0; i < len; i++) {
+ buf[i] = 0;
+ }
+ } else {
+ fifo_res = pl330_fifo_get(&s->fifo, buf, len, q->tag);
+ }
+ if (fifo_res == PL330_FIFO_OK || q->z) {
+ dma_memory_write(&address_space_memory, q->addr, buf, len);
+ if (PL330_ERR_DEBUG > 1) {
+ DB_PRINT("PL330 read from memory @%08" PRIx32
+ " (size = %08x):\n", q->addr, len);
+ qemu_hexdump((char *)buf, stderr, "", len);
+ }
+ if (q->inc) {
+ q->addr += len;
+ }
+ num_exec++;
+ } else if (fifo_res == PL330_FIFO_STALL) {
+ pl330_fault(&channel->parent->chan[q->tag],
+ PL330_FAULT_FIFOEMPTY_ERR);
+ }
+ q->n--;
+ if (!q->n) {
+ pl330_queue_remove_insn(&s->write_queue, q);
+ }
+ }
+
+ return num_exec;
+}
+
+static int pl330_exec_channel(PL330Chan *channel)
+{
+ int insr_exec = 0;
+
+ /* TODO: Is it all right to execute everything or should we do per-cycle
+ simulation? */
+ while (pl330_exec_cycle(channel)) {
+ insr_exec++;
+ }
+
+ /* Detect deadlock */
+ if (channel->state == pl330_chan_executing) {
+ pl330_fault(channel, PL330_FAULT_LOCKUP_ERR);
+ }
+ /* Situation when one of the queues has deadlocked but all channels
+ * have finished their programs should be impossible.
+ */
+
+ return insr_exec;
+}
+
+static inline void pl330_exec(PL330State *s)
+{
+ DB_PRINT("\n");
+ int i, insr_exec;
+ do {
+ insr_exec = pl330_exec_channel(&s->manager);
+
+ for (i = 0; i < s->num_chnls; i++) {
+ insr_exec += pl330_exec_channel(&s->chan[i]);
+ }
+ } while (insr_exec);
+}
+
+static void pl330_exec_cycle_timer(void *opaque)
+{
+ PL330State *s = (PL330State *)opaque;
+ pl330_exec(s);
+}
+
+/* Stop or restore dma operations */
+
+static void pl330_dma_stop_irq(void *opaque, int irq, int level)
+{
+ PL330State *s = (PL330State *)opaque;
+
+ if (s->periph_busy[irq] != level) {
+ s->periph_busy[irq] = level;
+ timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ }
+}
+
+static void pl330_debug_exec(PL330State *s)
+{
+ uint8_t args[5];
+ uint8_t opcode;
+ uint8_t chan_id;
+ int i;
+ PL330Chan *ch;
+ const PL330InsnDesc *insn;
+
+ s->debug_status = 1;
+ chan_id = (s->dbg[0] >> 8) & 0x07;
+ opcode = (s->dbg[0] >> 16) & 0xff;
+ args[0] = (s->dbg[0] >> 24) & 0xff;
+ args[1] = (s->dbg[1] >> 0) & 0xff;
+ args[2] = (s->dbg[1] >> 8) & 0xff;
+ args[3] = (s->dbg[1] >> 16) & 0xff;
+ args[4] = (s->dbg[1] >> 24) & 0xff;
+ DB_PRINT("chan id: %" PRIx8 "\n", chan_id);
+ if (s->dbg[0] & 1) {
+ ch = &s->chan[chan_id];
+ } else {
+ ch = &s->manager;
+ }
+ insn = NULL;
+ for (i = 0; debug_insn_desc[i].size; i++) {
+ if ((opcode & debug_insn_desc[i].opmask) == debug_insn_desc[i].opcode) {
+ insn = &debug_insn_desc[i];
+ }
+ }
+ if (!insn) {
+ pl330_fault(ch, PL330_FAULT_UNDEF_INSTR | PL330_FAULT_DBG_INSTR);
+ return ;
+ }
+ ch->stall = 0;
+ insn->exec(ch, opcode, args, insn->size - 1);
+ if (ch->fault_type) {
+ ch->fault_type |= PL330_FAULT_DBG_INSTR;
+ }
+ if (ch->stall) {
+ qemu_log_mask(LOG_UNIMP, "pl330: stall of debug instruction not "
+ "implemented\n");
+ }
+ s->debug_status = 0;
+}
+
+/* IOMEM mapped registers */
+
+static void pl330_iomem_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL330State *s = (PL330State *) opaque;
+ int i;
+
+ DB_PRINT("addr: %08x data: %08x\n", (unsigned)offset, (unsigned)value);
+
+ switch (offset) {
+ case PL330_REG_INTEN:
+ s->inten = value;
+ break;
+ case PL330_REG_INTCLR:
+ for (i = 0; i < s->num_events; i++) {
+ if (s->int_status & s->inten & value & (1 << i)) {
+ DB_PRINT("event interrupt lowered %d\n", i);
+ qemu_irq_lower(s->irq[i]);
+ }
+ }
+ s->ev_status &= ~(value & s->inten);
+ s->int_status &= ~(value & s->inten);
+ break;
+ case PL330_REG_DBGCMD:
+ if ((value & 3) == 0) {
+ pl330_debug_exec(s);
+ pl330_exec(s);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "pl330: write of illegal value %u "
+ "for offset " TARGET_FMT_plx "\n", (unsigned)value,
+ offset);
+ }
+ break;
+ case PL330_REG_DBGINST0:
+ DB_PRINT("s->dbg[0] = %08x\n", (unsigned)value);
+ s->dbg[0] = value;
+ break;
+ case PL330_REG_DBGINST1:
+ DB_PRINT("s->dbg[1] = %08x\n", (unsigned)value);
+ s->dbg[1] = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad write offset " TARGET_FMT_plx
+ "\n", offset);
+ break;
+ }
+}
+
+static inline uint32_t pl330_iomem_read_imp(void *opaque,
+ hwaddr offset)
+{
+ PL330State *s = (PL330State *)opaque;
+ int chan_id;
+ int i;
+ uint32_t res;
+
+ if (offset >= PL330_REG_PERIPH_ID && offset < PL330_REG_PERIPH_ID + 32) {
+ return pl330_id[(offset - PL330_REG_PERIPH_ID) >> 2];
+ }
+ if (offset >= PL330_REG_CR0_BASE && offset < PL330_REG_CR0_BASE + 24) {
+ return s->cfg[(offset - PL330_REG_CR0_BASE) >> 2];
+ }
+ if (offset >= PL330_REG_CHANCTRL && offset < PL330_REG_DBGSTATUS) {
+ offset -= PL330_REG_CHANCTRL;
+ chan_id = offset >> 5;
+ if (chan_id >= s->num_chnls) {
+ qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset "
+ TARGET_FMT_plx "\n", offset);
+ return 0;
+ }
+ switch (offset & 0x1f) {
+ case 0x00:
+ return s->chan[chan_id].src;
+ case 0x04:
+ return s->chan[chan_id].dst;
+ case 0x08:
+ return s->chan[chan_id].control;
+ case 0x0C:
+ return s->chan[chan_id].lc[0];
+ case 0x10:
+ return s->chan[chan_id].lc[1];
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset "
+ TARGET_FMT_plx "\n", offset);
+ return 0;
+ }
+ }
+ if (offset >= PL330_REG_CSR_BASE && offset < 0x400) {
+ offset -= PL330_REG_CSR_BASE;
+ chan_id = offset >> 3;
+ if (chan_id >= s->num_chnls) {
+ qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset "
+ TARGET_FMT_plx "\n", offset);
+ return 0;
+ }
+ switch ((offset >> 2) & 1) {
+ case 0x0:
+ res = (s->chan[chan_id].ns << 21) |
+ (s->chan[chan_id].wakeup << 4) |
+ (s->chan[chan_id].state) |
+ (s->chan[chan_id].wfp_sbp << 14);
+ return res;
+ case 0x1:
+ return s->chan[chan_id].pc;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "pl330: read error\n");
+ return 0;
+ }
+ }
+ if (offset >= PL330_REG_FTR_BASE && offset < 0x100) {
+ offset -= PL330_REG_FTR_BASE;
+ chan_id = offset >> 2;
+ if (chan_id >= s->num_chnls) {
+ qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset "
+ TARGET_FMT_plx "\n", offset);
+ return 0;
+ }
+ return s->chan[chan_id].fault_type;
+ }
+ switch (offset) {
+ case PL330_REG_DSR:
+ return (s->manager.ns << 9) | (s->manager.wakeup << 4) |
+ (s->manager.state & 0xf);
+ case PL330_REG_DPC:
+ return s->manager.pc;
+ case PL330_REG_INTEN:
+ return s->inten;
+ case PL330_REG_INT_EVENT_RIS:
+ return s->ev_status;
+ case PL330_REG_INTMIS:
+ return s->int_status;
+ case PL330_REG_INTCLR:
+ /* Documentation says that we can't read this register
+ * but linux kernel does it
+ */
+ return 0;
+ case PL330_REG_FSRD:
+ return s->manager.state ? 1 : 0;
+ case PL330_REG_FSRC:
+ res = 0;
+ for (i = 0; i < s->num_chnls; i++) {
+ if (s->chan[i].state == pl330_chan_fault ||
+ s->chan[i].state == pl330_chan_fault_completing) {
+ res |= 1 << i;
+ }
+ }
+ return res;
+ case PL330_REG_FTRD:
+ return s->manager.fault_type;
+ case PL330_REG_DBGSTATUS:
+ return s->debug_status;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "pl330: bad read offset "
+ TARGET_FMT_plx "\n", offset);
+ }
+ return 0;
+}
+
+static uint64_t pl330_iomem_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t ret = pl330_iomem_read_imp(opaque, offset);
+ DB_PRINT("addr: %08" HWADDR_PRIx " data: %08" PRIx32 "\n", offset, ret);
+ return ret;
+}
+
+static const MemoryRegionOps pl330_ops = {
+ .read = pl330_iomem_read,
+ .write = pl330_iomem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+/* Controller logic and initialization */
+
+static void pl330_chan_reset(PL330Chan *ch)
+{
+ ch->src = 0;
+ ch->dst = 0;
+ ch->pc = 0;
+ ch->state = pl330_chan_stopped;
+ ch->watchdog_timer = 0;
+ ch->stall = 0;
+ ch->control = 0;
+ ch->status = 0;
+ ch->fault_type = 0;
+}
+
+static void pl330_reset(DeviceState *d)
+{
+ int i;
+ PL330State *s = PL330(d);
+
+ s->inten = 0;
+ s->int_status = 0;
+ s->ev_status = 0;
+ s->debug_status = 0;
+ s->num_faulting = 0;
+ s->manager.ns = s->mgr_ns_at_rst;
+ pl330_fifo_reset(&s->fifo);
+ pl330_queue_reset(&s->read_queue);
+ pl330_queue_reset(&s->write_queue);
+
+ for (i = 0; i < s->num_chnls; i++) {
+ pl330_chan_reset(&s->chan[i]);
+ }
+ for (i = 0; i < s->num_periph_req; i++) {
+ s->periph_busy[i] = 0;
+ }
+
+ timer_del(s->timer);
+}
+
+static void pl330_realize(DeviceState *dev, Error **errp)
+{
+ int i;
+ PL330State *s = PL330(dev);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq_abort);
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl330_ops, s,
+ "dma", PL330_IOMEM_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, pl330_exec_cycle_timer, s);
+
+ s->cfg[0] = (s->mgr_ns_at_rst ? 0x4 : 0) |
+ (s->num_periph_req > 0 ? 1 : 0) |
+ ((s->num_chnls - 1) & 0x7) << 4 |
+ ((s->num_periph_req - 1) & 0x1f) << 12 |
+ ((s->num_events - 1) & 0x1f) << 17;
+
+ switch (s->i_cache_len) {
+ case (4):
+ s->cfg[1] |= 2;
+ break;
+ case (8):
+ s->cfg[1] |= 3;
+ break;
+ case (16):
+ s->cfg[1] |= 4;
+ break;
+ case (32):
+ s->cfg[1] |= 5;
+ break;
+ default:
+ error_setg(errp, "Bad value for i-cache_len property: %" PRIx8,
+ s->i_cache_len);
+ return;
+ }
+ s->cfg[1] |= ((s->num_i_cache_lines - 1) & 0xf) << 4;
+
+ s->chan = g_new0(PL330Chan, s->num_chnls);
+ s->hi_seqn = g_new0(uint8_t, s->num_chnls);
+ s->lo_seqn = g_new0(uint8_t, s->num_chnls);
+ for (i = 0; i < s->num_chnls; i++) {
+ s->chan[i].parent = s;
+ s->chan[i].tag = (uint8_t)i;
+ }
+ s->manager.parent = s;
+ s->manager.tag = s->num_chnls;
+ s->manager.is_manager = true;
+
+ s->irq = g_new0(qemu_irq, s->num_events);
+ for (i = 0; i < s->num_events; i++) {
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[i]);
+ }
+
+ qdev_init_gpio_in(dev, pl330_dma_stop_irq, PL330_PERIPH_NUM);
+
+ switch (s->data_width) {
+ case (32):
+ s->cfg[CFG_CRD] |= 0x2;
+ break;
+ case (64):
+ s->cfg[CFG_CRD] |= 0x3;
+ break;
+ case (128):
+ s->cfg[CFG_CRD] |= 0x4;
+ break;
+ default:
+ error_setg(errp, "Bad value for data_width property: %" PRIx8,
+ s->data_width);
+ return;
+ }
+
+ s->cfg[CFG_CRD] |= ((s->wr_cap - 1) & 0x7) << 4 |
+ ((s->wr_q_dep - 1) & 0xf) << 8 |
+ ((s->rd_cap - 1) & 0x7) << 12 |
+ ((s->rd_q_dep - 1) & 0xf) << 16 |
+ ((s->data_buffer_dep - 1) & 0x1ff) << 20;
+
+ pl330_queue_init(&s->read_queue, s->rd_q_dep, s);
+ pl330_queue_init(&s->write_queue, s->wr_q_dep, s);
+ pl330_fifo_init(&s->fifo, s->data_width / 4 * s->data_buffer_dep);
+}
+
+static Property pl330_properties[] = {
+ /* CR0 */
+ DEFINE_PROP_UINT32("num_chnls", PL330State, num_chnls, 8),
+ DEFINE_PROP_UINT8("num_periph_req", PL330State, num_periph_req, 4),
+ DEFINE_PROP_UINT8("num_events", PL330State, num_events, 16),
+ DEFINE_PROP_UINT8("mgr_ns_at_rst", PL330State, mgr_ns_at_rst, 0),
+ /* CR1 */
+ DEFINE_PROP_UINT8("i-cache_len", PL330State, i_cache_len, 4),
+ DEFINE_PROP_UINT8("num_i-cache_lines", PL330State, num_i_cache_lines, 8),
+ /* CR2-4 */
+ DEFINE_PROP_UINT32("boot_addr", PL330State, cfg[CFG_BOOT_ADDR], 0),
+ DEFINE_PROP_UINT32("INS", PL330State, cfg[CFG_INS], 0),
+ DEFINE_PROP_UINT32("PNS", PL330State, cfg[CFG_PNS], 0),
+ /* CRD */
+ DEFINE_PROP_UINT8("data_width", PL330State, data_width, 64),
+ DEFINE_PROP_UINT8("wr_cap", PL330State, wr_cap, 8),
+ DEFINE_PROP_UINT8("wr_q_dep", PL330State, wr_q_dep, 16),
+ DEFINE_PROP_UINT8("rd_cap", PL330State, rd_cap, 8),
+ DEFINE_PROP_UINT8("rd_q_dep", PL330State, rd_q_dep, 16),
+ DEFINE_PROP_UINT16("data_buffer_dep", PL330State, data_buffer_dep, 256),
+
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pl330_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pl330_realize;
+ dc->reset = pl330_reset;
+ dc->props = pl330_properties;
+ dc->vmsd = &vmstate_pl330;
+}
+
+static const TypeInfo pl330_type_info = {
+ .name = TYPE_PL330,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL330State),
+ .class_init = pl330_class_init,
+};
+
+static void pl330_register_types(void)
+{
+ type_register_static(&pl330_type_info);
+}
+
+type_init(pl330_register_types)
diff --git a/hw/dma/puv3_dma.c b/hw/dma/puv3_dma.c
new file mode 100644
index 00000000..101bd7f8
--- /dev/null
+++ b/hw/dma/puv3_dma.c
@@ -0,0 +1,113 @@
+/*
+ * DMA device simulation in PKUnity SoC
+ *
+ * Copyright (C) 2010-2012 Guan Xuetao
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation, or any later version.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+
+#undef DEBUG_PUV3
+#include "hw/unicore32/puv3.h"
+
+#define PUV3_DMA_CH_NR (6)
+#define PUV3_DMA_CH_MASK (0xff)
+#define PUV3_DMA_CH(offset) ((offset) >> 8)
+
+#define TYPE_PUV3_DMA "puv3_dma"
+#define PUV3_DMA(obj) OBJECT_CHECK(PUV3DMAState, (obj), TYPE_PUV3_DMA)
+
+typedef struct PUV3DMAState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t reg_CFG[PUV3_DMA_CH_NR];
+} PUV3DMAState;
+
+static uint64_t puv3_dma_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PUV3DMAState *s = opaque;
+ uint32_t ret = 0;
+
+ assert(PUV3_DMA_CH(offset) < PUV3_DMA_CH_NR);
+
+ switch (offset & PUV3_DMA_CH_MASK) {
+ case 0x10:
+ ret = s->reg_CFG[PUV3_DMA_CH(offset)];
+ break;
+ default:
+ DPRINTF("Bad offset 0x%x\n", offset);
+ }
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, ret);
+
+ return ret;
+}
+
+static void puv3_dma_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PUV3DMAState *s = opaque;
+
+ assert(PUV3_DMA_CH(offset) < PUV3_DMA_CH_NR);
+
+ switch (offset & PUV3_DMA_CH_MASK) {
+ case 0x10:
+ s->reg_CFG[PUV3_DMA_CH(offset)] = value;
+ break;
+ default:
+ DPRINTF("Bad offset 0x%x\n", offset);
+ }
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, value);
+}
+
+static const MemoryRegionOps puv3_dma_ops = {
+ .read = puv3_dma_read,
+ .write = puv3_dma_write,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int puv3_dma_init(SysBusDevice *dev)
+{
+ PUV3DMAState *s = PUV3_DMA(dev);
+ int i;
+
+ for (i = 0; i < PUV3_DMA_CH_NR; i++) {
+ s->reg_CFG[i] = 0x0;
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &puv3_dma_ops, s, "puv3_dma",
+ PUV3_REGS_OFFSET);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void puv3_dma_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = puv3_dma_init;
+}
+
+static const TypeInfo puv3_dma_info = {
+ .name = TYPE_PUV3_DMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PUV3DMAState),
+ .class_init = puv3_dma_class_init,
+};
+
+static void puv3_dma_register_type(void)
+{
+ type_register_static(&puv3_dma_info);
+}
+
+type_init(puv3_dma_register_type)
diff --git a/hw/dma/pxa2xx_dma.c b/hw/dma/pxa2xx_dma.c
new file mode 100644
index 00000000..d4501fb4
--- /dev/null
+++ b/hw/dma/pxa2xx_dma.c
@@ -0,0 +1,576 @@
+/*
+ * Intel XScale PXA255/270 DMA controller.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Copyright (c) 2006 Thorsten Zitterell
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "hw/sysbus.h"
+
+#define PXA255_DMA_NUM_CHANNELS 16
+#define PXA27X_DMA_NUM_CHANNELS 32
+
+#define PXA2XX_DMA_NUM_REQUESTS 75
+
+typedef struct {
+ uint32_t descr;
+ uint32_t src;
+ uint32_t dest;
+ uint32_t cmd;
+ uint32_t state;
+ int request;
+} PXA2xxDMAChannel;
+
+#define TYPE_PXA2XX_DMA "pxa2xx-dma"
+#define PXA2XX_DMA(obj) OBJECT_CHECK(PXA2xxDMAState, (obj), TYPE_PXA2XX_DMA)
+
+typedef struct PXA2xxDMAState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ uint32_t stopintr;
+ uint32_t eorintr;
+ uint32_t rasintr;
+ uint32_t startintr;
+ uint32_t endintr;
+
+ uint32_t align;
+ uint32_t pio;
+
+ int channels;
+ PXA2xxDMAChannel *chan;
+
+ uint8_t req[PXA2XX_DMA_NUM_REQUESTS];
+
+ /* Flag to avoid recursive DMA invocations. */
+ int running;
+} PXA2xxDMAState;
+
+#define DCSR0 0x0000 /* DMA Control / Status register for Channel 0 */
+#define DCSR31 0x007c /* DMA Control / Status register for Channel 31 */
+#define DALGN 0x00a0 /* DMA Alignment register */
+#define DPCSR 0x00a4 /* DMA Programmed I/O Control Status register */
+#define DRQSR0 0x00e0 /* DMA DREQ<0> Status register */
+#define DRQSR1 0x00e4 /* DMA DREQ<1> Status register */
+#define DRQSR2 0x00e8 /* DMA DREQ<2> Status register */
+#define DINT 0x00f0 /* DMA Interrupt register */
+#define DRCMR0 0x0100 /* Request to Channel Map register 0 */
+#define DRCMR63 0x01fc /* Request to Channel Map register 63 */
+#define D_CH0 0x0200 /* Channel 0 Descriptor start */
+#define DRCMR64 0x1100 /* Request to Channel Map register 64 */
+#define DRCMR74 0x1128 /* Request to Channel Map register 74 */
+
+/* Per-channel register */
+#define DDADR 0x00
+#define DSADR 0x01
+#define DTADR 0x02
+#define DCMD 0x03
+
+/* Bit-field masks */
+#define DRCMR_CHLNUM 0x1f
+#define DRCMR_MAPVLD (1 << 7)
+#define DDADR_STOP (1 << 0)
+#define DDADR_BREN (1 << 1)
+#define DCMD_LEN 0x1fff
+#define DCMD_WIDTH(x) (1 << ((((x) >> 14) & 3) - 1))
+#define DCMD_SIZE(x) (4 << (((x) >> 16) & 3))
+#define DCMD_FLYBYT (1 << 19)
+#define DCMD_FLYBYS (1 << 20)
+#define DCMD_ENDIRQEN (1 << 21)
+#define DCMD_STARTIRQEN (1 << 22)
+#define DCMD_CMPEN (1 << 25)
+#define DCMD_FLOWTRG (1 << 28)
+#define DCMD_FLOWSRC (1 << 29)
+#define DCMD_INCTRGADDR (1 << 30)
+#define DCMD_INCSRCADDR (1 << 31)
+#define DCSR_BUSERRINTR (1 << 0)
+#define DCSR_STARTINTR (1 << 1)
+#define DCSR_ENDINTR (1 << 2)
+#define DCSR_STOPINTR (1 << 3)
+#define DCSR_RASINTR (1 << 4)
+#define DCSR_REQPEND (1 << 8)
+#define DCSR_EORINT (1 << 9)
+#define DCSR_CMPST (1 << 10)
+#define DCSR_MASKRUN (1 << 22)
+#define DCSR_RASIRQEN (1 << 23)
+#define DCSR_CLRCMPST (1 << 24)
+#define DCSR_SETCMPST (1 << 25)
+#define DCSR_EORSTOPEN (1 << 26)
+#define DCSR_EORJMPEN (1 << 27)
+#define DCSR_EORIRQEN (1 << 28)
+#define DCSR_STOPIRQEN (1 << 29)
+#define DCSR_NODESCFETCH (1 << 30)
+#define DCSR_RUN (1 << 31)
+
+static inline void pxa2xx_dma_update(PXA2xxDMAState *s, int ch)
+{
+ if (ch >= 0) {
+ if ((s->chan[ch].state & DCSR_STOPIRQEN) &&
+ (s->chan[ch].state & DCSR_STOPINTR))
+ s->stopintr |= 1 << ch;
+ else
+ s->stopintr &= ~(1 << ch);
+
+ if ((s->chan[ch].state & DCSR_EORIRQEN) &&
+ (s->chan[ch].state & DCSR_EORINT))
+ s->eorintr |= 1 << ch;
+ else
+ s->eorintr &= ~(1 << ch);
+
+ if ((s->chan[ch].state & DCSR_RASIRQEN) &&
+ (s->chan[ch].state & DCSR_RASINTR))
+ s->rasintr |= 1 << ch;
+ else
+ s->rasintr &= ~(1 << ch);
+
+ if (s->chan[ch].state & DCSR_STARTINTR)
+ s->startintr |= 1 << ch;
+ else
+ s->startintr &= ~(1 << ch);
+
+ if (s->chan[ch].state & DCSR_ENDINTR)
+ s->endintr |= 1 << ch;
+ else
+ s->endintr &= ~(1 << ch);
+ }
+
+ if (s->stopintr | s->eorintr | s->rasintr | s->startintr | s->endintr)
+ qemu_irq_raise(s->irq);
+ else
+ qemu_irq_lower(s->irq);
+}
+
+static inline void pxa2xx_dma_descriptor_fetch(
+ PXA2xxDMAState *s, int ch)
+{
+ uint32_t desc[4];
+ hwaddr daddr = s->chan[ch].descr & ~0xf;
+ if ((s->chan[ch].descr & DDADR_BREN) && (s->chan[ch].state & DCSR_CMPST))
+ daddr += 32;
+
+ cpu_physical_memory_read(daddr, desc, 16);
+ s->chan[ch].descr = desc[DDADR];
+ s->chan[ch].src = desc[DSADR];
+ s->chan[ch].dest = desc[DTADR];
+ s->chan[ch].cmd = desc[DCMD];
+
+ if (s->chan[ch].cmd & DCMD_FLOWSRC)
+ s->chan[ch].src &= ~3;
+ if (s->chan[ch].cmd & DCMD_FLOWTRG)
+ s->chan[ch].dest &= ~3;
+
+ if (s->chan[ch].cmd & (DCMD_CMPEN | DCMD_FLYBYS | DCMD_FLYBYT))
+ printf("%s: unsupported mode in channel %i\n", __FUNCTION__, ch);
+
+ if (s->chan[ch].cmd & DCMD_STARTIRQEN)
+ s->chan[ch].state |= DCSR_STARTINTR;
+}
+
+static void pxa2xx_dma_run(PXA2xxDMAState *s)
+{
+ int c, srcinc, destinc;
+ uint32_t n, size;
+ uint32_t width;
+ uint32_t length;
+ uint8_t buffer[32];
+ PXA2xxDMAChannel *ch;
+
+ if (s->running ++)
+ return;
+
+ while (s->running) {
+ s->running = 1;
+ for (c = 0; c < s->channels; c ++) {
+ ch = &s->chan[c];
+
+ while ((ch->state & DCSR_RUN) && !(ch->state & DCSR_STOPINTR)) {
+ /* Test for pending requests */
+ if ((ch->cmd & (DCMD_FLOWSRC | DCMD_FLOWTRG)) && !ch->request)
+ break;
+
+ length = ch->cmd & DCMD_LEN;
+ size = DCMD_SIZE(ch->cmd);
+ width = DCMD_WIDTH(ch->cmd);
+
+ srcinc = (ch->cmd & DCMD_INCSRCADDR) ? width : 0;
+ destinc = (ch->cmd & DCMD_INCTRGADDR) ? width : 0;
+
+ while (length) {
+ size = MIN(length, size);
+
+ for (n = 0; n < size; n += width) {
+ cpu_physical_memory_read(ch->src, buffer + n, width);
+ ch->src += srcinc;
+ }
+
+ for (n = 0; n < size; n += width) {
+ cpu_physical_memory_write(ch->dest, buffer + n, width);
+ ch->dest += destinc;
+ }
+
+ length -= size;
+
+ if ((ch->cmd & (DCMD_FLOWSRC | DCMD_FLOWTRG)) &&
+ !ch->request) {
+ ch->state |= DCSR_EORINT;
+ if (ch->state & DCSR_EORSTOPEN)
+ ch->state |= DCSR_STOPINTR;
+ if ((ch->state & DCSR_EORJMPEN) &&
+ !(ch->state & DCSR_NODESCFETCH))
+ pxa2xx_dma_descriptor_fetch(s, c);
+ break;
+ }
+ }
+
+ ch->cmd = (ch->cmd & ~DCMD_LEN) | length;
+
+ /* Is the transfer complete now? */
+ if (!length) {
+ if (ch->cmd & DCMD_ENDIRQEN)
+ ch->state |= DCSR_ENDINTR;
+
+ if ((ch->state & DCSR_NODESCFETCH) ||
+ (ch->descr & DDADR_STOP) ||
+ (ch->state & DCSR_EORSTOPEN)) {
+ ch->state |= DCSR_STOPINTR;
+ ch->state &= ~DCSR_RUN;
+
+ break;
+ }
+
+ ch->state |= DCSR_STOPINTR;
+ break;
+ }
+ }
+ }
+
+ s->running --;
+ }
+}
+
+static uint64_t pxa2xx_dma_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PXA2xxDMAState *s = (PXA2xxDMAState *) opaque;
+ unsigned int channel;
+
+ if (size != 4) {
+ hw_error("%s: Bad access width\n", __FUNCTION__);
+ return 5;
+ }
+
+ switch (offset) {
+ case DRCMR64 ... DRCMR74:
+ offset -= DRCMR64 - DRCMR0 - (64 << 2);
+ /* Fall through */
+ case DRCMR0 ... DRCMR63:
+ channel = (offset - DRCMR0) >> 2;
+ return s->req[channel];
+
+ case DRQSR0:
+ case DRQSR1:
+ case DRQSR2:
+ return 0;
+
+ case DCSR0 ... DCSR31:
+ channel = offset >> 2;
+ if (s->chan[channel].request)
+ return s->chan[channel].state | DCSR_REQPEND;
+ return s->chan[channel].state;
+
+ case DINT:
+ return s->stopintr | s->eorintr | s->rasintr |
+ s->startintr | s->endintr;
+
+ case DALGN:
+ return s->align;
+
+ case DPCSR:
+ return s->pio;
+ }
+
+ if (offset >= D_CH0 && offset < D_CH0 + (s->channels << 4)) {
+ channel = (offset - D_CH0) >> 4;
+ switch ((offset & 0x0f) >> 2) {
+ case DDADR:
+ return s->chan[channel].descr;
+ case DSADR:
+ return s->chan[channel].src;
+ case DTADR:
+ return s->chan[channel].dest;
+ case DCMD:
+ return s->chan[channel].cmd;
+ }
+ }
+
+ hw_error("%s: Bad offset 0x" TARGET_FMT_plx "\n", __FUNCTION__, offset);
+ return 7;
+}
+
+static void pxa2xx_dma_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxDMAState *s = (PXA2xxDMAState *) opaque;
+ unsigned int channel;
+
+ if (size != 4) {
+ hw_error("%s: Bad access width\n", __FUNCTION__);
+ return;
+ }
+
+ switch (offset) {
+ case DRCMR64 ... DRCMR74:
+ offset -= DRCMR64 - DRCMR0 - (64 << 2);
+ /* Fall through */
+ case DRCMR0 ... DRCMR63:
+ channel = (offset - DRCMR0) >> 2;
+
+ if (value & DRCMR_MAPVLD)
+ if ((value & DRCMR_CHLNUM) > s->channels)
+ hw_error("%s: Bad DMA channel %i\n",
+ __FUNCTION__, (unsigned)value & DRCMR_CHLNUM);
+
+ s->req[channel] = value;
+ break;
+
+ case DRQSR0:
+ case DRQSR1:
+ case DRQSR2:
+ /* Nothing to do */
+ break;
+
+ case DCSR0 ... DCSR31:
+ channel = offset >> 2;
+ s->chan[channel].state &= 0x0000071f & ~(value &
+ (DCSR_EORINT | DCSR_ENDINTR |
+ DCSR_STARTINTR | DCSR_BUSERRINTR));
+ s->chan[channel].state |= value & 0xfc800000;
+
+ if (s->chan[channel].state & DCSR_STOPIRQEN)
+ s->chan[channel].state &= ~DCSR_STOPINTR;
+
+ if (value & DCSR_NODESCFETCH) {
+ /* No-descriptor-fetch mode */
+ if (value & DCSR_RUN) {
+ s->chan[channel].state &= ~DCSR_STOPINTR;
+ pxa2xx_dma_run(s);
+ }
+ } else {
+ /* Descriptor-fetch mode */
+ if (value & DCSR_RUN) {
+ s->chan[channel].state &= ~DCSR_STOPINTR;
+ pxa2xx_dma_descriptor_fetch(s, channel);
+ pxa2xx_dma_run(s);
+ }
+ }
+
+ /* Shouldn't matter as our DMA is synchronous. */
+ if (!(value & (DCSR_RUN | DCSR_MASKRUN)))
+ s->chan[channel].state |= DCSR_STOPINTR;
+
+ if (value & DCSR_CLRCMPST)
+ s->chan[channel].state &= ~DCSR_CMPST;
+ if (value & DCSR_SETCMPST)
+ s->chan[channel].state |= DCSR_CMPST;
+
+ pxa2xx_dma_update(s, channel);
+ break;
+
+ case DALGN:
+ s->align = value;
+ break;
+
+ case DPCSR:
+ s->pio = value & 0x80000001;
+ break;
+
+ default:
+ if (offset >= D_CH0 && offset < D_CH0 + (s->channels << 4)) {
+ channel = (offset - D_CH0) >> 4;
+ switch ((offset & 0x0f) >> 2) {
+ case DDADR:
+ s->chan[channel].descr = value;
+ break;
+ case DSADR:
+ s->chan[channel].src = value;
+ break;
+ case DTADR:
+ s->chan[channel].dest = value;
+ break;
+ case DCMD:
+ s->chan[channel].cmd = value;
+ break;
+ default:
+ goto fail;
+ }
+
+ break;
+ }
+ fail:
+ hw_error("%s: Bad offset " TARGET_FMT_plx "\n", __FUNCTION__, offset);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_dma_ops = {
+ .read = pxa2xx_dma_read,
+ .write = pxa2xx_dma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pxa2xx_dma_request(void *opaque, int req_num, int on)
+{
+ PXA2xxDMAState *s = opaque;
+ int ch;
+ if (req_num < 0 || req_num >= PXA2XX_DMA_NUM_REQUESTS)
+ hw_error("%s: Bad DMA request %i\n", __FUNCTION__, req_num);
+
+ if (!(s->req[req_num] & DRCMR_MAPVLD))
+ return;
+ ch = s->req[req_num] & DRCMR_CHLNUM;
+
+ if (!s->chan[ch].request && on)
+ s->chan[ch].state |= DCSR_RASINTR;
+ else
+ s->chan[ch].state &= ~DCSR_RASINTR;
+ if (s->chan[ch].request && !on)
+ s->chan[ch].state |= DCSR_EORINT;
+
+ s->chan[ch].request = on;
+ if (on) {
+ pxa2xx_dma_run(s);
+ pxa2xx_dma_update(s, ch);
+ }
+}
+
+static int pxa2xx_dma_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PXA2xxDMAState *s = PXA2XX_DMA(dev);
+ int i;
+
+ if (s->channels <= 0) {
+ return -1;
+ }
+
+ s->chan = g_malloc0(sizeof(PXA2xxDMAChannel) * s->channels);
+
+ memset(s->chan, 0, sizeof(PXA2xxDMAChannel) * s->channels);
+ for (i = 0; i < s->channels; i ++)
+ s->chan[i].state = DCSR_STOPINTR;
+
+ memset(s->req, 0, sizeof(uint8_t) * PXA2XX_DMA_NUM_REQUESTS);
+
+ qdev_init_gpio_in(dev, pxa2xx_dma_request, PXA2XX_DMA_NUM_REQUESTS);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_dma_ops, s,
+ "pxa2xx.dma", 0x00010000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ return 0;
+}
+
+DeviceState *pxa27x_dma_init(hwaddr base, qemu_irq irq)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "pxa2xx-dma");
+ qdev_prop_set_int32(dev, "channels", PXA27X_DMA_NUM_CHANNELS);
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
+
+ return dev;
+}
+
+DeviceState *pxa255_dma_init(hwaddr base, qemu_irq irq)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "pxa2xx-dma");
+ qdev_prop_set_int32(dev, "channels", PXA27X_DMA_NUM_CHANNELS);
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
+
+ return dev;
+}
+
+static bool is_version_0(void *opaque, int version_id)
+{
+ return version_id == 0;
+}
+
+static VMStateDescription vmstate_pxa2xx_dma_chan = {
+ .name = "pxa2xx_dma_chan",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(descr, PXA2xxDMAChannel),
+ VMSTATE_UINT32(src, PXA2xxDMAChannel),
+ VMSTATE_UINT32(dest, PXA2xxDMAChannel),
+ VMSTATE_UINT32(cmd, PXA2xxDMAChannel),
+ VMSTATE_UINT32(state, PXA2xxDMAChannel),
+ VMSTATE_INT32(request, PXA2xxDMAChannel),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static VMStateDescription vmstate_pxa2xx_dma = {
+ .name = "pxa2xx_dma",
+ .version_id = 1,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UNUSED_TEST(is_version_0, 4),
+ VMSTATE_UINT32(stopintr, PXA2xxDMAState),
+ VMSTATE_UINT32(eorintr, PXA2xxDMAState),
+ VMSTATE_UINT32(rasintr, PXA2xxDMAState),
+ VMSTATE_UINT32(startintr, PXA2xxDMAState),
+ VMSTATE_UINT32(endintr, PXA2xxDMAState),
+ VMSTATE_UINT32(align, PXA2xxDMAState),
+ VMSTATE_UINT32(pio, PXA2xxDMAState),
+ VMSTATE_BUFFER(req, PXA2xxDMAState),
+ VMSTATE_STRUCT_VARRAY_POINTER_INT32(chan, PXA2xxDMAState, channels,
+ vmstate_pxa2xx_dma_chan, PXA2xxDMAChannel),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static Property pxa2xx_dma_properties[] = {
+ DEFINE_PROP_INT32("channels", PXA2xxDMAState, channels, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxa2xx_dma_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pxa2xx_dma_init;
+ dc->desc = "PXA2xx DMA controller";
+ dc->vmsd = &vmstate_pxa2xx_dma;
+ dc->props = pxa2xx_dma_properties;
+}
+
+static const TypeInfo pxa2xx_dma_info = {
+ .name = TYPE_PXA2XX_DMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxDMAState),
+ .class_init = pxa2xx_dma_class_init,
+};
+
+static void pxa2xx_dma_register_types(void)
+{
+ type_register_static(&pxa2xx_dma_info);
+}
+
+type_init(pxa2xx_dma_register_types)
diff --git a/hw/dma/rc4030.c b/hw/dma/rc4030.c
new file mode 100644
index 00000000..3efa6de3
--- /dev/null
+++ b/hw/dma/rc4030.c
@@ -0,0 +1,841 @@
+/*
+ * QEMU JAZZ RC4030 chipset
+ *
+ * Copyright (c) 2007-2013 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/mips/mips.h"
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "exec/address-spaces.h"
+#include "trace.h"
+
+/********************************************************/
+/* rc4030 emulation */
+
+#define MAX_TL_ENTRIES 512
+
+typedef struct dma_pagetable_entry {
+ int32_t frame;
+ int32_t owner;
+} QEMU_PACKED dma_pagetable_entry;
+
+#define DMA_PAGESIZE 4096
+#define DMA_REG_ENABLE 1
+#define DMA_REG_COUNT 2
+#define DMA_REG_ADDRESS 3
+
+#define DMA_FLAG_ENABLE 0x0001
+#define DMA_FLAG_MEM_TO_DEV 0x0002
+#define DMA_FLAG_TC_INTR 0x0100
+#define DMA_FLAG_MEM_INTR 0x0200
+#define DMA_FLAG_ADDR_INTR 0x0400
+
+#define TYPE_RC4030 "rc4030"
+#define RC4030(obj) \
+ OBJECT_CHECK(rc4030State, (obj), TYPE_RC4030)
+
+typedef struct rc4030State
+{
+ SysBusDevice parent;
+
+ uint32_t config; /* 0x0000: RC4030 config register */
+ uint32_t revision; /* 0x0008: RC4030 Revision register */
+ uint32_t invalid_address_register; /* 0x0010: Invalid Address register */
+
+ /* DMA */
+ uint32_t dma_regs[8][4];
+ uint32_t dma_tl_base; /* 0x0018: DMA transl. table base */
+ uint32_t dma_tl_limit; /* 0x0020: DMA transl. table limit */
+
+ /* cache */
+ uint32_t cache_maint; /* 0x0030: Cache Maintenance */
+ uint32_t remote_failed_address; /* 0x0038: Remote Failed Address */
+ uint32_t memory_failed_address; /* 0x0040: Memory Failed Address */
+ uint32_t cache_ptag; /* 0x0048: I/O Cache Physical Tag */
+ uint32_t cache_ltag; /* 0x0050: I/O Cache Logical Tag */
+ uint32_t cache_bmask; /* 0x0058: I/O Cache Byte Mask */
+
+ uint32_t nmi_interrupt; /* 0x0200: interrupt source */
+ uint32_t memory_refresh_rate; /* 0x0210: memory refresh rate */
+ uint32_t nvram_protect; /* 0x0220: NV ram protect register */
+ uint32_t rem_speed[16];
+ uint32_t imr_jazz; /* Local bus int enable mask */
+ uint32_t isr_jazz; /* Local bus int source */
+
+ /* timer */
+ QEMUTimer *periodic_timer;
+ uint32_t itr; /* Interval timer reload */
+
+ qemu_irq timer_irq;
+ qemu_irq jazz_bus_irq;
+
+ /* biggest translation table */
+ MemoryRegion dma_tt;
+ /* translation table memory region alias, added to system RAM */
+ MemoryRegion dma_tt_alias;
+ /* whole DMA memory region, root of DMA address space */
+ MemoryRegion dma_mr;
+ /* translation table entry aliases, added to DMA memory region */
+ MemoryRegion dma_mrs[MAX_TL_ENTRIES];
+ AddressSpace dma_as;
+
+ MemoryRegion iomem_chipset;
+ MemoryRegion iomem_jazzio;
+} rc4030State;
+
+static void set_next_tick(rc4030State *s)
+{
+ qemu_irq_lower(s->timer_irq);
+ uint32_t tm_hz;
+
+ tm_hz = 1000 / (s->itr + 1);
+
+ timer_mod(s->periodic_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ get_ticks_per_sec() / tm_hz);
+}
+
+/* called for accesses to rc4030 */
+static uint64_t rc4030_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ rc4030State *s = opaque;
+ uint32_t val;
+
+ addr &= 0x3fff;
+ switch (addr & ~0x3) {
+ /* Global config register */
+ case 0x0000:
+ val = s->config;
+ break;
+ /* Revision register */
+ case 0x0008:
+ val = s->revision;
+ break;
+ /* Invalid Address register */
+ case 0x0010:
+ val = s->invalid_address_register;
+ break;
+ /* DMA transl. table base */
+ case 0x0018:
+ val = s->dma_tl_base;
+ break;
+ /* DMA transl. table limit */
+ case 0x0020:
+ val = s->dma_tl_limit;
+ break;
+ /* Remote Failed Address */
+ case 0x0038:
+ val = s->remote_failed_address;
+ break;
+ /* Memory Failed Address */
+ case 0x0040:
+ val = s->memory_failed_address;
+ break;
+ /* I/O Cache Byte Mask */
+ case 0x0058:
+ val = s->cache_bmask;
+ /* HACK */
+ if (s->cache_bmask == (uint32_t)-1)
+ s->cache_bmask = 0;
+ break;
+ /* Remote Speed Registers */
+ case 0x0070:
+ case 0x0078:
+ case 0x0080:
+ case 0x0088:
+ case 0x0090:
+ case 0x0098:
+ case 0x00a0:
+ case 0x00a8:
+ case 0x00b0:
+ case 0x00b8:
+ case 0x00c0:
+ case 0x00c8:
+ case 0x00d0:
+ case 0x00d8:
+ case 0x00e0:
+ case 0x00e8:
+ val = s->rem_speed[(addr - 0x0070) >> 3];
+ break;
+ /* DMA channel base address */
+ case 0x0100:
+ case 0x0108:
+ case 0x0110:
+ case 0x0118:
+ case 0x0120:
+ case 0x0128:
+ case 0x0130:
+ case 0x0138:
+ case 0x0140:
+ case 0x0148:
+ case 0x0150:
+ case 0x0158:
+ case 0x0160:
+ case 0x0168:
+ case 0x0170:
+ case 0x0178:
+ case 0x0180:
+ case 0x0188:
+ case 0x0190:
+ case 0x0198:
+ case 0x01a0:
+ case 0x01a8:
+ case 0x01b0:
+ case 0x01b8:
+ case 0x01c0:
+ case 0x01c8:
+ case 0x01d0:
+ case 0x01d8:
+ case 0x01e0:
+ case 0x01e8:
+ case 0x01f0:
+ case 0x01f8:
+ {
+ int entry = (addr - 0x0100) >> 5;
+ int idx = (addr & 0x1f) >> 3;
+ val = s->dma_regs[entry][idx];
+ }
+ break;
+ /* Interrupt source */
+ case 0x0200:
+ val = s->nmi_interrupt;
+ break;
+ /* Error type */
+ case 0x0208:
+ val = 0;
+ break;
+ /* Memory refresh rate */
+ case 0x0210:
+ val = s->memory_refresh_rate;
+ break;
+ /* NV ram protect register */
+ case 0x0220:
+ val = s->nvram_protect;
+ break;
+ /* Interval timer count */
+ case 0x0230:
+ val = 0;
+ qemu_irq_lower(s->timer_irq);
+ break;
+ /* EISA interrupt */
+ case 0x0238:
+ val = 7; /* FIXME: should be read from EISA controller */
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "rc4030: invalid read at 0x%x", (int)addr);
+ val = 0;
+ break;
+ }
+
+ if ((addr & ~3) != 0x230) {
+ trace_rc4030_read(addr, val);
+ }
+
+ return val;
+}
+
+static void rc4030_dma_as_update_one(rc4030State *s, int index, uint32_t frame)
+{
+ if (index < MAX_TL_ENTRIES) {
+ memory_region_set_enabled(&s->dma_mrs[index], false);
+ }
+
+ if (!frame) {
+ return;
+ }
+
+ if (index >= MAX_TL_ENTRIES) {
+ qemu_log_mask(LOG_UNIMP,
+ "rc4030: trying to use too high "
+ "translation table entry %d (max allowed=%d)",
+ index, MAX_TL_ENTRIES);
+ return;
+ }
+ memory_region_set_alias_offset(&s->dma_mrs[index], frame);
+ memory_region_set_enabled(&s->dma_mrs[index], true);
+}
+
+static void rc4030_dma_tt_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ rc4030State *s = opaque;
+
+ /* write memory */
+ memcpy(memory_region_get_ram_ptr(&s->dma_tt) + addr, &data, size);
+
+ /* update dma address space (only if frame field has been written) */
+ if (addr % sizeof(dma_pagetable_entry) == 0) {
+ int index = addr / sizeof(dma_pagetable_entry);
+ memory_region_transaction_begin();
+ rc4030_dma_as_update_one(s, index, (uint32_t)data);
+ memory_region_transaction_commit();
+ }
+}
+
+static const MemoryRegionOps rc4030_dma_tt_ops = {
+ .write = rc4030_dma_tt_write,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static void rc4030_dma_tt_update(rc4030State *s, uint32_t new_tl_base,
+ uint32_t new_tl_limit)
+{
+ int entries, i;
+ dma_pagetable_entry *dma_tl_contents;
+
+ if (s->dma_tl_limit) {
+ /* write old dma tl table to physical memory */
+ memory_region_del_subregion(get_system_memory(), &s->dma_tt_alias);
+ cpu_physical_memory_write(s->dma_tl_limit & 0x7fffffff,
+ memory_region_get_ram_ptr(&s->dma_tt),
+ memory_region_size(&s->dma_tt_alias));
+ }
+ object_unparent(OBJECT(&s->dma_tt_alias));
+
+ s->dma_tl_base = new_tl_base;
+ s->dma_tl_limit = new_tl_limit;
+ new_tl_base &= 0x7fffffff;
+
+ if (s->dma_tl_limit) {
+ uint64_t dma_tt_size;
+ if (s->dma_tl_limit <= memory_region_size(&s->dma_tt)) {
+ dma_tt_size = s->dma_tl_limit;
+ } else {
+ dma_tt_size = memory_region_size(&s->dma_tt);
+ }
+ memory_region_init_alias(&s->dma_tt_alias, OBJECT(s),
+ "dma-table-alias",
+ &s->dma_tt, 0, dma_tt_size);
+ dma_tl_contents = memory_region_get_ram_ptr(&s->dma_tt);
+ cpu_physical_memory_read(new_tl_base, dma_tl_contents, dma_tt_size);
+
+ memory_region_transaction_begin();
+ entries = dma_tt_size / sizeof(dma_pagetable_entry);
+ for (i = 0; i < entries; i++) {
+ rc4030_dma_as_update_one(s, i, dma_tl_contents[i].frame);
+ }
+ memory_region_add_subregion(get_system_memory(), new_tl_base,
+ &s->dma_tt_alias);
+ memory_region_transaction_commit();
+ } else {
+ memory_region_init(&s->dma_tt_alias, OBJECT(s),
+ "dma-table-alias", 0);
+ }
+}
+
+static void rc4030_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ rc4030State *s = opaque;
+ uint32_t val = data;
+ addr &= 0x3fff;
+
+ trace_rc4030_write(addr, val);
+
+ switch (addr & ~0x3) {
+ /* Global config register */
+ case 0x0000:
+ s->config = val;
+ break;
+ /* DMA transl. table base */
+ case 0x0018:
+ rc4030_dma_tt_update(s, val, s->dma_tl_limit);
+ break;
+ /* DMA transl. table limit */
+ case 0x0020:
+ rc4030_dma_tt_update(s, s->dma_tl_base, val);
+ break;
+ /* DMA transl. table invalidated */
+ case 0x0028:
+ break;
+ /* Cache Maintenance */
+ case 0x0030:
+ s->cache_maint = val;
+ break;
+ /* I/O Cache Physical Tag */
+ case 0x0048:
+ s->cache_ptag = val;
+ break;
+ /* I/O Cache Logical Tag */
+ case 0x0050:
+ s->cache_ltag = val;
+ break;
+ /* I/O Cache Byte Mask */
+ case 0x0058:
+ s->cache_bmask |= val; /* HACK */
+ break;
+ /* I/O Cache Buffer Window */
+ case 0x0060:
+ /* HACK */
+ if (s->cache_ltag == 0x80000001 && s->cache_bmask == 0xf0f0f0f) {
+ hwaddr dest = s->cache_ptag & ~0x1;
+ dest += (s->cache_maint & 0x3) << 3;
+ cpu_physical_memory_write(dest, &val, 4);
+ }
+ break;
+ /* Remote Speed Registers */
+ case 0x0070:
+ case 0x0078:
+ case 0x0080:
+ case 0x0088:
+ case 0x0090:
+ case 0x0098:
+ case 0x00a0:
+ case 0x00a8:
+ case 0x00b0:
+ case 0x00b8:
+ case 0x00c0:
+ case 0x00c8:
+ case 0x00d0:
+ case 0x00d8:
+ case 0x00e0:
+ case 0x00e8:
+ s->rem_speed[(addr - 0x0070) >> 3] = val;
+ break;
+ /* DMA channel base address */
+ case 0x0100:
+ case 0x0108:
+ case 0x0110:
+ case 0x0118:
+ case 0x0120:
+ case 0x0128:
+ case 0x0130:
+ case 0x0138:
+ case 0x0140:
+ case 0x0148:
+ case 0x0150:
+ case 0x0158:
+ case 0x0160:
+ case 0x0168:
+ case 0x0170:
+ case 0x0178:
+ case 0x0180:
+ case 0x0188:
+ case 0x0190:
+ case 0x0198:
+ case 0x01a0:
+ case 0x01a8:
+ case 0x01b0:
+ case 0x01b8:
+ case 0x01c0:
+ case 0x01c8:
+ case 0x01d0:
+ case 0x01d8:
+ case 0x01e0:
+ case 0x01e8:
+ case 0x01f0:
+ case 0x01f8:
+ {
+ int entry = (addr - 0x0100) >> 5;
+ int idx = (addr & 0x1f) >> 3;
+ s->dma_regs[entry][idx] = val;
+ }
+ break;
+ /* Memory refresh rate */
+ case 0x0210:
+ s->memory_refresh_rate = val;
+ break;
+ /* Interval timer reload */
+ case 0x0228:
+ s->itr = val;
+ qemu_irq_lower(s->timer_irq);
+ set_next_tick(s);
+ break;
+ /* EISA interrupt */
+ case 0x0238:
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "rc4030: invalid write of 0x%02x at 0x%x",
+ val, (int)addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps rc4030_ops = {
+ .read = rc4030_read,
+ .write = rc4030_write,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void update_jazz_irq(rc4030State *s)
+{
+ uint16_t pending;
+
+ pending = s->isr_jazz & s->imr_jazz;
+
+ if (pending != 0)
+ qemu_irq_raise(s->jazz_bus_irq);
+ else
+ qemu_irq_lower(s->jazz_bus_irq);
+}
+
+static void rc4030_irq_jazz_request(void *opaque, int irq, int level)
+{
+ rc4030State *s = opaque;
+
+ if (level) {
+ s->isr_jazz |= 1 << irq;
+ } else {
+ s->isr_jazz &= ~(1 << irq);
+ }
+
+ update_jazz_irq(s);
+}
+
+static void rc4030_periodic_timer(void *opaque)
+{
+ rc4030State *s = opaque;
+
+ set_next_tick(s);
+ qemu_irq_raise(s->timer_irq);
+}
+
+static uint64_t jazzio_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ rc4030State *s = opaque;
+ uint32_t val;
+ uint32_t irq;
+ addr &= 0xfff;
+
+ switch (addr) {
+ /* Local bus int source */
+ case 0x00: {
+ uint32_t pending = s->isr_jazz & s->imr_jazz;
+ val = 0;
+ irq = 0;
+ while (pending) {
+ if (pending & 1) {
+ val = (irq + 1) << 2;
+ break;
+ }
+ irq++;
+ pending >>= 1;
+ }
+ break;
+ }
+ /* Local bus int enable mask */
+ case 0x02:
+ val = s->imr_jazz;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "rc4030/jazzio: invalid read at 0x%x", (int)addr);
+ val = 0;
+ break;
+ }
+
+ trace_jazzio_read(addr, val);
+
+ return val;
+}
+
+static void jazzio_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ rc4030State *s = opaque;
+ uint32_t val = data;
+ addr &= 0xfff;
+
+ trace_jazzio_write(addr, val);
+
+ switch (addr) {
+ /* Local bus int enable mask */
+ case 0x02:
+ s->imr_jazz = val;
+ update_jazz_irq(s);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "rc4030/jazzio: invalid write of 0x%02x at 0x%x",
+ val, (int)addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps jazzio_ops = {
+ .read = jazzio_read,
+ .write = jazzio_write,
+ .impl.min_access_size = 2,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void rc4030_reset(DeviceState *dev)
+{
+ rc4030State *s = RC4030(dev);
+ int i;
+
+ s->config = 0x410; /* some boards seem to accept 0x104 too */
+ s->revision = 1;
+ s->invalid_address_register = 0;
+
+ memset(s->dma_regs, 0, sizeof(s->dma_regs));
+ rc4030_dma_tt_update(s, 0, 0);
+
+ s->remote_failed_address = s->memory_failed_address = 0;
+ s->cache_maint = 0;
+ s->cache_ptag = s->cache_ltag = 0;
+ s->cache_bmask = 0;
+
+ s->memory_refresh_rate = 0x18186;
+ s->nvram_protect = 7;
+ for (i = 0; i < 15; i++)
+ s->rem_speed[i] = 7;
+ s->imr_jazz = 0x10; /* XXX: required by firmware, but why? */
+ s->isr_jazz = 0;
+
+ s->itr = 0;
+
+ qemu_irq_lower(s->timer_irq);
+ qemu_irq_lower(s->jazz_bus_irq);
+}
+
+static int rc4030_load(QEMUFile *f, void *opaque, int version_id)
+{
+ rc4030State* s = opaque;
+ int i, j;
+
+ if (version_id != 2)
+ return -EINVAL;
+
+ s->config = qemu_get_be32(f);
+ s->invalid_address_register = qemu_get_be32(f);
+ for (i = 0; i < 8; i++)
+ for (j = 0; j < 4; j++)
+ s->dma_regs[i][j] = qemu_get_be32(f);
+ s->dma_tl_base = qemu_get_be32(f);
+ s->dma_tl_limit = qemu_get_be32(f);
+ s->cache_maint = qemu_get_be32(f);
+ s->remote_failed_address = qemu_get_be32(f);
+ s->memory_failed_address = qemu_get_be32(f);
+ s->cache_ptag = qemu_get_be32(f);
+ s->cache_ltag = qemu_get_be32(f);
+ s->cache_bmask = qemu_get_be32(f);
+ s->memory_refresh_rate = qemu_get_be32(f);
+ s->nvram_protect = qemu_get_be32(f);
+ for (i = 0; i < 15; i++)
+ s->rem_speed[i] = qemu_get_be32(f);
+ s->imr_jazz = qemu_get_be32(f);
+ s->isr_jazz = qemu_get_be32(f);
+ s->itr = qemu_get_be32(f);
+
+ set_next_tick(s);
+ update_jazz_irq(s);
+
+ return 0;
+}
+
+static void rc4030_save(QEMUFile *f, void *opaque)
+{
+ rc4030State* s = opaque;
+ int i, j;
+
+ qemu_put_be32(f, s->config);
+ qemu_put_be32(f, s->invalid_address_register);
+ for (i = 0; i < 8; i++)
+ for (j = 0; j < 4; j++)
+ qemu_put_be32(f, s->dma_regs[i][j]);
+ qemu_put_be32(f, s->dma_tl_base);
+ qemu_put_be32(f, s->dma_tl_limit);
+ qemu_put_be32(f, s->cache_maint);
+ qemu_put_be32(f, s->remote_failed_address);
+ qemu_put_be32(f, s->memory_failed_address);
+ qemu_put_be32(f, s->cache_ptag);
+ qemu_put_be32(f, s->cache_ltag);
+ qemu_put_be32(f, s->cache_bmask);
+ qemu_put_be32(f, s->memory_refresh_rate);
+ qemu_put_be32(f, s->nvram_protect);
+ for (i = 0; i < 15; i++)
+ qemu_put_be32(f, s->rem_speed[i]);
+ qemu_put_be32(f, s->imr_jazz);
+ qemu_put_be32(f, s->isr_jazz);
+ qemu_put_be32(f, s->itr);
+}
+
+static void rc4030_do_dma(void *opaque, int n, uint8_t *buf, int len, int is_write)
+{
+ rc4030State *s = opaque;
+ hwaddr dma_addr;
+ int dev_to_mem;
+
+ s->dma_regs[n][DMA_REG_ENABLE] &= ~(DMA_FLAG_TC_INTR | DMA_FLAG_MEM_INTR | DMA_FLAG_ADDR_INTR);
+
+ /* Check DMA channel consistency */
+ dev_to_mem = (s->dma_regs[n][DMA_REG_ENABLE] & DMA_FLAG_MEM_TO_DEV) ? 0 : 1;
+ if (!(s->dma_regs[n][DMA_REG_ENABLE] & DMA_FLAG_ENABLE) ||
+ (is_write != dev_to_mem)) {
+ s->dma_regs[n][DMA_REG_ENABLE] |= DMA_FLAG_MEM_INTR;
+ s->nmi_interrupt |= 1 << n;
+ return;
+ }
+
+ /* Get start address and len */
+ if (len > s->dma_regs[n][DMA_REG_COUNT])
+ len = s->dma_regs[n][DMA_REG_COUNT];
+ dma_addr = s->dma_regs[n][DMA_REG_ADDRESS];
+
+ /* Read/write data at right place */
+ address_space_rw(&s->dma_as, dma_addr, MEMTXATTRS_UNSPECIFIED,
+ buf, len, is_write);
+
+ s->dma_regs[n][DMA_REG_ENABLE] |= DMA_FLAG_TC_INTR;
+ s->dma_regs[n][DMA_REG_COUNT] -= len;
+}
+
+struct rc4030DMAState {
+ void *opaque;
+ int n;
+};
+
+void rc4030_dma_read(void *dma, uint8_t *buf, int len)
+{
+ rc4030_dma s = dma;
+ rc4030_do_dma(s->opaque, s->n, buf, len, 0);
+}
+
+void rc4030_dma_write(void *dma, uint8_t *buf, int len)
+{
+ rc4030_dma s = dma;
+ rc4030_do_dma(s->opaque, s->n, buf, len, 1);
+}
+
+static rc4030_dma *rc4030_allocate_dmas(void *opaque, int n)
+{
+ rc4030_dma *s;
+ struct rc4030DMAState *p;
+ int i;
+
+ s = (rc4030_dma *)g_malloc0(sizeof(rc4030_dma) * n);
+ p = (struct rc4030DMAState *)g_malloc0(sizeof(struct rc4030DMAState) * n);
+ for (i = 0; i < n; i++) {
+ p->opaque = opaque;
+ p->n = i;
+ s[i] = p;
+ p++;
+ }
+ return s;
+}
+
+static void rc4030_initfn(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ rc4030State *s = RC4030(obj);
+ SysBusDevice *sysbus = SYS_BUS_DEVICE(obj);
+
+ qdev_init_gpio_in(dev, rc4030_irq_jazz_request, 16);
+
+ sysbus_init_irq(sysbus, &s->timer_irq);
+ sysbus_init_irq(sysbus, &s->jazz_bus_irq);
+
+ register_savevm(NULL, "rc4030", 0, 2, rc4030_save, rc4030_load, s);
+
+ sysbus_init_mmio(sysbus, &s->iomem_chipset);
+ sysbus_init_mmio(sysbus, &s->iomem_jazzio);
+}
+
+static void rc4030_realize(DeviceState *dev, Error **errp)
+{
+ rc4030State *s = RC4030(dev);
+ Object *o = OBJECT(dev);
+ int i;
+
+ s->periodic_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ rc4030_periodic_timer, s);
+
+ memory_region_init_io(&s->iomem_chipset, NULL, &rc4030_ops, s,
+ "rc4030.chipset", 0x300);
+ memory_region_init_io(&s->iomem_jazzio, NULL, &jazzio_ops, s,
+ "rc4030.jazzio", 0x00001000);
+
+ memory_region_init_rom_device(&s->dma_tt, o,
+ &rc4030_dma_tt_ops, s, "dma-table",
+ MAX_TL_ENTRIES * sizeof(dma_pagetable_entry),
+ NULL);
+ memory_region_init(&s->dma_tt_alias, o, "dma-table-alias", 0);
+ memory_region_init(&s->dma_mr, o, "dma", INT32_MAX);
+ for (i = 0; i < MAX_TL_ENTRIES; ++i) {
+ memory_region_init_alias(&s->dma_mrs[i], o, "dma-alias",
+ get_system_memory(), 0, DMA_PAGESIZE);
+ memory_region_set_enabled(&s->dma_mrs[i], false);
+ memory_region_add_subregion(&s->dma_mr, i * DMA_PAGESIZE,
+ &s->dma_mrs[i]);
+ }
+ address_space_init(&s->dma_as, &s->dma_mr, "rc4030-dma");
+}
+
+static void rc4030_unrealize(DeviceState *dev, Error **errp)
+{
+ rc4030State *s = RC4030(dev);
+ int i;
+
+ timer_free(s->periodic_timer);
+
+ address_space_destroy(&s->dma_as);
+ object_unparent(OBJECT(&s->dma_tt));
+ object_unparent(OBJECT(&s->dma_tt_alias));
+ object_unparent(OBJECT(&s->dma_mr));
+ for (i = 0; i < MAX_TL_ENTRIES; ++i) {
+ memory_region_del_subregion(&s->dma_mr, &s->dma_mrs[i]);
+ object_unparent(OBJECT(&s->dma_mrs[i]));
+ }
+}
+
+static void rc4030_class_init(ObjectClass *klass, void *class_data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = rc4030_realize;
+ dc->unrealize = rc4030_unrealize;
+ dc->reset = rc4030_reset;
+}
+
+static const TypeInfo rc4030_info = {
+ .name = TYPE_RC4030,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(rc4030State),
+ .instance_init = rc4030_initfn,
+ .class_init = rc4030_class_init,
+};
+
+static void rc4030_register_types(void)
+{
+ type_register_static(&rc4030_info);
+}
+
+type_init(rc4030_register_types)
+
+DeviceState *rc4030_init(rc4030_dma **dmas, MemoryRegion **dma_mr)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, TYPE_RC4030);
+ qdev_init_nofail(dev);
+
+ *dmas = rc4030_allocate_dmas(dev, 4);
+ *dma_mr = &RC4030(dev)->dma_mr;
+ return dev;
+}
diff --git a/hw/dma/soc_dma.c b/hw/dma/soc_dma.c
new file mode 100644
index 00000000..c06aabb4
--- /dev/null
+++ b/hw/dma/soc_dma.c
@@ -0,0 +1,366 @@
+/*
+ * On-chip DMA controller framework.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "hw/arm/soc_dma.h"
+
+static void transfer_mem2mem(struct soc_dma_ch_s *ch)
+{
+ memcpy(ch->paddr[0], ch->paddr[1], ch->bytes);
+ ch->paddr[0] += ch->bytes;
+ ch->paddr[1] += ch->bytes;
+}
+
+static void transfer_mem2fifo(struct soc_dma_ch_s *ch)
+{
+ ch->io_fn[1](ch->io_opaque[1], ch->paddr[0], ch->bytes);
+ ch->paddr[0] += ch->bytes;
+}
+
+static void transfer_fifo2mem(struct soc_dma_ch_s *ch)
+{
+ ch->io_fn[0](ch->io_opaque[0], ch->paddr[1], ch->bytes);
+ ch->paddr[1] += ch->bytes;
+}
+
+/* This is further optimisable but isn't very important because often
+ * DMA peripherals forbid this kind of transfers and even when they don't,
+ * oprating systems may not need to use them. */
+static void *fifo_buf;
+static int fifo_size;
+static void transfer_fifo2fifo(struct soc_dma_ch_s *ch)
+{
+ if (ch->bytes > fifo_size)
+ fifo_buf = g_realloc(fifo_buf, fifo_size = ch->bytes);
+
+ /* Implement as transfer_fifo2linear + transfer_linear2fifo. */
+ ch->io_fn[0](ch->io_opaque[0], fifo_buf, ch->bytes);
+ ch->io_fn[1](ch->io_opaque[1], fifo_buf, ch->bytes);
+}
+
+struct dma_s {
+ struct soc_dma_s soc;
+ int chnum;
+ uint64_t ch_enable_mask;
+ int64_t channel_freq;
+ int enabled_count;
+
+ struct memmap_entry_s {
+ enum soc_dma_port_type type;
+ hwaddr addr;
+ union {
+ struct {
+ void *opaque;
+ soc_dma_io_t fn;
+ int out;
+ } fifo;
+ struct {
+ void *base;
+ size_t size;
+ } mem;
+ } u;
+ } *memmap;
+ int memmap_size;
+
+ struct soc_dma_ch_s ch[0];
+};
+
+static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, int delay_bytes)
+{
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ struct dma_s *dma = (struct dma_s *) ch->dma;
+
+ timer_mod(ch->timer, now + delay_bytes / dma->channel_freq);
+}
+
+static void soc_dma_ch_run(void *opaque)
+{
+ struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
+
+ ch->running = 1;
+ ch->dma->setup_fn(ch);
+ ch->transfer_fn(ch);
+ ch->running = 0;
+
+ if (ch->enable)
+ soc_dma_ch_schedule(ch, ch->bytes);
+ ch->bytes = 0;
+}
+
+static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
+ hwaddr addr)
+{
+ struct memmap_entry_s *lo;
+ int hi;
+
+ lo = dma->memmap;
+ hi = dma->memmap_size;
+
+ while (hi > 1) {
+ hi /= 2;
+ if (lo[hi].addr <= addr)
+ lo += hi;
+ }
+
+ return lo;
+}
+
+static inline enum soc_dma_port_type soc_dma_ch_update_type(
+ struct soc_dma_ch_s *ch, int port)
+{
+ struct dma_s *dma = (struct dma_s *) ch->dma;
+ struct memmap_entry_s *entry = soc_dma_lookup(dma, ch->vaddr[port]);
+
+ if (entry->type == soc_dma_port_fifo) {
+ while (entry < dma->memmap + dma->memmap_size &&
+ entry->u.fifo.out != port)
+ entry ++;
+ if (entry->addr != ch->vaddr[port] || entry->u.fifo.out != port)
+ return soc_dma_port_other;
+
+ if (ch->type[port] != soc_dma_access_const)
+ return soc_dma_port_other;
+
+ ch->io_fn[port] = entry->u.fifo.fn;
+ ch->io_opaque[port] = entry->u.fifo.opaque;
+ return soc_dma_port_fifo;
+ } else if (entry->type == soc_dma_port_mem) {
+ if (entry->addr > ch->vaddr[port] ||
+ entry->addr + entry->u.mem.size <= ch->vaddr[port])
+ return soc_dma_port_other;
+
+ /* TODO: support constant memory address for source port as used for
+ * drawing solid rectangles by PalmOS(R). */
+ if (ch->type[port] != soc_dma_access_const)
+ return soc_dma_port_other;
+
+ ch->paddr[port] = (uint8_t *) entry->u.mem.base +
+ (ch->vaddr[port] - entry->addr);
+ /* TODO: save bytes left to the end of the mapping somewhere so we
+ * can check we're not reading beyond it. */
+ return soc_dma_port_mem;
+ } else
+ return soc_dma_port_other;
+}
+
+void soc_dma_ch_update(struct soc_dma_ch_s *ch)
+{
+ enum soc_dma_port_type src, dst;
+
+ src = soc_dma_ch_update_type(ch, 0);
+ if (src == soc_dma_port_other) {
+ ch->update = 0;
+ ch->transfer_fn = ch->dma->transfer_fn;
+ return;
+ }
+ dst = soc_dma_ch_update_type(ch, 1);
+
+ /* TODO: use src and dst as array indices. */
+ if (src == soc_dma_port_mem && dst == soc_dma_port_mem)
+ ch->transfer_fn = transfer_mem2mem;
+ else if (src == soc_dma_port_mem && dst == soc_dma_port_fifo)
+ ch->transfer_fn = transfer_mem2fifo;
+ else if (src == soc_dma_port_fifo && dst == soc_dma_port_mem)
+ ch->transfer_fn = transfer_fifo2mem;
+ else if (src == soc_dma_port_fifo && dst == soc_dma_port_fifo)
+ ch->transfer_fn = transfer_fifo2fifo;
+ else
+ ch->transfer_fn = ch->dma->transfer_fn;
+
+ ch->update = (dst != soc_dma_port_other);
+}
+
+static void soc_dma_ch_freq_update(struct dma_s *s)
+{
+ if (s->enabled_count)
+ /* We completely ignore channel priorities and stuff */
+ s->channel_freq = s->soc.freq / s->enabled_count;
+ else {
+ /* TODO: Signal that we want to disable the functional clock and let
+ * the platform code decide what to do with it, i.e. check that
+ * auto-idle is enabled in the clock controller and if we are stopping
+ * the clock, do the same with any parent clocks that had only one
+ * user keeping them on and auto-idle enabled. */
+ }
+}
+
+void soc_dma_set_request(struct soc_dma_ch_s *ch, int level)
+{
+ struct dma_s *dma = (struct dma_s *) ch->dma;
+
+ dma->enabled_count += level - ch->enable;
+
+ if (level)
+ dma->ch_enable_mask |= 1 << ch->num;
+ else
+ dma->ch_enable_mask &= ~(1 << ch->num);
+
+ if (level != ch->enable) {
+ soc_dma_ch_freq_update(dma);
+ ch->enable = level;
+
+ if (!ch->enable)
+ timer_del(ch->timer);
+ else if (!ch->running)
+ soc_dma_ch_run(ch);
+ else
+ soc_dma_ch_schedule(ch, 1);
+ }
+}
+
+void soc_dma_reset(struct soc_dma_s *soc)
+{
+ struct dma_s *s = (struct dma_s *) soc;
+
+ s->soc.drqbmp = 0;
+ s->ch_enable_mask = 0;
+ s->enabled_count = 0;
+ soc_dma_ch_freq_update(s);
+}
+
+/* TODO: take a functional-clock argument */
+struct soc_dma_s *soc_dma_init(int n)
+{
+ int i;
+ struct dma_s *s = g_malloc0(sizeof(*s) + n * sizeof(*s->ch));
+
+ s->chnum = n;
+ s->soc.ch = s->ch;
+ for (i = 0; i < n; i ++) {
+ s->ch[i].dma = &s->soc;
+ s->ch[i].num = i;
+ s->ch[i].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, soc_dma_ch_run, &s->ch[i]);
+ }
+
+ soc_dma_reset(&s->soc);
+ fifo_size = 0;
+
+ return &s->soc;
+}
+
+void soc_dma_port_add_fifo(struct soc_dma_s *soc, hwaddr virt_base,
+ soc_dma_io_t fn, void *opaque, int out)
+{
+ struct memmap_entry_s *entry;
+ struct dma_s *dma = (struct dma_s *) soc;
+
+ dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
+ (dma->memmap_size + 1));
+ entry = soc_dma_lookup(dma, virt_base);
+
+ if (dma->memmap_size) {
+ if (entry->type == soc_dma_port_mem) {
+ if (entry->addr <= virt_base &&
+ entry->addr + entry->u.mem.size > virt_base) {
+ fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
+ " collides with RAM region at " TARGET_FMT_lx
+ "-" TARGET_FMT_lx "\n", __FUNCTION__,
+ (target_ulong) virt_base,
+ (target_ulong) entry->addr, (target_ulong)
+ (entry->addr + entry->u.mem.size));
+ exit(-1);
+ }
+
+ if (entry->addr <= virt_base)
+ entry ++;
+ } else
+ while (entry < dma->memmap + dma->memmap_size &&
+ entry->addr <= virt_base) {
+ if (entry->addr == virt_base && entry->u.fifo.out == out) {
+ fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
+ " collides FIFO at " TARGET_FMT_lx "\n",
+ __FUNCTION__, (target_ulong) virt_base,
+ (target_ulong) entry->addr);
+ exit(-1);
+ }
+
+ entry ++;
+ }
+
+ memmove(entry + 1, entry,
+ (uint8_t *) (dma->memmap + dma->memmap_size ++) -
+ (uint8_t *) entry);
+ } else
+ dma->memmap_size ++;
+
+ entry->addr = virt_base;
+ entry->type = soc_dma_port_fifo;
+ entry->u.fifo.fn = fn;
+ entry->u.fifo.opaque = opaque;
+ entry->u.fifo.out = out;
+}
+
+void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
+ hwaddr virt_base, size_t size)
+{
+ struct memmap_entry_s *entry;
+ struct dma_s *dma = (struct dma_s *) soc;
+
+ dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
+ (dma->memmap_size + 1));
+ entry = soc_dma_lookup(dma, virt_base);
+
+ if (dma->memmap_size) {
+ if (entry->type == soc_dma_port_mem) {
+ if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
+ (entry->addr <= virt_base &&
+ entry->addr + entry->u.mem.size > virt_base)) {
+ fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
+ " collides with RAM region at " TARGET_FMT_lx
+ "-" TARGET_FMT_lx "\n", __FUNCTION__,
+ (target_ulong) virt_base,
+ (target_ulong) (virt_base + size),
+ (target_ulong) entry->addr, (target_ulong)
+ (entry->addr + entry->u.mem.size));
+ exit(-1);
+ }
+
+ if (entry->addr <= virt_base)
+ entry ++;
+ } else {
+ if (entry->addr >= virt_base &&
+ entry->addr < virt_base + size) {
+ fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
+ " collides with FIFO at " TARGET_FMT_lx
+ "\n", __FUNCTION__,
+ (target_ulong) virt_base,
+ (target_ulong) (virt_base + size),
+ (target_ulong) entry->addr);
+ exit(-1);
+ }
+
+ while (entry < dma->memmap + dma->memmap_size &&
+ entry->addr <= virt_base)
+ entry ++;
+ }
+
+ memmove(entry + 1, entry,
+ (uint8_t *) (dma->memmap + dma->memmap_size ++) -
+ (uint8_t *) entry);
+ } else
+ dma->memmap_size ++;
+
+ entry->addr = virt_base;
+ entry->type = soc_dma_port_mem;
+ entry->u.mem.base = phys_base;
+ entry->u.mem.size = size;
+}
+
+/* TODO: port removal for ports like PCMCIA memory */
diff --git a/hw/dma/sparc32_dma.c b/hw/dma/sparc32_dma.c
new file mode 100644
index 00000000..e6a453ce
--- /dev/null
+++ b/hw/dma/sparc32_dma.c
@@ -0,0 +1,322 @@
+/*
+ * QEMU Sparc32 DMA controller emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * Modifications:
+ * 2010-Feb-14 Artyom Tarasenko : reworked irq generation
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/sparc/sparc32_dma.h"
+#include "hw/sparc/sun4m.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+
+/*
+ * This is the DMA controller part of chip STP2000 (Master I/O), also
+ * produced as NCR89C100. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
+ * and
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/DMA2.txt
+ */
+
+#define DMA_REGS 4
+#define DMA_SIZE (4 * sizeof(uint32_t))
+/* We need the mask, because one instance of the device is not page
+ aligned (ledma, start address 0x0010) */
+#define DMA_MASK (DMA_SIZE - 1)
+/* OBP says 0x20 bytes for ledma, the extras are aliased to espdma */
+#define DMA_ETH_SIZE (8 * sizeof(uint32_t))
+#define DMA_MAX_REG_OFFSET (2 * DMA_SIZE - 1)
+
+#define DMA_VER 0xa0000000
+#define DMA_INTR 1
+#define DMA_INTREN 0x10
+#define DMA_WRITE_MEM 0x100
+#define DMA_EN 0x200
+#define DMA_LOADED 0x04000000
+#define DMA_DRAIN_FIFO 0x40
+#define DMA_RESET 0x80
+
+/* XXX SCSI and ethernet should have different read-only bit masks */
+#define DMA_CSR_RO_MASK 0xfe000007
+
+#define TYPE_SPARC32_DMA "sparc32_dma"
+#define SPARC32_DMA(obj) OBJECT_CHECK(DMAState, (obj), TYPE_SPARC32_DMA)
+
+typedef struct DMAState DMAState;
+
+struct DMAState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t dmaregs[DMA_REGS];
+ qemu_irq irq;
+ void *iommu;
+ qemu_irq gpio[2];
+ uint32_t is_ledma;
+};
+
+enum {
+ GPIO_RESET = 0,
+ GPIO_DMA,
+};
+
+/* Note: on sparc, the lance 16 bit bus is swapped */
+void ledma_memory_read(void *opaque, hwaddr addr,
+ uint8_t *buf, int len, int do_bswap)
+{
+ DMAState *s = opaque;
+ int i;
+
+ addr |= s->dmaregs[3];
+ trace_ledma_memory_read(addr);
+ if (do_bswap) {
+ sparc_iommu_memory_read(s->iommu, addr, buf, len);
+ } else {
+ addr &= ~1;
+ len &= ~1;
+ sparc_iommu_memory_read(s->iommu, addr, buf, len);
+ for(i = 0; i < len; i += 2) {
+ bswap16s((uint16_t *)(buf + i));
+ }
+ }
+}
+
+void ledma_memory_write(void *opaque, hwaddr addr,
+ uint8_t *buf, int len, int do_bswap)
+{
+ DMAState *s = opaque;
+ int l, i;
+ uint16_t tmp_buf[32];
+
+ addr |= s->dmaregs[3];
+ trace_ledma_memory_write(addr);
+ if (do_bswap) {
+ sparc_iommu_memory_write(s->iommu, addr, buf, len);
+ } else {
+ addr &= ~1;
+ len &= ~1;
+ while (len > 0) {
+ l = len;
+ if (l > sizeof(tmp_buf))
+ l = sizeof(tmp_buf);
+ for(i = 0; i < l; i += 2) {
+ tmp_buf[i >> 1] = bswap16(*(uint16_t *)(buf + i));
+ }
+ sparc_iommu_memory_write(s->iommu, addr, (uint8_t *)tmp_buf, l);
+ len -= l;
+ buf += l;
+ addr += l;
+ }
+ }
+}
+
+static void dma_set_irq(void *opaque, int irq, int level)
+{
+ DMAState *s = opaque;
+ if (level) {
+ s->dmaregs[0] |= DMA_INTR;
+ if (s->dmaregs[0] & DMA_INTREN) {
+ trace_sparc32_dma_set_irq_raise();
+ qemu_irq_raise(s->irq);
+ }
+ } else {
+ if (s->dmaregs[0] & DMA_INTR) {
+ s->dmaregs[0] &= ~DMA_INTR;
+ if (s->dmaregs[0] & DMA_INTREN) {
+ trace_sparc32_dma_set_irq_lower();
+ qemu_irq_lower(s->irq);
+ }
+ }
+ }
+}
+
+void espdma_memory_read(void *opaque, uint8_t *buf, int len)
+{
+ DMAState *s = opaque;
+
+ trace_espdma_memory_read(s->dmaregs[1]);
+ sparc_iommu_memory_read(s->iommu, s->dmaregs[1], buf, len);
+ s->dmaregs[1] += len;
+}
+
+void espdma_memory_write(void *opaque, uint8_t *buf, int len)
+{
+ DMAState *s = opaque;
+
+ trace_espdma_memory_write(s->dmaregs[1]);
+ sparc_iommu_memory_write(s->iommu, s->dmaregs[1], buf, len);
+ s->dmaregs[1] += len;
+}
+
+static uint64_t dma_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ DMAState *s = opaque;
+ uint32_t saddr;
+
+ if (s->is_ledma && (addr > DMA_MAX_REG_OFFSET)) {
+ /* aliased to espdma, but we can't get there from here */
+ /* buggy driver if using undocumented behavior, just return 0 */
+ trace_sparc32_dma_mem_readl(addr, 0);
+ return 0;
+ }
+ saddr = (addr & DMA_MASK) >> 2;
+ trace_sparc32_dma_mem_readl(addr, s->dmaregs[saddr]);
+ return s->dmaregs[saddr];
+}
+
+static void dma_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ DMAState *s = opaque;
+ uint32_t saddr;
+
+ if (s->is_ledma && (addr > DMA_MAX_REG_OFFSET)) {
+ /* aliased to espdma, but we can't get there from here */
+ trace_sparc32_dma_mem_writel(addr, 0, val);
+ return;
+ }
+ saddr = (addr & DMA_MASK) >> 2;
+ trace_sparc32_dma_mem_writel(addr, s->dmaregs[saddr], val);
+ switch (saddr) {
+ case 0:
+ if (val & DMA_INTREN) {
+ if (s->dmaregs[0] & DMA_INTR) {
+ trace_sparc32_dma_set_irq_raise();
+ qemu_irq_raise(s->irq);
+ }
+ } else {
+ if (s->dmaregs[0] & (DMA_INTR | DMA_INTREN)) {
+ trace_sparc32_dma_set_irq_lower();
+ qemu_irq_lower(s->irq);
+ }
+ }
+ if (val & DMA_RESET) {
+ qemu_irq_raise(s->gpio[GPIO_RESET]);
+ qemu_irq_lower(s->gpio[GPIO_RESET]);
+ } else if (val & DMA_DRAIN_FIFO) {
+ val &= ~DMA_DRAIN_FIFO;
+ } else if (val == 0)
+ val = DMA_DRAIN_FIFO;
+
+ if (val & DMA_EN && !(s->dmaregs[0] & DMA_EN)) {
+ trace_sparc32_dma_enable_raise();
+ qemu_irq_raise(s->gpio[GPIO_DMA]);
+ } else if (!(val & DMA_EN) && !!(s->dmaregs[0] & DMA_EN)) {
+ trace_sparc32_dma_enable_lower();
+ qemu_irq_lower(s->gpio[GPIO_DMA]);
+ }
+
+ val &= ~DMA_CSR_RO_MASK;
+ val |= DMA_VER;
+ s->dmaregs[0] = (s->dmaregs[0] & DMA_CSR_RO_MASK) | val;
+ break;
+ case 1:
+ s->dmaregs[0] |= DMA_LOADED;
+ /* fall through */
+ default:
+ s->dmaregs[saddr] = val;
+ break;
+ }
+}
+
+static const MemoryRegionOps dma_mem_ops = {
+ .read = dma_mem_read,
+ .write = dma_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void dma_reset(DeviceState *d)
+{
+ DMAState *s = SPARC32_DMA(d);
+
+ memset(s->dmaregs, 0, DMA_SIZE);
+ s->dmaregs[0] = DMA_VER;
+}
+
+static const VMStateDescription vmstate_dma = {
+ .name ="sparc32_dma",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(dmaregs, DMAState, DMA_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int sparc32_dma_init1(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ DMAState *s = SPARC32_DMA(dev);
+ int reg_size;
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ reg_size = s->is_ledma ? DMA_ETH_SIZE : DMA_SIZE;
+ memory_region_init_io(&s->iomem, OBJECT(s), &dma_mem_ops, s,
+ "dma", reg_size);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ qdev_init_gpio_in(dev, dma_set_irq, 1);
+ qdev_init_gpio_out(dev, s->gpio, 2);
+
+ return 0;
+}
+
+static Property sparc32_dma_properties[] = {
+ DEFINE_PROP_PTR("iommu_opaque", DMAState, iommu),
+ DEFINE_PROP_UINT32("is_ledma", DMAState, is_ledma, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sparc32_dma_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = sparc32_dma_init1;
+ dc->reset = dma_reset;
+ dc->vmsd = &vmstate_dma;
+ dc->props = sparc32_dma_properties;
+ /* Reason: pointer property "iommu_opaque" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo sparc32_dma_info = {
+ .name = TYPE_SPARC32_DMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(DMAState),
+ .class_init = sparc32_dma_class_init,
+};
+
+static void sparc32_dma_register_types(void)
+{
+ type_register_static(&sparc32_dma_info);
+}
+
+type_init(sparc32_dma_register_types)
diff --git a/hw/dma/sun4m_iommu.c b/hw/dma/sun4m_iommu.c
new file mode 100644
index 00000000..9a488bc9
--- /dev/null
+++ b/hw/dma/sun4m_iommu.c
@@ -0,0 +1,392 @@
+/*
+ * QEMU Sun4m iommu emulation
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "hw/sparc/sun4m.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "trace.h"
+
+/*
+ * I/O MMU used by Sun4m systems
+ *
+ * Chipset docs:
+ * "Sun-4M System Architecture (revision 2.0) by Chuck Narad", 950-1373-01,
+ * http://mediacast.sun.com/users/Barton808/media/Sun4M_SystemArchitecture_edited2.pdf
+ */
+
+#define IOMMU_NREGS (4*4096/4)
+#define IOMMU_CTRL (0x0000 >> 2)
+#define IOMMU_CTRL_IMPL 0xf0000000 /* Implementation */
+#define IOMMU_CTRL_VERS 0x0f000000 /* Version */
+#define IOMMU_CTRL_RNGE 0x0000001c /* Mapping RANGE */
+#define IOMMU_RNGE_16MB 0x00000000 /* 0xff000000 -> 0xffffffff */
+#define IOMMU_RNGE_32MB 0x00000004 /* 0xfe000000 -> 0xffffffff */
+#define IOMMU_RNGE_64MB 0x00000008 /* 0xfc000000 -> 0xffffffff */
+#define IOMMU_RNGE_128MB 0x0000000c /* 0xf8000000 -> 0xffffffff */
+#define IOMMU_RNGE_256MB 0x00000010 /* 0xf0000000 -> 0xffffffff */
+#define IOMMU_RNGE_512MB 0x00000014 /* 0xe0000000 -> 0xffffffff */
+#define IOMMU_RNGE_1GB 0x00000018 /* 0xc0000000 -> 0xffffffff */
+#define IOMMU_RNGE_2GB 0x0000001c /* 0x80000000 -> 0xffffffff */
+#define IOMMU_CTRL_ENAB 0x00000001 /* IOMMU Enable */
+#define IOMMU_CTRL_MASK 0x0000001d
+
+#define IOMMU_BASE (0x0004 >> 2)
+#define IOMMU_BASE_MASK 0x07fffc00
+
+#define IOMMU_TLBFLUSH (0x0014 >> 2)
+#define IOMMU_TLBFLUSH_MASK 0xffffffff
+
+#define IOMMU_PGFLUSH (0x0018 >> 2)
+#define IOMMU_PGFLUSH_MASK 0xffffffff
+
+#define IOMMU_AFSR (0x1000 >> 2)
+#define IOMMU_AFSR_ERR 0x80000000 /* LE, TO, or BE asserted */
+#define IOMMU_AFSR_LE 0x40000000 /* SBUS reports error after
+ transaction */
+#define IOMMU_AFSR_TO 0x20000000 /* Write access took more than
+ 12.8 us. */
+#define IOMMU_AFSR_BE 0x10000000 /* Write access received error
+ acknowledge */
+#define IOMMU_AFSR_SIZE 0x0e000000 /* Size of transaction causing error */
+#define IOMMU_AFSR_S 0x01000000 /* Sparc was in supervisor mode */
+#define IOMMU_AFSR_RESV 0x00800000 /* Reserved, forced to 0x8 by
+ hardware */
+#define IOMMU_AFSR_ME 0x00080000 /* Multiple errors occurred */
+#define IOMMU_AFSR_RD 0x00040000 /* A read operation was in progress */
+#define IOMMU_AFSR_FAV 0x00020000 /* IOMMU afar has valid contents */
+#define IOMMU_AFSR_MASK 0xff0fffff
+
+#define IOMMU_AFAR (0x1004 >> 2)
+
+#define IOMMU_AER (0x1008 >> 2) /* Arbiter Enable Register */
+#define IOMMU_AER_EN_P0_ARB 0x00000001 /* MBus master 0x8 (Always 1) */
+#define IOMMU_AER_EN_P1_ARB 0x00000002 /* MBus master 0x9 */
+#define IOMMU_AER_EN_P2_ARB 0x00000004 /* MBus master 0xa */
+#define IOMMU_AER_EN_P3_ARB 0x00000008 /* MBus master 0xb */
+#define IOMMU_AER_EN_0 0x00010000 /* SBus slot 0 */
+#define IOMMU_AER_EN_1 0x00020000 /* SBus slot 1 */
+#define IOMMU_AER_EN_2 0x00040000 /* SBus slot 2 */
+#define IOMMU_AER_EN_3 0x00080000 /* SBus slot 3 */
+#define IOMMU_AER_EN_F 0x00100000 /* SBus on-board */
+#define IOMMU_AER_SBW 0x80000000 /* S-to-M asynchronous writes */
+#define IOMMU_AER_MASK 0x801f000f
+
+#define IOMMU_SBCFG0 (0x1010 >> 2) /* SBUS configration per-slot */
+#define IOMMU_SBCFG1 (0x1014 >> 2) /* SBUS configration per-slot */
+#define IOMMU_SBCFG2 (0x1018 >> 2) /* SBUS configration per-slot */
+#define IOMMU_SBCFG3 (0x101c >> 2) /* SBUS configration per-slot */
+#define IOMMU_SBCFG_SAB30 0x00010000 /* Phys-address bit 30 when
+ bypass enabled */
+#define IOMMU_SBCFG_BA16 0x00000004 /* Slave supports 16 byte bursts */
+#define IOMMU_SBCFG_BA8 0x00000002 /* Slave supports 8 byte bursts */
+#define IOMMU_SBCFG_BYPASS 0x00000001 /* Bypass IOMMU, treat all addresses
+ produced by this device as pure
+ physical. */
+#define IOMMU_SBCFG_MASK 0x00010003
+
+#define IOMMU_ARBEN (0x2000 >> 2) /* SBUS arbitration enable */
+#define IOMMU_ARBEN_MASK 0x001f0000
+#define IOMMU_MID 0x00000008
+
+#define IOMMU_MASK_ID (0x3018 >> 2) /* Mask ID */
+#define IOMMU_MASK_ID_MASK 0x00ffffff
+
+#define IOMMU_MSII_MASK 0x26000000 /* microSPARC II mask number */
+#define IOMMU_TS_MASK 0x23000000 /* turboSPARC mask number */
+
+/* The format of an iopte in the page tables */
+#define IOPTE_PAGE 0xffffff00 /* Physical page number (PA[35:12]) */
+#define IOPTE_CACHE 0x00000080 /* Cached (in vme IOCACHE or
+ Viking/MXCC) */
+#define IOPTE_WRITE 0x00000004 /* Writable */
+#define IOPTE_VALID 0x00000002 /* IOPTE is valid */
+#define IOPTE_WAZ 0x00000001 /* Write as zeros */
+
+#define IOMMU_PAGE_SHIFT 12
+#define IOMMU_PAGE_SIZE (1 << IOMMU_PAGE_SHIFT)
+#define IOMMU_PAGE_MASK ~(IOMMU_PAGE_SIZE - 1)
+
+#define TYPE_SUN4M_IOMMU "iommu"
+#define SUN4M_IOMMU(obj) OBJECT_CHECK(IOMMUState, (obj), TYPE_SUN4M_IOMMU)
+
+typedef struct IOMMUState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t regs[IOMMU_NREGS];
+ hwaddr iostart;
+ qemu_irq irq;
+ uint32_t version;
+} IOMMUState;
+
+static uint64_t iommu_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ IOMMUState *s = opaque;
+ hwaddr saddr;
+ uint32_t ret;
+
+ saddr = addr >> 2;
+ switch (saddr) {
+ default:
+ ret = s->regs[saddr];
+ break;
+ case IOMMU_AFAR:
+ case IOMMU_AFSR:
+ ret = s->regs[saddr];
+ qemu_irq_lower(s->irq);
+ break;
+ }
+ trace_sun4m_iommu_mem_readl(saddr, ret);
+ return ret;
+}
+
+static void iommu_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ IOMMUState *s = opaque;
+ hwaddr saddr;
+
+ saddr = addr >> 2;
+ trace_sun4m_iommu_mem_writel(saddr, val);
+ switch (saddr) {
+ case IOMMU_CTRL:
+ switch (val & IOMMU_CTRL_RNGE) {
+ case IOMMU_RNGE_16MB:
+ s->iostart = 0xffffffffff000000ULL;
+ break;
+ case IOMMU_RNGE_32MB:
+ s->iostart = 0xfffffffffe000000ULL;
+ break;
+ case IOMMU_RNGE_64MB:
+ s->iostart = 0xfffffffffc000000ULL;
+ break;
+ case IOMMU_RNGE_128MB:
+ s->iostart = 0xfffffffff8000000ULL;
+ break;
+ case IOMMU_RNGE_256MB:
+ s->iostart = 0xfffffffff0000000ULL;
+ break;
+ case IOMMU_RNGE_512MB:
+ s->iostart = 0xffffffffe0000000ULL;
+ break;
+ case IOMMU_RNGE_1GB:
+ s->iostart = 0xffffffffc0000000ULL;
+ break;
+ default:
+ case IOMMU_RNGE_2GB:
+ s->iostart = 0xffffffff80000000ULL;
+ break;
+ }
+ trace_sun4m_iommu_mem_writel_ctrl(s->iostart);
+ s->regs[saddr] = ((val & IOMMU_CTRL_MASK) | s->version);
+ break;
+ case IOMMU_BASE:
+ s->regs[saddr] = val & IOMMU_BASE_MASK;
+ break;
+ case IOMMU_TLBFLUSH:
+ trace_sun4m_iommu_mem_writel_tlbflush(val);
+ s->regs[saddr] = val & IOMMU_TLBFLUSH_MASK;
+ break;
+ case IOMMU_PGFLUSH:
+ trace_sun4m_iommu_mem_writel_pgflush(val);
+ s->regs[saddr] = val & IOMMU_PGFLUSH_MASK;
+ break;
+ case IOMMU_AFAR:
+ s->regs[saddr] = val;
+ qemu_irq_lower(s->irq);
+ break;
+ case IOMMU_AER:
+ s->regs[saddr] = (val & IOMMU_AER_MASK) | IOMMU_AER_EN_P0_ARB;
+ break;
+ case IOMMU_AFSR:
+ s->regs[saddr] = (val & IOMMU_AFSR_MASK) | IOMMU_AFSR_RESV;
+ qemu_irq_lower(s->irq);
+ break;
+ case IOMMU_SBCFG0:
+ case IOMMU_SBCFG1:
+ case IOMMU_SBCFG2:
+ case IOMMU_SBCFG3:
+ s->regs[saddr] = val & IOMMU_SBCFG_MASK;
+ break;
+ case IOMMU_ARBEN:
+ // XXX implement SBus probing: fault when reading unmapped
+ // addresses, fault cause and address stored to MMU/IOMMU
+ s->regs[saddr] = (val & IOMMU_ARBEN_MASK) | IOMMU_MID;
+ break;
+ case IOMMU_MASK_ID:
+ s->regs[saddr] |= val & IOMMU_MASK_ID_MASK;
+ break;
+ default:
+ s->regs[saddr] = val;
+ break;
+ }
+}
+
+static const MemoryRegionOps iommu_mem_ops = {
+ .read = iommu_mem_read,
+ .write = iommu_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint32_t iommu_page_get_flags(IOMMUState *s, hwaddr addr)
+{
+ uint32_t ret;
+ hwaddr iopte;
+ hwaddr pa = addr;
+
+ iopte = s->regs[IOMMU_BASE] << 4;
+ addr &= ~s->iostart;
+ iopte += (addr >> (IOMMU_PAGE_SHIFT - 2)) & ~3;
+ ret = address_space_ldl_be(&address_space_memory, iopte,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ trace_sun4m_iommu_page_get_flags(pa, iopte, ret);
+ return ret;
+}
+
+static hwaddr iommu_translate_pa(hwaddr addr,
+ uint32_t pte)
+{
+ hwaddr pa;
+
+ pa = ((pte & IOPTE_PAGE) << 4) + (addr & ~IOMMU_PAGE_MASK);
+ trace_sun4m_iommu_translate_pa(addr, pa, pte);
+ return pa;
+}
+
+static void iommu_bad_addr(IOMMUState *s, hwaddr addr,
+ int is_write)
+{
+ trace_sun4m_iommu_bad_addr(addr);
+ s->regs[IOMMU_AFSR] = IOMMU_AFSR_ERR | IOMMU_AFSR_LE | IOMMU_AFSR_RESV |
+ IOMMU_AFSR_FAV;
+ if (!is_write)
+ s->regs[IOMMU_AFSR] |= IOMMU_AFSR_RD;
+ s->regs[IOMMU_AFAR] = addr;
+ qemu_irq_raise(s->irq);
+}
+
+void sparc_iommu_memory_rw(void *opaque, hwaddr addr,
+ uint8_t *buf, int len, int is_write)
+{
+ int l;
+ uint32_t flags;
+ hwaddr page, phys_addr;
+
+ while (len > 0) {
+ page = addr & IOMMU_PAGE_MASK;
+ l = (page + IOMMU_PAGE_SIZE) - addr;
+ if (l > len)
+ l = len;
+ flags = iommu_page_get_flags(opaque, page);
+ if (!(flags & IOPTE_VALID)) {
+ iommu_bad_addr(opaque, page, is_write);
+ return;
+ }
+ phys_addr = iommu_translate_pa(addr, flags);
+ if (is_write) {
+ if (!(flags & IOPTE_WRITE)) {
+ iommu_bad_addr(opaque, page, is_write);
+ return;
+ }
+ cpu_physical_memory_write(phys_addr, buf, l);
+ } else {
+ cpu_physical_memory_read(phys_addr, buf, l);
+ }
+ len -= l;
+ buf += l;
+ addr += l;
+ }
+}
+
+static const VMStateDescription vmstate_iommu = {
+ .name ="iommu",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, IOMMUState, IOMMU_NREGS),
+ VMSTATE_UINT64(iostart, IOMMUState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void iommu_reset(DeviceState *d)
+{
+ IOMMUState *s = SUN4M_IOMMU(d);
+
+ memset(s->regs, 0, IOMMU_NREGS * 4);
+ s->iostart = 0;
+ s->regs[IOMMU_CTRL] = s->version;
+ s->regs[IOMMU_ARBEN] = IOMMU_MID;
+ s->regs[IOMMU_AFSR] = IOMMU_AFSR_RESV;
+ s->regs[IOMMU_AER] = IOMMU_AER_EN_P0_ARB | IOMMU_AER_EN_P1_ARB;
+ s->regs[IOMMU_MASK_ID] = IOMMU_TS_MASK;
+}
+
+static int iommu_init1(SysBusDevice *dev)
+{
+ IOMMUState *s = SUN4M_IOMMU(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &iommu_mem_ops, s, "iommu",
+ IOMMU_NREGS * sizeof(uint32_t));
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static Property iommu_properties[] = {
+ DEFINE_PROP_UINT32("version", IOMMUState, version, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void iommu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = iommu_init1;
+ dc->reset = iommu_reset;
+ dc->vmsd = &vmstate_iommu;
+ dc->props = iommu_properties;
+}
+
+static const TypeInfo iommu_info = {
+ .name = TYPE_SUN4M_IOMMU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IOMMUState),
+ .class_init = iommu_class_init,
+};
+
+static void iommu_register_types(void)
+{
+ type_register_static(&iommu_info);
+}
+
+type_init(iommu_register_types)
diff --git a/hw/dma/xilinx_axidma.c b/hw/dma/xilinx_axidma.c
new file mode 100644
index 00000000..cf842a3c
--- /dev/null
+++ b/hw/dma/xilinx_axidma.c
@@ -0,0 +1,674 @@
+/*
+ * QEMU model of Xilinx AXI-DMA block.
+ *
+ * Copyright (c) 2011 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+
+#include "hw/stream.h"
+
+#define D(x)
+
+#define TYPE_XILINX_AXI_DMA "xlnx.axi-dma"
+#define TYPE_XILINX_AXI_DMA_DATA_STREAM "xilinx-axi-dma-data-stream"
+#define TYPE_XILINX_AXI_DMA_CONTROL_STREAM "xilinx-axi-dma-control-stream"
+
+#define XILINX_AXI_DMA(obj) \
+ OBJECT_CHECK(XilinxAXIDMA, (obj), TYPE_XILINX_AXI_DMA)
+
+#define XILINX_AXI_DMA_DATA_STREAM(obj) \
+ OBJECT_CHECK(XilinxAXIDMAStreamSlave, (obj),\
+ TYPE_XILINX_AXI_DMA_DATA_STREAM)
+
+#define XILINX_AXI_DMA_CONTROL_STREAM(obj) \
+ OBJECT_CHECK(XilinxAXIDMAStreamSlave, (obj),\
+ TYPE_XILINX_AXI_DMA_CONTROL_STREAM)
+
+#define R_DMACR (0x00 / 4)
+#define R_DMASR (0x04 / 4)
+#define R_CURDESC (0x08 / 4)
+#define R_TAILDESC (0x10 / 4)
+#define R_MAX (0x30 / 4)
+
+#define CONTROL_PAYLOAD_WORDS 5
+#define CONTROL_PAYLOAD_SIZE (CONTROL_PAYLOAD_WORDS * (sizeof(uint32_t)))
+
+typedef struct XilinxAXIDMA XilinxAXIDMA;
+typedef struct XilinxAXIDMAStreamSlave XilinxAXIDMAStreamSlave;
+
+enum {
+ DMACR_RUNSTOP = 1,
+ DMACR_TAILPTR_MODE = 2,
+ DMACR_RESET = 4
+};
+
+enum {
+ DMASR_HALTED = 1,
+ DMASR_IDLE = 2,
+ DMASR_IOC_IRQ = 1 << 12,
+ DMASR_DLY_IRQ = 1 << 13,
+
+ DMASR_IRQ_MASK = 7 << 12
+};
+
+struct SDesc {
+ uint64_t nxtdesc;
+ uint64_t buffer_address;
+ uint64_t reserved;
+ uint32_t control;
+ uint32_t status;
+ uint8_t app[CONTROL_PAYLOAD_SIZE];
+};
+
+enum {
+ SDESC_CTRL_EOF = (1 << 26),
+ SDESC_CTRL_SOF = (1 << 27),
+
+ SDESC_CTRL_LEN_MASK = (1 << 23) - 1
+};
+
+enum {
+ SDESC_STATUS_EOF = (1 << 26),
+ SDESC_STATUS_SOF_BIT = 27,
+ SDESC_STATUS_SOF = (1 << SDESC_STATUS_SOF_BIT),
+ SDESC_STATUS_COMPLETE = (1 << 31)
+};
+
+struct Stream {
+ QEMUBH *bh;
+ ptimer_state *ptimer;
+ qemu_irq irq;
+
+ int nr;
+
+ struct SDesc desc;
+ int pos;
+ unsigned int complete_cnt;
+ uint32_t regs[R_MAX];
+ uint8_t app[20];
+};
+
+struct XilinxAXIDMAStreamSlave {
+ Object parent;
+
+ struct XilinxAXIDMA *dma;
+};
+
+struct XilinxAXIDMA {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ uint32_t freqhz;
+ StreamSlave *tx_data_dev;
+ StreamSlave *tx_control_dev;
+ XilinxAXIDMAStreamSlave rx_data_dev;
+ XilinxAXIDMAStreamSlave rx_control_dev;
+
+ struct Stream streams[2];
+
+ StreamCanPushNotifyFn notify;
+ void *notify_opaque;
+};
+
+/*
+ * Helper calls to extract info from desriptors and other trivial
+ * state from regs.
+ */
+static inline int stream_desc_sof(struct SDesc *d)
+{
+ return d->control & SDESC_CTRL_SOF;
+}
+
+static inline int stream_desc_eof(struct SDesc *d)
+{
+ return d->control & SDESC_CTRL_EOF;
+}
+
+static inline int stream_resetting(struct Stream *s)
+{
+ return !!(s->regs[R_DMACR] & DMACR_RESET);
+}
+
+static inline int stream_running(struct Stream *s)
+{
+ return s->regs[R_DMACR] & DMACR_RUNSTOP;
+}
+
+static inline int stream_idle(struct Stream *s)
+{
+ return !!(s->regs[R_DMASR] & DMASR_IDLE);
+}
+
+static void stream_reset(struct Stream *s)
+{
+ s->regs[R_DMASR] = DMASR_HALTED; /* starts up halted. */
+ s->regs[R_DMACR] = 1 << 16; /* Starts with one in compl threshold. */
+}
+
+/* Map an offset addr into a channel index. */
+static inline int streamid_from_addr(hwaddr addr)
+{
+ int sid;
+
+ sid = addr / (0x30);
+ sid &= 1;
+ return sid;
+}
+
+#ifdef DEBUG_ENET
+static void stream_desc_show(struct SDesc *d)
+{
+ qemu_log("buffer_addr = " PRIx64 "\n", d->buffer_address);
+ qemu_log("nxtdesc = " PRIx64 "\n", d->nxtdesc);
+ qemu_log("control = %x\n", d->control);
+ qemu_log("status = %x\n", d->status);
+}
+#endif
+
+static void stream_desc_load(struct Stream *s, hwaddr addr)
+{
+ struct SDesc *d = &s->desc;
+
+ cpu_physical_memory_read(addr, d, sizeof *d);
+
+ /* Convert from LE into host endianness. */
+ d->buffer_address = le64_to_cpu(d->buffer_address);
+ d->nxtdesc = le64_to_cpu(d->nxtdesc);
+ d->control = le32_to_cpu(d->control);
+ d->status = le32_to_cpu(d->status);
+}
+
+static void stream_desc_store(struct Stream *s, hwaddr addr)
+{
+ struct SDesc *d = &s->desc;
+
+ /* Convert from host endianness into LE. */
+ d->buffer_address = cpu_to_le64(d->buffer_address);
+ d->nxtdesc = cpu_to_le64(d->nxtdesc);
+ d->control = cpu_to_le32(d->control);
+ d->status = cpu_to_le32(d->status);
+ cpu_physical_memory_write(addr, d, sizeof *d);
+}
+
+static void stream_update_irq(struct Stream *s)
+{
+ unsigned int pending, mask, irq;
+
+ pending = s->regs[R_DMASR] & DMASR_IRQ_MASK;
+ mask = s->regs[R_DMACR] & DMASR_IRQ_MASK;
+
+ irq = pending & mask;
+
+ qemu_set_irq(s->irq, !!irq);
+}
+
+static void stream_reload_complete_cnt(struct Stream *s)
+{
+ unsigned int comp_th;
+ comp_th = (s->regs[R_DMACR] >> 16) & 0xff;
+ s->complete_cnt = comp_th;
+}
+
+static void timer_hit(void *opaque)
+{
+ struct Stream *s = opaque;
+
+ stream_reload_complete_cnt(s);
+ s->regs[R_DMASR] |= DMASR_DLY_IRQ;
+ stream_update_irq(s);
+}
+
+static void stream_complete(struct Stream *s)
+{
+ unsigned int comp_delay;
+
+ /* Start the delayed timer. */
+ comp_delay = s->regs[R_DMACR] >> 24;
+ if (comp_delay) {
+ ptimer_stop(s->ptimer);
+ ptimer_set_count(s->ptimer, comp_delay);
+ ptimer_run(s->ptimer, 1);
+ }
+
+ s->complete_cnt--;
+ if (s->complete_cnt == 0) {
+ /* Raise the IOC irq. */
+ s->regs[R_DMASR] |= DMASR_IOC_IRQ;
+ stream_reload_complete_cnt(s);
+ }
+}
+
+static void stream_process_mem2s(struct Stream *s, StreamSlave *tx_data_dev,
+ StreamSlave *tx_control_dev)
+{
+ uint32_t prev_d;
+ unsigned char txbuf[16 * 1024];
+ unsigned int txlen;
+
+ if (!stream_running(s) || stream_idle(s)) {
+ return;
+ }
+
+ while (1) {
+ stream_desc_load(s, s->regs[R_CURDESC]);
+
+ if (s->desc.status & SDESC_STATUS_COMPLETE) {
+ s->regs[R_DMASR] |= DMASR_HALTED;
+ break;
+ }
+
+ if (stream_desc_sof(&s->desc)) {
+ s->pos = 0;
+ stream_push(tx_control_dev, s->desc.app, sizeof(s->desc.app));
+ }
+
+ txlen = s->desc.control & SDESC_CTRL_LEN_MASK;
+ if ((txlen + s->pos) > sizeof txbuf) {
+ hw_error("%s: too small internal txbuf! %d\n", __func__,
+ txlen + s->pos);
+ }
+
+ cpu_physical_memory_read(s->desc.buffer_address,
+ txbuf + s->pos, txlen);
+ s->pos += txlen;
+
+ if (stream_desc_eof(&s->desc)) {
+ stream_push(tx_data_dev, txbuf, s->pos);
+ s->pos = 0;
+ stream_complete(s);
+ }
+
+ /* Update the descriptor. */
+ s->desc.status = txlen | SDESC_STATUS_COMPLETE;
+ stream_desc_store(s, s->regs[R_CURDESC]);
+
+ /* Advance. */
+ prev_d = s->regs[R_CURDESC];
+ s->regs[R_CURDESC] = s->desc.nxtdesc;
+ if (prev_d == s->regs[R_TAILDESC]) {
+ s->regs[R_DMASR] |= DMASR_IDLE;
+ break;
+ }
+ }
+}
+
+static size_t stream_process_s2mem(struct Stream *s, unsigned char *buf,
+ size_t len)
+{
+ uint32_t prev_d;
+ unsigned int rxlen;
+ size_t pos = 0;
+ int sof = 1;
+
+ if (!stream_running(s) || stream_idle(s)) {
+ return 0;
+ }
+
+ while (len) {
+ stream_desc_load(s, s->regs[R_CURDESC]);
+
+ if (s->desc.status & SDESC_STATUS_COMPLETE) {
+ s->regs[R_DMASR] |= DMASR_HALTED;
+ break;
+ }
+
+ rxlen = s->desc.control & SDESC_CTRL_LEN_MASK;
+ if (rxlen > len) {
+ /* It fits. */
+ rxlen = len;
+ }
+
+ cpu_physical_memory_write(s->desc.buffer_address, buf + pos, rxlen);
+ len -= rxlen;
+ pos += rxlen;
+
+ /* Update the descriptor. */
+ if (!len) {
+ stream_complete(s);
+ memcpy(s->desc.app, s->app, sizeof(s->desc.app));
+ s->desc.status |= SDESC_STATUS_EOF;
+ }
+
+ s->desc.status |= sof << SDESC_STATUS_SOF_BIT;
+ s->desc.status |= SDESC_STATUS_COMPLETE;
+ stream_desc_store(s, s->regs[R_CURDESC]);
+ sof = 0;
+
+ /* Advance. */
+ prev_d = s->regs[R_CURDESC];
+ s->regs[R_CURDESC] = s->desc.nxtdesc;
+ if (prev_d == s->regs[R_TAILDESC]) {
+ s->regs[R_DMASR] |= DMASR_IDLE;
+ break;
+ }
+ }
+
+ return pos;
+}
+
+static void xilinx_axidma_reset(DeviceState *dev)
+{
+ int i;
+ XilinxAXIDMA *s = XILINX_AXI_DMA(dev);
+
+ for (i = 0; i < 2; i++) {
+ stream_reset(&s->streams[i]);
+ }
+}
+
+static size_t
+xilinx_axidma_control_stream_push(StreamSlave *obj, unsigned char *buf,
+ size_t len)
+{
+ XilinxAXIDMAStreamSlave *cs = XILINX_AXI_DMA_CONTROL_STREAM(obj);
+ struct Stream *s = &cs->dma->streams[1];
+
+ if (len != CONTROL_PAYLOAD_SIZE) {
+ hw_error("AXI DMA requires %d byte control stream payload\n",
+ (int)CONTROL_PAYLOAD_SIZE);
+ }
+
+ memcpy(s->app, buf, len);
+ return len;
+}
+
+static bool
+xilinx_axidma_data_stream_can_push(StreamSlave *obj,
+ StreamCanPushNotifyFn notify,
+ void *notify_opaque)
+{
+ XilinxAXIDMAStreamSlave *ds = XILINX_AXI_DMA_DATA_STREAM(obj);
+ struct Stream *s = &ds->dma->streams[1];
+
+ if (!stream_running(s) || stream_idle(s)) {
+ ds->dma->notify = notify;
+ ds->dma->notify_opaque = notify_opaque;
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+xilinx_axidma_data_stream_push(StreamSlave *obj, unsigned char *buf, size_t len)
+{
+ XilinxAXIDMAStreamSlave *ds = XILINX_AXI_DMA_DATA_STREAM(obj);
+ struct Stream *s = &ds->dma->streams[1];
+ size_t ret;
+
+ ret = stream_process_s2mem(s, buf, len);
+ stream_update_irq(s);
+ return ret;
+}
+
+static uint64_t axidma_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ XilinxAXIDMA *d = opaque;
+ struct Stream *s;
+ uint32_t r = 0;
+ int sid;
+
+ sid = streamid_from_addr(addr);
+ s = &d->streams[sid];
+
+ addr = addr % 0x30;
+ addr >>= 2;
+ switch (addr) {
+ case R_DMACR:
+ /* Simulate one cycles reset delay. */
+ s->regs[addr] &= ~DMACR_RESET;
+ r = s->regs[addr];
+ break;
+ case R_DMASR:
+ s->regs[addr] &= 0xffff;
+ s->regs[addr] |= (s->complete_cnt & 0xff) << 16;
+ s->regs[addr] |= (ptimer_get_count(s->ptimer) & 0xff) << 24;
+ r = s->regs[addr];
+ break;
+ default:
+ r = s->regs[addr];
+ D(qemu_log("%s ch=%d addr=" TARGET_FMT_plx " v=%x\n",
+ __func__, sid, addr * 4, r));
+ break;
+ }
+ return r;
+
+}
+
+static void axidma_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ XilinxAXIDMA *d = opaque;
+ struct Stream *s;
+ int sid;
+
+ sid = streamid_from_addr(addr);
+ s = &d->streams[sid];
+
+ addr = addr % 0x30;
+ addr >>= 2;
+ switch (addr) {
+ case R_DMACR:
+ /* Tailptr mode is always on. */
+ value |= DMACR_TAILPTR_MODE;
+ /* Remember our previous reset state. */
+ value |= (s->regs[addr] & DMACR_RESET);
+ s->regs[addr] = value;
+
+ if (value & DMACR_RESET) {
+ stream_reset(s);
+ }
+
+ if ((value & 1) && !stream_resetting(s)) {
+ /* Start processing. */
+ s->regs[R_DMASR] &= ~(DMASR_HALTED | DMASR_IDLE);
+ }
+ stream_reload_complete_cnt(s);
+ break;
+
+ case R_DMASR:
+ /* Mask away write to clear irq lines. */
+ value &= ~(value & DMASR_IRQ_MASK);
+ s->regs[addr] = value;
+ break;
+
+ case R_TAILDESC:
+ s->regs[addr] = value;
+ s->regs[R_DMASR] &= ~DMASR_IDLE; /* Not idle. */
+ if (!sid) {
+ stream_process_mem2s(s, d->tx_data_dev, d->tx_control_dev);
+ }
+ break;
+ default:
+ D(qemu_log("%s: ch=%d addr=" TARGET_FMT_plx " v=%x\n",
+ __func__, sid, addr * 4, (unsigned)value));
+ s->regs[addr] = value;
+ break;
+ }
+ if (sid == 1 && d->notify) {
+ StreamCanPushNotifyFn notifytmp = d->notify;
+ d->notify = NULL;
+ notifytmp(d->notify_opaque);
+ }
+ stream_update_irq(s);
+}
+
+static const MemoryRegionOps axidma_ops = {
+ .read = axidma_read,
+ .write = axidma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void xilinx_axidma_realize(DeviceState *dev, Error **errp)
+{
+ XilinxAXIDMA *s = XILINX_AXI_DMA(dev);
+ XilinxAXIDMAStreamSlave *ds = XILINX_AXI_DMA_DATA_STREAM(&s->rx_data_dev);
+ XilinxAXIDMAStreamSlave *cs = XILINX_AXI_DMA_CONTROL_STREAM(
+ &s->rx_control_dev);
+ Error *local_err = NULL;
+
+ object_property_add_link(OBJECT(ds), "dma", TYPE_XILINX_AXI_DMA,
+ (Object **)&ds->dma,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &local_err);
+ object_property_add_link(OBJECT(cs), "dma", TYPE_XILINX_AXI_DMA,
+ (Object **)&cs->dma,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &local_err);
+ if (local_err) {
+ goto xilinx_axidma_realize_fail;
+ }
+ object_property_set_link(OBJECT(ds), OBJECT(s), "dma", &local_err);
+ object_property_set_link(OBJECT(cs), OBJECT(s), "dma", &local_err);
+ if (local_err) {
+ goto xilinx_axidma_realize_fail;
+ }
+
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ struct Stream *st = &s->streams[i];
+
+ st->nr = i;
+ st->bh = qemu_bh_new(timer_hit, st);
+ st->ptimer = ptimer_init(st->bh);
+ ptimer_set_freq(st->ptimer, s->freqhz);
+ }
+ return;
+
+xilinx_axidma_realize_fail:
+ if (!*errp) {
+ *errp = local_err;
+ }
+}
+
+static void xilinx_axidma_init(Object *obj)
+{
+ XilinxAXIDMA *s = XILINX_AXI_DMA(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ object_property_add_link(obj, "axistream-connected", TYPE_STREAM_SLAVE,
+ (Object **)&s->tx_data_dev,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &error_abort);
+ object_property_add_link(obj, "axistream-control-connected",
+ TYPE_STREAM_SLAVE,
+ (Object **)&s->tx_control_dev,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &error_abort);
+
+ object_initialize(&s->rx_data_dev, sizeof(s->rx_data_dev),
+ TYPE_XILINX_AXI_DMA_DATA_STREAM);
+ object_initialize(&s->rx_control_dev, sizeof(s->rx_control_dev),
+ TYPE_XILINX_AXI_DMA_CONTROL_STREAM);
+ object_property_add_child(OBJECT(s), "axistream-connected-target",
+ (Object *)&s->rx_data_dev, &error_abort);
+ object_property_add_child(OBJECT(s), "axistream-control-connected-target",
+ (Object *)&s->rx_control_dev, &error_abort);
+
+ sysbus_init_irq(sbd, &s->streams[0].irq);
+ sysbus_init_irq(sbd, &s->streams[1].irq);
+
+ memory_region_init_io(&s->iomem, obj, &axidma_ops, s,
+ "xlnx.axi-dma", R_MAX * 4 * 2);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static Property axidma_properties[] = {
+ DEFINE_PROP_UINT32("freqhz", XilinxAXIDMA, freqhz, 50000000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void axidma_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = xilinx_axidma_realize,
+ dc->reset = xilinx_axidma_reset;
+ dc->props = axidma_properties;
+}
+
+static StreamSlaveClass xilinx_axidma_data_stream_class = {
+ .push = xilinx_axidma_data_stream_push,
+ .can_push = xilinx_axidma_data_stream_can_push,
+};
+
+static StreamSlaveClass xilinx_axidma_control_stream_class = {
+ .push = xilinx_axidma_control_stream_push,
+};
+
+static void xilinx_axidma_stream_class_init(ObjectClass *klass, void *data)
+{
+ StreamSlaveClass *ssc = STREAM_SLAVE_CLASS(klass);
+
+ ssc->push = ((StreamSlaveClass *)data)->push;
+ ssc->can_push = ((StreamSlaveClass *)data)->can_push;
+}
+
+static const TypeInfo axidma_info = {
+ .name = TYPE_XILINX_AXI_DMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XilinxAXIDMA),
+ .class_init = axidma_class_init,
+ .instance_init = xilinx_axidma_init,
+};
+
+static const TypeInfo xilinx_axidma_data_stream_info = {
+ .name = TYPE_XILINX_AXI_DMA_DATA_STREAM,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(struct XilinxAXIDMAStreamSlave),
+ .class_init = xilinx_axidma_stream_class_init,
+ .class_data = &xilinx_axidma_data_stream_class,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_STREAM_SLAVE },
+ { }
+ }
+};
+
+static const TypeInfo xilinx_axidma_control_stream_info = {
+ .name = TYPE_XILINX_AXI_DMA_CONTROL_STREAM,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(struct XilinxAXIDMAStreamSlave),
+ .class_init = xilinx_axidma_stream_class_init,
+ .class_data = &xilinx_axidma_control_stream_class,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_STREAM_SLAVE },
+ { }
+ }
+};
+
+static void xilinx_axidma_register_types(void)
+{
+ type_register_static(&axidma_info);
+ type_register_static(&xilinx_axidma_data_stream_info);
+ type_register_static(&xilinx_axidma_control_stream_info);
+}
+
+type_init(xilinx_axidma_register_types)
diff --git a/hw/gpio/Makefile.objs b/hw/gpio/Makefile.objs
new file mode 100644
index 00000000..1abcf179
--- /dev/null
+++ b/hw/gpio/Makefile.objs
@@ -0,0 +1,7 @@
+common-obj-$(CONFIG_MAX7310) += max7310.o
+common-obj-$(CONFIG_PL061) += pl061.o
+common-obj-$(CONFIG_PUV3) += puv3_gpio.o
+common-obj-$(CONFIG_ZAURUS) += zaurus.o
+common-obj-$(CONFIG_E500) += mpc8xxx.o
+
+obj-$(CONFIG_OMAP) += omap_gpio.o
diff --git a/hw/gpio/max7310.c b/hw/gpio/max7310.c
new file mode 100644
index 00000000..2f59b134
--- /dev/null
+++ b/hw/gpio/max7310.c
@@ -0,0 +1,217 @@
+/*
+ * MAX7310 8-port GPIO expansion chip.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This file is licensed under GNU GPL.
+ */
+
+#include "hw/i2c/i2c.h"
+
+#define TYPE_MAX7310 "max7310"
+#define MAX7310(obj) OBJECT_CHECK(MAX7310State, (obj), TYPE_MAX7310)
+
+typedef struct MAX7310State {
+ I2CSlave parent_obj;
+
+ int i2c_command_byte;
+ int len;
+
+ uint8_t level;
+ uint8_t direction;
+ uint8_t polarity;
+ uint8_t status;
+ uint8_t command;
+ qemu_irq handler[8];
+ qemu_irq *gpio_in;
+} MAX7310State;
+
+static void max7310_reset(DeviceState *dev)
+{
+ MAX7310State *s = MAX7310(dev);
+
+ s->level &= s->direction;
+ s->direction = 0xff;
+ s->polarity = 0xf0;
+ s->status = 0x01;
+ s->command = 0x00;
+}
+
+static int max7310_rx(I2CSlave *i2c)
+{
+ MAX7310State *s = MAX7310(i2c);
+
+ switch (s->command) {
+ case 0x00: /* Input port */
+ return s->level ^ s->polarity;
+ break;
+
+ case 0x01: /* Output port */
+ return s->level & ~s->direction;
+ break;
+
+ case 0x02: /* Polarity inversion */
+ return s->polarity;
+
+ case 0x03: /* Configuration */
+ return s->direction;
+
+ case 0x04: /* Timeout */
+ return s->status;
+ break;
+
+ case 0xff: /* Reserved */
+ return 0xff;
+
+ default:
+#ifdef VERBOSE
+ printf("%s: unknown register %02x\n", __FUNCTION__, s->command);
+#endif
+ break;
+ }
+ return 0xff;
+}
+
+static int max7310_tx(I2CSlave *i2c, uint8_t data)
+{
+ MAX7310State *s = MAX7310(i2c);
+ uint8_t diff;
+ int line;
+
+ if (s->len ++ > 1) {
+#ifdef VERBOSE
+ printf("%s: message too long (%i bytes)\n", __FUNCTION__, s->len);
+#endif
+ return 1;
+ }
+
+ if (s->i2c_command_byte) {
+ s->command = data;
+ s->i2c_command_byte = 0;
+ return 0;
+ }
+
+ switch (s->command) {
+ case 0x01: /* Output port */
+ for (diff = (data ^ s->level) & ~s->direction; diff;
+ diff &= ~(1 << line)) {
+ line = ctz32(diff);
+ if (s->handler[line])
+ qemu_set_irq(s->handler[line], (data >> line) & 1);
+ }
+ s->level = (s->level & s->direction) | (data & ~s->direction);
+ break;
+
+ case 0x02: /* Polarity inversion */
+ s->polarity = data;
+ break;
+
+ case 0x03: /* Configuration */
+ s->level &= ~(s->direction ^ data);
+ s->direction = data;
+ break;
+
+ case 0x04: /* Timeout */
+ s->status = data;
+ break;
+
+ case 0x00: /* Input port - ignore writes */
+ break;
+ default:
+#ifdef VERBOSE
+ printf("%s: unknown register %02x\n", __FUNCTION__, s->command);
+#endif
+ return 1;
+ }
+
+ return 0;
+}
+
+static void max7310_event(I2CSlave *i2c, enum i2c_event event)
+{
+ MAX7310State *s = MAX7310(i2c);
+ s->len = 0;
+
+ switch (event) {
+ case I2C_START_SEND:
+ s->i2c_command_byte = 1;
+ break;
+ case I2C_FINISH:
+#ifdef VERBOSE
+ if (s->len == 1)
+ printf("%s: message too short (%i bytes)\n", __FUNCTION__, s->len);
+#endif
+ break;
+ default:
+ break;
+ }
+}
+
+static const VMStateDescription vmstate_max7310 = {
+ .name = "max7310",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(i2c_command_byte, MAX7310State),
+ VMSTATE_INT32(len, MAX7310State),
+ VMSTATE_UINT8(level, MAX7310State),
+ VMSTATE_UINT8(direction, MAX7310State),
+ VMSTATE_UINT8(polarity, MAX7310State),
+ VMSTATE_UINT8(status, MAX7310State),
+ VMSTATE_UINT8(command, MAX7310State),
+ VMSTATE_I2C_SLAVE(parent_obj, MAX7310State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void max7310_gpio_set(void *opaque, int line, int level)
+{
+ MAX7310State *s = (MAX7310State *) opaque;
+ if (line >= ARRAY_SIZE(s->handler) || line < 0)
+ hw_error("bad GPIO line");
+
+ if (level)
+ s->level |= s->direction & (1 << line);
+ else
+ s->level &= ~(s->direction & (1 << line));
+}
+
+/* MAX7310 is SMBus-compatible (can be used with only SMBus protocols),
+ * but also accepts sequences that are not SMBus so return an I2C device. */
+static int max7310_init(I2CSlave *i2c)
+{
+ MAX7310State *s = MAX7310(i2c);
+
+ qdev_init_gpio_in(&i2c->qdev, max7310_gpio_set, 8);
+ qdev_init_gpio_out(&i2c->qdev, s->handler, 8);
+
+ return 0;
+}
+
+static void max7310_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = max7310_init;
+ k->event = max7310_event;
+ k->recv = max7310_rx;
+ k->send = max7310_tx;
+ dc->reset = max7310_reset;
+ dc->vmsd = &vmstate_max7310;
+}
+
+static const TypeInfo max7310_info = {
+ .name = TYPE_MAX7310,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(MAX7310State),
+ .class_init = max7310_class_init,
+};
+
+static void max7310_register_types(void)
+{
+ type_register_static(&max7310_info);
+}
+
+type_init(max7310_register_types)
diff --git a/hw/gpio/mpc8xxx.c b/hw/gpio/mpc8xxx.c
new file mode 100644
index 00000000..1aeaaaaf
--- /dev/null
+++ b/hw/gpio/mpc8xxx.c
@@ -0,0 +1,217 @@
+/*
+ * GPIO Controller for a lot of Freescale SoCs
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+
+#define TYPE_MPC8XXX_GPIO "mpc8xxx_gpio"
+#define MPC8XXX_GPIO(obj) OBJECT_CHECK(MPC8XXXGPIOState, (obj), TYPE_MPC8XXX_GPIO)
+
+typedef struct MPC8XXXGPIOState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq out[32];
+
+ uint32_t dir;
+ uint32_t odr;
+ uint32_t dat;
+ uint32_t ier;
+ uint32_t imr;
+ uint32_t icr;
+} MPC8XXXGPIOState;
+
+static const VMStateDescription vmstate_mpc8xxx_gpio = {
+ .name = "mpc8xxx_gpio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(dir, MPC8XXXGPIOState),
+ VMSTATE_UINT32(odr, MPC8XXXGPIOState),
+ VMSTATE_UINT32(dat, MPC8XXXGPIOState),
+ VMSTATE_UINT32(ier, MPC8XXXGPIOState),
+ VMSTATE_UINT32(imr, MPC8XXXGPIOState),
+ VMSTATE_UINT32(icr, MPC8XXXGPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mpc8xxx_gpio_update(MPC8XXXGPIOState *s)
+{
+ qemu_set_irq(s->irq, !!(s->ier & s->imr));
+}
+
+static uint64_t mpc8xxx_gpio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+
+ if (size != 4) {
+ /* All registers are 32bit */
+ return 0;
+ }
+
+ switch (offset) {
+ case 0x0: /* Direction */
+ return s->dir;
+ case 0x4: /* Open Drain */
+ return s->odr;
+ case 0x8: /* Data */
+ return s->dat;
+ case 0xC: /* Interrupt Event */
+ return s->ier;
+ case 0x10: /* Interrupt Mask */
+ return s->imr;
+ case 0x14: /* Interrupt Control */
+ return s->icr;
+ default:
+ return 0;
+ }
+}
+
+static void mpc8xxx_write_data(MPC8XXXGPIOState *s, uint32_t new_data)
+{
+ uint32_t old_data = s->dat;
+ uint32_t diff = old_data ^ new_data;
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ uint32_t mask = 0x80000000 >> i;
+ if (!(diff & mask)) {
+ continue;
+ }
+
+ if (s->dir & mask) {
+ /* Output */
+ qemu_set_irq(s->out[i], (new_data & mask) != 0);
+ }
+ }
+
+ s->dat = new_data;
+}
+
+static void mpc8xxx_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+
+ if (size != 4) {
+ /* All registers are 32bit */
+ return;
+ }
+
+ switch (offset) {
+ case 0x0: /* Direction */
+ s->dir = value;
+ break;
+ case 0x4: /* Open Drain */
+ s->odr = value;
+ break;
+ case 0x8: /* Data */
+ mpc8xxx_write_data(s, value);
+ break;
+ case 0xC: /* Interrupt Event */
+ s->ier &= ~value;
+ break;
+ case 0x10: /* Interrupt Mask */
+ s->imr = value;
+ break;
+ case 0x14: /* Interrupt Control */
+ s->icr = value;
+ break;
+ }
+
+ mpc8xxx_gpio_update(s);
+}
+
+static void mpc8xxx_gpio_reset(MPC8XXXGPIOState *s)
+{
+ s->dir = 0;
+ s->odr = 0;
+ s->dat = 0;
+ s->ier = 0;
+ s->imr = 0;
+ s->icr = 0;
+}
+
+static void mpc8xxx_gpio_set_irq(void * opaque, int irq, int level)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+ uint32_t mask;
+
+ mask = 0x80000000 >> irq;
+ if ((s->dir & mask) == 0) {
+ uint32_t old_value = s->dat & mask;
+
+ s->dat &= ~mask;
+ if (level)
+ s->dat |= mask;
+
+ if (!(s->icr & irq) || (old_value && !level)) {
+ s->ier |= mask;
+ }
+
+ mpc8xxx_gpio_update(s);
+ }
+}
+
+static const MemoryRegionOps mpc8xxx_gpio_ops = {
+ .read = mpc8xxx_gpio_read,
+ .write = mpc8xxx_gpio_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static int mpc8xxx_gpio_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ MPC8XXXGPIOState *s = MPC8XXX_GPIO(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &mpc8xxx_gpio_ops, s, "mpc8xxx_gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_in(dev, mpc8xxx_gpio_set_irq, 32);
+ qdev_init_gpio_out(dev, s->out, 32);
+ mpc8xxx_gpio_reset(s);
+ return 0;
+}
+
+static void mpc8xxx_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mpc8xxx_gpio_initfn;
+ dc->vmsd = &vmstate_mpc8xxx_gpio;
+}
+
+static const TypeInfo mpc8xxx_gpio_info = {
+ .name = TYPE_MPC8XXX_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MPC8XXXGPIOState),
+ .class_init = mpc8xxx_gpio_class_init,
+};
+
+static void mpc8xxx_gpio_register_types(void)
+{
+ type_register_static(&mpc8xxx_gpio_info);
+}
+
+type_init(mpc8xxx_gpio_register_types)
diff --git a/hw/gpio/omap_gpio.c b/hw/gpio/omap_gpio.c
new file mode 100644
index 00000000..d92f8cfb
--- /dev/null
+++ b/hw/gpio/omap_gpio.c
@@ -0,0 +1,808 @@
+/*
+ * TI OMAP processors GPIO emulation.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2007-2009 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+#include "hw/sysbus.h"
+
+struct omap_gpio_s {
+ qemu_irq irq;
+ qemu_irq handler[16];
+
+ uint16_t inputs;
+ uint16_t outputs;
+ uint16_t dir;
+ uint16_t edge;
+ uint16_t mask;
+ uint16_t ints;
+ uint16_t pins;
+};
+
+#define TYPE_OMAP1_GPIO "omap-gpio"
+#define OMAP1_GPIO(obj) \
+ OBJECT_CHECK(struct omap_gpif_s, (obj), TYPE_OMAP1_GPIO)
+
+struct omap_gpif_s {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ int mpu_model;
+ void *clk;
+ struct omap_gpio_s omap1;
+};
+
+/* General-Purpose I/O of OMAP1 */
+static void omap_gpio_set(void *opaque, int line, int level)
+{
+ struct omap_gpio_s *s = &((struct omap_gpif_s *) opaque)->omap1;
+ uint16_t prev = s->inputs;
+
+ if (level)
+ s->inputs |= 1 << line;
+ else
+ s->inputs &= ~(1 << line);
+
+ if (((s->edge & s->inputs & ~prev) | (~s->edge & ~s->inputs & prev)) &
+ (1 << line) & s->dir & ~s->mask) {
+ s->ints |= 1 << line;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static uint64_t omap_gpio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_gpio_s *s = (struct omap_gpio_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* DATA_INPUT */
+ return s->inputs & s->pins;
+
+ case 0x04: /* DATA_OUTPUT */
+ return s->outputs;
+
+ case 0x08: /* DIRECTION_CONTROL */
+ return s->dir;
+
+ case 0x0c: /* INTERRUPT_CONTROL */
+ return s->edge;
+
+ case 0x10: /* INTERRUPT_MASK */
+ return s->mask;
+
+ case 0x14: /* INTERRUPT_STATUS */
+ return s->ints;
+
+ case 0x18: /* PIN_CONTROL (not in OMAP310) */
+ OMAP_BAD_REG(addr);
+ return s->pins;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_gpio_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_gpio_s *s = (struct omap_gpio_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ uint16_t diff;
+ int ln;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* DATA_INPUT */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x04: /* DATA_OUTPUT */
+ diff = (s->outputs ^ value) & ~s->dir;
+ s->outputs = value;
+ while ((ln = ctz32(diff)) != 32) {
+ if (s->handler[ln])
+ qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+ diff &= ~(1 << ln);
+ }
+ break;
+
+ case 0x08: /* DIRECTION_CONTROL */
+ diff = s->outputs & (s->dir ^ value);
+ s->dir = value;
+
+ value = s->outputs & ~s->dir;
+ while ((ln = ctz32(diff)) != 32) {
+ if (s->handler[ln])
+ qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+ diff &= ~(1 << ln);
+ }
+ break;
+
+ case 0x0c: /* INTERRUPT_CONTROL */
+ s->edge = value;
+ break;
+
+ case 0x10: /* INTERRUPT_MASK */
+ s->mask = value;
+ break;
+
+ case 0x14: /* INTERRUPT_STATUS */
+ s->ints &= ~value;
+ if (!s->ints)
+ qemu_irq_lower(s->irq);
+ break;
+
+ case 0x18: /* PIN_CONTROL (not in OMAP310 TRM) */
+ OMAP_BAD_REG(addr);
+ s->pins = value;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+/* *Some* sources say the memory region is 32-bit. */
+static const MemoryRegionOps omap_gpio_ops = {
+ .read = omap_gpio_read,
+ .write = omap_gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_gpio_reset(struct omap_gpio_s *s)
+{
+ s->inputs = 0;
+ s->outputs = ~0;
+ s->dir = ~0;
+ s->edge = ~0;
+ s->mask = ~0;
+ s->ints = 0;
+ s->pins = ~0;
+}
+
+struct omap2_gpio_s {
+ qemu_irq irq[2];
+ qemu_irq wkup;
+ qemu_irq *handler;
+ MemoryRegion iomem;
+
+ uint8_t revision;
+ uint8_t config[2];
+ uint32_t inputs;
+ uint32_t outputs;
+ uint32_t dir;
+ uint32_t level[2];
+ uint32_t edge[2];
+ uint32_t mask[2];
+ uint32_t wumask;
+ uint32_t ints[2];
+ uint32_t debounce;
+ uint8_t delay;
+};
+
+#define TYPE_OMAP2_GPIO "omap2-gpio"
+#define OMAP2_GPIO(obj) \
+ OBJECT_CHECK(struct omap2_gpif_s, (obj), TYPE_OMAP2_GPIO)
+
+struct omap2_gpif_s {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ int mpu_model;
+ void *iclk;
+ void *fclk[6];
+ int modulecount;
+ struct omap2_gpio_s *modules;
+ qemu_irq *handler;
+ int autoidle;
+ int gpo;
+};
+
+/* General-Purpose Interface of OMAP2/3 */
+static inline void omap2_gpio_module_int_update(struct omap2_gpio_s *s,
+ int line)
+{
+ qemu_set_irq(s->irq[line], s->ints[line] & s->mask[line]);
+}
+
+static void omap2_gpio_module_wake(struct omap2_gpio_s *s, int line)
+{
+ if (!(s->config[0] & (1 << 2))) /* ENAWAKEUP */
+ return;
+ if (!(s->config[0] & (3 << 3))) /* Force Idle */
+ return;
+ if (!(s->wumask & (1 << line)))
+ return;
+
+ qemu_irq_raise(s->wkup);
+}
+
+static inline void omap2_gpio_module_out_update(struct omap2_gpio_s *s,
+ uint32_t diff)
+{
+ int ln;
+
+ s->outputs ^= diff;
+ diff &= ~s->dir;
+ while ((ln = ctz32(diff)) != 32) {
+ qemu_set_irq(s->handler[ln], (s->outputs >> ln) & 1);
+ diff &= ~(1 << ln);
+ }
+}
+
+static void omap2_gpio_module_level_update(struct omap2_gpio_s *s, int line)
+{
+ s->ints[line] |= s->dir &
+ ((s->inputs & s->level[1]) | (~s->inputs & s->level[0]));
+ omap2_gpio_module_int_update(s, line);
+}
+
+static inline void omap2_gpio_module_int(struct omap2_gpio_s *s, int line)
+{
+ s->ints[0] |= 1 << line;
+ omap2_gpio_module_int_update(s, 0);
+ s->ints[1] |= 1 << line;
+ omap2_gpio_module_int_update(s, 1);
+ omap2_gpio_module_wake(s, line);
+}
+
+static void omap2_gpio_set(void *opaque, int line, int level)
+{
+ struct omap2_gpif_s *p = opaque;
+ struct omap2_gpio_s *s = &p->modules[line >> 5];
+
+ line &= 31;
+ if (level) {
+ if (s->dir & (1 << line) & ((~s->inputs & s->edge[0]) | s->level[1]))
+ omap2_gpio_module_int(s, line);
+ s->inputs |= 1 << line;
+ } else {
+ if (s->dir & (1 << line) & ((s->inputs & s->edge[1]) | s->level[0]))
+ omap2_gpio_module_int(s, line);
+ s->inputs &= ~(1 << line);
+ }
+}
+
+static void omap2_gpio_module_reset(struct omap2_gpio_s *s)
+{
+ s->config[0] = 0;
+ s->config[1] = 2;
+ s->ints[0] = 0;
+ s->ints[1] = 0;
+ s->mask[0] = 0;
+ s->mask[1] = 0;
+ s->wumask = 0;
+ s->dir = ~0;
+ s->level[0] = 0;
+ s->level[1] = 0;
+ s->edge[0] = 0;
+ s->edge[1] = 0;
+ s->debounce = 0;
+ s->delay = 0;
+}
+
+static uint32_t omap2_gpio_module_read(void *opaque, hwaddr addr)
+{
+ struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* GPIO_REVISION */
+ return s->revision;
+
+ case 0x10: /* GPIO_SYSCONFIG */
+ return s->config[0];
+
+ case 0x14: /* GPIO_SYSSTATUS */
+ return 0x01;
+
+ case 0x18: /* GPIO_IRQSTATUS1 */
+ return s->ints[0];
+
+ case 0x1c: /* GPIO_IRQENABLE1 */
+ case 0x60: /* GPIO_CLEARIRQENABLE1 */
+ case 0x64: /* GPIO_SETIRQENABLE1 */
+ return s->mask[0];
+
+ case 0x20: /* GPIO_WAKEUPENABLE */
+ case 0x80: /* GPIO_CLEARWKUENA */
+ case 0x84: /* GPIO_SETWKUENA */
+ return s->wumask;
+
+ case 0x28: /* GPIO_IRQSTATUS2 */
+ return s->ints[1];
+
+ case 0x2c: /* GPIO_IRQENABLE2 */
+ case 0x70: /* GPIO_CLEARIRQENABLE2 */
+ case 0x74: /* GPIO_SETIREQNEABLE2 */
+ return s->mask[1];
+
+ case 0x30: /* GPIO_CTRL */
+ return s->config[1];
+
+ case 0x34: /* GPIO_OE */
+ return s->dir;
+
+ case 0x38: /* GPIO_DATAIN */
+ return s->inputs;
+
+ case 0x3c: /* GPIO_DATAOUT */
+ case 0x90: /* GPIO_CLEARDATAOUT */
+ case 0x94: /* GPIO_SETDATAOUT */
+ return s->outputs;
+
+ case 0x40: /* GPIO_LEVELDETECT0 */
+ return s->level[0];
+
+ case 0x44: /* GPIO_LEVELDETECT1 */
+ return s->level[1];
+
+ case 0x48: /* GPIO_RISINGDETECT */
+ return s->edge[0];
+
+ case 0x4c: /* GPIO_FALLINGDETECT */
+ return s->edge[1];
+
+ case 0x50: /* GPIO_DEBOUNCENABLE */
+ return s->debounce;
+
+ case 0x54: /* GPIO_DEBOUNCINGTIME */
+ return s->delay;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap2_gpio_module_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque;
+ uint32_t diff;
+ int ln;
+
+ switch (addr) {
+ case 0x00: /* GPIO_REVISION */
+ case 0x14: /* GPIO_SYSSTATUS */
+ case 0x38: /* GPIO_DATAIN */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* GPIO_SYSCONFIG */
+ if (((value >> 3) & 3) == 3)
+ fprintf(stderr, "%s: bad IDLEMODE value\n", __FUNCTION__);
+ if (value & 2)
+ omap2_gpio_module_reset(s);
+ s->config[0] = value & 0x1d;
+ break;
+
+ case 0x18: /* GPIO_IRQSTATUS1 */
+ if (s->ints[0] & value) {
+ s->ints[0] &= ~value;
+ omap2_gpio_module_level_update(s, 0);
+ }
+ break;
+
+ case 0x1c: /* GPIO_IRQENABLE1 */
+ s->mask[0] = value;
+ omap2_gpio_module_int_update(s, 0);
+ break;
+
+ case 0x20: /* GPIO_WAKEUPENABLE */
+ s->wumask = value;
+ break;
+
+ case 0x28: /* GPIO_IRQSTATUS2 */
+ if (s->ints[1] & value) {
+ s->ints[1] &= ~value;
+ omap2_gpio_module_level_update(s, 1);
+ }
+ break;
+
+ case 0x2c: /* GPIO_IRQENABLE2 */
+ s->mask[1] = value;
+ omap2_gpio_module_int_update(s, 1);
+ break;
+
+ case 0x30: /* GPIO_CTRL */
+ s->config[1] = value & 7;
+ break;
+
+ case 0x34: /* GPIO_OE */
+ diff = s->outputs & (s->dir ^ value);
+ s->dir = value;
+
+ value = s->outputs & ~s->dir;
+ while ((ln = ctz32(diff)) != 32) {
+ diff &= ~(1 << ln);
+ qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+ }
+
+ omap2_gpio_module_level_update(s, 0);
+ omap2_gpio_module_level_update(s, 1);
+ break;
+
+ case 0x3c: /* GPIO_DATAOUT */
+ omap2_gpio_module_out_update(s, s->outputs ^ value);
+ break;
+
+ case 0x40: /* GPIO_LEVELDETECT0 */
+ s->level[0] = value;
+ omap2_gpio_module_level_update(s, 0);
+ omap2_gpio_module_level_update(s, 1);
+ break;
+
+ case 0x44: /* GPIO_LEVELDETECT1 */
+ s->level[1] = value;
+ omap2_gpio_module_level_update(s, 0);
+ omap2_gpio_module_level_update(s, 1);
+ break;
+
+ case 0x48: /* GPIO_RISINGDETECT */
+ s->edge[0] = value;
+ break;
+
+ case 0x4c: /* GPIO_FALLINGDETECT */
+ s->edge[1] = value;
+ break;
+
+ case 0x50: /* GPIO_DEBOUNCENABLE */
+ s->debounce = value;
+ break;
+
+ case 0x54: /* GPIO_DEBOUNCINGTIME */
+ s->delay = value;
+ break;
+
+ case 0x60: /* GPIO_CLEARIRQENABLE1 */
+ s->mask[0] &= ~value;
+ omap2_gpio_module_int_update(s, 0);
+ break;
+
+ case 0x64: /* GPIO_SETIRQENABLE1 */
+ s->mask[0] |= value;
+ omap2_gpio_module_int_update(s, 0);
+ break;
+
+ case 0x70: /* GPIO_CLEARIRQENABLE2 */
+ s->mask[1] &= ~value;
+ omap2_gpio_module_int_update(s, 1);
+ break;
+
+ case 0x74: /* GPIO_SETIREQNEABLE2 */
+ s->mask[1] |= value;
+ omap2_gpio_module_int_update(s, 1);
+ break;
+
+ case 0x80: /* GPIO_CLEARWKUENA */
+ s->wumask &= ~value;
+ break;
+
+ case 0x84: /* GPIO_SETWKUENA */
+ s->wumask |= value;
+ break;
+
+ case 0x90: /* GPIO_CLEARDATAOUT */
+ omap2_gpio_module_out_update(s, s->outputs & value);
+ break;
+
+ case 0x94: /* GPIO_SETDATAOUT */
+ omap2_gpio_module_out_update(s, ~s->outputs & value);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static uint32_t omap2_gpio_module_readp(void *opaque, hwaddr addr)
+{
+ return omap2_gpio_module_read(opaque, addr & ~3) >> ((addr & 3) << 3);
+}
+
+static void omap2_gpio_module_writep(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ uint32_t cur = 0;
+ uint32_t mask = 0xffff;
+
+ switch (addr & ~3) {
+ case 0x00: /* GPIO_REVISION */
+ case 0x14: /* GPIO_SYSSTATUS */
+ case 0x38: /* GPIO_DATAIN */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* GPIO_SYSCONFIG */
+ case 0x1c: /* GPIO_IRQENABLE1 */
+ case 0x20: /* GPIO_WAKEUPENABLE */
+ case 0x2c: /* GPIO_IRQENABLE2 */
+ case 0x30: /* GPIO_CTRL */
+ case 0x34: /* GPIO_OE */
+ case 0x3c: /* GPIO_DATAOUT */
+ case 0x40: /* GPIO_LEVELDETECT0 */
+ case 0x44: /* GPIO_LEVELDETECT1 */
+ case 0x48: /* GPIO_RISINGDETECT */
+ case 0x4c: /* GPIO_FALLINGDETECT */
+ case 0x50: /* GPIO_DEBOUNCENABLE */
+ case 0x54: /* GPIO_DEBOUNCINGTIME */
+ cur = omap2_gpio_module_read(opaque, addr & ~3) &
+ ~(mask << ((addr & 3) << 3));
+
+ /* Fall through. */
+ case 0x18: /* GPIO_IRQSTATUS1 */
+ case 0x28: /* GPIO_IRQSTATUS2 */
+ case 0x60: /* GPIO_CLEARIRQENABLE1 */
+ case 0x64: /* GPIO_SETIRQENABLE1 */
+ case 0x70: /* GPIO_CLEARIRQENABLE2 */
+ case 0x74: /* GPIO_SETIREQNEABLE2 */
+ case 0x80: /* GPIO_CLEARWKUENA */
+ case 0x84: /* GPIO_SETWKUENA */
+ case 0x90: /* GPIO_CLEARDATAOUT */
+ case 0x94: /* GPIO_SETDATAOUT */
+ value <<= (addr & 3) << 3;
+ omap2_gpio_module_write(opaque, addr, cur | value);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap2_gpio_module_ops = {
+ .old_mmio = {
+ .read = {
+ omap2_gpio_module_readp,
+ omap2_gpio_module_readp,
+ omap2_gpio_module_read,
+ },
+ .write = {
+ omap2_gpio_module_writep,
+ omap2_gpio_module_writep,
+ omap2_gpio_module_write,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_gpif_reset(DeviceState *dev)
+{
+ struct omap_gpif_s *s = OMAP1_GPIO(dev);
+
+ omap_gpio_reset(&s->omap1);
+}
+
+static void omap2_gpif_reset(DeviceState *dev)
+{
+ struct omap2_gpif_s *s = OMAP2_GPIO(dev);
+ int i;
+
+ for (i = 0; i < s->modulecount; i++) {
+ omap2_gpio_module_reset(&s->modules[i]);
+ }
+ s->autoidle = 0;
+ s->gpo = 0;
+}
+
+static uint64_t omap2_gpif_top_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap2_gpif_s *s = (struct omap2_gpif_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* IPGENERICOCPSPL_REVISION */
+ return 0x18;
+
+ case 0x10: /* IPGENERICOCPSPL_SYSCONFIG */
+ return s->autoidle;
+
+ case 0x14: /* IPGENERICOCPSPL_SYSSTATUS */
+ return 0x01;
+
+ case 0x18: /* IPGENERICOCPSPL_IRQSTATUS */
+ return 0x00;
+
+ case 0x40: /* IPGENERICOCPSPL_GPO */
+ return s->gpo;
+
+ case 0x50: /* IPGENERICOCPSPL_GPI */
+ return 0x00;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap2_gpif_top_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap2_gpif_s *s = (struct omap2_gpif_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* IPGENERICOCPSPL_REVISION */
+ case 0x14: /* IPGENERICOCPSPL_SYSSTATUS */
+ case 0x18: /* IPGENERICOCPSPL_IRQSTATUS */
+ case 0x50: /* IPGENERICOCPSPL_GPI */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* IPGENERICOCPSPL_SYSCONFIG */
+ if (value & (1 << 1)) /* SOFTRESET */
+ omap2_gpif_reset(DEVICE(s));
+ s->autoidle = value & 1;
+ break;
+
+ case 0x40: /* IPGENERICOCPSPL_GPO */
+ s->gpo = value & 1;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap2_gpif_top_ops = {
+ .read = omap2_gpif_top_read,
+ .write = omap2_gpif_top_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int omap_gpio_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ struct omap_gpif_s *s = OMAP1_GPIO(dev);
+
+ if (!s->clk) {
+ hw_error("omap-gpio: clk not connected\n");
+ }
+ qdev_init_gpio_in(dev, omap_gpio_set, 16);
+ qdev_init_gpio_out(dev, s->omap1.handler, 16);
+ sysbus_init_irq(sbd, &s->omap1.irq);
+ memory_region_init_io(&s->iomem, OBJECT(s), &omap_gpio_ops, &s->omap1,
+ "omap.gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ return 0;
+}
+
+static int omap2_gpio_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ struct omap2_gpif_s *s = OMAP2_GPIO(dev);
+ int i;
+
+ if (!s->iclk) {
+ hw_error("omap2-gpio: iclk not connected\n");
+ }
+ if (s->mpu_model < omap3430) {
+ s->modulecount = (s->mpu_model < omap2430) ? 4 : 5;
+ memory_region_init_io(&s->iomem, OBJECT(s), &omap2_gpif_top_ops, s,
+ "omap2.gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ } else {
+ s->modulecount = 6;
+ }
+ s->modules = g_malloc0(s->modulecount * sizeof(struct omap2_gpio_s));
+ s->handler = g_malloc0(s->modulecount * 32 * sizeof(qemu_irq));
+ qdev_init_gpio_in(dev, omap2_gpio_set, s->modulecount * 32);
+ qdev_init_gpio_out(dev, s->handler, s->modulecount * 32);
+ for (i = 0; i < s->modulecount; i++) {
+ struct omap2_gpio_s *m = &s->modules[i];
+ if (!s->fclk[i]) {
+ hw_error("omap2-gpio: fclk%d not connected\n", i);
+ }
+ m->revision = (s->mpu_model < omap3430) ? 0x18 : 0x25;
+ m->handler = &s->handler[i * 32];
+ sysbus_init_irq(sbd, &m->irq[0]); /* mpu irq */
+ sysbus_init_irq(sbd, &m->irq[1]); /* dsp irq */
+ sysbus_init_irq(sbd, &m->wkup);
+ memory_region_init_io(&m->iomem, OBJECT(s), &omap2_gpio_module_ops, m,
+ "omap.gpio-module", 0x1000);
+ sysbus_init_mmio(sbd, &m->iomem);
+ }
+ return 0;
+}
+
+/* Using qdev pointer properties for the clocks is not ideal.
+ * qdev should support a generic means of defining a 'port' with
+ * an arbitrary interface for connecting two devices. Then we
+ * could reframe the omap clock API in terms of clock ports,
+ * and get some type safety. For now the best qdev provides is
+ * passing an arbitrary pointer.
+ * (It's not possible to pass in the string which is the clock
+ * name, because this device does not have the necessary information
+ * (ie the struct omap_mpu_state_s*) to do the clockname to pointer
+ * translation.)
+ */
+
+static Property omap_gpio_properties[] = {
+ DEFINE_PROP_INT32("mpu_model", struct omap_gpif_s, mpu_model, 0),
+ DEFINE_PROP_PTR("clk", struct omap_gpif_s, clk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void omap_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = omap_gpio_init;
+ dc->reset = omap_gpif_reset;
+ dc->props = omap_gpio_properties;
+ /* Reason: pointer property "clk" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo omap_gpio_info = {
+ .name = TYPE_OMAP1_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct omap_gpif_s),
+ .class_init = omap_gpio_class_init,
+};
+
+static Property omap2_gpio_properties[] = {
+ DEFINE_PROP_INT32("mpu_model", struct omap2_gpif_s, mpu_model, 0),
+ DEFINE_PROP_PTR("iclk", struct omap2_gpif_s, iclk),
+ DEFINE_PROP_PTR("fclk0", struct omap2_gpif_s, fclk[0]),
+ DEFINE_PROP_PTR("fclk1", struct omap2_gpif_s, fclk[1]),
+ DEFINE_PROP_PTR("fclk2", struct omap2_gpif_s, fclk[2]),
+ DEFINE_PROP_PTR("fclk3", struct omap2_gpif_s, fclk[3]),
+ DEFINE_PROP_PTR("fclk4", struct omap2_gpif_s, fclk[4]),
+ DEFINE_PROP_PTR("fclk5", struct omap2_gpif_s, fclk[5]),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void omap2_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = omap2_gpio_init;
+ dc->reset = omap2_gpif_reset;
+ dc->props = omap2_gpio_properties;
+ /* Reason: pointer properties "iclk", "fclk0", ..., "fclk5" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo omap2_gpio_info = {
+ .name = TYPE_OMAP2_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct omap2_gpif_s),
+ .class_init = omap2_gpio_class_init,
+};
+
+static void omap_gpio_register_types(void)
+{
+ type_register_static(&omap_gpio_info);
+ type_register_static(&omap2_gpio_info);
+}
+
+type_init(omap_gpio_register_types)
diff --git a/hw/gpio/pl061.c b/hw/gpio/pl061.c
new file mode 100644
index 00000000..4ba730b4
--- /dev/null
+++ b/hw/gpio/pl061.c
@@ -0,0 +1,367 @@
+/*
+ * Arm PrimeCell PL061 General Purpose IO with additional
+ * Luminary Micro Stellaris bits.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+
+//#define DEBUG_PL061 1
+
+#ifdef DEBUG_PL061
+#define DPRINTF(fmt, ...) \
+do { printf("pl061: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+static const uint8_t pl061_id[12] =
+ { 0x00, 0x00, 0x00, 0x00, 0x61, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+static const uint8_t pl061_id_luminary[12] =
+ { 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 };
+
+#define TYPE_PL061 "pl061"
+#define PL061(obj) OBJECT_CHECK(PL061State, (obj), TYPE_PL061)
+
+typedef struct PL061State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t locked;
+ uint32_t data;
+ uint32_t old_out_data;
+ uint32_t old_in_data;
+ uint32_t dir;
+ uint32_t isense;
+ uint32_t ibe;
+ uint32_t iev;
+ uint32_t im;
+ uint32_t istate;
+ uint32_t afsel;
+ uint32_t dr2r;
+ uint32_t dr4r;
+ uint32_t dr8r;
+ uint32_t odr;
+ uint32_t pur;
+ uint32_t pdr;
+ uint32_t slr;
+ uint32_t den;
+ uint32_t cr;
+ uint32_t float_high;
+ uint32_t amsel;
+ qemu_irq irq;
+ qemu_irq out[8];
+ const unsigned char *id;
+} PL061State;
+
+static const VMStateDescription vmstate_pl061 = {
+ .name = "pl061",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(locked, PL061State),
+ VMSTATE_UINT32(data, PL061State),
+ VMSTATE_UINT32(old_out_data, PL061State),
+ VMSTATE_UINT32(old_in_data, PL061State),
+ VMSTATE_UINT32(dir, PL061State),
+ VMSTATE_UINT32(isense, PL061State),
+ VMSTATE_UINT32(ibe, PL061State),
+ VMSTATE_UINT32(iev, PL061State),
+ VMSTATE_UINT32(im, PL061State),
+ VMSTATE_UINT32(istate, PL061State),
+ VMSTATE_UINT32(afsel, PL061State),
+ VMSTATE_UINT32(dr2r, PL061State),
+ VMSTATE_UINT32(dr4r, PL061State),
+ VMSTATE_UINT32(dr8r, PL061State),
+ VMSTATE_UINT32(odr, PL061State),
+ VMSTATE_UINT32(pur, PL061State),
+ VMSTATE_UINT32(pdr, PL061State),
+ VMSTATE_UINT32(slr, PL061State),
+ VMSTATE_UINT32(den, PL061State),
+ VMSTATE_UINT32(cr, PL061State),
+ VMSTATE_UINT32(float_high, PL061State),
+ VMSTATE_UINT32_V(amsel, PL061State, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pl061_update(PL061State *s)
+{
+ uint8_t changed;
+ uint8_t mask;
+ uint8_t out;
+ int i;
+
+ DPRINTF("dir = %d, data = %d\n", s->dir, s->data);
+
+ /* Outputs float high. */
+ /* FIXME: This is board dependent. */
+ out = (s->data & s->dir) | ~s->dir;
+ changed = s->old_out_data ^ out;
+ if (changed) {
+ s->old_out_data = out;
+ for (i = 0; i < 8; i++) {
+ mask = 1 << i;
+ if (changed & mask) {
+ DPRINTF("Set output %d = %d\n", i, (out & mask) != 0);
+ qemu_set_irq(s->out[i], (out & mask) != 0);
+ }
+ }
+ }
+
+ /* Inputs */
+ changed = (s->old_in_data ^ s->data) & ~s->dir;
+ if (changed) {
+ s->old_in_data = s->data;
+ for (i = 0; i < 8; i++) {
+ mask = 1 << i;
+ if (changed & mask) {
+ DPRINTF("Changed input %d = %d\n", i, (s->data & mask) != 0);
+
+ if (!(s->isense & mask)) {
+ /* Edge interrupt */
+ if (s->ibe & mask) {
+ /* Any edge triggers the interrupt */
+ s->istate |= mask;
+ } else {
+ /* Edge is selected by IEV */
+ s->istate |= ~(s->data ^ s->iev) & mask;
+ }
+ }
+ }
+ }
+ }
+
+ /* Level interrupt */
+ s->istate |= ~(s->data ^ s->iev) & s->isense;
+
+ DPRINTF("istate = %02X\n", s->istate);
+
+ qemu_set_irq(s->irq, (s->istate & s->im) != 0);
+}
+
+static uint64_t pl061_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL061State *s = (PL061State *)opaque;
+
+ if (offset >= 0xfd0 && offset < 0x1000) {
+ return s->id[(offset - 0xfd0) >> 2];
+ }
+ if (offset < 0x400) {
+ return s->data & (offset >> 2);
+ }
+ switch (offset) {
+ case 0x400: /* Direction */
+ return s->dir;
+ case 0x404: /* Interrupt sense */
+ return s->isense;
+ case 0x408: /* Interrupt both edges */
+ return s->ibe;
+ case 0x40c: /* Interrupt event */
+ return s->iev;
+ case 0x410: /* Interrupt mask */
+ return s->im;
+ case 0x414: /* Raw interrupt status */
+ return s->istate;
+ case 0x418: /* Masked interrupt status */
+ return s->istate & s->im;
+ case 0x420: /* Alternate function select */
+ return s->afsel;
+ case 0x500: /* 2mA drive */
+ return s->dr2r;
+ case 0x504: /* 4mA drive */
+ return s->dr4r;
+ case 0x508: /* 8mA drive */
+ return s->dr8r;
+ case 0x50c: /* Open drain */
+ return s->odr;
+ case 0x510: /* Pull-up */
+ return s->pur;
+ case 0x514: /* Pull-down */
+ return s->pdr;
+ case 0x518: /* Slew rate control */
+ return s->slr;
+ case 0x51c: /* Digital enable */
+ return s->den;
+ case 0x520: /* Lock */
+ return s->locked;
+ case 0x524: /* Commit */
+ return s->cr;
+ case 0x528: /* Analog mode select */
+ return s->amsel;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl061_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl061_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL061State *s = (PL061State *)opaque;
+ uint8_t mask;
+
+ if (offset < 0x400) {
+ mask = (offset >> 2) & s->dir;
+ s->data = (s->data & ~mask) | (value & mask);
+ pl061_update(s);
+ return;
+ }
+ switch (offset) {
+ case 0x400: /* Direction */
+ s->dir = value & 0xff;
+ break;
+ case 0x404: /* Interrupt sense */
+ s->isense = value & 0xff;
+ break;
+ case 0x408: /* Interrupt both edges */
+ s->ibe = value & 0xff;
+ break;
+ case 0x40c: /* Interrupt event */
+ s->iev = value & 0xff;
+ break;
+ case 0x410: /* Interrupt mask */
+ s->im = value & 0xff;
+ break;
+ case 0x41c: /* Interrupt clear */
+ s->istate &= ~value;
+ break;
+ case 0x420: /* Alternate function select */
+ mask = s->cr;
+ s->afsel = (s->afsel & ~mask) | (value & mask);
+ break;
+ case 0x500: /* 2mA drive */
+ s->dr2r = value & 0xff;
+ break;
+ case 0x504: /* 4mA drive */
+ s->dr4r = value & 0xff;
+ break;
+ case 0x508: /* 8mA drive */
+ s->dr8r = value & 0xff;
+ break;
+ case 0x50c: /* Open drain */
+ s->odr = value & 0xff;
+ break;
+ case 0x510: /* Pull-up */
+ s->pur = value & 0xff;
+ break;
+ case 0x514: /* Pull-down */
+ s->pdr = value & 0xff;
+ break;
+ case 0x518: /* Slew rate control */
+ s->slr = value & 0xff;
+ break;
+ case 0x51c: /* Digital enable */
+ s->den = value & 0xff;
+ break;
+ case 0x520: /* Lock */
+ s->locked = (value != 0xacce551);
+ break;
+ case 0x524: /* Commit */
+ if (!s->locked)
+ s->cr = value & 0xff;
+ break;
+ case 0x528:
+ s->amsel = value & 0xff;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl061_write: Bad offset %x\n", (int)offset);
+ }
+ pl061_update(s);
+}
+
+static void pl061_reset(PL061State *s)
+{
+ s->locked = 1;
+ s->cr = 0xff;
+}
+
+static void pl061_set_irq(void * opaque, int irq, int level)
+{
+ PL061State *s = (PL061State *)opaque;
+ uint8_t mask;
+
+ mask = 1 << irq;
+ if ((s->dir & mask) == 0) {
+ s->data &= ~mask;
+ if (level)
+ s->data |= mask;
+ pl061_update(s);
+ }
+}
+
+static const MemoryRegionOps pl061_ops = {
+ .read = pl061_read,
+ .write = pl061_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pl061_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PL061State *s = PL061(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl061_ops, s, "pl061", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_in(dev, pl061_set_irq, 8);
+ qdev_init_gpio_out(dev, s->out, 8);
+ pl061_reset(s);
+ return 0;
+}
+
+static void pl061_luminary_init(Object *obj)
+{
+ PL061State *s = PL061(obj);
+
+ s->id = pl061_id_luminary;
+}
+
+static void pl061_init(Object *obj)
+{
+ PL061State *s = PL061(obj);
+
+ s->id = pl061_id;
+}
+
+static void pl061_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pl061_initfn;
+ dc->vmsd = &vmstate_pl061;
+}
+
+static const TypeInfo pl061_info = {
+ .name = TYPE_PL061,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL061State),
+ .instance_init = pl061_init,
+ .class_init = pl061_class_init,
+};
+
+static const TypeInfo pl061_luminary_info = {
+ .name = "pl061_luminary",
+ .parent = TYPE_PL061,
+ .instance_init = pl061_luminary_init,
+};
+
+static void pl061_register_types(void)
+{
+ type_register_static(&pl061_info);
+ type_register_static(&pl061_luminary_info);
+}
+
+type_init(pl061_register_types)
diff --git a/hw/gpio/puv3_gpio.c b/hw/gpio/puv3_gpio.c
new file mode 100644
index 00000000..39840aa7
--- /dev/null
+++ b/hw/gpio/puv3_gpio.c
@@ -0,0 +1,145 @@
+/*
+ * GPIO device simulation in PKUnity SoC
+ *
+ * Copyright (C) 2010-2012 Guan Xuetao
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation, or any later version.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+
+#undef DEBUG_PUV3
+#include "hw/unicore32/puv3.h"
+
+#define TYPE_PUV3_GPIO "puv3_gpio"
+#define PUV3_GPIO(obj) OBJECT_CHECK(PUV3GPIOState, (obj), TYPE_PUV3_GPIO)
+
+typedef struct PUV3GPIOState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq[9];
+
+ uint32_t reg_GPLR;
+ uint32_t reg_GPDR;
+ uint32_t reg_GPIR;
+} PUV3GPIOState;
+
+static uint64_t puv3_gpio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PUV3GPIOState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (offset) {
+ case 0x00:
+ ret = s->reg_GPLR;
+ break;
+ case 0x04:
+ ret = s->reg_GPDR;
+ break;
+ case 0x20:
+ ret = s->reg_GPIR;
+ break;
+ default:
+ DPRINTF("Bad offset 0x%x\n", offset);
+ }
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, ret);
+
+ return ret;
+}
+
+static void puv3_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PUV3GPIOState *s = opaque;
+
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, value);
+ switch (offset) {
+ case 0x04:
+ s->reg_GPDR = value;
+ break;
+ case 0x08:
+ if (s->reg_GPDR & value) {
+ s->reg_GPLR |= value;
+ } else {
+ DPRINTF("Write gpio input port error!");
+ }
+ break;
+ case 0x0c:
+ if (s->reg_GPDR & value) {
+ s->reg_GPLR &= ~value;
+ } else {
+ DPRINTF("Write gpio input port error!");
+ }
+ break;
+ case 0x10: /* GRER */
+ case 0x14: /* GFER */
+ case 0x18: /* GEDR */
+ break;
+ case 0x20: /* GPIR */
+ s->reg_GPIR = value;
+ break;
+ default:
+ DPRINTF("Bad offset 0x%x\n", offset);
+ }
+}
+
+static const MemoryRegionOps puv3_gpio_ops = {
+ .read = puv3_gpio_read,
+ .write = puv3_gpio_write,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int puv3_gpio_init(SysBusDevice *dev)
+{
+ PUV3GPIOState *s = PUV3_GPIO(dev);
+
+ s->reg_GPLR = 0;
+ s->reg_GPDR = 0;
+
+ /* FIXME: these irqs not handled yet */
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOLOW0]);
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOLOW1]);
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOLOW2]);
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOLOW3]);
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOLOW4]);
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOLOW5]);
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOLOW6]);
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOLOW7]);
+ sysbus_init_irq(dev, &s->irq[PUV3_IRQS_GPIOHIGH]);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &puv3_gpio_ops, s, "puv3_gpio",
+ PUV3_REGS_OFFSET);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void puv3_gpio_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = puv3_gpio_init;
+}
+
+static const TypeInfo puv3_gpio_info = {
+ .name = TYPE_PUV3_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PUV3GPIOState),
+ .class_init = puv3_gpio_class_init,
+};
+
+static void puv3_gpio_register_type(void)
+{
+ type_register_static(&puv3_gpio_info);
+}
+
+type_init(puv3_gpio_register_type)
diff --git a/hw/gpio/zaurus.c b/hw/gpio/zaurus.c
new file mode 100644
index 00000000..24a77272
--- /dev/null
+++ b/hw/gpio/zaurus.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2006-2008 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/arm/sharpsl.h"
+#include "hw/sysbus.h"
+
+#undef REG_FMT
+#define REG_FMT "0x%02lx"
+
+/* SCOOP devices */
+
+#define TYPE_SCOOP "scoop"
+#define SCOOP(obj) OBJECT_CHECK(ScoopInfo, (obj), TYPE_SCOOP)
+
+typedef struct ScoopInfo ScoopInfo;
+struct ScoopInfo {
+ SysBusDevice parent_obj;
+
+ qemu_irq handler[16];
+ MemoryRegion iomem;
+ uint16_t status;
+ uint16_t power;
+ uint32_t gpio_level;
+ uint32_t gpio_dir;
+ uint32_t prev_level;
+
+ uint16_t mcr;
+ uint16_t cdr;
+ uint16_t ccr;
+ uint16_t irr;
+ uint16_t imr;
+ uint16_t isr;
+};
+
+#define SCOOP_MCR 0x00
+#define SCOOP_CDR 0x04
+#define SCOOP_CSR 0x08
+#define SCOOP_CPR 0x0c
+#define SCOOP_CCR 0x10
+#define SCOOP_IRR_IRM 0x14
+#define SCOOP_IMR 0x18
+#define SCOOP_ISR 0x1c
+#define SCOOP_GPCR 0x20
+#define SCOOP_GPWR 0x24
+#define SCOOP_GPRR 0x28
+
+static inline void scoop_gpio_handler_update(ScoopInfo *s) {
+ uint32_t level, diff;
+ int bit;
+ level = s->gpio_level & s->gpio_dir;
+
+ for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
+ bit = ctz32(diff);
+ qemu_set_irq(s->handler[bit], (level >> bit) & 1);
+ }
+
+ s->prev_level = level;
+}
+
+static uint64_t scoop_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ScoopInfo *s = (ScoopInfo *) opaque;
+
+ switch (addr & 0x3f) {
+ case SCOOP_MCR:
+ return s->mcr;
+ case SCOOP_CDR:
+ return s->cdr;
+ case SCOOP_CSR:
+ return s->status;
+ case SCOOP_CPR:
+ return s->power;
+ case SCOOP_CCR:
+ return s->ccr;
+ case SCOOP_IRR_IRM:
+ return s->irr;
+ case SCOOP_IMR:
+ return s->imr;
+ case SCOOP_ISR:
+ return s->isr;
+ case SCOOP_GPCR:
+ return s->gpio_dir;
+ case SCOOP_GPWR:
+ case SCOOP_GPRR:
+ return s->gpio_level;
+ default:
+ zaurus_printf("Bad register offset " REG_FMT "\n", (unsigned long)addr);
+ }
+
+ return 0;
+}
+
+static void scoop_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ ScoopInfo *s = (ScoopInfo *) opaque;
+ value &= 0xffff;
+
+ switch (addr & 0x3f) {
+ case SCOOP_MCR:
+ s->mcr = value;
+ break;
+ case SCOOP_CDR:
+ s->cdr = value;
+ break;
+ case SCOOP_CPR:
+ s->power = value;
+ if (value & 0x80)
+ s->power |= 0x8040;
+ break;
+ case SCOOP_CCR:
+ s->ccr = value;
+ break;
+ case SCOOP_IRR_IRM:
+ s->irr = value;
+ break;
+ case SCOOP_IMR:
+ s->imr = value;
+ break;
+ case SCOOP_ISR:
+ s->isr = value;
+ break;
+ case SCOOP_GPCR:
+ s->gpio_dir = value;
+ scoop_gpio_handler_update(s);
+ break;
+ case SCOOP_GPWR:
+ case SCOOP_GPRR: /* GPRR is probably R/O in real HW */
+ s->gpio_level = value & s->gpio_dir;
+ scoop_gpio_handler_update(s);
+ break;
+ default:
+ zaurus_printf("Bad register offset " REG_FMT "\n", (unsigned long)addr);
+ }
+}
+
+static const MemoryRegionOps scoop_ops = {
+ .read = scoop_read,
+ .write = scoop_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void scoop_gpio_set(void *opaque, int line, int level)
+{
+ ScoopInfo *s = (ScoopInfo *) opaque;
+
+ if (level)
+ s->gpio_level |= (1 << line);
+ else
+ s->gpio_level &= ~(1 << line);
+}
+
+static int scoop_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ ScoopInfo *s = SCOOP(dev);
+
+ s->status = 0x02;
+ qdev_init_gpio_out(dev, s->handler, 16);
+ qdev_init_gpio_in(dev, scoop_gpio_set, 16);
+ memory_region_init_io(&s->iomem, OBJECT(s), &scoop_ops, s, "scoop", 0x1000);
+
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ return 0;
+}
+
+static int scoop_post_load(void *opaque, int version_id)
+{
+ ScoopInfo *s = (ScoopInfo *) opaque;
+ int i;
+ uint32_t level;
+
+ level = s->gpio_level & s->gpio_dir;
+
+ for (i = 0; i < 16; i++) {
+ qemu_set_irq(s->handler[i], (level >> i) & 1);
+ }
+
+ s->prev_level = level;
+
+ return 0;
+}
+
+static bool is_version_0 (void *opaque, int version_id)
+{
+ return version_id == 0;
+}
+
+static bool vmstate_scoop_validate(void *opaque, int version_id)
+{
+ ScoopInfo *s = opaque;
+
+ return !(s->prev_level & 0xffff0000) &&
+ !(s->gpio_level & 0xffff0000) &&
+ !(s->gpio_dir & 0xffff0000);
+}
+
+static const VMStateDescription vmstate_scoop_regs = {
+ .name = "scoop",
+ .version_id = 1,
+ .minimum_version_id = 0,
+ .post_load = scoop_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(status, ScoopInfo),
+ VMSTATE_UINT16(power, ScoopInfo),
+ VMSTATE_UINT32(gpio_level, ScoopInfo),
+ VMSTATE_UINT32(gpio_dir, ScoopInfo),
+ VMSTATE_UINT32(prev_level, ScoopInfo),
+ VMSTATE_VALIDATE("irq levels are 16 bit", vmstate_scoop_validate),
+ VMSTATE_UINT16(mcr, ScoopInfo),
+ VMSTATE_UINT16(cdr, ScoopInfo),
+ VMSTATE_UINT16(ccr, ScoopInfo),
+ VMSTATE_UINT16(irr, ScoopInfo),
+ VMSTATE_UINT16(imr, ScoopInfo),
+ VMSTATE_UINT16(isr, ScoopInfo),
+ VMSTATE_UNUSED_TEST(is_version_0, 2),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static Property scoop_sysbus_properties[] = {
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void scoop_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = scoop_init;
+ dc->desc = "Scoop2 Sharp custom ASIC";
+ dc->vmsd = &vmstate_scoop_regs;
+ dc->props = scoop_sysbus_properties;
+}
+
+static const TypeInfo scoop_sysbus_info = {
+ .name = TYPE_SCOOP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ScoopInfo),
+ .class_init = scoop_sysbus_class_init,
+};
+
+static void scoop_register_types(void)
+{
+ type_register_static(&scoop_sysbus_info);
+}
+
+type_init(scoop_register_types)
+
+/* Write the bootloader parameters memory area. */
+
+#define MAGIC_CHG(a, b, c, d) ((d << 24) | (c << 16) | (b << 8) | a)
+
+static struct QEMU_PACKED sl_param_info {
+ uint32_t comadj_keyword;
+ int32_t comadj;
+
+ uint32_t uuid_keyword;
+ char uuid[16];
+
+ uint32_t touch_keyword;
+ int32_t touch_xp;
+ int32_t touch_yp;
+ int32_t touch_xd;
+ int32_t touch_yd;
+
+ uint32_t adadj_keyword;
+ int32_t adadj;
+
+ uint32_t phad_keyword;
+ int32_t phadadj;
+} zaurus_bootparam = {
+ .comadj_keyword = MAGIC_CHG('C', 'M', 'A', 'D'),
+ .comadj = 125,
+ .uuid_keyword = MAGIC_CHG('U', 'U', 'I', 'D'),
+ .uuid = { -1 },
+ .touch_keyword = MAGIC_CHG('T', 'U', 'C', 'H'),
+ .touch_xp = -1,
+ .adadj_keyword = MAGIC_CHG('B', 'V', 'A', 'D'),
+ .adadj = -1,
+ .phad_keyword = MAGIC_CHG('P', 'H', 'A', 'D'),
+ .phadadj = 0x01,
+};
+
+void sl_bootparam_write(hwaddr ptr)
+{
+ cpu_physical_memory_write(ptr, &zaurus_bootparam,
+ sizeof(struct sl_param_info));
+}
diff --git a/hw/i2c/Makefile.objs b/hw/i2c/Makefile.objs
new file mode 100644
index 00000000..0f130608
--- /dev/null
+++ b/hw/i2c/Makefile.objs
@@ -0,0 +1,7 @@
+common-obj-y += core.o smbus.o smbus_eeprom.o
+common-obj-$(CONFIG_VERSATILE_I2C) += versatile_i2c.o
+common-obj-$(CONFIG_ACPI_X86) += smbus_ich9.o
+common-obj-$(CONFIG_APM) += pm_smbus.o
+common-obj-$(CONFIG_BITBANG_I2C) += bitbang_i2c.o
+common-obj-$(CONFIG_EXYNOS4) += exynos4210_i2c.o
+obj-$(CONFIG_OMAP) += omap_i2c.o
diff --git a/hw/i2c/bitbang_i2c.c b/hw/i2c/bitbang_i2c.c
new file mode 100644
index 00000000..6d1bb03d
--- /dev/null
+++ b/hw/i2c/bitbang_i2c.c
@@ -0,0 +1,252 @@
+/*
+ * Bit-Bang i2c emulation extracted from
+ * Marvell MV88W8618 / Freecom MusicPal emulation.
+ *
+ * Copyright (c) 2008 Jan Kiszka
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/hw.h"
+#include "bitbang_i2c.h"
+#include "hw/sysbus.h"
+
+//#define DEBUG_BITBANG_I2C
+
+#ifdef DEBUG_BITBANG_I2C
+#define DPRINTF(fmt, ...) \
+do { printf("bitbang_i2c: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+typedef enum bitbang_i2c_state {
+ STOPPED = 0,
+ SENDING_BIT7,
+ SENDING_BIT6,
+ SENDING_BIT5,
+ SENDING_BIT4,
+ SENDING_BIT3,
+ SENDING_BIT2,
+ SENDING_BIT1,
+ SENDING_BIT0,
+ WAITING_FOR_ACK,
+ RECEIVING_BIT7,
+ RECEIVING_BIT6,
+ RECEIVING_BIT5,
+ RECEIVING_BIT4,
+ RECEIVING_BIT3,
+ RECEIVING_BIT2,
+ RECEIVING_BIT1,
+ RECEIVING_BIT0,
+ SENDING_ACK,
+ SENT_NACK
+} bitbang_i2c_state;
+
+struct bitbang_i2c_interface {
+ I2CBus *bus;
+ bitbang_i2c_state state;
+ int last_data;
+ int last_clock;
+ int device_out;
+ uint8_t buffer;
+ int current_addr;
+};
+
+static void bitbang_i2c_enter_stop(bitbang_i2c_interface *i2c)
+{
+ DPRINTF("STOP\n");
+ if (i2c->current_addr >= 0)
+ i2c_end_transfer(i2c->bus);
+ i2c->current_addr = -1;
+ i2c->state = STOPPED;
+}
+
+/* Set device data pin. */
+static int bitbang_i2c_ret(bitbang_i2c_interface *i2c, int level)
+{
+ i2c->device_out = level;
+ //DPRINTF("%d %d %d\n", i2c->last_clock, i2c->last_data, i2c->device_out);
+ return level & i2c->last_data;
+}
+
+/* Leave device data pin unodified. */
+static int bitbang_i2c_nop(bitbang_i2c_interface *i2c)
+{
+ return bitbang_i2c_ret(i2c, i2c->device_out);
+}
+
+/* Returns data line level. */
+int bitbang_i2c_set(bitbang_i2c_interface *i2c, int line, int level)
+{
+ int data;
+
+ if (level != 0 && level != 1) {
+ abort();
+ }
+
+ if (line == BITBANG_I2C_SDA) {
+ if (level == i2c->last_data) {
+ return bitbang_i2c_nop(i2c);
+ }
+ i2c->last_data = level;
+ if (i2c->last_clock == 0) {
+ return bitbang_i2c_nop(i2c);
+ }
+ if (level == 0) {
+ DPRINTF("START\n");
+ /* START condition. */
+ i2c->state = SENDING_BIT7;
+ i2c->current_addr = -1;
+ } else {
+ /* STOP condition. */
+ bitbang_i2c_enter_stop(i2c);
+ }
+ return bitbang_i2c_ret(i2c, 1);
+ }
+
+ data = i2c->last_data;
+ if (i2c->last_clock == level) {
+ return bitbang_i2c_nop(i2c);
+ }
+ i2c->last_clock = level;
+ if (level == 0) {
+ /* State is set/read at the start of the clock pulse.
+ release the data line at the end. */
+ return bitbang_i2c_ret(i2c, 1);
+ }
+ switch (i2c->state) {
+ case STOPPED:
+ case SENT_NACK:
+ return bitbang_i2c_ret(i2c, 1);
+
+ case SENDING_BIT7 ... SENDING_BIT0:
+ i2c->buffer = (i2c->buffer << 1) | data;
+ /* will end up in WAITING_FOR_ACK */
+ i2c->state++;
+ return bitbang_i2c_ret(i2c, 1);
+
+ case WAITING_FOR_ACK:
+ if (i2c->current_addr < 0) {
+ i2c->current_addr = i2c->buffer;
+ DPRINTF("Address 0x%02x\n", i2c->current_addr);
+ i2c_start_transfer(i2c->bus, i2c->current_addr >> 1,
+ i2c->current_addr & 1);
+ } else {
+ DPRINTF("Sent 0x%02x\n", i2c->buffer);
+ i2c_send(i2c->bus, i2c->buffer);
+ }
+ if (i2c->current_addr & 1) {
+ i2c->state = RECEIVING_BIT7;
+ } else {
+ i2c->state = SENDING_BIT7;
+ }
+ return bitbang_i2c_ret(i2c, 0);
+
+ case RECEIVING_BIT7:
+ i2c->buffer = i2c_recv(i2c->bus);
+ DPRINTF("RX byte 0x%02x\n", i2c->buffer);
+ /* Fall through... */
+ case RECEIVING_BIT6 ... RECEIVING_BIT0:
+ data = i2c->buffer >> 7;
+ /* will end up in SENDING_ACK */
+ i2c->state++;
+ i2c->buffer <<= 1;
+ return bitbang_i2c_ret(i2c, data);
+
+ case SENDING_ACK:
+ i2c->state = RECEIVING_BIT7;
+ if (data != 0) {
+ DPRINTF("NACKED\n");
+ i2c->state = SENT_NACK;
+ i2c_nack(i2c->bus);
+ } else {
+ DPRINTF("ACKED\n");
+ }
+ return bitbang_i2c_ret(i2c, 1);
+ }
+ abort();
+}
+
+bitbang_i2c_interface *bitbang_i2c_init(I2CBus *bus)
+{
+ bitbang_i2c_interface *s;
+
+ s = g_malloc0(sizeof(bitbang_i2c_interface));
+
+ s->bus = bus;
+ s->last_data = 1;
+ s->last_clock = 1;
+ s->device_out = 1;
+
+ return s;
+}
+
+/* GPIO interface. */
+
+#define TYPE_GPIO_I2C "gpio_i2c"
+#define GPIO_I2C(obj) OBJECT_CHECK(GPIOI2CState, (obj), TYPE_GPIO_I2C)
+
+typedef struct GPIOI2CState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion dummy_iomem;
+ bitbang_i2c_interface *bitbang;
+ int last_level;
+ qemu_irq out;
+} GPIOI2CState;
+
+static void bitbang_i2c_gpio_set(void *opaque, int irq, int level)
+{
+ GPIOI2CState *s = opaque;
+
+ level = bitbang_i2c_set(s->bitbang, irq, level);
+ if (level != s->last_level) {
+ s->last_level = level;
+ qemu_set_irq(s->out, level);
+ }
+}
+
+static int gpio_i2c_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ GPIOI2CState *s = GPIO_I2C(dev);
+ I2CBus *bus;
+
+ memory_region_init(&s->dummy_iomem, OBJECT(s), "gpio_i2c", 0);
+ sysbus_init_mmio(sbd, &s->dummy_iomem);
+
+ bus = i2c_init_bus(dev, "i2c");
+ s->bitbang = bitbang_i2c_init(bus);
+
+ qdev_init_gpio_in(dev, bitbang_i2c_gpio_set, 2);
+ qdev_init_gpio_out(dev, &s->out, 1);
+
+ return 0;
+}
+
+static void gpio_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = gpio_i2c_init;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->desc = "Virtual GPIO to I2C bridge";
+}
+
+static const TypeInfo gpio_i2c_info = {
+ .name = TYPE_GPIO_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(GPIOI2CState),
+ .class_init = gpio_i2c_class_init,
+};
+
+static void bitbang_i2c_register_types(void)
+{
+ type_register_static(&gpio_i2c_info);
+}
+
+type_init(bitbang_i2c_register_types)
diff --git a/hw/i2c/bitbang_i2c.h b/hw/i2c/bitbang_i2c.h
new file mode 100644
index 00000000..3a7126d5
--- /dev/null
+++ b/hw/i2c/bitbang_i2c.h
@@ -0,0 +1,14 @@
+#ifndef BITBANG_I2C_H
+#define BITBANG_I2C_H
+
+#include "hw/i2c/i2c.h"
+
+typedef struct bitbang_i2c_interface bitbang_i2c_interface;
+
+#define BITBANG_I2C_SDA 0
+#define BITBANG_I2C_SCL 1
+
+bitbang_i2c_interface *bitbang_i2c_init(I2CBus *bus);
+int bitbang_i2c_set(bitbang_i2c_interface *i2c, int line, int level);
+
+#endif
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
new file mode 100644
index 00000000..5a640263
--- /dev/null
+++ b/hw/i2c/core.c
@@ -0,0 +1,245 @@
+/*
+ * QEMU I2C bus interface.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+#include "hw/i2c/i2c.h"
+
+struct I2CBus
+{
+ BusState qbus;
+ I2CSlave *current_dev;
+ I2CSlave *dev;
+ uint8_t saved_address;
+};
+
+static Property i2c_props[] = {
+ DEFINE_PROP_UINT8("address", struct I2CSlave, address, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+#define TYPE_I2C_BUS "i2c-bus"
+#define I2C_BUS(obj) OBJECT_CHECK(I2CBus, (obj), TYPE_I2C_BUS)
+
+static const TypeInfo i2c_bus_info = {
+ .name = TYPE_I2C_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(I2CBus),
+};
+
+static void i2c_bus_pre_save(void *opaque)
+{
+ I2CBus *bus = opaque;
+
+ bus->saved_address = bus->current_dev ? bus->current_dev->address : -1;
+}
+
+static int i2c_bus_post_load(void *opaque, int version_id)
+{
+ I2CBus *bus = opaque;
+
+ /* The bus is loaded before attached devices, so load and save the
+ current device id. Devices will check themselves as loaded. */
+ bus->current_dev = NULL;
+ return 0;
+}
+
+static const VMStateDescription vmstate_i2c_bus = {
+ .name = "i2c_bus",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = i2c_bus_pre_save,
+ .post_load = i2c_bus_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(saved_address, I2CBus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* Create a new I2C bus. */
+I2CBus *i2c_init_bus(DeviceState *parent, const char *name)
+{
+ I2CBus *bus;
+
+ bus = I2C_BUS(qbus_create(TYPE_I2C_BUS, parent, name));
+ vmstate_register(NULL, -1, &vmstate_i2c_bus, bus);
+ return bus;
+}
+
+void i2c_set_slave_address(I2CSlave *dev, uint8_t address)
+{
+ dev->address = address;
+}
+
+/* Return nonzero if bus is busy. */
+int i2c_bus_busy(I2CBus *bus)
+{
+ return bus->current_dev != NULL;
+}
+
+/* Returns non-zero if the address is not valid. */
+/* TODO: Make this handle multiple masters. */
+int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
+{
+ BusChild *kid;
+ I2CSlave *slave = NULL;
+ I2CSlaveClass *sc;
+
+ QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ I2CSlave *candidate = I2C_SLAVE(qdev);
+ if (candidate->address == address) {
+ slave = candidate;
+ break;
+ }
+ }
+
+ if (!slave) {
+ return 1;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(slave);
+ /* If the bus is already busy, assume this is a repeated
+ start condition. */
+ bus->current_dev = slave;
+ if (sc->event) {
+ sc->event(slave, recv ? I2C_START_RECV : I2C_START_SEND);
+ }
+ return 0;
+}
+
+void i2c_end_transfer(I2CBus *bus)
+{
+ I2CSlave *dev = bus->current_dev;
+ I2CSlaveClass *sc;
+
+ if (!dev) {
+ return;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(dev);
+ if (sc->event) {
+ sc->event(dev, I2C_FINISH);
+ }
+
+ bus->current_dev = NULL;
+}
+
+int i2c_send(I2CBus *bus, uint8_t data)
+{
+ I2CSlave *dev = bus->current_dev;
+ I2CSlaveClass *sc;
+
+ if (!dev) {
+ return -1;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(dev);
+ if (sc->send) {
+ return sc->send(dev, data);
+ }
+
+ return -1;
+}
+
+int i2c_recv(I2CBus *bus)
+{
+ I2CSlave *dev = bus->current_dev;
+ I2CSlaveClass *sc;
+
+ if (!dev) {
+ return -1;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(dev);
+ if (sc->recv) {
+ return sc->recv(dev);
+ }
+
+ return -1;
+}
+
+void i2c_nack(I2CBus *bus)
+{
+ I2CSlave *dev = bus->current_dev;
+ I2CSlaveClass *sc;
+
+ if (!dev) {
+ return;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(dev);
+ if (sc->event) {
+ sc->event(dev, I2C_NACK);
+ }
+}
+
+static int i2c_slave_post_load(void *opaque, int version_id)
+{
+ I2CSlave *dev = opaque;
+ I2CBus *bus;
+ bus = I2C_BUS(qdev_get_parent_bus(DEVICE(dev)));
+ if (bus->saved_address == dev->address) {
+ bus->current_dev = dev;
+ }
+ return 0;
+}
+
+const VMStateDescription vmstate_i2c_slave = {
+ .name = "I2CSlave",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = i2c_slave_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(address, I2CSlave),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int i2c_slave_qdev_init(DeviceState *dev)
+{
+ I2CSlave *s = I2C_SLAVE(dev);
+ I2CSlaveClass *sc = I2C_SLAVE_GET_CLASS(s);
+
+ return sc->init(s);
+}
+
+DeviceState *i2c_create_slave(I2CBus *bus, const char *name, uint8_t addr)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->qbus, name);
+ qdev_prop_set_uint8(dev, "address", addr);
+ qdev_init_nofail(dev);
+ return dev;
+}
+
+static void i2c_slave_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->init = i2c_slave_qdev_init;
+ set_bit(DEVICE_CATEGORY_MISC, k->categories);
+ k->bus_type = TYPE_I2C_BUS;
+ k->props = i2c_props;
+}
+
+static const TypeInfo i2c_slave_type_info = {
+ .name = TYPE_I2C_SLAVE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(I2CSlave),
+ .abstract = true,
+ .class_size = sizeof(I2CSlaveClass),
+ .class_init = i2c_slave_class_init,
+};
+
+static void i2c_slave_register_types(void)
+{
+ type_register_static(&i2c_bus_info);
+ type_register_static(&i2c_slave_type_info);
+}
+
+type_init(i2c_slave_register_types)
diff --git a/hw/i2c/exynos4210_i2c.c b/hw/i2c/exynos4210_i2c.c
new file mode 100644
index 00000000..fb99dfda
--- /dev/null
+++ b/hw/i2c/exynos4210_i2c.c
@@ -0,0 +1,336 @@
+/*
+ * Exynos4210 I2C Bus Serial Interface Emulation
+ *
+ * Copyright (C) 2012 Samsung Electronics Co Ltd.
+ * Maksim Kozlov, <m.kozlov@samsung.com>
+ * Igor Mitsyanko, <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/timer.h"
+#include "hw/sysbus.h"
+#include "hw/i2c/i2c.h"
+
+#ifndef EXYNOS4_I2C_DEBUG
+#define EXYNOS4_I2C_DEBUG 0
+#endif
+
+#define TYPE_EXYNOS4_I2C "exynos4210.i2c"
+#define EXYNOS4_I2C(obj) \
+ OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C)
+
+/* Exynos4210 I2C memory map */
+#define EXYNOS4_I2C_MEM_SIZE 0x14
+#define I2CCON_ADDR 0x00 /* control register */
+#define I2CSTAT_ADDR 0x04 /* control/status register */
+#define I2CADD_ADDR 0x08 /* address register */
+#define I2CDS_ADDR 0x0c /* data shift register */
+#define I2CLC_ADDR 0x10 /* line control register */
+
+#define I2CCON_ACK_GEN (1 << 7)
+#define I2CCON_INTRS_EN (1 << 5)
+#define I2CCON_INT_PEND (1 << 4)
+
+#define EXYNOS4_I2C_MODE(reg) (((reg) >> 6) & 3)
+#define I2C_IN_MASTER_MODE(reg) (((reg) >> 6) & 2)
+#define I2CMODE_MASTER_Rx 0x2
+#define I2CMODE_MASTER_Tx 0x3
+#define I2CSTAT_LAST_BIT (1 << 0)
+#define I2CSTAT_OUTPUT_EN (1 << 4)
+#define I2CSTAT_START_BUSY (1 << 5)
+
+
+#if EXYNOS4_I2C_DEBUG
+#define DPRINT(fmt, args...) \
+ do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0)
+
+static const char *exynos4_i2c_get_regname(unsigned offset)
+{
+ switch (offset) {
+ case I2CCON_ADDR:
+ return "I2CCON";
+ case I2CSTAT_ADDR:
+ return "I2CSTAT";
+ case I2CADD_ADDR:
+ return "I2CADD";
+ case I2CDS_ADDR:
+ return "I2CDS";
+ case I2CLC_ADDR:
+ return "I2CLC";
+ default:
+ return "[?]";
+ }
+}
+
+#else
+#define DPRINT(fmt, args...) do { } while (0)
+#endif
+
+typedef struct Exynos4210I2CState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ I2CBus *bus;
+ qemu_irq irq;
+
+ uint8_t i2ccon;
+ uint8_t i2cstat;
+ uint8_t i2cadd;
+ uint8_t i2cds;
+ uint8_t i2clc;
+ bool scl_free;
+} Exynos4210I2CState;
+
+static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s)
+{
+ if (s->i2ccon & I2CCON_INTRS_EN) {
+ s->i2ccon |= I2CCON_INT_PEND;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static void exynos4210_i2c_data_receive(void *opaque)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+ int ret;
+
+ s->i2cstat &= ~I2CSTAT_LAST_BIT;
+ s->scl_free = false;
+ ret = i2c_recv(s->bus);
+ if (ret < 0 && (s->i2ccon & I2CCON_ACK_GEN)) {
+ s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */
+ } else {
+ s->i2cds = ret;
+ }
+ exynos4210_i2c_raise_interrupt(s);
+}
+
+static void exynos4210_i2c_data_send(void *opaque)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+
+ s->i2cstat &= ~I2CSTAT_LAST_BIT;
+ s->scl_free = false;
+ if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) {
+ s->i2cstat |= I2CSTAT_LAST_BIT;
+ }
+ exynos4210_i2c_raise_interrupt(s);
+}
+
+static uint64_t exynos4210_i2c_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+ uint8_t value;
+
+ switch (offset) {
+ case I2CCON_ADDR:
+ value = s->i2ccon;
+ break;
+ case I2CSTAT_ADDR:
+ value = s->i2cstat;
+ break;
+ case I2CADD_ADDR:
+ value = s->i2cadd;
+ break;
+ case I2CDS_ADDR:
+ value = s->i2cds;
+ s->scl_free = true;
+ if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx &&
+ (s->i2cstat & I2CSTAT_START_BUSY) &&
+ !(s->i2ccon & I2CCON_INT_PEND)) {
+ exynos4210_i2c_data_receive(s);
+ }
+ break;
+ case I2CLC_ADDR:
+ value = s->i2clc;
+ break;
+ default:
+ value = 0;
+ DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset);
+ break;
+ }
+
+ DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset),
+ (unsigned int)offset, value);
+ return value;
+}
+
+static void exynos4210_i2c_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+ uint8_t v = value & 0xff;
+
+ DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset),
+ (unsigned int)offset, v);
+
+ switch (offset) {
+ case I2CCON_ADDR:
+ s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND);
+ if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) {
+ s->i2ccon &= ~I2CCON_INT_PEND;
+ qemu_irq_lower(s->irq);
+ if (!(s->i2ccon & I2CCON_INTRS_EN)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ }
+
+ if (s->i2cstat & I2CSTAT_START_BUSY) {
+ if (s->scl_free) {
+ if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) {
+ exynos4210_i2c_data_send(s);
+ } else if (EXYNOS4_I2C_MODE(s->i2cstat) ==
+ I2CMODE_MASTER_Rx) {
+ exynos4210_i2c_data_receive(s);
+ }
+ } else {
+ s->i2ccon |= I2CCON_INT_PEND;
+ qemu_irq_raise(s->irq);
+ }
+ }
+ }
+ break;
+ case I2CSTAT_ADDR:
+ s->i2cstat =
+ (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY);
+
+ if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ s->scl_free = true;
+ qemu_irq_lower(s->irq);
+ break;
+ }
+
+ /* Nothing to do if in i2c slave mode */
+ if (!I2C_IN_MASTER_MODE(s->i2cstat)) {
+ break;
+ }
+
+ if (v & I2CSTAT_START_BUSY) {
+ s->i2cstat &= ~I2CSTAT_LAST_BIT;
+ s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */
+ s->scl_free = false;
+
+ /* Generate start bit and send slave address */
+ if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) &&
+ (s->i2ccon & I2CCON_ACK_GEN)) {
+ s->i2cstat |= I2CSTAT_LAST_BIT;
+ } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) {
+ exynos4210_i2c_data_receive(s);
+ }
+ exynos4210_i2c_raise_interrupt(s);
+ } else {
+ i2c_end_transfer(s->bus);
+ if (!(s->i2ccon & I2CCON_INT_PEND)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ }
+ s->scl_free = true;
+ }
+ break;
+ case I2CADD_ADDR:
+ if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) {
+ s->i2cadd = v;
+ }
+ break;
+ case I2CDS_ADDR:
+ if (s->i2cstat & I2CSTAT_OUTPUT_EN) {
+ s->i2cds = v;
+ s->scl_free = true;
+ if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx &&
+ (s->i2cstat & I2CSTAT_START_BUSY) &&
+ !(s->i2ccon & I2CCON_INT_PEND)) {
+ exynos4210_i2c_data_send(s);
+ }
+ }
+ break;
+ case I2CLC_ADDR:
+ s->i2clc = v;
+ break;
+ default:
+ DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps exynos4210_i2c_ops = {
+ .read = exynos4210_i2c_read,
+ .write = exynos4210_i2c_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_i2c_vmstate = {
+ .name = "exynos4210.i2c",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(i2ccon, Exynos4210I2CState),
+ VMSTATE_UINT8(i2cstat, Exynos4210I2CState),
+ VMSTATE_UINT8(i2cds, Exynos4210I2CState),
+ VMSTATE_UINT8(i2cadd, Exynos4210I2CState),
+ VMSTATE_UINT8(i2clc, Exynos4210I2CState),
+ VMSTATE_BOOL(scl_free, Exynos4210I2CState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_i2c_reset(DeviceState *d)
+{
+ Exynos4210I2CState *s = EXYNOS4_I2C(d);
+
+ s->i2ccon = 0x00;
+ s->i2cstat = 0x00;
+ s->i2cds = 0xFF;
+ s->i2clc = 0x00;
+ s->i2cadd = 0xFF;
+ s->scl_free = true;
+}
+
+static int exynos4210_i2c_realize(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ Exynos4210I2CState *s = EXYNOS4_I2C(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_i2c_ops, s,
+ TYPE_EXYNOS4_I2C, EXYNOS4_I2C_MEM_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ s->bus = i2c_init_bus(dev, "i2c");
+ return 0;
+}
+
+static void exynos4210_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ dc->vmsd = &exynos4210_i2c_vmstate;
+ dc->reset = exynos4210_i2c_reset;
+ sbdc->init = exynos4210_i2c_realize;
+}
+
+static const TypeInfo exynos4210_i2c_type_info = {
+ .name = TYPE_EXYNOS4_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210I2CState),
+ .class_init = exynos4210_i2c_class_init,
+};
+
+static void exynos4210_i2c_register_types(void)
+{
+ type_register_static(&exynos4210_i2c_type_info);
+}
+
+type_init(exynos4210_i2c_register_types)
diff --git a/hw/i2c/omap_i2c.c b/hw/i2c/omap_i2c.c
new file mode 100644
index 00000000..b6f544a2
--- /dev/null
+++ b/hw/i2c/omap_i2c.c
@@ -0,0 +1,504 @@
+/*
+ * TI OMAP on-chip I2C controller. Only "new I2C" mode supported.
+ *
+ * Copyright (C) 2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "hw/arm/omap.h"
+#include "hw/sysbus.h"
+
+#define TYPE_OMAP_I2C "omap_i2c"
+#define OMAP_I2C(obj) OBJECT_CHECK(OMAPI2CState, (obj), TYPE_OMAP_I2C)
+
+typedef struct OMAPI2CState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq drq[2];
+ I2CBus *bus;
+
+ uint8_t revision;
+ void *iclk;
+ void *fclk;
+
+ uint8_t mask;
+ uint16_t stat;
+ uint16_t dma;
+ uint16_t count;
+ int count_cur;
+ uint32_t fifo;
+ int rxlen;
+ int txlen;
+ uint16_t control;
+ uint16_t addr[2];
+ uint8_t divider;
+ uint8_t times[2];
+ uint16_t test;
+} OMAPI2CState;
+
+#define OMAP2_INTR_REV 0x34
+#define OMAP2_GC_REV 0x34
+
+static void omap_i2c_interrupts_update(OMAPI2CState *s)
+{
+ qemu_set_irq(s->irq, s->stat & s->mask);
+ if ((s->dma >> 15) & 1) /* RDMA_EN */
+ qemu_set_irq(s->drq[0], (s->stat >> 3) & 1); /* RRDY */
+ if ((s->dma >> 7) & 1) /* XDMA_EN */
+ qemu_set_irq(s->drq[1], (s->stat >> 4) & 1); /* XRDY */
+}
+
+static void omap_i2c_fifo_run(OMAPI2CState *s)
+{
+ int ack = 1;
+
+ if (!i2c_bus_busy(s->bus))
+ return;
+
+ if ((s->control >> 2) & 1) { /* RM */
+ if ((s->control >> 1) & 1) { /* STP */
+ i2c_end_transfer(s->bus);
+ s->control &= ~(1 << 1); /* STP */
+ s->count_cur = s->count;
+ s->txlen = 0;
+ } else if ((s->control >> 9) & 1) { /* TRX */
+ while (ack && s->txlen)
+ ack = (i2c_send(s->bus,
+ (s->fifo >> ((-- s->txlen) << 3)) &
+ 0xff) >= 0);
+ s->stat |= 1 << 4; /* XRDY */
+ } else {
+ while (s->rxlen < 4)
+ s->fifo |= i2c_recv(s->bus) << ((s->rxlen ++) << 3);
+ s->stat |= 1 << 3; /* RRDY */
+ }
+ } else {
+ if ((s->control >> 9) & 1) { /* TRX */
+ while (ack && s->count_cur && s->txlen) {
+ ack = (i2c_send(s->bus,
+ (s->fifo >> ((-- s->txlen) << 3)) &
+ 0xff) >= 0);
+ s->count_cur --;
+ }
+ if (ack && s->count_cur)
+ s->stat |= 1 << 4; /* XRDY */
+ else
+ s->stat &= ~(1 << 4); /* XRDY */
+ if (!s->count_cur) {
+ s->stat |= 1 << 2; /* ARDY */
+ s->control &= ~(1 << 10); /* MST */
+ }
+ } else {
+ while (s->count_cur && s->rxlen < 4) {
+ s->fifo |= i2c_recv(s->bus) << ((s->rxlen ++) << 3);
+ s->count_cur --;
+ }
+ if (s->rxlen)
+ s->stat |= 1 << 3; /* RRDY */
+ else
+ s->stat &= ~(1 << 3); /* RRDY */
+ }
+ if (!s->count_cur) {
+ if ((s->control >> 1) & 1) { /* STP */
+ i2c_end_transfer(s->bus);
+ s->control &= ~(1 << 1); /* STP */
+ s->count_cur = s->count;
+ s->txlen = 0;
+ } else {
+ s->stat |= 1 << 2; /* ARDY */
+ s->control &= ~(1 << 10); /* MST */
+ }
+ }
+ }
+
+ s->stat |= (!ack) << 1; /* NACK */
+ if (!ack)
+ s->control &= ~(1 << 1); /* STP */
+}
+
+static void omap_i2c_reset(DeviceState *dev)
+{
+ OMAPI2CState *s = OMAP_I2C(dev);
+
+ s->mask = 0;
+ s->stat = 0;
+ s->dma = 0;
+ s->count = 0;
+ s->count_cur = 0;
+ s->fifo = 0;
+ s->rxlen = 0;
+ s->txlen = 0;
+ s->control = 0;
+ s->addr[0] = 0;
+ s->addr[1] = 0;
+ s->divider = 0;
+ s->times[0] = 0;
+ s->times[1] = 0;
+ s->test = 0;
+}
+
+static uint32_t omap_i2c_read(void *opaque, hwaddr addr)
+{
+ OMAPI2CState *s = opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ uint16_t ret;
+
+ switch (offset) {
+ case 0x00: /* I2C_REV */
+ return s->revision; /* REV */
+
+ case 0x04: /* I2C_IE */
+ return s->mask;
+
+ case 0x08: /* I2C_STAT */
+ return s->stat | (i2c_bus_busy(s->bus) << 12);
+
+ case 0x0c: /* I2C_IV */
+ if (s->revision >= OMAP2_INTR_REV)
+ break;
+ ret = ctz32(s->stat & s->mask);
+ if (ret != 32) {
+ s->stat ^= 1 << ret;
+ ret++;
+ } else {
+ ret = 0;
+ }
+ omap_i2c_interrupts_update(s);
+ return ret;
+
+ case 0x10: /* I2C_SYSS */
+ return (s->control >> 15) & 1; /* I2C_EN */
+
+ case 0x14: /* I2C_BUF */
+ return s->dma;
+
+ case 0x18: /* I2C_CNT */
+ return s->count_cur; /* DCOUNT */
+
+ case 0x1c: /* I2C_DATA */
+ ret = 0;
+ if (s->control & (1 << 14)) { /* BE */
+ ret |= ((s->fifo >> 0) & 0xff) << 8;
+ ret |= ((s->fifo >> 8) & 0xff) << 0;
+ } else {
+ ret |= ((s->fifo >> 8) & 0xff) << 8;
+ ret |= ((s->fifo >> 0) & 0xff) << 0;
+ }
+ if (s->rxlen == 1) {
+ s->stat |= 1 << 15; /* SBD */
+ s->rxlen = 0;
+ } else if (s->rxlen > 1) {
+ if (s->rxlen > 2)
+ s->fifo >>= 16;
+ s->rxlen -= 2;
+ } else {
+ /* XXX: remote access (qualifier) error - what's that? */
+ }
+ if (!s->rxlen) {
+ s->stat &= ~(1 << 3); /* RRDY */
+ if (((s->control >> 10) & 1) && /* MST */
+ ((~s->control >> 9) & 1)) { /* TRX */
+ s->stat |= 1 << 2; /* ARDY */
+ s->control &= ~(1 << 10); /* MST */
+ }
+ }
+ s->stat &= ~(1 << 11); /* ROVR */
+ omap_i2c_fifo_run(s);
+ omap_i2c_interrupts_update(s);
+ return ret;
+
+ case 0x20: /* I2C_SYSC */
+ return 0;
+
+ case 0x24: /* I2C_CON */
+ return s->control;
+
+ case 0x28: /* I2C_OA */
+ return s->addr[0];
+
+ case 0x2c: /* I2C_SA */
+ return s->addr[1];
+
+ case 0x30: /* I2C_PSC */
+ return s->divider;
+
+ case 0x34: /* I2C_SCLL */
+ return s->times[0];
+
+ case 0x38: /* I2C_SCLH */
+ return s->times[1];
+
+ case 0x3c: /* I2C_SYSTEST */
+ if (s->test & (1 << 15)) { /* ST_EN */
+ s->test ^= 0xa;
+ return s->test;
+ } else
+ return s->test & ~0x300f;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_i2c_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ OMAPI2CState *s = opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ int nack;
+
+ switch (offset) {
+ case 0x00: /* I2C_REV */
+ case 0x0c: /* I2C_IV */
+ case 0x10: /* I2C_SYSS */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x04: /* I2C_IE */
+ s->mask = value & (s->revision < OMAP2_GC_REV ? 0x1f : 0x3f);
+ break;
+
+ case 0x08: /* I2C_STAT */
+ if (s->revision < OMAP2_INTR_REV) {
+ OMAP_RO_REG(addr);
+ return;
+ }
+
+ /* RRDY and XRDY are reset by hardware. (in all versions???) */
+ s->stat &= ~(value & 0x27);
+ omap_i2c_interrupts_update(s);
+ break;
+
+ case 0x14: /* I2C_BUF */
+ s->dma = value & 0x8080;
+ if (value & (1 << 15)) /* RDMA_EN */
+ s->mask &= ~(1 << 3); /* RRDY_IE */
+ if (value & (1 << 7)) /* XDMA_EN */
+ s->mask &= ~(1 << 4); /* XRDY_IE */
+ break;
+
+ case 0x18: /* I2C_CNT */
+ s->count = value; /* DCOUNT */
+ break;
+
+ case 0x1c: /* I2C_DATA */
+ if (s->txlen > 2) {
+ /* XXX: remote access (qualifier) error - what's that? */
+ break;
+ }
+ s->fifo <<= 16;
+ s->txlen += 2;
+ if (s->control & (1 << 14)) { /* BE */
+ s->fifo |= ((value >> 8) & 0xff) << 8;
+ s->fifo |= ((value >> 0) & 0xff) << 0;
+ } else {
+ s->fifo |= ((value >> 0) & 0xff) << 8;
+ s->fifo |= ((value >> 8) & 0xff) << 0;
+ }
+ s->stat &= ~(1 << 10); /* XUDF */
+ if (s->txlen > 2)
+ s->stat &= ~(1 << 4); /* XRDY */
+ omap_i2c_fifo_run(s);
+ omap_i2c_interrupts_update(s);
+ break;
+
+ case 0x20: /* I2C_SYSC */
+ if (s->revision < OMAP2_INTR_REV) {
+ OMAP_BAD_REG(addr);
+ return;
+ }
+
+ if (value & 2) {
+ omap_i2c_reset(DEVICE(s));
+ }
+ break;
+
+ case 0x24: /* I2C_CON */
+ s->control = value & 0xcf87;
+ if (~value & (1 << 15)) { /* I2C_EN */
+ if (s->revision < OMAP2_INTR_REV) {
+ omap_i2c_reset(DEVICE(s));
+ }
+ break;
+ }
+ if ((value & (1 << 15)) && !(value & (1 << 10))) { /* MST */
+ fprintf(stderr, "%s: I^2C slave mode not supported\n",
+ __FUNCTION__);
+ break;
+ }
+ if ((value & (1 << 15)) && value & (1 << 8)) { /* XA */
+ fprintf(stderr, "%s: 10-bit addressing mode not supported\n",
+ __FUNCTION__);
+ break;
+ }
+ if ((value & (1 << 15)) && value & (1 << 0)) { /* STT */
+ nack = !!i2c_start_transfer(s->bus, s->addr[1], /* SA */
+ (~value >> 9) & 1); /* TRX */
+ s->stat |= nack << 1; /* NACK */
+ s->control &= ~(1 << 0); /* STT */
+ s->fifo = 0;
+ if (nack)
+ s->control &= ~(1 << 1); /* STP */
+ else {
+ s->count_cur = s->count;
+ omap_i2c_fifo_run(s);
+ }
+ omap_i2c_interrupts_update(s);
+ }
+ break;
+
+ case 0x28: /* I2C_OA */
+ s->addr[0] = value & 0x3ff;
+ break;
+
+ case 0x2c: /* I2C_SA */
+ s->addr[1] = value & 0x3ff;
+ break;
+
+ case 0x30: /* I2C_PSC */
+ s->divider = value;
+ break;
+
+ case 0x34: /* I2C_SCLL */
+ s->times[0] = value;
+ break;
+
+ case 0x38: /* I2C_SCLH */
+ s->times[1] = value;
+ break;
+
+ case 0x3c: /* I2C_SYSTEST */
+ s->test = value & 0xf80f;
+ if (value & (1 << 11)) /* SBB */
+ if (s->revision >= OMAP2_INTR_REV) {
+ s->stat |= 0x3f;
+ omap_i2c_interrupts_update(s);
+ }
+ if (value & (1 << 15)) /* ST_EN */
+ fprintf(stderr, "%s: System Test not supported\n", __FUNCTION__);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static void omap_i2c_writeb(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ OMAPI2CState *s = opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ switch (offset) {
+ case 0x1c: /* I2C_DATA */
+ if (s->txlen > 2) {
+ /* XXX: remote access (qualifier) error - what's that? */
+ break;
+ }
+ s->fifo <<= 8;
+ s->txlen += 1;
+ s->fifo |= value & 0xff;
+ s->stat &= ~(1 << 10); /* XUDF */
+ if (s->txlen > 2)
+ s->stat &= ~(1 << 4); /* XRDY */
+ omap_i2c_fifo_run(s);
+ omap_i2c_interrupts_update(s);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_i2c_ops = {
+ .old_mmio = {
+ .read = {
+ omap_badwidth_read16,
+ omap_i2c_read,
+ omap_badwidth_read16,
+ },
+ .write = {
+ omap_i2c_writeb, /* Only the last fifo write can be 8 bit. */
+ omap_i2c_write,
+ omap_badwidth_write16,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int omap_i2c_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ OMAPI2CState *s = OMAP_I2C(dev);
+
+ if (!s->fclk) {
+ hw_error("omap_i2c: fclk not connected\n");
+ }
+ if (s->revision >= OMAP2_INTR_REV && !s->iclk) {
+ /* Note that OMAP1 doesn't have a separate interface clock */
+ hw_error("omap_i2c: iclk not connected\n");
+ }
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->drq[0]);
+ sysbus_init_irq(sbd, &s->drq[1]);
+ memory_region_init_io(&s->iomem, OBJECT(s), &omap_i2c_ops, s, "omap.i2c",
+ (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ s->bus = i2c_init_bus(dev, NULL);
+ return 0;
+}
+
+static Property omap_i2c_properties[] = {
+ DEFINE_PROP_UINT8("revision", OMAPI2CState, revision, 0),
+ DEFINE_PROP_PTR("iclk", OMAPI2CState, iclk),
+ DEFINE_PROP_PTR("fclk", OMAPI2CState, fclk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void omap_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ k->init = omap_i2c_init;
+ dc->props = omap_i2c_properties;
+ dc->reset = omap_i2c_reset;
+ /* Reason: pointer properties "iclk", "fclk" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo omap_i2c_info = {
+ .name = TYPE_OMAP_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OMAPI2CState),
+ .class_init = omap_i2c_class_init,
+};
+
+static void omap_i2c_register_types(void)
+{
+ type_register_static(&omap_i2c_info);
+}
+
+I2CBus *omap_i2c_bus(DeviceState *omap_i2c)
+{
+ OMAPI2CState *s = OMAP_I2C(omap_i2c);
+ return s->bus;
+}
+
+type_init(omap_i2c_register_types)
diff --git a/hw/i2c/pm_smbus.c b/hw/i2c/pm_smbus.c
new file mode 100644
index 00000000..ce1713d2
--- /dev/null
+++ b/hw/i2c/pm_smbus.c
@@ -0,0 +1,227 @@
+/*
+ * PC SMBus implementation
+ * splitted from acpi.c
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/i2c/pm_smbus.h"
+#include "hw/i2c/smbus.h"
+
+/* no save/load? */
+
+#define SMBHSTSTS 0x00
+#define SMBHSTCNT 0x02
+#define SMBHSTCMD 0x03
+#define SMBHSTADD 0x04
+#define SMBHSTDAT0 0x05
+#define SMBHSTDAT1 0x06
+#define SMBBLKDAT 0x07
+
+#define STS_HOST_BUSY (1)
+#define STS_INTR (1<<1)
+#define STS_DEV_ERR (1<<2)
+#define STS_BUS_ERR (1<<3)
+#define STS_FAILED (1<<4)
+#define STS_SMBALERT (1<<5)
+#define STS_INUSE_STS (1<<6)
+#define STS_BYTE_DONE (1<<7)
+/* Signs of successfully transaction end :
+* ByteDoneStatus = 1 (STS_BYTE_DONE) and INTR = 1 (STS_INTR )
+*/
+
+//#define DEBUG
+
+#ifdef DEBUG
+# define SMBUS_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define SMBUS_DPRINTF(format, ...) do { } while (0)
+#endif
+
+
+static void smb_transaction(PMSMBus *s)
+{
+ uint8_t prot = (s->smb_ctl >> 2) & 0x07;
+ uint8_t read = s->smb_addr & 0x01;
+ uint8_t cmd = s->smb_cmd;
+ uint8_t addr = s->smb_addr >> 1;
+ I2CBus *bus = s->smbus;
+ int ret;
+
+ SMBUS_DPRINTF("SMBus trans addr=0x%02x prot=0x%02x\n", addr, prot);
+ /* Transaction isn't exec if STS_DEV_ERR bit set */
+ if ((s->smb_stat & STS_DEV_ERR) != 0) {
+ goto error;
+ }
+ switch(prot) {
+ case 0x0:
+ ret = smbus_quick_command(bus, addr, read);
+ goto done;
+ case 0x1:
+ if (read) {
+ ret = smbus_receive_byte(bus, addr);
+ goto data8;
+ } else {
+ ret = smbus_send_byte(bus, addr, cmd);
+ goto done;
+ }
+ case 0x2:
+ if (read) {
+ ret = smbus_read_byte(bus, addr, cmd);
+ goto data8;
+ } else {
+ ret = smbus_write_byte(bus, addr, cmd, s->smb_data0);
+ goto done;
+ }
+ break;
+ case 0x3:
+ if (read) {
+ ret = smbus_read_word(bus, addr, cmd);
+ goto data16;
+ } else {
+ ret = smbus_write_word(bus, addr, cmd, (s->smb_data1 << 8) | s->smb_data0);
+ goto done;
+ }
+ break;
+ case 0x5:
+ if (read) {
+ ret = smbus_read_block(bus, addr, cmd, s->smb_data);
+ goto data8;
+ } else {
+ ret = smbus_write_block(bus, addr, cmd, s->smb_data, s->smb_data0);
+ goto done;
+ }
+ break;
+ default:
+ goto error;
+ }
+ abort();
+
+data16:
+ if (ret < 0) {
+ goto error;
+ }
+ s->smb_data1 = ret >> 8;
+data8:
+ if (ret < 0) {
+ goto error;
+ }
+ s->smb_data0 = ret;
+done:
+ if (ret < 0) {
+ goto error;
+ }
+ s->smb_stat |= STS_BYTE_DONE | STS_INTR;
+ return;
+
+error:
+ s->smb_stat |= STS_DEV_ERR;
+ return;
+
+}
+
+static void smb_ioport_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ PMSMBus *s = opaque;
+
+ SMBUS_DPRINTF("SMB writeb port=0x%04" HWADDR_PRIx
+ " val=0x%02" PRIx64 "\n", addr, val);
+ switch(addr) {
+ case SMBHSTSTS:
+ s->smb_stat = (~(val & 0xff)) & s->smb_stat;
+ s->smb_index = 0;
+ break;
+ case SMBHSTCNT:
+ s->smb_ctl = val;
+ if (val & 0x40)
+ smb_transaction(s);
+ break;
+ case SMBHSTCMD:
+ s->smb_cmd = val;
+ break;
+ case SMBHSTADD:
+ s->smb_addr = val;
+ break;
+ case SMBHSTDAT0:
+ s->smb_data0 = val;
+ break;
+ case SMBHSTDAT1:
+ s->smb_data1 = val;
+ break;
+ case SMBBLKDAT:
+ s->smb_data[s->smb_index++] = val;
+ if (s->smb_index > 31)
+ s->smb_index = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t smb_ioport_readb(void *opaque, hwaddr addr, unsigned width)
+{
+ PMSMBus *s = opaque;
+ uint32_t val;
+
+ switch(addr) {
+ case SMBHSTSTS:
+ val = s->smb_stat;
+ break;
+ case SMBHSTCNT:
+ s->smb_index = 0;
+ val = s->smb_ctl & 0x1f;
+ break;
+ case SMBHSTCMD:
+ val = s->smb_cmd;
+ break;
+ case SMBHSTADD:
+ val = s->smb_addr;
+ break;
+ case SMBHSTDAT0:
+ val = s->smb_data0;
+ break;
+ case SMBHSTDAT1:
+ val = s->smb_data1;
+ break;
+ case SMBBLKDAT:
+ val = s->smb_data[s->smb_index++];
+ if (s->smb_index > 31)
+ s->smb_index = 0;
+ break;
+ default:
+ val = 0;
+ break;
+ }
+ SMBUS_DPRINTF("SMB readb port=0x%04" HWADDR_PRIx " val=0x%02x\n", addr, val);
+ return val;
+}
+
+static const MemoryRegionOps pm_smbus_ops = {
+ .read = smb_ioport_readb,
+ .write = smb_ioport_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void pm_smbus_init(DeviceState *parent, PMSMBus *smb)
+{
+ smb->smbus = i2c_init_bus(parent, "i2c");
+ memory_region_init_io(&smb->io, OBJECT(parent), &pm_smbus_ops, smb,
+ "pm-smbus", 64);
+}
diff --git a/hw/i2c/smbus.c b/hw/i2c/smbus.c
new file mode 100644
index 00000000..6e27ae8b
--- /dev/null
+++ b/hw/i2c/smbus.c
@@ -0,0 +1,361 @@
+/*
+ * QEMU SMBus device emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+/* TODO: Implement PEC. */
+
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus.h"
+
+//#define DEBUG_SMBUS 1
+
+#ifdef DEBUG_SMBUS
+#define DPRINTF(fmt, ...) \
+do { printf("smbus(%02x): " fmt , dev->i2c.address, ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "smbus: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "smbus: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+enum {
+ SMBUS_IDLE,
+ SMBUS_WRITE_DATA,
+ SMBUS_RECV_BYTE,
+ SMBUS_READ_DATA,
+ SMBUS_DONE,
+ SMBUS_CONFUSED = -1
+};
+
+static void smbus_do_quick_cmd(SMBusDevice *dev, int recv)
+{
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+
+ DPRINTF("Quick Command %d\n", recv);
+ if (sc->quick_cmd) {
+ sc->quick_cmd(dev, recv);
+ }
+}
+
+static void smbus_do_write(SMBusDevice *dev)
+{
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+
+ if (dev->data_len == 0) {
+ smbus_do_quick_cmd(dev, 0);
+ } else if (dev->data_len == 1) {
+ DPRINTF("Send Byte\n");
+ if (sc->send_byte) {
+ sc->send_byte(dev, dev->data_buf[0]);
+ }
+ } else {
+ dev->command = dev->data_buf[0];
+ DPRINTF("Command %d len %d\n", dev->command, dev->data_len - 1);
+ if (sc->write_data) {
+ sc->write_data(dev, dev->command, dev->data_buf + 1,
+ dev->data_len - 1);
+ }
+ }
+}
+
+static void smbus_i2c_event(I2CSlave *s, enum i2c_event event)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+
+ switch (event) {
+ case I2C_START_SEND:
+ switch (dev->mode) {
+ case SMBUS_IDLE:
+ DPRINTF("Incoming data\n");
+ dev->mode = SMBUS_WRITE_DATA;
+ break;
+ default:
+ BADF("Unexpected send start condition in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ break;
+
+ case I2C_START_RECV:
+ switch (dev->mode) {
+ case SMBUS_IDLE:
+ DPRINTF("Read mode\n");
+ dev->mode = SMBUS_RECV_BYTE;
+ break;
+ case SMBUS_WRITE_DATA:
+ if (dev->data_len == 0) {
+ BADF("Read after write with no data\n");
+ dev->mode = SMBUS_CONFUSED;
+ } else {
+ if (dev->data_len > 1) {
+ smbus_do_write(dev);
+ } else {
+ dev->command = dev->data_buf[0];
+ DPRINTF("%02x: Command %d\n", dev->i2c.address,
+ dev->command);
+ }
+ DPRINTF("Read mode\n");
+ dev->data_len = 0;
+ dev->mode = SMBUS_READ_DATA;
+ }
+ break;
+ default:
+ BADF("Unexpected recv start condition in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ break;
+
+ case I2C_FINISH:
+ switch (dev->mode) {
+ case SMBUS_WRITE_DATA:
+ smbus_do_write(dev);
+ break;
+ case SMBUS_RECV_BYTE:
+ smbus_do_quick_cmd(dev, 1);
+ break;
+ case SMBUS_READ_DATA:
+ BADF("Unexpected stop during receive\n");
+ break;
+ default:
+ /* Nothing to do. */
+ break;
+ }
+ dev->mode = SMBUS_IDLE;
+ dev->data_len = 0;
+ break;
+
+ case I2C_NACK:
+ switch (dev->mode) {
+ case SMBUS_DONE:
+ /* Nothing to do. */
+ break;
+ case SMBUS_READ_DATA:
+ dev->mode = SMBUS_DONE;
+ break;
+ default:
+ BADF("Unexpected NACK in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ }
+}
+
+static int smbus_i2c_recv(I2CSlave *s)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+ int ret;
+
+ switch (dev->mode) {
+ case SMBUS_RECV_BYTE:
+ if (sc->receive_byte) {
+ ret = sc->receive_byte(dev);
+ } else {
+ ret = 0;
+ }
+ DPRINTF("Receive Byte %02x\n", ret);
+ dev->mode = SMBUS_DONE;
+ break;
+ case SMBUS_READ_DATA:
+ if (sc->read_data) {
+ ret = sc->read_data(dev, dev->command, dev->data_len);
+ dev->data_len++;
+ } else {
+ ret = 0;
+ }
+ DPRINTF("Read data %02x\n", ret);
+ break;
+ default:
+ BADF("Unexpected read in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static int smbus_i2c_send(I2CSlave *s, uint8_t data)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+
+ switch (dev->mode) {
+ case SMBUS_WRITE_DATA:
+ DPRINTF("Write data %02x\n", data);
+ dev->data_buf[dev->data_len++] = data;
+ break;
+ default:
+ BADF("Unexpected write in state %d\n", dev->mode);
+ break;
+ }
+ return 0;
+}
+
+static int smbus_device_init(I2CSlave *i2c)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(i2c);
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+
+ return sc->init(dev);
+}
+
+/* Master device commands. */
+int smbus_quick_command(I2CBus *bus, uint8_t addr, int read)
+{
+ if (i2c_start_transfer(bus, addr, read)) {
+ return -1;
+ }
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+int smbus_receive_byte(I2CBus *bus, uint8_t addr)
+{
+ uint8_t data;
+
+ if (i2c_start_transfer(bus, addr, 1)) {
+ return -1;
+ }
+ data = i2c_recv(bus);
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data)
+{
+ if (i2c_start_transfer(bus, addr, 0)) {
+ return -1;
+ }
+ i2c_send(bus, data);
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
+{
+ uint8_t data;
+ if (i2c_start_transfer(bus, addr, 0)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ i2c_start_transfer(bus, addr, 1);
+ data = i2c_recv(bus);
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data)
+{
+ if (i2c_start_transfer(bus, addr, 0)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ i2c_send(bus, data);
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
+{
+ uint16_t data;
+ if (i2c_start_transfer(bus, addr, 0)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ i2c_start_transfer(bus, addr, 1);
+ data = i2c_recv(bus);
+ data |= i2c_recv(bus) << 8;
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+int smbus_write_word(I2CBus *bus, uint8_t addr, uint8_t command, uint16_t data)
+{
+ if (i2c_start_transfer(bus, addr, 0)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ i2c_send(bus, data & 0xff);
+ i2c_send(bus, data >> 8);
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+int smbus_read_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data)
+{
+ int len;
+ int i;
+
+ if (i2c_start_transfer(bus, addr, 0)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ i2c_start_transfer(bus, addr, 1);
+ len = i2c_recv(bus);
+ if (len > 32) {
+ len = 0;
+ }
+ for (i = 0; i < len; i++) {
+ data[i] = i2c_recv(bus);
+ }
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return len;
+}
+
+int smbus_write_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
+ int len)
+{
+ int i;
+
+ if (len > 32)
+ len = 32;
+
+ if (i2c_start_transfer(bus, addr, 0)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ i2c_send(bus, len);
+ for (i = 0; i < len; i++) {
+ i2c_send(bus, data[i]);
+ }
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+static void smbus_device_class_init(ObjectClass *klass, void *data)
+{
+ I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
+
+ sc->init = smbus_device_init;
+ sc->event = smbus_i2c_event;
+ sc->recv = smbus_i2c_recv;
+ sc->send = smbus_i2c_send;
+}
+
+static const TypeInfo smbus_device_type_info = {
+ .name = TYPE_SMBUS_DEVICE,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(SMBusDevice),
+ .abstract = true,
+ .class_size = sizeof(SMBusDeviceClass),
+ .class_init = smbus_device_class_init,
+};
+
+static void smbus_device_register_types(void)
+{
+ type_register_static(&smbus_device_type_info);
+}
+
+type_init(smbus_device_register_types)
diff --git a/hw/i2c/smbus_eeprom.c b/hw/i2c/smbus_eeprom.c
new file mode 100644
index 00000000..72c09cba
--- /dev/null
+++ b/hw/i2c/smbus_eeprom.c
@@ -0,0 +1,158 @@
+/*
+ * QEMU SMBus EEPROM device
+ *
+ * Copyright (c) 2007 Arastra, Inc.
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus.h"
+
+//#define DEBUG
+
+typedef struct SMBusEEPROMDevice {
+ SMBusDevice smbusdev;
+ void *data;
+ uint8_t offset;
+} SMBusEEPROMDevice;
+
+static void eeprom_quick_cmd(SMBusDevice *dev, uint8_t read)
+{
+#ifdef DEBUG
+ printf("eeprom_quick_cmd: addr=0x%02x read=%d\n", dev->i2c.address, read);
+#endif
+}
+
+static void eeprom_send_byte(SMBusDevice *dev, uint8_t val)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
+#ifdef DEBUG
+ printf("eeprom_send_byte: addr=0x%02x val=0x%02x\n",
+ dev->i2c.address, val);
+#endif
+ eeprom->offset = val;
+}
+
+static uint8_t eeprom_receive_byte(SMBusDevice *dev)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
+ uint8_t *data = eeprom->data;
+ uint8_t val = data[eeprom->offset++];
+#ifdef DEBUG
+ printf("eeprom_receive_byte: addr=0x%02x val=0x%02x\n",
+ dev->i2c.address, val);
+#endif
+ return val;
+}
+
+static void eeprom_write_data(SMBusDevice *dev, uint8_t cmd, uint8_t *buf, int len)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
+ int n;
+#ifdef DEBUG
+ printf("eeprom_write_byte: addr=0x%02x cmd=0x%02x val=0x%02x\n",
+ dev->i2c.address, cmd, buf[0]);
+#endif
+ /* A page write operation is not a valid SMBus command.
+ It is a block write without a length byte. Fortunately we
+ get the full block anyway. */
+ /* TODO: Should this set the current location? */
+ if (cmd + len > 256)
+ n = 256 - cmd;
+ else
+ n = len;
+ memcpy(eeprom->data + cmd, buf, n);
+ len -= n;
+ if (len)
+ memcpy(eeprom->data, buf + n, len);
+}
+
+static uint8_t eeprom_read_data(SMBusDevice *dev, uint8_t cmd, int n)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
+ /* If this is the first byte then set the current position. */
+ if (n == 0)
+ eeprom->offset = cmd;
+ /* As with writes, we implement block reads without the
+ SMBus length byte. */
+ return eeprom_receive_byte(dev);
+}
+
+static int smbus_eeprom_initfn(SMBusDevice *dev)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *)dev;
+
+ eeprom->offset = 0;
+ return 0;
+}
+
+static Property smbus_eeprom_properties[] = {
+ DEFINE_PROP_PTR("data", SMBusEEPROMDevice, data),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void smbus_eeprom_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SMBusDeviceClass *sc = SMBUS_DEVICE_CLASS(klass);
+
+ sc->init = smbus_eeprom_initfn;
+ sc->quick_cmd = eeprom_quick_cmd;
+ sc->send_byte = eeprom_send_byte;
+ sc->receive_byte = eeprom_receive_byte;
+ sc->write_data = eeprom_write_data;
+ sc->read_data = eeprom_read_data;
+ dc->props = smbus_eeprom_properties;
+ /* Reason: pointer property "data" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo smbus_eeprom_info = {
+ .name = "smbus-eeprom",
+ .parent = TYPE_SMBUS_DEVICE,
+ .instance_size = sizeof(SMBusEEPROMDevice),
+ .class_init = smbus_eeprom_class_initfn,
+};
+
+static void smbus_eeprom_register_types(void)
+{
+ type_register_static(&smbus_eeprom_info);
+}
+
+type_init(smbus_eeprom_register_types)
+
+void smbus_eeprom_init(I2CBus *smbus, int nb_eeprom,
+ const uint8_t *eeprom_spd, int eeprom_spd_size)
+{
+ int i;
+ uint8_t *eeprom_buf = g_malloc0(8 * 256); /* XXX: make this persistent */
+ if (eeprom_spd_size > 0) {
+ memcpy(eeprom_buf, eeprom_spd, eeprom_spd_size);
+ }
+
+ for (i = 0; i < nb_eeprom; i++) {
+ DeviceState *eeprom;
+ eeprom = qdev_create((BusState *)smbus, "smbus-eeprom");
+ qdev_prop_set_uint8(eeprom, "address", 0x50 + i);
+ qdev_prop_set_ptr(eeprom, "data", eeprom_buf + (i * 256));
+ qdev_init_nofail(eeprom);
+ }
+}
diff --git a/hw/i2c/smbus_ich9.c b/hw/i2c/smbus_ich9.c
new file mode 100644
index 00000000..91d4d322
--- /dev/null
+++ b/hw/i2c/smbus_ich9.c
@@ -0,0 +1,129 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This is based on acpi.c, but heavily rewritten.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ *
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/i2c/pm_smbus.h"
+#include "hw/pci/pci.h"
+#include "sysemu/sysemu.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus.h"
+
+#include "hw/i386/ich9.h"
+
+#define TYPE_ICH9_SMB_DEVICE "ICH9 SMB"
+#define ICH9_SMB_DEVICE(obj) \
+ OBJECT_CHECK(ICH9SMBState, (obj), TYPE_ICH9_SMB_DEVICE)
+
+typedef struct ICH9SMBState {
+ PCIDevice dev;
+
+ PMSMBus smb;
+} ICH9SMBState;
+
+static const VMStateDescription vmstate_ich9_smbus = {
+ .name = "ich9_smb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, struct ICH9SMBState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ich9_smbus_write_config(PCIDevice *d, uint32_t address,
+ uint32_t val, int len)
+{
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+
+ pci_default_write_config(d, address, val, len);
+ if (range_covers_byte(address, len, ICH9_SMB_HOSTC)) {
+ uint8_t hostc = s->dev.config[ICH9_SMB_HOSTC];
+ if ((hostc & ICH9_SMB_HOSTC_HST_EN) &&
+ !(hostc & ICH9_SMB_HOSTC_I2C_EN)) {
+ memory_region_set_enabled(&s->smb.io, true);
+ } else {
+ memory_region_set_enabled(&s->smb.io, false);
+ }
+ }
+}
+
+static void ich9_smbus_realize(PCIDevice *d, Error **errp)
+{
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+
+ /* TODO? D31IP.SMIP in chipset configuration space */
+ pci_config_set_interrupt_pin(d->config, 0x01); /* interrupt pin 1 */
+
+ pci_set_byte(d->config + ICH9_SMB_HOSTC, 0);
+ /* TODO bar0, bar1: 64bit BAR support*/
+
+ pm_smbus_init(&d->qdev, &s->smb);
+ pci_register_bar(d, ICH9_SMB_SMB_BASE_BAR, PCI_BASE_ADDRESS_SPACE_IO,
+ &s->smb.io);
+}
+
+static void ich9_smb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_ICH9_6;
+ k->revision = ICH9_A2_SMB_REVISION;
+ k->class_id = PCI_CLASS_SERIAL_SMBUS;
+ dc->vmsd = &vmstate_ich9_smbus;
+ dc->desc = "ICH9 SMBUS Bridge";
+ k->realize = ich9_smbus_realize;
+ k->config_write = ich9_smbus_write_config;
+ /*
+ * Reason: part of ICH9 southbridge, needs to be wired up by
+ * pc_q35_init()
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+I2CBus *ich9_smb_init(PCIBus *bus, int devfn, uint32_t smb_io_base)
+{
+ PCIDevice *d =
+ pci_create_simple_multifunction(bus, devfn, true, TYPE_ICH9_SMB_DEVICE);
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+ return s->smb.smbus;
+}
+
+static const TypeInfo ich9_smb_info = {
+ .name = TYPE_ICH9_SMB_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(ICH9SMBState),
+ .class_init = ich9_smb_class_init,
+};
+
+static void ich9_smb_register(void)
+{
+ type_register_static(&ich9_smb_info);
+}
+
+type_init(ich9_smb_register);
diff --git a/hw/i2c/versatile_i2c.c b/hw/i2c/versatile_i2c.c
new file mode 100644
index 00000000..3c0c2c10
--- /dev/null
+++ b/hw/i2c/versatile_i2c.c
@@ -0,0 +1,113 @@
+/*
+ * ARM Versatile I2C controller
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Copyright (c) 2012 Oskar Andero <oskar.andero@gmail.com>
+ *
+ * This file is derived from hw/realview.c by Paul Brook
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/sysbus.h"
+#include "bitbang_i2c.h"
+
+#define TYPE_VERSATILE_I2C "versatile_i2c"
+#define VERSATILE_I2C(obj) \
+ OBJECT_CHECK(VersatileI2CState, (obj), TYPE_VERSATILE_I2C)
+
+typedef struct VersatileI2CState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ bitbang_i2c_interface *bitbang;
+ int out;
+ int in;
+} VersatileI2CState;
+
+static uint64_t versatile_i2c_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ VersatileI2CState *s = (VersatileI2CState *)opaque;
+
+ if (offset == 0) {
+ return (s->out & 1) | (s->in << 1);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ return -1;
+ }
+}
+
+static void versatile_i2c_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ VersatileI2CState *s = (VersatileI2CState *)opaque;
+
+ switch (offset) {
+ case 0:
+ s->out |= value & 3;
+ break;
+ case 4:
+ s->out &= ~value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ }
+ bitbang_i2c_set(s->bitbang, BITBANG_I2C_SCL, (s->out & 1) != 0);
+ s->in = bitbang_i2c_set(s->bitbang, BITBANG_I2C_SDA, (s->out & 2) != 0);
+}
+
+static const MemoryRegionOps versatile_i2c_ops = {
+ .read = versatile_i2c_read,
+ .write = versatile_i2c_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int versatile_i2c_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ VersatileI2CState *s = VERSATILE_I2C(dev);
+ I2CBus *bus;
+
+ bus = i2c_init_bus(dev, "i2c");
+ s->bitbang = bitbang_i2c_init(bus);
+ memory_region_init_io(&s->iomem, OBJECT(s), &versatile_i2c_ops, s,
+ "versatile_i2c", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ return 0;
+}
+
+static void versatile_i2c_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = versatile_i2c_init;
+}
+
+static const TypeInfo versatile_i2c_info = {
+ .name = TYPE_VERSATILE_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(VersatileI2CState),
+ .class_init = versatile_i2c_class_init,
+};
+
+static void versatile_i2c_register_types(void)
+{
+ type_register_static(&versatile_i2c_info);
+}
+
+type_init(versatile_i2c_register_types)
diff --git a/hw/i386/Makefile.objs b/hw/i386/Makefile.objs
new file mode 100644
index 00000000..bd4f147f
--- /dev/null
+++ b/hw/i386/Makefile.objs
@@ -0,0 +1,33 @@
+obj-$(CONFIG_KVM) += kvm/
+obj-y += multiboot.o smbios.o
+obj-y += pc.o pc_piix.o pc_q35.o
+obj-y += pc_sysfw.o
+obj-y += intel_iommu.o
+obj-$(CONFIG_XEN) += ../xenpv/ xen/
+
+obj-y += kvmvapic.o
+obj-y += acpi-build.o
+hw/i386/acpi-build.o: hw/i386/acpi-build.c \
+ hw/i386/acpi-dsdt.hex hw/i386/q35-acpi-dsdt.hex
+
+iasl-option=$(shell if test -z "`$(1) $(2) 2>&1 > /dev/null`" \
+ ; then echo "$(2)"; else echo "$(3)"; fi ;)
+
+ifdef IASL
+#IASL Present. Generate hex files from .dsl
+hw/i386/%.hex: $(SRC_PATH)/hw/i386/%.dsl $(SRC_PATH)/scripts/acpi_extract_preprocess.py $(SRC_PATH)/scripts/acpi_extract.py
+ $(call quiet-command, $(CPP) -x c -P $(QEMU_DGFLAGS) $(QEMU_INCLUDES) $< -o $*.dsl.i.orig, " CPP $(TARGET_DIR)$*.dsl.i.orig")
+ $(call quiet-command, $(PYTHON) $(SRC_PATH)/scripts/acpi_extract_preprocess.py $*.dsl.i.orig > $*.dsl.i, " ACPI_PREPROCESS $(TARGET_DIR)$*.dsl.i")
+ $(call quiet-command, $(IASL) $(call iasl-option,$(IASL),-Pn,) -vs -l -tc -p $* $*.dsl.i $(if $(V), , > /dev/null) 2>&1 ," IASL $(TARGET_DIR)$*.dsl.i")
+ $(call quiet-command, $(PYTHON) $(SRC_PATH)/scripts/acpi_extract.py $*.lst > $*.off, " ACPI_EXTRACT $(TARGET_DIR)$*.off")
+ $(call quiet-command, cat $*.off > $@, " CAT $(TARGET_DIR)$@")
+else
+#IASL Not present. Restore pre-generated hex files.
+hw/i386/%.hex: $(SRC_PATH)/hw/i386/%.hex.generated
+ $(call quiet-command, cp -f $< $@, " CP $(TARGET_DIR)$@")
+endif
+
+.PHONY: cleanhex
+cleanhex:
+ rm -f hw/i386/*hex
+clean: cleanhex
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
new file mode 100644
index 00000000..46eddb8e
--- /dev/null
+++ b/hw/i386/acpi-build.c
@@ -0,0 +1,1939 @@
+/* Support for generating ACPI tables and passing them to Guests
+ *
+ * Copyright (C) 2008-2010 Kevin O'Connor <kevin@koconnor.net>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2013 Red Hat Inc
+ *
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "acpi-build.h"
+#include <stddef.h>
+#include <glib.h>
+#include "qemu-common.h"
+#include "qemu/bitmap.h"
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "hw/pci/pci.h"
+#include "qom/cpu.h"
+#include "hw/i386/pc.h"
+#include "target-i386/cpu.h"
+#include "hw/timer/hpet.h"
+#include "hw/acpi/acpi-defs.h"
+#include "hw/acpi/acpi.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/acpi/bios-linker-loader.h"
+#include "hw/loader.h"
+#include "hw/isa/isa.h"
+#include "hw/acpi/memory_hotplug.h"
+#include "sysemu/tpm.h"
+#include "hw/acpi/tpm.h"
+#include "sysemu/tpm_backend.h"
+
+/* Supported chipsets: */
+#include "hw/acpi/piix4.h"
+#include "hw/acpi/pcihp.h"
+#include "hw/i386/ich9.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci-host/q35.h"
+#include "hw/i386/intel_iommu.h"
+
+#include "hw/i386/q35-acpi-dsdt.hex"
+#include "hw/i386/acpi-dsdt.hex"
+
+#include "hw/acpi/aml-build.h"
+
+#include "qapi/qmp/qint.h"
+#include "qom/qom-qobject.h"
+
+/* These are used to size the ACPI tables for -M pc-i440fx-1.7 and
+ * -M pc-i440fx-2.0. Even if the actual amount of AML generated grows
+ * a little bit, there should be plenty of free space since the DSDT
+ * shrunk by ~1.5k between QEMU 2.0 and QEMU 2.1.
+ */
+#define ACPI_BUILD_LEGACY_CPU_AML_SIZE 97
+#define ACPI_BUILD_ALIGN_SIZE 0x1000
+
+#define ACPI_BUILD_TABLE_SIZE 0x20000
+
+/* #define DEBUG_ACPI_BUILD */
+#ifdef DEBUG_ACPI_BUILD
+#define ACPI_BUILD_DPRINTF(fmt, ...) \
+ do {printf("ACPI_BUILD: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define ACPI_BUILD_DPRINTF(fmt, ...)
+#endif
+
+typedef struct AcpiCpuInfo {
+ DECLARE_BITMAP(found_cpus, ACPI_CPU_HOTPLUG_ID_LIMIT);
+} AcpiCpuInfo;
+
+typedef struct AcpiMcfgInfo {
+ uint64_t mcfg_base;
+ uint32_t mcfg_size;
+} AcpiMcfgInfo;
+
+typedef struct AcpiPmInfo {
+ bool s3_disabled;
+ bool s4_disabled;
+ bool pcihp_bridge_en;
+ uint8_t s4_val;
+ uint16_t sci_int;
+ uint8_t acpi_enable_cmd;
+ uint8_t acpi_disable_cmd;
+ uint32_t gpe0_blk;
+ uint32_t gpe0_blk_len;
+ uint32_t io_base;
+ uint16_t cpu_hp_io_base;
+ uint16_t cpu_hp_io_len;
+ uint16_t mem_hp_io_base;
+ uint16_t mem_hp_io_len;
+ uint16_t pcihp_io_base;
+ uint16_t pcihp_io_len;
+} AcpiPmInfo;
+
+typedef struct AcpiMiscInfo {
+ bool has_hpet;
+ TPMVersion tpm_version;
+ const unsigned char *dsdt_code;
+ unsigned dsdt_size;
+ uint16_t pvpanic_port;
+ uint16_t applesmc_io_base;
+} AcpiMiscInfo;
+
+typedef struct AcpiBuildPciBusHotplugState {
+ GArray *device_table;
+ GArray *notify_table;
+ struct AcpiBuildPciBusHotplugState *parent;
+ bool pcihp_bridge_en;
+} AcpiBuildPciBusHotplugState;
+
+static void acpi_get_dsdt(AcpiMiscInfo *info)
+{
+ Object *piix = piix4_pm_find();
+ Object *lpc = ich9_lpc_find();
+ assert(!!piix != !!lpc);
+
+ if (piix) {
+ info->dsdt_code = AcpiDsdtAmlCode;
+ info->dsdt_size = sizeof AcpiDsdtAmlCode;
+ }
+ if (lpc) {
+ info->dsdt_code = Q35AcpiDsdtAmlCode;
+ info->dsdt_size = sizeof Q35AcpiDsdtAmlCode;
+ }
+}
+
+static
+int acpi_add_cpu_info(Object *o, void *opaque)
+{
+ AcpiCpuInfo *cpu = opaque;
+ uint64_t apic_id;
+
+ if (object_dynamic_cast(o, TYPE_CPU)) {
+ apic_id = object_property_get_int(o, "apic-id", NULL);
+ assert(apic_id < ACPI_CPU_HOTPLUG_ID_LIMIT);
+
+ set_bit(apic_id, cpu->found_cpus);
+ }
+
+ object_child_foreach(o, acpi_add_cpu_info, opaque);
+ return 0;
+}
+
+static void acpi_get_cpu_info(AcpiCpuInfo *cpu)
+{
+ Object *root = object_get_root();
+
+ memset(cpu->found_cpus, 0, sizeof cpu->found_cpus);
+ object_child_foreach(root, acpi_add_cpu_info, cpu);
+}
+
+static void acpi_get_pm_info(AcpiPmInfo *pm)
+{
+ Object *piix = piix4_pm_find();
+ Object *lpc = ich9_lpc_find();
+ Object *obj = NULL;
+ QObject *o;
+
+ pm->pcihp_io_base = 0;
+ pm->pcihp_io_len = 0;
+ if (piix) {
+ obj = piix;
+ pm->cpu_hp_io_base = PIIX4_CPU_HOTPLUG_IO_BASE;
+ pm->pcihp_io_base =
+ object_property_get_int(obj, ACPI_PCIHP_IO_BASE_PROP, NULL);
+ pm->pcihp_io_len =
+ object_property_get_int(obj, ACPI_PCIHP_IO_LEN_PROP, NULL);
+ }
+ if (lpc) {
+ obj = lpc;
+ pm->cpu_hp_io_base = ICH9_CPU_HOTPLUG_IO_BASE;
+ }
+ assert(obj);
+
+ pm->cpu_hp_io_len = ACPI_GPE_PROC_LEN;
+ pm->mem_hp_io_base = ACPI_MEMORY_HOTPLUG_BASE;
+ pm->mem_hp_io_len = ACPI_MEMORY_HOTPLUG_IO_LEN;
+
+ /* Fill in optional s3/s4 related properties */
+ o = object_property_get_qobject(obj, ACPI_PM_PROP_S3_DISABLED, NULL);
+ if (o) {
+ pm->s3_disabled = qint_get_int(qobject_to_qint(o));
+ } else {
+ pm->s3_disabled = false;
+ }
+ qobject_decref(o);
+ o = object_property_get_qobject(obj, ACPI_PM_PROP_S4_DISABLED, NULL);
+ if (o) {
+ pm->s4_disabled = qint_get_int(qobject_to_qint(o));
+ } else {
+ pm->s4_disabled = false;
+ }
+ qobject_decref(o);
+ o = object_property_get_qobject(obj, ACPI_PM_PROP_S4_VAL, NULL);
+ if (o) {
+ pm->s4_val = qint_get_int(qobject_to_qint(o));
+ } else {
+ pm->s4_val = false;
+ }
+ qobject_decref(o);
+
+ /* Fill in mandatory properties */
+ pm->sci_int = object_property_get_int(obj, ACPI_PM_PROP_SCI_INT, NULL);
+
+ pm->acpi_enable_cmd = object_property_get_int(obj,
+ ACPI_PM_PROP_ACPI_ENABLE_CMD,
+ NULL);
+ pm->acpi_disable_cmd = object_property_get_int(obj,
+ ACPI_PM_PROP_ACPI_DISABLE_CMD,
+ NULL);
+ pm->io_base = object_property_get_int(obj, ACPI_PM_PROP_PM_IO_BASE,
+ NULL);
+ pm->gpe0_blk = object_property_get_int(obj, ACPI_PM_PROP_GPE0_BLK,
+ NULL);
+ pm->gpe0_blk_len = object_property_get_int(obj, ACPI_PM_PROP_GPE0_BLK_LEN,
+ NULL);
+ pm->pcihp_bridge_en =
+ object_property_get_bool(obj, "acpi-pci-hotplug-with-bridge-support",
+ NULL);
+}
+
+static void acpi_get_misc_info(AcpiMiscInfo *info)
+{
+ info->has_hpet = hpet_find();
+ info->tpm_version = tpm_get_version();
+ info->pvpanic_port = pvpanic_port();
+ info->applesmc_io_base = applesmc_port();
+}
+
+/*
+ * Because of the PXB hosts we cannot simply query TYPE_PCI_HOST_BRIDGE.
+ * On i386 arch we only have two pci hosts, so we can look only for them.
+ */
+static Object *acpi_get_i386_pci_host(void)
+{
+ PCIHostState *host;
+
+ host = OBJECT_CHECK(PCIHostState,
+ object_resolve_path("/machine/i440fx", NULL),
+ TYPE_PCI_HOST_BRIDGE);
+ if (!host) {
+ host = OBJECT_CHECK(PCIHostState,
+ object_resolve_path("/machine/q35", NULL),
+ TYPE_PCI_HOST_BRIDGE);
+ }
+
+ return OBJECT(host);
+}
+
+static void acpi_get_pci_info(PcPciInfo *info)
+{
+ Object *pci_host;
+
+
+ pci_host = acpi_get_i386_pci_host();
+ g_assert(pci_host);
+
+ info->w32.begin = object_property_get_int(pci_host,
+ PCI_HOST_PROP_PCI_HOLE_START,
+ NULL);
+ info->w32.end = object_property_get_int(pci_host,
+ PCI_HOST_PROP_PCI_HOLE_END,
+ NULL);
+ info->w64.begin = object_property_get_int(pci_host,
+ PCI_HOST_PROP_PCI_HOLE64_START,
+ NULL);
+ info->w64.end = object_property_get_int(pci_host,
+ PCI_HOST_PROP_PCI_HOLE64_END,
+ NULL);
+}
+
+#define ACPI_PORT_SMI_CMD 0x00b2 /* TODO: this is APM_CNT_IOPORT */
+
+static void acpi_align_size(GArray *blob, unsigned align)
+{
+ /* Align size to multiple of given size. This reduces the chance
+ * we need to change size in the future (breaking cross version migration).
+ */
+ g_array_set_size(blob, ROUND_UP(acpi_data_len(blob), align));
+}
+
+/* FACS */
+static void
+build_facs(GArray *table_data, GArray *linker, PcGuestInfo *guest_info)
+{
+ AcpiFacsDescriptorRev1 *facs = acpi_data_push(table_data, sizeof *facs);
+ memcpy(&facs->signature, "FACS", 4);
+ facs->length = cpu_to_le32(sizeof(*facs));
+}
+
+/* Load chipset information in FADT */
+static void fadt_setup(AcpiFadtDescriptorRev1 *fadt, AcpiPmInfo *pm)
+{
+ fadt->model = 1;
+ fadt->reserved1 = 0;
+ fadt->sci_int = cpu_to_le16(pm->sci_int);
+ fadt->smi_cmd = cpu_to_le32(ACPI_PORT_SMI_CMD);
+ fadt->acpi_enable = pm->acpi_enable_cmd;
+ fadt->acpi_disable = pm->acpi_disable_cmd;
+ /* EVT, CNT, TMR offset matches hw/acpi/core.c */
+ fadt->pm1a_evt_blk = cpu_to_le32(pm->io_base);
+ fadt->pm1a_cnt_blk = cpu_to_le32(pm->io_base + 0x04);
+ fadt->pm_tmr_blk = cpu_to_le32(pm->io_base + 0x08);
+ fadt->gpe0_blk = cpu_to_le32(pm->gpe0_blk);
+ /* EVT, CNT, TMR length matches hw/acpi/core.c */
+ fadt->pm1_evt_len = 4;
+ fadt->pm1_cnt_len = 2;
+ fadt->pm_tmr_len = 4;
+ fadt->gpe0_blk_len = pm->gpe0_blk_len;
+ fadt->plvl2_lat = cpu_to_le16(0xfff); /* C2 state not supported */
+ fadt->plvl3_lat = cpu_to_le16(0xfff); /* C3 state not supported */
+ fadt->flags = cpu_to_le32((1 << ACPI_FADT_F_WBINVD) |
+ (1 << ACPI_FADT_F_PROC_C1) |
+ (1 << ACPI_FADT_F_SLP_BUTTON) |
+ (1 << ACPI_FADT_F_RTC_S4));
+ fadt->flags |= cpu_to_le32(1 << ACPI_FADT_F_USE_PLATFORM_CLOCK);
+ /* APIC destination mode ("Flat Logical") has an upper limit of 8 CPUs
+ * For more than 8 CPUs, "Clustered Logical" mode has to be used
+ */
+ if (max_cpus > 8) {
+ fadt->flags |= cpu_to_le32(1 << ACPI_FADT_F_FORCE_APIC_CLUSTER_MODEL);
+ }
+}
+
+
+/* FADT */
+static void
+build_fadt(GArray *table_data, GArray *linker, AcpiPmInfo *pm,
+ unsigned facs, unsigned dsdt)
+{
+ AcpiFadtDescriptorRev1 *fadt = acpi_data_push(table_data, sizeof(*fadt));
+
+ fadt->firmware_ctrl = cpu_to_le32(facs);
+ /* FACS address to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE,
+ ACPI_BUILD_TABLE_FILE,
+ table_data, &fadt->firmware_ctrl,
+ sizeof fadt->firmware_ctrl);
+
+ fadt->dsdt = cpu_to_le32(dsdt);
+ /* DSDT address to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE,
+ ACPI_BUILD_TABLE_FILE,
+ table_data, &fadt->dsdt,
+ sizeof fadt->dsdt);
+
+ fadt_setup(fadt, pm);
+
+ build_header(linker, table_data,
+ (void *)fadt, "FACP", sizeof(*fadt), 1);
+}
+
+static void
+build_madt(GArray *table_data, GArray *linker, AcpiCpuInfo *cpu,
+ PcGuestInfo *guest_info)
+{
+ int madt_start = table_data->len;
+
+ AcpiMultipleApicTable *madt;
+ AcpiMadtIoApic *io_apic;
+ AcpiMadtIntsrcovr *intsrcovr;
+ AcpiMadtLocalNmi *local_nmi;
+ int i;
+
+ madt = acpi_data_push(table_data, sizeof *madt);
+ madt->local_apic_address = cpu_to_le32(APIC_DEFAULT_ADDRESS);
+ madt->flags = cpu_to_le32(1);
+
+ for (i = 0; i < guest_info->apic_id_limit; i++) {
+ AcpiMadtProcessorApic *apic = acpi_data_push(table_data, sizeof *apic);
+ apic->type = ACPI_APIC_PROCESSOR;
+ apic->length = sizeof(*apic);
+ apic->processor_id = i;
+ apic->local_apic_id = i;
+ if (test_bit(i, cpu->found_cpus)) {
+ apic->flags = cpu_to_le32(1);
+ } else {
+ apic->flags = cpu_to_le32(0);
+ }
+ }
+ io_apic = acpi_data_push(table_data, sizeof *io_apic);
+ io_apic->type = ACPI_APIC_IO;
+ io_apic->length = sizeof(*io_apic);
+#define ACPI_BUILD_IOAPIC_ID 0x0
+ io_apic->io_apic_id = ACPI_BUILD_IOAPIC_ID;
+ io_apic->address = cpu_to_le32(IO_APIC_DEFAULT_ADDRESS);
+ io_apic->interrupt = cpu_to_le32(0);
+
+ if (guest_info->apic_xrupt_override) {
+ intsrcovr = acpi_data_push(table_data, sizeof *intsrcovr);
+ intsrcovr->type = ACPI_APIC_XRUPT_OVERRIDE;
+ intsrcovr->length = sizeof(*intsrcovr);
+ intsrcovr->source = 0;
+ intsrcovr->gsi = cpu_to_le32(2);
+ intsrcovr->flags = cpu_to_le16(0); /* conforms to bus specifications */
+ }
+ for (i = 1; i < 16; i++) {
+#define ACPI_BUILD_PCI_IRQS ((1<<5) | (1<<9) | (1<<10) | (1<<11))
+ if (!(ACPI_BUILD_PCI_IRQS & (1 << i))) {
+ /* No need for a INT source override structure. */
+ continue;
+ }
+ intsrcovr = acpi_data_push(table_data, sizeof *intsrcovr);
+ intsrcovr->type = ACPI_APIC_XRUPT_OVERRIDE;
+ intsrcovr->length = sizeof(*intsrcovr);
+ intsrcovr->source = i;
+ intsrcovr->gsi = cpu_to_le32(i);
+ intsrcovr->flags = cpu_to_le16(0xd); /* active high, level triggered */
+ }
+
+ local_nmi = acpi_data_push(table_data, sizeof *local_nmi);
+ local_nmi->type = ACPI_APIC_LOCAL_NMI;
+ local_nmi->length = sizeof(*local_nmi);
+ local_nmi->processor_id = 0xff; /* all processors */
+ local_nmi->flags = cpu_to_le16(0);
+ local_nmi->lint = 1; /* ACPI_LINT1 */
+
+ build_header(linker, table_data,
+ (void *)(table_data->data + madt_start), "APIC",
+ table_data->len - madt_start, 1);
+}
+
+/* Assign BSEL property to all buses. In the future, this can be changed
+ * to only assign to buses that support hotplug.
+ */
+static void *acpi_set_bsel(PCIBus *bus, void *opaque)
+{
+ unsigned *bsel_alloc = opaque;
+ unsigned *bus_bsel;
+
+ if (qbus_is_hotpluggable(BUS(bus))) {
+ bus_bsel = g_malloc(sizeof *bus_bsel);
+
+ *bus_bsel = (*bsel_alloc)++;
+ object_property_add_uint32_ptr(OBJECT(bus), ACPI_PCIHP_PROP_BSEL,
+ bus_bsel, NULL);
+ }
+
+ return bsel_alloc;
+}
+
+static void acpi_set_pci_info(void)
+{
+ PCIBus *bus = find_i440fx(); /* TODO: Q35 support */
+ unsigned bsel_alloc = 0;
+
+ if (bus) {
+ /* Scan all PCI buses. Set property to enable acpi based hotplug. */
+ pci_for_each_bus_depth_first(bus, acpi_set_bsel, NULL, &bsel_alloc);
+ }
+}
+
+static void build_append_pcihp_notify_entry(Aml *method, int slot)
+{
+ Aml *if_ctx;
+ int32_t devfn = PCI_DEVFN(slot, 0);
+
+ if_ctx = aml_if(aml_and(aml_arg(0), aml_int(0x1U << slot)));
+ aml_append(if_ctx, aml_notify(aml_name("S%.02X", devfn), aml_arg(1)));
+ aml_append(method, if_ctx);
+}
+
+static void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus,
+ bool pcihp_bridge_en)
+{
+ Aml *dev, *notify_method, *method;
+ QObject *bsel;
+ PCIBus *sec;
+ int i;
+
+ bsel = object_property_get_qobject(OBJECT(bus), ACPI_PCIHP_PROP_BSEL, NULL);
+ if (bsel) {
+ int64_t bsel_val = qint_get_int(qobject_to_qint(bsel));
+
+ aml_append(parent_scope, aml_name_decl("BSEL", aml_int(bsel_val)));
+ notify_method = aml_method("DVNT", 2);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bus->devices); i += PCI_FUNC_MAX) {
+ DeviceClass *dc;
+ PCIDeviceClass *pc;
+ PCIDevice *pdev = bus->devices[i];
+ int slot = PCI_SLOT(i);
+ bool hotplug_enabled_dev;
+ bool bridge_in_acpi;
+
+ if (!pdev) {
+ if (bsel) { /* add hotplug slots for non present devices */
+ dev = aml_device("S%.02X", PCI_DEVFN(slot, 0));
+ aml_append(dev, aml_name_decl("_SUN", aml_int(slot)));
+ aml_append(dev, aml_name_decl("_ADR", aml_int(slot << 16)));
+ method = aml_method("_EJ0", 1);
+ aml_append(method,
+ aml_call2("PCEJ", aml_name("BSEL"), aml_name("_SUN"))
+ );
+ aml_append(dev, method);
+ aml_append(parent_scope, dev);
+
+ build_append_pcihp_notify_entry(notify_method, slot);
+ }
+ continue;
+ }
+
+ pc = PCI_DEVICE_GET_CLASS(pdev);
+ dc = DEVICE_GET_CLASS(pdev);
+
+ /* When hotplug for bridges is enabled, bridges are
+ * described in ACPI separately (see build_pci_bus_end).
+ * In this case they aren't themselves hot-pluggable.
+ * Hotplugged bridges *are* hot-pluggable.
+ */
+ bridge_in_acpi = pc->is_bridge && pcihp_bridge_en &&
+ !DEVICE(pdev)->hotplugged;
+
+ hotplug_enabled_dev = bsel && dc->hotpluggable && !bridge_in_acpi;
+
+ if (pc->class_id == PCI_CLASS_BRIDGE_ISA) {
+ continue;
+ }
+
+ /* start to compose PCI slot descriptor */
+ dev = aml_device("S%.02X", PCI_DEVFN(slot, 0));
+ aml_append(dev, aml_name_decl("_ADR", aml_int(slot << 16)));
+
+ if (pc->class_id == PCI_CLASS_DISPLAY_VGA) {
+ /* add VGA specific AML methods */
+ int s3d;
+
+ if (object_dynamic_cast(OBJECT(pdev), "qxl-vga")) {
+ s3d = 3;
+ } else {
+ s3d = 0;
+ }
+
+ method = aml_method("_S1D", 0);
+ aml_append(method, aml_return(aml_int(0)));
+ aml_append(dev, method);
+
+ method = aml_method("_S2D", 0);
+ aml_append(method, aml_return(aml_int(0)));
+ aml_append(dev, method);
+
+ method = aml_method("_S3D", 0);
+ aml_append(method, aml_return(aml_int(s3d)));
+ aml_append(dev, method);
+ } else if (hotplug_enabled_dev) {
+ /* add _SUN/_EJ0 to make slot hotpluggable */
+ aml_append(dev, aml_name_decl("_SUN", aml_int(slot)));
+
+ method = aml_method("_EJ0", 1);
+ aml_append(method,
+ aml_call2("PCEJ", aml_name("BSEL"), aml_name("_SUN"))
+ );
+ aml_append(dev, method);
+
+ if (bsel) {
+ build_append_pcihp_notify_entry(notify_method, slot);
+ }
+ } else if (bridge_in_acpi) {
+ /*
+ * device is coldplugged bridge,
+ * add child device descriptions into its scope
+ */
+ PCIBus *sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(pdev));
+
+ build_append_pci_bus_devices(dev, sec_bus, pcihp_bridge_en);
+ }
+ /* slot descriptor has been composed, add it into parent context */
+ aml_append(parent_scope, dev);
+ }
+
+ if (bsel) {
+ aml_append(parent_scope, notify_method);
+ }
+
+ /* Append PCNT method to notify about events on local and child buses.
+ * Add unconditionally for root since DSDT expects it.
+ */
+ method = aml_method("PCNT", 0);
+
+ /* If bus supports hotplug select it and notify about local events */
+ if (bsel) {
+ int64_t bsel_val = qint_get_int(qobject_to_qint(bsel));
+ aml_append(method, aml_store(aml_int(bsel_val), aml_name("BNUM")));
+ aml_append(method,
+ aml_call2("DVNT", aml_name("PCIU"), aml_int(1) /* Device Check */)
+ );
+ aml_append(method,
+ aml_call2("DVNT", aml_name("PCID"), aml_int(3)/* Eject Request */)
+ );
+ }
+
+ /* Notify about child bus events in any case */
+ if (pcihp_bridge_en) {
+ QLIST_FOREACH(sec, &bus->child, sibling) {
+ int32_t devfn = sec->parent_dev->devfn;
+
+ aml_append(method, aml_name("^S%.02X.PCNT", devfn));
+ }
+ }
+ aml_append(parent_scope, method);
+ qobject_decref(bsel);
+}
+
+/*
+ * initialize_route - Initialize the interrupt routing rule
+ * through a specific LINK:
+ * if (lnk_idx == idx)
+ * route using link 'link_name'
+ */
+static Aml *initialize_route(Aml *route, const char *link_name,
+ Aml *lnk_idx, int idx)
+{
+ Aml *if_ctx = aml_if(aml_equal(lnk_idx, aml_int(idx)));
+ Aml *pkg = aml_package(4);
+
+ aml_append(pkg, aml_int(0));
+ aml_append(pkg, aml_int(0));
+ aml_append(pkg, aml_name("%s", link_name));
+ aml_append(pkg, aml_int(0));
+ aml_append(if_ctx, aml_store(pkg, route));
+
+ return if_ctx;
+}
+
+/*
+ * build_prt - Define interrupt rounting rules
+ *
+ * Returns an array of 128 routes, one for each device,
+ * based on device location.
+ * The main goal is to equaly distribute the interrupts
+ * over the 4 existing ACPI links (works only for i440fx).
+ * The hash function is (slot + pin) & 3 -> "LNK[D|A|B|C]".
+ *
+ */
+static Aml *build_prt(void)
+{
+ Aml *method, *while_ctx, *pin, *res;
+
+ method = aml_method("_PRT", 0);
+ res = aml_local(0);
+ pin = aml_local(1);
+ aml_append(method, aml_store(aml_package(128), res));
+ aml_append(method, aml_store(aml_int(0), pin));
+
+ /* while (pin < 128) */
+ while_ctx = aml_while(aml_lless(pin, aml_int(128)));
+ {
+ Aml *slot = aml_local(2);
+ Aml *lnk_idx = aml_local(3);
+ Aml *route = aml_local(4);
+
+ /* slot = pin >> 2 */
+ aml_append(while_ctx,
+ aml_store(aml_shiftright(pin, aml_int(2)), slot));
+ /* lnk_idx = (slot + pin) & 3 */
+ aml_append(while_ctx,
+ aml_store(aml_and(aml_add(pin, slot), aml_int(3)), lnk_idx));
+
+ /* route[2] = "LNK[D|A|B|C]", selection based on pin % 3 */
+ aml_append(while_ctx, initialize_route(route, "LNKD", lnk_idx, 0));
+ aml_append(while_ctx, initialize_route(route, "LNKA", lnk_idx, 1));
+ aml_append(while_ctx, initialize_route(route, "LNKB", lnk_idx, 2));
+ aml_append(while_ctx, initialize_route(route, "LNKC", lnk_idx, 3));
+
+ /* route[0] = 0x[slot]FFFF */
+ aml_append(while_ctx,
+ aml_store(aml_or(aml_shiftleft(slot, aml_int(16)), aml_int(0xFFFF)),
+ aml_index(route, aml_int(0))));
+ /* route[1] = pin & 3 */
+ aml_append(while_ctx,
+ aml_store(aml_and(pin, aml_int(3)), aml_index(route, aml_int(1))));
+ /* res[pin] = route */
+ aml_append(while_ctx, aml_store(route, aml_index(res, pin)));
+ /* pin++ */
+ aml_append(while_ctx, aml_increment(pin));
+ }
+ aml_append(method, while_ctx);
+ /* return res*/
+ aml_append(method, aml_return(res));
+
+ return method;
+}
+
+typedef struct CrsRangeEntry {
+ uint64_t base;
+ uint64_t limit;
+} CrsRangeEntry;
+
+static void crs_range_insert(GPtrArray *ranges, uint64_t base, uint64_t limit)
+{
+ CrsRangeEntry *entry;
+
+ entry = g_malloc(sizeof(*entry));
+ entry->base = base;
+ entry->limit = limit;
+
+ g_ptr_array_add(ranges, entry);
+}
+
+static void crs_range_free(gpointer data)
+{
+ CrsRangeEntry *entry = (CrsRangeEntry *)data;
+ g_free(entry);
+}
+
+static gint crs_range_compare(gconstpointer a, gconstpointer b)
+{
+ CrsRangeEntry *entry_a = *(CrsRangeEntry **)a;
+ CrsRangeEntry *entry_b = *(CrsRangeEntry **)b;
+
+ return (int64_t)entry_a->base - (int64_t)entry_b->base;
+}
+
+/*
+ * crs_replace_with_free_ranges - given the 'used' ranges within [start - end]
+ * interval, computes the 'free' ranges from the same interval.
+ * Example: If the input array is { [a1 - a2],[b1 - b2] }, the function
+ * will return { [base - a1], [a2 - b1], [b2 - limit] }.
+ */
+static void crs_replace_with_free_ranges(GPtrArray *ranges,
+ uint64_t start, uint64_t end)
+{
+ GPtrArray *free_ranges = g_ptr_array_new_with_free_func(crs_range_free);
+ uint64_t free_base = start;
+ int i;
+
+ g_ptr_array_sort(ranges, crs_range_compare);
+ for (i = 0; i < ranges->len; i++) {
+ CrsRangeEntry *used = g_ptr_array_index(ranges, i);
+
+ if (free_base < used->base) {
+ crs_range_insert(free_ranges, free_base, used->base - 1);
+ }
+
+ free_base = used->limit + 1;
+ }
+
+ if (free_base < end) {
+ crs_range_insert(free_ranges, free_base, end);
+ }
+
+ g_ptr_array_set_size(ranges, 0);
+ for (i = 0; i < free_ranges->len; i++) {
+ g_ptr_array_add(ranges, g_ptr_array_index(free_ranges, i));
+ }
+
+ g_ptr_array_free(free_ranges, false);
+}
+
+static Aml *build_crs(PCIHostState *host,
+ GPtrArray *io_ranges, GPtrArray *mem_ranges)
+{
+ Aml *crs = aml_resource_template();
+ uint8_t max_bus = pci_bus_num(host->bus);
+ uint8_t type;
+ int devfn;
+
+ for (devfn = 0; devfn < ARRAY_SIZE(host->bus->devices); devfn++) {
+ int i;
+ uint64_t range_base, range_limit;
+ PCIDevice *dev = host->bus->devices[devfn];
+
+ if (!dev) {
+ continue;
+ }
+
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ PCIIORegion *r = &dev->io_regions[i];
+
+ range_base = r->addr;
+ range_limit = r->addr + r->size - 1;
+
+ /*
+ * Work-around for old bioses
+ * that do not support multiple root buses
+ */
+ if (!range_base || range_base > range_limit) {
+ continue;
+ }
+
+ if (r->type & PCI_BASE_ADDRESS_SPACE_IO) {
+ aml_append(crs,
+ aml_word_io(AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_POS_DECODE, AML_ENTIRE_RANGE,
+ 0,
+ range_base,
+ range_limit,
+ 0,
+ range_limit - range_base + 1));
+ crs_range_insert(io_ranges, range_base, range_limit);
+ } else { /* "memory" */
+ aml_append(crs,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
+ AML_MAX_FIXED, AML_NON_CACHEABLE,
+ AML_READ_WRITE,
+ 0,
+ range_base,
+ range_limit,
+ 0,
+ range_limit - range_base + 1));
+ crs_range_insert(mem_ranges, range_base, range_limit);
+ }
+ }
+
+ type = dev->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;
+ if (type == PCI_HEADER_TYPE_BRIDGE) {
+ uint8_t subordinate = dev->config[PCI_SUBORDINATE_BUS];
+ if (subordinate > max_bus) {
+ max_bus = subordinate;
+ }
+
+ range_base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO);
+ range_limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO);
+
+ /*
+ * Work-around for old bioses
+ * that do not support multiple root buses
+ */
+ if (range_base && range_base <= range_limit) {
+ aml_append(crs,
+ aml_word_io(AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_POS_DECODE, AML_ENTIRE_RANGE,
+ 0,
+ range_base,
+ range_limit,
+ 0,
+ range_limit - range_base + 1));
+ crs_range_insert(io_ranges, range_base, range_limit);
+ }
+
+ range_base =
+ pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
+ range_limit =
+ pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
+
+ /*
+ * Work-around for old bioses
+ * that do not support multiple root buses
+ */
+ if (range_base && range_base <= range_limit) {
+ aml_append(crs,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
+ AML_MAX_FIXED, AML_NON_CACHEABLE,
+ AML_READ_WRITE,
+ 0,
+ range_base,
+ range_limit,
+ 0,
+ range_limit - range_base + 1));
+ crs_range_insert(mem_ranges, range_base, range_limit);
+ }
+
+ range_base =
+ pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+ range_limit =
+ pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+
+ /*
+ * Work-around for old bioses
+ * that do not support multiple root buses
+ */
+ if (range_base && range_base <= range_limit) {
+ aml_append(crs,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
+ AML_MAX_FIXED, AML_NON_CACHEABLE,
+ AML_READ_WRITE,
+ 0,
+ range_base,
+ range_limit,
+ 0,
+ range_limit - range_base + 1));
+ crs_range_insert(mem_ranges, range_base, range_limit);
+ }
+ }
+ }
+
+ aml_append(crs,
+ aml_word_bus_number(AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE,
+ 0,
+ pci_bus_num(host->bus),
+ max_bus,
+ 0,
+ max_bus - pci_bus_num(host->bus) + 1));
+
+ return crs;
+}
+
+static void
+build_ssdt(GArray *table_data, GArray *linker,
+ AcpiCpuInfo *cpu, AcpiPmInfo *pm, AcpiMiscInfo *misc,
+ PcPciInfo *pci, PcGuestInfo *guest_info)
+{
+ MachineState *machine = MACHINE(qdev_get_machine());
+ uint32_t nr_mem = machine->ram_slots;
+ unsigned acpi_cpus = guest_info->apic_id_limit;
+ Aml *ssdt, *sb_scope, *scope, *pkg, *dev, *method, *crs, *field, *ifctx;
+ PCIBus *bus = NULL;
+ GPtrArray *io_ranges = g_ptr_array_new_with_free_func(crs_range_free);
+ GPtrArray *mem_ranges = g_ptr_array_new_with_free_func(crs_range_free);
+ CrsRangeEntry *entry;
+ int root_bus_limit = 0xFF;
+ int i;
+
+ ssdt = init_aml_allocator();
+ /* The current AML generator can cover the APIC ID range [0..255],
+ * inclusive, for VCPU hotplug. */
+ QEMU_BUILD_BUG_ON(ACPI_CPU_HOTPLUG_ID_LIMIT > 256);
+ g_assert(acpi_cpus <= ACPI_CPU_HOTPLUG_ID_LIMIT);
+
+ /* Reserve space for header */
+ acpi_data_push(ssdt->buf, sizeof(AcpiTableHeader));
+
+ /* Extra PCI root buses are implemented only for i440fx */
+ bus = find_i440fx();
+ if (bus) {
+ QLIST_FOREACH(bus, &bus->child, sibling) {
+ uint8_t bus_num = pci_bus_num(bus);
+ uint8_t numa_node = pci_bus_numa_node(bus);
+
+ /* look only for expander root buses */
+ if (!pci_bus_is_root(bus)) {
+ continue;
+ }
+
+ if (bus_num < root_bus_limit) {
+ root_bus_limit = bus_num - 1;
+ }
+
+ scope = aml_scope("\\_SB");
+ dev = aml_device("PC%.02X", bus_num);
+ aml_append(dev, aml_name_decl("_UID", aml_int(bus_num)));
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A03")));
+ aml_append(dev, aml_name_decl("_BBN", aml_int(bus_num)));
+
+ if (numa_node != NUMA_NODE_UNASSIGNED) {
+ aml_append(dev, aml_name_decl("_PXM", aml_int(numa_node)));
+ }
+
+ aml_append(dev, build_prt());
+ crs = build_crs(PCI_HOST_BRIDGE(BUS(bus)->parent),
+ io_ranges, mem_ranges);
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(scope, dev);
+ aml_append(ssdt, scope);
+ }
+ }
+
+ scope = aml_scope("\\_SB.PCI0");
+ /* build PCI0._CRS */
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_word_bus_number(AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE,
+ 0x0000, 0x0, root_bus_limit,
+ 0x0000, root_bus_limit + 1));
+ aml_append(crs, aml_io(AML_DECODE16, 0x0CF8, 0x0CF8, 0x01, 0x08));
+
+ aml_append(crs,
+ aml_word_io(AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_POS_DECODE, AML_ENTIRE_RANGE,
+ 0x0000, 0x0000, 0x0CF7, 0x0000, 0x0CF8));
+
+ crs_replace_with_free_ranges(io_ranges, 0x0D00, 0xFFFF);
+ for (i = 0; i < io_ranges->len; i++) {
+ entry = g_ptr_array_index(io_ranges, i);
+ aml_append(crs,
+ aml_word_io(AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_POS_DECODE, AML_ENTIRE_RANGE,
+ 0x0000, entry->base, entry->limit,
+ 0x0000, entry->limit - entry->base + 1));
+ }
+
+ aml_append(crs,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_CACHEABLE, AML_READ_WRITE,
+ 0, 0x000A0000, 0x000BFFFF, 0, 0x00020000));
+
+ crs_replace_with_free_ranges(mem_ranges, pci->w32.begin, pci->w32.end - 1);
+ for (i = 0; i < mem_ranges->len; i++) {
+ entry = g_ptr_array_index(mem_ranges, i);
+ aml_append(crs,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_NON_CACHEABLE, AML_READ_WRITE,
+ 0, entry->base, entry->limit,
+ 0, entry->limit - entry->base + 1));
+ }
+
+ if (pci->w64.begin) {
+ aml_append(crs,
+ aml_qword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_CACHEABLE, AML_READ_WRITE,
+ 0, pci->w64.begin, pci->w64.end - 1, 0,
+ pci->w64.end - pci->w64.begin));
+ }
+ aml_append(scope, aml_name_decl("_CRS", crs));
+
+ /* reserve GPE0 block resources */
+ dev = aml_device("GPE0");
+ aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A06")));
+ aml_append(dev, aml_name_decl("_UID", aml_string("GPE0 resources")));
+ /* device present, functioning, decoding, not shown in UI */
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xB)));
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_io(AML_DECODE16, pm->gpe0_blk, pm->gpe0_blk, 1, pm->gpe0_blk_len)
+ );
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(scope, dev);
+
+ g_ptr_array_free(io_ranges, true);
+ g_ptr_array_free(mem_ranges, true);
+
+ /* reserve PCIHP resources */
+ if (pm->pcihp_io_len) {
+ dev = aml_device("PHPR");
+ aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A06")));
+ aml_append(dev,
+ aml_name_decl("_UID", aml_string("PCI Hotplug resources")));
+ /* device present, functioning, decoding, not shown in UI */
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xB)));
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_io(AML_DECODE16, pm->pcihp_io_base, pm->pcihp_io_base, 1,
+ pm->pcihp_io_len)
+ );
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(scope, dev);
+ }
+ aml_append(ssdt, scope);
+
+ /* create S3_ / S4_ / S5_ packages if necessary */
+ scope = aml_scope("\\");
+ if (!pm->s3_disabled) {
+ pkg = aml_package(4);
+ aml_append(pkg, aml_int(1)); /* PM1a_CNT.SLP_TYP */
+ aml_append(pkg, aml_int(1)); /* PM1b_CNT.SLP_TYP, FIXME: not impl. */
+ aml_append(pkg, aml_int(0)); /* reserved */
+ aml_append(pkg, aml_int(0)); /* reserved */
+ aml_append(scope, aml_name_decl("_S3", pkg));
+ }
+
+ if (!pm->s4_disabled) {
+ pkg = aml_package(4);
+ aml_append(pkg, aml_int(pm->s4_val)); /* PM1a_CNT.SLP_TYP */
+ /* PM1b_CNT.SLP_TYP, FIXME: not impl. */
+ aml_append(pkg, aml_int(pm->s4_val));
+ aml_append(pkg, aml_int(0)); /* reserved */
+ aml_append(pkg, aml_int(0)); /* reserved */
+ aml_append(scope, aml_name_decl("_S4", pkg));
+ }
+
+ pkg = aml_package(4);
+ aml_append(pkg, aml_int(0)); /* PM1a_CNT.SLP_TYP */
+ aml_append(pkg, aml_int(0)); /* PM1b_CNT.SLP_TYP not impl. */
+ aml_append(pkg, aml_int(0)); /* reserved */
+ aml_append(pkg, aml_int(0)); /* reserved */
+ aml_append(scope, aml_name_decl("_S5", pkg));
+ aml_append(ssdt, scope);
+
+ if (misc->applesmc_io_base) {
+ scope = aml_scope("\\_SB.PCI0.ISA");
+ dev = aml_device("SMC");
+
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("APP0001")));
+ /* device present, functioning, decoding, not shown in UI */
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xB)));
+
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_io(AML_DECODE16, misc->applesmc_io_base, misc->applesmc_io_base,
+ 0x01, APPLESMC_MAX_DATA_LENGTH)
+ );
+ aml_append(crs, aml_irq_no_flags(6));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+
+ aml_append(scope, dev);
+ aml_append(ssdt, scope);
+ }
+
+ if (misc->pvpanic_port) {
+ scope = aml_scope("\\_SB.PCI0.ISA");
+
+ dev = aml_device("PEVT");
+ aml_append(dev, aml_name_decl("_HID", aml_string("QEMU0001")));
+
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_io(AML_DECODE16, misc->pvpanic_port, misc->pvpanic_port, 1, 1)
+ );
+ aml_append(dev, aml_name_decl("_CRS", crs));
+
+ aml_append(dev, aml_operation_region("PEOR", AML_SYSTEM_IO,
+ misc->pvpanic_port, 1));
+ field = aml_field("PEOR", AML_BYTE_ACC, AML_PRESERVE);
+ aml_append(field, aml_named_field("PEPT", 8));
+ aml_append(dev, field);
+
+ /* device present, functioning, decoding, shown in UI */
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xF)));
+
+ method = aml_method("RDPT", 0);
+ aml_append(method, aml_store(aml_name("PEPT"), aml_local(0)));
+ aml_append(method, aml_return(aml_local(0)));
+ aml_append(dev, method);
+
+ method = aml_method("WRPT", 1);
+ aml_append(method, aml_store(aml_arg(0), aml_name("PEPT")));
+ aml_append(dev, method);
+
+ aml_append(scope, dev);
+ aml_append(ssdt, scope);
+ }
+
+ sb_scope = aml_scope("\\_SB");
+ {
+ /* create PCI0.PRES device and its _CRS to reserve CPU hotplug MMIO */
+ dev = aml_device("PCI0." stringify(CPU_HOTPLUG_RESOURCE_DEVICE));
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A06")));
+ aml_append(dev,
+ aml_name_decl("_UID", aml_string("CPU Hotplug resources"))
+ );
+ /* device present, functioning, decoding, not shown in UI */
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xB)));
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_io(AML_DECODE16, pm->cpu_hp_io_base, pm->cpu_hp_io_base, 1,
+ pm->cpu_hp_io_len)
+ );
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(sb_scope, dev);
+ /* declare CPU hotplug MMIO region and PRS field to access it */
+ aml_append(sb_scope, aml_operation_region(
+ "PRST", AML_SYSTEM_IO, pm->cpu_hp_io_base, pm->cpu_hp_io_len));
+ field = aml_field("PRST", AML_BYTE_ACC, AML_PRESERVE);
+ aml_append(field, aml_named_field("PRS", 256));
+ aml_append(sb_scope, field);
+
+ /* build Processor object for each processor */
+ for (i = 0; i < acpi_cpus; i++) {
+ dev = aml_processor(i, 0, 0, "CP%.02X", i);
+
+ method = aml_method("_MAT", 0);
+ aml_append(method, aml_return(aml_call1("CPMA", aml_int(i))));
+ aml_append(dev, method);
+
+ method = aml_method("_STA", 0);
+ aml_append(method, aml_return(aml_call1("CPST", aml_int(i))));
+ aml_append(dev, method);
+
+ method = aml_method("_EJ0", 1);
+ aml_append(method,
+ aml_return(aml_call2("CPEJ", aml_int(i), aml_arg(0)))
+ );
+ aml_append(dev, method);
+
+ aml_append(sb_scope, dev);
+ }
+
+ /* build this code:
+ * Method(NTFY, 2) {If (LEqual(Arg0, 0x00)) {Notify(CP00, Arg1)} ...}
+ */
+ /* Arg0 = Processor ID = APIC ID */
+ method = aml_method("NTFY", 2);
+ for (i = 0; i < acpi_cpus; i++) {
+ ifctx = aml_if(aml_equal(aml_arg(0), aml_int(i)));
+ aml_append(ifctx,
+ aml_notify(aml_name("CP%.02X", i), aml_arg(1))
+ );
+ aml_append(method, ifctx);
+ }
+ aml_append(sb_scope, method);
+
+ /* build "Name(CPON, Package() { One, One, ..., Zero, Zero, ... })"
+ *
+ * Note: The ability to create variable-sized packages was first
+ * introduced in ACPI 2.0. ACPI 1.0 only allowed fixed-size packages
+ * ith up to 255 elements. Windows guests up to win2k8 fail when
+ * VarPackageOp is used.
+ */
+ pkg = acpi_cpus <= 255 ? aml_package(acpi_cpus) :
+ aml_varpackage(acpi_cpus);
+
+ for (i = 0; i < acpi_cpus; i++) {
+ uint8_t b = test_bit(i, cpu->found_cpus) ? 0x01 : 0x00;
+ aml_append(pkg, aml_int(b));
+ }
+ aml_append(sb_scope, aml_name_decl("CPON", pkg));
+
+ /* build memory devices */
+ assert(nr_mem <= ACPI_MAX_RAM_SLOTS);
+ scope = aml_scope("\\_SB.PCI0." stringify(MEMORY_HOTPLUG_DEVICE));
+ aml_append(scope,
+ aml_name_decl(stringify(MEMORY_SLOTS_NUMBER), aml_int(nr_mem))
+ );
+
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_io(AML_DECODE16, pm->mem_hp_io_base, pm->mem_hp_io_base, 0,
+ pm->mem_hp_io_len)
+ );
+ aml_append(scope, aml_name_decl("_CRS", crs));
+
+ aml_append(scope, aml_operation_region(
+ stringify(MEMORY_HOTPLUG_IO_REGION), AML_SYSTEM_IO,
+ pm->mem_hp_io_base, pm->mem_hp_io_len)
+ );
+
+ field = aml_field(stringify(MEMORY_HOTPLUG_IO_REGION), AML_DWORD_ACC,
+ AML_PRESERVE);
+ aml_append(field, /* read only */
+ aml_named_field(stringify(MEMORY_SLOT_ADDR_LOW), 32));
+ aml_append(field, /* read only */
+ aml_named_field(stringify(MEMORY_SLOT_ADDR_HIGH), 32));
+ aml_append(field, /* read only */
+ aml_named_field(stringify(MEMORY_SLOT_SIZE_LOW), 32));
+ aml_append(field, /* read only */
+ aml_named_field(stringify(MEMORY_SLOT_SIZE_HIGH), 32));
+ aml_append(field, /* read only */
+ aml_named_field(stringify(MEMORY_SLOT_PROXIMITY), 32));
+ aml_append(scope, field);
+
+ field = aml_field(stringify(MEMORY_HOTPLUG_IO_REGION), AML_BYTE_ACC,
+ AML_WRITE_AS_ZEROS);
+ aml_append(field, aml_reserved_field(160 /* bits, Offset(20) */));
+ aml_append(field, /* 1 if enabled, read only */
+ aml_named_field(stringify(MEMORY_SLOT_ENABLED), 1));
+ aml_append(field,
+ /*(read) 1 if has a insert event. (write) 1 to clear event */
+ aml_named_field(stringify(MEMORY_SLOT_INSERT_EVENT), 1));
+ aml_append(field,
+ /* (read) 1 if has a remove event. (write) 1 to clear event */
+ aml_named_field(stringify(MEMORY_SLOT_REMOVE_EVENT), 1));
+ aml_append(field,
+ /* initiates device eject, write only */
+ aml_named_field(stringify(MEMORY_SLOT_EJECT), 1));
+ aml_append(scope, field);
+
+ field = aml_field(stringify(MEMORY_HOTPLUG_IO_REGION), AML_DWORD_ACC,
+ AML_PRESERVE);
+ aml_append(field, /* DIMM selector, write only */
+ aml_named_field(stringify(MEMORY_SLOT_SLECTOR), 32));
+ aml_append(field, /* _OST event code, write only */
+ aml_named_field(stringify(MEMORY_SLOT_OST_EVENT), 32));
+ aml_append(field, /* _OST status code, write only */
+ aml_named_field(stringify(MEMORY_SLOT_OST_STATUS), 32));
+ aml_append(scope, field);
+
+ aml_append(sb_scope, scope);
+
+ for (i = 0; i < nr_mem; i++) {
+ #define BASEPATH "\\_SB.PCI0." stringify(MEMORY_HOTPLUG_DEVICE) "."
+ const char *s;
+
+ dev = aml_device("MP%02X", i);
+ aml_append(dev, aml_name_decl("_UID", aml_string("0x%02X", i)));
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C80")));
+
+ method = aml_method("_CRS", 0);
+ s = BASEPATH stringify(MEMORY_SLOT_CRS_METHOD);
+ aml_append(method, aml_return(aml_call1(s, aml_name("_UID"))));
+ aml_append(dev, method);
+
+ method = aml_method("_STA", 0);
+ s = BASEPATH stringify(MEMORY_SLOT_STATUS_METHOD);
+ aml_append(method, aml_return(aml_call1(s, aml_name("_UID"))));
+ aml_append(dev, method);
+
+ method = aml_method("_PXM", 0);
+ s = BASEPATH stringify(MEMORY_SLOT_PROXIMITY_METHOD);
+ aml_append(method, aml_return(aml_call1(s, aml_name("_UID"))));
+ aml_append(dev, method);
+
+ method = aml_method("_OST", 3);
+ s = BASEPATH stringify(MEMORY_SLOT_OST_METHOD);
+ aml_append(method, aml_return(aml_call4(
+ s, aml_name("_UID"), aml_arg(0), aml_arg(1), aml_arg(2)
+ )));
+ aml_append(dev, method);
+
+ method = aml_method("_EJ0", 1);
+ s = BASEPATH stringify(MEMORY_SLOT_EJECT_METHOD);
+ aml_append(method, aml_return(aml_call2(
+ s, aml_name("_UID"), aml_arg(0))));
+ aml_append(dev, method);
+
+ aml_append(sb_scope, dev);
+ }
+
+ /* build Method(MEMORY_SLOT_NOTIFY_METHOD, 2) {
+ * If (LEqual(Arg0, 0x00)) {Notify(MP00, Arg1)} ... }
+ */
+ method = aml_method(stringify(MEMORY_SLOT_NOTIFY_METHOD), 2);
+ for (i = 0; i < nr_mem; i++) {
+ ifctx = aml_if(aml_equal(aml_arg(0), aml_int(i)));
+ aml_append(ifctx,
+ aml_notify(aml_name("MP%.02X", i), aml_arg(1))
+ );
+ aml_append(method, ifctx);
+ }
+ aml_append(sb_scope, method);
+
+ {
+ Object *pci_host;
+ PCIBus *bus = NULL;
+
+ pci_host = acpi_get_i386_pci_host();
+ if (pci_host) {
+ bus = PCI_HOST_BRIDGE(pci_host)->bus;
+ }
+
+ if (bus) {
+ Aml *scope = aml_scope("PCI0");
+ /* Scan all PCI buses. Generate tables to support hotplug. */
+ build_append_pci_bus_devices(scope, bus, pm->pcihp_bridge_en);
+
+ if (misc->tpm_version != TPM_VERSION_UNSPEC) {
+ dev = aml_device("ISA.TPM");
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C31")));
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xF)));
+ crs = aml_resource_template();
+ aml_append(crs, aml_memory32_fixed(TPM_TIS_ADDR_BASE,
+ TPM_TIS_ADDR_SIZE, AML_READ_WRITE));
+ aml_append(crs, aml_irq_no_flags(TPM_TIS_IRQ));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(scope, dev);
+ }
+
+ aml_append(sb_scope, scope);
+ }
+ }
+ aml_append(ssdt, sb_scope);
+ }
+
+ /* copy AML table into ACPI tables blob and patch header there */
+ g_array_append_vals(table_data, ssdt->buf->data, ssdt->buf->len);
+ build_header(linker, table_data,
+ (void *)(table_data->data + table_data->len - ssdt->buf->len),
+ "SSDT", ssdt->buf->len, 1);
+ free_aml_allocator();
+}
+
+static void
+build_hpet(GArray *table_data, GArray *linker)
+{
+ Acpi20Hpet *hpet;
+
+ hpet = acpi_data_push(table_data, sizeof(*hpet));
+ /* Note timer_block_id value must be kept in sync with value advertised by
+ * emulated hpet
+ */
+ hpet->timer_block_id = cpu_to_le32(0x8086a201);
+ hpet->addr.address = cpu_to_le64(HPET_BASE);
+ build_header(linker, table_data,
+ (void *)hpet, "HPET", sizeof(*hpet), 1);
+}
+
+static void
+build_tpm_tcpa(GArray *table_data, GArray *linker, GArray *tcpalog)
+{
+ Acpi20Tcpa *tcpa = acpi_data_push(table_data, sizeof *tcpa);
+ uint64_t log_area_start_address = acpi_data_len(tcpalog);
+
+ tcpa->platform_class = cpu_to_le16(TPM_TCPA_ACPI_CLASS_CLIENT);
+ tcpa->log_area_minimum_length = cpu_to_le32(TPM_LOG_AREA_MINIMUM_SIZE);
+ tcpa->log_area_start_address = cpu_to_le64(log_area_start_address);
+
+ bios_linker_loader_alloc(linker, ACPI_BUILD_TPMLOG_FILE, 1,
+ false /* high memory */);
+
+ /* log area start address to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE,
+ ACPI_BUILD_TPMLOG_FILE,
+ table_data, &tcpa->log_area_start_address,
+ sizeof(tcpa->log_area_start_address));
+
+ build_header(linker, table_data,
+ (void *)tcpa, "TCPA", sizeof(*tcpa), 2);
+
+ acpi_data_push(tcpalog, TPM_LOG_AREA_MINIMUM_SIZE);
+}
+
+static void
+build_tpm2(GArray *table_data, GArray *linker)
+{
+ Acpi20TPM2 *tpm2_ptr;
+
+ tpm2_ptr = acpi_data_push(table_data, sizeof *tpm2_ptr);
+
+ tpm2_ptr->platform_class = cpu_to_le16(TPM2_ACPI_CLASS_CLIENT);
+ tpm2_ptr->control_area_address = cpu_to_le64(0);
+ tpm2_ptr->start_method = cpu_to_le32(TPM2_START_METHOD_MMIO);
+
+ build_header(linker, table_data,
+ (void *)tpm2_ptr, "TPM2", sizeof(*tpm2_ptr), 4);
+}
+
+typedef enum {
+ MEM_AFFINITY_NOFLAGS = 0,
+ MEM_AFFINITY_ENABLED = (1 << 0),
+ MEM_AFFINITY_HOTPLUGGABLE = (1 << 1),
+ MEM_AFFINITY_NON_VOLATILE = (1 << 2),
+} MemoryAffinityFlags;
+
+static void
+acpi_build_srat_memory(AcpiSratMemoryAffinity *numamem, uint64_t base,
+ uint64_t len, int node, MemoryAffinityFlags flags)
+{
+ numamem->type = ACPI_SRAT_MEMORY;
+ numamem->length = sizeof(*numamem);
+ memset(numamem->proximity, 0, 4);
+ numamem->proximity[0] = node;
+ numamem->flags = cpu_to_le32(flags);
+ numamem->base_addr = cpu_to_le64(base);
+ numamem->range_length = cpu_to_le64(len);
+}
+
+static void
+build_srat(GArray *table_data, GArray *linker, PcGuestInfo *guest_info)
+{
+ AcpiSystemResourceAffinityTable *srat;
+ AcpiSratProcessorAffinity *core;
+ AcpiSratMemoryAffinity *numamem;
+
+ int i;
+ uint64_t curnode;
+ int srat_start, numa_start, slots;
+ uint64_t mem_len, mem_base, next_base;
+ PCMachineState *pcms = PC_MACHINE(qdev_get_machine());
+ ram_addr_t hotplugabble_address_space_size =
+ object_property_get_int(OBJECT(pcms), PC_MACHINE_MEMHP_REGION_SIZE,
+ NULL);
+
+ srat_start = table_data->len;
+
+ srat = acpi_data_push(table_data, sizeof *srat);
+ srat->reserved1 = cpu_to_le32(1);
+ core = (void *)(srat + 1);
+
+ for (i = 0; i < guest_info->apic_id_limit; ++i) {
+ core = acpi_data_push(table_data, sizeof *core);
+ core->type = ACPI_SRAT_PROCESSOR;
+ core->length = sizeof(*core);
+ core->local_apic_id = i;
+ curnode = guest_info->node_cpu[i];
+ core->proximity_lo = curnode;
+ memset(core->proximity_hi, 0, 3);
+ core->local_sapic_eid = 0;
+ core->flags = cpu_to_le32(1);
+ }
+
+
+ /* the memory map is a bit tricky, it contains at least one hole
+ * from 640k-1M and possibly another one from 3.5G-4G.
+ */
+ next_base = 0;
+ numa_start = table_data->len;
+
+ numamem = acpi_data_push(table_data, sizeof *numamem);
+ acpi_build_srat_memory(numamem, 0, 640*1024, 0, MEM_AFFINITY_ENABLED);
+ next_base = 1024 * 1024;
+ for (i = 1; i < guest_info->numa_nodes + 1; ++i) {
+ mem_base = next_base;
+ mem_len = guest_info->node_mem[i - 1];
+ if (i == 1) {
+ mem_len -= 1024 * 1024;
+ }
+ next_base = mem_base + mem_len;
+
+ /* Cut out the ACPI_PCI hole */
+ if (mem_base <= guest_info->ram_size_below_4g &&
+ next_base > guest_info->ram_size_below_4g) {
+ mem_len -= next_base - guest_info->ram_size_below_4g;
+ if (mem_len > 0) {
+ numamem = acpi_data_push(table_data, sizeof *numamem);
+ acpi_build_srat_memory(numamem, mem_base, mem_len, i - 1,
+ MEM_AFFINITY_ENABLED);
+ }
+ mem_base = 1ULL << 32;
+ mem_len = next_base - guest_info->ram_size_below_4g;
+ next_base += (1ULL << 32) - guest_info->ram_size_below_4g;
+ }
+ numamem = acpi_data_push(table_data, sizeof *numamem);
+ acpi_build_srat_memory(numamem, mem_base, mem_len, i - 1,
+ MEM_AFFINITY_ENABLED);
+ }
+ slots = (table_data->len - numa_start) / sizeof *numamem;
+ for (; slots < guest_info->numa_nodes + 2; slots++) {
+ numamem = acpi_data_push(table_data, sizeof *numamem);
+ acpi_build_srat_memory(numamem, 0, 0, 0, MEM_AFFINITY_NOFLAGS);
+ }
+
+ /*
+ * Entry is required for Windows to enable memory hotplug in OS.
+ * Memory devices may override proximity set by this entry,
+ * providing _PXM method if necessary.
+ */
+ if (hotplugabble_address_space_size) {
+ numamem = acpi_data_push(table_data, sizeof *numamem);
+ acpi_build_srat_memory(numamem, pcms->hotplug_memory.base,
+ hotplugabble_address_space_size, 0,
+ MEM_AFFINITY_HOTPLUGGABLE |
+ MEM_AFFINITY_ENABLED);
+ }
+
+ build_header(linker, table_data,
+ (void *)(table_data->data + srat_start),
+ "SRAT",
+ table_data->len - srat_start, 1);
+}
+
+static void
+build_mcfg_q35(GArray *table_data, GArray *linker, AcpiMcfgInfo *info)
+{
+ AcpiTableMcfg *mcfg;
+ const char *sig;
+ int len = sizeof(*mcfg) + 1 * sizeof(mcfg->allocation[0]);
+
+ mcfg = acpi_data_push(table_data, len);
+ mcfg->allocation[0].address = cpu_to_le64(info->mcfg_base);
+ /* Only a single allocation so no need to play with segments */
+ mcfg->allocation[0].pci_segment = cpu_to_le16(0);
+ mcfg->allocation[0].start_bus_number = 0;
+ mcfg->allocation[0].end_bus_number = PCIE_MMCFG_BUS(info->mcfg_size - 1);
+
+ /* MCFG is used for ECAM which can be enabled or disabled by guest.
+ * To avoid table size changes (which create migration issues),
+ * always create the table even if there are no allocations,
+ * but set the signature to a reserved value in this case.
+ * ACPI spec requires OSPMs to ignore such tables.
+ */
+ if (info->mcfg_base == PCIE_BASE_ADDR_UNMAPPED) {
+ /* Reserved signature: ignored by OSPM */
+ sig = "QEMU";
+ } else {
+ sig = "MCFG";
+ }
+ build_header(linker, table_data, (void *)mcfg, sig, len, 1);
+}
+
+static void
+build_dmar_q35(GArray *table_data, GArray *linker)
+{
+ int dmar_start = table_data->len;
+
+ AcpiTableDmar *dmar;
+ AcpiDmarHardwareUnit *drhd;
+
+ dmar = acpi_data_push(table_data, sizeof(*dmar));
+ dmar->host_address_width = VTD_HOST_ADDRESS_WIDTH - 1;
+ dmar->flags = 0; /* No intr_remap for now */
+
+ /* DMAR Remapping Hardware Unit Definition structure */
+ drhd = acpi_data_push(table_data, sizeof(*drhd));
+ drhd->type = cpu_to_le16(ACPI_DMAR_TYPE_HARDWARE_UNIT);
+ drhd->length = cpu_to_le16(sizeof(*drhd)); /* No device scope now */
+ drhd->flags = ACPI_DMAR_INCLUDE_PCI_ALL;
+ drhd->pci_segment = cpu_to_le16(0);
+ drhd->address = cpu_to_le64(Q35_HOST_BRIDGE_IOMMU_ADDR);
+
+ build_header(linker, table_data, (void *)(table_data->data + dmar_start),
+ "DMAR", table_data->len - dmar_start, 1);
+}
+
+static void
+build_dsdt(GArray *table_data, GArray *linker, AcpiMiscInfo *misc)
+{
+ AcpiTableHeader *dsdt;
+
+ assert(misc->dsdt_code && misc->dsdt_size);
+
+ dsdt = acpi_data_push(table_data, misc->dsdt_size);
+ memcpy(dsdt, misc->dsdt_code, misc->dsdt_size);
+
+ memset(dsdt, 0, sizeof *dsdt);
+ build_header(linker, table_data, dsdt, "DSDT",
+ misc->dsdt_size, 1);
+}
+
+static GArray *
+build_rsdp(GArray *rsdp_table, GArray *linker, unsigned rsdt)
+{
+ AcpiRsdpDescriptor *rsdp = acpi_data_push(rsdp_table, sizeof *rsdp);
+
+ bios_linker_loader_alloc(linker, ACPI_BUILD_RSDP_FILE, 16,
+ true /* fseg memory */);
+
+ memcpy(&rsdp->signature, "RSD PTR ", 8);
+ memcpy(rsdp->oem_id, ACPI_BUILD_APPNAME6, 6);
+ rsdp->rsdt_physical_address = cpu_to_le32(rsdt);
+ /* Address to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_RSDP_FILE,
+ ACPI_BUILD_TABLE_FILE,
+ rsdp_table, &rsdp->rsdt_physical_address,
+ sizeof rsdp->rsdt_physical_address);
+ rsdp->checksum = 0;
+ /* Checksum to be filled by Guest linker */
+ bios_linker_loader_add_checksum(linker, ACPI_BUILD_RSDP_FILE,
+ rsdp, rsdp, sizeof *rsdp, &rsdp->checksum);
+
+ return rsdp_table;
+}
+
+typedef
+struct AcpiBuildState {
+ /* Copy of table in RAM (for patching). */
+ MemoryRegion *table_mr;
+ /* Is table patched? */
+ uint8_t patched;
+ PcGuestInfo *guest_info;
+ void *rsdp;
+ MemoryRegion *rsdp_mr;
+ MemoryRegion *linker_mr;
+} AcpiBuildState;
+
+static bool acpi_get_mcfg(AcpiMcfgInfo *mcfg)
+{
+ Object *pci_host;
+ QObject *o;
+
+ pci_host = acpi_get_i386_pci_host();
+ g_assert(pci_host);
+
+ o = object_property_get_qobject(pci_host, PCIE_HOST_MCFG_BASE, NULL);
+ if (!o) {
+ return false;
+ }
+ mcfg->mcfg_base = qint_get_int(qobject_to_qint(o));
+ qobject_decref(o);
+
+ o = object_property_get_qobject(pci_host, PCIE_HOST_MCFG_SIZE, NULL);
+ assert(o);
+ mcfg->mcfg_size = qint_get_int(qobject_to_qint(o));
+ qobject_decref(o);
+ return true;
+}
+
+static bool acpi_has_iommu(void)
+{
+ bool ambiguous;
+ Object *intel_iommu;
+
+ intel_iommu = object_resolve_path_type("", TYPE_INTEL_IOMMU_DEVICE,
+ &ambiguous);
+ return intel_iommu && !ambiguous;
+}
+
+static
+void acpi_build(PcGuestInfo *guest_info, AcpiBuildTables *tables)
+{
+ GArray *table_offsets;
+ unsigned facs, ssdt, dsdt, rsdt;
+ AcpiCpuInfo cpu;
+ AcpiPmInfo pm;
+ AcpiMiscInfo misc;
+ AcpiMcfgInfo mcfg;
+ PcPciInfo pci;
+ uint8_t *u;
+ size_t aml_len = 0;
+ GArray *tables_blob = tables->table_data;
+
+ acpi_get_cpu_info(&cpu);
+ acpi_get_pm_info(&pm);
+ acpi_get_dsdt(&misc);
+ acpi_get_misc_info(&misc);
+ acpi_get_pci_info(&pci);
+
+ table_offsets = g_array_new(false, true /* clear */,
+ sizeof(uint32_t));
+ ACPI_BUILD_DPRINTF("init ACPI tables\n");
+
+ bios_linker_loader_alloc(tables->linker, ACPI_BUILD_TABLE_FILE,
+ 64 /* Ensure FACS is aligned */,
+ false /* high memory */);
+
+ /*
+ * FACS is pointed to by FADT.
+ * We place it first since it's the only table that has alignment
+ * requirements.
+ */
+ facs = tables_blob->len;
+ build_facs(tables_blob, tables->linker, guest_info);
+
+ /* DSDT is pointed to by FADT */
+ dsdt = tables_blob->len;
+ build_dsdt(tables_blob, tables->linker, &misc);
+
+ /* Count the size of the DSDT and SSDT, we will need it for legacy
+ * sizing of ACPI tables.
+ */
+ aml_len += tables_blob->len - dsdt;
+
+ /* ACPI tables pointed to by RSDT */
+ acpi_add_table(table_offsets, tables_blob);
+ build_fadt(tables_blob, tables->linker, &pm, facs, dsdt);
+
+ ssdt = tables_blob->len;
+ acpi_add_table(table_offsets, tables_blob);
+ build_ssdt(tables_blob, tables->linker, &cpu, &pm, &misc, &pci,
+ guest_info);
+ aml_len += tables_blob->len - ssdt;
+
+ acpi_add_table(table_offsets, tables_blob);
+ build_madt(tables_blob, tables->linker, &cpu, guest_info);
+
+ if (misc.has_hpet) {
+ acpi_add_table(table_offsets, tables_blob);
+ build_hpet(tables_blob, tables->linker);
+ }
+ if (misc.tpm_version != TPM_VERSION_UNSPEC) {
+ acpi_add_table(table_offsets, tables_blob);
+ build_tpm_tcpa(tables_blob, tables->linker, tables->tcpalog);
+
+ if (misc.tpm_version == TPM_VERSION_2_0) {
+ acpi_add_table(table_offsets, tables_blob);
+ build_tpm2(tables_blob, tables->linker);
+ }
+ }
+ if (guest_info->numa_nodes) {
+ acpi_add_table(table_offsets, tables_blob);
+ build_srat(tables_blob, tables->linker, guest_info);
+ }
+ if (acpi_get_mcfg(&mcfg)) {
+ acpi_add_table(table_offsets, tables_blob);
+ build_mcfg_q35(tables_blob, tables->linker, &mcfg);
+ }
+ if (acpi_has_iommu()) {
+ acpi_add_table(table_offsets, tables_blob);
+ build_dmar_q35(tables_blob, tables->linker);
+ }
+
+ /* Add tables supplied by user (if any) */
+ for (u = acpi_table_first(); u; u = acpi_table_next(u)) {
+ unsigned len = acpi_table_len(u);
+
+ acpi_add_table(table_offsets, tables_blob);
+ g_array_append_vals(tables_blob, u, len);
+ }
+
+ /* RSDT is pointed to by RSDP */
+ rsdt = tables_blob->len;
+ build_rsdt(tables_blob, tables->linker, table_offsets);
+
+ /* RSDP is in FSEG memory, so allocate it separately */
+ build_rsdp(tables->rsdp, tables->linker, rsdt);
+
+ /* We'll expose it all to Guest so we want to reduce
+ * chance of size changes.
+ *
+ * We used to align the tables to 4k, but of course this would
+ * too simple to be enough. 4k turned out to be too small an
+ * alignment very soon, and in fact it is almost impossible to
+ * keep the table size stable for all (max_cpus, max_memory_slots)
+ * combinations. So the table size is always 64k for pc-i440fx-2.1
+ * and we give an error if the table grows beyond that limit.
+ *
+ * We still have the problem of migrating from "-M pc-i440fx-2.0". For
+ * that, we exploit the fact that QEMU 2.1 generates _smaller_ tables
+ * than 2.0 and we can always pad the smaller tables with zeros. We can
+ * then use the exact size of the 2.0 tables.
+ *
+ * All this is for PIIX4, since QEMU 2.0 didn't support Q35 migration.
+ */
+ if (guest_info->legacy_acpi_table_size) {
+ /* Subtracting aml_len gives the size of fixed tables. Then add the
+ * size of the PIIX4 DSDT/SSDT in QEMU 2.0.
+ */
+ int legacy_aml_len =
+ guest_info->legacy_acpi_table_size +
+ ACPI_BUILD_LEGACY_CPU_AML_SIZE * max_cpus;
+ int legacy_table_size =
+ ROUND_UP(tables_blob->len - aml_len + legacy_aml_len,
+ ACPI_BUILD_ALIGN_SIZE);
+ if (tables_blob->len > legacy_table_size) {
+ /* Should happen only with PCI bridges and -M pc-i440fx-2.0. */
+ error_report("Warning: migration may not work.");
+ }
+ g_array_set_size(tables_blob, legacy_table_size);
+ } else {
+ /* Make sure we have a buffer in case we need to resize the tables. */
+ if (tables_blob->len > ACPI_BUILD_TABLE_SIZE / 2) {
+ /* As of QEMU 2.1, this fires with 160 VCPUs and 255 memory slots. */
+ error_report("Warning: ACPI tables are larger than 64k.");
+ error_report("Warning: migration may not work.");
+ error_report("Warning: please remove CPUs, NUMA nodes, "
+ "memory slots or PCI bridges.");
+ }
+ acpi_align_size(tables_blob, ACPI_BUILD_TABLE_SIZE);
+ }
+
+ acpi_align_size(tables->linker, ACPI_BUILD_ALIGN_SIZE);
+
+ /* Cleanup memory that's no longer used. */
+ g_array_free(table_offsets, true);
+}
+
+static void acpi_ram_update(MemoryRegion *mr, GArray *data)
+{
+ uint32_t size = acpi_data_len(data);
+
+ /* Make sure RAM size is correct - in case it got changed e.g. by migration */
+ memory_region_ram_resize(mr, size, &error_abort);
+
+ memcpy(memory_region_get_ram_ptr(mr), data->data, size);
+ memory_region_set_dirty(mr, 0, size);
+}
+
+static void acpi_build_update(void *build_opaque, uint32_t offset)
+{
+ AcpiBuildState *build_state = build_opaque;
+ AcpiBuildTables tables;
+
+ /* No state to update or already patched? Nothing to do. */
+ if (!build_state || build_state->patched) {
+ return;
+ }
+ build_state->patched = 1;
+
+ acpi_build_tables_init(&tables);
+
+ acpi_build(build_state->guest_info, &tables);
+
+ acpi_ram_update(build_state->table_mr, tables.table_data);
+
+ if (build_state->rsdp) {
+ memcpy(build_state->rsdp, tables.rsdp->data, acpi_data_len(tables.rsdp));
+ } else {
+ acpi_ram_update(build_state->rsdp_mr, tables.rsdp);
+ }
+
+ acpi_ram_update(build_state->linker_mr, tables.linker);
+ acpi_build_tables_cleanup(&tables, true);
+}
+
+static void acpi_build_reset(void *build_opaque)
+{
+ AcpiBuildState *build_state = build_opaque;
+ build_state->patched = 0;
+}
+
+static MemoryRegion *acpi_add_rom_blob(AcpiBuildState *build_state,
+ GArray *blob, const char *name,
+ uint64_t max_size)
+{
+ return rom_add_blob(name, blob->data, acpi_data_len(blob), max_size, -1,
+ name, acpi_build_update, build_state);
+}
+
+static const VMStateDescription vmstate_acpi_build = {
+ .name = "acpi_build",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(patched, AcpiBuildState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+void acpi_setup(PcGuestInfo *guest_info)
+{
+ AcpiBuildTables tables;
+ AcpiBuildState *build_state;
+
+ if (!guest_info->fw_cfg) {
+ ACPI_BUILD_DPRINTF("No fw cfg. Bailing out.\n");
+ return;
+ }
+
+ if (!guest_info->has_acpi_build) {
+ ACPI_BUILD_DPRINTF("ACPI build disabled. Bailing out.\n");
+ return;
+ }
+
+ if (!acpi_enabled) {
+ ACPI_BUILD_DPRINTF("ACPI disabled. Bailing out.\n");
+ return;
+ }
+
+ build_state = g_malloc0(sizeof *build_state);
+
+ build_state->guest_info = guest_info;
+
+ acpi_set_pci_info();
+
+ acpi_build_tables_init(&tables);
+ acpi_build(build_state->guest_info, &tables);
+
+ /* Now expose it all to Guest */
+ build_state->table_mr = acpi_add_rom_blob(build_state, tables.table_data,
+ ACPI_BUILD_TABLE_FILE,
+ ACPI_BUILD_TABLE_MAX_SIZE);
+ assert(build_state->table_mr != NULL);
+
+ build_state->linker_mr =
+ acpi_add_rom_blob(build_state, tables.linker, "etc/table-loader", 0);
+
+ fw_cfg_add_file(guest_info->fw_cfg, ACPI_BUILD_TPMLOG_FILE,
+ tables.tcpalog->data, acpi_data_len(tables.tcpalog));
+
+ if (!guest_info->rsdp_in_ram) {
+ /*
+ * Keep for compatibility with old machine types.
+ * Though RSDP is small, its contents isn't immutable, so
+ * we'll update it along with the rest of tables on guest access.
+ */
+ uint32_t rsdp_size = acpi_data_len(tables.rsdp);
+
+ build_state->rsdp = g_memdup(tables.rsdp->data, rsdp_size);
+ fw_cfg_add_file_callback(guest_info->fw_cfg, ACPI_BUILD_RSDP_FILE,
+ acpi_build_update, build_state,
+ build_state->rsdp, rsdp_size);
+ build_state->rsdp_mr = NULL;
+ } else {
+ build_state->rsdp = NULL;
+ build_state->rsdp_mr = acpi_add_rom_blob(build_state, tables.rsdp,
+ ACPI_BUILD_RSDP_FILE, 0);
+ }
+
+ qemu_register_reset(acpi_build_reset, build_state);
+ acpi_build_reset(build_state);
+ vmstate_register(NULL, 0, &vmstate_acpi_build, build_state);
+
+ /* Cleanup tables but don't free the memory: we track it
+ * in build_state.
+ */
+ acpi_build_tables_cleanup(&tables, false);
+}
diff --git a/hw/i386/acpi-build.h b/hw/i386/acpi-build.h
new file mode 100644
index 00000000..e57b1aaf
--- /dev/null
+++ b/hw/i386/acpi-build.h
@@ -0,0 +1,9 @@
+
+#ifndef HW_I386_ACPI_BUILD_H
+#define HW_I386_ACPI_BUILD_H
+
+#include "qemu/typedefs.h"
+
+void acpi_setup(PcGuestInfo *);
+
+#endif
diff --git a/hw/i386/acpi-dsdt-cpu-hotplug.dsl b/hw/i386/acpi-dsdt-cpu-hotplug.dsl
new file mode 100644
index 00000000..1aff7462
--- /dev/null
+++ b/hw/i386/acpi-dsdt-cpu-hotplug.dsl
@@ -0,0 +1,90 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/****************************************************************
+ * CPU hotplug
+ ****************************************************************/
+
+Scope(\_SB) {
+ /* Objects filled in by run-time generated SSDT */
+ External(NTFY, MethodObj)
+ External(CPON, PkgObj)
+ External(PRS, FieldUnitObj)
+
+ /* Methods called by run-time generated SSDT Processor objects */
+ Method(CPMA, 1, NotSerialized) {
+ // _MAT method - create an madt apic buffer
+ // Arg0 = Processor ID = Local APIC ID
+ // Local0 = CPON flag for this cpu
+ Store(DerefOf(Index(CPON, Arg0)), Local0)
+ // Local1 = Buffer (in madt apic form) to return
+ Store(Buffer(8) {0x00, 0x08, 0x00, 0x00, 0x00, 0, 0, 0}, Local1)
+ // Update the processor id, lapic id, and enable/disable status
+ Store(Arg0, Index(Local1, 2))
+ Store(Arg0, Index(Local1, 3))
+ Store(Local0, Index(Local1, 4))
+ Return (Local1)
+ }
+ Method(CPST, 1, NotSerialized) {
+ // _STA method - return ON status of cpu
+ // Arg0 = Processor ID = Local APIC ID
+ // Local0 = CPON flag for this cpu
+ Store(DerefOf(Index(CPON, Arg0)), Local0)
+ If (Local0) {
+ Return (0xF)
+ } Else {
+ Return (0x0)
+ }
+ }
+ Method(CPEJ, 2, NotSerialized) {
+ // _EJ0 method - eject callback
+ Sleep(200)
+ }
+
+#define CPU_STATUS_LEN ACPI_GPE_PROC_LEN
+ Method(PRSC, 0) {
+ // Local5 = active cpu bitmap
+ Store(PRS, Local5)
+ // Local2 = last read byte from bitmap
+ Store(Zero, Local2)
+ // Local0 = Processor ID / APIC ID iterator
+ Store(Zero, Local0)
+ While (LLess(Local0, SizeOf(CPON))) {
+ // Local1 = CPON flag for this cpu
+ Store(DerefOf(Index(CPON, Local0)), Local1)
+ If (And(Local0, 0x07)) {
+ // Shift down previously read bitmap byte
+ ShiftRight(Local2, 1, Local2)
+ } Else {
+ // Read next byte from cpu bitmap
+ Store(DerefOf(Index(Local5, ShiftRight(Local0, 3))), Local2)
+ }
+ // Local3 = active state for this cpu
+ Store(And(Local2, 1), Local3)
+
+ If (LNotEqual(Local1, Local3)) {
+ // State change - update CPON with new state
+ Store(Local3, Index(CPON, Local0))
+ // Do CPU notify
+ If (LEqual(Local3, 1)) {
+ NTFY(Local0, 1)
+ } Else {
+ NTFY(Local0, 3)
+ }
+ }
+ Increment(Local0)
+ }
+ }
+}
diff --git a/hw/i386/acpi-dsdt-dbug.dsl b/hw/i386/acpi-dsdt-dbug.dsl
new file mode 100644
index 00000000..86230f75
--- /dev/null
+++ b/hw/i386/acpi-dsdt-dbug.dsl
@@ -0,0 +1,41 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/****************************************************************
+ * Debugging
+ ****************************************************************/
+
+Scope(\) {
+ /* Debug Output */
+ OperationRegion(DBG, SystemIO, 0x0402, 0x01)
+ Field(DBG, ByteAcc, NoLock, Preserve) {
+ DBGB, 8,
+ }
+
+ /* Debug method - use this method to send output to the QEMU
+ * BIOS debug port. This method handles strings, integers,
+ * and buffers. For example: DBUG("abc") DBUG(0x123) */
+ Method(DBUG, 1) {
+ ToHexString(Arg0, Local0)
+ ToBuffer(Local0, Local0)
+ Subtract(SizeOf(Local0), 1, Local1)
+ Store(Zero, Local2)
+ While (LLess(Local2, Local1)) {
+ Store(DerefOf(Index(Local0, Local2)), DBGB)
+ Increment(Local2)
+ }
+ Store(0x0A, DBGB)
+ }
+}
diff --git a/hw/i386/acpi-dsdt-hpet.dsl b/hw/i386/acpi-dsdt-hpet.dsl
new file mode 100644
index 00000000..44961b87
--- /dev/null
+++ b/hw/i386/acpi-dsdt-hpet.dsl
@@ -0,0 +1,48 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/****************************************************************
+ * HPET
+ ****************************************************************/
+
+Scope(\_SB) {
+ Device(HPET) {
+ Name(_HID, EISAID("PNP0103"))
+ Name(_UID, 0)
+ OperationRegion(HPTM, SystemMemory, 0xFED00000, 0x400)
+ Field(HPTM, DWordAcc, Lock, Preserve) {
+ VEND, 32,
+ PRD, 32,
+ }
+ Method(_STA, 0, NotSerialized) {
+ Store(VEND, Local0)
+ Store(PRD, Local1)
+ ShiftRight(Local0, 16, Local0)
+ If (LOr(LEqual(Local0, 0), LEqual(Local0, 0xffff))) {
+ Return (0x0)
+ }
+ If (LOr(LEqual(Local1, 0), LGreater(Local1, 100000000))) {
+ Return (0x0)
+ }
+ Return (0x0F)
+ }
+ Name(_CRS, ResourceTemplate() {
+ Memory32Fixed(ReadOnly,
+ 0xFED00000, // Address Base
+ 0x00000400, // Address Length
+ )
+ })
+ }
+}
diff --git a/hw/i386/acpi-dsdt-isa.dsl b/hw/i386/acpi-dsdt-isa.dsl
new file mode 100644
index 00000000..89caa164
--- /dev/null
+++ b/hw/i386/acpi-dsdt-isa.dsl
@@ -0,0 +1,117 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Common legacy ISA style devices. */
+Scope(\_SB.PCI0.ISA) {
+
+ Device(RTC) {
+ Name(_HID, EisaId("PNP0B00"))
+ Name(_CRS, ResourceTemplate() {
+ IO(Decode16, 0x0070, 0x0070, 0x10, 0x02)
+ IRQNoFlags() { 8 }
+ IO(Decode16, 0x0072, 0x0072, 0x02, 0x06)
+ })
+ }
+
+ Device(KBD) {
+ Name(_HID, EisaId("PNP0303"))
+ Method(_STA, 0, NotSerialized) {
+ Return (0x0f)
+ }
+ Name(_CRS, ResourceTemplate() {
+ IO(Decode16, 0x0060, 0x0060, 0x01, 0x01)
+ IO(Decode16, 0x0064, 0x0064, 0x01, 0x01)
+ IRQNoFlags() { 1 }
+ })
+ }
+
+ Device(MOU) {
+ Name(_HID, EisaId("PNP0F13"))
+ Method(_STA, 0, NotSerialized) {
+ Return (0x0f)
+ }
+ Name(_CRS, ResourceTemplate() {
+ IRQNoFlags() { 12 }
+ })
+ }
+
+ Device(FDC0) {
+ Name(_HID, EisaId("PNP0700"))
+ Method(_STA, 0, NotSerialized) {
+ Store(FDEN, Local0)
+ If (LEqual(Local0, 0)) {
+ Return (0x00)
+ } Else {
+ Return (0x0F)
+ }
+ }
+ Name(_CRS, ResourceTemplate() {
+ IO(Decode16, 0x03F2, 0x03F2, 0x00, 0x04)
+ IO(Decode16, 0x03F7, 0x03F7, 0x00, 0x01)
+ IRQNoFlags() { 6 }
+ DMA(Compatibility, NotBusMaster, Transfer8) { 2 }
+ })
+ }
+
+ Device(LPT) {
+ Name(_HID, EisaId("PNP0400"))
+ Method(_STA, 0, NotSerialized) {
+ Store(LPEN, Local0)
+ If (LEqual(Local0, 0)) {
+ Return (0x00)
+ } Else {
+ Return (0x0F)
+ }
+ }
+ Name(_CRS, ResourceTemplate() {
+ IO(Decode16, 0x0378, 0x0378, 0x08, 0x08)
+ IRQNoFlags() { 7 }
+ })
+ }
+
+ Device(COM1) {
+ Name(_HID, EisaId("PNP0501"))
+ Name(_UID, 0x01)
+ Method(_STA, 0, NotSerialized) {
+ Store(CAEN, Local0)
+ If (LEqual(Local0, 0)) {
+ Return (0x00)
+ } Else {
+ Return (0x0F)
+ }
+ }
+ Name(_CRS, ResourceTemplate() {
+ IO(Decode16, 0x03F8, 0x03F8, 0x00, 0x08)
+ IRQNoFlags() { 4 }
+ })
+ }
+
+ Device(COM2) {
+ Name(_HID, EisaId("PNP0501"))
+ Name(_UID, 0x02)
+ Method(_STA, 0, NotSerialized) {
+ Store(CBEN, Local0)
+ If (LEqual(Local0, 0)) {
+ Return (0x00)
+ } Else {
+ Return (0x0F)
+ }
+ }
+ Name(_CRS, ResourceTemplate() {
+ IO(Decode16, 0x02F8, 0x02F8, 0x00, 0x08)
+ IRQNoFlags() { 3 }
+ })
+ }
+}
diff --git a/hw/i386/acpi-dsdt-mem-hotplug.dsl b/hw/i386/acpi-dsdt-mem-hotplug.dsl
new file mode 100644
index 00000000..c2bb6a16
--- /dev/null
+++ b/hw/i386/acpi-dsdt-mem-hotplug.dsl
@@ -0,0 +1,171 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+ External(MEMORY_SLOT_NOTIFY_METHOD, MethodObj)
+
+ Scope(\_SB.PCI0) {
+ Device(MEMORY_HOTPLUG_DEVICE) {
+ Name(_HID, "PNP0A06")
+ Name(_UID, "Memory hotplug resources")
+ External(MEMORY_SLOTS_NUMBER, IntObj)
+
+ /* Memory hotplug IO registers */
+ External(MEMORY_SLOT_ADDR_LOW, FieldUnitObj) // read only
+ External(MEMORY_SLOT_ADDR_HIGH, FieldUnitObj) // read only
+ External(MEMORY_SLOT_SIZE_LOW, FieldUnitObj) // read only
+ External(MEMORY_SLOT_SIZE_HIGH, FieldUnitObj) // read only
+ External(MEMORY_SLOT_PROXIMITY, FieldUnitObj) // read only
+ External(MEMORY_SLOT_ENABLED, FieldUnitObj) // 1 if enabled, read only
+ External(MEMORY_SLOT_INSERT_EVENT, FieldUnitObj) // (read) 1 if has a insert event. (write) 1 to clear event
+ External(MEMORY_SLOT_REMOVE_EVENT, FieldUnitObj) // (read) 1 if has a remove event. (write) 1 to clear event
+ External(MEMORY_SLOT_EJECT, FieldUnitObj) // initiates device eject, write only
+ External(MEMORY_SLOT_SLECTOR, FieldUnitObj) // DIMM selector, write only
+ External(MEMORY_SLOT_OST_EVENT, FieldUnitObj) // _OST event code, write only
+ External(MEMORY_SLOT_OST_STATUS, FieldUnitObj) // _OST status code, write only
+
+ Method(_STA, 0) {
+ If (LEqual(MEMORY_SLOTS_NUMBER, Zero)) {
+ Return(0x0)
+ }
+ /* present, functioning, decoding, not shown in UI */
+ Return(0xB)
+ }
+
+ Mutex (MEMORY_SLOT_LOCK, 0)
+
+ Method(MEMORY_SLOT_SCAN_METHOD, 0) {
+ If (LEqual(MEMORY_SLOTS_NUMBER, Zero)) {
+ Return(Zero)
+ }
+
+ Store(Zero, Local0) // Mem devs iterrator
+ Acquire(MEMORY_SLOT_LOCK, 0xFFFF)
+ while (LLess(Local0, MEMORY_SLOTS_NUMBER)) {
+ Store(Local0, MEMORY_SLOT_SLECTOR) // select Local0 DIMM
+ If (LEqual(MEMORY_SLOT_INSERT_EVENT, One)) { // Memory device needs check
+ MEMORY_SLOT_NOTIFY_METHOD(Local0, 1)
+ Store(1, MEMORY_SLOT_INSERT_EVENT)
+ } Elseif (LEqual(MEMORY_SLOT_REMOVE_EVENT, One)) { // Ejection request
+ MEMORY_SLOT_NOTIFY_METHOD(Local0, 3)
+ Store(1, MEMORY_SLOT_REMOVE_EVENT)
+ }
+ Add(Local0, One, Local0) // goto next DIMM
+ }
+ Release(MEMORY_SLOT_LOCK)
+ Return(One)
+ }
+
+ Method(MEMORY_SLOT_STATUS_METHOD, 1) {
+ Store(Zero, Local0)
+
+ Acquire(MEMORY_SLOT_LOCK, 0xFFFF)
+ Store(ToInteger(Arg0), MEMORY_SLOT_SLECTOR) // select DIMM
+
+ If (LEqual(MEMORY_SLOT_ENABLED, One)) {
+ Store(0xF, Local0)
+ }
+
+ Release(MEMORY_SLOT_LOCK)
+ Return(Local0)
+ }
+
+ Method(MEMORY_SLOT_CRS_METHOD, 1, Serialized) {
+ Acquire(MEMORY_SLOT_LOCK, 0xFFFF)
+ Store(ToInteger(Arg0), MEMORY_SLOT_SLECTOR) // select DIMM
+
+ Name(MR64, ResourceTemplate() {
+ QWordMemory(ResourceProducer, PosDecode, MinFixed, MaxFixed,
+ Cacheable, ReadWrite,
+ 0x0000000000000000, // Address Space Granularity
+ 0x0000000000000000, // Address Range Minimum
+ 0xFFFFFFFFFFFFFFFE, // Address Range Maximum
+ 0x0000000000000000, // Address Translation Offset
+ 0xFFFFFFFFFFFFFFFF, // Address Length
+ ,, MW64, AddressRangeMemory, TypeStatic)
+ })
+
+ CreateDWordField(MR64, 14, MINL)
+ CreateDWordField(MR64, 18, MINH)
+ CreateDWordField(MR64, 38, LENL)
+ CreateDWordField(MR64, 42, LENH)
+ CreateDWordField(MR64, 22, MAXL)
+ CreateDWordField(MR64, 26, MAXH)
+
+ Store(MEMORY_SLOT_ADDR_HIGH, MINH)
+ Store(MEMORY_SLOT_ADDR_LOW, MINL)
+ Store(MEMORY_SLOT_SIZE_HIGH, LENH)
+ Store(MEMORY_SLOT_SIZE_LOW, LENL)
+
+ // 64-bit math: MAX = MIN + LEN - 1
+ Add(MINL, LENL, MAXL)
+ Add(MINH, LENH, MAXH)
+ If (LLess(MAXL, MINL)) {
+ Add(MAXH, One, MAXH)
+ }
+ If (LLess(MAXL, One)) {
+ Subtract(MAXH, One, MAXH)
+ }
+ Subtract(MAXL, One, MAXL)
+
+ If (LEqual(MAXH, Zero)){
+ Name(MR32, ResourceTemplate() {
+ DWordMemory(ResourceProducer, PosDecode, MinFixed, MaxFixed,
+ Cacheable, ReadWrite,
+ 0x00000000, // Address Space Granularity
+ 0x00000000, // Address Range Minimum
+ 0xFFFFFFFE, // Address Range Maximum
+ 0x00000000, // Address Translation Offset
+ 0xFFFFFFFF, // Address Length
+ ,, MW32, AddressRangeMemory, TypeStatic)
+ })
+ CreateDWordField(MR32, MW32._MIN, MIN)
+ CreateDWordField(MR32, MW32._MAX, MAX)
+ CreateDWordField(MR32, MW32._LEN, LEN)
+ Store(MINL, MIN)
+ Store(MAXL, MAX)
+ Store(LENL, LEN)
+
+ Release(MEMORY_SLOT_LOCK)
+ Return(MR32)
+ }
+
+ Release(MEMORY_SLOT_LOCK)
+ Return(MR64)
+ }
+
+ Method(MEMORY_SLOT_PROXIMITY_METHOD, 1) {
+ Acquire(MEMORY_SLOT_LOCK, 0xFFFF)
+ Store(ToInteger(Arg0), MEMORY_SLOT_SLECTOR) // select DIMM
+ Store(MEMORY_SLOT_PROXIMITY, Local0)
+ Release(MEMORY_SLOT_LOCK)
+ Return(Local0)
+ }
+
+ Method(MEMORY_SLOT_OST_METHOD, 4) {
+ Acquire(MEMORY_SLOT_LOCK, 0xFFFF)
+ Store(ToInteger(Arg0), MEMORY_SLOT_SLECTOR) // select DIMM
+ Store(Arg1, MEMORY_SLOT_OST_EVENT)
+ Store(Arg2, MEMORY_SLOT_OST_STATUS)
+ Release(MEMORY_SLOT_LOCK)
+ }
+
+ Method(MEMORY_SLOT_EJECT_METHOD, 2) {
+ Acquire(MEMORY_SLOT_LOCK, 0xFFFF)
+ Store(ToInteger(Arg0), MEMORY_SLOT_SLECTOR) // select DIMM
+ Store(1, MEMORY_SLOT_EJECT)
+ Release(MEMORY_SLOT_LOCK)
+ }
+ } // Device()
+ } // Scope()
diff --git a/hw/i386/acpi-dsdt.dsl b/hw/i386/acpi-dsdt.dsl
new file mode 100644
index 00000000..a2d84ecf
--- /dev/null
+++ b/hw/i386/acpi-dsdt.dsl
@@ -0,0 +1,304 @@
+/*
+ * Bochs/QEMU ACPI DSDT ASL definition
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+ACPI_EXTRACT_ALL_CODE AcpiDsdtAmlCode
+
+DefinitionBlock (
+ "acpi-dsdt.aml", // Output Filename
+ "DSDT", // Signature
+ 0x01, // DSDT Compliance Revision
+ "BXPC", // OEMID
+ "BXDSDT", // TABLE ID
+ 0x1 // OEM Revision
+ )
+{
+
+#include "acpi-dsdt-dbug.dsl"
+
+ Scope(\_SB) {
+ Device(PCI0) {
+ Name(_HID, EisaId("PNP0A03"))
+ Name(_ADR, 0x00)
+ Name(_UID, 1)
+//#define PX13 S0B_
+// External(PX13, DeviceObj)
+ }
+ }
+
+#include "acpi-dsdt-hpet.dsl"
+
+
+/****************************************************************
+ * PIIX4 PM
+ ****************************************************************/
+
+ Scope(\_SB.PCI0) {
+ Device(PX13) {
+ Name(_ADR, 0x00010003)
+ OperationRegion(P13C, PCI_Config, 0x00, 0xff)
+ }
+ }
+
+
+/****************************************************************
+ * PIIX3 ISA bridge
+ ****************************************************************/
+
+ Scope(\_SB.PCI0) {
+
+ External(ISA, DeviceObj)
+
+ Device(ISA) {
+ Name(_ADR, 0x00010000)
+
+ /* PIIX PCI to ISA irq remapping */
+ OperationRegion(P40C, PCI_Config, 0x60, 0x04)
+
+ /* enable bits */
+ Field(\_SB.PCI0.PX13.P13C, AnyAcc, NoLock, Preserve) {
+ Offset(0x5f),
+ , 7,
+ LPEN, 1, // LPT
+ Offset(0x67),
+ , 3,
+ CAEN, 1, // COM1
+ , 3,
+ CBEN, 1, // COM2
+ }
+ Name(FDEN, 1)
+ }
+ }
+
+#include "acpi-dsdt-isa.dsl"
+
+
+/****************************************************************
+ * PCI hotplug
+ ****************************************************************/
+
+ Scope(\_SB.PCI0) {
+ OperationRegion(PCST, SystemIO, 0xae00, 0x08)
+ Field(PCST, DWordAcc, NoLock, WriteAsZeros) {
+ PCIU, 32,
+ PCID, 32,
+ }
+
+ OperationRegion(SEJ, SystemIO, 0xae08, 0x04)
+ Field(SEJ, DWordAcc, NoLock, WriteAsZeros) {
+ B0EJ, 32,
+ }
+
+ OperationRegion(BNMR, SystemIO, 0xae10, 0x04)
+ Field(BNMR, DWordAcc, NoLock, WriteAsZeros) {
+ BNUM, 32,
+ }
+
+ /* Lock to protect access to fields above. */
+ Mutex(BLCK, 0)
+
+ /* Methods called by bulk generated PCI devices below */
+
+ /* Methods called by hotplug devices */
+ Method(PCEJ, 2, NotSerialized) {
+ // _EJ0 method - eject callback
+ Acquire(BLCK, 0xFFFF)
+ Store(Arg0, BNUM)
+ Store(ShiftLeft(1, Arg1), B0EJ)
+ Release(BLCK)
+ Return (0x0)
+ }
+
+ /* Hotplug notification method supplied by SSDT */
+ External(\_SB.PCI0.PCNT, MethodObj)
+ }
+
+
+/****************************************************************
+ * PCI IRQs
+ ****************************************************************/
+
+ Scope(\_SB) {
+ Scope(PCI0) {
+ Method (_PRT, 0) {
+ Store(Package(128) {}, Local0)
+ Store(Zero, Local1)
+ While(LLess(Local1, 128)) {
+ // slot = pin >> 2
+ Store(ShiftRight(Local1, 2), Local2)
+
+ // lnk = (slot + pin) & 3
+ Store(And(Add(Local1, Local2), 3), Local3)
+ If (LEqual(Local3, 0)) {
+ Store(Package(4) { Zero, Zero, LNKD, Zero }, Local4)
+ }
+ If (LEqual(Local3, 1)) {
+ // device 1 is the power-management device, needs SCI
+ If (LEqual(Local1, 4)) {
+ Store(Package(4) { Zero, Zero, LNKS, Zero }, Local4)
+ } Else {
+ Store(Package(4) { Zero, Zero, LNKA, Zero }, Local4)
+ }
+ }
+ If (LEqual(Local3, 2)) {
+ Store(Package(4) { Zero, Zero, LNKB, Zero }, Local4)
+ }
+ If (LEqual(Local3, 3)) {
+ Store(Package(4) { Zero, Zero, LNKC, Zero }, Local4)
+ }
+
+ // Complete the interrupt routing entry:
+ // Package(4) { 0x[slot]FFFF, [pin], [link], 0) }
+
+ Store(Or(ShiftLeft(Local2, 16), 0xFFFF), Index(Local4, 0))
+ Store(And(Local1, 3), Index(Local4, 1))
+ Store(Local4, Index(Local0, Local1))
+
+ Increment(Local1)
+ }
+
+ Return(Local0)
+ }
+ }
+
+ Field(PCI0.ISA.P40C, ByteAcc, NoLock, Preserve) {
+ PRQ0, 8,
+ PRQ1, 8,
+ PRQ2, 8,
+ PRQ3, 8
+ }
+
+ Method(IQST, 1, NotSerialized) {
+ // _STA method - get status
+ If (And(0x80, Arg0)) {
+ Return (0x09)
+ }
+ Return (0x0B)
+ }
+ Method(IQCR, 1, Serialized) {
+ // _CRS method - get current settings
+ Name(PRR0, ResourceTemplate() {
+ Interrupt(, Level, ActiveHigh, Shared) { 0 }
+ })
+ CreateDWordField(PRR0, 0x05, PRRI)
+ If (LLess(Arg0, 0x80)) {
+ Store(Arg0, PRRI)
+ }
+ Return (PRR0)
+ }
+
+#define define_link(link, uid, reg) \
+ Device(link) { \
+ Name(_HID, EISAID("PNP0C0F")) \
+ Name(_UID, uid) \
+ Name(_PRS, ResourceTemplate() { \
+ Interrupt(, Level, ActiveHigh, Shared) { \
+ 5, 10, 11 \
+ } \
+ }) \
+ Method(_STA, 0, NotSerialized) { \
+ Return (IQST(reg)) \
+ } \
+ Method(_DIS, 0, NotSerialized) { \
+ Or(reg, 0x80, reg) \
+ } \
+ Method(_CRS, 0, NotSerialized) { \
+ Return (IQCR(reg)) \
+ } \
+ Method(_SRS, 1, NotSerialized) { \
+ CreateDWordField(Arg0, 0x05, PRRI) \
+ Store(PRRI, reg) \
+ } \
+ }
+
+ define_link(LNKA, 0, PRQ0)
+ define_link(LNKB, 1, PRQ1)
+ define_link(LNKC, 2, PRQ2)
+ define_link(LNKD, 3, PRQ3)
+
+ Device(LNKS) {
+ Name(_HID, EISAID("PNP0C0F"))
+ Name(_UID, 4)
+ Name(_PRS, ResourceTemplate() {
+ Interrupt(, Level, ActiveHigh, Shared) { 9 }
+ })
+
+ // The SCI cannot be disabled and is always attached to GSI 9,
+ // so these are no-ops. We only need this link to override the
+ // polarity to active high and match the content of the MADT.
+ Method(_STA, 0, NotSerialized) { Return (0x0b) }
+ Method(_DIS, 0, NotSerialized) { }
+ Method(_CRS, 0, NotSerialized) { Return (_PRS) }
+ Method(_SRS, 1, NotSerialized) { }
+ }
+ }
+
+#include "hw/acpi/pc-hotplug.h"
+#define CPU_STATUS_BASE PIIX4_CPU_HOTPLUG_IO_BASE
+#include "acpi-dsdt-cpu-hotplug.dsl"
+#include "acpi-dsdt-mem-hotplug.dsl"
+
+
+/****************************************************************
+ * General purpose events
+ ****************************************************************/
+ Scope(\_GPE) {
+ Name(_HID, "ACPI0006")
+
+ Method(_L00) {
+ }
+ Method(_E01) {
+ // PCI hotplug event
+ Acquire(\_SB.PCI0.BLCK, 0xFFFF)
+ \_SB.PCI0.PCNT()
+ Release(\_SB.PCI0.BLCK)
+ }
+ Method(_E02) {
+ // CPU hotplug event
+ \_SB.PRSC()
+ }
+ Method(_E03) {
+ // Memory hotplug event
+ \_SB.PCI0.MEMORY_HOTPLUG_DEVICE.MEMORY_SLOT_SCAN_METHOD()
+ }
+ Method(_L04) {
+ }
+ Method(_L05) {
+ }
+ Method(_L06) {
+ }
+ Method(_L07) {
+ }
+ Method(_L08) {
+ }
+ Method(_L09) {
+ }
+ Method(_L0A) {
+ }
+ Method(_L0B) {
+ }
+ Method(_L0C) {
+ }
+ Method(_L0D) {
+ }
+ Method(_L0E) {
+ }
+ Method(_L0F) {
+ }
+ }
+}
diff --git a/hw/i386/acpi-dsdt.hex.generated b/hw/i386/acpi-dsdt.hex.generated
new file mode 100644
index 00000000..ecaa4a54
--- /dev/null
+++ b/hw/i386/acpi-dsdt.hex.generated
@@ -0,0 +1,2972 @@
+static unsigned char AcpiDsdtAmlCode[] = {
+0x44,
+0x53,
+0x44,
+0x54,
+0x9a,
+0xb,
+0x0,
+0x0,
+0x1,
+0xf8,
+0x42,
+0x58,
+0x50,
+0x43,
+0x0,
+0x0,
+0x42,
+0x58,
+0x44,
+0x53,
+0x44,
+0x54,
+0x0,
+0x0,
+0x1,
+0x0,
+0x0,
+0x0,
+0x49,
+0x4e,
+0x54,
+0x4c,
+0x7,
+0x11,
+0x14,
+0x20,
+0x10,
+0x49,
+0x4,
+0x5c,
+0x0,
+0x5b,
+0x80,
+0x44,
+0x42,
+0x47,
+0x5f,
+0x1,
+0xb,
+0x2,
+0x4,
+0x1,
+0x5b,
+0x81,
+0xb,
+0x44,
+0x42,
+0x47,
+0x5f,
+0x1,
+0x44,
+0x42,
+0x47,
+0x42,
+0x8,
+0x14,
+0x2c,
+0x44,
+0x42,
+0x55,
+0x47,
+0x1,
+0x98,
+0x68,
+0x60,
+0x96,
+0x60,
+0x60,
+0x74,
+0x87,
+0x60,
+0x1,
+0x61,
+0x70,
+0x0,
+0x62,
+0xa2,
+0x10,
+0x95,
+0x62,
+0x61,
+0x70,
+0x83,
+0x88,
+0x60,
+0x62,
+0x0,
+0x44,
+0x42,
+0x47,
+0x42,
+0x75,
+0x62,
+0x70,
+0xa,
+0xa,
+0x44,
+0x42,
+0x47,
+0x42,
+0x10,
+0x22,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x5b,
+0x82,
+0x1b,
+0x50,
+0x43,
+0x49,
+0x30,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xa,
+0x3,
+0x8,
+0x5f,
+0x41,
+0x44,
+0x52,
+0x0,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x1,
+0x10,
+0x4d,
+0x8,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x5b,
+0x82,
+0x45,
+0x8,
+0x48,
+0x50,
+0x45,
+0x54,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x1,
+0x3,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x5b,
+0x80,
+0x48,
+0x50,
+0x54,
+0x4d,
+0x0,
+0xc,
+0x0,
+0x0,
+0xd0,
+0xfe,
+0xb,
+0x0,
+0x4,
+0x5b,
+0x81,
+0x10,
+0x48,
+0x50,
+0x54,
+0x4d,
+0x13,
+0x56,
+0x45,
+0x4e,
+0x44,
+0x20,
+0x50,
+0x52,
+0x44,
+0x5f,
+0x20,
+0x14,
+0x36,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x56,
+0x45,
+0x4e,
+0x44,
+0x60,
+0x70,
+0x50,
+0x52,
+0x44,
+0x5f,
+0x61,
+0x7a,
+0x60,
+0xa,
+0x10,
+0x60,
+0xa0,
+0xc,
+0x91,
+0x93,
+0x60,
+0x0,
+0x93,
+0x60,
+0xb,
+0xff,
+0xff,
+0xa4,
+0x0,
+0xa0,
+0xe,
+0x91,
+0x93,
+0x61,
+0x0,
+0x94,
+0x61,
+0xc,
+0x0,
+0xe1,
+0xf5,
+0x5,
+0xa4,
+0x0,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x11,
+0xa,
+0xe,
+0x86,
+0x9,
+0x0,
+0x0,
+0x0,
+0x0,
+0xd0,
+0xfe,
+0x0,
+0x4,
+0x0,
+0x0,
+0x79,
+0x0,
+0x10,
+0x25,
+0x2e,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x5b,
+0x82,
+0x19,
+0x50,
+0x58,
+0x31,
+0x33,
+0x8,
+0x5f,
+0x41,
+0x44,
+0x52,
+0xc,
+0x3,
+0x0,
+0x1,
+0x0,
+0x5b,
+0x80,
+0x50,
+0x31,
+0x33,
+0x43,
+0x2,
+0x0,
+0xa,
+0xff,
+0x10,
+0x46,
+0x5,
+0x2e,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x5b,
+0x82,
+0x49,
+0x4,
+0x49,
+0x53,
+0x41,
+0x5f,
+0x8,
+0x5f,
+0x41,
+0x44,
+0x52,
+0xc,
+0x0,
+0x0,
+0x1,
+0x0,
+0x5b,
+0x80,
+0x50,
+0x34,
+0x30,
+0x43,
+0x2,
+0xa,
+0x60,
+0xa,
+0x4,
+0x5b,
+0x81,
+0x26,
+0x5e,
+0x2e,
+0x50,
+0x58,
+0x31,
+0x33,
+0x50,
+0x31,
+0x33,
+0x43,
+0x0,
+0x0,
+0x48,
+0x2f,
+0x0,
+0x7,
+0x4c,
+0x50,
+0x45,
+0x4e,
+0x1,
+0x0,
+0x38,
+0x0,
+0x3,
+0x43,
+0x41,
+0x45,
+0x4e,
+0x1,
+0x0,
+0x3,
+0x43,
+0x42,
+0x45,
+0x4e,
+0x1,
+0x8,
+0x46,
+0x44,
+0x45,
+0x4e,
+0x1,
+0x10,
+0x4c,
+0x1b,
+0x2f,
+0x3,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x49,
+0x53,
+0x41,
+0x5f,
+0x5b,
+0x82,
+0x2d,
+0x52,
+0x54,
+0x43,
+0x5f,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xb,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x18,
+0xa,
+0x15,
+0x47,
+0x1,
+0x70,
+0x0,
+0x70,
+0x0,
+0x10,
+0x2,
+0x22,
+0x0,
+0x1,
+0x47,
+0x1,
+0x72,
+0x0,
+0x72,
+0x0,
+0x2,
+0x6,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x37,
+0x4b,
+0x42,
+0x44,
+0x5f,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x3,
+0x3,
+0x14,
+0x9,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x18,
+0xa,
+0x15,
+0x47,
+0x1,
+0x60,
+0x0,
+0x60,
+0x0,
+0x1,
+0x1,
+0x47,
+0x1,
+0x64,
+0x0,
+0x64,
+0x0,
+0x1,
+0x1,
+0x22,
+0x2,
+0x0,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x27,
+0x4d,
+0x4f,
+0x55,
+0x5f,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xf,
+0x13,
+0x14,
+0x9,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x8,
+0xa,
+0x5,
+0x22,
+0x0,
+0x10,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x4a,
+0x4,
+0x46,
+0x44,
+0x43,
+0x30,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x7,
+0x0,
+0x14,
+0x18,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x46,
+0x44,
+0x45,
+0x4e,
+0x60,
+0xa0,
+0x6,
+0x93,
+0x60,
+0x0,
+0xa4,
+0x0,
+0xa1,
+0x4,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x1b,
+0xa,
+0x18,
+0x47,
+0x1,
+0xf2,
+0x3,
+0xf2,
+0x3,
+0x0,
+0x4,
+0x47,
+0x1,
+0xf7,
+0x3,
+0xf7,
+0x3,
+0x0,
+0x1,
+0x22,
+0x40,
+0x0,
+0x2a,
+0x4,
+0x0,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x3e,
+0x4c,
+0x50,
+0x54,
+0x5f,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x4,
+0x0,
+0x14,
+0x18,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x4c,
+0x50,
+0x45,
+0x4e,
+0x60,
+0xa0,
+0x6,
+0x93,
+0x60,
+0x0,
+0xa4,
+0x0,
+0xa1,
+0x4,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x10,
+0xa,
+0xd,
+0x47,
+0x1,
+0x78,
+0x3,
+0x78,
+0x3,
+0x8,
+0x8,
+0x22,
+0x80,
+0x0,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x43,
+0x4f,
+0x4d,
+0x31,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x5,
+0x1,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x1,
+0x14,
+0x18,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x43,
+0x41,
+0x45,
+0x4e,
+0x60,
+0xa0,
+0x6,
+0x93,
+0x60,
+0x0,
+0xa4,
+0x0,
+0xa1,
+0x4,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x10,
+0xa,
+0xd,
+0x47,
+0x1,
+0xf8,
+0x3,
+0xf8,
+0x3,
+0x0,
+0x8,
+0x22,
+0x10,
+0x0,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x46,
+0x4,
+0x43,
+0x4f,
+0x4d,
+0x32,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x5,
+0x1,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x2,
+0x14,
+0x18,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x43,
+0x42,
+0x45,
+0x4e,
+0x60,
+0xa0,
+0x6,
+0x93,
+0x60,
+0x0,
+0xa4,
+0x0,
+0xa1,
+0x4,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x10,
+0xa,
+0xd,
+0x47,
+0x1,
+0xf8,
+0x2,
+0xf8,
+0x2,
+0x0,
+0x8,
+0x22,
+0x8,
+0x0,
+0x79,
+0x0,
+0x10,
+0x48,
+0x8,
+0x2e,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x5b,
+0x80,
+0x50,
+0x43,
+0x53,
+0x54,
+0x1,
+0xb,
+0x0,
+0xae,
+0xa,
+0x8,
+0x5b,
+0x81,
+0x10,
+0x50,
+0x43,
+0x53,
+0x54,
+0x43,
+0x50,
+0x43,
+0x49,
+0x55,
+0x20,
+0x50,
+0x43,
+0x49,
+0x44,
+0x20,
+0x5b,
+0x80,
+0x53,
+0x45,
+0x4a,
+0x5f,
+0x1,
+0xb,
+0x8,
+0xae,
+0xa,
+0x4,
+0x5b,
+0x81,
+0xb,
+0x53,
+0x45,
+0x4a,
+0x5f,
+0x43,
+0x42,
+0x30,
+0x45,
+0x4a,
+0x20,
+0x5b,
+0x80,
+0x42,
+0x4e,
+0x4d,
+0x52,
+0x1,
+0xb,
+0x10,
+0xae,
+0xa,
+0x4,
+0x5b,
+0x81,
+0xb,
+0x42,
+0x4e,
+0x4d,
+0x52,
+0x43,
+0x42,
+0x4e,
+0x55,
+0x4d,
+0x20,
+0x5b,
+0x1,
+0x42,
+0x4c,
+0x43,
+0x4b,
+0x0,
+0x14,
+0x25,
+0x50,
+0x43,
+0x45,
+0x4a,
+0x2,
+0x5b,
+0x23,
+0x42,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x68,
+0x42,
+0x4e,
+0x55,
+0x4d,
+0x70,
+0x79,
+0x1,
+0x69,
+0x0,
+0x42,
+0x30,
+0x45,
+0x4a,
+0x5b,
+0x27,
+0x42,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x0,
+0x10,
+0x4e,
+0x36,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x10,
+0x4b,
+0xa,
+0x50,
+0x43,
+0x49,
+0x30,
+0x14,
+0x44,
+0xa,
+0x5f,
+0x50,
+0x52,
+0x54,
+0x0,
+0x70,
+0x12,
+0x2,
+0x80,
+0x60,
+0x70,
+0x0,
+0x61,
+0xa2,
+0x42,
+0x9,
+0x95,
+0x61,
+0xa,
+0x80,
+0x70,
+0x7a,
+0x61,
+0xa,
+0x2,
+0x0,
+0x62,
+0x70,
+0x7b,
+0x72,
+0x61,
+0x62,
+0x0,
+0xa,
+0x3,
+0x0,
+0x63,
+0xa0,
+0x10,
+0x93,
+0x63,
+0x0,
+0x70,
+0x12,
+0x9,
+0x4,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x0,
+0x64,
+0xa0,
+0x24,
+0x93,
+0x63,
+0x1,
+0xa0,
+0x11,
+0x93,
+0x61,
+0xa,
+0x4,
+0x70,
+0x12,
+0x9,
+0x4,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x53,
+0x0,
+0x64,
+0xa1,
+0xd,
+0x70,
+0x12,
+0x9,
+0x4,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x0,
+0x64,
+0xa0,
+0x11,
+0x93,
+0x63,
+0xa,
+0x2,
+0x70,
+0x12,
+0x9,
+0x4,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x0,
+0x64,
+0xa0,
+0x11,
+0x93,
+0x63,
+0xa,
+0x3,
+0x70,
+0x12,
+0x9,
+0x4,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x0,
+0x64,
+0x70,
+0x7d,
+0x79,
+0x62,
+0xa,
+0x10,
+0x0,
+0xb,
+0xff,
+0xff,
+0x0,
+0x88,
+0x64,
+0x0,
+0x0,
+0x70,
+0x7b,
+0x61,
+0xa,
+0x3,
+0x0,
+0x88,
+0x64,
+0x1,
+0x0,
+0x70,
+0x64,
+0x88,
+0x60,
+0x61,
+0x0,
+0x75,
+0x61,
+0xa4,
+0x60,
+0x5b,
+0x81,
+0x24,
+0x2f,
+0x3,
+0x50,
+0x43,
+0x49,
+0x30,
+0x49,
+0x53,
+0x41,
+0x5f,
+0x50,
+0x34,
+0x30,
+0x43,
+0x1,
+0x50,
+0x52,
+0x51,
+0x30,
+0x8,
+0x50,
+0x52,
+0x51,
+0x31,
+0x8,
+0x50,
+0x52,
+0x51,
+0x32,
+0x8,
+0x50,
+0x52,
+0x51,
+0x33,
+0x8,
+0x14,
+0x13,
+0x49,
+0x51,
+0x53,
+0x54,
+0x1,
+0xa0,
+0x9,
+0x7b,
+0xa,
+0x80,
+0x68,
+0x0,
+0xa4,
+0xa,
+0x9,
+0xa4,
+0xa,
+0xb,
+0x14,
+0x36,
+0x49,
+0x51,
+0x43,
+0x52,
+0x9,
+0x8,
+0x50,
+0x52,
+0x52,
+0x30,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x0,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8a,
+0x50,
+0x52,
+0x52,
+0x30,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0xa0,
+0xb,
+0x95,
+0x68,
+0xa,
+0x80,
+0x70,
+0x68,
+0x50,
+0x52,
+0x52,
+0x49,
+0xa4,
+0x50,
+0x52,
+0x52,
+0x30,
+0x5b,
+0x82,
+0x4c,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x30,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x30,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x30,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x30,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x30,
+0x5b,
+0x82,
+0x4c,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x1,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x31,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x31,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x31,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x31,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x31,
+0x5b,
+0x82,
+0x4d,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x2,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x32,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x32,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x32,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x32,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x32,
+0x5b,
+0x82,
+0x4d,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x3,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x33,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x33,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x33,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x33,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x33,
+0x5b,
+0x82,
+0x4f,
+0x4,
+0x4c,
+0x4e,
+0x4b,
+0x53,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x4,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x9,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x9,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0xa,
+0xb,
+0x14,
+0x6,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x14,
+0xb,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x10,
+0x4d,
+0xc,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x14,
+0x35,
+0x43,
+0x50,
+0x4d,
+0x41,
+0x1,
+0x70,
+0x83,
+0x88,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x68,
+0x0,
+0x60,
+0x70,
+0x11,
+0xb,
+0xa,
+0x8,
+0x0,
+0x8,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x61,
+0x70,
+0x68,
+0x88,
+0x61,
+0xa,
+0x2,
+0x0,
+0x70,
+0x68,
+0x88,
+0x61,
+0xa,
+0x3,
+0x0,
+0x70,
+0x60,
+0x88,
+0x61,
+0xa,
+0x4,
+0x0,
+0xa4,
+0x61,
+0x14,
+0x1a,
+0x43,
+0x50,
+0x53,
+0x54,
+0x1,
+0x70,
+0x83,
+0x88,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x68,
+0x0,
+0x60,
+0xa0,
+0x5,
+0x60,
+0xa4,
+0xa,
+0xf,
+0xa1,
+0x3,
+0xa4,
+0x0,
+0x14,
+0xa,
+0x43,
+0x50,
+0x45,
+0x4a,
+0x2,
+0x5b,
+0x22,
+0xa,
+0xc8,
+0x14,
+0x4a,
+0x6,
+0x50,
+0x52,
+0x53,
+0x43,
+0x0,
+0x70,
+0x50,
+0x52,
+0x53,
+0x5f,
+0x65,
+0x70,
+0x0,
+0x62,
+0x70,
+0x0,
+0x60,
+0xa2,
+0x46,
+0x5,
+0x95,
+0x60,
+0x87,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x70,
+0x83,
+0x88,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x60,
+0x0,
+0x61,
+0xa0,
+0xa,
+0x7b,
+0x60,
+0xa,
+0x7,
+0x0,
+0x7a,
+0x62,
+0x1,
+0x62,
+0xa1,
+0xc,
+0x70,
+0x83,
+0x88,
+0x65,
+0x7a,
+0x60,
+0xa,
+0x3,
+0x0,
+0x0,
+0x62,
+0x70,
+0x7b,
+0x62,
+0x1,
+0x0,
+0x63,
+0xa0,
+0x22,
+0x92,
+0x93,
+0x61,
+0x63,
+0x70,
+0x63,
+0x88,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x60,
+0x0,
+0xa0,
+0xa,
+0x93,
+0x63,
+0x1,
+0x4e,
+0x54,
+0x46,
+0x59,
+0x60,
+0x1,
+0xa1,
+0x8,
+0x4e,
+0x54,
+0x46,
+0x59,
+0x60,
+0xa,
+0x3,
+0x75,
+0x60,
+0x10,
+0x44,
+0x2a,
+0x2e,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x5b,
+0x82,
+0x47,
+0x29,
+0x4d,
+0x48,
+0x50,
+0x44,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xd,
+0x50,
+0x4e,
+0x50,
+0x30,
+0x41,
+0x30,
+0x36,
+0x0,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xd,
+0x4d,
+0x65,
+0x6d,
+0x6f,
+0x72,
+0x79,
+0x20,
+0x68,
+0x6f,
+0x74,
+0x70,
+0x6c,
+0x75,
+0x67,
+0x20,
+0x72,
+0x65,
+0x73,
+0x6f,
+0x75,
+0x72,
+0x63,
+0x65,
+0x73,
+0x0,
+0x14,
+0x13,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa0,
+0x9,
+0x93,
+0x4d,
+0x44,
+0x4e,
+0x52,
+0x0,
+0xa4,
+0x0,
+0xa4,
+0xa,
+0xb,
+0x5b,
+0x1,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0x0,
+0x14,
+0x4a,
+0x4,
+0x4d,
+0x53,
+0x43,
+0x4e,
+0x0,
+0xa0,
+0x9,
+0x93,
+0x4d,
+0x44,
+0x4e,
+0x52,
+0x0,
+0xa4,
+0x0,
+0x70,
+0x0,
+0x60,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0xa2,
+0x25,
+0x95,
+0x60,
+0x4d,
+0x44,
+0x4e,
+0x52,
+0x70,
+0x60,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0xa0,
+0x13,
+0x93,
+0x4d,
+0x49,
+0x4e,
+0x53,
+0x1,
+0x4d,
+0x54,
+0x46,
+0x59,
+0x60,
+0x1,
+0x70,
+0x1,
+0x4d,
+0x49,
+0x4e,
+0x53,
+0x72,
+0x60,
+0x1,
+0x60,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x1,
+0x14,
+0x2d,
+0x4d,
+0x52,
+0x53,
+0x54,
+0x1,
+0x70,
+0x0,
+0x60,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x99,
+0x68,
+0x0,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0xa0,
+0xb,
+0x93,
+0x4d,
+0x45,
+0x53,
+0x5f,
+0x1,
+0x70,
+0xa,
+0xf,
+0x60,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x60,
+0x14,
+0x41,
+0x18,
+0x4d,
+0x43,
+0x52,
+0x53,
+0x9,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x99,
+0x68,
+0x0,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0x8,
+0x4d,
+0x52,
+0x36,
+0x34,
+0x11,
+0x33,
+0xa,
+0x30,
+0x8a,
+0x2b,
+0x0,
+0x0,
+0xc,
+0x3,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0xfe,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0x79,
+0x0,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0xe,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x12,
+0x4d,
+0x49,
+0x4e,
+0x48,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x26,
+0x4c,
+0x45,
+0x4e,
+0x4c,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x2a,
+0x4c,
+0x45,
+0x4e,
+0x48,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x16,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x1a,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x70,
+0x4d,
+0x52,
+0x42,
+0x48,
+0x4d,
+0x49,
+0x4e,
+0x48,
+0x70,
+0x4d,
+0x52,
+0x42,
+0x4c,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x70,
+0x4d,
+0x52,
+0x4c,
+0x48,
+0x4c,
+0x45,
+0x4e,
+0x48,
+0x70,
+0x4d,
+0x52,
+0x4c,
+0x4c,
+0x4c,
+0x45,
+0x4e,
+0x4c,
+0x72,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x4c,
+0x45,
+0x4e,
+0x4c,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x72,
+0x4d,
+0x49,
+0x4e,
+0x48,
+0x4c,
+0x45,
+0x4e,
+0x48,
+0x4d,
+0x41,
+0x58,
+0x48,
+0xa0,
+0x14,
+0x95,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x72,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x1,
+0x4d,
+0x41,
+0x58,
+0x48,
+0xa0,
+0x11,
+0x95,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x1,
+0x74,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x1,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x74,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x1,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0xa0,
+0x44,
+0x7,
+0x93,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x0,
+0x8,
+0x4d,
+0x52,
+0x33,
+0x32,
+0x11,
+0x1f,
+0xa,
+0x1c,
+0x87,
+0x17,
+0x0,
+0x0,
+0xc,
+0x3,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0xfe,
+0xff,
+0xff,
+0xff,
+0x0,
+0x0,
+0x0,
+0x0,
+0xff,
+0xff,
+0xff,
+0xff,
+0x79,
+0x0,
+0x8a,
+0x4d,
+0x52,
+0x33,
+0x32,
+0xa,
+0xa,
+0x4d,
+0x49,
+0x4e,
+0x5f,
+0x8a,
+0x4d,
+0x52,
+0x33,
+0x32,
+0xa,
+0xe,
+0x4d,
+0x41,
+0x58,
+0x5f,
+0x8a,
+0x4d,
+0x52,
+0x33,
+0x32,
+0xa,
+0x16,
+0x4c,
+0x45,
+0x4e,
+0x5f,
+0x70,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x4d,
+0x49,
+0x4e,
+0x5f,
+0x70,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x4d,
+0x41,
+0x58,
+0x5f,
+0x70,
+0x4c,
+0x45,
+0x4e,
+0x4c,
+0x4c,
+0x45,
+0x4e,
+0x5f,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x4d,
+0x52,
+0x33,
+0x32,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x4d,
+0x52,
+0x36,
+0x34,
+0x14,
+0x24,
+0x4d,
+0x50,
+0x58,
+0x4d,
+0x1,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x99,
+0x68,
+0x0,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0x70,
+0x4d,
+0x50,
+0x58,
+0x5f,
+0x60,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x60,
+0x14,
+0x28,
+0x4d,
+0x4f,
+0x53,
+0x54,
+0x4,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x99,
+0x68,
+0x0,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0x70,
+0x69,
+0x4d,
+0x4f,
+0x45,
+0x56,
+0x70,
+0x6a,
+0x4d,
+0x4f,
+0x53,
+0x43,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0x10,
+0x45,
+0xd,
+0x5f,
+0x47,
+0x50,
+0x45,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xd,
+0x41,
+0x43,
+0x50,
+0x49,
+0x30,
+0x30,
+0x30,
+0x36,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x30,
+0x0,
+0x14,
+0x39,
+0x5f,
+0x45,
+0x30,
+0x31,
+0x0,
+0x5b,
+0x23,
+0x5c,
+0x2f,
+0x3,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x42,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x5c,
+0x2f,
+0x3,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x50,
+0x43,
+0x4e,
+0x54,
+0x5b,
+0x27,
+0x5c,
+0x2f,
+0x3,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x42,
+0x4c,
+0x43,
+0x4b,
+0x14,
+0x10,
+0x5f,
+0x45,
+0x30,
+0x32,
+0x0,
+0x5c,
+0x2e,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x43,
+0x14,
+0x19,
+0x5f,
+0x45,
+0x30,
+0x33,
+0x0,
+0x5c,
+0x2f,
+0x4,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x4d,
+0x48,
+0x50,
+0x44,
+0x4d,
+0x53,
+0x43,
+0x4e,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x34,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x35,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x36,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x37,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x38,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x39,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x41,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x42,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x43,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x44,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x45,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x46,
+0x0
+};
diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
new file mode 100644
index 00000000..08055a8d
--- /dev/null
+++ b/hw/i386/intel_iommu.c
@@ -0,0 +1,1967 @@
+/*
+ * QEMU emulation of an Intel IOMMU (VT-d)
+ * (DMA Remapping device)
+ *
+ * Copyright (C) 2013 Knut Omang, Oracle <knut.omang@oracle.com>
+ * Copyright (C) 2014 Le Tan, <tamlokveer@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "intel_iommu_internal.h"
+
+/*#define DEBUG_INTEL_IOMMU*/
+#ifdef DEBUG_INTEL_IOMMU
+enum {
+ DEBUG_GENERAL, DEBUG_CSR, DEBUG_INV, DEBUG_MMU, DEBUG_FLOG,
+ DEBUG_CACHE,
+};
+#define VTD_DBGBIT(x) (1 << DEBUG_##x)
+static int vtd_dbgflags = VTD_DBGBIT(GENERAL) | VTD_DBGBIT(CSR);
+
+#define VTD_DPRINTF(what, fmt, ...) do { \
+ if (vtd_dbgflags & VTD_DBGBIT(what)) { \
+ fprintf(stderr, "(vtd)%s: " fmt "\n", __func__, \
+ ## __VA_ARGS__); } \
+ } while (0)
+#else
+#define VTD_DPRINTF(what, fmt, ...) do {} while (0)
+#endif
+
+static void vtd_define_quad(IntelIOMMUState *s, hwaddr addr, uint64_t val,
+ uint64_t wmask, uint64_t w1cmask)
+{
+ stq_le_p(&s->csr[addr], val);
+ stq_le_p(&s->wmask[addr], wmask);
+ stq_le_p(&s->w1cmask[addr], w1cmask);
+}
+
+static void vtd_define_quad_wo(IntelIOMMUState *s, hwaddr addr, uint64_t mask)
+{
+ stq_le_p(&s->womask[addr], mask);
+}
+
+static void vtd_define_long(IntelIOMMUState *s, hwaddr addr, uint32_t val,
+ uint32_t wmask, uint32_t w1cmask)
+{
+ stl_le_p(&s->csr[addr], val);
+ stl_le_p(&s->wmask[addr], wmask);
+ stl_le_p(&s->w1cmask[addr], w1cmask);
+}
+
+static void vtd_define_long_wo(IntelIOMMUState *s, hwaddr addr, uint32_t mask)
+{
+ stl_le_p(&s->womask[addr], mask);
+}
+
+/* "External" get/set operations */
+static void vtd_set_quad(IntelIOMMUState *s, hwaddr addr, uint64_t val)
+{
+ uint64_t oldval = ldq_le_p(&s->csr[addr]);
+ uint64_t wmask = ldq_le_p(&s->wmask[addr]);
+ uint64_t w1cmask = ldq_le_p(&s->w1cmask[addr]);
+ stq_le_p(&s->csr[addr],
+ ((oldval & ~wmask) | (val & wmask)) & ~(w1cmask & val));
+}
+
+static void vtd_set_long(IntelIOMMUState *s, hwaddr addr, uint32_t val)
+{
+ uint32_t oldval = ldl_le_p(&s->csr[addr]);
+ uint32_t wmask = ldl_le_p(&s->wmask[addr]);
+ uint32_t w1cmask = ldl_le_p(&s->w1cmask[addr]);
+ stl_le_p(&s->csr[addr],
+ ((oldval & ~wmask) | (val & wmask)) & ~(w1cmask & val));
+}
+
+static uint64_t vtd_get_quad(IntelIOMMUState *s, hwaddr addr)
+{
+ uint64_t val = ldq_le_p(&s->csr[addr]);
+ uint64_t womask = ldq_le_p(&s->womask[addr]);
+ return val & ~womask;
+}
+
+static uint32_t vtd_get_long(IntelIOMMUState *s, hwaddr addr)
+{
+ uint32_t val = ldl_le_p(&s->csr[addr]);
+ uint32_t womask = ldl_le_p(&s->womask[addr]);
+ return val & ~womask;
+}
+
+/* "Internal" get/set operations */
+static uint64_t vtd_get_quad_raw(IntelIOMMUState *s, hwaddr addr)
+{
+ return ldq_le_p(&s->csr[addr]);
+}
+
+static uint32_t vtd_get_long_raw(IntelIOMMUState *s, hwaddr addr)
+{
+ return ldl_le_p(&s->csr[addr]);
+}
+
+static void vtd_set_quad_raw(IntelIOMMUState *s, hwaddr addr, uint64_t val)
+{
+ stq_le_p(&s->csr[addr], val);
+}
+
+static uint32_t vtd_set_clear_mask_long(IntelIOMMUState *s, hwaddr addr,
+ uint32_t clear, uint32_t mask)
+{
+ uint32_t new_val = (ldl_le_p(&s->csr[addr]) & ~clear) | mask;
+ stl_le_p(&s->csr[addr], new_val);
+ return new_val;
+}
+
+static uint64_t vtd_set_clear_mask_quad(IntelIOMMUState *s, hwaddr addr,
+ uint64_t clear, uint64_t mask)
+{
+ uint64_t new_val = (ldq_le_p(&s->csr[addr]) & ~clear) | mask;
+ stq_le_p(&s->csr[addr], new_val);
+ return new_val;
+}
+
+/* GHashTable functions */
+static gboolean vtd_uint64_equal(gconstpointer v1, gconstpointer v2)
+{
+ return *((const uint64_t *)v1) == *((const uint64_t *)v2);
+}
+
+static guint vtd_uint64_hash(gconstpointer v)
+{
+ return (guint)*(const uint64_t *)v;
+}
+
+static gboolean vtd_hash_remove_by_domain(gpointer key, gpointer value,
+ gpointer user_data)
+{
+ VTDIOTLBEntry *entry = (VTDIOTLBEntry *)value;
+ uint16_t domain_id = *(uint16_t *)user_data;
+ return entry->domain_id == domain_id;
+}
+
+static gboolean vtd_hash_remove_by_page(gpointer key, gpointer value,
+ gpointer user_data)
+{
+ VTDIOTLBEntry *entry = (VTDIOTLBEntry *)value;
+ VTDIOTLBPageInvInfo *info = (VTDIOTLBPageInvInfo *)user_data;
+ uint64_t gfn = info->gfn & info->mask;
+ return (entry->domain_id == info->domain_id) &&
+ ((entry->gfn & info->mask) == gfn);
+}
+
+/* Reset all the gen of VTDAddressSpace to zero and set the gen of
+ * IntelIOMMUState to 1.
+ */
+static void vtd_reset_context_cache(IntelIOMMUState *s)
+{
+ VTDAddressSpace **pvtd_as;
+ VTDAddressSpace *vtd_as;
+ uint32_t bus_it;
+ uint32_t devfn_it;
+
+ VTD_DPRINTF(CACHE, "global context_cache_gen=1");
+ for (bus_it = 0; bus_it < VTD_PCI_BUS_MAX; ++bus_it) {
+ pvtd_as = s->address_spaces[bus_it];
+ if (!pvtd_as) {
+ continue;
+ }
+ for (devfn_it = 0; devfn_it < VTD_PCI_DEVFN_MAX; ++devfn_it) {
+ vtd_as = pvtd_as[devfn_it];
+ if (!vtd_as) {
+ continue;
+ }
+ vtd_as->context_cache_entry.context_cache_gen = 0;
+ }
+ }
+ s->context_cache_gen = 1;
+}
+
+static void vtd_reset_iotlb(IntelIOMMUState *s)
+{
+ assert(s->iotlb);
+ g_hash_table_remove_all(s->iotlb);
+}
+
+static VTDIOTLBEntry *vtd_lookup_iotlb(IntelIOMMUState *s, uint16_t source_id,
+ hwaddr addr)
+{
+ uint64_t key;
+
+ key = (addr >> VTD_PAGE_SHIFT_4K) |
+ ((uint64_t)(source_id) << VTD_IOTLB_SID_SHIFT);
+ return g_hash_table_lookup(s->iotlb, &key);
+
+}
+
+static void vtd_update_iotlb(IntelIOMMUState *s, uint16_t source_id,
+ uint16_t domain_id, hwaddr addr, uint64_t slpte,
+ bool read_flags, bool write_flags)
+{
+ VTDIOTLBEntry *entry = g_malloc(sizeof(*entry));
+ uint64_t *key = g_malloc(sizeof(*key));
+ uint64_t gfn = addr >> VTD_PAGE_SHIFT_4K;
+
+ VTD_DPRINTF(CACHE, "update iotlb sid 0x%"PRIx16 " gpa 0x%"PRIx64
+ " slpte 0x%"PRIx64 " did 0x%"PRIx16, source_id, addr, slpte,
+ domain_id);
+ if (g_hash_table_size(s->iotlb) >= VTD_IOTLB_MAX_SIZE) {
+ VTD_DPRINTF(CACHE, "iotlb exceeds size limit, forced to reset");
+ vtd_reset_iotlb(s);
+ }
+
+ entry->gfn = gfn;
+ entry->domain_id = domain_id;
+ entry->slpte = slpte;
+ entry->read_flags = read_flags;
+ entry->write_flags = write_flags;
+ *key = gfn | ((uint64_t)(source_id) << VTD_IOTLB_SID_SHIFT);
+ g_hash_table_replace(s->iotlb, key, entry);
+}
+
+/* Given the reg addr of both the message data and address, generate an
+ * interrupt via MSI.
+ */
+static void vtd_generate_interrupt(IntelIOMMUState *s, hwaddr mesg_addr_reg,
+ hwaddr mesg_data_reg)
+{
+ hwaddr addr;
+ uint32_t data;
+
+ assert(mesg_data_reg < DMAR_REG_SIZE);
+ assert(mesg_addr_reg < DMAR_REG_SIZE);
+
+ addr = vtd_get_long_raw(s, mesg_addr_reg);
+ data = vtd_get_long_raw(s, mesg_data_reg);
+
+ VTD_DPRINTF(FLOG, "msi: addr 0x%"PRIx64 " data 0x%"PRIx32, addr, data);
+ address_space_stl_le(&address_space_memory, addr, data,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+}
+
+/* Generate a fault event to software via MSI if conditions are met.
+ * Notice that the value of FSTS_REG being passed to it should be the one
+ * before any update.
+ */
+static void vtd_generate_fault_event(IntelIOMMUState *s, uint32_t pre_fsts)
+{
+ if (pre_fsts & VTD_FSTS_PPF || pre_fsts & VTD_FSTS_PFO ||
+ pre_fsts & VTD_FSTS_IQE) {
+ VTD_DPRINTF(FLOG, "there are previous interrupt conditions "
+ "to be serviced by software, fault event is not generated "
+ "(FSTS_REG 0x%"PRIx32 ")", pre_fsts);
+ return;
+ }
+ vtd_set_clear_mask_long(s, DMAR_FECTL_REG, 0, VTD_FECTL_IP);
+ if (vtd_get_long_raw(s, DMAR_FECTL_REG) & VTD_FECTL_IM) {
+ VTD_DPRINTF(FLOG, "Interrupt Mask set, fault event is not generated");
+ } else {
+ vtd_generate_interrupt(s, DMAR_FEADDR_REG, DMAR_FEDATA_REG);
+ vtd_set_clear_mask_long(s, DMAR_FECTL_REG, VTD_FECTL_IP, 0);
+ }
+}
+
+/* Check if the Fault (F) field of the Fault Recording Register referenced by
+ * @index is Set.
+ */
+static bool vtd_is_frcd_set(IntelIOMMUState *s, uint16_t index)
+{
+ /* Each reg is 128-bit */
+ hwaddr addr = DMAR_FRCD_REG_OFFSET + (((uint64_t)index) << 4);
+ addr += 8; /* Access the high 64-bit half */
+
+ assert(index < DMAR_FRCD_REG_NR);
+
+ return vtd_get_quad_raw(s, addr) & VTD_FRCD_F;
+}
+
+/* Update the PPF field of Fault Status Register.
+ * Should be called whenever change the F field of any fault recording
+ * registers.
+ */
+static void vtd_update_fsts_ppf(IntelIOMMUState *s)
+{
+ uint32_t i;
+ uint32_t ppf_mask = 0;
+
+ for (i = 0; i < DMAR_FRCD_REG_NR; i++) {
+ if (vtd_is_frcd_set(s, i)) {
+ ppf_mask = VTD_FSTS_PPF;
+ break;
+ }
+ }
+ vtd_set_clear_mask_long(s, DMAR_FSTS_REG, VTD_FSTS_PPF, ppf_mask);
+ VTD_DPRINTF(FLOG, "set PPF of FSTS_REG to %d", ppf_mask ? 1 : 0);
+}
+
+static void vtd_set_frcd_and_update_ppf(IntelIOMMUState *s, uint16_t index)
+{
+ /* Each reg is 128-bit */
+ hwaddr addr = DMAR_FRCD_REG_OFFSET + (((uint64_t)index) << 4);
+ addr += 8; /* Access the high 64-bit half */
+
+ assert(index < DMAR_FRCD_REG_NR);
+
+ vtd_set_clear_mask_quad(s, addr, 0, VTD_FRCD_F);
+ vtd_update_fsts_ppf(s);
+}
+
+/* Must not update F field now, should be done later */
+static void vtd_record_frcd(IntelIOMMUState *s, uint16_t index,
+ uint16_t source_id, hwaddr addr,
+ VTDFaultReason fault, bool is_write)
+{
+ uint64_t hi = 0, lo;
+ hwaddr frcd_reg_addr = DMAR_FRCD_REG_OFFSET + (((uint64_t)index) << 4);
+
+ assert(index < DMAR_FRCD_REG_NR);
+
+ lo = VTD_FRCD_FI(addr);
+ hi = VTD_FRCD_SID(source_id) | VTD_FRCD_FR(fault);
+ if (!is_write) {
+ hi |= VTD_FRCD_T;
+ }
+ vtd_set_quad_raw(s, frcd_reg_addr, lo);
+ vtd_set_quad_raw(s, frcd_reg_addr + 8, hi);
+ VTD_DPRINTF(FLOG, "record to FRCD_REG #%"PRIu16 ": hi 0x%"PRIx64
+ ", lo 0x%"PRIx64, index, hi, lo);
+}
+
+/* Try to collapse multiple pending faults from the same requester */
+static bool vtd_try_collapse_fault(IntelIOMMUState *s, uint16_t source_id)
+{
+ uint32_t i;
+ uint64_t frcd_reg;
+ hwaddr addr = DMAR_FRCD_REG_OFFSET + 8; /* The high 64-bit half */
+
+ for (i = 0; i < DMAR_FRCD_REG_NR; i++) {
+ frcd_reg = vtd_get_quad_raw(s, addr);
+ VTD_DPRINTF(FLOG, "frcd_reg #%d 0x%"PRIx64, i, frcd_reg);
+ if ((frcd_reg & VTD_FRCD_F) &&
+ ((frcd_reg & VTD_FRCD_SID_MASK) == source_id)) {
+ return true;
+ }
+ addr += 16; /* 128-bit for each */
+ }
+ return false;
+}
+
+/* Log and report an DMAR (address translation) fault to software */
+static void vtd_report_dmar_fault(IntelIOMMUState *s, uint16_t source_id,
+ hwaddr addr, VTDFaultReason fault,
+ bool is_write)
+{
+ uint32_t fsts_reg = vtd_get_long_raw(s, DMAR_FSTS_REG);
+
+ assert(fault < VTD_FR_MAX);
+
+ if (fault == VTD_FR_RESERVED_ERR) {
+ /* This is not a normal fault reason case. Drop it. */
+ return;
+ }
+ VTD_DPRINTF(FLOG, "sid 0x%"PRIx16 ", fault %d, addr 0x%"PRIx64
+ ", is_write %d", source_id, fault, addr, is_write);
+ if (fsts_reg & VTD_FSTS_PFO) {
+ VTD_DPRINTF(FLOG, "new fault is not recorded due to "
+ "Primary Fault Overflow");
+ return;
+ }
+ if (vtd_try_collapse_fault(s, source_id)) {
+ VTD_DPRINTF(FLOG, "new fault is not recorded due to "
+ "compression of faults");
+ return;
+ }
+ if (vtd_is_frcd_set(s, s->next_frcd_reg)) {
+ VTD_DPRINTF(FLOG, "Primary Fault Overflow and "
+ "new fault is not recorded, set PFO field");
+ vtd_set_clear_mask_long(s, DMAR_FSTS_REG, 0, VTD_FSTS_PFO);
+ return;
+ }
+
+ vtd_record_frcd(s, s->next_frcd_reg, source_id, addr, fault, is_write);
+
+ if (fsts_reg & VTD_FSTS_PPF) {
+ VTD_DPRINTF(FLOG, "there are pending faults already, "
+ "fault event is not generated");
+ vtd_set_frcd_and_update_ppf(s, s->next_frcd_reg);
+ s->next_frcd_reg++;
+ if (s->next_frcd_reg == DMAR_FRCD_REG_NR) {
+ s->next_frcd_reg = 0;
+ }
+ } else {
+ vtd_set_clear_mask_long(s, DMAR_FSTS_REG, VTD_FSTS_FRI_MASK,
+ VTD_FSTS_FRI(s->next_frcd_reg));
+ vtd_set_frcd_and_update_ppf(s, s->next_frcd_reg); /* Will set PPF */
+ s->next_frcd_reg++;
+ if (s->next_frcd_reg == DMAR_FRCD_REG_NR) {
+ s->next_frcd_reg = 0;
+ }
+ /* This case actually cause the PPF to be Set.
+ * So generate fault event (interrupt).
+ */
+ vtd_generate_fault_event(s, fsts_reg);
+ }
+}
+
+/* Handle Invalidation Queue Errors of queued invalidation interface error
+ * conditions.
+ */
+static void vtd_handle_inv_queue_error(IntelIOMMUState *s)
+{
+ uint32_t fsts_reg = vtd_get_long_raw(s, DMAR_FSTS_REG);
+
+ vtd_set_clear_mask_long(s, DMAR_FSTS_REG, 0, VTD_FSTS_IQE);
+ vtd_generate_fault_event(s, fsts_reg);
+}
+
+/* Set the IWC field and try to generate an invalidation completion interrupt */
+static void vtd_generate_completion_event(IntelIOMMUState *s)
+{
+ VTD_DPRINTF(INV, "completes an invalidation wait command with "
+ "Interrupt Flag");
+ if (vtd_get_long_raw(s, DMAR_ICS_REG) & VTD_ICS_IWC) {
+ VTD_DPRINTF(INV, "there is a previous interrupt condition to be "
+ "serviced by software, "
+ "new invalidation event is not generated");
+ return;
+ }
+ vtd_set_clear_mask_long(s, DMAR_ICS_REG, 0, VTD_ICS_IWC);
+ vtd_set_clear_mask_long(s, DMAR_IECTL_REG, 0, VTD_IECTL_IP);
+ if (vtd_get_long_raw(s, DMAR_IECTL_REG) & VTD_IECTL_IM) {
+ VTD_DPRINTF(INV, "IM filed in IECTL_REG is set, new invalidation "
+ "event is not generated");
+ return;
+ } else {
+ /* Generate the interrupt event */
+ vtd_generate_interrupt(s, DMAR_IEADDR_REG, DMAR_IEDATA_REG);
+ vtd_set_clear_mask_long(s, DMAR_IECTL_REG, VTD_IECTL_IP, 0);
+ }
+}
+
+static inline bool vtd_root_entry_present(VTDRootEntry *root)
+{
+ return root->val & VTD_ROOT_ENTRY_P;
+}
+
+static int vtd_get_root_entry(IntelIOMMUState *s, uint8_t index,
+ VTDRootEntry *re)
+{
+ dma_addr_t addr;
+
+ addr = s->root + index * sizeof(*re);
+ if (dma_memory_read(&address_space_memory, addr, re, sizeof(*re))) {
+ VTD_DPRINTF(GENERAL, "error: fail to access root-entry at 0x%"PRIx64
+ " + %"PRIu8, s->root, index);
+ re->val = 0;
+ return -VTD_FR_ROOT_TABLE_INV;
+ }
+ re->val = le64_to_cpu(re->val);
+ return 0;
+}
+
+static inline bool vtd_context_entry_present(VTDContextEntry *context)
+{
+ return context->lo & VTD_CONTEXT_ENTRY_P;
+}
+
+static int vtd_get_context_entry_from_root(VTDRootEntry *root, uint8_t index,
+ VTDContextEntry *ce)
+{
+ dma_addr_t addr;
+
+ if (!vtd_root_entry_present(root)) {
+ VTD_DPRINTF(GENERAL, "error: root-entry is not present");
+ return -VTD_FR_ROOT_ENTRY_P;
+ }
+ addr = (root->val & VTD_ROOT_ENTRY_CTP) + index * sizeof(*ce);
+ if (dma_memory_read(&address_space_memory, addr, ce, sizeof(*ce))) {
+ VTD_DPRINTF(GENERAL, "error: fail to access context-entry at 0x%"PRIx64
+ " + %"PRIu8,
+ (uint64_t)(root->val & VTD_ROOT_ENTRY_CTP), index);
+ return -VTD_FR_CONTEXT_TABLE_INV;
+ }
+ ce->lo = le64_to_cpu(ce->lo);
+ ce->hi = le64_to_cpu(ce->hi);
+ return 0;
+}
+
+static inline dma_addr_t vtd_get_slpt_base_from_context(VTDContextEntry *ce)
+{
+ return ce->lo & VTD_CONTEXT_ENTRY_SLPTPTR;
+}
+
+/* The shift of an addr for a certain level of paging structure */
+static inline uint32_t vtd_slpt_level_shift(uint32_t level)
+{
+ return VTD_PAGE_SHIFT_4K + (level - 1) * VTD_SL_LEVEL_BITS;
+}
+
+static inline uint64_t vtd_get_slpte_addr(uint64_t slpte)
+{
+ return slpte & VTD_SL_PT_BASE_ADDR_MASK;
+}
+
+/* Whether the pte indicates the address of the page frame */
+static inline bool vtd_is_last_slpte(uint64_t slpte, uint32_t level)
+{
+ return level == VTD_SL_PT_LEVEL || (slpte & VTD_SL_PT_PAGE_SIZE_MASK);
+}
+
+/* Get the content of a spte located in @base_addr[@index] */
+static uint64_t vtd_get_slpte(dma_addr_t base_addr, uint32_t index)
+{
+ uint64_t slpte;
+
+ assert(index < VTD_SL_PT_ENTRY_NR);
+
+ if (dma_memory_read(&address_space_memory,
+ base_addr + index * sizeof(slpte), &slpte,
+ sizeof(slpte))) {
+ slpte = (uint64_t)-1;
+ return slpte;
+ }
+ slpte = le64_to_cpu(slpte);
+ return slpte;
+}
+
+/* Given a gpa and the level of paging structure, return the offset of current
+ * level.
+ */
+static inline uint32_t vtd_gpa_level_offset(uint64_t gpa, uint32_t level)
+{
+ return (gpa >> vtd_slpt_level_shift(level)) &
+ ((1ULL << VTD_SL_LEVEL_BITS) - 1);
+}
+
+/* Check Capability Register to see if the @level of page-table is supported */
+static inline bool vtd_is_level_supported(IntelIOMMUState *s, uint32_t level)
+{
+ return VTD_CAP_SAGAW_MASK & s->cap &
+ (1ULL << (level - 2 + VTD_CAP_SAGAW_SHIFT));
+}
+
+/* Get the page-table level that hardware should use for the second-level
+ * page-table walk from the Address Width field of context-entry.
+ */
+static inline uint32_t vtd_get_level_from_context_entry(VTDContextEntry *ce)
+{
+ return 2 + (ce->hi & VTD_CONTEXT_ENTRY_AW);
+}
+
+static inline uint32_t vtd_get_agaw_from_context_entry(VTDContextEntry *ce)
+{
+ return 30 + (ce->hi & VTD_CONTEXT_ENTRY_AW) * 9;
+}
+
+static const uint64_t vtd_paging_entry_rsvd_field[] = {
+ [0] = ~0ULL,
+ /* For not large page */
+ [1] = 0x800ULL | ~(VTD_HAW_MASK | VTD_SL_IGN_COM),
+ [2] = 0x800ULL | ~(VTD_HAW_MASK | VTD_SL_IGN_COM),
+ [3] = 0x800ULL | ~(VTD_HAW_MASK | VTD_SL_IGN_COM),
+ [4] = 0x880ULL | ~(VTD_HAW_MASK | VTD_SL_IGN_COM),
+ /* For large page */
+ [5] = 0x800ULL | ~(VTD_HAW_MASK | VTD_SL_IGN_COM),
+ [6] = 0x1ff800ULL | ~(VTD_HAW_MASK | VTD_SL_IGN_COM),
+ [7] = 0x3ffff800ULL | ~(VTD_HAW_MASK | VTD_SL_IGN_COM),
+ [8] = 0x880ULL | ~(VTD_HAW_MASK | VTD_SL_IGN_COM),
+};
+
+static bool vtd_slpte_nonzero_rsvd(uint64_t slpte, uint32_t level)
+{
+ if (slpte & VTD_SL_PT_PAGE_SIZE_MASK) {
+ /* Maybe large page */
+ return slpte & vtd_paging_entry_rsvd_field[level + 4];
+ } else {
+ return slpte & vtd_paging_entry_rsvd_field[level];
+ }
+}
+
+/* Given the @gpa, get relevant @slptep. @slpte_level will be the last level
+ * of the translation, can be used for deciding the size of large page.
+ */
+static int vtd_gpa_to_slpte(VTDContextEntry *ce, uint64_t gpa, bool is_write,
+ uint64_t *slptep, uint32_t *slpte_level,
+ bool *reads, bool *writes)
+{
+ dma_addr_t addr = vtd_get_slpt_base_from_context(ce);
+ uint32_t level = vtd_get_level_from_context_entry(ce);
+ uint32_t offset;
+ uint64_t slpte;
+ uint32_t ce_agaw = vtd_get_agaw_from_context_entry(ce);
+ uint64_t access_right_check;
+
+ /* Check if @gpa is above 2^X-1, where X is the minimum of MGAW in CAP_REG
+ * and AW in context-entry.
+ */
+ if (gpa & ~((1ULL << MIN(ce_agaw, VTD_MGAW)) - 1)) {
+ VTD_DPRINTF(GENERAL, "error: gpa 0x%"PRIx64 " exceeds limits", gpa);
+ return -VTD_FR_ADDR_BEYOND_MGAW;
+ }
+
+ /* FIXME: what is the Atomics request here? */
+ access_right_check = is_write ? VTD_SL_W : VTD_SL_R;
+
+ while (true) {
+ offset = vtd_gpa_level_offset(gpa, level);
+ slpte = vtd_get_slpte(addr, offset);
+
+ if (slpte == (uint64_t)-1) {
+ VTD_DPRINTF(GENERAL, "error: fail to access second-level paging "
+ "entry at level %"PRIu32 " for gpa 0x%"PRIx64,
+ level, gpa);
+ if (level == vtd_get_level_from_context_entry(ce)) {
+ /* Invalid programming of context-entry */
+ return -VTD_FR_CONTEXT_ENTRY_INV;
+ } else {
+ return -VTD_FR_PAGING_ENTRY_INV;
+ }
+ }
+ *reads = (*reads) && (slpte & VTD_SL_R);
+ *writes = (*writes) && (slpte & VTD_SL_W);
+ if (!(slpte & access_right_check)) {
+ VTD_DPRINTF(GENERAL, "error: lack of %s permission for "
+ "gpa 0x%"PRIx64 " slpte 0x%"PRIx64,
+ (is_write ? "write" : "read"), gpa, slpte);
+ return is_write ? -VTD_FR_WRITE : -VTD_FR_READ;
+ }
+ if (vtd_slpte_nonzero_rsvd(slpte, level)) {
+ VTD_DPRINTF(GENERAL, "error: non-zero reserved field in second "
+ "level paging entry level %"PRIu32 " slpte 0x%"PRIx64,
+ level, slpte);
+ return -VTD_FR_PAGING_ENTRY_RSVD;
+ }
+
+ if (vtd_is_last_slpte(slpte, level)) {
+ *slptep = slpte;
+ *slpte_level = level;
+ return 0;
+ }
+ addr = vtd_get_slpte_addr(slpte);
+ level--;
+ }
+}
+
+/* Map a device to its corresponding domain (context-entry) */
+static int vtd_dev_to_context_entry(IntelIOMMUState *s, uint8_t bus_num,
+ uint8_t devfn, VTDContextEntry *ce)
+{
+ VTDRootEntry re;
+ int ret_fr;
+
+ ret_fr = vtd_get_root_entry(s, bus_num, &re);
+ if (ret_fr) {
+ return ret_fr;
+ }
+
+ if (!vtd_root_entry_present(&re)) {
+ VTD_DPRINTF(GENERAL, "error: root-entry #%"PRIu8 " is not present",
+ bus_num);
+ return -VTD_FR_ROOT_ENTRY_P;
+ } else if (re.rsvd || (re.val & VTD_ROOT_ENTRY_RSVD)) {
+ VTD_DPRINTF(GENERAL, "error: non-zero reserved field in root-entry "
+ "hi 0x%"PRIx64 " lo 0x%"PRIx64, re.rsvd, re.val);
+ return -VTD_FR_ROOT_ENTRY_RSVD;
+ }
+
+ ret_fr = vtd_get_context_entry_from_root(&re, devfn, ce);
+ if (ret_fr) {
+ return ret_fr;
+ }
+
+ if (!vtd_context_entry_present(ce)) {
+ VTD_DPRINTF(GENERAL,
+ "error: context-entry #%"PRIu8 "(bus #%"PRIu8 ") "
+ "is not present", devfn, bus_num);
+ return -VTD_FR_CONTEXT_ENTRY_P;
+ } else if ((ce->hi & VTD_CONTEXT_ENTRY_RSVD_HI) ||
+ (ce->lo & VTD_CONTEXT_ENTRY_RSVD_LO)) {
+ VTD_DPRINTF(GENERAL,
+ "error: non-zero reserved field in context-entry "
+ "hi 0x%"PRIx64 " lo 0x%"PRIx64, ce->hi, ce->lo);
+ return -VTD_FR_CONTEXT_ENTRY_RSVD;
+ }
+ /* Check if the programming of context-entry is valid */
+ if (!vtd_is_level_supported(s, vtd_get_level_from_context_entry(ce))) {
+ VTD_DPRINTF(GENERAL, "error: unsupported Address Width value in "
+ "context-entry hi 0x%"PRIx64 " lo 0x%"PRIx64,
+ ce->hi, ce->lo);
+ return -VTD_FR_CONTEXT_ENTRY_INV;
+ } else if (ce->lo & VTD_CONTEXT_ENTRY_TT) {
+ VTD_DPRINTF(GENERAL, "error: unsupported Translation Type in "
+ "context-entry hi 0x%"PRIx64 " lo 0x%"PRIx64,
+ ce->hi, ce->lo);
+ return -VTD_FR_CONTEXT_ENTRY_INV;
+ }
+ return 0;
+}
+
+static inline uint16_t vtd_make_source_id(uint8_t bus_num, uint8_t devfn)
+{
+ return ((bus_num & 0xffUL) << 8) | (devfn & 0xffUL);
+}
+
+static const bool vtd_qualified_faults[] = {
+ [VTD_FR_RESERVED] = false,
+ [VTD_FR_ROOT_ENTRY_P] = false,
+ [VTD_FR_CONTEXT_ENTRY_P] = true,
+ [VTD_FR_CONTEXT_ENTRY_INV] = true,
+ [VTD_FR_ADDR_BEYOND_MGAW] = true,
+ [VTD_FR_WRITE] = true,
+ [VTD_FR_READ] = true,
+ [VTD_FR_PAGING_ENTRY_INV] = true,
+ [VTD_FR_ROOT_TABLE_INV] = false,
+ [VTD_FR_CONTEXT_TABLE_INV] = false,
+ [VTD_FR_ROOT_ENTRY_RSVD] = false,
+ [VTD_FR_PAGING_ENTRY_RSVD] = true,
+ [VTD_FR_CONTEXT_ENTRY_TT] = true,
+ [VTD_FR_RESERVED_ERR] = false,
+ [VTD_FR_MAX] = false,
+};
+
+/* To see if a fault condition is "qualified", which is reported to software
+ * only if the FPD field in the context-entry used to process the faulting
+ * request is 0.
+ */
+static inline bool vtd_is_qualified_fault(VTDFaultReason fault)
+{
+ return vtd_qualified_faults[fault];
+}
+
+static inline bool vtd_is_interrupt_addr(hwaddr addr)
+{
+ return VTD_INTERRUPT_ADDR_FIRST <= addr && addr <= VTD_INTERRUPT_ADDR_LAST;
+}
+
+/* Map dev to context-entry then do a paging-structures walk to do a iommu
+ * translation.
+ *
+ * Called from RCU critical section.
+ *
+ * @bus_num: The bus number
+ * @devfn: The devfn, which is the combined of device and function number
+ * @is_write: The access is a write operation
+ * @entry: IOMMUTLBEntry that contain the addr to be translated and result
+ */
+static void vtd_do_iommu_translate(VTDAddressSpace *vtd_as, uint8_t bus_num,
+ uint8_t devfn, hwaddr addr, bool is_write,
+ IOMMUTLBEntry *entry)
+{
+ IntelIOMMUState *s = vtd_as->iommu_state;
+ VTDContextEntry ce;
+ VTDContextCacheEntry *cc_entry = &vtd_as->context_cache_entry;
+ uint64_t slpte;
+ uint32_t level;
+ uint16_t source_id = vtd_make_source_id(bus_num, devfn);
+ int ret_fr;
+ bool is_fpd_set = false;
+ bool reads = true;
+ bool writes = true;
+ VTDIOTLBEntry *iotlb_entry;
+
+ /* Check if the request is in interrupt address range */
+ if (vtd_is_interrupt_addr(addr)) {
+ if (is_write) {
+ /* FIXME: since we don't know the length of the access here, we
+ * treat Non-DWORD length write requests without PASID as
+ * interrupt requests, too. Withoud interrupt remapping support,
+ * we just use 1:1 mapping.
+ */
+ VTD_DPRINTF(MMU, "write request to interrupt address "
+ "gpa 0x%"PRIx64, addr);
+ entry->iova = addr & VTD_PAGE_MASK_4K;
+ entry->translated_addr = addr & VTD_PAGE_MASK_4K;
+ entry->addr_mask = ~VTD_PAGE_MASK_4K;
+ entry->perm = IOMMU_WO;
+ return;
+ } else {
+ VTD_DPRINTF(GENERAL, "error: read request from interrupt address "
+ "gpa 0x%"PRIx64, addr);
+ vtd_report_dmar_fault(s, source_id, addr, VTD_FR_READ, is_write);
+ return;
+ }
+ }
+ /* Try to fetch slpte form IOTLB */
+ iotlb_entry = vtd_lookup_iotlb(s, source_id, addr);
+ if (iotlb_entry) {
+ VTD_DPRINTF(CACHE, "hit iotlb sid 0x%"PRIx16 " gpa 0x%"PRIx64
+ " slpte 0x%"PRIx64 " did 0x%"PRIx16, source_id, addr,
+ iotlb_entry->slpte, iotlb_entry->domain_id);
+ slpte = iotlb_entry->slpte;
+ reads = iotlb_entry->read_flags;
+ writes = iotlb_entry->write_flags;
+ goto out;
+ }
+ /* Try to fetch context-entry from cache first */
+ if (cc_entry->context_cache_gen == s->context_cache_gen) {
+ VTD_DPRINTF(CACHE, "hit context-cache bus %d devfn %d "
+ "(hi %"PRIx64 " lo %"PRIx64 " gen %"PRIu32 ")",
+ bus_num, devfn, cc_entry->context_entry.hi,
+ cc_entry->context_entry.lo, cc_entry->context_cache_gen);
+ ce = cc_entry->context_entry;
+ is_fpd_set = ce.lo & VTD_CONTEXT_ENTRY_FPD;
+ } else {
+ ret_fr = vtd_dev_to_context_entry(s, bus_num, devfn, &ce);
+ is_fpd_set = ce.lo & VTD_CONTEXT_ENTRY_FPD;
+ if (ret_fr) {
+ ret_fr = -ret_fr;
+ if (is_fpd_set && vtd_is_qualified_fault(ret_fr)) {
+ VTD_DPRINTF(FLOG, "fault processing is disabled for DMA "
+ "requests through this context-entry "
+ "(with FPD Set)");
+ } else {
+ vtd_report_dmar_fault(s, source_id, addr, ret_fr, is_write);
+ }
+ return;
+ }
+ /* Update context-cache */
+ VTD_DPRINTF(CACHE, "update context-cache bus %d devfn %d "
+ "(hi %"PRIx64 " lo %"PRIx64 " gen %"PRIu32 "->%"PRIu32 ")",
+ bus_num, devfn, ce.hi, ce.lo,
+ cc_entry->context_cache_gen, s->context_cache_gen);
+ cc_entry->context_entry = ce;
+ cc_entry->context_cache_gen = s->context_cache_gen;
+ }
+
+ ret_fr = vtd_gpa_to_slpte(&ce, addr, is_write, &slpte, &level,
+ &reads, &writes);
+ if (ret_fr) {
+ ret_fr = -ret_fr;
+ if (is_fpd_set && vtd_is_qualified_fault(ret_fr)) {
+ VTD_DPRINTF(FLOG, "fault processing is disabled for DMA requests "
+ "through this context-entry (with FPD Set)");
+ } else {
+ vtd_report_dmar_fault(s, source_id, addr, ret_fr, is_write);
+ }
+ return;
+ }
+
+ vtd_update_iotlb(s, source_id, VTD_CONTEXT_ENTRY_DID(ce.hi), addr, slpte,
+ reads, writes);
+out:
+ entry->iova = addr & VTD_PAGE_MASK_4K;
+ entry->translated_addr = vtd_get_slpte_addr(slpte) & VTD_PAGE_MASK_4K;
+ entry->addr_mask = ~VTD_PAGE_MASK_4K;
+ entry->perm = (writes ? 2 : 0) + (reads ? 1 : 0);
+}
+
+static void vtd_root_table_setup(IntelIOMMUState *s)
+{
+ s->root = vtd_get_quad_raw(s, DMAR_RTADDR_REG);
+ s->root_extended = s->root & VTD_RTADDR_RTT;
+ s->root &= VTD_RTADDR_ADDR_MASK;
+
+ VTD_DPRINTF(CSR, "root_table addr 0x%"PRIx64 " %s", s->root,
+ (s->root_extended ? "(extended)" : ""));
+}
+
+static void vtd_context_global_invalidate(IntelIOMMUState *s)
+{
+ s->context_cache_gen++;
+ if (s->context_cache_gen == VTD_CONTEXT_CACHE_GEN_MAX) {
+ vtd_reset_context_cache(s);
+ }
+}
+
+/* Do a context-cache device-selective invalidation.
+ * @func_mask: FM field after shifting
+ */
+static void vtd_context_device_invalidate(IntelIOMMUState *s,
+ uint16_t source_id,
+ uint16_t func_mask)
+{
+ uint16_t mask;
+ VTDAddressSpace **pvtd_as;
+ VTDAddressSpace *vtd_as;
+ uint16_t devfn;
+ uint16_t devfn_it;
+
+ switch (func_mask & 3) {
+ case 0:
+ mask = 0; /* No bits in the SID field masked */
+ break;
+ case 1:
+ mask = 4; /* Mask bit 2 in the SID field */
+ break;
+ case 2:
+ mask = 6; /* Mask bit 2:1 in the SID field */
+ break;
+ case 3:
+ mask = 7; /* Mask bit 2:0 in the SID field */
+ break;
+ }
+ VTD_DPRINTF(INV, "device-selective invalidation source 0x%"PRIx16
+ " mask %"PRIu16, source_id, mask);
+ pvtd_as = s->address_spaces[VTD_SID_TO_BUS(source_id)];
+ if (pvtd_as) {
+ devfn = VTD_SID_TO_DEVFN(source_id);
+ for (devfn_it = 0; devfn_it < VTD_PCI_DEVFN_MAX; ++devfn_it) {
+ vtd_as = pvtd_as[devfn_it];
+ if (vtd_as && ((devfn_it & mask) == (devfn & mask))) {
+ VTD_DPRINTF(INV, "invalidate context-cahce of devfn 0x%"PRIx16,
+ devfn_it);
+ vtd_as->context_cache_entry.context_cache_gen = 0;
+ }
+ }
+ }
+}
+
+/* Context-cache invalidation
+ * Returns the Context Actual Invalidation Granularity.
+ * @val: the content of the CCMD_REG
+ */
+static uint64_t vtd_context_cache_invalidate(IntelIOMMUState *s, uint64_t val)
+{
+ uint64_t caig;
+ uint64_t type = val & VTD_CCMD_CIRG_MASK;
+
+ switch (type) {
+ case VTD_CCMD_DOMAIN_INVL:
+ VTD_DPRINTF(INV, "domain-selective invalidation domain 0x%"PRIx16,
+ (uint16_t)VTD_CCMD_DID(val));
+ /* Fall through */
+ case VTD_CCMD_GLOBAL_INVL:
+ VTD_DPRINTF(INV, "global invalidation");
+ caig = VTD_CCMD_GLOBAL_INVL_A;
+ vtd_context_global_invalidate(s);
+ break;
+
+ case VTD_CCMD_DEVICE_INVL:
+ caig = VTD_CCMD_DEVICE_INVL_A;
+ vtd_context_device_invalidate(s, VTD_CCMD_SID(val), VTD_CCMD_FM(val));
+ break;
+
+ default:
+ VTD_DPRINTF(GENERAL, "error: invalid granularity");
+ caig = 0;
+ }
+ return caig;
+}
+
+static void vtd_iotlb_global_invalidate(IntelIOMMUState *s)
+{
+ vtd_reset_iotlb(s);
+}
+
+static void vtd_iotlb_domain_invalidate(IntelIOMMUState *s, uint16_t domain_id)
+{
+ g_hash_table_foreach_remove(s->iotlb, vtd_hash_remove_by_domain,
+ &domain_id);
+}
+
+static void vtd_iotlb_page_invalidate(IntelIOMMUState *s, uint16_t domain_id,
+ hwaddr addr, uint8_t am)
+{
+ VTDIOTLBPageInvInfo info;
+
+ assert(am <= VTD_MAMV);
+ info.domain_id = domain_id;
+ info.gfn = addr >> VTD_PAGE_SHIFT_4K;
+ info.mask = ~((1 << am) - 1);
+ g_hash_table_foreach_remove(s->iotlb, vtd_hash_remove_by_page, &info);
+}
+
+/* Flush IOTLB
+ * Returns the IOTLB Actual Invalidation Granularity.
+ * @val: the content of the IOTLB_REG
+ */
+static uint64_t vtd_iotlb_flush(IntelIOMMUState *s, uint64_t val)
+{
+ uint64_t iaig;
+ uint64_t type = val & VTD_TLB_FLUSH_GRANU_MASK;
+ uint16_t domain_id;
+ hwaddr addr;
+ uint8_t am;
+
+ switch (type) {
+ case VTD_TLB_GLOBAL_FLUSH:
+ VTD_DPRINTF(INV, "global invalidation");
+ iaig = VTD_TLB_GLOBAL_FLUSH_A;
+ vtd_iotlb_global_invalidate(s);
+ break;
+
+ case VTD_TLB_DSI_FLUSH:
+ domain_id = VTD_TLB_DID(val);
+ VTD_DPRINTF(INV, "domain-selective invalidation domain 0x%"PRIx16,
+ domain_id);
+ iaig = VTD_TLB_DSI_FLUSH_A;
+ vtd_iotlb_domain_invalidate(s, domain_id);
+ break;
+
+ case VTD_TLB_PSI_FLUSH:
+ domain_id = VTD_TLB_DID(val);
+ addr = vtd_get_quad_raw(s, DMAR_IVA_REG);
+ am = VTD_IVA_AM(addr);
+ addr = VTD_IVA_ADDR(addr);
+ VTD_DPRINTF(INV, "page-selective invalidation domain 0x%"PRIx16
+ " addr 0x%"PRIx64 " mask %"PRIu8, domain_id, addr, am);
+ if (am > VTD_MAMV) {
+ VTD_DPRINTF(GENERAL, "error: supported max address mask value is "
+ "%"PRIu8, (uint8_t)VTD_MAMV);
+ iaig = 0;
+ break;
+ }
+ iaig = VTD_TLB_PSI_FLUSH_A;
+ vtd_iotlb_page_invalidate(s, domain_id, addr, am);
+ break;
+
+ default:
+ VTD_DPRINTF(GENERAL, "error: invalid granularity");
+ iaig = 0;
+ }
+ return iaig;
+}
+
+static inline bool vtd_queued_inv_enable_check(IntelIOMMUState *s)
+{
+ return s->iq_tail == 0;
+}
+
+static inline bool vtd_queued_inv_disable_check(IntelIOMMUState *s)
+{
+ return s->qi_enabled && (s->iq_tail == s->iq_head) &&
+ (s->iq_last_desc_type == VTD_INV_DESC_WAIT);
+}
+
+static void vtd_handle_gcmd_qie(IntelIOMMUState *s, bool en)
+{
+ uint64_t iqa_val = vtd_get_quad_raw(s, DMAR_IQA_REG);
+
+ VTD_DPRINTF(INV, "Queued Invalidation Enable %s", (en ? "on" : "off"));
+ if (en) {
+ if (vtd_queued_inv_enable_check(s)) {
+ s->iq = iqa_val & VTD_IQA_IQA_MASK;
+ /* 2^(x+8) entries */
+ s->iq_size = 1UL << ((iqa_val & VTD_IQA_QS) + 8);
+ s->qi_enabled = true;
+ VTD_DPRINTF(INV, "DMAR_IQA_REG 0x%"PRIx64, iqa_val);
+ VTD_DPRINTF(INV, "Invalidation Queue addr 0x%"PRIx64 " size %d",
+ s->iq, s->iq_size);
+ /* Ok - report back to driver */
+ vtd_set_clear_mask_long(s, DMAR_GSTS_REG, 0, VTD_GSTS_QIES);
+ } else {
+ VTD_DPRINTF(GENERAL, "error: can't enable Queued Invalidation: "
+ "tail %"PRIu16, s->iq_tail);
+ }
+ } else {
+ if (vtd_queued_inv_disable_check(s)) {
+ /* disable Queued Invalidation */
+ vtd_set_quad_raw(s, DMAR_IQH_REG, 0);
+ s->iq_head = 0;
+ s->qi_enabled = false;
+ /* Ok - report back to driver */
+ vtd_set_clear_mask_long(s, DMAR_GSTS_REG, VTD_GSTS_QIES, 0);
+ } else {
+ VTD_DPRINTF(GENERAL, "error: can't disable Queued Invalidation: "
+ "head %"PRIu16 ", tail %"PRIu16
+ ", last_descriptor %"PRIu8,
+ s->iq_head, s->iq_tail, s->iq_last_desc_type);
+ }
+ }
+}
+
+/* Set Root Table Pointer */
+static void vtd_handle_gcmd_srtp(IntelIOMMUState *s)
+{
+ VTD_DPRINTF(CSR, "set Root Table Pointer");
+
+ vtd_root_table_setup(s);
+ /* Ok - report back to driver */
+ vtd_set_clear_mask_long(s, DMAR_GSTS_REG, 0, VTD_GSTS_RTPS);
+}
+
+/* Handle Translation Enable/Disable */
+static void vtd_handle_gcmd_te(IntelIOMMUState *s, bool en)
+{
+ VTD_DPRINTF(CSR, "Translation Enable %s", (en ? "on" : "off"));
+
+ if (en) {
+ s->dmar_enabled = true;
+ /* Ok - report back to driver */
+ vtd_set_clear_mask_long(s, DMAR_GSTS_REG, 0, VTD_GSTS_TES);
+ } else {
+ s->dmar_enabled = false;
+
+ /* Clear the index of Fault Recording Register */
+ s->next_frcd_reg = 0;
+ /* Ok - report back to driver */
+ vtd_set_clear_mask_long(s, DMAR_GSTS_REG, VTD_GSTS_TES, 0);
+ }
+}
+
+/* Handle write to Global Command Register */
+static void vtd_handle_gcmd_write(IntelIOMMUState *s)
+{
+ uint32_t status = vtd_get_long_raw(s, DMAR_GSTS_REG);
+ uint32_t val = vtd_get_long_raw(s, DMAR_GCMD_REG);
+ uint32_t changed = status ^ val;
+
+ VTD_DPRINTF(CSR, "value 0x%"PRIx32 " status 0x%"PRIx32, val, status);
+ if (changed & VTD_GCMD_TE) {
+ /* Translation enable/disable */
+ vtd_handle_gcmd_te(s, val & VTD_GCMD_TE);
+ }
+ if (val & VTD_GCMD_SRTP) {
+ /* Set/update the root-table pointer */
+ vtd_handle_gcmd_srtp(s);
+ }
+ if (changed & VTD_GCMD_QIE) {
+ /* Queued Invalidation Enable */
+ vtd_handle_gcmd_qie(s, val & VTD_GCMD_QIE);
+ }
+}
+
+/* Handle write to Context Command Register */
+static void vtd_handle_ccmd_write(IntelIOMMUState *s)
+{
+ uint64_t ret;
+ uint64_t val = vtd_get_quad_raw(s, DMAR_CCMD_REG);
+
+ /* Context-cache invalidation request */
+ if (val & VTD_CCMD_ICC) {
+ if (s->qi_enabled) {
+ VTD_DPRINTF(GENERAL, "error: Queued Invalidation enabled, "
+ "should not use register-based invalidation");
+ return;
+ }
+ ret = vtd_context_cache_invalidate(s, val);
+ /* Invalidation completed. Change something to show */
+ vtd_set_clear_mask_quad(s, DMAR_CCMD_REG, VTD_CCMD_ICC, 0ULL);
+ ret = vtd_set_clear_mask_quad(s, DMAR_CCMD_REG, VTD_CCMD_CAIG_MASK,
+ ret);
+ VTD_DPRINTF(INV, "CCMD_REG write-back val: 0x%"PRIx64, ret);
+ }
+}
+
+/* Handle write to IOTLB Invalidation Register */
+static void vtd_handle_iotlb_write(IntelIOMMUState *s)
+{
+ uint64_t ret;
+ uint64_t val = vtd_get_quad_raw(s, DMAR_IOTLB_REG);
+
+ /* IOTLB invalidation request */
+ if (val & VTD_TLB_IVT) {
+ if (s->qi_enabled) {
+ VTD_DPRINTF(GENERAL, "error: Queued Invalidation enabled, "
+ "should not use register-based invalidation");
+ return;
+ }
+ ret = vtd_iotlb_flush(s, val);
+ /* Invalidation completed. Change something to show */
+ vtd_set_clear_mask_quad(s, DMAR_IOTLB_REG, VTD_TLB_IVT, 0ULL);
+ ret = vtd_set_clear_mask_quad(s, DMAR_IOTLB_REG,
+ VTD_TLB_FLUSH_GRANU_MASK_A, ret);
+ VTD_DPRINTF(INV, "IOTLB_REG write-back val: 0x%"PRIx64, ret);
+ }
+}
+
+/* Fetch an Invalidation Descriptor from the Invalidation Queue */
+static bool vtd_get_inv_desc(dma_addr_t base_addr, uint32_t offset,
+ VTDInvDesc *inv_desc)
+{
+ dma_addr_t addr = base_addr + offset * sizeof(*inv_desc);
+ if (dma_memory_read(&address_space_memory, addr, inv_desc,
+ sizeof(*inv_desc))) {
+ VTD_DPRINTF(GENERAL, "error: fail to fetch Invalidation Descriptor "
+ "base_addr 0x%"PRIx64 " offset %"PRIu32, base_addr, offset);
+ inv_desc->lo = 0;
+ inv_desc->hi = 0;
+
+ return false;
+ }
+ inv_desc->lo = le64_to_cpu(inv_desc->lo);
+ inv_desc->hi = le64_to_cpu(inv_desc->hi);
+ return true;
+}
+
+static bool vtd_process_wait_desc(IntelIOMMUState *s, VTDInvDesc *inv_desc)
+{
+ if ((inv_desc->hi & VTD_INV_DESC_WAIT_RSVD_HI) ||
+ (inv_desc->lo & VTD_INV_DESC_WAIT_RSVD_LO)) {
+ VTD_DPRINTF(GENERAL, "error: non-zero reserved field in Invalidation "
+ "Wait Descriptor hi 0x%"PRIx64 " lo 0x%"PRIx64,
+ inv_desc->hi, inv_desc->lo);
+ return false;
+ }
+ if (inv_desc->lo & VTD_INV_DESC_WAIT_SW) {
+ /* Status Write */
+ uint32_t status_data = (uint32_t)(inv_desc->lo >>
+ VTD_INV_DESC_WAIT_DATA_SHIFT);
+
+ assert(!(inv_desc->lo & VTD_INV_DESC_WAIT_IF));
+
+ /* FIXME: need to be masked with HAW? */
+ dma_addr_t status_addr = inv_desc->hi;
+ VTD_DPRINTF(INV, "status data 0x%x, status addr 0x%"PRIx64,
+ status_data, status_addr);
+ status_data = cpu_to_le32(status_data);
+ if (dma_memory_write(&address_space_memory, status_addr, &status_data,
+ sizeof(status_data))) {
+ VTD_DPRINTF(GENERAL, "error: fail to perform a coherent write");
+ return false;
+ }
+ } else if (inv_desc->lo & VTD_INV_DESC_WAIT_IF) {
+ /* Interrupt flag */
+ VTD_DPRINTF(INV, "Invalidation Wait Descriptor interrupt completion");
+ vtd_generate_completion_event(s);
+ } else {
+ VTD_DPRINTF(GENERAL, "error: invalid Invalidation Wait Descriptor: "
+ "hi 0x%"PRIx64 " lo 0x%"PRIx64, inv_desc->hi, inv_desc->lo);
+ return false;
+ }
+ return true;
+}
+
+static bool vtd_process_context_cache_desc(IntelIOMMUState *s,
+ VTDInvDesc *inv_desc)
+{
+ if ((inv_desc->lo & VTD_INV_DESC_CC_RSVD) || inv_desc->hi) {
+ VTD_DPRINTF(GENERAL, "error: non-zero reserved field in Context-cache "
+ "Invalidate Descriptor");
+ return false;
+ }
+ switch (inv_desc->lo & VTD_INV_DESC_CC_G) {
+ case VTD_INV_DESC_CC_DOMAIN:
+ VTD_DPRINTF(INV, "domain-selective invalidation domain 0x%"PRIx16,
+ (uint16_t)VTD_INV_DESC_CC_DID(inv_desc->lo));
+ /* Fall through */
+ case VTD_INV_DESC_CC_GLOBAL:
+ VTD_DPRINTF(INV, "global invalidation");
+ vtd_context_global_invalidate(s);
+ break;
+
+ case VTD_INV_DESC_CC_DEVICE:
+ vtd_context_device_invalidate(s, VTD_INV_DESC_CC_SID(inv_desc->lo),
+ VTD_INV_DESC_CC_FM(inv_desc->lo));
+ break;
+
+ default:
+ VTD_DPRINTF(GENERAL, "error: invalid granularity in Context-cache "
+ "Invalidate Descriptor hi 0x%"PRIx64 " lo 0x%"PRIx64,
+ inv_desc->hi, inv_desc->lo);
+ return false;
+ }
+ return true;
+}
+
+static bool vtd_process_iotlb_desc(IntelIOMMUState *s, VTDInvDesc *inv_desc)
+{
+ uint16_t domain_id;
+ uint8_t am;
+ hwaddr addr;
+
+ if ((inv_desc->lo & VTD_INV_DESC_IOTLB_RSVD_LO) ||
+ (inv_desc->hi & VTD_INV_DESC_IOTLB_RSVD_HI)) {
+ VTD_DPRINTF(GENERAL, "error: non-zero reserved field in IOTLB "
+ "Invalidate Descriptor hi 0x%"PRIx64 " lo 0x%"PRIx64,
+ inv_desc->hi, inv_desc->lo);
+ return false;
+ }
+
+ switch (inv_desc->lo & VTD_INV_DESC_IOTLB_G) {
+ case VTD_INV_DESC_IOTLB_GLOBAL:
+ VTD_DPRINTF(INV, "global invalidation");
+ vtd_iotlb_global_invalidate(s);
+ break;
+
+ case VTD_INV_DESC_IOTLB_DOMAIN:
+ domain_id = VTD_INV_DESC_IOTLB_DID(inv_desc->lo);
+ VTD_DPRINTF(INV, "domain-selective invalidation domain 0x%"PRIx16,
+ domain_id);
+ vtd_iotlb_domain_invalidate(s, domain_id);
+ break;
+
+ case VTD_INV_DESC_IOTLB_PAGE:
+ domain_id = VTD_INV_DESC_IOTLB_DID(inv_desc->lo);
+ addr = VTD_INV_DESC_IOTLB_ADDR(inv_desc->hi);
+ am = VTD_INV_DESC_IOTLB_AM(inv_desc->hi);
+ VTD_DPRINTF(INV, "page-selective invalidation domain 0x%"PRIx16
+ " addr 0x%"PRIx64 " mask %"PRIu8, domain_id, addr, am);
+ if (am > VTD_MAMV) {
+ VTD_DPRINTF(GENERAL, "error: supported max address mask value is "
+ "%"PRIu8, (uint8_t)VTD_MAMV);
+ return false;
+ }
+ vtd_iotlb_page_invalidate(s, domain_id, addr, am);
+ break;
+
+ default:
+ VTD_DPRINTF(GENERAL, "error: invalid granularity in IOTLB Invalidate "
+ "Descriptor hi 0x%"PRIx64 " lo 0x%"PRIx64,
+ inv_desc->hi, inv_desc->lo);
+ return false;
+ }
+ return true;
+}
+
+static bool vtd_process_inv_desc(IntelIOMMUState *s)
+{
+ VTDInvDesc inv_desc;
+ uint8_t desc_type;
+
+ VTD_DPRINTF(INV, "iq head %"PRIu16, s->iq_head);
+ if (!vtd_get_inv_desc(s->iq, s->iq_head, &inv_desc)) {
+ s->iq_last_desc_type = VTD_INV_DESC_NONE;
+ return false;
+ }
+ desc_type = inv_desc.lo & VTD_INV_DESC_TYPE;
+ /* FIXME: should update at first or at last? */
+ s->iq_last_desc_type = desc_type;
+
+ switch (desc_type) {
+ case VTD_INV_DESC_CC:
+ VTD_DPRINTF(INV, "Context-cache Invalidate Descriptor hi 0x%"PRIx64
+ " lo 0x%"PRIx64, inv_desc.hi, inv_desc.lo);
+ if (!vtd_process_context_cache_desc(s, &inv_desc)) {
+ return false;
+ }
+ break;
+
+ case VTD_INV_DESC_IOTLB:
+ VTD_DPRINTF(INV, "IOTLB Invalidate Descriptor hi 0x%"PRIx64
+ " lo 0x%"PRIx64, inv_desc.hi, inv_desc.lo);
+ if (!vtd_process_iotlb_desc(s, &inv_desc)) {
+ return false;
+ }
+ break;
+
+ case VTD_INV_DESC_WAIT:
+ VTD_DPRINTF(INV, "Invalidation Wait Descriptor hi 0x%"PRIx64
+ " lo 0x%"PRIx64, inv_desc.hi, inv_desc.lo);
+ if (!vtd_process_wait_desc(s, &inv_desc)) {
+ return false;
+ }
+ break;
+
+ default:
+ VTD_DPRINTF(GENERAL, "error: unkonw Invalidation Descriptor type "
+ "hi 0x%"PRIx64 " lo 0x%"PRIx64 " type %"PRIu8,
+ inv_desc.hi, inv_desc.lo, desc_type);
+ return false;
+ }
+ s->iq_head++;
+ if (s->iq_head == s->iq_size) {
+ s->iq_head = 0;
+ }
+ return true;
+}
+
+/* Try to fetch and process more Invalidation Descriptors */
+static void vtd_fetch_inv_desc(IntelIOMMUState *s)
+{
+ VTD_DPRINTF(INV, "fetch Invalidation Descriptors");
+ if (s->iq_tail >= s->iq_size) {
+ /* Detects an invalid Tail pointer */
+ VTD_DPRINTF(GENERAL, "error: iq_tail is %"PRIu16
+ " while iq_size is %"PRIu16, s->iq_tail, s->iq_size);
+ vtd_handle_inv_queue_error(s);
+ return;
+ }
+ while (s->iq_head != s->iq_tail) {
+ if (!vtd_process_inv_desc(s)) {
+ /* Invalidation Queue Errors */
+ vtd_handle_inv_queue_error(s);
+ break;
+ }
+ /* Must update the IQH_REG in time */
+ vtd_set_quad_raw(s, DMAR_IQH_REG,
+ (((uint64_t)(s->iq_head)) << VTD_IQH_QH_SHIFT) &
+ VTD_IQH_QH_MASK);
+ }
+}
+
+/* Handle write to Invalidation Queue Tail Register */
+static void vtd_handle_iqt_write(IntelIOMMUState *s)
+{
+ uint64_t val = vtd_get_quad_raw(s, DMAR_IQT_REG);
+
+ s->iq_tail = VTD_IQT_QT(val);
+ VTD_DPRINTF(INV, "set iq tail %"PRIu16, s->iq_tail);
+ if (s->qi_enabled && !(vtd_get_long_raw(s, DMAR_FSTS_REG) & VTD_FSTS_IQE)) {
+ /* Process Invalidation Queue here */
+ vtd_fetch_inv_desc(s);
+ }
+}
+
+static void vtd_handle_fsts_write(IntelIOMMUState *s)
+{
+ uint32_t fsts_reg = vtd_get_long_raw(s, DMAR_FSTS_REG);
+ uint32_t fectl_reg = vtd_get_long_raw(s, DMAR_FECTL_REG);
+ uint32_t status_fields = VTD_FSTS_PFO | VTD_FSTS_PPF | VTD_FSTS_IQE;
+
+ if ((fectl_reg & VTD_FECTL_IP) && !(fsts_reg & status_fields)) {
+ vtd_set_clear_mask_long(s, DMAR_FECTL_REG, VTD_FECTL_IP, 0);
+ VTD_DPRINTF(FLOG, "all pending interrupt conditions serviced, clear "
+ "IP field of FECTL_REG");
+ }
+ /* FIXME: when IQE is Clear, should we try to fetch some Invalidation
+ * Descriptors if there are any when Queued Invalidation is enabled?
+ */
+}
+
+static void vtd_handle_fectl_write(IntelIOMMUState *s)
+{
+ uint32_t fectl_reg;
+ /* FIXME: when software clears the IM field, check the IP field. But do we
+ * need to compare the old value and the new value to conclude that
+ * software clears the IM field? Or just check if the IM field is zero?
+ */
+ fectl_reg = vtd_get_long_raw(s, DMAR_FECTL_REG);
+ if ((fectl_reg & VTD_FECTL_IP) && !(fectl_reg & VTD_FECTL_IM)) {
+ vtd_generate_interrupt(s, DMAR_FEADDR_REG, DMAR_FEDATA_REG);
+ vtd_set_clear_mask_long(s, DMAR_FECTL_REG, VTD_FECTL_IP, 0);
+ VTD_DPRINTF(FLOG, "IM field is cleared, generate "
+ "fault event interrupt");
+ }
+}
+
+static void vtd_handle_ics_write(IntelIOMMUState *s)
+{
+ uint32_t ics_reg = vtd_get_long_raw(s, DMAR_ICS_REG);
+ uint32_t iectl_reg = vtd_get_long_raw(s, DMAR_IECTL_REG);
+
+ if ((iectl_reg & VTD_IECTL_IP) && !(ics_reg & VTD_ICS_IWC)) {
+ vtd_set_clear_mask_long(s, DMAR_IECTL_REG, VTD_IECTL_IP, 0);
+ VTD_DPRINTF(INV, "pending completion interrupt condition serviced, "
+ "clear IP field of IECTL_REG");
+ }
+}
+
+static void vtd_handle_iectl_write(IntelIOMMUState *s)
+{
+ uint32_t iectl_reg;
+ /* FIXME: when software clears the IM field, check the IP field. But do we
+ * need to compare the old value and the new value to conclude that
+ * software clears the IM field? Or just check if the IM field is zero?
+ */
+ iectl_reg = vtd_get_long_raw(s, DMAR_IECTL_REG);
+ if ((iectl_reg & VTD_IECTL_IP) && !(iectl_reg & VTD_IECTL_IM)) {
+ vtd_generate_interrupt(s, DMAR_IEADDR_REG, DMAR_IEDATA_REG);
+ vtd_set_clear_mask_long(s, DMAR_IECTL_REG, VTD_IECTL_IP, 0);
+ VTD_DPRINTF(INV, "IM field is cleared, generate "
+ "invalidation event interrupt");
+ }
+}
+
+static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+ IntelIOMMUState *s = opaque;
+ uint64_t val;
+
+ if (addr + size > DMAR_REG_SIZE) {
+ VTD_DPRINTF(GENERAL, "error: addr outside region: max 0x%"PRIx64
+ ", got 0x%"PRIx64 " %d",
+ (uint64_t)DMAR_REG_SIZE, addr, size);
+ return (uint64_t)-1;
+ }
+
+ switch (addr) {
+ /* Root Table Address Register, 64-bit */
+ case DMAR_RTADDR_REG:
+ if (size == 4) {
+ val = s->root & ((1ULL << 32) - 1);
+ } else {
+ val = s->root;
+ }
+ break;
+
+ case DMAR_RTADDR_REG_HI:
+ assert(size == 4);
+ val = s->root >> 32;
+ break;
+
+ /* Invalidation Queue Address Register, 64-bit */
+ case DMAR_IQA_REG:
+ val = s->iq | (vtd_get_quad(s, DMAR_IQA_REG) & VTD_IQA_QS);
+ if (size == 4) {
+ val = val & ((1ULL << 32) - 1);
+ }
+ break;
+
+ case DMAR_IQA_REG_HI:
+ assert(size == 4);
+ val = s->iq >> 32;
+ break;
+
+ default:
+ if (size == 4) {
+ val = vtd_get_long(s, addr);
+ } else {
+ val = vtd_get_quad(s, addr);
+ }
+ }
+ VTD_DPRINTF(CSR, "addr 0x%"PRIx64 " size %d val 0x%"PRIx64,
+ addr, size, val);
+ return val;
+}
+
+static void vtd_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ IntelIOMMUState *s = opaque;
+
+ if (addr + size > DMAR_REG_SIZE) {
+ VTD_DPRINTF(GENERAL, "error: addr outside region: max 0x%"PRIx64
+ ", got 0x%"PRIx64 " %d",
+ (uint64_t)DMAR_REG_SIZE, addr, size);
+ return;
+ }
+
+ switch (addr) {
+ /* Global Command Register, 32-bit */
+ case DMAR_GCMD_REG:
+ VTD_DPRINTF(CSR, "DMAR_GCMD_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ vtd_set_long(s, addr, val);
+ vtd_handle_gcmd_write(s);
+ break;
+
+ /* Context Command Register, 64-bit */
+ case DMAR_CCMD_REG:
+ VTD_DPRINTF(CSR, "DMAR_CCMD_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ vtd_handle_ccmd_write(s);
+ }
+ break;
+
+ case DMAR_CCMD_REG_HI:
+ VTD_DPRINTF(CSR, "DMAR_CCMD_REG_HI write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ vtd_handle_ccmd_write(s);
+ break;
+
+ /* IOTLB Invalidation Register, 64-bit */
+ case DMAR_IOTLB_REG:
+ VTD_DPRINTF(INV, "DMAR_IOTLB_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ vtd_handle_iotlb_write(s);
+ }
+ break;
+
+ case DMAR_IOTLB_REG_HI:
+ VTD_DPRINTF(INV, "DMAR_IOTLB_REG_HI write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ vtd_handle_iotlb_write(s);
+ break;
+
+ /* Invalidate Address Register, 64-bit */
+ case DMAR_IVA_REG:
+ VTD_DPRINTF(INV, "DMAR_IVA_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ }
+ break;
+
+ case DMAR_IVA_REG_HI:
+ VTD_DPRINTF(INV, "DMAR_IVA_REG_HI write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Fault Status Register, 32-bit */
+ case DMAR_FSTS_REG:
+ VTD_DPRINTF(FLOG, "DMAR_FSTS_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ vtd_handle_fsts_write(s);
+ break;
+
+ /* Fault Event Control Register, 32-bit */
+ case DMAR_FECTL_REG:
+ VTD_DPRINTF(FLOG, "DMAR_FECTL_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ vtd_handle_fectl_write(s);
+ break;
+
+ /* Fault Event Data Register, 32-bit */
+ case DMAR_FEDATA_REG:
+ VTD_DPRINTF(FLOG, "DMAR_FEDATA_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Fault Event Address Register, 32-bit */
+ case DMAR_FEADDR_REG:
+ VTD_DPRINTF(FLOG, "DMAR_FEADDR_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Fault Event Upper Address Register, 32-bit */
+ case DMAR_FEUADDR_REG:
+ VTD_DPRINTF(FLOG, "DMAR_FEUADDR_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Protected Memory Enable Register, 32-bit */
+ case DMAR_PMEN_REG:
+ VTD_DPRINTF(CSR, "DMAR_PMEN_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Root Table Address Register, 64-bit */
+ case DMAR_RTADDR_REG:
+ VTD_DPRINTF(CSR, "DMAR_RTADDR_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ }
+ break;
+
+ case DMAR_RTADDR_REG_HI:
+ VTD_DPRINTF(CSR, "DMAR_RTADDR_REG_HI write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Invalidation Queue Tail Register, 64-bit */
+ case DMAR_IQT_REG:
+ VTD_DPRINTF(INV, "DMAR_IQT_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ }
+ vtd_handle_iqt_write(s);
+ break;
+
+ case DMAR_IQT_REG_HI:
+ VTD_DPRINTF(INV, "DMAR_IQT_REG_HI write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ /* 19:63 of IQT_REG is RsvdZ, do nothing here */
+ break;
+
+ /* Invalidation Queue Address Register, 64-bit */
+ case DMAR_IQA_REG:
+ VTD_DPRINTF(INV, "DMAR_IQA_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ }
+ break;
+
+ case DMAR_IQA_REG_HI:
+ VTD_DPRINTF(INV, "DMAR_IQA_REG_HI write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Invalidation Completion Status Register, 32-bit */
+ case DMAR_ICS_REG:
+ VTD_DPRINTF(INV, "DMAR_ICS_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ vtd_handle_ics_write(s);
+ break;
+
+ /* Invalidation Event Control Register, 32-bit */
+ case DMAR_IECTL_REG:
+ VTD_DPRINTF(INV, "DMAR_IECTL_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ vtd_handle_iectl_write(s);
+ break;
+
+ /* Invalidation Event Data Register, 32-bit */
+ case DMAR_IEDATA_REG:
+ VTD_DPRINTF(INV, "DMAR_IEDATA_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Invalidation Event Address Register, 32-bit */
+ case DMAR_IEADDR_REG:
+ VTD_DPRINTF(INV, "DMAR_IEADDR_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Invalidation Event Upper Address Register, 32-bit */
+ case DMAR_IEUADDR_REG:
+ VTD_DPRINTF(INV, "DMAR_IEUADDR_REG write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ /* Fault Recording Registers, 128-bit */
+ case DMAR_FRCD_REG_0_0:
+ VTD_DPRINTF(FLOG, "DMAR_FRCD_REG_0_0 write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ }
+ break;
+
+ case DMAR_FRCD_REG_0_1:
+ VTD_DPRINTF(FLOG, "DMAR_FRCD_REG_0_1 write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ break;
+
+ case DMAR_FRCD_REG_0_2:
+ VTD_DPRINTF(FLOG, "DMAR_FRCD_REG_0_2 write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ /* May clear bit 127 (Fault), update PPF */
+ vtd_update_fsts_ppf(s);
+ }
+ break;
+
+ case DMAR_FRCD_REG_0_3:
+ VTD_DPRINTF(FLOG, "DMAR_FRCD_REG_0_3 write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ assert(size == 4);
+ vtd_set_long(s, addr, val);
+ /* May clear bit 127 (Fault), update PPF */
+ vtd_update_fsts_ppf(s);
+ break;
+
+ default:
+ VTD_DPRINTF(GENERAL, "error: unhandled reg write addr 0x%"PRIx64
+ ", size %d, val 0x%"PRIx64, addr, size, val);
+ if (size == 4) {
+ vtd_set_long(s, addr, val);
+ } else {
+ vtd_set_quad(s, addr, val);
+ }
+ }
+}
+
+static IOMMUTLBEntry vtd_iommu_translate(MemoryRegion *iommu, hwaddr addr,
+ bool is_write)
+{
+ VTDAddressSpace *vtd_as = container_of(iommu, VTDAddressSpace, iommu);
+ IntelIOMMUState *s = vtd_as->iommu_state;
+ IOMMUTLBEntry ret = {
+ .target_as = &address_space_memory,
+ .iova = addr,
+ .translated_addr = 0,
+ .addr_mask = ~(hwaddr)0,
+ .perm = IOMMU_NONE,
+ };
+
+ if (!s->dmar_enabled) {
+ /* DMAR disabled, passthrough, use 4k-page*/
+ ret.iova = addr & VTD_PAGE_MASK_4K;
+ ret.translated_addr = addr & VTD_PAGE_MASK_4K;
+ ret.addr_mask = ~VTD_PAGE_MASK_4K;
+ ret.perm = IOMMU_RW;
+ return ret;
+ }
+
+ vtd_do_iommu_translate(vtd_as, vtd_as->bus_num, vtd_as->devfn, addr,
+ is_write, &ret);
+ VTD_DPRINTF(MMU,
+ "bus %"PRIu8 " slot %"PRIu8 " func %"PRIu8 " devfn %"PRIu8
+ " gpa 0x%"PRIx64 " hpa 0x%"PRIx64, vtd_as->bus_num,
+ VTD_PCI_SLOT(vtd_as->devfn), VTD_PCI_FUNC(vtd_as->devfn),
+ vtd_as->devfn, addr, ret.translated_addr);
+ return ret;
+}
+
+static const VMStateDescription vtd_vmstate = {
+ .name = "iommu-intel",
+ .unmigratable = 1,
+};
+
+static const MemoryRegionOps vtd_mem_ops = {
+ .read = vtd_mem_read,
+ .write = vtd_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static Property vtd_properties[] = {
+ DEFINE_PROP_UINT32("version", IntelIOMMUState, version, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+/* Do the initialization. It will also be called when reset, so pay
+ * attention when adding new initialization stuff.
+ */
+static void vtd_init(IntelIOMMUState *s)
+{
+ memset(s->csr, 0, DMAR_REG_SIZE);
+ memset(s->wmask, 0, DMAR_REG_SIZE);
+ memset(s->w1cmask, 0, DMAR_REG_SIZE);
+ memset(s->womask, 0, DMAR_REG_SIZE);
+
+ s->iommu_ops.translate = vtd_iommu_translate;
+ s->root = 0;
+ s->root_extended = false;
+ s->dmar_enabled = false;
+ s->iq_head = 0;
+ s->iq_tail = 0;
+ s->iq = 0;
+ s->iq_size = 0;
+ s->qi_enabled = false;
+ s->iq_last_desc_type = VTD_INV_DESC_NONE;
+ s->next_frcd_reg = 0;
+ s->cap = VTD_CAP_FRO | VTD_CAP_NFR | VTD_CAP_ND | VTD_CAP_MGAW |
+ VTD_CAP_SAGAW | VTD_CAP_MAMV | VTD_CAP_PSI;
+ s->ecap = VTD_ECAP_QI | VTD_ECAP_IRO;
+
+ vtd_reset_context_cache(s);
+ vtd_reset_iotlb(s);
+
+ /* Define registers with default values and bit semantics */
+ vtd_define_long(s, DMAR_VER_REG, 0x10UL, 0, 0);
+ vtd_define_quad(s, DMAR_CAP_REG, s->cap, 0, 0);
+ vtd_define_quad(s, DMAR_ECAP_REG, s->ecap, 0, 0);
+ vtd_define_long(s, DMAR_GCMD_REG, 0, 0xff800000UL, 0);
+ vtd_define_long_wo(s, DMAR_GCMD_REG, 0xff800000UL);
+ vtd_define_long(s, DMAR_GSTS_REG, 0, 0, 0);
+ vtd_define_quad(s, DMAR_RTADDR_REG, 0, 0xfffffffffffff000ULL, 0);
+ vtd_define_quad(s, DMAR_CCMD_REG, 0, 0xe0000003ffffffffULL, 0);
+ vtd_define_quad_wo(s, DMAR_CCMD_REG, 0x3ffff0000ULL);
+
+ /* Advanced Fault Logging not supported */
+ vtd_define_long(s, DMAR_FSTS_REG, 0, 0, 0x11UL);
+ vtd_define_long(s, DMAR_FECTL_REG, 0x80000000UL, 0x80000000UL, 0);
+ vtd_define_long(s, DMAR_FEDATA_REG, 0, 0x0000ffffUL, 0);
+ vtd_define_long(s, DMAR_FEADDR_REG, 0, 0xfffffffcUL, 0);
+
+ /* Treated as RsvdZ when EIM in ECAP_REG is not supported
+ * vtd_define_long(s, DMAR_FEUADDR_REG, 0, 0xffffffffUL, 0);
+ */
+ vtd_define_long(s, DMAR_FEUADDR_REG, 0, 0, 0);
+
+ /* Treated as RO for implementations that PLMR and PHMR fields reported
+ * as Clear in the CAP_REG.
+ * vtd_define_long(s, DMAR_PMEN_REG, 0, 0x80000000UL, 0);
+ */
+ vtd_define_long(s, DMAR_PMEN_REG, 0, 0, 0);
+
+ vtd_define_quad(s, DMAR_IQH_REG, 0, 0, 0);
+ vtd_define_quad(s, DMAR_IQT_REG, 0, 0x7fff0ULL, 0);
+ vtd_define_quad(s, DMAR_IQA_REG, 0, 0xfffffffffffff007ULL, 0);
+ vtd_define_long(s, DMAR_ICS_REG, 0, 0, 0x1UL);
+ vtd_define_long(s, DMAR_IECTL_REG, 0x80000000UL, 0x80000000UL, 0);
+ vtd_define_long(s, DMAR_IEDATA_REG, 0, 0xffffffffUL, 0);
+ vtd_define_long(s, DMAR_IEADDR_REG, 0, 0xfffffffcUL, 0);
+ /* Treadted as RsvdZ when EIM in ECAP_REG is not supported */
+ vtd_define_long(s, DMAR_IEUADDR_REG, 0, 0, 0);
+
+ /* IOTLB registers */
+ vtd_define_quad(s, DMAR_IOTLB_REG, 0, 0Xb003ffff00000000ULL, 0);
+ vtd_define_quad(s, DMAR_IVA_REG, 0, 0xfffffffffffff07fULL, 0);
+ vtd_define_quad_wo(s, DMAR_IVA_REG, 0xfffffffffffff07fULL);
+
+ /* Fault Recording Registers, 128-bit */
+ vtd_define_quad(s, DMAR_FRCD_REG_0_0, 0, 0, 0);
+ vtd_define_quad(s, DMAR_FRCD_REG_0_2, 0, 0, 0x8000000000000000ULL);
+}
+
+/* Should not reset address_spaces when reset because devices will still use
+ * the address space they got at first (won't ask the bus again).
+ */
+static void vtd_reset(DeviceState *dev)
+{
+ IntelIOMMUState *s = INTEL_IOMMU_DEVICE(dev);
+
+ VTD_DPRINTF(GENERAL, "");
+ vtd_init(s);
+}
+
+static void vtd_realize(DeviceState *dev, Error **errp)
+{
+ IntelIOMMUState *s = INTEL_IOMMU_DEVICE(dev);
+
+ VTD_DPRINTF(GENERAL, "");
+ memset(s->address_spaces, 0, sizeof(s->address_spaces));
+ memory_region_init_io(&s->csrmem, OBJECT(s), &vtd_mem_ops, s,
+ "intel_iommu", DMAR_REG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->csrmem);
+ /* No corresponding destroy */
+ s->iotlb = g_hash_table_new_full(vtd_uint64_hash, vtd_uint64_equal,
+ g_free, g_free);
+ vtd_init(s);
+}
+
+static void vtd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = vtd_reset;
+ dc->realize = vtd_realize;
+ dc->vmsd = &vtd_vmstate;
+ dc->props = vtd_properties;
+}
+
+static const TypeInfo vtd_info = {
+ .name = TYPE_INTEL_IOMMU_DEVICE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IntelIOMMUState),
+ .class_init = vtd_class_init,
+};
+
+static void vtd_register_types(void)
+{
+ VTD_DPRINTF(GENERAL, "");
+ type_register_static(&vtd_info);
+}
+
+type_init(vtd_register_types)
diff --git a/hw/i386/intel_iommu_internal.h b/hw/i386/intel_iommu_internal.h
new file mode 100644
index 00000000..ba288ab1
--- /dev/null
+++ b/hw/i386/intel_iommu_internal.h
@@ -0,0 +1,389 @@
+/*
+ * QEMU emulation of an Intel IOMMU (VT-d)
+ * (DMA Remapping device)
+ *
+ * Copyright (C) 2013 Knut Omang, Oracle <knut.omang@oracle.com>
+ * Copyright (C) 2014 Le Tan, <tamlokveer@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Lots of defines copied from kernel/include/linux/intel-iommu.h:
+ * Copyright (C) 2006-2008 Intel Corporation
+ * Author: Ashok Raj <ashok.raj@intel.com>
+ * Author: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>
+ *
+ */
+
+#ifndef HW_I386_INTEL_IOMMU_INTERNAL_H
+#define HW_I386_INTEL_IOMMU_INTERNAL_H
+#include "hw/i386/intel_iommu.h"
+
+/*
+ * Intel IOMMU register specification
+ */
+#define DMAR_VER_REG 0x0 /* Arch version supported by this IOMMU */
+#define DMAR_CAP_REG 0x8 /* Hardware supported capabilities */
+#define DMAR_CAP_REG_HI 0xc /* High 32-bit of DMAR_CAP_REG */
+#define DMAR_ECAP_REG 0x10 /* Extended capabilities supported */
+#define DMAR_ECAP_REG_HI 0X14
+#define DMAR_GCMD_REG 0x18 /* Global command */
+#define DMAR_GSTS_REG 0x1c /* Global status */
+#define DMAR_RTADDR_REG 0x20 /* Root entry table */
+#define DMAR_RTADDR_REG_HI 0X24
+#define DMAR_CCMD_REG 0x28 /* Context command */
+#define DMAR_CCMD_REG_HI 0x2c
+#define DMAR_FSTS_REG 0x34 /* Fault status */
+#define DMAR_FECTL_REG 0x38 /* Fault control */
+#define DMAR_FEDATA_REG 0x3c /* Fault event interrupt data */
+#define DMAR_FEADDR_REG 0x40 /* Fault event interrupt addr */
+#define DMAR_FEUADDR_REG 0x44 /* Upper address */
+#define DMAR_AFLOG_REG 0x58 /* Advanced fault control */
+#define DMAR_AFLOG_REG_HI 0X5c
+#define DMAR_PMEN_REG 0x64 /* Enable protected memory region */
+#define DMAR_PLMBASE_REG 0x68 /* PMRR low addr */
+#define DMAR_PLMLIMIT_REG 0x6c /* PMRR low limit */
+#define DMAR_PHMBASE_REG 0x70 /* PMRR high base addr */
+#define DMAR_PHMBASE_REG_HI 0X74
+#define DMAR_PHMLIMIT_REG 0x78 /* PMRR high limit */
+#define DMAR_PHMLIMIT_REG_HI 0x7c
+#define DMAR_IQH_REG 0x80 /* Invalidation queue head */
+#define DMAR_IQH_REG_HI 0X84
+#define DMAR_IQT_REG 0x88 /* Invalidation queue tail */
+#define DMAR_IQT_REG_HI 0X8c
+#define DMAR_IQA_REG 0x90 /* Invalidation queue addr */
+#define DMAR_IQA_REG_HI 0x94
+#define DMAR_ICS_REG 0x9c /* Invalidation complete status */
+#define DMAR_IRTA_REG 0xb8 /* Interrupt remapping table addr */
+#define DMAR_IRTA_REG_HI 0xbc
+#define DMAR_IECTL_REG 0xa0 /* Invalidation event control */
+#define DMAR_IEDATA_REG 0xa4 /* Invalidation event data */
+#define DMAR_IEADDR_REG 0xa8 /* Invalidation event address */
+#define DMAR_IEUADDR_REG 0xac /* Invalidation event address */
+#define DMAR_PQH_REG 0xc0 /* Page request queue head */
+#define DMAR_PQH_REG_HI 0xc4
+#define DMAR_PQT_REG 0xc8 /* Page request queue tail*/
+#define DMAR_PQT_REG_HI 0xcc
+#define DMAR_PQA_REG 0xd0 /* Page request queue address */
+#define DMAR_PQA_REG_HI 0xd4
+#define DMAR_PRS_REG 0xdc /* Page request status */
+#define DMAR_PECTL_REG 0xe0 /* Page request event control */
+#define DMAR_PEDATA_REG 0xe4 /* Page request event data */
+#define DMAR_PEADDR_REG 0xe8 /* Page request event address */
+#define DMAR_PEUADDR_REG 0xec /* Page event upper address */
+#define DMAR_MTRRCAP_REG 0x100 /* MTRR capability */
+#define DMAR_MTRRCAP_REG_HI 0x104
+#define DMAR_MTRRDEF_REG 0x108 /* MTRR default type */
+#define DMAR_MTRRDEF_REG_HI 0x10c
+
+/* IOTLB registers */
+#define DMAR_IOTLB_REG_OFFSET 0xf0 /* Offset to the IOTLB registers */
+#define DMAR_IVA_REG DMAR_IOTLB_REG_OFFSET /* Invalidate address */
+#define DMAR_IVA_REG_HI (DMAR_IVA_REG + 4)
+/* IOTLB invalidate register */
+#define DMAR_IOTLB_REG (DMAR_IOTLB_REG_OFFSET + 0x8)
+#define DMAR_IOTLB_REG_HI (DMAR_IOTLB_REG + 4)
+
+/* FRCD */
+#define DMAR_FRCD_REG_OFFSET 0x220 /* Offset to the fault recording regs */
+/* NOTICE: If you change the DMAR_FRCD_REG_NR, please remember to change the
+ * DMAR_REG_SIZE in include/hw/i386/intel_iommu.h.
+ * #define DMAR_REG_SIZE (DMAR_FRCD_REG_OFFSET + 16 * DMAR_FRCD_REG_NR)
+ */
+#define DMAR_FRCD_REG_NR 1ULL /* Num of fault recording regs */
+
+#define DMAR_FRCD_REG_0_0 0x220 /* The 0th fault recording regs */
+#define DMAR_FRCD_REG_0_1 0x224
+#define DMAR_FRCD_REG_0_2 0x228
+#define DMAR_FRCD_REG_0_3 0x22c
+
+/* Interrupt Address Range */
+#define VTD_INTERRUPT_ADDR_FIRST 0xfee00000ULL
+#define VTD_INTERRUPT_ADDR_LAST 0xfeefffffULL
+
+/* The shift of source_id in the key of IOTLB hash table */
+#define VTD_IOTLB_SID_SHIFT 36
+#define VTD_IOTLB_MAX_SIZE 1024 /* Max size of the hash table */
+
+/* IOTLB_REG */
+#define VTD_TLB_GLOBAL_FLUSH (1ULL << 60) /* Global invalidation */
+#define VTD_TLB_DSI_FLUSH (2ULL << 60) /* Domain-selective */
+#define VTD_TLB_PSI_FLUSH (3ULL << 60) /* Page-selective */
+#define VTD_TLB_FLUSH_GRANU_MASK (3ULL << 60)
+#define VTD_TLB_GLOBAL_FLUSH_A (1ULL << 57)
+#define VTD_TLB_DSI_FLUSH_A (2ULL << 57)
+#define VTD_TLB_PSI_FLUSH_A (3ULL << 57)
+#define VTD_TLB_FLUSH_GRANU_MASK_A (3ULL << 57)
+#define VTD_TLB_IVT (1ULL << 63)
+#define VTD_TLB_DID(val) (((val) >> 32) & VTD_DOMAIN_ID_MASK)
+
+/* IVA_REG */
+#define VTD_IVA_ADDR(val) ((val) & ~0xfffULL & ((1ULL << VTD_MGAW) - 1))
+#define VTD_IVA_AM(val) ((val) & 0x3fULL)
+
+/* GCMD_REG */
+#define VTD_GCMD_TE (1UL << 31)
+#define VTD_GCMD_SRTP (1UL << 30)
+#define VTD_GCMD_SFL (1UL << 29)
+#define VTD_GCMD_EAFL (1UL << 28)
+#define VTD_GCMD_WBF (1UL << 27)
+#define VTD_GCMD_QIE (1UL << 26)
+#define VTD_GCMD_IRE (1UL << 25)
+#define VTD_GCMD_SIRTP (1UL << 24)
+#define VTD_GCMD_CFI (1UL << 23)
+
+/* GSTS_REG */
+#define VTD_GSTS_TES (1UL << 31)
+#define VTD_GSTS_RTPS (1UL << 30)
+#define VTD_GSTS_FLS (1UL << 29)
+#define VTD_GSTS_AFLS (1UL << 28)
+#define VTD_GSTS_WBFS (1UL << 27)
+#define VTD_GSTS_QIES (1UL << 26)
+#define VTD_GSTS_IRES (1UL << 25)
+#define VTD_GSTS_IRTPS (1UL << 24)
+#define VTD_GSTS_CFIS (1UL << 23)
+
+/* CCMD_REG */
+#define VTD_CCMD_ICC (1ULL << 63)
+#define VTD_CCMD_GLOBAL_INVL (1ULL << 61)
+#define VTD_CCMD_DOMAIN_INVL (2ULL << 61)
+#define VTD_CCMD_DEVICE_INVL (3ULL << 61)
+#define VTD_CCMD_CIRG_MASK (3ULL << 61)
+#define VTD_CCMD_GLOBAL_INVL_A (1ULL << 59)
+#define VTD_CCMD_DOMAIN_INVL_A (2ULL << 59)
+#define VTD_CCMD_DEVICE_INVL_A (3ULL << 59)
+#define VTD_CCMD_CAIG_MASK (3ULL << 59)
+#define VTD_CCMD_DID(val) ((val) & VTD_DOMAIN_ID_MASK)
+#define VTD_CCMD_SID(val) (((val) >> 16) & 0xffffULL)
+#define VTD_CCMD_FM(val) (((val) >> 32) & 3ULL)
+
+/* RTADDR_REG */
+#define VTD_RTADDR_RTT (1ULL << 11)
+#define VTD_RTADDR_ADDR_MASK (VTD_HAW_MASK ^ 0xfffULL)
+
+/* ECAP_REG */
+/* (offset >> 4) << 8 */
+#define VTD_ECAP_IRO (DMAR_IOTLB_REG_OFFSET << 4)
+#define VTD_ECAP_QI (1ULL << 1)
+
+/* CAP_REG */
+/* (offset >> 4) << 24 */
+#define VTD_CAP_FRO (DMAR_FRCD_REG_OFFSET << 20)
+#define VTD_CAP_NFR ((DMAR_FRCD_REG_NR - 1) << 40)
+#define VTD_DOMAIN_ID_SHIFT 16 /* 16-bit domain id for 64K domains */
+#define VTD_DOMAIN_ID_MASK ((1UL << VTD_DOMAIN_ID_SHIFT) - 1)
+#define VTD_CAP_ND (((VTD_DOMAIN_ID_SHIFT - 4) / 2) & 7ULL)
+#define VTD_MGAW 39 /* Maximum Guest Address Width */
+#define VTD_CAP_MGAW (((VTD_MGAW - 1) & 0x3fULL) << 16)
+#define VTD_MAMV 9ULL
+#define VTD_CAP_MAMV (VTD_MAMV << 48)
+#define VTD_CAP_PSI (1ULL << 39)
+
+/* Supported Adjusted Guest Address Widths */
+#define VTD_CAP_SAGAW_SHIFT 8
+#define VTD_CAP_SAGAW_MASK (0x1fULL << VTD_CAP_SAGAW_SHIFT)
+ /* 39-bit AGAW, 3-level page-table */
+#define VTD_CAP_SAGAW_39bit (0x2ULL << VTD_CAP_SAGAW_SHIFT)
+ /* 48-bit AGAW, 4-level page-table */
+#define VTD_CAP_SAGAW_48bit (0x4ULL << VTD_CAP_SAGAW_SHIFT)
+#define VTD_CAP_SAGAW VTD_CAP_SAGAW_39bit
+
+/* IQT_REG */
+#define VTD_IQT_QT(val) (((val) >> 4) & 0x7fffULL)
+
+/* IQA_REG */
+#define VTD_IQA_IQA_MASK (VTD_HAW_MASK ^ 0xfffULL)
+#define VTD_IQA_QS 0x7ULL
+
+/* IQH_REG */
+#define VTD_IQH_QH_SHIFT 4
+#define VTD_IQH_QH_MASK 0x7fff0ULL
+
+/* ICS_REG */
+#define VTD_ICS_IWC 1UL
+
+/* IECTL_REG */
+#define VTD_IECTL_IM (1UL << 31)
+#define VTD_IECTL_IP (1UL << 30)
+
+/* FSTS_REG */
+#define VTD_FSTS_FRI_MASK 0xff00UL
+#define VTD_FSTS_FRI(val) ((((uint32_t)(val)) << 8) & VTD_FSTS_FRI_MASK)
+#define VTD_FSTS_IQE (1UL << 4)
+#define VTD_FSTS_PPF (1UL << 1)
+#define VTD_FSTS_PFO 1UL
+
+/* FECTL_REG */
+#define VTD_FECTL_IM (1UL << 31)
+#define VTD_FECTL_IP (1UL << 30)
+
+/* Fault Recording Register */
+/* For the high 64-bit of 128-bit */
+#define VTD_FRCD_F (1ULL << 63)
+#define VTD_FRCD_T (1ULL << 62)
+#define VTD_FRCD_FR(val) (((val) & 0xffULL) << 32)
+#define VTD_FRCD_SID_MASK 0xffffULL
+#define VTD_FRCD_SID(val) ((val) & VTD_FRCD_SID_MASK)
+/* For the low 64-bit of 128-bit */
+#define VTD_FRCD_FI(val) ((val) & (((1ULL << VTD_MGAW) - 1) ^ 0xfffULL))
+
+/* DMA Remapping Fault Conditions */
+typedef enum VTDFaultReason {
+ VTD_FR_RESERVED = 0, /* Reserved for Advanced Fault logging */
+ VTD_FR_ROOT_ENTRY_P = 1, /* The Present(P) field of root-entry is 0 */
+ VTD_FR_CONTEXT_ENTRY_P, /* The Present(P) field of context-entry is 0 */
+ VTD_FR_CONTEXT_ENTRY_INV, /* Invalid programming of a context-entry */
+ VTD_FR_ADDR_BEYOND_MGAW, /* Input-address above (2^x-1) */
+ VTD_FR_WRITE, /* No write permission */
+ VTD_FR_READ, /* No read permission */
+ /* Fail to access a second-level paging entry (not SL_PML4E) */
+ VTD_FR_PAGING_ENTRY_INV,
+ VTD_FR_ROOT_TABLE_INV, /* Fail to access a root-entry */
+ VTD_FR_CONTEXT_TABLE_INV, /* Fail to access a context-entry */
+ /* Non-zero reserved field in a present root-entry */
+ VTD_FR_ROOT_ENTRY_RSVD,
+ /* Non-zero reserved field in a present context-entry */
+ VTD_FR_CONTEXT_ENTRY_RSVD,
+ /* Non-zero reserved field in a second-level paging entry with at lease one
+ * Read(R) and Write(W) or Execute(E) field is Set.
+ */
+ VTD_FR_PAGING_ENTRY_RSVD,
+ /* Translation request or translated request explicitly blocked dut to the
+ * programming of the Translation Type (T) field in the present
+ * context-entry.
+ */
+ VTD_FR_CONTEXT_ENTRY_TT,
+ /* This is not a normal fault reason. We use this to indicate some faults
+ * that are not referenced by the VT-d specification.
+ * Fault event with such reason should not be recorded.
+ */
+ VTD_FR_RESERVED_ERR,
+ VTD_FR_MAX, /* Guard */
+} VTDFaultReason;
+
+#define VTD_CONTEXT_CACHE_GEN_MAX 0xffffffffUL
+
+/* Queued Invalidation Descriptor */
+struct VTDInvDesc {
+ uint64_t lo;
+ uint64_t hi;
+};
+typedef struct VTDInvDesc VTDInvDesc;
+
+/* Masks for struct VTDInvDesc */
+#define VTD_INV_DESC_TYPE 0xf
+#define VTD_INV_DESC_CC 0x1 /* Context-cache Invalidate Desc */
+#define VTD_INV_DESC_IOTLB 0x2
+#define VTD_INV_DESC_WAIT 0x5 /* Invalidation Wait Descriptor */
+#define VTD_INV_DESC_NONE 0 /* Not an Invalidate Descriptor */
+
+/* Masks for Invalidation Wait Descriptor*/
+#define VTD_INV_DESC_WAIT_SW (1ULL << 5)
+#define VTD_INV_DESC_WAIT_IF (1ULL << 4)
+#define VTD_INV_DESC_WAIT_FN (1ULL << 6)
+#define VTD_INV_DESC_WAIT_DATA_SHIFT 32
+#define VTD_INV_DESC_WAIT_RSVD_LO 0Xffffff80ULL
+#define VTD_INV_DESC_WAIT_RSVD_HI 3ULL
+
+/* Masks for Context-cache Invalidation Descriptor */
+#define VTD_INV_DESC_CC_G (3ULL << 4)
+#define VTD_INV_DESC_CC_GLOBAL (1ULL << 4)
+#define VTD_INV_DESC_CC_DOMAIN (2ULL << 4)
+#define VTD_INV_DESC_CC_DEVICE (3ULL << 4)
+#define VTD_INV_DESC_CC_DID(val) (((val) >> 16) & VTD_DOMAIN_ID_MASK)
+#define VTD_INV_DESC_CC_SID(val) (((val) >> 32) & 0xffffUL)
+#define VTD_INV_DESC_CC_FM(val) (((val) >> 48) & 3UL)
+#define VTD_INV_DESC_CC_RSVD 0xfffc00000000ffc0ULL
+
+/* Masks for IOTLB Invalidate Descriptor */
+#define VTD_INV_DESC_IOTLB_G (3ULL << 4)
+#define VTD_INV_DESC_IOTLB_GLOBAL (1ULL << 4)
+#define VTD_INV_DESC_IOTLB_DOMAIN (2ULL << 4)
+#define VTD_INV_DESC_IOTLB_PAGE (3ULL << 4)
+#define VTD_INV_DESC_IOTLB_DID(val) (((val) >> 16) & VTD_DOMAIN_ID_MASK)
+#define VTD_INV_DESC_IOTLB_ADDR(val) ((val) & ~0xfffULL & \
+ ((1ULL << VTD_MGAW) - 1))
+#define VTD_INV_DESC_IOTLB_AM(val) ((val) & 0x3fULL)
+#define VTD_INV_DESC_IOTLB_RSVD_LO 0xffffffff0000ff00ULL
+#define VTD_INV_DESC_IOTLB_RSVD_HI 0xf80ULL
+
+/* Information about page-selective IOTLB invalidate */
+struct VTDIOTLBPageInvInfo {
+ uint16_t domain_id;
+ uint64_t gfn;
+ uint8_t mask;
+};
+typedef struct VTDIOTLBPageInvInfo VTDIOTLBPageInvInfo;
+
+/* Pagesize of VTD paging structures, including root and context tables */
+#define VTD_PAGE_SHIFT 12
+#define VTD_PAGE_SIZE (1ULL << VTD_PAGE_SHIFT)
+
+#define VTD_PAGE_SHIFT_4K 12
+#define VTD_PAGE_MASK_4K (~((1ULL << VTD_PAGE_SHIFT_4K) - 1))
+#define VTD_PAGE_SHIFT_2M 21
+#define VTD_PAGE_MASK_2M (~((1ULL << VTD_PAGE_SHIFT_2M) - 1))
+#define VTD_PAGE_SHIFT_1G 30
+#define VTD_PAGE_MASK_1G (~((1ULL << VTD_PAGE_SHIFT_1G) - 1))
+
+struct VTDRootEntry {
+ uint64_t val;
+ uint64_t rsvd;
+};
+typedef struct VTDRootEntry VTDRootEntry;
+
+/* Masks for struct VTDRootEntry */
+#define VTD_ROOT_ENTRY_P 1ULL
+#define VTD_ROOT_ENTRY_CTP (~0xfffULL)
+
+#define VTD_ROOT_ENTRY_NR (VTD_PAGE_SIZE / sizeof(VTDRootEntry))
+#define VTD_ROOT_ENTRY_RSVD (0xffeULL | ~VTD_HAW_MASK)
+
+/* Masks for struct VTDContextEntry */
+/* lo */
+#define VTD_CONTEXT_ENTRY_P (1ULL << 0)
+#define VTD_CONTEXT_ENTRY_FPD (1ULL << 1) /* Fault Processing Disable */
+#define VTD_CONTEXT_ENTRY_TT (3ULL << 2) /* Translation Type */
+#define VTD_CONTEXT_TT_MULTI_LEVEL 0
+#define VTD_CONTEXT_TT_DEV_IOTLB 1
+#define VTD_CONTEXT_TT_PASS_THROUGH 2
+/* Second Level Page Translation Pointer*/
+#define VTD_CONTEXT_ENTRY_SLPTPTR (~0xfffULL)
+#define VTD_CONTEXT_ENTRY_RSVD_LO (0xff0ULL | ~VTD_HAW_MASK)
+/* hi */
+#define VTD_CONTEXT_ENTRY_AW 7ULL /* Adjusted guest-address-width */
+#define VTD_CONTEXT_ENTRY_DID(val) (((val) >> 8) & VTD_DOMAIN_ID_MASK)
+#define VTD_CONTEXT_ENTRY_RSVD_HI 0xffffffffff000080ULL
+
+#define VTD_CONTEXT_ENTRY_NR (VTD_PAGE_SIZE / sizeof(VTDContextEntry))
+
+/* Paging Structure common */
+#define VTD_SL_PT_PAGE_SIZE_MASK (1ULL << 7)
+/* Bits to decide the offset for each level */
+#define VTD_SL_LEVEL_BITS 9
+
+/* Second Level Paging Structure */
+#define VTD_SL_PML4_LEVEL 4
+#define VTD_SL_PDP_LEVEL 3
+#define VTD_SL_PD_LEVEL 2
+#define VTD_SL_PT_LEVEL 1
+#define VTD_SL_PT_ENTRY_NR 512
+
+/* Masks for Second Level Paging Entry */
+#define VTD_SL_RW_MASK 3ULL
+#define VTD_SL_R 1ULL
+#define VTD_SL_W (1ULL << 1)
+#define VTD_SL_PT_BASE_ADDR_MASK (~(VTD_PAGE_SIZE - 1) & VTD_HAW_MASK)
+#define VTD_SL_IGN_COM 0xbff0000000000000ULL
+
+#endif
diff --git a/hw/i386/kvm/Makefile.objs b/hw/i386/kvm/Makefile.objs
new file mode 100644
index 00000000..d8bce209
--- /dev/null
+++ b/hw/i386/kvm/Makefile.objs
@@ -0,0 +1 @@
+obj-y += clock.o apic.o i8259.o ioapic.o i8254.o pci-assign.o
diff --git a/hw/i386/kvm/apic.c b/hw/i386/kvm/apic.c
new file mode 100644
index 00000000..5b470562
--- /dev/null
+++ b/hw/i386/kvm/apic.c
@@ -0,0 +1,218 @@
+/*
+ * KVM in-kernel APIC support
+ *
+ * Copyright (c) 2011 Siemens AG
+ *
+ * Authors:
+ * Jan Kiszka <jan.kiszka@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL version 2.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/i386/apic_internal.h"
+#include "hw/pci/msi.h"
+#include "sysemu/kvm.h"
+
+static inline void kvm_apic_set_reg(struct kvm_lapic_state *kapic,
+ int reg_id, uint32_t val)
+{
+ *((uint32_t *)(kapic->regs + (reg_id << 4))) = val;
+}
+
+static inline uint32_t kvm_apic_get_reg(struct kvm_lapic_state *kapic,
+ int reg_id)
+{
+ return *((uint32_t *)(kapic->regs + (reg_id << 4)));
+}
+
+void kvm_put_apic_state(DeviceState *dev, struct kvm_lapic_state *kapic)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ int i;
+
+ memset(kapic, 0, sizeof(*kapic));
+ kvm_apic_set_reg(kapic, 0x2, s->id << 24);
+ kvm_apic_set_reg(kapic, 0x8, s->tpr);
+ kvm_apic_set_reg(kapic, 0xd, s->log_dest << 24);
+ kvm_apic_set_reg(kapic, 0xe, s->dest_mode << 28 | 0x0fffffff);
+ kvm_apic_set_reg(kapic, 0xf, s->spurious_vec);
+ for (i = 0; i < 8; i++) {
+ kvm_apic_set_reg(kapic, 0x10 + i, s->isr[i]);
+ kvm_apic_set_reg(kapic, 0x18 + i, s->tmr[i]);
+ kvm_apic_set_reg(kapic, 0x20 + i, s->irr[i]);
+ }
+ kvm_apic_set_reg(kapic, 0x28, s->esr);
+ kvm_apic_set_reg(kapic, 0x30, s->icr[0]);
+ kvm_apic_set_reg(kapic, 0x31, s->icr[1]);
+ for (i = 0; i < APIC_LVT_NB; i++) {
+ kvm_apic_set_reg(kapic, 0x32 + i, s->lvt[i]);
+ }
+ kvm_apic_set_reg(kapic, 0x38, s->initial_count);
+ kvm_apic_set_reg(kapic, 0x3e, s->divide_conf);
+}
+
+void kvm_get_apic_state(DeviceState *dev, struct kvm_lapic_state *kapic)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ int i, v;
+
+ s->id = kvm_apic_get_reg(kapic, 0x2) >> 24;
+ s->tpr = kvm_apic_get_reg(kapic, 0x8);
+ s->arb_id = kvm_apic_get_reg(kapic, 0x9);
+ s->log_dest = kvm_apic_get_reg(kapic, 0xd) >> 24;
+ s->dest_mode = kvm_apic_get_reg(kapic, 0xe) >> 28;
+ s->spurious_vec = kvm_apic_get_reg(kapic, 0xf);
+ for (i = 0; i < 8; i++) {
+ s->isr[i] = kvm_apic_get_reg(kapic, 0x10 + i);
+ s->tmr[i] = kvm_apic_get_reg(kapic, 0x18 + i);
+ s->irr[i] = kvm_apic_get_reg(kapic, 0x20 + i);
+ }
+ s->esr = kvm_apic_get_reg(kapic, 0x28);
+ s->icr[0] = kvm_apic_get_reg(kapic, 0x30);
+ s->icr[1] = kvm_apic_get_reg(kapic, 0x31);
+ for (i = 0; i < APIC_LVT_NB; i++) {
+ s->lvt[i] = kvm_apic_get_reg(kapic, 0x32 + i);
+ }
+ s->initial_count = kvm_apic_get_reg(kapic, 0x38);
+ s->divide_conf = kvm_apic_get_reg(kapic, 0x3e);
+
+ v = (s->divide_conf & 3) | ((s->divide_conf >> 1) & 4);
+ s->count_shift = (v + 1) & 7;
+
+ s->initial_count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ apic_next_timer(s, s->initial_count_load_time);
+}
+
+static void kvm_apic_set_base(APICCommonState *s, uint64_t val)
+{
+ s->apicbase = val;
+}
+
+static void kvm_apic_set_tpr(APICCommonState *s, uint8_t val)
+{
+ s->tpr = (val & 0x0f) << 4;
+}
+
+static uint8_t kvm_apic_get_tpr(APICCommonState *s)
+{
+ return s->tpr >> 4;
+}
+
+static void kvm_apic_enable_tpr_reporting(APICCommonState *s, bool enable)
+{
+ struct kvm_tpr_access_ctl ctl = {
+ .enabled = enable
+ };
+
+ kvm_vcpu_ioctl(CPU(s->cpu), KVM_TPR_ACCESS_REPORTING, &ctl);
+}
+
+static void kvm_apic_vapic_base_update(APICCommonState *s)
+{
+ struct kvm_vapic_addr vapid_addr = {
+ .vapic_addr = s->vapic_paddr,
+ };
+ int ret;
+
+ ret = kvm_vcpu_ioctl(CPU(s->cpu), KVM_SET_VAPIC_ADDR, &vapid_addr);
+ if (ret < 0) {
+ fprintf(stderr, "KVM: setting VAPIC address failed (%s)\n",
+ strerror(-ret));
+ abort();
+ }
+}
+
+static void do_inject_external_nmi(void *data)
+{
+ APICCommonState *s = data;
+ CPUState *cpu = CPU(s->cpu);
+ uint32_t lvt;
+ int ret;
+
+ cpu_synchronize_state(cpu);
+
+ lvt = s->lvt[APIC_LVT_LINT1];
+ if (!(lvt & APIC_LVT_MASKED) && ((lvt >> 8) & 7) == APIC_DM_NMI) {
+ ret = kvm_vcpu_ioctl(cpu, KVM_NMI);
+ if (ret < 0) {
+ fprintf(stderr, "KVM: injection failed, NMI lost (%s)\n",
+ strerror(-ret));
+ }
+ }
+}
+
+static void kvm_apic_external_nmi(APICCommonState *s)
+{
+ run_on_cpu(CPU(s->cpu), do_inject_external_nmi, s);
+}
+
+static uint64_t kvm_apic_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return ~(uint64_t)0;
+}
+
+static void kvm_apic_mem_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ MSIMessage msg = { .address = addr, .data = data };
+ int ret;
+
+ ret = kvm_irqchip_send_msi(kvm_state, msg);
+ if (ret < 0) {
+ fprintf(stderr, "KVM: injection failed, MSI lost (%s)\n",
+ strerror(-ret));
+ }
+}
+
+static const MemoryRegionOps kvm_apic_io_ops = {
+ .read = kvm_apic_mem_read,
+ .write = kvm_apic_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void kvm_apic_reset(APICCommonState *s)
+{
+ /* Not used by KVM, which uses the CPU mp_state instead. */
+ s->wait_for_sipi = 0;
+}
+
+static void kvm_apic_realize(DeviceState *dev, Error **errp)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+
+ memory_region_init_io(&s->io_memory, NULL, &kvm_apic_io_ops, s, "kvm-apic-msi",
+ APIC_SPACE_SIZE);
+
+ if (kvm_has_gsi_routing()) {
+ msi_supported = true;
+ }
+}
+
+static void kvm_apic_class_init(ObjectClass *klass, void *data)
+{
+ APICCommonClass *k = APIC_COMMON_CLASS(klass);
+
+ k->realize = kvm_apic_realize;
+ k->reset = kvm_apic_reset;
+ k->set_base = kvm_apic_set_base;
+ k->set_tpr = kvm_apic_set_tpr;
+ k->get_tpr = kvm_apic_get_tpr;
+ k->enable_tpr_reporting = kvm_apic_enable_tpr_reporting;
+ k->vapic_base_update = kvm_apic_vapic_base_update;
+ k->external_nmi = kvm_apic_external_nmi;
+}
+
+static const TypeInfo kvm_apic_info = {
+ .name = "kvm-apic",
+ .parent = TYPE_APIC_COMMON,
+ .instance_size = sizeof(APICCommonState),
+ .class_init = kvm_apic_class_init,
+};
+
+static void kvm_apic_register_types(void)
+{
+ type_register_static(&kvm_apic_info);
+}
+
+type_init(kvm_apic_register_types)
diff --git a/hw/i386/kvm/clock.c b/hw/i386/kvm/clock.c
new file mode 100644
index 00000000..efdf1658
--- /dev/null
+++ b/hw/i386/kvm/clock.c
@@ -0,0 +1,209 @@
+/*
+ * QEMU KVM support, paravirtual clock device
+ *
+ * Copyright (C) 2011 Siemens AG
+ *
+ * Authors:
+ * Jan Kiszka <jan.kiszka@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL version 2.
+ * See the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu-common.h"
+#include "qemu/host-utils.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/kvm.h"
+#include "sysemu/cpus.h"
+#include "hw/sysbus.h"
+#include "hw/kvm/clock.h"
+
+#include <linux/kvm.h>
+#include <linux/kvm_para.h>
+
+#define TYPE_KVM_CLOCK "kvmclock"
+#define KVM_CLOCK(obj) OBJECT_CHECK(KVMClockState, (obj), TYPE_KVM_CLOCK)
+
+typedef struct KVMClockState {
+ /*< private >*/
+ SysBusDevice busdev;
+ /*< public >*/
+
+ uint64_t clock;
+ bool clock_valid;
+} KVMClockState;
+
+struct pvclock_vcpu_time_info {
+ uint32_t version;
+ uint32_t pad0;
+ uint64_t tsc_timestamp;
+ uint64_t system_time;
+ uint32_t tsc_to_system_mul;
+ int8_t tsc_shift;
+ uint8_t flags;
+ uint8_t pad[2];
+} __attribute__((__packed__)); /* 32 bytes */
+
+static uint64_t kvmclock_current_nsec(KVMClockState *s)
+{
+ CPUState *cpu = first_cpu;
+ CPUX86State *env = cpu->env_ptr;
+ hwaddr kvmclock_struct_pa = env->system_time_msr & ~1ULL;
+ uint64_t migration_tsc = env->tsc;
+ struct pvclock_vcpu_time_info time;
+ uint64_t delta;
+ uint64_t nsec_lo;
+ uint64_t nsec_hi;
+ uint64_t nsec;
+
+ if (!(env->system_time_msr & 1ULL)) {
+ /* KVM clock not active */
+ return 0;
+ }
+
+ cpu_physical_memory_read(kvmclock_struct_pa, &time, sizeof(time));
+
+ assert(time.tsc_timestamp <= migration_tsc);
+ delta = migration_tsc - time.tsc_timestamp;
+ if (time.tsc_shift < 0) {
+ delta >>= -time.tsc_shift;
+ } else {
+ delta <<= time.tsc_shift;
+ }
+
+ mulu64(&nsec_lo, &nsec_hi, delta, time.tsc_to_system_mul);
+ nsec = (nsec_lo >> 32) | (nsec_hi << 32);
+ return nsec + time.system_time;
+}
+
+static void kvmclock_vm_state_change(void *opaque, int running,
+ RunState state)
+{
+ KVMClockState *s = opaque;
+ CPUState *cpu;
+ int cap_clock_ctrl = kvm_check_extension(kvm_state, KVM_CAP_KVMCLOCK_CTRL);
+ int ret;
+
+ if (running) {
+ struct kvm_clock_data data = {};
+ uint64_t time_at_migration = kvmclock_current_nsec(s);
+
+ s->clock_valid = false;
+
+ /* We can't rely on the migrated clock value, just discard it */
+ if (time_at_migration) {
+ s->clock = time_at_migration;
+ }
+
+ data.clock = s->clock;
+ ret = kvm_vm_ioctl(kvm_state, KVM_SET_CLOCK, &data);
+ if (ret < 0) {
+ fprintf(stderr, "KVM_SET_CLOCK failed: %s\n", strerror(ret));
+ abort();
+ }
+
+ if (!cap_clock_ctrl) {
+ return;
+ }
+ CPU_FOREACH(cpu) {
+ ret = kvm_vcpu_ioctl(cpu, KVM_KVMCLOCK_CTRL, 0);
+ if (ret) {
+ if (ret != -EINVAL) {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(-ret));
+ }
+ return;
+ }
+ }
+ } else {
+ struct kvm_clock_data data;
+ int ret;
+
+ if (s->clock_valid) {
+ return;
+ }
+
+ cpu_synchronize_all_states();
+ /* In theory, the cpu_synchronize_all_states() call above wouldn't
+ * affect the rest of the code, as the VCPU state inside CPUState
+ * is supposed to always match the VCPU state on the kernel side.
+ *
+ * In practice, calling cpu_synchronize_state() too soon will load the
+ * kernel-side APIC state into X86CPU.apic_state too early, APIC state
+ * won't be reloaded later because CPUState.vcpu_dirty==true, and
+ * outdated APIC state may be migrated to another host.
+ *
+ * The real fix would be to make sure outdated APIC state is read
+ * from the kernel again when necessary. While this is not fixed, we
+ * need the cpu_clean_all_dirty() call below.
+ */
+ cpu_clean_all_dirty();
+
+ ret = kvm_vm_ioctl(kvm_state, KVM_GET_CLOCK, &data);
+ if (ret < 0) {
+ fprintf(stderr, "KVM_GET_CLOCK failed: %s\n", strerror(ret));
+ abort();
+ }
+ s->clock = data.clock;
+
+ /*
+ * If the VM is stopped, declare the clock state valid to
+ * avoid re-reading it on next vmsave (which would return
+ * a different value). Will be reset when the VM is continued.
+ */
+ s->clock_valid = true;
+ }
+}
+
+static void kvmclock_realize(DeviceState *dev, Error **errp)
+{
+ KVMClockState *s = KVM_CLOCK(dev);
+
+ qemu_add_vm_change_state_handler(kvmclock_vm_state_change, s);
+}
+
+static const VMStateDescription kvmclock_vmsd = {
+ .name = "kvmclock",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(clock, KVMClockState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void kvmclock_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = kvmclock_realize;
+ dc->vmsd = &kvmclock_vmsd;
+}
+
+static const TypeInfo kvmclock_info = {
+ .name = TYPE_KVM_CLOCK,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(KVMClockState),
+ .class_init = kvmclock_class_init,
+};
+
+/* Note: Must be called after VCPU initialization. */
+void kvmclock_create(void)
+{
+ X86CPU *cpu = X86_CPU(first_cpu);
+
+ if (kvm_enabled() &&
+ cpu->env.features[FEAT_KVM] & ((1ULL << KVM_FEATURE_CLOCKSOURCE) |
+ (1ULL << KVM_FEATURE_CLOCKSOURCE2))) {
+ sysbus_create_simple(TYPE_KVM_CLOCK, -1, NULL);
+ }
+}
+
+static void kvmclock_register_types(void)
+{
+ type_register_static(&kvmclock_info);
+}
+
+type_init(kvmclock_register_types)
diff --git a/hw/i386/kvm/i8254.c b/hw/i386/kvm/i8254.c
new file mode 100644
index 00000000..90eea10d
--- /dev/null
+++ b/hw/i386/kvm/i8254.c
@@ -0,0 +1,335 @@
+/*
+ * KVM in-kernel PIT (i8254) support
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2012 Jan Kiszka, Siemens AG
+ *
+ * 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.
+ */
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/timer/i8254.h"
+#include "hw/timer/i8254_internal.h"
+#include "sysemu/kvm.h"
+
+#define KVM_PIT_REINJECT_BIT 0
+
+#define CALIBRATION_ROUNDS 3
+
+#define KVM_PIT(obj) OBJECT_CHECK(KVMPITState, (obj), TYPE_KVM_I8254)
+#define KVM_PIT_CLASS(class) \
+ OBJECT_CLASS_CHECK(KVMPITClass, (class), TYPE_KVM_I8254)
+#define KVM_PIT_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(KVMPITClass, (obj), TYPE_KVM_I8254)
+
+typedef struct KVMPITState {
+ PITCommonState parent_obj;
+
+ LostTickPolicy lost_tick_policy;
+ bool vm_stopped;
+ int64_t kernel_clock_offset;
+} KVMPITState;
+
+typedef struct KVMPITClass {
+ PITCommonClass parent_class;
+
+ DeviceRealize parent_realize;
+} KVMPITClass;
+
+static int64_t abs64(int64_t v)
+{
+ return v < 0 ? -v : v;
+}
+
+static void kvm_pit_update_clock_offset(KVMPITState *s)
+{
+ int64_t offset, clock_offset;
+ struct timespec ts;
+ int i;
+
+ /*
+ * Measure the delta between CLOCK_MONOTONIC, the base used for
+ * kvm_pit_channel_state::count_load_time, and QEMU_CLOCK_VIRTUAL. Take the
+ * minimum of several samples to filter out scheduling noise.
+ */
+ clock_offset = INT64_MAX;
+ for (i = 0; i < CALIBRATION_ROUNDS; i++) {
+ offset = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ offset -= ts.tv_nsec;
+ offset -= (int64_t)ts.tv_sec * 1000000000;
+ if (abs64(offset) < abs64(clock_offset)) {
+ clock_offset = offset;
+ }
+ }
+ s->kernel_clock_offset = clock_offset;
+}
+
+static void kvm_pit_get(PITCommonState *pit)
+{
+ KVMPITState *s = KVM_PIT(pit);
+ struct kvm_pit_state2 kpit;
+ struct kvm_pit_channel_state *kchan;
+ struct PITChannelState *sc;
+ int i, ret;
+
+ /* No need to re-read the state if VM is stopped. */
+ if (s->vm_stopped) {
+ return;
+ }
+
+ if (kvm_has_pit_state2()) {
+ ret = kvm_vm_ioctl(kvm_state, KVM_GET_PIT2, &kpit);
+ if (ret < 0) {
+ fprintf(stderr, "KVM_GET_PIT2 failed: %s\n", strerror(ret));
+ abort();
+ }
+ pit->channels[0].irq_disabled = kpit.flags & KVM_PIT_FLAGS_HPET_LEGACY;
+ } else {
+ /*
+ * kvm_pit_state2 is superset of kvm_pit_state struct,
+ * so we can use it for KVM_GET_PIT as well.
+ */
+ ret = kvm_vm_ioctl(kvm_state, KVM_GET_PIT, &kpit);
+ if (ret < 0) {
+ fprintf(stderr, "KVM_GET_PIT failed: %s\n", strerror(ret));
+ abort();
+ }
+ }
+ for (i = 0; i < 3; i++) {
+ kchan = &kpit.channels[i];
+ sc = &pit->channels[i];
+ sc->count = kchan->count;
+ sc->latched_count = kchan->latched_count;
+ sc->count_latched = kchan->count_latched;
+ sc->status_latched = kchan->status_latched;
+ sc->status = kchan->status;
+ sc->read_state = kchan->read_state;
+ sc->write_state = kchan->write_state;
+ sc->write_latch = kchan->write_latch;
+ sc->rw_mode = kchan->rw_mode;
+ sc->mode = kchan->mode;
+ sc->bcd = kchan->bcd;
+ sc->gate = kchan->gate;
+ sc->count_load_time = kchan->count_load_time + s->kernel_clock_offset;
+ }
+
+ sc = &pit->channels[0];
+ sc->next_transition_time =
+ pit_get_next_transition_time(sc, sc->count_load_time);
+}
+
+static void kvm_pit_put(PITCommonState *pit)
+{
+ KVMPITState *s = KVM_PIT(pit);
+ struct kvm_pit_state2 kpit = {};
+ struct kvm_pit_channel_state *kchan;
+ struct PITChannelState *sc;
+ int i, ret;
+
+ /* The offset keeps changing as long as the VM is stopped. */
+ if (s->vm_stopped) {
+ kvm_pit_update_clock_offset(s);
+ }
+
+ kpit.flags = pit->channels[0].irq_disabled ? KVM_PIT_FLAGS_HPET_LEGACY : 0;
+ for (i = 0; i < 3; i++) {
+ kchan = &kpit.channels[i];
+ sc = &pit->channels[i];
+ kchan->count = sc->count;
+ kchan->latched_count = sc->latched_count;
+ kchan->count_latched = sc->count_latched;
+ kchan->status_latched = sc->status_latched;
+ kchan->status = sc->status;
+ kchan->read_state = sc->read_state;
+ kchan->write_state = sc->write_state;
+ kchan->write_latch = sc->write_latch;
+ kchan->rw_mode = sc->rw_mode;
+ kchan->mode = sc->mode;
+ kchan->bcd = sc->bcd;
+ kchan->gate = sc->gate;
+ kchan->count_load_time = sc->count_load_time - s->kernel_clock_offset;
+ }
+
+ ret = kvm_vm_ioctl(kvm_state,
+ kvm_has_pit_state2() ? KVM_SET_PIT2 : KVM_SET_PIT,
+ &kpit);
+ if (ret < 0) {
+ fprintf(stderr, "%s failed: %s\n",
+ kvm_has_pit_state2() ? "KVM_SET_PIT2" : "KVM_SET_PIT",
+ strerror(ret));
+ abort();
+ }
+}
+
+static void kvm_pit_set_gate(PITCommonState *s, PITChannelState *sc, int val)
+{
+ kvm_pit_get(s);
+
+ switch (sc->mode) {
+ default:
+ case 0:
+ case 4:
+ /* XXX: just disable/enable counting */
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 5:
+ if (sc->gate < val) {
+ /* restart counting on rising edge */
+ sc->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+ break;
+ }
+ sc->gate = val;
+
+ kvm_pit_put(s);
+}
+
+static void kvm_pit_get_channel_info(PITCommonState *s, PITChannelState *sc,
+ PITChannelInfo *info)
+{
+ kvm_pit_get(s);
+
+ pit_get_channel_info_common(s, sc, info);
+}
+
+static void kvm_pit_reset(DeviceState *dev)
+{
+ PITCommonState *s = PIT_COMMON(dev);
+
+ pit_reset_common(s);
+
+ kvm_pit_put(s);
+}
+
+static void kvm_pit_irq_control(void *opaque, int n, int enable)
+{
+ PITCommonState *pit = opaque;
+ PITChannelState *s = &pit->channels[0];
+
+ kvm_pit_get(pit);
+
+ s->irq_disabled = !enable;
+
+ kvm_pit_put(pit);
+}
+
+static void kvm_pit_vm_state_change(void *opaque, int running,
+ RunState state)
+{
+ KVMPITState *s = opaque;
+
+ if (running) {
+ kvm_pit_update_clock_offset(s);
+ kvm_pit_put(PIT_COMMON(s));
+ s->vm_stopped = false;
+ } else {
+ kvm_pit_update_clock_offset(s);
+ kvm_pit_get(PIT_COMMON(s));
+ s->vm_stopped = true;
+ }
+}
+
+static void kvm_pit_realizefn(DeviceState *dev, Error **errp)
+{
+ PITCommonState *pit = PIT_COMMON(dev);
+ KVMPITClass *kpc = KVM_PIT_GET_CLASS(dev);
+ KVMPITState *s = KVM_PIT(pit);
+ struct kvm_pit_config config = {
+ .flags = 0,
+ };
+ int ret;
+
+ if (kvm_check_extension(kvm_state, KVM_CAP_PIT2)) {
+ ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_PIT2, &config);
+ } else {
+ ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_PIT);
+ }
+ if (ret < 0) {
+ error_setg(errp, "Create kernel PIC irqchip failed: %s",
+ strerror(ret));
+ return;
+ }
+ switch (s->lost_tick_policy) {
+ case LOST_TICK_POLICY_DELAY:
+ break; /* enabled by default */
+ case LOST_TICK_POLICY_DISCARD:
+ if (kvm_check_extension(kvm_state, KVM_CAP_REINJECT_CONTROL)) {
+ struct kvm_reinject_control control = { .pit_reinject = 0 };
+
+ ret = kvm_vm_ioctl(kvm_state, KVM_REINJECT_CONTROL, &control);
+ if (ret < 0) {
+ error_setg(errp,
+ "Can't disable in-kernel PIT reinjection: %s",
+ strerror(ret));
+ return;
+ }
+ }
+ break;
+ default:
+ error_setg(errp, "Lost tick policy not supported.");
+ return;
+ }
+
+ memory_region_init_reservation(&pit->ioports, NULL, "kvm-pit", 4);
+
+ qdev_init_gpio_in(dev, kvm_pit_irq_control, 1);
+
+ qemu_add_vm_change_state_handler(kvm_pit_vm_state_change, s);
+
+ kpc->parent_realize(dev, errp);
+}
+
+static Property kvm_pit_properties[] = {
+ DEFINE_PROP_UINT32("iobase", PITCommonState, iobase, -1),
+ DEFINE_PROP_LOSTTICKPOLICY("lost_tick_policy", KVMPITState,
+ lost_tick_policy, LOST_TICK_POLICY_DELAY),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void kvm_pit_class_init(ObjectClass *klass, void *data)
+{
+ KVMPITClass *kpc = KVM_PIT_CLASS(klass);
+ PITCommonClass *k = PIT_COMMON_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ kpc->parent_realize = dc->realize;
+ dc->realize = kvm_pit_realizefn;
+ k->set_channel_gate = kvm_pit_set_gate;
+ k->get_channel_info = kvm_pit_get_channel_info;
+ dc->reset = kvm_pit_reset;
+ dc->props = kvm_pit_properties;
+}
+
+static const TypeInfo kvm_pit_info = {
+ .name = TYPE_KVM_I8254,
+ .parent = TYPE_PIT_COMMON,
+ .instance_size = sizeof(KVMPITState),
+ .class_init = kvm_pit_class_init,
+ .class_size = sizeof(KVMPITClass),
+};
+
+static void kvm_pit_register(void)
+{
+ type_register_static(&kvm_pit_info);
+}
+
+type_init(kvm_pit_register)
diff --git a/hw/i386/kvm/i8259.c b/hw/i386/kvm/i8259.c
new file mode 100644
index 00000000..53e3ca8c
--- /dev/null
+++ b/hw/i386/kvm/i8259.c
@@ -0,0 +1,162 @@
+/*
+ * KVM in-kernel PIC (i8259) support
+ *
+ * Copyright (c) 2011 Siemens AG
+ *
+ * Authors:
+ * Jan Kiszka <jan.kiszka@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL version 2.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/isa/i8259_internal.h"
+#include "hw/i386/apic_internal.h"
+#include "sysemu/kvm.h"
+
+#define TYPE_KVM_I8259 "kvm-i8259"
+#define KVM_PIC_CLASS(class) \
+ OBJECT_CLASS_CHECK(KVMPICClass, (class), TYPE_KVM_I8259)
+#define KVM_PIC_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(KVMPICClass, (obj), TYPE_KVM_I8259)
+
+/**
+ * KVMPICClass:
+ * @parent_realize: The parent's realizefn.
+ */
+typedef struct KVMPICClass {
+ PICCommonClass parent_class;
+
+ DeviceRealize parent_realize;
+} KVMPICClass;
+
+static void kvm_pic_get(PICCommonState *s)
+{
+ struct kvm_irqchip chip;
+ struct kvm_pic_state *kpic;
+ int ret;
+
+ chip.chip_id = s->master ? KVM_IRQCHIP_PIC_MASTER : KVM_IRQCHIP_PIC_SLAVE;
+ ret = kvm_vm_ioctl(kvm_state, KVM_GET_IRQCHIP, &chip);
+ if (ret < 0) {
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
+ abort();
+ }
+
+ kpic = &chip.chip.pic;
+
+ s->last_irr = kpic->last_irr;
+ s->irr = kpic->irr;
+ s->imr = kpic->imr;
+ s->isr = kpic->isr;
+ s->priority_add = kpic->priority_add;
+ s->irq_base = kpic->irq_base;
+ s->read_reg_select = kpic->read_reg_select;
+ s->poll = kpic->poll;
+ s->special_mask = kpic->special_mask;
+ s->init_state = kpic->init_state;
+ s->auto_eoi = kpic->auto_eoi;
+ s->rotate_on_auto_eoi = kpic->rotate_on_auto_eoi;
+ s->special_fully_nested_mode = kpic->special_fully_nested_mode;
+ s->init4 = kpic->init4;
+ s->elcr = kpic->elcr;
+ s->elcr_mask = kpic->elcr_mask;
+}
+
+static void kvm_pic_put(PICCommonState *s)
+{
+ struct kvm_irqchip chip;
+ struct kvm_pic_state *kpic;
+ int ret;
+
+ chip.chip_id = s->master ? KVM_IRQCHIP_PIC_MASTER : KVM_IRQCHIP_PIC_SLAVE;
+
+ kpic = &chip.chip.pic;
+
+ kpic->last_irr = s->last_irr;
+ kpic->irr = s->irr;
+ kpic->imr = s->imr;
+ kpic->isr = s->isr;
+ kpic->priority_add = s->priority_add;
+ kpic->irq_base = s->irq_base;
+ kpic->read_reg_select = s->read_reg_select;
+ kpic->poll = s->poll;
+ kpic->special_mask = s->special_mask;
+ kpic->init_state = s->init_state;
+ kpic->auto_eoi = s->auto_eoi;
+ kpic->rotate_on_auto_eoi = s->rotate_on_auto_eoi;
+ kpic->special_fully_nested_mode = s->special_fully_nested_mode;
+ kpic->init4 = s->init4;
+ kpic->elcr = s->elcr;
+ kpic->elcr_mask = s->elcr_mask;
+
+ ret = kvm_vm_ioctl(kvm_state, KVM_SET_IRQCHIP, &chip);
+ if (ret < 0) {
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
+ abort();
+ }
+}
+
+static void kvm_pic_reset(DeviceState *dev)
+{
+ PICCommonState *s = PIC_COMMON(dev);
+
+ s->elcr = 0;
+ pic_reset_common(s);
+
+ kvm_pic_put(s);
+}
+
+static void kvm_pic_set_irq(void *opaque, int irq, int level)
+{
+ int delivered;
+
+ delivered = kvm_set_irq(kvm_state, irq, level);
+ apic_report_irq_delivered(delivered);
+}
+
+static void kvm_pic_realize(DeviceState *dev, Error **errp)
+{
+ PICCommonState *s = PIC_COMMON(dev);
+ KVMPICClass *kpc = KVM_PIC_GET_CLASS(dev);
+
+ memory_region_init_reservation(&s->base_io, NULL, "kvm-pic", 2);
+ memory_region_init_reservation(&s->elcr_io, NULL, "kvm-elcr", 1);
+
+ kpc->parent_realize(dev, errp);
+}
+
+qemu_irq *kvm_i8259_init(ISABus *bus)
+{
+ i8259_init_chip(TYPE_KVM_I8259, bus, true);
+ i8259_init_chip(TYPE_KVM_I8259, bus, false);
+
+ return qemu_allocate_irqs(kvm_pic_set_irq, NULL, ISA_NUM_IRQS);
+}
+
+static void kvm_i8259_class_init(ObjectClass *klass, void *data)
+{
+ KVMPICClass *kpc = KVM_PIC_CLASS(klass);
+ PICCommonClass *k = PIC_COMMON_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = kvm_pic_reset;
+ kpc->parent_realize = dc->realize;
+ dc->realize = kvm_pic_realize;
+ k->pre_save = kvm_pic_get;
+ k->post_load = kvm_pic_put;
+}
+
+static const TypeInfo kvm_i8259_info = {
+ .name = TYPE_KVM_I8259,
+ .parent = TYPE_PIC_COMMON,
+ .instance_size = sizeof(PICCommonState),
+ .class_init = kvm_i8259_class_init,
+ .class_size = sizeof(KVMPICClass),
+};
+
+static void kvm_pic_register_types(void)
+{
+ type_register_static(&kvm_i8259_info);
+}
+
+type_init(kvm_pic_register_types)
diff --git a/hw/i386/kvm/ioapic.c b/hw/i386/kvm/ioapic.c
new file mode 100644
index 00000000..d2a6c4cf
--- /dev/null
+++ b/hw/i386/kvm/ioapic.c
@@ -0,0 +1,168 @@
+/*
+ * KVM in-kernel IOPIC support
+ *
+ * Copyright (c) 2011 Siemens AG
+ *
+ * Authors:
+ * Jan Kiszka <jan.kiszka@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL version 2.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "hw/i386/pc.h"
+#include "hw/i386/ioapic_internal.h"
+#include "hw/i386/apic_internal.h"
+#include "sysemu/kvm.h"
+
+/* PC Utility function */
+void kvm_pc_setup_irq_routing(bool pci_enabled)
+{
+ KVMState *s = kvm_state;
+ int i;
+
+ if (kvm_check_extension(s, KVM_CAP_IRQ_ROUTING)) {
+ for (i = 0; i < 8; ++i) {
+ if (i == 2) {
+ continue;
+ }
+ kvm_irqchip_add_irq_route(s, i, KVM_IRQCHIP_PIC_MASTER, i);
+ }
+ for (i = 8; i < 16; ++i) {
+ kvm_irqchip_add_irq_route(s, i, KVM_IRQCHIP_PIC_SLAVE, i - 8);
+ }
+ if (pci_enabled) {
+ for (i = 0; i < 24; ++i) {
+ if (i == 0) {
+ kvm_irqchip_add_irq_route(s, i, KVM_IRQCHIP_IOAPIC, 2);
+ } else if (i != 2) {
+ kvm_irqchip_add_irq_route(s, i, KVM_IRQCHIP_IOAPIC, i);
+ }
+ }
+ }
+ kvm_irqchip_commit_routes(s);
+ }
+}
+
+void kvm_pc_gsi_handler(void *opaque, int n, int level)
+{
+ GSIState *s = opaque;
+
+ if (n < ISA_NUM_IRQS) {
+ /* Kernel will forward to both PIC and IOAPIC */
+ qemu_set_irq(s->i8259_irq[n], level);
+ } else {
+ qemu_set_irq(s->ioapic_irq[n], level);
+ }
+}
+
+typedef struct KVMIOAPICState KVMIOAPICState;
+
+struct KVMIOAPICState {
+ IOAPICCommonState ioapic;
+ uint32_t kvm_gsi_base;
+};
+
+static void kvm_ioapic_get(IOAPICCommonState *s)
+{
+ struct kvm_irqchip chip;
+ struct kvm_ioapic_state *kioapic;
+ int ret, i;
+
+ chip.chip_id = KVM_IRQCHIP_IOAPIC;
+ ret = kvm_vm_ioctl(kvm_state, KVM_GET_IRQCHIP, &chip);
+ if (ret < 0) {
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
+ abort();
+ }
+
+ kioapic = &chip.chip.ioapic;
+
+ s->id = kioapic->id;
+ s->ioregsel = kioapic->ioregsel;
+ s->irr = kioapic->irr;
+ for (i = 0; i < IOAPIC_NUM_PINS; i++) {
+ s->ioredtbl[i] = kioapic->redirtbl[i].bits;
+ }
+}
+
+static void kvm_ioapic_put(IOAPICCommonState *s)
+{
+ struct kvm_irqchip chip;
+ struct kvm_ioapic_state *kioapic;
+ int ret, i;
+
+ chip.chip_id = KVM_IRQCHIP_IOAPIC;
+ kioapic = &chip.chip.ioapic;
+
+ kioapic->id = s->id;
+ kioapic->ioregsel = s->ioregsel;
+ kioapic->base_address = s->busdev.mmio[0].addr;
+ kioapic->irr = s->irr;
+ for (i = 0; i < IOAPIC_NUM_PINS; i++) {
+ kioapic->redirtbl[i].bits = s->ioredtbl[i];
+ }
+
+ ret = kvm_vm_ioctl(kvm_state, KVM_SET_IRQCHIP, &chip);
+ if (ret < 0) {
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
+ abort();
+ }
+}
+
+static void kvm_ioapic_reset(DeviceState *dev)
+{
+ IOAPICCommonState *s = IOAPIC_COMMON(dev);
+
+ ioapic_reset_common(dev);
+ kvm_ioapic_put(s);
+}
+
+static void kvm_ioapic_set_irq(void *opaque, int irq, int level)
+{
+ KVMIOAPICState *s = opaque;
+ int delivered;
+
+ delivered = kvm_set_irq(kvm_state, s->kvm_gsi_base + irq, level);
+ apic_report_irq_delivered(delivered);
+}
+
+static void kvm_ioapic_realize(DeviceState *dev, Error **errp)
+{
+ IOAPICCommonState *s = IOAPIC_COMMON(dev);
+
+ memory_region_init_reservation(&s->io_memory, NULL, "kvm-ioapic", 0x1000);
+
+ qdev_init_gpio_in(dev, kvm_ioapic_set_irq, IOAPIC_NUM_PINS);
+}
+
+static Property kvm_ioapic_properties[] = {
+ DEFINE_PROP_UINT32("gsi_base", KVMIOAPICState, kvm_gsi_base, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void kvm_ioapic_class_init(ObjectClass *klass, void *data)
+{
+ IOAPICCommonClass *k = IOAPIC_COMMON_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = kvm_ioapic_realize;
+ k->pre_save = kvm_ioapic_get;
+ k->post_load = kvm_ioapic_put;
+ dc->reset = kvm_ioapic_reset;
+ dc->props = kvm_ioapic_properties;
+}
+
+static const TypeInfo kvm_ioapic_info = {
+ .name = "kvm-ioapic",
+ .parent = TYPE_IOAPIC_COMMON,
+ .instance_size = sizeof(KVMIOAPICState),
+ .class_init = kvm_ioapic_class_init,
+};
+
+static void kvm_ioapic_register_types(void)
+{
+ type_register_static(&kvm_ioapic_info);
+}
+
+type_init(kvm_ioapic_register_types)
diff --git a/hw/i386/kvm/pci-assign.c b/hw/i386/kvm/pci-assign.c
new file mode 100644
index 00000000..74d22f4f
--- /dev/null
+++ b/hw/i386/kvm/pci-assign.c
@@ -0,0 +1,1968 @@
+/*
+ * Copyright (c) 2007, Neocleus Corporation.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ *
+ * Assign a PCI device from the host to a guest VM.
+ *
+ * This implementation uses the classic device assignment interface of KVM
+ * and is only available on x86 hosts. It is expected to be obsoleted by VFIO
+ * based device assignment.
+ *
+ * Adapted for KVM (qemu-kvm) by Qumranet. QEMU version was based on qemu-kvm
+ * revision 4144fe9d48. See its repository for the history.
+ *
+ * Copyright (c) 2007, Neocleus, Alex Novik (alex@neocleus.com)
+ * Copyright (c) 2007, Neocleus, Guy Zana (guy@neocleus.com)
+ * Copyright (C) 2008, Qumranet, Amit Shah (amit.shah@qumranet.com)
+ * Copyright (C) 2008, Red Hat, Amit Shah (amit.shah@redhat.com)
+ * Copyright (C) 2008, IBM, Muli Ben-Yehuda (muli@il.ibm.com)
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/io.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "qemu/error-report.h"
+#include "ui/console.h"
+#include "hw/loader.h"
+#include "monitor/monitor.h"
+#include "qemu/range.h"
+#include "sysemu/sysemu.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/msi.h"
+#include "kvm_i386.h"
+
+#define MSIX_PAGE_SIZE 0x1000
+
+/* From linux/ioport.h */
+#define IORESOURCE_IO 0x00000100 /* Resource type */
+#define IORESOURCE_MEM 0x00000200
+#define IORESOURCE_IRQ 0x00000400
+#define IORESOURCE_DMA 0x00000800
+#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
+#define IORESOURCE_MEM_64 0x00100000
+
+//#define DEVICE_ASSIGNMENT_DEBUG
+
+#ifdef DEVICE_ASSIGNMENT_DEBUG
+#define DEBUG(fmt, ...) \
+ do { \
+ fprintf(stderr, "%s: " fmt, __func__ , __VA_ARGS__); \
+ } while (0)
+#else
+#define DEBUG(fmt, ...)
+#endif
+
+typedef struct PCIRegion {
+ int type; /* Memory or port I/O */
+ int valid;
+ uint64_t base_addr;
+ uint64_t size; /* size of the region */
+ int resource_fd;
+} PCIRegion;
+
+typedef struct PCIDevRegions {
+ uint8_t bus, dev, func; /* Bus inside domain, device and function */
+ int irq; /* IRQ number */
+ uint16_t region_number; /* number of active regions */
+
+ /* Port I/O or MMIO Regions */
+ PCIRegion regions[PCI_NUM_REGIONS - 1];
+ int config_fd;
+} PCIDevRegions;
+
+typedef struct AssignedDevRegion {
+ MemoryRegion container;
+ MemoryRegion real_iomem;
+ union {
+ uint8_t *r_virtbase; /* mmapped access address for memory regions */
+ uint32_t r_baseport; /* the base guest port for I/O regions */
+ } u;
+ pcibus_t e_size; /* emulated size of region in bytes */
+ pcibus_t r_size; /* real size of region in bytes */
+ PCIRegion *region;
+} AssignedDevRegion;
+
+#define ASSIGNED_DEVICE_PREFER_MSI_BIT 0
+#define ASSIGNED_DEVICE_SHARE_INTX_BIT 1
+
+#define ASSIGNED_DEVICE_PREFER_MSI_MASK (1 << ASSIGNED_DEVICE_PREFER_MSI_BIT)
+#define ASSIGNED_DEVICE_SHARE_INTX_MASK (1 << ASSIGNED_DEVICE_SHARE_INTX_BIT)
+
+typedef struct MSIXTableEntry {
+ uint32_t addr_lo;
+ uint32_t addr_hi;
+ uint32_t data;
+ uint32_t ctrl;
+} MSIXTableEntry;
+
+typedef enum AssignedIRQType {
+ ASSIGNED_IRQ_NONE = 0,
+ ASSIGNED_IRQ_INTX_HOST_INTX,
+ ASSIGNED_IRQ_INTX_HOST_MSI,
+ ASSIGNED_IRQ_MSI,
+ ASSIGNED_IRQ_MSIX
+} AssignedIRQType;
+
+typedef struct AssignedDevice {
+ PCIDevice dev;
+ PCIHostDeviceAddress host;
+ uint32_t dev_id;
+ uint32_t features;
+ int intpin;
+ AssignedDevRegion v_addrs[PCI_NUM_REGIONS - 1];
+ PCIDevRegions real_device;
+ PCIINTxRoute intx_route;
+ AssignedIRQType assigned_irq_type;
+ struct {
+#define ASSIGNED_DEVICE_CAP_MSI (1 << 0)
+#define ASSIGNED_DEVICE_CAP_MSIX (1 << 1)
+ uint32_t available;
+#define ASSIGNED_DEVICE_MSI_ENABLED (1 << 0)
+#define ASSIGNED_DEVICE_MSIX_ENABLED (1 << 1)
+#define ASSIGNED_DEVICE_MSIX_MASKED (1 << 2)
+ uint32_t state;
+ } cap;
+ uint8_t emulate_config_read[PCI_CONFIG_SPACE_SIZE];
+ uint8_t emulate_config_write[PCI_CONFIG_SPACE_SIZE];
+ int msi_virq_nr;
+ int *msi_virq;
+ MSIXTableEntry *msix_table;
+ hwaddr msix_table_addr;
+ uint16_t msix_max;
+ MemoryRegion mmio;
+ char *configfd_name;
+ int32_t bootindex;
+} AssignedDevice;
+
+#define TYPE_PCI_ASSIGN "kvm-pci-assign"
+#define PCI_ASSIGN(obj) OBJECT_CHECK(AssignedDevice, (obj), TYPE_PCI_ASSIGN)
+
+static void assigned_dev_update_irq_routing(PCIDevice *dev);
+
+static void assigned_dev_load_option_rom(AssignedDevice *dev);
+
+static void assigned_dev_unregister_msix_mmio(AssignedDevice *dev);
+
+static uint64_t assigned_dev_ioport_rw(AssignedDevRegion *dev_region,
+ hwaddr addr, int size,
+ uint64_t *data)
+{
+ uint64_t val = 0;
+ int fd = dev_region->region->resource_fd;
+
+ if (data) {
+ DEBUG("pwrite data=%" PRIx64 ", size=%d, e_phys=" TARGET_FMT_plx
+ ", addr="TARGET_FMT_plx"\n", *data, size, addr, addr);
+ if (pwrite(fd, data, size, addr) != size) {
+ error_report("%s - pwrite failed %s", __func__, strerror(errno));
+ }
+ } else {
+ if (pread(fd, &val, size, addr) != size) {
+ error_report("%s - pread failed %s", __func__, strerror(errno));
+ val = (1UL << (size * 8)) - 1;
+ }
+ DEBUG("pread val=%" PRIx64 ", size=%d, e_phys=" TARGET_FMT_plx
+ ", addr=" TARGET_FMT_plx "\n", val, size, addr, addr);
+ }
+ return val;
+}
+
+static void assigned_dev_ioport_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ assigned_dev_ioport_rw(opaque, addr, size, &data);
+}
+
+static uint64_t assigned_dev_ioport_read(void *opaque,
+ hwaddr addr, unsigned size)
+{
+ return assigned_dev_ioport_rw(opaque, addr, size, NULL);
+}
+
+static uint32_t slow_bar_readb(void *opaque, hwaddr addr)
+{
+ AssignedDevRegion *d = opaque;
+ uint8_t *in = d->u.r_virtbase + addr;
+ uint32_t r;
+
+ r = *in;
+ DEBUG("addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r);
+
+ return r;
+}
+
+static uint32_t slow_bar_readw(void *opaque, hwaddr addr)
+{
+ AssignedDevRegion *d = opaque;
+ uint16_t *in = (uint16_t *)(d->u.r_virtbase + addr);
+ uint32_t r;
+
+ r = *in;
+ DEBUG("addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r);
+
+ return r;
+}
+
+static uint32_t slow_bar_readl(void *opaque, hwaddr addr)
+{
+ AssignedDevRegion *d = opaque;
+ uint32_t *in = (uint32_t *)(d->u.r_virtbase + addr);
+ uint32_t r;
+
+ r = *in;
+ DEBUG("addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r);
+
+ return r;
+}
+
+static void slow_bar_writeb(void *opaque, hwaddr addr, uint32_t val)
+{
+ AssignedDevRegion *d = opaque;
+ uint8_t *out = d->u.r_virtbase + addr;
+
+ DEBUG("addr=0x" TARGET_FMT_plx " val=0x%02x\n", addr, val);
+ *out = val;
+}
+
+static void slow_bar_writew(void *opaque, hwaddr addr, uint32_t val)
+{
+ AssignedDevRegion *d = opaque;
+ uint16_t *out = (uint16_t *)(d->u.r_virtbase + addr);
+
+ DEBUG("addr=0x" TARGET_FMT_plx " val=0x%04x\n", addr, val);
+ *out = val;
+}
+
+static void slow_bar_writel(void *opaque, hwaddr addr, uint32_t val)
+{
+ AssignedDevRegion *d = opaque;
+ uint32_t *out = (uint32_t *)(d->u.r_virtbase + addr);
+
+ DEBUG("addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, val);
+ *out = val;
+}
+
+static const MemoryRegionOps slow_bar_ops = {
+ .old_mmio = {
+ .read = { slow_bar_readb, slow_bar_readw, slow_bar_readl, },
+ .write = { slow_bar_writeb, slow_bar_writew, slow_bar_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void assigned_dev_iomem_setup(PCIDevice *pci_dev, int region_num,
+ pcibus_t e_size)
+{
+ AssignedDevice *r_dev = PCI_ASSIGN(pci_dev);
+ AssignedDevRegion *region = &r_dev->v_addrs[region_num];
+ PCIRegion *real_region = &r_dev->real_device.regions[region_num];
+
+ if (e_size > 0) {
+ memory_region_init(&region->container, OBJECT(pci_dev),
+ "assigned-dev-container", e_size);
+ memory_region_add_subregion(&region->container, 0, &region->real_iomem);
+
+ /* deal with MSI-X MMIO page */
+ if (real_region->base_addr <= r_dev->msix_table_addr &&
+ real_region->base_addr + real_region->size >
+ r_dev->msix_table_addr) {
+ uint64_t offset = r_dev->msix_table_addr - real_region->base_addr;
+
+ memory_region_add_subregion_overlap(&region->container,
+ offset,
+ &r_dev->mmio,
+ 1);
+ }
+ }
+}
+
+static const MemoryRegionOps assigned_dev_ioport_ops = {
+ .read = assigned_dev_ioport_read,
+ .write = assigned_dev_ioport_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void assigned_dev_ioport_setup(PCIDevice *pci_dev, int region_num,
+ pcibus_t size)
+{
+ AssignedDevice *r_dev = PCI_ASSIGN(pci_dev);
+ AssignedDevRegion *region = &r_dev->v_addrs[region_num];
+
+ region->e_size = size;
+ memory_region_init(&region->container, OBJECT(pci_dev),
+ "assigned-dev-container", size);
+ memory_region_init_io(&region->real_iomem, OBJECT(pci_dev),
+ &assigned_dev_ioport_ops, r_dev->v_addrs + region_num,
+ "assigned-dev-iomem", size);
+ memory_region_add_subregion(&region->container, 0, &region->real_iomem);
+}
+
+static uint32_t assigned_dev_pci_read(PCIDevice *d, int pos, int len)
+{
+ AssignedDevice *pci_dev = PCI_ASSIGN(d);
+ uint32_t val;
+ ssize_t ret;
+ int fd = pci_dev->real_device.config_fd;
+
+again:
+ ret = pread(fd, &val, len, pos);
+ if (ret != len) {
+ if ((ret < 0) && (errno == EINTR || errno == EAGAIN)) {
+ goto again;
+ }
+
+ hw_error("pci read failed, ret = %zd errno = %d\n", ret, errno);
+ }
+
+ return val;
+}
+
+static uint8_t assigned_dev_pci_read_byte(PCIDevice *d, int pos)
+{
+ return (uint8_t)assigned_dev_pci_read(d, pos, 1);
+}
+
+static void assigned_dev_pci_write(PCIDevice *d, int pos, uint32_t val, int len)
+{
+ AssignedDevice *pci_dev = PCI_ASSIGN(d);
+ ssize_t ret;
+ int fd = pci_dev->real_device.config_fd;
+
+again:
+ ret = pwrite(fd, &val, len, pos);
+ if (ret != len) {
+ if ((ret < 0) && (errno == EINTR || errno == EAGAIN)) {
+ goto again;
+ }
+
+ hw_error("pci write failed, ret = %zd errno = %d\n", ret, errno);
+ }
+}
+
+static void assigned_dev_emulate_config_read(AssignedDevice *dev,
+ uint32_t offset, uint32_t len)
+{
+ memset(dev->emulate_config_read + offset, 0xff, len);
+}
+
+static void assigned_dev_direct_config_read(AssignedDevice *dev,
+ uint32_t offset, uint32_t len)
+{
+ memset(dev->emulate_config_read + offset, 0, len);
+}
+
+static void assigned_dev_direct_config_write(AssignedDevice *dev,
+ uint32_t offset, uint32_t len)
+{
+ memset(dev->emulate_config_write + offset, 0, len);
+}
+
+static uint8_t pci_find_cap_offset(PCIDevice *d, uint8_t cap, uint8_t start)
+{
+ int id;
+ int max_cap = 48;
+ int pos = start ? start : PCI_CAPABILITY_LIST;
+ int status;
+
+ status = assigned_dev_pci_read_byte(d, PCI_STATUS);
+ if ((status & PCI_STATUS_CAP_LIST) == 0) {
+ return 0;
+ }
+
+ while (max_cap--) {
+ pos = assigned_dev_pci_read_byte(d, pos);
+ if (pos < 0x40) {
+ break;
+ }
+
+ pos &= ~3;
+ id = assigned_dev_pci_read_byte(d, pos + PCI_CAP_LIST_ID);
+
+ if (id == 0xff) {
+ break;
+ }
+ if (id == cap) {
+ return pos;
+ }
+
+ pos += PCI_CAP_LIST_NEXT;
+ }
+ return 0;
+}
+
+static void assigned_dev_register_regions(PCIRegion *io_regions,
+ unsigned long regions_num,
+ AssignedDevice *pci_dev,
+ Error **errp)
+{
+ uint32_t i;
+ PCIRegion *cur_region = io_regions;
+
+ for (i = 0; i < regions_num; i++, cur_region++) {
+ if (!cur_region->valid) {
+ continue;
+ }
+
+ /* handle memory io regions */
+ if (cur_region->type & IORESOURCE_MEM) {
+ int t = PCI_BASE_ADDRESS_SPACE_MEMORY;
+ if (cur_region->type & IORESOURCE_PREFETCH) {
+ t |= PCI_BASE_ADDRESS_MEM_PREFETCH;
+ }
+ if (cur_region->type & IORESOURCE_MEM_64) {
+ t |= PCI_BASE_ADDRESS_MEM_TYPE_64;
+ }
+
+ /* map physical memory */
+ pci_dev->v_addrs[i].u.r_virtbase = mmap(NULL, cur_region->size,
+ PROT_WRITE | PROT_READ,
+ MAP_SHARED,
+ cur_region->resource_fd,
+ (off_t)0);
+
+ if (pci_dev->v_addrs[i].u.r_virtbase == MAP_FAILED) {
+ pci_dev->v_addrs[i].u.r_virtbase = NULL;
+ error_setg_errno(errp, errno, "Couldn't mmap 0x%" PRIx64 "!",
+ cur_region->base_addr);
+ return;
+ }
+
+ pci_dev->v_addrs[i].r_size = cur_region->size;
+ pci_dev->v_addrs[i].e_size = 0;
+
+ /* add offset */
+ pci_dev->v_addrs[i].u.r_virtbase +=
+ (cur_region->base_addr & 0xFFF);
+
+ if (cur_region->size & 0xFFF) {
+ error_report("PCI region %d at address 0x%" PRIx64 " has "
+ "size 0x%" PRIx64 ", which is not a multiple of "
+ "4K. You might experience some performance hit "
+ "due to that.",
+ i, cur_region->base_addr, cur_region->size);
+ memory_region_init_io(&pci_dev->v_addrs[i].real_iomem,
+ OBJECT(pci_dev), &slow_bar_ops,
+ &pci_dev->v_addrs[i],
+ "assigned-dev-slow-bar",
+ cur_region->size);
+ } else {
+ void *virtbase = pci_dev->v_addrs[i].u.r_virtbase;
+ char name[32];
+ snprintf(name, sizeof(name), "%s.bar%d",
+ object_get_typename(OBJECT(pci_dev)), i);
+ memory_region_init_ram_ptr(&pci_dev->v_addrs[i].real_iomem,
+ OBJECT(pci_dev), name,
+ cur_region->size, virtbase);
+ vmstate_register_ram(&pci_dev->v_addrs[i].real_iomem,
+ &pci_dev->dev.qdev);
+ }
+
+ assigned_dev_iomem_setup(&pci_dev->dev, i, cur_region->size);
+ pci_register_bar((PCIDevice *) pci_dev, i, t,
+ &pci_dev->v_addrs[i].container);
+ continue;
+ } else {
+ /* handle port io regions */
+ uint32_t val;
+ int ret;
+
+ /* Test kernel support for ioport resource read/write. Old
+ * kernels return EIO. New kernels only allow 1/2/4 byte reads
+ * so should return EINVAL for a 3 byte read */
+ ret = pread(pci_dev->v_addrs[i].region->resource_fd, &val, 3, 0);
+ if (ret >= 0) {
+ error_report("Unexpected return from I/O port read: %d", ret);
+ abort();
+ } else if (errno != EINVAL) {
+ error_report("Kernel doesn't support ioport resource "
+ "access, hiding this region.");
+ close(pci_dev->v_addrs[i].region->resource_fd);
+ cur_region->valid = 0;
+ continue;
+ }
+
+ pci_dev->v_addrs[i].u.r_baseport = cur_region->base_addr;
+ pci_dev->v_addrs[i].r_size = cur_region->size;
+ pci_dev->v_addrs[i].e_size = 0;
+
+ assigned_dev_ioport_setup(&pci_dev->dev, i, cur_region->size);
+ pci_register_bar((PCIDevice *) pci_dev, i,
+ PCI_BASE_ADDRESS_SPACE_IO,
+ &pci_dev->v_addrs[i].container);
+ }
+ }
+
+ /* success */
+}
+
+static void get_real_id(const char *devpath, const char *idname, uint16_t *val,
+ Error **errp)
+{
+ FILE *f;
+ char name[128];
+ long id;
+
+ snprintf(name, sizeof(name), "%s%s", devpath, idname);
+ f = fopen(name, "r");
+ if (f == NULL) {
+ error_setg_file_open(errp, errno, name);
+ return;
+ }
+ if (fscanf(f, "%li\n", &id) == 1) {
+ *val = id;
+ } else {
+ error_setg(errp, "Failed to parse contents of '%s'", name);
+ }
+ fclose(f);
+}
+
+static void get_real_vendor_id(const char *devpath, uint16_t *val,
+ Error **errp)
+{
+ get_real_id(devpath, "vendor", val, errp);
+}
+
+static void get_real_device_id(const char *devpath, uint16_t *val,
+ Error **errp)
+{
+ get_real_id(devpath, "device", val, errp);
+}
+
+static void get_real_device(AssignedDevice *pci_dev, Error **errp)
+{
+ char dir[128], name[128];
+ int fd, r = 0;
+ FILE *f;
+ uint64_t start, end, size, flags;
+ uint16_t id;
+ PCIRegion *rp;
+ PCIDevRegions *dev = &pci_dev->real_device;
+ Error *local_err = NULL;
+
+ dev->region_number = 0;
+
+ snprintf(dir, sizeof(dir), "/sys/bus/pci/devices/%04x:%02x:%02x.%x/",
+ pci_dev->host.domain, pci_dev->host.bus,
+ pci_dev->host.slot, pci_dev->host.function);
+
+ snprintf(name, sizeof(name), "%sconfig", dir);
+
+ if (pci_dev->configfd_name && *pci_dev->configfd_name) {
+ dev->config_fd = monitor_fd_param(cur_mon, pci_dev->configfd_name,
+ &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ } else {
+ dev->config_fd = open(name, O_RDWR);
+
+ if (dev->config_fd == -1) {
+ error_setg_file_open(errp, errno, name);
+ return;
+ }
+ }
+again:
+ r = read(dev->config_fd, pci_dev->dev.config,
+ pci_config_size(&pci_dev->dev));
+ if (r < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ goto again;
+ }
+ error_setg_errno(errp, errno, "read(\"%s\")",
+ (pci_dev->configfd_name && *pci_dev->configfd_name) ?
+ pci_dev->configfd_name : name);
+ return;
+ }
+
+ /* Restore or clear multifunction, this is always controlled by qemu */
+ if (pci_dev->dev.cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
+ pci_dev->dev.config[PCI_HEADER_TYPE] |= PCI_HEADER_TYPE_MULTI_FUNCTION;
+ } else {
+ pci_dev->dev.config[PCI_HEADER_TYPE] &= ~PCI_HEADER_TYPE_MULTI_FUNCTION;
+ }
+
+ /* Clear host resource mapping info. If we choose not to register a
+ * BAR, such as might be the case with the option ROM, we can get
+ * confusing, unwritable, residual addresses from the host here. */
+ memset(&pci_dev->dev.config[PCI_BASE_ADDRESS_0], 0, 24);
+ memset(&pci_dev->dev.config[PCI_ROM_ADDRESS], 0, 4);
+
+ snprintf(name, sizeof(name), "%sresource", dir);
+
+ f = fopen(name, "r");
+ if (f == NULL) {
+ error_setg_file_open(errp, errno, name);
+ return;
+ }
+
+ for (r = 0; r < PCI_ROM_SLOT; r++) {
+ if (fscanf(f, "%" SCNi64 " %" SCNi64 " %" SCNi64 "\n",
+ &start, &end, &flags) != 3) {
+ break;
+ }
+
+ rp = dev->regions + r;
+ rp->valid = 0;
+ rp->resource_fd = -1;
+ size = end - start + 1;
+ flags &= IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH
+ | IORESOURCE_MEM_64;
+ if (size == 0 || (flags & ~IORESOURCE_PREFETCH) == 0) {
+ continue;
+ }
+ if (flags & IORESOURCE_MEM) {
+ flags &= ~IORESOURCE_IO;
+ } else {
+ flags &= ~IORESOURCE_PREFETCH;
+ }
+ snprintf(name, sizeof(name), "%sresource%d", dir, r);
+ fd = open(name, O_RDWR);
+ if (fd == -1) {
+ continue;
+ }
+ rp->resource_fd = fd;
+
+ rp->type = flags;
+ rp->valid = 1;
+ rp->base_addr = start;
+ rp->size = size;
+ pci_dev->v_addrs[r].region = rp;
+ DEBUG("region %d size %" PRIu64 " start 0x%" PRIx64
+ " type %d resource_fd %d\n",
+ r, rp->size, start, rp->type, rp->resource_fd);
+ }
+
+ fclose(f);
+
+ /* read and fill vendor ID */
+ get_real_vendor_id(dir, &id, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ pci_dev->dev.config[0] = id & 0xff;
+ pci_dev->dev.config[1] = (id & 0xff00) >> 8;
+
+ /* read and fill device ID */
+ get_real_device_id(dir, &id, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ pci_dev->dev.config[2] = id & 0xff;
+ pci_dev->dev.config[3] = (id & 0xff00) >> 8;
+
+ pci_word_test_and_clear_mask(pci_dev->emulate_config_write + PCI_COMMAND,
+ PCI_COMMAND_MASTER | PCI_COMMAND_INTX_DISABLE);
+
+ dev->region_number = r;
+}
+
+static void free_msi_virqs(AssignedDevice *dev)
+{
+ int i;
+
+ for (i = 0; i < dev->msi_virq_nr; i++) {
+ if (dev->msi_virq[i] >= 0) {
+ kvm_irqchip_release_virq(kvm_state, dev->msi_virq[i]);
+ dev->msi_virq[i] = -1;
+ }
+ }
+ g_free(dev->msi_virq);
+ dev->msi_virq = NULL;
+ dev->msi_virq_nr = 0;
+}
+
+static void free_assigned_device(AssignedDevice *dev)
+{
+ int i;
+
+ if (dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) {
+ assigned_dev_unregister_msix_mmio(dev);
+ }
+ for (i = 0; i < dev->real_device.region_number; i++) {
+ PCIRegion *pci_region = &dev->real_device.regions[i];
+ AssignedDevRegion *region = &dev->v_addrs[i];
+
+ if (!pci_region->valid) {
+ continue;
+ }
+ if (pci_region->type & IORESOURCE_IO) {
+ if (region->u.r_baseport) {
+ memory_region_del_subregion(&region->container,
+ &region->real_iomem);
+ }
+ } else if (pci_region->type & IORESOURCE_MEM) {
+ if (region->u.r_virtbase) {
+ memory_region_del_subregion(&region->container,
+ &region->real_iomem);
+
+ /* Remove MSI-X table subregion */
+ if (pci_region->base_addr <= dev->msix_table_addr &&
+ pci_region->base_addr + pci_region->size >
+ dev->msix_table_addr) {
+ memory_region_del_subregion(&region->container,
+ &dev->mmio);
+ }
+ if (munmap(region->u.r_virtbase,
+ (pci_region->size + 0xFFF) & 0xFFFFF000)) {
+ error_report("Failed to unmap assigned device region: %s",
+ strerror(errno));
+ }
+ }
+ }
+ if (pci_region->resource_fd >= 0) {
+ close(pci_region->resource_fd);
+ }
+ }
+
+ if (dev->real_device.config_fd >= 0) {
+ close(dev->real_device.config_fd);
+ }
+
+ free_msi_virqs(dev);
+}
+
+/* This function tries to determine the cause of the PCI assignment failure. It
+ * always returns the cause as a dynamically allocated, human readable string.
+ * If the function fails to determine the cause for any internal reason, then
+ * the returned string will state that fact.
+ */
+static char *assign_failed_examine(const AssignedDevice *dev)
+{
+ char name[PATH_MAX], dir[PATH_MAX], driver[PATH_MAX] = {}, *ns;
+ uint16_t vendor_id, device_id;
+ int r;
+ Error *local_err = NULL;
+
+ snprintf(dir, sizeof(dir), "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/",
+ dev->host.domain, dev->host.bus, dev->host.slot,
+ dev->host.function);
+
+ snprintf(name, sizeof(name), "%sdriver", dir);
+
+ r = readlink(name, driver, sizeof(driver));
+ if ((r <= 0) || r >= sizeof(driver)) {
+ goto fail;
+ }
+
+ driver[r] = 0;
+ ns = strrchr(driver, '/');
+ if (!ns) {
+ goto fail;
+ }
+
+ ns++;
+
+ if ((get_real_vendor_id(dir, &vendor_id, &local_err), local_err) ||
+ (get_real_device_id(dir, &device_id, &local_err), local_err)) {
+ /* We're already analyzing an assignment error, so we suppress this
+ * one just like the others above.
+ */
+ error_free(local_err);
+ goto fail;
+ }
+
+ return g_strdup_printf(
+ "*** The driver '%s' is occupying your device %04x:%02x:%02x.%x.\n"
+ "***\n"
+ "*** You can try the following commands to free it:\n"
+ "***\n"
+ "*** $ echo \"%04x %04x\" > /sys/bus/pci/drivers/pci-stub/new_id\n"
+ "*** $ echo \"%04x:%02x:%02x.%x\" > /sys/bus/pci/drivers/%s/unbind\n"
+ "*** $ echo \"%04x:%02x:%02x.%x\" > /sys/bus/pci/drivers/"
+ "pci-stub/bind\n"
+ "*** $ echo \"%04x %04x\" > /sys/bus/pci/drivers/pci-stub/remove_id\n"
+ "***",
+ ns, dev->host.domain, dev->host.bus, dev->host.slot,
+ dev->host.function, vendor_id, device_id,
+ dev->host.domain, dev->host.bus, dev->host.slot, dev->host.function,
+ ns, dev->host.domain, dev->host.bus, dev->host.slot,
+ dev->host.function, vendor_id, device_id);
+
+fail:
+ return g_strdup("Couldn't find out why.");
+}
+
+static void assign_device(AssignedDevice *dev, Error **errp)
+{
+ uint32_t flags = KVM_DEV_ASSIGN_ENABLE_IOMMU;
+ int r;
+
+ /* Only pass non-zero PCI segment to capable module */
+ if (!kvm_check_extension(kvm_state, KVM_CAP_PCI_SEGMENT) &&
+ dev->host.domain) {
+ error_setg(errp, "Can't assign device inside non-zero PCI segment "
+ "as this KVM module doesn't support it.");
+ return;
+ }
+
+ if (!kvm_check_extension(kvm_state, KVM_CAP_IOMMU)) {
+ error_setg(errp, "No IOMMU found. Unable to assign device \"%s\"",
+ dev->dev.qdev.id);
+ return;
+ }
+
+ if (dev->features & ASSIGNED_DEVICE_SHARE_INTX_MASK &&
+ kvm_has_intx_set_mask()) {
+ flags |= KVM_DEV_ASSIGN_PCI_2_3;
+ }
+
+ r = kvm_device_pci_assign(kvm_state, &dev->host, flags, &dev->dev_id);
+ if (r < 0) {
+ switch (r) {
+ case -EBUSY: {
+ char *cause;
+
+ cause = assign_failed_examine(dev);
+ error_setg_errno(errp, -r, "Failed to assign device \"%s\"\n%s",
+ dev->dev.qdev.id, cause);
+ g_free(cause);
+ break;
+ }
+ default:
+ error_setg_errno(errp, -r, "Failed to assign device \"%s\"",
+ dev->dev.qdev.id);
+ break;
+ }
+ }
+}
+
+static void verify_irqchip_in_kernel(Error **errp)
+{
+ if (kvm_irqchip_in_kernel()) {
+ return;
+ }
+ error_setg(errp, "pci-assign requires KVM with in-kernel irqchip enabled");
+}
+
+static int assign_intx(AssignedDevice *dev, Error **errp)
+{
+ AssignedIRQType new_type;
+ PCIINTxRoute intx_route;
+ bool intx_host_msi;
+ int r;
+ Error *local_err = NULL;
+
+ /* Interrupt PIN 0 means don't use INTx */
+ if (assigned_dev_pci_read_byte(&dev->dev, PCI_INTERRUPT_PIN) == 0) {
+ pci_device_set_intx_routing_notifier(&dev->dev, NULL);
+ return 0;
+ }
+
+ verify_irqchip_in_kernel(&local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return -ENOTSUP;
+ }
+
+ pci_device_set_intx_routing_notifier(&dev->dev,
+ assigned_dev_update_irq_routing);
+
+ intx_route = pci_device_route_intx_to_irq(&dev->dev, dev->intpin);
+ assert(intx_route.mode != PCI_INTX_INVERTED);
+
+ if (!pci_intx_route_changed(&dev->intx_route, &intx_route)) {
+ return 0;
+ }
+
+ switch (dev->assigned_irq_type) {
+ case ASSIGNED_IRQ_INTX_HOST_INTX:
+ case ASSIGNED_IRQ_INTX_HOST_MSI:
+ intx_host_msi = dev->assigned_irq_type == ASSIGNED_IRQ_INTX_HOST_MSI;
+ r = kvm_device_intx_deassign(kvm_state, dev->dev_id, intx_host_msi);
+ break;
+ case ASSIGNED_IRQ_MSI:
+ r = kvm_device_msi_deassign(kvm_state, dev->dev_id);
+ break;
+ case ASSIGNED_IRQ_MSIX:
+ r = kvm_device_msix_deassign(kvm_state, dev->dev_id);
+ break;
+ default:
+ r = 0;
+ break;
+ }
+ if (r) {
+ perror("assign_intx: deassignment of previous interrupt failed");
+ }
+ dev->assigned_irq_type = ASSIGNED_IRQ_NONE;
+
+ if (intx_route.mode == PCI_INTX_DISABLED) {
+ dev->intx_route = intx_route;
+ return 0;
+ }
+
+retry:
+ if (dev->features & ASSIGNED_DEVICE_PREFER_MSI_MASK &&
+ dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) {
+ intx_host_msi = true;
+ new_type = ASSIGNED_IRQ_INTX_HOST_MSI;
+ } else {
+ intx_host_msi = false;
+ new_type = ASSIGNED_IRQ_INTX_HOST_INTX;
+ }
+
+ r = kvm_device_intx_assign(kvm_state, dev->dev_id, intx_host_msi,
+ intx_route.irq);
+ if (r < 0) {
+ if (r == -EIO && !(dev->features & ASSIGNED_DEVICE_PREFER_MSI_MASK) &&
+ dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) {
+ /* Retry with host-side MSI. There might be an IRQ conflict and
+ * either the kernel or the device doesn't support sharing. */
+ error_report("Host-side INTx sharing not supported, "
+ "using MSI instead");
+ error_printf("Some devices do not work properly in this mode.\n");
+ dev->features |= ASSIGNED_DEVICE_PREFER_MSI_MASK;
+ goto retry;
+ }
+ error_setg_errno(errp, -r,
+ "Failed to assign irq for \"%s\"\n"
+ "Perhaps you are assigning a device "
+ "that shares an IRQ with another device?",
+ dev->dev.qdev.id);
+ return r;
+ }
+
+ dev->intx_route = intx_route;
+ dev->assigned_irq_type = new_type;
+ return r;
+}
+
+static void deassign_device(AssignedDevice *dev)
+{
+ int r;
+
+ r = kvm_device_pci_deassign(kvm_state, dev->dev_id);
+ assert(r == 0);
+}
+
+/* The pci config space got updated. Check if irq numbers have changed
+ * for our devices
+ */
+static void assigned_dev_update_irq_routing(PCIDevice *dev)
+{
+ AssignedDevice *assigned_dev = PCI_ASSIGN(dev);
+ Error *err = NULL;
+ int r;
+
+ r = assign_intx(assigned_dev, &err);
+ if (r < 0) {
+ error_report_err(err);
+ err = NULL;
+ qdev_unplug(&dev->qdev, &err);
+ assert(!err);
+ }
+}
+
+static void assigned_dev_update_msi(PCIDevice *pci_dev)
+{
+ AssignedDevice *assigned_dev = PCI_ASSIGN(pci_dev);
+ uint8_t ctrl_byte = pci_get_byte(pci_dev->config + pci_dev->msi_cap +
+ PCI_MSI_FLAGS);
+ int r;
+
+ /* Some guests gratuitously disable MSI even if they're not using it,
+ * try to catch this by only deassigning irqs if the guest is using
+ * MSI or intends to start. */
+ if (assigned_dev->assigned_irq_type == ASSIGNED_IRQ_MSI ||
+ (ctrl_byte & PCI_MSI_FLAGS_ENABLE)) {
+ r = kvm_device_msi_deassign(kvm_state, assigned_dev->dev_id);
+ /* -ENXIO means no assigned irq */
+ if (r && r != -ENXIO) {
+ perror("assigned_dev_update_msi: deassign irq");
+ }
+
+ free_msi_virqs(assigned_dev);
+
+ assigned_dev->assigned_irq_type = ASSIGNED_IRQ_NONE;
+ pci_device_set_intx_routing_notifier(pci_dev, NULL);
+ }
+
+ if (ctrl_byte & PCI_MSI_FLAGS_ENABLE) {
+ MSIMessage msg = msi_get_message(pci_dev, 0);
+ int virq;
+
+ virq = kvm_irqchip_add_msi_route(kvm_state, msg);
+ if (virq < 0) {
+ perror("assigned_dev_update_msi: kvm_irqchip_add_msi_route");
+ return;
+ }
+
+ assigned_dev->msi_virq = g_malloc(sizeof(*assigned_dev->msi_virq));
+ assigned_dev->msi_virq_nr = 1;
+ assigned_dev->msi_virq[0] = virq;
+ if (kvm_device_msi_assign(kvm_state, assigned_dev->dev_id, virq) < 0) {
+ perror("assigned_dev_update_msi: kvm_device_msi_assign");
+ }
+
+ assigned_dev->intx_route.mode = PCI_INTX_DISABLED;
+ assigned_dev->intx_route.irq = -1;
+ assigned_dev->assigned_irq_type = ASSIGNED_IRQ_MSI;
+ } else {
+ Error *local_err = NULL;
+
+ assign_intx(assigned_dev, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ }
+ }
+}
+
+static void assigned_dev_update_msi_msg(PCIDevice *pci_dev)
+{
+ AssignedDevice *assigned_dev = PCI_ASSIGN(pci_dev);
+ uint8_t ctrl_byte = pci_get_byte(pci_dev->config + pci_dev->msi_cap +
+ PCI_MSI_FLAGS);
+
+ if (assigned_dev->assigned_irq_type != ASSIGNED_IRQ_MSI ||
+ !(ctrl_byte & PCI_MSI_FLAGS_ENABLE)) {
+ return;
+ }
+
+ kvm_irqchip_update_msi_route(kvm_state, assigned_dev->msi_virq[0],
+ msi_get_message(pci_dev, 0));
+}
+
+static bool assigned_dev_msix_masked(MSIXTableEntry *entry)
+{
+ return (entry->ctrl & cpu_to_le32(0x1)) != 0;
+}
+
+/*
+ * When MSI-X is first enabled the vector table typically has all the
+ * vectors masked, so we can't use that as the obvious test to figure out
+ * how many vectors to initially enable. Instead we look at the data field
+ * because this is what worked for pci-assign for a long time. This makes
+ * sure the physical MSI-X state tracks the guest's view, which is important
+ * for some VF/PF and PF/fw communication channels.
+ */
+static bool assigned_dev_msix_skipped(MSIXTableEntry *entry)
+{
+ return !entry->data;
+}
+
+static int assigned_dev_update_msix_mmio(PCIDevice *pci_dev)
+{
+ AssignedDevice *adev = PCI_ASSIGN(pci_dev);
+ uint16_t entries_nr = 0;
+ int i, r = 0;
+ MSIXTableEntry *entry = adev->msix_table;
+ MSIMessage msg;
+
+ /* Get the usable entry number for allocating */
+ for (i = 0; i < adev->msix_max; i++, entry++) {
+ if (assigned_dev_msix_skipped(entry)) {
+ continue;
+ }
+ entries_nr++;
+ }
+
+ DEBUG("MSI-X entries: %d\n", entries_nr);
+
+ /* It's valid to enable MSI-X with all entries masked */
+ if (!entries_nr) {
+ return 0;
+ }
+
+ r = kvm_device_msix_init_vectors(kvm_state, adev->dev_id, entries_nr);
+ if (r != 0) {
+ error_report("fail to set MSI-X entry number for MSIX! %s",
+ strerror(-r));
+ return r;
+ }
+
+ free_msi_virqs(adev);
+
+ adev->msi_virq_nr = adev->msix_max;
+ adev->msi_virq = g_malloc(adev->msix_max * sizeof(*adev->msi_virq));
+
+ entry = adev->msix_table;
+ for (i = 0; i < adev->msix_max; i++, entry++) {
+ adev->msi_virq[i] = -1;
+
+ if (assigned_dev_msix_skipped(entry)) {
+ continue;
+ }
+
+ msg.address = entry->addr_lo | ((uint64_t)entry->addr_hi << 32);
+ msg.data = entry->data;
+ r = kvm_irqchip_add_msi_route(kvm_state, msg);
+ if (r < 0) {
+ return r;
+ }
+ adev->msi_virq[i] = r;
+
+ DEBUG("MSI-X vector %d, gsi %d, addr %08x_%08x, data %08x\n", i,
+ r, entry->addr_hi, entry->addr_lo, entry->data);
+
+ r = kvm_device_msix_set_vector(kvm_state, adev->dev_id, i,
+ adev->msi_virq[i]);
+ if (r) {
+ error_report("fail to set MSI-X entry! %s", strerror(-r));
+ break;
+ }
+ }
+
+ return r;
+}
+
+static void assigned_dev_update_msix(PCIDevice *pci_dev)
+{
+ AssignedDevice *assigned_dev = PCI_ASSIGN(pci_dev);
+ uint16_t ctrl_word = pci_get_word(pci_dev->config + pci_dev->msix_cap +
+ PCI_MSIX_FLAGS);
+ int r;
+
+ /* Some guests gratuitously disable MSIX even if they're not using it,
+ * try to catch this by only deassigning irqs if the guest is using
+ * MSIX or intends to start. */
+ if ((assigned_dev->assigned_irq_type == ASSIGNED_IRQ_MSIX) ||
+ (ctrl_word & PCI_MSIX_FLAGS_ENABLE)) {
+ r = kvm_device_msix_deassign(kvm_state, assigned_dev->dev_id);
+ /* -ENXIO means no assigned irq */
+ if (r && r != -ENXIO) {
+ perror("assigned_dev_update_msix: deassign irq");
+ }
+
+ free_msi_virqs(assigned_dev);
+
+ assigned_dev->assigned_irq_type = ASSIGNED_IRQ_NONE;
+ pci_device_set_intx_routing_notifier(pci_dev, NULL);
+ }
+
+ if (ctrl_word & PCI_MSIX_FLAGS_ENABLE) {
+ if (assigned_dev_update_msix_mmio(pci_dev) < 0) {
+ perror("assigned_dev_update_msix_mmio");
+ return;
+ }
+
+ if (assigned_dev->msi_virq_nr > 0) {
+ if (kvm_device_msix_assign(kvm_state, assigned_dev->dev_id) < 0) {
+ perror("assigned_dev_enable_msix: assign irq");
+ return;
+ }
+ }
+ assigned_dev->intx_route.mode = PCI_INTX_DISABLED;
+ assigned_dev->intx_route.irq = -1;
+ assigned_dev->assigned_irq_type = ASSIGNED_IRQ_MSIX;
+ } else {
+ Error *local_err = NULL;
+
+ assign_intx(assigned_dev, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ }
+ }
+}
+
+static uint32_t assigned_dev_pci_read_config(PCIDevice *pci_dev,
+ uint32_t address, int len)
+{
+ AssignedDevice *assigned_dev = PCI_ASSIGN(pci_dev);
+ uint32_t virt_val = pci_default_read_config(pci_dev, address, len);
+ uint32_t real_val, emulate_mask, full_emulation_mask;
+
+ emulate_mask = 0;
+ memcpy(&emulate_mask, assigned_dev->emulate_config_read + address, len);
+ emulate_mask = le32_to_cpu(emulate_mask);
+
+ full_emulation_mask = 0xffffffff >> (32 - len * 8);
+
+ if (emulate_mask != full_emulation_mask) {
+ real_val = assigned_dev_pci_read(pci_dev, address, len);
+ return (virt_val & emulate_mask) | (real_val & ~emulate_mask);
+ } else {
+ return virt_val;
+ }
+}
+
+static void assigned_dev_pci_write_config(PCIDevice *pci_dev, uint32_t address,
+ uint32_t val, int len)
+{
+ AssignedDevice *assigned_dev = PCI_ASSIGN(pci_dev);
+ uint16_t old_cmd = pci_get_word(pci_dev->config + PCI_COMMAND);
+ uint32_t emulate_mask, full_emulation_mask;
+ int ret;
+
+ pci_default_write_config(pci_dev, address, val, len);
+
+ if (kvm_has_intx_set_mask() &&
+ range_covers_byte(address, len, PCI_COMMAND + 1)) {
+ bool intx_masked = (pci_get_word(pci_dev->config + PCI_COMMAND) &
+ PCI_COMMAND_INTX_DISABLE);
+
+ if (intx_masked != !!(old_cmd & PCI_COMMAND_INTX_DISABLE)) {
+ ret = kvm_device_intx_set_mask(kvm_state, assigned_dev->dev_id,
+ intx_masked);
+ if (ret) {
+ perror("assigned_dev_pci_write_config: set intx mask");
+ }
+ }
+ }
+ if (assigned_dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) {
+ if (range_covers_byte(address, len,
+ pci_dev->msi_cap + PCI_MSI_FLAGS)) {
+ assigned_dev_update_msi(pci_dev);
+ } else if (ranges_overlap(address, len, /* 32bit MSI only */
+ pci_dev->msi_cap + PCI_MSI_ADDRESS_LO, 6)) {
+ assigned_dev_update_msi_msg(pci_dev);
+ }
+ }
+ if (assigned_dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) {
+ if (range_covers_byte(address, len,
+ pci_dev->msix_cap + PCI_MSIX_FLAGS + 1)) {
+ assigned_dev_update_msix(pci_dev);
+ }
+ }
+
+ emulate_mask = 0;
+ memcpy(&emulate_mask, assigned_dev->emulate_config_write + address, len);
+ emulate_mask = le32_to_cpu(emulate_mask);
+
+ full_emulation_mask = 0xffffffff >> (32 - len * 8);
+
+ if (emulate_mask != full_emulation_mask) {
+ if (emulate_mask) {
+ val &= ~emulate_mask;
+ val |= assigned_dev_pci_read(pci_dev, address, len) & emulate_mask;
+ }
+ assigned_dev_pci_write(pci_dev, address, val, len);
+ }
+}
+
+static void assigned_dev_setup_cap_read(AssignedDevice *dev, uint32_t offset,
+ uint32_t len)
+{
+ assigned_dev_direct_config_read(dev, offset, len);
+ assigned_dev_emulate_config_read(dev, offset + PCI_CAP_LIST_NEXT, 1);
+}
+
+static int assigned_device_pci_cap_init(PCIDevice *pci_dev, Error **errp)
+{
+ AssignedDevice *dev = PCI_ASSIGN(pci_dev);
+ PCIRegion *pci_region = dev->real_device.regions;
+ int ret, pos;
+ Error *local_err = NULL;
+
+ /* Clear initial capabilities pointer and status copied from hw */
+ pci_set_byte(pci_dev->config + PCI_CAPABILITY_LIST, 0);
+ pci_set_word(pci_dev->config + PCI_STATUS,
+ pci_get_word(pci_dev->config + PCI_STATUS) &
+ ~PCI_STATUS_CAP_LIST);
+
+ /* Expose MSI capability
+ * MSI capability is the 1st capability in capability config */
+ pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_MSI, 0);
+ if (pos != 0 && kvm_check_extension(kvm_state, KVM_CAP_ASSIGN_DEV_IRQ)) {
+ verify_irqchip_in_kernel(&local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return -ENOTSUP;
+ }
+ dev->cap.available |= ASSIGNED_DEVICE_CAP_MSI;
+ /* Only 32-bit/no-mask currently supported */
+ ret = pci_add_capability2(pci_dev, PCI_CAP_ID_MSI, pos, 10,
+ &local_err);
+ if (ret < 0) {
+ error_propagate(errp, local_err);
+ return ret;
+ }
+ pci_dev->msi_cap = pos;
+
+ pci_set_word(pci_dev->config + pos + PCI_MSI_FLAGS,
+ pci_get_word(pci_dev->config + pos + PCI_MSI_FLAGS) &
+ PCI_MSI_FLAGS_QMASK);
+ pci_set_long(pci_dev->config + pos + PCI_MSI_ADDRESS_LO, 0);
+ pci_set_word(pci_dev->config + pos + PCI_MSI_DATA_32, 0);
+
+ /* Set writable fields */
+ pci_set_word(pci_dev->wmask + pos + PCI_MSI_FLAGS,
+ PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
+ pci_set_long(pci_dev->wmask + pos + PCI_MSI_ADDRESS_LO, 0xfffffffc);
+ pci_set_word(pci_dev->wmask + pos + PCI_MSI_DATA_32, 0xffff);
+ }
+ /* Expose MSI-X capability */
+ pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_MSIX, 0);
+ if (pos != 0 && kvm_device_msix_supported(kvm_state)) {
+ int bar_nr;
+ uint32_t msix_table_entry;
+ uint16_t msix_max;
+
+ verify_irqchip_in_kernel(&local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return -ENOTSUP;
+ }
+ dev->cap.available |= ASSIGNED_DEVICE_CAP_MSIX;
+ ret = pci_add_capability2(pci_dev, PCI_CAP_ID_MSIX, pos, 12,
+ &local_err);
+ if (ret < 0) {
+ error_propagate(errp, local_err);
+ return ret;
+ }
+ pci_dev->msix_cap = pos;
+
+ msix_max = (pci_get_word(pci_dev->config + pos + PCI_MSIX_FLAGS) &
+ PCI_MSIX_FLAGS_QSIZE) + 1;
+ msix_max = MIN(msix_max, KVM_MAX_MSIX_PER_DEV);
+ pci_set_word(pci_dev->config + pos + PCI_MSIX_FLAGS, msix_max - 1);
+
+ /* Only enable and function mask bits are writable */
+ pci_set_word(pci_dev->wmask + pos + PCI_MSIX_FLAGS,
+ PCI_MSIX_FLAGS_ENABLE | PCI_MSIX_FLAGS_MASKALL);
+
+ msix_table_entry = pci_get_long(pci_dev->config + pos + PCI_MSIX_TABLE);
+ bar_nr = msix_table_entry & PCI_MSIX_FLAGS_BIRMASK;
+ msix_table_entry &= ~PCI_MSIX_FLAGS_BIRMASK;
+ dev->msix_table_addr = pci_region[bar_nr].base_addr + msix_table_entry;
+ dev->msix_max = msix_max;
+ }
+
+ /* Minimal PM support, nothing writable, device appears to NAK changes */
+ pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_PM, 0);
+ if (pos) {
+ uint16_t pmc;
+
+ ret = pci_add_capability2(pci_dev, PCI_CAP_ID_PM, pos, PCI_PM_SIZEOF,
+ &local_err);
+ if (ret < 0) {
+ error_propagate(errp, local_err);
+ return ret;
+ }
+
+ assigned_dev_setup_cap_read(dev, pos, PCI_PM_SIZEOF);
+
+ pmc = pci_get_word(pci_dev->config + pos + PCI_CAP_FLAGS);
+ pmc &= (PCI_PM_CAP_VER_MASK | PCI_PM_CAP_DSI);
+ pci_set_word(pci_dev->config + pos + PCI_CAP_FLAGS, pmc);
+
+ /* assign_device will bring the device up to D0, so we don't need
+ * to worry about doing that ourselves here. */
+ pci_set_word(pci_dev->config + pos + PCI_PM_CTRL,
+ PCI_PM_CTRL_NO_SOFT_RESET);
+
+ pci_set_byte(pci_dev->config + pos + PCI_PM_PPB_EXTENSIONS, 0);
+ pci_set_byte(pci_dev->config + pos + PCI_PM_DATA_REGISTER, 0);
+ }
+
+ pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_EXP, 0);
+ if (pos) {
+ uint8_t version, size = 0;
+ uint16_t type, devctl, lnksta;
+ uint32_t devcap, lnkcap;
+
+ version = pci_get_byte(pci_dev->config + pos + PCI_EXP_FLAGS);
+ version &= PCI_EXP_FLAGS_VERS;
+ if (version == 1) {
+ size = 0x14;
+ } else if (version == 2) {
+ /*
+ * Check for non-std size, accept reduced size to 0x34,
+ * which is what bcm5761 implemented, violating the
+ * PCIe v3.0 spec that regs should exist and be read as 0,
+ * not optionally provided and shorten the struct size.
+ */
+ size = MIN(0x3c, PCI_CONFIG_SPACE_SIZE - pos);
+ if (size < 0x34) {
+ error_setg(errp, "Invalid size PCIe cap-id 0x%x",
+ PCI_CAP_ID_EXP);
+ return -EINVAL;
+ } else if (size != 0x3c) {
+ error_report("WARNING, %s: PCIe cap-id 0x%x has "
+ "non-standard size 0x%x; std size should be 0x3c",
+ __func__, PCI_CAP_ID_EXP, size);
+ }
+ } else if (version == 0) {
+ uint16_t vid, did;
+ vid = pci_get_word(pci_dev->config + PCI_VENDOR_ID);
+ did = pci_get_word(pci_dev->config + PCI_DEVICE_ID);
+ if (vid == PCI_VENDOR_ID_INTEL && did == 0x10ed) {
+ /*
+ * quirk for Intel 82599 VF with invalid PCIe capability
+ * version, should really be version 2 (same as PF)
+ */
+ size = 0x3c;
+ }
+ }
+
+ if (size == 0) {
+ error_setg(errp, "Unsupported PCI express capability version %d",
+ version);
+ return -EINVAL;
+ }
+
+ ret = pci_add_capability2(pci_dev, PCI_CAP_ID_EXP, pos, size,
+ &local_err);
+ if (ret < 0) {
+ error_propagate(errp, local_err);
+ return ret;
+ }
+
+ assigned_dev_setup_cap_read(dev, pos, size);
+
+ type = pci_get_word(pci_dev->config + pos + PCI_EXP_FLAGS);
+ type = (type & PCI_EXP_FLAGS_TYPE) >> 4;
+ if (type != PCI_EXP_TYPE_ENDPOINT &&
+ type != PCI_EXP_TYPE_LEG_END && type != PCI_EXP_TYPE_RC_END) {
+ error_setg(errp, "Device assignment only supports endpoint "
+ "assignment, device type %d", type);
+ return -EINVAL;
+ }
+
+ /* capabilities, pass existing read-only copy
+ * PCI_EXP_FLAGS_IRQ: updated by hardware, should be direct read */
+
+ /* device capabilities: hide FLR */
+ devcap = pci_get_long(pci_dev->config + pos + PCI_EXP_DEVCAP);
+ devcap &= ~PCI_EXP_DEVCAP_FLR;
+ pci_set_long(pci_dev->config + pos + PCI_EXP_DEVCAP, devcap);
+
+ /* device control: clear all error reporting enable bits, leaving
+ * only a few host values. Note, these are
+ * all writable, but not passed to hw.
+ */
+ devctl = pci_get_word(pci_dev->config + pos + PCI_EXP_DEVCTL);
+ devctl = (devctl & (PCI_EXP_DEVCTL_READRQ | PCI_EXP_DEVCTL_PAYLOAD)) |
+ PCI_EXP_DEVCTL_RELAX_EN | PCI_EXP_DEVCTL_NOSNOOP_EN;
+ pci_set_word(pci_dev->config + pos + PCI_EXP_DEVCTL, devctl);
+ devctl = PCI_EXP_DEVCTL_BCR_FLR | PCI_EXP_DEVCTL_AUX_PME;
+ pci_set_word(pci_dev->wmask + pos + PCI_EXP_DEVCTL, ~devctl);
+
+ /* Clear device status */
+ pci_set_word(pci_dev->config + pos + PCI_EXP_DEVSTA, 0);
+
+ /* Link capabilities, expose links and latencues, clear reporting */
+ lnkcap = pci_get_long(pci_dev->config + pos + PCI_EXP_LNKCAP);
+ lnkcap &= (PCI_EXP_LNKCAP_SLS | PCI_EXP_LNKCAP_MLW |
+ PCI_EXP_LNKCAP_ASPMS | PCI_EXP_LNKCAP_L0SEL |
+ PCI_EXP_LNKCAP_L1EL);
+ pci_set_long(pci_dev->config + pos + PCI_EXP_LNKCAP, lnkcap);
+
+ /* Link control, pass existing read-only copy. Should be writable? */
+
+ /* Link status, only expose current speed and width */
+ lnksta = pci_get_word(pci_dev->config + pos + PCI_EXP_LNKSTA);
+ lnksta &= (PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW);
+ pci_set_word(pci_dev->config + pos + PCI_EXP_LNKSTA, lnksta);
+
+ if (version >= 2) {
+ /* Slot capabilities, control, status - not needed for endpoints */
+ pci_set_long(pci_dev->config + pos + PCI_EXP_SLTCAP, 0);
+ pci_set_word(pci_dev->config + pos + PCI_EXP_SLTCTL, 0);
+ pci_set_word(pci_dev->config + pos + PCI_EXP_SLTSTA, 0);
+
+ /* Root control, capabilities, status - not needed for endpoints */
+ pci_set_word(pci_dev->config + pos + PCI_EXP_RTCTL, 0);
+ pci_set_word(pci_dev->config + pos + PCI_EXP_RTCAP, 0);
+ pci_set_long(pci_dev->config + pos + PCI_EXP_RTSTA, 0);
+
+ /* Device capabilities/control 2, pass existing read-only copy */
+ /* Link control 2, pass existing read-only copy */
+ }
+ }
+
+ pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_PCIX, 0);
+ if (pos) {
+ uint16_t cmd;
+ uint32_t status;
+
+ /* Only expose the minimum, 8 byte capability */
+ ret = pci_add_capability2(pci_dev, PCI_CAP_ID_PCIX, pos, 8,
+ &local_err);
+ if (ret < 0) {
+ error_propagate(errp, local_err);
+ return ret;
+ }
+
+ assigned_dev_setup_cap_read(dev, pos, 8);
+
+ /* Command register, clear upper bits, including extended modes */
+ cmd = pci_get_word(pci_dev->config + pos + PCI_X_CMD);
+ cmd &= (PCI_X_CMD_DPERR_E | PCI_X_CMD_ERO | PCI_X_CMD_MAX_READ |
+ PCI_X_CMD_MAX_SPLIT);
+ pci_set_word(pci_dev->config + pos + PCI_X_CMD, cmd);
+
+ /* Status register, update with emulated PCI bus location, clear
+ * error bits, leave the rest. */
+ status = pci_get_long(pci_dev->config + pos + PCI_X_STATUS);
+ status &= ~(PCI_X_STATUS_BUS | PCI_X_STATUS_DEVFN);
+ status |= (pci_bus_num(pci_dev->bus) << 8) | pci_dev->devfn;
+ status &= ~(PCI_X_STATUS_SPL_DISC | PCI_X_STATUS_UNX_SPL |
+ PCI_X_STATUS_SPL_ERR);
+ pci_set_long(pci_dev->config + pos + PCI_X_STATUS, status);
+ }
+
+ pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_VPD, 0);
+ if (pos) {
+ /* Direct R/W passthrough */
+ ret = pci_add_capability2(pci_dev, PCI_CAP_ID_VPD, pos, 8,
+ &local_err);
+ if (ret < 0) {
+ error_propagate(errp, local_err);
+ return ret;
+ }
+
+ assigned_dev_setup_cap_read(dev, pos, 8);
+
+ /* direct write for cap content */
+ assigned_dev_direct_config_write(dev, pos + 2, 6);
+ }
+
+ /* Devices can have multiple vendor capabilities, get them all */
+ for (pos = 0; (pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_VNDR, pos));
+ pos += PCI_CAP_LIST_NEXT) {
+ uint8_t len = pci_get_byte(pci_dev->config + pos + PCI_CAP_FLAGS);
+ /* Direct R/W passthrough */
+ ret = pci_add_capability2(pci_dev, PCI_CAP_ID_VNDR, pos, len,
+ &local_err);
+ if (ret < 0) {
+ error_propagate(errp, local_err);
+ return ret;
+ }
+
+ assigned_dev_setup_cap_read(dev, pos, len);
+
+ /* direct write for cap content */
+ assigned_dev_direct_config_write(dev, pos + 2, len - 2);
+ }
+
+ /* If real and virtual capability list status bits differ, virtualize the
+ * access. */
+ if ((pci_get_word(pci_dev->config + PCI_STATUS) & PCI_STATUS_CAP_LIST) !=
+ (assigned_dev_pci_read_byte(pci_dev, PCI_STATUS) &
+ PCI_STATUS_CAP_LIST)) {
+ dev->emulate_config_read[PCI_STATUS] |= PCI_STATUS_CAP_LIST;
+ }
+
+ return 0;
+}
+
+static uint64_t
+assigned_dev_msix_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ AssignedDevice *adev = opaque;
+ uint64_t val;
+
+ memcpy(&val, (void *)((uint8_t *)adev->msix_table + addr), size);
+
+ return val;
+}
+
+static void assigned_dev_msix_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ AssignedDevice *adev = opaque;
+ PCIDevice *pdev = &adev->dev;
+ uint16_t ctrl;
+ MSIXTableEntry orig;
+ int i = addr >> 4;
+
+ if (i >= adev->msix_max) {
+ return; /* Drop write */
+ }
+
+ ctrl = pci_get_word(pdev->config + pdev->msix_cap + PCI_MSIX_FLAGS);
+
+ DEBUG("write to MSI-X table offset 0x%lx, val 0x%lx\n", addr, val);
+
+ if (ctrl & PCI_MSIX_FLAGS_ENABLE) {
+ orig = adev->msix_table[i];
+ }
+
+ memcpy((uint8_t *)adev->msix_table + addr, &val, size);
+
+ if (ctrl & PCI_MSIX_FLAGS_ENABLE) {
+ MSIXTableEntry *entry = &adev->msix_table[i];
+
+ if (!assigned_dev_msix_masked(&orig) &&
+ assigned_dev_msix_masked(entry)) {
+ /*
+ * Vector masked, disable it
+ *
+ * XXX It's not clear if we can or should actually attempt
+ * to mask or disable the interrupt. KVM doesn't have
+ * support for pending bits and kvm_assign_set_msix_entry
+ * doesn't modify the device hardware mask. Interrupts
+ * while masked are simply not injected to the guest, so
+ * are lost. Can we get away with always injecting an
+ * interrupt on unmask?
+ */
+ } else if (assigned_dev_msix_masked(&orig) &&
+ !assigned_dev_msix_masked(entry)) {
+ /* Vector unmasked */
+ if (i >= adev->msi_virq_nr || adev->msi_virq[i] < 0) {
+ /* Previously unassigned vector, start from scratch */
+ assigned_dev_update_msix(pdev);
+ return;
+ } else {
+ /* Update an existing, previously masked vector */
+ MSIMessage msg;
+ int ret;
+
+ msg.address = entry->addr_lo |
+ ((uint64_t)entry->addr_hi << 32);
+ msg.data = entry->data;
+
+ ret = kvm_irqchip_update_msi_route(kvm_state,
+ adev->msi_virq[i], msg);
+ if (ret) {
+ error_report("Error updating irq routing entry (%d)", ret);
+ }
+ }
+ }
+ }
+}
+
+static const MemoryRegionOps assigned_dev_msix_mmio_ops = {
+ .read = assigned_dev_msix_mmio_read,
+ .write = assigned_dev_msix_mmio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static void assigned_dev_msix_reset(AssignedDevice *dev)
+{
+ MSIXTableEntry *entry;
+ int i;
+
+ if (!dev->msix_table) {
+ return;
+ }
+
+ memset(dev->msix_table, 0, MSIX_PAGE_SIZE);
+
+ for (i = 0, entry = dev->msix_table; i < dev->msix_max; i++, entry++) {
+ entry->ctrl = cpu_to_le32(0x1); /* Masked */
+ }
+}
+
+static void assigned_dev_register_msix_mmio(AssignedDevice *dev, Error **errp)
+{
+ dev->msix_table = mmap(NULL, MSIX_PAGE_SIZE, PROT_READ|PROT_WRITE,
+ MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
+ if (dev->msix_table == MAP_FAILED) {
+ error_setg_errno(errp, errno, "failed to allocate msix_table");
+ dev->msix_table = NULL;
+ return;
+ }
+
+ assigned_dev_msix_reset(dev);
+
+ memory_region_init_io(&dev->mmio, OBJECT(dev), &assigned_dev_msix_mmio_ops,
+ dev, "assigned-dev-msix", MSIX_PAGE_SIZE);
+}
+
+static void assigned_dev_unregister_msix_mmio(AssignedDevice *dev)
+{
+ if (!dev->msix_table) {
+ return;
+ }
+
+ if (munmap(dev->msix_table, MSIX_PAGE_SIZE) == -1) {
+ error_report("error unmapping msix_table! %s", strerror(errno));
+ }
+ dev->msix_table = NULL;
+}
+
+static const VMStateDescription vmstate_assigned_device = {
+ .name = "pci-assign",
+ .unmigratable = 1,
+};
+
+static void reset_assigned_device(DeviceState *dev)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(dev);
+ AssignedDevice *adev = PCI_ASSIGN(pci_dev);
+ char reset_file[64];
+ const char reset[] = "1";
+ int fd, ret;
+
+ /*
+ * If a guest is reset without being shutdown, MSI/MSI-X can still
+ * be running. We want to return the device to a known state on
+ * reset, so disable those here. We especially do not want MSI-X
+ * enabled since it lives in MMIO space, which is about to get
+ * disabled.
+ */
+ if (adev->assigned_irq_type == ASSIGNED_IRQ_MSIX) {
+ uint16_t ctrl = pci_get_word(pci_dev->config +
+ pci_dev->msix_cap + PCI_MSIX_FLAGS);
+
+ pci_set_word(pci_dev->config + pci_dev->msix_cap + PCI_MSIX_FLAGS,
+ ctrl & ~PCI_MSIX_FLAGS_ENABLE);
+ assigned_dev_update_msix(pci_dev);
+ } else if (adev->assigned_irq_type == ASSIGNED_IRQ_MSI) {
+ uint8_t ctrl = pci_get_byte(pci_dev->config +
+ pci_dev->msi_cap + PCI_MSI_FLAGS);
+
+ pci_set_byte(pci_dev->config + pci_dev->msi_cap + PCI_MSI_FLAGS,
+ ctrl & ~PCI_MSI_FLAGS_ENABLE);
+ assigned_dev_update_msi(pci_dev);
+ }
+
+ snprintf(reset_file, sizeof(reset_file),
+ "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/reset",
+ adev->host.domain, adev->host.bus, adev->host.slot,
+ adev->host.function);
+
+ /*
+ * Issue a device reset via pci-sysfs. Note that we use write(2) here
+ * and ignore the return value because some kernels have a bug that
+ * returns 0 rather than bytes written on success, sending us into an
+ * infinite retry loop using other write mechanisms.
+ */
+ fd = open(reset_file, O_WRONLY);
+ if (fd != -1) {
+ ret = write(fd, reset, strlen(reset));
+ (void)ret;
+ close(fd);
+ }
+
+ /*
+ * When a 0 is written to the bus master register, the device is logically
+ * disconnected from the PCI bus. This avoids further DMA transfers.
+ */
+ assigned_dev_pci_write_config(pci_dev, PCI_COMMAND, 0, 1);
+}
+
+static void assigned_realize(struct PCIDevice *pci_dev, Error **errp)
+{
+ AssignedDevice *dev = PCI_ASSIGN(pci_dev);
+ uint8_t e_intx;
+ int r;
+ Error *local_err = NULL;
+
+ if (!kvm_enabled()) {
+ error_setg(&local_err, "pci-assign requires KVM support");
+ goto exit_with_error;
+ }
+
+ if (!dev->host.domain && !dev->host.bus && !dev->host.slot &&
+ !dev->host.function) {
+ error_setg(&local_err, "no host device specified");
+ goto exit_with_error;
+ }
+
+ /*
+ * Set up basic config space access control. Will be further refined during
+ * device initialization.
+ */
+ assigned_dev_emulate_config_read(dev, 0, PCI_CONFIG_SPACE_SIZE);
+ assigned_dev_direct_config_read(dev, PCI_STATUS, 2);
+ assigned_dev_direct_config_read(dev, PCI_REVISION_ID, 1);
+ assigned_dev_direct_config_read(dev, PCI_CLASS_PROG, 3);
+ assigned_dev_direct_config_read(dev, PCI_CACHE_LINE_SIZE, 1);
+ assigned_dev_direct_config_read(dev, PCI_LATENCY_TIMER, 1);
+ assigned_dev_direct_config_read(dev, PCI_BIST, 1);
+ assigned_dev_direct_config_read(dev, PCI_CARDBUS_CIS, 4);
+ assigned_dev_direct_config_read(dev, PCI_SUBSYSTEM_VENDOR_ID, 2);
+ assigned_dev_direct_config_read(dev, PCI_SUBSYSTEM_ID, 2);
+ assigned_dev_direct_config_read(dev, PCI_CAPABILITY_LIST + 1, 7);
+ assigned_dev_direct_config_read(dev, PCI_MIN_GNT, 1);
+ assigned_dev_direct_config_read(dev, PCI_MAX_LAT, 1);
+ memcpy(dev->emulate_config_write, dev->emulate_config_read,
+ sizeof(dev->emulate_config_read));
+
+ get_real_device(dev, &local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ if (assigned_device_pci_cap_init(pci_dev, &local_err) < 0) {
+ goto out;
+ }
+
+ /* intercept MSI-X entry page in the MMIO */
+ if (dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) {
+ assigned_dev_register_msix_mmio(dev, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ }
+
+ /* handle real device's MMIO/PIO BARs */
+ assigned_dev_register_regions(dev->real_device.regions,
+ dev->real_device.region_number, dev,
+ &local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ /* handle interrupt routing */
+ e_intx = dev->dev.config[PCI_INTERRUPT_PIN] - 1;
+ dev->intpin = e_intx;
+ dev->intx_route.mode = PCI_INTX_DISABLED;
+ dev->intx_route.irq = -1;
+
+ /* assign device to guest */
+ assign_device(dev, &local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ /* assign legacy INTx to the device */
+ r = assign_intx(dev, &local_err);
+ if (r < 0) {
+ goto assigned_out;
+ }
+
+ assigned_dev_load_option_rom(dev);
+
+ return;
+
+assigned_out:
+ deassign_device(dev);
+
+out:
+ free_assigned_device(dev);
+
+exit_with_error:
+ assert(local_err);
+ error_propagate(errp, local_err);
+}
+
+static void assigned_exitfn(struct PCIDevice *pci_dev)
+{
+ AssignedDevice *dev = PCI_ASSIGN(pci_dev);
+
+ deassign_device(dev);
+ free_assigned_device(dev);
+}
+
+static void assigned_dev_instance_init(Object *obj)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(obj);
+ AssignedDevice *d = PCI_ASSIGN(pci_dev);
+
+ device_add_bootindex_property(obj, &d->bootindex,
+ "bootindex", NULL,
+ &pci_dev->qdev, NULL);
+}
+
+static Property assigned_dev_properties[] = {
+ DEFINE_PROP_PCI_HOST_DEVADDR("host", AssignedDevice, host),
+ DEFINE_PROP_BIT("prefer_msi", AssignedDevice, features,
+ ASSIGNED_DEVICE_PREFER_MSI_BIT, false),
+ DEFINE_PROP_BIT("share_intx", AssignedDevice, features,
+ ASSIGNED_DEVICE_SHARE_INTX_BIT, true),
+ DEFINE_PROP_STRING("configfd", AssignedDevice, configfd_name),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void assign_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = assigned_realize;
+ k->exit = assigned_exitfn;
+ k->config_read = assigned_dev_pci_read_config;
+ k->config_write = assigned_dev_pci_write_config;
+ dc->props = assigned_dev_properties;
+ dc->vmsd = &vmstate_assigned_device;
+ dc->reset = reset_assigned_device;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->desc = "KVM-based PCI passthrough";
+}
+
+static const TypeInfo assign_info = {
+ .name = TYPE_PCI_ASSIGN,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(AssignedDevice),
+ .class_init = assign_class_init,
+ .instance_init = assigned_dev_instance_init,
+};
+
+static void assign_register_types(void)
+{
+ type_register_static(&assign_info);
+}
+
+type_init(assign_register_types)
+
+/*
+ * Scan the assigned devices for the devices that have an option ROM, and then
+ * load the corresponding ROM data to RAM. If an error occurs while loading an
+ * option ROM, we just ignore that option ROM and continue with the next one.
+ */
+static void assigned_dev_load_option_rom(AssignedDevice *dev)
+{
+ char name[32], rom_file[64];
+ FILE *fp;
+ uint8_t val;
+ struct stat st;
+ void *ptr;
+
+ /* If loading ROM from file, pci handles it */
+ if (dev->dev.romfile || !dev->dev.rom_bar) {
+ return;
+ }
+
+ snprintf(rom_file, sizeof(rom_file),
+ "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/rom",
+ dev->host.domain, dev->host.bus, dev->host.slot,
+ dev->host.function);
+
+ if (stat(rom_file, &st)) {
+ return;
+ }
+
+ if (access(rom_file, F_OK)) {
+ error_report("pci-assign: Insufficient privileges for %s", rom_file);
+ return;
+ }
+
+ /* Write "1" to the ROM file to enable it */
+ fp = fopen(rom_file, "r+");
+ if (fp == NULL) {
+ return;
+ }
+ val = 1;
+ if (fwrite(&val, 1, 1, fp) != 1) {
+ goto close_rom;
+ }
+ fseek(fp, 0, SEEK_SET);
+
+ snprintf(name, sizeof(name), "%s.rom",
+ object_get_typename(OBJECT(dev)));
+ memory_region_init_ram(&dev->dev.rom, OBJECT(dev), name, st.st_size,
+ &error_abort);
+ vmstate_register_ram(&dev->dev.rom, &dev->dev.qdev);
+ ptr = memory_region_get_ram_ptr(&dev->dev.rom);
+ memset(ptr, 0xff, st.st_size);
+
+ if (!fread(ptr, 1, st.st_size, fp)) {
+ error_report("pci-assign: Cannot read from host %s", rom_file);
+ error_printf("Device option ROM contents are probably invalid "
+ "(check dmesg).\nSkip option ROM probe with rombar=0, "
+ "or load from file with romfile=\n");
+ goto close_rom;
+ }
+
+ pci_register_bar(&dev->dev, PCI_ROM_SLOT, 0, &dev->dev.rom);
+ dev->dev.has_rom = true;
+close_rom:
+ /* Write "0" to disable ROM */
+ fseek(fp, 0, SEEK_SET);
+ val = 0;
+ if (!fwrite(&val, 1, 1, fp)) {
+ DEBUG("%s\n", "Failed to disable pci-sysfs rom file");
+ }
+ fclose(fp);
+}
diff --git a/hw/i386/kvmvapic.c b/hw/i386/kvmvapic.c
new file mode 100644
index 00000000..f0922da6
--- /dev/null
+++ b/hw/i386/kvmvapic.c
@@ -0,0 +1,865 @@
+/*
+ * TPR optimization for 32-bit Windows guests (XP and Server 2003)
+ *
+ * Copyright (C) 2007-2008 Qumranet Technologies
+ * Copyright (C) 2012 Jan Kiszka, Siemens AG
+ *
+ * This work is licensed under the terms of the GNU GPL version 2, or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+#include "sysemu/sysemu.h"
+#include "sysemu/cpus.h"
+#include "sysemu/kvm.h"
+#include "hw/i386/apic_internal.h"
+#include "hw/sysbus.h"
+
+#define VAPIC_IO_PORT 0x7e
+
+#define VAPIC_CPU_SHIFT 7
+
+#define ROM_BLOCK_SIZE 512
+#define ROM_BLOCK_MASK (~(ROM_BLOCK_SIZE - 1))
+
+typedef enum VAPICMode {
+ VAPIC_INACTIVE = 0,
+ VAPIC_ACTIVE = 1,
+ VAPIC_STANDBY = 2,
+} VAPICMode;
+
+typedef struct VAPICHandlers {
+ uint32_t set_tpr;
+ uint32_t set_tpr_eax;
+ uint32_t get_tpr[8];
+ uint32_t get_tpr_stack;
+} QEMU_PACKED VAPICHandlers;
+
+typedef struct GuestROMState {
+ char signature[8];
+ uint32_t vaddr;
+ uint32_t fixup_start;
+ uint32_t fixup_end;
+ uint32_t vapic_vaddr;
+ uint32_t vapic_size;
+ uint32_t vcpu_shift;
+ uint32_t real_tpr_addr;
+ VAPICHandlers up;
+ VAPICHandlers mp;
+} QEMU_PACKED GuestROMState;
+
+typedef struct VAPICROMState {
+ SysBusDevice busdev;
+ MemoryRegion io;
+ MemoryRegion rom;
+ uint32_t state;
+ uint32_t rom_state_paddr;
+ uint32_t rom_state_vaddr;
+ uint32_t vapic_paddr;
+ uint32_t real_tpr_addr;
+ GuestROMState rom_state;
+ size_t rom_size;
+ bool rom_mapped_writable;
+ VMChangeStateEntry *vmsentry;
+} VAPICROMState;
+
+#define TYPE_VAPIC "kvmvapic"
+#define VAPIC(obj) OBJECT_CHECK(VAPICROMState, (obj), TYPE_VAPIC)
+
+#define TPR_INSTR_ABS_MODRM 0x1
+#define TPR_INSTR_MATCH_MODRM_REG 0x2
+
+typedef struct TPRInstruction {
+ uint8_t opcode;
+ uint8_t modrm_reg;
+ unsigned int flags;
+ TPRAccess access;
+ size_t length;
+ off_t addr_offset;
+} TPRInstruction;
+
+/* must be sorted by length, shortest first */
+static const TPRInstruction tpr_instr[] = {
+ { /* mov abs to eax */
+ .opcode = 0xa1,
+ .access = TPR_ACCESS_READ,
+ .length = 5,
+ .addr_offset = 1,
+ },
+ { /* mov eax to abs */
+ .opcode = 0xa3,
+ .access = TPR_ACCESS_WRITE,
+ .length = 5,
+ .addr_offset = 1,
+ },
+ { /* mov r32 to r/m32 */
+ .opcode = 0x89,
+ .flags = TPR_INSTR_ABS_MODRM,
+ .access = TPR_ACCESS_WRITE,
+ .length = 6,
+ .addr_offset = 2,
+ },
+ { /* mov r/m32 to r32 */
+ .opcode = 0x8b,
+ .flags = TPR_INSTR_ABS_MODRM,
+ .access = TPR_ACCESS_READ,
+ .length = 6,
+ .addr_offset = 2,
+ },
+ { /* push r/m32 */
+ .opcode = 0xff,
+ .modrm_reg = 6,
+ .flags = TPR_INSTR_ABS_MODRM | TPR_INSTR_MATCH_MODRM_REG,
+ .access = TPR_ACCESS_READ,
+ .length = 6,
+ .addr_offset = 2,
+ },
+ { /* mov imm32, r/m32 (c7/0) */
+ .opcode = 0xc7,
+ .modrm_reg = 0,
+ .flags = TPR_INSTR_ABS_MODRM | TPR_INSTR_MATCH_MODRM_REG,
+ .access = TPR_ACCESS_WRITE,
+ .length = 10,
+ .addr_offset = 2,
+ },
+};
+
+static void read_guest_rom_state(VAPICROMState *s)
+{
+ cpu_physical_memory_read(s->rom_state_paddr, &s->rom_state,
+ sizeof(GuestROMState));
+}
+
+static void write_guest_rom_state(VAPICROMState *s)
+{
+ cpu_physical_memory_write(s->rom_state_paddr, &s->rom_state,
+ sizeof(GuestROMState));
+}
+
+static void update_guest_rom_state(VAPICROMState *s)
+{
+ read_guest_rom_state(s);
+
+ s->rom_state.real_tpr_addr = cpu_to_le32(s->real_tpr_addr);
+ s->rom_state.vcpu_shift = cpu_to_le32(VAPIC_CPU_SHIFT);
+
+ write_guest_rom_state(s);
+}
+
+static int find_real_tpr_addr(VAPICROMState *s, CPUX86State *env)
+{
+ CPUState *cs = CPU(x86_env_get_cpu(env));
+ hwaddr paddr;
+ target_ulong addr;
+
+ if (s->state == VAPIC_ACTIVE) {
+ return 0;
+ }
+ /*
+ * If there is no prior TPR access instruction we could analyze (which is
+ * the case after resume from hibernation), we need to scan the possible
+ * virtual address space for the APIC mapping.
+ */
+ for (addr = 0xfffff000; addr >= 0x80000000; addr -= TARGET_PAGE_SIZE) {
+ paddr = cpu_get_phys_page_debug(cs, addr);
+ if (paddr != APIC_DEFAULT_ADDRESS) {
+ continue;
+ }
+ s->real_tpr_addr = addr + 0x80;
+ update_guest_rom_state(s);
+ return 0;
+ }
+ return -1;
+}
+
+static uint8_t modrm_reg(uint8_t modrm)
+{
+ return (modrm >> 3) & 7;
+}
+
+static bool is_abs_modrm(uint8_t modrm)
+{
+ return (modrm & 0xc7) == 0x05;
+}
+
+static bool opcode_matches(uint8_t *opcode, const TPRInstruction *instr)
+{
+ return opcode[0] == instr->opcode &&
+ (!(instr->flags & TPR_INSTR_ABS_MODRM) || is_abs_modrm(opcode[1])) &&
+ (!(instr->flags & TPR_INSTR_MATCH_MODRM_REG) ||
+ modrm_reg(opcode[1]) == instr->modrm_reg);
+}
+
+static int evaluate_tpr_instruction(VAPICROMState *s, X86CPU *cpu,
+ target_ulong *pip, TPRAccess access)
+{
+ CPUState *cs = CPU(cpu);
+ const TPRInstruction *instr;
+ target_ulong ip = *pip;
+ uint8_t opcode[2];
+ uint32_t real_tpr_addr;
+ int i;
+
+ if ((ip & 0xf0000000ULL) != 0x80000000ULL &&
+ (ip & 0xf0000000ULL) != 0xe0000000ULL) {
+ return -1;
+ }
+
+ /*
+ * Early Windows 2003 SMP initialization contains a
+ *
+ * mov imm32, r/m32
+ *
+ * instruction that is patched by TPR optimization. The problem is that
+ * RSP, used by the patched instruction, is zero, so the guest gets a
+ * double fault and dies.
+ */
+ if (cpu->env.regs[R_ESP] == 0) {
+ return -1;
+ }
+
+ if (kvm_enabled() && !kvm_irqchip_in_kernel()) {
+ /*
+ * KVM without kernel-based TPR access reporting will pass an IP that
+ * points after the accessing instruction. So we need to look backward
+ * to find the reason.
+ */
+ for (i = 0; i < ARRAY_SIZE(tpr_instr); i++) {
+ instr = &tpr_instr[i];
+ if (instr->access != access) {
+ continue;
+ }
+ if (cpu_memory_rw_debug(cs, ip - instr->length, opcode,
+ sizeof(opcode), 0) < 0) {
+ return -1;
+ }
+ if (opcode_matches(opcode, instr)) {
+ ip -= instr->length;
+ goto instruction_ok;
+ }
+ }
+ return -1;
+ } else {
+ if (cpu_memory_rw_debug(cs, ip, opcode, sizeof(opcode), 0) < 0) {
+ return -1;
+ }
+ for (i = 0; i < ARRAY_SIZE(tpr_instr); i++) {
+ instr = &tpr_instr[i];
+ if (opcode_matches(opcode, instr)) {
+ goto instruction_ok;
+ }
+ }
+ return -1;
+ }
+
+instruction_ok:
+ /*
+ * Grab the virtual TPR address from the instruction
+ * and update the cached values.
+ */
+ if (cpu_memory_rw_debug(cs, ip + instr->addr_offset,
+ (void *)&real_tpr_addr,
+ sizeof(real_tpr_addr), 0) < 0) {
+ return -1;
+ }
+ real_tpr_addr = le32_to_cpu(real_tpr_addr);
+ if ((real_tpr_addr & 0xfff) != 0x80) {
+ return -1;
+ }
+ s->real_tpr_addr = real_tpr_addr;
+ update_guest_rom_state(s);
+
+ *pip = ip;
+ return 0;
+}
+
+static int update_rom_mapping(VAPICROMState *s, CPUX86State *env, target_ulong ip)
+{
+ CPUState *cs = CPU(x86_env_get_cpu(env));
+ hwaddr paddr;
+ uint32_t rom_state_vaddr;
+ uint32_t pos, patch, offset;
+
+ /* nothing to do if already activated */
+ if (s->state == VAPIC_ACTIVE) {
+ return 0;
+ }
+
+ /* bail out if ROM init code was not executed (missing ROM?) */
+ if (s->state == VAPIC_INACTIVE) {
+ return -1;
+ }
+
+ /* find out virtual address of the ROM */
+ rom_state_vaddr = s->rom_state_paddr + (ip & 0xf0000000);
+ paddr = cpu_get_phys_page_debug(cs, rom_state_vaddr);
+ if (paddr == -1) {
+ return -1;
+ }
+ paddr += rom_state_vaddr & ~TARGET_PAGE_MASK;
+ if (paddr != s->rom_state_paddr) {
+ return -1;
+ }
+ read_guest_rom_state(s);
+ if (memcmp(s->rom_state.signature, "kvm aPiC", 8) != 0) {
+ return -1;
+ }
+ s->rom_state_vaddr = rom_state_vaddr;
+
+ /* fixup addresses in ROM if needed */
+ if (rom_state_vaddr == le32_to_cpu(s->rom_state.vaddr)) {
+ return 0;
+ }
+ for (pos = le32_to_cpu(s->rom_state.fixup_start);
+ pos < le32_to_cpu(s->rom_state.fixup_end);
+ pos += 4) {
+ cpu_physical_memory_read(paddr + pos - s->rom_state.vaddr,
+ &offset, sizeof(offset));
+ offset = le32_to_cpu(offset);
+ cpu_physical_memory_read(paddr + offset, &patch, sizeof(patch));
+ patch = le32_to_cpu(patch);
+ patch += rom_state_vaddr - le32_to_cpu(s->rom_state.vaddr);
+ patch = cpu_to_le32(patch);
+ cpu_physical_memory_write(paddr + offset, &patch, sizeof(patch));
+ }
+ read_guest_rom_state(s);
+ s->vapic_paddr = paddr + le32_to_cpu(s->rom_state.vapic_vaddr) -
+ le32_to_cpu(s->rom_state.vaddr);
+
+ return 0;
+}
+
+/*
+ * Tries to read the unique processor number from the Kernel Processor Control
+ * Region (KPCR) of 32-bit Windows XP and Server 2003. Returns -1 if the KPCR
+ * cannot be accessed or is considered invalid. This also ensures that we are
+ * not patching the wrong guest.
+ */
+static int get_kpcr_number(X86CPU *cpu)
+{
+ CPUX86State *env = &cpu->env;
+ struct kpcr {
+ uint8_t fill1[0x1c];
+ uint32_t self;
+ uint8_t fill2[0x31];
+ uint8_t number;
+ } QEMU_PACKED kpcr;
+
+ if (cpu_memory_rw_debug(CPU(cpu), env->segs[R_FS].base,
+ (void *)&kpcr, sizeof(kpcr), 0) < 0 ||
+ kpcr.self != env->segs[R_FS].base) {
+ return -1;
+ }
+ return kpcr.number;
+}
+
+static int vapic_enable(VAPICROMState *s, X86CPU *cpu)
+{
+ int cpu_number = get_kpcr_number(cpu);
+ hwaddr vapic_paddr;
+ static const uint8_t enabled = 1;
+
+ if (cpu_number < 0) {
+ return -1;
+ }
+ vapic_paddr = s->vapic_paddr +
+ (((hwaddr)cpu_number) << VAPIC_CPU_SHIFT);
+ cpu_physical_memory_write(vapic_paddr + offsetof(VAPICState, enabled),
+ &enabled, sizeof(enabled));
+ apic_enable_vapic(cpu->apic_state, vapic_paddr);
+
+ s->state = VAPIC_ACTIVE;
+
+ return 0;
+}
+
+static void patch_byte(X86CPU *cpu, target_ulong addr, uint8_t byte)
+{
+ cpu_memory_rw_debug(CPU(cpu), addr, &byte, 1, 1);
+}
+
+static void patch_call(VAPICROMState *s, X86CPU *cpu, target_ulong ip,
+ uint32_t target)
+{
+ uint32_t offset;
+
+ offset = cpu_to_le32(target - ip - 5);
+ patch_byte(cpu, ip, 0xe8); /* call near */
+ cpu_memory_rw_debug(CPU(cpu), ip + 1, (void *)&offset, sizeof(offset), 1);
+}
+
+static void patch_instruction(VAPICROMState *s, X86CPU *cpu, target_ulong ip)
+{
+ CPUState *cs = CPU(cpu);
+ CPUX86State *env = &cpu->env;
+ VAPICHandlers *handlers;
+ uint8_t opcode[2];
+ uint32_t imm32;
+ target_ulong current_pc = 0;
+ target_ulong current_cs_base = 0;
+ int current_flags = 0;
+
+ if (smp_cpus == 1) {
+ handlers = &s->rom_state.up;
+ } else {
+ handlers = &s->rom_state.mp;
+ }
+
+ if (!kvm_enabled()) {
+ cpu_get_tb_cpu_state(env, &current_pc, &current_cs_base,
+ &current_flags);
+ }
+
+ pause_all_vcpus();
+
+ cpu_memory_rw_debug(cs, ip, opcode, sizeof(opcode), 0);
+
+ switch (opcode[0]) {
+ case 0x89: /* mov r32 to r/m32 */
+ patch_byte(cpu, ip, 0x50 + modrm_reg(opcode[1])); /* push reg */
+ patch_call(s, cpu, ip + 1, handlers->set_tpr);
+ break;
+ case 0x8b: /* mov r/m32 to r32 */
+ patch_byte(cpu, ip, 0x90);
+ patch_call(s, cpu, ip + 1, handlers->get_tpr[modrm_reg(opcode[1])]);
+ break;
+ case 0xa1: /* mov abs to eax */
+ patch_call(s, cpu, ip, handlers->get_tpr[0]);
+ break;
+ case 0xa3: /* mov eax to abs */
+ patch_call(s, cpu, ip, handlers->set_tpr_eax);
+ break;
+ case 0xc7: /* mov imm32, r/m32 (c7/0) */
+ patch_byte(cpu, ip, 0x68); /* push imm32 */
+ cpu_memory_rw_debug(cs, ip + 6, (void *)&imm32, sizeof(imm32), 0);
+ cpu_memory_rw_debug(cs, ip + 1, (void *)&imm32, sizeof(imm32), 1);
+ patch_call(s, cpu, ip + 5, handlers->set_tpr);
+ break;
+ case 0xff: /* push r/m32 */
+ patch_byte(cpu, ip, 0x50); /* push eax */
+ patch_call(s, cpu, ip + 1, handlers->get_tpr_stack);
+ break;
+ default:
+ abort();
+ }
+
+ resume_all_vcpus();
+
+ if (!kvm_enabled()) {
+ cs->current_tb = NULL;
+ tb_gen_code(cs, current_pc, current_cs_base, current_flags, 1);
+ cpu_resume_from_signal(cs, NULL);
+ }
+}
+
+void vapic_report_tpr_access(DeviceState *dev, CPUState *cs, target_ulong ip,
+ TPRAccess access)
+{
+ VAPICROMState *s = VAPIC(dev);
+ X86CPU *cpu = X86_CPU(cs);
+ CPUX86State *env = &cpu->env;
+
+ cpu_synchronize_state(cs);
+
+ if (evaluate_tpr_instruction(s, cpu, &ip, access) < 0) {
+ if (s->state == VAPIC_ACTIVE) {
+ vapic_enable(s, cpu);
+ }
+ return;
+ }
+ if (update_rom_mapping(s, env, ip) < 0) {
+ return;
+ }
+ if (vapic_enable(s, cpu) < 0) {
+ return;
+ }
+ patch_instruction(s, cpu, ip);
+}
+
+typedef struct VAPICEnableTPRReporting {
+ DeviceState *apic;
+ bool enable;
+} VAPICEnableTPRReporting;
+
+static void vapic_do_enable_tpr_reporting(void *data)
+{
+ VAPICEnableTPRReporting *info = data;
+
+ apic_enable_tpr_access_reporting(info->apic, info->enable);
+}
+
+static void vapic_enable_tpr_reporting(bool enable)
+{
+ VAPICEnableTPRReporting info = {
+ .enable = enable,
+ };
+ CPUState *cs;
+ X86CPU *cpu;
+
+ CPU_FOREACH(cs) {
+ cpu = X86_CPU(cs);
+ info.apic = cpu->apic_state;
+ run_on_cpu(cs, vapic_do_enable_tpr_reporting, &info);
+ }
+}
+
+static void vapic_reset(DeviceState *dev)
+{
+ VAPICROMState *s = VAPIC(dev);
+
+ s->state = VAPIC_INACTIVE;
+ s->rom_state_paddr = 0;
+ vapic_enable_tpr_reporting(false);
+}
+
+/*
+ * Set the IRQ polling hypercalls to the supported variant:
+ * - vmcall if using KVM in-kernel irqchip
+ * - 32-bit VAPIC port write otherwise
+ */
+static int patch_hypercalls(VAPICROMState *s)
+{
+ hwaddr rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK;
+ static const uint8_t vmcall_pattern[] = { /* vmcall */
+ 0xb8, 0x1, 0, 0, 0, 0xf, 0x1, 0xc1
+ };
+ static const uint8_t outl_pattern[] = { /* nop; outl %eax,0x7e */
+ 0xb8, 0x1, 0, 0, 0, 0x90, 0xe7, 0x7e
+ };
+ uint8_t alternates[2];
+ const uint8_t *pattern;
+ const uint8_t *patch;
+ int patches = 0;
+ off_t pos;
+ uint8_t *rom;
+
+ rom = g_malloc(s->rom_size);
+ cpu_physical_memory_read(rom_paddr, rom, s->rom_size);
+
+ for (pos = 0; pos < s->rom_size - sizeof(vmcall_pattern); pos++) {
+ if (kvm_irqchip_in_kernel()) {
+ pattern = outl_pattern;
+ alternates[0] = outl_pattern[7];
+ alternates[1] = outl_pattern[7];
+ patch = &vmcall_pattern[5];
+ } else {
+ pattern = vmcall_pattern;
+ alternates[0] = vmcall_pattern[7];
+ alternates[1] = 0xd9; /* AMD's VMMCALL */
+ patch = &outl_pattern[5];
+ }
+ if (memcmp(rom + pos, pattern, 7) == 0 &&
+ (rom[pos + 7] == alternates[0] || rom[pos + 7] == alternates[1])) {
+ cpu_physical_memory_write(rom_paddr + pos + 5, patch, 3);
+ /*
+ * Don't flush the tb here. Under ordinary conditions, the patched
+ * calls are miles away from the current IP. Under malicious
+ * conditions, the guest could trick us to crash.
+ */
+ }
+ }
+
+ g_free(rom);
+
+ if (patches != 0 && patches != 2) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * For TCG mode or the time KVM honors read-only memory regions, we need to
+ * enable write access to the option ROM so that variables can be updated by
+ * the guest.
+ */
+static int vapic_map_rom_writable(VAPICROMState *s)
+{
+ hwaddr rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK;
+ MemoryRegionSection section;
+ MemoryRegion *as;
+ size_t rom_size;
+ uint8_t *ram;
+
+ as = sysbus_address_space(&s->busdev);
+
+ if (s->rom_mapped_writable) {
+ memory_region_del_subregion(as, &s->rom);
+ object_unparent(OBJECT(&s->rom));
+ }
+
+ /* grab RAM memory region (region @rom_paddr may still be pc.rom) */
+ section = memory_region_find(as, 0, 1);
+
+ /* read ROM size from RAM region */
+ if (rom_paddr + 2 >= memory_region_size(section.mr)) {
+ return -1;
+ }
+ ram = memory_region_get_ram_ptr(section.mr);
+ rom_size = ram[rom_paddr + 2] * ROM_BLOCK_SIZE;
+ if (rom_size == 0) {
+ return -1;
+ }
+ s->rom_size = rom_size;
+
+ /* We need to round to avoid creating subpages
+ * from which we cannot run code. */
+ rom_size += rom_paddr & ~TARGET_PAGE_MASK;
+ rom_paddr &= TARGET_PAGE_MASK;
+ rom_size = TARGET_PAGE_ALIGN(rom_size);
+
+ memory_region_init_alias(&s->rom, OBJECT(s), "kvmvapic-rom", section.mr,
+ rom_paddr, rom_size);
+ memory_region_add_subregion_overlap(as, rom_paddr, &s->rom, 1000);
+ s->rom_mapped_writable = true;
+ memory_region_unref(section.mr);
+
+ return 0;
+}
+
+static int vapic_prepare(VAPICROMState *s)
+{
+ if (vapic_map_rom_writable(s) < 0) {
+ return -1;
+ }
+
+ if (patch_hypercalls(s) < 0) {
+ return -1;
+ }
+
+ vapic_enable_tpr_reporting(true);
+
+ return 0;
+}
+
+static void vapic_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ VAPICROMState *s = opaque;
+ X86CPU *cpu;
+ CPUX86State *env;
+ hwaddr rom_paddr;
+
+ if (!current_cpu) {
+ return;
+ }
+
+ cpu_synchronize_state(current_cpu);
+ cpu = X86_CPU(current_cpu);
+ env = &cpu->env;
+
+ /*
+ * The VAPIC supports two PIO-based hypercalls, both via port 0x7E.
+ * o 16-bit write access:
+ * Reports the option ROM initialization to the hypervisor. Written
+ * value is the offset of the state structure in the ROM.
+ * o 8-bit write access:
+ * Reactivates the VAPIC after a guest hibernation, i.e. after the
+ * option ROM content has been re-initialized by a guest power cycle.
+ * o 32-bit write access:
+ * Poll for pending IRQs, considering the current VAPIC state.
+ */
+ switch (size) {
+ case 2:
+ if (s->state == VAPIC_INACTIVE) {
+ rom_paddr = (env->segs[R_CS].base + env->eip) & ROM_BLOCK_MASK;
+ s->rom_state_paddr = rom_paddr + data;
+
+ s->state = VAPIC_STANDBY;
+ }
+ if (vapic_prepare(s) < 0) {
+ s->state = VAPIC_INACTIVE;
+ s->rom_state_paddr = 0;
+ break;
+ }
+ break;
+ case 1:
+ if (kvm_enabled()) {
+ /*
+ * Disable triggering instruction in ROM by writing a NOP.
+ *
+ * We cannot do this in TCG mode as the reported IP is not
+ * accurate.
+ */
+ pause_all_vcpus();
+ patch_byte(cpu, env->eip - 2, 0x66);
+ patch_byte(cpu, env->eip - 1, 0x90);
+ resume_all_vcpus();
+ }
+
+ if (s->state == VAPIC_ACTIVE) {
+ break;
+ }
+ if (update_rom_mapping(s, env, env->eip) < 0) {
+ break;
+ }
+ if (find_real_tpr_addr(s, env) < 0) {
+ break;
+ }
+ vapic_enable(s, cpu);
+ break;
+ default:
+ case 4:
+ if (!kvm_irqchip_in_kernel()) {
+ apic_poll_irq(cpu->apic_state);
+ }
+ break;
+ }
+}
+
+static uint64_t vapic_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0xffffffff;
+}
+
+static const MemoryRegionOps vapic_ops = {
+ .write = vapic_write,
+ .read = vapic_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void vapic_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ VAPICROMState *s = VAPIC(dev);
+
+ memory_region_init_io(&s->io, OBJECT(s), &vapic_ops, s, "kvmvapic", 2);
+ sysbus_add_io(sbd, VAPIC_IO_PORT, &s->io);
+ sysbus_init_ioports(sbd, VAPIC_IO_PORT, 2);
+
+ option_rom[nb_option_roms].name = "kvmvapic.bin";
+ option_rom[nb_option_roms].bootindex = -1;
+ nb_option_roms++;
+}
+
+static void do_vapic_enable(void *data)
+{
+ VAPICROMState *s = data;
+ X86CPU *cpu = X86_CPU(first_cpu);
+
+ static const uint8_t enabled = 1;
+ cpu_physical_memory_write(s->vapic_paddr + offsetof(VAPICState, enabled),
+ &enabled, sizeof(enabled));
+ apic_enable_vapic(cpu->apic_state, s->vapic_paddr);
+ s->state = VAPIC_ACTIVE;
+}
+
+static void kvmvapic_vm_state_change(void *opaque, int running,
+ RunState state)
+{
+ VAPICROMState *s = opaque;
+ uint8_t *zero;
+
+ if (!running) {
+ return;
+ }
+
+ if (s->state == VAPIC_ACTIVE) {
+ if (smp_cpus == 1) {
+ run_on_cpu(first_cpu, do_vapic_enable, s);
+ } else {
+ zero = g_malloc0(s->rom_state.vapic_size);
+ cpu_physical_memory_write(s->vapic_paddr, zero,
+ s->rom_state.vapic_size);
+ g_free(zero);
+ }
+ }
+
+ qemu_del_vm_change_state_handler(s->vmsentry);
+}
+
+static int vapic_post_load(void *opaque, int version_id)
+{
+ VAPICROMState *s = opaque;
+
+ /*
+ * The old implementation of qemu-kvm did not provide the state
+ * VAPIC_STANDBY. Reconstruct it.
+ */
+ if (s->state == VAPIC_INACTIVE && s->rom_state_paddr != 0) {
+ s->state = VAPIC_STANDBY;
+ }
+
+ if (s->state != VAPIC_INACTIVE) {
+ if (vapic_prepare(s) < 0) {
+ return -1;
+ }
+ }
+
+ if (!s->vmsentry) {
+ s->vmsentry =
+ qemu_add_vm_change_state_handler(kvmvapic_vm_state_change, s);
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_handlers = {
+ .name = "kvmvapic-handlers",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(set_tpr, VAPICHandlers),
+ VMSTATE_UINT32(set_tpr_eax, VAPICHandlers),
+ VMSTATE_UINT32_ARRAY(get_tpr, VAPICHandlers, 8),
+ VMSTATE_UINT32(get_tpr_stack, VAPICHandlers),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_guest_rom = {
+ .name = "kvmvapic-guest-rom",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UNUSED(8), /* signature */
+ VMSTATE_UINT32(vaddr, GuestROMState),
+ VMSTATE_UINT32(fixup_start, GuestROMState),
+ VMSTATE_UINT32(fixup_end, GuestROMState),
+ VMSTATE_UINT32(vapic_vaddr, GuestROMState),
+ VMSTATE_UINT32(vapic_size, GuestROMState),
+ VMSTATE_UINT32(vcpu_shift, GuestROMState),
+ VMSTATE_UINT32(real_tpr_addr, GuestROMState),
+ VMSTATE_STRUCT(up, GuestROMState, 0, vmstate_handlers, VAPICHandlers),
+ VMSTATE_STRUCT(mp, GuestROMState, 0, vmstate_handlers, VAPICHandlers),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_vapic = {
+ .name = "kvm-tpr-opt", /* compatible with qemu-kvm VAPIC */
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = vapic_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(rom_state, VAPICROMState, 0, vmstate_guest_rom,
+ GuestROMState),
+ VMSTATE_UINT32(state, VAPICROMState),
+ VMSTATE_UINT32(real_tpr_addr, VAPICROMState),
+ VMSTATE_UINT32(rom_state_vaddr, VAPICROMState),
+ VMSTATE_UINT32(vapic_paddr, VAPICROMState),
+ VMSTATE_UINT32(rom_state_paddr, VAPICROMState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void vapic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = vapic_reset;
+ dc->vmsd = &vmstate_vapic;
+ dc->realize = vapic_realize;
+}
+
+static const TypeInfo vapic_type = {
+ .name = TYPE_VAPIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(VAPICROMState),
+ .class_init = vapic_class_init,
+};
+
+static void vapic_register(void)
+{
+ type_register_static(&vapic_type);
+}
+
+type_init(vapic_register);
diff --git a/hw/i386/multiboot.c b/hw/i386/multiboot.c
new file mode 100644
index 00000000..1adbe9e2
--- /dev/null
+++ b/hw/i386/multiboot.c
@@ -0,0 +1,371 @@
+/*
+ * QEMU PC System Emulator
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/nvram/fw_cfg.h"
+#include "multiboot.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "sysemu/sysemu.h"
+
+/* Show multiboot debug output */
+//#define DEBUG_MULTIBOOT
+
+#ifdef DEBUG_MULTIBOOT
+#define mb_debug(a...) fprintf(stderr, ## a)
+#else
+#define mb_debug(a...)
+#endif
+
+#define MULTIBOOT_STRUCT_ADDR 0x9000
+
+#if MULTIBOOT_STRUCT_ADDR > 0xf0000
+#error multiboot struct needs to fit in 16 bit real mode
+#endif
+
+enum {
+ /* Multiboot info */
+ MBI_FLAGS = 0,
+ MBI_MEM_LOWER = 4,
+ MBI_MEM_UPPER = 8,
+ MBI_BOOT_DEVICE = 12,
+ MBI_CMDLINE = 16,
+ MBI_MODS_COUNT = 20,
+ MBI_MODS_ADDR = 24,
+ MBI_MMAP_ADDR = 48,
+ MBI_BOOTLOADER = 64,
+
+ MBI_SIZE = 88,
+
+ /* Multiboot modules */
+ MB_MOD_START = 0,
+ MB_MOD_END = 4,
+ MB_MOD_CMDLINE = 8,
+
+ MB_MOD_SIZE = 16,
+
+ /* Region offsets */
+ ADDR_E820_MAP = MULTIBOOT_STRUCT_ADDR + 0,
+ ADDR_MBI = ADDR_E820_MAP + 0x500,
+
+ /* Multiboot flags */
+ MULTIBOOT_FLAGS_MEMORY = 1 << 0,
+ MULTIBOOT_FLAGS_BOOT_DEVICE = 1 << 1,
+ MULTIBOOT_FLAGS_CMDLINE = 1 << 2,
+ MULTIBOOT_FLAGS_MODULES = 1 << 3,
+ MULTIBOOT_FLAGS_MMAP = 1 << 6,
+ MULTIBOOT_FLAGS_BOOTLOADER = 1 << 9,
+};
+
+typedef struct {
+ /* buffer holding kernel, cmdlines and mb_infos */
+ void *mb_buf;
+ /* address in target */
+ hwaddr mb_buf_phys;
+ /* size of mb_buf in bytes */
+ unsigned mb_buf_size;
+ /* offset of mb-info's in bytes */
+ hwaddr offset_mbinfo;
+ /* offset in buffer for cmdlines in bytes */
+ hwaddr offset_cmdlines;
+ /* offset in buffer for bootloader name in bytes */
+ hwaddr offset_bootloader;
+ /* offset of modules in bytes */
+ hwaddr offset_mods;
+ /* available slots for mb modules infos */
+ int mb_mods_avail;
+ /* currently used slots of mb modules */
+ int mb_mods_count;
+} MultibootState;
+
+const char *bootloader_name = "qemu";
+
+static uint32_t mb_add_cmdline(MultibootState *s, const char *cmdline)
+{
+ hwaddr p = s->offset_cmdlines;
+ char *b = (char *)s->mb_buf + p;
+
+ get_opt_value(b, strlen(cmdline) + 1, cmdline);
+ s->offset_cmdlines += strlen(b) + 1;
+ return s->mb_buf_phys + p;
+}
+
+static uint32_t mb_add_bootloader(MultibootState *s, const char *bootloader)
+{
+ hwaddr p = s->offset_bootloader;
+ char *b = (char *)s->mb_buf + p;
+
+ memcpy(b, bootloader, strlen(bootloader) + 1);
+ s->offset_bootloader += strlen(b) + 1;
+ return s->mb_buf_phys + p;
+}
+
+static void mb_add_mod(MultibootState *s,
+ hwaddr start, hwaddr end,
+ hwaddr cmdline_phys)
+{
+ char *p;
+ assert(s->mb_mods_count < s->mb_mods_avail);
+
+ p = (char *)s->mb_buf + s->offset_mbinfo + MB_MOD_SIZE * s->mb_mods_count;
+
+ stl_p(p + MB_MOD_START, start);
+ stl_p(p + MB_MOD_END, end);
+ stl_p(p + MB_MOD_CMDLINE, cmdline_phys);
+
+ mb_debug("mod%02d: "TARGET_FMT_plx" - "TARGET_FMT_plx"\n",
+ s->mb_mods_count, start, end);
+
+ s->mb_mods_count++;
+}
+
+int load_multiboot(FWCfgState *fw_cfg,
+ FILE *f,
+ const char *kernel_filename,
+ const char *initrd_filename,
+ const char *kernel_cmdline,
+ int kernel_file_size,
+ uint8_t *header)
+{
+ int i, is_multiboot = 0;
+ uint32_t flags = 0;
+ uint32_t mh_entry_addr;
+ uint32_t mh_load_addr;
+ uint32_t mb_kernel_size;
+ MultibootState mbs;
+ uint8_t bootinfo[MBI_SIZE];
+ uint8_t *mb_bootinfo_data;
+ uint32_t cmdline_len;
+
+ /* Ok, let's see if it is a multiboot image.
+ The header is 12x32bit long, so the latest entry may be 8192 - 48. */
+ for (i = 0; i < (8192 - 48); i += 4) {
+ if (ldl_p(header+i) == 0x1BADB002) {
+ uint32_t checksum = ldl_p(header+i+8);
+ flags = ldl_p(header+i+4);
+ checksum += flags;
+ checksum += (uint32_t)0x1BADB002;
+ if (!checksum) {
+ is_multiboot = 1;
+ break;
+ }
+ }
+ }
+
+ if (!is_multiboot)
+ return 0; /* no multiboot */
+
+ mb_debug("qemu: I believe we found a multiboot image!\n");
+ memset(bootinfo, 0, sizeof(bootinfo));
+ memset(&mbs, 0, sizeof(mbs));
+
+ if (flags & 0x00000004) { /* MULTIBOOT_HEADER_HAS_VBE */
+ fprintf(stderr, "qemu: multiboot knows VBE. we don't.\n");
+ }
+ if (!(flags & 0x00010000)) { /* MULTIBOOT_HEADER_HAS_ADDR */
+ uint64_t elf_entry;
+ uint64_t elf_low, elf_high;
+ int kernel_size;
+ fclose(f);
+
+ if (((struct elf64_hdr*)header)->e_machine == EM_X86_64) {
+ fprintf(stderr, "Cannot load x86-64 image, give a 32bit one.\n");
+ exit(1);
+ }
+
+ kernel_size = load_elf(kernel_filename, NULL, NULL, &elf_entry,
+ &elf_low, &elf_high, 0, ELF_MACHINE, 0);
+ if (kernel_size < 0) {
+ fprintf(stderr, "Error while loading elf kernel\n");
+ exit(1);
+ }
+ mh_load_addr = elf_low;
+ mb_kernel_size = elf_high - elf_low;
+ mh_entry_addr = elf_entry;
+
+ mbs.mb_buf = g_malloc(mb_kernel_size);
+ if (rom_copy(mbs.mb_buf, mh_load_addr, mb_kernel_size) != mb_kernel_size) {
+ fprintf(stderr, "Error while fetching elf kernel from rom\n");
+ exit(1);
+ }
+
+ mb_debug("qemu: loading multiboot-elf kernel (%#x bytes) with entry %#zx\n",
+ mb_kernel_size, (size_t)mh_entry_addr);
+ } else {
+ /* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_ADDR. */
+ uint32_t mh_header_addr = ldl_p(header+i+12);
+ uint32_t mh_load_end_addr = ldl_p(header+i+20);
+ uint32_t mh_bss_end_addr = ldl_p(header+i+24);
+ mh_load_addr = ldl_p(header+i+16);
+ uint32_t mb_kernel_text_offset = i - (mh_header_addr - mh_load_addr);
+ uint32_t mb_load_size = 0;
+ mh_entry_addr = ldl_p(header+i+28);
+
+ if (mh_load_end_addr) {
+ mb_kernel_size = mh_bss_end_addr - mh_load_addr;
+ mb_load_size = mh_load_end_addr - mh_load_addr;
+ } else {
+ mb_kernel_size = kernel_file_size - mb_kernel_text_offset;
+ mb_load_size = mb_kernel_size;
+ }
+
+ /* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_VBE.
+ uint32_t mh_mode_type = ldl_p(header+i+32);
+ uint32_t mh_width = ldl_p(header+i+36);
+ uint32_t mh_height = ldl_p(header+i+40);
+ uint32_t mh_depth = ldl_p(header+i+44); */
+
+ mb_debug("multiboot: mh_header_addr = %#x\n", mh_header_addr);
+ mb_debug("multiboot: mh_load_addr = %#x\n", mh_load_addr);
+ mb_debug("multiboot: mh_load_end_addr = %#x\n", mh_load_end_addr);
+ mb_debug("multiboot: mh_bss_end_addr = %#x\n", mh_bss_end_addr);
+ mb_debug("qemu: loading multiboot kernel (%#x bytes) at %#x\n",
+ mb_load_size, mh_load_addr);
+
+ mbs.mb_buf = g_malloc(mb_kernel_size);
+ fseek(f, mb_kernel_text_offset, SEEK_SET);
+ if (fread(mbs.mb_buf, 1, mb_load_size, f) != mb_load_size) {
+ fprintf(stderr, "fread() failed\n");
+ exit(1);
+ }
+ memset(mbs.mb_buf + mb_load_size, 0, mb_kernel_size - mb_load_size);
+ fclose(f);
+ }
+
+ mbs.mb_buf_phys = mh_load_addr;
+
+ mbs.mb_buf_size = TARGET_PAGE_ALIGN(mb_kernel_size);
+ mbs.offset_mbinfo = mbs.mb_buf_size;
+
+ /* Calculate space for cmdlines, bootloader name, and mb_mods */
+ cmdline_len = strlen(kernel_filename) + 1;
+ cmdline_len += strlen(kernel_cmdline) + 1;
+ if (initrd_filename) {
+ const char *r = initrd_filename;
+ cmdline_len += strlen(r) + 1;
+ mbs.mb_mods_avail = 1;
+ while (*(r = get_opt_value(NULL, 0, r))) {
+ mbs.mb_mods_avail++;
+ r++;
+ }
+ }
+
+ mbs.mb_buf_size += cmdline_len;
+ mbs.mb_buf_size += MB_MOD_SIZE * mbs.mb_mods_avail;
+ mbs.mb_buf_size += strlen(bootloader_name) + 1;
+
+ mbs.mb_buf_size = TARGET_PAGE_ALIGN(mbs.mb_buf_size);
+
+ /* enlarge mb_buf to hold cmdlines, bootloader, mb-info structs */
+ mbs.mb_buf = g_realloc(mbs.mb_buf, mbs.mb_buf_size);
+ mbs.offset_cmdlines = mbs.offset_mbinfo + mbs.mb_mods_avail * MB_MOD_SIZE;
+ mbs.offset_bootloader = mbs.offset_cmdlines + cmdline_len;
+
+ if (initrd_filename) {
+ char *next_initrd, not_last;
+
+ mbs.offset_mods = mbs.mb_buf_size;
+
+ do {
+ char *next_space;
+ int mb_mod_length;
+ uint32_t offs = mbs.mb_buf_size;
+
+ next_initrd = (char *)get_opt_value(NULL, 0, initrd_filename);
+ not_last = *next_initrd;
+ *next_initrd = '\0';
+ /* if a space comes after the module filename, treat everything
+ after that as parameters */
+ hwaddr c = mb_add_cmdline(&mbs, initrd_filename);
+ if ((next_space = strchr(initrd_filename, ' ')))
+ *next_space = '\0';
+ mb_debug("multiboot loading module: %s\n", initrd_filename);
+ mb_mod_length = get_image_size(initrd_filename);
+ if (mb_mod_length < 0) {
+ fprintf(stderr, "Failed to open file '%s'\n", initrd_filename);
+ exit(1);
+ }
+
+ mbs.mb_buf_size = TARGET_PAGE_ALIGN(mb_mod_length + mbs.mb_buf_size);
+ mbs.mb_buf = g_realloc(mbs.mb_buf, mbs.mb_buf_size);
+
+ load_image(initrd_filename, (unsigned char *)mbs.mb_buf + offs);
+ mb_add_mod(&mbs, mbs.mb_buf_phys + offs,
+ mbs.mb_buf_phys + offs + mb_mod_length, c);
+
+ mb_debug("mod_start: %p\nmod_end: %p\n cmdline: "TARGET_FMT_plx"\n",
+ (char *)mbs.mb_buf + offs,
+ (char *)mbs.mb_buf + offs + mb_mod_length, c);
+ initrd_filename = next_initrd+1;
+ } while (not_last);
+ }
+
+ /* Commandline support */
+ char kcmdline[strlen(kernel_filename) + strlen(kernel_cmdline) + 2];
+ snprintf(kcmdline, sizeof(kcmdline), "%s %s",
+ kernel_filename, kernel_cmdline);
+ stl_p(bootinfo + MBI_CMDLINE, mb_add_cmdline(&mbs, kcmdline));
+
+ stl_p(bootinfo + MBI_BOOTLOADER, mb_add_bootloader(&mbs, bootloader_name));
+
+ stl_p(bootinfo + MBI_MODS_ADDR, mbs.mb_buf_phys + mbs.offset_mbinfo);
+ stl_p(bootinfo + MBI_MODS_COUNT, mbs.mb_mods_count); /* mods_count */
+
+ /* the kernel is where we want it to be now */
+ stl_p(bootinfo + MBI_FLAGS, MULTIBOOT_FLAGS_MEMORY
+ | MULTIBOOT_FLAGS_BOOT_DEVICE
+ | MULTIBOOT_FLAGS_CMDLINE
+ | MULTIBOOT_FLAGS_MODULES
+ | MULTIBOOT_FLAGS_MMAP
+ | MULTIBOOT_FLAGS_BOOTLOADER);
+ stl_p(bootinfo + MBI_BOOT_DEVICE, 0x8000ffff); /* XXX: use the -boot switch? */
+ stl_p(bootinfo + MBI_MMAP_ADDR, ADDR_E820_MAP);
+
+ mb_debug("multiboot: mh_entry_addr = %#x\n", mh_entry_addr);
+ mb_debug(" mb_buf_phys = "TARGET_FMT_plx"\n", mbs.mb_buf_phys);
+ mb_debug(" mod_start = "TARGET_FMT_plx"\n", mbs.mb_buf_phys + mbs.offset_mods);
+ mb_debug(" mb_mods_count = %d\n", mbs.mb_mods_count);
+
+ /* save bootinfo off the stack */
+ mb_bootinfo_data = g_malloc(sizeof(bootinfo));
+ memcpy(mb_bootinfo_data, bootinfo, sizeof(bootinfo));
+
+ /* Pass variables to option rom */
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ENTRY, mh_entry_addr);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, mh_load_addr);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_SIZE, mbs.mb_buf_size);
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_KERNEL_DATA,
+ mbs.mb_buf, mbs.mb_buf_size);
+
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, ADDR_MBI);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, sizeof(bootinfo));
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_INITRD_DATA, mb_bootinfo_data,
+ sizeof(bootinfo));
+
+ option_rom[nb_option_roms].name = "multiboot.bin";
+ option_rom[nb_option_roms].bootindex = 0;
+ nb_option_roms++;
+
+ return 1; /* yes, we are multiboot */
+}
diff --git a/hw/i386/multiboot.h b/hw/i386/multiboot.h
new file mode 100644
index 00000000..60de309c
--- /dev/null
+++ b/hw/i386/multiboot.h
@@ -0,0 +1,14 @@
+#ifndef QEMU_MULTIBOOT_H
+#define QEMU_MULTIBOOT_H
+
+#include "hw/nvram/fw_cfg.h"
+
+int load_multiboot(FWCfgState *fw_cfg,
+ FILE *f,
+ const char *kernel_filename,
+ const char *initrd_filename,
+ const char *kernel_cmdline,
+ int kernel_file_size,
+ uint8_t *header);
+
+#endif
diff --git a/hw/i386/pc.c b/hw/i386/pc.c
new file mode 100644
index 00000000..7661ea9c
--- /dev/null
+++ b/hw/i386/pc.c
@@ -0,0 +1,1965 @@
+/*
+ * QEMU PC System Emulator
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/char/serial.h"
+#include "hw/i386/apic.h"
+#include "hw/i386/topology.h"
+#include "sysemu/cpus.h"
+#include "hw/block/fdc.h"
+#include "hw/ide.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/timer/hpet.h"
+#include "hw/i386/smbios.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "multiboot.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/timer/i8254.h"
+#include "hw/audio/pcspk.h"
+#include "hw/pci/msi.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/numa.h"
+#include "sysemu/kvm.h"
+#include "sysemu/qtest.h"
+#include "kvm_i386.h"
+#include "hw/xen/xen.h"
+#include "sysemu/block-backend.h"
+#include "hw/block/block.h"
+#include "ui/qemu-spice.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+#include "sysemu/arch_init.h"
+#include "qemu/bitmap.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/cpu_hotplug.h"
+#include "hw/cpu/icc_bus.h"
+#include "hw/boards.h"
+#include "hw/pci/pci_host.h"
+#include "acpi-build.h"
+#include "hw/mem/pc-dimm.h"
+#include "qapi/visitor.h"
+#include "qapi-visit.h"
+
+/* debug PC/ISA interrupts */
+//#define DEBUG_IRQ
+
+#ifdef DEBUG_IRQ
+#define DPRINTF(fmt, ...) \
+ do { printf("CPUIRQ: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+/* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables
+ * (128K) and other BIOS datastructures (less than 4K reported to be used at
+ * the moment, 32K should be enough for a while). */
+static unsigned acpi_data_size = 0x20000 + 0x8000;
+void pc_set_legacy_acpi_data_size(void)
+{
+ acpi_data_size = 0x10000;
+}
+
+#define BIOS_CFG_IOPORT 0x510
+#define FW_CFG_ACPI_TABLES (FW_CFG_ARCH_LOCAL + 0)
+#define FW_CFG_SMBIOS_ENTRIES (FW_CFG_ARCH_LOCAL + 1)
+#define FW_CFG_IRQ0_OVERRIDE (FW_CFG_ARCH_LOCAL + 2)
+#define FW_CFG_E820_TABLE (FW_CFG_ARCH_LOCAL + 3)
+#define FW_CFG_HPET (FW_CFG_ARCH_LOCAL + 4)
+
+#define E820_NR_ENTRIES 16
+
+struct e820_entry {
+ uint64_t address;
+ uint64_t length;
+ uint32_t type;
+} QEMU_PACKED __attribute((__aligned__(4)));
+
+struct e820_table {
+ uint32_t count;
+ struct e820_entry entry[E820_NR_ENTRIES];
+} QEMU_PACKED __attribute((__aligned__(4)));
+
+static struct e820_table e820_reserve;
+static struct e820_entry *e820_table;
+static unsigned e820_entries;
+struct hpet_fw_config hpet_cfg = {.count = UINT8_MAX};
+
+void gsi_handler(void *opaque, int n, int level)
+{
+ GSIState *s = opaque;
+
+ DPRINTF("pc: %s GSI %d\n", level ? "raising" : "lowering", n);
+ if (n < ISA_NUM_IRQS) {
+ qemu_set_irq(s->i8259_irq[n], level);
+ }
+ qemu_set_irq(s->ioapic_irq[n], level);
+}
+
+static void ioport80_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned size)
+{
+}
+
+static uint64_t ioport80_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0xffffffffffffffffULL;
+}
+
+/* MSDOS compatibility mode FPU exception support */
+static qemu_irq ferr_irq;
+
+void pc_register_ferr_irq(qemu_irq irq)
+{
+ ferr_irq = irq;
+}
+
+/* XXX: add IGNNE support */
+void cpu_set_ferr(CPUX86State *s)
+{
+ qemu_irq_raise(ferr_irq);
+}
+
+static void ioportF0_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned size)
+{
+ qemu_irq_lower(ferr_irq);
+}
+
+static uint64_t ioportF0_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0xffffffffffffffffULL;
+}
+
+/* TSC handling */
+uint64_t cpu_get_tsc(CPUX86State *env)
+{
+ return cpu_get_ticks();
+}
+
+/* IRQ handling */
+int cpu_get_pic_interrupt(CPUX86State *env)
+{
+ X86CPU *cpu = x86_env_get_cpu(env);
+ int intno;
+
+ intno = apic_get_interrupt(cpu->apic_state);
+ if (intno >= 0) {
+ return intno;
+ }
+ /* read the irq from the PIC */
+ if (!apic_accept_pic_intr(cpu->apic_state)) {
+ return -1;
+ }
+
+ intno = pic_read_irq(isa_pic);
+ return intno;
+}
+
+static void pic_irq_request(void *opaque, int irq, int level)
+{
+ CPUState *cs = first_cpu;
+ X86CPU *cpu = X86_CPU(cs);
+
+ DPRINTF("pic_irqs: %s irq %d\n", level? "raise" : "lower", irq);
+ if (cpu->apic_state) {
+ CPU_FOREACH(cs) {
+ cpu = X86_CPU(cs);
+ if (apic_accept_pic_intr(cpu->apic_state)) {
+ apic_deliver_pic_intr(cpu->apic_state, level);
+ }
+ }
+ } else {
+ if (level) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ }
+}
+
+/* PC cmos mappings */
+
+#define REG_EQUIPMENT_BYTE 0x14
+
+static int cmos_get_fd_drive_type(FDriveType fd0)
+{
+ int val;
+
+ switch (fd0) {
+ case FDRIVE_DRV_144:
+ /* 1.44 Mb 3"5 drive */
+ val = 4;
+ break;
+ case FDRIVE_DRV_288:
+ /* 2.88 Mb 3"5 drive */
+ val = 5;
+ break;
+ case FDRIVE_DRV_120:
+ /* 1.2 Mb 5"5 drive */
+ val = 2;
+ break;
+ case FDRIVE_DRV_NONE:
+ default:
+ val = 0;
+ break;
+ }
+ return val;
+}
+
+static void cmos_init_hd(ISADevice *s, int type_ofs, int info_ofs,
+ int16_t cylinders, int8_t heads, int8_t sectors)
+{
+ rtc_set_memory(s, type_ofs, 47);
+ rtc_set_memory(s, info_ofs, cylinders);
+ rtc_set_memory(s, info_ofs + 1, cylinders >> 8);
+ rtc_set_memory(s, info_ofs + 2, heads);
+ rtc_set_memory(s, info_ofs + 3, 0xff);
+ rtc_set_memory(s, info_ofs + 4, 0xff);
+ rtc_set_memory(s, info_ofs + 5, 0xc0 | ((heads > 8) << 3));
+ rtc_set_memory(s, info_ofs + 6, cylinders);
+ rtc_set_memory(s, info_ofs + 7, cylinders >> 8);
+ rtc_set_memory(s, info_ofs + 8, sectors);
+}
+
+/* convert boot_device letter to something recognizable by the bios */
+static int boot_device2nibble(char boot_device)
+{
+ switch(boot_device) {
+ case 'a':
+ case 'b':
+ return 0x01; /* floppy boot */
+ case 'c':
+ return 0x02; /* hard drive boot */
+ case 'd':
+ return 0x03; /* CD-ROM boot */
+ case 'n':
+ return 0x04; /* Network boot */
+ }
+ return 0;
+}
+
+static void set_boot_dev(ISADevice *s, const char *boot_device, Error **errp)
+{
+#define PC_MAX_BOOT_DEVICES 3
+ int nbds, bds[3] = { 0, };
+ int i;
+
+ nbds = strlen(boot_device);
+ if (nbds > PC_MAX_BOOT_DEVICES) {
+ error_setg(errp, "Too many boot devices for PC");
+ return;
+ }
+ for (i = 0; i < nbds; i++) {
+ bds[i] = boot_device2nibble(boot_device[i]);
+ if (bds[i] == 0) {
+ error_setg(errp, "Invalid boot device for PC: '%c'",
+ boot_device[i]);
+ return;
+ }
+ }
+ rtc_set_memory(s, 0x3d, (bds[1] << 4) | bds[0]);
+ rtc_set_memory(s, 0x38, (bds[2] << 4) | (fd_bootchk ? 0x0 : 0x1));
+}
+
+static void pc_boot_set(void *opaque, const char *boot_device, Error **errp)
+{
+ set_boot_dev(opaque, boot_device, errp);
+}
+
+static void pc_cmos_init_floppy(ISADevice *rtc_state, ISADevice *floppy)
+{
+ int val, nb, i;
+ FDriveType fd_type[2] = { FDRIVE_DRV_NONE, FDRIVE_DRV_NONE };
+
+ /* floppy type */
+ if (floppy) {
+ for (i = 0; i < 2; i++) {
+ fd_type[i] = isa_fdc_get_drive_type(floppy, i);
+ }
+ }
+ val = (cmos_get_fd_drive_type(fd_type[0]) << 4) |
+ cmos_get_fd_drive_type(fd_type[1]);
+ rtc_set_memory(rtc_state, 0x10, val);
+
+ val = rtc_get_memory(rtc_state, REG_EQUIPMENT_BYTE);
+ nb = 0;
+ if (fd_type[0] < FDRIVE_DRV_NONE) {
+ nb++;
+ }
+ if (fd_type[1] < FDRIVE_DRV_NONE) {
+ nb++;
+ }
+ switch (nb) {
+ case 0:
+ break;
+ case 1:
+ val |= 0x01; /* 1 drive, ready for boot */
+ break;
+ case 2:
+ val |= 0x41; /* 2 drives, ready for boot */
+ break;
+ }
+ rtc_set_memory(rtc_state, REG_EQUIPMENT_BYTE, val);
+}
+
+typedef struct pc_cmos_init_late_arg {
+ ISADevice *rtc_state;
+ BusState *idebus[2];
+} pc_cmos_init_late_arg;
+
+typedef struct check_fdc_state {
+ ISADevice *floppy;
+ bool multiple;
+} CheckFdcState;
+
+static int check_fdc(Object *obj, void *opaque)
+{
+ CheckFdcState *state = opaque;
+ Object *fdc;
+ uint32_t iobase;
+ Error *local_err = NULL;
+
+ fdc = object_dynamic_cast(obj, TYPE_ISA_FDC);
+ if (!fdc) {
+ return 0;
+ }
+
+ iobase = object_property_get_int(obj, "iobase", &local_err);
+ if (local_err || iobase != 0x3f0) {
+ error_free(local_err);
+ return 0;
+ }
+
+ if (state->floppy) {
+ state->multiple = true;
+ } else {
+ state->floppy = ISA_DEVICE(obj);
+ }
+ return 0;
+}
+
+static const char * const fdc_container_path[] = {
+ "/unattached", "/peripheral", "/peripheral-anon"
+};
+
+static void pc_cmos_init_late(void *opaque)
+{
+ pc_cmos_init_late_arg *arg = opaque;
+ ISADevice *s = arg->rtc_state;
+ int16_t cylinders;
+ int8_t heads, sectors;
+ int val;
+ int i, trans;
+ Object *container;
+ CheckFdcState state = { 0 };
+
+ val = 0;
+ if (ide_get_geometry(arg->idebus[0], 0,
+ &cylinders, &heads, &sectors) >= 0) {
+ cmos_init_hd(s, 0x19, 0x1b, cylinders, heads, sectors);
+ val |= 0xf0;
+ }
+ if (ide_get_geometry(arg->idebus[0], 1,
+ &cylinders, &heads, &sectors) >= 0) {
+ cmos_init_hd(s, 0x1a, 0x24, cylinders, heads, sectors);
+ val |= 0x0f;
+ }
+ rtc_set_memory(s, 0x12, val);
+
+ val = 0;
+ for (i = 0; i < 4; i++) {
+ /* NOTE: ide_get_geometry() returns the physical
+ geometry. It is always such that: 1 <= sects <= 63, 1
+ <= heads <= 16, 1 <= cylinders <= 16383. The BIOS
+ geometry can be different if a translation is done. */
+ if (ide_get_geometry(arg->idebus[i / 2], i % 2,
+ &cylinders, &heads, &sectors) >= 0) {
+ trans = ide_get_bios_chs_trans(arg->idebus[i / 2], i % 2) - 1;
+ assert((trans & ~3) == 0);
+ val |= trans << (i * 2);
+ }
+ }
+ rtc_set_memory(s, 0x39, val);
+
+ /*
+ * Locate the FDC at IO address 0x3f0, and configure the CMOS registers
+ * accordingly.
+ */
+ for (i = 0; i < ARRAY_SIZE(fdc_container_path); i++) {
+ container = container_get(qdev_get_machine(), fdc_container_path[i]);
+ object_child_foreach(container, check_fdc, &state);
+ }
+
+ if (state.multiple) {
+ error_report("warning: multiple floppy disk controllers with "
+ "iobase=0x3f0 have been found;\n"
+ "the one being picked for CMOS setup might not reflect "
+ "your intent");
+ }
+ pc_cmos_init_floppy(s, state.floppy);
+
+ qemu_unregister_reset(pc_cmos_init_late, opaque);
+}
+
+void pc_cmos_init(ram_addr_t ram_size, ram_addr_t above_4g_mem_size,
+ const char *boot_device, MachineState *machine,
+ BusState *idebus0, BusState *idebus1,
+ ISADevice *s)
+{
+ int val;
+ static pc_cmos_init_late_arg arg;
+ PCMachineState *pc_machine = PC_MACHINE(machine);
+ Error *local_err = NULL;
+
+ /* various important CMOS locations needed by PC/Bochs bios */
+
+ /* memory size */
+ /* base memory (first MiB) */
+ val = MIN(ram_size / 1024, 640);
+ rtc_set_memory(s, 0x15, val);
+ rtc_set_memory(s, 0x16, val >> 8);
+ /* extended memory (next 64MiB) */
+ if (ram_size > 1024 * 1024) {
+ val = (ram_size - 1024 * 1024) / 1024;
+ } else {
+ val = 0;
+ }
+ if (val > 65535)
+ val = 65535;
+ rtc_set_memory(s, 0x17, val);
+ rtc_set_memory(s, 0x18, val >> 8);
+ rtc_set_memory(s, 0x30, val);
+ rtc_set_memory(s, 0x31, val >> 8);
+ /* memory between 16MiB and 4GiB */
+ if (ram_size > 16 * 1024 * 1024) {
+ val = (ram_size - 16 * 1024 * 1024) / 65536;
+ } else {
+ val = 0;
+ }
+ if (val > 65535)
+ val = 65535;
+ rtc_set_memory(s, 0x34, val);
+ rtc_set_memory(s, 0x35, val >> 8);
+ /* memory above 4GiB */
+ val = above_4g_mem_size / 65536;
+ rtc_set_memory(s, 0x5b, val);
+ rtc_set_memory(s, 0x5c, val >> 8);
+ rtc_set_memory(s, 0x5d, val >> 16);
+
+ /* set the number of CPU */
+ rtc_set_memory(s, 0x5f, smp_cpus - 1);
+
+ object_property_add_link(OBJECT(machine), "rtc_state",
+ TYPE_ISA_DEVICE,
+ (Object **)&pc_machine->rtc,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE, &error_abort);
+ object_property_set_link(OBJECT(machine), OBJECT(s),
+ "rtc_state", &error_abort);
+
+ set_boot_dev(s, boot_device, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ exit(1);
+ }
+
+ val = 0;
+ val |= 0x02; /* FPU is there */
+ val |= 0x04; /* PS/2 mouse installed */
+ rtc_set_memory(s, REG_EQUIPMENT_BYTE, val);
+
+ /* hard drives and FDC */
+ arg.rtc_state = s;
+ arg.idebus[0] = idebus0;
+ arg.idebus[1] = idebus1;
+ qemu_register_reset(pc_cmos_init_late, &arg);
+}
+
+#define TYPE_PORT92 "port92"
+#define PORT92(obj) OBJECT_CHECK(Port92State, (obj), TYPE_PORT92)
+
+/* port 92 stuff: could be split off */
+typedef struct Port92State {
+ ISADevice parent_obj;
+
+ MemoryRegion io;
+ uint8_t outport;
+ qemu_irq *a20_out;
+} Port92State;
+
+static void port92_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ Port92State *s = opaque;
+ int oldval = s->outport;
+
+ DPRINTF("port92: write 0x%02" PRIx64 "\n", val);
+ s->outport = val;
+ qemu_set_irq(*s->a20_out, (val >> 1) & 1);
+ if ((val & 1) && !(oldval & 1)) {
+ qemu_system_reset_request();
+ }
+}
+
+static uint64_t port92_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ Port92State *s = opaque;
+ uint32_t ret;
+
+ ret = s->outport;
+ DPRINTF("port92: read 0x%02x\n", ret);
+ return ret;
+}
+
+static void port92_init(ISADevice *dev, qemu_irq *a20_out)
+{
+ Port92State *s = PORT92(dev);
+
+ s->a20_out = a20_out;
+}
+
+static const VMStateDescription vmstate_port92_isa = {
+ .name = "port92",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(outport, Port92State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void port92_reset(DeviceState *d)
+{
+ Port92State *s = PORT92(d);
+
+ s->outport &= ~1;
+}
+
+static const MemoryRegionOps port92_ops = {
+ .read = port92_read,
+ .write = port92_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void port92_initfn(Object *obj)
+{
+ Port92State *s = PORT92(obj);
+
+ memory_region_init_io(&s->io, OBJECT(s), &port92_ops, s, "port92", 1);
+
+ s->outport = 0;
+}
+
+static void port92_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ Port92State *s = PORT92(dev);
+
+ isa_register_ioport(isadev, &s->io, 0x92);
+}
+
+static void port92_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = port92_realizefn;
+ dc->reset = port92_reset;
+ dc->vmsd = &vmstate_port92_isa;
+ /*
+ * Reason: unlike ordinary ISA devices, this one needs additional
+ * wiring: its A20 output line needs to be wired up by
+ * port92_init().
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo port92_info = {
+ .name = TYPE_PORT92,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(Port92State),
+ .instance_init = port92_initfn,
+ .class_init = port92_class_initfn,
+};
+
+static void port92_register_types(void)
+{
+ type_register_static(&port92_info);
+}
+
+type_init(port92_register_types)
+
+static void handle_a20_line_change(void *opaque, int irq, int level)
+{
+ X86CPU *cpu = opaque;
+
+ /* XXX: send to all CPUs ? */
+ /* XXX: add logic to handle multiple A20 line sources */
+ x86_cpu_set_a20(cpu, level);
+}
+
+int e820_add_entry(uint64_t address, uint64_t length, uint32_t type)
+{
+ int index = le32_to_cpu(e820_reserve.count);
+ struct e820_entry *entry;
+
+ if (type != E820_RAM) {
+ /* old FW_CFG_E820_TABLE entry -- reservations only */
+ if (index >= E820_NR_ENTRIES) {
+ return -EBUSY;
+ }
+ entry = &e820_reserve.entry[index++];
+
+ entry->address = cpu_to_le64(address);
+ entry->length = cpu_to_le64(length);
+ entry->type = cpu_to_le32(type);
+
+ e820_reserve.count = cpu_to_le32(index);
+ }
+
+ /* new "etc/e820" file -- include ram too */
+ e820_table = g_renew(struct e820_entry, e820_table, e820_entries + 1);
+ e820_table[e820_entries].address = cpu_to_le64(address);
+ e820_table[e820_entries].length = cpu_to_le64(length);
+ e820_table[e820_entries].type = cpu_to_le32(type);
+ e820_entries++;
+
+ return e820_entries;
+}
+
+int e820_get_num_entries(void)
+{
+ return e820_entries;
+}
+
+bool e820_get_entry(int idx, uint32_t type, uint64_t *address, uint64_t *length)
+{
+ if (idx < e820_entries && e820_table[idx].type == cpu_to_le32(type)) {
+ *address = le64_to_cpu(e820_table[idx].address);
+ *length = le64_to_cpu(e820_table[idx].length);
+ return true;
+ }
+ return false;
+}
+
+/* Enables contiguous-apic-ID mode, for compatibility */
+static bool compat_apic_id_mode;
+
+void enable_compat_apic_id_mode(void)
+{
+ compat_apic_id_mode = true;
+}
+
+/* Calculates initial APIC ID for a specific CPU index
+ *
+ * Currently we need to be able to calculate the APIC ID from the CPU index
+ * alone (without requiring a CPU object), as the QEMU<->Seabios interfaces have
+ * no concept of "CPU index", and the NUMA tables on fw_cfg need the APIC ID of
+ * all CPUs up to max_cpus.
+ */
+static uint32_t x86_cpu_apic_id_from_index(unsigned int cpu_index)
+{
+ uint32_t correct_id;
+ static bool warned;
+
+ correct_id = x86_apicid_from_cpu_idx(smp_cores, smp_threads, cpu_index);
+ if (compat_apic_id_mode) {
+ if (cpu_index != correct_id && !warned && !qtest_enabled()) {
+ error_report("APIC IDs set in compatibility mode, "
+ "CPU topology won't match the configuration");
+ warned = true;
+ }
+ return cpu_index;
+ } else {
+ return correct_id;
+ }
+}
+
+/* Calculates the limit to CPU APIC ID values
+ *
+ * This function returns the limit for the APIC ID value, so that all
+ * CPU APIC IDs are < pc_apic_id_limit().
+ *
+ * This is used for FW_CFG_MAX_CPUS. See comments on bochs_bios_init().
+ */
+static unsigned int pc_apic_id_limit(unsigned int max_cpus)
+{
+ return x86_cpu_apic_id_from_index(max_cpus - 1) + 1;
+}
+
+static FWCfgState *bochs_bios_init(void)
+{
+ FWCfgState *fw_cfg;
+ uint8_t *smbios_tables, *smbios_anchor;
+ size_t smbios_tables_len, smbios_anchor_len;
+ uint64_t *numa_fw_cfg;
+ int i, j;
+ unsigned int apic_id_limit = pc_apic_id_limit(max_cpus);
+
+ fw_cfg = fw_cfg_init_io(BIOS_CFG_IOPORT);
+ /* FW_CFG_MAX_CPUS is a bit confusing/problematic on x86:
+ *
+ * SeaBIOS needs FW_CFG_MAX_CPUS for CPU hotplug, but the CPU hotplug
+ * QEMU<->SeaBIOS interface is not based on the "CPU index", but on the APIC
+ * ID of hotplugged CPUs[1]. This means that FW_CFG_MAX_CPUS is not the
+ * "maximum number of CPUs", but the "limit to the APIC ID values SeaBIOS
+ * may see".
+ *
+ * So, this means we must not use max_cpus, here, but the maximum possible
+ * APIC ID value, plus one.
+ *
+ * [1] The only kind of "CPU identifier" used between SeaBIOS and QEMU is
+ * the APIC ID, not the "CPU index"
+ */
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, (uint16_t)apic_id_limit);
+ fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size);
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_ACPI_TABLES,
+ acpi_tables, acpi_tables_len);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_IRQ0_OVERRIDE, kvm_allows_irq0_override());
+
+ smbios_tables = smbios_get_table_legacy(&smbios_tables_len);
+ if (smbios_tables) {
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_SMBIOS_ENTRIES,
+ smbios_tables, smbios_tables_len);
+ }
+
+ smbios_get_tables(&smbios_tables, &smbios_tables_len,
+ &smbios_anchor, &smbios_anchor_len);
+ if (smbios_anchor) {
+ fw_cfg_add_file(fw_cfg, "etc/smbios/smbios-tables",
+ smbios_tables, smbios_tables_len);
+ fw_cfg_add_file(fw_cfg, "etc/smbios/smbios-anchor",
+ smbios_anchor, smbios_anchor_len);
+ }
+
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_E820_TABLE,
+ &e820_reserve, sizeof(e820_reserve));
+ fw_cfg_add_file(fw_cfg, "etc/e820", e820_table,
+ sizeof(struct e820_entry) * e820_entries);
+
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, &hpet_cfg, sizeof(hpet_cfg));
+ /* allocate memory for the NUMA channel: one (64bit) word for the number
+ * of nodes, one word for each VCPU->node and one word for each node to
+ * hold the amount of memory.
+ */
+ numa_fw_cfg = g_new0(uint64_t, 1 + apic_id_limit + nb_numa_nodes);
+ numa_fw_cfg[0] = cpu_to_le64(nb_numa_nodes);
+ for (i = 0; i < max_cpus; i++) {
+ unsigned int apic_id = x86_cpu_apic_id_from_index(i);
+ assert(apic_id < apic_id_limit);
+ for (j = 0; j < nb_numa_nodes; j++) {
+ if (test_bit(i, numa_info[j].node_cpu)) {
+ numa_fw_cfg[apic_id + 1] = cpu_to_le64(j);
+ break;
+ }
+ }
+ }
+ for (i = 0; i < nb_numa_nodes; i++) {
+ numa_fw_cfg[apic_id_limit + 1 + i] = cpu_to_le64(numa_info[i].node_mem);
+ }
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_NUMA, numa_fw_cfg,
+ (1 + apic_id_limit + nb_numa_nodes) *
+ sizeof(*numa_fw_cfg));
+
+ return fw_cfg;
+}
+
+static long get_file_size(FILE *f)
+{
+ long where, size;
+
+ /* XXX: on Unix systems, using fstat() probably makes more sense */
+
+ where = ftell(f);
+ fseek(f, 0, SEEK_END);
+ size = ftell(f);
+ fseek(f, where, SEEK_SET);
+
+ return size;
+}
+
+static void load_linux(FWCfgState *fw_cfg,
+ const char *kernel_filename,
+ const char *initrd_filename,
+ const char *kernel_cmdline,
+ hwaddr max_ram_size)
+{
+ uint16_t protocol;
+ int setup_size, kernel_size, initrd_size = 0, cmdline_size;
+ uint32_t initrd_max;
+ uint8_t header[8192], *setup, *kernel, *initrd_data;
+ hwaddr real_addr, prot_addr, cmdline_addr, initrd_addr = 0;
+ FILE *f;
+ char *vmode;
+
+ /* Align to 16 bytes as a paranoia measure */
+ cmdline_size = (strlen(kernel_cmdline)+16) & ~15;
+
+ /* load the kernel header */
+ f = fopen(kernel_filename, "rb");
+ if (!f || !(kernel_size = get_file_size(f)) ||
+ fread(header, 1, MIN(ARRAY_SIZE(header), kernel_size), f) !=
+ MIN(ARRAY_SIZE(header), kernel_size)) {
+ fprintf(stderr, "qemu: could not load kernel '%s': %s\n",
+ kernel_filename, strerror(errno));
+ exit(1);
+ }
+
+ /* kernel protocol version */
+#if 0
+ fprintf(stderr, "header magic: %#x\n", ldl_p(header+0x202));
+#endif
+ if (ldl_p(header+0x202) == 0x53726448) {
+ protocol = lduw_p(header+0x206);
+ } else {
+ /* This looks like a multiboot kernel. If it is, let's stop
+ treating it like a Linux kernel. */
+ if (load_multiboot(fw_cfg, f, kernel_filename, initrd_filename,
+ kernel_cmdline, kernel_size, header)) {
+ return;
+ }
+ protocol = 0;
+ }
+
+ if (protocol < 0x200 || !(header[0x211] & 0x01)) {
+ /* Low kernel */
+ real_addr = 0x90000;
+ cmdline_addr = 0x9a000 - cmdline_size;
+ prot_addr = 0x10000;
+ } else if (protocol < 0x202) {
+ /* High but ancient kernel */
+ real_addr = 0x90000;
+ cmdline_addr = 0x9a000 - cmdline_size;
+ prot_addr = 0x100000;
+ } else {
+ /* High and recent kernel */
+ real_addr = 0x10000;
+ cmdline_addr = 0x20000;
+ prot_addr = 0x100000;
+ }
+
+#if 0
+ fprintf(stderr,
+ "qemu: real_addr = 0x" TARGET_FMT_plx "\n"
+ "qemu: cmdline_addr = 0x" TARGET_FMT_plx "\n"
+ "qemu: prot_addr = 0x" TARGET_FMT_plx "\n",
+ real_addr,
+ cmdline_addr,
+ prot_addr);
+#endif
+
+ /* highest address for loading the initrd */
+ if (protocol >= 0x203) {
+ initrd_max = ldl_p(header+0x22c);
+ } else {
+ initrd_max = 0x37ffffff;
+ }
+
+ if (initrd_max >= max_ram_size - acpi_data_size) {
+ initrd_max = max_ram_size - acpi_data_size - 1;
+ }
+
+ fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_ADDR, cmdline_addr);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, strlen(kernel_cmdline)+1);
+ fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, kernel_cmdline);
+
+ if (protocol >= 0x202) {
+ stl_p(header+0x228, cmdline_addr);
+ } else {
+ stw_p(header+0x20, 0xA33F);
+ stw_p(header+0x22, cmdline_addr-real_addr);
+ }
+
+ /* handle vga= parameter */
+ vmode = strstr(kernel_cmdline, "vga=");
+ if (vmode) {
+ unsigned int video_mode;
+ /* skip "vga=" */
+ vmode += 4;
+ if (!strncmp(vmode, "normal", 6)) {
+ video_mode = 0xffff;
+ } else if (!strncmp(vmode, "ext", 3)) {
+ video_mode = 0xfffe;
+ } else if (!strncmp(vmode, "ask", 3)) {
+ video_mode = 0xfffd;
+ } else {
+ video_mode = strtol(vmode, NULL, 0);
+ }
+ stw_p(header+0x1fa, video_mode);
+ }
+
+ /* loader type */
+ /* High nybble = B reserved for QEMU; low nybble is revision number.
+ If this code is substantially changed, you may want to consider
+ incrementing the revision. */
+ if (protocol >= 0x200) {
+ header[0x210] = 0xB0;
+ }
+ /* heap */
+ if (protocol >= 0x201) {
+ header[0x211] |= 0x80; /* CAN_USE_HEAP */
+ stw_p(header+0x224, cmdline_addr-real_addr-0x200);
+ }
+
+ /* load initrd */
+ if (initrd_filename) {
+ if (protocol < 0x200) {
+ fprintf(stderr, "qemu: linux kernel too old to load a ram disk\n");
+ exit(1);
+ }
+
+ initrd_size = get_image_size(initrd_filename);
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: error reading initrd %s: %s\n",
+ initrd_filename, strerror(errno));
+ exit(1);
+ }
+
+ initrd_addr = (initrd_max-initrd_size) & ~4095;
+
+ initrd_data = g_malloc(initrd_size);
+ load_image(initrd_filename, initrd_data);
+
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, initrd_addr);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, initrd_size);
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_INITRD_DATA, initrd_data, initrd_size);
+
+ stl_p(header+0x218, initrd_addr);
+ stl_p(header+0x21c, initrd_size);
+ }
+
+ /* load kernel and setup */
+ setup_size = header[0x1f1];
+ if (setup_size == 0) {
+ setup_size = 4;
+ }
+ setup_size = (setup_size+1)*512;
+ kernel_size -= setup_size;
+
+ setup = g_malloc(setup_size);
+ kernel = g_malloc(kernel_size);
+ fseek(f, 0, SEEK_SET);
+ if (fread(setup, 1, setup_size, f) != setup_size) {
+ fprintf(stderr, "fread() failed\n");
+ exit(1);
+ }
+ if (fread(kernel, 1, kernel_size, f) != kernel_size) {
+ fprintf(stderr, "fread() failed\n");
+ exit(1);
+ }
+ fclose(f);
+ memcpy(setup, header, MIN(sizeof(header), setup_size));
+
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, prot_addr);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_SIZE, kernel_size);
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_KERNEL_DATA, kernel, kernel_size);
+
+ fw_cfg_add_i32(fw_cfg, FW_CFG_SETUP_ADDR, real_addr);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_SETUP_SIZE, setup_size);
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_SETUP_DATA, setup, setup_size);
+
+ option_rom[nb_option_roms].name = "linuxboot.bin";
+ option_rom[nb_option_roms].bootindex = 0;
+ nb_option_roms++;
+}
+
+#define NE2000_NB_MAX 6
+
+static const int ne2000_io[NE2000_NB_MAX] = { 0x300, 0x320, 0x340, 0x360,
+ 0x280, 0x380 };
+static const int ne2000_irq[NE2000_NB_MAX] = { 9, 10, 11, 3, 4, 5 };
+
+void pc_init_ne2k_isa(ISABus *bus, NICInfo *nd)
+{
+ static int nb_ne2k = 0;
+
+ if (nb_ne2k == NE2000_NB_MAX)
+ return;
+ isa_ne2000_init(bus, ne2000_io[nb_ne2k],
+ ne2000_irq[nb_ne2k], nd);
+ nb_ne2k++;
+}
+
+DeviceState *cpu_get_current_apic(void)
+{
+ if (current_cpu) {
+ X86CPU *cpu = X86_CPU(current_cpu);
+ return cpu->apic_state;
+ } else {
+ return NULL;
+ }
+}
+
+void pc_acpi_smi_interrupt(void *opaque, int irq, int level)
+{
+ X86CPU *cpu = opaque;
+
+ if (level) {
+ cpu_interrupt(CPU(cpu), CPU_INTERRUPT_SMI);
+ }
+}
+
+static X86CPU *pc_new_cpu(const char *cpu_model, int64_t apic_id,
+ DeviceState *icc_bridge, Error **errp)
+{
+ X86CPU *cpu = NULL;
+ Error *local_err = NULL;
+
+ if (icc_bridge == NULL) {
+ error_setg(&local_err, "Invalid icc-bridge value");
+ goto out;
+ }
+
+ cpu = cpu_x86_create(cpu_model, &local_err);
+ if (local_err != NULL) {
+ goto out;
+ }
+
+ qdev_set_parent_bus(DEVICE(cpu), qdev_get_child_bus(icc_bridge, "icc"));
+
+ object_property_set_int(OBJECT(cpu), apic_id, "apic-id", &local_err);
+ object_property_set_bool(OBJECT(cpu), true, "realized", &local_err);
+
+out:
+ if (local_err) {
+ error_propagate(errp, local_err);
+ object_unref(OBJECT(cpu));
+ cpu = NULL;
+ }
+ return cpu;
+}
+
+static const char *current_cpu_model;
+
+void pc_hot_add_cpu(const int64_t id, Error **errp)
+{
+ DeviceState *icc_bridge;
+ X86CPU *cpu;
+ int64_t apic_id = x86_cpu_apic_id_from_index(id);
+ Error *local_err = NULL;
+
+ if (id < 0) {
+ error_setg(errp, "Invalid CPU id: %" PRIi64, id);
+ return;
+ }
+
+ if (cpu_exists(apic_id)) {
+ error_setg(errp, "Unable to add CPU: %" PRIi64
+ ", it already exists", id);
+ return;
+ }
+
+ if (id >= max_cpus) {
+ error_setg(errp, "Unable to add CPU: %" PRIi64
+ ", max allowed: %d", id, max_cpus - 1);
+ return;
+ }
+
+ if (apic_id >= ACPI_CPU_HOTPLUG_ID_LIMIT) {
+ error_setg(errp, "Unable to add CPU: %" PRIi64
+ ", resulting APIC ID (%" PRIi64 ") is too large",
+ id, apic_id);
+ return;
+ }
+
+ icc_bridge = DEVICE(object_resolve_path_type("icc-bridge",
+ TYPE_ICC_BRIDGE, NULL));
+ cpu = pc_new_cpu(current_cpu_model, apic_id, icc_bridge, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ object_unref(OBJECT(cpu));
+}
+
+void pc_cpus_init(const char *cpu_model, DeviceState *icc_bridge)
+{
+ int i;
+ X86CPU *cpu = NULL;
+ Error *error = NULL;
+ unsigned long apic_id_limit;
+
+ /* init CPUs */
+ if (cpu_model == NULL) {
+#ifdef TARGET_X86_64
+ cpu_model = "qemu64";
+#else
+ cpu_model = "qemu32";
+#endif
+ }
+ current_cpu_model = cpu_model;
+
+ apic_id_limit = pc_apic_id_limit(max_cpus);
+ if (apic_id_limit > ACPI_CPU_HOTPLUG_ID_LIMIT) {
+ error_report("max_cpus is too large. APIC ID of last CPU is %lu",
+ apic_id_limit - 1);
+ exit(1);
+ }
+
+ for (i = 0; i < smp_cpus; i++) {
+ cpu = pc_new_cpu(cpu_model, x86_cpu_apic_id_from_index(i),
+ icc_bridge, &error);
+ if (error) {
+ error_report_err(error);
+ exit(1);
+ }
+ object_unref(OBJECT(cpu));
+ }
+
+ /* map APIC MMIO area if CPU has APIC */
+ if (cpu && cpu->apic_state) {
+ /* XXX: what if the base changes? */
+ sysbus_mmio_map_overlap(SYS_BUS_DEVICE(icc_bridge), 0,
+ APIC_DEFAULT_ADDRESS, 0x1000);
+ }
+
+ /* tell smbios about cpuid version and features */
+ smbios_set_cpuid(cpu->env.cpuid_version, cpu->env.features[FEAT_1_EDX]);
+}
+
+/* pci-info ROM file. Little endian format */
+typedef struct PcRomPciInfo {
+ uint64_t w32_min;
+ uint64_t w32_max;
+ uint64_t w64_min;
+ uint64_t w64_max;
+} PcRomPciInfo;
+
+typedef struct PcGuestInfoState {
+ PcGuestInfo info;
+ Notifier machine_done;
+} PcGuestInfoState;
+
+static
+void pc_guest_info_machine_done(Notifier *notifier, void *data)
+{
+ PcGuestInfoState *guest_info_state = container_of(notifier,
+ PcGuestInfoState,
+ machine_done);
+ PCIBus *bus = find_i440fx();
+
+ if (bus) {
+ int extra_hosts = 0;
+
+ QLIST_FOREACH(bus, &bus->child, sibling) {
+ /* look for expander root buses */
+ if (pci_bus_is_root(bus)) {
+ extra_hosts++;
+ }
+ }
+ if (extra_hosts && guest_info_state->info.fw_cfg) {
+ uint64_t *val = g_malloc(sizeof(*val));
+ *val = cpu_to_le64(extra_hosts);
+ fw_cfg_add_file(guest_info_state->info.fw_cfg,
+ "etc/extra-pci-roots", val, sizeof(*val));
+ }
+ }
+
+ acpi_setup(&guest_info_state->info);
+}
+
+PcGuestInfo *pc_guest_info_init(ram_addr_t below_4g_mem_size,
+ ram_addr_t above_4g_mem_size)
+{
+ PcGuestInfoState *guest_info_state = g_malloc0(sizeof *guest_info_state);
+ PcGuestInfo *guest_info = &guest_info_state->info;
+ int i, j;
+
+ guest_info->ram_size_below_4g = below_4g_mem_size;
+ guest_info->ram_size = below_4g_mem_size + above_4g_mem_size;
+ guest_info->apic_id_limit = pc_apic_id_limit(max_cpus);
+ guest_info->apic_xrupt_override = kvm_allows_irq0_override();
+ guest_info->numa_nodes = nb_numa_nodes;
+ guest_info->node_mem = g_malloc0(guest_info->numa_nodes *
+ sizeof *guest_info->node_mem);
+ for (i = 0; i < nb_numa_nodes; i++) {
+ guest_info->node_mem[i] = numa_info[i].node_mem;
+ }
+
+ guest_info->node_cpu = g_malloc0(guest_info->apic_id_limit *
+ sizeof *guest_info->node_cpu);
+
+ for (i = 0; i < max_cpus; i++) {
+ unsigned int apic_id = x86_cpu_apic_id_from_index(i);
+ assert(apic_id < guest_info->apic_id_limit);
+ for (j = 0; j < nb_numa_nodes; j++) {
+ if (test_bit(i, numa_info[j].node_cpu)) {
+ guest_info->node_cpu[apic_id] = j;
+ break;
+ }
+ }
+ }
+
+ guest_info_state->machine_done.notify = pc_guest_info_machine_done;
+ qemu_add_machine_init_done_notifier(&guest_info_state->machine_done);
+ return guest_info;
+}
+
+/* setup pci memory address space mapping into system address space */
+void pc_pci_as_mapping_init(Object *owner, MemoryRegion *system_memory,
+ MemoryRegion *pci_address_space)
+{
+ /* Set to lower priority than RAM */
+ memory_region_add_subregion_overlap(system_memory, 0x0,
+ pci_address_space, -1);
+}
+
+void pc_acpi_init(const char *default_dsdt)
+{
+ char *filename;
+
+ if (acpi_tables != NULL) {
+ /* manually set via -acpitable, leave it alone */
+ return;
+ }
+
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, default_dsdt);
+ if (filename == NULL) {
+ fprintf(stderr, "WARNING: failed to find %s\n", default_dsdt);
+ } else {
+ QemuOpts *opts = qemu_opts_create(qemu_find_opts("acpi"), NULL, 0,
+ &error_abort);
+ Error *err = NULL;
+
+ qemu_opt_set(opts, "file", filename, &error_abort);
+
+ acpi_table_add_builtin(opts, &err);
+ if (err) {
+ error_report("WARNING: failed to load %s: %s", filename,
+ error_get_pretty(err));
+ error_free(err);
+ }
+ g_free(filename);
+ }
+}
+
+FWCfgState *xen_load_linux(const char *kernel_filename,
+ const char *kernel_cmdline,
+ const char *initrd_filename,
+ ram_addr_t below_4g_mem_size,
+ PcGuestInfo *guest_info)
+{
+ int i;
+ FWCfgState *fw_cfg;
+
+ assert(kernel_filename != NULL);
+
+ fw_cfg = fw_cfg_init_io(BIOS_CFG_IOPORT);
+ rom_set_fw(fw_cfg);
+
+ load_linux(fw_cfg, kernel_filename, initrd_filename,
+ kernel_cmdline, below_4g_mem_size);
+ for (i = 0; i < nb_option_roms; i++) {
+ assert(!strcmp(option_rom[i].name, "linuxboot.bin") ||
+ !strcmp(option_rom[i].name, "multiboot.bin"));
+ rom_add_option(option_rom[i].name, option_rom[i].bootindex);
+ }
+ guest_info->fw_cfg = fw_cfg;
+ return fw_cfg;
+}
+
+FWCfgState *pc_memory_init(MachineState *machine,
+ MemoryRegion *system_memory,
+ ram_addr_t below_4g_mem_size,
+ ram_addr_t above_4g_mem_size,
+ MemoryRegion *rom_memory,
+ MemoryRegion **ram_memory,
+ PcGuestInfo *guest_info)
+{
+ int linux_boot, i;
+ MemoryRegion *ram, *option_rom_mr;
+ MemoryRegion *ram_below_4g, *ram_above_4g;
+ FWCfgState *fw_cfg;
+ PCMachineState *pcms = PC_MACHINE(machine);
+
+ assert(machine->ram_size == below_4g_mem_size + above_4g_mem_size);
+
+ linux_boot = (machine->kernel_filename != NULL);
+
+ /* Allocate RAM. We allocate it as a single memory region and use
+ * aliases to address portions of it, mostly for backwards compatibility
+ * with older qemus that used qemu_ram_alloc().
+ */
+ ram = g_malloc(sizeof(*ram));
+ memory_region_allocate_system_memory(ram, NULL, "pc.ram",
+ machine->ram_size);
+ *ram_memory = ram;
+ ram_below_4g = g_malloc(sizeof(*ram_below_4g));
+ memory_region_init_alias(ram_below_4g, NULL, "ram-below-4g", ram,
+ 0, below_4g_mem_size);
+ memory_region_add_subregion(system_memory, 0, ram_below_4g);
+ e820_add_entry(0, below_4g_mem_size, E820_RAM);
+ if (above_4g_mem_size > 0) {
+ ram_above_4g = g_malloc(sizeof(*ram_above_4g));
+ memory_region_init_alias(ram_above_4g, NULL, "ram-above-4g", ram,
+ below_4g_mem_size, above_4g_mem_size);
+ memory_region_add_subregion(system_memory, 0x100000000ULL,
+ ram_above_4g);
+ e820_add_entry(0x100000000ULL, above_4g_mem_size, E820_RAM);
+ }
+
+ if (!guest_info->has_reserved_memory &&
+ (machine->ram_slots ||
+ (machine->maxram_size > machine->ram_size))) {
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+
+ error_report("\"-memory 'slots|maxmem'\" is not supported by: %s",
+ mc->name);
+ exit(EXIT_FAILURE);
+ }
+
+ /* initialize hotplug memory address space */
+ if (guest_info->has_reserved_memory &&
+ (machine->ram_size < machine->maxram_size)) {
+ ram_addr_t hotplug_mem_size =
+ machine->maxram_size - machine->ram_size;
+
+ if (machine->ram_slots > ACPI_MAX_RAM_SLOTS) {
+ error_report("unsupported amount of memory slots: %"PRIu64,
+ machine->ram_slots);
+ exit(EXIT_FAILURE);
+ }
+
+ if (QEMU_ALIGN_UP(machine->maxram_size,
+ TARGET_PAGE_SIZE) != machine->maxram_size) {
+ error_report("maximum memory size must by aligned to multiple of "
+ "%d bytes", TARGET_PAGE_SIZE);
+ exit(EXIT_FAILURE);
+ }
+
+ pcms->hotplug_memory.base =
+ ROUND_UP(0x100000000ULL + above_4g_mem_size, 1ULL << 30);
+
+ if (pcms->enforce_aligned_dimm) {
+ /* size hotplug region assuming 1G page max alignment per slot */
+ hotplug_mem_size += (1ULL << 30) * machine->ram_slots;
+ }
+
+ if ((pcms->hotplug_memory.base + hotplug_mem_size) <
+ hotplug_mem_size) {
+ error_report("unsupported amount of maximum memory: " RAM_ADDR_FMT,
+ machine->maxram_size);
+ exit(EXIT_FAILURE);
+ }
+
+ memory_region_init(&pcms->hotplug_memory.mr, OBJECT(pcms),
+ "hotplug-memory", hotplug_mem_size);
+ memory_region_add_subregion(system_memory, pcms->hotplug_memory.base,
+ &pcms->hotplug_memory.mr);
+ }
+
+ /* Initialize PC system firmware */
+ pc_system_firmware_init(rom_memory, guest_info->isapc_ram_fw);
+
+ option_rom_mr = g_malloc(sizeof(*option_rom_mr));
+ memory_region_init_ram(option_rom_mr, NULL, "pc.rom", PC_ROM_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(option_rom_mr);
+ memory_region_add_subregion_overlap(rom_memory,
+ PC_ROM_MIN_VGA,
+ option_rom_mr,
+ 1);
+
+ fw_cfg = bochs_bios_init();
+ rom_set_fw(fw_cfg);
+
+ if (guest_info->has_reserved_memory && pcms->hotplug_memory.base) {
+ uint64_t *val = g_malloc(sizeof(*val));
+ *val = cpu_to_le64(ROUND_UP(pcms->hotplug_memory.base, 0x1ULL << 30));
+ fw_cfg_add_file(fw_cfg, "etc/reserved-memory-end", val, sizeof(*val));
+ }
+
+ if (linux_boot) {
+ load_linux(fw_cfg, machine->kernel_filename, machine->initrd_filename,
+ machine->kernel_cmdline, below_4g_mem_size);
+ }
+
+ for (i = 0; i < nb_option_roms; i++) {
+ rom_add_option(option_rom[i].name, option_rom[i].bootindex);
+ }
+ guest_info->fw_cfg = fw_cfg;
+ return fw_cfg;
+}
+
+qemu_irq pc_allocate_cpu_irq(void)
+{
+ return qemu_allocate_irq(pic_irq_request, NULL, 0);
+}
+
+DeviceState *pc_vga_init(ISABus *isa_bus, PCIBus *pci_bus)
+{
+ DeviceState *dev = NULL;
+
+ if (pci_bus) {
+ PCIDevice *pcidev = pci_vga_init(pci_bus);
+ dev = pcidev ? &pcidev->qdev : NULL;
+ } else if (isa_bus) {
+ ISADevice *isadev = isa_vga_init(isa_bus);
+ dev = isadev ? DEVICE(isadev) : NULL;
+ }
+ return dev;
+}
+
+static void cpu_request_exit(void *opaque, int irq, int level)
+{
+ CPUState *cpu = current_cpu;
+
+ if (cpu && level) {
+ cpu_exit(cpu);
+ }
+}
+
+static const MemoryRegionOps ioport80_io_ops = {
+ .write = ioport80_write,
+ .read = ioport80_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps ioportF0_io_ops = {
+ .write = ioportF0_write,
+ .read = ioportF0_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+void pc_basic_device_init(ISABus *isa_bus, qemu_irq *gsi,
+ ISADevice **rtc_state,
+ bool create_fdctrl,
+ bool no_vmport,
+ uint32 hpet_irqs)
+{
+ int i;
+ DriveInfo *fd[MAX_FD];
+ DeviceState *hpet = NULL;
+ int pit_isa_irq = 0;
+ qemu_irq pit_alt_irq = NULL;
+ qemu_irq rtc_irq = NULL;
+ qemu_irq *a20_line;
+ ISADevice *i8042, *port92, *vmmouse, *pit = NULL;
+ qemu_irq *cpu_exit_irq;
+ MemoryRegion *ioport80_io = g_new(MemoryRegion, 1);
+ MemoryRegion *ioportF0_io = g_new(MemoryRegion, 1);
+
+ memory_region_init_io(ioport80_io, NULL, &ioport80_io_ops, NULL, "ioport80", 1);
+ memory_region_add_subregion(isa_bus->address_space_io, 0x80, ioport80_io);
+
+ memory_region_init_io(ioportF0_io, NULL, &ioportF0_io_ops, NULL, "ioportF0", 1);
+ memory_region_add_subregion(isa_bus->address_space_io, 0xf0, ioportF0_io);
+
+ /*
+ * Check if an HPET shall be created.
+ *
+ * Without KVM_CAP_PIT_STATE2, we cannot switch off the in-kernel PIT
+ * when the HPET wants to take over. Thus we have to disable the latter.
+ */
+ if (!no_hpet && (!kvm_irqchip_in_kernel() || kvm_has_pit_state2())) {
+ /* In order to set property, here not using sysbus_try_create_simple */
+ hpet = qdev_try_create(NULL, TYPE_HPET);
+ if (hpet) {
+ /* For pc-piix-*, hpet's intcap is always IRQ2. For pc-q35-1.7
+ * and earlier, use IRQ2 for compat. Otherwise, use IRQ16~23,
+ * IRQ8 and IRQ2.
+ */
+ uint8_t compat = object_property_get_int(OBJECT(hpet),
+ HPET_INTCAP, NULL);
+ if (!compat) {
+ qdev_prop_set_uint32(hpet, HPET_INTCAP, hpet_irqs);
+ }
+ qdev_init_nofail(hpet);
+ sysbus_mmio_map(SYS_BUS_DEVICE(hpet), 0, HPET_BASE);
+
+ for (i = 0; i < GSI_NUM_PINS; i++) {
+ sysbus_connect_irq(SYS_BUS_DEVICE(hpet), i, gsi[i]);
+ }
+ pit_isa_irq = -1;
+ pit_alt_irq = qdev_get_gpio_in(hpet, HPET_LEGACY_PIT_INT);
+ rtc_irq = qdev_get_gpio_in(hpet, HPET_LEGACY_RTC_INT);
+ }
+ }
+ *rtc_state = rtc_init(isa_bus, 2000, rtc_irq);
+
+ qemu_register_boot_set(pc_boot_set, *rtc_state);
+
+ if (!xen_enabled()) {
+ if (kvm_irqchip_in_kernel()) {
+ pit = kvm_pit_init(isa_bus, 0x40);
+ } else {
+ pit = pit_init(isa_bus, 0x40, pit_isa_irq, pit_alt_irq);
+ }
+ if (hpet) {
+ /* connect PIT to output control line of the HPET */
+ qdev_connect_gpio_out(hpet, 0, qdev_get_gpio_in(DEVICE(pit), 0));
+ }
+ pcspk_init(isa_bus, pit);
+ }
+
+ serial_hds_isa_init(isa_bus, MAX_SERIAL_PORTS);
+ parallel_hds_isa_init(isa_bus, MAX_PARALLEL_PORTS);
+
+ a20_line = qemu_allocate_irqs(handle_a20_line_change, first_cpu, 2);
+ i8042 = isa_create_simple(isa_bus, "i8042");
+ i8042_setup_a20_line(i8042, &a20_line[0]);
+ if (!no_vmport) {
+ vmport_init(isa_bus);
+ vmmouse = isa_try_create(isa_bus, "vmmouse");
+ } else {
+ vmmouse = NULL;
+ }
+ if (vmmouse) {
+ DeviceState *dev = DEVICE(vmmouse);
+ qdev_prop_set_ptr(dev, "ps2_mouse", i8042);
+ qdev_init_nofail(dev);
+ }
+ port92 = isa_create_simple(isa_bus, "port92");
+ port92_init(port92, &a20_line[1]);
+
+ cpu_exit_irq = qemu_allocate_irqs(cpu_request_exit, NULL, 1);
+ DMA_init(0, cpu_exit_irq);
+
+ for(i = 0; i < MAX_FD; i++) {
+ fd[i] = drive_get(IF_FLOPPY, 0, i);
+ create_fdctrl |= !!fd[i];
+ }
+ if (create_fdctrl) {
+ fdctrl_init_isa(isa_bus, fd);
+ }
+}
+
+void pc_nic_init(ISABus *isa_bus, PCIBus *pci_bus)
+{
+ int i;
+
+ for (i = 0; i < nb_nics; i++) {
+ NICInfo *nd = &nd_table[i];
+
+ if (!pci_bus || (nd->model && strcmp(nd->model, "ne2k_isa") == 0)) {
+ pc_init_ne2k_isa(isa_bus, nd);
+ } else {
+ pci_nic_init_nofail(nd, pci_bus, "e1000", NULL);
+ }
+ }
+}
+
+void pc_pci_device_init(PCIBus *pci_bus)
+{
+ int max_bus;
+ int bus;
+
+ max_bus = drive_get_max_bus(IF_SCSI);
+ for (bus = 0; bus <= max_bus; bus++) {
+ pci_create_simple(pci_bus, -1, "lsi53c895a");
+ }
+}
+
+void ioapic_init_gsi(GSIState *gsi_state, const char *parent_name)
+{
+ DeviceState *dev;
+ SysBusDevice *d;
+ unsigned int i;
+
+ if (kvm_irqchip_in_kernel()) {
+ dev = qdev_create(NULL, "kvm-ioapic");
+ } else {
+ dev = qdev_create(NULL, "ioapic");
+ }
+ if (parent_name) {
+ object_property_add_child(object_resolve_path(parent_name, NULL),
+ "ioapic", OBJECT(dev), NULL);
+ }
+ qdev_init_nofail(dev);
+ d = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(d, 0, IO_APIC_DEFAULT_ADDRESS);
+
+ for (i = 0; i < IOAPIC_NUM_PINS; i++) {
+ gsi_state->ioapic_irq[i] = qdev_get_gpio_in(dev, i);
+ }
+}
+
+static void pc_dimm_plug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ HotplugHandlerClass *hhc;
+ Error *local_err = NULL;
+ PCMachineState *pcms = PC_MACHINE(hotplug_dev);
+ PCDIMMDevice *dimm = PC_DIMM(dev);
+ PCDIMMDeviceClass *ddc = PC_DIMM_GET_CLASS(dimm);
+ MemoryRegion *mr = ddc->get_memory_region(dimm);
+ uint64_t align = TARGET_PAGE_SIZE;
+
+ if (memory_region_get_alignment(mr) && pcms->enforce_aligned_dimm) {
+ align = memory_region_get_alignment(mr);
+ }
+
+ if (!pcms->acpi_dev) {
+ error_setg(&local_err,
+ "memory hotplug is not enabled: missing acpi device");
+ goto out;
+ }
+
+ pc_dimm_memory_plug(dev, &pcms->hotplug_memory, mr, align, &local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ hhc = HOTPLUG_HANDLER_GET_CLASS(pcms->acpi_dev);
+ hhc->plug(HOTPLUG_HANDLER(pcms->acpi_dev), dev, &error_abort);
+out:
+ error_propagate(errp, local_err);
+}
+
+static void pc_dimm_unplug_request(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ HotplugHandlerClass *hhc;
+ Error *local_err = NULL;
+ PCMachineState *pcms = PC_MACHINE(hotplug_dev);
+
+ if (!pcms->acpi_dev) {
+ error_setg(&local_err,
+ "memory hotplug is not enabled: missing acpi device");
+ goto out;
+ }
+
+ hhc = HOTPLUG_HANDLER_GET_CLASS(pcms->acpi_dev);
+ hhc->unplug_request(HOTPLUG_HANDLER(pcms->acpi_dev), dev, &local_err);
+
+out:
+ error_propagate(errp, local_err);
+}
+
+static void pc_dimm_unplug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(hotplug_dev);
+ PCDIMMDevice *dimm = PC_DIMM(dev);
+ PCDIMMDeviceClass *ddc = PC_DIMM_GET_CLASS(dimm);
+ MemoryRegion *mr = ddc->get_memory_region(dimm);
+ HotplugHandlerClass *hhc;
+ Error *local_err = NULL;
+
+ hhc = HOTPLUG_HANDLER_GET_CLASS(pcms->acpi_dev);
+ hhc->unplug(HOTPLUG_HANDLER(pcms->acpi_dev), dev, &local_err);
+
+ if (local_err) {
+ goto out;
+ }
+
+ pc_dimm_memory_unplug(dev, &pcms->hotplug_memory, mr);
+ object_unparent(OBJECT(dev));
+
+ out:
+ error_propagate(errp, local_err);
+}
+
+static void pc_cpu_plug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ HotplugHandlerClass *hhc;
+ Error *local_err = NULL;
+ PCMachineState *pcms = PC_MACHINE(hotplug_dev);
+
+ if (!dev->hotplugged) {
+ goto out;
+ }
+
+ if (!pcms->acpi_dev) {
+ error_setg(&local_err,
+ "cpu hotplug is not enabled: missing acpi device");
+ goto out;
+ }
+
+ hhc = HOTPLUG_HANDLER_GET_CLASS(pcms->acpi_dev);
+ hhc->plug(HOTPLUG_HANDLER(pcms->acpi_dev), dev, &local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ /* increment the number of CPUs */
+ rtc_set_memory(pcms->rtc, 0x5f, rtc_get_memory(pcms->rtc, 0x5f) + 1);
+out:
+ error_propagate(errp, local_err);
+}
+
+static void pc_machine_device_plug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ pc_dimm_plug(hotplug_dev, dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
+ pc_cpu_plug(hotplug_dev, dev, errp);
+ }
+}
+
+static void pc_machine_device_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ pc_dimm_unplug_request(hotplug_dev, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug request for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void pc_machine_device_unplug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ pc_dimm_unplug(hotplug_dev, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static HotplugHandler *pc_get_hotpug_handler(MachineState *machine,
+ DeviceState *dev)
+{
+ PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(machine);
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM) ||
+ object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
+ return HOTPLUG_HANDLER(machine);
+ }
+
+ return pcmc->get_hotplug_handler ?
+ pcmc->get_hotplug_handler(machine, dev) : NULL;
+}
+
+static void
+pc_machine_get_hotplug_memory_region_size(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+ int64_t value = memory_region_size(&pcms->hotplug_memory.mr);
+
+ visit_type_int(v, &value, name, errp);
+}
+
+static void pc_machine_get_max_ram_below_4g(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+ uint64_t value = pcms->max_ram_below_4g;
+
+ visit_type_size(v, &value, name, errp);
+}
+
+static void pc_machine_set_max_ram_below_4g(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+ Error *error = NULL;
+ uint64_t value;
+
+ visit_type_size(v, &value, name, &error);
+ if (error) {
+ error_propagate(errp, error);
+ return;
+ }
+ if (value > (1ULL << 32)) {
+ error_set(&error, ERROR_CLASS_GENERIC_ERROR,
+ "Machine option 'max-ram-below-4g=%"PRIu64
+ "' expects size less than or equal to 4G", value);
+ error_propagate(errp, error);
+ return;
+ }
+
+ if (value < (1ULL << 20)) {
+ error_report("Warning: small max_ram_below_4g(%"PRIu64
+ ") less than 1M. BIOS may not work..",
+ value);
+ }
+
+ pcms->max_ram_below_4g = value;
+}
+
+static void pc_machine_get_vmport(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+ OnOffAuto vmport = pcms->vmport;
+
+ visit_type_OnOffAuto(v, &vmport, name, errp);
+}
+
+static void pc_machine_set_vmport(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+
+ visit_type_OnOffAuto(v, &pcms->vmport, name, errp);
+}
+
+bool pc_machine_is_smm_enabled(PCMachineState *pcms)
+{
+ bool smm_available = false;
+
+ if (pcms->smm == ON_OFF_AUTO_OFF) {
+ return false;
+ }
+
+ if (tcg_enabled() || qtest_enabled()) {
+ smm_available = true;
+ } else if (kvm_enabled()) {
+ smm_available = kvm_has_smm();
+ }
+
+ if (smm_available) {
+ return true;
+ }
+
+ if (pcms->smm == ON_OFF_AUTO_ON) {
+ error_report("System Management Mode not supported by this hypervisor.");
+ exit(1);
+ }
+ return false;
+}
+
+static void pc_machine_get_smm(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+ OnOffAuto smm = pcms->smm;
+
+ visit_type_OnOffAuto(v, &smm, name, errp);
+}
+
+static void pc_machine_set_smm(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+
+ visit_type_OnOffAuto(v, &pcms->smm, name, errp);
+}
+
+static bool pc_machine_get_aligned_dimm(Object *obj, Error **errp)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+
+ return pcms->enforce_aligned_dimm;
+}
+
+static void pc_machine_initfn(Object *obj)
+{
+ PCMachineState *pcms = PC_MACHINE(obj);
+
+ object_property_add(obj, PC_MACHINE_MEMHP_REGION_SIZE, "int",
+ pc_machine_get_hotplug_memory_region_size,
+ NULL, NULL, NULL, NULL);
+
+ pcms->max_ram_below_4g = 1ULL << 32; /* 4G */
+ object_property_add(obj, PC_MACHINE_MAX_RAM_BELOW_4G, "size",
+ pc_machine_get_max_ram_below_4g,
+ pc_machine_set_max_ram_below_4g,
+ NULL, NULL, NULL);
+ object_property_set_description(obj, PC_MACHINE_MAX_RAM_BELOW_4G,
+ "Maximum ram below the 4G boundary (32bit boundary)",
+ NULL);
+
+ pcms->smm = ON_OFF_AUTO_AUTO;
+ object_property_add(obj, PC_MACHINE_SMM, "OnOffAuto",
+ pc_machine_get_smm,
+ pc_machine_set_smm,
+ NULL, NULL, NULL);
+ object_property_set_description(obj, PC_MACHINE_SMM,
+ "Enable SMM (pc & q35)",
+ NULL);
+
+ pcms->vmport = ON_OFF_AUTO_AUTO;
+ object_property_add(obj, PC_MACHINE_VMPORT, "OnOffAuto",
+ pc_machine_get_vmport,
+ pc_machine_set_vmport,
+ NULL, NULL, NULL);
+ object_property_set_description(obj, PC_MACHINE_VMPORT,
+ "Enable vmport (pc & q35)",
+ NULL);
+
+ pcms->enforce_aligned_dimm = true;
+ object_property_add_bool(obj, PC_MACHINE_ENFORCE_ALIGNED_DIMM,
+ pc_machine_get_aligned_dimm,
+ NULL, NULL);
+}
+
+static unsigned pc_cpu_index_to_socket_id(unsigned cpu_index)
+{
+ unsigned pkg_id, core_id, smt_id;
+ x86_topo_ids_from_idx(smp_cores, smp_threads, cpu_index,
+ &pkg_id, &core_id, &smt_id);
+ return pkg_id;
+}
+
+static void pc_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ PCMachineClass *pcmc = PC_MACHINE_CLASS(oc);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc);
+
+ pcmc->get_hotplug_handler = mc->get_hotplug_handler;
+ mc->get_hotplug_handler = pc_get_hotpug_handler;
+ mc->cpu_index_to_socket_id = pc_cpu_index_to_socket_id;
+ hc->plug = pc_machine_device_plug_cb;
+ hc->unplug_request = pc_machine_device_unplug_request_cb;
+ hc->unplug = pc_machine_device_unplug_cb;
+}
+
+static const TypeInfo pc_machine_info = {
+ .name = TYPE_PC_MACHINE,
+ .parent = TYPE_MACHINE,
+ .abstract = true,
+ .instance_size = sizeof(PCMachineState),
+ .instance_init = pc_machine_initfn,
+ .class_size = sizeof(PCMachineClass),
+ .class_init = pc_machine_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ },
+};
+
+static void pc_machine_register_types(void)
+{
+ type_register_static(&pc_machine_info);
+}
+
+type_init(pc_machine_register_types)
diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
new file mode 100644
index 00000000..a896624f
--- /dev/null
+++ b/hw/i386/pc_piix.c
@@ -0,0 +1,933 @@
+/*
+ * QEMU PC System Emulator
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include <glib.h>
+
+#include "hw/hw.h"
+#include "hw/loader.h"
+#include "hw/i386/pc.h"
+#include "hw/i386/apic.h"
+#include "hw/i386/smbios.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_ids.h"
+#include "hw/usb.h"
+#include "net/net.h"
+#include "hw/boards.h"
+#include "hw/ide.h"
+#include "sysemu/kvm.h"
+#include "hw/kvm/clock.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+#include "hw/cpu/icc_bus.h"
+#include "sysemu/arch_init.h"
+#include "sysemu/block-backend.h"
+#include "hw/i2c/smbus.h"
+#include "hw/xen/xen.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+#include "hw/acpi/acpi.h"
+#include "cpu.h"
+#include "qemu/error-report.h"
+#ifdef CONFIG_XEN
+# include <xen/hvm/hvm_info_table.h>
+#endif
+#include "migration/migration.h"
+
+#define MAX_IDE_BUS 2
+
+static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 };
+static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 };
+static const int ide_irq[MAX_IDE_BUS] = { 14, 15 };
+
+static bool pci_enabled = true;
+static bool has_acpi_build = true;
+static bool rsdp_in_ram = true;
+static int legacy_acpi_table_size;
+static bool smbios_defaults = true;
+static bool smbios_legacy_mode;
+static bool smbios_uuid_encoded = true;
+/* Make sure that guest addresses aligned at 1Gbyte boundaries get mapped to
+ * host addresses aligned at 1Gbyte boundaries. This way we can use 1GByte
+ * pages in the host.
+ */
+static bool gigabyte_align = true;
+static bool has_reserved_memory = true;
+static bool kvmclock_enabled = true;
+
+/* PC hardware initialisation */
+static void pc_init1(MachineState *machine)
+{
+ PCMachineState *pc_machine = PC_MACHINE(machine);
+ MemoryRegion *system_memory = get_system_memory();
+ MemoryRegion *system_io = get_system_io();
+ int i;
+ ram_addr_t below_4g_mem_size, above_4g_mem_size;
+ PCIBus *pci_bus;
+ ISABus *isa_bus;
+ PCII440FXState *i440fx_state;
+ int piix3_devfn = -1;
+ qemu_irq *gsi;
+ qemu_irq *i8259;
+ qemu_irq smi_irq;
+ GSIState *gsi_state;
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+ BusState *idebus[MAX_IDE_BUS];
+ ISADevice *rtc_state;
+ MemoryRegion *ram_memory;
+ MemoryRegion *pci_memory;
+ MemoryRegion *rom_memory;
+ DeviceState *icc_bridge;
+ PcGuestInfo *guest_info;
+ ram_addr_t lowmem;
+
+ /* Check whether RAM fits below 4G (leaving 1/2 GByte for IO memory).
+ * If it doesn't, we need to split it in chunks below and above 4G.
+ * In any case, try to make sure that guest addresses aligned at
+ * 1G boundaries get mapped to host addresses aligned at 1G boundaries.
+ * For old machine types, use whatever split we used historically to avoid
+ * breaking migration.
+ */
+ if (machine->ram_size >= 0xe0000000) {
+ lowmem = gigabyte_align ? 0xc0000000 : 0xe0000000;
+ } else {
+ lowmem = 0xe0000000;
+ }
+
+ /* Handle the machine opt max-ram-below-4g. It is basically doing
+ * min(qemu limit, user limit).
+ */
+ if (lowmem > pc_machine->max_ram_below_4g) {
+ lowmem = pc_machine->max_ram_below_4g;
+ if (machine->ram_size - lowmem > lowmem &&
+ lowmem & ((1ULL << 30) - 1)) {
+ error_report("Warning: Large machine and max_ram_below_4g(%"PRIu64
+ ") not a multiple of 1G; possible bad performance.",
+ pc_machine->max_ram_below_4g);
+ }
+ }
+
+ if (machine->ram_size >= lowmem) {
+ above_4g_mem_size = machine->ram_size - lowmem;
+ below_4g_mem_size = lowmem;
+ } else {
+ above_4g_mem_size = 0;
+ below_4g_mem_size = machine->ram_size;
+ }
+
+ if (xen_enabled() && xen_hvm_init(&below_4g_mem_size, &above_4g_mem_size,
+ &ram_memory) != 0) {
+ fprintf(stderr, "xen hardware virtual machine initialisation failed\n");
+ exit(1);
+ }
+
+ icc_bridge = qdev_create(NULL, TYPE_ICC_BRIDGE);
+ object_property_add_child(qdev_get_machine(), "icc-bridge",
+ OBJECT(icc_bridge), NULL);
+
+ pc_cpus_init(machine->cpu_model, icc_bridge);
+
+ if (kvm_enabled() && kvmclock_enabled) {
+ kvmclock_create();
+ }
+
+ if (pci_enabled) {
+ pci_memory = g_new(MemoryRegion, 1);
+ memory_region_init(pci_memory, NULL, "pci", UINT64_MAX);
+ rom_memory = pci_memory;
+ } else {
+ pci_memory = NULL;
+ rom_memory = system_memory;
+ }
+
+ guest_info = pc_guest_info_init(below_4g_mem_size, above_4g_mem_size);
+
+ guest_info->has_acpi_build = has_acpi_build;
+ guest_info->legacy_acpi_table_size = legacy_acpi_table_size;
+
+ guest_info->isapc_ram_fw = !pci_enabled;
+ guest_info->has_reserved_memory = has_reserved_memory;
+ guest_info->rsdp_in_ram = rsdp_in_ram;
+
+ if (smbios_defaults) {
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+ /* These values are guest ABI, do not change */
+ smbios_set_defaults("QEMU", "Standard PC (i440FX + PIIX, 1996)",
+ mc->name, smbios_legacy_mode, smbios_uuid_encoded);
+ }
+
+ /* allocate ram and load rom/bios */
+ if (!xen_enabled()) {
+ pc_memory_init(machine, system_memory,
+ below_4g_mem_size, above_4g_mem_size,
+ rom_memory, &ram_memory, guest_info);
+ } else if (machine->kernel_filename != NULL) {
+ /* For xen HVM direct kernel boot, load linux here */
+ xen_load_linux(machine->kernel_filename,
+ machine->kernel_cmdline,
+ machine->initrd_filename,
+ below_4g_mem_size,
+ guest_info);
+ }
+
+ gsi_state = g_malloc0(sizeof(*gsi_state));
+ if (kvm_irqchip_in_kernel()) {
+ kvm_pc_setup_irq_routing(pci_enabled);
+ gsi = qemu_allocate_irqs(kvm_pc_gsi_handler, gsi_state,
+ GSI_NUM_PINS);
+ } else {
+ gsi = qemu_allocate_irqs(gsi_handler, gsi_state, GSI_NUM_PINS);
+ }
+
+ if (pci_enabled) {
+ pci_bus = i440fx_init(&i440fx_state, &piix3_devfn, &isa_bus, gsi,
+ system_memory, system_io, machine->ram_size,
+ below_4g_mem_size,
+ above_4g_mem_size,
+ pci_memory, ram_memory);
+ } else {
+ pci_bus = NULL;
+ i440fx_state = NULL;
+ isa_bus = isa_bus_new(NULL, get_system_memory(), system_io);
+ no_hpet = 1;
+ }
+ isa_bus_irqs(isa_bus, gsi);
+
+ if (kvm_irqchip_in_kernel()) {
+ i8259 = kvm_i8259_init(isa_bus);
+ } else if (xen_enabled()) {
+ i8259 = xen_interrupt_controller_init();
+ } else {
+ i8259 = i8259_init(isa_bus, pc_allocate_cpu_irq());
+ }
+
+ for (i = 0; i < ISA_NUM_IRQS; i++) {
+ gsi_state->i8259_irq[i] = i8259[i];
+ }
+ g_free(i8259);
+ if (pci_enabled) {
+ ioapic_init_gsi(gsi_state, "i440fx");
+ }
+ qdev_init_nofail(icc_bridge);
+
+ pc_register_ferr_irq(gsi[13]);
+
+ pc_vga_init(isa_bus, pci_enabled ? pci_bus : NULL);
+
+ assert(pc_machine->vmport != ON_OFF_AUTO_MAX);
+ if (pc_machine->vmport == ON_OFF_AUTO_AUTO) {
+ pc_machine->vmport = xen_enabled() ? ON_OFF_AUTO_OFF : ON_OFF_AUTO_ON;
+ }
+
+ /* init basic PC hardware */
+ pc_basic_device_init(isa_bus, gsi, &rtc_state, true,
+ (pc_machine->vmport != ON_OFF_AUTO_ON), 0x4);
+
+ pc_nic_init(isa_bus, pci_bus);
+
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+ if (pci_enabled) {
+ PCIDevice *dev;
+ if (xen_enabled()) {
+ dev = pci_piix3_xen_ide_init(pci_bus, hd, piix3_devfn + 1);
+ } else {
+ dev = pci_piix3_ide_init(pci_bus, hd, piix3_devfn + 1);
+ }
+ idebus[0] = qdev_get_child_bus(&dev->qdev, "ide.0");
+ idebus[1] = qdev_get_child_bus(&dev->qdev, "ide.1");
+ } else {
+ for(i = 0; i < MAX_IDE_BUS; i++) {
+ ISADevice *dev;
+ char busname[] = "ide.0";
+ dev = isa_ide_init(isa_bus, ide_iobase[i], ide_iobase2[i],
+ ide_irq[i],
+ hd[MAX_IDE_DEVS * i], hd[MAX_IDE_DEVS * i + 1]);
+ /*
+ * The ide bus name is ide.0 for the first bus and ide.1 for the
+ * second one.
+ */
+ busname[4] = '0' + i;
+ idebus[i] = qdev_get_child_bus(DEVICE(dev), busname);
+ }
+ }
+
+ pc_cmos_init(below_4g_mem_size, above_4g_mem_size, machine->boot_order,
+ machine, idebus[0], idebus[1], rtc_state);
+
+ if (pci_enabled && usb_enabled()) {
+ pci_create_simple(pci_bus, piix3_devfn + 2, "piix3-usb-uhci");
+ }
+
+ if (pci_enabled && acpi_enabled) {
+ DeviceState *piix4_pm;
+ I2CBus *smbus;
+
+ smi_irq = qemu_allocate_irq(pc_acpi_smi_interrupt, first_cpu, 0);
+ /* TODO: Populate SPD eeprom data. */
+ smbus = piix4_pm_init(pci_bus, piix3_devfn + 3, 0xb100,
+ gsi[9], smi_irq,
+ pc_machine_is_smm_enabled(pc_machine),
+ &piix4_pm);
+ smbus_eeprom_init(smbus, 8, NULL, 0);
+
+ object_property_add_link(OBJECT(machine), PC_MACHINE_ACPI_DEVICE_PROP,
+ TYPE_HOTPLUG_HANDLER,
+ (Object **)&pc_machine->acpi_dev,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE, &error_abort);
+ object_property_set_link(OBJECT(machine), OBJECT(piix4_pm),
+ PC_MACHINE_ACPI_DEVICE_PROP, &error_abort);
+ }
+
+ if (pci_enabled) {
+ pc_pci_device_init(pci_bus);
+ }
+}
+
+static void pc_compat_2_3(MachineState *machine)
+{
+ PCMachineState *pcms = PC_MACHINE(machine);
+ savevm_skip_section_footers();
+ if (kvm_enabled()) {
+ pcms->smm = ON_OFF_AUTO_OFF;
+ }
+ global_state_set_optional();
+ savevm_skip_configuration();
+}
+
+static void pc_compat_2_2(MachineState *machine)
+{
+ pc_compat_2_3(machine);
+ rsdp_in_ram = false;
+ x86_cpu_compat_set_features("kvm64", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("kvm32", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Conroe", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Penryn", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Nehalem", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Westmere", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("SandyBridge", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Haswell", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Broadwell", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G1", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G2", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G3", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G4", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G5", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Haswell", FEAT_1_ECX, 0, CPUID_EXT_F16C);
+ x86_cpu_compat_set_features("Haswell", FEAT_1_ECX, 0, CPUID_EXT_RDRAND);
+ x86_cpu_compat_set_features("Broadwell", FEAT_1_ECX, 0, CPUID_EXT_F16C);
+ x86_cpu_compat_set_features("Broadwell", FEAT_1_ECX, 0, CPUID_EXT_RDRAND);
+ machine->suppress_vmdesc = true;
+}
+
+static void pc_compat_2_1(MachineState *machine)
+{
+ PCMachineState *pcms = PC_MACHINE(machine);
+
+ pc_compat_2_2(machine);
+ smbios_uuid_encoded = false;
+ x86_cpu_compat_set_features("coreduo", FEAT_1_ECX, CPUID_EXT_VMX, 0);
+ x86_cpu_compat_set_features("core2duo", FEAT_1_ECX, CPUID_EXT_VMX, 0);
+ x86_cpu_compat_kvm_no_autodisable(FEAT_8000_0001_ECX, CPUID_EXT3_SVM);
+ pcms->enforce_aligned_dimm = false;
+}
+
+static void pc_compat_2_0(MachineState *machine)
+{
+ pc_compat_2_1(machine);
+ /* This value depends on the actual DSDT and SSDT compiled into
+ * the source QEMU; unfortunately it depends on the binary and
+ * not on the machine type, so we cannot make pc-i440fx-1.7 work on
+ * both QEMU 1.7 and QEMU 2.0.
+ *
+ * Large variations cause migration to fail for more than one
+ * consecutive value of the "-smp" maxcpus option.
+ *
+ * For small variations of the kind caused by different iasl versions,
+ * the 4k rounding usually leaves slack. However, there could be still
+ * one or two values that break. For QEMU 1.7 and QEMU 2.0 the
+ * slack is only ~10 bytes before one "-smp maxcpus" value breaks!
+ *
+ * 6652 is valid for QEMU 2.0, the right value for pc-i440fx-1.7 on
+ * QEMU 1.7 it is 6414. For RHEL/CentOS 7.0 it is 6418.
+ */
+ legacy_acpi_table_size = 6652;
+ smbios_legacy_mode = true;
+ has_reserved_memory = false;
+ pc_set_legacy_acpi_data_size();
+}
+
+static void pc_compat_1_7(MachineState *machine)
+{
+ pc_compat_2_0(machine);
+ smbios_defaults = false;
+ gigabyte_align = false;
+ option_rom_has_mr = true;
+ legacy_acpi_table_size = 6414;
+ x86_cpu_compat_kvm_no_autoenable(FEAT_1_ECX, CPUID_EXT_X2APIC);
+}
+
+static void pc_compat_1_6(MachineState *machine)
+{
+ pc_compat_1_7(machine);
+ rom_file_has_mr = false;
+ has_acpi_build = false;
+}
+
+static void pc_compat_1_5(MachineState *machine)
+{
+ pc_compat_1_6(machine);
+}
+
+static void pc_compat_1_4(MachineState *machine)
+{
+ pc_compat_1_5(machine);
+ x86_cpu_compat_set_features("n270", FEAT_1_ECX, 0, CPUID_EXT_MOVBE);
+ x86_cpu_compat_set_features("Westmere", FEAT_1_ECX, 0, CPUID_EXT_PCLMULQDQ);
+}
+
+static void pc_compat_1_3(MachineState *machine)
+{
+ pc_compat_1_4(machine);
+ enable_compat_apic_id_mode();
+}
+
+/* PC compat function for pc-0.14 to pc-1.2 */
+static void pc_compat_1_2(MachineState *machine)
+{
+ pc_compat_1_3(machine);
+ x86_cpu_compat_kvm_no_autoenable(FEAT_KVM, 1 << KVM_FEATURE_PV_EOI);
+}
+
+/* PC compat function for pc-0.10 to pc-0.13 */
+static void pc_compat_0_13(MachineState *machine)
+{
+ pc_compat_1_2(machine);
+ kvmclock_enabled = false;
+}
+
+static void pc_init_isa(MachineState *machine)
+{
+ pci_enabled = false;
+ has_acpi_build = false;
+ smbios_defaults = false;
+ gigabyte_align = false;
+ smbios_legacy_mode = true;
+ has_reserved_memory = false;
+ option_rom_has_mr = true;
+ rom_file_has_mr = false;
+ if (!machine->cpu_model) {
+ machine->cpu_model = "486";
+ }
+ x86_cpu_compat_kvm_no_autoenable(FEAT_KVM, 1 << KVM_FEATURE_PV_EOI);
+ enable_compat_apic_id_mode();
+ pc_init1(machine);
+}
+
+#ifdef CONFIG_XEN
+static void pc_xen_hvm_init(MachineState *machine)
+{
+ PCIBus *bus;
+
+ pc_init1(machine);
+
+ bus = pci_find_primary_bus();
+ if (bus != NULL) {
+ pci_create_simple(bus, -1, "xen-platform");
+ }
+}
+#endif
+
+#define DEFINE_I440FX_MACHINE(suffix, name, compatfn, optionfn) \
+ static void pc_init_##suffix(MachineState *machine) \
+ { \
+ void (*compat)(MachineState *m) = (compatfn); \
+ if (compat) { \
+ compat(machine); \
+ } \
+ pc_init1(machine); \
+ } \
+ DEFINE_PC_MACHINE(suffix, name, pc_init_##suffix, optionfn)
+
+static void pc_i440fx_machine_options(MachineClass *m)
+{
+ pc_default_machine_options(m);
+ m->family = "pc_piix";
+ m->desc = "Standard PC (i440FX + PIIX, 1996)";
+ m->hot_add_cpu = pc_hot_add_cpu;
+}
+
+static void pc_i440fx_2_4_machine_options(MachineClass *m)
+{
+ pc_i440fx_machine_options(m);
+ m->default_machine_opts = "firmware=bios-256k.bin";
+ m->default_display = "std";
+ m->alias = "pc";
+ m->is_default = 1;
+}
+
+DEFINE_I440FX_MACHINE(v2_4, "pc-i440fx-2.4", NULL,
+ pc_i440fx_2_4_machine_options)
+
+
+static void pc_i440fx_2_3_machine_options(MachineClass *m)
+{
+ pc_i440fx_2_4_machine_options(m);
+ m->alias = NULL;
+ m->is_default = 0;
+ SET_MACHINE_COMPAT(m, PC_COMPAT_2_3);
+}
+
+DEFINE_I440FX_MACHINE(v2_3, "pc-i440fx-2.3", pc_compat_2_3,
+ pc_i440fx_2_3_machine_options);
+
+
+static void pc_i440fx_2_2_machine_options(MachineClass *m)
+{
+ pc_i440fx_2_3_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_2_2);
+}
+
+DEFINE_I440FX_MACHINE(v2_2, "pc-i440fx-2.2", pc_compat_2_2,
+ pc_i440fx_2_2_machine_options);
+
+
+static void pc_i440fx_2_1_machine_options(MachineClass *m)
+{
+ pc_i440fx_2_2_machine_options(m);
+ m->default_display = NULL;
+ SET_MACHINE_COMPAT(m, PC_COMPAT_2_1);
+}
+
+DEFINE_I440FX_MACHINE(v2_1, "pc-i440fx-2.1", pc_compat_2_1,
+ pc_i440fx_2_1_machine_options);
+
+
+
+static void pc_i440fx_2_0_machine_options(MachineClass *m)
+{
+ pc_i440fx_2_1_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_2_0);
+}
+
+DEFINE_I440FX_MACHINE(v2_0, "pc-i440fx-2.0", pc_compat_2_0,
+ pc_i440fx_2_0_machine_options);
+
+
+static void pc_i440fx_1_7_machine_options(MachineClass *m)
+{
+ pc_i440fx_2_0_machine_options(m);
+ m->default_machine_opts = NULL;
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_7);
+}
+
+DEFINE_I440FX_MACHINE(v1_7, "pc-i440fx-1.7", pc_compat_1_7,
+ pc_i440fx_1_7_machine_options);
+
+
+static void pc_i440fx_1_6_machine_options(MachineClass *m)
+{
+ pc_i440fx_1_7_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_6);
+}
+
+DEFINE_I440FX_MACHINE(v1_6, "pc-i440fx-1.6", pc_compat_1_6,
+ pc_i440fx_1_6_machine_options);
+
+
+static void pc_i440fx_1_5_machine_options(MachineClass *m)
+{
+ pc_i440fx_1_6_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_5);
+}
+
+DEFINE_I440FX_MACHINE(v1_5, "pc-i440fx-1.5", pc_compat_1_5,
+ pc_i440fx_1_5_machine_options);
+
+
+static void pc_i440fx_1_4_machine_options(MachineClass *m)
+{
+ pc_i440fx_1_5_machine_options(m);
+ m->hot_add_cpu = NULL;
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_4);
+}
+
+DEFINE_I440FX_MACHINE(v1_4, "pc-i440fx-1.4", pc_compat_1_4,
+ pc_i440fx_1_4_machine_options);
+
+
+#define PC_COMPAT_1_3 \
+ PC_COMPAT_1_4 \
+ {\
+ .driver = "usb-tablet",\
+ .property = "usb_version",\
+ .value = stringify(1),\
+ },{\
+ .driver = "virtio-net-pci",\
+ .property = "ctrl_mac_addr",\
+ .value = "off", \
+ },{ \
+ .driver = "virtio-net-pci", \
+ .property = "mq", \
+ .value = "off", \
+ }, {\
+ .driver = "e1000",\
+ .property = "autonegotiation",\
+ .value = "off",\
+ },
+
+
+static void pc_i440fx_1_3_machine_options(MachineClass *m)
+{
+ pc_i440fx_1_4_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_3);
+}
+
+DEFINE_I440FX_MACHINE(v1_3, "pc-1.3", pc_compat_1_3,
+ pc_i440fx_1_3_machine_options);
+
+
+#define PC_COMPAT_1_2 \
+ PC_COMPAT_1_3 \
+ {\
+ .driver = "nec-usb-xhci",\
+ .property = "msi",\
+ .value = "off",\
+ },{\
+ .driver = "nec-usb-xhci",\
+ .property = "msix",\
+ .value = "off",\
+ },{\
+ .driver = "ivshmem",\
+ .property = "use64",\
+ .value = "0",\
+ },{\
+ .driver = "qxl",\
+ .property = "revision",\
+ .value = stringify(3),\
+ },{\
+ .driver = "qxl-vga",\
+ .property = "revision",\
+ .value = stringify(3),\
+ },{\
+ .driver = "VGA",\
+ .property = "mmio",\
+ .value = "off",\
+ },
+
+static void pc_i440fx_1_2_machine_options(MachineClass *m)
+{
+ pc_i440fx_1_3_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_2);
+}
+
+DEFINE_I440FX_MACHINE(v1_2, "pc-1.2", pc_compat_1_2,
+ pc_i440fx_1_2_machine_options);
+
+
+#define PC_COMPAT_1_1 \
+ PC_COMPAT_1_2 \
+ {\
+ .driver = "virtio-scsi-pci",\
+ .property = "hotplug",\
+ .value = "off",\
+ },{\
+ .driver = "virtio-scsi-pci",\
+ .property = "param_change",\
+ .value = "off",\
+ },{\
+ .driver = "VGA",\
+ .property = "vgamem_mb",\
+ .value = stringify(8),\
+ },{\
+ .driver = "vmware-svga",\
+ .property = "vgamem_mb",\
+ .value = stringify(8),\
+ },{\
+ .driver = "qxl-vga",\
+ .property = "vgamem_mb",\
+ .value = stringify(8),\
+ },{\
+ .driver = "qxl",\
+ .property = "vgamem_mb",\
+ .value = stringify(8),\
+ },{\
+ .driver = "virtio-blk-pci",\
+ .property = "config-wce",\
+ .value = "off",\
+ },
+
+static void pc_i440fx_1_1_machine_options(MachineClass *m)
+{
+ pc_i440fx_1_2_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_1);
+}
+
+DEFINE_I440FX_MACHINE(v1_1, "pc-1.1", pc_compat_1_2,
+ pc_i440fx_1_1_machine_options);
+
+
+#define PC_COMPAT_1_0 \
+ PC_COMPAT_1_1 \
+ {\
+ .driver = TYPE_ISA_FDC,\
+ .property = "check_media_rate",\
+ .value = "off",\
+ }, {\
+ .driver = "virtio-balloon-pci",\
+ .property = "class",\
+ .value = stringify(PCI_CLASS_MEMORY_RAM),\
+ },{\
+ .driver = "apic-common",\
+ .property = "vapic",\
+ .value = "off",\
+ },{\
+ .driver = TYPE_USB_DEVICE,\
+ .property = "full-path",\
+ .value = "no",\
+ },
+
+static void pc_i440fx_1_0_machine_options(MachineClass *m)
+{
+ pc_i440fx_1_1_machine_options(m);
+ m->hw_version = "1.0";
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_0);
+}
+
+DEFINE_I440FX_MACHINE(v1_0, "pc-1.0", pc_compat_1_2,
+ pc_i440fx_1_0_machine_options);
+
+
+#define PC_COMPAT_0_15 \
+ PC_COMPAT_1_0
+
+static void pc_i440fx_0_15_machine_options(MachineClass *m)
+{
+ pc_i440fx_1_0_machine_options(m);
+ m->hw_version = "0.15";
+ SET_MACHINE_COMPAT(m, PC_COMPAT_0_15);
+}
+
+DEFINE_I440FX_MACHINE(v0_15, "pc-0.15", pc_compat_1_2,
+ pc_i440fx_0_15_machine_options);
+
+
+#define PC_COMPAT_0_14 \
+ PC_COMPAT_0_15 \
+ {\
+ .driver = "virtio-blk-pci",\
+ .property = "event_idx",\
+ .value = "off",\
+ },{\
+ .driver = "virtio-serial-pci",\
+ .property = "event_idx",\
+ .value = "off",\
+ },{\
+ .driver = "virtio-net-pci",\
+ .property = "event_idx",\
+ .value = "off",\
+ },{\
+ .driver = "virtio-balloon-pci",\
+ .property = "event_idx",\
+ .value = "off",\
+ },{\
+ .driver = "qxl",\
+ .property = "revision",\
+ .value = stringify(2),\
+ },{\
+ .driver = "qxl-vga",\
+ .property = "revision",\
+ .value = stringify(2),\
+ },
+
+static void pc_i440fx_0_14_machine_options(MachineClass *m)
+{
+ pc_i440fx_0_15_machine_options(m);
+ m->hw_version = "0.14";
+ SET_MACHINE_COMPAT(m, PC_COMPAT_0_14);
+}
+
+DEFINE_I440FX_MACHINE(v0_14, "pc-0.14", pc_compat_1_2,
+ pc_i440fx_0_14_machine_options);
+
+
+#define PC_COMPAT_0_13 \
+ PC_COMPAT_0_14 \
+ {\
+ .driver = TYPE_PCI_DEVICE,\
+ .property = "command_serr_enable",\
+ .value = "off",\
+ },{\
+ .driver = "AC97",\
+ .property = "use_broken_id",\
+ .value = stringify(1),\
+ },{\
+ .driver = "virtio-9p-pci",\
+ .property = "vectors",\
+ .value = stringify(0),\
+ },{\
+ .driver = "VGA",\
+ .property = "rombar",\
+ .value = stringify(0),\
+ },{\
+ .driver = "vmware-svga",\
+ .property = "rombar",\
+ .value = stringify(0),\
+ },
+
+static void pc_i440fx_0_13_machine_options(MachineClass *m)
+{
+ pc_i440fx_0_14_machine_options(m);
+ m->hw_version = "0.13";
+ SET_MACHINE_COMPAT(m, PC_COMPAT_0_13);
+}
+
+DEFINE_I440FX_MACHINE(v0_13, "pc-0.13", pc_compat_0_13,
+ pc_i440fx_0_13_machine_options);
+
+
+#define PC_COMPAT_0_12 \
+ PC_COMPAT_0_13 \
+ {\
+ .driver = "virtio-serial-pci",\
+ .property = "max_ports",\
+ .value = stringify(1),\
+ },{\
+ .driver = "virtio-serial-pci",\
+ .property = "vectors",\
+ .value = stringify(0),\
+ },{\
+ .driver = "usb-mouse",\
+ .property = "serial",\
+ .value = "1",\
+ },{\
+ .driver = "usb-tablet",\
+ .property = "serial",\
+ .value = "1",\
+ },{\
+ .driver = "usb-kbd",\
+ .property = "serial",\
+ .value = "1",\
+ },
+
+static void pc_i440fx_0_12_machine_options(MachineClass *m)
+{
+ pc_i440fx_0_13_machine_options(m);
+ m->hw_version = "0.12";
+ SET_MACHINE_COMPAT(m, PC_COMPAT_0_12);
+}
+
+DEFINE_I440FX_MACHINE(v0_12, "pc-0.12", pc_compat_0_13,
+ pc_i440fx_0_12_machine_options);
+
+
+#define PC_COMPAT_0_11 \
+ PC_COMPAT_0_12 \
+ {\
+ .driver = "virtio-blk-pci",\
+ .property = "vectors",\
+ .value = stringify(0),\
+ },{\
+ .driver = TYPE_PCI_DEVICE,\
+ .property = "rombar",\
+ .value = stringify(0),\
+ },{\
+ .driver = "ide-drive",\
+ .property = "ver",\
+ .value = "0.11",\
+ },{\
+ .driver = "scsi-disk",\
+ .property = "ver",\
+ .value = "0.11",\
+ },
+
+static void pc_i440fx_0_11_machine_options(MachineClass *m)
+{
+ pc_i440fx_0_12_machine_options(m);
+ m->hw_version = "0.11";
+ SET_MACHINE_COMPAT(m, PC_COMPAT_0_11);
+}
+
+DEFINE_I440FX_MACHINE(v0_11, "pc-0.11", pc_compat_0_13,
+ pc_i440fx_0_11_machine_options);
+
+
+#define PC_COMPAT_0_10 \
+ PC_COMPAT_0_11 \
+ {\
+ .driver = "virtio-blk-pci",\
+ .property = "class",\
+ .value = stringify(PCI_CLASS_STORAGE_OTHER),\
+ },{\
+ .driver = "virtio-serial-pci",\
+ .property = "class",\
+ .value = stringify(PCI_CLASS_DISPLAY_OTHER),\
+ },{\
+ .driver = "virtio-net-pci",\
+ .property = "vectors",\
+ .value = stringify(0),\
+ },{\
+ .driver = "ide-drive",\
+ .property = "ver",\
+ .value = "0.10",\
+ },{\
+ .driver = "scsi-disk",\
+ .property = "ver",\
+ .value = "0.10",\
+ },
+
+static void pc_i440fx_0_10_machine_options(MachineClass *m)
+{
+ pc_i440fx_0_11_machine_options(m);
+ m->hw_version = "0.10";
+ SET_MACHINE_COMPAT(m, PC_COMPAT_0_10);
+}
+
+DEFINE_I440FX_MACHINE(v0_10, "pc-0.10", pc_compat_0_13,
+ pc_i440fx_0_10_machine_options);
+
+
+static void isapc_machine_options(MachineClass *m)
+{
+ pc_common_machine_options(m);
+ m->desc = "ISA-only PC";
+ m->max_cpus = 1;
+}
+
+DEFINE_PC_MACHINE(isapc, "isapc", pc_init_isa,
+ isapc_machine_options);
+
+
+#ifdef CONFIG_XEN
+static void xenfv_machine_options(MachineClass *m)
+{
+ pc_common_machine_options(m);
+ m->desc = "Xen Fully-virtualized PC";
+ m->max_cpus = HVM_MAX_VCPUS;
+ m->default_machine_opts = "accel=xen";
+ m->hot_add_cpu = pc_hot_add_cpu;
+}
+
+DEFINE_PC_MACHINE(xenfv, "xenfv", pc_xen_hvm_init,
+ xenfv_machine_options);
+#endif
diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c
new file mode 100644
index 00000000..974aead5
--- /dev/null
+++ b/hw/i386/pc_q35.c
@@ -0,0 +1,492 @@
+/*
+ * Q35 chipset based pc system emulator
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2009, 2010
+ * Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This is based on pc.c, but heavily modified.
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/loader.h"
+#include "sysemu/arch_init.h"
+#include "hw/i2c/smbus.h"
+#include "hw/boards.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/xen/xen.h"
+#include "sysemu/kvm.h"
+#include "hw/kvm/clock.h"
+#include "hw/pci-host/q35.h"
+#include "exec/address-spaces.h"
+#include "hw/i386/ich9.h"
+#include "hw/i386/smbios.h"
+#include "hw/ide/pci.h"
+#include "hw/ide/ahci.h"
+#include "hw/usb.h"
+#include "hw/cpu/icc_bus.h"
+#include "qemu/error-report.h"
+#include "migration/migration.h"
+
+/* ICH9 AHCI has 6 ports */
+#define MAX_SATA_PORTS 6
+
+static bool has_acpi_build = true;
+static bool rsdp_in_ram = true;
+static bool smbios_defaults = true;
+static bool smbios_legacy_mode;
+static bool smbios_uuid_encoded = true;
+/* Make sure that guest addresses aligned at 1Gbyte boundaries get mapped to
+ * host addresses aligned at 1Gbyte boundaries. This way we can use 1GByte
+ * pages in the host.
+ */
+static bool gigabyte_align = true;
+static bool has_reserved_memory = true;
+
+/* PC hardware initialisation */
+static void pc_q35_init(MachineState *machine)
+{
+ PCMachineState *pc_machine = PC_MACHINE(machine);
+ ram_addr_t below_4g_mem_size, above_4g_mem_size;
+ Q35PCIHost *q35_host;
+ PCIHostState *phb;
+ PCIBus *host_bus;
+ PCIDevice *lpc;
+ BusState *idebus[MAX_SATA_PORTS];
+ ISADevice *rtc_state;
+ MemoryRegion *pci_memory;
+ MemoryRegion *rom_memory;
+ MemoryRegion *ram_memory;
+ GSIState *gsi_state;
+ ISABus *isa_bus;
+ int pci_enabled = 1;
+ qemu_irq *gsi;
+ qemu_irq *i8259;
+ int i;
+ ICH9LPCState *ich9_lpc;
+ PCIDevice *ahci;
+ DeviceState *icc_bridge;
+ PcGuestInfo *guest_info;
+ ram_addr_t lowmem;
+ DriveInfo *hd[MAX_SATA_PORTS];
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+
+ /* Check whether RAM fits below 4G (leaving 1/2 GByte for IO memory
+ * and 256 Mbytes for PCI Express Enhanced Configuration Access Mapping
+ * also known as MMCFG).
+ * If it doesn't, we need to split it in chunks below and above 4G.
+ * In any case, try to make sure that guest addresses aligned at
+ * 1G boundaries get mapped to host addresses aligned at 1G boundaries.
+ * For old machine types, use whatever split we used historically to avoid
+ * breaking migration.
+ */
+ if (machine->ram_size >= 0xb0000000) {
+ lowmem = gigabyte_align ? 0x80000000 : 0xb0000000;
+ } else {
+ lowmem = 0xb0000000;
+ }
+
+ /* Handle the machine opt max-ram-below-4g. It is basically doing
+ * min(qemu limit, user limit).
+ */
+ if (lowmem > pc_machine->max_ram_below_4g) {
+ lowmem = pc_machine->max_ram_below_4g;
+ if (machine->ram_size - lowmem > lowmem &&
+ lowmem & ((1ULL << 30) - 1)) {
+ error_report("Warning: Large machine and max_ram_below_4g(%"PRIu64
+ ") not a multiple of 1G; possible bad performance.",
+ pc_machine->max_ram_below_4g);
+ }
+ }
+
+ if (machine->ram_size >= lowmem) {
+ above_4g_mem_size = machine->ram_size - lowmem;
+ below_4g_mem_size = lowmem;
+ } else {
+ above_4g_mem_size = 0;
+ below_4g_mem_size = machine->ram_size;
+ }
+
+ if (xen_enabled() && xen_hvm_init(&below_4g_mem_size, &above_4g_mem_size,
+ &ram_memory) != 0) {
+ fprintf(stderr, "xen hardware virtual machine initialisation failed\n");
+ exit(1);
+ }
+
+ icc_bridge = qdev_create(NULL, TYPE_ICC_BRIDGE);
+ object_property_add_child(qdev_get_machine(), "icc-bridge",
+ OBJECT(icc_bridge), NULL);
+
+ pc_cpus_init(machine->cpu_model, icc_bridge);
+ pc_acpi_init("q35-acpi-dsdt.aml");
+
+ kvmclock_create();
+
+ /* pci enabled */
+ if (pci_enabled) {
+ pci_memory = g_new(MemoryRegion, 1);
+ memory_region_init(pci_memory, NULL, "pci", UINT64_MAX);
+ rom_memory = pci_memory;
+ } else {
+ pci_memory = NULL;
+ rom_memory = get_system_memory();
+ }
+
+ guest_info = pc_guest_info_init(below_4g_mem_size, above_4g_mem_size);
+ guest_info->isapc_ram_fw = false;
+ guest_info->has_acpi_build = has_acpi_build;
+ guest_info->has_reserved_memory = has_reserved_memory;
+ guest_info->rsdp_in_ram = rsdp_in_ram;
+
+ /* Migration was not supported in 2.0 for Q35, so do not bother
+ * with this hack (see hw/i386/acpi-build.c).
+ */
+ guest_info->legacy_acpi_table_size = 0;
+
+ if (smbios_defaults) {
+ /* These values are guest ABI, do not change */
+ smbios_set_defaults("QEMU", "Standard PC (Q35 + ICH9, 2009)",
+ mc->name, smbios_legacy_mode, smbios_uuid_encoded);
+ }
+
+ /* allocate ram and load rom/bios */
+ if (!xen_enabled()) {
+ pc_memory_init(machine, get_system_memory(),
+ below_4g_mem_size, above_4g_mem_size,
+ rom_memory, &ram_memory, guest_info);
+ }
+
+ /* irq lines */
+ gsi_state = g_malloc0(sizeof(*gsi_state));
+ if (kvm_irqchip_in_kernel()) {
+ kvm_pc_setup_irq_routing(pci_enabled);
+ gsi = qemu_allocate_irqs(kvm_pc_gsi_handler, gsi_state,
+ GSI_NUM_PINS);
+ } else {
+ gsi = qemu_allocate_irqs(gsi_handler, gsi_state, GSI_NUM_PINS);
+ }
+
+ /* create pci host bus */
+ q35_host = Q35_HOST_DEVICE(qdev_create(NULL, TYPE_Q35_HOST_DEVICE));
+
+ object_property_add_child(qdev_get_machine(), "q35", OBJECT(q35_host), NULL);
+ q35_host->mch.ram_memory = ram_memory;
+ q35_host->mch.pci_address_space = pci_memory;
+ q35_host->mch.system_memory = get_system_memory();
+ q35_host->mch.address_space_io = get_system_io();
+ q35_host->mch.below_4g_mem_size = below_4g_mem_size;
+ q35_host->mch.above_4g_mem_size = above_4g_mem_size;
+ q35_host->mch.guest_info = guest_info;
+ /* pci */
+ qdev_init_nofail(DEVICE(q35_host));
+ phb = PCI_HOST_BRIDGE(q35_host);
+ host_bus = phb->bus;
+ /* create ISA bus */
+ lpc = pci_create_simple_multifunction(host_bus, PCI_DEVFN(ICH9_LPC_DEV,
+ ICH9_LPC_FUNC), true,
+ TYPE_ICH9_LPC_DEVICE);
+
+ object_property_add_link(OBJECT(machine), PC_MACHINE_ACPI_DEVICE_PROP,
+ TYPE_HOTPLUG_HANDLER,
+ (Object **)&pc_machine->acpi_dev,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE, &error_abort);
+ object_property_set_link(OBJECT(machine), OBJECT(lpc),
+ PC_MACHINE_ACPI_DEVICE_PROP, &error_abort);
+
+ ich9_lpc = ICH9_LPC_DEVICE(lpc);
+ ich9_lpc->pic = gsi;
+ ich9_lpc->ioapic = gsi_state->ioapic_irq;
+ pci_bus_irqs(host_bus, ich9_lpc_set_irq, ich9_lpc_map_irq, ich9_lpc,
+ ICH9_LPC_NB_PIRQS);
+ pci_bus_set_route_irq_fn(host_bus, ich9_route_intx_pin_to_irq);
+ isa_bus = ich9_lpc->isa_bus;
+
+ /*end early*/
+ isa_bus_irqs(isa_bus, gsi);
+
+ if (kvm_irqchip_in_kernel()) {
+ i8259 = kvm_i8259_init(isa_bus);
+ } else if (xen_enabled()) {
+ i8259 = xen_interrupt_controller_init();
+ } else {
+ i8259 = i8259_init(isa_bus, pc_allocate_cpu_irq());
+ }
+
+ for (i = 0; i < ISA_NUM_IRQS; i++) {
+ gsi_state->i8259_irq[i] = i8259[i];
+ }
+ if (pci_enabled) {
+ ioapic_init_gsi(gsi_state, "q35");
+ }
+ qdev_init_nofail(icc_bridge);
+
+ pc_register_ferr_irq(gsi[13]);
+
+ assert(pc_machine->vmport != ON_OFF_AUTO_MAX);
+ if (pc_machine->vmport == ON_OFF_AUTO_AUTO) {
+ pc_machine->vmport = xen_enabled() ? ON_OFF_AUTO_OFF : ON_OFF_AUTO_ON;
+ }
+
+ /* init basic PC hardware */
+ pc_basic_device_init(isa_bus, gsi, &rtc_state, !mc->no_floppy,
+ (pc_machine->vmport != ON_OFF_AUTO_ON), 0xff0104);
+
+ /* connect pm stuff to lpc */
+ ich9_lpc_pm_init(lpc, pc_machine_is_smm_enabled(pc_machine), !mc->no_tco);
+
+ /* ahci and SATA device, for q35 1 ahci controller is built-in */
+ ahci = pci_create_simple_multifunction(host_bus,
+ PCI_DEVFN(ICH9_SATA1_DEV,
+ ICH9_SATA1_FUNC),
+ true, "ich9-ahci");
+ idebus[0] = qdev_get_child_bus(&ahci->qdev, "ide.0");
+ idebus[1] = qdev_get_child_bus(&ahci->qdev, "ide.1");
+ g_assert(MAX_SATA_PORTS == ICH_AHCI(ahci)->ahci.ports);
+ ide_drive_get(hd, ICH_AHCI(ahci)->ahci.ports);
+ ahci_ide_create_devs(ahci, hd);
+
+ if (usb_enabled()) {
+ /* Should we create 6 UHCI according to ich9 spec? */
+ ehci_create_ich9_with_companions(host_bus, 0x1d);
+ }
+
+ /* TODO: Populate SPD eeprom data. */
+ smbus_eeprom_init(ich9_smb_init(host_bus,
+ PCI_DEVFN(ICH9_SMB_DEV, ICH9_SMB_FUNC),
+ 0xb100),
+ 8, NULL, 0);
+
+ pc_cmos_init(below_4g_mem_size, above_4g_mem_size, machine->boot_order,
+ machine, idebus[0], idebus[1], rtc_state);
+
+ /* the rest devices to which pci devfn is automatically assigned */
+ pc_vga_init(isa_bus, host_bus);
+ pc_nic_init(isa_bus, host_bus);
+ if (pci_enabled) {
+ pc_pci_device_init(host_bus);
+ }
+}
+
+static void pc_compat_2_3(MachineState *machine)
+{
+ PCMachineState *pcms = PC_MACHINE(machine);
+ savevm_skip_section_footers();
+ if (kvm_enabled()) {
+ pcms->smm = ON_OFF_AUTO_OFF;
+ }
+ global_state_set_optional();
+ savevm_skip_configuration();
+}
+
+static void pc_compat_2_2(MachineState *machine)
+{
+ pc_compat_2_3(machine);
+ rsdp_in_ram = false;
+ x86_cpu_compat_set_features("kvm64", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("kvm32", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Conroe", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Penryn", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Nehalem", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Westmere", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("SandyBridge", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Haswell", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Broadwell", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G1", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G2", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G3", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G4", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Opteron_G5", FEAT_1_EDX, 0, CPUID_VME);
+ x86_cpu_compat_set_features("Haswell", FEAT_1_ECX, 0, CPUID_EXT_F16C);
+ x86_cpu_compat_set_features("Haswell", FEAT_1_ECX, 0, CPUID_EXT_RDRAND);
+ x86_cpu_compat_set_features("Broadwell", FEAT_1_ECX, 0, CPUID_EXT_F16C);
+ x86_cpu_compat_set_features("Broadwell", FEAT_1_ECX, 0, CPUID_EXT_RDRAND);
+ machine->suppress_vmdesc = true;
+}
+
+static void pc_compat_2_1(MachineState *machine)
+{
+ PCMachineState *pcms = PC_MACHINE(machine);
+
+ pc_compat_2_2(machine);
+ pcms->enforce_aligned_dimm = false;
+ smbios_uuid_encoded = false;
+ x86_cpu_compat_set_features("coreduo", FEAT_1_ECX, CPUID_EXT_VMX, 0);
+ x86_cpu_compat_set_features("core2duo", FEAT_1_ECX, CPUID_EXT_VMX, 0);
+ x86_cpu_compat_kvm_no_autodisable(FEAT_8000_0001_ECX, CPUID_EXT3_SVM);
+}
+
+static void pc_compat_2_0(MachineState *machine)
+{
+ pc_compat_2_1(machine);
+ smbios_legacy_mode = true;
+ has_reserved_memory = false;
+ pc_set_legacy_acpi_data_size();
+}
+
+static void pc_compat_1_7(MachineState *machine)
+{
+ pc_compat_2_0(machine);
+ smbios_defaults = false;
+ gigabyte_align = false;
+ option_rom_has_mr = true;
+ x86_cpu_compat_kvm_no_autoenable(FEAT_1_ECX, CPUID_EXT_X2APIC);
+}
+
+static void pc_compat_1_6(MachineState *machine)
+{
+ pc_compat_1_7(machine);
+ rom_file_has_mr = false;
+ has_acpi_build = false;
+}
+
+static void pc_compat_1_5(MachineState *machine)
+{
+ pc_compat_1_6(machine);
+}
+
+static void pc_compat_1_4(MachineState *machine)
+{
+ pc_compat_1_5(machine);
+ x86_cpu_compat_set_features("n270", FEAT_1_ECX, 0, CPUID_EXT_MOVBE);
+ x86_cpu_compat_set_features("Westmere", FEAT_1_ECX, 0, CPUID_EXT_PCLMULQDQ);
+}
+
+#define DEFINE_Q35_MACHINE(suffix, name, compatfn, optionfn) \
+ static void pc_init_##suffix(MachineState *machine) \
+ { \
+ void (*compat)(MachineState *m) = (compatfn); \
+ if (compat) { \
+ compat(machine); \
+ } \
+ pc_q35_init(machine); \
+ } \
+ DEFINE_PC_MACHINE(suffix, name, pc_init_##suffix, optionfn)
+
+
+static void pc_q35_machine_options(MachineClass *m)
+{
+ pc_default_machine_options(m);
+ m->family = "pc_q35";
+ m->desc = "Standard PC (Q35 + ICH9, 2009)";
+ m->hot_add_cpu = pc_hot_add_cpu;
+ m->units_per_default_bus = 1;
+}
+
+static void pc_q35_2_4_machine_options(MachineClass *m)
+{
+ pc_q35_machine_options(m);
+ m->default_machine_opts = "firmware=bios-256k.bin";
+ m->default_display = "std";
+ m->no_floppy = 1;
+ m->no_tco = 0;
+ m->alias = "q35";
+}
+
+DEFINE_Q35_MACHINE(v2_4, "pc-q35-2.4", NULL,
+ pc_q35_2_4_machine_options);
+
+
+static void pc_q35_2_3_machine_options(MachineClass *m)
+{
+ pc_q35_2_4_machine_options(m);
+ m->no_floppy = 0;
+ m->no_tco = 1;
+ m->alias = NULL;
+ SET_MACHINE_COMPAT(m, PC_COMPAT_2_3);
+}
+
+DEFINE_Q35_MACHINE(v2_3, "pc-q35-2.3", pc_compat_2_3,
+ pc_q35_2_3_machine_options);
+
+
+static void pc_q35_2_2_machine_options(MachineClass *m)
+{
+ pc_q35_2_3_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_2_2);
+}
+
+DEFINE_Q35_MACHINE(v2_2, "pc-q35-2.2", pc_compat_2_2,
+ pc_q35_2_2_machine_options);
+
+
+static void pc_q35_2_1_machine_options(MachineClass *m)
+{
+ pc_q35_2_2_machine_options(m);
+ m->default_display = NULL;
+ SET_MACHINE_COMPAT(m, PC_COMPAT_2_1);
+}
+
+DEFINE_Q35_MACHINE(v2_1, "pc-q35-2.1", pc_compat_2_1,
+ pc_q35_2_1_machine_options);
+
+
+static void pc_q35_2_0_machine_options(MachineClass *m)
+{
+ pc_q35_2_1_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_2_0);
+}
+
+DEFINE_Q35_MACHINE(v2_0, "pc-q35-2.0", pc_compat_2_0,
+ pc_q35_2_0_machine_options);
+
+
+static void pc_q35_1_7_machine_options(MachineClass *m)
+{
+ pc_q35_2_0_machine_options(m);
+ m->default_machine_opts = NULL;
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_7);
+}
+
+DEFINE_Q35_MACHINE(v1_7, "pc-q35-1.7", pc_compat_1_7,
+ pc_q35_1_7_machine_options);
+
+
+static void pc_q35_1_6_machine_options(MachineClass *m)
+{
+ pc_q35_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_6);
+}
+
+DEFINE_Q35_MACHINE(v1_6, "pc-q35-1.6", pc_compat_1_6,
+ pc_q35_1_6_machine_options);
+
+
+static void pc_q35_1_5_machine_options(MachineClass *m)
+{
+ pc_q35_1_6_machine_options(m);
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_5);
+}
+
+DEFINE_Q35_MACHINE(v1_5, "pc-q35-1.5", pc_compat_1_5,
+ pc_q35_1_5_machine_options);
+
+
+static void pc_q35_1_4_machine_options(MachineClass *m)
+{
+ pc_q35_1_5_machine_options(m);
+ m->hot_add_cpu = NULL;
+ SET_MACHINE_COMPAT(m, PC_COMPAT_1_4);
+}
+
+DEFINE_Q35_MACHINE(v1_4, "pc-q35-1.4", pc_compat_1_4,
+ pc_q35_1_4_machine_options);
diff --git a/hw/i386/pc_sysfw.c b/hw/i386/pc_sysfw.c
new file mode 100644
index 00000000..662d9976
--- /dev/null
+++ b/hw/i386/pc_sysfw.c
@@ -0,0 +1,251 @@
+/*
+ * QEMU PC System Firmware
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2011-2012 Intel Corporation
+ *
+ * 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.
+ */
+
+#include "sysemu/block-backend.h"
+#include "qemu/error-report.h"
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "sysemu/sysemu.h"
+#include "hw/block/flash.h"
+#include "sysemu/kvm.h"
+
+#define BIOS_FILENAME "bios.bin"
+
+typedef struct PcSysFwDevice {
+ SysBusDevice busdev;
+ uint8_t isapc_ram_fw;
+} PcSysFwDevice;
+
+static void pc_isa_bios_init(MemoryRegion *rom_memory,
+ MemoryRegion *flash_mem,
+ int ram_size)
+{
+ int isa_bios_size;
+ MemoryRegion *isa_bios;
+ uint64_t flash_size;
+ void *flash_ptr, *isa_bios_ptr;
+
+ flash_size = memory_region_size(flash_mem);
+
+ /* map the last 128KB of the BIOS in ISA space */
+ isa_bios_size = MIN(flash_size, 128 * 1024);
+ isa_bios = g_malloc(sizeof(*isa_bios));
+ memory_region_init_ram(isa_bios, NULL, "isa-bios", isa_bios_size,
+ &error_abort);
+ vmstate_register_ram_global(isa_bios);
+ memory_region_add_subregion_overlap(rom_memory,
+ 0x100000 - isa_bios_size,
+ isa_bios,
+ 1);
+
+ /* copy ISA rom image from top of flash memory */
+ flash_ptr = memory_region_get_ram_ptr(flash_mem);
+ isa_bios_ptr = memory_region_get_ram_ptr(isa_bios);
+ memcpy(isa_bios_ptr,
+ ((uint8_t*)flash_ptr) + (flash_size - isa_bios_size),
+ isa_bios_size);
+
+ memory_region_set_readonly(isa_bios, true);
+}
+
+#define FLASH_MAP_UNIT_MAX 2
+
+/* We don't have a theoretically justifiable exact lower bound on the base
+ * address of any flash mapping. In practice, the IO-APIC MMIO range is
+ * [0xFEE00000..0xFEE01000[ -- see IO_APIC_DEFAULT_ADDRESS --, leaving free
+ * only 18MB-4KB below 4G. For now, restrict the cumulative mapping to 8MB in
+ * size.
+ */
+#define FLASH_MAP_BASE_MIN ((hwaddr)(0x100000000ULL - 8*1024*1024))
+
+/* This function maps flash drives from 4G downward, in order of their unit
+ * numbers. The mapping starts at unit#0, with unit number increments of 1, and
+ * stops before the first missing flash drive, or before
+ * unit#FLASH_MAP_UNIT_MAX, whichever is reached first.
+ *
+ * Addressing within one flash drive is of course not reversed.
+ *
+ * An error message is printed and the process exits if:
+ * - the size of the backing file for a flash drive is non-positive, or not a
+ * multiple of the required sector size, or
+ * - the current mapping's base address would fall below FLASH_MAP_BASE_MIN.
+ *
+ * The drive with unit#0 (if available) is mapped at the highest address, and
+ * it is passed to pc_isa_bios_init(). Merging several drives for isa-bios is
+ * not supported.
+ */
+static void pc_system_flash_init(MemoryRegion *rom_memory)
+{
+ int unit;
+ DriveInfo *pflash_drv;
+ BlockBackend *blk;
+ int64_t size;
+ char *fatal_errmsg = NULL;
+ hwaddr phys_addr = 0x100000000ULL;
+ int sector_bits, sector_size;
+ pflash_t *system_flash;
+ MemoryRegion *flash_mem;
+ char name[64];
+
+ sector_bits = 12;
+ sector_size = 1 << sector_bits;
+
+ for (unit = 0;
+ (unit < FLASH_MAP_UNIT_MAX &&
+ (pflash_drv = drive_get(IF_PFLASH, 0, unit)) != NULL);
+ ++unit) {
+ blk = blk_by_legacy_dinfo(pflash_drv);
+ size = blk_getlength(blk);
+ if (size < 0) {
+ fatal_errmsg = g_strdup_printf("failed to get backing file size");
+ } else if (size == 0) {
+ fatal_errmsg = g_strdup_printf("PC system firmware (pflash) "
+ "cannot have zero size");
+ } else if ((size % sector_size) != 0) {
+ fatal_errmsg = g_strdup_printf("PC system firmware (pflash) "
+ "must be a multiple of 0x%x", sector_size);
+ } else if (phys_addr < size || phys_addr - size < FLASH_MAP_BASE_MIN) {
+ fatal_errmsg = g_strdup_printf("oversized backing file, pflash "
+ "segments cannot be mapped under "
+ TARGET_FMT_plx, FLASH_MAP_BASE_MIN);
+ }
+ if (fatal_errmsg != NULL) {
+ Location loc;
+
+ /* push a new, "none" location on the location stack; overwrite its
+ * contents with the location saved in the option; print the error
+ * (includes location); pop the top
+ */
+ loc_push_none(&loc);
+ if (pflash_drv->opts != NULL) {
+ qemu_opts_loc_restore(pflash_drv->opts);
+ }
+ error_report("%s", fatal_errmsg);
+ loc_pop(&loc);
+ g_free(fatal_errmsg);
+ exit(1);
+ }
+
+ phys_addr -= size;
+
+ /* pflash_cfi01_register() creates a deep copy of the name */
+ snprintf(name, sizeof name, "system.flash%d", unit);
+ system_flash = pflash_cfi01_register(phys_addr, NULL /* qdev */, name,
+ size, blk, sector_size,
+ size >> sector_bits,
+ 1 /* width */,
+ 0x0000 /* id0 */,
+ 0x0000 /* id1 */,
+ 0x0000 /* id2 */,
+ 0x0000 /* id3 */,
+ 0 /* be */);
+ if (unit == 0) {
+ flash_mem = pflash_cfi01_get_memory(system_flash);
+ pc_isa_bios_init(rom_memory, flash_mem, size);
+ }
+ }
+}
+
+static void old_pc_system_rom_init(MemoryRegion *rom_memory, bool isapc_ram_fw)
+{
+ char *filename;
+ MemoryRegion *bios, *isa_bios;
+ int bios_size, isa_bios_size;
+ int ret;
+
+ /* BIOS load */
+ if (bios_name == NULL) {
+ bios_name = BIOS_FILENAME;
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = get_image_size(filename);
+ } else {
+ bios_size = -1;
+ }
+ if (bios_size <= 0 ||
+ (bios_size % 65536) != 0) {
+ goto bios_error;
+ }
+ bios = g_malloc(sizeof(*bios));
+ memory_region_init_ram(bios, NULL, "pc.bios", bios_size, &error_abort);
+ vmstate_register_ram_global(bios);
+ if (!isapc_ram_fw) {
+ memory_region_set_readonly(bios, true);
+ }
+ ret = rom_add_file_fixed(bios_name, (uint32_t)(-bios_size), -1);
+ if (ret != 0) {
+ bios_error:
+ fprintf(stderr, "qemu: could not load PC BIOS '%s'\n", bios_name);
+ exit(1);
+ }
+ g_free(filename);
+
+ /* map the last 128KB of the BIOS in ISA space */
+ isa_bios_size = bios_size;
+ if (isa_bios_size > (128 * 1024)) {
+ isa_bios_size = 128 * 1024;
+ }
+ isa_bios = g_malloc(sizeof(*isa_bios));
+ memory_region_init_alias(isa_bios, NULL, "isa-bios", bios,
+ bios_size - isa_bios_size, isa_bios_size);
+ memory_region_add_subregion_overlap(rom_memory,
+ 0x100000 - isa_bios_size,
+ isa_bios,
+ 1);
+ if (!isapc_ram_fw) {
+ memory_region_set_readonly(isa_bios, true);
+ }
+
+ /* map all the bios at the top of memory */
+ memory_region_add_subregion(rom_memory,
+ (uint32_t)(-bios_size),
+ bios);
+}
+
+void pc_system_firmware_init(MemoryRegion *rom_memory, bool isapc_ram_fw)
+{
+ DriveInfo *pflash_drv;
+
+ pflash_drv = drive_get(IF_PFLASH, 0, 0);
+
+ if (isapc_ram_fw || pflash_drv == NULL) {
+ /* When a pflash drive is not found, use rom-mode */
+ old_pc_system_rom_init(rom_memory, isapc_ram_fw);
+ return;
+ }
+
+ if (kvm_enabled() && !kvm_readonly_mem_enabled()) {
+ /* Older KVM cannot execute from device memory. So, flash memory
+ * cannot be used unless the readonly memory kvm capability is present. */
+ fprintf(stderr, "qemu: pflash with kvm requires KVM readonly memory support\n");
+ exit(1);
+ }
+
+ pc_system_flash_init(rom_memory);
+}
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl
new file mode 100644
index 00000000..16eaca3f
--- /dev/null
+++ b/hw/i386/q35-acpi-dsdt.dsl
@@ -0,0 +1,435 @@
+/*
+ * Bochs/QEMU ACPI DSDT ASL definition
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+/*
+ * Copyright (c) 2010 Isaku Yamahata
+ * yamahata at valinux co jp
+ * Based on acpi-dsdt.dsl, but heavily modified for q35 chipset.
+ */
+
+ACPI_EXTRACT_ALL_CODE Q35AcpiDsdtAmlCode
+
+DefinitionBlock (
+ "q35-acpi-dsdt.aml",// Output Filename
+ "DSDT", // Signature
+ 0x01, // DSDT Compliance Revision
+ "BXPC", // OEMID
+ "BXDSDT", // TABLE ID
+ 0x2 // OEM Revision
+ )
+{
+
+#include "acpi-dsdt-dbug.dsl"
+
+ Scope(\_SB) {
+ OperationRegion(PCST, SystemIO, 0xae00, 0x0c)
+ OperationRegion(PCSB, SystemIO, 0xae0c, 0x01)
+ Field(PCSB, AnyAcc, NoLock, WriteAsZeros) {
+ PCIB, 8,
+ }
+ }
+
+
+/****************************************************************
+ * PCI Bus definition
+ ****************************************************************/
+ Scope(\_SB) {
+ Device(PCI0) {
+ Name(_HID, EisaId("PNP0A08"))
+ Name(_CID, EisaId("PNP0A03"))
+ Name(_ADR, 0x00)
+ Name(_UID, 1)
+
+ External(ISA, DeviceObj)
+
+ // _OSC: based on sample of ACPI3.0b spec
+ Name(SUPP, 0) // PCI _OSC Support Field value
+ Name(CTRL, 0) // PCI _OSC Control Field value
+ Method(_OSC, 4) {
+ // Create DWORD-addressable fields from the Capabilities Buffer
+ CreateDWordField(Arg3, 0, CDW1)
+
+ // Check for proper UUID
+ If (LEqual(Arg0, ToUUID("33DB4D5B-1FF7-401C-9657-7441C03DD766"))) {
+ // Create DWORD-addressable fields from the Capabilities Buffer
+ CreateDWordField(Arg3, 4, CDW2)
+ CreateDWordField(Arg3, 8, CDW3)
+
+ // Save Capabilities DWORD2 & 3
+ Store(CDW2, SUPP)
+ Store(CDW3, CTRL)
+
+ // Always allow native PME, AER (no dependencies)
+ // Never allow SHPC (no SHPC controller in this system)
+ And(CTRL, 0x1D, CTRL)
+
+#if 0 // For now, nothing to do
+ If (Not(And(CDW1, 1))) { // Query flag clear?
+ // Disable GPEs for features granted native control.
+ If (And(CTRL, 0x01)) { // Hot plug control granted?
+ Store(0, HPCE) // clear the hot plug SCI enable bit
+ Store(1, HPCS) // clear the hot plug SCI status bit
+ }
+ If (And(CTRL, 0x04)) { // PME control granted?
+ Store(0, PMCE) // clear the PME SCI enable bit
+ Store(1, PMCS) // clear the PME SCI status bit
+ }
+ If (And(CTRL, 0x10)) { // OS restoring PCI Express cap structure?
+ // Set status to not restore PCI Express cap structure
+ // upon resume from S3
+ Store(1, S3CR)
+ }
+ }
+#endif
+ If (LNotEqual(Arg1, One)) {
+ // Unknown revision
+ Or(CDW1, 0x08, CDW1)
+ }
+ If (LNotEqual(CDW3, CTRL)) {
+ // Capabilities bits were masked
+ Or(CDW1, 0x10, CDW1)
+ }
+ // Update DWORD3 in the buffer
+ Store(CTRL, CDW3)
+ } Else {
+ Or(CDW1, 4, CDW1) // Unrecognized UUID
+ }
+ Return (Arg3)
+ }
+ }
+ }
+
+#include "acpi-dsdt-hpet.dsl"
+
+
+/****************************************************************
+ * LPC ISA bridge
+ ****************************************************************/
+
+ Scope(\_SB.PCI0) {
+ /* PCI D31:f0 LPC ISA bridge */
+ Device(ISA) {
+ Name (_ADR, 0x001F0000) // _ADR: Address
+
+ /* ICH9 PCI to ISA irq remapping */
+ OperationRegion(PIRQ, PCI_Config, 0x60, 0x0C)
+
+ OperationRegion(LPCD, PCI_Config, 0x80, 0x2)
+ Field(LPCD, AnyAcc, NoLock, Preserve) {
+ COMA, 3,
+ , 1,
+ COMB, 3,
+
+ Offset(0x01),
+ LPTD, 2,
+ , 2,
+ FDCD, 2
+ }
+ OperationRegion(LPCE, PCI_Config, 0x82, 0x2)
+ Field(LPCE, AnyAcc, NoLock, Preserve) {
+ CAEN, 1,
+ CBEN, 1,
+ LPEN, 1,
+ FDEN, 1
+ }
+ }
+ }
+
+#include "acpi-dsdt-isa.dsl"
+
+
+/****************************************************************
+ * PCI IRQs
+ ****************************************************************/
+
+ /* Zero => PIC mode, One => APIC Mode */
+ Name(\PICF, Zero)
+ Method(\_PIC, 1, NotSerialized) {
+ Store(Arg0, \PICF)
+ }
+
+ Scope(\_SB) {
+ Scope(PCI0) {
+#define prt_slot_lnk(nr, lnk0, lnk1, lnk2, lnk3) \
+ Package() { nr##ffff, 0, lnk0, 0 }, \
+ Package() { nr##ffff, 1, lnk1, 0 }, \
+ Package() { nr##ffff, 2, lnk2, 0 }, \
+ Package() { nr##ffff, 3, lnk3, 0 }
+
+#define prt_slot_lnkA(nr) prt_slot_lnk(nr, LNKA, LNKB, LNKC, LNKD)
+#define prt_slot_lnkB(nr) prt_slot_lnk(nr, LNKB, LNKC, LNKD, LNKA)
+#define prt_slot_lnkC(nr) prt_slot_lnk(nr, LNKC, LNKD, LNKA, LNKB)
+#define prt_slot_lnkD(nr) prt_slot_lnk(nr, LNKD, LNKA, LNKB, LNKC)
+
+#define prt_slot_lnkE(nr) prt_slot_lnk(nr, LNKE, LNKF, LNKG, LNKH)
+#define prt_slot_lnkF(nr) prt_slot_lnk(nr, LNKF, LNKG, LNKH, LNKE)
+#define prt_slot_lnkG(nr) prt_slot_lnk(nr, LNKG, LNKH, LNKE, LNKF)
+#define prt_slot_lnkH(nr) prt_slot_lnk(nr, LNKH, LNKE, LNKF, LNKG)
+
+ Name(PRTP, package() {
+ prt_slot_lnkE(0x0000),
+ prt_slot_lnkF(0x0001),
+ prt_slot_lnkG(0x0002),
+ prt_slot_lnkH(0x0003),
+ prt_slot_lnkE(0x0004),
+ prt_slot_lnkF(0x0005),
+ prt_slot_lnkG(0x0006),
+ prt_slot_lnkH(0x0007),
+ prt_slot_lnkE(0x0008),
+ prt_slot_lnkF(0x0009),
+ prt_slot_lnkG(0x000a),
+ prt_slot_lnkH(0x000b),
+ prt_slot_lnkE(0x000c),
+ prt_slot_lnkF(0x000d),
+ prt_slot_lnkG(0x000e),
+ prt_slot_lnkH(0x000f),
+ prt_slot_lnkE(0x0010),
+ prt_slot_lnkF(0x0011),
+ prt_slot_lnkG(0x0012),
+ prt_slot_lnkH(0x0013),
+ prt_slot_lnkE(0x0014),
+ prt_slot_lnkF(0x0015),
+ prt_slot_lnkG(0x0016),
+ prt_slot_lnkH(0x0017),
+ prt_slot_lnkE(0x0018),
+
+ /* INTA -> PIRQA for slot 25 - 31
+ see the default value of D<N>IR */
+ prt_slot_lnkA(0x0019),
+ prt_slot_lnkA(0x001a),
+ prt_slot_lnkA(0x001b),
+ prt_slot_lnkA(0x001c),
+ prt_slot_lnkA(0x001d),
+
+ /* PCIe->PCI bridge. use PIRQ[E-H] */
+ prt_slot_lnkE(0x001e),
+
+ prt_slot_lnkA(0x001f)
+ })
+
+#define prt_slot_gsi(nr, gsi0, gsi1, gsi2, gsi3) \
+ Package() { nr##ffff, 0, gsi0, 0 }, \
+ Package() { nr##ffff, 1, gsi1, 0 }, \
+ Package() { nr##ffff, 2, gsi2, 0 }, \
+ Package() { nr##ffff, 3, gsi3, 0 }
+
+#define prt_slot_gsiA(nr) prt_slot_gsi(nr, GSIA, GSIB, GSIC, GSID)
+#define prt_slot_gsiB(nr) prt_slot_gsi(nr, GSIB, GSIC, GSID, GSIA)
+#define prt_slot_gsiC(nr) prt_slot_gsi(nr, GSIC, GSID, GSIA, GSIB)
+#define prt_slot_gsiD(nr) prt_slot_gsi(nr, GSID, GSIA, GSIB, GSIC)
+
+#define prt_slot_gsiE(nr) prt_slot_gsi(nr, GSIE, GSIF, GSIG, GSIH)
+#define prt_slot_gsiF(nr) prt_slot_gsi(nr, GSIF, GSIG, GSIH, GSIE)
+#define prt_slot_gsiG(nr) prt_slot_gsi(nr, GSIG, GSIH, GSIE, GSIF)
+#define prt_slot_gsiH(nr) prt_slot_gsi(nr, GSIH, GSIE, GSIF, GSIG)
+
+ Name(PRTA, package() {
+ prt_slot_gsiE(0x0000),
+ prt_slot_gsiF(0x0001),
+ prt_slot_gsiG(0x0002),
+ prt_slot_gsiH(0x0003),
+ prt_slot_gsiE(0x0004),
+ prt_slot_gsiF(0x0005),
+ prt_slot_gsiG(0x0006),
+ prt_slot_gsiH(0x0007),
+ prt_slot_gsiE(0x0008),
+ prt_slot_gsiF(0x0009),
+ prt_slot_gsiG(0x000a),
+ prt_slot_gsiH(0x000b),
+ prt_slot_gsiE(0x000c),
+ prt_slot_gsiF(0x000d),
+ prt_slot_gsiG(0x000e),
+ prt_slot_gsiH(0x000f),
+ prt_slot_gsiE(0x0010),
+ prt_slot_gsiF(0x0011),
+ prt_slot_gsiG(0x0012),
+ prt_slot_gsiH(0x0013),
+ prt_slot_gsiE(0x0014),
+ prt_slot_gsiF(0x0015),
+ prt_slot_gsiG(0x0016),
+ prt_slot_gsiH(0x0017),
+ prt_slot_gsiE(0x0018),
+
+ /* INTA -> PIRQA for slot 25 - 31, but 30
+ see the default value of D<N>IR */
+ prt_slot_gsiA(0x0019),
+ prt_slot_gsiA(0x001a),
+ prt_slot_gsiA(0x001b),
+ prt_slot_gsiA(0x001c),
+ prt_slot_gsiA(0x001d),
+
+ /* PCIe->PCI bridge. use PIRQ[E-H] */
+ prt_slot_gsiE(0x001e),
+
+ prt_slot_gsiA(0x001f)
+ })
+
+ Method(_PRT, 0, NotSerialized) {
+ /* PCI IRQ routing table, example from ACPI 2.0a specification,
+ section 6.2.8.1 */
+ /* Note: we provide the same info as the PCI routing
+ table of the Bochs BIOS */
+ If (LEqual(\PICF, Zero)) {
+ Return (PRTP)
+ } Else {
+ Return (PRTA)
+ }
+ }
+ }
+
+ Field(PCI0.ISA.PIRQ, ByteAcc, NoLock, Preserve) {
+ PRQA, 8,
+ PRQB, 8,
+ PRQC, 8,
+ PRQD, 8,
+
+ Offset(0x08),
+ PRQE, 8,
+ PRQF, 8,
+ PRQG, 8,
+ PRQH, 8
+ }
+
+ Method(IQST, 1, NotSerialized) {
+ // _STA method - get status
+ If (And(0x80, Arg0)) {
+ Return (0x09)
+ }
+ Return (0x0B)
+ }
+ Method(IQCR, 1, Serialized) {
+ // _CRS method - get current settings
+ Name(PRR0, ResourceTemplate() {
+ Interrupt(, Level, ActiveHigh, Shared) { 0 }
+ })
+ CreateDWordField(PRR0, 0x05, PRRI)
+ Store(And(Arg0, 0x0F), PRRI)
+ Return (PRR0)
+ }
+
+#define define_link(link, uid, reg) \
+ Device(link) { \
+ Name(_HID, EISAID("PNP0C0F")) \
+ Name(_UID, uid) \
+ Name(_PRS, ResourceTemplate() { \
+ Interrupt(, Level, ActiveHigh, Shared) { \
+ 5, 10, 11 \
+ } \
+ }) \
+ Method(_STA, 0, NotSerialized) { \
+ Return (IQST(reg)) \
+ } \
+ Method(_DIS, 0, NotSerialized) { \
+ Or(reg, 0x80, reg) \
+ } \
+ Method(_CRS, 0, NotSerialized) { \
+ Return (IQCR(reg)) \
+ } \
+ Method(_SRS, 1, NotSerialized) { \
+ CreateDWordField(Arg0, 0x05, PRRI) \
+ Store(PRRI, reg) \
+ } \
+ }
+
+ define_link(LNKA, 0, PRQA)
+ define_link(LNKB, 1, PRQB)
+ define_link(LNKC, 2, PRQC)
+ define_link(LNKD, 3, PRQD)
+ define_link(LNKE, 4, PRQE)
+ define_link(LNKF, 5, PRQF)
+ define_link(LNKG, 6, PRQG)
+ define_link(LNKH, 7, PRQH)
+
+#define define_gsi_link(link, uid, gsi) \
+ Device(link) { \
+ Name(_HID, EISAID("PNP0C0F")) \
+ Name(_UID, uid) \
+ Name(_PRS, ResourceTemplate() { \
+ Interrupt(, Level, ActiveHigh, Shared) { \
+ gsi \
+ } \
+ }) \
+ Name(_CRS, ResourceTemplate() { \
+ Interrupt(, Level, ActiveHigh, Shared) { \
+ gsi \
+ } \
+ }) \
+ Method(_SRS, 1, NotSerialized) { \
+ } \
+ }
+
+ define_gsi_link(GSIA, 0, 0x10)
+ define_gsi_link(GSIB, 0, 0x11)
+ define_gsi_link(GSIC, 0, 0x12)
+ define_gsi_link(GSID, 0, 0x13)
+ define_gsi_link(GSIE, 0, 0x14)
+ define_gsi_link(GSIF, 0, 0x15)
+ define_gsi_link(GSIG, 0, 0x16)
+ define_gsi_link(GSIH, 0, 0x17)
+ }
+
+#include "hw/acpi/pc-hotplug.h"
+#define CPU_STATUS_BASE ICH9_CPU_HOTPLUG_IO_BASE
+#include "acpi-dsdt-cpu-hotplug.dsl"
+#include "acpi-dsdt-mem-hotplug.dsl"
+
+
+/****************************************************************
+ * General purpose events
+ ****************************************************************/
+ Scope(\_GPE) {
+ Name(_HID, "ACPI0006")
+
+ Method(_L00) {
+ }
+ Method(_L01) {
+ }
+ Method(_E02) {
+ // CPU hotplug event
+ \_SB.PRSC()
+ }
+ Method(_E03) {
+ // Memory hotplug event
+ \_SB.PCI0.MEMORY_HOTPLUG_DEVICE.MEMORY_SLOT_SCAN_METHOD()
+ }
+ Method(_L04) {
+ }
+ Method(_L05) {
+ }
+ Method(_L06) {
+ }
+ Method(_L07) {
+ }
+ Method(_L08) {
+ }
+ Method(_L09) {
+ }
+ Method(_L0A) {
+ }
+ Method(_L0B) {
+ }
+ Method(_L0C) {
+ }
+ Method(_L0D) {
+ }
+ Method(_L0E) {
+ }
+ Method(_L0F) {
+ }
+ }
+}
diff --git a/hw/i386/q35-acpi-dsdt.hex.generated b/hw/i386/q35-acpi-dsdt.hex.generated
new file mode 100644
index 00000000..ed9a2cc8
--- /dev/null
+++ b/hw/i386/q35-acpi-dsdt.hex.generated
@@ -0,0 +1,7610 @@
+static unsigned char Q35AcpiDsdtAmlCode[] = {
+0x44,
+0x53,
+0x44,
+0x54,
+0xb8,
+0x1d,
+0x0,
+0x0,
+0x1,
+0x35,
+0x42,
+0x58,
+0x50,
+0x43,
+0x0,
+0x0,
+0x42,
+0x58,
+0x44,
+0x53,
+0x44,
+0x54,
+0x0,
+0x0,
+0x2,
+0x0,
+0x0,
+0x0,
+0x49,
+0x4e,
+0x54,
+0x4c,
+0x7,
+0x11,
+0x14,
+0x20,
+0x10,
+0x49,
+0x4,
+0x5c,
+0x0,
+0x5b,
+0x80,
+0x44,
+0x42,
+0x47,
+0x5f,
+0x1,
+0xb,
+0x2,
+0x4,
+0x1,
+0x5b,
+0x81,
+0xb,
+0x44,
+0x42,
+0x47,
+0x5f,
+0x1,
+0x44,
+0x42,
+0x47,
+0x42,
+0x8,
+0x14,
+0x2c,
+0x44,
+0x42,
+0x55,
+0x47,
+0x1,
+0x98,
+0x68,
+0x60,
+0x96,
+0x60,
+0x60,
+0x74,
+0x87,
+0x60,
+0x1,
+0x61,
+0x70,
+0x0,
+0x62,
+0xa2,
+0x10,
+0x95,
+0x62,
+0x61,
+0x70,
+0x83,
+0x88,
+0x60,
+0x62,
+0x0,
+0x44,
+0x42,
+0x47,
+0x42,
+0x75,
+0x62,
+0x70,
+0xa,
+0xa,
+0x44,
+0x42,
+0x47,
+0x42,
+0x10,
+0x29,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x5b,
+0x80,
+0x50,
+0x43,
+0x53,
+0x54,
+0x1,
+0xb,
+0x0,
+0xae,
+0xa,
+0xc,
+0x5b,
+0x80,
+0x50,
+0x43,
+0x53,
+0x42,
+0x1,
+0xb,
+0xc,
+0xae,
+0x1,
+0x5b,
+0x81,
+0xb,
+0x50,
+0x43,
+0x53,
+0x42,
+0x40,
+0x50,
+0x43,
+0x49,
+0x42,
+0x8,
+0x10,
+0x4f,
+0xc,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x5b,
+0x82,
+0x47,
+0xc,
+0x50,
+0x43,
+0x49,
+0x30,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xa,
+0x8,
+0x8,
+0x5f,
+0x43,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xa,
+0x3,
+0x8,
+0x5f,
+0x41,
+0x44,
+0x52,
+0x0,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x1,
+0x8,
+0x53,
+0x55,
+0x50,
+0x50,
+0x0,
+0x8,
+0x43,
+0x54,
+0x52,
+0x4c,
+0x0,
+0x14,
+0x44,
+0x9,
+0x5f,
+0x4f,
+0x53,
+0x43,
+0x4,
+0x8a,
+0x6b,
+0x0,
+0x43,
+0x44,
+0x57,
+0x31,
+0xa0,
+0x46,
+0x7,
+0x93,
+0x68,
+0x11,
+0x13,
+0xa,
+0x10,
+0x5b,
+0x4d,
+0xdb,
+0x33,
+0xf7,
+0x1f,
+0x1c,
+0x40,
+0x96,
+0x57,
+0x74,
+0x41,
+0xc0,
+0x3d,
+0xd7,
+0x66,
+0x8a,
+0x6b,
+0xa,
+0x4,
+0x43,
+0x44,
+0x57,
+0x32,
+0x8a,
+0x6b,
+0xa,
+0x8,
+0x43,
+0x44,
+0x57,
+0x33,
+0x70,
+0x43,
+0x44,
+0x57,
+0x32,
+0x53,
+0x55,
+0x50,
+0x50,
+0x70,
+0x43,
+0x44,
+0x57,
+0x33,
+0x43,
+0x54,
+0x52,
+0x4c,
+0x7b,
+0x43,
+0x54,
+0x52,
+0x4c,
+0xa,
+0x1d,
+0x43,
+0x54,
+0x52,
+0x4c,
+0xa0,
+0x10,
+0x92,
+0x93,
+0x69,
+0x1,
+0x7d,
+0x43,
+0x44,
+0x57,
+0x31,
+0xa,
+0x8,
+0x43,
+0x44,
+0x57,
+0x31,
+0xa0,
+0x16,
+0x92,
+0x93,
+0x43,
+0x44,
+0x57,
+0x33,
+0x43,
+0x54,
+0x52,
+0x4c,
+0x7d,
+0x43,
+0x44,
+0x57,
+0x31,
+0xa,
+0x10,
+0x43,
+0x44,
+0x57,
+0x31,
+0x70,
+0x43,
+0x54,
+0x52,
+0x4c,
+0x43,
+0x44,
+0x57,
+0x33,
+0xa1,
+0xc,
+0x7d,
+0x43,
+0x44,
+0x57,
+0x31,
+0xa,
+0x4,
+0x43,
+0x44,
+0x57,
+0x31,
+0xa4,
+0x6b,
+0x10,
+0x4d,
+0x8,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x5b,
+0x82,
+0x45,
+0x8,
+0x48,
+0x50,
+0x45,
+0x54,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x1,
+0x3,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x5b,
+0x80,
+0x48,
+0x50,
+0x54,
+0x4d,
+0x0,
+0xc,
+0x0,
+0x0,
+0xd0,
+0xfe,
+0xb,
+0x0,
+0x4,
+0x5b,
+0x81,
+0x10,
+0x48,
+0x50,
+0x54,
+0x4d,
+0x13,
+0x56,
+0x45,
+0x4e,
+0x44,
+0x20,
+0x50,
+0x52,
+0x44,
+0x5f,
+0x20,
+0x14,
+0x36,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x56,
+0x45,
+0x4e,
+0x44,
+0x60,
+0x70,
+0x50,
+0x52,
+0x44,
+0x5f,
+0x61,
+0x7a,
+0x60,
+0xa,
+0x10,
+0x60,
+0xa0,
+0xc,
+0x91,
+0x93,
+0x60,
+0x0,
+0x93,
+0x60,
+0xb,
+0xff,
+0xff,
+0xa4,
+0x0,
+0xa0,
+0xe,
+0x91,
+0x93,
+0x61,
+0x0,
+0x94,
+0x61,
+0xc,
+0x0,
+0xe1,
+0xf5,
+0x5,
+0xa4,
+0x0,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x11,
+0xa,
+0xe,
+0x86,
+0x9,
+0x0,
+0x0,
+0x0,
+0x0,
+0xd0,
+0xfe,
+0x0,
+0x4,
+0x0,
+0x0,
+0x79,
+0x0,
+0x10,
+0x4c,
+0x7,
+0x2e,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x5b,
+0x82,
+0x4f,
+0x6,
+0x49,
+0x53,
+0x41,
+0x5f,
+0x8,
+0x5f,
+0x41,
+0x44,
+0x52,
+0xc,
+0x0,
+0x0,
+0x1f,
+0x0,
+0x5b,
+0x80,
+0x50,
+0x49,
+0x52,
+0x51,
+0x2,
+0xa,
+0x60,
+0xa,
+0xc,
+0x5b,
+0x80,
+0x4c,
+0x50,
+0x43,
+0x44,
+0x2,
+0xa,
+0x80,
+0xa,
+0x2,
+0x5b,
+0x81,
+0x20,
+0x4c,
+0x50,
+0x43,
+0x44,
+0x0,
+0x43,
+0x4f,
+0x4d,
+0x41,
+0x3,
+0x0,
+0x1,
+0x43,
+0x4f,
+0x4d,
+0x42,
+0x3,
+0x0,
+0x1,
+0x4c,
+0x50,
+0x54,
+0x44,
+0x2,
+0x0,
+0x2,
+0x46,
+0x44,
+0x43,
+0x44,
+0x2,
+0x5b,
+0x80,
+0x4c,
+0x50,
+0x43,
+0x45,
+0x2,
+0xa,
+0x82,
+0xa,
+0x2,
+0x5b,
+0x81,
+0x1a,
+0x4c,
+0x50,
+0x43,
+0x45,
+0x0,
+0x43,
+0x41,
+0x45,
+0x4e,
+0x1,
+0x43,
+0x42,
+0x45,
+0x4e,
+0x1,
+0x4c,
+0x50,
+0x45,
+0x4e,
+0x1,
+0x46,
+0x44,
+0x45,
+0x4e,
+0x1,
+0x10,
+0x4c,
+0x1b,
+0x2f,
+0x3,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x49,
+0x53,
+0x41,
+0x5f,
+0x5b,
+0x82,
+0x2d,
+0x52,
+0x54,
+0x43,
+0x5f,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xb,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x18,
+0xa,
+0x15,
+0x47,
+0x1,
+0x70,
+0x0,
+0x70,
+0x0,
+0x10,
+0x2,
+0x22,
+0x0,
+0x1,
+0x47,
+0x1,
+0x72,
+0x0,
+0x72,
+0x0,
+0x2,
+0x6,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x37,
+0x4b,
+0x42,
+0x44,
+0x5f,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x3,
+0x3,
+0x14,
+0x9,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x18,
+0xa,
+0x15,
+0x47,
+0x1,
+0x60,
+0x0,
+0x60,
+0x0,
+0x1,
+0x1,
+0x47,
+0x1,
+0x64,
+0x0,
+0x64,
+0x0,
+0x1,
+0x1,
+0x22,
+0x2,
+0x0,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x27,
+0x4d,
+0x4f,
+0x55,
+0x5f,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xf,
+0x13,
+0x14,
+0x9,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x8,
+0xa,
+0x5,
+0x22,
+0x0,
+0x10,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x4a,
+0x4,
+0x46,
+0x44,
+0x43,
+0x30,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x7,
+0x0,
+0x14,
+0x18,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x46,
+0x44,
+0x45,
+0x4e,
+0x60,
+0xa0,
+0x6,
+0x93,
+0x60,
+0x0,
+0xa4,
+0x0,
+0xa1,
+0x4,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x1b,
+0xa,
+0x18,
+0x47,
+0x1,
+0xf2,
+0x3,
+0xf2,
+0x3,
+0x0,
+0x4,
+0x47,
+0x1,
+0xf7,
+0x3,
+0xf7,
+0x3,
+0x0,
+0x1,
+0x22,
+0x40,
+0x0,
+0x2a,
+0x4,
+0x0,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x3e,
+0x4c,
+0x50,
+0x54,
+0x5f,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x4,
+0x0,
+0x14,
+0x18,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x4c,
+0x50,
+0x45,
+0x4e,
+0x60,
+0xa0,
+0x6,
+0x93,
+0x60,
+0x0,
+0xa4,
+0x0,
+0xa1,
+0x4,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x10,
+0xa,
+0xd,
+0x47,
+0x1,
+0x78,
+0x3,
+0x78,
+0x3,
+0x8,
+0x8,
+0x22,
+0x80,
+0x0,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x43,
+0x4f,
+0x4d,
+0x31,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x5,
+0x1,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x1,
+0x14,
+0x18,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x43,
+0x41,
+0x45,
+0x4e,
+0x60,
+0xa0,
+0x6,
+0x93,
+0x60,
+0x0,
+0xa4,
+0x0,
+0xa1,
+0x4,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x10,
+0xa,
+0xd,
+0x47,
+0x1,
+0xf8,
+0x3,
+0xf8,
+0x3,
+0x0,
+0x8,
+0x22,
+0x10,
+0x0,
+0x79,
+0x0,
+0x5b,
+0x82,
+0x46,
+0x4,
+0x43,
+0x4f,
+0x4d,
+0x32,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0x5,
+0x1,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x2,
+0x14,
+0x18,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0x70,
+0x43,
+0x42,
+0x45,
+0x4e,
+0x60,
+0xa0,
+0x6,
+0x93,
+0x60,
+0x0,
+0xa4,
+0x0,
+0xa1,
+0x4,
+0xa4,
+0xa,
+0xf,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0x10,
+0xa,
+0xd,
+0x47,
+0x1,
+0xf8,
+0x2,
+0xf8,
+0x2,
+0x0,
+0x8,
+0x22,
+0x8,
+0x0,
+0x79,
+0x0,
+0x8,
+0x50,
+0x49,
+0x43,
+0x46,
+0x0,
+0x14,
+0xc,
+0x5f,
+0x50,
+0x49,
+0x43,
+0x1,
+0x70,
+0x68,
+0x50,
+0x49,
+0x43,
+0x46,
+0x10,
+0x8e,
+0x55,
+0x1,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x10,
+0x43,
+0xea,
+0x50,
+0x43,
+0x49,
+0x30,
+0x8,
+0x50,
+0x52,
+0x54,
+0x50,
+0x12,
+0x4b,
+0x73,
+0x80,
+0x12,
+0xb,
+0x4,
+0xb,
+0xff,
+0xff,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xb,
+0x4,
+0xb,
+0xff,
+0xff,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xc,
+0x4,
+0xb,
+0xff,
+0xff,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xc,
+0x4,
+0xb,
+0xff,
+0xff,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x2,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x2,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x2,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x2,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x3,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x3,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x3,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x3,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x4,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x4,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x4,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x4,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x5,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x5,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x5,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x5,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x6,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x6,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x6,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x6,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x7,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x7,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x7,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x7,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x8,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x8,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x8,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x8,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x9,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x9,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x9,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x9,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xa,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xa,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xa,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xa,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xb,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xb,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xb,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xb,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xc,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xc,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xc,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xc,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xd,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xd,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xd,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xd,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xe,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xe,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xe,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xe,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xf,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xf,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xf,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xf,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x10,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x10,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x10,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x10,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x11,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x11,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x11,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x11,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x12,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x12,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x12,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x12,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x13,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x13,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x13,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x13,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x14,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x14,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x14,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x14,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x15,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x15,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x15,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x15,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x16,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x16,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x16,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x16,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x17,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x17,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x17,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x17,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x18,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x18,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x18,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x18,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x19,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x19,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x19,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x19,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1a,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1a,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1a,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1a,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1b,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1b,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1b,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1b,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1c,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1c,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1c,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1c,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1d,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1d,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1d,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1d,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1e,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1e,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1e,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1e,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1f,
+0x0,
+0x0,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1f,
+0x0,
+0x1,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1f,
+0x0,
+0xa,
+0x2,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1f,
+0x0,
+0xa,
+0x3,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x0,
+0x8,
+0x50,
+0x52,
+0x54,
+0x41,
+0x12,
+0x4b,
+0x73,
+0x80,
+0x12,
+0xb,
+0x4,
+0xb,
+0xff,
+0xff,
+0x0,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xb,
+0x4,
+0xb,
+0xff,
+0xff,
+0x1,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xc,
+0x4,
+0xb,
+0xff,
+0xff,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xc,
+0x4,
+0xb,
+0xff,
+0xff,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x2,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x2,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x2,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x2,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x3,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x3,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x3,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x3,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x4,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x4,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x4,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x4,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x5,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x5,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x5,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x5,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x6,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x6,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x6,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x6,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x7,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x7,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x7,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x7,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x8,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x8,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x8,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x8,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x9,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x9,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x9,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x9,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xa,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xa,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xa,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xa,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xb,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xb,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xb,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xb,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xc,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xc,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xc,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xc,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xd,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xd,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xd,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xd,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xe,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xe,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xe,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xe,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xf,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0xf,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xf,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0xf,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x10,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x10,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x10,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x10,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x11,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x11,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x11,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x11,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x12,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x12,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x12,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x12,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x13,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x13,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x13,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x13,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x14,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x14,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x14,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x14,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x15,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x15,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x15,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x15,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x16,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x16,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x16,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x16,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x17,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x17,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x17,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x17,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x18,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x18,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x18,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x18,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x19,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x19,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x19,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x19,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1a,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1a,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1a,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1a,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1b,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1b,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1b,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1b,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1c,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1c,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1c,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1c,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1d,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1d,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1d,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1d,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x44,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1e,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x45,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1e,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x46,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1e,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x47,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1e,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x48,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1f,
+0x0,
+0x0,
+0x47,
+0x53,
+0x49,
+0x41,
+0x0,
+0x12,
+0xd,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1f,
+0x0,
+0x1,
+0x47,
+0x53,
+0x49,
+0x42,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1f,
+0x0,
+0xa,
+0x2,
+0x47,
+0x53,
+0x49,
+0x43,
+0x0,
+0x12,
+0xe,
+0x4,
+0xc,
+0xff,
+0xff,
+0x1f,
+0x0,
+0xa,
+0x3,
+0x47,
+0x53,
+0x49,
+0x44,
+0x0,
+0x14,
+0x1a,
+0x5f,
+0x50,
+0x52,
+0x54,
+0x0,
+0xa0,
+0xc,
+0x93,
+0x50,
+0x49,
+0x43,
+0x46,
+0x0,
+0xa4,
+0x50,
+0x52,
+0x54,
+0x50,
+0xa1,
+0x6,
+0xa4,
+0x50,
+0x52,
+0x54,
+0x41,
+0x5b,
+0x81,
+0x3a,
+0x2f,
+0x3,
+0x50,
+0x43,
+0x49,
+0x30,
+0x49,
+0x53,
+0x41,
+0x5f,
+0x50,
+0x49,
+0x52,
+0x51,
+0x1,
+0x50,
+0x52,
+0x51,
+0x41,
+0x8,
+0x50,
+0x52,
+0x51,
+0x42,
+0x8,
+0x50,
+0x52,
+0x51,
+0x43,
+0x8,
+0x50,
+0x52,
+0x51,
+0x44,
+0x8,
+0x0,
+0x20,
+0x50,
+0x52,
+0x51,
+0x45,
+0x8,
+0x50,
+0x52,
+0x51,
+0x46,
+0x8,
+0x50,
+0x52,
+0x51,
+0x47,
+0x8,
+0x50,
+0x52,
+0x51,
+0x48,
+0x8,
+0x14,
+0x13,
+0x49,
+0x51,
+0x53,
+0x54,
+0x1,
+0xa0,
+0x9,
+0x7b,
+0xa,
+0x80,
+0x68,
+0x0,
+0xa4,
+0xa,
+0x9,
+0xa4,
+0xa,
+0xb,
+0x14,
+0x34,
+0x49,
+0x51,
+0x43,
+0x52,
+0x9,
+0x8,
+0x50,
+0x52,
+0x52,
+0x30,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x0,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8a,
+0x50,
+0x52,
+0x52,
+0x30,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x7b,
+0x68,
+0xa,
+0xf,
+0x0,
+0x50,
+0x52,
+0x52,
+0x49,
+0xa4,
+0x50,
+0x52,
+0x52,
+0x30,
+0x5b,
+0x82,
+0x4c,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x41,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x41,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x41,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x41,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x41,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x41,
+0x5b,
+0x82,
+0x4c,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x42,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x1,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x42,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x42,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x42,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x42,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x42,
+0x5b,
+0x82,
+0x4d,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x43,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x2,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x43,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x43,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x43,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x43,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x43,
+0x5b,
+0x82,
+0x4d,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x44,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x3,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x44,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x44,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x44,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x44,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x44,
+0x5b,
+0x82,
+0x4d,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x45,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x4,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x45,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x45,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x45,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x45,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x45,
+0x5b,
+0x82,
+0x4d,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x46,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x5,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x46,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x46,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x46,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x46,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x46,
+0x5b,
+0x82,
+0x4d,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x47,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x6,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x47,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x47,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x47,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x47,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x47,
+0x5b,
+0x82,
+0x4d,
+0x7,
+0x4c,
+0x4e,
+0x4b,
+0x48,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xa,
+0x7,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0x16,
+0xa,
+0x13,
+0x89,
+0xe,
+0x0,
+0x9,
+0x3,
+0x5,
+0x0,
+0x0,
+0x0,
+0xa,
+0x0,
+0x0,
+0x0,
+0xb,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0xf,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x53,
+0x54,
+0x50,
+0x52,
+0x51,
+0x48,
+0x14,
+0x11,
+0x5f,
+0x44,
+0x49,
+0x53,
+0x0,
+0x7d,
+0x50,
+0x52,
+0x51,
+0x48,
+0xa,
+0x80,
+0x50,
+0x52,
+0x51,
+0x48,
+0x14,
+0xf,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x0,
+0xa4,
+0x49,
+0x51,
+0x43,
+0x52,
+0x50,
+0x52,
+0x51,
+0x48,
+0x14,
+0x17,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x8a,
+0x68,
+0xa,
+0x5,
+0x50,
+0x52,
+0x52,
+0x49,
+0x70,
+0x50,
+0x52,
+0x52,
+0x49,
+0x50,
+0x52,
+0x51,
+0x48,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x47,
+0x53,
+0x49,
+0x41,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x10,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x10,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x47,
+0x53,
+0x49,
+0x42,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x11,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x11,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x47,
+0x53,
+0x49,
+0x43,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x12,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x12,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x47,
+0x53,
+0x49,
+0x44,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x13,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x13,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x47,
+0x53,
+0x49,
+0x45,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x14,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x14,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x47,
+0x53,
+0x49,
+0x46,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x15,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x15,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x47,
+0x53,
+0x49,
+0x47,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x16,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x16,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x5b,
+0x82,
+0x45,
+0x4,
+0x47,
+0x53,
+0x49,
+0x48,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xc,
+0x41,
+0xd0,
+0xc,
+0xf,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0x0,
+0x8,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x17,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x8,
+0x5f,
+0x43,
+0x52,
+0x53,
+0x11,
+0xe,
+0xa,
+0xb,
+0x89,
+0x6,
+0x0,
+0x9,
+0x1,
+0x17,
+0x0,
+0x0,
+0x0,
+0x79,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x53,
+0x52,
+0x53,
+0x1,
+0x10,
+0x4d,
+0xc,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x14,
+0x35,
+0x43,
+0x50,
+0x4d,
+0x41,
+0x1,
+0x70,
+0x83,
+0x88,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x68,
+0x0,
+0x60,
+0x70,
+0x11,
+0xb,
+0xa,
+0x8,
+0x0,
+0x8,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x61,
+0x70,
+0x68,
+0x88,
+0x61,
+0xa,
+0x2,
+0x0,
+0x70,
+0x68,
+0x88,
+0x61,
+0xa,
+0x3,
+0x0,
+0x70,
+0x60,
+0x88,
+0x61,
+0xa,
+0x4,
+0x0,
+0xa4,
+0x61,
+0x14,
+0x1a,
+0x43,
+0x50,
+0x53,
+0x54,
+0x1,
+0x70,
+0x83,
+0x88,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x68,
+0x0,
+0x60,
+0xa0,
+0x5,
+0x60,
+0xa4,
+0xa,
+0xf,
+0xa1,
+0x3,
+0xa4,
+0x0,
+0x14,
+0xa,
+0x43,
+0x50,
+0x45,
+0x4a,
+0x2,
+0x5b,
+0x22,
+0xa,
+0xc8,
+0x14,
+0x4a,
+0x6,
+0x50,
+0x52,
+0x53,
+0x43,
+0x0,
+0x70,
+0x50,
+0x52,
+0x53,
+0x5f,
+0x65,
+0x70,
+0x0,
+0x62,
+0x70,
+0x0,
+0x60,
+0xa2,
+0x46,
+0x5,
+0x95,
+0x60,
+0x87,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x70,
+0x83,
+0x88,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x60,
+0x0,
+0x61,
+0xa0,
+0xa,
+0x7b,
+0x60,
+0xa,
+0x7,
+0x0,
+0x7a,
+0x62,
+0x1,
+0x62,
+0xa1,
+0xc,
+0x70,
+0x83,
+0x88,
+0x65,
+0x7a,
+0x60,
+0xa,
+0x3,
+0x0,
+0x0,
+0x62,
+0x70,
+0x7b,
+0x62,
+0x1,
+0x0,
+0x63,
+0xa0,
+0x22,
+0x92,
+0x93,
+0x61,
+0x63,
+0x70,
+0x63,
+0x88,
+0x43,
+0x50,
+0x4f,
+0x4e,
+0x60,
+0x0,
+0xa0,
+0xa,
+0x93,
+0x63,
+0x1,
+0x4e,
+0x54,
+0x46,
+0x59,
+0x60,
+0x1,
+0xa1,
+0x8,
+0x4e,
+0x54,
+0x46,
+0x59,
+0x60,
+0xa,
+0x3,
+0x75,
+0x60,
+0x10,
+0x44,
+0x2a,
+0x2e,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x5b,
+0x82,
+0x47,
+0x29,
+0x4d,
+0x48,
+0x50,
+0x44,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xd,
+0x50,
+0x4e,
+0x50,
+0x30,
+0x41,
+0x30,
+0x36,
+0x0,
+0x8,
+0x5f,
+0x55,
+0x49,
+0x44,
+0xd,
+0x4d,
+0x65,
+0x6d,
+0x6f,
+0x72,
+0x79,
+0x20,
+0x68,
+0x6f,
+0x74,
+0x70,
+0x6c,
+0x75,
+0x67,
+0x20,
+0x72,
+0x65,
+0x73,
+0x6f,
+0x75,
+0x72,
+0x63,
+0x65,
+0x73,
+0x0,
+0x14,
+0x13,
+0x5f,
+0x53,
+0x54,
+0x41,
+0x0,
+0xa0,
+0x9,
+0x93,
+0x4d,
+0x44,
+0x4e,
+0x52,
+0x0,
+0xa4,
+0x0,
+0xa4,
+0xa,
+0xb,
+0x5b,
+0x1,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0x0,
+0x14,
+0x4a,
+0x4,
+0x4d,
+0x53,
+0x43,
+0x4e,
+0x0,
+0xa0,
+0x9,
+0x93,
+0x4d,
+0x44,
+0x4e,
+0x52,
+0x0,
+0xa4,
+0x0,
+0x70,
+0x0,
+0x60,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0xa2,
+0x25,
+0x95,
+0x60,
+0x4d,
+0x44,
+0x4e,
+0x52,
+0x70,
+0x60,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0xa0,
+0x13,
+0x93,
+0x4d,
+0x49,
+0x4e,
+0x53,
+0x1,
+0x4d,
+0x54,
+0x46,
+0x59,
+0x60,
+0x1,
+0x70,
+0x1,
+0x4d,
+0x49,
+0x4e,
+0x53,
+0x72,
+0x60,
+0x1,
+0x60,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x1,
+0x14,
+0x2d,
+0x4d,
+0x52,
+0x53,
+0x54,
+0x1,
+0x70,
+0x0,
+0x60,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x99,
+0x68,
+0x0,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0xa0,
+0xb,
+0x93,
+0x4d,
+0x45,
+0x53,
+0x5f,
+0x1,
+0x70,
+0xa,
+0xf,
+0x60,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x60,
+0x14,
+0x41,
+0x18,
+0x4d,
+0x43,
+0x52,
+0x53,
+0x9,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x99,
+0x68,
+0x0,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0x8,
+0x4d,
+0x52,
+0x36,
+0x34,
+0x11,
+0x33,
+0xa,
+0x30,
+0x8a,
+0x2b,
+0x0,
+0x0,
+0xc,
+0x3,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0xfe,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0xff,
+0x79,
+0x0,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0xe,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x12,
+0x4d,
+0x49,
+0x4e,
+0x48,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x26,
+0x4c,
+0x45,
+0x4e,
+0x4c,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x2a,
+0x4c,
+0x45,
+0x4e,
+0x48,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x16,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x8a,
+0x4d,
+0x52,
+0x36,
+0x34,
+0xa,
+0x1a,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x70,
+0x4d,
+0x52,
+0x42,
+0x48,
+0x4d,
+0x49,
+0x4e,
+0x48,
+0x70,
+0x4d,
+0x52,
+0x42,
+0x4c,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x70,
+0x4d,
+0x52,
+0x4c,
+0x48,
+0x4c,
+0x45,
+0x4e,
+0x48,
+0x70,
+0x4d,
+0x52,
+0x4c,
+0x4c,
+0x4c,
+0x45,
+0x4e,
+0x4c,
+0x72,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x4c,
+0x45,
+0x4e,
+0x4c,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x72,
+0x4d,
+0x49,
+0x4e,
+0x48,
+0x4c,
+0x45,
+0x4e,
+0x48,
+0x4d,
+0x41,
+0x58,
+0x48,
+0xa0,
+0x14,
+0x95,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x72,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x1,
+0x4d,
+0x41,
+0x58,
+0x48,
+0xa0,
+0x11,
+0x95,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x1,
+0x74,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x1,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x74,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x1,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0xa0,
+0x44,
+0x7,
+0x93,
+0x4d,
+0x41,
+0x58,
+0x48,
+0x0,
+0x8,
+0x4d,
+0x52,
+0x33,
+0x32,
+0x11,
+0x1f,
+0xa,
+0x1c,
+0x87,
+0x17,
+0x0,
+0x0,
+0xc,
+0x3,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0x0,
+0xfe,
+0xff,
+0xff,
+0xff,
+0x0,
+0x0,
+0x0,
+0x0,
+0xff,
+0xff,
+0xff,
+0xff,
+0x79,
+0x0,
+0x8a,
+0x4d,
+0x52,
+0x33,
+0x32,
+0xa,
+0xa,
+0x4d,
+0x49,
+0x4e,
+0x5f,
+0x8a,
+0x4d,
+0x52,
+0x33,
+0x32,
+0xa,
+0xe,
+0x4d,
+0x41,
+0x58,
+0x5f,
+0x8a,
+0x4d,
+0x52,
+0x33,
+0x32,
+0xa,
+0x16,
+0x4c,
+0x45,
+0x4e,
+0x5f,
+0x70,
+0x4d,
+0x49,
+0x4e,
+0x4c,
+0x4d,
+0x49,
+0x4e,
+0x5f,
+0x70,
+0x4d,
+0x41,
+0x58,
+0x4c,
+0x4d,
+0x41,
+0x58,
+0x5f,
+0x70,
+0x4c,
+0x45,
+0x4e,
+0x4c,
+0x4c,
+0x45,
+0x4e,
+0x5f,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x4d,
+0x52,
+0x33,
+0x32,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x4d,
+0x52,
+0x36,
+0x34,
+0x14,
+0x24,
+0x4d,
+0x50,
+0x58,
+0x4d,
+0x1,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x99,
+0x68,
+0x0,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0x70,
+0x4d,
+0x50,
+0x58,
+0x5f,
+0x60,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xa4,
+0x60,
+0x14,
+0x28,
+0x4d,
+0x4f,
+0x53,
+0x54,
+0x4,
+0x5b,
+0x23,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0xff,
+0xff,
+0x70,
+0x99,
+0x68,
+0x0,
+0x4d,
+0x53,
+0x45,
+0x4c,
+0x70,
+0x69,
+0x4d,
+0x4f,
+0x45,
+0x56,
+0x70,
+0x6a,
+0x4d,
+0x4f,
+0x53,
+0x43,
+0x5b,
+0x27,
+0x4d,
+0x4c,
+0x43,
+0x4b,
+0x10,
+0x42,
+0xa,
+0x5f,
+0x47,
+0x50,
+0x45,
+0x8,
+0x5f,
+0x48,
+0x49,
+0x44,
+0xd,
+0x41,
+0x43,
+0x50,
+0x49,
+0x30,
+0x30,
+0x30,
+0x36,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x30,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x31,
+0x0,
+0x14,
+0x10,
+0x5f,
+0x45,
+0x30,
+0x32,
+0x0,
+0x5c,
+0x2e,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x52,
+0x53,
+0x43,
+0x14,
+0x19,
+0x5f,
+0x45,
+0x30,
+0x33,
+0x0,
+0x5c,
+0x2f,
+0x4,
+0x5f,
+0x53,
+0x42,
+0x5f,
+0x50,
+0x43,
+0x49,
+0x30,
+0x4d,
+0x48,
+0x50,
+0x44,
+0x4d,
+0x53,
+0x43,
+0x4e,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x34,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x35,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x36,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x37,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x38,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x39,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x41,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x42,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x43,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x44,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x45,
+0x0,
+0x14,
+0x6,
+0x5f,
+0x4c,
+0x30,
+0x46,
+0x0
+};
diff --git a/hw/i386/smbios.c b/hw/i386/smbios.c
new file mode 100644
index 00000000..1341e023
--- /dev/null
+++ b/hw/i386/smbios.c
@@ -0,0 +1,1102 @@
+/*
+ * SMBIOS Support
+ *
+ * Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * Authors:
+ * Alex Williamson <alex.williamson@hp.com>
+ * Markus Armbruster <armbru@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/cpus.h"
+#include "hw/i386/pc.h"
+#include "hw/i386/smbios.h"
+#include "hw/loader.h"
+
+
+/* legacy structures and constants for <= 2.0 machines */
+struct smbios_header {
+ uint16_t length;
+ uint8_t type;
+} QEMU_PACKED;
+
+struct smbios_field {
+ struct smbios_header header;
+ uint8_t type;
+ uint16_t offset;
+ uint8_t data[];
+} QEMU_PACKED;
+
+struct smbios_table {
+ struct smbios_header header;
+ uint8_t data[];
+} QEMU_PACKED;
+
+#define SMBIOS_FIELD_ENTRY 0
+#define SMBIOS_TABLE_ENTRY 1
+
+static uint8_t *smbios_entries;
+static size_t smbios_entries_len;
+static bool smbios_legacy = true;
+static bool smbios_uuid_encoded = true;
+/* end: legacy structures & constants for <= 2.0 machines */
+
+
+static uint8_t *smbios_tables;
+static size_t smbios_tables_len;
+static unsigned smbios_table_max;
+static unsigned smbios_table_cnt;
+static struct smbios_entry_point ep;
+
+static int smbios_type4_count = 0;
+static bool smbios_immutable;
+static bool smbios_have_defaults;
+static uint32_t smbios_cpuid_version, smbios_cpuid_features, smbios_smp_sockets;
+
+static DECLARE_BITMAP(have_binfile_bitmap, SMBIOS_MAX_TYPE+1);
+static DECLARE_BITMAP(have_fields_bitmap, SMBIOS_MAX_TYPE+1);
+
+static struct {
+ const char *vendor, *version, *date;
+ bool have_major_minor, uefi;
+ uint8_t major, minor;
+} type0;
+
+static struct {
+ const char *manufacturer, *product, *version, *serial, *sku, *family;
+ /* uuid is in qemu_uuid[] */
+} type1;
+
+static struct {
+ const char *manufacturer, *product, *version, *serial, *asset, *location;
+} type2;
+
+static struct {
+ const char *manufacturer, *version, *serial, *asset, *sku;
+} type3;
+
+static struct {
+ const char *sock_pfx, *manufacturer, *version, *serial, *asset, *part;
+} type4;
+
+static struct {
+ const char *loc_pfx, *bank, *manufacturer, *serial, *asset, *part;
+ uint16_t speed;
+} type17;
+
+static QemuOptsList qemu_smbios_opts = {
+ .name = "smbios",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_smbios_opts.head),
+ .desc = {
+ /*
+ * no elements => accept any params
+ * validation will happen later
+ */
+ { /* end of list */ }
+ }
+};
+
+static const QemuOptDesc qemu_smbios_file_opts[] = {
+ {
+ .name = "file",
+ .type = QEMU_OPT_STRING,
+ .help = "binary file containing an SMBIOS element",
+ },
+ { /* end of list */ }
+};
+
+static const QemuOptDesc qemu_smbios_type0_opts[] = {
+ {
+ .name = "type",
+ .type = QEMU_OPT_NUMBER,
+ .help = "SMBIOS element type",
+ },{
+ .name = "vendor",
+ .type = QEMU_OPT_STRING,
+ .help = "vendor name",
+ },{
+ .name = "version",
+ .type = QEMU_OPT_STRING,
+ .help = "version number",
+ },{
+ .name = "date",
+ .type = QEMU_OPT_STRING,
+ .help = "release date",
+ },{
+ .name = "release",
+ .type = QEMU_OPT_STRING,
+ .help = "revision number",
+ },{
+ .name = "uefi",
+ .type = QEMU_OPT_BOOL,
+ .help = "uefi support",
+ },
+ { /* end of list */ }
+};
+
+static const QemuOptDesc qemu_smbios_type1_opts[] = {
+ {
+ .name = "type",
+ .type = QEMU_OPT_NUMBER,
+ .help = "SMBIOS element type",
+ },{
+ .name = "manufacturer",
+ .type = QEMU_OPT_STRING,
+ .help = "manufacturer name",
+ },{
+ .name = "product",
+ .type = QEMU_OPT_STRING,
+ .help = "product name",
+ },{
+ .name = "version",
+ .type = QEMU_OPT_STRING,
+ .help = "version number",
+ },{
+ .name = "serial",
+ .type = QEMU_OPT_STRING,
+ .help = "serial number",
+ },{
+ .name = "uuid",
+ .type = QEMU_OPT_STRING,
+ .help = "UUID",
+ },{
+ .name = "sku",
+ .type = QEMU_OPT_STRING,
+ .help = "SKU number",
+ },{
+ .name = "family",
+ .type = QEMU_OPT_STRING,
+ .help = "family name",
+ },
+ { /* end of list */ }
+};
+
+static const QemuOptDesc qemu_smbios_type2_opts[] = {
+ {
+ .name = "type",
+ .type = QEMU_OPT_NUMBER,
+ .help = "SMBIOS element type",
+ },{
+ .name = "manufacturer",
+ .type = QEMU_OPT_STRING,
+ .help = "manufacturer name",
+ },{
+ .name = "product",
+ .type = QEMU_OPT_STRING,
+ .help = "product name",
+ },{
+ .name = "version",
+ .type = QEMU_OPT_STRING,
+ .help = "version number",
+ },{
+ .name = "serial",
+ .type = QEMU_OPT_STRING,
+ .help = "serial number",
+ },{
+ .name = "asset",
+ .type = QEMU_OPT_STRING,
+ .help = "asset tag number",
+ },{
+ .name = "location",
+ .type = QEMU_OPT_STRING,
+ .help = "location in chassis",
+ },
+ { /* end of list */ }
+};
+
+static const QemuOptDesc qemu_smbios_type3_opts[] = {
+ {
+ .name = "type",
+ .type = QEMU_OPT_NUMBER,
+ .help = "SMBIOS element type",
+ },{
+ .name = "manufacturer",
+ .type = QEMU_OPT_STRING,
+ .help = "manufacturer name",
+ },{
+ .name = "version",
+ .type = QEMU_OPT_STRING,
+ .help = "version number",
+ },{
+ .name = "serial",
+ .type = QEMU_OPT_STRING,
+ .help = "serial number",
+ },{
+ .name = "asset",
+ .type = QEMU_OPT_STRING,
+ .help = "asset tag number",
+ },{
+ .name = "sku",
+ .type = QEMU_OPT_STRING,
+ .help = "SKU number",
+ },
+ { /* end of list */ }
+};
+
+static const QemuOptDesc qemu_smbios_type4_opts[] = {
+ {
+ .name = "type",
+ .type = QEMU_OPT_NUMBER,
+ .help = "SMBIOS element type",
+ },{
+ .name = "sock_pfx",
+ .type = QEMU_OPT_STRING,
+ .help = "socket designation string prefix",
+ },{
+ .name = "manufacturer",
+ .type = QEMU_OPT_STRING,
+ .help = "manufacturer name",
+ },{
+ .name = "version",
+ .type = QEMU_OPT_STRING,
+ .help = "version number",
+ },{
+ .name = "serial",
+ .type = QEMU_OPT_STRING,
+ .help = "serial number",
+ },{
+ .name = "asset",
+ .type = QEMU_OPT_STRING,
+ .help = "asset tag number",
+ },{
+ .name = "part",
+ .type = QEMU_OPT_STRING,
+ .help = "part number",
+ },
+ { /* end of list */ }
+};
+
+static const QemuOptDesc qemu_smbios_type17_opts[] = {
+ {
+ .name = "type",
+ .type = QEMU_OPT_NUMBER,
+ .help = "SMBIOS element type",
+ },{
+ .name = "loc_pfx",
+ .type = QEMU_OPT_STRING,
+ .help = "device locator string prefix",
+ },{
+ .name = "bank",
+ .type = QEMU_OPT_STRING,
+ .help = "bank locator string",
+ },{
+ .name = "manufacturer",
+ .type = QEMU_OPT_STRING,
+ .help = "manufacturer name",
+ },{
+ .name = "serial",
+ .type = QEMU_OPT_STRING,
+ .help = "serial number",
+ },{
+ .name = "asset",
+ .type = QEMU_OPT_STRING,
+ .help = "asset tag number",
+ },{
+ .name = "part",
+ .type = QEMU_OPT_STRING,
+ .help = "part number",
+ },{
+ .name = "speed",
+ .type = QEMU_OPT_NUMBER,
+ .help = "maximum capable speed",
+ },
+ { /* end of list */ }
+};
+
+static void smbios_register_config(void)
+{
+ qemu_add_opts(&qemu_smbios_opts);
+}
+
+machine_init(smbios_register_config);
+
+static void smbios_validate_table(void)
+{
+ uint32_t expect_t4_count = smbios_legacy ? smp_cpus : smbios_smp_sockets;
+
+ if (smbios_type4_count && smbios_type4_count != expect_t4_count) {
+ error_report("Expected %d SMBIOS Type 4 tables, got %d instead",
+ expect_t4_count, smbios_type4_count);
+ exit(1);
+ }
+}
+
+
+/* legacy setup functions for <= 2.0 machines */
+static void smbios_add_field(int type, int offset, const void *data, size_t len)
+{
+ struct smbios_field *field;
+
+ if (!smbios_entries) {
+ smbios_entries_len = sizeof(uint16_t);
+ smbios_entries = g_malloc0(smbios_entries_len);
+ }
+ smbios_entries = g_realloc(smbios_entries, smbios_entries_len +
+ sizeof(*field) + len);
+ field = (struct smbios_field *)(smbios_entries + smbios_entries_len);
+ field->header.type = SMBIOS_FIELD_ENTRY;
+ field->header.length = cpu_to_le16(sizeof(*field) + len);
+
+ field->type = type;
+ field->offset = cpu_to_le16(offset);
+ memcpy(field->data, data, len);
+
+ smbios_entries_len += sizeof(*field) + len;
+ (*(uint16_t *)smbios_entries) =
+ cpu_to_le16(le16_to_cpu(*(uint16_t *)smbios_entries) + 1);
+}
+
+static void smbios_maybe_add_str(int type, int offset, const char *data)
+{
+ if (data) {
+ smbios_add_field(type, offset, data, strlen(data) + 1);
+ }
+}
+
+static void smbios_build_type_0_fields(void)
+{
+ smbios_maybe_add_str(0, offsetof(struct smbios_type_0, vendor_str),
+ type0.vendor);
+ smbios_maybe_add_str(0, offsetof(struct smbios_type_0, bios_version_str),
+ type0.version);
+ smbios_maybe_add_str(0, offsetof(struct smbios_type_0,
+ bios_release_date_str),
+ type0.date);
+ if (type0.have_major_minor) {
+ smbios_add_field(0, offsetof(struct smbios_type_0,
+ system_bios_major_release),
+ &type0.major, 1);
+ smbios_add_field(0, offsetof(struct smbios_type_0,
+ system_bios_minor_release),
+ &type0.minor, 1);
+ }
+}
+
+static void smbios_build_type_1_fields(void)
+{
+ smbios_maybe_add_str(1, offsetof(struct smbios_type_1, manufacturer_str),
+ type1.manufacturer);
+ smbios_maybe_add_str(1, offsetof(struct smbios_type_1, product_name_str),
+ type1.product);
+ smbios_maybe_add_str(1, offsetof(struct smbios_type_1, version_str),
+ type1.version);
+ smbios_maybe_add_str(1, offsetof(struct smbios_type_1, serial_number_str),
+ type1.serial);
+ smbios_maybe_add_str(1, offsetof(struct smbios_type_1, sku_number_str),
+ type1.sku);
+ smbios_maybe_add_str(1, offsetof(struct smbios_type_1, family_str),
+ type1.family);
+ if (qemu_uuid_set) {
+ /* We don't encode the UUID in the "wire format" here because this
+ * function is for legacy mode and needs to keep the guest ABI, and
+ * because we don't know what's the SMBIOS version advertised by the
+ * BIOS.
+ */
+ smbios_add_field(1, offsetof(struct smbios_type_1, uuid),
+ qemu_uuid, 16);
+ }
+}
+
+uint8_t *smbios_get_table_legacy(size_t *length)
+{
+ if (!smbios_legacy) {
+ *length = 0;
+ return NULL;
+ }
+
+ if (!smbios_immutable) {
+ smbios_build_type_0_fields();
+ smbios_build_type_1_fields();
+ smbios_validate_table();
+ smbios_immutable = true;
+ }
+ *length = smbios_entries_len;
+ return smbios_entries;
+}
+/* end: legacy setup functions for <= 2.0 machines */
+
+
+static bool smbios_skip_table(uint8_t type, bool required_table)
+{
+ if (test_bit(type, have_binfile_bitmap)) {
+ return true; /* user provided their own binary blob(s) */
+ }
+ if (test_bit(type, have_fields_bitmap)) {
+ return false; /* user provided fields via command line */
+ }
+ if (smbios_have_defaults && required_table) {
+ return false; /* we're building tables, and this one's required */
+ }
+ return true;
+}
+
+#define SMBIOS_BUILD_TABLE_PRE(tbl_type, tbl_handle, tbl_required) \
+ struct smbios_type_##tbl_type *t; \
+ size_t t_off; /* table offset into smbios_tables */ \
+ int str_index = 0; \
+ do { \
+ /* should we skip building this table ? */ \
+ if (smbios_skip_table(tbl_type, tbl_required)) { \
+ return; \
+ } \
+ \
+ /* use offset of table t within smbios_tables */ \
+ /* (pointer must be updated after each realloc) */ \
+ t_off = smbios_tables_len; \
+ smbios_tables_len += sizeof(*t); \
+ smbios_tables = g_realloc(smbios_tables, smbios_tables_len); \
+ t = (struct smbios_type_##tbl_type *)(smbios_tables + t_off); \
+ \
+ t->header.type = tbl_type; \
+ t->header.length = sizeof(*t); \
+ t->header.handle = cpu_to_le16(tbl_handle); \
+ } while (0)
+
+#define SMBIOS_TABLE_SET_STR(tbl_type, field, value) \
+ do { \
+ int len = (value != NULL) ? strlen(value) + 1 : 0; \
+ if (len > 1) { \
+ smbios_tables = g_realloc(smbios_tables, \
+ smbios_tables_len + len); \
+ memcpy(smbios_tables + smbios_tables_len, value, len); \
+ smbios_tables_len += len; \
+ /* update pointer post-realloc */ \
+ t = (struct smbios_type_##tbl_type *)(smbios_tables + t_off); \
+ t->field = ++str_index; \
+ } else { \
+ t->field = 0; \
+ } \
+ } while (0)
+
+#define SMBIOS_BUILD_TABLE_POST \
+ do { \
+ size_t term_cnt, t_size; \
+ \
+ /* add '\0' terminator (add two if no strings defined) */ \
+ term_cnt = (str_index == 0) ? 2 : 1; \
+ smbios_tables = g_realloc(smbios_tables, \
+ smbios_tables_len + term_cnt); \
+ memset(smbios_tables + smbios_tables_len, 0, term_cnt); \
+ smbios_tables_len += term_cnt; \
+ \
+ /* update smbios max. element size */ \
+ t_size = smbios_tables_len - t_off; \
+ if (t_size > smbios_table_max) { \
+ smbios_table_max = t_size; \
+ } \
+ \
+ /* update smbios element count */ \
+ smbios_table_cnt++; \
+ } while (0)
+
+static void smbios_build_type_0_table(void)
+{
+ SMBIOS_BUILD_TABLE_PRE(0, 0x000, false); /* optional, leave up to BIOS */
+
+ SMBIOS_TABLE_SET_STR(0, vendor_str, type0.vendor);
+ SMBIOS_TABLE_SET_STR(0, bios_version_str, type0.version);
+
+ t->bios_starting_address_segment = cpu_to_le16(0xE800); /* from SeaBIOS */
+
+ SMBIOS_TABLE_SET_STR(0, bios_release_date_str, type0.date);
+
+ t->bios_rom_size = 0; /* hardcoded in SeaBIOS with FIXME comment */
+
+ t->bios_characteristics = cpu_to_le64(0x08); /* Not supported */
+ t->bios_characteristics_extension_bytes[0] = 0;
+ t->bios_characteristics_extension_bytes[1] = 0x14; /* TCD/SVVP | VM */
+ if (type0.uefi) {
+ t->bios_characteristics_extension_bytes[1] |= 0x08; /* |= UEFI */
+ }
+
+ if (type0.have_major_minor) {
+ t->system_bios_major_release = type0.major;
+ t->system_bios_minor_release = type0.minor;
+ } else {
+ t->system_bios_major_release = 0;
+ t->system_bios_minor_release = 0;
+ }
+
+ /* hardcoded in SeaBIOS */
+ t->embedded_controller_major_release = 0xFF;
+ t->embedded_controller_minor_release = 0xFF;
+
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+/* Encode UUID from the big endian encoding described on RFC4122 to the wire
+ * format specified by SMBIOS version 2.6.
+ */
+static void smbios_encode_uuid(struct smbios_uuid *uuid, const uint8_t *buf)
+{
+ memcpy(uuid, buf, 16);
+ if (smbios_uuid_encoded) {
+ uuid->time_low = bswap32(uuid->time_low);
+ uuid->time_mid = bswap16(uuid->time_mid);
+ uuid->time_hi_and_version = bswap16(uuid->time_hi_and_version);
+ }
+}
+
+static void smbios_build_type_1_table(void)
+{
+ SMBIOS_BUILD_TABLE_PRE(1, 0x100, true); /* required */
+
+ SMBIOS_TABLE_SET_STR(1, manufacturer_str, type1.manufacturer);
+ SMBIOS_TABLE_SET_STR(1, product_name_str, type1.product);
+ SMBIOS_TABLE_SET_STR(1, version_str, type1.version);
+ SMBIOS_TABLE_SET_STR(1, serial_number_str, type1.serial);
+ if (qemu_uuid_set) {
+ smbios_encode_uuid(&t->uuid, qemu_uuid);
+ } else {
+ memset(&t->uuid, 0, 16);
+ }
+ t->wake_up_type = 0x06; /* power switch */
+ SMBIOS_TABLE_SET_STR(1, sku_number_str, type1.sku);
+ SMBIOS_TABLE_SET_STR(1, family_str, type1.family);
+
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+static void smbios_build_type_2_table(void)
+{
+ SMBIOS_BUILD_TABLE_PRE(2, 0x200, false); /* optional */
+
+ SMBIOS_TABLE_SET_STR(2, manufacturer_str, type2.manufacturer);
+ SMBIOS_TABLE_SET_STR(2, product_str, type2.product);
+ SMBIOS_TABLE_SET_STR(2, version_str, type2.version);
+ SMBIOS_TABLE_SET_STR(2, serial_number_str, type2.serial);
+ SMBIOS_TABLE_SET_STR(2, asset_tag_number_str, type2.asset);
+ t->feature_flags = 0x01; /* Motherboard */
+ SMBIOS_TABLE_SET_STR(2, location_str, type2.location);
+ t->chassis_handle = cpu_to_le16(0x300); /* Type 3 (System enclosure) */
+ t->board_type = 0x0A; /* Motherboard */
+ t->contained_element_count = 0;
+
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+static void smbios_build_type_3_table(void)
+{
+ SMBIOS_BUILD_TABLE_PRE(3, 0x300, true); /* required */
+
+ SMBIOS_TABLE_SET_STR(3, manufacturer_str, type3.manufacturer);
+ t->type = 0x01; /* Other */
+ SMBIOS_TABLE_SET_STR(3, version_str, type3.version);
+ SMBIOS_TABLE_SET_STR(3, serial_number_str, type3.serial);
+ SMBIOS_TABLE_SET_STR(3, asset_tag_number_str, type3.asset);
+ t->boot_up_state = 0x03; /* Safe */
+ t->power_supply_state = 0x03; /* Safe */
+ t->thermal_state = 0x03; /* Safe */
+ t->security_status = 0x02; /* Unknown */
+ t->oem_defined = cpu_to_le32(0);
+ t->height = 0;
+ t->number_of_power_cords = 0;
+ t->contained_element_count = 0;
+ SMBIOS_TABLE_SET_STR(3, sku_number_str, type3.sku);
+
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+static void smbios_build_type_4_table(unsigned instance)
+{
+ char sock_str[128];
+
+ SMBIOS_BUILD_TABLE_PRE(4, 0x400 + instance, true); /* required */
+
+ snprintf(sock_str, sizeof(sock_str), "%s%2x", type4.sock_pfx, instance);
+ SMBIOS_TABLE_SET_STR(4, socket_designation_str, sock_str);
+ t->processor_type = 0x03; /* CPU */
+ t->processor_family = 0x01; /* Other */
+ SMBIOS_TABLE_SET_STR(4, processor_manufacturer_str, type4.manufacturer);
+ t->processor_id[0] = cpu_to_le32(smbios_cpuid_version);
+ t->processor_id[1] = cpu_to_le32(smbios_cpuid_features);
+ SMBIOS_TABLE_SET_STR(4, processor_version_str, type4.version);
+ t->voltage = 0;
+ t->external_clock = cpu_to_le16(0); /* Unknown */
+ /* SVVP requires max_speed and current_speed to not be unknown. */
+ t->max_speed = cpu_to_le16(2000); /* 2000 MHz */
+ t->current_speed = cpu_to_le16(2000); /* 2000 MHz */
+ t->status = 0x41; /* Socket populated, CPU enabled */
+ t->processor_upgrade = 0x01; /* Other */
+ t->l1_cache_handle = cpu_to_le16(0xFFFF); /* N/A */
+ t->l2_cache_handle = cpu_to_le16(0xFFFF); /* N/A */
+ t->l3_cache_handle = cpu_to_le16(0xFFFF); /* N/A */
+ SMBIOS_TABLE_SET_STR(4, serial_number_str, type4.serial);
+ SMBIOS_TABLE_SET_STR(4, asset_tag_number_str, type4.asset);
+ SMBIOS_TABLE_SET_STR(4, part_number_str, type4.part);
+ t->core_count = t->core_enabled = smp_cores;
+ t->thread_count = smp_threads;
+ t->processor_characteristics = cpu_to_le16(0x02); /* Unknown */
+ t->processor_family2 = cpu_to_le16(0x01); /* Other */
+
+ SMBIOS_BUILD_TABLE_POST;
+ smbios_type4_count++;
+}
+
+#define ONE_KB ((ram_addr_t)1 << 10)
+#define ONE_MB ((ram_addr_t)1 << 20)
+#define ONE_GB ((ram_addr_t)1 << 30)
+
+#define MAX_T16_STD_SZ 0x80000000 /* 2T in Kilobytes */
+
+static void smbios_build_type_16_table(unsigned dimm_cnt)
+{
+ uint64_t size_kb;
+
+ SMBIOS_BUILD_TABLE_PRE(16, 0x1000, true); /* required */
+
+ t->location = 0x01; /* Other */
+ t->use = 0x03; /* System memory */
+ t->error_correction = 0x06; /* Multi-bit ECC (for Microsoft, per SeaBIOS) */
+ size_kb = QEMU_ALIGN_UP(ram_size, ONE_KB) / ONE_KB;
+ if (size_kb < MAX_T16_STD_SZ) {
+ t->maximum_capacity = cpu_to_le32(size_kb);
+ t->extended_maximum_capacity = cpu_to_le64(0);
+ } else {
+ t->maximum_capacity = cpu_to_le32(MAX_T16_STD_SZ);
+ t->extended_maximum_capacity = cpu_to_le64(ram_size);
+ }
+ t->memory_error_information_handle = cpu_to_le16(0xFFFE); /* Not provided */
+ t->number_of_memory_devices = cpu_to_le16(dimm_cnt);
+
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+#define MAX_T17_STD_SZ 0x7FFF /* (32G - 1M), in Megabytes */
+#define MAX_T17_EXT_SZ 0x80000000 /* 2P, in Megabytes */
+
+static void smbios_build_type_17_table(unsigned instance, uint64_t size)
+{
+ char loc_str[128];
+ uint64_t size_mb;
+
+ SMBIOS_BUILD_TABLE_PRE(17, 0x1100 + instance, true); /* required */
+
+ t->physical_memory_array_handle = cpu_to_le16(0x1000); /* Type 16 above */
+ t->memory_error_information_handle = cpu_to_le16(0xFFFE); /* Not provided */
+ t->total_width = cpu_to_le16(0xFFFF); /* Unknown */
+ t->data_width = cpu_to_le16(0xFFFF); /* Unknown */
+ size_mb = QEMU_ALIGN_UP(size, ONE_MB) / ONE_MB;
+ if (size_mb < MAX_T17_STD_SZ) {
+ t->size = cpu_to_le16(size_mb);
+ t->extended_size = cpu_to_le32(0);
+ } else {
+ assert(size_mb < MAX_T17_EXT_SZ);
+ t->size = cpu_to_le16(MAX_T17_STD_SZ);
+ t->extended_size = cpu_to_le32(size_mb);
+ }
+ t->form_factor = 0x09; /* DIMM */
+ t->device_set = 0; /* Not in a set */
+ snprintf(loc_str, sizeof(loc_str), "%s %d", type17.loc_pfx, instance);
+ SMBIOS_TABLE_SET_STR(17, device_locator_str, loc_str);
+ SMBIOS_TABLE_SET_STR(17, bank_locator_str, type17.bank);
+ t->memory_type = 0x07; /* RAM */
+ t->type_detail = cpu_to_le16(0x02); /* Other */
+ t->speed = cpu_to_le16(type17.speed);
+ SMBIOS_TABLE_SET_STR(17, manufacturer_str, type17.manufacturer);
+ SMBIOS_TABLE_SET_STR(17, serial_number_str, type17.serial);
+ SMBIOS_TABLE_SET_STR(17, asset_tag_number_str, type17.asset);
+ SMBIOS_TABLE_SET_STR(17, part_number_str, type17.part);
+ t->attributes = 0; /* Unknown */
+ t->configured_clock_speed = t->speed; /* reuse value for max speed */
+ t->minimum_voltage = cpu_to_le16(0); /* Unknown */
+ t->maximum_voltage = cpu_to_le16(0); /* Unknown */
+ t->configured_voltage = cpu_to_le16(0); /* Unknown */
+
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+static void smbios_build_type_19_table(unsigned instance,
+ uint64_t start, uint64_t size)
+{
+ uint64_t end, start_kb, end_kb;
+
+ SMBIOS_BUILD_TABLE_PRE(19, 0x1300 + instance, true); /* required */
+
+ end = start + size - 1;
+ assert(end > start);
+ start_kb = start / ONE_KB;
+ end_kb = end / ONE_KB;
+ if (start_kb < UINT32_MAX && end_kb < UINT32_MAX) {
+ t->starting_address = cpu_to_le32(start_kb);
+ t->ending_address = cpu_to_le32(end_kb);
+ t->extended_starting_address =
+ t->extended_ending_address = cpu_to_le64(0);
+ } else {
+ t->starting_address = t->ending_address = cpu_to_le32(UINT32_MAX);
+ t->extended_starting_address = cpu_to_le64(start);
+ t->extended_ending_address = cpu_to_le64(end);
+ }
+ t->memory_array_handle = cpu_to_le16(0x1000); /* Type 16 above */
+ t->partition_width = 1; /* One device per row */
+
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+static void smbios_build_type_32_table(void)
+{
+ SMBIOS_BUILD_TABLE_PRE(32, 0x2000, true); /* required */
+
+ memset(t->reserved, 0, 6);
+ t->boot_status = 0; /* No errors detected */
+
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+static void smbios_build_type_127_table(void)
+{
+ SMBIOS_BUILD_TABLE_PRE(127, 0x7F00, true); /* required */
+ SMBIOS_BUILD_TABLE_POST;
+}
+
+void smbios_set_cpuid(uint32_t version, uint32_t features)
+{
+ smbios_cpuid_version = version;
+ smbios_cpuid_features = features;
+}
+
+#define SMBIOS_SET_DEFAULT(field, value) \
+ if (!field) { \
+ field = value; \
+ }
+
+void smbios_set_defaults(const char *manufacturer, const char *product,
+ const char *version, bool legacy_mode,
+ bool uuid_encoded)
+{
+ smbios_have_defaults = true;
+ smbios_legacy = legacy_mode;
+ smbios_uuid_encoded = uuid_encoded;
+
+ /* drop unwanted version of command-line file blob(s) */
+ if (smbios_legacy) {
+ g_free(smbios_tables);
+ /* in legacy mode, also complain if fields were given for types > 1 */
+ if (find_next_bit(have_fields_bitmap,
+ SMBIOS_MAX_TYPE+1, 2) < SMBIOS_MAX_TYPE+1) {
+ error_report("can't process fields for smbios "
+ "types > 1 on machine versions < 2.1!");
+ exit(1);
+ }
+ } else {
+ g_free(smbios_entries);
+ }
+
+ SMBIOS_SET_DEFAULT(type1.manufacturer, manufacturer);
+ SMBIOS_SET_DEFAULT(type1.product, product);
+ SMBIOS_SET_DEFAULT(type1.version, version);
+ SMBIOS_SET_DEFAULT(type2.manufacturer, manufacturer);
+ SMBIOS_SET_DEFAULT(type2.product, product);
+ SMBIOS_SET_DEFAULT(type2.version, version);
+ SMBIOS_SET_DEFAULT(type3.manufacturer, manufacturer);
+ SMBIOS_SET_DEFAULT(type3.version, version);
+ SMBIOS_SET_DEFAULT(type4.sock_pfx, "CPU");
+ SMBIOS_SET_DEFAULT(type4.manufacturer, manufacturer);
+ SMBIOS_SET_DEFAULT(type4.version, version);
+ SMBIOS_SET_DEFAULT(type17.loc_pfx, "DIMM");
+ SMBIOS_SET_DEFAULT(type17.manufacturer, manufacturer);
+}
+
+static void smbios_entry_point_setup(void)
+{
+ memcpy(ep.anchor_string, "_SM_", 4);
+ memcpy(ep.intermediate_anchor_string, "_DMI_", 5);
+ ep.length = sizeof(struct smbios_entry_point);
+ ep.entry_point_revision = 0; /* formatted_area reserved, per spec v2.1+ */
+ memset(ep.formatted_area, 0, 5);
+
+ /* compliant with smbios spec v2.8 */
+ ep.smbios_major_version = 2;
+ ep.smbios_minor_version = 8;
+ ep.smbios_bcd_revision = 0x28;
+
+ /* set during table construction, but BIOS may override: */
+ ep.structure_table_length = cpu_to_le16(smbios_tables_len);
+ ep.max_structure_size = cpu_to_le16(smbios_table_max);
+ ep.number_of_structures = cpu_to_le16(smbios_table_cnt);
+
+ /* BIOS must recalculate: */
+ ep.checksum = 0;
+ ep.intermediate_checksum = 0;
+ ep.structure_table_address = cpu_to_le32(0);
+}
+
+void smbios_get_tables(uint8_t **tables, size_t *tables_len,
+ uint8_t **anchor, size_t *anchor_len)
+{
+ unsigned i, dimm_cnt, instance;
+
+ if (smbios_legacy) {
+ *tables = *anchor = NULL;
+ *tables_len = *anchor_len = 0;
+ return;
+ }
+
+ if (!smbios_immutable) {
+ smbios_build_type_0_table();
+ smbios_build_type_1_table();
+ smbios_build_type_2_table();
+ smbios_build_type_3_table();
+
+ smbios_smp_sockets = DIV_ROUND_UP(smp_cpus, smp_cores * smp_threads);
+ assert(smbios_smp_sockets >= 1);
+
+ for (i = 0; i < smbios_smp_sockets; i++) {
+ smbios_build_type_4_table(i);
+ }
+
+#define MAX_DIMM_SZ (16ll * ONE_GB)
+#define GET_DIMM_SZ ((i < dimm_cnt - 1) ? MAX_DIMM_SZ \
+ : ((ram_size - 1) % MAX_DIMM_SZ) + 1)
+
+ dimm_cnt = QEMU_ALIGN_UP(ram_size, MAX_DIMM_SZ) / MAX_DIMM_SZ;
+
+ smbios_build_type_16_table(dimm_cnt);
+
+ for (i = 0; i < dimm_cnt; i++) {
+ smbios_build_type_17_table(i, GET_DIMM_SZ);
+ }
+
+ for (i = 0, instance = 0; i < e820_get_num_entries(); i++) {
+ uint64_t address, length;
+ if (e820_get_entry(i, E820_RAM, &address, &length)) {
+ smbios_build_type_19_table(instance++, address, length);
+ }
+ }
+
+ smbios_build_type_32_table();
+ smbios_build_type_127_table();
+
+ smbios_validate_table();
+ smbios_entry_point_setup();
+ smbios_immutable = true;
+ }
+
+ /* return tables blob and entry point (anchor), and their sizes */
+ *tables = smbios_tables;
+ *tables_len = smbios_tables_len;
+ *anchor = (uint8_t *)&ep;
+ *anchor_len = sizeof(struct smbios_entry_point);
+}
+
+static void save_opt(const char **dest, QemuOpts *opts, const char *name)
+{
+ const char *val = qemu_opt_get(opts, name);
+
+ if (val) {
+ *dest = val;
+ }
+}
+
+void smbios_entry_add(QemuOpts *opts)
+{
+ Error *local_err = NULL;
+ const char *val;
+
+ assert(!smbios_immutable);
+
+ val = qemu_opt_get(opts, "file");
+ if (val) {
+ struct smbios_structure_header *header;
+ int size;
+ struct smbios_table *table; /* legacy mode only */
+
+ qemu_opts_validate(opts, qemu_smbios_file_opts, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ exit(1);
+ }
+
+ size = get_image_size(val);
+ if (size == -1 || size < sizeof(struct smbios_structure_header)) {
+ error_report("Cannot read SMBIOS file %s", val);
+ exit(1);
+ }
+
+ /*
+ * NOTE: standard double '\0' terminator expected, per smbios spec.
+ * (except in legacy mode, where the second '\0' is implicit and
+ * will be inserted by the BIOS).
+ */
+ smbios_tables = g_realloc(smbios_tables, smbios_tables_len + size);
+ header = (struct smbios_structure_header *)(smbios_tables +
+ smbios_tables_len);
+
+ if (load_image(val, (uint8_t *)header) != size) {
+ error_report("Failed to load SMBIOS file %s", val);
+ exit(1);
+ }
+
+ if (test_bit(header->type, have_fields_bitmap)) {
+ error_report("can't load type %d struct, fields already specified!",
+ header->type);
+ exit(1);
+ }
+ set_bit(header->type, have_binfile_bitmap);
+
+ if (header->type == 4) {
+ smbios_type4_count++;
+ }
+
+ smbios_tables_len += size;
+ if (size > smbios_table_max) {
+ smbios_table_max = size;
+ }
+ smbios_table_cnt++;
+
+ /* add a copy of the newly loaded blob to legacy smbios_entries */
+ /* NOTE: This code runs before smbios_set_defaults(), so we don't
+ * yet know which mode (legacy vs. aggregate-table) will be
+ * required. We therefore add the binary blob to both legacy
+ * (smbios_entries) and aggregate (smbios_tables) tables, and
+ * delete the one we don't need from smbios_set_defaults(),
+ * once we know which machine version has been requested.
+ */
+ if (!smbios_entries) {
+ smbios_entries_len = sizeof(uint16_t);
+ smbios_entries = g_malloc0(smbios_entries_len);
+ }
+ smbios_entries = g_realloc(smbios_entries, smbios_entries_len +
+ size + sizeof(*table));
+ table = (struct smbios_table *)(smbios_entries + smbios_entries_len);
+ table->header.type = SMBIOS_TABLE_ENTRY;
+ table->header.length = cpu_to_le16(sizeof(*table) + size);
+ memcpy(table->data, header, size);
+ smbios_entries_len += sizeof(*table) + size;
+ (*(uint16_t *)smbios_entries) =
+ cpu_to_le16(le16_to_cpu(*(uint16_t *)smbios_entries) + 1);
+ /* end: add a copy of the newly loaded blob to legacy smbios_entries */
+
+ return;
+ }
+
+ val = qemu_opt_get(opts, "type");
+ if (val) {
+ unsigned long type = strtoul(val, NULL, 0);
+
+ if (type > SMBIOS_MAX_TYPE) {
+ error_report("out of range!");
+ exit(1);
+ }
+
+ if (test_bit(type, have_binfile_bitmap)) {
+ error_report("can't add fields, binary file already loaded!");
+ exit(1);
+ }
+ set_bit(type, have_fields_bitmap);
+
+ switch (type) {
+ case 0:
+ qemu_opts_validate(opts, qemu_smbios_type0_opts, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ save_opt(&type0.vendor, opts, "vendor");
+ save_opt(&type0.version, opts, "version");
+ save_opt(&type0.date, opts, "date");
+ type0.uefi = qemu_opt_get_bool(opts, "uefi", false);
+
+ val = qemu_opt_get(opts, "release");
+ if (val) {
+ if (sscanf(val, "%hhu.%hhu", &type0.major, &type0.minor) != 2) {
+ error_report("Invalid release");
+ exit(1);
+ }
+ type0.have_major_minor = true;
+ }
+ return;
+ case 1:
+ qemu_opts_validate(opts, qemu_smbios_type1_opts, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ save_opt(&type1.manufacturer, opts, "manufacturer");
+ save_opt(&type1.product, opts, "product");
+ save_opt(&type1.version, opts, "version");
+ save_opt(&type1.serial, opts, "serial");
+ save_opt(&type1.sku, opts, "sku");
+ save_opt(&type1.family, opts, "family");
+
+ val = qemu_opt_get(opts, "uuid");
+ if (val) {
+ if (qemu_uuid_parse(val, qemu_uuid) != 0) {
+ error_report("Invalid UUID");
+ exit(1);
+ }
+ qemu_uuid_set = true;
+ }
+ return;
+ case 2:
+ qemu_opts_validate(opts, qemu_smbios_type2_opts, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ save_opt(&type2.manufacturer, opts, "manufacturer");
+ save_opt(&type2.product, opts, "product");
+ save_opt(&type2.version, opts, "version");
+ save_opt(&type2.serial, opts, "serial");
+ save_opt(&type2.asset, opts, "asset");
+ save_opt(&type2.location, opts, "location");
+ return;
+ case 3:
+ qemu_opts_validate(opts, qemu_smbios_type3_opts, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ save_opt(&type3.manufacturer, opts, "manufacturer");
+ save_opt(&type3.version, opts, "version");
+ save_opt(&type3.serial, opts, "serial");
+ save_opt(&type3.asset, opts, "asset");
+ save_opt(&type3.sku, opts, "sku");
+ return;
+ case 4:
+ qemu_opts_validate(opts, qemu_smbios_type4_opts, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ save_opt(&type4.sock_pfx, opts, "sock_pfx");
+ save_opt(&type4.manufacturer, opts, "manufacturer");
+ save_opt(&type4.version, opts, "version");
+ save_opt(&type4.serial, opts, "serial");
+ save_opt(&type4.asset, opts, "asset");
+ save_opt(&type4.part, opts, "part");
+ return;
+ case 17:
+ qemu_opts_validate(opts, qemu_smbios_type17_opts, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ exit(1);
+ }
+ save_opt(&type17.loc_pfx, opts, "loc_pfx");
+ save_opt(&type17.bank, opts, "bank");
+ save_opt(&type17.manufacturer, opts, "manufacturer");
+ save_opt(&type17.serial, opts, "serial");
+ save_opt(&type17.asset, opts, "asset");
+ save_opt(&type17.part, opts, "part");
+ type17.speed = qemu_opt_get_number(opts, "speed", 0);
+ return;
+ default:
+ error_report("Don't know how to build fields for SMBIOS type %ld",
+ type);
+ exit(1);
+ }
+ }
+
+ error_report("Must specify type= or file=");
+ exit(1);
+}
diff --git a/hw/i386/xen/Makefile.objs b/hw/i386/xen/Makefile.objs
new file mode 100644
index 00000000..801a68d3
--- /dev/null
+++ b/hw/i386/xen/Makefile.objs
@@ -0,0 +1 @@
+obj-y += xen_platform.o xen_apic.o xen_pvdevice.o
diff --git a/hw/i386/xen/xen_apic.c b/hw/i386/xen/xen_apic.c
new file mode 100644
index 00000000..f5acd6a0
--- /dev/null
+++ b/hw/i386/xen/xen_apic.c
@@ -0,0 +1,98 @@
+/*
+ * Xen basic APIC support
+ *
+ * Copyright (c) 2012 Citrix
+ *
+ * Authors:
+ * Wei Liu <wei.liu2@citrix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+#include "hw/i386/apic_internal.h"
+#include "hw/pci/msi.h"
+#include "hw/xen/xen.h"
+
+static uint64_t xen_apic_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return ~(uint64_t)0;
+}
+
+static void xen_apic_mem_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ if (size != sizeof(uint32_t)) {
+ fprintf(stderr, "Xen: APIC write data size = %d, invalid\n", size);
+ return;
+ }
+
+ xen_hvm_inject_msi(addr, data);
+}
+
+static const MemoryRegionOps xen_apic_io_ops = {
+ .read = xen_apic_mem_read,
+ .write = xen_apic_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void xen_apic_realize(DeviceState *dev, Error **errp)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+
+ s->vapic_control = 0;
+ memory_region_init_io(&s->io_memory, OBJECT(s), &xen_apic_io_ops, s,
+ "xen-apic-msi", APIC_SPACE_SIZE);
+
+#if defined(CONFIG_XEN_CTRL_INTERFACE_VERSION) \
+ && CONFIG_XEN_CTRL_INTERFACE_VERSION >= 420
+ msi_supported = true;
+#endif
+}
+
+static void xen_apic_set_base(APICCommonState *s, uint64_t val)
+{
+}
+
+static void xen_apic_set_tpr(APICCommonState *s, uint8_t val)
+{
+}
+
+static uint8_t xen_apic_get_tpr(APICCommonState *s)
+{
+ return 0;
+}
+
+static void xen_apic_vapic_base_update(APICCommonState *s)
+{
+}
+
+static void xen_apic_external_nmi(APICCommonState *s)
+{
+}
+
+static void xen_apic_class_init(ObjectClass *klass, void *data)
+{
+ APICCommonClass *k = APIC_COMMON_CLASS(klass);
+
+ k->realize = xen_apic_realize;
+ k->set_base = xen_apic_set_base;
+ k->set_tpr = xen_apic_set_tpr;
+ k->get_tpr = xen_apic_get_tpr;
+ k->vapic_base_update = xen_apic_vapic_base_update;
+ k->external_nmi = xen_apic_external_nmi;
+}
+
+static const TypeInfo xen_apic_info = {
+ .name = "xen-apic",
+ .parent = TYPE_APIC_COMMON,
+ .instance_size = sizeof(APICCommonState),
+ .class_init = xen_apic_class_init,
+};
+
+static void xen_apic_register_types(void)
+{
+ type_register_static(&xen_apic_info);
+}
+
+type_init(xen_apic_register_types)
diff --git a/hw/i386/xen/xen_platform.c b/hw/i386/xen/xen_platform.c
new file mode 100644
index 00000000..28b324a6
--- /dev/null
+++ b/hw/i386/xen/xen_platform.c
@@ -0,0 +1,450 @@
+/*
+ * XEN platform pci device, formerly known as the event channel device
+ *
+ * Copyright (c) 2003-2004 Intel Corp.
+ * Copyright (c) 2006 XenSource
+ *
+ * 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.
+ */
+
+#include <assert.h>
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/ide.h"
+#include "hw/pci/pci.h"
+#include "hw/irq.h"
+#include "hw/xen/xen_common.h"
+#include "hw/xen/xen_backend.h"
+#include "trace.h"
+#include "exec/address-spaces.h"
+#include "sysemu/block-backend.h"
+
+#include <xenguest.h>
+
+//#define DEBUG_PLATFORM
+
+#ifdef DEBUG_PLATFORM
+#define DPRINTF(fmt, ...) do { \
+ fprintf(stderr, "xen_platform: " fmt, ## __VA_ARGS__); \
+} while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+#define PFFLAG_ROM_LOCK 1 /* Sets whether ROM memory area is RW or RO */
+
+typedef struct PCIXenPlatformState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion fixed_io;
+ MemoryRegion bar;
+ MemoryRegion mmio_bar;
+ uint8_t flags; /* used only for version_id == 2 */
+ int drivers_blacklisted;
+ uint16_t driver_product_version;
+
+ /* Log from guest drivers */
+ char log_buffer[4096];
+ int log_buffer_off;
+} PCIXenPlatformState;
+
+#define TYPE_XEN_PLATFORM "xen-platform"
+#define XEN_PLATFORM(obj) \
+ OBJECT_CHECK(PCIXenPlatformState, (obj), TYPE_XEN_PLATFORM)
+
+#define XEN_PLATFORM_IOPORT 0x10
+
+/* Send bytes to syslog */
+static void log_writeb(PCIXenPlatformState *s, char val)
+{
+ if (val == '\n' || s->log_buffer_off == sizeof(s->log_buffer) - 1) {
+ /* Flush buffer */
+ s->log_buffer[s->log_buffer_off] = 0;
+ trace_xen_platform_log(s->log_buffer);
+ s->log_buffer_off = 0;
+ } else {
+ s->log_buffer[s->log_buffer_off++] = val;
+ }
+}
+
+/* Xen Platform, Fixed IOPort */
+#define UNPLUG_ALL_IDE_DISKS 1
+#define UNPLUG_ALL_NICS 2
+#define UNPLUG_AUX_IDE_DISKS 4
+
+static void unplug_nic(PCIBus *b, PCIDevice *d, void *o)
+{
+ /* We have to ignore passthrough devices */
+ if (pci_get_word(d->config + PCI_CLASS_DEVICE) ==
+ PCI_CLASS_NETWORK_ETHERNET
+ && strcmp(d->name, "xen-pci-passthrough") != 0) {
+ object_unparent(OBJECT(d));
+ }
+}
+
+static void pci_unplug_nics(PCIBus *bus)
+{
+ pci_for_each_device(bus, 0, unplug_nic, NULL);
+}
+
+static void unplug_disks(PCIBus *b, PCIDevice *d, void *o)
+{
+ /* We have to ignore passthrough devices */
+ if (pci_get_word(d->config + PCI_CLASS_DEVICE) ==
+ PCI_CLASS_STORAGE_IDE
+ && strcmp(d->name, "xen-pci-passthrough") != 0) {
+ pci_piix3_xen_ide_unplug(DEVICE(d));
+ }
+}
+
+static void pci_unplug_disks(PCIBus *bus)
+{
+ pci_for_each_device(bus, 0, unplug_disks, NULL);
+}
+
+static void platform_fixed_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
+{
+ PCIXenPlatformState *s = opaque;
+
+ switch (addr) {
+ case 0: {
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ /* Unplug devices. Value is a bitmask of which devices to
+ unplug, with bit 0 the IDE devices, bit 1 the network
+ devices, and bit 2 the non-primary-master IDE devices. */
+ if (val & UNPLUG_ALL_IDE_DISKS) {
+ DPRINTF("unplug disks\n");
+ blk_drain_all();
+ blk_flush_all();
+ pci_unplug_disks(pci_dev->bus);
+ }
+ if (val & UNPLUG_ALL_NICS) {
+ DPRINTF("unplug nics\n");
+ pci_unplug_nics(pci_dev->bus);
+ }
+ if (val & UNPLUG_AUX_IDE_DISKS) {
+ DPRINTF("unplug auxiliary disks not supported\n");
+ }
+ break;
+ }
+ case 2:
+ switch (val) {
+ case 1:
+ DPRINTF("Citrix Windows PV drivers loaded in guest\n");
+ break;
+ case 0:
+ DPRINTF("Guest claimed to be running PV product 0?\n");
+ break;
+ default:
+ DPRINTF("Unknown PV product %d loaded in guest\n", val);
+ break;
+ }
+ s->driver_product_version = val;
+ break;
+ }
+}
+
+static void platform_fixed_ioport_writel(void *opaque, uint32_t addr,
+ uint32_t val)
+{
+ switch (addr) {
+ case 0:
+ /* PV driver version */
+ break;
+ }
+}
+
+static void platform_fixed_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
+{
+ PCIXenPlatformState *s = opaque;
+
+ switch (addr) {
+ case 0: /* Platform flags */ {
+ hvmmem_type_t mem_type = (val & PFFLAG_ROM_LOCK) ?
+ HVMMEM_ram_ro : HVMMEM_ram_rw;
+ if (xc_hvm_set_mem_type(xen_xc, xen_domid, mem_type, 0xc0, 0x40)) {
+ DPRINTF("unable to change ro/rw state of ROM memory area!\n");
+ } else {
+ s->flags = val & PFFLAG_ROM_LOCK;
+ DPRINTF("changed ro/rw state of ROM memory area. now is %s state.\n",
+ (mem_type == HVMMEM_ram_ro ? "ro":"rw"));
+ }
+ break;
+ }
+ case 2:
+ log_writeb(s, val);
+ break;
+ }
+}
+
+static uint32_t platform_fixed_ioport_readw(void *opaque, uint32_t addr)
+{
+ PCIXenPlatformState *s = opaque;
+
+ switch (addr) {
+ case 0:
+ if (s->drivers_blacklisted) {
+ /* The drivers will recognise this magic number and refuse
+ * to do anything. */
+ return 0xd249;
+ } else {
+ /* Magic value so that you can identify the interface. */
+ return 0x49d2;
+ }
+ default:
+ return 0xffff;
+ }
+}
+
+static uint32_t platform_fixed_ioport_readb(void *opaque, uint32_t addr)
+{
+ PCIXenPlatformState *s = opaque;
+
+ switch (addr) {
+ case 0:
+ /* Platform flags */
+ return s->flags;
+ case 2:
+ /* Version number */
+ return 1;
+ default:
+ return 0xff;
+ }
+}
+
+static void platform_fixed_ioport_reset(void *opaque)
+{
+ PCIXenPlatformState *s = opaque;
+
+ platform_fixed_ioport_writeb(s, 0, 0);
+}
+
+static uint64_t platform_fixed_ioport_read(void *opaque,
+ hwaddr addr,
+ unsigned size)
+{
+ switch (size) {
+ case 1:
+ return platform_fixed_ioport_readb(opaque, addr);
+ case 2:
+ return platform_fixed_ioport_readw(opaque, addr);
+ default:
+ return -1;
+ }
+}
+
+static void platform_fixed_ioport_write(void *opaque, hwaddr addr,
+
+ uint64_t val, unsigned size)
+{
+ switch (size) {
+ case 1:
+ platform_fixed_ioport_writeb(opaque, addr, val);
+ break;
+ case 2:
+ platform_fixed_ioport_writew(opaque, addr, val);
+ break;
+ case 4:
+ platform_fixed_ioport_writel(opaque, addr, val);
+ break;
+ }
+}
+
+
+static const MemoryRegionOps platform_fixed_io_ops = {
+ .read = platform_fixed_ioport_read,
+ .write = platform_fixed_ioport_write,
+ .valid = {
+ .unaligned = true,
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = true,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void platform_fixed_ioport_init(PCIXenPlatformState* s)
+{
+ memory_region_init_io(&s->fixed_io, OBJECT(s), &platform_fixed_io_ops, s,
+ "xen-fixed", 16);
+ memory_region_add_subregion(get_system_io(), XEN_PLATFORM_IOPORT,
+ &s->fixed_io);
+}
+
+/* Xen Platform PCI Device */
+
+static uint64_t xen_platform_ioport_readb(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ if (addr == 0) {
+ return platform_fixed_ioport_readb(opaque, 0);
+ } else {
+ return ~0u;
+ }
+}
+
+static void xen_platform_ioport_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ PCIXenPlatformState *s = opaque;
+
+ switch (addr) {
+ case 0: /* Platform flags */
+ platform_fixed_ioport_writeb(opaque, 0, (uint32_t)val);
+ break;
+ case 8:
+ log_writeb(s, (uint32_t)val);
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps xen_pci_io_ops = {
+ .read = xen_platform_ioport_readb,
+ .write = xen_platform_ioport_writeb,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+};
+
+static void platform_ioport_bar_setup(PCIXenPlatformState *d)
+{
+ memory_region_init_io(&d->bar, OBJECT(d), &xen_pci_io_ops, d,
+ "xen-pci", 0x100);
+}
+
+static uint64_t platform_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ DPRINTF("Warning: attempted read from physical address "
+ "0x" TARGET_FMT_plx " in xen platform mmio space\n", addr);
+
+ return 0;
+}
+
+static void platform_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ DPRINTF("Warning: attempted write of 0x%"PRIx64" to physical "
+ "address 0x" TARGET_FMT_plx " in xen platform mmio space\n",
+ val, addr);
+}
+
+static const MemoryRegionOps platform_mmio_handler = {
+ .read = &platform_mmio_read,
+ .write = &platform_mmio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void platform_mmio_setup(PCIXenPlatformState *d)
+{
+ memory_region_init_io(&d->mmio_bar, OBJECT(d), &platform_mmio_handler, d,
+ "xen-mmio", 0x1000000);
+}
+
+static int xen_platform_post_load(void *opaque, int version_id)
+{
+ PCIXenPlatformState *s = opaque;
+
+ platform_fixed_ioport_writeb(s, 0, s->flags);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_xen_platform = {
+ .name = "platform",
+ .version_id = 4,
+ .minimum_version_id = 4,
+ .post_load = xen_platform_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PCIXenPlatformState),
+ VMSTATE_UINT8(flags, PCIXenPlatformState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int xen_platform_initfn(PCIDevice *dev)
+{
+ PCIXenPlatformState *d = XEN_PLATFORM(dev);
+ uint8_t *pci_conf;
+
+ pci_conf = dev->config;
+
+ pci_set_word(pci_conf + PCI_COMMAND, PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
+
+ pci_config_set_prog_interface(pci_conf, 0);
+
+ pci_conf[PCI_INTERRUPT_PIN] = 1;
+
+ platform_ioport_bar_setup(d);
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->bar);
+
+ /* reserve 16MB mmio address for share memory*/
+ platform_mmio_setup(d);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &d->mmio_bar);
+
+ platform_fixed_ioport_init(d);
+
+ return 0;
+}
+
+static void platform_reset(DeviceState *dev)
+{
+ PCIXenPlatformState *s = XEN_PLATFORM(dev);
+
+ platform_fixed_ioport_reset(s);
+}
+
+static void xen_platform_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = xen_platform_initfn;
+ k->vendor_id = PCI_VENDOR_ID_XEN;
+ k->device_id = PCI_DEVICE_ID_XEN_PLATFORM;
+ k->class_id = PCI_CLASS_OTHERS << 8 | 0x80;
+ k->subsystem_vendor_id = PCI_VENDOR_ID_XEN;
+ k->subsystem_id = PCI_DEVICE_ID_XEN_PLATFORM;
+ k->revision = 1;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->desc = "XEN platform pci device";
+ dc->reset = platform_reset;
+ dc->vmsd = &vmstate_xen_platform;
+}
+
+static const TypeInfo xen_platform_info = {
+ .name = TYPE_XEN_PLATFORM,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIXenPlatformState),
+ .class_init = xen_platform_class_init,
+};
+
+static void xen_platform_register_types(void)
+{
+ type_register_static(&xen_platform_info);
+}
+
+type_init(xen_platform_register_types)
diff --git a/hw/i386/xen/xen_pvdevice.c b/hw/i386/xen/xen_pvdevice.c
new file mode 100644
index 00000000..c2189473
--- /dev/null
+++ b/hw/i386/xen/xen_pvdevice.c
@@ -0,0 +1,135 @@
+/* Copyright (c) Citrix Systems Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "trace.h"
+
+#define TYPE_XEN_PV_DEVICE "xen-pvdevice"
+
+#define XEN_PV_DEVICE(obj) \
+ OBJECT_CHECK(XenPVDevice, (obj), TYPE_XEN_PV_DEVICE)
+
+typedef struct XenPVDevice {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+ uint16_t vendor_id;
+ uint16_t device_id;
+ uint8_t revision;
+ uint32_t size;
+ MemoryRegion mmio;
+} XenPVDevice;
+
+static uint64_t xen_pv_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ trace_xen_pv_mmio_read(addr);
+
+ return ~(uint64_t)0;
+}
+
+static void xen_pv_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ trace_xen_pv_mmio_write(addr);
+}
+
+static const MemoryRegionOps xen_pv_mmio_ops = {
+ .read = &xen_pv_mmio_read,
+ .write = &xen_pv_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static int xen_pv_init(PCIDevice *pci_dev)
+{
+ XenPVDevice *d = XEN_PV_DEVICE(pci_dev);
+ uint8_t *pci_conf;
+
+ /* device-id property must always be supplied */
+ if (d->device_id == 0xffff)
+ return -1;
+
+ pci_conf = pci_dev->config;
+
+ pci_set_word(pci_conf + PCI_VENDOR_ID, d->vendor_id);
+ pci_set_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID, d->vendor_id);
+ pci_set_word(pci_conf + PCI_DEVICE_ID, d->device_id);
+ pci_set_word(pci_conf + PCI_SUBSYSTEM_ID, d->device_id);
+ pci_set_byte(pci_conf + PCI_REVISION_ID, d->revision);
+
+ pci_set_word(pci_conf + PCI_COMMAND, PCI_COMMAND_MEMORY);
+
+ pci_config_set_prog_interface(pci_conf, 0);
+
+ pci_conf[PCI_INTERRUPT_PIN] = 1;
+
+ memory_region_init_io(&d->mmio, NULL, &xen_pv_mmio_ops, d,
+ "mmio", d->size);
+
+ pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &d->mmio);
+
+ return 0;
+}
+
+static Property xen_pv_props[] = {
+ DEFINE_PROP_UINT16("vendor-id", XenPVDevice, vendor_id, PCI_VENDOR_ID_XEN),
+ DEFINE_PROP_UINT16("device-id", XenPVDevice, device_id, 0xffff),
+ DEFINE_PROP_UINT8("revision", XenPVDevice, revision, 0x01),
+ DEFINE_PROP_UINT32("size", XenPVDevice, size, 0x400000),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void xen_pv_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = xen_pv_init;
+ k->class_id = PCI_CLASS_SYSTEM_OTHER;
+ dc->desc = "Xen PV Device";
+ dc->props = xen_pv_props;
+}
+
+static const TypeInfo xen_pv_type_info = {
+ .name = TYPE_XEN_PV_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(XenPVDevice),
+ .class_init = xen_pv_class_init,
+};
+
+static void xen_pv_register_types(void)
+{
+ type_register_static(&xen_pv_type_info);
+}
+
+type_init(xen_pv_register_types)
diff --git a/hw/ide/Makefile.objs b/hw/ide/Makefile.objs
new file mode 100644
index 00000000..729e9bd0
--- /dev/null
+++ b/hw/ide/Makefile.objs
@@ -0,0 +1,12 @@
+common-obj-$(CONFIG_IDE_CORE) += core.o atapi.o
+common-obj-$(CONFIG_IDE_QDEV) += qdev.o
+common-obj-$(CONFIG_IDE_PCI) += pci.o
+common-obj-$(CONFIG_IDE_ISA) += isa.o
+common-obj-$(CONFIG_IDE_PIIX) += piix.o
+common-obj-$(CONFIG_IDE_CMD646) += cmd646.o
+common-obj-$(CONFIG_IDE_MACIO) += macio.o
+common-obj-$(CONFIG_IDE_MMIO) += mmio.o
+common-obj-$(CONFIG_IDE_VIA) += via.o
+common-obj-$(CONFIG_MICRODRIVE) += microdrive.o
+common-obj-$(CONFIG_AHCI) += ahci.o
+common-obj-$(CONFIG_AHCI) += ich.o
diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c
new file mode 100644
index 00000000..73948af5
--- /dev/null
+++ b/hw/ide/ahci.c
@@ -0,0 +1,1702 @@
+/*
+ * QEMU AHCI Emulation
+ *
+ * Copyright (c) 2010 qiaochong@loongson.cn
+ * Copyright (c) 2010 Roland Elek <elek.roland@gmail.com>
+ * Copyright (c) 2010 Sebastian Herbszt <herbszt@gmx.de>
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <hw/hw.h>
+#include <hw/pci/msi.h>
+#include <hw/i386/pc.h>
+#include <hw/pci/pci.h>
+#include <hw/sysbus.h>
+
+#include "qemu/error-report.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/dma.h"
+#include "internal.h"
+#include <hw/ide/pci.h>
+#include <hw/ide/ahci.h>
+
+#define DEBUG_AHCI 0
+
+#define DPRINTF(port, fmt, ...) \
+do { \
+ if (DEBUG_AHCI) { \
+ fprintf(stderr, "ahci: %s: [%d] ", __func__, port); \
+ fprintf(stderr, fmt, ## __VA_ARGS__); \
+ } \
+} while (0)
+
+static void check_cmd(AHCIState *s, int port);
+static int handle_cmd(AHCIState *s, int port, uint8_t slot);
+static void ahci_reset_port(AHCIState *s, int port);
+static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis);
+static void ahci_init_d2h(AHCIDevice *ad);
+static int ahci_dma_prepare_buf(IDEDMA *dma, int32_t limit);
+static bool ahci_map_clb_address(AHCIDevice *ad);
+static bool ahci_map_fis_address(AHCIDevice *ad);
+static void ahci_unmap_clb_address(AHCIDevice *ad);
+static void ahci_unmap_fis_address(AHCIDevice *ad);
+
+
+static uint32_t ahci_port_read(AHCIState *s, int port, int offset)
+{
+ uint32_t val;
+ AHCIPortRegs *pr;
+ pr = &s->dev[port].port_regs;
+
+ switch (offset) {
+ case PORT_LST_ADDR:
+ val = pr->lst_addr;
+ break;
+ case PORT_LST_ADDR_HI:
+ val = pr->lst_addr_hi;
+ break;
+ case PORT_FIS_ADDR:
+ val = pr->fis_addr;
+ break;
+ case PORT_FIS_ADDR_HI:
+ val = pr->fis_addr_hi;
+ break;
+ case PORT_IRQ_STAT:
+ val = pr->irq_stat;
+ break;
+ case PORT_IRQ_MASK:
+ val = pr->irq_mask;
+ break;
+ case PORT_CMD:
+ val = pr->cmd;
+ break;
+ case PORT_TFDATA:
+ val = pr->tfdata;
+ break;
+ case PORT_SIG:
+ val = pr->sig;
+ break;
+ case PORT_SCR_STAT:
+ if (s->dev[port].port.ifs[0].blk) {
+ val = SATA_SCR_SSTATUS_DET_DEV_PRESENT_PHY_UP |
+ SATA_SCR_SSTATUS_SPD_GEN1 | SATA_SCR_SSTATUS_IPM_ACTIVE;
+ } else {
+ val = SATA_SCR_SSTATUS_DET_NODEV;
+ }
+ break;
+ case PORT_SCR_CTL:
+ val = pr->scr_ctl;
+ break;
+ case PORT_SCR_ERR:
+ val = pr->scr_err;
+ break;
+ case PORT_SCR_ACT:
+ val = pr->scr_act;
+ break;
+ case PORT_CMD_ISSUE:
+ val = pr->cmd_issue;
+ break;
+ case PORT_RESERVED:
+ default:
+ val = 0;
+ }
+ DPRINTF(port, "offset: 0x%x val: 0x%x\n", offset, val);
+ return val;
+
+}
+
+static void ahci_irq_raise(AHCIState *s, AHCIDevice *dev)
+{
+ AHCIPCIState *d = container_of(s, AHCIPCIState, ahci);
+ PCIDevice *pci_dev =
+ (PCIDevice *)object_dynamic_cast(OBJECT(d), TYPE_PCI_DEVICE);
+
+ DPRINTF(0, "raise irq\n");
+
+ if (pci_dev && msi_enabled(pci_dev)) {
+ msi_notify(pci_dev, 0);
+ } else {
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static void ahci_irq_lower(AHCIState *s, AHCIDevice *dev)
+{
+ AHCIPCIState *d = container_of(s, AHCIPCIState, ahci);
+ PCIDevice *pci_dev =
+ (PCIDevice *)object_dynamic_cast(OBJECT(d), TYPE_PCI_DEVICE);
+
+ DPRINTF(0, "lower irq\n");
+
+ if (!pci_dev || !msi_enabled(pci_dev)) {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void ahci_check_irq(AHCIState *s)
+{
+ int i;
+
+ DPRINTF(-1, "check irq %#x\n", s->control_regs.irqstatus);
+
+ s->control_regs.irqstatus = 0;
+ for (i = 0; i < s->ports; i++) {
+ AHCIPortRegs *pr = &s->dev[i].port_regs;
+ if (pr->irq_stat & pr->irq_mask) {
+ s->control_regs.irqstatus |= (1 << i);
+ }
+ }
+
+ if (s->control_regs.irqstatus &&
+ (s->control_regs.ghc & HOST_CTL_IRQ_EN)) {
+ ahci_irq_raise(s, NULL);
+ } else {
+ ahci_irq_lower(s, NULL);
+ }
+}
+
+static void ahci_trigger_irq(AHCIState *s, AHCIDevice *d,
+ int irq_type)
+{
+ DPRINTF(d->port_no, "trigger irq %#x -> %x\n",
+ irq_type, d->port_regs.irq_mask & irq_type);
+
+ d->port_regs.irq_stat |= irq_type;
+ ahci_check_irq(s);
+}
+
+static void map_page(AddressSpace *as, uint8_t **ptr, uint64_t addr,
+ uint32_t wanted)
+{
+ hwaddr len = wanted;
+
+ if (*ptr) {
+ dma_memory_unmap(as, *ptr, len, DMA_DIRECTION_FROM_DEVICE, len);
+ }
+
+ *ptr = dma_memory_map(as, addr, &len, DMA_DIRECTION_FROM_DEVICE);
+ if (len < wanted) {
+ dma_memory_unmap(as, *ptr, len, DMA_DIRECTION_FROM_DEVICE, len);
+ *ptr = NULL;
+ }
+}
+
+/**
+ * Check the cmd register to see if we should start or stop
+ * the DMA or FIS RX engines.
+ *
+ * @ad: Device to engage.
+ * @allow_stop: Allow device to transition from started to stopped?
+ * 'no' is useful for migration post_load, which does not expect a transition.
+ *
+ * @return 0 on success, -1 on error.
+ */
+static int ahci_cond_start_engines(AHCIDevice *ad, bool allow_stop)
+{
+ AHCIPortRegs *pr = &ad->port_regs;
+
+ if (pr->cmd & PORT_CMD_START) {
+ if (ahci_map_clb_address(ad)) {
+ pr->cmd |= PORT_CMD_LIST_ON;
+ } else {
+ error_report("AHCI: Failed to start DMA engine: "
+ "bad command list buffer address");
+ return -1;
+ }
+ } else if (pr->cmd & PORT_CMD_LIST_ON) {
+ if (allow_stop) {
+ ahci_unmap_clb_address(ad);
+ pr->cmd = pr->cmd & ~(PORT_CMD_LIST_ON);
+ } else {
+ error_report("AHCI: DMA engine should be off, "
+ "but appears to still be running");
+ return -1;
+ }
+ }
+
+ if (pr->cmd & PORT_CMD_FIS_RX) {
+ if (ahci_map_fis_address(ad)) {
+ pr->cmd |= PORT_CMD_FIS_ON;
+ } else {
+ error_report("AHCI: Failed to start FIS receive engine: "
+ "bad FIS receive buffer address");
+ return -1;
+ }
+ } else if (pr->cmd & PORT_CMD_FIS_ON) {
+ if (allow_stop) {
+ ahci_unmap_fis_address(ad);
+ pr->cmd = pr->cmd & ~(PORT_CMD_FIS_ON);
+ } else {
+ error_report("AHCI: FIS receive engine should be off, "
+ "but appears to still be running");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
+{
+ AHCIPortRegs *pr = &s->dev[port].port_regs;
+
+ DPRINTF(port, "offset: 0x%x val: 0x%x\n", offset, val);
+ switch (offset) {
+ case PORT_LST_ADDR:
+ pr->lst_addr = val;
+ break;
+ case PORT_LST_ADDR_HI:
+ pr->lst_addr_hi = val;
+ break;
+ case PORT_FIS_ADDR:
+ pr->fis_addr = val;
+ break;
+ case PORT_FIS_ADDR_HI:
+ pr->fis_addr_hi = val;
+ break;
+ case PORT_IRQ_STAT:
+ pr->irq_stat &= ~val;
+ ahci_check_irq(s);
+ break;
+ case PORT_IRQ_MASK:
+ pr->irq_mask = val & 0xfdc000ff;
+ ahci_check_irq(s);
+ break;
+ case PORT_CMD:
+ /* Block any Read-only fields from being set;
+ * including LIST_ON and FIS_ON.
+ * The spec requires to set ICC bits to zero after the ICC change
+ * is done. We don't support ICC state changes, therefore always
+ * force the ICC bits to zero.
+ */
+ pr->cmd = (pr->cmd & PORT_CMD_RO_MASK) |
+ (val & ~(PORT_CMD_RO_MASK|PORT_CMD_ICC_MASK));
+
+ /* Check FIS RX and CLB engines, allow transition to false: */
+ ahci_cond_start_engines(&s->dev[port], true);
+
+ /* XXX usually the FIS would be pending on the bus here and
+ issuing deferred until the OS enables FIS receival.
+ Instead, we only submit it once - which works in most
+ cases, but is a hack. */
+ if ((pr->cmd & PORT_CMD_FIS_ON) &&
+ !s->dev[port].init_d2h_sent) {
+ ahci_init_d2h(&s->dev[port]);
+ s->dev[port].init_d2h_sent = true;
+ }
+
+ check_cmd(s, port);
+ break;
+ case PORT_TFDATA:
+ /* Read Only. */
+ break;
+ case PORT_SIG:
+ /* Read Only */
+ break;
+ case PORT_SCR_STAT:
+ /* Read Only */
+ break;
+ case PORT_SCR_CTL:
+ if (((pr->scr_ctl & AHCI_SCR_SCTL_DET) == 1) &&
+ ((val & AHCI_SCR_SCTL_DET) == 0)) {
+ ahci_reset_port(s, port);
+ }
+ pr->scr_ctl = val;
+ break;
+ case PORT_SCR_ERR:
+ pr->scr_err &= ~val;
+ break;
+ case PORT_SCR_ACT:
+ /* RW1 */
+ pr->scr_act |= val;
+ break;
+ case PORT_CMD_ISSUE:
+ pr->cmd_issue |= val;
+ check_cmd(s, port);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t ahci_mem_read_32(void *opaque, hwaddr addr)
+{
+ AHCIState *s = opaque;
+ uint32_t val = 0;
+
+ if (addr < AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR) {
+ switch (addr) {
+ case HOST_CAP:
+ val = s->control_regs.cap;
+ break;
+ case HOST_CTL:
+ val = s->control_regs.ghc;
+ break;
+ case HOST_IRQ_STAT:
+ val = s->control_regs.irqstatus;
+ break;
+ case HOST_PORTS_IMPL:
+ val = s->control_regs.impl;
+ break;
+ case HOST_VERSION:
+ val = s->control_regs.version;
+ break;
+ }
+
+ DPRINTF(-1, "(addr 0x%08X), val 0x%08X\n", (unsigned) addr, val);
+ } else if ((addr >= AHCI_PORT_REGS_START_ADDR) &&
+ (addr < (AHCI_PORT_REGS_START_ADDR +
+ (s->ports * AHCI_PORT_ADDR_OFFSET_LEN)))) {
+ val = ahci_port_read(s, (addr - AHCI_PORT_REGS_START_ADDR) >> 7,
+ addr & AHCI_PORT_ADDR_OFFSET_MASK);
+ }
+
+ return val;
+}
+
+
+/**
+ * AHCI 1.3 section 3 ("HBA Memory Registers")
+ * Support unaligned 8/16/32 bit reads, and 64 bit aligned reads.
+ * Caller is responsible for masking unwanted higher order bytes.
+ */
+static uint64_t ahci_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+ hwaddr aligned = addr & ~0x3;
+ int ofst = addr - aligned;
+ uint64_t lo = ahci_mem_read_32(opaque, aligned);
+ uint64_t hi;
+
+ /* if < 8 byte read does not cross 4 byte boundary */
+ if (ofst + size <= 4) {
+ return lo >> (ofst * 8);
+ }
+ g_assert_cmpint(size, >, 1);
+
+ /* If the 64bit read is unaligned, we will produce undefined
+ * results. AHCI does not support unaligned 64bit reads. */
+ hi = ahci_mem_read_32(opaque, aligned + 4);
+ return (hi << 32 | lo) >> (ofst * 8);
+}
+
+
+static void ahci_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ AHCIState *s = opaque;
+
+ /* Only aligned reads are allowed on AHCI */
+ if (addr & 3) {
+ fprintf(stderr, "ahci: Mis-aligned write to addr 0x"
+ TARGET_FMT_plx "\n", addr);
+ return;
+ }
+
+ if (addr < AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR) {
+ DPRINTF(-1, "(addr 0x%08X), val 0x%08"PRIX64"\n", (unsigned) addr, val);
+
+ switch (addr) {
+ case HOST_CAP: /* R/WO, RO */
+ /* FIXME handle R/WO */
+ break;
+ case HOST_CTL: /* R/W */
+ if (val & HOST_CTL_RESET) {
+ DPRINTF(-1, "HBA Reset\n");
+ ahci_reset(s);
+ } else {
+ s->control_regs.ghc = (val & 0x3) | HOST_CTL_AHCI_EN;
+ ahci_check_irq(s);
+ }
+ break;
+ case HOST_IRQ_STAT: /* R/WC, RO */
+ s->control_regs.irqstatus &= ~val;
+ ahci_check_irq(s);
+ break;
+ case HOST_PORTS_IMPL: /* R/WO, RO */
+ /* FIXME handle R/WO */
+ break;
+ case HOST_VERSION: /* RO */
+ /* FIXME report write? */
+ break;
+ default:
+ DPRINTF(-1, "write to unknown register 0x%x\n", (unsigned)addr);
+ }
+ } else if ((addr >= AHCI_PORT_REGS_START_ADDR) &&
+ (addr < (AHCI_PORT_REGS_START_ADDR +
+ (s->ports * AHCI_PORT_ADDR_OFFSET_LEN)))) {
+ ahci_port_write(s, (addr - AHCI_PORT_REGS_START_ADDR) >> 7,
+ addr & AHCI_PORT_ADDR_OFFSET_MASK, val);
+ }
+
+}
+
+static const MemoryRegionOps ahci_mem_ops = {
+ .read = ahci_mem_read,
+ .write = ahci_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t ahci_idp_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ AHCIState *s = opaque;
+
+ if (addr == s->idp_offset) {
+ /* index register */
+ return s->idp_index;
+ } else if (addr == s->idp_offset + 4) {
+ /* data register - do memory read at location selected by index */
+ return ahci_mem_read(opaque, s->idp_index, size);
+ } else {
+ return 0;
+ }
+}
+
+static void ahci_idp_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ AHCIState *s = opaque;
+
+ if (addr == s->idp_offset) {
+ /* index register - mask off reserved bits */
+ s->idp_index = (uint32_t)val & ((AHCI_MEM_BAR_SIZE - 1) & ~3);
+ } else if (addr == s->idp_offset + 4) {
+ /* data register - do memory write at location selected by index */
+ ahci_mem_write(opaque, s->idp_index, val, size);
+ }
+}
+
+static const MemoryRegionOps ahci_idp_ops = {
+ .read = ahci_idp_read,
+ .write = ahci_idp_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+
+static void ahci_reg_init(AHCIState *s)
+{
+ int i;
+
+ s->control_regs.cap = (s->ports - 1) |
+ (AHCI_NUM_COMMAND_SLOTS << 8) |
+ (AHCI_SUPPORTED_SPEED_GEN1 << AHCI_SUPPORTED_SPEED) |
+ HOST_CAP_NCQ | HOST_CAP_AHCI;
+
+ s->control_regs.impl = (1 << s->ports) - 1;
+
+ s->control_regs.version = AHCI_VERSION_1_0;
+
+ for (i = 0; i < s->ports; i++) {
+ s->dev[i].port_state = STATE_RUN;
+ }
+}
+
+static void check_cmd(AHCIState *s, int port)
+{
+ AHCIPortRegs *pr = &s->dev[port].port_regs;
+ uint8_t slot;
+
+ if ((pr->cmd & PORT_CMD_START) && pr->cmd_issue) {
+ for (slot = 0; (slot < 32) && pr->cmd_issue; slot++) {
+ if ((pr->cmd_issue & (1U << slot)) &&
+ !handle_cmd(s, port, slot)) {
+ pr->cmd_issue &= ~(1U << slot);
+ }
+ }
+ }
+}
+
+static void ahci_check_cmd_bh(void *opaque)
+{
+ AHCIDevice *ad = opaque;
+
+ qemu_bh_delete(ad->check_bh);
+ ad->check_bh = NULL;
+
+ if ((ad->busy_slot != -1) &&
+ !(ad->port.ifs[0].status & (BUSY_STAT|DRQ_STAT))) {
+ /* no longer busy */
+ ad->port_regs.cmd_issue &= ~(1 << ad->busy_slot);
+ ad->busy_slot = -1;
+ }
+
+ check_cmd(ad->hba, ad->port_no);
+}
+
+static void ahci_init_d2h(AHCIDevice *ad)
+{
+ uint8_t init_fis[20];
+ IDEState *ide_state = &ad->port.ifs[0];
+
+ memset(init_fis, 0, sizeof(init_fis));
+
+ init_fis[4] = 1;
+ init_fis[12] = 1;
+
+ if (ide_state->drive_kind == IDE_CD) {
+ init_fis[5] = ide_state->lcyl;
+ init_fis[6] = ide_state->hcyl;
+ }
+
+ ahci_write_fis_d2h(ad, init_fis);
+}
+
+static void ahci_reset_port(AHCIState *s, int port)
+{
+ AHCIDevice *d = &s->dev[port];
+ AHCIPortRegs *pr = &d->port_regs;
+ IDEState *ide_state = &d->port.ifs[0];
+ int i;
+
+ DPRINTF(port, "reset port\n");
+
+ ide_bus_reset(&d->port);
+ ide_state->ncq_queues = AHCI_MAX_CMDS;
+
+ pr->scr_stat = 0;
+ pr->scr_err = 0;
+ pr->scr_act = 0;
+ pr->tfdata = 0x7F;
+ pr->sig = 0xFFFFFFFF;
+ d->busy_slot = -1;
+ d->init_d2h_sent = false;
+
+ ide_state = &s->dev[port].port.ifs[0];
+ if (!ide_state->blk) {
+ return;
+ }
+
+ /* reset ncq queue */
+ for (i = 0; i < AHCI_MAX_CMDS; i++) {
+ NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[i];
+ ncq_tfs->halt = false;
+ if (!ncq_tfs->used) {
+ continue;
+ }
+
+ if (ncq_tfs->aiocb) {
+ blk_aio_cancel(ncq_tfs->aiocb);
+ ncq_tfs->aiocb = NULL;
+ }
+
+ /* Maybe we just finished the request thanks to blk_aio_cancel() */
+ if (!ncq_tfs->used) {
+ continue;
+ }
+
+ qemu_sglist_destroy(&ncq_tfs->sglist);
+ ncq_tfs->used = 0;
+ }
+
+ s->dev[port].port_state = STATE_RUN;
+ if (!ide_state->blk) {
+ pr->sig = 0;
+ ide_state->status = SEEK_STAT | WRERR_STAT;
+ } else if (ide_state->drive_kind == IDE_CD) {
+ pr->sig = SATA_SIGNATURE_CDROM;
+ ide_state->lcyl = 0x14;
+ ide_state->hcyl = 0xeb;
+ DPRINTF(port, "set lcyl = %d\n", ide_state->lcyl);
+ ide_state->status = SEEK_STAT | WRERR_STAT | READY_STAT;
+ } else {
+ pr->sig = SATA_SIGNATURE_DISK;
+ ide_state->status = SEEK_STAT | WRERR_STAT;
+ }
+
+ ide_state->error = 1;
+ ahci_init_d2h(d);
+}
+
+static void debug_print_fis(uint8_t *fis, int cmd_len)
+{
+#if DEBUG_AHCI
+ int i;
+
+ fprintf(stderr, "fis:");
+ for (i = 0; i < cmd_len; i++) {
+ if ((i & 0xf) == 0) {
+ fprintf(stderr, "\n%02x:",i);
+ }
+ fprintf(stderr, "%02x ",fis[i]);
+ }
+ fprintf(stderr, "\n");
+#endif
+}
+
+static bool ahci_map_fis_address(AHCIDevice *ad)
+{
+ AHCIPortRegs *pr = &ad->port_regs;
+ map_page(ad->hba->as, &ad->res_fis,
+ ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256);
+ return ad->res_fis != NULL;
+}
+
+static void ahci_unmap_fis_address(AHCIDevice *ad)
+{
+ dma_memory_unmap(ad->hba->as, ad->res_fis, 256,
+ DMA_DIRECTION_FROM_DEVICE, 256);
+ ad->res_fis = NULL;
+}
+
+static bool ahci_map_clb_address(AHCIDevice *ad)
+{
+ AHCIPortRegs *pr = &ad->port_regs;
+ ad->cur_cmd = NULL;
+ map_page(ad->hba->as, &ad->lst,
+ ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024);
+ return ad->lst != NULL;
+}
+
+static void ahci_unmap_clb_address(AHCIDevice *ad)
+{
+ dma_memory_unmap(ad->hba->as, ad->lst, 1024,
+ DMA_DIRECTION_FROM_DEVICE, 1024);
+ ad->lst = NULL;
+}
+
+static void ahci_write_fis_sdb(AHCIState *s, NCQTransferState *ncq_tfs)
+{
+ AHCIDevice *ad = ncq_tfs->drive;
+ AHCIPortRegs *pr = &ad->port_regs;
+ IDEState *ide_state;
+ SDBFIS *sdb_fis;
+
+ if (!ad->res_fis ||
+ !(pr->cmd & PORT_CMD_FIS_RX)) {
+ return;
+ }
+
+ sdb_fis = (SDBFIS *)&ad->res_fis[RES_FIS_SDBFIS];
+ ide_state = &ad->port.ifs[0];
+
+ sdb_fis->type = SATA_FIS_TYPE_SDB;
+ /* Interrupt pending & Notification bit */
+ sdb_fis->flags = 0x40; /* Interrupt bit, always 1 for NCQ */
+ sdb_fis->status = ide_state->status & 0x77;
+ sdb_fis->error = ide_state->error;
+ /* update SAct field in SDB_FIS */
+ sdb_fis->payload = cpu_to_le32(ad->finished);
+
+ /* Update shadow registers (except BSY 0x80 and DRQ 0x08) */
+ pr->tfdata = (ad->port.ifs[0].error << 8) |
+ (ad->port.ifs[0].status & 0x77) |
+ (pr->tfdata & 0x88);
+ pr->scr_act &= ~ad->finished;
+ ad->finished = 0;
+
+ /* Trigger IRQ if interrupt bit is set (which currently, it always is) */
+ if (sdb_fis->flags & 0x40) {
+ ahci_trigger_irq(s, ad, PORT_IRQ_SDB_FIS);
+ }
+}
+
+static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len)
+{
+ AHCIPortRegs *pr = &ad->port_regs;
+ uint8_t *pio_fis;
+ IDEState *s = &ad->port.ifs[0];
+
+ if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) {
+ return;
+ }
+
+ pio_fis = &ad->res_fis[RES_FIS_PSFIS];
+
+ pio_fis[0] = SATA_FIS_TYPE_PIO_SETUP;
+ pio_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0);
+ pio_fis[2] = s->status;
+ pio_fis[3] = s->error;
+
+ pio_fis[4] = s->sector;
+ pio_fis[5] = s->lcyl;
+ pio_fis[6] = s->hcyl;
+ pio_fis[7] = s->select;
+ pio_fis[8] = s->hob_sector;
+ pio_fis[9] = s->hob_lcyl;
+ pio_fis[10] = s->hob_hcyl;
+ pio_fis[11] = 0;
+ pio_fis[12] = s->nsector & 0xFF;
+ pio_fis[13] = (s->nsector >> 8) & 0xFF;
+ pio_fis[14] = 0;
+ pio_fis[15] = s->status;
+ pio_fis[16] = len & 255;
+ pio_fis[17] = len >> 8;
+ pio_fis[18] = 0;
+ pio_fis[19] = 0;
+
+ /* Update shadow registers: */
+ pr->tfdata = (ad->port.ifs[0].error << 8) |
+ ad->port.ifs[0].status;
+
+ if (pio_fis[2] & ERR_STAT) {
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
+ }
+
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_PIOS_FIS);
+}
+
+static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis)
+{
+ AHCIPortRegs *pr = &ad->port_regs;
+ uint8_t *d2h_fis;
+ int i;
+ IDEState *s = &ad->port.ifs[0];
+
+ if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) {
+ return;
+ }
+
+ d2h_fis = &ad->res_fis[RES_FIS_RFIS];
+
+ d2h_fis[0] = SATA_FIS_TYPE_REGISTER_D2H;
+ d2h_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0);
+ d2h_fis[2] = s->status;
+ d2h_fis[3] = s->error;
+
+ d2h_fis[4] = s->sector;
+ d2h_fis[5] = s->lcyl;
+ d2h_fis[6] = s->hcyl;
+ d2h_fis[7] = s->select;
+ d2h_fis[8] = s->hob_sector;
+ d2h_fis[9] = s->hob_lcyl;
+ d2h_fis[10] = s->hob_hcyl;
+ d2h_fis[11] = 0;
+ d2h_fis[12] = s->nsector & 0xFF;
+ d2h_fis[13] = (s->nsector >> 8) & 0xFF;
+ for (i = 14; i < 20; i++) {
+ d2h_fis[i] = 0;
+ }
+
+ /* Update shadow registers: */
+ pr->tfdata = (ad->port.ifs[0].error << 8) |
+ ad->port.ifs[0].status;
+
+ if (d2h_fis[2] & ERR_STAT) {
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
+ }
+
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_D2H_REG_FIS);
+}
+
+static int prdt_tbl_entry_size(const AHCI_SG *tbl)
+{
+ /* flags_size is zero-based */
+ return (le32_to_cpu(tbl->flags_size) & AHCI_PRDT_SIZE_MASK) + 1;
+}
+
+static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist,
+ AHCICmdHdr *cmd, int64_t limit, int32_t offset)
+{
+ uint16_t opts = le16_to_cpu(cmd->opts);
+ uint16_t prdtl = le16_to_cpu(cmd->prdtl);
+ uint64_t cfis_addr = le64_to_cpu(cmd->tbl_addr);
+ uint64_t prdt_addr = cfis_addr + 0x80;
+ dma_addr_t prdt_len = (prdtl * sizeof(AHCI_SG));
+ dma_addr_t real_prdt_len = prdt_len;
+ uint8_t *prdt;
+ int i;
+ int r = 0;
+ uint64_t sum = 0;
+ int off_idx = -1;
+ int64_t off_pos = -1;
+ int tbl_entry_size;
+ IDEBus *bus = &ad->port;
+ BusState *qbus = BUS(bus);
+
+ /*
+ * Note: AHCI PRDT can describe up to 256GiB. SATA/ATA only support
+ * transactions of up to 32MiB as of ATA8-ACS3 rev 1b, assuming a
+ * 512 byte sector size. We limit the PRDT in this implementation to
+ * a reasonably large 2GiB, which can accommodate the maximum transfer
+ * request for sector sizes up to 32K.
+ */
+
+ if (!prdtl) {
+ DPRINTF(ad->port_no, "no sg list given by guest: 0x%08x\n", opts);
+ return -1;
+ }
+
+ /* map PRDT */
+ if (!(prdt = dma_memory_map(ad->hba->as, prdt_addr, &prdt_len,
+ DMA_DIRECTION_TO_DEVICE))){
+ DPRINTF(ad->port_no, "map failed\n");
+ return -1;
+ }
+
+ if (prdt_len < real_prdt_len) {
+ DPRINTF(ad->port_no, "mapped less than expected\n");
+ r = -1;
+ goto out;
+ }
+
+ /* Get entries in the PRDT, init a qemu sglist accordingly */
+ if (prdtl > 0) {
+ AHCI_SG *tbl = (AHCI_SG *)prdt;
+ sum = 0;
+ for (i = 0; i < prdtl; i++) {
+ tbl_entry_size = prdt_tbl_entry_size(&tbl[i]);
+ if (offset < (sum + tbl_entry_size)) {
+ off_idx = i;
+ off_pos = offset - sum;
+ break;
+ }
+ sum += tbl_entry_size;
+ }
+ if ((off_idx == -1) || (off_pos < 0) || (off_pos > tbl_entry_size)) {
+ DPRINTF(ad->port_no, "%s: Incorrect offset! "
+ "off_idx: %d, off_pos: %"PRId64"\n",
+ __func__, off_idx, off_pos);
+ r = -1;
+ goto out;
+ }
+
+ qemu_sglist_init(sglist, qbus->parent, (prdtl - off_idx),
+ ad->hba->as);
+ qemu_sglist_add(sglist, le64_to_cpu(tbl[off_idx].addr) + off_pos,
+ MIN(prdt_tbl_entry_size(&tbl[off_idx]) - off_pos,
+ limit));
+
+ for (i = off_idx + 1; i < prdtl && sglist->size < limit; i++) {
+ qemu_sglist_add(sglist, le64_to_cpu(tbl[i].addr),
+ MIN(prdt_tbl_entry_size(&tbl[i]),
+ limit - sglist->size));
+ if (sglist->size > INT32_MAX) {
+ error_report("AHCI Physical Region Descriptor Table describes "
+ "more than 2 GiB.\n");
+ qemu_sglist_destroy(sglist);
+ r = -1;
+ goto out;
+ }
+ }
+ }
+
+out:
+ dma_memory_unmap(ad->hba->as, prdt, prdt_len,
+ DMA_DIRECTION_TO_DEVICE, prdt_len);
+ return r;
+}
+
+static void ncq_err(NCQTransferState *ncq_tfs)
+{
+ IDEState *ide_state = &ncq_tfs->drive->port.ifs[0];
+
+ ide_state->error = ABRT_ERR;
+ ide_state->status = READY_STAT | ERR_STAT;
+ ncq_tfs->drive->port_regs.scr_err |= (1 << ncq_tfs->tag);
+ ncq_tfs->used = 0;
+}
+
+static void ncq_finish(NCQTransferState *ncq_tfs)
+{
+ /* If we didn't error out, set our finished bit. Errored commands
+ * do not get a bit set for the SDB FIS ACT register, nor do they
+ * clear the outstanding bit in scr_act (PxSACT). */
+ if (!(ncq_tfs->drive->port_regs.scr_err & (1 << ncq_tfs->tag))) {
+ ncq_tfs->drive->finished |= (1 << ncq_tfs->tag);
+ }
+
+ ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs);
+
+ DPRINTF(ncq_tfs->drive->port_no, "NCQ transfer tag %d finished\n",
+ ncq_tfs->tag);
+
+ block_acct_done(blk_get_stats(ncq_tfs->drive->port.ifs[0].blk),
+ &ncq_tfs->acct);
+ qemu_sglist_destroy(&ncq_tfs->sglist);
+ ncq_tfs->used = 0;
+}
+
+static void ncq_cb(void *opaque, int ret)
+{
+ NCQTransferState *ncq_tfs = (NCQTransferState *)opaque;
+ IDEState *ide_state = &ncq_tfs->drive->port.ifs[0];
+
+ if (ret == -ECANCELED) {
+ return;
+ }
+
+ if (ret < 0) {
+ bool is_read = ncq_tfs->cmd == READ_FPDMA_QUEUED;
+ BlockErrorAction action = blk_get_error_action(ide_state->blk,
+ is_read, -ret);
+ if (action == BLOCK_ERROR_ACTION_STOP) {
+ ncq_tfs->halt = true;
+ ide_state->bus->error_status = IDE_RETRY_HBA;
+ } else if (action == BLOCK_ERROR_ACTION_REPORT) {
+ ncq_err(ncq_tfs);
+ }
+ blk_error_action(ide_state->blk, action, is_read, -ret);
+ } else {
+ ide_state->status = READY_STAT | SEEK_STAT;
+ }
+
+ if (!ncq_tfs->halt) {
+ ncq_finish(ncq_tfs);
+ }
+}
+
+static int is_ncq(uint8_t ata_cmd)
+{
+ /* Based on SATA 3.2 section 13.6.3.2 */
+ switch (ata_cmd) {
+ case READ_FPDMA_QUEUED:
+ case WRITE_FPDMA_QUEUED:
+ case NCQ_NON_DATA:
+ case RECEIVE_FPDMA_QUEUED:
+ case SEND_FPDMA_QUEUED:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void execute_ncq_command(NCQTransferState *ncq_tfs)
+{
+ AHCIDevice *ad = ncq_tfs->drive;
+ IDEState *ide_state = &ad->port.ifs[0];
+ int port = ad->port_no;
+
+ g_assert(is_ncq(ncq_tfs->cmd));
+ ncq_tfs->halt = false;
+
+ switch (ncq_tfs->cmd) {
+ case READ_FPDMA_QUEUED:
+ DPRINTF(port, "NCQ reading %d sectors from LBA %"PRId64", tag %d\n",
+ ncq_tfs->sector_count, ncq_tfs->lba, ncq_tfs->tag);
+
+ DPRINTF(port, "tag %d aio read %"PRId64"\n",
+ ncq_tfs->tag, ncq_tfs->lba);
+
+ dma_acct_start(ide_state->blk, &ncq_tfs->acct,
+ &ncq_tfs->sglist, BLOCK_ACCT_READ);
+ ncq_tfs->aiocb = dma_blk_read(ide_state->blk, &ncq_tfs->sglist,
+ ncq_tfs->lba, ncq_cb, ncq_tfs);
+ break;
+ case WRITE_FPDMA_QUEUED:
+ DPRINTF(port, "NCQ writing %d sectors to LBA %"PRId64", tag %d\n",
+ ncq_tfs->sector_count, ncq_tfs->lba, ncq_tfs->tag);
+
+ DPRINTF(port, "tag %d aio write %"PRId64"\n",
+ ncq_tfs->tag, ncq_tfs->lba);
+
+ dma_acct_start(ide_state->blk, &ncq_tfs->acct,
+ &ncq_tfs->sglist, BLOCK_ACCT_WRITE);
+ ncq_tfs->aiocb = dma_blk_write(ide_state->blk, &ncq_tfs->sglist,
+ ncq_tfs->lba, ncq_cb, ncq_tfs);
+ break;
+ default:
+ DPRINTF(port, "error: unsupported NCQ command (0x%02x) received\n",
+ ncq_tfs->cmd);
+ qemu_sglist_destroy(&ncq_tfs->sglist);
+ ncq_err(ncq_tfs);
+ }
+}
+
+
+static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis,
+ uint8_t slot)
+{
+ AHCIDevice *ad = &s->dev[port];
+ IDEState *ide_state = &ad->port.ifs[0];
+ NCQFrame *ncq_fis = (NCQFrame*)cmd_fis;
+ uint8_t tag = ncq_fis->tag >> 3;
+ NCQTransferState *ncq_tfs = &ad->ncq_tfs[tag];
+ size_t size;
+
+ g_assert(is_ncq(ncq_fis->command));
+ if (ncq_tfs->used) {
+ /* error - already in use */
+ fprintf(stderr, "%s: tag %d already used\n", __FUNCTION__, tag);
+ return;
+ }
+
+ ncq_tfs->used = 1;
+ ncq_tfs->drive = ad;
+ ncq_tfs->slot = slot;
+ ncq_tfs->cmdh = &((AHCICmdHdr *)ad->lst)[slot];
+ ncq_tfs->cmd = ncq_fis->command;
+ ncq_tfs->lba = ((uint64_t)ncq_fis->lba5 << 40) |
+ ((uint64_t)ncq_fis->lba4 << 32) |
+ ((uint64_t)ncq_fis->lba3 << 24) |
+ ((uint64_t)ncq_fis->lba2 << 16) |
+ ((uint64_t)ncq_fis->lba1 << 8) |
+ (uint64_t)ncq_fis->lba0;
+ ncq_tfs->tag = tag;
+
+ /* Sanity-check the NCQ packet */
+ if (tag != slot) {
+ DPRINTF(port, "Warn: NCQ slot (%d) did not match the given tag (%d)\n",
+ slot, tag);
+ }
+
+ if (ncq_fis->aux0 || ncq_fis->aux1 || ncq_fis->aux2 || ncq_fis->aux3) {
+ DPRINTF(port, "Warn: Attempt to use NCQ auxiliary fields.\n");
+ }
+ if (ncq_fis->prio || ncq_fis->icc) {
+ DPRINTF(port, "Warn: Unsupported attempt to use PRIO/ICC fields\n");
+ }
+ if (ncq_fis->fua & NCQ_FIS_FUA_MASK) {
+ DPRINTF(port, "Warn: Unsupported attempt to use Force Unit Access\n");
+ }
+ if (ncq_fis->tag & NCQ_FIS_RARC_MASK) {
+ DPRINTF(port, "Warn: Unsupported attempt to use Rebuild Assist\n");
+ }
+
+ ncq_tfs->sector_count = ((ncq_fis->sector_count_high << 8) |
+ ncq_fis->sector_count_low);
+ if (!ncq_tfs->sector_count) {
+ ncq_tfs->sector_count = 0x10000;
+ }
+ size = ncq_tfs->sector_count * 512;
+ ahci_populate_sglist(ad, &ncq_tfs->sglist, ncq_tfs->cmdh, size, 0);
+
+ if (ncq_tfs->sglist.size < size) {
+ error_report("ahci: PRDT length for NCQ command (0x%zx) "
+ "is smaller than the requested size (0x%zx)",
+ ncq_tfs->sglist.size, size);
+ qemu_sglist_destroy(&ncq_tfs->sglist);
+ ncq_err(ncq_tfs);
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_OVERFLOW);
+ return;
+ } else if (ncq_tfs->sglist.size != size) {
+ DPRINTF(port, "Warn: PRDTL (0x%zx)"
+ " does not match requested size (0x%zx)",
+ ncq_tfs->sglist.size, size);
+ }
+
+ DPRINTF(port, "NCQ transfer LBA from %"PRId64" to %"PRId64", "
+ "drive max %"PRId64"\n",
+ ncq_tfs->lba, ncq_tfs->lba + ncq_tfs->sector_count - 1,
+ ide_state->nb_sectors - 1);
+
+ execute_ncq_command(ncq_tfs);
+}
+
+static AHCICmdHdr *get_cmd_header(AHCIState *s, uint8_t port, uint8_t slot)
+{
+ if (port >= s->ports || slot >= AHCI_MAX_CMDS) {
+ return NULL;
+ }
+
+ return s->dev[port].lst ? &((AHCICmdHdr *)s->dev[port].lst)[slot] : NULL;
+}
+
+static void handle_reg_h2d_fis(AHCIState *s, int port,
+ uint8_t slot, uint8_t *cmd_fis)
+{
+ IDEState *ide_state = &s->dev[port].port.ifs[0];
+ AHCICmdHdr *cmd = get_cmd_header(s, port, slot);
+ uint16_t opts = le16_to_cpu(cmd->opts);
+
+ if (cmd_fis[1] & 0x0F) {
+ DPRINTF(port, "Port Multiplier not supported."
+ " cmd_fis[0]=%02x cmd_fis[1]=%02x cmd_fis[2]=%02x\n",
+ cmd_fis[0], cmd_fis[1], cmd_fis[2]);
+ return;
+ }
+
+ if (cmd_fis[1] & 0x70) {
+ DPRINTF(port, "Reserved flags set in H2D Register FIS."
+ " cmd_fis[0]=%02x cmd_fis[1]=%02x cmd_fis[2]=%02x\n",
+ cmd_fis[0], cmd_fis[1], cmd_fis[2]);
+ return;
+ }
+
+ if (!(cmd_fis[1] & SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER)) {
+ switch (s->dev[port].port_state) {
+ case STATE_RUN:
+ if (cmd_fis[15] & ATA_SRST) {
+ s->dev[port].port_state = STATE_RESET;
+ }
+ break;
+ case STATE_RESET:
+ if (!(cmd_fis[15] & ATA_SRST)) {
+ ahci_reset_port(s, port);
+ }
+ break;
+ }
+ return;
+ }
+
+ /* Check for NCQ command */
+ if (is_ncq(cmd_fis[2])) {
+ process_ncq_command(s, port, cmd_fis, slot);
+ return;
+ }
+
+ /* Decompose the FIS:
+ * AHCI does not interpret FIS packets, it only forwards them.
+ * SATA 1.0 describes how to decode LBA28 and CHS FIS packets.
+ * Later specifications, e.g, SATA 3.2, describe LBA48 FIS packets.
+ *
+ * ATA4 describes sector number for LBA28/CHS commands.
+ * ATA6 describes sector number for LBA48 commands.
+ * ATA8 deprecates CHS fully, describing only LBA28/48.
+ *
+ * We dutifully convert the FIS into IDE registers, and allow the
+ * core layer to interpret them as needed. */
+ ide_state->feature = cmd_fis[3];
+ ide_state->sector = cmd_fis[4]; /* LBA 7:0 */
+ ide_state->lcyl = cmd_fis[5]; /* LBA 15:8 */
+ ide_state->hcyl = cmd_fis[6]; /* LBA 23:16 */
+ ide_state->select = cmd_fis[7]; /* LBA 27:24 (LBA28) */
+ ide_state->hob_sector = cmd_fis[8]; /* LBA 31:24 */
+ ide_state->hob_lcyl = cmd_fis[9]; /* LBA 39:32 */
+ ide_state->hob_hcyl = cmd_fis[10]; /* LBA 47:40 */
+ ide_state->hob_feature = cmd_fis[11];
+ ide_state->nsector = (int64_t)((cmd_fis[13] << 8) | cmd_fis[12]);
+ /* 14, 16, 17, 18, 19: Reserved (SATA 1.0) */
+ /* 15: Only valid when UPDATE_COMMAND not set. */
+
+ /* Copy the ACMD field (ATAPI packet, if any) from the AHCI command
+ * table to ide_state->io_buffer */
+ if (opts & AHCI_CMD_ATAPI) {
+ memcpy(ide_state->io_buffer, &cmd_fis[AHCI_COMMAND_TABLE_ACMD], 0x10);
+ debug_print_fis(ide_state->io_buffer, 0x10);
+ s->dev[port].done_atapi_packet = false;
+ /* XXX send PIO setup FIS */
+ }
+
+ ide_state->error = 0;
+
+ /* Reset transferred byte counter */
+ cmd->status = 0;
+
+ /* We're ready to process the command in FIS byte 2. */
+ ide_exec_cmd(&s->dev[port].port, cmd_fis[2]);
+}
+
+static int handle_cmd(AHCIState *s, int port, uint8_t slot)
+{
+ IDEState *ide_state;
+ uint64_t tbl_addr;
+ AHCICmdHdr *cmd;
+ uint8_t *cmd_fis;
+ dma_addr_t cmd_len;
+
+ if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) {
+ /* Engine currently busy, try again later */
+ DPRINTF(port, "engine busy\n");
+ return -1;
+ }
+
+ if (!s->dev[port].lst) {
+ DPRINTF(port, "error: lst not given but cmd handled");
+ return -1;
+ }
+ cmd = get_cmd_header(s, port, slot);
+ /* remember current slot handle for later */
+ s->dev[port].cur_cmd = cmd;
+
+ /* The device we are working for */
+ ide_state = &s->dev[port].port.ifs[0];
+ if (!ide_state->blk) {
+ DPRINTF(port, "error: guest accessed unused port");
+ return -1;
+ }
+
+ tbl_addr = le64_to_cpu(cmd->tbl_addr);
+ cmd_len = 0x80;
+ cmd_fis = dma_memory_map(s->as, tbl_addr, &cmd_len,
+ DMA_DIRECTION_FROM_DEVICE);
+ if (!cmd_fis) {
+ DPRINTF(port, "error: guest passed us an invalid cmd fis\n");
+ return -1;
+ } else if (cmd_len != 0x80) {
+ ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_HBUS_ERR);
+ DPRINTF(port, "error: dma_memory_map failed: "
+ "(len(%02"PRIx64") != 0x80)\n",
+ cmd_len);
+ goto out;
+ }
+ debug_print_fis(cmd_fis, 0x80);
+
+ switch (cmd_fis[0]) {
+ case SATA_FIS_TYPE_REGISTER_H2D:
+ handle_reg_h2d_fis(s, port, slot, cmd_fis);
+ break;
+ default:
+ DPRINTF(port, "unknown command cmd_fis[0]=%02x cmd_fis[1]=%02x "
+ "cmd_fis[2]=%02x\n", cmd_fis[0], cmd_fis[1],
+ cmd_fis[2]);
+ break;
+ }
+
+out:
+ dma_memory_unmap(s->as, cmd_fis, cmd_len, DMA_DIRECTION_FROM_DEVICE,
+ cmd_len);
+
+ if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) {
+ /* async command, complete later */
+ s->dev[port].busy_slot = slot;
+ return -1;
+ }
+
+ /* done handling the command */
+ return 0;
+}
+
+/* DMA dev <-> ram */
+static void ahci_start_transfer(IDEDMA *dma)
+{
+ AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
+ IDEState *s = &ad->port.ifs[0];
+ uint32_t size = (uint32_t)(s->data_end - s->data_ptr);
+ /* write == ram -> device */
+ uint16_t opts = le16_to_cpu(ad->cur_cmd->opts);
+ int is_write = opts & AHCI_CMD_WRITE;
+ int is_atapi = opts & AHCI_CMD_ATAPI;
+ int has_sglist = 0;
+
+ if (is_atapi && !ad->done_atapi_packet) {
+ /* already prepopulated iobuffer */
+ ad->done_atapi_packet = true;
+ size = 0;
+ goto out;
+ }
+
+ if (ahci_dma_prepare_buf(dma, size)) {
+ has_sglist = 1;
+ }
+
+ DPRINTF(ad->port_no, "%sing %d bytes on %s w/%s sglist\n",
+ is_write ? "writ" : "read", size, is_atapi ? "atapi" : "ata",
+ has_sglist ? "" : "o");
+
+ if (has_sglist && size) {
+ if (is_write) {
+ dma_buf_write(s->data_ptr, size, &s->sg);
+ } else {
+ dma_buf_read(s->data_ptr, size, &s->sg);
+ }
+ }
+
+out:
+ /* declare that we processed everything */
+ s->data_ptr = s->data_end;
+
+ /* Update number of transferred bytes, destroy sglist */
+ dma_buf_commit(s, size);
+
+ s->end_transfer_func(s);
+
+ if (!(s->status & DRQ_STAT)) {
+ /* done with PIO send/receive */
+ ahci_write_fis_pio(ad, le32_to_cpu(ad->cur_cmd->status));
+ }
+}
+
+static void ahci_start_dma(IDEDMA *dma, IDEState *s,
+ BlockCompletionFunc *dma_cb)
+{
+ AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
+ DPRINTF(ad->port_no, "\n");
+ s->io_buffer_offset = 0;
+ dma_cb(s, 0);
+}
+
+static void ahci_restart_dma(IDEDMA *dma)
+{
+ /* Nothing to do, ahci_start_dma already resets s->io_buffer_offset. */
+}
+
+/**
+ * IDE/PIO restarts are handled by the core layer, but NCQ commands
+ * need an extra kick from the AHCI HBA.
+ */
+static void ahci_restart(IDEDMA *dma)
+{
+ AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
+ int i;
+
+ for (i = 0; i < AHCI_MAX_CMDS; i++) {
+ NCQTransferState *ncq_tfs = &ad->ncq_tfs[i];
+ if (ncq_tfs->halt) {
+ execute_ncq_command(ncq_tfs);
+ }
+ }
+}
+
+/**
+ * Called in DMA and PIO R/W chains to read the PRDT.
+ * Not shared with NCQ pathways.
+ */
+static int32_t ahci_dma_prepare_buf(IDEDMA *dma, int32_t limit)
+{
+ AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
+ IDEState *s = &ad->port.ifs[0];
+
+ if (ahci_populate_sglist(ad, &s->sg, ad->cur_cmd,
+ limit, s->io_buffer_offset) == -1) {
+ DPRINTF(ad->port_no, "ahci_dma_prepare_buf failed.\n");
+ return -1;
+ }
+ s->io_buffer_size = s->sg.size;
+
+ DPRINTF(ad->port_no, "len=%#x\n", s->io_buffer_size);
+ return s->io_buffer_size;
+}
+
+/**
+ * Updates the command header with a bytes-read value.
+ * Called via dma_buf_commit, for both DMA and PIO paths.
+ * sglist destruction is handled within dma_buf_commit.
+ */
+static void ahci_commit_buf(IDEDMA *dma, uint32_t tx_bytes)
+{
+ AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
+
+ tx_bytes += le32_to_cpu(ad->cur_cmd->status);
+ ad->cur_cmd->status = cpu_to_le32(tx_bytes);
+}
+
+static int ahci_dma_rw_buf(IDEDMA *dma, int is_write)
+{
+ AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
+ IDEState *s = &ad->port.ifs[0];
+ uint8_t *p = s->io_buffer + s->io_buffer_index;
+ int l = s->io_buffer_size - s->io_buffer_index;
+
+ if (ahci_populate_sglist(ad, &s->sg, ad->cur_cmd, l, s->io_buffer_offset)) {
+ return 0;
+ }
+
+ if (is_write) {
+ dma_buf_read(p, l, &s->sg);
+ } else {
+ dma_buf_write(p, l, &s->sg);
+ }
+
+ /* free sglist, update byte count */
+ dma_buf_commit(s, l);
+
+ s->io_buffer_index += l;
+
+ DPRINTF(ad->port_no, "len=%#x\n", l);
+
+ return 1;
+}
+
+static void ahci_cmd_done(IDEDMA *dma)
+{
+ AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
+
+ DPRINTF(ad->port_no, "cmd done\n");
+
+ /* update d2h status */
+ ahci_write_fis_d2h(ad, NULL);
+
+ if (!ad->check_bh) {
+ /* maybe we still have something to process, check later */
+ ad->check_bh = qemu_bh_new(ahci_check_cmd_bh, ad);
+ qemu_bh_schedule(ad->check_bh);
+ }
+}
+
+static void ahci_irq_set(void *opaque, int n, int level)
+{
+}
+
+static const IDEDMAOps ahci_dma_ops = {
+ .start_dma = ahci_start_dma,
+ .restart = ahci_restart,
+ .restart_dma = ahci_restart_dma,
+ .start_transfer = ahci_start_transfer,
+ .prepare_buf = ahci_dma_prepare_buf,
+ .commit_buf = ahci_commit_buf,
+ .rw_buf = ahci_dma_rw_buf,
+ .cmd_done = ahci_cmd_done,
+};
+
+void ahci_init(AHCIState *s, DeviceState *qdev, AddressSpace *as, int ports)
+{
+ qemu_irq *irqs;
+ int i;
+
+ s->as = as;
+ s->ports = ports;
+ s->dev = g_new0(AHCIDevice, ports);
+ ahci_reg_init(s);
+ /* XXX BAR size should be 1k, but that breaks, so bump it to 4k for now */
+ memory_region_init_io(&s->mem, OBJECT(qdev), &ahci_mem_ops, s,
+ "ahci", AHCI_MEM_BAR_SIZE);
+ memory_region_init_io(&s->idp, OBJECT(qdev), &ahci_idp_ops, s,
+ "ahci-idp", 32);
+
+ irqs = qemu_allocate_irqs(ahci_irq_set, s, s->ports);
+
+ for (i = 0; i < s->ports; i++) {
+ AHCIDevice *ad = &s->dev[i];
+
+ ide_bus_new(&ad->port, sizeof(ad->port), qdev, i, 1);
+ ide_init2(&ad->port, irqs[i]);
+
+ ad->hba = s;
+ ad->port_no = i;
+ ad->port.dma = &ad->dma;
+ ad->port.dma->ops = &ahci_dma_ops;
+ ide_register_restart_cb(&ad->port);
+ }
+}
+
+void ahci_uninit(AHCIState *s)
+{
+ g_free(s->dev);
+}
+
+void ahci_reset(AHCIState *s)
+{
+ AHCIPortRegs *pr;
+ int i;
+
+ s->control_regs.irqstatus = 0;
+ /* AHCI Enable (AE)
+ * The implementation of this bit is dependent upon the value of the
+ * CAP.SAM bit. If CAP.SAM is '0', then GHC.AE shall be read-write and
+ * shall have a reset value of '0'. If CAP.SAM is '1', then AE shall be
+ * read-only and shall have a reset value of '1'.
+ *
+ * We set HOST_CAP_AHCI so we must enable AHCI at reset.
+ */
+ s->control_regs.ghc = HOST_CTL_AHCI_EN;
+
+ for (i = 0; i < s->ports; i++) {
+ pr = &s->dev[i].port_regs;
+ pr->irq_stat = 0;
+ pr->irq_mask = 0;
+ pr->scr_ctl = 0;
+ pr->cmd = PORT_CMD_SPIN_UP | PORT_CMD_POWER_ON;
+ ahci_reset_port(s, i);
+ }
+}
+
+static const VMStateDescription vmstate_ncq_tfs = {
+ .name = "ncq state",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(sector_count, NCQTransferState),
+ VMSTATE_UINT64(lba, NCQTransferState),
+ VMSTATE_UINT8(tag, NCQTransferState),
+ VMSTATE_UINT8(cmd, NCQTransferState),
+ VMSTATE_UINT8(slot, NCQTransferState),
+ VMSTATE_BOOL(used, NCQTransferState),
+ VMSTATE_BOOL(halt, NCQTransferState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const VMStateDescription vmstate_ahci_device = {
+ .name = "ahci port",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_IDE_BUS(port, AHCIDevice),
+ VMSTATE_IDE_DRIVE(port.ifs[0], AHCIDevice),
+ VMSTATE_UINT32(port_state, AHCIDevice),
+ VMSTATE_UINT32(finished, AHCIDevice),
+ VMSTATE_UINT32(port_regs.lst_addr, AHCIDevice),
+ VMSTATE_UINT32(port_regs.lst_addr_hi, AHCIDevice),
+ VMSTATE_UINT32(port_regs.fis_addr, AHCIDevice),
+ VMSTATE_UINT32(port_regs.fis_addr_hi, AHCIDevice),
+ VMSTATE_UINT32(port_regs.irq_stat, AHCIDevice),
+ VMSTATE_UINT32(port_regs.irq_mask, AHCIDevice),
+ VMSTATE_UINT32(port_regs.cmd, AHCIDevice),
+ VMSTATE_UINT32(port_regs.tfdata, AHCIDevice),
+ VMSTATE_UINT32(port_regs.sig, AHCIDevice),
+ VMSTATE_UINT32(port_regs.scr_stat, AHCIDevice),
+ VMSTATE_UINT32(port_regs.scr_ctl, AHCIDevice),
+ VMSTATE_UINT32(port_regs.scr_err, AHCIDevice),
+ VMSTATE_UINT32(port_regs.scr_act, AHCIDevice),
+ VMSTATE_UINT32(port_regs.cmd_issue, AHCIDevice),
+ VMSTATE_BOOL(done_atapi_packet, AHCIDevice),
+ VMSTATE_INT32(busy_slot, AHCIDevice),
+ VMSTATE_BOOL(init_d2h_sent, AHCIDevice),
+ VMSTATE_STRUCT_ARRAY(ncq_tfs, AHCIDevice, AHCI_MAX_CMDS,
+ 1, vmstate_ncq_tfs, NCQTransferState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static int ahci_state_post_load(void *opaque, int version_id)
+{
+ int i, j;
+ struct AHCIDevice *ad;
+ NCQTransferState *ncq_tfs;
+ AHCIState *s = opaque;
+
+ for (i = 0; i < s->ports; i++) {
+ ad = &s->dev[i];
+
+ /* Only remap the CLB address if appropriate, disallowing a state
+ * transition from 'on' to 'off' it should be consistent here. */
+ if (ahci_cond_start_engines(ad, false) != 0) {
+ return -1;
+ }
+
+ for (j = 0; j < AHCI_MAX_CMDS; j++) {
+ ncq_tfs = &ad->ncq_tfs[j];
+ ncq_tfs->drive = ad;
+
+ if (ncq_tfs->used != ncq_tfs->halt) {
+ return -1;
+ }
+ if (!ncq_tfs->halt) {
+ continue;
+ }
+ if (!is_ncq(ncq_tfs->cmd)) {
+ return -1;
+ }
+ if (ncq_tfs->slot != ncq_tfs->tag) {
+ return -1;
+ }
+ /* If ncq_tfs->halt is justly set, the engine should be engaged,
+ * and the command list buffer should be mapped. */
+ ncq_tfs->cmdh = get_cmd_header(s, i, ncq_tfs->slot);
+ if (!ncq_tfs->cmdh) {
+ return -1;
+ }
+ ahci_populate_sglist(ncq_tfs->drive, &ncq_tfs->sglist,
+ ncq_tfs->cmdh, ncq_tfs->sector_count * 512,
+ 0);
+ if (ncq_tfs->sector_count != ncq_tfs->sglist.size >> 9) {
+ return -1;
+ }
+ }
+
+
+ /*
+ * If an error is present, ad->busy_slot will be valid and not -1.
+ * In this case, an operation is waiting to resume and will re-check
+ * for additional AHCI commands to execute upon completion.
+ *
+ * In the case where no error was present, busy_slot will be -1,
+ * and we should check to see if there are additional commands waiting.
+ */
+ if (ad->busy_slot == -1) {
+ check_cmd(s, i);
+ } else {
+ /* We are in the middle of a command, and may need to access
+ * the command header in guest memory again. */
+ if (ad->busy_slot < 0 || ad->busy_slot >= AHCI_MAX_CMDS) {
+ return -1;
+ }
+ ad->cur_cmd = get_cmd_header(s, i, ad->busy_slot);
+ }
+ }
+
+ return 0;
+}
+
+const VMStateDescription vmstate_ahci = {
+ .name = "ahci",
+ .version_id = 1,
+ .post_load = ahci_state_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_VARRAY_POINTER_INT32(dev, AHCIState, ports,
+ vmstate_ahci_device, AHCIDevice),
+ VMSTATE_UINT32(control_regs.cap, AHCIState),
+ VMSTATE_UINT32(control_regs.ghc, AHCIState),
+ VMSTATE_UINT32(control_regs.irqstatus, AHCIState),
+ VMSTATE_UINT32(control_regs.impl, AHCIState),
+ VMSTATE_UINT32(control_regs.version, AHCIState),
+ VMSTATE_UINT32(idp_index, AHCIState),
+ VMSTATE_INT32_EQUAL(ports, AHCIState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+#define TYPE_SYSBUS_AHCI "sysbus-ahci"
+#define SYSBUS_AHCI(obj) OBJECT_CHECK(SysbusAHCIState, (obj), TYPE_SYSBUS_AHCI)
+
+typedef struct SysbusAHCIState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ AHCIState ahci;
+ uint32_t num_ports;
+} SysbusAHCIState;
+
+static const VMStateDescription vmstate_sysbus_ahci = {
+ .name = "sysbus-ahci",
+ .fields = (VMStateField[]) {
+ VMSTATE_AHCI(ahci, SysbusAHCIState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void sysbus_ahci_reset(DeviceState *dev)
+{
+ SysbusAHCIState *s = SYSBUS_AHCI(dev);
+
+ ahci_reset(&s->ahci);
+}
+
+static void sysbus_ahci_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ SysbusAHCIState *s = SYSBUS_AHCI(dev);
+
+ ahci_init(&s->ahci, dev, &address_space_memory, s->num_ports);
+
+ sysbus_init_mmio(sbd, &s->ahci.mem);
+ sysbus_init_irq(sbd, &s->ahci.irq);
+}
+
+static Property sysbus_ahci_properties[] = {
+ DEFINE_PROP_UINT32("num-ports", SysbusAHCIState, num_ports, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sysbus_ahci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = sysbus_ahci_realize;
+ dc->vmsd = &vmstate_sysbus_ahci;
+ dc->props = sysbus_ahci_properties;
+ dc->reset = sysbus_ahci_reset;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo sysbus_ahci_info = {
+ .name = TYPE_SYSBUS_AHCI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysbusAHCIState),
+ .class_init = sysbus_ahci_class_init,
+};
+
+static void sysbus_ahci_register_types(void)
+{
+ type_register_static(&sysbus_ahci_info);
+}
+
+type_init(sysbus_ahci_register_types)
+
+void ahci_ide_create_devs(PCIDevice *dev, DriveInfo **hd)
+{
+ AHCIPCIState *d = ICH_AHCI(dev);
+ AHCIState *ahci = &d->ahci;
+ int i;
+
+ for (i = 0; i < ahci->ports; i++) {
+ if (hd[i] == NULL) {
+ continue;
+ }
+ ide_create_drive(&ahci->dev[i].port, 0, hd[i]);
+ }
+
+}
diff --git a/hw/ide/ahci.h b/hw/ide/ahci.h
new file mode 100644
index 00000000..79a463d9
--- /dev/null
+++ b/hw/ide/ahci.h
@@ -0,0 +1,372 @@
+/*
+ * QEMU AHCI Emulation
+ *
+ * Copyright (c) 2010 qiaochong@loongson.cn
+ * Copyright (c) 2010 Roland Elek <elek.roland@gmail.com>
+ * Copyright (c) 2010 Sebastian Herbszt <herbszt@gmx.de>
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef HW_IDE_AHCI_H
+#define HW_IDE_AHCI_H
+
+#define AHCI_MEM_BAR_SIZE 0x1000
+#define AHCI_MAX_PORTS 32
+#define AHCI_MAX_SG 168 /* hardware max is 64K */
+#define AHCI_DMA_BOUNDARY 0xffffffff
+#define AHCI_USE_CLUSTERING 0
+#define AHCI_MAX_CMDS 32
+#define AHCI_CMD_SZ 32
+#define AHCI_CMD_SLOT_SZ (AHCI_MAX_CMDS * AHCI_CMD_SZ)
+#define AHCI_RX_FIS_SZ 256
+#define AHCI_CMD_TBL_CDB 0x40
+#define AHCI_CMD_TBL_HDR_SZ 0x80
+#define AHCI_CMD_TBL_SZ (AHCI_CMD_TBL_HDR_SZ + (AHCI_MAX_SG * 16))
+#define AHCI_CMD_TBL_AR_SZ (AHCI_CMD_TBL_SZ * AHCI_MAX_CMDS)
+#define AHCI_PORT_PRIV_DMA_SZ (AHCI_CMD_SLOT_SZ + AHCI_CMD_TBL_AR_SZ + \
+ AHCI_RX_FIS_SZ)
+
+#define AHCI_IRQ_ON_SG (1U << 31)
+#define AHCI_CMD_ATAPI (1 << 5)
+#define AHCI_CMD_WRITE (1 << 6)
+#define AHCI_CMD_PREFETCH (1 << 7)
+#define AHCI_CMD_RESET (1 << 8)
+#define AHCI_CMD_CLR_BUSY (1 << 10)
+
+#define RX_FIS_D2H_REG 0x40 /* offset of D2H Register FIS data */
+#define RX_FIS_SDB 0x58 /* offset of SDB FIS data */
+#define RX_FIS_UNK 0x60 /* offset of Unknown FIS data */
+
+/* global controller registers */
+#define HOST_CAP 0x00 /* host capabilities */
+#define HOST_CTL 0x04 /* global host control */
+#define HOST_IRQ_STAT 0x08 /* interrupt status */
+#define HOST_PORTS_IMPL 0x0c /* bitmap of implemented ports */
+#define HOST_VERSION 0x10 /* AHCI spec. version compliancy */
+
+/* HOST_CTL bits */
+#define HOST_CTL_RESET (1 << 0) /* reset controller; self-clear */
+#define HOST_CTL_IRQ_EN (1 << 1) /* global IRQ enable */
+#define HOST_CTL_AHCI_EN (1U << 31) /* AHCI enabled */
+
+/* HOST_CAP bits */
+#define HOST_CAP_SSC (1 << 14) /* Slumber capable */
+#define HOST_CAP_AHCI (1 << 18) /* AHCI only */
+#define HOST_CAP_CLO (1 << 24) /* Command List Override support */
+#define HOST_CAP_SSS (1 << 27) /* Staggered Spin-up */
+#define HOST_CAP_NCQ (1 << 30) /* Native Command Queueing */
+#define HOST_CAP_64 (1U << 31) /* PCI DAC (64-bit DMA) support */
+
+/* registers for each SATA port */
+#define PORT_LST_ADDR 0x00 /* command list DMA addr */
+#define PORT_LST_ADDR_HI 0x04 /* command list DMA addr hi */
+#define PORT_FIS_ADDR 0x08 /* FIS rx buf addr */
+#define PORT_FIS_ADDR_HI 0x0c /* FIS rx buf addr hi */
+#define PORT_IRQ_STAT 0x10 /* interrupt status */
+#define PORT_IRQ_MASK 0x14 /* interrupt enable/disable mask */
+#define PORT_CMD 0x18 /* port command */
+#define PORT_TFDATA 0x20 /* taskfile data */
+#define PORT_SIG 0x24 /* device TF signature */
+#define PORT_SCR_STAT 0x28 /* SATA phy register: SStatus */
+#define PORT_SCR_CTL 0x2c /* SATA phy register: SControl */
+#define PORT_SCR_ERR 0x30 /* SATA phy register: SError */
+#define PORT_SCR_ACT 0x34 /* SATA phy register: SActive */
+#define PORT_CMD_ISSUE 0x38 /* command issue */
+#define PORT_RESERVED 0x3c /* reserved */
+
+/* PORT_IRQ_{STAT,MASK} bits */
+#define PORT_IRQ_COLD_PRES (1U << 31) /* cold presence detect */
+#define PORT_IRQ_TF_ERR (1 << 30) /* task file error */
+#define PORT_IRQ_HBUS_ERR (1 << 29) /* host bus fatal error */
+#define PORT_IRQ_HBUS_DATA_ERR (1 << 28) /* host bus data error */
+#define PORT_IRQ_IF_ERR (1 << 27) /* interface fatal error */
+#define PORT_IRQ_IF_NONFATAL (1 << 26) /* interface non-fatal error */
+#define PORT_IRQ_OVERFLOW (1 << 24) /* xfer exhausted available S/G */
+#define PORT_IRQ_BAD_PMP (1 << 23) /* incorrect port multiplier */
+
+#define PORT_IRQ_PHYRDY (1 << 22) /* PhyRdy changed */
+#define PORT_IRQ_DEV_ILCK (1 << 7) /* device interlock */
+#define PORT_IRQ_CONNECT (1 << 6) /* port connect change status */
+#define PORT_IRQ_SG_DONE (1 << 5) /* descriptor processed */
+#define PORT_IRQ_UNK_FIS (1 << 4) /* unknown FIS rx'd */
+#define PORT_IRQ_SDB_FIS (1 << 3) /* Set Device Bits FIS rx'd */
+#define PORT_IRQ_DMAS_FIS (1 << 2) /* DMA Setup FIS rx'd */
+#define PORT_IRQ_PIOS_FIS (1 << 1) /* PIO Setup FIS rx'd */
+#define PORT_IRQ_D2H_REG_FIS (1 << 0) /* D2H Register FIS rx'd */
+
+#define PORT_IRQ_FREEZE (PORT_IRQ_HBUS_ERR | PORT_IRQ_IF_ERR | \
+ PORT_IRQ_CONNECT | PORT_IRQ_PHYRDY | \
+ PORT_IRQ_UNK_FIS)
+#define PORT_IRQ_ERROR (PORT_IRQ_FREEZE | PORT_IRQ_TF_ERR | \
+ PORT_IRQ_HBUS_DATA_ERR)
+#define DEF_PORT_IRQ (PORT_IRQ_ERROR | PORT_IRQ_SG_DONE | \
+ PORT_IRQ_SDB_FIS | PORT_IRQ_DMAS_FIS | \
+ PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS)
+
+/* PORT_CMD bits */
+#define PORT_CMD_ATAPI (1 << 24) /* Device is ATAPI */
+#define PORT_CMD_LIST_ON (1 << 15) /* cmd list DMA engine running */
+#define PORT_CMD_FIS_ON (1 << 14) /* FIS DMA engine running */
+#define PORT_CMD_FIS_RX (1 << 4) /* Enable FIS receive DMA engine */
+#define PORT_CMD_CLO (1 << 3) /* Command list override */
+#define PORT_CMD_POWER_ON (1 << 2) /* Power up device */
+#define PORT_CMD_SPIN_UP (1 << 1) /* Spin up device */
+#define PORT_CMD_START (1 << 0) /* Enable port DMA engine */
+
+#define PORT_CMD_ICC_MASK (0xfU << 28) /* i/f ICC state mask */
+#define PORT_CMD_ICC_ACTIVE (0x1 << 28) /* Put i/f in active state */
+#define PORT_CMD_ICC_PARTIAL (0x2 << 28) /* Put i/f in partial state */
+#define PORT_CMD_ICC_SLUMBER (0x6 << 28) /* Put i/f in slumber state */
+
+#define PORT_CMD_RO_MASK 0x007dffe0 /* Which CMD bits are read only? */
+
+/* ap->flags bits */
+#define AHCI_FLAG_NO_NCQ (1 << 24)
+#define AHCI_FLAG_IGN_IRQ_IF_ERR (1 << 25) /* ignore IRQ_IF_ERR */
+#define AHCI_FLAG_HONOR_PI (1 << 26) /* honor PORTS_IMPL */
+#define AHCI_FLAG_IGN_SERR_INTERNAL (1 << 27) /* ignore SERR_INTERNAL */
+#define AHCI_FLAG_32BIT_ONLY (1 << 28) /* force 32bit */
+
+#define ATA_SRST (1 << 2) /* software reset */
+
+#define STATE_RUN 0
+#define STATE_RESET 1
+
+#define SATA_SCR_SSTATUS_DET_NODEV 0x0
+#define SATA_SCR_SSTATUS_DET_DEV_PRESENT_PHY_UP 0x3
+
+#define SATA_SCR_SSTATUS_SPD_NODEV 0x00
+#define SATA_SCR_SSTATUS_SPD_GEN1 0x10
+
+#define SATA_SCR_SSTATUS_IPM_NODEV 0x000
+#define SATA_SCR_SSTATUS_IPM_ACTIVE 0X100
+
+#define AHCI_SCR_SCTL_DET 0xf
+
+#define SATA_FIS_TYPE_REGISTER_H2D 0x27
+#define SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER 0x80
+#define SATA_FIS_TYPE_REGISTER_D2H 0x34
+#define SATA_FIS_TYPE_PIO_SETUP 0x5f
+#define SATA_FIS_TYPE_SDB 0xA1
+
+#define AHCI_CMD_HDR_CMD_FIS_LEN 0x1f
+#define AHCI_CMD_HDR_PRDT_LEN 16
+
+#define SATA_SIGNATURE_CDROM 0xeb140101
+#define SATA_SIGNATURE_DISK 0x00000101
+
+#define AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR 0x20
+ /* Shouldn't this be 0x2c? */
+
+#define AHCI_PORT_REGS_START_ADDR 0x100
+#define AHCI_PORT_ADDR_OFFSET_MASK 0x7f
+#define AHCI_PORT_ADDR_OFFSET_LEN 0x80
+
+#define AHCI_NUM_COMMAND_SLOTS 31
+#define AHCI_SUPPORTED_SPEED 20
+#define AHCI_SUPPORTED_SPEED_GEN1 1
+#define AHCI_VERSION_1_0 0x10000
+
+#define AHCI_PROGMODE_MAJOR_REV_1 1
+
+#define AHCI_COMMAND_TABLE_ACMD 0x40
+
+#define AHCI_PRDT_SIZE_MASK 0x3fffff
+
+#define IDE_FEATURE_DMA 1
+
+#define READ_FPDMA_QUEUED 0x60
+#define WRITE_FPDMA_QUEUED 0x61
+#define NCQ_NON_DATA 0x63
+#define RECEIVE_FPDMA_QUEUED 0x65
+#define SEND_FPDMA_QUEUED 0x64
+
+#define NCQ_FIS_FUA_MASK 0x80
+#define NCQ_FIS_RARC_MASK 0x01
+
+#define RES_FIS_DSFIS 0x00
+#define RES_FIS_PSFIS 0x20
+#define RES_FIS_RFIS 0x40
+#define RES_FIS_SDBFIS 0x58
+#define RES_FIS_UFIS 0x60
+
+#define SATA_CAP_SIZE 0x8
+#define SATA_CAP_REV 0x2
+#define SATA_CAP_BAR 0x4
+
+typedef struct AHCIControlRegs {
+ uint32_t cap;
+ uint32_t ghc;
+ uint32_t irqstatus;
+ uint32_t impl;
+ uint32_t version;
+} AHCIControlRegs;
+
+typedef struct AHCIPortRegs {
+ uint32_t lst_addr;
+ uint32_t lst_addr_hi;
+ uint32_t fis_addr;
+ uint32_t fis_addr_hi;
+ uint32_t irq_stat;
+ uint32_t irq_mask;
+ uint32_t cmd;
+ uint32_t unused0;
+ uint32_t tfdata;
+ uint32_t sig;
+ uint32_t scr_stat;
+ uint32_t scr_ctl;
+ uint32_t scr_err;
+ uint32_t scr_act;
+ uint32_t cmd_issue;
+ uint32_t reserved;
+} AHCIPortRegs;
+
+typedef struct AHCICmdHdr {
+ uint16_t opts;
+ uint16_t prdtl;
+ uint32_t status;
+ uint64_t tbl_addr;
+ uint32_t reserved[4];
+} QEMU_PACKED AHCICmdHdr;
+
+typedef struct AHCI_SG {
+ uint64_t addr;
+ uint32_t reserved;
+ uint32_t flags_size;
+} QEMU_PACKED AHCI_SG;
+
+typedef struct AHCIDevice AHCIDevice;
+
+typedef struct NCQTransferState {
+ AHCIDevice *drive;
+ BlockAIOCB *aiocb;
+ AHCICmdHdr *cmdh;
+ QEMUSGList sglist;
+ BlockAcctCookie acct;
+ uint32_t sector_count;
+ uint64_t lba;
+ uint8_t tag;
+ uint8_t cmd;
+ uint8_t slot;
+ bool used;
+ bool halt;
+} NCQTransferState;
+
+struct AHCIDevice {
+ IDEDMA dma;
+ IDEBus port;
+ int port_no;
+ uint32_t port_state;
+ uint32_t finished;
+ AHCIPortRegs port_regs;
+ struct AHCIState *hba;
+ QEMUBH *check_bh;
+ uint8_t *lst;
+ uint8_t *res_fis;
+ bool done_atapi_packet;
+ int32_t busy_slot;
+ bool init_d2h_sent;
+ AHCICmdHdr *cur_cmd;
+ NCQTransferState ncq_tfs[AHCI_MAX_CMDS];
+};
+
+typedef struct AHCIState {
+ AHCIDevice *dev;
+ AHCIControlRegs control_regs;
+ MemoryRegion mem;
+ MemoryRegion idp; /* Index-Data Pair I/O port space */
+ unsigned idp_offset; /* Offset of index in I/O port space */
+ uint32_t idp_index; /* Current IDP index */
+ int32_t ports;
+ qemu_irq irq;
+ AddressSpace *as;
+} AHCIState;
+
+typedef struct AHCIPCIState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ AHCIState ahci;
+} AHCIPCIState;
+
+#define TYPE_ICH9_AHCI "ich9-ahci"
+
+#define ICH_AHCI(obj) \
+ OBJECT_CHECK(AHCIPCIState, (obj), TYPE_ICH9_AHCI)
+
+extern const VMStateDescription vmstate_ahci;
+
+#define VMSTATE_AHCI(_field, _state) { \
+ .name = (stringify(_field)), \
+ .size = sizeof(AHCIState), \
+ .vmsd = &vmstate_ahci, \
+ .flags = VMS_STRUCT, \
+ .offset = vmstate_offset_value(_state, _field, AHCIState), \
+}
+
+/**
+ * NCQFrame is the same as a Register H2D FIS (described in SATA 3.2),
+ * but some fields have been re-mapped and re-purposed, as seen in
+ * SATA 3.2 section 13.6.4.1 ("READ FPDMA QUEUED")
+ *
+ * cmd_fis[3], feature 7:0, becomes sector count 7:0.
+ * cmd_fis[7], device 7:0, uses bit 7 as the Force Unit Access bit.
+ * cmd_fis[11], feature 15:8, becomes sector count 15:8.
+ * cmd_fis[12], count 7:0, becomes the NCQ TAG (7:3) and RARC bit (0)
+ * cmd_fis[13], count 15:8, becomes the priority value (7:6)
+ * bytes 16-19 become an le32 "auxiliary" field.
+ */
+typedef struct NCQFrame {
+ uint8_t fis_type;
+ uint8_t c;
+ uint8_t command;
+ uint8_t sector_count_low; /* (feature 7:0) */
+ uint8_t lba0;
+ uint8_t lba1;
+ uint8_t lba2;
+ uint8_t fua; /* (device 7:0) */
+ uint8_t lba3;
+ uint8_t lba4;
+ uint8_t lba5;
+ uint8_t sector_count_high; /* (feature 15:8) */
+ uint8_t tag; /* (count 0:7) */
+ uint8_t prio; /* (count 15:8) */
+ uint8_t icc;
+ uint8_t control;
+ uint8_t aux0;
+ uint8_t aux1;
+ uint8_t aux2;
+ uint8_t aux3;
+} QEMU_PACKED NCQFrame;
+
+typedef struct SDBFIS {
+ uint8_t type;
+ uint8_t flags;
+ uint8_t status;
+ uint8_t error;
+ uint32_t payload;
+} QEMU_PACKED SDBFIS;
+
+void ahci_init(AHCIState *s, DeviceState *qdev, AddressSpace *as, int ports);
+void ahci_uninit(AHCIState *s);
+
+void ahci_reset(AHCIState *s);
+
+void ahci_ide_create_devs(PCIDevice *dev, DriveInfo **hd);
+
+#endif /* HW_IDE_AHCI_H */
diff --git a/hw/ide/atapi.c b/hw/ide/atapi.c
new file mode 100644
index 00000000..79dd1671
--- /dev/null
+++ b/hw/ide/atapi.c
@@ -0,0 +1,1261 @@
+/*
+ * QEMU ATAPI Emulation
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+
+#include "hw/ide/internal.h"
+#include "hw/scsi/scsi.h"
+#include "sysemu/block-backend.h"
+
+static void ide_atapi_cmd_read_dma_cb(void *opaque, int ret);
+
+static void padstr8(uint8_t *buf, int buf_size, const char *src)
+{
+ int i;
+ for(i = 0; i < buf_size; i++) {
+ if (*src)
+ buf[i] = *src++;
+ else
+ buf[i] = ' ';
+ }
+}
+
+static inline void cpu_to_ube16(uint8_t *buf, int val)
+{
+ buf[0] = val >> 8;
+ buf[1] = val & 0xff;
+}
+
+static inline void cpu_to_ube32(uint8_t *buf, unsigned int val)
+{
+ buf[0] = val >> 24;
+ buf[1] = val >> 16;
+ buf[2] = val >> 8;
+ buf[3] = val & 0xff;
+}
+
+static inline int ube16_to_cpu(const uint8_t *buf)
+{
+ return (buf[0] << 8) | buf[1];
+}
+
+static inline int ube32_to_cpu(const uint8_t *buf)
+{
+ return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+}
+
+static void lba_to_msf(uint8_t *buf, int lba)
+{
+ lba += 150;
+ buf[0] = (lba / 75) / 60;
+ buf[1] = (lba / 75) % 60;
+ buf[2] = lba % 75;
+}
+
+static inline int media_present(IDEState *s)
+{
+ return !s->tray_open && s->nb_sectors > 0;
+}
+
+/* XXX: DVDs that could fit on a CD will be reported as a CD */
+static inline int media_is_dvd(IDEState *s)
+{
+ return (media_present(s) && s->nb_sectors > CD_MAX_SECTORS);
+}
+
+static inline int media_is_cd(IDEState *s)
+{
+ return (media_present(s) && s->nb_sectors <= CD_MAX_SECTORS);
+}
+
+static void cd_data_to_raw(uint8_t *buf, int lba)
+{
+ /* sync bytes */
+ buf[0] = 0x00;
+ memset(buf + 1, 0xff, 10);
+ buf[11] = 0x00;
+ buf += 12;
+ /* MSF */
+ lba_to_msf(buf, lba);
+ buf[3] = 0x01; /* mode 1 data */
+ buf += 4;
+ /* data */
+ buf += 2048;
+ /* XXX: ECC not computed */
+ memset(buf, 0, 288);
+}
+
+static int cd_read_sector(IDEState *s, int lba, uint8_t *buf, int sector_size)
+{
+ int ret;
+
+ switch(sector_size) {
+ case 2048:
+ block_acct_start(blk_get_stats(s->blk), &s->acct,
+ 4 * BDRV_SECTOR_SIZE, BLOCK_ACCT_READ);
+ ret = blk_read(s->blk, (int64_t)lba << 2, buf, 4);
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ break;
+ case 2352:
+ block_acct_start(blk_get_stats(s->blk), &s->acct,
+ 4 * BDRV_SECTOR_SIZE, BLOCK_ACCT_READ);
+ ret = blk_read(s->blk, (int64_t)lba << 2, buf + 16, 4);
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ if (ret < 0)
+ return ret;
+ cd_data_to_raw(buf, lba);
+ break;
+ default:
+ ret = -EIO;
+ break;
+ }
+ return ret;
+}
+
+void ide_atapi_cmd_ok(IDEState *s)
+{
+ s->error = 0;
+ s->status = READY_STAT | SEEK_STAT;
+ s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ ide_transfer_stop(s);
+ ide_set_irq(s->bus);
+}
+
+void ide_atapi_cmd_error(IDEState *s, int sense_key, int asc)
+{
+#ifdef DEBUG_IDE_ATAPI
+ printf("atapi_cmd_error: sense=0x%x asc=0x%x\n", sense_key, asc);
+#endif
+ s->error = sense_key << 4;
+ s->status = READY_STAT | ERR_STAT;
+ s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ s->sense_key = sense_key;
+ s->asc = asc;
+ ide_transfer_stop(s);
+ ide_set_irq(s->bus);
+}
+
+void ide_atapi_io_error(IDEState *s, int ret)
+{
+ /* XXX: handle more errors */
+ if (ret == -ENOMEDIUM) {
+ ide_atapi_cmd_error(s, NOT_READY,
+ ASC_MEDIUM_NOT_PRESENT);
+ } else {
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_LOGICAL_BLOCK_OOR);
+ }
+}
+
+/* The whole ATAPI transfer logic is handled in this function */
+void ide_atapi_cmd_reply_end(IDEState *s)
+{
+ int byte_count_limit, size, ret;
+#ifdef DEBUG_IDE_ATAPI
+ printf("reply: tx_size=%d elem_tx_size=%d index=%d\n",
+ s->packet_transfer_size,
+ s->elementary_transfer_size,
+ s->io_buffer_index);
+#endif
+ if (s->packet_transfer_size <= 0) {
+ /* end of transfer */
+ ide_atapi_cmd_ok(s);
+ ide_set_irq(s->bus);
+#ifdef DEBUG_IDE_ATAPI
+ printf("status=0x%x\n", s->status);
+#endif
+ } else {
+ /* see if a new sector must be read */
+ if (s->lba != -1 && s->io_buffer_index >= s->cd_sector_size) {
+ ret = cd_read_sector(s, s->lba, s->io_buffer, s->cd_sector_size);
+ if (ret < 0) {
+ ide_atapi_io_error(s, ret);
+ return;
+ }
+ s->lba++;
+ s->io_buffer_index = 0;
+ }
+ if (s->elementary_transfer_size > 0) {
+ /* there are some data left to transmit in this elementary
+ transfer */
+ size = s->cd_sector_size - s->io_buffer_index;
+ if (size > s->elementary_transfer_size)
+ size = s->elementary_transfer_size;
+ s->packet_transfer_size -= size;
+ s->elementary_transfer_size -= size;
+ s->io_buffer_index += size;
+ ide_transfer_start(s, s->io_buffer + s->io_buffer_index - size,
+ size, ide_atapi_cmd_reply_end);
+ } else {
+ /* a new transfer is needed */
+ s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO;
+ byte_count_limit = s->lcyl | (s->hcyl << 8);
+#ifdef DEBUG_IDE_ATAPI
+ printf("byte_count_limit=%d\n", byte_count_limit);
+#endif
+ if (byte_count_limit == 0xffff)
+ byte_count_limit--;
+ size = s->packet_transfer_size;
+ if (size > byte_count_limit) {
+ /* byte count limit must be even if this case */
+ if (byte_count_limit & 1)
+ byte_count_limit--;
+ size = byte_count_limit;
+ }
+ s->lcyl = size;
+ s->hcyl = size >> 8;
+ s->elementary_transfer_size = size;
+ /* we cannot transmit more than one sector at a time */
+ if (s->lba != -1) {
+ if (size > (s->cd_sector_size - s->io_buffer_index))
+ size = (s->cd_sector_size - s->io_buffer_index);
+ }
+ s->packet_transfer_size -= size;
+ s->elementary_transfer_size -= size;
+ s->io_buffer_index += size;
+ ide_transfer_start(s, s->io_buffer + s->io_buffer_index - size,
+ size, ide_atapi_cmd_reply_end);
+ ide_set_irq(s->bus);
+#ifdef DEBUG_IDE_ATAPI
+ printf("status=0x%x\n", s->status);
+#endif
+ }
+ }
+}
+
+/* send a reply of 'size' bytes in s->io_buffer to an ATAPI command */
+static void ide_atapi_cmd_reply(IDEState *s, int size, int max_size)
+{
+ if (size > max_size)
+ size = max_size;
+ s->lba = -1; /* no sector read */
+ s->packet_transfer_size = size;
+ s->io_buffer_size = size; /* dma: send the reply data as one chunk */
+ s->elementary_transfer_size = 0;
+
+ if (s->atapi_dma) {
+ block_acct_start(blk_get_stats(s->blk), &s->acct, size,
+ BLOCK_ACCT_READ);
+ s->status = READY_STAT | SEEK_STAT | DRQ_STAT;
+ ide_start_dma(s, ide_atapi_cmd_read_dma_cb);
+ } else {
+ s->status = READY_STAT | SEEK_STAT;
+ s->io_buffer_index = 0;
+ ide_atapi_cmd_reply_end(s);
+ }
+}
+
+/* start a CD-CDROM read command */
+static void ide_atapi_cmd_read_pio(IDEState *s, int lba, int nb_sectors,
+ int sector_size)
+{
+ s->lba = lba;
+ s->packet_transfer_size = nb_sectors * sector_size;
+ s->elementary_transfer_size = 0;
+ s->io_buffer_index = sector_size;
+ s->cd_sector_size = sector_size;
+
+ s->status = READY_STAT | SEEK_STAT;
+ ide_atapi_cmd_reply_end(s);
+}
+
+static void ide_atapi_cmd_check_status(IDEState *s)
+{
+#ifdef DEBUG_IDE_ATAPI
+ printf("atapi_cmd_check_status\n");
+#endif
+ s->error = MC_ERR | (UNIT_ATTENTION << 4);
+ s->status = ERR_STAT;
+ s->nsector = 0;
+ ide_set_irq(s->bus);
+}
+/* ATAPI DMA support */
+
+/* XXX: handle read errors */
+static void ide_atapi_cmd_read_dma_cb(void *opaque, int ret)
+{
+ IDEState *s = opaque;
+ int data_offset, n;
+
+ if (ret < 0) {
+ ide_atapi_io_error(s, ret);
+ goto eot;
+ }
+
+ if (s->io_buffer_size > 0) {
+ /*
+ * For a cdrom read sector command (s->lba != -1),
+ * adjust the lba for the next s->io_buffer_size chunk
+ * and dma the current chunk.
+ * For a command != read (s->lba == -1), just transfer
+ * the reply data.
+ */
+ if (s->lba != -1) {
+ if (s->cd_sector_size == 2352) {
+ n = 1;
+ cd_data_to_raw(s->io_buffer, s->lba);
+ } else {
+ n = s->io_buffer_size >> 11;
+ }
+ s->lba += n;
+ }
+ s->packet_transfer_size -= s->io_buffer_size;
+ if (s->bus->dma->ops->rw_buf(s->bus->dma, 1) == 0)
+ goto eot;
+ }
+
+ if (s->packet_transfer_size <= 0) {
+ s->status = READY_STAT | SEEK_STAT;
+ s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ ide_set_irq(s->bus);
+ goto eot;
+ }
+
+ s->io_buffer_index = 0;
+ if (s->cd_sector_size == 2352) {
+ n = 1;
+ s->io_buffer_size = s->cd_sector_size;
+ data_offset = 16;
+ } else {
+ n = s->packet_transfer_size >> 11;
+ if (n > (IDE_DMA_BUF_SECTORS / 4))
+ n = (IDE_DMA_BUF_SECTORS / 4);
+ s->io_buffer_size = n * 2048;
+ data_offset = 0;
+ }
+#ifdef DEBUG_AIO
+ printf("aio_read_cd: lba=%u n=%d\n", s->lba, n);
+#endif
+
+ s->bus->dma->iov.iov_base = (void *)(s->io_buffer + data_offset);
+ s->bus->dma->iov.iov_len = n * 4 * 512;
+ qemu_iovec_init_external(&s->bus->dma->qiov, &s->bus->dma->iov, 1);
+
+ s->bus->dma->aiocb = blk_aio_readv(s->blk, (int64_t)s->lba << 2,
+ &s->bus->dma->qiov, n * 4,
+ ide_atapi_cmd_read_dma_cb, s);
+ return;
+
+eot:
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ ide_set_inactive(s, false);
+}
+
+/* start a CD-CDROM read command with DMA */
+/* XXX: test if DMA is available */
+static void ide_atapi_cmd_read_dma(IDEState *s, int lba, int nb_sectors,
+ int sector_size)
+{
+ s->lba = lba;
+ s->packet_transfer_size = nb_sectors * sector_size;
+ s->io_buffer_size = 0;
+ s->cd_sector_size = sector_size;
+
+ block_acct_start(blk_get_stats(s->blk), &s->acct, s->packet_transfer_size,
+ BLOCK_ACCT_READ);
+
+ /* XXX: check if BUSY_STAT should be set */
+ s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT;
+ ide_start_dma(s, ide_atapi_cmd_read_dma_cb);
+}
+
+static void ide_atapi_cmd_read(IDEState *s, int lba, int nb_sectors,
+ int sector_size)
+{
+#ifdef DEBUG_IDE_ATAPI
+ printf("read %s: LBA=%d nb_sectors=%d\n", s->atapi_dma ? "dma" : "pio",
+ lba, nb_sectors);
+#endif
+ if (s->atapi_dma) {
+ ide_atapi_cmd_read_dma(s, lba, nb_sectors, sector_size);
+ } else {
+ ide_atapi_cmd_read_pio(s, lba, nb_sectors, sector_size);
+ }
+}
+
+
+/* Called by *_restart_bh when the transfer function points
+ * to ide_atapi_cmd
+ */
+void ide_atapi_dma_restart(IDEState *s)
+{
+ /*
+ * I'm not sure we have enough stored to restart the command
+ * safely, so give the guest an error it should recover from.
+ * I'm assuming most guests will try to recover from something
+ * listed as a medium error on a CD; it seems to work on Linux.
+ * This would be more of a problem if we did any other type of
+ * DMA operation.
+ */
+ ide_atapi_cmd_error(s, MEDIUM_ERROR, ASC_NO_SEEK_COMPLETE);
+}
+
+static inline uint8_t ide_atapi_set_profile(uint8_t *buf, uint8_t *index,
+ uint16_t profile)
+{
+ uint8_t *buf_profile = buf + 12; /* start of profiles */
+
+ buf_profile += ((*index) * 4); /* start of indexed profile */
+ cpu_to_ube16 (buf_profile, profile);
+ buf_profile[2] = ((buf_profile[0] == buf[6]) && (buf_profile[1] == buf[7]));
+
+ /* each profile adds 4 bytes to the response */
+ (*index)++;
+ buf[11] += 4; /* Additional Length */
+
+ return 4;
+}
+
+static int ide_dvd_read_structure(IDEState *s, int format,
+ const uint8_t *packet, uint8_t *buf)
+{
+ switch (format) {
+ case 0x0: /* Physical format information */
+ {
+ int layer = packet[6];
+ uint64_t total_sectors;
+
+ if (layer != 0)
+ return -ASC_INV_FIELD_IN_CMD_PACKET;
+
+ total_sectors = s->nb_sectors >> 2;
+ if (total_sectors == 0) {
+ return -ASC_MEDIUM_NOT_PRESENT;
+ }
+
+ buf[4] = 1; /* DVD-ROM, part version 1 */
+ buf[5] = 0xf; /* 120mm disc, minimum rate unspecified */
+ buf[6] = 1; /* one layer, read-only (per MMC-2 spec) */
+ buf[7] = 0; /* default densities */
+
+ /* FIXME: 0x30000 per spec? */
+ cpu_to_ube32(buf + 8, 0); /* start sector */
+ cpu_to_ube32(buf + 12, total_sectors - 1); /* end sector */
+ cpu_to_ube32(buf + 16, total_sectors - 1); /* l0 end sector */
+
+ /* Size of buffer, not including 2 byte size field */
+ stw_be_p(buf, 2048 + 2);
+
+ /* 2k data + 4 byte header */
+ return (2048 + 4);
+ }
+
+ case 0x01: /* DVD copyright information */
+ buf[4] = 0; /* no copyright data */
+ buf[5] = 0; /* no region restrictions */
+
+ /* Size of buffer, not including 2 byte size field */
+ stw_be_p(buf, 4 + 2);
+
+ /* 4 byte header + 4 byte data */
+ return (4 + 4);
+
+ case 0x03: /* BCA information - invalid field for no BCA info */
+ return -ASC_INV_FIELD_IN_CMD_PACKET;
+
+ case 0x04: /* DVD disc manufacturing information */
+ /* Size of buffer, not including 2 byte size field */
+ stw_be_p(buf, 2048 + 2);
+
+ /* 2k data + 4 byte header */
+ return (2048 + 4);
+
+ case 0xff:
+ /*
+ * This lists all the command capabilities above. Add new ones
+ * in order and update the length and buffer return values.
+ */
+
+ buf[4] = 0x00; /* Physical format */
+ buf[5] = 0x40; /* Not writable, is readable */
+ stw_be_p(buf + 6, 2048 + 4);
+
+ buf[8] = 0x01; /* Copyright info */
+ buf[9] = 0x40; /* Not writable, is readable */
+ stw_be_p(buf + 10, 4 + 4);
+
+ buf[12] = 0x03; /* BCA info */
+ buf[13] = 0x40; /* Not writable, is readable */
+ stw_be_p(buf + 14, 188 + 4);
+
+ buf[16] = 0x04; /* Manufacturing info */
+ buf[17] = 0x40; /* Not writable, is readable */
+ stw_be_p(buf + 18, 2048 + 4);
+
+ /* Size of buffer, not including 2 byte size field */
+ stw_be_p(buf, 16 + 2);
+
+ /* data written + 4 byte header */
+ return (16 + 4);
+
+ default: /* TODO: formats beyond DVD-ROM requires */
+ return -ASC_INV_FIELD_IN_CMD_PACKET;
+ }
+}
+
+static unsigned int event_status_media(IDEState *s,
+ uint8_t *buf)
+{
+ uint8_t event_code, media_status;
+
+ media_status = 0;
+ if (s->tray_open) {
+ media_status = MS_TRAY_OPEN;
+ } else if (blk_is_inserted(s->blk)) {
+ media_status = MS_MEDIA_PRESENT;
+ }
+
+ /* Event notification descriptor */
+ event_code = MEC_NO_CHANGE;
+ if (media_status != MS_TRAY_OPEN) {
+ if (s->events.new_media) {
+ event_code = MEC_NEW_MEDIA;
+ s->events.new_media = false;
+ } else if (s->events.eject_request) {
+ event_code = MEC_EJECT_REQUESTED;
+ s->events.eject_request = false;
+ }
+ }
+
+ buf[4] = event_code;
+ buf[5] = media_status;
+
+ /* These fields are reserved, just clear them. */
+ buf[6] = 0;
+ buf[7] = 0;
+
+ return 8; /* We wrote to 4 extra bytes from the header */
+}
+
+static void cmd_get_event_status_notification(IDEState *s,
+ uint8_t *buf)
+{
+ const uint8_t *packet = buf;
+
+ struct {
+ uint8_t opcode;
+ uint8_t polled; /* lsb bit is polled; others are reserved */
+ uint8_t reserved2[2];
+ uint8_t class;
+ uint8_t reserved3[2];
+ uint16_t len;
+ uint8_t control;
+ } QEMU_PACKED *gesn_cdb;
+
+ struct {
+ uint16_t len;
+ uint8_t notification_class;
+ uint8_t supported_events;
+ } QEMU_PACKED *gesn_event_header;
+ unsigned int max_len, used_len;
+
+ gesn_cdb = (void *)packet;
+ gesn_event_header = (void *)buf;
+
+ max_len = be16_to_cpu(gesn_cdb->len);
+
+ /* It is fine by the MMC spec to not support async mode operations */
+ if (!(gesn_cdb->polled & 0x01)) { /* asynchronous mode */
+ /* Only polling is supported, asynchronous mode is not. */
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INV_FIELD_IN_CMD_PACKET);
+ return;
+ }
+
+ /* polling mode operation */
+
+ /*
+ * These are the supported events.
+ *
+ * We currently only support requests of the 'media' type.
+ * Notification class requests and supported event classes are bitmasks,
+ * but they are build from the same values as the "notification class"
+ * field.
+ */
+ gesn_event_header->supported_events = 1 << GESN_MEDIA;
+
+ /*
+ * We use |= below to set the class field; other bits in this byte
+ * are reserved now but this is useful to do if we have to use the
+ * reserved fields later.
+ */
+ gesn_event_header->notification_class = 0;
+
+ /*
+ * Responses to requests are to be based on request priority. The
+ * notification_class_request_type enum above specifies the
+ * priority: upper elements are higher prio than lower ones.
+ */
+ if (gesn_cdb->class & (1 << GESN_MEDIA)) {
+ gesn_event_header->notification_class |= GESN_MEDIA;
+ used_len = event_status_media(s, buf);
+ } else {
+ gesn_event_header->notification_class = 0x80; /* No event available */
+ used_len = sizeof(*gesn_event_header);
+ }
+ gesn_event_header->len = cpu_to_be16(used_len
+ - sizeof(*gesn_event_header));
+ ide_atapi_cmd_reply(s, used_len, max_len);
+}
+
+static void cmd_request_sense(IDEState *s, uint8_t *buf)
+{
+ int max_len = buf[4];
+
+ memset(buf, 0, 18);
+ buf[0] = 0x70 | (1 << 7);
+ buf[2] = s->sense_key;
+ buf[7] = 10;
+ buf[12] = s->asc;
+
+ if (s->sense_key == UNIT_ATTENTION) {
+ s->sense_key = NO_SENSE;
+ }
+
+ ide_atapi_cmd_reply(s, 18, max_len);
+}
+
+static void cmd_inquiry(IDEState *s, uint8_t *buf)
+{
+ uint8_t page_code = buf[2];
+ int max_len = buf[4];
+
+ unsigned idx = 0;
+ unsigned size_idx;
+ unsigned preamble_len;
+
+ /* If the EVPD (Enable Vital Product Data) bit is set in byte 1,
+ * we are being asked for a specific page of info indicated by byte 2. */
+ if (buf[1] & 0x01) {
+ preamble_len = 4;
+ size_idx = 3;
+
+ buf[idx++] = 0x05; /* CD-ROM */
+ buf[idx++] = page_code; /* Page Code */
+ buf[idx++] = 0x00; /* reserved */
+ idx++; /* length (set later) */
+
+ switch (page_code) {
+ case 0x00:
+ /* Supported Pages: List of supported VPD responses. */
+ buf[idx++] = 0x00; /* 0x00: Supported Pages, and: */
+ buf[idx++] = 0x83; /* 0x83: Device Identification. */
+ break;
+
+ case 0x83:
+ /* Device Identification. Each entry is optional, but the entries
+ * included here are modeled after libata's VPD responses.
+ * If the response is given, at least one entry must be present. */
+
+ /* Entry 1: Serial */
+ if (idx + 24 > max_len) {
+ /* Not enough room for even the first entry: */
+ /* 4 byte header + 20 byte string */
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_DATA_PHASE_ERROR);
+ return;
+ }
+ buf[idx++] = 0x02; /* Ascii */
+ buf[idx++] = 0x00; /* Vendor Specific */
+ buf[idx++] = 0x00;
+ buf[idx++] = 20; /* Remaining length */
+ padstr8(buf + idx, 20, s->drive_serial_str);
+ idx += 20;
+
+ /* Entry 2: Drive Model and Serial */
+ if (idx + 72 > max_len) {
+ /* 4 (header) + 8 (vendor) + 60 (model & serial) */
+ goto out;
+ }
+ buf[idx++] = 0x02; /* Ascii */
+ buf[idx++] = 0x01; /* T10 Vendor */
+ buf[idx++] = 0x00;
+ buf[idx++] = 68;
+ padstr8(buf + idx, 8, "ATA"); /* Generic T10 vendor */
+ idx += 8;
+ padstr8(buf + idx, 40, s->drive_model_str);
+ idx += 40;
+ padstr8(buf + idx, 20, s->drive_serial_str);
+ idx += 20;
+
+ /* Entry 3: WWN */
+ if (s->wwn && (idx + 12 <= max_len)) {
+ /* 4 byte header + 8 byte wwn */
+ buf[idx++] = 0x01; /* Binary */
+ buf[idx++] = 0x03; /* NAA */
+ buf[idx++] = 0x00;
+ buf[idx++] = 0x08;
+ stq_be_p(&buf[idx], s->wwn);
+ idx += 8;
+ }
+ break;
+
+ default:
+ /* SPC-3, revision 23 sec. 6.4 */
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INV_FIELD_IN_CMD_PACKET);
+ return;
+ }
+ } else {
+ preamble_len = 5;
+ size_idx = 4;
+
+ buf[0] = 0x05; /* CD-ROM */
+ buf[1] = 0x80; /* removable */
+ buf[2] = 0x00; /* ISO */
+ buf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
+ /* buf[size_idx] set below. */
+ buf[5] = 0; /* reserved */
+ buf[6] = 0; /* reserved */
+ buf[7] = 0; /* reserved */
+ padstr8(buf + 8, 8, "QEMU");
+ padstr8(buf + 16, 16, "QEMU DVD-ROM");
+ padstr8(buf + 32, 4, s->version);
+ idx = 36;
+ }
+
+ out:
+ buf[size_idx] = idx - preamble_len;
+ ide_atapi_cmd_reply(s, idx, max_len);
+ return;
+}
+
+static void cmd_get_configuration(IDEState *s, uint8_t *buf)
+{
+ uint32_t len;
+ uint8_t index = 0;
+ int max_len;
+
+ /* only feature 0 is supported */
+ if (buf[2] != 0 || buf[3] != 0) {
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INV_FIELD_IN_CMD_PACKET);
+ return;
+ }
+
+ /* XXX: could result in alignment problems in some architectures */
+ max_len = ube16_to_cpu(buf + 7);
+
+ /*
+ * XXX: avoid overflow for io_buffer if max_len is bigger than
+ * the size of that buffer (dimensioned to max number of
+ * sectors to transfer at once)
+ *
+ * Only a problem if the feature/profiles grow.
+ */
+ if (max_len > 512) {
+ /* XXX: assume 1 sector */
+ max_len = 512;
+ }
+
+ memset(buf, 0, max_len);
+ /*
+ * the number of sectors from the media tells us which profile
+ * to use as current. 0 means there is no media
+ */
+ if (media_is_dvd(s)) {
+ cpu_to_ube16(buf + 6, MMC_PROFILE_DVD_ROM);
+ } else if (media_is_cd(s)) {
+ cpu_to_ube16(buf + 6, MMC_PROFILE_CD_ROM);
+ }
+
+ buf[10] = 0x02 | 0x01; /* persistent and current */
+ len = 12; /* headers: 8 + 4 */
+ len += ide_atapi_set_profile(buf, &index, MMC_PROFILE_DVD_ROM);
+ len += ide_atapi_set_profile(buf, &index, MMC_PROFILE_CD_ROM);
+ cpu_to_ube32(buf, len - 4); /* data length */
+
+ ide_atapi_cmd_reply(s, len, max_len);
+}
+
+static void cmd_mode_sense(IDEState *s, uint8_t *buf)
+{
+ int action, code;
+ int max_len;
+
+ max_len = ube16_to_cpu(buf + 7);
+ action = buf[2] >> 6;
+ code = buf[2] & 0x3f;
+
+ switch(action) {
+ case 0: /* current values */
+ switch(code) {
+ case MODE_PAGE_R_W_ERROR: /* error recovery */
+ cpu_to_ube16(&buf[0], 16 - 2);
+ buf[2] = 0x70;
+ buf[3] = 0;
+ buf[4] = 0;
+ buf[5] = 0;
+ buf[6] = 0;
+ buf[7] = 0;
+
+ buf[8] = MODE_PAGE_R_W_ERROR;
+ buf[9] = 16 - 10;
+ buf[10] = 0x00;
+ buf[11] = 0x05;
+ buf[12] = 0x00;
+ buf[13] = 0x00;
+ buf[14] = 0x00;
+ buf[15] = 0x00;
+ ide_atapi_cmd_reply(s, 16, max_len);
+ break;
+ case MODE_PAGE_AUDIO_CTL:
+ cpu_to_ube16(&buf[0], 24 - 2);
+ buf[2] = 0x70;
+ buf[3] = 0;
+ buf[4] = 0;
+ buf[5] = 0;
+ buf[6] = 0;
+ buf[7] = 0;
+
+ buf[8] = MODE_PAGE_AUDIO_CTL;
+ buf[9] = 24 - 10;
+ /* Fill with CDROM audio volume */
+ buf[17] = 0;
+ buf[19] = 0;
+ buf[21] = 0;
+ buf[23] = 0;
+
+ ide_atapi_cmd_reply(s, 24, max_len);
+ break;
+ case MODE_PAGE_CAPABILITIES:
+ cpu_to_ube16(&buf[0], 30 - 2);
+ buf[2] = 0x70;
+ buf[3] = 0;
+ buf[4] = 0;
+ buf[5] = 0;
+ buf[6] = 0;
+ buf[7] = 0;
+
+ buf[8] = MODE_PAGE_CAPABILITIES;
+ buf[9] = 30 - 10;
+ buf[10] = 0x3b; /* read CDR/CDRW/DVDROM/DVDR/DVDRAM */
+ buf[11] = 0x00;
+
+ /* Claim PLAY_AUDIO capability (0x01) since some Linux
+ code checks for this to automount media. */
+ buf[12] = 0x71;
+ buf[13] = 3 << 5;
+ buf[14] = (1 << 0) | (1 << 3) | (1 << 5);
+ if (s->tray_locked) {
+ buf[14] |= 1 << 1;
+ }
+ buf[15] = 0x00; /* No volume & mute control, no changer */
+ cpu_to_ube16(&buf[16], 704); /* 4x read speed */
+ buf[18] = 0; /* Two volume levels */
+ buf[19] = 2;
+ cpu_to_ube16(&buf[20], 512); /* 512k buffer */
+ cpu_to_ube16(&buf[22], 704); /* 4x read speed current */
+ buf[24] = 0;
+ buf[25] = 0;
+ buf[26] = 0;
+ buf[27] = 0;
+ buf[28] = 0;
+ buf[29] = 0;
+ ide_atapi_cmd_reply(s, 30, max_len);
+ break;
+ default:
+ goto error_cmd;
+ }
+ break;
+ case 1: /* changeable values */
+ goto error_cmd;
+ case 2: /* default values */
+ goto error_cmd;
+ default:
+ case 3: /* saved values */
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+ break;
+ }
+ return;
+
+error_cmd:
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
+}
+
+static void cmd_test_unit_ready(IDEState *s, uint8_t *buf)
+{
+ /* Not Ready Conditions are already handled in ide_atapi_cmd(), so if we
+ * come here, we know that it's ready. */
+ ide_atapi_cmd_ok(s);
+}
+
+static void cmd_prevent_allow_medium_removal(IDEState *s, uint8_t* buf)
+{
+ s->tray_locked = buf[4] & 1;
+ blk_lock_medium(s->blk, buf[4] & 1);
+ ide_atapi_cmd_ok(s);
+}
+
+static void cmd_read(IDEState *s, uint8_t* buf)
+{
+ int nb_sectors, lba;
+
+ if (buf[0] == GPCMD_READ_10) {
+ nb_sectors = ube16_to_cpu(buf + 7);
+ } else {
+ nb_sectors = ube32_to_cpu(buf + 6);
+ }
+
+ lba = ube32_to_cpu(buf + 2);
+ if (nb_sectors == 0) {
+ ide_atapi_cmd_ok(s);
+ return;
+ }
+
+ ide_atapi_cmd_read(s, lba, nb_sectors, 2048);
+}
+
+static void cmd_read_cd(IDEState *s, uint8_t* buf)
+{
+ int nb_sectors, lba, transfer_request;
+
+ nb_sectors = (buf[6] << 16) | (buf[7] << 8) | buf[8];
+ lba = ube32_to_cpu(buf + 2);
+
+ if (nb_sectors == 0) {
+ ide_atapi_cmd_ok(s);
+ return;
+ }
+
+ transfer_request = buf[9];
+ switch(transfer_request & 0xf8) {
+ case 0x00:
+ /* nothing */
+ ide_atapi_cmd_ok(s);
+ break;
+ case 0x10:
+ /* normal read */
+ ide_atapi_cmd_read(s, lba, nb_sectors, 2048);
+ break;
+ case 0xf8:
+ /* read all data */
+ ide_atapi_cmd_read(s, lba, nb_sectors, 2352);
+ break;
+ default:
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+}
+
+static void cmd_seek(IDEState *s, uint8_t* buf)
+{
+ unsigned int lba;
+ uint64_t total_sectors = s->nb_sectors >> 2;
+
+ lba = ube32_to_cpu(buf + 2);
+ if (lba >= total_sectors) {
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR);
+ return;
+ }
+
+ ide_atapi_cmd_ok(s);
+}
+
+static void cmd_start_stop_unit(IDEState *s, uint8_t* buf)
+{
+ int sense;
+ bool start = buf[4] & 1;
+ bool loej = buf[4] & 2; /* load on start, eject on !start */
+ int pwrcnd = buf[4] & 0xf0;
+
+ if (pwrcnd) {
+ /* eject/load only happens for power condition == 0 */
+ ide_atapi_cmd_ok(s);
+ return;
+ }
+
+ if (loej) {
+ if (!start && !s->tray_open && s->tray_locked) {
+ sense = blk_is_inserted(s->blk)
+ ? NOT_READY : ILLEGAL_REQUEST;
+ ide_atapi_cmd_error(s, sense, ASC_MEDIA_REMOVAL_PREVENTED);
+ return;
+ }
+
+ if (s->tray_open != !start) {
+ blk_eject(s->blk, !start);
+ s->tray_open = !start;
+ }
+ }
+
+ ide_atapi_cmd_ok(s);
+}
+
+static void cmd_mechanism_status(IDEState *s, uint8_t* buf)
+{
+ int max_len = ube16_to_cpu(buf + 8);
+
+ cpu_to_ube16(buf, 0);
+ /* no current LBA */
+ buf[2] = 0;
+ buf[3] = 0;
+ buf[4] = 0;
+ buf[5] = 1;
+ cpu_to_ube16(buf + 6, 0);
+ ide_atapi_cmd_reply(s, 8, max_len);
+}
+
+static void cmd_read_toc_pma_atip(IDEState *s, uint8_t* buf)
+{
+ int format, msf, start_track, len;
+ int max_len;
+ uint64_t total_sectors = s->nb_sectors >> 2;
+
+ max_len = ube16_to_cpu(buf + 7);
+ format = buf[9] >> 6;
+ msf = (buf[1] >> 1) & 1;
+ start_track = buf[6];
+
+ switch(format) {
+ case 0:
+ len = cdrom_read_toc(total_sectors, buf, msf, start_track);
+ if (len < 0)
+ goto error_cmd;
+ ide_atapi_cmd_reply(s, len, max_len);
+ break;
+ case 1:
+ /* multi session : only a single session defined */
+ memset(buf, 0, 12);
+ buf[1] = 0x0a;
+ buf[2] = 0x01;
+ buf[3] = 0x01;
+ ide_atapi_cmd_reply(s, 12, max_len);
+ break;
+ case 2:
+ len = cdrom_read_toc_raw(total_sectors, buf, msf, start_track);
+ if (len < 0)
+ goto error_cmd;
+ ide_atapi_cmd_reply(s, len, max_len);
+ break;
+ default:
+ error_cmd:
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INV_FIELD_IN_CMD_PACKET);
+ }
+}
+
+static void cmd_read_cdvd_capacity(IDEState *s, uint8_t* buf)
+{
+ uint64_t total_sectors = s->nb_sectors >> 2;
+
+ /* NOTE: it is really the number of sectors minus 1 */
+ cpu_to_ube32(buf, total_sectors - 1);
+ cpu_to_ube32(buf + 4, 2048);
+ ide_atapi_cmd_reply(s, 8, 8);
+}
+
+static void cmd_read_disc_information(IDEState *s, uint8_t* buf)
+{
+ uint8_t type = buf[1] & 7;
+ uint32_t max_len = ube16_to_cpu(buf + 7);
+
+ /* Types 1/2 are only defined for Blu-Ray. */
+ if (type != 0) {
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INV_FIELD_IN_CMD_PACKET);
+ return;
+ }
+
+ memset(buf, 0, 34);
+ buf[1] = 32;
+ buf[2] = 0xe; /* last session complete, disc finalized */
+ buf[3] = 1; /* first track on disc */
+ buf[4] = 1; /* # of sessions */
+ buf[5] = 1; /* first track of last session */
+ buf[6] = 1; /* last track of last session */
+ buf[7] = 0x20; /* unrestricted use */
+ buf[8] = 0x00; /* CD-ROM or DVD-ROM */
+ /* 9-10-11: most significant byte corresponding bytes 4-5-6 */
+ /* 12-23: not meaningful for CD-ROM or DVD-ROM */
+ /* 24-31: disc bar code */
+ /* 32: disc application code */
+ /* 33: number of OPC tables */
+
+ ide_atapi_cmd_reply(s, 34, max_len);
+}
+
+static void cmd_read_dvd_structure(IDEState *s, uint8_t* buf)
+{
+ int max_len;
+ int media = buf[1];
+ int format = buf[7];
+ int ret;
+
+ max_len = ube16_to_cpu(buf + 8);
+
+ if (format < 0xff) {
+ if (media_is_cd(s)) {
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INCOMPATIBLE_FORMAT);
+ return;
+ } else if (!media_present(s)) {
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INV_FIELD_IN_CMD_PACKET);
+ return;
+ }
+ }
+
+ memset(buf, 0, max_len > IDE_DMA_BUF_SECTORS * 512 + 4 ?
+ IDE_DMA_BUF_SECTORS * 512 + 4 : max_len);
+
+ switch (format) {
+ case 0x00 ... 0x7f:
+ case 0xff:
+ if (media == 0) {
+ ret = ide_dvd_read_structure(s, format, buf, buf);
+
+ if (ret < 0) {
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST, -ret);
+ } else {
+ ide_atapi_cmd_reply(s, ret, max_len);
+ }
+
+ break;
+ }
+ /* TODO: BD support, fall through for now */
+
+ /* Generic disk structures */
+ case 0x80: /* TODO: AACS volume identifier */
+ case 0x81: /* TODO: AACS media serial number */
+ case 0x82: /* TODO: AACS media identifier */
+ case 0x83: /* TODO: AACS media key block */
+ case 0x90: /* TODO: List of recognized format layers */
+ case 0xc0: /* TODO: Write protection status */
+ default:
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
+ ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+}
+
+static void cmd_set_speed(IDEState *s, uint8_t* buf)
+{
+ ide_atapi_cmd_ok(s);
+}
+
+enum {
+ /*
+ * Only commands flagged as ALLOW_UA are allowed to run under a
+ * unit attention condition. (See MMC-5, section 4.1.6.1)
+ */
+ ALLOW_UA = 0x01,
+
+ /*
+ * Commands flagged with CHECK_READY can only execute if a medium is present.
+ * Otherwise they report the Not Ready Condition. (See MMC-5, section
+ * 4.1.8)
+ */
+ CHECK_READY = 0x02,
+};
+
+static const struct {
+ void (*handler)(IDEState *s, uint8_t *buf);
+ int flags;
+} atapi_cmd_table[0x100] = {
+ [ 0x00 ] = { cmd_test_unit_ready, CHECK_READY },
+ [ 0x03 ] = { cmd_request_sense, ALLOW_UA },
+ [ 0x12 ] = { cmd_inquiry, ALLOW_UA },
+ [ 0x1b ] = { cmd_start_stop_unit, 0 }, /* [1] */
+ [ 0x1e ] = { cmd_prevent_allow_medium_removal, 0 },
+ [ 0x25 ] = { cmd_read_cdvd_capacity, CHECK_READY },
+ [ 0x28 ] = { cmd_read, /* (10) */ CHECK_READY },
+ [ 0x2b ] = { cmd_seek, CHECK_READY },
+ [ 0x43 ] = { cmd_read_toc_pma_atip, CHECK_READY },
+ [ 0x46 ] = { cmd_get_configuration, ALLOW_UA },
+ [ 0x4a ] = { cmd_get_event_status_notification, ALLOW_UA },
+ [ 0x51 ] = { cmd_read_disc_information, CHECK_READY },
+ [ 0x5a ] = { cmd_mode_sense, /* (10) */ 0 },
+ [ 0xa8 ] = { cmd_read, /* (12) */ CHECK_READY },
+ [ 0xad ] = { cmd_read_dvd_structure, CHECK_READY },
+ [ 0xbb ] = { cmd_set_speed, 0 },
+ [ 0xbd ] = { cmd_mechanism_status, 0 },
+ [ 0xbe ] = { cmd_read_cd, CHECK_READY },
+ /* [1] handler detects and reports not ready condition itself */
+};
+
+void ide_atapi_cmd(IDEState *s)
+{
+ uint8_t *buf;
+
+ buf = s->io_buffer;
+#ifdef DEBUG_IDE_ATAPI
+ {
+ int i;
+ printf("ATAPI limit=0x%x packet:", s->lcyl | (s->hcyl << 8));
+ for(i = 0; i < ATAPI_PACKET_SIZE; i++) {
+ printf(" %02x", buf[i]);
+ }
+ printf("\n");
+ }
+#endif
+ /*
+ * If there's a UNIT_ATTENTION condition pending, only command flagged with
+ * ALLOW_UA are allowed to complete. with other commands getting a CHECK
+ * condition response unless a higher priority status, defined by the drive
+ * here, is pending.
+ */
+ if (s->sense_key == UNIT_ATTENTION &&
+ !(atapi_cmd_table[s->io_buffer[0]].flags & ALLOW_UA)) {
+ ide_atapi_cmd_check_status(s);
+ return;
+ }
+ /*
+ * When a CD gets changed, we have to report an ejected state and
+ * then a loaded state to guests so that they detect tray
+ * open/close and media change events. Guests that do not use
+ * GET_EVENT_STATUS_NOTIFICATION to detect such tray open/close
+ * states rely on this behavior.
+ */
+ if (!(atapi_cmd_table[s->io_buffer[0]].flags & ALLOW_UA) &&
+ !s->tray_open && blk_is_inserted(s->blk) && s->cdrom_changed) {
+
+ if (s->cdrom_changed == 1) {
+ ide_atapi_cmd_error(s, NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+ s->cdrom_changed = 2;
+ } else {
+ ide_atapi_cmd_error(s, UNIT_ATTENTION, ASC_MEDIUM_MAY_HAVE_CHANGED);
+ s->cdrom_changed = 0;
+ }
+
+ return;
+ }
+
+ /* Report a Not Ready condition if appropriate for the command */
+ if ((atapi_cmd_table[s->io_buffer[0]].flags & CHECK_READY) &&
+ (!media_present(s) || !blk_is_inserted(s->blk)))
+ {
+ ide_atapi_cmd_error(s, NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+ return;
+ }
+
+ /* Execute the command */
+ if (atapi_cmd_table[s->io_buffer[0]].handler) {
+ atapi_cmd_table[s->io_buffer[0]].handler(s, buf);
+ return;
+ }
+
+ ide_atapi_cmd_error(s, ILLEGAL_REQUEST, ASC_ILLEGAL_OPCODE);
+}
diff --git a/hw/ide/cmd646.c b/hw/ide/cmd646.c
new file mode 100644
index 00000000..66fb9d96
--- /dev/null
+++ b/hw/ide/cmd646.c
@@ -0,0 +1,433 @@
+/*
+ * QEMU IDE Emulation: PCI cmd646 support.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+#include <hw/hw.h>
+#include <hw/i386/pc.h>
+#include <hw/pci/pci.h>
+#include <hw/isa/isa.h>
+#include "sysemu/block-backend.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/dma.h"
+
+#include <hw/ide/pci.h>
+
+/* CMD646 specific */
+#define CFR 0x50
+#define CFR_INTR_CH0 0x04
+#define CNTRL 0x51
+#define CNTRL_EN_CH0 0x04
+#define CNTRL_EN_CH1 0x08
+#define ARTTIM23 0x57
+#define ARTTIM23_INTR_CH1 0x10
+#define MRDMODE 0x71
+#define MRDMODE_INTR_CH0 0x04
+#define MRDMODE_INTR_CH1 0x08
+#define MRDMODE_BLK_CH0 0x10
+#define MRDMODE_BLK_CH1 0x20
+#define UDIDETCR0 0x73
+#define UDIDETCR1 0x7B
+
+static void cmd646_update_irq(PCIDevice *pd);
+
+static uint64_t cmd646_cmd_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CMD646BAR *cmd646bar = opaque;
+
+ if (addr != 2 || size != 1) {
+ return ((uint64_t)1 << (size * 8)) - 1;
+ }
+ return ide_status_read(cmd646bar->bus, addr + 2);
+}
+
+static void cmd646_cmd_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ CMD646BAR *cmd646bar = opaque;
+
+ if (addr != 2 || size != 1) {
+ return;
+ }
+ ide_cmd_write(cmd646bar->bus, addr + 2, data);
+}
+
+static const MemoryRegionOps cmd646_cmd_ops = {
+ .read = cmd646_cmd_read,
+ .write = cmd646_cmd_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t cmd646_data_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CMD646BAR *cmd646bar = opaque;
+
+ if (size == 1) {
+ return ide_ioport_read(cmd646bar->bus, addr);
+ } else if (addr == 0) {
+ if (size == 2) {
+ return ide_data_readw(cmd646bar->bus, addr);
+ } else {
+ return ide_data_readl(cmd646bar->bus, addr);
+ }
+ }
+ return ((uint64_t)1 << (size * 8)) - 1;
+}
+
+static void cmd646_data_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ CMD646BAR *cmd646bar = opaque;
+
+ if (size == 1) {
+ ide_ioport_write(cmd646bar->bus, addr, data);
+ } else if (addr == 0) {
+ if (size == 2) {
+ ide_data_writew(cmd646bar->bus, addr, data);
+ } else {
+ ide_data_writel(cmd646bar->bus, addr, data);
+ }
+ }
+}
+
+static const MemoryRegionOps cmd646_data_ops = {
+ .read = cmd646_data_read,
+ .write = cmd646_data_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void setup_cmd646_bar(PCIIDEState *d, int bus_num)
+{
+ IDEBus *bus = &d->bus[bus_num];
+ CMD646BAR *bar = &d->cmd646_bar[bus_num];
+
+ bar->bus = bus;
+ bar->pci_dev = d;
+ memory_region_init_io(&bar->cmd, OBJECT(d), &cmd646_cmd_ops, bar,
+ "cmd646-cmd", 4);
+ memory_region_init_io(&bar->data, OBJECT(d), &cmd646_data_ops, bar,
+ "cmd646-data", 8);
+}
+
+static void cmd646_update_dma_interrupts(PCIDevice *pd)
+{
+ /* Sync DMA interrupt status from UDMA interrupt status */
+ if (pd->config[MRDMODE] & MRDMODE_INTR_CH0) {
+ pd->config[CFR] |= CFR_INTR_CH0;
+ } else {
+ pd->config[CFR] &= ~CFR_INTR_CH0;
+ }
+
+ if (pd->config[MRDMODE] & MRDMODE_INTR_CH1) {
+ pd->config[ARTTIM23] |= ARTTIM23_INTR_CH1;
+ } else {
+ pd->config[ARTTIM23] &= ~ARTTIM23_INTR_CH1;
+ }
+}
+
+static void cmd646_update_udma_interrupts(PCIDevice *pd)
+{
+ /* Sync UDMA interrupt status from DMA interrupt status */
+ if (pd->config[CFR] & CFR_INTR_CH0) {
+ pd->config[MRDMODE] |= MRDMODE_INTR_CH0;
+ } else {
+ pd->config[MRDMODE] &= ~MRDMODE_INTR_CH0;
+ }
+
+ if (pd->config[ARTTIM23] & ARTTIM23_INTR_CH1) {
+ pd->config[MRDMODE] |= MRDMODE_INTR_CH1;
+ } else {
+ pd->config[MRDMODE] &= ~MRDMODE_INTR_CH1;
+ }
+}
+
+static uint64_t bmdma_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ BMDMAState *bm = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(bm->pci_dev);
+ uint32_t val;
+
+ if (size != 1) {
+ return ((uint64_t)1 << (size * 8)) - 1;
+ }
+
+ switch(addr & 3) {
+ case 0:
+ val = bm->cmd;
+ break;
+ case 1:
+ val = pci_dev->config[MRDMODE];
+ break;
+ case 2:
+ val = bm->status;
+ break;
+ case 3:
+ if (bm == &bm->pci_dev->bmdma[0]) {
+ val = pci_dev->config[UDIDETCR0];
+ } else {
+ val = pci_dev->config[UDIDETCR1];
+ }
+ break;
+ default:
+ val = 0xff;
+ break;
+ }
+#ifdef DEBUG_IDE
+ printf("bmdma: readb " TARGET_FMT_plx " : 0x%02x\n", addr, val);
+#endif
+ return val;
+}
+
+static void bmdma_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ BMDMAState *bm = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(bm->pci_dev);
+
+ if (size != 1) {
+ return;
+ }
+
+#ifdef DEBUG_IDE
+ printf("bmdma: writeb " TARGET_FMT_plx " : 0x%" PRIx64 "\n", addr, val);
+#endif
+ switch(addr & 3) {
+ case 0:
+ bmdma_cmd_writeb(bm, val);
+ break;
+ case 1:
+ pci_dev->config[MRDMODE] =
+ (pci_dev->config[MRDMODE] & ~0x30) | (val & 0x30);
+ cmd646_update_dma_interrupts(pci_dev);
+ cmd646_update_irq(pci_dev);
+ break;
+ case 2:
+ bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06);
+ break;
+ case 3:
+ if (bm == &bm->pci_dev->bmdma[0]) {
+ pci_dev->config[UDIDETCR0] = val;
+ } else {
+ pci_dev->config[UDIDETCR1] = val;
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps cmd646_bmdma_ops = {
+ .read = bmdma_read,
+ .write = bmdma_write,
+};
+
+static void bmdma_setup_bar(PCIIDEState *d)
+{
+ BMDMAState *bm;
+ int i;
+
+ memory_region_init(&d->bmdma_bar, OBJECT(d), "cmd646-bmdma", 16);
+ for(i = 0;i < 2; i++) {
+ bm = &d->bmdma[i];
+ memory_region_init_io(&bm->extra_io, OBJECT(d), &cmd646_bmdma_ops, bm,
+ "cmd646-bmdma-bus", 4);
+ memory_region_add_subregion(&d->bmdma_bar, i * 8, &bm->extra_io);
+ memory_region_init_io(&bm->addr_ioport, OBJECT(d),
+ &bmdma_addr_ioport_ops, bm,
+ "cmd646-bmdma-ioport", 4);
+ memory_region_add_subregion(&d->bmdma_bar, i * 8 + 4, &bm->addr_ioport);
+ }
+}
+
+static void cmd646_update_irq(PCIDevice *pd)
+{
+ int pci_level;
+
+ pci_level = ((pd->config[MRDMODE] & MRDMODE_INTR_CH0) &&
+ !(pd->config[MRDMODE] & MRDMODE_BLK_CH0)) ||
+ ((pd->config[MRDMODE] & MRDMODE_INTR_CH1) &&
+ !(pd->config[MRDMODE] & MRDMODE_BLK_CH1));
+ pci_set_irq(pd, pci_level);
+}
+
+/* the PCI irq level is the logical OR of the two channels */
+static void cmd646_set_irq(void *opaque, int channel, int level)
+{
+ PCIIDEState *d = opaque;
+ PCIDevice *pd = PCI_DEVICE(d);
+ int irq_mask;
+
+ irq_mask = MRDMODE_INTR_CH0 << channel;
+ if (level) {
+ pd->config[MRDMODE] |= irq_mask;
+ } else {
+ pd->config[MRDMODE] &= ~irq_mask;
+ }
+ cmd646_update_dma_interrupts(pd);
+ cmd646_update_irq(pd);
+}
+
+static void cmd646_reset(void *opaque)
+{
+ PCIIDEState *d = opaque;
+ unsigned int i;
+
+ for (i = 0; i < 2; i++) {
+ ide_bus_reset(&d->bus[i]);
+ }
+}
+
+static uint32_t cmd646_pci_config_read(PCIDevice *d,
+ uint32_t address, int len)
+{
+ return pci_default_read_config(d, address, len);
+}
+
+static void cmd646_pci_config_write(PCIDevice *d, uint32_t addr, uint32_t val,
+ int l)
+{
+ uint32_t i;
+
+ pci_default_write_config(d, addr, val, l);
+
+ for (i = addr; i < addr + l; i++) {
+ switch (i) {
+ case CFR:
+ case ARTTIM23:
+ cmd646_update_udma_interrupts(d);
+ break;
+ case MRDMODE:
+ cmd646_update_dma_interrupts(d);
+ break;
+ }
+ }
+
+ cmd646_update_irq(d);
+}
+
+/* CMD646 PCI IDE controller */
+static void pci_cmd646_ide_realize(PCIDevice *dev, Error **errp)
+{
+ PCIIDEState *d = PCI_IDE(dev);
+ uint8_t *pci_conf = dev->config;
+ qemu_irq *irq;
+ int i;
+
+ pci_conf[PCI_CLASS_PROG] = 0x8f;
+
+ pci_conf[CNTRL] = CNTRL_EN_CH0; // enable IDE0
+ if (d->secondary) {
+ /* XXX: if not enabled, really disable the seconday IDE controller */
+ pci_conf[CNTRL] |= CNTRL_EN_CH1; /* enable IDE1 */
+ }
+
+ /* Set write-to-clear interrupt bits */
+ dev->wmask[CFR] = 0x0;
+ dev->w1cmask[CFR] = CFR_INTR_CH0;
+ dev->wmask[ARTTIM23] = 0x0;
+ dev->w1cmask[ARTTIM23] = ARTTIM23_INTR_CH1;
+ dev->wmask[MRDMODE] = 0x0;
+ dev->w1cmask[MRDMODE] = MRDMODE_INTR_CH0 | MRDMODE_INTR_CH1;
+
+ setup_cmd646_bar(d, 0);
+ setup_cmd646_bar(d, 1);
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd646_bar[0].data);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd646_bar[0].cmd);
+ pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd646_bar[1].data);
+ pci_register_bar(dev, 3, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd646_bar[1].cmd);
+ bmdma_setup_bar(d);
+ pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &d->bmdma_bar);
+
+ /* TODO: RST# value should be 0 */
+ pci_conf[PCI_INTERRUPT_PIN] = 0x01; // interrupt on pin 1
+
+ irq = qemu_allocate_irqs(cmd646_set_irq, d, 2);
+ for (i = 0; i < 2; i++) {
+ ide_bus_new(&d->bus[i], sizeof(d->bus[i]), DEVICE(dev), i, 2);
+ ide_init2(&d->bus[i], irq[i]);
+
+ bmdma_init(&d->bus[i], &d->bmdma[i], d);
+ d->bmdma[i].bus = &d->bus[i];
+ ide_register_restart_cb(&d->bus[i]);
+ }
+
+ vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
+ qemu_register_reset(cmd646_reset, d);
+}
+
+static void pci_cmd646_ide_exitfn(PCIDevice *dev)
+{
+ PCIIDEState *d = PCI_IDE(dev);
+ unsigned i;
+
+ for (i = 0; i < 2; ++i) {
+ memory_region_del_subregion(&d->bmdma_bar, &d->bmdma[i].extra_io);
+ memory_region_del_subregion(&d->bmdma_bar, &d->bmdma[i].addr_ioport);
+ }
+}
+
+void pci_cmd646_ide_init(PCIBus *bus, DriveInfo **hd_table,
+ int secondary_ide_enabled)
+{
+ PCIDevice *dev;
+
+ dev = pci_create(bus, -1, "cmd646-ide");
+ qdev_prop_set_uint32(&dev->qdev, "secondary", secondary_ide_enabled);
+ qdev_init_nofail(&dev->qdev);
+
+ pci_ide_create_devs(dev, hd_table);
+}
+
+static Property cmd646_ide_properties[] = {
+ DEFINE_PROP_UINT32("secondary", PCIIDEState, secondary, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void cmd646_ide_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_cmd646_ide_realize;
+ k->exit = pci_cmd646_ide_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_CMD;
+ k->device_id = PCI_DEVICE_ID_CMD_646;
+ k->revision = 0x07;
+ k->class_id = PCI_CLASS_STORAGE_IDE;
+ k->config_read = cmd646_pci_config_read;
+ k->config_write = cmd646_pci_config_write;
+ dc->props = cmd646_ide_properties;
+}
+
+static const TypeInfo cmd646_ide_info = {
+ .name = "cmd646-ide",
+ .parent = TYPE_PCI_IDE,
+ .class_init = cmd646_ide_class_init,
+};
+
+static void cmd646_ide_register_types(void)
+{
+ type_register_static(&cmd646_ide_info);
+}
+
+type_init(cmd646_ide_register_types)
diff --git a/hw/ide/core.c b/hw/ide/core.c
new file mode 100644
index 00000000..1cc6945d
--- /dev/null
+++ b/hw/ide/core.c
@@ -0,0 +1,2723 @@
+/*
+ * QEMU IDE disk and CD/DVD-ROM Emulator
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+#include <hw/hw.h>
+#include <hw/i386/pc.h>
+#include <hw/pci/pci.h>
+#include <hw/isa/isa.h>
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/dma.h"
+#include "hw/block/block.h"
+#include "sysemu/block-backend.h"
+
+#include <hw/ide/internal.h>
+
+/* These values were based on a Seagate ST3500418AS but have been modified
+ to make more sense in QEMU */
+static const int smart_attributes[][12] = {
+ /* id, flags, hflags, val, wrst, raw (6 bytes), threshold */
+ /* raw read error rate*/
+ { 0x01, 0x03, 0x00, 0x64, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06},
+ /* spin up */
+ { 0x03, 0x03, 0x00, 0x64, 0x64, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ /* start stop count */
+ { 0x04, 0x02, 0x00, 0x64, 0x64, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14},
+ /* remapped sectors */
+ { 0x05, 0x03, 0x00, 0x64, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24},
+ /* power on hours */
+ { 0x09, 0x03, 0x00, 0x64, 0x64, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ /* power cycle count */
+ { 0x0c, 0x03, 0x00, 0x64, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ /* airflow-temperature-celsius */
+ { 190, 0x03, 0x00, 0x45, 0x45, 0x1f, 0x00, 0x1f, 0x1f, 0x00, 0x00, 0x32},
+};
+
+static int ide_handle_rw_error(IDEState *s, int error, int op);
+static void ide_dummy_transfer_stop(IDEState *s);
+
+static void padstr(char *str, const char *src, int len)
+{
+ int i, v;
+ for(i = 0; i < len; i++) {
+ if (*src)
+ v = *src++;
+ else
+ v = ' ';
+ str[i^1] = v;
+ }
+}
+
+static void put_le16(uint16_t *p, unsigned int v)
+{
+ *p = cpu_to_le16(v);
+}
+
+static void ide_identify_size(IDEState *s)
+{
+ uint16_t *p = (uint16_t *)s->identify_data;
+ put_le16(p + 60, s->nb_sectors);
+ put_le16(p + 61, s->nb_sectors >> 16);
+ put_le16(p + 100, s->nb_sectors);
+ put_le16(p + 101, s->nb_sectors >> 16);
+ put_le16(p + 102, s->nb_sectors >> 32);
+ put_le16(p + 103, s->nb_sectors >> 48);
+}
+
+static void ide_identify(IDEState *s)
+{
+ uint16_t *p;
+ unsigned int oldsize;
+ IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master;
+
+ p = (uint16_t *)s->identify_data;
+ if (s->identify_set) {
+ goto fill_buffer;
+ }
+ memset(p, 0, sizeof(s->identify_data));
+
+ put_le16(p + 0, 0x0040);
+ put_le16(p + 1, s->cylinders);
+ put_le16(p + 3, s->heads);
+ put_le16(p + 4, 512 * s->sectors); /* XXX: retired, remove ? */
+ put_le16(p + 5, 512); /* XXX: retired, remove ? */
+ put_le16(p + 6, s->sectors);
+ padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
+ put_le16(p + 20, 3); /* XXX: retired, remove ? */
+ put_le16(p + 21, 512); /* cache size in sectors */
+ put_le16(p + 22, 4); /* ecc bytes */
+ padstr((char *)(p + 23), s->version, 8); /* firmware version */
+ padstr((char *)(p + 27), s->drive_model_str, 40); /* model */
+#if MAX_MULT_SECTORS > 1
+ put_le16(p + 47, 0x8000 | MAX_MULT_SECTORS);
+#endif
+ put_le16(p + 48, 1); /* dword I/O */
+ put_le16(p + 49, (1 << 11) | (1 << 9) | (1 << 8)); /* DMA and LBA supported */
+ put_le16(p + 51, 0x200); /* PIO transfer cycle */
+ put_le16(p + 52, 0x200); /* DMA transfer cycle */
+ put_le16(p + 53, 1 | (1 << 1) | (1 << 2)); /* words 54-58,64-70,88 are valid */
+ put_le16(p + 54, s->cylinders);
+ put_le16(p + 55, s->heads);
+ put_le16(p + 56, s->sectors);
+ oldsize = s->cylinders * s->heads * s->sectors;
+ put_le16(p + 57, oldsize);
+ put_le16(p + 58, oldsize >> 16);
+ if (s->mult_sectors)
+ put_le16(p + 59, 0x100 | s->mult_sectors);
+ /* *(p + 60) := nb_sectors -- see ide_identify_size */
+ /* *(p + 61) := nb_sectors >> 16 -- see ide_identify_size */
+ put_le16(p + 62, 0x07); /* single word dma0-2 supported */
+ put_le16(p + 63, 0x07); /* mdma0-2 supported */
+ put_le16(p + 64, 0x03); /* pio3-4 supported */
+ put_le16(p + 65, 120);
+ put_le16(p + 66, 120);
+ put_le16(p + 67, 120);
+ put_le16(p + 68, 120);
+ if (dev && dev->conf.discard_granularity) {
+ put_le16(p + 69, (1 << 14)); /* determinate TRIM behavior */
+ }
+
+ if (s->ncq_queues) {
+ put_le16(p + 75, s->ncq_queues - 1);
+ /* NCQ supported */
+ put_le16(p + 76, (1 << 8));
+ }
+
+ put_le16(p + 80, 0xf0); /* ata3 -> ata6 supported */
+ put_le16(p + 81, 0x16); /* conforms to ata5 */
+ /* 14=NOP supported, 5=WCACHE supported, 0=SMART supported */
+ put_le16(p + 82, (1 << 14) | (1 << 5) | 1);
+ /* 13=flush_cache_ext,12=flush_cache,10=lba48 */
+ put_le16(p + 83, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10));
+ /* 14=set to 1, 8=has WWN, 1=SMART self test, 0=SMART error logging */
+ if (s->wwn) {
+ put_le16(p + 84, (1 << 14) | (1 << 8) | 0);
+ } else {
+ put_le16(p + 84, (1 << 14) | 0);
+ }
+ /* 14 = NOP supported, 5=WCACHE enabled, 0=SMART feature set enabled */
+ if (blk_enable_write_cache(s->blk)) {
+ put_le16(p + 85, (1 << 14) | (1 << 5) | 1);
+ } else {
+ put_le16(p + 85, (1 << 14) | 1);
+ }
+ /* 13=flush_cache_ext,12=flush_cache,10=lba48 */
+ put_le16(p + 86, (1 << 13) | (1 <<12) | (1 << 10));
+ /* 14=set to 1, 8=has WWN, 1=SMART self test, 0=SMART error logging */
+ if (s->wwn) {
+ put_le16(p + 87, (1 << 14) | (1 << 8) | 0);
+ } else {
+ put_le16(p + 87, (1 << 14) | 0);
+ }
+ put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */
+ put_le16(p + 93, 1 | (1 << 14) | 0x2000);
+ /* *(p + 100) := nb_sectors -- see ide_identify_size */
+ /* *(p + 101) := nb_sectors >> 16 -- see ide_identify_size */
+ /* *(p + 102) := nb_sectors >> 32 -- see ide_identify_size */
+ /* *(p + 103) := nb_sectors >> 48 -- see ide_identify_size */
+
+ if (dev && dev->conf.physical_block_size)
+ put_le16(p + 106, 0x6000 | get_physical_block_exp(&dev->conf));
+ if (s->wwn) {
+ /* LE 16-bit words 111-108 contain 64-bit World Wide Name */
+ put_le16(p + 108, s->wwn >> 48);
+ put_le16(p + 109, s->wwn >> 32);
+ put_le16(p + 110, s->wwn >> 16);
+ put_le16(p + 111, s->wwn);
+ }
+ if (dev && dev->conf.discard_granularity) {
+ put_le16(p + 169, 1); /* TRIM support */
+ }
+
+ ide_identify_size(s);
+ s->identify_set = 1;
+
+fill_buffer:
+ memcpy(s->io_buffer, p, sizeof(s->identify_data));
+}
+
+static void ide_atapi_identify(IDEState *s)
+{
+ uint16_t *p;
+
+ p = (uint16_t *)s->identify_data;
+ if (s->identify_set) {
+ goto fill_buffer;
+ }
+ memset(p, 0, sizeof(s->identify_data));
+
+ /* Removable CDROM, 50us response, 12 byte packets */
+ put_le16(p + 0, (2 << 14) | (5 << 8) | (1 << 7) | (2 << 5) | (0 << 0));
+ padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
+ put_le16(p + 20, 3); /* buffer type */
+ put_le16(p + 21, 512); /* cache size in sectors */
+ put_le16(p + 22, 4); /* ecc bytes */
+ padstr((char *)(p + 23), s->version, 8); /* firmware version */
+ padstr((char *)(p + 27), s->drive_model_str, 40); /* model */
+ put_le16(p + 48, 1); /* dword I/O (XXX: should not be set on CDROM) */
+#ifdef USE_DMA_CDROM
+ put_le16(p + 49, 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ put_le16(p + 53, 7); /* words 64-70, 54-58, 88 valid */
+ put_le16(p + 62, 7); /* single word dma0-2 supported */
+ put_le16(p + 63, 7); /* mdma0-2 supported */
+#else
+ put_le16(p + 49, 1 << 9); /* LBA supported, no DMA */
+ put_le16(p + 53, 3); /* words 64-70, 54-58 valid */
+ put_le16(p + 63, 0x103); /* DMA modes XXX: may be incorrect */
+#endif
+ put_le16(p + 64, 3); /* pio3-4 supported */
+ put_le16(p + 65, 0xb4); /* minimum DMA multiword tx cycle time */
+ put_le16(p + 66, 0xb4); /* recommended DMA multiword tx cycle time */
+ put_le16(p + 67, 0x12c); /* minimum PIO cycle time without flow control */
+ put_le16(p + 68, 0xb4); /* minimum PIO cycle time with IORDY flow control */
+
+ put_le16(p + 71, 30); /* in ns */
+ put_le16(p + 72, 30); /* in ns */
+
+ if (s->ncq_queues) {
+ put_le16(p + 75, s->ncq_queues - 1);
+ /* NCQ supported */
+ put_le16(p + 76, (1 << 8));
+ }
+
+ put_le16(p + 80, 0x1e); /* support up to ATA/ATAPI-4 */
+ if (s->wwn) {
+ put_le16(p + 84, (1 << 8)); /* supports WWN for words 108-111 */
+ put_le16(p + 87, (1 << 8)); /* WWN enabled */
+ }
+
+#ifdef USE_DMA_CDROM
+ put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */
+#endif
+
+ if (s->wwn) {
+ /* LE 16-bit words 111-108 contain 64-bit World Wide Name */
+ put_le16(p + 108, s->wwn >> 48);
+ put_le16(p + 109, s->wwn >> 32);
+ put_le16(p + 110, s->wwn >> 16);
+ put_le16(p + 111, s->wwn);
+ }
+
+ s->identify_set = 1;
+
+fill_buffer:
+ memcpy(s->io_buffer, p, sizeof(s->identify_data));
+}
+
+static void ide_cfata_identify_size(IDEState *s)
+{
+ uint16_t *p = (uint16_t *)s->identify_data;
+ put_le16(p + 7, s->nb_sectors >> 16); /* Sectors per card */
+ put_le16(p + 8, s->nb_sectors); /* Sectors per card */
+ put_le16(p + 60, s->nb_sectors); /* Total LBA sectors */
+ put_le16(p + 61, s->nb_sectors >> 16); /* Total LBA sectors */
+}
+
+static void ide_cfata_identify(IDEState *s)
+{
+ uint16_t *p;
+ uint32_t cur_sec;
+
+ p = (uint16_t *)s->identify_data;
+ if (s->identify_set) {
+ goto fill_buffer;
+ }
+ memset(p, 0, sizeof(s->identify_data));
+
+ cur_sec = s->cylinders * s->heads * s->sectors;
+
+ put_le16(p + 0, 0x848a); /* CF Storage Card signature */
+ put_le16(p + 1, s->cylinders); /* Default cylinders */
+ put_le16(p + 3, s->heads); /* Default heads */
+ put_le16(p + 6, s->sectors); /* Default sectors per track */
+ /* *(p + 7) := nb_sectors >> 16 -- see ide_cfata_identify_size */
+ /* *(p + 8) := nb_sectors -- see ide_cfata_identify_size */
+ padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
+ put_le16(p + 22, 0x0004); /* ECC bytes */
+ padstr((char *) (p + 23), s->version, 8); /* Firmware Revision */
+ padstr((char *) (p + 27), s->drive_model_str, 40);/* Model number */
+#if MAX_MULT_SECTORS > 1
+ put_le16(p + 47, 0x8000 | MAX_MULT_SECTORS);
+#else
+ put_le16(p + 47, 0x0000);
+#endif
+ put_le16(p + 49, 0x0f00); /* Capabilities */
+ put_le16(p + 51, 0x0002); /* PIO cycle timing mode */
+ put_le16(p + 52, 0x0001); /* DMA cycle timing mode */
+ put_le16(p + 53, 0x0003); /* Translation params valid */
+ put_le16(p + 54, s->cylinders); /* Current cylinders */
+ put_le16(p + 55, s->heads); /* Current heads */
+ put_le16(p + 56, s->sectors); /* Current sectors */
+ put_le16(p + 57, cur_sec); /* Current capacity */
+ put_le16(p + 58, cur_sec >> 16); /* Current capacity */
+ if (s->mult_sectors) /* Multiple sector setting */
+ put_le16(p + 59, 0x100 | s->mult_sectors);
+ /* *(p + 60) := nb_sectors -- see ide_cfata_identify_size */
+ /* *(p + 61) := nb_sectors >> 16 -- see ide_cfata_identify_size */
+ put_le16(p + 63, 0x0203); /* Multiword DMA capability */
+ put_le16(p + 64, 0x0001); /* Flow Control PIO support */
+ put_le16(p + 65, 0x0096); /* Min. Multiword DMA cycle */
+ put_le16(p + 66, 0x0096); /* Rec. Multiword DMA cycle */
+ put_le16(p + 68, 0x00b4); /* Min. PIO cycle time */
+ put_le16(p + 82, 0x400c); /* Command Set supported */
+ put_le16(p + 83, 0x7068); /* Command Set supported */
+ put_le16(p + 84, 0x4000); /* Features supported */
+ put_le16(p + 85, 0x000c); /* Command Set enabled */
+ put_le16(p + 86, 0x7044); /* Command Set enabled */
+ put_le16(p + 87, 0x4000); /* Features enabled */
+ put_le16(p + 91, 0x4060); /* Current APM level */
+ put_le16(p + 129, 0x0002); /* Current features option */
+ put_le16(p + 130, 0x0005); /* Reassigned sectors */
+ put_le16(p + 131, 0x0001); /* Initial power mode */
+ put_le16(p + 132, 0x0000); /* User signature */
+ put_le16(p + 160, 0x8100); /* Power requirement */
+ put_le16(p + 161, 0x8001); /* CF command set */
+
+ ide_cfata_identify_size(s);
+ s->identify_set = 1;
+
+fill_buffer:
+ memcpy(s->io_buffer, p, sizeof(s->identify_data));
+}
+
+static void ide_set_signature(IDEState *s)
+{
+ s->select &= 0xf0; /* clear head */
+ /* put signature */
+ s->nsector = 1;
+ s->sector = 1;
+ if (s->drive_kind == IDE_CD) {
+ s->lcyl = 0x14;
+ s->hcyl = 0xeb;
+ } else if (s->blk) {
+ s->lcyl = 0;
+ s->hcyl = 0;
+ } else {
+ s->lcyl = 0xff;
+ s->hcyl = 0xff;
+ }
+}
+
+typedef struct TrimAIOCB {
+ BlockAIOCB common;
+ BlockBackend *blk;
+ QEMUBH *bh;
+ int ret;
+ QEMUIOVector *qiov;
+ BlockAIOCB *aiocb;
+ int i, j;
+} TrimAIOCB;
+
+static void trim_aio_cancel(BlockAIOCB *acb)
+{
+ TrimAIOCB *iocb = container_of(acb, TrimAIOCB, common);
+
+ /* Exit the loop so ide_issue_trim_cb will not continue */
+ iocb->j = iocb->qiov->niov - 1;
+ iocb->i = (iocb->qiov->iov[iocb->j].iov_len / 8) - 1;
+
+ iocb->ret = -ECANCELED;
+
+ if (iocb->aiocb) {
+ blk_aio_cancel_async(iocb->aiocb);
+ iocb->aiocb = NULL;
+ }
+}
+
+static const AIOCBInfo trim_aiocb_info = {
+ .aiocb_size = sizeof(TrimAIOCB),
+ .cancel_async = trim_aio_cancel,
+};
+
+static void ide_trim_bh_cb(void *opaque)
+{
+ TrimAIOCB *iocb = opaque;
+
+ iocb->common.cb(iocb->common.opaque, iocb->ret);
+
+ qemu_bh_delete(iocb->bh);
+ iocb->bh = NULL;
+ qemu_aio_unref(iocb);
+}
+
+static void ide_issue_trim_cb(void *opaque, int ret)
+{
+ TrimAIOCB *iocb = opaque;
+ if (ret >= 0) {
+ while (iocb->j < iocb->qiov->niov) {
+ int j = iocb->j;
+ while (++iocb->i < iocb->qiov->iov[j].iov_len / 8) {
+ int i = iocb->i;
+ uint64_t *buffer = iocb->qiov->iov[j].iov_base;
+
+ /* 6-byte LBA + 2-byte range per entry */
+ uint64_t entry = le64_to_cpu(buffer[i]);
+ uint64_t sector = entry & 0x0000ffffffffffffULL;
+ uint16_t count = entry >> 48;
+
+ if (count == 0) {
+ continue;
+ }
+
+ /* Got an entry! Submit and exit. */
+ iocb->aiocb = blk_aio_discard(iocb->blk, sector, count,
+ ide_issue_trim_cb, opaque);
+ return;
+ }
+
+ iocb->j++;
+ iocb->i = -1;
+ }
+ } else {
+ iocb->ret = ret;
+ }
+
+ iocb->aiocb = NULL;
+ if (iocb->bh) {
+ qemu_bh_schedule(iocb->bh);
+ }
+}
+
+BlockAIOCB *ide_issue_trim(BlockBackend *blk,
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
+ BlockCompletionFunc *cb, void *opaque)
+{
+ TrimAIOCB *iocb;
+
+ iocb = blk_aio_get(&trim_aiocb_info, blk, cb, opaque);
+ iocb->blk = blk;
+ iocb->bh = qemu_bh_new(ide_trim_bh_cb, iocb);
+ iocb->ret = 0;
+ iocb->qiov = qiov;
+ iocb->i = -1;
+ iocb->j = 0;
+ ide_issue_trim_cb(iocb, 0);
+ return &iocb->common;
+}
+
+static inline void ide_abort_command(IDEState *s)
+{
+ ide_transfer_stop(s);
+ s->status = READY_STAT | ERR_STAT;
+ s->error = ABRT_ERR;
+}
+
+/* prepare data transfer and tell what to do after */
+void ide_transfer_start(IDEState *s, uint8_t *buf, int size,
+ EndTransferFunc *end_transfer_func)
+{
+ s->end_transfer_func = end_transfer_func;
+ s->data_ptr = buf;
+ s->data_end = buf + size;
+ if (!(s->status & ERR_STAT)) {
+ s->status |= DRQ_STAT;
+ }
+ if (s->bus->dma->ops->start_transfer) {
+ s->bus->dma->ops->start_transfer(s->bus->dma);
+ }
+}
+
+static void ide_cmd_done(IDEState *s)
+{
+ if (s->bus->dma->ops->cmd_done) {
+ s->bus->dma->ops->cmd_done(s->bus->dma);
+ }
+}
+
+void ide_transfer_stop(IDEState *s)
+{
+ s->end_transfer_func = ide_transfer_stop;
+ s->data_ptr = s->io_buffer;
+ s->data_end = s->io_buffer;
+ s->status &= ~DRQ_STAT;
+ ide_cmd_done(s);
+}
+
+int64_t ide_get_sector(IDEState *s)
+{
+ int64_t sector_num;
+ if (s->select & 0x40) {
+ /* lba */
+ if (!s->lba48) {
+ sector_num = ((s->select & 0x0f) << 24) | (s->hcyl << 16) |
+ (s->lcyl << 8) | s->sector;
+ } else {
+ sector_num = ((int64_t)s->hob_hcyl << 40) |
+ ((int64_t) s->hob_lcyl << 32) |
+ ((int64_t) s->hob_sector << 24) |
+ ((int64_t) s->hcyl << 16) |
+ ((int64_t) s->lcyl << 8) | s->sector;
+ }
+ } else {
+ sector_num = ((s->hcyl << 8) | s->lcyl) * s->heads * s->sectors +
+ (s->select & 0x0f) * s->sectors + (s->sector - 1);
+ }
+ return sector_num;
+}
+
+void ide_set_sector(IDEState *s, int64_t sector_num)
+{
+ unsigned int cyl, r;
+ if (s->select & 0x40) {
+ if (!s->lba48) {
+ s->select = (s->select & 0xf0) | (sector_num >> 24);
+ s->hcyl = (sector_num >> 16);
+ s->lcyl = (sector_num >> 8);
+ s->sector = (sector_num);
+ } else {
+ s->sector = sector_num;
+ s->lcyl = sector_num >> 8;
+ s->hcyl = sector_num >> 16;
+ s->hob_sector = sector_num >> 24;
+ s->hob_lcyl = sector_num >> 32;
+ s->hob_hcyl = sector_num >> 40;
+ }
+ } else {
+ cyl = sector_num / (s->heads * s->sectors);
+ r = sector_num % (s->heads * s->sectors);
+ s->hcyl = cyl >> 8;
+ s->lcyl = cyl;
+ s->select = (s->select & 0xf0) | ((r / s->sectors) & 0x0f);
+ s->sector = (r % s->sectors) + 1;
+ }
+}
+
+static void ide_rw_error(IDEState *s) {
+ ide_abort_command(s);
+ ide_set_irq(s->bus);
+}
+
+static bool ide_sect_range_ok(IDEState *s,
+ uint64_t sector, uint64_t nb_sectors)
+{
+ uint64_t total_sectors;
+
+ blk_get_geometry(s->blk, &total_sectors);
+ if (sector > total_sectors || nb_sectors > total_sectors - sector) {
+ return false;
+ }
+ return true;
+}
+
+static void ide_sector_read(IDEState *s);
+
+static void ide_sector_read_cb(void *opaque, int ret)
+{
+ IDEState *s = opaque;
+ int n;
+
+ s->pio_aiocb = NULL;
+ s->status &= ~BUSY_STAT;
+
+ if (ret == -ECANCELED) {
+ return;
+ }
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ if (ret != 0) {
+ if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO |
+ IDE_RETRY_READ)) {
+ return;
+ }
+ }
+
+ n = s->nsector;
+ if (n > s->req_nb_sectors) {
+ n = s->req_nb_sectors;
+ }
+
+ ide_set_sector(s, ide_get_sector(s) + n);
+ s->nsector -= n;
+ /* Allow the guest to read the io_buffer */
+ ide_transfer_start(s, s->io_buffer, n * BDRV_SECTOR_SIZE, ide_sector_read);
+ ide_set_irq(s->bus);
+}
+
+static void ide_sector_read(IDEState *s)
+{
+ int64_t sector_num;
+ int n;
+
+ s->status = READY_STAT | SEEK_STAT;
+ s->error = 0; /* not needed by IDE spec, but needed by Windows */
+ sector_num = ide_get_sector(s);
+ n = s->nsector;
+
+ if (n == 0) {
+ ide_transfer_stop(s);
+ return;
+ }
+
+ s->status |= BUSY_STAT;
+
+ if (n > s->req_nb_sectors) {
+ n = s->req_nb_sectors;
+ }
+
+#if defined(DEBUG_IDE)
+ printf("sector=%" PRId64 "\n", sector_num);
+#endif
+
+ if (!ide_sect_range_ok(s, sector_num, n)) {
+ ide_rw_error(s);
+ return;
+ }
+
+ s->iov.iov_base = s->io_buffer;
+ s->iov.iov_len = n * BDRV_SECTOR_SIZE;
+ qemu_iovec_init_external(&s->qiov, &s->iov, 1);
+
+ block_acct_start(blk_get_stats(s->blk), &s->acct,
+ n * BDRV_SECTOR_SIZE, BLOCK_ACCT_READ);
+ s->pio_aiocb = blk_aio_readv(s->blk, sector_num, &s->qiov, n,
+ ide_sector_read_cb, s);
+}
+
+void dma_buf_commit(IDEState *s, uint32_t tx_bytes)
+{
+ if (s->bus->dma->ops->commit_buf) {
+ s->bus->dma->ops->commit_buf(s->bus->dma, tx_bytes);
+ }
+ s->io_buffer_offset += tx_bytes;
+ qemu_sglist_destroy(&s->sg);
+}
+
+void ide_set_inactive(IDEState *s, bool more)
+{
+ s->bus->dma->aiocb = NULL;
+ s->bus->retry_unit = -1;
+ s->bus->retry_sector_num = 0;
+ s->bus->retry_nsector = 0;
+ if (s->bus->dma->ops->set_inactive) {
+ s->bus->dma->ops->set_inactive(s->bus->dma, more);
+ }
+ ide_cmd_done(s);
+}
+
+void ide_dma_error(IDEState *s)
+{
+ dma_buf_commit(s, 0);
+ ide_abort_command(s);
+ ide_set_inactive(s, false);
+ ide_set_irq(s->bus);
+}
+
+static int ide_handle_rw_error(IDEState *s, int error, int op)
+{
+ bool is_read = (op & IDE_RETRY_READ) != 0;
+ BlockErrorAction action = blk_get_error_action(s->blk, is_read, error);
+
+ if (action == BLOCK_ERROR_ACTION_STOP) {
+ assert(s->bus->retry_unit == s->unit);
+ s->bus->error_status = op;
+ } else if (action == BLOCK_ERROR_ACTION_REPORT) {
+ if (op & IDE_RETRY_DMA) {
+ ide_dma_error(s);
+ } else {
+ ide_rw_error(s);
+ }
+ }
+ blk_error_action(s->blk, action, is_read, error);
+ return action != BLOCK_ERROR_ACTION_IGNORE;
+}
+
+static void ide_dma_cb(void *opaque, int ret)
+{
+ IDEState *s = opaque;
+ int n;
+ int64_t sector_num;
+ bool stay_active = false;
+
+ if (ret == -ECANCELED) {
+ return;
+ }
+ if (ret < 0) {
+ int op = IDE_RETRY_DMA;
+
+ if (s->dma_cmd == IDE_DMA_READ)
+ op |= IDE_RETRY_READ;
+ else if (s->dma_cmd == IDE_DMA_TRIM)
+ op |= IDE_RETRY_TRIM;
+
+ if (ide_handle_rw_error(s, -ret, op)) {
+ return;
+ }
+ }
+
+ n = s->io_buffer_size >> 9;
+ if (n > s->nsector) {
+ /* The PRDs were longer than needed for this request. Shorten them so
+ * we don't get a negative remainder. The Active bit must remain set
+ * after the request completes. */
+ n = s->nsector;
+ stay_active = true;
+ }
+
+ sector_num = ide_get_sector(s);
+ if (n > 0) {
+ assert(n * 512 == s->sg.size);
+ dma_buf_commit(s, s->sg.size);
+ sector_num += n;
+ ide_set_sector(s, sector_num);
+ s->nsector -= n;
+ }
+
+ /* end of transfer ? */
+ if (s->nsector == 0) {
+ s->status = READY_STAT | SEEK_STAT;
+ ide_set_irq(s->bus);
+ goto eot;
+ }
+
+ /* launch next transfer */
+ n = s->nsector;
+ s->io_buffer_index = 0;
+ s->io_buffer_size = n * 512;
+ if (s->bus->dma->ops->prepare_buf(s->bus->dma, s->io_buffer_size) < 512) {
+ /* The PRDs were too short. Reset the Active bit, but don't raise an
+ * interrupt. */
+ s->status = READY_STAT | SEEK_STAT;
+ dma_buf_commit(s, 0);
+ goto eot;
+ }
+
+#ifdef DEBUG_AIO
+ printf("ide_dma_cb: sector_num=%" PRId64 " n=%d, cmd_cmd=%d\n",
+ sector_num, n, s->dma_cmd);
+#endif
+
+ if ((s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) &&
+ !ide_sect_range_ok(s, sector_num, n)) {
+ ide_dma_error(s);
+ return;
+ }
+
+ switch (s->dma_cmd) {
+ case IDE_DMA_READ:
+ s->bus->dma->aiocb = dma_blk_read(s->blk, &s->sg, sector_num,
+ ide_dma_cb, s);
+ break;
+ case IDE_DMA_WRITE:
+ s->bus->dma->aiocb = dma_blk_write(s->blk, &s->sg, sector_num,
+ ide_dma_cb, s);
+ break;
+ case IDE_DMA_TRIM:
+ s->bus->dma->aiocb = dma_blk_io(s->blk, &s->sg, sector_num,
+ ide_issue_trim, ide_dma_cb, s,
+ DMA_DIRECTION_TO_DEVICE);
+ break;
+ }
+ return;
+
+eot:
+ if (s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) {
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ }
+ ide_set_inactive(s, stay_active);
+}
+
+static void ide_sector_start_dma(IDEState *s, enum ide_dma_cmd dma_cmd)
+{
+ s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT;
+ s->io_buffer_size = 0;
+ s->dma_cmd = dma_cmd;
+
+ switch (dma_cmd) {
+ case IDE_DMA_READ:
+ block_acct_start(blk_get_stats(s->blk), &s->acct,
+ s->nsector * BDRV_SECTOR_SIZE, BLOCK_ACCT_READ);
+ break;
+ case IDE_DMA_WRITE:
+ block_acct_start(blk_get_stats(s->blk), &s->acct,
+ s->nsector * BDRV_SECTOR_SIZE, BLOCK_ACCT_WRITE);
+ break;
+ default:
+ break;
+ }
+
+ ide_start_dma(s, ide_dma_cb);
+}
+
+void ide_start_dma(IDEState *s, BlockCompletionFunc *cb)
+{
+ s->io_buffer_index = 0;
+ s->bus->retry_unit = s->unit;
+ s->bus->retry_sector_num = ide_get_sector(s);
+ s->bus->retry_nsector = s->nsector;
+ if (s->bus->dma->ops->start_dma) {
+ s->bus->dma->ops->start_dma(s->bus->dma, s, cb);
+ }
+}
+
+static void ide_sector_write(IDEState *s);
+
+static void ide_sector_write_timer_cb(void *opaque)
+{
+ IDEState *s = opaque;
+ ide_set_irq(s->bus);
+}
+
+static void ide_sector_write_cb(void *opaque, int ret)
+{
+ IDEState *s = opaque;
+ int n;
+
+ if (ret == -ECANCELED) {
+ return;
+ }
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+
+ s->pio_aiocb = NULL;
+ s->status &= ~BUSY_STAT;
+
+ if (ret != 0) {
+ if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO)) {
+ return;
+ }
+ }
+
+ n = s->nsector;
+ if (n > s->req_nb_sectors) {
+ n = s->req_nb_sectors;
+ }
+ s->nsector -= n;
+
+ ide_set_sector(s, ide_get_sector(s) + n);
+ if (s->nsector == 0) {
+ /* no more sectors to write */
+ ide_transfer_stop(s);
+ } else {
+ int n1 = s->nsector;
+ if (n1 > s->req_nb_sectors) {
+ n1 = s->req_nb_sectors;
+ }
+ ide_transfer_start(s, s->io_buffer, n1 * BDRV_SECTOR_SIZE,
+ ide_sector_write);
+ }
+
+ if (win2k_install_hack && ((++s->irq_count % 16) == 0)) {
+ /* It seems there is a bug in the Windows 2000 installer HDD
+ IDE driver which fills the disk with empty logs when the
+ IDE write IRQ comes too early. This hack tries to correct
+ that at the expense of slower write performances. Use this
+ option _only_ to install Windows 2000. You must disable it
+ for normal use. */
+ timer_mod(s->sector_write_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() / 1000));
+ } else {
+ ide_set_irq(s->bus);
+ }
+}
+
+static void ide_sector_write(IDEState *s)
+{
+ int64_t sector_num;
+ int n;
+
+ s->status = READY_STAT | SEEK_STAT | BUSY_STAT;
+ sector_num = ide_get_sector(s);
+#if defined(DEBUG_IDE)
+ printf("sector=%" PRId64 "\n", sector_num);
+#endif
+ n = s->nsector;
+ if (n > s->req_nb_sectors) {
+ n = s->req_nb_sectors;
+ }
+
+ if (!ide_sect_range_ok(s, sector_num, n)) {
+ ide_rw_error(s);
+ return;
+ }
+
+ s->iov.iov_base = s->io_buffer;
+ s->iov.iov_len = n * BDRV_SECTOR_SIZE;
+ qemu_iovec_init_external(&s->qiov, &s->iov, 1);
+
+ block_acct_start(blk_get_stats(s->blk), &s->acct,
+ n * BDRV_SECTOR_SIZE, BLOCK_ACCT_READ);
+ s->pio_aiocb = blk_aio_writev(s->blk, sector_num, &s->qiov, n,
+ ide_sector_write_cb, s);
+}
+
+static void ide_flush_cb(void *opaque, int ret)
+{
+ IDEState *s = opaque;
+
+ s->pio_aiocb = NULL;
+
+ if (ret == -ECANCELED) {
+ return;
+ }
+ if (ret < 0) {
+ /* XXX: What sector number to set here? */
+ if (ide_handle_rw_error(s, -ret, IDE_RETRY_FLUSH)) {
+ return;
+ }
+ }
+
+ if (s->blk) {
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ }
+ s->status = READY_STAT | SEEK_STAT;
+ ide_cmd_done(s);
+ ide_set_irq(s->bus);
+}
+
+static void ide_flush_cache(IDEState *s)
+{
+ if (s->blk == NULL) {
+ ide_flush_cb(s, 0);
+ return;
+ }
+
+ s->status |= BUSY_STAT;
+ block_acct_start(blk_get_stats(s->blk), &s->acct, 0, BLOCK_ACCT_FLUSH);
+ s->pio_aiocb = blk_aio_flush(s->blk, ide_flush_cb, s);
+}
+
+static void ide_cfata_metadata_inquiry(IDEState *s)
+{
+ uint16_t *p;
+ uint32_t spd;
+
+ p = (uint16_t *) s->io_buffer;
+ memset(p, 0, 0x200);
+ spd = ((s->mdata_size - 1) >> 9) + 1;
+
+ put_le16(p + 0, 0x0001); /* Data format revision */
+ put_le16(p + 1, 0x0000); /* Media property: silicon */
+ put_le16(p + 2, s->media_changed); /* Media status */
+ put_le16(p + 3, s->mdata_size & 0xffff); /* Capacity in bytes (low) */
+ put_le16(p + 4, s->mdata_size >> 16); /* Capacity in bytes (high) */
+ put_le16(p + 5, spd & 0xffff); /* Sectors per device (low) */
+ put_le16(p + 6, spd >> 16); /* Sectors per device (high) */
+}
+
+static void ide_cfata_metadata_read(IDEState *s)
+{
+ uint16_t *p;
+
+ if (((s->hcyl << 16) | s->lcyl) << 9 > s->mdata_size + 2) {
+ s->status = ERR_STAT;
+ s->error = ABRT_ERR;
+ return;
+ }
+
+ p = (uint16_t *) s->io_buffer;
+ memset(p, 0, 0x200);
+
+ put_le16(p + 0, s->media_changed); /* Media status */
+ memcpy(p + 1, s->mdata_storage + (((s->hcyl << 16) | s->lcyl) << 9),
+ MIN(MIN(s->mdata_size - (((s->hcyl << 16) | s->lcyl) << 9),
+ s->nsector << 9), 0x200 - 2));
+}
+
+static void ide_cfata_metadata_write(IDEState *s)
+{
+ if (((s->hcyl << 16) | s->lcyl) << 9 > s->mdata_size + 2) {
+ s->status = ERR_STAT;
+ s->error = ABRT_ERR;
+ return;
+ }
+
+ s->media_changed = 0;
+
+ memcpy(s->mdata_storage + (((s->hcyl << 16) | s->lcyl) << 9),
+ s->io_buffer + 2,
+ MIN(MIN(s->mdata_size - (((s->hcyl << 16) | s->lcyl) << 9),
+ s->nsector << 9), 0x200 - 2));
+}
+
+/* called when the inserted state of the media has changed */
+static void ide_cd_change_cb(void *opaque, bool load)
+{
+ IDEState *s = opaque;
+ uint64_t nb_sectors;
+
+ s->tray_open = !load;
+ blk_get_geometry(s->blk, &nb_sectors);
+ s->nb_sectors = nb_sectors;
+
+ /*
+ * First indicate to the guest that a CD has been removed. That's
+ * done on the next command the guest sends us.
+ *
+ * Then we set UNIT_ATTENTION, by which the guest will
+ * detect a new CD in the drive. See ide_atapi_cmd() for details.
+ */
+ s->cdrom_changed = 1;
+ s->events.new_media = true;
+ s->events.eject_request = false;
+ ide_set_irq(s->bus);
+}
+
+static void ide_cd_eject_request_cb(void *opaque, bool force)
+{
+ IDEState *s = opaque;
+
+ s->events.eject_request = true;
+ if (force) {
+ s->tray_locked = false;
+ }
+ ide_set_irq(s->bus);
+}
+
+static void ide_cmd_lba48_transform(IDEState *s, int lba48)
+{
+ s->lba48 = lba48;
+
+ /* handle the 'magic' 0 nsector count conversion here. to avoid
+ * fiddling with the rest of the read logic, we just store the
+ * full sector count in ->nsector and ignore ->hob_nsector from now
+ */
+ if (!s->lba48) {
+ if (!s->nsector)
+ s->nsector = 256;
+ } else {
+ if (!s->nsector && !s->hob_nsector)
+ s->nsector = 65536;
+ else {
+ int lo = s->nsector;
+ int hi = s->hob_nsector;
+
+ s->nsector = (hi << 8) | lo;
+ }
+ }
+}
+
+static void ide_clear_hob(IDEBus *bus)
+{
+ /* any write clears HOB high bit of device control register */
+ bus->ifs[0].select &= ~(1 << 7);
+ bus->ifs[1].select &= ~(1 << 7);
+}
+
+void ide_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ IDEBus *bus = opaque;
+
+#ifdef DEBUG_IDE
+ printf("IDE: write addr=0x%x val=0x%02x\n", addr, val);
+#endif
+
+ addr &= 7;
+
+ /* ignore writes to command block while busy with previous command */
+ if (addr != 7 && (idebus_active_if(bus)->status & (BUSY_STAT|DRQ_STAT)))
+ return;
+
+ switch(addr) {
+ case 0:
+ break;
+ case 1:
+ ide_clear_hob(bus);
+ /* NOTE: data is written to the two drives */
+ bus->ifs[0].hob_feature = bus->ifs[0].feature;
+ bus->ifs[1].hob_feature = bus->ifs[1].feature;
+ bus->ifs[0].feature = val;
+ bus->ifs[1].feature = val;
+ break;
+ case 2:
+ ide_clear_hob(bus);
+ bus->ifs[0].hob_nsector = bus->ifs[0].nsector;
+ bus->ifs[1].hob_nsector = bus->ifs[1].nsector;
+ bus->ifs[0].nsector = val;
+ bus->ifs[1].nsector = val;
+ break;
+ case 3:
+ ide_clear_hob(bus);
+ bus->ifs[0].hob_sector = bus->ifs[0].sector;
+ bus->ifs[1].hob_sector = bus->ifs[1].sector;
+ bus->ifs[0].sector = val;
+ bus->ifs[1].sector = val;
+ break;
+ case 4:
+ ide_clear_hob(bus);
+ bus->ifs[0].hob_lcyl = bus->ifs[0].lcyl;
+ bus->ifs[1].hob_lcyl = bus->ifs[1].lcyl;
+ bus->ifs[0].lcyl = val;
+ bus->ifs[1].lcyl = val;
+ break;
+ case 5:
+ ide_clear_hob(bus);
+ bus->ifs[0].hob_hcyl = bus->ifs[0].hcyl;
+ bus->ifs[1].hob_hcyl = bus->ifs[1].hcyl;
+ bus->ifs[0].hcyl = val;
+ bus->ifs[1].hcyl = val;
+ break;
+ case 6:
+ /* FIXME: HOB readback uses bit 7 */
+ bus->ifs[0].select = (val & ~0x10) | 0xa0;
+ bus->ifs[1].select = (val | 0x10) | 0xa0;
+ /* select drive */
+ bus->unit = (val >> 4) & 1;
+ break;
+ default:
+ case 7:
+ /* command */
+ ide_exec_cmd(bus, val);
+ break;
+ }
+}
+
+static bool cmd_nop(IDEState *s, uint8_t cmd)
+{
+ return true;
+}
+
+static bool cmd_data_set_management(IDEState *s, uint8_t cmd)
+{
+ switch (s->feature) {
+ case DSM_TRIM:
+ if (s->blk) {
+ ide_sector_start_dma(s, IDE_DMA_TRIM);
+ return false;
+ }
+ break;
+ }
+
+ ide_abort_command(s);
+ return true;
+}
+
+static bool cmd_identify(IDEState *s, uint8_t cmd)
+{
+ if (s->blk && s->drive_kind != IDE_CD) {
+ if (s->drive_kind != IDE_CFATA) {
+ ide_identify(s);
+ } else {
+ ide_cfata_identify(s);
+ }
+ s->status = READY_STAT | SEEK_STAT;
+ ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop);
+ ide_set_irq(s->bus);
+ return false;
+ } else {
+ if (s->drive_kind == IDE_CD) {
+ ide_set_signature(s);
+ }
+ ide_abort_command(s);
+ }
+
+ return true;
+}
+
+static bool cmd_verify(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_VERIFY_EXT);
+
+ /* do sector number check ? */
+ ide_cmd_lba48_transform(s, lba48);
+
+ return true;
+}
+
+static bool cmd_set_multiple_mode(IDEState *s, uint8_t cmd)
+{
+ if (s->drive_kind == IDE_CFATA && s->nsector == 0) {
+ /* Disable Read and Write Multiple */
+ s->mult_sectors = 0;
+ } else if ((s->nsector & 0xff) != 0 &&
+ ((s->nsector & 0xff) > MAX_MULT_SECTORS ||
+ (s->nsector & (s->nsector - 1)) != 0)) {
+ ide_abort_command(s);
+ } else {
+ s->mult_sectors = s->nsector & 0xff;
+ }
+
+ return true;
+}
+
+static bool cmd_read_multiple(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_MULTREAD_EXT);
+
+ if (!s->blk || !s->mult_sectors) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ s->req_nb_sectors = s->mult_sectors;
+ ide_sector_read(s);
+ return false;
+}
+
+static bool cmd_write_multiple(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_MULTWRITE_EXT);
+ int n;
+
+ if (!s->blk || !s->mult_sectors) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+
+ s->req_nb_sectors = s->mult_sectors;
+ n = MIN(s->nsector, s->req_nb_sectors);
+
+ s->status = SEEK_STAT | READY_STAT;
+ ide_transfer_start(s, s->io_buffer, 512 * n, ide_sector_write);
+
+ s->media_changed = 1;
+
+ return false;
+}
+
+static bool cmd_read_pio(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_READ_EXT);
+
+ if (s->drive_kind == IDE_CD) {
+ ide_set_signature(s); /* odd, but ATA4 8.27.5.2 requires it */
+ ide_abort_command(s);
+ return true;
+ }
+
+ if (!s->blk) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ s->req_nb_sectors = 1;
+ ide_sector_read(s);
+
+ return false;
+}
+
+static bool cmd_write_pio(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_WRITE_EXT);
+
+ if (!s->blk) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+
+ s->req_nb_sectors = 1;
+ s->status = SEEK_STAT | READY_STAT;
+ ide_transfer_start(s, s->io_buffer, 512, ide_sector_write);
+
+ s->media_changed = 1;
+
+ return false;
+}
+
+static bool cmd_read_dma(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_READDMA_EXT);
+
+ if (!s->blk) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ ide_sector_start_dma(s, IDE_DMA_READ);
+
+ return false;
+}
+
+static bool cmd_write_dma(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_WRITEDMA_EXT);
+
+ if (!s->blk) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ ide_sector_start_dma(s, IDE_DMA_WRITE);
+
+ s->media_changed = 1;
+
+ return false;
+}
+
+static bool cmd_flush_cache(IDEState *s, uint8_t cmd)
+{
+ ide_flush_cache(s);
+ return false;
+}
+
+static bool cmd_seek(IDEState *s, uint8_t cmd)
+{
+ /* XXX: Check that seek is within bounds */
+ return true;
+}
+
+static bool cmd_read_native_max(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_READ_NATIVE_MAX_EXT);
+
+ /* Refuse if no sectors are addressable (e.g. medium not inserted) */
+ if (s->nb_sectors == 0) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ ide_set_sector(s, s->nb_sectors - 1);
+
+ return true;
+}
+
+static bool cmd_check_power_mode(IDEState *s, uint8_t cmd)
+{
+ s->nsector = 0xff; /* device active or idle */
+ return true;
+}
+
+static bool cmd_set_features(IDEState *s, uint8_t cmd)
+{
+ uint16_t *identify_data;
+
+ if (!s->blk) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ /* XXX: valid for CDROM ? */
+ switch (s->feature) {
+ case 0x02: /* write cache enable */
+ blk_set_enable_write_cache(s->blk, true);
+ identify_data = (uint16_t *)s->identify_data;
+ put_le16(identify_data + 85, (1 << 14) | (1 << 5) | 1);
+ return true;
+ case 0x82: /* write cache disable */
+ blk_set_enable_write_cache(s->blk, false);
+ identify_data = (uint16_t *)s->identify_data;
+ put_le16(identify_data + 85, (1 << 14) | 1);
+ ide_flush_cache(s);
+ return false;
+ case 0xcc: /* reverting to power-on defaults enable */
+ case 0x66: /* reverting to power-on defaults disable */
+ case 0xaa: /* read look-ahead enable */
+ case 0x55: /* read look-ahead disable */
+ case 0x05: /* set advanced power management mode */
+ case 0x85: /* disable advanced power management mode */
+ case 0x69: /* NOP */
+ case 0x67: /* NOP */
+ case 0x96: /* NOP */
+ case 0x9a: /* NOP */
+ case 0x42: /* enable Automatic Acoustic Mode */
+ case 0xc2: /* disable Automatic Acoustic Mode */
+ return true;
+ case 0x03: /* set transfer mode */
+ {
+ uint8_t val = s->nsector & 0x07;
+ identify_data = (uint16_t *)s->identify_data;
+
+ switch (s->nsector >> 3) {
+ case 0x00: /* pio default */
+ case 0x01: /* pio mode */
+ put_le16(identify_data + 62, 0x07);
+ put_le16(identify_data + 63, 0x07);
+ put_le16(identify_data + 88, 0x3f);
+ break;
+ case 0x02: /* sigle word dma mode*/
+ put_le16(identify_data + 62, 0x07 | (1 << (val + 8)));
+ put_le16(identify_data + 63, 0x07);
+ put_le16(identify_data + 88, 0x3f);
+ break;
+ case 0x04: /* mdma mode */
+ put_le16(identify_data + 62, 0x07);
+ put_le16(identify_data + 63, 0x07 | (1 << (val + 8)));
+ put_le16(identify_data + 88, 0x3f);
+ break;
+ case 0x08: /* udma mode */
+ put_le16(identify_data + 62, 0x07);
+ put_le16(identify_data + 63, 0x07);
+ put_le16(identify_data + 88, 0x3f | (1 << (val + 8)));
+ break;
+ default:
+ goto abort_cmd;
+ }
+ return true;
+ }
+ }
+
+abort_cmd:
+ ide_abort_command(s);
+ return true;
+}
+
+
+/*** ATAPI commands ***/
+
+static bool cmd_identify_packet(IDEState *s, uint8_t cmd)
+{
+ ide_atapi_identify(s);
+ s->status = READY_STAT | SEEK_STAT;
+ ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop);
+ ide_set_irq(s->bus);
+ return false;
+}
+
+static bool cmd_exec_dev_diagnostic(IDEState *s, uint8_t cmd)
+{
+ ide_set_signature(s);
+
+ if (s->drive_kind == IDE_CD) {
+ s->status = 0; /* ATAPI spec (v6) section 9.10 defines packet
+ * devices to return a clear status register
+ * with READY_STAT *not* set. */
+ s->error = 0x01;
+ } else {
+ s->status = READY_STAT | SEEK_STAT;
+ /* The bits of the error register are not as usual for this command!
+ * They are part of the regular output (this is why ERR_STAT isn't set)
+ * Device 0 passed, Device 1 passed or not present. */
+ s->error = 0x01;
+ ide_set_irq(s->bus);
+ }
+
+ return false;
+}
+
+static bool cmd_device_reset(IDEState *s, uint8_t cmd)
+{
+ ide_set_signature(s);
+ s->status = 0x00; /* NOTE: READY is _not_ set */
+ s->error = 0x01;
+
+ return false;
+}
+
+static bool cmd_packet(IDEState *s, uint8_t cmd)
+{
+ /* overlapping commands not supported */
+ if (s->feature & 0x02) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ s->status = READY_STAT | SEEK_STAT;
+ s->atapi_dma = s->feature & 1;
+ s->nsector = 1;
+ ide_transfer_start(s, s->io_buffer, ATAPI_PACKET_SIZE,
+ ide_atapi_cmd);
+ return false;
+}
+
+
+/*** CF-ATA commands ***/
+
+static bool cmd_cfa_req_ext_error_code(IDEState *s, uint8_t cmd)
+{
+ s->error = 0x09; /* miscellaneous error */
+ s->status = READY_STAT | SEEK_STAT;
+ ide_set_irq(s->bus);
+
+ return false;
+}
+
+static bool cmd_cfa_erase_sectors(IDEState *s, uint8_t cmd)
+{
+ /* WIN_SECURITY_FREEZE_LOCK has the same ID as CFA_WEAR_LEVEL and is
+ * required for Windows 8 to work with AHCI */
+
+ if (cmd == CFA_WEAR_LEVEL) {
+ s->nsector = 0;
+ }
+
+ if (cmd == CFA_ERASE_SECTORS) {
+ s->media_changed = 1;
+ }
+
+ return true;
+}
+
+static bool cmd_cfa_translate_sector(IDEState *s, uint8_t cmd)
+{
+ s->status = READY_STAT | SEEK_STAT;
+
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0x00] = s->hcyl; /* Cyl MSB */
+ s->io_buffer[0x01] = s->lcyl; /* Cyl LSB */
+ s->io_buffer[0x02] = s->select; /* Head */
+ s->io_buffer[0x03] = s->sector; /* Sector */
+ s->io_buffer[0x04] = ide_get_sector(s) >> 16; /* LBA MSB */
+ s->io_buffer[0x05] = ide_get_sector(s) >> 8; /* LBA */
+ s->io_buffer[0x06] = ide_get_sector(s) >> 0; /* LBA LSB */
+ s->io_buffer[0x13] = 0x00; /* Erase flag */
+ s->io_buffer[0x18] = 0x00; /* Hot count */
+ s->io_buffer[0x19] = 0x00; /* Hot count */
+ s->io_buffer[0x1a] = 0x01; /* Hot count */
+
+ ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+ ide_set_irq(s->bus);
+
+ return false;
+}
+
+static bool cmd_cfa_access_metadata_storage(IDEState *s, uint8_t cmd)
+{
+ switch (s->feature) {
+ case 0x02: /* Inquiry Metadata Storage */
+ ide_cfata_metadata_inquiry(s);
+ break;
+ case 0x03: /* Read Metadata Storage */
+ ide_cfata_metadata_read(s);
+ break;
+ case 0x04: /* Write Metadata Storage */
+ ide_cfata_metadata_write(s);
+ break;
+ default:
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+ s->status = 0x00; /* NOTE: READY is _not_ set */
+ ide_set_irq(s->bus);
+
+ return false;
+}
+
+static bool cmd_ibm_sense_condition(IDEState *s, uint8_t cmd)
+{
+ switch (s->feature) {
+ case 0x01: /* sense temperature in device */
+ s->nsector = 0x50; /* +20 C */
+ break;
+ default:
+ ide_abort_command(s);
+ return true;
+ }
+
+ return true;
+}
+
+
+/*** SMART commands ***/
+
+static bool cmd_smart(IDEState *s, uint8_t cmd)
+{
+ int n;
+
+ if (s->hcyl != 0xc2 || s->lcyl != 0x4f) {
+ goto abort_cmd;
+ }
+
+ if (!s->smart_enabled && s->feature != SMART_ENABLE) {
+ goto abort_cmd;
+ }
+
+ switch (s->feature) {
+ case SMART_DISABLE:
+ s->smart_enabled = 0;
+ return true;
+
+ case SMART_ENABLE:
+ s->smart_enabled = 1;
+ return true;
+
+ case SMART_ATTR_AUTOSAVE:
+ switch (s->sector) {
+ case 0x00:
+ s->smart_autosave = 0;
+ break;
+ case 0xf1:
+ s->smart_autosave = 1;
+ break;
+ default:
+ goto abort_cmd;
+ }
+ return true;
+
+ case SMART_STATUS:
+ if (!s->smart_errors) {
+ s->hcyl = 0xc2;
+ s->lcyl = 0x4f;
+ } else {
+ s->hcyl = 0x2c;
+ s->lcyl = 0xf4;
+ }
+ return true;
+
+ case SMART_READ_THRESH:
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0] = 0x01; /* smart struct version */
+
+ for (n = 0; n < ARRAY_SIZE(smart_attributes); n++) {
+ s->io_buffer[2 + 0 + (n * 12)] = smart_attributes[n][0];
+ s->io_buffer[2 + 1 + (n * 12)] = smart_attributes[n][11];
+ }
+
+ /* checksum */
+ for (n = 0; n < 511; n++) {
+ s->io_buffer[511] += s->io_buffer[n];
+ }
+ s->io_buffer[511] = 0x100 - s->io_buffer[511];
+
+ s->status = READY_STAT | SEEK_STAT;
+ ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+ ide_set_irq(s->bus);
+ return false;
+
+ case SMART_READ_DATA:
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0] = 0x01; /* smart struct version */
+
+ for (n = 0; n < ARRAY_SIZE(smart_attributes); n++) {
+ int i;
+ for (i = 0; i < 11; i++) {
+ s->io_buffer[2 + i + (n * 12)] = smart_attributes[n][i];
+ }
+ }
+
+ s->io_buffer[362] = 0x02 | (s->smart_autosave ? 0x80 : 0x00);
+ if (s->smart_selftest_count == 0) {
+ s->io_buffer[363] = 0;
+ } else {
+ s->io_buffer[363] =
+ s->smart_selftest_data[3 +
+ (s->smart_selftest_count - 1) *
+ 24];
+ }
+ s->io_buffer[364] = 0x20;
+ s->io_buffer[365] = 0x01;
+ /* offline data collection capacity: execute + self-test*/
+ s->io_buffer[367] = (1 << 4 | 1 << 3 | 1);
+ s->io_buffer[368] = 0x03; /* smart capability (1) */
+ s->io_buffer[369] = 0x00; /* smart capability (2) */
+ s->io_buffer[370] = 0x01; /* error logging supported */
+ s->io_buffer[372] = 0x02; /* minutes for poll short test */
+ s->io_buffer[373] = 0x36; /* minutes for poll ext test */
+ s->io_buffer[374] = 0x01; /* minutes for poll conveyance */
+
+ for (n = 0; n < 511; n++) {
+ s->io_buffer[511] += s->io_buffer[n];
+ }
+ s->io_buffer[511] = 0x100 - s->io_buffer[511];
+
+ s->status = READY_STAT | SEEK_STAT;
+ ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+ ide_set_irq(s->bus);
+ return false;
+
+ case SMART_READ_LOG:
+ switch (s->sector) {
+ case 0x01: /* summary smart error log */
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0] = 0x01;
+ s->io_buffer[1] = 0x00; /* no error entries */
+ s->io_buffer[452] = s->smart_errors & 0xff;
+ s->io_buffer[453] = (s->smart_errors & 0xff00) >> 8;
+
+ for (n = 0; n < 511; n++) {
+ s->io_buffer[511] += s->io_buffer[n];
+ }
+ s->io_buffer[511] = 0x100 - s->io_buffer[511];
+ break;
+ case 0x06: /* smart self test log */
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0] = 0x01;
+ if (s->smart_selftest_count == 0) {
+ s->io_buffer[508] = 0;
+ } else {
+ s->io_buffer[508] = s->smart_selftest_count;
+ for (n = 2; n < 506; n++) {
+ s->io_buffer[n] = s->smart_selftest_data[n];
+ }
+ }
+
+ for (n = 0; n < 511; n++) {
+ s->io_buffer[511] += s->io_buffer[n];
+ }
+ s->io_buffer[511] = 0x100 - s->io_buffer[511];
+ break;
+ default:
+ goto abort_cmd;
+ }
+ s->status = READY_STAT | SEEK_STAT;
+ ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+ ide_set_irq(s->bus);
+ return false;
+
+ case SMART_EXECUTE_OFFLINE:
+ switch (s->sector) {
+ case 0: /* off-line routine */
+ case 1: /* short self test */
+ case 2: /* extended self test */
+ s->smart_selftest_count++;
+ if (s->smart_selftest_count > 21) {
+ s->smart_selftest_count = 1;
+ }
+ n = 2 + (s->smart_selftest_count - 1) * 24;
+ s->smart_selftest_data[n] = s->sector;
+ s->smart_selftest_data[n + 1] = 0x00; /* OK and finished */
+ s->smart_selftest_data[n + 2] = 0x34; /* hour count lsb */
+ s->smart_selftest_data[n + 3] = 0x12; /* hour count msb */
+ break;
+ default:
+ goto abort_cmd;
+ }
+ return true;
+ }
+
+abort_cmd:
+ ide_abort_command(s);
+ return true;
+}
+
+#define HD_OK (1u << IDE_HD)
+#define CD_OK (1u << IDE_CD)
+#define CFA_OK (1u << IDE_CFATA)
+#define HD_CFA_OK (HD_OK | CFA_OK)
+#define ALL_OK (HD_OK | CD_OK | CFA_OK)
+
+/* Set the Disk Seek Completed status bit during completion */
+#define SET_DSC (1u << 8)
+
+/* See ACS-2 T13/2015-D Table B.2 Command codes */
+static const struct {
+ /* Returns true if the completion code should be run */
+ bool (*handler)(IDEState *s, uint8_t cmd);
+ int flags;
+} ide_cmd_table[0x100] = {
+ /* NOP not implemented, mandatory for CD */
+ [CFA_REQ_EXT_ERROR_CODE] = { cmd_cfa_req_ext_error_code, CFA_OK },
+ [WIN_DSM] = { cmd_data_set_management, HD_CFA_OK },
+ [WIN_DEVICE_RESET] = { cmd_device_reset, CD_OK },
+ [WIN_RECAL] = { cmd_nop, HD_CFA_OK | SET_DSC},
+ [WIN_READ] = { cmd_read_pio, ALL_OK },
+ [WIN_READ_ONCE] = { cmd_read_pio, HD_CFA_OK },
+ [WIN_READ_EXT] = { cmd_read_pio, HD_CFA_OK },
+ [WIN_READDMA_EXT] = { cmd_read_dma, HD_CFA_OK },
+ [WIN_READ_NATIVE_MAX_EXT] = { cmd_read_native_max, HD_CFA_OK | SET_DSC },
+ [WIN_MULTREAD_EXT] = { cmd_read_multiple, HD_CFA_OK },
+ [WIN_WRITE] = { cmd_write_pio, HD_CFA_OK },
+ [WIN_WRITE_ONCE] = { cmd_write_pio, HD_CFA_OK },
+ [WIN_WRITE_EXT] = { cmd_write_pio, HD_CFA_OK },
+ [WIN_WRITEDMA_EXT] = { cmd_write_dma, HD_CFA_OK },
+ [CFA_WRITE_SECT_WO_ERASE] = { cmd_write_pio, CFA_OK },
+ [WIN_MULTWRITE_EXT] = { cmd_write_multiple, HD_CFA_OK },
+ [WIN_WRITE_VERIFY] = { cmd_write_pio, HD_CFA_OK },
+ [WIN_VERIFY] = { cmd_verify, HD_CFA_OK | SET_DSC },
+ [WIN_VERIFY_ONCE] = { cmd_verify, HD_CFA_OK | SET_DSC },
+ [WIN_VERIFY_EXT] = { cmd_verify, HD_CFA_OK | SET_DSC },
+ [WIN_SEEK] = { cmd_seek, HD_CFA_OK | SET_DSC },
+ [CFA_TRANSLATE_SECTOR] = { cmd_cfa_translate_sector, CFA_OK },
+ [WIN_DIAGNOSE] = { cmd_exec_dev_diagnostic, ALL_OK },
+ [WIN_SPECIFY] = { cmd_nop, HD_CFA_OK | SET_DSC },
+ [WIN_STANDBYNOW2] = { cmd_nop, HD_CFA_OK },
+ [WIN_IDLEIMMEDIATE2] = { cmd_nop, HD_CFA_OK },
+ [WIN_STANDBY2] = { cmd_nop, HD_CFA_OK },
+ [WIN_SETIDLE2] = { cmd_nop, HD_CFA_OK },
+ [WIN_CHECKPOWERMODE2] = { cmd_check_power_mode, HD_CFA_OK | SET_DSC },
+ [WIN_SLEEPNOW2] = { cmd_nop, HD_CFA_OK },
+ [WIN_PACKETCMD] = { cmd_packet, CD_OK },
+ [WIN_PIDENTIFY] = { cmd_identify_packet, CD_OK },
+ [WIN_SMART] = { cmd_smart, HD_CFA_OK | SET_DSC },
+ [CFA_ACCESS_METADATA_STORAGE] = { cmd_cfa_access_metadata_storage, CFA_OK },
+ [CFA_ERASE_SECTORS] = { cmd_cfa_erase_sectors, CFA_OK | SET_DSC },
+ [WIN_MULTREAD] = { cmd_read_multiple, HD_CFA_OK },
+ [WIN_MULTWRITE] = { cmd_write_multiple, HD_CFA_OK },
+ [WIN_SETMULT] = { cmd_set_multiple_mode, HD_CFA_OK | SET_DSC },
+ [WIN_READDMA] = { cmd_read_dma, HD_CFA_OK },
+ [WIN_READDMA_ONCE] = { cmd_read_dma, HD_CFA_OK },
+ [WIN_WRITEDMA] = { cmd_write_dma, HD_CFA_OK },
+ [WIN_WRITEDMA_ONCE] = { cmd_write_dma, HD_CFA_OK },
+ [CFA_WRITE_MULTI_WO_ERASE] = { cmd_write_multiple, CFA_OK },
+ [WIN_STANDBYNOW1] = { cmd_nop, HD_CFA_OK },
+ [WIN_IDLEIMMEDIATE] = { cmd_nop, HD_CFA_OK },
+ [WIN_STANDBY] = { cmd_nop, HD_CFA_OK },
+ [WIN_SETIDLE1] = { cmd_nop, HD_CFA_OK },
+ [WIN_CHECKPOWERMODE1] = { cmd_check_power_mode, HD_CFA_OK | SET_DSC },
+ [WIN_SLEEPNOW1] = { cmd_nop, HD_CFA_OK },
+ [WIN_FLUSH_CACHE] = { cmd_flush_cache, ALL_OK },
+ [WIN_FLUSH_CACHE_EXT] = { cmd_flush_cache, HD_CFA_OK },
+ [WIN_IDENTIFY] = { cmd_identify, ALL_OK },
+ [WIN_SETFEATURES] = { cmd_set_features, ALL_OK | SET_DSC },
+ [IBM_SENSE_CONDITION] = { cmd_ibm_sense_condition, CFA_OK | SET_DSC },
+ [CFA_WEAR_LEVEL] = { cmd_cfa_erase_sectors, HD_CFA_OK | SET_DSC },
+ [WIN_READ_NATIVE_MAX] = { cmd_read_native_max, HD_CFA_OK | SET_DSC },
+};
+
+static bool ide_cmd_permitted(IDEState *s, uint32_t cmd)
+{
+ return cmd < ARRAY_SIZE(ide_cmd_table)
+ && (ide_cmd_table[cmd].flags & (1u << s->drive_kind));
+}
+
+void ide_exec_cmd(IDEBus *bus, uint32_t val)
+{
+ IDEState *s;
+ bool complete;
+
+#if defined(DEBUG_IDE)
+ printf("ide: CMD=%02x\n", val);
+#endif
+ s = idebus_active_if(bus);
+ /* ignore commands to non existent slave */
+ if (s != bus->ifs && !s->blk) {
+ return;
+ }
+
+ /* Only DEVICE RESET is allowed while BSY or/and DRQ are set */
+ if ((s->status & (BUSY_STAT|DRQ_STAT)) && val != WIN_DEVICE_RESET)
+ return;
+
+ if (!ide_cmd_permitted(s, val)) {
+ ide_abort_command(s);
+ ide_set_irq(s->bus);
+ return;
+ }
+
+ s->status = READY_STAT | BUSY_STAT;
+ s->error = 0;
+ s->io_buffer_offset = 0;
+
+ complete = ide_cmd_table[val].handler(s, val);
+ if (complete) {
+ s->status &= ~BUSY_STAT;
+ assert(!!s->error == !!(s->status & ERR_STAT));
+
+ if ((ide_cmd_table[val].flags & SET_DSC) && !s->error) {
+ s->status |= SEEK_STAT;
+ }
+
+ ide_cmd_done(s);
+ ide_set_irq(s->bus);
+ }
+}
+
+uint32_t ide_ioport_read(void *opaque, uint32_t addr1)
+{
+ IDEBus *bus = opaque;
+ IDEState *s = idebus_active_if(bus);
+ uint32_t addr;
+ int ret, hob;
+
+ addr = addr1 & 7;
+ /* FIXME: HOB readback uses bit 7, but it's always set right now */
+ //hob = s->select & (1 << 7);
+ hob = 0;
+ switch(addr) {
+ case 0:
+ ret = 0xff;
+ break;
+ case 1:
+ if ((!bus->ifs[0].blk && !bus->ifs[1].blk) ||
+ (s != bus->ifs && !s->blk)) {
+ ret = 0;
+ } else if (!hob) {
+ ret = s->error;
+ } else {
+ ret = s->hob_feature;
+ }
+ break;
+ case 2:
+ if (!bus->ifs[0].blk && !bus->ifs[1].blk) {
+ ret = 0;
+ } else if (!hob) {
+ ret = s->nsector & 0xff;
+ } else {
+ ret = s->hob_nsector;
+ }
+ break;
+ case 3:
+ if (!bus->ifs[0].blk && !bus->ifs[1].blk) {
+ ret = 0;
+ } else if (!hob) {
+ ret = s->sector;
+ } else {
+ ret = s->hob_sector;
+ }
+ break;
+ case 4:
+ if (!bus->ifs[0].blk && !bus->ifs[1].blk) {
+ ret = 0;
+ } else if (!hob) {
+ ret = s->lcyl;
+ } else {
+ ret = s->hob_lcyl;
+ }
+ break;
+ case 5:
+ if (!bus->ifs[0].blk && !bus->ifs[1].blk) {
+ ret = 0;
+ } else if (!hob) {
+ ret = s->hcyl;
+ } else {
+ ret = s->hob_hcyl;
+ }
+ break;
+ case 6:
+ if (!bus->ifs[0].blk && !bus->ifs[1].blk) {
+ ret = 0;
+ } else {
+ ret = s->select;
+ }
+ break;
+ default:
+ case 7:
+ if ((!bus->ifs[0].blk && !bus->ifs[1].blk) ||
+ (s != bus->ifs && !s->blk)) {
+ ret = 0;
+ } else {
+ ret = s->status;
+ }
+ qemu_irq_lower(bus->irq);
+ break;
+ }
+#ifdef DEBUG_IDE
+ printf("ide: read addr=0x%x val=%02x\n", addr1, ret);
+#endif
+ return ret;
+}
+
+uint32_t ide_status_read(void *opaque, uint32_t addr)
+{
+ IDEBus *bus = opaque;
+ IDEState *s = idebus_active_if(bus);
+ int ret;
+
+ if ((!bus->ifs[0].blk && !bus->ifs[1].blk) ||
+ (s != bus->ifs && !s->blk)) {
+ ret = 0;
+ } else {
+ ret = s->status;
+ }
+#ifdef DEBUG_IDE
+ printf("ide: read status addr=0x%x val=%02x\n", addr, ret);
+#endif
+ return ret;
+}
+
+void ide_cmd_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ IDEBus *bus = opaque;
+ IDEState *s;
+ int i;
+
+#ifdef DEBUG_IDE
+ printf("ide: write control addr=0x%x val=%02x\n", addr, val);
+#endif
+ /* common for both drives */
+ if (!(bus->cmd & IDE_CMD_RESET) &&
+ (val & IDE_CMD_RESET)) {
+ /* reset low to high */
+ for(i = 0;i < 2; i++) {
+ s = &bus->ifs[i];
+ s->status = BUSY_STAT | SEEK_STAT;
+ s->error = 0x01;
+ }
+ } else if ((bus->cmd & IDE_CMD_RESET) &&
+ !(val & IDE_CMD_RESET)) {
+ /* high to low */
+ for(i = 0;i < 2; i++) {
+ s = &bus->ifs[i];
+ if (s->drive_kind == IDE_CD)
+ s->status = 0x00; /* NOTE: READY is _not_ set */
+ else
+ s->status = READY_STAT | SEEK_STAT;
+ ide_set_signature(s);
+ }
+ }
+
+ bus->cmd = val;
+}
+
+/*
+ * Returns true if the running PIO transfer is a PIO out (i.e. data is
+ * transferred from the device to the guest), false if it's a PIO in
+ */
+static bool ide_is_pio_out(IDEState *s)
+{
+ if (s->end_transfer_func == ide_sector_write ||
+ s->end_transfer_func == ide_atapi_cmd) {
+ return false;
+ } else if (s->end_transfer_func == ide_sector_read ||
+ s->end_transfer_func == ide_transfer_stop ||
+ s->end_transfer_func == ide_atapi_cmd_reply_end ||
+ s->end_transfer_func == ide_dummy_transfer_stop) {
+ return true;
+ }
+
+ abort();
+}
+
+void ide_data_writew(void *opaque, uint32_t addr, uint32_t val)
+{
+ IDEBus *bus = opaque;
+ IDEState *s = idebus_active_if(bus);
+ uint8_t *p;
+
+ /* PIO data access allowed only when DRQ bit is set. The result of a write
+ * during PIO out is indeterminate, just ignore it. */
+ if (!(s->status & DRQ_STAT) || ide_is_pio_out(s)) {
+ return;
+ }
+
+ p = s->data_ptr;
+ if (p + 2 > s->data_end) {
+ return;
+ }
+
+ *(uint16_t *)p = le16_to_cpu(val);
+ p += 2;
+ s->data_ptr = p;
+ if (p >= s->data_end) {
+ s->status &= ~DRQ_STAT;
+ s->end_transfer_func(s);
+ }
+}
+
+uint32_t ide_data_readw(void *opaque, uint32_t addr)
+{
+ IDEBus *bus = opaque;
+ IDEState *s = idebus_active_if(bus);
+ uint8_t *p;
+ int ret;
+
+ /* PIO data access allowed only when DRQ bit is set. The result of a read
+ * during PIO in is indeterminate, return 0 and don't move forward. */
+ if (!(s->status & DRQ_STAT) || !ide_is_pio_out(s)) {
+ return 0;
+ }
+
+ p = s->data_ptr;
+ if (p + 2 > s->data_end) {
+ return 0;
+ }
+
+ ret = cpu_to_le16(*(uint16_t *)p);
+ p += 2;
+ s->data_ptr = p;
+ if (p >= s->data_end) {
+ s->status &= ~DRQ_STAT;
+ s->end_transfer_func(s);
+ }
+ return ret;
+}
+
+void ide_data_writel(void *opaque, uint32_t addr, uint32_t val)
+{
+ IDEBus *bus = opaque;
+ IDEState *s = idebus_active_if(bus);
+ uint8_t *p;
+
+ /* PIO data access allowed only when DRQ bit is set. The result of a write
+ * during PIO out is indeterminate, just ignore it. */
+ if (!(s->status & DRQ_STAT) || ide_is_pio_out(s)) {
+ return;
+ }
+
+ p = s->data_ptr;
+ if (p + 4 > s->data_end) {
+ return;
+ }
+
+ *(uint32_t *)p = le32_to_cpu(val);
+ p += 4;
+ s->data_ptr = p;
+ if (p >= s->data_end) {
+ s->status &= ~DRQ_STAT;
+ s->end_transfer_func(s);
+ }
+}
+
+uint32_t ide_data_readl(void *opaque, uint32_t addr)
+{
+ IDEBus *bus = opaque;
+ IDEState *s = idebus_active_if(bus);
+ uint8_t *p;
+ int ret;
+
+ /* PIO data access allowed only when DRQ bit is set. The result of a read
+ * during PIO in is indeterminate, return 0 and don't move forward. */
+ if (!(s->status & DRQ_STAT) || !ide_is_pio_out(s)) {
+ return 0;
+ }
+
+ p = s->data_ptr;
+ if (p + 4 > s->data_end) {
+ return 0;
+ }
+
+ ret = cpu_to_le32(*(uint32_t *)p);
+ p += 4;
+ s->data_ptr = p;
+ if (p >= s->data_end) {
+ s->status &= ~DRQ_STAT;
+ s->end_transfer_func(s);
+ }
+ return ret;
+}
+
+static void ide_dummy_transfer_stop(IDEState *s)
+{
+ s->data_ptr = s->io_buffer;
+ s->data_end = s->io_buffer;
+ s->io_buffer[0] = 0xff;
+ s->io_buffer[1] = 0xff;
+ s->io_buffer[2] = 0xff;
+ s->io_buffer[3] = 0xff;
+}
+
+static void ide_reset(IDEState *s)
+{
+#ifdef DEBUG_IDE
+ printf("ide: reset\n");
+#endif
+
+ if (s->pio_aiocb) {
+ blk_aio_cancel(s->pio_aiocb);
+ s->pio_aiocb = NULL;
+ }
+
+ if (s->drive_kind == IDE_CFATA)
+ s->mult_sectors = 0;
+ else
+ s->mult_sectors = MAX_MULT_SECTORS;
+ /* ide regs */
+ s->feature = 0;
+ s->error = 0;
+ s->nsector = 0;
+ s->sector = 0;
+ s->lcyl = 0;
+ s->hcyl = 0;
+
+ /* lba48 */
+ s->hob_feature = 0;
+ s->hob_sector = 0;
+ s->hob_nsector = 0;
+ s->hob_lcyl = 0;
+ s->hob_hcyl = 0;
+
+ s->select = 0xa0;
+ s->status = READY_STAT | SEEK_STAT;
+
+ s->lba48 = 0;
+
+ /* ATAPI specific */
+ s->sense_key = 0;
+ s->asc = 0;
+ s->cdrom_changed = 0;
+ s->packet_transfer_size = 0;
+ s->elementary_transfer_size = 0;
+ s->io_buffer_index = 0;
+ s->cd_sector_size = 0;
+ s->atapi_dma = 0;
+ s->tray_locked = 0;
+ s->tray_open = 0;
+ /* ATA DMA state */
+ s->io_buffer_size = 0;
+ s->req_nb_sectors = 0;
+
+ ide_set_signature(s);
+ /* init the transfer handler so that 0xffff is returned on data
+ accesses */
+ s->end_transfer_func = ide_dummy_transfer_stop;
+ ide_dummy_transfer_stop(s);
+ s->media_changed = 0;
+}
+
+void ide_bus_reset(IDEBus *bus)
+{
+ bus->unit = 0;
+ bus->cmd = 0;
+ ide_reset(&bus->ifs[0]);
+ ide_reset(&bus->ifs[1]);
+ ide_clear_hob(bus);
+
+ /* pending async DMA */
+ if (bus->dma->aiocb) {
+#ifdef DEBUG_AIO
+ printf("aio_cancel\n");
+#endif
+ blk_aio_cancel(bus->dma->aiocb);
+ bus->dma->aiocb = NULL;
+ }
+
+ /* reset dma provider too */
+ if (bus->dma->ops->reset) {
+ bus->dma->ops->reset(bus->dma);
+ }
+}
+
+static bool ide_cd_is_tray_open(void *opaque)
+{
+ return ((IDEState *)opaque)->tray_open;
+}
+
+static bool ide_cd_is_medium_locked(void *opaque)
+{
+ return ((IDEState *)opaque)->tray_locked;
+}
+
+static void ide_resize_cb(void *opaque)
+{
+ IDEState *s = opaque;
+ uint64_t nb_sectors;
+
+ if (!s->identify_set) {
+ return;
+ }
+
+ blk_get_geometry(s->blk, &nb_sectors);
+ s->nb_sectors = nb_sectors;
+
+ /* Update the identify data buffer. */
+ if (s->drive_kind == IDE_CFATA) {
+ ide_cfata_identify_size(s);
+ } else {
+ /* IDE_CD uses a different set of callbacks entirely. */
+ assert(s->drive_kind != IDE_CD);
+ ide_identify_size(s);
+ }
+}
+
+static const BlockDevOps ide_cd_block_ops = {
+ .change_media_cb = ide_cd_change_cb,
+ .eject_request_cb = ide_cd_eject_request_cb,
+ .is_tray_open = ide_cd_is_tray_open,
+ .is_medium_locked = ide_cd_is_medium_locked,
+};
+
+static const BlockDevOps ide_hd_block_ops = {
+ .resize_cb = ide_resize_cb,
+};
+
+int ide_init_drive(IDEState *s, BlockBackend *blk, IDEDriveKind kind,
+ const char *version, const char *serial, const char *model,
+ uint64_t wwn,
+ uint32_t cylinders, uint32_t heads, uint32_t secs,
+ int chs_trans)
+{
+ uint64_t nb_sectors;
+
+ s->blk = blk;
+ s->drive_kind = kind;
+
+ blk_get_geometry(blk, &nb_sectors);
+ s->cylinders = cylinders;
+ s->heads = heads;
+ s->sectors = secs;
+ s->chs_trans = chs_trans;
+ s->nb_sectors = nb_sectors;
+ s->wwn = wwn;
+ /* The SMART values should be preserved across power cycles
+ but they aren't. */
+ s->smart_enabled = 1;
+ s->smart_autosave = 1;
+ s->smart_errors = 0;
+ s->smart_selftest_count = 0;
+ if (kind == IDE_CD) {
+ blk_set_dev_ops(blk, &ide_cd_block_ops, s);
+ blk_set_guest_block_size(blk, 2048);
+ } else {
+ if (!blk_is_inserted(s->blk)) {
+ error_report("Device needs media, but drive is empty");
+ return -1;
+ }
+ if (blk_is_read_only(blk)) {
+ error_report("Can't use a read-only drive");
+ return -1;
+ }
+ blk_set_dev_ops(blk, &ide_hd_block_ops, s);
+ }
+ if (serial) {
+ pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), serial);
+ } else {
+ snprintf(s->drive_serial_str, sizeof(s->drive_serial_str),
+ "QM%05d", s->drive_serial);
+ }
+ if (model) {
+ pstrcpy(s->drive_model_str, sizeof(s->drive_model_str), model);
+ } else {
+ switch (kind) {
+ case IDE_CD:
+ strcpy(s->drive_model_str, "QEMU DVD-ROM");
+ break;
+ case IDE_CFATA:
+ strcpy(s->drive_model_str, "QEMU MICRODRIVE");
+ break;
+ default:
+ strcpy(s->drive_model_str, "QEMU HARDDISK");
+ break;
+ }
+ }
+
+ if (version) {
+ pstrcpy(s->version, sizeof(s->version), version);
+ } else {
+ pstrcpy(s->version, sizeof(s->version), qemu_get_version());
+ }
+
+ ide_reset(s);
+ blk_iostatus_enable(blk);
+ return 0;
+}
+
+static void ide_init1(IDEBus *bus, int unit)
+{
+ static int drive_serial = 1;
+ IDEState *s = &bus->ifs[unit];
+
+ s->bus = bus;
+ s->unit = unit;
+ s->drive_serial = drive_serial++;
+ /* we need at least 2k alignment for accessing CDROMs using O_DIRECT */
+ s->io_buffer_total_len = IDE_DMA_BUF_SECTORS*512 + 4;
+ s->io_buffer = qemu_memalign(2048, s->io_buffer_total_len);
+ memset(s->io_buffer, 0, s->io_buffer_total_len);
+
+ s->smart_selftest_data = blk_blockalign(s->blk, 512);
+ memset(s->smart_selftest_data, 0, 512);
+
+ s->sector_write_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ ide_sector_write_timer_cb, s);
+}
+
+static int ide_nop_int(IDEDMA *dma, int x)
+{
+ return 0;
+}
+
+static void ide_nop(IDEDMA *dma)
+{
+}
+
+static int32_t ide_nop_int32(IDEDMA *dma, int32_t l)
+{
+ return 0;
+}
+
+static const IDEDMAOps ide_dma_nop_ops = {
+ .prepare_buf = ide_nop_int32,
+ .restart_dma = ide_nop,
+ .rw_buf = ide_nop_int,
+};
+
+static void ide_restart_dma(IDEState *s, enum ide_dma_cmd dma_cmd)
+{
+ s->unit = s->bus->retry_unit;
+ ide_set_sector(s, s->bus->retry_sector_num);
+ s->nsector = s->bus->retry_nsector;
+ s->bus->dma->ops->restart_dma(s->bus->dma);
+ s->io_buffer_size = 0;
+ s->dma_cmd = dma_cmd;
+ ide_start_dma(s, ide_dma_cb);
+}
+
+static void ide_restart_bh(void *opaque)
+{
+ IDEBus *bus = opaque;
+ IDEState *s;
+ bool is_read;
+ int error_status;
+
+ qemu_bh_delete(bus->bh);
+ bus->bh = NULL;
+
+ error_status = bus->error_status;
+ if (bus->error_status == 0) {
+ return;
+ }
+
+ s = idebus_active_if(bus);
+ is_read = (bus->error_status & IDE_RETRY_READ) != 0;
+
+ /* The error status must be cleared before resubmitting the request: The
+ * request may fail again, and this case can only be distinguished if the
+ * called function can set a new error status. */
+ bus->error_status = 0;
+
+ /* The HBA has generically asked to be kicked on retry */
+ if (error_status & IDE_RETRY_HBA) {
+ if (s->bus->dma->ops->restart) {
+ s->bus->dma->ops->restart(s->bus->dma);
+ }
+ }
+
+ if (error_status & IDE_RETRY_DMA) {
+ if (error_status & IDE_RETRY_TRIM) {
+ ide_restart_dma(s, IDE_DMA_TRIM);
+ } else {
+ ide_restart_dma(s, is_read ? IDE_DMA_READ : IDE_DMA_WRITE);
+ }
+ } else if (error_status & IDE_RETRY_PIO) {
+ if (is_read) {
+ ide_sector_read(s);
+ } else {
+ ide_sector_write(s);
+ }
+ } else if (error_status & IDE_RETRY_FLUSH) {
+ ide_flush_cache(s);
+ } else {
+ /*
+ * We've not got any bits to tell us about ATAPI - but
+ * we do have the end_transfer_func that tells us what
+ * we're trying to do.
+ */
+ if (s->end_transfer_func == ide_atapi_cmd) {
+ ide_atapi_dma_restart(s);
+ }
+ }
+}
+
+static void ide_restart_cb(void *opaque, int running, RunState state)
+{
+ IDEBus *bus = opaque;
+
+ if (!running)
+ return;
+
+ if (!bus->bh) {
+ bus->bh = qemu_bh_new(ide_restart_bh, bus);
+ qemu_bh_schedule(bus->bh);
+ }
+}
+
+void ide_register_restart_cb(IDEBus *bus)
+{
+ if (bus->dma->ops->restart_dma) {
+ qemu_add_vm_change_state_handler(ide_restart_cb, bus);
+ }
+}
+
+static IDEDMA ide_dma_nop = {
+ .ops = &ide_dma_nop_ops,
+ .aiocb = NULL,
+};
+
+void ide_init2(IDEBus *bus, qemu_irq irq)
+{
+ int i;
+
+ for(i = 0; i < 2; i++) {
+ ide_init1(bus, i);
+ ide_reset(&bus->ifs[i]);
+ }
+ bus->irq = irq;
+ bus->dma = &ide_dma_nop;
+}
+
+static const MemoryRegionPortio ide_portio_list[] = {
+ { 0, 8, 1, .read = ide_ioport_read, .write = ide_ioport_write },
+ { 0, 1, 2, .read = ide_data_readw, .write = ide_data_writew },
+ { 0, 1, 4, .read = ide_data_readl, .write = ide_data_writel },
+ PORTIO_END_OF_LIST(),
+};
+
+static const MemoryRegionPortio ide_portio2_list[] = {
+ { 0, 1, 1, .read = ide_status_read, .write = ide_cmd_write },
+ PORTIO_END_OF_LIST(),
+};
+
+void ide_init_ioport(IDEBus *bus, ISADevice *dev, int iobase, int iobase2)
+{
+ /* ??? Assume only ISA and PCI configurations, and that the PCI-ISA
+ bridge has been setup properly to always register with ISA. */
+ isa_register_portio_list(dev, iobase, ide_portio_list, bus, "ide");
+
+ if (iobase2) {
+ isa_register_portio_list(dev, iobase2, ide_portio2_list, bus, "ide");
+ }
+}
+
+static bool is_identify_set(void *opaque, int version_id)
+{
+ IDEState *s = opaque;
+
+ return s->identify_set != 0;
+}
+
+static EndTransferFunc* transfer_end_table[] = {
+ ide_sector_read,
+ ide_sector_write,
+ ide_transfer_stop,
+ ide_atapi_cmd_reply_end,
+ ide_atapi_cmd,
+ ide_dummy_transfer_stop,
+};
+
+static int transfer_end_table_idx(EndTransferFunc *fn)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(transfer_end_table); i++)
+ if (transfer_end_table[i] == fn)
+ return i;
+
+ return -1;
+}
+
+static int ide_drive_post_load(void *opaque, int version_id)
+{
+ IDEState *s = opaque;
+
+ if (s->blk && s->identify_set) {
+ blk_set_enable_write_cache(s->blk, !!(s->identify_data[85] & (1 << 5)));
+ }
+ return 0;
+}
+
+static int ide_drive_pio_post_load(void *opaque, int version_id)
+{
+ IDEState *s = opaque;
+
+ if (s->end_transfer_fn_idx >= ARRAY_SIZE(transfer_end_table)) {
+ return -EINVAL;
+ }
+ s->end_transfer_func = transfer_end_table[s->end_transfer_fn_idx];
+ s->data_ptr = s->io_buffer + s->cur_io_buffer_offset;
+ s->data_end = s->data_ptr + s->cur_io_buffer_len;
+ s->atapi_dma = s->feature & 1; /* as per cmd_packet */
+
+ return 0;
+}
+
+static void ide_drive_pio_pre_save(void *opaque)
+{
+ IDEState *s = opaque;
+ int idx;
+
+ s->cur_io_buffer_offset = s->data_ptr - s->io_buffer;
+ s->cur_io_buffer_len = s->data_end - s->data_ptr;
+
+ idx = transfer_end_table_idx(s->end_transfer_func);
+ if (idx == -1) {
+ fprintf(stderr, "%s: invalid end_transfer_func for DRQ_STAT\n",
+ __func__);
+ s->end_transfer_fn_idx = 2;
+ } else {
+ s->end_transfer_fn_idx = idx;
+ }
+}
+
+static bool ide_drive_pio_state_needed(void *opaque)
+{
+ IDEState *s = opaque;
+
+ return ((s->status & DRQ_STAT) != 0)
+ || (s->bus->error_status & IDE_RETRY_PIO);
+}
+
+static bool ide_tray_state_needed(void *opaque)
+{
+ IDEState *s = opaque;
+
+ return s->tray_open || s->tray_locked;
+}
+
+static bool ide_atapi_gesn_needed(void *opaque)
+{
+ IDEState *s = opaque;
+
+ return s->events.new_media || s->events.eject_request;
+}
+
+static bool ide_error_needed(void *opaque)
+{
+ IDEBus *bus = opaque;
+
+ return (bus->error_status != 0);
+}
+
+/* Fields for GET_EVENT_STATUS_NOTIFICATION ATAPI command */
+static const VMStateDescription vmstate_ide_atapi_gesn_state = {
+ .name ="ide_drive/atapi/gesn_state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ide_atapi_gesn_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(events.new_media, IDEState),
+ VMSTATE_BOOL(events.eject_request, IDEState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ide_tray_state = {
+ .name = "ide_drive/tray_state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ide_tray_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(tray_open, IDEState),
+ VMSTATE_BOOL(tray_locked, IDEState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ide_drive_pio_state = {
+ .name = "ide_drive/pio_state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = ide_drive_pio_pre_save,
+ .post_load = ide_drive_pio_post_load,
+ .needed = ide_drive_pio_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(req_nb_sectors, IDEState),
+ VMSTATE_VARRAY_INT32(io_buffer, IDEState, io_buffer_total_len, 1,
+ vmstate_info_uint8, uint8_t),
+ VMSTATE_INT32(cur_io_buffer_offset, IDEState),
+ VMSTATE_INT32(cur_io_buffer_len, IDEState),
+ VMSTATE_UINT8(end_transfer_fn_idx, IDEState),
+ VMSTATE_INT32(elementary_transfer_size, IDEState),
+ VMSTATE_INT32(packet_transfer_size, IDEState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_ide_drive = {
+ .name = "ide_drive",
+ .version_id = 3,
+ .minimum_version_id = 0,
+ .post_load = ide_drive_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(mult_sectors, IDEState),
+ VMSTATE_INT32(identify_set, IDEState),
+ VMSTATE_BUFFER_TEST(identify_data, IDEState, is_identify_set),
+ VMSTATE_UINT8(feature, IDEState),
+ VMSTATE_UINT8(error, IDEState),
+ VMSTATE_UINT32(nsector, IDEState),
+ VMSTATE_UINT8(sector, IDEState),
+ VMSTATE_UINT8(lcyl, IDEState),
+ VMSTATE_UINT8(hcyl, IDEState),
+ VMSTATE_UINT8(hob_feature, IDEState),
+ VMSTATE_UINT8(hob_sector, IDEState),
+ VMSTATE_UINT8(hob_nsector, IDEState),
+ VMSTATE_UINT8(hob_lcyl, IDEState),
+ VMSTATE_UINT8(hob_hcyl, IDEState),
+ VMSTATE_UINT8(select, IDEState),
+ VMSTATE_UINT8(status, IDEState),
+ VMSTATE_UINT8(lba48, IDEState),
+ VMSTATE_UINT8(sense_key, IDEState),
+ VMSTATE_UINT8(asc, IDEState),
+ VMSTATE_UINT8_V(cdrom_changed, IDEState, 3),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_ide_drive_pio_state,
+ &vmstate_ide_tray_state,
+ &vmstate_ide_atapi_gesn_state,
+ NULL
+ }
+};
+
+static const VMStateDescription vmstate_ide_error_status = {
+ .name ="ide_bus/error",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .needed = ide_error_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(error_status, IDEBus),
+ VMSTATE_INT64_V(retry_sector_num, IDEBus, 2),
+ VMSTATE_UINT32_V(retry_nsector, IDEBus, 2),
+ VMSTATE_UINT8_V(retry_unit, IDEBus, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_ide_bus = {
+ .name = "ide_bus",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(cmd, IDEBus),
+ VMSTATE_UINT8(unit, IDEBus),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_ide_error_status,
+ NULL
+ }
+};
+
+void ide_drive_get(DriveInfo **hd, int n)
+{
+ int i;
+ int highest_bus = drive_get_max_bus(IF_IDE) + 1;
+ int max_devs = drive_get_max_devs(IF_IDE);
+ int n_buses = max_devs ? (n / max_devs) : n;
+
+ /*
+ * Note: The number of actual buses available is not known.
+ * We compute this based on the size of the DriveInfo* array, n.
+ * If it is less than max_devs * <num_real_buses>,
+ * We will stop looking for drives prematurely instead of overfilling
+ * the array.
+ */
+
+ if (highest_bus > n_buses) {
+ error_report("Too many IDE buses defined (%d > %d)",
+ highest_bus, n_buses);
+ exit(1);
+ }
+
+ for (i = 0; i < n; i++) {
+ hd[i] = drive_get_by_index(IF_IDE, i);
+ }
+}
diff --git a/hw/ide/ich.c b/hw/ide/ich.c
new file mode 100644
index 00000000..350c7f1c
--- /dev/null
+++ b/hw/ide/ich.c
@@ -0,0 +1,182 @@
+/*
+ * QEMU ICH Emulation
+ *
+ * Copyright (c) 2010 Sebastian Herbszt <herbszt@gmx.de>
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * lspci dump of a ICH-9 real device
+ *
+ * 00:1f.2 SATA controller [0106]: Intel Corporation 82801IR/IO/IH (ICH9R/DO/DH) 6 port SATA AHCI Controller [8086:2922] (rev 02) (prog-if 01 [AHCI 1.0])
+ * Subsystem: Intel Corporation 82801IR/IO/IH (ICH9R/DO/DH) 6 port SATA AHCI Controller [8086:2922]
+ * Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
+ * Status: Cap+ 66MHz+ UDF- FastB2B+ ParErr- DEVSEL=medium >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
+ * Latency: 0
+ * Interrupt: pin B routed to IRQ 222
+ * Region 0: I/O ports at d000 [size=8]
+ * Region 1: I/O ports at cc00 [size=4]
+ * Region 2: I/O ports at c880 [size=8]
+ * Region 3: I/O ports at c800 [size=4]
+ * Region 4: I/O ports at c480 [size=32]
+ * Region 5: Memory at febf9000 (32-bit, non-prefetchable) [size=2K]
+ * Capabilities: [80] Message Signalled Interrupts: Mask- 64bit- Count=1/16 Enable+
+ * Address: fee0f00c Data: 41d9
+ * Capabilities: [70] Power Management version 3
+ * Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0-,D1-,D2-,D3hot+,D3cold-)
+ * Status: D0 PME-Enable- DSel=0 DScale=0 PME-
+ * Capabilities: [a8] SATA HBA <?>
+ * Capabilities: [b0] Vendor Specific Information <?>
+ * Kernel driver in use: ahci
+ * Kernel modules: ahci
+ * 00: 86 80 22 29 07 04 b0 02 02 01 06 01 00 00 00 00
+ * 10: 01 d0 00 00 01 cc 00 00 81 c8 00 00 01 c8 00 00
+ * 20: 81 c4 00 00 00 90 bf fe 00 00 00 00 86 80 22 29
+ * 30: 00 00 00 00 80 00 00 00 00 00 00 00 0f 02 00 00
+ * 40: 00 80 00 80 00 00 00 00 00 00 00 00 00 00 00 00
+ * 50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 70: 01 a8 03 40 08 00 00 00 00 00 00 00 00 00 00 00
+ * 80: 05 70 09 00 0c f0 e0 fe d9 41 00 00 00 00 00 00
+ * 90: 40 00 0f 82 93 01 00 00 00 00 00 00 00 00 00 00
+ * a0: ac 00 00 00 0a 00 12 00 12 b0 10 00 48 00 00 00
+ * b0: 09 00 06 20 00 00 00 00 00 00 00 00 00 00 00 00
+ * c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * f0: 00 00 00 00 00 00 00 00 86 0f 02 00 00 00 00 00
+ *
+ */
+
+#include <hw/hw.h>
+#include <hw/pci/msi.h>
+#include <hw/i386/pc.h>
+#include <hw/pci/pci.h>
+#include <hw/isa/isa.h>
+#include "sysemu/block-backend.h"
+#include "sysemu/dma.h"
+
+#include <hw/ide/pci.h>
+#include <hw/ide/ahci.h>
+
+#define ICH9_MSI_CAP_OFFSET 0x80
+#define ICH9_SATA_CAP_OFFSET 0xA8
+
+#define ICH9_IDP_BAR 4
+#define ICH9_MEM_BAR 5
+
+#define ICH9_IDP_INDEX 0x10
+#define ICH9_IDP_INDEX_LOG2 0x04
+
+static const VMStateDescription vmstate_ich9_ahci = {
+ .name = "ich9_ahci",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, AHCIPCIState),
+ VMSTATE_AHCI(ahci, AHCIPCIState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void pci_ich9_reset(DeviceState *dev)
+{
+ AHCIPCIState *d = ICH_AHCI(dev);
+
+ ahci_reset(&d->ahci);
+}
+
+static void pci_ich9_ahci_realize(PCIDevice *dev, Error **errp)
+{
+ struct AHCIPCIState *d;
+ int sata_cap_offset;
+ uint8_t *sata_cap;
+ d = ICH_AHCI(dev);
+
+ ahci_init(&d->ahci, DEVICE(dev), pci_get_address_space(dev), 6);
+
+ pci_config_set_prog_interface(dev->config, AHCI_PROGMODE_MAJOR_REV_1);
+
+ dev->config[PCI_CACHE_LINE_SIZE] = 0x08; /* Cache line size */
+ dev->config[PCI_LATENCY_TIMER] = 0x00; /* Latency timer */
+ pci_config_set_interrupt_pin(dev->config, 1);
+
+ /* XXX Software should program this register */
+ dev->config[0x90] = 1 << 6; /* Address Map Register - AHCI mode */
+
+ d->ahci.irq = pci_allocate_irq(dev);
+
+ pci_register_bar(dev, ICH9_IDP_BAR, PCI_BASE_ADDRESS_SPACE_IO,
+ &d->ahci.idp);
+ pci_register_bar(dev, ICH9_MEM_BAR, PCI_BASE_ADDRESS_SPACE_MEMORY,
+ &d->ahci.mem);
+
+ sata_cap_offset = pci_add_capability2(dev, PCI_CAP_ID_SATA,
+ ICH9_SATA_CAP_OFFSET, SATA_CAP_SIZE,
+ errp);
+ if (sata_cap_offset < 0) {
+ return;
+ }
+
+ sata_cap = dev->config + sata_cap_offset;
+ pci_set_word(sata_cap + SATA_CAP_REV, 0x10);
+ pci_set_long(sata_cap + SATA_CAP_BAR,
+ (ICH9_IDP_BAR + 0x4) | (ICH9_IDP_INDEX_LOG2 << 4));
+ d->ahci.idp_offset = ICH9_IDP_INDEX;
+
+ /* Although the AHCI 1.3 specification states that the first capability
+ * should be PMCAP, the Intel ICH9 data sheet specifies that the ICH9
+ * AHCI device puts the MSI capability first, pointing to 0x80. */
+ msi_init(dev, ICH9_MSI_CAP_OFFSET, 1, true, false);
+}
+
+static void pci_ich9_uninit(PCIDevice *dev)
+{
+ struct AHCIPCIState *d;
+ d = ICH_AHCI(dev);
+
+ msi_uninit(dev);
+ ahci_uninit(&d->ahci);
+ qemu_free_irq(d->ahci.irq);
+}
+
+static void ich_ahci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_ich9_ahci_realize;
+ k->exit = pci_ich9_uninit;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82801IR;
+ k->revision = 0x02;
+ k->class_id = PCI_CLASS_STORAGE_SATA;
+ dc->vmsd = &vmstate_ich9_ahci;
+ dc->reset = pci_ich9_reset;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo ich_ahci_info = {
+ .name = TYPE_ICH9_AHCI,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(AHCIPCIState),
+ .class_init = ich_ahci_class_init,
+};
+
+static void ich_ahci_register_types(void)
+{
+ type_register_static(&ich_ahci_info);
+}
+
+type_init(ich_ahci_register_types)
diff --git a/hw/ide/internal.h b/hw/ide/internal.h
new file mode 100644
index 00000000..7288a677
--- /dev/null
+++ b/hw/ide/internal.h
@@ -0,0 +1,584 @@
+#ifndef HW_IDE_INTERNAL_H
+#define HW_IDE_INTERNAL_H
+
+/*
+ * QEMU IDE Emulation -- internal header file
+ * only files in hw/ide/ are supposed to include this file.
+ * non-internal declarations are in hw/ide.h
+ */
+#include <hw/ide.h>
+#include <hw/isa/isa.h>
+#include "sysemu/dma.h"
+#include "sysemu/sysemu.h"
+#include "hw/block/block.h"
+#include "block/scsi.h"
+
+/* debug IDE devices */
+//#define DEBUG_IDE
+//#define DEBUG_IDE_ATAPI
+//#define DEBUG_AIO
+#define USE_DMA_CDROM
+
+typedef struct IDEBus IDEBus;
+typedef struct IDEDevice IDEDevice;
+typedef struct IDEState IDEState;
+typedef struct IDEDMA IDEDMA;
+typedef struct IDEDMAOps IDEDMAOps;
+
+#define TYPE_IDE_BUS "IDE"
+#define IDE_BUS(obj) OBJECT_CHECK(IDEBus, (obj), TYPE_IDE_BUS)
+
+/* Bits of HD_STATUS */
+#define ERR_STAT 0x01
+#define INDEX_STAT 0x02
+#define ECC_STAT 0x04 /* Corrected error */
+#define DRQ_STAT 0x08
+#define SEEK_STAT 0x10
+#define SRV_STAT 0x10
+#define WRERR_STAT 0x20
+#define READY_STAT 0x40
+#define BUSY_STAT 0x80
+
+/* Bits for HD_ERROR */
+#define MARK_ERR 0x01 /* Bad address mark */
+#define TRK0_ERR 0x02 /* couldn't find track 0 */
+#define ABRT_ERR 0x04 /* Command aborted */
+#define MCR_ERR 0x08 /* media change request */
+#define ID_ERR 0x10 /* ID field not found */
+#define MC_ERR 0x20 /* media changed */
+#define ECC_ERR 0x40 /* Uncorrectable ECC error */
+#define BBD_ERR 0x80 /* pre-EIDE meaning: block marked bad */
+#define ICRC_ERR 0x80 /* new meaning: CRC error during transfer */
+
+/* Bits of HD_NSECTOR */
+#define CD 0x01
+#define IO 0x02
+#define REL 0x04
+#define TAG_MASK 0xf8
+
+#define IDE_CMD_RESET 0x04
+#define IDE_CMD_DISABLE_IRQ 0x02
+
+/* ACS-2 T13/2015-D Table B.2 Command codes */
+#define WIN_NOP 0x00
+/* reserved 0x01..0x02 */
+#define CFA_REQ_EXT_ERROR_CODE 0x03 /* CFA Request Extended Error Code */
+/* reserved 0x04..0x05 */
+#define WIN_DSM 0x06
+/* reserved 0x07 */
+#define WIN_DEVICE_RESET 0x08
+/* reserved 0x09..0x0a */
+/* REQUEST SENSE DATA EXT 0x0B */
+/* reserved 0x0C..0x0F */
+#define WIN_RECAL 0x10 /* obsolete since ATA4 */
+/* obsolete since ATA3, retired in ATA4 0x11..0x1F */
+#define WIN_READ 0x20 /* 28-Bit */
+#define WIN_READ_ONCE 0x21 /* 28-Bit w/o retries, obsolete since ATA5 */
+/* obsolete since ATA4 0x22..0x23 */
+#define WIN_READ_EXT 0x24 /* 48-Bit */
+#define WIN_READDMA_EXT 0x25 /* 48-Bit */
+#define WIN_READDMA_QUEUED_EXT 0x26 /* 48-Bit, obsolete since ACS2 */
+#define WIN_READ_NATIVE_MAX_EXT 0x27 /* 48-Bit */
+/* reserved 0x28 */
+#define WIN_MULTREAD_EXT 0x29 /* 48-Bit */
+/* READ STREAM DMA EXT 0x2A */
+/* READ STREAM EXT 0x2B */
+/* reserved 0x2C..0x2E */
+/* READ LOG EXT 0x2F */
+#define WIN_WRITE 0x30 /* 28-Bit */
+#define WIN_WRITE_ONCE 0x31 /* 28-Bit w/o retries, obsolete since ATA5 */
+/* obsolete since ATA4 0x32..0x33 */
+#define WIN_WRITE_EXT 0x34 /* 48-Bit */
+#define WIN_WRITEDMA_EXT 0x35 /* 48-Bit */
+#define WIN_WRITEDMA_QUEUED_EXT 0x36 /* 48-Bit */
+#define WIN_SET_MAX_EXT 0x37 /* 48-Bit, obsolete since ACS2 */
+#define WIN_SET_MAX_EXT 0x37 /* 48-Bit */
+#define CFA_WRITE_SECT_WO_ERASE 0x38 /* CFA Write Sectors without erase */
+#define WIN_MULTWRITE_EXT 0x39 /* 48-Bit */
+/* WRITE STREAM DMA EXT 0x3A */
+/* WRITE STREAM EXT 0x3B */
+#define WIN_WRITE_VERIFY 0x3C /* 28-Bit, obsolete since ATA4 */
+/* WRITE DMA FUA EXT 0x3D */
+/* obsolete since ACS2 0x3E */
+/* WRITE LOG EXT 0x3F */
+#define WIN_VERIFY 0x40 /* 28-Bit - Read Verify Sectors */
+#define WIN_VERIFY_ONCE 0x41 /* 28-Bit - w/o retries, obsolete since ATA5 */
+#define WIN_VERIFY_EXT 0x42 /* 48-Bit */
+/* reserved 0x43..0x44 */
+/* WRITE UNCORRECTABLE EXT 0x45 */
+/* reserved 0x46 */
+/* READ LOG DMA EXT 0x47 */
+/* reserved 0x48..0x4F */
+/* obsolete since ATA4 0x50 */
+/* CONFIGURE STREAM 0x51 */
+/* reserved 0x52..0x56 */
+/* WRITE LOG DMA EXT 0x57 */
+/* reserved 0x58..0x5A */
+/* TRUSTED NON DATA 0x5B */
+/* TRUSTED RECEIVE 0x5C */
+/* TRUSTED RECEIVE DMA 0x5D */
+/* TRUSTED SEND 0x5E */
+/* TRUSTED SEND DMA 0x5F */
+/* READ FPDMA QUEUED 0x60 */
+/* WRITE FPDMA QUEUED 0x61 */
+/* reserved 0x62->0x6F */
+#define WIN_SEEK 0x70 /* obsolete since ATA7 */
+/* reserved 0x71-0x7F */
+/* vendor specific 0x80-0x86 */
+#define CFA_TRANSLATE_SECTOR 0x87 /* CFA Translate Sector */
+/* vendor specific 0x88-0x8F */
+#define WIN_DIAGNOSE 0x90
+#define WIN_SPECIFY 0x91 /* set drive geometry translation, obsolete since ATA6 */
+#define WIN_DOWNLOAD_MICROCODE 0x92
+/* DOWNLOAD MICROCODE DMA 0x93 */
+#define WIN_STANDBYNOW2 0x94 /* retired in ATA4 */
+#define WIN_IDLEIMMEDIATE2 0x95 /* force drive to become "ready", retired in ATA4 */
+#define WIN_STANDBY2 0x96 /* retired in ATA4 */
+#define WIN_SETIDLE2 0x97 /* retired in ATA4 */
+#define WIN_CHECKPOWERMODE2 0x98 /* retired in ATA4 */
+#define WIN_SLEEPNOW2 0x99 /* retired in ATA4 */
+/* vendor specific 0x9A */
+/* reserved 0x9B..0x9F */
+#define WIN_PACKETCMD 0xA0 /* Send a packet command. */
+#define WIN_PIDENTIFY 0xA1 /* identify ATAPI device */
+#define WIN_QUEUED_SERVICE 0xA2 /* obsolete since ACS2 */
+/* reserved 0xA3..0xAF */
+#define WIN_SMART 0xB0 /* self-monitoring and reporting */
+/* Device Configuration Overlay 0xB1 */
+/* reserved 0xB2..0xB3 */
+/* Sanitize Device 0xB4 */
+/* reserved 0xB5 */
+/* NV Cache 0xB6 */
+/* reserved for CFA 0xB7..0xBB */
+#define CFA_ACCESS_METADATA_STORAGE 0xB8
+/* reserved 0xBC..0xBF */
+#define CFA_ERASE_SECTORS 0xC0 /* microdrives implement as NOP */
+/* vendor specific 0xC1..0xC3 */
+#define WIN_MULTREAD 0xC4 /* read sectors using multiple mode*/
+#define WIN_MULTWRITE 0xC5 /* write sectors using multiple mode */
+#define WIN_SETMULT 0xC6 /* enable/disable multiple mode */
+#define WIN_READDMA_QUEUED 0xC7 /* read sectors using Queued DMA transfers, obsolete since ACS2 */
+#define WIN_READDMA 0xC8 /* read sectors using DMA transfers */
+#define WIN_READDMA_ONCE 0xC9 /* 28-Bit - w/o retries, obsolete since ATA5 */
+#define WIN_WRITEDMA 0xCA /* write sectors using DMA transfers */
+#define WIN_WRITEDMA_ONCE 0xCB /* 28-Bit - w/o retries, obsolete since ATA5 */
+#define WIN_WRITEDMA_QUEUED 0xCC /* write sectors using Queued DMA transfers, obsolete since ACS2 */
+#define CFA_WRITE_MULTI_WO_ERASE 0xCD /* CFA Write multiple without erase */
+/* WRITE MULTIPLE FUA EXT 0xCE */
+/* reserved 0xCF..0xDO */
+/* CHECK MEDIA CARD TYPE 0xD1 */
+/* reserved for media card pass through 0xD2..0xD4 */
+/* reserved 0xD5..0xD9 */
+#define WIN_GETMEDIASTATUS 0xDA /* obsolete since ATA8 */
+/* obsolete since ATA3, retired in ATA4 0xDB..0xDD */
+#define WIN_DOORLOCK 0xDE /* lock door on removable drives, obsolete since ATA8 */
+#define WIN_DOORUNLOCK 0xDF /* unlock door on removable drives, obsolete since ATA8 */
+#define WIN_STANDBYNOW1 0xE0
+#define WIN_IDLEIMMEDIATE 0xE1 /* force drive to become "ready" */
+#define WIN_STANDBY 0xE2 /* Set device in Standby Mode */
+#define WIN_SETIDLE1 0xE3
+#define WIN_READ_BUFFER 0xE4 /* force read only 1 sector */
+#define WIN_CHECKPOWERMODE1 0xE5
+#define WIN_SLEEPNOW1 0xE6
+#define WIN_FLUSH_CACHE 0xE7
+#define WIN_WRITE_BUFFER 0xE8 /* force write only 1 sector */
+/* READ BUFFER DMA 0xE9 */
+#define WIN_FLUSH_CACHE_EXT 0xEA /* 48-Bit */
+/* WRITE BUFFER DMA 0xEB */
+#define WIN_IDENTIFY 0xEC /* ask drive to identify itself */
+#define WIN_MEDIAEJECT 0xED /* obsolete since ATA8 */
+/* obsolete since ATA4 0xEE */
+#define WIN_SETFEATURES 0xEF /* set special drive features */
+#define IBM_SENSE_CONDITION 0xF0 /* measure disk temperature, vendor specific */
+#define WIN_SECURITY_SET_PASS 0xF1
+#define WIN_SECURITY_UNLOCK 0xF2
+#define WIN_SECURITY_ERASE_PREPARE 0xF3
+#define WIN_SECURITY_ERASE_UNIT 0xF4
+#define WIN_SECURITY_FREEZE_LOCK 0xF5
+#define CFA_WEAR_LEVEL 0xF5 /* microdrives implement as NOP; not specified in T13! */
+#define WIN_SECURITY_DISABLE 0xF6
+/* vendor specific 0xF7 */
+#define WIN_READ_NATIVE_MAX 0xF8 /* return the native maximum address */
+#define WIN_SET_MAX 0xF9
+/* vendor specific 0xFA..0xFF */
+
+/* set to 1 set disable mult support */
+#define MAX_MULT_SECTORS 16
+
+#define IDE_DMA_BUF_SECTORS 256
+
+/* feature values for Data Set Management */
+#define DSM_TRIM 0x01
+
+#if (IDE_DMA_BUF_SECTORS < MAX_MULT_SECTORS)
+#error "IDE_DMA_BUF_SECTORS must be bigger or equal to MAX_MULT_SECTORS"
+#endif
+
+/* ATAPI defines */
+
+#define ATAPI_PACKET_SIZE 12
+
+/* The generic packet command opcodes for CD/DVD Logical Units,
+ * From Table 57 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */
+#define GPCMD_BLANK 0xa1
+#define GPCMD_CLOSE_TRACK 0x5b
+#define GPCMD_FLUSH_CACHE 0x35
+#define GPCMD_FORMAT_UNIT 0x04
+#define GPCMD_GET_CONFIGURATION 0x46
+#define GPCMD_GET_EVENT_STATUS_NOTIFICATION 0x4a
+#define GPCMD_GET_PERFORMANCE 0xac
+#define GPCMD_INQUIRY 0x12
+#define GPCMD_LOAD_UNLOAD 0xa6
+#define GPCMD_MECHANISM_STATUS 0xbd
+#define GPCMD_MODE_SELECT_10 0x55
+#define GPCMD_MODE_SENSE_10 0x5a
+#define GPCMD_PAUSE_RESUME 0x4b
+#define GPCMD_PLAY_AUDIO_10 0x45
+#define GPCMD_PLAY_AUDIO_MSF 0x47
+#define GPCMD_PLAY_AUDIO_TI 0x48
+#define GPCMD_PLAY_CD 0xbc
+#define GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e
+#define GPCMD_READ_10 0x28
+#define GPCMD_READ_12 0xa8
+#define GPCMD_READ_CDVD_CAPACITY 0x25
+#define GPCMD_READ_CD 0xbe
+#define GPCMD_READ_CD_MSF 0xb9
+#define GPCMD_READ_DISC_INFO 0x51
+#define GPCMD_READ_DVD_STRUCTURE 0xad
+#define GPCMD_READ_FORMAT_CAPACITIES 0x23
+#define GPCMD_READ_HEADER 0x44
+#define GPCMD_READ_TRACK_RZONE_INFO 0x52
+#define GPCMD_READ_SUBCHANNEL 0x42
+#define GPCMD_READ_TOC_PMA_ATIP 0x43
+#define GPCMD_REPAIR_RZONE_TRACK 0x58
+#define GPCMD_REPORT_KEY 0xa4
+#define GPCMD_REQUEST_SENSE 0x03
+#define GPCMD_RESERVE_RZONE_TRACK 0x53
+#define GPCMD_SCAN 0xba
+#define GPCMD_SEEK 0x2b
+#define GPCMD_SEND_DVD_STRUCTURE 0xad
+#define GPCMD_SEND_EVENT 0xa2
+#define GPCMD_SEND_KEY 0xa3
+#define GPCMD_SEND_OPC 0x54
+#define GPCMD_SET_READ_AHEAD 0xa7
+#define GPCMD_SET_STREAMING 0xb6
+#define GPCMD_START_STOP_UNIT 0x1b
+#define GPCMD_STOP_PLAY_SCAN 0x4e
+#define GPCMD_TEST_UNIT_READY 0x00
+#define GPCMD_VERIFY_10 0x2f
+#define GPCMD_WRITE_10 0x2a
+#define GPCMD_WRITE_AND_VERIFY_10 0x2e
+/* This is listed as optional in ATAPI 2.6, but is (curiously)
+ * missing from Mt. Fuji, Table 57. It _is_ mentioned in Mt. Fuji
+ * Table 377 as an MMC command for SCSi devices though... Most ATAPI
+ * drives support it. */
+#define GPCMD_SET_SPEED 0xbb
+/* This seems to be a SCSI specific CD-ROM opcode
+ * to play data at track/index */
+#define GPCMD_PLAYAUDIO_TI 0x48
+/*
+ * From MS Media Status Notification Support Specification. For
+ * older drives only.
+ */
+#define GPCMD_GET_MEDIA_STATUS 0xda
+#define GPCMD_MODE_SENSE_6 0x1a
+
+#define ATAPI_INT_REASON_CD 0x01 /* 0 = data transfer */
+#define ATAPI_INT_REASON_IO 0x02 /* 1 = transfer to the host */
+#define ATAPI_INT_REASON_REL 0x04
+#define ATAPI_INT_REASON_TAG 0xf8
+
+/* same constants as bochs */
+#define ASC_NO_SEEK_COMPLETE 0x02
+#define ASC_ILLEGAL_OPCODE 0x20
+#define ASC_LOGICAL_BLOCK_OOR 0x21
+#define ASC_INV_FIELD_IN_CMD_PACKET 0x24
+#define ASC_MEDIUM_MAY_HAVE_CHANGED 0x28
+#define ASC_INCOMPATIBLE_FORMAT 0x30
+#define ASC_MEDIUM_NOT_PRESENT 0x3a
+#define ASC_SAVING_PARAMETERS_NOT_SUPPORTED 0x39
+#define ASC_DATA_PHASE_ERROR 0x4b
+#define ASC_MEDIA_REMOVAL_PREVENTED 0x53
+
+#define CFA_NO_ERROR 0x00
+#define CFA_MISC_ERROR 0x09
+#define CFA_INVALID_COMMAND 0x20
+#define CFA_INVALID_ADDRESS 0x21
+#define CFA_ADDRESS_OVERFLOW 0x2f
+
+#define SMART_READ_DATA 0xd0
+#define SMART_READ_THRESH 0xd1
+#define SMART_ATTR_AUTOSAVE 0xd2
+#define SMART_SAVE_ATTR 0xd3
+#define SMART_EXECUTE_OFFLINE 0xd4
+#define SMART_READ_LOG 0xd5
+#define SMART_WRITE_LOG 0xd6
+#define SMART_ENABLE 0xd8
+#define SMART_DISABLE 0xd9
+#define SMART_STATUS 0xda
+
+typedef enum { IDE_HD, IDE_CD, IDE_CFATA } IDEDriveKind;
+
+typedef void EndTransferFunc(IDEState *);
+
+typedef void DMAStartFunc(IDEDMA *, IDEState *, BlockCompletionFunc *);
+typedef void DMAVoidFunc(IDEDMA *);
+typedef int DMAIntFunc(IDEDMA *, int);
+typedef int32_t DMAInt32Func(IDEDMA *, int32_t len);
+typedef void DMAu32Func(IDEDMA *, uint32_t);
+typedef void DMAStopFunc(IDEDMA *, bool);
+typedef void DMARestartFunc(void *, int, RunState);
+
+struct unreported_events {
+ bool eject_request;
+ bool new_media;
+};
+
+enum ide_dma_cmd {
+ IDE_DMA_READ,
+ IDE_DMA_WRITE,
+ IDE_DMA_TRIM,
+};
+
+#define ide_cmd_is_read(s) \
+ ((s)->dma_cmd == IDE_DMA_READ)
+
+/* NOTE: IDEState represents in fact one drive */
+struct IDEState {
+ IDEBus *bus;
+ uint8_t unit;
+ /* ide config */
+ IDEDriveKind drive_kind;
+ int cylinders, heads, sectors, chs_trans;
+ int64_t nb_sectors;
+ int mult_sectors;
+ int identify_set;
+ uint8_t identify_data[512];
+ int drive_serial;
+ char drive_serial_str[21];
+ char drive_model_str[41];
+ uint64_t wwn;
+ /* ide regs */
+ uint8_t feature;
+ uint8_t error;
+ uint32_t nsector;
+ uint8_t sector;
+ uint8_t lcyl;
+ uint8_t hcyl;
+ /* other part of tf for lba48 support */
+ uint8_t hob_feature;
+ uint8_t hob_nsector;
+ uint8_t hob_sector;
+ uint8_t hob_lcyl;
+ uint8_t hob_hcyl;
+
+ uint8_t select;
+ uint8_t status;
+
+ /* set for lba48 access */
+ uint8_t lba48;
+ BlockBackend *blk;
+ char version[9];
+ /* ATAPI specific */
+ struct unreported_events events;
+ uint8_t sense_key;
+ uint8_t asc;
+ bool tray_open;
+ bool tray_locked;
+ uint8_t cdrom_changed;
+ int packet_transfer_size;
+ int elementary_transfer_size;
+ int32_t io_buffer_index;
+ int lba;
+ int cd_sector_size;
+ int atapi_dma; /* true if dma is requested for the packet cmd */
+ BlockAcctCookie acct;
+ BlockAIOCB *pio_aiocb;
+ struct iovec iov;
+ QEMUIOVector qiov;
+ /* ATA DMA state */
+ int32_t io_buffer_offset;
+ int32_t io_buffer_size;
+ QEMUSGList sg;
+ /* PIO transfer handling */
+ int req_nb_sectors; /* number of sectors per interrupt */
+ EndTransferFunc *end_transfer_func;
+ uint8_t *data_ptr;
+ uint8_t *data_end;
+ uint8_t *io_buffer;
+ /* PIO save/restore */
+ int32_t io_buffer_total_len;
+ int32_t cur_io_buffer_offset;
+ int32_t cur_io_buffer_len;
+ uint8_t end_transfer_fn_idx;
+ QEMUTimer *sector_write_timer; /* only used for win2k install hack */
+ uint32_t irq_count; /* counts IRQs when using win2k install hack */
+ /* CF-ATA extended error */
+ uint8_t ext_error;
+ /* CF-ATA metadata storage */
+ uint32_t mdata_size;
+ uint8_t *mdata_storage;
+ int media_changed;
+ enum ide_dma_cmd dma_cmd;
+ /* SMART */
+ uint8_t smart_enabled;
+ uint8_t smart_autosave;
+ int smart_errors;
+ uint8_t smart_selftest_count;
+ uint8_t *smart_selftest_data;
+ /* AHCI */
+ int ncq_queues;
+};
+
+struct IDEDMAOps {
+ DMAStartFunc *start_dma;
+ DMAVoidFunc *start_transfer;
+ DMAInt32Func *prepare_buf;
+ DMAu32Func *commit_buf;
+ DMAIntFunc *rw_buf;
+ DMAVoidFunc *restart;
+ DMAVoidFunc *restart_dma;
+ DMAStopFunc *set_inactive;
+ DMAVoidFunc *cmd_done;
+ DMAVoidFunc *reset;
+};
+
+struct IDEDMA {
+ const struct IDEDMAOps *ops;
+ struct iovec iov;
+ QEMUIOVector qiov;
+ BlockAIOCB *aiocb;
+};
+
+struct IDEBus {
+ BusState qbus;
+ IDEDevice *master;
+ IDEDevice *slave;
+ IDEState ifs[2];
+ QEMUBH *bh;
+
+ int bus_id;
+ int max_units;
+ IDEDMA *dma;
+ uint8_t unit;
+ uint8_t cmd;
+ qemu_irq irq;
+
+ int error_status;
+ uint8_t retry_unit;
+ int64_t retry_sector_num;
+ uint32_t retry_nsector;
+};
+
+#define TYPE_IDE_DEVICE "ide-device"
+#define IDE_DEVICE(obj) \
+ OBJECT_CHECK(IDEDevice, (obj), TYPE_IDE_DEVICE)
+#define IDE_DEVICE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(IDEDeviceClass, (klass), TYPE_IDE_DEVICE)
+#define IDE_DEVICE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(IDEDeviceClass, (obj), TYPE_IDE_DEVICE)
+
+typedef struct IDEDeviceClass {
+ DeviceClass parent_class;
+ int (*init)(IDEDevice *dev);
+} IDEDeviceClass;
+
+struct IDEDevice {
+ DeviceState qdev;
+ uint32_t unit;
+ BlockConf conf;
+ int chs_trans;
+ char *version;
+ char *serial;
+ char *model;
+ uint64_t wwn;
+};
+
+/* These are used for the error_status field of IDEBus */
+#define IDE_RETRY_DMA 0x08
+#define IDE_RETRY_PIO 0x10
+#define IDE_RETRY_READ 0x20
+#define IDE_RETRY_FLUSH 0x40
+#define IDE_RETRY_TRIM 0x80
+#define IDE_RETRY_HBA 0x100
+
+static inline IDEState *idebus_active_if(IDEBus *bus)
+{
+ return bus->ifs + bus->unit;
+}
+
+static inline void ide_set_irq(IDEBus *bus)
+{
+ if (!(bus->cmd & IDE_CMD_DISABLE_IRQ)) {
+ qemu_irq_raise(bus->irq);
+ }
+}
+
+/* hw/ide/core.c */
+extern const VMStateDescription vmstate_ide_bus;
+
+#define VMSTATE_IDE_BUS(_field, _state) \
+ VMSTATE_STRUCT(_field, _state, 1, vmstate_ide_bus, IDEBus)
+
+#define VMSTATE_IDE_BUS_ARRAY(_field, _state, _num) \
+ VMSTATE_STRUCT_ARRAY(_field, _state, _num, 1, vmstate_ide_bus, IDEBus)
+
+extern const VMStateDescription vmstate_ide_drive;
+
+#define VMSTATE_IDE_DRIVES(_field, _state) \
+ VMSTATE_STRUCT_ARRAY(_field, _state, 2, 3, vmstate_ide_drive, IDEState)
+
+#define VMSTATE_IDE_DRIVE(_field, _state) \
+ VMSTATE_STRUCT(_field, _state, 1, vmstate_ide_drive, IDEState)
+
+void ide_bus_reset(IDEBus *bus);
+int64_t ide_get_sector(IDEState *s);
+void ide_set_sector(IDEState *s, int64_t sector_num);
+
+void ide_start_dma(IDEState *s, BlockCompletionFunc *cb);
+void dma_buf_commit(IDEState *s, uint32_t tx_bytes);
+void ide_dma_error(IDEState *s);
+
+void ide_atapi_cmd_ok(IDEState *s);
+void ide_atapi_cmd_error(IDEState *s, int sense_key, int asc);
+void ide_atapi_dma_restart(IDEState *s);
+void ide_atapi_io_error(IDEState *s, int ret);
+
+void ide_ioport_write(void *opaque, uint32_t addr, uint32_t val);
+uint32_t ide_ioport_read(void *opaque, uint32_t addr1);
+uint32_t ide_status_read(void *opaque, uint32_t addr);
+void ide_cmd_write(void *opaque, uint32_t addr, uint32_t val);
+void ide_data_writew(void *opaque, uint32_t addr, uint32_t val);
+uint32_t ide_data_readw(void *opaque, uint32_t addr);
+void ide_data_writel(void *opaque, uint32_t addr, uint32_t val);
+uint32_t ide_data_readl(void *opaque, uint32_t addr);
+
+int ide_init_drive(IDEState *s, BlockBackend *blk, IDEDriveKind kind,
+ const char *version, const char *serial, const char *model,
+ uint64_t wwn,
+ uint32_t cylinders, uint32_t heads, uint32_t secs,
+ int chs_trans);
+void ide_init2(IDEBus *bus, qemu_irq irq);
+void ide_init_ioport(IDEBus *bus, ISADevice *isa, int iobase, int iobase2);
+void ide_register_restart_cb(IDEBus *bus);
+
+void ide_exec_cmd(IDEBus *bus, uint32_t val);
+
+void ide_transfer_start(IDEState *s, uint8_t *buf, int size,
+ EndTransferFunc *end_transfer_func);
+void ide_transfer_stop(IDEState *s);
+void ide_set_inactive(IDEState *s, bool more);
+BlockAIOCB *ide_issue_trim(BlockBackend *blk,
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
+ BlockCompletionFunc *cb, void *opaque);
+
+/* hw/ide/atapi.c */
+void ide_atapi_cmd(IDEState *s);
+void ide_atapi_cmd_reply_end(IDEState *s);
+
+/* hw/ide/qdev.c */
+void ide_bus_new(IDEBus *idebus, size_t idebus_size, DeviceState *dev,
+ int bus_id, int max_units);
+IDEDevice *ide_create_drive(IDEBus *bus, int unit, DriveInfo *drive);
+
+#endif /* HW_IDE_INTERNAL_H */
diff --git a/hw/ide/isa.c b/hw/ide/isa.c
new file mode 100644
index 00000000..9f80503f
--- /dev/null
+++ b/hw/ide/isa.c
@@ -0,0 +1,134 @@
+/*
+ * QEMU IDE Emulation: ISA Bus support.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+#include <hw/hw.h>
+#include <hw/i386/pc.h>
+#include <hw/isa/isa.h>
+#include "sysemu/block-backend.h"
+#include "sysemu/dma.h"
+
+#include <hw/ide/internal.h>
+
+/***********************************************************/
+/* ISA IDE definitions */
+
+#define TYPE_ISA_IDE "isa-ide"
+#define ISA_IDE(obj) OBJECT_CHECK(ISAIDEState, (obj), TYPE_ISA_IDE)
+
+typedef struct ISAIDEState {
+ ISADevice parent_obj;
+
+ IDEBus bus;
+ uint32_t iobase;
+ uint32_t iobase2;
+ uint32_t isairq;
+ qemu_irq irq;
+} ISAIDEState;
+
+static void isa_ide_reset(DeviceState *d)
+{
+ ISAIDEState *s = ISA_IDE(d);
+
+ ide_bus_reset(&s->bus);
+}
+
+static const VMStateDescription vmstate_ide_isa = {
+ .name = "isa-ide",
+ .version_id = 3,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_IDE_BUS(bus, ISAIDEState),
+ VMSTATE_IDE_DRIVES(bus.ifs, ISAIDEState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void isa_ide_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISAIDEState *s = ISA_IDE(dev);
+
+ ide_bus_new(&s->bus, sizeof(s->bus), dev, 0, 2);
+ ide_init_ioport(&s->bus, isadev, s->iobase, s->iobase2);
+ isa_init_irq(isadev, &s->irq, s->isairq);
+ ide_init2(&s->bus, s->irq);
+ vmstate_register(dev, 0, &vmstate_ide_isa, s);
+ ide_register_restart_cb(&s->bus);
+}
+
+ISADevice *isa_ide_init(ISABus *bus, int iobase, int iobase2, int isairq,
+ DriveInfo *hd0, DriveInfo *hd1)
+{
+ DeviceState *dev;
+ ISADevice *isadev;
+ ISAIDEState *s;
+
+ isadev = isa_create(bus, TYPE_ISA_IDE);
+ dev = DEVICE(isadev);
+ qdev_prop_set_uint32(dev, "iobase", iobase);
+ qdev_prop_set_uint32(dev, "iobase2", iobase2);
+ qdev_prop_set_uint32(dev, "irq", isairq);
+ qdev_init_nofail(dev);
+
+ s = ISA_IDE(dev);
+ if (hd0) {
+ ide_create_drive(&s->bus, 0, hd0);
+ }
+ if (hd1) {
+ ide_create_drive(&s->bus, 1, hd1);
+ }
+ return isadev;
+}
+
+static Property isa_ide_properties[] = {
+ DEFINE_PROP_UINT32("iobase", ISAIDEState, iobase, 0x1f0),
+ DEFINE_PROP_UINT32("iobase2", ISAIDEState, iobase2, 0x3f6),
+ DEFINE_PROP_UINT32("irq", ISAIDEState, isairq, 14),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void isa_ide_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = isa_ide_realizefn;
+ dc->fw_name = "ide";
+ dc->reset = isa_ide_reset;
+ dc->props = isa_ide_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo isa_ide_info = {
+ .name = TYPE_ISA_IDE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISAIDEState),
+ .class_init = isa_ide_class_initfn,
+};
+
+static void isa_ide_register_types(void)
+{
+ type_register_static(&isa_ide_info);
+}
+
+type_init(isa_ide_register_types)
diff --git a/hw/ide/macio.c b/hw/ide/macio.c
new file mode 100644
index 00000000..66ac2baa
--- /dev/null
+++ b/hw/ide/macio.c
@@ -0,0 +1,627 @@
+/*
+ * QEMU IDE Emulation: MacIO support.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/mac.h"
+#include "hw/ppc/mac_dbdma.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/dma.h"
+
+#include <hw/ide/internal.h>
+
+/* debug MACIO */
+// #define DEBUG_MACIO
+
+#ifdef DEBUG_MACIO
+static const int debug_macio = 1;
+#else
+static const int debug_macio = 0;
+#endif
+
+#define MACIO_DPRINTF(fmt, ...) do { \
+ if (debug_macio) { \
+ printf(fmt , ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+
+/***********************************************************/
+/* MacIO based PowerPC IDE */
+
+#define MACIO_PAGE_SIZE 4096
+
+/*
+ * Unaligned DMA read/write access functions required for OS X/Darwin which
+ * don't perform DMA transactions on sector boundaries. These functions are
+ * modelled on bdrv_co_do_preadv()/bdrv_co_do_pwritev() and so should be
+ * easy to remove if the unaligned block APIs are ever exposed.
+ */
+
+static void pmac_dma_read(BlockBackend *blk,
+ int64_t offset, unsigned int bytes,
+ void (*cb)(void *opaque, int ret), void *opaque)
+{
+ DBDMA_io *io = opaque;
+ MACIOIDEState *m = io->opaque;
+ IDEState *s = idebus_active_if(&m->bus);
+ dma_addr_t dma_addr, dma_len;
+ void *mem;
+ int64_t sector_num;
+ int nsector;
+ uint64_t align = BDRV_SECTOR_SIZE;
+ size_t head_bytes, tail_bytes;
+
+ qemu_iovec_destroy(&io->iov);
+ qemu_iovec_init(&io->iov, io->len / MACIO_PAGE_SIZE + 1);
+
+ sector_num = (offset >> 9);
+ nsector = (io->len >> 9);
+
+ MACIO_DPRINTF("--- DMA read transfer (0x%" HWADDR_PRIx ",0x%x): "
+ "sector_num: %" PRId64 ", nsector: %d\n", io->addr, io->len,
+ sector_num, nsector);
+
+ dma_addr = io->addr;
+ dma_len = io->len;
+ mem = dma_memory_map(&address_space_memory, dma_addr, &dma_len,
+ DMA_DIRECTION_FROM_DEVICE);
+
+ if (offset & (align - 1)) {
+ head_bytes = offset & (align - 1);
+
+ MACIO_DPRINTF("--- DMA unaligned head: sector %" PRId64 ", "
+ "discarding %zu bytes\n", sector_num, head_bytes);
+
+ qemu_iovec_add(&io->iov, &io->head_remainder, head_bytes);
+
+ bytes += offset & (align - 1);
+ offset = offset & ~(align - 1);
+ }
+
+ qemu_iovec_add(&io->iov, mem, io->len);
+
+ if ((offset + bytes) & (align - 1)) {
+ tail_bytes = (offset + bytes) & (align - 1);
+
+ MACIO_DPRINTF("--- DMA unaligned tail: sector %" PRId64 ", "
+ "discarding bytes %zu\n", sector_num, tail_bytes);
+
+ qemu_iovec_add(&io->iov, &io->tail_remainder, align - tail_bytes);
+ bytes = ROUND_UP(bytes, align);
+ }
+
+ s->io_buffer_size -= io->len;
+ s->io_buffer_index += io->len;
+
+ io->len = 0;
+
+ MACIO_DPRINTF("--- Block read transfer - sector_num: %" PRIx64 " "
+ "nsector: %x\n", (offset >> 9), (bytes >> 9));
+
+ m->aiocb = blk_aio_readv(blk, (offset >> 9), &io->iov, (bytes >> 9),
+ cb, io);
+}
+
+static void pmac_dma_write(BlockBackend *blk,
+ int64_t offset, int bytes,
+ void (*cb)(void *opaque, int ret), void *opaque)
+{
+ DBDMA_io *io = opaque;
+ MACIOIDEState *m = io->opaque;
+ IDEState *s = idebus_active_if(&m->bus);
+ dma_addr_t dma_addr, dma_len;
+ void *mem;
+ int64_t sector_num;
+ int nsector;
+ uint64_t align = BDRV_SECTOR_SIZE;
+ size_t head_bytes, tail_bytes;
+ bool unaligned_head = false, unaligned_tail = false;
+
+ qemu_iovec_destroy(&io->iov);
+ qemu_iovec_init(&io->iov, io->len / MACIO_PAGE_SIZE + 1);
+
+ sector_num = (offset >> 9);
+ nsector = (io->len >> 9);
+
+ MACIO_DPRINTF("--- DMA write transfer (0x%" HWADDR_PRIx ",0x%x): "
+ "sector_num: %" PRId64 ", nsector: %d\n", io->addr, io->len,
+ sector_num, nsector);
+
+ dma_addr = io->addr;
+ dma_len = io->len;
+ mem = dma_memory_map(&address_space_memory, dma_addr, &dma_len,
+ DMA_DIRECTION_TO_DEVICE);
+
+ if (offset & (align - 1)) {
+ head_bytes = offset & (align - 1);
+ sector_num = ((offset & ~(align - 1)) >> 9);
+
+ MACIO_DPRINTF("--- DMA unaligned head: pre-reading head sector %"
+ PRId64 "\n", sector_num);
+
+ blk_pread(s->blk, (sector_num << 9), &io->head_remainder, align);
+
+ qemu_iovec_add(&io->iov, &io->head_remainder, head_bytes);
+ qemu_iovec_add(&io->iov, mem, io->len);
+
+ bytes += offset & (align - 1);
+ offset = offset & ~(align - 1);
+
+ unaligned_head = true;
+ }
+
+ if ((offset + bytes) & (align - 1)) {
+ tail_bytes = (offset + bytes) & (align - 1);
+ sector_num = (((offset + bytes) & ~(align - 1)) >> 9);
+
+ MACIO_DPRINTF("--- DMA unaligned tail: pre-reading tail sector %"
+ PRId64 "\n", sector_num);
+
+ blk_pread(s->blk, (sector_num << 9), &io->tail_remainder, align);
+
+ if (!unaligned_head) {
+ qemu_iovec_add(&io->iov, mem, io->len);
+ }
+
+ qemu_iovec_add(&io->iov, &io->tail_remainder + tail_bytes,
+ align - tail_bytes);
+
+ bytes = ROUND_UP(bytes, align);
+
+ unaligned_tail = true;
+ }
+
+ if (!unaligned_head && !unaligned_tail) {
+ qemu_iovec_add(&io->iov, mem, io->len);
+ }
+
+ s->io_buffer_size -= io->len;
+ s->io_buffer_index += io->len;
+
+ io->len = 0;
+
+ MACIO_DPRINTF("--- Block write transfer - sector_num: %" PRIx64 " "
+ "nsector: %x\n", (offset >> 9), (bytes >> 9));
+
+ m->aiocb = blk_aio_writev(blk, (offset >> 9), &io->iov, (bytes >> 9),
+ cb, io);
+}
+
+static void pmac_dma_trim(BlockBackend *blk,
+ int64_t offset, int bytes,
+ void (*cb)(void *opaque, int ret), void *opaque)
+{
+ DBDMA_io *io = opaque;
+ MACIOIDEState *m = io->opaque;
+ IDEState *s = idebus_active_if(&m->bus);
+ dma_addr_t dma_addr, dma_len;
+ void *mem;
+
+ qemu_iovec_destroy(&io->iov);
+ qemu_iovec_init(&io->iov, io->len / MACIO_PAGE_SIZE + 1);
+
+ dma_addr = io->addr;
+ dma_len = io->len;
+ mem = dma_memory_map(&address_space_memory, dma_addr, &dma_len,
+ DMA_DIRECTION_TO_DEVICE);
+
+ qemu_iovec_add(&io->iov, mem, io->len);
+ s->io_buffer_size -= io->len;
+ s->io_buffer_index += io->len;
+ io->len = 0;
+
+ m->aiocb = ide_issue_trim(blk, (offset >> 9), &io->iov, (bytes >> 9),
+ cb, io);
+}
+
+static void pmac_ide_atapi_transfer_cb(void *opaque, int ret)
+{
+ DBDMA_io *io = opaque;
+ MACIOIDEState *m = io->opaque;
+ IDEState *s = idebus_active_if(&m->bus);
+ int64_t offset;
+
+ MACIO_DPRINTF("pmac_ide_atapi_transfer_cb\n");
+
+ if (ret < 0) {
+ MACIO_DPRINTF("DMA error: %d\n", ret);
+ ide_atapi_io_error(s, ret);
+ goto done;
+ }
+
+ if (!m->dma_active) {
+ MACIO_DPRINTF("waiting for data (%#x - %#x - %x)\n",
+ s->nsector, io->len, s->status);
+ /* data not ready yet, wait for the channel to get restarted */
+ io->processing = false;
+ return;
+ }
+
+ if (s->io_buffer_size <= 0) {
+ MACIO_DPRINTF("End of IDE transfer\n");
+ ide_atapi_cmd_ok(s);
+ m->dma_active = false;
+ goto done;
+ }
+
+ if (io->len == 0) {
+ MACIO_DPRINTF("End of DMA transfer\n");
+ goto done;
+ }
+
+ if (s->lba == -1) {
+ /* Non-block ATAPI transfer - just copy to RAM */
+ s->io_buffer_size = MIN(s->io_buffer_size, io->len);
+ cpu_physical_memory_write(io->addr, s->io_buffer, s->io_buffer_size);
+ ide_atapi_cmd_ok(s);
+ m->dma_active = false;
+ goto done;
+ }
+
+ /* Calculate current offset */
+ offset = (int64_t)(s->lba << 11) + s->io_buffer_index;
+
+ pmac_dma_read(s->blk, offset, io->len, pmac_ide_atapi_transfer_cb, io);
+ return;
+
+done:
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ io->dma_end(opaque);
+
+ return;
+}
+
+static void pmac_ide_transfer_cb(void *opaque, int ret)
+{
+ DBDMA_io *io = opaque;
+ MACIOIDEState *m = io->opaque;
+ IDEState *s = idebus_active_if(&m->bus);
+ int64_t offset;
+
+ MACIO_DPRINTF("pmac_ide_transfer_cb\n");
+
+ if (ret < 0) {
+ MACIO_DPRINTF("DMA error: %d\n", ret);
+ m->aiocb = NULL;
+ ide_dma_error(s);
+ goto done;
+ }
+
+ if (!m->dma_active) {
+ MACIO_DPRINTF("waiting for data (%#x - %#x - %x)\n",
+ s->nsector, io->len, s->status);
+ /* data not ready yet, wait for the channel to get restarted */
+ io->processing = false;
+ return;
+ }
+
+ if (s->io_buffer_size <= 0) {
+ MACIO_DPRINTF("End of IDE transfer\n");
+ s->status = READY_STAT | SEEK_STAT;
+ ide_set_irq(s->bus);
+ m->dma_active = false;
+ goto done;
+ }
+
+ if (io->len == 0) {
+ MACIO_DPRINTF("End of DMA transfer\n");
+ goto done;
+ }
+
+ /* Calculate number of sectors */
+ offset = (ide_get_sector(s) << 9) + s->io_buffer_index;
+
+ switch (s->dma_cmd) {
+ case IDE_DMA_READ:
+ pmac_dma_read(s->blk, offset, io->len, pmac_ide_transfer_cb, io);
+ break;
+ case IDE_DMA_WRITE:
+ pmac_dma_write(s->blk, offset, io->len, pmac_ide_transfer_cb, io);
+ break;
+ case IDE_DMA_TRIM:
+ pmac_dma_trim(s->blk, offset, io->len, pmac_ide_transfer_cb, io);
+ break;
+ }
+
+ return;
+
+done:
+ if (s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) {
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ }
+ io->dma_end(opaque);
+}
+
+static void pmac_ide_transfer(DBDMA_io *io)
+{
+ MACIOIDEState *m = io->opaque;
+ IDEState *s = idebus_active_if(&m->bus);
+
+ MACIO_DPRINTF("\n");
+
+ if (s->drive_kind == IDE_CD) {
+ block_acct_start(blk_get_stats(s->blk), &s->acct, io->len,
+ BLOCK_ACCT_READ);
+
+ pmac_ide_atapi_transfer_cb(io, 0);
+ return;
+ }
+
+ switch (s->dma_cmd) {
+ case IDE_DMA_READ:
+ block_acct_start(blk_get_stats(s->blk), &s->acct, io->len,
+ BLOCK_ACCT_READ);
+ break;
+ case IDE_DMA_WRITE:
+ block_acct_start(blk_get_stats(s->blk), &s->acct, io->len,
+ BLOCK_ACCT_WRITE);
+ break;
+ default:
+ break;
+ }
+
+ pmac_ide_transfer_cb(io, 0);
+}
+
+static void pmac_ide_flush(DBDMA_io *io)
+{
+ MACIOIDEState *m = io->opaque;
+
+ if (m->aiocb) {
+ blk_drain_all();
+ }
+}
+
+/* PowerMac IDE memory IO */
+static void pmac_ide_writeb (void *opaque,
+ hwaddr addr, uint32_t val)
+{
+ MACIOIDEState *d = opaque;
+
+ addr = (addr & 0xFFF) >> 4;
+ switch (addr) {
+ case 1 ... 7:
+ ide_ioport_write(&d->bus, addr, val);
+ break;
+ case 8:
+ case 22:
+ ide_cmd_write(&d->bus, 0, val);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint32_t pmac_ide_readb (void *opaque,hwaddr addr)
+{
+ uint8_t retval;
+ MACIOIDEState *d = opaque;
+
+ addr = (addr & 0xFFF) >> 4;
+ switch (addr) {
+ case 1 ... 7:
+ retval = ide_ioport_read(&d->bus, addr);
+ break;
+ case 8:
+ case 22:
+ retval = ide_status_read(&d->bus, 0);
+ break;
+ default:
+ retval = 0xFF;
+ break;
+ }
+ return retval;
+}
+
+static void pmac_ide_writew (void *opaque,
+ hwaddr addr, uint32_t val)
+{
+ MACIOIDEState *d = opaque;
+
+ addr = (addr & 0xFFF) >> 4;
+ val = bswap16(val);
+ if (addr == 0) {
+ ide_data_writew(&d->bus, 0, val);
+ }
+}
+
+static uint32_t pmac_ide_readw (void *opaque,hwaddr addr)
+{
+ uint16_t retval;
+ MACIOIDEState *d = opaque;
+
+ addr = (addr & 0xFFF) >> 4;
+ if (addr == 0) {
+ retval = ide_data_readw(&d->bus, 0);
+ } else {
+ retval = 0xFFFF;
+ }
+ retval = bswap16(retval);
+ return retval;
+}
+
+static void pmac_ide_writel (void *opaque,
+ hwaddr addr, uint32_t val)
+{
+ MACIOIDEState *d = opaque;
+
+ addr = (addr & 0xFFF) >> 4;
+ val = bswap32(val);
+ if (addr == 0) {
+ ide_data_writel(&d->bus, 0, val);
+ }
+}
+
+static uint32_t pmac_ide_readl (void *opaque,hwaddr addr)
+{
+ uint32_t retval;
+ MACIOIDEState *d = opaque;
+
+ addr = (addr & 0xFFF) >> 4;
+ if (addr == 0) {
+ retval = ide_data_readl(&d->bus, 0);
+ } else {
+ retval = 0xFFFFFFFF;
+ }
+ retval = bswap32(retval);
+ return retval;
+}
+
+static const MemoryRegionOps pmac_ide_ops = {
+ .old_mmio = {
+ .write = {
+ pmac_ide_writeb,
+ pmac_ide_writew,
+ pmac_ide_writel,
+ },
+ .read = {
+ pmac_ide_readb,
+ pmac_ide_readw,
+ pmac_ide_readl,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pmac = {
+ .name = "ide",
+ .version_id = 3,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_IDE_BUS(bus, MACIOIDEState),
+ VMSTATE_IDE_DRIVES(bus.ifs, MACIOIDEState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void macio_ide_reset(DeviceState *dev)
+{
+ MACIOIDEState *d = MACIO_IDE(dev);
+
+ ide_bus_reset(&d->bus);
+}
+
+static int ide_nop_int(IDEDMA *dma, int x)
+{
+ return 0;
+}
+
+static int32_t ide_nop_int32(IDEDMA *dma, int32_t l)
+{
+ return 0;
+}
+
+static void ide_dbdma_start(IDEDMA *dma, IDEState *s,
+ BlockCompletionFunc *cb)
+{
+ MACIOIDEState *m = container_of(dma, MACIOIDEState, dma);
+
+ s->io_buffer_index = 0;
+ if (s->drive_kind == IDE_CD) {
+ s->io_buffer_size = s->packet_transfer_size;
+ } else {
+ s->io_buffer_size = s->nsector * BDRV_SECTOR_SIZE;
+ }
+
+ MACIO_DPRINTF("\n\n------------ IDE transfer\n");
+ MACIO_DPRINTF("buffer_size: %x buffer_index: %x\n",
+ s->io_buffer_size, s->io_buffer_index);
+ MACIO_DPRINTF("lba: %x size: %x\n", s->lba, s->io_buffer_size);
+ MACIO_DPRINTF("-------------------------\n");
+
+ m->dma_active = true;
+ DBDMA_kick(m->dbdma);
+}
+
+static const IDEDMAOps dbdma_ops = {
+ .start_dma = ide_dbdma_start,
+ .prepare_buf = ide_nop_int32,
+ .rw_buf = ide_nop_int,
+};
+
+static void macio_ide_realizefn(DeviceState *dev, Error **errp)
+{
+ MACIOIDEState *s = MACIO_IDE(dev);
+
+ ide_init2(&s->bus, s->irq);
+
+ /* Register DMA callbacks */
+ s->dma.ops = &dbdma_ops;
+ s->bus.dma = &s->dma;
+}
+
+static void macio_ide_initfn(Object *obj)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(obj);
+ MACIOIDEState *s = MACIO_IDE(obj);
+
+ ide_bus_new(&s->bus, sizeof(s->bus), DEVICE(obj), 0, 2);
+ memory_region_init_io(&s->mem, obj, &pmac_ide_ops, s, "pmac-ide", 0x1000);
+ sysbus_init_mmio(d, &s->mem);
+ sysbus_init_irq(d, &s->irq);
+ sysbus_init_irq(d, &s->dma_irq);
+}
+
+static void macio_ide_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = macio_ide_realizefn;
+ dc->reset = macio_ide_reset;
+ dc->vmsd = &vmstate_pmac;
+}
+
+static const TypeInfo macio_ide_type_info = {
+ .name = TYPE_MACIO_IDE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MACIOIDEState),
+ .instance_init = macio_ide_initfn,
+ .class_init = macio_ide_class_init,
+};
+
+static void macio_ide_register_types(void)
+{
+ type_register_static(&macio_ide_type_info);
+}
+
+/* hd_table must contain 2 block drivers */
+void macio_ide_init_drives(MACIOIDEState *s, DriveInfo **hd_table)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (hd_table[i]) {
+ ide_create_drive(&s->bus, i, hd_table[i]);
+ }
+ }
+}
+
+void macio_ide_register_dma(MACIOIDEState *s, void *dbdma, int channel)
+{
+ s->dbdma = dbdma;
+ DBDMA_register_channel(dbdma, channel, s->dma_irq,
+ pmac_ide_transfer, pmac_ide_flush, s);
+}
+
+type_init(macio_ide_register_types)
diff --git a/hw/ide/microdrive.c b/hw/ide/microdrive.c
new file mode 100644
index 00000000..6639dd48
--- /dev/null
+++ b/hw/ide/microdrive.c
@@ -0,0 +1,637 @@
+/*
+ * QEMU IDE Emulation: microdrive (CF / PCMCIA)
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+#include <hw/hw.h>
+#include <hw/i386/pc.h>
+#include <hw/pcmcia.h>
+#include "sysemu/block-backend.h"
+#include "sysemu/dma.h"
+
+#include <hw/ide/internal.h>
+
+#define TYPE_MICRODRIVE "microdrive"
+#define MICRODRIVE(obj) OBJECT_CHECK(MicroDriveState, (obj), TYPE_MICRODRIVE)
+
+/***********************************************************/
+/* CF-ATA Microdrive */
+
+#define METADATA_SIZE 0x20
+
+/* DSCM-1XXXX Microdrive hard disk with CF+ II / PCMCIA interface. */
+
+typedef struct MicroDriveState {
+ /*< private >*/
+ PCMCIACardState parent_obj;
+ /*< public >*/
+
+ IDEBus bus;
+ uint32_t attr_base;
+ uint32_t io_base;
+
+ /* Card state */
+ uint8_t opt;
+ uint8_t stat;
+ uint8_t pins;
+
+ uint8_t ctrl;
+ uint16_t io;
+ uint8_t cycle;
+} MicroDriveState;
+
+/* Register bitfields */
+enum md_opt {
+ OPT_MODE_MMAP = 0,
+ OPT_MODE_IOMAP16 = 1,
+ OPT_MODE_IOMAP1 = 2,
+ OPT_MODE_IOMAP2 = 3,
+ OPT_MODE = 0x3f,
+ OPT_LEVIREQ = 0x40,
+ OPT_SRESET = 0x80,
+};
+enum md_cstat {
+ STAT_INT = 0x02,
+ STAT_PWRDWN = 0x04,
+ STAT_XE = 0x10,
+ STAT_IOIS8 = 0x20,
+ STAT_SIGCHG = 0x40,
+ STAT_CHANGED = 0x80,
+};
+enum md_pins {
+ PINS_MRDY = 0x02,
+ PINS_CRDY = 0x20,
+};
+enum md_ctrl {
+ CTRL_IEN = 0x02,
+ CTRL_SRST = 0x04,
+};
+
+static inline void md_interrupt_update(MicroDriveState *s)
+{
+ PCMCIACardState *card = PCMCIA_CARD(s);
+
+ if (card->slot == NULL) {
+ return;
+ }
+
+ qemu_set_irq(card->slot->irq,
+ !(s->stat & STAT_INT) && /* Inverted */
+ !(s->ctrl & (CTRL_IEN | CTRL_SRST)) &&
+ !(s->opt & OPT_SRESET));
+}
+
+static void md_set_irq(void *opaque, int irq, int level)
+{
+ MicroDriveState *s = opaque;
+
+ if (level) {
+ s->stat |= STAT_INT;
+ } else {
+ s->stat &= ~STAT_INT;
+ }
+
+ md_interrupt_update(s);
+}
+
+static void md_reset(DeviceState *dev)
+{
+ MicroDriveState *s = MICRODRIVE(dev);
+
+ s->opt = OPT_MODE_MMAP;
+ s->stat = 0;
+ s->pins = 0;
+ s->cycle = 0;
+ s->ctrl = 0;
+ ide_bus_reset(&s->bus);
+}
+
+static uint8_t md_attr_read(PCMCIACardState *card, uint32_t at)
+{
+ MicroDriveState *s = MICRODRIVE(card);
+ PCMCIACardClass *pcc = PCMCIA_CARD_GET_CLASS(card);
+
+ if (at < s->attr_base) {
+ if (at < pcc->cis_len) {
+ return pcc->cis[at];
+ } else {
+ return 0x00;
+ }
+ }
+
+ at -= s->attr_base;
+
+ switch (at) {
+ case 0x00: /* Configuration Option Register */
+ return s->opt;
+ case 0x02: /* Card Configuration Status Register */
+ if (s->ctrl & CTRL_IEN) {
+ return s->stat & ~STAT_INT;
+ } else {
+ return s->stat;
+ }
+ case 0x04: /* Pin Replacement Register */
+ return (s->pins & PINS_CRDY) | 0x0c;
+ case 0x06: /* Socket and Copy Register */
+ return 0x00;
+#ifdef VERBOSE
+ default:
+ printf("%s: Bad attribute space register %02x\n", __FUNCTION__, at);
+#endif
+ }
+
+ return 0;
+}
+
+static void md_attr_write(PCMCIACardState *card, uint32_t at, uint8_t value)
+{
+ MicroDriveState *s = MICRODRIVE(card);
+
+ at -= s->attr_base;
+
+ switch (at) {
+ case 0x00: /* Configuration Option Register */
+ s->opt = value & 0xcf;
+ if (value & OPT_SRESET) {
+ device_reset(DEVICE(s));
+ }
+ md_interrupt_update(s);
+ break;
+ case 0x02: /* Card Configuration Status Register */
+ if ((s->stat ^ value) & STAT_PWRDWN) {
+ s->pins |= PINS_CRDY;
+ }
+ s->stat &= 0x82;
+ s->stat |= value & 0x74;
+ md_interrupt_update(s);
+ /* Word 170 in Identify Device must be equal to STAT_XE */
+ break;
+ case 0x04: /* Pin Replacement Register */
+ s->pins &= PINS_CRDY;
+ s->pins |= value & PINS_MRDY;
+ break;
+ case 0x06: /* Socket and Copy Register */
+ break;
+ default:
+ printf("%s: Bad attribute space register %02x\n", __FUNCTION__, at);
+ }
+}
+
+static uint16_t md_common_read(PCMCIACardState *card, uint32_t at)
+{
+ MicroDriveState *s = MICRODRIVE(card);
+ IDEState *ifs;
+ uint16_t ret;
+ at -= s->io_base;
+
+ switch (s->opt & OPT_MODE) {
+ case OPT_MODE_MMAP:
+ if ((at & ~0x3ff) == 0x400) {
+ at = 0;
+ }
+ break;
+ case OPT_MODE_IOMAP16:
+ at &= 0xf;
+ break;
+ case OPT_MODE_IOMAP1:
+ if ((at & ~0xf) == 0x3f0) {
+ at -= 0x3e8;
+ } else if ((at & ~0xf) == 0x1f0) {
+ at -= 0x1f0;
+ }
+ break;
+ case OPT_MODE_IOMAP2:
+ if ((at & ~0xf) == 0x370) {
+ at -= 0x368;
+ } else if ((at & ~0xf) == 0x170) {
+ at -= 0x170;
+ }
+ }
+
+ switch (at) {
+ case 0x0: /* Even RD Data */
+ case 0x8:
+ return ide_data_readw(&s->bus, 0);
+
+ /* TODO: 8-bit accesses */
+ if (s->cycle) {
+ ret = s->io >> 8;
+ } else {
+ s->io = ide_data_readw(&s->bus, 0);
+ ret = s->io & 0xff;
+ }
+ s->cycle = !s->cycle;
+ return ret;
+ case 0x9: /* Odd RD Data */
+ return s->io >> 8;
+ case 0xd: /* Error */
+ return ide_ioport_read(&s->bus, 0x1);
+ case 0xe: /* Alternate Status */
+ ifs = idebus_active_if(&s->bus);
+ if (ifs->blk) {
+ return ifs->status;
+ } else {
+ return 0;
+ }
+ case 0xf: /* Device Address */
+ ifs = idebus_active_if(&s->bus);
+ return 0xc2 | ((~ifs->select << 2) & 0x3c);
+ default:
+ return ide_ioport_read(&s->bus, at);
+ }
+
+ return 0;
+}
+
+static void md_common_write(PCMCIACardState *card, uint32_t at, uint16_t value)
+{
+ MicroDriveState *s = MICRODRIVE(card);
+ at -= s->io_base;
+
+ switch (s->opt & OPT_MODE) {
+ case OPT_MODE_MMAP:
+ if ((at & ~0x3ff) == 0x400) {
+ at = 0;
+ }
+ break;
+ case OPT_MODE_IOMAP16:
+ at &= 0xf;
+ break;
+ case OPT_MODE_IOMAP1:
+ if ((at & ~0xf) == 0x3f0) {
+ at -= 0x3e8;
+ } else if ((at & ~0xf) == 0x1f0) {
+ at -= 0x1f0;
+ }
+ break;
+ case OPT_MODE_IOMAP2:
+ if ((at & ~0xf) == 0x370) {
+ at -= 0x368;
+ } else if ((at & ~0xf) == 0x170) {
+ at -= 0x170;
+ }
+ }
+
+ switch (at) {
+ case 0x0: /* Even WR Data */
+ case 0x8:
+ ide_data_writew(&s->bus, 0, value);
+ break;
+
+ /* TODO: 8-bit accesses */
+ if (s->cycle) {
+ ide_data_writew(&s->bus, 0, s->io | (value << 8));
+ } else {
+ s->io = value & 0xff;
+ }
+ s->cycle = !s->cycle;
+ break;
+ case 0x9:
+ s->io = value & 0xff;
+ s->cycle = !s->cycle;
+ break;
+ case 0xd: /* Features */
+ ide_ioport_write(&s->bus, 0x1, value);
+ break;
+ case 0xe: /* Device Control */
+ s->ctrl = value;
+ if (value & CTRL_SRST) {
+ device_reset(DEVICE(s));
+ }
+ md_interrupt_update(s);
+ break;
+ default:
+ if (s->stat & STAT_PWRDWN) {
+ s->pins |= PINS_CRDY;
+ s->stat &= ~STAT_PWRDWN;
+ }
+ ide_ioport_write(&s->bus, at, value);
+ }
+}
+
+static const VMStateDescription vmstate_microdrive = {
+ .name = "microdrive",
+ .version_id = 3,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(opt, MicroDriveState),
+ VMSTATE_UINT8(stat, MicroDriveState),
+ VMSTATE_UINT8(pins, MicroDriveState),
+ VMSTATE_UINT8(ctrl, MicroDriveState),
+ VMSTATE_UINT16(io, MicroDriveState),
+ VMSTATE_UINT8(cycle, MicroDriveState),
+ VMSTATE_IDE_BUS(bus, MicroDriveState),
+ VMSTATE_IDE_DRIVES(bus.ifs, MicroDriveState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const uint8_t dscm1xxxx_cis[0x14a] = {
+ [0x000] = CISTPL_DEVICE, /* 5V Device Information */
+ [0x002] = 0x03, /* Tuple length = 4 bytes */
+ [0x004] = 0xdb, /* ID: DTYPE_FUNCSPEC, non WP, DSPEED_150NS */
+ [0x006] = 0x01, /* Size = 2K bytes */
+ [0x008] = CISTPL_ENDMARK,
+
+ [0x00a] = CISTPL_DEVICE_OC, /* Additional Device Information */
+ [0x00c] = 0x04, /* Tuple length = 4 byest */
+ [0x00e] = 0x03, /* Conditions: Ext = 0, Vcc 3.3V, MWAIT = 1 */
+ [0x010] = 0xdb, /* ID: DTYPE_FUNCSPEC, non WP, DSPEED_150NS */
+ [0x012] = 0x01, /* Size = 2K bytes */
+ [0x014] = CISTPL_ENDMARK,
+
+ [0x016] = CISTPL_JEDEC_C, /* JEDEC ID */
+ [0x018] = 0x02, /* Tuple length = 2 bytes */
+ [0x01a] = 0xdf, /* PC Card ATA with no Vpp required */
+ [0x01c] = 0x01,
+
+ [0x01e] = CISTPL_MANFID, /* Manufacture ID */
+ [0x020] = 0x04, /* Tuple length = 4 bytes */
+ [0x022] = 0xa4, /* TPLMID_MANF = 00a4 (IBM) */
+ [0x024] = 0x00,
+ [0x026] = 0x00, /* PLMID_CARD = 0000 */
+ [0x028] = 0x00,
+
+ [0x02a] = CISTPL_VERS_1, /* Level 1 Version */
+ [0x02c] = 0x12, /* Tuple length = 23 bytes */
+ [0x02e] = 0x04, /* Major Version = JEIDA 4.2 / PCMCIA 2.1 */
+ [0x030] = 0x01, /* Minor Version = 1 */
+ [0x032] = 'I',
+ [0x034] = 'B',
+ [0x036] = 'M',
+ [0x038] = 0x00,
+ [0x03a] = 'm',
+ [0x03c] = 'i',
+ [0x03e] = 'c',
+ [0x040] = 'r',
+ [0x042] = 'o',
+ [0x044] = 'd',
+ [0x046] = 'r',
+ [0x048] = 'i',
+ [0x04a] = 'v',
+ [0x04c] = 'e',
+ [0x04e] = 0x00,
+ [0x050] = CISTPL_ENDMARK,
+
+ [0x052] = CISTPL_FUNCID, /* Function ID */
+ [0x054] = 0x02, /* Tuple length = 2 bytes */
+ [0x056] = 0x04, /* TPLFID_FUNCTION = Fixed Disk */
+ [0x058] = 0x01, /* TPLFID_SYSINIT: POST = 1, ROM = 0 */
+
+ [0x05a] = CISTPL_FUNCE, /* Function Extension */
+ [0x05c] = 0x02, /* Tuple length = 2 bytes */
+ [0x05e] = 0x01, /* TPLFE_TYPE = Disk Device Interface */
+ [0x060] = 0x01, /* TPLFE_DATA = PC Card ATA Interface */
+
+ [0x062] = CISTPL_FUNCE, /* Function Extension */
+ [0x064] = 0x03, /* Tuple length = 3 bytes */
+ [0x066] = 0x02, /* TPLFE_TYPE = Basic PC Card ATA Interface */
+ [0x068] = 0x08, /* TPLFE_DATA: Rotating, Unique, Single */
+ [0x06a] = 0x0f, /* TPLFE_DATA: Sleep, Standby, Idle, Auto */
+
+ [0x06c] = CISTPL_CONFIG, /* Configuration */
+ [0x06e] = 0x05, /* Tuple length = 5 bytes */
+ [0x070] = 0x01, /* TPCC_RASZ = 2 bytes, TPCC_RMSZ = 1 byte */
+ [0x072] = 0x07, /* TPCC_LAST = 7 */
+ [0x074] = 0x00, /* TPCC_RADR = 0200 */
+ [0x076] = 0x02,
+ [0x078] = 0x0f, /* TPCC_RMSK = 200, 202, 204, 206 */
+
+ [0x07a] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
+ [0x07c] = 0x0b, /* Tuple length = 11 bytes */
+ [0x07e] = 0xc0, /* TPCE_INDX = Memory Mode, Default, Iface */
+ [0x080] = 0xc0, /* TPCE_IF = Memory, no BVDs, no WP, READY */
+ [0x082] = 0xa1, /* TPCE_FS = Vcc only, no I/O, Memory, Misc */
+ [0x084] = 0x27, /* NomV = 1, MinV = 1, MaxV = 1, Peakl = 1 */
+ [0x086] = 0x55, /* NomV: 5.0 V */
+ [0x088] = 0x4d, /* MinV: 4.5 V */
+ [0x08a] = 0x5d, /* MaxV: 5.5 V */
+ [0x08c] = 0x4e, /* Peakl: 450 mA */
+ [0x08e] = 0x08, /* TPCE_MS = 1 window, 1 byte, Host address */
+ [0x090] = 0x00, /* Window descriptor: Window length = 0 */
+ [0x092] = 0x20, /* TPCE_MI: support power down mode, RW */
+
+ [0x094] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
+ [0x096] = 0x06, /* Tuple length = 6 bytes */
+ [0x098] = 0x00, /* TPCE_INDX = Memory Mode, no Default */
+ [0x09a] = 0x01, /* TPCE_FS = Vcc only, no I/O, no Memory */
+ [0x09c] = 0x21, /* NomV = 1, MinV = 0, MaxV = 0, Peakl = 1 */
+ [0x09e] = 0xb5, /* NomV: 3.3 V */
+ [0x0a0] = 0x1e,
+ [0x0a2] = 0x3e, /* Peakl: 350 mA */
+
+ [0x0a4] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
+ [0x0a6] = 0x0d, /* Tuple length = 13 bytes */
+ [0x0a8] = 0xc1, /* TPCE_INDX = I/O and Memory Mode, Default */
+ [0x0aa] = 0x41, /* TPCE_IF = I/O and Memory, no BVD, no WP */
+ [0x0ac] = 0x99, /* TPCE_FS = Vcc only, I/O, Interrupt, Misc */
+ [0x0ae] = 0x27, /* NomV = 1, MinV = 1, MaxV = 1, Peakl = 1 */
+ [0x0b0] = 0x55, /* NomV: 5.0 V */
+ [0x0b2] = 0x4d, /* MinV: 4.5 V */
+ [0x0b4] = 0x5d, /* MaxV: 5.5 V */
+ [0x0b6] = 0x4e, /* Peakl: 450 mA */
+ [0x0b8] = 0x64, /* TPCE_IO = 16-byte boundary, 16/8 accesses */
+ [0x0ba] = 0xf0, /* TPCE_IR = MASK, Level, Pulse, Share */
+ [0x0bc] = 0xff, /* IRQ0..IRQ7 supported */
+ [0x0be] = 0xff, /* IRQ8..IRQ15 supported */
+ [0x0c0] = 0x20, /* TPCE_MI = support power down mode */
+
+ [0x0c2] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
+ [0x0c4] = 0x06, /* Tuple length = 6 bytes */
+ [0x0c6] = 0x01, /* TPCE_INDX = I/O and Memory Mode */
+ [0x0c8] = 0x01, /* TPCE_FS = Vcc only, no I/O, no Memory */
+ [0x0ca] = 0x21, /* NomV = 1, MinV = 0, MaxV = 0, Peakl = 1 */
+ [0x0cc] = 0xb5, /* NomV: 3.3 V */
+ [0x0ce] = 0x1e,
+ [0x0d0] = 0x3e, /* Peakl: 350 mA */
+
+ [0x0d2] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
+ [0x0d4] = 0x12, /* Tuple length = 18 bytes */
+ [0x0d6] = 0xc2, /* TPCE_INDX = I/O Primary Mode */
+ [0x0d8] = 0x41, /* TPCE_IF = I/O and Memory, no BVD, no WP */
+ [0x0da] = 0x99, /* TPCE_FS = Vcc only, I/O, Interrupt, Misc */
+ [0x0dc] = 0x27, /* NomV = 1, MinV = 1, MaxV = 1, Peakl = 1 */
+ [0x0de] = 0x55, /* NomV: 5.0 V */
+ [0x0e0] = 0x4d, /* MinV: 4.5 V */
+ [0x0e2] = 0x5d, /* MaxV: 5.5 V */
+ [0x0e4] = 0x4e, /* Peakl: 450 mA */
+ [0x0e6] = 0xea, /* TPCE_IO = 1K boundary, 16/8 access, Range */
+ [0x0e8] = 0x61, /* Range: 2 fields, 2 bytes addr, 1 byte len */
+ [0x0ea] = 0xf0, /* Field 1 address = 0x01f0 */
+ [0x0ec] = 0x01,
+ [0x0ee] = 0x07, /* Address block length = 8 */
+ [0x0f0] = 0xf6, /* Field 2 address = 0x03f6 */
+ [0x0f2] = 0x03,
+ [0x0f4] = 0x01, /* Address block length = 2 */
+ [0x0f6] = 0xee, /* TPCE_IR = IRQ E, Level, Pulse, Share */
+ [0x0f8] = 0x20, /* TPCE_MI = support power down mode */
+
+ [0x0fa] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
+ [0x0fc] = 0x06, /* Tuple length = 6 bytes */
+ [0x0fe] = 0x02, /* TPCE_INDX = I/O Primary Mode, no Default */
+ [0x100] = 0x01, /* TPCE_FS = Vcc only, no I/O, no Memory */
+ [0x102] = 0x21, /* NomV = 1, MinV = 0, MaxV = 0, Peakl = 1 */
+ [0x104] = 0xb5, /* NomV: 3.3 V */
+ [0x106] = 0x1e,
+ [0x108] = 0x3e, /* Peakl: 350 mA */
+
+ [0x10a] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
+ [0x10c] = 0x12, /* Tuple length = 18 bytes */
+ [0x10e] = 0xc3, /* TPCE_INDX = I/O Secondary Mode, Default */
+ [0x110] = 0x41, /* TPCE_IF = I/O and Memory, no BVD, no WP */
+ [0x112] = 0x99, /* TPCE_FS = Vcc only, I/O, Interrupt, Misc */
+ [0x114] = 0x27, /* NomV = 1, MinV = 1, MaxV = 1, Peakl = 1 */
+ [0x116] = 0x55, /* NomV: 5.0 V */
+ [0x118] = 0x4d, /* MinV: 4.5 V */
+ [0x11a] = 0x5d, /* MaxV: 5.5 V */
+ [0x11c] = 0x4e, /* Peakl: 450 mA */
+ [0x11e] = 0xea, /* TPCE_IO = 1K boundary, 16/8 access, Range */
+ [0x120] = 0x61, /* Range: 2 fields, 2 byte addr, 1 byte len */
+ [0x122] = 0x70, /* Field 1 address = 0x0170 */
+ [0x124] = 0x01,
+ [0x126] = 0x07, /* Address block length = 8 */
+ [0x128] = 0x76, /* Field 2 address = 0x0376 */
+ [0x12a] = 0x03,
+ [0x12c] = 0x01, /* Address block length = 2 */
+ [0x12e] = 0xee, /* TPCE_IR = IRQ E, Level, Pulse, Share */
+ [0x130] = 0x20, /* TPCE_MI = support power down mode */
+
+ [0x132] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
+ [0x134] = 0x06, /* Tuple length = 6 bytes */
+ [0x136] = 0x03, /* TPCE_INDX = I/O Secondary Mode */
+ [0x138] = 0x01, /* TPCE_FS = Vcc only, no I/O, no Memory */
+ [0x13a] = 0x21, /* NomV = 1, MinV = 0, MaxV = 0, Peakl = 1 */
+ [0x13c] = 0xb5, /* NomV: 3.3 V */
+ [0x13e] = 0x1e,
+ [0x140] = 0x3e, /* Peakl: 350 mA */
+
+ [0x142] = CISTPL_NO_LINK, /* No Link */
+ [0x144] = 0x00, /* Tuple length = 0 bytes */
+
+ [0x146] = CISTPL_END, /* Tuple End */
+};
+
+#define TYPE_DSCM1XXXX "dscm1xxxx"
+
+static int dscm1xxxx_attach(PCMCIACardState *card)
+{
+ MicroDriveState *md = MICRODRIVE(card);
+ PCMCIACardClass *pcc = PCMCIA_CARD_GET_CLASS(card);
+
+ md->attr_base = pcc->cis[0x74] | (pcc->cis[0x76] << 8);
+ md->io_base = 0x0;
+
+ device_reset(DEVICE(md));
+ md_interrupt_update(md);
+
+ return 0;
+}
+
+static int dscm1xxxx_detach(PCMCIACardState *card)
+{
+ MicroDriveState *md = MICRODRIVE(card);
+
+ device_reset(DEVICE(md));
+ return 0;
+}
+
+PCMCIACardState *dscm1xxxx_init(DriveInfo *dinfo)
+{
+ MicroDriveState *md;
+
+ md = MICRODRIVE(object_new(TYPE_DSCM1XXXX));
+ qdev_init_nofail(DEVICE(md));
+
+ if (dinfo != NULL) {
+ ide_create_drive(&md->bus, 0, dinfo);
+ }
+ md->bus.ifs[0].drive_kind = IDE_CFATA;
+ md->bus.ifs[0].mdata_size = METADATA_SIZE;
+ md->bus.ifs[0].mdata_storage = g_malloc0(METADATA_SIZE);
+
+ return PCMCIA_CARD(md);
+}
+
+static void dscm1xxxx_class_init(ObjectClass *oc, void *data)
+{
+ PCMCIACardClass *pcc = PCMCIA_CARD_CLASS(oc);
+
+ pcc->cis = dscm1xxxx_cis;
+ pcc->cis_len = sizeof(dscm1xxxx_cis);
+
+ pcc->attach = dscm1xxxx_attach;
+ pcc->detach = dscm1xxxx_detach;
+}
+
+static const TypeInfo dscm1xxxx_type_info = {
+ .name = TYPE_DSCM1XXXX,
+ .parent = TYPE_MICRODRIVE,
+ .class_init = dscm1xxxx_class_init,
+};
+
+static void microdrive_realize(DeviceState *dev, Error **errp)
+{
+ MicroDriveState *md = MICRODRIVE(dev);
+
+ ide_init2(&md->bus, qemu_allocate_irq(md_set_irq, md, 0));
+}
+
+static void microdrive_init(Object *obj)
+{
+ MicroDriveState *md = MICRODRIVE(obj);
+
+ ide_bus_new(&md->bus, sizeof(md->bus), DEVICE(obj), 0, 1);
+}
+
+static void microdrive_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ PCMCIACardClass *pcc = PCMCIA_CARD_CLASS(oc);
+
+ pcc->attr_read = md_attr_read;
+ pcc->attr_write = md_attr_write;
+ pcc->common_read = md_common_read;
+ pcc->common_write = md_common_write;
+ pcc->io_read = md_common_read;
+ pcc->io_write = md_common_write;
+
+ dc->realize = microdrive_realize;
+ dc->reset = md_reset;
+ dc->vmsd = &vmstate_microdrive;
+}
+
+static const TypeInfo microdrive_type_info = {
+ .name = TYPE_MICRODRIVE,
+ .parent = TYPE_PCMCIA_CARD,
+ .instance_size = sizeof(MicroDriveState),
+ .instance_init = microdrive_init,
+ .abstract = true,
+ .class_init = microdrive_class_init,
+};
+
+static void microdrive_register_types(void)
+{
+ type_register_static(&microdrive_type_info);
+ type_register_static(&dscm1xxxx_type_info);
+}
+
+type_init(microdrive_register_types)
diff --git a/hw/ide/mmio.c b/hw/ide/mmio.c
new file mode 100644
index 00000000..b6ce62ac
--- /dev/null
+++ b/hw/ide/mmio.c
@@ -0,0 +1,183 @@
+/*
+ * QEMU IDE Emulation: mmio support (for embedded).
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/dma.h"
+
+#include <hw/ide/internal.h>
+
+/***********************************************************/
+/* MMIO based ide port
+ * This emulates IDE device connected directly to the CPU bus without
+ * dedicated ide controller, which is often seen on embedded boards.
+ */
+
+#define TYPE_MMIO_IDE "mmio-ide"
+#define MMIO_IDE(obj) OBJECT_CHECK(MMIOState, (obj), TYPE_MMIO_IDE)
+
+typedef struct MMIOIDEState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ IDEBus bus;
+
+ uint32_t shift;
+ qemu_irq irq;
+ MemoryRegion iomem1, iomem2;
+} MMIOState;
+
+static void mmio_ide_reset(DeviceState *dev)
+{
+ MMIOState *s = MMIO_IDE(dev);
+
+ ide_bus_reset(&s->bus);
+}
+
+static uint64_t mmio_ide_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MMIOState *s = opaque;
+ addr >>= s->shift;
+ if (addr & 7)
+ return ide_ioport_read(&s->bus, addr);
+ else
+ return ide_data_readw(&s->bus, 0);
+}
+
+static void mmio_ide_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MMIOState *s = opaque;
+ addr >>= s->shift;
+ if (addr & 7)
+ ide_ioport_write(&s->bus, addr, val);
+ else
+ ide_data_writew(&s->bus, 0, val);
+}
+
+static const MemoryRegionOps mmio_ide_ops = {
+ .read = mmio_ide_read,
+ .write = mmio_ide_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t mmio_ide_status_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MMIOState *s= opaque;
+ return ide_status_read(&s->bus, 0);
+}
+
+static void mmio_ide_cmd_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MMIOState *s = opaque;
+ ide_cmd_write(&s->bus, 0, val);
+}
+
+static const MemoryRegionOps mmio_ide_cs_ops = {
+ .read = mmio_ide_status_read,
+ .write = mmio_ide_cmd_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_ide_mmio = {
+ .name = "mmio-ide",
+ .version_id = 3,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_IDE_BUS(bus, MMIOState),
+ VMSTATE_IDE_DRIVES(bus.ifs, MMIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mmio_ide_realizefn(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(dev);
+ MMIOState *s = MMIO_IDE(dev);
+
+ ide_init2(&s->bus, s->irq);
+
+ memory_region_init_io(&s->iomem1, OBJECT(s), &mmio_ide_ops, s,
+ "ide-mmio.1", 16 << s->shift);
+ memory_region_init_io(&s->iomem2, OBJECT(s), &mmio_ide_cs_ops, s,
+ "ide-mmio.2", 2 << s->shift);
+ sysbus_init_mmio(d, &s->iomem1);
+ sysbus_init_mmio(d, &s->iomem2);
+}
+
+static void mmio_ide_initfn(Object *obj)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(obj);
+ MMIOState *s = MMIO_IDE(obj);
+
+ ide_bus_new(&s->bus, sizeof(s->bus), DEVICE(obj), 0, 2);
+ sysbus_init_irq(d, &s->irq);
+}
+
+static Property mmio_ide_properties[] = {
+ DEFINE_PROP_UINT32("shift", MMIOState, shift, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void mmio_ide_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = mmio_ide_realizefn;
+ dc->reset = mmio_ide_reset;
+ dc->props = mmio_ide_properties;
+ dc->vmsd = &vmstate_ide_mmio;
+}
+
+static const TypeInfo mmio_ide_type_info = {
+ .name = TYPE_MMIO_IDE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MMIOState),
+ .instance_init = mmio_ide_initfn,
+ .class_init = mmio_ide_class_init,
+};
+
+static void mmio_ide_register_types(void)
+{
+ type_register_static(&mmio_ide_type_info);
+}
+
+void mmio_ide_init_drives(DeviceState *dev, DriveInfo *hd0, DriveInfo *hd1)
+{
+ MMIOState *s = MMIO_IDE(dev);
+
+ if (hd0 != NULL) {
+ ide_create_drive(&s->bus, 0, hd0);
+ }
+ if (hd1 != NULL) {
+ ide_create_drive(&s->bus, 1, hd1);
+ }
+}
+
+type_init(mmio_ide_register_types)
diff --git a/hw/ide/pci.c b/hw/ide/pci.c
new file mode 100644
index 00000000..d31ff885
--- /dev/null
+++ b/hw/ide/pci.c
@@ -0,0 +1,485 @@
+/*
+ * QEMU IDE Emulation: PCI Bus support.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+#include <hw/hw.h>
+#include <hw/i386/pc.h>
+#include <hw/pci/pci.h>
+#include <hw/isa/isa.h>
+#include "sysemu/block-backend.h"
+#include "sysemu/dma.h"
+#include "qemu/error-report.h"
+#include <hw/ide/pci.h>
+
+#define BMDMA_PAGE_SIZE 4096
+
+#define BM_MIGRATION_COMPAT_STATUS_BITS \
+ (IDE_RETRY_DMA | IDE_RETRY_PIO | \
+ IDE_RETRY_READ | IDE_RETRY_FLUSH)
+
+static void bmdma_start_dma(IDEDMA *dma, IDEState *s,
+ BlockCompletionFunc *dma_cb)
+{
+ BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
+
+ bm->dma_cb = dma_cb;
+ bm->cur_prd_last = 0;
+ bm->cur_prd_addr = 0;
+ bm->cur_prd_len = 0;
+
+ if (bm->status & BM_STATUS_DMAING) {
+ bm->dma_cb(bmdma_active_if(bm), 0);
+ }
+}
+
+/**
+ * Prepare an sglist based on available PRDs.
+ * @limit: How many bytes to prepare total.
+ *
+ * Returns the number of bytes prepared, -1 on error.
+ * IDEState.io_buffer_size will contain the number of bytes described
+ * by the PRDs, whether or not we added them to the sglist.
+ */
+static int32_t bmdma_prepare_buf(IDEDMA *dma, int32_t limit)
+{
+ BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
+ IDEState *s = bmdma_active_if(bm);
+ PCIDevice *pci_dev = PCI_DEVICE(bm->pci_dev);
+ struct {
+ uint32_t addr;
+ uint32_t size;
+ } prd;
+ int l, len;
+
+ pci_dma_sglist_init(&s->sg, pci_dev,
+ s->nsector / (BMDMA_PAGE_SIZE / 512) + 1);
+ s->io_buffer_size = 0;
+ for(;;) {
+ if (bm->cur_prd_len == 0) {
+ /* end of table (with a fail safe of one page) */
+ if (bm->cur_prd_last ||
+ (bm->cur_addr - bm->addr) >= BMDMA_PAGE_SIZE) {
+ return s->sg.size;
+ }
+ pci_dma_read(pci_dev, bm->cur_addr, &prd, 8);
+ bm->cur_addr += 8;
+ prd.addr = le32_to_cpu(prd.addr);
+ prd.size = le32_to_cpu(prd.size);
+ len = prd.size & 0xfffe;
+ if (len == 0)
+ len = 0x10000;
+ bm->cur_prd_len = len;
+ bm->cur_prd_addr = prd.addr;
+ bm->cur_prd_last = (prd.size & 0x80000000);
+ }
+ l = bm->cur_prd_len;
+ if (l > 0) {
+ uint64_t sg_len;
+
+ /* Don't add extra bytes to the SGList; consume any remaining
+ * PRDs from the guest, but ignore them. */
+ sg_len = MIN(limit - s->sg.size, bm->cur_prd_len);
+ if (sg_len) {
+ qemu_sglist_add(&s->sg, bm->cur_prd_addr, sg_len);
+ }
+
+ /* Note: We limit the max transfer to be 2GiB.
+ * This should accommodate the largest ATA transaction
+ * for LBA48 (65,536 sectors) and 32K sector sizes. */
+ if (s->sg.size > INT32_MAX) {
+ error_report("IDE: sglist describes more than 2GiB.");
+ break;
+ }
+ bm->cur_prd_addr += l;
+ bm->cur_prd_len -= l;
+ s->io_buffer_size += l;
+ }
+ }
+
+ qemu_sglist_destroy(&s->sg);
+ s->io_buffer_size = 0;
+ return -1;
+}
+
+/* return 0 if buffer completed */
+static int bmdma_rw_buf(IDEDMA *dma, int is_write)
+{
+ BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
+ IDEState *s = bmdma_active_if(bm);
+ PCIDevice *pci_dev = PCI_DEVICE(bm->pci_dev);
+ struct {
+ uint32_t addr;
+ uint32_t size;
+ } prd;
+ int l, len;
+
+ for(;;) {
+ l = s->io_buffer_size - s->io_buffer_index;
+ if (l <= 0)
+ break;
+ if (bm->cur_prd_len == 0) {
+ /* end of table (with a fail safe of one page) */
+ if (bm->cur_prd_last ||
+ (bm->cur_addr - bm->addr) >= BMDMA_PAGE_SIZE)
+ return 0;
+ pci_dma_read(pci_dev, bm->cur_addr, &prd, 8);
+ bm->cur_addr += 8;
+ prd.addr = le32_to_cpu(prd.addr);
+ prd.size = le32_to_cpu(prd.size);
+ len = prd.size & 0xfffe;
+ if (len == 0)
+ len = 0x10000;
+ bm->cur_prd_len = len;
+ bm->cur_prd_addr = prd.addr;
+ bm->cur_prd_last = (prd.size & 0x80000000);
+ }
+ if (l > bm->cur_prd_len)
+ l = bm->cur_prd_len;
+ if (l > 0) {
+ if (is_write) {
+ pci_dma_write(pci_dev, bm->cur_prd_addr,
+ s->io_buffer + s->io_buffer_index, l);
+ } else {
+ pci_dma_read(pci_dev, bm->cur_prd_addr,
+ s->io_buffer + s->io_buffer_index, l);
+ }
+ bm->cur_prd_addr += l;
+ bm->cur_prd_len -= l;
+ s->io_buffer_index += l;
+ }
+ }
+ return 1;
+}
+
+static void bmdma_set_inactive(IDEDMA *dma, bool more)
+{
+ BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
+
+ bm->dma_cb = NULL;
+ if (more) {
+ bm->status |= BM_STATUS_DMAING;
+ } else {
+ bm->status &= ~BM_STATUS_DMAING;
+ }
+}
+
+static void bmdma_restart_dma(IDEDMA *dma)
+{
+ BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
+
+ bm->cur_addr = bm->addr;
+}
+
+static void bmdma_cancel(BMDMAState *bm)
+{
+ if (bm->status & BM_STATUS_DMAING) {
+ /* cancel DMA request */
+ bmdma_set_inactive(&bm->dma, false);
+ }
+}
+
+static void bmdma_reset(IDEDMA *dma)
+{
+ BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
+
+#ifdef DEBUG_IDE
+ printf("ide: dma_reset\n");
+#endif
+ bmdma_cancel(bm);
+ bm->cmd = 0;
+ bm->status = 0;
+ bm->addr = 0;
+ bm->cur_addr = 0;
+ bm->cur_prd_last = 0;
+ bm->cur_prd_addr = 0;
+ bm->cur_prd_len = 0;
+}
+
+static void bmdma_irq(void *opaque, int n, int level)
+{
+ BMDMAState *bm = opaque;
+
+ if (!level) {
+ /* pass through lower */
+ qemu_set_irq(bm->irq, level);
+ return;
+ }
+
+ bm->status |= BM_STATUS_INT;
+
+ /* trigger the real irq */
+ qemu_set_irq(bm->irq, level);
+}
+
+void bmdma_cmd_writeb(BMDMAState *bm, uint32_t val)
+{
+#ifdef DEBUG_IDE
+ printf("%s: 0x%08x\n", __func__, val);
+#endif
+
+ /* Ignore writes to SSBM if it keeps the old value */
+ if ((val & BM_CMD_START) != (bm->cmd & BM_CMD_START)) {
+ if (!(val & BM_CMD_START)) {
+ /*
+ * We can't cancel Scatter Gather DMA in the middle of the
+ * operation or a partial (not full) DMA transfer would reach
+ * the storage so we wait for completion instead (we beahve
+ * like if the DMA was completed by the time the guest trying
+ * to cancel dma with bmdma_cmd_writeb with BM_CMD_START not
+ * set).
+ *
+ * In the future we'll be able to safely cancel the I/O if the
+ * whole DMA operation will be submitted to disk with a single
+ * aio operation with preadv/pwritev.
+ */
+ if (bm->bus->dma->aiocb) {
+ blk_drain_all();
+ assert(bm->bus->dma->aiocb == NULL);
+ }
+ bm->status &= ~BM_STATUS_DMAING;
+ } else {
+ bm->cur_addr = bm->addr;
+ if (!(bm->status & BM_STATUS_DMAING)) {
+ bm->status |= BM_STATUS_DMAING;
+ /* start dma transfer if possible */
+ if (bm->dma_cb)
+ bm->dma_cb(bmdma_active_if(bm), 0);
+ }
+ }
+ }
+
+ bm->cmd = val & 0x09;
+}
+
+static uint64_t bmdma_addr_read(void *opaque, hwaddr addr,
+ unsigned width)
+{
+ BMDMAState *bm = opaque;
+ uint32_t mask = (1ULL << (width * 8)) - 1;
+ uint64_t data;
+
+ data = (bm->addr >> (addr * 8)) & mask;
+#ifdef DEBUG_IDE
+ printf("%s: 0x%08x\n", __func__, (unsigned)data);
+#endif
+ return data;
+}
+
+static void bmdma_addr_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned width)
+{
+ BMDMAState *bm = opaque;
+ int shift = addr * 8;
+ uint32_t mask = (1ULL << (width * 8)) - 1;
+
+#ifdef DEBUG_IDE
+ printf("%s: 0x%08x\n", __func__, (unsigned)data);
+#endif
+ bm->addr &= ~(mask << shift);
+ bm->addr |= ((data & mask) << shift) & ~3;
+}
+
+MemoryRegionOps bmdma_addr_ioport_ops = {
+ .read = bmdma_addr_read,
+ .write = bmdma_addr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static bool ide_bmdma_current_needed(void *opaque)
+{
+ BMDMAState *bm = opaque;
+
+ return (bm->cur_prd_len != 0);
+}
+
+static bool ide_bmdma_status_needed(void *opaque)
+{
+ BMDMAState *bm = opaque;
+
+ /* Older versions abused some bits in the status register for internal
+ * error state. If any of these bits are set, we must add a subsection to
+ * transfer the real status register */
+ uint8_t abused_bits = BM_MIGRATION_COMPAT_STATUS_BITS;
+
+ return ((bm->status & abused_bits) != 0);
+}
+
+static void ide_bmdma_pre_save(void *opaque)
+{
+ BMDMAState *bm = opaque;
+ uint8_t abused_bits = BM_MIGRATION_COMPAT_STATUS_BITS;
+
+ bm->migration_retry_unit = bm->bus->retry_unit;
+ bm->migration_retry_sector_num = bm->bus->retry_sector_num;
+ bm->migration_retry_nsector = bm->bus->retry_nsector;
+ bm->migration_compat_status =
+ (bm->status & ~abused_bits) | (bm->bus->error_status & abused_bits);
+}
+
+/* This function accesses bm->bus->error_status which is loaded only after
+ * BMDMA itself. This is why the function is called from ide_pci_post_load
+ * instead of being registered with VMState where it would run too early. */
+static int ide_bmdma_post_load(void *opaque, int version_id)
+{
+ BMDMAState *bm = opaque;
+ uint8_t abused_bits = BM_MIGRATION_COMPAT_STATUS_BITS;
+
+ if (bm->status == 0) {
+ bm->status = bm->migration_compat_status & ~abused_bits;
+ bm->bus->error_status |= bm->migration_compat_status & abused_bits;
+ }
+ if (bm->bus->error_status) {
+ bm->bus->retry_sector_num = bm->migration_retry_sector_num;
+ bm->bus->retry_nsector = bm->migration_retry_nsector;
+ bm->bus->retry_unit = bm->migration_retry_unit;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_bmdma_current = {
+ .name = "ide bmdma_current",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ide_bmdma_current_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cur_addr, BMDMAState),
+ VMSTATE_UINT32(cur_prd_last, BMDMAState),
+ VMSTATE_UINT32(cur_prd_addr, BMDMAState),
+ VMSTATE_UINT32(cur_prd_len, BMDMAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_bmdma_status = {
+ .name ="ide bmdma/status",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ide_bmdma_status_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(status, BMDMAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_bmdma = {
+ .name = "ide bmdma",
+ .version_id = 3,
+ .minimum_version_id = 0,
+ .pre_save = ide_bmdma_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(cmd, BMDMAState),
+ VMSTATE_UINT8(migration_compat_status, BMDMAState),
+ VMSTATE_UINT32(addr, BMDMAState),
+ VMSTATE_INT64(migration_retry_sector_num, BMDMAState),
+ VMSTATE_UINT32(migration_retry_nsector, BMDMAState),
+ VMSTATE_UINT8(migration_retry_unit, BMDMAState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_bmdma_current,
+ &vmstate_bmdma_status,
+ NULL
+ }
+};
+
+static int ide_pci_post_load(void *opaque, int version_id)
+{
+ PCIIDEState *d = opaque;
+ int i;
+
+ for(i = 0; i < 2; i++) {
+ /* current versions always store 0/1, but older version
+ stored bigger values. We only need last bit */
+ d->bmdma[i].migration_retry_unit &= 1;
+ ide_bmdma_post_load(&d->bmdma[i], -1);
+ }
+
+ return 0;
+}
+
+const VMStateDescription vmstate_ide_pci = {
+ .name = "ide",
+ .version_id = 3,
+ .minimum_version_id = 0,
+ .post_load = ide_pci_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PCIIDEState),
+ VMSTATE_STRUCT_ARRAY(bmdma, PCIIDEState, 2, 0,
+ vmstate_bmdma, BMDMAState),
+ VMSTATE_IDE_BUS_ARRAY(bus, PCIIDEState, 2),
+ VMSTATE_IDE_DRIVES(bus[0].ifs, PCIIDEState),
+ VMSTATE_IDE_DRIVES(bus[1].ifs, PCIIDEState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+void pci_ide_create_devs(PCIDevice *dev, DriveInfo **hd_table)
+{
+ PCIIDEState *d = PCI_IDE(dev);
+ static const int bus[4] = { 0, 0, 1, 1 };
+ static const int unit[4] = { 0, 1, 0, 1 };
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (hd_table[i] == NULL)
+ continue;
+ ide_create_drive(d->bus+bus[i], unit[i], hd_table[i]);
+ }
+}
+
+static const struct IDEDMAOps bmdma_ops = {
+ .start_dma = bmdma_start_dma,
+ .prepare_buf = bmdma_prepare_buf,
+ .rw_buf = bmdma_rw_buf,
+ .restart_dma = bmdma_restart_dma,
+ .set_inactive = bmdma_set_inactive,
+ .reset = bmdma_reset,
+};
+
+void bmdma_init(IDEBus *bus, BMDMAState *bm, PCIIDEState *d)
+{
+ if (bus->dma == &bm->dma) {
+ return;
+ }
+
+ bm->dma.ops = &bmdma_ops;
+ bus->dma = &bm->dma;
+ bm->irq = bus->irq;
+ bus->irq = qemu_allocate_irq(bmdma_irq, bm, 0);
+ bm->pci_dev = d;
+}
+
+static const TypeInfo pci_ide_type_info = {
+ .name = TYPE_PCI_IDE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIIDEState),
+ .abstract = true,
+};
+
+static void pci_ide_register_types(void)
+{
+ type_register_static(&pci_ide_type_info);
+}
+
+type_init(pci_ide_register_types)
diff --git a/hw/ide/pci.h b/hw/ide/pci.h
new file mode 100644
index 00000000..0f2d4b91
--- /dev/null
+++ b/hw/ide/pci.h
@@ -0,0 +1,76 @@
+#ifndef HW_IDE_PCI_H
+#define HW_IDE_PCI_H
+
+#include <hw/ide/internal.h>
+
+#define BM_STATUS_DMAING 0x01
+#define BM_STATUS_ERROR 0x02
+#define BM_STATUS_INT 0x04
+
+#define BM_CMD_START 0x01
+#define BM_CMD_READ 0x08
+
+typedef struct BMDMAState {
+ IDEDMA dma;
+ uint8_t cmd;
+ uint8_t status;
+ uint32_t addr;
+
+ IDEBus *bus;
+ /* current transfer state */
+ uint32_t cur_addr;
+ uint32_t cur_prd_last;
+ uint32_t cur_prd_addr;
+ uint32_t cur_prd_len;
+ BlockCompletionFunc *dma_cb;
+ MemoryRegion addr_ioport;
+ MemoryRegion extra_io;
+ qemu_irq irq;
+
+ /* Bit 0-2 and 7: BM status register
+ * Bit 3-6: bus->error_status */
+ uint8_t migration_compat_status;
+ uint8_t migration_retry_unit;
+ int64_t migration_retry_sector_num;
+ uint32_t migration_retry_nsector;
+
+ struct PCIIDEState *pci_dev;
+} BMDMAState;
+
+typedef struct CMD646BAR {
+ MemoryRegion cmd;
+ MemoryRegion data;
+ IDEBus *bus;
+ struct PCIIDEState *pci_dev;
+} CMD646BAR;
+
+#define TYPE_PCI_IDE "pci-ide"
+#define PCI_IDE(obj) OBJECT_CHECK(PCIIDEState, (obj), TYPE_PCI_IDE)
+
+typedef struct PCIIDEState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ IDEBus bus[2];
+ BMDMAState bmdma[2];
+ uint32_t secondary; /* used only for cmd646 */
+ MemoryRegion bmdma_bar;
+ CMD646BAR cmd646_bar[2]; /* used only for cmd646 */
+} PCIIDEState;
+
+
+static inline IDEState *bmdma_active_if(BMDMAState *bmdma)
+{
+ assert(bmdma->bus->retry_unit != (uint8_t)-1);
+ return bmdma->bus->ifs + bmdma->bus->retry_unit;
+}
+
+
+void bmdma_init(IDEBus *bus, BMDMAState *bm, PCIIDEState *d);
+void bmdma_cmd_writeb(BMDMAState *bm, uint32_t val);
+extern MemoryRegionOps bmdma_addr_ioport_ops;
+void pci_ide_create_devs(PCIDevice *dev, DriveInfo **hd_table);
+
+extern const VMStateDescription vmstate_ide_pci;
+#endif
diff --git a/hw/ide/piix.c b/hw/ide/piix.c
new file mode 100644
index 00000000..5a26c86a
--- /dev/null
+++ b/hw/ide/piix.c
@@ -0,0 +1,305 @@
+/*
+ * QEMU IDE Emulation: PCI PIIX3/4 support.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * 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.
+ */
+
+#include <hw/hw.h>
+#include <hw/i386/pc.h>
+#include <hw/pci/pci.h>
+#include <hw/isa/isa.h>
+#include "sysemu/block-backend.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/dma.h"
+
+#include <hw/ide/pci.h>
+
+static uint64_t bmdma_read(void *opaque, hwaddr addr, unsigned size)
+{
+ BMDMAState *bm = opaque;
+ uint32_t val;
+
+ if (size != 1) {
+ return ((uint64_t)1 << (size * 8)) - 1;
+ }
+
+ switch(addr & 3) {
+ case 0:
+ val = bm->cmd;
+ break;
+ case 2:
+ val = bm->status;
+ break;
+ default:
+ val = 0xff;
+ break;
+ }
+#ifdef DEBUG_IDE
+ printf("bmdma: readb 0x%02x : 0x%02x\n", (uint8_t)addr, val);
+#endif
+ return val;
+}
+
+static void bmdma_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ BMDMAState *bm = opaque;
+
+ if (size != 1) {
+ return;
+ }
+
+#ifdef DEBUG_IDE
+ printf("bmdma: writeb 0x%02x : 0x%02x\n", (uint8_t)addr, (uint8_t)val);
+#endif
+ switch(addr & 3) {
+ case 0:
+ bmdma_cmd_writeb(bm, val);
+ break;
+ case 2:
+ bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06);
+ break;
+ }
+}
+
+static const MemoryRegionOps piix_bmdma_ops = {
+ .read = bmdma_read,
+ .write = bmdma_write,
+};
+
+static void bmdma_setup_bar(PCIIDEState *d)
+{
+ int i;
+
+ memory_region_init(&d->bmdma_bar, OBJECT(d), "piix-bmdma-container", 16);
+ for(i = 0;i < 2; i++) {
+ BMDMAState *bm = &d->bmdma[i];
+
+ memory_region_init_io(&bm->extra_io, OBJECT(d), &piix_bmdma_ops, bm,
+ "piix-bmdma", 4);
+ memory_region_add_subregion(&d->bmdma_bar, i * 8, &bm->extra_io);
+ memory_region_init_io(&bm->addr_ioport, OBJECT(d),
+ &bmdma_addr_ioport_ops, bm, "bmdma", 4);
+ memory_region_add_subregion(&d->bmdma_bar, i * 8 + 4, &bm->addr_ioport);
+ }
+}
+
+static void piix3_reset(void *opaque)
+{
+ PCIIDEState *d = opaque;
+ PCIDevice *pd = PCI_DEVICE(d);
+ uint8_t *pci_conf = pd->config;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ ide_bus_reset(&d->bus[i]);
+ }
+
+ /* TODO: this is the default. do not override. */
+ pci_conf[PCI_COMMAND] = 0x00;
+ /* TODO: this is the default. do not override. */
+ pci_conf[PCI_COMMAND + 1] = 0x00;
+ /* TODO: use pci_set_word */
+ pci_conf[PCI_STATUS] = PCI_STATUS_FAST_BACK;
+ pci_conf[PCI_STATUS + 1] = PCI_STATUS_DEVSEL_MEDIUM >> 8;
+ pci_conf[0x20] = 0x01; /* BMIBA: 20-23h */
+}
+
+static void pci_piix_init_ports(PCIIDEState *d) {
+ static const struct {
+ int iobase;
+ int iobase2;
+ int isairq;
+ } port_info[] = {
+ {0x1f0, 0x3f6, 14},
+ {0x170, 0x376, 15},
+ };
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ ide_bus_new(&d->bus[i], sizeof(d->bus[i]), DEVICE(d), i, 2);
+ ide_init_ioport(&d->bus[i], NULL, port_info[i].iobase,
+ port_info[i].iobase2);
+ ide_init2(&d->bus[i], isa_get_irq(NULL, port_info[i].isairq));
+
+ bmdma_init(&d->bus[i], &d->bmdma[i], d);
+ d->bmdma[i].bus = &d->bus[i];
+ ide_register_restart_cb(&d->bus[i]);
+ }
+}
+
+static void pci_piix_ide_realize(PCIDevice *dev, Error **errp)
+{
+ PCIIDEState *d = PCI_IDE(dev);
+ uint8_t *pci_conf = dev->config;
+
+ pci_conf[PCI_CLASS_PROG] = 0x80; // legacy ATA mode
+
+ qemu_register_reset(piix3_reset, d);
+
+ bmdma_setup_bar(d);
+ pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &d->bmdma_bar);
+
+ vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
+
+ pci_piix_init_ports(d);
+}
+
+int pci_piix3_xen_ide_unplug(DeviceState *dev)
+{
+ PCIIDEState *pci_ide;
+ DriveInfo *di;
+ int i;
+ IDEDevice *idedev;
+
+ pci_ide = PCI_IDE(dev);
+
+ for (i = 0; i < 4; i++) {
+ di = drive_get_by_index(IF_IDE, i);
+ if (di != NULL && !di->media_cd) {
+ BlockBackend *blk = blk_by_legacy_dinfo(di);
+ DeviceState *ds = blk_get_attached_dev(blk);
+ if (ds) {
+ blk_detach_dev(blk, ds);
+ }
+ pci_ide->bus[di->bus].ifs[di->unit].blk = NULL;
+ if (!(i % 2)) {
+ idedev = pci_ide->bus[di->bus].master;
+ } else {
+ idedev = pci_ide->bus[di->bus].slave;
+ }
+ idedev->conf.blk = NULL;
+ blk_unref(blk);
+ }
+ }
+ qdev_reset_all(DEVICE(dev));
+ return 0;
+}
+
+PCIDevice *pci_piix3_xen_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn)
+{
+ PCIDevice *dev;
+
+ dev = pci_create_simple(bus, devfn, "piix3-ide-xen");
+ pci_ide_create_devs(dev, hd_table);
+ return dev;
+}
+
+static void pci_piix_ide_exitfn(PCIDevice *dev)
+{
+ PCIIDEState *d = PCI_IDE(dev);
+ unsigned i;
+
+ for (i = 0; i < 2; ++i) {
+ memory_region_del_subregion(&d->bmdma_bar, &d->bmdma[i].extra_io);
+ memory_region_del_subregion(&d->bmdma_bar, &d->bmdma[i].addr_ioport);
+ }
+}
+
+/* hd_table must contain 4 block drivers */
+/* NOTE: for the PIIX3, the IRQs and IOports are hardcoded */
+PCIDevice *pci_piix3_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn)
+{
+ PCIDevice *dev;
+
+ dev = pci_create_simple(bus, devfn, "piix3-ide");
+ pci_ide_create_devs(dev, hd_table);
+ return dev;
+}
+
+/* hd_table must contain 4 block drivers */
+/* NOTE: for the PIIX4, the IRQs and IOports are hardcoded */
+PCIDevice *pci_piix4_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn)
+{
+ PCIDevice *dev;
+
+ dev = pci_create_simple(bus, devfn, "piix4-ide");
+ pci_ide_create_devs(dev, hd_table);
+ return dev;
+}
+
+static void piix3_ide_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_piix_ide_realize;
+ k->exit = pci_piix_ide_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82371SB_1;
+ k->class_id = PCI_CLASS_STORAGE_IDE;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo piix3_ide_info = {
+ .name = "piix3-ide",
+ .parent = TYPE_PCI_IDE,
+ .class_init = piix3_ide_class_init,
+};
+
+static void piix3_ide_xen_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_piix_ide_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82371SB_1;
+ k->class_id = PCI_CLASS_STORAGE_IDE;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo piix3_ide_xen_info = {
+ .name = "piix3-ide-xen",
+ .parent = TYPE_PCI_IDE,
+ .class_init = piix3_ide_xen_class_init,
+};
+
+static void piix4_ide_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_piix_ide_realize;
+ k->exit = pci_piix_ide_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82371AB;
+ k->class_id = PCI_CLASS_STORAGE_IDE;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo piix4_ide_info = {
+ .name = "piix4-ide",
+ .parent = TYPE_PCI_IDE,
+ .class_init = piix4_ide_class_init,
+};
+
+static void piix_ide_register_types(void)
+{
+ type_register_static(&piix3_ide_info);
+ type_register_static(&piix3_ide_xen_info);
+ type_register_static(&piix4_ide_info);
+}
+
+type_init(piix_ide_register_types)
diff --git a/hw/ide/qdev.c b/hw/ide/qdev.c
new file mode 100644
index 00000000..788b3613
--- /dev/null
+++ b/hw/ide/qdev.c
@@ -0,0 +1,367 @@
+/*
+ * ide bus support for qdev.
+ *
+ * Copyright (c) 2009 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <hw/hw.h>
+#include "sysemu/dma.h"
+#include "qemu/error-report.h"
+#include <hw/ide/internal.h>
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/block/block.h"
+#include "sysemu/sysemu.h"
+#include "qapi/visitor.h"
+
+/* --------------------------------- */
+
+static char *idebus_get_fw_dev_path(DeviceState *dev);
+
+static Property ide_props[] = {
+ DEFINE_PROP_UINT32("unit", IDEDevice, unit, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ide_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+
+ k->get_fw_dev_path = idebus_get_fw_dev_path;
+}
+
+static const TypeInfo ide_bus_info = {
+ .name = TYPE_IDE_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(IDEBus),
+ .class_init = ide_bus_class_init,
+};
+
+void ide_bus_new(IDEBus *idebus, size_t idebus_size, DeviceState *dev,
+ int bus_id, int max_units)
+{
+ qbus_create_inplace(idebus, idebus_size, TYPE_IDE_BUS, dev, NULL);
+ idebus->bus_id = bus_id;
+ idebus->max_units = max_units;
+}
+
+static char *idebus_get_fw_dev_path(DeviceState *dev)
+{
+ char path[30];
+
+ snprintf(path, sizeof(path), "%s@%x", qdev_fw_name(dev),
+ ((IDEBus*)dev->parent_bus)->bus_id);
+
+ return g_strdup(path);
+}
+
+static int ide_qdev_init(DeviceState *qdev)
+{
+ IDEDevice *dev = IDE_DEVICE(qdev);
+ IDEDeviceClass *dc = IDE_DEVICE_GET_CLASS(dev);
+ IDEBus *bus = DO_UPCAST(IDEBus, qbus, qdev->parent_bus);
+
+ if (!dev->conf.blk) {
+ error_report("No drive specified");
+ goto err;
+ }
+ if (dev->unit == -1) {
+ dev->unit = bus->master ? 1 : 0;
+ }
+
+ if (dev->unit >= bus->max_units) {
+ error_report("Can't create IDE unit %d, bus supports only %d units",
+ dev->unit, bus->max_units);
+ goto err;
+ }
+
+ switch (dev->unit) {
+ case 0:
+ if (bus->master) {
+ error_report("IDE unit %d is in use", dev->unit);
+ goto err;
+ }
+ bus->master = dev;
+ break;
+ case 1:
+ if (bus->slave) {
+ error_report("IDE unit %d is in use", dev->unit);
+ goto err;
+ }
+ bus->slave = dev;
+ break;
+ default:
+ error_report("Invalid IDE unit %d", dev->unit);
+ goto err;
+ }
+ return dc->init(dev);
+
+err:
+ return -1;
+}
+
+IDEDevice *ide_create_drive(IDEBus *bus, int unit, DriveInfo *drive)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->qbus, drive->media_cd ? "ide-cd" : "ide-hd");
+ qdev_prop_set_uint32(dev, "unit", unit);
+ qdev_prop_set_drive_nofail(dev, "drive", blk_by_legacy_dinfo(drive));
+ qdev_init_nofail(dev);
+ return DO_UPCAST(IDEDevice, qdev, dev);
+}
+
+int ide_get_geometry(BusState *bus, int unit,
+ int16_t *cyls, int8_t *heads, int8_t *secs)
+{
+ IDEState *s = &DO_UPCAST(IDEBus, qbus, bus)->ifs[unit];
+
+ if (s->drive_kind != IDE_HD || !s->blk) {
+ return -1;
+ }
+
+ *cyls = s->cylinders;
+ *heads = s->heads;
+ *secs = s->sectors;
+ return 0;
+}
+
+int ide_get_bios_chs_trans(BusState *bus, int unit)
+{
+ return DO_UPCAST(IDEBus, qbus, bus)->ifs[unit].chs_trans;
+}
+
+/* --------------------------------- */
+
+typedef struct IDEDrive {
+ IDEDevice dev;
+} IDEDrive;
+
+static int ide_dev_initfn(IDEDevice *dev, IDEDriveKind kind)
+{
+ IDEBus *bus = DO_UPCAST(IDEBus, qbus, dev->qdev.parent_bus);
+ IDEState *s = bus->ifs + dev->unit;
+ Error *err = NULL;
+
+ if (dev->conf.discard_granularity == -1) {
+ dev->conf.discard_granularity = 512;
+ } else if (dev->conf.discard_granularity &&
+ dev->conf.discard_granularity != 512) {
+ error_report("discard_granularity must be 512 for ide");
+ return -1;
+ }
+
+ blkconf_blocksizes(&dev->conf);
+ if (dev->conf.logical_block_size != 512) {
+ error_report("logical_block_size must be 512 for IDE");
+ return -1;
+ }
+
+ blkconf_serial(&dev->conf, &dev->serial);
+ if (kind != IDE_CD) {
+ blkconf_geometry(&dev->conf, &dev->chs_trans, 65536, 16, 255, &err);
+ if (err) {
+ error_report_err(err);
+ return -1;
+ }
+ }
+
+ if (ide_init_drive(s, dev->conf.blk, kind,
+ dev->version, dev->serial, dev->model, dev->wwn,
+ dev->conf.cyls, dev->conf.heads, dev->conf.secs,
+ dev->chs_trans) < 0) {
+ return -1;
+ }
+
+ if (!dev->version) {
+ dev->version = g_strdup(s->version);
+ }
+ if (!dev->serial) {
+ dev->serial = g_strdup(s->drive_serial_str);
+ }
+
+ add_boot_device_path(dev->conf.bootindex, &dev->qdev,
+ dev->unit ? "/disk@1" : "/disk@0");
+
+ return 0;
+}
+
+static void ide_dev_get_bootindex(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ IDEDevice *d = IDE_DEVICE(obj);
+
+ visit_type_int32(v, &d->conf.bootindex, name, errp);
+}
+
+static void ide_dev_set_bootindex(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ IDEDevice *d = IDE_DEVICE(obj);
+ int32_t boot_index;
+ Error *local_err = NULL;
+
+ visit_type_int32(v, &boot_index, name, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ /* check whether bootindex is present in fw_boot_order list */
+ check_boot_index(boot_index, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ /* change bootindex to a new one */
+ d->conf.bootindex = boot_index;
+
+ if (d->unit != -1) {
+ add_boot_device_path(d->conf.bootindex, &d->qdev,
+ d->unit ? "/disk@1" : "/disk@0");
+ }
+out:
+ if (local_err) {
+ error_propagate(errp, local_err);
+ }
+}
+
+static void ide_dev_instance_init(Object *obj)
+{
+ object_property_add(obj, "bootindex", "int32",
+ ide_dev_get_bootindex,
+ ide_dev_set_bootindex, NULL, NULL, NULL);
+ object_property_set_int(obj, -1, "bootindex", NULL);
+}
+
+static int ide_hd_initfn(IDEDevice *dev)
+{
+ return ide_dev_initfn(dev, IDE_HD);
+}
+
+static int ide_cd_initfn(IDEDevice *dev)
+{
+ return ide_dev_initfn(dev, IDE_CD);
+}
+
+static int ide_drive_initfn(IDEDevice *dev)
+{
+ DriveInfo *dinfo = blk_legacy_dinfo(dev->conf.blk);
+
+ return ide_dev_initfn(dev, dinfo && dinfo->media_cd ? IDE_CD : IDE_HD);
+}
+
+#define DEFINE_IDE_DEV_PROPERTIES() \
+ DEFINE_BLOCK_PROPERTIES(IDEDrive, dev.conf), \
+ DEFINE_PROP_STRING("ver", IDEDrive, dev.version), \
+ DEFINE_PROP_UINT64("wwn", IDEDrive, dev.wwn, 0), \
+ DEFINE_PROP_STRING("serial", IDEDrive, dev.serial),\
+ DEFINE_PROP_STRING("model", IDEDrive, dev.model)
+
+static Property ide_hd_properties[] = {
+ DEFINE_IDE_DEV_PROPERTIES(),
+ DEFINE_BLOCK_CHS_PROPERTIES(IDEDrive, dev.conf),
+ DEFINE_PROP_BIOS_CHS_TRANS("bios-chs-trans",
+ IDEDrive, dev.chs_trans, BIOS_ATA_TRANSLATION_AUTO),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ide_hd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IDEDeviceClass *k = IDE_DEVICE_CLASS(klass);
+ k->init = ide_hd_initfn;
+ dc->fw_name = "drive";
+ dc->desc = "virtual IDE disk";
+ dc->props = ide_hd_properties;
+}
+
+static const TypeInfo ide_hd_info = {
+ .name = "ide-hd",
+ .parent = TYPE_IDE_DEVICE,
+ .instance_size = sizeof(IDEDrive),
+ .class_init = ide_hd_class_init,
+};
+
+static Property ide_cd_properties[] = {
+ DEFINE_IDE_DEV_PROPERTIES(),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ide_cd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IDEDeviceClass *k = IDE_DEVICE_CLASS(klass);
+ k->init = ide_cd_initfn;
+ dc->fw_name = "drive";
+ dc->desc = "virtual IDE CD-ROM";
+ dc->props = ide_cd_properties;
+}
+
+static const TypeInfo ide_cd_info = {
+ .name = "ide-cd",
+ .parent = TYPE_IDE_DEVICE,
+ .instance_size = sizeof(IDEDrive),
+ .class_init = ide_cd_class_init,
+};
+
+static Property ide_drive_properties[] = {
+ DEFINE_IDE_DEV_PROPERTIES(),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ide_drive_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IDEDeviceClass *k = IDE_DEVICE_CLASS(klass);
+ k->init = ide_drive_initfn;
+ dc->fw_name = "drive";
+ dc->desc = "virtual IDE disk or CD-ROM (legacy)";
+ dc->props = ide_drive_properties;
+}
+
+static const TypeInfo ide_drive_info = {
+ .name = "ide-drive",
+ .parent = TYPE_IDE_DEVICE,
+ .instance_size = sizeof(IDEDrive),
+ .class_init = ide_drive_class_init,
+};
+
+static void ide_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->init = ide_qdev_init;
+ set_bit(DEVICE_CATEGORY_STORAGE, k->categories);
+ k->bus_type = TYPE_IDE_BUS;
+ k->props = ide_props;
+}
+
+static const TypeInfo ide_device_type_info = {
+ .name = TYPE_IDE_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(IDEDevice),
+ .abstract = true,
+ .class_size = sizeof(IDEDeviceClass),
+ .class_init = ide_device_class_init,
+ .instance_init = ide_dev_instance_init,
+};
+
+static void ide_register_types(void)
+{
+ type_register_static(&ide_bus_info);
+ type_register_static(&ide_hd_info);
+ type_register_static(&ide_cd_info);
+ type_register_static(&ide_drive_info);
+ type_register_static(&ide_device_type_info);
+}
+
+type_init(ide_register_types)
diff --git a/hw/ide/via.c b/hw/ide/via.c
new file mode 100644
index 00000000..e2da9ef7
--- /dev/null
+++ b/hw/ide/via.c
@@ -0,0 +1,235 @@
+/*
+ * QEMU IDE Emulation: PCI VIA82C686B support.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Copyright (c) 2010 Huacai Chen <zltjiangshi@gmail.com>
+ *
+ * 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.
+ */
+#include <hw/hw.h>
+#include <hw/i386/pc.h>
+#include <hw/pci/pci.h>
+#include <hw/isa/isa.h>
+#include "sysemu/block-backend.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/dma.h"
+
+#include <hw/ide/pci.h>
+
+static uint64_t bmdma_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ BMDMAState *bm = opaque;
+ uint32_t val;
+
+ if (size != 1) {
+ return ((uint64_t)1 << (size * 8)) - 1;
+ }
+
+ switch (addr & 3) {
+ case 0:
+ val = bm->cmd;
+ break;
+ case 2:
+ val = bm->status;
+ break;
+ default:
+ val = 0xff;
+ break;
+ }
+#ifdef DEBUG_IDE
+ printf("bmdma: readb 0x%02x : 0x%02x\n", addr, val);
+#endif
+ return val;
+}
+
+static void bmdma_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ BMDMAState *bm = opaque;
+
+ if (size != 1) {
+ return;
+ }
+
+#ifdef DEBUG_IDE
+ printf("bmdma: writeb 0x%02x : 0x%02x\n", addr, val);
+#endif
+ switch (addr & 3) {
+ case 0:
+ bmdma_cmd_writeb(bm, val);
+ break;
+ case 2:
+ bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06);
+ break;
+ default:;
+ }
+}
+
+static const MemoryRegionOps via_bmdma_ops = {
+ .read = bmdma_read,
+ .write = bmdma_write,
+};
+
+static void bmdma_setup_bar(PCIIDEState *d)
+{
+ int i;
+
+ memory_region_init(&d->bmdma_bar, OBJECT(d), "via-bmdma-container", 16);
+ for(i = 0;i < 2; i++) {
+ BMDMAState *bm = &d->bmdma[i];
+
+ memory_region_init_io(&bm->extra_io, OBJECT(d), &via_bmdma_ops, bm,
+ "via-bmdma", 4);
+ memory_region_add_subregion(&d->bmdma_bar, i * 8, &bm->extra_io);
+ memory_region_init_io(&bm->addr_ioport, OBJECT(d),
+ &bmdma_addr_ioport_ops, bm, "bmdma", 4);
+ memory_region_add_subregion(&d->bmdma_bar, i * 8 + 4, &bm->addr_ioport);
+ }
+}
+
+static void via_reset(void *opaque)
+{
+ PCIIDEState *d = opaque;
+ PCIDevice *pd = PCI_DEVICE(d);
+ uint8_t *pci_conf = pd->config;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ ide_bus_reset(&d->bus[i]);
+ }
+
+ pci_set_word(pci_conf + PCI_COMMAND, PCI_COMMAND_WAIT);
+ pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_FAST_BACK |
+ PCI_STATUS_DEVSEL_MEDIUM);
+
+ pci_set_long(pci_conf + PCI_BASE_ADDRESS_0, 0x000001f0);
+ pci_set_long(pci_conf + PCI_BASE_ADDRESS_1, 0x000003f4);
+ pci_set_long(pci_conf + PCI_BASE_ADDRESS_2, 0x00000170);
+ pci_set_long(pci_conf + PCI_BASE_ADDRESS_3, 0x00000374);
+ pci_set_long(pci_conf + PCI_BASE_ADDRESS_4, 0x0000cc01); /* BMIBA: 20-23h */
+ pci_set_long(pci_conf + PCI_INTERRUPT_LINE, 0x0000010e);
+
+ /* IDE chip enable, IDE configuration 1/2, IDE FIFO Configuration*/
+ pci_set_long(pci_conf + 0x40, 0x0a090600);
+ /* IDE misc configuration 1/2/3 */
+ pci_set_long(pci_conf + 0x44, 0x00c00068);
+ /* IDE Timing control */
+ pci_set_long(pci_conf + 0x48, 0xa8a8a8a8);
+ /* IDE Address Setup Time */
+ pci_set_long(pci_conf + 0x4c, 0x000000ff);
+ /* UltraDMA Extended Timing Control*/
+ pci_set_long(pci_conf + 0x50, 0x07070707);
+ /* UltraDMA FIFO Control */
+ pci_set_long(pci_conf + 0x54, 0x00000004);
+ /* IDE primary sector size */
+ pci_set_long(pci_conf + 0x60, 0x00000200);
+ /* IDE secondary sector size */
+ pci_set_long(pci_conf + 0x68, 0x00000200);
+ /* PCI PM Block */
+ pci_set_long(pci_conf + 0xc0, 0x00020001);
+}
+
+static void vt82c686b_init_ports(PCIIDEState *d) {
+ static const struct {
+ int iobase;
+ int iobase2;
+ int isairq;
+ } port_info[] = {
+ {0x1f0, 0x3f6, 14},
+ {0x170, 0x376, 15},
+ };
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ ide_bus_new(&d->bus[i], sizeof(d->bus[i]), DEVICE(d), i, 2);
+ ide_init_ioport(&d->bus[i], NULL, port_info[i].iobase,
+ port_info[i].iobase2);
+ ide_init2(&d->bus[i], isa_get_irq(NULL, port_info[i].isairq));
+
+ bmdma_init(&d->bus[i], &d->bmdma[i], d);
+ d->bmdma[i].bus = &d->bus[i];
+ ide_register_restart_cb(&d->bus[i]);
+ }
+}
+
+/* via ide func */
+static void vt82c686b_ide_realize(PCIDevice *dev, Error **errp)
+{
+ PCIIDEState *d = PCI_IDE(dev);
+ uint8_t *pci_conf = dev->config;
+
+ pci_config_set_prog_interface(pci_conf, 0x8a); /* legacy ATA mode */
+ pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
+
+ qemu_register_reset(via_reset, d);
+ bmdma_setup_bar(d);
+ pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &d->bmdma_bar);
+
+ vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
+
+ vt82c686b_init_ports(d);
+}
+
+static void vt82c686b_ide_exitfn(PCIDevice *dev)
+{
+ PCIIDEState *d = PCI_IDE(dev);
+ unsigned i;
+
+ for (i = 0; i < 2; ++i) {
+ memory_region_del_subregion(&d->bmdma_bar, &d->bmdma[i].extra_io);
+ memory_region_del_subregion(&d->bmdma_bar, &d->bmdma[i].addr_ioport);
+ }
+}
+
+void vt82c686b_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn)
+{
+ PCIDevice *dev;
+
+ dev = pci_create_simple(bus, devfn, "via-ide");
+ pci_ide_create_devs(dev, hd_table);
+}
+
+static void via_ide_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = vt82c686b_ide_realize;
+ k->exit = vt82c686b_ide_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_VIA;
+ k->device_id = PCI_DEVICE_ID_VIA_IDE;
+ k->revision = 0x06;
+ k->class_id = PCI_CLASS_STORAGE_IDE;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo via_ide_info = {
+ .name = "via-ide",
+ .parent = TYPE_PCI_IDE,
+ .class_init = via_ide_class_init,
+};
+
+static void via_ide_register_types(void)
+{
+ type_register_static(&via_ide_info);
+}
+
+type_init(via_ide_register_types)
diff --git a/hw/input/Makefile.objs b/hw/input/Makefile.objs
new file mode 100644
index 00000000..624ba7ea
--- /dev/null
+++ b/hw/input/Makefile.objs
@@ -0,0 +1,19 @@
+common-obj-$(CONFIG_ADB) += adb.o
+common-obj-y += hid.o
+common-obj-$(CONFIG_LM832X) += lm832x.o
+common-obj-$(CONFIG_PCKBD) += pckbd.o
+common-obj-$(CONFIG_PL050) += pl050.o
+common-obj-y += ps2.o
+common-obj-$(CONFIG_STELLARIS_INPUT) += stellaris_input.o
+common-obj-$(CONFIG_TSC2005) += tsc2005.o
+common-obj-$(CONFIG_VMMOUSE) += vmmouse.o
+
+ifeq ($(CONFIG_LINUX),y)
+common-obj-$(CONFIG_VIRTIO) += virtio-input.o
+common-obj-$(CONFIG_VIRTIO) += virtio-input-hid.o
+common-obj-$(CONFIG_VIRTIO) += virtio-input-host.o
+endif
+
+obj-$(CONFIG_MILKYMIST) += milkymist-softusb.o
+obj-$(CONFIG_PXA2XX) += pxa2xx_keypad.o
+obj-$(CONFIG_TSC210X) += tsc210x.o
diff --git a/hw/input/adb.c b/hw/input/adb.c
new file mode 100644
index 00000000..a18eea26
--- /dev/null
+++ b/hw/input/adb.c
@@ -0,0 +1,593 @@
+/*
+ * QEMU ADB support
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/input/adb.h"
+#include "ui/console.h"
+
+/* debug ADB */
+//#define DEBUG_ADB
+
+#ifdef DEBUG_ADB
+#define ADB_DPRINTF(fmt, ...) \
+do { printf("ADB: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define ADB_DPRINTF(fmt, ...)
+#endif
+
+/* ADB commands */
+#define ADB_BUSRESET 0x00
+#define ADB_FLUSH 0x01
+#define ADB_WRITEREG 0x08
+#define ADB_READREG 0x0c
+
+/* ADB device commands */
+#define ADB_CMD_SELF_TEST 0xff
+#define ADB_CMD_CHANGE_ID 0xfe
+#define ADB_CMD_CHANGE_ID_AND_ACT 0xfd
+#define ADB_CMD_CHANGE_ID_AND_ENABLE 0x00
+
+/* ADB default device IDs (upper 4 bits of ADB command byte) */
+#define ADB_DEVID_DONGLE 1
+#define ADB_DEVID_KEYBOARD 2
+#define ADB_DEVID_MOUSE 3
+#define ADB_DEVID_TABLET 4
+#define ADB_DEVID_MODEM 5
+#define ADB_DEVID_MISC 7
+
+/* error codes */
+#define ADB_RET_NOTPRESENT (-2)
+
+static void adb_device_reset(ADBDevice *d)
+{
+ qdev_reset_all(DEVICE(d));
+}
+
+int adb_request(ADBBusState *s, uint8_t *obuf, const uint8_t *buf, int len)
+{
+ ADBDevice *d;
+ int devaddr, cmd, i;
+
+ cmd = buf[0] & 0xf;
+ if (cmd == ADB_BUSRESET) {
+ for(i = 0; i < s->nb_devices; i++) {
+ d = s->devices[i];
+ adb_device_reset(d);
+ }
+ return 0;
+ }
+ devaddr = buf[0] >> 4;
+ for(i = 0; i < s->nb_devices; i++) {
+ d = s->devices[i];
+ if (d->devaddr == devaddr) {
+ ADBDeviceClass *adc = ADB_DEVICE_GET_CLASS(d);
+ return adc->devreq(d, obuf, buf, len);
+ }
+ }
+ return ADB_RET_NOTPRESENT;
+}
+
+/* XXX: move that to cuda ? */
+int adb_poll(ADBBusState *s, uint8_t *obuf)
+{
+ ADBDevice *d;
+ int olen, i;
+ uint8_t buf[1];
+
+ olen = 0;
+ for(i = 0; i < s->nb_devices; i++) {
+ if (s->poll_index >= s->nb_devices)
+ s->poll_index = 0;
+ d = s->devices[s->poll_index];
+ buf[0] = ADB_READREG | (d->devaddr << 4);
+ olen = adb_request(s, obuf + 1, buf, 1);
+ /* if there is data, we poll again the same device */
+ if (olen > 0) {
+ obuf[0] = buf[0];
+ olen++;
+ break;
+ }
+ s->poll_index++;
+ }
+ return olen;
+}
+
+static const TypeInfo adb_bus_type_info = {
+ .name = TYPE_ADB_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(ADBBusState),
+};
+
+static const VMStateDescription vmstate_adb_device = {
+ .name = "adb_device",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(devaddr, ADBDevice),
+ VMSTATE_INT32(handler, ADBDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adb_device_realizefn(DeviceState *dev, Error **errp)
+{
+ ADBDevice *d = ADB_DEVICE(dev);
+ ADBBusState *bus = ADB_BUS(qdev_get_parent_bus(dev));
+
+ if (bus->nb_devices >= MAX_ADB_DEVICES) {
+ return;
+ }
+
+ bus->devices[bus->nb_devices++] = d;
+}
+
+static void adb_device_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = adb_device_realizefn;
+ dc->bus_type = TYPE_ADB_BUS;
+}
+
+static const TypeInfo adb_device_type_info = {
+ .name = TYPE_ADB_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(ADBDevice),
+ .abstract = true,
+ .class_init = adb_device_class_init,
+};
+
+/***************************************************************/
+/* Keyboard ADB device */
+
+#define ADB_KEYBOARD(obj) OBJECT_CHECK(KBDState, (obj), TYPE_ADB_KEYBOARD)
+
+typedef struct KBDState {
+ /*< private >*/
+ ADBDevice parent_obj;
+ /*< public >*/
+
+ uint8_t data[128];
+ int rptr, wptr, count;
+} KBDState;
+
+#define ADB_KEYBOARD_CLASS(class) \
+ OBJECT_CLASS_CHECK(ADBKeyboardClass, (class), TYPE_ADB_KEYBOARD)
+#define ADB_KEYBOARD_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(ADBKeyboardClass, (obj), TYPE_ADB_KEYBOARD)
+
+typedef struct ADBKeyboardClass {
+ /*< private >*/
+ ADBDeviceClass parent_class;
+ /*< public >*/
+
+ DeviceRealize parent_realize;
+} ADBKeyboardClass;
+
+static const uint8_t pc_to_adb_keycode[256] = {
+ 0, 53, 18, 19, 20, 21, 23, 22, 26, 28, 25, 29, 27, 24, 51, 48,
+ 12, 13, 14, 15, 17, 16, 32, 34, 31, 35, 33, 30, 36, 54, 0, 1,
+ 2, 3, 5, 4, 38, 40, 37, 41, 39, 50, 56, 42, 6, 7, 8, 9,
+ 11, 45, 46, 43, 47, 44,123, 67, 58, 49, 57,122,120, 99,118, 96,
+ 97, 98,100,101,109, 71,107, 89, 91, 92, 78, 86, 87, 88, 69, 83,
+ 84, 85, 82, 65, 0, 0, 10,103,111, 0, 0,110, 81, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 94, 0, 93, 0, 0, 0, 0, 0, 0,104,102, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76,125, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,105, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 75, 0, 0,124, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,115, 62,116, 0, 59, 0, 60, 0,119,
+ 61,121,114,117, 0, 0, 0, 0, 0, 0, 0, 55,126, 0,127, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void adb_kbd_put_keycode(void *opaque, int keycode)
+{
+ KBDState *s = opaque;
+
+ if (s->count < sizeof(s->data)) {
+ s->data[s->wptr] = keycode;
+ if (++s->wptr == sizeof(s->data))
+ s->wptr = 0;
+ s->count++;
+ }
+}
+
+static int adb_kbd_poll(ADBDevice *d, uint8_t *obuf)
+{
+ static int ext_keycode;
+ KBDState *s = ADB_KEYBOARD(d);
+ int adb_keycode, keycode;
+ int olen;
+
+ olen = 0;
+ for(;;) {
+ if (s->count == 0)
+ break;
+ keycode = s->data[s->rptr];
+ if (++s->rptr == sizeof(s->data))
+ s->rptr = 0;
+ s->count--;
+
+ if (keycode == 0xe0) {
+ ext_keycode = 1;
+ } else {
+ if (ext_keycode)
+ adb_keycode = pc_to_adb_keycode[keycode | 0x80];
+ else
+ adb_keycode = pc_to_adb_keycode[keycode & 0x7f];
+ obuf[0] = adb_keycode | (keycode & 0x80);
+ /* NOTE: could put a second keycode if needed */
+ obuf[1] = 0xff;
+ olen = 2;
+ ext_keycode = 0;
+ break;
+ }
+ }
+ return olen;
+}
+
+static int adb_kbd_request(ADBDevice *d, uint8_t *obuf,
+ const uint8_t *buf, int len)
+{
+ KBDState *s = ADB_KEYBOARD(d);
+ int cmd, reg, olen;
+
+ if ((buf[0] & 0x0f) == ADB_FLUSH) {
+ /* flush keyboard fifo */
+ s->wptr = s->rptr = s->count = 0;
+ return 0;
+ }
+
+ cmd = buf[0] & 0xc;
+ reg = buf[0] & 0x3;
+ olen = 0;
+ switch(cmd) {
+ case ADB_WRITEREG:
+ switch(reg) {
+ case 2:
+ /* LED status */
+ break;
+ case 3:
+ switch(buf[2]) {
+ case ADB_CMD_SELF_TEST:
+ break;
+ case ADB_CMD_CHANGE_ID:
+ case ADB_CMD_CHANGE_ID_AND_ACT:
+ case ADB_CMD_CHANGE_ID_AND_ENABLE:
+ d->devaddr = buf[1] & 0xf;
+ break;
+ default:
+ /* XXX: check this */
+ d->devaddr = buf[1] & 0xf;
+ d->handler = buf[2];
+ break;
+ }
+ }
+ break;
+ case ADB_READREG:
+ switch(reg) {
+ case 0:
+ olen = adb_kbd_poll(d, obuf);
+ break;
+ case 1:
+ break;
+ case 2:
+ obuf[0] = 0x00; /* XXX: check this */
+ obuf[1] = 0x07; /* led status */
+ olen = 2;
+ break;
+ case 3:
+ obuf[0] = d->handler;
+ obuf[1] = d->devaddr;
+ olen = 2;
+ break;
+ }
+ break;
+ }
+ return olen;
+}
+
+static const VMStateDescription vmstate_adb_kbd = {
+ .name = "adb_kbd",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(parent_obj, KBDState, 0, vmstate_adb_device, ADBDevice),
+ VMSTATE_BUFFER(data, KBDState),
+ VMSTATE_INT32(rptr, KBDState),
+ VMSTATE_INT32(wptr, KBDState),
+ VMSTATE_INT32(count, KBDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adb_kbd_reset(DeviceState *dev)
+{
+ ADBDevice *d = ADB_DEVICE(dev);
+ KBDState *s = ADB_KEYBOARD(dev);
+
+ d->handler = 1;
+ d->devaddr = ADB_DEVID_KEYBOARD;
+ memset(s->data, 0, sizeof(s->data));
+ s->rptr = 0;
+ s->wptr = 0;
+ s->count = 0;
+}
+
+static void adb_kbd_realizefn(DeviceState *dev, Error **errp)
+{
+ ADBDevice *d = ADB_DEVICE(dev);
+ ADBKeyboardClass *akc = ADB_KEYBOARD_GET_CLASS(dev);
+
+ akc->parent_realize(dev, errp);
+
+ qemu_add_kbd_event_handler(adb_kbd_put_keycode, d);
+}
+
+static void adb_kbd_initfn(Object *obj)
+{
+ ADBDevice *d = ADB_DEVICE(obj);
+
+ d->devaddr = ADB_DEVID_KEYBOARD;
+}
+
+static void adb_kbd_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ ADBDeviceClass *adc = ADB_DEVICE_CLASS(oc);
+ ADBKeyboardClass *akc = ADB_KEYBOARD_CLASS(oc);
+
+ akc->parent_realize = dc->realize;
+ dc->realize = adb_kbd_realizefn;
+
+ adc->devreq = adb_kbd_request;
+ dc->reset = adb_kbd_reset;
+ dc->vmsd = &vmstate_adb_kbd;
+}
+
+static const TypeInfo adb_kbd_type_info = {
+ .name = TYPE_ADB_KEYBOARD,
+ .parent = TYPE_ADB_DEVICE,
+ .instance_size = sizeof(KBDState),
+ .instance_init = adb_kbd_initfn,
+ .class_init = adb_kbd_class_init,
+ .class_size = sizeof(ADBKeyboardClass),
+};
+
+/***************************************************************/
+/* Mouse ADB device */
+
+#define ADB_MOUSE(obj) OBJECT_CHECK(MouseState, (obj), TYPE_ADB_MOUSE)
+
+typedef struct MouseState {
+ /*< public >*/
+ ADBDevice parent_obj;
+ /*< private >*/
+
+ int buttons_state, last_buttons_state;
+ int dx, dy, dz;
+} MouseState;
+
+#define ADB_MOUSE_CLASS(class) \
+ OBJECT_CLASS_CHECK(ADBMouseClass, (class), TYPE_ADB_MOUSE)
+#define ADB_MOUSE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(ADBMouseClass, (obj), TYPE_ADB_MOUSE)
+
+typedef struct ADBMouseClass {
+ /*< public >*/
+ ADBDeviceClass parent_class;
+ /*< private >*/
+
+ DeviceRealize parent_realize;
+} ADBMouseClass;
+
+static void adb_mouse_event(void *opaque,
+ int dx1, int dy1, int dz1, int buttons_state)
+{
+ MouseState *s = opaque;
+
+ s->dx += dx1;
+ s->dy += dy1;
+ s->dz += dz1;
+ s->buttons_state = buttons_state;
+}
+
+
+static int adb_mouse_poll(ADBDevice *d, uint8_t *obuf)
+{
+ MouseState *s = ADB_MOUSE(d);
+ int dx, dy;
+
+ if (s->last_buttons_state == s->buttons_state &&
+ s->dx == 0 && s->dy == 0)
+ return 0;
+
+ dx = s->dx;
+ if (dx < -63)
+ dx = -63;
+ else if (dx > 63)
+ dx = 63;
+
+ dy = s->dy;
+ if (dy < -63)
+ dy = -63;
+ else if (dy > 63)
+ dy = 63;
+
+ s->dx -= dx;
+ s->dy -= dy;
+ s->last_buttons_state = s->buttons_state;
+
+ dx &= 0x7f;
+ dy &= 0x7f;
+
+ if (!(s->buttons_state & MOUSE_EVENT_LBUTTON))
+ dy |= 0x80;
+ if (!(s->buttons_state & MOUSE_EVENT_RBUTTON))
+ dx |= 0x80;
+
+ obuf[0] = dy;
+ obuf[1] = dx;
+ return 2;
+}
+
+static int adb_mouse_request(ADBDevice *d, uint8_t *obuf,
+ const uint8_t *buf, int len)
+{
+ MouseState *s = ADB_MOUSE(d);
+ int cmd, reg, olen;
+
+ if ((buf[0] & 0x0f) == ADB_FLUSH) {
+ /* flush mouse fifo */
+ s->buttons_state = s->last_buttons_state;
+ s->dx = 0;
+ s->dy = 0;
+ s->dz = 0;
+ return 0;
+ }
+
+ cmd = buf[0] & 0xc;
+ reg = buf[0] & 0x3;
+ olen = 0;
+ switch(cmd) {
+ case ADB_WRITEREG:
+ ADB_DPRINTF("write reg %d val 0x%2.2x\n", reg, buf[1]);
+ switch(reg) {
+ case 2:
+ break;
+ case 3:
+ switch(buf[2]) {
+ case ADB_CMD_SELF_TEST:
+ break;
+ case ADB_CMD_CHANGE_ID:
+ case ADB_CMD_CHANGE_ID_AND_ACT:
+ case ADB_CMD_CHANGE_ID_AND_ENABLE:
+ d->devaddr = buf[1] & 0xf;
+ break;
+ default:
+ /* XXX: check this */
+ d->devaddr = buf[1] & 0xf;
+ break;
+ }
+ }
+ break;
+ case ADB_READREG:
+ switch(reg) {
+ case 0:
+ olen = adb_mouse_poll(d, obuf);
+ break;
+ case 1:
+ break;
+ case 3:
+ obuf[0] = d->handler;
+ obuf[1] = d->devaddr;
+ olen = 2;
+ break;
+ }
+ ADB_DPRINTF("read reg %d obuf[0] 0x%2.2x obuf[1] 0x%2.2x\n", reg,
+ obuf[0], obuf[1]);
+ break;
+ }
+ return olen;
+}
+
+static void adb_mouse_reset(DeviceState *dev)
+{
+ ADBDevice *d = ADB_DEVICE(dev);
+ MouseState *s = ADB_MOUSE(dev);
+
+ d->handler = 2;
+ d->devaddr = ADB_DEVID_MOUSE;
+ s->last_buttons_state = s->buttons_state = 0;
+ s->dx = s->dy = s->dz = 0;
+}
+
+static const VMStateDescription vmstate_adb_mouse = {
+ .name = "adb_mouse",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(parent_obj, MouseState, 0, vmstate_adb_device,
+ ADBDevice),
+ VMSTATE_INT32(buttons_state, MouseState),
+ VMSTATE_INT32(last_buttons_state, MouseState),
+ VMSTATE_INT32(dx, MouseState),
+ VMSTATE_INT32(dy, MouseState),
+ VMSTATE_INT32(dz, MouseState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adb_mouse_realizefn(DeviceState *dev, Error **errp)
+{
+ MouseState *s = ADB_MOUSE(dev);
+ ADBMouseClass *amc = ADB_MOUSE_GET_CLASS(dev);
+
+ amc->parent_realize(dev, errp);
+
+ qemu_add_mouse_event_handler(adb_mouse_event, s, 0, "QEMU ADB Mouse");
+}
+
+static void adb_mouse_initfn(Object *obj)
+{
+ ADBDevice *d = ADB_DEVICE(obj);
+
+ d->devaddr = ADB_DEVID_MOUSE;
+}
+
+static void adb_mouse_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ ADBDeviceClass *adc = ADB_DEVICE_CLASS(oc);
+ ADBMouseClass *amc = ADB_MOUSE_CLASS(oc);
+
+ amc->parent_realize = dc->realize;
+ dc->realize = adb_mouse_realizefn;
+
+ adc->devreq = adb_mouse_request;
+ dc->reset = adb_mouse_reset;
+ dc->vmsd = &vmstate_adb_mouse;
+}
+
+static const TypeInfo adb_mouse_type_info = {
+ .name = TYPE_ADB_MOUSE,
+ .parent = TYPE_ADB_DEVICE,
+ .instance_size = sizeof(MouseState),
+ .instance_init = adb_mouse_initfn,
+ .class_init = adb_mouse_class_init,
+ .class_size = sizeof(ADBMouseClass),
+};
+
+
+static void adb_register_types(void)
+{
+ type_register_static(&adb_bus_type_info);
+ type_register_static(&adb_device_type_info);
+ type_register_static(&adb_kbd_type_info);
+ type_register_static(&adb_mouse_type_info);
+}
+
+type_init(adb_register_types)
diff --git a/hw/input/hid.c b/hw/input/hid.c
new file mode 100644
index 00000000..21ebd9e7
--- /dev/null
+++ b/hw/input/hid.c
@@ -0,0 +1,611 @@
+/*
+ * QEMU HID devices
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ * Copyright (c) 2007 OpenMoko, Inc. (andrew@openedhand.com)
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "qemu/timer.h"
+#include "hw/input/hid.h"
+
+#define HID_USAGE_ERROR_ROLLOVER 0x01
+#define HID_USAGE_POSTFAIL 0x02
+#define HID_USAGE_ERROR_UNDEFINED 0x03
+
+/* Indices are QEMU keycodes, values are from HID Usage Table. Indices
+ * above 0x80 are for keys that come after 0xe0 or 0xe1+0x1d or 0xe1+0x9d. */
+static const uint8_t hid_usage_keys[0x100] = {
+ 0x00, 0x29, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x2d, 0x2e, 0x2a, 0x2b,
+ 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c,
+ 0x12, 0x13, 0x2f, 0x30, 0x28, 0xe0, 0x04, 0x16,
+ 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x33,
+ 0x34, 0x35, 0xe1, 0x31, 0x1d, 0x1b, 0x06, 0x19,
+ 0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0xe5, 0x55,
+ 0xe2, 0x2c, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+ 0x3f, 0x40, 0x41, 0x42, 0x43, 0x53, 0x47, 0x5f,
+ 0x60, 0x61, 0x56, 0x5c, 0x5d, 0x5e, 0x57, 0x59,
+ 0x5a, 0x5b, 0x62, 0x63, 0x00, 0x00, 0x00, 0x44,
+ 0x45, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
+ 0xe8, 0xe9, 0x71, 0x72, 0x73, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65,
+
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x58, 0xe4, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x46,
+ 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x4a,
+ 0x52, 0x4b, 0x00, 0x50, 0x00, 0x4f, 0x00, 0x4d,
+ 0x51, 0x4e, 0x49, 0x4c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+bool hid_has_events(HIDState *hs)
+{
+ return hs->n > 0 || hs->idle_pending;
+}
+
+static void hid_idle_timer(void *opaque)
+{
+ HIDState *hs = opaque;
+
+ hs->idle_pending = true;
+ hs->event(hs);
+}
+
+static void hid_del_idle_timer(HIDState *hs)
+{
+ if (hs->idle_timer) {
+ timer_del(hs->idle_timer);
+ timer_free(hs->idle_timer);
+ hs->idle_timer = NULL;
+ }
+}
+
+void hid_set_next_idle(HIDState *hs)
+{
+ if (hs->idle) {
+ uint64_t expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ get_ticks_per_sec() * hs->idle * 4 / 1000;
+ if (!hs->idle_timer) {
+ hs->idle_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hid_idle_timer, hs);
+ }
+ timer_mod_ns(hs->idle_timer, expire_time);
+ } else {
+ hid_del_idle_timer(hs);
+ }
+}
+
+static void hid_pointer_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ static const int bmap[INPUT_BUTTON_MAX] = {
+ [INPUT_BUTTON_LEFT] = 0x01,
+ [INPUT_BUTTON_RIGHT] = 0x02,
+ [INPUT_BUTTON_MIDDLE] = 0x04,
+ };
+ HIDState *hs = (HIDState *)dev;
+ HIDPointerEvent *e;
+
+ assert(hs->n < QUEUE_LENGTH);
+ e = &hs->ptr.queue[(hs->head + hs->n) & QUEUE_MASK];
+
+ switch (evt->kind) {
+ case INPUT_EVENT_KIND_REL:
+ if (evt->rel->axis == INPUT_AXIS_X) {
+ e->xdx += evt->rel->value;
+ } else if (evt->rel->axis == INPUT_AXIS_Y) {
+ e->ydy += evt->rel->value;
+ }
+ break;
+
+ case INPUT_EVENT_KIND_ABS:
+ if (evt->rel->axis == INPUT_AXIS_X) {
+ e->xdx = evt->rel->value;
+ } else if (evt->rel->axis == INPUT_AXIS_Y) {
+ e->ydy = evt->rel->value;
+ }
+ break;
+
+ case INPUT_EVENT_KIND_BTN:
+ if (evt->btn->down) {
+ e->buttons_state |= bmap[evt->btn->button];
+ if (evt->btn->button == INPUT_BUTTON_WHEEL_UP) {
+ e->dz--;
+ } else if (evt->btn->button == INPUT_BUTTON_WHEEL_DOWN) {
+ e->dz++;
+ }
+ } else {
+ e->buttons_state &= ~bmap[evt->btn->button];
+ }
+ break;
+
+ default:
+ /* keep gcc happy */
+ break;
+ }
+
+}
+
+static void hid_pointer_sync(DeviceState *dev)
+{
+ HIDState *hs = (HIDState *)dev;
+ HIDPointerEvent *prev, *curr, *next;
+ bool event_compression = false;
+
+ if (hs->n == QUEUE_LENGTH-1) {
+ /*
+ * Queue full. We are losing information, but we at least
+ * keep track of most recent button state.
+ */
+ return;
+ }
+
+ prev = &hs->ptr.queue[(hs->head + hs->n - 1) & QUEUE_MASK];
+ curr = &hs->ptr.queue[(hs->head + hs->n) & QUEUE_MASK];
+ next = &hs->ptr.queue[(hs->head + hs->n + 1) & QUEUE_MASK];
+
+ if (hs->n > 0) {
+ /*
+ * No button state change between previous and current event
+ * (and previous wasn't seen by the guest yet), so there is
+ * motion information only and we can combine the two event
+ * into one.
+ */
+ if (curr->buttons_state == prev->buttons_state) {
+ event_compression = true;
+ }
+ }
+
+ if (event_compression) {
+ /* add current motion to previous, clear current */
+ if (hs->kind == HID_MOUSE) {
+ prev->xdx += curr->xdx;
+ curr->xdx = 0;
+ prev->ydy += curr->ydy;
+ curr->ydy = 0;
+ } else {
+ prev->xdx = curr->xdx;
+ prev->ydy = curr->ydy;
+ }
+ prev->dz += curr->dz;
+ curr->dz = 0;
+ } else {
+ /* prepate next (clear rel, copy abs + btns) */
+ if (hs->kind == HID_MOUSE) {
+ next->xdx = 0;
+ next->ydy = 0;
+ } else {
+ next->xdx = curr->xdx;
+ next->ydy = curr->ydy;
+ }
+ next->dz = 0;
+ next->buttons_state = curr->buttons_state;
+ /* make current guest visible, notify guest */
+ hs->n++;
+ hs->event(hs);
+ }
+}
+
+static void hid_keyboard_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ HIDState *hs = (HIDState *)dev;
+ int scancodes[3], i, count;
+ int slot;
+
+ count = qemu_input_key_value_to_scancode(evt->key->key,
+ evt->key->down,
+ scancodes);
+ if (hs->n + count > QUEUE_LENGTH) {
+ fprintf(stderr, "usb-kbd: warning: key event queue full\n");
+ return;
+ }
+ for (i = 0; i < count; i++) {
+ slot = (hs->head + hs->n) & QUEUE_MASK; hs->n++;
+ hs->kbd.keycodes[slot] = scancodes[i];
+ }
+ hs->event(hs);
+}
+
+static void hid_keyboard_process_keycode(HIDState *hs)
+{
+ uint8_t hid_code, index, key;
+ int i, keycode, slot;
+
+ if (hs->n == 0) {
+ return;
+ }
+ slot = hs->head & QUEUE_MASK; QUEUE_INCR(hs->head); hs->n--;
+ keycode = hs->kbd.keycodes[slot];
+
+ key = keycode & 0x7f;
+ index = key | ((hs->kbd.modifiers & (1 << 8)) >> 1);
+ hid_code = hid_usage_keys[index];
+ hs->kbd.modifiers &= ~(1 << 8);
+
+ switch (hid_code) {
+ case 0x00:
+ return;
+
+ case 0xe0:
+ assert(key == 0x1d);
+ if (hs->kbd.modifiers & (1 << 9)) {
+ /* The hid_codes for the 0xe1/0x1d scancode sequence are 0xe9/0xe0.
+ * Here we're processing the second hid_code. By dropping bit 9
+ * and setting bit 8, the scancode after 0x1d will access the
+ * second half of the table.
+ */
+ hs->kbd.modifiers ^= (1 << 8) | (1 << 9);
+ return;
+ }
+ /* fall through to process Ctrl_L */
+ case 0xe1 ... 0xe7:
+ /* Ctrl_L/Ctrl_R, Shift_L/Shift_R, Alt_L/Alt_R, Win_L/Win_R.
+ * Handle releases here, or fall through to process presses.
+ */
+ if (keycode & (1 << 7)) {
+ hs->kbd.modifiers &= ~(1 << (hid_code & 0x0f));
+ return;
+ }
+ /* fall through */
+ case 0xe8 ... 0xe9:
+ /* USB modifiers are just 1 byte long. Bits 8 and 9 of
+ * hs->kbd.modifiers implement a state machine that detects the
+ * 0xe0 and 0xe1/0x1d sequences. These bits do not follow the
+ * usual rules where bit 7 marks released keys; they are cleared
+ * elsewhere in the function as the state machine dictates.
+ */
+ hs->kbd.modifiers |= 1 << (hid_code & 0x0f);
+ return;
+
+ case 0xea ... 0xef:
+ abort();
+
+ default:
+ break;
+ }
+
+ if (keycode & (1 << 7)) {
+ for (i = hs->kbd.keys - 1; i >= 0; i--) {
+ if (hs->kbd.key[i] == hid_code) {
+ hs->kbd.key[i] = hs->kbd.key[-- hs->kbd.keys];
+ hs->kbd.key[hs->kbd.keys] = 0x00;
+ break;
+ }
+ }
+ if (i < 0) {
+ return;
+ }
+ } else {
+ for (i = hs->kbd.keys - 1; i >= 0; i--) {
+ if (hs->kbd.key[i] == hid_code) {
+ break;
+ }
+ }
+ if (i < 0) {
+ if (hs->kbd.keys < sizeof(hs->kbd.key)) {
+ hs->kbd.key[hs->kbd.keys++] = hid_code;
+ }
+ } else {
+ return;
+ }
+ }
+}
+
+static inline int int_clamp(int val, int vmin, int vmax)
+{
+ if (val < vmin) {
+ return vmin;
+ } else if (val > vmax) {
+ return vmax;
+ } else {
+ return val;
+ }
+}
+
+void hid_pointer_activate(HIDState *hs)
+{
+ if (!hs->ptr.mouse_grabbed) {
+ qemu_input_handler_activate(hs->s);
+ hs->ptr.mouse_grabbed = 1;
+ }
+}
+
+int hid_pointer_poll(HIDState *hs, uint8_t *buf, int len)
+{
+ int dx, dy, dz, l;
+ int index;
+ HIDPointerEvent *e;
+
+ hs->idle_pending = false;
+
+ hid_pointer_activate(hs);
+
+ /* When the buffer is empty, return the last event. Relative
+ movements will all be zero. */
+ index = (hs->n ? hs->head : hs->head - 1);
+ e = &hs->ptr.queue[index & QUEUE_MASK];
+
+ if (hs->kind == HID_MOUSE) {
+ dx = int_clamp(e->xdx, -127, 127);
+ dy = int_clamp(e->ydy, -127, 127);
+ e->xdx -= dx;
+ e->ydy -= dy;
+ } else {
+ dx = e->xdx;
+ dy = e->ydy;
+ }
+ dz = int_clamp(e->dz, -127, 127);
+ e->dz -= dz;
+
+ if (hs->n &&
+ !e->dz &&
+ (hs->kind == HID_TABLET || (!e->xdx && !e->ydy))) {
+ /* that deals with this event */
+ QUEUE_INCR(hs->head);
+ hs->n--;
+ }
+
+ /* Appears we have to invert the wheel direction */
+ dz = 0 - dz;
+ l = 0;
+ switch (hs->kind) {
+ case HID_MOUSE:
+ if (len > l) {
+ buf[l++] = e->buttons_state;
+ }
+ if (len > l) {
+ buf[l++] = dx;
+ }
+ if (len > l) {
+ buf[l++] = dy;
+ }
+ if (len > l) {
+ buf[l++] = dz;
+ }
+ break;
+
+ case HID_TABLET:
+ if (len > l) {
+ buf[l++] = e->buttons_state;
+ }
+ if (len > l) {
+ buf[l++] = dx & 0xff;
+ }
+ if (len > l) {
+ buf[l++] = dx >> 8;
+ }
+ if (len > l) {
+ buf[l++] = dy & 0xff;
+ }
+ if (len > l) {
+ buf[l++] = dy >> 8;
+ }
+ if (len > l) {
+ buf[l++] = dz;
+ }
+ break;
+
+ default:
+ abort();
+ }
+
+ return l;
+}
+
+int hid_keyboard_poll(HIDState *hs, uint8_t *buf, int len)
+{
+ hs->idle_pending = false;
+
+ if (len < 2) {
+ return 0;
+ }
+
+ hid_keyboard_process_keycode(hs);
+
+ buf[0] = hs->kbd.modifiers & 0xff;
+ buf[1] = 0;
+ if (hs->kbd.keys > 6) {
+ memset(buf + 2, HID_USAGE_ERROR_ROLLOVER, MIN(8, len) - 2);
+ } else {
+ memcpy(buf + 2, hs->kbd.key, MIN(8, len) - 2);
+ }
+
+ return MIN(8, len);
+}
+
+int hid_keyboard_write(HIDState *hs, uint8_t *buf, int len)
+{
+ if (len > 0) {
+ int ledstate = 0;
+ /* 0x01: Num Lock LED
+ * 0x02: Caps Lock LED
+ * 0x04: Scroll Lock LED
+ * 0x08: Compose LED
+ * 0x10: Kana LED */
+ hs->kbd.leds = buf[0];
+ if (hs->kbd.leds & 0x04) {
+ ledstate |= QEMU_SCROLL_LOCK_LED;
+ }
+ if (hs->kbd.leds & 0x01) {
+ ledstate |= QEMU_NUM_LOCK_LED;
+ }
+ if (hs->kbd.leds & 0x02) {
+ ledstate |= QEMU_CAPS_LOCK_LED;
+ }
+ kbd_put_ledstate(ledstate);
+ }
+ return 0;
+}
+
+void hid_reset(HIDState *hs)
+{
+ switch (hs->kind) {
+ case HID_KEYBOARD:
+ memset(hs->kbd.keycodes, 0, sizeof(hs->kbd.keycodes));
+ memset(hs->kbd.key, 0, sizeof(hs->kbd.key));
+ hs->kbd.keys = 0;
+ break;
+ case HID_MOUSE:
+ case HID_TABLET:
+ memset(hs->ptr.queue, 0, sizeof(hs->ptr.queue));
+ break;
+ }
+ hs->head = 0;
+ hs->n = 0;
+ hs->protocol = 1;
+ hs->idle = 0;
+ hs->idle_pending = false;
+ hid_del_idle_timer(hs);
+}
+
+void hid_free(HIDState *hs)
+{
+ qemu_input_handler_unregister(hs->s);
+ hid_del_idle_timer(hs);
+}
+
+static QemuInputHandler hid_keyboard_handler = {
+ .name = "QEMU HID Keyboard",
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = hid_keyboard_event,
+};
+
+static QemuInputHandler hid_mouse_handler = {
+ .name = "QEMU HID Mouse",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+ .event = hid_pointer_event,
+ .sync = hid_pointer_sync,
+};
+
+static QemuInputHandler hid_tablet_handler = {
+ .name = "QEMU HID Tablet",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+ .event = hid_pointer_event,
+ .sync = hid_pointer_sync,
+};
+
+void hid_init(HIDState *hs, int kind, HIDEventFunc event)
+{
+ hs->kind = kind;
+ hs->event = event;
+
+ if (hs->kind == HID_KEYBOARD) {
+ hs->s = qemu_input_handler_register((DeviceState *)hs,
+ &hid_keyboard_handler);
+ qemu_input_handler_activate(hs->s);
+ } else if (hs->kind == HID_MOUSE) {
+ hs->s = qemu_input_handler_register((DeviceState *)hs,
+ &hid_mouse_handler);
+ } else if (hs->kind == HID_TABLET) {
+ hs->s = qemu_input_handler_register((DeviceState *)hs,
+ &hid_tablet_handler);
+ }
+}
+
+static int hid_post_load(void *opaque, int version_id)
+{
+ HIDState *s = opaque;
+
+ hid_set_next_idle(s);
+
+ if (s->n == QUEUE_LENGTH && (s->kind == HID_TABLET ||
+ s->kind == HID_MOUSE)) {
+ /*
+ * Handle ptr device migration from old qemu with full queue.
+ *
+ * Throw away everything but the last event, so we propagate
+ * at least the current button state to the guest. Also keep
+ * current position for the tablet, signal "no motion" for the
+ * mouse.
+ */
+ HIDPointerEvent evt;
+ evt = s->ptr.queue[(s->head+s->n) & QUEUE_MASK];
+ if (s->kind == HID_MOUSE) {
+ evt.xdx = 0;
+ evt.ydy = 0;
+ }
+ s->ptr.queue[0] = evt;
+ s->head = 0;
+ s->n = 1;
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_hid_ptr_queue = {
+ .name = "HIDPointerEventQueue",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(xdx, HIDPointerEvent),
+ VMSTATE_INT32(ydy, HIDPointerEvent),
+ VMSTATE_INT32(dz, HIDPointerEvent),
+ VMSTATE_INT32(buttons_state, HIDPointerEvent),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_hid_ptr_device = {
+ .name = "HIDPointerDevice",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = hid_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(ptr.queue, HIDState, QUEUE_LENGTH, 0,
+ vmstate_hid_ptr_queue, HIDPointerEvent),
+ VMSTATE_UINT32(head, HIDState),
+ VMSTATE_UINT32(n, HIDState),
+ VMSTATE_INT32(protocol, HIDState),
+ VMSTATE_UINT8(idle, HIDState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+const VMStateDescription vmstate_hid_keyboard_device = {
+ .name = "HIDKeyboardDevice",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = hid_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(kbd.keycodes, HIDState, QUEUE_LENGTH),
+ VMSTATE_UINT32(head, HIDState),
+ VMSTATE_UINT32(n, HIDState),
+ VMSTATE_UINT16(kbd.modifiers, HIDState),
+ VMSTATE_UINT8(kbd.leds, HIDState),
+ VMSTATE_UINT8_ARRAY(kbd.key, HIDState, 16),
+ VMSTATE_INT32(kbd.keys, HIDState),
+ VMSTATE_INT32(protocol, HIDState),
+ VMSTATE_UINT8(idle, HIDState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
diff --git a/hw/input/lm832x.c b/hw/input/lm832x.c
new file mode 100644
index 00000000..530a6e01
--- /dev/null
+++ b/hw/input/lm832x.c
@@ -0,0 +1,524 @@
+/*
+ * National Semiconductor LM8322/8323 GPIO keyboard & PWM chips.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "qemu/timer.h"
+#include "ui/console.h"
+
+#define TYPE_LM8323 "lm8323"
+#define LM8323(obj) OBJECT_CHECK(LM823KbdState, (obj), TYPE_LM8323)
+
+typedef struct {
+ I2CSlave parent_obj;
+
+ uint8_t i2c_dir;
+ uint8_t i2c_cycle;
+ uint8_t reg;
+
+ qemu_irq nirq;
+ uint16_t model;
+
+ struct {
+ qemu_irq out[2];
+ int in[2][2];
+ } mux;
+
+ uint8_t config;
+ uint8_t status;
+ uint8_t acttime;
+ uint8_t error;
+ uint8_t clock;
+
+ struct {
+ uint16_t pull;
+ uint16_t mask;
+ uint16_t dir;
+ uint16_t level;
+ qemu_irq out[16];
+ } gpio;
+
+ struct {
+ uint8_t dbnctime;
+ uint8_t size;
+ uint8_t start;
+ uint8_t len;
+ uint8_t fifo[16];
+ } kbd;
+
+ struct {
+ uint16_t file[256];
+ uint8_t faddr;
+ uint8_t addr[3];
+ QEMUTimer *tm[3];
+ } pwm;
+} LM823KbdState;
+
+#define INT_KEYPAD (1 << 0)
+#define INT_ERROR (1 << 3)
+#define INT_NOINIT (1 << 4)
+#define INT_PWMEND(n) (1 << (5 + n))
+
+#define ERR_BADPAR (1 << 0)
+#define ERR_CMDUNK (1 << 1)
+#define ERR_KEYOVR (1 << 2)
+#define ERR_FIFOOVR (1 << 6)
+
+static void lm_kbd_irq_update(LM823KbdState *s)
+{
+ qemu_set_irq(s->nirq, !s->status);
+}
+
+static void lm_kbd_gpio_update(LM823KbdState *s)
+{
+}
+
+static void lm_kbd_reset(LM823KbdState *s)
+{
+ s->config = 0x80;
+ s->status = INT_NOINIT;
+ s->acttime = 125;
+ s->kbd.dbnctime = 3;
+ s->kbd.size = 0x33;
+ s->clock = 0x08;
+
+ lm_kbd_irq_update(s);
+ lm_kbd_gpio_update(s);
+}
+
+static void lm_kbd_error(LM823KbdState *s, int err)
+{
+ s->error |= err;
+ s->status |= INT_ERROR;
+ lm_kbd_irq_update(s);
+}
+
+static void lm_kbd_pwm_tick(LM823KbdState *s, int line)
+{
+}
+
+static void lm_kbd_pwm_start(LM823KbdState *s, int line)
+{
+ lm_kbd_pwm_tick(s, line);
+}
+
+static void lm_kbd_pwm0_tick(void *opaque)
+{
+ lm_kbd_pwm_tick(opaque, 0);
+}
+static void lm_kbd_pwm1_tick(void *opaque)
+{
+ lm_kbd_pwm_tick(opaque, 1);
+}
+static void lm_kbd_pwm2_tick(void *opaque)
+{
+ lm_kbd_pwm_tick(opaque, 2);
+}
+
+enum {
+ LM832x_CMD_READ_ID = 0x80, /* Read chip ID. */
+ LM832x_CMD_WRITE_CFG = 0x81, /* Set configuration item. */
+ LM832x_CMD_READ_INT = 0x82, /* Get interrupt status. */
+ LM832x_CMD_RESET = 0x83, /* Reset, same as external one */
+ LM823x_CMD_WRITE_PULL_DOWN = 0x84, /* Select GPIO pull-up/down. */
+ LM832x_CMD_WRITE_PORT_SEL = 0x85, /* Select GPIO in/out. */
+ LM832x_CMD_WRITE_PORT_STATE = 0x86, /* Set GPIO pull-up/down. */
+ LM832x_CMD_READ_PORT_SEL = 0x87, /* Get GPIO in/out. */
+ LM832x_CMD_READ_PORT_STATE = 0x88, /* Get GPIO pull-up/down. */
+ LM832x_CMD_READ_FIFO = 0x89, /* Read byte from FIFO. */
+ LM832x_CMD_RPT_READ_FIFO = 0x8a, /* Read FIFO (no increment). */
+ LM832x_CMD_SET_ACTIVE = 0x8b, /* Set active time. */
+ LM832x_CMD_READ_ERROR = 0x8c, /* Get error status. */
+ LM832x_CMD_READ_ROTATOR = 0x8e, /* Read rotator status. */
+ LM832x_CMD_SET_DEBOUNCE = 0x8f, /* Set debouncing time. */
+ LM832x_CMD_SET_KEY_SIZE = 0x90, /* Set keypad size. */
+ LM832x_CMD_READ_KEY_SIZE = 0x91, /* Get keypad size. */
+ LM832x_CMD_READ_CFG = 0x92, /* Get configuration item. */
+ LM832x_CMD_WRITE_CLOCK = 0x93, /* Set clock config. */
+ LM832x_CMD_READ_CLOCK = 0x94, /* Get clock config. */
+ LM832x_CMD_PWM_WRITE = 0x95, /* Write PWM script. */
+ LM832x_CMD_PWM_START = 0x96, /* Start PWM engine. */
+ LM832x_CMD_PWM_STOP = 0x97, /* Stop PWM engine. */
+ LM832x_GENERAL_ERROR = 0xff, /* There was one error.
+ Previously was represented by -1
+ This is not a command */
+};
+
+#define LM832x_MAX_KPX 8
+#define LM832x_MAX_KPY 12
+
+static uint8_t lm_kbd_read(LM823KbdState *s, int reg, int byte)
+{
+ int ret;
+
+ switch (reg) {
+ case LM832x_CMD_READ_ID:
+ ret = 0x0400;
+ break;
+
+ case LM832x_CMD_READ_INT:
+ ret = s->status;
+ if (!(s->status & INT_NOINIT)) {
+ s->status = 0;
+ lm_kbd_irq_update(s);
+ }
+ break;
+
+ case LM832x_CMD_READ_PORT_SEL:
+ ret = s->gpio.dir;
+ break;
+ case LM832x_CMD_READ_PORT_STATE:
+ ret = s->gpio.mask;
+ break;
+
+ case LM832x_CMD_READ_FIFO:
+ if (s->kbd.len <= 1)
+ return 0x00;
+
+ /* Example response from the two commands after a INT_KEYPAD
+ * interrupt caused by the key 0x3c being pressed:
+ * RPT_READ_FIFO: 55 bc 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01
+ * READ_FIFO: bc 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01
+ * RPT_READ_FIFO: bc 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01
+ *
+ * 55 is the code of the key release event serviced in the previous
+ * interrupt handling.
+ *
+ * TODO: find out whether the FIFO is advanced a single character
+ * before reading every byte or the whole size of the FIFO at the
+ * last LM832x_CMD_READ_FIFO. This affects LM832x_CMD_RPT_READ_FIFO
+ * output in cases where there are more than one event in the FIFO.
+ * Assume 0xbc and 0x3c events are in the FIFO:
+ * RPT_READ_FIFO: 55 bc 3c 00 4e ff 0a 50 08 00 29 d9 08 01 c9
+ * READ_FIFO: bc 3c 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9
+ * Does RPT_READ_FIFO now return 0xbc and 0x3c or only 0x3c?
+ */
+ s->kbd.start ++;
+ s->kbd.start &= sizeof(s->kbd.fifo) - 1;
+ s->kbd.len --;
+
+ return s->kbd.fifo[s->kbd.start];
+ case LM832x_CMD_RPT_READ_FIFO:
+ if (byte >= s->kbd.len)
+ return 0x00;
+
+ return s->kbd.fifo[(s->kbd.start + byte) & (sizeof(s->kbd.fifo) - 1)];
+
+ case LM832x_CMD_READ_ERROR:
+ return s->error;
+
+ case LM832x_CMD_READ_ROTATOR:
+ return 0;
+
+ case LM832x_CMD_READ_KEY_SIZE:
+ return s->kbd.size;
+
+ case LM832x_CMD_READ_CFG:
+ return s->config & 0xf;
+
+ case LM832x_CMD_READ_CLOCK:
+ return (s->clock & 0xfc) | 2;
+
+ default:
+ lm_kbd_error(s, ERR_CMDUNK);
+ fprintf(stderr, "%s: unknown command %02x\n", __FUNCTION__, reg);
+ return 0x00;
+ }
+
+ return ret >> (byte << 3);
+}
+
+static void lm_kbd_write(LM823KbdState *s, int reg, int byte, uint8_t value)
+{
+ switch (reg) {
+ case LM832x_CMD_WRITE_CFG:
+ s->config = value;
+ /* This must be done whenever s->mux.in is updated (never). */
+ if ((s->config >> 1) & 1) /* MUX1EN */
+ qemu_set_irq(s->mux.out[0], s->mux.in[0][(s->config >> 0) & 1]);
+ if ((s->config >> 3) & 1) /* MUX2EN */
+ qemu_set_irq(s->mux.out[0], s->mux.in[0][(s->config >> 2) & 1]);
+ /* TODO: check that this is issued only following the chip reset
+ * and not in the middle of operation and that it is followed by
+ * the GPIO ports re-resablishing through WRITE_PORT_SEL and
+ * WRITE_PORT_STATE (using a timer perhaps) and otherwise output
+ * warnings. */
+ s->status = 0;
+ lm_kbd_irq_update(s);
+ s->kbd.len = 0;
+ s->kbd.start = 0;
+ s->reg = LM832x_GENERAL_ERROR;
+ break;
+
+ case LM832x_CMD_RESET:
+ if (value == 0xaa)
+ lm_kbd_reset(s);
+ else
+ lm_kbd_error(s, ERR_BADPAR);
+ s->reg = LM832x_GENERAL_ERROR;
+ break;
+
+ case LM823x_CMD_WRITE_PULL_DOWN:
+ if (!byte)
+ s->gpio.pull = value;
+ else {
+ s->gpio.pull |= value << 8;
+ lm_kbd_gpio_update(s);
+ s->reg = LM832x_GENERAL_ERROR;
+ }
+ break;
+ case LM832x_CMD_WRITE_PORT_SEL:
+ if (!byte)
+ s->gpio.dir = value;
+ else {
+ s->gpio.dir |= value << 8;
+ lm_kbd_gpio_update(s);
+ s->reg = LM832x_GENERAL_ERROR;
+ }
+ break;
+ case LM832x_CMD_WRITE_PORT_STATE:
+ if (!byte)
+ s->gpio.mask = value;
+ else {
+ s->gpio.mask |= value << 8;
+ lm_kbd_gpio_update(s);
+ s->reg = LM832x_GENERAL_ERROR;
+ }
+ break;
+
+ case LM832x_CMD_SET_ACTIVE:
+ s->acttime = value;
+ s->reg = LM832x_GENERAL_ERROR;
+ break;
+
+ case LM832x_CMD_SET_DEBOUNCE:
+ s->kbd.dbnctime = value;
+ s->reg = LM832x_GENERAL_ERROR;
+ if (!value)
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+
+ case LM832x_CMD_SET_KEY_SIZE:
+ s->kbd.size = value;
+ s->reg = LM832x_GENERAL_ERROR;
+ if (
+ (value & 0xf) < 3 || (value & 0xf) > LM832x_MAX_KPY ||
+ (value >> 4) < 3 || (value >> 4) > LM832x_MAX_KPX)
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+
+ case LM832x_CMD_WRITE_CLOCK:
+ s->clock = value;
+ s->reg = LM832x_GENERAL_ERROR;
+ if ((value & 3) && (value & 3) != 3) {
+ lm_kbd_error(s, ERR_BADPAR);
+ fprintf(stderr, "%s: invalid clock setting in RCPWM\n",
+ __FUNCTION__);
+ }
+ /* TODO: Validate that the command is only issued once */
+ break;
+
+ case LM832x_CMD_PWM_WRITE:
+ if (byte == 0) {
+ if (!(value & 3) || (value >> 2) > 59) {
+ lm_kbd_error(s, ERR_BADPAR);
+ s->reg = LM832x_GENERAL_ERROR;
+ break;
+ }
+
+ s->pwm.faddr = value;
+ s->pwm.file[s->pwm.faddr] = 0;
+ } else if (byte == 1) {
+ s->pwm.file[s->pwm.faddr] |= value << 8;
+ } else if (byte == 2) {
+ s->pwm.file[s->pwm.faddr] |= value << 0;
+ s->reg = LM832x_GENERAL_ERROR;
+ }
+ break;
+ case LM832x_CMD_PWM_START:
+ s->reg = LM832x_GENERAL_ERROR;
+ if (!(value & 3) || (value >> 2) > 59) {
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+ }
+
+ s->pwm.addr[(value & 3) - 1] = value >> 2;
+ lm_kbd_pwm_start(s, (value & 3) - 1);
+ break;
+ case LM832x_CMD_PWM_STOP:
+ s->reg = LM832x_GENERAL_ERROR;
+ if (!(value & 3)) {
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+ }
+
+ timer_del(s->pwm.tm[(value & 3) - 1]);
+ break;
+
+ case LM832x_GENERAL_ERROR:
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+ default:
+ lm_kbd_error(s, ERR_CMDUNK);
+ fprintf(stderr, "%s: unknown command %02x\n", __FUNCTION__, reg);
+ break;
+ }
+}
+
+static void lm_i2c_event(I2CSlave *i2c, enum i2c_event event)
+{
+ LM823KbdState *s = LM8323(i2c);
+
+ switch (event) {
+ case I2C_START_RECV:
+ case I2C_START_SEND:
+ s->i2c_cycle = 0;
+ s->i2c_dir = (event == I2C_START_SEND);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int lm_i2c_rx(I2CSlave *i2c)
+{
+ LM823KbdState *s = LM8323(i2c);
+
+ return lm_kbd_read(s, s->reg, s->i2c_cycle ++);
+}
+
+static int lm_i2c_tx(I2CSlave *i2c, uint8_t data)
+{
+ LM823KbdState *s = LM8323(i2c);
+
+ if (!s->i2c_cycle)
+ s->reg = data;
+ else
+ lm_kbd_write(s, s->reg, s->i2c_cycle - 1, data);
+ s->i2c_cycle ++;
+
+ return 0;
+}
+
+static int lm_kbd_post_load(void *opaque, int version_id)
+{
+ LM823KbdState *s = opaque;
+
+ lm_kbd_irq_update(s);
+ lm_kbd_gpio_update(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm_kbd = {
+ .name = "LM8323",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = lm_kbd_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_I2C_SLAVE(parent_obj, LM823KbdState),
+ VMSTATE_UINT8(i2c_dir, LM823KbdState),
+ VMSTATE_UINT8(i2c_cycle, LM823KbdState),
+ VMSTATE_UINT8(reg, LM823KbdState),
+ VMSTATE_UINT8(config, LM823KbdState),
+ VMSTATE_UINT8(status, LM823KbdState),
+ VMSTATE_UINT8(acttime, LM823KbdState),
+ VMSTATE_UINT8(error, LM823KbdState),
+ VMSTATE_UINT8(clock, LM823KbdState),
+ VMSTATE_UINT16(gpio.pull, LM823KbdState),
+ VMSTATE_UINT16(gpio.mask, LM823KbdState),
+ VMSTATE_UINT16(gpio.dir, LM823KbdState),
+ VMSTATE_UINT16(gpio.level, LM823KbdState),
+ VMSTATE_UINT8(kbd.dbnctime, LM823KbdState),
+ VMSTATE_UINT8(kbd.size, LM823KbdState),
+ VMSTATE_UINT8(kbd.start, LM823KbdState),
+ VMSTATE_UINT8(kbd.len, LM823KbdState),
+ VMSTATE_BUFFER(kbd.fifo, LM823KbdState),
+ VMSTATE_UINT16_ARRAY(pwm.file, LM823KbdState, 256),
+ VMSTATE_UINT8(pwm.faddr, LM823KbdState),
+ VMSTATE_BUFFER(pwm.addr, LM823KbdState),
+ VMSTATE_TIMER_PTR_ARRAY(pwm.tm, LM823KbdState, 3),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+static int lm8323_init(I2CSlave *i2c)
+{
+ LM823KbdState *s = LM8323(i2c);
+
+ s->model = 0x8323;
+ s->pwm.tm[0] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm0_tick, s);
+ s->pwm.tm[1] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm1_tick, s);
+ s->pwm.tm[2] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm2_tick, s);
+ qdev_init_gpio_out(DEVICE(i2c), &s->nirq, 1);
+
+ lm_kbd_reset(s);
+
+ qemu_register_reset((void *) lm_kbd_reset, s);
+ return 0;
+}
+
+void lm832x_key_event(DeviceState *dev, int key, int state)
+{
+ LM823KbdState *s = LM8323(dev);
+
+ if ((s->status & INT_ERROR) && (s->error & ERR_FIFOOVR))
+ return;
+
+ if (s->kbd.len >= sizeof(s->kbd.fifo)) {
+ lm_kbd_error(s, ERR_FIFOOVR);
+ return;
+ }
+
+ s->kbd.fifo[(s->kbd.start + s->kbd.len ++) & (sizeof(s->kbd.fifo) - 1)] =
+ key | (state << 7);
+
+ /* We never set ERR_KEYOVR because we support multiple keys fine. */
+ s->status |= INT_KEYPAD;
+ lm_kbd_irq_update(s);
+}
+
+static void lm8323_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = lm8323_init;
+ k->event = lm_i2c_event;
+ k->recv = lm_i2c_rx;
+ k->send = lm_i2c_tx;
+ dc->vmsd = &vmstate_lm_kbd;
+}
+
+static const TypeInfo lm8323_info = {
+ .name = TYPE_LM8323,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(LM823KbdState),
+ .class_init = lm8323_class_init,
+};
+
+static void lm832x_register_types(void)
+{
+ type_register_static(&lm8323_info);
+}
+
+type_init(lm832x_register_types)
diff --git a/hw/input/milkymist-softusb.c b/hw/input/milkymist-softusb.c
new file mode 100644
index 00000000..7b0f4db8
--- /dev/null
+++ b/hw/input/milkymist-softusb.c
@@ -0,0 +1,317 @@
+/*
+ * QEMU model of the Milkymist SoftUSB block.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * not available yet
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "ui/console.h"
+#include "hw/input/hid.h"
+#include "qemu/error-report.h"
+
+enum {
+ R_CTRL = 0,
+ R_MAX
+};
+
+enum {
+ CTRL_RESET = (1<<0),
+};
+
+#define COMLOC_DEBUG_PRODUCE 0x1000
+#define COMLOC_DEBUG_BASE 0x1001
+#define COMLOC_MEVT_PRODUCE 0x1101
+#define COMLOC_MEVT_BASE 0x1102
+#define COMLOC_KEVT_PRODUCE 0x1142
+#define COMLOC_KEVT_BASE 0x1143
+
+#define TYPE_MILKYMIST_SOFTUSB "milkymist-softusb"
+#define MILKYMIST_SOFTUSB(obj) \
+ OBJECT_CHECK(MilkymistSoftUsbState, (obj), TYPE_MILKYMIST_SOFTUSB)
+
+struct MilkymistSoftUsbState {
+ SysBusDevice parent_obj;
+
+ HIDState hid_kbd;
+ HIDState hid_mouse;
+
+ MemoryRegion regs_region;
+ MemoryRegion pmem;
+ MemoryRegion dmem;
+ qemu_irq irq;
+
+ void *pmem_ptr;
+ void *dmem_ptr;
+
+ /* device properties */
+ uint32_t pmem_size;
+ uint32_t dmem_size;
+
+ /* device registers */
+ uint32_t regs[R_MAX];
+
+ /* mouse state */
+ uint8_t mouse_hid_buffer[4];
+
+ /* keyboard state */
+ uint8_t kbd_hid_buffer[8];
+};
+typedef struct MilkymistSoftUsbState MilkymistSoftUsbState;
+
+static uint64_t softusb_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistSoftUsbState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTRL:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_softusb: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_softusb_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void
+softusb_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistSoftUsbState *s = opaque;
+
+ trace_milkymist_softusb_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTRL:
+ s->regs[addr] = value;
+ break;
+
+ default:
+ error_report("milkymist_softusb: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps softusb_mmio_ops = {
+ .read = softusb_read,
+ .write = softusb_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static inline void softusb_read_dmem(MilkymistSoftUsbState *s,
+ uint32_t offset, uint8_t *buf, uint32_t len)
+{
+ if (offset + len >= s->dmem_size) {
+ error_report("milkymist_softusb: read dmem out of bounds "
+ "at offset 0x%x, len %d", offset, len);
+ memset(buf, 0, len);
+ return;
+ }
+
+ memcpy(buf, s->dmem_ptr + offset, len);
+}
+
+static inline void softusb_write_dmem(MilkymistSoftUsbState *s,
+ uint32_t offset, uint8_t *buf, uint32_t len)
+{
+ if (offset + len >= s->dmem_size) {
+ error_report("milkymist_softusb: write dmem out of bounds "
+ "at offset 0x%x, len %d", offset, len);
+ return;
+ }
+
+ memcpy(s->dmem_ptr + offset, buf, len);
+}
+
+static void softusb_mouse_changed(MilkymistSoftUsbState *s)
+{
+ uint8_t m;
+
+ softusb_read_dmem(s, COMLOC_MEVT_PRODUCE, &m, 1);
+ trace_milkymist_softusb_mevt(m);
+ softusb_write_dmem(s, COMLOC_MEVT_BASE + 4 * m, s->mouse_hid_buffer, 4);
+ m = (m + 1) & 0xf;
+ softusb_write_dmem(s, COMLOC_MEVT_PRODUCE, &m, 1);
+
+ trace_milkymist_softusb_pulse_irq();
+ qemu_irq_pulse(s->irq);
+}
+
+static void softusb_kbd_changed(MilkymistSoftUsbState *s)
+{
+ uint8_t m;
+
+ softusb_read_dmem(s, COMLOC_KEVT_PRODUCE, &m, 1);
+ trace_milkymist_softusb_kevt(m);
+ softusb_write_dmem(s, COMLOC_KEVT_BASE + 8 * m, s->kbd_hid_buffer, 8);
+ m = (m + 1) & 0x7;
+ softusb_write_dmem(s, COMLOC_KEVT_PRODUCE, &m, 1);
+
+ trace_milkymist_softusb_pulse_irq();
+ qemu_irq_pulse(s->irq);
+}
+
+static void softusb_kbd_hid_datain(HIDState *hs)
+{
+ MilkymistSoftUsbState *s = container_of(hs, MilkymistSoftUsbState, hid_kbd);
+ int len;
+
+ /* if device is in reset, do nothing */
+ if (s->regs[R_CTRL] & CTRL_RESET) {
+ return;
+ }
+
+ while (hid_has_events(hs)) {
+ len = hid_keyboard_poll(hs, s->kbd_hid_buffer,
+ sizeof(s->kbd_hid_buffer));
+
+ if (len == 8) {
+ softusb_kbd_changed(s);
+ }
+ }
+}
+
+static void softusb_mouse_hid_datain(HIDState *hs)
+{
+ MilkymistSoftUsbState *s =
+ container_of(hs, MilkymistSoftUsbState, hid_mouse);
+ int len;
+
+ /* if device is in reset, do nothing */
+ if (s->regs[R_CTRL] & CTRL_RESET) {
+ return;
+ }
+
+ while (hid_has_events(hs)) {
+ len = hid_pointer_poll(hs, s->mouse_hid_buffer,
+ sizeof(s->mouse_hid_buffer));
+
+ if (len == 4) {
+ softusb_mouse_changed(s);
+ }
+ }
+}
+
+static void milkymist_softusb_reset(DeviceState *d)
+{
+ MilkymistSoftUsbState *s = MILKYMIST_SOFTUSB(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+ memset(s->kbd_hid_buffer, 0, sizeof(s->kbd_hid_buffer));
+ memset(s->mouse_hid_buffer, 0, sizeof(s->mouse_hid_buffer));
+
+ hid_reset(&s->hid_kbd);
+ hid_reset(&s->hid_mouse);
+
+ /* defaults */
+ s->regs[R_CTRL] = CTRL_RESET;
+}
+
+static int milkymist_softusb_init(SysBusDevice *dev)
+{
+ MilkymistSoftUsbState *s = MILKYMIST_SOFTUSB(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &softusb_mmio_ops, s,
+ "milkymist-softusb", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ /* register pmem and dmem */
+ memory_region_init_ram(&s->pmem, OBJECT(s), "milkymist-softusb.pmem",
+ s->pmem_size, &error_abort);
+ vmstate_register_ram_global(&s->pmem);
+ s->pmem_ptr = memory_region_get_ram_ptr(&s->pmem);
+ sysbus_init_mmio(dev, &s->pmem);
+ memory_region_init_ram(&s->dmem, OBJECT(s), "milkymist-softusb.dmem",
+ s->dmem_size, &error_abort);
+ vmstate_register_ram_global(&s->dmem);
+ s->dmem_ptr = memory_region_get_ram_ptr(&s->dmem);
+ sysbus_init_mmio(dev, &s->dmem);
+
+ hid_init(&s->hid_kbd, HID_KEYBOARD, softusb_kbd_hid_datain);
+ hid_init(&s->hid_mouse, HID_MOUSE, softusb_mouse_hid_datain);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_softusb = {
+ .name = "milkymist-softusb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistSoftUsbState, R_MAX),
+ VMSTATE_HID_KEYBOARD_DEVICE(hid_kbd, MilkymistSoftUsbState),
+ VMSTATE_HID_POINTER_DEVICE(hid_mouse, MilkymistSoftUsbState),
+ VMSTATE_BUFFER(kbd_hid_buffer, MilkymistSoftUsbState),
+ VMSTATE_BUFFER(mouse_hid_buffer, MilkymistSoftUsbState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property milkymist_softusb_properties[] = {
+ DEFINE_PROP_UINT32("pmem_size", MilkymistSoftUsbState, pmem_size, 0x00001000),
+ DEFINE_PROP_UINT32("dmem_size", MilkymistSoftUsbState, dmem_size, 0x00002000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void milkymist_softusb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_softusb_init;
+ dc->reset = milkymist_softusb_reset;
+ dc->vmsd = &vmstate_milkymist_softusb;
+ dc->props = milkymist_softusb_properties;
+}
+
+static const TypeInfo milkymist_softusb_info = {
+ .name = TYPE_MILKYMIST_SOFTUSB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistSoftUsbState),
+ .class_init = milkymist_softusb_class_init,
+};
+
+static void milkymist_softusb_register_types(void)
+{
+ type_register_static(&milkymist_softusb_info);
+}
+
+type_init(milkymist_softusb_register_types)
diff --git a/hw/input/pckbd.c b/hw/input/pckbd.c
new file mode 100644
index 00000000..ddac69df
--- /dev/null
+++ b/hw/input/pckbd.c
@@ -0,0 +1,594 @@
+/*
+ * QEMU PC keyboard emulation
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+#include "hw/input/ps2.h"
+#include "sysemu/sysemu.h"
+
+/* debug PC keyboard */
+//#define DEBUG_KBD
+#ifdef DEBUG_KBD
+#define DPRINTF(fmt, ...) \
+ do { printf("KBD: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+/* Keyboard Controller Commands */
+#define KBD_CCMD_READ_MODE 0x20 /* Read mode bits */
+#define KBD_CCMD_WRITE_MODE 0x60 /* Write mode bits */
+#define KBD_CCMD_GET_VERSION 0xA1 /* Get controller version */
+#define KBD_CCMD_MOUSE_DISABLE 0xA7 /* Disable mouse interface */
+#define KBD_CCMD_MOUSE_ENABLE 0xA8 /* Enable mouse interface */
+#define KBD_CCMD_TEST_MOUSE 0xA9 /* Mouse interface test */
+#define KBD_CCMD_SELF_TEST 0xAA /* Controller self test */
+#define KBD_CCMD_KBD_TEST 0xAB /* Keyboard interface test */
+#define KBD_CCMD_KBD_DISABLE 0xAD /* Keyboard interface disable */
+#define KBD_CCMD_KBD_ENABLE 0xAE /* Keyboard interface enable */
+#define KBD_CCMD_READ_INPORT 0xC0 /* read input port */
+#define KBD_CCMD_READ_OUTPORT 0xD0 /* read output port */
+#define KBD_CCMD_WRITE_OUTPORT 0xD1 /* write output port */
+#define KBD_CCMD_WRITE_OBUF 0xD2
+#define KBD_CCMD_WRITE_AUX_OBUF 0xD3 /* Write to output buffer as if
+ initiated by the auxiliary device */
+#define KBD_CCMD_WRITE_MOUSE 0xD4 /* Write the following byte to the mouse */
+#define KBD_CCMD_DISABLE_A20 0xDD /* HP vectra only ? */
+#define KBD_CCMD_ENABLE_A20 0xDF /* HP vectra only ? */
+#define KBD_CCMD_PULSE_BITS_3_0 0xF0 /* Pulse bits 3-0 of the output port P2. */
+#define KBD_CCMD_RESET 0xFE /* Pulse bit 0 of the output port P2 = CPU reset. */
+#define KBD_CCMD_NO_OP 0xFF /* Pulse no bits of the output port P2. */
+
+/* Keyboard Commands */
+#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */
+#define KBD_CMD_ECHO 0xEE
+#define KBD_CMD_GET_ID 0xF2 /* get keyboard ID */
+#define KBD_CMD_SET_RATE 0xF3 /* Set typematic rate */
+#define KBD_CMD_ENABLE 0xF4 /* Enable scanning */
+#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */
+#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */
+#define KBD_CMD_RESET 0xFF /* Reset */
+
+/* Keyboard Replies */
+#define KBD_REPLY_POR 0xAA /* Power on reset */
+#define KBD_REPLY_ACK 0xFA /* Command ACK */
+#define KBD_REPLY_RESEND 0xFE /* Command NACK, send the cmd again */
+
+/* Status Register Bits */
+#define KBD_STAT_OBF 0x01 /* Keyboard output buffer full */
+#define KBD_STAT_IBF 0x02 /* Keyboard input buffer full */
+#define KBD_STAT_SELFTEST 0x04 /* Self test successful */
+#define KBD_STAT_CMD 0x08 /* Last write was a command write (0=data) */
+#define KBD_STAT_UNLOCKED 0x10 /* Zero if keyboard locked */
+#define KBD_STAT_MOUSE_OBF 0x20 /* Mouse output buffer full */
+#define KBD_STAT_GTO 0x40 /* General receive/xmit timeout */
+#define KBD_STAT_PERR 0x80 /* Parity error */
+
+/* Controller Mode Register Bits */
+#define KBD_MODE_KBD_INT 0x01 /* Keyboard data generate IRQ1 */
+#define KBD_MODE_MOUSE_INT 0x02 /* Mouse data generate IRQ12 */
+#define KBD_MODE_SYS 0x04 /* The system flag (?) */
+#define KBD_MODE_NO_KEYLOCK 0x08 /* The keylock doesn't affect the keyboard if set */
+#define KBD_MODE_DISABLE_KBD 0x10 /* Disable keyboard interface */
+#define KBD_MODE_DISABLE_MOUSE 0x20 /* Disable mouse interface */
+#define KBD_MODE_KCC 0x40 /* Scan code conversion to PC format */
+#define KBD_MODE_RFU 0x80
+
+/* Output Port Bits */
+#define KBD_OUT_RESET 0x01 /* 1=normal mode, 0=reset */
+#define KBD_OUT_A20 0x02 /* x86 only */
+#define KBD_OUT_OBF 0x10 /* Keyboard output buffer full */
+#define KBD_OUT_MOUSE_OBF 0x20 /* Mouse output buffer full */
+
+/* OSes typically write 0xdd/0xdf to turn the A20 line off and on.
+ * We make the default value of the outport include these four bits,
+ * so that the subsection is rarely necessary.
+ */
+#define KBD_OUT_ONES 0xcc
+
+/* Mouse Commands */
+#define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */
+#define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */
+#define AUX_SET_RES 0xE8 /* Set resolution */
+#define AUX_GET_SCALE 0xE9 /* Get scaling factor */
+#define AUX_SET_STREAM 0xEA /* Set stream mode */
+#define AUX_POLL 0xEB /* Poll */
+#define AUX_RESET_WRAP 0xEC /* Reset wrap mode */
+#define AUX_SET_WRAP 0xEE /* Set wrap mode */
+#define AUX_SET_REMOTE 0xF0 /* Set remote mode */
+#define AUX_GET_TYPE 0xF2 /* Get type */
+#define AUX_SET_SAMPLE 0xF3 /* Set sample rate */
+#define AUX_ENABLE_DEV 0xF4 /* Enable aux device */
+#define AUX_DISABLE_DEV 0xF5 /* Disable aux device */
+#define AUX_SET_DEFAULT 0xF6
+#define AUX_RESET 0xFF /* Reset aux device */
+#define AUX_ACK 0xFA /* Command byte ACK. */
+
+#define MOUSE_STATUS_REMOTE 0x40
+#define MOUSE_STATUS_ENABLED 0x20
+#define MOUSE_STATUS_SCALE21 0x10
+
+#define KBD_PENDING_KBD 1
+#define KBD_PENDING_AUX 2
+
+typedef struct KBDState {
+ uint8_t write_cmd; /* if non zero, write data to port 60 is expected */
+ uint8_t status;
+ uint8_t mode;
+ uint8_t outport;
+ bool outport_present;
+ /* Bitmask of devices with data available. */
+ uint8_t pending;
+ void *kbd;
+ void *mouse;
+
+ qemu_irq irq_kbd;
+ qemu_irq irq_mouse;
+ qemu_irq *a20_out;
+ hwaddr mask;
+} KBDState;
+
+/* update irq and KBD_STAT_[MOUSE_]OBF */
+/* XXX: not generating the irqs if KBD_MODE_DISABLE_KBD is set may be
+ incorrect, but it avoids having to simulate exact delays */
+static void kbd_update_irq(KBDState *s)
+{
+ int irq_kbd_level, irq_mouse_level;
+
+ irq_kbd_level = 0;
+ irq_mouse_level = 0;
+ s->status &= ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF);
+ s->outport &= ~(KBD_OUT_OBF | KBD_OUT_MOUSE_OBF);
+ if (s->pending) {
+ s->status |= KBD_STAT_OBF;
+ s->outport |= KBD_OUT_OBF;
+ /* kbd data takes priority over aux data. */
+ if (s->pending == KBD_PENDING_AUX) {
+ s->status |= KBD_STAT_MOUSE_OBF;
+ s->outport |= KBD_OUT_MOUSE_OBF;
+ if (s->mode & KBD_MODE_MOUSE_INT)
+ irq_mouse_level = 1;
+ } else {
+ if ((s->mode & KBD_MODE_KBD_INT) &&
+ !(s->mode & KBD_MODE_DISABLE_KBD))
+ irq_kbd_level = 1;
+ }
+ }
+ qemu_set_irq(s->irq_kbd, irq_kbd_level);
+ qemu_set_irq(s->irq_mouse, irq_mouse_level);
+}
+
+static void kbd_update_kbd_irq(void *opaque, int level)
+{
+ KBDState *s = (KBDState *)opaque;
+
+ if (level)
+ s->pending |= KBD_PENDING_KBD;
+ else
+ s->pending &= ~KBD_PENDING_KBD;
+ kbd_update_irq(s);
+}
+
+static void kbd_update_aux_irq(void *opaque, int level)
+{
+ KBDState *s = (KBDState *)opaque;
+
+ if (level)
+ s->pending |= KBD_PENDING_AUX;
+ else
+ s->pending &= ~KBD_PENDING_AUX;
+ kbd_update_irq(s);
+}
+
+static uint64_t kbd_read_status(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ KBDState *s = opaque;
+ int val;
+ val = s->status;
+ DPRINTF("kbd: read status=0x%02x\n", val);
+ return val;
+}
+
+static void kbd_queue(KBDState *s, int b, int aux)
+{
+ if (aux)
+ ps2_queue(s->mouse, b);
+ else
+ ps2_queue(s->kbd, b);
+}
+
+static void outport_write(KBDState *s, uint32_t val)
+{
+ DPRINTF("kbd: write outport=0x%02x\n", val);
+ s->outport = val;
+ if (s->a20_out) {
+ qemu_set_irq(*s->a20_out, (val >> 1) & 1);
+ }
+ if (!(val & 1)) {
+ qemu_system_reset_request();
+ }
+}
+
+static void kbd_write_command(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ KBDState *s = opaque;
+
+ DPRINTF("kbd: write cmd=0x%02" PRIx64 "\n", val);
+
+ /* Bits 3-0 of the output port P2 of the keyboard controller may be pulsed
+ * low for approximately 6 micro seconds. Bits 3-0 of the KBD_CCMD_PULSE
+ * command specify the output port bits to be pulsed.
+ * 0: Bit should be pulsed. 1: Bit should not be modified.
+ * The only useful version of this command is pulsing bit 0,
+ * which does a CPU reset.
+ */
+ if((val & KBD_CCMD_PULSE_BITS_3_0) == KBD_CCMD_PULSE_BITS_3_0) {
+ if(!(val & 1))
+ val = KBD_CCMD_RESET;
+ else
+ val = KBD_CCMD_NO_OP;
+ }
+
+ switch(val) {
+ case KBD_CCMD_READ_MODE:
+ kbd_queue(s, s->mode, 0);
+ break;
+ case KBD_CCMD_WRITE_MODE:
+ case KBD_CCMD_WRITE_OBUF:
+ case KBD_CCMD_WRITE_AUX_OBUF:
+ case KBD_CCMD_WRITE_MOUSE:
+ case KBD_CCMD_WRITE_OUTPORT:
+ s->write_cmd = val;
+ break;
+ case KBD_CCMD_MOUSE_DISABLE:
+ s->mode |= KBD_MODE_DISABLE_MOUSE;
+ break;
+ case KBD_CCMD_MOUSE_ENABLE:
+ s->mode &= ~KBD_MODE_DISABLE_MOUSE;
+ break;
+ case KBD_CCMD_TEST_MOUSE:
+ kbd_queue(s, 0x00, 0);
+ break;
+ case KBD_CCMD_SELF_TEST:
+ s->status |= KBD_STAT_SELFTEST;
+ kbd_queue(s, 0x55, 0);
+ break;
+ case KBD_CCMD_KBD_TEST:
+ kbd_queue(s, 0x00, 0);
+ break;
+ case KBD_CCMD_KBD_DISABLE:
+ s->mode |= KBD_MODE_DISABLE_KBD;
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_KBD_ENABLE:
+ s->mode &= ~KBD_MODE_DISABLE_KBD;
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_READ_INPORT:
+ kbd_queue(s, 0x80, 0);
+ break;
+ case KBD_CCMD_READ_OUTPORT:
+ kbd_queue(s, s->outport, 0);
+ break;
+ case KBD_CCMD_ENABLE_A20:
+ if (s->a20_out) {
+ qemu_irq_raise(*s->a20_out);
+ }
+ s->outport |= KBD_OUT_A20;
+ break;
+ case KBD_CCMD_DISABLE_A20:
+ if (s->a20_out) {
+ qemu_irq_lower(*s->a20_out);
+ }
+ s->outport &= ~KBD_OUT_A20;
+ break;
+ case KBD_CCMD_RESET:
+ qemu_system_reset_request();
+ break;
+ case KBD_CCMD_NO_OP:
+ /* ignore that */
+ break;
+ default:
+ fprintf(stderr, "qemu: unsupported keyboard cmd=0x%02x\n", (int)val);
+ break;
+ }
+}
+
+static uint64_t kbd_read_data(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ KBDState *s = opaque;
+ uint32_t val;
+
+ if (s->pending == KBD_PENDING_AUX)
+ val = ps2_read_data(s->mouse);
+ else
+ val = ps2_read_data(s->kbd);
+
+ DPRINTF("kbd: read data=0x%02x\n", val);
+ return val;
+}
+
+static void kbd_write_data(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ KBDState *s = opaque;
+
+ DPRINTF("kbd: write data=0x%02" PRIx64 "\n", val);
+
+ switch(s->write_cmd) {
+ case 0:
+ ps2_write_keyboard(s->kbd, val);
+ break;
+ case KBD_CCMD_WRITE_MODE:
+ s->mode = val;
+ ps2_keyboard_set_translation(s->kbd, (s->mode & KBD_MODE_KCC) != 0);
+ /* ??? */
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_WRITE_OBUF:
+ kbd_queue(s, val, 0);
+ break;
+ case KBD_CCMD_WRITE_AUX_OBUF:
+ kbd_queue(s, val, 1);
+ break;
+ case KBD_CCMD_WRITE_OUTPORT:
+ outport_write(s, val);
+ break;
+ case KBD_CCMD_WRITE_MOUSE:
+ ps2_write_mouse(s->mouse, val);
+ break;
+ default:
+ break;
+ }
+ s->write_cmd = 0;
+}
+
+static void kbd_reset(void *opaque)
+{
+ KBDState *s = opaque;
+
+ s->mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT;
+ s->status = KBD_STAT_CMD | KBD_STAT_UNLOCKED;
+ s->outport = KBD_OUT_RESET | KBD_OUT_A20 | KBD_OUT_ONES;
+ s->outport_present = false;
+}
+
+static uint8_t kbd_outport_default(KBDState *s)
+{
+ return KBD_OUT_RESET | KBD_OUT_A20 | KBD_OUT_ONES
+ | (s->status & KBD_STAT_OBF ? KBD_OUT_OBF : 0)
+ | (s->status & KBD_STAT_MOUSE_OBF ? KBD_OUT_MOUSE_OBF : 0);
+}
+
+static int kbd_outport_post_load(void *opaque, int version_id)
+{
+ KBDState *s = opaque;
+ s->outport_present = true;
+ return 0;
+}
+
+static bool kbd_outport_needed(void *opaque)
+{
+ KBDState *s = opaque;
+ return s->outport != kbd_outport_default(s);
+}
+
+static const VMStateDescription vmstate_kbd_outport = {
+ .name = "pckbd_outport",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = kbd_outport_post_load,
+ .needed = kbd_outport_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(outport, KBDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int kbd_post_load(void *opaque, int version_id)
+{
+ KBDState *s = opaque;
+ if (!s->outport_present) {
+ s->outport = kbd_outport_default(s);
+ }
+ s->outport_present = false;
+ return 0;
+}
+
+static const VMStateDescription vmstate_kbd = {
+ .name = "pckbd",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .post_load = kbd_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(write_cmd, KBDState),
+ VMSTATE_UINT8(status, KBDState),
+ VMSTATE_UINT8(mode, KBDState),
+ VMSTATE_UINT8(pending, KBDState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_kbd_outport,
+ NULL
+ }
+};
+
+/* Memory mapped interface */
+static uint32_t kbd_mm_readb (void *opaque, hwaddr addr)
+{
+ KBDState *s = opaque;
+
+ if (addr & s->mask)
+ return kbd_read_status(s, 0, 1) & 0xff;
+ else
+ return kbd_read_data(s, 0, 1) & 0xff;
+}
+
+static void kbd_mm_writeb (void *opaque, hwaddr addr, uint32_t value)
+{
+ KBDState *s = opaque;
+
+ if (addr & s->mask)
+ kbd_write_command(s, 0, value & 0xff, 1);
+ else
+ kbd_write_data(s, 0, value & 0xff, 1);
+}
+
+static const MemoryRegionOps i8042_mmio_ops = {
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .old_mmio = {
+ .read = { kbd_mm_readb, kbd_mm_readb, kbd_mm_readb },
+ .write = { kbd_mm_writeb, kbd_mm_writeb, kbd_mm_writeb },
+ },
+};
+
+void i8042_mm_init(qemu_irq kbd_irq, qemu_irq mouse_irq,
+ MemoryRegion *region, ram_addr_t size,
+ hwaddr mask)
+{
+ KBDState *s = g_malloc0(sizeof(KBDState));
+
+ s->irq_kbd = kbd_irq;
+ s->irq_mouse = mouse_irq;
+ s->mask = mask;
+
+ vmstate_register(NULL, 0, &vmstate_kbd, s);
+
+ memory_region_init_io(region, NULL, &i8042_mmio_ops, s, "i8042", size);
+
+ s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s);
+ s->mouse = ps2_mouse_init(kbd_update_aux_irq, s);
+ qemu_register_reset(kbd_reset, s);
+}
+
+#define TYPE_I8042 "i8042"
+#define I8042(obj) OBJECT_CHECK(ISAKBDState, (obj), TYPE_I8042)
+
+typedef struct ISAKBDState {
+ ISADevice parent_obj;
+
+ KBDState kbd;
+ MemoryRegion io[2];
+} ISAKBDState;
+
+void i8042_isa_mouse_fake_event(void *opaque)
+{
+ ISADevice *dev = opaque;
+ ISAKBDState *isa = I8042(dev);
+ KBDState *s = &isa->kbd;
+
+ ps2_mouse_fake_event(s->mouse);
+}
+
+void i8042_setup_a20_line(ISADevice *dev, qemu_irq *a20_out)
+{
+ ISAKBDState *isa = I8042(dev);
+ KBDState *s = &isa->kbd;
+
+ s->a20_out = a20_out;
+}
+
+static const VMStateDescription vmstate_kbd_isa = {
+ .name = "pckbd",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(kbd, ISAKBDState, 0, vmstate_kbd, KBDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const MemoryRegionOps i8042_data_ops = {
+ .read = kbd_read_data,
+ .write = kbd_write_data,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps i8042_cmd_ops = {
+ .read = kbd_read_status,
+ .write = kbd_write_command,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void i8042_initfn(Object *obj)
+{
+ ISAKBDState *isa_s = I8042(obj);
+ KBDState *s = &isa_s->kbd;
+
+ memory_region_init_io(isa_s->io + 0, obj, &i8042_data_ops, s,
+ "i8042-data", 1);
+ memory_region_init_io(isa_s->io + 1, obj, &i8042_cmd_ops, s,
+ "i8042-cmd", 1);
+}
+
+static void i8042_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISAKBDState *isa_s = I8042(dev);
+ KBDState *s = &isa_s->kbd;
+
+ isa_init_irq(isadev, &s->irq_kbd, 1);
+ isa_init_irq(isadev, &s->irq_mouse, 12);
+
+ isa_register_ioport(isadev, isa_s->io + 0, 0x60);
+ isa_register_ioport(isadev, isa_s->io + 1, 0x64);
+
+ s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s);
+ s->mouse = ps2_mouse_init(kbd_update_aux_irq, s);
+ qemu_register_reset(kbd_reset, s);
+}
+
+static void i8042_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = i8042_realizefn;
+ dc->vmsd = &vmstate_kbd_isa;
+}
+
+static const TypeInfo i8042_info = {
+ .name = TYPE_I8042,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISAKBDState),
+ .instance_init = i8042_initfn,
+ .class_init = i8042_class_initfn,
+};
+
+static void i8042_register_types(void)
+{
+ type_register_static(&i8042_info);
+}
+
+type_init(i8042_register_types)
diff --git a/hw/input/pl050.c b/hw/input/pl050.c
new file mode 100644
index 00000000..c1b08d5a
--- /dev/null
+++ b/hw/input/pl050.c
@@ -0,0 +1,205 @@
+/*
+ * Arm PrimeCell PL050 Keyboard / Mouse Interface
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/input/ps2.h"
+
+#define TYPE_PL050 "pl050"
+#define PL050(obj) OBJECT_CHECK(PL050State, (obj), TYPE_PL050)
+
+typedef struct PL050State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ void *dev;
+ uint32_t cr;
+ uint32_t clk;
+ uint32_t last;
+ int pending;
+ qemu_irq irq;
+ bool is_mouse;
+} PL050State;
+
+static const VMStateDescription vmstate_pl050 = {
+ .name = "pl050",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cr, PL050State),
+ VMSTATE_UINT32(clk, PL050State),
+ VMSTATE_UINT32(last, PL050State),
+ VMSTATE_INT32(pending, PL050State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define PL050_TXEMPTY (1 << 6)
+#define PL050_TXBUSY (1 << 5)
+#define PL050_RXFULL (1 << 4)
+#define PL050_RXBUSY (1 << 3)
+#define PL050_RXPARITY (1 << 2)
+#define PL050_KMIC (1 << 1)
+#define PL050_KMID (1 << 0)
+
+static const unsigned char pl050_id[] =
+{ 0x50, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static void pl050_update(void *opaque, int level)
+{
+ PL050State *s = (PL050State *)opaque;
+ int raise;
+
+ s->pending = level;
+ raise = (s->pending && (s->cr & 0x10) != 0)
+ || (s->cr & 0x08) != 0;
+ qemu_set_irq(s->irq, raise);
+}
+
+static uint64_t pl050_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL050State *s = (PL050State *)opaque;
+ if (offset >= 0xfe0 && offset < 0x1000)
+ return pl050_id[(offset - 0xfe0) >> 2];
+
+ switch (offset >> 2) {
+ case 0: /* KMICR */
+ return s->cr;
+ case 1: /* KMISTAT */
+ {
+ uint8_t val;
+ uint32_t stat;
+
+ val = s->last;
+ val = val ^ (val >> 4);
+ val = val ^ (val >> 2);
+ val = (val ^ (val >> 1)) & 1;
+
+ stat = PL050_TXEMPTY;
+ if (val)
+ stat |= PL050_RXPARITY;
+ if (s->pending)
+ stat |= PL050_RXFULL;
+
+ return stat;
+ }
+ case 2: /* KMIDATA */
+ if (s->pending)
+ s->last = ps2_read_data(s->dev);
+ return s->last;
+ case 3: /* KMICLKDIV */
+ return s->clk;
+ case 4: /* KMIIR */
+ return s->pending | 2;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl050_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl050_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL050State *s = (PL050State *)opaque;
+ switch (offset >> 2) {
+ case 0: /* KMICR */
+ s->cr = value;
+ pl050_update(s, s->pending);
+ /* ??? Need to implement the enable/disable bit. */
+ break;
+ case 2: /* KMIDATA */
+ /* ??? This should toggle the TX interrupt line. */
+ /* ??? This means kbd/mouse can block each other. */
+ if (s->is_mouse) {
+ ps2_write_mouse(s->dev, value);
+ } else {
+ ps2_write_keyboard(s->dev, value);
+ }
+ break;
+ case 3: /* KMICLKDIV */
+ s->clk = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl050_write: Bad offset %x\n", (int)offset);
+ }
+}
+static const MemoryRegionOps pl050_ops = {
+ .read = pl050_read,
+ .write = pl050_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pl050_initfn(SysBusDevice *dev)
+{
+ PL050State *s = PL050(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl050_ops, s, "pl050", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+ if (s->is_mouse) {
+ s->dev = ps2_mouse_init(pl050_update, s);
+ } else {
+ s->dev = ps2_kbd_init(pl050_update, s);
+ }
+ return 0;
+}
+
+static void pl050_keyboard_init(Object *obj)
+{
+ PL050State *s = PL050(obj);
+
+ s->is_mouse = false;
+}
+
+static void pl050_mouse_init(Object *obj)
+{
+ PL050State *s = PL050(obj);
+
+ s->is_mouse = true;
+}
+
+static const TypeInfo pl050_kbd_info = {
+ .name = "pl050_keyboard",
+ .parent = TYPE_PL050,
+ .instance_init = pl050_keyboard_init,
+};
+
+static const TypeInfo pl050_mouse_info = {
+ .name = "pl050_mouse",
+ .parent = TYPE_PL050,
+ .instance_init = pl050_mouse_init,
+};
+
+static void pl050_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(oc);
+
+ sdc->init = pl050_initfn;
+ dc->vmsd = &vmstate_pl050;
+}
+
+static const TypeInfo pl050_type_info = {
+ .name = TYPE_PL050,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL050State),
+ .abstract = true,
+ .class_init = pl050_class_init,
+};
+
+static void pl050_register_types(void)
+{
+ type_register_static(&pl050_type_info);
+ type_register_static(&pl050_kbd_info);
+ type_register_static(&pl050_mouse_info);
+}
+
+type_init(pl050_register_types)
diff --git a/hw/input/ps2.c b/hw/input/ps2.c
new file mode 100644
index 00000000..fdbe565e
--- /dev/null
+++ b/hw/input/ps2.c
@@ -0,0 +1,807 @@
+/*
+ * QEMU PS/2 keyboard/mouse emulation
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/input/ps2.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "sysemu/sysemu.h"
+
+#include "trace.h"
+
+/* debug PC keyboard */
+//#define DEBUG_KBD
+
+/* debug PC keyboard : only mouse */
+//#define DEBUG_MOUSE
+
+/* Keyboard Commands */
+#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */
+#define KBD_CMD_ECHO 0xEE
+#define KBD_CMD_SCANCODE 0xF0 /* Get/set scancode set */
+#define KBD_CMD_GET_ID 0xF2 /* get keyboard ID */
+#define KBD_CMD_SET_RATE 0xF3 /* Set typematic rate */
+#define KBD_CMD_ENABLE 0xF4 /* Enable scanning */
+#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */
+#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */
+#define KBD_CMD_RESET 0xFF /* Reset */
+
+/* Keyboard Replies */
+#define KBD_REPLY_POR 0xAA /* Power on reset */
+#define KBD_REPLY_ID 0xAB /* Keyboard ID */
+#define KBD_REPLY_ACK 0xFA /* Command ACK */
+#define KBD_REPLY_RESEND 0xFE /* Command NACK, send the cmd again */
+
+/* Mouse Commands */
+#define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */
+#define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */
+#define AUX_SET_RES 0xE8 /* Set resolution */
+#define AUX_GET_SCALE 0xE9 /* Get scaling factor */
+#define AUX_SET_STREAM 0xEA /* Set stream mode */
+#define AUX_POLL 0xEB /* Poll */
+#define AUX_RESET_WRAP 0xEC /* Reset wrap mode */
+#define AUX_SET_WRAP 0xEE /* Set wrap mode */
+#define AUX_SET_REMOTE 0xF0 /* Set remote mode */
+#define AUX_GET_TYPE 0xF2 /* Get type */
+#define AUX_SET_SAMPLE 0xF3 /* Set sample rate */
+#define AUX_ENABLE_DEV 0xF4 /* Enable aux device */
+#define AUX_DISABLE_DEV 0xF5 /* Disable aux device */
+#define AUX_SET_DEFAULT 0xF6
+#define AUX_RESET 0xFF /* Reset aux device */
+#define AUX_ACK 0xFA /* Command byte ACK. */
+
+#define MOUSE_STATUS_REMOTE 0x40
+#define MOUSE_STATUS_ENABLED 0x20
+#define MOUSE_STATUS_SCALE21 0x10
+
+#define PS2_QUEUE_SIZE 16 /* Buffer size required by PS/2 protocol */
+
+typedef struct {
+ /* Keep the data array 256 bytes long, which compatibility
+ with older qemu versions. */
+ uint8_t data[256];
+ int rptr, wptr, count;
+} PS2Queue;
+
+typedef struct {
+ PS2Queue queue;
+ int32_t write_cmd;
+ void (*update_irq)(void *, int);
+ void *update_arg;
+} PS2State;
+
+typedef struct {
+ PS2State common;
+ int scan_enabled;
+ /* QEMU uses translated PC scancodes internally. To avoid multiple
+ conversions we do the translation (if any) in the PS/2 emulation
+ not the keyboard controller. */
+ int translate;
+ int scancode_set; /* 1=XT, 2=AT, 3=PS/2 */
+ int ledstate;
+} PS2KbdState;
+
+typedef struct {
+ PS2State common;
+ uint8_t mouse_status;
+ uint8_t mouse_resolution;
+ uint8_t mouse_sample_rate;
+ uint8_t mouse_wrap;
+ uint8_t mouse_type; /* 0 = PS2, 3 = IMPS/2, 4 = IMEX */
+ uint8_t mouse_detect_state;
+ int mouse_dx; /* current values, needed for 'poll' mode */
+ int mouse_dy;
+ int mouse_dz;
+ uint8_t mouse_buttons;
+} PS2MouseState;
+
+/* Table to convert from PC scancodes to raw scancodes. */
+static const unsigned char ps2_raw_keycode[128] = {
+ 0, 118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85, 102, 13,
+ 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27,
+ 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42,
+ 50, 49, 58, 65, 73, 74, 89, 124, 17, 41, 88, 5, 6, 4, 12, 3,
+ 11, 2, 10, 1, 9, 119, 126, 108, 117, 125, 123, 107, 115, 116, 121, 105,
+114, 122, 112, 113, 127, 96, 97, 120, 7, 15, 23, 31, 39, 47, 55, 63,
+ 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87, 111,
+ 19, 25, 57, 81, 83, 92, 95, 98, 99, 100, 101, 103, 104, 106, 109, 110
+};
+static const unsigned char ps2_raw_keycode_set3[128] = {
+ 0, 8, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85, 102, 13,
+ 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 17, 28, 27,
+ 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 92, 26, 34, 33, 42,
+ 50, 49, 58, 65, 73, 74, 89, 126, 25, 41, 20, 7, 15, 23, 31, 39,
+ 47, 2, 63, 71, 79, 118, 95, 108, 117, 125, 132, 107, 115, 116, 124, 105,
+114, 122, 112, 113, 127, 96, 97, 86, 94, 15, 23, 31, 39, 47, 55, 63,
+ 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87, 111,
+ 19, 25, 57, 81, 83, 92, 95, 98, 99, 100, 101, 103, 104, 106, 109, 110
+};
+
+void ps2_queue(void *opaque, int b)
+{
+ PS2State *s = (PS2State *)opaque;
+ PS2Queue *q = &s->queue;
+
+ if (q->count >= PS2_QUEUE_SIZE - 1)
+ return;
+ q->data[q->wptr] = b;
+ if (++q->wptr == PS2_QUEUE_SIZE)
+ q->wptr = 0;
+ q->count++;
+ s->update_irq(s->update_arg, 1);
+}
+
+/*
+ keycode is expressed as follow:
+ bit 7 - 0 key pressed, 1 = key released
+ bits 6-0 - translated scancode set 2
+ */
+static void ps2_put_keycode(void *opaque, int keycode)
+{
+ PS2KbdState *s = opaque;
+
+ trace_ps2_put_keycode(opaque, keycode);
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
+ /* XXX: add support for scancode set 1 */
+ if (!s->translate && keycode < 0xe0 && s->scancode_set > 1) {
+ if (keycode & 0x80) {
+ ps2_queue(&s->common, 0xf0);
+ }
+ if (s->scancode_set == 2) {
+ keycode = ps2_raw_keycode[keycode & 0x7f];
+ } else if (s->scancode_set == 3) {
+ keycode = ps2_raw_keycode_set3[keycode & 0x7f];
+ }
+ }
+ ps2_queue(&s->common, keycode);
+}
+
+static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ PS2KbdState *s = (PS2KbdState *)dev;
+ int scancodes[3], i, count;
+
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
+ count = qemu_input_key_value_to_scancode(evt->key->key,
+ evt->key->down,
+ scancodes);
+ for (i = 0; i < count; i++) {
+ ps2_put_keycode(s, scancodes[i]);
+ }
+}
+
+uint32_t ps2_read_data(void *opaque)
+{
+ PS2State *s = (PS2State *)opaque;
+ PS2Queue *q;
+ int val, index;
+
+ trace_ps2_read_data(opaque);
+ q = &s->queue;
+ if (q->count == 0) {
+ /* NOTE: if no data left, we return the last keyboard one
+ (needed for EMM386) */
+ /* XXX: need a timer to do things correctly */
+ index = q->rptr - 1;
+ if (index < 0)
+ index = PS2_QUEUE_SIZE - 1;
+ val = q->data[index];
+ } else {
+ val = q->data[q->rptr];
+ if (++q->rptr == PS2_QUEUE_SIZE)
+ q->rptr = 0;
+ q->count--;
+ /* reading deasserts IRQ */
+ s->update_irq(s->update_arg, 0);
+ /* reassert IRQs if data left */
+ s->update_irq(s->update_arg, q->count != 0);
+ }
+ return val;
+}
+
+static void ps2_set_ledstate(PS2KbdState *s, int ledstate)
+{
+ trace_ps2_set_ledstate(s, ledstate);
+ s->ledstate = ledstate;
+ kbd_put_ledstate(ledstate);
+}
+
+static void ps2_reset_keyboard(PS2KbdState *s)
+{
+ trace_ps2_reset_keyboard(s);
+ s->scan_enabled = 1;
+ s->scancode_set = 2;
+ ps2_set_ledstate(s, 0);
+}
+
+void ps2_write_keyboard(void *opaque, int val)
+{
+ PS2KbdState *s = (PS2KbdState *)opaque;
+
+ trace_ps2_write_keyboard(opaque, val);
+ switch(s->common.write_cmd) {
+ default:
+ case -1:
+ switch(val) {
+ case 0x00:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case 0x05:
+ ps2_queue(&s->common, KBD_REPLY_RESEND);
+ break;
+ case KBD_CMD_GET_ID:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ /* We emulate a MF2 AT keyboard here */
+ ps2_queue(&s->common, KBD_REPLY_ID);
+ if (s->translate)
+ ps2_queue(&s->common, 0x41);
+ else
+ ps2_queue(&s->common, 0x83);
+ break;
+ case KBD_CMD_ECHO:
+ ps2_queue(&s->common, KBD_CMD_ECHO);
+ break;
+ case KBD_CMD_ENABLE:
+ s->scan_enabled = 1;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case KBD_CMD_SCANCODE:
+ case KBD_CMD_SET_LEDS:
+ case KBD_CMD_SET_RATE:
+ s->common.write_cmd = val;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case KBD_CMD_RESET_DISABLE:
+ ps2_reset_keyboard(s);
+ s->scan_enabled = 0;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case KBD_CMD_RESET_ENABLE:
+ ps2_reset_keyboard(s);
+ s->scan_enabled = 1;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case KBD_CMD_RESET:
+ ps2_reset_keyboard(s);
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ ps2_queue(&s->common, KBD_REPLY_POR);
+ break;
+ default:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ }
+ break;
+ case KBD_CMD_SCANCODE:
+ if (val == 0) {
+ if (s->scancode_set == 1)
+ ps2_put_keycode(s, 0x43);
+ else if (s->scancode_set == 2)
+ ps2_put_keycode(s, 0x41);
+ else if (s->scancode_set == 3)
+ ps2_put_keycode(s, 0x3f);
+ } else {
+ if (val >= 1 && val <= 3)
+ s->scancode_set = val;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ }
+ s->common.write_cmd = -1;
+ break;
+ case KBD_CMD_SET_LEDS:
+ ps2_set_ledstate(s, val);
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ s->common.write_cmd = -1;
+ break;
+ case KBD_CMD_SET_RATE:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ s->common.write_cmd = -1;
+ break;
+ }
+}
+
+/* Set the scancode translation mode.
+ 0 = raw scancodes.
+ 1 = translated scancodes (used by qemu internally). */
+
+void ps2_keyboard_set_translation(void *opaque, int mode)
+{
+ PS2KbdState *s = (PS2KbdState *)opaque;
+ trace_ps2_keyboard_set_translation(opaque, mode);
+ s->translate = mode;
+}
+
+static void ps2_mouse_send_packet(PS2MouseState *s)
+{
+ unsigned int b;
+ int dx1, dy1, dz1;
+
+ dx1 = s->mouse_dx;
+ dy1 = s->mouse_dy;
+ dz1 = s->mouse_dz;
+ /* XXX: increase range to 8 bits ? */
+ if (dx1 > 127)
+ dx1 = 127;
+ else if (dx1 < -127)
+ dx1 = -127;
+ if (dy1 > 127)
+ dy1 = 127;
+ else if (dy1 < -127)
+ dy1 = -127;
+ b = 0x08 | ((dx1 < 0) << 4) | ((dy1 < 0) << 5) | (s->mouse_buttons & 0x07);
+ ps2_queue(&s->common, b);
+ ps2_queue(&s->common, dx1 & 0xff);
+ ps2_queue(&s->common, dy1 & 0xff);
+ /* extra byte for IMPS/2 or IMEX */
+ switch(s->mouse_type) {
+ default:
+ break;
+ case 3:
+ if (dz1 > 127)
+ dz1 = 127;
+ else if (dz1 < -127)
+ dz1 = -127;
+ ps2_queue(&s->common, dz1 & 0xff);
+ break;
+ case 4:
+ if (dz1 > 7)
+ dz1 = 7;
+ else if (dz1 < -7)
+ dz1 = -7;
+ b = (dz1 & 0x0f) | ((s->mouse_buttons & 0x18) << 1);
+ ps2_queue(&s->common, b);
+ break;
+ }
+
+ trace_ps2_mouse_send_packet(s, dx1, dy1, dz1, b);
+ /* update deltas */
+ s->mouse_dx -= dx1;
+ s->mouse_dy -= dy1;
+ s->mouse_dz -= dz1;
+}
+
+static void ps2_mouse_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ static const int bmap[INPUT_BUTTON_MAX] = {
+ [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON,
+ [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON,
+ [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON,
+ };
+ PS2MouseState *s = (PS2MouseState *)dev;
+
+ /* check if deltas are recorded when disabled */
+ if (!(s->mouse_status & MOUSE_STATUS_ENABLED))
+ return;
+
+ switch (evt->kind) {
+ case INPUT_EVENT_KIND_REL:
+ if (evt->rel->axis == INPUT_AXIS_X) {
+ s->mouse_dx += evt->rel->value;
+ } else if (evt->rel->axis == INPUT_AXIS_Y) {
+ s->mouse_dy -= evt->rel->value;
+ }
+ break;
+
+ case INPUT_EVENT_KIND_BTN:
+ if (evt->btn->down) {
+ s->mouse_buttons |= bmap[evt->btn->button];
+ if (evt->btn->button == INPUT_BUTTON_WHEEL_UP) {
+ s->mouse_dz--;
+ } else if (evt->btn->button == INPUT_BUTTON_WHEEL_DOWN) {
+ s->mouse_dz++;
+ }
+ } else {
+ s->mouse_buttons &= ~bmap[evt->btn->button];
+ }
+ break;
+
+ default:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+static void ps2_mouse_sync(DeviceState *dev)
+{
+ PS2MouseState *s = (PS2MouseState *)dev;
+
+ if (s->mouse_buttons) {
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
+ }
+ if (!(s->mouse_status & MOUSE_STATUS_REMOTE)) {
+ while (s->common.queue.count < PS2_QUEUE_SIZE - 4) {
+ /* if not remote, send event. Multiple events are sent if
+ too big deltas */
+ ps2_mouse_send_packet(s);
+ if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0)
+ break;
+ }
+ }
+}
+
+void ps2_mouse_fake_event(void *opaque)
+{
+ PS2MouseState *s = opaque;
+ trace_ps2_mouse_fake_event(opaque);
+ s->mouse_dx++;
+ ps2_mouse_sync(opaque);
+}
+
+void ps2_write_mouse(void *opaque, int val)
+{
+ PS2MouseState *s = (PS2MouseState *)opaque;
+
+ trace_ps2_write_mouse(opaque, val);
+#ifdef DEBUG_MOUSE
+ printf("kbd: write mouse 0x%02x\n", val);
+#endif
+ switch(s->common.write_cmd) {
+ default:
+ case -1:
+ /* mouse command */
+ if (s->mouse_wrap) {
+ if (val == AUX_RESET_WRAP) {
+ s->mouse_wrap = 0;
+ ps2_queue(&s->common, AUX_ACK);
+ return;
+ } else if (val != AUX_RESET) {
+ ps2_queue(&s->common, val);
+ return;
+ }
+ }
+ switch(val) {
+ case AUX_SET_SCALE11:
+ s->mouse_status &= ~MOUSE_STATUS_SCALE21;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_SCALE21:
+ s->mouse_status |= MOUSE_STATUS_SCALE21;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_STREAM:
+ s->mouse_status &= ~MOUSE_STATUS_REMOTE;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_WRAP:
+ s->mouse_wrap = 1;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_REMOTE:
+ s->mouse_status |= MOUSE_STATUS_REMOTE;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_GET_TYPE:
+ ps2_queue(&s->common, AUX_ACK);
+ ps2_queue(&s->common, s->mouse_type);
+ break;
+ case AUX_SET_RES:
+ case AUX_SET_SAMPLE:
+ s->common.write_cmd = val;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_GET_SCALE:
+ ps2_queue(&s->common, AUX_ACK);
+ ps2_queue(&s->common, s->mouse_status);
+ ps2_queue(&s->common, s->mouse_resolution);
+ ps2_queue(&s->common, s->mouse_sample_rate);
+ break;
+ case AUX_POLL:
+ ps2_queue(&s->common, AUX_ACK);
+ ps2_mouse_send_packet(s);
+ break;
+ case AUX_ENABLE_DEV:
+ s->mouse_status |= MOUSE_STATUS_ENABLED;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_DISABLE_DEV:
+ s->mouse_status &= ~MOUSE_STATUS_ENABLED;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_DEFAULT:
+ s->mouse_sample_rate = 100;
+ s->mouse_resolution = 2;
+ s->mouse_status = 0;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_RESET:
+ s->mouse_sample_rate = 100;
+ s->mouse_resolution = 2;
+ s->mouse_status = 0;
+ s->mouse_type = 0;
+ ps2_queue(&s->common, AUX_ACK);
+ ps2_queue(&s->common, 0xaa);
+ ps2_queue(&s->common, s->mouse_type);
+ break;
+ default:
+ break;
+ }
+ break;
+ case AUX_SET_SAMPLE:
+ s->mouse_sample_rate = val;
+ /* detect IMPS/2 or IMEX */
+ switch(s->mouse_detect_state) {
+ default:
+ case 0:
+ if (val == 200)
+ s->mouse_detect_state = 1;
+ break;
+ case 1:
+ if (val == 100)
+ s->mouse_detect_state = 2;
+ else if (val == 200)
+ s->mouse_detect_state = 3;
+ else
+ s->mouse_detect_state = 0;
+ break;
+ case 2:
+ if (val == 80)
+ s->mouse_type = 3; /* IMPS/2 */
+ s->mouse_detect_state = 0;
+ break;
+ case 3:
+ if (val == 80)
+ s->mouse_type = 4; /* IMEX */
+ s->mouse_detect_state = 0;
+ break;
+ }
+ ps2_queue(&s->common, AUX_ACK);
+ s->common.write_cmd = -1;
+ break;
+ case AUX_SET_RES:
+ s->mouse_resolution = val;
+ ps2_queue(&s->common, AUX_ACK);
+ s->common.write_cmd = -1;
+ break;
+ }
+}
+
+static void ps2_common_reset(PS2State *s)
+{
+ PS2Queue *q;
+ s->write_cmd = -1;
+ q = &s->queue;
+ q->rptr = 0;
+ q->wptr = 0;
+ q->count = 0;
+ s->update_irq(s->update_arg, 0);
+}
+
+static void ps2_common_post_load(PS2State *s)
+{
+ PS2Queue *q = &s->queue;
+ int size;
+ int i;
+ int tmp_data[PS2_QUEUE_SIZE];
+
+ /* set the useful data buffer queue size, < PS2_QUEUE_SIZE */
+ size = q->count > PS2_QUEUE_SIZE ? 0 : q->count;
+
+ /* move the queue elements to the start of data array */
+ if (size > 0) {
+ for (i = 0; i < size; i++) {
+ /* move the queue elements to the temporary buffer */
+ tmp_data[i] = q->data[q->rptr];
+ if (++q->rptr == 256) {
+ q->rptr = 0;
+ }
+ }
+ memcpy(q->data, tmp_data, size);
+ }
+ /* reset rptr/wptr/count */
+ q->rptr = 0;
+ q->wptr = size;
+ q->count = size;
+ s->update_irq(s->update_arg, q->count != 0);
+}
+
+static void ps2_kbd_reset(void *opaque)
+{
+ PS2KbdState *s = (PS2KbdState *) opaque;
+
+ trace_ps2_kbd_reset(opaque);
+ ps2_common_reset(&s->common);
+ s->scan_enabled = 0;
+ s->translate = 0;
+ s->scancode_set = 0;
+}
+
+static void ps2_mouse_reset(void *opaque)
+{
+ PS2MouseState *s = (PS2MouseState *) opaque;
+
+ trace_ps2_mouse_reset(opaque);
+ ps2_common_reset(&s->common);
+ s->mouse_status = 0;
+ s->mouse_resolution = 0;
+ s->mouse_sample_rate = 0;
+ s->mouse_wrap = 0;
+ s->mouse_type = 0;
+ s->mouse_detect_state = 0;
+ s->mouse_dx = 0;
+ s->mouse_dy = 0;
+ s->mouse_dz = 0;
+ s->mouse_buttons = 0;
+}
+
+static const VMStateDescription vmstate_ps2_common = {
+ .name = "PS2 Common State",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(write_cmd, PS2State),
+ VMSTATE_INT32(queue.rptr, PS2State),
+ VMSTATE_INT32(queue.wptr, PS2State),
+ VMSTATE_INT32(queue.count, PS2State),
+ VMSTATE_BUFFER(queue.data, PS2State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool ps2_keyboard_ledstate_needed(void *opaque)
+{
+ PS2KbdState *s = opaque;
+
+ return s->ledstate != 0; /* 0 is default state */
+}
+
+static int ps2_kbd_ledstate_post_load(void *opaque, int version_id)
+{
+ PS2KbdState *s = opaque;
+
+ kbd_put_ledstate(s->ledstate);
+ return 0;
+}
+
+static const VMStateDescription vmstate_ps2_keyboard_ledstate = {
+ .name = "ps2kbd/ledstate",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .post_load = ps2_kbd_ledstate_post_load,
+ .needed = ps2_keyboard_ledstate_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(ledstate, PS2KbdState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int ps2_kbd_post_load(void* opaque, int version_id)
+{
+ PS2KbdState *s = (PS2KbdState*)opaque;
+ PS2State *ps2 = &s->common;
+
+ if (version_id == 2)
+ s->scancode_set=2;
+
+ ps2_common_post_load(ps2);
+
+ return 0;
+}
+
+static void ps2_kbd_pre_save(void *opaque)
+{
+ PS2KbdState *s = (PS2KbdState *)opaque;
+ PS2State *ps2 = &s->common;
+
+ ps2_common_post_load(ps2);
+}
+
+static const VMStateDescription vmstate_ps2_keyboard = {
+ .name = "ps2kbd",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .post_load = ps2_kbd_post_load,
+ .pre_save = ps2_kbd_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(common, PS2KbdState, 0, vmstate_ps2_common, PS2State),
+ VMSTATE_INT32(scan_enabled, PS2KbdState),
+ VMSTATE_INT32(translate, PS2KbdState),
+ VMSTATE_INT32_V(scancode_set, PS2KbdState,3),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_ps2_keyboard_ledstate,
+ NULL
+ }
+};
+
+static int ps2_mouse_post_load(void *opaque, int version_id)
+{
+ PS2MouseState *s = (PS2MouseState *)opaque;
+ PS2State *ps2 = &s->common;
+
+ ps2_common_post_load(ps2);
+
+ return 0;
+}
+
+static void ps2_mouse_pre_save(void *opaque)
+{
+ PS2MouseState *s = (PS2MouseState *)opaque;
+ PS2State *ps2 = &s->common;
+
+ ps2_common_post_load(ps2);
+}
+
+static const VMStateDescription vmstate_ps2_mouse = {
+ .name = "ps2mouse",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = ps2_mouse_post_load,
+ .pre_save = ps2_mouse_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(common, PS2MouseState, 0, vmstate_ps2_common, PS2State),
+ VMSTATE_UINT8(mouse_status, PS2MouseState),
+ VMSTATE_UINT8(mouse_resolution, PS2MouseState),
+ VMSTATE_UINT8(mouse_sample_rate, PS2MouseState),
+ VMSTATE_UINT8(mouse_wrap, PS2MouseState),
+ VMSTATE_UINT8(mouse_type, PS2MouseState),
+ VMSTATE_UINT8(mouse_detect_state, PS2MouseState),
+ VMSTATE_INT32(mouse_dx, PS2MouseState),
+ VMSTATE_INT32(mouse_dy, PS2MouseState),
+ VMSTATE_INT32(mouse_dz, PS2MouseState),
+ VMSTATE_UINT8(mouse_buttons, PS2MouseState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static QemuInputHandler ps2_keyboard_handler = {
+ .name = "QEMU PS/2 Keyboard",
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = ps2_keyboard_event,
+};
+
+void *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg)
+{
+ PS2KbdState *s = (PS2KbdState *)g_malloc0(sizeof(PS2KbdState));
+
+ trace_ps2_kbd_init(s);
+ s->common.update_irq = update_irq;
+ s->common.update_arg = update_arg;
+ s->scancode_set = 2;
+ vmstate_register(NULL, 0, &vmstate_ps2_keyboard, s);
+ qemu_input_handler_register((DeviceState *)s,
+ &ps2_keyboard_handler);
+ qemu_register_reset(ps2_kbd_reset, s);
+ return s;
+}
+
+static QemuInputHandler ps2_mouse_handler = {
+ .name = "QEMU PS/2 Mouse",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+ .event = ps2_mouse_event,
+ .sync = ps2_mouse_sync,
+};
+
+void *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg)
+{
+ PS2MouseState *s = (PS2MouseState *)g_malloc0(sizeof(PS2MouseState));
+
+ trace_ps2_mouse_init(s);
+ s->common.update_irq = update_irq;
+ s->common.update_arg = update_arg;
+ vmstate_register(NULL, 0, &vmstate_ps2_mouse, s);
+ qemu_input_handler_register((DeviceState *)s,
+ &ps2_mouse_handler);
+ qemu_register_reset(ps2_mouse_reset, s);
+ return s;
+}
diff --git a/hw/input/pxa2xx_keypad.c b/hw/input/pxa2xx_keypad.c
new file mode 100644
index 00000000..85011145
--- /dev/null
+++ b/hw/input/pxa2xx_keypad.c
@@ -0,0 +1,334 @@
+/*
+ * Intel PXA27X Keypad Controller emulation.
+ *
+ * Copyright (c) 2007 MontaVista Software, Inc
+ * Written by Armin Kuster <akuster@kama-aina.net>
+ * or <Akuster@mvista.com>
+ *
+ * This code is licensed under the GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "ui/console.h"
+
+/*
+ * Keypad
+ */
+#define KPC 0x00 /* Keypad Interface Control register */
+#define KPDK 0x08 /* Keypad Interface Direct Key register */
+#define KPREC 0x10 /* Keypad Interface Rotary Encoder register */
+#define KPMK 0x18 /* Keypad Interface Matrix Key register */
+#define KPAS 0x20 /* Keypad Interface Automatic Scan register */
+#define KPASMKP0 0x28 /* Keypad Interface Automatic Scan Multiple
+ Key Presser register 0 */
+#define KPASMKP1 0x30 /* Keypad Interface Automatic Scan Multiple
+ Key Presser register 1 */
+#define KPASMKP2 0x38 /* Keypad Interface Automatic Scan Multiple
+ Key Presser register 2 */
+#define KPASMKP3 0x40 /* Keypad Interface Automatic Scan Multiple
+ Key Presser register 3 */
+#define KPKDI 0x48 /* Keypad Interface Key Debounce Interval
+ register */
+
+/* Keypad defines */
+#define KPC_AS (0x1 << 30) /* Automatic Scan bit */
+#define KPC_ASACT (0x1 << 29) /* Automatic Scan on Activity */
+#define KPC_MI (0x1 << 22) /* Matrix interrupt bit */
+#define KPC_IMKP (0x1 << 21) /* Ignore Multiple Key Press */
+#define KPC_MS7 (0x1 << 20) /* Matrix scan line 7 */
+#define KPC_MS6 (0x1 << 19) /* Matrix scan line 6 */
+#define KPC_MS5 (0x1 << 18) /* Matrix scan line 5 */
+#define KPC_MS4 (0x1 << 17) /* Matrix scan line 4 */
+#define KPC_MS3 (0x1 << 16) /* Matrix scan line 3 */
+#define KPC_MS2 (0x1 << 15) /* Matrix scan line 2 */
+#define KPC_MS1 (0x1 << 14) /* Matrix scan line 1 */
+#define KPC_MS0 (0x1 << 13) /* Matrix scan line 0 */
+#define KPC_ME (0x1 << 12) /* Matrix Keypad Enable */
+#define KPC_MIE (0x1 << 11) /* Matrix Interrupt Enable */
+#define KPC_DK_DEB_SEL (0x1 << 9) /* Direct Keypad Debounce Select */
+#define KPC_DI (0x1 << 5) /* Direct key interrupt bit */
+#define KPC_RE_ZERO_DEB (0x1 << 4) /* Rotary Encoder Zero Debounce */
+#define KPC_REE1 (0x1 << 3) /* Rotary Encoder1 Enable */
+#define KPC_REE0 (0x1 << 2) /* Rotary Encoder0 Enable */
+#define KPC_DE (0x1 << 1) /* Direct Keypad Enable */
+#define KPC_DIE (0x1 << 0) /* Direct Keypad interrupt Enable */
+
+#define KPDK_DKP (0x1 << 31)
+#define KPDK_DK7 (0x1 << 7)
+#define KPDK_DK6 (0x1 << 6)
+#define KPDK_DK5 (0x1 << 5)
+#define KPDK_DK4 (0x1 << 4)
+#define KPDK_DK3 (0x1 << 3)
+#define KPDK_DK2 (0x1 << 2)
+#define KPDK_DK1 (0x1 << 1)
+#define KPDK_DK0 (0x1 << 0)
+
+#define KPREC_OF1 (0x1 << 31)
+#define KPREC_UF1 (0x1 << 30)
+#define KPREC_OF0 (0x1 << 15)
+#define KPREC_UF0 (0x1 << 14)
+
+#define KPMK_MKP (0x1 << 31)
+#define KPAS_SO (0x1 << 31)
+#define KPASMKPx_SO (0x1 << 31)
+
+
+#define KPASMKPx_MKC(row, col) (1 << (row + 16 * (col % 2)))
+
+#define PXAKBD_MAXROW 8
+#define PXAKBD_MAXCOL 8
+
+struct PXA2xxKeyPadState {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ const struct keymap *map;
+ int pressed_cnt;
+ int alt_code;
+
+ uint32_t kpc;
+ uint32_t kpdk;
+ uint32_t kprec;
+ uint32_t kpmk;
+ uint32_t kpas;
+ uint32_t kpasmkp[4];
+ uint32_t kpkdi;
+};
+
+static void pxa27x_keypad_find_pressed_key(PXA2xxKeyPadState *kp, int *row, int *col)
+{
+ int i;
+ for (i = 0; i < 4; i++)
+ {
+ *col = i * 2;
+ for (*row = 0; *row < 8; (*row)++) {
+ if (kp->kpasmkp[i] & (1 << *row))
+ return;
+ }
+ *col = i * 2 + 1;
+ for (*row = 0; *row < 8; (*row)++) {
+ if (kp->kpasmkp[i] & (1 << (*row + 16)))
+ return;
+ }
+ }
+}
+
+static void pxa27x_keyboard_event (PXA2xxKeyPadState *kp, int keycode)
+{
+ int row, col, rel, assert_irq = 0;
+ uint32_t val;
+
+ if (keycode == 0xe0) {
+ kp->alt_code = 1;
+ return;
+ }
+
+ if(!(kp->kpc & KPC_ME)) /* skip if not enabled */
+ return;
+
+ rel = (keycode & 0x80) ? 1 : 0; /* key release from qemu */
+ keycode &= ~0x80; /* strip qemu key release bit */
+ if (kp->alt_code) {
+ keycode |= 0x80;
+ kp->alt_code = 0;
+ }
+
+ row = kp->map[keycode].row;
+ col = kp->map[keycode].column;
+ if (row == -1 || col == -1) {
+ return;
+ }
+
+ val = KPASMKPx_MKC(row, col);
+ if (rel) {
+ if (kp->kpasmkp[col / 2] & val) {
+ kp->kpasmkp[col / 2] &= ~val;
+ kp->pressed_cnt--;
+ assert_irq = 1;
+ }
+ } else {
+ if (!(kp->kpasmkp[col / 2] & val)) {
+ kp->kpasmkp[col / 2] |= val;
+ kp->pressed_cnt++;
+ assert_irq = 1;
+ }
+ }
+ kp->kpas = ((kp->pressed_cnt & 0x1f) << 26) | (0xf << 4) | 0xf;
+ if (kp->pressed_cnt == 1) {
+ kp->kpas &= ~((0xf << 4) | 0xf);
+ if (rel) {
+ pxa27x_keypad_find_pressed_key(kp, &row, &col);
+ }
+ kp->kpas |= ((row & 0xf) << 4) | (col & 0xf);
+ }
+
+ if (!(kp->kpc & (KPC_AS | KPC_ASACT)))
+ assert_irq = 0;
+
+ if (assert_irq && (kp->kpc & KPC_MIE)) {
+ kp->kpc |= KPC_MI;
+ qemu_irq_raise(kp->irq);
+ }
+}
+
+static uint64_t pxa2xx_keypad_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque;
+ uint32_t tmp;
+
+ switch (offset) {
+ case KPC:
+ tmp = s->kpc;
+ if(tmp & KPC_MI)
+ s->kpc &= ~(KPC_MI);
+ if(tmp & KPC_DI)
+ s->kpc &= ~(KPC_DI);
+ qemu_irq_lower(s->irq);
+ return tmp;
+ break;
+ case KPDK:
+ return s->kpdk;
+ break;
+ case KPREC:
+ tmp = s->kprec;
+ if(tmp & KPREC_OF1)
+ s->kprec &= ~(KPREC_OF1);
+ if(tmp & KPREC_UF1)
+ s->kprec &= ~(KPREC_UF1);
+ if(tmp & KPREC_OF0)
+ s->kprec &= ~(KPREC_OF0);
+ if(tmp & KPREC_UF0)
+ s->kprec &= ~(KPREC_UF0);
+ return tmp;
+ break;
+ case KPMK:
+ tmp = s->kpmk;
+ if(tmp & KPMK_MKP)
+ s->kpmk &= ~(KPMK_MKP);
+ return tmp;
+ break;
+ case KPAS:
+ return s->kpas;
+ break;
+ case KPASMKP0:
+ return s->kpasmkp[0];
+ break;
+ case KPASMKP1:
+ return s->kpasmkp[1];
+ break;
+ case KPASMKP2:
+ return s->kpasmkp[2];
+ break;
+ case KPASMKP3:
+ return s->kpasmkp[3];
+ break;
+ case KPKDI:
+ return s->kpkdi;
+ break;
+ default:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_keypad_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque;
+
+ switch (offset) {
+ case KPC:
+ s->kpc = value;
+ if (s->kpc & KPC_AS) {
+ s->kpc &= ~(KPC_AS);
+ }
+ break;
+ case KPDK:
+ s->kpdk = value;
+ break;
+ case KPREC:
+ s->kprec = value;
+ break;
+ case KPMK:
+ s->kpmk = value;
+ break;
+ case KPAS:
+ s->kpas = value;
+ break;
+ case KPASMKP0:
+ s->kpasmkp[0] = value;
+ break;
+ case KPASMKP1:
+ s->kpasmkp[1] = value;
+ break;
+ case KPASMKP2:
+ s->kpasmkp[2] = value;
+ break;
+ case KPASMKP3:
+ s->kpasmkp[3] = value;
+ break;
+ case KPKDI:
+ s->kpkdi = value;
+ break;
+
+ default:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_keypad_ops = {
+ .read = pxa2xx_keypad_read,
+ .write = pxa2xx_keypad_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pxa2xx_keypad = {
+ .name = "pxa2xx_keypad",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(kpc, PXA2xxKeyPadState),
+ VMSTATE_UINT32(kpdk, PXA2xxKeyPadState),
+ VMSTATE_UINT32(kprec, PXA2xxKeyPadState),
+ VMSTATE_UINT32(kpmk, PXA2xxKeyPadState),
+ VMSTATE_UINT32(kpas, PXA2xxKeyPadState),
+ VMSTATE_UINT32_ARRAY(kpasmkp, PXA2xxKeyPadState, 4),
+ VMSTATE_UINT32(kpkdi, PXA2xxKeyPadState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+PXA2xxKeyPadState *pxa27x_keypad_init(MemoryRegion *sysmem,
+ hwaddr base,
+ qemu_irq irq)
+{
+ PXA2xxKeyPadState *s;
+
+ s = (PXA2xxKeyPadState *) g_malloc0(sizeof(PXA2xxKeyPadState));
+ s->irq = irq;
+
+ memory_region_init_io(&s->iomem, NULL, &pxa2xx_keypad_ops, s,
+ "pxa2xx-keypad", 0x00100000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_keypad, s);
+
+ return s;
+}
+
+void pxa27x_register_keypad(PXA2xxKeyPadState *kp,
+ const struct keymap *map, int size)
+{
+ if(!map || size < 0x80) {
+ fprintf(stderr, "%s - No PXA keypad map defined\n", __FUNCTION__);
+ exit(-1);
+ }
+
+ kp->map = map;
+ qemu_add_kbd_event_handler((QEMUPutKBDEvent *) pxa27x_keyboard_event, kp);
+}
diff --git a/hw/input/stellaris_input.c b/hw/input/stellaris_input.c
new file mode 100644
index 00000000..0609e808
--- /dev/null
+++ b/hw/input/stellaris_input.c
@@ -0,0 +1,87 @@
+/*
+ * Gamepad style buttons connected to IRQ/GPIO lines
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+#include "hw/hw.h"
+#include "hw/devices.h"
+#include "ui/console.h"
+
+typedef struct {
+ qemu_irq irq;
+ int keycode;
+ uint8_t pressed;
+} gamepad_button;
+
+typedef struct {
+ gamepad_button *buttons;
+ int num_buttons;
+ int extension;
+} gamepad_state;
+
+static void stellaris_gamepad_put_key(void * opaque, int keycode)
+{
+ gamepad_state *s = (gamepad_state *)opaque;
+ int i;
+ int down;
+
+ if (keycode == 0xe0 && !s->extension) {
+ s->extension = 0x80;
+ return;
+ }
+
+ down = (keycode & 0x80) == 0;
+ keycode = (keycode & 0x7f) | s->extension;
+
+ for (i = 0; i < s->num_buttons; i++) {
+ if (s->buttons[i].keycode == keycode
+ && s->buttons[i].pressed != down) {
+ s->buttons[i].pressed = down;
+ qemu_set_irq(s->buttons[i].irq, down);
+ }
+ }
+
+ s->extension = 0;
+}
+
+static const VMStateDescription vmstate_stellaris_button = {
+ .name = "stellaris_button",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(pressed, gamepad_button),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_stellaris_gamepad = {
+ .name = "stellaris_gamepad",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(extension, gamepad_state),
+ VMSTATE_STRUCT_VARRAY_INT32(buttons, gamepad_state, num_buttons, 0,
+ vmstate_stellaris_button, gamepad_button),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* Returns an array 5 ouput slots. */
+void stellaris_gamepad_init(int n, qemu_irq *irq, const int *keycode)
+{
+ gamepad_state *s;
+ int i;
+
+ s = (gamepad_state *)g_malloc0(sizeof (gamepad_state));
+ s->buttons = (gamepad_button *)g_malloc0(n * sizeof (gamepad_button));
+ for (i = 0; i < n; i++) {
+ s->buttons[i].irq = irq[i];
+ s->buttons[i].keycode = keycode[i];
+ }
+ s->num_buttons = n;
+ qemu_add_kbd_event_handler(stellaris_gamepad_put_key, s);
+ vmstate_register(NULL, -1, &vmstate_stellaris_gamepad, s);
+}
diff --git a/hw/input/tsc2005.c b/hw/input/tsc2005.c
new file mode 100644
index 00000000..21d4f4db
--- /dev/null
+++ b/hw/input/tsc2005.c
@@ -0,0 +1,593 @@
+/*
+ * TI TSC2005 emulator.
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "ui/console.h"
+#include "hw/devices.h"
+
+#define TSC_CUT_RESOLUTION(value, p) ((value) >> (16 - (p ? 12 : 10)))
+
+typedef struct {
+ qemu_irq pint; /* Combination of the nPENIRQ and DAV signals */
+ QEMUTimer *timer;
+ uint16_t model;
+
+ int x, y;
+ int pressure;
+
+ int state, reg, irq, command;
+ uint16_t data, dav;
+
+ int busy;
+ int enabled;
+ int host_mode;
+ int function;
+ int nextfunction;
+ int precision;
+ int nextprecision;
+ int filter;
+ int pin_func;
+ int timing[2];
+ int noise;
+ int reset;
+ int pdst;
+ int pnd0;
+ uint16_t temp_thr[2];
+ uint16_t aux_thr[2];
+
+ int tr[8];
+} TSC2005State;
+
+enum {
+ TSC_MODE_XYZ_SCAN = 0x0,
+ TSC_MODE_XY_SCAN,
+ TSC_MODE_X,
+ TSC_MODE_Y,
+ TSC_MODE_Z,
+ TSC_MODE_AUX,
+ TSC_MODE_TEMP1,
+ TSC_MODE_TEMP2,
+ TSC_MODE_AUX_SCAN,
+ TSC_MODE_X_TEST,
+ TSC_MODE_Y_TEST,
+ TSC_MODE_TS_TEST,
+ TSC_MODE_RESERVED,
+ TSC_MODE_XX_DRV,
+ TSC_MODE_YY_DRV,
+ TSC_MODE_YX_DRV,
+};
+
+static const uint16_t mode_regs[16] = {
+ 0xf000, /* X, Y, Z scan */
+ 0xc000, /* X, Y scan */
+ 0x8000, /* X */
+ 0x4000, /* Y */
+ 0x3000, /* Z */
+ 0x0800, /* AUX */
+ 0x0400, /* TEMP1 */
+ 0x0200, /* TEMP2 */
+ 0x0800, /* AUX scan */
+ 0x0040, /* X test */
+ 0x0020, /* Y test */
+ 0x0080, /* Short-circuit test */
+ 0x0000, /* Reserved */
+ 0x0000, /* X+, X- drivers */
+ 0x0000, /* Y+, Y- drivers */
+ 0x0000, /* Y+, X- drivers */
+};
+
+#define X_TRANSFORM(s) \
+ ((s->y * s->tr[0] - s->x * s->tr[1]) / s->tr[2] + s->tr[3])
+#define Y_TRANSFORM(s) \
+ ((s->y * s->tr[4] - s->x * s->tr[5]) / s->tr[6] + s->tr[7])
+#define Z1_TRANSFORM(s) \
+ ((400 - ((s)->x >> 7) + ((s)->pressure << 10)) << 4)
+#define Z2_TRANSFORM(s) \
+ ((4000 + ((s)->y >> 7) - ((s)->pressure << 10)) << 4)
+
+#define AUX_VAL (700 << 4) /* +/- 3 at 12-bit */
+#define TEMP1_VAL (1264 << 4) /* +/- 5 at 12-bit */
+#define TEMP2_VAL (1531 << 4) /* +/- 5 at 12-bit */
+
+static uint16_t tsc2005_read(TSC2005State *s, int reg)
+{
+ uint16_t ret;
+
+ switch (reg) {
+ case 0x0: /* X */
+ s->dav &= ~mode_regs[TSC_MODE_X];
+ return TSC_CUT_RESOLUTION(X_TRANSFORM(s), s->precision) +
+ (s->noise & 3);
+ case 0x1: /* Y */
+ s->dav &= ~mode_regs[TSC_MODE_Y];
+ s->noise ++;
+ return TSC_CUT_RESOLUTION(Y_TRANSFORM(s), s->precision) ^
+ (s->noise & 3);
+ case 0x2: /* Z1 */
+ s->dav &= 0xdfff;
+ return TSC_CUT_RESOLUTION(Z1_TRANSFORM(s), s->precision) -
+ (s->noise & 3);
+ case 0x3: /* Z2 */
+ s->dav &= 0xefff;
+ return TSC_CUT_RESOLUTION(Z2_TRANSFORM(s), s->precision) |
+ (s->noise & 3);
+
+ case 0x4: /* AUX */
+ s->dav &= ~mode_regs[TSC_MODE_AUX];
+ return TSC_CUT_RESOLUTION(AUX_VAL, s->precision);
+
+ case 0x5: /* TEMP1 */
+ s->dav &= ~mode_regs[TSC_MODE_TEMP1];
+ return TSC_CUT_RESOLUTION(TEMP1_VAL, s->precision) -
+ (s->noise & 5);
+ case 0x6: /* TEMP2 */
+ s->dav &= 0xdfff;
+ s->dav &= ~mode_regs[TSC_MODE_TEMP2];
+ return TSC_CUT_RESOLUTION(TEMP2_VAL, s->precision) ^
+ (s->noise & 3);
+
+ case 0x7: /* Status */
+ ret = s->dav | (s->reset << 7) | (s->pdst << 2) | 0x0;
+ s->dav &= ~(mode_regs[TSC_MODE_X_TEST] | mode_regs[TSC_MODE_Y_TEST] |
+ mode_regs[TSC_MODE_TS_TEST]);
+ s->reset = 1;
+ return ret;
+
+ case 0x8: /* AUX high treshold */
+ return s->aux_thr[1];
+ case 0x9: /* AUX low treshold */
+ return s->aux_thr[0];
+
+ case 0xa: /* TEMP high treshold */
+ return s->temp_thr[1];
+ case 0xb: /* TEMP low treshold */
+ return s->temp_thr[0];
+
+ case 0xc: /* CFR0 */
+ return (s->pressure << 15) | ((!s->busy) << 14) |
+ (s->nextprecision << 13) | s->timing[0];
+ case 0xd: /* CFR1 */
+ return s->timing[1];
+ case 0xe: /* CFR2 */
+ return (s->pin_func << 14) | s->filter;
+
+ case 0xf: /* Function select status */
+ return s->function >= 0 ? 1 << s->function : 0;
+ }
+
+ /* Never gets here */
+ return 0xffff;
+}
+
+static void tsc2005_write(TSC2005State *s, int reg, uint16_t data)
+{
+ switch (reg) {
+ case 0x8: /* AUX high treshold */
+ s->aux_thr[1] = data;
+ break;
+ case 0x9: /* AUX low treshold */
+ s->aux_thr[0] = data;
+ break;
+
+ case 0xa: /* TEMP high treshold */
+ s->temp_thr[1] = data;
+ break;
+ case 0xb: /* TEMP low treshold */
+ s->temp_thr[0] = data;
+ break;
+
+ case 0xc: /* CFR0 */
+ s->host_mode = data >> 15;
+ if (s->enabled != !(data & 0x4000)) {
+ s->enabled = !(data & 0x4000);
+ fprintf(stderr, "%s: touchscreen sense %sabled\n",
+ __FUNCTION__, s->enabled ? "en" : "dis");
+ if (s->busy && !s->enabled)
+ timer_del(s->timer);
+ s->busy &= s->enabled;
+ }
+ s->nextprecision = (data >> 13) & 1;
+ s->timing[0] = data & 0x1fff;
+ if ((s->timing[0] >> 11) == 3)
+ fprintf(stderr, "%s: illegal conversion clock setting\n",
+ __FUNCTION__);
+ break;
+ case 0xd: /* CFR1 */
+ s->timing[1] = data & 0xf07;
+ break;
+ case 0xe: /* CFR2 */
+ s->pin_func = (data >> 14) & 3;
+ s->filter = data & 0x3fff;
+ break;
+
+ default:
+ fprintf(stderr, "%s: write into read-only register %x\n",
+ __FUNCTION__, reg);
+ }
+}
+
+/* This handles most of the chip's logic. */
+static void tsc2005_pin_update(TSC2005State *s)
+{
+ int64_t expires;
+ int pin_state;
+
+ switch (s->pin_func) {
+ case 0:
+ pin_state = !s->pressure && !!s->dav;
+ break;
+ case 1:
+ case 3:
+ default:
+ pin_state = !s->dav;
+ break;
+ case 2:
+ pin_state = !s->pressure;
+ }
+
+ if (pin_state != s->irq) {
+ s->irq = pin_state;
+ qemu_set_irq(s->pint, s->irq);
+ }
+
+ switch (s->nextfunction) {
+ case TSC_MODE_XYZ_SCAN:
+ case TSC_MODE_XY_SCAN:
+ if (!s->host_mode && s->dav)
+ s->enabled = 0;
+ if (!s->pressure)
+ return;
+ /* Fall through */
+ case TSC_MODE_AUX_SCAN:
+ break;
+
+ case TSC_MODE_X:
+ case TSC_MODE_Y:
+ case TSC_MODE_Z:
+ if (!s->pressure)
+ return;
+ /* Fall through */
+ case TSC_MODE_AUX:
+ case TSC_MODE_TEMP1:
+ case TSC_MODE_TEMP2:
+ case TSC_MODE_X_TEST:
+ case TSC_MODE_Y_TEST:
+ case TSC_MODE_TS_TEST:
+ if (s->dav)
+ s->enabled = 0;
+ break;
+
+ case TSC_MODE_RESERVED:
+ case TSC_MODE_XX_DRV:
+ case TSC_MODE_YY_DRV:
+ case TSC_MODE_YX_DRV:
+ default:
+ return;
+ }
+
+ if (!s->enabled || s->busy)
+ return;
+
+ s->busy = 1;
+ s->precision = s->nextprecision;
+ s->function = s->nextfunction;
+ s->pdst = !s->pnd0; /* Synchronised on internal clock */
+ expires = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() >> 7);
+ timer_mod(s->timer, expires);
+}
+
+static void tsc2005_reset(TSC2005State *s)
+{
+ s->state = 0;
+ s->pin_func = 0;
+ s->enabled = 0;
+ s->busy = 0;
+ s->nextprecision = 0;
+ s->nextfunction = 0;
+ s->timing[0] = 0;
+ s->timing[1] = 0;
+ s->irq = 0;
+ s->dav = 0;
+ s->reset = 0;
+ s->pdst = 1;
+ s->pnd0 = 0;
+ s->function = -1;
+ s->temp_thr[0] = 0x000;
+ s->temp_thr[1] = 0xfff;
+ s->aux_thr[0] = 0x000;
+ s->aux_thr[1] = 0xfff;
+
+ tsc2005_pin_update(s);
+}
+
+static uint8_t tsc2005_txrx_word(void *opaque, uint8_t value)
+{
+ TSC2005State *s = opaque;
+ uint32_t ret = 0;
+
+ switch (s->state ++) {
+ case 0:
+ if (value & 0x80) {
+ /* Command */
+ if (value & (1 << 1))
+ tsc2005_reset(s);
+ else {
+ s->nextfunction = (value >> 3) & 0xf;
+ s->nextprecision = (value >> 2) & 1;
+ if (s->enabled != !(value & 1)) {
+ s->enabled = !(value & 1);
+ fprintf(stderr, "%s: touchscreen sense %sabled\n",
+ __FUNCTION__, s->enabled ? "en" : "dis");
+ if (s->busy && !s->enabled)
+ timer_del(s->timer);
+ s->busy &= s->enabled;
+ }
+ tsc2005_pin_update(s);
+ }
+
+ s->state = 0;
+ } else if (value) {
+ /* Data transfer */
+ s->reg = (value >> 3) & 0xf;
+ s->pnd0 = (value >> 1) & 1;
+ s->command = value & 1;
+
+ if (s->command) {
+ /* Read */
+ s->data = tsc2005_read(s, s->reg);
+ tsc2005_pin_update(s);
+ } else
+ s->data = 0;
+ } else
+ s->state = 0;
+ break;
+
+ case 1:
+ if (s->command)
+ ret = (s->data >> 8) & 0xff;
+ else
+ s->data |= value << 8;
+ break;
+
+ case 2:
+ if (s->command)
+ ret = s->data & 0xff;
+ else {
+ s->data |= value;
+ tsc2005_write(s, s->reg, s->data);
+ tsc2005_pin_update(s);
+ }
+
+ s->state = 0;
+ break;
+ }
+
+ return ret;
+}
+
+uint32_t tsc2005_txrx(void *opaque, uint32_t value, int len)
+{
+ uint32_t ret = 0;
+
+ len &= ~7;
+ while (len > 0) {
+ len -= 8;
+ ret |= tsc2005_txrx_word(opaque, (value >> len) & 0xff) << len;
+ }
+
+ return ret;
+}
+
+static void tsc2005_timer_tick(void *opaque)
+{
+ TSC2005State *s = opaque;
+
+ /* Timer ticked -- a set of conversions has been finished. */
+
+ if (!s->busy)
+ return;
+
+ s->busy = 0;
+ s->dav |= mode_regs[s->function];
+ s->function = -1;
+ tsc2005_pin_update(s);
+}
+
+static void tsc2005_touchscreen_event(void *opaque,
+ int x, int y, int z, int buttons_state)
+{
+ TSC2005State *s = opaque;
+ int p = s->pressure;
+
+ if (buttons_state) {
+ s->x = x;
+ s->y = y;
+ }
+ s->pressure = !!buttons_state;
+
+ /*
+ * Note: We would get better responsiveness in the guest by
+ * signaling TS events immediately, but for now we simulate
+ * the first conversion delay for sake of correctness.
+ */
+ if (p != s->pressure)
+ tsc2005_pin_update(s);
+}
+
+static void tsc2005_save(QEMUFile *f, void *opaque)
+{
+ TSC2005State *s = (TSC2005State *) opaque;
+ int i;
+
+ qemu_put_be16(f, s->x);
+ qemu_put_be16(f, s->y);
+ qemu_put_byte(f, s->pressure);
+
+ qemu_put_byte(f, s->state);
+ qemu_put_byte(f, s->reg);
+ qemu_put_byte(f, s->command);
+
+ qemu_put_byte(f, s->irq);
+ qemu_put_be16s(f, &s->dav);
+ qemu_put_be16s(f, &s->data);
+
+ timer_put(f, s->timer);
+ qemu_put_byte(f, s->enabled);
+ qemu_put_byte(f, s->host_mode);
+ qemu_put_byte(f, s->function);
+ qemu_put_byte(f, s->nextfunction);
+ qemu_put_byte(f, s->precision);
+ qemu_put_byte(f, s->nextprecision);
+ qemu_put_be16(f, s->filter);
+ qemu_put_byte(f, s->pin_func);
+ qemu_put_be16(f, s->timing[0]);
+ qemu_put_be16(f, s->timing[1]);
+ qemu_put_be16s(f, &s->temp_thr[0]);
+ qemu_put_be16s(f, &s->temp_thr[1]);
+ qemu_put_be16s(f, &s->aux_thr[0]);
+ qemu_put_be16s(f, &s->aux_thr[1]);
+ qemu_put_be32(f, s->noise);
+ qemu_put_byte(f, s->reset);
+ qemu_put_byte(f, s->pdst);
+ qemu_put_byte(f, s->pnd0);
+
+ for (i = 0; i < 8; i ++)
+ qemu_put_be32(f, s->tr[i]);
+}
+
+static int tsc2005_load(QEMUFile *f, void *opaque, int version_id)
+{
+ TSC2005State *s = (TSC2005State *) opaque;
+ int i;
+
+ s->x = qemu_get_be16(f);
+ s->y = qemu_get_be16(f);
+ s->pressure = qemu_get_byte(f);
+
+ s->state = qemu_get_byte(f);
+ s->reg = qemu_get_byte(f);
+ s->command = qemu_get_byte(f);
+
+ s->irq = qemu_get_byte(f);
+ qemu_get_be16s(f, &s->dav);
+ qemu_get_be16s(f, &s->data);
+
+ timer_get(f, s->timer);
+ s->enabled = qemu_get_byte(f);
+ s->host_mode = qemu_get_byte(f);
+ s->function = qemu_get_byte(f);
+ s->nextfunction = qemu_get_byte(f);
+ s->precision = qemu_get_byte(f);
+ s->nextprecision = qemu_get_byte(f);
+ s->filter = qemu_get_be16(f);
+ s->pin_func = qemu_get_byte(f);
+ s->timing[0] = qemu_get_be16(f);
+ s->timing[1] = qemu_get_be16(f);
+ qemu_get_be16s(f, &s->temp_thr[0]);
+ qemu_get_be16s(f, &s->temp_thr[1]);
+ qemu_get_be16s(f, &s->aux_thr[0]);
+ qemu_get_be16s(f, &s->aux_thr[1]);
+ s->noise = qemu_get_be32(f);
+ s->reset = qemu_get_byte(f);
+ s->pdst = qemu_get_byte(f);
+ s->pnd0 = qemu_get_byte(f);
+
+ for (i = 0; i < 8; i ++)
+ s->tr[i] = qemu_get_be32(f);
+
+ s->busy = timer_pending(s->timer);
+ tsc2005_pin_update(s);
+
+ return 0;
+}
+
+void *tsc2005_init(qemu_irq pintdav)
+{
+ TSC2005State *s;
+
+ s = (TSC2005State *)
+ g_malloc0(sizeof(TSC2005State));
+ s->x = 400;
+ s->y = 240;
+ s->pressure = 0;
+ s->precision = s->nextprecision = 0;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc2005_timer_tick, s);
+ s->pint = pintdav;
+ s->model = 0x2005;
+
+ s->tr[0] = 0;
+ s->tr[1] = 1;
+ s->tr[2] = 1;
+ s->tr[3] = 0;
+ s->tr[4] = 1;
+ s->tr[5] = 0;
+ s->tr[6] = 1;
+ s->tr[7] = 0;
+
+ tsc2005_reset(s);
+
+ qemu_add_mouse_event_handler(tsc2005_touchscreen_event, s, 1,
+ "QEMU TSC2005-driven Touchscreen");
+
+ qemu_register_reset((void *) tsc2005_reset, s);
+ register_savevm(NULL, "tsc2005", -1, 0, tsc2005_save, tsc2005_load, s);
+
+ return s;
+}
+
+/*
+ * Use tslib generated calibration data to generate ADC input values
+ * from the touchscreen. Assuming 12-bit precision was used during
+ * tslib calibration.
+ */
+void tsc2005_set_transform(void *opaque, MouseTransformInfo *info)
+{
+ TSC2005State *s = (TSC2005State *) opaque;
+
+ /* This version assumes touchscreen X & Y axis are parallel or
+ * perpendicular to LCD's X & Y axis in some way. */
+ if (abs(info->a[0]) > abs(info->a[1])) {
+ s->tr[0] = 0;
+ s->tr[1] = -info->a[6] * info->x;
+ s->tr[2] = info->a[0];
+ s->tr[3] = -info->a[2] / info->a[0];
+ s->tr[4] = info->a[6] * info->y;
+ s->tr[5] = 0;
+ s->tr[6] = info->a[4];
+ s->tr[7] = -info->a[5] / info->a[4];
+ } else {
+ s->tr[0] = info->a[6] * info->y;
+ s->tr[1] = 0;
+ s->tr[2] = info->a[1];
+ s->tr[3] = -info->a[2] / info->a[1];
+ s->tr[4] = 0;
+ s->tr[5] = -info->a[6] * info->x;
+ s->tr[6] = info->a[3];
+ s->tr[7] = -info->a[5] / info->a[3];
+ }
+
+ s->tr[0] >>= 11;
+ s->tr[1] >>= 11;
+ s->tr[3] <<= 4;
+ s->tr[4] >>= 11;
+ s->tr[5] >>= 11;
+ s->tr[7] <<= 4;
+}
diff --git a/hw/input/tsc210x.c b/hw/input/tsc210x.c
new file mode 100644
index 00000000..fae33856
--- /dev/null
+++ b/hw/input/tsc210x.c
@@ -0,0 +1,1275 @@
+/*
+ * TI TSC2102 (touchscreen/sensors/audio controller) emulator.
+ * TI TSC2301 (touchscreen/sensors/keypad).
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "audio/audio.h"
+#include "qemu/timer.h"
+#include "ui/console.h"
+#include "hw/arm/omap.h" /* For I2SCodec and uWireSlave */
+#include "hw/devices.h"
+
+#define TSC_DATA_REGISTERS_PAGE 0x0
+#define TSC_CONTROL_REGISTERS_PAGE 0x1
+#define TSC_AUDIO_REGISTERS_PAGE 0x2
+
+#define TSC_VERBOSE
+
+#define TSC_CUT_RESOLUTION(value, p) ((value) >> (16 - resolution[p]))
+
+typedef struct {
+ qemu_irq pint;
+ qemu_irq kbint;
+ qemu_irq davint;
+ QEMUTimer *timer;
+ QEMUSoundCard card;
+ uWireSlave chip;
+ I2SCodec codec;
+ uint8_t in_fifo[16384];
+ uint8_t out_fifo[16384];
+ uint16_t model;
+
+ int x, y;
+ int pressure;
+
+ int state, page, offset, irq;
+ uint16_t command, dav;
+
+ int busy;
+ int enabled;
+ int host_mode;
+ int function;
+ int nextfunction;
+ int precision;
+ int nextprecision;
+ int filter;
+ int pin_func;
+ int ref;
+ int timing;
+ int noise;
+
+ uint16_t audio_ctrl1;
+ uint16_t audio_ctrl2;
+ uint16_t audio_ctrl3;
+ uint16_t pll[3];
+ uint16_t volume;
+ int64_t volume_change;
+ int softstep;
+ uint16_t dac_power;
+ int64_t powerdown;
+ uint16_t filter_data[0x14];
+
+ const char *name;
+ SWVoiceIn *adc_voice[1];
+ SWVoiceOut *dac_voice[1];
+ int i2s_rx_rate;
+ int i2s_tx_rate;
+
+ int tr[8];
+
+ struct {
+ uint16_t down;
+ uint16_t mask;
+ int scan;
+ int debounce;
+ int mode;
+ int intr;
+ } kb;
+} TSC210xState;
+
+static const int resolution[4] = { 12, 8, 10, 12 };
+
+#define TSC_MODE_NO_SCAN 0x0
+#define TSC_MODE_XY_SCAN 0x1
+#define TSC_MODE_XYZ_SCAN 0x2
+#define TSC_MODE_X 0x3
+#define TSC_MODE_Y 0x4
+#define TSC_MODE_Z 0x5
+#define TSC_MODE_BAT1 0x6
+#define TSC_MODE_BAT2 0x7
+#define TSC_MODE_AUX 0x8
+#define TSC_MODE_AUX_SCAN 0x9
+#define TSC_MODE_TEMP1 0xa
+#define TSC_MODE_PORT_SCAN 0xb
+#define TSC_MODE_TEMP2 0xc
+#define TSC_MODE_XX_DRV 0xd
+#define TSC_MODE_YY_DRV 0xe
+#define TSC_MODE_YX_DRV 0xf
+
+static const uint16_t mode_regs[16] = {
+ 0x0000, /* No scan */
+ 0x0600, /* X, Y scan */
+ 0x0780, /* X, Y, Z scan */
+ 0x0400, /* X */
+ 0x0200, /* Y */
+ 0x0180, /* Z */
+ 0x0040, /* BAT1 */
+ 0x0030, /* BAT2 */
+ 0x0010, /* AUX */
+ 0x0010, /* AUX scan */
+ 0x0004, /* TEMP1 */
+ 0x0070, /* Port scan */
+ 0x0002, /* TEMP2 */
+ 0x0000, /* X+, X- drivers */
+ 0x0000, /* Y+, Y- drivers */
+ 0x0000, /* Y+, X- drivers */
+};
+
+#define X_TRANSFORM(s) \
+ ((s->y * s->tr[0] - s->x * s->tr[1]) / s->tr[2] + s->tr[3])
+#define Y_TRANSFORM(s) \
+ ((s->y * s->tr[4] - s->x * s->tr[5]) / s->tr[6] + s->tr[7])
+#define Z1_TRANSFORM(s) \
+ ((400 - ((s)->x >> 7) + ((s)->pressure << 10)) << 4)
+#define Z2_TRANSFORM(s) \
+ ((4000 + ((s)->y >> 7) - ((s)->pressure << 10)) << 4)
+
+#define BAT1_VAL 0x8660
+#define BAT2_VAL 0x0000
+#define AUX1_VAL 0x35c0
+#define AUX2_VAL 0xffff
+#define TEMP1_VAL 0x8c70
+#define TEMP2_VAL 0xa5b0
+
+#define TSC_POWEROFF_DELAY 50
+#define TSC_SOFTSTEP_DELAY 50
+
+static void tsc210x_reset(TSC210xState *s)
+{
+ s->state = 0;
+ s->pin_func = 2;
+ s->enabled = 0;
+ s->busy = 0;
+ s->nextfunction = 0;
+ s->ref = 0;
+ s->timing = 0;
+ s->irq = 0;
+ s->dav = 0;
+
+ s->audio_ctrl1 = 0x0000;
+ s->audio_ctrl2 = 0x4410;
+ s->audio_ctrl3 = 0x0000;
+ s->pll[0] = 0x1004;
+ s->pll[1] = 0x0000;
+ s->pll[2] = 0x1fff;
+ s->volume = 0xffff;
+ s->dac_power = 0x8540;
+ s->softstep = 1;
+ s->volume_change = 0;
+ s->powerdown = 0;
+ s->filter_data[0x00] = 0x6be3;
+ s->filter_data[0x01] = 0x9666;
+ s->filter_data[0x02] = 0x675d;
+ s->filter_data[0x03] = 0x6be3;
+ s->filter_data[0x04] = 0x9666;
+ s->filter_data[0x05] = 0x675d;
+ s->filter_data[0x06] = 0x7d83;
+ s->filter_data[0x07] = 0x84ee;
+ s->filter_data[0x08] = 0x7d83;
+ s->filter_data[0x09] = 0x84ee;
+ s->filter_data[0x0a] = 0x6be3;
+ s->filter_data[0x0b] = 0x9666;
+ s->filter_data[0x0c] = 0x675d;
+ s->filter_data[0x0d] = 0x6be3;
+ s->filter_data[0x0e] = 0x9666;
+ s->filter_data[0x0f] = 0x675d;
+ s->filter_data[0x10] = 0x7d83;
+ s->filter_data[0x11] = 0x84ee;
+ s->filter_data[0x12] = 0x7d83;
+ s->filter_data[0x13] = 0x84ee;
+
+ s->i2s_tx_rate = 0;
+ s->i2s_rx_rate = 0;
+
+ s->kb.scan = 1;
+ s->kb.debounce = 0;
+ s->kb.mask = 0x0000;
+ s->kb.mode = 3;
+ s->kb.intr = 0;
+
+ qemu_set_irq(s->pint, !s->irq);
+ qemu_set_irq(s->davint, !s->dav);
+ qemu_irq_raise(s->kbint);
+}
+
+typedef struct {
+ int rate;
+ int dsor;
+ int fsref;
+} TSC210xRateInfo;
+
+/* { rate, dsor, fsref } */
+static const TSC210xRateInfo tsc2102_rates[] = {
+ /* Fsref / 6.0 */
+ { 7350, 63, 1 },
+ { 8000, 63, 0 },
+ /* Fsref / 6.0 */
+ { 7350, 54, 1 },
+ { 8000, 54, 0 },
+ /* Fsref / 5.0 */
+ { 8820, 45, 1 },
+ { 9600, 45, 0 },
+ /* Fsref / 4.0 */
+ { 11025, 36, 1 },
+ { 12000, 36, 0 },
+ /* Fsref / 3.0 */
+ { 14700, 27, 1 },
+ { 16000, 27, 0 },
+ /* Fsref / 2.0 */
+ { 22050, 18, 1 },
+ { 24000, 18, 0 },
+ /* Fsref / 1.5 */
+ { 29400, 9, 1 },
+ { 32000, 9, 0 },
+ /* Fsref */
+ { 44100, 0, 1 },
+ { 48000, 0, 0 },
+
+ { 0, 0, 0 },
+};
+
+static inline void tsc210x_out_flush(TSC210xState *s, int len)
+{
+ uint8_t *data = s->codec.out.fifo + s->codec.out.start;
+ uint8_t *end = data + len;
+
+ while (data < end)
+ data += AUD_write(s->dac_voice[0], data, end - data) ?: (end - data);
+
+ s->codec.out.len -= len;
+ if (s->codec.out.len)
+ memmove(s->codec.out.fifo, end, s->codec.out.len);
+ s->codec.out.start = 0;
+}
+
+static void tsc210x_audio_out_cb(TSC210xState *s, int free_b)
+{
+ if (s->codec.out.len >= free_b) {
+ tsc210x_out_flush(s, free_b);
+ return;
+ }
+
+ s->codec.out.size = MIN(free_b, 16384);
+ qemu_irq_raise(s->codec.tx_start);
+}
+
+static void tsc2102_audio_rate_update(TSC210xState *s)
+{
+ const TSC210xRateInfo *rate;
+
+ s->codec.tx_rate = 0;
+ s->codec.rx_rate = 0;
+ if (s->dac_power & (1 << 15)) /* PWDNC */
+ return;
+
+ for (rate = tsc2102_rates; rate->rate; rate ++)
+ if (rate->dsor == (s->audio_ctrl1 & 0x3f) && /* DACFS */
+ rate->fsref == ((s->audio_ctrl3 >> 13) & 1))/* REFFS */
+ break;
+ if (!rate->rate) {
+ printf("%s: unknown sampling rate configured\n", __FUNCTION__);
+ return;
+ }
+
+ s->codec.tx_rate = rate->rate;
+}
+
+static void tsc2102_audio_output_update(TSC210xState *s)
+{
+ int enable;
+ struct audsettings fmt;
+
+ if (s->dac_voice[0]) {
+ tsc210x_out_flush(s, s->codec.out.len);
+ s->codec.out.size = 0;
+ AUD_set_active_out(s->dac_voice[0], 0);
+ AUD_close_out(&s->card, s->dac_voice[0]);
+ s->dac_voice[0] = NULL;
+ }
+ s->codec.cts = 0;
+
+ enable =
+ (~s->dac_power & (1 << 15)) && /* PWDNC */
+ (~s->dac_power & (1 << 10)); /* DAPWDN */
+ if (!enable || !s->codec.tx_rate)
+ return;
+
+ /* Force our own sampling rate even in slave DAC mode */
+ fmt.endianness = 0;
+ fmt.nchannels = 2;
+ fmt.freq = s->codec.tx_rate;
+ fmt.fmt = AUD_FMT_S16;
+
+ s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0],
+ "tsc2102.sink", s, (void *) tsc210x_audio_out_cb, &fmt);
+ if (s->dac_voice[0]) {
+ s->codec.cts = 1;
+ AUD_set_active_out(s->dac_voice[0], 1);
+ }
+}
+
+static uint16_t tsc2102_data_register_read(TSC210xState *s, int reg)
+{
+ switch (reg) {
+ case 0x00: /* X */
+ s->dav &= 0xfbff;
+ return TSC_CUT_RESOLUTION(X_TRANSFORM(s), s->precision) +
+ (s->noise & 3);
+
+ case 0x01: /* Y */
+ s->noise ++;
+ s->dav &= 0xfdff;
+ return TSC_CUT_RESOLUTION(Y_TRANSFORM(s), s->precision) ^
+ (s->noise & 3);
+
+ case 0x02: /* Z1 */
+ s->dav &= 0xfeff;
+ return TSC_CUT_RESOLUTION(Z1_TRANSFORM(s), s->precision) -
+ (s->noise & 3);
+
+ case 0x03: /* Z2 */
+ s->dav &= 0xff7f;
+ return TSC_CUT_RESOLUTION(Z2_TRANSFORM(s), s->precision) |
+ (s->noise & 3);
+
+ case 0x04: /* KPData */
+ if ((s->model & 0xff00) == 0x2300) {
+ if (s->kb.intr && (s->kb.mode & 2)) {
+ s->kb.intr = 0;
+ qemu_irq_raise(s->kbint);
+ }
+ return s->kb.down;
+ }
+
+ return 0xffff;
+
+ case 0x05: /* BAT1 */
+ s->dav &= 0xffbf;
+ return TSC_CUT_RESOLUTION(BAT1_VAL, s->precision) +
+ (s->noise & 6);
+
+ case 0x06: /* BAT2 */
+ s->dav &= 0xffdf;
+ return TSC_CUT_RESOLUTION(BAT2_VAL, s->precision);
+
+ case 0x07: /* AUX1 */
+ s->dav &= 0xffef;
+ return TSC_CUT_RESOLUTION(AUX1_VAL, s->precision);
+
+ case 0x08: /* AUX2 */
+ s->dav &= 0xfff7;
+ return 0xffff;
+
+ case 0x09: /* TEMP1 */
+ s->dav &= 0xfffb;
+ return TSC_CUT_RESOLUTION(TEMP1_VAL, s->precision) -
+ (s->noise & 5);
+
+ case 0x0a: /* TEMP2 */
+ s->dav &= 0xfffd;
+ return TSC_CUT_RESOLUTION(TEMP2_VAL, s->precision) ^
+ (s->noise & 3);
+
+ case 0x0b: /* DAC */
+ s->dav &= 0xfffe;
+ return 0xffff;
+
+ default:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_data_register_read: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ return 0xffff;
+ }
+}
+
+static uint16_t tsc2102_control_register_read(
+ TSC210xState *s, int reg)
+{
+ switch (reg) {
+ case 0x00: /* TSC ADC */
+ return (s->pressure << 15) | ((!s->busy) << 14) |
+ (s->nextfunction << 10) | (s->nextprecision << 8) | s->filter;
+
+ case 0x01: /* Status / Keypad Control */
+ if ((s->model & 0xff00) == 0x2100)
+ return (s->pin_func << 14) | ((!s->enabled) << 13) |
+ (s->host_mode << 12) | ((!!s->dav) << 11) | s->dav;
+ else
+ return (s->kb.intr << 15) | ((s->kb.scan || !s->kb.down) << 14) |
+ (s->kb.debounce << 11);
+
+ case 0x02: /* DAC Control */
+ if ((s->model & 0xff00) == 0x2300)
+ return s->dac_power & 0x8000;
+ else
+ goto bad_reg;
+
+ case 0x03: /* Reference */
+ return s->ref;
+
+ case 0x04: /* Reset */
+ return 0xffff;
+
+ case 0x05: /* Configuration */
+ return s->timing;
+
+ case 0x06: /* Secondary configuration */
+ if ((s->model & 0xff00) == 0x2100)
+ goto bad_reg;
+ return ((!s->dav) << 15) | ((s->kb.mode & 1) << 14) | s->pll[2];
+
+ case 0x10: /* Keypad Mask */
+ if ((s->model & 0xff00) == 0x2100)
+ goto bad_reg;
+ return s->kb.mask;
+
+ default:
+ bad_reg:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_control_register_read: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ return 0xffff;
+ }
+}
+
+static uint16_t tsc2102_audio_register_read(TSC210xState *s, int reg)
+{
+ int l_ch, r_ch;
+ uint16_t val;
+
+ switch (reg) {
+ case 0x00: /* Audio Control 1 */
+ return s->audio_ctrl1;
+
+ case 0x01:
+ return 0xff00;
+
+ case 0x02: /* DAC Volume Control */
+ return s->volume;
+
+ case 0x03:
+ return 0x8b00;
+
+ case 0x04: /* Audio Control 2 */
+ l_ch = 1;
+ r_ch = 1;
+ if (s->softstep && !(s->dac_power & (1 << 10))) {
+ l_ch = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >
+ s->volume_change + TSC_SOFTSTEP_DELAY);
+ r_ch = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >
+ s->volume_change + TSC_SOFTSTEP_DELAY);
+ }
+
+ return s->audio_ctrl2 | (l_ch << 3) | (r_ch << 2);
+
+ case 0x05: /* Stereo DAC Power Control */
+ return 0x2aa0 | s->dac_power |
+ (((s->dac_power & (1 << 10)) &&
+ (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >
+ s->powerdown + TSC_POWEROFF_DELAY)) << 6);
+
+ case 0x06: /* Audio Control 3 */
+ val = s->audio_ctrl3 | 0x0001;
+ s->audio_ctrl3 &= 0xff3f;
+ return val;
+
+ case 0x07: /* LCH_BASS_BOOST_N0 */
+ case 0x08: /* LCH_BASS_BOOST_N1 */
+ case 0x09: /* LCH_BASS_BOOST_N2 */
+ case 0x0a: /* LCH_BASS_BOOST_N3 */
+ case 0x0b: /* LCH_BASS_BOOST_N4 */
+ case 0x0c: /* LCH_BASS_BOOST_N5 */
+ case 0x0d: /* LCH_BASS_BOOST_D1 */
+ case 0x0e: /* LCH_BASS_BOOST_D2 */
+ case 0x0f: /* LCH_BASS_BOOST_D4 */
+ case 0x10: /* LCH_BASS_BOOST_D5 */
+ case 0x11: /* RCH_BASS_BOOST_N0 */
+ case 0x12: /* RCH_BASS_BOOST_N1 */
+ case 0x13: /* RCH_BASS_BOOST_N2 */
+ case 0x14: /* RCH_BASS_BOOST_N3 */
+ case 0x15: /* RCH_BASS_BOOST_N4 */
+ case 0x16: /* RCH_BASS_BOOST_N5 */
+ case 0x17: /* RCH_BASS_BOOST_D1 */
+ case 0x18: /* RCH_BASS_BOOST_D2 */
+ case 0x19: /* RCH_BASS_BOOST_D4 */
+ case 0x1a: /* RCH_BASS_BOOST_D5 */
+ return s->filter_data[reg - 0x07];
+
+ case 0x1b: /* PLL Programmability 1 */
+ return s->pll[0];
+
+ case 0x1c: /* PLL Programmability 2 */
+ return s->pll[1];
+
+ case 0x1d: /* Audio Control 4 */
+ return (!s->softstep) << 14;
+
+ default:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_audio_register_read: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ return 0xffff;
+ }
+}
+
+static void tsc2102_data_register_write(
+ TSC210xState *s, int reg, uint16_t value)
+{
+ switch (reg) {
+ case 0x00: /* X */
+ case 0x01: /* Y */
+ case 0x02: /* Z1 */
+ case 0x03: /* Z2 */
+ case 0x05: /* BAT1 */
+ case 0x06: /* BAT2 */
+ case 0x07: /* AUX1 */
+ case 0x08: /* AUX2 */
+ case 0x09: /* TEMP1 */
+ case 0x0a: /* TEMP2 */
+ return;
+
+ default:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_data_register_write: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ }
+}
+
+static void tsc2102_control_register_write(
+ TSC210xState *s, int reg, uint16_t value)
+{
+ switch (reg) {
+ case 0x00: /* TSC ADC */
+ s->host_mode = value >> 15;
+ s->enabled = !(value & 0x4000);
+ if (s->busy && !s->enabled)
+ timer_del(s->timer);
+ s->busy &= s->enabled;
+ s->nextfunction = (value >> 10) & 0xf;
+ s->nextprecision = (value >> 8) & 3;
+ s->filter = value & 0xff;
+ return;
+
+ case 0x01: /* Status / Keypad Control */
+ if ((s->model & 0xff00) == 0x2100)
+ s->pin_func = value >> 14;
+ else {
+ s->kb.scan = (value >> 14) & 1;
+ s->kb.debounce = (value >> 11) & 7;
+ if (s->kb.intr && s->kb.scan) {
+ s->kb.intr = 0;
+ qemu_irq_raise(s->kbint);
+ }
+ }
+ return;
+
+ case 0x02: /* DAC Control */
+ if ((s->model & 0xff00) == 0x2300) {
+ s->dac_power &= 0x7fff;
+ s->dac_power |= 0x8000 & value;
+ } else
+ goto bad_reg;
+ break;
+
+ case 0x03: /* Reference */
+ s->ref = value & 0x1f;
+ return;
+
+ case 0x04: /* Reset */
+ if (value == 0xbb00) {
+ if (s->busy)
+ timer_del(s->timer);
+ tsc210x_reset(s);
+#ifdef TSC_VERBOSE
+ } else {
+ fprintf(stderr, "tsc2102_control_register_write: "
+ "wrong value written into RESET\n");
+#endif
+ }
+ return;
+
+ case 0x05: /* Configuration */
+ s->timing = value & 0x3f;
+#ifdef TSC_VERBOSE
+ if (value & ~0x3f)
+ fprintf(stderr, "tsc2102_control_register_write: "
+ "wrong value written into CONFIG\n");
+#endif
+ return;
+
+ case 0x06: /* Secondary configuration */
+ if ((s->model & 0xff00) == 0x2100)
+ goto bad_reg;
+ s->kb.mode = value >> 14;
+ s->pll[2] = value & 0x3ffff;
+ return;
+
+ case 0x10: /* Keypad Mask */
+ if ((s->model & 0xff00) == 0x2100)
+ goto bad_reg;
+ s->kb.mask = value;
+ return;
+
+ default:
+ bad_reg:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_control_register_write: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ }
+}
+
+static void tsc2102_audio_register_write(
+ TSC210xState *s, int reg, uint16_t value)
+{
+ switch (reg) {
+ case 0x00: /* Audio Control 1 */
+ s->audio_ctrl1 = value & 0x0f3f;
+#ifdef TSC_VERBOSE
+ if ((value & ~0x0f3f) || ((value & 7) != ((value >> 3) & 7)))
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Audio 1\n");
+#endif
+ tsc2102_audio_rate_update(s);
+ tsc2102_audio_output_update(s);
+ return;
+
+ case 0x01:
+#ifdef TSC_VERBOSE
+ if (value != 0xff00)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into reg 0x01\n");
+#endif
+ return;
+
+ case 0x02: /* DAC Volume Control */
+ s->volume = value;
+ s->volume_change = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ return;
+
+ case 0x03:
+#ifdef TSC_VERBOSE
+ if (value != 0x8b00)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into reg 0x03\n");
+#endif
+ return;
+
+ case 0x04: /* Audio Control 2 */
+ s->audio_ctrl2 = value & 0xf7f2;
+#ifdef TSC_VERBOSE
+ if (value & ~0xf7fd)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Audio 2\n");
+#endif
+ return;
+
+ case 0x05: /* Stereo DAC Power Control */
+ if ((value & ~s->dac_power) & (1 << 10))
+ s->powerdown = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ s->dac_power = value & 0x9543;
+#ifdef TSC_VERBOSE
+ if ((value & ~0x9543) != 0x2aa0)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Power\n");
+#endif
+ tsc2102_audio_rate_update(s);
+ tsc2102_audio_output_update(s);
+ return;
+
+ case 0x06: /* Audio Control 3 */
+ s->audio_ctrl3 &= 0x00c0;
+ s->audio_ctrl3 |= value & 0xf800;
+#ifdef TSC_VERBOSE
+ if (value & ~0xf8c7)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Audio 3\n");
+#endif
+ tsc2102_audio_output_update(s);
+ return;
+
+ case 0x07: /* LCH_BASS_BOOST_N0 */
+ case 0x08: /* LCH_BASS_BOOST_N1 */
+ case 0x09: /* LCH_BASS_BOOST_N2 */
+ case 0x0a: /* LCH_BASS_BOOST_N3 */
+ case 0x0b: /* LCH_BASS_BOOST_N4 */
+ case 0x0c: /* LCH_BASS_BOOST_N5 */
+ case 0x0d: /* LCH_BASS_BOOST_D1 */
+ case 0x0e: /* LCH_BASS_BOOST_D2 */
+ case 0x0f: /* LCH_BASS_BOOST_D4 */
+ case 0x10: /* LCH_BASS_BOOST_D5 */
+ case 0x11: /* RCH_BASS_BOOST_N0 */
+ case 0x12: /* RCH_BASS_BOOST_N1 */
+ case 0x13: /* RCH_BASS_BOOST_N2 */
+ case 0x14: /* RCH_BASS_BOOST_N3 */
+ case 0x15: /* RCH_BASS_BOOST_N4 */
+ case 0x16: /* RCH_BASS_BOOST_N5 */
+ case 0x17: /* RCH_BASS_BOOST_D1 */
+ case 0x18: /* RCH_BASS_BOOST_D2 */
+ case 0x19: /* RCH_BASS_BOOST_D4 */
+ case 0x1a: /* RCH_BASS_BOOST_D5 */
+ s->filter_data[reg - 0x07] = value;
+ return;
+
+ case 0x1b: /* PLL Programmability 1 */
+ s->pll[0] = value & 0xfffc;
+#ifdef TSC_VERBOSE
+ if (value & ~0xfffc)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into PLL 1\n");
+#endif
+ return;
+
+ case 0x1c: /* PLL Programmability 2 */
+ s->pll[1] = value & 0xfffc;
+#ifdef TSC_VERBOSE
+ if (value & ~0xfffc)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into PLL 2\n");
+#endif
+ return;
+
+ case 0x1d: /* Audio Control 4 */
+ s->softstep = !(value & 0x4000);
+#ifdef TSC_VERBOSE
+ if (value & ~0x4000)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Audio 4\n");
+#endif
+ return;
+
+ default:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ }
+}
+
+/* This handles most of the chip logic. */
+static void tsc210x_pin_update(TSC210xState *s)
+{
+ int64_t expires;
+ int pin_state;
+
+ switch (s->pin_func) {
+ case 0:
+ pin_state = s->pressure;
+ break;
+ case 1:
+ pin_state = !!s->dav;
+ break;
+ case 2:
+ default:
+ pin_state = s->pressure && !s->dav;
+ }
+
+ if (!s->enabled)
+ pin_state = 0;
+
+ if (pin_state != s->irq) {
+ s->irq = pin_state;
+ qemu_set_irq(s->pint, !s->irq);
+ }
+
+ switch (s->nextfunction) {
+ case TSC_MODE_XY_SCAN:
+ case TSC_MODE_XYZ_SCAN:
+ if (!s->pressure)
+ return;
+ break;
+
+ case TSC_MODE_X:
+ case TSC_MODE_Y:
+ case TSC_MODE_Z:
+ if (!s->pressure)
+ return;
+ /* Fall through */
+ case TSC_MODE_BAT1:
+ case TSC_MODE_BAT2:
+ case TSC_MODE_AUX:
+ case TSC_MODE_TEMP1:
+ case TSC_MODE_TEMP2:
+ if (s->dav)
+ s->enabled = 0;
+ break;
+
+ case TSC_MODE_AUX_SCAN:
+ case TSC_MODE_PORT_SCAN:
+ break;
+
+ case TSC_MODE_NO_SCAN:
+ case TSC_MODE_XX_DRV:
+ case TSC_MODE_YY_DRV:
+ case TSC_MODE_YX_DRV:
+ default:
+ return;
+ }
+
+ if (!s->enabled || s->busy || s->dav)
+ return;
+
+ s->busy = 1;
+ s->precision = s->nextprecision;
+ s->function = s->nextfunction;
+ expires = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() >> 10);
+ timer_mod(s->timer, expires);
+}
+
+static uint16_t tsc210x_read(TSC210xState *s)
+{
+ uint16_t ret = 0x0000;
+
+ if (!s->command)
+ fprintf(stderr, "tsc210x_read: SPI underrun!\n");
+
+ switch (s->page) {
+ case TSC_DATA_REGISTERS_PAGE:
+ ret = tsc2102_data_register_read(s, s->offset);
+ if (!s->dav)
+ qemu_irq_raise(s->davint);
+ break;
+ case TSC_CONTROL_REGISTERS_PAGE:
+ ret = tsc2102_control_register_read(s, s->offset);
+ break;
+ case TSC_AUDIO_REGISTERS_PAGE:
+ ret = tsc2102_audio_register_read(s, s->offset);
+ break;
+ default:
+ hw_error("tsc210x_read: wrong memory page\n");
+ }
+
+ tsc210x_pin_update(s);
+
+ /* Allow sequential reads. */
+ s->offset ++;
+ s->state = 0;
+ return ret;
+}
+
+static void tsc210x_write(TSC210xState *s, uint16_t value)
+{
+ /*
+ * This is a two-state state machine for reading
+ * command and data every second time.
+ */
+ if (!s->state) {
+ s->command = value >> 15;
+ s->page = (value >> 11) & 0x0f;
+ s->offset = (value >> 5) & 0x3f;
+ s->state = 1;
+ } else {
+ if (s->command)
+ fprintf(stderr, "tsc210x_write: SPI overrun!\n");
+ else
+ switch (s->page) {
+ case TSC_DATA_REGISTERS_PAGE:
+ tsc2102_data_register_write(s, s->offset, value);
+ break;
+ case TSC_CONTROL_REGISTERS_PAGE:
+ tsc2102_control_register_write(s, s->offset, value);
+ break;
+ case TSC_AUDIO_REGISTERS_PAGE:
+ tsc2102_audio_register_write(s, s->offset, value);
+ break;
+ default:
+ hw_error("tsc210x_write: wrong memory page\n");
+ }
+
+ tsc210x_pin_update(s);
+ s->state = 0;
+ }
+}
+
+uint32_t tsc210x_txrx(void *opaque, uint32_t value, int len)
+{
+ TSC210xState *s = opaque;
+ uint32_t ret = 0;
+
+ if (len != 16)
+ hw_error("%s: FIXME: bad SPI word width %i\n", __FUNCTION__, len);
+
+ /* TODO: sequential reads etc - how do we make sure the host doesn't
+ * unintentionally read out a conversion result from a register while
+ * transmitting the command word of the next command? */
+ if (!value || (s->state && s->command))
+ ret = tsc210x_read(s);
+ if (value || (s->state && !s->command))
+ tsc210x_write(s, value);
+
+ return ret;
+}
+
+static void tsc210x_timer_tick(void *opaque)
+{
+ TSC210xState *s = opaque;
+
+ /* Timer ticked -- a set of conversions has been finished. */
+
+ if (!s->busy)
+ return;
+
+ s->busy = 0;
+ s->dav |= mode_regs[s->function];
+ tsc210x_pin_update(s);
+ qemu_irq_lower(s->davint);
+}
+
+static void tsc210x_touchscreen_event(void *opaque,
+ int x, int y, int z, int buttons_state)
+{
+ TSC210xState *s = opaque;
+ int p = s->pressure;
+
+ if (buttons_state) {
+ s->x = x;
+ s->y = y;
+ }
+ s->pressure = !!buttons_state;
+
+ /*
+ * Note: We would get better responsiveness in the guest by
+ * signaling TS events immediately, but for now we simulate
+ * the first conversion delay for sake of correctness.
+ */
+ if (p != s->pressure)
+ tsc210x_pin_update(s);
+}
+
+static void tsc210x_i2s_swallow(TSC210xState *s)
+{
+ if (s->dac_voice[0])
+ tsc210x_out_flush(s, s->codec.out.len);
+ else
+ s->codec.out.len = 0;
+}
+
+static void tsc210x_i2s_set_rate(TSC210xState *s, int in, int out)
+{
+ s->i2s_tx_rate = out;
+ s->i2s_rx_rate = in;
+}
+
+static void tsc210x_save(QEMUFile *f, void *opaque)
+{
+ TSC210xState *s = (TSC210xState *) opaque;
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ int i;
+
+ qemu_put_be16(f, s->x);
+ qemu_put_be16(f, s->y);
+ qemu_put_byte(f, s->pressure);
+
+ qemu_put_byte(f, s->state);
+ qemu_put_byte(f, s->page);
+ qemu_put_byte(f, s->offset);
+ qemu_put_byte(f, s->command);
+
+ qemu_put_byte(f, s->irq);
+ qemu_put_be16s(f, &s->dav);
+
+ timer_put(f, s->timer);
+ qemu_put_byte(f, s->enabled);
+ qemu_put_byte(f, s->host_mode);
+ qemu_put_byte(f, s->function);
+ qemu_put_byte(f, s->nextfunction);
+ qemu_put_byte(f, s->precision);
+ qemu_put_byte(f, s->nextprecision);
+ qemu_put_byte(f, s->filter);
+ qemu_put_byte(f, s->pin_func);
+ qemu_put_byte(f, s->ref);
+ qemu_put_byte(f, s->timing);
+ qemu_put_be32(f, s->noise);
+
+ qemu_put_be16s(f, &s->audio_ctrl1);
+ qemu_put_be16s(f, &s->audio_ctrl2);
+ qemu_put_be16s(f, &s->audio_ctrl3);
+ qemu_put_be16s(f, &s->pll[0]);
+ qemu_put_be16s(f, &s->pll[1]);
+ qemu_put_be16s(f, &s->volume);
+ qemu_put_sbe64(f, (s->volume_change - now));
+ qemu_put_sbe64(f, (s->powerdown - now));
+ qemu_put_byte(f, s->softstep);
+ qemu_put_be16s(f, &s->dac_power);
+
+ for (i = 0; i < 0x14; i ++)
+ qemu_put_be16s(f, &s->filter_data[i]);
+}
+
+static int tsc210x_load(QEMUFile *f, void *opaque, int version_id)
+{
+ TSC210xState *s = (TSC210xState *) opaque;
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ int i;
+
+ s->x = qemu_get_be16(f);
+ s->y = qemu_get_be16(f);
+ s->pressure = qemu_get_byte(f);
+
+ s->state = qemu_get_byte(f);
+ s->page = qemu_get_byte(f);
+ s->offset = qemu_get_byte(f);
+ s->command = qemu_get_byte(f);
+
+ s->irq = qemu_get_byte(f);
+ qemu_get_be16s(f, &s->dav);
+
+ timer_get(f, s->timer);
+ s->enabled = qemu_get_byte(f);
+ s->host_mode = qemu_get_byte(f);
+ s->function = qemu_get_byte(f);
+ if (s->function < 0 || s->function >= ARRAY_SIZE(mode_regs)) {
+ return -EINVAL;
+ }
+ s->nextfunction = qemu_get_byte(f);
+ if (s->nextfunction < 0 || s->nextfunction >= ARRAY_SIZE(mode_regs)) {
+ return -EINVAL;
+ }
+ s->precision = qemu_get_byte(f);
+ if (s->precision < 0 || s->precision >= ARRAY_SIZE(resolution)) {
+ return -EINVAL;
+ }
+ s->nextprecision = qemu_get_byte(f);
+ if (s->nextprecision < 0 || s->nextprecision >= ARRAY_SIZE(resolution)) {
+ return -EINVAL;
+ }
+ s->filter = qemu_get_byte(f);
+ s->pin_func = qemu_get_byte(f);
+ s->ref = qemu_get_byte(f);
+ s->timing = qemu_get_byte(f);
+ s->noise = qemu_get_be32(f);
+
+ qemu_get_be16s(f, &s->audio_ctrl1);
+ qemu_get_be16s(f, &s->audio_ctrl2);
+ qemu_get_be16s(f, &s->audio_ctrl3);
+ qemu_get_be16s(f, &s->pll[0]);
+ qemu_get_be16s(f, &s->pll[1]);
+ qemu_get_be16s(f, &s->volume);
+ s->volume_change = qemu_get_sbe64(f) + now;
+ s->powerdown = qemu_get_sbe64(f) + now;
+ s->softstep = qemu_get_byte(f);
+ qemu_get_be16s(f, &s->dac_power);
+
+ for (i = 0; i < 0x14; i ++)
+ qemu_get_be16s(f, &s->filter_data[i]);
+
+ s->busy = timer_pending(s->timer);
+ qemu_set_irq(s->pint, !s->irq);
+ qemu_set_irq(s->davint, !s->dav);
+
+ return 0;
+}
+
+uWireSlave *tsc2102_init(qemu_irq pint)
+{
+ TSC210xState *s;
+
+ s = (TSC210xState *)
+ g_malloc0(sizeof(TSC210xState));
+ memset(s, 0, sizeof(TSC210xState));
+ s->x = 160;
+ s->y = 160;
+ s->pressure = 0;
+ s->precision = s->nextprecision = 0;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc210x_timer_tick, s);
+ s->pint = pint;
+ s->model = 0x2102;
+ s->name = "tsc2102";
+
+ s->tr[0] = 0;
+ s->tr[1] = 1;
+ s->tr[2] = 1;
+ s->tr[3] = 0;
+ s->tr[4] = 1;
+ s->tr[5] = 0;
+ s->tr[6] = 1;
+ s->tr[7] = 0;
+
+ s->chip.opaque = s;
+ s->chip.send = (void *) tsc210x_write;
+ s->chip.receive = (void *) tsc210x_read;
+
+ s->codec.opaque = s;
+ s->codec.tx_swallow = (void *) tsc210x_i2s_swallow;
+ s->codec.set_rate = (void *) tsc210x_i2s_set_rate;
+ s->codec.in.fifo = s->in_fifo;
+ s->codec.out.fifo = s->out_fifo;
+
+ tsc210x_reset(s);
+
+ qemu_add_mouse_event_handler(tsc210x_touchscreen_event, s, 1,
+ "QEMU TSC2102-driven Touchscreen");
+
+ AUD_register_card(s->name, &s->card);
+
+ qemu_register_reset((void *) tsc210x_reset, s);
+ register_savevm(NULL, s->name, -1, 0,
+ tsc210x_save, tsc210x_load, s);
+
+ return &s->chip;
+}
+
+uWireSlave *tsc2301_init(qemu_irq penirq, qemu_irq kbirq, qemu_irq dav)
+{
+ TSC210xState *s;
+
+ s = (TSC210xState *)
+ g_malloc0(sizeof(TSC210xState));
+ memset(s, 0, sizeof(TSC210xState));
+ s->x = 400;
+ s->y = 240;
+ s->pressure = 0;
+ s->precision = s->nextprecision = 0;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc210x_timer_tick, s);
+ s->pint = penirq;
+ s->kbint = kbirq;
+ s->davint = dav;
+ s->model = 0x2301;
+ s->name = "tsc2301";
+
+ s->tr[0] = 0;
+ s->tr[1] = 1;
+ s->tr[2] = 1;
+ s->tr[3] = 0;
+ s->tr[4] = 1;
+ s->tr[5] = 0;
+ s->tr[6] = 1;
+ s->tr[7] = 0;
+
+ s->chip.opaque = s;
+ s->chip.send = (void *) tsc210x_write;
+ s->chip.receive = (void *) tsc210x_read;
+
+ s->codec.opaque = s;
+ s->codec.tx_swallow = (void *) tsc210x_i2s_swallow;
+ s->codec.set_rate = (void *) tsc210x_i2s_set_rate;
+ s->codec.in.fifo = s->in_fifo;
+ s->codec.out.fifo = s->out_fifo;
+
+ tsc210x_reset(s);
+
+ qemu_add_mouse_event_handler(tsc210x_touchscreen_event, s, 1,
+ "QEMU TSC2301-driven Touchscreen");
+
+ AUD_register_card(s->name, &s->card);
+
+ qemu_register_reset((void *) tsc210x_reset, s);
+ register_savevm(NULL, s->name, -1, 0, tsc210x_save, tsc210x_load, s);
+
+ return &s->chip;
+}
+
+I2SCodec *tsc210x_codec(uWireSlave *chip)
+{
+ TSC210xState *s = (TSC210xState *) chip->opaque;
+
+ return &s->codec;
+}
+
+/*
+ * Use tslib generated calibration data to generate ADC input values
+ * from the touchscreen. Assuming 12-bit precision was used during
+ * tslib calibration.
+ */
+void tsc210x_set_transform(uWireSlave *chip,
+ MouseTransformInfo *info)
+{
+ TSC210xState *s = (TSC210xState *) chip->opaque;
+#if 0
+ int64_t ltr[8];
+
+ ltr[0] = (int64_t) info->a[1] * info->y;
+ ltr[1] = (int64_t) info->a[4] * info->x;
+ ltr[2] = (int64_t) info->a[1] * info->a[3] -
+ (int64_t) info->a[4] * info->a[0];
+ ltr[3] = (int64_t) info->a[2] * info->a[4] -
+ (int64_t) info->a[5] * info->a[1];
+ ltr[4] = (int64_t) info->a[0] * info->y;
+ ltr[5] = (int64_t) info->a[3] * info->x;
+ ltr[6] = (int64_t) info->a[4] * info->a[0] -
+ (int64_t) info->a[1] * info->a[3];
+ ltr[7] = (int64_t) info->a[2] * info->a[3] -
+ (int64_t) info->a[5] * info->a[0];
+
+ /* Avoid integer overflow */
+ s->tr[0] = ltr[0] >> 11;
+ s->tr[1] = ltr[1] >> 11;
+ s->tr[2] = muldiv64(ltr[2], 1, info->a[6]);
+ s->tr[3] = muldiv64(ltr[3], 1 << 4, ltr[2]);
+ s->tr[4] = ltr[4] >> 11;
+ s->tr[5] = ltr[5] >> 11;
+ s->tr[6] = muldiv64(ltr[6], 1, info->a[6]);
+ s->tr[7] = muldiv64(ltr[7], 1 << 4, ltr[6]);
+#else
+
+ /* This version assumes touchscreen X & Y axis are parallel or
+ * perpendicular to LCD's X & Y axis in some way. */
+ if (abs(info->a[0]) > abs(info->a[1])) {
+ s->tr[0] = 0;
+ s->tr[1] = -info->a[6] * info->x;
+ s->tr[2] = info->a[0];
+ s->tr[3] = -info->a[2] / info->a[0];
+ s->tr[4] = info->a[6] * info->y;
+ s->tr[5] = 0;
+ s->tr[6] = info->a[4];
+ s->tr[7] = -info->a[5] / info->a[4];
+ } else {
+ s->tr[0] = info->a[6] * info->y;
+ s->tr[1] = 0;
+ s->tr[2] = info->a[1];
+ s->tr[3] = -info->a[2] / info->a[1];
+ s->tr[4] = 0;
+ s->tr[5] = -info->a[6] * info->x;
+ s->tr[6] = info->a[3];
+ s->tr[7] = -info->a[5] / info->a[3];
+ }
+
+ s->tr[0] >>= 11;
+ s->tr[1] >>= 11;
+ s->tr[3] <<= 4;
+ s->tr[4] >>= 11;
+ s->tr[5] >>= 11;
+ s->tr[7] <<= 4;
+#endif
+}
+
+void tsc210x_key_event(uWireSlave *chip, int key, int down)
+{
+ TSC210xState *s = (TSC210xState *) chip->opaque;
+
+ if (down)
+ s->kb.down |= 1 << key;
+ else
+ s->kb.down &= ~(1 << key);
+
+ if (down && (s->kb.down & ~s->kb.mask) && !s->kb.intr) {
+ s->kb.intr = 1;
+ qemu_irq_lower(s->kbint);
+ } else if (s->kb.intr && !(s->kb.down & ~s->kb.mask) &&
+ !(s->kb.mode & 1)) {
+ s->kb.intr = 0;
+ qemu_irq_raise(s->kbint);
+ }
+}
diff --git a/hw/input/virtio-input-hid.c b/hw/input/virtio-input-hid.c
new file mode 100644
index 00000000..4d85dad4
--- /dev/null
+++ b/hw/input/virtio-input-hid.c
@@ -0,0 +1,514 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/iov.h"
+
+#include "hw/qdev.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-input.h"
+
+#undef CONFIG_CURSES
+#include "ui/console.h"
+
+#include "standard-headers/linux/input.h"
+
+#define VIRTIO_ID_NAME_KEYBOARD "QEMU Virtio Keyboard"
+#define VIRTIO_ID_NAME_MOUSE "QEMU Virtio Mouse"
+#define VIRTIO_ID_NAME_TABLET "QEMU Virtio Tablet"
+
+/* ----------------------------------------------------------------- */
+
+static const unsigned int keymap_qcode[Q_KEY_CODE_MAX] = {
+ [Q_KEY_CODE_ESC] = KEY_ESC,
+ [Q_KEY_CODE_1] = KEY_1,
+ [Q_KEY_CODE_2] = KEY_2,
+ [Q_KEY_CODE_3] = KEY_3,
+ [Q_KEY_CODE_4] = KEY_4,
+ [Q_KEY_CODE_5] = KEY_5,
+ [Q_KEY_CODE_6] = KEY_6,
+ [Q_KEY_CODE_7] = KEY_7,
+ [Q_KEY_CODE_8] = KEY_8,
+ [Q_KEY_CODE_9] = KEY_9,
+ [Q_KEY_CODE_0] = KEY_0,
+ [Q_KEY_CODE_MINUS] = KEY_MINUS,
+ [Q_KEY_CODE_EQUAL] = KEY_EQUAL,
+ [Q_KEY_CODE_BACKSPACE] = KEY_BACKSPACE,
+
+ [Q_KEY_CODE_TAB] = KEY_TAB,
+ [Q_KEY_CODE_Q] = KEY_Q,
+ [Q_KEY_CODE_W] = KEY_W,
+ [Q_KEY_CODE_E] = KEY_E,
+ [Q_KEY_CODE_R] = KEY_R,
+ [Q_KEY_CODE_T] = KEY_T,
+ [Q_KEY_CODE_Y] = KEY_Y,
+ [Q_KEY_CODE_U] = KEY_U,
+ [Q_KEY_CODE_I] = KEY_I,
+ [Q_KEY_CODE_O] = KEY_O,
+ [Q_KEY_CODE_P] = KEY_P,
+ [Q_KEY_CODE_BRACKET_LEFT] = KEY_LEFTBRACE,
+ [Q_KEY_CODE_BRACKET_RIGHT] = KEY_RIGHTBRACE,
+ [Q_KEY_CODE_RET] = KEY_ENTER,
+
+ [Q_KEY_CODE_CTRL] = KEY_LEFTCTRL,
+ [Q_KEY_CODE_A] = KEY_A,
+ [Q_KEY_CODE_S] = KEY_S,
+ [Q_KEY_CODE_D] = KEY_D,
+ [Q_KEY_CODE_F] = KEY_F,
+ [Q_KEY_CODE_G] = KEY_G,
+ [Q_KEY_CODE_H] = KEY_H,
+ [Q_KEY_CODE_J] = KEY_J,
+ [Q_KEY_CODE_K] = KEY_K,
+ [Q_KEY_CODE_L] = KEY_L,
+ [Q_KEY_CODE_SEMICOLON] = KEY_SEMICOLON,
+ [Q_KEY_CODE_APOSTROPHE] = KEY_APOSTROPHE,
+ [Q_KEY_CODE_GRAVE_ACCENT] = KEY_GRAVE,
+
+ [Q_KEY_CODE_SHIFT] = KEY_LEFTSHIFT,
+ [Q_KEY_CODE_BACKSLASH] = KEY_BACKSLASH,
+ [Q_KEY_CODE_LESS] = KEY_102ND,
+ [Q_KEY_CODE_Z] = KEY_Z,
+ [Q_KEY_CODE_X] = KEY_X,
+ [Q_KEY_CODE_C] = KEY_C,
+ [Q_KEY_CODE_V] = KEY_V,
+ [Q_KEY_CODE_B] = KEY_B,
+ [Q_KEY_CODE_N] = KEY_N,
+ [Q_KEY_CODE_M] = KEY_M,
+ [Q_KEY_CODE_COMMA] = KEY_COMMA,
+ [Q_KEY_CODE_DOT] = KEY_DOT,
+ [Q_KEY_CODE_SLASH] = KEY_SLASH,
+ [Q_KEY_CODE_SHIFT_R] = KEY_RIGHTSHIFT,
+
+ [Q_KEY_CODE_ALT] = KEY_LEFTALT,
+ [Q_KEY_CODE_SPC] = KEY_SPACE,
+ [Q_KEY_CODE_CAPS_LOCK] = KEY_CAPSLOCK,
+
+ [Q_KEY_CODE_F1] = KEY_F1,
+ [Q_KEY_CODE_F2] = KEY_F2,
+ [Q_KEY_CODE_F3] = KEY_F3,
+ [Q_KEY_CODE_F4] = KEY_F4,
+ [Q_KEY_CODE_F5] = KEY_F5,
+ [Q_KEY_CODE_F6] = KEY_F6,
+ [Q_KEY_CODE_F7] = KEY_F7,
+ [Q_KEY_CODE_F8] = KEY_F8,
+ [Q_KEY_CODE_F9] = KEY_F9,
+ [Q_KEY_CODE_F10] = KEY_F10,
+ [Q_KEY_CODE_NUM_LOCK] = KEY_NUMLOCK,
+ [Q_KEY_CODE_SCROLL_LOCK] = KEY_SCROLLLOCK,
+
+ [Q_KEY_CODE_KP_0] = KEY_KP0,
+ [Q_KEY_CODE_KP_1] = KEY_KP1,
+ [Q_KEY_CODE_KP_2] = KEY_KP2,
+ [Q_KEY_CODE_KP_3] = KEY_KP3,
+ [Q_KEY_CODE_KP_4] = KEY_KP4,
+ [Q_KEY_CODE_KP_5] = KEY_KP5,
+ [Q_KEY_CODE_KP_6] = KEY_KP6,
+ [Q_KEY_CODE_KP_7] = KEY_KP7,
+ [Q_KEY_CODE_KP_8] = KEY_KP8,
+ [Q_KEY_CODE_KP_9] = KEY_KP9,
+ [Q_KEY_CODE_KP_SUBTRACT] = KEY_KPMINUS,
+ [Q_KEY_CODE_KP_ADD] = KEY_KPPLUS,
+ [Q_KEY_CODE_KP_DECIMAL] = KEY_KPDOT,
+ [Q_KEY_CODE_KP_ENTER] = KEY_KPENTER,
+ [Q_KEY_CODE_KP_DIVIDE] = KEY_KPSLASH,
+ [Q_KEY_CODE_KP_MULTIPLY] = KEY_KPASTERISK,
+
+ [Q_KEY_CODE_F11] = KEY_F11,
+ [Q_KEY_CODE_F12] = KEY_F12,
+
+ [Q_KEY_CODE_CTRL_R] = KEY_RIGHTCTRL,
+ [Q_KEY_CODE_SYSRQ] = KEY_SYSRQ,
+ [Q_KEY_CODE_ALT_R] = KEY_RIGHTALT,
+
+ [Q_KEY_CODE_HOME] = KEY_HOME,
+ [Q_KEY_CODE_UP] = KEY_UP,
+ [Q_KEY_CODE_PGUP] = KEY_PAGEUP,
+ [Q_KEY_CODE_LEFT] = KEY_LEFT,
+ [Q_KEY_CODE_RIGHT] = KEY_RIGHT,
+ [Q_KEY_CODE_END] = KEY_END,
+ [Q_KEY_CODE_DOWN] = KEY_DOWN,
+ [Q_KEY_CODE_PGDN] = KEY_PAGEDOWN,
+ [Q_KEY_CODE_INSERT] = KEY_INSERT,
+ [Q_KEY_CODE_DELETE] = KEY_DELETE,
+
+ [Q_KEY_CODE_META_L] = KEY_LEFTMETA,
+ [Q_KEY_CODE_META_R] = KEY_RIGHTMETA,
+ [Q_KEY_CODE_MENU] = KEY_MENU,
+};
+
+static const unsigned int keymap_button[INPUT_BUTTON_MAX] = {
+ [INPUT_BUTTON_LEFT] = BTN_LEFT,
+ [INPUT_BUTTON_RIGHT] = BTN_RIGHT,
+ [INPUT_BUTTON_MIDDLE] = BTN_MIDDLE,
+ [INPUT_BUTTON_WHEEL_UP] = BTN_GEAR_UP,
+ [INPUT_BUTTON_WHEEL_DOWN] = BTN_GEAR_DOWN,
+};
+
+static const unsigned int axismap_rel[INPUT_AXIS_MAX] = {
+ [INPUT_AXIS_X] = REL_X,
+ [INPUT_AXIS_Y] = REL_Y,
+};
+
+static const unsigned int axismap_abs[INPUT_AXIS_MAX] = {
+ [INPUT_AXIS_X] = ABS_X,
+ [INPUT_AXIS_Y] = ABS_Y,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_input_key_config(VirtIOInput *vinput,
+ const unsigned int *keymap,
+ size_t mapsize)
+{
+ virtio_input_config keys;
+ int i, bit, byte, bmax = 0;
+
+ memset(&keys, 0, sizeof(keys));
+ for (i = 0; i < mapsize; i++) {
+ bit = keymap[i];
+ if (!bit) {
+ continue;
+ }
+ byte = bit / 8;
+ bit = bit % 8;
+ keys.u.bitmap[byte] |= (1 << bit);
+ if (bmax < byte+1) {
+ bmax = byte+1;
+ }
+ }
+ keys.select = VIRTIO_INPUT_CFG_EV_BITS;
+ keys.subsel = EV_KEY;
+ keys.size = bmax;
+ virtio_input_add_config(vinput, &keys);
+}
+
+static void virtio_input_handle_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(dev);
+ virtio_input_event event;
+ int qcode;
+
+ switch (evt->kind) {
+ case INPUT_EVENT_KIND_KEY:
+ qcode = qemu_input_key_value_to_qcode(evt->key->key);
+ if (qcode && keymap_qcode[qcode]) {
+ event.type = cpu_to_le16(EV_KEY);
+ event.code = cpu_to_le16(keymap_qcode[qcode]);
+ event.value = cpu_to_le32(evt->key->down ? 1 : 0);
+ virtio_input_send(vinput, &event);
+ } else {
+ if (evt->key->down) {
+ fprintf(stderr, "%s: unmapped key: %d [%s]\n", __func__,
+ qcode, QKeyCode_lookup[qcode]);
+ }
+ }
+ break;
+ case INPUT_EVENT_KIND_BTN:
+ if (keymap_button[evt->btn->button]) {
+ event.type = cpu_to_le16(EV_KEY);
+ event.code = cpu_to_le16(keymap_button[evt->btn->button]);
+ event.value = cpu_to_le32(evt->btn->down ? 1 : 0);
+ virtio_input_send(vinput, &event);
+ } else {
+ if (evt->btn->down) {
+ fprintf(stderr, "%s: unmapped button: %d [%s]\n", __func__,
+ evt->btn->button, InputButton_lookup[evt->btn->button]);
+ }
+ }
+ break;
+ case INPUT_EVENT_KIND_REL:
+ event.type = cpu_to_le16(EV_REL);
+ event.code = cpu_to_le16(axismap_rel[evt->rel->axis]);
+ event.value = cpu_to_le32(evt->rel->value);
+ virtio_input_send(vinput, &event);
+ break;
+ case INPUT_EVENT_KIND_ABS:
+ event.type = cpu_to_le16(EV_ABS);
+ event.code = cpu_to_le16(axismap_abs[evt->abs->axis]);
+ event.value = cpu_to_le32(evt->abs->value);
+ virtio_input_send(vinput, &event);
+ break;
+ default:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+static void virtio_input_handle_sync(DeviceState *dev)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(dev);
+ virtio_input_event event = {
+ .type = cpu_to_le16(EV_SYN),
+ .code = cpu_to_le16(SYN_REPORT),
+ .value = 0,
+ };
+
+ virtio_input_send(vinput, &event);
+}
+
+static void virtio_input_hid_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(dev);
+
+ vhid->hs = qemu_input_handler_register(dev, vhid->handler);
+ if (vhid->display && vhid->hs) {
+ qemu_input_handler_bind(vhid->hs, vhid->display, vhid->head, NULL);
+ }
+}
+
+static void virtio_input_hid_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(dev);
+ qemu_input_handler_unregister(vhid->hs);
+}
+
+static void virtio_input_hid_change_active(VirtIOInput *vinput)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(vinput);
+
+ if (vinput->active) {
+ qemu_input_handler_activate(vhid->hs);
+ } else {
+ qemu_input_handler_deactivate(vhid->hs);
+ }
+}
+
+static void virtio_input_hid_handle_status(VirtIOInput *vinput,
+ virtio_input_event *event)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(vinput);
+ int ledbit = 0;
+
+ switch (le16_to_cpu(event->type)) {
+ case EV_LED:
+ if (event->code == LED_NUML) {
+ ledbit = QEMU_NUM_LOCK_LED;
+ } else if (event->code == LED_CAPSL) {
+ ledbit = QEMU_CAPS_LOCK_LED;
+ } else if (event->code == LED_SCROLLL) {
+ ledbit = QEMU_SCROLL_LOCK_LED;
+ }
+ if (event->value) {
+ vhid->ledstate |= ledbit;
+ } else {
+ vhid->ledstate &= ~ledbit;
+ }
+ kbd_put_ledstate(vhid->ledstate);
+ break;
+ default:
+ fprintf(stderr, "%s: unknown type %d\n", __func__,
+ le16_to_cpu(event->type));
+ break;
+ }
+}
+
+static Property virtio_input_hid_properties[] = {
+ DEFINE_PROP_STRING("display", VirtIOInputHID, display),
+ DEFINE_PROP_UINT32("head", VirtIOInputHID, head, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_input_hid_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOInputClass *vic = VIRTIO_INPUT_CLASS(klass);
+
+ dc->props = virtio_input_hid_properties;
+ vic->realize = virtio_input_hid_realize;
+ vic->unrealize = virtio_input_hid_unrealize;
+ vic->change_active = virtio_input_hid_change_active;
+ vic->handle_status = virtio_input_hid_handle_status;
+}
+
+static const TypeInfo virtio_input_hid_info = {
+ .name = TYPE_VIRTIO_INPUT_HID,
+ .parent = TYPE_VIRTIO_INPUT,
+ .instance_size = sizeof(VirtIOInputHID),
+ .class_init = virtio_input_hid_class_init,
+ .abstract = true,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_keyboard_handler = {
+ .name = VIRTIO_ID_NAME_KEYBOARD,
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = virtio_input_handle_event,
+ .sync = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_keyboard_config[] = {
+ {
+ .select = VIRTIO_INPUT_CFG_ID_NAME,
+ .size = sizeof(VIRTIO_ID_NAME_KEYBOARD),
+ .u.string = VIRTIO_ID_NAME_KEYBOARD,
+ },{
+ .select = VIRTIO_INPUT_CFG_ID_DEVIDS,
+ .size = sizeof(struct virtio_input_devids),
+ .u.ids = {
+ .bustype = const_le16(BUS_VIRTUAL),
+ .vendor = const_le16(0x0627), /* same we use for usb hid devices */
+ .product = const_le16(0x0001),
+ .version = const_le16(0x0001),
+ },
+ },{
+ .select = VIRTIO_INPUT_CFG_EV_BITS,
+ .subsel = EV_REP,
+ .size = 1,
+ },{
+ .select = VIRTIO_INPUT_CFG_EV_BITS,
+ .subsel = EV_LED,
+ .size = 1,
+ .u.bitmap = {
+ (1 << LED_NUML) | (1 << LED_CAPSL) | (1 << LED_SCROLLL),
+ },
+ },
+ { /* end of list */ },
+};
+
+static void virtio_keyboard_init(Object *obj)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj);
+ VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+ vhid->handler = &virtio_keyboard_handler;
+ virtio_input_init_config(vinput, virtio_keyboard_config);
+ virtio_input_key_config(vinput, keymap_qcode,
+ ARRAY_SIZE(keymap_qcode));
+}
+
+static const TypeInfo virtio_keyboard_info = {
+ .name = TYPE_VIRTIO_KEYBOARD,
+ .parent = TYPE_VIRTIO_INPUT_HID,
+ .instance_size = sizeof(VirtIOInputHID),
+ .instance_init = virtio_keyboard_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_mouse_handler = {
+ .name = VIRTIO_ID_NAME_MOUSE,
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+ .event = virtio_input_handle_event,
+ .sync = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_mouse_config[] = {
+ {
+ .select = VIRTIO_INPUT_CFG_ID_NAME,
+ .size = sizeof(VIRTIO_ID_NAME_MOUSE),
+ .u.string = VIRTIO_ID_NAME_MOUSE,
+ },{
+ .select = VIRTIO_INPUT_CFG_ID_DEVIDS,
+ .size = sizeof(struct virtio_input_devids),
+ .u.ids = {
+ .bustype = const_le16(BUS_VIRTUAL),
+ .vendor = const_le16(0x0627), /* same we use for usb hid devices */
+ .product = const_le16(0x0002),
+ .version = const_le16(0x0001),
+ },
+ },{
+ .select = VIRTIO_INPUT_CFG_EV_BITS,
+ .subsel = EV_REL,
+ .size = 1,
+ .u.bitmap = {
+ (1 << REL_X) | (1 << REL_Y),
+ },
+ },
+ { /* end of list */ },
+};
+
+static void virtio_mouse_init(Object *obj)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj);
+ VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+ vhid->handler = &virtio_mouse_handler;
+ virtio_input_init_config(vinput, virtio_mouse_config);
+ virtio_input_key_config(vinput, keymap_button,
+ ARRAY_SIZE(keymap_button));
+}
+
+static const TypeInfo virtio_mouse_info = {
+ .name = TYPE_VIRTIO_MOUSE,
+ .parent = TYPE_VIRTIO_INPUT_HID,
+ .instance_size = sizeof(VirtIOInputHID),
+ .instance_init = virtio_mouse_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_tablet_handler = {
+ .name = VIRTIO_ID_NAME_TABLET,
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+ .event = virtio_input_handle_event,
+ .sync = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_tablet_config[] = {
+ {
+ .select = VIRTIO_INPUT_CFG_ID_NAME,
+ .size = sizeof(VIRTIO_ID_NAME_TABLET),
+ .u.string = VIRTIO_ID_NAME_TABLET,
+ },{
+ .select = VIRTIO_INPUT_CFG_ID_DEVIDS,
+ .size = sizeof(struct virtio_input_devids),
+ .u.ids = {
+ .bustype = const_le16(BUS_VIRTUAL),
+ .vendor = const_le16(0x0627), /* same we use for usb hid devices */
+ .product = const_le16(0x0003),
+ .version = const_le16(0x0001),
+ },
+ },{
+ .select = VIRTIO_INPUT_CFG_EV_BITS,
+ .subsel = EV_ABS,
+ .size = 1,
+ .u.bitmap = {
+ (1 << ABS_X) | (1 << ABS_Y),
+ },
+ },{
+ .select = VIRTIO_INPUT_CFG_ABS_INFO,
+ .subsel = ABS_X,
+ .size = sizeof(virtio_input_absinfo),
+ .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE),
+ },{
+ .select = VIRTIO_INPUT_CFG_ABS_INFO,
+ .subsel = ABS_Y,
+ .size = sizeof(virtio_input_absinfo),
+ .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE),
+ },
+ { /* end of list */ },
+};
+
+static void virtio_tablet_init(Object *obj)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj);
+ VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+ vhid->handler = &virtio_tablet_handler;
+ virtio_input_init_config(vinput, virtio_tablet_config);
+ virtio_input_key_config(vinput, keymap_button,
+ ARRAY_SIZE(keymap_button));
+}
+
+static const TypeInfo virtio_tablet_info = {
+ .name = TYPE_VIRTIO_TABLET,
+ .parent = TYPE_VIRTIO_INPUT_HID,
+ .instance_size = sizeof(VirtIOInputHID),
+ .instance_init = virtio_tablet_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_input_hid_info);
+ type_register_static(&virtio_keyboard_info);
+ type_register_static(&virtio_mouse_info);
+ type_register_static(&virtio_tablet_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/input/virtio-input-host.c b/hw/input/virtio-input-host.c
new file mode 100644
index 00000000..8978f16b
--- /dev/null
+++ b/hw/input/virtio-input-host.c
@@ -0,0 +1,189 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu-common.h"
+#include "qemu/sockets.h"
+
+#include "hw/qdev.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-input.h"
+
+#include <sys/ioctl.h>
+#include "standard-headers/linux/input.h"
+
+/* ----------------------------------------------------------------- */
+
+static struct virtio_input_config virtio_input_host_config[] = {
+ { /* empty list */ },
+};
+
+static void virtio_input_host_event(void *opaque)
+{
+ VirtIOInputHost *vih = opaque;
+ VirtIOInput *vinput = VIRTIO_INPUT(vih);
+ struct virtio_input_event virtio;
+ struct input_event evdev;
+ int rc;
+
+ for (;;) {
+ rc = read(vih->fd, &evdev, sizeof(evdev));
+ if (rc != sizeof(evdev)) {
+ break;
+ }
+
+ virtio.type = cpu_to_le16(evdev.type);
+ virtio.code = cpu_to_le16(evdev.code);
+ virtio.value = cpu_to_le32(evdev.value);
+ virtio_input_send(vinput, &virtio);
+ }
+}
+
+static void virtio_input_bits_config(VirtIOInputHost *vih,
+ int type, int count)
+{
+ virtio_input_config bits;
+ int rc, i, size = 0;
+
+ memset(&bits, 0, sizeof(bits));
+ rc = ioctl(vih->fd, EVIOCGBIT(type, count/8), bits.u.bitmap);
+ if (rc < 0) {
+ return;
+ }
+
+ for (i = 0; i < count/8; i++) {
+ if (bits.u.bitmap[i]) {
+ size = i+1;
+ }
+ }
+ if (size == 0) {
+ return;
+ }
+
+ bits.select = VIRTIO_INPUT_CFG_EV_BITS;
+ bits.subsel = type;
+ bits.size = size;
+ virtio_input_add_config(VIRTIO_INPUT(vih), &bits);
+}
+
+static void virtio_input_host_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputHost *vih = VIRTIO_INPUT_HOST(dev);
+ VirtIOInput *vinput = VIRTIO_INPUT(dev);
+ virtio_input_config id;
+ struct input_id ids;
+ int rc, ver;
+
+ if (!vih->evdev) {
+ error_setg(errp, "evdev property is required");
+ return;
+ }
+
+ vih->fd = open(vih->evdev, O_RDWR);
+ if (vih->fd < 0) {
+ error_setg_file_open(errp, errno, vih->evdev);
+ return;
+ }
+ qemu_set_nonblock(vih->fd);
+
+ rc = ioctl(vih->fd, EVIOCGVERSION, &ver);
+ if (rc < 0) {
+ error_setg(errp, "%s: is not an evdev device", vih->evdev);
+ goto err_close;
+ }
+
+ rc = ioctl(vih->fd, EVIOCGRAB, 1);
+ if (rc < 0) {
+ error_setg_errno(errp, errno, "%s: failed to get exclusive access",
+ vih->evdev);
+ goto err_close;
+ }
+
+ memset(&id, 0, sizeof(id));
+ ioctl(vih->fd, EVIOCGNAME(sizeof(id.u.string)-1), id.u.string);
+ id.select = VIRTIO_INPUT_CFG_ID_NAME;
+ id.size = strlen(id.u.string);
+ virtio_input_add_config(vinput, &id);
+
+ if (ioctl(vih->fd, EVIOCGID, &ids) == 0) {
+ memset(&id, 0, sizeof(id));
+ id.select = VIRTIO_INPUT_CFG_ID_DEVIDS;
+ id.size = sizeof(struct virtio_input_devids);
+ id.u.ids.bustype = cpu_to_le16(ids.bustype);
+ id.u.ids.vendor = cpu_to_le16(ids.vendor);
+ id.u.ids.product = cpu_to_le16(ids.product);
+ id.u.ids.version = cpu_to_le16(ids.version);
+ virtio_input_add_config(vinput, &id);
+ }
+
+ virtio_input_bits_config(vih, EV_KEY, KEY_CNT);
+ virtio_input_bits_config(vih, EV_REL, REL_CNT);
+ virtio_input_bits_config(vih, EV_ABS, ABS_CNT);
+ virtio_input_bits_config(vih, EV_MSC, MSC_CNT);
+ virtio_input_bits_config(vih, EV_SW, SW_CNT);
+
+ qemu_set_fd_handler(vih->fd, virtio_input_host_event, NULL, vih);
+ return;
+
+err_close:
+ close(vih->fd);
+ vih->fd = -1;
+ return;
+}
+
+static void virtio_input_host_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputHost *vih = VIRTIO_INPUT_HOST(dev);
+
+ if (vih->fd > 0) {
+ qemu_set_fd_handler(vih->fd, NULL, NULL, NULL);
+ close(vih->fd);
+ }
+}
+
+static const VMStateDescription vmstate_virtio_input_host = {
+ .name = "virtio-input-host",
+ .unmigratable = 1,
+};
+
+static Property virtio_input_host_properties[] = {
+ DEFINE_PROP_STRING("evdev", VirtIOInputHost, evdev),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_input_host_class_init(ObjectClass *klass, void *data)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_virtio_input_host;
+ dc->props = virtio_input_host_properties;
+ vic->realize = virtio_input_host_realize;
+ vic->unrealize = virtio_input_host_unrealize;
+}
+
+static void virtio_input_host_init(Object *obj)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+ virtio_input_init_config(vinput, virtio_input_host_config);
+}
+
+static const TypeInfo virtio_input_host_info = {
+ .name = TYPE_VIRTIO_INPUT_HOST,
+ .parent = TYPE_VIRTIO_INPUT,
+ .instance_size = sizeof(VirtIOInputHost),
+ .instance_init = virtio_input_host_init,
+ .class_init = virtio_input_host_class_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_input_host_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/input/virtio-input.c b/hw/input/virtio-input.c
new file mode 100644
index 00000000..1f5a40de
--- /dev/null
+++ b/hw/input/virtio-input.c
@@ -0,0 +1,293 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/iov.h"
+
+#include "hw/qdev.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-input.h"
+
+#include "standard-headers/linux/input.h"
+
+/* ----------------------------------------------------------------- */
+
+void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event)
+{
+ VirtQueueElement elem;
+ unsigned have, need;
+ int i, len;
+
+ if (!vinput->active) {
+ return;
+ }
+
+ /* queue up events ... */
+ if (vinput->qindex == vinput->qsize) {
+ vinput->qsize++;
+ vinput->queue = realloc(vinput->queue, vinput->qsize *
+ sizeof(virtio_input_event));
+ }
+ vinput->queue[vinput->qindex++] = *event;
+
+ /* ... until we see a report sync ... */
+ if (event->type != cpu_to_le16(EV_SYN) ||
+ event->code != cpu_to_le16(SYN_REPORT)) {
+ return;
+ }
+
+ /* ... then check available space ... */
+ need = sizeof(virtio_input_event) * vinput->qindex;
+ virtqueue_get_avail_bytes(vinput->evt, &have, NULL, need, 0);
+ if (have < need) {
+ vinput->qindex = 0;
+ fprintf(stderr, "%s: ENOSPC in vq, dropping events\n", __func__);
+ return;
+ }
+
+ /* ... and finally pass them to the guest */
+ for (i = 0; i < vinput->qindex; i++) {
+ if (!virtqueue_pop(vinput->evt, &elem)) {
+ /* should not happen, we've checked for space beforehand */
+ fprintf(stderr, "%s: Huh? No vq elem available ...\n", __func__);
+ return;
+ }
+ len = iov_from_buf(elem.in_sg, elem.in_num,
+ 0, vinput->queue+i, sizeof(virtio_input_event));
+ virtqueue_push(vinput->evt, &elem, len);
+ }
+ virtio_notify(VIRTIO_DEVICE(vinput), vinput->evt);
+ vinput->qindex = 0;
+}
+
+static void virtio_input_handle_evt(VirtIODevice *vdev, VirtQueue *vq)
+{
+ /* nothing */
+}
+
+static void virtio_input_handle_sts(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev);
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+ virtio_input_event event;
+ VirtQueueElement elem;
+ int len;
+
+ while (virtqueue_pop(vinput->sts, &elem)) {
+ memset(&event, 0, sizeof(event));
+ len = iov_to_buf(elem.out_sg, elem.out_num,
+ 0, &event, sizeof(event));
+ if (vic->handle_status) {
+ vic->handle_status(vinput, &event);
+ }
+ virtqueue_push(vinput->sts, &elem, len);
+ }
+ virtio_notify(vdev, vinput->sts);
+}
+
+static virtio_input_config *virtio_input_find_config(VirtIOInput *vinput,
+ uint8_t select,
+ uint8_t subsel)
+{
+ VirtIOInputConfig *cfg;
+
+ QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) {
+ if (select == cfg->config.select &&
+ subsel == cfg->config.subsel) {
+ return &cfg->config;
+ }
+ }
+ return NULL;
+}
+
+void virtio_input_add_config(VirtIOInput *vinput,
+ virtio_input_config *config)
+{
+ VirtIOInputConfig *cfg;
+
+ if (virtio_input_find_config(vinput, config->select, config->subsel)) {
+ /* should not happen */
+ fprintf(stderr, "%s: duplicate config: %d/%d\n",
+ __func__, config->select, config->subsel);
+ abort();
+ }
+
+ cfg = g_new0(VirtIOInputConfig, 1);
+ cfg->config = *config;
+ QTAILQ_INSERT_TAIL(&vinput->cfg_list, cfg, node);
+}
+
+void virtio_input_init_config(VirtIOInput *vinput,
+ virtio_input_config *config)
+{
+ int i = 0;
+
+ QTAILQ_INIT(&vinput->cfg_list);
+ while (config[i].select) {
+ virtio_input_add_config(vinput, config + i);
+ i++;
+ }
+}
+
+void virtio_input_idstr_config(VirtIOInput *vinput,
+ uint8_t select, const char *string)
+{
+ virtio_input_config id;
+
+ if (!string) {
+ return;
+ }
+ memset(&id, 0, sizeof(id));
+ id.select = select;
+ id.size = snprintf(id.u.string, sizeof(id.u.string), "%s", string);
+ virtio_input_add_config(vinput, &id);
+}
+
+static void virtio_input_get_config(VirtIODevice *vdev, uint8_t *config_data)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+ virtio_input_config *config;
+
+ config = virtio_input_find_config(vinput, vinput->cfg_select,
+ vinput->cfg_subsel);
+ if (config) {
+ memcpy(config_data, config, vinput->cfg_size);
+ } else {
+ memset(config_data, 0, vinput->cfg_size);
+ }
+}
+
+static void virtio_input_set_config(VirtIODevice *vdev,
+ const uint8_t *config_data)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+ virtio_input_config *config = (virtio_input_config *)config_data;
+
+ vinput->cfg_select = config->select;
+ vinput->cfg_subsel = config->subsel;
+ virtio_notify_config(vdev);
+}
+
+static uint64_t virtio_input_get_features(VirtIODevice *vdev, uint64_t f,
+ Error **errp)
+{
+ return f;
+}
+
+static void virtio_input_set_status(VirtIODevice *vdev, uint8_t val)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev);
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+
+ if (val & VIRTIO_CONFIG_S_DRIVER_OK) {
+ if (!vinput->active) {
+ vinput->active = true;
+ if (vic->change_active) {
+ vic->change_active(vinput);
+ }
+ }
+ }
+}
+
+static void virtio_input_reset(VirtIODevice *vdev)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev);
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+
+ if (vinput->active) {
+ vinput->active = false;
+ if (vic->change_active) {
+ vic->change_active(vinput);
+ }
+ }
+}
+
+static void virtio_input_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev);
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOInput *vinput = VIRTIO_INPUT(dev);
+ VirtIOInputConfig *cfg;
+ Error *local_err = NULL;
+
+ if (vic->realize) {
+ vic->realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ }
+
+ virtio_input_idstr_config(vinput, VIRTIO_INPUT_CFG_ID_SERIAL,
+ vinput->serial);
+
+ QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) {
+ if (vinput->cfg_size < cfg->config.size) {
+ vinput->cfg_size = cfg->config.size;
+ }
+ }
+ vinput->cfg_size += 8;
+ assert(vinput->cfg_size <= sizeof(virtio_input_config));
+
+ virtio_init(vdev, "virtio-input", VIRTIO_ID_INPUT,
+ vinput->cfg_size);
+ vinput->evt = virtio_add_queue(vdev, 64, virtio_input_handle_evt);
+ vinput->sts = virtio_add_queue(vdev, 64, virtio_input_handle_sts);
+}
+
+static void virtio_input_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev);
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ Error *local_err = NULL;
+
+ if (vic->unrealize) {
+ vic->unrealize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ }
+ virtio_cleanup(vdev);
+}
+
+static Property virtio_input_properties[] = {
+ DEFINE_PROP_STRING("serial", VirtIOInput, serial),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_input_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ dc->props = virtio_input_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ vdc->realize = virtio_input_device_realize;
+ vdc->unrealize = virtio_input_device_unrealize;
+ vdc->get_config = virtio_input_get_config;
+ vdc->set_config = virtio_input_set_config;
+ vdc->get_features = virtio_input_get_features;
+ vdc->set_status = virtio_input_set_status;
+ vdc->reset = virtio_input_reset;
+}
+
+static const TypeInfo virtio_input_info = {
+ .name = TYPE_VIRTIO_INPUT,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOInput),
+ .class_size = sizeof(VirtIOInputClass),
+ .class_init = virtio_input_class_init,
+ .abstract = true,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_input_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/input/vmmouse.c b/hw/input/vmmouse.c
new file mode 100644
index 00000000..d7b1c76f
--- /dev/null
+++ b/hw/input/vmmouse.c
@@ -0,0 +1,303 @@
+/*
+ * QEMU VMMouse emulation
+ *
+ * Copyright (C) 2007 Anthony Liguori <anthony@codemonkey.ws>
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/input/ps2.h"
+#include "hw/i386/pc.h"
+#include "hw/qdev.h"
+
+/* debug only vmmouse */
+//#define DEBUG_VMMOUSE
+
+/* VMMouse Commands */
+#define VMMOUSE_GETVERSION 10
+#define VMMOUSE_DATA 39
+#define VMMOUSE_STATUS 40
+#define VMMOUSE_COMMAND 41
+
+#define VMMOUSE_READ_ID 0x45414552
+#define VMMOUSE_DISABLE 0x000000f5
+#define VMMOUSE_REQUEST_RELATIVE 0x4c455252
+#define VMMOUSE_REQUEST_ABSOLUTE 0x53424152
+
+#define VMMOUSE_QUEUE_SIZE 1024
+
+#define VMMOUSE_VERSION 0x3442554a
+
+#ifdef DEBUG_VMMOUSE
+#define DPRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+#define TYPE_VMMOUSE "vmmouse"
+#define VMMOUSE(obj) OBJECT_CHECK(VMMouseState, (obj), TYPE_VMMOUSE)
+
+typedef struct VMMouseState
+{
+ ISADevice parent_obj;
+
+ uint32_t queue[VMMOUSE_QUEUE_SIZE];
+ int32_t queue_size;
+ uint16_t nb_queue;
+ uint16_t status;
+ uint8_t absolute;
+ QEMUPutMouseEntry *entry;
+ void *ps2_mouse;
+} VMMouseState;
+
+static uint32_t vmmouse_get_status(VMMouseState *s)
+{
+ DPRINTF("vmmouse_get_status()\n");
+ return (s->status << 16) | s->nb_queue;
+}
+
+static void vmmouse_mouse_event(void *opaque, int x, int y, int dz, int buttons_state)
+{
+ VMMouseState *s = opaque;
+ int buttons = 0;
+
+ if (s->nb_queue > (VMMOUSE_QUEUE_SIZE - 4))
+ return;
+
+ DPRINTF("vmmouse_mouse_event(%d, %d, %d, %d)\n",
+ x, y, dz, buttons_state);
+
+ if ((buttons_state & MOUSE_EVENT_LBUTTON))
+ buttons |= 0x20;
+ if ((buttons_state & MOUSE_EVENT_RBUTTON))
+ buttons |= 0x10;
+ if ((buttons_state & MOUSE_EVENT_MBUTTON))
+ buttons |= 0x08;
+
+ if (s->absolute) {
+ x <<= 1;
+ y <<= 1;
+ }
+
+ s->queue[s->nb_queue++] = buttons;
+ s->queue[s->nb_queue++] = x;
+ s->queue[s->nb_queue++] = y;
+ s->queue[s->nb_queue++] = dz;
+
+ /* need to still generate PS2 events to notify driver to
+ read from queue */
+ i8042_isa_mouse_fake_event(s->ps2_mouse);
+}
+
+static void vmmouse_remove_handler(VMMouseState *s)
+{
+ if (s->entry) {
+ qemu_remove_mouse_event_handler(s->entry);
+ s->entry = NULL;
+ }
+}
+
+static void vmmouse_update_handler(VMMouseState *s, int absolute)
+{
+ if (s->status != 0) {
+ return;
+ }
+ if (s->absolute != absolute) {
+ s->absolute = absolute;
+ vmmouse_remove_handler(s);
+ }
+ if (s->entry == NULL) {
+ s->entry = qemu_add_mouse_event_handler(vmmouse_mouse_event,
+ s, s->absolute,
+ "vmmouse");
+ qemu_activate_mouse_event_handler(s->entry);
+ }
+}
+
+static void vmmouse_read_id(VMMouseState *s)
+{
+ DPRINTF("vmmouse_read_id()\n");
+
+ if (s->nb_queue == VMMOUSE_QUEUE_SIZE)
+ return;
+
+ s->queue[s->nb_queue++] = VMMOUSE_VERSION;
+ s->status = 0;
+}
+
+static void vmmouse_request_relative(VMMouseState *s)
+{
+ DPRINTF("vmmouse_request_relative()\n");
+ vmmouse_update_handler(s, 0);
+}
+
+static void vmmouse_request_absolute(VMMouseState *s)
+{
+ DPRINTF("vmmouse_request_absolute()\n");
+ vmmouse_update_handler(s, 1);
+}
+
+static void vmmouse_disable(VMMouseState *s)
+{
+ DPRINTF("vmmouse_disable()\n");
+ s->status = 0xffff;
+ vmmouse_remove_handler(s);
+}
+
+static void vmmouse_data(VMMouseState *s, uint32_t *data, uint32_t size)
+{
+ int i;
+
+ DPRINTF("vmmouse_data(%d)\n", size);
+
+ if (size == 0 || size > 6 || size > s->nb_queue) {
+ printf("vmmouse: driver requested too much data %d\n", size);
+ s->status = 0xffff;
+ vmmouse_remove_handler(s);
+ return;
+ }
+
+ for (i = 0; i < size; i++)
+ data[i] = s->queue[i];
+
+ s->nb_queue -= size;
+ if (s->nb_queue)
+ memmove(s->queue, &s->queue[size], sizeof(s->queue[0]) * s->nb_queue);
+}
+
+static uint32_t vmmouse_ioport_read(void *opaque, uint32_t addr)
+{
+ VMMouseState *s = opaque;
+ uint32_t data[6];
+ uint16_t command;
+
+ vmmouse_get_data(data);
+
+ command = data[2] & 0xFFFF;
+
+ switch (command) {
+ case VMMOUSE_STATUS:
+ data[0] = vmmouse_get_status(s);
+ break;
+ case VMMOUSE_COMMAND:
+ switch (data[1]) {
+ case VMMOUSE_DISABLE:
+ vmmouse_disable(s);
+ break;
+ case VMMOUSE_READ_ID:
+ vmmouse_read_id(s);
+ break;
+ case VMMOUSE_REQUEST_RELATIVE:
+ vmmouse_request_relative(s);
+ break;
+ case VMMOUSE_REQUEST_ABSOLUTE:
+ vmmouse_request_absolute(s);
+ break;
+ default:
+ printf("vmmouse: unknown command %x\n", data[1]);
+ break;
+ }
+ break;
+ case VMMOUSE_DATA:
+ vmmouse_data(s, data, data[1]);
+ break;
+ default:
+ printf("vmmouse: unknown command %x\n", command);
+ break;
+ }
+
+ vmmouse_set_data(data);
+ return data[0];
+}
+
+static int vmmouse_post_load(void *opaque, int version_id)
+{
+ VMMouseState *s = opaque;
+
+ vmmouse_remove_handler(s);
+ vmmouse_update_handler(s, s->absolute);
+ return 0;
+}
+
+static const VMStateDescription vmstate_vmmouse = {
+ .name = "vmmouse",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = vmmouse_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_EQUAL(queue_size, VMMouseState),
+ VMSTATE_UINT32_ARRAY(queue, VMMouseState, VMMOUSE_QUEUE_SIZE),
+ VMSTATE_UINT16(nb_queue, VMMouseState),
+ VMSTATE_UINT16(status, VMMouseState),
+ VMSTATE_UINT8(absolute, VMMouseState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void vmmouse_reset(DeviceState *d)
+{
+ VMMouseState *s = VMMOUSE(d);
+
+ s->queue_size = VMMOUSE_QUEUE_SIZE;
+
+ vmmouse_disable(s);
+}
+
+static void vmmouse_realizefn(DeviceState *dev, Error **errp)
+{
+ VMMouseState *s = VMMOUSE(dev);
+
+ DPRINTF("vmmouse_init\n");
+
+ vmport_register(VMMOUSE_STATUS, vmmouse_ioport_read, s);
+ vmport_register(VMMOUSE_COMMAND, vmmouse_ioport_read, s);
+ vmport_register(VMMOUSE_DATA, vmmouse_ioport_read, s);
+}
+
+static Property vmmouse_properties[] = {
+ DEFINE_PROP_PTR("ps2_mouse", VMMouseState, ps2_mouse),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vmmouse_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = vmmouse_realizefn;
+ dc->reset = vmmouse_reset;
+ dc->vmsd = &vmstate_vmmouse;
+ dc->props = vmmouse_properties;
+ /* Reason: pointer property "ps2_mouse" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo vmmouse_info = {
+ .name = TYPE_VMMOUSE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(VMMouseState),
+ .class_init = vmmouse_class_initfn,
+};
+
+static void vmmouse_register_types(void)
+{
+ type_register_static(&vmmouse_info);
+}
+
+type_init(vmmouse_register_types)
diff --git a/hw/intc/Makefile.objs b/hw/intc/Makefile.objs
new file mode 100644
index 00000000..092d8a80
--- /dev/null
+++ b/hw/intc/Makefile.objs
@@ -0,0 +1,30 @@
+common-obj-$(CONFIG_HEATHROW_PIC) += heathrow_pic.o
+common-obj-$(CONFIG_I8259) += i8259_common.o i8259.o
+common-obj-$(CONFIG_PL190) += pl190.o
+common-obj-$(CONFIG_PUV3) += puv3_intc.o
+common-obj-$(CONFIG_XILINX) += xilinx_intc.o
+common-obj-$(CONFIG_ETRAXFS) += etraxfs_pic.o
+common-obj-$(CONFIG_IMX) += imx_avic.o
+common-obj-$(CONFIG_LM32) += lm32_pic.o
+common-obj-$(CONFIG_REALVIEW) += realview_gic.o
+common-obj-$(CONFIG_SLAVIO) += slavio_intctl.o
+common-obj-$(CONFIG_IOAPIC) += ioapic_common.o
+common-obj-$(CONFIG_ARM_GIC) += arm_gic_common.o
+common-obj-$(CONFIG_ARM_GIC) += arm_gic.o
+common-obj-$(CONFIG_ARM_GIC) += arm_gicv2m.o
+common-obj-$(CONFIG_OPENPIC) += openpic.o
+
+obj-$(CONFIG_APIC) += apic.o apic_common.o
+obj-$(CONFIG_ARM_GIC_KVM) += arm_gic_kvm.o
+obj-$(CONFIG_STELLARIS) += armv7m_nvic.o
+obj-$(CONFIG_EXYNOS4) += exynos4210_gic.o exynos4210_combiner.o
+obj-$(CONFIG_GRLIB) += grlib_irqmp.o
+obj-$(CONFIG_IOAPIC) += ioapic.o
+obj-$(CONFIG_OMAP) += omap_intc.o
+obj-$(CONFIG_OPENPIC_KVM) += openpic_kvm.o
+obj-$(CONFIG_SH4) += sh_intc.o
+obj-$(CONFIG_XICS) += xics.o
+obj-$(CONFIG_XICS_KVM) += xics_kvm.o
+obj-$(CONFIG_ALLWINNER_A10_PIC) += allwinner-a10-pic.o
+obj-$(CONFIG_S390_FLIC) += s390_flic.o
+obj-$(CONFIG_S390_FLIC_KVM) += s390_flic_kvm.o
diff --git a/hw/intc/allwinner-a10-pic.c b/hw/intc/allwinner-a10-pic.c
new file mode 100644
index 00000000..eed7621f
--- /dev/null
+++ b/hw/intc/allwinner-a10-pic.c
@@ -0,0 +1,212 @@
+/*
+ * Allwinner A10 interrupt controller device emulation
+ *
+ * Copyright (C) 2013 Li Guang
+ * Written by Li Guang <lig.fnst@cn.fujitsu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/devices.h"
+#include "sysemu/sysemu.h"
+#include "hw/intc/allwinner-a10-pic.h"
+
+static void aw_a10_pic_update(AwA10PICState *s)
+{
+ uint8_t i;
+ int irq = 0, fiq = 0, zeroes;
+
+ s->vector = 0;
+
+ for (i = 0; i < AW_A10_PIC_REG_NUM; i++) {
+ irq |= s->irq_pending[i] & ~s->mask[i];
+ fiq |= s->select[i] & s->irq_pending[i] & ~s->mask[i];
+
+ if (!s->vector) {
+ zeroes = ctz32(s->irq_pending[i] & ~s->mask[i]);
+ if (zeroes != 32) {
+ s->vector = (i * 32 + zeroes) * 4;
+ }
+ }
+ }
+
+ qemu_set_irq(s->parent_irq, !!irq);
+ qemu_set_irq(s->parent_fiq, !!fiq);
+}
+
+static void aw_a10_pic_set_irq(void *opaque, int irq, int level)
+{
+ AwA10PICState *s = opaque;
+
+ if (level) {
+ set_bit(irq % 32, (void *)&s->irq_pending[irq / 32]);
+ } else {
+ clear_bit(irq % 32, (void *)&s->irq_pending[irq / 32]);
+ }
+ aw_a10_pic_update(s);
+}
+
+static uint64_t aw_a10_pic_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AwA10PICState *s = opaque;
+ uint8_t index = (offset & 0xc) / 4;
+
+ switch (offset) {
+ case AW_A10_PIC_VECTOR:
+ return s->vector;
+ case AW_A10_PIC_BASE_ADDR:
+ return s->base_addr;
+ case AW_A10_PIC_PROTECT:
+ return s->protect;
+ case AW_A10_PIC_NMI:
+ return s->nmi;
+ case AW_A10_PIC_IRQ_PENDING ... AW_A10_PIC_IRQ_PENDING + 8:
+ return s->irq_pending[index];
+ case AW_A10_PIC_FIQ_PENDING ... AW_A10_PIC_FIQ_PENDING + 8:
+ return s->fiq_pending[index];
+ case AW_A10_PIC_SELECT ... AW_A10_PIC_SELECT + 8:
+ return s->select[index];
+ case AW_A10_PIC_ENABLE ... AW_A10_PIC_ENABLE + 8:
+ return s->enable[index];
+ case AW_A10_PIC_MASK ... AW_A10_PIC_MASK + 8:
+ return s->mask[index];
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ break;
+ }
+
+ return 0;
+}
+
+static void aw_a10_pic_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ AwA10PICState *s = opaque;
+ uint8_t index = (offset & 0xc) / 4;
+
+ switch (offset) {
+ case AW_A10_PIC_BASE_ADDR:
+ s->base_addr = value & ~0x3;
+ break;
+ case AW_A10_PIC_PROTECT:
+ s->protect = value;
+ break;
+ case AW_A10_PIC_NMI:
+ s->nmi = value;
+ break;
+ case AW_A10_PIC_IRQ_PENDING ... AW_A10_PIC_IRQ_PENDING + 8:
+ /*
+ * The register is read-only; nevertheless, Linux (including
+ * the version originally shipped by Allwinner) pretends to
+ * write to the register. Just ignore it.
+ */
+ break;
+ case AW_A10_PIC_FIQ_PENDING ... AW_A10_PIC_FIQ_PENDING + 8:
+ s->fiq_pending[index] &= ~value;
+ break;
+ case AW_A10_PIC_SELECT ... AW_A10_PIC_SELECT + 8:
+ s->select[index] = value;
+ break;
+ case AW_A10_PIC_ENABLE ... AW_A10_PIC_ENABLE + 8:
+ s->enable[index] = value;
+ break;
+ case AW_A10_PIC_MASK ... AW_A10_PIC_MASK + 8:
+ s->mask[index] = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ break;
+ }
+
+ aw_a10_pic_update(s);
+}
+
+static const MemoryRegionOps aw_a10_pic_ops = {
+ .read = aw_a10_pic_read,
+ .write = aw_a10_pic_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_aw_a10_pic = {
+ .name = "a10.pic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(vector, AwA10PICState),
+ VMSTATE_UINT32(base_addr, AwA10PICState),
+ VMSTATE_UINT32(protect, AwA10PICState),
+ VMSTATE_UINT32(nmi, AwA10PICState),
+ VMSTATE_UINT32_ARRAY(irq_pending, AwA10PICState, AW_A10_PIC_REG_NUM),
+ VMSTATE_UINT32_ARRAY(fiq_pending, AwA10PICState, AW_A10_PIC_REG_NUM),
+ VMSTATE_UINT32_ARRAY(enable, AwA10PICState, AW_A10_PIC_REG_NUM),
+ VMSTATE_UINT32_ARRAY(select, AwA10PICState, AW_A10_PIC_REG_NUM),
+ VMSTATE_UINT32_ARRAY(mask, AwA10PICState, AW_A10_PIC_REG_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void aw_a10_pic_init(Object *obj)
+{
+ AwA10PICState *s = AW_A10_PIC(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ qdev_init_gpio_in(DEVICE(dev), aw_a10_pic_set_irq, AW_A10_PIC_INT_NR);
+ sysbus_init_irq(dev, &s->parent_irq);
+ sysbus_init_irq(dev, &s->parent_fiq);
+ memory_region_init_io(&s->iomem, OBJECT(s), &aw_a10_pic_ops, s,
+ TYPE_AW_A10_PIC, 0x400);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static void aw_a10_pic_reset(DeviceState *d)
+{
+ AwA10PICState *s = AW_A10_PIC(d);
+ uint8_t i;
+
+ s->base_addr = 0;
+ s->protect = 0;
+ s->nmi = 0;
+ s->vector = 0;
+ for (i = 0; i < AW_A10_PIC_REG_NUM; i++) {
+ s->irq_pending[i] = 0;
+ s->fiq_pending[i] = 0;
+ s->select[i] = 0;
+ s->enable[i] = 0;
+ s->mask[i] = 0;
+ }
+}
+
+static void aw_a10_pic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = aw_a10_pic_reset;
+ dc->desc = "allwinner a10 pic";
+ dc->vmsd = &vmstate_aw_a10_pic;
+ }
+
+static const TypeInfo aw_a10_pic_info = {
+ .name = TYPE_AW_A10_PIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AwA10PICState),
+ .instance_init = aw_a10_pic_init,
+ .class_init = aw_a10_pic_class_init,
+};
+
+static void aw_a10_register_types(void)
+{
+ type_register_static(&aw_a10_pic_info);
+}
+
+type_init(aw_a10_register_types);
diff --git a/hw/intc/apic.c b/hw/intc/apic.c
new file mode 100644
index 00000000..77b639cc
--- /dev/null
+++ b/hw/intc/apic.c
@@ -0,0 +1,921 @@
+/*
+ * APIC support
+ *
+ * Copyright (c) 2004-2005 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+#include "qemu/thread.h"
+#include "hw/i386/apic_internal.h"
+#include "hw/i386/apic.h"
+#include "hw/i386/ioapic.h"
+#include "hw/pci/msi.h"
+#include "qemu/host-utils.h"
+#include "trace.h"
+#include "hw/i386/pc.h"
+#include "hw/i386/apic-msidef.h"
+
+#define MAX_APIC_WORDS 8
+
+#define SYNC_FROM_VAPIC 0x1
+#define SYNC_TO_VAPIC 0x2
+#define SYNC_ISR_IRR_TO_VAPIC 0x4
+
+static APICCommonState *local_apics[MAX_APICS + 1];
+
+static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode);
+static void apic_update_irq(APICCommonState *s);
+static void apic_get_delivery_bitmask(uint32_t *deliver_bitmask,
+ uint8_t dest, uint8_t dest_mode);
+
+/* Find first bit starting from msb */
+static int apic_fls_bit(uint32_t value)
+{
+ return 31 - clz32(value);
+}
+
+/* Find first bit starting from lsb */
+static int apic_ffs_bit(uint32_t value)
+{
+ return ctz32(value);
+}
+
+static inline void apic_set_bit(uint32_t *tab, int index)
+{
+ int i, mask;
+ i = index >> 5;
+ mask = 1 << (index & 0x1f);
+ tab[i] |= mask;
+}
+
+static inline void apic_reset_bit(uint32_t *tab, int index)
+{
+ int i, mask;
+ i = index >> 5;
+ mask = 1 << (index & 0x1f);
+ tab[i] &= ~mask;
+}
+
+static inline int apic_get_bit(uint32_t *tab, int index)
+{
+ int i, mask;
+ i = index >> 5;
+ mask = 1 << (index & 0x1f);
+ return !!(tab[i] & mask);
+}
+
+/* return -1 if no bit is set */
+static int get_highest_priority_int(uint32_t *tab)
+{
+ int i;
+ for (i = 7; i >= 0; i--) {
+ if (tab[i] != 0) {
+ return i * 32 + apic_fls_bit(tab[i]);
+ }
+ }
+ return -1;
+}
+
+static void apic_sync_vapic(APICCommonState *s, int sync_type)
+{
+ VAPICState vapic_state;
+ size_t length;
+ off_t start;
+ int vector;
+
+ if (!s->vapic_paddr) {
+ return;
+ }
+ if (sync_type & SYNC_FROM_VAPIC) {
+ cpu_physical_memory_read(s->vapic_paddr, &vapic_state,
+ sizeof(vapic_state));
+ s->tpr = vapic_state.tpr;
+ }
+ if (sync_type & (SYNC_TO_VAPIC | SYNC_ISR_IRR_TO_VAPIC)) {
+ start = offsetof(VAPICState, isr);
+ length = offsetof(VAPICState, enabled) - offsetof(VAPICState, isr);
+
+ if (sync_type & SYNC_TO_VAPIC) {
+ assert(qemu_cpu_is_self(CPU(s->cpu)));
+
+ vapic_state.tpr = s->tpr;
+ vapic_state.enabled = 1;
+ start = 0;
+ length = sizeof(VAPICState);
+ }
+
+ vector = get_highest_priority_int(s->isr);
+ if (vector < 0) {
+ vector = 0;
+ }
+ vapic_state.isr = vector & 0xf0;
+
+ vapic_state.zero = 0;
+
+ vector = get_highest_priority_int(s->irr);
+ if (vector < 0) {
+ vector = 0;
+ }
+ vapic_state.irr = vector & 0xff;
+
+ cpu_physical_memory_write_rom(&address_space_memory,
+ s->vapic_paddr + start,
+ ((void *)&vapic_state) + start, length);
+ }
+}
+
+static void apic_vapic_base_update(APICCommonState *s)
+{
+ apic_sync_vapic(s, SYNC_TO_VAPIC);
+}
+
+static void apic_local_deliver(APICCommonState *s, int vector)
+{
+ uint32_t lvt = s->lvt[vector];
+ int trigger_mode;
+
+ trace_apic_local_deliver(vector, (lvt >> 8) & 7);
+
+ if (lvt & APIC_LVT_MASKED)
+ return;
+
+ switch ((lvt >> 8) & 7) {
+ case APIC_DM_SMI:
+ cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_SMI);
+ break;
+
+ case APIC_DM_NMI:
+ cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_NMI);
+ break;
+
+ case APIC_DM_EXTINT:
+ cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HARD);
+ break;
+
+ case APIC_DM_FIXED:
+ trigger_mode = APIC_TRIGGER_EDGE;
+ if ((vector == APIC_LVT_LINT0 || vector == APIC_LVT_LINT1) &&
+ (lvt & APIC_LVT_LEVEL_TRIGGER))
+ trigger_mode = APIC_TRIGGER_LEVEL;
+ apic_set_irq(s, lvt & 0xff, trigger_mode);
+ }
+}
+
+void apic_deliver_pic_intr(DeviceState *dev, int level)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+
+ if (level) {
+ apic_local_deliver(s, APIC_LVT_LINT0);
+ } else {
+ uint32_t lvt = s->lvt[APIC_LVT_LINT0];
+
+ switch ((lvt >> 8) & 7) {
+ case APIC_DM_FIXED:
+ if (!(lvt & APIC_LVT_LEVEL_TRIGGER))
+ break;
+ apic_reset_bit(s->irr, lvt & 0xff);
+ /* fall through */
+ case APIC_DM_EXTINT:
+ apic_update_irq(s);
+ break;
+ }
+ }
+}
+
+static void apic_external_nmi(APICCommonState *s)
+{
+ apic_local_deliver(s, APIC_LVT_LINT1);
+}
+
+#define foreach_apic(apic, deliver_bitmask, code) \
+{\
+ int __i, __j;\
+ for(__i = 0; __i < MAX_APIC_WORDS; __i++) {\
+ uint32_t __mask = deliver_bitmask[__i];\
+ if (__mask) {\
+ for(__j = 0; __j < 32; __j++) {\
+ if (__mask & (1U << __j)) {\
+ apic = local_apics[__i * 32 + __j];\
+ if (apic) {\
+ code;\
+ }\
+ }\
+ }\
+ }\
+ }\
+}
+
+static void apic_bus_deliver(const uint32_t *deliver_bitmask,
+ uint8_t delivery_mode, uint8_t vector_num,
+ uint8_t trigger_mode)
+{
+ APICCommonState *apic_iter;
+
+ switch (delivery_mode) {
+ case APIC_DM_LOWPRI:
+ /* XXX: search for focus processor, arbitration */
+ {
+ int i, d;
+ d = -1;
+ for(i = 0; i < MAX_APIC_WORDS; i++) {
+ if (deliver_bitmask[i]) {
+ d = i * 32 + apic_ffs_bit(deliver_bitmask[i]);
+ break;
+ }
+ }
+ if (d >= 0) {
+ apic_iter = local_apics[d];
+ if (apic_iter) {
+ apic_set_irq(apic_iter, vector_num, trigger_mode);
+ }
+ }
+ }
+ return;
+
+ case APIC_DM_FIXED:
+ break;
+
+ case APIC_DM_SMI:
+ foreach_apic(apic_iter, deliver_bitmask,
+ cpu_interrupt(CPU(apic_iter->cpu), CPU_INTERRUPT_SMI)
+ );
+ return;
+
+ case APIC_DM_NMI:
+ foreach_apic(apic_iter, deliver_bitmask,
+ cpu_interrupt(CPU(apic_iter->cpu), CPU_INTERRUPT_NMI)
+ );
+ return;
+
+ case APIC_DM_INIT:
+ /* normal INIT IPI sent to processors */
+ foreach_apic(apic_iter, deliver_bitmask,
+ cpu_interrupt(CPU(apic_iter->cpu),
+ CPU_INTERRUPT_INIT)
+ );
+ return;
+
+ case APIC_DM_EXTINT:
+ /* handled in I/O APIC code */
+ break;
+
+ default:
+ return;
+ }
+
+ foreach_apic(apic_iter, deliver_bitmask,
+ apic_set_irq(apic_iter, vector_num, trigger_mode) );
+}
+
+void apic_deliver_irq(uint8_t dest, uint8_t dest_mode, uint8_t delivery_mode,
+ uint8_t vector_num, uint8_t trigger_mode)
+{
+ uint32_t deliver_bitmask[MAX_APIC_WORDS];
+
+ trace_apic_deliver_irq(dest, dest_mode, delivery_mode, vector_num,
+ trigger_mode);
+
+ apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode);
+ apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, trigger_mode);
+}
+
+static void apic_set_base(APICCommonState *s, uint64_t val)
+{
+ s->apicbase = (val & 0xfffff000) |
+ (s->apicbase & (MSR_IA32_APICBASE_BSP | MSR_IA32_APICBASE_ENABLE));
+ /* if disabled, cannot be enabled again */
+ if (!(val & MSR_IA32_APICBASE_ENABLE)) {
+ s->apicbase &= ~MSR_IA32_APICBASE_ENABLE;
+ cpu_clear_apic_feature(&s->cpu->env);
+ s->spurious_vec &= ~APIC_SV_ENABLE;
+ }
+}
+
+static void apic_set_tpr(APICCommonState *s, uint8_t val)
+{
+ /* Updates from cr8 are ignored while the VAPIC is active */
+ if (!s->vapic_paddr) {
+ s->tpr = val << 4;
+ apic_update_irq(s);
+ }
+}
+
+static uint8_t apic_get_tpr(APICCommonState *s)
+{
+ apic_sync_vapic(s, SYNC_FROM_VAPIC);
+ return s->tpr >> 4;
+}
+
+static int apic_get_ppr(APICCommonState *s)
+{
+ int tpr, isrv, ppr;
+
+ tpr = (s->tpr >> 4);
+ isrv = get_highest_priority_int(s->isr);
+ if (isrv < 0)
+ isrv = 0;
+ isrv >>= 4;
+ if (tpr >= isrv)
+ ppr = s->tpr;
+ else
+ ppr = isrv << 4;
+ return ppr;
+}
+
+static int apic_get_arb_pri(APICCommonState *s)
+{
+ /* XXX: arbitration */
+ return 0;
+}
+
+
+/*
+ * <0 - low prio interrupt,
+ * 0 - no interrupt,
+ * >0 - interrupt number
+ */
+static int apic_irq_pending(APICCommonState *s)
+{
+ int irrv, ppr;
+
+ if (!(s->spurious_vec & APIC_SV_ENABLE)) {
+ return 0;
+ }
+
+ irrv = get_highest_priority_int(s->irr);
+ if (irrv < 0) {
+ return 0;
+ }
+ ppr = apic_get_ppr(s);
+ if (ppr && (irrv & 0xf0) <= (ppr & 0xf0)) {
+ return -1;
+ }
+
+ return irrv;
+}
+
+/* signal the CPU if an irq is pending */
+static void apic_update_irq(APICCommonState *s)
+{
+ CPUState *cpu;
+ DeviceState *dev = (DeviceState *)s;
+
+ cpu = CPU(s->cpu);
+ if (!qemu_cpu_is_self(cpu)) {
+ cpu_interrupt(cpu, CPU_INTERRUPT_POLL);
+ } else if (apic_irq_pending(s) > 0) {
+ cpu_interrupt(cpu, CPU_INTERRUPT_HARD);
+ } else if (!apic_accept_pic_intr(dev) || !pic_get_output(isa_pic)) {
+ cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD);
+ }
+}
+
+void apic_poll_irq(DeviceState *dev)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+
+ apic_sync_vapic(s, SYNC_FROM_VAPIC);
+ apic_update_irq(s);
+}
+
+static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode)
+{
+ apic_report_irq_delivered(!apic_get_bit(s->irr, vector_num));
+
+ apic_set_bit(s->irr, vector_num);
+ if (trigger_mode)
+ apic_set_bit(s->tmr, vector_num);
+ else
+ apic_reset_bit(s->tmr, vector_num);
+ if (s->vapic_paddr) {
+ apic_sync_vapic(s, SYNC_ISR_IRR_TO_VAPIC);
+ /*
+ * The vcpu thread needs to see the new IRR before we pull its current
+ * TPR value. That way, if we miss a lowering of the TRP, the guest
+ * has the chance to notice the new IRR and poll for IRQs on its own.
+ */
+ smp_wmb();
+ apic_sync_vapic(s, SYNC_FROM_VAPIC);
+ }
+ apic_update_irq(s);
+}
+
+static void apic_eoi(APICCommonState *s)
+{
+ int isrv;
+ isrv = get_highest_priority_int(s->isr);
+ if (isrv < 0)
+ return;
+ apic_reset_bit(s->isr, isrv);
+ if (!(s->spurious_vec & APIC_SV_DIRECTED_IO) && apic_get_bit(s->tmr, isrv)) {
+ ioapic_eoi_broadcast(isrv);
+ }
+ apic_sync_vapic(s, SYNC_FROM_VAPIC | SYNC_TO_VAPIC);
+ apic_update_irq(s);
+}
+
+static int apic_find_dest(uint8_t dest)
+{
+ APICCommonState *apic = local_apics[dest];
+ int i;
+
+ if (apic && apic->id == dest)
+ return dest; /* shortcut in case apic->id == apic->idx */
+
+ for (i = 0; i < MAX_APICS; i++) {
+ apic = local_apics[i];
+ if (apic && apic->id == dest)
+ return i;
+ if (!apic)
+ break;
+ }
+
+ return -1;
+}
+
+static void apic_get_delivery_bitmask(uint32_t *deliver_bitmask,
+ uint8_t dest, uint8_t dest_mode)
+{
+ APICCommonState *apic_iter;
+ int i;
+
+ if (dest_mode == 0) {
+ if (dest == 0xff) {
+ memset(deliver_bitmask, 0xff, MAX_APIC_WORDS * sizeof(uint32_t));
+ } else {
+ int idx = apic_find_dest(dest);
+ memset(deliver_bitmask, 0x00, MAX_APIC_WORDS * sizeof(uint32_t));
+ if (idx >= 0)
+ apic_set_bit(deliver_bitmask, idx);
+ }
+ } else {
+ /* XXX: cluster mode */
+ memset(deliver_bitmask, 0x00, MAX_APIC_WORDS * sizeof(uint32_t));
+ for(i = 0; i < MAX_APICS; i++) {
+ apic_iter = local_apics[i];
+ if (apic_iter) {
+ if (apic_iter->dest_mode == 0xf) {
+ if (dest & apic_iter->log_dest)
+ apic_set_bit(deliver_bitmask, i);
+ } else if (apic_iter->dest_mode == 0x0) {
+ if ((dest & 0xf0) == (apic_iter->log_dest & 0xf0) &&
+ (dest & apic_iter->log_dest & 0x0f)) {
+ apic_set_bit(deliver_bitmask, i);
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+static void apic_startup(APICCommonState *s, int vector_num)
+{
+ s->sipi_vector = vector_num;
+ cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_SIPI);
+}
+
+void apic_sipi(DeviceState *dev)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+
+ cpu_reset_interrupt(CPU(s->cpu), CPU_INTERRUPT_SIPI);
+
+ if (!s->wait_for_sipi)
+ return;
+ cpu_x86_load_seg_cache_sipi(s->cpu, s->sipi_vector);
+ s->wait_for_sipi = 0;
+}
+
+static void apic_deliver(DeviceState *dev, uint8_t dest, uint8_t dest_mode,
+ uint8_t delivery_mode, uint8_t vector_num,
+ uint8_t trigger_mode)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ uint32_t deliver_bitmask[MAX_APIC_WORDS];
+ int dest_shorthand = (s->icr[0] >> 18) & 3;
+ APICCommonState *apic_iter;
+
+ switch (dest_shorthand) {
+ case 0:
+ apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode);
+ break;
+ case 1:
+ memset(deliver_bitmask, 0x00, sizeof(deliver_bitmask));
+ apic_set_bit(deliver_bitmask, s->idx);
+ break;
+ case 2:
+ memset(deliver_bitmask, 0xff, sizeof(deliver_bitmask));
+ break;
+ case 3:
+ memset(deliver_bitmask, 0xff, sizeof(deliver_bitmask));
+ apic_reset_bit(deliver_bitmask, s->idx);
+ break;
+ }
+
+ switch (delivery_mode) {
+ case APIC_DM_INIT:
+ {
+ int trig_mode = (s->icr[0] >> 15) & 1;
+ int level = (s->icr[0] >> 14) & 1;
+ if (level == 0 && trig_mode == 1) {
+ foreach_apic(apic_iter, deliver_bitmask,
+ apic_iter->arb_id = apic_iter->id );
+ return;
+ }
+ }
+ break;
+
+ case APIC_DM_SIPI:
+ foreach_apic(apic_iter, deliver_bitmask,
+ apic_startup(apic_iter, vector_num) );
+ return;
+ }
+
+ apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, trigger_mode);
+}
+
+static bool apic_check_pic(APICCommonState *s)
+{
+ DeviceState *dev = (DeviceState *)s;
+
+ if (!apic_accept_pic_intr(dev) || !pic_get_output(isa_pic)) {
+ return false;
+ }
+ apic_deliver_pic_intr(dev, 1);
+ return true;
+}
+
+int apic_get_interrupt(DeviceState *dev)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ int intno;
+
+ /* if the APIC is installed or enabled, we let the 8259 handle the
+ IRQs */
+ if (!s)
+ return -1;
+ if (!(s->spurious_vec & APIC_SV_ENABLE))
+ return -1;
+
+ apic_sync_vapic(s, SYNC_FROM_VAPIC);
+ intno = apic_irq_pending(s);
+
+ /* if there is an interrupt from the 8259, let the caller handle
+ * that first since ExtINT interrupts ignore the priority.
+ */
+ if (intno == 0 || apic_check_pic(s)) {
+ apic_sync_vapic(s, SYNC_TO_VAPIC);
+ return -1;
+ } else if (intno < 0) {
+ apic_sync_vapic(s, SYNC_TO_VAPIC);
+ return s->spurious_vec & 0xff;
+ }
+ apic_reset_bit(s->irr, intno);
+ apic_set_bit(s->isr, intno);
+ apic_sync_vapic(s, SYNC_TO_VAPIC);
+
+ apic_update_irq(s);
+
+ return intno;
+}
+
+int apic_accept_pic_intr(DeviceState *dev)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ uint32_t lvt0;
+
+ if (!s)
+ return -1;
+
+ lvt0 = s->lvt[APIC_LVT_LINT0];
+
+ if ((s->apicbase & MSR_IA32_APICBASE_ENABLE) == 0 ||
+ (lvt0 & APIC_LVT_MASKED) == 0)
+ return 1;
+
+ return 0;
+}
+
+static uint32_t apic_get_current_count(APICCommonState *s)
+{
+ int64_t d;
+ uint32_t val;
+ d = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->initial_count_load_time) >>
+ s->count_shift;
+ if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_TIMER_PERIODIC) {
+ /* periodic */
+ val = s->initial_count - (d % ((uint64_t)s->initial_count + 1));
+ } else {
+ if (d >= s->initial_count)
+ val = 0;
+ else
+ val = s->initial_count - d;
+ }
+ return val;
+}
+
+static void apic_timer_update(APICCommonState *s, int64_t current_time)
+{
+ if (apic_next_timer(s, current_time)) {
+ timer_mod(s->timer, s->next_time);
+ } else {
+ timer_del(s->timer);
+ }
+}
+
+static void apic_timer(void *opaque)
+{
+ APICCommonState *s = opaque;
+
+ apic_local_deliver(s, APIC_LVT_TIMER);
+ apic_timer_update(s, s->next_time);
+}
+
+static uint32_t apic_mem_readb(void *opaque, hwaddr addr)
+{
+ return 0;
+}
+
+static uint32_t apic_mem_readw(void *opaque, hwaddr addr)
+{
+ return 0;
+}
+
+static void apic_mem_writeb(void *opaque, hwaddr addr, uint32_t val)
+{
+}
+
+static void apic_mem_writew(void *opaque, hwaddr addr, uint32_t val)
+{
+}
+
+static uint32_t apic_mem_readl(void *opaque, hwaddr addr)
+{
+ DeviceState *dev;
+ APICCommonState *s;
+ uint32_t val;
+ int index;
+
+ dev = cpu_get_current_apic();
+ if (!dev) {
+ return 0;
+ }
+ s = APIC_COMMON(dev);
+
+ index = (addr >> 4) & 0xff;
+ switch(index) {
+ case 0x02: /* id */
+ val = s->id << 24;
+ break;
+ case 0x03: /* version */
+ val = s->version | ((APIC_LVT_NB - 1) << 16);
+ break;
+ case 0x08:
+ apic_sync_vapic(s, SYNC_FROM_VAPIC);
+ if (apic_report_tpr_access) {
+ cpu_report_tpr_access(&s->cpu->env, TPR_ACCESS_READ);
+ }
+ val = s->tpr;
+ break;
+ case 0x09:
+ val = apic_get_arb_pri(s);
+ break;
+ case 0x0a:
+ /* ppr */
+ val = apic_get_ppr(s);
+ break;
+ case 0x0b:
+ val = 0;
+ break;
+ case 0x0d:
+ val = s->log_dest << 24;
+ break;
+ case 0x0e:
+ val = (s->dest_mode << 28) | 0xfffffff;
+ break;
+ case 0x0f:
+ val = s->spurious_vec;
+ break;
+ case 0x10 ... 0x17:
+ val = s->isr[index & 7];
+ break;
+ case 0x18 ... 0x1f:
+ val = s->tmr[index & 7];
+ break;
+ case 0x20 ... 0x27:
+ val = s->irr[index & 7];
+ break;
+ case 0x28:
+ val = s->esr;
+ break;
+ case 0x30:
+ case 0x31:
+ val = s->icr[index & 1];
+ break;
+ case 0x32 ... 0x37:
+ val = s->lvt[index - 0x32];
+ break;
+ case 0x38:
+ val = s->initial_count;
+ break;
+ case 0x39:
+ val = apic_get_current_count(s);
+ break;
+ case 0x3e:
+ val = s->divide_conf;
+ break;
+ default:
+ s->esr |= ESR_ILLEGAL_ADDRESS;
+ val = 0;
+ break;
+ }
+ trace_apic_mem_readl(addr, val);
+ return val;
+}
+
+static void apic_send_msi(hwaddr addr, uint32_t data)
+{
+ uint8_t dest = (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT;
+ uint8_t vector = (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT;
+ uint8_t dest_mode = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1;
+ uint8_t trigger_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1;
+ uint8_t delivery = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & 0x7;
+ /* XXX: Ignore redirection hint. */
+ apic_deliver_irq(dest, dest_mode, delivery, vector, trigger_mode);
+}
+
+static void apic_mem_writel(void *opaque, hwaddr addr, uint32_t val)
+{
+ DeviceState *dev;
+ APICCommonState *s;
+ int index = (addr >> 4) & 0xff;
+ if (addr > 0xfff || !index) {
+ /* MSI and MMIO APIC are at the same memory location,
+ * but actually not on the global bus: MSI is on PCI bus
+ * APIC is connected directly to the CPU.
+ * Mapping them on the global bus happens to work because
+ * MSI registers are reserved in APIC MMIO and vice versa. */
+ apic_send_msi(addr, val);
+ return;
+ }
+
+ dev = cpu_get_current_apic();
+ if (!dev) {
+ return;
+ }
+ s = APIC_COMMON(dev);
+
+ trace_apic_mem_writel(addr, val);
+
+ switch(index) {
+ case 0x02:
+ s->id = (val >> 24);
+ break;
+ case 0x03:
+ break;
+ case 0x08:
+ if (apic_report_tpr_access) {
+ cpu_report_tpr_access(&s->cpu->env, TPR_ACCESS_WRITE);
+ }
+ s->tpr = val;
+ apic_sync_vapic(s, SYNC_TO_VAPIC);
+ apic_update_irq(s);
+ break;
+ case 0x09:
+ case 0x0a:
+ break;
+ case 0x0b: /* EOI */
+ apic_eoi(s);
+ break;
+ case 0x0d:
+ s->log_dest = val >> 24;
+ break;
+ case 0x0e:
+ s->dest_mode = val >> 28;
+ break;
+ case 0x0f:
+ s->spurious_vec = val & 0x1ff;
+ apic_update_irq(s);
+ break;
+ case 0x10 ... 0x17:
+ case 0x18 ... 0x1f:
+ case 0x20 ... 0x27:
+ case 0x28:
+ break;
+ case 0x30:
+ s->icr[0] = val;
+ apic_deliver(dev, (s->icr[1] >> 24) & 0xff, (s->icr[0] >> 11) & 1,
+ (s->icr[0] >> 8) & 7, (s->icr[0] & 0xff),
+ (s->icr[0] >> 15) & 1);
+ break;
+ case 0x31:
+ s->icr[1] = val;
+ break;
+ case 0x32 ... 0x37:
+ {
+ int n = index - 0x32;
+ s->lvt[n] = val;
+ if (n == APIC_LVT_TIMER) {
+ apic_timer_update(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ } else if (n == APIC_LVT_LINT0 && apic_check_pic(s)) {
+ apic_update_irq(s);
+ }
+ }
+ break;
+ case 0x38:
+ s->initial_count = val;
+ s->initial_count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ apic_timer_update(s, s->initial_count_load_time);
+ break;
+ case 0x39:
+ break;
+ case 0x3e:
+ {
+ int v;
+ s->divide_conf = val & 0xb;
+ v = (s->divide_conf & 3) | ((s->divide_conf >> 1) & 4);
+ s->count_shift = (v + 1) & 7;
+ }
+ break;
+ default:
+ s->esr |= ESR_ILLEGAL_ADDRESS;
+ break;
+ }
+}
+
+static void apic_pre_save(APICCommonState *s)
+{
+ apic_sync_vapic(s, SYNC_FROM_VAPIC);
+}
+
+static void apic_post_load(APICCommonState *s)
+{
+ if (s->timer_expiry != -1) {
+ timer_mod(s->timer, s->timer_expiry);
+ } else {
+ timer_del(s->timer);
+ }
+}
+
+static const MemoryRegionOps apic_io_ops = {
+ .old_mmio = {
+ .read = { apic_mem_readb, apic_mem_readw, apic_mem_readl, },
+ .write = { apic_mem_writeb, apic_mem_writew, apic_mem_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void apic_realize(DeviceState *dev, Error **errp)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+
+ memory_region_init_io(&s->io_memory, OBJECT(s), &apic_io_ops, s, "apic-msi",
+ APIC_SPACE_SIZE);
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, apic_timer, s);
+ local_apics[s->idx] = s;
+
+ msi_supported = true;
+}
+
+static void apic_class_init(ObjectClass *klass, void *data)
+{
+ APICCommonClass *k = APIC_COMMON_CLASS(klass);
+
+ k->realize = apic_realize;
+ k->set_base = apic_set_base;
+ k->set_tpr = apic_set_tpr;
+ k->get_tpr = apic_get_tpr;
+ k->vapic_base_update = apic_vapic_base_update;
+ k->external_nmi = apic_external_nmi;
+ k->pre_save = apic_pre_save;
+ k->post_load = apic_post_load;
+}
+
+static const TypeInfo apic_info = {
+ .name = "apic",
+ .instance_size = sizeof(APICCommonState),
+ .parent = TYPE_APIC_COMMON,
+ .class_init = apic_class_init,
+};
+
+static void apic_register_types(void)
+{
+ type_register_static(&apic_info);
+}
+
+type_init(apic_register_types)
diff --git a/hw/intc/apic_common.c b/hw/intc/apic_common.c
new file mode 100644
index 00000000..0032b97c
--- /dev/null
+++ b/hw/intc/apic_common.c
@@ -0,0 +1,456 @@
+/*
+ * APIC support - common bits of emulated and KVM kernel model
+ *
+ * Copyright (c) 2004-2005 Fabrice Bellard
+ * Copyright (c) 2011 Jan Kiszka, Siemens AG
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+#include "hw/i386/apic.h"
+#include "hw/i386/apic_internal.h"
+#include "trace.h"
+#include "sysemu/kvm.h"
+#include "hw/qdev.h"
+#include "hw/sysbus.h"
+
+static int apic_irq_delivered;
+bool apic_report_tpr_access;
+
+void cpu_set_apic_base(DeviceState *dev, uint64_t val)
+{
+ trace_cpu_set_apic_base(val);
+
+ if (dev) {
+ APICCommonState *s = APIC_COMMON(dev);
+ APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
+ info->set_base(s, val);
+ }
+}
+
+uint64_t cpu_get_apic_base(DeviceState *dev)
+{
+ if (dev) {
+ APICCommonState *s = APIC_COMMON(dev);
+ trace_cpu_get_apic_base((uint64_t)s->apicbase);
+ return s->apicbase;
+ } else {
+ trace_cpu_get_apic_base(MSR_IA32_APICBASE_BSP);
+ return MSR_IA32_APICBASE_BSP;
+ }
+}
+
+void cpu_set_apic_tpr(DeviceState *dev, uint8_t val)
+{
+ APICCommonState *s;
+ APICCommonClass *info;
+
+ if (!dev) {
+ return;
+ }
+
+ s = APIC_COMMON(dev);
+ info = APIC_COMMON_GET_CLASS(s);
+
+ info->set_tpr(s, val);
+}
+
+uint8_t cpu_get_apic_tpr(DeviceState *dev)
+{
+ APICCommonState *s;
+ APICCommonClass *info;
+
+ if (!dev) {
+ return 0;
+ }
+
+ s = APIC_COMMON(dev);
+ info = APIC_COMMON_GET_CLASS(s);
+
+ return info->get_tpr(s);
+}
+
+void apic_enable_tpr_access_reporting(DeviceState *dev, bool enable)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
+
+ apic_report_tpr_access = enable;
+ if (info->enable_tpr_reporting) {
+ info->enable_tpr_reporting(s, enable);
+ }
+}
+
+void apic_enable_vapic(DeviceState *dev, hwaddr paddr)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
+
+ s->vapic_paddr = paddr;
+ info->vapic_base_update(s);
+}
+
+void apic_handle_tpr_access_report(DeviceState *dev, target_ulong ip,
+ TPRAccess access)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+
+ vapic_report_tpr_access(s->vapic, CPU(s->cpu), ip, access);
+}
+
+void apic_report_irq_delivered(int delivered)
+{
+ apic_irq_delivered += delivered;
+
+ trace_apic_report_irq_delivered(apic_irq_delivered);
+}
+
+void apic_reset_irq_delivered(void)
+{
+ /* Copy this into a local variable to encourage gcc to emit a plain
+ * register for a sys/sdt.h marker. For details on this workaround, see:
+ * https://sourceware.org/bugzilla/show_bug.cgi?id=13296
+ */
+ volatile int a_i_d = apic_irq_delivered;
+ trace_apic_reset_irq_delivered(a_i_d);
+
+ apic_irq_delivered = 0;
+}
+
+int apic_get_irq_delivered(void)
+{
+ trace_apic_get_irq_delivered(apic_irq_delivered);
+
+ return apic_irq_delivered;
+}
+
+void apic_deliver_nmi(DeviceState *dev)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
+
+ info->external_nmi(s);
+}
+
+bool apic_next_timer(APICCommonState *s, int64_t current_time)
+{
+ int64_t d;
+
+ /* We need to store the timer state separately to support APIC
+ * implementations that maintain a non-QEMU timer, e.g. inside the
+ * host kernel. This open-coded state allows us to migrate between
+ * both models. */
+ s->timer_expiry = -1;
+
+ if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_MASKED) {
+ return false;
+ }
+
+ d = (current_time - s->initial_count_load_time) >> s->count_shift;
+
+ if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_TIMER_PERIODIC) {
+ if (!s->initial_count) {
+ return false;
+ }
+ d = ((d / ((uint64_t)s->initial_count + 1)) + 1) *
+ ((uint64_t)s->initial_count + 1);
+ } else {
+ if (d >= s->initial_count) {
+ return false;
+ }
+ d = (uint64_t)s->initial_count + 1;
+ }
+ s->next_time = s->initial_count_load_time + (d << s->count_shift);
+ s->timer_expiry = s->next_time;
+ return true;
+}
+
+void apic_init_reset(DeviceState *dev)
+{
+ APICCommonState *s;
+ APICCommonClass *info;
+ int i;
+
+ if (!dev) {
+ return;
+ }
+ s = APIC_COMMON(dev);
+ s->tpr = 0;
+ s->spurious_vec = 0xff;
+ s->log_dest = 0;
+ s->dest_mode = 0xf;
+ memset(s->isr, 0, sizeof(s->isr));
+ memset(s->tmr, 0, sizeof(s->tmr));
+ memset(s->irr, 0, sizeof(s->irr));
+ for (i = 0; i < APIC_LVT_NB; i++) {
+ s->lvt[i] = APIC_LVT_MASKED;
+ }
+ s->esr = 0;
+ memset(s->icr, 0, sizeof(s->icr));
+ s->divide_conf = 0;
+ s->count_shift = 0;
+ s->initial_count = 0;
+ s->initial_count_load_time = 0;
+ s->next_time = 0;
+ s->wait_for_sipi = !cpu_is_bsp(s->cpu);
+
+ if (s->timer) {
+ timer_del(s->timer);
+ }
+ s->timer_expiry = -1;
+
+ info = APIC_COMMON_GET_CLASS(s);
+ if (info->reset) {
+ info->reset(s);
+ }
+}
+
+void apic_designate_bsp(DeviceState *dev, bool bsp)
+{
+ if (dev == NULL) {
+ return;
+ }
+
+ APICCommonState *s = APIC_COMMON(dev);
+ if (bsp) {
+ s->apicbase |= MSR_IA32_APICBASE_BSP;
+ } else {
+ s->apicbase &= ~MSR_IA32_APICBASE_BSP;
+ }
+}
+
+static void apic_reset_common(DeviceState *dev)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
+ uint32_t bsp;
+
+ bsp = s->apicbase & MSR_IA32_APICBASE_BSP;
+ s->apicbase = APIC_DEFAULT_ADDRESS | bsp | MSR_IA32_APICBASE_ENABLE;
+
+ s->vapic_paddr = 0;
+ info->vapic_base_update(s);
+
+ apic_init_reset(dev);
+}
+
+/* This function is only used for old state version 1 and 2 */
+static int apic_load_old(QEMUFile *f, void *opaque, int version_id)
+{
+ APICCommonState *s = opaque;
+ APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
+ int i;
+
+ if (version_id > 2) {
+ return -EINVAL;
+ }
+
+ /* XXX: what if the base changes? (registered memory regions) */
+ qemu_get_be32s(f, &s->apicbase);
+ qemu_get_8s(f, &s->id);
+ qemu_get_8s(f, &s->arb_id);
+ qemu_get_8s(f, &s->tpr);
+ qemu_get_be32s(f, &s->spurious_vec);
+ qemu_get_8s(f, &s->log_dest);
+ qemu_get_8s(f, &s->dest_mode);
+ for (i = 0; i < 8; i++) {
+ qemu_get_be32s(f, &s->isr[i]);
+ qemu_get_be32s(f, &s->tmr[i]);
+ qemu_get_be32s(f, &s->irr[i]);
+ }
+ for (i = 0; i < APIC_LVT_NB; i++) {
+ qemu_get_be32s(f, &s->lvt[i]);
+ }
+ qemu_get_be32s(f, &s->esr);
+ qemu_get_be32s(f, &s->icr[0]);
+ qemu_get_be32s(f, &s->icr[1]);
+ qemu_get_be32s(f, &s->divide_conf);
+ s->count_shift = qemu_get_be32(f);
+ qemu_get_be32s(f, &s->initial_count);
+ s->initial_count_load_time = qemu_get_be64(f);
+ s->next_time = qemu_get_be64(f);
+
+ if (version_id >= 2) {
+ s->timer_expiry = qemu_get_be64(f);
+ }
+
+ if (info->post_load) {
+ info->post_load(s);
+ }
+ return 0;
+}
+
+static void apic_common_realize(DeviceState *dev, Error **errp)
+{
+ APICCommonState *s = APIC_COMMON(dev);
+ APICCommonClass *info;
+ static DeviceState *vapic;
+ static int apic_no;
+ static bool mmio_registered;
+
+ if (apic_no >= MAX_APICS) {
+ error_setg(errp, "%s initialization failed.",
+ object_get_typename(OBJECT(dev)));
+ return;
+ }
+ s->idx = apic_no++;
+
+ info = APIC_COMMON_GET_CLASS(s);
+ info->realize(dev, errp);
+ if (!mmio_registered) {
+ ICCBus *b = ICC_BUS(qdev_get_parent_bus(dev));
+ memory_region_add_subregion(b->apic_address_space, 0, &s->io_memory);
+ mmio_registered = true;
+ }
+
+ /* Note: We need at least 1M to map the VAPIC option ROM */
+ if (!vapic && s->vapic_control & VAPIC_ENABLE_MASK &&
+ ram_size >= 1024 * 1024) {
+ vapic = sysbus_create_simple("kvmvapic", -1, NULL);
+ }
+ s->vapic = vapic;
+ if (apic_report_tpr_access && info->enable_tpr_reporting) {
+ info->enable_tpr_reporting(s, true);
+ }
+
+}
+
+static int apic_pre_load(void *opaque)
+{
+ APICCommonState *s = APIC_COMMON(opaque);
+
+ /* The default is !cpu_is_bsp(s->cpu), but the common value is 0
+ * so that's what apic_common_sipi_needed checks for. Reset to
+ * the value that is assumed when the apic_sipi subsection is
+ * absent.
+ */
+ s->wait_for_sipi = 0;
+ return 0;
+}
+
+static void apic_dispatch_pre_save(void *opaque)
+{
+ APICCommonState *s = APIC_COMMON(opaque);
+ APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
+
+ if (info->pre_save) {
+ info->pre_save(s);
+ }
+}
+
+static int apic_dispatch_post_load(void *opaque, int version_id)
+{
+ APICCommonState *s = APIC_COMMON(opaque);
+ APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
+
+ if (info->post_load) {
+ info->post_load(s);
+ }
+ return 0;
+}
+
+static bool apic_common_sipi_needed(void *opaque)
+{
+ APICCommonState *s = APIC_COMMON(opaque);
+ return s->wait_for_sipi != 0;
+}
+
+static const VMStateDescription vmstate_apic_common_sipi = {
+ .name = "apic_sipi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = apic_common_sipi_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(sipi_vector, APICCommonState),
+ VMSTATE_INT32(wait_for_sipi, APICCommonState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_apic_common = {
+ .name = "apic",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .minimum_version_id_old = 1,
+ .load_state_old = apic_load_old,
+ .pre_load = apic_pre_load,
+ .pre_save = apic_dispatch_pre_save,
+ .post_load = apic_dispatch_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(apicbase, APICCommonState),
+ VMSTATE_UINT8(id, APICCommonState),
+ VMSTATE_UINT8(arb_id, APICCommonState),
+ VMSTATE_UINT8(tpr, APICCommonState),
+ VMSTATE_UINT32(spurious_vec, APICCommonState),
+ VMSTATE_UINT8(log_dest, APICCommonState),
+ VMSTATE_UINT8(dest_mode, APICCommonState),
+ VMSTATE_UINT32_ARRAY(isr, APICCommonState, 8),
+ VMSTATE_UINT32_ARRAY(tmr, APICCommonState, 8),
+ VMSTATE_UINT32_ARRAY(irr, APICCommonState, 8),
+ VMSTATE_UINT32_ARRAY(lvt, APICCommonState, APIC_LVT_NB),
+ VMSTATE_UINT32(esr, APICCommonState),
+ VMSTATE_UINT32_ARRAY(icr, APICCommonState, 2),
+ VMSTATE_UINT32(divide_conf, APICCommonState),
+ VMSTATE_INT32(count_shift, APICCommonState),
+ VMSTATE_UINT32(initial_count, APICCommonState),
+ VMSTATE_INT64(initial_count_load_time, APICCommonState),
+ VMSTATE_INT64(next_time, APICCommonState),
+ VMSTATE_INT64(timer_expiry,
+ APICCommonState), /* open-coded timer state */
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_apic_common_sipi,
+ NULL
+ }
+};
+
+static Property apic_properties_common[] = {
+ DEFINE_PROP_UINT8("id", APICCommonState, id, -1),
+ DEFINE_PROP_UINT8("version", APICCommonState, version, 0x14),
+ DEFINE_PROP_BIT("vapic", APICCommonState, vapic_control, VAPIC_ENABLE_BIT,
+ true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void apic_common_class_init(ObjectClass *klass, void *data)
+{
+ ICCDeviceClass *idc = ICC_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_apic_common;
+ dc->reset = apic_reset_common;
+ dc->props = apic_properties_common;
+ idc->realize = apic_common_realize;
+ /*
+ * Reason: APIC and CPU need to be wired up by
+ * x86_cpu_apic_create()
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo apic_common_type = {
+ .name = TYPE_APIC_COMMON,
+ .parent = TYPE_ICC_DEVICE,
+ .instance_size = sizeof(APICCommonState),
+ .class_size = sizeof(APICCommonClass),
+ .class_init = apic_common_class_init,
+ .abstract = true,
+};
+
+static void apic_common_register_types(void)
+{
+ type_register_static(&apic_common_type);
+}
+
+type_init(apic_common_register_types)
diff --git a/hw/intc/arm_gic.c b/hw/intc/arm_gic.c
new file mode 100644
index 00000000..454bfd7d
--- /dev/null
+++ b/hw/intc/arm_gic.c
@@ -0,0 +1,1160 @@
+/*
+ * ARM Generic/Distributed Interrupt Controller
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+/* This file contains implementation code for the RealView EB interrupt
+ * controller, MPCore distributed interrupt controller and ARMv7-M
+ * Nested Vectored Interrupt Controller.
+ * It is compiled in two ways:
+ * (1) as a standalone file to produce a sysbus device which is a GIC
+ * that can be used on the realview board and as one of the builtin
+ * private peripherals for the ARM MP CPUs (11MPCore, A9, etc)
+ * (2) by being directly #included into armv7m_nvic.c to produce the
+ * armv7m_nvic device.
+ */
+
+#include "hw/sysbus.h"
+#include "gic_internal.h"
+#include "qom/cpu.h"
+
+//#define DEBUG_GIC
+
+#ifdef DEBUG_GIC
+#define DPRINTF(fmt, ...) \
+do { fprintf(stderr, "arm_gic: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+static const uint8_t gic_id[] = {
+ 0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1
+};
+
+#define NUM_CPU(s) ((s)->num_cpu)
+
+static inline int gic_get_current_cpu(GICState *s)
+{
+ if (s->num_cpu > 1) {
+ return current_cpu->cpu_index;
+ }
+ return 0;
+}
+
+/* Return true if this GIC config has interrupt groups, which is
+ * true if we're a GICv2, or a GICv1 with the security extensions.
+ */
+static inline bool gic_has_groups(GICState *s)
+{
+ return s->revision == 2 || s->security_extn;
+}
+
+/* TODO: Many places that call this routine could be optimized. */
+/* Update interrupt status after enabled or pending bits have been changed. */
+void gic_update(GICState *s)
+{
+ int best_irq;
+ int best_prio;
+ int irq;
+ int irq_level, fiq_level;
+ int cpu;
+ int cm;
+
+ for (cpu = 0; cpu < NUM_CPU(s); cpu++) {
+ cm = 1 << cpu;
+ s->current_pending[cpu] = 1023;
+ if (!(s->ctlr & (GICD_CTLR_EN_GRP0 | GICD_CTLR_EN_GRP1))
+ || !(s->cpu_ctlr[cpu] & (GICC_CTLR_EN_GRP0 | GICC_CTLR_EN_GRP1))) {
+ qemu_irq_lower(s->parent_irq[cpu]);
+ qemu_irq_lower(s->parent_fiq[cpu]);
+ continue;
+ }
+ best_prio = 0x100;
+ best_irq = 1023;
+ for (irq = 0; irq < s->num_irq; irq++) {
+ if (GIC_TEST_ENABLED(irq, cm) && gic_test_pending(s, irq, cm) &&
+ (irq < GIC_INTERNAL || GIC_TARGET(irq) & cm)) {
+ if (GIC_GET_PRIORITY(irq, cpu) < best_prio) {
+ best_prio = GIC_GET_PRIORITY(irq, cpu);
+ best_irq = irq;
+ }
+ }
+ }
+
+ irq_level = fiq_level = 0;
+
+ if (best_prio < s->priority_mask[cpu]) {
+ s->current_pending[cpu] = best_irq;
+ if (best_prio < s->running_priority[cpu]) {
+ int group = GIC_TEST_GROUP(best_irq, cm);
+
+ if (extract32(s->ctlr, group, 1) &&
+ extract32(s->cpu_ctlr[cpu], group, 1)) {
+ if (group == 0 && s->cpu_ctlr[cpu] & GICC_CTLR_FIQ_EN) {
+ DPRINTF("Raised pending FIQ %d (cpu %d)\n",
+ best_irq, cpu);
+ fiq_level = 1;
+ } else {
+ DPRINTF("Raised pending IRQ %d (cpu %d)\n",
+ best_irq, cpu);
+ irq_level = 1;
+ }
+ }
+ }
+ }
+
+ qemu_set_irq(s->parent_irq[cpu], irq_level);
+ qemu_set_irq(s->parent_fiq[cpu], fiq_level);
+ }
+}
+
+void gic_set_pending_private(GICState *s, int cpu, int irq)
+{
+ int cm = 1 << cpu;
+
+ if (gic_test_pending(s, irq, cm)) {
+ return;
+ }
+
+ DPRINTF("Set %d pending cpu %d\n", irq, cpu);
+ GIC_SET_PENDING(irq, cm);
+ gic_update(s);
+}
+
+static void gic_set_irq_11mpcore(GICState *s, int irq, int level,
+ int cm, int target)
+{
+ if (level) {
+ GIC_SET_LEVEL(irq, cm);
+ if (GIC_TEST_EDGE_TRIGGER(irq) || GIC_TEST_ENABLED(irq, cm)) {
+ DPRINTF("Set %d pending mask %x\n", irq, target);
+ GIC_SET_PENDING(irq, target);
+ }
+ } else {
+ GIC_CLEAR_LEVEL(irq, cm);
+ }
+}
+
+static void gic_set_irq_generic(GICState *s, int irq, int level,
+ int cm, int target)
+{
+ if (level) {
+ GIC_SET_LEVEL(irq, cm);
+ DPRINTF("Set %d pending mask %x\n", irq, target);
+ if (GIC_TEST_EDGE_TRIGGER(irq)) {
+ GIC_SET_PENDING(irq, target);
+ }
+ } else {
+ GIC_CLEAR_LEVEL(irq, cm);
+ }
+}
+
+/* Process a change in an external IRQ input. */
+static void gic_set_irq(void *opaque, int irq, int level)
+{
+ /* Meaning of the 'irq' parameter:
+ * [0..N-1] : external interrupts
+ * [N..N+31] : PPI (internal) interrupts for CPU 0
+ * [N+32..N+63] : PPI (internal interrupts for CPU 1
+ * ...
+ */
+ GICState *s = (GICState *)opaque;
+ int cm, target;
+ if (irq < (s->num_irq - GIC_INTERNAL)) {
+ /* The first external input line is internal interrupt 32. */
+ cm = ALL_CPU_MASK;
+ irq += GIC_INTERNAL;
+ target = GIC_TARGET(irq);
+ } else {
+ int cpu;
+ irq -= (s->num_irq - GIC_INTERNAL);
+ cpu = irq / GIC_INTERNAL;
+ irq %= GIC_INTERNAL;
+ cm = 1 << cpu;
+ target = cm;
+ }
+
+ assert(irq >= GIC_NR_SGIS);
+
+ if (level == GIC_TEST_LEVEL(irq, cm)) {
+ return;
+ }
+
+ if (s->revision == REV_11MPCORE || s->revision == REV_NVIC) {
+ gic_set_irq_11mpcore(s, irq, level, cm, target);
+ } else {
+ gic_set_irq_generic(s, irq, level, cm, target);
+ }
+
+ gic_update(s);
+}
+
+static uint16_t gic_get_current_pending_irq(GICState *s, int cpu,
+ MemTxAttrs attrs)
+{
+ uint16_t pending_irq = s->current_pending[cpu];
+
+ if (pending_irq < GIC_MAXIRQ && gic_has_groups(s)) {
+ int group = GIC_TEST_GROUP(pending_irq, (1 << cpu));
+ /* On a GIC without the security extensions, reading this register
+ * behaves in the same way as a secure access to a GIC with them.
+ */
+ bool secure = !s->security_extn || attrs.secure;
+
+ if (group == 0 && !secure) {
+ /* Group0 interrupts hidden from Non-secure access */
+ return 1023;
+ }
+ if (group == 1 && secure && !(s->cpu_ctlr[cpu] & GICC_CTLR_ACK_CTL)) {
+ /* Group1 interrupts only seen by Secure access if
+ * AckCtl bit set.
+ */
+ return 1022;
+ }
+ }
+ return pending_irq;
+}
+
+static void gic_set_running_irq(GICState *s, int cpu, int irq)
+{
+ s->running_irq[cpu] = irq;
+ if (irq == 1023) {
+ s->running_priority[cpu] = 0x100;
+ } else {
+ s->running_priority[cpu] = GIC_GET_PRIORITY(irq, cpu);
+ }
+ gic_update(s);
+}
+
+uint32_t gic_acknowledge_irq(GICState *s, int cpu, MemTxAttrs attrs)
+{
+ int ret, irq, src;
+ int cm = 1 << cpu;
+
+ /* gic_get_current_pending_irq() will return 1022 or 1023 appropriately
+ * for the case where this GIC supports grouping and the pending interrupt
+ * is in the wrong group.
+ */
+ irq = gic_get_current_pending_irq(s, cpu, attrs);;
+
+ if (irq >= GIC_MAXIRQ) {
+ DPRINTF("ACK, no pending interrupt or it is hidden: %d\n", irq);
+ return irq;
+ }
+
+ if (GIC_GET_PRIORITY(irq, cpu) >= s->running_priority[cpu]) {
+ DPRINTF("ACK, pending interrupt (%d) has insufficient priority\n", irq);
+ return 1023;
+ }
+ s->last_active[irq][cpu] = s->running_irq[cpu];
+
+ if (s->revision == REV_11MPCORE || s->revision == REV_NVIC) {
+ /* Clear pending flags for both level and edge triggered interrupts.
+ * Level triggered IRQs will be reasserted once they become inactive.
+ */
+ GIC_CLEAR_PENDING(irq, GIC_TEST_MODEL(irq) ? ALL_CPU_MASK : cm);
+ ret = irq;
+ } else {
+ if (irq < GIC_NR_SGIS) {
+ /* Lookup the source CPU for the SGI and clear this in the
+ * sgi_pending map. Return the src and clear the overall pending
+ * state on this CPU if the SGI is not pending from any CPUs.
+ */
+ assert(s->sgi_pending[irq][cpu] != 0);
+ src = ctz32(s->sgi_pending[irq][cpu]);
+ s->sgi_pending[irq][cpu] &= ~(1 << src);
+ if (s->sgi_pending[irq][cpu] == 0) {
+ GIC_CLEAR_PENDING(irq, GIC_TEST_MODEL(irq) ? ALL_CPU_MASK : cm);
+ }
+ ret = irq | ((src & 0x7) << 10);
+ } else {
+ /* Clear pending state for both level and edge triggered
+ * interrupts. (level triggered interrupts with an active line
+ * remain pending, see gic_test_pending)
+ */
+ GIC_CLEAR_PENDING(irq, GIC_TEST_MODEL(irq) ? ALL_CPU_MASK : cm);
+ ret = irq;
+ }
+ }
+
+ gic_set_running_irq(s, cpu, irq);
+ DPRINTF("ACK %d\n", irq);
+ return ret;
+}
+
+void gic_set_priority(GICState *s, int cpu, int irq, uint8_t val,
+ MemTxAttrs attrs)
+{
+ if (s->security_extn && !attrs.secure) {
+ if (!GIC_TEST_GROUP(irq, (1 << cpu))) {
+ return; /* Ignore Non-secure access of Group0 IRQ */
+ }
+ val = 0x80 | (val >> 1); /* Non-secure view */
+ }
+
+ if (irq < GIC_INTERNAL) {
+ s->priority1[irq][cpu] = val;
+ } else {
+ s->priority2[(irq) - GIC_INTERNAL] = val;
+ }
+}
+
+static uint32_t gic_get_priority(GICState *s, int cpu, int irq,
+ MemTxAttrs attrs)
+{
+ uint32_t prio = GIC_GET_PRIORITY(irq, cpu);
+
+ if (s->security_extn && !attrs.secure) {
+ if (!GIC_TEST_GROUP(irq, (1 << cpu))) {
+ return 0; /* Non-secure access cannot read priority of Group0 IRQ */
+ }
+ prio = (prio << 1) & 0xff; /* Non-secure view */
+ }
+ return prio;
+}
+
+static void gic_set_priority_mask(GICState *s, int cpu, uint8_t pmask,
+ MemTxAttrs attrs)
+{
+ if (s->security_extn && !attrs.secure) {
+ if (s->priority_mask[cpu] & 0x80) {
+ /* Priority Mask in upper half */
+ pmask = 0x80 | (pmask >> 1);
+ } else {
+ /* Non-secure write ignored if priority mask is in lower half */
+ return;
+ }
+ }
+ s->priority_mask[cpu] = pmask;
+}
+
+static uint32_t gic_get_priority_mask(GICState *s, int cpu, MemTxAttrs attrs)
+{
+ uint32_t pmask = s->priority_mask[cpu];
+
+ if (s->security_extn && !attrs.secure) {
+ if (pmask & 0x80) {
+ /* Priority Mask in upper half, return Non-secure view */
+ pmask = (pmask << 1) & 0xff;
+ } else {
+ /* Priority Mask in lower half, RAZ */
+ pmask = 0;
+ }
+ }
+ return pmask;
+}
+
+static uint32_t gic_get_cpu_control(GICState *s, int cpu, MemTxAttrs attrs)
+{
+ uint32_t ret = s->cpu_ctlr[cpu];
+
+ if (s->security_extn && !attrs.secure) {
+ /* Construct the NS banked view of GICC_CTLR from the correct
+ * bits of the S banked view. We don't need to move the bypass
+ * control bits because we don't implement that (IMPDEF) part
+ * of the GIC architecture.
+ */
+ ret = (ret & (GICC_CTLR_EN_GRP1 | GICC_CTLR_EOIMODE_NS)) >> 1;
+ }
+ return ret;
+}
+
+static void gic_set_cpu_control(GICState *s, int cpu, uint32_t value,
+ MemTxAttrs attrs)
+{
+ uint32_t mask;
+
+ if (s->security_extn && !attrs.secure) {
+ /* The NS view can only write certain bits in the register;
+ * the rest are unchanged
+ */
+ mask = GICC_CTLR_EN_GRP1;
+ if (s->revision == 2) {
+ mask |= GICC_CTLR_EOIMODE_NS;
+ }
+ s->cpu_ctlr[cpu] &= ~mask;
+ s->cpu_ctlr[cpu] |= (value << 1) & mask;
+ } else {
+ if (s->revision == 2) {
+ mask = s->security_extn ? GICC_CTLR_V2_S_MASK : GICC_CTLR_V2_MASK;
+ } else {
+ mask = s->security_extn ? GICC_CTLR_V1_S_MASK : GICC_CTLR_V1_MASK;
+ }
+ s->cpu_ctlr[cpu] = value & mask;
+ }
+ DPRINTF("CPU Interface %d: Group0 Interrupts %sabled, "
+ "Group1 Interrupts %sabled\n", cpu,
+ (s->cpu_ctlr[cpu] & GICC_CTLR_EN_GRP0) ? "En" : "Dis",
+ (s->cpu_ctlr[cpu] & GICC_CTLR_EN_GRP1) ? "En" : "Dis");
+}
+
+static uint8_t gic_get_running_priority(GICState *s, int cpu, MemTxAttrs attrs)
+{
+ if (s->security_extn && !attrs.secure) {
+ if (s->running_priority[cpu] & 0x80) {
+ /* Running priority in upper half of range: return the Non-secure
+ * view of the priority.
+ */
+ return s->running_priority[cpu] << 1;
+ } else {
+ /* Running priority in lower half of range: RAZ */
+ return 0;
+ }
+ } else {
+ return s->running_priority[cpu];
+ }
+}
+
+void gic_complete_irq(GICState *s, int cpu, int irq, MemTxAttrs attrs)
+{
+ int update = 0;
+ int cm = 1 << cpu;
+ DPRINTF("EOI %d\n", irq);
+ if (irq >= s->num_irq) {
+ /* This handles two cases:
+ * 1. If software writes the ID of a spurious interrupt [ie 1023]
+ * to the GICC_EOIR, the GIC ignores that write.
+ * 2. If software writes the number of a non-existent interrupt
+ * this must be a subcase of "value written does not match the last
+ * valid interrupt value read from the Interrupt Acknowledge
+ * register" and so this is UNPREDICTABLE. We choose to ignore it.
+ */
+ return;
+ }
+ if (s->running_irq[cpu] == 1023)
+ return; /* No active IRQ. */
+
+ if (s->revision == REV_11MPCORE || s->revision == REV_NVIC) {
+ /* Mark level triggered interrupts as pending if they are still
+ raised. */
+ if (!GIC_TEST_EDGE_TRIGGER(irq) && GIC_TEST_ENABLED(irq, cm)
+ && GIC_TEST_LEVEL(irq, cm) && (GIC_TARGET(irq) & cm) != 0) {
+ DPRINTF("Set %d pending mask %x\n", irq, cm);
+ GIC_SET_PENDING(irq, cm);
+ update = 1;
+ }
+ }
+
+ if (s->security_extn && !attrs.secure && !GIC_TEST_GROUP(irq, cm)) {
+ DPRINTF("Non-secure EOI for Group0 interrupt %d ignored\n", irq);
+ return;
+ }
+
+ /* Secure EOI with GICC_CTLR.AckCtl == 0 when the IRQ is a Group 1
+ * interrupt is UNPREDICTABLE. We choose to handle it as if AckCtl == 1,
+ * i.e. go ahead and complete the irq anyway.
+ */
+
+ if (irq != s->running_irq[cpu]) {
+ /* Complete an IRQ that is not currently running. */
+ int tmp = s->running_irq[cpu];
+ while (s->last_active[tmp][cpu] != 1023) {
+ if (s->last_active[tmp][cpu] == irq) {
+ s->last_active[tmp][cpu] = s->last_active[irq][cpu];
+ break;
+ }
+ tmp = s->last_active[tmp][cpu];
+ }
+ if (update) {
+ gic_update(s);
+ }
+ } else {
+ /* Complete the current running IRQ. */
+ gic_set_running_irq(s, cpu, s->last_active[s->running_irq[cpu]][cpu]);
+ }
+}
+
+static uint32_t gic_dist_readb(void *opaque, hwaddr offset, MemTxAttrs attrs)
+{
+ GICState *s = (GICState *)opaque;
+ uint32_t res;
+ int irq;
+ int i;
+ int cpu;
+ int cm;
+ int mask;
+
+ cpu = gic_get_current_cpu(s);
+ cm = 1 << cpu;
+ if (offset < 0x100) {
+ if (offset == 0) { /* GICD_CTLR */
+ if (s->security_extn && !attrs.secure) {
+ /* The NS bank of this register is just an alias of the
+ * EnableGrp1 bit in the S bank version.
+ */
+ return extract32(s->ctlr, 1, 1);
+ } else {
+ return s->ctlr;
+ }
+ }
+ if (offset == 4)
+ /* Interrupt Controller Type Register */
+ return ((s->num_irq / 32) - 1)
+ | ((NUM_CPU(s) - 1) << 5)
+ | (s->security_extn << 10);
+ if (offset < 0x08)
+ return 0;
+ if (offset >= 0x80) {
+ /* Interrupt Group Registers: these RAZ/WI if this is an NS
+ * access to a GIC with the security extensions, or if the GIC
+ * doesn't have groups at all.
+ */
+ res = 0;
+ if (!(s->security_extn && !attrs.secure) && gic_has_groups(s)) {
+ /* Every byte offset holds 8 group status bits */
+ irq = (offset - 0x080) * 8 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq) {
+ goto bad_reg;
+ }
+ for (i = 0; i < 8; i++) {
+ if (GIC_TEST_GROUP(irq + i, cm)) {
+ res |= (1 << i);
+ }
+ }
+ }
+ return res;
+ }
+ goto bad_reg;
+ } else if (offset < 0x200) {
+ /* Interrupt Set/Clear Enable. */
+ if (offset < 0x180)
+ irq = (offset - 0x100) * 8;
+ else
+ irq = (offset - 0x180) * 8;
+ irq += GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ res = 0;
+ for (i = 0; i < 8; i++) {
+ if (GIC_TEST_ENABLED(irq + i, cm)) {
+ res |= (1 << i);
+ }
+ }
+ } else if (offset < 0x300) {
+ /* Interrupt Set/Clear Pending. */
+ if (offset < 0x280)
+ irq = (offset - 0x200) * 8;
+ else
+ irq = (offset - 0x280) * 8;
+ irq += GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ res = 0;
+ mask = (irq < GIC_INTERNAL) ? cm : ALL_CPU_MASK;
+ for (i = 0; i < 8; i++) {
+ if (gic_test_pending(s, irq + i, mask)) {
+ res |= (1 << i);
+ }
+ }
+ } else if (offset < 0x400) {
+ /* Interrupt Active. */
+ irq = (offset - 0x300) * 8 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ res = 0;
+ mask = (irq < GIC_INTERNAL) ? cm : ALL_CPU_MASK;
+ for (i = 0; i < 8; i++) {
+ if (GIC_TEST_ACTIVE(irq + i, mask)) {
+ res |= (1 << i);
+ }
+ }
+ } else if (offset < 0x800) {
+ /* Interrupt Priority. */
+ irq = (offset - 0x400) + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ res = gic_get_priority(s, cpu, irq, attrs);
+ } else if (offset < 0xc00) {
+ /* Interrupt CPU Target. */
+ if (s->num_cpu == 1 && s->revision != REV_11MPCORE) {
+ /* For uniprocessor GICs these RAZ/WI */
+ res = 0;
+ } else {
+ irq = (offset - 0x800) + GIC_BASE_IRQ;
+ if (irq >= s->num_irq) {
+ goto bad_reg;
+ }
+ if (irq >= 29 && irq <= 31) {
+ res = cm;
+ } else {
+ res = GIC_TARGET(irq);
+ }
+ }
+ } else if (offset < 0xf00) {
+ /* Interrupt Configuration. */
+ irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ res = 0;
+ for (i = 0; i < 4; i++) {
+ if (GIC_TEST_MODEL(irq + i))
+ res |= (1 << (i * 2));
+ if (GIC_TEST_EDGE_TRIGGER(irq + i))
+ res |= (2 << (i * 2));
+ }
+ } else if (offset < 0xf10) {
+ goto bad_reg;
+ } else if (offset < 0xf30) {
+ if (s->revision == REV_11MPCORE || s->revision == REV_NVIC) {
+ goto bad_reg;
+ }
+
+ if (offset < 0xf20) {
+ /* GICD_CPENDSGIRn */
+ irq = (offset - 0xf10);
+ } else {
+ irq = (offset - 0xf20);
+ /* GICD_SPENDSGIRn */
+ }
+
+ res = s->sgi_pending[irq][cpu];
+ } else if (offset < 0xfe0) {
+ goto bad_reg;
+ } else /* offset >= 0xfe0 */ {
+ if (offset & 3) {
+ res = 0;
+ } else {
+ res = gic_id[(offset - 0xfe0) >> 2];
+ }
+ }
+ return res;
+bad_reg:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "gic_dist_readb: Bad offset %x\n", (int)offset);
+ return 0;
+}
+
+static MemTxResult gic_dist_read(void *opaque, hwaddr offset, uint64_t *data,
+ unsigned size, MemTxAttrs attrs)
+{
+ switch (size) {
+ case 1:
+ *data = gic_dist_readb(opaque, offset, attrs);
+ return MEMTX_OK;
+ case 2:
+ *data = gic_dist_readb(opaque, offset, attrs);
+ *data |= gic_dist_readb(opaque, offset + 1, attrs) << 8;
+ return MEMTX_OK;
+ case 4:
+ *data = gic_dist_readb(opaque, offset, attrs);
+ *data |= gic_dist_readb(opaque, offset + 1, attrs) << 8;
+ *data |= gic_dist_readb(opaque, offset + 2, attrs) << 16;
+ *data |= gic_dist_readb(opaque, offset + 3, attrs) << 24;
+ return MEMTX_OK;
+ default:
+ return MEMTX_ERROR;
+ }
+}
+
+static void gic_dist_writeb(void *opaque, hwaddr offset,
+ uint32_t value, MemTxAttrs attrs)
+{
+ GICState *s = (GICState *)opaque;
+ int irq;
+ int i;
+ int cpu;
+
+ cpu = gic_get_current_cpu(s);
+ if (offset < 0x100) {
+ if (offset == 0) {
+ if (s->security_extn && !attrs.secure) {
+ /* NS version is just an alias of the S version's bit 1 */
+ s->ctlr = deposit32(s->ctlr, 1, 1, value);
+ } else if (gic_has_groups(s)) {
+ s->ctlr = value & (GICD_CTLR_EN_GRP0 | GICD_CTLR_EN_GRP1);
+ } else {
+ s->ctlr = value & GICD_CTLR_EN_GRP0;
+ }
+ DPRINTF("Distributor: Group0 %sabled; Group 1 %sabled\n",
+ s->ctlr & GICD_CTLR_EN_GRP0 ? "En" : "Dis",
+ s->ctlr & GICD_CTLR_EN_GRP1 ? "En" : "Dis");
+ } else if (offset < 4) {
+ /* ignored. */
+ } else if (offset >= 0x80) {
+ /* Interrupt Group Registers: RAZ/WI for NS access to secure
+ * GIC, or for GICs without groups.
+ */
+ if (!(s->security_extn && !attrs.secure) && gic_has_groups(s)) {
+ /* Every byte offset holds 8 group status bits */
+ irq = (offset - 0x80) * 8 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq) {
+ goto bad_reg;
+ }
+ for (i = 0; i < 8; i++) {
+ /* Group bits are banked for private interrupts */
+ int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
+ if (value & (1 << i)) {
+ /* Group1 (Non-secure) */
+ GIC_SET_GROUP(irq + i, cm);
+ } else {
+ /* Group0 (Secure) */
+ GIC_CLEAR_GROUP(irq + i, cm);
+ }
+ }
+ }
+ } else {
+ goto bad_reg;
+ }
+ } else if (offset < 0x180) {
+ /* Interrupt Set Enable. */
+ irq = (offset - 0x100) * 8 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ if (irq < GIC_NR_SGIS) {
+ value = 0xff;
+ }
+
+ for (i = 0; i < 8; i++) {
+ if (value & (1 << i)) {
+ int mask =
+ (irq < GIC_INTERNAL) ? (1 << cpu) : GIC_TARGET(irq + i);
+ int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
+
+ if (!GIC_TEST_ENABLED(irq + i, cm)) {
+ DPRINTF("Enabled IRQ %d\n", irq + i);
+ }
+ GIC_SET_ENABLED(irq + i, cm);
+ /* If a raised level triggered IRQ enabled then mark
+ is as pending. */
+ if (GIC_TEST_LEVEL(irq + i, mask)
+ && !GIC_TEST_EDGE_TRIGGER(irq + i)) {
+ DPRINTF("Set %d pending mask %x\n", irq + i, mask);
+ GIC_SET_PENDING(irq + i, mask);
+ }
+ }
+ }
+ } else if (offset < 0x200) {
+ /* Interrupt Clear Enable. */
+ irq = (offset - 0x180) * 8 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ if (irq < GIC_NR_SGIS) {
+ value = 0;
+ }
+
+ for (i = 0; i < 8; i++) {
+ if (value & (1 << i)) {
+ int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
+
+ if (GIC_TEST_ENABLED(irq + i, cm)) {
+ DPRINTF("Disabled IRQ %d\n", irq + i);
+ }
+ GIC_CLEAR_ENABLED(irq + i, cm);
+ }
+ }
+ } else if (offset < 0x280) {
+ /* Interrupt Set Pending. */
+ irq = (offset - 0x200) * 8 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ if (irq < GIC_NR_SGIS) {
+ value = 0;
+ }
+
+ for (i = 0; i < 8; i++) {
+ if (value & (1 << i)) {
+ GIC_SET_PENDING(irq + i, GIC_TARGET(irq + i));
+ }
+ }
+ } else if (offset < 0x300) {
+ /* Interrupt Clear Pending. */
+ irq = (offset - 0x280) * 8 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ if (irq < GIC_NR_SGIS) {
+ value = 0;
+ }
+
+ for (i = 0; i < 8; i++) {
+ /* ??? This currently clears the pending bit for all CPUs, even
+ for per-CPU interrupts. It's unclear whether this is the
+ corect behavior. */
+ if (value & (1 << i)) {
+ GIC_CLEAR_PENDING(irq + i, ALL_CPU_MASK);
+ }
+ }
+ } else if (offset < 0x400) {
+ /* Interrupt Active. */
+ goto bad_reg;
+ } else if (offset < 0x800) {
+ /* Interrupt Priority. */
+ irq = (offset - 0x400) + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ gic_set_priority(s, cpu, irq, value, attrs);
+ } else if (offset < 0xc00) {
+ /* Interrupt CPU Target. RAZ/WI on uniprocessor GICs, with the
+ * annoying exception of the 11MPCore's GIC.
+ */
+ if (s->num_cpu != 1 || s->revision == REV_11MPCORE) {
+ irq = (offset - 0x800) + GIC_BASE_IRQ;
+ if (irq >= s->num_irq) {
+ goto bad_reg;
+ }
+ if (irq < 29) {
+ value = 0;
+ } else if (irq < GIC_INTERNAL) {
+ value = ALL_CPU_MASK;
+ }
+ s->irq_target[irq] = value & ALL_CPU_MASK;
+ }
+ } else if (offset < 0xf00) {
+ /* Interrupt Configuration. */
+ irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ;
+ if (irq >= s->num_irq)
+ goto bad_reg;
+ if (irq < GIC_NR_SGIS)
+ value |= 0xaa;
+ for (i = 0; i < 4; i++) {
+ if (s->revision == REV_11MPCORE || s->revision == REV_NVIC) {
+ if (value & (1 << (i * 2))) {
+ GIC_SET_MODEL(irq + i);
+ } else {
+ GIC_CLEAR_MODEL(irq + i);
+ }
+ }
+ if (value & (2 << (i * 2))) {
+ GIC_SET_EDGE_TRIGGER(irq + i);
+ } else {
+ GIC_CLEAR_EDGE_TRIGGER(irq + i);
+ }
+ }
+ } else if (offset < 0xf10) {
+ /* 0xf00 is only handled for 32-bit writes. */
+ goto bad_reg;
+ } else if (offset < 0xf20) {
+ /* GICD_CPENDSGIRn */
+ if (s->revision == REV_11MPCORE || s->revision == REV_NVIC) {
+ goto bad_reg;
+ }
+ irq = (offset - 0xf10);
+
+ s->sgi_pending[irq][cpu] &= ~value;
+ if (s->sgi_pending[irq][cpu] == 0) {
+ GIC_CLEAR_PENDING(irq, 1 << cpu);
+ }
+ } else if (offset < 0xf30) {
+ /* GICD_SPENDSGIRn */
+ if (s->revision == REV_11MPCORE || s->revision == REV_NVIC) {
+ goto bad_reg;
+ }
+ irq = (offset - 0xf20);
+
+ GIC_SET_PENDING(irq, 1 << cpu);
+ s->sgi_pending[irq][cpu] |= value;
+ } else {
+ goto bad_reg;
+ }
+ gic_update(s);
+ return;
+bad_reg:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "gic_dist_writeb: Bad offset %x\n", (int)offset);
+}
+
+static void gic_dist_writew(void *opaque, hwaddr offset,
+ uint32_t value, MemTxAttrs attrs)
+{
+ gic_dist_writeb(opaque, offset, value & 0xff, attrs);
+ gic_dist_writeb(opaque, offset + 1, value >> 8, attrs);
+}
+
+static void gic_dist_writel(void *opaque, hwaddr offset,
+ uint32_t value, MemTxAttrs attrs)
+{
+ GICState *s = (GICState *)opaque;
+ if (offset == 0xf00) {
+ int cpu;
+ int irq;
+ int mask;
+ int target_cpu;
+
+ cpu = gic_get_current_cpu(s);
+ irq = value & 0x3ff;
+ switch ((value >> 24) & 3) {
+ case 0:
+ mask = (value >> 16) & ALL_CPU_MASK;
+ break;
+ case 1:
+ mask = ALL_CPU_MASK ^ (1 << cpu);
+ break;
+ case 2:
+ mask = 1 << cpu;
+ break;
+ default:
+ DPRINTF("Bad Soft Int target filter\n");
+ mask = ALL_CPU_MASK;
+ break;
+ }
+ GIC_SET_PENDING(irq, mask);
+ target_cpu = ctz32(mask);
+ while (target_cpu < GIC_NCPU) {
+ s->sgi_pending[irq][target_cpu] |= (1 << cpu);
+ mask &= ~(1 << target_cpu);
+ target_cpu = ctz32(mask);
+ }
+ gic_update(s);
+ return;
+ }
+ gic_dist_writew(opaque, offset, value & 0xffff, attrs);
+ gic_dist_writew(opaque, offset + 2, value >> 16, attrs);
+}
+
+static MemTxResult gic_dist_write(void *opaque, hwaddr offset, uint64_t data,
+ unsigned size, MemTxAttrs attrs)
+{
+ switch (size) {
+ case 1:
+ gic_dist_writeb(opaque, offset, data, attrs);
+ return MEMTX_OK;
+ case 2:
+ gic_dist_writew(opaque, offset, data, attrs);
+ return MEMTX_OK;
+ case 4:
+ gic_dist_writel(opaque, offset, data, attrs);
+ return MEMTX_OK;
+ default:
+ return MEMTX_ERROR;
+ }
+}
+
+static const MemoryRegionOps gic_dist_ops = {
+ .read_with_attrs = gic_dist_read,
+ .write_with_attrs = gic_dist_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static MemTxResult gic_cpu_read(GICState *s, int cpu, int offset,
+ uint64_t *data, MemTxAttrs attrs)
+{
+ switch (offset) {
+ case 0x00: /* Control */
+ *data = gic_get_cpu_control(s, cpu, attrs);
+ break;
+ case 0x04: /* Priority mask */
+ *data = gic_get_priority_mask(s, cpu, attrs);
+ break;
+ case 0x08: /* Binary Point */
+ if (s->security_extn && !attrs.secure) {
+ /* BPR is banked. Non-secure copy stored in ABPR. */
+ *data = s->abpr[cpu];
+ } else {
+ *data = s->bpr[cpu];
+ }
+ break;
+ case 0x0c: /* Acknowledge */
+ *data = gic_acknowledge_irq(s, cpu, attrs);
+ break;
+ case 0x14: /* Running Priority */
+ *data = gic_get_running_priority(s, cpu, attrs);
+ break;
+ case 0x18: /* Highest Pending Interrupt */
+ *data = gic_get_current_pending_irq(s, cpu, attrs);
+ break;
+ case 0x1c: /* Aliased Binary Point */
+ /* GIC v2, no security: ABPR
+ * GIC v1, no security: not implemented (RAZ/WI)
+ * With security extensions, secure access: ABPR (alias of NS BPR)
+ * With security extensions, nonsecure access: RAZ/WI
+ */
+ if (!gic_has_groups(s) || (s->security_extn && !attrs.secure)) {
+ *data = 0;
+ } else {
+ *data = s->abpr[cpu];
+ }
+ break;
+ case 0xd0: case 0xd4: case 0xd8: case 0xdc:
+ *data = s->apr[(offset - 0xd0) / 4][cpu];
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "gic_cpu_read: Bad offset %x\n", (int)offset);
+ return MEMTX_ERROR;
+ }
+ return MEMTX_OK;
+}
+
+static MemTxResult gic_cpu_write(GICState *s, int cpu, int offset,
+ uint32_t value, MemTxAttrs attrs)
+{
+ switch (offset) {
+ case 0x00: /* Control */
+ gic_set_cpu_control(s, cpu, value, attrs);
+ break;
+ case 0x04: /* Priority mask */
+ gic_set_priority_mask(s, cpu, value, attrs);
+ break;
+ case 0x08: /* Binary Point */
+ if (s->security_extn && !attrs.secure) {
+ s->abpr[cpu] = MAX(value & 0x7, GIC_MIN_ABPR);
+ } else {
+ s->bpr[cpu] = MAX(value & 0x7, GIC_MIN_BPR);
+ }
+ break;
+ case 0x10: /* End Of Interrupt */
+ gic_complete_irq(s, cpu, value & 0x3ff, attrs);
+ return MEMTX_OK;
+ case 0x1c: /* Aliased Binary Point */
+ if (!gic_has_groups(s) || (s->security_extn && !attrs.secure)) {
+ /* unimplemented, or NS access: RAZ/WI */
+ return MEMTX_OK;
+ } else {
+ s->abpr[cpu] = MAX(value & 0x7, GIC_MIN_ABPR);
+ }
+ break;
+ case 0xd0: case 0xd4: case 0xd8: case 0xdc:
+ qemu_log_mask(LOG_UNIMP, "Writing APR not implemented\n");
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "gic_cpu_write: Bad offset %x\n", (int)offset);
+ return MEMTX_ERROR;
+ }
+ gic_update(s);
+ return MEMTX_OK;
+}
+
+/* Wrappers to read/write the GIC CPU interface for the current CPU */
+static MemTxResult gic_thiscpu_read(void *opaque, hwaddr addr, uint64_t *data,
+ unsigned size, MemTxAttrs attrs)
+{
+ GICState *s = (GICState *)opaque;
+ return gic_cpu_read(s, gic_get_current_cpu(s), addr, data, attrs);
+}
+
+static MemTxResult gic_thiscpu_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ GICState *s = (GICState *)opaque;
+ return gic_cpu_write(s, gic_get_current_cpu(s), addr, value, attrs);
+}
+
+/* Wrappers to read/write the GIC CPU interface for a specific CPU.
+ * These just decode the opaque pointer into GICState* + cpu id.
+ */
+static MemTxResult gic_do_cpu_read(void *opaque, hwaddr addr, uint64_t *data,
+ unsigned size, MemTxAttrs attrs)
+{
+ GICState **backref = (GICState **)opaque;
+ GICState *s = *backref;
+ int id = (backref - s->backref);
+ return gic_cpu_read(s, id, addr, data, attrs);
+}
+
+static MemTxResult gic_do_cpu_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ GICState **backref = (GICState **)opaque;
+ GICState *s = *backref;
+ int id = (backref - s->backref);
+ return gic_cpu_write(s, id, addr, value, attrs);
+}
+
+static const MemoryRegionOps gic_thiscpu_ops = {
+ .read_with_attrs = gic_thiscpu_read,
+ .write_with_attrs = gic_thiscpu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps gic_cpu_ops = {
+ .read_with_attrs = gic_do_cpu_read,
+ .write_with_attrs = gic_do_cpu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void gic_init_irqs_and_distributor(GICState *s)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(s);
+ int i;
+
+ i = s->num_irq - GIC_INTERNAL;
+ /* For the GIC, also expose incoming GPIO lines for PPIs for each CPU.
+ * GPIO array layout is thus:
+ * [0..N-1] SPIs
+ * [N..N+31] PPIs for CPU 0
+ * [N+32..N+63] PPIs for CPU 1
+ * ...
+ */
+ if (s->revision != REV_NVIC) {
+ i += (GIC_INTERNAL * s->num_cpu);
+ }
+ qdev_init_gpio_in(DEVICE(s), gic_set_irq, i);
+ for (i = 0; i < NUM_CPU(s); i++) {
+ sysbus_init_irq(sbd, &s->parent_irq[i]);
+ }
+ for (i = 0; i < NUM_CPU(s); i++) {
+ sysbus_init_irq(sbd, &s->parent_fiq[i]);
+ }
+ memory_region_init_io(&s->iomem, OBJECT(s), &gic_dist_ops, s,
+ "gic_dist", 0x1000);
+}
+
+static void arm_gic_realize(DeviceState *dev, Error **errp)
+{
+ /* Device instance realize function for the GIC sysbus device */
+ int i;
+ GICState *s = ARM_GIC(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ ARMGICClass *agc = ARM_GIC_GET_CLASS(s);
+ Error *local_err = NULL;
+
+ agc->parent_realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ gic_init_irqs_and_distributor(s);
+
+ /* Memory regions for the CPU interfaces (NVIC doesn't have these):
+ * a region for "CPU interface for this core", then a region for
+ * "CPU interface for core 0", "for core 1", ...
+ * NB that the memory region size of 0x100 applies for the 11MPCore
+ * and also cores following the GIC v1 spec (ie A9).
+ * GIC v2 defines a larger memory region (0x1000) so this will need
+ * to be extended when we implement A15.
+ */
+ memory_region_init_io(&s->cpuiomem[0], OBJECT(s), &gic_thiscpu_ops, s,
+ "gic_cpu", 0x100);
+ for (i = 0; i < NUM_CPU(s); i++) {
+ s->backref[i] = s;
+ memory_region_init_io(&s->cpuiomem[i+1], OBJECT(s), &gic_cpu_ops,
+ &s->backref[i], "gic_cpu", 0x100);
+ }
+ /* Distributor */
+ sysbus_init_mmio(sbd, &s->iomem);
+ /* cpu interfaces (one for "current cpu" plus one per cpu) */
+ for (i = 0; i <= NUM_CPU(s); i++) {
+ sysbus_init_mmio(sbd, &s->cpuiomem[i]);
+ }
+}
+
+static void arm_gic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ARMGICClass *agc = ARM_GIC_CLASS(klass);
+
+ agc->parent_realize = dc->realize;
+ dc->realize = arm_gic_realize;
+}
+
+static const TypeInfo arm_gic_info = {
+ .name = TYPE_ARM_GIC,
+ .parent = TYPE_ARM_GIC_COMMON,
+ .instance_size = sizeof(GICState),
+ .class_init = arm_gic_class_init,
+ .class_size = sizeof(ARMGICClass),
+};
+
+static void arm_gic_register_types(void)
+{
+ type_register_static(&arm_gic_info);
+}
+
+type_init(arm_gic_register_types)
diff --git a/hw/intc/arm_gic_common.c b/hw/intc/arm_gic_common.c
new file mode 100644
index 00000000..a64d0714
--- /dev/null
+++ b/hw/intc/arm_gic_common.c
@@ -0,0 +1,204 @@
+/*
+ * ARM GIC support - common bits of emulated and KVM kernel model
+ *
+ * Copyright (c) 2012 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gic_internal.h"
+
+static void gic_pre_save(void *opaque)
+{
+ GICState *s = (GICState *)opaque;
+ ARMGICCommonClass *c = ARM_GIC_COMMON_GET_CLASS(s);
+
+ if (c->pre_save) {
+ c->pre_save(s);
+ }
+}
+
+static int gic_post_load(void *opaque, int version_id)
+{
+ GICState *s = (GICState *)opaque;
+ ARMGICCommonClass *c = ARM_GIC_COMMON_GET_CLASS(s);
+
+ if (c->post_load) {
+ c->post_load(s);
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_gic_irq_state = {
+ .name = "arm_gic_irq_state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(enabled, gic_irq_state),
+ VMSTATE_UINT8(pending, gic_irq_state),
+ VMSTATE_UINT8(active, gic_irq_state),
+ VMSTATE_UINT8(level, gic_irq_state),
+ VMSTATE_BOOL(model, gic_irq_state),
+ VMSTATE_BOOL(edge_trigger, gic_irq_state),
+ VMSTATE_UINT8(group, gic_irq_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_gic = {
+ .name = "arm_gic",
+ .version_id = 10,
+ .minimum_version_id = 10,
+ .pre_save = gic_pre_save,
+ .post_load = gic_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ctlr, GICState),
+ VMSTATE_UINT32_ARRAY(cpu_ctlr, GICState, GIC_NCPU),
+ VMSTATE_STRUCT_ARRAY(irq_state, GICState, GIC_MAXIRQ, 1,
+ vmstate_gic_irq_state, gic_irq_state),
+ VMSTATE_UINT8_ARRAY(irq_target, GICState, GIC_MAXIRQ),
+ VMSTATE_UINT8_2DARRAY(priority1, GICState, GIC_INTERNAL, GIC_NCPU),
+ VMSTATE_UINT8_ARRAY(priority2, GICState, GIC_MAXIRQ - GIC_INTERNAL),
+ VMSTATE_UINT16_2DARRAY(last_active, GICState, GIC_MAXIRQ, GIC_NCPU),
+ VMSTATE_UINT8_2DARRAY(sgi_pending, GICState, GIC_NR_SGIS, GIC_NCPU),
+ VMSTATE_UINT16_ARRAY(priority_mask, GICState, GIC_NCPU),
+ VMSTATE_UINT16_ARRAY(running_irq, GICState, GIC_NCPU),
+ VMSTATE_UINT16_ARRAY(running_priority, GICState, GIC_NCPU),
+ VMSTATE_UINT16_ARRAY(current_pending, GICState, GIC_NCPU),
+ VMSTATE_UINT8_ARRAY(bpr, GICState, GIC_NCPU),
+ VMSTATE_UINT8_ARRAY(abpr, GICState, GIC_NCPU),
+ VMSTATE_UINT32_2DARRAY(apr, GICState, GIC_NR_APRS, GIC_NCPU),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void arm_gic_common_realize(DeviceState *dev, Error **errp)
+{
+ GICState *s = ARM_GIC_COMMON(dev);
+ int num_irq = s->num_irq;
+
+ if (s->num_cpu > GIC_NCPU) {
+ error_setg(errp, "requested %u CPUs exceeds GIC maximum %d",
+ s->num_cpu, GIC_NCPU);
+ return;
+ }
+ s->num_irq += GIC_BASE_IRQ;
+ if (s->num_irq > GIC_MAXIRQ) {
+ error_setg(errp,
+ "requested %u interrupt lines exceeds GIC maximum %d",
+ num_irq, GIC_MAXIRQ);
+ return;
+ }
+ /* ITLinesNumber is represented as (N / 32) - 1 (see
+ * gic_dist_readb) so this is an implementation imposed
+ * restriction, not an architectural one:
+ */
+ if (s->num_irq < 32 || (s->num_irq % 32)) {
+ error_setg(errp,
+ "%d interrupt lines unsupported: not divisible by 32",
+ num_irq);
+ return;
+ }
+
+ if (s->security_extn &&
+ (s->revision == REV_11MPCORE || s->revision == REV_NVIC)) {
+ error_setg(errp, "this GIC revision does not implement "
+ "the security extensions");
+ return;
+ }
+}
+
+static void arm_gic_common_reset(DeviceState *dev)
+{
+ GICState *s = ARM_GIC_COMMON(dev);
+ int i, j;
+ memset(s->irq_state, 0, GIC_MAXIRQ * sizeof(gic_irq_state));
+ for (i = 0 ; i < s->num_cpu; i++) {
+ if (s->revision == REV_11MPCORE) {
+ s->priority_mask[i] = 0xf0;
+ } else {
+ s->priority_mask[i] = 0;
+ }
+ s->current_pending[i] = 1023;
+ s->running_irq[i] = 1023;
+ s->running_priority[i] = 0x100;
+ s->cpu_ctlr[i] = 0;
+ s->bpr[i] = GIC_MIN_BPR;
+ s->abpr[i] = GIC_MIN_ABPR;
+ for (j = 0; j < GIC_INTERNAL; j++) {
+ s->priority1[j][i] = 0;
+ }
+ for (j = 0; j < GIC_NR_SGIS; j++) {
+ s->sgi_pending[j][i] = 0;
+ }
+ }
+ for (i = 0; i < GIC_NR_SGIS; i++) {
+ GIC_SET_ENABLED(i, ALL_CPU_MASK);
+ GIC_SET_EDGE_TRIGGER(i);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(s->priority2); i++) {
+ s->priority2[i] = 0;
+ }
+
+ for (i = 0; i < GIC_MAXIRQ; i++) {
+ /* For uniprocessor GICs all interrupts always target the sole CPU */
+ if (s->num_cpu == 1) {
+ s->irq_target[i] = 1;
+ } else {
+ s->irq_target[i] = 0;
+ }
+ }
+ s->ctlr = 0;
+}
+
+static Property arm_gic_common_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", GICState, num_cpu, 1),
+ DEFINE_PROP_UINT32("num-irq", GICState, num_irq, 32),
+ /* Revision can be 1 or 2 for GIC architecture specification
+ * versions 1 or 2, or 0 to indicate the legacy 11MPCore GIC.
+ * (Internally, 0xffffffff also indicates "not a GIC but an NVIC".)
+ */
+ DEFINE_PROP_UINT32("revision", GICState, revision, 1),
+ /* True if the GIC should implement the security extensions */
+ DEFINE_PROP_BOOL("has-security-extensions", GICState, security_extn, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void arm_gic_common_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = arm_gic_common_reset;
+ dc->realize = arm_gic_common_realize;
+ dc->props = arm_gic_common_properties;
+ dc->vmsd = &vmstate_gic;
+}
+
+static const TypeInfo arm_gic_common_type = {
+ .name = TYPE_ARM_GIC_COMMON,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(GICState),
+ .class_size = sizeof(ARMGICCommonClass),
+ .class_init = arm_gic_common_class_init,
+ .abstract = true,
+};
+
+static void register_types(void)
+{
+ type_register_static(&arm_gic_common_type);
+}
+
+type_init(register_types)
diff --git a/hw/intc/arm_gic_kvm.c b/hw/intc/arm_gic_kvm.c
new file mode 100644
index 00000000..f56bff1a
--- /dev/null
+++ b/hw/intc/arm_gic_kvm.c
@@ -0,0 +1,663 @@
+/*
+ * ARM Generic Interrupt Controller using KVM in-kernel support
+ *
+ * Copyright (c) 2012 Linaro Limited
+ * Written by Peter Maydell
+ * Save/Restore logic added by Christoffer Dall.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+#include "kvm_arm.h"
+#include "gic_internal.h"
+
+//#define DEBUG_GIC_KVM
+
+#ifdef DEBUG_GIC_KVM
+static const int debug_gic_kvm = 1;
+#else
+static const int debug_gic_kvm = 0;
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (debug_gic_kvm) { \
+ printf("arm_gic: " fmt , ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define TYPE_KVM_ARM_GIC "kvm-arm-gic"
+#define KVM_ARM_GIC(obj) \
+ OBJECT_CHECK(GICState, (obj), TYPE_KVM_ARM_GIC)
+#define KVM_ARM_GIC_CLASS(klass) \
+ OBJECT_CLASS_CHECK(KVMARMGICClass, (klass), TYPE_KVM_ARM_GIC)
+#define KVM_ARM_GIC_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(KVMARMGICClass, (obj), TYPE_KVM_ARM_GIC)
+
+typedef struct KVMARMGICClass {
+ ARMGICCommonClass parent_class;
+ DeviceRealize parent_realize;
+ void (*parent_reset)(DeviceState *dev);
+} KVMARMGICClass;
+
+static void kvm_arm_gic_set_irq(void *opaque, int irq, int level)
+{
+ /* Meaning of the 'irq' parameter:
+ * [0..N-1] : external interrupts
+ * [N..N+31] : PPI (internal) interrupts for CPU 0
+ * [N+32..N+63] : PPI (internal interrupts for CPU 1
+ * ...
+ * Convert this to the kernel's desired encoding, which
+ * has separate fields in the irq number for type,
+ * CPU number and interrupt number.
+ */
+ GICState *s = (GICState *)opaque;
+ int kvm_irq, irqtype, cpu;
+
+ if (irq < (s->num_irq - GIC_INTERNAL)) {
+ /* External interrupt. The kernel numbers these like the GIC
+ * hardware, with external interrupt IDs starting after the
+ * internal ones.
+ */
+ irqtype = KVM_ARM_IRQ_TYPE_SPI;
+ cpu = 0;
+ irq += GIC_INTERNAL;
+ } else {
+ /* Internal interrupt: decode into (cpu, interrupt id) */
+ irqtype = KVM_ARM_IRQ_TYPE_PPI;
+ irq -= (s->num_irq - GIC_INTERNAL);
+ cpu = irq / GIC_INTERNAL;
+ irq %= GIC_INTERNAL;
+ }
+ kvm_irq = (irqtype << KVM_ARM_IRQ_TYPE_SHIFT)
+ | (cpu << KVM_ARM_IRQ_VCPU_SHIFT) | irq;
+
+ kvm_set_irq(kvm_state, kvm_irq, !!level);
+}
+
+static bool kvm_arm_gic_can_save_restore(GICState *s)
+{
+ return s->dev_fd >= 0;
+}
+
+static bool kvm_gic_supports_attr(GICState *s, int group, int attrnum)
+{
+ struct kvm_device_attr attr = {
+ .group = group,
+ .attr = attrnum,
+ .flags = 0,
+ };
+
+ if (s->dev_fd == -1) {
+ return false;
+ }
+
+ return kvm_device_ioctl(s->dev_fd, KVM_HAS_DEVICE_ATTR, &attr) == 0;
+}
+
+static void kvm_gic_access(GICState *s, int group, int offset,
+ int cpu, uint32_t *val, bool write)
+{
+ struct kvm_device_attr attr;
+ int type;
+ int err;
+
+ cpu = cpu & 0xff;
+
+ attr.flags = 0;
+ attr.group = group;
+ attr.attr = (((uint64_t)cpu << KVM_DEV_ARM_VGIC_CPUID_SHIFT) &
+ KVM_DEV_ARM_VGIC_CPUID_MASK) |
+ (((uint64_t)offset << KVM_DEV_ARM_VGIC_OFFSET_SHIFT) &
+ KVM_DEV_ARM_VGIC_OFFSET_MASK);
+ attr.addr = (uintptr_t)val;
+
+ if (write) {
+ type = KVM_SET_DEVICE_ATTR;
+ } else {
+ type = KVM_GET_DEVICE_ATTR;
+ }
+
+ err = kvm_device_ioctl(s->dev_fd, type, &attr);
+ if (err < 0) {
+ fprintf(stderr, "KVM_{SET/GET}_DEVICE_ATTR failed: %s\n",
+ strerror(-err));
+ abort();
+ }
+}
+
+static void kvm_gicd_access(GICState *s, int offset, int cpu,
+ uint32_t *val, bool write)
+{
+ kvm_gic_access(s, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ offset, cpu, val, write);
+}
+
+static void kvm_gicc_access(GICState *s, int offset, int cpu,
+ uint32_t *val, bool write)
+{
+ kvm_gic_access(s, KVM_DEV_ARM_VGIC_GRP_CPU_REGS,
+ offset, cpu, val, write);
+}
+
+#define for_each_irq_reg(_ctr, _max_irq, _field_width) \
+ for (_ctr = 0; _ctr < ((_max_irq) / (32 / (_field_width))); _ctr++)
+
+/*
+ * Translate from the in-kernel field for an IRQ value to/from the qemu
+ * representation.
+ */
+typedef void (*vgic_translate_fn)(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel);
+
+/* synthetic translate function used for clear/set registers to completely
+ * clear a setting using a clear-register before setting the remaining bits
+ * using a set-register */
+static void translate_clear(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ if (to_kernel) {
+ *field = ~0;
+ } else {
+ /* does not make sense: qemu model doesn't use set/clear regs */
+ abort();
+ }
+}
+
+static void translate_group(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
+
+ if (to_kernel) {
+ *field = GIC_TEST_GROUP(irq, cm);
+ } else {
+ if (*field & 1) {
+ GIC_SET_GROUP(irq, cm);
+ }
+ }
+}
+
+static void translate_enabled(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
+
+ if (to_kernel) {
+ *field = GIC_TEST_ENABLED(irq, cm);
+ } else {
+ if (*field & 1) {
+ GIC_SET_ENABLED(irq, cm);
+ }
+ }
+}
+
+static void translate_pending(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
+
+ if (to_kernel) {
+ *field = gic_test_pending(s, irq, cm);
+ } else {
+ if (*field & 1) {
+ GIC_SET_PENDING(irq, cm);
+ /* TODO: Capture is level-line is held high in the kernel */
+ }
+ }
+}
+
+static void translate_active(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
+
+ if (to_kernel) {
+ *field = GIC_TEST_ACTIVE(irq, cm);
+ } else {
+ if (*field & 1) {
+ GIC_SET_ACTIVE(irq, cm);
+ }
+ }
+}
+
+static void translate_trigger(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ if (to_kernel) {
+ *field = (GIC_TEST_EDGE_TRIGGER(irq)) ? 0x2 : 0x0;
+ } else {
+ if (*field & 0x2) {
+ GIC_SET_EDGE_TRIGGER(irq);
+ }
+ }
+}
+
+static void translate_priority(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ if (to_kernel) {
+ *field = GIC_GET_PRIORITY(irq, cpu) & 0xff;
+ } else {
+ gic_set_priority(s, cpu, irq, *field & 0xff, MEMTXATTRS_UNSPECIFIED);
+ }
+}
+
+static void translate_targets(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ if (to_kernel) {
+ *field = s->irq_target[irq] & 0xff;
+ } else {
+ s->irq_target[irq] = *field & 0xff;
+ }
+}
+
+static void translate_sgisource(GICState *s, int irq, int cpu,
+ uint32_t *field, bool to_kernel)
+{
+ if (to_kernel) {
+ *field = s->sgi_pending[irq][cpu] & 0xff;
+ } else {
+ s->sgi_pending[irq][cpu] = *field & 0xff;
+ }
+}
+
+/* Read a register group from the kernel VGIC */
+static void kvm_dist_get(GICState *s, uint32_t offset, int width,
+ int maxirq, vgic_translate_fn translate_fn)
+{
+ uint32_t reg;
+ int i;
+ int j;
+ int irq;
+ int cpu;
+ int regsz = 32 / width; /* irqs per kernel register */
+ uint32_t field;
+
+ for_each_irq_reg(i, maxirq, width) {
+ irq = i * regsz;
+ cpu = 0;
+ while ((cpu < s->num_cpu && irq < GIC_INTERNAL) || cpu == 0) {
+ kvm_gicd_access(s, offset, cpu, &reg, false);
+ for (j = 0; j < regsz; j++) {
+ field = extract32(reg, j * width, width);
+ translate_fn(s, irq + j, cpu, &field, false);
+ }
+
+ cpu++;
+ }
+ offset += 4;
+ }
+}
+
+/* Write a register group to the kernel VGIC */
+static void kvm_dist_put(GICState *s, uint32_t offset, int width,
+ int maxirq, vgic_translate_fn translate_fn)
+{
+ uint32_t reg;
+ int i;
+ int j;
+ int irq;
+ int cpu;
+ int regsz = 32 / width; /* irqs per kernel register */
+ uint32_t field;
+
+ for_each_irq_reg(i, maxirq, width) {
+ irq = i * regsz;
+ cpu = 0;
+ while ((cpu < s->num_cpu && irq < GIC_INTERNAL) || cpu == 0) {
+ reg = 0;
+ for (j = 0; j < regsz; j++) {
+ translate_fn(s, irq + j, cpu, &field, true);
+ reg = deposit32(reg, j * width, width, field);
+ }
+ kvm_gicd_access(s, offset, cpu, &reg, true);
+
+ cpu++;
+ }
+ offset += 4;
+ }
+}
+
+static void kvm_arm_gic_put(GICState *s)
+{
+ uint32_t reg;
+ int i;
+ int cpu;
+ int num_cpu;
+ int num_irq;
+
+ if (!kvm_arm_gic_can_save_restore(s)) {
+ DPRINTF("Cannot put kernel gic state, no kernel interface");
+ return;
+ }
+
+ /* Note: We do the restore in a slightly different order than the save
+ * (where the order doesn't matter and is simply ordered according to the
+ * register offset values */
+
+ /*****************************************************************
+ * Distributor State
+ */
+
+ /* s->ctlr -> GICD_CTLR */
+ reg = s->ctlr;
+ kvm_gicd_access(s, 0x0, 0, &reg, true);
+
+ /* Sanity checking on GICD_TYPER and s->num_irq, s->num_cpu */
+ kvm_gicd_access(s, 0x4, 0, &reg, false);
+ num_irq = ((reg & 0x1f) + 1) * 32;
+ num_cpu = ((reg & 0xe0) >> 5) + 1;
+
+ if (num_irq < s->num_irq) {
+ fprintf(stderr, "Restoring %u IRQs, but kernel supports max %d\n",
+ s->num_irq, num_irq);
+ abort();
+ } else if (num_cpu != s->num_cpu) {
+ fprintf(stderr, "Restoring %u CPU interfaces, kernel only has %d\n",
+ s->num_cpu, num_cpu);
+ /* Did we not create the VCPUs in the kernel yet? */
+ abort();
+ }
+
+ /* TODO: Consider checking compatibility with the IIDR ? */
+
+ /* irq_state[n].enabled -> GICD_ISENABLERn */
+ kvm_dist_put(s, 0x180, 1, s->num_irq, translate_clear);
+ kvm_dist_put(s, 0x100, 1, s->num_irq, translate_enabled);
+
+ /* irq_state[n].group -> GICD_IGROUPRn */
+ kvm_dist_put(s, 0x80, 1, s->num_irq, translate_group);
+
+ /* s->irq_target[irq] -> GICD_ITARGETSRn
+ * (restore targets before pending to ensure the pending state is set on
+ * the appropriate CPU interfaces in the kernel) */
+ kvm_dist_put(s, 0x800, 8, s->num_irq, translate_targets);
+
+ /* irq_state[n].trigger -> GICD_ICFGRn
+ * (restore configuration registers before pending IRQs so we treat
+ * level/edge correctly) */
+ kvm_dist_put(s, 0xc00, 2, s->num_irq, translate_trigger);
+
+ /* irq_state[n].pending + irq_state[n].level -> GICD_ISPENDRn */
+ kvm_dist_put(s, 0x280, 1, s->num_irq, translate_clear);
+ kvm_dist_put(s, 0x200, 1, s->num_irq, translate_pending);
+
+ /* irq_state[n].active -> GICD_ISACTIVERn */
+ kvm_dist_put(s, 0x380, 1, s->num_irq, translate_clear);
+ kvm_dist_put(s, 0x300, 1, s->num_irq, translate_active);
+
+
+ /* s->priorityX[irq] -> ICD_IPRIORITYRn */
+ kvm_dist_put(s, 0x400, 8, s->num_irq, translate_priority);
+
+ /* s->sgi_pending -> ICD_CPENDSGIRn */
+ kvm_dist_put(s, 0xf10, 8, GIC_NR_SGIS, translate_clear);
+ kvm_dist_put(s, 0xf20, 8, GIC_NR_SGIS, translate_sgisource);
+
+
+ /*****************************************************************
+ * CPU Interface(s) State
+ */
+
+ for (cpu = 0; cpu < s->num_cpu; cpu++) {
+ /* s->cpu_ctlr[cpu] -> GICC_CTLR */
+ reg = s->cpu_ctlr[cpu];
+ kvm_gicc_access(s, 0x00, cpu, &reg, true);
+
+ /* s->priority_mask[cpu] -> GICC_PMR */
+ reg = (s->priority_mask[cpu] & 0xff);
+ kvm_gicc_access(s, 0x04, cpu, &reg, true);
+
+ /* s->bpr[cpu] -> GICC_BPR */
+ reg = (s->bpr[cpu] & 0x7);
+ kvm_gicc_access(s, 0x08, cpu, &reg, true);
+
+ /* s->abpr[cpu] -> GICC_ABPR */
+ reg = (s->abpr[cpu] & 0x7);
+ kvm_gicc_access(s, 0x1c, cpu, &reg, true);
+
+ /* s->apr[n][cpu] -> GICC_APRn */
+ for (i = 0; i < 4; i++) {
+ reg = s->apr[i][cpu];
+ kvm_gicc_access(s, 0xd0 + i * 4, cpu, &reg, true);
+ }
+ }
+}
+
+static void kvm_arm_gic_get(GICState *s)
+{
+ uint32_t reg;
+ int i;
+ int cpu;
+
+ if (!kvm_arm_gic_can_save_restore(s)) {
+ DPRINTF("Cannot get kernel gic state, no kernel interface");
+ return;
+ }
+
+ /*****************************************************************
+ * Distributor State
+ */
+
+ /* GICD_CTLR -> s->ctlr */
+ kvm_gicd_access(s, 0x0, 0, &reg, false);
+ s->ctlr = reg;
+
+ /* Sanity checking on GICD_TYPER -> s->num_irq, s->num_cpu */
+ kvm_gicd_access(s, 0x4, 0, &reg, false);
+ s->num_irq = ((reg & 0x1f) + 1) * 32;
+ s->num_cpu = ((reg & 0xe0) >> 5) + 1;
+
+ if (s->num_irq > GIC_MAXIRQ) {
+ fprintf(stderr, "Too many IRQs reported from the kernel: %d\n",
+ s->num_irq);
+ abort();
+ }
+
+ /* GICD_IIDR -> ? */
+ kvm_gicd_access(s, 0x8, 0, &reg, false);
+
+ /* Clear all the IRQ settings */
+ for (i = 0; i < s->num_irq; i++) {
+ memset(&s->irq_state[i], 0, sizeof(s->irq_state[0]));
+ }
+
+ /* GICD_IGROUPRn -> irq_state[n].group */
+ kvm_dist_get(s, 0x80, 1, s->num_irq, translate_group);
+
+ /* GICD_ISENABLERn -> irq_state[n].enabled */
+ kvm_dist_get(s, 0x100, 1, s->num_irq, translate_enabled);
+
+ /* GICD_ISPENDRn -> irq_state[n].pending + irq_state[n].level */
+ kvm_dist_get(s, 0x200, 1, s->num_irq, translate_pending);
+
+ /* GICD_ISACTIVERn -> irq_state[n].active */
+ kvm_dist_get(s, 0x300, 1, s->num_irq, translate_active);
+
+ /* GICD_ICFRn -> irq_state[n].trigger */
+ kvm_dist_get(s, 0xc00, 2, s->num_irq, translate_trigger);
+
+ /* GICD_IPRIORITYRn -> s->priorityX[irq] */
+ kvm_dist_get(s, 0x400, 8, s->num_irq, translate_priority);
+
+ /* GICD_ITARGETSRn -> s->irq_target[irq] */
+ kvm_dist_get(s, 0x800, 8, s->num_irq, translate_targets);
+
+ /* GICD_CPENDSGIRn -> s->sgi_pending */
+ kvm_dist_get(s, 0xf10, 8, GIC_NR_SGIS, translate_sgisource);
+
+
+ /*****************************************************************
+ * CPU Interface(s) State
+ */
+
+ for (cpu = 0; cpu < s->num_cpu; cpu++) {
+ /* GICC_CTLR -> s->cpu_ctlr[cpu] */
+ kvm_gicc_access(s, 0x00, cpu, &reg, false);
+ s->cpu_ctlr[cpu] = reg;
+
+ /* GICC_PMR -> s->priority_mask[cpu] */
+ kvm_gicc_access(s, 0x04, cpu, &reg, false);
+ s->priority_mask[cpu] = (reg & 0xff);
+
+ /* GICC_BPR -> s->bpr[cpu] */
+ kvm_gicc_access(s, 0x08, cpu, &reg, false);
+ s->bpr[cpu] = (reg & 0x7);
+
+ /* GICC_ABPR -> s->abpr[cpu] */
+ kvm_gicc_access(s, 0x1c, cpu, &reg, false);
+ s->abpr[cpu] = (reg & 0x7);
+
+ /* GICC_APRn -> s->apr[n][cpu] */
+ for (i = 0; i < 4; i++) {
+ kvm_gicc_access(s, 0xd0 + i * 4, cpu, &reg, false);
+ s->apr[i][cpu] = reg;
+ }
+ }
+}
+
+static void kvm_arm_gic_reset(DeviceState *dev)
+{
+ GICState *s = ARM_GIC_COMMON(dev);
+ KVMARMGICClass *kgc = KVM_ARM_GIC_GET_CLASS(s);
+
+ kgc->parent_reset(dev);
+ kvm_arm_gic_put(s);
+}
+
+static void kvm_arm_gic_realize(DeviceState *dev, Error **errp)
+{
+ int i;
+ GICState *s = KVM_ARM_GIC(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ KVMARMGICClass *kgc = KVM_ARM_GIC_GET_CLASS(s);
+ Error *local_err = NULL;
+ int ret;
+
+ kgc->parent_realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (s->security_extn) {
+ error_setg(errp, "the in-kernel VGIC does not implement the "
+ "security extensions");
+ return;
+ }
+
+ i = s->num_irq - GIC_INTERNAL;
+ /* For the GIC, also expose incoming GPIO lines for PPIs for each CPU.
+ * GPIO array layout is thus:
+ * [0..N-1] SPIs
+ * [N..N+31] PPIs for CPU 0
+ * [N+32..N+63] PPIs for CPU 1
+ * ...
+ */
+ i += (GIC_INTERNAL * s->num_cpu);
+ qdev_init_gpio_in(dev, kvm_arm_gic_set_irq, i);
+
+ for (i = 0; i < s->num_irq - GIC_INTERNAL; i++) {
+ qemu_irq irq = qdev_get_gpio_in(dev, i);
+ kvm_irqchip_set_qemuirq_gsi(kvm_state, irq, i);
+ }
+
+ /* We never use our outbound IRQ/FIQ lines but provide them so that
+ * we maintain the same interface as the non-KVM GIC.
+ */
+ for (i = 0; i < s->num_cpu; i++) {
+ sysbus_init_irq(sbd, &s->parent_irq[i]);
+ }
+ for (i = 0; i < s->num_cpu; i++) {
+ sysbus_init_irq(sbd, &s->parent_fiq[i]);
+ }
+
+ /* Try to create the device via the device control API */
+ s->dev_fd = -1;
+ ret = kvm_create_device(kvm_state, KVM_DEV_TYPE_ARM_VGIC_V2, false);
+ if (ret >= 0) {
+ s->dev_fd = ret;
+ } else if (ret != -ENODEV && ret != -ENOTSUP) {
+ error_setg_errno(errp, -ret, "error creating in-kernel VGIC");
+ return;
+ }
+
+ if (kvm_gic_supports_attr(s, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0)) {
+ uint32_t numirqs = s->num_irq;
+ kvm_gic_access(s, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0, 0, &numirqs, 1);
+ }
+
+ /* Tell the kernel to complete VGIC initialization now */
+ if (kvm_gic_supports_attr(s, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT)) {
+ kvm_gic_access(s, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, 0, 0, 1);
+ }
+
+ /* Distributor */
+ memory_region_init_reservation(&s->iomem, OBJECT(s),
+ "kvm-gic_dist", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ kvm_arm_register_device(&s->iomem,
+ (KVM_ARM_DEVICE_VGIC_V2 << KVM_ARM_DEVICE_ID_SHIFT)
+ | KVM_VGIC_V2_ADDR_TYPE_DIST,
+ KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V2_ADDR_TYPE_DIST,
+ s->dev_fd);
+ /* CPU interface for current core. Unlike arm_gic, we don't
+ * provide the "interface for core #N" memory regions, because
+ * cores with a VGIC don't have those.
+ */
+ memory_region_init_reservation(&s->cpuiomem[0], OBJECT(s),
+ "kvm-gic_cpu", 0x1000);
+ sysbus_init_mmio(sbd, &s->cpuiomem[0]);
+ kvm_arm_register_device(&s->cpuiomem[0],
+ (KVM_ARM_DEVICE_VGIC_V2 << KVM_ARM_DEVICE_ID_SHIFT)
+ | KVM_VGIC_V2_ADDR_TYPE_CPU,
+ KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V2_ADDR_TYPE_CPU,
+ s->dev_fd);
+}
+
+static void kvm_arm_gic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ARMGICCommonClass *agcc = ARM_GIC_COMMON_CLASS(klass);
+ KVMARMGICClass *kgc = KVM_ARM_GIC_CLASS(klass);
+
+ agcc->pre_save = kvm_arm_gic_get;
+ agcc->post_load = kvm_arm_gic_put;
+ kgc->parent_realize = dc->realize;
+ kgc->parent_reset = dc->reset;
+ dc->realize = kvm_arm_gic_realize;
+ dc->reset = kvm_arm_gic_reset;
+}
+
+static const TypeInfo kvm_arm_gic_info = {
+ .name = TYPE_KVM_ARM_GIC,
+ .parent = TYPE_ARM_GIC_COMMON,
+ .instance_size = sizeof(GICState),
+ .class_init = kvm_arm_gic_class_init,
+ .class_size = sizeof(KVMARMGICClass),
+};
+
+static void kvm_arm_gic_register_types(void)
+{
+ type_register_static(&kvm_arm_gic_info);
+}
+
+type_init(kvm_arm_gic_register_types)
diff --git a/hw/intc/arm_gicv2m.c b/hw/intc/arm_gicv2m.c
new file mode 100644
index 00000000..43d1976c
--- /dev/null
+++ b/hw/intc/arm_gicv2m.c
@@ -0,0 +1,192 @@
+/*
+ * GICv2m extension for MSI/MSI-x support with a GICv2-based system
+ *
+ * Copyright (C) 2015 Linaro, All rights reserved.
+ *
+ * Author: Christoffer Dall <christoffer.dall@linaro.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This file implements an emulated GICv2m widget as described in the ARM
+ * Server Base System Architecture (SBSA) specification Version 2.2
+ * (ARM-DEN-0029 v2.2) pages 35-39 without any optional implementation defined
+ * identification registers and with a single non-secure MSI register frame.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/pci/msi.h"
+
+#define TYPE_ARM_GICV2M "arm-gicv2m"
+#define ARM_GICV2M(obj) OBJECT_CHECK(ARMGICv2mState, (obj), TYPE_ARM_GICV2M)
+
+#define GICV2M_NUM_SPI_MAX 128
+
+#define V2M_MSI_TYPER 0x008
+#define V2M_MSI_SETSPI_NS 0x040
+#define V2M_MSI_IIDR 0xFCC
+#define V2M_IIDR0 0xFD0
+#define V2M_IIDR11 0xFFC
+
+#define PRODUCT_ID_QEMU 0x51 /* ASCII code Q */
+
+typedef struct ARMGICv2mState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq spi[GICV2M_NUM_SPI_MAX];
+
+ uint32_t base_spi;
+ uint32_t num_spi;
+} ARMGICv2mState;
+
+static void gicv2m_set_irq(void *opaque, int irq)
+{
+ ARMGICv2mState *s = (ARMGICv2mState *)opaque;
+
+ qemu_irq_pulse(s->spi[irq]);
+}
+
+static uint64_t gicv2m_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ ARMGICv2mState *s = (ARMGICv2mState *)opaque;
+ uint32_t val;
+
+ if (size != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR, "gicv2m_read: bad size %u\n", size);
+ return 0;
+ }
+
+ switch (offset) {
+ case V2M_MSI_TYPER:
+ val = (s->base_spi + 32) << 16;
+ val |= s->num_spi;
+ return val;
+ case V2M_MSI_IIDR:
+ /* We don't have any valid implementor so we leave that field as zero
+ * and we return 0 in the arch revision as per the spec.
+ */
+ return (PRODUCT_ID_QEMU << 20);
+ case V2M_IIDR0 ... V2M_IIDR11:
+ /* We do not implement any optional identification registers and the
+ * mandatory MSI_PIDR2 register reads as 0x0, so we capture all
+ * implementation defined registers here.
+ */
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "gicv2m_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void gicv2m_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ ARMGICv2mState *s = (ARMGICv2mState *)opaque;
+
+ if (size != 2 && size != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR, "gicv2m_write: bad size %u\n", size);
+ return;
+ }
+
+ switch (offset) {
+ case V2M_MSI_SETSPI_NS: {
+ int spi;
+
+ spi = (value & 0x3ff) - (s->base_spi + 32);
+ if (spi >= 0 && spi < s->num_spi) {
+ gicv2m_set_irq(s, spi);
+ }
+ return;
+ }
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "gicv2m_write: Bad offset %x\n", (int)offset);
+ }
+}
+
+static const MemoryRegionOps gicv2m_ops = {
+ .read = gicv2m_read,
+ .write = gicv2m_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void gicv2m_realize(DeviceState *dev, Error **errp)
+{
+ ARMGICv2mState *s = ARM_GICV2M(dev);
+ int i;
+
+ if (s->num_spi > GICV2M_NUM_SPI_MAX) {
+ error_setg(errp,
+ "requested %u SPIs exceeds GICv2m frame maximum %d",
+ s->num_spi, GICV2M_NUM_SPI_MAX);
+ return;
+ }
+
+ if (s->base_spi + 32 > 1020 - s->num_spi) {
+ error_setg(errp,
+ "requested base SPI %u+%u exceeds max. number 1020",
+ s->base_spi + 32, s->num_spi);
+ return;
+ }
+
+ for (i = 0; i < s->num_spi; i++) {
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->spi[i]);
+ }
+
+ msi_supported = true;
+ kvm_gsi_direct_mapping = true;
+ kvm_msi_via_irqfd_allowed = kvm_irqfds_enabled();
+}
+
+static void gicv2m_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARMGICv2mState *s = ARM_GICV2M(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &gicv2m_ops, s,
+ "gicv2m", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static Property gicv2m_properties[] = {
+ DEFINE_PROP_UINT32("base-spi", ARMGICv2mState, base_spi, 0),
+ DEFINE_PROP_UINT32("num-spi", ARMGICv2mState, num_spi, 64),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void gicv2m_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->props = gicv2m_properties;
+ dc->realize = gicv2m_realize;
+}
+
+static const TypeInfo gicv2m_info = {
+ .name = TYPE_ARM_GICV2M,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARMGICv2mState),
+ .instance_init = gicv2m_init,
+ .class_init = gicv2m_class_init,
+};
+
+static void gicv2m_register_types(void)
+{
+ type_register_static(&gicv2m_info);
+}
+
+type_init(gicv2m_register_types)
diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
new file mode 100644
index 00000000..e13b729e
--- /dev/null
+++ b/hw/intc/armv7m_nvic.c
@@ -0,0 +1,572 @@
+/*
+ * ARM Nested Vectored Interrupt Controller
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ *
+ * The ARMv7M System controller is fairly tightly tied in with the
+ * NVIC. Much of that is also implemented here.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/arm/arm.h"
+#include "exec/address-spaces.h"
+#include "gic_internal.h"
+
+typedef struct {
+ GICState gic;
+ struct {
+ uint32_t control;
+ uint32_t reload;
+ int64_t tick;
+ QEMUTimer *timer;
+ } systick;
+ MemoryRegion sysregmem;
+ MemoryRegion gic_iomem_alias;
+ MemoryRegion container;
+ uint32_t num_irq;
+} nvic_state;
+
+#define TYPE_NVIC "armv7m_nvic"
+/**
+ * NVICClass:
+ * @parent_reset: the parent class' reset handler.
+ *
+ * A model of the v7M NVIC and System Controller
+ */
+typedef struct NVICClass {
+ /*< private >*/
+ ARMGICClass parent_class;
+ /*< public >*/
+ DeviceRealize parent_realize;
+ void (*parent_reset)(DeviceState *dev);
+} NVICClass;
+
+#define NVIC_CLASS(klass) \
+ OBJECT_CLASS_CHECK(NVICClass, (klass), TYPE_NVIC)
+#define NVIC_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(NVICClass, (obj), TYPE_NVIC)
+#define NVIC(obj) \
+ OBJECT_CHECK(nvic_state, (obj), TYPE_NVIC)
+
+static const uint8_t nvic_id[] = {
+ 0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1
+};
+
+/* qemu timers run at 1GHz. We want something closer to 1MHz. */
+#define SYSTICK_SCALE 1000ULL
+
+#define SYSTICK_ENABLE (1 << 0)
+#define SYSTICK_TICKINT (1 << 1)
+#define SYSTICK_CLKSOURCE (1 << 2)
+#define SYSTICK_COUNTFLAG (1 << 16)
+
+int system_clock_scale;
+
+/* Conversion factor from qemu timer to SysTick frequencies. */
+static inline int64_t systick_scale(nvic_state *s)
+{
+ if (s->systick.control & SYSTICK_CLKSOURCE)
+ return system_clock_scale;
+ else
+ return 1000;
+}
+
+static void systick_reload(nvic_state *s, int reset)
+{
+ /* The Cortex-M3 Devices Generic User Guide says that "When the
+ * ENABLE bit is set to 1, the counter loads the RELOAD value from the
+ * SYST RVR register and then counts down". So, we need to check the
+ * ENABLE bit before reloading the value.
+ */
+ if ((s->systick.control & SYSTICK_ENABLE) == 0) {
+ return;
+ }
+
+ if (reset)
+ s->systick.tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->systick.tick += (s->systick.reload + 1) * systick_scale(s);
+ timer_mod(s->systick.timer, s->systick.tick);
+}
+
+static void systick_timer_tick(void * opaque)
+{
+ nvic_state *s = (nvic_state *)opaque;
+ s->systick.control |= SYSTICK_COUNTFLAG;
+ if (s->systick.control & SYSTICK_TICKINT) {
+ /* Trigger the interrupt. */
+ armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK);
+ }
+ if (s->systick.reload == 0) {
+ s->systick.control &= ~SYSTICK_ENABLE;
+ } else {
+ systick_reload(s, 0);
+ }
+}
+
+static void systick_reset(nvic_state *s)
+{
+ s->systick.control = 0;
+ s->systick.reload = 0;
+ s->systick.tick = 0;
+ timer_del(s->systick.timer);
+}
+
+/* The external routines use the hardware vector numbering, ie. the first
+ IRQ is #16. The internal GIC routines use #32 as the first IRQ. */
+void armv7m_nvic_set_pending(void *opaque, int irq)
+{
+ nvic_state *s = (nvic_state *)opaque;
+ if (irq >= 16)
+ irq += 16;
+ gic_set_pending_private(&s->gic, 0, irq);
+}
+
+/* Make pending IRQ active. */
+int armv7m_nvic_acknowledge_irq(void *opaque)
+{
+ nvic_state *s = (nvic_state *)opaque;
+ uint32_t irq;
+
+ irq = gic_acknowledge_irq(&s->gic, 0, MEMTXATTRS_UNSPECIFIED);
+ if (irq == 1023)
+ hw_error("Interrupt but no vector\n");
+ if (irq >= 32)
+ irq -= 16;
+ return irq;
+}
+
+void armv7m_nvic_complete_irq(void *opaque, int irq)
+{
+ nvic_state *s = (nvic_state *)opaque;
+ if (irq >= 16)
+ irq += 16;
+ gic_complete_irq(&s->gic, 0, irq, MEMTXATTRS_UNSPECIFIED);
+}
+
+static uint32_t nvic_readl(nvic_state *s, uint32_t offset)
+{
+ ARMCPU *cpu;
+ uint32_t val;
+ int irq;
+
+ switch (offset) {
+ case 4: /* Interrupt Control Type. */
+ return (s->num_irq / 32) - 1;
+ case 0x10: /* SysTick Control and Status. */
+ val = s->systick.control;
+ s->systick.control &= ~SYSTICK_COUNTFLAG;
+ return val;
+ case 0x14: /* SysTick Reload Value. */
+ return s->systick.reload;
+ case 0x18: /* SysTick Current Value. */
+ {
+ int64_t t;
+ if ((s->systick.control & SYSTICK_ENABLE) == 0)
+ return 0;
+ t = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ if (t >= s->systick.tick)
+ return 0;
+ val = ((s->systick.tick - (t + 1)) / systick_scale(s)) + 1;
+ /* The interrupt in triggered when the timer reaches zero.
+ However the counter is not reloaded until the next clock
+ tick. This is a hack to return zero during the first tick. */
+ if (val > s->systick.reload)
+ val = 0;
+ return val;
+ }
+ case 0x1c: /* SysTick Calibration Value. */
+ return 10000;
+ case 0xd00: /* CPUID Base. */
+ cpu = ARM_CPU(current_cpu);
+ return cpu->midr;
+ case 0xd04: /* Interrupt Control State. */
+ /* VECTACTIVE */
+ val = s->gic.running_irq[0];
+ if (val == 1023) {
+ val = 0;
+ } else if (val >= 32) {
+ val -= 16;
+ }
+ /* RETTOBASE */
+ if (s->gic.running_irq[0] == 1023
+ || s->gic.last_active[s->gic.running_irq[0]][0] == 1023) {
+ val |= (1 << 11);
+ }
+ /* VECTPENDING */
+ if (s->gic.current_pending[0] != 1023)
+ val |= (s->gic.current_pending[0] << 12);
+ /* ISRPENDING */
+ for (irq = 32; irq < s->num_irq; irq++) {
+ if (s->gic.irq_state[irq].pending) {
+ val |= (1 << 22);
+ break;
+ }
+ }
+ /* PENDSTSET */
+ if (s->gic.irq_state[ARMV7M_EXCP_SYSTICK].pending)
+ val |= (1 << 26);
+ /* PENDSVSET */
+ if (s->gic.irq_state[ARMV7M_EXCP_PENDSV].pending)
+ val |= (1 << 28);
+ /* NMIPENDSET */
+ if (s->gic.irq_state[ARMV7M_EXCP_NMI].pending)
+ val |= (1 << 31);
+ return val;
+ case 0xd08: /* Vector Table Offset. */
+ cpu = ARM_CPU(current_cpu);
+ return cpu->env.v7m.vecbase;
+ case 0xd0c: /* Application Interrupt/Reset Control. */
+ return 0xfa050000;
+ case 0xd10: /* System Control. */
+ /* TODO: Implement SLEEPONEXIT. */
+ return 0;
+ case 0xd14: /* Configuration Control. */
+ /* TODO: Implement Configuration Control bits. */
+ return 0;
+ case 0xd24: /* System Handler Status. */
+ val = 0;
+ if (s->gic.irq_state[ARMV7M_EXCP_MEM].active) val |= (1 << 0);
+ if (s->gic.irq_state[ARMV7M_EXCP_BUS].active) val |= (1 << 1);
+ if (s->gic.irq_state[ARMV7M_EXCP_USAGE].active) val |= (1 << 3);
+ if (s->gic.irq_state[ARMV7M_EXCP_SVC].active) val |= (1 << 7);
+ if (s->gic.irq_state[ARMV7M_EXCP_DEBUG].active) val |= (1 << 8);
+ if (s->gic.irq_state[ARMV7M_EXCP_PENDSV].active) val |= (1 << 10);
+ if (s->gic.irq_state[ARMV7M_EXCP_SYSTICK].active) val |= (1 << 11);
+ if (s->gic.irq_state[ARMV7M_EXCP_USAGE].pending) val |= (1 << 12);
+ if (s->gic.irq_state[ARMV7M_EXCP_MEM].pending) val |= (1 << 13);
+ if (s->gic.irq_state[ARMV7M_EXCP_BUS].pending) val |= (1 << 14);
+ if (s->gic.irq_state[ARMV7M_EXCP_SVC].pending) val |= (1 << 15);
+ if (s->gic.irq_state[ARMV7M_EXCP_MEM].enabled) val |= (1 << 16);
+ if (s->gic.irq_state[ARMV7M_EXCP_BUS].enabled) val |= (1 << 17);
+ if (s->gic.irq_state[ARMV7M_EXCP_USAGE].enabled) val |= (1 << 18);
+ return val;
+ case 0xd28: /* Configurable Fault Status. */
+ /* TODO: Implement Fault Status. */
+ qemu_log_mask(LOG_UNIMP, "Configurable Fault Status unimplemented\n");
+ return 0;
+ case 0xd2c: /* Hard Fault Status. */
+ case 0xd30: /* Debug Fault Status. */
+ case 0xd34: /* Mem Manage Address. */
+ case 0xd38: /* Bus Fault Address. */
+ case 0xd3c: /* Aux Fault Status. */
+ /* TODO: Implement fault status registers. */
+ qemu_log_mask(LOG_UNIMP, "Fault status registers unimplemented\n");
+ return 0;
+ case 0xd40: /* PFR0. */
+ return 0x00000030;
+ case 0xd44: /* PRF1. */
+ return 0x00000200;
+ case 0xd48: /* DFR0. */
+ return 0x00100000;
+ case 0xd4c: /* AFR0. */
+ return 0x00000000;
+ case 0xd50: /* MMFR0. */
+ return 0x00000030;
+ case 0xd54: /* MMFR1. */
+ return 0x00000000;
+ case 0xd58: /* MMFR2. */
+ return 0x00000000;
+ case 0xd5c: /* MMFR3. */
+ return 0x00000000;
+ case 0xd60: /* ISAR0. */
+ return 0x01141110;
+ case 0xd64: /* ISAR1. */
+ return 0x02111000;
+ case 0xd68: /* ISAR2. */
+ return 0x21112231;
+ case 0xd6c: /* ISAR3. */
+ return 0x01111110;
+ case 0xd70: /* ISAR4. */
+ return 0x01310102;
+ /* TODO: Implement debug registers. */
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "NVIC: Bad read offset 0x%x\n", offset);
+ return 0;
+ }
+}
+
+static void nvic_writel(nvic_state *s, uint32_t offset, uint32_t value)
+{
+ ARMCPU *cpu;
+ uint32_t oldval;
+ switch (offset) {
+ case 0x10: /* SysTick Control and Status. */
+ oldval = s->systick.control;
+ s->systick.control &= 0xfffffff8;
+ s->systick.control |= value & 7;
+ if ((oldval ^ value) & SYSTICK_ENABLE) {
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ if (value & SYSTICK_ENABLE) {
+ if (s->systick.tick) {
+ s->systick.tick += now;
+ timer_mod(s->systick.timer, s->systick.tick);
+ } else {
+ systick_reload(s, 1);
+ }
+ } else {
+ timer_del(s->systick.timer);
+ s->systick.tick -= now;
+ if (s->systick.tick < 0)
+ s->systick.tick = 0;
+ }
+ } else if ((oldval ^ value) & SYSTICK_CLKSOURCE) {
+ /* This is a hack. Force the timer to be reloaded
+ when the reference clock is changed. */
+ systick_reload(s, 1);
+ }
+ break;
+ case 0x14: /* SysTick Reload Value. */
+ s->systick.reload = value;
+ break;
+ case 0x18: /* SysTick Current Value. Writes reload the timer. */
+ systick_reload(s, 1);
+ s->systick.control &= ~SYSTICK_COUNTFLAG;
+ break;
+ case 0xd04: /* Interrupt Control State. */
+ if (value & (1 << 31)) {
+ armv7m_nvic_set_pending(s, ARMV7M_EXCP_NMI);
+ }
+ if (value & (1 << 28)) {
+ armv7m_nvic_set_pending(s, ARMV7M_EXCP_PENDSV);
+ } else if (value & (1 << 27)) {
+ s->gic.irq_state[ARMV7M_EXCP_PENDSV].pending = 0;
+ gic_update(&s->gic);
+ }
+ if (value & (1 << 26)) {
+ armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK);
+ } else if (value & (1 << 25)) {
+ s->gic.irq_state[ARMV7M_EXCP_SYSTICK].pending = 0;
+ gic_update(&s->gic);
+ }
+ break;
+ case 0xd08: /* Vector Table Offset. */
+ cpu = ARM_CPU(current_cpu);
+ cpu->env.v7m.vecbase = value & 0xffffff80;
+ break;
+ case 0xd0c: /* Application Interrupt/Reset Control. */
+ if ((value >> 16) == 0x05fa) {
+ if (value & 2) {
+ qemu_log_mask(LOG_UNIMP, "VECTCLRACTIVE unimplemented\n");
+ }
+ if (value & 5) {
+ qemu_log_mask(LOG_UNIMP, "AIRCR system reset unimplemented\n");
+ }
+ if (value & 0x700) {
+ qemu_log_mask(LOG_UNIMP, "PRIGROUP unimplemented\n");
+ }
+ }
+ break;
+ case 0xd10: /* System Control. */
+ case 0xd14: /* Configuration Control. */
+ /* TODO: Implement control registers. */
+ qemu_log_mask(LOG_UNIMP, "NVIC: SCR and CCR unimplemented\n");
+ break;
+ case 0xd24: /* System Handler Control. */
+ /* TODO: Real hardware allows you to set/clear the active bits
+ under some circumstances. We don't implement this. */
+ s->gic.irq_state[ARMV7M_EXCP_MEM].enabled = (value & (1 << 16)) != 0;
+ s->gic.irq_state[ARMV7M_EXCP_BUS].enabled = (value & (1 << 17)) != 0;
+ s->gic.irq_state[ARMV7M_EXCP_USAGE].enabled = (value & (1 << 18)) != 0;
+ break;
+ case 0xd28: /* Configurable Fault Status. */
+ case 0xd2c: /* Hard Fault Status. */
+ case 0xd30: /* Debug Fault Status. */
+ case 0xd34: /* Mem Manage Address. */
+ case 0xd38: /* Bus Fault Address. */
+ case 0xd3c: /* Aux Fault Status. */
+ qemu_log_mask(LOG_UNIMP,
+ "NVIC: fault status registers unimplemented\n");
+ break;
+ case 0xf00: /* Software Triggered Interrupt Register */
+ if ((value & 0x1ff) < s->num_irq) {
+ gic_set_pending_private(&s->gic, 0, value & 0x1ff);
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "NVIC: Bad write offset 0x%x\n", offset);
+ }
+}
+
+static uint64_t nvic_sysreg_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ nvic_state *s = (nvic_state *)opaque;
+ uint32_t offset = addr;
+ int i;
+ uint32_t val;
+
+ switch (offset) {
+ case 0xd18 ... 0xd23: /* System Handler Priority. */
+ val = 0;
+ for (i = 0; i < size; i++) {
+ val |= s->gic.priority1[(offset - 0xd14) + i][0] << (i * 8);
+ }
+ return val;
+ case 0xfe0 ... 0xfff: /* ID. */
+ if (offset & 3) {
+ return 0;
+ }
+ return nvic_id[(offset - 0xfe0) >> 2];
+ }
+ if (size == 4) {
+ return nvic_readl(s, offset);
+ }
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "NVIC: Bad read of size %d at offset 0x%x\n", size, offset);
+ return 0;
+}
+
+static void nvic_sysreg_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ nvic_state *s = (nvic_state *)opaque;
+ uint32_t offset = addr;
+ int i;
+
+ switch (offset) {
+ case 0xd18 ... 0xd23: /* System Handler Priority. */
+ for (i = 0; i < size; i++) {
+ s->gic.priority1[(offset - 0xd14) + i][0] =
+ (value >> (i * 8)) & 0xff;
+ }
+ gic_update(&s->gic);
+ return;
+ }
+ if (size == 4) {
+ nvic_writel(s, offset, value);
+ return;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "NVIC: Bad write of size %d at offset 0x%x\n", size, offset);
+}
+
+static const MemoryRegionOps nvic_sysreg_ops = {
+ .read = nvic_sysreg_read,
+ .write = nvic_sysreg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_nvic = {
+ .name = "armv7m_nvic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(systick.control, nvic_state),
+ VMSTATE_UINT32(systick.reload, nvic_state),
+ VMSTATE_INT64(systick.tick, nvic_state),
+ VMSTATE_TIMER_PTR(systick.timer, nvic_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void armv7m_nvic_reset(DeviceState *dev)
+{
+ nvic_state *s = NVIC(dev);
+ NVICClass *nc = NVIC_GET_CLASS(s);
+ nc->parent_reset(dev);
+ /* Common GIC reset resets to disabled; the NVIC doesn't have
+ * per-CPU interfaces so mark our non-existent CPU interface
+ * as enabled by default, and with a priority mask which allows
+ * all interrupts through.
+ */
+ s->gic.cpu_ctlr[0] = GICC_CTLR_EN_GRP0;
+ s->gic.priority_mask[0] = 0x100;
+ /* The NVIC as a whole is always enabled. */
+ s->gic.ctlr = 1;
+ systick_reset(s);
+}
+
+static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
+{
+ nvic_state *s = NVIC(dev);
+ NVICClass *nc = NVIC_GET_CLASS(s);
+ Error *local_err = NULL;
+
+ /* The NVIC always has only one CPU */
+ s->gic.num_cpu = 1;
+ /* Tell the common code we're an NVIC */
+ s->gic.revision = 0xffffffff;
+ s->num_irq = s->gic.num_irq;
+ nc->parent_realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ gic_init_irqs_and_distributor(&s->gic);
+ /* The NVIC and system controller register area looks like this:
+ * 0..0xff : system control registers, including systick
+ * 0x100..0xcff : GIC-like registers
+ * 0xd00..0xfff : system control registers
+ * We use overlaying to put the GIC like registers
+ * over the top of the system control register region.
+ */
+ memory_region_init(&s->container, OBJECT(s), "nvic", 0x1000);
+ /* The system register region goes at the bottom of the priority
+ * stack as it covers the whole page.
+ */
+ memory_region_init_io(&s->sysregmem, OBJECT(s), &nvic_sysreg_ops, s,
+ "nvic_sysregs", 0x1000);
+ memory_region_add_subregion(&s->container, 0, &s->sysregmem);
+ /* Alias the GIC region so we can get only the section of it
+ * we need, and layer it on top of the system register region.
+ */
+ memory_region_init_alias(&s->gic_iomem_alias, OBJECT(s),
+ "nvic-gic", &s->gic.iomem,
+ 0x100, 0xc00);
+ memory_region_add_subregion_overlap(&s->container, 0x100,
+ &s->gic_iomem_alias, 1);
+ /* Map the whole thing into system memory at the location required
+ * by the v7M architecture.
+ */
+ memory_region_add_subregion(get_system_memory(), 0xe000e000, &s->container);
+ s->systick.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, systick_timer_tick, s);
+}
+
+static void armv7m_nvic_instance_init(Object *obj)
+{
+ /* We have a different default value for the num-irq property
+ * than our superclass. This function runs after qdev init
+ * has set the defaults from the Property array and before
+ * any user-specified property setting, so just modify the
+ * value in the GICState struct.
+ */
+ GICState *s = ARM_GIC_COMMON(obj);
+ /* The ARM v7m may have anything from 0 to 496 external interrupt
+ * IRQ lines. We default to 64. Other boards may differ and should
+ * set the num-irq property appropriately.
+ */
+ s->num_irq = 64;
+}
+
+static void armv7m_nvic_class_init(ObjectClass *klass, void *data)
+{
+ NVICClass *nc = NVIC_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ nc->parent_reset = dc->reset;
+ nc->parent_realize = dc->realize;
+ dc->vmsd = &vmstate_nvic;
+ dc->reset = armv7m_nvic_reset;
+ dc->realize = armv7m_nvic_realize;
+}
+
+static const TypeInfo armv7m_nvic_info = {
+ .name = TYPE_NVIC,
+ .parent = TYPE_ARM_GIC_COMMON,
+ .instance_init = armv7m_nvic_instance_init,
+ .instance_size = sizeof(nvic_state),
+ .class_init = armv7m_nvic_class_init,
+ .class_size = sizeof(NVICClass),
+};
+
+static void armv7m_nvic_register_types(void)
+{
+ type_register_static(&armv7m_nvic_info);
+}
+
+type_init(armv7m_nvic_register_types)
diff --git a/hw/intc/etraxfs_pic.c b/hw/intc/etraxfs_pic.c
new file mode 100644
index 00000000..bd588681
--- /dev/null
+++ b/hw/intc/etraxfs_pic.c
@@ -0,0 +1,193 @@
+/*
+ * QEMU ETRAX Interrupt Controller.
+ *
+ * Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+//#include "pc.h"
+//#include "etraxfs.h"
+
+#define D(x)
+
+#define R_RW_MASK 0
+#define R_R_VECT 1
+#define R_R_MASKED_VECT 2
+#define R_R_NMI 3
+#define R_R_GURU 4
+#define R_MAX 5
+
+#define TYPE_ETRAX_FS_PIC "etraxfs,pic"
+#define ETRAX_FS_PIC(obj) \
+ OBJECT_CHECK(struct etrax_pic, (obj), TYPE_ETRAX_FS_PIC)
+
+struct etrax_pic
+{
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ void *interrupt_vector;
+ qemu_irq parent_irq;
+ qemu_irq parent_nmi;
+ uint32_t regs[R_MAX];
+};
+
+static void pic_update(struct etrax_pic *fs)
+{
+ uint32_t vector = 0;
+ int i;
+
+ fs->regs[R_R_MASKED_VECT] = fs->regs[R_R_VECT] & fs->regs[R_RW_MASK];
+
+ /* The ETRAX interrupt controller signals interrupts to the core
+ through an interrupt request wire and an irq vector bus. If
+ multiple interrupts are simultaneously active it chooses vector
+ 0x30 and lets the sw choose the priorities. */
+ if (fs->regs[R_R_MASKED_VECT]) {
+ uint32_t mv = fs->regs[R_R_MASKED_VECT];
+ for (i = 0; i < 31; i++) {
+ if (mv & 1) {
+ vector = 0x31 + i;
+ /* Check for multiple interrupts. */
+ if (mv > 1)
+ vector = 0x30;
+ break;
+ }
+ mv >>= 1;
+ }
+ }
+
+ if (fs->interrupt_vector) {
+ /* hack alert: ptr property */
+ *(uint32_t*)(fs->interrupt_vector) = vector;
+ }
+ qemu_set_irq(fs->parent_irq, !!vector);
+}
+
+static uint64_t
+pic_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ struct etrax_pic *fs = opaque;
+ uint32_t rval;
+
+ rval = fs->regs[addr >> 2];
+ D(printf("%s %x=%x\n", __func__, addr, rval));
+ return rval;
+}
+
+static void pic_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned int size)
+{
+ struct etrax_pic *fs = opaque;
+ D(printf("%s addr=%x val=%x\n", __func__, addr, value));
+
+ if (addr == R_RW_MASK) {
+ fs->regs[R_RW_MASK] = value;
+ pic_update(fs);
+ }
+}
+
+static const MemoryRegionOps pic_ops = {
+ .read = pic_read,
+ .write = pic_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void nmi_handler(void *opaque, int irq, int level)
+{
+ struct etrax_pic *fs = (void *)opaque;
+ uint32_t mask;
+
+ mask = 1 << irq;
+ if (level)
+ fs->regs[R_R_NMI] |= mask;
+ else
+ fs->regs[R_R_NMI] &= ~mask;
+
+ qemu_set_irq(fs->parent_nmi, !!fs->regs[R_R_NMI]);
+}
+
+static void irq_handler(void *opaque, int irq, int level)
+{
+ struct etrax_pic *fs = (void *)opaque;
+
+ if (irq >= 30) {
+ nmi_handler(opaque, irq, level);
+ return;
+ }
+
+ irq -= 1;
+ fs->regs[R_R_VECT] &= ~(1 << irq);
+ fs->regs[R_R_VECT] |= (!!level << irq);
+ pic_update(fs);
+}
+
+static int etraxfs_pic_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ struct etrax_pic *s = ETRAX_FS_PIC(dev);
+
+ qdev_init_gpio_in(dev, irq_handler, 32);
+ sysbus_init_irq(sbd, &s->parent_irq);
+ sysbus_init_irq(sbd, &s->parent_nmi);
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &pic_ops, s,
+ "etraxfs-pic", R_MAX * 4);
+ sysbus_init_mmio(sbd, &s->mmio);
+ return 0;
+}
+
+static Property etraxfs_pic_properties[] = {
+ DEFINE_PROP_PTR("interrupt_vector", struct etrax_pic, interrupt_vector),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void etraxfs_pic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = etraxfs_pic_init;
+ dc->props = etraxfs_pic_properties;
+ /*
+ * Note: pointer property "interrupt_vector" may remain null, thus
+ * no need for dc->cannot_instantiate_with_device_add_yet = true;
+ */
+}
+
+static const TypeInfo etraxfs_pic_info = {
+ .name = TYPE_ETRAX_FS_PIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct etrax_pic),
+ .class_init = etraxfs_pic_class_init,
+};
+
+static void etraxfs_pic_register_types(void)
+{
+ type_register_static(&etraxfs_pic_info);
+}
+
+type_init(etraxfs_pic_register_types)
diff --git a/hw/intc/exynos4210_combiner.c b/hw/intc/exynos4210_combiner.c
new file mode 100644
index 00000000..a6b70289
--- /dev/null
+++ b/hw/intc/exynos4210_combiner.c
@@ -0,0 +1,458 @@
+/*
+ * Samsung exynos4210 Interrupt Combiner
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Exynos4210 Combiner represents an OR gate for SOC's IRQ lines. It combines
+ * IRQ sources into groups and provides signal output to GIC from each group. It
+ * is driven by common mask and enable/disable logic. Take a note that not all
+ * IRQs are passed to GIC through Combiner.
+ */
+
+#include "hw/sysbus.h"
+
+#include "hw/arm/exynos4210.h"
+
+//#define DEBUG_COMBINER
+
+#ifdef DEBUG_COMBINER
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define IIC_NGRP 64 /* Internal Interrupt Combiner
+ Groups number */
+#define IIC_NIRQ (IIC_NGRP * 8)/* Internal Interrupt Combiner
+ Interrupts number */
+#define IIC_REGION_SIZE 0x108 /* Size of memory mapped region */
+#define IIC_REGSET_SIZE 0x41
+
+/*
+ * State for each output signal of internal combiner
+ */
+typedef struct CombinerGroupState {
+ uint8_t src_mask; /* 1 - source enabled, 0 - disabled */
+ uint8_t src_pending; /* Pending source interrupts before masking */
+} CombinerGroupState;
+
+#define TYPE_EXYNOS4210_COMBINER "exynos4210.combiner"
+#define EXYNOS4210_COMBINER(obj) \
+ OBJECT_CHECK(Exynos4210CombinerState, (obj), TYPE_EXYNOS4210_COMBINER)
+
+typedef struct Exynos4210CombinerState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ struct CombinerGroupState group[IIC_NGRP];
+ uint32_t reg_set[IIC_REGSET_SIZE];
+ uint32_t icipsr[2];
+ uint32_t external; /* 1 means that this combiner is external */
+
+ qemu_irq output_irq[IIC_NGRP];
+} Exynos4210CombinerState;
+
+static const VMStateDescription vmstate_exynos4210_combiner_group_state = {
+ .name = "exynos4210.combiner.groupstate",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(src_mask, CombinerGroupState),
+ VMSTATE_UINT8(src_pending, CombinerGroupState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_combiner = {
+ .name = "exynos4210.combiner",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0,
+ vmstate_exynos4210_combiner_group_state, CombinerGroupState),
+ VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState,
+ IIC_REGSET_SIZE),
+ VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2),
+ VMSTATE_UINT32(external, Exynos4210CombinerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/*
+ * Get Combiner input GPIO into irqs structure
+ */
+void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev,
+ int ext)
+{
+ int n;
+ int bit;
+ int max;
+ qemu_irq *irq;
+
+ max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ :
+ EXYNOS4210_MAX_INT_COMBINER_IN_IRQ;
+ irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq;
+
+ /*
+ * Some IRQs of Int/External Combiner are going to two Combiners groups,
+ * so let split them.
+ */
+ for (n = 0; n < max; n++) {
+
+ bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+
+ switch (n) {
+ /* MDNIE_LCD1 INTG1 */
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]);
+ continue;
+
+ /* TMU INTG3 */
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]);
+ continue;
+
+ /* LCD1 INTG12 */
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]);
+ continue;
+
+ /* Multi-Core Timer INTG12 */
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+
+ /* Multi-Core Timer INTG35 */
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+
+ /* Multi-Core Timer INTG51 */
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+
+ /* Multi-Core Timer INTG53 */
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ }
+
+ irq[n] = qdev_get_gpio_in(dev, n);
+ }
+}
+
+static uint64_t
+exynos4210_combiner_read(void *opaque, hwaddr offset, unsigned size)
+{
+ struct Exynos4210CombinerState *s =
+ (struct Exynos4210CombinerState *)opaque;
+ uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and
+ get a start of corresponding group quad */
+ uint32_t grp_quad_base_n; /* Base of group quad */
+ uint32_t reg_n; /* Register number inside the quad */
+ uint32_t val;
+
+ req_quad_base_n = offset >> 4;
+ grp_quad_base_n = req_quad_base_n << 2;
+ reg_n = (offset - (req_quad_base_n << 4)) >> 2;
+
+ if (req_quad_base_n >= IIC_NGRP) {
+ /* Read of ICIPSR register */
+ return s->icipsr[reg_n];
+ }
+
+ val = 0;
+
+ switch (reg_n) {
+ /* IISTR */
+ case 2:
+ val |= s->group[grp_quad_base_n].src_pending;
+ val |= s->group[grp_quad_base_n + 1].src_pending << 8;
+ val |= s->group[grp_quad_base_n + 2].src_pending << 16;
+ val |= s->group[grp_quad_base_n + 3].src_pending << 24;
+ break;
+ /* IIMSR */
+ case 3:
+ val |= s->group[grp_quad_base_n].src_mask &
+ s->group[grp_quad_base_n].src_pending;
+ val |= (s->group[grp_quad_base_n + 1].src_mask &
+ s->group[grp_quad_base_n + 1].src_pending) << 8;
+ val |= (s->group[grp_quad_base_n + 2].src_mask &
+ s->group[grp_quad_base_n + 2].src_pending) << 16;
+ val |= (s->group[grp_quad_base_n + 3].src_mask &
+ s->group[grp_quad_base_n + 3].src_pending) << 24;
+ break;
+ default:
+ if (offset >> 2 >= IIC_REGSET_SIZE) {
+ hw_error("exynos4210.combiner: overflow of reg_set by 0x"
+ TARGET_FMT_plx "offset\n", offset);
+ }
+ val = s->reg_set[offset >> 2];
+ return 0;
+ }
+ return val;
+}
+
+static void exynos4210_combiner_update(void *opaque, uint8_t group_n)
+{
+ struct Exynos4210CombinerState *s =
+ (struct Exynos4210CombinerState *)opaque;
+
+ /* Send interrupt if needed */
+ if (s->group[group_n].src_mask & s->group[group_n].src_pending) {
+#ifdef DEBUG_COMBINER
+ if (group_n != 26) {
+ /* skip uart */
+ DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
+ }
+#endif
+
+ /* Set Combiner interrupt pending status after masking */
+ if (group_n >= 32) {
+ s->icipsr[1] |= 1 << (group_n - 32);
+ } else {
+ s->icipsr[0] |= 1 << group_n;
+ }
+
+ qemu_irq_raise(s->output_irq[group_n]);
+ } else {
+#ifdef DEBUG_COMBINER
+ if (group_n != 26) {
+ /* skip uart */
+ DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
+ }
+#endif
+
+ /* Set Combiner interrupt pending status after masking */
+ if (group_n >= 32) {
+ s->icipsr[1] &= ~(1 << (group_n - 32));
+ } else {
+ s->icipsr[0] &= ~(1 << group_n);
+ }
+
+ qemu_irq_lower(s->output_irq[group_n]);
+ }
+}
+
+static void exynos4210_combiner_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ struct Exynos4210CombinerState *s =
+ (struct Exynos4210CombinerState *)opaque;
+ uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and
+ get a start of corresponding group quad */
+ uint32_t grp_quad_base_n; /* Base of group quad */
+ uint32_t reg_n; /* Register number inside the quad */
+
+ req_quad_base_n = offset >> 4;
+ grp_quad_base_n = req_quad_base_n << 2;
+ reg_n = (offset - (req_quad_base_n << 4)) >> 2;
+
+ if (req_quad_base_n >= IIC_NGRP) {
+ hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ return;
+ }
+
+ if (reg_n > 1) {
+ hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ return;
+ }
+
+ if (offset >> 2 >= IIC_REGSET_SIZE) {
+ hw_error("exynos4210.combiner: overflow of reg_set by 0x"
+ TARGET_FMT_plx "offset\n", offset);
+ }
+ s->reg_set[offset >> 2] = val;
+
+ switch (reg_n) {
+ /* IIESR */
+ case 0:
+ /* FIXME: what if irq is pending, allowed by mask, and we allow it
+ * again. Interrupt will rise again! */
+
+ DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n",
+ s->external ? "EXT" : "INT",
+ grp_quad_base_n,
+ grp_quad_base_n + 1,
+ grp_quad_base_n + 2,
+ grp_quad_base_n + 3);
+
+ /* Enable interrupt sources */
+ s->group[grp_quad_base_n].src_mask |= val & 0xFF;
+ s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8;
+ s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16;
+ s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24;
+
+ exynos4210_combiner_update(s, grp_quad_base_n);
+ exynos4210_combiner_update(s, grp_quad_base_n + 1);
+ exynos4210_combiner_update(s, grp_quad_base_n + 2);
+ exynos4210_combiner_update(s, grp_quad_base_n + 3);
+ break;
+ /* IIECR */
+ case 1:
+ DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n",
+ s->external ? "EXT" : "INT",
+ grp_quad_base_n,
+ grp_quad_base_n + 1,
+ grp_quad_base_n + 2,
+ grp_quad_base_n + 3);
+
+ /* Disable interrupt sources */
+ s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF);
+ s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8);
+ s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16);
+ s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24);
+
+ exynos4210_combiner_update(s, grp_quad_base_n);
+ exynos4210_combiner_update(s, grp_quad_base_n + 1);
+ exynos4210_combiner_update(s, grp_quad_base_n + 2);
+ exynos4210_combiner_update(s, grp_quad_base_n + 3);
+ break;
+ default:
+ hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+/* Get combiner group and bit from irq number */
+static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit)
+{
+ *bit = irq - ((irq >> 3) << 3);
+ return irq >> 3;
+}
+
+/* Process a change in an external IRQ input. */
+static void exynos4210_combiner_handler(void *opaque, int irq, int level)
+{
+ struct Exynos4210CombinerState *s =
+ (struct Exynos4210CombinerState *)opaque;
+ uint8_t bit_n, group_n;
+
+ group_n = get_combiner_group_and_bit(irq, &bit_n);
+
+ if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) {
+ DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT"
+ , group_n);
+ return;
+ }
+
+ if (level) {
+ s->group[group_n].src_pending |= 1 << bit_n;
+ } else {
+ s->group[group_n].src_pending &= ~(1 << bit_n);
+ }
+
+ exynos4210_combiner_update(s, group_n);
+}
+
+static void exynos4210_combiner_reset(DeviceState *d)
+{
+ struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d;
+
+ memset(&s->group, 0, sizeof(s->group));
+ memset(&s->reg_set, 0, sizeof(s->reg_set));
+
+ s->reg_set[0xC0 >> 2] = 0x01010101;
+ s->reg_set[0xC4 >> 2] = 0x01010101;
+ s->reg_set[0xD0 >> 2] = 0x01010101;
+ s->reg_set[0xD4 >> 2] = 0x01010101;
+}
+
+static const MemoryRegionOps exynos4210_combiner_ops = {
+ .read = exynos4210_combiner_read,
+ .write = exynos4210_combiner_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * Internal Combiner initialization.
+ */
+static int exynos4210_combiner_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ Exynos4210CombinerState *s = EXYNOS4210_COMBINER(dev);
+ unsigned int i;
+
+ /* Allocate general purpose input signals and connect a handler to each of
+ * them */
+ qdev_init_gpio_in(dev, exynos4210_combiner_handler, IIC_NIRQ);
+
+ /* Connect SysBusDev irqs to device specific irqs */
+ for (i = 0; i < IIC_NGRP; i++) {
+ sysbus_init_irq(sbd, &s->output_irq[i]);
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_combiner_ops, s,
+ "exynos4210-combiner", IIC_REGION_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ return 0;
+}
+
+static Property exynos4210_combiner_properties[] = {
+ DEFINE_PROP_UINT32("external", Exynos4210CombinerState, external, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void exynos4210_combiner_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_combiner_init;
+ dc->reset = exynos4210_combiner_reset;
+ dc->props = exynos4210_combiner_properties;
+ dc->vmsd = &vmstate_exynos4210_combiner;
+}
+
+static const TypeInfo exynos4210_combiner_info = {
+ .name = TYPE_EXYNOS4210_COMBINER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210CombinerState),
+ .class_init = exynos4210_combiner_class_init,
+};
+
+static void exynos4210_combiner_register_types(void)
+{
+ type_register_static(&exynos4210_combiner_info);
+}
+
+type_init(exynos4210_combiner_register_types)
diff --git a/hw/intc/exynos4210_gic.c b/hw/intc/exynos4210_gic.c
new file mode 100644
index 00000000..b2a4950b
--- /dev/null
+++ b/hw/intc/exynos4210_gic.c
@@ -0,0 +1,471 @@
+/*
+ * Samsung exynos4210 GIC implementation. Based on hw/arm_gic.c
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu-common.h"
+#include "hw/irq.h"
+#include "hw/arm/exynos4210.h"
+
+enum ExtGicId {
+ EXT_GIC_ID_MDMA_LCD0 = 66,
+ EXT_GIC_ID_PDMA0,
+ EXT_GIC_ID_PDMA1,
+ EXT_GIC_ID_TIMER0,
+ EXT_GIC_ID_TIMER1,
+ EXT_GIC_ID_TIMER2,
+ EXT_GIC_ID_TIMER3,
+ EXT_GIC_ID_TIMER4,
+ EXT_GIC_ID_MCT_L0,
+ EXT_GIC_ID_WDT,
+ EXT_GIC_ID_RTC_ALARM,
+ EXT_GIC_ID_RTC_TIC,
+ EXT_GIC_ID_GPIO_XB,
+ EXT_GIC_ID_GPIO_XA,
+ EXT_GIC_ID_MCT_L1,
+ EXT_GIC_ID_IEM_APC,
+ EXT_GIC_ID_IEM_IEC,
+ EXT_GIC_ID_NFC,
+ EXT_GIC_ID_UART0,
+ EXT_GIC_ID_UART1,
+ EXT_GIC_ID_UART2,
+ EXT_GIC_ID_UART3,
+ EXT_GIC_ID_UART4,
+ EXT_GIC_ID_MCT_G0,
+ EXT_GIC_ID_I2C0,
+ EXT_GIC_ID_I2C1,
+ EXT_GIC_ID_I2C2,
+ EXT_GIC_ID_I2C3,
+ EXT_GIC_ID_I2C4,
+ EXT_GIC_ID_I2C5,
+ EXT_GIC_ID_I2C6,
+ EXT_GIC_ID_I2C7,
+ EXT_GIC_ID_SPI0,
+ EXT_GIC_ID_SPI1,
+ EXT_GIC_ID_SPI2,
+ EXT_GIC_ID_MCT_G1,
+ EXT_GIC_ID_USB_HOST,
+ EXT_GIC_ID_USB_DEVICE,
+ EXT_GIC_ID_MODEMIF,
+ EXT_GIC_ID_HSMMC0,
+ EXT_GIC_ID_HSMMC1,
+ EXT_GIC_ID_HSMMC2,
+ EXT_GIC_ID_HSMMC3,
+ EXT_GIC_ID_SDMMC,
+ EXT_GIC_ID_MIPI_CSI_4LANE,
+ EXT_GIC_ID_MIPI_DSI_4LANE,
+ EXT_GIC_ID_MIPI_CSI_2LANE,
+ EXT_GIC_ID_MIPI_DSI_2LANE,
+ EXT_GIC_ID_ONENAND_AUDI,
+ EXT_GIC_ID_ROTATOR,
+ EXT_GIC_ID_FIMC0,
+ EXT_GIC_ID_FIMC1,
+ EXT_GIC_ID_FIMC2,
+ EXT_GIC_ID_FIMC3,
+ EXT_GIC_ID_JPEG,
+ EXT_GIC_ID_2D,
+ EXT_GIC_ID_PCIe,
+ EXT_GIC_ID_MIXER,
+ EXT_GIC_ID_HDMI,
+ EXT_GIC_ID_HDMI_I2C,
+ EXT_GIC_ID_MFC,
+ EXT_GIC_ID_TVENC,
+};
+
+enum ExtInt {
+ EXT_GIC_ID_EXTINT0 = 48,
+ EXT_GIC_ID_EXTINT1,
+ EXT_GIC_ID_EXTINT2,
+ EXT_GIC_ID_EXTINT3,
+ EXT_GIC_ID_EXTINT4,
+ EXT_GIC_ID_EXTINT5,
+ EXT_GIC_ID_EXTINT6,
+ EXT_GIC_ID_EXTINT7,
+ EXT_GIC_ID_EXTINT8,
+ EXT_GIC_ID_EXTINT9,
+ EXT_GIC_ID_EXTINT10,
+ EXT_GIC_ID_EXTINT11,
+ EXT_GIC_ID_EXTINT12,
+ EXT_GIC_ID_EXTINT13,
+ EXT_GIC_ID_EXTINT14,
+ EXT_GIC_ID_EXTINT15
+};
+
+/*
+ * External GIC sources which are not from External Interrupt Combiner or
+ * External Interrupts are starting from EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ,
+ * which is INTG16 in Internal Interrupt Combiner.
+ */
+
+static uint32_t
+combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = {
+ /* int combiner groups 16-19 */
+ { }, { }, { }, { },
+ /* int combiner group 20 */
+ { 0, EXT_GIC_ID_MDMA_LCD0 },
+ /* int combiner group 21 */
+ { EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1 },
+ /* int combiner group 22 */
+ { EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2,
+ EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4 },
+ /* int combiner group 23 */
+ { EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC },
+ /* int combiner group 24 */
+ { EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA },
+ /* int combiner group 25 */
+ { EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC },
+ /* int combiner group 26 */
+ { EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3,
+ EXT_GIC_ID_UART4 },
+ /* int combiner group 27 */
+ { EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3,
+ EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6,
+ EXT_GIC_ID_I2C7 },
+ /* int combiner group 28 */
+ { EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2 , EXT_GIC_ID_USB_HOST},
+ /* int combiner group 29 */
+ { EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2,
+ EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC },
+ /* int combiner group 30 */
+ { EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE },
+ /* int combiner group 31 */
+ { EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE },
+ /* int combiner group 32 */
+ { EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1 },
+ /* int combiner group 33 */
+ { EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3 },
+ /* int combiner group 34 */
+ { EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC },
+ /* int combiner group 35 */
+ { 0, 0, 0, EXT_GIC_ID_MCT_L1, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 },
+ /* int combiner group 36 */
+ { EXT_GIC_ID_MIXER },
+ /* int combiner group 37 */
+ { EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6,
+ EXT_GIC_ID_EXTINT7 },
+ /* groups 38-50 */
+ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { },
+ /* int combiner group 51 */
+ { EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 },
+ /* group 52 */
+ { },
+ /* int combiner group 53 */
+ { EXT_GIC_ID_WDT, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 },
+ /* groups 54-63 */
+ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }
+};
+
+#define EXYNOS4210_GIC_NIRQ 160
+
+#define EXYNOS4210_EXT_GIC_CPU_REGION_SIZE 0x10000
+#define EXYNOS4210_EXT_GIC_DIST_REGION_SIZE 0x10000
+
+#define EXYNOS4210_EXT_GIC_PER_CPU_OFFSET 0x8000
+#define EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(n) \
+ ((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET)
+#define EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(n) \
+ ((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET)
+
+#define EXYNOS4210_GIC_CPU_REGION_SIZE 0x100
+#define EXYNOS4210_GIC_DIST_REGION_SIZE 0x1000
+
+static void exynos4210_irq_handler(void *opaque, int irq, int level)
+{
+ Exynos4210Irq *s = (Exynos4210Irq *)opaque;
+
+ /* Bypass */
+ qemu_set_irq(s->board_irqs[irq], level);
+}
+
+/*
+ * Initialize exynos4210 IRQ subsystem stub.
+ */
+qemu_irq *exynos4210_init_irq(Exynos4210Irq *s)
+{
+ return qemu_allocate_irqs(exynos4210_irq_handler, s,
+ EXYNOS4210_MAX_INT_COMBINER_IN_IRQ);
+}
+
+/*
+ * Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs.
+ */
+void exynos4210_init_board_irqs(Exynos4210Irq *s)
+{
+ uint32_t grp, bit, irq_id, n;
+
+ for (n = 0; n < EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ; n++) {
+ irq_id = 0;
+ if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4) ||
+ n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4)) {
+ /* MCT_G0 is passed to External GIC */
+ irq_id = EXT_GIC_ID_MCT_G0;
+ }
+ if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5) ||
+ n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 5)) {
+ /* MCT_G1 is passed to External and GIC */
+ irq_id = EXT_GIC_ID_MCT_G1;
+ }
+ if (irq_id) {
+ s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+ s->ext_gic_irq[irq_id-32]);
+ } else {
+ s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+ s->ext_combiner_irq[n]);
+ }
+ }
+ for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) {
+ /* these IDs are passed to Internal Combiner and External GIC */
+ grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n);
+ bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+ irq_id = combiner_grp_to_gic_id[grp -
+ EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][bit];
+
+ if (irq_id) {
+ s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+ s->ext_gic_irq[irq_id-32]);
+ }
+ }
+}
+
+/*
+ * Get IRQ number from exynos4210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ * grp - group number
+ * bit - bit number inside group
+ */
+uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit)
+{
+ return EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit);
+}
+
+/********* GIC part *********/
+
+#define TYPE_EXYNOS4210_GIC "exynos4210.gic"
+#define EXYNOS4210_GIC(obj) \
+ OBJECT_CHECK(Exynos4210GicState, (obj), TYPE_EXYNOS4210_GIC)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion cpu_container;
+ MemoryRegion dist_container;
+ MemoryRegion cpu_alias[EXYNOS4210_NCPUS];
+ MemoryRegion dist_alias[EXYNOS4210_NCPUS];
+ uint32_t num_cpu;
+ DeviceState *gic;
+} Exynos4210GicState;
+
+static void exynos4210_gic_set_irq(void *opaque, int irq, int level)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *)opaque;
+ qemu_set_irq(qdev_get_gpio_in(s->gic, irq), level);
+}
+
+static int exynos4210_gic_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ Exynos4210GicState *s = EXYNOS4210_GIC(dev);
+ uint32_t i;
+ const char cpu_prefix[] = "exynos4210-gic-alias_cpu";
+ const char dist_prefix[] = "exynos4210-gic-alias_dist";
+ char cpu_alias_name[sizeof(cpu_prefix) + 3];
+ char dist_alias_name[sizeof(cpu_prefix) + 3];
+ SysBusDevice *busdev;
+
+ s->gic = qdev_create(NULL, "arm_gic");
+ qdev_prop_set_uint32(s->gic, "num-cpu", s->num_cpu);
+ qdev_prop_set_uint32(s->gic, "num-irq", EXYNOS4210_GIC_NIRQ);
+ qdev_init_nofail(s->gic);
+ busdev = SYS_BUS_DEVICE(s->gic);
+
+ /* Pass through outbound IRQ lines from the GIC */
+ sysbus_pass_irq(sbd, busdev);
+
+ /* Pass through inbound GPIO lines to the GIC */
+ qdev_init_gpio_in(dev, exynos4210_gic_set_irq,
+ EXYNOS4210_GIC_NIRQ - 32);
+
+ memory_region_init(&s->cpu_container, OBJECT(s), "exynos4210-cpu-container",
+ EXYNOS4210_EXT_GIC_CPU_REGION_SIZE);
+ memory_region_init(&s->dist_container, OBJECT(s), "exynos4210-dist-container",
+ EXYNOS4210_EXT_GIC_DIST_REGION_SIZE);
+
+ for (i = 0; i < s->num_cpu; i++) {
+ /* Map CPU interface per SMP Core */
+ sprintf(cpu_alias_name, "%s%x", cpu_prefix, i);
+ memory_region_init_alias(&s->cpu_alias[i], OBJECT(s),
+ cpu_alias_name,
+ sysbus_mmio_get_region(busdev, 1),
+ 0,
+ EXYNOS4210_GIC_CPU_REGION_SIZE);
+ memory_region_add_subregion(&s->cpu_container,
+ EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(i), &s->cpu_alias[i]);
+
+ /* Map Distributor per SMP Core */
+ sprintf(dist_alias_name, "%s%x", dist_prefix, i);
+ memory_region_init_alias(&s->dist_alias[i], OBJECT(s),
+ dist_alias_name,
+ sysbus_mmio_get_region(busdev, 0),
+ 0,
+ EXYNOS4210_GIC_DIST_REGION_SIZE);
+ memory_region_add_subregion(&s->dist_container,
+ EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(i), &s->dist_alias[i]);
+ }
+
+ sysbus_init_mmio(sbd, &s->cpu_container);
+ sysbus_init_mmio(sbd, &s->dist_container);
+
+ return 0;
+}
+
+static Property exynos4210_gic_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void exynos4210_gic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_gic_init;
+ dc->props = exynos4210_gic_properties;
+}
+
+static const TypeInfo exynos4210_gic_info = {
+ .name = TYPE_EXYNOS4210_GIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210GicState),
+ .class_init = exynos4210_gic_class_init,
+};
+
+static void exynos4210_gic_register_types(void)
+{
+ type_register_static(&exynos4210_gic_info);
+}
+
+type_init(exynos4210_gic_register_types)
+
+/* IRQ OR Gate struct.
+ *
+ * This device models an OR gate. There are n_in input qdev gpio lines and one
+ * output sysbus IRQ line. The output IRQ level is formed as OR between all
+ * gpio inputs.
+ */
+
+#define TYPE_EXYNOS4210_IRQ_GATE "exynos4210.irq_gate"
+#define EXYNOS4210_IRQ_GATE(obj) \
+ OBJECT_CHECK(Exynos4210IRQGateState, (obj), TYPE_EXYNOS4210_IRQ_GATE)
+
+typedef struct Exynos4210IRQGateState {
+ SysBusDevice parent_obj;
+
+ uint32_t n_in; /* inputs amount */
+ uint32_t *level; /* input levels */
+ qemu_irq out; /* output IRQ */
+} Exynos4210IRQGateState;
+
+static Property exynos4210_irq_gate_properties[] = {
+ DEFINE_PROP_UINT32("n_in", Exynos4210IRQGateState, n_in, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_exynos4210_irq_gate = {
+ .name = "exynos4210.irq_gate",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_VBUFFER_UINT32(level, Exynos4210IRQGateState, 1, NULL, 0, n_in),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* Process a change in IRQ input. */
+static void exynos4210_irq_gate_handler(void *opaque, int irq, int level)
+{
+ Exynos4210IRQGateState *s = (Exynos4210IRQGateState *)opaque;
+ uint32_t i;
+
+ assert(irq < s->n_in);
+
+ s->level[irq] = level;
+
+ for (i = 0; i < s->n_in; i++) {
+ if (s->level[i] >= 1) {
+ qemu_irq_raise(s->out);
+ return;
+ }
+ }
+
+ qemu_irq_lower(s->out);
+}
+
+static void exynos4210_irq_gate_reset(DeviceState *d)
+{
+ Exynos4210IRQGateState *s = EXYNOS4210_IRQ_GATE(d);
+
+ memset(s->level, 0, s->n_in * sizeof(*s->level));
+}
+
+/*
+ * IRQ Gate initialization.
+ */
+static int exynos4210_irq_gate_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ Exynos4210IRQGateState *s = EXYNOS4210_IRQ_GATE(dev);
+
+ /* Allocate general purpose input signals and connect a handler to each of
+ * them */
+ qdev_init_gpio_in(dev, exynos4210_irq_gate_handler, s->n_in);
+
+ s->level = g_malloc0(s->n_in * sizeof(*s->level));
+
+ sysbus_init_irq(sbd, &s->out);
+
+ return 0;
+}
+
+static void exynos4210_irq_gate_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_irq_gate_init;
+ dc->reset = exynos4210_irq_gate_reset;
+ dc->vmsd = &vmstate_exynos4210_irq_gate;
+ dc->props = exynos4210_irq_gate_properties;
+}
+
+static const TypeInfo exynos4210_irq_gate_info = {
+ .name = TYPE_EXYNOS4210_IRQ_GATE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210IRQGateState),
+ .class_init = exynos4210_irq_gate_class_init,
+};
+
+static void exynos4210_irq_gate_register_types(void)
+{
+ type_register_static(&exynos4210_irq_gate_info);
+}
+
+type_init(exynos4210_irq_gate_register_types)
diff --git a/hw/intc/gic_internal.h b/hw/intc/gic_internal.h
new file mode 100644
index 00000000..20c1e8a2
--- /dev/null
+++ b/hw/intc/gic_internal.h
@@ -0,0 +1,103 @@
+/*
+ * ARM GIC support - internal interfaces
+ *
+ * Copyright (c) 2012 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_ARM_GIC_INTERNAL_H
+#define QEMU_ARM_GIC_INTERNAL_H
+
+#include "hw/intc/arm_gic.h"
+
+#define ALL_CPU_MASK ((unsigned)(((1 << GIC_NCPU) - 1)))
+
+/* The NVIC has 16 internal vectors. However these are not exposed
+ through the normal GIC interface. */
+#define GIC_BASE_IRQ ((s->revision == REV_NVIC) ? 32 : 0)
+
+#define GIC_SET_ENABLED(irq, cm) s->irq_state[irq].enabled |= (cm)
+#define GIC_CLEAR_ENABLED(irq, cm) s->irq_state[irq].enabled &= ~(cm)
+#define GIC_TEST_ENABLED(irq, cm) ((s->irq_state[irq].enabled & (cm)) != 0)
+#define GIC_SET_PENDING(irq, cm) s->irq_state[irq].pending |= (cm)
+#define GIC_CLEAR_PENDING(irq, cm) s->irq_state[irq].pending &= ~(cm)
+#define GIC_SET_ACTIVE(irq, cm) s->irq_state[irq].active |= (cm)
+#define GIC_CLEAR_ACTIVE(irq, cm) s->irq_state[irq].active &= ~(cm)
+#define GIC_TEST_ACTIVE(irq, cm) ((s->irq_state[irq].active & (cm)) != 0)
+#define GIC_SET_MODEL(irq) s->irq_state[irq].model = true
+#define GIC_CLEAR_MODEL(irq) s->irq_state[irq].model = false
+#define GIC_TEST_MODEL(irq) s->irq_state[irq].model
+#define GIC_SET_LEVEL(irq, cm) s->irq_state[irq].level |= (cm)
+#define GIC_CLEAR_LEVEL(irq, cm) s->irq_state[irq].level &= ~(cm)
+#define GIC_TEST_LEVEL(irq, cm) ((s->irq_state[irq].level & (cm)) != 0)
+#define GIC_SET_EDGE_TRIGGER(irq) s->irq_state[irq].edge_trigger = true
+#define GIC_CLEAR_EDGE_TRIGGER(irq) s->irq_state[irq].edge_trigger = false
+#define GIC_TEST_EDGE_TRIGGER(irq) (s->irq_state[irq].edge_trigger)
+#define GIC_GET_PRIORITY(irq, cpu) (((irq) < GIC_INTERNAL) ? \
+ s->priority1[irq][cpu] : \
+ s->priority2[(irq) - GIC_INTERNAL])
+#define GIC_TARGET(irq) s->irq_target[irq]
+#define GIC_CLEAR_GROUP(irq, cm) (s->irq_state[irq].group &= ~(cm))
+#define GIC_SET_GROUP(irq, cm) (s->irq_state[irq].group |= (cm))
+#define GIC_TEST_GROUP(irq, cm) ((s->irq_state[irq].group & (cm)) != 0)
+
+#define GICD_CTLR_EN_GRP0 (1U << 0)
+#define GICD_CTLR_EN_GRP1 (1U << 1)
+
+#define GICC_CTLR_EN_GRP0 (1U << 0)
+#define GICC_CTLR_EN_GRP1 (1U << 1)
+#define GICC_CTLR_ACK_CTL (1U << 2)
+#define GICC_CTLR_FIQ_EN (1U << 3)
+#define GICC_CTLR_CBPR (1U << 4) /* GICv1: SBPR */
+#define GICC_CTLR_EOIMODE (1U << 9)
+#define GICC_CTLR_EOIMODE_NS (1U << 10)
+
+/* Valid bits for GICC_CTLR for GICv1, v1 with security extensions,
+ * GICv2 and GICv2 with security extensions:
+ */
+#define GICC_CTLR_V1_MASK 0x1
+#define GICC_CTLR_V1_S_MASK 0x1f
+#define GICC_CTLR_V2_MASK 0x21f
+#define GICC_CTLR_V2_S_MASK 0x61f
+
+/* The special cases for the revision property: */
+#define REV_11MPCORE 0
+#define REV_NVIC 0xffffffff
+
+void gic_set_pending_private(GICState *s, int cpu, int irq);
+uint32_t gic_acknowledge_irq(GICState *s, int cpu, MemTxAttrs attrs);
+void gic_complete_irq(GICState *s, int cpu, int irq, MemTxAttrs attrs);
+void gic_update(GICState *s);
+void gic_init_irqs_and_distributor(GICState *s);
+void gic_set_priority(GICState *s, int cpu, int irq, uint8_t val,
+ MemTxAttrs attrs);
+
+static inline bool gic_test_pending(GICState *s, int irq, int cm)
+{
+ if (s->revision == REV_NVIC || s->revision == REV_11MPCORE) {
+ return s->irq_state[irq].pending & cm;
+ } else {
+ /* Edge-triggered interrupts are marked pending on a rising edge, but
+ * level-triggered interrupts are either considered pending when the
+ * level is active or if software has explicitly written to
+ * GICD_ISPENDR to set the state pending.
+ */
+ return (s->irq_state[irq].pending & cm) ||
+ (!GIC_TEST_EDGE_TRIGGER(irq) && GIC_TEST_LEVEL(irq, cm));
+ }
+}
+
+#endif /* !QEMU_ARM_GIC_INTERNAL_H */
diff --git a/hw/intc/grlib_irqmp.c b/hw/intc/grlib_irqmp.c
new file mode 100644
index 00000000..d1813f76
--- /dev/null
+++ b/hw/intc/grlib_irqmp.c
@@ -0,0 +1,374 @@
+/*
+ * QEMU GRLIB IRQMP Emulator
+ *
+ * (Multiprocessor and extended interrupt not supported)
+ *
+ * Copyright (c) 2010-2011 AdaCore
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "cpu.h"
+
+#include "hw/sparc/grlib.h"
+
+#include "trace.h"
+
+#define IRQMP_MAX_CPU 16
+#define IRQMP_REG_SIZE 256 /* Size of memory mapped registers */
+
+/* Memory mapped register offsets */
+#define LEVEL_OFFSET 0x00
+#define PENDING_OFFSET 0x04
+#define FORCE0_OFFSET 0x08
+#define CLEAR_OFFSET 0x0C
+#define MP_STATUS_OFFSET 0x10
+#define BROADCAST_OFFSET 0x14
+#define MASK_OFFSET 0x40
+#define FORCE_OFFSET 0x80
+#define EXTENDED_OFFSET 0xC0
+
+#define TYPE_GRLIB_IRQMP "grlib,irqmp"
+#define GRLIB_IRQMP(obj) OBJECT_CHECK(IRQMP, (obj), TYPE_GRLIB_IRQMP)
+
+typedef struct IRQMPState IRQMPState;
+
+typedef struct IRQMP {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ void *set_pil_in;
+ void *set_pil_in_opaque;
+
+ IRQMPState *state;
+} IRQMP;
+
+struct IRQMPState {
+ uint32_t level;
+ uint32_t pending;
+ uint32_t clear;
+ uint32_t broadcast;
+
+ uint32_t mask[IRQMP_MAX_CPU];
+ uint32_t force[IRQMP_MAX_CPU];
+ uint32_t extended[IRQMP_MAX_CPU];
+
+ IRQMP *parent;
+};
+
+static void grlib_irqmp_check_irqs(IRQMPState *state)
+{
+ uint32_t pend = 0;
+ uint32_t level0 = 0;
+ uint32_t level1 = 0;
+ set_pil_in_fn set_pil_in;
+
+ assert(state != NULL);
+ assert(state->parent != NULL);
+
+ /* IRQ for CPU 0 (no SMP support) */
+ pend = (state->pending | state->force[0])
+ & state->mask[0];
+
+ level0 = pend & ~state->level;
+ level1 = pend & state->level;
+
+ trace_grlib_irqmp_check_irqs(state->pending, state->force[0],
+ state->mask[0], level1, level0);
+
+ set_pil_in = (set_pil_in_fn)state->parent->set_pil_in;
+
+ /* Trigger level1 interrupt first and level0 if there is no level1 */
+ if (level1 != 0) {
+ set_pil_in(state->parent->set_pil_in_opaque, level1);
+ } else {
+ set_pil_in(state->parent->set_pil_in_opaque, level0);
+ }
+}
+
+void grlib_irqmp_ack(DeviceState *dev, int intno)
+{
+ IRQMP *irqmp = GRLIB_IRQMP(dev);
+ IRQMPState *state;
+ uint32_t mask;
+
+ state = irqmp->state;
+ assert(state != NULL);
+
+ intno &= 15;
+ mask = 1 << intno;
+
+ trace_grlib_irqmp_ack(intno);
+
+ /* Clear registers */
+ state->pending &= ~mask;
+ state->force[0] &= ~mask; /* Only CPU 0 (No SMP support) */
+
+ grlib_irqmp_check_irqs(state);
+}
+
+void grlib_irqmp_set_irq(void *opaque, int irq, int level)
+{
+ IRQMP *irqmp = GRLIB_IRQMP(opaque);
+ IRQMPState *s;
+ int i = 0;
+
+ s = irqmp->state;
+ assert(s != NULL);
+ assert(s->parent != NULL);
+
+
+ if (level) {
+ trace_grlib_irqmp_set_irq(irq);
+
+ if (s->broadcast & 1 << irq) {
+ /* Broadcasted IRQ */
+ for (i = 0; i < IRQMP_MAX_CPU; i++) {
+ s->force[i] |= 1 << irq;
+ }
+ } else {
+ s->pending |= 1 << irq;
+ }
+ grlib_irqmp_check_irqs(s);
+
+ }
+}
+
+static uint64_t grlib_irqmp_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ IRQMP *irqmp = opaque;
+ IRQMPState *state;
+
+ assert(irqmp != NULL);
+ state = irqmp->state;
+ assert(state != NULL);
+
+ addr &= 0xff;
+
+ /* global registers */
+ switch (addr) {
+ case LEVEL_OFFSET:
+ return state->level;
+
+ case PENDING_OFFSET:
+ return state->pending;
+
+ case FORCE0_OFFSET:
+ /* This register is an "alias" for the force register of CPU 0 */
+ return state->force[0];
+
+ case CLEAR_OFFSET:
+ case MP_STATUS_OFFSET:
+ /* Always read as 0 */
+ return 0;
+
+ case BROADCAST_OFFSET:
+ return state->broadcast;
+
+ default:
+ break;
+ }
+
+ /* mask registers */
+ if (addr >= MASK_OFFSET && addr < FORCE_OFFSET) {
+ int cpu = (addr - MASK_OFFSET) / 4;
+ assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
+
+ return state->mask[cpu];
+ }
+
+ /* force registers */
+ if (addr >= FORCE_OFFSET && addr < EXTENDED_OFFSET) {
+ int cpu = (addr - FORCE_OFFSET) / 4;
+ assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
+
+ return state->force[cpu];
+ }
+
+ /* extended (not supported) */
+ if (addr >= EXTENDED_OFFSET && addr < IRQMP_REG_SIZE) {
+ int cpu = (addr - EXTENDED_OFFSET) / 4;
+ assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
+
+ return state->extended[cpu];
+ }
+
+ trace_grlib_irqmp_readl_unknown(addr);
+ return 0;
+}
+
+static void grlib_irqmp_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ IRQMP *irqmp = opaque;
+ IRQMPState *state;
+
+ assert(irqmp != NULL);
+ state = irqmp->state;
+ assert(state != NULL);
+
+ addr &= 0xff;
+
+ /* global registers */
+ switch (addr) {
+ case LEVEL_OFFSET:
+ value &= 0xFFFF << 1; /* clean up the value */
+ state->level = value;
+ return;
+
+ case PENDING_OFFSET:
+ /* Read Only */
+ return;
+
+ case FORCE0_OFFSET:
+ /* This register is an "alias" for the force register of CPU 0 */
+
+ value &= 0xFFFE; /* clean up the value */
+ state->force[0] = value;
+ grlib_irqmp_check_irqs(irqmp->state);
+ return;
+
+ case CLEAR_OFFSET:
+ value &= ~1; /* clean up the value */
+ state->pending &= ~value;
+ return;
+
+ case MP_STATUS_OFFSET:
+ /* Read Only (no SMP support) */
+ return;
+
+ case BROADCAST_OFFSET:
+ value &= 0xFFFE; /* clean up the value */
+ state->broadcast = value;
+ return;
+
+ default:
+ break;
+ }
+
+ /* mask registers */
+ if (addr >= MASK_OFFSET && addr < FORCE_OFFSET) {
+ int cpu = (addr - MASK_OFFSET) / 4;
+ assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
+
+ value &= ~1; /* clean up the value */
+ state->mask[cpu] = value;
+ grlib_irqmp_check_irqs(irqmp->state);
+ return;
+ }
+
+ /* force registers */
+ if (addr >= FORCE_OFFSET && addr < EXTENDED_OFFSET) {
+ int cpu = (addr - FORCE_OFFSET) / 4;
+ assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
+
+ uint32_t force = value & 0xFFFE;
+ uint32_t clear = (value >> 16) & 0xFFFE;
+ uint32_t old = state->force[cpu];
+
+ state->force[cpu] = (old | force) & ~clear;
+ grlib_irqmp_check_irqs(irqmp->state);
+ return;
+ }
+
+ /* extended (not supported) */
+ if (addr >= EXTENDED_OFFSET && addr < IRQMP_REG_SIZE) {
+ int cpu = (addr - EXTENDED_OFFSET) / 4;
+ assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
+
+ value &= 0xF; /* clean up the value */
+ state->extended[cpu] = value;
+ return;
+ }
+
+ trace_grlib_irqmp_writel_unknown(addr, value);
+}
+
+static const MemoryRegionOps grlib_irqmp_ops = {
+ .read = grlib_irqmp_read,
+ .write = grlib_irqmp_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void grlib_irqmp_reset(DeviceState *d)
+{
+ IRQMP *irqmp = GRLIB_IRQMP(d);
+ assert(irqmp->state != NULL);
+
+ memset(irqmp->state, 0, sizeof *irqmp->state);
+ irqmp->state->parent = irqmp;
+}
+
+static int grlib_irqmp_init(SysBusDevice *dev)
+{
+ IRQMP *irqmp = GRLIB_IRQMP(dev);
+
+ /* Check parameters */
+ if (irqmp->set_pil_in == NULL) {
+ return -1;
+ }
+
+ memory_region_init_io(&irqmp->iomem, OBJECT(dev), &grlib_irqmp_ops, irqmp,
+ "irqmp", IRQMP_REG_SIZE);
+
+ irqmp->state = g_malloc0(sizeof *irqmp->state);
+
+ sysbus_init_mmio(dev, &irqmp->iomem);
+
+ return 0;
+}
+
+static Property grlib_irqmp_properties[] = {
+ DEFINE_PROP_PTR("set_pil_in", IRQMP, set_pil_in),
+ DEFINE_PROP_PTR("set_pil_in_opaque", IRQMP, set_pil_in_opaque),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void grlib_irqmp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = grlib_irqmp_init;
+ dc->reset = grlib_irqmp_reset;
+ dc->props = grlib_irqmp_properties;
+ /* Reason: pointer properties "set_pil_in", "set_pil_in_opaque" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo grlib_irqmp_info = {
+ .name = TYPE_GRLIB_IRQMP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IRQMP),
+ .class_init = grlib_irqmp_class_init,
+};
+
+static void grlib_irqmp_register_types(void)
+{
+ type_register_static(&grlib_irqmp_info);
+}
+
+type_init(grlib_irqmp_register_types)
diff --git a/hw/intc/heathrow_pic.c b/hw/intc/heathrow_pic.c
new file mode 100644
index 00000000..9ff3119e
--- /dev/null
+++ b/hw/intc/heathrow_pic.c
@@ -0,0 +1,213 @@
+/*
+ * Heathrow PIC support (OldWorld PowerMac)
+ *
+ * Copyright (c) 2005-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/mac.h"
+
+/* debug PIC */
+//#define DEBUG_PIC
+
+#ifdef DEBUG_PIC
+#define PIC_DPRINTF(fmt, ...) \
+ do { printf("PIC: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define PIC_DPRINTF(fmt, ...)
+#endif
+
+typedef struct HeathrowPIC {
+ uint32_t events;
+ uint32_t mask;
+ uint32_t levels;
+ uint32_t level_triggered;
+} HeathrowPIC;
+
+typedef struct HeathrowPICS {
+ MemoryRegion mem;
+ HeathrowPIC pics[2];
+ qemu_irq *irqs;
+} HeathrowPICS;
+
+static inline int check_irq(HeathrowPIC *pic)
+{
+ return (pic->events | (pic->levels & pic->level_triggered)) & pic->mask;
+}
+
+/* update the CPU irq state */
+static void heathrow_pic_update(HeathrowPICS *s)
+{
+ if (check_irq(&s->pics[0]) || check_irq(&s->pics[1])) {
+ qemu_irq_raise(s->irqs[0]);
+ } else {
+ qemu_irq_lower(s->irqs[0]);
+ }
+}
+
+static void pic_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ HeathrowPICS *s = opaque;
+ HeathrowPIC *pic;
+ unsigned int n;
+
+ n = ((addr & 0xfff) - 0x10) >> 4;
+ PIC_DPRINTF("writel: " TARGET_FMT_plx " %u: %08x\n", addr, n, value);
+ if (n >= 2)
+ return;
+ pic = &s->pics[n];
+ switch(addr & 0xf) {
+ case 0x04:
+ pic->mask = value;
+ heathrow_pic_update(s);
+ break;
+ case 0x08:
+ /* do not reset level triggered IRQs */
+ value &= ~pic->level_triggered;
+ pic->events &= ~value;
+ heathrow_pic_update(s);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t pic_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ HeathrowPICS *s = opaque;
+ HeathrowPIC *pic;
+ unsigned int n;
+ uint32_t value;
+
+ n = ((addr & 0xfff) - 0x10) >> 4;
+ if (n >= 2) {
+ value = 0;
+ } else {
+ pic = &s->pics[n];
+ switch(addr & 0xf) {
+ case 0x0:
+ value = pic->events;
+ break;
+ case 0x4:
+ value = pic->mask;
+ break;
+ case 0xc:
+ value = pic->levels;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ }
+ PIC_DPRINTF("readl: " TARGET_FMT_plx " %u: %08x\n", addr, n, value);
+ return value;
+}
+
+static const MemoryRegionOps heathrow_pic_ops = {
+ .read = pic_read,
+ .write = pic_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void heathrow_pic_set_irq(void *opaque, int num, int level)
+{
+ HeathrowPICS *s = opaque;
+ HeathrowPIC *pic;
+ unsigned int irq_bit;
+
+#if defined(DEBUG)
+ {
+ static int last_level[64];
+ if (last_level[num] != level) {
+ PIC_DPRINTF("set_irq: num=0x%02x level=%d\n", num, level);
+ last_level[num] = level;
+ }
+ }
+#endif
+ pic = &s->pics[1 - (num >> 5)];
+ irq_bit = 1 << (num & 0x1f);
+ if (level) {
+ pic->events |= irq_bit & ~pic->level_triggered;
+ pic->levels |= irq_bit;
+ } else {
+ pic->levels &= ~irq_bit;
+ }
+ heathrow_pic_update(s);
+}
+
+static const VMStateDescription vmstate_heathrow_pic_one = {
+ .name = "heathrow_pic_one",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(events, HeathrowPIC),
+ VMSTATE_UINT32(mask, HeathrowPIC),
+ VMSTATE_UINT32(levels, HeathrowPIC),
+ VMSTATE_UINT32(level_triggered, HeathrowPIC),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_heathrow_pic = {
+ .name = "heathrow_pic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(pics, HeathrowPICS, 2, 1,
+ vmstate_heathrow_pic_one, HeathrowPIC),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void heathrow_pic_reset_one(HeathrowPIC *s)
+{
+ memset(s, '\0', sizeof(HeathrowPIC));
+}
+
+static void heathrow_pic_reset(void *opaque)
+{
+ HeathrowPICS *s = opaque;
+
+ heathrow_pic_reset_one(&s->pics[0]);
+ heathrow_pic_reset_one(&s->pics[1]);
+
+ s->pics[0].level_triggered = 0;
+ s->pics[1].level_triggered = 0x1ff00000;
+}
+
+qemu_irq *heathrow_pic_init(MemoryRegion **pmem,
+ int nb_cpus, qemu_irq **irqs)
+{
+ HeathrowPICS *s;
+
+ s = g_malloc0(sizeof(HeathrowPICS));
+ /* only 1 CPU */
+ s->irqs = irqs[0];
+ memory_region_init_io(&s->mem, NULL, &heathrow_pic_ops, s,
+ "heathrow-pic", 0x1000);
+ *pmem = &s->mem;
+
+ vmstate_register(NULL, -1, &vmstate_heathrow_pic, s);
+ qemu_register_reset(heathrow_pic_reset, s);
+ return qemu_allocate_irqs(heathrow_pic_set_irq, s, 64);
+}
diff --git a/hw/intc/i8259.c b/hw/intc/i8259.c
new file mode 100644
index 00000000..0f5c0259
--- /dev/null
+++ b/hw/intc/i8259.c
@@ -0,0 +1,523 @@
+/*
+ * QEMU 8259 interrupt controller emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/isa.h"
+#include "monitor/monitor.h"
+#include "qemu/timer.h"
+#include "hw/isa/i8259_internal.h"
+
+/* debug PIC */
+//#define DEBUG_PIC
+
+#ifdef DEBUG_PIC
+#define DPRINTF(fmt, ...) \
+ do { printf("pic: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+//#define DEBUG_IRQ_LATENCY
+//#define DEBUG_IRQ_COUNT
+
+#define TYPE_I8259 "isa-i8259"
+#define PIC_CLASS(class) OBJECT_CLASS_CHECK(PICClass, (class), TYPE_I8259)
+#define PIC_GET_CLASS(obj) OBJECT_GET_CLASS(PICClass, (obj), TYPE_I8259)
+
+/**
+ * PICClass:
+ * @parent_realize: The parent's realizefn.
+ */
+typedef struct PICClass {
+ PICCommonClass parent_class;
+
+ DeviceRealize parent_realize;
+} PICClass;
+
+#if defined(DEBUG_PIC) || defined(DEBUG_IRQ_COUNT)
+static int irq_level[16];
+#endif
+#ifdef DEBUG_IRQ_COUNT
+static uint64_t irq_count[16];
+#endif
+#ifdef DEBUG_IRQ_LATENCY
+static int64_t irq_time[16];
+#endif
+DeviceState *isa_pic;
+static PICCommonState *slave_pic;
+
+/* return the highest priority found in mask (highest = smallest
+ number). Return 8 if no irq */
+static int get_priority(PICCommonState *s, int mask)
+{
+ int priority;
+
+ if (mask == 0) {
+ return 8;
+ }
+ priority = 0;
+ while ((mask & (1 << ((priority + s->priority_add) & 7))) == 0) {
+ priority++;
+ }
+ return priority;
+}
+
+/* return the pic wanted interrupt. return -1 if none */
+static int pic_get_irq(PICCommonState *s)
+{
+ int mask, cur_priority, priority;
+
+ mask = s->irr & ~s->imr;
+ priority = get_priority(s, mask);
+ if (priority == 8) {
+ return -1;
+ }
+ /* compute current priority. If special fully nested mode on the
+ master, the IRQ coming from the slave is not taken into account
+ for the priority computation. */
+ mask = s->isr;
+ if (s->special_mask) {
+ mask &= ~s->imr;
+ }
+ if (s->special_fully_nested_mode && s->master) {
+ mask &= ~(1 << 2);
+ }
+ cur_priority = get_priority(s, mask);
+ if (priority < cur_priority) {
+ /* higher priority found: an irq should be generated */
+ return (priority + s->priority_add) & 7;
+ } else {
+ return -1;
+ }
+}
+
+/* Update INT output. Must be called every time the output may have changed. */
+static void pic_update_irq(PICCommonState *s)
+{
+ int irq;
+
+ irq = pic_get_irq(s);
+ if (irq >= 0) {
+ DPRINTF("pic%d: imr=%x irr=%x padd=%d\n",
+ s->master ? 0 : 1, s->imr, s->irr, s->priority_add);
+ qemu_irq_raise(s->int_out[0]);
+ } else {
+ qemu_irq_lower(s->int_out[0]);
+ }
+}
+
+/* set irq level. If an edge is detected, then the IRR is set to 1 */
+static void pic_set_irq(void *opaque, int irq, int level)
+{
+ PICCommonState *s = opaque;
+ int mask = 1 << irq;
+
+#if defined(DEBUG_PIC) || defined(DEBUG_IRQ_COUNT) || \
+ defined(DEBUG_IRQ_LATENCY)
+ int irq_index = s->master ? irq : irq + 8;
+#endif
+#if defined(DEBUG_PIC) || defined(DEBUG_IRQ_COUNT)
+ if (level != irq_level[irq_index]) {
+ DPRINTF("pic_set_irq: irq=%d level=%d\n", irq_index, level);
+ irq_level[irq_index] = level;
+#ifdef DEBUG_IRQ_COUNT
+ if (level == 1) {
+ irq_count[irq_index]++;
+ }
+#endif
+ }
+#endif
+#ifdef DEBUG_IRQ_LATENCY
+ if (level) {
+ irq_time[irq_index] = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+#endif
+
+ if (s->elcr & mask) {
+ /* level triggered */
+ if (level) {
+ s->irr |= mask;
+ s->last_irr |= mask;
+ } else {
+ s->irr &= ~mask;
+ s->last_irr &= ~mask;
+ }
+ } else {
+ /* edge triggered */
+ if (level) {
+ if ((s->last_irr & mask) == 0) {
+ s->irr |= mask;
+ }
+ s->last_irr |= mask;
+ } else {
+ s->last_irr &= ~mask;
+ }
+ }
+ pic_update_irq(s);
+}
+
+/* acknowledge interrupt 'irq' */
+static void pic_intack(PICCommonState *s, int irq)
+{
+ if (s->auto_eoi) {
+ if (s->rotate_on_auto_eoi) {
+ s->priority_add = (irq + 1) & 7;
+ }
+ } else {
+ s->isr |= (1 << irq);
+ }
+ /* We don't clear a level sensitive interrupt here */
+ if (!(s->elcr & (1 << irq))) {
+ s->irr &= ~(1 << irq);
+ }
+ pic_update_irq(s);
+}
+
+int pic_read_irq(DeviceState *d)
+{
+ PICCommonState *s = PIC_COMMON(d);
+ int irq, irq2, intno;
+
+ irq = pic_get_irq(s);
+ if (irq >= 0) {
+ if (irq == 2) {
+ irq2 = pic_get_irq(slave_pic);
+ if (irq2 >= 0) {
+ pic_intack(slave_pic, irq2);
+ } else {
+ /* spurious IRQ on slave controller */
+ irq2 = 7;
+ }
+ intno = slave_pic->irq_base + irq2;
+ } else {
+ intno = s->irq_base + irq;
+ }
+ pic_intack(s, irq);
+ } else {
+ /* spurious IRQ on host controller */
+ irq = 7;
+ intno = s->irq_base + irq;
+ }
+
+#if defined(DEBUG_PIC) || defined(DEBUG_IRQ_LATENCY)
+ if (irq == 2) {
+ irq = irq2 + 8;
+ }
+#endif
+#ifdef DEBUG_IRQ_LATENCY
+ printf("IRQ%d latency=%0.3fus\n",
+ irq,
+ (double)(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
+ irq_time[irq]) * 1000000.0 / get_ticks_per_sec());
+#endif
+ DPRINTF("pic_interrupt: irq=%d\n", irq);
+ return intno;
+}
+
+static void pic_init_reset(PICCommonState *s)
+{
+ pic_reset_common(s);
+ pic_update_irq(s);
+}
+
+static void pic_reset(DeviceState *dev)
+{
+ PICCommonState *s = PIC_COMMON(dev);
+
+ s->elcr = 0;
+ pic_init_reset(s);
+}
+
+static void pic_ioport_write(void *opaque, hwaddr addr64,
+ uint64_t val64, unsigned size)
+{
+ PICCommonState *s = opaque;
+ uint32_t addr = addr64;
+ uint32_t val = val64;
+ int priority, cmd, irq;
+
+ DPRINTF("write: addr=0x%02x val=0x%02x\n", addr, val);
+ if (addr == 0) {
+ if (val & 0x10) {
+ pic_init_reset(s);
+ s->init_state = 1;
+ s->init4 = val & 1;
+ s->single_mode = val & 2;
+ if (val & 0x08) {
+ qemu_log_mask(LOG_UNIMP,
+ "i8259: level sensitive irq not supported\n");
+ }
+ } else if (val & 0x08) {
+ if (val & 0x04) {
+ s->poll = 1;
+ }
+ if (val & 0x02) {
+ s->read_reg_select = val & 1;
+ }
+ if (val & 0x40) {
+ s->special_mask = (val >> 5) & 1;
+ }
+ } else {
+ cmd = val >> 5;
+ switch (cmd) {
+ case 0:
+ case 4:
+ s->rotate_on_auto_eoi = cmd >> 2;
+ break;
+ case 1: /* end of interrupt */
+ case 5:
+ priority = get_priority(s, s->isr);
+ if (priority != 8) {
+ irq = (priority + s->priority_add) & 7;
+ s->isr &= ~(1 << irq);
+ if (cmd == 5) {
+ s->priority_add = (irq + 1) & 7;
+ }
+ pic_update_irq(s);
+ }
+ break;
+ case 3:
+ irq = val & 7;
+ s->isr &= ~(1 << irq);
+ pic_update_irq(s);
+ break;
+ case 6:
+ s->priority_add = (val + 1) & 7;
+ pic_update_irq(s);
+ break;
+ case 7:
+ irq = val & 7;
+ s->isr &= ~(1 << irq);
+ s->priority_add = (irq + 1) & 7;
+ pic_update_irq(s);
+ break;
+ default:
+ /* no operation */
+ break;
+ }
+ }
+ } else {
+ switch (s->init_state) {
+ case 0:
+ /* normal mode */
+ s->imr = val;
+ pic_update_irq(s);
+ break;
+ case 1:
+ s->irq_base = val & 0xf8;
+ s->init_state = s->single_mode ? (s->init4 ? 3 : 0) : 2;
+ break;
+ case 2:
+ if (s->init4) {
+ s->init_state = 3;
+ } else {
+ s->init_state = 0;
+ }
+ break;
+ case 3:
+ s->special_fully_nested_mode = (val >> 4) & 1;
+ s->auto_eoi = (val >> 1) & 1;
+ s->init_state = 0;
+ break;
+ }
+ }
+}
+
+static uint64_t pic_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PICCommonState *s = opaque;
+ int ret;
+
+ if (s->poll) {
+ ret = pic_get_irq(s);
+ if (ret >= 0) {
+ pic_intack(s, ret);
+ ret |= 0x80;
+ } else {
+ ret = 0;
+ }
+ s->poll = 0;
+ } else {
+ if (addr == 0) {
+ if (s->read_reg_select) {
+ ret = s->isr;
+ } else {
+ ret = s->irr;
+ }
+ } else {
+ ret = s->imr;
+ }
+ }
+ DPRINTF("read: addr=0x%02" HWADDR_PRIx " val=0x%02x\n", addr, ret);
+ return ret;
+}
+
+int pic_get_output(DeviceState *d)
+{
+ PICCommonState *s = PIC_COMMON(d);
+
+ return (pic_get_irq(s) >= 0);
+}
+
+static void elcr_ioport_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PICCommonState *s = opaque;
+ s->elcr = val & s->elcr_mask;
+}
+
+static uint64_t elcr_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PICCommonState *s = opaque;
+ return s->elcr;
+}
+
+static const MemoryRegionOps pic_base_ioport_ops = {
+ .read = pic_ioport_read,
+ .write = pic_ioport_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps pic_elcr_ioport_ops = {
+ .read = elcr_ioport_read,
+ .write = elcr_ioport_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void pic_realize(DeviceState *dev, Error **errp)
+{
+ PICCommonState *s = PIC_COMMON(dev);
+ PICClass *pc = PIC_GET_CLASS(dev);
+
+ memory_region_init_io(&s->base_io, OBJECT(s), &pic_base_ioport_ops, s,
+ "pic", 2);
+ memory_region_init_io(&s->elcr_io, OBJECT(s), &pic_elcr_ioport_ops, s,
+ "elcr", 1);
+
+ qdev_init_gpio_out(dev, s->int_out, ARRAY_SIZE(s->int_out));
+ qdev_init_gpio_in(dev, pic_set_irq, 8);
+
+ pc->parent_realize(dev, errp);
+}
+
+void hmp_info_pic(Monitor *mon, const QDict *qdict)
+{
+ int i;
+ PICCommonState *s;
+
+ if (!isa_pic) {
+ return;
+ }
+ for (i = 0; i < 2; i++) {
+ s = i == 0 ? PIC_COMMON(isa_pic) : slave_pic;
+ monitor_printf(mon, "pic%d: irr=%02x imr=%02x isr=%02x hprio=%d "
+ "irq_base=%02x rr_sel=%d elcr=%02x fnm=%d\n",
+ i, s->irr, s->imr, s->isr, s->priority_add,
+ s->irq_base, s->read_reg_select, s->elcr,
+ s->special_fully_nested_mode);
+ }
+}
+
+void hmp_info_irq(Monitor *mon, const QDict *qdict)
+{
+#ifndef DEBUG_IRQ_COUNT
+ monitor_printf(mon, "irq statistic code not compiled.\n");
+#else
+ int i;
+ int64_t count;
+
+ monitor_printf(mon, "IRQ statistics:\n");
+ for (i = 0; i < 16; i++) {
+ count = irq_count[i];
+ if (count > 0) {
+ monitor_printf(mon, "%2d: %" PRId64 "\n", i, count);
+ }
+ }
+#endif
+}
+
+qemu_irq *i8259_init(ISABus *bus, qemu_irq parent_irq)
+{
+ qemu_irq *irq_set;
+ DeviceState *dev;
+ ISADevice *isadev;
+ int i;
+
+ irq_set = g_new0(qemu_irq, ISA_NUM_IRQS);
+
+ isadev = i8259_init_chip(TYPE_I8259, bus, true);
+ dev = DEVICE(isadev);
+
+ qdev_connect_gpio_out(dev, 0, parent_irq);
+ for (i = 0 ; i < 8; i++) {
+ irq_set[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ isa_pic = dev;
+
+ isadev = i8259_init_chip(TYPE_I8259, bus, false);
+ dev = DEVICE(isadev);
+
+ qdev_connect_gpio_out(dev, 0, irq_set[2]);
+ for (i = 0 ; i < 8; i++) {
+ irq_set[i + 8] = qdev_get_gpio_in(dev, i);
+ }
+
+ slave_pic = PIC_COMMON(dev);
+
+ return irq_set;
+}
+
+static void i8259_class_init(ObjectClass *klass, void *data)
+{
+ PICClass *k = PIC_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->parent_realize = dc->realize;
+ dc->realize = pic_realize;
+ dc->reset = pic_reset;
+}
+
+static const TypeInfo i8259_info = {
+ .name = TYPE_I8259,
+ .instance_size = sizeof(PICCommonState),
+ .parent = TYPE_PIC_COMMON,
+ .class_init = i8259_class_init,
+ .class_size = sizeof(PICClass),
+};
+
+static void pic_register_types(void)
+{
+ type_register_static(&i8259_info);
+}
+
+type_init(pic_register_types)
diff --git a/hw/intc/i8259_common.c b/hw/intc/i8259_common.c
new file mode 100644
index 00000000..fbf26e55
--- /dev/null
+++ b/hw/intc/i8259_common.c
@@ -0,0 +1,162 @@
+/*
+ * QEMU 8259 - common bits of emulated and KVM kernel model
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2011 Jan Kiszka, Siemens AG
+ *
+ * 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.
+ */
+#include "hw/i386/pc.h"
+#include "hw/isa/i8259_internal.h"
+
+void pic_reset_common(PICCommonState *s)
+{
+ s->last_irr = 0;
+ s->irr &= s->elcr;
+ s->imr = 0;
+ s->isr = 0;
+ s->priority_add = 0;
+ s->irq_base = 0;
+ s->read_reg_select = 0;
+ s->poll = 0;
+ s->special_mask = 0;
+ s->init_state = 0;
+ s->auto_eoi = 0;
+ s->rotate_on_auto_eoi = 0;
+ s->special_fully_nested_mode = 0;
+ s->init4 = 0;
+ s->single_mode = 0;
+ /* Note: ELCR is not reset */
+}
+
+static void pic_dispatch_pre_save(void *opaque)
+{
+ PICCommonState *s = opaque;
+ PICCommonClass *info = PIC_COMMON_GET_CLASS(s);
+
+ if (info->pre_save) {
+ info->pre_save(s);
+ }
+}
+
+static int pic_dispatch_post_load(void *opaque, int version_id)
+{
+ PICCommonState *s = opaque;
+ PICCommonClass *info = PIC_COMMON_GET_CLASS(s);
+
+ if (info->post_load) {
+ info->post_load(s);
+ }
+ return 0;
+}
+
+static void pic_common_realize(DeviceState *dev, Error **errp)
+{
+ PICCommonState *s = PIC_COMMON(dev);
+
+ isa_register_ioport(NULL, &s->base_io, s->iobase);
+ if (s->elcr_addr != -1) {
+ isa_register_ioport(NULL, &s->elcr_io, s->elcr_addr);
+ }
+
+ qdev_set_legacy_instance_id(dev, s->iobase, 1);
+}
+
+ISADevice *i8259_init_chip(const char *name, ISABus *bus, bool master)
+{
+ DeviceState *dev;
+ ISADevice *isadev;
+
+ isadev = isa_create(bus, name);
+ dev = DEVICE(isadev);
+ qdev_prop_set_uint32(dev, "iobase", master ? 0x20 : 0xa0);
+ qdev_prop_set_uint32(dev, "elcr_addr", master ? 0x4d0 : 0x4d1);
+ qdev_prop_set_uint8(dev, "elcr_mask", master ? 0xf8 : 0xde);
+ qdev_prop_set_bit(dev, "master", master);
+ qdev_init_nofail(dev);
+
+ return isadev;
+}
+
+static const VMStateDescription vmstate_pic_common = {
+ .name = "i8259",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = pic_dispatch_pre_save,
+ .post_load = pic_dispatch_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(last_irr, PICCommonState),
+ VMSTATE_UINT8(irr, PICCommonState),
+ VMSTATE_UINT8(imr, PICCommonState),
+ VMSTATE_UINT8(isr, PICCommonState),
+ VMSTATE_UINT8(priority_add, PICCommonState),
+ VMSTATE_UINT8(irq_base, PICCommonState),
+ VMSTATE_UINT8(read_reg_select, PICCommonState),
+ VMSTATE_UINT8(poll, PICCommonState),
+ VMSTATE_UINT8(special_mask, PICCommonState),
+ VMSTATE_UINT8(init_state, PICCommonState),
+ VMSTATE_UINT8(auto_eoi, PICCommonState),
+ VMSTATE_UINT8(rotate_on_auto_eoi, PICCommonState),
+ VMSTATE_UINT8(special_fully_nested_mode, PICCommonState),
+ VMSTATE_UINT8(init4, PICCommonState),
+ VMSTATE_UINT8(single_mode, PICCommonState),
+ VMSTATE_UINT8(elcr, PICCommonState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property pic_properties_common[] = {
+ DEFINE_PROP_UINT32("iobase", PICCommonState, iobase, -1),
+ DEFINE_PROP_UINT32("elcr_addr", PICCommonState, elcr_addr, -1),
+ DEFINE_PROP_UINT8("elcr_mask", PICCommonState, elcr_mask, -1),
+ DEFINE_PROP_BIT("master", PICCommonState, master, 0, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pic_common_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_pic_common;
+ dc->props = pic_properties_common;
+ dc->realize = pic_common_realize;
+ /*
+ * Reason: unlike ordinary ISA devices, the PICs need additional
+ * wiring: its IRQ input lines are set up by board code, and the
+ * wiring of the slave to the master is hard-coded in device model
+ * code.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo pic_common_type = {
+ .name = TYPE_PIC_COMMON,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(PICCommonState),
+ .class_size = sizeof(PICCommonClass),
+ .class_init = pic_common_class_init,
+ .abstract = true,
+};
+
+static void pic_common_register_types(void)
+{
+ type_register_static(&pic_common_type);
+}
+
+type_init(pic_common_register_types)
diff --git a/hw/intc/imx_avic.c b/hw/intc/imx_avic.c
new file mode 100644
index 00000000..e48f66c8
--- /dev/null
+++ b/hw/intc/imx_avic.c
@@ -0,0 +1,406 @@
+/*
+ * i.MX31 Vectored Interrupt Controller
+ *
+ * Note this is NOT the PL192 provided by ARM, but
+ * a custom implementation by Freescale.
+ *
+ * Copyright (c) 2008 OKL
+ * Copyright (c) 2011 NICTA Pty Ltd
+ * Originally written by Hans Jiang
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ *
+ * TODO: implement vectors.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "qemu/host-utils.h"
+
+#define DEBUG_INT 1
+#undef DEBUG_INT /* comment out for debugging */
+
+#ifdef DEBUG_INT
+#define DPRINTF(fmt, args...) \
+do { printf("imx_avic: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Define to 1 for messages about attempts to
+ * access unimplemented registers or similar.
+ */
+#define DEBUG_IMPLEMENTATION 1
+#if DEBUG_IMPLEMENTATION
+# define IPRINTF(fmt, args...) \
+ do { fprintf(stderr, "imx_avic: " fmt, ##args); } while (0)
+#else
+# define IPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define IMX_AVIC_NUM_IRQS 64
+
+/* Interrupt Control Bits */
+#define ABFLAG (1<<25)
+#define ABFEN (1<<24)
+#define NIDIS (1<<22) /* Normal Interrupt disable */
+#define FIDIS (1<<21) /* Fast interrupt disable */
+#define NIAD (1<<20) /* Normal Interrupt Arbiter Rise ARM level */
+#define FIAD (1<<19) /* Fast Interrupt Arbiter Rise ARM level */
+#define NM (1<<18) /* Normal interrupt mode */
+
+
+#define PRIO_PER_WORD (sizeof(uint32_t) * 8 / 4)
+#define PRIO_WORDS (IMX_AVIC_NUM_IRQS/PRIO_PER_WORD)
+
+#define TYPE_IMX_AVIC "imx_avic"
+#define IMX_AVIC(obj) \
+ OBJECT_CHECK(IMXAVICState, (obj), TYPE_IMX_AVIC)
+
+typedef struct IMXAVICState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint64_t pending;
+ uint64_t enabled;
+ uint64_t is_fiq;
+ uint32_t intcntl;
+ uint32_t intmask;
+ qemu_irq irq;
+ qemu_irq fiq;
+ uint32_t prio[PRIO_WORDS]; /* Priorities are 4-bits each */
+} IMXAVICState;
+
+static const VMStateDescription vmstate_imx_avic = {
+ .name = "imx-avic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(pending, IMXAVICState),
+ VMSTATE_UINT64(enabled, IMXAVICState),
+ VMSTATE_UINT64(is_fiq, IMXAVICState),
+ VMSTATE_UINT32(intcntl, IMXAVICState),
+ VMSTATE_UINT32(intmask, IMXAVICState),
+ VMSTATE_UINT32_ARRAY(prio, IMXAVICState, PRIO_WORDS),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+
+
+static inline int imx_avic_prio(IMXAVICState *s, int irq)
+{
+ uint32_t word = irq / PRIO_PER_WORD;
+ uint32_t part = 4 * (irq % PRIO_PER_WORD);
+ return 0xf & (s->prio[word] >> part);
+}
+
+/* Update interrupts. */
+static void imx_avic_update(IMXAVICState *s)
+{
+ int i;
+ uint64_t new = s->pending & s->enabled;
+ uint64_t flags;
+
+ flags = new & s->is_fiq;
+ qemu_set_irq(s->fiq, !!flags);
+
+ flags = new & ~s->is_fiq;
+ if (!flags || (s->intmask == 0x1f)) {
+ qemu_set_irq(s->irq, !!flags);
+ return;
+ }
+
+ /*
+ * Take interrupt if there's a pending interrupt with
+ * priority higher than the value of intmask
+ */
+ for (i = 0; i < IMX_AVIC_NUM_IRQS; i++) {
+ if (flags & (1UL << i)) {
+ if (imx_avic_prio(s, i) > s->intmask) {
+ qemu_set_irq(s->irq, 1);
+ return;
+ }
+ }
+ }
+ qemu_set_irq(s->irq, 0);
+}
+
+static void imx_avic_set_irq(void *opaque, int irq, int level)
+{
+ IMXAVICState *s = (IMXAVICState *)opaque;
+
+ if (level) {
+ DPRINTF("Raising IRQ %d, prio %d\n",
+ irq, imx_avic_prio(s, irq));
+ s->pending |= (1ULL << irq);
+ } else {
+ DPRINTF("Clearing IRQ %d, prio %d\n",
+ irq, imx_avic_prio(s, irq));
+ s->pending &= ~(1ULL << irq);
+ }
+
+ imx_avic_update(s);
+}
+
+
+static uint64_t imx_avic_read(void *opaque,
+ hwaddr offset, unsigned size)
+{
+ IMXAVICState *s = (IMXAVICState *)opaque;
+
+
+ DPRINTF("read(offset = 0x%x)\n", offset >> 2);
+ switch (offset >> 2) {
+ case 0: /* INTCNTL */
+ return s->intcntl;
+
+ case 1: /* Normal Interrupt Mask Register, NIMASK */
+ return s->intmask;
+
+ case 2: /* Interrupt Enable Number Register, INTENNUM */
+ case 3: /* Interrupt Disable Number Register, INTDISNUM */
+ return 0;
+
+ case 4: /* Interrupt Enabled Number Register High */
+ return s->enabled >> 32;
+
+ case 5: /* Interrupt Enabled Number Register Low */
+ return s->enabled & 0xffffffffULL;
+
+ case 6: /* Interrupt Type Register High */
+ return s->is_fiq >> 32;
+
+ case 7: /* Interrupt Type Register Low */
+ return s->is_fiq & 0xffffffffULL;
+
+ case 8: /* Normal Interrupt Priority Register 7 */
+ case 9: /* Normal Interrupt Priority Register 6 */
+ case 10:/* Normal Interrupt Priority Register 5 */
+ case 11:/* Normal Interrupt Priority Register 4 */
+ case 12:/* Normal Interrupt Priority Register 3 */
+ case 13:/* Normal Interrupt Priority Register 2 */
+ case 14:/* Normal Interrupt Priority Register 1 */
+ case 15:/* Normal Interrupt Priority Register 0 */
+ return s->prio[15-(offset>>2)];
+
+ case 16: /* Normal interrupt vector and status register */
+ {
+ /*
+ * This returns the highest priority
+ * outstanding interrupt. Where there is more than
+ * one pending IRQ with the same priority,
+ * take the highest numbered one.
+ */
+ uint64_t flags = s->pending & s->enabled & ~s->is_fiq;
+ int i;
+ int prio = -1;
+ int irq = -1;
+ for (i = 63; i >= 0; --i) {
+ if (flags & (1ULL<<i)) {
+ int irq_prio = imx_avic_prio(s, i);
+ if (irq_prio > prio) {
+ irq = i;
+ prio = irq_prio;
+ }
+ }
+ }
+ if (irq >= 0) {
+ imx_avic_set_irq(s, irq, 0);
+ return irq << 16 | prio;
+ }
+ return 0xffffffffULL;
+ }
+ case 17:/* Fast Interrupt vector and status register */
+ {
+ uint64_t flags = s->pending & s->enabled & s->is_fiq;
+ int i = ctz64(flags);
+ if (i < 64) {
+ imx_avic_set_irq(opaque, i, 0);
+ return i;
+ }
+ return 0xffffffffULL;
+ }
+ case 18:/* Interrupt source register high */
+ return s->pending >> 32;
+
+ case 19:/* Interrupt source register low */
+ return s->pending & 0xffffffffULL;
+
+ case 20:/* Interrupt Force Register high */
+ case 21:/* Interrupt Force Register low */
+ return 0;
+
+ case 22:/* Normal Interrupt Pending Register High */
+ return (s->pending & s->enabled & ~s->is_fiq) >> 32;
+
+ case 23:/* Normal Interrupt Pending Register Low */
+ return (s->pending & s->enabled & ~s->is_fiq) & 0xffffffffULL;
+
+ case 24: /* Fast Interrupt Pending Register High */
+ return (s->pending & s->enabled & s->is_fiq) >> 32;
+
+ case 25: /* Fast Interrupt Pending Register Low */
+ return (s->pending & s->enabled & s->is_fiq) & 0xffffffffULL;
+
+ case 0x40: /* AVIC vector 0, use for WFI WAR */
+ return 0x4;
+
+ default:
+ IPRINTF("imx_avic_read: Bad offset 0x%x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void imx_avic_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ IMXAVICState *s = (IMXAVICState *)opaque;
+
+ /* Vector Registers not yet supported */
+ if (offset >= 0x100 && offset <= 0x2fc) {
+ IPRINTF("imx_avic_write to vector register %d ignored\n",
+ (unsigned int)((offset - 0x100) >> 2));
+ return;
+ }
+
+ DPRINTF("imx_avic_write(0x%x) = %x\n",
+ (unsigned int)offset>>2, (unsigned int)val);
+ switch (offset >> 2) {
+ case 0: /* Interrupt Control Register, INTCNTL */
+ s->intcntl = val & (ABFEN | NIDIS | FIDIS | NIAD | FIAD | NM);
+ if (s->intcntl & ABFEN) {
+ s->intcntl &= ~(val & ABFLAG);
+ }
+ break;
+
+ case 1: /* Normal Interrupt Mask Register, NIMASK */
+ s->intmask = val & 0x1f;
+ break;
+
+ case 2: /* Interrupt Enable Number Register, INTENNUM */
+ DPRINTF("enable(%d)\n", (int)val);
+ val &= 0x3f;
+ s->enabled |= (1ULL << val);
+ break;
+
+ case 3: /* Interrupt Disable Number Register, INTDISNUM */
+ DPRINTF("disable(%d)\n", (int)val);
+ val &= 0x3f;
+ s->enabled &= ~(1ULL << val);
+ break;
+
+ case 4: /* Interrupt Enable Number Register High */
+ s->enabled = (s->enabled & 0xffffffffULL) | (val << 32);
+ break;
+
+ case 5: /* Interrupt Enable Number Register Low */
+ s->enabled = (s->enabled & 0xffffffff00000000ULL) | val;
+ break;
+
+ case 6: /* Interrupt Type Register High */
+ s->is_fiq = (s->is_fiq & 0xffffffffULL) | (val << 32);
+ break;
+
+ case 7: /* Interrupt Type Register Low */
+ s->is_fiq = (s->is_fiq & 0xffffffff00000000ULL) | val;
+ break;
+
+ case 8: /* Normal Interrupt Priority Register 7 */
+ case 9: /* Normal Interrupt Priority Register 6 */
+ case 10:/* Normal Interrupt Priority Register 5 */
+ case 11:/* Normal Interrupt Priority Register 4 */
+ case 12:/* Normal Interrupt Priority Register 3 */
+ case 13:/* Normal Interrupt Priority Register 2 */
+ case 14:/* Normal Interrupt Priority Register 1 */
+ case 15:/* Normal Interrupt Priority Register 0 */
+ s->prio[15-(offset>>2)] = val;
+ break;
+
+ /* Read-only registers, writes ignored */
+ case 16:/* Normal Interrupt Vector and Status register */
+ case 17:/* Fast Interrupt vector and status register */
+ case 18:/* Interrupt source register high */
+ case 19:/* Interrupt source register low */
+ return;
+
+ case 20:/* Interrupt Force Register high */
+ s->pending = (s->pending & 0xffffffffULL) | (val << 32);
+ break;
+
+ case 21:/* Interrupt Force Register low */
+ s->pending = (s->pending & 0xffffffff00000000ULL) | val;
+ break;
+
+ case 22:/* Normal Interrupt Pending Register High */
+ case 23:/* Normal Interrupt Pending Register Low */
+ case 24: /* Fast Interrupt Pending Register High */
+ case 25: /* Fast Interrupt Pending Register Low */
+ return;
+
+ default:
+ IPRINTF("imx_avic_write: Bad offset %x\n", (int)offset);
+ }
+ imx_avic_update(s);
+}
+
+static const MemoryRegionOps imx_avic_ops = {
+ .read = imx_avic_read,
+ .write = imx_avic_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void imx_avic_reset(DeviceState *dev)
+{
+ IMXAVICState *s = IMX_AVIC(dev);
+
+ s->pending = 0;
+ s->enabled = 0;
+ s->is_fiq = 0;
+ s->intmask = 0x1f;
+ s->intcntl = 0;
+ memset(s->prio, 0, sizeof s->prio);
+}
+
+static int imx_avic_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ IMXAVICState *s = IMX_AVIC(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &imx_avic_ops, s,
+ "imx_avic", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ qdev_init_gpio_in(dev, imx_avic_set_irq, IMX_AVIC_NUM_IRQS);
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->fiq);
+
+ return 0;
+}
+
+
+static void imx_avic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ k->init = imx_avic_init;
+ dc->vmsd = &vmstate_imx_avic;
+ dc->reset = imx_avic_reset;
+ dc->desc = "i.MX Advanced Vector Interrupt Controller";
+}
+
+static const TypeInfo imx_avic_info = {
+ .name = TYPE_IMX_AVIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXAVICState),
+ .class_init = imx_avic_class_init,
+};
+
+static void imx_avic_register_types(void)
+{
+ type_register_static(&imx_avic_info);
+}
+
+type_init(imx_avic_register_types)
diff --git a/hw/intc/ioapic.c b/hw/intc/ioapic.c
new file mode 100644
index 00000000..b5279323
--- /dev/null
+++ b/hw/intc/ioapic.c
@@ -0,0 +1,261 @@
+/*
+ * ioapic.c IOAPIC emulation logic
+ *
+ * Copyright (c) 2004-2005 Fabrice Bellard
+ *
+ * Split the ioapic logic from apic.c
+ * Xiantao Zhang <xiantao.zhang@intel.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/i386/ioapic.h"
+#include "hw/i386/ioapic_internal.h"
+
+//#define DEBUG_IOAPIC
+
+#ifdef DEBUG_IOAPIC
+#define DPRINTF(fmt, ...) \
+ do { printf("ioapic: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+static IOAPICCommonState *ioapics[MAX_IOAPICS];
+
+/* global variable from ioapic_common.c */
+extern int ioapic_no;
+
+static void ioapic_service(IOAPICCommonState *s)
+{
+ uint8_t i;
+ uint8_t trig_mode;
+ uint8_t vector;
+ uint8_t delivery_mode;
+ uint32_t mask;
+ uint64_t entry;
+ uint8_t dest;
+ uint8_t dest_mode;
+
+ for (i = 0; i < IOAPIC_NUM_PINS; i++) {
+ mask = 1 << i;
+ if (s->irr & mask) {
+ entry = s->ioredtbl[i];
+ if (!(entry & IOAPIC_LVT_MASKED)) {
+ trig_mode = ((entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1);
+ dest = entry >> IOAPIC_LVT_DEST_SHIFT;
+ dest_mode = (entry >> IOAPIC_LVT_DEST_MODE_SHIFT) & 1;
+ delivery_mode =
+ (entry >> IOAPIC_LVT_DELIV_MODE_SHIFT) & IOAPIC_DM_MASK;
+ if (trig_mode == IOAPIC_TRIGGER_EDGE) {
+ s->irr &= ~mask;
+ } else {
+ s->ioredtbl[i] |= IOAPIC_LVT_REMOTE_IRR;
+ }
+ if (delivery_mode == IOAPIC_DM_EXTINT) {
+ vector = pic_read_irq(isa_pic);
+ } else {
+ vector = entry & IOAPIC_VECTOR_MASK;
+ }
+ apic_deliver_irq(dest, dest_mode, delivery_mode,
+ vector, trig_mode);
+ }
+ }
+ }
+}
+
+static void ioapic_set_irq(void *opaque, int vector, int level)
+{
+ IOAPICCommonState *s = opaque;
+
+ /* ISA IRQs map to GSI 1-1 except for IRQ0 which maps
+ * to GSI 2. GSI maps to ioapic 1-1. This is not
+ * the cleanest way of doing it but it should work. */
+
+ DPRINTF("%s: %s vec %x\n", __func__, level ? "raise" : "lower", vector);
+ if (vector == 0) {
+ vector = 2;
+ }
+ if (vector >= 0 && vector < IOAPIC_NUM_PINS) {
+ uint32_t mask = 1 << vector;
+ uint64_t entry = s->ioredtbl[vector];
+
+ if (((entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1) ==
+ IOAPIC_TRIGGER_LEVEL) {
+ /* level triggered */
+ if (level) {
+ s->irr |= mask;
+ ioapic_service(s);
+ } else {
+ s->irr &= ~mask;
+ }
+ } else {
+ /* According to the 82093AA manual, we must ignore edge requests
+ * if the input pin is masked. */
+ if (level && !(entry & IOAPIC_LVT_MASKED)) {
+ s->irr |= mask;
+ ioapic_service(s);
+ }
+ }
+ }
+}
+
+void ioapic_eoi_broadcast(int vector)
+{
+ IOAPICCommonState *s;
+ uint64_t entry;
+ int i, n;
+
+ for (i = 0; i < MAX_IOAPICS; i++) {
+ s = ioapics[i];
+ if (!s) {
+ continue;
+ }
+ for (n = 0; n < IOAPIC_NUM_PINS; n++) {
+ entry = s->ioredtbl[n];
+ if ((entry & IOAPIC_LVT_REMOTE_IRR)
+ && (entry & IOAPIC_VECTOR_MASK) == vector) {
+ s->ioredtbl[n] = entry & ~IOAPIC_LVT_REMOTE_IRR;
+ if (!(entry & IOAPIC_LVT_MASKED) && (s->irr & (1 << n))) {
+ ioapic_service(s);
+ }
+ }
+ }
+ }
+}
+
+static uint64_t
+ioapic_mem_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ IOAPICCommonState *s = opaque;
+ int index;
+ uint32_t val = 0;
+
+ switch (addr & 0xff) {
+ case IOAPIC_IOREGSEL:
+ val = s->ioregsel;
+ break;
+ case IOAPIC_IOWIN:
+ if (size != 4) {
+ break;
+ }
+ switch (s->ioregsel) {
+ case IOAPIC_REG_ID:
+ val = s->id << IOAPIC_ID_SHIFT;
+ break;
+ case IOAPIC_REG_VER:
+ val = IOAPIC_VERSION |
+ ((IOAPIC_NUM_PINS - 1) << IOAPIC_VER_ENTRIES_SHIFT);
+ break;
+ case IOAPIC_REG_ARB:
+ val = 0;
+ break;
+ default:
+ index = (s->ioregsel - IOAPIC_REG_REDTBL_BASE) >> 1;
+ if (index >= 0 && index < IOAPIC_NUM_PINS) {
+ if (s->ioregsel & 1) {
+ val = s->ioredtbl[index] >> 32;
+ } else {
+ val = s->ioredtbl[index] & 0xffffffff;
+ }
+ }
+ }
+ DPRINTF("read: %08x = %08x\n", s->ioregsel, val);
+ break;
+ }
+ return val;
+}
+
+static void
+ioapic_mem_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ IOAPICCommonState *s = opaque;
+ int index;
+
+ switch (addr & 0xff) {
+ case IOAPIC_IOREGSEL:
+ s->ioregsel = val;
+ break;
+ case IOAPIC_IOWIN:
+ if (size != 4) {
+ break;
+ }
+ DPRINTF("write: %08x = %08" PRIx64 "\n", s->ioregsel, val);
+ switch (s->ioregsel) {
+ case IOAPIC_REG_ID:
+ s->id = (val >> IOAPIC_ID_SHIFT) & IOAPIC_ID_MASK;
+ break;
+ case IOAPIC_REG_VER:
+ case IOAPIC_REG_ARB:
+ break;
+ default:
+ index = (s->ioregsel - IOAPIC_REG_REDTBL_BASE) >> 1;
+ if (index >= 0 && index < IOAPIC_NUM_PINS) {
+ if (s->ioregsel & 1) {
+ s->ioredtbl[index] &= 0xffffffff;
+ s->ioredtbl[index] |= (uint64_t)val << 32;
+ } else {
+ s->ioredtbl[index] &= ~0xffffffffULL;
+ s->ioredtbl[index] |= val;
+ }
+ ioapic_service(s);
+ }
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps ioapic_io_ops = {
+ .read = ioapic_mem_read,
+ .write = ioapic_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ioapic_realize(DeviceState *dev, Error **errp)
+{
+ IOAPICCommonState *s = IOAPIC_COMMON(dev);
+
+ memory_region_init_io(&s->io_memory, OBJECT(s), &ioapic_io_ops, s,
+ "ioapic", 0x1000);
+
+ qdev_init_gpio_in(dev, ioapic_set_irq, IOAPIC_NUM_PINS);
+
+ ioapics[ioapic_no] = s;
+}
+
+static void ioapic_class_init(ObjectClass *klass, void *data)
+{
+ IOAPICCommonClass *k = IOAPIC_COMMON_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = ioapic_realize;
+ dc->reset = ioapic_reset_common;
+}
+
+static const TypeInfo ioapic_info = {
+ .name = "ioapic",
+ .parent = TYPE_IOAPIC_COMMON,
+ .instance_size = sizeof(IOAPICCommonState),
+ .class_init = ioapic_class_init,
+};
+
+static void ioapic_register_types(void)
+{
+ type_register_static(&ioapic_info);
+}
+
+type_init(ioapic_register_types)
diff --git a/hw/intc/ioapic_common.c b/hw/intc/ioapic_common.c
new file mode 100644
index 00000000..8b7d1180
--- /dev/null
+++ b/hw/intc/ioapic_common.c
@@ -0,0 +1,123 @@
+/*
+ * IOAPIC emulation logic - common bits of emulated and KVM kernel model
+ *
+ * Copyright (c) 2004-2005 Fabrice Bellard
+ * Copyright (c) 2009 Xiantao Zhang, Intel
+ * Copyright (c) 2011 Jan Kiszka, Siemens AG
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/i386/ioapic.h"
+#include "hw/i386/ioapic_internal.h"
+#include "hw/sysbus.h"
+
+/* ioapic_no count start from 0 to MAX_IOAPICS,
+ * remove as static variable from ioapic_common_init.
+ * now as a global variable, let child to increase the counter
+ * then we can drop the 'instance_no' argument
+ * and convert to our QOM's realize function
+ */
+int ioapic_no;
+
+void ioapic_reset_common(DeviceState *dev)
+{
+ IOAPICCommonState *s = IOAPIC_COMMON(dev);
+ int i;
+
+ s->id = 0;
+ s->ioregsel = 0;
+ s->irr = 0;
+ for (i = 0; i < IOAPIC_NUM_PINS; i++) {
+ s->ioredtbl[i] = 1 << IOAPIC_LVT_MASKED_SHIFT;
+ }
+}
+
+static void ioapic_dispatch_pre_save(void *opaque)
+{
+ IOAPICCommonState *s = IOAPIC_COMMON(opaque);
+ IOAPICCommonClass *info = IOAPIC_COMMON_GET_CLASS(s);
+
+ if (info->pre_save) {
+ info->pre_save(s);
+ }
+}
+
+static int ioapic_dispatch_post_load(void *opaque, int version_id)
+{
+ IOAPICCommonState *s = IOAPIC_COMMON(opaque);
+ IOAPICCommonClass *info = IOAPIC_COMMON_GET_CLASS(s);
+
+ if (info->post_load) {
+ info->post_load(s);
+ }
+ return 0;
+}
+
+static void ioapic_common_realize(DeviceState *dev, Error **errp)
+{
+ IOAPICCommonState *s = IOAPIC_COMMON(dev);
+ IOAPICCommonClass *info;
+
+ if (ioapic_no >= MAX_IOAPICS) {
+ error_setg(errp, "Only %d ioapics allowed", MAX_IOAPICS);
+ return;
+ }
+
+ info = IOAPIC_COMMON_GET_CLASS(s);
+ info->realize(dev, errp);
+
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->io_memory);
+ ioapic_no++;
+}
+
+static const VMStateDescription vmstate_ioapic_common = {
+ .name = "ioapic",
+ .version_id = 3,
+ .minimum_version_id = 1,
+ .pre_save = ioapic_dispatch_pre_save,
+ .post_load = ioapic_dispatch_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(id, IOAPICCommonState),
+ VMSTATE_UINT8(ioregsel, IOAPICCommonState),
+ VMSTATE_UNUSED_V(2, 8), /* to account for qemu-kvm's v2 format */
+ VMSTATE_UINT32_V(irr, IOAPICCommonState, 2),
+ VMSTATE_UINT64_ARRAY(ioredtbl, IOAPICCommonState, IOAPIC_NUM_PINS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ioapic_common_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = ioapic_common_realize;
+ dc->vmsd = &vmstate_ioapic_common;
+}
+
+static const TypeInfo ioapic_common_type = {
+ .name = TYPE_IOAPIC_COMMON,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IOAPICCommonState),
+ .class_size = sizeof(IOAPICCommonClass),
+ .class_init = ioapic_common_class_init,
+ .abstract = true,
+};
+
+static void ioapic_common_register_types(void)
+{
+ type_register_static(&ioapic_common_type);
+}
+
+type_init(ioapic_common_register_types)
diff --git a/hw/intc/lm32_pic.c b/hw/intc/lm32_pic.c
new file mode 100644
index 00000000..641ee472
--- /dev/null
+++ b/hw/intc/lm32_pic.c
@@ -0,0 +1,203 @@
+/*
+ * LatticeMico32 CPU interrupt controller logic.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "monitor/monitor.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "hw/lm32/lm32_pic.h"
+
+#define TYPE_LM32_PIC "lm32-pic"
+#define LM32_PIC(obj) OBJECT_CHECK(LM32PicState, (obj), TYPE_LM32_PIC)
+
+struct LM32PicState {
+ SysBusDevice parent_obj;
+
+ qemu_irq parent_irq;
+ uint32_t im; /* interrupt mask */
+ uint32_t ip; /* interrupt pending */
+ uint32_t irq_state;
+
+ /* statistics */
+ uint32_t stats_irq_count[32];
+};
+typedef struct LM32PicState LM32PicState;
+
+static LM32PicState *pic;
+void lm32_hmp_info_pic(Monitor *mon, const QDict *qdict)
+{
+ if (pic == NULL) {
+ return;
+ }
+
+ monitor_printf(mon, "lm32-pic: im=%08x ip=%08x irq_state=%08x\n",
+ pic->im, pic->ip, pic->irq_state);
+}
+
+void lm32_hmp_info_irq(Monitor *mon, const QDict *qdict)
+{
+ int i;
+ uint32_t count;
+
+ if (pic == NULL) {
+ return;
+ }
+
+ monitor_printf(mon, "IRQ statistics:\n");
+ for (i = 0; i < 32; i++) {
+ count = pic->stats_irq_count[i];
+ if (count > 0) {
+ monitor_printf(mon, "%2d: %u\n", i, count);
+ }
+ }
+}
+
+static void update_irq(LM32PicState *s)
+{
+ s->ip |= s->irq_state;
+
+ if (s->ip & s->im) {
+ trace_lm32_pic_raise_irq();
+ qemu_irq_raise(s->parent_irq);
+ } else {
+ trace_lm32_pic_lower_irq();
+ qemu_irq_lower(s->parent_irq);
+ }
+}
+
+static void irq_handler(void *opaque, int irq, int level)
+{
+ LM32PicState *s = opaque;
+
+ assert(irq < 32);
+ trace_lm32_pic_interrupt(irq, level);
+
+ if (level) {
+ s->irq_state |= (1 << irq);
+ s->stats_irq_count[irq]++;
+ } else {
+ s->irq_state &= ~(1 << irq);
+ }
+
+ update_irq(s);
+}
+
+void lm32_pic_set_im(DeviceState *d, uint32_t im)
+{
+ LM32PicState *s = LM32_PIC(d);
+
+ trace_lm32_pic_set_im(im);
+ s->im = im;
+
+ update_irq(s);
+}
+
+void lm32_pic_set_ip(DeviceState *d, uint32_t ip)
+{
+ LM32PicState *s = LM32_PIC(d);
+
+ trace_lm32_pic_set_ip(ip);
+
+ /* ack interrupt */
+ s->ip &= ~ip;
+
+ update_irq(s);
+}
+
+uint32_t lm32_pic_get_im(DeviceState *d)
+{
+ LM32PicState *s = LM32_PIC(d);
+
+ trace_lm32_pic_get_im(s->im);
+ return s->im;
+}
+
+uint32_t lm32_pic_get_ip(DeviceState *d)
+{
+ LM32PicState *s = LM32_PIC(d);
+
+ trace_lm32_pic_get_ip(s->ip);
+ return s->ip;
+}
+
+static void pic_reset(DeviceState *d)
+{
+ LM32PicState *s = LM32_PIC(d);
+ int i;
+
+ s->im = 0;
+ s->ip = 0;
+ s->irq_state = 0;
+ for (i = 0; i < 32; i++) {
+ s->stats_irq_count[i] = 0;
+ }
+}
+
+static int lm32_pic_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ LM32PicState *s = LM32_PIC(dev);
+
+ qdev_init_gpio_in(dev, irq_handler, 32);
+ sysbus_init_irq(sbd, &s->parent_irq);
+
+ pic = s;
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm32_pic = {
+ .name = "lm32-pic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(im, LM32PicState),
+ VMSTATE_UINT32(ip, LM32PicState),
+ VMSTATE_UINT32(irq_state, LM32PicState),
+ VMSTATE_UINT32_ARRAY(stats_irq_count, LM32PicState, 32),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void lm32_pic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lm32_pic_init;
+ dc->reset = pic_reset;
+ dc->vmsd = &vmstate_lm32_pic;
+}
+
+static const TypeInfo lm32_pic_info = {
+ .name = TYPE_LM32_PIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LM32PicState),
+ .class_init = lm32_pic_class_init,
+};
+
+static void lm32_pic_register_types(void)
+{
+ type_register_static(&lm32_pic_info);
+}
+
+type_init(lm32_pic_register_types)
diff --git a/hw/intc/omap_intc.c b/hw/intc/omap_intc.c
new file mode 100644
index 00000000..e9b38a3c
--- /dev/null
+++ b/hw/intc/omap_intc.c
@@ -0,0 +1,667 @@
+/*
+ * TI OMAP interrupt controller emulation.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+#include "hw/sysbus.h"
+
+/* Interrupt Handlers */
+struct omap_intr_handler_bank_s {
+ uint32_t irqs;
+ uint32_t inputs;
+ uint32_t mask;
+ uint32_t fiq;
+ uint32_t sens_edge;
+ uint32_t swi;
+ unsigned char priority[32];
+};
+
+#define TYPE_OMAP_INTC "common-omap-intc"
+#define OMAP_INTC(obj) \
+ OBJECT_CHECK(struct omap_intr_handler_s, (obj), TYPE_OMAP_INTC)
+
+struct omap_intr_handler_s {
+ SysBusDevice parent_obj;
+
+ qemu_irq *pins;
+ qemu_irq parent_intr[2];
+ MemoryRegion mmio;
+ void *iclk;
+ void *fclk;
+ unsigned char nbanks;
+ int level_only;
+ uint32_t size;
+
+ uint8_t revision;
+
+ /* state */
+ uint32_t new_agr[2];
+ int sir_intr[2];
+ int autoidle;
+ uint32_t mask;
+ struct omap_intr_handler_bank_s bank[3];
+};
+
+static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq)
+{
+ int i, j, sir_intr, p_intr, p;
+ uint32_t level;
+ sir_intr = 0;
+ p_intr = 255;
+
+ /* Find the interrupt line with the highest dynamic priority.
+ * Note: 0 denotes the hightest priority.
+ * If all interrupts have the same priority, the default order is IRQ_N,
+ * IRQ_N-1,...,IRQ_0. */
+ for (j = 0; j < s->nbanks; ++j) {
+ level = s->bank[j].irqs & ~s->bank[j].mask &
+ (is_fiq ? s->bank[j].fiq : ~s->bank[j].fiq);
+
+ while (level != 0) {
+ i = ctz32(level);
+ p = s->bank[j].priority[i];
+ if (p <= p_intr) {
+ p_intr = p;
+ sir_intr = 32 * j + i;
+ }
+ level &= level - 1;
+ }
+ }
+ s->sir_intr[is_fiq] = sir_intr;
+}
+
+static inline void omap_inth_update(struct omap_intr_handler_s *s, int is_fiq)
+{
+ int i;
+ uint32_t has_intr = 0;
+
+ for (i = 0; i < s->nbanks; ++i)
+ has_intr |= s->bank[i].irqs & ~s->bank[i].mask &
+ (is_fiq ? s->bank[i].fiq : ~s->bank[i].fiq);
+
+ if (s->new_agr[is_fiq] & has_intr & s->mask) {
+ s->new_agr[is_fiq] = 0;
+ omap_inth_sir_update(s, is_fiq);
+ qemu_set_irq(s->parent_intr[is_fiq], 1);
+ }
+}
+
+#define INT_FALLING_EDGE 0
+#define INT_LOW_LEVEL 1
+
+static void omap_set_intr(void *opaque, int irq, int req)
+{
+ struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque;
+ uint32_t rise;
+
+ struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5];
+ int n = irq & 31;
+
+ if (req) {
+ rise = ~bank->irqs & (1 << n);
+ if (~bank->sens_edge & (1 << n))
+ rise &= ~bank->inputs;
+
+ bank->inputs |= (1 << n);
+ if (rise) {
+ bank->irqs |= rise;
+ omap_inth_update(ih, 0);
+ omap_inth_update(ih, 1);
+ }
+ } else {
+ rise = bank->sens_edge & bank->irqs & (1 << n);
+ bank->irqs &= ~rise;
+ bank->inputs &= ~(1 << n);
+ }
+}
+
+/* Simplified version with no edge detection */
+static void omap_set_intr_noedge(void *opaque, int irq, int req)
+{
+ struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque;
+ uint32_t rise;
+
+ struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5];
+ int n = irq & 31;
+
+ if (req) {
+ rise = ~bank->inputs & (1 << n);
+ if (rise) {
+ bank->irqs |= bank->inputs |= rise;
+ omap_inth_update(ih, 0);
+ omap_inth_update(ih, 1);
+ }
+ } else
+ bank->irqs = (bank->inputs &= ~(1 << n)) | bank->swi;
+}
+
+static uint64_t omap_inth_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
+ int i, offset = addr;
+ int bank_no = offset >> 8;
+ int line_no;
+ struct omap_intr_handler_bank_s *bank = &s->bank[bank_no];
+ offset &= 0xff;
+
+ switch (offset) {
+ case 0x00: /* ITR */
+ return bank->irqs;
+
+ case 0x04: /* MIR */
+ return bank->mask;
+
+ case 0x10: /* SIR_IRQ_CODE */
+ case 0x14: /* SIR_FIQ_CODE */
+ if (bank_no != 0)
+ break;
+ line_no = s->sir_intr[(offset - 0x10) >> 2];
+ bank = &s->bank[line_no >> 5];
+ i = line_no & 31;
+ if (((bank->sens_edge >> i) & 1) == INT_FALLING_EDGE)
+ bank->irqs &= ~(1 << i);
+ return line_no;
+
+ case 0x18: /* CONTROL_REG */
+ if (bank_no != 0)
+ break;
+ return 0;
+
+ case 0x1c: /* ILR0 */
+ case 0x20: /* ILR1 */
+ case 0x24: /* ILR2 */
+ case 0x28: /* ILR3 */
+ case 0x2c: /* ILR4 */
+ case 0x30: /* ILR5 */
+ case 0x34: /* ILR6 */
+ case 0x38: /* ILR7 */
+ case 0x3c: /* ILR8 */
+ case 0x40: /* ILR9 */
+ case 0x44: /* ILR10 */
+ case 0x48: /* ILR11 */
+ case 0x4c: /* ILR12 */
+ case 0x50: /* ILR13 */
+ case 0x54: /* ILR14 */
+ case 0x58: /* ILR15 */
+ case 0x5c: /* ILR16 */
+ case 0x60: /* ILR17 */
+ case 0x64: /* ILR18 */
+ case 0x68: /* ILR19 */
+ case 0x6c: /* ILR20 */
+ case 0x70: /* ILR21 */
+ case 0x74: /* ILR22 */
+ case 0x78: /* ILR23 */
+ case 0x7c: /* ILR24 */
+ case 0x80: /* ILR25 */
+ case 0x84: /* ILR26 */
+ case 0x88: /* ILR27 */
+ case 0x8c: /* ILR28 */
+ case 0x90: /* ILR29 */
+ case 0x94: /* ILR30 */
+ case 0x98: /* ILR31 */
+ i = (offset - 0x1c) >> 2;
+ return (bank->priority[i] << 2) |
+ (((bank->sens_edge >> i) & 1) << 1) |
+ ((bank->fiq >> i) & 1);
+
+ case 0x9c: /* ISR */
+ return 0x00000000;
+
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_inth_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
+ int i, offset = addr;
+ int bank_no = offset >> 8;
+ struct omap_intr_handler_bank_s *bank = &s->bank[bank_no];
+ offset &= 0xff;
+
+ switch (offset) {
+ case 0x00: /* ITR */
+ /* Important: ignore the clearing if the IRQ is level-triggered and
+ the input bit is 1 */
+ bank->irqs &= value | (bank->inputs & bank->sens_edge);
+ return;
+
+ case 0x04: /* MIR */
+ bank->mask = value;
+ omap_inth_update(s, 0);
+ omap_inth_update(s, 1);
+ return;
+
+ case 0x10: /* SIR_IRQ_CODE */
+ case 0x14: /* SIR_FIQ_CODE */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x18: /* CONTROL_REG */
+ if (bank_no != 0)
+ break;
+ if (value & 2) {
+ qemu_set_irq(s->parent_intr[1], 0);
+ s->new_agr[1] = ~0;
+ omap_inth_update(s, 1);
+ }
+ if (value & 1) {
+ qemu_set_irq(s->parent_intr[0], 0);
+ s->new_agr[0] = ~0;
+ omap_inth_update(s, 0);
+ }
+ return;
+
+ case 0x1c: /* ILR0 */
+ case 0x20: /* ILR1 */
+ case 0x24: /* ILR2 */
+ case 0x28: /* ILR3 */
+ case 0x2c: /* ILR4 */
+ case 0x30: /* ILR5 */
+ case 0x34: /* ILR6 */
+ case 0x38: /* ILR7 */
+ case 0x3c: /* ILR8 */
+ case 0x40: /* ILR9 */
+ case 0x44: /* ILR10 */
+ case 0x48: /* ILR11 */
+ case 0x4c: /* ILR12 */
+ case 0x50: /* ILR13 */
+ case 0x54: /* ILR14 */
+ case 0x58: /* ILR15 */
+ case 0x5c: /* ILR16 */
+ case 0x60: /* ILR17 */
+ case 0x64: /* ILR18 */
+ case 0x68: /* ILR19 */
+ case 0x6c: /* ILR20 */
+ case 0x70: /* ILR21 */
+ case 0x74: /* ILR22 */
+ case 0x78: /* ILR23 */
+ case 0x7c: /* ILR24 */
+ case 0x80: /* ILR25 */
+ case 0x84: /* ILR26 */
+ case 0x88: /* ILR27 */
+ case 0x8c: /* ILR28 */
+ case 0x90: /* ILR29 */
+ case 0x94: /* ILR30 */
+ case 0x98: /* ILR31 */
+ i = (offset - 0x1c) >> 2;
+ bank->priority[i] = (value >> 2) & 0x1f;
+ bank->sens_edge &= ~(1 << i);
+ bank->sens_edge |= ((value >> 1) & 1) << i;
+ bank->fiq &= ~(1 << i);
+ bank->fiq |= (value & 1) << i;
+ return;
+
+ case 0x9c: /* ISR */
+ for (i = 0; i < 32; i ++)
+ if (value & (1 << i)) {
+ omap_set_intr(s, 32 * bank_no + i, 1);
+ return;
+ }
+ return;
+ }
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap_inth_mem_ops = {
+ .read = omap_inth_read,
+ .write = omap_inth_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void omap_inth_reset(DeviceState *dev)
+{
+ struct omap_intr_handler_s *s = OMAP_INTC(dev);
+ int i;
+
+ for (i = 0; i < s->nbanks; ++i){
+ s->bank[i].irqs = 0x00000000;
+ s->bank[i].mask = 0xffffffff;
+ s->bank[i].sens_edge = 0x00000000;
+ s->bank[i].fiq = 0x00000000;
+ s->bank[i].inputs = 0x00000000;
+ s->bank[i].swi = 0x00000000;
+ memset(s->bank[i].priority, 0, sizeof(s->bank[i].priority));
+
+ if (s->level_only)
+ s->bank[i].sens_edge = 0xffffffff;
+ }
+
+ s->new_agr[0] = ~0;
+ s->new_agr[1] = ~0;
+ s->sir_intr[0] = 0;
+ s->sir_intr[1] = 0;
+ s->autoidle = 0;
+ s->mask = ~0;
+
+ qemu_set_irq(s->parent_intr[0], 0);
+ qemu_set_irq(s->parent_intr[1], 0);
+}
+
+static int omap_intc_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ struct omap_intr_handler_s *s = OMAP_INTC(dev);
+
+ if (!s->iclk) {
+ hw_error("omap-intc: clk not connected\n");
+ }
+ s->nbanks = 1;
+ sysbus_init_irq(sbd, &s->parent_intr[0]);
+ sysbus_init_irq(sbd, &s->parent_intr[1]);
+ qdev_init_gpio_in(dev, omap_set_intr, s->nbanks * 32);
+ memory_region_init_io(&s->mmio, OBJECT(s), &omap_inth_mem_ops, s,
+ "omap-intc", s->size);
+ sysbus_init_mmio(sbd, &s->mmio);
+ return 0;
+}
+
+static Property omap_intc_properties[] = {
+ DEFINE_PROP_UINT32("size", struct omap_intr_handler_s, size, 0x100),
+ DEFINE_PROP_PTR("clk", struct omap_intr_handler_s, iclk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void omap_intc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = omap_intc_init;
+ dc->reset = omap_inth_reset;
+ dc->props = omap_intc_properties;
+ /* Reason: pointer property "clk" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo omap_intc_info = {
+ .name = "omap-intc",
+ .parent = TYPE_OMAP_INTC,
+ .class_init = omap_intc_class_init,
+};
+
+static uint64_t omap2_inth_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
+ int offset = addr;
+ int bank_no, line_no;
+ struct omap_intr_handler_bank_s *bank = NULL;
+
+ if ((offset & 0xf80) == 0x80) {
+ bank_no = (offset & 0x60) >> 5;
+ if (bank_no < s->nbanks) {
+ offset &= ~0x60;
+ bank = &s->bank[bank_no];
+ } else {
+ OMAP_BAD_REG(addr);
+ return 0;
+ }
+ }
+
+ switch (offset) {
+ case 0x00: /* INTC_REVISION */
+ return s->revision;
+
+ case 0x10: /* INTC_SYSCONFIG */
+ return (s->autoidle >> 2) & 1;
+
+ case 0x14: /* INTC_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x40: /* INTC_SIR_IRQ */
+ return s->sir_intr[0];
+
+ case 0x44: /* INTC_SIR_FIQ */
+ return s->sir_intr[1];
+
+ case 0x48: /* INTC_CONTROL */
+ return (!s->mask) << 2; /* GLOBALMASK */
+
+ case 0x4c: /* INTC_PROTECTION */
+ return 0;
+
+ case 0x50: /* INTC_IDLE */
+ return s->autoidle & 3;
+
+ /* Per-bank registers */
+ case 0x80: /* INTC_ITR */
+ return bank->inputs;
+
+ case 0x84: /* INTC_MIR */
+ return bank->mask;
+
+ case 0x88: /* INTC_MIR_CLEAR */
+ case 0x8c: /* INTC_MIR_SET */
+ return 0;
+
+ case 0x90: /* INTC_ISR_SET */
+ return bank->swi;
+
+ case 0x94: /* INTC_ISR_CLEAR */
+ return 0;
+
+ case 0x98: /* INTC_PENDING_IRQ */
+ return bank->irqs & ~bank->mask & ~bank->fiq;
+
+ case 0x9c: /* INTC_PENDING_FIQ */
+ return bank->irqs & ~bank->mask & bank->fiq;
+
+ /* Per-line registers */
+ case 0x100 ... 0x300: /* INTC_ILR */
+ bank_no = (offset - 0x100) >> 7;
+ if (bank_no > s->nbanks)
+ break;
+ bank = &s->bank[bank_no];
+ line_no = (offset & 0x7f) >> 2;
+ return (bank->priority[line_no] << 2) |
+ ((bank->fiq >> line_no) & 1);
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap2_inth_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
+ int offset = addr;
+ int bank_no, line_no;
+ struct omap_intr_handler_bank_s *bank = NULL;
+
+ if ((offset & 0xf80) == 0x80) {
+ bank_no = (offset & 0x60) >> 5;
+ if (bank_no < s->nbanks) {
+ offset &= ~0x60;
+ bank = &s->bank[bank_no];
+ } else {
+ OMAP_BAD_REG(addr);
+ return;
+ }
+ }
+
+ switch (offset) {
+ case 0x10: /* INTC_SYSCONFIG */
+ s->autoidle &= 4;
+ s->autoidle |= (value & 1) << 2;
+ if (value & 2) { /* SOFTRESET */
+ omap_inth_reset(DEVICE(s));
+ }
+ return;
+
+ case 0x48: /* INTC_CONTROL */
+ s->mask = (value & 4) ? 0 : ~0; /* GLOBALMASK */
+ if (value & 2) { /* NEWFIQAGR */
+ qemu_set_irq(s->parent_intr[1], 0);
+ s->new_agr[1] = ~0;
+ omap_inth_update(s, 1);
+ }
+ if (value & 1) { /* NEWIRQAGR */
+ qemu_set_irq(s->parent_intr[0], 0);
+ s->new_agr[0] = ~0;
+ omap_inth_update(s, 0);
+ }
+ return;
+
+ case 0x4c: /* INTC_PROTECTION */
+ /* TODO: Make a bitmap (or sizeof(char)map) of access privileges
+ * for every register, see Chapter 3 and 4 for privileged mode. */
+ if (value & 1)
+ fprintf(stderr, "%s: protection mode enable attempt\n",
+ __FUNCTION__);
+ return;
+
+ case 0x50: /* INTC_IDLE */
+ s->autoidle &= ~3;
+ s->autoidle |= value & 3;
+ return;
+
+ /* Per-bank registers */
+ case 0x84: /* INTC_MIR */
+ bank->mask = value;
+ omap_inth_update(s, 0);
+ omap_inth_update(s, 1);
+ return;
+
+ case 0x88: /* INTC_MIR_CLEAR */
+ bank->mask &= ~value;
+ omap_inth_update(s, 0);
+ omap_inth_update(s, 1);
+ return;
+
+ case 0x8c: /* INTC_MIR_SET */
+ bank->mask |= value;
+ return;
+
+ case 0x90: /* INTC_ISR_SET */
+ bank->irqs |= bank->swi |= value;
+ omap_inth_update(s, 0);
+ omap_inth_update(s, 1);
+ return;
+
+ case 0x94: /* INTC_ISR_CLEAR */
+ bank->swi &= ~value;
+ bank->irqs = bank->swi & bank->inputs;
+ return;
+
+ /* Per-line registers */
+ case 0x100 ... 0x300: /* INTC_ILR */
+ bank_no = (offset - 0x100) >> 7;
+ if (bank_no > s->nbanks)
+ break;
+ bank = &s->bank[bank_no];
+ line_no = (offset & 0x7f) >> 2;
+ bank->priority[line_no] = (value >> 2) & 0x3f;
+ bank->fiq &= ~(1 << line_no);
+ bank->fiq |= (value & 1) << line_no;
+ return;
+
+ case 0x00: /* INTC_REVISION */
+ case 0x14: /* INTC_SYSSTATUS */
+ case 0x40: /* INTC_SIR_IRQ */
+ case 0x44: /* INTC_SIR_FIQ */
+ case 0x80: /* INTC_ITR */
+ case 0x98: /* INTC_PENDING_IRQ */
+ case 0x9c: /* INTC_PENDING_FIQ */
+ OMAP_RO_REG(addr);
+ return;
+ }
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap2_inth_mem_ops = {
+ .read = omap2_inth_read,
+ .write = omap2_inth_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static int omap2_intc_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ struct omap_intr_handler_s *s = OMAP_INTC(dev);
+
+ if (!s->iclk) {
+ hw_error("omap2-intc: iclk not connected\n");
+ }
+ if (!s->fclk) {
+ hw_error("omap2-intc: fclk not connected\n");
+ }
+ s->level_only = 1;
+ s->nbanks = 3;
+ sysbus_init_irq(sbd, &s->parent_intr[0]);
+ sysbus_init_irq(sbd, &s->parent_intr[1]);
+ qdev_init_gpio_in(dev, omap_set_intr_noedge, s->nbanks * 32);
+ memory_region_init_io(&s->mmio, OBJECT(s), &omap2_inth_mem_ops, s,
+ "omap2-intc", 0x1000);
+ sysbus_init_mmio(sbd, &s->mmio);
+ return 0;
+}
+
+static Property omap2_intc_properties[] = {
+ DEFINE_PROP_UINT8("revision", struct omap_intr_handler_s,
+ revision, 0x21),
+ DEFINE_PROP_PTR("iclk", struct omap_intr_handler_s, iclk),
+ DEFINE_PROP_PTR("fclk", struct omap_intr_handler_s, fclk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void omap2_intc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = omap2_intc_init;
+ dc->reset = omap_inth_reset;
+ dc->props = omap2_intc_properties;
+ /* Reason: pointer property "iclk", "fclk" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo omap2_intc_info = {
+ .name = "omap2-intc",
+ .parent = TYPE_OMAP_INTC,
+ .class_init = omap2_intc_class_init,
+};
+
+static const TypeInfo omap_intc_type_info = {
+ .name = TYPE_OMAP_INTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct omap_intr_handler_s),
+ .abstract = true,
+};
+
+static void omap_intc_register_types(void)
+{
+ type_register_static(&omap_intc_type_info);
+ type_register_static(&omap_intc_info);
+ type_register_static(&omap2_intc_info);
+}
+
+type_init(omap_intc_register_types)
diff --git a/hw/intc/openpic.c b/hw/intc/openpic.c
new file mode 100644
index 00000000..14ab0e31
--- /dev/null
+++ b/hw/intc/openpic.c
@@ -0,0 +1,1661 @@
+/*
+ * OpenPIC emulation
+ *
+ * Copyright (c) 2004 Jocelyn Mayer
+ * 2011 Alexander Graf
+ *
+ * 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.
+ */
+/*
+ *
+ * Based on OpenPic implementations:
+ * - Intel GW80314 I/O companion chip developer's manual
+ * - Motorola MPC8245 & MPC8540 user manuals.
+ * - Motorola MCP750 (aka Raven) programmer manual.
+ * - Motorola Harrier programmer manuel
+ *
+ * Serial interrupts, as implemented in Raven chipset are not supported yet.
+ *
+ */
+#include "hw/hw.h"
+#include "hw/ppc/mac.h"
+#include "hw/pci/pci.h"
+#include "hw/ppc/openpic.h"
+#include "hw/ppc/ppc_e500.h"
+#include "hw/sysbus.h"
+#include "hw/pci/msi.h"
+#include "qemu/bitops.h"
+#include "qapi/qmp/qerror.h"
+
+//#define DEBUG_OPENPIC
+
+#ifdef DEBUG_OPENPIC
+static const int debug_openpic = 1;
+#else
+static const int debug_openpic = 0;
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (debug_openpic) { \
+ printf(fmt , ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define MAX_CPU 32
+#define MAX_MSI 8
+#define VID 0x03 /* MPIC version ID */
+
+/* OpenPIC capability flags */
+#define OPENPIC_FLAG_IDR_CRIT (1 << 0)
+#define OPENPIC_FLAG_ILR (2 << 0)
+
+/* OpenPIC address map */
+#define OPENPIC_GLB_REG_START 0x0
+#define OPENPIC_GLB_REG_SIZE 0x10F0
+#define OPENPIC_TMR_REG_START 0x10F0
+#define OPENPIC_TMR_REG_SIZE 0x220
+#define OPENPIC_MSI_REG_START 0x1600
+#define OPENPIC_MSI_REG_SIZE 0x200
+#define OPENPIC_SUMMARY_REG_START 0x3800
+#define OPENPIC_SUMMARY_REG_SIZE 0x800
+#define OPENPIC_SRC_REG_START 0x10000
+#define OPENPIC_SRC_REG_SIZE (OPENPIC_MAX_SRC * 0x20)
+#define OPENPIC_CPU_REG_START 0x20000
+#define OPENPIC_CPU_REG_SIZE 0x100 + ((MAX_CPU - 1) * 0x1000)
+
+/* Raven */
+#define RAVEN_MAX_CPU 2
+#define RAVEN_MAX_EXT 48
+#define RAVEN_MAX_IRQ 64
+#define RAVEN_MAX_TMR OPENPIC_MAX_TMR
+#define RAVEN_MAX_IPI OPENPIC_MAX_IPI
+
+/* Interrupt definitions */
+#define RAVEN_FE_IRQ (RAVEN_MAX_EXT) /* Internal functional IRQ */
+#define RAVEN_ERR_IRQ (RAVEN_MAX_EXT + 1) /* Error IRQ */
+#define RAVEN_TMR_IRQ (RAVEN_MAX_EXT + 2) /* First timer IRQ */
+#define RAVEN_IPI_IRQ (RAVEN_TMR_IRQ + RAVEN_MAX_TMR) /* First IPI IRQ */
+/* First doorbell IRQ */
+#define RAVEN_DBL_IRQ (RAVEN_IPI_IRQ + (RAVEN_MAX_CPU * RAVEN_MAX_IPI))
+
+typedef struct FslMpicInfo {
+ int max_ext;
+} FslMpicInfo;
+
+static FslMpicInfo fsl_mpic_20 = {
+ .max_ext = 12,
+};
+
+static FslMpicInfo fsl_mpic_42 = {
+ .max_ext = 12,
+};
+
+#define FRR_NIRQ_SHIFT 16
+#define FRR_NCPU_SHIFT 8
+#define FRR_VID_SHIFT 0
+
+#define VID_REVISION_1_2 2
+#define VID_REVISION_1_3 3
+
+#define VIR_GENERIC 0x00000000 /* Generic Vendor ID */
+
+#define GCR_RESET 0x80000000
+#define GCR_MODE_PASS 0x00000000
+#define GCR_MODE_MIXED 0x20000000
+#define GCR_MODE_PROXY 0x60000000
+
+#define TBCR_CI 0x80000000 /* count inhibit */
+#define TCCR_TOG 0x80000000 /* toggles when decrement to zero */
+
+#define IDR_EP_SHIFT 31
+#define IDR_EP_MASK (1U << IDR_EP_SHIFT)
+#define IDR_CI0_SHIFT 30
+#define IDR_CI1_SHIFT 29
+#define IDR_P1_SHIFT 1
+#define IDR_P0_SHIFT 0
+
+#define ILR_INTTGT_MASK 0x000000ff
+#define ILR_INTTGT_INT 0x00
+#define ILR_INTTGT_CINT 0x01 /* critical */
+#define ILR_INTTGT_MCP 0x02 /* machine check */
+
+/* The currently supported INTTGT values happen to be the same as QEMU's
+ * openpic output codes, but don't depend on this. The output codes
+ * could change (unlikely, but...) or support could be added for
+ * more INTTGT values.
+ */
+static const int inttgt_output[][2] = {
+ { ILR_INTTGT_INT, OPENPIC_OUTPUT_INT },
+ { ILR_INTTGT_CINT, OPENPIC_OUTPUT_CINT },
+ { ILR_INTTGT_MCP, OPENPIC_OUTPUT_MCK },
+};
+
+static int inttgt_to_output(int inttgt)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(inttgt_output); i++) {
+ if (inttgt_output[i][0] == inttgt) {
+ return inttgt_output[i][1];
+ }
+ }
+
+ fprintf(stderr, "%s: unsupported inttgt %d\n", __func__, inttgt);
+ return OPENPIC_OUTPUT_INT;
+}
+
+static int output_to_inttgt(int output)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(inttgt_output); i++) {
+ if (inttgt_output[i][1] == output) {
+ return inttgt_output[i][0];
+ }
+ }
+
+ abort();
+}
+
+#define MSIIR_OFFSET 0x140
+#define MSIIR_SRS_SHIFT 29
+#define MSIIR_SRS_MASK (0x7 << MSIIR_SRS_SHIFT)
+#define MSIIR_IBS_SHIFT 24
+#define MSIIR_IBS_MASK (0x1f << MSIIR_IBS_SHIFT)
+
+static int get_current_cpu(void)
+{
+ if (!current_cpu) {
+ return -1;
+ }
+
+ return current_cpu->cpu_index;
+}
+
+static uint32_t openpic_cpu_read_internal(void *opaque, hwaddr addr,
+ int idx);
+static void openpic_cpu_write_internal(void *opaque, hwaddr addr,
+ uint32_t val, int idx);
+static void openpic_reset(DeviceState *d);
+
+typedef enum IRQType {
+ IRQ_TYPE_NORMAL = 0,
+ IRQ_TYPE_FSLINT, /* FSL internal interrupt -- level only */
+ IRQ_TYPE_FSLSPECIAL, /* FSL timer/IPI interrupt, edge, no polarity */
+} IRQType;
+
+/* Round up to the nearest 64 IRQs so that the queue length
+ * won't change when moving between 32 and 64 bit hosts.
+ */
+#define IRQQUEUE_SIZE_BITS ((OPENPIC_MAX_IRQ + 63) & ~63)
+
+typedef struct IRQQueue {
+ unsigned long *queue;
+ int32_t queue_size; /* Only used for VMSTATE_BITMAP */
+ int next;
+ int priority;
+} IRQQueue;
+
+typedef struct IRQSource {
+ uint32_t ivpr; /* IRQ vector/priority register */
+ uint32_t idr; /* IRQ destination register */
+ uint32_t destmask; /* bitmap of CPU destinations */
+ int last_cpu;
+ int output; /* IRQ level, e.g. OPENPIC_OUTPUT_INT */
+ int pending; /* TRUE if IRQ is pending */
+ IRQType type;
+ bool level:1; /* level-triggered */
+ bool nomask:1; /* critical interrupts ignore mask on some FSL MPICs */
+} IRQSource;
+
+#define IVPR_MASK_SHIFT 31
+#define IVPR_MASK_MASK (1U << IVPR_MASK_SHIFT)
+#define IVPR_ACTIVITY_SHIFT 30
+#define IVPR_ACTIVITY_MASK (1U << IVPR_ACTIVITY_SHIFT)
+#define IVPR_MODE_SHIFT 29
+#define IVPR_MODE_MASK (1U << IVPR_MODE_SHIFT)
+#define IVPR_POLARITY_SHIFT 23
+#define IVPR_POLARITY_MASK (1U << IVPR_POLARITY_SHIFT)
+#define IVPR_SENSE_SHIFT 22
+#define IVPR_SENSE_MASK (1U << IVPR_SENSE_SHIFT)
+
+#define IVPR_PRIORITY_MASK (0xFU << 16)
+#define IVPR_PRIORITY(_ivprr_) ((int)(((_ivprr_) & IVPR_PRIORITY_MASK) >> 16))
+#define IVPR_VECTOR(opp, _ivprr_) ((_ivprr_) & (opp)->vector_mask)
+
+/* IDR[EP/CI] are only for FSL MPIC prior to v4.0 */
+#define IDR_EP 0x80000000 /* external pin */
+#define IDR_CI 0x40000000 /* critical interrupt */
+
+typedef struct OpenPICTimer {
+ uint32_t tccr; /* Global timer current count register */
+ uint32_t tbcr; /* Global timer base count register */
+} OpenPICTimer;
+
+typedef struct OpenPICMSI {
+ uint32_t msir; /* Shared Message Signaled Interrupt Register */
+} OpenPICMSI;
+
+typedef struct IRQDest {
+ int32_t ctpr; /* CPU current task priority */
+ IRQQueue raised;
+ IRQQueue servicing;
+ qemu_irq *irqs;
+
+ /* Count of IRQ sources asserting on non-INT outputs */
+ uint32_t outputs_active[OPENPIC_OUTPUT_NB];
+} IRQDest;
+
+#define OPENPIC(obj) OBJECT_CHECK(OpenPICState, (obj), TYPE_OPENPIC)
+
+typedef struct OpenPICState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion mem;
+
+ /* Behavior control */
+ FslMpicInfo *fsl;
+ uint32_t model;
+ uint32_t flags;
+ uint32_t nb_irqs;
+ uint32_t vid;
+ uint32_t vir; /* Vendor identification register */
+ uint32_t vector_mask;
+ uint32_t tfrr_reset;
+ uint32_t ivpr_reset;
+ uint32_t idr_reset;
+ uint32_t brr1;
+ uint32_t mpic_mode_mask;
+
+ /* Sub-regions */
+ MemoryRegion sub_io_mem[6];
+
+ /* Global registers */
+ uint32_t frr; /* Feature reporting register */
+ uint32_t gcr; /* Global configuration register */
+ uint32_t pir; /* Processor initialization register */
+ uint32_t spve; /* Spurious vector register */
+ uint32_t tfrr; /* Timer frequency reporting register */
+ /* Source registers */
+ IRQSource src[OPENPIC_MAX_IRQ];
+ /* Local registers per output pin */
+ IRQDest dst[MAX_CPU];
+ uint32_t nb_cpus;
+ /* Timer registers */
+ OpenPICTimer timers[OPENPIC_MAX_TMR];
+ /* Shared MSI registers */
+ OpenPICMSI msi[MAX_MSI];
+ uint32_t max_irq;
+ uint32_t irq_ipi0;
+ uint32_t irq_tim0;
+ uint32_t irq_msi;
+} OpenPICState;
+
+static inline void IRQ_setbit(IRQQueue *q, int n_IRQ)
+{
+ set_bit(n_IRQ, q->queue);
+}
+
+static inline void IRQ_resetbit(IRQQueue *q, int n_IRQ)
+{
+ clear_bit(n_IRQ, q->queue);
+}
+
+static void IRQ_check(OpenPICState *opp, IRQQueue *q)
+{
+ int irq = -1;
+ int next = -1;
+ int priority = -1;
+
+ for (;;) {
+ irq = find_next_bit(q->queue, opp->max_irq, irq + 1);
+ if (irq == opp->max_irq) {
+ break;
+ }
+
+ DPRINTF("IRQ_check: irq %d set ivpr_pr=%d pr=%d\n",
+ irq, IVPR_PRIORITY(opp->src[irq].ivpr), priority);
+
+ if (IVPR_PRIORITY(opp->src[irq].ivpr) > priority) {
+ next = irq;
+ priority = IVPR_PRIORITY(opp->src[irq].ivpr);
+ }
+ }
+
+ q->next = next;
+ q->priority = priority;
+}
+
+static int IRQ_get_next(OpenPICState *opp, IRQQueue *q)
+{
+ /* XXX: optimize */
+ IRQ_check(opp, q);
+
+ return q->next;
+}
+
+static void IRQ_local_pipe(OpenPICState *opp, int n_CPU, int n_IRQ,
+ bool active, bool was_active)
+{
+ IRQDest *dst;
+ IRQSource *src;
+ int priority;
+
+ dst = &opp->dst[n_CPU];
+ src = &opp->src[n_IRQ];
+
+ DPRINTF("%s: IRQ %d active %d was %d\n",
+ __func__, n_IRQ, active, was_active);
+
+ if (src->output != OPENPIC_OUTPUT_INT) {
+ DPRINTF("%s: output %d irq %d active %d was %d count %d\n",
+ __func__, src->output, n_IRQ, active, was_active,
+ dst->outputs_active[src->output]);
+
+ /* On Freescale MPIC, critical interrupts ignore priority,
+ * IACK, EOI, etc. Before MPIC v4.1 they also ignore
+ * masking.
+ */
+ if (active) {
+ if (!was_active && dst->outputs_active[src->output]++ == 0) {
+ DPRINTF("%s: Raise OpenPIC output %d cpu %d irq %d\n",
+ __func__, src->output, n_CPU, n_IRQ);
+ qemu_irq_raise(dst->irqs[src->output]);
+ }
+ } else {
+ if (was_active && --dst->outputs_active[src->output] == 0) {
+ DPRINTF("%s: Lower OpenPIC output %d cpu %d irq %d\n",
+ __func__, src->output, n_CPU, n_IRQ);
+ qemu_irq_lower(dst->irqs[src->output]);
+ }
+ }
+
+ return;
+ }
+
+ priority = IVPR_PRIORITY(src->ivpr);
+
+ /* Even if the interrupt doesn't have enough priority,
+ * it is still raised, in case ctpr is lowered later.
+ */
+ if (active) {
+ IRQ_setbit(&dst->raised, n_IRQ);
+ } else {
+ IRQ_resetbit(&dst->raised, n_IRQ);
+ }
+
+ IRQ_check(opp, &dst->raised);
+
+ if (active && priority <= dst->ctpr) {
+ DPRINTF("%s: IRQ %d priority %d too low for ctpr %d on CPU %d\n",
+ __func__, n_IRQ, priority, dst->ctpr, n_CPU);
+ active = 0;
+ }
+
+ if (active) {
+ if (IRQ_get_next(opp, &dst->servicing) >= 0 &&
+ priority <= dst->servicing.priority) {
+ DPRINTF("%s: IRQ %d is hidden by servicing IRQ %d on CPU %d\n",
+ __func__, n_IRQ, dst->servicing.next, n_CPU);
+ } else {
+ DPRINTF("%s: Raise OpenPIC INT output cpu %d irq %d/%d\n",
+ __func__, n_CPU, n_IRQ, dst->raised.next);
+ qemu_irq_raise(opp->dst[n_CPU].irqs[OPENPIC_OUTPUT_INT]);
+ }
+ } else {
+ IRQ_get_next(opp, &dst->servicing);
+ if (dst->raised.priority > dst->ctpr &&
+ dst->raised.priority > dst->servicing.priority) {
+ DPRINTF("%s: IRQ %d inactive, IRQ %d prio %d above %d/%d, CPU %d\n",
+ __func__, n_IRQ, dst->raised.next, dst->raised.priority,
+ dst->ctpr, dst->servicing.priority, n_CPU);
+ /* IRQ line stays asserted */
+ } else {
+ DPRINTF("%s: IRQ %d inactive, current prio %d/%d, CPU %d\n",
+ __func__, n_IRQ, dst->ctpr, dst->servicing.priority, n_CPU);
+ qemu_irq_lower(opp->dst[n_CPU].irqs[OPENPIC_OUTPUT_INT]);
+ }
+ }
+}
+
+/* update pic state because registers for n_IRQ have changed value */
+static void openpic_update_irq(OpenPICState *opp, int n_IRQ)
+{
+ IRQSource *src;
+ bool active, was_active;
+ int i;
+
+ src = &opp->src[n_IRQ];
+ active = src->pending;
+
+ if ((src->ivpr & IVPR_MASK_MASK) && !src->nomask) {
+ /* Interrupt source is disabled */
+ DPRINTF("%s: IRQ %d is disabled\n", __func__, n_IRQ);
+ active = false;
+ }
+
+ was_active = !!(src->ivpr & IVPR_ACTIVITY_MASK);
+
+ /*
+ * We don't have a similar check for already-active because
+ * ctpr may have changed and we need to withdraw the interrupt.
+ */
+ if (!active && !was_active) {
+ DPRINTF("%s: IRQ %d is already inactive\n", __func__, n_IRQ);
+ return;
+ }
+
+ if (active) {
+ src->ivpr |= IVPR_ACTIVITY_MASK;
+ } else {
+ src->ivpr &= ~IVPR_ACTIVITY_MASK;
+ }
+
+ if (src->destmask == 0) {
+ /* No target */
+ DPRINTF("%s: IRQ %d has no target\n", __func__, n_IRQ);
+ return;
+ }
+
+ if (src->destmask == (1 << src->last_cpu)) {
+ /* Only one CPU is allowed to receive this IRQ */
+ IRQ_local_pipe(opp, src->last_cpu, n_IRQ, active, was_active);
+ } else if (!(src->ivpr & IVPR_MODE_MASK)) {
+ /* Directed delivery mode */
+ for (i = 0; i < opp->nb_cpus; i++) {
+ if (src->destmask & (1 << i)) {
+ IRQ_local_pipe(opp, i, n_IRQ, active, was_active);
+ }
+ }
+ } else {
+ /* Distributed delivery mode */
+ for (i = src->last_cpu + 1; i != src->last_cpu; i++) {
+ if (i == opp->nb_cpus) {
+ i = 0;
+ }
+ if (src->destmask & (1 << i)) {
+ IRQ_local_pipe(opp, i, n_IRQ, active, was_active);
+ src->last_cpu = i;
+ break;
+ }
+ }
+ }
+}
+
+static void openpic_set_irq(void *opaque, int n_IRQ, int level)
+{
+ OpenPICState *opp = opaque;
+ IRQSource *src;
+
+ if (n_IRQ >= OPENPIC_MAX_IRQ) {
+ fprintf(stderr, "%s: IRQ %d out of range\n", __func__, n_IRQ);
+ abort();
+ }
+
+ src = &opp->src[n_IRQ];
+ DPRINTF("openpic: set irq %d = %d ivpr=0x%08x\n",
+ n_IRQ, level, src->ivpr);
+ if (src->level) {
+ /* level-sensitive irq */
+ src->pending = level;
+ openpic_update_irq(opp, n_IRQ);
+ } else {
+ /* edge-sensitive irq */
+ if (level) {
+ src->pending = 1;
+ openpic_update_irq(opp, n_IRQ);
+ }
+
+ if (src->output != OPENPIC_OUTPUT_INT) {
+ /* Edge-triggered interrupts shouldn't be used
+ * with non-INT delivery, but just in case,
+ * try to make it do something sane rather than
+ * cause an interrupt storm. This is close to
+ * what you'd probably see happen in real hardware.
+ */
+ src->pending = 0;
+ openpic_update_irq(opp, n_IRQ);
+ }
+ }
+}
+
+static inline uint32_t read_IRQreg_idr(OpenPICState *opp, int n_IRQ)
+{
+ return opp->src[n_IRQ].idr;
+}
+
+static inline uint32_t read_IRQreg_ilr(OpenPICState *opp, int n_IRQ)
+{
+ if (opp->flags & OPENPIC_FLAG_ILR) {
+ return output_to_inttgt(opp->src[n_IRQ].output);
+ }
+
+ return 0xffffffff;
+}
+
+static inline uint32_t read_IRQreg_ivpr(OpenPICState *opp, int n_IRQ)
+{
+ return opp->src[n_IRQ].ivpr;
+}
+
+static inline void write_IRQreg_idr(OpenPICState *opp, int n_IRQ, uint32_t val)
+{
+ IRQSource *src = &opp->src[n_IRQ];
+ uint32_t normal_mask = (1UL << opp->nb_cpus) - 1;
+ uint32_t crit_mask = 0;
+ uint32_t mask = normal_mask;
+ int crit_shift = IDR_EP_SHIFT - opp->nb_cpus;
+ int i;
+
+ if (opp->flags & OPENPIC_FLAG_IDR_CRIT) {
+ crit_mask = mask << crit_shift;
+ mask |= crit_mask | IDR_EP;
+ }
+
+ src->idr = val & mask;
+ DPRINTF("Set IDR %d to 0x%08x\n", n_IRQ, src->idr);
+
+ if (opp->flags & OPENPIC_FLAG_IDR_CRIT) {
+ if (src->idr & crit_mask) {
+ if (src->idr & normal_mask) {
+ DPRINTF("%s: IRQ configured for multiple output types, using "
+ "critical\n", __func__);
+ }
+
+ src->output = OPENPIC_OUTPUT_CINT;
+ src->nomask = true;
+ src->destmask = 0;
+
+ for (i = 0; i < opp->nb_cpus; i++) {
+ int n_ci = IDR_CI0_SHIFT - i;
+
+ if (src->idr & (1UL << n_ci)) {
+ src->destmask |= 1UL << i;
+ }
+ }
+ } else {
+ src->output = OPENPIC_OUTPUT_INT;
+ src->nomask = false;
+ src->destmask = src->idr & normal_mask;
+ }
+ } else {
+ src->destmask = src->idr;
+ }
+}
+
+static inline void write_IRQreg_ilr(OpenPICState *opp, int n_IRQ, uint32_t val)
+{
+ if (opp->flags & OPENPIC_FLAG_ILR) {
+ IRQSource *src = &opp->src[n_IRQ];
+
+ src->output = inttgt_to_output(val & ILR_INTTGT_MASK);
+ DPRINTF("Set ILR %d to 0x%08x, output %d\n", n_IRQ, src->idr,
+ src->output);
+
+ /* TODO: on MPIC v4.0 only, set nomask for non-INT */
+ }
+}
+
+static inline void write_IRQreg_ivpr(OpenPICState *opp, int n_IRQ, uint32_t val)
+{
+ uint32_t mask;
+
+ /* NOTE when implementing newer FSL MPIC models: starting with v4.0,
+ * the polarity bit is read-only on internal interrupts.
+ */
+ mask = IVPR_MASK_MASK | IVPR_PRIORITY_MASK | IVPR_SENSE_MASK |
+ IVPR_POLARITY_MASK | opp->vector_mask;
+
+ /* ACTIVITY bit is read-only */
+ opp->src[n_IRQ].ivpr =
+ (opp->src[n_IRQ].ivpr & IVPR_ACTIVITY_MASK) | (val & mask);
+
+ /* For FSL internal interrupts, The sense bit is reserved and zero,
+ * and the interrupt is always level-triggered. Timers and IPIs
+ * have no sense or polarity bits, and are edge-triggered.
+ */
+ switch (opp->src[n_IRQ].type) {
+ case IRQ_TYPE_NORMAL:
+ opp->src[n_IRQ].level = !!(opp->src[n_IRQ].ivpr & IVPR_SENSE_MASK);
+ break;
+
+ case IRQ_TYPE_FSLINT:
+ opp->src[n_IRQ].ivpr &= ~IVPR_SENSE_MASK;
+ break;
+
+ case IRQ_TYPE_FSLSPECIAL:
+ opp->src[n_IRQ].ivpr &= ~(IVPR_POLARITY_MASK | IVPR_SENSE_MASK);
+ break;
+ }
+
+ openpic_update_irq(opp, n_IRQ);
+ DPRINTF("Set IVPR %d to 0x%08x -> 0x%08x\n", n_IRQ, val,
+ opp->src[n_IRQ].ivpr);
+}
+
+static void openpic_gcr_write(OpenPICState *opp, uint64_t val)
+{
+ bool mpic_proxy = false;
+
+ if (val & GCR_RESET) {
+ openpic_reset(DEVICE(opp));
+ return;
+ }
+
+ opp->gcr &= ~opp->mpic_mode_mask;
+ opp->gcr |= val & opp->mpic_mode_mask;
+
+ /* Set external proxy mode */
+ if ((val & opp->mpic_mode_mask) == GCR_MODE_PROXY) {
+ mpic_proxy = true;
+ }
+
+ ppce500_set_mpic_proxy(mpic_proxy);
+}
+
+static void openpic_gbl_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned len)
+{
+ OpenPICState *opp = opaque;
+ IRQDest *dst;
+ int idx;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx " <= %08" PRIx64 "\n",
+ __func__, addr, val);
+ if (addr & 0xF) {
+ return;
+ }
+ switch (addr) {
+ case 0x00: /* Block Revision Register1 (BRR1) is Readonly */
+ break;
+ case 0x40:
+ case 0x50:
+ case 0x60:
+ case 0x70:
+ case 0x80:
+ case 0x90:
+ case 0xA0:
+ case 0xB0:
+ openpic_cpu_write_internal(opp, addr, val, get_current_cpu());
+ break;
+ case 0x1000: /* FRR */
+ break;
+ case 0x1020: /* GCR */
+ openpic_gcr_write(opp, val);
+ break;
+ case 0x1080: /* VIR */
+ break;
+ case 0x1090: /* PIR */
+ for (idx = 0; idx < opp->nb_cpus; idx++) {
+ if ((val & (1 << idx)) && !(opp->pir & (1 << idx))) {
+ DPRINTF("Raise OpenPIC RESET output for CPU %d\n", idx);
+ dst = &opp->dst[idx];
+ qemu_irq_raise(dst->irqs[OPENPIC_OUTPUT_RESET]);
+ } else if (!(val & (1 << idx)) && (opp->pir & (1 << idx))) {
+ DPRINTF("Lower OpenPIC RESET output for CPU %d\n", idx);
+ dst = &opp->dst[idx];
+ qemu_irq_lower(dst->irqs[OPENPIC_OUTPUT_RESET]);
+ }
+ }
+ opp->pir = val;
+ break;
+ case 0x10A0: /* IPI_IVPR */
+ case 0x10B0:
+ case 0x10C0:
+ case 0x10D0:
+ {
+ int idx;
+ idx = (addr - 0x10A0) >> 4;
+ write_IRQreg_ivpr(opp, opp->irq_ipi0 + idx, val);
+ }
+ break;
+ case 0x10E0: /* SPVE */
+ opp->spve = val & opp->vector_mask;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t openpic_gbl_read(void *opaque, hwaddr addr, unsigned len)
+{
+ OpenPICState *opp = opaque;
+ uint32_t retval;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr);
+ retval = 0xFFFFFFFF;
+ if (addr & 0xF) {
+ return retval;
+ }
+ switch (addr) {
+ case 0x1000: /* FRR */
+ retval = opp->frr;
+ break;
+ case 0x1020: /* GCR */
+ retval = opp->gcr;
+ break;
+ case 0x1080: /* VIR */
+ retval = opp->vir;
+ break;
+ case 0x1090: /* PIR */
+ retval = 0x00000000;
+ break;
+ case 0x00: /* Block Revision Register1 (BRR1) */
+ retval = opp->brr1;
+ break;
+ case 0x40:
+ case 0x50:
+ case 0x60:
+ case 0x70:
+ case 0x80:
+ case 0x90:
+ case 0xA0:
+ case 0xB0:
+ retval = openpic_cpu_read_internal(opp, addr, get_current_cpu());
+ break;
+ case 0x10A0: /* IPI_IVPR */
+ case 0x10B0:
+ case 0x10C0:
+ case 0x10D0:
+ {
+ int idx;
+ idx = (addr - 0x10A0) >> 4;
+ retval = read_IRQreg_ivpr(opp, opp->irq_ipi0 + idx);
+ }
+ break;
+ case 0x10E0: /* SPVE */
+ retval = opp->spve;
+ break;
+ default:
+ break;
+ }
+ DPRINTF("%s: => 0x%08x\n", __func__, retval);
+
+ return retval;
+}
+
+static void openpic_tmr_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned len)
+{
+ OpenPICState *opp = opaque;
+ int idx;
+
+ addr += 0x10f0;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx " <= %08" PRIx64 "\n",
+ __func__, addr, val);
+ if (addr & 0xF) {
+ return;
+ }
+
+ if (addr == 0x10f0) {
+ /* TFRR */
+ opp->tfrr = val;
+ return;
+ }
+
+ idx = (addr >> 6) & 0x3;
+ addr = addr & 0x30;
+
+ switch (addr & 0x30) {
+ case 0x00: /* TCCR */
+ break;
+ case 0x10: /* TBCR */
+ if ((opp->timers[idx].tccr & TCCR_TOG) != 0 &&
+ (val & TBCR_CI) == 0 &&
+ (opp->timers[idx].tbcr & TBCR_CI) != 0) {
+ opp->timers[idx].tccr &= ~TCCR_TOG;
+ }
+ opp->timers[idx].tbcr = val;
+ break;
+ case 0x20: /* TVPR */
+ write_IRQreg_ivpr(opp, opp->irq_tim0 + idx, val);
+ break;
+ case 0x30: /* TDR */
+ write_IRQreg_idr(opp, opp->irq_tim0 + idx, val);
+ break;
+ }
+}
+
+static uint64_t openpic_tmr_read(void *opaque, hwaddr addr, unsigned len)
+{
+ OpenPICState *opp = opaque;
+ uint32_t retval = -1;
+ int idx;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr);
+ if (addr & 0xF) {
+ goto out;
+ }
+ idx = (addr >> 6) & 0x3;
+ if (addr == 0x0) {
+ /* TFRR */
+ retval = opp->tfrr;
+ goto out;
+ }
+ switch (addr & 0x30) {
+ case 0x00: /* TCCR */
+ retval = opp->timers[idx].tccr;
+ break;
+ case 0x10: /* TBCR */
+ retval = opp->timers[idx].tbcr;
+ break;
+ case 0x20: /* TIPV */
+ retval = read_IRQreg_ivpr(opp, opp->irq_tim0 + idx);
+ break;
+ case 0x30: /* TIDE (TIDR) */
+ retval = read_IRQreg_idr(opp, opp->irq_tim0 + idx);
+ break;
+ }
+
+out:
+ DPRINTF("%s: => 0x%08x\n", __func__, retval);
+
+ return retval;
+}
+
+static void openpic_src_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned len)
+{
+ OpenPICState *opp = opaque;
+ int idx;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx " <= %08" PRIx64 "\n",
+ __func__, addr, val);
+
+ addr = addr & 0xffff;
+ idx = addr >> 5;
+
+ switch (addr & 0x1f) {
+ case 0x00:
+ write_IRQreg_ivpr(opp, idx, val);
+ break;
+ case 0x10:
+ write_IRQreg_idr(opp, idx, val);
+ break;
+ case 0x18:
+ write_IRQreg_ilr(opp, idx, val);
+ break;
+ }
+}
+
+static uint64_t openpic_src_read(void *opaque, uint64_t addr, unsigned len)
+{
+ OpenPICState *opp = opaque;
+ uint32_t retval;
+ int idx;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr);
+ retval = 0xFFFFFFFF;
+
+ addr = addr & 0xffff;
+ idx = addr >> 5;
+
+ switch (addr & 0x1f) {
+ case 0x00:
+ retval = read_IRQreg_ivpr(opp, idx);
+ break;
+ case 0x10:
+ retval = read_IRQreg_idr(opp, idx);
+ break;
+ case 0x18:
+ retval = read_IRQreg_ilr(opp, idx);
+ break;
+ }
+
+ DPRINTF("%s: => 0x%08x\n", __func__, retval);
+ return retval;
+}
+
+static void openpic_msi_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ OpenPICState *opp = opaque;
+ int idx = opp->irq_msi;
+ int srs, ibs;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx " <= 0x%08" PRIx64 "\n",
+ __func__, addr, val);
+ if (addr & 0xF) {
+ return;
+ }
+
+ switch (addr) {
+ case MSIIR_OFFSET:
+ srs = val >> MSIIR_SRS_SHIFT;
+ idx += srs;
+ ibs = (val & MSIIR_IBS_MASK) >> MSIIR_IBS_SHIFT;
+ opp->msi[srs].msir |= 1 << ibs;
+ openpic_set_irq(opp, idx, 1);
+ break;
+ default:
+ /* most registers are read-only, thus ignored */
+ break;
+ }
+}
+
+static uint64_t openpic_msi_read(void *opaque, hwaddr addr, unsigned size)
+{
+ OpenPICState *opp = opaque;
+ uint64_t r = 0;
+ int i, srs;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr);
+ if (addr & 0xF) {
+ return -1;
+ }
+
+ srs = addr >> 4;
+
+ switch (addr) {
+ case 0x00:
+ case 0x10:
+ case 0x20:
+ case 0x30:
+ case 0x40:
+ case 0x50:
+ case 0x60:
+ case 0x70: /* MSIRs */
+ r = opp->msi[srs].msir;
+ /* Clear on read */
+ opp->msi[srs].msir = 0;
+ openpic_set_irq(opp, opp->irq_msi + srs, 0);
+ break;
+ case 0x120: /* MSISR */
+ for (i = 0; i < MAX_MSI; i++) {
+ r |= (opp->msi[i].msir ? 1 : 0) << i;
+ }
+ break;
+ }
+
+ return r;
+}
+
+static uint64_t openpic_summary_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint64_t r = 0;
+
+ DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr);
+
+ /* TODO: EISR/EIMR */
+
+ return r;
+}
+
+static void openpic_summary_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ DPRINTF("%s: addr %#" HWADDR_PRIx " <= 0x%08" PRIx64 "\n",
+ __func__, addr, val);
+
+ /* TODO: EISR/EIMR */
+}
+
+static void openpic_cpu_write_internal(void *opaque, hwaddr addr,
+ uint32_t val, int idx)
+{
+ OpenPICState *opp = opaque;
+ IRQSource *src;
+ IRQDest *dst;
+ int s_IRQ, n_IRQ;
+
+ DPRINTF("%s: cpu %d addr %#" HWADDR_PRIx " <= 0x%08x\n", __func__, idx,
+ addr, val);
+
+ if (idx < 0 || idx >= opp->nb_cpus) {
+ return;
+ }
+
+ if (addr & 0xF) {
+ return;
+ }
+ dst = &opp->dst[idx];
+ addr &= 0xFF0;
+ switch (addr) {
+ case 0x40: /* IPIDR */
+ case 0x50:
+ case 0x60:
+ case 0x70:
+ idx = (addr - 0x40) >> 4;
+ /* we use IDE as mask which CPUs to deliver the IPI to still. */
+ opp->src[opp->irq_ipi0 + idx].destmask |= val;
+ openpic_set_irq(opp, opp->irq_ipi0 + idx, 1);
+ openpic_set_irq(opp, opp->irq_ipi0 + idx, 0);
+ break;
+ case 0x80: /* CTPR */
+ dst->ctpr = val & 0x0000000F;
+
+ DPRINTF("%s: set CPU %d ctpr to %d, raised %d servicing %d\n",
+ __func__, idx, dst->ctpr, dst->raised.priority,
+ dst->servicing.priority);
+
+ if (dst->raised.priority <= dst->ctpr) {
+ DPRINTF("%s: Lower OpenPIC INT output cpu %d due to ctpr\n",
+ __func__, idx);
+ qemu_irq_lower(dst->irqs[OPENPIC_OUTPUT_INT]);
+ } else if (dst->raised.priority > dst->servicing.priority) {
+ DPRINTF("%s: Raise OpenPIC INT output cpu %d irq %d\n",
+ __func__, idx, dst->raised.next);
+ qemu_irq_raise(dst->irqs[OPENPIC_OUTPUT_INT]);
+ }
+
+ break;
+ case 0x90: /* WHOAMI */
+ /* Read-only register */
+ break;
+ case 0xA0: /* IACK */
+ /* Read-only register */
+ break;
+ case 0xB0: /* EOI */
+ DPRINTF("EOI\n");
+ s_IRQ = IRQ_get_next(opp, &dst->servicing);
+
+ if (s_IRQ < 0) {
+ DPRINTF("%s: EOI with no interrupt in service\n", __func__);
+ break;
+ }
+
+ IRQ_resetbit(&dst->servicing, s_IRQ);
+ /* Set up next servicing IRQ */
+ s_IRQ = IRQ_get_next(opp, &dst->servicing);
+ /* Check queued interrupts. */
+ n_IRQ = IRQ_get_next(opp, &dst->raised);
+ src = &opp->src[n_IRQ];
+ if (n_IRQ != -1 &&
+ (s_IRQ == -1 ||
+ IVPR_PRIORITY(src->ivpr) > dst->servicing.priority)) {
+ DPRINTF("Raise OpenPIC INT output cpu %d irq %d\n",
+ idx, n_IRQ);
+ qemu_irq_raise(opp->dst[idx].irqs[OPENPIC_OUTPUT_INT]);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void openpic_cpu_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned len)
+{
+ openpic_cpu_write_internal(opaque, addr, val, (addr & 0x1f000) >> 12);
+}
+
+
+static uint32_t openpic_iack(OpenPICState *opp, IRQDest *dst, int cpu)
+{
+ IRQSource *src;
+ int retval, irq;
+
+ DPRINTF("Lower OpenPIC INT output\n");
+ qemu_irq_lower(dst->irqs[OPENPIC_OUTPUT_INT]);
+
+ irq = IRQ_get_next(opp, &dst->raised);
+ DPRINTF("IACK: irq=%d\n", irq);
+
+ if (irq == -1) {
+ /* No more interrupt pending */
+ return opp->spve;
+ }
+
+ src = &opp->src[irq];
+ if (!(src->ivpr & IVPR_ACTIVITY_MASK) ||
+ !(IVPR_PRIORITY(src->ivpr) > dst->ctpr)) {
+ fprintf(stderr, "%s: bad raised IRQ %d ctpr %d ivpr 0x%08x\n",
+ __func__, irq, dst->ctpr, src->ivpr);
+ openpic_update_irq(opp, irq);
+ retval = opp->spve;
+ } else {
+ /* IRQ enter servicing state */
+ IRQ_setbit(&dst->servicing, irq);
+ retval = IVPR_VECTOR(opp, src->ivpr);
+ }
+
+ if (!src->level) {
+ /* edge-sensitive IRQ */
+ src->ivpr &= ~IVPR_ACTIVITY_MASK;
+ src->pending = 0;
+ IRQ_resetbit(&dst->raised, irq);
+ }
+
+ if ((irq >= opp->irq_ipi0) && (irq < (opp->irq_ipi0 + OPENPIC_MAX_IPI))) {
+ src->destmask &= ~(1 << cpu);
+ if (src->destmask && !src->level) {
+ /* trigger on CPUs that didn't know about it yet */
+ openpic_set_irq(opp, irq, 1);
+ openpic_set_irq(opp, irq, 0);
+ /* if all CPUs knew about it, set active bit again */
+ src->ivpr |= IVPR_ACTIVITY_MASK;
+ }
+ }
+
+ return retval;
+}
+
+static uint32_t openpic_cpu_read_internal(void *opaque, hwaddr addr,
+ int idx)
+{
+ OpenPICState *opp = opaque;
+ IRQDest *dst;
+ uint32_t retval;
+
+ DPRINTF("%s: cpu %d addr %#" HWADDR_PRIx "\n", __func__, idx, addr);
+ retval = 0xFFFFFFFF;
+
+ if (idx < 0 || idx >= opp->nb_cpus) {
+ return retval;
+ }
+
+ if (addr & 0xF) {
+ return retval;
+ }
+ dst = &opp->dst[idx];
+ addr &= 0xFF0;
+ switch (addr) {
+ case 0x80: /* CTPR */
+ retval = dst->ctpr;
+ break;
+ case 0x90: /* WHOAMI */
+ retval = idx;
+ break;
+ case 0xA0: /* IACK */
+ retval = openpic_iack(opp, dst, idx);
+ break;
+ case 0xB0: /* EOI */
+ retval = 0;
+ break;
+ default:
+ break;
+ }
+ DPRINTF("%s: => 0x%08x\n", __func__, retval);
+
+ return retval;
+}
+
+static uint64_t openpic_cpu_read(void *opaque, hwaddr addr, unsigned len)
+{
+ return openpic_cpu_read_internal(opaque, addr, (addr & 0x1f000) >> 12);
+}
+
+static const MemoryRegionOps openpic_glb_ops_le = {
+ .write = openpic_gbl_write,
+ .read = openpic_gbl_read,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_glb_ops_be = {
+ .write = openpic_gbl_write,
+ .read = openpic_gbl_read,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_tmr_ops_le = {
+ .write = openpic_tmr_write,
+ .read = openpic_tmr_read,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_tmr_ops_be = {
+ .write = openpic_tmr_write,
+ .read = openpic_tmr_read,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_cpu_ops_le = {
+ .write = openpic_cpu_write,
+ .read = openpic_cpu_read,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_cpu_ops_be = {
+ .write = openpic_cpu_write,
+ .read = openpic_cpu_read,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_src_ops_le = {
+ .write = openpic_src_write,
+ .read = openpic_src_read,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_src_ops_be = {
+ .write = openpic_src_write,
+ .read = openpic_src_read,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_msi_ops_be = {
+ .read = openpic_msi_read,
+ .write = openpic_msi_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps openpic_summary_ops_be = {
+ .read = openpic_summary_read,
+ .write = openpic_summary_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void openpic_reset(DeviceState *d)
+{
+ OpenPICState *opp = OPENPIC(d);
+ int i;
+
+ opp->gcr = GCR_RESET;
+ /* Initialise controller registers */
+ opp->frr = ((opp->nb_irqs - 1) << FRR_NIRQ_SHIFT) |
+ ((opp->nb_cpus - 1) << FRR_NCPU_SHIFT) |
+ (opp->vid << FRR_VID_SHIFT);
+
+ opp->pir = 0;
+ opp->spve = -1 & opp->vector_mask;
+ opp->tfrr = opp->tfrr_reset;
+ /* Initialise IRQ sources */
+ for (i = 0; i < opp->max_irq; i++) {
+ opp->src[i].ivpr = opp->ivpr_reset;
+ switch (opp->src[i].type) {
+ case IRQ_TYPE_NORMAL:
+ opp->src[i].level = !!(opp->ivpr_reset & IVPR_SENSE_MASK);
+ break;
+
+ case IRQ_TYPE_FSLINT:
+ opp->src[i].ivpr |= IVPR_POLARITY_MASK;
+ break;
+
+ case IRQ_TYPE_FSLSPECIAL:
+ break;
+ }
+
+ write_IRQreg_idr(opp, i, opp->idr_reset);
+ }
+ /* Initialise IRQ destinations */
+ for (i = 0; i < opp->nb_cpus; i++) {
+ opp->dst[i].ctpr = 15;
+ opp->dst[i].raised.next = -1;
+ opp->dst[i].raised.priority = 0;
+ bitmap_clear(opp->dst[i].raised.queue, 0, IRQQUEUE_SIZE_BITS);
+ opp->dst[i].servicing.next = -1;
+ opp->dst[i].servicing.priority = 0;
+ bitmap_clear(opp->dst[i].servicing.queue, 0, IRQQUEUE_SIZE_BITS);
+ }
+ /* Initialise timers */
+ for (i = 0; i < OPENPIC_MAX_TMR; i++) {
+ opp->timers[i].tccr = 0;
+ opp->timers[i].tbcr = TBCR_CI;
+ }
+ /* Go out of RESET state */
+ opp->gcr = 0;
+}
+
+typedef struct MemReg {
+ const char *name;
+ MemoryRegionOps const *ops;
+ hwaddr start_addr;
+ ram_addr_t size;
+} MemReg;
+
+static void fsl_common_init(OpenPICState *opp)
+{
+ int i;
+ int virq = OPENPIC_MAX_SRC;
+
+ opp->vid = VID_REVISION_1_2;
+ opp->vir = VIR_GENERIC;
+ opp->vector_mask = 0xFFFF;
+ opp->tfrr_reset = 0;
+ opp->ivpr_reset = IVPR_MASK_MASK;
+ opp->idr_reset = 1 << 0;
+ opp->max_irq = OPENPIC_MAX_IRQ;
+
+ opp->irq_ipi0 = virq;
+ virq += OPENPIC_MAX_IPI;
+ opp->irq_tim0 = virq;
+ virq += OPENPIC_MAX_TMR;
+
+ assert(virq <= OPENPIC_MAX_IRQ);
+
+ opp->irq_msi = 224;
+
+ msi_supported = true;
+ for (i = 0; i < opp->fsl->max_ext; i++) {
+ opp->src[i].level = false;
+ }
+
+ /* Internal interrupts, including message and MSI */
+ for (i = 16; i < OPENPIC_MAX_SRC; i++) {
+ opp->src[i].type = IRQ_TYPE_FSLINT;
+ opp->src[i].level = true;
+ }
+
+ /* timers and IPIs */
+ for (i = OPENPIC_MAX_SRC; i < virq; i++) {
+ opp->src[i].type = IRQ_TYPE_FSLSPECIAL;
+ opp->src[i].level = false;
+ }
+}
+
+static void map_list(OpenPICState *opp, const MemReg *list, int *count)
+{
+ while (list->name) {
+ assert(*count < ARRAY_SIZE(opp->sub_io_mem));
+
+ memory_region_init_io(&opp->sub_io_mem[*count], OBJECT(opp), list->ops,
+ opp, list->name, list->size);
+
+ memory_region_add_subregion(&opp->mem, list->start_addr,
+ &opp->sub_io_mem[*count]);
+
+ (*count)++;
+ list++;
+ }
+}
+
+static const VMStateDescription vmstate_openpic_irq_queue = {
+ .name = "openpic_irq_queue",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_BITMAP(queue, IRQQueue, 0, queue_size),
+ VMSTATE_INT32(next, IRQQueue),
+ VMSTATE_INT32(priority, IRQQueue),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_openpic_irqdest = {
+ .name = "openpic_irqdest",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(ctpr, IRQDest),
+ VMSTATE_STRUCT(raised, IRQDest, 0, vmstate_openpic_irq_queue,
+ IRQQueue),
+ VMSTATE_STRUCT(servicing, IRQDest, 0, vmstate_openpic_irq_queue,
+ IRQQueue),
+ VMSTATE_UINT32_ARRAY(outputs_active, IRQDest, OPENPIC_OUTPUT_NB),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_openpic_irqsource = {
+ .name = "openpic_irqsource",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ivpr, IRQSource),
+ VMSTATE_UINT32(idr, IRQSource),
+ VMSTATE_UINT32(destmask, IRQSource),
+ VMSTATE_INT32(last_cpu, IRQSource),
+ VMSTATE_INT32(pending, IRQSource),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_openpic_timer = {
+ .name = "openpic_timer",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(tccr, OpenPICTimer),
+ VMSTATE_UINT32(tbcr, OpenPICTimer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_openpic_msi = {
+ .name = "openpic_msi",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(msir, OpenPICMSI),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int openpic_post_load(void *opaque, int version_id)
+{
+ OpenPICState *opp = (OpenPICState *)opaque;
+ int i;
+
+ /* Update internal ivpr and idr variables */
+ for (i = 0; i < opp->max_irq; i++) {
+ write_IRQreg_idr(opp, i, opp->src[i].idr);
+ write_IRQreg_ivpr(opp, i, opp->src[i].ivpr);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_openpic = {
+ .name = "openpic",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .post_load = openpic_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(gcr, OpenPICState),
+ VMSTATE_UINT32(vir, OpenPICState),
+ VMSTATE_UINT32(pir, OpenPICState),
+ VMSTATE_UINT32(spve, OpenPICState),
+ VMSTATE_UINT32(tfrr, OpenPICState),
+ VMSTATE_UINT32(max_irq, OpenPICState),
+ VMSTATE_STRUCT_VARRAY_UINT32(src, OpenPICState, max_irq, 0,
+ vmstate_openpic_irqsource, IRQSource),
+ VMSTATE_UINT32_EQUAL(nb_cpus, OpenPICState),
+ VMSTATE_STRUCT_VARRAY_UINT32(dst, OpenPICState, nb_cpus, 0,
+ vmstate_openpic_irqdest, IRQDest),
+ VMSTATE_STRUCT_ARRAY(timers, OpenPICState, OPENPIC_MAX_TMR, 0,
+ vmstate_openpic_timer, OpenPICTimer),
+ VMSTATE_STRUCT_ARRAY(msi, OpenPICState, MAX_MSI, 0,
+ vmstate_openpic_msi, OpenPICMSI),
+ VMSTATE_UINT32(irq_ipi0, OpenPICState),
+ VMSTATE_UINT32(irq_tim0, OpenPICState),
+ VMSTATE_UINT32(irq_msi, OpenPICState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void openpic_init(Object *obj)
+{
+ OpenPICState *opp = OPENPIC(obj);
+
+ memory_region_init(&opp->mem, obj, "openpic", 0x40000);
+}
+
+static void openpic_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(dev);
+ OpenPICState *opp = OPENPIC(dev);
+ int i, j;
+ int list_count = 0;
+ static const MemReg list_le[] = {
+ {"glb", &openpic_glb_ops_le,
+ OPENPIC_GLB_REG_START, OPENPIC_GLB_REG_SIZE},
+ {"tmr", &openpic_tmr_ops_le,
+ OPENPIC_TMR_REG_START, OPENPIC_TMR_REG_SIZE},
+ {"src", &openpic_src_ops_le,
+ OPENPIC_SRC_REG_START, OPENPIC_SRC_REG_SIZE},
+ {"cpu", &openpic_cpu_ops_le,
+ OPENPIC_CPU_REG_START, OPENPIC_CPU_REG_SIZE},
+ {NULL}
+ };
+ static const MemReg list_be[] = {
+ {"glb", &openpic_glb_ops_be,
+ OPENPIC_GLB_REG_START, OPENPIC_GLB_REG_SIZE},
+ {"tmr", &openpic_tmr_ops_be,
+ OPENPIC_TMR_REG_START, OPENPIC_TMR_REG_SIZE},
+ {"src", &openpic_src_ops_be,
+ OPENPIC_SRC_REG_START, OPENPIC_SRC_REG_SIZE},
+ {"cpu", &openpic_cpu_ops_be,
+ OPENPIC_CPU_REG_START, OPENPIC_CPU_REG_SIZE},
+ {NULL}
+ };
+ static const MemReg list_fsl[] = {
+ {"msi", &openpic_msi_ops_be,
+ OPENPIC_MSI_REG_START, OPENPIC_MSI_REG_SIZE},
+ {"summary", &openpic_summary_ops_be,
+ OPENPIC_SUMMARY_REG_START, OPENPIC_SUMMARY_REG_SIZE},
+ {NULL}
+ };
+
+ if (opp->nb_cpus > MAX_CPU) {
+ error_setg(errp, QERR_PROPERTY_VALUE_OUT_OF_RANGE,
+ TYPE_OPENPIC, "nb_cpus", (uint64_t)opp->nb_cpus,
+ (uint64_t)0, (uint64_t)MAX_CPU);
+ return;
+ }
+
+ switch (opp->model) {
+ case OPENPIC_MODEL_FSL_MPIC_20:
+ default:
+ opp->fsl = &fsl_mpic_20;
+ opp->brr1 = 0x00400200;
+ opp->flags |= OPENPIC_FLAG_IDR_CRIT;
+ opp->nb_irqs = 80;
+ opp->mpic_mode_mask = GCR_MODE_MIXED;
+
+ fsl_common_init(opp);
+ map_list(opp, list_be, &list_count);
+ map_list(opp, list_fsl, &list_count);
+
+ break;
+
+ case OPENPIC_MODEL_FSL_MPIC_42:
+ opp->fsl = &fsl_mpic_42;
+ opp->brr1 = 0x00400402;
+ opp->flags |= OPENPIC_FLAG_ILR;
+ opp->nb_irqs = 196;
+ opp->mpic_mode_mask = GCR_MODE_PROXY;
+
+ fsl_common_init(opp);
+ map_list(opp, list_be, &list_count);
+ map_list(opp, list_fsl, &list_count);
+
+ break;
+
+ case OPENPIC_MODEL_RAVEN:
+ opp->nb_irqs = RAVEN_MAX_EXT;
+ opp->vid = VID_REVISION_1_3;
+ opp->vir = VIR_GENERIC;
+ opp->vector_mask = 0xFF;
+ opp->tfrr_reset = 4160000;
+ opp->ivpr_reset = IVPR_MASK_MASK | IVPR_MODE_MASK;
+ opp->idr_reset = 0;
+ opp->max_irq = RAVEN_MAX_IRQ;
+ opp->irq_ipi0 = RAVEN_IPI_IRQ;
+ opp->irq_tim0 = RAVEN_TMR_IRQ;
+ opp->brr1 = -1;
+ opp->mpic_mode_mask = GCR_MODE_MIXED;
+
+ if (opp->nb_cpus != 1) {
+ error_setg(errp, "Only UP supported today");
+ return;
+ }
+
+ map_list(opp, list_le, &list_count);
+ break;
+ }
+
+ for (i = 0; i < opp->nb_cpus; i++) {
+ opp->dst[i].irqs = g_new0(qemu_irq, OPENPIC_OUTPUT_NB);
+ for (j = 0; j < OPENPIC_OUTPUT_NB; j++) {
+ sysbus_init_irq(d, &opp->dst[i].irqs[j]);
+ }
+
+ opp->dst[i].raised.queue_size = IRQQUEUE_SIZE_BITS;
+ opp->dst[i].raised.queue = bitmap_new(IRQQUEUE_SIZE_BITS);
+ opp->dst[i].servicing.queue_size = IRQQUEUE_SIZE_BITS;
+ opp->dst[i].servicing.queue = bitmap_new(IRQQUEUE_SIZE_BITS);
+ }
+
+ sysbus_init_mmio(d, &opp->mem);
+ qdev_init_gpio_in(dev, openpic_set_irq, opp->max_irq);
+}
+
+static Property openpic_properties[] = {
+ DEFINE_PROP_UINT32("model", OpenPICState, model, OPENPIC_MODEL_FSL_MPIC_20),
+ DEFINE_PROP_UINT32("nb_cpus", OpenPICState, nb_cpus, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void openpic_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = openpic_realize;
+ dc->props = openpic_properties;
+ dc->reset = openpic_reset;
+ dc->vmsd = &vmstate_openpic;
+}
+
+static const TypeInfo openpic_info = {
+ .name = TYPE_OPENPIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OpenPICState),
+ .instance_init = openpic_init,
+ .class_init = openpic_class_init,
+};
+
+static void openpic_register_types(void)
+{
+ type_register_static(&openpic_info);
+}
+
+type_init(openpic_register_types)
diff --git a/hw/intc/openpic_kvm.c b/hw/intc/openpic_kvm.c
new file mode 100644
index 00000000..f7cac585
--- /dev/null
+++ b/hw/intc/openpic_kvm.c
@@ -0,0 +1,293 @@
+/*
+ * KVM in-kernel OpenPIC
+ *
+ * Copyright 2013 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ */
+
+#include <sys/ioctl.h>
+#include "exec/address-spaces.h"
+#include "hw/hw.h"
+#include "hw/ppc/openpic.h"
+#include "hw/pci/msi.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+#include "qemu/log.h"
+
+#define GCR_RESET 0x80000000
+
+#define KVM_OPENPIC(obj) \
+ OBJECT_CHECK(KVMOpenPICState, (obj), TYPE_KVM_OPENPIC)
+
+typedef struct KVMOpenPICState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion mem;
+ MemoryListener mem_listener;
+ uint32_t fd;
+ uint32_t model;
+ hwaddr mapped;
+} KVMOpenPICState;
+
+static void kvm_openpic_set_irq(void *opaque, int n_IRQ, int level)
+{
+ kvm_set_irq(kvm_state, n_IRQ, level);
+}
+
+static void kvm_openpic_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ KVMOpenPICState *opp = opaque;
+ struct kvm_device_attr attr;
+ uint32_t val32 = val;
+ int ret;
+
+ attr.group = KVM_DEV_MPIC_GRP_REGISTER;
+ attr.attr = addr;
+ attr.addr = (uint64_t)(unsigned long)&val32;
+
+ ret = ioctl(opp->fd, KVM_SET_DEVICE_ATTR, &attr);
+ if (ret < 0) {
+ qemu_log_mask(LOG_UNIMP, "%s: %s %" PRIx64 "\n", __func__,
+ strerror(errno), attr.attr);
+ }
+}
+
+static void kvm_openpic_reset(DeviceState *d)
+{
+ KVMOpenPICState *opp = KVM_OPENPIC(d);
+
+ /* Trigger the GCR.RESET bit to reset the PIC */
+ kvm_openpic_write(opp, 0x1020, GCR_RESET, sizeof(uint32_t));
+}
+
+static uint64_t kvm_openpic_read(void *opaque, hwaddr addr, unsigned size)
+{
+ KVMOpenPICState *opp = opaque;
+ struct kvm_device_attr attr;
+ uint32_t val = 0xdeadbeef;
+ int ret;
+
+ attr.group = KVM_DEV_MPIC_GRP_REGISTER;
+ attr.attr = addr;
+ attr.addr = (uint64_t)(unsigned long)&val;
+
+ ret = ioctl(opp->fd, KVM_GET_DEVICE_ATTR, &attr);
+ if (ret < 0) {
+ qemu_log_mask(LOG_UNIMP, "%s: %s %" PRIx64 "\n", __func__,
+ strerror(errno), attr.attr);
+ return 0;
+ }
+
+ return val;
+}
+
+static const MemoryRegionOps kvm_openpic_mem_ops = {
+ .write = kvm_openpic_write,
+ .read = kvm_openpic_read,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void kvm_openpic_region_add(MemoryListener *listener,
+ MemoryRegionSection *section)
+{
+ KVMOpenPICState *opp = container_of(listener, KVMOpenPICState,
+ mem_listener);
+ struct kvm_device_attr attr;
+ uint64_t reg_base;
+ int ret;
+
+ if (section->address_space != &address_space_memory) {
+ abort();
+ }
+
+ /* Ignore events on regions that are not us */
+ if (section->mr != &opp->mem) {
+ return;
+ }
+
+ if (opp->mapped) {
+ /*
+ * We can only map the MPIC once. Since we are already mapped,
+ * the best we can do is ignore new maps.
+ */
+ return;
+ }
+
+ reg_base = section->offset_within_address_space;
+ opp->mapped = reg_base;
+
+ attr.group = KVM_DEV_MPIC_GRP_MISC;
+ attr.attr = KVM_DEV_MPIC_BASE_ADDR;
+ attr.addr = (uint64_t)(unsigned long)&reg_base;
+
+ ret = ioctl(opp->fd, KVM_SET_DEVICE_ATTR, &attr);
+ if (ret < 0) {
+ fprintf(stderr, "%s: %s %" PRIx64 "\n", __func__,
+ strerror(errno), reg_base);
+ }
+}
+
+static void kvm_openpic_region_del(MemoryListener *listener,
+ MemoryRegionSection *section)
+{
+ KVMOpenPICState *opp = container_of(listener, KVMOpenPICState,
+ mem_listener);
+ struct kvm_device_attr attr;
+ uint64_t reg_base = 0;
+ int ret;
+
+ /* Ignore events on regions that are not us */
+ if (section->mr != &opp->mem) {
+ return;
+ }
+
+ if (section->offset_within_address_space != opp->mapped) {
+ /*
+ * We can only map the MPIC once. This mapping was a secondary
+ * one that we couldn't fulfill. Ignore it.
+ */
+ return;
+ }
+ opp->mapped = 0;
+
+ attr.group = KVM_DEV_MPIC_GRP_MISC;
+ attr.attr = KVM_DEV_MPIC_BASE_ADDR;
+ attr.addr = (uint64_t)(unsigned long)&reg_base;
+
+ ret = ioctl(opp->fd, KVM_SET_DEVICE_ATTR, &attr);
+ if (ret < 0) {
+ fprintf(stderr, "%s: %s %" PRIx64 "\n", __func__,
+ strerror(errno), reg_base);
+ }
+}
+
+static void kvm_openpic_init(Object *obj)
+{
+ KVMOpenPICState *opp = KVM_OPENPIC(obj);
+
+ memory_region_init_io(&opp->mem, OBJECT(opp), &kvm_openpic_mem_ops, opp,
+ "kvm-openpic", 0x40000);
+}
+
+static void kvm_openpic_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(dev);
+ KVMOpenPICState *opp = KVM_OPENPIC(dev);
+ KVMState *s = kvm_state;
+ int kvm_openpic_model;
+ struct kvm_create_device cd = {0};
+ int ret, i;
+
+ if (!kvm_check_extension(s, KVM_CAP_DEVICE_CTRL)) {
+ error_setg(errp, "Kernel is lacking Device Control API");
+ return;
+ }
+
+ switch (opp->model) {
+ case OPENPIC_MODEL_FSL_MPIC_20:
+ kvm_openpic_model = KVM_DEV_TYPE_FSL_MPIC_20;
+ break;
+
+ case OPENPIC_MODEL_FSL_MPIC_42:
+ kvm_openpic_model = KVM_DEV_TYPE_FSL_MPIC_42;
+ break;
+
+ default:
+ error_setg(errp, "Unsupported OpenPIC model %" PRIu32, opp->model);
+ return;
+ }
+
+ cd.type = kvm_openpic_model;
+ ret = kvm_vm_ioctl(s, KVM_CREATE_DEVICE, &cd);
+ if (ret < 0) {
+ error_setg(errp, "Can't create device %d: %s",
+ cd.type, strerror(errno));
+ return;
+ }
+ opp->fd = cd.fd;
+
+ sysbus_init_mmio(d, &opp->mem);
+ qdev_init_gpio_in(dev, kvm_openpic_set_irq, OPENPIC_MAX_IRQ);
+
+ opp->mem_listener.region_add = kvm_openpic_region_add;
+ opp->mem_listener.region_del = kvm_openpic_region_del;
+ memory_listener_register(&opp->mem_listener, &address_space_memory);
+
+ /* indicate pic capabilities */
+ msi_supported = true;
+ kvm_kernel_irqchip = true;
+ kvm_async_interrupts_allowed = true;
+
+ /* set up irq routing */
+ kvm_init_irq_routing(kvm_state);
+ for (i = 0; i < 256; ++i) {
+ kvm_irqchip_add_irq_route(kvm_state, i, 0, i);
+ }
+
+ kvm_msi_via_irqfd_allowed = true;
+ kvm_gsi_routing_allowed = true;
+
+ kvm_irqchip_commit_routes(s);
+}
+
+int kvm_openpic_connect_vcpu(DeviceState *d, CPUState *cs)
+{
+ KVMOpenPICState *opp = KVM_OPENPIC(d);
+
+ return kvm_vcpu_enable_cap(cs, KVM_CAP_IRQ_MPIC, 0, opp->fd,
+ kvm_arch_vcpu_id(cs));
+}
+
+static Property kvm_openpic_properties[] = {
+ DEFINE_PROP_UINT32("model", KVMOpenPICState, model,
+ OPENPIC_MODEL_FSL_MPIC_20),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void kvm_openpic_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = kvm_openpic_realize;
+ dc->props = kvm_openpic_properties;
+ dc->reset = kvm_openpic_reset;
+}
+
+static const TypeInfo kvm_openpic_info = {
+ .name = TYPE_KVM_OPENPIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(KVMOpenPICState),
+ .instance_init = kvm_openpic_init,
+ .class_init = kvm_openpic_class_init,
+};
+
+static void kvm_openpic_register_types(void)
+{
+ type_register_static(&kvm_openpic_info);
+}
+
+type_init(kvm_openpic_register_types)
diff --git a/hw/intc/pl190.c b/hw/intc/pl190.c
new file mode 100644
index 00000000..2bf359a7
--- /dev/null
+++ b/hw/intc/pl190.c
@@ -0,0 +1,292 @@
+/*
+ * Arm PrimeCell PL190 Vector Interrupt Controller
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+
+/* The number of virtual priority levels. 16 user vectors plus the
+ unvectored IRQ. Chained interrupts would require an additional level
+ if implemented. */
+
+#define PL190_NUM_PRIO 17
+
+#define TYPE_PL190 "pl190"
+#define PL190(obj) OBJECT_CHECK(PL190State, (obj), TYPE_PL190)
+
+typedef struct PL190State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t level;
+ uint32_t soft_level;
+ uint32_t irq_enable;
+ uint32_t fiq_select;
+ uint8_t vect_control[16];
+ uint32_t vect_addr[PL190_NUM_PRIO];
+ /* Mask containing interrupts with higher priority than this one. */
+ uint32_t prio_mask[PL190_NUM_PRIO + 1];
+ int protected;
+ /* Current priority level. */
+ int priority;
+ int prev_prio[PL190_NUM_PRIO];
+ qemu_irq irq;
+ qemu_irq fiq;
+} PL190State;
+
+static const unsigned char pl190_id[] =
+{ 0x90, 0x11, 0x04, 0x00, 0x0D, 0xf0, 0x05, 0xb1 };
+
+static inline uint32_t pl190_irq_level(PL190State *s)
+{
+ return (s->level | s->soft_level) & s->irq_enable & ~s->fiq_select;
+}
+
+/* Update interrupts. */
+static void pl190_update(PL190State *s)
+{
+ uint32_t level = pl190_irq_level(s);
+ int set;
+
+ set = (level & s->prio_mask[s->priority]) != 0;
+ qemu_set_irq(s->irq, set);
+ set = ((s->level | s->soft_level) & s->fiq_select) != 0;
+ qemu_set_irq(s->fiq, set);
+}
+
+static void pl190_set_irq(void *opaque, int irq, int level)
+{
+ PL190State *s = (PL190State *)opaque;
+
+ if (level)
+ s->level |= 1u << irq;
+ else
+ s->level &= ~(1u << irq);
+ pl190_update(s);
+}
+
+static void pl190_update_vectors(PL190State *s)
+{
+ uint32_t mask;
+ int i;
+ int n;
+
+ mask = 0;
+ for (i = 0; i < 16; i++)
+ {
+ s->prio_mask[i] = mask;
+ if (s->vect_control[i] & 0x20)
+ {
+ n = s->vect_control[i] & 0x1f;
+ mask |= 1 << n;
+ }
+ }
+ s->prio_mask[16] = mask;
+ pl190_update(s);
+}
+
+static uint64_t pl190_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL190State *s = (PL190State *)opaque;
+ int i;
+
+ if (offset >= 0xfe0 && offset < 0x1000) {
+ return pl190_id[(offset - 0xfe0) >> 2];
+ }
+ if (offset >= 0x100 && offset < 0x140) {
+ return s->vect_addr[(offset - 0x100) >> 2];
+ }
+ if (offset >= 0x200 && offset < 0x240) {
+ return s->vect_control[(offset - 0x200) >> 2];
+ }
+ switch (offset >> 2) {
+ case 0: /* IRQSTATUS */
+ return pl190_irq_level(s);
+ case 1: /* FIQSATUS */
+ return (s->level | s->soft_level) & s->fiq_select;
+ case 2: /* RAWINTR */
+ return s->level | s->soft_level;
+ case 3: /* INTSELECT */
+ return s->fiq_select;
+ case 4: /* INTENABLE */
+ return s->irq_enable;
+ case 6: /* SOFTINT */
+ return s->soft_level;
+ case 8: /* PROTECTION */
+ return s->protected;
+ case 12: /* VECTADDR */
+ /* Read vector address at the start of an ISR. Increases the
+ * current priority level to that of the current interrupt.
+ *
+ * Since an enabled interrupt X at priority P causes prio_mask[Y]
+ * to have bit X set for all Y > P, this loop will stop with
+ * i == the priority of the highest priority set interrupt.
+ */
+ for (i = 0; i < s->priority; i++) {
+ if ((s->level | s->soft_level) & s->prio_mask[i + 1]) {
+ break;
+ }
+ }
+
+ /* Reading this value with no pending interrupts is undefined.
+ We return the default address. */
+ if (i == PL190_NUM_PRIO)
+ return s->vect_addr[16];
+ if (i < s->priority)
+ {
+ s->prev_prio[i] = s->priority;
+ s->priority = i;
+ pl190_update(s);
+ }
+ return s->vect_addr[s->priority];
+ case 13: /* DEFVECTADDR */
+ return s->vect_addr[16];
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl190_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl190_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ PL190State *s = (PL190State *)opaque;
+
+ if (offset >= 0x100 && offset < 0x140) {
+ s->vect_addr[(offset - 0x100) >> 2] = val;
+ pl190_update_vectors(s);
+ return;
+ }
+ if (offset >= 0x200 && offset < 0x240) {
+ s->vect_control[(offset - 0x200) >> 2] = val;
+ pl190_update_vectors(s);
+ return;
+ }
+ switch (offset >> 2) {
+ case 0: /* SELECT */
+ /* This is a readonly register, but linux tries to write to it
+ anyway. Ignore the write. */
+ break;
+ case 3: /* INTSELECT */
+ s->fiq_select = val;
+ break;
+ case 4: /* INTENABLE */
+ s->irq_enable |= val;
+ break;
+ case 5: /* INTENCLEAR */
+ s->irq_enable &= ~val;
+ break;
+ case 6: /* SOFTINT */
+ s->soft_level |= val;
+ break;
+ case 7: /* SOFTINTCLEAR */
+ s->soft_level &= ~val;
+ break;
+ case 8: /* PROTECTION */
+ /* TODO: Protection (supervisor only access) is not implemented. */
+ s->protected = val & 1;
+ break;
+ case 12: /* VECTADDR */
+ /* Restore the previous priority level. The value written is
+ ignored. */
+ if (s->priority < PL190_NUM_PRIO)
+ s->priority = s->prev_prio[s->priority];
+ break;
+ case 13: /* DEFVECTADDR */
+ s->vect_addr[16] = val;
+ break;
+ case 0xc0: /* ITCR */
+ if (val) {
+ qemu_log_mask(LOG_UNIMP, "pl190: Test mode not implemented\n");
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl190_write: Bad offset %x\n", (int)offset);
+ return;
+ }
+ pl190_update(s);
+}
+
+static const MemoryRegionOps pl190_ops = {
+ .read = pl190_read,
+ .write = pl190_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pl190_reset(DeviceState *d)
+{
+ PL190State *s = PL190(d);
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ s->vect_addr[i] = 0;
+ s->vect_control[i] = 0;
+ }
+ s->vect_addr[16] = 0;
+ s->prio_mask[17] = 0xffffffff;
+ s->priority = PL190_NUM_PRIO;
+ pl190_update_vectors(s);
+}
+
+static int pl190_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PL190State *s = PL190(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl190_ops, s, "pl190", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ qdev_init_gpio_in(dev, pl190_set_irq, 32);
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->fiq);
+ return 0;
+}
+
+static const VMStateDescription vmstate_pl190 = {
+ .name = "pl190",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(level, PL190State),
+ VMSTATE_UINT32(soft_level, PL190State),
+ VMSTATE_UINT32(irq_enable, PL190State),
+ VMSTATE_UINT32(fiq_select, PL190State),
+ VMSTATE_UINT8_ARRAY(vect_control, PL190State, 16),
+ VMSTATE_UINT32_ARRAY(vect_addr, PL190State, PL190_NUM_PRIO),
+ VMSTATE_UINT32_ARRAY(prio_mask, PL190State, PL190_NUM_PRIO+1),
+ VMSTATE_INT32(protected, PL190State),
+ VMSTATE_INT32(priority, PL190State),
+ VMSTATE_INT32_ARRAY(prev_prio, PL190State, PL190_NUM_PRIO),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pl190_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pl190_init;
+ dc->reset = pl190_reset;
+ dc->vmsd = &vmstate_pl190;
+}
+
+static const TypeInfo pl190_info = {
+ .name = TYPE_PL190,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL190State),
+ .class_init = pl190_class_init,
+};
+
+static void pl190_register_types(void)
+{
+ type_register_static(&pl190_info);
+}
+
+type_init(pl190_register_types)
diff --git a/hw/intc/puv3_intc.c b/hw/intc/puv3_intc.c
new file mode 100644
index 00000000..c2803d07
--- /dev/null
+++ b/hw/intc/puv3_intc.c
@@ -0,0 +1,140 @@
+/*
+ * INTC device simulation in PKUnity SoC
+ *
+ * Copyright (C) 2010-2012 Guan Xuetao
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation, or any later version.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/sysbus.h"
+
+#undef DEBUG_PUV3
+#include "hw/unicore32/puv3.h"
+
+#define TYPE_PUV3_INTC "puv3_intc"
+#define PUV3_INTC(obj) OBJECT_CHECK(PUV3INTCState, (obj), TYPE_PUV3_INTC)
+
+typedef struct PUV3INTCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq parent_irq;
+
+ uint32_t reg_ICMR;
+ uint32_t reg_ICPR;
+} PUV3INTCState;
+
+/* Update interrupt status after enabled or pending bits have been changed. */
+static void puv3_intc_update(PUV3INTCState *s)
+{
+ if (s->reg_ICMR & s->reg_ICPR) {
+ qemu_irq_raise(s->parent_irq);
+ } else {
+ qemu_irq_lower(s->parent_irq);
+ }
+}
+
+/* Process a change in an external INTC input. */
+static void puv3_intc_handler(void *opaque, int irq, int level)
+{
+ PUV3INTCState *s = opaque;
+
+ DPRINTF("irq 0x%x, level 0x%x\n", irq, level);
+ if (level) {
+ s->reg_ICPR |= (1 << irq);
+ } else {
+ s->reg_ICPR &= ~(1 << irq);
+ }
+ puv3_intc_update(s);
+}
+
+static uint64_t puv3_intc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PUV3INTCState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (offset) {
+ case 0x04: /* INTC_ICMR */
+ ret = s->reg_ICMR;
+ break;
+ case 0x0c: /* INTC_ICIP */
+ ret = s->reg_ICPR; /* the same value with ICPR */
+ break;
+ default:
+ DPRINTF("Bad offset %x\n", (int)offset);
+ }
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, ret);
+ return ret;
+}
+
+static void puv3_intc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PUV3INTCState *s = opaque;
+
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, value);
+ switch (offset) {
+ case 0x00: /* INTC_ICLR */
+ case 0x14: /* INTC_ICCR */
+ break;
+ case 0x04: /* INTC_ICMR */
+ s->reg_ICMR = value;
+ break;
+ default:
+ DPRINTF("Bad offset 0x%x\n", (int)offset);
+ return;
+ }
+ puv3_intc_update(s);
+}
+
+static const MemoryRegionOps puv3_intc_ops = {
+ .read = puv3_intc_read,
+ .write = puv3_intc_write,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int puv3_intc_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PUV3INTCState *s = PUV3_INTC(dev);
+
+ qdev_init_gpio_in(dev, puv3_intc_handler, PUV3_IRQS_NR);
+ sysbus_init_irq(sbd, &s->parent_irq);
+
+ s->reg_ICMR = 0;
+ s->reg_ICPR = 0;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &puv3_intc_ops, s, "puv3_intc",
+ PUV3_REGS_OFFSET);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ return 0;
+}
+
+static void puv3_intc_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = puv3_intc_init;
+}
+
+static const TypeInfo puv3_intc_info = {
+ .name = TYPE_PUV3_INTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PUV3INTCState),
+ .class_init = puv3_intc_class_init,
+};
+
+static void puv3_intc_register_type(void)
+{
+ type_register_static(&puv3_intc_info);
+}
+
+type_init(puv3_intc_register_type)
diff --git a/hw/intc/realview_gic.c b/hw/intc/realview_gic.c
new file mode 100644
index 00000000..6c812961
--- /dev/null
+++ b/hw/intc/realview_gic.c
@@ -0,0 +1,87 @@
+/*
+ * ARM RealView Emulation Baseboard Interrupt Controller
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/intc/realview_gic.h"
+
+static void realview_gic_set_irq(void *opaque, int irq, int level)
+{
+ RealViewGICState *s = (RealViewGICState *)opaque;
+
+ qemu_set_irq(qdev_get_gpio_in(DEVICE(&s->gic), irq), level);
+}
+
+static void realview_gic_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ RealViewGICState *s = REALVIEW_GIC(dev);
+ SysBusDevice *busdev;
+ Error *err = NULL;
+ /* The GICs on the RealView boards have a fixed nonconfigurable
+ * number of interrupt lines, so we don't need to expose this as
+ * a qdev property.
+ */
+ int numirq = 96;
+
+ qdev_prop_set_uint32(DEVICE(&s->gic), "num-irq", numirq);
+ object_property_set_bool(OBJECT(&s->gic), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ busdev = SYS_BUS_DEVICE(&s->gic);
+
+ /* Pass through outbound IRQ lines from the GIC */
+ sysbus_pass_irq(sbd, busdev);
+
+ /* Pass through inbound GPIO lines to the GIC */
+ qdev_init_gpio_in(dev, realview_gic_set_irq, numirq - 32);
+
+ memory_region_add_subregion(&s->container, 0,
+ sysbus_mmio_get_region(busdev, 1));
+ memory_region_add_subregion(&s->container, 0x1000,
+ sysbus_mmio_get_region(busdev, 0));
+}
+
+static void realview_gic_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ RealViewGICState *s = REALVIEW_GIC(obj);
+ DeviceState *gicdev;
+
+ memory_region_init(&s->container, OBJECT(s),
+ "realview-gic-container", 0x2000);
+ sysbus_init_mmio(sbd, &s->container);
+
+ object_initialize(&s->gic, sizeof(s->gic), TYPE_ARM_GIC);
+ gicdev = DEVICE(&s->gic);
+ qdev_set_parent_bus(gicdev, sysbus_get_default());
+ qdev_prop_set_uint32(gicdev, "num-cpu", 1);
+}
+
+static void realview_gic_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = realview_gic_realize;
+}
+
+static const TypeInfo realview_gic_info = {
+ .name = TYPE_REALVIEW_GIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RealViewGICState),
+ .instance_init = realview_gic_init,
+ .class_init = realview_gic_class_init,
+};
+
+static void realview_gic_register_types(void)
+{
+ type_register_static(&realview_gic_info);
+}
+
+type_init(realview_gic_register_types)
diff --git a/hw/intc/s390_flic.c b/hw/intc/s390_flic.c
new file mode 100644
index 00000000..02e10b75
--- /dev/null
+++ b/hw/intc/s390_flic.c
@@ -0,0 +1,99 @@
+/*
+ * QEMU S390x floating interrupt controller (flic)
+ *
+ * Copyright 2014 IBM Corp.
+ * Author(s): Jens Freimann <jfrei@linux.vnet.ibm.com>
+ * Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "qemu/error-report.h"
+#include "hw/sysbus.h"
+#include "migration/qemu-file.h"
+#include "hw/s390x/s390_flic.h"
+#include "trace.h"
+
+S390FLICState *s390_get_flic(void)
+{
+ S390FLICState *fs;
+
+ fs = S390_FLIC_COMMON(object_resolve_path(TYPE_KVM_S390_FLIC, NULL));
+ if (!fs) {
+ fs = S390_FLIC_COMMON(object_resolve_path(TYPE_QEMU_S390_FLIC, NULL));
+ }
+ return fs;
+}
+
+void s390_flic_init(void)
+{
+ DeviceState *dev;
+
+ dev = s390_flic_kvm_create();
+ if (!dev) {
+ dev = qdev_create(NULL, TYPE_QEMU_S390_FLIC);
+ object_property_add_child(qdev_get_machine(), TYPE_QEMU_S390_FLIC,
+ OBJECT(dev), NULL);
+ }
+ qdev_init_nofail(dev);
+}
+
+static int qemu_s390_register_io_adapter(S390FLICState *fs, uint32_t id,
+ uint8_t isc, bool swap,
+ bool is_maskable)
+{
+ /* nothing to do */
+ return 0;
+}
+
+static int qemu_s390_io_adapter_map(S390FLICState *fs, uint32_t id,
+ uint64_t map_addr, bool do_map)
+{
+ /* nothing to do */
+ return 0;
+}
+
+static int qemu_s390_add_adapter_routes(S390FLICState *fs,
+ AdapterRoutes *routes)
+{
+ return -ENOSYS;
+}
+
+static void qemu_s390_release_adapter_routes(S390FLICState *fs,
+ AdapterRoutes *routes)
+{
+}
+
+static void qemu_s390_flic_class_init(ObjectClass *oc, void *data)
+{
+ S390FLICStateClass *fsc = S390_FLIC_COMMON_CLASS(oc);
+
+ fsc->register_io_adapter = qemu_s390_register_io_adapter;
+ fsc->io_adapter_map = qemu_s390_io_adapter_map;
+ fsc->add_adapter_routes = qemu_s390_add_adapter_routes;
+ fsc->release_adapter_routes = qemu_s390_release_adapter_routes;
+}
+
+static const TypeInfo qemu_s390_flic_info = {
+ .name = TYPE_QEMU_S390_FLIC,
+ .parent = TYPE_S390_FLIC_COMMON,
+ .instance_size = sizeof(QEMUS390FLICState),
+ .class_init = qemu_s390_flic_class_init,
+};
+
+static const TypeInfo s390_flic_common_info = {
+ .name = TYPE_S390_FLIC_COMMON,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(S390FLICState),
+ .class_size = sizeof(S390FLICStateClass),
+};
+
+static void qemu_s390_flic_register_types(void)
+{
+ type_register_static(&s390_flic_common_info);
+ type_register_static(&qemu_s390_flic_info);
+}
+
+type_init(qemu_s390_flic_register_types)
diff --git a/hw/intc/s390_flic_kvm.c b/hw/intc/s390_flic_kvm.c
new file mode 100644
index 00000000..b471e7a4
--- /dev/null
+++ b/hw/intc/s390_flic_kvm.c
@@ -0,0 +1,432 @@
+/*
+ * QEMU S390x KVM floating interrupt controller (flic)
+ *
+ * Copyright 2014 IBM Corp.
+ * Author(s): Jens Freimann <jfrei@linux.vnet.ibm.com>
+ * Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include <sys/ioctl.h>
+#include "qemu/error-report.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+#include "migration/qemu-file.h"
+#include "hw/s390x/s390_flic.h"
+#include "hw/s390x/adapter.h"
+#include "trace.h"
+
+#define FLIC_SAVE_INITIAL_SIZE getpagesize()
+#define FLIC_FAILED (-1UL)
+#define FLIC_SAVEVM_VERSION 1
+
+typedef struct KVMS390FLICState {
+ S390FLICState parent_obj;
+
+ uint32_t fd;
+} KVMS390FLICState;
+
+DeviceState *s390_flic_kvm_create(void)
+{
+ DeviceState *dev = NULL;
+
+ if (kvm_enabled()) {
+ dev = qdev_create(NULL, TYPE_KVM_S390_FLIC);
+ object_property_add_child(qdev_get_machine(), TYPE_KVM_S390_FLIC,
+ OBJECT(dev), NULL);
+ }
+ return dev;
+}
+
+/**
+ * flic_get_all_irqs - store all pending irqs in buffer
+ * @buf: pointer to buffer which is passed to kernel
+ * @len: length of buffer
+ * @flic: pointer to flic device state
+ *
+ * Returns: -ENOMEM if buffer is too small,
+ * -EINVAL if attr.group is invalid,
+ * -EFAULT if copying to userspace failed,
+ * on success return number of stored interrupts
+ */
+static int flic_get_all_irqs(KVMS390FLICState *flic,
+ void *buf, int len)
+{
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_GET_ALL_IRQS,
+ .addr = (uint64_t) buf,
+ .attr = len,
+ };
+ int rc;
+
+ rc = ioctl(flic->fd, KVM_GET_DEVICE_ATTR, &attr);
+
+ return rc == -1 ? -errno : rc;
+}
+
+static void flic_enable_pfault(KVMS390FLICState *flic)
+{
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_APF_ENABLE,
+ };
+ int rc;
+
+ rc = ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr);
+
+ if (rc) {
+ fprintf(stderr, "flic: couldn't enable pfault\n");
+ }
+}
+
+static void flic_disable_wait_pfault(KVMS390FLICState *flic)
+{
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_APF_DISABLE_WAIT,
+ };
+ int rc;
+
+ rc = ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr);
+
+ if (rc) {
+ fprintf(stderr, "flic: couldn't disable pfault\n");
+ }
+}
+
+/** flic_enqueue_irqs - returns 0 on success
+ * @buf: pointer to buffer which is passed to kernel
+ * @len: length of buffer
+ * @flic: pointer to flic device state
+ *
+ * Returns: -EINVAL if attr.group is unknown
+ */
+static int flic_enqueue_irqs(void *buf, uint64_t len,
+ KVMS390FLICState *flic)
+{
+ int rc;
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_ENQUEUE,
+ .addr = (uint64_t) buf,
+ .attr = len,
+ };
+
+ rc = ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr);
+
+ return rc ? -errno : 0;
+}
+
+int kvm_s390_inject_flic(struct kvm_s390_irq *irq)
+{
+ static KVMS390FLICState *flic;
+
+ if (unlikely(!flic)) {
+ flic = KVM_S390_FLIC(s390_get_flic());
+ }
+ return flic_enqueue_irqs(irq, sizeof(*irq), flic);
+}
+
+/**
+ * __get_all_irqs - store all pending irqs in buffer
+ * @flic: pointer to flic device state
+ * @buf: pointer to pointer to a buffer
+ * @len: length of buffer
+ *
+ * Returns: return value of flic_get_all_irqs
+ * Note: Retry and increase buffer size until flic_get_all_irqs
+ * either returns a value >= 0 or a negative error code.
+ * -ENOMEM is an exception, which means the buffer is too small
+ * and we should try again. Other negative error codes can be
+ * -EFAULT and -EINVAL which we ignore at this point
+ */
+static int __get_all_irqs(KVMS390FLICState *flic,
+ void **buf, int len)
+{
+ int r;
+
+ do {
+ /* returns -ENOMEM if buffer is too small and number
+ * of queued interrupts on success */
+ r = flic_get_all_irqs(flic, *buf, len);
+ if (r >= 0) {
+ break;
+ }
+ len *= 2;
+ *buf = g_try_realloc(*buf, len);
+ if (!buf) {
+ return -ENOMEM;
+ }
+ } while (r == -ENOMEM && len <= KVM_S390_FLIC_MAX_BUFFER);
+
+ return r;
+}
+
+static int kvm_s390_register_io_adapter(S390FLICState *fs, uint32_t id,
+ uint8_t isc, bool swap,
+ bool is_maskable)
+{
+ struct kvm_s390_io_adapter adapter = {
+ .id = id,
+ .isc = isc,
+ .maskable = is_maskable,
+ .swap = swap,
+ };
+ KVMS390FLICState *flic = KVM_S390_FLIC(fs);
+ int r, ret;
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_ADAPTER_REGISTER,
+ .addr = (uint64_t)&adapter,
+ };
+
+ if (!kvm_check_extension(kvm_state, KVM_CAP_IRQ_ROUTING)) {
+ /* nothing to do */
+ return 0;
+ }
+
+ r = ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr);
+
+ ret = r ? -errno : 0;
+ return ret;
+}
+
+static int kvm_s390_io_adapter_map(S390FLICState *fs, uint32_t id,
+ uint64_t map_addr, bool do_map)
+{
+ struct kvm_s390_io_adapter_req req = {
+ .id = id,
+ .type = do_map ? KVM_S390_IO_ADAPTER_MAP : KVM_S390_IO_ADAPTER_UNMAP,
+ .addr = map_addr,
+ };
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_ADAPTER_MODIFY,
+ .addr = (uint64_t)&req,
+ };
+ KVMS390FLICState *flic = KVM_S390_FLIC(fs);
+ int r;
+
+ if (!kvm_check_extension(kvm_state, KVM_CAP_IRQ_ROUTING)) {
+ /* nothing to do */
+ return 0;
+ }
+
+ r = ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr);
+ return r ? -errno : 0;
+}
+
+static int kvm_s390_add_adapter_routes(S390FLICState *fs,
+ AdapterRoutes *routes)
+{
+ int ret, i;
+ uint64_t ind_offset = routes->adapter.ind_offset;
+
+ for (i = 0; i < routes->num_routes; i++) {
+ ret = kvm_irqchip_add_adapter_route(kvm_state, &routes->adapter);
+ if (ret < 0) {
+ goto out_undo;
+ }
+ routes->gsi[i] = ret;
+ routes->adapter.ind_offset++;
+ }
+ /* Restore passed-in structure to original state. */
+ routes->adapter.ind_offset = ind_offset;
+ return 0;
+out_undo:
+ while (--i >= 0) {
+ kvm_irqchip_release_virq(kvm_state, routes->gsi[i]);
+ routes->gsi[i] = -1;
+ }
+ routes->adapter.ind_offset = ind_offset;
+ return ret;
+}
+
+static void kvm_s390_release_adapter_routes(S390FLICState *fs,
+ AdapterRoutes *routes)
+{
+ int i;
+
+ for (i = 0; i < routes->num_routes; i++) {
+ if (routes->gsi[i] >= 0) {
+ kvm_irqchip_release_virq(kvm_state, routes->gsi[i]);
+ routes->gsi[i] = -1;
+ }
+ }
+}
+
+/**
+ * kvm_flic_save - Save pending floating interrupts
+ * @f: QEMUFile containing migration state
+ * @opaque: pointer to flic device state
+ *
+ * Note: Pass buf and len to kernel. Start with one page and
+ * increase until buffer is sufficient or maxium size is
+ * reached
+ */
+static void kvm_flic_save(QEMUFile *f, void *opaque)
+{
+ KVMS390FLICState *flic = opaque;
+ int len = FLIC_SAVE_INITIAL_SIZE;
+ void *buf;
+ int count;
+
+ flic_disable_wait_pfault((struct KVMS390FLICState *) opaque);
+
+ buf = g_try_malloc0(len);
+ if (!buf) {
+ /* Storing FLIC_FAILED into the count field here will cause the
+ * target system to fail when attempting to load irqs from the
+ * migration state */
+ error_report("flic: couldn't allocate memory");
+ qemu_put_be64(f, FLIC_FAILED);
+ return;
+ }
+
+ count = __get_all_irqs(flic, &buf, len);
+ if (count < 0) {
+ error_report("flic: couldn't retrieve irqs from kernel, rc %d",
+ count);
+ /* Storing FLIC_FAILED into the count field here will cause the
+ * target system to fail when attempting to load irqs from the
+ * migration state */
+ qemu_put_be64(f, FLIC_FAILED);
+ } else {
+ qemu_put_be64(f, count);
+ qemu_put_buffer(f, (uint8_t *) buf,
+ count * sizeof(struct kvm_s390_irq));
+ }
+ g_free(buf);
+}
+
+/**
+ * kvm_flic_load - Load pending floating interrupts
+ * @f: QEMUFile containing migration state
+ * @opaque: pointer to flic device state
+ * @version_id: version id for migration
+ *
+ * Returns: value of flic_enqueue_irqs, -EINVAL on error
+ * Note: Do nothing when no interrupts where stored
+ * in QEMUFile
+ */
+static int kvm_flic_load(QEMUFile *f, void *opaque, int version_id)
+{
+ uint64_t len = 0;
+ uint64_t count = 0;
+ void *buf = NULL;
+ int r = 0;
+
+ if (version_id != FLIC_SAVEVM_VERSION) {
+ r = -EINVAL;
+ goto out;
+ }
+
+ flic_enable_pfault((struct KVMS390FLICState *) opaque);
+
+ count = qemu_get_be64(f);
+ len = count * sizeof(struct kvm_s390_irq);
+ if (count == FLIC_FAILED) {
+ r = -EINVAL;
+ goto out;
+ }
+ if (count == 0) {
+ r = 0;
+ goto out;
+ }
+ buf = g_try_malloc0(len);
+ if (!buf) {
+ r = -ENOMEM;
+ goto out;
+ }
+
+ if (qemu_get_buffer(f, (uint8_t *) buf, len) != len) {
+ r = -EINVAL;
+ goto out_free;
+ }
+ r = flic_enqueue_irqs(buf, len, (struct KVMS390FLICState *) opaque);
+
+out_free:
+ g_free(buf);
+out:
+ return r;
+}
+
+static void kvm_s390_flic_realize(DeviceState *dev, Error **errp)
+{
+ KVMS390FLICState *flic_state = KVM_S390_FLIC(dev);
+ struct kvm_create_device cd = {0};
+ int ret;
+
+ flic_state->fd = -1;
+ if (!kvm_check_extension(kvm_state, KVM_CAP_DEVICE_CTRL)) {
+ trace_flic_no_device_api(errno);
+ return;
+ }
+
+ cd.type = KVM_DEV_TYPE_FLIC;
+ ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &cd);
+ if (ret < 0) {
+ trace_flic_create_device(errno);
+ return;
+ }
+ flic_state->fd = cd.fd;
+
+ /* Register savevm handler for floating interrupts */
+ register_savevm(NULL, "s390-flic", 0, 1, kvm_flic_save,
+ kvm_flic_load, (void *) flic_state);
+}
+
+static void kvm_s390_flic_unrealize(DeviceState *dev, Error **errp)
+{
+ KVMS390FLICState *flic_state = KVM_S390_FLIC(dev);
+
+ unregister_savevm(DEVICE(flic_state), "s390-flic", flic_state);
+}
+
+static void kvm_s390_flic_reset(DeviceState *dev)
+{
+ KVMS390FLICState *flic = KVM_S390_FLIC(dev);
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_CLEAR_IRQS,
+ };
+ int rc = 0;
+
+ if (flic->fd == -1) {
+ return;
+ }
+
+ flic_disable_wait_pfault(flic);
+
+ rc = ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr);
+ if (rc) {
+ trace_flic_reset_failed(errno);
+ }
+
+ flic_enable_pfault(flic);
+}
+
+static void kvm_s390_flic_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ S390FLICStateClass *fsc = S390_FLIC_COMMON_CLASS(oc);
+
+ dc->realize = kvm_s390_flic_realize;
+ dc->unrealize = kvm_s390_flic_unrealize;
+ dc->reset = kvm_s390_flic_reset;
+ fsc->register_io_adapter = kvm_s390_register_io_adapter;
+ fsc->io_adapter_map = kvm_s390_io_adapter_map;
+ fsc->add_adapter_routes = kvm_s390_add_adapter_routes;
+ fsc->release_adapter_routes = kvm_s390_release_adapter_routes;
+}
+
+static const TypeInfo kvm_s390_flic_info = {
+ .name = TYPE_KVM_S390_FLIC,
+ .parent = TYPE_S390_FLIC_COMMON,
+ .instance_size = sizeof(KVMS390FLICState),
+ .class_init = kvm_s390_flic_class_init,
+};
+
+static void kvm_s390_flic_register_types(void)
+{
+ type_register_static(&kvm_s390_flic_info);
+}
+
+type_init(kvm_s390_flic_register_types)
diff --git a/hw/intc/sh_intc.c b/hw/intc/sh_intc.c
new file mode 100644
index 00000000..55c76e4a
--- /dev/null
+++ b/hw/intc/sh_intc.c
@@ -0,0 +1,512 @@
+/*
+ * SuperH interrupt controller module
+ *
+ * Copyright (c) 2007 Magnus Damm
+ * Based on sh_timer.c and arm_timer.c by Paul Brook
+ * Copyright (c) 2005-2006 CodeSourcery.
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sh4/sh_intc.h"
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+
+//#define DEBUG_INTC
+//#define DEBUG_INTC_SOURCES
+
+#define INTC_A7(x) ((x) & 0x1fffffff)
+
+void sh_intc_toggle_source(struct intc_source *source,
+ int enable_adj, int assert_adj)
+{
+ int enable_changed = 0;
+ int pending_changed = 0;
+ int old_pending;
+
+ if ((source->enable_count == source->enable_max) && (enable_adj == -1))
+ enable_changed = -1;
+
+ source->enable_count += enable_adj;
+
+ if (source->enable_count == source->enable_max)
+ enable_changed = 1;
+
+ source->asserted += assert_adj;
+
+ old_pending = source->pending;
+ source->pending = source->asserted &&
+ (source->enable_count == source->enable_max);
+
+ if (old_pending != source->pending)
+ pending_changed = 1;
+
+ if (pending_changed) {
+ if (source->pending) {
+ source->parent->pending++;
+ if (source->parent->pending == 1) {
+ cpu_interrupt(first_cpu, CPU_INTERRUPT_HARD);
+ }
+ } else {
+ source->parent->pending--;
+ if (source->parent->pending == 0) {
+ cpu_reset_interrupt(first_cpu, CPU_INTERRUPT_HARD);
+ }
+ }
+ }
+
+ if (enable_changed || assert_adj || pending_changed) {
+#ifdef DEBUG_INTC_SOURCES
+ printf("sh_intc: (%d/%d/%d/%d) interrupt source 0x%x %s%s%s\n",
+ source->parent->pending,
+ source->asserted,
+ source->enable_count,
+ source->enable_max,
+ source->vect,
+ source->asserted ? "asserted " :
+ assert_adj ? "deasserted" : "",
+ enable_changed == 1 ? "enabled " :
+ enable_changed == -1 ? "disabled " : "",
+ source->pending ? "pending" : "");
+#endif
+ }
+}
+
+static void sh_intc_set_irq (void *opaque, int n, int level)
+{
+ struct intc_desc *desc = opaque;
+ struct intc_source *source = &(desc->sources[n]);
+
+ if (level && !source->asserted)
+ sh_intc_toggle_source(source, 0, 1);
+ else if (!level && source->asserted)
+ sh_intc_toggle_source(source, 0, -1);
+}
+
+int sh_intc_get_pending_vector(struct intc_desc *desc, int imask)
+{
+ unsigned int i;
+
+ /* slow: use a linked lists of pending sources instead */
+ /* wrong: take interrupt priority into account (one list per priority) */
+
+ if (imask == 0x0f) {
+ return -1; /* FIXME, update code to include priority per source */
+ }
+
+ for (i = 0; i < desc->nr_sources; i++) {
+ struct intc_source *source = desc->sources + i;
+
+ if (source->pending) {
+#ifdef DEBUG_INTC_SOURCES
+ printf("sh_intc: (%d) returning interrupt source 0x%x\n",
+ desc->pending, source->vect);
+#endif
+ return source->vect;
+ }
+ }
+
+ abort();
+}
+
+#define INTC_MODE_NONE 0
+#define INTC_MODE_DUAL_SET 1
+#define INTC_MODE_DUAL_CLR 2
+#define INTC_MODE_ENABLE_REG 3
+#define INTC_MODE_MASK_REG 4
+#define INTC_MODE_IS_PRIO 8
+
+static unsigned int sh_intc_mode(unsigned long address,
+ unsigned long set_reg, unsigned long clr_reg)
+{
+ if ((address != INTC_A7(set_reg)) &&
+ (address != INTC_A7(clr_reg)))
+ return INTC_MODE_NONE;
+
+ if (set_reg && clr_reg) {
+ if (address == INTC_A7(set_reg))
+ return INTC_MODE_DUAL_SET;
+ else
+ return INTC_MODE_DUAL_CLR;
+ }
+
+ if (set_reg)
+ return INTC_MODE_ENABLE_REG;
+ else
+ return INTC_MODE_MASK_REG;
+}
+
+static void sh_intc_locate(struct intc_desc *desc,
+ unsigned long address,
+ unsigned long **datap,
+ intc_enum **enums,
+ unsigned int *first,
+ unsigned int *width,
+ unsigned int *modep)
+{
+ unsigned int i, mode;
+
+ /* this is slow but works for now */
+
+ if (desc->mask_regs) {
+ for (i = 0; i < desc->nr_mask_regs; i++) {
+ struct intc_mask_reg *mr = desc->mask_regs + i;
+
+ mode = sh_intc_mode(address, mr->set_reg, mr->clr_reg);
+ if (mode == INTC_MODE_NONE)
+ continue;
+
+ *modep = mode;
+ *datap = &mr->value;
+ *enums = mr->enum_ids;
+ *first = mr->reg_width - 1;
+ *width = 1;
+ return;
+ }
+ }
+
+ if (desc->prio_regs) {
+ for (i = 0; i < desc->nr_prio_regs; i++) {
+ struct intc_prio_reg *pr = desc->prio_regs + i;
+
+ mode = sh_intc_mode(address, pr->set_reg, pr->clr_reg);
+ if (mode == INTC_MODE_NONE)
+ continue;
+
+ *modep = mode | INTC_MODE_IS_PRIO;
+ *datap = &pr->value;
+ *enums = pr->enum_ids;
+ *first = (pr->reg_width / pr->field_width) - 1;
+ *width = pr->field_width;
+ return;
+ }
+ }
+
+ abort();
+}
+
+static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id,
+ int enable, int is_group)
+{
+ struct intc_source *source = desc->sources + id;
+
+ if (!id)
+ return;
+
+ if (!source->next_enum_id && (!source->enable_max || !source->vect)) {
+#ifdef DEBUG_INTC_SOURCES
+ printf("sh_intc: reserved interrupt source %d modified\n", id);
+#endif
+ return;
+ }
+
+ if (source->vect)
+ sh_intc_toggle_source(source, enable ? 1 : -1, 0);
+
+#ifdef DEBUG_INTC
+ else {
+ printf("setting interrupt group %d to %d\n", id, !!enable);
+ }
+#endif
+
+ if ((is_group || !source->vect) && source->next_enum_id) {
+ sh_intc_toggle_mask(desc, source->next_enum_id, enable, 1);
+ }
+
+#ifdef DEBUG_INTC
+ if (!source->vect) {
+ printf("setting interrupt group %d to %d - done\n", id, !!enable);
+ }
+#endif
+}
+
+static uint64_t sh_intc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ struct intc_desc *desc = opaque;
+ intc_enum *enum_ids = NULL;
+ unsigned int first = 0;
+ unsigned int width = 0;
+ unsigned int mode = 0;
+ unsigned long *valuep;
+
+#ifdef DEBUG_INTC
+ printf("sh_intc_read 0x%lx\n", (unsigned long) offset);
+#endif
+
+ sh_intc_locate(desc, (unsigned long)offset, &valuep,
+ &enum_ids, &first, &width, &mode);
+ return *valuep;
+}
+
+static void sh_intc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ struct intc_desc *desc = opaque;
+ intc_enum *enum_ids = NULL;
+ unsigned int first = 0;
+ unsigned int width = 0;
+ unsigned int mode = 0;
+ unsigned int k;
+ unsigned long *valuep;
+ unsigned long mask;
+
+#ifdef DEBUG_INTC
+ printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
+#endif
+
+ sh_intc_locate(desc, (unsigned long)offset, &valuep,
+ &enum_ids, &first, &width, &mode);
+
+ switch (mode) {
+ case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break;
+ case INTC_MODE_DUAL_SET: value |= *valuep; break;
+ case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break;
+ default: abort();
+ }
+
+ for (k = 0; k <= first; k++) {
+ mask = ((1 << width) - 1) << ((first - k) * width);
+
+ if ((*valuep & mask) == (value & mask))
+ continue;
+#if 0
+ printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n",
+ k, first, enum_ids[k], (unsigned int)mask);
+#endif
+ sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0);
+ }
+
+ *valuep = value;
+
+#ifdef DEBUG_INTC
+ printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value);
+#endif
+}
+
+static const MemoryRegionOps sh_intc_ops = {
+ .read = sh_intc_read,
+ .write = sh_intc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id)
+{
+ if (id)
+ return desc->sources + id;
+
+ return NULL;
+}
+
+static unsigned int sh_intc_register(MemoryRegion *sysmem,
+ struct intc_desc *desc,
+ const unsigned long address,
+ const char *type,
+ const char *action,
+ const unsigned int index)
+{
+ char name[60];
+ MemoryRegion *iomem, *iomem_p4, *iomem_a7;
+
+ if (!address) {
+ return 0;
+ }
+
+ iomem = &desc->iomem;
+ iomem_p4 = desc->iomem_aliases + index;
+ iomem_a7 = iomem_p4 + 1;
+
+#define SH_INTC_IOMEM_FORMAT "interrupt-controller-%s-%s-%s"
+ snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "p4");
+ memory_region_init_alias(iomem_p4, NULL, name, iomem, INTC_A7(address), 4);
+ memory_region_add_subregion(sysmem, P4ADDR(address), iomem_p4);
+
+ snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "a7");
+ memory_region_init_alias(iomem_a7, NULL, name, iomem, INTC_A7(address), 4);
+ memory_region_add_subregion(sysmem, A7ADDR(address), iomem_a7);
+#undef SH_INTC_IOMEM_FORMAT
+
+ /* used to increment aliases index */
+ return 2;
+}
+
+static void sh_intc_register_source(struct intc_desc *desc,
+ intc_enum source,
+ struct intc_group *groups,
+ int nr_groups)
+{
+ unsigned int i, k;
+ struct intc_source *s;
+
+ if (desc->mask_regs) {
+ for (i = 0; i < desc->nr_mask_regs; i++) {
+ struct intc_mask_reg *mr = desc->mask_regs + i;
+
+ for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) {
+ if (mr->enum_ids[k] != source)
+ continue;
+
+ s = sh_intc_source(desc, mr->enum_ids[k]);
+ if (s)
+ s->enable_max++;
+ }
+ }
+ }
+
+ if (desc->prio_regs) {
+ for (i = 0; i < desc->nr_prio_regs; i++) {
+ struct intc_prio_reg *pr = desc->prio_regs + i;
+
+ for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) {
+ if (pr->enum_ids[k] != source)
+ continue;
+
+ s = sh_intc_source(desc, pr->enum_ids[k]);
+ if (s)
+ s->enable_max++;
+ }
+ }
+ }
+
+ if (groups) {
+ for (i = 0; i < nr_groups; i++) {
+ struct intc_group *gr = groups + i;
+
+ for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) {
+ if (gr->enum_ids[k] != source)
+ continue;
+
+ s = sh_intc_source(desc, gr->enum_ids[k]);
+ if (s)
+ s->enable_max++;
+ }
+ }
+ }
+
+}
+
+void sh_intc_register_sources(struct intc_desc *desc,
+ struct intc_vect *vectors,
+ int nr_vectors,
+ struct intc_group *groups,
+ int nr_groups)
+{
+ unsigned int i, k;
+ struct intc_source *s;
+
+ for (i = 0; i < nr_vectors; i++) {
+ struct intc_vect *vect = vectors + i;
+
+ sh_intc_register_source(desc, vect->enum_id, groups, nr_groups);
+ s = sh_intc_source(desc, vect->enum_id);
+ if (s) {
+ s->vect = vect->vect;
+
+#ifdef DEBUG_INTC_SOURCES
+ printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n",
+ vect->enum_id, s->vect, s->enable_count, s->enable_max);
+#endif
+ }
+ }
+
+ if (groups) {
+ for (i = 0; i < nr_groups; i++) {
+ struct intc_group *gr = groups + i;
+
+ s = sh_intc_source(desc, gr->enum_id);
+ s->next_enum_id = gr->enum_ids[0];
+
+ for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) {
+ if (!gr->enum_ids[k])
+ continue;
+
+ s = sh_intc_source(desc, gr->enum_ids[k - 1]);
+ s->next_enum_id = gr->enum_ids[k];
+ }
+
+#ifdef DEBUG_INTC_SOURCES
+ printf("sh_intc: registered group %d (%d/%d)\n",
+ gr->enum_id, s->enable_count, s->enable_max);
+#endif
+ }
+ }
+}
+
+int sh_intc_init(MemoryRegion *sysmem,
+ struct intc_desc *desc,
+ int nr_sources,
+ struct intc_mask_reg *mask_regs,
+ int nr_mask_regs,
+ struct intc_prio_reg *prio_regs,
+ int nr_prio_regs)
+{
+ unsigned int i, j;
+
+ desc->pending = 0;
+ desc->nr_sources = nr_sources;
+ desc->mask_regs = mask_regs;
+ desc->nr_mask_regs = nr_mask_regs;
+ desc->prio_regs = prio_regs;
+ desc->nr_prio_regs = nr_prio_regs;
+ /* Allocate 4 MemoryRegions per register (2 actions * 2 aliases).
+ **/
+ desc->iomem_aliases = g_new0(MemoryRegion,
+ (nr_mask_regs + nr_prio_regs) * 4);
+
+ j = 0;
+ i = sizeof(struct intc_source) * nr_sources;
+ desc->sources = g_malloc0(i);
+
+ for (i = 0; i < desc->nr_sources; i++) {
+ struct intc_source *source = desc->sources + i;
+
+ source->parent = desc;
+ }
+
+ desc->irqs = qemu_allocate_irqs(sh_intc_set_irq, desc, nr_sources);
+
+ memory_region_init_io(&desc->iomem, NULL, &sh_intc_ops, desc,
+ "interrupt-controller", 0x100000000ULL);
+
+#define INT_REG_PARAMS(reg_struct, type, action, j) \
+ reg_struct->action##_reg, #type, #action, j
+ if (desc->mask_regs) {
+ for (i = 0; i < desc->nr_mask_regs; i++) {
+ struct intc_mask_reg *mr = desc->mask_regs + i;
+
+ j += sh_intc_register(sysmem, desc,
+ INT_REG_PARAMS(mr, mask, set, j));
+ j += sh_intc_register(sysmem, desc,
+ INT_REG_PARAMS(mr, mask, clr, j));
+ }
+ }
+
+ if (desc->prio_regs) {
+ for (i = 0; i < desc->nr_prio_regs; i++) {
+ struct intc_prio_reg *pr = desc->prio_regs + i;
+
+ j += sh_intc_register(sysmem, desc,
+ INT_REG_PARAMS(pr, prio, set, j));
+ j += sh_intc_register(sysmem, desc,
+ INT_REG_PARAMS(pr, prio, clr, j));
+ }
+ }
+#undef INT_REG_PARAMS
+
+ return 0;
+}
+
+/* Assert level <n> IRL interrupt.
+ 0:deassert. 1:lowest priority,... 15:highest priority. */
+void sh_intc_set_irl(void *opaque, int n, int level)
+{
+ struct intc_source *s = opaque;
+ int i, irl = level ^ 15;
+ for (i = 0; (s = sh_intc_source(s->parent, s->next_enum_id)); i++) {
+ if (i == irl)
+ sh_intc_toggle_source(s, s->enable_count?0:1, s->asserted?0:1);
+ else
+ if (s->asserted)
+ sh_intc_toggle_source(s, 0, -1);
+ }
+}
diff --git a/hw/intc/slavio_intctl.c b/hw/intc/slavio_intctl.c
new file mode 100644
index 00000000..f22aba03
--- /dev/null
+++ b/hw/intc/slavio_intctl.c
@@ -0,0 +1,471 @@
+/*
+ * QEMU Sparc SLAVIO interrupt controller emulation
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "hw/sparc/sun4m.h"
+#include "monitor/monitor.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+
+//#define DEBUG_IRQ_COUNT
+
+/*
+ * Registers of interrupt controller in sun4m.
+ *
+ * This is the interrupt controller part of chip STP2001 (Slave I/O), also
+ * produced as NCR89C105. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
+ *
+ * There is a system master controller and one for each cpu.
+ *
+ */
+
+#define MAX_CPUS 16
+#define MAX_PILS 16
+
+struct SLAVIO_INTCTLState;
+
+typedef struct SLAVIO_CPUINTCTLState {
+ MemoryRegion iomem;
+ struct SLAVIO_INTCTLState *master;
+ uint32_t intreg_pending;
+ uint32_t cpu;
+ uint32_t irl_out;
+} SLAVIO_CPUINTCTLState;
+
+#define TYPE_SLAVIO_INTCTL "slavio_intctl"
+#define SLAVIO_INTCTL(obj) \
+ OBJECT_CHECK(SLAVIO_INTCTLState, (obj), TYPE_SLAVIO_INTCTL)
+
+typedef struct SLAVIO_INTCTLState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+#ifdef DEBUG_IRQ_COUNT
+ uint64_t irq_count[32];
+#endif
+ qemu_irq cpu_irqs[MAX_CPUS][MAX_PILS];
+ SLAVIO_CPUINTCTLState slaves[MAX_CPUS];
+ uint32_t intregm_pending;
+ uint32_t intregm_disabled;
+ uint32_t target_cpu;
+} SLAVIO_INTCTLState;
+
+#define INTCTL_MAXADDR 0xf
+#define INTCTL_SIZE (INTCTL_MAXADDR + 1)
+#define INTCTLM_SIZE 0x14
+#define MASTER_IRQ_MASK ~0x0fa2007f
+#define MASTER_DISABLE 0x80000000
+#define CPU_SOFTIRQ_MASK 0xfffe0000
+#define CPU_IRQ_INT15_IN (1 << 15)
+#define CPU_IRQ_TIMER_IN (1 << 14)
+
+static void slavio_check_interrupts(SLAVIO_INTCTLState *s, int set_irqs);
+
+// per-cpu interrupt controller
+static uint64_t slavio_intctl_mem_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SLAVIO_CPUINTCTLState *s = opaque;
+ uint32_t saddr, ret;
+
+ saddr = addr >> 2;
+ switch (saddr) {
+ case 0:
+ ret = s->intreg_pending;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ trace_slavio_intctl_mem_readl(s->cpu, addr, ret);
+
+ return ret;
+}
+
+static void slavio_intctl_mem_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ SLAVIO_CPUINTCTLState *s = opaque;
+ uint32_t saddr;
+
+ saddr = addr >> 2;
+ trace_slavio_intctl_mem_writel(s->cpu, addr, val);
+ switch (saddr) {
+ case 1: // clear pending softints
+ val &= CPU_SOFTIRQ_MASK | CPU_IRQ_INT15_IN;
+ s->intreg_pending &= ~val;
+ slavio_check_interrupts(s->master, 1);
+ trace_slavio_intctl_mem_writel_clear(s->cpu, val, s->intreg_pending);
+ break;
+ case 2: // set softint
+ val &= CPU_SOFTIRQ_MASK;
+ s->intreg_pending |= val;
+ slavio_check_interrupts(s->master, 1);
+ trace_slavio_intctl_mem_writel_set(s->cpu, val, s->intreg_pending);
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps slavio_intctl_mem_ops = {
+ .read = slavio_intctl_mem_readl,
+ .write = slavio_intctl_mem_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+// master system interrupt controller
+static uint64_t slavio_intctlm_mem_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SLAVIO_INTCTLState *s = opaque;
+ uint32_t saddr, ret;
+
+ saddr = addr >> 2;
+ switch (saddr) {
+ case 0:
+ ret = s->intregm_pending & ~MASTER_DISABLE;
+ break;
+ case 1:
+ ret = s->intregm_disabled & MASTER_IRQ_MASK;
+ break;
+ case 4:
+ ret = s->target_cpu;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ trace_slavio_intctlm_mem_readl(addr, ret);
+
+ return ret;
+}
+
+static void slavio_intctlm_mem_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ SLAVIO_INTCTLState *s = opaque;
+ uint32_t saddr;
+
+ saddr = addr >> 2;
+ trace_slavio_intctlm_mem_writel(addr, val);
+ switch (saddr) {
+ case 2: // clear (enable)
+ // Force clear unused bits
+ val &= MASTER_IRQ_MASK;
+ s->intregm_disabled &= ~val;
+ trace_slavio_intctlm_mem_writel_enable(val, s->intregm_disabled);
+ slavio_check_interrupts(s, 1);
+ break;
+ case 3: // set (disable; doesn't affect pending)
+ // Force clear unused bits
+ val &= MASTER_IRQ_MASK;
+ s->intregm_disabled |= val;
+ slavio_check_interrupts(s, 1);
+ trace_slavio_intctlm_mem_writel_disable(val, s->intregm_disabled);
+ break;
+ case 4:
+ s->target_cpu = val & (MAX_CPUS - 1);
+ slavio_check_interrupts(s, 1);
+ trace_slavio_intctlm_mem_writel_target(s->target_cpu);
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps slavio_intctlm_mem_ops = {
+ .read = slavio_intctlm_mem_readl,
+ .write = slavio_intctlm_mem_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+void slavio_pic_info(Monitor *mon, DeviceState *dev)
+{
+ SLAVIO_INTCTLState *s = SLAVIO_INTCTL(dev);
+ int i;
+
+ for (i = 0; i < MAX_CPUS; i++) {
+ monitor_printf(mon, "per-cpu %d: pending 0x%08x\n", i,
+ s->slaves[i].intreg_pending);
+ }
+ monitor_printf(mon, "master: pending 0x%08x, disabled 0x%08x\n",
+ s->intregm_pending, s->intregm_disabled);
+}
+
+void slavio_irq_info(Monitor *mon, DeviceState *dev)
+{
+#ifndef DEBUG_IRQ_COUNT
+ monitor_printf(mon, "irq statistic code not compiled.\n");
+#else
+ SLAVIO_INTCTLState *s = SLAVIO_INTCTL(dev);
+ int i;
+ int64_t count;
+
+ s = SLAVIO_INTCTL(dev);
+ monitor_printf(mon, "IRQ statistics:\n");
+ for (i = 0; i < 32; i++) {
+ count = s->irq_count[i];
+ if (count > 0)
+ monitor_printf(mon, "%2d: %" PRId64 "\n", i, count);
+ }
+#endif
+}
+
+static const uint32_t intbit_to_level[] = {
+ 2, 3, 5, 7, 9, 11, 13, 2, 3, 5, 7, 9, 11, 13, 12, 12,
+ 6, 13, 4, 10, 8, 9, 11, 0, 0, 0, 0, 15, 15, 15, 15, 0,
+};
+
+static void slavio_check_interrupts(SLAVIO_INTCTLState *s, int set_irqs)
+{
+ uint32_t pending = s->intregm_pending, pil_pending;
+ unsigned int i, j;
+
+ pending &= ~s->intregm_disabled;
+
+ trace_slavio_check_interrupts(pending, s->intregm_disabled);
+ for (i = 0; i < MAX_CPUS; i++) {
+ pil_pending = 0;
+
+ /* If we are the current interrupt target, get hard interrupts */
+ if (pending && !(s->intregm_disabled & MASTER_DISABLE) &&
+ (i == s->target_cpu)) {
+ for (j = 0; j < 32; j++) {
+ if ((pending & (1 << j)) && intbit_to_level[j]) {
+ pil_pending |= 1 << intbit_to_level[j];
+ }
+ }
+ }
+
+ /* Calculate current pending hard interrupts for display */
+ s->slaves[i].intreg_pending &= CPU_SOFTIRQ_MASK | CPU_IRQ_INT15_IN |
+ CPU_IRQ_TIMER_IN;
+ if (i == s->target_cpu) {
+ for (j = 0; j < 32; j++) {
+ if ((s->intregm_pending & (1U << j)) && intbit_to_level[j]) {
+ s->slaves[i].intreg_pending |= 1 << intbit_to_level[j];
+ }
+ }
+ }
+
+ /* Level 15 and CPU timer interrupts are only masked when
+ the MASTER_DISABLE bit is set */
+ if (!(s->intregm_disabled & MASTER_DISABLE)) {
+ pil_pending |= s->slaves[i].intreg_pending &
+ (CPU_IRQ_INT15_IN | CPU_IRQ_TIMER_IN);
+ }
+
+ /* Add soft interrupts */
+ pil_pending |= (s->slaves[i].intreg_pending & CPU_SOFTIRQ_MASK) >> 16;
+
+ if (set_irqs) {
+ /* Since there is not really an interrupt 0 (and pil_pending
+ * and irl_out bit zero are thus always zero) there is no need
+ * to do anything with cpu_irqs[i][0] and it is OK not to do
+ * the j=0 iteration of this loop.
+ */
+ for (j = MAX_PILS-1; j > 0; j--) {
+ if (pil_pending & (1 << j)) {
+ if (!(s->slaves[i].irl_out & (1 << j))) {
+ qemu_irq_raise(s->cpu_irqs[i][j]);
+ }
+ } else {
+ if (s->slaves[i].irl_out & (1 << j)) {
+ qemu_irq_lower(s->cpu_irqs[i][j]);
+ }
+ }
+ }
+ }
+ s->slaves[i].irl_out = pil_pending;
+ }
+}
+
+/*
+ * "irq" here is the bit number in the system interrupt register to
+ * separate serial and keyboard interrupts sharing a level.
+ */
+static void slavio_set_irq(void *opaque, int irq, int level)
+{
+ SLAVIO_INTCTLState *s = opaque;
+ uint32_t mask = 1 << irq;
+ uint32_t pil = intbit_to_level[irq];
+ unsigned int i;
+
+ trace_slavio_set_irq(s->target_cpu, irq, pil, level);
+ if (pil > 0) {
+ if (level) {
+#ifdef DEBUG_IRQ_COUNT
+ s->irq_count[pil]++;
+#endif
+ s->intregm_pending |= mask;
+ if (pil == 15) {
+ for (i = 0; i < MAX_CPUS; i++) {
+ s->slaves[i].intreg_pending |= 1 << pil;
+ }
+ }
+ } else {
+ s->intregm_pending &= ~mask;
+ if (pil == 15) {
+ for (i = 0; i < MAX_CPUS; i++) {
+ s->slaves[i].intreg_pending &= ~(1 << pil);
+ }
+ }
+ }
+ slavio_check_interrupts(s, 1);
+ }
+}
+
+static void slavio_set_timer_irq_cpu(void *opaque, int cpu, int level)
+{
+ SLAVIO_INTCTLState *s = opaque;
+
+ trace_slavio_set_timer_irq_cpu(cpu, level);
+
+ if (level) {
+ s->slaves[cpu].intreg_pending |= CPU_IRQ_TIMER_IN;
+ } else {
+ s->slaves[cpu].intreg_pending &= ~CPU_IRQ_TIMER_IN;
+ }
+
+ slavio_check_interrupts(s, 1);
+}
+
+static void slavio_set_irq_all(void *opaque, int irq, int level)
+{
+ if (irq < 32) {
+ slavio_set_irq(opaque, irq, level);
+ } else {
+ slavio_set_timer_irq_cpu(opaque, irq - 32, level);
+ }
+}
+
+static int vmstate_intctl_post_load(void *opaque, int version_id)
+{
+ SLAVIO_INTCTLState *s = opaque;
+
+ slavio_check_interrupts(s, 0);
+ return 0;
+}
+
+static const VMStateDescription vmstate_intctl_cpu = {
+ .name ="slavio_intctl_cpu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(intreg_pending, SLAVIO_CPUINTCTLState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_intctl = {
+ .name ="slavio_intctl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = vmstate_intctl_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(slaves, SLAVIO_INTCTLState, MAX_CPUS, 1,
+ vmstate_intctl_cpu, SLAVIO_CPUINTCTLState),
+ VMSTATE_UINT32(intregm_pending, SLAVIO_INTCTLState),
+ VMSTATE_UINT32(intregm_disabled, SLAVIO_INTCTLState),
+ VMSTATE_UINT32(target_cpu, SLAVIO_INTCTLState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void slavio_intctl_reset(DeviceState *d)
+{
+ SLAVIO_INTCTLState *s = SLAVIO_INTCTL(d);
+ int i;
+
+ for (i = 0; i < MAX_CPUS; i++) {
+ s->slaves[i].intreg_pending = 0;
+ s->slaves[i].irl_out = 0;
+ }
+ s->intregm_disabled = ~MASTER_IRQ_MASK;
+ s->intregm_pending = 0;
+ s->target_cpu = 0;
+ slavio_check_interrupts(s, 0);
+}
+
+static int slavio_intctl_init1(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ SLAVIO_INTCTLState *s = SLAVIO_INTCTL(dev);
+ unsigned int i, j;
+ char slave_name[45];
+
+ qdev_init_gpio_in(dev, slavio_set_irq_all, 32 + MAX_CPUS);
+ memory_region_init_io(&s->iomem, OBJECT(s), &slavio_intctlm_mem_ops, s,
+ "master-interrupt-controller", INTCTLM_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ for (i = 0; i < MAX_CPUS; i++) {
+ snprintf(slave_name, sizeof(slave_name),
+ "slave-interrupt-controller-%i", i);
+ for (j = 0; j < MAX_PILS; j++) {
+ sysbus_init_irq(sbd, &s->cpu_irqs[i][j]);
+ }
+ memory_region_init_io(&s->slaves[i].iomem, OBJECT(s),
+ &slavio_intctl_mem_ops,
+ &s->slaves[i], slave_name, INTCTL_SIZE);
+ sysbus_init_mmio(sbd, &s->slaves[i].iomem);
+ s->slaves[i].cpu = i;
+ s->slaves[i].master = s;
+ }
+
+ return 0;
+}
+
+static void slavio_intctl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = slavio_intctl_init1;
+ dc->reset = slavio_intctl_reset;
+ dc->vmsd = &vmstate_intctl;
+}
+
+static const TypeInfo slavio_intctl_info = {
+ .name = TYPE_SLAVIO_INTCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SLAVIO_INTCTLState),
+ .class_init = slavio_intctl_class_init,
+};
+
+static void slavio_intctl_register_types(void)
+{
+ type_register_static(&slavio_intctl_info);
+}
+
+type_init(slavio_intctl_register_types)
diff --git a/hw/intc/xics.c b/hw/intc/xics.c
new file mode 100644
index 00000000..924b1ae3
--- /dev/null
+++ b/hw/intc/xics.c
@@ -0,0 +1,1084 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * PAPR Virtualized Interrupt System, aka ICS/ICP aka xics
+ *
+ * Copyright (c) 2010,2011 David Gibson, IBM Corporation.
+ *
+ * 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.
+ *
+ */
+
+#include "hw/hw.h"
+#include "trace.h"
+#include "qemu/timer.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/xics.h"
+#include "qemu/error-report.h"
+#include "qapi/visitor.h"
+
+static int get_cpu_index_by_dt_id(int cpu_dt_id)
+{
+ PowerPCCPU *cpu = ppc_get_vcpu_by_dt_id(cpu_dt_id);
+
+ if (cpu) {
+ return cpu->parent_obj.cpu_index;
+ }
+
+ return -1;
+}
+
+void xics_cpu_setup(XICSState *icp, PowerPCCPU *cpu)
+{
+ CPUState *cs = CPU(cpu);
+ CPUPPCState *env = &cpu->env;
+ ICPState *ss = &icp->ss[cs->cpu_index];
+ XICSStateClass *info = XICS_COMMON_GET_CLASS(icp);
+
+ assert(cs->cpu_index < icp->nr_servers);
+
+ if (info->cpu_setup) {
+ info->cpu_setup(icp, cpu);
+ }
+
+ switch (PPC_INPUT(env)) {
+ case PPC_FLAGS_INPUT_POWER7:
+ ss->output = env->irq_inputs[POWER7_INPUT_INT];
+ break;
+
+ case PPC_FLAGS_INPUT_970:
+ ss->output = env->irq_inputs[PPC970_INPUT_INT];
+ break;
+
+ default:
+ error_report("XICS interrupt controller does not support this CPU "
+ "bus model");
+ abort();
+ }
+}
+
+/*
+ * XICS Common class - parent for emulated XICS and KVM-XICS
+ */
+static void xics_common_reset(DeviceState *d)
+{
+ XICSState *icp = XICS_COMMON(d);
+ int i;
+
+ for (i = 0; i < icp->nr_servers; i++) {
+ device_reset(DEVICE(&icp->ss[i]));
+ }
+
+ device_reset(DEVICE(icp->ics));
+}
+
+static void xics_prop_get_nr_irqs(Object *obj, Visitor *v,
+ void *opaque, const char *name, Error **errp)
+{
+ XICSState *icp = XICS_COMMON(obj);
+ int64_t value = icp->nr_irqs;
+
+ visit_type_int(v, &value, name, errp);
+}
+
+static void xics_prop_set_nr_irqs(Object *obj, Visitor *v,
+ void *opaque, const char *name, Error **errp)
+{
+ XICSState *icp = XICS_COMMON(obj);
+ XICSStateClass *info = XICS_COMMON_GET_CLASS(icp);
+ Error *error = NULL;
+ int64_t value;
+
+ visit_type_int(v, &value, name, &error);
+ if (error) {
+ error_propagate(errp, error);
+ return;
+ }
+ if (icp->nr_irqs) {
+ error_setg(errp, "Number of interrupts is already set to %u",
+ icp->nr_irqs);
+ return;
+ }
+
+ assert(info->set_nr_irqs);
+ assert(icp->ics);
+ info->set_nr_irqs(icp, value, errp);
+}
+
+static void xics_prop_get_nr_servers(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ XICSState *icp = XICS_COMMON(obj);
+ int64_t value = icp->nr_servers;
+
+ visit_type_int(v, &value, name, errp);
+}
+
+static void xics_prop_set_nr_servers(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ XICSState *icp = XICS_COMMON(obj);
+ XICSStateClass *info = XICS_COMMON_GET_CLASS(icp);
+ Error *error = NULL;
+ int64_t value;
+
+ visit_type_int(v, &value, name, &error);
+ if (error) {
+ error_propagate(errp, error);
+ return;
+ }
+ if (icp->nr_servers) {
+ error_setg(errp, "Number of servers is already set to %u",
+ icp->nr_servers);
+ return;
+ }
+
+ assert(info->set_nr_servers);
+ info->set_nr_servers(icp, value, errp);
+}
+
+static void xics_common_initfn(Object *obj)
+{
+ object_property_add(obj, "nr_irqs", "int",
+ xics_prop_get_nr_irqs, xics_prop_set_nr_irqs,
+ NULL, NULL, NULL);
+ object_property_add(obj, "nr_servers", "int",
+ xics_prop_get_nr_servers, xics_prop_set_nr_servers,
+ NULL, NULL, NULL);
+}
+
+static void xics_common_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->reset = xics_common_reset;
+}
+
+static const TypeInfo xics_common_info = {
+ .name = TYPE_XICS_COMMON,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XICSState),
+ .class_size = sizeof(XICSStateClass),
+ .instance_init = xics_common_initfn,
+ .class_init = xics_common_class_init,
+};
+
+/*
+ * ICP: Presentation layer
+ */
+
+#define XISR_MASK 0x00ffffff
+#define CPPR_MASK 0xff000000
+
+#define XISR(ss) (((ss)->xirr) & XISR_MASK)
+#define CPPR(ss) (((ss)->xirr) >> 24)
+
+static void ics_reject(ICSState *ics, int nr);
+static void ics_resend(ICSState *ics);
+static void ics_eoi(ICSState *ics, int nr);
+
+static void icp_check_ipi(XICSState *icp, int server)
+{
+ ICPState *ss = icp->ss + server;
+
+ if (XISR(ss) && (ss->pending_priority <= ss->mfrr)) {
+ return;
+ }
+
+ trace_xics_icp_check_ipi(server, ss->mfrr);
+
+ if (XISR(ss)) {
+ ics_reject(icp->ics, XISR(ss));
+ }
+
+ ss->xirr = (ss->xirr & ~XISR_MASK) | XICS_IPI;
+ ss->pending_priority = ss->mfrr;
+ qemu_irq_raise(ss->output);
+}
+
+static void icp_resend(XICSState *icp, int server)
+{
+ ICPState *ss = icp->ss + server;
+
+ if (ss->mfrr < CPPR(ss)) {
+ icp_check_ipi(icp, server);
+ }
+ ics_resend(icp->ics);
+}
+
+static void icp_set_cppr(XICSState *icp, int server, uint8_t cppr)
+{
+ ICPState *ss = icp->ss + server;
+ uint8_t old_cppr;
+ uint32_t old_xisr;
+
+ old_cppr = CPPR(ss);
+ ss->xirr = (ss->xirr & ~CPPR_MASK) | (cppr << 24);
+
+ if (cppr < old_cppr) {
+ if (XISR(ss) && (cppr <= ss->pending_priority)) {
+ old_xisr = XISR(ss);
+ ss->xirr &= ~XISR_MASK; /* Clear XISR */
+ ss->pending_priority = 0xff;
+ qemu_irq_lower(ss->output);
+ ics_reject(icp->ics, old_xisr);
+ }
+ } else {
+ if (!XISR(ss)) {
+ icp_resend(icp, server);
+ }
+ }
+}
+
+static void icp_set_mfrr(XICSState *icp, int server, uint8_t mfrr)
+{
+ ICPState *ss = icp->ss + server;
+
+ ss->mfrr = mfrr;
+ if (mfrr < CPPR(ss)) {
+ icp_check_ipi(icp, server);
+ }
+}
+
+static uint32_t icp_accept(ICPState *ss)
+{
+ uint32_t xirr = ss->xirr;
+
+ qemu_irq_lower(ss->output);
+ ss->xirr = ss->pending_priority << 24;
+ ss->pending_priority = 0xff;
+
+ trace_xics_icp_accept(xirr, ss->xirr);
+
+ return xirr;
+}
+
+static void icp_eoi(XICSState *icp, int server, uint32_t xirr)
+{
+ ICPState *ss = icp->ss + server;
+
+ /* Send EOI -> ICS */
+ ss->xirr = (ss->xirr & ~CPPR_MASK) | (xirr & CPPR_MASK);
+ trace_xics_icp_eoi(server, xirr, ss->xirr);
+ ics_eoi(icp->ics, xirr & XISR_MASK);
+ if (!XISR(ss)) {
+ icp_resend(icp, server);
+ }
+}
+
+static void icp_irq(XICSState *icp, int server, int nr, uint8_t priority)
+{
+ ICPState *ss = icp->ss + server;
+
+ trace_xics_icp_irq(server, nr, priority);
+
+ if ((priority >= CPPR(ss))
+ || (XISR(ss) && (ss->pending_priority <= priority))) {
+ ics_reject(icp->ics, nr);
+ } else {
+ if (XISR(ss)) {
+ ics_reject(icp->ics, XISR(ss));
+ }
+ ss->xirr = (ss->xirr & ~XISR_MASK) | (nr & XISR_MASK);
+ ss->pending_priority = priority;
+ trace_xics_icp_raise(ss->xirr, ss->pending_priority);
+ qemu_irq_raise(ss->output);
+ }
+}
+
+static void icp_dispatch_pre_save(void *opaque)
+{
+ ICPState *ss = opaque;
+ ICPStateClass *info = ICP_GET_CLASS(ss);
+
+ if (info->pre_save) {
+ info->pre_save(ss);
+ }
+}
+
+static int icp_dispatch_post_load(void *opaque, int version_id)
+{
+ ICPState *ss = opaque;
+ ICPStateClass *info = ICP_GET_CLASS(ss);
+
+ if (info->post_load) {
+ return info->post_load(ss, version_id);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_icp_server = {
+ .name = "icp/server",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = icp_dispatch_pre_save,
+ .post_load = icp_dispatch_post_load,
+ .fields = (VMStateField[]) {
+ /* Sanity check */
+ VMSTATE_UINT32(xirr, ICPState),
+ VMSTATE_UINT8(pending_priority, ICPState),
+ VMSTATE_UINT8(mfrr, ICPState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void icp_reset(DeviceState *dev)
+{
+ ICPState *icp = ICP(dev);
+
+ icp->xirr = 0;
+ icp->pending_priority = 0xff;
+ icp->mfrr = 0xff;
+
+ /* Make all outputs are deasserted */
+ qemu_set_irq(icp->output, 0);
+}
+
+static void icp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = icp_reset;
+ dc->vmsd = &vmstate_icp_server;
+}
+
+static const TypeInfo icp_info = {
+ .name = TYPE_ICP,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(ICPState),
+ .class_init = icp_class_init,
+ .class_size = sizeof(ICPStateClass),
+};
+
+/*
+ * ICS: Source layer
+ */
+static int ics_valid_irq(ICSState *ics, uint32_t nr)
+{
+ return (nr >= ics->offset)
+ && (nr < (ics->offset + ics->nr_irqs));
+}
+
+static void resend_msi(ICSState *ics, int srcno)
+{
+ ICSIRQState *irq = ics->irqs + srcno;
+
+ /* FIXME: filter by server#? */
+ if (irq->status & XICS_STATUS_REJECTED) {
+ irq->status &= ~XICS_STATUS_REJECTED;
+ if (irq->priority != 0xff) {
+ icp_irq(ics->icp, irq->server, srcno + ics->offset,
+ irq->priority);
+ }
+ }
+}
+
+static void resend_lsi(ICSState *ics, int srcno)
+{
+ ICSIRQState *irq = ics->irqs + srcno;
+
+ if ((irq->priority != 0xff)
+ && (irq->status & XICS_STATUS_ASSERTED)
+ && !(irq->status & XICS_STATUS_SENT)) {
+ irq->status |= XICS_STATUS_SENT;
+ icp_irq(ics->icp, irq->server, srcno + ics->offset, irq->priority);
+ }
+}
+
+static void set_irq_msi(ICSState *ics, int srcno, int val)
+{
+ ICSIRQState *irq = ics->irqs + srcno;
+
+ trace_xics_set_irq_msi(srcno, srcno + ics->offset);
+
+ if (val) {
+ if (irq->priority == 0xff) {
+ irq->status |= XICS_STATUS_MASKED_PENDING;
+ trace_xics_masked_pending();
+ } else {
+ icp_irq(ics->icp, irq->server, srcno + ics->offset, irq->priority);
+ }
+ }
+}
+
+static void set_irq_lsi(ICSState *ics, int srcno, int val)
+{
+ ICSIRQState *irq = ics->irqs + srcno;
+
+ trace_xics_set_irq_lsi(srcno, srcno + ics->offset);
+ if (val) {
+ irq->status |= XICS_STATUS_ASSERTED;
+ } else {
+ irq->status &= ~XICS_STATUS_ASSERTED;
+ }
+ resend_lsi(ics, srcno);
+}
+
+static void ics_set_irq(void *opaque, int srcno, int val)
+{
+ ICSState *ics = (ICSState *)opaque;
+
+ if (ics->irqs[srcno].flags & XICS_FLAGS_IRQ_LSI) {
+ set_irq_lsi(ics, srcno, val);
+ } else {
+ set_irq_msi(ics, srcno, val);
+ }
+}
+
+static void write_xive_msi(ICSState *ics, int srcno)
+{
+ ICSIRQState *irq = ics->irqs + srcno;
+
+ if (!(irq->status & XICS_STATUS_MASKED_PENDING)
+ || (irq->priority == 0xff)) {
+ return;
+ }
+
+ irq->status &= ~XICS_STATUS_MASKED_PENDING;
+ icp_irq(ics->icp, irq->server, srcno + ics->offset, irq->priority);
+}
+
+static void write_xive_lsi(ICSState *ics, int srcno)
+{
+ resend_lsi(ics, srcno);
+}
+
+static void ics_write_xive(ICSState *ics, int nr, int server,
+ uint8_t priority, uint8_t saved_priority)
+{
+ int srcno = nr - ics->offset;
+ ICSIRQState *irq = ics->irqs + srcno;
+
+ irq->server = server;
+ irq->priority = priority;
+ irq->saved_priority = saved_priority;
+
+ trace_xics_ics_write_xive(nr, srcno, server, priority);
+
+ if (ics->irqs[srcno].flags & XICS_FLAGS_IRQ_LSI) {
+ write_xive_lsi(ics, srcno);
+ } else {
+ write_xive_msi(ics, srcno);
+ }
+}
+
+static void ics_reject(ICSState *ics, int nr)
+{
+ ICSIRQState *irq = ics->irqs + nr - ics->offset;
+
+ trace_xics_ics_reject(nr, nr - ics->offset);
+ irq->status |= XICS_STATUS_REJECTED; /* Irrelevant but harmless for LSI */
+ irq->status &= ~XICS_STATUS_SENT; /* Irrelevant but harmless for MSI */
+}
+
+static void ics_resend(ICSState *ics)
+{
+ int i;
+
+ for (i = 0; i < ics->nr_irqs; i++) {
+ /* FIXME: filter by server#? */
+ if (ics->irqs[i].flags & XICS_FLAGS_IRQ_LSI) {
+ resend_lsi(ics, i);
+ } else {
+ resend_msi(ics, i);
+ }
+ }
+}
+
+static void ics_eoi(ICSState *ics, int nr)
+{
+ int srcno = nr - ics->offset;
+ ICSIRQState *irq = ics->irqs + srcno;
+
+ trace_xics_ics_eoi(nr);
+
+ if (ics->irqs[srcno].flags & XICS_FLAGS_IRQ_LSI) {
+ irq->status &= ~XICS_STATUS_SENT;
+ }
+}
+
+static void ics_reset(DeviceState *dev)
+{
+ ICSState *ics = ICS(dev);
+ int i;
+ uint8_t flags[ics->nr_irqs];
+
+ for (i = 0; i < ics->nr_irqs; i++) {
+ flags[i] = ics->irqs[i].flags;
+ }
+
+ memset(ics->irqs, 0, sizeof(ICSIRQState) * ics->nr_irqs);
+
+ for (i = 0; i < ics->nr_irqs; i++) {
+ ics->irqs[i].priority = 0xff;
+ ics->irqs[i].saved_priority = 0xff;
+ ics->irqs[i].flags = flags[i];
+ }
+}
+
+static int ics_post_load(ICSState *ics, int version_id)
+{
+ int i;
+
+ for (i = 0; i < ics->icp->nr_servers; i++) {
+ icp_resend(ics->icp, i);
+ }
+
+ return 0;
+}
+
+static void ics_dispatch_pre_save(void *opaque)
+{
+ ICSState *ics = opaque;
+ ICSStateClass *info = ICS_GET_CLASS(ics);
+
+ if (info->pre_save) {
+ info->pre_save(ics);
+ }
+}
+
+static int ics_dispatch_post_load(void *opaque, int version_id)
+{
+ ICSState *ics = opaque;
+ ICSStateClass *info = ICS_GET_CLASS(ics);
+
+ if (info->post_load) {
+ return info->post_load(ics, version_id);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_ics_irq = {
+ .name = "ics/irq",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(server, ICSIRQState),
+ VMSTATE_UINT8(priority, ICSIRQState),
+ VMSTATE_UINT8(saved_priority, ICSIRQState),
+ VMSTATE_UINT8(status, ICSIRQState),
+ VMSTATE_UINT8(flags, ICSIRQState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const VMStateDescription vmstate_ics = {
+ .name = "ics",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = ics_dispatch_pre_save,
+ .post_load = ics_dispatch_post_load,
+ .fields = (VMStateField[]) {
+ /* Sanity check */
+ VMSTATE_UINT32_EQUAL(nr_irqs, ICSState),
+
+ VMSTATE_STRUCT_VARRAY_POINTER_UINT32(irqs, ICSState, nr_irqs,
+ vmstate_ics_irq, ICSIRQState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void ics_initfn(Object *obj)
+{
+ ICSState *ics = ICS(obj);
+
+ ics->offset = XICS_IRQ_BASE;
+}
+
+static void ics_realize(DeviceState *dev, Error **errp)
+{
+ ICSState *ics = ICS(dev);
+
+ if (!ics->nr_irqs) {
+ error_setg(errp, "Number of interrupts needs to be greater 0");
+ return;
+ }
+ ics->irqs = g_malloc0(ics->nr_irqs * sizeof(ICSIRQState));
+ ics->qirqs = qemu_allocate_irqs(ics_set_irq, ics, ics->nr_irqs);
+}
+
+static void ics_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ICSStateClass *isc = ICS_CLASS(klass);
+
+ dc->realize = ics_realize;
+ dc->vmsd = &vmstate_ics;
+ dc->reset = ics_reset;
+ isc->post_load = ics_post_load;
+}
+
+static const TypeInfo ics_info = {
+ .name = TYPE_ICS,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(ICSState),
+ .class_init = ics_class_init,
+ .class_size = sizeof(ICSStateClass),
+ .instance_init = ics_initfn,
+};
+
+/*
+ * Exported functions
+ */
+static int xics_find_source(XICSState *icp, int irq)
+{
+ int sources = 1;
+ int src;
+
+ /* FIXME: implement multiple sources */
+ for (src = 0; src < sources; ++src) {
+ ICSState *ics = &icp->ics[src];
+ if (ics_valid_irq(ics, irq)) {
+ return src;
+ }
+ }
+
+ return -1;
+}
+
+qemu_irq xics_get_qirq(XICSState *icp, int irq)
+{
+ int src = xics_find_source(icp, irq);
+
+ if (src >= 0) {
+ ICSState *ics = &icp->ics[src];
+ return ics->qirqs[irq - ics->offset];
+ }
+
+ return NULL;
+}
+
+static void ics_set_irq_type(ICSState *ics, int srcno, bool lsi)
+{
+ assert(!(ics->irqs[srcno].flags & XICS_FLAGS_IRQ_MASK));
+
+ ics->irqs[srcno].flags |=
+ lsi ? XICS_FLAGS_IRQ_LSI : XICS_FLAGS_IRQ_MSI;
+}
+
+void xics_set_irq_type(XICSState *icp, int irq, bool lsi)
+{
+ int src = xics_find_source(icp, irq);
+ ICSState *ics;
+
+ assert(src >= 0);
+
+ ics = &icp->ics[src];
+ ics_set_irq_type(ics, irq - ics->offset, lsi);
+}
+
+#define ICS_IRQ_FREE(ics, srcno) \
+ (!((ics)->irqs[(srcno)].flags & (XICS_FLAGS_IRQ_MASK)))
+
+static int ics_find_free_block(ICSState *ics, int num, int alignnum)
+{
+ int first, i;
+
+ for (first = 0; first < ics->nr_irqs; first += alignnum) {
+ if (num > (ics->nr_irqs - first)) {
+ return -1;
+ }
+ for (i = first; i < first + num; ++i) {
+ if (!ICS_IRQ_FREE(ics, i)) {
+ break;
+ }
+ }
+ if (i == (first + num)) {
+ return first;
+ }
+ }
+
+ return -1;
+}
+
+int xics_alloc(XICSState *icp, int src, int irq_hint, bool lsi)
+{
+ ICSState *ics = &icp->ics[src];
+ int irq;
+
+ if (irq_hint) {
+ assert(src == xics_find_source(icp, irq_hint));
+ if (!ICS_IRQ_FREE(ics, irq_hint - ics->offset)) {
+ trace_xics_alloc_failed_hint(src, irq_hint);
+ return -1;
+ }
+ irq = irq_hint;
+ } else {
+ irq = ics_find_free_block(ics, 1, 1);
+ if (irq < 0) {
+ trace_xics_alloc_failed_no_left(src);
+ return -1;
+ }
+ irq += ics->offset;
+ }
+
+ ics_set_irq_type(ics, irq - ics->offset, lsi);
+ trace_xics_alloc(src, irq);
+
+ return irq;
+}
+
+/*
+ * Allocate block of consequtive IRQs, returns a number of the first.
+ * If align==true, aligns the first IRQ number to num.
+ */
+int xics_alloc_block(XICSState *icp, int src, int num, bool lsi, bool align)
+{
+ int i, first = -1;
+ ICSState *ics = &icp->ics[src];
+
+ assert(src == 0);
+ /*
+ * MSIMesage::data is used for storing VIRQ so
+ * it has to be aligned to num to support multiple
+ * MSI vectors. MSI-X is not affected by this.
+ * The hint is used for the first IRQ, the rest should
+ * be allocated continuously.
+ */
+ if (align) {
+ assert((num == 1) || (num == 2) || (num == 4) ||
+ (num == 8) || (num == 16) || (num == 32));
+ first = ics_find_free_block(ics, num, num);
+ } else {
+ first = ics_find_free_block(ics, num, 1);
+ }
+
+ if (first >= 0) {
+ for (i = first; i < first + num; ++i) {
+ ics_set_irq_type(ics, i, lsi);
+ }
+ }
+ first += ics->offset;
+
+ trace_xics_alloc_block(src, first, num, lsi, align);
+
+ return first;
+}
+
+static void ics_free(ICSState *ics, int srcno, int num)
+{
+ int i;
+
+ for (i = srcno; i < srcno + num; ++i) {
+ if (ICS_IRQ_FREE(ics, i)) {
+ trace_xics_ics_free_warn(ics - ics->icp->ics, i + ics->offset);
+ }
+ memset(&ics->irqs[i], 0, sizeof(ICSIRQState));
+ }
+}
+
+void xics_free(XICSState *icp, int irq, int num)
+{
+ int src = xics_find_source(icp, irq);
+
+ if (src >= 0) {
+ ICSState *ics = &icp->ics[src];
+
+ /* FIXME: implement multiple sources */
+ assert(src == 0);
+
+ trace_xics_ics_free(ics - icp->ics, irq, num);
+ ics_free(ics, irq - ics->offset, num);
+ }
+}
+
+/*
+ * Guest interfaces
+ */
+
+static target_ulong h_cppr(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ target_ulong cppr = args[0];
+
+ icp_set_cppr(spapr->icp, cs->cpu_index, cppr);
+ return H_SUCCESS;
+}
+
+static target_ulong h_ipi(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong server = get_cpu_index_by_dt_id(args[0]);
+ target_ulong mfrr = args[1];
+
+ if (server >= spapr->icp->nr_servers) {
+ return H_PARAMETER;
+ }
+
+ icp_set_mfrr(spapr->icp, server, mfrr);
+ return H_SUCCESS;
+}
+
+static target_ulong h_xirr(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ uint32_t xirr = icp_accept(spapr->icp->ss + cs->cpu_index);
+
+ args[0] = xirr;
+ return H_SUCCESS;
+}
+
+static target_ulong h_xirr_x(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ ICPState *ss = &spapr->icp->ss[cs->cpu_index];
+ uint32_t xirr = icp_accept(ss);
+
+ args[0] = xirr;
+ args[1] = cpu_get_real_ticks();
+ return H_SUCCESS;
+}
+
+static target_ulong h_eoi(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ target_ulong xirr = args[0];
+
+ icp_eoi(spapr->icp, cs->cpu_index, xirr);
+ return H_SUCCESS;
+}
+
+static target_ulong h_ipoll(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ ICPState *ss = &spapr->icp->ss[cs->cpu_index];
+
+ args[0] = ss->xirr;
+ args[1] = ss->mfrr;
+
+ return H_SUCCESS;
+}
+
+static void rtas_set_xive(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ ICSState *ics = spapr->icp->ics;
+ uint32_t nr, server, priority;
+
+ if ((nargs != 3) || (nret != 1)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ nr = rtas_ld(args, 0);
+ server = get_cpu_index_by_dt_id(rtas_ld(args, 1));
+ priority = rtas_ld(args, 2);
+
+ if (!ics_valid_irq(ics, nr) || (server >= ics->icp->nr_servers)
+ || (priority > 0xff)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ ics_write_xive(ics, nr, server, priority, priority);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+static void rtas_get_xive(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ ICSState *ics = spapr->icp->ics;
+ uint32_t nr;
+
+ if ((nargs != 1) || (nret != 3)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ nr = rtas_ld(args, 0);
+
+ if (!ics_valid_irq(ics, nr)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, ics->irqs[nr - ics->offset].server);
+ rtas_st(rets, 2, ics->irqs[nr - ics->offset].priority);
+}
+
+static void rtas_int_off(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ ICSState *ics = spapr->icp->ics;
+ uint32_t nr;
+
+ if ((nargs != 1) || (nret != 1)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ nr = rtas_ld(args, 0);
+
+ if (!ics_valid_irq(ics, nr)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ ics_write_xive(ics, nr, ics->irqs[nr - ics->offset].server, 0xff,
+ ics->irqs[nr - ics->offset].priority);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+static void rtas_int_on(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ ICSState *ics = spapr->icp->ics;
+ uint32_t nr;
+
+ if ((nargs != 1) || (nret != 1)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ nr = rtas_ld(args, 0);
+
+ if (!ics_valid_irq(ics, nr)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ ics_write_xive(ics, nr, ics->irqs[nr - ics->offset].server,
+ ics->irqs[nr - ics->offset].saved_priority,
+ ics->irqs[nr - ics->offset].saved_priority);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+/*
+ * XICS
+ */
+
+static void xics_set_nr_irqs(XICSState *icp, uint32_t nr_irqs, Error **errp)
+{
+ icp->nr_irqs = icp->ics->nr_irqs = nr_irqs;
+}
+
+static void xics_set_nr_servers(XICSState *icp, uint32_t nr_servers,
+ Error **errp)
+{
+ int i;
+
+ icp->nr_servers = nr_servers;
+
+ icp->ss = g_malloc0(icp->nr_servers*sizeof(ICPState));
+ for (i = 0; i < icp->nr_servers; i++) {
+ char buffer[32];
+ object_initialize(&icp->ss[i], sizeof(icp->ss[i]), TYPE_ICP);
+ snprintf(buffer, sizeof(buffer), "icp[%d]", i);
+ object_property_add_child(OBJECT(icp), buffer, OBJECT(&icp->ss[i]),
+ errp);
+ }
+}
+
+static void xics_realize(DeviceState *dev, Error **errp)
+{
+ XICSState *icp = XICS(dev);
+ Error *error = NULL;
+ int i;
+
+ if (!icp->nr_servers) {
+ error_setg(errp, "Number of servers needs to be greater 0");
+ return;
+ }
+
+ /* Registration of global state belongs into realize */
+ spapr_rtas_register(RTAS_IBM_SET_XIVE, "ibm,set-xive", rtas_set_xive);
+ spapr_rtas_register(RTAS_IBM_GET_XIVE, "ibm,get-xive", rtas_get_xive);
+ spapr_rtas_register(RTAS_IBM_INT_OFF, "ibm,int-off", rtas_int_off);
+ spapr_rtas_register(RTAS_IBM_INT_ON, "ibm,int-on", rtas_int_on);
+
+ spapr_register_hypercall(H_CPPR, h_cppr);
+ spapr_register_hypercall(H_IPI, h_ipi);
+ spapr_register_hypercall(H_XIRR, h_xirr);
+ spapr_register_hypercall(H_XIRR_X, h_xirr_x);
+ spapr_register_hypercall(H_EOI, h_eoi);
+ spapr_register_hypercall(H_IPOLL, h_ipoll);
+
+ object_property_set_bool(OBJECT(icp->ics), true, "realized", &error);
+ if (error) {
+ error_propagate(errp, error);
+ return;
+ }
+
+ for (i = 0; i < icp->nr_servers; i++) {
+ object_property_set_bool(OBJECT(&icp->ss[i]), true, "realized", &error);
+ if (error) {
+ error_propagate(errp, error);
+ return;
+ }
+ }
+}
+
+static void xics_initfn(Object *obj)
+{
+ XICSState *xics = XICS(obj);
+
+ xics->ics = ICS(object_new(TYPE_ICS));
+ object_property_add_child(obj, "ics", OBJECT(xics->ics), NULL);
+ xics->ics->icp = xics;
+}
+
+static void xics_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ XICSStateClass *xsc = XICS_CLASS(oc);
+
+ dc->realize = xics_realize;
+ xsc->set_nr_irqs = xics_set_nr_irqs;
+ xsc->set_nr_servers = xics_set_nr_servers;
+}
+
+static const TypeInfo xics_info = {
+ .name = TYPE_XICS,
+ .parent = TYPE_XICS_COMMON,
+ .instance_size = sizeof(XICSState),
+ .class_size = sizeof(XICSStateClass),
+ .class_init = xics_class_init,
+ .instance_init = xics_initfn,
+};
+
+static void xics_register_types(void)
+{
+ type_register_static(&xics_common_info);
+ type_register_static(&xics_info);
+ type_register_static(&ics_info);
+ type_register_static(&icp_info);
+}
+
+type_init(xics_register_types)
diff --git a/hw/intc/xics_kvm.c b/hw/intc/xics_kvm.c
new file mode 100644
index 00000000..d58729cf
--- /dev/null
+++ b/hw/intc/xics_kvm.c
@@ -0,0 +1,508 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * PAPR Virtualized Interrupt System, aka ICS/ICP aka xics, in-kernel emulation
+ *
+ * Copyright (c) 2013 David Gibson, IBM Corporation.
+ *
+ * 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.
+ *
+ */
+
+#include "hw/hw.h"
+#include "trace.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/xics.h"
+#include "kvm_ppc.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+
+#include <sys/ioctl.h>
+
+typedef struct KVMXICSState {
+ XICSState parent_obj;
+
+ int kernel_xics_fd;
+} KVMXICSState;
+
+/*
+ * ICP-KVM
+ */
+static void icp_get_kvm_state(ICPState *ss)
+{
+ uint64_t state;
+ struct kvm_one_reg reg = {
+ .id = KVM_REG_PPC_ICP_STATE,
+ .addr = (uintptr_t)&state,
+ };
+ int ret;
+
+ /* ICP for this CPU thread is not in use, exiting */
+ if (!ss->cs) {
+ return;
+ }
+
+ ret = kvm_vcpu_ioctl(ss->cs, KVM_GET_ONE_REG, &reg);
+ if (ret != 0) {
+ error_report("Unable to retrieve KVM interrupt controller state"
+ " for CPU %ld: %s", kvm_arch_vcpu_id(ss->cs), strerror(errno));
+ exit(1);
+ }
+
+ ss->xirr = state >> KVM_REG_PPC_ICP_XISR_SHIFT;
+ ss->mfrr = (state >> KVM_REG_PPC_ICP_MFRR_SHIFT)
+ & KVM_REG_PPC_ICP_MFRR_MASK;
+ ss->pending_priority = (state >> KVM_REG_PPC_ICP_PPRI_SHIFT)
+ & KVM_REG_PPC_ICP_PPRI_MASK;
+}
+
+static int icp_set_kvm_state(ICPState *ss, int version_id)
+{
+ uint64_t state;
+ struct kvm_one_reg reg = {
+ .id = KVM_REG_PPC_ICP_STATE,
+ .addr = (uintptr_t)&state,
+ };
+ int ret;
+
+ /* ICP for this CPU thread is not in use, exiting */
+ if (!ss->cs) {
+ return 0;
+ }
+
+ state = ((uint64_t)ss->xirr << KVM_REG_PPC_ICP_XISR_SHIFT)
+ | ((uint64_t)ss->mfrr << KVM_REG_PPC_ICP_MFRR_SHIFT)
+ | ((uint64_t)ss->pending_priority << KVM_REG_PPC_ICP_PPRI_SHIFT);
+
+ ret = kvm_vcpu_ioctl(ss->cs, KVM_SET_ONE_REG, &reg);
+ if (ret != 0) {
+ error_report("Unable to restore KVM interrupt controller state (0x%"
+ PRIx64 ") for CPU %ld: %s", state, kvm_arch_vcpu_id(ss->cs),
+ strerror(errno));
+ return ret;
+ }
+
+ return 0;
+}
+
+static void icp_kvm_reset(DeviceState *dev)
+{
+ ICPState *icp = ICP(dev);
+
+ icp->xirr = 0;
+ icp->pending_priority = 0xff;
+ icp->mfrr = 0xff;
+
+ /* Make all outputs are deasserted */
+ qemu_set_irq(icp->output, 0);
+
+ icp_set_kvm_state(icp, 1);
+}
+
+static void icp_kvm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ICPStateClass *icpc = ICP_CLASS(klass);
+
+ dc->reset = icp_kvm_reset;
+ icpc->pre_save = icp_get_kvm_state;
+ icpc->post_load = icp_set_kvm_state;
+}
+
+static const TypeInfo icp_kvm_info = {
+ .name = TYPE_KVM_ICP,
+ .parent = TYPE_ICP,
+ .instance_size = sizeof(ICPState),
+ .class_init = icp_kvm_class_init,
+ .class_size = sizeof(ICPStateClass),
+};
+
+/*
+ * ICS-KVM
+ */
+static void ics_get_kvm_state(ICSState *ics)
+{
+ KVMXICSState *icpkvm = KVM_XICS(ics->icp);
+ uint64_t state;
+ struct kvm_device_attr attr = {
+ .flags = 0,
+ .group = KVM_DEV_XICS_GRP_SOURCES,
+ .addr = (uint64_t)(uintptr_t)&state,
+ };
+ int i;
+
+ for (i = 0; i < ics->nr_irqs; i++) {
+ ICSIRQState *irq = &ics->irqs[i];
+ int ret;
+
+ attr.attr = i + ics->offset;
+
+ ret = ioctl(icpkvm->kernel_xics_fd, KVM_GET_DEVICE_ATTR, &attr);
+ if (ret != 0) {
+ error_report("Unable to retrieve KVM interrupt controller state"
+ " for IRQ %d: %s", i + ics->offset, strerror(errno));
+ exit(1);
+ }
+
+ irq->server = state & KVM_XICS_DESTINATION_MASK;
+ irq->saved_priority = (state >> KVM_XICS_PRIORITY_SHIFT)
+ & KVM_XICS_PRIORITY_MASK;
+ /*
+ * To be consistent with the software emulation in xics.c, we
+ * split out the masked state + priority that we get from the
+ * kernel into 'current priority' (0xff if masked) and
+ * 'saved priority' (if masked, this is the priority the
+ * interrupt had before it was masked). Masking and unmasking
+ * are done with the ibm,int-off and ibm,int-on RTAS calls.
+ */
+ if (state & KVM_XICS_MASKED) {
+ irq->priority = 0xff;
+ } else {
+ irq->priority = irq->saved_priority;
+ }
+
+ if (state & KVM_XICS_PENDING) {
+ if (state & KVM_XICS_LEVEL_SENSITIVE) {
+ irq->status |= XICS_STATUS_ASSERTED;
+ } else {
+ /*
+ * A pending edge-triggered interrupt (or MSI)
+ * must have been rejected previously when we
+ * first detected it and tried to deliver it,
+ * so mark it as pending and previously rejected
+ * for consistency with how xics.c works.
+ */
+ irq->status |= XICS_STATUS_MASKED_PENDING
+ | XICS_STATUS_REJECTED;
+ }
+ }
+ }
+}
+
+static int ics_set_kvm_state(ICSState *ics, int version_id)
+{
+ KVMXICSState *icpkvm = KVM_XICS(ics->icp);
+ uint64_t state;
+ struct kvm_device_attr attr = {
+ .flags = 0,
+ .group = KVM_DEV_XICS_GRP_SOURCES,
+ .addr = (uint64_t)(uintptr_t)&state,
+ };
+ int i;
+
+ for (i = 0; i < ics->nr_irqs; i++) {
+ ICSIRQState *irq = &ics->irqs[i];
+ int ret;
+
+ attr.attr = i + ics->offset;
+
+ state = irq->server;
+ state |= (uint64_t)(irq->saved_priority & KVM_XICS_PRIORITY_MASK)
+ << KVM_XICS_PRIORITY_SHIFT;
+ if (irq->priority != irq->saved_priority) {
+ assert(irq->priority == 0xff);
+ state |= KVM_XICS_MASKED;
+ }
+
+ if (ics->irqs[i].flags & XICS_FLAGS_IRQ_LSI) {
+ state |= KVM_XICS_LEVEL_SENSITIVE;
+ if (irq->status & XICS_STATUS_ASSERTED) {
+ state |= KVM_XICS_PENDING;
+ }
+ } else {
+ if (irq->status & XICS_STATUS_MASKED_PENDING) {
+ state |= KVM_XICS_PENDING;
+ }
+ }
+
+ ret = ioctl(icpkvm->kernel_xics_fd, KVM_SET_DEVICE_ATTR, &attr);
+ if (ret != 0) {
+ error_report("Unable to restore KVM interrupt controller state"
+ " for IRQs %d: %s", i + ics->offset, strerror(errno));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void ics_kvm_set_irq(void *opaque, int srcno, int val)
+{
+ ICSState *ics = opaque;
+ struct kvm_irq_level args;
+ int rc;
+
+ args.irq = srcno + ics->offset;
+ if (ics->irqs[srcno].flags & XICS_FLAGS_IRQ_MSI) {
+ if (!val) {
+ return;
+ }
+ args.level = KVM_INTERRUPT_SET;
+ } else {
+ args.level = val ? KVM_INTERRUPT_SET_LEVEL : KVM_INTERRUPT_UNSET;
+ }
+ rc = kvm_vm_ioctl(kvm_state, KVM_IRQ_LINE, &args);
+ if (rc < 0) {
+ perror("kvm_irq_line");
+ }
+}
+
+static void ics_kvm_reset(DeviceState *dev)
+{
+ ICSState *ics = ICS(dev);
+ int i;
+ uint8_t flags[ics->nr_irqs];
+
+ for (i = 0; i < ics->nr_irqs; i++) {
+ flags[i] = ics->irqs[i].flags;
+ }
+
+ memset(ics->irqs, 0, sizeof(ICSIRQState) * ics->nr_irqs);
+
+ for (i = 0; i < ics->nr_irqs; i++) {
+ ics->irqs[i].priority = 0xff;
+ ics->irqs[i].saved_priority = 0xff;
+ ics->irqs[i].flags = flags[i];
+ }
+
+ ics_set_kvm_state(ics, 1);
+}
+
+static void ics_kvm_realize(DeviceState *dev, Error **errp)
+{
+ ICSState *ics = ICS(dev);
+
+ if (!ics->nr_irqs) {
+ error_setg(errp, "Number of interrupts needs to be greater 0");
+ return;
+ }
+ ics->irqs = g_malloc0(ics->nr_irqs * sizeof(ICSIRQState));
+ ics->qirqs = qemu_allocate_irqs(ics_kvm_set_irq, ics, ics->nr_irqs);
+}
+
+static void ics_kvm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ICSStateClass *icsc = ICS_CLASS(klass);
+
+ dc->realize = ics_kvm_realize;
+ dc->reset = ics_kvm_reset;
+ icsc->pre_save = ics_get_kvm_state;
+ icsc->post_load = ics_set_kvm_state;
+}
+
+static const TypeInfo ics_kvm_info = {
+ .name = TYPE_KVM_ICS,
+ .parent = TYPE_ICS,
+ .instance_size = sizeof(ICSState),
+ .class_init = ics_kvm_class_init,
+};
+
+/*
+ * XICS-KVM
+ */
+static void xics_kvm_cpu_setup(XICSState *icp, PowerPCCPU *cpu)
+{
+ CPUState *cs;
+ ICPState *ss;
+ KVMXICSState *icpkvm = KVM_XICS(icp);
+
+ cs = CPU(cpu);
+ ss = &icp->ss[cs->cpu_index];
+
+ assert(cs->cpu_index < icp->nr_servers);
+ if (icpkvm->kernel_xics_fd == -1) {
+ abort();
+ }
+
+ /*
+ * If we are reusing a parked vCPU fd corresponding to the CPU
+ * which was hot-removed earlier we don't have to renable
+ * KVM_CAP_IRQ_XICS capability again.
+ */
+ if (ss->cap_irq_xics_enabled) {
+ return;
+ }
+
+ if (icpkvm->kernel_xics_fd != -1) {
+ int ret;
+
+ ss->cs = cs;
+
+ ret = kvm_vcpu_enable_cap(cs, KVM_CAP_IRQ_XICS, 0,
+ icpkvm->kernel_xics_fd, kvm_arch_vcpu_id(cs));
+ if (ret < 0) {
+ error_report("Unable to connect CPU%ld to kernel XICS: %s",
+ kvm_arch_vcpu_id(cs), strerror(errno));
+ exit(1);
+ }
+ ss->cap_irq_xics_enabled = true;
+ }
+}
+
+static void xics_kvm_set_nr_irqs(XICSState *icp, uint32_t nr_irqs, Error **errp)
+{
+ icp->nr_irqs = icp->ics->nr_irqs = nr_irqs;
+}
+
+static void xics_kvm_set_nr_servers(XICSState *icp, uint32_t nr_servers,
+ Error **errp)
+{
+ int i;
+
+ icp->nr_servers = nr_servers;
+
+ icp->ss = g_malloc0(icp->nr_servers*sizeof(ICPState));
+ for (i = 0; i < icp->nr_servers; i++) {
+ char buffer[32];
+ object_initialize(&icp->ss[i], sizeof(icp->ss[i]), TYPE_KVM_ICP);
+ snprintf(buffer, sizeof(buffer), "icp[%d]", i);
+ object_property_add_child(OBJECT(icp), buffer, OBJECT(&icp->ss[i]),
+ errp);
+ }
+}
+
+static void rtas_dummy(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ error_report("pseries: %s must never be called for in-kernel XICS",
+ __func__);
+}
+
+static void xics_kvm_realize(DeviceState *dev, Error **errp)
+{
+ KVMXICSState *icpkvm = KVM_XICS(dev);
+ XICSState *icp = XICS_COMMON(dev);
+ int i, rc;
+ Error *error = NULL;
+ struct kvm_create_device xics_create_device = {
+ .type = KVM_DEV_TYPE_XICS,
+ .flags = 0,
+ };
+
+ if (!kvm_enabled() || !kvm_check_extension(kvm_state, KVM_CAP_IRQ_XICS)) {
+ error_setg(errp,
+ "KVM and IRQ_XICS capability must be present for in-kernel XICS");
+ goto fail;
+ }
+
+ spapr_rtas_register(RTAS_IBM_SET_XIVE, "ibm,set-xive", rtas_dummy);
+ spapr_rtas_register(RTAS_IBM_GET_XIVE, "ibm,get-xive", rtas_dummy);
+ spapr_rtas_register(RTAS_IBM_INT_OFF, "ibm,int-off", rtas_dummy);
+ spapr_rtas_register(RTAS_IBM_INT_ON, "ibm,int-on", rtas_dummy);
+
+ rc = kvmppc_define_rtas_kernel_token(RTAS_IBM_SET_XIVE, "ibm,set-xive");
+ if (rc < 0) {
+ error_setg(errp, "kvmppc_define_rtas_kernel_token: ibm,set-xive");
+ goto fail;
+ }
+
+ rc = kvmppc_define_rtas_kernel_token(RTAS_IBM_GET_XIVE, "ibm,get-xive");
+ if (rc < 0) {
+ error_setg(errp, "kvmppc_define_rtas_kernel_token: ibm,get-xive");
+ goto fail;
+ }
+
+ rc = kvmppc_define_rtas_kernel_token(RTAS_IBM_INT_ON, "ibm,int-on");
+ if (rc < 0) {
+ error_setg(errp, "kvmppc_define_rtas_kernel_token: ibm,int-on");
+ goto fail;
+ }
+
+ rc = kvmppc_define_rtas_kernel_token(RTAS_IBM_INT_OFF, "ibm,int-off");
+ if (rc < 0) {
+ error_setg(errp, "kvmppc_define_rtas_kernel_token: ibm,int-off");
+ goto fail;
+ }
+
+ /* Create the kernel ICP */
+ rc = kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &xics_create_device);
+ if (rc < 0) {
+ error_setg_errno(errp, -rc, "Error on KVM_CREATE_DEVICE for XICS");
+ goto fail;
+ }
+
+ icpkvm->kernel_xics_fd = xics_create_device.fd;
+
+ object_property_set_bool(OBJECT(icp->ics), true, "realized", &error);
+ if (error) {
+ error_propagate(errp, error);
+ goto fail;
+ }
+
+ assert(icp->nr_servers);
+ for (i = 0; i < icp->nr_servers; i++) {
+ object_property_set_bool(OBJECT(&icp->ss[i]), true, "realized", &error);
+ if (error) {
+ error_propagate(errp, error);
+ goto fail;
+ }
+ }
+
+ kvm_kernel_irqchip = true;
+ kvm_msi_via_irqfd_allowed = true;
+ kvm_gsi_direct_mapping = true;
+
+ return;
+
+fail:
+ kvmppc_define_rtas_kernel_token(0, "ibm,set-xive");
+ kvmppc_define_rtas_kernel_token(0, "ibm,get-xive");
+ kvmppc_define_rtas_kernel_token(0, "ibm,int-on");
+ kvmppc_define_rtas_kernel_token(0, "ibm,int-off");
+}
+
+static void xics_kvm_initfn(Object *obj)
+{
+ XICSState *xics = XICS_COMMON(obj);
+
+ xics->ics = ICS(object_new(TYPE_KVM_ICS));
+ object_property_add_child(obj, "ics", OBJECT(xics->ics), NULL);
+ xics->ics->icp = xics;
+}
+
+static void xics_kvm_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ XICSStateClass *xsc = XICS_COMMON_CLASS(oc);
+
+ dc->realize = xics_kvm_realize;
+ xsc->cpu_setup = xics_kvm_cpu_setup;
+ xsc->set_nr_irqs = xics_kvm_set_nr_irqs;
+ xsc->set_nr_servers = xics_kvm_set_nr_servers;
+}
+
+static const TypeInfo xics_kvm_info = {
+ .name = TYPE_KVM_XICS,
+ .parent = TYPE_XICS_COMMON,
+ .instance_size = sizeof(KVMXICSState),
+ .class_init = xics_kvm_class_init,
+ .instance_init = xics_kvm_initfn,
+};
+
+static void xics_kvm_register_types(void)
+{
+ type_register_static(&xics_kvm_info);
+ type_register_static(&ics_kvm_info);
+ type_register_static(&icp_kvm_info);
+}
+
+type_init(xics_kvm_register_types)
diff --git a/hw/intc/xilinx_intc.c b/hw/intc/xilinx_intc.c
new file mode 100644
index 00000000..12804ab7
--- /dev/null
+++ b/hw/intc/xilinx_intc.c
@@ -0,0 +1,201 @@
+/*
+ * QEMU Xilinx OPB Interrupt Controller.
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+
+#define D(x)
+
+#define R_ISR 0
+#define R_IPR 1
+#define R_IER 2
+#define R_IAR 3
+#define R_SIE 4
+#define R_CIE 5
+#define R_IVR 6
+#define R_MER 7
+#define R_MAX 8
+
+#define TYPE_XILINX_INTC "xlnx.xps-intc"
+#define XILINX_INTC(obj) OBJECT_CHECK(struct xlx_pic, (obj), TYPE_XILINX_INTC)
+
+struct xlx_pic
+{
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ qemu_irq parent_irq;
+
+ /* Configuration reg chosen at synthesis-time. QEMU populates
+ the bits at board-setup. */
+ uint32_t c_kind_of_intr;
+
+ /* Runtime control registers. */
+ uint32_t regs[R_MAX];
+ /* state of the interrupt input pins */
+ uint32_t irq_pin_state;
+};
+
+static void update_irq(struct xlx_pic *p)
+{
+ uint32_t i;
+
+ /* level triggered interrupt */
+ if (p->regs[R_MER] & 2) {
+ p->regs[R_ISR] |= p->irq_pin_state & ~p->c_kind_of_intr;
+ }
+
+ /* Update the pending register. */
+ p->regs[R_IPR] = p->regs[R_ISR] & p->regs[R_IER];
+
+ /* Update the vector register. */
+ for (i = 0; i < 32; i++) {
+ if (p->regs[R_IPR] & (1U << i)) {
+ break;
+ }
+ }
+ if (i == 32)
+ i = ~0;
+
+ p->regs[R_IVR] = i;
+ qemu_set_irq(p->parent_irq, (p->regs[R_MER] & 1) && p->regs[R_IPR]);
+}
+
+static uint64_t
+pic_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ struct xlx_pic *p = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr)
+ {
+ default:
+ if (addr < ARRAY_SIZE(p->regs))
+ r = p->regs[addr];
+ break;
+
+ }
+ D(printf("%s %x=%x\n", __func__, addr * 4, r));
+ return r;
+}
+
+static void
+pic_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ struct xlx_pic *p = opaque;
+ uint32_t value = val64;
+
+ addr >>= 2;
+ D(qemu_log("%s addr=%x val=%x\n", __func__, addr * 4, value));
+ switch (addr)
+ {
+ case R_IAR:
+ p->regs[R_ISR] &= ~value; /* ACK. */
+ break;
+ case R_SIE:
+ p->regs[R_IER] |= value; /* Atomic set ie. */
+ break;
+ case R_CIE:
+ p->regs[R_IER] &= ~value; /* Atomic clear ie. */
+ break;
+ case R_MER:
+ p->regs[R_MER] = value & 0x3;
+ break;
+ case R_ISR:
+ if ((p->regs[R_MER] & 2)) {
+ break;
+ }
+ /* fallthrough */
+ default:
+ if (addr < ARRAY_SIZE(p->regs))
+ p->regs[addr] = value;
+ break;
+ }
+ update_irq(p);
+}
+
+static const MemoryRegionOps pic_ops = {
+ .read = pic_read,
+ .write = pic_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void irq_handler(void *opaque, int irq, int level)
+{
+ struct xlx_pic *p = opaque;
+
+ /* edge triggered interrupt */
+ if (p->c_kind_of_intr & (1 << irq) && p->regs[R_MER] & 2) {
+ p->regs[R_ISR] |= (level << irq);
+ }
+
+ p->irq_pin_state &= ~(1 << irq);
+ p->irq_pin_state |= level << irq;
+ update_irq(p);
+}
+
+static void xilinx_intc_init(Object *obj)
+{
+ struct xlx_pic *p = XILINX_INTC(obj);
+
+ qdev_init_gpio_in(DEVICE(obj), irq_handler, 32);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &p->parent_irq);
+
+ memory_region_init_io(&p->mmio, obj, &pic_ops, p, "xlnx.xps-intc",
+ R_MAX * 4);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &p->mmio);
+}
+
+static Property xilinx_intc_properties[] = {
+ DEFINE_PROP_UINT32("kind-of-intr", struct xlx_pic, c_kind_of_intr, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_intc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->props = xilinx_intc_properties;
+}
+
+static const TypeInfo xilinx_intc_info = {
+ .name = TYPE_XILINX_INTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct xlx_pic),
+ .instance_init = xilinx_intc_init,
+ .class_init = xilinx_intc_class_init,
+};
+
+static void xilinx_intc_register_types(void)
+{
+ type_register_static(&xilinx_intc_info);
+}
+
+type_init(xilinx_intc_register_types)
diff --git a/hw/ipack/Makefile.objs b/hw/ipack/Makefile.objs
new file mode 100644
index 00000000..8b9bdcb5
--- /dev/null
+++ b/hw/ipack/Makefile.objs
@@ -0,0 +1,2 @@
+common-obj-$(CONFIG_IPACK) += ipack.o
+common-obj-$(CONFIG_IPACK) += tpci200.o
diff --git a/hw/ipack/ipack.c b/hw/ipack/ipack.c
new file mode 100644
index 00000000..59bfe286
--- /dev/null
+++ b/hw/ipack/ipack.c
@@ -0,0 +1,119 @@
+/*
+ * QEMU IndustryPack emulation
+ *
+ * Copyright (C) 2012 Igalia, S.L.
+ * Author: Alberto Garcia <agarcia@igalia.com>
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any
+ * later version.
+ */
+
+#include "hw/ipack/ipack.h"
+
+IPackDevice *ipack_device_find(IPackBus *bus, int32_t slot)
+{
+ BusChild *kid;
+
+ QTAILQ_FOREACH(kid, &BUS(bus)->children, sibling) {
+ DeviceState *qdev = kid->child;
+ IPackDevice *ip = IPACK_DEVICE(qdev);
+ if (ip->slot == slot) {
+ return ip;
+ }
+ }
+ return NULL;
+}
+
+void ipack_bus_new_inplace(IPackBus *bus, size_t bus_size,
+ DeviceState *parent,
+ const char *name, uint8_t n_slots,
+ qemu_irq_handler handler)
+{
+ qbus_create_inplace(bus, bus_size, TYPE_IPACK_BUS, parent, name);
+ bus->n_slots = n_slots;
+ bus->set_irq = handler;
+}
+
+static void ipack_device_realize(DeviceState *dev, Error **errp)
+{
+ IPackDevice *idev = IPACK_DEVICE(dev);
+ IPackBus *bus = IPACK_BUS(qdev_get_parent_bus(dev));
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(dev);
+
+ if (idev->slot < 0) {
+ idev->slot = bus->free_slot;
+ }
+ if (idev->slot >= bus->n_slots) {
+ error_setg(errp, "Only %" PRIu8 " slots available.", bus->n_slots);
+ return;
+ }
+ bus->free_slot = idev->slot + 1;
+
+ idev->irq = qemu_allocate_irqs(bus->set_irq, idev, 2);
+
+ k->realize(dev, errp);
+}
+
+static void ipack_device_unrealize(DeviceState *dev, Error **errp)
+{
+ IPackDevice *idev = IPACK_DEVICE(dev);
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(dev);
+ Error *err = NULL;
+
+ if (k->unrealize) {
+ k->unrealize(dev, &err);
+ error_propagate(errp, err);
+ return;
+ }
+
+ qemu_free_irqs(idev->irq, 2);
+}
+
+static Property ipack_device_props[] = {
+ DEFINE_PROP_INT32("slot", IPackDevice, slot, -1),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void ipack_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_INPUT, k->categories);
+ k->bus_type = TYPE_IPACK_BUS;
+ k->realize = ipack_device_realize;
+ k->unrealize = ipack_device_unrealize;
+ k->props = ipack_device_props;
+}
+
+const VMStateDescription vmstate_ipack_device = {
+ .name = "ipack_device",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(slot, IPackDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const TypeInfo ipack_device_info = {
+ .name = TYPE_IPACK_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(IPackDevice),
+ .class_size = sizeof(IPackDeviceClass),
+ .class_init = ipack_device_class_init,
+ .abstract = true,
+};
+
+static const TypeInfo ipack_bus_info = {
+ .name = TYPE_IPACK_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(IPackBus),
+};
+
+static void ipack_register_types(void)
+{
+ type_register_static(&ipack_device_info);
+ type_register_static(&ipack_bus_info);
+}
+
+type_init(ipack_register_types)
diff --git a/hw/ipack/tpci200.c b/hw/ipack/tpci200.c
new file mode 100644
index 00000000..1df02ee8
--- /dev/null
+++ b/hw/ipack/tpci200.c
@@ -0,0 +1,656 @@
+/*
+ * QEMU TEWS TPCI200 IndustryPack carrier emulation
+ *
+ * Copyright (C) 2012 Igalia, S.L.
+ * Author: Alberto Garcia <agarcia@igalia.com>
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any
+ * later version.
+ */
+
+#include "hw/ipack/ipack.h"
+#include "hw/pci/pci.h"
+#include "qemu/bitops.h"
+#include <stdio.h>
+
+/* #define DEBUG_TPCI */
+
+#ifdef DEBUG_TPCI
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, "TPCI200: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+#define N_MODULES 4
+
+#define IP_ID_SPACE 2
+#define IP_INT_SPACE 3
+#define IP_IO_SPACE_ADDR_MASK 0x7F
+#define IP_ID_SPACE_ADDR_MASK 0x3F
+#define IP_INT_SPACE_ADDR_MASK 0x3F
+
+#define STATUS_INT(IP, INTNO) BIT((IP) * 2 + (INTNO))
+#define STATUS_TIME(IP) BIT((IP) + 12)
+#define STATUS_ERR_ANY 0xF00
+
+#define CTRL_CLKRATE BIT(0)
+#define CTRL_RECOVER BIT(1)
+#define CTRL_TIME_INT BIT(2)
+#define CTRL_ERR_INT BIT(3)
+#define CTRL_INT_EDGE(INTNO) BIT(4 + (INTNO))
+#define CTRL_INT(INTNO) BIT(6 + (INTNO))
+
+#define REG_REV_ID 0x00
+#define REG_IP_A_CTRL 0x02
+#define REG_IP_B_CTRL 0x04
+#define REG_IP_C_CTRL 0x06
+#define REG_IP_D_CTRL 0x08
+#define REG_RESET 0x0A
+#define REG_STATUS 0x0C
+#define IP_N_FROM_REG(REG) ((REG) / 2 - 1)
+
+typedef struct {
+ PCIDevice dev;
+ IPackBus bus;
+ MemoryRegion mmio;
+ MemoryRegion io;
+ MemoryRegion las0;
+ MemoryRegion las1;
+ MemoryRegion las2;
+ MemoryRegion las3;
+ bool big_endian[3];
+ uint8_t ctrl[N_MODULES];
+ uint16_t status;
+ uint8_t int_set;
+} TPCI200State;
+
+#define TYPE_TPCI200 "tpci200"
+
+#define TPCI200(obj) \
+ OBJECT_CHECK(TPCI200State, (obj), TYPE_TPCI200)
+
+static const uint8_t local_config_regs[] = {
+ 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0xFC, 0xFF, 0x0F, 0x00, 0x00, 0x00,
+ 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x08, 0x01, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x60, 0x41, 0xD4,
+ 0xA2, 0x20, 0x41, 0x14, 0xA2, 0x20, 0x41, 0x14, 0xA2, 0x20, 0x01,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x08, 0x01, 0x02,
+ 0x00, 0x04, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x80, 0x02, 0x41,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x7A, 0x00, 0x52, 0x92, 0x24, 0x02
+};
+
+static void adjust_addr(bool big_endian, hwaddr *addr, unsigned size)
+{
+ /* During 8 bit access in big endian mode,
+ odd and even addresses are swapped */
+ if (big_endian && size == 1) {
+ *addr ^= 1;
+ }
+}
+
+static uint64_t adjust_value(bool big_endian, uint64_t *val, unsigned size)
+{
+ /* Local spaces only support 8/16 bit access,
+ * so there's no need to care for sizes > 2 */
+ if (big_endian && size == 2) {
+ *val = bswap16(*val);
+ }
+ return *val;
+}
+
+static void tpci200_set_irq(void *opaque, int intno, int level)
+{
+ IPackDevice *ip = opaque;
+ IPackBus *bus = IPACK_BUS(qdev_get_parent_bus(DEVICE(ip)));
+ PCIDevice *pcidev = PCI_DEVICE(BUS(bus)->parent);
+ TPCI200State *dev = TPCI200(pcidev);
+ unsigned ip_n = ip->slot;
+ uint16_t prev_status = dev->status;
+
+ assert(ip->slot >= 0 && ip->slot < N_MODULES);
+
+ /* The requested interrupt must be enabled in the IP CONTROL
+ * register */
+ if (!(dev->ctrl[ip_n] & CTRL_INT(intno))) {
+ return;
+ }
+
+ /* Update the interrupt status in the IP STATUS register */
+ if (level) {
+ dev->status |= STATUS_INT(ip_n, intno);
+ } else {
+ dev->status &= ~STATUS_INT(ip_n, intno);
+ }
+
+ /* Return if there are no changes */
+ if (dev->status == prev_status) {
+ return;
+ }
+
+ DPRINTF("IP %u INT%u#: %u\n", ip_n, intno, level);
+
+ /* Check if the interrupt is edge sensitive */
+ if (dev->ctrl[ip_n] & CTRL_INT_EDGE(intno)) {
+ if (level) {
+ pci_set_irq(&dev->dev, !dev->int_set);
+ pci_set_irq(&dev->dev, dev->int_set);
+ }
+ } else {
+ unsigned i, j;
+ uint16_t level_status = dev->status;
+
+ /* Check if there are any level sensitive interrupts set by
+ removing the ones that are edge sensitive from the status
+ register */
+ for (i = 0; i < N_MODULES; i++) {
+ for (j = 0; j < 2; j++) {
+ if (dev->ctrl[i] & CTRL_INT_EDGE(j)) {
+ level_status &= ~STATUS_INT(i, j);
+ }
+ }
+ }
+
+ if (level_status && !dev->int_set) {
+ pci_irq_assert(&dev->dev);
+ dev->int_set = 1;
+ } else if (!level_status && dev->int_set) {
+ pci_irq_deassert(&dev->dev);
+ dev->int_set = 0;
+ }
+ }
+}
+
+static uint64_t tpci200_read_cfg(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ uint8_t ret = 0;
+ if (addr < ARRAY_SIZE(local_config_regs)) {
+ ret = local_config_regs[addr];
+ }
+ /* Endianness is stored in the first bit of these registers */
+ if ((addr == 0x2b && s->big_endian[0]) ||
+ (addr == 0x2f && s->big_endian[1]) ||
+ (addr == 0x33 && s->big_endian[2])) {
+ ret |= 1;
+ }
+ DPRINTF("Read from LCR 0x%x: 0x%x\n", (unsigned) addr, (unsigned) ret);
+ return ret;
+}
+
+static void tpci200_write_cfg(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+ /* Endianness is stored in the first bit of these registers */
+ if (addr == 0x2b || addr == 0x2f || addr == 0x33) {
+ unsigned las = (addr - 0x2b) / 4;
+ s->big_endian[las] = val & 1;
+ DPRINTF("LAS%u big endian mode: %u\n", las, (unsigned) val & 1);
+ } else {
+ DPRINTF("Write to LCR 0x%x: 0x%x\n", (unsigned) addr, (unsigned) val);
+ }
+}
+
+static uint64_t tpci200_read_las0(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ uint64_t ret = 0;
+
+ switch (addr) {
+
+ case REG_REV_ID:
+ DPRINTF("Read REVISION ID\n"); /* Current value is 0x00 */
+ break;
+
+ case REG_IP_A_CTRL:
+ case REG_IP_B_CTRL:
+ case REG_IP_C_CTRL:
+ case REG_IP_D_CTRL:
+ {
+ unsigned ip_n = IP_N_FROM_REG(addr);
+ ret = s->ctrl[ip_n];
+ DPRINTF("Read IP %c CONTROL: 0x%x\n", 'A' + ip_n, (unsigned) ret);
+ }
+ break;
+
+ case REG_RESET:
+ DPRINTF("Read RESET\n"); /* Not implemented */
+ break;
+
+ case REG_STATUS:
+ ret = s->status;
+ DPRINTF("Read STATUS: 0x%x\n", (unsigned) ret);
+ break;
+
+ /* Reserved */
+ default:
+ DPRINTF("Unsupported read from LAS0 0x%x\n", (unsigned) addr);
+ break;
+ }
+
+ return adjust_value(s->big_endian[0], &ret, size);
+}
+
+static void tpci200_write_las0(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+
+ adjust_value(s->big_endian[0], &val, size);
+
+ switch (addr) {
+
+ case REG_REV_ID:
+ DPRINTF("Write Revision ID: 0x%x\n", (unsigned) val); /* No effect */
+ break;
+
+ case REG_IP_A_CTRL:
+ case REG_IP_B_CTRL:
+ case REG_IP_C_CTRL:
+ case REG_IP_D_CTRL:
+ {
+ unsigned ip_n = IP_N_FROM_REG(addr);
+ s->ctrl[ip_n] = val;
+ DPRINTF("Write IP %c CONTROL: 0x%x\n", 'A' + ip_n, (unsigned) val);
+ }
+ break;
+
+ case REG_RESET:
+ DPRINTF("Write RESET: 0x%x\n", (unsigned) val); /* Not implemented */
+ break;
+
+ case REG_STATUS:
+ {
+ unsigned i;
+
+ for (i = 0; i < N_MODULES; i++) {
+ IPackDevice *ip = ipack_device_find(&s->bus, i);
+
+ if (ip != NULL) {
+ if (val & STATUS_INT(i, 0)) {
+ DPRINTF("Clear IP %c INT0# status\n", 'A' + i);
+ qemu_irq_lower(ip->irq[0]);
+ }
+ if (val & STATUS_INT(i, 1)) {
+ DPRINTF("Clear IP %c INT1# status\n", 'A' + i);
+ qemu_irq_lower(ip->irq[1]);
+ }
+ }
+
+ if (val & STATUS_TIME(i)) {
+ DPRINTF("Clear IP %c timeout\n", 'A' + i);
+ s->status &= ~STATUS_TIME(i);
+ }
+ }
+
+ if (val & STATUS_ERR_ANY) {
+ DPRINTF("Unexpected write to STATUS register: 0x%x\n",
+ (unsigned) val);
+ }
+ }
+ break;
+
+ /* Reserved */
+ default:
+ DPRINTF("Unsupported write to LAS0 0x%x: 0x%x\n",
+ (unsigned) addr, (unsigned) val);
+ break;
+ }
+}
+
+static uint64_t tpci200_read_las1(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ uint64_t ret = 0;
+ unsigned ip_n, space;
+ uint8_t offset;
+
+ adjust_addr(s->big_endian[1], &addr, size);
+
+ /*
+ * The address is divided into the IP module number (0-4), the IP
+ * address space (I/O, ID, INT) and the offset within that space.
+ */
+ ip_n = addr >> 8;
+ space = (addr >> 6) & 3;
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Read LAS1: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ switch (space) {
+
+ case IP_ID_SPACE:
+ offset = addr & IP_ID_SPACE_ADDR_MASK;
+ if (k->id_read) {
+ ret = k->id_read(ip, offset);
+ }
+ break;
+
+ case IP_INT_SPACE:
+ offset = addr & IP_INT_SPACE_ADDR_MASK;
+
+ /* Read address 0 to ACK IP INT0# and address 2 to ACK IP INT1# */
+ if (offset == 0 || offset == 2) {
+ unsigned intno = offset / 2;
+ bool int_set = s->status & STATUS_INT(ip_n, intno);
+ bool int_edge_sensitive = s->ctrl[ip_n] & CTRL_INT_EDGE(intno);
+ if (int_set && !int_edge_sensitive) {
+ qemu_irq_lower(ip->irq[intno]);
+ }
+ }
+
+ if (k->int_read) {
+ ret = k->int_read(ip, offset);
+ }
+ break;
+
+ default:
+ offset = addr & IP_IO_SPACE_ADDR_MASK;
+ if (k->io_read) {
+ ret = k->io_read(ip, offset);
+ }
+ break;
+ }
+ }
+
+ return adjust_value(s->big_endian[1], &ret, size);
+}
+
+static void tpci200_write_las1(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ unsigned ip_n, space;
+ uint8_t offset;
+
+ adjust_addr(s->big_endian[1], &addr, size);
+ adjust_value(s->big_endian[1], &val, size);
+
+ /*
+ * The address is divided into the IP module number, the IP
+ * address space (I/O, ID, INT) and the offset within that space.
+ */
+ ip_n = addr >> 8;
+ space = (addr >> 6) & 3;
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Write LAS1: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ switch (space) {
+
+ case IP_ID_SPACE:
+ offset = addr & IP_ID_SPACE_ADDR_MASK;
+ if (k->id_write) {
+ k->id_write(ip, offset, val);
+ }
+ break;
+
+ case IP_INT_SPACE:
+ offset = addr & IP_INT_SPACE_ADDR_MASK;
+ if (k->int_write) {
+ k->int_write(ip, offset, val);
+ }
+ break;
+
+ default:
+ offset = addr & IP_IO_SPACE_ADDR_MASK;
+ if (k->io_write) {
+ k->io_write(ip, offset, val);
+ }
+ break;
+ }
+ }
+}
+
+static uint64_t tpci200_read_las2(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ uint64_t ret = 0;
+ unsigned ip_n;
+ uint32_t offset;
+
+ adjust_addr(s->big_endian[2], &addr, size);
+
+ /*
+ * The address is divided into the IP module number and the offset
+ * within the IP module MEM space.
+ */
+ ip_n = addr >> 23;
+ offset = addr & 0x7fffff;
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Read LAS2: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ if (k->mem_read16) {
+ ret = k->mem_read16(ip, offset);
+ }
+ }
+
+ return adjust_value(s->big_endian[2], &ret, size);
+}
+
+static void tpci200_write_las2(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ unsigned ip_n;
+ uint32_t offset;
+
+ adjust_addr(s->big_endian[2], &addr, size);
+ adjust_value(s->big_endian[2], &val, size);
+
+ /*
+ * The address is divided into the IP module number and the offset
+ * within the IP module MEM space.
+ */
+ ip_n = addr >> 23;
+ offset = addr & 0x7fffff;
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Write LAS2: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ if (k->mem_write16) {
+ k->mem_write16(ip, offset, val);
+ }
+ }
+}
+
+static uint64_t tpci200_read_las3(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ uint64_t ret = 0;
+ /*
+ * The address is divided into the IP module number and the offset
+ * within the IP module MEM space.
+ */
+ unsigned ip_n = addr >> 22;
+ uint32_t offset = addr & 0x3fffff;
+
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Read LAS3: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ if (k->mem_read8) {
+ ret = k->mem_read8(ip, offset);
+ }
+ }
+
+ return ret;
+}
+
+static void tpci200_write_las3(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ /*
+ * The address is divided into the IP module number and the offset
+ * within the IP module MEM space.
+ */
+ unsigned ip_n = addr >> 22;
+ uint32_t offset = addr & 0x3fffff;
+
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Write LAS3: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ if (k->mem_write8) {
+ k->mem_write8(ip, offset, val);
+ }
+ }
+}
+
+static const MemoryRegionOps tpci200_cfg_ops = {
+ .read = tpci200_read_cfg,
+ .write = tpci200_write_cfg,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1
+ }
+};
+
+static const MemoryRegionOps tpci200_las0_ops = {
+ .read = tpci200_read_las0,
+ .write = tpci200_write_las0,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 2,
+ .max_access_size = 2
+ }
+};
+
+static const MemoryRegionOps tpci200_las1_ops = {
+ .read = tpci200_read_las1,
+ .write = tpci200_write_las1,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 2
+ }
+};
+
+static const MemoryRegionOps tpci200_las2_ops = {
+ .read = tpci200_read_las2,
+ .write = tpci200_write_las2,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 2
+ }
+};
+
+static const MemoryRegionOps tpci200_las3_ops = {
+ .read = tpci200_read_las3,
+ .write = tpci200_write_las3,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1
+ }
+};
+
+static void tpci200_realize(PCIDevice *pci_dev, Error **errp)
+{
+ TPCI200State *s = TPCI200(pci_dev);
+ uint8_t *c = s->dev.config;
+
+ pci_set_word(c + PCI_COMMAND, 0x0003);
+ pci_set_word(c + PCI_STATUS, 0x0280);
+
+ pci_set_byte(c + PCI_INTERRUPT_PIN, 0x01); /* Interrupt pin A */
+
+ pci_set_byte(c + PCI_CAPABILITY_LIST, 0x40);
+ pci_set_long(c + 0x40, 0x48014801);
+ pci_set_long(c + 0x48, 0x00024C06);
+ pci_set_long(c + 0x4C, 0x00000003);
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &tpci200_cfg_ops,
+ s, "tpci200_mmio", 128);
+ memory_region_init_io(&s->io, OBJECT(s), &tpci200_cfg_ops,
+ s, "tpci200_io", 128);
+ memory_region_init_io(&s->las0, OBJECT(s), &tpci200_las0_ops,
+ s, "tpci200_las0", 256);
+ memory_region_init_io(&s->las1, OBJECT(s), &tpci200_las1_ops,
+ s, "tpci200_las1", 1024);
+ memory_region_init_io(&s->las2, OBJECT(s), &tpci200_las2_ops,
+ s, "tpci200_las2", 1024*1024*32);
+ memory_region_init_io(&s->las3, OBJECT(s), &tpci200_las3_ops,
+ s, "tpci200_las3", 1024*1024*16);
+ pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
+ pci_register_bar(&s->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+ pci_register_bar(&s->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las0);
+ pci_register_bar(&s->dev, 3, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las1);
+ pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las2);
+ pci_register_bar(&s->dev, 5, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las3);
+
+ ipack_bus_new_inplace(&s->bus, sizeof(s->bus), DEVICE(pci_dev), NULL,
+ N_MODULES, tpci200_set_irq);
+}
+
+static const VMStateDescription vmstate_tpci200 = {
+ .name = "tpci200",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, TPCI200State),
+ VMSTATE_BOOL_ARRAY(big_endian, TPCI200State, 3),
+ VMSTATE_UINT8_ARRAY(ctrl, TPCI200State, N_MODULES),
+ VMSTATE_UINT16(status, TPCI200State),
+ VMSTATE_UINT8(int_set, TPCI200State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tpci200_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = tpci200_realize;
+ k->vendor_id = PCI_VENDOR_ID_TEWS;
+ k->device_id = PCI_DEVICE_ID_TEWS_TPCI200;
+ k->class_id = PCI_CLASS_BRIDGE_OTHER;
+ k->subsystem_vendor_id = PCI_VENDOR_ID_TEWS;
+ k->subsystem_id = 0x300A;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "TEWS TPCI200 IndustryPack carrier";
+ dc->vmsd = &vmstate_tpci200;
+}
+
+static const TypeInfo tpci200_info = {
+ .name = TYPE_TPCI200,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(TPCI200State),
+ .class_init = tpci200_class_init,
+};
+
+static void tpci200_register_types(void)
+{
+ type_register_static(&tpci200_info);
+}
+
+type_init(tpci200_register_types)
diff --git a/hw/isa/Makefile.objs b/hw/isa/Makefile.objs
new file mode 100644
index 00000000..9164556a
--- /dev/null
+++ b/hw/isa/Makefile.objs
@@ -0,0 +1,8 @@
+common-obj-y += isa-bus.o
+common-obj-$(CONFIG_APM) += apm.o
+common-obj-$(CONFIG_I82378) += i82378.o
+common-obj-$(CONFIG_PC87312) += pc87312.o
+common-obj-$(CONFIG_PIIX4) += piix4.o
+common-obj-$(CONFIG_VT82C686) += vt82c686.o
+
+obj-$(CONFIG_LPC_ICH9) += lpc_ich9.o
diff --git a/hw/isa/apm.c b/hw/isa/apm.c
new file mode 100644
index 00000000..26ab1702
--- /dev/null
+++ b/hw/isa/apm.c
@@ -0,0 +1,102 @@
+/*
+ * QEMU PC APM controller Emulation
+ * This is split out from acpi.c
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/isa/apm.h"
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+# define APM_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define APM_DPRINTF(format, ...) do { } while (0)
+#endif
+
+/* fixed I/O location */
+#define APM_CNT_IOPORT 0xb2
+#define APM_STS_IOPORT 0xb3
+
+static void apm_ioport_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ APMState *apm = opaque;
+ addr &= 1;
+ APM_DPRINTF("apm_ioport_writeb addr=0x%" HWADDR_PRIx
+ " val=0x%02" PRIx64 "\n", addr, val);
+ if (addr == 0) {
+ apm->apmc = val;
+
+ if (apm->callback) {
+ (apm->callback)(val, apm->arg);
+ }
+ } else {
+ apm->apms = val;
+ }
+}
+
+static uint64_t apm_ioport_readb(void *opaque, hwaddr addr, unsigned size)
+{
+ APMState *apm = opaque;
+ uint32_t val;
+
+ addr &= 1;
+ if (addr == 0) {
+ val = apm->apmc;
+ } else {
+ val = apm->apms;
+ }
+ APM_DPRINTF("apm_ioport_readb addr=0x%" HWADDR_PRIx " val=0x%02x\n", addr, val);
+ return val;
+}
+
+const VMStateDescription vmstate_apm = {
+ .name = "APM State",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(apmc, APMState),
+ VMSTATE_UINT8(apms, APMState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const MemoryRegionOps apm_ops = {
+ .read = apm_ioport_readb,
+ .write = apm_ioport_writeb,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+void apm_init(PCIDevice *dev, APMState *apm, apm_ctrl_changed_t callback,
+ void *arg)
+{
+ apm->callback = callback;
+ apm->arg = arg;
+
+ /* ioport 0xb2, 0xb3 */
+ memory_region_init_io(&apm->io, OBJECT(dev), &apm_ops, apm, "apm-io", 2);
+ memory_region_add_subregion(pci_address_space_io(dev), APM_CNT_IOPORT,
+ &apm->io);
+}
diff --git a/hw/isa/i82378.c b/hw/isa/i82378.c
new file mode 100644
index 00000000..fcf97d86
--- /dev/null
+++ b/hw/isa/i82378.c
@@ -0,0 +1,145 @@
+/*
+ * QEMU Intel i82378 emulation (PCI to ISA bridge)
+ *
+ * Copyright (c) 2010-2011 Hervé Poussineau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/pci/pci.h"
+#include "hw/i386/pc.h"
+#include "hw/timer/i8254.h"
+#include "hw/audio/pcspk.h"
+
+#define TYPE_I82378 "i82378"
+#define I82378(obj) \
+ OBJECT_CHECK(I82378State, (obj), TYPE_I82378)
+
+typedef struct I82378State {
+ PCIDevice parent_obj;
+
+ qemu_irq out[2];
+ qemu_irq *i8259;
+ MemoryRegion io;
+} I82378State;
+
+static const VMStateDescription vmstate_i82378 = {
+ .name = "pci-i82378",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, I82378State),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void i82378_request_out0_irq(void *opaque, int irq, int level)
+{
+ I82378State *s = opaque;
+ qemu_set_irq(s->out[0], level);
+}
+
+static void i82378_request_pic_irq(void *opaque, int irq, int level)
+{
+ DeviceState *dev = opaque;
+ I82378State *s = I82378(dev);
+
+ qemu_set_irq(s->i8259[irq], level);
+}
+
+static void i82378_realize(PCIDevice *pci, Error **errp)
+{
+ DeviceState *dev = DEVICE(pci);
+ I82378State *s = I82378(dev);
+ uint8_t *pci_conf;
+ ISABus *isabus;
+ ISADevice *isa;
+
+ pci_conf = pci->config;
+ pci_set_word(pci_conf + PCI_COMMAND,
+ PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+ pci_set_word(pci_conf + PCI_STATUS,
+ PCI_STATUS_DEVSEL_MEDIUM);
+
+ pci_config_set_interrupt_pin(pci_conf, 1); /* interrupt pin 0 */
+
+ isabus = isa_bus_new(dev, get_system_memory(),
+ pci_address_space_io(pci));
+
+ /* This device has:
+ 2 82C59 (irq)
+ 1 82C54 (pit)
+ 2 82C37 (dma)
+ NMI
+ Utility Bus Support Registers
+
+ All devices accept byte access only, except timer
+ */
+
+ /* 2 82C59 (irq) */
+ s->i8259 = i8259_init(isabus,
+ qemu_allocate_irq(i82378_request_out0_irq, s, 0));
+ isa_bus_irqs(isabus, s->i8259);
+
+ /* 1 82C54 (pit) */
+ isa = pit_init(isabus, 0x40, 0, NULL);
+
+ /* speaker */
+ pcspk_init(isabus, isa);
+
+ /* 2 82C37 (dma) */
+ isa = isa_create_simple(isabus, "i82374");
+ qdev_connect_gpio_out(DEVICE(isa), 0, s->out[1]);
+
+ /* timer */
+ isa_create_simple(isabus, "mc146818rtc");
+}
+
+static void i82378_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ I82378State *s = I82378(obj);
+
+ qdev_init_gpio_out(dev, s->out, 2);
+ qdev_init_gpio_in(dev, i82378_request_pic_irq, 16);
+}
+
+static void i82378_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = i82378_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82378;
+ k->revision = 0x03;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ dc->vmsd = &vmstate_i82378;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo i82378_type_info = {
+ .name = TYPE_I82378,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(I82378State),
+ .instance_init = i82378_init,
+ .class_init = i82378_class_init,
+};
+
+static void i82378_register_types(void)
+{
+ type_register_static(&i82378_type_info);
+}
+
+type_init(i82378_register_types)
diff --git a/hw/isa/isa-bus.c b/hw/isa/isa-bus.c
new file mode 100644
index 00000000..43e0cd8d
--- /dev/null
+++ b/hw/isa/isa-bus.c
@@ -0,0 +1,298 @@
+/*
+ * isa bus support for qdev.
+ *
+ * Copyright (c) 2009 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "monitor/monitor.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+
+static ISABus *isabus;
+
+static void isabus_dev_print(Monitor *mon, DeviceState *dev, int indent);
+static char *isabus_get_fw_dev_path(DeviceState *dev);
+
+static void isa_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+
+ k->print_dev = isabus_dev_print;
+ k->get_fw_dev_path = isabus_get_fw_dev_path;
+}
+
+static const TypeInfo isa_bus_info = {
+ .name = TYPE_ISA_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(ISABus),
+ .class_init = isa_bus_class_init,
+};
+
+ISABus *isa_bus_new(DeviceState *dev, MemoryRegion* address_space,
+ MemoryRegion *address_space_io)
+{
+ if (isabus) {
+ fprintf(stderr, "Can't create a second ISA bus\n");
+ return NULL;
+ }
+ if (!dev) {
+ dev = qdev_create(NULL, "isabus-bridge");
+ qdev_init_nofail(dev);
+ }
+
+ isabus = ISA_BUS(qbus_create(TYPE_ISA_BUS, dev, NULL));
+ isabus->address_space = address_space;
+ isabus->address_space_io = address_space_io;
+ return isabus;
+}
+
+void isa_bus_irqs(ISABus *bus, qemu_irq *irqs)
+{
+ if (!bus) {
+ hw_error("Can't set isa irqs with no isa bus present.");
+ }
+ bus->irqs = irqs;
+}
+
+/*
+ * isa_get_irq() returns the corresponding qemu_irq entry for the i8259.
+ *
+ * This function is only for special cases such as the 'ferr', and
+ * temporary use for normal devices until they are converted to qdev.
+ */
+qemu_irq isa_get_irq(ISADevice *dev, int isairq)
+{
+ assert(!dev || ISA_BUS(qdev_get_parent_bus(DEVICE(dev))) == isabus);
+ if (isairq < 0 || isairq > 15) {
+ hw_error("isa irq %d invalid", isairq);
+ }
+ return isabus->irqs[isairq];
+}
+
+void isa_init_irq(ISADevice *dev, qemu_irq *p, int isairq)
+{
+ assert(dev->nirqs < ARRAY_SIZE(dev->isairq));
+ dev->isairq[dev->nirqs] = isairq;
+ *p = isa_get_irq(dev, isairq);
+ dev->nirqs++;
+}
+
+static inline void isa_init_ioport(ISADevice *dev, uint16_t ioport)
+{
+ if (dev && (dev->ioport_id == 0 || ioport < dev->ioport_id)) {
+ dev->ioport_id = ioport;
+ }
+}
+
+void isa_register_ioport(ISADevice *dev, MemoryRegion *io, uint16_t start)
+{
+ memory_region_add_subregion(isabus->address_space_io, start, io);
+ isa_init_ioport(dev, start);
+}
+
+void isa_register_portio_list(ISADevice *dev, uint16_t start,
+ const MemoryRegionPortio *pio_start,
+ void *opaque, const char *name)
+{
+ PortioList piolist;
+
+ /* START is how we should treat DEV, regardless of the actual
+ contents of the portio array. This is how the old code
+ actually handled e.g. the FDC device. */
+ isa_init_ioport(dev, start);
+
+ /* FIXME: the device should store created PortioList in its state. Note
+ that DEV can be NULL here and that single device can register several
+ portio lists. Current implementation is leaking memory allocated
+ in portio_list_init. The leak is not critical because it happens only
+ at initialization time. */
+ portio_list_init(&piolist, OBJECT(dev), pio_start, opaque, name);
+ portio_list_add(&piolist, isabus->address_space_io, start);
+}
+
+static void isa_device_init(Object *obj)
+{
+ ISADevice *dev = ISA_DEVICE(obj);
+
+ dev->isairq[0] = -1;
+ dev->isairq[1] = -1;
+}
+
+ISADevice *isa_create(ISABus *bus, const char *name)
+{
+ DeviceState *dev;
+
+ if (!bus) {
+ hw_error("Tried to create isa device %s with no isa bus present.",
+ name);
+ }
+ dev = qdev_create(BUS(bus), name);
+ return ISA_DEVICE(dev);
+}
+
+ISADevice *isa_try_create(ISABus *bus, const char *name)
+{
+ DeviceState *dev;
+
+ if (!bus) {
+ hw_error("Tried to create isa device %s with no isa bus present.",
+ name);
+ }
+ dev = qdev_try_create(BUS(bus), name);
+ return ISA_DEVICE(dev);
+}
+
+ISADevice *isa_create_simple(ISABus *bus, const char *name)
+{
+ ISADevice *dev;
+
+ dev = isa_create(bus, name);
+ qdev_init_nofail(DEVICE(dev));
+ return dev;
+}
+
+ISADevice *isa_vga_init(ISABus *bus)
+{
+ switch (vga_interface_type) {
+ case VGA_CIRRUS:
+ return isa_create_simple(bus, "isa-cirrus-vga");
+ case VGA_QXL:
+ fprintf(stderr, "%s: qxl: no PCI bus\n", __func__);
+ return NULL;
+ case VGA_STD:
+ return isa_create_simple(bus, "isa-vga");
+ case VGA_VMWARE:
+ fprintf(stderr, "%s: vmware_vga: no PCI bus\n", __func__);
+ return NULL;
+ case VGA_VIRTIO:
+ fprintf(stderr, "%s: virtio-vga: no PCI bus\n", __func__);
+ return NULL;
+ case VGA_NONE:
+ default:
+ return NULL;
+ }
+}
+
+static void isabus_dev_print(Monitor *mon, DeviceState *dev, int indent)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+
+ if (d->isairq[1] != -1) {
+ monitor_printf(mon, "%*sisa irqs %d,%d\n", indent, "",
+ d->isairq[0], d->isairq[1]);
+ } else if (d->isairq[0] != -1) {
+ monitor_printf(mon, "%*sisa irq %d\n", indent, "",
+ d->isairq[0]);
+ }
+}
+
+static void isabus_bridge_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->fw_name = "isa";
+}
+
+static const TypeInfo isabus_bridge_info = {
+ .name = "isabus-bridge",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .class_init = isabus_bridge_class_init,
+};
+
+static void isa_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->bus_type = TYPE_ISA_BUS;
+}
+
+static const TypeInfo isa_device_type_info = {
+ .name = TYPE_ISA_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(ISADevice),
+ .instance_init = isa_device_init,
+ .abstract = true,
+ .class_size = sizeof(ISADeviceClass),
+ .class_init = isa_device_class_init,
+};
+
+static void isabus_register_types(void)
+{
+ type_register_static(&isa_bus_info);
+ type_register_static(&isabus_bridge_info);
+ type_register_static(&isa_device_type_info);
+}
+
+static char *isabus_get_fw_dev_path(DeviceState *dev)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+ char path[40];
+ int off;
+
+ off = snprintf(path, sizeof(path), "%s", qdev_fw_name(dev));
+ if (d->ioport_id) {
+ snprintf(path + off, sizeof(path) - off, "@%04x", d->ioport_id);
+ }
+
+ return g_strdup(path);
+}
+
+MemoryRegion *isa_address_space(ISADevice *dev)
+{
+ if (dev) {
+ return isa_bus_from_device(dev)->address_space;
+ }
+
+ return isabus->address_space;
+}
+
+MemoryRegion *isa_address_space_io(ISADevice *dev)
+{
+ if (dev) {
+ return isa_bus_from_device(dev)->address_space_io;
+ }
+
+ return isabus->address_space_io;
+}
+
+type_init(isabus_register_types)
+
+static void parallel_init(ISABus *bus, int index, CharDriverState *chr)
+{
+ DeviceState *dev;
+ ISADevice *isadev;
+
+ isadev = isa_create(bus, "isa-parallel");
+ dev = DEVICE(isadev);
+ qdev_prop_set_uint32(dev, "index", index);
+ qdev_prop_set_chr(dev, "chardev", chr);
+ qdev_init_nofail(dev);
+}
+
+void parallel_hds_isa_init(ISABus *bus, int n)
+{
+ int i;
+
+ assert(n <= MAX_PARALLEL_PORTS);
+
+ for (i = 0; i < n; i++) {
+ if (parallel_hds[i]) {
+ parallel_init(bus, i, parallel_hds[i]);
+ }
+ }
+}
diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c
new file mode 100644
index 00000000..360699f6
--- /dev/null
+++ b/hw/isa/lpc_ich9.c
@@ -0,0 +1,747 @@
+/*
+ * QEMU ICH9 Emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2009, 2010, 2011
+ * Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This is based on piix.c, but heavily modified.
+ *
+ * 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.
+ */
+#include "qemu-common.h"
+#include "hw/hw.h"
+#include "qapi/visitor.h"
+#include "qemu/range.h"
+#include "hw/isa/isa.h"
+#include "hw/sysbus.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/apm.h"
+#include "hw/i386/ioapic.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pcie_host.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/i386/ich9.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/ich9.h"
+#include "hw/pci/pci_bus.h"
+#include "exec/address-spaces.h"
+#include "sysemu/sysemu.h"
+
+static int ich9_lpc_sci_irq(ICH9LPCState *lpc);
+
+/*****************************************************************************/
+/* ICH9 LPC PCI to ISA bridge */
+
+static void ich9_lpc_reset(DeviceState *qdev);
+
+/* chipset configuration register
+ * to access chipset configuration registers, pci_[sg]et_{byte, word, long}
+ * are used.
+ * Although it's not pci configuration space, it's little endian as Intel.
+ */
+
+static void ich9_cc_update_ir(uint8_t irr[PCI_NUM_PINS], uint16_t ir)
+{
+ int intx;
+ for (intx = 0; intx < PCI_NUM_PINS; intx++) {
+ irr[intx] = (ir >> (intx * ICH9_CC_DIR_SHIFT)) & ICH9_CC_DIR_MASK;
+ }
+}
+
+static void ich9_cc_update(ICH9LPCState *lpc)
+{
+ int slot;
+ int pci_intx;
+
+ const int reg_offsets[] = {
+ ICH9_CC_D25IR,
+ ICH9_CC_D26IR,
+ ICH9_CC_D27IR,
+ ICH9_CC_D28IR,
+ ICH9_CC_D29IR,
+ ICH9_CC_D30IR,
+ ICH9_CC_D31IR,
+ };
+ const int *offset;
+
+ /* D{25 - 31}IR, but D30IR is read only to 0. */
+ for (slot = 25, offset = reg_offsets; slot < 32; slot++, offset++) {
+ if (slot == 30) {
+ continue;
+ }
+ ich9_cc_update_ir(lpc->irr[slot],
+ pci_get_word(lpc->chip_config + *offset));
+ }
+
+ /*
+ * D30: DMI2PCI bridge
+ * It is arbitrarily decided how INTx lines of PCI devicesbehind the bridge
+ * are connected to pirq lines. Our choice is PIRQ[E-H].
+ * INT[A-D] are connected to PIRQ[E-H]
+ */
+ for (pci_intx = 0; pci_intx < PCI_NUM_PINS; pci_intx++) {
+ lpc->irr[30][pci_intx] = pci_intx + 4;
+ }
+}
+
+static void ich9_cc_init(ICH9LPCState *lpc)
+{
+ int slot;
+ int intx;
+
+ /* the default irq routing is arbitrary as long as it matches with
+ * acpi irq routing table.
+ * The one that is incompatible with piix_pci(= bochs) one is
+ * intentionally chosen to let the users know that the different
+ * board is used.
+ *
+ * int[A-D] -> pirq[E-F]
+ * avoid pirq A-D because they are used for pci express port
+ */
+ for (slot = 0; slot < PCI_SLOT_MAX; slot++) {
+ for (intx = 0; intx < PCI_NUM_PINS; intx++) {
+ lpc->irr[slot][intx] = (slot + intx) % 4 + 4;
+ }
+ }
+ ich9_cc_update(lpc);
+}
+
+static void ich9_cc_reset(ICH9LPCState *lpc)
+{
+ uint8_t *c = lpc->chip_config;
+
+ memset(lpc->chip_config, 0, sizeof(lpc->chip_config));
+
+ pci_set_long(c + ICH9_CC_D31IR, ICH9_CC_DIR_DEFAULT);
+ pci_set_long(c + ICH9_CC_D30IR, ICH9_CC_D30IR_DEFAULT);
+ pci_set_long(c + ICH9_CC_D29IR, ICH9_CC_DIR_DEFAULT);
+ pci_set_long(c + ICH9_CC_D28IR, ICH9_CC_DIR_DEFAULT);
+ pci_set_long(c + ICH9_CC_D27IR, ICH9_CC_DIR_DEFAULT);
+ pci_set_long(c + ICH9_CC_D26IR, ICH9_CC_DIR_DEFAULT);
+ pci_set_long(c + ICH9_CC_D25IR, ICH9_CC_DIR_DEFAULT);
+ pci_set_long(c + ICH9_CC_GCS, ICH9_CC_GCS_DEFAULT);
+
+ ich9_cc_update(lpc);
+}
+
+static void ich9_cc_addr_len(uint64_t *addr, unsigned *len)
+{
+ *addr &= ICH9_CC_ADDR_MASK;
+ if (*addr + *len >= ICH9_CC_SIZE) {
+ *len = ICH9_CC_SIZE - *addr;
+ }
+}
+
+/* val: little endian */
+static void ich9_cc_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned len)
+{
+ ICH9LPCState *lpc = (ICH9LPCState *)opaque;
+
+ ich9_cc_addr_len(&addr, &len);
+ memcpy(lpc->chip_config + addr, &val, len);
+ pci_bus_fire_intx_routing_notifier(lpc->d.bus);
+ ich9_cc_update(lpc);
+}
+
+/* return value: little endian */
+static uint64_t ich9_cc_read(void *opaque, hwaddr addr,
+ unsigned len)
+{
+ ICH9LPCState *lpc = (ICH9LPCState *)opaque;
+
+ uint32_t val = 0;
+ ich9_cc_addr_len(&addr, &len);
+ memcpy(&val, lpc->chip_config + addr, len);
+ return val;
+}
+
+/* IRQ routing */
+/* */
+static void ich9_lpc_rout(uint8_t pirq_rout, int *pic_irq, int *pic_dis)
+{
+ *pic_irq = pirq_rout & ICH9_LPC_PIRQ_ROUT_MASK;
+ *pic_dis = pirq_rout & ICH9_LPC_PIRQ_ROUT_IRQEN;
+}
+
+static void ich9_lpc_pic_irq(ICH9LPCState *lpc, int pirq_num,
+ int *pic_irq, int *pic_dis)
+{
+ switch (pirq_num) {
+ case 0 ... 3: /* A-D */
+ ich9_lpc_rout(lpc->d.config[ICH9_LPC_PIRQA_ROUT + pirq_num],
+ pic_irq, pic_dis);
+ return;
+ case 4 ... 7: /* E-H */
+ ich9_lpc_rout(lpc->d.config[ICH9_LPC_PIRQE_ROUT + (pirq_num - 4)],
+ pic_irq, pic_dis);
+ return;
+ default:
+ break;
+ }
+ abort();
+}
+
+/* pic_irq: i8254 irq 0-15 */
+static void ich9_lpc_update_pic(ICH9LPCState *lpc, int pic_irq)
+{
+ int i, pic_level;
+
+ /* The pic level is the logical OR of all the PCI irqs mapped to it */
+ pic_level = 0;
+ for (i = 0; i < ICH9_LPC_NB_PIRQS; i++) {
+ int tmp_irq;
+ int tmp_dis;
+ ich9_lpc_pic_irq(lpc, i, &tmp_irq, &tmp_dis);
+ if (!tmp_dis && pic_irq == tmp_irq) {
+ pic_level |= pci_bus_get_irq_level(lpc->d.bus, i);
+ }
+ }
+ if (pic_irq == ich9_lpc_sci_irq(lpc)) {
+ pic_level |= lpc->sci_level;
+ }
+
+ qemu_set_irq(lpc->pic[pic_irq], pic_level);
+}
+
+/* pirq: pirq[A-H] 0-7*/
+static void ich9_lpc_update_by_pirq(ICH9LPCState *lpc, int pirq)
+{
+ int pic_irq;
+ int pic_dis;
+
+ ich9_lpc_pic_irq(lpc, pirq, &pic_irq, &pic_dis);
+ assert(pic_irq < ICH9_LPC_PIC_NUM_PINS);
+ if (pic_dis) {
+ return;
+ }
+
+ ich9_lpc_update_pic(lpc, pic_irq);
+}
+
+/* APIC mode: GSIx: PIRQ[A-H] -> GSI 16, ... no pirq shares same APIC pins. */
+static int ich9_pirq_to_gsi(int pirq)
+{
+ return pirq + ICH9_LPC_PIC_NUM_PINS;
+}
+
+static int ich9_gsi_to_pirq(int gsi)
+{
+ return gsi - ICH9_LPC_PIC_NUM_PINS;
+}
+
+static void ich9_lpc_update_apic(ICH9LPCState *lpc, int gsi)
+{
+ int level = 0;
+
+ if (gsi >= ICH9_LPC_PIC_NUM_PINS) {
+ level |= pci_bus_get_irq_level(lpc->d.bus, ich9_gsi_to_pirq(gsi));
+ }
+ if (gsi == ich9_lpc_sci_irq(lpc)) {
+ level |= lpc->sci_level;
+ }
+
+ qemu_set_irq(lpc->ioapic[gsi], level);
+}
+
+void ich9_lpc_set_irq(void *opaque, int pirq, int level)
+{
+ ICH9LPCState *lpc = opaque;
+
+ assert(0 <= pirq);
+ assert(pirq < ICH9_LPC_NB_PIRQS);
+
+ ich9_lpc_update_apic(lpc, ich9_pirq_to_gsi(pirq));
+ ich9_lpc_update_by_pirq(lpc, pirq);
+}
+
+/* return the pirq number (PIRQ[A-H]:0-7) corresponding to
+ * a given device irq pin.
+ */
+int ich9_lpc_map_irq(PCIDevice *pci_dev, int intx)
+{
+ BusState *bus = qdev_get_parent_bus(&pci_dev->qdev);
+ PCIBus *pci_bus = PCI_BUS(bus);
+ PCIDevice *lpc_pdev =
+ pci_bus->devices[PCI_DEVFN(ICH9_LPC_DEV, ICH9_LPC_FUNC)];
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(lpc_pdev);
+
+ return lpc->irr[PCI_SLOT(pci_dev->devfn)][intx];
+}
+
+PCIINTxRoute ich9_route_intx_pin_to_irq(void *opaque, int pirq_pin)
+{
+ ICH9LPCState *lpc = opaque;
+ PCIINTxRoute route;
+ int pic_irq;
+ int pic_dis;
+
+ assert(0 <= pirq_pin);
+ assert(pirq_pin < ICH9_LPC_NB_PIRQS);
+
+ route.mode = PCI_INTX_ENABLED;
+ ich9_lpc_pic_irq(lpc, pirq_pin, &pic_irq, &pic_dis);
+ if (!pic_dis) {
+ if (pic_irq < ICH9_LPC_PIC_NUM_PINS) {
+ route.irq = pic_irq;
+ } else {
+ route.mode = PCI_INTX_DISABLED;
+ route.irq = -1;
+ }
+ } else {
+ route.irq = ich9_pirq_to_gsi(pirq_pin);
+ }
+
+ return route;
+}
+
+void ich9_generate_smi(void)
+{
+ cpu_interrupt(first_cpu, CPU_INTERRUPT_SMI);
+}
+
+void ich9_generate_nmi(void)
+{
+ cpu_interrupt(first_cpu, CPU_INTERRUPT_NMI);
+}
+
+static int ich9_lpc_sci_irq(ICH9LPCState *lpc)
+{
+ switch (lpc->d.config[ICH9_LPC_ACPI_CTRL] &
+ ICH9_LPC_ACPI_CTRL_SCI_IRQ_SEL_MASK) {
+ case ICH9_LPC_ACPI_CTRL_9:
+ return 9;
+ case ICH9_LPC_ACPI_CTRL_10:
+ return 10;
+ case ICH9_LPC_ACPI_CTRL_11:
+ return 11;
+ case ICH9_LPC_ACPI_CTRL_20:
+ return 20;
+ case ICH9_LPC_ACPI_CTRL_21:
+ return 21;
+ default:
+ /* reserved */
+ break;
+ }
+ return -1;
+}
+
+static void ich9_set_sci(void *opaque, int irq_num, int level)
+{
+ ICH9LPCState *lpc = opaque;
+ int irq;
+
+ assert(irq_num == 0);
+ level = !!level;
+ if (level == lpc->sci_level) {
+ return;
+ }
+ lpc->sci_level = level;
+
+ irq = ich9_lpc_sci_irq(lpc);
+ if (irq < 0) {
+ return;
+ }
+
+ ich9_lpc_update_apic(lpc, irq);
+ if (irq < ICH9_LPC_PIC_NUM_PINS) {
+ ich9_lpc_update_pic(lpc, irq);
+ }
+}
+
+void ich9_lpc_pm_init(PCIDevice *lpc_pci, bool smm_enabled, bool enable_tco)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(lpc_pci);
+ qemu_irq sci_irq;
+
+ sci_irq = qemu_allocate_irq(ich9_set_sci, lpc, 0);
+ ich9_pm_init(lpc_pci, &lpc->pm, smm_enabled, enable_tco, sci_irq);
+ ich9_lpc_reset(&lpc->d.qdev);
+}
+
+/* APM */
+
+static void ich9_apm_ctrl_changed(uint32_t val, void *arg)
+{
+ ICH9LPCState *lpc = arg;
+
+ /* ACPI specs 3.0, 4.7.2.5 */
+ acpi_pm1_cnt_update(&lpc->pm.acpi_regs,
+ val == ICH9_APM_ACPI_ENABLE,
+ val == ICH9_APM_ACPI_DISABLE);
+ if (val == ICH9_APM_ACPI_ENABLE || val == ICH9_APM_ACPI_DISABLE) {
+ return;
+ }
+
+ /* SMI_EN = PMBASE + 30. SMI control and enable register */
+ if (lpc->pm.smi_en & ICH9_PMIO_SMI_EN_APMC_EN) {
+ cpu_interrupt(first_cpu, CPU_INTERRUPT_SMI);
+ }
+}
+
+/* config:PMBASE */
+static void
+ich9_lpc_pmbase_update(ICH9LPCState *lpc)
+{
+ uint32_t pm_io_base = pci_get_long(lpc->d.config + ICH9_LPC_PMBASE);
+ pm_io_base &= ICH9_LPC_PMBASE_BASE_ADDRESS_MASK;
+
+ ich9_pm_iospace_update(&lpc->pm, pm_io_base);
+}
+
+/* config:RBCA */
+static void ich9_lpc_rcba_update(ICH9LPCState *lpc, uint32_t rbca_old)
+{
+ uint32_t rbca = pci_get_long(lpc->d.config + ICH9_LPC_RCBA);
+
+ if (rbca_old & ICH9_LPC_RCBA_EN) {
+ memory_region_del_subregion(get_system_memory(), &lpc->rbca_mem);
+ }
+ if (rbca & ICH9_LPC_RCBA_EN) {
+ memory_region_add_subregion_overlap(get_system_memory(),
+ rbca & ICH9_LPC_RCBA_BA_MASK,
+ &lpc->rbca_mem, 1);
+ }
+}
+
+/* config:GEN_PMCON* */
+static void
+ich9_lpc_pmcon_update(ICH9LPCState *lpc)
+{
+ uint16_t gen_pmcon_1 = pci_get_word(lpc->d.config + ICH9_LPC_GEN_PMCON_1);
+ uint16_t wmask;
+
+ if (gen_pmcon_1 & ICH9_LPC_GEN_PMCON_1_SMI_LOCK) {
+ wmask = pci_get_word(lpc->d.wmask + ICH9_LPC_GEN_PMCON_1);
+ wmask &= ~ICH9_LPC_GEN_PMCON_1_SMI_LOCK;
+ pci_set_word(lpc->d.wmask + ICH9_LPC_GEN_PMCON_1, wmask);
+ lpc->pm.smi_en_wmask &= ~1;
+ }
+}
+
+static int ich9_lpc_post_load(void *opaque, int version_id)
+{
+ ICH9LPCState *lpc = opaque;
+
+ ich9_lpc_pmbase_update(lpc);
+ ich9_lpc_rcba_update(lpc, 0 /* disabled ICH9_LPC_RBCA_EN */);
+ ich9_lpc_pmcon_update(lpc);
+ return 0;
+}
+
+static void ich9_lpc_config_write(PCIDevice *d,
+ uint32_t addr, uint32_t val, int len)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(d);
+ uint32_t rbca_old = pci_get_long(d->config + ICH9_LPC_RCBA);
+
+ pci_default_write_config(d, addr, val, len);
+ if (ranges_overlap(addr, len, ICH9_LPC_PMBASE, 4)) {
+ ich9_lpc_pmbase_update(lpc);
+ }
+ if (ranges_overlap(addr, len, ICH9_LPC_RCBA, 4)) {
+ ich9_lpc_rcba_update(lpc, rbca_old);
+ }
+ if (ranges_overlap(addr, len, ICH9_LPC_PIRQA_ROUT, 4)) {
+ pci_bus_fire_intx_routing_notifier(lpc->d.bus);
+ }
+ if (ranges_overlap(addr, len, ICH9_LPC_PIRQE_ROUT, 4)) {
+ pci_bus_fire_intx_routing_notifier(lpc->d.bus);
+ }
+ if (ranges_overlap(addr, len, ICH9_LPC_GEN_PMCON_1, 8)) {
+ ich9_lpc_pmcon_update(lpc);
+ }
+}
+
+static void ich9_lpc_reset(DeviceState *qdev)
+{
+ PCIDevice *d = PCI_DEVICE(qdev);
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(d);
+ uint32_t rbca_old = pci_get_long(d->config + ICH9_LPC_RCBA);
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ pci_set_byte(d->config + ICH9_LPC_PIRQA_ROUT + i,
+ ICH9_LPC_PIRQ_ROUT_DEFAULT);
+ }
+ for (i = 0; i < 4; i++) {
+ pci_set_byte(d->config + ICH9_LPC_PIRQE_ROUT + i,
+ ICH9_LPC_PIRQ_ROUT_DEFAULT);
+ }
+ pci_set_byte(d->config + ICH9_LPC_ACPI_CTRL, ICH9_LPC_ACPI_CTRL_DEFAULT);
+
+ pci_set_long(d->config + ICH9_LPC_PMBASE, ICH9_LPC_PMBASE_DEFAULT);
+ pci_set_long(d->config + ICH9_LPC_RCBA, ICH9_LPC_RCBA_DEFAULT);
+
+ ich9_cc_reset(lpc);
+
+ ich9_lpc_pmbase_update(lpc);
+ ich9_lpc_rcba_update(lpc, rbca_old);
+
+ lpc->sci_level = 0;
+ lpc->rst_cnt = 0;
+}
+
+static const MemoryRegionOps rbca_mmio_ops = {
+ .read = ich9_cc_read,
+ .write = ich9_cc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ich9_lpc_machine_ready(Notifier *n, void *opaque)
+{
+ ICH9LPCState *s = container_of(n, ICH9LPCState, machine_ready);
+ MemoryRegion *io_as = pci_address_space_io(&s->d);
+ uint8_t *pci_conf;
+
+ pci_conf = s->d.config;
+ if (memory_region_present(io_as, 0x3f8)) {
+ /* com1 */
+ pci_conf[0x82] |= 0x01;
+ }
+ if (memory_region_present(io_as, 0x2f8)) {
+ /* com2 */
+ pci_conf[0x82] |= 0x02;
+ }
+ if (memory_region_present(io_as, 0x378)) {
+ /* lpt */
+ pci_conf[0x82] |= 0x04;
+ }
+ if (memory_region_present(io_as, 0x3f2)) {
+ /* floppy */
+ pci_conf[0x82] |= 0x08;
+ }
+}
+
+/* reset control */
+static void ich9_rst_cnt_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned len)
+{
+ ICH9LPCState *lpc = opaque;
+
+ if (val & 4) {
+ qemu_system_reset_request();
+ return;
+ }
+ lpc->rst_cnt = val & 0xA; /* keep FULL_RST (bit 3) and SYS_RST (bit 1) */
+}
+
+static uint64_t ich9_rst_cnt_read(void *opaque, hwaddr addr, unsigned len)
+{
+ ICH9LPCState *lpc = opaque;
+
+ return lpc->rst_cnt;
+}
+
+static const MemoryRegionOps ich9_rst_cnt_ops = {
+ .read = ich9_rst_cnt_read,
+ .write = ich9_rst_cnt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN
+};
+
+Object *ich9_lpc_find(void)
+{
+ bool ambig;
+ Object *o = object_resolve_path_type("", TYPE_ICH9_LPC_DEVICE, &ambig);
+
+ if (ambig) {
+ return NULL;
+ }
+ return o;
+}
+
+static void ich9_lpc_get_sci_int(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(obj);
+ uint32_t value = ich9_lpc_sci_irq(lpc);
+
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static void ich9_lpc_add_properties(ICH9LPCState *lpc)
+{
+ static const uint8_t acpi_enable_cmd = ICH9_APM_ACPI_ENABLE;
+ static const uint8_t acpi_disable_cmd = ICH9_APM_ACPI_DISABLE;
+
+ object_property_add(OBJECT(lpc), ACPI_PM_PROP_SCI_INT, "uint32",
+ ich9_lpc_get_sci_int,
+ NULL, NULL, NULL, NULL);
+ object_property_add_uint8_ptr(OBJECT(lpc), ACPI_PM_PROP_ACPI_ENABLE_CMD,
+ &acpi_enable_cmd, NULL);
+ object_property_add_uint8_ptr(OBJECT(lpc), ACPI_PM_PROP_ACPI_DISABLE_CMD,
+ &acpi_disable_cmd, NULL);
+
+ ich9_pm_add_properties(OBJECT(lpc), &lpc->pm, NULL);
+}
+
+static void ich9_lpc_initfn(Object *obj)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(obj);
+
+ ich9_lpc_add_properties(lpc);
+}
+
+static int ich9_lpc_init(PCIDevice *d)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(d);
+ ISABus *isa_bus;
+
+ isa_bus = isa_bus_new(DEVICE(d), get_system_memory(), get_system_io());
+
+ pci_set_long(d->wmask + ICH9_LPC_PMBASE,
+ ICH9_LPC_PMBASE_BASE_ADDRESS_MASK);
+
+ memory_region_init_io(&lpc->rbca_mem, OBJECT(d), &rbca_mmio_ops, lpc,
+ "lpc-rbca-mmio", ICH9_CC_SIZE);
+
+ lpc->isa_bus = isa_bus;
+
+ ich9_cc_init(lpc);
+ apm_init(d, &lpc->apm, ich9_apm_ctrl_changed, lpc);
+
+ lpc->machine_ready.notify = ich9_lpc_machine_ready;
+ qemu_add_machine_init_done_notifier(&lpc->machine_ready);
+
+ memory_region_init_io(&lpc->rst_cnt_mem, OBJECT(d), &ich9_rst_cnt_ops, lpc,
+ "lpc-reset-control", 1);
+ memory_region_add_subregion_overlap(pci_address_space_io(d),
+ ICH9_RST_CNT_IOPORT, &lpc->rst_cnt_mem,
+ 1);
+ return 0;
+}
+
+static void ich9_device_plug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(hotplug_dev);
+
+ ich9_pm_device_plug_cb(&lpc->pm, dev, errp);
+}
+
+static void ich9_device_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(hotplug_dev);
+
+ ich9_pm_device_unplug_request_cb(&lpc->pm, dev, errp);
+}
+
+static void ich9_device_unplug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(hotplug_dev);
+
+ ich9_pm_device_unplug_cb(&lpc->pm, dev, errp);
+}
+
+static bool ich9_rst_cnt_needed(void *opaque)
+{
+ ICH9LPCState *lpc = opaque;
+
+ return (lpc->rst_cnt != 0);
+}
+
+static const VMStateDescription vmstate_ich9_rst_cnt = {
+ .name = "ICH9LPC/rst_cnt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ich9_rst_cnt_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(rst_cnt, ICH9LPCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ich9_lpc = {
+ .name = "ICH9LPC",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ich9_lpc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(d, ICH9LPCState),
+ VMSTATE_STRUCT(apm, ICH9LPCState, 0, vmstate_apm, APMState),
+ VMSTATE_STRUCT(pm, ICH9LPCState, 0, vmstate_ich9_pm, ICH9LPCPMRegs),
+ VMSTATE_UINT8_ARRAY(chip_config, ICH9LPCState, ICH9_CC_SIZE),
+ VMSTATE_UINT32(sci_level, ICH9LPCState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_ich9_rst_cnt,
+ NULL
+ }
+};
+
+static Property ich9_lpc_properties[] = {
+ DEFINE_PROP_BOOL("noreboot", ICH9LPCState, pin_strap.spkr_hi, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ich9_lpc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->reset = ich9_lpc_reset;
+ k->init = ich9_lpc_init;
+ dc->vmsd = &vmstate_ich9_lpc;
+ dc->props = ich9_lpc_properties;
+ k->config_write = ich9_lpc_config_write;
+ dc->desc = "ICH9 LPC bridge";
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_ICH9_8;
+ k->revision = ICH9_A2_LPC_REVISION;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ /*
+ * Reason: part of ICH9 southbridge, needs to be wired up by
+ * pc_q35_init()
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+ hc->plug = ich9_device_plug_cb;
+ hc->unplug_request = ich9_device_unplug_request_cb;
+ hc->unplug = ich9_device_unplug_cb;
+ adevc->ospm_status = ich9_pm_ospm_status;
+}
+
+static const TypeInfo ich9_lpc_info = {
+ .name = TYPE_ICH9_LPC_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(struct ICH9LPCState),
+ .instance_init = ich9_lpc_initfn,
+ .class_init = ich9_lpc_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { TYPE_ACPI_DEVICE_IF },
+ { }
+ }
+};
+
+static void ich9_lpc_register(void)
+{
+ type_register_static(&ich9_lpc_info);
+}
+
+type_init(ich9_lpc_register);
diff --git a/hw/isa/pc87312.c b/hw/isa/pc87312.c
new file mode 100644
index 00000000..3b1fcec5
--- /dev/null
+++ b/hw/isa/pc87312.c
@@ -0,0 +1,402 @@
+/*
+ * QEMU National Semiconductor PC87312 (Super I/O)
+ *
+ * Copyright (c) 2010-2012 Herve Poussineau
+ * Copyright (c) 2011-2012 Andreas Färber
+ *
+ * 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.
+ */
+
+#include "hw/isa/pc87312.h"
+#include "qemu/error-report.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/char.h"
+#include "trace.h"
+
+
+#define REG_FER 0
+#define REG_FAR 1
+#define REG_PTR 2
+
+#define FER_PARALLEL_EN 0x01
+#define FER_UART1_EN 0x02
+#define FER_UART2_EN 0x04
+#define FER_FDC_EN 0x08
+#define FER_FDC_4 0x10
+#define FER_FDC_ADDR 0x20
+#define FER_IDE_EN 0x40
+#define FER_IDE_ADDR 0x80
+
+#define FAR_PARALLEL_ADDR 0x03
+#define FAR_UART1_ADDR 0x0C
+#define FAR_UART2_ADDR 0x30
+#define FAR_UART_3_4 0xC0
+
+#define PTR_POWER_DOWN 0x01
+#define PTR_CLOCK_DOWN 0x02
+#define PTR_PWDN 0x04
+#define PTR_IRQ_5_7 0x08
+#define PTR_UART1_TEST 0x10
+#define PTR_UART2_TEST 0x20
+#define PTR_LOCK_CONF 0x40
+#define PTR_EPP_MODE 0x80
+
+
+/* Parallel port */
+
+static inline bool is_parallel_enabled(PC87312State *s)
+{
+ return s->regs[REG_FER] & FER_PARALLEL_EN;
+}
+
+static const uint32_t parallel_base[] = { 0x378, 0x3bc, 0x278, 0x00 };
+
+static inline uint32_t get_parallel_iobase(PC87312State *s)
+{
+ return parallel_base[s->regs[REG_FAR] & FAR_PARALLEL_ADDR];
+}
+
+static const uint32_t parallel_irq[] = { 5, 7, 5, 0 };
+
+static inline uint32_t get_parallel_irq(PC87312State *s)
+{
+ int idx;
+ idx = (s->regs[REG_FAR] & FAR_PARALLEL_ADDR);
+ if (idx == 0) {
+ return (s->regs[REG_PTR] & PTR_IRQ_5_7) ? 7 : 5;
+ } else {
+ return parallel_irq[idx];
+ }
+}
+
+
+/* UARTs */
+
+static const uint32_t uart_base[2][4] = {
+ { 0x3e8, 0x338, 0x2e8, 0x220 },
+ { 0x2e8, 0x238, 0x2e0, 0x228 }
+};
+
+static inline uint32_t get_uart_iobase(PC87312State *s, int i)
+{
+ int idx;
+ idx = (s->regs[REG_FAR] >> (2 * i + 2)) & 0x3;
+ if (idx == 0) {
+ return 0x3f8;
+ } else if (idx == 1) {
+ return 0x2f8;
+ } else {
+ return uart_base[idx & 1][(s->regs[REG_FAR] & FAR_UART_3_4) >> 6];
+ }
+}
+
+static inline uint32_t get_uart_irq(PC87312State *s, int i)
+{
+ int idx;
+ idx = (s->regs[REG_FAR] >> (2 * i + 2)) & 0x3;
+ return (idx & 1) ? 3 : 4;
+}
+
+static inline bool is_uart_enabled(PC87312State *s, int i)
+{
+ return s->regs[REG_FER] & (FER_UART1_EN << i);
+}
+
+
+/* Floppy controller */
+
+static inline bool is_fdc_enabled(PC87312State *s)
+{
+ return s->regs[REG_FER] & FER_FDC_EN;
+}
+
+static inline uint32_t get_fdc_iobase(PC87312State *s)
+{
+ return (s->regs[REG_FER] & FER_FDC_ADDR) ? 0x370 : 0x3f0;
+}
+
+
+/* IDE controller */
+
+static inline bool is_ide_enabled(PC87312State *s)
+{
+ return s->regs[REG_FER] & FER_IDE_EN;
+}
+
+static inline uint32_t get_ide_iobase(PC87312State *s)
+{
+ return (s->regs[REG_FER] & FER_IDE_ADDR) ? 0x170 : 0x1f0;
+}
+
+
+static void reconfigure_devices(PC87312State *s)
+{
+ error_report("pc87312: unsupported device reconfiguration (%02x %02x %02x)",
+ s->regs[REG_FER], s->regs[REG_FAR], s->regs[REG_PTR]);
+}
+
+static void pc87312_soft_reset(PC87312State *s)
+{
+ static const uint8_t fer_init[] = {
+ 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4b, 0x4b,
+ 0x4b, 0x4b, 0x4b, 0x4b, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x49, 0x49, 0x49, 0x49, 0x07, 0x07, 0x07, 0x07,
+ 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x08, 0x00,
+ };
+ static const uint8_t far_init[] = {
+ 0x10, 0x11, 0x11, 0x39, 0x24, 0x38, 0x00, 0x01,
+ 0x01, 0x09, 0x08, 0x08, 0x10, 0x11, 0x39, 0x24,
+ 0x00, 0x01, 0x01, 0x00, 0x10, 0x11, 0x39, 0x24,
+ 0x10, 0x11, 0x11, 0x39, 0x24, 0x38, 0x10, 0x10,
+ };
+ static const uint8_t ptr_init[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ };
+
+ s->read_id_step = 0;
+ s->selected_index = REG_FER;
+
+ s->regs[REG_FER] = fer_init[s->config & 0x1f];
+ s->regs[REG_FAR] = far_init[s->config & 0x1f];
+ s->regs[REG_PTR] = ptr_init[s->config & 0x1f];
+}
+
+static void pc87312_hard_reset(PC87312State *s)
+{
+ pc87312_soft_reset(s);
+}
+
+static void pc87312_io_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ PC87312State *s = opaque;
+
+ trace_pc87312_io_write(addr, val);
+
+ if ((addr & 1) == 0) {
+ /* Index register */
+ s->read_id_step = 2;
+ s->selected_index = val;
+ } else {
+ /* Data register */
+ if (s->selected_index < 3) {
+ s->regs[s->selected_index] = val;
+ reconfigure_devices(s);
+ }
+ }
+}
+
+static uint64_t pc87312_io_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ PC87312State *s = opaque;
+ uint32_t val;
+
+ if ((addr & 1) == 0) {
+ /* Index register */
+ if (s->read_id_step++ == 0) {
+ val = 0x88;
+ } else if (s->read_id_step++ == 1) {
+ val = 0;
+ } else {
+ val = s->selected_index;
+ }
+ } else {
+ /* Data register */
+ if (s->selected_index < 3) {
+ val = s->regs[s->selected_index];
+ } else {
+ /* Invalid selected index */
+ val = 0;
+ }
+ }
+
+ trace_pc87312_io_read(addr, val);
+ return val;
+}
+
+static const MemoryRegionOps pc87312_io_ops = {
+ .read = pc87312_io_read,
+ .write = pc87312_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static int pc87312_post_load(void *opaque, int version_id)
+{
+ PC87312State *s = opaque;
+
+ reconfigure_devices(s);
+ return 0;
+}
+
+static void pc87312_reset(DeviceState *d)
+{
+ PC87312State *s = PC87312(d);
+
+ pc87312_soft_reset(s);
+}
+
+static void pc87312_realize(DeviceState *dev, Error **errp)
+{
+ PC87312State *s;
+ DeviceState *d;
+ ISADevice *isa;
+ ISABus *bus;
+ CharDriverState *chr;
+ DriveInfo *drive;
+ char name[5];
+ int i;
+
+ s = PC87312(dev);
+ isa = ISA_DEVICE(dev);
+ bus = isa_bus_from_device(isa);
+ isa_register_ioport(isa, &s->io, s->iobase);
+ pc87312_hard_reset(s);
+
+ if (is_parallel_enabled(s)) {
+ /* FIXME use a qdev chardev prop instead of parallel_hds[] */
+ chr = parallel_hds[0];
+ if (chr == NULL) {
+ chr = qemu_chr_new("par0", "null", NULL);
+ }
+ isa = isa_create(bus, "isa-parallel");
+ d = DEVICE(isa);
+ qdev_prop_set_uint32(d, "index", 0);
+ qdev_prop_set_uint32(d, "iobase", get_parallel_iobase(s));
+ qdev_prop_set_uint32(d, "irq", get_parallel_irq(s));
+ qdev_prop_set_chr(d, "chardev", chr);
+ qdev_init_nofail(d);
+ s->parallel.dev = isa;
+ trace_pc87312_info_parallel(get_parallel_iobase(s),
+ get_parallel_irq(s));
+ }
+
+ for (i = 0; i < 2; i++) {
+ if (is_uart_enabled(s, i)) {
+ /* FIXME use a qdev chardev prop instead of serial_hds[] */
+ chr = serial_hds[i];
+ if (chr == NULL) {
+ snprintf(name, sizeof(name), "ser%d", i);
+ chr = qemu_chr_new(name, "null", NULL);
+ }
+ isa = isa_create(bus, "isa-serial");
+ d = DEVICE(isa);
+ qdev_prop_set_uint32(d, "index", i);
+ qdev_prop_set_uint32(d, "iobase", get_uart_iobase(s, i));
+ qdev_prop_set_uint32(d, "irq", get_uart_irq(s, i));
+ qdev_prop_set_chr(d, "chardev", chr);
+ qdev_init_nofail(d);
+ s->uart[i].dev = isa;
+ trace_pc87312_info_serial(i, get_uart_iobase(s, i),
+ get_uart_irq(s, i));
+ }
+ }
+
+ if (is_fdc_enabled(s)) {
+ isa = isa_create(bus, "isa-fdc");
+ d = DEVICE(isa);
+ qdev_prop_set_uint32(d, "iobase", get_fdc_iobase(s));
+ qdev_prop_set_uint32(d, "irq", 6);
+ /* FIXME use a qdev drive property instead of drive_get() */
+ drive = drive_get(IF_FLOPPY, 0, 0);
+ if (drive != NULL) {
+ qdev_prop_set_drive_nofail(d, "driveA",
+ blk_by_legacy_dinfo(drive));
+ }
+ /* FIXME use a qdev drive property instead of drive_get() */
+ drive = drive_get(IF_FLOPPY, 0, 1);
+ if (drive != NULL) {
+ qdev_prop_set_drive_nofail(d, "driveB",
+ blk_by_legacy_dinfo(drive));
+ }
+ qdev_init_nofail(d);
+ s->fdc.dev = isa;
+ trace_pc87312_info_floppy(get_fdc_iobase(s));
+ }
+
+ if (is_ide_enabled(s)) {
+ isa = isa_create(bus, "isa-ide");
+ d = DEVICE(isa);
+ qdev_prop_set_uint32(d, "iobase", get_ide_iobase(s));
+ qdev_prop_set_uint32(d, "iobase2", get_ide_iobase(s) + 0x206);
+ qdev_prop_set_uint32(d, "irq", 14);
+ qdev_init_nofail(d);
+ s->ide.dev = isa;
+ trace_pc87312_info_ide(get_ide_iobase(s));
+ }
+}
+
+static void pc87312_initfn(Object *obj)
+{
+ PC87312State *s = PC87312(obj);
+
+ memory_region_init_io(&s->io, obj, &pc87312_io_ops, s, "pc87312", 2);
+}
+
+static const VMStateDescription vmstate_pc87312 = {
+ .name = "pc87312",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pc87312_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(read_id_step, PC87312State),
+ VMSTATE_UINT8(selected_index, PC87312State),
+ VMSTATE_UINT8_ARRAY(regs, PC87312State, 3),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property pc87312_properties[] = {
+ DEFINE_PROP_UINT32("iobase", PC87312State, iobase, 0x398),
+ DEFINE_PROP_UINT8("config", PC87312State, config, 1),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void pc87312_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pc87312_realize;
+ dc->reset = pc87312_reset;
+ dc->vmsd = &vmstate_pc87312;
+ dc->props = pc87312_properties;
+}
+
+static const TypeInfo pc87312_type_info = {
+ .name = TYPE_PC87312,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(PC87312State),
+ .instance_init = pc87312_initfn,
+ .class_init = pc87312_class_init,
+};
+
+static void pc87312_register_types(void)
+{
+ type_register_static(&pc87312_type_info);
+}
+
+type_init(pc87312_register_types)
diff --git a/hw/isa/piix4.c b/hw/isa/piix4.c
new file mode 100644
index 00000000..2c59e91f
--- /dev/null
+++ b/hw/isa/piix4.c
@@ -0,0 +1,139 @@
+/*
+ * QEMU PIIX4 PCI Bridge Emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/pci/pci.h"
+#include "hw/isa/isa.h"
+#include "hw/sysbus.h"
+
+PCIDevice *piix4_dev;
+
+typedef struct PIIX4State {
+ PCIDevice dev;
+} PIIX4State;
+
+#define TYPE_PIIX4_PCI_DEVICE "PIIX4"
+#define PIIX4_PCI_DEVICE(obj) \
+ OBJECT_CHECK(PIIX4State, (obj), TYPE_PIIX4_PCI_DEVICE)
+
+static void piix4_reset(void *opaque)
+{
+ PIIX4State *d = opaque;
+ uint8_t *pci_conf = d->dev.config;
+
+ pci_conf[0x04] = 0x07; // master, memory and I/O
+ pci_conf[0x05] = 0x00;
+ pci_conf[0x06] = 0x00;
+ pci_conf[0x07] = 0x02; // PCI_status_devsel_medium
+ pci_conf[0x4c] = 0x4d;
+ pci_conf[0x4e] = 0x03;
+ pci_conf[0x4f] = 0x00;
+ pci_conf[0x60] = 0x0a; // PCI A -> IRQ 10
+ pci_conf[0x61] = 0x0a; // PCI B -> IRQ 10
+ pci_conf[0x62] = 0x0b; // PCI C -> IRQ 11
+ pci_conf[0x63] = 0x0b; // PCI D -> IRQ 11
+ pci_conf[0x69] = 0x02;
+ pci_conf[0x70] = 0x80;
+ pci_conf[0x76] = 0x0c;
+ pci_conf[0x77] = 0x0c;
+ pci_conf[0x78] = 0x02;
+ pci_conf[0x79] = 0x00;
+ pci_conf[0x80] = 0x00;
+ pci_conf[0x82] = 0x00;
+ pci_conf[0xa0] = 0x08;
+ pci_conf[0xa2] = 0x00;
+ pci_conf[0xa3] = 0x00;
+ pci_conf[0xa4] = 0x00;
+ pci_conf[0xa5] = 0x00;
+ pci_conf[0xa6] = 0x00;
+ pci_conf[0xa7] = 0x00;
+ pci_conf[0xa8] = 0x0f;
+ pci_conf[0xaa] = 0x00;
+ pci_conf[0xab] = 0x00;
+ pci_conf[0xac] = 0x00;
+ pci_conf[0xae] = 0x00;
+}
+
+static const VMStateDescription vmstate_piix4 = {
+ .name = "PIIX4",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PIIX4State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void piix4_realize(PCIDevice *dev, Error **errp)
+{
+ PIIX4State *d = PIIX4_PCI_DEVICE(dev);
+
+ isa_bus_new(DEVICE(d), pci_address_space(dev),
+ pci_address_space_io(dev));
+ piix4_dev = &d->dev;
+ qemu_register_reset(piix4_reset, d);
+}
+
+int piix4_init(PCIBus *bus, ISABus **isa_bus, int devfn)
+{
+ PCIDevice *d;
+
+ d = pci_create_simple_multifunction(bus, devfn, true, "PIIX4");
+ *isa_bus = ISA_BUS(qdev_get_child_bus(DEVICE(d), "isa.0"));
+ return d->devfn;
+}
+
+static void piix4_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = piix4_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82371AB_0;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ dc->desc = "ISA bridge";
+ dc->vmsd = &vmstate_piix4;
+ /*
+ * Reason: part of PIIX4 southbridge, needs to be wired up,
+ * e.g. by mips_malta_init()
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo piix4_info = {
+ .name = TYPE_PIIX4_PCI_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PIIX4State),
+ .class_init = piix4_class_init,
+};
+
+static void piix4_register_types(void)
+{
+ type_register_static(&piix4_info);
+}
+
+type_init(piix4_register_types)
diff --git a/hw/isa/vt82c686.c b/hw/isa/vt82c686.c
new file mode 100644
index 00000000..252e1d71
--- /dev/null
+++ b/hw/isa/vt82c686.c
@@ -0,0 +1,511 @@
+/*
+ * VT82C686B south bridge support
+ *
+ * Copyright (c) 2008 yajin (yajin@vm-kernel.org)
+ * Copyright (c) 2009 chenming (chenming@rdc.faw.com.cn)
+ * Copyright (c) 2010 Huacai Chen (zltjiangshi@gmail.com)
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/vt82c686.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus.h"
+#include "hw/pci/pci.h"
+#include "hw/isa/isa.h"
+#include "hw/sysbus.h"
+#include "hw/mips/mips.h"
+#include "hw/isa/apm.h"
+#include "hw/acpi/acpi.h"
+#include "hw/i2c/pm_smbus.h"
+#include "sysemu/sysemu.h"
+#include "qemu/timer.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_VT82C686B
+
+#ifdef DEBUG_VT82C686B
+#define DPRINTF(fmt, ...) fprintf(stderr, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+typedef struct SuperIOConfig
+{
+ uint8_t config[0x100];
+ uint8_t index;
+ uint8_t data;
+} SuperIOConfig;
+
+typedef struct VT82C686BState {
+ PCIDevice dev;
+ MemoryRegion superio;
+ SuperIOConfig superio_conf;
+} VT82C686BState;
+
+#define TYPE_VT82C686B_DEVICE "VT82C686B"
+#define VT82C686B_DEVICE(obj) \
+ OBJECT_CHECK(VT82C686BState, (obj), TYPE_VT82C686B_DEVICE)
+
+static void superio_ioport_writeb(void *opaque, hwaddr addr, uint64_t data,
+ unsigned size)
+{
+ SuperIOConfig *superio_conf = opaque;
+
+ DPRINTF("superio_ioport_writeb address 0x%x val 0x%x\n", addr, data);
+ if (addr == 0x3f0) {
+ superio_conf->index = data & 0xff;
+ } else {
+ bool can_write = true;
+ /* 0x3f1 */
+ switch (superio_conf->index) {
+ case 0x00 ... 0xdf:
+ case 0xe4:
+ case 0xe5:
+ case 0xe9 ... 0xed:
+ case 0xf3:
+ case 0xf5:
+ case 0xf7:
+ case 0xf9 ... 0xfb:
+ case 0xfd ... 0xff:
+ can_write = false;
+ break;
+ case 0xe7:
+ if ((data & 0xff) != 0xfe) {
+ DPRINTF("change uart 1 base. unsupported yet\n");
+ can_write = false;
+ }
+ break;
+ case 0xe8:
+ if ((data & 0xff) != 0xbe) {
+ DPRINTF("change uart 2 base. unsupported yet\n");
+ can_write = false;
+ }
+ break;
+ default:
+ break;
+
+ }
+ if (can_write) {
+ superio_conf->config[superio_conf->index] = data & 0xff;
+ }
+ }
+}
+
+static uint64_t superio_ioport_readb(void *opaque, hwaddr addr, unsigned size)
+{
+ SuperIOConfig *superio_conf = opaque;
+
+ DPRINTF("superio_ioport_readb address 0x%x\n", addr);
+ return (superio_conf->config[superio_conf->index]);
+}
+
+static const MemoryRegionOps superio_ops = {
+ .read = superio_ioport_readb,
+ .write = superio_ioport_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void vt82c686b_reset(void * opaque)
+{
+ PCIDevice *d = opaque;
+ uint8_t *pci_conf = d->config;
+ VT82C686BState *vt82c = VT82C686B_DEVICE(d);
+
+ pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
+ pci_set_word(pci_conf + PCI_COMMAND, PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER | PCI_COMMAND_SPECIAL);
+ pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_DEVSEL_MEDIUM);
+
+ pci_conf[0x48] = 0x01; /* Miscellaneous Control 3 */
+ pci_conf[0x4a] = 0x04; /* IDE interrupt Routing */
+ pci_conf[0x4f] = 0x03; /* DMA/Master Mem Access Control 3 */
+ pci_conf[0x50] = 0x2d; /* PnP DMA Request Control */
+ pci_conf[0x59] = 0x04;
+ pci_conf[0x5a] = 0x04; /* KBC/RTC Control*/
+ pci_conf[0x5f] = 0x04;
+ pci_conf[0x77] = 0x10; /* GPIO Control 1/2/3/4 */
+
+ vt82c->superio_conf.config[0xe0] = 0x3c;
+ vt82c->superio_conf.config[0xe2] = 0x03;
+ vt82c->superio_conf.config[0xe3] = 0xfc;
+ vt82c->superio_conf.config[0xe6] = 0xde;
+ vt82c->superio_conf.config[0xe7] = 0xfe;
+ vt82c->superio_conf.config[0xe8] = 0xbe;
+}
+
+/* write config pci function0 registers. PCI-ISA bridge */
+static void vt82c686b_write_config(PCIDevice * d, uint32_t address,
+ uint32_t val, int len)
+{
+ VT82C686BState *vt686 = VT82C686B_DEVICE(d);
+
+ DPRINTF("vt82c686b_write_config address 0x%x val 0x%x len 0x%x\n",
+ address, val, len);
+
+ pci_default_write_config(d, address, val, len);
+ if (address == 0x85) { /* enable or disable super IO configure */
+ memory_region_set_enabled(&vt686->superio, val & 0x2);
+ }
+}
+
+#define ACPI_DBG_IO_ADDR 0xb044
+
+typedef struct VT686PMState {
+ PCIDevice dev;
+ MemoryRegion io;
+ ACPIREGS ar;
+ APMState apm;
+ PMSMBus smb;
+ uint32_t smb_io_base;
+} VT686PMState;
+
+typedef struct VT686AC97State {
+ PCIDevice dev;
+} VT686AC97State;
+
+typedef struct VT686MC97State {
+ PCIDevice dev;
+} VT686MC97State;
+
+#define TYPE_VT82C686B_PM_DEVICE "VT82C686B_PM"
+#define VT82C686B_PM_DEVICE(obj) \
+ OBJECT_CHECK(VT686PMState, (obj), TYPE_VT82C686B_PM_DEVICE)
+
+#define TYPE_VT82C686B_MC97_DEVICE "VT82C686B_MC97"
+#define VT82C686B_MC97_DEVICE(obj) \
+ OBJECT_CHECK(VT686MC97State, (obj), TYPE_VT82C686B_MC97_DEVICE)
+
+#define TYPE_VT82C686B_AC97_DEVICE "VT82C686B_AC97"
+#define VT82C686B_AC97_DEVICE(obj) \
+ OBJECT_CHECK(VT686AC97State, (obj), TYPE_VT82C686B_AC97_DEVICE)
+
+static void pm_update_sci(VT686PMState *s)
+{
+ int sci_level, pmsts;
+
+ pmsts = acpi_pm1_evt_get_sts(&s->ar);
+ sci_level = (((pmsts & s->ar.pm1.evt.en) &
+ (ACPI_BITMASK_RT_CLOCK_ENABLE |
+ ACPI_BITMASK_POWER_BUTTON_ENABLE |
+ ACPI_BITMASK_GLOBAL_LOCK_ENABLE |
+ ACPI_BITMASK_TIMER_ENABLE)) != 0);
+ pci_set_irq(&s->dev, sci_level);
+ /* schedule a timer interruption if needed */
+ acpi_pm_tmr_update(&s->ar, (s->ar.pm1.evt.en & ACPI_BITMASK_TIMER_ENABLE) &&
+ !(pmsts & ACPI_BITMASK_TIMER_STATUS));
+}
+
+static void pm_tmr_timer(ACPIREGS *ar)
+{
+ VT686PMState *s = container_of(ar, VT686PMState, ar);
+ pm_update_sci(s);
+}
+
+static void pm_io_space_update(VT686PMState *s)
+{
+ uint32_t pm_io_base;
+
+ pm_io_base = pci_get_long(s->dev.config + 0x40);
+ pm_io_base &= 0xffc0;
+
+ memory_region_transaction_begin();
+ memory_region_set_enabled(&s->io, s->dev.config[0x80] & 1);
+ memory_region_set_address(&s->io, pm_io_base);
+ memory_region_transaction_commit();
+}
+
+static void pm_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ DPRINTF("pm_write_config address 0x%x val 0x%x len 0x%x\n",
+ address, val, len);
+ pci_default_write_config(d, address, val, len);
+}
+
+static int vmstate_acpi_post_load(void *opaque, int version_id)
+{
+ VT686PMState *s = opaque;
+
+ pm_io_space_update(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_acpi = {
+ .name = "vt82c686b_pm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = vmstate_acpi_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, VT686PMState),
+ VMSTATE_UINT16(ar.pm1.evt.sts, VT686PMState),
+ VMSTATE_UINT16(ar.pm1.evt.en, VT686PMState),
+ VMSTATE_UINT16(ar.pm1.cnt.cnt, VT686PMState),
+ VMSTATE_STRUCT(apm, VT686PMState, 0, vmstate_apm, APMState),
+ VMSTATE_TIMER_PTR(ar.tmr.timer, VT686PMState),
+ VMSTATE_INT64(ar.tmr.overflow_time, VT686PMState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/*
+ * TODO: vt82c686b_ac97_init() and vt82c686b_mc97_init()
+ * just register a PCI device now, functionalities will be implemented later.
+ */
+
+static void vt82c686b_ac97_realize(PCIDevice *dev, Error **errp)
+{
+ VT686AC97State *s = VT82C686B_AC97_DEVICE(dev);
+ uint8_t *pci_conf = s->dev.config;
+
+ pci_set_word(pci_conf + PCI_COMMAND, PCI_COMMAND_INVALIDATE |
+ PCI_COMMAND_PARITY);
+ pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_CAP_LIST |
+ PCI_STATUS_DEVSEL_MEDIUM);
+ pci_set_long(pci_conf + PCI_INTERRUPT_PIN, 0x03);
+}
+
+void vt82c686b_ac97_init(PCIBus *bus, int devfn)
+{
+ PCIDevice *dev;
+
+ dev = pci_create(bus, devfn, TYPE_VT82C686B_AC97_DEVICE);
+ qdev_init_nofail(&dev->qdev);
+}
+
+static void via_ac97_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = vt82c686b_ac97_realize;
+ k->vendor_id = PCI_VENDOR_ID_VIA;
+ k->device_id = PCI_DEVICE_ID_VIA_AC97;
+ k->revision = 0x50;
+ k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->desc = "AC97";
+}
+
+static const TypeInfo via_ac97_info = {
+ .name = TYPE_VT82C686B_AC97_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(VT686AC97State),
+ .class_init = via_ac97_class_init,
+};
+
+static void vt82c686b_mc97_realize(PCIDevice *dev, Error **errp)
+{
+ VT686MC97State *s = VT82C686B_MC97_DEVICE(dev);
+ uint8_t *pci_conf = s->dev.config;
+
+ pci_set_word(pci_conf + PCI_COMMAND, PCI_COMMAND_INVALIDATE |
+ PCI_COMMAND_VGA_PALETTE);
+ pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_DEVSEL_MEDIUM);
+ pci_set_long(pci_conf + PCI_INTERRUPT_PIN, 0x03);
+}
+
+void vt82c686b_mc97_init(PCIBus *bus, int devfn)
+{
+ PCIDevice *dev;
+
+ dev = pci_create(bus, devfn, TYPE_VT82C686B_MC97_DEVICE);
+ qdev_init_nofail(&dev->qdev);
+}
+
+static void via_mc97_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = vt82c686b_mc97_realize;
+ k->vendor_id = PCI_VENDOR_ID_VIA;
+ k->device_id = PCI_DEVICE_ID_VIA_MC97;
+ k->class_id = PCI_CLASS_COMMUNICATION_OTHER;
+ k->revision = 0x30;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->desc = "MC97";
+}
+
+static const TypeInfo via_mc97_info = {
+ .name = TYPE_VT82C686B_MC97_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(VT686MC97State),
+ .class_init = via_mc97_class_init,
+};
+
+/* vt82c686 pm init */
+static void vt82c686b_pm_realize(PCIDevice *dev, Error **errp)
+{
+ VT686PMState *s = VT82C686B_PM_DEVICE(dev);
+ uint8_t *pci_conf;
+
+ pci_conf = s->dev.config;
+ pci_set_word(pci_conf + PCI_COMMAND, 0);
+ pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_FAST_BACK |
+ PCI_STATUS_DEVSEL_MEDIUM);
+
+ /* 0x48-0x4B is Power Management I/O Base */
+ pci_set_long(pci_conf + 0x48, 0x00000001);
+
+ /* SMB ports:0xeee0~0xeeef */
+ s->smb_io_base =((s->smb_io_base & 0xfff0) + 0x0);
+ pci_conf[0x90] = s->smb_io_base | 1;
+ pci_conf[0x91] = s->smb_io_base >> 8;
+ pci_conf[0xd2] = 0x90;
+ pm_smbus_init(&s->dev.qdev, &s->smb);
+ memory_region_add_subregion(get_system_io(), s->smb_io_base, &s->smb.io);
+
+ apm_init(dev, &s->apm, NULL, s);
+
+ memory_region_init(&s->io, OBJECT(dev), "vt82c686-pm", 64);
+ memory_region_set_enabled(&s->io, false);
+ memory_region_add_subregion(get_system_io(), 0, &s->io);
+
+ acpi_pm_tmr_init(&s->ar, pm_tmr_timer, &s->io);
+ acpi_pm1_evt_init(&s->ar, pm_tmr_timer, &s->io);
+ acpi_pm1_cnt_init(&s->ar, &s->io, false, false, 2);
+}
+
+I2CBus *vt82c686b_pm_init(PCIBus *bus, int devfn, uint32_t smb_io_base,
+ qemu_irq sci_irq)
+{
+ PCIDevice *dev;
+ VT686PMState *s;
+
+ dev = pci_create(bus, devfn, TYPE_VT82C686B_PM_DEVICE);
+ qdev_prop_set_uint32(&dev->qdev, "smb_io_base", smb_io_base);
+
+ s = VT82C686B_PM_DEVICE(dev);
+
+ qdev_init_nofail(&dev->qdev);
+
+ return s->smb.smbus;
+}
+
+static Property via_pm_properties[] = {
+ DEFINE_PROP_UINT32("smb_io_base", VT686PMState, smb_io_base, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void via_pm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = vt82c686b_pm_realize;
+ k->config_write = pm_write_config;
+ k->vendor_id = PCI_VENDOR_ID_VIA;
+ k->device_id = PCI_DEVICE_ID_VIA_ACPI;
+ k->class_id = PCI_CLASS_BRIDGE_OTHER;
+ k->revision = 0x40;
+ dc->desc = "PM";
+ dc->vmsd = &vmstate_acpi;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->props = via_pm_properties;
+}
+
+static const TypeInfo via_pm_info = {
+ .name = TYPE_VT82C686B_PM_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(VT686PMState),
+ .class_init = via_pm_class_init,
+};
+
+static const VMStateDescription vmstate_via = {
+ .name = "vt82c686b",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, VT82C686BState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* init the PCI-to-ISA bridge */
+static void vt82c686b_realize(PCIDevice *d, Error **errp)
+{
+ VT82C686BState *vt82c = VT82C686B_DEVICE(d);
+ uint8_t *pci_conf;
+ ISABus *isa_bus;
+ uint8_t *wmask;
+ int i;
+
+ isa_bus = isa_bus_new(DEVICE(d), get_system_memory(),
+ pci_address_space_io(d));
+
+ pci_conf = d->config;
+ pci_config_set_prog_interface(pci_conf, 0x0);
+
+ wmask = d->wmask;
+ for (i = 0x00; i < 0xff; i++) {
+ if (i<=0x03 || (i>=0x08 && i<=0x3f)) {
+ wmask[i] = 0x00;
+ }
+ }
+
+ memory_region_init_io(&vt82c->superio, OBJECT(d), &superio_ops,
+ &vt82c->superio_conf, "superio", 2);
+ memory_region_set_enabled(&vt82c->superio, false);
+ /* The floppy also uses 0x3f0 and 0x3f1.
+ * But we do not emulate a floppy, so just set it here. */
+ memory_region_add_subregion(isa_bus->address_space_io, 0x3f0,
+ &vt82c->superio);
+
+ qemu_register_reset(vt82c686b_reset, d);
+}
+
+ISABus *vt82c686b_init(PCIBus *bus, int devfn)
+{
+ PCIDevice *d;
+
+ d = pci_create_simple_multifunction(bus, devfn, true,
+ TYPE_VT82C686B_DEVICE);
+
+ return ISA_BUS(qdev_get_child_bus(DEVICE(d), "isa.0"));
+}
+
+static void via_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = vt82c686b_realize;
+ k->config_write = vt82c686b_write_config;
+ k->vendor_id = PCI_VENDOR_ID_VIA;
+ k->device_id = PCI_DEVICE_ID_VIA_ISA_BRIDGE;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ k->revision = 0x40;
+ dc->desc = "ISA bridge";
+ dc->vmsd = &vmstate_via;
+ /*
+ * Reason: part of VIA VT82C686 southbridge, needs to be wired up,
+ * e.g. by mips_fulong2e_init()
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo via_info = {
+ .name = TYPE_VT82C686B_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(VT82C686BState),
+ .class_init = via_class_init,
+};
+
+static void vt82c686b_register_types(void)
+{
+ type_register_static(&via_ac97_info);
+ type_register_static(&via_mc97_info);
+ type_register_static(&via_pm_info);
+ type_register_static(&via_info);
+}
+
+type_init(vt82c686b_register_types)
diff --git a/hw/lm32/Makefile.objs b/hw/lm32/Makefile.objs
new file mode 100644
index 00000000..ea6418ae
--- /dev/null
+++ b/hw/lm32/Makefile.objs
@@ -0,0 +1,3 @@
+# LM32 boards
+obj-y += lm32_boards.o
+obj-y += milkymist.o
diff --git a/hw/lm32/lm32.h b/hw/lm32/lm32.h
new file mode 100644
index 00000000..18aa6fdc
--- /dev/null
+++ b/hw/lm32/lm32.h
@@ -0,0 +1,29 @@
+#ifndef HW_LM32_H
+#define HW_LM32_H 1
+
+#include "hw/char/lm32_juart.h"
+
+static inline DeviceState *lm32_pic_init(qemu_irq cpu_irq)
+{
+ DeviceState *dev;
+ SysBusDevice *d;
+
+ dev = qdev_create(NULL, "lm32-pic");
+ qdev_init_nofail(dev);
+ d = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(d, 0, cpu_irq);
+
+ return dev;
+}
+
+static inline DeviceState *lm32_juart_init(void)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, TYPE_LM32_JUART);
+ qdev_init_nofail(dev);
+
+ return dev;
+}
+
+#endif
diff --git a/hw/lm32/lm32_boards.c b/hw/lm32/lm32_boards.c
new file mode 100644
index 00000000..70f48d3b
--- /dev/null
+++ b/hw/lm32/lm32_boards.c
@@ -0,0 +1,315 @@
+/*
+ * QEMU models for LatticeMico32 uclinux and evr32 boards.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/block/flash.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "sysemu/block-backend.h"
+#include "elf.h"
+#include "lm32_hwsetup.h"
+#include "lm32.h"
+#include "exec/address-spaces.h"
+
+typedef struct {
+ LM32CPU *cpu;
+ hwaddr bootstrap_pc;
+ hwaddr flash_base;
+ hwaddr hwsetup_base;
+ hwaddr initrd_base;
+ size_t initrd_size;
+ hwaddr cmdline_base;
+} ResetInfo;
+
+static void cpu_irq_handler(void *opaque, int irq, int level)
+{
+ LM32CPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+
+ if (level) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ ResetInfo *reset_info = opaque;
+ CPULM32State *env = &reset_info->cpu->env;
+
+ cpu_reset(CPU(reset_info->cpu));
+
+ /* init defaults */
+ env->pc = (uint32_t)reset_info->bootstrap_pc;
+ env->regs[R_R1] = (uint32_t)reset_info->hwsetup_base;
+ env->regs[R_R2] = (uint32_t)reset_info->cmdline_base;
+ env->regs[R_R3] = (uint32_t)reset_info->initrd_base;
+ env->regs[R_R4] = (uint32_t)(reset_info->initrd_base +
+ reset_info->initrd_size);
+ env->eba = reset_info->flash_base;
+ env->deba = reset_info->flash_base;
+}
+
+static void lm32_evr_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ LM32CPU *cpu;
+ CPULM32State *env;
+ DriveInfo *dinfo;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *phys_ram = g_new(MemoryRegion, 1);
+ qemu_irq irq[32];
+ ResetInfo *reset_info;
+ int i;
+
+ /* memory map */
+ hwaddr flash_base = 0x04000000;
+ size_t flash_sector_size = 256 * 1024;
+ size_t flash_size = 32 * 1024 * 1024;
+ hwaddr ram_base = 0x08000000;
+ size_t ram_size = 64 * 1024 * 1024;
+ hwaddr timer0_base = 0x80002000;
+ hwaddr uart0_base = 0x80006000;
+ hwaddr timer1_base = 0x8000a000;
+ int uart0_irq = 0;
+ int timer0_irq = 1;
+ int timer1_irq = 3;
+
+ reset_info = g_malloc0(sizeof(ResetInfo));
+
+ if (cpu_model == NULL) {
+ cpu_model = "lm32-full";
+ }
+ cpu = cpu_lm32_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "qemu: unable to find CPU '%s'\n", cpu_model);
+ exit(1);
+ }
+
+ env = &cpu->env;
+ reset_info->cpu = cpu;
+
+ reset_info->flash_base = flash_base;
+
+ memory_region_allocate_system_memory(phys_ram, NULL, "lm32_evr.sdram",
+ ram_size);
+ memory_region_add_subregion(address_space_mem, ram_base, phys_ram);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ /* Spansion S29NS128P */
+ pflash_cfi02_register(flash_base, NULL, "lm32_evr.flash", flash_size,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ flash_sector_size, flash_size / flash_sector_size,
+ 1, 2, 0x01, 0x7e, 0x43, 0x00, 0x555, 0x2aa, 1);
+
+ /* create irq lines */
+ env->pic_state = lm32_pic_init(qemu_allocate_irq(cpu_irq_handler, cpu, 0));
+ for (i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in(env->pic_state, i);
+ }
+
+ sysbus_create_simple("lm32-uart", uart0_base, irq[uart0_irq]);
+ sysbus_create_simple("lm32-timer", timer0_base, irq[timer0_irq]);
+ sysbus_create_simple("lm32-timer", timer1_base, irq[timer1_irq]);
+
+ /* make sure juart isn't the first chardev */
+ env->juart_state = lm32_juart_init();
+
+ reset_info->bootstrap_pc = flash_base;
+
+ if (kernel_filename) {
+ uint64_t entry;
+ int kernel_size;
+
+ kernel_size = load_elf(kernel_filename, NULL, NULL, &entry, NULL, NULL,
+ 1, ELF_MACHINE, 0);
+ reset_info->bootstrap_pc = entry;
+
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename, ram_base,
+ ram_size);
+ reset_info->bootstrap_pc = ram_base;
+ }
+
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ }
+
+ qemu_register_reset(main_cpu_reset, reset_info);
+}
+
+static void lm32_uclinux_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ LM32CPU *cpu;
+ CPULM32State *env;
+ DriveInfo *dinfo;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *phys_ram = g_new(MemoryRegion, 1);
+ qemu_irq irq[32];
+ HWSetup *hw;
+ ResetInfo *reset_info;
+ int i;
+
+ /* memory map */
+ hwaddr flash_base = 0x04000000;
+ size_t flash_sector_size = 256 * 1024;
+ size_t flash_size = 32 * 1024 * 1024;
+ hwaddr ram_base = 0x08000000;
+ size_t ram_size = 64 * 1024 * 1024;
+ hwaddr uart0_base = 0x80000000;
+ hwaddr timer0_base = 0x80002000;
+ hwaddr timer1_base = 0x80010000;
+ hwaddr timer2_base = 0x80012000;
+ int uart0_irq = 0;
+ int timer0_irq = 1;
+ int timer1_irq = 20;
+ int timer2_irq = 21;
+ hwaddr hwsetup_base = 0x0bffe000;
+ hwaddr cmdline_base = 0x0bfff000;
+ hwaddr initrd_base = 0x08400000;
+ size_t initrd_max = 0x01000000;
+
+ reset_info = g_malloc0(sizeof(ResetInfo));
+
+ if (cpu_model == NULL) {
+ cpu_model = "lm32-full";
+ }
+ cpu = cpu_lm32_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "qemu: unable to find CPU '%s'\n", cpu_model);
+ exit(1);
+ }
+
+ env = &cpu->env;
+ reset_info->cpu = cpu;
+
+ reset_info->flash_base = flash_base;
+
+ memory_region_allocate_system_memory(phys_ram, NULL,
+ "lm32_uclinux.sdram", ram_size);
+ memory_region_add_subregion(address_space_mem, ram_base, phys_ram);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ /* Spansion S29NS128P */
+ pflash_cfi02_register(flash_base, NULL, "lm32_uclinux.flash", flash_size,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ flash_sector_size, flash_size / flash_sector_size,
+ 1, 2, 0x01, 0x7e, 0x43, 0x00, 0x555, 0x2aa, 1);
+
+ /* create irq lines */
+ env->pic_state = lm32_pic_init(qemu_allocate_irq(cpu_irq_handler, env, 0));
+ for (i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in(env->pic_state, i);
+ }
+
+ sysbus_create_simple("lm32-uart", uart0_base, irq[uart0_irq]);
+ sysbus_create_simple("lm32-timer", timer0_base, irq[timer0_irq]);
+ sysbus_create_simple("lm32-timer", timer1_base, irq[timer1_irq]);
+ sysbus_create_simple("lm32-timer", timer2_base, irq[timer2_irq]);
+
+ /* make sure juart isn't the first chardev */
+ env->juart_state = lm32_juart_init();
+
+ reset_info->bootstrap_pc = flash_base;
+
+ if (kernel_filename) {
+ uint64_t entry;
+ int kernel_size;
+
+ kernel_size = load_elf(kernel_filename, NULL, NULL, &entry, NULL, NULL,
+ 1, ELF_MACHINE, 0);
+ reset_info->bootstrap_pc = entry;
+
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename, ram_base,
+ ram_size);
+ reset_info->bootstrap_pc = ram_base;
+ }
+
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ }
+
+ /* generate a rom with the hardware description */
+ hw = hwsetup_init();
+ hwsetup_add_cpu(hw, "LM32", 75000000);
+ hwsetup_add_flash(hw, "flash", flash_base, flash_size);
+ hwsetup_add_ddr_sdram(hw, "ddr_sdram", ram_base, ram_size);
+ hwsetup_add_timer(hw, "timer0", timer0_base, timer0_irq);
+ hwsetup_add_timer(hw, "timer1_dev_only", timer1_base, timer1_irq);
+ hwsetup_add_timer(hw, "timer2_dev_only", timer2_base, timer2_irq);
+ hwsetup_add_uart(hw, "uart", uart0_base, uart0_irq);
+ hwsetup_add_trailer(hw);
+ hwsetup_create_rom(hw, hwsetup_base);
+ hwsetup_free(hw);
+
+ reset_info->hwsetup_base = hwsetup_base;
+
+ if (kernel_cmdline && strlen(kernel_cmdline)) {
+ pstrcpy_targphys("cmdline", cmdline_base, TARGET_PAGE_SIZE,
+ kernel_cmdline);
+ reset_info->cmdline_base = cmdline_base;
+ }
+
+ if (initrd_filename) {
+ size_t initrd_size;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ initrd_max);
+ reset_info->initrd_base = initrd_base;
+ reset_info->initrd_size = initrd_size;
+ }
+
+ qemu_register_reset(main_cpu_reset, reset_info);
+}
+
+static QEMUMachine lm32_evr_machine = {
+ .name = "lm32-evr",
+ .desc = "LatticeMico32 EVR32 eval system",
+ .init = lm32_evr_init,
+ .is_default = 1,
+};
+
+static QEMUMachine lm32_uclinux_machine = {
+ .name = "lm32-uclinux",
+ .desc = "lm32 platform for uClinux and u-boot by Theobroma Systems",
+ .init = lm32_uclinux_init,
+ .is_default = 0,
+};
+
+static void lm32_machine_init(void)
+{
+ qemu_register_machine(&lm32_uclinux_machine);
+ qemu_register_machine(&lm32_evr_machine);
+}
+
+machine_init(lm32_machine_init);
diff --git a/hw/lm32/lm32_hwsetup.h b/hw/lm32/lm32_hwsetup.h
new file mode 100644
index 00000000..838754d5
--- /dev/null
+++ b/hw/lm32/lm32_hwsetup.h
@@ -0,0 +1,179 @@
+/*
+ * LatticeMico32 hwsetup helper functions.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * These are helper functions for creating the hardware description blob used
+ * in the Theobroma's uClinux port.
+ */
+
+#ifndef QEMU_HW_LM32_HWSETUP_H
+#define QEMU_HW_LM32_HWSETUP_H
+
+#include "qemu-common.h"
+#include "hw/loader.h"
+
+typedef struct {
+ void *data;
+ void *ptr;
+} HWSetup;
+
+enum hwsetup_tag {
+ HWSETUP_TAG_EOL = 0,
+ HWSETUP_TAG_CPU = 1,
+ HWSETUP_TAG_ASRAM = 2,
+ HWSETUP_TAG_FLASH = 3,
+ HWSETUP_TAG_SDRAM = 4,
+ HWSETUP_TAG_OCM = 5,
+ HWSETUP_TAG_DDR_SDRAM = 6,
+ HWSETUP_TAG_DDR2_SDRAM = 7,
+ HWSETUP_TAG_TIMER = 8,
+ HWSETUP_TAG_UART = 9,
+ HWSETUP_TAG_GPIO = 10,
+ HWSETUP_TAG_TRISPEEDMAC = 11,
+ HWSETUP_TAG_I2CM = 12,
+ HWSETUP_TAG_LEDS = 13,
+ HWSETUP_TAG_7SEG = 14,
+ HWSETUP_TAG_SPI_S = 15,
+ HWSETUP_TAG_SPI_M = 16,
+};
+
+static inline HWSetup *hwsetup_init(void)
+{
+ HWSetup *hw;
+
+ hw = g_malloc(sizeof(HWSetup));
+ hw->data = g_malloc0(TARGET_PAGE_SIZE);
+ hw->ptr = hw->data;
+
+ return hw;
+}
+
+static inline void hwsetup_free(HWSetup *hw)
+{
+ g_free(hw->data);
+ g_free(hw);
+}
+
+static inline void hwsetup_create_rom(HWSetup *hw,
+ hwaddr base)
+{
+ rom_add_blob("hwsetup", hw->data, TARGET_PAGE_SIZE,
+ TARGET_PAGE_SIZE, base, NULL, NULL, NULL);
+}
+
+static inline void hwsetup_add_u8(HWSetup *hw, uint8_t u)
+{
+ stb_p(hw->ptr, u);
+ hw->ptr += 1;
+}
+
+static inline void hwsetup_add_u32(HWSetup *hw, uint32_t u)
+{
+ stl_p(hw->ptr, u);
+ hw->ptr += 4;
+}
+
+static inline void hwsetup_add_tag(HWSetup *hw, enum hwsetup_tag t)
+{
+ stl_p(hw->ptr, t);
+ hw->ptr += 4;
+}
+
+static inline void hwsetup_add_str(HWSetup *hw, const char *str)
+{
+ pstrcpy(hw->ptr, 32, str);
+ hw->ptr += 32;
+}
+
+static inline void hwsetup_add_trailer(HWSetup *hw)
+{
+ hwsetup_add_u32(hw, 8); /* size */
+ hwsetup_add_tag(hw, HWSETUP_TAG_EOL);
+}
+
+static inline void hwsetup_add_cpu(HWSetup *hw,
+ const char *name, uint32_t frequency)
+{
+ hwsetup_add_u32(hw, 44); /* size */
+ hwsetup_add_tag(hw, HWSETUP_TAG_CPU);
+ hwsetup_add_str(hw, name);
+ hwsetup_add_u32(hw, frequency);
+}
+
+static inline void hwsetup_add_flash(HWSetup *hw,
+ const char *name, uint32_t base, uint32_t size)
+{
+ hwsetup_add_u32(hw, 52); /* size */
+ hwsetup_add_tag(hw, HWSETUP_TAG_FLASH);
+ hwsetup_add_str(hw, name);
+ hwsetup_add_u32(hw, base);
+ hwsetup_add_u32(hw, size);
+ hwsetup_add_u8(hw, 8); /* read latency */
+ hwsetup_add_u8(hw, 8); /* write latency */
+ hwsetup_add_u8(hw, 25); /* address width */
+ hwsetup_add_u8(hw, 32); /* data width */
+}
+
+static inline void hwsetup_add_ddr_sdram(HWSetup *hw,
+ const char *name, uint32_t base, uint32_t size)
+{
+ hwsetup_add_u32(hw, 48); /* size */
+ hwsetup_add_tag(hw, HWSETUP_TAG_DDR_SDRAM);
+ hwsetup_add_str(hw, name);
+ hwsetup_add_u32(hw, base);
+ hwsetup_add_u32(hw, size);
+}
+
+static inline void hwsetup_add_timer(HWSetup *hw,
+ const char *name, uint32_t base, uint32_t irq)
+{
+ hwsetup_add_u32(hw, 56); /* size */
+ hwsetup_add_tag(hw, HWSETUP_TAG_TIMER);
+ hwsetup_add_str(hw, name);
+ hwsetup_add_u32(hw, base);
+ hwsetup_add_u8(hw, 1); /* wr_tickcount */
+ hwsetup_add_u8(hw, 1); /* rd_tickcount */
+ hwsetup_add_u8(hw, 1); /* start_stop_control */
+ hwsetup_add_u8(hw, 32); /* counter_width */
+ hwsetup_add_u32(hw, 20); /* reload_ticks */
+ hwsetup_add_u8(hw, irq);
+ hwsetup_add_u8(hw, 0); /* padding */
+ hwsetup_add_u8(hw, 0); /* padding */
+ hwsetup_add_u8(hw, 0); /* padding */
+}
+
+static inline void hwsetup_add_uart(HWSetup *hw,
+ const char *name, uint32_t base, uint32_t irq)
+{
+ hwsetup_add_u32(hw, 56); /* size */
+ hwsetup_add_tag(hw, HWSETUP_TAG_UART);
+ hwsetup_add_str(hw, name);
+ hwsetup_add_u32(hw, base);
+ hwsetup_add_u32(hw, 115200); /* baudrate */
+ hwsetup_add_u8(hw, 8); /* databits */
+ hwsetup_add_u8(hw, 1); /* stopbits */
+ hwsetup_add_u8(hw, 1); /* use_interrupt */
+ hwsetup_add_u8(hw, 1); /* block_on_transmit */
+ hwsetup_add_u8(hw, 1); /* block_on_receive */
+ hwsetup_add_u8(hw, 4); /* rx_buffer_size */
+ hwsetup_add_u8(hw, 4); /* tx_buffer_size */
+ hwsetup_add_u8(hw, irq);
+}
+
+#endif /* QEMU_HW_LM32_HWSETUP_H */
diff --git a/hw/lm32/milkymist-hw.h b/hw/lm32/milkymist-hw.h
new file mode 100644
index 00000000..8d20cac1
--- /dev/null
+++ b/hw/lm32/milkymist-hw.h
@@ -0,0 +1,207 @@
+#ifndef QEMU_HW_MILKYMIST_H
+#define QEMU_HW_MILKYMIST_H
+
+#include "hw/qdev.h"
+#include "net/net.h"
+
+static inline DeviceState *milkymist_uart_create(hwaddr base,
+ qemu_irq irq)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "milkymist-uart");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
+
+ return dev;
+}
+
+static inline DeviceState *milkymist_hpdmc_create(hwaddr base)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "milkymist-hpdmc");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+
+ return dev;
+}
+
+static inline DeviceState *milkymist_memcard_create(hwaddr base)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "milkymist-memcard");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+
+ return dev;
+}
+
+static inline DeviceState *milkymist_vgafb_create(hwaddr base,
+ uint32_t fb_offset, uint32_t fb_mask)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "milkymist-vgafb");
+ qdev_prop_set_uint32(dev, "fb_offset", fb_offset);
+ qdev_prop_set_uint32(dev, "fb_mask", fb_mask);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+
+ return dev;
+}
+
+static inline DeviceState *milkymist_sysctl_create(hwaddr base,
+ qemu_irq gpio_irq, qemu_irq timer0_irq, qemu_irq timer1_irq,
+ uint32_t freq_hz, uint32_t system_id, uint32_t capabilities,
+ uint32_t gpio_strappings)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "milkymist-sysctl");
+ qdev_prop_set_uint32(dev, "frequency", freq_hz);
+ qdev_prop_set_uint32(dev, "systemid", system_id);
+ qdev_prop_set_uint32(dev, "capabilities", capabilities);
+ qdev_prop_set_uint32(dev, "gpio_strappings", gpio_strappings);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, gpio_irq);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, timer0_irq);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, timer1_irq);
+
+ return dev;
+}
+
+static inline DeviceState *milkymist_pfpu_create(hwaddr base,
+ qemu_irq irq)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "milkymist-pfpu");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
+ return dev;
+}
+
+#ifdef CONFIG_OPENGL
+#include <X11/Xlib.h>
+#include <GL/glx.h>
+static const int glx_fbconfig_attr[] = {
+ GLX_GREEN_SIZE, 5,
+ GLX_GREEN_SIZE, 6,
+ GLX_BLUE_SIZE, 5,
+ None
+};
+#endif
+
+static inline DeviceState *milkymist_tmu2_create(hwaddr base,
+ qemu_irq irq)
+{
+#ifdef CONFIG_OPENGL
+ DeviceState *dev;
+ Display *d;
+ GLXFBConfig *configs;
+ int nelements;
+ int ver_major, ver_minor;
+
+ if (display_type == DT_NOGRAPHIC) {
+ return NULL;
+ }
+
+ /* check that GLX will work */
+ d = XOpenDisplay(NULL);
+ if (d == NULL) {
+ return NULL;
+ }
+
+ if (!glXQueryVersion(d, &ver_major, &ver_minor)) {
+ /* Yeah, sometimes getting the GLX version can fail.
+ * Isn't X beautiful? */
+ XCloseDisplay(d);
+ return NULL;
+ }
+
+ if ((ver_major < 1) || ((ver_major == 1) && (ver_minor < 3))) {
+ printf("Your GLX version is %d.%d,"
+ "but TMU emulation needs at least 1.3. TMU disabled.\n",
+ ver_major, ver_minor);
+ XCloseDisplay(d);
+ return NULL;
+ }
+
+ configs = glXChooseFBConfig(d, 0, glx_fbconfig_attr, &nelements);
+ if (configs == NULL) {
+ XCloseDisplay(d);
+ return NULL;
+ }
+
+ XFree(configs);
+ XCloseDisplay(d);
+
+ dev = qdev_create(NULL, "milkymist-tmu2");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
+
+ return dev;
+#else
+ return NULL;
+#endif
+}
+
+static inline DeviceState *milkymist_ac97_create(hwaddr base,
+ qemu_irq crrequest_irq, qemu_irq crreply_irq, qemu_irq dmar_irq,
+ qemu_irq dmaw_irq)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "milkymist-ac97");
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, crrequest_irq);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, crreply_irq);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, dmar_irq);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 3, dmaw_irq);
+
+ return dev;
+}
+
+static inline DeviceState *milkymist_minimac2_create(hwaddr base,
+ hwaddr buffers_base, qemu_irq rx_irq, qemu_irq tx_irq)
+{
+ DeviceState *dev;
+
+ qemu_check_nic_model(&nd_table[0], "minimac2");
+ dev = qdev_create(NULL, "milkymist-minimac2");
+ qdev_set_nic_properties(dev, &nd_table[0]);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 1, buffers_base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, rx_irq);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, tx_irq);
+
+ return dev;
+}
+
+static inline DeviceState *milkymist_softusb_create(hwaddr base,
+ qemu_irq irq, uint32_t pmem_base, uint32_t pmem_size,
+ uint32_t dmem_base, uint32_t dmem_size)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "milkymist-softusb");
+ qdev_prop_set_uint32(dev, "pmem_size", pmem_size);
+ qdev_prop_set_uint32(dev, "dmem_size", dmem_size);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 1, pmem_base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, dmem_base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
+
+ return dev;
+}
+
+#endif /* QEMU_HW_MILKYMIST_H */
diff --git a/hw/lm32/milkymist.c b/hw/lm32/milkymist.c
new file mode 100644
index 00000000..e755f5b2
--- /dev/null
+++ b/hw/lm32/milkymist.c
@@ -0,0 +1,224 @@
+/*
+ * QEMU model for the Milkymist board.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/block/flash.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "sysemu/block-backend.h"
+#include "milkymist-hw.h"
+#include "lm32.h"
+#include "exec/address-spaces.h"
+
+#define BIOS_FILENAME "mmone-bios.bin"
+#define BIOS_OFFSET 0x00860000
+#define BIOS_SIZE (512*1024)
+#define KERNEL_LOAD_ADDR 0x40000000
+
+typedef struct {
+ LM32CPU *cpu;
+ hwaddr bootstrap_pc;
+ hwaddr flash_base;
+ hwaddr initrd_base;
+ size_t initrd_size;
+ hwaddr cmdline_base;
+} ResetInfo;
+
+static void cpu_irq_handler(void *opaque, int irq, int level)
+{
+ LM32CPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+
+ if (level) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ ResetInfo *reset_info = opaque;
+ CPULM32State *env = &reset_info->cpu->env;
+
+ cpu_reset(CPU(reset_info->cpu));
+
+ /* init defaults */
+ env->pc = reset_info->bootstrap_pc;
+ env->regs[R_R1] = reset_info->cmdline_base;
+ env->regs[R_R2] = reset_info->initrd_base;
+ env->regs[R_R3] = reset_info->initrd_base + reset_info->initrd_size;
+ env->eba = reset_info->flash_base;
+ env->deba = reset_info->flash_base;
+}
+
+static void
+milkymist_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ LM32CPU *cpu;
+ CPULM32State *env;
+ int kernel_size;
+ DriveInfo *dinfo;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *phys_sdram = g_new(MemoryRegion, 1);
+ qemu_irq irq[32];
+ int i;
+ char *bios_filename;
+ ResetInfo *reset_info;
+
+ /* memory map */
+ hwaddr flash_base = 0x00000000;
+ size_t flash_sector_size = 128 * 1024;
+ size_t flash_size = 32 * 1024 * 1024;
+ hwaddr sdram_base = 0x40000000;
+ size_t sdram_size = 128 * 1024 * 1024;
+
+ hwaddr initrd_base = sdram_base + 0x1002000;
+ hwaddr cmdline_base = sdram_base + 0x1000000;
+ size_t initrd_max = sdram_size - 0x1002000;
+
+ reset_info = g_malloc0(sizeof(ResetInfo));
+
+ if (cpu_model == NULL) {
+ cpu_model = "lm32-full";
+ }
+ cpu = cpu_lm32_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "qemu: unable to find CPU '%s'\n", cpu_model);
+ exit(1);
+ }
+
+ env = &cpu->env;
+ reset_info->cpu = cpu;
+
+ cpu_lm32_set_phys_msb_ignore(env, 1);
+
+ memory_region_allocate_system_memory(phys_sdram, NULL, "milkymist.sdram",
+ sdram_size);
+ memory_region_add_subregion(address_space_mem, sdram_base, phys_sdram);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ /* Numonyx JS28F256J3F105 */
+ pflash_cfi01_register(flash_base, NULL, "milkymist.flash", flash_size,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ flash_sector_size, flash_size / flash_sector_size,
+ 2, 0x00, 0x89, 0x00, 0x1d, 1);
+
+ /* create irq lines */
+ env->pic_state = lm32_pic_init(qemu_allocate_irq(cpu_irq_handler, cpu, 0));
+ for (i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in(env->pic_state, i);
+ }
+
+ /* load bios rom */
+ if (bios_name == NULL) {
+ bios_name = BIOS_FILENAME;
+ }
+ bios_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+
+ if (bios_filename) {
+ load_image_targphys(bios_filename, BIOS_OFFSET, BIOS_SIZE);
+ }
+
+ reset_info->bootstrap_pc = BIOS_OFFSET;
+
+ /* if no kernel is given no valid bios rom is a fatal error */
+ if (!kernel_filename && !dinfo && !bios_filename && !qtest_enabled()) {
+ fprintf(stderr, "qemu: could not load Milkymist One bios '%s'\n",
+ bios_name);
+ exit(1);
+ }
+ g_free(bios_filename);
+
+ milkymist_uart_create(0x60000000, irq[0]);
+ milkymist_sysctl_create(0x60001000, irq[1], irq[2], irq[3],
+ 80000000, 0x10014d31, 0x0000041f, 0x00000001);
+ milkymist_hpdmc_create(0x60002000);
+ milkymist_vgafb_create(0x60003000, 0x40000000, 0x0fffffff);
+ milkymist_memcard_create(0x60004000);
+ milkymist_ac97_create(0x60005000, irq[4], irq[5], irq[6], irq[7]);
+ milkymist_pfpu_create(0x60006000, irq[8]);
+ milkymist_tmu2_create(0x60007000, irq[9]);
+ milkymist_minimac2_create(0x60008000, 0x30000000, irq[10], irq[11]);
+ milkymist_softusb_create(0x6000f000, irq[15],
+ 0x20000000, 0x1000, 0x20020000, 0x2000);
+
+ /* make sure juart isn't the first chardev */
+ env->juart_state = lm32_juart_init();
+
+ if (kernel_filename) {
+ uint64_t entry;
+
+ /* Boots a kernel elf binary. */
+ kernel_size = load_elf(kernel_filename, NULL, NULL, &entry, NULL, NULL,
+ 1, ELF_MACHINE, 0);
+ reset_info->bootstrap_pc = entry;
+
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename, sdram_base,
+ sdram_size);
+ reset_info->bootstrap_pc = sdram_base;
+ }
+
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ }
+
+ if (kernel_cmdline && strlen(kernel_cmdline)) {
+ pstrcpy_targphys("cmdline", cmdline_base, TARGET_PAGE_SIZE,
+ kernel_cmdline);
+ reset_info->cmdline_base = (uint32_t)cmdline_base;
+ }
+
+ if (initrd_filename) {
+ size_t initrd_size;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ initrd_max);
+ reset_info->initrd_base = (uint32_t)initrd_base;
+ reset_info->initrd_size = (uint32_t)initrd_size;
+ }
+
+ qemu_register_reset(main_cpu_reset, reset_info);
+}
+
+static QEMUMachine milkymist_machine = {
+ .name = "milkymist",
+ .desc = "Milkymist One",
+ .init = milkymist_init,
+ .is_default = 0,
+};
+
+static void milkymist_machine_init(void)
+{
+ qemu_register_machine(&milkymist_machine);
+}
+
+machine_init(milkymist_machine_init);
diff --git a/hw/m68k/Makefile.objs b/hw/m68k/Makefile.objs
new file mode 100644
index 00000000..c4352e78
--- /dev/null
+++ b/hw/m68k/Makefile.objs
@@ -0,0 +1,4 @@
+obj-y += an5206.o mcf5208.o
+obj-y += dummy_m68k.o
+
+obj-y += mcf5206.o mcf_intc.o
diff --git a/hw/m68k/an5206.c b/hw/m68k/an5206.c
new file mode 100644
index 00000000..f63ab2b9
--- /dev/null
+++ b/hw/m68k/an5206.c
@@ -0,0 +1,103 @@
+/*
+ * Arnewsh 5206 ColdFire system emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+
+#include "hw/hw.h"
+#include "hw/m68k/mcf.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "exec/address-spaces.h"
+#include "sysemu/qtest.h"
+
+#define KERNEL_LOAD_ADDR 0x10000
+#define AN5206_MBAR_ADDR 0x10000000
+#define AN5206_RAMBAR_ADDR 0x20000000
+
+/* Board init. */
+
+static void an5206_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ M68kCPU *cpu;
+ CPUM68KState *env;
+ int kernel_size;
+ uint64_t elf_entry;
+ hwaddr entry;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+
+ if (!cpu_model) {
+ cpu_model = "m5206";
+ }
+ cpu = cpu_m68k_init(cpu_model);
+ if (!cpu) {
+ hw_error("Unable to find m68k CPU definition\n");
+ }
+ env = &cpu->env;
+
+ /* Initialize CPU registers. */
+ env->vbr = 0;
+ /* TODO: allow changing MBAR and RAMBAR. */
+ env->mbar = AN5206_MBAR_ADDR | 1;
+ env->rambar0 = AN5206_RAMBAR_ADDR | 1;
+
+ /* DRAM at address zero */
+ memory_region_allocate_system_memory(ram, NULL, "an5206.ram", ram_size);
+ memory_region_add_subregion(address_space_mem, 0, ram);
+
+ /* Internal SRAM. */
+ memory_region_init_ram(sram, NULL, "an5206.sram", 512, &error_abort);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(address_space_mem, AN5206_RAMBAR_ADDR, sram);
+
+ mcf5206_init(address_space_mem, AN5206_MBAR_ADDR, cpu);
+
+ /* Load kernel. */
+ if (!kernel_filename) {
+ if (qtest_enabled()) {
+ return;
+ }
+ fprintf(stderr, "Kernel image must be specified\n");
+ exit(1);
+ }
+
+ kernel_size = load_elf(kernel_filename, NULL, NULL, &elf_entry,
+ NULL, NULL, 1, ELF_MACHINE, 0);
+ entry = elf_entry;
+ if (kernel_size < 0) {
+ kernel_size = load_uimage(kernel_filename, &entry, NULL, NULL,
+ NULL, NULL);
+ }
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename, KERNEL_LOAD_ADDR,
+ ram_size - KERNEL_LOAD_ADDR);
+ entry = KERNEL_LOAD_ADDR;
+ }
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n", kernel_filename);
+ exit(1);
+ }
+
+ env->pc = entry;
+}
+
+static QEMUMachine an5206_machine = {
+ .name = "an5206",
+ .desc = "Arnewsh 5206",
+ .init = an5206_init,
+};
+
+static void an5206_machine_init(void)
+{
+ qemu_register_machine(&an5206_machine);
+}
+
+machine_init(an5206_machine_init);
diff --git a/hw/m68k/dummy_m68k.c b/hw/m68k/dummy_m68k.c
new file mode 100644
index 00000000..5b77d930
--- /dev/null
+++ b/hw/m68k/dummy_m68k.c
@@ -0,0 +1,86 @@
+/*
+ * Dummy board with just RAM and CPU for use as an ISS.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+
+#include "hw/hw.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "exec/address-spaces.h"
+
+#define KERNEL_LOAD_ADDR 0x10000
+
+/* Board init. */
+
+static void dummy_m68k_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ M68kCPU *cpu;
+ CPUM68KState *env;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ int kernel_size;
+ uint64_t elf_entry;
+ hwaddr entry;
+
+ if (!cpu_model)
+ cpu_model = "cfv4e";
+ cpu = cpu_m68k_init(cpu_model);
+ if (!cpu) {
+ fprintf(stderr, "Unable to find m68k CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ /* Initialize CPU registers. */
+ env->vbr = 0;
+
+ /* RAM at address zero */
+ memory_region_allocate_system_memory(ram, NULL, "dummy_m68k.ram",
+ ram_size);
+ memory_region_add_subregion(address_space_mem, 0, ram);
+
+ /* Load kernel. */
+ if (kernel_filename) {
+ kernel_size = load_elf(kernel_filename, NULL, NULL, &elf_entry,
+ NULL, NULL, 1, ELF_MACHINE, 0);
+ entry = elf_entry;
+ if (kernel_size < 0) {
+ kernel_size = load_uimage(kernel_filename, &entry, NULL, NULL,
+ NULL, NULL);
+ }
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename,
+ KERNEL_LOAD_ADDR,
+ ram_size - KERNEL_LOAD_ADDR);
+ entry = KERNEL_LOAD_ADDR;
+ }
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ } else {
+ entry = 0;
+ }
+ env->pc = entry;
+}
+
+static QEMUMachine dummy_m68k_machine = {
+ .name = "dummy",
+ .desc = "Dummy board",
+ .init = dummy_m68k_init,
+};
+
+static void dummy_m68k_machine_init(void)
+{
+ qemu_register_machine(&dummy_m68k_machine);
+}
+
+machine_init(dummy_m68k_machine_init);
diff --git a/hw/m68k/mcf5206.c b/hw/m68k/mcf5206.c
new file mode 100644
index 00000000..1727a468
--- /dev/null
+++ b/hw/m68k/mcf5206.c
@@ -0,0 +1,548 @@
+/*
+ * Motorola ColdFire MCF5206 SoC embedded peripheral emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+#include "hw/hw.h"
+#include "hw/m68k/mcf.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "sysemu/sysemu.h"
+#include "exec/address-spaces.h"
+
+/* General purpose timer module. */
+typedef struct {
+ uint16_t tmr;
+ uint16_t trr;
+ uint16_t tcr;
+ uint16_t ter;
+ ptimer_state *timer;
+ qemu_irq irq;
+ int irq_state;
+} m5206_timer_state;
+
+#define TMR_RST 0x01
+#define TMR_CLK 0x06
+#define TMR_FRR 0x08
+#define TMR_ORI 0x10
+#define TMR_OM 0x20
+#define TMR_CE 0xc0
+
+#define TER_CAP 0x01
+#define TER_REF 0x02
+
+static void m5206_timer_update(m5206_timer_state *s)
+{
+ if ((s->tmr & TMR_ORI) != 0 && (s->ter & TER_REF))
+ qemu_irq_raise(s->irq);
+ else
+ qemu_irq_lower(s->irq);
+}
+
+static void m5206_timer_reset(m5206_timer_state *s)
+{
+ s->tmr = 0;
+ s->trr = 0;
+}
+
+static void m5206_timer_recalibrate(m5206_timer_state *s)
+{
+ int prescale;
+ int mode;
+
+ ptimer_stop(s->timer);
+
+ if ((s->tmr & TMR_RST) == 0)
+ return;
+
+ prescale = (s->tmr >> 8) + 1;
+ mode = (s->tmr >> 1) & 3;
+ if (mode == 2)
+ prescale *= 16;
+
+ if (mode == 3 || mode == 0)
+ hw_error("m5206_timer: mode %d not implemented\n", mode);
+ if ((s->tmr & TMR_FRR) == 0)
+ hw_error("m5206_timer: free running mode not implemented\n");
+
+ /* Assume 66MHz system clock. */
+ ptimer_set_freq(s->timer, 66000000 / prescale);
+
+ ptimer_set_limit(s->timer, s->trr, 0);
+
+ ptimer_run(s->timer, 0);
+}
+
+static void m5206_timer_trigger(void *opaque)
+{
+ m5206_timer_state *s = (m5206_timer_state *)opaque;
+ s->ter |= TER_REF;
+ m5206_timer_update(s);
+}
+
+static uint32_t m5206_timer_read(m5206_timer_state *s, uint32_t addr)
+{
+ switch (addr) {
+ case 0:
+ return s->tmr;
+ case 4:
+ return s->trr;
+ case 8:
+ return s->tcr;
+ case 0xc:
+ return s->trr - ptimer_get_count(s->timer);
+ case 0x11:
+ return s->ter;
+ default:
+ return 0;
+ }
+}
+
+static void m5206_timer_write(m5206_timer_state *s, uint32_t addr, uint32_t val)
+{
+ switch (addr) {
+ case 0:
+ if ((s->tmr & TMR_RST) != 0 && (val & TMR_RST) == 0) {
+ m5206_timer_reset(s);
+ }
+ s->tmr = val;
+ m5206_timer_recalibrate(s);
+ break;
+ case 4:
+ s->trr = val;
+ m5206_timer_recalibrate(s);
+ break;
+ case 8:
+ s->tcr = val;
+ break;
+ case 0xc:
+ ptimer_set_count(s->timer, val);
+ break;
+ case 0x11:
+ s->ter &= ~val;
+ break;
+ default:
+ break;
+ }
+ m5206_timer_update(s);
+}
+
+static m5206_timer_state *m5206_timer_init(qemu_irq irq)
+{
+ m5206_timer_state *s;
+ QEMUBH *bh;
+
+ s = (m5206_timer_state *)g_malloc0(sizeof(m5206_timer_state));
+ bh = qemu_bh_new(m5206_timer_trigger, s);
+ s->timer = ptimer_init(bh);
+ s->irq = irq;
+ m5206_timer_reset(s);
+ return s;
+}
+
+/* System Integration Module. */
+
+typedef struct {
+ M68kCPU *cpu;
+ MemoryRegion iomem;
+ m5206_timer_state *timer[2];
+ void *uart[2];
+ uint8_t scr;
+ uint8_t icr[14];
+ uint16_t imr; /* 1 == interrupt is masked. */
+ uint16_t ipr;
+ uint8_t rsr;
+ uint8_t swivr;
+ uint8_t par;
+ /* Include the UART vector registers here. */
+ uint8_t uivr[2];
+} m5206_mbar_state;
+
+/* Interrupt controller. */
+
+static int m5206_find_pending_irq(m5206_mbar_state *s)
+{
+ int level;
+ int vector;
+ uint16_t active;
+ int i;
+
+ level = 0;
+ vector = 0;
+ active = s->ipr & ~s->imr;
+ if (!active)
+ return 0;
+
+ for (i = 1; i < 14; i++) {
+ if (active & (1 << i)) {
+ if ((s->icr[i] & 0x1f) > level) {
+ level = s->icr[i] & 0x1f;
+ vector = i;
+ }
+ }
+ }
+
+ if (level < 4)
+ vector = 0;
+
+ return vector;
+}
+
+static void m5206_mbar_update(m5206_mbar_state *s)
+{
+ int irq;
+ int vector;
+ int level;
+
+ irq = m5206_find_pending_irq(s);
+ if (irq) {
+ int tmp;
+ tmp = s->icr[irq];
+ level = (tmp >> 2) & 7;
+ if (tmp & 0x80) {
+ /* Autovector. */
+ vector = 24 + level;
+ } else {
+ switch (irq) {
+ case 8: /* SWT */
+ vector = s->swivr;
+ break;
+ case 12: /* UART1 */
+ vector = s->uivr[0];
+ break;
+ case 13: /* UART2 */
+ vector = s->uivr[1];
+ break;
+ default:
+ /* Unknown vector. */
+ fprintf(stderr, "Unhandled vector for IRQ %d\n", irq);
+ vector = 0xf;
+ break;
+ }
+ }
+ } else {
+ level = 0;
+ vector = 0;
+ }
+ m68k_set_irq_level(s->cpu, level, vector);
+}
+
+static void m5206_mbar_set_irq(void *opaque, int irq, int level)
+{
+ m5206_mbar_state *s = (m5206_mbar_state *)opaque;
+ if (level) {
+ s->ipr |= 1 << irq;
+ } else {
+ s->ipr &= ~(1 << irq);
+ }
+ m5206_mbar_update(s);
+}
+
+/* System Integration Module. */
+
+static void m5206_mbar_reset(m5206_mbar_state *s)
+{
+ s->scr = 0xc0;
+ s->icr[1] = 0x04;
+ s->icr[2] = 0x08;
+ s->icr[3] = 0x0c;
+ s->icr[4] = 0x10;
+ s->icr[5] = 0x14;
+ s->icr[6] = 0x18;
+ s->icr[7] = 0x1c;
+ s->icr[8] = 0x1c;
+ s->icr[9] = 0x80;
+ s->icr[10] = 0x80;
+ s->icr[11] = 0x80;
+ s->icr[12] = 0x00;
+ s->icr[13] = 0x00;
+ s->imr = 0x3ffe;
+ s->rsr = 0x80;
+ s->swivr = 0x0f;
+ s->par = 0;
+}
+
+static uint64_t m5206_mbar_read(m5206_mbar_state *s,
+ uint64_t offset, unsigned size)
+{
+ if (offset >= 0x100 && offset < 0x120) {
+ return m5206_timer_read(s->timer[0], offset - 0x100);
+ } else if (offset >= 0x120 && offset < 0x140) {
+ return m5206_timer_read(s->timer[1], offset - 0x120);
+ } else if (offset >= 0x140 && offset < 0x160) {
+ return mcf_uart_read(s->uart[0], offset - 0x140, size);
+ } else if (offset >= 0x180 && offset < 0x1a0) {
+ return mcf_uart_read(s->uart[1], offset - 0x180, size);
+ }
+ switch (offset) {
+ case 0x03: return s->scr;
+ case 0x14 ... 0x20: return s->icr[offset - 0x13];
+ case 0x36: return s->imr;
+ case 0x3a: return s->ipr;
+ case 0x40: return s->rsr;
+ case 0x41: return 0;
+ case 0x42: return s->swivr;
+ case 0x50:
+ /* DRAM mask register. */
+ /* FIXME: currently hardcoded to 128Mb. */
+ {
+ uint32_t mask = ~0;
+ while (mask > ram_size)
+ mask >>= 1;
+ return mask & 0x0ffe0000;
+ }
+ case 0x5c: return 1; /* DRAM bank 1 empty. */
+ case 0xcb: return s->par;
+ case 0x170: return s->uivr[0];
+ case 0x1b0: return s->uivr[1];
+ }
+ hw_error("Bad MBAR read offset 0x%x", (int)offset);
+ return 0;
+}
+
+static void m5206_mbar_write(m5206_mbar_state *s, uint32_t offset,
+ uint64_t value, unsigned size)
+{
+ if (offset >= 0x100 && offset < 0x120) {
+ m5206_timer_write(s->timer[0], offset - 0x100, value);
+ return;
+ } else if (offset >= 0x120 && offset < 0x140) {
+ m5206_timer_write(s->timer[1], offset - 0x120, value);
+ return;
+ } else if (offset >= 0x140 && offset < 0x160) {
+ mcf_uart_write(s->uart[0], offset - 0x140, value, size);
+ return;
+ } else if (offset >= 0x180 && offset < 0x1a0) {
+ mcf_uart_write(s->uart[1], offset - 0x180, value, size);
+ return;
+ }
+ switch (offset) {
+ case 0x03:
+ s->scr = value;
+ break;
+ case 0x14 ... 0x20:
+ s->icr[offset - 0x13] = value;
+ m5206_mbar_update(s);
+ break;
+ case 0x36:
+ s->imr = value;
+ m5206_mbar_update(s);
+ break;
+ case 0x40:
+ s->rsr &= ~value;
+ break;
+ case 0x41:
+ /* TODO: implement watchdog. */
+ break;
+ case 0x42:
+ s->swivr = value;
+ break;
+ case 0xcb:
+ s->par = value;
+ break;
+ case 0x170:
+ s->uivr[0] = value;
+ break;
+ case 0x178: case 0x17c: case 0x1c8: case 0x1bc:
+ /* Not implemented: UART Output port bits. */
+ break;
+ case 0x1b0:
+ s->uivr[1] = value;
+ break;
+ default:
+ hw_error("Bad MBAR write offset 0x%x", (int)offset);
+ break;
+ }
+}
+
+/* Internal peripherals use a variety of register widths.
+ This lookup table allows a single routine to handle all of them. */
+static const uint8_t m5206_mbar_width[] =
+{
+ /* 000-040 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
+ /* 040-080 */ 1, 2, 2, 2, 4, 1, 2, 4, 1, 2, 4, 2, 2, 4, 2, 2,
+ /* 080-0c0 */ 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4,
+ /* 0c0-100 */ 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 100-140 */ 2, 2, 2, 2, 1, 0, 0, 0, 2, 2, 2, 2, 1, 0, 0, 0,
+ /* 140-180 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 180-1c0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 1c0-200 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+static uint32_t m5206_mbar_readw(void *opaque, hwaddr offset);
+static uint32_t m5206_mbar_readl(void *opaque, hwaddr offset);
+
+static uint32_t m5206_mbar_readb(void *opaque, hwaddr offset)
+{
+ m5206_mbar_state *s = (m5206_mbar_state *)opaque;
+ offset &= 0x3ff;
+ if (offset >= 0x200) {
+ hw_error("Bad MBAR read offset 0x%x", (int)offset);
+ }
+ if (m5206_mbar_width[offset >> 2] > 1) {
+ uint16_t val;
+ val = m5206_mbar_readw(opaque, offset & ~1);
+ if ((offset & 1) == 0) {
+ val >>= 8;
+ }
+ return val & 0xff;
+ }
+ return m5206_mbar_read(s, offset, 1);
+}
+
+static uint32_t m5206_mbar_readw(void *opaque, hwaddr offset)
+{
+ m5206_mbar_state *s = (m5206_mbar_state *)opaque;
+ int width;
+ offset &= 0x3ff;
+ if (offset >= 0x200) {
+ hw_error("Bad MBAR read offset 0x%x", (int)offset);
+ }
+ width = m5206_mbar_width[offset >> 2];
+ if (width > 2) {
+ uint32_t val;
+ val = m5206_mbar_readl(opaque, offset & ~3);
+ if ((offset & 3) == 0)
+ val >>= 16;
+ return val & 0xffff;
+ } else if (width < 2) {
+ uint16_t val;
+ val = m5206_mbar_readb(opaque, offset) << 8;
+ val |= m5206_mbar_readb(opaque, offset + 1);
+ return val;
+ }
+ return m5206_mbar_read(s, offset, 2);
+}
+
+static uint32_t m5206_mbar_readl(void *opaque, hwaddr offset)
+{
+ m5206_mbar_state *s = (m5206_mbar_state *)opaque;
+ int width;
+ offset &= 0x3ff;
+ if (offset >= 0x200) {
+ hw_error("Bad MBAR read offset 0x%x", (int)offset);
+ }
+ width = m5206_mbar_width[offset >> 2];
+ if (width < 4) {
+ uint32_t val;
+ val = m5206_mbar_readw(opaque, offset) << 16;
+ val |= m5206_mbar_readw(opaque, offset + 2);
+ return val;
+ }
+ return m5206_mbar_read(s, offset, 4);
+}
+
+static void m5206_mbar_writew(void *opaque, hwaddr offset,
+ uint32_t value);
+static void m5206_mbar_writel(void *opaque, hwaddr offset,
+ uint32_t value);
+
+static void m5206_mbar_writeb(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ m5206_mbar_state *s = (m5206_mbar_state *)opaque;
+ int width;
+ offset &= 0x3ff;
+ if (offset >= 0x200) {
+ hw_error("Bad MBAR write offset 0x%x", (int)offset);
+ }
+ width = m5206_mbar_width[offset >> 2];
+ if (width > 1) {
+ uint32_t tmp;
+ tmp = m5206_mbar_readw(opaque, offset & ~1);
+ if (offset & 1) {
+ tmp = (tmp & 0xff00) | value;
+ } else {
+ tmp = (tmp & 0x00ff) | (value << 8);
+ }
+ m5206_mbar_writew(opaque, offset & ~1, tmp);
+ return;
+ }
+ m5206_mbar_write(s, offset, value, 1);
+}
+
+static void m5206_mbar_writew(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ m5206_mbar_state *s = (m5206_mbar_state *)opaque;
+ int width;
+ offset &= 0x3ff;
+ if (offset >= 0x200) {
+ hw_error("Bad MBAR write offset 0x%x", (int)offset);
+ }
+ width = m5206_mbar_width[offset >> 2];
+ if (width > 2) {
+ uint32_t tmp;
+ tmp = m5206_mbar_readl(opaque, offset & ~3);
+ if (offset & 3) {
+ tmp = (tmp & 0xffff0000) | value;
+ } else {
+ tmp = (tmp & 0x0000ffff) | (value << 16);
+ }
+ m5206_mbar_writel(opaque, offset & ~3, tmp);
+ return;
+ } else if (width < 2) {
+ m5206_mbar_writeb(opaque, offset, value >> 8);
+ m5206_mbar_writeb(opaque, offset + 1, value & 0xff);
+ return;
+ }
+ m5206_mbar_write(s, offset, value, 2);
+}
+
+static void m5206_mbar_writel(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ m5206_mbar_state *s = (m5206_mbar_state *)opaque;
+ int width;
+ offset &= 0x3ff;
+ if (offset >= 0x200) {
+ hw_error("Bad MBAR write offset 0x%x", (int)offset);
+ }
+ width = m5206_mbar_width[offset >> 2];
+ if (width < 4) {
+ m5206_mbar_writew(opaque, offset, value >> 16);
+ m5206_mbar_writew(opaque, offset + 2, value & 0xffff);
+ return;
+ }
+ m5206_mbar_write(s, offset, value, 4);
+}
+
+static const MemoryRegionOps m5206_mbar_ops = {
+ .old_mmio = {
+ .read = {
+ m5206_mbar_readb,
+ m5206_mbar_readw,
+ m5206_mbar_readl,
+ },
+ .write = {
+ m5206_mbar_writeb,
+ m5206_mbar_writew,
+ m5206_mbar_writel,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+qemu_irq *mcf5206_init(MemoryRegion *sysmem, uint32_t base, M68kCPU *cpu)
+{
+ m5206_mbar_state *s;
+ qemu_irq *pic;
+
+ s = (m5206_mbar_state *)g_malloc0(sizeof(m5206_mbar_state));
+
+ memory_region_init_io(&s->iomem, NULL, &m5206_mbar_ops, s,
+ "mbar", 0x00001000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ pic = qemu_allocate_irqs(m5206_mbar_set_irq, s, 14);
+ s->timer[0] = m5206_timer_init(pic[9]);
+ s->timer[1] = m5206_timer_init(pic[10]);
+ s->uart[0] = mcf_uart_init(pic[12], serial_hds[0]);
+ s->uart[1] = mcf_uart_init(pic[13], serial_hds[1]);
+ s->cpu = cpu;
+
+ m5206_mbar_reset(s);
+ return pic;
+}
diff --git a/hw/m68k/mcf5208.c b/hw/m68k/mcf5208.c
new file mode 100644
index 00000000..326a42d2
--- /dev/null
+++ b/hw/m68k/mcf5208.c
@@ -0,0 +1,309 @@
+/*
+ * Motorola ColdFire MCF5208 SoC emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+#include "hw/hw.h"
+#include "hw/m68k/mcf.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+#include "net/net.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "exec/address-spaces.h"
+
+#define SYS_FREQ 66000000
+
+#define PCSR_EN 0x0001
+#define PCSR_RLD 0x0002
+#define PCSR_PIF 0x0004
+#define PCSR_PIE 0x0008
+#define PCSR_OVW 0x0010
+#define PCSR_DBG 0x0020
+#define PCSR_DOZE 0x0040
+#define PCSR_PRE_SHIFT 8
+#define PCSR_PRE_MASK 0x0f00
+
+typedef struct {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ ptimer_state *timer;
+ uint16_t pcsr;
+ uint16_t pmr;
+ uint16_t pcntr;
+} m5208_timer_state;
+
+static void m5208_timer_update(m5208_timer_state *s)
+{
+ if ((s->pcsr & (PCSR_PIE | PCSR_PIF)) == (PCSR_PIE | PCSR_PIF))
+ qemu_irq_raise(s->irq);
+ else
+ qemu_irq_lower(s->irq);
+}
+
+static void m5208_timer_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ m5208_timer_state *s = (m5208_timer_state *)opaque;
+ int prescale;
+ int limit;
+ switch (offset) {
+ case 0:
+ /* The PIF bit is set-to-clear. */
+ if (value & PCSR_PIF) {
+ s->pcsr &= ~PCSR_PIF;
+ value &= ~PCSR_PIF;
+ }
+ /* Avoid frobbing the timer if we're just twiddling IRQ bits. */
+ if (((s->pcsr ^ value) & ~PCSR_PIE) == 0) {
+ s->pcsr = value;
+ m5208_timer_update(s);
+ return;
+ }
+
+ if (s->pcsr & PCSR_EN)
+ ptimer_stop(s->timer);
+
+ s->pcsr = value;
+
+ prescale = 1 << ((s->pcsr & PCSR_PRE_MASK) >> PCSR_PRE_SHIFT);
+ ptimer_set_freq(s->timer, (SYS_FREQ / 2) / prescale);
+ if (s->pcsr & PCSR_RLD)
+ limit = s->pmr;
+ else
+ limit = 0xffff;
+ ptimer_set_limit(s->timer, limit, 0);
+
+ if (s->pcsr & PCSR_EN)
+ ptimer_run(s->timer, 0);
+ break;
+ case 2:
+ s->pmr = value;
+ s->pcsr &= ~PCSR_PIF;
+ if ((s->pcsr & PCSR_RLD) == 0) {
+ if (s->pcsr & PCSR_OVW)
+ ptimer_set_count(s->timer, value);
+ } else {
+ ptimer_set_limit(s->timer, value, s->pcsr & PCSR_OVW);
+ }
+ break;
+ case 4:
+ break;
+ default:
+ hw_error("m5208_timer_write: Bad offset 0x%x\n", (int)offset);
+ break;
+ }
+ m5208_timer_update(s);
+}
+
+static void m5208_timer_trigger(void *opaque)
+{
+ m5208_timer_state *s = (m5208_timer_state *)opaque;
+ s->pcsr |= PCSR_PIF;
+ m5208_timer_update(s);
+}
+
+static uint64_t m5208_timer_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ m5208_timer_state *s = (m5208_timer_state *)opaque;
+ switch (addr) {
+ case 0:
+ return s->pcsr;
+ case 2:
+ return s->pmr;
+ case 4:
+ return ptimer_get_count(s->timer);
+ default:
+ hw_error("m5208_timer_read: Bad offset 0x%x\n", (int)addr);
+ return 0;
+ }
+}
+
+static const MemoryRegionOps m5208_timer_ops = {
+ .read = m5208_timer_read,
+ .write = m5208_timer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t m5208_sys_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ switch (addr) {
+ case 0x110: /* SDCS0 */
+ {
+ int n;
+ for (n = 0; n < 32; n++) {
+ if (ram_size < (2u << n))
+ break;
+ }
+ return (n - 1) | 0x40000000;
+ }
+ case 0x114: /* SDCS1 */
+ return 0;
+
+ default:
+ hw_error("m5208_sys_read: Bad offset 0x%x\n", (int)addr);
+ return 0;
+ }
+}
+
+static void m5208_sys_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ hw_error("m5208_sys_write: Bad offset 0x%x\n", (int)addr);
+}
+
+static const MemoryRegionOps m5208_sys_ops = {
+ .read = m5208_sys_read,
+ .write = m5208_sys_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void mcf5208_sys_init(MemoryRegion *address_space, qemu_irq *pic)
+{
+ MemoryRegion *iomem = g_new(MemoryRegion, 1);
+ m5208_timer_state *s;
+ QEMUBH *bh;
+ int i;
+
+ /* SDRAMC. */
+ memory_region_init_io(iomem, NULL, &m5208_sys_ops, NULL, "m5208-sys", 0x00004000);
+ memory_region_add_subregion(address_space, 0xfc0a8000, iomem);
+ /* Timers. */
+ for (i = 0; i < 2; i++) {
+ s = (m5208_timer_state *)g_malloc0(sizeof(m5208_timer_state));
+ bh = qemu_bh_new(m5208_timer_trigger, s);
+ s->timer = ptimer_init(bh);
+ memory_region_init_io(&s->iomem, NULL, &m5208_timer_ops, s,
+ "m5208-timer", 0x00004000);
+ memory_region_add_subregion(address_space, 0xfc080000 + 0x4000 * i,
+ &s->iomem);
+ s->irq = pic[4 + i];
+ }
+}
+
+static void mcf5208evb_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ M68kCPU *cpu;
+ CPUM68KState *env;
+ int kernel_size;
+ uint64_t elf_entry;
+ hwaddr entry;
+ qemu_irq *pic;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+
+ if (!cpu_model) {
+ cpu_model = "m5208";
+ }
+ cpu = cpu_m68k_init(cpu_model);
+ if (!cpu) {
+ fprintf(stderr, "Unable to find m68k CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ /* Initialize CPU registers. */
+ env->vbr = 0;
+ /* TODO: Configure BARs. */
+
+ /* DRAM at 0x40000000 */
+ memory_region_allocate_system_memory(ram, NULL, "mcf5208.ram", ram_size);
+ memory_region_add_subregion(address_space_mem, 0x40000000, ram);
+
+ /* Internal SRAM. */
+ memory_region_init_ram(sram, NULL, "mcf5208.sram", 16384, &error_abort);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(address_space_mem, 0x80000000, sram);
+
+ /* Internal peripherals. */
+ pic = mcf_intc_init(address_space_mem, 0xfc048000, cpu);
+
+ mcf_uart_mm_init(address_space_mem, 0xfc060000, pic[26], serial_hds[0]);
+ mcf_uart_mm_init(address_space_mem, 0xfc064000, pic[27], serial_hds[1]);
+ mcf_uart_mm_init(address_space_mem, 0xfc068000, pic[28], serial_hds[2]);
+
+ mcf5208_sys_init(address_space_mem, pic);
+
+ if (nb_nics > 1) {
+ fprintf(stderr, "Too many NICs\n");
+ exit(1);
+ }
+ if (nd_table[0].used)
+ mcf_fec_init(address_space_mem, &nd_table[0],
+ 0xfc030000, pic + 36);
+
+ /* 0xfc000000 SCM. */
+ /* 0xfc004000 XBS. */
+ /* 0xfc008000 FlexBus CS. */
+ /* 0xfc030000 FEC. */
+ /* 0xfc040000 SCM + Power management. */
+ /* 0xfc044000 eDMA. */
+ /* 0xfc048000 INTC. */
+ /* 0xfc058000 I2C. */
+ /* 0xfc05c000 QSPI. */
+ /* 0xfc060000 UART0. */
+ /* 0xfc064000 UART0. */
+ /* 0xfc068000 UART0. */
+ /* 0xfc070000 DMA timers. */
+ /* 0xfc080000 PIT0. */
+ /* 0xfc084000 PIT1. */
+ /* 0xfc088000 EPORT. */
+ /* 0xfc08c000 Watchdog. */
+ /* 0xfc090000 clock module. */
+ /* 0xfc0a0000 CCM + reset. */
+ /* 0xfc0a4000 GPIO. */
+ /* 0xfc0a8000 SDRAM controller. */
+
+ /* Load kernel. */
+ if (!kernel_filename) {
+ if (qtest_enabled()) {
+ return;
+ }
+ fprintf(stderr, "Kernel image must be specified\n");
+ exit(1);
+ }
+
+ kernel_size = load_elf(kernel_filename, NULL, NULL, &elf_entry,
+ NULL, NULL, 1, ELF_MACHINE, 0);
+ entry = elf_entry;
+ if (kernel_size < 0) {
+ kernel_size = load_uimage(kernel_filename, &entry, NULL, NULL,
+ NULL, NULL);
+ }
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename, 0x40000000,
+ ram_size);
+ entry = 0x40000000;
+ }
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n", kernel_filename);
+ exit(1);
+ }
+
+ env->pc = entry;
+}
+
+static QEMUMachine mcf5208evb_machine = {
+ .name = "mcf5208evb",
+ .desc = "MCF5206EVB",
+ .init = mcf5208evb_init,
+ .is_default = 1,
+};
+
+static void mcf5208evb_machine_init(void)
+{
+ qemu_register_machine(&mcf5208evb_machine);
+}
+
+machine_init(mcf5208evb_machine_init);
diff --git a/hw/m68k/mcf_intc.c b/hw/m68k/mcf_intc.c
new file mode 100644
index 00000000..f13c7f3a
--- /dev/null
+++ b/hw/m68k/mcf_intc.c
@@ -0,0 +1,168 @@
+/*
+ * ColdFire Interrupt Controller emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+#include "hw/hw.h"
+#include "hw/m68k/mcf.h"
+#include "exec/address-spaces.h"
+
+typedef struct {
+ MemoryRegion iomem;
+ uint64_t ipr;
+ uint64_t imr;
+ uint64_t ifr;
+ uint64_t enabled;
+ uint8_t icr[64];
+ M68kCPU *cpu;
+ int active_vector;
+} mcf_intc_state;
+
+static void mcf_intc_update(mcf_intc_state *s)
+{
+ uint64_t active;
+ int i;
+ int best;
+ int best_level;
+
+ active = (s->ipr | s->ifr) & s->enabled & ~s->imr;
+ best_level = 0;
+ best = 64;
+ if (active) {
+ for (i = 0; i < 64; i++) {
+ if ((active & 1) != 0 && s->icr[i] >= best_level) {
+ best_level = s->icr[i];
+ best = i;
+ }
+ active >>= 1;
+ }
+ }
+ s->active_vector = ((best == 64) ? 24 : (best + 64));
+ m68k_set_irq_level(s->cpu, best_level, s->active_vector);
+}
+
+static uint64_t mcf_intc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ int offset;
+ mcf_intc_state *s = (mcf_intc_state *)opaque;
+ offset = addr & 0xff;
+ if (offset >= 0x40 && offset < 0x80) {
+ return s->icr[offset - 0x40];
+ }
+ switch (offset) {
+ case 0x00:
+ return (uint32_t)(s->ipr >> 32);
+ case 0x04:
+ return (uint32_t)s->ipr;
+ case 0x08:
+ return (uint32_t)(s->imr >> 32);
+ case 0x0c:
+ return (uint32_t)s->imr;
+ case 0x10:
+ return (uint32_t)(s->ifr >> 32);
+ case 0x14:
+ return (uint32_t)s->ifr;
+ case 0xe0: /* SWIACK. */
+ return s->active_vector;
+ case 0xe1: case 0xe2: case 0xe3: case 0xe4:
+ case 0xe5: case 0xe6: case 0xe7:
+ /* LnIACK */
+ hw_error("mcf_intc_read: LnIACK not implemented\n");
+ default:
+ return 0;
+ }
+}
+
+static void mcf_intc_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ int offset;
+ mcf_intc_state *s = (mcf_intc_state *)opaque;
+ offset = addr & 0xff;
+ if (offset >= 0x40 && offset < 0x80) {
+ int n = offset - 0x40;
+ s->icr[n] = val;
+ if (val == 0)
+ s->enabled &= ~(1ull << n);
+ else
+ s->enabled |= (1ull << n);
+ mcf_intc_update(s);
+ return;
+ }
+ switch (offset) {
+ case 0x00: case 0x04:
+ /* Ignore IPR writes. */
+ return;
+ case 0x08:
+ s->imr = (s->imr & 0xffffffff) | ((uint64_t)val << 32);
+ break;
+ case 0x0c:
+ s->imr = (s->imr & 0xffffffff00000000ull) | (uint32_t)val;
+ break;
+ case 0x1c:
+ if (val & 0x40) {
+ s->imr = ~0ull;
+ } else {
+ s->imr |= (0x1ull << (val & 0x3f));
+ }
+ break;
+ case 0x1d:
+ if (val & 0x40) {
+ s->imr = 0ull;
+ } else {
+ s->imr &= ~(0x1ull << (val & 0x3f));
+ }
+ break;
+ default:
+ hw_error("mcf_intc_write: Bad write offset %d\n", offset);
+ break;
+ }
+ mcf_intc_update(s);
+}
+
+static void mcf_intc_set_irq(void *opaque, int irq, int level)
+{
+ mcf_intc_state *s = (mcf_intc_state *)opaque;
+ if (irq >= 64)
+ return;
+ if (level)
+ s->ipr |= 1ull << irq;
+ else
+ s->ipr &= ~(1ull << irq);
+ mcf_intc_update(s);
+}
+
+static void mcf_intc_reset(mcf_intc_state *s)
+{
+ s->imr = ~0ull;
+ s->ipr = 0;
+ s->ifr = 0;
+ s->enabled = 0;
+ memset(s->icr, 0, 64);
+ s->active_vector = 24;
+}
+
+static const MemoryRegionOps mcf_intc_ops = {
+ .read = mcf_intc_read,
+ .write = mcf_intc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+qemu_irq *mcf_intc_init(MemoryRegion *sysmem,
+ hwaddr base,
+ M68kCPU *cpu)
+{
+ mcf_intc_state *s;
+
+ s = g_malloc0(sizeof(mcf_intc_state));
+ s->cpu = cpu;
+ mcf_intc_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &mcf_intc_ops, s, "mcf", 0x100);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ return qemu_allocate_irqs(mcf_intc_set_irq, s, 64);
+}
diff --git a/hw/mem/Makefile.objs b/hw/mem/Makefile.objs
new file mode 100644
index 00000000..b000fb42
--- /dev/null
+++ b/hw/mem/Makefile.objs
@@ -0,0 +1 @@
+common-obj-$(CONFIG_MEM_HOTPLUG) += pc-dimm.o
diff --git a/hw/mem/pc-dimm.c b/hw/mem/pc-dimm.c
new file mode 100644
index 00000000..bb04862d
--- /dev/null
+++ b/hw/mem/pc-dimm.c
@@ -0,0 +1,456 @@
+/*
+ * Dimm device for Memory Hotplug
+ *
+ * Copyright ProfitBricks GmbH 2012
+ * Copyright (C) 2014 Red Hat Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "hw/mem/pc-dimm.h"
+#include "qemu/config-file.h"
+#include "qapi/visitor.h"
+#include "qemu/range.h"
+#include "sysemu/numa.h"
+#include "sysemu/kvm.h"
+#include "trace.h"
+
+typedef struct pc_dimms_capacity {
+ uint64_t size;
+ Error **errp;
+} pc_dimms_capacity;
+
+void pc_dimm_memory_plug(DeviceState *dev, MemoryHotplugState *hpms,
+ MemoryRegion *mr, uint64_t align, Error **errp)
+{
+ int slot;
+ MachineState *machine = MACHINE(qdev_get_machine());
+ PCDIMMDevice *dimm = PC_DIMM(dev);
+ Error *local_err = NULL;
+ uint64_t existing_dimms_capacity = 0;
+ uint64_t addr;
+
+ addr = object_property_get_int(OBJECT(dimm), PC_DIMM_ADDR_PROP, &local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ addr = pc_dimm_get_free_addr(hpms->base,
+ memory_region_size(&hpms->mr),
+ !addr ? NULL : &addr, align,
+ memory_region_size(mr), &local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ existing_dimms_capacity = pc_existing_dimms_capacity(&local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ if (existing_dimms_capacity + memory_region_size(mr) >
+ machine->maxram_size - machine->ram_size) {
+ error_setg(&local_err, "not enough space, currently 0x%" PRIx64
+ " in use of total hot pluggable 0x" RAM_ADDR_FMT,
+ existing_dimms_capacity,
+ machine->maxram_size - machine->ram_size);
+ goto out;
+ }
+
+ object_property_set_int(OBJECT(dev), addr, PC_DIMM_ADDR_PROP, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ trace_mhp_pc_dimm_assigned_address(addr);
+
+ slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP, &local_err);
+ if (local_err) {
+ goto out;
+ }
+
+ slot = pc_dimm_get_free_slot(slot == PC_DIMM_UNASSIGNED_SLOT ? NULL : &slot,
+ machine->ram_slots, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ object_property_set_int(OBJECT(dev), slot, PC_DIMM_SLOT_PROP, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ trace_mhp_pc_dimm_assigned_slot(slot);
+
+ if (kvm_enabled() && !kvm_has_free_slot(machine)) {
+ error_setg(&local_err, "hypervisor has no free memory slots left");
+ goto out;
+ }
+
+ memory_region_add_subregion(&hpms->mr, addr - hpms->base, mr);
+ vmstate_register_ram(mr, dev);
+ numa_set_mem_node_id(addr, memory_region_size(mr), dimm->node);
+
+out:
+ error_propagate(errp, local_err);
+}
+
+void pc_dimm_memory_unplug(DeviceState *dev, MemoryHotplugState *hpms,
+ MemoryRegion *mr)
+{
+ PCDIMMDevice *dimm = PC_DIMM(dev);
+
+ numa_unset_mem_node_id(dimm->addr, memory_region_size(mr), dimm->node);
+ memory_region_del_subregion(&hpms->mr, mr);
+ vmstate_unregister_ram(mr, dev);
+}
+
+static int pc_existing_dimms_capacity_internal(Object *obj, void *opaque)
+{
+ pc_dimms_capacity *cap = opaque;
+ uint64_t *size = &cap->size;
+
+ if (object_dynamic_cast(obj, TYPE_PC_DIMM)) {
+ DeviceState *dev = DEVICE(obj);
+
+ if (dev->realized) {
+ (*size) += object_property_get_int(obj, PC_DIMM_SIZE_PROP,
+ cap->errp);
+ }
+
+ if (cap->errp && *cap->errp) {
+ return 1;
+ }
+ }
+ object_child_foreach(obj, pc_existing_dimms_capacity_internal, opaque);
+ return 0;
+}
+
+uint64_t pc_existing_dimms_capacity(Error **errp)
+{
+ pc_dimms_capacity cap;
+
+ cap.size = 0;
+ cap.errp = errp;
+
+ pc_existing_dimms_capacity_internal(qdev_get_machine(), &cap);
+ return cap.size;
+}
+
+int qmp_pc_dimm_device_list(Object *obj, void *opaque)
+{
+ MemoryDeviceInfoList ***prev = opaque;
+
+ if (object_dynamic_cast(obj, TYPE_PC_DIMM)) {
+ DeviceState *dev = DEVICE(obj);
+
+ if (dev->realized) {
+ MemoryDeviceInfoList *elem = g_new0(MemoryDeviceInfoList, 1);
+ MemoryDeviceInfo *info = g_new0(MemoryDeviceInfo, 1);
+ PCDIMMDeviceInfo *di = g_new0(PCDIMMDeviceInfo, 1);
+ DeviceClass *dc = DEVICE_GET_CLASS(obj);
+ PCDIMMDevice *dimm = PC_DIMM(obj);
+
+ if (dev->id) {
+ di->has_id = true;
+ di->id = g_strdup(dev->id);
+ }
+ di->hotplugged = dev->hotplugged;
+ di->hotpluggable = dc->hotpluggable;
+ di->addr = dimm->addr;
+ di->slot = dimm->slot;
+ di->node = dimm->node;
+ di->size = object_property_get_int(OBJECT(dimm), PC_DIMM_SIZE_PROP,
+ NULL);
+ di->memdev = object_get_canonical_path(OBJECT(dimm->hostmem));
+
+ info->dimm = di;
+ elem->value = info;
+ elem->next = NULL;
+ **prev = elem;
+ *prev = &elem->next;
+ }
+ }
+
+ object_child_foreach(obj, qmp_pc_dimm_device_list, opaque);
+ return 0;
+}
+
+ram_addr_t get_current_ram_size(void)
+{
+ MemoryDeviceInfoList *info_list = NULL;
+ MemoryDeviceInfoList **prev = &info_list;
+ MemoryDeviceInfoList *info;
+ ram_addr_t size = ram_size;
+
+ qmp_pc_dimm_device_list(qdev_get_machine(), &prev);
+ for (info = info_list; info; info = info->next) {
+ MemoryDeviceInfo *value = info->value;
+
+ if (value) {
+ switch (value->kind) {
+ case MEMORY_DEVICE_INFO_KIND_DIMM:
+ size += value->dimm->size;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ qapi_free_MemoryDeviceInfoList(info_list);
+
+ return size;
+}
+
+static int pc_dimm_slot2bitmap(Object *obj, void *opaque)
+{
+ unsigned long *bitmap = opaque;
+
+ if (object_dynamic_cast(obj, TYPE_PC_DIMM)) {
+ DeviceState *dev = DEVICE(obj);
+ if (dev->realized) { /* count only realized DIMMs */
+ PCDIMMDevice *d = PC_DIMM(obj);
+ set_bit(d->slot, bitmap);
+ }
+ }
+
+ object_child_foreach(obj, pc_dimm_slot2bitmap, opaque);
+ return 0;
+}
+
+int pc_dimm_get_free_slot(const int *hint, int max_slots, Error **errp)
+{
+ unsigned long *bitmap = bitmap_new(max_slots);
+ int slot = 0;
+
+ object_child_foreach(qdev_get_machine(), pc_dimm_slot2bitmap, bitmap);
+
+ /* check if requested slot is not occupied */
+ if (hint) {
+ if (*hint >= max_slots) {
+ error_setg(errp, "invalid slot# %d, should be less than %d",
+ *hint, max_slots);
+ } else if (!test_bit(*hint, bitmap)) {
+ slot = *hint;
+ } else {
+ error_setg(errp, "slot %d is busy", *hint);
+ }
+ goto out;
+ }
+
+ /* search for free slot */
+ slot = find_first_zero_bit(bitmap, max_slots);
+ if (slot == max_slots) {
+ error_setg(errp, "no free slots available");
+ }
+out:
+ g_free(bitmap);
+ return slot;
+}
+
+static gint pc_dimm_addr_sort(gconstpointer a, gconstpointer b)
+{
+ PCDIMMDevice *x = PC_DIMM(a);
+ PCDIMMDevice *y = PC_DIMM(b);
+ Int128 diff = int128_sub(int128_make64(x->addr), int128_make64(y->addr));
+
+ if (int128_lt(diff, int128_zero())) {
+ return -1;
+ } else if (int128_gt(diff, int128_zero())) {
+ return 1;
+ }
+ return 0;
+}
+
+static int pc_dimm_built_list(Object *obj, void *opaque)
+{
+ GSList **list = opaque;
+
+ if (object_dynamic_cast(obj, TYPE_PC_DIMM)) {
+ DeviceState *dev = DEVICE(obj);
+ if (dev->realized) { /* only realized DIMMs matter */
+ *list = g_slist_insert_sorted(*list, dev, pc_dimm_addr_sort);
+ }
+ }
+
+ object_child_foreach(obj, pc_dimm_built_list, opaque);
+ return 0;
+}
+
+uint64_t pc_dimm_get_free_addr(uint64_t address_space_start,
+ uint64_t address_space_size,
+ uint64_t *hint, uint64_t align, uint64_t size,
+ Error **errp)
+{
+ GSList *list = NULL, *item;
+ uint64_t new_addr, ret = 0;
+ uint64_t address_space_end = address_space_start + address_space_size;
+
+ g_assert(QEMU_ALIGN_UP(address_space_start, align) == address_space_start);
+
+ if (!address_space_size) {
+ error_setg(errp, "memory hotplug is not enabled, "
+ "please add maxmem option");
+ goto out;
+ }
+
+ if (hint && QEMU_ALIGN_UP(*hint, align) != *hint) {
+ error_setg(errp, "address must be aligned to 0x%" PRIx64 " bytes",
+ align);
+ goto out;
+ }
+
+ if (QEMU_ALIGN_UP(size, align) != size) {
+ error_setg(errp, "backend memory size must be multiple of 0x%"
+ PRIx64, align);
+ goto out;
+ }
+
+ assert(address_space_end > address_space_start);
+ object_child_foreach(qdev_get_machine(), pc_dimm_built_list, &list);
+
+ if (hint) {
+ new_addr = *hint;
+ } else {
+ new_addr = address_space_start;
+ }
+
+ /* find address range that will fit new DIMM */
+ for (item = list; item; item = g_slist_next(item)) {
+ PCDIMMDevice *dimm = item->data;
+ uint64_t dimm_size = object_property_get_int(OBJECT(dimm),
+ PC_DIMM_SIZE_PROP,
+ errp);
+ if (errp && *errp) {
+ goto out;
+ }
+
+ if (ranges_overlap(dimm->addr, dimm_size, new_addr, size)) {
+ if (hint) {
+ DeviceState *d = DEVICE(dimm);
+ error_setg(errp, "address range conflicts with '%s'", d->id);
+ goto out;
+ }
+ new_addr = QEMU_ALIGN_UP(dimm->addr + dimm_size, align);
+ }
+ }
+ ret = new_addr;
+
+ if (new_addr < address_space_start) {
+ error_setg(errp, "can't add memory [0x%" PRIx64 ":0x%" PRIx64
+ "] at 0x%" PRIx64, new_addr, size, address_space_start);
+ } else if ((new_addr + size) > address_space_end) {
+ error_setg(errp, "can't add memory [0x%" PRIx64 ":0x%" PRIx64
+ "] beyond 0x%" PRIx64, new_addr, size, address_space_end);
+ }
+
+out:
+ g_slist_free(list);
+ return ret;
+}
+
+static Property pc_dimm_properties[] = {
+ DEFINE_PROP_UINT64(PC_DIMM_ADDR_PROP, PCDIMMDevice, addr, 0),
+ DEFINE_PROP_UINT32(PC_DIMM_NODE_PROP, PCDIMMDevice, node, 0),
+ DEFINE_PROP_INT32(PC_DIMM_SLOT_PROP, PCDIMMDevice, slot,
+ PC_DIMM_UNASSIGNED_SLOT),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pc_dimm_get_size(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ int64_t value;
+ MemoryRegion *mr;
+ PCDIMMDevice *dimm = PC_DIMM(obj);
+
+ mr = host_memory_backend_get_memory(dimm->hostmem, errp);
+ value = memory_region_size(mr);
+
+ visit_type_int(v, &value, name, errp);
+}
+
+static void pc_dimm_check_memdev_is_busy(Object *obj, const char *name,
+ Object *val, Error **errp)
+{
+ MemoryRegion *mr;
+
+ mr = host_memory_backend_get_memory(MEMORY_BACKEND(val), errp);
+ if (memory_region_is_mapped(mr)) {
+ char *path = object_get_canonical_path_component(val);
+ error_setg(errp, "can't use already busy memdev: %s", path);
+ g_free(path);
+ } else {
+ qdev_prop_allow_set_link_before_realize(obj, name, val, errp);
+ }
+}
+
+static void pc_dimm_init(Object *obj)
+{
+ PCDIMMDevice *dimm = PC_DIMM(obj);
+
+ object_property_add(obj, PC_DIMM_SIZE_PROP, "int", pc_dimm_get_size,
+ NULL, NULL, NULL, &error_abort);
+ object_property_add_link(obj, PC_DIMM_MEMDEV_PROP, TYPE_MEMORY_BACKEND,
+ (Object **)&dimm->hostmem,
+ pc_dimm_check_memdev_is_busy,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &error_abort);
+}
+
+static void pc_dimm_realize(DeviceState *dev, Error **errp)
+{
+ PCDIMMDevice *dimm = PC_DIMM(dev);
+
+ if (!dimm->hostmem) {
+ error_setg(errp, "'" PC_DIMM_MEMDEV_PROP "' property is not set");
+ return;
+ }
+ if ((nb_numa_nodes > 0) && (dimm->node >= nb_numa_nodes)) {
+ error_setg(errp, "'DIMM property " PC_DIMM_NODE_PROP " has value %"
+ PRIu32 "' which exceeds the number of numa nodes: %d",
+ dimm->node, nb_numa_nodes);
+ return;
+ }
+}
+
+static MemoryRegion *pc_dimm_get_memory_region(PCDIMMDevice *dimm)
+{
+ return host_memory_backend_get_memory(dimm->hostmem, &error_abort);
+}
+
+static void pc_dimm_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ PCDIMMDeviceClass *ddc = PC_DIMM_CLASS(oc);
+
+ dc->realize = pc_dimm_realize;
+ dc->props = pc_dimm_properties;
+ dc->desc = "DIMM memory module";
+
+ ddc->get_memory_region = pc_dimm_get_memory_region;
+}
+
+static TypeInfo pc_dimm_info = {
+ .name = TYPE_PC_DIMM,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(PCDIMMDevice),
+ .instance_init = pc_dimm_init,
+ .class_init = pc_dimm_class_init,
+ .class_size = sizeof(PCDIMMDeviceClass),
+};
+
+static void pc_dimm_register_types(void)
+{
+ type_register_static(&pc_dimm_info);
+}
+
+type_init(pc_dimm_register_types)
diff --git a/hw/microblaze/Makefile.objs b/hw/microblaze/Makefile.objs
new file mode 100644
index 00000000..b2517d87
--- /dev/null
+++ b/hw/microblaze/Makefile.objs
@@ -0,0 +1,3 @@
+obj-y += petalogix_s3adsp1800_mmu.o
+obj-y += petalogix_ml605_mmu.o
+obj-y += boot.o
diff --git a/hw/microblaze/boot.c b/hw/microblaze/boot.c
new file mode 100644
index 00000000..3e8820f3
--- /dev/null
+++ b/hw/microblaze/boot.c
@@ -0,0 +1,211 @@
+/*
+ * Microblaze kernel loader
+ *
+ * Copyright (c) 2012 Peter Crosthwaite <peter.crosthwaite@petalogix.com>
+ * Copyright (c) 2012 PetaLogix
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+#include "qemu-common.h"
+#include "sysemu/device_tree.h"
+#include "sysemu/sysemu.h"
+#include "hw/loader.h"
+#include "elf.h"
+
+#include "boot.h"
+
+static struct
+{
+ void (*machine_cpu_reset)(MicroBlazeCPU *);
+ uint32_t bootstrap_pc;
+ uint32_t cmdline;
+ uint32_t initrd_start;
+ uint32_t initrd_end;
+ uint32_t fdt;
+} boot_info;
+
+static void main_cpu_reset(void *opaque)
+{
+ MicroBlazeCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+ CPUMBState *env = &cpu->env;
+
+ cpu_reset(cs);
+ env->regs[5] = boot_info.cmdline;
+ env->regs[6] = boot_info.initrd_start;
+ env->regs[7] = boot_info.fdt;
+ cpu_set_pc(cs, boot_info.bootstrap_pc);
+ if (boot_info.machine_cpu_reset) {
+ boot_info.machine_cpu_reset(cpu);
+ }
+}
+
+static int microblaze_load_dtb(hwaddr addr,
+ uint32_t ramsize,
+ uint32_t initrd_start,
+ uint32_t initrd_end,
+ const char *kernel_cmdline,
+ const char *dtb_filename)
+{
+ int fdt_size;
+ void *fdt = NULL;
+ int r;
+
+ if (dtb_filename) {
+ fdt = load_device_tree(dtb_filename, &fdt_size);
+ }
+ if (!fdt) {
+ return 0;
+ }
+
+ if (kernel_cmdline) {
+ r = qemu_fdt_setprop_string(fdt, "/chosen", "bootargs",
+ kernel_cmdline);
+ if (r < 0) {
+ fprintf(stderr, "couldn't set /chosen/bootargs\n");
+ }
+ }
+
+ if (initrd_start) {
+ qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-start",
+ initrd_start);
+
+ qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-end",
+ initrd_end);
+ }
+
+ cpu_physical_memory_write(addr, fdt, fdt_size);
+ return fdt_size;
+}
+
+static uint64_t translate_kernel_address(void *opaque, uint64_t addr)
+{
+ return addr - 0x30000000LL;
+}
+
+void microblaze_load_kernel(MicroBlazeCPU *cpu, hwaddr ddr_base,
+ uint32_t ramsize,
+ const char *initrd_filename,
+ const char *dtb_filename,
+ void (*machine_cpu_reset)(MicroBlazeCPU *))
+{
+ QemuOpts *machine_opts;
+ const char *kernel_filename;
+ const char *kernel_cmdline;
+ const char *dtb_arg;
+ char *filename = NULL;
+
+ machine_opts = qemu_get_machine_opts();
+ kernel_filename = qemu_opt_get(machine_opts, "kernel");
+ kernel_cmdline = qemu_opt_get(machine_opts, "append");
+ dtb_arg = qemu_opt_get(machine_opts, "dtb");
+ /* default to pcbios dtb as passed by machine_init */
+ if (!dtb_arg) {
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, dtb_filename);
+ }
+
+ boot_info.machine_cpu_reset = machine_cpu_reset;
+ qemu_register_reset(main_cpu_reset, cpu);
+
+ if (kernel_filename) {
+ int kernel_size;
+ uint64_t entry, low, high;
+ uint32_t base32;
+ int big_endian = 0;
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ big_endian = 1;
+#endif
+
+ /* Boots a kernel elf binary. */
+ kernel_size = load_elf(kernel_filename, NULL, NULL,
+ &entry, &low, &high,
+ big_endian, ELF_MACHINE, 0);
+ base32 = entry;
+ if (base32 == 0xc0000000) {
+ kernel_size = load_elf(kernel_filename, translate_kernel_address,
+ NULL, &entry, NULL, NULL,
+ big_endian, ELF_MACHINE, 0);
+ }
+ /* Always boot into physical ram. */
+ boot_info.bootstrap_pc = (uint32_t)entry;
+
+ /* If it wasn't an ELF image, try an u-boot image. */
+ if (kernel_size < 0) {
+ hwaddr uentry, loadaddr;
+
+ kernel_size = load_uimage(kernel_filename, &uentry, &loadaddr, 0,
+ NULL, NULL);
+ boot_info.bootstrap_pc = uentry;
+ high = (loadaddr + kernel_size + 3) & ~3;
+ }
+
+ /* Not an ELF image nor an u-boot image, try a RAW image. */
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename, ddr_base,
+ ram_size);
+ boot_info.bootstrap_pc = ddr_base;
+ high = (ddr_base + kernel_size + 3) & ~3;
+ }
+
+ if (initrd_filename) {
+ int initrd_size;
+ uint32_t initrd_offset;
+
+ high = ROUND_UP(high + kernel_size, 4);
+ boot_info.initrd_start = high;
+ initrd_offset = boot_info.initrd_start - ddr_base;
+
+ initrd_size = load_ramdisk(initrd_filename,
+ boot_info.initrd_start,
+ ram_size - initrd_offset);
+ if (initrd_size < 0) {
+ initrd_size = load_image_targphys(initrd_filename,
+ boot_info.initrd_start,
+ ram_size - initrd_offset);
+ }
+ if (initrd_size < 0) {
+ error_report("qemu: could not load initrd '%s'",
+ initrd_filename);
+ exit(EXIT_FAILURE);
+ }
+ boot_info.initrd_end = boot_info.initrd_start + initrd_size;
+ high = ROUND_UP(high + initrd_size, 4);
+ }
+
+ boot_info.cmdline = high + 4096;
+ if (kernel_cmdline && strlen(kernel_cmdline)) {
+ pstrcpy_targphys("cmdline", boot_info.cmdline, 256, kernel_cmdline);
+ }
+ /* Provide a device-tree. */
+ boot_info.fdt = boot_info.cmdline + 4096;
+ microblaze_load_dtb(boot_info.fdt, ram_size,
+ boot_info.initrd_start,
+ boot_info.initrd_end,
+ kernel_cmdline,
+ /* Preference a -dtb argument */
+ dtb_arg ? dtb_arg : filename);
+ }
+ g_free(filename);
+}
diff --git a/hw/microblaze/boot.h b/hw/microblaze/boot.h
new file mode 100644
index 00000000..0eb7f8e4
--- /dev/null
+++ b/hw/microblaze/boot.h
@@ -0,0 +1,12 @@
+#ifndef __MICROBLAZE_BOOT__
+#define __MICROBLAZE_BOOT__
+
+#include "hw/hw.h"
+
+void microblaze_load_kernel(MicroBlazeCPU *cpu, hwaddr ddr_base,
+ uint32_t ramsize,
+ const char *initrd_filename,
+ const char *dtb_filename,
+ void (*machine_cpu_reset)(MicroBlazeCPU *));
+
+#endif /* __MICROBLAZE_BOOT __ */
diff --git a/hw/microblaze/petalogix_ml605_mmu.c b/hw/microblaze/petalogix_ml605_mmu.c
new file mode 100644
index 00000000..ed84a37e
--- /dev/null
+++ b/hw/microblaze/petalogix_ml605_mmu.c
@@ -0,0 +1,221 @@
+/*
+ * Model of Petalogix linux reference design targeting Xilinx Spartan ml605
+ * board.
+ *
+ * Copyright (c) 2011 Michal Simek <monstr@monstr.eu>
+ * Copyright (c) 2011 PetaLogix
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "net/net.h"
+#include "hw/block/flash.h"
+#include "sysemu/sysemu.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "sysemu/block-backend.h"
+#include "hw/char/serial.h"
+#include "exec/address-spaces.h"
+#include "hw/ssi.h"
+
+#include "boot.h"
+
+#include "hw/stream.h"
+
+#define LMB_BRAM_SIZE (128 * 1024)
+#define FLASH_SIZE (32 * 1024 * 1024)
+
+#define BINARY_DEVICE_TREE_FILE "petalogix-ml605.dtb"
+
+#define NUM_SPI_FLASHES 4
+
+#define SPI_BASEADDR 0x40a00000
+#define MEMORY_BASEADDR 0x50000000
+#define FLASH_BASEADDR 0x86000000
+#define INTC_BASEADDR 0x81800000
+#define TIMER_BASEADDR 0x83c00000
+#define UART16550_BASEADDR 0x83e00000
+#define AXIENET_BASEADDR 0x82780000
+#define AXIDMA_BASEADDR 0x84600000
+
+#define AXIDMA_IRQ1 0
+#define AXIDMA_IRQ0 1
+#define TIMER_IRQ 2
+#define AXIENET_IRQ 3
+#define SPI_IRQ 4
+#define UART16550_IRQ 5
+
+static void
+petalogix_ml605_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ MemoryRegion *address_space_mem = get_system_memory();
+ DeviceState *dev, *dma, *eth0;
+ Object *ds, *cs;
+ MicroBlazeCPU *cpu;
+ SysBusDevice *busdev;
+ DriveInfo *dinfo;
+ int i;
+ MemoryRegion *phys_lmb_bram = g_new(MemoryRegion, 1);
+ MemoryRegion *phys_ram = g_new(MemoryRegion, 1);
+ qemu_irq irq[32];
+
+ /* init CPUs */
+ cpu = MICROBLAZE_CPU(object_new(TYPE_MICROBLAZE_CPU));
+ /* Use FPU but don't use floating point conversion and square
+ * root instructions
+ */
+ object_property_set_int(OBJECT(cpu), 1, "use-fpu", &error_abort);
+ object_property_set_bool(OBJECT(cpu), true, "dcache-writeback",
+ &error_abort);
+ object_property_set_bool(OBJECT(cpu), true, "endianness", &error_abort);
+ object_property_set_bool(OBJECT(cpu), true, "realized", &error_abort);
+
+ /* Attach emulated BRAM through the LMB. */
+ memory_region_init_ram(phys_lmb_bram, NULL, "petalogix_ml605.lmb_bram",
+ LMB_BRAM_SIZE, &error_abort);
+ vmstate_register_ram_global(phys_lmb_bram);
+ memory_region_add_subregion(address_space_mem, 0x00000000, phys_lmb_bram);
+
+ memory_region_init_ram(phys_ram, NULL, "petalogix_ml605.ram", ram_size,
+ &error_abort);
+ vmstate_register_ram_global(phys_ram);
+ memory_region_add_subregion(address_space_mem, MEMORY_BASEADDR, phys_ram);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ /* 5th parameter 2 means bank-width
+ * 10th paremeter 0 means little-endian */
+ pflash_cfi01_register(FLASH_BASEADDR,
+ NULL, "petalogix_ml605.flash", FLASH_SIZE,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ (64 * 1024), FLASH_SIZE >> 16,
+ 2, 0x89, 0x18, 0x0000, 0x0, 0);
+
+
+ dev = qdev_create(NULL, "xlnx.xps-intc");
+ qdev_prop_set_uint32(dev, "kind-of-intr", 1 << TIMER_IRQ);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, INTC_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
+ qdev_get_gpio_in(DEVICE(cpu), MB_CPU_IRQ));
+ for (i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ serial_mm_init(address_space_mem, UART16550_BASEADDR + 0x1000, 2,
+ irq[UART16550_IRQ], 115200, serial_hds[0],
+ DEVICE_LITTLE_ENDIAN);
+
+ /* 2 timers at irq 2 @ 100 Mhz. */
+ dev = qdev_create(NULL, "xlnx.xps-timer");
+ qdev_prop_set_uint32(dev, "one-timer-only", 0);
+ qdev_prop_set_uint32(dev, "clock-frequency", 100 * 1000000);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, TIMER_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq[TIMER_IRQ]);
+
+ /* axi ethernet and dma initialization. */
+ qemu_check_nic_model(&nd_table[0], "xlnx.axi-ethernet");
+ eth0 = qdev_create(NULL, "xlnx.axi-ethernet");
+ dma = qdev_create(NULL, "xlnx.axi-dma");
+
+ /* FIXME: attach to the sysbus instead */
+ object_property_add_child(qdev_get_machine(), "xilinx-eth", OBJECT(eth0),
+ NULL);
+ object_property_add_child(qdev_get_machine(), "xilinx-dma", OBJECT(dma),
+ NULL);
+
+ ds = object_property_get_link(OBJECT(dma),
+ "axistream-connected-target", NULL);
+ cs = object_property_get_link(OBJECT(dma),
+ "axistream-control-connected-target", NULL);
+ qdev_set_nic_properties(eth0, &nd_table[0]);
+ qdev_prop_set_uint32(eth0, "rxmem", 0x1000);
+ qdev_prop_set_uint32(eth0, "txmem", 0x1000);
+ object_property_set_link(OBJECT(eth0), OBJECT(ds),
+ "axistream-connected", &error_abort);
+ object_property_set_link(OBJECT(eth0), OBJECT(cs),
+ "axistream-control-connected", &error_abort);
+ qdev_init_nofail(eth0);
+ sysbus_mmio_map(SYS_BUS_DEVICE(eth0), 0, AXIENET_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(eth0), 0, irq[AXIENET_IRQ]);
+
+ ds = object_property_get_link(OBJECT(eth0),
+ "axistream-connected-target", NULL);
+ cs = object_property_get_link(OBJECT(eth0),
+ "axistream-control-connected-target", NULL);
+ qdev_prop_set_uint32(dma, "freqhz", 100 * 1000000);
+ object_property_set_link(OBJECT(dma), OBJECT(ds),
+ "axistream-connected", &error_abort);
+ object_property_set_link(OBJECT(dma), OBJECT(cs),
+ "axistream-control-connected", &error_abort);
+ qdev_init_nofail(dma);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dma), 0, AXIDMA_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dma), 0, irq[AXIDMA_IRQ0]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dma), 1, irq[AXIDMA_IRQ1]);
+
+ {
+ SSIBus *spi;
+
+ dev = qdev_create(NULL, "xlnx.xps-spi");
+ qdev_prop_set_uint8(dev, "num-ss-bits", NUM_SPI_FLASHES);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, SPI_BASEADDR);
+ sysbus_connect_irq(busdev, 0, irq[SPI_IRQ]);
+
+ spi = (SSIBus *)qdev_get_child_bus(dev, "spi");
+
+ for (i = 0; i < NUM_SPI_FLASHES; i++) {
+ qemu_irq cs_line;
+
+ dev = ssi_create_slave(spi, "n25q128");
+ cs_line = qdev_get_gpio_in_named(dev, SSI_GPIO_CS, 0);
+ sysbus_connect_irq(busdev, i+1, cs_line);
+ }
+ }
+
+ /* setup PVR to match kernel settings */
+ cpu->env.pvr.regs[4] = 0xc56b8000;
+ cpu->env.pvr.regs[5] = 0xc56be000;
+ cpu->env.pvr.regs[10] = 0x0e000000; /* virtex 6 */
+
+ microblaze_load_kernel(cpu, MEMORY_BASEADDR, ram_size,
+ machine->initrd_filename,
+ BINARY_DEVICE_TREE_FILE,
+ NULL);
+
+}
+
+static QEMUMachine petalogix_ml605_machine = {
+ .name = "petalogix-ml605",
+ .desc = "PetaLogix linux refdesign for xilinx ml605 little endian",
+ .init = petalogix_ml605_init,
+ .is_default = 0,
+};
+
+static void petalogix_ml605_machine_init(void)
+{
+ qemu_register_machine(&petalogix_ml605_machine);
+}
+
+machine_init(petalogix_ml605_machine_init);
diff --git a/hw/microblaze/petalogix_s3adsp1800_mmu.c b/hw/microblaze/petalogix_s3adsp1800_mmu.c
new file mode 100644
index 00000000..0c2140c3
--- /dev/null
+++ b/hw/microblaze/petalogix_s3adsp1800_mmu.c
@@ -0,0 +1,139 @@
+/*
+ * Model of Petalogix linux reference design targeting Xilinx Spartan 3ADSP-1800
+ * boards.
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "net/net.h"
+#include "hw/block/flash.h"
+#include "sysemu/sysemu.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+
+#include "boot.h"
+
+#define LMB_BRAM_SIZE (128 * 1024)
+#define FLASH_SIZE (16 * 1024 * 1024)
+
+#define BINARY_DEVICE_TREE_FILE "petalogix-s3adsp1800.dtb"
+
+#define MEMORY_BASEADDR 0x90000000
+#define FLASH_BASEADDR 0xa0000000
+#define INTC_BASEADDR 0x81800000
+#define TIMER_BASEADDR 0x83c00000
+#define UARTLITE_BASEADDR 0x84000000
+#define ETHLITE_BASEADDR 0x81000000
+
+#define TIMER_IRQ 0
+#define ETHLITE_IRQ 1
+#define UARTLITE_IRQ 3
+
+static void
+petalogix_s3adsp1800_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ DeviceState *dev;
+ MicroBlazeCPU *cpu;
+ DriveInfo *dinfo;
+ int i;
+ hwaddr ddr_base = MEMORY_BASEADDR;
+ MemoryRegion *phys_lmb_bram = g_new(MemoryRegion, 1);
+ MemoryRegion *phys_ram = g_new(MemoryRegion, 1);
+ qemu_irq irq[32];
+ MemoryRegion *sysmem = get_system_memory();
+
+ cpu = MICROBLAZE_CPU(object_new(TYPE_MICROBLAZE_CPU));
+ object_property_set_bool(OBJECT(cpu), true, "realized", &error_abort);
+
+ /* Attach emulated BRAM through the LMB. */
+ memory_region_init_ram(phys_lmb_bram, NULL,
+ "petalogix_s3adsp1800.lmb_bram", LMB_BRAM_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(phys_lmb_bram);
+ memory_region_add_subregion(sysmem, 0x00000000, phys_lmb_bram);
+
+ memory_region_init_ram(phys_ram, NULL, "petalogix_s3adsp1800.ram",
+ ram_size, &error_abort);
+ vmstate_register_ram_global(phys_ram);
+ memory_region_add_subregion(sysmem, ddr_base, phys_ram);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ pflash_cfi01_register(FLASH_BASEADDR,
+ NULL, "petalogix_s3adsp1800.flash", FLASH_SIZE,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ (64 * 1024), FLASH_SIZE >> 16,
+ 1, 0x89, 0x18, 0x0000, 0x0, 1);
+
+ dev = qdev_create(NULL, "xlnx.xps-intc");
+ qdev_prop_set_uint32(dev, "kind-of-intr",
+ 1 << ETHLITE_IRQ | 1 << UARTLITE_IRQ);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, INTC_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
+ qdev_get_gpio_in(DEVICE(cpu), MB_CPU_IRQ));
+ for (i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ sysbus_create_simple("xlnx.xps-uartlite", UARTLITE_BASEADDR,
+ irq[UARTLITE_IRQ]);
+
+ /* 2 timers at irq 2 @ 62 Mhz. */
+ dev = qdev_create(NULL, "xlnx.xps-timer");
+ qdev_prop_set_uint32(dev, "one-timer-only", 0);
+ qdev_prop_set_uint32(dev, "clock-frequency", 62 * 1000000);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, TIMER_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq[TIMER_IRQ]);
+
+ qemu_check_nic_model(&nd_table[0], "xlnx.xps-ethernetlite");
+ dev = qdev_create(NULL, "xlnx.xps-ethernetlite");
+ qdev_set_nic_properties(dev, &nd_table[0]);
+ qdev_prop_set_uint32(dev, "tx-ping-pong", 0);
+ qdev_prop_set_uint32(dev, "rx-ping-pong", 0);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, ETHLITE_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq[ETHLITE_IRQ]);
+
+ microblaze_load_kernel(cpu, ddr_base, ram_size,
+ machine->initrd_filename,
+ BINARY_DEVICE_TREE_FILE,
+ NULL);
+}
+
+static QEMUMachine petalogix_s3adsp1800_machine = {
+ .name = "petalogix-s3adsp1800",
+ .desc = "PetaLogix linux refdesign for xilinx Spartan 3ADSP1800",
+ .init = petalogix_s3adsp1800_init,
+ .is_default = 1,
+};
+
+static void petalogix_s3adsp1800_machine_init(void)
+{
+ qemu_register_machine(&petalogix_s3adsp1800_machine);
+}
+
+machine_init(petalogix_s3adsp1800_machine_init);
diff --git a/hw/mips/Makefile.objs b/hw/mips/Makefile.objs
new file mode 100644
index 00000000..9633f3a5
--- /dev/null
+++ b/hw/mips/Makefile.objs
@@ -0,0 +1,5 @@
+obj-y += mips_r4k.o mips_malta.o mips_mipssim.o
+obj-y += addr.o cputimer.o mips_int.o
+obj-$(CONFIG_JAZZ) += mips_jazz.o
+obj-$(CONFIG_FULONG) += mips_fulong2e.o
+obj-y += gt64xxx_pci.o
diff --git a/hw/mips/addr.c b/hw/mips/addr.c
new file mode 100644
index 00000000..ff3b9526
--- /dev/null
+++ b/hw/mips/addr.c
@@ -0,0 +1,39 @@
+/*
+ * QEMU MIPS address translation support
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/mips/cpudevs.h"
+
+uint64_t cpu_mips_kseg0_to_phys(void *opaque, uint64_t addr)
+{
+ return addr & 0x1fffffffll;
+}
+
+uint64_t cpu_mips_phys_to_kseg0(void *opaque, uint64_t addr)
+{
+ return addr | ~0x7fffffffll;
+}
+
+uint64_t cpu_mips_kvm_um_phys_to_kseg0(void *opaque, uint64_t addr)
+{
+ return addr | 0x40000000ll;
+}
diff --git a/hw/mips/cputimer.c b/hw/mips/cputimer.c
new file mode 100644
index 00000000..577c9aea
--- /dev/null
+++ b/hw/mips/cputimer.c
@@ -0,0 +1,157 @@
+/*
+ * QEMU MIPS timer support
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/mips/cpudevs.h"
+#include "qemu/timer.h"
+#include "sysemu/kvm.h"
+
+#define TIMER_FREQ 100 * 1000 * 1000
+
+/* XXX: do not use a global */
+uint32_t cpu_mips_get_random (CPUMIPSState *env)
+{
+ static uint32_t lfsr = 1;
+ static uint32_t prev_idx = 0;
+ uint32_t idx;
+ /* Don't return same value twice, so get another value */
+ do {
+ lfsr = (lfsr >> 1) ^ (-(lfsr & 1u) & 0xd0000001u);
+ idx = lfsr % (env->tlb->nb_tlb - env->CP0_Wired) + env->CP0_Wired;
+ } while (idx == prev_idx);
+ prev_idx = idx;
+ return idx;
+}
+
+/* MIPS R4K timer */
+static void cpu_mips_timer_update(CPUMIPSState *env)
+{
+ uint64_t now, next;
+ uint32_t wait;
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ wait = env->CP0_Compare - env->CP0_Count -
+ (uint32_t)muldiv64(now, TIMER_FREQ, get_ticks_per_sec());
+ next = now + muldiv64(wait, get_ticks_per_sec(), TIMER_FREQ);
+ timer_mod(env->timer, next);
+}
+
+/* Expire the timer. */
+static void cpu_mips_timer_expire(CPUMIPSState *env)
+{
+ cpu_mips_timer_update(env);
+ if (env->insn_flags & ISA_MIPS32R2) {
+ env->CP0_Cause |= 1 << CP0Ca_TI;
+ }
+ qemu_irq_raise(env->irq[(env->CP0_IntCtl >> CP0IntCtl_IPTI) & 0x7]);
+}
+
+uint32_t cpu_mips_get_count (CPUMIPSState *env)
+{
+ if (env->CP0_Cause & (1 << CP0Ca_DC)) {
+ return env->CP0_Count;
+ } else {
+ uint64_t now;
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ if (timer_pending(env->timer)
+ && timer_expired(env->timer, now)) {
+ /* The timer has already expired. */
+ cpu_mips_timer_expire(env);
+ }
+
+ return env->CP0_Count +
+ (uint32_t)muldiv64(now, TIMER_FREQ, get_ticks_per_sec());
+ }
+}
+
+void cpu_mips_store_count (CPUMIPSState *env, uint32_t count)
+{
+ /*
+ * This gets called from cpu_state_reset(), potentially before timer init.
+ * So env->timer may be NULL, which is also the case with KVM enabled so
+ * treat timer as disabled in that case.
+ */
+ if (env->CP0_Cause & (1 << CP0Ca_DC) || !env->timer)
+ env->CP0_Count = count;
+ else {
+ /* Store new count register */
+ env->CP0_Count =
+ count - (uint32_t)muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ TIMER_FREQ, get_ticks_per_sec());
+ /* Update timer timer */
+ cpu_mips_timer_update(env);
+ }
+}
+
+void cpu_mips_store_compare (CPUMIPSState *env, uint32_t value)
+{
+ env->CP0_Compare = value;
+ if (!(env->CP0_Cause & (1 << CP0Ca_DC)))
+ cpu_mips_timer_update(env);
+ if (env->insn_flags & ISA_MIPS32R2)
+ env->CP0_Cause &= ~(1 << CP0Ca_TI);
+ qemu_irq_lower(env->irq[(env->CP0_IntCtl >> CP0IntCtl_IPTI) & 0x7]);
+}
+
+void cpu_mips_start_count(CPUMIPSState *env)
+{
+ cpu_mips_store_count(env, env->CP0_Count);
+}
+
+void cpu_mips_stop_count(CPUMIPSState *env)
+{
+ /* Store the current value */
+ env->CP0_Count += (uint32_t)muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ TIMER_FREQ, get_ticks_per_sec());
+}
+
+static void mips_timer_cb (void *opaque)
+{
+ CPUMIPSState *env;
+
+ env = opaque;
+#if 0
+ qemu_log("%s\n", __func__);
+#endif
+
+ if (env->CP0_Cause & (1 << CP0Ca_DC))
+ return;
+
+ /* ??? This callback should occur when the counter is exactly equal to
+ the comparator value. Offset the count by one to avoid immediately
+ retriggering the callback before any virtual time has passed. */
+ env->CP0_Count++;
+ cpu_mips_timer_expire(env);
+ env->CP0_Count--;
+}
+
+void cpu_mips_clock_init (CPUMIPSState *env)
+{
+ /*
+ * If we're in KVM mode, don't create the periodic timer, that is handled in
+ * kernel.
+ */
+ if (!kvm_enabled()) {
+ env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &mips_timer_cb, env);
+ }
+}
diff --git a/hw/mips/gt64xxx_pci.c b/hw/mips/gt64xxx_pci.c
new file mode 100644
index 00000000..10fcca33
--- /dev/null
+++ b/hw/mips/gt64xxx_pci.c
@@ -0,0 +1,1259 @@
+/*
+ * QEMU GT64120 PCI host
+ *
+ * Copyright (c) 2006,2007 Aurelien Jarno
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/mips/mips.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "hw/i386/pc.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+#define DPRINTF(fmt, ...) fprintf(stderr, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+#define GT_REGS (0x1000 >> 2)
+
+/* CPU Configuration */
+#define GT_CPU (0x000 >> 2)
+#define GT_MULTI (0x120 >> 2)
+
+/* CPU Address Decode */
+#define GT_SCS10LD (0x008 >> 2)
+#define GT_SCS10HD (0x010 >> 2)
+#define GT_SCS32LD (0x018 >> 2)
+#define GT_SCS32HD (0x020 >> 2)
+#define GT_CS20LD (0x028 >> 2)
+#define GT_CS20HD (0x030 >> 2)
+#define GT_CS3BOOTLD (0x038 >> 2)
+#define GT_CS3BOOTHD (0x040 >> 2)
+#define GT_PCI0IOLD (0x048 >> 2)
+#define GT_PCI0IOHD (0x050 >> 2)
+#define GT_PCI0M0LD (0x058 >> 2)
+#define GT_PCI0M0HD (0x060 >> 2)
+#define GT_PCI0M1LD (0x080 >> 2)
+#define GT_PCI0M1HD (0x088 >> 2)
+#define GT_PCI1IOLD (0x090 >> 2)
+#define GT_PCI1IOHD (0x098 >> 2)
+#define GT_PCI1M0LD (0x0a0 >> 2)
+#define GT_PCI1M0HD (0x0a8 >> 2)
+#define GT_PCI1M1LD (0x0b0 >> 2)
+#define GT_PCI1M1HD (0x0b8 >> 2)
+#define GT_ISD (0x068 >> 2)
+
+#define GT_SCS10AR (0x0d0 >> 2)
+#define GT_SCS32AR (0x0d8 >> 2)
+#define GT_CS20R (0x0e0 >> 2)
+#define GT_CS3BOOTR (0x0e8 >> 2)
+
+#define GT_PCI0IOREMAP (0x0f0 >> 2)
+#define GT_PCI0M0REMAP (0x0f8 >> 2)
+#define GT_PCI0M1REMAP (0x100 >> 2)
+#define GT_PCI1IOREMAP (0x108 >> 2)
+#define GT_PCI1M0REMAP (0x110 >> 2)
+#define GT_PCI1M1REMAP (0x118 >> 2)
+
+/* CPU Error Report */
+#define GT_CPUERR_ADDRLO (0x070 >> 2)
+#define GT_CPUERR_ADDRHI (0x078 >> 2)
+#define GT_CPUERR_DATALO (0x128 >> 2) /* GT-64120A only */
+#define GT_CPUERR_DATAHI (0x130 >> 2) /* GT-64120A only */
+#define GT_CPUERR_PARITY (0x138 >> 2) /* GT-64120A only */
+
+/* CPU Sync Barrier */
+#define GT_PCI0SYNC (0x0c0 >> 2)
+#define GT_PCI1SYNC (0x0c8 >> 2)
+
+/* SDRAM and Device Address Decode */
+#define GT_SCS0LD (0x400 >> 2)
+#define GT_SCS0HD (0x404 >> 2)
+#define GT_SCS1LD (0x408 >> 2)
+#define GT_SCS1HD (0x40c >> 2)
+#define GT_SCS2LD (0x410 >> 2)
+#define GT_SCS2HD (0x414 >> 2)
+#define GT_SCS3LD (0x418 >> 2)
+#define GT_SCS3HD (0x41c >> 2)
+#define GT_CS0LD (0x420 >> 2)
+#define GT_CS0HD (0x424 >> 2)
+#define GT_CS1LD (0x428 >> 2)
+#define GT_CS1HD (0x42c >> 2)
+#define GT_CS2LD (0x430 >> 2)
+#define GT_CS2HD (0x434 >> 2)
+#define GT_CS3LD (0x438 >> 2)
+#define GT_CS3HD (0x43c >> 2)
+#define GT_BOOTLD (0x440 >> 2)
+#define GT_BOOTHD (0x444 >> 2)
+#define GT_ADERR (0x470 >> 2)
+
+/* SDRAM Configuration */
+#define GT_SDRAM_CFG (0x448 >> 2)
+#define GT_SDRAM_OPMODE (0x474 >> 2)
+#define GT_SDRAM_BM (0x478 >> 2)
+#define GT_SDRAM_ADDRDECODE (0x47c >> 2)
+
+/* SDRAM Parameters */
+#define GT_SDRAM_B0 (0x44c >> 2)
+#define GT_SDRAM_B1 (0x450 >> 2)
+#define GT_SDRAM_B2 (0x454 >> 2)
+#define GT_SDRAM_B3 (0x458 >> 2)
+
+/* Device Parameters */
+#define GT_DEV_B0 (0x45c >> 2)
+#define GT_DEV_B1 (0x460 >> 2)
+#define GT_DEV_B2 (0x464 >> 2)
+#define GT_DEV_B3 (0x468 >> 2)
+#define GT_DEV_BOOT (0x46c >> 2)
+
+/* ECC */
+#define GT_ECC_ERRDATALO (0x480 >> 2) /* GT-64120A only */
+#define GT_ECC_ERRDATAHI (0x484 >> 2) /* GT-64120A only */
+#define GT_ECC_MEM (0x488 >> 2) /* GT-64120A only */
+#define GT_ECC_CALC (0x48c >> 2) /* GT-64120A only */
+#define GT_ECC_ERRADDR (0x490 >> 2) /* GT-64120A only */
+
+/* DMA Record */
+#define GT_DMA0_CNT (0x800 >> 2)
+#define GT_DMA1_CNT (0x804 >> 2)
+#define GT_DMA2_CNT (0x808 >> 2)
+#define GT_DMA3_CNT (0x80c >> 2)
+#define GT_DMA0_SA (0x810 >> 2)
+#define GT_DMA1_SA (0x814 >> 2)
+#define GT_DMA2_SA (0x818 >> 2)
+#define GT_DMA3_SA (0x81c >> 2)
+#define GT_DMA0_DA (0x820 >> 2)
+#define GT_DMA1_DA (0x824 >> 2)
+#define GT_DMA2_DA (0x828 >> 2)
+#define GT_DMA3_DA (0x82c >> 2)
+#define GT_DMA0_NEXT (0x830 >> 2)
+#define GT_DMA1_NEXT (0x834 >> 2)
+#define GT_DMA2_NEXT (0x838 >> 2)
+#define GT_DMA3_NEXT (0x83c >> 2)
+#define GT_DMA0_CUR (0x870 >> 2)
+#define GT_DMA1_CUR (0x874 >> 2)
+#define GT_DMA2_CUR (0x878 >> 2)
+#define GT_DMA3_CUR (0x87c >> 2)
+
+/* DMA Channel Control */
+#define GT_DMA0_CTRL (0x840 >> 2)
+#define GT_DMA1_CTRL (0x844 >> 2)
+#define GT_DMA2_CTRL (0x848 >> 2)
+#define GT_DMA3_CTRL (0x84c >> 2)
+
+/* DMA Arbiter */
+#define GT_DMA_ARB (0x860 >> 2)
+
+/* Timer/Counter */
+#define GT_TC0 (0x850 >> 2)
+#define GT_TC1 (0x854 >> 2)
+#define GT_TC2 (0x858 >> 2)
+#define GT_TC3 (0x85c >> 2)
+#define GT_TC_CONTROL (0x864 >> 2)
+
+/* PCI Internal */
+#define GT_PCI0_CMD (0xc00 >> 2)
+#define GT_PCI0_TOR (0xc04 >> 2)
+#define GT_PCI0_BS_SCS10 (0xc08 >> 2)
+#define GT_PCI0_BS_SCS32 (0xc0c >> 2)
+#define GT_PCI0_BS_CS20 (0xc10 >> 2)
+#define GT_PCI0_BS_CS3BT (0xc14 >> 2)
+#define GT_PCI1_IACK (0xc30 >> 2)
+#define GT_PCI0_IACK (0xc34 >> 2)
+#define GT_PCI0_BARE (0xc3c >> 2)
+#define GT_PCI0_PREFMBR (0xc40 >> 2)
+#define GT_PCI0_SCS10_BAR (0xc48 >> 2)
+#define GT_PCI0_SCS32_BAR (0xc4c >> 2)
+#define GT_PCI0_CS20_BAR (0xc50 >> 2)
+#define GT_PCI0_CS3BT_BAR (0xc54 >> 2)
+#define GT_PCI0_SSCS10_BAR (0xc58 >> 2)
+#define GT_PCI0_SSCS32_BAR (0xc5c >> 2)
+#define GT_PCI0_SCS3BT_BAR (0xc64 >> 2)
+#define GT_PCI1_CMD (0xc80 >> 2)
+#define GT_PCI1_TOR (0xc84 >> 2)
+#define GT_PCI1_BS_SCS10 (0xc88 >> 2)
+#define GT_PCI1_BS_SCS32 (0xc8c >> 2)
+#define GT_PCI1_BS_CS20 (0xc90 >> 2)
+#define GT_PCI1_BS_CS3BT (0xc94 >> 2)
+#define GT_PCI1_BARE (0xcbc >> 2)
+#define GT_PCI1_PREFMBR (0xcc0 >> 2)
+#define GT_PCI1_SCS10_BAR (0xcc8 >> 2)
+#define GT_PCI1_SCS32_BAR (0xccc >> 2)
+#define GT_PCI1_CS20_BAR (0xcd0 >> 2)
+#define GT_PCI1_CS3BT_BAR (0xcd4 >> 2)
+#define GT_PCI1_SSCS10_BAR (0xcd8 >> 2)
+#define GT_PCI1_SSCS32_BAR (0xcdc >> 2)
+#define GT_PCI1_SCS3BT_BAR (0xce4 >> 2)
+#define GT_PCI1_CFGADDR (0xcf0 >> 2)
+#define GT_PCI1_CFGDATA (0xcf4 >> 2)
+#define GT_PCI0_CFGADDR (0xcf8 >> 2)
+#define GT_PCI0_CFGDATA (0xcfc >> 2)
+
+/* Interrupts */
+#define GT_INTRCAUSE (0xc18 >> 2)
+#define GT_INTRMASK (0xc1c >> 2)
+#define GT_PCI0_ICMASK (0xc24 >> 2)
+#define GT_PCI0_SERR0MASK (0xc28 >> 2)
+#define GT_CPU_INTSEL (0xc70 >> 2)
+#define GT_PCI0_INTSEL (0xc74 >> 2)
+#define GT_HINTRCAUSE (0xc98 >> 2)
+#define GT_HINTRMASK (0xc9c >> 2)
+#define GT_PCI0_HICMASK (0xca4 >> 2)
+#define GT_PCI1_SERR1MASK (0xca8 >> 2)
+
+#define PCI_MAPPING_ENTRY(regname) \
+ hwaddr regname ##_start; \
+ hwaddr regname ##_length; \
+ MemoryRegion regname ##_mem
+
+#define TYPE_GT64120_PCI_HOST_BRIDGE "gt64120"
+
+#define GT64120_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(GT64120State, (obj), TYPE_GT64120_PCI_HOST_BRIDGE)
+
+typedef struct GT64120State {
+ PCIHostState parent_obj;
+
+ uint32_t regs[GT_REGS];
+ PCI_MAPPING_ENTRY(PCI0IO);
+ PCI_MAPPING_ENTRY(PCI0M0);
+ PCI_MAPPING_ENTRY(PCI0M1);
+ PCI_MAPPING_ENTRY(ISD);
+ MemoryRegion pci0_mem;
+ AddressSpace pci0_mem_as;
+} GT64120State;
+
+/* Adjust range to avoid touching space which isn't mappable via PCI */
+/* XXX: Hardcoded values for Malta: 0x1e000000 - 0x1f100000
+ 0x1fc00000 - 0x1fd00000 */
+static void check_reserved_space (hwaddr *start,
+ hwaddr *length)
+{
+ hwaddr begin = *start;
+ hwaddr end = *start + *length;
+
+ if (end >= 0x1e000000LL && end < 0x1f100000LL)
+ end = 0x1e000000LL;
+ if (begin >= 0x1e000000LL && begin < 0x1f100000LL)
+ begin = 0x1f100000LL;
+ if (end >= 0x1fc00000LL && end < 0x1fd00000LL)
+ end = 0x1fc00000LL;
+ if (begin >= 0x1fc00000LL && begin < 0x1fd00000LL)
+ begin = 0x1fd00000LL;
+ /* XXX: This is broken when a reserved range splits the requested range */
+ if (end >= 0x1f100000LL && begin < 0x1e000000LL)
+ end = 0x1e000000LL;
+ if (end >= 0x1fd00000LL && begin < 0x1fc00000LL)
+ end = 0x1fc00000LL;
+
+ *start = begin;
+ *length = end - begin;
+}
+
+static void gt64120_isd_mapping(GT64120State *s)
+{
+ hwaddr start = s->regs[GT_ISD] << 21;
+ hwaddr length = 0x1000;
+
+ if (s->ISD_length) {
+ memory_region_del_subregion(get_system_memory(), &s->ISD_mem);
+ }
+ check_reserved_space(&start, &length);
+ length = 0x1000;
+ /* Map new address */
+ DPRINTF("ISD: "TARGET_FMT_plx"@"TARGET_FMT_plx
+ " -> "TARGET_FMT_plx"@"TARGET_FMT_plx"\n",
+ s->ISD_length, s->ISD_start, length, start);
+ s->ISD_start = start;
+ s->ISD_length = length;
+ memory_region_add_subregion(get_system_memory(), s->ISD_start, &s->ISD_mem);
+}
+
+static void gt64120_pci_mapping(GT64120State *s)
+{
+ /* Update PCI0IO mapping */
+ if ((s->regs[GT_PCI0IOLD] & 0x7f) <= s->regs[GT_PCI0IOHD]) {
+ /* Unmap old IO address */
+ if (s->PCI0IO_length) {
+ memory_region_del_subregion(get_system_memory(), &s->PCI0IO_mem);
+ object_unparent(OBJECT(&s->PCI0IO_mem));
+ }
+ /* Map new IO address */
+ s->PCI0IO_start = s->regs[GT_PCI0IOLD] << 21;
+ s->PCI0IO_length = ((s->regs[GT_PCI0IOHD] + 1) -
+ (s->regs[GT_PCI0IOLD] & 0x7f)) << 21;
+ if (s->PCI0IO_length) {
+ memory_region_init_alias(&s->PCI0IO_mem, OBJECT(s), "pci0-io",
+ get_system_io(), 0, s->PCI0IO_length);
+ memory_region_add_subregion(get_system_memory(), s->PCI0IO_start,
+ &s->PCI0IO_mem);
+ }
+ }
+
+ /* Update PCI0M0 mapping */
+ if ((s->regs[GT_PCI0M0LD] & 0x7f) <= s->regs[GT_PCI0M0HD]) {
+ /* Unmap old MEM address */
+ if (s->PCI0M0_length) {
+ memory_region_del_subregion(get_system_memory(), &s->PCI0M0_mem);
+ object_unparent(OBJECT(&s->PCI0M0_mem));
+ }
+ /* Map new mem address */
+ s->PCI0M0_start = s->regs[GT_PCI0M0LD] << 21;
+ s->PCI0M0_length = ((s->regs[GT_PCI0M0HD] + 1) -
+ (s->regs[GT_PCI0M0LD] & 0x7f)) << 21;
+ if (s->PCI0M0_length) {
+ memory_region_init_alias(&s->PCI0M0_mem, OBJECT(s), "pci0-mem0",
+ &s->pci0_mem, s->PCI0M0_start,
+ s->PCI0M0_length);
+ memory_region_add_subregion(get_system_memory(), s->PCI0M0_start,
+ &s->PCI0M0_mem);
+ }
+ }
+
+ /* Update PCI0M1 mapping */
+ if ((s->regs[GT_PCI0M1LD] & 0x7f) <= s->regs[GT_PCI0M1HD]) {
+ /* Unmap old MEM address */
+ if (s->PCI0M1_length) {
+ memory_region_del_subregion(get_system_memory(), &s->PCI0M1_mem);
+ object_unparent(OBJECT(&s->PCI0M1_mem));
+ }
+ /* Map new mem address */
+ s->PCI0M1_start = s->regs[GT_PCI0M1LD] << 21;
+ s->PCI0M1_length = ((s->regs[GT_PCI0M1HD] + 1) -
+ (s->regs[GT_PCI0M1LD] & 0x7f)) << 21;
+ if (s->PCI0M1_length) {
+ memory_region_init_alias(&s->PCI0M1_mem, OBJECT(s), "pci0-mem1",
+ &s->pci0_mem, s->PCI0M1_start,
+ s->PCI0M1_length);
+ memory_region_add_subregion(get_system_memory(), s->PCI0M1_start,
+ &s->PCI0M1_mem);
+ }
+ }
+}
+
+static int gt64120_post_load(void *opaque, int version_id)
+{
+ GT64120State *s = opaque;
+
+ gt64120_isd_mapping(s);
+ gt64120_pci_mapping(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_gt64120 = {
+ .name = "gt64120",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = gt64120_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, GT64120State, GT_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void gt64120_writel (void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ GT64120State *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+ uint32_t saddr;
+
+ if (!(s->regs[GT_CPU] & 0x00001000))
+ val = bswap32(val);
+
+ saddr = (addr & 0xfff) >> 2;
+ switch (saddr) {
+
+ /* CPU Configuration */
+ case GT_CPU:
+ s->regs[GT_CPU] = val;
+ break;
+ case GT_MULTI:
+ /* Read-only register as only one GT64xxx is present on the CPU bus */
+ break;
+
+ /* CPU Address Decode */
+ case GT_PCI0IOLD:
+ s->regs[GT_PCI0IOLD] = val & 0x00007fff;
+ s->regs[GT_PCI0IOREMAP] = val & 0x000007ff;
+ gt64120_pci_mapping(s);
+ break;
+ case GT_PCI0M0LD:
+ s->regs[GT_PCI0M0LD] = val & 0x00007fff;
+ s->regs[GT_PCI0M0REMAP] = val & 0x000007ff;
+ gt64120_pci_mapping(s);
+ break;
+ case GT_PCI0M1LD:
+ s->regs[GT_PCI0M1LD] = val & 0x00007fff;
+ s->regs[GT_PCI0M1REMAP] = val & 0x000007ff;
+ gt64120_pci_mapping(s);
+ break;
+ case GT_PCI1IOLD:
+ s->regs[GT_PCI1IOLD] = val & 0x00007fff;
+ s->regs[GT_PCI1IOREMAP] = val & 0x000007ff;
+ break;
+ case GT_PCI1M0LD:
+ s->regs[GT_PCI1M0LD] = val & 0x00007fff;
+ s->regs[GT_PCI1M0REMAP] = val & 0x000007ff;
+ break;
+ case GT_PCI1M1LD:
+ s->regs[GT_PCI1M1LD] = val & 0x00007fff;
+ s->regs[GT_PCI1M1REMAP] = val & 0x000007ff;
+ break;
+ case GT_PCI0M0HD:
+ case GT_PCI0M1HD:
+ case GT_PCI0IOHD:
+ s->regs[saddr] = val & 0x0000007f;
+ gt64120_pci_mapping(s);
+ break;
+ case GT_PCI1IOHD:
+ case GT_PCI1M0HD:
+ case GT_PCI1M1HD:
+ s->regs[saddr] = val & 0x0000007f;
+ break;
+ case GT_ISD:
+ s->regs[saddr] = val & 0x00007fff;
+ gt64120_isd_mapping(s);
+ break;
+
+ case GT_PCI0IOREMAP:
+ case GT_PCI0M0REMAP:
+ case GT_PCI0M1REMAP:
+ case GT_PCI1IOREMAP:
+ case GT_PCI1M0REMAP:
+ case GT_PCI1M1REMAP:
+ s->regs[saddr] = val & 0x000007ff;
+ break;
+
+ /* CPU Error Report */
+ case GT_CPUERR_ADDRLO:
+ case GT_CPUERR_ADDRHI:
+ case GT_CPUERR_DATALO:
+ case GT_CPUERR_DATAHI:
+ case GT_CPUERR_PARITY:
+ /* Read-only registers, do nothing */
+ break;
+
+ /* CPU Sync Barrier */
+ case GT_PCI0SYNC:
+ case GT_PCI1SYNC:
+ /* Read-only registers, do nothing */
+ break;
+
+ /* SDRAM and Device Address Decode */
+ case GT_SCS0LD:
+ case GT_SCS0HD:
+ case GT_SCS1LD:
+ case GT_SCS1HD:
+ case GT_SCS2LD:
+ case GT_SCS2HD:
+ case GT_SCS3LD:
+ case GT_SCS3HD:
+ case GT_CS0LD:
+ case GT_CS0HD:
+ case GT_CS1LD:
+ case GT_CS1HD:
+ case GT_CS2LD:
+ case GT_CS2HD:
+ case GT_CS3LD:
+ case GT_CS3HD:
+ case GT_BOOTLD:
+ case GT_BOOTHD:
+ case GT_ADERR:
+ /* SDRAM Configuration */
+ case GT_SDRAM_CFG:
+ case GT_SDRAM_OPMODE:
+ case GT_SDRAM_BM:
+ case GT_SDRAM_ADDRDECODE:
+ /* Accept and ignore SDRAM interleave configuration */
+ s->regs[saddr] = val;
+ break;
+
+ /* Device Parameters */
+ case GT_DEV_B0:
+ case GT_DEV_B1:
+ case GT_DEV_B2:
+ case GT_DEV_B3:
+ case GT_DEV_BOOT:
+ /* Not implemented */
+ DPRINTF ("Unimplemented device register offset 0x%x\n", saddr << 2);
+ break;
+
+ /* ECC */
+ case GT_ECC_ERRDATALO:
+ case GT_ECC_ERRDATAHI:
+ case GT_ECC_MEM:
+ case GT_ECC_CALC:
+ case GT_ECC_ERRADDR:
+ /* Read-only registers, do nothing */
+ break;
+
+ /* DMA Record */
+ case GT_DMA0_CNT:
+ case GT_DMA1_CNT:
+ case GT_DMA2_CNT:
+ case GT_DMA3_CNT:
+ case GT_DMA0_SA:
+ case GT_DMA1_SA:
+ case GT_DMA2_SA:
+ case GT_DMA3_SA:
+ case GT_DMA0_DA:
+ case GT_DMA1_DA:
+ case GT_DMA2_DA:
+ case GT_DMA3_DA:
+ case GT_DMA0_NEXT:
+ case GT_DMA1_NEXT:
+ case GT_DMA2_NEXT:
+ case GT_DMA3_NEXT:
+ case GT_DMA0_CUR:
+ case GT_DMA1_CUR:
+ case GT_DMA2_CUR:
+ case GT_DMA3_CUR:
+ /* Not implemented */
+ DPRINTF ("Unimplemented DMA register offset 0x%x\n", saddr << 2);
+ break;
+
+ /* DMA Channel Control */
+ case GT_DMA0_CTRL:
+ case GT_DMA1_CTRL:
+ case GT_DMA2_CTRL:
+ case GT_DMA3_CTRL:
+ /* Not implemented */
+ DPRINTF ("Unimplemented DMA register offset 0x%x\n", saddr << 2);
+ break;
+
+ /* DMA Arbiter */
+ case GT_DMA_ARB:
+ /* Not implemented */
+ DPRINTF ("Unimplemented DMA register offset 0x%x\n", saddr << 2);
+ break;
+
+ /* Timer/Counter */
+ case GT_TC0:
+ case GT_TC1:
+ case GT_TC2:
+ case GT_TC3:
+ case GT_TC_CONTROL:
+ /* Not implemented */
+ DPRINTF ("Unimplemented timer register offset 0x%x\n", saddr << 2);
+ break;
+
+ /* PCI Internal */
+ case GT_PCI0_CMD:
+ case GT_PCI1_CMD:
+ s->regs[saddr] = val & 0x0401fc0f;
+ break;
+ case GT_PCI0_TOR:
+ case GT_PCI0_BS_SCS10:
+ case GT_PCI0_BS_SCS32:
+ case GT_PCI0_BS_CS20:
+ case GT_PCI0_BS_CS3BT:
+ case GT_PCI1_IACK:
+ case GT_PCI0_IACK:
+ case GT_PCI0_BARE:
+ case GT_PCI0_PREFMBR:
+ case GT_PCI0_SCS10_BAR:
+ case GT_PCI0_SCS32_BAR:
+ case GT_PCI0_CS20_BAR:
+ case GT_PCI0_CS3BT_BAR:
+ case GT_PCI0_SSCS10_BAR:
+ case GT_PCI0_SSCS32_BAR:
+ case GT_PCI0_SCS3BT_BAR:
+ case GT_PCI1_TOR:
+ case GT_PCI1_BS_SCS10:
+ case GT_PCI1_BS_SCS32:
+ case GT_PCI1_BS_CS20:
+ case GT_PCI1_BS_CS3BT:
+ case GT_PCI1_BARE:
+ case GT_PCI1_PREFMBR:
+ case GT_PCI1_SCS10_BAR:
+ case GT_PCI1_SCS32_BAR:
+ case GT_PCI1_CS20_BAR:
+ case GT_PCI1_CS3BT_BAR:
+ case GT_PCI1_SSCS10_BAR:
+ case GT_PCI1_SSCS32_BAR:
+ case GT_PCI1_SCS3BT_BAR:
+ case GT_PCI1_CFGADDR:
+ case GT_PCI1_CFGDATA:
+ /* not implemented */
+ break;
+ case GT_PCI0_CFGADDR:
+ phb->config_reg = val & 0x80fffffc;
+ break;
+ case GT_PCI0_CFGDATA:
+ if (!(s->regs[GT_PCI0_CMD] & 1) && (phb->config_reg & 0x00fff800)) {
+ val = bswap32(val);
+ }
+ if (phb->config_reg & (1u << 31)) {
+ pci_data_write(phb->bus, phb->config_reg, val, 4);
+ }
+ break;
+
+ /* Interrupts */
+ case GT_INTRCAUSE:
+ /* not really implemented */
+ s->regs[saddr] = ~(~(s->regs[saddr]) | ~(val & 0xfffffffe));
+ s->regs[saddr] |= !!(s->regs[saddr] & 0xfffffffe);
+ DPRINTF("INTRCAUSE %" PRIx64 "\n", val);
+ break;
+ case GT_INTRMASK:
+ s->regs[saddr] = val & 0x3c3ffffe;
+ DPRINTF("INTRMASK %" PRIx64 "\n", val);
+ break;
+ case GT_PCI0_ICMASK:
+ s->regs[saddr] = val & 0x03fffffe;
+ DPRINTF("ICMASK %" PRIx64 "\n", val);
+ break;
+ case GT_PCI0_SERR0MASK:
+ s->regs[saddr] = val & 0x0000003f;
+ DPRINTF("SERR0MASK %" PRIx64 "\n", val);
+ break;
+
+ /* Reserved when only PCI_0 is configured. */
+ case GT_HINTRCAUSE:
+ case GT_CPU_INTSEL:
+ case GT_PCI0_INTSEL:
+ case GT_HINTRMASK:
+ case GT_PCI0_HICMASK:
+ case GT_PCI1_SERR1MASK:
+ /* not implemented */
+ break;
+
+ /* SDRAM Parameters */
+ case GT_SDRAM_B0:
+ case GT_SDRAM_B1:
+ case GT_SDRAM_B2:
+ case GT_SDRAM_B3:
+ /* We don't simulate electrical parameters of the SDRAM.
+ Accept, but ignore the values. */
+ s->regs[saddr] = val;
+ break;
+
+ default:
+ DPRINTF ("Bad register offset 0x%x\n", (int)addr);
+ break;
+ }
+}
+
+static uint64_t gt64120_readl (void *opaque,
+ hwaddr addr, unsigned size)
+{
+ GT64120State *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+ uint32_t val;
+ uint32_t saddr;
+
+ saddr = (addr & 0xfff) >> 2;
+ switch (saddr) {
+
+ /* CPU Configuration */
+ case GT_MULTI:
+ /* Only one GT64xxx is present on the CPU bus, return
+ the initial value */
+ val = s->regs[saddr];
+ break;
+
+ /* CPU Error Report */
+ case GT_CPUERR_ADDRLO:
+ case GT_CPUERR_ADDRHI:
+ case GT_CPUERR_DATALO:
+ case GT_CPUERR_DATAHI:
+ case GT_CPUERR_PARITY:
+ /* Emulated memory has no error, always return the initial
+ values */
+ val = s->regs[saddr];
+ break;
+
+ /* CPU Sync Barrier */
+ case GT_PCI0SYNC:
+ case GT_PCI1SYNC:
+ /* Reading those register should empty all FIFO on the PCI
+ bus, which are not emulated. The return value should be
+ a random value that should be ignored. */
+ val = 0xc000ffee;
+ break;
+
+ /* ECC */
+ case GT_ECC_ERRDATALO:
+ case GT_ECC_ERRDATAHI:
+ case GT_ECC_MEM:
+ case GT_ECC_CALC:
+ case GT_ECC_ERRADDR:
+ /* Emulated memory has no error, always return the initial
+ values */
+ val = s->regs[saddr];
+ break;
+
+ case GT_CPU:
+ case GT_SCS10LD:
+ case GT_SCS10HD:
+ case GT_SCS32LD:
+ case GT_SCS32HD:
+ case GT_CS20LD:
+ case GT_CS20HD:
+ case GT_CS3BOOTLD:
+ case GT_CS3BOOTHD:
+ case GT_SCS10AR:
+ case GT_SCS32AR:
+ case GT_CS20R:
+ case GT_CS3BOOTR:
+ case GT_PCI0IOLD:
+ case GT_PCI0M0LD:
+ case GT_PCI0M1LD:
+ case GT_PCI1IOLD:
+ case GT_PCI1M0LD:
+ case GT_PCI1M1LD:
+ case GT_PCI0IOHD:
+ case GT_PCI0M0HD:
+ case GT_PCI0M1HD:
+ case GT_PCI1IOHD:
+ case GT_PCI1M0HD:
+ case GT_PCI1M1HD:
+ case GT_PCI0IOREMAP:
+ case GT_PCI0M0REMAP:
+ case GT_PCI0M1REMAP:
+ case GT_PCI1IOREMAP:
+ case GT_PCI1M0REMAP:
+ case GT_PCI1M1REMAP:
+ case GT_ISD:
+ val = s->regs[saddr];
+ break;
+ case GT_PCI0_IACK:
+ /* Read the IRQ number */
+ val = pic_read_irq(isa_pic);
+ break;
+
+ /* SDRAM and Device Address Decode */
+ case GT_SCS0LD:
+ case GT_SCS0HD:
+ case GT_SCS1LD:
+ case GT_SCS1HD:
+ case GT_SCS2LD:
+ case GT_SCS2HD:
+ case GT_SCS3LD:
+ case GT_SCS3HD:
+ case GT_CS0LD:
+ case GT_CS0HD:
+ case GT_CS1LD:
+ case GT_CS1HD:
+ case GT_CS2LD:
+ case GT_CS2HD:
+ case GT_CS3LD:
+ case GT_CS3HD:
+ case GT_BOOTLD:
+ case GT_BOOTHD:
+ case GT_ADERR:
+ val = s->regs[saddr];
+ break;
+
+ /* SDRAM Configuration */
+ case GT_SDRAM_CFG:
+ case GT_SDRAM_OPMODE:
+ case GT_SDRAM_BM:
+ case GT_SDRAM_ADDRDECODE:
+ val = s->regs[saddr];
+ break;
+
+ /* SDRAM Parameters */
+ case GT_SDRAM_B0:
+ case GT_SDRAM_B1:
+ case GT_SDRAM_B2:
+ case GT_SDRAM_B3:
+ /* We don't simulate electrical parameters of the SDRAM.
+ Just return the last written value. */
+ val = s->regs[saddr];
+ break;
+
+ /* Device Parameters */
+ case GT_DEV_B0:
+ case GT_DEV_B1:
+ case GT_DEV_B2:
+ case GT_DEV_B3:
+ case GT_DEV_BOOT:
+ val = s->regs[saddr];
+ break;
+
+ /* DMA Record */
+ case GT_DMA0_CNT:
+ case GT_DMA1_CNT:
+ case GT_DMA2_CNT:
+ case GT_DMA3_CNT:
+ case GT_DMA0_SA:
+ case GT_DMA1_SA:
+ case GT_DMA2_SA:
+ case GT_DMA3_SA:
+ case GT_DMA0_DA:
+ case GT_DMA1_DA:
+ case GT_DMA2_DA:
+ case GT_DMA3_DA:
+ case GT_DMA0_NEXT:
+ case GT_DMA1_NEXT:
+ case GT_DMA2_NEXT:
+ case GT_DMA3_NEXT:
+ case GT_DMA0_CUR:
+ case GT_DMA1_CUR:
+ case GT_DMA2_CUR:
+ case GT_DMA3_CUR:
+ val = s->regs[saddr];
+ break;
+
+ /* DMA Channel Control */
+ case GT_DMA0_CTRL:
+ case GT_DMA1_CTRL:
+ case GT_DMA2_CTRL:
+ case GT_DMA3_CTRL:
+ val = s->regs[saddr];
+ break;
+
+ /* DMA Arbiter */
+ case GT_DMA_ARB:
+ val = s->regs[saddr];
+ break;
+
+ /* Timer/Counter */
+ case GT_TC0:
+ case GT_TC1:
+ case GT_TC2:
+ case GT_TC3:
+ case GT_TC_CONTROL:
+ val = s->regs[saddr];
+ break;
+
+ /* PCI Internal */
+ case GT_PCI0_CFGADDR:
+ val = phb->config_reg;
+ break;
+ case GT_PCI0_CFGDATA:
+ if (!(phb->config_reg & (1 << 31))) {
+ val = 0xffffffff;
+ } else {
+ val = pci_data_read(phb->bus, phb->config_reg, 4);
+ }
+ if (!(s->regs[GT_PCI0_CMD] & 1) && (phb->config_reg & 0x00fff800)) {
+ val = bswap32(val);
+ }
+ break;
+
+ case GT_PCI0_CMD:
+ case GT_PCI0_TOR:
+ case GT_PCI0_BS_SCS10:
+ case GT_PCI0_BS_SCS32:
+ case GT_PCI0_BS_CS20:
+ case GT_PCI0_BS_CS3BT:
+ case GT_PCI1_IACK:
+ case GT_PCI0_BARE:
+ case GT_PCI0_PREFMBR:
+ case GT_PCI0_SCS10_BAR:
+ case GT_PCI0_SCS32_BAR:
+ case GT_PCI0_CS20_BAR:
+ case GT_PCI0_CS3BT_BAR:
+ case GT_PCI0_SSCS10_BAR:
+ case GT_PCI0_SSCS32_BAR:
+ case GT_PCI0_SCS3BT_BAR:
+ case GT_PCI1_CMD:
+ case GT_PCI1_TOR:
+ case GT_PCI1_BS_SCS10:
+ case GT_PCI1_BS_SCS32:
+ case GT_PCI1_BS_CS20:
+ case GT_PCI1_BS_CS3BT:
+ case GT_PCI1_BARE:
+ case GT_PCI1_PREFMBR:
+ case GT_PCI1_SCS10_BAR:
+ case GT_PCI1_SCS32_BAR:
+ case GT_PCI1_CS20_BAR:
+ case GT_PCI1_CS3BT_BAR:
+ case GT_PCI1_SSCS10_BAR:
+ case GT_PCI1_SSCS32_BAR:
+ case GT_PCI1_SCS3BT_BAR:
+ case GT_PCI1_CFGADDR:
+ case GT_PCI1_CFGDATA:
+ val = s->regs[saddr];
+ break;
+
+ /* Interrupts */
+ case GT_INTRCAUSE:
+ val = s->regs[saddr];
+ DPRINTF("INTRCAUSE %x\n", val);
+ break;
+ case GT_INTRMASK:
+ val = s->regs[saddr];
+ DPRINTF("INTRMASK %x\n", val);
+ break;
+ case GT_PCI0_ICMASK:
+ val = s->regs[saddr];
+ DPRINTF("ICMASK %x\n", val);
+ break;
+ case GT_PCI0_SERR0MASK:
+ val = s->regs[saddr];
+ DPRINTF("SERR0MASK %x\n", val);
+ break;
+
+ /* Reserved when only PCI_0 is configured. */
+ case GT_HINTRCAUSE:
+ case GT_CPU_INTSEL:
+ case GT_PCI0_INTSEL:
+ case GT_HINTRMASK:
+ case GT_PCI0_HICMASK:
+ case GT_PCI1_SERR1MASK:
+ val = s->regs[saddr];
+ break;
+
+ default:
+ val = s->regs[saddr];
+ DPRINTF ("Bad register offset 0x%x\n", (int)addr);
+ break;
+ }
+
+ if (!(s->regs[GT_CPU] & 0x00001000))
+ val = bswap32(val);
+
+ return val;
+}
+
+static const MemoryRegionOps isd_mem_ops = {
+ .read = gt64120_readl,
+ .write = gt64120_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int gt64120_pci_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ int slot;
+
+ slot = (pci_dev->devfn >> 3);
+
+ switch (slot) {
+ /* PIIX4 USB */
+ case 10:
+ return 3;
+ /* AMD 79C973 Ethernet */
+ case 11:
+ return 1;
+ /* Crystal 4281 Sound */
+ case 12:
+ return 2;
+ /* PCI slot 1 to 4 */
+ case 18 ... 21:
+ return ((slot - 18) + irq_num) & 0x03;
+ /* Unknown device, don't do any translation */
+ default:
+ return irq_num;
+ }
+}
+
+static int pci_irq_levels[4];
+
+static void gt64120_pci_set_irq(void *opaque, int irq_num, int level)
+{
+ int i, pic_irq, pic_level;
+ qemu_irq *pic = opaque;
+
+ pci_irq_levels[irq_num] = level;
+
+ /* now we change the pic irq level according to the piix irq mappings */
+ /* XXX: optimize */
+ pic_irq = piix4_dev->config[0x60 + irq_num];
+ if (pic_irq < 16) {
+ /* The pic level is the logical OR of all the PCI irqs mapped
+ to it */
+ pic_level = 0;
+ for (i = 0; i < 4; i++) {
+ if (pic_irq == piix4_dev->config[0x60 + i])
+ pic_level |= pci_irq_levels[i];
+ }
+ qemu_set_irq(pic[pic_irq], pic_level);
+ }
+}
+
+
+static void gt64120_reset(void *opaque)
+{
+ GT64120State *s = opaque;
+
+ /* FIXME: Malta specific hw assumptions ahead */
+
+ /* CPU Configuration */
+#ifdef TARGET_WORDS_BIGENDIAN
+ s->regs[GT_CPU] = 0x00000000;
+#else
+ s->regs[GT_CPU] = 0x00001000;
+#endif
+ s->regs[GT_MULTI] = 0x00000003;
+
+ /* CPU Address decode */
+ s->regs[GT_SCS10LD] = 0x00000000;
+ s->regs[GT_SCS10HD] = 0x00000007;
+ s->regs[GT_SCS32LD] = 0x00000008;
+ s->regs[GT_SCS32HD] = 0x0000000f;
+ s->regs[GT_CS20LD] = 0x000000e0;
+ s->regs[GT_CS20HD] = 0x00000070;
+ s->regs[GT_CS3BOOTLD] = 0x000000f8;
+ s->regs[GT_CS3BOOTHD] = 0x0000007f;
+
+ s->regs[GT_PCI0IOLD] = 0x00000080;
+ s->regs[GT_PCI0IOHD] = 0x0000000f;
+ s->regs[GT_PCI0M0LD] = 0x00000090;
+ s->regs[GT_PCI0M0HD] = 0x0000001f;
+ s->regs[GT_ISD] = 0x000000a0;
+ s->regs[GT_PCI0M1LD] = 0x00000790;
+ s->regs[GT_PCI0M1HD] = 0x0000001f;
+ s->regs[GT_PCI1IOLD] = 0x00000100;
+ s->regs[GT_PCI1IOHD] = 0x0000000f;
+ s->regs[GT_PCI1M0LD] = 0x00000110;
+ s->regs[GT_PCI1M0HD] = 0x0000001f;
+ s->regs[GT_PCI1M1LD] = 0x00000120;
+ s->regs[GT_PCI1M1HD] = 0x0000002f;
+
+ s->regs[GT_SCS10AR] = 0x00000000;
+ s->regs[GT_SCS32AR] = 0x00000008;
+ s->regs[GT_CS20R] = 0x000000e0;
+ s->regs[GT_CS3BOOTR] = 0x000000f8;
+
+ s->regs[GT_PCI0IOREMAP] = 0x00000080;
+ s->regs[GT_PCI0M0REMAP] = 0x00000090;
+ s->regs[GT_PCI0M1REMAP] = 0x00000790;
+ s->regs[GT_PCI1IOREMAP] = 0x00000100;
+ s->regs[GT_PCI1M0REMAP] = 0x00000110;
+ s->regs[GT_PCI1M1REMAP] = 0x00000120;
+
+ /* CPU Error Report */
+ s->regs[GT_CPUERR_ADDRLO] = 0x00000000;
+ s->regs[GT_CPUERR_ADDRHI] = 0x00000000;
+ s->regs[GT_CPUERR_DATALO] = 0xffffffff;
+ s->regs[GT_CPUERR_DATAHI] = 0xffffffff;
+ s->regs[GT_CPUERR_PARITY] = 0x000000ff;
+
+ /* CPU Sync Barrier */
+ s->regs[GT_PCI0SYNC] = 0x00000000;
+ s->regs[GT_PCI1SYNC] = 0x00000000;
+
+ /* SDRAM and Device Address Decode */
+ s->regs[GT_SCS0LD] = 0x00000000;
+ s->regs[GT_SCS0HD] = 0x00000007;
+ s->regs[GT_SCS1LD] = 0x00000008;
+ s->regs[GT_SCS1HD] = 0x0000000f;
+ s->regs[GT_SCS2LD] = 0x00000010;
+ s->regs[GT_SCS2HD] = 0x00000017;
+ s->regs[GT_SCS3LD] = 0x00000018;
+ s->regs[GT_SCS3HD] = 0x0000001f;
+ s->regs[GT_CS0LD] = 0x000000c0;
+ s->regs[GT_CS0HD] = 0x000000c7;
+ s->regs[GT_CS1LD] = 0x000000c8;
+ s->regs[GT_CS1HD] = 0x000000cf;
+ s->regs[GT_CS2LD] = 0x000000d0;
+ s->regs[GT_CS2HD] = 0x000000df;
+ s->regs[GT_CS3LD] = 0x000000f0;
+ s->regs[GT_CS3HD] = 0x000000fb;
+ s->regs[GT_BOOTLD] = 0x000000fc;
+ s->regs[GT_BOOTHD] = 0x000000ff;
+ s->regs[GT_ADERR] = 0xffffffff;
+
+ /* SDRAM Configuration */
+ s->regs[GT_SDRAM_CFG] = 0x00000200;
+ s->regs[GT_SDRAM_OPMODE] = 0x00000000;
+ s->regs[GT_SDRAM_BM] = 0x00000007;
+ s->regs[GT_SDRAM_ADDRDECODE] = 0x00000002;
+
+ /* SDRAM Parameters */
+ s->regs[GT_SDRAM_B0] = 0x00000005;
+ s->regs[GT_SDRAM_B1] = 0x00000005;
+ s->regs[GT_SDRAM_B2] = 0x00000005;
+ s->regs[GT_SDRAM_B3] = 0x00000005;
+
+ /* ECC */
+ s->regs[GT_ECC_ERRDATALO] = 0x00000000;
+ s->regs[GT_ECC_ERRDATAHI] = 0x00000000;
+ s->regs[GT_ECC_MEM] = 0x00000000;
+ s->regs[GT_ECC_CALC] = 0x00000000;
+ s->regs[GT_ECC_ERRADDR] = 0x00000000;
+
+ /* Device Parameters */
+ s->regs[GT_DEV_B0] = 0x386fffff;
+ s->regs[GT_DEV_B1] = 0x386fffff;
+ s->regs[GT_DEV_B2] = 0x386fffff;
+ s->regs[GT_DEV_B3] = 0x386fffff;
+ s->regs[GT_DEV_BOOT] = 0x146fffff;
+
+ /* DMA registers are all zeroed at reset */
+
+ /* Timer/Counter */
+ s->regs[GT_TC0] = 0xffffffff;
+ s->regs[GT_TC1] = 0x00ffffff;
+ s->regs[GT_TC2] = 0x00ffffff;
+ s->regs[GT_TC3] = 0x00ffffff;
+ s->regs[GT_TC_CONTROL] = 0x00000000;
+
+ /* PCI Internal */
+#ifdef TARGET_WORDS_BIGENDIAN
+ s->regs[GT_PCI0_CMD] = 0x00000000;
+#else
+ s->regs[GT_PCI0_CMD] = 0x00010001;
+#endif
+ s->regs[GT_PCI0_TOR] = 0x0000070f;
+ s->regs[GT_PCI0_BS_SCS10] = 0x00fff000;
+ s->regs[GT_PCI0_BS_SCS32] = 0x00fff000;
+ s->regs[GT_PCI0_BS_CS20] = 0x01fff000;
+ s->regs[GT_PCI0_BS_CS3BT] = 0x00fff000;
+ s->regs[GT_PCI1_IACK] = 0x00000000;
+ s->regs[GT_PCI0_IACK] = 0x00000000;
+ s->regs[GT_PCI0_BARE] = 0x0000000f;
+ s->regs[GT_PCI0_PREFMBR] = 0x00000040;
+ s->regs[GT_PCI0_SCS10_BAR] = 0x00000000;
+ s->regs[GT_PCI0_SCS32_BAR] = 0x01000000;
+ s->regs[GT_PCI0_CS20_BAR] = 0x1c000000;
+ s->regs[GT_PCI0_CS3BT_BAR] = 0x1f000000;
+ s->regs[GT_PCI0_SSCS10_BAR] = 0x00000000;
+ s->regs[GT_PCI0_SSCS32_BAR] = 0x01000000;
+ s->regs[GT_PCI0_SCS3BT_BAR] = 0x1f000000;
+#ifdef TARGET_WORDS_BIGENDIAN
+ s->regs[GT_PCI1_CMD] = 0x00000000;
+#else
+ s->regs[GT_PCI1_CMD] = 0x00010001;
+#endif
+ s->regs[GT_PCI1_TOR] = 0x0000070f;
+ s->regs[GT_PCI1_BS_SCS10] = 0x00fff000;
+ s->regs[GT_PCI1_BS_SCS32] = 0x00fff000;
+ s->regs[GT_PCI1_BS_CS20] = 0x01fff000;
+ s->regs[GT_PCI1_BS_CS3BT] = 0x00fff000;
+ s->regs[GT_PCI1_BARE] = 0x0000000f;
+ s->regs[GT_PCI1_PREFMBR] = 0x00000040;
+ s->regs[GT_PCI1_SCS10_BAR] = 0x00000000;
+ s->regs[GT_PCI1_SCS32_BAR] = 0x01000000;
+ s->regs[GT_PCI1_CS20_BAR] = 0x1c000000;
+ s->regs[GT_PCI1_CS3BT_BAR] = 0x1f000000;
+ s->regs[GT_PCI1_SSCS10_BAR] = 0x00000000;
+ s->regs[GT_PCI1_SSCS32_BAR] = 0x01000000;
+ s->regs[GT_PCI1_SCS3BT_BAR] = 0x1f000000;
+ s->regs[GT_PCI1_CFGADDR] = 0x00000000;
+ s->regs[GT_PCI1_CFGDATA] = 0x00000000;
+ s->regs[GT_PCI0_CFGADDR] = 0x00000000;
+
+ /* Interrupt registers are all zeroed at reset */
+
+ gt64120_isd_mapping(s);
+ gt64120_pci_mapping(s);
+}
+
+PCIBus *gt64120_register(qemu_irq *pic)
+{
+ GT64120State *d;
+ PCIHostState *phb;
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, TYPE_GT64120_PCI_HOST_BRIDGE);
+ qdev_init_nofail(dev);
+ d = GT64120_PCI_HOST_BRIDGE(dev);
+ phb = PCI_HOST_BRIDGE(dev);
+ memory_region_init(&d->pci0_mem, OBJECT(dev), "pci0-mem", UINT32_MAX);
+ address_space_init(&d->pci0_mem_as, &d->pci0_mem, "pci0-mem");
+ phb->bus = pci_register_bus(dev, "pci",
+ gt64120_pci_set_irq, gt64120_pci_map_irq,
+ pic,
+ &d->pci0_mem,
+ get_system_io(),
+ PCI_DEVFN(18, 0), 4, TYPE_PCI_BUS);
+ memory_region_init_io(&d->ISD_mem, OBJECT(dev), &isd_mem_ops, d, "isd-mem", 0x1000);
+
+ pci_create_simple(phb->bus, PCI_DEVFN(0, 0), "gt64120_pci");
+ return phb->bus;
+}
+
+static int gt64120_init(SysBusDevice *dev)
+{
+ GT64120State *s;
+
+ s = GT64120_PCI_HOST_BRIDGE(dev);
+
+ qemu_register_reset(gt64120_reset, s);
+ return 0;
+}
+
+static int gt64120_pci_init(PCIDevice *d)
+{
+ /* FIXME: Malta specific hw assumptions ahead */
+ pci_set_word(d->config + PCI_COMMAND, 0);
+ pci_set_word(d->config + PCI_STATUS,
+ PCI_STATUS_FAST_BACK | PCI_STATUS_DEVSEL_MEDIUM);
+ pci_config_set_prog_interface(d->config, 0);
+ pci_set_long(d->config + PCI_BASE_ADDRESS_0, 0x00000008);
+ pci_set_long(d->config + PCI_BASE_ADDRESS_1, 0x01000008);
+ pci_set_long(d->config + PCI_BASE_ADDRESS_2, 0x1c000000);
+ pci_set_long(d->config + PCI_BASE_ADDRESS_3, 0x1f000000);
+ pci_set_long(d->config + PCI_BASE_ADDRESS_4, 0x14000000);
+ pci_set_long(d->config + PCI_BASE_ADDRESS_5, 0x14000001);
+ pci_set_byte(d->config + 0x3d, 0x01);
+
+ return 0;
+}
+
+static void gt64120_pci_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->init = gt64120_pci_init;
+ k->vendor_id = PCI_VENDOR_ID_MARVELL;
+ k->device_id = PCI_DEVICE_ID_MARVELL_GT6412X;
+ k->revision = 0x10;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo gt64120_pci_info = {
+ .name = "gt64120_pci",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = gt64120_pci_class_init,
+};
+
+static void gt64120_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = gt64120_init;
+ dc->vmsd = &vmstate_gt64120;
+}
+
+static const TypeInfo gt64120_info = {
+ .name = TYPE_GT64120_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(GT64120State),
+ .class_init = gt64120_class_init,
+};
+
+static void gt64120_pci_register_types(void)
+{
+ type_register_static(&gt64120_info);
+ type_register_static(&gt64120_pci_info);
+}
+
+type_init(gt64120_pci_register_types)
diff --git a/hw/mips/mips_fulong2e.c b/hw/mips/mips_fulong2e.c
new file mode 100644
index 00000000..dea941ad
--- /dev/null
+++ b/hw/mips/mips_fulong2e.c
@@ -0,0 +1,406 @@
+/*
+ * QEMU fulong 2e mini pc support
+ *
+ * Copyright (c) 2008 yajin (yajin@vm-kernel.org)
+ * Copyright (c) 2009 chenming (chenming@rdc.faw.com.cn)
+ * Copyright (c) 2010 Huacai Chen (zltjiangshi@gmail.com)
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+/*
+ * Fulong 2e mini pc is based on ICT/ST Loongson 2e CPU (MIPS III like, 800MHz)
+ * http://www.linux-mips.org/wiki/Fulong
+ *
+ * Loongson 2e user manual:
+ * http://www.loongsondeveloper.com/doc/Loongson2EUserGuide.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/char/serial.h"
+#include "hw/block/fdc.h"
+#include "net/net.h"
+#include "hw/boards.h"
+#include "hw/i2c/smbus.h"
+#include "sysemu/block-backend.h"
+#include "hw/block/flash.h"
+#include "hw/mips/mips.h"
+#include "hw/mips/cpudevs.h"
+#include "hw/pci/pci.h"
+#include "sysemu/char.h"
+#include "sysemu/sysemu.h"
+#include "audio/audio.h"
+#include "qemu/log.h"
+#include "hw/loader.h"
+#include "hw/mips/bios.h"
+#include "hw/ide.h"
+#include "elf.h"
+#include "hw/isa/vt82c686.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/timer/i8254.h"
+#include "sysemu/blockdev.h"
+#include "exec/address-spaces.h"
+#include "sysemu/qtest.h"
+#include "qemu/error-report.h"
+
+#define DEBUG_FULONG2E_INIT
+
+#define ENVP_ADDR 0x80002000l
+#define ENVP_NB_ENTRIES 16
+#define ENVP_ENTRY_SIZE 256
+
+#define MAX_IDE_BUS 2
+
+/*
+ * PMON is not part of qemu and released with BSD license, anyone
+ * who want to build a pmon binary please first git-clone the source
+ * from the git repository at:
+ * http://www.loongson.cn/support/git/pmon
+ * Then follow the "Compile Guide" available at:
+ * http://dev.lemote.com/code/pmon
+ *
+ * Notes:
+ * 1, don't use the source at http://dev.lemote.com/http_git/pmon.git
+ * 2, use "Bonito2edev" to replace "dir_corresponding_to_your_target_hardware"
+ * in the "Compile Guide".
+ */
+#define FULONG_BIOSNAME "pmon_fulong2e.bin"
+
+/* PCI SLOT in fulong 2e */
+#define FULONG2E_VIA_SLOT 5
+#define FULONG2E_ATI_SLOT 6
+#define FULONG2E_RTL8139_SLOT 7
+
+static ISADevice *pit;
+
+static struct _loaderparams {
+ int ram_size;
+ const char *kernel_filename;
+ const char *kernel_cmdline;
+ const char *initrd_filename;
+} loaderparams;
+
+static void GCC_FMT_ATTR(3, 4) prom_set(uint32_t* prom_buf, int index,
+ const char *string, ...)
+{
+ va_list ap;
+ int32_t table_addr;
+
+ if (index >= ENVP_NB_ENTRIES)
+ return;
+
+ if (string == NULL) {
+ prom_buf[index] = 0;
+ return;
+ }
+
+ table_addr = sizeof(int32_t) * ENVP_NB_ENTRIES + index * ENVP_ENTRY_SIZE;
+ prom_buf[index] = tswap32(ENVP_ADDR + table_addr);
+
+ va_start(ap, string);
+ vsnprintf((char *)prom_buf + table_addr, ENVP_ENTRY_SIZE, string, ap);
+ va_end(ap);
+}
+
+static int64_t load_kernel (CPUMIPSState *env)
+{
+ int64_t kernel_entry, kernel_low, kernel_high;
+ int index = 0;
+ long initrd_size;
+ ram_addr_t initrd_offset;
+ uint32_t *prom_buf;
+ long prom_size;
+
+ if (load_elf(loaderparams.kernel_filename, cpu_mips_kseg0_to_phys, NULL,
+ (uint64_t *)&kernel_entry, (uint64_t *)&kernel_low,
+ (uint64_t *)&kernel_high, 0, ELF_MACHINE, 1) < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ loaderparams.kernel_filename);
+ exit(1);
+ }
+
+ /* load initrd */
+ initrd_size = 0;
+ initrd_offset = 0;
+ if (loaderparams.initrd_filename) {
+ initrd_size = get_image_size (loaderparams.initrd_filename);
+ if (initrd_size > 0) {
+ initrd_offset = (kernel_high + ~INITRD_PAGE_MASK) & INITRD_PAGE_MASK;
+ if (initrd_offset + initrd_size > ram_size) {
+ fprintf(stderr,
+ "qemu: memory too small for initial ram disk '%s'\n",
+ loaderparams.initrd_filename);
+ exit(1);
+ }
+ initrd_size = load_image_targphys(loaderparams.initrd_filename,
+ initrd_offset, ram_size - initrd_offset);
+ }
+ if (initrd_size == (target_ulong) -1) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ loaderparams.initrd_filename);
+ exit(1);
+ }
+ }
+
+ /* Setup prom parameters. */
+ prom_size = ENVP_NB_ENTRIES * (sizeof(int32_t) + ENVP_ENTRY_SIZE);
+ prom_buf = g_malloc(prom_size);
+
+ prom_set(prom_buf, index++, "%s", loaderparams.kernel_filename);
+ if (initrd_size > 0) {
+ prom_set(prom_buf, index++, "rd_start=0x%" PRIx64 " rd_size=%li %s",
+ cpu_mips_phys_to_kseg0(NULL, initrd_offset), initrd_size,
+ loaderparams.kernel_cmdline);
+ } else {
+ prom_set(prom_buf, index++, "%s", loaderparams.kernel_cmdline);
+ }
+
+ /* Setup minimum environment variables */
+ prom_set(prom_buf, index++, "busclock=33000000");
+ prom_set(prom_buf, index++, "cpuclock=100000000");
+ prom_set(prom_buf, index++, "memsize=%i", loaderparams.ram_size/1024/1024);
+ prom_set(prom_buf, index++, "modetty0=38400n8r");
+ prom_set(prom_buf, index++, NULL);
+
+ rom_add_blob_fixed("prom", prom_buf, prom_size,
+ cpu_mips_kseg0_to_phys(NULL, ENVP_ADDR));
+
+ g_free(prom_buf);
+ return kernel_entry;
+}
+
+static void write_bootloader (CPUMIPSState *env, uint8_t *base, int64_t kernel_addr)
+{
+ uint32_t *p;
+
+ /* Small bootloader */
+ p = (uint32_t *) base;
+
+ stl_p(p++, 0x0bf00010); /* j 0x1fc00040 */
+ stl_p(p++, 0x00000000); /* nop */
+
+ /* Second part of the bootloader */
+ p = (uint32_t *) (base + 0x040);
+
+ stl_p(p++, 0x3c040000); /* lui a0, 0 */
+ stl_p(p++, 0x34840002); /* ori a0, a0, 2 */
+ stl_p(p++, 0x3c050000 | ((ENVP_ADDR >> 16) & 0xffff)); /* lui a1, high(ENVP_ADDR) */
+ stl_p(p++, 0x34a50000 | (ENVP_ADDR & 0xffff)); /* ori a1, a0, low(ENVP_ADDR) */
+ stl_p(p++, 0x3c060000 | (((ENVP_ADDR + 8) >> 16) & 0xffff)); /* lui a2, high(ENVP_ADDR + 8) */
+ stl_p(p++, 0x34c60000 | ((ENVP_ADDR + 8) & 0xffff)); /* ori a2, a2, low(ENVP_ADDR + 8) */
+ stl_p(p++, 0x3c070000 | (loaderparams.ram_size >> 16)); /* lui a3, high(env->ram_size) */
+ stl_p(p++, 0x34e70000 | (loaderparams.ram_size & 0xffff)); /* ori a3, a3, low(env->ram_size) */
+ stl_p(p++, 0x3c1f0000 | ((kernel_addr >> 16) & 0xffff)); /* lui ra, high(kernel_addr) */;
+ stl_p(p++, 0x37ff0000 | (kernel_addr & 0xffff)); /* ori ra, ra, low(kernel_addr) */
+ stl_p(p++, 0x03e00008); /* jr ra */
+ stl_p(p++, 0x00000000); /* nop */
+}
+
+
+static void main_cpu_reset(void *opaque)
+{
+ MIPSCPU *cpu = opaque;
+ CPUMIPSState *env = &cpu->env;
+
+ cpu_reset(CPU(cpu));
+ /* TODO: 2E reset stuff */
+ if (loaderparams.kernel_filename) {
+ env->CP0_Status &= ~((1 << CP0St_BEV) | (1 << CP0St_ERL));
+ }
+}
+
+static const uint8_t eeprom_spd[0x80] = {
+ 0x80,0x08,0x07,0x0d,0x09,0x02,0x40,0x00,0x04,0x70,
+ 0x70,0x00,0x82,0x10,0x00,0x01,0x0e,0x04,0x0c,0x01,
+ 0x02,0x20,0x80,0x75,0x70,0x00,0x00,0x50,0x3c,0x50,
+ 0x2d,0x20,0xb0,0xb0,0x50,0x50,0x00,0x00,0x00,0x00,
+ 0x00,0x41,0x48,0x3c,0x32,0x75,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x9c,0x7b,0x07,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x48,0x42,0x35,0x34,0x41,0x32,
+ 0x35,0x36,0x38,0x4b,0x4e,0x2d,0x41,0x37,0x35,0x42,
+ 0x20,0x30,0x20
+};
+
+/* Audio support */
+static void audio_init (PCIBus *pci_bus)
+{
+ vt82c686b_ac97_init(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 5));
+ vt82c686b_mc97_init(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 6));
+}
+
+/* Network support */
+static void network_init (PCIBus *pci_bus)
+{
+ int i;
+
+ for(i = 0; i < nb_nics; i++) {
+ NICInfo *nd = &nd_table[i];
+ const char *default_devaddr = NULL;
+
+ if (i == 0 && (!nd->model || strcmp(nd->model, "rtl8139") == 0)) {
+ /* The fulong board has a RTL8139 card using PCI SLOT 7 */
+ default_devaddr = "07";
+ }
+
+ pci_nic_init_nofail(nd, pci_bus, "rtl8139", default_devaddr);
+ }
+}
+
+static void cpu_request_exit(void *opaque, int irq, int level)
+{
+ CPUState *cpu = current_cpu;
+
+ if (cpu && level) {
+ cpu_exit(cpu);
+ }
+}
+
+static void mips_fulong2e_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ char *filename;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *bios = g_new(MemoryRegion, 1);
+ long bios_size;
+ int64_t kernel_entry;
+ qemu_irq *i8259;
+ qemu_irq *cpu_exit_irq;
+ PCIBus *pci_bus;
+ ISABus *isa_bus;
+ I2CBus *smbus;
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+ MIPSCPU *cpu;
+ CPUMIPSState *env;
+
+ /* init CPUs */
+ if (cpu_model == NULL) {
+ cpu_model = "Loongson-2E";
+ }
+ cpu = cpu_mips_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ qemu_register_reset(main_cpu_reset, cpu);
+
+ /* fulong 2e has 256M ram. */
+ ram_size = 256 * 1024 * 1024;
+
+ /* fulong 2e has a 1M flash.Winbond W39L040AP70Z */
+ bios_size = 1024 * 1024;
+
+ /* allocate RAM */
+ memory_region_allocate_system_memory(ram, NULL, "fulong2e.ram", ram_size);
+ memory_region_init_ram(bios, NULL, "fulong2e.bios", bios_size,
+ &error_abort);
+ vmstate_register_ram_global(bios);
+ memory_region_set_readonly(bios, true);
+
+ memory_region_add_subregion(address_space_mem, 0, ram);
+ memory_region_add_subregion(address_space_mem, 0x1fc00000LL, bios);
+
+ /* We do not support flash operation, just loading pmon.bin as raw BIOS.
+ * Please use -L to set the BIOS path and -bios to set bios name. */
+
+ if (kernel_filename) {
+ loaderparams.ram_size = ram_size;
+ loaderparams.kernel_filename = kernel_filename;
+ loaderparams.kernel_cmdline = kernel_cmdline;
+ loaderparams.initrd_filename = initrd_filename;
+ kernel_entry = load_kernel (env);
+ write_bootloader(env, memory_region_get_ram_ptr(bios), kernel_entry);
+ } else {
+ if (bios_name == NULL) {
+ bios_name = FULONG_BIOSNAME;
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = load_image_targphys(filename, 0x1fc00000LL,
+ BIOS_SIZE);
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+
+ if ((bios_size < 0 || bios_size > BIOS_SIZE) &&
+ !kernel_filename && !qtest_enabled()) {
+ error_report("Could not load MIPS bios '%s'", bios_name);
+ exit(1);
+ }
+ }
+
+ /* Init internal devices */
+ cpu_mips_irq_init_cpu(env);
+ cpu_mips_clock_init(env);
+
+ /* North bridge, Bonito --> IP2 */
+ pci_bus = bonito_init((qemu_irq *)&(env->irq[2]));
+
+ /* South bridge */
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+
+ isa_bus = vt82c686b_init(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 0));
+ if (!isa_bus) {
+ fprintf(stderr, "vt82c686b_init error\n");
+ exit(1);
+ }
+
+ /* Interrupt controller */
+ /* The 8259 -> IP5 */
+ i8259 = i8259_init(isa_bus, env->irq[5]);
+ isa_bus_irqs(isa_bus, i8259);
+
+ vt82c686b_ide_init(pci_bus, hd, PCI_DEVFN(FULONG2E_VIA_SLOT, 1));
+ pci_create_simple(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 2),
+ "vt82c686b-usb-uhci");
+ pci_create_simple(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 3),
+ "vt82c686b-usb-uhci");
+
+ smbus = vt82c686b_pm_init(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 4),
+ 0xeee1, NULL);
+ /* TODO: Populate SPD eeprom data. */
+ smbus_eeprom_init(smbus, 1, eeprom_spd, sizeof(eeprom_spd));
+
+ /* init other devices */
+ pit = pit_init(isa_bus, 0x40, 0, NULL);
+ cpu_exit_irq = qemu_allocate_irqs(cpu_request_exit, NULL, 1);
+ DMA_init(0, cpu_exit_irq);
+
+ /* Super I/O */
+ isa_create_simple(isa_bus, "i8042");
+
+ rtc_init(isa_bus, 2000, NULL);
+
+ serial_hds_isa_init(isa_bus, MAX_SERIAL_PORTS);
+ parallel_hds_isa_init(isa_bus, 1);
+
+ /* Sound card */
+ audio_init(pci_bus);
+ /* Network card */
+ network_init(pci_bus);
+}
+
+static QEMUMachine mips_fulong2e_machine = {
+ .name = "fulong2e",
+ .desc = "Fulong 2e mini pc",
+ .init = mips_fulong2e_init,
+};
+
+static void mips_fulong2e_machine_init(void)
+{
+ qemu_register_machine(&mips_fulong2e_machine);
+}
+
+machine_init(mips_fulong2e_machine_init);
diff --git a/hw/mips/mips_int.c b/hw/mips/mips_int.c
new file mode 100644
index 00000000..d740046b
--- /dev/null
+++ b/hw/mips/mips_int.c
@@ -0,0 +1,78 @@
+/*
+ * QEMU MIPS interrupt support
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/mips/cpudevs.h"
+#include "cpu.h"
+#include "sysemu/kvm.h"
+#include "kvm_mips.h"
+
+static void cpu_mips_irq_request(void *opaque, int irq, int level)
+{
+ MIPSCPU *cpu = opaque;
+ CPUMIPSState *env = &cpu->env;
+ CPUState *cs = CPU(cpu);
+
+ if (irq < 0 || irq > 7)
+ return;
+
+ if (level) {
+ env->CP0_Cause |= 1 << (irq + CP0Ca_IP);
+
+ if (kvm_enabled() && irq == 2) {
+ kvm_mips_set_interrupt(cpu, irq, level);
+ }
+
+ } else {
+ env->CP0_Cause &= ~(1 << (irq + CP0Ca_IP));
+
+ if (kvm_enabled() && irq == 2) {
+ kvm_mips_set_interrupt(cpu, irq, level);
+ }
+ }
+
+ if (env->CP0_Cause & CP0Ca_IP_mask) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+}
+
+void cpu_mips_irq_init_cpu(CPUMIPSState *env)
+{
+ qemu_irq *qi;
+ int i;
+
+ qi = qemu_allocate_irqs(cpu_mips_irq_request, mips_env_get_cpu(env), 8);
+ for (i = 0; i < 8; i++) {
+ env->irq[i] = qi[i];
+ }
+}
+
+void cpu_mips_soft_irq(CPUMIPSState *env, int irq, int level)
+{
+ if (irq < 0 || irq > 2) {
+ return;
+ }
+
+ qemu_set_irq(env->irq[irq], level);
+}
diff --git a/hw/mips/mips_jazz.c b/hw/mips/mips_jazz.c
new file mode 100644
index 00000000..9d60633e
--- /dev/null
+++ b/hw/mips/mips_jazz.c
@@ -0,0 +1,383 @@
+/*
+ * QEMU MIPS Jazz support
+ *
+ * Copyright (c) 2007-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.
+ */
+
+#include "hw/hw.h"
+#include "hw/mips/mips.h"
+#include "hw/mips/cpudevs.h"
+#include "hw/i386/pc.h"
+#include "hw/char/serial.h"
+#include "hw/isa/isa.h"
+#include "hw/block/fdc.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/arch_init.h"
+#include "hw/boards.h"
+#include "net/net.h"
+#include "hw/scsi/esp.h"
+#include "hw/mips/bios.h"
+#include "hw/loader.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/timer/i8254.h"
+#include "hw/audio/pcspk.h"
+#include "sysemu/block-backend.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "sysemu/qtest.h"
+#include "qemu/error-report.h"
+
+enum jazz_model_e
+{
+ JAZZ_MAGNUM,
+ JAZZ_PICA61,
+};
+
+static void main_cpu_reset(void *opaque)
+{
+ MIPSCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+static uint64_t rtc_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint8_t val;
+ address_space_read(&address_space_memory, 0x90000071,
+ MEMTXATTRS_UNSPECIFIED, &val, 1);
+ return val;
+}
+
+static void rtc_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ uint8_t buf = val & 0xff;
+ address_space_write(&address_space_memory, 0x90000071,
+ MEMTXATTRS_UNSPECIFIED, &buf, 1);
+}
+
+static const MemoryRegionOps rtc_ops = {
+ .read = rtc_read,
+ .write = rtc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t dma_dummy_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ /* Nothing to do. That is only to ensure that
+ * the current DMA acknowledge cycle is completed. */
+ return 0xff;
+}
+
+static void dma_dummy_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ /* Nothing to do. That is only to ensure that
+ * the current DMA acknowledge cycle is completed. */
+}
+
+static const MemoryRegionOps dma_dummy_ops = {
+ .read = dma_dummy_read,
+ .write = dma_dummy_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+#define MAGNUM_BIOS_SIZE_MAX 0x7e000
+#define MAGNUM_BIOS_SIZE (BIOS_SIZE < MAGNUM_BIOS_SIZE_MAX ? BIOS_SIZE : MAGNUM_BIOS_SIZE_MAX)
+
+static void cpu_request_exit(void *opaque, int irq, int level)
+{
+ CPUState *cpu = current_cpu;
+
+ if (cpu && level) {
+ cpu_exit(cpu);
+ }
+}
+
+static CPUUnassignedAccess real_do_unassigned_access;
+static void mips_jazz_do_unassigned_access(CPUState *cpu, hwaddr addr,
+ bool is_write, bool is_exec,
+ int opaque, unsigned size)
+{
+ if (!is_exec) {
+ /* ignore invalid access (ie do not raise exception) */
+ return;
+ }
+ (*real_do_unassigned_access)(cpu, addr, is_write, is_exec, opaque, size);
+}
+
+static void mips_jazz_init(MachineState *machine,
+ enum jazz_model_e jazz_model)
+{
+ MemoryRegion *address_space = get_system_memory();
+ const char *cpu_model = machine->cpu_model;
+ char *filename;
+ int bios_size, n;
+ MIPSCPU *cpu;
+ CPUClass *cc;
+ CPUMIPSState *env;
+ qemu_irq *i8259;
+ rc4030_dma *dmas;
+ MemoryRegion *rc4030_dma_mr;
+ MemoryRegion *isa_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *isa_io = g_new(MemoryRegion, 1);
+ MemoryRegion *rtc = g_new(MemoryRegion, 1);
+ MemoryRegion *i8042 = g_new(MemoryRegion, 1);
+ MemoryRegion *dma_dummy = g_new(MemoryRegion, 1);
+ NICInfo *nd;
+ DeviceState *dev, *rc4030;
+ SysBusDevice *sysbus;
+ ISABus *isa_bus;
+ ISADevice *pit;
+ DriveInfo *fds[MAX_FD];
+ qemu_irq esp_reset, dma_enable;
+ qemu_irq *cpu_exit_irq;
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *bios = g_new(MemoryRegion, 1);
+ MemoryRegion *bios2 = g_new(MemoryRegion, 1);
+
+ /* init CPUs */
+ if (cpu_model == NULL) {
+ cpu_model = "R4000";
+ }
+ cpu = cpu_mips_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+ qemu_register_reset(main_cpu_reset, cpu);
+
+ /* Chipset returns 0 in invalid reads and do not raise data exceptions.
+ * However, we can't simply add a global memory region to catch
+ * everything, as memory core directly call unassigned_mem_read/write
+ * on some invalid accesses, which call do_unassigned_access on the
+ * CPU, which raise an exception.
+ * Handle that case by hijacking the do_unassigned_access method on
+ * the CPU, and do not raise exceptions for data access. */
+ cc = CPU_GET_CLASS(cpu);
+ real_do_unassigned_access = cc->do_unassigned_access;
+ cc->do_unassigned_access = mips_jazz_do_unassigned_access;
+
+ /* allocate RAM */
+ memory_region_allocate_system_memory(ram, NULL, "mips_jazz.ram",
+ machine->ram_size);
+ memory_region_add_subregion(address_space, 0, ram);
+
+ memory_region_init_ram(bios, NULL, "mips_jazz.bios", MAGNUM_BIOS_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(bios);
+ memory_region_set_readonly(bios, true);
+ memory_region_init_alias(bios2, NULL, "mips_jazz.bios", bios,
+ 0, MAGNUM_BIOS_SIZE);
+ memory_region_add_subregion(address_space, 0x1fc00000LL, bios);
+ memory_region_add_subregion(address_space, 0xfff00000LL, bios2);
+
+ /* load the BIOS image. */
+ if (bios_name == NULL)
+ bios_name = BIOS_FILENAME;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = load_image_targphys(filename, 0xfff00000LL,
+ MAGNUM_BIOS_SIZE);
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+ if ((bios_size < 0 || bios_size > MAGNUM_BIOS_SIZE) && !qtest_enabled()) {
+ error_report("Could not load MIPS bios '%s'", bios_name);
+ exit(1);
+ }
+
+ /* Init CPU internal devices */
+ cpu_mips_irq_init_cpu(env);
+ cpu_mips_clock_init(env);
+
+ /* Chipset */
+ rc4030 = rc4030_init(&dmas, &rc4030_dma_mr);
+ sysbus = SYS_BUS_DEVICE(rc4030);
+ sysbus_connect_irq(sysbus, 0, env->irq[6]);
+ sysbus_connect_irq(sysbus, 1, env->irq[3]);
+ memory_region_add_subregion(address_space, 0x80000000,
+ sysbus_mmio_get_region(sysbus, 0));
+ memory_region_add_subregion(address_space, 0xf0000000,
+ sysbus_mmio_get_region(sysbus, 1));
+ memory_region_init_io(dma_dummy, NULL, &dma_dummy_ops, NULL, "dummy_dma", 0x1000);
+ memory_region_add_subregion(address_space, 0x8000d000, dma_dummy);
+
+ /* ISA bus: IO space at 0x90000000, mem space at 0x91000000 */
+ memory_region_init(isa_io, NULL, "isa-io", 0x00010000);
+ memory_region_init(isa_mem, NULL, "isa-mem", 0x01000000);
+ memory_region_add_subregion(address_space, 0x90000000, isa_io);
+ memory_region_add_subregion(address_space, 0x91000000, isa_mem);
+ isa_bus = isa_bus_new(NULL, isa_mem, isa_io);
+
+ /* ISA devices */
+ i8259 = i8259_init(isa_bus, env->irq[4]);
+ isa_bus_irqs(isa_bus, i8259);
+ cpu_exit_irq = qemu_allocate_irqs(cpu_request_exit, NULL, 1);
+ DMA_init(0, cpu_exit_irq);
+ pit = pit_init(isa_bus, 0x40, 0, NULL);
+ pcspk_init(isa_bus, pit);
+
+ /* Video card */
+ switch (jazz_model) {
+ case JAZZ_MAGNUM:
+ dev = qdev_create(NULL, "sysbus-g364");
+ qdev_init_nofail(dev);
+ sysbus = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(sysbus, 0, 0x60080000);
+ sysbus_mmio_map(sysbus, 1, 0x40000000);
+ sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(rc4030, 3));
+ {
+ /* Simple ROM, so user doesn't have to provide one */
+ MemoryRegion *rom_mr = g_new(MemoryRegion, 1);
+ memory_region_init_ram(rom_mr, NULL, "g364fb.rom", 0x80000,
+ &error_abort);
+ vmstate_register_ram_global(rom_mr);
+ memory_region_set_readonly(rom_mr, true);
+ uint8_t *rom = memory_region_get_ram_ptr(rom_mr);
+ memory_region_add_subregion(address_space, 0x60000000, rom_mr);
+ rom[0] = 0x10; /* Mips G364 */
+ }
+ break;
+ case JAZZ_PICA61:
+ isa_vga_mm_init(0x40000000, 0x60000000, 0, get_system_memory());
+ break;
+ default:
+ break;
+ }
+
+ /* Network controller */
+ for (n = 0; n < nb_nics; n++) {
+ nd = &nd_table[n];
+ if (!nd->model)
+ nd->model = g_strdup("dp83932");
+ if (strcmp(nd->model, "dp83932") == 0) {
+ qemu_check_nic_model(nd, "dp83932");
+
+ dev = qdev_create(NULL, "dp8393x");
+ qdev_set_nic_properties(dev, nd);
+ qdev_prop_set_uint8(dev, "it_shift", 2);
+ qdev_prop_set_ptr(dev, "dma_mr", rc4030_dma_mr);
+ qdev_init_nofail(dev);
+ sysbus = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(sysbus, 0, 0x80001000);
+ sysbus_mmio_map(sysbus, 1, 0x8000b000);
+ sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(rc4030, 4));
+ break;
+ } else if (is_help_option(nd->model)) {
+ fprintf(stderr, "qemu: Supported NICs: dp83932\n");
+ exit(1);
+ } else {
+ fprintf(stderr, "qemu: Unsupported NIC: %s\n", nd->model);
+ exit(1);
+ }
+ }
+
+ /* SCSI adapter */
+ esp_init(0x80002000, 0,
+ rc4030_dma_read, rc4030_dma_write, dmas[0],
+ qdev_get_gpio_in(rc4030, 5), &esp_reset, &dma_enable);
+
+ /* Floppy */
+ if (drive_get_max_bus(IF_FLOPPY) >= MAX_FD) {
+ fprintf(stderr, "qemu: too many floppy drives\n");
+ exit(1);
+ }
+ for (n = 0; n < MAX_FD; n++) {
+ fds[n] = drive_get(IF_FLOPPY, 0, n);
+ }
+ fdctrl_init_sysbus(qdev_get_gpio_in(rc4030, 1), 0, 0x80003000, fds);
+
+ /* Real time clock */
+ rtc_init(isa_bus, 1980, NULL);
+ memory_region_init_io(rtc, NULL, &rtc_ops, NULL, "rtc", 0x1000);
+ memory_region_add_subregion(address_space, 0x80004000, rtc);
+
+ /* Keyboard (i8042) */
+ i8042_mm_init(qdev_get_gpio_in(rc4030, 6), qdev_get_gpio_in(rc4030, 7),
+ i8042, 0x1000, 0x1);
+ memory_region_add_subregion(address_space, 0x80005000, i8042);
+
+ /* Serial ports */
+ if (serial_hds[0]) {
+ serial_mm_init(address_space, 0x80006000, 0,
+ qdev_get_gpio_in(rc4030, 8), 8000000/16,
+ serial_hds[0], DEVICE_NATIVE_ENDIAN);
+ }
+ if (serial_hds[1]) {
+ serial_mm_init(address_space, 0x80007000, 0,
+ qdev_get_gpio_in(rc4030, 9), 8000000/16,
+ serial_hds[1], DEVICE_NATIVE_ENDIAN);
+ }
+
+ /* Parallel port */
+ if (parallel_hds[0])
+ parallel_mm_init(address_space, 0x80008000, 0,
+ qdev_get_gpio_in(rc4030, 0), parallel_hds[0]);
+
+ /* FIXME: missing Jazz sound at 0x8000c000, rc4030[2] */
+
+ /* NVRAM */
+ dev = qdev_create(NULL, "ds1225y");
+ qdev_init_nofail(dev);
+ sysbus = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(sysbus, 0, 0x80009000);
+
+ /* LED indicator */
+ sysbus_create_simple("jazz-led", 0x8000f000, NULL);
+}
+
+static
+void mips_magnum_init(MachineState *machine)
+{
+ mips_jazz_init(machine, JAZZ_MAGNUM);
+}
+
+static
+void mips_pica61_init(MachineState *machine)
+{
+ mips_jazz_init(machine, JAZZ_PICA61);
+}
+
+static QEMUMachine mips_magnum_machine = {
+ .name = "magnum",
+ .desc = "MIPS Magnum",
+ .init = mips_magnum_init,
+ .block_default_type = IF_SCSI,
+};
+
+static QEMUMachine mips_pica61_machine = {
+ .name = "pica61",
+ .desc = "Acer Pica 61",
+ .init = mips_pica61_init,
+ .block_default_type = IF_SCSI,
+};
+
+static void mips_jazz_machine_init(void)
+{
+ qemu_register_machine(&mips_magnum_machine);
+ qemu_register_machine(&mips_pica61_machine);
+}
+
+machine_init(mips_jazz_machine_init);
diff --git a/hw/mips/mips_malta.c b/hw/mips/mips_malta.c
new file mode 100644
index 00000000..3082e753
--- /dev/null
+++ b/hw/mips/mips_malta.c
@@ -0,0 +1,1238 @@
+/*
+ * QEMU Malta board support
+ *
+ * Copyright (c) 2006 Aurelien Jarno
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/char/serial.h"
+#include "hw/block/fdc.h"
+#include "net/net.h"
+#include "hw/boards.h"
+#include "hw/i2c/smbus.h"
+#include "sysemu/block-backend.h"
+#include "hw/block/flash.h"
+#include "hw/mips/mips.h"
+#include "hw/mips/cpudevs.h"
+#include "hw/pci/pci.h"
+#include "sysemu/char.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/arch_init.h"
+#include "qemu/log.h"
+#include "hw/mips/bios.h"
+#include "hw/ide.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/timer/i8254.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "exec/address-spaces.h"
+#include "hw/sysbus.h" /* SysBusDevice */
+#include "qemu/host-utils.h"
+#include "sysemu/qtest.h"
+#include "qemu/error-report.h"
+#include "hw/empty_slot.h"
+#include "sysemu/kvm.h"
+#include "exec/semihost.h"
+
+//#define DEBUG_BOARD_INIT
+
+#define ENVP_ADDR 0x80002000l
+#define ENVP_NB_ENTRIES 16
+#define ENVP_ENTRY_SIZE 256
+
+/* Hardware addresses */
+#define FLASH_ADDRESS 0x1e000000ULL
+#define FPGA_ADDRESS 0x1f000000ULL
+#define RESET_ADDRESS 0x1fc00000ULL
+
+#define FLASH_SIZE 0x400000
+
+#define MAX_IDE_BUS 2
+
+typedef struct {
+ MemoryRegion iomem;
+ MemoryRegion iomem_lo; /* 0 - 0x900 */
+ MemoryRegion iomem_hi; /* 0xa00 - 0x100000 */
+ uint32_t leds;
+ uint32_t brk;
+ uint32_t gpout;
+ uint32_t i2cin;
+ uint32_t i2coe;
+ uint32_t i2cout;
+ uint32_t i2csel;
+ CharDriverState *display;
+ char display_text[9];
+ SerialState *uart;
+} MaltaFPGAState;
+
+#define TYPE_MIPS_MALTA "mips-malta"
+#define MIPS_MALTA(obj) OBJECT_CHECK(MaltaState, (obj), TYPE_MIPS_MALTA)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ qemu_irq *i8259;
+} MaltaState;
+
+static ISADevice *pit;
+
+static struct _loaderparams {
+ int ram_size, ram_low_size;
+ const char *kernel_filename;
+ const char *kernel_cmdline;
+ const char *initrd_filename;
+} loaderparams;
+
+/* Malta FPGA */
+static void malta_fpga_update_display(void *opaque)
+{
+ char leds_text[9];
+ int i;
+ MaltaFPGAState *s = opaque;
+
+ for (i = 7 ; i >= 0 ; i--) {
+ if (s->leds & (1 << i))
+ leds_text[i] = '#';
+ else
+ leds_text[i] = ' ';
+ }
+ leds_text[8] = '\0';
+
+ qemu_chr_fe_printf(s->display, "\e[H\n\n|\e[32m%-8.8s\e[00m|\r\n", leds_text);
+ qemu_chr_fe_printf(s->display, "\n\n\n\n|\e[31m%-8.8s\e[00m|", s->display_text);
+}
+
+/*
+ * EEPROM 24C01 / 24C02 emulation.
+ *
+ * Emulation for serial EEPROMs:
+ * 24C01 - 1024 bit (128 x 8)
+ * 24C02 - 2048 bit (256 x 8)
+ *
+ * Typical device names include Microchip 24C02SC or SGS Thomson ST24C02.
+ */
+
+//~ #define DEBUG
+
+#if defined(DEBUG)
+# define logout(fmt, ...) fprintf(stderr, "MALTA\t%-24s" fmt, __func__, ## __VA_ARGS__)
+#else
+# define logout(fmt, ...) ((void)0)
+#endif
+
+struct _eeprom24c0x_t {
+ uint8_t tick;
+ uint8_t address;
+ uint8_t command;
+ uint8_t ack;
+ uint8_t scl;
+ uint8_t sda;
+ uint8_t data;
+ //~ uint16_t size;
+ uint8_t contents[256];
+};
+
+typedef struct _eeprom24c0x_t eeprom24c0x_t;
+
+static eeprom24c0x_t spd_eeprom = {
+ .contents = {
+ /* 00000000: */ 0x80,0x08,0xFF,0x0D,0x0A,0xFF,0x40,0x00,
+ /* 00000008: */ 0x01,0x75,0x54,0x00,0x82,0x08,0x00,0x01,
+ /* 00000010: */ 0x8F,0x04,0x02,0x01,0x01,0x00,0x00,0x00,
+ /* 00000018: */ 0x00,0x00,0x00,0x14,0x0F,0x14,0x2D,0xFF,
+ /* 00000020: */ 0x15,0x08,0x15,0x08,0x00,0x00,0x00,0x00,
+ /* 00000028: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000030: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000038: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x12,0xD0,
+ /* 00000040: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000048: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000050: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000058: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000060: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000068: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000070: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /* 00000078: */ 0x00,0x00,0x00,0x00,0x00,0x00,0x64,0xF4,
+ },
+};
+
+static void generate_eeprom_spd(uint8_t *eeprom, ram_addr_t ram_size)
+{
+ enum { SDR = 0x4, DDR2 = 0x8 } type;
+ uint8_t *spd = spd_eeprom.contents;
+ uint8_t nbanks = 0;
+ uint16_t density = 0;
+ int i;
+
+ /* work in terms of MB */
+ ram_size >>= 20;
+
+ while ((ram_size >= 4) && (nbanks <= 2)) {
+ int sz_log2 = MIN(31 - clz32(ram_size), 14);
+ nbanks++;
+ density |= 1 << (sz_log2 - 2);
+ ram_size -= 1 << sz_log2;
+ }
+
+ /* split to 2 banks if possible */
+ if ((nbanks == 1) && (density > 1)) {
+ nbanks++;
+ density >>= 1;
+ }
+
+ if (density & 0xff00) {
+ density = (density & 0xe0) | ((density >> 8) & 0x1f);
+ type = DDR2;
+ } else if (!(density & 0x1f)) {
+ type = DDR2;
+ } else {
+ type = SDR;
+ }
+
+ if (ram_size) {
+ fprintf(stderr, "Warning: SPD cannot represent final %dMB"
+ " of SDRAM\n", (int)ram_size);
+ }
+
+ /* fill in SPD memory information */
+ spd[2] = type;
+ spd[5] = nbanks;
+ spd[31] = density;
+
+ /* checksum */
+ spd[63] = 0;
+ for (i = 0; i < 63; i++) {
+ spd[63] += spd[i];
+ }
+
+ /* copy for SMBUS */
+ memcpy(eeprom, spd, sizeof(spd_eeprom.contents));
+}
+
+static void generate_eeprom_serial(uint8_t *eeprom)
+{
+ int i, pos = 0;
+ uint8_t mac[6] = { 0x00 };
+ uint8_t sn[5] = { 0x01, 0x23, 0x45, 0x67, 0x89 };
+
+ /* version */
+ eeprom[pos++] = 0x01;
+
+ /* count */
+ eeprom[pos++] = 0x02;
+
+ /* MAC address */
+ eeprom[pos++] = 0x01; /* MAC */
+ eeprom[pos++] = 0x06; /* length */
+ memcpy(&eeprom[pos], mac, sizeof(mac));
+ pos += sizeof(mac);
+
+ /* serial number */
+ eeprom[pos++] = 0x02; /* serial */
+ eeprom[pos++] = 0x05; /* length */
+ memcpy(&eeprom[pos], sn, sizeof(sn));
+ pos += sizeof(sn);
+
+ /* checksum */
+ eeprom[pos] = 0;
+ for (i = 0; i < pos; i++) {
+ eeprom[pos] += eeprom[i];
+ }
+}
+
+static uint8_t eeprom24c0x_read(eeprom24c0x_t *eeprom)
+{
+ logout("%u: scl = %u, sda = %u, data = 0x%02x\n",
+ eeprom->tick, eeprom->scl, eeprom->sda, eeprom->data);
+ return eeprom->sda;
+}
+
+static void eeprom24c0x_write(eeprom24c0x_t *eeprom, int scl, int sda)
+{
+ if (eeprom->scl && scl && (eeprom->sda != sda)) {
+ logout("%u: scl = %u->%u, sda = %u->%u i2c %s\n",
+ eeprom->tick, eeprom->scl, scl, eeprom->sda, sda,
+ sda ? "stop" : "start");
+ if (!sda) {
+ eeprom->tick = 1;
+ eeprom->command = 0;
+ }
+ } else if (eeprom->tick == 0 && !eeprom->ack) {
+ /* Waiting for start. */
+ logout("%u: scl = %u->%u, sda = %u->%u wait for i2c start\n",
+ eeprom->tick, eeprom->scl, scl, eeprom->sda, sda);
+ } else if (!eeprom->scl && scl) {
+ logout("%u: scl = %u->%u, sda = %u->%u trigger bit\n",
+ eeprom->tick, eeprom->scl, scl, eeprom->sda, sda);
+ if (eeprom->ack) {
+ logout("\ti2c ack bit = 0\n");
+ sda = 0;
+ eeprom->ack = 0;
+ } else if (eeprom->sda == sda) {
+ uint8_t bit = (sda != 0);
+ logout("\ti2c bit = %d\n", bit);
+ if (eeprom->tick < 9) {
+ eeprom->command <<= 1;
+ eeprom->command += bit;
+ eeprom->tick++;
+ if (eeprom->tick == 9) {
+ logout("\tcommand 0x%04x, %s\n", eeprom->command,
+ bit ? "read" : "write");
+ eeprom->ack = 1;
+ }
+ } else if (eeprom->tick < 17) {
+ if (eeprom->command & 1) {
+ sda = ((eeprom->data & 0x80) != 0);
+ }
+ eeprom->address <<= 1;
+ eeprom->address += bit;
+ eeprom->tick++;
+ eeprom->data <<= 1;
+ if (eeprom->tick == 17) {
+ eeprom->data = eeprom->contents[eeprom->address];
+ logout("\taddress 0x%04x, data 0x%02x\n",
+ eeprom->address, eeprom->data);
+ eeprom->ack = 1;
+ eeprom->tick = 0;
+ }
+ } else if (eeprom->tick >= 17) {
+ sda = 0;
+ }
+ } else {
+ logout("\tsda changed with raising scl\n");
+ }
+ } else {
+ logout("%u: scl = %u->%u, sda = %u->%u\n", eeprom->tick, eeprom->scl,
+ scl, eeprom->sda, sda);
+ }
+ eeprom->scl = scl;
+ eeprom->sda = sda;
+}
+
+static uint64_t malta_fpga_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MaltaFPGAState *s = opaque;
+ uint32_t val = 0;
+ uint32_t saddr;
+
+ saddr = (addr & 0xfffff);
+
+ switch (saddr) {
+
+ /* SWITCH Register */
+ case 0x00200:
+ val = 0x00000000; /* All switches closed */
+ break;
+
+ /* STATUS Register */
+ case 0x00208:
+#ifdef TARGET_WORDS_BIGENDIAN
+ val = 0x00000012;
+#else
+ val = 0x00000010;
+#endif
+ break;
+
+ /* JMPRS Register */
+ case 0x00210:
+ val = 0x00;
+ break;
+
+ /* LEDBAR Register */
+ case 0x00408:
+ val = s->leds;
+ break;
+
+ /* BRKRES Register */
+ case 0x00508:
+ val = s->brk;
+ break;
+
+ /* UART Registers are handled directly by the serial device */
+
+ /* GPOUT Register */
+ case 0x00a00:
+ val = s->gpout;
+ break;
+
+ /* XXX: implement a real I2C controller */
+
+ /* GPINP Register */
+ case 0x00a08:
+ /* IN = OUT until a real I2C control is implemented */
+ if (s->i2csel)
+ val = s->i2cout;
+ else
+ val = 0x00;
+ break;
+
+ /* I2CINP Register */
+ case 0x00b00:
+ val = ((s->i2cin & ~1) | eeprom24c0x_read(&spd_eeprom));
+ break;
+
+ /* I2COE Register */
+ case 0x00b08:
+ val = s->i2coe;
+ break;
+
+ /* I2COUT Register */
+ case 0x00b10:
+ val = s->i2cout;
+ break;
+
+ /* I2CSEL Register */
+ case 0x00b18:
+ val = s->i2csel;
+ break;
+
+ default:
+#if 0
+ printf ("malta_fpga_read: Bad register offset 0x" TARGET_FMT_lx "\n",
+ addr);
+#endif
+ break;
+ }
+ return val;
+}
+
+static void malta_fpga_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MaltaFPGAState *s = opaque;
+ uint32_t saddr;
+
+ saddr = (addr & 0xfffff);
+
+ switch (saddr) {
+
+ /* SWITCH Register */
+ case 0x00200:
+ break;
+
+ /* JMPRS Register */
+ case 0x00210:
+ break;
+
+ /* LEDBAR Register */
+ case 0x00408:
+ s->leds = val & 0xff;
+ malta_fpga_update_display(s);
+ break;
+
+ /* ASCIIWORD Register */
+ case 0x00410:
+ snprintf(s->display_text, 9, "%08X", (uint32_t)val);
+ malta_fpga_update_display(s);
+ break;
+
+ /* ASCIIPOS0 to ASCIIPOS7 Registers */
+ case 0x00418:
+ case 0x00420:
+ case 0x00428:
+ case 0x00430:
+ case 0x00438:
+ case 0x00440:
+ case 0x00448:
+ case 0x00450:
+ s->display_text[(saddr - 0x00418) >> 3] = (char) val;
+ malta_fpga_update_display(s);
+ break;
+
+ /* SOFTRES Register */
+ case 0x00500:
+ if (val == 0x42)
+ qemu_system_reset_request ();
+ break;
+
+ /* BRKRES Register */
+ case 0x00508:
+ s->brk = val & 0xff;
+ break;
+
+ /* UART Registers are handled directly by the serial device */
+
+ /* GPOUT Register */
+ case 0x00a00:
+ s->gpout = val & 0xff;
+ break;
+
+ /* I2COE Register */
+ case 0x00b08:
+ s->i2coe = val & 0x03;
+ break;
+
+ /* I2COUT Register */
+ case 0x00b10:
+ eeprom24c0x_write(&spd_eeprom, val & 0x02, val & 0x01);
+ s->i2cout = val;
+ break;
+
+ /* I2CSEL Register */
+ case 0x00b18:
+ s->i2csel = val & 0x01;
+ break;
+
+ default:
+#if 0
+ printf ("malta_fpga_write: Bad register offset 0x" TARGET_FMT_lx "\n",
+ addr);
+#endif
+ break;
+ }
+}
+
+static const MemoryRegionOps malta_fpga_ops = {
+ .read = malta_fpga_read,
+ .write = malta_fpga_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void malta_fpga_reset(void *opaque)
+{
+ MaltaFPGAState *s = opaque;
+
+ s->leds = 0x00;
+ s->brk = 0x0a;
+ s->gpout = 0x00;
+ s->i2cin = 0x3;
+ s->i2coe = 0x0;
+ s->i2cout = 0x3;
+ s->i2csel = 0x1;
+
+ s->display_text[8] = '\0';
+ snprintf(s->display_text, 9, " ");
+}
+
+static void malta_fpga_led_init(CharDriverState *chr)
+{
+ qemu_chr_fe_printf(chr, "\e[HMalta LEDBAR\r\n");
+ qemu_chr_fe_printf(chr, "+--------+\r\n");
+ qemu_chr_fe_printf(chr, "+ +\r\n");
+ qemu_chr_fe_printf(chr, "+--------+\r\n");
+ qemu_chr_fe_printf(chr, "\n");
+ qemu_chr_fe_printf(chr, "Malta ASCII\r\n");
+ qemu_chr_fe_printf(chr, "+--------+\r\n");
+ qemu_chr_fe_printf(chr, "+ +\r\n");
+ qemu_chr_fe_printf(chr, "+--------+\r\n");
+}
+
+static MaltaFPGAState *malta_fpga_init(MemoryRegion *address_space,
+ hwaddr base, qemu_irq uart_irq, CharDriverState *uart_chr)
+{
+ MaltaFPGAState *s;
+
+ s = (MaltaFPGAState *)g_malloc0(sizeof(MaltaFPGAState));
+
+ memory_region_init_io(&s->iomem, NULL, &malta_fpga_ops, s,
+ "malta-fpga", 0x100000);
+ memory_region_init_alias(&s->iomem_lo, NULL, "malta-fpga",
+ &s->iomem, 0, 0x900);
+ memory_region_init_alias(&s->iomem_hi, NULL, "malta-fpga",
+ &s->iomem, 0xa00, 0x10000-0xa00);
+
+ memory_region_add_subregion(address_space, base, &s->iomem_lo);
+ memory_region_add_subregion(address_space, base + 0xa00, &s->iomem_hi);
+
+ s->display = qemu_chr_new("fpga", "vc:320x200", malta_fpga_led_init);
+
+ s->uart = serial_mm_init(address_space, base + 0x900, 3, uart_irq,
+ 230400, uart_chr, DEVICE_NATIVE_ENDIAN);
+
+ malta_fpga_reset(s);
+ qemu_register_reset(malta_fpga_reset, s);
+
+ return s;
+}
+
+/* Network support */
+static void network_init(PCIBus *pci_bus)
+{
+ int i;
+
+ for(i = 0; i < nb_nics; i++) {
+ NICInfo *nd = &nd_table[i];
+ const char *default_devaddr = NULL;
+
+ if (i == 0 && (!nd->model || strcmp(nd->model, "pcnet") == 0))
+ /* The malta board has a PCNet card using PCI SLOT 11 */
+ default_devaddr = "0b";
+
+ pci_nic_init_nofail(nd, pci_bus, "pcnet", default_devaddr);
+ }
+}
+
+/* ROM and pseudo bootloader
+
+ The following code implements a very very simple bootloader. It first
+ loads the registers a0 to a3 to the values expected by the OS, and
+ then jump at the kernel address.
+
+ The bootloader should pass the locations of the kernel arguments and
+ environment variables tables. Those tables contain the 32-bit address
+ of NULL terminated strings. The environment variables table should be
+ terminated by a NULL address.
+
+ For a simpler implementation, the number of kernel arguments is fixed
+ to two (the name of the kernel and the command line), and the two
+ tables are actually the same one.
+
+ The registers a0 to a3 should contain the following values:
+ a0 - number of kernel arguments
+ a1 - 32-bit address of the kernel arguments table
+ a2 - 32-bit address of the environment variables table
+ a3 - RAM size in bytes
+*/
+
+static void write_bootloader (CPUMIPSState *env, uint8_t *base,
+ int64_t run_addr, int64_t kernel_entry)
+{
+ uint32_t *p;
+
+ /* Small bootloader */
+ p = (uint32_t *)base;
+
+ stl_p(p++, 0x08000000 | /* j 0x1fc00580 */
+ ((run_addr + 0x580) & 0x0fffffff) >> 2);
+ stl_p(p++, 0x00000000); /* nop */
+
+ /* YAMON service vector */
+ stl_p(base + 0x500, run_addr + 0x0580); /* start: */
+ stl_p(base + 0x504, run_addr + 0x083c); /* print_count: */
+ stl_p(base + 0x520, run_addr + 0x0580); /* start: */
+ stl_p(base + 0x52c, run_addr + 0x0800); /* flush_cache: */
+ stl_p(base + 0x534, run_addr + 0x0808); /* print: */
+ stl_p(base + 0x538, run_addr + 0x0800); /* reg_cpu_isr: */
+ stl_p(base + 0x53c, run_addr + 0x0800); /* unred_cpu_isr: */
+ stl_p(base + 0x540, run_addr + 0x0800); /* reg_ic_isr: */
+ stl_p(base + 0x544, run_addr + 0x0800); /* unred_ic_isr: */
+ stl_p(base + 0x548, run_addr + 0x0800); /* reg_esr: */
+ stl_p(base + 0x54c, run_addr + 0x0800); /* unreg_esr: */
+ stl_p(base + 0x550, run_addr + 0x0800); /* getchar: */
+ stl_p(base + 0x554, run_addr + 0x0800); /* syscon_read: */
+
+
+ /* Second part of the bootloader */
+ p = (uint32_t *) (base + 0x580);
+
+ if (semihosting_get_argc()) {
+ /* Preserve a0 content as arguments have been passed */
+ stl_p(p++, 0x00000000); /* nop */
+ } else {
+ stl_p(p++, 0x24040002); /* addiu a0, zero, 2 */
+ }
+ stl_p(p++, 0x3c1d0000 | (((ENVP_ADDR - 64) >> 16) & 0xffff)); /* lui sp, high(ENVP_ADDR) */
+ stl_p(p++, 0x37bd0000 | ((ENVP_ADDR - 64) & 0xffff)); /* ori sp, sp, low(ENVP_ADDR) */
+ stl_p(p++, 0x3c050000 | ((ENVP_ADDR >> 16) & 0xffff)); /* lui a1, high(ENVP_ADDR) */
+ stl_p(p++, 0x34a50000 | (ENVP_ADDR & 0xffff)); /* ori a1, a1, low(ENVP_ADDR) */
+ stl_p(p++, 0x3c060000 | (((ENVP_ADDR + 8) >> 16) & 0xffff)); /* lui a2, high(ENVP_ADDR + 8) */
+ stl_p(p++, 0x34c60000 | ((ENVP_ADDR + 8) & 0xffff)); /* ori a2, a2, low(ENVP_ADDR + 8) */
+ stl_p(p++, 0x3c070000 | (loaderparams.ram_low_size >> 16)); /* lui a3, high(ram_low_size) */
+ stl_p(p++, 0x34e70000 | (loaderparams.ram_low_size & 0xffff)); /* ori a3, a3, low(ram_low_size) */
+
+ /* Load BAR registers as done by YAMON */
+ stl_p(p++, 0x3c09b400); /* lui t1, 0xb400 */
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ stl_p(p++, 0x3c08df00); /* lui t0, 0xdf00 */
+#else
+ stl_p(p++, 0x340800df); /* ori t0, r0, 0x00df */
+#endif
+ stl_p(p++, 0xad280068); /* sw t0, 0x0068(t1) */
+
+ stl_p(p++, 0x3c09bbe0); /* lui t1, 0xbbe0 */
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ stl_p(p++, 0x3c08c000); /* lui t0, 0xc000 */
+#else
+ stl_p(p++, 0x340800c0); /* ori t0, r0, 0x00c0 */
+#endif
+ stl_p(p++, 0xad280048); /* sw t0, 0x0048(t1) */
+#ifdef TARGET_WORDS_BIGENDIAN
+ stl_p(p++, 0x3c084000); /* lui t0, 0x4000 */
+#else
+ stl_p(p++, 0x34080040); /* ori t0, r0, 0x0040 */
+#endif
+ stl_p(p++, 0xad280050); /* sw t0, 0x0050(t1) */
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ stl_p(p++, 0x3c088000); /* lui t0, 0x8000 */
+#else
+ stl_p(p++, 0x34080080); /* ori t0, r0, 0x0080 */
+#endif
+ stl_p(p++, 0xad280058); /* sw t0, 0x0058(t1) */
+#ifdef TARGET_WORDS_BIGENDIAN
+ stl_p(p++, 0x3c083f00); /* lui t0, 0x3f00 */
+#else
+ stl_p(p++, 0x3408003f); /* ori t0, r0, 0x003f */
+#endif
+ stl_p(p++, 0xad280060); /* sw t0, 0x0060(t1) */
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ stl_p(p++, 0x3c08c100); /* lui t0, 0xc100 */
+#else
+ stl_p(p++, 0x340800c1); /* ori t0, r0, 0x00c1 */
+#endif
+ stl_p(p++, 0xad280080); /* sw t0, 0x0080(t1) */
+#ifdef TARGET_WORDS_BIGENDIAN
+ stl_p(p++, 0x3c085e00); /* lui t0, 0x5e00 */
+#else
+ stl_p(p++, 0x3408005e); /* ori t0, r0, 0x005e */
+#endif
+ stl_p(p++, 0xad280088); /* sw t0, 0x0088(t1) */
+
+ /* Jump to kernel code */
+ stl_p(p++, 0x3c1f0000 | ((kernel_entry >> 16) & 0xffff)); /* lui ra, high(kernel_entry) */
+ stl_p(p++, 0x37ff0000 | (kernel_entry & 0xffff)); /* ori ra, ra, low(kernel_entry) */
+ stl_p(p++, 0x03e00009); /* jalr ra */
+ stl_p(p++, 0x00000000); /* nop */
+
+ /* YAMON subroutines */
+ p = (uint32_t *) (base + 0x800);
+ stl_p(p++, 0x03e00009); /* jalr ra */
+ stl_p(p++, 0x24020000); /* li v0,0 */
+ /* 808 YAMON print */
+ stl_p(p++, 0x03e06821); /* move t5,ra */
+ stl_p(p++, 0x00805821); /* move t3,a0 */
+ stl_p(p++, 0x00a05021); /* move t2,a1 */
+ stl_p(p++, 0x91440000); /* lbu a0,0(t2) */
+ stl_p(p++, 0x254a0001); /* addiu t2,t2,1 */
+ stl_p(p++, 0x10800005); /* beqz a0,834 */
+ stl_p(p++, 0x00000000); /* nop */
+ stl_p(p++, 0x0ff0021c); /* jal 870 */
+ stl_p(p++, 0x00000000); /* nop */
+ stl_p(p++, 0x08000205); /* j 814 */
+ stl_p(p++, 0x00000000); /* nop */
+ stl_p(p++, 0x01a00009); /* jalr t5 */
+ stl_p(p++, 0x01602021); /* move a0,t3 */
+ /* 0x83c YAMON print_count */
+ stl_p(p++, 0x03e06821); /* move t5,ra */
+ stl_p(p++, 0x00805821); /* move t3,a0 */
+ stl_p(p++, 0x00a05021); /* move t2,a1 */
+ stl_p(p++, 0x00c06021); /* move t4,a2 */
+ stl_p(p++, 0x91440000); /* lbu a0,0(t2) */
+ stl_p(p++, 0x0ff0021c); /* jal 870 */
+ stl_p(p++, 0x00000000); /* nop */
+ stl_p(p++, 0x254a0001); /* addiu t2,t2,1 */
+ stl_p(p++, 0x258cffff); /* addiu t4,t4,-1 */
+ stl_p(p++, 0x1580fffa); /* bnez t4,84c */
+ stl_p(p++, 0x00000000); /* nop */
+ stl_p(p++, 0x01a00009); /* jalr t5 */
+ stl_p(p++, 0x01602021); /* move a0,t3 */
+ /* 0x870 */
+ stl_p(p++, 0x3c08b800); /* lui t0,0xb400 */
+ stl_p(p++, 0x350803f8); /* ori t0,t0,0x3f8 */
+ stl_p(p++, 0x91090005); /* lbu t1,5(t0) */
+ stl_p(p++, 0x00000000); /* nop */
+ stl_p(p++, 0x31290040); /* andi t1,t1,0x40 */
+ stl_p(p++, 0x1120fffc); /* beqz t1,878 <outch+0x8> */
+ stl_p(p++, 0x00000000); /* nop */
+ stl_p(p++, 0x03e00009); /* jalr ra */
+ stl_p(p++, 0xa1040000); /* sb a0,0(t0) */
+
+}
+
+static void GCC_FMT_ATTR(3, 4) prom_set(uint32_t* prom_buf, int index,
+ const char *string, ...)
+{
+ va_list ap;
+ int32_t table_addr;
+
+ if (index >= ENVP_NB_ENTRIES)
+ return;
+
+ if (string == NULL) {
+ prom_buf[index] = 0;
+ return;
+ }
+
+ table_addr = sizeof(int32_t) * ENVP_NB_ENTRIES + index * ENVP_ENTRY_SIZE;
+ prom_buf[index] = tswap32(ENVP_ADDR + table_addr);
+
+ va_start(ap, string);
+ vsnprintf((char *)prom_buf + table_addr, ENVP_ENTRY_SIZE, string, ap);
+ va_end(ap);
+}
+
+/* Kernel */
+static int64_t load_kernel (void)
+{
+ int64_t kernel_entry, kernel_high;
+ long initrd_size;
+ ram_addr_t initrd_offset;
+ int big_endian;
+ uint32_t *prom_buf;
+ long prom_size;
+ int prom_index = 0;
+ uint64_t (*xlate_to_kseg0) (void *opaque, uint64_t addr);
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ big_endian = 1;
+#else
+ big_endian = 0;
+#endif
+
+ if (load_elf(loaderparams.kernel_filename, cpu_mips_kseg0_to_phys, NULL,
+ (uint64_t *)&kernel_entry, NULL, (uint64_t *)&kernel_high,
+ big_endian, ELF_MACHINE, 1) < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ loaderparams.kernel_filename);
+ exit(1);
+ }
+
+ /* Sanity check where the kernel has been linked */
+ if (kvm_enabled()) {
+ if (kernel_entry & 0x80000000ll) {
+ error_report("KVM guest kernels must be linked in useg. "
+ "Did you forget to enable CONFIG_KVM_GUEST?");
+ exit(1);
+ }
+
+ xlate_to_kseg0 = cpu_mips_kvm_um_phys_to_kseg0;
+ } else {
+ if (!(kernel_entry & 0x80000000ll)) {
+ error_report("KVM guest kernels aren't supported with TCG. "
+ "Did you unintentionally enable CONFIG_KVM_GUEST?");
+ exit(1);
+ }
+
+ xlate_to_kseg0 = cpu_mips_phys_to_kseg0;
+ }
+
+ /* load initrd */
+ initrd_size = 0;
+ initrd_offset = 0;
+ if (loaderparams.initrd_filename) {
+ initrd_size = get_image_size (loaderparams.initrd_filename);
+ if (initrd_size > 0) {
+ initrd_offset = (kernel_high + ~INITRD_PAGE_MASK) & INITRD_PAGE_MASK;
+ if (initrd_offset + initrd_size > ram_size) {
+ fprintf(stderr,
+ "qemu: memory too small for initial ram disk '%s'\n",
+ loaderparams.initrd_filename);
+ exit(1);
+ }
+ initrd_size = load_image_targphys(loaderparams.initrd_filename,
+ initrd_offset,
+ ram_size - initrd_offset);
+ }
+ if (initrd_size == (target_ulong) -1) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ loaderparams.initrd_filename);
+ exit(1);
+ }
+ }
+
+ /* Setup prom parameters. */
+ prom_size = ENVP_NB_ENTRIES * (sizeof(int32_t) + ENVP_ENTRY_SIZE);
+ prom_buf = g_malloc(prom_size);
+
+ prom_set(prom_buf, prom_index++, "%s", loaderparams.kernel_filename);
+ if (initrd_size > 0) {
+ prom_set(prom_buf, prom_index++, "rd_start=0x%" PRIx64 " rd_size=%li %s",
+ xlate_to_kseg0(NULL, initrd_offset), initrd_size,
+ loaderparams.kernel_cmdline);
+ } else {
+ prom_set(prom_buf, prom_index++, "%s", loaderparams.kernel_cmdline);
+ }
+
+ prom_set(prom_buf, prom_index++, "memsize");
+ prom_set(prom_buf, prom_index++, "%u", loaderparams.ram_low_size);
+
+ prom_set(prom_buf, prom_index++, "ememsize");
+ prom_set(prom_buf, prom_index++, "%u", loaderparams.ram_size);
+
+ prom_set(prom_buf, prom_index++, "modetty0");
+ prom_set(prom_buf, prom_index++, "38400n8r");
+ prom_set(prom_buf, prom_index++, NULL);
+
+ rom_add_blob_fixed("prom", prom_buf, prom_size,
+ cpu_mips_kseg0_to_phys(NULL, ENVP_ADDR));
+
+ g_free(prom_buf);
+ return kernel_entry;
+}
+
+static void malta_mips_config(MIPSCPU *cpu)
+{
+ CPUMIPSState *env = &cpu->env;
+ CPUState *cs = CPU(cpu);
+
+ env->mvp->CP0_MVPConf0 |= ((smp_cpus - 1) << CP0MVPC0_PVPE) |
+ ((smp_cpus * cs->nr_threads - 1) << CP0MVPC0_PTC);
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ MIPSCPU *cpu = opaque;
+ CPUMIPSState *env = &cpu->env;
+
+ cpu_reset(CPU(cpu));
+
+ /* The bootloader does not need to be rewritten as it is located in a
+ read only location. The kernel location and the arguments table
+ location does not change. */
+ if (loaderparams.kernel_filename) {
+ env->CP0_Status &= ~(1 << CP0St_ERL);
+ }
+
+ malta_mips_config(cpu);
+
+ if (kvm_enabled()) {
+ /* Start running from the bootloader we wrote to end of RAM */
+ env->active_tc.PC = 0x40000000 + loaderparams.ram_size;
+ }
+}
+
+static void cpu_request_exit(void *opaque, int irq, int level)
+{
+ CPUState *cpu = current_cpu;
+
+ if (cpu && level) {
+ cpu_exit(cpu);
+ }
+}
+
+static
+void mips_malta_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ ram_addr_t ram_low_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ char *filename;
+ pflash_t *fl;
+ MemoryRegion *system_memory = get_system_memory();
+ MemoryRegion *ram_high = g_new(MemoryRegion, 1);
+ MemoryRegion *ram_low_preio = g_new(MemoryRegion, 1);
+ MemoryRegion *ram_low_postio;
+ MemoryRegion *bios, *bios_copy = g_new(MemoryRegion, 1);
+ target_long bios_size = FLASH_SIZE;
+ const size_t smbus_eeprom_size = 8 * 256;
+ uint8_t *smbus_eeprom_buf = g_malloc0(smbus_eeprom_size);
+ int64_t kernel_entry, bootloader_run_addr;
+ PCIBus *pci_bus;
+ ISABus *isa_bus;
+ MIPSCPU *cpu;
+ CPUMIPSState *env;
+ qemu_irq *isa_irq;
+ qemu_irq *cpu_exit_irq;
+ int piix4_devfn;
+ I2CBus *smbus;
+ int i;
+ DriveInfo *dinfo;
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+ DriveInfo *fd[MAX_FD];
+ int fl_idx = 0;
+ int fl_sectors = bios_size >> 16;
+ int be;
+
+ DeviceState *dev = qdev_create(NULL, TYPE_MIPS_MALTA);
+ MaltaState *s = MIPS_MALTA(dev);
+
+ /* The whole address space decoded by the GT-64120A doesn't generate
+ exception when accessing invalid memory. Create an empty slot to
+ emulate this feature. */
+ empty_slot_init(0, 0x20000000);
+
+ qdev_init_nofail(dev);
+
+ /* Make sure the first 3 serial ports are associated with a device. */
+ for(i = 0; i < 3; i++) {
+ if (!serial_hds[i]) {
+ char label[32];
+ snprintf(label, sizeof(label), "serial%d", i);
+ serial_hds[i] = qemu_chr_new(label, "null", NULL);
+ }
+ }
+
+ /* init CPUs */
+ if (cpu_model == NULL) {
+#ifdef TARGET_MIPS64
+ cpu_model = "20Kc";
+#else
+ cpu_model = "24Kf";
+#endif
+ }
+
+ for (i = 0; i < smp_cpus; i++) {
+ cpu = cpu_mips_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ /* Init internal devices */
+ cpu_mips_irq_init_cpu(env);
+ cpu_mips_clock_init(env);
+ qemu_register_reset(main_cpu_reset, cpu);
+ }
+ cpu = MIPS_CPU(first_cpu);
+ env = &cpu->env;
+
+ /* allocate RAM */
+ if (ram_size > (2048u << 20)) {
+ fprintf(stderr,
+ "qemu: Too much memory for this machine: %d MB, maximum 2048 MB\n",
+ ((unsigned int)ram_size / (1 << 20)));
+ exit(1);
+ }
+
+ /* register RAM at high address where it is undisturbed by IO */
+ memory_region_allocate_system_memory(ram_high, NULL, "mips_malta.ram",
+ ram_size);
+ memory_region_add_subregion(system_memory, 0x80000000, ram_high);
+
+ /* alias for pre IO hole access */
+ memory_region_init_alias(ram_low_preio, NULL, "mips_malta_low_preio.ram",
+ ram_high, 0, MIN(ram_size, (256 << 20)));
+ memory_region_add_subregion(system_memory, 0, ram_low_preio);
+
+ /* alias for post IO hole access, if there is enough RAM */
+ if (ram_size > (512 << 20)) {
+ ram_low_postio = g_new(MemoryRegion, 1);
+ memory_region_init_alias(ram_low_postio, NULL,
+ "mips_malta_low_postio.ram",
+ ram_high, 512 << 20,
+ ram_size - (512 << 20));
+ memory_region_add_subregion(system_memory, 512 << 20, ram_low_postio);
+ }
+
+ /* generate SPD EEPROM data */
+ generate_eeprom_spd(&smbus_eeprom_buf[0 * 256], ram_size);
+ generate_eeprom_serial(&smbus_eeprom_buf[6 * 256]);
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ be = 1;
+#else
+ be = 0;
+#endif
+ /* FPGA */
+ /* The CBUS UART is attached to the MIPS CPU INT2 pin, ie interrupt 4 */
+ malta_fpga_init(system_memory, FPGA_ADDRESS, env->irq[4], serial_hds[2]);
+
+ /* Load firmware in flash / BIOS. */
+ dinfo = drive_get(IF_PFLASH, 0, fl_idx);
+#ifdef DEBUG_BOARD_INIT
+ if (dinfo) {
+ printf("Register parallel flash %d size " TARGET_FMT_lx " at "
+ "addr %08llx '%s' %x\n",
+ fl_idx, bios_size, FLASH_ADDRESS,
+ blk_name(dinfo->bdrv), fl_sectors);
+ }
+#endif
+ fl = pflash_cfi01_register(FLASH_ADDRESS, NULL, "mips_malta.bios",
+ BIOS_SIZE,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ 65536, fl_sectors,
+ 4, 0x0000, 0x0000, 0x0000, 0x0000, be);
+ bios = pflash_cfi01_get_memory(fl);
+ fl_idx++;
+ if (kernel_filename) {
+ ram_low_size = MIN(ram_size, 256 << 20);
+ /* For KVM we reserve 1MB of RAM for running bootloader */
+ if (kvm_enabled()) {
+ ram_low_size -= 0x100000;
+ bootloader_run_addr = 0x40000000 + ram_low_size;
+ } else {
+ bootloader_run_addr = 0xbfc00000;
+ }
+
+ /* Write a small bootloader to the flash location. */
+ loaderparams.ram_size = ram_size;
+ loaderparams.ram_low_size = ram_low_size;
+ loaderparams.kernel_filename = kernel_filename;
+ loaderparams.kernel_cmdline = kernel_cmdline;
+ loaderparams.initrd_filename = initrd_filename;
+ kernel_entry = load_kernel();
+
+ write_bootloader(env, memory_region_get_ram_ptr(bios),
+ bootloader_run_addr, kernel_entry);
+ if (kvm_enabled()) {
+ /* Write the bootloader code @ the end of RAM, 1MB reserved */
+ write_bootloader(env, memory_region_get_ram_ptr(ram_low_preio) +
+ ram_low_size,
+ bootloader_run_addr, kernel_entry);
+ }
+ } else {
+ /* The flash region isn't executable from a KVM guest */
+ if (kvm_enabled()) {
+ error_report("KVM enabled but no -kernel argument was specified. "
+ "Booting from flash is not supported with KVM.");
+ exit(1);
+ }
+ /* Load firmware from flash. */
+ if (!dinfo) {
+ /* Load a BIOS image. */
+ if (bios_name == NULL) {
+ bios_name = BIOS_FILENAME;
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = load_image_targphys(filename, FLASH_ADDRESS,
+ BIOS_SIZE);
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+ if ((bios_size < 0 || bios_size > BIOS_SIZE) &&
+ !kernel_filename && !qtest_enabled()) {
+ error_report("Could not load MIPS bios '%s', and no "
+ "-kernel argument was specified", bios_name);
+ exit(1);
+ }
+ }
+ /* In little endian mode the 32bit words in the bios are swapped,
+ a neat trick which allows bi-endian firmware. */
+#ifndef TARGET_WORDS_BIGENDIAN
+ {
+ uint32_t *end, *addr = rom_ptr(FLASH_ADDRESS);
+ if (!addr) {
+ addr = memory_region_get_ram_ptr(bios);
+ }
+ end = (void *)addr + MIN(bios_size, 0x3e0000);
+ while (addr < end) {
+ bswap32s(addr);
+ addr++;
+ }
+ }
+#endif
+ }
+
+ /*
+ * Map the BIOS at a 2nd physical location, as on the real board.
+ * Copy it so that we can patch in the MIPS revision, which cannot be
+ * handled by an overlapping region as the resulting ROM code subpage
+ * regions are not executable.
+ */
+ memory_region_init_ram(bios_copy, NULL, "bios.1fc", BIOS_SIZE,
+ &error_abort);
+ if (!rom_copy(memory_region_get_ram_ptr(bios_copy),
+ FLASH_ADDRESS, BIOS_SIZE)) {
+ memcpy(memory_region_get_ram_ptr(bios_copy),
+ memory_region_get_ram_ptr(bios), BIOS_SIZE);
+ }
+ memory_region_set_readonly(bios_copy, true);
+ memory_region_add_subregion(system_memory, RESET_ADDRESS, bios_copy);
+
+ /* Board ID = 0x420 (Malta Board with CoreLV) */
+ stl_p(memory_region_get_ram_ptr(bios_copy) + 0x10, 0x00000420);
+
+ /* Init internal devices */
+ cpu_mips_irq_init_cpu(env);
+ cpu_mips_clock_init(env);
+
+ /*
+ * We have a circular dependency problem: pci_bus depends on isa_irq,
+ * isa_irq is provided by i8259, i8259 depends on ISA, ISA depends
+ * on piix4, and piix4 depends on pci_bus. To stop the cycle we have
+ * qemu_irq_proxy() adds an extra bit of indirection, allowing us
+ * to resolve the isa_irq -> i8259 dependency after i8259 is initialized.
+ */
+ isa_irq = qemu_irq_proxy(&s->i8259, 16);
+
+ /* Northbridge */
+ pci_bus = gt64120_register(isa_irq);
+
+ /* Southbridge */
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+
+ piix4_devfn = piix4_init(pci_bus, &isa_bus, 80);
+
+ /* Interrupt controller */
+ /* The 8259 is attached to the MIPS CPU INT0 pin, ie interrupt 2 */
+ s->i8259 = i8259_init(isa_bus, env->irq[2]);
+
+ isa_bus_irqs(isa_bus, s->i8259);
+ pci_piix4_ide_init(pci_bus, hd, piix4_devfn + 1);
+ pci_create_simple(pci_bus, piix4_devfn + 2, "piix4-usb-uhci");
+ smbus = piix4_pm_init(pci_bus, piix4_devfn + 3, 0x1100,
+ isa_get_irq(NULL, 9), NULL, 0, NULL);
+ smbus_eeprom_init(smbus, 8, smbus_eeprom_buf, smbus_eeprom_size);
+ g_free(smbus_eeprom_buf);
+ pit = pit_init(isa_bus, 0x40, 0, NULL);
+ cpu_exit_irq = qemu_allocate_irqs(cpu_request_exit, NULL, 1);
+ DMA_init(0, cpu_exit_irq);
+
+ /* Super I/O */
+ isa_create_simple(isa_bus, "i8042");
+
+ rtc_init(isa_bus, 2000, NULL);
+ serial_hds_isa_init(isa_bus, 2);
+ parallel_hds_isa_init(isa_bus, 1);
+
+ for(i = 0; i < MAX_FD; i++) {
+ fd[i] = drive_get(IF_FLOPPY, 0, i);
+ }
+ fdctrl_init_isa(isa_bus, fd);
+
+ /* Network card */
+ network_init(pci_bus);
+
+ /* Optional PCI video card */
+ pci_vga_init(pci_bus);
+}
+
+static int mips_malta_sysbus_device_init(SysBusDevice *sysbusdev)
+{
+ return 0;
+}
+
+static void mips_malta_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mips_malta_sysbus_device_init;
+}
+
+static const TypeInfo mips_malta_device = {
+ .name = TYPE_MIPS_MALTA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MaltaState),
+ .class_init = mips_malta_class_init,
+};
+
+static QEMUMachine mips_malta_machine = {
+ .name = "malta",
+ .desc = "MIPS Malta Core LV",
+ .init = mips_malta_init,
+ .max_cpus = 16,
+ .is_default = 1,
+};
+
+static void mips_malta_register_types(void)
+{
+ type_register_static(&mips_malta_device);
+}
+
+static void mips_malta_machine_init(void)
+{
+ qemu_register_machine(&mips_malta_machine);
+}
+
+type_init(mips_malta_register_types)
+machine_init(mips_malta_machine_init);
diff --git a/hw/mips/mips_mipssim.c b/hw/mips/mips_mipssim.c
new file mode 100644
index 00000000..61f74a63
--- /dev/null
+++ b/hw/mips/mips_mipssim.c
@@ -0,0 +1,245 @@
+/*
+ * QEMU/mipssim emulation
+ *
+ * Emulates a very simple machine model similar to the one used by the
+ * proprietary MIPS emulator.
+ *
+ * Copyright (c) 2007 Thiemo Seufer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/mips/mips.h"
+#include "hw/mips/cpudevs.h"
+#include "hw/char/serial.h"
+#include "hw/isa/isa.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/mips/bios.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+#include "sysemu/qtest.h"
+
+static struct _loaderparams {
+ int ram_size;
+ const char *kernel_filename;
+ const char *kernel_cmdline;
+ const char *initrd_filename;
+} loaderparams;
+
+typedef struct ResetData {
+ MIPSCPU *cpu;
+ uint64_t vector;
+} ResetData;
+
+static int64_t load_kernel(void)
+{
+ int64_t entry, kernel_high;
+ long kernel_size;
+ long initrd_size;
+ ram_addr_t initrd_offset;
+ int big_endian;
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ big_endian = 1;
+#else
+ big_endian = 0;
+#endif
+
+ kernel_size = load_elf(loaderparams.kernel_filename, cpu_mips_kseg0_to_phys,
+ NULL, (uint64_t *)&entry, NULL,
+ (uint64_t *)&kernel_high, big_endian,
+ ELF_MACHINE, 1);
+ if (kernel_size >= 0) {
+ if ((entry & ~0x7fffffffULL) == 0x80000000)
+ entry = (int32_t)entry;
+ } else {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ loaderparams.kernel_filename);
+ exit(1);
+ }
+
+ /* load initrd */
+ initrd_size = 0;
+ initrd_offset = 0;
+ if (loaderparams.initrd_filename) {
+ initrd_size = get_image_size (loaderparams.initrd_filename);
+ if (initrd_size > 0) {
+ initrd_offset = (kernel_high + ~INITRD_PAGE_MASK) & INITRD_PAGE_MASK;
+ if (initrd_offset + initrd_size > loaderparams.ram_size) {
+ fprintf(stderr,
+ "qemu: memory too small for initial ram disk '%s'\n",
+ loaderparams.initrd_filename);
+ exit(1);
+ }
+ initrd_size = load_image_targphys(loaderparams.initrd_filename,
+ initrd_offset, loaderparams.ram_size - initrd_offset);
+ }
+ if (initrd_size == (target_ulong) -1) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ loaderparams.initrd_filename);
+ exit(1);
+ }
+ }
+ return entry;
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ ResetData *s = (ResetData *)opaque;
+ CPUMIPSState *env = &s->cpu->env;
+
+ cpu_reset(CPU(s->cpu));
+ env->active_tc.PC = s->vector & ~(target_ulong)1;
+ if (s->vector & 1) {
+ env->hflags |= MIPS_HFLAG_M16;
+ }
+}
+
+static void mipsnet_init(int base, qemu_irq irq, NICInfo *nd)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "mipsnet");
+ qdev_set_nic_properties(dev, nd);
+ qdev_init_nofail(dev);
+
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irq);
+ memory_region_add_subregion(get_system_io(),
+ base,
+ sysbus_mmio_get_region(s, 0));
+}
+
+static void
+mips_mipssim_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ char *filename;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *isa = g_new(MemoryRegion, 1);
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *bios = g_new(MemoryRegion, 1);
+ MIPSCPU *cpu;
+ CPUMIPSState *env;
+ ResetData *reset_info;
+ int bios_size;
+
+ /* Init CPUs. */
+ if (cpu_model == NULL) {
+#ifdef TARGET_MIPS64
+ cpu_model = "5Kf";
+#else
+ cpu_model = "24Kf";
+#endif
+ }
+ cpu = cpu_mips_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ reset_info = g_malloc0(sizeof(ResetData));
+ reset_info->cpu = cpu;
+ reset_info->vector = env->active_tc.PC;
+ qemu_register_reset(main_cpu_reset, reset_info);
+
+ /* Allocate RAM. */
+ memory_region_allocate_system_memory(ram, NULL, "mips_mipssim.ram",
+ ram_size);
+ memory_region_init_ram(bios, NULL, "mips_mipssim.bios", BIOS_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(bios);
+ memory_region_set_readonly(bios, true);
+
+ memory_region_add_subregion(address_space_mem, 0, ram);
+
+ /* Map the BIOS / boot exception handler. */
+ memory_region_add_subregion(address_space_mem, 0x1fc00000LL, bios);
+ /* Load a BIOS / boot exception handler image. */
+ if (bios_name == NULL)
+ bios_name = BIOS_FILENAME;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = load_image_targphys(filename, 0x1fc00000LL, BIOS_SIZE);
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+ if ((bios_size < 0 || bios_size > BIOS_SIZE) &&
+ !kernel_filename && !qtest_enabled()) {
+ /* Bail out if we have neither a kernel image nor boot vector code. */
+ error_report("Could not load MIPS bios '%s', and no "
+ "-kernel argument was specified", bios_name);
+ exit(1);
+ } else {
+ /* We have a boot vector start address. */
+ env->active_tc.PC = (target_long)(int32_t)0xbfc00000;
+ }
+
+ if (kernel_filename) {
+ loaderparams.ram_size = ram_size;
+ loaderparams.kernel_filename = kernel_filename;
+ loaderparams.kernel_cmdline = kernel_cmdline;
+ loaderparams.initrd_filename = initrd_filename;
+ reset_info->vector = load_kernel();
+ }
+
+ /* Init CPU internal devices. */
+ cpu_mips_irq_init_cpu(env);
+ cpu_mips_clock_init(env);
+
+ /* Register 64 KB of ISA IO space at 0x1fd00000. */
+ memory_region_init_alias(isa, NULL, "isa_mmio",
+ get_system_io(), 0, 0x00010000);
+ memory_region_add_subregion(get_system_memory(), 0x1fd00000, isa);
+
+ /* A single 16450 sits at offset 0x3f8. It is attached to
+ MIPS CPU INT2, which is interrupt 4. */
+ if (serial_hds[0])
+ serial_init(0x3f8, env->irq[4], 115200, serial_hds[0],
+ get_system_io());
+
+ if (nd_table[0].used)
+ /* MIPSnet uses the MIPS CPU INT0, which is interrupt 2. */
+ mipsnet_init(0x4200, env->irq[2], &nd_table[0]);
+}
+
+static QEMUMachine mips_mipssim_machine = {
+ .name = "mipssim",
+ .desc = "MIPS MIPSsim platform",
+ .init = mips_mipssim_init,
+};
+
+static void mips_mipssim_machine_init(void)
+{
+ qemu_register_machine(&mips_mipssim_machine);
+}
+
+machine_init(mips_mipssim_machine_init);
diff --git a/hw/mips/mips_r4k.c b/hw/mips/mips_r4k.c
new file mode 100644
index 00000000..f4dcacd8
--- /dev/null
+++ b/hw/mips/mips_r4k.c
@@ -0,0 +1,314 @@
+/*
+ * QEMU/MIPS pseudo-board
+ *
+ * emulates a simple machine with ISA-like bus.
+ * ISA IO space mapped to the 0x14000000 (PHYS) and
+ * ISA memory at the 0x10000000 (PHYS, 16Mb in size).
+ * All peripherial devices are attached to this "bus" with
+ * the standard PC ISA addresses.
+*/
+#include "hw/hw.h"
+#include "hw/mips/mips.h"
+#include "hw/mips/cpudevs.h"
+#include "hw/i386/pc.h"
+#include "hw/char/serial.h"
+#include "hw/isa/isa.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/block/flash.h"
+#include "qemu/log.h"
+#include "hw/mips/bios.h"
+#include "hw/ide.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/timer/i8254.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "sysemu/qtest.h"
+
+#define MAX_IDE_BUS 2
+
+static const int ide_iobase[2] = { 0x1f0, 0x170 };
+static const int ide_iobase2[2] = { 0x3f6, 0x376 };
+static const int ide_irq[2] = { 14, 15 };
+
+static ISADevice *pit; /* PIT i8254 */
+
+/* i8254 PIT is attached to the IRQ0 at PIC i8259 */
+
+static struct _loaderparams {
+ int ram_size;
+ const char *kernel_filename;
+ const char *kernel_cmdline;
+ const char *initrd_filename;
+} loaderparams;
+
+static void mips_qemu_write (void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ if ((addr & 0xffff) == 0 && val == 42)
+ qemu_system_reset_request ();
+ else if ((addr & 0xffff) == 4 && val == 42)
+ qemu_system_shutdown_request ();
+}
+
+static uint64_t mips_qemu_read (void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static const MemoryRegionOps mips_qemu_ops = {
+ .read = mips_qemu_read,
+ .write = mips_qemu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+typedef struct ResetData {
+ MIPSCPU *cpu;
+ uint64_t vector;
+} ResetData;
+
+static int64_t load_kernel(void)
+{
+ int64_t entry, kernel_high;
+ long kernel_size, initrd_size, params_size;
+ ram_addr_t initrd_offset;
+ uint32_t *params_buf;
+ int big_endian;
+
+#ifdef TARGET_WORDS_BIGENDIAN
+ big_endian = 1;
+#else
+ big_endian = 0;
+#endif
+ kernel_size = load_elf(loaderparams.kernel_filename, cpu_mips_kseg0_to_phys,
+ NULL, (uint64_t *)&entry, NULL,
+ (uint64_t *)&kernel_high, big_endian,
+ ELF_MACHINE, 1);
+ if (kernel_size >= 0) {
+ if ((entry & ~0x7fffffffULL) == 0x80000000)
+ entry = (int32_t)entry;
+ } else {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ loaderparams.kernel_filename);
+ exit(1);
+ }
+
+ /* load initrd */
+ initrd_size = 0;
+ initrd_offset = 0;
+ if (loaderparams.initrd_filename) {
+ initrd_size = get_image_size (loaderparams.initrd_filename);
+ if (initrd_size > 0) {
+ initrd_offset = (kernel_high + ~INITRD_PAGE_MASK) & INITRD_PAGE_MASK;
+ if (initrd_offset + initrd_size > ram_size) {
+ fprintf(stderr,
+ "qemu: memory too small for initial ram disk '%s'\n",
+ loaderparams.initrd_filename);
+ exit(1);
+ }
+ initrd_size = load_image_targphys(loaderparams.initrd_filename,
+ initrd_offset,
+ ram_size - initrd_offset);
+ }
+ if (initrd_size == (target_ulong) -1) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ loaderparams.initrd_filename);
+ exit(1);
+ }
+ }
+
+ /* Store command line. */
+ params_size = 264;
+ params_buf = g_malloc(params_size);
+
+ params_buf[0] = tswap32(ram_size);
+ params_buf[1] = tswap32(0x12345678);
+
+ if (initrd_size > 0) {
+ snprintf((char *)params_buf + 8, 256, "rd_start=0x%" PRIx64 " rd_size=%li %s",
+ cpu_mips_phys_to_kseg0(NULL, initrd_offset),
+ initrd_size, loaderparams.kernel_cmdline);
+ } else {
+ snprintf((char *)params_buf + 8, 256, "%s", loaderparams.kernel_cmdline);
+ }
+
+ rom_add_blob_fixed("params", params_buf, params_size,
+ (16 << 20) - 264);
+
+ g_free(params_buf);
+ return entry;
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ ResetData *s = (ResetData *)opaque;
+ CPUMIPSState *env = &s->cpu->env;
+
+ cpu_reset(CPU(s->cpu));
+ env->active_tc.PC = s->vector;
+}
+
+static const int sector_len = 32 * 1024;
+static
+void mips_r4k_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ char *filename;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *bios;
+ MemoryRegion *iomem = g_new(MemoryRegion, 1);
+ MemoryRegion *isa_io = g_new(MemoryRegion, 1);
+ MemoryRegion *isa_mem = g_new(MemoryRegion, 1);
+ int bios_size;
+ MIPSCPU *cpu;
+ CPUMIPSState *env;
+ ResetData *reset_info;
+ int i;
+ qemu_irq *i8259;
+ ISABus *isa_bus;
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+ DriveInfo *dinfo;
+ int be;
+
+ /* init CPUs */
+ if (cpu_model == NULL) {
+#ifdef TARGET_MIPS64
+ cpu_model = "R4000";
+#else
+ cpu_model = "24Kf";
+#endif
+ }
+ cpu = cpu_mips_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ reset_info = g_malloc0(sizeof(ResetData));
+ reset_info->cpu = cpu;
+ reset_info->vector = env->active_tc.PC;
+ qemu_register_reset(main_cpu_reset, reset_info);
+
+ /* allocate RAM */
+ if (ram_size > (256 << 20)) {
+ fprintf(stderr,
+ "qemu: Too much memory for this machine: %d MB, maximum 256 MB\n",
+ ((unsigned int)ram_size / (1 << 20)));
+ exit(1);
+ }
+ memory_region_allocate_system_memory(ram, NULL, "mips_r4k.ram", ram_size);
+
+ memory_region_add_subregion(address_space_mem, 0, ram);
+
+ memory_region_init_io(iomem, NULL, &mips_qemu_ops, NULL, "mips-qemu", 0x10000);
+ memory_region_add_subregion(address_space_mem, 0x1fbf0000, iomem);
+
+ /* Try to load a BIOS image. If this fails, we continue regardless,
+ but initialize the hardware ourselves. When a kernel gets
+ preloaded we also initialize the hardware, since the BIOS wasn't
+ run. */
+ if (bios_name == NULL)
+ bios_name = BIOS_FILENAME;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = get_image_size(filename);
+ } else {
+ bios_size = -1;
+ }
+#ifdef TARGET_WORDS_BIGENDIAN
+ be = 1;
+#else
+ be = 0;
+#endif
+ if ((bios_size > 0) && (bios_size <= BIOS_SIZE)) {
+ bios = g_new(MemoryRegion, 1);
+ memory_region_init_ram(bios, NULL, "mips_r4k.bios", BIOS_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(bios);
+ memory_region_set_readonly(bios, true);
+ memory_region_add_subregion(get_system_memory(), 0x1fc00000, bios);
+
+ load_image_targphys(filename, 0x1fc00000, BIOS_SIZE);
+ } else if ((dinfo = drive_get(IF_PFLASH, 0, 0)) != NULL) {
+ uint32_t mips_rom = 0x00400000;
+ if (!pflash_cfi01_register(0x1fc00000, NULL, "mips_r4k.bios", mips_rom,
+ blk_by_legacy_dinfo(dinfo),
+ sector_len, mips_rom / sector_len,
+ 4, 0, 0, 0, 0, be)) {
+ fprintf(stderr, "qemu: Error registering flash memory.\n");
+ }
+ } else if (!qtest_enabled()) {
+ /* not fatal */
+ fprintf(stderr, "qemu: Warning, could not load MIPS bios '%s'\n",
+ bios_name);
+ }
+ if (filename) {
+ g_free(filename);
+ }
+
+ if (kernel_filename) {
+ loaderparams.ram_size = ram_size;
+ loaderparams.kernel_filename = kernel_filename;
+ loaderparams.kernel_cmdline = kernel_cmdline;
+ loaderparams.initrd_filename = initrd_filename;
+ reset_info->vector = load_kernel();
+ }
+
+ /* Init CPU internal devices */
+ cpu_mips_irq_init_cpu(env);
+ cpu_mips_clock_init(env);
+
+ /* ISA bus: IO space at 0x14000000, mem space at 0x10000000 */
+ memory_region_init_alias(isa_io, NULL, "isa-io",
+ get_system_io(), 0, 0x00010000);
+ memory_region_init(isa_mem, NULL, "isa-mem", 0x01000000);
+ memory_region_add_subregion(get_system_memory(), 0x14000000, isa_io);
+ memory_region_add_subregion(get_system_memory(), 0x10000000, isa_mem);
+ isa_bus = isa_bus_new(NULL, isa_mem, get_system_io());
+
+ /* The PIC is attached to the MIPS CPU INT0 pin */
+ i8259 = i8259_init(isa_bus, env->irq[2]);
+ isa_bus_irqs(isa_bus, i8259);
+
+ rtc_init(isa_bus, 2000, NULL);
+
+ pit = pit_init(isa_bus, 0x40, 0, NULL);
+
+ serial_hds_isa_init(isa_bus, MAX_SERIAL_PORTS);
+
+ isa_vga_init(isa_bus);
+
+ if (nd_table[0].used)
+ isa_ne2000_init(isa_bus, 0x300, 9, &nd_table[0]);
+
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+ for(i = 0; i < MAX_IDE_BUS; i++)
+ isa_ide_init(isa_bus, ide_iobase[i], ide_iobase2[i], ide_irq[i],
+ hd[MAX_IDE_DEVS * i],
+ hd[MAX_IDE_DEVS * i + 1]);
+
+ isa_create_simple(isa_bus, "i8042");
+}
+
+static QEMUMachine mips_machine = {
+ .name = "mips",
+ .desc = "mips r4k platform",
+ .init = mips_r4k_init,
+};
+
+static void mips_machine_init(void)
+{
+ qemu_register_machine(&mips_machine);
+}
+
+machine_init(mips_machine_init);
diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs
new file mode 100644
index 00000000..4aa76ffe
--- /dev/null
+++ b/hw/misc/Makefile.objs
@@ -0,0 +1,42 @@
+common-obj-$(CONFIG_APPLESMC) += applesmc.o
+common-obj-$(CONFIG_MAX111X) += max111x.o
+common-obj-$(CONFIG_TMP105) += tmp105.o
+common-obj-$(CONFIG_ISA_DEBUG) += debugexit.o
+common-obj-$(CONFIG_SGA) += sga.o
+common-obj-$(CONFIG_ISA_TESTDEV) += pc-testdev.o
+common-obj-$(CONFIG_PCI_TESTDEV) += pci-testdev.o
+
+obj-$(CONFIG_VMPORT) += vmport.o
+
+# ARM devices
+common-obj-$(CONFIG_PL310) += arm_l2x0.o
+common-obj-$(CONFIG_INTEGRATOR_DEBUG) += arm_integrator_debug.o
+common-obj-$(CONFIG_A9SCU) += a9scu.o
+common-obj-$(CONFIG_ARM11SCU) += arm11scu.o
+
+# PKUnity SoC devices
+common-obj-$(CONFIG_PUV3) += puv3_pm.o
+
+common-obj-$(CONFIG_MACIO) += macio/
+
+obj-$(CONFIG_IVSHMEM) += ivshmem.o
+
+obj-$(CONFIG_REALVIEW) += arm_sysctl.o
+obj-$(CONFIG_NSERIES) += cbus.o
+obj-$(CONFIG_ECCMEMCTL) += eccmemctl.o
+obj-$(CONFIG_EXYNOS4) += exynos4210_pmu.o
+obj-$(CONFIG_IMX) += imx_ccm.o
+obj-$(CONFIG_MILKYMIST) += milkymist-hpdmc.o
+obj-$(CONFIG_MILKYMIST) += milkymist-pfpu.o
+obj-$(CONFIG_MAINSTONE) += mst_fpga.o
+obj-$(CONFIG_OMAP) += omap_clk.o
+obj-$(CONFIG_OMAP) += omap_gpmc.o
+obj-$(CONFIG_OMAP) += omap_l4.o
+obj-$(CONFIG_OMAP) += omap_sdrc.o
+obj-$(CONFIG_OMAP) += omap_tap.o
+obj-$(CONFIG_SLAVIO) += slavio_misc.o
+obj-$(CONFIG_ZYNQ) += zynq_slcr.o
+obj-$(CONFIG_STM32F2XX_SYSCFG) += stm32f2xx_syscfg.o
+
+obj-$(CONFIG_PVPANIC) += pvpanic.o
+obj-$(CONFIG_EDU) += edu.o
diff --git a/hw/misc/a9scu.c b/hw/misc/a9scu.c
new file mode 100644
index 00000000..44349459
--- /dev/null
+++ b/hw/misc/a9scu.c
@@ -0,0 +1,152 @@
+/*
+ * Cortex-A9MPCore Snoop Control Unit (SCU) emulation.
+ *
+ * Copyright (c) 2009 CodeSourcery.
+ * Copyright (c) 2011 Linaro Limited.
+ * Written by Paul Brook, Peter Maydell.
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/misc/a9scu.h"
+
+static uint64_t a9_scu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ A9SCUState *s = (A9SCUState *)opaque;
+ switch (offset) {
+ case 0x00: /* Control */
+ return s->control;
+ case 0x04: /* Configuration */
+ return (((1 << s->num_cpu) - 1) << 4) | (s->num_cpu - 1);
+ case 0x08: /* CPU Power Status */
+ return s->status;
+ case 0x09: /* CPU status. */
+ return s->status >> 8;
+ case 0x0a: /* CPU status. */
+ return s->status >> 16;
+ case 0x0b: /* CPU status. */
+ return s->status >> 24;
+ case 0x0c: /* Invalidate All Registers In Secure State */
+ return 0;
+ case 0x40: /* Filtering Start Address Register */
+ case 0x44: /* Filtering End Address Register */
+ /* RAZ/WI, like an implementation with only one AXI master */
+ return 0;
+ case 0x50: /* SCU Access Control Register */
+ case 0x54: /* SCU Non-secure Access Control Register */
+ /* unimplemented, fall through */
+ default:
+ return 0;
+ }
+}
+
+static void a9_scu_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ A9SCUState *s = (A9SCUState *)opaque;
+ uint32_t mask;
+ uint32_t shift;
+ switch (size) {
+ case 1:
+ mask = 0xff;
+ break;
+ case 2:
+ mask = 0xffff;
+ break;
+ case 4:
+ mask = 0xffffffff;
+ break;
+ default:
+ fprintf(stderr, "Invalid size %u in write to a9 scu register %x\n",
+ size, (unsigned)offset);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* Control */
+ s->control = value & 1;
+ break;
+ case 0x4: /* Configuration: RO */
+ break;
+ case 0x08: case 0x09: case 0x0A: case 0x0B: /* Power Control */
+ shift = (offset - 0x8) * 8;
+ s->status &= ~(mask << shift);
+ s->status |= ((value & mask) << shift);
+ break;
+ case 0x0c: /* Invalidate All Registers In Secure State */
+ /* no-op as we do not implement caches */
+ break;
+ case 0x40: /* Filtering Start Address Register */
+ case 0x44: /* Filtering End Address Register */
+ /* RAZ/WI, like an implementation with only one AXI master */
+ break;
+ case 0x50: /* SCU Access Control Register */
+ case 0x54: /* SCU Non-secure Access Control Register */
+ /* unimplemented, fall through */
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps a9_scu_ops = {
+ .read = a9_scu_read,
+ .write = a9_scu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void a9_scu_reset(DeviceState *dev)
+{
+ A9SCUState *s = A9_SCU(dev);
+ s->control = 0;
+}
+
+static void a9_scu_init(Object *obj)
+{
+ A9SCUState *s = A9_SCU(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &a9_scu_ops, s,
+ "a9-scu", 0x100);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_a9_scu = {
+ .name = "a9-scu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(control, A9SCUState),
+ VMSTATE_UINT32(status, A9SCUState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property a9_scu_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", A9SCUState, num_cpu, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void a9_scu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->props = a9_scu_properties;
+ dc->vmsd = &vmstate_a9_scu;
+ dc->reset = a9_scu_reset;
+}
+
+static const TypeInfo a9_scu_info = {
+ .name = TYPE_A9_SCU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(A9SCUState),
+ .instance_init = a9_scu_init,
+ .class_init = a9_scu_class_init,
+};
+
+static void a9mp_register_types(void)
+{
+ type_register_static(&a9_scu_info);
+}
+
+type_init(a9mp_register_types)
diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c
new file mode 100644
index 00000000..6bd61e78
--- /dev/null
+++ b/hw/misc/applesmc.c
@@ -0,0 +1,279 @@
+/*
+ * Apple SMC controller
+ *
+ * Copyright (c) 2007 Alexander Graf
+ *
+ * Authors: Alexander Graf <agraf@suse.de>
+ * Susanne Graf <suse@csgraf.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * *****************************************************************
+ *
+ * In all Intel-based Apple hardware there is an SMC chip to control the
+ * backlight, fans and several other generic device parameters. It also
+ * contains the magic keys used to dongle Mac OS X to the device.
+ *
+ * This driver was mostly created by looking at the Linux AppleSMC driver
+ * implementation and does not support IRQ.
+ *
+ */
+
+#include "hw/hw.h"
+#include "hw/isa/isa.h"
+#include "ui/console.h"
+#include "qemu/timer.h"
+
+/* #define DEBUG_SMC */
+
+#define APPLESMC_DEFAULT_IOBASE 0x300
+/* data port used by Apple SMC */
+#define APPLESMC_DATA_PORT 0x0
+/* command/status port used by Apple SMC */
+#define APPLESMC_CMD_PORT 0x4
+#define APPLESMC_NR_PORTS 32
+
+#define APPLESMC_READ_CMD 0x10
+#define APPLESMC_WRITE_CMD 0x11
+#define APPLESMC_GET_KEY_BY_INDEX_CMD 0x12
+#define APPLESMC_GET_KEY_TYPE_CMD 0x13
+
+#ifdef DEBUG_SMC
+#define smc_debug(...) fprintf(stderr, "AppleSMC: " __VA_ARGS__)
+#else
+#define smc_debug(...) do { } while(0)
+#endif
+
+static char default_osk[64] = "This is a dummy key. Enter the real key "
+ "using the -osk parameter";
+
+struct AppleSMCData {
+ uint8_t len;
+ const char *key;
+ const char *data;
+ QLIST_ENTRY(AppleSMCData) node;
+};
+
+#define APPLE_SMC(obj) OBJECT_CHECK(AppleSMCState, (obj), TYPE_APPLE_SMC)
+
+typedef struct AppleSMCState AppleSMCState;
+struct AppleSMCState {
+ ISADevice parent_obj;
+
+ MemoryRegion io_data;
+ MemoryRegion io_cmd;
+ uint32_t iobase;
+ uint8_t cmd;
+ uint8_t status;
+ uint8_t key[4];
+ uint8_t read_pos;
+ uint8_t data_len;
+ uint8_t data_pos;
+ uint8_t data[255];
+ uint8_t charactic[4];
+ char *osk;
+ QLIST_HEAD(, AppleSMCData) data_def;
+};
+
+static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ AppleSMCState *s = opaque;
+
+ smc_debug("CMD Write B: %#x = %#x\n", addr, val);
+ switch(val) {
+ case APPLESMC_READ_CMD:
+ s->status = 0x0c;
+ break;
+ }
+ s->cmd = val;
+ s->read_pos = 0;
+ s->data_pos = 0;
+}
+
+static void applesmc_fill_data(AppleSMCState *s)
+{
+ struct AppleSMCData *d;
+
+ QLIST_FOREACH(d, &s->data_def, node) {
+ if (!memcmp(d->key, s->key, 4)) {
+ smc_debug("Key matched (%s Len=%d Data=%s)\n", d->key,
+ d->len, d->data);
+ memcpy(s->data, d->data, d->len);
+ return;
+ }
+ }
+}
+
+static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ AppleSMCState *s = opaque;
+
+ smc_debug("DATA Write B: %#x = %#x\n", addr, val);
+ switch(s->cmd) {
+ case APPLESMC_READ_CMD:
+ if(s->read_pos < 4) {
+ s->key[s->read_pos] = val;
+ s->status = 0x04;
+ } else if(s->read_pos == 4) {
+ s->data_len = val;
+ s->status = 0x05;
+ s->data_pos = 0;
+ smc_debug("Key = %c%c%c%c Len = %d\n", s->key[0],
+ s->key[1], s->key[2], s->key[3], val);
+ applesmc_fill_data(s);
+ }
+ s->read_pos++;
+ break;
+ }
+}
+
+static uint64_t applesmc_io_data_read(void *opaque, hwaddr addr1,
+ unsigned size)
+{
+ AppleSMCState *s = opaque;
+ uint8_t retval = 0;
+
+ switch(s->cmd) {
+ case APPLESMC_READ_CMD:
+ if(s->data_pos < s->data_len) {
+ retval = s->data[s->data_pos];
+ smc_debug("READ_DATA[%d] = %#hhx\n", s->data_pos,
+ retval);
+ s->data_pos++;
+ if(s->data_pos == s->data_len) {
+ s->status = 0x00;
+ smc_debug("EOF\n");
+ } else
+ s->status = 0x05;
+ }
+ }
+ smc_debug("DATA Read b: %#x = %#x\n", addr1, retval);
+
+ return retval;
+}
+
+static uint64_t applesmc_io_cmd_read(void *opaque, hwaddr addr1, unsigned size)
+{
+ AppleSMCState *s = opaque;
+
+ smc_debug("CMD Read B: %#x\n", addr1);
+ return s->status;
+}
+
+static void applesmc_add_key(AppleSMCState *s, const char *key,
+ int len, const char *data)
+{
+ struct AppleSMCData *def;
+
+ def = g_malloc0(sizeof(struct AppleSMCData));
+ def->key = key;
+ def->len = len;
+ def->data = data;
+
+ QLIST_INSERT_HEAD(&s->data_def, def, node);
+}
+
+static void qdev_applesmc_isa_reset(DeviceState *dev)
+{
+ AppleSMCState *s = APPLE_SMC(dev);
+ struct AppleSMCData *d, *next;
+
+ /* Remove existing entries */
+ QLIST_FOREACH_SAFE(d, &s->data_def, node, next) {
+ QLIST_REMOVE(d, node);
+ }
+
+ applesmc_add_key(s, "REV ", 6, "\x01\x13\x0f\x00\x00\x03");
+ applesmc_add_key(s, "OSK0", 32, s->osk);
+ applesmc_add_key(s, "OSK1", 32, s->osk + 32);
+ applesmc_add_key(s, "NATJ", 1, "\0");
+ applesmc_add_key(s, "MSSP", 1, "\0");
+ applesmc_add_key(s, "MSSD", 1, "\0x3");
+}
+
+static const MemoryRegionOps applesmc_data_io_ops = {
+ .write = applesmc_io_data_write,
+ .read = applesmc_io_data_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps applesmc_cmd_io_ops = {
+ .write = applesmc_io_cmd_write,
+ .read = applesmc_io_cmd_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void applesmc_isa_realize(DeviceState *dev, Error **errp)
+{
+ AppleSMCState *s = APPLE_SMC(dev);
+
+ memory_region_init_io(&s->io_data, OBJECT(s), &applesmc_data_io_ops, s,
+ "applesmc-data", 4);
+ isa_register_ioport(&s->parent_obj, &s->io_data,
+ s->iobase + APPLESMC_DATA_PORT);
+
+ memory_region_init_io(&s->io_cmd, OBJECT(s), &applesmc_cmd_io_ops, s,
+ "applesmc-cmd", 4);
+ isa_register_ioport(&s->parent_obj, &s->io_cmd,
+ s->iobase + APPLESMC_CMD_PORT);
+
+ if (!s->osk || (strlen(s->osk) != 64)) {
+ fprintf(stderr, "WARNING: Using AppleSMC with invalid key\n");
+ s->osk = default_osk;
+ }
+
+ QLIST_INIT(&s->data_def);
+ qdev_applesmc_isa_reset(dev);
+}
+
+static Property applesmc_isa_properties[] = {
+ DEFINE_PROP_UINT32(APPLESMC_PROP_IO_BASE, AppleSMCState, iobase,
+ APPLESMC_DEFAULT_IOBASE),
+ DEFINE_PROP_STRING("osk", AppleSMCState, osk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void qdev_applesmc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = applesmc_isa_realize;
+ dc->reset = qdev_applesmc_isa_reset;
+ dc->props = applesmc_isa_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo applesmc_isa_info = {
+ .name = TYPE_APPLE_SMC,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(AppleSMCState),
+ .class_init = qdev_applesmc_class_init,
+};
+
+static void applesmc_register_types(void)
+{
+ type_register_static(&applesmc_isa_info);
+}
+
+type_init(applesmc_register_types)
diff --git a/hw/misc/arm11scu.c b/hw/misc/arm11scu.c
new file mode 100644
index 00000000..a7916754
--- /dev/null
+++ b/hw/misc/arm11scu.c
@@ -0,0 +1,100 @@
+/*
+ * ARM11MPCore Snoop Control Unit (SCU) emulation
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Copyright (c) 2013 SUSE LINUX Products GmbH
+ * Written by Paul Brook and Andreas Färber
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/misc/arm11scu.h"
+
+static uint64_t mpcore_scu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ ARM11SCUState *s = (ARM11SCUState *)opaque;
+ int id;
+ /* SCU */
+ switch (offset) {
+ case 0x00: /* Control. */
+ return s->control;
+ case 0x04: /* Configuration. */
+ id = ((1 << s->num_cpu) - 1) << 4;
+ return id | (s->num_cpu - 1);
+ case 0x08: /* CPU status. */
+ return 0;
+ case 0x0c: /* Invalidate all. */
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "mpcore_priv_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void mpcore_scu_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ ARM11SCUState *s = (ARM11SCUState *)opaque;
+ /* SCU */
+ switch (offset) {
+ case 0: /* Control register. */
+ s->control = value & 1;
+ break;
+ case 0x0c: /* Invalidate all. */
+ /* This is a no-op as cache is not emulated. */
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "mpcore_priv_read: Bad offset %x\n", (int)offset);
+ }
+}
+
+static const MemoryRegionOps mpcore_scu_ops = {
+ .read = mpcore_scu_read,
+ .write = mpcore_scu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void arm11_scu_realize(DeviceState *dev, Error **errp)
+{
+}
+
+static void arm11_scu_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARM11SCUState *s = ARM11_SCU(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s),
+ &mpcore_scu_ops, s, "mpcore-scu", 0x100);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static Property arm11_scu_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", ARM11SCUState, num_cpu, 1),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void arm11_scu_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = arm11_scu_realize;
+ dc->props = arm11_scu_properties;
+}
+
+static const TypeInfo arm11_scu_type_info = {
+ .name = TYPE_ARM11_SCU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARM11SCUState),
+ .instance_init = arm11_scu_init,
+ .class_init = arm11_scu_class_init,
+};
+
+static void arm11_scu_register_types(void)
+{
+ type_register_static(&arm11_scu_type_info);
+}
+
+type_init(arm11_scu_register_types)
diff --git a/hw/misc/arm_integrator_debug.c b/hw/misc/arm_integrator_debug.c
new file mode 100644
index 00000000..6d9dd74e
--- /dev/null
+++ b/hw/misc/arm_integrator_debug.c
@@ -0,0 +1,99 @@
+/*
+ * LED, Switch and Debug control registers for ARM Integrator Boards
+ *
+ * This is currently a stub for this functionality but at least
+ * ensures something other than unassigned_mem_read() handles access
+ * to this area.
+ *
+ * The real h/w is described at:
+ * http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0159b/Babbfijf.html
+ *
+ * Copyright (c) 2013 Alex Bennée <alex@bennee.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "hw/misc/arm_integrator_debug.h"
+
+#define INTEGRATOR_DEBUG(obj) \
+ OBJECT_CHECK(IntegratorDebugState, (obj), TYPE_INTEGRATOR_DEBUG)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+} IntegratorDebugState;
+
+static uint64_t intdbg_control_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ switch (offset >> 2) {
+ case 0: /* ALPHA */
+ case 1: /* LEDS */
+ case 2: /* SWITCHES */
+ qemu_log_mask(LOG_UNIMP,
+ "%s: returning zero from %" HWADDR_PRIx ":%u\n",
+ __func__, offset, size);
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset %" HWADDR_PRIx,
+ __func__, offset);
+ return 0;
+ }
+}
+
+static void intdbg_control_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ switch (offset >> 2) {
+ case 1: /* ALPHA */
+ case 2: /* LEDS */
+ case 3: /* SWITCHES */
+ /* Nothing interesting implemented yet. */
+ qemu_log_mask(LOG_UNIMP,
+ "%s: ignoring write of %" PRIu64
+ " to %" HWADDR_PRIx ":%u\n",
+ __func__, value, offset, size);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write of %" PRIu64
+ " to bad offset %" HWADDR_PRIx "\n",
+ __func__, value, offset);
+ }
+}
+
+static const MemoryRegionOps intdbg_control_ops = {
+ .read = intdbg_control_read,
+ .write = intdbg_control_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void intdbg_control_init(Object *obj)
+{
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IntegratorDebugState *s = INTEGRATOR_DEBUG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &intdbg_control_ops,
+ NULL, "dbg-leds", 0x1000000);
+ sysbus_init_mmio(sd, &s->iomem);
+}
+
+static const TypeInfo intdbg_info = {
+ .name = TYPE_INTEGRATOR_DEBUG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IntegratorDebugState),
+ .instance_init = intdbg_control_init,
+};
+
+static void intdbg_register_types(void)
+{
+ type_register_static(&intdbg_info);
+}
+
+type_init(intdbg_register_types)
diff --git a/hw/misc/arm_l2x0.c b/hw/misc/arm_l2x0.c
new file mode 100644
index 00000000..9e220c9a
--- /dev/null
+++ b/hw/misc/arm_l2x0.c
@@ -0,0 +1,198 @@
+/*
+ * ARM dummy L210, L220, PL310 cache controller.
+ *
+ * Copyright (c) 2010-2012 Calxeda
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or any later version, as published by the Free Software
+ * Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/sysbus.h"
+
+/* L2C-310 r3p2 */
+#define CACHE_ID 0x410000c8
+
+#define TYPE_ARM_L2X0 "l2x0"
+#define ARM_L2X0(obj) OBJECT_CHECK(L2x0State, (obj), TYPE_ARM_L2X0)
+
+typedef struct L2x0State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t cache_type;
+ uint32_t ctrl;
+ uint32_t aux_ctrl;
+ uint32_t data_ctrl;
+ uint32_t tag_ctrl;
+ uint32_t filter_start;
+ uint32_t filter_end;
+} L2x0State;
+
+static const VMStateDescription vmstate_l2x0 = {
+ .name = "l2x0",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ctrl, L2x0State),
+ VMSTATE_UINT32(aux_ctrl, L2x0State),
+ VMSTATE_UINT32(data_ctrl, L2x0State),
+ VMSTATE_UINT32(tag_ctrl, L2x0State),
+ VMSTATE_UINT32(filter_start, L2x0State),
+ VMSTATE_UINT32(filter_end, L2x0State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+static uint64_t l2x0_priv_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t cache_data;
+ L2x0State *s = (L2x0State *)opaque;
+ offset &= 0xfff;
+ if (offset >= 0x730 && offset < 0x800) {
+ return 0; /* cache ops complete */
+ }
+ switch (offset) {
+ case 0:
+ return CACHE_ID;
+ case 0x4:
+ /* aux_ctrl values affect cache_type values */
+ cache_data = (s->aux_ctrl & (7 << 17)) >> 15;
+ cache_data |= (s->aux_ctrl & (1 << 16)) >> 16;
+ return s->cache_type |= (cache_data << 18) | (cache_data << 6);
+ case 0x100:
+ return s->ctrl;
+ case 0x104:
+ return s->aux_ctrl;
+ case 0x108:
+ return s->tag_ctrl;
+ case 0x10C:
+ return s->data_ctrl;
+ case 0xC00:
+ return s->filter_start;
+ case 0xC04:
+ return s->filter_end;
+ case 0xF40:
+ return 0;
+ case 0xF60:
+ return 0;
+ case 0xF80:
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "l2x0_priv_read: Bad offset %x\n", (int)offset);
+ break;
+ }
+ return 0;
+}
+
+static void l2x0_priv_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ L2x0State *s = (L2x0State *)opaque;
+ offset &= 0xfff;
+ if (offset >= 0x730 && offset < 0x800) {
+ /* ignore */
+ return;
+ }
+ switch (offset) {
+ case 0x100:
+ s->ctrl = value & 1;
+ break;
+ case 0x104:
+ s->aux_ctrl = value;
+ break;
+ case 0x108:
+ s->tag_ctrl = value;
+ break;
+ case 0x10C:
+ s->data_ctrl = value;
+ break;
+ case 0xC00:
+ s->filter_start = value;
+ break;
+ case 0xC04:
+ s->filter_end = value;
+ break;
+ case 0xF40:
+ return;
+ case 0xF60:
+ return;
+ case 0xF80:
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "l2x0_priv_write: Bad offset %x\n", (int)offset);
+ break;
+ }
+}
+
+static void l2x0_priv_reset(DeviceState *dev)
+{
+ L2x0State *s = ARM_L2X0(dev);
+
+ s->ctrl = 0;
+ s->aux_ctrl = 0x02020000;
+ s->tag_ctrl = 0;
+ s->data_ctrl = 0;
+ s->filter_start = 0;
+ s->filter_end = 0;
+}
+
+static const MemoryRegionOps l2x0_mem_ops = {
+ .read = l2x0_priv_read,
+ .write = l2x0_priv_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ };
+
+static int l2x0_priv_init(SysBusDevice *dev)
+{
+ L2x0State *s = ARM_L2X0(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &l2x0_mem_ops, s,
+ "l2x0_cc", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static Property l2x0_properties[] = {
+ DEFINE_PROP_UINT32("cache-type", L2x0State, cache_type, 0x1c100100),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void l2x0_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->init = l2x0_priv_init;
+ dc->vmsd = &vmstate_l2x0;
+ dc->props = l2x0_properties;
+ dc->reset = l2x0_priv_reset;
+}
+
+static const TypeInfo l2x0_info = {
+ .name = TYPE_ARM_L2X0,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(L2x0State),
+ .class_init = l2x0_class_init,
+};
+
+static void l2x0_register_types(void)
+{
+ type_register_static(&l2x0_info);
+}
+
+type_init(l2x0_register_types)
diff --git a/hw/misc/arm_sysctl.c b/hw/misc/arm_sysctl.c
new file mode 100644
index 00000000..3fad6f86
--- /dev/null
+++ b/hw/misc/arm_sysctl.c
@@ -0,0 +1,656 @@
+/*
+ * Status and system control registers for ARM RealView/Versatile boards.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "qemu/bitops.h"
+#include "hw/sysbus.h"
+#include "hw/arm/primecell.h"
+#include "sysemu/sysemu.h"
+
+#define LOCK_VALUE 0xa05f
+
+#define TYPE_ARM_SYSCTL "realview_sysctl"
+#define ARM_SYSCTL(obj) \
+ OBJECT_CHECK(arm_sysctl_state, (obj), TYPE_ARM_SYSCTL)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq pl110_mux_ctrl;
+
+ uint32_t sys_id;
+ uint32_t leds;
+ uint16_t lockval;
+ uint32_t cfgdata1;
+ uint32_t cfgdata2;
+ uint32_t flags;
+ uint32_t nvflags;
+ uint32_t resetlevel;
+ uint32_t proc_id;
+ uint32_t sys_mci;
+ uint32_t sys_cfgdata;
+ uint32_t sys_cfgctrl;
+ uint32_t sys_cfgstat;
+ uint32_t sys_clcd;
+ uint32_t mb_clock[6];
+ uint32_t *db_clock;
+ uint32_t db_num_vsensors;
+ uint32_t *db_voltage;
+ uint32_t db_num_clocks;
+ uint32_t *db_clock_reset;
+} arm_sysctl_state;
+
+static const VMStateDescription vmstate_arm_sysctl = {
+ .name = "realview_sysctl",
+ .version_id = 4,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(leds, arm_sysctl_state),
+ VMSTATE_UINT16(lockval, arm_sysctl_state),
+ VMSTATE_UINT32(cfgdata1, arm_sysctl_state),
+ VMSTATE_UINT32(cfgdata2, arm_sysctl_state),
+ VMSTATE_UINT32(flags, arm_sysctl_state),
+ VMSTATE_UINT32(nvflags, arm_sysctl_state),
+ VMSTATE_UINT32(resetlevel, arm_sysctl_state),
+ VMSTATE_UINT32_V(sys_mci, arm_sysctl_state, 2),
+ VMSTATE_UINT32_V(sys_cfgdata, arm_sysctl_state, 2),
+ VMSTATE_UINT32_V(sys_cfgctrl, arm_sysctl_state, 2),
+ VMSTATE_UINT32_V(sys_cfgstat, arm_sysctl_state, 2),
+ VMSTATE_UINT32_V(sys_clcd, arm_sysctl_state, 3),
+ VMSTATE_UINT32_ARRAY_V(mb_clock, arm_sysctl_state, 6, 4),
+ VMSTATE_VARRAY_UINT32(db_clock, arm_sysctl_state, db_num_clocks,
+ 4, vmstate_info_uint32, uint32_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* The PB926 actually uses a different format for
+ * its SYS_ID register. Fortunately the bits which are
+ * board type on later boards are distinct.
+ */
+#define BOARD_ID_PB926 0x100
+#define BOARD_ID_EB 0x140
+#define BOARD_ID_PBA8 0x178
+#define BOARD_ID_PBX 0x182
+#define BOARD_ID_VEXPRESS 0x190
+
+static int board_id(arm_sysctl_state *s)
+{
+ /* Extract the board ID field from the SYS_ID register value */
+ return (s->sys_id >> 16) & 0xfff;
+}
+
+static void arm_sysctl_reset(DeviceState *d)
+{
+ arm_sysctl_state *s = ARM_SYSCTL(d);
+ int i;
+
+ s->leds = 0;
+ s->lockval = 0;
+ s->cfgdata1 = 0;
+ s->cfgdata2 = 0;
+ s->flags = 0;
+ s->resetlevel = 0;
+ /* Motherboard oscillators (in Hz) */
+ s->mb_clock[0] = 50000000; /* Static memory clock: 50MHz */
+ s->mb_clock[1] = 23750000; /* motherboard CLCD clock: 23.75MHz */
+ s->mb_clock[2] = 24000000; /* IO FPGA peripheral clock: 24MHz */
+ s->mb_clock[3] = 24000000; /* IO FPGA reserved clock: 24MHz */
+ s->mb_clock[4] = 24000000; /* System bus global clock: 24MHz */
+ s->mb_clock[5] = 24000000; /* IO FPGA reserved clock: 24MHz */
+ /* Daughterboard oscillators: reset from property values */
+ for (i = 0; i < s->db_num_clocks; i++) {
+ s->db_clock[i] = s->db_clock_reset[i];
+ }
+ if (board_id(s) == BOARD_ID_VEXPRESS) {
+ /* On VExpress this register will RAZ/WI */
+ s->sys_clcd = 0;
+ } else {
+ /* All others: CLCDID 0x1f, indicating VGA */
+ s->sys_clcd = 0x1f00;
+ }
+}
+
+static uint64_t arm_sysctl_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ arm_sysctl_state *s = (arm_sysctl_state *)opaque;
+
+ switch (offset) {
+ case 0x00: /* ID */
+ return s->sys_id;
+ case 0x04: /* SW */
+ /* General purpose hardware switches.
+ We don't have a useful way of exposing these to the user. */
+ return 0;
+ case 0x08: /* LED */
+ return s->leds;
+ case 0x20: /* LOCK */
+ return s->lockval;
+ case 0x0c: /* OSC0 */
+ case 0x10: /* OSC1 */
+ case 0x14: /* OSC2 */
+ case 0x18: /* OSC3 */
+ case 0x1c: /* OSC4 */
+ case 0x24: /* 100HZ */
+ /* ??? Implement these. */
+ return 0;
+ case 0x28: /* CFGDATA1 */
+ return s->cfgdata1;
+ case 0x2c: /* CFGDATA2 */
+ return s->cfgdata2;
+ case 0x30: /* FLAGS */
+ return s->flags;
+ case 0x38: /* NVFLAGS */
+ return s->nvflags;
+ case 0x40: /* RESETCTL */
+ if (board_id(s) == BOARD_ID_VEXPRESS) {
+ /* reserved: RAZ/WI */
+ return 0;
+ }
+ return s->resetlevel;
+ case 0x44: /* PCICTL */
+ return 1;
+ case 0x48: /* MCI */
+ return s->sys_mci;
+ case 0x4c: /* FLASH */
+ return 0;
+ case 0x50: /* CLCD */
+ return s->sys_clcd;
+ case 0x54: /* CLCDSER */
+ return 0;
+ case 0x58: /* BOOTCS */
+ return 0;
+ case 0x5c: /* 24MHz */
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24000000, get_ticks_per_sec());
+ case 0x60: /* MISC */
+ return 0;
+ case 0x84: /* PROCID0 */
+ return s->proc_id;
+ case 0x88: /* PROCID1 */
+ return 0xff000000;
+ case 0x64: /* DMAPSR0 */
+ case 0x68: /* DMAPSR1 */
+ case 0x6c: /* DMAPSR2 */
+ case 0x70: /* IOSEL */
+ case 0x74: /* PLDCTL */
+ case 0x80: /* BUSID */
+ case 0x8c: /* OSCRESET0 */
+ case 0x90: /* OSCRESET1 */
+ case 0x94: /* OSCRESET2 */
+ case 0x98: /* OSCRESET3 */
+ case 0x9c: /* OSCRESET4 */
+ case 0xc0: /* SYS_TEST_OSC0 */
+ case 0xc4: /* SYS_TEST_OSC1 */
+ case 0xc8: /* SYS_TEST_OSC2 */
+ case 0xcc: /* SYS_TEST_OSC3 */
+ case 0xd0: /* SYS_TEST_OSC4 */
+ return 0;
+ case 0xa0: /* SYS_CFGDATA */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ return s->sys_cfgdata;
+ case 0xa4: /* SYS_CFGCTRL */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ return s->sys_cfgctrl;
+ case 0xa8: /* SYS_CFGSTAT */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ return s->sys_cfgstat;
+ default:
+ bad_reg:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "arm_sysctl_read: Bad register offset 0x%x\n",
+ (int)offset);
+ return 0;
+ }
+}
+
+/* SYS_CFGCTRL functions */
+#define SYS_CFG_OSC 1
+#define SYS_CFG_VOLT 2
+#define SYS_CFG_AMP 3
+#define SYS_CFG_TEMP 4
+#define SYS_CFG_RESET 5
+#define SYS_CFG_SCC 6
+#define SYS_CFG_MUXFPGA 7
+#define SYS_CFG_SHUTDOWN 8
+#define SYS_CFG_REBOOT 9
+#define SYS_CFG_DVIMODE 11
+#define SYS_CFG_POWER 12
+#define SYS_CFG_ENERGY 13
+
+/* SYS_CFGCTRL site field values */
+#define SYS_CFG_SITE_MB 0
+#define SYS_CFG_SITE_DB1 1
+#define SYS_CFG_SITE_DB2 2
+
+/**
+ * vexpress_cfgctrl_read:
+ * @s: arm_sysctl_state pointer
+ * @dcc, @function, @site, @position, @device: split out values from
+ * SYS_CFGCTRL register
+ * @val: pointer to where to put the read data on success
+ *
+ * Handle a VExpress SYS_CFGCTRL register read. On success, return true and
+ * write the read value to *val. On failure, return false (and val may
+ * or may not be written to).
+ */
+static bool vexpress_cfgctrl_read(arm_sysctl_state *s, unsigned int dcc,
+ unsigned int function, unsigned int site,
+ unsigned int position, unsigned int device,
+ uint32_t *val)
+{
+ /* We don't support anything other than DCC 0, board stack position 0
+ * or sites other than motherboard/daughterboard:
+ */
+ if (dcc != 0 || position != 0 ||
+ (site != SYS_CFG_SITE_MB && site != SYS_CFG_SITE_DB1)) {
+ goto cfgctrl_unimp;
+ }
+
+ switch (function) {
+ case SYS_CFG_VOLT:
+ if (site == SYS_CFG_SITE_DB1 && device < s->db_num_vsensors) {
+ *val = s->db_voltage[device];
+ return true;
+ }
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ /* There is only one motherboard voltage sensor:
+ * VIO : 3.3V : bus voltage between mother and daughterboard
+ */
+ *val = 3300000;
+ return true;
+ }
+ break;
+ case SYS_CFG_OSC:
+ if (site == SYS_CFG_SITE_MB && device < ARRAY_SIZE(s->mb_clock)) {
+ /* motherboard clock */
+ *val = s->mb_clock[device];
+ return true;
+ }
+ if (site == SYS_CFG_SITE_DB1 && device < s->db_num_clocks) {
+ /* daughterboard clock */
+ *val = s->db_clock[device];
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+cfgctrl_unimp:
+ qemu_log_mask(LOG_UNIMP,
+ "arm_sysctl: Unimplemented SYS_CFGCTRL read of function "
+ "0x%x DCC 0x%x site 0x%x position 0x%x device 0x%x\n",
+ function, dcc, site, position, device);
+ return false;
+}
+
+/**
+ * vexpress_cfgctrl_write:
+ * @s: arm_sysctl_state pointer
+ * @dcc, @function, @site, @position, @device: split out values from
+ * SYS_CFGCTRL register
+ * @val: data to write
+ *
+ * Handle a VExpress SYS_CFGCTRL register write. On success, return true.
+ * On failure, return false.
+ */
+static bool vexpress_cfgctrl_write(arm_sysctl_state *s, unsigned int dcc,
+ unsigned int function, unsigned int site,
+ unsigned int position, unsigned int device,
+ uint32_t val)
+{
+ /* We don't support anything other than DCC 0, board stack position 0
+ * or sites other than motherboard/daughterboard:
+ */
+ if (dcc != 0 || position != 0 ||
+ (site != SYS_CFG_SITE_MB && site != SYS_CFG_SITE_DB1)) {
+ goto cfgctrl_unimp;
+ }
+
+ switch (function) {
+ case SYS_CFG_OSC:
+ if (site == SYS_CFG_SITE_MB && device < ARRAY_SIZE(s->mb_clock)) {
+ /* motherboard clock */
+ s->mb_clock[device] = val;
+ return true;
+ }
+ if (site == SYS_CFG_SITE_DB1 && device < s->db_num_clocks) {
+ /* daughterboard clock */
+ s->db_clock[device] = val;
+ return true;
+ }
+ break;
+ case SYS_CFG_MUXFPGA:
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ /* Select whether video output comes from motherboard
+ * or daughterboard: log and ignore as QEMU doesn't
+ * support this.
+ */
+ qemu_log_mask(LOG_UNIMP, "arm_sysctl: selection of video output "
+ "not supported, ignoring\n");
+ return true;
+ }
+ break;
+ case SYS_CFG_SHUTDOWN:
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ qemu_system_shutdown_request();
+ return true;
+ }
+ break;
+ case SYS_CFG_REBOOT:
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ qemu_system_reset_request();
+ return true;
+ }
+ break;
+ case SYS_CFG_DVIMODE:
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ /* Selecting DVI mode is meaningless for QEMU: we will
+ * always display the output correctly according to the
+ * pixel height/width programmed into the CLCD controller.
+ */
+ return true;
+ }
+ default:
+ break;
+ }
+
+cfgctrl_unimp:
+ qemu_log_mask(LOG_UNIMP,
+ "arm_sysctl: Unimplemented SYS_CFGCTRL write of function "
+ "0x%x DCC 0x%x site 0x%x position 0x%x device 0x%x\n",
+ function, dcc, site, position, device);
+ return false;
+}
+
+static void arm_sysctl_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ arm_sysctl_state *s = (arm_sysctl_state *)opaque;
+
+ switch (offset) {
+ case 0x08: /* LED */
+ s->leds = val;
+ break;
+ case 0x0c: /* OSC0 */
+ case 0x10: /* OSC1 */
+ case 0x14: /* OSC2 */
+ case 0x18: /* OSC3 */
+ case 0x1c: /* OSC4 */
+ /* ??? */
+ break;
+ case 0x20: /* LOCK */
+ if (val == LOCK_VALUE)
+ s->lockval = val;
+ else
+ s->lockval = val & 0x7fff;
+ break;
+ case 0x28: /* CFGDATA1 */
+ /* ??? Need to implement this. */
+ s->cfgdata1 = val;
+ break;
+ case 0x2c: /* CFGDATA2 */
+ /* ??? Need to implement this. */
+ s->cfgdata2 = val;
+ break;
+ case 0x30: /* FLAGSSET */
+ s->flags |= val;
+ break;
+ case 0x34: /* FLAGSCLR */
+ s->flags &= ~val;
+ break;
+ case 0x38: /* NVFLAGSSET */
+ s->nvflags |= val;
+ break;
+ case 0x3c: /* NVFLAGSCLR */
+ s->nvflags &= ~val;
+ break;
+ case 0x40: /* RESETCTL */
+ switch (board_id(s)) {
+ case BOARD_ID_PB926:
+ if (s->lockval == LOCK_VALUE) {
+ s->resetlevel = val;
+ if (val & 0x100) {
+ qemu_system_reset_request();
+ }
+ }
+ break;
+ case BOARD_ID_PBX:
+ case BOARD_ID_PBA8:
+ if (s->lockval == LOCK_VALUE) {
+ s->resetlevel = val;
+ if (val & 0x04) {
+ qemu_system_reset_request();
+ }
+ }
+ break;
+ case BOARD_ID_VEXPRESS:
+ case BOARD_ID_EB:
+ default:
+ /* reserved: RAZ/WI */
+ break;
+ }
+ break;
+ case 0x44: /* PCICTL */
+ /* nothing to do. */
+ break;
+ case 0x4c: /* FLASH */
+ break;
+ case 0x50: /* CLCD */
+ switch (board_id(s)) {
+ case BOARD_ID_PB926:
+ /* On 926 bits 13:8 are R/O, bits 1:0 control
+ * the mux that defines how to interpret the PL110
+ * graphics format, and other bits are r/w but we
+ * don't implement them to do anything.
+ */
+ s->sys_clcd &= 0x3f00;
+ s->sys_clcd |= val & ~0x3f00;
+ qemu_set_irq(s->pl110_mux_ctrl, val & 3);
+ break;
+ case BOARD_ID_EB:
+ /* The EB is the same except that there is no mux since
+ * the EB has a PL111.
+ */
+ s->sys_clcd &= 0x3f00;
+ s->sys_clcd |= val & ~0x3f00;
+ break;
+ case BOARD_ID_PBA8:
+ case BOARD_ID_PBX:
+ /* On PBA8 and PBX bit 7 is r/w and all other bits
+ * are either r/o or RAZ/WI.
+ */
+ s->sys_clcd &= (1 << 7);
+ s->sys_clcd |= val & ~(1 << 7);
+ break;
+ case BOARD_ID_VEXPRESS:
+ default:
+ /* On VExpress this register is unimplemented and will RAZ/WI */
+ break;
+ }
+ break;
+ case 0x54: /* CLCDSER */
+ case 0x64: /* DMAPSR0 */
+ case 0x68: /* DMAPSR1 */
+ case 0x6c: /* DMAPSR2 */
+ case 0x70: /* IOSEL */
+ case 0x74: /* PLDCTL */
+ case 0x80: /* BUSID */
+ case 0x84: /* PROCID0 */
+ case 0x88: /* PROCID1 */
+ case 0x8c: /* OSCRESET0 */
+ case 0x90: /* OSCRESET1 */
+ case 0x94: /* OSCRESET2 */
+ case 0x98: /* OSCRESET3 */
+ case 0x9c: /* OSCRESET4 */
+ break;
+ case 0xa0: /* SYS_CFGDATA */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ s->sys_cfgdata = val;
+ return;
+ case 0xa4: /* SYS_CFGCTRL */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ /* Undefined bits [19:18] are RAZ/WI, and writing to
+ * the start bit just triggers the action; it always reads
+ * as zero.
+ */
+ s->sys_cfgctrl = val & ~((3 << 18) | (1 << 31));
+ if (val & (1 << 31)) {
+ /* Start bit set -- actually do something */
+ unsigned int dcc = extract32(s->sys_cfgctrl, 26, 4);
+ unsigned int function = extract32(s->sys_cfgctrl, 20, 6);
+ unsigned int site = extract32(s->sys_cfgctrl, 16, 2);
+ unsigned int position = extract32(s->sys_cfgctrl, 12, 4);
+ unsigned int device = extract32(s->sys_cfgctrl, 0, 12);
+ s->sys_cfgstat = 1; /* complete */
+ if (s->sys_cfgctrl & (1 << 30)) {
+ if (!vexpress_cfgctrl_write(s, dcc, function, site, position,
+ device, s->sys_cfgdata)) {
+ s->sys_cfgstat |= 2; /* error */
+ }
+ } else {
+ uint32_t val;
+ if (!vexpress_cfgctrl_read(s, dcc, function, site, position,
+ device, &val)) {
+ s->sys_cfgstat |= 2; /* error */
+ } else {
+ s->sys_cfgdata = val;
+ }
+ }
+ }
+ s->sys_cfgctrl &= ~(1 << 31);
+ return;
+ case 0xa8: /* SYS_CFGSTAT */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ s->sys_cfgstat = val & 3;
+ return;
+ default:
+ bad_reg:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "arm_sysctl_write: Bad register offset 0x%x\n",
+ (int)offset);
+ return;
+ }
+}
+
+static const MemoryRegionOps arm_sysctl_ops = {
+ .read = arm_sysctl_read,
+ .write = arm_sysctl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void arm_sysctl_gpio_set(void *opaque, int line, int level)
+{
+ arm_sysctl_state *s = (arm_sysctl_state *)opaque;
+ switch (line) {
+ case ARM_SYSCTL_GPIO_MMC_WPROT:
+ {
+ /* For PB926 and EB write-protect is bit 2 of SYS_MCI;
+ * for all later boards it is bit 1.
+ */
+ int bit = 2;
+ if ((board_id(s) == BOARD_ID_PB926) || (board_id(s) == BOARD_ID_EB)) {
+ bit = 4;
+ }
+ s->sys_mci &= ~bit;
+ if (level) {
+ s->sys_mci |= bit;
+ }
+ break;
+ }
+ case ARM_SYSCTL_GPIO_MMC_CARDIN:
+ s->sys_mci &= ~1;
+ if (level) {
+ s->sys_mci |= 1;
+ }
+ break;
+ }
+}
+
+static void arm_sysctl_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ arm_sysctl_state *s = ARM_SYSCTL(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &arm_sysctl_ops, s,
+ "arm-sysctl", 0x1000);
+ sysbus_init_mmio(sd, &s->iomem);
+ qdev_init_gpio_in(dev, arm_sysctl_gpio_set, 2);
+ qdev_init_gpio_out(dev, &s->pl110_mux_ctrl, 1);
+}
+
+static void arm_sysctl_realize(DeviceState *d, Error **errp)
+{
+ arm_sysctl_state *s = ARM_SYSCTL(d);
+
+ s->db_clock = g_new0(uint32_t, s->db_num_clocks);
+}
+
+static void arm_sysctl_finalize(Object *obj)
+{
+ arm_sysctl_state *s = ARM_SYSCTL(obj);
+
+ g_free(s->db_voltage);
+ g_free(s->db_clock);
+ g_free(s->db_clock_reset);
+}
+
+static Property arm_sysctl_properties[] = {
+ DEFINE_PROP_UINT32("sys_id", arm_sysctl_state, sys_id, 0),
+ DEFINE_PROP_UINT32("proc_id", arm_sysctl_state, proc_id, 0),
+ /* Daughterboard power supply voltages (as reported via SYS_CFG) */
+ DEFINE_PROP_ARRAY("db-voltage", arm_sysctl_state, db_num_vsensors,
+ db_voltage, qdev_prop_uint32, uint32_t),
+ /* Daughterboard clock reset values (as reported via SYS_CFG) */
+ DEFINE_PROP_ARRAY("db-clock", arm_sysctl_state, db_num_clocks,
+ db_clock_reset, qdev_prop_uint32, uint32_t),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void arm_sysctl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = arm_sysctl_realize;
+ dc->reset = arm_sysctl_reset;
+ dc->vmsd = &vmstate_arm_sysctl;
+ dc->props = arm_sysctl_properties;
+}
+
+static const TypeInfo arm_sysctl_info = {
+ .name = TYPE_ARM_SYSCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(arm_sysctl_state),
+ .instance_init = arm_sysctl_init,
+ .instance_finalize = arm_sysctl_finalize,
+ .class_init = arm_sysctl_class_init,
+};
+
+static void arm_sysctl_register_types(void)
+{
+ type_register_static(&arm_sysctl_info);
+}
+
+type_init(arm_sysctl_register_types)
diff --git a/hw/misc/cbus.c b/hw/misc/cbus.c
new file mode 100644
index 00000000..495d5078
--- /dev/null
+++ b/hw/misc/cbus.c
@@ -0,0 +1,618 @@
+/*
+ * CBUS three-pin bus and the Retu / Betty / Tahvo / Vilma / Avilma /
+ * Hinku / Vinku / Ahne / Pihi chips used in various Nokia platforms.
+ * Based on reverse-engineering of a linux driver.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "hw/irq.h"
+#include "hw/devices.h"
+#include "sysemu/sysemu.h"
+
+//#define DEBUG
+
+typedef struct {
+ void *opaque;
+ void (*io)(void *opaque, int rw, int reg, uint16_t *val);
+ int addr;
+} CBusSlave;
+
+typedef struct {
+ CBus cbus;
+
+ int sel;
+ int dat;
+ int clk;
+ int bit;
+ int dir;
+ uint16_t val;
+ qemu_irq dat_out;
+
+ int addr;
+ int reg;
+ int rw;
+ enum {
+ cbus_address,
+ cbus_value,
+ } cycle;
+
+ CBusSlave *slave[8];
+} CBusPriv;
+
+static void cbus_io(CBusPriv *s)
+{
+ if (s->slave[s->addr])
+ s->slave[s->addr]->io(s->slave[s->addr]->opaque,
+ s->rw, s->reg, &s->val);
+ else
+ hw_error("%s: bad slave address %i\n", __FUNCTION__, s->addr);
+}
+
+static void cbus_cycle(CBusPriv *s)
+{
+ switch (s->cycle) {
+ case cbus_address:
+ s->addr = (s->val >> 6) & 7;
+ s->rw = (s->val >> 5) & 1;
+ s->reg = (s->val >> 0) & 0x1f;
+
+ s->cycle = cbus_value;
+ s->bit = 15;
+ s->dir = !s->rw;
+ s->val = 0;
+
+ if (s->rw)
+ cbus_io(s);
+ break;
+
+ case cbus_value:
+ if (!s->rw)
+ cbus_io(s);
+
+ s->cycle = cbus_address;
+ s->bit = 8;
+ s->dir = 1;
+ s->val = 0;
+ break;
+ }
+}
+
+static void cbus_clk(void *opaque, int line, int level)
+{
+ CBusPriv *s = (CBusPriv *) opaque;
+
+ if (!s->sel && level && !s->clk) {
+ if (s->dir)
+ s->val |= s->dat << (s->bit --);
+ else
+ qemu_set_irq(s->dat_out, (s->val >> (s->bit --)) & 1);
+
+ if (s->bit < 0)
+ cbus_cycle(s);
+ }
+
+ s->clk = level;
+}
+
+static void cbus_dat(void *opaque, int line, int level)
+{
+ CBusPriv *s = (CBusPriv *) opaque;
+
+ s->dat = level;
+}
+
+static void cbus_sel(void *opaque, int line, int level)
+{
+ CBusPriv *s = (CBusPriv *) opaque;
+
+ if (!level) {
+ s->dir = 1;
+ s->bit = 8;
+ s->val = 0;
+ }
+
+ s->sel = level;
+}
+
+CBus *cbus_init(qemu_irq dat)
+{
+ CBusPriv *s = (CBusPriv *) g_malloc0(sizeof(*s));
+
+ s->dat_out = dat;
+ s->cbus.clk = qemu_allocate_irq(cbus_clk, s, 0);
+ s->cbus.dat = qemu_allocate_irq(cbus_dat, s, 0);
+ s->cbus.sel = qemu_allocate_irq(cbus_sel, s, 0);
+
+ s->sel = 1;
+ s->clk = 0;
+ s->dat = 0;
+
+ return &s->cbus;
+}
+
+void cbus_attach(CBus *bus, void *slave_opaque)
+{
+ CBusSlave *slave = (CBusSlave *) slave_opaque;
+ CBusPriv *s = (CBusPriv *) bus;
+
+ s->slave[slave->addr] = slave;
+}
+
+/* Retu/Vilma */
+typedef struct {
+ uint16_t irqst;
+ uint16_t irqen;
+ uint16_t cc[2];
+ int channel;
+ uint16_t result[16];
+ uint16_t sample;
+ uint16_t status;
+
+ struct {
+ uint16_t cal;
+ } rtc;
+
+ int is_vilma;
+ qemu_irq irq;
+ CBusSlave cbus;
+} CBusRetu;
+
+static void retu_interrupt_update(CBusRetu *s)
+{
+ qemu_set_irq(s->irq, s->irqst & ~s->irqen);
+}
+
+#define RETU_REG_ASICR 0x00 /* (RO) ASIC ID & revision */
+#define RETU_REG_IDR 0x01 /* (T) Interrupt ID */
+#define RETU_REG_IMR 0x02 /* (RW) Interrupt mask */
+#define RETU_REG_RTCDSR 0x03 /* (RW) RTC seconds register */
+#define RETU_REG_RTCHMR 0x04 /* (RO) RTC hours and minutes reg */
+#define RETU_REG_RTCHMAR 0x05 /* (RW) RTC hours and minutes set reg */
+#define RETU_REG_RTCCALR 0x06 /* (RW) RTC calibration register */
+#define RETU_REG_ADCR 0x08 /* (RW) ADC result register */
+#define RETU_REG_ADCSCR 0x09 /* (RW) ADC sample control register */
+#define RETU_REG_AFCR 0x0a /* (RW) AFC register */
+#define RETU_REG_ANTIFR 0x0b /* (RW) AntiF register */
+#define RETU_REG_CALIBR 0x0c /* (RW) CalibR register*/
+#define RETU_REG_CCR1 0x0d /* (RW) Common control register 1 */
+#define RETU_REG_CCR2 0x0e /* (RW) Common control register 2 */
+#define RETU_REG_RCTRL_CLR 0x0f /* (T) Regulator clear register */
+#define RETU_REG_RCTRL_SET 0x10 /* (T) Regulator set register */
+#define RETU_REG_TXCR 0x11 /* (RW) TxC register */
+#define RETU_REG_STATUS 0x16 /* (RO) Status register */
+#define RETU_REG_WATCHDOG 0x17 /* (RW) Watchdog register */
+#define RETU_REG_AUDTXR 0x18 /* (RW) Audio Codec Tx register */
+#define RETU_REG_AUDPAR 0x19 /* (RW) AudioPA register */
+#define RETU_REG_AUDRXR1 0x1a /* (RW) Audio receive register 1 */
+#define RETU_REG_AUDRXR2 0x1b /* (RW) Audio receive register 2 */
+#define RETU_REG_SGR1 0x1c /* (RW) */
+#define RETU_REG_SCR1 0x1d /* (RW) */
+#define RETU_REG_SGR2 0x1e /* (RW) */
+#define RETU_REG_SCR2 0x1f /* (RW) */
+
+/* Retu Interrupt sources */
+enum {
+ retu_int_pwr = 0, /* Power button */
+ retu_int_char = 1, /* Charger */
+ retu_int_rtcs = 2, /* Seconds */
+ retu_int_rtcm = 3, /* Minutes */
+ retu_int_rtcd = 4, /* Days */
+ retu_int_rtca = 5, /* Alarm */
+ retu_int_hook = 6, /* Hook */
+ retu_int_head = 7, /* Headset */
+ retu_int_adcs = 8, /* ADC sample */
+};
+
+/* Retu ADC channel wiring */
+enum {
+ retu_adc_bsi = 1, /* BSI */
+ retu_adc_batt_temp = 2, /* Battery temperature */
+ retu_adc_chg_volt = 3, /* Charger voltage */
+ retu_adc_head_det = 4, /* Headset detection */
+ retu_adc_hook_det = 5, /* Hook detection */
+ retu_adc_rf_gp = 6, /* RF GP */
+ retu_adc_tx_det = 7, /* Wideband Tx detection */
+ retu_adc_batt_volt = 8, /* Battery voltage */
+ retu_adc_sens = 10, /* Light sensor */
+ retu_adc_sens_temp = 11, /* Light sensor temperature */
+ retu_adc_bbatt_volt = 12, /* Backup battery voltage */
+ retu_adc_self_temp = 13, /* RETU temperature */
+};
+
+static inline uint16_t retu_read(CBusRetu *s, int reg)
+{
+#ifdef DEBUG
+ printf("RETU read at %02x\n", reg);
+#endif
+
+ switch (reg) {
+ case RETU_REG_ASICR:
+ return 0x0215 | (s->is_vilma << 7);
+
+ case RETU_REG_IDR: /* TODO: Or is this ffs(s->irqst)? */
+ return s->irqst;
+
+ case RETU_REG_IMR:
+ return s->irqen;
+
+ case RETU_REG_RTCDSR:
+ case RETU_REG_RTCHMR:
+ case RETU_REG_RTCHMAR:
+ /* TODO */
+ return 0x0000;
+
+ case RETU_REG_RTCCALR:
+ return s->rtc.cal;
+
+ case RETU_REG_ADCR:
+ return (s->channel << 10) | s->result[s->channel];
+ case RETU_REG_ADCSCR:
+ return s->sample;
+
+ case RETU_REG_AFCR:
+ case RETU_REG_ANTIFR:
+ case RETU_REG_CALIBR:
+ /* TODO */
+ return 0x0000;
+
+ case RETU_REG_CCR1:
+ return s->cc[0];
+ case RETU_REG_CCR2:
+ return s->cc[1];
+
+ case RETU_REG_RCTRL_CLR:
+ case RETU_REG_RCTRL_SET:
+ case RETU_REG_TXCR:
+ /* TODO */
+ return 0x0000;
+
+ case RETU_REG_STATUS:
+ return s->status;
+
+ case RETU_REG_WATCHDOG:
+ case RETU_REG_AUDTXR:
+ case RETU_REG_AUDPAR:
+ case RETU_REG_AUDRXR1:
+ case RETU_REG_AUDRXR2:
+ case RETU_REG_SGR1:
+ case RETU_REG_SCR1:
+ case RETU_REG_SGR2:
+ case RETU_REG_SCR2:
+ /* TODO */
+ return 0x0000;
+
+ default:
+ hw_error("%s: bad register %02x\n", __FUNCTION__, reg);
+ }
+}
+
+static inline void retu_write(CBusRetu *s, int reg, uint16_t val)
+{
+#ifdef DEBUG
+ printf("RETU write of %04x at %02x\n", val, reg);
+#endif
+
+ switch (reg) {
+ case RETU_REG_IDR:
+ s->irqst ^= val;
+ retu_interrupt_update(s);
+ break;
+
+ case RETU_REG_IMR:
+ s->irqen = val;
+ retu_interrupt_update(s);
+ break;
+
+ case RETU_REG_RTCDSR:
+ case RETU_REG_RTCHMAR:
+ /* TODO */
+ break;
+
+ case RETU_REG_RTCCALR:
+ s->rtc.cal = val;
+ break;
+
+ case RETU_REG_ADCR:
+ s->channel = (val >> 10) & 0xf;
+ s->irqst |= 1 << retu_int_adcs;
+ retu_interrupt_update(s);
+ break;
+ case RETU_REG_ADCSCR:
+ s->sample &= ~val;
+ break;
+
+ case RETU_REG_AFCR:
+ case RETU_REG_ANTIFR:
+ case RETU_REG_CALIBR:
+
+ case RETU_REG_CCR1:
+ s->cc[0] = val;
+ break;
+ case RETU_REG_CCR2:
+ s->cc[1] = val;
+ break;
+
+ case RETU_REG_RCTRL_CLR:
+ case RETU_REG_RCTRL_SET:
+ /* TODO */
+ break;
+
+ case RETU_REG_WATCHDOG:
+ if (val == 0 && (s->cc[0] & 2))
+ qemu_system_shutdown_request();
+ break;
+
+ case RETU_REG_TXCR:
+ case RETU_REG_AUDTXR:
+ case RETU_REG_AUDPAR:
+ case RETU_REG_AUDRXR1:
+ case RETU_REG_AUDRXR2:
+ case RETU_REG_SGR1:
+ case RETU_REG_SCR1:
+ case RETU_REG_SGR2:
+ case RETU_REG_SCR2:
+ /* TODO */
+ break;
+
+ default:
+ hw_error("%s: bad register %02x\n", __FUNCTION__, reg);
+ }
+}
+
+static void retu_io(void *opaque, int rw, int reg, uint16_t *val)
+{
+ CBusRetu *s = (CBusRetu *) opaque;
+
+ if (rw)
+ *val = retu_read(s, reg);
+ else
+ retu_write(s, reg, *val);
+}
+
+void *retu_init(qemu_irq irq, int vilma)
+{
+ CBusRetu *s = (CBusRetu *) g_malloc0(sizeof(*s));
+
+ s->irq = irq;
+ s->irqen = 0xffff;
+ s->irqst = 0x0000;
+ s->status = 0x0020;
+ s->is_vilma = !!vilma;
+ s->rtc.cal = 0x01;
+ s->result[retu_adc_bsi] = 0x3c2;
+ s->result[retu_adc_batt_temp] = 0x0fc;
+ s->result[retu_adc_chg_volt] = 0x165;
+ s->result[retu_adc_head_det] = 123;
+ s->result[retu_adc_hook_det] = 1023;
+ s->result[retu_adc_rf_gp] = 0x11;
+ s->result[retu_adc_tx_det] = 0x11;
+ s->result[retu_adc_batt_volt] = 0x250;
+ s->result[retu_adc_sens] = 2;
+ s->result[retu_adc_sens_temp] = 0x11;
+ s->result[retu_adc_bbatt_volt] = 0x3d0;
+ s->result[retu_adc_self_temp] = 0x330;
+
+ s->cbus.opaque = s;
+ s->cbus.io = retu_io;
+ s->cbus.addr = 1;
+
+ return &s->cbus;
+}
+
+void retu_key_event(void *retu, int state)
+{
+ CBusSlave *slave = (CBusSlave *) retu;
+ CBusRetu *s = (CBusRetu *) slave->opaque;
+
+ s->irqst |= 1 << retu_int_pwr;
+ retu_interrupt_update(s);
+
+ if (state)
+ s->status &= ~(1 << 5);
+ else
+ s->status |= 1 << 5;
+}
+
+#if 0
+static void retu_head_event(void *retu, int state)
+{
+ CBusSlave *slave = (CBusSlave *) retu;
+ CBusRetu *s = (CBusRetu *) slave->opaque;
+
+ if ((s->cc[0] & 0x500) == 0x500) { /* TODO: Which bits? */
+ /* TODO: reissue the interrupt every 100ms or so. */
+ s->irqst |= 1 << retu_int_head;
+ retu_interrupt_update(s);
+ }
+
+ if (state)
+ s->result[retu_adc_head_det] = 50;
+ else
+ s->result[retu_adc_head_det] = 123;
+}
+
+static void retu_hook_event(void *retu, int state)
+{
+ CBusSlave *slave = (CBusSlave *) retu;
+ CBusRetu *s = (CBusRetu *) slave->opaque;
+
+ if ((s->cc[0] & 0x500) == 0x500) {
+ /* TODO: reissue the interrupt every 100ms or so. */
+ s->irqst |= 1 << retu_int_hook;
+ retu_interrupt_update(s);
+ }
+
+ if (state)
+ s->result[retu_adc_hook_det] = 50;
+ else
+ s->result[retu_adc_hook_det] = 123;
+}
+#endif
+
+/* Tahvo/Betty */
+typedef struct {
+ uint16_t irqst;
+ uint16_t irqen;
+ uint8_t charger;
+ uint8_t backlight;
+ uint16_t usbr;
+ uint16_t power;
+
+ int is_betty;
+ qemu_irq irq;
+ CBusSlave cbus;
+} CBusTahvo;
+
+static void tahvo_interrupt_update(CBusTahvo *s)
+{
+ qemu_set_irq(s->irq, s->irqst & ~s->irqen);
+}
+
+#define TAHVO_REG_ASICR 0x00 /* (RO) ASIC ID & revision */
+#define TAHVO_REG_IDR 0x01 /* (T) Interrupt ID */
+#define TAHVO_REG_IDSR 0x02 /* (RO) Interrupt status */
+#define TAHVO_REG_IMR 0x03 /* (RW) Interrupt mask */
+#define TAHVO_REG_CHAPWMR 0x04 /* (RW) Charger PWM */
+#define TAHVO_REG_LEDPWMR 0x05 /* (RW) LED PWM */
+#define TAHVO_REG_USBR 0x06 /* (RW) USB control */
+#define TAHVO_REG_RCR 0x07 /* (RW) Some kind of power management */
+#define TAHVO_REG_CCR1 0x08 /* (RW) Common control register 1 */
+#define TAHVO_REG_CCR2 0x09 /* (RW) Common control register 2 */
+#define TAHVO_REG_TESTR1 0x0a /* (RW) Test register 1 */
+#define TAHVO_REG_TESTR2 0x0b /* (RW) Test register 2 */
+#define TAHVO_REG_NOPR 0x0c /* (RW) Number of periods */
+#define TAHVO_REG_FRR 0x0d /* (RO) FR */
+
+static inline uint16_t tahvo_read(CBusTahvo *s, int reg)
+{
+#ifdef DEBUG
+ printf("TAHVO read at %02x\n", reg);
+#endif
+
+ switch (reg) {
+ case TAHVO_REG_ASICR:
+ return 0x0021 | (s->is_betty ? 0x0b00 : 0x0300); /* 22 in N810 */
+
+ case TAHVO_REG_IDR:
+ case TAHVO_REG_IDSR: /* XXX: what does this do? */
+ return s->irqst;
+
+ case TAHVO_REG_IMR:
+ return s->irqen;
+
+ case TAHVO_REG_CHAPWMR:
+ return s->charger;
+
+ case TAHVO_REG_LEDPWMR:
+ return s->backlight;
+
+ case TAHVO_REG_USBR:
+ return s->usbr;
+
+ case TAHVO_REG_RCR:
+ return s->power;
+
+ case TAHVO_REG_CCR1:
+ case TAHVO_REG_CCR2:
+ case TAHVO_REG_TESTR1:
+ case TAHVO_REG_TESTR2:
+ case TAHVO_REG_NOPR:
+ case TAHVO_REG_FRR:
+ return 0x0000;
+
+ default:
+ hw_error("%s: bad register %02x\n", __FUNCTION__, reg);
+ }
+}
+
+static inline void tahvo_write(CBusTahvo *s, int reg, uint16_t val)
+{
+#ifdef DEBUG
+ printf("TAHVO write of %04x at %02x\n", val, reg);
+#endif
+
+ switch (reg) {
+ case TAHVO_REG_IDR:
+ s->irqst ^= val;
+ tahvo_interrupt_update(s);
+ break;
+
+ case TAHVO_REG_IMR:
+ s->irqen = val;
+ tahvo_interrupt_update(s);
+ break;
+
+ case TAHVO_REG_CHAPWMR:
+ s->charger = val;
+ break;
+
+ case TAHVO_REG_LEDPWMR:
+ if (s->backlight != (val & 0x7f)) {
+ s->backlight = val & 0x7f;
+ printf("%s: LCD backlight now at %i / 127\n",
+ __FUNCTION__, s->backlight);
+ }
+ break;
+
+ case TAHVO_REG_USBR:
+ s->usbr = val;
+ break;
+
+ case TAHVO_REG_RCR:
+ s->power = val;
+ break;
+
+ case TAHVO_REG_CCR1:
+ case TAHVO_REG_CCR2:
+ case TAHVO_REG_TESTR1:
+ case TAHVO_REG_TESTR2:
+ case TAHVO_REG_NOPR:
+ case TAHVO_REG_FRR:
+ break;
+
+ default:
+ hw_error("%s: bad register %02x\n", __FUNCTION__, reg);
+ }
+}
+
+static void tahvo_io(void *opaque, int rw, int reg, uint16_t *val)
+{
+ CBusTahvo *s = (CBusTahvo *) opaque;
+
+ if (rw)
+ *val = tahvo_read(s, reg);
+ else
+ tahvo_write(s, reg, *val);
+}
+
+void *tahvo_init(qemu_irq irq, int betty)
+{
+ CBusTahvo *s = (CBusTahvo *) g_malloc0(sizeof(*s));
+
+ s->irq = irq;
+ s->irqen = 0xffff;
+ s->irqst = 0x0000;
+ s->is_betty = !!betty;
+
+ s->cbus.opaque = s;
+ s->cbus.io = tahvo_io;
+ s->cbus.addr = 2;
+
+ return &s->cbus;
+}
diff --git a/hw/misc/debugexit.c b/hw/misc/debugexit.c
new file mode 100644
index 00000000..69a1b004
--- /dev/null
+++ b/hw/misc/debugexit.c
@@ -0,0 +1,76 @@
+/*
+ * debug exit port emulation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/isa/isa.h"
+
+#define TYPE_ISA_DEBUG_EXIT_DEVICE "isa-debug-exit"
+#define ISA_DEBUG_EXIT_DEVICE(obj) \
+ OBJECT_CHECK(ISADebugExitState, (obj), TYPE_ISA_DEBUG_EXIT_DEVICE)
+
+typedef struct ISADebugExitState {
+ ISADevice parent_obj;
+
+ uint32_t iobase;
+ uint32_t iosize;
+ MemoryRegion io;
+} ISADebugExitState;
+
+static void debug_exit_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ exit((val << 1) | 1);
+}
+
+static const MemoryRegionOps debug_exit_ops = {
+ .write = debug_exit_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void debug_exit_realizefn(DeviceState *d, Error **errp)
+{
+ ISADevice *dev = ISA_DEVICE(d);
+ ISADebugExitState *isa = ISA_DEBUG_EXIT_DEVICE(d);
+
+ memory_region_init_io(&isa->io, OBJECT(dev), &debug_exit_ops, isa,
+ TYPE_ISA_DEBUG_EXIT_DEVICE, isa->iosize);
+ memory_region_add_subregion(isa_address_space_io(dev),
+ isa->iobase, &isa->io);
+}
+
+static Property debug_exit_properties[] = {
+ DEFINE_PROP_UINT32("iobase", ISADebugExitState, iobase, 0x501),
+ DEFINE_PROP_UINT32("iosize", ISADebugExitState, iosize, 0x02),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void debug_exit_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = debug_exit_realizefn;
+ dc->props = debug_exit_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo debug_exit_info = {
+ .name = TYPE_ISA_DEBUG_EXIT_DEVICE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISADebugExitState),
+ .class_init = debug_exit_class_initfn,
+};
+
+static void debug_exit_register_types(void)
+{
+ type_register_static(&debug_exit_info);
+}
+
+type_init(debug_exit_register_types)
diff --git a/hw/misc/eccmemctl.c b/hw/misc/eccmemctl.c
new file mode 100644
index 00000000..8bad6f68
--- /dev/null
+++ b/hw/misc/eccmemctl.c
@@ -0,0 +1,344 @@
+/*
+ * QEMU Sparc Sun4m ECC memory controller emulation
+ *
+ * Copyright (c) 2007 Robert Reif
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "trace.h"
+
+/* There are 3 versions of this chip used in SMP sun4m systems:
+ * MCC (version 0, implementation 0) SS-600MP
+ * EMC (version 0, implementation 1) SS-10
+ * SMC (version 0, implementation 2) SS-10SX and SS-20
+ *
+ * Chipset docs:
+ * "Sun-4M System Architecture (revision 2.0) by Chuck Narad", 950-1373-01,
+ * http://mediacast.sun.com/users/Barton808/media/Sun4M_SystemArchitecture_edited2.pdf
+ */
+
+#define ECC_MCC 0x00000000
+#define ECC_EMC 0x10000000
+#define ECC_SMC 0x20000000
+
+/* Register indexes */
+#define ECC_MER 0 /* Memory Enable Register */
+#define ECC_MDR 1 /* Memory Delay Register */
+#define ECC_MFSR 2 /* Memory Fault Status Register */
+#define ECC_VCR 3 /* Video Configuration Register */
+#define ECC_MFAR0 4 /* Memory Fault Address Register 0 */
+#define ECC_MFAR1 5 /* Memory Fault Address Register 1 */
+#define ECC_DR 6 /* Diagnostic Register */
+#define ECC_ECR0 7 /* Event Count Register 0 */
+#define ECC_ECR1 8 /* Event Count Register 1 */
+
+/* ECC fault control register */
+#define ECC_MER_EE 0x00000001 /* Enable ECC checking */
+#define ECC_MER_EI 0x00000002 /* Enable Interrupts on
+ correctable errors */
+#define ECC_MER_MRR0 0x00000004 /* SIMM 0 */
+#define ECC_MER_MRR1 0x00000008 /* SIMM 1 */
+#define ECC_MER_MRR2 0x00000010 /* SIMM 2 */
+#define ECC_MER_MRR3 0x00000020 /* SIMM 3 */
+#define ECC_MER_MRR4 0x00000040 /* SIMM 4 */
+#define ECC_MER_MRR5 0x00000080 /* SIMM 5 */
+#define ECC_MER_MRR6 0x00000100 /* SIMM 6 */
+#define ECC_MER_MRR7 0x00000200 /* SIMM 7 */
+#define ECC_MER_REU 0x00000100 /* Memory Refresh Enable (600MP) */
+#define ECC_MER_MRR 0x000003fc /* MRR mask */
+#define ECC_MER_A 0x00000400 /* Memory controller addr map select */
+#define ECC_MER_DCI 0x00000800 /* Disables Coherent Invalidate ACK */
+#define ECC_MER_VER 0x0f000000 /* Version */
+#define ECC_MER_IMPL 0xf0000000 /* Implementation */
+#define ECC_MER_MASK_0 0x00000103 /* Version 0 (MCC) mask */
+#define ECC_MER_MASK_1 0x00000bff /* Version 1 (EMC) mask */
+#define ECC_MER_MASK_2 0x00000bff /* Version 2 (SMC) mask */
+
+/* ECC memory delay register */
+#define ECC_MDR_RRI 0x000003ff /* Refresh Request Interval */
+#define ECC_MDR_MI 0x00001c00 /* MIH Delay */
+#define ECC_MDR_CI 0x0000e000 /* Coherent Invalidate Delay */
+#define ECC_MDR_MDL 0x001f0000 /* MBus Master arbitration delay */
+#define ECC_MDR_MDH 0x03e00000 /* MBus Master arbitration delay */
+#define ECC_MDR_GAD 0x7c000000 /* Graphics Arbitration Delay */
+#define ECC_MDR_RSC 0x80000000 /* Refresh load control */
+#define ECC_MDR_MASK 0x7fffffff
+
+/* ECC fault status register */
+#define ECC_MFSR_CE 0x00000001 /* Correctable error */
+#define ECC_MFSR_BS 0x00000002 /* C2 graphics bad slot access */
+#define ECC_MFSR_TO 0x00000004 /* Timeout on write */
+#define ECC_MFSR_UE 0x00000008 /* Uncorrectable error */
+#define ECC_MFSR_DW 0x000000f0 /* Index of double word in block */
+#define ECC_MFSR_SYND 0x0000ff00 /* Syndrome for correctable error */
+#define ECC_MFSR_ME 0x00010000 /* Multiple errors */
+#define ECC_MFSR_C2ERR 0x00020000 /* C2 graphics error */
+
+/* ECC fault address register 0 */
+#define ECC_MFAR0_PADDR 0x0000000f /* PA[32-35] */
+#define ECC_MFAR0_TYPE 0x000000f0 /* Transaction type */
+#define ECC_MFAR0_SIZE 0x00000700 /* Transaction size */
+#define ECC_MFAR0_CACHE 0x00000800 /* Mapped cacheable */
+#define ECC_MFAR0_LOCK 0x00001000 /* Error occurred in atomic cycle */
+#define ECC_MFAR0_BMODE 0x00002000 /* Boot mode */
+#define ECC_MFAR0_VADDR 0x003fc000 /* VA[12-19] (superset bits) */
+#define ECC_MFAR0_S 0x08000000 /* Supervisor mode */
+#define ECC_MFARO_MID 0xf0000000 /* Module ID */
+
+/* ECC diagnostic register */
+#define ECC_DR_CBX 0x00000001
+#define ECC_DR_CB0 0x00000002
+#define ECC_DR_CB1 0x00000004
+#define ECC_DR_CB2 0x00000008
+#define ECC_DR_CB4 0x00000010
+#define ECC_DR_CB8 0x00000020
+#define ECC_DR_CB16 0x00000040
+#define ECC_DR_CB32 0x00000080
+#define ECC_DR_DMODE 0x00000c00
+
+#define ECC_NREGS 9
+#define ECC_SIZE (ECC_NREGS * sizeof(uint32_t))
+
+#define ECC_DIAG_SIZE 4
+#define ECC_DIAG_MASK (ECC_DIAG_SIZE - 1)
+
+#define TYPE_ECC_MEMCTL "eccmemctl"
+#define ECC_MEMCTL(obj) OBJECT_CHECK(ECCState, (obj), TYPE_ECC_MEMCTL)
+
+typedef struct ECCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem, iomem_diag;
+ qemu_irq irq;
+ uint32_t regs[ECC_NREGS];
+ uint8_t diag[ECC_DIAG_SIZE];
+ uint32_t version;
+} ECCState;
+
+static void ecc_mem_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ ECCState *s = opaque;
+
+ switch (addr >> 2) {
+ case ECC_MER:
+ if (s->version == ECC_MCC)
+ s->regs[ECC_MER] = (val & ECC_MER_MASK_0);
+ else if (s->version == ECC_EMC)
+ s->regs[ECC_MER] = s->version | (val & ECC_MER_MASK_1);
+ else if (s->version == ECC_SMC)
+ s->regs[ECC_MER] = s->version | (val & ECC_MER_MASK_2);
+ trace_ecc_mem_writel_mer(val);
+ break;
+ case ECC_MDR:
+ s->regs[ECC_MDR] = val & ECC_MDR_MASK;
+ trace_ecc_mem_writel_mdr(val);
+ break;
+ case ECC_MFSR:
+ s->regs[ECC_MFSR] = val;
+ qemu_irq_lower(s->irq);
+ trace_ecc_mem_writel_mfsr(val);
+ break;
+ case ECC_VCR:
+ s->regs[ECC_VCR] = val;
+ trace_ecc_mem_writel_vcr(val);
+ break;
+ case ECC_DR:
+ s->regs[ECC_DR] = val;
+ trace_ecc_mem_writel_dr(val);
+ break;
+ case ECC_ECR0:
+ s->regs[ECC_ECR0] = val;
+ trace_ecc_mem_writel_ecr0(val);
+ break;
+ case ECC_ECR1:
+ s->regs[ECC_ECR0] = val;
+ trace_ecc_mem_writel_ecr1(val);
+ break;
+ }
+}
+
+static uint64_t ecc_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ECCState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (addr >> 2) {
+ case ECC_MER:
+ ret = s->regs[ECC_MER];
+ trace_ecc_mem_readl_mer(ret);
+ break;
+ case ECC_MDR:
+ ret = s->regs[ECC_MDR];
+ trace_ecc_mem_readl_mdr(ret);
+ break;
+ case ECC_MFSR:
+ ret = s->regs[ECC_MFSR];
+ trace_ecc_mem_readl_mfsr(ret);
+ break;
+ case ECC_VCR:
+ ret = s->regs[ECC_VCR];
+ trace_ecc_mem_readl_vcr(ret);
+ break;
+ case ECC_MFAR0:
+ ret = s->regs[ECC_MFAR0];
+ trace_ecc_mem_readl_mfar0(ret);
+ break;
+ case ECC_MFAR1:
+ ret = s->regs[ECC_MFAR1];
+ trace_ecc_mem_readl_mfar1(ret);
+ break;
+ case ECC_DR:
+ ret = s->regs[ECC_DR];
+ trace_ecc_mem_readl_dr(ret);
+ break;
+ case ECC_ECR0:
+ ret = s->regs[ECC_ECR0];
+ trace_ecc_mem_readl_ecr0(ret);
+ break;
+ case ECC_ECR1:
+ ret = s->regs[ECC_ECR0];
+ trace_ecc_mem_readl_ecr1(ret);
+ break;
+ }
+ return ret;
+}
+
+static const MemoryRegionOps ecc_mem_ops = {
+ .read = ecc_mem_read,
+ .write = ecc_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void ecc_diag_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ ECCState *s = opaque;
+
+ trace_ecc_diag_mem_writeb(addr, val);
+ s->diag[addr & ECC_DIAG_MASK] = val;
+}
+
+static uint64_t ecc_diag_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ECCState *s = opaque;
+ uint32_t ret = s->diag[(int)addr];
+
+ trace_ecc_diag_mem_readb(addr, ret);
+ return ret;
+}
+
+static const MemoryRegionOps ecc_diag_mem_ops = {
+ .read = ecc_diag_mem_read,
+ .write = ecc_diag_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const VMStateDescription vmstate_ecc = {
+ .name ="ECC",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, ECCState, ECC_NREGS),
+ VMSTATE_BUFFER(diag, ECCState),
+ VMSTATE_UINT32(version, ECCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ecc_reset(DeviceState *d)
+{
+ ECCState *s = ECC_MEMCTL(d);
+
+ if (s->version == ECC_MCC) {
+ s->regs[ECC_MER] &= ECC_MER_REU;
+ } else {
+ s->regs[ECC_MER] &= (ECC_MER_VER | ECC_MER_IMPL | ECC_MER_MRR |
+ ECC_MER_DCI);
+ }
+ s->regs[ECC_MDR] = 0x20;
+ s->regs[ECC_MFSR] = 0;
+ s->regs[ECC_VCR] = 0;
+ s->regs[ECC_MFAR0] = 0x07c00000;
+ s->regs[ECC_MFAR1] = 0;
+ s->regs[ECC_DR] = 0;
+ s->regs[ECC_ECR0] = 0;
+ s->regs[ECC_ECR1] = 0;
+}
+
+static int ecc_init1(SysBusDevice *dev)
+{
+ ECCState *s = ECC_MEMCTL(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+ s->regs[0] = s->version;
+ memory_region_init_io(&s->iomem, OBJECT(dev), &ecc_mem_ops, s, "ecc", ECC_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ if (s->version == ECC_MCC) { // SS-600MP only
+ memory_region_init_io(&s->iomem_diag, OBJECT(dev), &ecc_diag_mem_ops, s,
+ "ecc.diag", ECC_DIAG_SIZE);
+ sysbus_init_mmio(dev, &s->iomem_diag);
+ }
+
+ return 0;
+}
+
+static Property ecc_properties[] = {
+ DEFINE_PROP_UINT32("version", ECCState, version, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ecc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = ecc_init1;
+ dc->reset = ecc_reset;
+ dc->vmsd = &vmstate_ecc;
+ dc->props = ecc_properties;
+}
+
+static const TypeInfo ecc_info = {
+ .name = TYPE_ECC_MEMCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ECCState),
+ .class_init = ecc_class_init,
+};
+
+
+static void ecc_register_types(void)
+{
+ type_register_static(&ecc_info);
+}
+
+type_init(ecc_register_types)
diff --git a/hw/misc/edu.c b/hw/misc/edu.c
new file mode 100644
index 00000000..fe50b42a
--- /dev/null
+++ b/hw/misc/edu.c
@@ -0,0 +1,408 @@
+/*
+ * QEMU educational PCI device
+ *
+ * Copyright (c) 2012-2015 Jiri Slaby
+ *
+ * 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.
+ */
+
+#include "hw/pci/pci.h"
+#include "qemu/timer.h"
+#include "qemu/main-loop.h" /* iothread mutex */
+#include "qapi/visitor.h"
+
+#define EDU(obj) OBJECT_CHECK(EduState, obj, "edu")
+
+#define FACT_IRQ 0x00000001
+#define DMA_IRQ 0x00000100
+
+#define DMA_START 0x40000
+#define DMA_SIZE 4096
+
+typedef struct {
+ PCIDevice pdev;
+ MemoryRegion mmio;
+
+ QemuThread thread;
+ QemuMutex thr_mutex;
+ QemuCond thr_cond;
+ bool stopping;
+
+ uint32_t addr4;
+ uint32_t fact;
+#define EDU_STATUS_COMPUTING 0x01
+#define EDU_STATUS_IRQFACT 0x80
+ uint32_t status;
+
+ uint32_t irq_status;
+
+#define EDU_DMA_RUN 0x1
+#define EDU_DMA_DIR(cmd) (((cmd) & 0x2) >> 1)
+# define EDU_DMA_FROM_PCI 0
+# define EDU_DMA_TO_PCI 1
+#define EDU_DMA_IRQ 0x4
+ struct dma_state {
+ dma_addr_t src;
+ dma_addr_t dst;
+ dma_addr_t cnt;
+ dma_addr_t cmd;
+ } dma;
+ QEMUTimer dma_timer;
+ char dma_buf[DMA_SIZE];
+ uint64_t dma_mask;
+} EduState;
+
+static void edu_raise_irq(EduState *edu, uint32_t val)
+{
+ edu->irq_status |= val;
+ if (edu->irq_status) {
+ pci_set_irq(&edu->pdev, 1);
+ }
+}
+
+static void edu_lower_irq(EduState *edu, uint32_t val)
+{
+ edu->irq_status &= ~val;
+
+ if (!edu->irq_status) {
+ pci_set_irq(&edu->pdev, 0);
+ }
+}
+
+static bool within(uint32_t addr, uint32_t start, uint32_t end)
+{
+ return start <= addr && addr < end;
+}
+
+static void edu_check_range(uint32_t addr, uint32_t size1, uint32_t start,
+ uint32_t size2)
+{
+ uint32_t end1 = addr + size1;
+ uint32_t end2 = start + size2;
+
+ if (within(addr, start, end2) &&
+ end1 > addr && within(end1, start, end2)) {
+ return;
+ }
+
+ hw_error("EDU: DMA range 0x%.8x-0x%.8x out of bounds (0x%.8x-0x%.8x)!",
+ addr, end1 - 1, start, end2 - 1);
+}
+
+static dma_addr_t edu_clamp_addr(const EduState *edu, dma_addr_t addr)
+{
+ dma_addr_t res = addr & edu->dma_mask;
+
+ if (addr != res) {
+ printf("EDU: clamping DMA %#.16"PRIx64" to %#.16"PRIx64"!\n", addr, res);
+ }
+
+ return res;
+}
+
+static void edu_dma_timer(void *opaque)
+{
+ EduState *edu = opaque;
+ bool raise_irq = false;
+
+ if (!(edu->dma.cmd & EDU_DMA_RUN)) {
+ return;
+ }
+
+ if (EDU_DMA_DIR(edu->dma.cmd) == EDU_DMA_FROM_PCI) {
+ uint32_t dst = edu->dma.dst;
+ edu_check_range(dst, edu->dma.cnt, DMA_START, DMA_SIZE);
+ dst -= DMA_START;
+ pci_dma_read(&edu->pdev, edu_clamp_addr(edu, edu->dma.src),
+ edu->dma_buf + dst, edu->dma.cnt);
+ } else {
+ uint32_t src = edu->dma.src;
+ edu_check_range(src, edu->dma.cnt, DMA_START, DMA_SIZE);
+ src -= DMA_START;
+ pci_dma_write(&edu->pdev, edu_clamp_addr(edu, edu->dma.dst),
+ edu->dma_buf + src, edu->dma.cnt);
+ }
+
+ edu->dma.cmd &= ~EDU_DMA_RUN;
+ if (edu->dma.cmd & EDU_DMA_IRQ) {
+ raise_irq = true;
+ }
+
+ if (raise_irq) {
+ edu_raise_irq(edu, DMA_IRQ);
+ }
+}
+
+static void dma_rw(EduState *edu, bool write, dma_addr_t *val, dma_addr_t *dma,
+ bool timer)
+{
+ if (write && (edu->dma.cmd & EDU_DMA_RUN)) {
+ return;
+ }
+
+ if (write) {
+ *dma = *val;
+ } else {
+ *val = *dma;
+ }
+
+ if (timer) {
+ timer_mod(&edu->dma_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 100);
+ }
+}
+
+static uint64_t edu_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ EduState *edu = opaque;
+ uint64_t val = ~0ULL;
+
+ if (size != 4) {
+ return val;
+ }
+
+ switch (addr) {
+ case 0x00:
+ val = 0x010000edu;
+ break;
+ case 0x04:
+ val = edu->addr4;
+ break;
+ case 0x08:
+ qemu_mutex_lock(&edu->thr_mutex);
+ val = edu->fact;
+ qemu_mutex_unlock(&edu->thr_mutex);
+ break;
+ case 0x20:
+ val = atomic_read(&edu->status);
+ break;
+ case 0x24:
+ val = edu->irq_status;
+ break;
+ case 0x80:
+ dma_rw(edu, false, &val, &edu->dma.src, false);
+ break;
+ case 0x88:
+ dma_rw(edu, false, &val, &edu->dma.dst, false);
+ break;
+ case 0x90:
+ dma_rw(edu, false, &val, &edu->dma.cnt, false);
+ break;
+ case 0x98:
+ dma_rw(edu, false, &val, &edu->dma.cmd, false);
+ break;
+ }
+
+ return val;
+}
+
+static void edu_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ EduState *edu = opaque;
+
+ if (addr < 0x80 && size != 4) {
+ return;
+ }
+
+ if (addr >= 0x80 && size != 4 && size != 8) {
+ return;
+ }
+
+ switch (addr) {
+ case 0x04:
+ edu->addr4 = ~val;
+ break;
+ case 0x08:
+ if (atomic_read(&edu->status) & EDU_STATUS_COMPUTING) {
+ break;
+ }
+ /* EDU_STATUS_COMPUTING cannot go 0->1 concurrently, because it is only
+ * set in this function and it is under the iothread mutex.
+ */
+ qemu_mutex_lock(&edu->thr_mutex);
+ edu->fact = val;
+ atomic_or(&edu->status, EDU_STATUS_COMPUTING);
+ qemu_cond_signal(&edu->thr_cond);
+ qemu_mutex_unlock(&edu->thr_mutex);
+ break;
+ case 0x20:
+ if (val & EDU_STATUS_IRQFACT) {
+ atomic_or(&edu->status, EDU_STATUS_IRQFACT);
+ } else {
+ atomic_and(&edu->status, ~EDU_STATUS_IRQFACT);
+ }
+ break;
+ case 0x60:
+ edu_raise_irq(edu, val);
+ break;
+ case 0x64:
+ edu_lower_irq(edu, val);
+ break;
+ case 0x80:
+ dma_rw(edu, true, &val, &edu->dma.src, false);
+ break;
+ case 0x88:
+ dma_rw(edu, true, &val, &edu->dma.dst, false);
+ break;
+ case 0x90:
+ dma_rw(edu, true, &val, &edu->dma.cnt, false);
+ break;
+ case 0x98:
+ if (!(val & EDU_DMA_RUN)) {
+ break;
+ }
+ dma_rw(edu, true, &val, &edu->dma.cmd, true);
+ break;
+ }
+}
+
+static const MemoryRegionOps edu_mmio_ops = {
+ .read = edu_mmio_read,
+ .write = edu_mmio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * We purposely use a thread, so that users are forced to wait for the status
+ * register.
+ */
+static void *edu_fact_thread(void *opaque)
+{
+ EduState *edu = opaque;
+
+ while (1) {
+ uint32_t val, ret = 1;
+
+ qemu_mutex_lock(&edu->thr_mutex);
+ while ((atomic_read(&edu->status) & EDU_STATUS_COMPUTING) == 0 &&
+ !edu->stopping) {
+ qemu_cond_wait(&edu->thr_cond, &edu->thr_mutex);
+ }
+
+ if (edu->stopping) {
+ qemu_mutex_unlock(&edu->thr_mutex);
+ break;
+ }
+
+ val = edu->fact;
+ qemu_mutex_unlock(&edu->thr_mutex);
+
+ while (val > 0) {
+ ret *= val--;
+ }
+
+ /*
+ * We should sleep for a random period here, so that students are
+ * forced to check the status properly.
+ */
+
+ qemu_mutex_lock(&edu->thr_mutex);
+ edu->fact = ret;
+ qemu_mutex_unlock(&edu->thr_mutex);
+ atomic_and(&edu->status, ~EDU_STATUS_COMPUTING);
+
+ if (atomic_read(&edu->status) & EDU_STATUS_IRQFACT) {
+ qemu_mutex_lock_iothread();
+ edu_raise_irq(edu, FACT_IRQ);
+ qemu_mutex_unlock_iothread();
+ }
+ }
+
+ return NULL;
+}
+
+static int pci_edu_init(PCIDevice *pdev)
+{
+ EduState *edu = DO_UPCAST(EduState, pdev, pdev);
+ uint8_t *pci_conf = pdev->config;
+
+ timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);
+
+ qemu_mutex_init(&edu->thr_mutex);
+ qemu_cond_init(&edu->thr_cond);
+ qemu_thread_create(&edu->thread, "edu", edu_fact_thread,
+ edu, QEMU_THREAD_JOINABLE);
+
+ pci_config_set_interrupt_pin(pci_conf, 1);
+
+ memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
+ "edu-mmio", 1 << 20);
+ pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);
+
+ return 0;
+}
+
+static void pci_edu_uninit(PCIDevice *pdev)
+{
+ EduState *edu = DO_UPCAST(EduState, pdev, pdev);
+
+ qemu_mutex_lock(&edu->thr_mutex);
+ edu->stopping = true;
+ qemu_mutex_unlock(&edu->thr_mutex);
+ qemu_cond_signal(&edu->thr_cond);
+ qemu_thread_join(&edu->thread);
+
+ qemu_cond_destroy(&edu->thr_cond);
+ qemu_mutex_destroy(&edu->thr_mutex);
+
+ timer_del(&edu->dma_timer);
+}
+
+static void edu_obj_uint64(Object *obj, struct Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ uint64_t *val = opaque;
+
+ visit_type_uint64(v, val, name, errp);
+}
+
+static void edu_instance_init(Object *obj)
+{
+ EduState *edu = EDU(obj);
+
+ edu->dma_mask = (1UL << 28) - 1;
+ object_property_add(obj, "dma_mask", "uint64", edu_obj_uint64,
+ edu_obj_uint64, NULL, &edu->dma_mask, NULL);
+}
+
+static void edu_class_init(ObjectClass *class, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
+
+ k->init = pci_edu_init;
+ k->exit = pci_edu_uninit;
+ k->vendor_id = PCI_VENDOR_ID_QEMU;
+ k->device_id = 0x11e8;
+ k->revision = 0x10;
+ k->class_id = PCI_CLASS_OTHERS;
+}
+
+static void pci_edu_register_types(void)
+{
+ static const TypeInfo edu_info = {
+ .name = "edu",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(EduState),
+ .instance_init = edu_instance_init,
+ .class_init = edu_class_init,
+ };
+
+ type_register_static(&edu_info);
+}
+type_init(pci_edu_register_types)
diff --git a/hw/misc/exynos4210_pmu.c b/hw/misc/exynos4210_pmu.c
new file mode 100644
index 00000000..2b118c72
--- /dev/null
+++ b/hw/misc/exynos4210_pmu.c
@@ -0,0 +1,502 @@
+/*
+ * Exynos4210 Power Management Unit (PMU) Emulation
+ *
+ * Copyright (C) 2011 Samsung Electronics Co Ltd.
+ * Maksim Kozlov <m.kozlov@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This model implements PMU registers just as a bulk of memory. Currently,
+ * the only reason this device exists is that secondary CPU boot loader
+ * uses PMU INFORM5 register as a holding pen.
+ */
+
+#include "hw/sysbus.h"
+
+#ifndef DEBUG_PMU
+#define DEBUG_PMU 0
+#endif
+
+#ifndef DEBUG_PMU_EXTEND
+#define DEBUG_PMU_EXTEND 0
+#endif
+
+#if DEBUG_PMU
+#define PRINT_DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+#if DEBUG_PMU_EXTEND
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+#else
+#define PRINT_DEBUG_EXTEND(fmt, args...) do {} while (0)
+#endif /* EXTEND */
+
+#else
+#define PRINT_DEBUG(fmt, args...) do {} while (0)
+#define PRINT_DEBUG_EXTEND(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Offsets for PMU registers
+ */
+#define OM_STAT 0x0000 /* OM status register */
+#define RTC_CLKO_SEL 0x000C /* Controls RTCCLKOUT */
+#define GNSS_RTC_OUT_CTRL 0x0010 /* Controls GNSS_RTC_OUT */
+/* Decides whether system-level low-power mode is used. */
+#define SYSTEM_POWER_DOWN_CTRL 0x0200
+/* Sets control options for CENTRAL_SEQ */
+#define SYSTEM_POWER_DOWN_OPTION 0x0208
+#define SWRESET 0x0400 /* Generate software reset */
+#define RST_STAT 0x0404 /* Reset status register */
+#define WAKEUP_STAT 0x0600 /* Wakeup status register */
+#define EINT_WAKEUP_MASK 0x0604 /* Configure External INTerrupt mask */
+#define WAKEUP_MASK 0x0608 /* Configure wakeup source mask */
+#define HDMI_PHY_CONTROL 0x0700 /* HDMI PHY control register */
+#define USBDEVICE_PHY_CONTROL 0x0704 /* USB Device PHY control register */
+#define USBHOST_PHY_CONTROL 0x0708 /* USB HOST PHY control register */
+#define DAC_PHY_CONTROL 0x070C /* DAC control register */
+#define MIPI_PHY0_CONTROL 0x0710 /* MIPI PHY control register */
+#define MIPI_PHY1_CONTROL 0x0714 /* MIPI PHY control register */
+#define ADC_PHY_CONTROL 0x0718 /* TS-ADC control register */
+#define PCIe_PHY_CONTROL 0x071C /* TS-PCIe control register */
+#define SATA_PHY_CONTROL 0x0720 /* TS-SATA control register */
+#define INFORM0 0x0800 /* Information register 0 */
+#define INFORM1 0x0804 /* Information register 1 */
+#define INFORM2 0x0808 /* Information register 2 */
+#define INFORM3 0x080C /* Information register 3 */
+#define INFORM4 0x0810 /* Information register 4 */
+#define INFORM5 0x0814 /* Information register 5 */
+#define INFORM6 0x0818 /* Information register 6 */
+#define INFORM7 0x081C /* Information register 7 */
+#define PMU_DEBUG 0x0A00 /* PMU debug register */
+/* Registers to set system-level low-power option */
+#define ARM_CORE0_SYS_PWR_REG 0x1000
+#define ARM_CORE1_SYS_PWR_REG 0x1010
+#define ARM_COMMON_SYS_PWR_REG 0x1080
+#define ARM_CPU_L2_0_SYS_PWR_REG 0x10C0
+#define ARM_CPU_L2_1_SYS_PWR_REG 0x10C4
+#define CMU_ACLKSTOP_SYS_PWR_REG 0x1100
+#define CMU_SCLKSTOP_SYS_PWR_REG 0x1104
+#define CMU_RESET_SYS_PWR_REG 0x110C
+#define APLL_SYSCLK_SYS_PWR_REG 0x1120
+#define MPLL_SYSCLK_SYS_PWR_REG 0x1124
+#define VPLL_SYSCLK_SYS_PWR_REG 0x1128
+#define EPLL_SYSCLK_SYS_PWR_REG 0x112C
+#define CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG 0x1138
+#define CMU_RESET_GPS_ALIVE_SYS_PWR_REG 0x113C
+#define CMU_CLKSTOP_CAM_SYS_PWR_REG 0x1140
+#define CMU_CLKSTOP_TV_SYS_PWR_REG 0x1144
+#define CMU_CLKSTOP_MFC_SYS_PWR_REG 0x1148
+#define CMU_CLKSTOP_G3D_SYS_PWR_REG 0x114C
+#define CMU_CLKSTOP_LCD0_SYS_PWR_REG 0x1150
+#define CMU_CLKSTOP_LCD1_SYS_PWR_REG 0x1154
+#define CMU_CLKSTOP_MAUDIO_SYS_PWR_REG 0x1158
+#define CMU_CLKSTOP_GPS_SYS_PWR_REG 0x115C
+#define CMU_RESET_CAM_SYS_PWR_REG 0x1160
+#define CMU_RESET_TV_SYS_PWR_REG 0x1164
+#define CMU_RESET_MFC_SYS_PWR_REG 0x1168
+#define CMU_RESET_G3D_SYS_PWR_REG 0x116C
+#define CMU_RESET_LCD0_SYS_PWR_REG 0x1170
+#define CMU_RESET_LCD1_SYS_PWR_REG 0x1174
+#define CMU_RESET_MAUDIO_SYS_PWR_REG 0x1178
+#define CMU_RESET_GPS_SYS_PWR_REG 0x117C
+#define TOP_BUS_SYS_PWR_REG 0x1180
+#define TOP_RETENTION_SYS_PWR_REG 0x1184
+#define TOP_PWR_SYS_PWR_REG 0x1188
+#define LOGIC_RESET_SYS_PWR_REG 0x11A0
+#define OneNANDXL_MEM_SYS_PWR_REG 0x11C0
+#define MODEMIF_MEM_SYS_PWR_REG 0x11C4
+#define USBDEVICE_MEM_SYS_PWR_REG 0x11CC
+#define SDMMC_MEM_SYS_PWR_REG 0x11D0
+#define CSSYS_MEM_SYS_PWR_REG 0x11D4
+#define SECSS_MEM_SYS_PWR_REG 0x11D8
+#define PCIe_MEM_SYS_PWR_REG 0x11E0
+#define SATA_MEM_SYS_PWR_REG 0x11E4
+#define PAD_RETENTION_DRAM_SYS_PWR_REG 0x1200
+#define PAD_RETENTION_MAUDIO_SYS_PWR_REG 0x1204
+#define PAD_RETENTION_GPIO_SYS_PWR_REG 0x1220
+#define PAD_RETENTION_UART_SYS_PWR_REG 0x1224
+#define PAD_RETENTION_MMCA_SYS_PWR_REG 0x1228
+#define PAD_RETENTION_MMCB_SYS_PWR_REG 0x122C
+#define PAD_RETENTION_EBIA_SYS_PWR_REG 0x1230
+#define PAD_RETENTION_EBIB_SYS_PWR_REG 0x1234
+#define PAD_ISOLATION_SYS_PWR_REG 0x1240
+#define PAD_ALV_SEL_SYS_PWR_REG 0x1260
+#define XUSBXTI_SYS_PWR_REG 0x1280
+#define XXTI_SYS_PWR_REG 0x1284
+#define EXT_REGULATOR_SYS_PWR_REG 0x12C0
+#define GPIO_MODE_SYS_PWR_REG 0x1300
+#define GPIO_MODE_MAUDIO_SYS_PWR_REG 0x1340
+#define CAM_SYS_PWR_REG 0x1380
+#define TV_SYS_PWR_REG 0x1384
+#define MFC_SYS_PWR_REG 0x1388
+#define G3D_SYS_PWR_REG 0x138C
+#define LCD0_SYS_PWR_REG 0x1390
+#define LCD1_SYS_PWR_REG 0x1394
+#define MAUDIO_SYS_PWR_REG 0x1398
+#define GPS_SYS_PWR_REG 0x139C
+#define GPS_ALIVE_SYS_PWR_REG 0x13A0
+#define ARM_CORE0_CONFIGURATION 0x2000 /* Configure power mode of ARM_CORE0 */
+#define ARM_CORE0_STATUS 0x2004 /* Check power mode of ARM_CORE0 */
+#define ARM_CORE0_OPTION 0x2008 /* Sets control options for ARM_CORE0 */
+#define ARM_CORE1_CONFIGURATION 0x2080 /* Configure power mode of ARM_CORE1 */
+#define ARM_CORE1_STATUS 0x2084 /* Check power mode of ARM_CORE1 */
+#define ARM_CORE1_OPTION 0x2088 /* Sets control options for ARM_CORE0 */
+#define ARM_COMMON_OPTION 0x2408 /* Sets control options for ARM_COMMON */
+/* Configure power mode of ARM_CPU_L2_0 */
+#define ARM_CPU_L2_0_CONFIGURATION 0x2600
+#define ARM_CPU_L2_0_STATUS 0x2604 /* Check power mode of ARM_CPU_L2_0 */
+/* Configure power mode of ARM_CPU_L2_1 */
+#define ARM_CPU_L2_1_CONFIGURATION 0x2620
+#define ARM_CPU_L2_1_STATUS 0x2624 /* Check power mode of ARM_CPU_L2_1 */
+/* Sets control options for PAD_RETENTION_MAUDIO */
+#define PAD_RETENTION_MAUDIO_OPTION 0x3028
+/* Sets control options for PAD_RETENTION_GPIO */
+#define PAD_RETENTION_GPIO_OPTION 0x3108
+/* Sets control options for PAD_RETENTION_UART */
+#define PAD_RETENTION_UART_OPTION 0x3128
+/* Sets control options for PAD_RETENTION_MMCA */
+#define PAD_RETENTION_MMCA_OPTION 0x3148
+/* Sets control options for PAD_RETENTION_MMCB */
+#define PAD_RETENTION_MMCB_OPTION 0x3168
+/* Sets control options for PAD_RETENTION_EBIA */
+#define PAD_RETENTION_EBIA_OPTION 0x3188
+/* Sets control options for PAD_RETENTION_EBIB */
+#define PAD_RETENTION_EBIB_OPTION 0x31A8
+#define PS_HOLD_CONTROL 0x330C /* PS_HOLD control register */
+#define XUSBXTI_CONFIGURATION 0x3400 /* Configure the pad of XUSBXTI */
+#define XUSBXTI_STATUS 0x3404 /* Check the pad of XUSBXTI */
+/* Sets time required for XUSBXTI to be stabilized */
+#define XUSBXTI_DURATION 0x341C
+#define XXTI_CONFIGURATION 0x3420 /* Configure the pad of XXTI */
+#define XXTI_STATUS 0x3424 /* Check the pad of XXTI */
+/* Sets time required for XXTI to be stabilized */
+#define XXTI_DURATION 0x343C
+/* Sets time required for EXT_REGULATOR to be stabilized */
+#define EXT_REGULATOR_DURATION 0x361C
+#define CAM_CONFIGURATION 0x3C00 /* Configure power mode of CAM */
+#define CAM_STATUS 0x3C04 /* Check power mode of CAM */
+#define CAM_OPTION 0x3C08 /* Sets control options for CAM */
+#define TV_CONFIGURATION 0x3C20 /* Configure power mode of TV */
+#define TV_STATUS 0x3C24 /* Check power mode of TV */
+#define TV_OPTION 0x3C28 /* Sets control options for TV */
+#define MFC_CONFIGURATION 0x3C40 /* Configure power mode of MFC */
+#define MFC_STATUS 0x3C44 /* Check power mode of MFC */
+#define MFC_OPTION 0x3C48 /* Sets control options for MFC */
+#define G3D_CONFIGURATION 0x3C60 /* Configure power mode of G3D */
+#define G3D_STATUS 0x3C64 /* Check power mode of G3D */
+#define G3D_OPTION 0x3C68 /* Sets control options for G3D */
+#define LCD0_CONFIGURATION 0x3C80 /* Configure power mode of LCD0 */
+#define LCD0_STATUS 0x3C84 /* Check power mode of LCD0 */
+#define LCD0_OPTION 0x3C88 /* Sets control options for LCD0 */
+#define LCD1_CONFIGURATION 0x3CA0 /* Configure power mode of LCD1 */
+#define LCD1_STATUS 0x3CA4 /* Check power mode of LCD1 */
+#define LCD1_OPTION 0x3CA8 /* Sets control options for LCD1 */
+#define GPS_CONFIGURATION 0x3CE0 /* Configure power mode of GPS */
+#define GPS_STATUS 0x3CE4 /* Check power mode of GPS */
+#define GPS_OPTION 0x3CE8 /* Sets control options for GPS */
+#define GPS_ALIVE_CONFIGURATION 0x3D00 /* Configure power mode of GPS */
+#define GPS_ALIVE_STATUS 0x3D04 /* Check power mode of GPS */
+#define GPS_ALIVE_OPTION 0x3D08 /* Sets control options for GPS */
+
+#define EXYNOS4210_PMU_REGS_MEM_SIZE 0x3d0c
+
+typedef struct Exynos4210PmuReg {
+ const char *name; /* for debug only */
+ uint32_t offset;
+ uint32_t reset_value;
+} Exynos4210PmuReg;
+
+static const Exynos4210PmuReg exynos4210_pmu_regs[] = {
+ {"OM_STAT", OM_STAT, 0x00000000},
+ {"RTC_CLKO_SEL", RTC_CLKO_SEL, 0x00000000},
+ {"GNSS_RTC_OUT_CTRL", GNSS_RTC_OUT_CTRL, 0x00000001},
+ {"SYSTEM_POWER_DOWN_CTRL", SYSTEM_POWER_DOWN_CTRL, 0x00010000},
+ {"SYSTEM_POWER_DOWN_OPTION", SYSTEM_POWER_DOWN_OPTION, 0x03030000},
+ {"SWRESET", SWRESET, 0x00000000},
+ {"RST_STAT", RST_STAT, 0x00000000},
+ {"WAKEUP_STAT", WAKEUP_STAT, 0x00000000},
+ {"EINT_WAKEUP_MASK", EINT_WAKEUP_MASK, 0x00000000},
+ {"WAKEUP_MASK", WAKEUP_MASK, 0x00000000},
+ {"HDMI_PHY_CONTROL", HDMI_PHY_CONTROL, 0x00960000},
+ {"USBDEVICE_PHY_CONTROL", USBDEVICE_PHY_CONTROL, 0x00000000},
+ {"USBHOST_PHY_CONTROL", USBHOST_PHY_CONTROL, 0x00000000},
+ {"DAC_PHY_CONTROL", DAC_PHY_CONTROL, 0x00000000},
+ {"MIPI_PHY0_CONTROL", MIPI_PHY0_CONTROL, 0x00000000},
+ {"MIPI_PHY1_CONTROL", MIPI_PHY1_CONTROL, 0x00000000},
+ {"ADC_PHY_CONTROL", ADC_PHY_CONTROL, 0x00000001},
+ {"PCIe_PHY_CONTROL", PCIe_PHY_CONTROL, 0x00000000},
+ {"SATA_PHY_CONTROL", SATA_PHY_CONTROL, 0x00000000},
+ {"INFORM0", INFORM0, 0x00000000},
+ {"INFORM1", INFORM1, 0x00000000},
+ {"INFORM2", INFORM2, 0x00000000},
+ {"INFORM3", INFORM3, 0x00000000},
+ {"INFORM4", INFORM4, 0x00000000},
+ {"INFORM5", INFORM5, 0x00000000},
+ {"INFORM6", INFORM6, 0x00000000},
+ {"INFORM7", INFORM7, 0x00000000},
+ {"PMU_DEBUG", PMU_DEBUG, 0x00000000},
+ {"ARM_CORE0_SYS_PWR_REG", ARM_CORE0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_CORE1_SYS_PWR_REG", ARM_CORE1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_COMMON_SYS_PWR_REG", ARM_COMMON_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_CPU_L2_0_SYS_PWR_REG", ARM_CPU_L2_0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_CPU_L2_1_SYS_PWR_REG", ARM_CPU_L2_1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_ACLKSTOP_SYS_PWR_REG", CMU_ACLKSTOP_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_SCLKSTOP_SYS_PWR_REG", CMU_SCLKSTOP_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_SYS_PWR_REG", CMU_RESET_SYS_PWR_REG, 0xFFFFFFFF},
+ {"APLL_SYSCLK_SYS_PWR_REG", APLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+ {"MPLL_SYSCLK_SYS_PWR_REG", MPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+ {"VPLL_SYSCLK_SYS_PWR_REG", VPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+ {"EPLL_SYSCLK_SYS_PWR_REG", EPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG", CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"CMU_RESET_GPS_ALIVE_SYS_PWR_REG", CMU_RESET_GPS_ALIVE_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"CMU_CLKSTOP_CAM_SYS_PWR_REG", CMU_CLKSTOP_CAM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_TV_SYS_PWR_REG", CMU_CLKSTOP_TV_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_MFC_SYS_PWR_REG", CMU_CLKSTOP_MFC_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_G3D_SYS_PWR_REG", CMU_CLKSTOP_G3D_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_LCD0_SYS_PWR_REG", CMU_CLKSTOP_LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_LCD1_SYS_PWR_REG", CMU_CLKSTOP_LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_MAUDIO_SYS_PWR_REG", CMU_CLKSTOP_MAUDIO_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"CMU_CLKSTOP_GPS_SYS_PWR_REG", CMU_CLKSTOP_GPS_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_CAM_SYS_PWR_REG", CMU_RESET_CAM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_TV_SYS_PWR_REG", CMU_RESET_TV_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_MFC_SYS_PWR_REG", CMU_RESET_MFC_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_G3D_SYS_PWR_REG", CMU_RESET_G3D_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_LCD0_SYS_PWR_REG", CMU_RESET_LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_LCD1_SYS_PWR_REG", CMU_RESET_LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_MAUDIO_SYS_PWR_REG", CMU_RESET_MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_GPS_SYS_PWR_REG", CMU_RESET_GPS_SYS_PWR_REG, 0xFFFFFFFF},
+ {"TOP_BUS_SYS_PWR_REG", TOP_BUS_SYS_PWR_REG, 0xFFFFFFFF},
+ {"TOP_RETENTION_SYS_PWR_REG", TOP_RETENTION_SYS_PWR_REG, 0xFFFFFFFF},
+ {"TOP_PWR_SYS_PWR_REG", TOP_PWR_SYS_PWR_REG, 0xFFFFFFFF},
+ {"LOGIC_RESET_SYS_PWR_REG", LOGIC_RESET_SYS_PWR_REG, 0xFFFFFFFF},
+ {"OneNANDXL_MEM_SYS_PWR_REG", OneNANDXL_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"MODEMIF_MEM_SYS_PWR_REG", MODEMIF_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"USBDEVICE_MEM_SYS_PWR_REG", USBDEVICE_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"SDMMC_MEM_SYS_PWR_REG", SDMMC_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CSSYS_MEM_SYS_PWR_REG", CSSYS_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"SECSS_MEM_SYS_PWR_REG", SECSS_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"PCIe_MEM_SYS_PWR_REG", PCIe_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"SATA_MEM_SYS_PWR_REG", SATA_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"PAD_RETENTION_DRAM_SYS_PWR_REG", PAD_RETENTION_DRAM_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_MAUDIO_SYS_PWR_REG", PAD_RETENTION_MAUDIO_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_GPIO_SYS_PWR_REG", PAD_RETENTION_GPIO_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_UART_SYS_PWR_REG", PAD_RETENTION_UART_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_MMCA_SYS_PWR_REG", PAD_RETENTION_MMCA_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_MMCB_SYS_PWR_REG", PAD_RETENTION_MMCB_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_EBIA_SYS_PWR_REG", PAD_RETENTION_EBIA_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_EBIB_SYS_PWR_REG", PAD_RETENTION_EBIB_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_ISOLATION_SYS_PWR_REG", PAD_ISOLATION_SYS_PWR_REG, 0xFFFFFFFF},
+ {"PAD_ALV_SEL_SYS_PWR_REG", PAD_ALV_SEL_SYS_PWR_REG, 0xFFFFFFFF},
+ {"XUSBXTI_SYS_PWR_REG", XUSBXTI_SYS_PWR_REG, 0xFFFFFFFF},
+ {"XXTI_SYS_PWR_REG", XXTI_SYS_PWR_REG, 0xFFFFFFFF},
+ {"EXT_REGULATOR_SYS_PWR_REG", EXT_REGULATOR_SYS_PWR_REG, 0xFFFFFFFF},
+ {"GPIO_MODE_SYS_PWR_REG", GPIO_MODE_SYS_PWR_REG, 0xFFFFFFFF},
+ {"GPIO_MODE_MAUDIO_SYS_PWR_REG", GPIO_MODE_MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CAM_SYS_PWR_REG", CAM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"TV_SYS_PWR_REG", TV_SYS_PWR_REG, 0xFFFFFFFF},
+ {"MFC_SYS_PWR_REG", MFC_SYS_PWR_REG, 0xFFFFFFFF},
+ {"G3D_SYS_PWR_REG", G3D_SYS_PWR_REG, 0xFFFFFFFF},
+ {"LCD0_SYS_PWR_REG", LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"LCD1_SYS_PWR_REG", LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"MAUDIO_SYS_PWR_REG", MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+ {"GPS_SYS_PWR_REG", GPS_SYS_PWR_REG, 0xFFFFFFFF},
+ {"GPS_ALIVE_SYS_PWR_REG", GPS_ALIVE_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_CORE0_CONFIGURATION", ARM_CORE0_CONFIGURATION, 0x00000003},
+ {"ARM_CORE0_STATUS", ARM_CORE0_STATUS, 0x00030003},
+ {"ARM_CORE0_OPTION", ARM_CORE0_OPTION, 0x01010001},
+ {"ARM_CORE1_CONFIGURATION", ARM_CORE1_CONFIGURATION, 0x00000003},
+ {"ARM_CORE1_STATUS", ARM_CORE1_STATUS, 0x00030003},
+ {"ARM_CORE1_OPTION", ARM_CORE1_OPTION, 0x01010001},
+ {"ARM_COMMON_OPTION", ARM_COMMON_OPTION, 0x00000001},
+ {"ARM_CPU_L2_0_CONFIGURATION", ARM_CPU_L2_0_CONFIGURATION, 0x00000003},
+ {"ARM_CPU_L2_0_STATUS", ARM_CPU_L2_0_STATUS, 0x00000003},
+ {"ARM_CPU_L2_1_CONFIGURATION", ARM_CPU_L2_1_CONFIGURATION, 0x00000003},
+ {"ARM_CPU_L2_1_STATUS", ARM_CPU_L2_1_STATUS, 0x00000003},
+ {"PAD_RETENTION_MAUDIO_OPTION", PAD_RETENTION_MAUDIO_OPTION, 0x00000000},
+ {"PAD_RETENTION_GPIO_OPTION", PAD_RETENTION_GPIO_OPTION, 0x00000000},
+ {"PAD_RETENTION_UART_OPTION", PAD_RETENTION_UART_OPTION, 0x00000000},
+ {"PAD_RETENTION_MMCA_OPTION", PAD_RETENTION_MMCA_OPTION, 0x00000000},
+ {"PAD_RETENTION_MMCB_OPTION", PAD_RETENTION_MMCB_OPTION, 0x00000000},
+ {"PAD_RETENTION_EBIA_OPTION", PAD_RETENTION_EBIA_OPTION, 0x00000000},
+ {"PAD_RETENTION_EBIB_OPTION", PAD_RETENTION_EBIB_OPTION, 0x00000000},
+ {"PS_HOLD_CONTROL", PS_HOLD_CONTROL, 0x00005200},
+ {"XUSBXTI_CONFIGURATION", XUSBXTI_CONFIGURATION, 0x00000001},
+ {"XUSBXTI_STATUS", XUSBXTI_STATUS, 0x00000001},
+ {"XUSBXTI_DURATION", XUSBXTI_DURATION, 0xFFF00000},
+ {"XXTI_CONFIGURATION", XXTI_CONFIGURATION, 0x00000001},
+ {"XXTI_STATUS", XXTI_STATUS, 0x00000001},
+ {"XXTI_DURATION", XXTI_DURATION, 0xFFF00000},
+ {"EXT_REGULATOR_DURATION", EXT_REGULATOR_DURATION, 0xFFF03FFF},
+ {"CAM_CONFIGURATION", CAM_CONFIGURATION, 0x00000007},
+ {"CAM_STATUS", CAM_STATUS, 0x00060007},
+ {"CAM_OPTION", CAM_OPTION, 0x00000001},
+ {"TV_CONFIGURATION", TV_CONFIGURATION, 0x00000007},
+ {"TV_STATUS", TV_STATUS, 0x00060007},
+ {"TV_OPTION", TV_OPTION, 0x00000001},
+ {"MFC_CONFIGURATION", MFC_CONFIGURATION, 0x00000007},
+ {"MFC_STATUS", MFC_STATUS, 0x00060007},
+ {"MFC_OPTION", MFC_OPTION, 0x00000001},
+ {"G3D_CONFIGURATION", G3D_CONFIGURATION, 0x00000007},
+ {"G3D_STATUS", G3D_STATUS, 0x00060007},
+ {"G3D_OPTION", G3D_OPTION, 0x00000001},
+ {"LCD0_CONFIGURATION", LCD0_CONFIGURATION, 0x00000007},
+ {"LCD0_STATUS", LCD0_STATUS, 0x00060007},
+ {"LCD0_OPTION", LCD0_OPTION, 0x00000001},
+ {"LCD1_CONFIGURATION", LCD1_CONFIGURATION, 0x00000007},
+ {"LCD1_STATUS", LCD1_STATUS, 0x00060007},
+ {"LCD1_OPTION", LCD1_OPTION, 0x00000001},
+ {"GPS_CONFIGURATION", GPS_CONFIGURATION, 0x00000007},
+ {"GPS_STATUS", GPS_STATUS, 0x00060007},
+ {"GPS_OPTION", GPS_OPTION, 0x00000001},
+ {"GPS_ALIVE_CONFIGURATION", GPS_ALIVE_CONFIGURATION, 0x00000007},
+ {"GPS_ALIVE_STATUS", GPS_ALIVE_STATUS, 0x00060007},
+ {"GPS_ALIVE_OPTION", GPS_ALIVE_OPTION, 0x00000001},
+};
+
+#define PMU_NUM_OF_REGISTERS ARRAY_SIZE(exynos4210_pmu_regs)
+
+#define TYPE_EXYNOS4210_PMU "exynos4210.pmu"
+#define EXYNOS4210_PMU(obj) \
+ OBJECT_CHECK(Exynos4210PmuState, (obj), TYPE_EXYNOS4210_PMU)
+
+typedef struct Exynos4210PmuState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t reg[PMU_NUM_OF_REGISTERS];
+} Exynos4210PmuState;
+
+static uint64_t exynos4210_pmu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210PmuState *s = (Exynos4210PmuState *)opaque;
+ unsigned i;
+ const Exynos4210PmuReg *reg_p = exynos4210_pmu_regs;
+
+ for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) {
+ if (reg_p->offset == offset) {
+ PRINT_DEBUG_EXTEND("%s [0x%04x] -> 0x%04x\n", reg_p->name,
+ (uint32_t)offset, s->reg[i]);
+ return s->reg[i];
+ }
+ reg_p++;
+ }
+ PRINT_DEBUG("QEMU PMU ERROR: bad read offset 0x%04x\n", (uint32_t)offset);
+ return 0;
+}
+
+static void exynos4210_pmu_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210PmuState *s = (Exynos4210PmuState *)opaque;
+ unsigned i;
+ const Exynos4210PmuReg *reg_p = exynos4210_pmu_regs;
+
+ for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) {
+ if (reg_p->offset == offset) {
+ PRINT_DEBUG_EXTEND("%s <0x%04x> <- 0x%04x\n", reg_p->name,
+ (uint32_t)offset, (uint32_t)val);
+ s->reg[i] = val;
+ return;
+ }
+ reg_p++;
+ }
+ PRINT_DEBUG("QEMU PMU ERROR: bad write offset 0x%04x\n", (uint32_t)offset);
+}
+
+static const MemoryRegionOps exynos4210_pmu_ops = {
+ .read = exynos4210_pmu_read,
+ .write = exynos4210_pmu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false
+ }
+};
+
+static void exynos4210_pmu_reset(DeviceState *dev)
+{
+ Exynos4210PmuState *s = EXYNOS4210_PMU(dev);
+ unsigned i;
+
+ /* Set default values for registers */
+ for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) {
+ s->reg[i] = exynos4210_pmu_regs[i].reset_value;
+ }
+}
+
+static int exynos4210_pmu_init(SysBusDevice *dev)
+{
+ Exynos4210PmuState *s = EXYNOS4210_PMU(dev);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(dev), &exynos4210_pmu_ops, s,
+ "exynos4210.pmu", EXYNOS4210_PMU_REGS_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static const VMStateDescription exynos4210_pmu_vmstate = {
+ .name = "exynos4210.pmu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg, Exynos4210PmuState, PMU_NUM_OF_REGISTERS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_pmu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_pmu_init;
+ dc->reset = exynos4210_pmu_reset;
+ dc->vmsd = &exynos4210_pmu_vmstate;
+}
+
+static const TypeInfo exynos4210_pmu_info = {
+ .name = TYPE_EXYNOS4210_PMU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210PmuState),
+ .class_init = exynos4210_pmu_class_init,
+};
+
+static void exynos4210_pmu_register(void)
+{
+ type_register_static(&exynos4210_pmu_info);
+}
+
+type_init(exynos4210_pmu_register)
diff --git a/hw/misc/imx_ccm.c b/hw/misc/imx_ccm.c
new file mode 100644
index 00000000..09202886
--- /dev/null
+++ b/hw/misc/imx_ccm.c
@@ -0,0 +1,326 @@
+/*
+ * IMX31 Clock Control Module
+ *
+ * Copyright (C) 2012 NICTA
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * To get the timer frequencies right, we need to emulate at least part of
+ * the CCM.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "hw/arm/imx.h"
+
+#define CKIH_FREQ 26000000 /* 26MHz crystal input */
+#define CKIL_FREQ 32768 /* nominal 32khz clock */
+
+
+//#define DEBUG_CCM 1
+#ifdef DEBUG_CCM
+#define DPRINTF(fmt, args...) \
+do { printf("imx_ccm: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+static int imx_ccm_post_load(void *opaque, int version_id);
+
+#define TYPE_IMX_CCM "imx_ccm"
+#define IMX_CCM(obj) OBJECT_CHECK(IMXCCMState, (obj), TYPE_IMX_CCM)
+
+typedef struct IMXCCMState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t ccmr;
+ uint32_t pdr0;
+ uint32_t pdr1;
+ uint32_t mpctl;
+ uint32_t spctl;
+ uint32_t cgr[3];
+ uint32_t pmcr0;
+ uint32_t pmcr1;
+
+ /* Frequencies precalculated on register changes */
+ uint32_t pll_refclk_freq;
+ uint32_t mcu_clk_freq;
+ uint32_t hsp_clk_freq;
+ uint32_t ipg_clk_freq;
+} IMXCCMState;
+
+static const VMStateDescription vmstate_imx_ccm = {
+ .name = "imx-ccm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ccmr, IMXCCMState),
+ VMSTATE_UINT32(pdr0, IMXCCMState),
+ VMSTATE_UINT32(pdr1, IMXCCMState),
+ VMSTATE_UINT32(mpctl, IMXCCMState),
+ VMSTATE_UINT32(spctl, IMXCCMState),
+ VMSTATE_UINT32_ARRAY(cgr, IMXCCMState, 3),
+ VMSTATE_UINT32(pmcr0, IMXCCMState),
+ VMSTATE_UINT32(pmcr1, IMXCCMState),
+ VMSTATE_UINT32(pll_refclk_freq, IMXCCMState),
+ VMSTATE_END_OF_LIST()
+ },
+ .post_load = imx_ccm_post_load,
+};
+
+/* CCMR */
+#define CCMR_FPME (1<<0)
+#define CCMR_MPE (1<<3)
+#define CCMR_MDS (1<<7)
+#define CCMR_FPMF (1<<26)
+#define CCMR_PRCS (3<<1)
+
+/* PDR0 */
+#define PDR0_MCU_PODF_SHIFT (0)
+#define PDR0_MCU_PODF_MASK (0x7)
+#define PDR0_MAX_PODF_SHIFT (3)
+#define PDR0_MAX_PODF_MASK (0x7)
+#define PDR0_IPG_PODF_SHIFT (6)
+#define PDR0_IPG_PODF_MASK (0x3)
+#define PDR0_NFC_PODF_SHIFT (8)
+#define PDR0_NFC_PODF_MASK (0x7)
+#define PDR0_HSP_PODF_SHIFT (11)
+#define PDR0_HSP_PODF_MASK (0x7)
+#define PDR0_PER_PODF_SHIFT (16)
+#define PDR0_PER_PODF_MASK (0x1f)
+#define PDR0_CSI_PODF_SHIFT (23)
+#define PDR0_CSI_PODF_MASK (0x1ff)
+
+#define EXTRACT(value, name) (((value) >> PDR0_##name##_PODF_SHIFT) \
+ & PDR0_##name##_PODF_MASK)
+#define INSERT(value, name) (((value) & PDR0_##name##_PODF_MASK) << \
+ PDR0_##name##_PODF_SHIFT)
+/* PLL control registers */
+#define PD(v) (((v) >> 26) & 0xf)
+#define MFD(v) (((v) >> 16) & 0x3ff)
+#define MFI(v) (((v) >> 10) & 0xf);
+#define MFN(v) ((v) & 0x3ff)
+
+#define PLL_PD(x) (((x) & 0xf) << 26)
+#define PLL_MFD(x) (((x) & 0x3ff) << 16)
+#define PLL_MFI(x) (((x) & 0xf) << 10)
+#define PLL_MFN(x) (((x) & 0x3ff) << 0)
+
+uint32_t imx_clock_frequency(DeviceState *dev, IMXClk clock)
+{
+ IMXCCMState *s = IMX_CCM(dev);
+
+ switch (clock) {
+ case NOCLK:
+ return 0;
+ case MCU:
+ return s->mcu_clk_freq;
+ case HSP:
+ return s->hsp_clk_freq;
+ case IPG:
+ return s->ipg_clk_freq;
+ case CLK_32k:
+ return CKIL_FREQ;
+ }
+ return 0;
+}
+
+/*
+ * Calculate PLL output frequency
+ */
+static uint32_t calc_pll(uint32_t pllreg, uint32_t base_freq)
+{
+ int32_t mfn = MFN(pllreg); /* Numerator */
+ uint32_t mfi = MFI(pllreg); /* Integer part */
+ uint32_t mfd = 1 + MFD(pllreg); /* Denominator */
+ uint32_t pd = 1 + PD(pllreg); /* Pre-divider */
+
+ if (mfi < 5) {
+ mfi = 5;
+ }
+ /* mfn is 10-bit signed twos-complement */
+ mfn <<= 32 - 10;
+ mfn >>= 32 - 10;
+
+ return ((2 * (base_freq >> 10) * (mfi * mfd + mfn)) /
+ (mfd * pd)) << 10;
+}
+
+static void update_clocks(IMXCCMState *s)
+{
+ /*
+ * If we ever emulate more clocks, this should switch to a data-driven
+ * approach
+ */
+
+ if ((s->ccmr & CCMR_PRCS) == 2) {
+ s->pll_refclk_freq = CKIL_FREQ * 1024;
+ } else {
+ s->pll_refclk_freq = CKIH_FREQ;
+ }
+
+ /* ipg_clk_arm aka MCU clock */
+ if ((s->ccmr & CCMR_MDS) || !(s->ccmr & CCMR_MPE)) {
+ s->mcu_clk_freq = s->pll_refclk_freq;
+ } else {
+ s->mcu_clk_freq = calc_pll(s->mpctl, s->pll_refclk_freq);
+ }
+
+ /* High-speed clock */
+ s->hsp_clk_freq = s->mcu_clk_freq / (1 + EXTRACT(s->pdr0, HSP));
+ s->ipg_clk_freq = s->hsp_clk_freq / (1 + EXTRACT(s->pdr0, IPG));
+
+ DPRINTF("Clocks: mcu %uMHz, HSP %uMHz, IPG %uHz\n",
+ s->mcu_clk_freq / 1000000,
+ s->hsp_clk_freq / 1000000,
+ s->ipg_clk_freq);
+}
+
+static void imx_ccm_reset(DeviceState *dev)
+{
+ IMXCCMState *s = IMX_CCM(dev);
+
+ s->ccmr = 0x074b0b7b;
+ s->pdr0 = 0xff870b48;
+ s->pdr1 = 0x49fcfe7f;
+ s->mpctl = PLL_PD(1) | PLL_MFD(0) | PLL_MFI(6) | PLL_MFN(0);
+ s->cgr[0] = s->cgr[1] = s->cgr[2] = 0xffffffff;
+ s->spctl = PLL_PD(1) | PLL_MFD(4) | PLL_MFI(0xc) | PLL_MFN(1);
+ s->pmcr0 = 0x80209828;
+
+ update_clocks(s);
+}
+
+static uint64_t imx_ccm_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ IMXCCMState *s = (IMXCCMState *)opaque;
+
+ DPRINTF("read(offset=%x)", offset >> 2);
+ switch (offset >> 2) {
+ case 0: /* CCMR */
+ DPRINTF(" ccmr = 0x%x\n", s->ccmr);
+ return s->ccmr;
+ case 1:
+ DPRINTF(" pdr0 = 0x%x\n", s->pdr0);
+ return s->pdr0;
+ case 2:
+ DPRINTF(" pdr1 = 0x%x\n", s->pdr1);
+ return s->pdr1;
+ case 4:
+ DPRINTF(" mpctl = 0x%x\n", s->mpctl);
+ return s->mpctl;
+ case 6:
+ DPRINTF(" spctl = 0x%x\n", s->spctl);
+ return s->spctl;
+ case 8:
+ DPRINTF(" cgr0 = 0x%x\n", s->cgr[0]);
+ return s->cgr[0];
+ case 9:
+ DPRINTF(" cgr1 = 0x%x\n", s->cgr[1]);
+ return s->cgr[1];
+ case 10:
+ DPRINTF(" cgr2 = 0x%x\n", s->cgr[2]);
+ return s->cgr[2];
+ case 18: /* LTR1 */
+ return 0x00004040;
+ case 23:
+ DPRINTF(" pcmr0 = 0x%x\n", s->pmcr0);
+ return s->pmcr0;
+ }
+ DPRINTF(" return 0\n");
+ return 0;
+}
+
+static void imx_ccm_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IMXCCMState *s = (IMXCCMState *)opaque;
+
+ DPRINTF("write(offset=%x, value = %x)\n",
+ offset >> 2, (unsigned int)value);
+ switch (offset >> 2) {
+ case 0:
+ s->ccmr = CCMR_FPMF | (value & 0x3b6fdfff);
+ break;
+ case 1:
+ s->pdr0 = value & 0xff9f3fff;
+ break;
+ case 2:
+ s->pdr1 = value;
+ break;
+ case 4:
+ s->mpctl = value & 0xbfff3fff;
+ break;
+ case 6:
+ s->spctl = value & 0xbfff3fff;
+ break;
+ case 8:
+ s->cgr[0] = value;
+ return;
+ case 9:
+ s->cgr[1] = value;
+ return;
+ case 10:
+ s->cgr[2] = value;
+ return;
+
+ default:
+ return;
+ }
+ update_clocks(s);
+}
+
+static const struct MemoryRegionOps imx_ccm_ops = {
+ .read = imx_ccm_read,
+ .write = imx_ccm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int imx_ccm_init(SysBusDevice *dev)
+{
+ IMXCCMState *s = IMX_CCM(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &imx_ccm_ops, s,
+ "imx_ccm", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static int imx_ccm_post_load(void *opaque, int version_id)
+{
+ IMXCCMState *s = (IMXCCMState *)opaque;
+
+ update_clocks(s);
+ return 0;
+}
+
+static void imx_ccm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sbc->init = imx_ccm_init;
+ dc->reset = imx_ccm_reset;
+ dc->vmsd = &vmstate_imx_ccm;
+ dc->desc = "i.MX Clock Control Module";
+}
+
+static const TypeInfo imx_ccm_info = {
+ .name = TYPE_IMX_CCM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXCCMState),
+ .class_init = imx_ccm_class_init,
+};
+
+static void imx_ccm_register_types(void)
+{
+ type_register_static(&imx_ccm_info);
+}
+
+type_init(imx_ccm_register_types)
diff --git a/hw/misc/ivshmem.c b/hw/misc/ivshmem.c
new file mode 100644
index 00000000..cc76989a
--- /dev/null
+++ b/hw/misc/ivshmem.c
@@ -0,0 +1,891 @@
+/*
+ * Inter-VM Shared Memory PCI device.
+ *
+ * Author:
+ * Cam Macdonell <cam@cs.ualberta.ca>
+ *
+ * Based On: cirrus_vga.c
+ * Copyright (c) 2004 Fabrice Bellard
+ * Copyright (c) 2004 Makoto Suzuki (suzu)
+ *
+ * and rtl8139.c
+ * Copyright (c) 2006 Igor Kovalenko
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/msix.h"
+#include "sysemu/kvm.h"
+#include "migration/migration.h"
+#include "qemu/error-report.h"
+#include "qemu/event_notifier.h"
+#include "qemu/fifo8.h"
+#include "sysemu/char.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <limits.h>
+
+#define PCI_VENDOR_ID_IVSHMEM PCI_VENDOR_ID_REDHAT_QUMRANET
+#define PCI_DEVICE_ID_IVSHMEM 0x1110
+
+#define IVSHMEM_IOEVENTFD 0
+#define IVSHMEM_MSI 1
+
+#define IVSHMEM_PEER 0
+#define IVSHMEM_MASTER 1
+
+#define IVSHMEM_REG_BAR_SIZE 0x100
+
+//#define DEBUG_IVSHMEM
+#ifdef DEBUG_IVSHMEM
+#define IVSHMEM_DPRINTF(fmt, ...) \
+ do {printf("IVSHMEM: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define IVSHMEM_DPRINTF(fmt, ...)
+#endif
+
+#define TYPE_IVSHMEM "ivshmem"
+#define IVSHMEM(obj) \
+ OBJECT_CHECK(IVShmemState, (obj), TYPE_IVSHMEM)
+
+typedef struct Peer {
+ int nb_eventfds;
+ EventNotifier *eventfds;
+} Peer;
+
+typedef struct EventfdEntry {
+ PCIDevice *pdev;
+ int vector;
+} EventfdEntry;
+
+typedef struct IVShmemState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ uint32_t intrmask;
+ uint32_t intrstatus;
+ uint32_t doorbell;
+
+ CharDriverState **eventfd_chr;
+ CharDriverState *server_chr;
+ Fifo8 incoming_fifo;
+ MemoryRegion ivshmem_mmio;
+
+ /* We might need to register the BAR before we actually have the memory.
+ * So prepare a container MemoryRegion for the BAR immediately and
+ * add a subregion when we have the memory.
+ */
+ MemoryRegion bar;
+ MemoryRegion ivshmem;
+ uint64_t ivshmem_size; /* size of shared memory region */
+ uint32_t ivshmem_attr;
+ uint32_t ivshmem_64bit;
+ int shm_fd; /* shared memory file descriptor */
+
+ Peer *peers;
+ int nb_peers; /* how many guests we have space for */
+ int max_peer; /* maximum numbered peer */
+
+ int vm_id;
+ uint32_t vectors;
+ uint32_t features;
+ EventfdEntry *eventfd_table;
+
+ Error *migration_blocker;
+
+ char * shmobj;
+ char * sizearg;
+ char * role;
+ int role_val; /* scalar to avoid multiple string comparisons */
+} IVShmemState;
+
+/* registers for the Inter-VM shared memory device */
+enum ivshmem_registers {
+ INTRMASK = 0,
+ INTRSTATUS = 4,
+ IVPOSITION = 8,
+ DOORBELL = 12,
+};
+
+static inline uint32_t ivshmem_has_feature(IVShmemState *ivs,
+ unsigned int feature) {
+ return (ivs->features & (1 << feature));
+}
+
+static inline bool is_power_of_two(uint64_t x) {
+ return (x & (x - 1)) == 0;
+}
+
+/* accessing registers - based on rtl8139 */
+static void ivshmem_update_irq(IVShmemState *s, int val)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int isr;
+ isr = (s->intrstatus & s->intrmask) & 0xffffffff;
+
+ /* don't print ISR resets */
+ if (isr) {
+ IVSHMEM_DPRINTF("Set IRQ to %d (%04x %04x)\n",
+ isr ? 1 : 0, s->intrstatus, s->intrmask);
+ }
+
+ pci_set_irq(d, (isr != 0));
+}
+
+static void ivshmem_IntrMask_write(IVShmemState *s, uint32_t val)
+{
+ IVSHMEM_DPRINTF("IntrMask write(w) val = 0x%04x\n", val);
+
+ s->intrmask = val;
+
+ ivshmem_update_irq(s, val);
+}
+
+static uint32_t ivshmem_IntrMask_read(IVShmemState *s)
+{
+ uint32_t ret = s->intrmask;
+
+ IVSHMEM_DPRINTF("intrmask read(w) val = 0x%04x\n", ret);
+
+ return ret;
+}
+
+static void ivshmem_IntrStatus_write(IVShmemState *s, uint32_t val)
+{
+ IVSHMEM_DPRINTF("IntrStatus write(w) val = 0x%04x\n", val);
+
+ s->intrstatus = val;
+
+ ivshmem_update_irq(s, val);
+}
+
+static uint32_t ivshmem_IntrStatus_read(IVShmemState *s)
+{
+ uint32_t ret = s->intrstatus;
+
+ /* reading ISR clears all interrupts */
+ s->intrstatus = 0;
+
+ ivshmem_update_irq(s, 0);
+
+ return ret;
+}
+
+static void ivshmem_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ IVShmemState *s = opaque;
+
+ uint16_t dest = val >> 16;
+ uint16_t vector = val & 0xff;
+
+ addr &= 0xfc;
+
+ IVSHMEM_DPRINTF("writing to addr " TARGET_FMT_plx "\n", addr);
+ switch (addr)
+ {
+ case INTRMASK:
+ ivshmem_IntrMask_write(s, val);
+ break;
+
+ case INTRSTATUS:
+ ivshmem_IntrStatus_write(s, val);
+ break;
+
+ case DOORBELL:
+ /* check that dest VM ID is reasonable */
+ if (dest > s->max_peer) {
+ IVSHMEM_DPRINTF("Invalid destination VM ID (%d)\n", dest);
+ break;
+ }
+
+ /* check doorbell range */
+ if (vector < s->peers[dest].nb_eventfds) {
+ IVSHMEM_DPRINTF("Notifying VM %d on vector %d\n", dest, vector);
+ event_notifier_set(&s->peers[dest].eventfds[vector]);
+ }
+ break;
+ default:
+ IVSHMEM_DPRINTF("Invalid VM Doorbell VM %d\n", dest);
+ }
+}
+
+static uint64_t ivshmem_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+
+ IVShmemState *s = opaque;
+ uint32_t ret;
+
+ switch (addr)
+ {
+ case INTRMASK:
+ ret = ivshmem_IntrMask_read(s);
+ break;
+
+ case INTRSTATUS:
+ ret = ivshmem_IntrStatus_read(s);
+ break;
+
+ case IVPOSITION:
+ /* return my VM ID if the memory is mapped */
+ if (s->shm_fd > 0) {
+ ret = s->vm_id;
+ } else {
+ ret = -1;
+ }
+ break;
+
+ default:
+ IVSHMEM_DPRINTF("why are we reading " TARGET_FMT_plx "\n", addr);
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static const MemoryRegionOps ivshmem_mmio_ops = {
+ .read = ivshmem_io_read,
+ .write = ivshmem_io_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void ivshmem_receive(void *opaque, const uint8_t *buf, int size)
+{
+ IVShmemState *s = opaque;
+
+ ivshmem_IntrStatus_write(s, *buf);
+
+ IVSHMEM_DPRINTF("ivshmem_receive 0x%02x\n", *buf);
+}
+
+static int ivshmem_can_receive(void * opaque)
+{
+ return 8;
+}
+
+static void ivshmem_event(void *opaque, int event)
+{
+ IVSHMEM_DPRINTF("ivshmem_event %d\n", event);
+}
+
+static void fake_irqfd(void *opaque, const uint8_t *buf, int size) {
+
+ EventfdEntry *entry = opaque;
+ PCIDevice *pdev = entry->pdev;
+
+ IVSHMEM_DPRINTF("interrupt on vector %p %d\n", pdev, entry->vector);
+ msix_notify(pdev, entry->vector);
+}
+
+static CharDriverState* create_eventfd_chr_device(void * opaque, EventNotifier *n,
+ int vector)
+{
+ /* create a event character device based on the passed eventfd */
+ IVShmemState *s = opaque;
+ CharDriverState * chr;
+ int eventfd = event_notifier_get_fd(n);
+
+ chr = qemu_chr_open_eventfd(eventfd);
+
+ if (chr == NULL) {
+ error_report("creating eventfd for eventfd %d failed", eventfd);
+ exit(1);
+ }
+ qemu_chr_fe_claim_no_fail(chr);
+
+ /* if MSI is supported we need multiple interrupts */
+ if (ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ s->eventfd_table[vector].pdev = PCI_DEVICE(s);
+ s->eventfd_table[vector].vector = vector;
+
+ qemu_chr_add_handlers(chr, ivshmem_can_receive, fake_irqfd,
+ ivshmem_event, &s->eventfd_table[vector]);
+ } else {
+ qemu_chr_add_handlers(chr, ivshmem_can_receive, ivshmem_receive,
+ ivshmem_event, s);
+ }
+
+ return chr;
+
+}
+
+static int check_shm_size(IVShmemState *s, int fd) {
+ /* check that the guest isn't going to try and map more memory than the
+ * the object has allocated return -1 to indicate error */
+
+ struct stat buf;
+
+ if (fstat(fd, &buf) < 0) {
+ error_report("exiting: fstat on fd %d failed: %s",
+ fd, strerror(errno));
+ return -1;
+ }
+
+ if (s->ivshmem_size > buf.st_size) {
+ error_report("Requested memory size greater"
+ " than shared object size (%" PRIu64 " > %" PRIu64")",
+ s->ivshmem_size, (uint64_t)buf.st_size);
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+/* create the shared memory BAR when we are not using the server, so we can
+ * create the BAR and map the memory immediately */
+static void create_shared_memory_BAR(IVShmemState *s, int fd) {
+
+ void * ptr;
+
+ s->shm_fd = fd;
+
+ ptr = mmap(0, s->ivshmem_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+
+ memory_region_init_ram_ptr(&s->ivshmem, OBJECT(s), "ivshmem.bar2",
+ s->ivshmem_size, ptr);
+ vmstate_register_ram(&s->ivshmem, DEVICE(s));
+ memory_region_add_subregion(&s->bar, 0, &s->ivshmem);
+
+ /* region for shared memory */
+ pci_register_bar(PCI_DEVICE(s), 2, s->ivshmem_attr, &s->bar);
+}
+
+static void ivshmem_add_eventfd(IVShmemState *s, int posn, int i)
+{
+ memory_region_add_eventfd(&s->ivshmem_mmio,
+ DOORBELL,
+ 4,
+ true,
+ (posn << 16) | i,
+ &s->peers[posn].eventfds[i]);
+}
+
+static void ivshmem_del_eventfd(IVShmemState *s, int posn, int i)
+{
+ memory_region_del_eventfd(&s->ivshmem_mmio,
+ DOORBELL,
+ 4,
+ true,
+ (posn << 16) | i,
+ &s->peers[posn].eventfds[i]);
+}
+
+static void close_guest_eventfds(IVShmemState *s, int posn)
+{
+ int i, guest_curr_max;
+
+ if (!ivshmem_has_feature(s, IVSHMEM_IOEVENTFD)) {
+ return;
+ }
+ if (posn < 0 || posn >= s->nb_peers) {
+ return;
+ }
+
+ guest_curr_max = s->peers[posn].nb_eventfds;
+
+ memory_region_transaction_begin();
+ for (i = 0; i < guest_curr_max; i++) {
+ ivshmem_del_eventfd(s, posn, i);
+ }
+ memory_region_transaction_commit();
+ for (i = 0; i < guest_curr_max; i++) {
+ event_notifier_cleanup(&s->peers[posn].eventfds[i]);
+ }
+
+ g_free(s->peers[posn].eventfds);
+ s->peers[posn].nb_eventfds = 0;
+}
+
+/* this function increase the dynamic storage need to store data about other
+ * guests */
+static int increase_dynamic_storage(IVShmemState *s, int new_min_size)
+{
+
+ int j, old_nb_alloc;
+
+ /* check for integer overflow */
+ if (new_min_size >= INT_MAX / sizeof(Peer) - 1 || new_min_size <= 0) {
+ return -1;
+ }
+
+ old_nb_alloc = s->nb_peers;
+
+ if (new_min_size >= s->nb_peers) {
+ /* +1 because #new_min_size is used as last array index */
+ s->nb_peers = new_min_size + 1;
+ } else {
+ return 0;
+ }
+
+ IVSHMEM_DPRINTF("bumping storage to %d guests\n", s->nb_peers);
+ s->peers = g_realloc(s->peers, s->nb_peers * sizeof(Peer));
+
+ /* zero out new pointers */
+ for (j = old_nb_alloc; j < s->nb_peers; j++) {
+ s->peers[j].eventfds = NULL;
+ s->peers[j].nb_eventfds = 0;
+ }
+
+ return 0;
+}
+
+static void ivshmem_read(void *opaque, const uint8_t *buf, int size)
+{
+ IVShmemState *s = opaque;
+ int incoming_fd, tmp_fd;
+ int guest_max_eventfd;
+ long incoming_posn;
+
+ if (fifo8_is_empty(&s->incoming_fifo) && size == sizeof(incoming_posn)) {
+ memcpy(&incoming_posn, buf, size);
+ } else {
+ const uint8_t *p;
+ uint32_t num;
+
+ IVSHMEM_DPRINTF("short read of %d bytes\n", size);
+ num = MAX(size, sizeof(long) - fifo8_num_used(&s->incoming_fifo));
+ fifo8_push_all(&s->incoming_fifo, buf, num);
+ if (fifo8_num_used(&s->incoming_fifo) < sizeof(incoming_posn)) {
+ return;
+ }
+ size -= num;
+ buf += num;
+ p = fifo8_pop_buf(&s->incoming_fifo, sizeof(incoming_posn), &num);
+ g_assert(num == sizeof(incoming_posn));
+ memcpy(&incoming_posn, p, sizeof(incoming_posn));
+ if (size > 0) {
+ fifo8_push_all(&s->incoming_fifo, buf, size);
+ }
+ }
+
+ if (incoming_posn < -1) {
+ IVSHMEM_DPRINTF("invalid incoming_posn %ld\n", incoming_posn);
+ return;
+ }
+
+ /* pick off s->server_chr->msgfd and store it, posn should accompany msg */
+ tmp_fd = qemu_chr_fe_get_msgfd(s->server_chr);
+ IVSHMEM_DPRINTF("posn is %ld, fd is %d\n", incoming_posn, tmp_fd);
+
+ /* make sure we have enough space for this guest */
+ if (incoming_posn >= s->nb_peers) {
+ if (increase_dynamic_storage(s, incoming_posn) < 0) {
+ error_report("increase_dynamic_storage() failed");
+ if (tmp_fd != -1) {
+ close(tmp_fd);
+ }
+ return;
+ }
+ }
+
+ if (tmp_fd == -1) {
+ /* if posn is positive and unseen before then this is our posn*/
+ if ((incoming_posn >= 0) &&
+ (s->peers[incoming_posn].eventfds == NULL)) {
+ /* receive our posn */
+ s->vm_id = incoming_posn;
+ return;
+ } else {
+ /* otherwise an fd == -1 means an existing guest has gone away */
+ IVSHMEM_DPRINTF("posn %ld has gone away\n", incoming_posn);
+ close_guest_eventfds(s, incoming_posn);
+ return;
+ }
+ }
+
+ /* because of the implementation of get_msgfd, we need a dup */
+ incoming_fd = dup(tmp_fd);
+
+ if (incoming_fd == -1) {
+ error_report("could not allocate file descriptor %s", strerror(errno));
+ close(tmp_fd);
+ return;
+ }
+
+ /* if the position is -1, then it's shared memory region fd */
+ if (incoming_posn == -1) {
+
+ void * map_ptr;
+
+ s->max_peer = 0;
+
+ if (check_shm_size(s, incoming_fd) == -1) {
+ exit(1);
+ }
+
+ /* mmap the region and map into the BAR2 */
+ map_ptr = mmap(0, s->ivshmem_size, PROT_READ|PROT_WRITE, MAP_SHARED,
+ incoming_fd, 0);
+ memory_region_init_ram_ptr(&s->ivshmem, OBJECT(s),
+ "ivshmem.bar2", s->ivshmem_size, map_ptr);
+ vmstate_register_ram(&s->ivshmem, DEVICE(s));
+
+ IVSHMEM_DPRINTF("guest h/w addr = %p, size = %" PRIu64 "\n",
+ map_ptr, s->ivshmem_size);
+
+ memory_region_add_subregion(&s->bar, 0, &s->ivshmem);
+
+ /* only store the fd if it is successfully mapped */
+ s->shm_fd = incoming_fd;
+
+ return;
+ }
+
+ /* each guest has an array of eventfds, and we keep track of how many
+ * guests for each VM */
+ guest_max_eventfd = s->peers[incoming_posn].nb_eventfds;
+
+ if (guest_max_eventfd == 0) {
+ /* one eventfd per MSI vector */
+ s->peers[incoming_posn].eventfds = g_new(EventNotifier, s->vectors);
+ }
+
+ /* this is an eventfd for a particular guest VM */
+ IVSHMEM_DPRINTF("eventfds[%ld][%d] = %d\n", incoming_posn,
+ guest_max_eventfd, incoming_fd);
+ event_notifier_init_fd(&s->peers[incoming_posn].eventfds[guest_max_eventfd],
+ incoming_fd);
+
+ /* increment count for particular guest */
+ s->peers[incoming_posn].nb_eventfds++;
+
+ /* keep track of the maximum VM ID */
+ if (incoming_posn > s->max_peer) {
+ s->max_peer = incoming_posn;
+ }
+
+ if (incoming_posn == s->vm_id) {
+ s->eventfd_chr[guest_max_eventfd] = create_eventfd_chr_device(s,
+ &s->peers[s->vm_id].eventfds[guest_max_eventfd],
+ guest_max_eventfd);
+ }
+
+ if (ivshmem_has_feature(s, IVSHMEM_IOEVENTFD)) {
+ ivshmem_add_eventfd(s, incoming_posn, guest_max_eventfd);
+ }
+}
+
+/* Select the MSI-X vectors used by device.
+ * ivshmem maps events to vectors statically, so
+ * we just enable all vectors on init and after reset. */
+static void ivshmem_use_msix(IVShmemState * s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int i;
+
+ if (!msix_present(d)) {
+ return;
+ }
+
+ for (i = 0; i < s->vectors; i++) {
+ msix_vector_use(d, i);
+ }
+}
+
+static void ivshmem_reset(DeviceState *d)
+{
+ IVShmemState *s = IVSHMEM(d);
+
+ s->intrstatus = 0;
+ ivshmem_use_msix(s);
+}
+
+static uint64_t ivshmem_get_size(IVShmemState * s) {
+
+ uint64_t value;
+ char *ptr;
+
+ value = strtoull(s->sizearg, &ptr, 10);
+ switch (*ptr) {
+ case 0: case 'M': case 'm':
+ value <<= 20;
+ break;
+ case 'G': case 'g':
+ value <<= 30;
+ break;
+ default:
+ error_report("invalid ram size: %s", s->sizearg);
+ exit(1);
+ }
+
+ /* BARs must be a power of 2 */
+ if (!is_power_of_two(value)) {
+ error_report("size must be power of 2");
+ exit(1);
+ }
+
+ return value;
+}
+
+static void ivshmem_setup_msi(IVShmemState * s)
+{
+ if (msix_init_exclusive_bar(PCI_DEVICE(s), s->vectors, 1)) {
+ IVSHMEM_DPRINTF("msix initialization failed\n");
+ exit(1);
+ }
+
+ IVSHMEM_DPRINTF("msix initialized (%d vectors)\n", s->vectors);
+
+ /* allocate QEMU char devices for receiving interrupts */
+ s->eventfd_table = g_malloc0(s->vectors * sizeof(EventfdEntry));
+
+ ivshmem_use_msix(s);
+}
+
+static void ivshmem_save(QEMUFile* f, void *opaque)
+{
+ IVShmemState *proxy = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(proxy);
+
+ IVSHMEM_DPRINTF("ivshmem_save\n");
+ pci_device_save(pci_dev, f);
+
+ if (ivshmem_has_feature(proxy, IVSHMEM_MSI)) {
+ msix_save(pci_dev, f);
+ } else {
+ qemu_put_be32(f, proxy->intrstatus);
+ qemu_put_be32(f, proxy->intrmask);
+ }
+
+}
+
+static int ivshmem_load(QEMUFile* f, void *opaque, int version_id)
+{
+ IVSHMEM_DPRINTF("ivshmem_load\n");
+
+ IVShmemState *proxy = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(proxy);
+ int ret;
+
+ if (version_id > 0) {
+ return -EINVAL;
+ }
+
+ if (proxy->role_val == IVSHMEM_PEER) {
+ error_report("'peer' devices are not migratable");
+ return -EINVAL;
+ }
+
+ ret = pci_device_load(pci_dev, f);
+ if (ret) {
+ return ret;
+ }
+
+ if (ivshmem_has_feature(proxy, IVSHMEM_MSI)) {
+ msix_load(pci_dev, f);
+ ivshmem_use_msix(proxy);
+ } else {
+ proxy->intrstatus = qemu_get_be32(f);
+ proxy->intrmask = qemu_get_be32(f);
+ }
+
+ return 0;
+}
+
+static void ivshmem_write_config(PCIDevice *pci_dev, uint32_t address,
+ uint32_t val, int len)
+{
+ pci_default_write_config(pci_dev, address, val, len);
+}
+
+static int pci_ivshmem_init(PCIDevice *dev)
+{
+ IVShmemState *s = IVSHMEM(dev);
+ uint8_t *pci_conf;
+
+ if (s->sizearg == NULL)
+ s->ivshmem_size = 4 << 20; /* 4 MB default */
+ else {
+ s->ivshmem_size = ivshmem_get_size(s);
+ }
+
+ fifo8_create(&s->incoming_fifo, sizeof(long));
+
+ register_savevm(DEVICE(dev), "ivshmem", 0, 0, ivshmem_save, ivshmem_load,
+ dev);
+
+ /* IRQFD requires MSI */
+ if (ivshmem_has_feature(s, IVSHMEM_IOEVENTFD) &&
+ !ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ error_report("ioeventfd/irqfd requires MSI");
+ exit(1);
+ }
+
+ /* check that role is reasonable */
+ if (s->role) {
+ if (strncmp(s->role, "peer", 5) == 0) {
+ s->role_val = IVSHMEM_PEER;
+ } else if (strncmp(s->role, "master", 7) == 0) {
+ s->role_val = IVSHMEM_MASTER;
+ } else {
+ error_report("'role' must be 'peer' or 'master'");
+ exit(1);
+ }
+ } else {
+ s->role_val = IVSHMEM_MASTER; /* default */
+ }
+
+ if (s->role_val == IVSHMEM_PEER) {
+ error_setg(&s->migration_blocker,
+ "Migration is disabled when using feature 'peer mode' in device 'ivshmem'");
+ migrate_add_blocker(s->migration_blocker);
+ }
+
+ pci_conf = dev->config;
+ pci_conf[PCI_COMMAND] = PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
+
+ pci_config_set_interrupt_pin(pci_conf, 1);
+
+ s->shm_fd = 0;
+
+ memory_region_init_io(&s->ivshmem_mmio, OBJECT(s), &ivshmem_mmio_ops, s,
+ "ivshmem-mmio", IVSHMEM_REG_BAR_SIZE);
+
+ /* region for registers*/
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY,
+ &s->ivshmem_mmio);
+
+ memory_region_init(&s->bar, OBJECT(s), "ivshmem-bar2-container", s->ivshmem_size);
+ s->ivshmem_attr = PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_PREFETCH;
+ if (s->ivshmem_64bit) {
+ s->ivshmem_attr |= PCI_BASE_ADDRESS_MEM_TYPE_64;
+ }
+
+ if ((s->server_chr != NULL) &&
+ (strncmp(s->server_chr->filename, "unix:", 5) == 0)) {
+ /* if we get a UNIX socket as the parameter we will talk
+ * to the ivshmem server to receive the memory region */
+
+ if (s->shmobj != NULL) {
+ error_report("WARNING: do not specify both 'chardev' "
+ "and 'shm' with ivshmem");
+ }
+
+ IVSHMEM_DPRINTF("using shared memory server (socket = %s)\n",
+ s->server_chr->filename);
+
+ if (ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ ivshmem_setup_msi(s);
+ }
+
+ /* we allocate enough space for 16 guests and grow as needed */
+ s->nb_peers = 16;
+ s->vm_id = -1;
+
+ /* allocate/initialize space for interrupt handling */
+ s->peers = g_malloc0(s->nb_peers * sizeof(Peer));
+
+ pci_register_bar(dev, 2, s->ivshmem_attr, &s->bar);
+
+ s->eventfd_chr = g_malloc0(s->vectors * sizeof(CharDriverState *));
+
+ qemu_chr_add_handlers(s->server_chr, ivshmem_can_receive, ivshmem_read,
+ ivshmem_event, s);
+ } else {
+ /* just map the file immediately, we're not using a server */
+ int fd;
+
+ if (s->shmobj == NULL) {
+ error_report("Must specify 'chardev' or 'shm' to ivshmem");
+ exit(1);
+ }
+
+ IVSHMEM_DPRINTF("using shm_open (shm object = %s)\n", s->shmobj);
+
+ /* try opening with O_EXCL and if it succeeds zero the memory
+ * by truncating to 0 */
+ if ((fd = shm_open(s->shmobj, O_CREAT|O_RDWR|O_EXCL,
+ S_IRWXU|S_IRWXG|S_IRWXO)) > 0) {
+ /* truncate file to length PCI device's memory */
+ if (ftruncate(fd, s->ivshmem_size) != 0) {
+ error_report("could not truncate shared file");
+ }
+
+ } else if ((fd = shm_open(s->shmobj, O_CREAT|O_RDWR,
+ S_IRWXU|S_IRWXG|S_IRWXO)) < 0) {
+ error_report("could not open shared file");
+ exit(1);
+
+ }
+
+ if (check_shm_size(s, fd) == -1) {
+ exit(1);
+ }
+
+ create_shared_memory_BAR(s, fd);
+
+ }
+
+ dev->config_write = ivshmem_write_config;
+
+ return 0;
+}
+
+static void pci_ivshmem_uninit(PCIDevice *dev)
+{
+ IVShmemState *s = IVSHMEM(dev);
+
+ if (s->migration_blocker) {
+ migrate_del_blocker(s->migration_blocker);
+ error_free(s->migration_blocker);
+ }
+
+ memory_region_del_subregion(&s->bar, &s->ivshmem);
+ vmstate_unregister_ram(&s->ivshmem, DEVICE(dev));
+ unregister_savevm(DEVICE(dev), "ivshmem", s);
+ fifo8_destroy(&s->incoming_fifo);
+}
+
+static Property ivshmem_properties[] = {
+ DEFINE_PROP_CHR("chardev", IVShmemState, server_chr),
+ DEFINE_PROP_STRING("size", IVShmemState, sizearg),
+ DEFINE_PROP_UINT32("vectors", IVShmemState, vectors, 1),
+ DEFINE_PROP_BIT("ioeventfd", IVShmemState, features, IVSHMEM_IOEVENTFD, false),
+ DEFINE_PROP_BIT("msi", IVShmemState, features, IVSHMEM_MSI, true),
+ DEFINE_PROP_STRING("shm", IVShmemState, shmobj),
+ DEFINE_PROP_STRING("role", IVShmemState, role),
+ DEFINE_PROP_UINT32("use64", IVShmemState, ivshmem_64bit, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ivshmem_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = pci_ivshmem_init;
+ k->exit = pci_ivshmem_uninit;
+ k->vendor_id = PCI_VENDOR_ID_IVSHMEM;
+ k->device_id = PCI_DEVICE_ID_IVSHMEM;
+ k->class_id = PCI_CLASS_MEMORY_RAM;
+ dc->reset = ivshmem_reset;
+ dc->props = ivshmem_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo ivshmem_info = {
+ .name = TYPE_IVSHMEM,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(IVShmemState),
+ .class_init = ivshmem_class_init,
+};
+
+static void ivshmem_register_types(void)
+{
+ type_register_static(&ivshmem_info);
+}
+
+type_init(ivshmem_register_types)
diff --git a/hw/misc/macio/Makefile.objs b/hw/misc/macio/Makefile.objs
new file mode 100644
index 00000000..ef7ac249
--- /dev/null
+++ b/hw/misc/macio/Makefile.objs
@@ -0,0 +1,3 @@
+common-obj-y += macio.o
+common-obj-$(CONFIG_CUDA) += cuda.o
+common-obj-$(CONFIG_MAC_DBDMA) += mac_dbdma.o
diff --git a/hw/misc/macio/cuda.c b/hw/misc/macio/cuda.c
new file mode 100644
index 00000000..5d7043e9
--- /dev/null
+++ b/hw/misc/macio/cuda.c
@@ -0,0 +1,756 @@
+/*
+ * QEMU PowerMac CUDA device support
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/mac.h"
+#include "hw/input/adb.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+
+/* XXX: implement all timer modes */
+
+/* debug CUDA */
+//#define DEBUG_CUDA
+
+/* debug CUDA packets */
+//#define DEBUG_CUDA_PACKET
+
+#ifdef DEBUG_CUDA
+#define CUDA_DPRINTF(fmt, ...) \
+ do { printf("CUDA: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define CUDA_DPRINTF(fmt, ...)
+#endif
+
+/* Bits in B data register: all active low */
+#define TREQ 0x08 /* Transfer request (input) */
+#define TACK 0x10 /* Transfer acknowledge (output) */
+#define TIP 0x20 /* Transfer in progress (output) */
+
+/* Bits in ACR */
+#define SR_CTRL 0x1c /* Shift register control bits */
+#define SR_EXT 0x0c /* Shift on external clock */
+#define SR_OUT 0x10 /* Shift out if 1 */
+
+/* Bits in IFR and IER */
+#define IER_SET 0x80 /* set bits in IER */
+#define IER_CLR 0 /* clear bits in IER */
+#define SR_INT 0x04 /* Shift register full/empty */
+#define T1_INT 0x40 /* Timer 1 interrupt */
+#define T2_INT 0x20 /* Timer 2 interrupt */
+
+/* Bits in ACR */
+#define T1MODE 0xc0 /* Timer 1 mode */
+#define T1MODE_CONT 0x40 /* continuous interrupts */
+
+/* commands (1st byte) */
+#define ADB_PACKET 0
+#define CUDA_PACKET 1
+#define ERROR_PACKET 2
+#define TIMER_PACKET 3
+#define POWER_PACKET 4
+#define MACIIC_PACKET 5
+#define PMU_PACKET 6
+
+
+/* CUDA commands (2nd byte) */
+#define CUDA_WARM_START 0x0
+#define CUDA_AUTOPOLL 0x1
+#define CUDA_GET_6805_ADDR 0x2
+#define CUDA_GET_TIME 0x3
+#define CUDA_GET_PRAM 0x7
+#define CUDA_SET_6805_ADDR 0x8
+#define CUDA_SET_TIME 0x9
+#define CUDA_POWERDOWN 0xa
+#define CUDA_POWERUP_TIME 0xb
+#define CUDA_SET_PRAM 0xc
+#define CUDA_MS_RESET 0xd
+#define CUDA_SEND_DFAC 0xe
+#define CUDA_BATTERY_SWAP_SENSE 0x10
+#define CUDA_RESET_SYSTEM 0x11
+#define CUDA_SET_IPL 0x12
+#define CUDA_FILE_SERVER_FLAG 0x13
+#define CUDA_SET_AUTO_RATE 0x14
+#define CUDA_GET_AUTO_RATE 0x16
+#define CUDA_SET_DEVICE_LIST 0x19
+#define CUDA_GET_DEVICE_LIST 0x1a
+#define CUDA_SET_ONE_SECOND_MODE 0x1b
+#define CUDA_SET_POWER_MESSAGES 0x21
+#define CUDA_GET_SET_IIC 0x22
+#define CUDA_WAKEUP 0x23
+#define CUDA_TIMER_TICKLE 0x24
+#define CUDA_COMBINED_FORMAT_IIC 0x25
+
+#define CUDA_TIMER_FREQ (4700000 / 6)
+#define CUDA_ADB_POLL_FREQ 50
+
+/* CUDA returns time_t's offset from Jan 1, 1904, not 1970 */
+#define RTC_OFFSET 2082844800
+
+static void cuda_update(CUDAState *s);
+static void cuda_receive_packet_from_host(CUDAState *s,
+ const uint8_t *data, int len);
+static void cuda_timer_update(CUDAState *s, CUDATimer *ti,
+ int64_t current_time);
+
+static void cuda_update_irq(CUDAState *s)
+{
+ if (s->ifr & s->ier & (SR_INT | T1_INT)) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static uint64_t get_tb(uint64_t freq)
+{
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ freq, get_ticks_per_sec());
+}
+
+static unsigned int get_counter(CUDATimer *s)
+{
+ int64_t d;
+ unsigned int counter;
+ uint64_t tb_diff;
+
+ /* Reverse of the tb calculation algorithm that Mac OS X uses on bootup. */
+ tb_diff = get_tb(s->frequency) - s->load_time;
+ d = (tb_diff * 0xBF401675E5DULL) / (s->frequency << 24);
+
+ if (s->index == 0) {
+ /* the timer goes down from latch to -1 (period of latch + 2) */
+ if (d <= (s->counter_value + 1)) {
+ counter = (s->counter_value - d) & 0xffff;
+ } else {
+ counter = (d - (s->counter_value + 1)) % (s->latch + 2);
+ counter = (s->latch - counter) & 0xffff;
+ }
+ } else {
+ counter = (s->counter_value - d) & 0xffff;
+ }
+ return counter;
+}
+
+static void set_counter(CUDAState *s, CUDATimer *ti, unsigned int val)
+{
+ CUDA_DPRINTF("T%d.counter=%d\n", 1 + (ti->timer == NULL), val);
+ ti->load_time = get_tb(s->frequency);
+ ti->counter_value = val;
+ cuda_timer_update(s, ti, ti->load_time);
+}
+
+static int64_t get_next_irq_time(CUDATimer *s, int64_t current_time)
+{
+ int64_t d, next_time;
+ unsigned int counter;
+
+ /* current counter value */
+ d = muldiv64(current_time - s->load_time,
+ CUDA_TIMER_FREQ, get_ticks_per_sec());
+ /* the timer goes down from latch to -1 (period of latch + 2) */
+ if (d <= (s->counter_value + 1)) {
+ counter = (s->counter_value - d) & 0xffff;
+ } else {
+ counter = (d - (s->counter_value + 1)) % (s->latch + 2);
+ counter = (s->latch - counter) & 0xffff;
+ }
+
+ /* Note: we consider the irq is raised on 0 */
+ if (counter == 0xffff) {
+ next_time = d + s->latch + 1;
+ } else if (counter == 0) {
+ next_time = d + s->latch + 2;
+ } else {
+ next_time = d + counter;
+ }
+ CUDA_DPRINTF("latch=%d counter=%" PRId64 " delta_next=%" PRId64 "\n",
+ s->latch, d, next_time - d);
+ next_time = muldiv64(next_time, get_ticks_per_sec(), CUDA_TIMER_FREQ) +
+ s->load_time;
+ if (next_time <= current_time)
+ next_time = current_time + 1;
+ return next_time;
+}
+
+static void cuda_timer_update(CUDAState *s, CUDATimer *ti,
+ int64_t current_time)
+{
+ if (!ti->timer)
+ return;
+ if ((s->acr & T1MODE) != T1MODE_CONT) {
+ timer_del(ti->timer);
+ } else {
+ ti->next_irq_time = get_next_irq_time(ti, current_time);
+ timer_mod(ti->timer, ti->next_irq_time);
+ }
+}
+
+static void cuda_timer1(void *opaque)
+{
+ CUDAState *s = opaque;
+ CUDATimer *ti = &s->timers[0];
+
+ cuda_timer_update(s, ti, ti->next_irq_time);
+ s->ifr |= T1_INT;
+ cuda_update_irq(s);
+}
+
+static uint32_t cuda_readb(void *opaque, hwaddr addr)
+{
+ CUDAState *s = opaque;
+ uint32_t val;
+
+ addr = (addr >> 9) & 0xf;
+ switch(addr) {
+ case 0:
+ val = s->b;
+ break;
+ case 1:
+ val = s->a;
+ break;
+ case 2:
+ val = s->dirb;
+ break;
+ case 3:
+ val = s->dira;
+ break;
+ case 4:
+ val = get_counter(&s->timers[0]) & 0xff;
+ s->ifr &= ~T1_INT;
+ cuda_update_irq(s);
+ break;
+ case 5:
+ val = get_counter(&s->timers[0]) >> 8;
+ cuda_update_irq(s);
+ break;
+ case 6:
+ val = s->timers[0].latch & 0xff;
+ break;
+ case 7:
+ /* XXX: check this */
+ val = (s->timers[0].latch >> 8) & 0xff;
+ break;
+ case 8:
+ val = get_counter(&s->timers[1]) & 0xff;
+ s->ifr &= ~T2_INT;
+ break;
+ case 9:
+ val = get_counter(&s->timers[1]) >> 8;
+ break;
+ case 10:
+ val = s->sr;
+ s->ifr &= ~SR_INT;
+ cuda_update_irq(s);
+ break;
+ case 11:
+ val = s->acr;
+ break;
+ case 12:
+ val = s->pcr;
+ break;
+ case 13:
+ val = s->ifr;
+ if (s->ifr & s->ier)
+ val |= 0x80;
+ break;
+ case 14:
+ val = s->ier | 0x80;
+ break;
+ default:
+ case 15:
+ val = s->anh;
+ break;
+ }
+ if (addr != 13 || val != 0) {
+ CUDA_DPRINTF("read: reg=0x%x val=%02x\n", (int)addr, val);
+ }
+
+ return val;
+}
+
+static void cuda_writeb(void *opaque, hwaddr addr, uint32_t val)
+{
+ CUDAState *s = opaque;
+
+ addr = (addr >> 9) & 0xf;
+ CUDA_DPRINTF("write: reg=0x%x val=%02x\n", (int)addr, val);
+
+ switch(addr) {
+ case 0:
+ s->b = val;
+ cuda_update(s);
+ break;
+ case 1:
+ s->a = val;
+ break;
+ case 2:
+ s->dirb = val;
+ break;
+ case 3:
+ s->dira = val;
+ break;
+ case 4:
+ s->timers[0].latch = (s->timers[0].latch & 0xff00) | val;
+ cuda_timer_update(s, &s->timers[0], qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ case 5:
+ s->timers[0].latch = (s->timers[0].latch & 0xff) | (val << 8);
+ s->ifr &= ~T1_INT;
+ set_counter(s, &s->timers[0], s->timers[0].latch);
+ break;
+ case 6:
+ s->timers[0].latch = (s->timers[0].latch & 0xff00) | val;
+ cuda_timer_update(s, &s->timers[0], qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ case 7:
+ s->timers[0].latch = (s->timers[0].latch & 0xff) | (val << 8);
+ s->ifr &= ~T1_INT;
+ cuda_timer_update(s, &s->timers[0], qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ case 8:
+ s->timers[1].latch = val;
+ set_counter(s, &s->timers[1], val);
+ break;
+ case 9:
+ set_counter(s, &s->timers[1], (val << 8) | s->timers[1].latch);
+ break;
+ case 10:
+ s->sr = val;
+ break;
+ case 11:
+ s->acr = val;
+ cuda_timer_update(s, &s->timers[0], qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ cuda_update(s);
+ break;
+ case 12:
+ s->pcr = val;
+ break;
+ case 13:
+ /* reset bits */
+ s->ifr &= ~val;
+ cuda_update_irq(s);
+ break;
+ case 14:
+ if (val & IER_SET) {
+ /* set bits */
+ s->ier |= val & 0x7f;
+ } else {
+ /* reset bits */
+ s->ier &= ~val;
+ }
+ cuda_update_irq(s);
+ break;
+ default:
+ case 15:
+ s->anh = val;
+ break;
+ }
+}
+
+/* NOTE: TIP and TREQ are negated */
+static void cuda_update(CUDAState *s)
+{
+ int packet_received, len;
+
+ packet_received = 0;
+ if (!(s->b & TIP)) {
+ /* transfer requested from host */
+
+ if (s->acr & SR_OUT) {
+ /* data output */
+ if ((s->b & (TACK | TIP)) != (s->last_b & (TACK | TIP))) {
+ if (s->data_out_index < sizeof(s->data_out)) {
+ CUDA_DPRINTF("send: %02x\n", s->sr);
+ s->data_out[s->data_out_index++] = s->sr;
+ s->ifr |= SR_INT;
+ cuda_update_irq(s);
+ }
+ }
+ } else {
+ if (s->data_in_index < s->data_in_size) {
+ /* data input */
+ if ((s->b & (TACK | TIP)) != (s->last_b & (TACK | TIP))) {
+ s->sr = s->data_in[s->data_in_index++];
+ CUDA_DPRINTF("recv: %02x\n", s->sr);
+ /* indicate end of transfer */
+ if (s->data_in_index >= s->data_in_size) {
+ s->b = (s->b | TREQ);
+ }
+ s->ifr |= SR_INT;
+ cuda_update_irq(s);
+ }
+ }
+ }
+ } else {
+ /* no transfer requested: handle sync case */
+ if ((s->last_b & TIP) && (s->b & TACK) != (s->last_b & TACK)) {
+ /* update TREQ state each time TACK change state */
+ if (s->b & TACK)
+ s->b = (s->b | TREQ);
+ else
+ s->b = (s->b & ~TREQ);
+ s->ifr |= SR_INT;
+ cuda_update_irq(s);
+ } else {
+ if (!(s->last_b & TIP)) {
+ /* handle end of host to cuda transfer */
+ packet_received = (s->data_out_index > 0);
+ /* always an IRQ at the end of transfer */
+ s->ifr |= SR_INT;
+ cuda_update_irq(s);
+ }
+ /* signal if there is data to read */
+ if (s->data_in_index < s->data_in_size) {
+ s->b = (s->b & ~TREQ);
+ }
+ }
+ }
+
+ s->last_acr = s->acr;
+ s->last_b = s->b;
+
+ /* NOTE: cuda_receive_packet_from_host() can call cuda_update()
+ recursively */
+ if (packet_received) {
+ len = s->data_out_index;
+ s->data_out_index = 0;
+ cuda_receive_packet_from_host(s, s->data_out, len);
+ }
+}
+
+static void cuda_send_packet_to_host(CUDAState *s,
+ const uint8_t *data, int len)
+{
+#ifdef DEBUG_CUDA_PACKET
+ {
+ int i;
+ printf("cuda_send_packet_to_host:\n");
+ for(i = 0; i < len; i++)
+ printf(" %02x", data[i]);
+ printf("\n");
+ }
+#endif
+ memcpy(s->data_in, data, len);
+ s->data_in_size = len;
+ s->data_in_index = 0;
+ cuda_update(s);
+ s->ifr |= SR_INT;
+ cuda_update_irq(s);
+}
+
+static void cuda_adb_poll(void *opaque)
+{
+ CUDAState *s = opaque;
+ uint8_t obuf[ADB_MAX_OUT_LEN + 2];
+ int olen;
+
+ olen = adb_poll(&s->adb_bus, obuf + 2);
+ if (olen > 0) {
+ obuf[0] = ADB_PACKET;
+ obuf[1] = 0x40; /* polled data */
+ cuda_send_packet_to_host(s, obuf, olen + 2);
+ }
+ timer_mod(s->adb_poll_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (get_ticks_per_sec() / CUDA_ADB_POLL_FREQ));
+}
+
+static void cuda_receive_packet(CUDAState *s,
+ const uint8_t *data, int len)
+{
+ uint8_t obuf[16];
+ int autopoll;
+ uint32_t ti;
+
+ switch(data[0]) {
+ case CUDA_AUTOPOLL:
+ autopoll = (data[1] != 0);
+ if (autopoll != s->autopoll) {
+ s->autopoll = autopoll;
+ if (autopoll) {
+ timer_mod(s->adb_poll_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (get_ticks_per_sec() / CUDA_ADB_POLL_FREQ));
+ } else {
+ timer_del(s->adb_poll_timer);
+ }
+ }
+ obuf[0] = CUDA_PACKET;
+ obuf[1] = data[1];
+ cuda_send_packet_to_host(s, obuf, 2);
+ break;
+ case CUDA_SET_TIME:
+ ti = (((uint32_t)data[1]) << 24) + (((uint32_t)data[2]) << 16) + (((uint32_t)data[3]) << 8) + data[4];
+ s->tick_offset = ti - (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / get_ticks_per_sec());
+ obuf[0] = CUDA_PACKET;
+ obuf[1] = 0;
+ obuf[2] = 0;
+ cuda_send_packet_to_host(s, obuf, 3);
+ break;
+ case CUDA_GET_TIME:
+ ti = s->tick_offset + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / get_ticks_per_sec());
+ obuf[0] = CUDA_PACKET;
+ obuf[1] = 0;
+ obuf[2] = 0;
+ obuf[3] = ti >> 24;
+ obuf[4] = ti >> 16;
+ obuf[5] = ti >> 8;
+ obuf[6] = ti;
+ cuda_send_packet_to_host(s, obuf, 7);
+ break;
+ case CUDA_FILE_SERVER_FLAG:
+ case CUDA_SET_DEVICE_LIST:
+ case CUDA_SET_AUTO_RATE:
+ case CUDA_SET_POWER_MESSAGES:
+ obuf[0] = CUDA_PACKET;
+ obuf[1] = 0;
+ cuda_send_packet_to_host(s, obuf, 2);
+ break;
+ case CUDA_POWERDOWN:
+ obuf[0] = CUDA_PACKET;
+ obuf[1] = 0;
+ cuda_send_packet_to_host(s, obuf, 2);
+ qemu_system_shutdown_request();
+ break;
+ case CUDA_RESET_SYSTEM:
+ obuf[0] = CUDA_PACKET;
+ obuf[1] = 0;
+ cuda_send_packet_to_host(s, obuf, 2);
+ qemu_system_reset_request();
+ break;
+ default:
+ break;
+ }
+}
+
+static void cuda_receive_packet_from_host(CUDAState *s,
+ const uint8_t *data, int len)
+{
+#ifdef DEBUG_CUDA_PACKET
+ {
+ int i;
+ printf("cuda_receive_packet_from_host:\n");
+ for(i = 0; i < len; i++)
+ printf(" %02x", data[i]);
+ printf("\n");
+ }
+#endif
+ switch(data[0]) {
+ case ADB_PACKET:
+ {
+ uint8_t obuf[ADB_MAX_OUT_LEN + 2];
+ int olen;
+ olen = adb_request(&s->adb_bus, obuf + 2, data + 1, len - 1);
+ if (olen > 0) {
+ obuf[0] = ADB_PACKET;
+ obuf[1] = 0x00;
+ } else {
+ /* error */
+ obuf[0] = ADB_PACKET;
+ obuf[1] = -olen;
+ olen = 0;
+ }
+ cuda_send_packet_to_host(s, obuf, olen + 2);
+ }
+ break;
+ case CUDA_PACKET:
+ cuda_receive_packet(s, data + 1, len - 1);
+ break;
+ }
+}
+
+static void cuda_writew (void *opaque, hwaddr addr, uint32_t value)
+{
+}
+
+static void cuda_writel (void *opaque, hwaddr addr, uint32_t value)
+{
+}
+
+static uint32_t cuda_readw (void *opaque, hwaddr addr)
+{
+ return 0;
+}
+
+static uint32_t cuda_readl (void *opaque, hwaddr addr)
+{
+ return 0;
+}
+
+static const MemoryRegionOps cuda_ops = {
+ .old_mmio = {
+ .write = {
+ cuda_writeb,
+ cuda_writew,
+ cuda_writel,
+ },
+ .read = {
+ cuda_readb,
+ cuda_readw,
+ cuda_readl,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static bool cuda_timer_exist(void *opaque, int version_id)
+{
+ CUDATimer *s = opaque;
+
+ return s->timer != NULL;
+}
+
+static const VMStateDescription vmstate_cuda_timer = {
+ .name = "cuda_timer",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(latch, CUDATimer),
+ VMSTATE_UINT16(counter_value, CUDATimer),
+ VMSTATE_INT64(load_time, CUDATimer),
+ VMSTATE_INT64(next_irq_time, CUDATimer),
+ VMSTATE_TIMER_PTR_TEST(timer, CUDATimer, cuda_timer_exist),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_cuda = {
+ .name = "cuda",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(a, CUDAState),
+ VMSTATE_UINT8(b, CUDAState),
+ VMSTATE_UINT8(dira, CUDAState),
+ VMSTATE_UINT8(dirb, CUDAState),
+ VMSTATE_UINT8(sr, CUDAState),
+ VMSTATE_UINT8(acr, CUDAState),
+ VMSTATE_UINT8(pcr, CUDAState),
+ VMSTATE_UINT8(ifr, CUDAState),
+ VMSTATE_UINT8(ier, CUDAState),
+ VMSTATE_UINT8(anh, CUDAState),
+ VMSTATE_INT32(data_in_size, CUDAState),
+ VMSTATE_INT32(data_in_index, CUDAState),
+ VMSTATE_INT32(data_out_index, CUDAState),
+ VMSTATE_UINT8(autopoll, CUDAState),
+ VMSTATE_BUFFER(data_in, CUDAState),
+ VMSTATE_BUFFER(data_out, CUDAState),
+ VMSTATE_UINT32(tick_offset, CUDAState),
+ VMSTATE_STRUCT_ARRAY(timers, CUDAState, 2, 1,
+ vmstate_cuda_timer, CUDATimer),
+ VMSTATE_TIMER_PTR(adb_poll_timer, CUDAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cuda_reset(DeviceState *dev)
+{
+ CUDAState *s = CUDA(dev);
+
+ s->b = 0;
+ s->a = 0;
+ s->dirb = 0;
+ s->dira = 0;
+ s->sr = 0;
+ s->acr = 0;
+ s->pcr = 0;
+ s->ifr = 0;
+ s->ier = 0;
+ // s->ier = T1_INT | SR_INT;
+ s->anh = 0;
+ s->data_in_size = 0;
+ s->data_in_index = 0;
+ s->data_out_index = 0;
+ s->autopoll = 0;
+
+ s->timers[0].latch = 0xffff;
+ set_counter(s, &s->timers[0], 0xffff);
+
+ s->timers[1].latch = 0;
+ set_counter(s, &s->timers[1], 0xffff);
+}
+
+static void cuda_realizefn(DeviceState *dev, Error **errp)
+{
+ CUDAState *s = CUDA(dev);
+ struct tm tm;
+
+ s->timers[0].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, cuda_timer1, s);
+ s->timers[0].frequency = s->frequency;
+ s->timers[1].frequency = s->frequency;
+
+ qemu_get_timedate(&tm, 0);
+ s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET;
+
+ s->adb_poll_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, cuda_adb_poll, s);
+}
+
+static void cuda_initfn(Object *obj)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(obj);
+ CUDAState *s = CUDA(obj);
+ int i;
+
+ memory_region_init_io(&s->mem, obj, &cuda_ops, s, "cuda", 0x2000);
+ sysbus_init_mmio(d, &s->mem);
+ sysbus_init_irq(d, &s->irq);
+
+ for (i = 0; i < ARRAY_SIZE(s->timers); i++) {
+ s->timers[i].index = i;
+ }
+
+ qbus_create_inplace(&s->adb_bus, sizeof(s->adb_bus), TYPE_ADB_BUS,
+ DEVICE(obj), "adb.0");
+}
+
+static Property cuda_properties[] = {
+ DEFINE_PROP_UINT64("frequency", CUDAState, frequency, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void cuda_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = cuda_realizefn;
+ dc->reset = cuda_reset;
+ dc->vmsd = &vmstate_cuda;
+ dc->props = cuda_properties;
+}
+
+static const TypeInfo cuda_type_info = {
+ .name = TYPE_CUDA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CUDAState),
+ .instance_init = cuda_initfn,
+ .class_init = cuda_class_init,
+};
+
+static void cuda_register_types(void)
+{
+ type_register_static(&cuda_type_info);
+}
+
+type_init(cuda_register_types)
diff --git a/hw/misc/macio/mac_dbdma.c b/hw/misc/macio/mac_dbdma.c
new file mode 100644
index 00000000..779683cc
--- /dev/null
+++ b/hw/misc/macio/mac_dbdma.c
@@ -0,0 +1,766 @@
+/*
+ * PowerMac descriptor-based DMA emulation
+ *
+ * Copyright (c) 2005-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2009 Laurent Vivier
+ *
+ * some parts from linux-2.6.28, arch/powerpc/include/asm/dbdma.h
+ *
+ * Definitions for using the Apple Descriptor-Based DMA controller
+ * in Power Macintosh computers.
+ *
+ * Copyright (C) 1996 Paul Mackerras.
+ *
+ * some parts from mol 0.9.71
+ *
+ * Descriptor based DMA emulation
+ *
+ * Copyright (C) 1998-2004 Samuel Rydh (samuel@ibrium.se)
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/isa/isa.h"
+#include "hw/ppc/mac_dbdma.h"
+#include "qemu/main-loop.h"
+
+/* debug DBDMA */
+//#define DEBUG_DBDMA
+
+#ifdef DEBUG_DBDMA
+#define DBDMA_DPRINTF(fmt, ...) \
+ do { printf("DBDMA: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DBDMA_DPRINTF(fmt, ...)
+#endif
+
+/*
+ */
+
+static DBDMAState *dbdma_from_ch(DBDMA_channel *ch)
+{
+ return container_of(ch, DBDMAState, channels[ch->channel]);
+}
+
+#ifdef DEBUG_DBDMA
+static void dump_dbdma_cmd(dbdma_cmd *cmd)
+{
+ printf("dbdma_cmd %p\n", cmd);
+ printf(" req_count 0x%04x\n", le16_to_cpu(cmd->req_count));
+ printf(" command 0x%04x\n", le16_to_cpu(cmd->command));
+ printf(" phy_addr 0x%08x\n", le32_to_cpu(cmd->phy_addr));
+ printf(" cmd_dep 0x%08x\n", le32_to_cpu(cmd->cmd_dep));
+ printf(" res_count 0x%04x\n", le16_to_cpu(cmd->res_count));
+ printf(" xfer_status 0x%04x\n", le16_to_cpu(cmd->xfer_status));
+}
+#else
+static void dump_dbdma_cmd(dbdma_cmd *cmd)
+{
+}
+#endif
+static void dbdma_cmdptr_load(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTF("dbdma_cmdptr_load 0x%08x\n",
+ ch->regs[DBDMA_CMDPTR_LO]);
+ cpu_physical_memory_read(ch->regs[DBDMA_CMDPTR_LO],
+ &ch->current, sizeof(dbdma_cmd));
+}
+
+static void dbdma_cmdptr_save(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTF("dbdma_cmdptr_save 0x%08x\n",
+ ch->regs[DBDMA_CMDPTR_LO]);
+ DBDMA_DPRINTF("xfer_status 0x%08x res_count 0x%04x\n",
+ le16_to_cpu(ch->current.xfer_status),
+ le16_to_cpu(ch->current.res_count));
+ cpu_physical_memory_write(ch->regs[DBDMA_CMDPTR_LO],
+ &ch->current, sizeof(dbdma_cmd));
+}
+
+static void kill_channel(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTF("kill_channel\n");
+
+ ch->regs[DBDMA_STATUS] |= DEAD;
+ ch->regs[DBDMA_STATUS] &= ~ACTIVE;
+
+ qemu_irq_raise(ch->irq);
+}
+
+static void conditional_interrupt(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t intr;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+
+ DBDMA_DPRINTF("%s\n", __func__);
+
+ intr = le16_to_cpu(current->command) & INTR_MASK;
+
+ switch(intr) {
+ case INTR_NEVER: /* don't interrupt */
+ return;
+ case INTR_ALWAYS: /* always interrupt */
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTF("%s: raise\n", __func__);
+ return;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_INTR_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_INTR_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(intr) {
+ case INTR_IFSET: /* intr if condition bit is 1 */
+ if (cond) {
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTF("%s: raise\n", __func__);
+ }
+ return;
+ case INTR_IFCLR: /* intr if condition bit is 0 */
+ if (!cond) {
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTF("%s: raise\n", __func__);
+ }
+ return;
+ }
+}
+
+static int conditional_wait(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t wait;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+
+ DBDMA_DPRINTF("conditional_wait\n");
+
+ wait = le16_to_cpu(current->command) & WAIT_MASK;
+
+ switch(wait) {
+ case WAIT_NEVER: /* don't wait */
+ return 0;
+ case WAIT_ALWAYS: /* always wait */
+ return 1;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_WAIT_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_WAIT_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(wait) {
+ case WAIT_IFSET: /* wait if condition bit is 1 */
+ if (cond)
+ return 1;
+ return 0;
+ case WAIT_IFCLR: /* wait if condition bit is 0 */
+ if (!cond)
+ return 1;
+ return 0;
+ }
+ return 0;
+}
+
+static void next(DBDMA_channel *ch)
+{
+ uint32_t cp;
+
+ ch->regs[DBDMA_STATUS] &= ~BT;
+
+ cp = ch->regs[DBDMA_CMDPTR_LO];
+ ch->regs[DBDMA_CMDPTR_LO] = cp + sizeof(dbdma_cmd);
+ dbdma_cmdptr_load(ch);
+}
+
+static void branch(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+
+ ch->regs[DBDMA_CMDPTR_LO] = current->cmd_dep;
+ ch->regs[DBDMA_STATUS] |= BT;
+ dbdma_cmdptr_load(ch);
+}
+
+static void conditional_branch(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t br;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+
+ DBDMA_DPRINTF("conditional_branch\n");
+
+ /* check if we must branch */
+
+ br = le16_to_cpu(current->command) & BR_MASK;
+
+ switch(br) {
+ case BR_NEVER: /* don't branch */
+ next(ch);
+ return;
+ case BR_ALWAYS: /* always branch */
+ branch(ch);
+ return;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_BRANCH_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_BRANCH_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(br) {
+ case BR_IFSET: /* branch if condition bit is 1 */
+ if (cond)
+ branch(ch);
+ else
+ next(ch);
+ return;
+ case BR_IFCLR: /* branch if condition bit is 0 */
+ if (!cond)
+ branch(ch);
+ else
+ next(ch);
+ return;
+ }
+}
+
+static void channel_run(DBDMA_channel *ch);
+
+static void dbdma_end(DBDMA_io *io)
+{
+ DBDMA_channel *ch = io->channel;
+ dbdma_cmd *current = &ch->current;
+
+ DBDMA_DPRINTF("%s\n", __func__);
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ current->res_count = cpu_to_le16(io->len);
+ dbdma_cmdptr_save(ch);
+ if (io->is_last)
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ conditional_branch(ch);
+
+wait:
+ /* Indicate that we're ready for a new DMA round */
+ ch->io.processing = false;
+
+ if ((ch->regs[DBDMA_STATUS] & RUN) &&
+ (ch->regs[DBDMA_STATUS] & ACTIVE))
+ channel_run(ch);
+}
+
+static void start_output(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t req_count, int is_last)
+{
+ DBDMA_DPRINTF("start_output\n");
+
+ /* KEY_REGS, KEY_DEVICE and KEY_STREAM
+ * are not implemented in the mac-io chip
+ */
+
+ DBDMA_DPRINTF("addr 0x%x key 0x%x\n", addr, key);
+ if (!addr || key > KEY_STREAM3) {
+ kill_channel(ch);
+ return;
+ }
+
+ ch->io.addr = addr;
+ ch->io.len = req_count;
+ ch->io.is_last = is_last;
+ ch->io.dma_end = dbdma_end;
+ ch->io.is_dma_out = 1;
+ ch->io.processing = true;
+ if (ch->rw) {
+ ch->rw(&ch->io);
+ }
+}
+
+static void start_input(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t req_count, int is_last)
+{
+ DBDMA_DPRINTF("start_input\n");
+
+ /* KEY_REGS, KEY_DEVICE and KEY_STREAM
+ * are not implemented in the mac-io chip
+ */
+
+ DBDMA_DPRINTF("addr 0x%x key 0x%x\n", addr, key);
+ if (!addr || key > KEY_STREAM3) {
+ kill_channel(ch);
+ return;
+ }
+
+ ch->io.addr = addr;
+ ch->io.len = req_count;
+ ch->io.is_last = is_last;
+ ch->io.dma_end = dbdma_end;
+ ch->io.is_dma_out = 0;
+ ch->io.processing = true;
+ if (ch->rw) {
+ ch->rw(&ch->io);
+ }
+}
+
+static void load_word(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t len)
+{
+ dbdma_cmd *current = &ch->current;
+ uint32_t val;
+
+ DBDMA_DPRINTF("load_word\n");
+
+ /* only implements KEY_SYSTEM */
+
+ if (key != KEY_SYSTEM) {
+ printf("DBDMA: LOAD_WORD, unimplemented key %x\n", key);
+ kill_channel(ch);
+ return;
+ }
+
+ cpu_physical_memory_read(addr, &val, len);
+
+ if (len == 2)
+ val = (val << 16) | (current->cmd_dep & 0x0000ffff);
+ else if (len == 1)
+ val = (val << 24) | (current->cmd_dep & 0x00ffffff);
+
+ current->cmd_dep = val;
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ next(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void store_word(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t len)
+{
+ dbdma_cmd *current = &ch->current;
+ uint32_t val;
+
+ DBDMA_DPRINTF("store_word\n");
+
+ /* only implements KEY_SYSTEM */
+
+ if (key != KEY_SYSTEM) {
+ printf("DBDMA: STORE_WORD, unimplemented key %x\n", key);
+ kill_channel(ch);
+ return;
+ }
+
+ val = current->cmd_dep;
+ if (len == 2)
+ val >>= 16;
+ else if (len == 1)
+ val >>= 24;
+
+ cpu_physical_memory_write(addr, &val, len);
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ next(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void nop(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+
+ conditional_interrupt(ch);
+ conditional_branch(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void stop(DBDMA_channel *ch)
+{
+ ch->regs[DBDMA_STATUS] &= ~(ACTIVE|DEAD|FLUSH);
+
+ /* the stop command does not increment command pointer */
+}
+
+static void channel_run(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t cmd, key;
+ uint16_t req_count;
+ uint32_t phy_addr;
+
+ DBDMA_DPRINTF("channel_run\n");
+ dump_dbdma_cmd(current);
+
+ /* clear WAKE flag at command fetch */
+
+ ch->regs[DBDMA_STATUS] &= ~WAKE;
+
+ cmd = le16_to_cpu(current->command) & COMMAND_MASK;
+
+ switch (cmd) {
+ case DBDMA_NOP:
+ nop(ch);
+ return;
+
+ case DBDMA_STOP:
+ stop(ch);
+ return;
+ }
+
+ key = le16_to_cpu(current->command) & 0x0700;
+ req_count = le16_to_cpu(current->req_count);
+ phy_addr = le32_to_cpu(current->phy_addr);
+
+ if (key == KEY_STREAM4) {
+ printf("command %x, invalid key 4\n", cmd);
+ kill_channel(ch);
+ return;
+ }
+
+ switch (cmd) {
+ case OUTPUT_MORE:
+ start_output(ch, key, phy_addr, req_count, 0);
+ return;
+
+ case OUTPUT_LAST:
+ start_output(ch, key, phy_addr, req_count, 1);
+ return;
+
+ case INPUT_MORE:
+ start_input(ch, key, phy_addr, req_count, 0);
+ return;
+
+ case INPUT_LAST:
+ start_input(ch, key, phy_addr, req_count, 1);
+ return;
+ }
+
+ if (key < KEY_REGS) {
+ printf("command %x, invalid key %x\n", cmd, key);
+ key = KEY_SYSTEM;
+ }
+
+ /* for LOAD_WORD and STORE_WORD, req_count is on 3 bits
+ * and BRANCH is invalid
+ */
+
+ req_count = req_count & 0x0007;
+ if (req_count & 0x4) {
+ req_count = 4;
+ phy_addr &= ~3;
+ } else if (req_count & 0x2) {
+ req_count = 2;
+ phy_addr &= ~1;
+ } else
+ req_count = 1;
+
+ switch (cmd) {
+ case LOAD_WORD:
+ load_word(ch, key, phy_addr, req_count);
+ return;
+
+ case STORE_WORD:
+ store_word(ch, key, phy_addr, req_count);
+ return;
+ }
+}
+
+static void DBDMA_run(DBDMAState *s)
+{
+ int channel;
+
+ for (channel = 0; channel < DBDMA_CHANNELS; channel++) {
+ DBDMA_channel *ch = &s->channels[channel];
+ uint32_t status = ch->regs[DBDMA_STATUS];
+ if (!ch->io.processing && (status & RUN) && (status & ACTIVE)) {
+ channel_run(ch);
+ }
+ }
+}
+
+static void DBDMA_run_bh(void *opaque)
+{
+ DBDMAState *s = opaque;
+
+ DBDMA_DPRINTF("DBDMA_run_bh\n");
+
+ DBDMA_run(s);
+}
+
+void DBDMA_kick(DBDMAState *dbdma)
+{
+ qemu_bh_schedule(dbdma->bh);
+}
+
+void DBDMA_register_channel(void *dbdma, int nchan, qemu_irq irq,
+ DBDMA_rw rw, DBDMA_flush flush,
+ void *opaque)
+{
+ DBDMAState *s = dbdma;
+ DBDMA_channel *ch = &s->channels[nchan];
+
+ DBDMA_DPRINTF("DBDMA_register_channel 0x%x\n", nchan);
+
+ ch->irq = irq;
+ ch->channel = nchan;
+ ch->rw = rw;
+ ch->flush = flush;
+ ch->io.opaque = opaque;
+ ch->io.channel = ch;
+}
+
+static void
+dbdma_control_write(DBDMA_channel *ch)
+{
+ uint16_t mask, value;
+ uint32_t status;
+
+ mask = (ch->regs[DBDMA_CONTROL] >> 16) & 0xffff;
+ value = ch->regs[DBDMA_CONTROL] & 0xffff;
+
+ value &= (RUN | PAUSE | FLUSH | WAKE | DEVSTAT);
+
+ status = ch->regs[DBDMA_STATUS];
+
+ status = (value & mask) | (status & ~mask);
+
+ if (status & WAKE)
+ status |= ACTIVE;
+ if (status & RUN) {
+ status |= ACTIVE;
+ status &= ~DEAD;
+ }
+ if (status & PAUSE)
+ status &= ~ACTIVE;
+ if ((ch->regs[DBDMA_STATUS] & RUN) && !(status & RUN)) {
+ /* RUN is cleared */
+ status &= ~(ACTIVE|DEAD);
+ }
+
+ if ((status & FLUSH) && ch->flush) {
+ ch->flush(&ch->io);
+ status &= ~FLUSH;
+ }
+
+ DBDMA_DPRINTF(" status 0x%08x\n", status);
+
+ ch->regs[DBDMA_STATUS] = status;
+
+ if (status & ACTIVE) {
+ DBDMA_kick(dbdma_from_ch(ch));
+ }
+}
+
+static void dbdma_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ int channel = addr >> DBDMA_CHANNEL_SHIFT;
+ DBDMAState *s = opaque;
+ DBDMA_channel *ch = &s->channels[channel];
+ int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2;
+
+ DBDMA_DPRINTF("writel 0x" TARGET_FMT_plx " <= 0x%08"PRIx64"\n",
+ addr, value);
+ DBDMA_DPRINTF("channel 0x%x reg 0x%x\n",
+ (uint32_t)addr >> DBDMA_CHANNEL_SHIFT, reg);
+
+ /* cmdptr cannot be modified if channel is ACTIVE */
+
+ if (reg == DBDMA_CMDPTR_LO && (ch->regs[DBDMA_STATUS] & ACTIVE)) {
+ return;
+ }
+
+ ch->regs[reg] = value;
+
+ switch(reg) {
+ case DBDMA_CONTROL:
+ dbdma_control_write(ch);
+ break;
+ case DBDMA_CMDPTR_LO:
+ /* 16-byte aligned */
+ ch->regs[DBDMA_CMDPTR_LO] &= ~0xf;
+ dbdma_cmdptr_load(ch);
+ break;
+ case DBDMA_STATUS:
+ case DBDMA_INTR_SEL:
+ case DBDMA_BRANCH_SEL:
+ case DBDMA_WAIT_SEL:
+ /* nothing to do */
+ break;
+ case DBDMA_XFER_MODE:
+ case DBDMA_CMDPTR_HI:
+ case DBDMA_DATA2PTR_HI:
+ case DBDMA_DATA2PTR_LO:
+ case DBDMA_ADDRESS_HI:
+ case DBDMA_BRANCH_ADDR_HI:
+ case DBDMA_RES1:
+ case DBDMA_RES2:
+ case DBDMA_RES3:
+ case DBDMA_RES4:
+ /* unused */
+ break;
+ }
+}
+
+static uint64_t dbdma_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t value;
+ int channel = addr >> DBDMA_CHANNEL_SHIFT;
+ DBDMAState *s = opaque;
+ DBDMA_channel *ch = &s->channels[channel];
+ int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2;
+
+ value = ch->regs[reg];
+
+ DBDMA_DPRINTF("readl 0x" TARGET_FMT_plx " => 0x%08x\n", addr, value);
+ DBDMA_DPRINTF("channel 0x%x reg 0x%x\n",
+ (uint32_t)addr >> DBDMA_CHANNEL_SHIFT, reg);
+
+ switch(reg) {
+ case DBDMA_CONTROL:
+ value = 0;
+ break;
+ case DBDMA_STATUS:
+ case DBDMA_CMDPTR_LO:
+ case DBDMA_INTR_SEL:
+ case DBDMA_BRANCH_SEL:
+ case DBDMA_WAIT_SEL:
+ /* nothing to do */
+ break;
+ case DBDMA_XFER_MODE:
+ case DBDMA_CMDPTR_HI:
+ case DBDMA_DATA2PTR_HI:
+ case DBDMA_DATA2PTR_LO:
+ case DBDMA_ADDRESS_HI:
+ case DBDMA_BRANCH_ADDR_HI:
+ /* unused */
+ value = 0;
+ break;
+ case DBDMA_RES1:
+ case DBDMA_RES2:
+ case DBDMA_RES3:
+ case DBDMA_RES4:
+ /* reserved */
+ break;
+ }
+
+ return value;
+}
+
+static const MemoryRegionOps dbdma_ops = {
+ .read = dbdma_read,
+ .write = dbdma_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const VMStateDescription vmstate_dbdma_channel = {
+ .name = "dbdma_channel",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, struct DBDMA_channel, DBDMA_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_dbdma = {
+ .name = "dbdma",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(channels, DBDMAState, DBDMA_CHANNELS, 1,
+ vmstate_dbdma_channel, DBDMA_channel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void dbdma_reset(void *opaque)
+{
+ DBDMAState *s = opaque;
+ int i;
+
+ for (i = 0; i < DBDMA_CHANNELS; i++)
+ memset(s->channels[i].regs, 0, DBDMA_SIZE);
+}
+
+void* DBDMA_init (MemoryRegion **dbdma_mem)
+{
+ DBDMAState *s;
+ int i;
+
+ s = g_malloc0(sizeof(DBDMAState));
+
+ for (i = 0; i < DBDMA_CHANNELS; i++) {
+ DBDMA_io *io = &s->channels[i].io;
+ qemu_iovec_init(&io->iov, 1);
+ }
+
+ memory_region_init_io(&s->mem, NULL, &dbdma_ops, s, "dbdma", 0x1000);
+ *dbdma_mem = &s->mem;
+ vmstate_register(NULL, -1, &vmstate_dbdma, s);
+ qemu_register_reset(dbdma_reset, s);
+
+ s->bh = qemu_bh_new(DBDMA_run_bh, s);
+
+ return s;
+}
diff --git a/hw/misc/macio/macio.c b/hw/misc/macio/macio.c
new file mode 100644
index 00000000..c661f86c
--- /dev/null
+++ b/hw/misc/macio/macio.c
@@ -0,0 +1,446 @@
+/*
+ * PowerMac MacIO device emulation
+ *
+ * Copyright (c) 2005-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/mac.h"
+#include "hw/pci/pci.h"
+#include "hw/ppc/mac_dbdma.h"
+#include "hw/char/escc.h"
+
+#define TYPE_MACIO "macio"
+#define MACIO(obj) OBJECT_CHECK(MacIOState, (obj), TYPE_MACIO)
+
+typedef struct MacIOState
+{
+ /*< private >*/
+ PCIDevice parent;
+ /*< public >*/
+
+ MemoryRegion bar;
+ CUDAState cuda;
+ void *dbdma;
+ MemoryRegion *pic_mem;
+ MemoryRegion *escc_mem;
+ uint64_t frequency;
+} MacIOState;
+
+#define OLDWORLD_MACIO(obj) \
+ OBJECT_CHECK(OldWorldMacIOState, (obj), TYPE_OLDWORLD_MACIO)
+
+typedef struct OldWorldMacIOState {
+ /*< private >*/
+ MacIOState parent_obj;
+ /*< public >*/
+
+ qemu_irq irqs[5];
+
+ MacIONVRAMState nvram;
+ MACIOIDEState ide[2];
+} OldWorldMacIOState;
+
+#define NEWWORLD_MACIO(obj) \
+ OBJECT_CHECK(NewWorldMacIOState, (obj), TYPE_NEWWORLD_MACIO)
+
+typedef struct NewWorldMacIOState {
+ /*< private >*/
+ MacIOState parent_obj;
+ /*< public >*/
+ qemu_irq irqs[5];
+ MACIOIDEState ide[2];
+} NewWorldMacIOState;
+
+/*
+ * The mac-io has two interfaces to the ESCC. One is called "escc-legacy",
+ * while the other one is the normal, current ESCC interface.
+ *
+ * The magic below creates memory aliases to spawn the escc-legacy device
+ * purely by rerouting the respective registers to our escc region. This
+ * works because the only difference between the two memory regions is the
+ * register layout, not their semantics.
+ *
+ * Reference: ftp://ftp.software.ibm.com/rs6000/technology/spec/chrp/inwork/CHRP_IORef_1.0.pdf
+ */
+static void macio_escc_legacy_setup(MacIOState *macio_state)
+{
+ MemoryRegion *escc_legacy = g_new(MemoryRegion, 1);
+ MemoryRegion *bar = &macio_state->bar;
+ int i;
+ static const int maps[] = {
+ 0x00, 0x00,
+ 0x02, 0x20,
+ 0x04, 0x10,
+ 0x06, 0x30,
+ 0x08, 0x40,
+ 0x0A, 0x50,
+ 0x60, 0x60,
+ 0x70, 0x70,
+ 0x80, 0x70,
+ 0x90, 0x80,
+ 0xA0, 0x90,
+ 0xB0, 0xA0,
+ 0xC0, 0xB0,
+ 0xD0, 0xC0,
+ 0xE0, 0xD0,
+ 0xF0, 0xE0,
+ };
+
+ memory_region_init(escc_legacy, OBJECT(macio_state), "escc-legacy", 256);
+ for (i = 0; i < ARRAY_SIZE(maps); i += 2) {
+ MemoryRegion *port = g_new(MemoryRegion, 1);
+ memory_region_init_alias(port, OBJECT(macio_state), "escc-legacy-port",
+ macio_state->escc_mem, maps[i+1], 0x2);
+ memory_region_add_subregion(escc_legacy, maps[i], port);
+ }
+
+ memory_region_add_subregion(bar, 0x12000, escc_legacy);
+}
+
+static void macio_bar_setup(MacIOState *macio_state)
+{
+ MemoryRegion *bar = &macio_state->bar;
+
+ if (macio_state->escc_mem) {
+ memory_region_add_subregion(bar, 0x13000, macio_state->escc_mem);
+ macio_escc_legacy_setup(macio_state);
+ }
+}
+
+static void macio_common_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ SysBusDevice *sysbus_dev;
+ Error *err = NULL;
+ MemoryRegion *dbdma_mem;
+
+ s->dbdma = DBDMA_init(&dbdma_mem);
+ memory_region_add_subregion(&s->bar, 0x08000, dbdma_mem);
+
+ object_property_set_bool(OBJECT(&s->cuda), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->cuda);
+ memory_region_add_subregion(&s->bar, 0x16000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ macio_bar_setup(s);
+ pci_register_bar(d, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar);
+}
+
+static void macio_realize_ide(MacIOState *s, MACIOIDEState *ide,
+ qemu_irq irq0, qemu_irq irq1, int dmaid,
+ Error **errp)
+{
+ SysBusDevice *sysbus_dev;
+
+ sysbus_dev = SYS_BUS_DEVICE(ide);
+ sysbus_connect_irq(sysbus_dev, 0, irq0);
+ sysbus_connect_irq(sysbus_dev, 1, irq1);
+ macio_ide_register_dma(ide, s->dbdma, dmaid);
+ object_property_set_bool(OBJECT(ide), true, "realized", errp);
+}
+
+static void macio_oldworld_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ OldWorldMacIOState *os = OLDWORLD_MACIO(d);
+ Error *err = NULL;
+ SysBusDevice *sysbus_dev;
+ int i;
+ int cur_irq = 0;
+
+ macio_common_realize(d, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ sysbus_dev = SYS_BUS_DEVICE(&s->cuda);
+ sysbus_connect_irq(sysbus_dev, 0, os->irqs[cur_irq++]);
+
+ object_property_set_bool(OBJECT(&os->nvram), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&os->nvram);
+ memory_region_add_subregion(&s->bar, 0x60000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ pmac_format_nvram_partition(&os->nvram, os->nvram.size);
+
+ if (s->pic_mem) {
+ /* Heathrow PIC */
+ memory_region_add_subregion(&s->bar, 0x00000, s->pic_mem);
+ }
+
+ /* IDE buses */
+ for (i = 0; i < ARRAY_SIZE(os->ide); i++) {
+ qemu_irq irq0 = os->irqs[cur_irq++];
+ qemu_irq irq1 = os->irqs[cur_irq++];
+
+ macio_realize_ide(s, &os->ide[i], irq0, irq1, 0x16 + (i * 4), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ }
+}
+
+static void macio_init_ide(MacIOState *s, MACIOIDEState *ide, size_t ide_size,
+ int index)
+{
+ gchar *name;
+
+ object_initialize(ide, ide_size, TYPE_MACIO_IDE);
+ qdev_set_parent_bus(DEVICE(ide), sysbus_get_default());
+ memory_region_add_subregion(&s->bar, 0x1f000 + ((index + 1) * 0x1000),
+ &ide->mem);
+ name = g_strdup_printf("ide[%i]", index);
+ object_property_add_child(OBJECT(s), name, OBJECT(ide), NULL);
+ g_free(name);
+}
+
+static void macio_oldworld_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+ OldWorldMacIOState *os = OLDWORLD_MACIO(obj);
+ DeviceState *dev;
+ int i;
+
+ qdev_init_gpio_out(DEVICE(obj), os->irqs, ARRAY_SIZE(os->irqs));
+
+ object_initialize(&os->nvram, sizeof(os->nvram), TYPE_MACIO_NVRAM);
+ dev = DEVICE(&os->nvram);
+ qdev_prop_set_uint32(dev, "size", 0x2000);
+ qdev_prop_set_uint32(dev, "it_shift", 4);
+
+ for (i = 0; i < 2; i++) {
+ macio_init_ide(s, &os->ide[i], sizeof(os->ide[i]), i);
+ }
+}
+
+static void timer_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+}
+
+static uint64_t timer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint32_t value = 0;
+ uint64_t systime = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ uint64_t kltime;
+
+ kltime = muldiv64(systime, 4194300, get_ticks_per_sec() * 4);
+ kltime = muldiv64(kltime, 18432000, 1048575);
+
+ switch (addr) {
+ case 0x38:
+ value = kltime;
+ break;
+ case 0x3c:
+ value = kltime >> 32;
+ break;
+ }
+
+ return value;
+}
+
+static const MemoryRegionOps timer_ops = {
+ .read = timer_read,
+ .write = timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void macio_newworld_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ NewWorldMacIOState *ns = NEWWORLD_MACIO(d);
+ Error *err = NULL;
+ SysBusDevice *sysbus_dev;
+ MemoryRegion *timer_memory = NULL;
+ int i;
+ int cur_irq = 0;
+
+ macio_common_realize(d, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ sysbus_dev = SYS_BUS_DEVICE(&s->cuda);
+ sysbus_connect_irq(sysbus_dev, 0, ns->irqs[cur_irq++]);
+
+ if (s->pic_mem) {
+ /* OpenPIC */
+ memory_region_add_subregion(&s->bar, 0x40000, s->pic_mem);
+ }
+
+ /* IDE buses */
+ for (i = 0; i < ARRAY_SIZE(ns->ide); i++) {
+ qemu_irq irq0 = ns->irqs[cur_irq++];
+ qemu_irq irq1 = ns->irqs[cur_irq++];
+
+ macio_realize_ide(s, &ns->ide[i], irq0, irq1, 0x16 + (i * 4), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ }
+
+ /* Timer */
+ timer_memory = g_new(MemoryRegion, 1);
+ memory_region_init_io(timer_memory, OBJECT(s), &timer_ops, NULL, "timer",
+ 0x1000);
+ memory_region_add_subregion(&s->bar, 0x15000, timer_memory);
+}
+
+static void macio_newworld_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+ NewWorldMacIOState *ns = NEWWORLD_MACIO(obj);
+ int i;
+
+ qdev_init_gpio_out(DEVICE(obj), ns->irqs, ARRAY_SIZE(ns->irqs));
+
+ for (i = 0; i < 2; i++) {
+ macio_init_ide(s, &ns->ide[i], sizeof(ns->ide[i]), i);
+ }
+}
+
+static void macio_instance_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+
+ memory_region_init(&s->bar, obj, "macio", 0x80000);
+
+ object_initialize(&s->cuda, sizeof(s->cuda), TYPE_CUDA);
+ qdev_set_parent_bus(DEVICE(&s->cuda), sysbus_get_default());
+ object_property_add_child(obj, "cuda", OBJECT(&s->cuda), NULL);
+}
+
+static const VMStateDescription vmstate_macio_oldworld = {
+ .name = "macio-oldworld",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj.parent, OldWorldMacIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void macio_oldworld_class_init(ObjectClass *oc, void *data)
+{
+ PCIDeviceClass *pdc = PCI_DEVICE_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ pdc->realize = macio_oldworld_realize;
+ pdc->device_id = PCI_DEVICE_ID_APPLE_343S1201;
+ dc->vmsd = &vmstate_macio_oldworld;
+}
+
+static const VMStateDescription vmstate_macio_newworld = {
+ .name = "macio-newworld",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj.parent, NewWorldMacIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void macio_newworld_class_init(ObjectClass *oc, void *data)
+{
+ PCIDeviceClass *pdc = PCI_DEVICE_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ pdc->realize = macio_newworld_realize;
+ pdc->device_id = PCI_DEVICE_ID_APPLE_UNI_N_KEYL;
+ dc->vmsd = &vmstate_macio_newworld;
+}
+
+static Property macio_properties[] = {
+ DEFINE_PROP_UINT64("frequency", MacIOState, frequency, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void macio_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_APPLE;
+ k->class_id = PCI_CLASS_OTHERS << 8;
+ dc->props = macio_properties;
+}
+
+static const TypeInfo macio_oldworld_type_info = {
+ .name = TYPE_OLDWORLD_MACIO,
+ .parent = TYPE_MACIO,
+ .instance_size = sizeof(OldWorldMacIOState),
+ .instance_init = macio_oldworld_init,
+ .class_init = macio_oldworld_class_init,
+};
+
+static const TypeInfo macio_newworld_type_info = {
+ .name = TYPE_NEWWORLD_MACIO,
+ .parent = TYPE_MACIO,
+ .instance_size = sizeof(NewWorldMacIOState),
+ .instance_init = macio_newworld_init,
+ .class_init = macio_newworld_class_init,
+};
+
+static const TypeInfo macio_type_info = {
+ .name = TYPE_MACIO,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(MacIOState),
+ .instance_init = macio_instance_init,
+ .abstract = true,
+ .class_init = macio_class_init,
+};
+
+static void macio_register_types(void)
+{
+ type_register_static(&macio_type_info);
+ type_register_static(&macio_oldworld_type_info);
+ type_register_static(&macio_newworld_type_info);
+}
+
+type_init(macio_register_types)
+
+void macio_init(PCIDevice *d,
+ MemoryRegion *pic_mem,
+ MemoryRegion *escc_mem)
+{
+ MacIOState *macio_state = MACIO(d);
+
+ macio_state->pic_mem = pic_mem;
+ macio_state->escc_mem = escc_mem;
+ /* Note: this code is strongly inspirated from the corresponding code
+ in PearPC */
+ qdev_prop_set_uint64(DEVICE(&macio_state->cuda), "frequency",
+ macio_state->frequency);
+
+ qdev_init_nofail(DEVICE(d));
+}
diff --git a/hw/misc/max111x.c b/hw/misc/max111x.c
new file mode 100644
index 00000000..bef3651d
--- /dev/null
+++ b/hw/misc/max111x.c
@@ -0,0 +1,214 @@
+/*
+ * Maxim MAX1110/1111 ADC chip emulation.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GNU GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/ssi.h"
+
+typedef struct {
+ SSISlave parent_obj;
+
+ qemu_irq interrupt;
+ uint8_t tb1, rb2, rb3;
+ int cycle;
+
+ uint8_t input[8];
+ int inputs, com;
+} MAX111xState;
+
+#define TYPE_MAX_111X "max111x"
+
+#define MAX_111X(obj) \
+ OBJECT_CHECK(MAX111xState, (obj), TYPE_MAX_111X)
+
+#define TYPE_MAX_1110 "max1110"
+#define TYPE_MAX_1111 "max1111"
+
+/* Control-byte bitfields */
+#define CB_PD0 (1 << 0)
+#define CB_PD1 (1 << 1)
+#define CB_SGL (1 << 2)
+#define CB_UNI (1 << 3)
+#define CB_SEL0 (1 << 4)
+#define CB_SEL1 (1 << 5)
+#define CB_SEL2 (1 << 6)
+#define CB_START (1 << 7)
+
+#define CHANNEL_NUM(v, b0, b1, b2) \
+ ((((v) >> (2 + (b0))) & 4) | \
+ (((v) >> (3 + (b1))) & 2) | \
+ (((v) >> (4 + (b2))) & 1))
+
+static uint32_t max111x_read(MAX111xState *s)
+{
+ if (!s->tb1)
+ return 0;
+
+ switch (s->cycle ++) {
+ case 1:
+ return s->rb2;
+ case 2:
+ return s->rb3;
+ }
+
+ return 0;
+}
+
+/* Interpret a control-byte */
+static void max111x_write(MAX111xState *s, uint32_t value)
+{
+ int measure, chan;
+
+ /* Ignore the value if START bit is zero */
+ if (!(value & CB_START))
+ return;
+
+ s->cycle = 0;
+
+ if (!(value & CB_PD1)) {
+ s->tb1 = 0;
+ return;
+ }
+
+ s->tb1 = value;
+
+ if (s->inputs == 8)
+ chan = CHANNEL_NUM(value, 1, 0, 2);
+ else
+ chan = CHANNEL_NUM(value & ~CB_SEL0, 0, 1, 2);
+
+ if (value & CB_SGL)
+ measure = s->input[chan] - s->com;
+ else
+ measure = s->input[chan] - s->input[chan ^ 1];
+
+ if (!(value & CB_UNI))
+ measure ^= 0x80;
+
+ s->rb2 = (measure >> 2) & 0x3f;
+ s->rb3 = (measure << 6) & 0xc0;
+
+ /* FIXME: When should the IRQ be lowered? */
+ qemu_irq_raise(s->interrupt);
+}
+
+static uint32_t max111x_transfer(SSISlave *dev, uint32_t value)
+{
+ MAX111xState *s = MAX_111X(dev);
+ max111x_write(s, value);
+ return max111x_read(s);
+}
+
+static const VMStateDescription vmstate_max111x = {
+ .name = "max111x",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_SSI_SLAVE(parent_obj, MAX111xState),
+ VMSTATE_UINT8(tb1, MAX111xState),
+ VMSTATE_UINT8(rb2, MAX111xState),
+ VMSTATE_UINT8(rb3, MAX111xState),
+ VMSTATE_INT32_EQUAL(inputs, MAX111xState),
+ VMSTATE_INT32(com, MAX111xState),
+ VMSTATE_ARRAY_INT32_UNSAFE(input, MAX111xState, inputs,
+ vmstate_info_uint8, uint8_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int max111x_init(SSISlave *d, int inputs)
+{
+ DeviceState *dev = DEVICE(d);
+ MAX111xState *s = MAX_111X(dev);
+
+ qdev_init_gpio_out(dev, &s->interrupt, 1);
+
+ s->inputs = inputs;
+ /* TODO: add a user interface for setting these */
+ s->input[0] = 0xf0;
+ s->input[1] = 0xe0;
+ s->input[2] = 0xd0;
+ s->input[3] = 0xc0;
+ s->input[4] = 0xb0;
+ s->input[5] = 0xa0;
+ s->input[6] = 0x90;
+ s->input[7] = 0x80;
+ s->com = 0;
+
+ vmstate_register(dev, -1, &vmstate_max111x, s);
+ return 0;
+}
+
+static int max1110_init(SSISlave *dev)
+{
+ return max111x_init(dev, 8);
+}
+
+static int max1111_init(SSISlave *dev)
+{
+ return max111x_init(dev, 4);
+}
+
+void max111x_set_input(DeviceState *dev, int line, uint8_t value)
+{
+ MAX111xState *s = MAX_111X(dev);
+ assert(line >= 0 && line < s->inputs);
+ s->input[line] = value;
+}
+
+static void max111x_class_init(ObjectClass *klass, void *data)
+{
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->transfer = max111x_transfer;
+}
+
+static const TypeInfo max111x_info = {
+ .name = TYPE_MAX_111X,
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(MAX111xState),
+ .class_init = max111x_class_init,
+ .abstract = true,
+};
+
+static void max1110_class_init(ObjectClass *klass, void *data)
+{
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = max1110_init;
+}
+
+static const TypeInfo max1110_info = {
+ .name = TYPE_MAX_1110,
+ .parent = TYPE_MAX_111X,
+ .class_init = max1110_class_init,
+};
+
+static void max1111_class_init(ObjectClass *klass, void *data)
+{
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = max1111_init;
+}
+
+static const TypeInfo max1111_info = {
+ .name = TYPE_MAX_1111,
+ .parent = TYPE_MAX_111X,
+ .class_init = max1111_class_init,
+};
+
+static void max111x_register_types(void)
+{
+ type_register_static(&max111x_info);
+ type_register_static(&max1110_info);
+ type_register_static(&max1111_info);
+}
+
+type_init(max111x_register_types)
diff --git a/hw/misc/milkymist-hpdmc.c b/hw/misc/milkymist-hpdmc.c
new file mode 100644
index 00000000..f5f4c1b3
--- /dev/null
+++ b/hw/misc/milkymist-hpdmc.c
@@ -0,0 +1,174 @@
+/*
+ * QEMU model of the Milkymist High Performance Dynamic Memory Controller.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/hpdmc.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "qemu/error-report.h"
+
+enum {
+ R_SYSTEM = 0,
+ R_BYPASS,
+ R_TIMING,
+ R_IODELAY,
+ R_MAX
+};
+
+enum {
+ IODELAY_DQSDELAY_RDY = (1<<5),
+ IODELAY_PLL1_LOCKED = (1<<6),
+ IODELAY_PLL2_LOCKED = (1<<7),
+};
+
+#define TYPE_MILKYMIST_HPDMC "milkymist-hpdmc"
+#define MILKYMIST_HPDMC(obj) \
+ OBJECT_CHECK(MilkymistHpdmcState, (obj), TYPE_MILKYMIST_HPDMC)
+
+struct MilkymistHpdmcState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+
+ uint32_t regs[R_MAX];
+};
+typedef struct MilkymistHpdmcState MilkymistHpdmcState;
+
+static uint64_t hpdmc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistHpdmcState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SYSTEM:
+ case R_BYPASS:
+ case R_TIMING:
+ case R_IODELAY:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_hpdmc: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_hpdmc_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void hpdmc_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistHpdmcState *s = opaque;
+
+ trace_milkymist_hpdmc_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SYSTEM:
+ case R_BYPASS:
+ case R_TIMING:
+ s->regs[addr] = value;
+ break;
+ case R_IODELAY:
+ /* ignore writes */
+ break;
+
+ default:
+ error_report("milkymist_hpdmc: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps hpdmc_mmio_ops = {
+ .read = hpdmc_read,
+ .write = hpdmc_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void milkymist_hpdmc_reset(DeviceState *d)
+{
+ MilkymistHpdmcState *s = MILKYMIST_HPDMC(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ /* defaults */
+ s->regs[R_IODELAY] = IODELAY_DQSDELAY_RDY | IODELAY_PLL1_LOCKED
+ | IODELAY_PLL2_LOCKED;
+}
+
+static int milkymist_hpdmc_init(SysBusDevice *dev)
+{
+ MilkymistHpdmcState *s = MILKYMIST_HPDMC(dev);
+
+ memory_region_init_io(&s->regs_region, OBJECT(dev), &hpdmc_mmio_ops, s,
+ "milkymist-hpdmc", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_hpdmc = {
+ .name = "milkymist-hpdmc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistHpdmcState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void milkymist_hpdmc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_hpdmc_init;
+ dc->reset = milkymist_hpdmc_reset;
+ dc->vmsd = &vmstate_milkymist_hpdmc;
+}
+
+static const TypeInfo milkymist_hpdmc_info = {
+ .name = TYPE_MILKYMIST_HPDMC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistHpdmcState),
+ .class_init = milkymist_hpdmc_class_init,
+};
+
+static void milkymist_hpdmc_register_types(void)
+{
+ type_register_static(&milkymist_hpdmc_info);
+}
+
+type_init(milkymist_hpdmc_register_types)
diff --git a/hw/misc/milkymist-pfpu.c b/hw/misc/milkymist-pfpu.c
new file mode 100644
index 00000000..08b604f1
--- /dev/null
+++ b/hw/misc/milkymist-pfpu.c
@@ -0,0 +1,548 @@
+/*
+ * QEMU model of the Milkymist programmable FPU.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/pfpu.pdf
+ *
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include <math.h>
+
+/* #define TRACE_EXEC */
+
+#ifdef TRACE_EXEC
+# define D_EXEC(x) x
+#else
+# define D_EXEC(x)
+#endif
+
+enum {
+ R_CTL = 0,
+ R_MESHBASE,
+ R_HMESHLAST,
+ R_VMESHLAST,
+ R_CODEPAGE,
+ R_VERTICES,
+ R_COLLISIONS,
+ R_STRAYWRITES,
+ R_LASTDMA,
+ R_PC,
+ R_DREGBASE,
+ R_CODEBASE,
+ R_MAX
+};
+
+enum {
+ CTL_START_BUSY = (1<<0),
+};
+
+enum {
+ OP_NOP = 0,
+ OP_FADD,
+ OP_FSUB,
+ OP_FMUL,
+ OP_FABS,
+ OP_F2I,
+ OP_I2F,
+ OP_VECTOUT,
+ OP_SIN,
+ OP_COS,
+ OP_ABOVE,
+ OP_EQUAL,
+ OP_COPY,
+ OP_IF,
+ OP_TSIGN,
+ OP_QUAKE,
+};
+
+enum {
+ GPR_X = 0,
+ GPR_Y = 1,
+ GPR_FLAGS = 2,
+};
+
+enum {
+ LATENCY_FADD = 5,
+ LATENCY_FSUB = 5,
+ LATENCY_FMUL = 7,
+ LATENCY_FABS = 2,
+ LATENCY_F2I = 2,
+ LATENCY_I2F = 3,
+ LATENCY_VECTOUT = 0,
+ LATENCY_SIN = 4,
+ LATENCY_COS = 4,
+ LATENCY_ABOVE = 2,
+ LATENCY_EQUAL = 2,
+ LATENCY_COPY = 2,
+ LATENCY_IF = 2,
+ LATENCY_TSIGN = 2,
+ LATENCY_QUAKE = 2,
+ MAX_LATENCY = 7
+};
+
+#define GPR_BEGIN 0x100
+#define GPR_END 0x17f
+#define MICROCODE_BEGIN 0x200
+#define MICROCODE_END 0x3ff
+#define MICROCODE_WORDS 2048
+
+#define REINTERPRET_CAST(type, val) (*((type *)&(val)))
+
+#ifdef TRACE_EXEC
+static const char *opcode_to_str[] = {
+ "NOP", "FADD", "FSUB", "FMUL", "FABS", "F2I", "I2F", "VECTOUT",
+ "SIN", "COS", "ABOVE", "EQUAL", "COPY", "IF", "TSIGN", "QUAKE",
+};
+#endif
+
+#define TYPE_MILKYMIST_PFPU "milkymist-pfpu"
+#define MILKYMIST_PFPU(obj) \
+ OBJECT_CHECK(MilkymistPFPUState, (obj), TYPE_MILKYMIST_PFPU)
+
+struct MilkymistPFPUState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t regs[R_MAX];
+ uint32_t gp_regs[128];
+ uint32_t microcode[MICROCODE_WORDS];
+
+ int output_queue_pos;
+ uint32_t output_queue[MAX_LATENCY];
+};
+typedef struct MilkymistPFPUState MilkymistPFPUState;
+
+static inline hwaddr
+get_dma_address(uint32_t base, uint32_t x, uint32_t y)
+{
+ return base + 8 * (128 * y + x);
+}
+
+static inline void
+output_queue_insert(MilkymistPFPUState *s, uint32_t val, int pos)
+{
+ s->output_queue[(s->output_queue_pos + pos) % MAX_LATENCY] = val;
+}
+
+static inline uint32_t
+output_queue_remove(MilkymistPFPUState *s)
+{
+ return s->output_queue[s->output_queue_pos];
+}
+
+static inline void
+output_queue_advance(MilkymistPFPUState *s)
+{
+ s->output_queue[s->output_queue_pos] = 0;
+ s->output_queue_pos = (s->output_queue_pos + 1) % MAX_LATENCY;
+}
+
+static int pfpu_decode_insn(MilkymistPFPUState *s)
+{
+ uint32_t pc = s->regs[R_PC];
+ uint32_t insn = s->microcode[pc];
+ uint32_t reg_a = (insn >> 18) & 0x7f;
+ uint32_t reg_b = (insn >> 11) & 0x7f;
+ uint32_t op = (insn >> 7) & 0xf;
+ uint32_t reg_d = insn & 0x7f;
+ uint32_t r = 0;
+ int latency = 0;
+
+ switch (op) {
+ case OP_NOP:
+ break;
+ case OP_FADD:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);
+ float t = a + b;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_FADD;
+ D_EXEC(qemu_log("ADD a=%f b=%f t=%f, r=%08x\n", a, b, t, r));
+ } break;
+ case OP_FSUB:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);
+ float t = a - b;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_FSUB;
+ D_EXEC(qemu_log("SUB a=%f b=%f t=%f, r=%08x\n", a, b, t, r));
+ } break;
+ case OP_FMUL:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);
+ float t = a * b;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_FMUL;
+ D_EXEC(qemu_log("MUL a=%f b=%f t=%f, r=%08x\n", a, b, t, r));
+ } break;
+ case OP_FABS:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ float t = fabsf(a);
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_FABS;
+ D_EXEC(qemu_log("ABS a=%f t=%f, r=%08x\n", a, t, r));
+ } break;
+ case OP_F2I:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ int32_t t = a;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_F2I;
+ D_EXEC(qemu_log("F2I a=%f t=%d, r=%08x\n", a, t, r));
+ } break;
+ case OP_I2F:
+ {
+ int32_t a = REINTERPRET_CAST(int32_t, s->gp_regs[reg_a]);
+ float t = a;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_I2F;
+ D_EXEC(qemu_log("I2F a=%08x t=%f, r=%08x\n", a, t, r));
+ } break;
+ case OP_VECTOUT:
+ {
+ uint32_t a = cpu_to_be32(s->gp_regs[reg_a]);
+ uint32_t b = cpu_to_be32(s->gp_regs[reg_b]);
+ hwaddr dma_ptr =
+ get_dma_address(s->regs[R_MESHBASE],
+ s->gp_regs[GPR_X], s->gp_regs[GPR_Y]);
+ cpu_physical_memory_write(dma_ptr, &a, 4);
+ cpu_physical_memory_write(dma_ptr + 4, &b, 4);
+ s->regs[R_LASTDMA] = dma_ptr + 4;
+ D_EXEC(qemu_log("VECTOUT a=%08x b=%08x dma=%08x\n", a, b, dma_ptr));
+ trace_milkymist_pfpu_vectout(a, b, dma_ptr);
+ } break;
+ case OP_SIN:
+ {
+ int32_t a = REINTERPRET_CAST(int32_t, s->gp_regs[reg_a]);
+ float t = sinf(a * (1.0f / (M_PI * 4096.0f)));
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_SIN;
+ D_EXEC(qemu_log("SIN a=%d t=%f, r=%08x\n", a, t, r));
+ } break;
+ case OP_COS:
+ {
+ int32_t a = REINTERPRET_CAST(int32_t, s->gp_regs[reg_a]);
+ float t = cosf(a * (1.0f / (M_PI * 4096.0f)));
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_COS;
+ D_EXEC(qemu_log("COS a=%d t=%f, r=%08x\n", a, t, r));
+ } break;
+ case OP_ABOVE:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);
+ float t = (a > b) ? 1.0f : 0.0f;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_ABOVE;
+ D_EXEC(qemu_log("ABOVE a=%f b=%f t=%f, r=%08x\n", a, b, t, r));
+ } break;
+ case OP_EQUAL:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);
+ float t = (a == b) ? 1.0f : 0.0f;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_EQUAL;
+ D_EXEC(qemu_log("EQUAL a=%f b=%f t=%f, r=%08x\n", a, b, t, r));
+ } break;
+ case OP_COPY:
+ {
+ r = s->gp_regs[reg_a];
+ latency = LATENCY_COPY;
+ D_EXEC(qemu_log("COPY"));
+ } break;
+ case OP_IF:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);
+ uint32_t f = s->gp_regs[GPR_FLAGS];
+ float t = (f != 0) ? a : b;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_IF;
+ D_EXEC(qemu_log("IF f=%u a=%f b=%f t=%f, r=%08x\n", f, a, b, t, r));
+ } break;
+ case OP_TSIGN:
+ {
+ float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);
+ float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);
+ float t = (b < 0) ? -a : a;
+ r = REINTERPRET_CAST(uint32_t, t);
+ latency = LATENCY_TSIGN;
+ D_EXEC(qemu_log("TSIGN a=%f b=%f t=%f, r=%08x\n", a, b, t, r));
+ } break;
+ case OP_QUAKE:
+ {
+ uint32_t a = s->gp_regs[reg_a];
+ r = 0x5f3759df - (a >> 1);
+ latency = LATENCY_QUAKE;
+ D_EXEC(qemu_log("QUAKE a=%d r=%08x\n", a, r));
+ } break;
+
+ default:
+ error_report("milkymist_pfpu: unknown opcode %d", op);
+ break;
+ }
+
+ if (!reg_d) {
+ D_EXEC(qemu_log("%04d %8s R%03d, R%03d <L=%d, E=%04d>\n",
+ s->regs[R_PC], opcode_to_str[op], reg_a, reg_b, latency,
+ s->regs[R_PC] + latency));
+ } else {
+ D_EXEC(qemu_log("%04d %8s R%03d, R%03d <L=%d, E=%04d> -> R%03d\n",
+ s->regs[R_PC], opcode_to_str[op], reg_a, reg_b, latency,
+ s->regs[R_PC] + latency, reg_d));
+ }
+
+ if (op == OP_VECTOUT) {
+ return 0;
+ }
+
+ /* store output for this cycle */
+ if (reg_d) {
+ uint32_t val = output_queue_remove(s);
+ D_EXEC(qemu_log("R%03d <- 0x%08x\n", reg_d, val));
+ s->gp_regs[reg_d] = val;
+ }
+
+ output_queue_advance(s);
+
+ /* store op output */
+ if (op != OP_NOP) {
+ output_queue_insert(s, r, latency-1);
+ }
+
+ /* advance PC */
+ s->regs[R_PC]++;
+
+ return 1;
+};
+
+static void pfpu_start(MilkymistPFPUState *s)
+{
+ int x, y;
+ int i;
+
+ for (y = 0; y <= s->regs[R_VMESHLAST]; y++) {
+ for (x = 0; x <= s->regs[R_HMESHLAST]; x++) {
+ D_EXEC(qemu_log("\nprocessing x=%d y=%d\n", x, y));
+
+ /* set current position */
+ s->gp_regs[GPR_X] = x;
+ s->gp_regs[GPR_Y] = y;
+
+ /* run microcode on this position */
+ i = 0;
+ while (pfpu_decode_insn(s)) {
+ /* decode at most MICROCODE_WORDS instructions */
+ if (++i >= MICROCODE_WORDS) {
+ error_report("milkymist_pfpu: too many instructions "
+ "executed in microcode. No VECTOUT?");
+ break;
+ }
+ }
+
+ /* reset pc for next run */
+ s->regs[R_PC] = 0;
+ }
+ }
+
+ s->regs[R_VERTICES] = x * y;
+
+ trace_milkymist_pfpu_pulse_irq();
+ qemu_irq_pulse(s->irq);
+}
+
+static inline int get_microcode_address(MilkymistPFPUState *s, uint32_t addr)
+{
+ return (512 * s->regs[R_CODEPAGE]) + addr - MICROCODE_BEGIN;
+}
+
+static uint64_t pfpu_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistPFPUState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTL:
+ case R_MESHBASE:
+ case R_HMESHLAST:
+ case R_VMESHLAST:
+ case R_CODEPAGE:
+ case R_VERTICES:
+ case R_COLLISIONS:
+ case R_STRAYWRITES:
+ case R_LASTDMA:
+ case R_PC:
+ case R_DREGBASE:
+ case R_CODEBASE:
+ r = s->regs[addr];
+ break;
+ case GPR_BEGIN ... GPR_END:
+ r = s->gp_regs[addr - GPR_BEGIN];
+ break;
+ case MICROCODE_BEGIN ... MICROCODE_END:
+ r = s->microcode[get_microcode_address(s, addr)];
+ break;
+
+ default:
+ error_report("milkymist_pfpu: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_pfpu_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void pfpu_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistPFPUState *s = opaque;
+
+ trace_milkymist_pfpu_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTL:
+ if (value & CTL_START_BUSY) {
+ pfpu_start(s);
+ }
+ break;
+ case R_MESHBASE:
+ case R_HMESHLAST:
+ case R_VMESHLAST:
+ case R_CODEPAGE:
+ case R_VERTICES:
+ case R_COLLISIONS:
+ case R_STRAYWRITES:
+ case R_LASTDMA:
+ case R_PC:
+ case R_DREGBASE:
+ case R_CODEBASE:
+ s->regs[addr] = value;
+ break;
+ case GPR_BEGIN ... GPR_END:
+ s->gp_regs[addr - GPR_BEGIN] = value;
+ break;
+ case MICROCODE_BEGIN ... MICROCODE_END:
+ s->microcode[get_microcode_address(s, addr)] = value;
+ break;
+
+ default:
+ error_report("milkymist_pfpu: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps pfpu_mmio_ops = {
+ .read = pfpu_read,
+ .write = pfpu_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void milkymist_pfpu_reset(DeviceState *d)
+{
+ MilkymistPFPUState *s = MILKYMIST_PFPU(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+ for (i = 0; i < 128; i++) {
+ s->gp_regs[i] = 0;
+ }
+ for (i = 0; i < MICROCODE_WORDS; i++) {
+ s->microcode[i] = 0;
+ }
+ s->output_queue_pos = 0;
+ for (i = 0; i < MAX_LATENCY; i++) {
+ s->output_queue[i] = 0;
+ }
+}
+
+static int milkymist_pfpu_init(SysBusDevice *dev)
+{
+ MilkymistPFPUState *s = MILKYMIST_PFPU(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->regs_region, OBJECT(dev), &pfpu_mmio_ops, s,
+ "milkymist-pfpu", MICROCODE_END * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_pfpu = {
+ .name = "milkymist-pfpu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistPFPUState, R_MAX),
+ VMSTATE_UINT32_ARRAY(gp_regs, MilkymistPFPUState, 128),
+ VMSTATE_UINT32_ARRAY(microcode, MilkymistPFPUState, MICROCODE_WORDS),
+ VMSTATE_INT32(output_queue_pos, MilkymistPFPUState),
+ VMSTATE_UINT32_ARRAY(output_queue, MilkymistPFPUState, MAX_LATENCY),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void milkymist_pfpu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_pfpu_init;
+ dc->reset = milkymist_pfpu_reset;
+ dc->vmsd = &vmstate_milkymist_pfpu;
+}
+
+static const TypeInfo milkymist_pfpu_info = {
+ .name = TYPE_MILKYMIST_PFPU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistPFPUState),
+ .class_init = milkymist_pfpu_class_init,
+};
+
+static void milkymist_pfpu_register_types(void)
+{
+ type_register_static(&milkymist_pfpu_info);
+}
+
+type_init(milkymist_pfpu_register_types)
diff --git a/hw/misc/mst_fpga.c b/hw/misc/mst_fpga.c
new file mode 100644
index 00000000..d5090799
--- /dev/null
+++ b/hw/misc/mst_fpga.c
@@ -0,0 +1,266 @@
+/*
+ * PXA270-based Intel Mainstone platforms.
+ * FPGA driver
+ *
+ * Copyright (c) 2007 by Armin Kuster <akuster@kama-aina.net> or
+ * <akuster@mvista.com>
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+
+/* Mainstone FPGA for extern irqs */
+#define FPGA_GPIO_PIN 0
+#define MST_NUM_IRQS 16
+#define MST_LEDDAT1 0x10
+#define MST_LEDDAT2 0x14
+#define MST_LEDCTRL 0x40
+#define MST_GPSWR 0x60
+#define MST_MSCWR1 0x80
+#define MST_MSCWR2 0x84
+#define MST_MSCWR3 0x88
+#define MST_MSCRD 0x90
+#define MST_INTMSKENA 0xc0
+#define MST_INTSETCLR 0xd0
+#define MST_PCMCIA0 0xe0
+#define MST_PCMCIA1 0xe4
+
+#define MST_PCMCIAx_READY (1 << 10)
+#define MST_PCMCIAx_nCD (1 << 5)
+
+#define MST_PCMCIA_CD0_IRQ 9
+#define MST_PCMCIA_CD1_IRQ 13
+
+#define TYPE_MAINSTONE_FPGA "mainstone-fpga"
+#define MAINSTONE_FPGA(obj) \
+ OBJECT_CHECK(mst_irq_state, (obj), TYPE_MAINSTONE_FPGA)
+
+typedef struct mst_irq_state{
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ qemu_irq parent;
+
+ uint32_t prev_level;
+ uint32_t leddat1;
+ uint32_t leddat2;
+ uint32_t ledctrl;
+ uint32_t gpswr;
+ uint32_t mscwr1;
+ uint32_t mscwr2;
+ uint32_t mscwr3;
+ uint32_t mscrd;
+ uint32_t intmskena;
+ uint32_t intsetclr;
+ uint32_t pcmcia0;
+ uint32_t pcmcia1;
+}mst_irq_state;
+
+static void
+mst_fpga_set_irq(void *opaque, int irq, int level)
+{
+ mst_irq_state *s = (mst_irq_state *)opaque;
+ uint32_t oldint = s->intsetclr & s->intmskena;
+
+ if (level)
+ s->prev_level |= 1u << irq;
+ else
+ s->prev_level &= ~(1u << irq);
+
+ switch(irq) {
+ case MST_PCMCIA_CD0_IRQ:
+ if (level)
+ s->pcmcia0 &= ~MST_PCMCIAx_nCD;
+ else
+ s->pcmcia0 |= MST_PCMCIAx_nCD;
+ break;
+ case MST_PCMCIA_CD1_IRQ:
+ if (level)
+ s->pcmcia1 &= ~MST_PCMCIAx_nCD;
+ else
+ s->pcmcia1 |= MST_PCMCIAx_nCD;
+ break;
+ }
+
+ if ((s->intmskena & (1u << irq)) && level)
+ s->intsetclr |= 1u << irq;
+
+ if (oldint != (s->intsetclr & s->intmskena))
+ qemu_set_irq(s->parent, s->intsetclr & s->intmskena);
+}
+
+
+static uint64_t
+mst_fpga_readb(void *opaque, hwaddr addr, unsigned size)
+{
+ mst_irq_state *s = (mst_irq_state *) opaque;
+
+ switch (addr) {
+ case MST_LEDDAT1:
+ return s->leddat1;
+ case MST_LEDDAT2:
+ return s->leddat2;
+ case MST_LEDCTRL:
+ return s->ledctrl;
+ case MST_GPSWR:
+ return s->gpswr;
+ case MST_MSCWR1:
+ return s->mscwr1;
+ case MST_MSCWR2:
+ return s->mscwr2;
+ case MST_MSCWR3:
+ return s->mscwr3;
+ case MST_MSCRD:
+ return s->mscrd;
+ case MST_INTMSKENA:
+ return s->intmskena;
+ case MST_INTSETCLR:
+ return s->intsetclr;
+ case MST_PCMCIA0:
+ return s->pcmcia0;
+ case MST_PCMCIA1:
+ return s->pcmcia1;
+ default:
+ printf("Mainstone - mst_fpga_readb: Bad register offset "
+ "0x" TARGET_FMT_plx "\n", addr);
+ }
+ return 0;
+}
+
+static void
+mst_fpga_writeb(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ mst_irq_state *s = (mst_irq_state *) opaque;
+ value &= 0xffffffff;
+
+ switch (addr) {
+ case MST_LEDDAT1:
+ s->leddat1 = value;
+ break;
+ case MST_LEDDAT2:
+ s->leddat2 = value;
+ break;
+ case MST_LEDCTRL:
+ s->ledctrl = value;
+ break;
+ case MST_GPSWR:
+ s->gpswr = value;
+ break;
+ case MST_MSCWR1:
+ s->mscwr1 = value;
+ break;
+ case MST_MSCWR2:
+ s->mscwr2 = value;
+ break;
+ case MST_MSCWR3:
+ s->mscwr3 = value;
+ break;
+ case MST_MSCRD:
+ s->mscrd = value;
+ break;
+ case MST_INTMSKENA: /* Mask interrupt */
+ s->intmskena = (value & 0xFEEFF);
+ qemu_set_irq(s->parent, s->intsetclr & s->intmskena);
+ break;
+ case MST_INTSETCLR: /* clear or set interrupt */
+ s->intsetclr = (value & 0xFEEFF);
+ qemu_set_irq(s->parent, s->intsetclr & s->intmskena);
+ break;
+ /* For PCMCIAx allow the to change only power and reset */
+ case MST_PCMCIA0:
+ s->pcmcia0 = (value & 0x1f) | (s->pcmcia0 & ~0x1f);
+ break;
+ case MST_PCMCIA1:
+ s->pcmcia1 = (value & 0x1f) | (s->pcmcia1 & ~0x1f);
+ break;
+ default:
+ printf("Mainstone - mst_fpga_writeb: Bad register offset "
+ "0x" TARGET_FMT_plx "\n", addr);
+ }
+}
+
+static const MemoryRegionOps mst_fpga_ops = {
+ .read = mst_fpga_readb,
+ .write = mst_fpga_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int mst_fpga_post_load(void *opaque, int version_id)
+{
+ mst_irq_state *s = (mst_irq_state *) opaque;
+
+ qemu_set_irq(s->parent, s->intsetclr & s->intmskena);
+ return 0;
+}
+
+static int mst_fpga_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ mst_irq_state *s = MAINSTONE_FPGA(dev);
+
+ s->pcmcia0 = MST_PCMCIAx_READY | MST_PCMCIAx_nCD;
+ s->pcmcia1 = MST_PCMCIAx_READY | MST_PCMCIAx_nCD;
+
+ sysbus_init_irq(sbd, &s->parent);
+
+ /* alloc the external 16 irqs */
+ qdev_init_gpio_in(dev, mst_fpga_set_irq, MST_NUM_IRQS);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &mst_fpga_ops, s,
+ "fpga", 0x00100000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ return 0;
+}
+
+static VMStateDescription vmstate_mst_fpga_regs = {
+ .name = "mainstone_fpga",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = mst_fpga_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(prev_level, mst_irq_state),
+ VMSTATE_UINT32(leddat1, mst_irq_state),
+ VMSTATE_UINT32(leddat2, mst_irq_state),
+ VMSTATE_UINT32(ledctrl, mst_irq_state),
+ VMSTATE_UINT32(gpswr, mst_irq_state),
+ VMSTATE_UINT32(mscwr1, mst_irq_state),
+ VMSTATE_UINT32(mscwr2, mst_irq_state),
+ VMSTATE_UINT32(mscwr3, mst_irq_state),
+ VMSTATE_UINT32(mscrd, mst_irq_state),
+ VMSTATE_UINT32(intmskena, mst_irq_state),
+ VMSTATE_UINT32(intsetclr, mst_irq_state),
+ VMSTATE_UINT32(pcmcia0, mst_irq_state),
+ VMSTATE_UINT32(pcmcia1, mst_irq_state),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void mst_fpga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mst_fpga_init;
+ dc->desc = "Mainstone II FPGA";
+ dc->vmsd = &vmstate_mst_fpga_regs;
+}
+
+static const TypeInfo mst_fpga_info = {
+ .name = TYPE_MAINSTONE_FPGA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(mst_irq_state),
+ .class_init = mst_fpga_class_init,
+};
+
+static void mst_fpga_register_types(void)
+{
+ type_register_static(&mst_fpga_info);
+}
+
+type_init(mst_fpga_register_types)
diff --git a/hw/misc/omap_clk.c b/hw/misc/omap_clk.c
new file mode 100644
index 00000000..80a3c50e
--- /dev/null
+++ b/hw/misc/omap_clk.c
@@ -0,0 +1,1264 @@
+/*
+ * OMAP clocks.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * Clocks data comes in part from arch/arm/mach-omap1/clock.h in Linux.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+
+struct clk {
+ const char *name;
+ const char *alias;
+ struct clk *parent;
+ struct clk *child1;
+ struct clk *sibling;
+#define ALWAYS_ENABLED (1 << 0)
+#define CLOCK_IN_OMAP310 (1 << 10)
+#define CLOCK_IN_OMAP730 (1 << 11)
+#define CLOCK_IN_OMAP1510 (1 << 12)
+#define CLOCK_IN_OMAP16XX (1 << 13)
+#define CLOCK_IN_OMAP242X (1 << 14)
+#define CLOCK_IN_OMAP243X (1 << 15)
+#define CLOCK_IN_OMAP343X (1 << 16)
+ uint32_t flags;
+ int id;
+
+ int running; /* Is currently ticking */
+ int enabled; /* Is enabled, regardless of its input clk */
+ unsigned long rate; /* Current rate (if .running) */
+ unsigned int divisor; /* Rate relative to input (if .enabled) */
+ unsigned int multiplier; /* Rate relative to input (if .enabled) */
+ qemu_irq users[16]; /* Who to notify on change */
+ int usecount; /* Automatically idle when unused */
+};
+
+static struct clk xtal_osc12m = {
+ .name = "xtal_osc_12m",
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk xtal_osc32k = {
+ .name = "xtal_osc_32k",
+ .rate = 32768,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+};
+
+static struct clk ck_ref = {
+ .name = "ck_ref",
+ .alias = "clkin",
+ .parent = &xtal_osc12m,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+/* If a dpll is disabled it becomes a bypass, child clocks don't stop */
+static struct clk dpll1 = {
+ .name = "dpll1",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk dpll2 = {
+ .name = "dpll2",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk dpll3 = {
+ .name = "dpll3",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk dpll4 = {
+ .name = "dpll4",
+ .parent = &ck_ref,
+ .multiplier = 4,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk apll = {
+ .name = "apll",
+ .parent = &ck_ref,
+ .multiplier = 48,
+ .divisor = 12,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk ck_48m = {
+ .name = "ck_48m",
+ .parent = &dpll4, /* either dpll4 or apll */
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk ck_dpll1out = {
+ .name = "ck_dpll1out",
+ .parent = &dpll1,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk sossi_ck = {
+ .name = "ck_sossi",
+ .parent = &ck_dpll1out,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk clkm1 = {
+ .name = "clkm1",
+ .alias = "ck_gen1",
+ .parent = &dpll1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk clkm2 = {
+ .name = "clkm2",
+ .alias = "ck_gen2",
+ .parent = &dpll1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk clkm3 = {
+ .name = "clkm3",
+ .alias = "ck_gen3",
+ .parent = &dpll1, /* either dpll1 or ck_ref */
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk arm_ck = {
+ .name = "arm_ck",
+ .alias = "mpu_ck",
+ .parent = &clkm1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk armper_ck = {
+ .name = "armper_ck",
+ .alias = "mpuper_ck",
+ .parent = &clkm1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk arm_gpio_ck = {
+ .name = "arm_gpio_ck",
+ .alias = "mpu_gpio_ck",
+ .parent = &clkm1,
+ .divisor = 1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk armxor_ck = {
+ .name = "armxor_ck",
+ .alias = "mpuxor_ck",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk armtim_ck = {
+ .name = "armtim_ck",
+ .alias = "mputim_ck",
+ .parent = &ck_ref, /* either CLKIN or DPLL1 */
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk armwdt_ck = {
+ .name = "armwdt_ck",
+ .alias = "mpuwd_ck",
+ .parent = &clkm1,
+ .divisor = 14,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk arminth_ck16xx = {
+ .name = "arminth_ck",
+ .parent = &arm_ck,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+ /* Note: On 16xx the frequency can be divided by 2 by programming
+ * ARM_CKCTL:ARM_INTHCK_SEL(14) to 1
+ *
+ * 1510 version is in TC clocks.
+ */
+};
+
+static struct clk dsp_ck = {
+ .name = "dsp_ck",
+ .parent = &clkm2,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk dspmmu_ck = {
+ .name = "dspmmu_ck",
+ .parent = &clkm2,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
+ ALWAYS_ENABLED,
+};
+
+static struct clk dspper_ck = {
+ .name = "dspper_ck",
+ .parent = &clkm2,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk dspxor_ck = {
+ .name = "dspxor_ck",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk dsptim_ck = {
+ .name = "dsptim_ck",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk tc_ck = {
+ .name = "tc_ck",
+ .parent = &clkm3,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
+ CLOCK_IN_OMAP730 | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk arminth_ck15xx = {
+ .name = "arminth_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+ /* Note: On 1510 the frequency follows TC_CK
+ *
+ * 16xx version is in MPU clocks.
+ */
+};
+
+static struct clk tipb_ck = {
+ /* No-idle controlled by "tc_ck" */
+ .name = "tipb_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk l3_ocpi_ck = {
+ /* No-idle controlled by "tc_ck" */
+ .name = "l3_ocpi_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk tc1_ck = {
+ .name = "tc1_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk tc2_ck = {
+ .name = "tc2_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk dma_ck = {
+ /* No-idle controlled by "tc_ck" */
+ .name = "dma_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk dma_lcdfree_ck = {
+ .name = "dma_lcdfree_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+};
+
+static struct clk api_ck = {
+ .name = "api_ck",
+ .alias = "mpui_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk lb_ck = {
+ .name = "lb_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk lbfree_ck = {
+ .name = "lbfree_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk hsab_ck = {
+ .name = "hsab_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk rhea1_ck = {
+ .name = "rhea1_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+};
+
+static struct clk rhea2_ck = {
+ .name = "rhea2_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+};
+
+static struct clk lcd_ck_16xx = {
+ .name = "lcd_ck",
+ .parent = &clkm3,
+ .flags = CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP730,
+};
+
+static struct clk lcd_ck_1510 = {
+ .name = "lcd_ck",
+ .parent = &clkm3,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk uart1_1510 = {
+ .name = "uart1_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck, /* either armper_ck or dpll4 */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk uart1_16xx = {
+ .name = "uart1_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck,
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk uart2_ck = {
+ .name = "uart2_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck, /* either armper_ck or dpll4 */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk uart3_1510 = {
+ .name = "uart3_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck, /* either armper_ck or dpll4 */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk uart3_16xx = {
+ .name = "uart3_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck,
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk usb_clk0 = { /* 6 MHz output on W4_USB_CLK0 */
+ .name = "usb_clk0",
+ .alias = "usb.clko",
+ /* Direct from ULPD, no parent */
+ .rate = 6000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk usb_hhc_ck1510 = {
+ .name = "usb_hhc_ck",
+ /* Direct from ULPD, no parent */
+ .rate = 48000000, /* Actually 2 clocks, 12MHz and 48MHz */
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk usb_hhc_ck16xx = {
+ .name = "usb_hhc_ck",
+ /* Direct from ULPD, no parent */
+ .rate = 48000000,
+ /* OTG_SYSCON_2.OTG_PADEN == 0 (not 1510-compatible) */
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk usb_w2fc_mclk = {
+ .name = "usb_w2fc_mclk",
+ .alias = "usb_w2fc_ck",
+ .parent = &ck_48m,
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk mclk_1510 = {
+ .name = "mclk",
+ /* Direct from ULPD, no parent. May be enabled by ext hardware. */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510,
+};
+
+static struct clk bclk_310 = {
+ .name = "bt_mclk_out", /* Alias midi_mclk_out? */
+ .parent = &armper_ck,
+ .flags = CLOCK_IN_OMAP310,
+};
+
+static struct clk mclk_310 = {
+ .name = "com_mclk_out",
+ .parent = &armper_ck,
+ .flags = CLOCK_IN_OMAP310,
+};
+
+static struct clk mclk_16xx = {
+ .name = "mclk",
+ /* Direct from ULPD, no parent. May be enabled by ext hardware. */
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk bclk_1510 = {
+ .name = "bclk",
+ /* Direct from ULPD, no parent. May be enabled by ext hardware. */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510,
+};
+
+static struct clk bclk_16xx = {
+ .name = "bclk",
+ /* Direct from ULPD, no parent. May be enabled by ext hardware. */
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk mmc1_ck = {
+ .name = "mmc_ck",
+ .id = 1,
+ /* Functional clock is direct from ULPD, interface clock is ARMPER */
+ .parent = &armper_ck, /* either armper_ck or dpll4 */
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk mmc2_ck = {
+ .name = "mmc_ck",
+ .id = 2,
+ /* Functional clock is direct from ULPD, interface clock is ARMPER */
+ .parent = &armper_ck,
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk cam_mclk = {
+ .name = "cam.mclk",
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+ .rate = 12000000,
+};
+
+static struct clk cam_exclk = {
+ .name = "cam.exclk",
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+ /* Either 12M from cam.mclk or 48M from dpll4 */
+ .parent = &cam_mclk,
+};
+
+static struct clk cam_lclk = {
+ .name = "cam.lclk",
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk i2c_fck = {
+ .name = "i2c_fck",
+ .id = 1,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
+ ALWAYS_ENABLED,
+ .parent = &armxor_ck,
+};
+
+static struct clk i2c_ick = {
+ .name = "i2c_ick",
+ .id = 1,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+ .parent = &armper_ck,
+};
+
+static struct clk clk32k = {
+ .name = "clk32-kHz",
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
+ CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .parent = &xtal_osc32k,
+};
+
+static struct clk ref_clk = {
+ .name = "ref_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 12000000, /* 12 MHz or 13 MHz or 19.2 MHz */
+ /*.parent = sys.xtalin */
+};
+
+static struct clk apll_96m = {
+ .name = "apll_96m",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 96000000,
+ /*.parent = ref_clk */
+};
+
+static struct clk apll_54m = {
+ .name = "apll_54m",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 54000000,
+ /*.parent = ref_clk */
+};
+
+static struct clk sys_clk = {
+ .name = "sys_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 32768,
+ /*.parent = sys.xtalin */
+};
+
+static struct clk sleep_clk = {
+ .name = "sleep_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 32768,
+ /*.parent = sys.xtalin */
+};
+
+static struct clk dpll_ck = {
+ .name = "dpll",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .parent = &ref_clk,
+};
+
+static struct clk dpll_x2_ck = {
+ .name = "dpll_x2",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .parent = &ref_clk,
+};
+
+static struct clk wdt1_sys_clk = {
+ .name = "wdt1_sys_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 32768,
+ /*.parent = sys.xtalin */
+};
+
+static struct clk func_96m_clk = {
+ .name = "func_96m_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 1,
+ .parent = &apll_96m,
+};
+
+static struct clk func_48m_clk = {
+ .name = "func_48m_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 2,
+ .parent = &apll_96m,
+};
+
+static struct clk func_12m_clk = {
+ .name = "func_12m_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 8,
+ .parent = &apll_96m,
+};
+
+static struct clk func_54m_clk = {
+ .name = "func_54m_clk",
+ .flags = CLOCK_IN_OMAP242X,
+ .divisor = 1,
+ .parent = &apll_54m,
+};
+
+static struct clk sys_clkout = {
+ .name = "clkout",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk sys_clkout2 = {
+ .name = "clkout2",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_clk = {
+ .name = "core_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &dpll_x2_ck, /* Switchable between dpll_ck and clk32k */
+};
+
+static struct clk l3_clk = {
+ .name = "l3_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk core_l4_iclk = {
+ .name = "core_l4_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &l3_clk,
+};
+
+static struct clk wu_l4_iclk = {
+ .name = "wu_l4_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &l3_clk,
+};
+
+static struct clk core_l3_iclk = {
+ .name = "core_l3_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk core_l4_usb_clk = {
+ .name = "core_l4_usb_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &l3_clk,
+};
+
+static struct clk wu_gpt1_clk = {
+ .name = "wu_gpt1_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk wu_32k_clk = {
+ .name = "wu_32k_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk uart1_fclk = {
+ .name = "uart1_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+};
+
+static struct clk uart1_iclk = {
+ .name = "uart1_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk uart2_fclk = {
+ .name = "uart2_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+};
+
+static struct clk uart2_iclk = {
+ .name = "uart2_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk uart3_fclk = {
+ .name = "uart3_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+};
+
+static struct clk uart3_iclk = {
+ .name = "uart3_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk mpu_fclk = {
+ .name = "mpu_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk mpu_iclk = {
+ .name = "mpu_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk int_m_fclk = {
+ .name = "int_m_fclk",
+ .alias = "mpu_intc_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk int_m_iclk = {
+ .name = "int_m_iclk",
+ .alias = "mpu_intc_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk core_gpt2_clk = {
+ .name = "core_gpt2_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt3_clk = {
+ .name = "core_gpt3_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt4_clk = {
+ .name = "core_gpt4_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt5_clk = {
+ .name = "core_gpt5_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt6_clk = {
+ .name = "core_gpt6_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt7_clk = {
+ .name = "core_gpt7_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt8_clk = {
+ .name = "core_gpt8_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt9_clk = {
+ .name = "core_gpt9_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt10_clk = {
+ .name = "core_gpt10_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt11_clk = {
+ .name = "core_gpt11_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt12_clk = {
+ .name = "core_gpt12_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk mcbsp1_clk = {
+ .name = "mcbsp1_cg",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 2,
+ .parent = &func_96m_clk,
+};
+
+static struct clk mcbsp2_clk = {
+ .name = "mcbsp2_cg",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 2,
+ .parent = &func_96m_clk,
+};
+
+static struct clk emul_clk = {
+ .name = "emul_ck",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_54m_clk,
+};
+
+static struct clk sdma_fclk = {
+ .name = "sdma_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &l3_clk,
+};
+
+static struct clk sdma_iclk = {
+ .name = "sdma_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l3_iclk, /* core_l4_iclk for the configuration port */
+};
+
+static struct clk i2c1_fclk = {
+ .name = "i2c1.fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_12m_clk,
+ .divisor = 1,
+};
+
+static struct clk i2c1_iclk = {
+ .name = "i2c1.iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk i2c2_fclk = {
+ .name = "i2c2.fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_12m_clk,
+ .divisor = 1,
+};
+
+static struct clk i2c2_iclk = {
+ .name = "i2c2.iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk gpio_dbclk[5] = {
+ {
+ .name = "gpio1_dbclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ }, {
+ .name = "gpio2_dbclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ }, {
+ .name = "gpio3_dbclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ }, {
+ .name = "gpio4_dbclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ }, {
+ .name = "gpio5_dbclk",
+ .flags = CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ },
+};
+
+static struct clk gpio_iclk = {
+ .name = "gpio_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_l4_iclk,
+};
+
+static struct clk mmc_fck = {
+ .name = "mmc_fclk",
+ .flags = CLOCK_IN_OMAP242X,
+ .parent = &func_96m_clk,
+};
+
+static struct clk mmc_ick = {
+ .name = "mmc_iclk",
+ .flags = CLOCK_IN_OMAP242X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk spi_fclk[3] = {
+ {
+ .name = "spi1_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+ }, {
+ .name = "spi2_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+ }, {
+ .name = "spi3_fclk",
+ .flags = CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+ },
+};
+
+static struct clk dss_clk[2] = {
+ {
+ .name = "dss_clk1",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+ }, {
+ .name = "dss_clk2",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+ },
+};
+
+static struct clk dss_54m_clk = {
+ .name = "dss_54m_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_54m_clk,
+};
+
+static struct clk dss_l3_iclk = {
+ .name = "dss_l3_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l3_iclk,
+};
+
+static struct clk dss_l4_iclk = {
+ .name = "dss_l4_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk spi_iclk[3] = {
+ {
+ .name = "spi1_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+ }, {
+ .name = "spi2_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+ }, {
+ .name = "spi3_iclk",
+ .flags = CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+ },
+};
+
+static struct clk omapctrl_clk = {
+ .name = "omapctrl_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ /* XXX Should be in WKUP domain */
+ .parent = &core_l4_iclk,
+};
+
+static struct clk *onchip_clks[] = {
+ /* OMAP 1 */
+
+ /* non-ULPD clocks */
+ &xtal_osc12m,
+ &xtal_osc32k,
+ &ck_ref,
+ &dpll1,
+ &dpll2,
+ &dpll3,
+ &dpll4,
+ &apll,
+ &ck_48m,
+ /* CK_GEN1 clocks */
+ &clkm1,
+ &ck_dpll1out,
+ &sossi_ck,
+ &arm_ck,
+ &armper_ck,
+ &arm_gpio_ck,
+ &armxor_ck,
+ &armtim_ck,
+ &armwdt_ck,
+ &arminth_ck15xx, &arminth_ck16xx,
+ /* CK_GEN2 clocks */
+ &clkm2,
+ &dsp_ck,
+ &dspmmu_ck,
+ &dspper_ck,
+ &dspxor_ck,
+ &dsptim_ck,
+ /* CK_GEN3 clocks */
+ &clkm3,
+ &tc_ck,
+ &tipb_ck,
+ &l3_ocpi_ck,
+ &tc1_ck,
+ &tc2_ck,
+ &dma_ck,
+ &dma_lcdfree_ck,
+ &api_ck,
+ &lb_ck,
+ &lbfree_ck,
+ &hsab_ck,
+ &rhea1_ck,
+ &rhea2_ck,
+ &lcd_ck_16xx,
+ &lcd_ck_1510,
+ /* ULPD clocks */
+ &uart1_1510,
+ &uart1_16xx,
+ &uart2_ck,
+ &uart3_1510,
+ &uart3_16xx,
+ &usb_clk0,
+ &usb_hhc_ck1510, &usb_hhc_ck16xx,
+ &mclk_1510, &mclk_16xx, &mclk_310,
+ &bclk_1510, &bclk_16xx, &bclk_310,
+ &mmc1_ck,
+ &mmc2_ck,
+ &cam_mclk,
+ &cam_exclk,
+ &cam_lclk,
+ &clk32k,
+ &usb_w2fc_mclk,
+ /* Virtual clocks */
+ &i2c_fck,
+ &i2c_ick,
+
+ /* OMAP 2 */
+
+ &ref_clk,
+ &apll_96m,
+ &apll_54m,
+ &sys_clk,
+ &sleep_clk,
+ &dpll_ck,
+ &dpll_x2_ck,
+ &wdt1_sys_clk,
+ &func_96m_clk,
+ &func_48m_clk,
+ &func_12m_clk,
+ &func_54m_clk,
+ &sys_clkout,
+ &sys_clkout2,
+ &core_clk,
+ &l3_clk,
+ &core_l4_iclk,
+ &wu_l4_iclk,
+ &core_l3_iclk,
+ &core_l4_usb_clk,
+ &wu_gpt1_clk,
+ &wu_32k_clk,
+ &uart1_fclk,
+ &uart1_iclk,
+ &uart2_fclk,
+ &uart2_iclk,
+ &uart3_fclk,
+ &uart3_iclk,
+ &mpu_fclk,
+ &mpu_iclk,
+ &int_m_fclk,
+ &int_m_iclk,
+ &core_gpt2_clk,
+ &core_gpt3_clk,
+ &core_gpt4_clk,
+ &core_gpt5_clk,
+ &core_gpt6_clk,
+ &core_gpt7_clk,
+ &core_gpt8_clk,
+ &core_gpt9_clk,
+ &core_gpt10_clk,
+ &core_gpt11_clk,
+ &core_gpt12_clk,
+ &mcbsp1_clk,
+ &mcbsp2_clk,
+ &emul_clk,
+ &sdma_fclk,
+ &sdma_iclk,
+ &i2c1_fclk,
+ &i2c1_iclk,
+ &i2c2_fclk,
+ &i2c2_iclk,
+ &gpio_dbclk[0],
+ &gpio_dbclk[1],
+ &gpio_dbclk[2],
+ &gpio_dbclk[3],
+ &gpio_iclk,
+ &mmc_fck,
+ &mmc_ick,
+ &spi_fclk[0],
+ &spi_iclk[0],
+ &spi_fclk[1],
+ &spi_iclk[1],
+ &spi_fclk[2],
+ &spi_iclk[2],
+ &dss_clk[0],
+ &dss_clk[1],
+ &dss_54m_clk,
+ &dss_l3_iclk,
+ &dss_l4_iclk,
+ &omapctrl_clk,
+
+ NULL
+};
+
+void omap_clk_adduser(struct clk *clk, qemu_irq user)
+{
+ qemu_irq *i;
+
+ for (i = clk->users; *i; i ++);
+ *i = user;
+}
+
+struct clk *omap_findclk(struct omap_mpu_state_s *mpu, const char *name)
+{
+ struct clk *i;
+
+ for (i = mpu->clks; i->name; i ++)
+ if (!strcmp(i->name, name) || (i->alias && !strcmp(i->alias, name)))
+ return i;
+ hw_error("%s: %s not found\n", __FUNCTION__, name);
+}
+
+void omap_clk_get(struct clk *clk)
+{
+ clk->usecount ++;
+}
+
+void omap_clk_put(struct clk *clk)
+{
+ if (!(clk->usecount --))
+ hw_error("%s: %s is not in use\n", __FUNCTION__, clk->name);
+}
+
+static void omap_clk_update(struct clk *clk)
+{
+ int parent, running;
+ qemu_irq *user;
+ struct clk *i;
+
+ if (clk->parent)
+ parent = clk->parent->running;
+ else
+ parent = 1;
+
+ running = parent && (clk->enabled ||
+ ((clk->flags & ALWAYS_ENABLED) && clk->usecount));
+ if (clk->running != running) {
+ clk->running = running;
+ for (user = clk->users; *user; user ++)
+ qemu_set_irq(*user, running);
+ for (i = clk->child1; i; i = i->sibling)
+ omap_clk_update(i);
+ }
+}
+
+static void omap_clk_rate_update_full(struct clk *clk, unsigned long int rate,
+ unsigned long int div, unsigned long int mult)
+{
+ struct clk *i;
+ qemu_irq *user;
+
+ clk->rate = muldiv64(rate, mult, div);
+ if (clk->running)
+ for (user = clk->users; *user; user ++)
+ qemu_irq_raise(*user);
+ for (i = clk->child1; i; i = i->sibling)
+ omap_clk_rate_update_full(i, rate,
+ div * i->divisor, mult * i->multiplier);
+}
+
+static void omap_clk_rate_update(struct clk *clk)
+{
+ struct clk *i;
+ unsigned long int div, mult = div = 1;
+
+ for (i = clk; i->parent; i = i->parent) {
+ div *= i->divisor;
+ mult *= i->multiplier;
+ }
+
+ omap_clk_rate_update_full(clk, i->rate, div, mult);
+}
+
+void omap_clk_reparent(struct clk *clk, struct clk *parent)
+{
+ struct clk **p;
+
+ if (clk->parent) {
+ for (p = &clk->parent->child1; *p != clk; p = &(*p)->sibling);
+ *p = clk->sibling;
+ }
+
+ clk->parent = parent;
+ if (parent) {
+ clk->sibling = parent->child1;
+ parent->child1 = clk;
+ omap_clk_update(clk);
+ omap_clk_rate_update(clk);
+ } else
+ clk->sibling = NULL;
+}
+
+void omap_clk_onoff(struct clk *clk, int on)
+{
+ clk->enabled = on;
+ omap_clk_update(clk);
+}
+
+void omap_clk_canidle(struct clk *clk, int can)
+{
+ if (can)
+ omap_clk_put(clk);
+ else
+ omap_clk_get(clk);
+}
+
+void omap_clk_setrate(struct clk *clk, int divide, int multiply)
+{
+ clk->divisor = divide;
+ clk->multiplier = multiply;
+ omap_clk_rate_update(clk);
+}
+
+int64_t omap_clk_getrate(omap_clk clk)
+{
+ return clk->rate;
+}
+
+void omap_clk_init(struct omap_mpu_state_s *mpu)
+{
+ struct clk **i, *j, *k;
+ int count;
+ int flag;
+
+ if (cpu_is_omap310(mpu))
+ flag = CLOCK_IN_OMAP310;
+ else if (cpu_is_omap1510(mpu))
+ flag = CLOCK_IN_OMAP1510;
+ else if (cpu_is_omap2410(mpu) || cpu_is_omap2420(mpu))
+ flag = CLOCK_IN_OMAP242X;
+ else if (cpu_is_omap2430(mpu))
+ flag = CLOCK_IN_OMAP243X;
+ else if (cpu_is_omap3430(mpu))
+ flag = CLOCK_IN_OMAP243X;
+ else
+ return;
+
+ for (i = onchip_clks, count = 0; *i; i ++)
+ if ((*i)->flags & flag)
+ count ++;
+ mpu->clks = (struct clk *) g_malloc0(sizeof(struct clk) * (count + 1));
+ for (i = onchip_clks, j = mpu->clks; *i; i ++)
+ if ((*i)->flags & flag) {
+ memcpy(j, *i, sizeof(struct clk));
+ for (k = mpu->clks; k < j; k ++)
+ if (j->parent && !strcmp(j->parent->name, k->name)) {
+ j->parent = k;
+ j->sibling = k->child1;
+ k->child1 = j;
+ } else if (k->parent && !strcmp(k->parent->name, j->name)) {
+ k->parent = j;
+ k->sibling = j->child1;
+ j->child1 = k;
+ }
+ j->divisor = j->divisor ?: 1;
+ j->multiplier = j->multiplier ?: 1;
+ j ++;
+ }
+ for (j = mpu->clks; count --; j ++) {
+ omap_clk_update(j);
+ omap_clk_rate_update(j);
+ }
+}
diff --git a/hw/misc/omap_gpmc.c b/hw/misc/omap_gpmc.c
new file mode 100644
index 00000000..74fc91c8
--- /dev/null
+++ b/hw/misc/omap_gpmc.c
@@ -0,0 +1,897 @@
+/*
+ * TI OMAP general purpose memory controller emulation.
+ *
+ * Copyright (C) 2007-2009 Nokia Corporation
+ * Original code written by Andrzej Zaborowski <andrew@openedhand.com>
+ * Enhancements for OMAP3 and NAND support written by Juha Riihimäki
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/block/flash.h"
+#include "hw/arm/omap.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+
+/* General-Purpose Memory Controller */
+struct omap_gpmc_s {
+ qemu_irq irq;
+ qemu_irq drq;
+ MemoryRegion iomem;
+ int accept_256;
+
+ uint8_t revision;
+ uint8_t sysconfig;
+ uint16_t irqst;
+ uint16_t irqen;
+ uint16_t lastirq;
+ uint16_t timeout;
+ uint16_t config;
+ struct omap_gpmc_cs_file_s {
+ uint32_t config[7];
+ MemoryRegion *iomem;
+ MemoryRegion container;
+ MemoryRegion nandiomem;
+ DeviceState *dev;
+ } cs_file[8];
+ int ecc_cs;
+ int ecc_ptr;
+ uint32_t ecc_cfg;
+ ECCState ecc[9];
+ struct prefetch {
+ uint32_t config1; /* GPMC_PREFETCH_CONFIG1 */
+ uint32_t transfercount; /* GPMC_PREFETCH_CONFIG2:TRANSFERCOUNT */
+ int startengine; /* GPMC_PREFETCH_CONTROL:STARTENGINE */
+ int fifopointer; /* GPMC_PREFETCH_STATUS:FIFOPOINTER */
+ int count; /* GPMC_PREFETCH_STATUS:COUNTVALUE */
+ MemoryRegion iomem;
+ uint8_t fifo[64];
+ } prefetch;
+};
+
+#define OMAP_GPMC_8BIT 0
+#define OMAP_GPMC_16BIT 1
+#define OMAP_GPMC_NOR 0
+#define OMAP_GPMC_NAND 2
+
+static int omap_gpmc_devtype(struct omap_gpmc_cs_file_s *f)
+{
+ return (f->config[0] >> 10) & 3;
+}
+
+static int omap_gpmc_devsize(struct omap_gpmc_cs_file_s *f)
+{
+ /* devsize field is really 2 bits but we ignore the high
+ * bit to ensure consistent behaviour if the guest sets
+ * it (values 2 and 3 are reserved in the TRM)
+ */
+ return (f->config[0] >> 12) & 1;
+}
+
+/* Extract the chip-select value from the prefetch config1 register */
+static int prefetch_cs(uint32_t config1)
+{
+ return (config1 >> 24) & 7;
+}
+
+static int prefetch_threshold(uint32_t config1)
+{
+ return (config1 >> 8) & 0x7f;
+}
+
+static void omap_gpmc_int_update(struct omap_gpmc_s *s)
+{
+ /* The TRM is a bit unclear, but it seems to say that
+ * the TERMINALCOUNTSTATUS bit is set only on the
+ * transition when the prefetch engine goes from
+ * active to inactive, whereas the FIFOEVENTSTATUS
+ * bit is held high as long as the fifo has at
+ * least THRESHOLD bytes available.
+ * So we do the latter here, but TERMINALCOUNTSTATUS
+ * is set elsewhere.
+ */
+ if (s->prefetch.fifopointer >= prefetch_threshold(s->prefetch.config1)) {
+ s->irqst |= 1;
+ }
+ if ((s->irqen & s->irqst) != s->lastirq) {
+ s->lastirq = s->irqen & s->irqst;
+ qemu_set_irq(s->irq, s->lastirq);
+ }
+}
+
+static void omap_gpmc_dma_update(struct omap_gpmc_s *s, int value)
+{
+ if (s->prefetch.config1 & 4) {
+ qemu_set_irq(s->drq, value);
+ }
+}
+
+/* Access functions for when a NAND-like device is mapped into memory:
+ * all addresses in the region behave like accesses to the relevant
+ * GPMC_NAND_DATA_i register (which is actually implemented to call these)
+ */
+static uint64_t omap_nand_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_gpmc_cs_file_s *f = (struct omap_gpmc_cs_file_s *)opaque;
+ uint64_t v;
+ nand_setpins(f->dev, 0, 0, 0, 1, 0);
+ switch (omap_gpmc_devsize(f)) {
+ case OMAP_GPMC_8BIT:
+ v = nand_getio(f->dev);
+ if (size == 1) {
+ return v;
+ }
+ v |= (nand_getio(f->dev) << 8);
+ if (size == 2) {
+ return v;
+ }
+ v |= (nand_getio(f->dev) << 16);
+ v |= (nand_getio(f->dev) << 24);
+ return v;
+ case OMAP_GPMC_16BIT:
+ v = nand_getio(f->dev);
+ if (size == 1) {
+ /* 8 bit read from 16 bit device : probably a guest bug */
+ return v & 0xff;
+ }
+ if (size == 2) {
+ return v;
+ }
+ v |= (nand_getio(f->dev) << 16);
+ return v;
+ default:
+ abort();
+ }
+}
+
+static void omap_nand_setio(DeviceState *dev, uint64_t value,
+ int nandsize, int size)
+{
+ /* Write the specified value to the NAND device, respecting
+ * both size of the NAND device and size of the write access.
+ */
+ switch (nandsize) {
+ case OMAP_GPMC_8BIT:
+ switch (size) {
+ case 1:
+ nand_setio(dev, value & 0xff);
+ break;
+ case 2:
+ nand_setio(dev, value & 0xff);
+ nand_setio(dev, (value >> 8) & 0xff);
+ break;
+ case 4:
+ default:
+ nand_setio(dev, value & 0xff);
+ nand_setio(dev, (value >> 8) & 0xff);
+ nand_setio(dev, (value >> 16) & 0xff);
+ nand_setio(dev, (value >> 24) & 0xff);
+ break;
+ }
+ break;
+ case OMAP_GPMC_16BIT:
+ switch (size) {
+ case 1:
+ /* writing to a 16bit device with 8bit access is probably a guest
+ * bug; pass the value through anyway.
+ */
+ case 2:
+ nand_setio(dev, value & 0xffff);
+ break;
+ case 4:
+ default:
+ nand_setio(dev, value & 0xffff);
+ nand_setio(dev, (value >> 16) & 0xffff);
+ break;
+ }
+ break;
+ }
+}
+
+static void omap_nand_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_gpmc_cs_file_s *f = (struct omap_gpmc_cs_file_s *)opaque;
+ nand_setpins(f->dev, 0, 0, 0, 1, 0);
+ omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size);
+}
+
+static const MemoryRegionOps omap_nand_ops = {
+ .read = omap_nand_read,
+ .write = omap_nand_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void fill_prefetch_fifo(struct omap_gpmc_s *s)
+{
+ /* Fill the prefetch FIFO by reading data from NAND.
+ * We do this synchronously, unlike the hardware which
+ * will do this asynchronously. We refill when the
+ * FIFO has THRESHOLD bytes free, and we always refill
+ * as much data as possible starting at the top end
+ * of the FIFO.
+ * (We have to refill at THRESHOLD rather than waiting
+ * for the FIFO to empty to allow for the case where
+ * the FIFO size isn't an exact multiple of THRESHOLD
+ * and we're doing DMA transfers.)
+ * This means we never need to handle wrap-around in
+ * the fifo-reading code, and the next byte of data
+ * to read is always fifo[63 - fifopointer].
+ */
+ int fptr;
+ int cs = prefetch_cs(s->prefetch.config1);
+ int is16bit = (((s->cs_file[cs].config[0] >> 12) & 3) != 0);
+ int bytes;
+ /* Don't believe the bit of the OMAP TRM that says that COUNTVALUE
+ * and TRANSFERCOUNT are in units of 16 bit words for 16 bit NAND.
+ * Instead believe the bit that says it is always a byte count.
+ */
+ bytes = 64 - s->prefetch.fifopointer;
+ if (bytes > s->prefetch.count) {
+ bytes = s->prefetch.count;
+ }
+ if (is16bit) {
+ bytes &= ~1;
+ }
+
+ s->prefetch.count -= bytes;
+ s->prefetch.fifopointer += bytes;
+ fptr = 64 - s->prefetch.fifopointer;
+ /* Move the existing data in the FIFO so it sits just
+ * before what we're about to read in
+ */
+ while (fptr < (64 - bytes)) {
+ s->prefetch.fifo[fptr] = s->prefetch.fifo[fptr + bytes];
+ fptr++;
+ }
+ while (fptr < 64) {
+ if (is16bit) {
+ uint32_t v = omap_nand_read(&s->cs_file[cs], 0, 2);
+ s->prefetch.fifo[fptr++] = v & 0xff;
+ s->prefetch.fifo[fptr++] = (v >> 8) & 0xff;
+ } else {
+ s->prefetch.fifo[fptr++] = omap_nand_read(&s->cs_file[cs], 0, 1);
+ }
+ }
+ if (s->prefetch.startengine && (s->prefetch.count == 0)) {
+ /* This was the final transfer: raise TERMINALCOUNTSTATUS */
+ s->irqst |= 2;
+ s->prefetch.startengine = 0;
+ }
+ /* If there are any bytes in the FIFO at this point then
+ * we must raise a DMA request (either this is a final part
+ * transfer, or we filled the FIFO in which case we certainly
+ * have THRESHOLD bytes available)
+ */
+ if (s->prefetch.fifopointer != 0) {
+ omap_gpmc_dma_update(s, 1);
+ }
+ omap_gpmc_int_update(s);
+}
+
+/* Access functions for a NAND-like device when the prefetch/postwrite
+ * engine is enabled -- all addresses in the region behave alike:
+ * data is read or written to the FIFO.
+ */
+static uint64_t omap_gpmc_prefetch_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+ uint32_t data;
+ if (s->prefetch.config1 & 1) {
+ /* The TRM doesn't define the behaviour if you read from the
+ * FIFO when the prefetch engine is in write mode. We choose
+ * to always return zero.
+ */
+ return 0;
+ }
+ /* Note that trying to read an empty fifo repeats the last byte */
+ if (s->prefetch.fifopointer) {
+ s->prefetch.fifopointer--;
+ }
+ data = s->prefetch.fifo[63 - s->prefetch.fifopointer];
+ if (s->prefetch.fifopointer ==
+ (64 - prefetch_threshold(s->prefetch.config1))) {
+ /* We've drained THRESHOLD bytes now. So deassert the
+ * DMA request, then refill the FIFO (which will probably
+ * assert it again.)
+ */
+ omap_gpmc_dma_update(s, 0);
+ fill_prefetch_fifo(s);
+ }
+ omap_gpmc_int_update(s);
+ return data;
+}
+
+static void omap_gpmc_prefetch_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+ int cs = prefetch_cs(s->prefetch.config1);
+ if ((s->prefetch.config1 & 1) == 0) {
+ /* The TRM doesn't define the behaviour of writing to the
+ * FIFO when the prefetch engine is in read mode. We
+ * choose to ignore the write.
+ */
+ return;
+ }
+ if (s->prefetch.count == 0) {
+ /* The TRM doesn't define the behaviour of writing to the
+ * FIFO if the transfer is complete. We choose to ignore.
+ */
+ return;
+ }
+ /* The only reason we do any data buffering in postwrite
+ * mode is if we are talking to a 16 bit NAND device, in
+ * which case we need to buffer the first byte of the
+ * 16 bit word until the other byte arrives.
+ */
+ int is16bit = (((s->cs_file[cs].config[0] >> 12) & 3) != 0);
+ if (is16bit) {
+ /* fifopointer alternates between 64 (waiting for first
+ * byte of word) and 63 (waiting for second byte)
+ */
+ if (s->prefetch.fifopointer == 64) {
+ s->prefetch.fifo[0] = value;
+ s->prefetch.fifopointer--;
+ } else {
+ value = (value << 8) | s->prefetch.fifo[0];
+ omap_nand_write(&s->cs_file[cs], 0, value, 2);
+ s->prefetch.count--;
+ s->prefetch.fifopointer = 64;
+ }
+ } else {
+ /* Just write the byte : fifopointer remains 64 at all times */
+ omap_nand_write(&s->cs_file[cs], 0, value, 1);
+ s->prefetch.count--;
+ }
+ if (s->prefetch.count == 0) {
+ /* Final transfer: raise TERMINALCOUNTSTATUS */
+ s->irqst |= 2;
+ s->prefetch.startengine = 0;
+ }
+ omap_gpmc_int_update(s);
+}
+
+static const MemoryRegionOps omap_prefetch_ops = {
+ .read = omap_gpmc_prefetch_read,
+ .write = omap_gpmc_prefetch_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+};
+
+static MemoryRegion *omap_gpmc_cs_memregion(struct omap_gpmc_s *s, int cs)
+{
+ /* Return the MemoryRegion* to map/unmap for this chipselect */
+ struct omap_gpmc_cs_file_s *f = &s->cs_file[cs];
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NOR) {
+ return f->iomem;
+ }
+ if ((s->prefetch.config1 & 0x80) &&
+ (prefetch_cs(s->prefetch.config1) == cs)) {
+ /* The prefetch engine is enabled for this CS: map the FIFO */
+ return &s->prefetch.iomem;
+ }
+ return &f->nandiomem;
+}
+
+static void omap_gpmc_cs_map(struct omap_gpmc_s *s, int cs)
+{
+ struct omap_gpmc_cs_file_s *f = &s->cs_file[cs];
+ uint32_t mask = (f->config[6] >> 8) & 0xf;
+ uint32_t base = f->config[6] & 0x3f;
+ uint32_t size;
+
+ if (!f->iomem && !f->dev) {
+ return;
+ }
+
+ if (!(f->config[6] & (1 << 6))) {
+ /* Do nothing unless CSVALID */
+ return;
+ }
+
+ /* TODO: check for overlapping regions and report access errors */
+ if (mask != 0x8 && mask != 0xc && mask != 0xe && mask != 0xf
+ && !(s->accept_256 && !mask)) {
+ fprintf(stderr, "%s: invalid chip-select mask address (0x%x)\n",
+ __func__, mask);
+ }
+
+ base <<= 24;
+ size = (0x0fffffff & ~(mask << 24)) + 1;
+ /* TODO: rather than setting the size of the mapping (which should be
+ * constant), the mask should cause wrapping of the address space, so
+ * that the same memory becomes accessible at every <i>size</i> bytes
+ * starting from <i>base</i>. */
+ memory_region_init(&f->container, NULL, "omap-gpmc-file", size);
+ memory_region_add_subregion(&f->container, 0,
+ omap_gpmc_cs_memregion(s, cs));
+ memory_region_add_subregion(get_system_memory(), base,
+ &f->container);
+}
+
+static void omap_gpmc_cs_unmap(struct omap_gpmc_s *s, int cs)
+{
+ struct omap_gpmc_cs_file_s *f = &s->cs_file[cs];
+ if (!(f->config[6] & (1 << 6))) {
+ /* Do nothing unless CSVALID */
+ return;
+ }
+ if (!f->iomem && !f->dev) {
+ return;
+ }
+ memory_region_del_subregion(get_system_memory(), &f->container);
+ memory_region_del_subregion(&f->container, omap_gpmc_cs_memregion(s, cs));
+ object_unparent(OBJECT(&f->container));
+}
+
+void omap_gpmc_reset(struct omap_gpmc_s *s)
+{
+ int i;
+
+ s->sysconfig = 0;
+ s->irqst = 0;
+ s->irqen = 0;
+ omap_gpmc_int_update(s);
+ for (i = 0; i < 8; i++) {
+ /* This has to happen before we change any of the config
+ * used to determine which memory regions are mapped or unmapped.
+ */
+ omap_gpmc_cs_unmap(s, i);
+ }
+ s->timeout = 0;
+ s->config = 0xa00;
+ s->prefetch.config1 = 0x00004000;
+ s->prefetch.transfercount = 0x00000000;
+ s->prefetch.startengine = 0;
+ s->prefetch.fifopointer = 0;
+ s->prefetch.count = 0;
+ for (i = 0; i < 8; i ++) {
+ s->cs_file[i].config[1] = 0x101001;
+ s->cs_file[i].config[2] = 0x020201;
+ s->cs_file[i].config[3] = 0x10031003;
+ s->cs_file[i].config[4] = 0x10f1111;
+ s->cs_file[i].config[5] = 0;
+ s->cs_file[i].config[6] = 0xf00;
+ /* In theory we could probe attached devices for some CFG1
+ * bits here, but we just retain them across resets as they
+ * were set initially by omap_gpmc_attach().
+ */
+ if (i == 0) {
+ s->cs_file[i].config[0] &= 0x00433e00;
+ s->cs_file[i].config[6] |= 1 << 6; /* CSVALID */
+ omap_gpmc_cs_map(s, i);
+ } else {
+ s->cs_file[i].config[0] &= 0x00403c00;
+ }
+ }
+ s->ecc_cs = 0;
+ s->ecc_ptr = 0;
+ s->ecc_cfg = 0x3fcff000;
+ for (i = 0; i < 9; i ++)
+ ecc_reset(&s->ecc[i]);
+}
+
+static int gpmc_wordaccess_only(hwaddr addr)
+{
+ /* Return true if the register offset is to a register that
+ * only permits word width accesses.
+ * Non-word accesses are only OK for GPMC_NAND_DATA/ADDRESS/COMMAND
+ * for any chipselect.
+ */
+ if (addr >= 0x60 && addr <= 0x1d4) {
+ int cs = (addr - 0x60) / 0x30;
+ addr -= cs * 0x30;
+ if (addr >= 0x7c && addr < 0x88) {
+ /* GPMC_NAND_COMMAND, GPMC_NAND_ADDRESS, GPMC_NAND_DATA */
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static uint64_t omap_gpmc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+ int cs;
+ struct omap_gpmc_cs_file_s *f;
+
+ if (size != 4 && gpmc_wordaccess_only(addr)) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x000: /* GPMC_REVISION */
+ return s->revision;
+
+ case 0x010: /* GPMC_SYSCONFIG */
+ return s->sysconfig;
+
+ case 0x014: /* GPMC_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x018: /* GPMC_IRQSTATUS */
+ return s->irqst;
+
+ case 0x01c: /* GPMC_IRQENABLE */
+ return s->irqen;
+
+ case 0x040: /* GPMC_TIMEOUT_CONTROL */
+ return s->timeout;
+
+ case 0x044: /* GPMC_ERR_ADDRESS */
+ case 0x048: /* GPMC_ERR_TYPE */
+ return 0;
+
+ case 0x050: /* GPMC_CONFIG */
+ return s->config;
+
+ case 0x054: /* GPMC_STATUS */
+ return 0x001;
+
+ case 0x060 ... 0x1d4:
+ cs = (addr - 0x060) / 0x30;
+ addr -= cs * 0x30;
+ f = s->cs_file + cs;
+ switch (addr) {
+ case 0x60: /* GPMC_CONFIG1 */
+ return f->config[0];
+ case 0x64: /* GPMC_CONFIG2 */
+ return f->config[1];
+ case 0x68: /* GPMC_CONFIG3 */
+ return f->config[2];
+ case 0x6c: /* GPMC_CONFIG4 */
+ return f->config[3];
+ case 0x70: /* GPMC_CONFIG5 */
+ return f->config[4];
+ case 0x74: /* GPMC_CONFIG6 */
+ return f->config[5];
+ case 0x78: /* GPMC_CONFIG7 */
+ return f->config[6];
+ case 0x84 ... 0x87: /* GPMC_NAND_DATA */
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) {
+ return omap_nand_read(f, 0, size);
+ }
+ return 0;
+ }
+ break;
+
+ case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */
+ return s->prefetch.config1;
+ case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */
+ return s->prefetch.transfercount;
+ case 0x1ec: /* GPMC_PREFETCH_CONTROL */
+ return s->prefetch.startengine;
+ case 0x1f0: /* GPMC_PREFETCH_STATUS */
+ /* NB: The OMAP3 TRM is inconsistent about whether the GPMC
+ * FIFOTHRESHOLDSTATUS bit should be set when
+ * FIFOPOINTER > FIFOTHRESHOLD or when it is >= FIFOTHRESHOLD.
+ * Apparently the underlying functional spec from which the TRM was
+ * created states that the behaviour is ">=", and this also
+ * makes more conceptual sense.
+ */
+ return (s->prefetch.fifopointer << 24) |
+ ((s->prefetch.fifopointer >=
+ ((s->prefetch.config1 >> 8) & 0x7f) ? 1 : 0) << 16) |
+ s->prefetch.count;
+
+ case 0x1f4: /* GPMC_ECC_CONFIG */
+ return s->ecc_cs;
+ case 0x1f8: /* GPMC_ECC_CONTROL */
+ return s->ecc_ptr;
+ case 0x1fc: /* GPMC_ECC_SIZE_CONFIG */
+ return s->ecc_cfg;
+ case 0x200 ... 0x220: /* GPMC_ECC_RESULT */
+ cs = (addr & 0x1f) >> 2;
+ /* TODO: check correctness */
+ return
+ ((s->ecc[cs].cp & 0x07) << 0) |
+ ((s->ecc[cs].cp & 0x38) << 13) |
+ ((s->ecc[cs].lp[0] & 0x1ff) << 3) |
+ ((s->ecc[cs].lp[1] & 0x1ff) << 19);
+
+ case 0x230: /* GPMC_TESTMODE_CTRL */
+ return 0;
+ case 0x234: /* GPMC_PSA_LSB */
+ case 0x238: /* GPMC_PSA_MSB */
+ return 0x00000000;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_gpmc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+ int cs;
+ struct omap_gpmc_cs_file_s *f;
+
+ if (size != 4 && gpmc_wordaccess_only(addr)) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x000: /* GPMC_REVISION */
+ case 0x014: /* GPMC_SYSSTATUS */
+ case 0x054: /* GPMC_STATUS */
+ case 0x1f0: /* GPMC_PREFETCH_STATUS */
+ case 0x200 ... 0x220: /* GPMC_ECC_RESULT */
+ case 0x234: /* GPMC_PSA_LSB */
+ case 0x238: /* GPMC_PSA_MSB */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x010: /* GPMC_SYSCONFIG */
+ if ((value >> 3) == 0x3)
+ fprintf(stderr, "%s: bad SDRAM idle mode %"PRIi64"\n",
+ __FUNCTION__, value >> 3);
+ if (value & 2)
+ omap_gpmc_reset(s);
+ s->sysconfig = value & 0x19;
+ break;
+
+ case 0x018: /* GPMC_IRQSTATUS */
+ s->irqst &= ~value;
+ omap_gpmc_int_update(s);
+ break;
+
+ case 0x01c: /* GPMC_IRQENABLE */
+ s->irqen = value & 0xf03;
+ omap_gpmc_int_update(s);
+ break;
+
+ case 0x040: /* GPMC_TIMEOUT_CONTROL */
+ s->timeout = value & 0x1ff1;
+ break;
+
+ case 0x044: /* GPMC_ERR_ADDRESS */
+ case 0x048: /* GPMC_ERR_TYPE */
+ break;
+
+ case 0x050: /* GPMC_CONFIG */
+ s->config = value & 0xf13;
+ break;
+
+ case 0x060 ... 0x1d4:
+ cs = (addr - 0x060) / 0x30;
+ addr -= cs * 0x30;
+ f = s->cs_file + cs;
+ switch (addr) {
+ case 0x60: /* GPMC_CONFIG1 */
+ f->config[0] = value & 0xffef3e13;
+ break;
+ case 0x64: /* GPMC_CONFIG2 */
+ f->config[1] = value & 0x001f1f8f;
+ break;
+ case 0x68: /* GPMC_CONFIG3 */
+ f->config[2] = value & 0x001f1f8f;
+ break;
+ case 0x6c: /* GPMC_CONFIG4 */
+ f->config[3] = value & 0x1f8f1f8f;
+ break;
+ case 0x70: /* GPMC_CONFIG5 */
+ f->config[4] = value & 0x0f1f1f1f;
+ break;
+ case 0x74: /* GPMC_CONFIG6 */
+ f->config[5] = value & 0x00000fcf;
+ break;
+ case 0x78: /* GPMC_CONFIG7 */
+ if ((f->config[6] ^ value) & 0xf7f) {
+ omap_gpmc_cs_unmap(s, cs);
+ f->config[6] = value & 0x00000f7f;
+ omap_gpmc_cs_map(s, cs);
+ }
+ break;
+ case 0x7c ... 0x7f: /* GPMC_NAND_COMMAND */
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) {
+ nand_setpins(f->dev, 1, 0, 0, 1, 0); /* CLE */
+ omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size);
+ }
+ break;
+ case 0x80 ... 0x83: /* GPMC_NAND_ADDRESS */
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) {
+ nand_setpins(f->dev, 0, 1, 0, 1, 0); /* ALE */
+ omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size);
+ }
+ break;
+ case 0x84 ... 0x87: /* GPMC_NAND_DATA */
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) {
+ omap_nand_write(f, 0, value, size);
+ }
+ break;
+ default:
+ goto bad_reg;
+ }
+ break;
+
+ case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */
+ if (!s->prefetch.startengine) {
+ uint32_t newconfig1 = value & 0x7f8f7fbf;
+ uint32_t changed;
+ changed = newconfig1 ^ s->prefetch.config1;
+ if (changed & (0x80 | 0x7000000)) {
+ /* Turning the engine on or off, or mapping it somewhere else.
+ * cs_map() and cs_unmap() check the prefetch config and
+ * overall CSVALID bits, so it is sufficient to unmap-and-map
+ * both the old cs and the new one. Note that we adhere to
+ * the "unmap/change config/map" order (and not unmap twice
+ * if newcs == oldcs), otherwise we'll try to delete the wrong
+ * memory region.
+ */
+ int oldcs = prefetch_cs(s->prefetch.config1);
+ int newcs = prefetch_cs(newconfig1);
+ omap_gpmc_cs_unmap(s, oldcs);
+ if (oldcs != newcs) {
+ omap_gpmc_cs_unmap(s, newcs);
+ }
+ s->prefetch.config1 = newconfig1;
+ omap_gpmc_cs_map(s, oldcs);
+ if (oldcs != newcs) {
+ omap_gpmc_cs_map(s, newcs);
+ }
+ } else {
+ s->prefetch.config1 = newconfig1;
+ }
+ }
+ break;
+
+ case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */
+ if (!s->prefetch.startengine) {
+ s->prefetch.transfercount = value & 0x3fff;
+ }
+ break;
+
+ case 0x1ec: /* GPMC_PREFETCH_CONTROL */
+ if (s->prefetch.startengine != (value & 1)) {
+ s->prefetch.startengine = value & 1;
+ if (s->prefetch.startengine) {
+ /* Prefetch engine start */
+ s->prefetch.count = s->prefetch.transfercount;
+ if (s->prefetch.config1 & 1) {
+ /* Write */
+ s->prefetch.fifopointer = 64;
+ } else {
+ /* Read */
+ s->prefetch.fifopointer = 0;
+ fill_prefetch_fifo(s);
+ }
+ } else {
+ /* Prefetch engine forcibly stopped. The TRM
+ * doesn't define the behaviour if you do this.
+ * We clear the prefetch count, which means that
+ * we permit no more writes, and don't read any
+ * more data from NAND. The CPU can still drain
+ * the FIFO of unread data.
+ */
+ s->prefetch.count = 0;
+ }
+ omap_gpmc_int_update(s);
+ }
+ break;
+
+ case 0x1f4: /* GPMC_ECC_CONFIG */
+ s->ecc_cs = 0x8f;
+ break;
+ case 0x1f8: /* GPMC_ECC_CONTROL */
+ if (value & (1 << 8))
+ for (cs = 0; cs < 9; cs ++)
+ ecc_reset(&s->ecc[cs]);
+ s->ecc_ptr = value & 0xf;
+ if (s->ecc_ptr == 0 || s->ecc_ptr > 9) {
+ s->ecc_ptr = 0;
+ s->ecc_cs &= ~1;
+ }
+ break;
+ case 0x1fc: /* GPMC_ECC_SIZE_CONFIG */
+ s->ecc_cfg = value & 0x3fcff1ff;
+ break;
+ case 0x230: /* GPMC_TESTMODE_CTRL */
+ if (value & 7)
+ fprintf(stderr, "%s: test mode enable attempt\n", __FUNCTION__);
+ break;
+
+ default:
+ bad_reg:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_gpmc_ops = {
+ .read = omap_gpmc_read,
+ .write = omap_gpmc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_gpmc_s *omap_gpmc_init(struct omap_mpu_state_s *mpu,
+ hwaddr base,
+ qemu_irq irq, qemu_irq drq)
+{
+ int cs;
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *)
+ g_malloc0(sizeof(struct omap_gpmc_s));
+
+ memory_region_init_io(&s->iomem, NULL, &omap_gpmc_ops, s, "omap-gpmc", 0x1000);
+ memory_region_add_subregion(get_system_memory(), base, &s->iomem);
+
+ s->irq = irq;
+ s->drq = drq;
+ s->accept_256 = cpu_is_omap3630(mpu);
+ s->revision = cpu_class_omap3(mpu) ? 0x50 : 0x20;
+ s->lastirq = 0;
+ omap_gpmc_reset(s);
+
+ /* We have to register a different IO memory handler for each
+ * chip select region in case a NAND device is mapped there. We
+ * make the region the worst-case size of 256MB and rely on the
+ * container memory region in cs_map to chop it down to the actual
+ * guest-requested size.
+ */
+ for (cs = 0; cs < 8; cs++) {
+ memory_region_init_io(&s->cs_file[cs].nandiomem, NULL,
+ &omap_nand_ops,
+ &s->cs_file[cs],
+ "omap-nand",
+ 256 * 1024 * 1024);
+ }
+
+ memory_region_init_io(&s->prefetch.iomem, NULL, &omap_prefetch_ops, s,
+ "omap-gpmc-prefetch", 256 * 1024 * 1024);
+ return s;
+}
+
+void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, MemoryRegion *iomem)
+{
+ struct omap_gpmc_cs_file_s *f;
+ assert(iomem);
+
+ if (cs < 0 || cs >= 8) {
+ fprintf(stderr, "%s: bad chip-select %i\n", __FUNCTION__, cs);
+ exit(-1);
+ }
+ f = &s->cs_file[cs];
+
+ omap_gpmc_cs_unmap(s, cs);
+ f->config[0] &= ~(0xf << 10);
+ f->iomem = iomem;
+ omap_gpmc_cs_map(s, cs);
+}
+
+void omap_gpmc_attach_nand(struct omap_gpmc_s *s, int cs, DeviceState *nand)
+{
+ struct omap_gpmc_cs_file_s *f;
+ assert(nand);
+
+ if (cs < 0 || cs >= 8) {
+ fprintf(stderr, "%s: bad chip-select %i\n", __func__, cs);
+ exit(-1);
+ }
+ f = &s->cs_file[cs];
+
+ omap_gpmc_cs_unmap(s, cs);
+ f->config[0] &= ~(0xf << 10);
+ f->config[0] |= (OMAP_GPMC_NAND << 10);
+ f->dev = nand;
+ if (nand_getbuswidth(f->dev) == 16) {
+ f->config[0] |= OMAP_GPMC_16BIT << 12;
+ }
+ omap_gpmc_cs_map(s, cs);
+}
diff --git a/hw/misc/omap_l4.c b/hw/misc/omap_l4.c
new file mode 100644
index 00000000..245ceac8
--- /dev/null
+++ b/hw/misc/omap_l4.c
@@ -0,0 +1,163 @@
+/*
+ * TI OMAP L4 interconnect emulation.
+ *
+ * Copyright (C) 2007-2009 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+
+struct omap_l4_s {
+ MemoryRegion *address_space;
+ hwaddr base;
+ int ta_num;
+ struct omap_target_agent_s ta[0];
+};
+
+struct omap_l4_s *omap_l4_init(MemoryRegion *address_space,
+ hwaddr base, int ta_num)
+{
+ struct omap_l4_s *bus = g_malloc0(
+ sizeof(*bus) + ta_num * sizeof(*bus->ta));
+
+ bus->address_space = address_space;
+ bus->ta_num = ta_num;
+ bus->base = base;
+
+ return bus;
+}
+
+hwaddr omap_l4_region_base(struct omap_target_agent_s *ta,
+ int region)
+{
+ return ta->bus->base + ta->start[region].offset;
+}
+
+hwaddr omap_l4_region_size(struct omap_target_agent_s *ta,
+ int region)
+{
+ return ta->start[region].size;
+}
+
+static uint64_t omap_l4ta_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_target_agent_s *s = (struct omap_target_agent_s *) opaque;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* COMPONENT */
+ return s->component;
+
+ case 0x20: /* AGENT_CONTROL */
+ return s->control;
+
+ case 0x28: /* AGENT_STATUS */
+ return s->status;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_l4ta_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_target_agent_s *s = (struct omap_target_agent_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* COMPONENT */
+ case 0x28: /* AGENT_STATUS */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x20: /* AGENT_CONTROL */
+ s->control = value & 0x01000700;
+ if (value & 1) /* OCP_RESET */
+ s->status &= ~1; /* REQ_TIMEOUT */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_l4ta_ops = {
+ .read = omap_l4ta_read,
+ .write = omap_l4ta_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_target_agent_s *omap_l4ta_get(struct omap_l4_s *bus,
+ const struct omap_l4_region_s *regions,
+ const struct omap_l4_agent_info_s *agents,
+ int cs)
+{
+ int i;
+ struct omap_target_agent_s *ta = NULL;
+ const struct omap_l4_agent_info_s *info = NULL;
+
+ for (i = 0; i < bus->ta_num; i ++)
+ if (agents[i].ta == cs) {
+ ta = &bus->ta[i];
+ info = &agents[i];
+ break;
+ }
+ if (!ta) {
+ fprintf(stderr, "%s: bad target agent (%i)\n", __FUNCTION__, cs);
+ exit(-1);
+ }
+
+ ta->bus = bus;
+ ta->start = &regions[info->region];
+ ta->regions = info->regions;
+
+ ta->component = ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+ ta->status = 0x00000000;
+ ta->control = 0x00000200; /* XXX 01000200 for L4TAO */
+
+ memory_region_init_io(&ta->iomem, NULL, &omap_l4ta_ops, ta, "omap.l4ta",
+ omap_l4_region_size(ta, info->ta_region));
+ omap_l4_attach(ta, info->ta_region, &ta->iomem);
+
+ return ta;
+}
+
+hwaddr omap_l4_attach(struct omap_target_agent_s *ta,
+ int region, MemoryRegion *mr)
+{
+ hwaddr base;
+
+ if (region < 0 || region >= ta->regions) {
+ fprintf(stderr, "%s: bad io region (%i)\n", __FUNCTION__, region);
+ exit(-1);
+ }
+
+ base = ta->bus->base + ta->start[region].offset;
+ if (mr) {
+ memory_region_add_subregion(ta->bus->address_space, base, mr);
+ }
+
+ return base;
+}
diff --git a/hw/misc/omap_sdrc.c b/hw/misc/omap_sdrc.c
new file mode 100644
index 00000000..3de0c0e9
--- /dev/null
+++ b/hw/misc/omap_sdrc.c
@@ -0,0 +1,169 @@
+/*
+ * TI OMAP SDRAM controller emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+
+/* SDRAM Controller Subsystem */
+struct omap_sdrc_s {
+ MemoryRegion iomem;
+ uint8_t config;
+};
+
+void omap_sdrc_reset(struct omap_sdrc_s *s)
+{
+ s->config = 0x10;
+}
+
+static uint64_t omap_sdrc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_sdrc_s *s = (struct omap_sdrc_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* SDRC_REVISION */
+ return 0x20;
+
+ case 0x10: /* SDRC_SYSCONFIG */
+ return s->config;
+
+ case 0x14: /* SDRC_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x40: /* SDRC_CS_CFG */
+ case 0x44: /* SDRC_SHARING */
+ case 0x48: /* SDRC_ERR_ADDR */
+ case 0x4c: /* SDRC_ERR_TYPE */
+ case 0x60: /* SDRC_DLLA_SCTRL */
+ case 0x64: /* SDRC_DLLA_STATUS */
+ case 0x68: /* SDRC_DLLB_CTRL */
+ case 0x6c: /* SDRC_DLLB_STATUS */
+ case 0x70: /* SDRC_POWER */
+ case 0x80: /* SDRC_MCFG_0 */
+ case 0x84: /* SDRC_MR_0 */
+ case 0x88: /* SDRC_EMR1_0 */
+ case 0x8c: /* SDRC_EMR2_0 */
+ case 0x90: /* SDRC_EMR3_0 */
+ case 0x94: /* SDRC_DCDL1_CTRL */
+ case 0x98: /* SDRC_DCDL2_CTRL */
+ case 0x9c: /* SDRC_ACTIM_CTRLA_0 */
+ case 0xa0: /* SDRC_ACTIM_CTRLB_0 */
+ case 0xa4: /* SDRC_RFR_CTRL_0 */
+ case 0xa8: /* SDRC_MANUAL_0 */
+ case 0xb0: /* SDRC_MCFG_1 */
+ case 0xb4: /* SDRC_MR_1 */
+ case 0xb8: /* SDRC_EMR1_1 */
+ case 0xbc: /* SDRC_EMR2_1 */
+ case 0xc0: /* SDRC_EMR3_1 */
+ case 0xc4: /* SDRC_ACTIM_CTRLA_1 */
+ case 0xc8: /* SDRC_ACTIM_CTRLB_1 */
+ case 0xd4: /* SDRC_RFR_CTRL_1 */
+ case 0xd8: /* SDRC_MANUAL_1 */
+ return 0x00;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_sdrc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_sdrc_s *s = (struct omap_sdrc_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* SDRC_REVISION */
+ case 0x14: /* SDRC_SYSSTATUS */
+ case 0x48: /* SDRC_ERR_ADDR */
+ case 0x64: /* SDRC_DLLA_STATUS */
+ case 0x6c: /* SDRC_DLLB_STATUS */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x10: /* SDRC_SYSCONFIG */
+ if ((value >> 3) != 0x2)
+ fprintf(stderr, "%s: bad SDRAM idle mode %i\n",
+ __FUNCTION__, (unsigned)value >> 3);
+ if (value & 2)
+ omap_sdrc_reset(s);
+ s->config = value & 0x18;
+ break;
+
+ case 0x40: /* SDRC_CS_CFG */
+ case 0x44: /* SDRC_SHARING */
+ case 0x4c: /* SDRC_ERR_TYPE */
+ case 0x60: /* SDRC_DLLA_SCTRL */
+ case 0x68: /* SDRC_DLLB_CTRL */
+ case 0x70: /* SDRC_POWER */
+ case 0x80: /* SDRC_MCFG_0 */
+ case 0x84: /* SDRC_MR_0 */
+ case 0x88: /* SDRC_EMR1_0 */
+ case 0x8c: /* SDRC_EMR2_0 */
+ case 0x90: /* SDRC_EMR3_0 */
+ case 0x94: /* SDRC_DCDL1_CTRL */
+ case 0x98: /* SDRC_DCDL2_CTRL */
+ case 0x9c: /* SDRC_ACTIM_CTRLA_0 */
+ case 0xa0: /* SDRC_ACTIM_CTRLB_0 */
+ case 0xa4: /* SDRC_RFR_CTRL_0 */
+ case 0xa8: /* SDRC_MANUAL_0 */
+ case 0xb0: /* SDRC_MCFG_1 */
+ case 0xb4: /* SDRC_MR_1 */
+ case 0xb8: /* SDRC_EMR1_1 */
+ case 0xbc: /* SDRC_EMR2_1 */
+ case 0xc0: /* SDRC_EMR3_1 */
+ case 0xc4: /* SDRC_ACTIM_CTRLA_1 */
+ case 0xc8: /* SDRC_ACTIM_CTRLB_1 */
+ case 0xd4: /* SDRC_RFR_CTRL_1 */
+ case 0xd8: /* SDRC_MANUAL_1 */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_sdrc_ops = {
+ .read = omap_sdrc_read,
+ .write = omap_sdrc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_sdrc_s *omap_sdrc_init(MemoryRegion *sysmem,
+ hwaddr base)
+{
+ struct omap_sdrc_s *s = (struct omap_sdrc_s *)
+ g_malloc0(sizeof(struct omap_sdrc_s));
+
+ omap_sdrc_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_sdrc_ops, s, "omap.sdrc", 0x1000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ return s;
+}
diff --git a/hw/misc/omap_tap.c b/hw/misc/omap_tap.c
new file mode 100644
index 00000000..6f02bb9e
--- /dev/null
+++ b/hw/misc/omap_tap.c
@@ -0,0 +1,117 @@
+/*
+ * TI OMAP TEST-Chip-level TAP emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+
+/* TEST-Chip-level TAP */
+static uint64_t omap_tap_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x204: /* IDCODE_reg */
+ switch (s->mpu_model) {
+ case omap2420:
+ case omap2422:
+ case omap2423:
+ return 0x5b5d902f; /* ES 2.2 */
+ case omap2430:
+ return 0x5b68a02f; /* ES 2.2 */
+ case omap3430:
+ return 0x1b7ae02f; /* ES 2 */
+ default:
+ hw_error("%s: Bad mpu model\n", __FUNCTION__);
+ }
+
+ case 0x208: /* PRODUCTION_ID_reg for OMAP2 */
+ case 0x210: /* PRODUCTION_ID_reg for OMAP3 */
+ switch (s->mpu_model) {
+ case omap2420:
+ return 0x000254f0; /* POP ESHS2.1.1 in N91/93/95, ES2 in N800 */
+ case omap2422:
+ return 0x000400f0;
+ case omap2423:
+ return 0x000800f0;
+ case omap2430:
+ return 0x000000f0;
+ case omap3430:
+ return 0x000000f0;
+ default:
+ hw_error("%s: Bad mpu model\n", __FUNCTION__);
+ }
+
+ case 0x20c:
+ switch (s->mpu_model) {
+ case omap2420:
+ case omap2422:
+ case omap2423:
+ return 0xcafeb5d9; /* ES 2.2 */
+ case omap2430:
+ return 0xcafeb68a; /* ES 2.2 */
+ case omap3430:
+ return 0xcafeb7ae; /* ES 2 */
+ default:
+ hw_error("%s: Bad mpu model\n", __FUNCTION__);
+ }
+
+ case 0x218: /* DIE_ID_reg */
+ return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+ case 0x21c: /* DIE_ID_reg */
+ return 0x54 << 24;
+ case 0x220: /* DIE_ID_reg */
+ return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+ case 0x224: /* DIE_ID_reg */
+ return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_tap_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap_tap_ops = {
+ .read = omap_tap_read,
+ .write = omap_tap_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void omap_tap_init(struct omap_target_agent_s *ta,
+ struct omap_mpu_state_s *mpu)
+{
+ memory_region_init_io(&mpu->tap_iomem, NULL, &omap_tap_ops, mpu, "omap.tap",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &mpu->tap_iomem);
+}
diff --git a/hw/misc/pc-testdev.c b/hw/misc/pc-testdev.c
new file mode 100644
index 00000000..18e94e07
--- /dev/null
+++ b/hw/misc/pc-testdev.c
@@ -0,0 +1,207 @@
+/*
+ * QEMU x86 ISA testdev
+ *
+ * Copyright (c) 2012 Avi Kivity, Gerd Hoffmann, Marcelo Tosatti
+ *
+ * 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.
+ */
+
+/*
+ * This device is used to test KVM features specific to the x86 port, such
+ * as emulation, power management, interrupt routing, among others. It's meant
+ * to be used like:
+ *
+ * qemu-system-x86_64 -device pc-testdev -serial stdio \
+ * -device isa-debug-exit,iobase=0xf4,iosize=0x4 \
+ * -kernel /home/lmr/Code/virt-test.git/kvm/unittests/msr.flat
+ *
+ * Where msr.flat is one of the KVM unittests, present on a separate repo,
+ * git://git.kernel.org/pub/scm/virt/kvm/kvm-unit-tests.git
+*/
+
+#include "config-host.h"
+#if defined(CONFIG_POSIX)
+#include <sys/mman.h>
+#endif
+#include "hw/hw.h"
+#include "hw/qdev.h"
+#include "hw/isa/isa.h"
+
+#define IOMEM_LEN 0x10000
+
+typedef struct PCTestdev {
+ ISADevice parent_obj;
+
+ MemoryRegion ioport;
+ MemoryRegion ioport_byte;
+ MemoryRegion flush;
+ MemoryRegion irq;
+ MemoryRegion iomem;
+ uint32_t ioport_data;
+ char iomem_buf[IOMEM_LEN];
+} PCTestdev;
+
+#define TYPE_TESTDEV "pc-testdev"
+#define TESTDEV(obj) \
+ OBJECT_CHECK(PCTestdev, (obj), TYPE_TESTDEV)
+
+static void test_irq_line(void *opaque, hwaddr addr, uint64_t data,
+ unsigned len)
+{
+ PCTestdev *dev = opaque;
+ ISADevice *isa = ISA_DEVICE(dev);
+
+ qemu_set_irq(isa_get_irq(isa, addr), !!data);
+}
+
+static const MemoryRegionOps test_irq_ops = {
+ .write = test_irq_line,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void test_ioport_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned len)
+{
+ PCTestdev *dev = opaque;
+ int bits = len * 8;
+ int start_bit = (addr & 3) * 8;
+ uint32_t mask = ((uint32_t)-1 >> (32 - bits)) << start_bit;
+ dev->ioport_data &= ~mask;
+ dev->ioport_data |= data << start_bit;
+}
+
+static uint64_t test_ioport_read(void *opaque, hwaddr addr, unsigned len)
+{
+ PCTestdev *dev = opaque;
+ int bits = len * 8;
+ int start_bit = (addr & 3) * 8;
+ uint32_t mask = ((uint32_t)-1 >> (32 - bits)) << start_bit;
+ return (dev->ioport_data & mask) >> start_bit;
+}
+
+static const MemoryRegionOps test_ioport_ops = {
+ .read = test_ioport_read,
+ .write = test_ioport_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps test_ioport_byte_ops = {
+ .read = test_ioport_read,
+ .write = test_ioport_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void test_flush_page(void *opaque, hwaddr addr, uint64_t data,
+ unsigned len)
+{
+ hwaddr page = 4096;
+ void *a = cpu_physical_memory_map(data & ~0xffful, &page, 0);
+
+ /* We might not be able to get the full page, only mprotect what we actually
+ have mapped */
+#if defined(CONFIG_POSIX)
+ mprotect(a, page, PROT_NONE);
+ mprotect(a, page, PROT_READ|PROT_WRITE);
+#endif
+ cpu_physical_memory_unmap(a, page, 0, 0);
+}
+
+static const MemoryRegionOps test_flush_ops = {
+ .write = test_flush_page,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t test_iomem_read(void *opaque, hwaddr addr, unsigned len)
+{
+ PCTestdev *dev = opaque;
+ uint64_t ret = 0;
+ memcpy(&ret, &dev->iomem_buf[addr], len);
+
+ return ret;
+}
+
+static void test_iomem_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned len)
+{
+ PCTestdev *dev = opaque;
+ memcpy(&dev->iomem_buf[addr], &val, len);
+ dev->iomem_buf[addr] = val;
+}
+
+static const MemoryRegionOps test_iomem_ops = {
+ .read = test_iomem_read,
+ .write = test_iomem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void testdev_realizefn(DeviceState *d, Error **errp)
+{
+ ISADevice *isa = ISA_DEVICE(d);
+ PCTestdev *dev = TESTDEV(d);
+ MemoryRegion *mem = isa_address_space(isa);
+ MemoryRegion *io = isa_address_space_io(isa);
+
+ memory_region_init_io(&dev->ioport, OBJECT(dev), &test_ioport_ops, dev,
+ "pc-testdev-ioport", 4);
+ memory_region_init_io(&dev->ioport_byte, OBJECT(dev),
+ &test_ioport_byte_ops, dev,
+ "pc-testdev-ioport-byte", 4);
+ memory_region_init_io(&dev->flush, OBJECT(dev), &test_flush_ops, dev,
+ "pc-testdev-flush-page", 4);
+ memory_region_init_io(&dev->irq, OBJECT(dev), &test_irq_ops, dev,
+ "pc-testdev-irq-line", 24);
+ memory_region_init_io(&dev->iomem, OBJECT(dev), &test_iomem_ops, dev,
+ "pc-testdev-iomem", IOMEM_LEN);
+
+ memory_region_add_subregion(io, 0xe0, &dev->ioport);
+ memory_region_add_subregion(io, 0xe4, &dev->flush);
+ memory_region_add_subregion(io, 0xe8, &dev->ioport_byte);
+ memory_region_add_subregion(io, 0x2000, &dev->irq);
+ memory_region_add_subregion(mem, 0xff000000, &dev->iomem);
+}
+
+static void testdev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->realize = testdev_realizefn;
+}
+
+static const TypeInfo testdev_info = {
+ .name = TYPE_TESTDEV,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(PCTestdev),
+ .class_init = testdev_class_init,
+};
+
+static void testdev_register_types(void)
+{
+ type_register_static(&testdev_info);
+}
+
+type_init(testdev_register_types)
diff --git a/hw/misc/pci-testdev.c b/hw/misc/pci-testdev.c
new file mode 100644
index 00000000..26b9b861
--- /dev/null
+++ b/hw/misc/pci-testdev.c
@@ -0,0 +1,330 @@
+/*
+ * QEMU PCI test device
+ *
+ * Copyright (c) 2012 Red Hat Inc.
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "qemu/event_notifier.h"
+#include "qemu/osdep.h"
+
+typedef struct PCITestDevHdr {
+ uint8_t test;
+ uint8_t width;
+ uint8_t pad0[2];
+ uint32_t offset;
+ uint8_t data;
+ uint8_t pad1[3];
+ uint32_t count;
+ uint8_t name[];
+} PCITestDevHdr;
+
+typedef struct IOTest {
+ MemoryRegion *mr;
+ EventNotifier notifier;
+ bool hasnotifier;
+ unsigned size;
+ bool match_data;
+ PCITestDevHdr *hdr;
+ unsigned bufsize;
+} IOTest;
+
+#define IOTEST_DATAMATCH 0xFA
+#define IOTEST_NOMATCH 0xCE
+
+#define IOTEST_IOSIZE 128
+#define IOTEST_MEMSIZE 2048
+
+static const char *iotest_test[] = {
+ "no-eventfd",
+ "wildcard-eventfd",
+ "datamatch-eventfd"
+};
+
+static const char *iotest_type[] = {
+ "mmio",
+ "portio"
+};
+
+#define IOTEST_TEST(i) (iotest_test[((i) % ARRAY_SIZE(iotest_test))])
+#define IOTEST_TYPE(i) (iotest_type[((i) / ARRAY_SIZE(iotest_test))])
+#define IOTEST_MAX_TEST (ARRAY_SIZE(iotest_test))
+#define IOTEST_MAX_TYPE (ARRAY_SIZE(iotest_type))
+#define IOTEST_MAX (IOTEST_MAX_TEST * IOTEST_MAX_TYPE)
+
+enum {
+ IOTEST_ACCESS_NAME,
+ IOTEST_ACCESS_DATA,
+ IOTEST_ACCESS_MAX,
+};
+
+#define IOTEST_ACCESS_TYPE uint8_t
+#define IOTEST_ACCESS_WIDTH (sizeof(uint8_t))
+
+typedef struct PCITestDevState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion mmio;
+ MemoryRegion portio;
+ IOTest *tests;
+ int current;
+} PCITestDevState;
+
+#define TYPE_PCI_TEST_DEV "pci-testdev"
+
+#define PCI_TEST_DEV(obj) \
+ OBJECT_CHECK(PCITestDevState, (obj), TYPE_PCI_TEST_DEV)
+
+#define IOTEST_IS_MEM(i) (strcmp(IOTEST_TYPE(i), "portio"))
+#define IOTEST_REGION(d, i) (IOTEST_IS_MEM(i) ? &(d)->mmio : &(d)->portio)
+#define IOTEST_SIZE(i) (IOTEST_IS_MEM(i) ? IOTEST_MEMSIZE : IOTEST_IOSIZE)
+#define IOTEST_PCI_BAR(i) (IOTEST_IS_MEM(i) ? PCI_BASE_ADDRESS_SPACE_MEMORY : \
+ PCI_BASE_ADDRESS_SPACE_IO)
+
+static int pci_testdev_start(IOTest *test)
+{
+ test->hdr->count = 0;
+ if (!test->hasnotifier) {
+ return 0;
+ }
+ event_notifier_test_and_clear(&test->notifier);
+ memory_region_add_eventfd(test->mr,
+ le32_to_cpu(test->hdr->offset),
+ test->size,
+ test->match_data,
+ test->hdr->data,
+ &test->notifier);
+ return 0;
+}
+
+static void pci_testdev_stop(IOTest *test)
+{
+ if (!test->hasnotifier) {
+ return;
+ }
+ memory_region_del_eventfd(test->mr,
+ le32_to_cpu(test->hdr->offset),
+ test->size,
+ test->match_data,
+ test->hdr->data,
+ &test->notifier);
+}
+
+static void
+pci_testdev_reset(PCITestDevState *d)
+{
+ if (d->current == -1) {
+ return;
+ }
+ pci_testdev_stop(&d->tests[d->current]);
+ d->current = -1;
+}
+
+static void pci_testdev_inc(IOTest *test, unsigned inc)
+{
+ uint32_t c = le32_to_cpu(test->hdr->count);
+ test->hdr->count = cpu_to_le32(c + inc);
+}
+
+static void
+pci_testdev_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size, int type)
+{
+ PCITestDevState *d = opaque;
+ IOTest *test;
+ int t, r;
+
+ if (addr == offsetof(PCITestDevHdr, test)) {
+ pci_testdev_reset(d);
+ if (val >= IOTEST_MAX_TEST) {
+ return;
+ }
+ t = type * IOTEST_MAX_TEST + val;
+ r = pci_testdev_start(&d->tests[t]);
+ if (r < 0) {
+ return;
+ }
+ d->current = t;
+ return;
+ }
+ if (d->current < 0) {
+ return;
+ }
+ test = &d->tests[d->current];
+ if (addr != le32_to_cpu(test->hdr->offset)) {
+ return;
+ }
+ if (test->match_data && test->size != size) {
+ return;
+ }
+ if (test->match_data && val != test->hdr->data) {
+ return;
+ }
+ pci_testdev_inc(test, 1);
+}
+
+static uint64_t
+pci_testdev_read(void *opaque, hwaddr addr, unsigned size)
+{
+ PCITestDevState *d = opaque;
+ const char *buf;
+ IOTest *test;
+ if (d->current < 0) {
+ return 0;
+ }
+ test = &d->tests[d->current];
+ buf = (const char *)test->hdr;
+ if (addr + size >= test->bufsize) {
+ return 0;
+ }
+ if (test->hasnotifier) {
+ event_notifier_test_and_clear(&test->notifier);
+ }
+ return buf[addr];
+}
+
+static void
+pci_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ pci_testdev_write(opaque, addr, val, size, 0);
+}
+
+static void
+pci_testdev_pio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ pci_testdev_write(opaque, addr, val, size, 1);
+}
+
+static const MemoryRegionOps pci_testdev_mmio_ops = {
+ .read = pci_testdev_read,
+ .write = pci_testdev_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps pci_testdev_pio_ops = {
+ .read = pci_testdev_read,
+ .write = pci_testdev_pio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void pci_testdev_realize(PCIDevice *pci_dev, Error **errp)
+{
+ PCITestDevState *d = PCI_TEST_DEV(pci_dev);
+ uint8_t *pci_conf;
+ char *name;
+ int r, i;
+
+ pci_conf = pci_dev->config;
+
+ pci_conf[PCI_INTERRUPT_PIN] = 0; /* no interrupt pin */
+
+ memory_region_init_io(&d->mmio, OBJECT(d), &pci_testdev_mmio_ops, d,
+ "pci-testdev-mmio", IOTEST_MEMSIZE * 2);
+ memory_region_init_io(&d->portio, OBJECT(d), &pci_testdev_pio_ops, d,
+ "pci-testdev-portio", IOTEST_IOSIZE * 2);
+ pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+ pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->portio);
+
+ d->current = -1;
+ d->tests = g_malloc0(IOTEST_MAX * sizeof *d->tests);
+ for (i = 0; i < IOTEST_MAX; ++i) {
+ IOTest *test = &d->tests[i];
+ name = g_strdup_printf("%s-%s", IOTEST_TYPE(i), IOTEST_TEST(i));
+ test->bufsize = sizeof(PCITestDevHdr) + strlen(name) + 1;
+ test->hdr = g_malloc0(test->bufsize);
+ memcpy(test->hdr->name, name, strlen(name) + 1);
+ g_free(name);
+ test->hdr->offset = cpu_to_le32(IOTEST_SIZE(i) + i * IOTEST_ACCESS_WIDTH);
+ test->size = IOTEST_ACCESS_WIDTH;
+ test->match_data = strcmp(IOTEST_TEST(i), "wildcard-eventfd");
+ test->hdr->test = i;
+ test->hdr->data = test->match_data ? IOTEST_DATAMATCH : IOTEST_NOMATCH;
+ test->hdr->width = IOTEST_ACCESS_WIDTH;
+ test->mr = IOTEST_REGION(d, i);
+ if (!strcmp(IOTEST_TEST(i), "no-eventfd")) {
+ test->hasnotifier = false;
+ continue;
+ }
+ r = event_notifier_init(&test->notifier, 0);
+ assert(r >= 0);
+ test->hasnotifier = true;
+ }
+}
+
+static void
+pci_testdev_uninit(PCIDevice *dev)
+{
+ PCITestDevState *d = PCI_TEST_DEV(dev);
+ int i;
+
+ pci_testdev_reset(d);
+ for (i = 0; i < IOTEST_MAX; ++i) {
+ if (d->tests[i].hasnotifier) {
+ event_notifier_cleanup(&d->tests[i].notifier);
+ }
+ g_free(d->tests[i].hdr);
+ }
+ g_free(d->tests);
+}
+
+static void qdev_pci_testdev_reset(DeviceState *dev)
+{
+ PCITestDevState *d = PCI_TEST_DEV(dev);
+ pci_testdev_reset(d);
+}
+
+static void pci_testdev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_testdev_realize;
+ k->exit = pci_testdev_uninit;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT;
+ k->device_id = PCI_DEVICE_ID_REDHAT_TEST;
+ k->revision = 0x00;
+ k->class_id = PCI_CLASS_OTHERS;
+ dc->desc = "PCI Test Device";
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->reset = qdev_pci_testdev_reset;
+}
+
+static const TypeInfo pci_testdev_info = {
+ .name = TYPE_PCI_TEST_DEV,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCITestDevState),
+ .class_init = pci_testdev_class_init,
+};
+
+static void pci_testdev_register_types(void)
+{
+ type_register_static(&pci_testdev_info);
+}
+
+type_init(pci_testdev_register_types)
diff --git a/hw/misc/puv3_pm.c b/hw/misc/puv3_pm.c
new file mode 100644
index 00000000..37f23695
--- /dev/null
+++ b/hw/misc/puv3_pm.c
@@ -0,0 +1,153 @@
+/*
+ * Power Management device simulation in PKUnity SoC
+ *
+ * Copyright (C) 2010-2012 Guan Xuetao
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation, or any later version.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+
+#undef DEBUG_PUV3
+#include "hw/unicore32/puv3.h"
+
+#define TYPE_PUV3_PM "puv3_pm"
+#define PUV3_PM(obj) OBJECT_CHECK(PUV3PMState, (obj), TYPE_PUV3_PM)
+
+typedef struct PUV3PMState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t reg_PMCR;
+ uint32_t reg_PCGR;
+ uint32_t reg_PLL_SYS_CFG;
+ uint32_t reg_PLL_DDR_CFG;
+ uint32_t reg_PLL_VGA_CFG;
+ uint32_t reg_DIVCFG;
+} PUV3PMState;
+
+static uint64_t puv3_pm_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PUV3PMState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (offset) {
+ case 0x14:
+ ret = s->reg_PCGR;
+ break;
+ case 0x18:
+ ret = s->reg_PLL_SYS_CFG;
+ break;
+ case 0x1c:
+ ret = s->reg_PLL_DDR_CFG;
+ break;
+ case 0x20:
+ ret = s->reg_PLL_VGA_CFG;
+ break;
+ case 0x24:
+ ret = s->reg_DIVCFG;
+ break;
+ case 0x28: /* PLL SYS STATUS */
+ ret = 0x00002401;
+ break;
+ case 0x2c: /* PLL DDR STATUS */
+ ret = 0x00100c00;
+ break;
+ case 0x30: /* PLL VGA STATUS */
+ ret = 0x00003801;
+ break;
+ case 0x34: /* DIV STATUS */
+ ret = 0x22f52015;
+ break;
+ case 0x38: /* SW RESET */
+ ret = 0x0;
+ break;
+ case 0x44: /* PLL DFC DONE */
+ ret = 0x7;
+ break;
+ default:
+ DPRINTF("Bad offset 0x%x\n", offset);
+ }
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, ret);
+
+ return ret;
+}
+
+static void puv3_pm_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PUV3PMState *s = opaque;
+
+ switch (offset) {
+ case 0x0:
+ s->reg_PMCR = value;
+ break;
+ case 0x14:
+ s->reg_PCGR = value;
+ break;
+ case 0x18:
+ s->reg_PLL_SYS_CFG = value;
+ break;
+ case 0x1c:
+ s->reg_PLL_DDR_CFG = value;
+ break;
+ case 0x20:
+ s->reg_PLL_VGA_CFG = value;
+ break;
+ case 0x24:
+ case 0x38:
+ break;
+ default:
+ DPRINTF("Bad offset 0x%x\n", offset);
+ }
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, value);
+}
+
+static const MemoryRegionOps puv3_pm_ops = {
+ .read = puv3_pm_read,
+ .write = puv3_pm_write,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int puv3_pm_init(SysBusDevice *dev)
+{
+ PUV3PMState *s = PUV3_PM(dev);
+
+ s->reg_PCGR = 0x0;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &puv3_pm_ops, s, "puv3_pm",
+ PUV3_REGS_OFFSET);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void puv3_pm_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = puv3_pm_init;
+}
+
+static const TypeInfo puv3_pm_info = {
+ .name = TYPE_PUV3_PM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PUV3PMState),
+ .class_init = puv3_pm_class_init,
+};
+
+static void puv3_pm_register_type(void)
+{
+ type_register_static(&puv3_pm_info);
+}
+
+type_init(puv3_pm_register_type)
diff --git a/hw/misc/pvpanic.c b/hw/misc/pvpanic.c
new file mode 100644
index 00000000..994f8af8
--- /dev/null
+++ b/hw/misc/pvpanic.c
@@ -0,0 +1,144 @@
+/*
+ * QEMU simulated pvpanic device.
+ *
+ * Copyright Fujitsu, Corp. 2013
+ *
+ * Authors:
+ * Wen Congyang <wency@cn.fujitsu.com>
+ * Hu Tao <hutao@cn.fujitsu.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qapi/qmp/qobject.h"
+#include "qapi/qmp/qjson.h"
+#include "sysemu/sysemu.h"
+#include "qemu/log.h"
+
+#include "hw/nvram/fw_cfg.h"
+#include "hw/i386/pc.h"
+#include "qapi-event.h"
+
+/* The bit of supported pv event */
+#define PVPANIC_F_PANICKED 0
+
+/* The pv event value */
+#define PVPANIC_PANICKED (1 << PVPANIC_F_PANICKED)
+
+#define TYPE_ISA_PVPANIC_DEVICE "pvpanic"
+#define ISA_PVPANIC_DEVICE(obj) \
+ OBJECT_CHECK(PVPanicState, (obj), TYPE_ISA_PVPANIC_DEVICE)
+
+static void handle_event(int event)
+{
+ static bool logged;
+
+ if (event & ~PVPANIC_PANICKED && !logged) {
+ qemu_log_mask(LOG_GUEST_ERROR, "pvpanic: unknown event %#x.\n", event);
+ logged = true;
+ }
+
+ if (event & PVPANIC_PANICKED) {
+ qapi_event_send_guest_panicked(GUEST_PANIC_ACTION_PAUSE, &error_abort);
+ vm_stop(RUN_STATE_GUEST_PANICKED);
+ return;
+ }
+}
+
+#include "hw/isa/isa.h"
+
+typedef struct PVPanicState {
+ ISADevice parent_obj;
+
+ MemoryRegion io;
+ uint16_t ioport;
+} PVPanicState;
+
+/* return supported events on read */
+static uint64_t pvpanic_ioport_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return PVPANIC_PANICKED;
+}
+
+static void pvpanic_ioport_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ handle_event(val);
+}
+
+static const MemoryRegionOps pvpanic_ops = {
+ .read = pvpanic_ioport_read,
+ .write = pvpanic_ioport_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void pvpanic_isa_initfn(Object *obj)
+{
+ PVPanicState *s = ISA_PVPANIC_DEVICE(obj);
+
+ memory_region_init_io(&s->io, OBJECT(s), &pvpanic_ops, s, "pvpanic", 1);
+}
+
+static void pvpanic_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+ PVPanicState *s = ISA_PVPANIC_DEVICE(dev);
+ FWCfgState *fw_cfg = fw_cfg_find();
+ uint16_t *pvpanic_port;
+
+ if (!fw_cfg) {
+ return;
+ }
+
+ pvpanic_port = g_malloc(sizeof(*pvpanic_port));
+ *pvpanic_port = cpu_to_le16(s->ioport);
+ fw_cfg_add_file(fw_cfg, "etc/pvpanic-port", pvpanic_port,
+ sizeof(*pvpanic_port));
+
+ isa_register_ioport(d, &s->io, s->ioport);
+}
+
+#define PVPANIC_IOPORT_PROP "ioport"
+
+uint16_t pvpanic_port(void)
+{
+ Object *o = object_resolve_path_type("", TYPE_ISA_PVPANIC_DEVICE, NULL);
+ if (!o) {
+ return 0;
+ }
+ return object_property_get_int(o, PVPANIC_IOPORT_PROP, NULL);
+}
+
+static Property pvpanic_isa_properties[] = {
+ DEFINE_PROP_UINT16(PVPANIC_IOPORT_PROP, PVPanicState, ioport, 0x505),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pvpanic_isa_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pvpanic_isa_realizefn;
+ dc->props = pvpanic_isa_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static TypeInfo pvpanic_isa_info = {
+ .name = TYPE_ISA_PVPANIC_DEVICE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(PVPanicState),
+ .instance_init = pvpanic_isa_initfn,
+ .class_init = pvpanic_isa_class_init,
+};
+
+static void pvpanic_register_types(void)
+{
+ type_register_static(&pvpanic_isa_info);
+}
+
+type_init(pvpanic_register_types)
diff --git a/hw/misc/sga.c b/hw/misc/sga.c
new file mode 100644
index 00000000..83d2fd9d
--- /dev/null
+++ b/hw/misc/sga.c
@@ -0,0 +1,67 @@
+/*
+ * QEMU dummy ISA device for loading sgabios option rom.
+ *
+ * Copyright (c) 2011 Glauber Costa, Red Hat Inc.
+ *
+ * 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.
+ *
+ * sgabios code originally available at code.google.com/p/sgabios
+ *
+ */
+#include "hw/pci/pci.h"
+#include "hw/i386/pc.h"
+#include "hw/loader.h"
+#include "sysemu/sysemu.h"
+
+#define SGABIOS_FILENAME "sgabios.bin"
+
+#define TYPE_SGA "sga"
+#define SGA(obj) OBJECT_CHECK(ISASGAState, (obj), TYPE_SGA)
+
+typedef struct ISASGAState {
+ ISADevice parent_obj;
+} ISASGAState;
+
+static void sga_realizefn(DeviceState *dev, Error **errp)
+{
+ rom_add_vga(SGABIOS_FILENAME);
+}
+
+static void sga_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->realize = sga_realizefn;
+ dc->desc = "Serial Graphics Adapter";
+}
+
+static const TypeInfo sga_info = {
+ .name = TYPE_SGA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISASGAState),
+ .class_init = sga_class_initfn,
+};
+
+static void sga_register_types(void)
+{
+ type_register_static(&sga_info);
+}
+
+type_init(sga_register_types)
diff --git a/hw/misc/slavio_misc.c b/hw/misc/slavio_misc.c
new file mode 100644
index 00000000..ec50f107
--- /dev/null
+++ b/hw/misc/slavio_misc.c
@@ -0,0 +1,517 @@
+/*
+ * QEMU Sparc SLAVIO aux io port emulation
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+
+/*
+ * This is the auxio port, chip control and system control part of
+ * chip STP2001 (Slave I/O), also produced as NCR89C105. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
+ *
+ * This also includes the PMC CPU idle controller.
+ */
+
+#define TYPE_SLAVIO_MISC "slavio_misc"
+#define SLAVIO_MISC(obj) OBJECT_CHECK(MiscState, (obj), TYPE_SLAVIO_MISC)
+
+typedef struct MiscState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion cfg_iomem;
+ MemoryRegion diag_iomem;
+ MemoryRegion mdm_iomem;
+ MemoryRegion led_iomem;
+ MemoryRegion sysctrl_iomem;
+ MemoryRegion aux1_iomem;
+ MemoryRegion aux2_iomem;
+ qemu_irq irq;
+ qemu_irq fdc_tc;
+ uint32_t dummy;
+ uint8_t config;
+ uint8_t aux1, aux2;
+ uint8_t diag, mctrl;
+ uint8_t sysctrl;
+ uint16_t leds;
+} MiscState;
+
+#define TYPE_APC "apc"
+#define APC(obj) OBJECT_CHECK(APCState, (obj), TYPE_APC)
+
+typedef struct APCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq cpu_halt;
+} APCState;
+
+#define MISC_SIZE 1
+#define LED_SIZE 2
+#define SYSCTRL_SIZE 4
+
+#define AUX1_TC 0x02
+
+#define AUX2_PWROFF 0x01
+#define AUX2_PWRINTCLR 0x02
+#define AUX2_PWRFAIL 0x20
+
+#define CFG_PWRINTEN 0x08
+
+#define SYS_RESET 0x01
+#define SYS_RESETSTAT 0x02
+
+static void slavio_misc_update_irq(void *opaque)
+{
+ MiscState *s = opaque;
+
+ if ((s->aux2 & AUX2_PWRFAIL) && (s->config & CFG_PWRINTEN)) {
+ trace_slavio_misc_update_irq_raise();
+ qemu_irq_raise(s->irq);
+ } else {
+ trace_slavio_misc_update_irq_lower();
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void slavio_misc_reset(DeviceState *d)
+{
+ MiscState *s = SLAVIO_MISC(d);
+
+ // Diagnostic and system control registers not cleared in reset
+ s->config = s->aux1 = s->aux2 = s->mctrl = 0;
+}
+
+static void slavio_set_power_fail(void *opaque, int irq, int power_failing)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_set_power_fail(power_failing, s->config);
+ if (power_failing && (s->config & CFG_PWRINTEN)) {
+ s->aux2 |= AUX2_PWRFAIL;
+ } else {
+ s->aux2 &= ~AUX2_PWRFAIL;
+ }
+ slavio_misc_update_irq(s);
+}
+
+static void slavio_cfg_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_cfg_mem_writeb(val & 0xff);
+ s->config = val & 0xff;
+ slavio_misc_update_irq(s);
+}
+
+static uint64_t slavio_cfg_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->config;
+ trace_slavio_cfg_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_cfg_mem_ops = {
+ .read = slavio_cfg_mem_readb,
+ .write = slavio_cfg_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void slavio_diag_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_diag_mem_writeb(val & 0xff);
+ s->diag = val & 0xff;
+}
+
+static uint64_t slavio_diag_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->diag;
+ trace_slavio_diag_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_diag_mem_ops = {
+ .read = slavio_diag_mem_readb,
+ .write = slavio_diag_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void slavio_mdm_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_mdm_mem_writeb(val & 0xff);
+ s->mctrl = val & 0xff;
+}
+
+static uint64_t slavio_mdm_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->mctrl;
+ trace_slavio_mdm_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_mdm_mem_ops = {
+ .read = slavio_mdm_mem_readb,
+ .write = slavio_mdm_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void slavio_aux1_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_aux1_mem_writeb(val & 0xff);
+ if (val & AUX1_TC) {
+ // Send a pulse to floppy terminal count line
+ if (s->fdc_tc) {
+ qemu_irq_raise(s->fdc_tc);
+ qemu_irq_lower(s->fdc_tc);
+ }
+ val &= ~AUX1_TC;
+ }
+ s->aux1 = val & 0xff;
+}
+
+static uint64_t slavio_aux1_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->aux1;
+ trace_slavio_aux1_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_aux1_mem_ops = {
+ .read = slavio_aux1_mem_readb,
+ .write = slavio_aux1_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void slavio_aux2_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ val &= AUX2_PWRINTCLR | AUX2_PWROFF;
+ trace_slavio_aux2_mem_writeb(val & 0xff);
+ val |= s->aux2 & AUX2_PWRFAIL;
+ if (val & AUX2_PWRINTCLR) // Clear Power Fail int
+ val &= AUX2_PWROFF;
+ s->aux2 = val;
+ if (val & AUX2_PWROFF)
+ qemu_system_shutdown_request();
+ slavio_misc_update_irq(s);
+}
+
+static uint64_t slavio_aux2_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->aux2;
+ trace_slavio_aux2_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_aux2_mem_ops = {
+ .read = slavio_aux2_mem_readb,
+ .write = slavio_aux2_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void apc_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ APCState *s = opaque;
+
+ trace_apc_mem_writeb(val & 0xff);
+ qemu_irq_raise(s->cpu_halt);
+}
+
+static uint64_t apc_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t ret = 0;
+
+ trace_apc_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps apc_mem_ops = {
+ .read = apc_mem_readb,
+ .write = apc_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ }
+};
+
+static uint64_t slavio_sysctrl_mem_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case 0:
+ ret = s->sysctrl;
+ break;
+ default:
+ break;
+ }
+ trace_slavio_sysctrl_mem_readl(ret);
+ return ret;
+}
+
+static void slavio_sysctrl_mem_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_sysctrl_mem_writel(val);
+ switch (addr) {
+ case 0:
+ if (val & SYS_RESET) {
+ s->sysctrl = SYS_RESETSTAT;
+ qemu_system_reset_request();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps slavio_sysctrl_mem_ops = {
+ .read = slavio_sysctrl_mem_readl,
+ .write = slavio_sysctrl_mem_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t slavio_led_mem_readw(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case 0:
+ ret = s->leds;
+ break;
+ default:
+ break;
+ }
+ trace_slavio_led_mem_readw(ret);
+ return ret;
+}
+
+static void slavio_led_mem_writew(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_led_mem_writew(val & 0xffff);
+ switch (addr) {
+ case 0:
+ s->leds = val;
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps slavio_led_mem_ops = {
+ .read = slavio_led_mem_readw,
+ .write = slavio_led_mem_writew,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 2,
+ .max_access_size = 2,
+ },
+};
+
+static const VMStateDescription vmstate_misc = {
+ .name ="slavio_misc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(dummy, MiscState),
+ VMSTATE_UINT8(config, MiscState),
+ VMSTATE_UINT8(aux1, MiscState),
+ VMSTATE_UINT8(aux2, MiscState),
+ VMSTATE_UINT8(diag, MiscState),
+ VMSTATE_UINT8(mctrl, MiscState),
+ VMSTATE_UINT8(sysctrl, MiscState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int apc_init1(SysBusDevice *dev)
+{
+ APCState *s = APC(dev);
+
+ sysbus_init_irq(dev, &s->cpu_halt);
+
+ /* Power management (APC) XXX: not a Slavio device */
+ memory_region_init_io(&s->iomem, OBJECT(s), &apc_mem_ops, s,
+ "apc", MISC_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static int slavio_misc_init1(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ MiscState *s = SLAVIO_MISC(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->fdc_tc);
+
+ /* 8 bit registers */
+ /* Slavio control */
+ memory_region_init_io(&s->cfg_iomem, OBJECT(s), &slavio_cfg_mem_ops, s,
+ "configuration", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->cfg_iomem);
+
+ /* Diagnostics */
+ memory_region_init_io(&s->diag_iomem, OBJECT(s), &slavio_diag_mem_ops, s,
+ "diagnostic", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->diag_iomem);
+
+ /* Modem control */
+ memory_region_init_io(&s->mdm_iomem, OBJECT(s), &slavio_mdm_mem_ops, s,
+ "modem", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->mdm_iomem);
+
+ /* 16 bit registers */
+ /* ss600mp diag LEDs */
+ memory_region_init_io(&s->led_iomem, OBJECT(s), &slavio_led_mem_ops, s,
+ "leds", LED_SIZE);
+ sysbus_init_mmio(sbd, &s->led_iomem);
+
+ /* 32 bit registers */
+ /* System control */
+ memory_region_init_io(&s->sysctrl_iomem, OBJECT(s), &slavio_sysctrl_mem_ops, s,
+ "system-control", SYSCTRL_SIZE);
+ sysbus_init_mmio(sbd, &s->sysctrl_iomem);
+
+ /* AUX 1 (Misc System Functions) */
+ memory_region_init_io(&s->aux1_iomem, OBJECT(s), &slavio_aux1_mem_ops, s,
+ "misc-system-functions", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->aux1_iomem);
+
+ /* AUX 2 (Software Powerdown Control) */
+ memory_region_init_io(&s->aux2_iomem, OBJECT(s), &slavio_aux2_mem_ops, s,
+ "software-powerdown-control", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->aux2_iomem);
+
+ qdev_init_gpio_in(dev, slavio_set_power_fail, 1);
+
+ return 0;
+}
+
+static void slavio_misc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = slavio_misc_init1;
+ dc->reset = slavio_misc_reset;
+ dc->vmsd = &vmstate_misc;
+}
+
+static const TypeInfo slavio_misc_info = {
+ .name = TYPE_SLAVIO_MISC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MiscState),
+ .class_init = slavio_misc_class_init,
+};
+
+static void apc_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = apc_init1;
+}
+
+static const TypeInfo apc_info = {
+ .name = TYPE_APC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MiscState),
+ .class_init = apc_class_init,
+};
+
+static void slavio_misc_register_types(void)
+{
+ type_register_static(&slavio_misc_info);
+ type_register_static(&apc_info);
+}
+
+type_init(slavio_misc_register_types)
diff --git a/hw/misc/stm32f2xx_syscfg.c b/hw/misc/stm32f2xx_syscfg.c
new file mode 100644
index 00000000..4ae4042b
--- /dev/null
+++ b/hw/misc/stm32f2xx_syscfg.c
@@ -0,0 +1,160 @@
+/*
+ * STM32F2XX SYSCFG
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * 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.
+ */
+
+#include "hw/misc/stm32f2xx_syscfg.h"
+
+#ifndef STM_SYSCFG_ERR_DEBUG
+#define STM_SYSCFG_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(lvl, fmt, args...) do { \
+ if (STM_SYSCFG_ERR_DEBUG >= lvl) { \
+ qemu_log("%s: " fmt, __func__, ## args); \
+ } \
+} while (0);
+
+#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
+
+static void stm32f2xx_syscfg_reset(DeviceState *dev)
+{
+ STM32F2XXSyscfgState *s = STM32F2XX_SYSCFG(dev);
+
+ s->syscfg_memrmp = 0x00000000;
+ s->syscfg_pmc = 0x00000000;
+ s->syscfg_exticr1 = 0x00000000;
+ s->syscfg_exticr2 = 0x00000000;
+ s->syscfg_exticr3 = 0x00000000;
+ s->syscfg_exticr4 = 0x00000000;
+ s->syscfg_cmpcr = 0x00000000;
+}
+
+static uint64_t stm32f2xx_syscfg_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ STM32F2XXSyscfgState *s = opaque;
+
+ DB_PRINT("0x%"HWADDR_PRIx"\n", addr);
+
+ switch (addr) {
+ case SYSCFG_MEMRMP:
+ return s->syscfg_memrmp;
+ case SYSCFG_PMC:
+ return s->syscfg_pmc;
+ case SYSCFG_EXTICR1:
+ return s->syscfg_exticr1;
+ case SYSCFG_EXTICR2:
+ return s->syscfg_exticr2;
+ case SYSCFG_EXTICR3:
+ return s->syscfg_exticr3;
+ case SYSCFG_EXTICR4:
+ return s->syscfg_exticr4;
+ case SYSCFG_CMPCR:
+ return s->syscfg_cmpcr;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+ return 0;
+ }
+
+ return 0;
+}
+
+static void stm32f2xx_syscfg_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ STM32F2XXSyscfgState *s = opaque;
+ uint32_t value = val64;
+
+ DB_PRINT("0x%x, 0x%"HWADDR_PRIx"\n", value, addr);
+
+ switch (addr) {
+ case SYSCFG_MEMRMP:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Changeing the memory mapping isn't supported " \
+ "in QEMU\n", __func__);
+ return;
+ case SYSCFG_PMC:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Changeing the memory mapping isn't supported " \
+ "in QEMU\n", __func__);
+ return;
+ case SYSCFG_EXTICR1:
+ s->syscfg_exticr1 = (value & 0xFFFF);
+ return;
+ case SYSCFG_EXTICR2:
+ s->syscfg_exticr2 = (value & 0xFFFF);
+ return;
+ case SYSCFG_EXTICR3:
+ s->syscfg_exticr3 = (value & 0xFFFF);
+ return;
+ case SYSCFG_EXTICR4:
+ s->syscfg_exticr4 = (value & 0xFFFF);
+ return;
+ case SYSCFG_CMPCR:
+ s->syscfg_cmpcr = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+ }
+}
+
+static const MemoryRegionOps stm32f2xx_syscfg_ops = {
+ .read = stm32f2xx_syscfg_read,
+ .write = stm32f2xx_syscfg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void stm32f2xx_syscfg_init(Object *obj)
+{
+ STM32F2XXSyscfgState *s = STM32F2XX_SYSCFG(obj);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+ memory_region_init_io(&s->mmio, obj, &stm32f2xx_syscfg_ops, s,
+ TYPE_STM32F2XX_SYSCFG, 0x400);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static void stm32f2xx_syscfg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = stm32f2xx_syscfg_reset;
+}
+
+static const TypeInfo stm32f2xx_syscfg_info = {
+ .name = TYPE_STM32F2XX_SYSCFG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F2XXSyscfgState),
+ .instance_init = stm32f2xx_syscfg_init,
+ .class_init = stm32f2xx_syscfg_class_init,
+};
+
+static void stm32f2xx_syscfg_register_types(void)
+{
+ type_register_static(&stm32f2xx_syscfg_info);
+}
+
+type_init(stm32f2xx_syscfg_register_types)
diff --git a/hw/misc/tmp105.c b/hw/misc/tmp105.c
new file mode 100644
index 00000000..f3fe8b81
--- /dev/null
+++ b/hw/misc/tmp105.c
@@ -0,0 +1,272 @@
+/*
+ * Texas Instruments TMP105 temperature sensor.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "tmp105.h"
+#include "qapi/visitor.h"
+
+static void tmp105_interrupt_update(TMP105State *s)
+{
+ qemu_set_irq(s->pin, s->alarm ^ ((~s->config >> 2) & 1)); /* POL */
+}
+
+static void tmp105_alarm_update(TMP105State *s)
+{
+ if ((s->config >> 0) & 1) { /* SD */
+ if ((s->config >> 7) & 1) /* OS */
+ s->config &= ~(1 << 7); /* OS */
+ else
+ return;
+ }
+
+ if ((s->config >> 1) & 1) { /* TM */
+ if (s->temperature >= s->limit[1])
+ s->alarm = 1;
+ else if (s->temperature < s->limit[0])
+ s->alarm = 1;
+ } else {
+ if (s->temperature >= s->limit[1])
+ s->alarm = 1;
+ else if (s->temperature < s->limit[0])
+ s->alarm = 0;
+ }
+
+ tmp105_interrupt_update(s);
+}
+
+static void tmp105_get_temperature(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ TMP105State *s = TMP105(obj);
+ int64_t value = s->temperature * 1000 / 256;
+
+ visit_type_int(v, &value, name, errp);
+}
+
+/* Units are 0.001 centigrades relative to 0 C. s->temperature is 8.8
+ * fixed point, so units are 1/256 centigrades. A simple ratio will do.
+ */
+static void tmp105_set_temperature(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ TMP105State *s = TMP105(obj);
+ Error *local_err = NULL;
+ int64_t temp;
+
+ visit_type_int(v, &temp, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ if (temp >= 128000 || temp < -128000) {
+ error_setg(errp, "value %" PRId64 ".%03" PRIu64 " °C is out of range",
+ temp / 1000, temp % 1000);
+ return;
+ }
+
+ s->temperature = (int16_t) (temp * 256 / 1000);
+
+ tmp105_alarm_update(s);
+}
+
+static const int tmp105_faultq[4] = { 1, 2, 4, 6 };
+
+static void tmp105_read(TMP105State *s)
+{
+ s->len = 0;
+
+ if ((s->config >> 1) & 1) { /* TM */
+ s->alarm = 0;
+ tmp105_interrupt_update(s);
+ }
+
+ switch (s->pointer & 3) {
+ case TMP105_REG_TEMPERATURE:
+ s->buf[s->len ++] = (((uint16_t) s->temperature) >> 8);
+ s->buf[s->len ++] = (((uint16_t) s->temperature) >> 0) &
+ (0xf0 << ((~s->config >> 5) & 3)); /* R */
+ break;
+
+ case TMP105_REG_CONFIG:
+ s->buf[s->len ++] = s->config;
+ break;
+
+ case TMP105_REG_T_LOW:
+ s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 8;
+ s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 0;
+ break;
+
+ case TMP105_REG_T_HIGH:
+ s->buf[s->len ++] = ((uint16_t) s->limit[1]) >> 8;
+ s->buf[s->len ++] = ((uint16_t) s->limit[1]) >> 0;
+ break;
+ }
+}
+
+static void tmp105_write(TMP105State *s)
+{
+ switch (s->pointer & 3) {
+ case TMP105_REG_TEMPERATURE:
+ break;
+
+ case TMP105_REG_CONFIG:
+ if (s->buf[0] & ~s->config & (1 << 0)) /* SD */
+ printf("%s: TMP105 shutdown\n", __FUNCTION__);
+ s->config = s->buf[0];
+ s->faults = tmp105_faultq[(s->config >> 3) & 3]; /* F */
+ tmp105_alarm_update(s);
+ break;
+
+ case TMP105_REG_T_LOW:
+ case TMP105_REG_T_HIGH:
+ if (s->len >= 3)
+ s->limit[s->pointer & 1] = (int16_t)
+ ((((uint16_t) s->buf[0]) << 8) | s->buf[1]);
+ tmp105_alarm_update(s);
+ break;
+ }
+}
+
+static int tmp105_rx(I2CSlave *i2c)
+{
+ TMP105State *s = TMP105(i2c);
+
+ if (s->len < 2) {
+ return s->buf[s->len ++];
+ } else {
+ return 0xff;
+ }
+}
+
+static int tmp105_tx(I2CSlave *i2c, uint8_t data)
+{
+ TMP105State *s = TMP105(i2c);
+
+ if (s->len == 0) {
+ s->pointer = data;
+ s->len++;
+ } else {
+ if (s->len <= 2) {
+ s->buf[s->len - 1] = data;
+ }
+ s->len++;
+ tmp105_write(s);
+ }
+
+ return 0;
+}
+
+static void tmp105_event(I2CSlave *i2c, enum i2c_event event)
+{
+ TMP105State *s = TMP105(i2c);
+
+ if (event == I2C_START_RECV) {
+ tmp105_read(s);
+ }
+
+ s->len = 0;
+}
+
+static int tmp105_post_load(void *opaque, int version_id)
+{
+ TMP105State *s = opaque;
+
+ s->faults = tmp105_faultq[(s->config >> 3) & 3]; /* F */
+
+ tmp105_interrupt_update(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_tmp105 = {
+ .name = "TMP105",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = tmp105_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(len, TMP105State),
+ VMSTATE_UINT8_ARRAY(buf, TMP105State, 2),
+ VMSTATE_UINT8(pointer, TMP105State),
+ VMSTATE_UINT8(config, TMP105State),
+ VMSTATE_INT16(temperature, TMP105State),
+ VMSTATE_INT16_ARRAY(limit, TMP105State, 2),
+ VMSTATE_UINT8(alarm, TMP105State),
+ VMSTATE_I2C_SLAVE(i2c, TMP105State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tmp105_reset(I2CSlave *i2c)
+{
+ TMP105State *s = TMP105(i2c);
+
+ s->temperature = 0;
+ s->pointer = 0;
+ s->config = 0;
+ s->faults = tmp105_faultq[(s->config >> 3) & 3];
+ s->alarm = 0;
+
+ tmp105_interrupt_update(s);
+}
+
+static int tmp105_init(I2CSlave *i2c)
+{
+ TMP105State *s = TMP105(i2c);
+
+ qdev_init_gpio_out(&i2c->qdev, &s->pin, 1);
+
+ tmp105_reset(&s->i2c);
+
+ return 0;
+}
+
+static void tmp105_initfn(Object *obj)
+{
+ object_property_add(obj, "temperature", "int",
+ tmp105_get_temperature,
+ tmp105_set_temperature, NULL, NULL, NULL);
+}
+
+static void tmp105_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = tmp105_init;
+ k->event = tmp105_event;
+ k->recv = tmp105_rx;
+ k->send = tmp105_tx;
+ dc->vmsd = &vmstate_tmp105;
+}
+
+static const TypeInfo tmp105_info = {
+ .name = TYPE_TMP105,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(TMP105State),
+ .instance_init = tmp105_initfn,
+ .class_init = tmp105_class_init,
+};
+
+static void tmp105_register_types(void)
+{
+ type_register_static(&tmp105_info);
+}
+
+type_init(tmp105_register_types)
diff --git a/hw/misc/tmp105.h b/hw/misc/tmp105.h
new file mode 100644
index 00000000..9ba05ecc
--- /dev/null
+++ b/hw/misc/tmp105.h
@@ -0,0 +1,47 @@
+/*
+ * Texas Instruments TMP105 Temperature Sensor
+ *
+ * Browse the data sheet:
+ *
+ * http://www.ti.com/lit/gpn/tmp105
+ *
+ * Copyright (C) 2012 Alex Horn <alex.horn@cs.ox.ac.uk>
+ * Copyright (C) 2008-2012 Andrzej Zaborowski <balrogg@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+#ifndef QEMU_TMP105_H
+#define QEMU_TMP105_H
+
+#include "hw/i2c/i2c.h"
+#include "hw/misc/tmp105_regs.h"
+
+#define TYPE_TMP105 "tmp105"
+#define TMP105(obj) OBJECT_CHECK(TMP105State, (obj), TYPE_TMP105)
+
+/**
+ * TMP105State:
+ * @config: Bits 5 and 6 (value 32 and 64) determine the precision of the
+ * temperature. See Table 8 in the data sheet.
+ *
+ * @see_also: http://www.ti.com/lit/gpn/tmp105
+ */
+typedef struct TMP105State {
+ /*< private >*/
+ I2CSlave i2c;
+ /*< public >*/
+
+ uint8_t len;
+ uint8_t buf[2];
+ qemu_irq pin;
+
+ uint8_t pointer;
+ uint8_t config;
+ int16_t temperature;
+ int16_t limit[2];
+ int faults;
+ uint8_t alarm;
+} TMP105State;
+
+#endif
diff --git a/hw/misc/vmport.c b/hw/misc/vmport.c
new file mode 100644
index 00000000..cd5716a4
--- /dev/null
+++ b/hw/misc/vmport.c
@@ -0,0 +1,181 @@
+/*
+ * QEMU VMPort emulation
+ *
+ * Copyright (C) 2007 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.
+ */
+#include "hw/hw.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+#include "sysemu/kvm.h"
+#include "hw/qdev.h"
+
+//#define VMPORT_DEBUG
+
+#define VMPORT_CMD_GETVERSION 0x0a
+#define VMPORT_CMD_GETRAMSIZE 0x14
+
+#define VMPORT_ENTRIES 0x2c
+#define VMPORT_MAGIC 0x564D5868
+
+#define TYPE_VMPORT "vmport"
+#define VMPORT(obj) OBJECT_CHECK(VMPortState, (obj), TYPE_VMPORT)
+
+typedef struct VMPortState
+{
+ ISADevice parent_obj;
+
+ MemoryRegion io;
+ VMPortReadFunc *func[VMPORT_ENTRIES];
+ void *opaque[VMPORT_ENTRIES];
+} VMPortState;
+
+static VMPortState *port_state;
+
+void vmport_register(unsigned char command, VMPortReadFunc *func, void *opaque)
+{
+ if (command >= VMPORT_ENTRIES)
+ return;
+
+ port_state->func[command] = func;
+ port_state->opaque[command] = opaque;
+}
+
+static uint64_t vmport_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ VMPortState *s = opaque;
+ CPUState *cs = current_cpu;
+ X86CPU *cpu = X86_CPU(cs);
+ CPUX86State *env = &cpu->env;
+ unsigned char command;
+ uint32_t eax;
+
+ cpu_synchronize_state(cs);
+
+ eax = env->regs[R_EAX];
+ if (eax != VMPORT_MAGIC)
+ return eax;
+
+ command = env->regs[R_ECX];
+ if (command >= VMPORT_ENTRIES)
+ return eax;
+ if (!s->func[command])
+ {
+#ifdef VMPORT_DEBUG
+ fprintf(stderr, "vmport: unknown command %x\n", command);
+#endif
+ return eax;
+ }
+
+ return s->func[command](s->opaque[command], addr);
+}
+
+static void vmport_ioport_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ X86CPU *cpu = X86_CPU(current_cpu);
+
+ cpu->env.regs[R_EAX] = vmport_ioport_read(opaque, addr, 4);
+}
+
+static uint32_t vmport_cmd_get_version(void *opaque, uint32_t addr)
+{
+ X86CPU *cpu = X86_CPU(current_cpu);
+
+ cpu->env.regs[R_EBX] = VMPORT_MAGIC;
+ return 6;
+}
+
+static uint32_t vmport_cmd_ram_size(void *opaque, uint32_t addr)
+{
+ X86CPU *cpu = X86_CPU(current_cpu);
+
+ cpu->env.regs[R_EBX] = 0x1177;
+ return ram_size;
+}
+
+/* vmmouse helpers */
+void vmmouse_get_data(uint32_t *data)
+{
+ X86CPU *cpu = X86_CPU(current_cpu);
+ CPUX86State *env = &cpu->env;
+
+ data[0] = env->regs[R_EAX]; data[1] = env->regs[R_EBX];
+ data[2] = env->regs[R_ECX]; data[3] = env->regs[R_EDX];
+ data[4] = env->regs[R_ESI]; data[5] = env->regs[R_EDI];
+}
+
+void vmmouse_set_data(const uint32_t *data)
+{
+ X86CPU *cpu = X86_CPU(current_cpu);
+ CPUX86State *env = &cpu->env;
+
+ env->regs[R_EAX] = data[0]; env->regs[R_EBX] = data[1];
+ env->regs[R_ECX] = data[2]; env->regs[R_EDX] = data[3];
+ env->regs[R_ESI] = data[4]; env->regs[R_EDI] = data[5];
+}
+
+static const MemoryRegionOps vmport_ops = {
+ .read = vmport_ioport_read,
+ .write = vmport_ioport_write,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void vmport_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ VMPortState *s = VMPORT(dev);
+
+ memory_region_init_io(&s->io, OBJECT(s), &vmport_ops, s, "vmport", 1);
+ isa_register_ioport(isadev, &s->io, 0x5658);
+
+ port_state = s;
+ /* Register some generic port commands */
+ vmport_register(VMPORT_CMD_GETVERSION, vmport_cmd_get_version, NULL);
+ vmport_register(VMPORT_CMD_GETRAMSIZE, vmport_cmd_ram_size, NULL);
+}
+
+static void vmport_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = vmport_realizefn;
+ /* Reason: realize sets global port_state */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo vmport_info = {
+ .name = TYPE_VMPORT,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(VMPortState),
+ .class_init = vmport_class_initfn,
+};
+
+static void vmport_register_types(void)
+{
+ type_register_static(&vmport_info);
+}
+
+type_init(vmport_register_types)
diff --git a/hw/misc/zynq_slcr.c b/hw/misc/zynq_slcr.c
new file mode 100644
index 00000000..3d787085
--- /dev/null
+++ b/hw/misc/zynq_slcr.c
@@ -0,0 +1,458 @@
+/*
+ * Status and system control registers for Xilinx Zynq Platform
+ *
+ * Copyright (c) 2011 Michal Simek <monstr@monstr.eu>
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ * Based on hw/arm_sysctl.c, written by Paul Brook
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+
+#ifndef ZYNQ_SLCR_ERR_DEBUG
+#define ZYNQ_SLCR_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT(...) do { \
+ if (ZYNQ_SLCR_ERR_DEBUG) { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } \
+ } while (0);
+
+#define XILINX_LOCK_KEY 0x767b
+#define XILINX_UNLOCK_KEY 0xdf0d
+
+#define R_PSS_RST_CTRL_SOFT_RST 0x1
+
+enum {
+ SCL = 0x000 / 4,
+ LOCK,
+ UNLOCK,
+ LOCKSTA,
+
+ ARM_PLL_CTRL = 0x100 / 4,
+ DDR_PLL_CTRL,
+ IO_PLL_CTRL,
+ PLL_STATUS,
+ ARM_PLL_CFG,
+ DDR_PLL_CFG,
+ IO_PLL_CFG,
+
+ ARM_CLK_CTRL = 0x120 / 4,
+ DDR_CLK_CTRL,
+ DCI_CLK_CTRL,
+ APER_CLK_CTRL,
+ USB0_CLK_CTRL,
+ USB1_CLK_CTRL,
+ GEM0_RCLK_CTRL,
+ GEM1_RCLK_CTRL,
+ GEM0_CLK_CTRL,
+ GEM1_CLK_CTRL,
+ SMC_CLK_CTRL,
+ LQSPI_CLK_CTRL,
+ SDIO_CLK_CTRL,
+ UART_CLK_CTRL,
+ SPI_CLK_CTRL,
+ CAN_CLK_CTRL,
+ CAN_MIOCLK_CTRL,
+ DBG_CLK_CTRL,
+ PCAP_CLK_CTRL,
+ TOPSW_CLK_CTRL,
+
+#define FPGA_CTRL_REGS(n, start) \
+ FPGA ## n ## _CLK_CTRL = (start) / 4, \
+ FPGA ## n ## _THR_CTRL, \
+ FPGA ## n ## _THR_CNT, \
+ FPGA ## n ## _THR_STA,
+ FPGA_CTRL_REGS(0, 0x170)
+ FPGA_CTRL_REGS(1, 0x180)
+ FPGA_CTRL_REGS(2, 0x190)
+ FPGA_CTRL_REGS(3, 0x1a0)
+
+ BANDGAP_TRIP = 0x1b8 / 4,
+ PLL_PREDIVISOR = 0x1c0 / 4,
+ CLK_621_TRUE,
+
+ PSS_RST_CTRL = 0x200 / 4,
+ DDR_RST_CTRL,
+ TOPSW_RESET_CTRL,
+ DMAC_RST_CTRL,
+ USB_RST_CTRL,
+ GEM_RST_CTRL,
+ SDIO_RST_CTRL,
+ SPI_RST_CTRL,
+ CAN_RST_CTRL,
+ I2C_RST_CTRL,
+ UART_RST_CTRL,
+ GPIO_RST_CTRL,
+ LQSPI_RST_CTRL,
+ SMC_RST_CTRL,
+ OCM_RST_CTRL,
+ FPGA_RST_CTRL = 0x240 / 4,
+ A9_CPU_RST_CTRL,
+
+ RS_AWDT_CTRL = 0x24c / 4,
+ RST_REASON,
+
+ REBOOT_STATUS = 0x258 / 4,
+ BOOT_MODE,
+
+ APU_CTRL = 0x300 / 4,
+ WDT_CLK_SEL,
+
+ TZ_DMA_NS = 0x440 / 4,
+ TZ_DMA_IRQ_NS,
+ TZ_DMA_PERIPH_NS,
+
+ PSS_IDCODE = 0x530 / 4,
+
+ DDR_URGENT = 0x600 / 4,
+ DDR_CAL_START = 0x60c / 4,
+ DDR_REF_START = 0x614 / 4,
+ DDR_CMD_STA,
+ DDR_URGENT_SEL,
+ DDR_DFI_STATUS,
+
+ MIO = 0x700 / 4,
+#define MIO_LENGTH 54
+
+ MIO_LOOPBACK = 0x804 / 4,
+ MIO_MST_TRI0,
+ MIO_MST_TRI1,
+
+ SD0_WP_CD_SEL = 0x830 / 4,
+ SD1_WP_CD_SEL,
+
+ LVL_SHFTR_EN = 0x900 / 4,
+ OCM_CFG = 0x910 / 4,
+
+ CPU_RAM = 0xa00 / 4,
+
+ IOU = 0xa30 / 4,
+
+ DMAC_RAM = 0xa50 / 4,
+
+ AFI0 = 0xa60 / 4,
+ AFI1 = AFI0 + 3,
+ AFI2 = AFI1 + 3,
+ AFI3 = AFI2 + 3,
+#define AFI_LENGTH 3
+
+ OCM = 0xa90 / 4,
+
+ DEVCI_RAM = 0xaa0 / 4,
+
+ CSG_RAM = 0xab0 / 4,
+
+ GPIOB_CTRL = 0xb00 / 4,
+ GPIOB_CFG_CMOS18,
+ GPIOB_CFG_CMOS25,
+ GPIOB_CFG_CMOS33,
+ GPIOB_CFG_HSTL = 0xb14 / 4,
+ GPIOB_DRVR_BIAS_CTRL,
+
+ DDRIOB = 0xb40 / 4,
+#define DDRIOB_LENGTH 14
+};
+
+#define ZYNQ_SLCR_MMIO_SIZE 0x1000
+#define ZYNQ_SLCR_NUM_REGS (ZYNQ_SLCR_MMIO_SIZE / 4)
+
+#define TYPE_ZYNQ_SLCR "xilinx,zynq_slcr"
+#define ZYNQ_SLCR(obj) OBJECT_CHECK(ZynqSLCRState, (obj), TYPE_ZYNQ_SLCR)
+
+typedef struct ZynqSLCRState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t regs[ZYNQ_SLCR_NUM_REGS];
+} ZynqSLCRState;
+
+static void zynq_slcr_reset(DeviceState *d)
+{
+ ZynqSLCRState *s = ZYNQ_SLCR(d);
+ int i;
+
+ DB_PRINT("RESET\n");
+
+ s->regs[LOCKSTA] = 1;
+ /* 0x100 - 0x11C */
+ s->regs[ARM_PLL_CTRL] = 0x0001A008;
+ s->regs[DDR_PLL_CTRL] = 0x0001A008;
+ s->regs[IO_PLL_CTRL] = 0x0001A008;
+ s->regs[PLL_STATUS] = 0x0000003F;
+ s->regs[ARM_PLL_CFG] = 0x00014000;
+ s->regs[DDR_PLL_CFG] = 0x00014000;
+ s->regs[IO_PLL_CFG] = 0x00014000;
+
+ /* 0x120 - 0x16C */
+ s->regs[ARM_CLK_CTRL] = 0x1F000400;
+ s->regs[DDR_CLK_CTRL] = 0x18400003;
+ s->regs[DCI_CLK_CTRL] = 0x01E03201;
+ s->regs[APER_CLK_CTRL] = 0x01FFCCCD;
+ s->regs[USB0_CLK_CTRL] = s->regs[USB1_CLK_CTRL] = 0x00101941;
+ s->regs[GEM0_RCLK_CTRL] = s->regs[GEM1_RCLK_CTRL] = 0x00000001;
+ s->regs[GEM0_CLK_CTRL] = s->regs[GEM1_CLK_CTRL] = 0x00003C01;
+ s->regs[SMC_CLK_CTRL] = 0x00003C01;
+ s->regs[LQSPI_CLK_CTRL] = 0x00002821;
+ s->regs[SDIO_CLK_CTRL] = 0x00001E03;
+ s->regs[UART_CLK_CTRL] = 0x00003F03;
+ s->regs[SPI_CLK_CTRL] = 0x00003F03;
+ s->regs[CAN_CLK_CTRL] = 0x00501903;
+ s->regs[DBG_CLK_CTRL] = 0x00000F03;
+ s->regs[PCAP_CLK_CTRL] = 0x00000F01;
+
+ /* 0x170 - 0x1AC */
+ s->regs[FPGA0_CLK_CTRL] = s->regs[FPGA1_CLK_CTRL] = s->regs[FPGA2_CLK_CTRL]
+ = s->regs[FPGA3_CLK_CTRL] = 0x00101800;
+ s->regs[FPGA0_THR_STA] = s->regs[FPGA1_THR_STA] = s->regs[FPGA2_THR_STA]
+ = s->regs[FPGA3_THR_STA] = 0x00010000;
+
+ /* 0x1B0 - 0x1D8 */
+ s->regs[BANDGAP_TRIP] = 0x0000001F;
+ s->regs[PLL_PREDIVISOR] = 0x00000001;
+ s->regs[CLK_621_TRUE] = 0x00000001;
+
+ /* 0x200 - 0x25C */
+ s->regs[FPGA_RST_CTRL] = 0x01F33F0F;
+ s->regs[RST_REASON] = 0x00000040;
+
+ s->regs[BOOT_MODE] = 0x00000001;
+
+ /* 0x700 - 0x7D4 */
+ for (i = 0; i < 54; i++) {
+ s->regs[MIO + i] = 0x00001601;
+ }
+ for (i = 2; i <= 8; i++) {
+ s->regs[MIO + i] = 0x00000601;
+ }
+
+ s->regs[MIO_MST_TRI0] = s->regs[MIO_MST_TRI1] = 0xFFFFFFFF;
+
+ s->regs[CPU_RAM + 0] = s->regs[CPU_RAM + 1] = s->regs[CPU_RAM + 3]
+ = s->regs[CPU_RAM + 4] = s->regs[CPU_RAM + 7]
+ = 0x00010101;
+ s->regs[CPU_RAM + 2] = s->regs[CPU_RAM + 5] = 0x01010101;
+ s->regs[CPU_RAM + 6] = 0x00000001;
+
+ s->regs[IOU + 0] = s->regs[IOU + 1] = s->regs[IOU + 2] = s->regs[IOU + 3]
+ = 0x09090909;
+ s->regs[IOU + 4] = s->regs[IOU + 5] = 0x00090909;
+ s->regs[IOU + 6] = 0x00000909;
+
+ s->regs[DMAC_RAM] = 0x00000009;
+
+ s->regs[AFI0 + 0] = s->regs[AFI0 + 1] = 0x09090909;
+ s->regs[AFI1 + 0] = s->regs[AFI1 + 1] = 0x09090909;
+ s->regs[AFI2 + 0] = s->regs[AFI2 + 1] = 0x09090909;
+ s->regs[AFI3 + 0] = s->regs[AFI3 + 1] = 0x09090909;
+ s->regs[AFI0 + 2] = s->regs[AFI1 + 2] = s->regs[AFI2 + 2]
+ = s->regs[AFI3 + 2] = 0x00000909;
+
+ s->regs[OCM + 0] = 0x01010101;
+ s->regs[OCM + 1] = s->regs[OCM + 2] = 0x09090909;
+
+ s->regs[DEVCI_RAM] = 0x00000909;
+ s->regs[CSG_RAM] = 0x00000001;
+
+ s->regs[DDRIOB + 0] = s->regs[DDRIOB + 1] = s->regs[DDRIOB + 2]
+ = s->regs[DDRIOB + 3] = 0x00000e00;
+ s->regs[DDRIOB + 4] = s->regs[DDRIOB + 5] = s->regs[DDRIOB + 6]
+ = 0x00000e00;
+ s->regs[DDRIOB + 12] = 0x00000021;
+}
+
+
+static bool zynq_slcr_check_offset(hwaddr offset, bool rnw)
+{
+ switch (offset) {
+ case LOCK:
+ case UNLOCK:
+ case DDR_CAL_START:
+ case DDR_REF_START:
+ return !rnw; /* Write only */
+ case LOCKSTA:
+ case FPGA0_THR_STA:
+ case FPGA1_THR_STA:
+ case FPGA2_THR_STA:
+ case FPGA3_THR_STA:
+ case BOOT_MODE:
+ case PSS_IDCODE:
+ case DDR_CMD_STA:
+ case DDR_DFI_STATUS:
+ case PLL_STATUS:
+ return rnw;/* read only */
+ case SCL:
+ case ARM_PLL_CTRL ... IO_PLL_CTRL:
+ case ARM_PLL_CFG ... IO_PLL_CFG:
+ case ARM_CLK_CTRL ... TOPSW_CLK_CTRL:
+ case FPGA0_CLK_CTRL ... FPGA0_THR_CNT:
+ case FPGA1_CLK_CTRL ... FPGA1_THR_CNT:
+ case FPGA2_CLK_CTRL ... FPGA2_THR_CNT:
+ case FPGA3_CLK_CTRL ... FPGA3_THR_CNT:
+ case BANDGAP_TRIP:
+ case PLL_PREDIVISOR:
+ case CLK_621_TRUE:
+ case PSS_RST_CTRL ... A9_CPU_RST_CTRL:
+ case RS_AWDT_CTRL:
+ case RST_REASON:
+ case REBOOT_STATUS:
+ case APU_CTRL:
+ case WDT_CLK_SEL:
+ case TZ_DMA_NS ... TZ_DMA_PERIPH_NS:
+ case DDR_URGENT:
+ case DDR_URGENT_SEL:
+ case MIO ... MIO + MIO_LENGTH - 1:
+ case MIO_LOOPBACK ... MIO_MST_TRI1:
+ case SD0_WP_CD_SEL:
+ case SD1_WP_CD_SEL:
+ case LVL_SHFTR_EN:
+ case OCM_CFG:
+ case CPU_RAM:
+ case IOU:
+ case DMAC_RAM:
+ case AFI0 ... AFI3 + AFI_LENGTH - 1:
+ case OCM:
+ case DEVCI_RAM:
+ case CSG_RAM:
+ case GPIOB_CTRL ... GPIOB_CFG_CMOS33:
+ case GPIOB_CFG_HSTL:
+ case GPIOB_DRVR_BIAS_CTRL:
+ case DDRIOB ... DDRIOB + DDRIOB_LENGTH - 1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static uint64_t zynq_slcr_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ ZynqSLCRState *s = opaque;
+ offset /= 4;
+ uint32_t ret = s->regs[offset];
+
+ if (!zynq_slcr_check_offset(offset, true)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "zynq_slcr: Invalid read access to "
+ " addr %" HWADDR_PRIx "\n", offset * 4);
+ }
+
+ DB_PRINT("addr: %08" HWADDR_PRIx " data: %08" PRIx32 "\n", offset * 4, ret);
+ return ret;
+}
+
+static void zynq_slcr_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ ZynqSLCRState *s = (ZynqSLCRState *)opaque;
+ offset /= 4;
+
+ DB_PRINT("addr: %08" HWADDR_PRIx " data: %08" PRIx64 "\n", offset * 4, val);
+
+ if (!zynq_slcr_check_offset(offset, false)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "zynq_slcr: Invalid write access to "
+ "addr %" HWADDR_PRIx "\n", offset * 4);
+ return;
+ }
+
+ switch (offset) {
+ case SCL:
+ s->regs[SCL] = val & 0x1;
+ return;
+ case LOCK:
+ if ((val & 0xFFFF) == XILINX_LOCK_KEY) {
+ DB_PRINT("XILINX LOCK 0xF8000000 + 0x%x <= 0x%x\n", (int)offset,
+ (unsigned)val & 0xFFFF);
+ s->regs[LOCKSTA] = 1;
+ } else {
+ DB_PRINT("WRONG XILINX LOCK KEY 0xF8000000 + 0x%x <= 0x%x\n",
+ (int)offset, (unsigned)val & 0xFFFF);
+ }
+ return;
+ case UNLOCK:
+ if ((val & 0xFFFF) == XILINX_UNLOCK_KEY) {
+ DB_PRINT("XILINX UNLOCK 0xF8000000 + 0x%x <= 0x%x\n", (int)offset,
+ (unsigned)val & 0xFFFF);
+ s->regs[LOCKSTA] = 0;
+ } else {
+ DB_PRINT("WRONG XILINX UNLOCK KEY 0xF8000000 + 0x%x <= 0x%x\n",
+ (int)offset, (unsigned)val & 0xFFFF);
+ }
+ return;
+ }
+
+ if (s->regs[LOCKSTA]) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SCLR registers are locked. Unlock them first\n");
+ return;
+ }
+ s->regs[offset] = val;
+
+ switch (offset) {
+ case PSS_RST_CTRL:
+ if (val & R_PSS_RST_CTRL_SOFT_RST) {
+ qemu_system_reset_request();
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps slcr_ops = {
+ .read = zynq_slcr_read,
+ .write = zynq_slcr_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void zynq_slcr_init(Object *obj)
+{
+ ZynqSLCRState *s = ZYNQ_SLCR(obj);
+
+ memory_region_init_io(&s->iomem, obj, &slcr_ops, s, "slcr",
+ ZYNQ_SLCR_MMIO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription vmstate_zynq_slcr = {
+ .name = "zynq_slcr",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, ZynqSLCRState, ZYNQ_SLCR_NUM_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void zynq_slcr_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_zynq_slcr;
+ dc->reset = zynq_slcr_reset;
+}
+
+static const TypeInfo zynq_slcr_info = {
+ .class_init = zynq_slcr_class_init,
+ .name = TYPE_ZYNQ_SLCR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ZynqSLCRState),
+ .instance_init = zynq_slcr_init,
+};
+
+static void zynq_slcr_register_types(void)
+{
+ type_register_static(&zynq_slcr_info);
+}
+
+type_init(zynq_slcr_register_types)
diff --git a/hw/moxie/Makefile.objs b/hw/moxie/Makefile.objs
new file mode 100644
index 00000000..bfc90012
--- /dev/null
+++ b/hw/moxie/Makefile.objs
@@ -0,0 +1,2 @@
+# moxie boards
+obj-y += moxiesim.o
diff --git a/hw/moxie/moxiesim.c b/hw/moxie/moxiesim.c
new file mode 100644
index 00000000..80bcc5b4
--- /dev/null
+++ b/hw/moxie/moxiesim.c
@@ -0,0 +1,161 @@
+/*
+ * QEMU/moxiesim emulation
+ *
+ * Emulates a very simple machine model similar to the one used by the
+ * GDB moxie simulator.
+ *
+ * Copyright (c) 2008, 2009, 2010, 2013 Anthony Green
+ *
+ * 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.
+ */
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/isa.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "hw/char/serial.h"
+#include "exec/address-spaces.h"
+
+#define PHYS_MEM_BASE 0x80000000
+
+typedef struct {
+ uint64_t ram_size;
+ const char *kernel_filename;
+ const char *kernel_cmdline;
+ const char *initrd_filename;
+} LoaderParams;
+
+static void load_kernel(MoxieCPU *cpu, LoaderParams *loader_params)
+{
+ uint64_t entry, kernel_low, kernel_high;
+ long kernel_size;
+ long initrd_size;
+ ram_addr_t initrd_offset;
+
+ kernel_size = load_elf(loader_params->kernel_filename, NULL, NULL,
+ &entry, &kernel_low, &kernel_high, 1,
+ ELF_MACHINE, 0);
+
+ if (kernel_size <= 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ loader_params->kernel_filename);
+ exit(1);
+ }
+
+ /* load initrd */
+ initrd_size = 0;
+ initrd_offset = 0;
+ if (loader_params->initrd_filename) {
+ initrd_size = get_image_size(loader_params->initrd_filename);
+ if (initrd_size > 0) {
+ initrd_offset = (kernel_high + ~TARGET_PAGE_MASK)
+ & TARGET_PAGE_MASK;
+ if (initrd_offset + initrd_size > loader_params->ram_size) {
+ fprintf(stderr,
+ "qemu: memory too small for initial ram disk '%s'\n",
+ loader_params->initrd_filename);
+ exit(1);
+ }
+ initrd_size = load_image_targphys(loader_params->initrd_filename,
+ initrd_offset,
+ ram_size);
+ }
+ if (initrd_size == (target_ulong)-1) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ loader_params->initrd_filename);
+ exit(1);
+ }
+ }
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ MoxieCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+static void moxiesim_init(MachineState *machine)
+{
+ MoxieCPU *cpu = NULL;
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ CPUMoxieState *env;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *rom = g_new(MemoryRegion, 1);
+ hwaddr ram_base = 0x200000;
+ LoaderParams loader_params;
+
+ /* Init CPUs. */
+ if (cpu_model == NULL) {
+ cpu_model = "MoxieLite-moxie-cpu";
+ }
+ cpu = cpu_moxie_init(cpu_model);
+ if (!cpu) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ qemu_register_reset(main_cpu_reset, cpu);
+
+ /* Allocate RAM. */
+ memory_region_init_ram(ram, NULL, "moxiesim.ram", ram_size, &error_abort);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(address_space_mem, ram_base, ram);
+
+ memory_region_init_ram(rom, NULL, "moxie.rom", 128*0x1000, &error_abort);
+ vmstate_register_ram_global(rom);
+ memory_region_add_subregion(get_system_memory(), 0x1000, rom);
+
+ if (kernel_filename) {
+ loader_params.ram_size = ram_size;
+ loader_params.kernel_filename = kernel_filename;
+ loader_params.kernel_cmdline = kernel_cmdline;
+ loader_params.initrd_filename = initrd_filename;
+ load_kernel(cpu, &loader_params);
+ }
+
+ /* A single 16450 sits at offset 0x3f8. */
+ if (serial_hds[0]) {
+ serial_mm_init(address_space_mem, 0x3f8, 0, env->irq[4],
+ 8000000/16, serial_hds[0], DEVICE_LITTLE_ENDIAN);
+ }
+}
+
+static QEMUMachine moxiesim_machine = {
+ .name = "moxiesim",
+ .desc = "Moxie simulator platform",
+ .init = moxiesim_init,
+ .is_default = 1,
+};
+
+static void moxie_machine_init(void)
+{
+ qemu_register_machine(&moxiesim_machine);
+}
+
+machine_init(moxie_machine_init)
diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs
new file mode 100644
index 00000000..98801739
--- /dev/null
+++ b/hw/net/Makefile.objs
@@ -0,0 +1,42 @@
+common-obj-$(CONFIG_DP8393X) += dp8393x.o
+common-obj-$(CONFIG_XEN_BACKEND) += xen_nic.o
+
+# PCI network cards
+common-obj-$(CONFIG_NE2000_PCI) += ne2000.o
+common-obj-$(CONFIG_EEPRO100_PCI) += eepro100.o
+common-obj-$(CONFIG_PCNET_PCI) += pcnet-pci.o
+common-obj-$(CONFIG_PCNET_COMMON) += pcnet.o
+common-obj-$(CONFIG_E1000_PCI) += e1000.o
+common-obj-$(CONFIG_RTL8139_PCI) += rtl8139.o
+common-obj-$(CONFIG_VMXNET3_PCI) += vmxnet_tx_pkt.o vmxnet_rx_pkt.o
+common-obj-$(CONFIG_VMXNET3_PCI) += vmxnet3.o
+
+common-obj-$(CONFIG_SMC91C111) += smc91c111.o
+common-obj-$(CONFIG_LAN9118) += lan9118.o
+common-obj-$(CONFIG_NE2000_ISA) += ne2000-isa.o
+common-obj-$(CONFIG_OPENCORES_ETH) += opencores_eth.o
+common-obj-$(CONFIG_XGMAC) += xgmac.o
+common-obj-$(CONFIG_MIPSNET) += mipsnet.o
+common-obj-$(CONFIG_XILINX_AXI) += xilinx_axienet.o
+common-obj-$(CONFIG_ALLWINNER_EMAC) += allwinner_emac.o
+
+common-obj-$(CONFIG_CADENCE) += cadence_gem.o
+common-obj-$(CONFIG_STELLARIS_ENET) += stellaris_enet.o
+common-obj-$(CONFIG_LANCE) += lance.o
+
+obj-$(CONFIG_ETRAXFS) += etraxfs_eth.o
+obj-$(CONFIG_COLDFIRE) += mcf_fec.o
+obj-$(CONFIG_MILKYMIST) += milkymist-minimac2.o
+obj-$(CONFIG_PSERIES) += spapr_llan.o
+obj-$(CONFIG_XILINX_ETHLITE) += xilinx_ethlite.o
+
+obj-$(CONFIG_VIRTIO) += virtio-net.o
+obj-y += vhost_net.o
+
+obj-$(CONFIG_ETSEC) += fsl_etsec/etsec.o fsl_etsec/registers.o \
+ fsl_etsec/rings.o fsl_etsec/miim.o
+
+common-obj-$(CONFIG_ROCKER) += rocker/rocker.o rocker/rocker_fp.o \
+ rocker/rocker_desc.o rocker/rocker_world.o \
+ rocker/rocker_of_dpa.o
+obj-$(call lnot,$(CONFIG_ROCKER)) += rocker/qmp-norocker.o
diff --git a/hw/net/allwinner_emac.c b/hw/net/allwinner_emac.c
new file mode 100644
index 00000000..0407dee6
--- /dev/null
+++ b/hw/net/allwinner_emac.c
@@ -0,0 +1,533 @@
+/*
+ * Emulation of Allwinner EMAC Fast Ethernet controller and
+ * Realtek RTL8201CP PHY
+ *
+ * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com>
+ *
+ * This model is based on reverse-engineering of Linux kernel driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "qemu/fifo8.h"
+#include "hw/net/allwinner_emac.h"
+#include <zlib.h>
+
+static uint8_t padding[60];
+
+static void mii_set_link(RTL8201CPState *mii, bool link_ok)
+{
+ if (link_ok) {
+ mii->bmsr |= MII_BMSR_LINK_ST | MII_BMSR_AN_COMP;
+ mii->anlpar |= MII_ANAR_TXFD | MII_ANAR_10FD | MII_ANAR_10 |
+ MII_ANAR_CSMACD;
+ } else {
+ mii->bmsr &= ~(MII_BMSR_LINK_ST | MII_BMSR_AN_COMP);
+ mii->anlpar = MII_ANAR_TX;
+ }
+}
+
+static void mii_reset(RTL8201CPState *mii, bool link_ok)
+{
+ mii->bmcr = MII_BMCR_FD | MII_BMCR_AUTOEN | MII_BMCR_SPEED;
+ mii->bmsr = MII_BMSR_100TX_FD | MII_BMSR_100TX_HD | MII_BMSR_10T_FD |
+ MII_BMSR_10T_HD | MII_BMSR_MFPS | MII_BMSR_AUTONEG;
+ mii->anar = MII_ANAR_TXFD | MII_ANAR_TX | MII_ANAR_10FD | MII_ANAR_10 |
+ MII_ANAR_CSMACD;
+ mii->anlpar = MII_ANAR_TX;
+
+ mii_set_link(mii, link_ok);
+}
+
+static uint16_t RTL8201CP_mdio_read(AwEmacState *s, uint8_t addr, uint8_t reg)
+{
+ RTL8201CPState *mii = &s->mii;
+ uint16_t ret = 0xffff;
+
+ if (addr == s->phy_addr) {
+ switch (reg) {
+ case MII_BMCR:
+ return mii->bmcr;
+ case MII_BMSR:
+ return mii->bmsr;
+ case MII_PHYID1:
+ return RTL8201CP_PHYID1;
+ case MII_PHYID2:
+ return RTL8201CP_PHYID2;
+ case MII_ANAR:
+ return mii->anar;
+ case MII_ANLPAR:
+ return mii->anlpar;
+ case MII_ANER:
+ case MII_NSR:
+ case MII_LBREMR:
+ case MII_REC:
+ case MII_SNRDR:
+ case MII_TEST:
+ qemu_log_mask(LOG_UNIMP,
+ "allwinner_emac: read from unimpl. mii reg 0x%x\n",
+ reg);
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "allwinner_emac: read from invalid mii reg 0x%x\n",
+ reg);
+ return 0;
+ }
+ }
+ return ret;
+}
+
+static void RTL8201CP_mdio_write(AwEmacState *s, uint8_t addr, uint8_t reg,
+ uint16_t value)
+{
+ RTL8201CPState *mii = &s->mii;
+ NetClientState *nc;
+
+ if (addr == s->phy_addr) {
+ switch (reg) {
+ case MII_BMCR:
+ if (value & MII_BMCR_RESET) {
+ nc = qemu_get_queue(s->nic);
+ mii_reset(mii, !nc->link_down);
+ } else {
+ mii->bmcr = value;
+ }
+ break;
+ case MII_ANAR:
+ mii->anar = value;
+ break;
+ case MII_BMSR:
+ case MII_PHYID1:
+ case MII_PHYID2:
+ case MII_ANLPAR:
+ case MII_ANER:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "allwinner_emac: write to read-only mii reg 0x%x\n",
+ reg);
+ break;
+ case MII_NSR:
+ case MII_LBREMR:
+ case MII_REC:
+ case MII_SNRDR:
+ case MII_TEST:
+ qemu_log_mask(LOG_UNIMP,
+ "allwinner_emac: write to unimpl. mii reg 0x%x\n",
+ reg);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "allwinner_emac: write to invalid mii reg 0x%x\n",
+ reg);
+ }
+ }
+}
+
+static void aw_emac_update_irq(AwEmacState *s)
+{
+ qemu_set_irq(s->irq, (s->int_sta & s->int_ctl) != 0);
+}
+
+static void aw_emac_tx_reset(AwEmacState *s, int chan)
+{
+ fifo8_reset(&s->tx_fifo[chan]);
+ s->tx_length[chan] = 0;
+}
+
+static void aw_emac_rx_reset(AwEmacState *s)
+{
+ fifo8_reset(&s->rx_fifo);
+ s->rx_num_packets = 0;
+ s->rx_packet_size = 0;
+ s->rx_packet_pos = 0;
+}
+
+static void fifo8_push_word(Fifo8 *fifo, uint32_t val)
+{
+ fifo8_push(fifo, val);
+ fifo8_push(fifo, val >> 8);
+ fifo8_push(fifo, val >> 16);
+ fifo8_push(fifo, val >> 24);
+}
+
+static uint32_t fifo8_pop_word(Fifo8 *fifo)
+{
+ uint32_t ret;
+
+ ret = fifo8_pop(fifo);
+ ret |= fifo8_pop(fifo) << 8;
+ ret |= fifo8_pop(fifo) << 16;
+ ret |= fifo8_pop(fifo) << 24;
+
+ return ret;
+}
+
+static int aw_emac_can_receive(NetClientState *nc)
+{
+ AwEmacState *s = qemu_get_nic_opaque(nc);
+
+ /*
+ * To avoid packet drops, allow reception only when there is space
+ * for a full frame: 1522 + 8 (rx headers) + 2 (padding).
+ */
+ return (s->ctl & EMAC_CTL_RX_EN) && (fifo8_num_free(&s->rx_fifo) >= 1532);
+}
+
+static ssize_t aw_emac_receive(NetClientState *nc, const uint8_t *buf,
+ size_t size)
+{
+ AwEmacState *s = qemu_get_nic_opaque(nc);
+ Fifo8 *fifo = &s->rx_fifo;
+ size_t padded_size, total_size;
+ uint32_t crc;
+
+ padded_size = size > 60 ? size : 60;
+ total_size = QEMU_ALIGN_UP(RX_HDR_SIZE + padded_size + CRC_SIZE, 4);
+
+ if (!(s->ctl & EMAC_CTL_RX_EN) || (fifo8_num_free(fifo) < total_size)) {
+ return -1;
+ }
+
+ fifo8_push_word(fifo, EMAC_UNDOCUMENTED_MAGIC);
+ fifo8_push_word(fifo, EMAC_RX_HEADER(padded_size + CRC_SIZE,
+ EMAC_RX_IO_DATA_STATUS_OK));
+ fifo8_push_all(fifo, buf, size);
+ crc = crc32(~0, buf, size);
+
+ if (padded_size != size) {
+ fifo8_push_all(fifo, padding, padded_size - size);
+ crc = crc32(crc, padding, padded_size - size);
+ }
+
+ fifo8_push_word(fifo, crc);
+ fifo8_push_all(fifo, padding, QEMU_ALIGN_UP(padded_size, 4) - padded_size);
+ s->rx_num_packets++;
+
+ s->int_sta |= EMAC_INT_RX;
+ aw_emac_update_irq(s);
+
+ return size;
+}
+
+static void aw_emac_reset(DeviceState *dev)
+{
+ AwEmacState *s = AW_EMAC(dev);
+ NetClientState *nc = qemu_get_queue(s->nic);
+
+ s->ctl = 0;
+ s->tx_mode = 0;
+ s->int_ctl = 0;
+ s->int_sta = 0;
+ s->tx_channel = 0;
+ s->phy_target = 0;
+
+ aw_emac_tx_reset(s, 0);
+ aw_emac_tx_reset(s, 1);
+ aw_emac_rx_reset(s);
+
+ mii_reset(&s->mii, !nc->link_down);
+}
+
+static uint64_t aw_emac_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AwEmacState *s = opaque;
+ Fifo8 *fifo = &s->rx_fifo;
+ NetClientState *nc;
+ uint64_t ret;
+
+ switch (offset) {
+ case EMAC_CTL_REG:
+ return s->ctl;
+ case EMAC_TX_MODE_REG:
+ return s->tx_mode;
+ case EMAC_TX_INS_REG:
+ return s->tx_channel;
+ case EMAC_RX_CTL_REG:
+ return s->rx_ctl;
+ case EMAC_RX_IO_DATA_REG:
+ if (!s->rx_num_packets) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "Read IO data register when no packet available");
+ return 0;
+ }
+
+ ret = fifo8_pop_word(fifo);
+
+ switch (s->rx_packet_pos) {
+ case 0: /* Word is magic header */
+ s->rx_packet_pos += 4;
+ break;
+ case 4: /* Word is rx info header */
+ s->rx_packet_pos += 4;
+ s->rx_packet_size = QEMU_ALIGN_UP(extract32(ret, 0, 16), 4);
+ break;
+ default: /* Word is packet data */
+ s->rx_packet_pos += 4;
+ s->rx_packet_size -= 4;
+
+ if (!s->rx_packet_size) {
+ s->rx_packet_pos = 0;
+ s->rx_num_packets--;
+ nc = qemu_get_queue(s->nic);
+ if (aw_emac_can_receive(nc)) {
+ qemu_flush_queued_packets(nc);
+ }
+ }
+ }
+ return ret;
+ case EMAC_RX_FBC_REG:
+ return s->rx_num_packets;
+ case EMAC_INT_CTL_REG:
+ return s->int_ctl;
+ case EMAC_INT_STA_REG:
+ return s->int_sta;
+ case EMAC_MAC_MRDD_REG:
+ return RTL8201CP_mdio_read(s,
+ extract32(s->phy_target, PHY_ADDR_SHIFT, 8),
+ extract32(s->phy_target, PHY_REG_SHIFT, 8));
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "allwinner_emac: read access to unknown register 0x"
+ TARGET_FMT_plx "\n", offset);
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void aw_emac_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ AwEmacState *s = opaque;
+ Fifo8 *fifo;
+ NetClientState *nc = qemu_get_queue(s->nic);
+ int chan;
+
+ switch (offset) {
+ case EMAC_CTL_REG:
+ if (value & EMAC_CTL_RESET) {
+ aw_emac_reset(DEVICE(s));
+ value &= ~EMAC_CTL_RESET;
+ }
+ s->ctl = value;
+ if (aw_emac_can_receive(nc)) {
+ qemu_flush_queued_packets(nc);
+ }
+ break;
+ case EMAC_TX_MODE_REG:
+ s->tx_mode = value;
+ break;
+ case EMAC_TX_CTL0_REG:
+ case EMAC_TX_CTL1_REG:
+ chan = (offset == EMAC_TX_CTL0_REG ? 0 : 1);
+ if ((value & 1) && (s->ctl & EMAC_CTL_TX_EN)) {
+ uint32_t len, ret;
+ const uint8_t *data;
+
+ fifo = &s->tx_fifo[chan];
+ len = s->tx_length[chan];
+
+ if (len > fifo8_num_used(fifo)) {
+ len = fifo8_num_used(fifo);
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "allwinner_emac: TX length > fifo data length\n");
+ }
+ if (len > 0) {
+ data = fifo8_pop_buf(fifo, len, &ret);
+ qemu_send_packet(nc, data, ret);
+ aw_emac_tx_reset(s, chan);
+ /* Raise TX interrupt */
+ s->int_sta |= EMAC_INT_TX_CHAN(chan);
+ aw_emac_update_irq(s);
+ }
+ }
+ break;
+ case EMAC_TX_INS_REG:
+ s->tx_channel = value < NUM_TX_FIFOS ? value : 0;
+ break;
+ case EMAC_TX_PL0_REG:
+ case EMAC_TX_PL1_REG:
+ chan = (offset == EMAC_TX_PL0_REG ? 0 : 1);
+ if (value > TX_FIFO_SIZE) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "allwinner_emac: invalid TX frame length %d\n",
+ (int)value);
+ value = TX_FIFO_SIZE;
+ }
+ s->tx_length[chan] = value;
+ break;
+ case EMAC_TX_IO_DATA_REG:
+ fifo = &s->tx_fifo[s->tx_channel];
+ if (fifo8_num_free(fifo) < 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "allwinner_emac: TX data overruns fifo\n");
+ break;
+ }
+ fifo8_push_word(fifo, value);
+ break;
+ case EMAC_RX_CTL_REG:
+ s->rx_ctl = value;
+ break;
+ case EMAC_RX_FBC_REG:
+ if (value == 0) {
+ aw_emac_rx_reset(s);
+ }
+ break;
+ case EMAC_INT_CTL_REG:
+ s->int_ctl = value;
+ aw_emac_update_irq(s);
+ break;
+ case EMAC_INT_STA_REG:
+ s->int_sta &= ~value;
+ aw_emac_update_irq(s);
+ break;
+ case EMAC_MAC_MADR_REG:
+ s->phy_target = value;
+ break;
+ case EMAC_MAC_MWTD_REG:
+ RTL8201CP_mdio_write(s, extract32(s->phy_target, PHY_ADDR_SHIFT, 8),
+ extract32(s->phy_target, PHY_REG_SHIFT, 8), value);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "allwinner_emac: write access to unknown register 0x"
+ TARGET_FMT_plx "\n", offset);
+ }
+}
+
+static void aw_emac_set_link(NetClientState *nc)
+{
+ AwEmacState *s = qemu_get_nic_opaque(nc);
+
+ mii_set_link(&s->mii, !nc->link_down);
+}
+
+static const MemoryRegionOps aw_emac_mem_ops = {
+ .read = aw_emac_read,
+ .write = aw_emac_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static NetClientInfo net_aw_emac_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = aw_emac_can_receive,
+ .receive = aw_emac_receive,
+ .link_status_changed = aw_emac_set_link,
+};
+
+static void aw_emac_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwEmacState *s = AW_EMAC(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &aw_emac_mem_ops, s,
+ "aw_emac", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+}
+
+static void aw_emac_realize(DeviceState *dev, Error **errp)
+{
+ AwEmacState *s = AW_EMAC(dev);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(&net_aw_emac_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ fifo8_create(&s->rx_fifo, RX_FIFO_SIZE);
+ fifo8_create(&s->tx_fifo[0], TX_FIFO_SIZE);
+ fifo8_create(&s->tx_fifo[1], TX_FIFO_SIZE);
+}
+
+static Property aw_emac_properties[] = {
+ DEFINE_NIC_PROPERTIES(AwEmacState, conf),
+ DEFINE_PROP_UINT8("phy-addr", AwEmacState, phy_addr, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_mii = {
+ .name = "rtl8201cp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(bmcr, RTL8201CPState),
+ VMSTATE_UINT16(bmsr, RTL8201CPState),
+ VMSTATE_UINT16(anar, RTL8201CPState),
+ VMSTATE_UINT16(anlpar, RTL8201CPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int aw_emac_post_load(void *opaque, int version_id)
+{
+ AwEmacState *s = opaque;
+
+ aw_emac_set_link(qemu_get_queue(s->nic));
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_aw_emac = {
+ .name = "allwinner_emac",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = aw_emac_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(mii, AwEmacState, 1, vmstate_mii, RTL8201CPState),
+ VMSTATE_UINT32(ctl, AwEmacState),
+ VMSTATE_UINT32(tx_mode, AwEmacState),
+ VMSTATE_UINT32(rx_ctl, AwEmacState),
+ VMSTATE_UINT32(int_ctl, AwEmacState),
+ VMSTATE_UINT32(int_sta, AwEmacState),
+ VMSTATE_UINT32(phy_target, AwEmacState),
+ VMSTATE_FIFO8(rx_fifo, AwEmacState),
+ VMSTATE_UINT32(rx_num_packets, AwEmacState),
+ VMSTATE_UINT32(rx_packet_size, AwEmacState),
+ VMSTATE_UINT32(rx_packet_pos, AwEmacState),
+ VMSTATE_STRUCT_ARRAY(tx_fifo, AwEmacState, NUM_TX_FIFOS, 1,
+ vmstate_fifo8, Fifo8),
+ VMSTATE_UINT32_ARRAY(tx_length, AwEmacState, NUM_TX_FIFOS),
+ VMSTATE_UINT32(tx_channel, AwEmacState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void aw_emac_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = aw_emac_realize;
+ dc->props = aw_emac_properties;
+ dc->reset = aw_emac_reset;
+ dc->vmsd = &vmstate_aw_emac;
+}
+
+static const TypeInfo aw_emac_info = {
+ .name = TYPE_AW_EMAC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AwEmacState),
+ .instance_init = aw_emac_init,
+ .class_init = aw_emac_class_init,
+};
+
+static void aw_emac_register_types(void)
+{
+ type_register_static(&aw_emac_info);
+}
+
+type_init(aw_emac_register_types)
diff --git a/hw/net/cadence_gem.c b/hw/net/cadence_gem.c
new file mode 100644
index 00000000..494a346c
--- /dev/null
+++ b/hw/net/cadence_gem.c
@@ -0,0 +1,1248 @@
+/*
+ * QEMU Cadence GEM emulation
+ *
+ * Copyright (c) 2011 Xilinx, Inc.
+ *
+ * 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.
+ */
+
+#include <zlib.h> /* For crc32 */
+
+#include "hw/net/cadence_gem.h"
+#include "net/checksum.h"
+
+#ifdef CADENCE_GEM_ERR_DEBUG
+#define DB_PRINT(...) do { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } while (0);
+#else
+ #define DB_PRINT(...)
+#endif
+
+#define GEM_NWCTRL (0x00000000/4) /* Network Control reg */
+#define GEM_NWCFG (0x00000004/4) /* Network Config reg */
+#define GEM_NWSTATUS (0x00000008/4) /* Network Status reg */
+#define GEM_USERIO (0x0000000C/4) /* User IO reg */
+#define GEM_DMACFG (0x00000010/4) /* DMA Control reg */
+#define GEM_TXSTATUS (0x00000014/4) /* TX Status reg */
+#define GEM_RXQBASE (0x00000018/4) /* RX Q Base address reg */
+#define GEM_TXQBASE (0x0000001C/4) /* TX Q Base address reg */
+#define GEM_RXSTATUS (0x00000020/4) /* RX Status reg */
+#define GEM_ISR (0x00000024/4) /* Interrupt Status reg */
+#define GEM_IER (0x00000028/4) /* Interrupt Enable reg */
+#define GEM_IDR (0x0000002C/4) /* Interrupt Disable reg */
+#define GEM_IMR (0x00000030/4) /* Interrupt Mask reg */
+#define GEM_PHYMNTNC (0x00000034/4) /* Phy Maintenance reg */
+#define GEM_RXPAUSE (0x00000038/4) /* RX Pause Time reg */
+#define GEM_TXPAUSE (0x0000003C/4) /* TX Pause Time reg */
+#define GEM_TXPARTIALSF (0x00000040/4) /* TX Partial Store and Forward */
+#define GEM_RXPARTIALSF (0x00000044/4) /* RX Partial Store and Forward */
+#define GEM_HASHLO (0x00000080/4) /* Hash Low address reg */
+#define GEM_HASHHI (0x00000084/4) /* Hash High address reg */
+#define GEM_SPADDR1LO (0x00000088/4) /* Specific addr 1 low reg */
+#define GEM_SPADDR1HI (0x0000008C/4) /* Specific addr 1 high reg */
+#define GEM_SPADDR2LO (0x00000090/4) /* Specific addr 2 low reg */
+#define GEM_SPADDR2HI (0x00000094/4) /* Specific addr 2 high reg */
+#define GEM_SPADDR3LO (0x00000098/4) /* Specific addr 3 low reg */
+#define GEM_SPADDR3HI (0x0000009C/4) /* Specific addr 3 high reg */
+#define GEM_SPADDR4LO (0x000000A0/4) /* Specific addr 4 low reg */
+#define GEM_SPADDR4HI (0x000000A4/4) /* Specific addr 4 high reg */
+#define GEM_TIDMATCH1 (0x000000A8/4) /* Type ID1 Match reg */
+#define GEM_TIDMATCH2 (0x000000AC/4) /* Type ID2 Match reg */
+#define GEM_TIDMATCH3 (0x000000B0/4) /* Type ID3 Match reg */
+#define GEM_TIDMATCH4 (0x000000B4/4) /* Type ID4 Match reg */
+#define GEM_WOLAN (0x000000B8/4) /* Wake on LAN reg */
+#define GEM_IPGSTRETCH (0x000000BC/4) /* IPG Stretch reg */
+#define GEM_SVLAN (0x000000C0/4) /* Stacked VLAN reg */
+#define GEM_MODID (0x000000FC/4) /* Module ID reg */
+#define GEM_OCTTXLO (0x00000100/4) /* Octects transmitted Low reg */
+#define GEM_OCTTXHI (0x00000104/4) /* Octects transmitted High reg */
+#define GEM_TXCNT (0x00000108/4) /* Error-free Frames transmitted */
+#define GEM_TXBCNT (0x0000010C/4) /* Error-free Broadcast Frames */
+#define GEM_TXMCNT (0x00000110/4) /* Error-free Multicast Frame */
+#define GEM_TXPAUSECNT (0x00000114/4) /* Pause Frames Transmitted */
+#define GEM_TX64CNT (0x00000118/4) /* Error-free 64 TX */
+#define GEM_TX65CNT (0x0000011C/4) /* Error-free 65-127 TX */
+#define GEM_TX128CNT (0x00000120/4) /* Error-free 128-255 TX */
+#define GEM_TX256CNT (0x00000124/4) /* Error-free 256-511 */
+#define GEM_TX512CNT (0x00000128/4) /* Error-free 512-1023 TX */
+#define GEM_TX1024CNT (0x0000012C/4) /* Error-free 1024-1518 TX */
+#define GEM_TX1519CNT (0x00000130/4) /* Error-free larger than 1519 TX */
+#define GEM_TXURUNCNT (0x00000134/4) /* TX under run error counter */
+#define GEM_SINGLECOLLCNT (0x00000138/4) /* Single Collision Frames */
+#define GEM_MULTCOLLCNT (0x0000013C/4) /* Multiple Collision Frames */
+#define GEM_EXCESSCOLLCNT (0x00000140/4) /* Excessive Collision Frames */
+#define GEM_LATECOLLCNT (0x00000144/4) /* Late Collision Frames */
+#define GEM_DEFERTXCNT (0x00000148/4) /* Deferred Transmission Frames */
+#define GEM_CSENSECNT (0x0000014C/4) /* Carrier Sense Error Counter */
+#define GEM_OCTRXLO (0x00000150/4) /* Octects Received register Low */
+#define GEM_OCTRXHI (0x00000154/4) /* Octects Received register High */
+#define GEM_RXCNT (0x00000158/4) /* Error-free Frames Received */
+#define GEM_RXBROADCNT (0x0000015C/4) /* Error-free Broadcast Frames RX */
+#define GEM_RXMULTICNT (0x00000160/4) /* Error-free Multicast Frames RX */
+#define GEM_RXPAUSECNT (0x00000164/4) /* Pause Frames Received Counter */
+#define GEM_RX64CNT (0x00000168/4) /* Error-free 64 byte Frames RX */
+#define GEM_RX65CNT (0x0000016C/4) /* Error-free 65-127B Frames RX */
+#define GEM_RX128CNT (0x00000170/4) /* Error-free 128-255B Frames RX */
+#define GEM_RX256CNT (0x00000174/4) /* Error-free 256-512B Frames RX */
+#define GEM_RX512CNT (0x00000178/4) /* Error-free 512-1023B Frames RX */
+#define GEM_RX1024CNT (0x0000017C/4) /* Error-free 1024-1518B Frames RX */
+#define GEM_RX1519CNT (0x00000180/4) /* Error-free 1519-max Frames RX */
+#define GEM_RXUNDERCNT (0x00000184/4) /* Undersize Frames Received */
+#define GEM_RXOVERCNT (0x00000188/4) /* Oversize Frames Received */
+#define GEM_RXJABCNT (0x0000018C/4) /* Jabbers Received Counter */
+#define GEM_RXFCSCNT (0x00000190/4) /* Frame Check seq. Error Counter */
+#define GEM_RXLENERRCNT (0x00000194/4) /* Length Field Error Counter */
+#define GEM_RXSYMERRCNT (0x00000198/4) /* Symbol Error Counter */
+#define GEM_RXALIGNERRCNT (0x0000019C/4) /* Alignment Error Counter */
+#define GEM_RXRSCERRCNT (0x000001A0/4) /* Receive Resource Error Counter */
+#define GEM_RXORUNCNT (0x000001A4/4) /* Receive Overrun Counter */
+#define GEM_RXIPCSERRCNT (0x000001A8/4) /* IP header Checksum Error Counter */
+#define GEM_RXTCPCCNT (0x000001AC/4) /* TCP Checksum Error Counter */
+#define GEM_RXUDPCCNT (0x000001B0/4) /* UDP Checksum Error Counter */
+
+#define GEM_1588S (0x000001D0/4) /* 1588 Timer Seconds */
+#define GEM_1588NS (0x000001D4/4) /* 1588 Timer Nanoseconds */
+#define GEM_1588ADJ (0x000001D8/4) /* 1588 Timer Adjust */
+#define GEM_1588INC (0x000001DC/4) /* 1588 Timer Increment */
+#define GEM_PTPETXS (0x000001E0/4) /* PTP Event Frame Transmitted (s) */
+#define GEM_PTPETXNS (0x000001E4/4) /* PTP Event Frame Transmitted (ns) */
+#define GEM_PTPERXS (0x000001E8/4) /* PTP Event Frame Received (s) */
+#define GEM_PTPERXNS (0x000001EC/4) /* PTP Event Frame Received (ns) */
+#define GEM_PTPPTXS (0x000001E0/4) /* PTP Peer Frame Transmitted (s) */
+#define GEM_PTPPTXNS (0x000001E4/4) /* PTP Peer Frame Transmitted (ns) */
+#define GEM_PTPPRXS (0x000001E8/4) /* PTP Peer Frame Received (s) */
+#define GEM_PTPPRXNS (0x000001EC/4) /* PTP Peer Frame Received (ns) */
+
+/* Design Configuration Registers */
+#define GEM_DESCONF (0x00000280/4)
+#define GEM_DESCONF2 (0x00000284/4)
+#define GEM_DESCONF3 (0x00000288/4)
+#define GEM_DESCONF4 (0x0000028C/4)
+#define GEM_DESCONF5 (0x00000290/4)
+#define GEM_DESCONF6 (0x00000294/4)
+#define GEM_DESCONF7 (0x00000298/4)
+
+/*****************************************/
+#define GEM_NWCTRL_TXSTART 0x00000200 /* Transmit Enable */
+#define GEM_NWCTRL_TXENA 0x00000008 /* Transmit Enable */
+#define GEM_NWCTRL_RXENA 0x00000004 /* Receive Enable */
+#define GEM_NWCTRL_LOCALLOOP 0x00000002 /* Local Loopback */
+
+#define GEM_NWCFG_STRIP_FCS 0x00020000 /* Strip FCS field */
+#define GEM_NWCFG_LERR_DISC 0x00010000 /* Discard RX frames with len err */
+#define GEM_NWCFG_BUFF_OFST_M 0x0000C000 /* Receive buffer offset mask */
+#define GEM_NWCFG_BUFF_OFST_S 14 /* Receive buffer offset shift */
+#define GEM_NWCFG_UCAST_HASH 0x00000080 /* accept unicast if hash match */
+#define GEM_NWCFG_MCAST_HASH 0x00000040 /* accept multicast if hash match */
+#define GEM_NWCFG_BCAST_REJ 0x00000020 /* Reject broadcast packets */
+#define GEM_NWCFG_PROMISC 0x00000010 /* Accept all packets */
+
+#define GEM_DMACFG_RBUFSZ_M 0x00FF0000 /* DMA RX Buffer Size mask */
+#define GEM_DMACFG_RBUFSZ_S 16 /* DMA RX Buffer Size shift */
+#define GEM_DMACFG_RBUFSZ_MUL 64 /* DMA RX Buffer Size multiplier */
+#define GEM_DMACFG_TXCSUM_OFFL 0x00000800 /* Transmit checksum offload */
+
+#define GEM_TXSTATUS_TXCMPL 0x00000020 /* Transmit Complete */
+#define GEM_TXSTATUS_USED 0x00000001 /* sw owned descriptor encountered */
+
+#define GEM_RXSTATUS_FRMRCVD 0x00000002 /* Frame received */
+#define GEM_RXSTATUS_NOBUF 0x00000001 /* Buffer unavailable */
+
+/* GEM_ISR GEM_IER GEM_IDR GEM_IMR */
+#define GEM_INT_TXCMPL 0x00000080 /* Transmit Complete */
+#define GEM_INT_TXUSED 0x00000008
+#define GEM_INT_RXUSED 0x00000004
+#define GEM_INT_RXCMPL 0x00000002
+
+#define GEM_PHYMNTNC_OP_R 0x20000000 /* read operation */
+#define GEM_PHYMNTNC_OP_W 0x10000000 /* write operation */
+#define GEM_PHYMNTNC_ADDR 0x0F800000 /* Address bits */
+#define GEM_PHYMNTNC_ADDR_SHFT 23
+#define GEM_PHYMNTNC_REG 0x007C0000 /* register bits */
+#define GEM_PHYMNTNC_REG_SHIFT 18
+
+/* Marvell PHY definitions */
+#define BOARD_PHY_ADDRESS 23 /* PHY address we will emulate a device at */
+
+#define PHY_REG_CONTROL 0
+#define PHY_REG_STATUS 1
+#define PHY_REG_PHYID1 2
+#define PHY_REG_PHYID2 3
+#define PHY_REG_ANEGADV 4
+#define PHY_REG_LINKPABIL 5
+#define PHY_REG_ANEGEXP 6
+#define PHY_REG_NEXTP 7
+#define PHY_REG_LINKPNEXTP 8
+#define PHY_REG_100BTCTRL 9
+#define PHY_REG_1000BTSTAT 10
+#define PHY_REG_EXTSTAT 15
+#define PHY_REG_PHYSPCFC_CTL 16
+#define PHY_REG_PHYSPCFC_ST 17
+#define PHY_REG_INT_EN 18
+#define PHY_REG_INT_ST 19
+#define PHY_REG_EXT_PHYSPCFC_CTL 20
+#define PHY_REG_RXERR 21
+#define PHY_REG_EACD 22
+#define PHY_REG_LED 24
+#define PHY_REG_LED_OVRD 25
+#define PHY_REG_EXT_PHYSPCFC_CTL2 26
+#define PHY_REG_EXT_PHYSPCFC_ST 27
+#define PHY_REG_CABLE_DIAG 28
+
+#define PHY_REG_CONTROL_RST 0x8000
+#define PHY_REG_CONTROL_LOOP 0x4000
+#define PHY_REG_CONTROL_ANEG 0x1000
+
+#define PHY_REG_STATUS_LINK 0x0004
+#define PHY_REG_STATUS_ANEGCMPL 0x0020
+
+#define PHY_REG_INT_ST_ANEGCMPL 0x0800
+#define PHY_REG_INT_ST_LINKC 0x0400
+#define PHY_REG_INT_ST_ENERGY 0x0010
+
+/***********************************************************************/
+#define GEM_RX_REJECT (-1)
+#define GEM_RX_PROMISCUOUS_ACCEPT (-2)
+#define GEM_RX_BROADCAST_ACCEPT (-3)
+#define GEM_RX_MULTICAST_HASH_ACCEPT (-4)
+#define GEM_RX_UNICAST_HASH_ACCEPT (-5)
+
+#define GEM_RX_SAR_ACCEPT 0
+
+/***********************************************************************/
+
+#define DESC_1_USED 0x80000000
+#define DESC_1_LENGTH 0x00001FFF
+
+#define DESC_1_TX_WRAP 0x40000000
+#define DESC_1_TX_LAST 0x00008000
+
+#define DESC_0_RX_WRAP 0x00000002
+#define DESC_0_RX_OWNERSHIP 0x00000001
+
+#define R_DESC_1_RX_SAR_SHIFT 25
+#define R_DESC_1_RX_SAR_LENGTH 2
+#define R_DESC_1_RX_SAR_MATCH (1 << 27)
+#define R_DESC_1_RX_UNICAST_HASH (1 << 29)
+#define R_DESC_1_RX_MULTICAST_HASH (1 << 30)
+#define R_DESC_1_RX_BROADCAST (1 << 31)
+
+#define DESC_1_RX_SOF 0x00004000
+#define DESC_1_RX_EOF 0x00008000
+
+static inline unsigned tx_desc_get_buffer(unsigned *desc)
+{
+ return desc[0];
+}
+
+static inline unsigned tx_desc_get_used(unsigned *desc)
+{
+ return (desc[1] & DESC_1_USED) ? 1 : 0;
+}
+
+static inline void tx_desc_set_used(unsigned *desc)
+{
+ desc[1] |= DESC_1_USED;
+}
+
+static inline unsigned tx_desc_get_wrap(unsigned *desc)
+{
+ return (desc[1] & DESC_1_TX_WRAP) ? 1 : 0;
+}
+
+static inline unsigned tx_desc_get_last(unsigned *desc)
+{
+ return (desc[1] & DESC_1_TX_LAST) ? 1 : 0;
+}
+
+static inline unsigned tx_desc_get_length(unsigned *desc)
+{
+ return desc[1] & DESC_1_LENGTH;
+}
+
+static inline void print_gem_tx_desc(unsigned *desc)
+{
+ DB_PRINT("TXDESC:\n");
+ DB_PRINT("bufaddr: 0x%08x\n", *desc);
+ DB_PRINT("used_hw: %d\n", tx_desc_get_used(desc));
+ DB_PRINT("wrap: %d\n", tx_desc_get_wrap(desc));
+ DB_PRINT("last: %d\n", tx_desc_get_last(desc));
+ DB_PRINT("length: %d\n", tx_desc_get_length(desc));
+}
+
+static inline unsigned rx_desc_get_buffer(unsigned *desc)
+{
+ return desc[0] & ~0x3UL;
+}
+
+static inline unsigned rx_desc_get_wrap(unsigned *desc)
+{
+ return desc[0] & DESC_0_RX_WRAP ? 1 : 0;
+}
+
+static inline unsigned rx_desc_get_ownership(unsigned *desc)
+{
+ return desc[0] & DESC_0_RX_OWNERSHIP ? 1 : 0;
+}
+
+static inline void rx_desc_set_ownership(unsigned *desc)
+{
+ desc[0] |= DESC_0_RX_OWNERSHIP;
+}
+
+static inline void rx_desc_set_sof(unsigned *desc)
+{
+ desc[1] |= DESC_1_RX_SOF;
+}
+
+static inline void rx_desc_set_eof(unsigned *desc)
+{
+ desc[1] |= DESC_1_RX_EOF;
+}
+
+static inline void rx_desc_set_length(unsigned *desc, unsigned len)
+{
+ desc[1] &= ~DESC_1_LENGTH;
+ desc[1] |= len;
+}
+
+static inline void rx_desc_set_broadcast(unsigned *desc)
+{
+ desc[1] |= R_DESC_1_RX_BROADCAST;
+}
+
+static inline void rx_desc_set_unicast_hash(unsigned *desc)
+{
+ desc[1] |= R_DESC_1_RX_UNICAST_HASH;
+}
+
+static inline void rx_desc_set_multicast_hash(unsigned *desc)
+{
+ desc[1] |= R_DESC_1_RX_MULTICAST_HASH;
+}
+
+static inline void rx_desc_set_sar(unsigned *desc, int sar_idx)
+{
+ desc[1] = deposit32(desc[1], R_DESC_1_RX_SAR_SHIFT, R_DESC_1_RX_SAR_LENGTH,
+ sar_idx);
+ desc[1] |= R_DESC_1_RX_SAR_MATCH;
+}
+
+/* The broadcast MAC address: 0xFFFFFFFFFFFF */
+static const uint8_t broadcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+/*
+ * gem_init_register_masks:
+ * One time initialization.
+ * Set masks to identify which register bits have magical clear properties
+ */
+static void gem_init_register_masks(CadenceGEMState *s)
+{
+ /* Mask of register bits which are read only */
+ memset(&s->regs_ro[0], 0, sizeof(s->regs_ro));
+ s->regs_ro[GEM_NWCTRL] = 0xFFF80000;
+ s->regs_ro[GEM_NWSTATUS] = 0xFFFFFFFF;
+ s->regs_ro[GEM_DMACFG] = 0xFE00F000;
+ s->regs_ro[GEM_TXSTATUS] = 0xFFFFFE08;
+ s->regs_ro[GEM_RXQBASE] = 0x00000003;
+ s->regs_ro[GEM_TXQBASE] = 0x00000003;
+ s->regs_ro[GEM_RXSTATUS] = 0xFFFFFFF0;
+ s->regs_ro[GEM_ISR] = 0xFFFFFFFF;
+ s->regs_ro[GEM_IMR] = 0xFFFFFFFF;
+ s->regs_ro[GEM_MODID] = 0xFFFFFFFF;
+
+ /* Mask of register bits which are clear on read */
+ memset(&s->regs_rtc[0], 0, sizeof(s->regs_rtc));
+ s->regs_rtc[GEM_ISR] = 0xFFFFFFFF;
+
+ /* Mask of register bits which are write 1 to clear */
+ memset(&s->regs_w1c[0], 0, sizeof(s->regs_w1c));
+ s->regs_w1c[GEM_TXSTATUS] = 0x000001F7;
+ s->regs_w1c[GEM_RXSTATUS] = 0x0000000F;
+
+ /* Mask of register bits which are write only */
+ memset(&s->regs_wo[0], 0, sizeof(s->regs_wo));
+ s->regs_wo[GEM_NWCTRL] = 0x00073E60;
+ s->regs_wo[GEM_IER] = 0x07FFFFFF;
+ s->regs_wo[GEM_IDR] = 0x07FFFFFF;
+}
+
+/*
+ * phy_update_link:
+ * Make the emulated PHY link state match the QEMU "interface" state.
+ */
+static void phy_update_link(CadenceGEMState *s)
+{
+ DB_PRINT("down %d\n", qemu_get_queue(s->nic)->link_down);
+
+ /* Autonegotiation status mirrors link status. */
+ if (qemu_get_queue(s->nic)->link_down) {
+ s->phy_regs[PHY_REG_STATUS] &= ~(PHY_REG_STATUS_ANEGCMPL |
+ PHY_REG_STATUS_LINK);
+ s->phy_regs[PHY_REG_INT_ST] |= PHY_REG_INT_ST_LINKC;
+ } else {
+ s->phy_regs[PHY_REG_STATUS] |= (PHY_REG_STATUS_ANEGCMPL |
+ PHY_REG_STATUS_LINK);
+ s->phy_regs[PHY_REG_INT_ST] |= (PHY_REG_INT_ST_LINKC |
+ PHY_REG_INT_ST_ANEGCMPL |
+ PHY_REG_INT_ST_ENERGY);
+ }
+}
+
+static int gem_can_receive(NetClientState *nc)
+{
+ CadenceGEMState *s;
+
+ s = qemu_get_nic_opaque(nc);
+
+ /* Do nothing if receive is not enabled. */
+ if (!(s->regs[GEM_NWCTRL] & GEM_NWCTRL_RXENA)) {
+ if (s->can_rx_state != 1) {
+ s->can_rx_state = 1;
+ DB_PRINT("can't receive - no enable\n");
+ }
+ return 0;
+ }
+
+ if (rx_desc_get_ownership(s->rx_desc) == 1) {
+ if (s->can_rx_state != 2) {
+ s->can_rx_state = 2;
+ DB_PRINT("can't receive - busy buffer descriptor 0x%x\n",
+ s->rx_desc_addr);
+ }
+ return 0;
+ }
+
+ if (s->can_rx_state != 0) {
+ s->can_rx_state = 0;
+ DB_PRINT("can receive 0x%x\n", s->rx_desc_addr);
+ }
+ return 1;
+}
+
+/*
+ * gem_update_int_status:
+ * Raise or lower interrupt based on current status.
+ */
+static void gem_update_int_status(CadenceGEMState *s)
+{
+ if (s->regs[GEM_ISR]) {
+ DB_PRINT("asserting int. (0x%08x)\n", s->regs[GEM_ISR]);
+ qemu_set_irq(s->irq, 1);
+ }
+}
+
+/*
+ * gem_receive_updatestats:
+ * Increment receive statistics.
+ */
+static void gem_receive_updatestats(CadenceGEMState *s, const uint8_t *packet,
+ unsigned bytes)
+{
+ uint64_t octets;
+
+ /* Total octets (bytes) received */
+ octets = ((uint64_t)(s->regs[GEM_OCTRXLO]) << 32) |
+ s->regs[GEM_OCTRXHI];
+ octets += bytes;
+ s->regs[GEM_OCTRXLO] = octets >> 32;
+ s->regs[GEM_OCTRXHI] = octets;
+
+ /* Error-free Frames received */
+ s->regs[GEM_RXCNT]++;
+
+ /* Error-free Broadcast Frames counter */
+ if (!memcmp(packet, broadcast_addr, 6)) {
+ s->regs[GEM_RXBROADCNT]++;
+ }
+
+ /* Error-free Multicast Frames counter */
+ if (packet[0] == 0x01) {
+ s->regs[GEM_RXMULTICNT]++;
+ }
+
+ if (bytes <= 64) {
+ s->regs[GEM_RX64CNT]++;
+ } else if (bytes <= 127) {
+ s->regs[GEM_RX65CNT]++;
+ } else if (bytes <= 255) {
+ s->regs[GEM_RX128CNT]++;
+ } else if (bytes <= 511) {
+ s->regs[GEM_RX256CNT]++;
+ } else if (bytes <= 1023) {
+ s->regs[GEM_RX512CNT]++;
+ } else if (bytes <= 1518) {
+ s->regs[GEM_RX1024CNT]++;
+ } else {
+ s->regs[GEM_RX1519CNT]++;
+ }
+}
+
+/*
+ * Get the MAC Address bit from the specified position
+ */
+static unsigned get_bit(const uint8_t *mac, unsigned bit)
+{
+ unsigned byte;
+
+ byte = mac[bit / 8];
+ byte >>= (bit & 0x7);
+ byte &= 1;
+
+ return byte;
+}
+
+/*
+ * Calculate a GEM MAC Address hash index
+ */
+static unsigned calc_mac_hash(const uint8_t *mac)
+{
+ int index_bit, mac_bit;
+ unsigned hash_index;
+
+ hash_index = 0;
+ mac_bit = 5;
+ for (index_bit = 5; index_bit >= 0; index_bit--) {
+ hash_index |= (get_bit(mac, mac_bit) ^
+ get_bit(mac, mac_bit + 6) ^
+ get_bit(mac, mac_bit + 12) ^
+ get_bit(mac, mac_bit + 18) ^
+ get_bit(mac, mac_bit + 24) ^
+ get_bit(mac, mac_bit + 30) ^
+ get_bit(mac, mac_bit + 36) ^
+ get_bit(mac, mac_bit + 42)) << index_bit;
+ mac_bit--;
+ }
+
+ return hash_index;
+}
+
+/*
+ * gem_mac_address_filter:
+ * Accept or reject this destination address?
+ * Returns:
+ * GEM_RX_REJECT: reject
+ * >= 0: Specific address accept (which matched SAR is returned)
+ * others for various other modes of accept:
+ * GEM_RM_PROMISCUOUS_ACCEPT, GEM_RX_BROADCAST_ACCEPT,
+ * GEM_RX_MULTICAST_HASH_ACCEPT or GEM_RX_UNICAST_HASH_ACCEPT
+ */
+static int gem_mac_address_filter(CadenceGEMState *s, const uint8_t *packet)
+{
+ uint8_t *gem_spaddr;
+ int i;
+
+ /* Promiscuous mode? */
+ if (s->regs[GEM_NWCFG] & GEM_NWCFG_PROMISC) {
+ return GEM_RX_PROMISCUOUS_ACCEPT;
+ }
+
+ if (!memcmp(packet, broadcast_addr, 6)) {
+ /* Reject broadcast packets? */
+ if (s->regs[GEM_NWCFG] & GEM_NWCFG_BCAST_REJ) {
+ return GEM_RX_REJECT;
+ }
+ return GEM_RX_BROADCAST_ACCEPT;
+ }
+
+ /* Accept packets -w- hash match? */
+ if ((packet[0] == 0x01 && (s->regs[GEM_NWCFG] & GEM_NWCFG_MCAST_HASH)) ||
+ (packet[0] != 0x01 && (s->regs[GEM_NWCFG] & GEM_NWCFG_UCAST_HASH))) {
+ unsigned hash_index;
+
+ hash_index = calc_mac_hash(packet);
+ if (hash_index < 32) {
+ if (s->regs[GEM_HASHLO] & (1<<hash_index)) {
+ return packet[0] == 0x01 ? GEM_RX_MULTICAST_HASH_ACCEPT :
+ GEM_RX_UNICAST_HASH_ACCEPT;
+ }
+ } else {
+ hash_index -= 32;
+ if (s->regs[GEM_HASHHI] & (1<<hash_index)) {
+ return packet[0] == 0x01 ? GEM_RX_MULTICAST_HASH_ACCEPT :
+ GEM_RX_UNICAST_HASH_ACCEPT;
+ }
+ }
+ }
+
+ /* Check all 4 specific addresses */
+ gem_spaddr = (uint8_t *)&(s->regs[GEM_SPADDR1LO]);
+ for (i = 3; i >= 0; i--) {
+ if (s->sar_active[i] && !memcmp(packet, gem_spaddr + 8 * i, 6)) {
+ return GEM_RX_SAR_ACCEPT + i;
+ }
+ }
+
+ /* No address match; reject the packet */
+ return GEM_RX_REJECT;
+}
+
+static void gem_get_rx_desc(CadenceGEMState *s)
+{
+ DB_PRINT("read descriptor 0x%x\n", (unsigned)s->rx_desc_addr);
+ /* read current descriptor */
+ cpu_physical_memory_read(s->rx_desc_addr,
+ (uint8_t *)s->rx_desc, sizeof(s->rx_desc));
+
+ /* Descriptor owned by software ? */
+ if (rx_desc_get_ownership(s->rx_desc) == 1) {
+ DB_PRINT("descriptor 0x%x owned by sw.\n",
+ (unsigned)s->rx_desc_addr);
+ s->regs[GEM_RXSTATUS] |= GEM_RXSTATUS_NOBUF;
+ s->regs[GEM_ISR] |= GEM_INT_RXUSED & ~(s->regs[GEM_IMR]);
+ /* Handle interrupt consequences */
+ gem_update_int_status(s);
+ }
+}
+
+/*
+ * gem_receive:
+ * Fit a packet handed to us by QEMU into the receive descriptor ring.
+ */
+static ssize_t gem_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ CadenceGEMState *s;
+ unsigned rxbufsize, bytes_to_copy;
+ unsigned rxbuf_offset;
+ uint8_t rxbuf[2048];
+ uint8_t *rxbuf_ptr;
+ bool first_desc = true;
+ int maf;
+
+ s = qemu_get_nic_opaque(nc);
+
+ /* Is this destination MAC address "for us" ? */
+ maf = gem_mac_address_filter(s, buf);
+ if (maf == GEM_RX_REJECT) {
+ return -1;
+ }
+
+ /* Discard packets with receive length error enabled ? */
+ if (s->regs[GEM_NWCFG] & GEM_NWCFG_LERR_DISC) {
+ unsigned type_len;
+
+ /* Fish the ethertype / length field out of the RX packet */
+ type_len = buf[12] << 8 | buf[13];
+ /* It is a length field, not an ethertype */
+ if (type_len < 0x600) {
+ if (size < type_len) {
+ /* discard */
+ return -1;
+ }
+ }
+ }
+
+ /*
+ * Determine configured receive buffer offset (probably 0)
+ */
+ rxbuf_offset = (s->regs[GEM_NWCFG] & GEM_NWCFG_BUFF_OFST_M) >>
+ GEM_NWCFG_BUFF_OFST_S;
+
+ /* The configure size of each receive buffer. Determines how many
+ * buffers needed to hold this packet.
+ */
+ rxbufsize = ((s->regs[GEM_DMACFG] & GEM_DMACFG_RBUFSZ_M) >>
+ GEM_DMACFG_RBUFSZ_S) * GEM_DMACFG_RBUFSZ_MUL;
+ bytes_to_copy = size;
+
+ /* Pad to minimum length. Assume FCS field is stripped, logic
+ * below will increment it to the real minimum of 64 when
+ * not FCS stripping
+ */
+ if (size < 60) {
+ size = 60;
+ }
+
+ /* Strip of FCS field ? (usually yes) */
+ if (s->regs[GEM_NWCFG] & GEM_NWCFG_STRIP_FCS) {
+ rxbuf_ptr = (void *)buf;
+ } else {
+ unsigned crc_val;
+
+ /* The application wants the FCS field, which QEMU does not provide.
+ * We must try and calculate one.
+ */
+
+ memcpy(rxbuf, buf, size);
+ memset(rxbuf + size, 0, sizeof(rxbuf) - size);
+ rxbuf_ptr = rxbuf;
+ crc_val = cpu_to_le32(crc32(0, rxbuf, MAX(size, 60)));
+ memcpy(rxbuf + size, &crc_val, sizeof(crc_val));
+
+ bytes_to_copy += 4;
+ size += 4;
+ }
+
+ DB_PRINT("config bufsize: %d packet size: %ld\n", rxbufsize, size);
+
+ while (bytes_to_copy) {
+ /* Do nothing if receive is not enabled. */
+ if (!gem_can_receive(nc)) {
+ assert(!first_desc);
+ return -1;
+ }
+
+ DB_PRINT("copy %d bytes to 0x%x\n", MIN(bytes_to_copy, rxbufsize),
+ rx_desc_get_buffer(s->rx_desc));
+
+ /* Copy packet data to emulated DMA buffer */
+ cpu_physical_memory_write(rx_desc_get_buffer(s->rx_desc) + rxbuf_offset,
+ rxbuf_ptr, MIN(bytes_to_copy, rxbufsize));
+ rxbuf_ptr += MIN(bytes_to_copy, rxbufsize);
+ bytes_to_copy -= MIN(bytes_to_copy, rxbufsize);
+
+ /* Update the descriptor. */
+ if (first_desc) {
+ rx_desc_set_sof(s->rx_desc);
+ first_desc = false;
+ }
+ if (bytes_to_copy == 0) {
+ rx_desc_set_eof(s->rx_desc);
+ rx_desc_set_length(s->rx_desc, size);
+ }
+ rx_desc_set_ownership(s->rx_desc);
+
+ switch (maf) {
+ case GEM_RX_PROMISCUOUS_ACCEPT:
+ break;
+ case GEM_RX_BROADCAST_ACCEPT:
+ rx_desc_set_broadcast(s->rx_desc);
+ break;
+ case GEM_RX_UNICAST_HASH_ACCEPT:
+ rx_desc_set_unicast_hash(s->rx_desc);
+ break;
+ case GEM_RX_MULTICAST_HASH_ACCEPT:
+ rx_desc_set_multicast_hash(s->rx_desc);
+ break;
+ case GEM_RX_REJECT:
+ abort();
+ default: /* SAR */
+ rx_desc_set_sar(s->rx_desc, maf);
+ }
+
+ /* Descriptor write-back. */
+ cpu_physical_memory_write(s->rx_desc_addr,
+ (uint8_t *)s->rx_desc, sizeof(s->rx_desc));
+
+ /* Next descriptor */
+ if (rx_desc_get_wrap(s->rx_desc)) {
+ DB_PRINT("wrapping RX descriptor list\n");
+ s->rx_desc_addr = s->regs[GEM_RXQBASE];
+ } else {
+ DB_PRINT("incrementing RX descriptor list\n");
+ s->rx_desc_addr += 8;
+ }
+ gem_get_rx_desc(s);
+ }
+
+ /* Count it */
+ gem_receive_updatestats(s, buf, size);
+
+ s->regs[GEM_RXSTATUS] |= GEM_RXSTATUS_FRMRCVD;
+ s->regs[GEM_ISR] |= GEM_INT_RXCMPL & ~(s->regs[GEM_IMR]);
+
+ /* Handle interrupt consequences */
+ gem_update_int_status(s);
+
+ return size;
+}
+
+/*
+ * gem_transmit_updatestats:
+ * Increment transmit statistics.
+ */
+static void gem_transmit_updatestats(CadenceGEMState *s, const uint8_t *packet,
+ unsigned bytes)
+{
+ uint64_t octets;
+
+ /* Total octets (bytes) transmitted */
+ octets = ((uint64_t)(s->regs[GEM_OCTTXLO]) << 32) |
+ s->regs[GEM_OCTTXHI];
+ octets += bytes;
+ s->regs[GEM_OCTTXLO] = octets >> 32;
+ s->regs[GEM_OCTTXHI] = octets;
+
+ /* Error-free Frames transmitted */
+ s->regs[GEM_TXCNT]++;
+
+ /* Error-free Broadcast Frames counter */
+ if (!memcmp(packet, broadcast_addr, 6)) {
+ s->regs[GEM_TXBCNT]++;
+ }
+
+ /* Error-free Multicast Frames counter */
+ if (packet[0] == 0x01) {
+ s->regs[GEM_TXMCNT]++;
+ }
+
+ if (bytes <= 64) {
+ s->regs[GEM_TX64CNT]++;
+ } else if (bytes <= 127) {
+ s->regs[GEM_TX65CNT]++;
+ } else if (bytes <= 255) {
+ s->regs[GEM_TX128CNT]++;
+ } else if (bytes <= 511) {
+ s->regs[GEM_TX256CNT]++;
+ } else if (bytes <= 1023) {
+ s->regs[GEM_TX512CNT]++;
+ } else if (bytes <= 1518) {
+ s->regs[GEM_TX1024CNT]++;
+ } else {
+ s->regs[GEM_TX1519CNT]++;
+ }
+}
+
+/*
+ * gem_transmit:
+ * Fish packets out of the descriptor ring and feed them to QEMU
+ */
+static void gem_transmit(CadenceGEMState *s)
+{
+ unsigned desc[2];
+ hwaddr packet_desc_addr;
+ uint8_t tx_packet[2048];
+ uint8_t *p;
+ unsigned total_bytes;
+
+ /* Do nothing if transmit is not enabled. */
+ if (!(s->regs[GEM_NWCTRL] & GEM_NWCTRL_TXENA)) {
+ return;
+ }
+
+ DB_PRINT("\n");
+
+ /* The packet we will hand off to QEMU.
+ * Packets scattered across multiple descriptors are gathered to this
+ * one contiguous buffer first.
+ */
+ p = tx_packet;
+ total_bytes = 0;
+
+ /* read current descriptor */
+ packet_desc_addr = s->tx_desc_addr;
+
+ DB_PRINT("read descriptor 0x%" HWADDR_PRIx "\n", packet_desc_addr);
+ cpu_physical_memory_read(packet_desc_addr,
+ (uint8_t *)desc, sizeof(desc));
+ /* Handle all descriptors owned by hardware */
+ while (tx_desc_get_used(desc) == 0) {
+
+ /* Do nothing if transmit is not enabled. */
+ if (!(s->regs[GEM_NWCTRL] & GEM_NWCTRL_TXENA)) {
+ return;
+ }
+ print_gem_tx_desc(desc);
+
+ /* The real hardware would eat this (and possibly crash).
+ * For QEMU let's lend a helping hand.
+ */
+ if ((tx_desc_get_buffer(desc) == 0) ||
+ (tx_desc_get_length(desc) == 0)) {
+ DB_PRINT("Invalid TX descriptor @ 0x%x\n",
+ (unsigned)packet_desc_addr);
+ break;
+ }
+
+ /* Gather this fragment of the packet from "dma memory" to our contig.
+ * buffer.
+ */
+ cpu_physical_memory_read(tx_desc_get_buffer(desc), p,
+ tx_desc_get_length(desc));
+ p += tx_desc_get_length(desc);
+ total_bytes += tx_desc_get_length(desc);
+
+ /* Last descriptor for this packet; hand the whole thing off */
+ if (tx_desc_get_last(desc)) {
+ unsigned desc_first[2];
+
+ /* Modify the 1st descriptor of this packet to be owned by
+ * the processor.
+ */
+ cpu_physical_memory_read(s->tx_desc_addr, (uint8_t *)desc_first,
+ sizeof(desc_first));
+ tx_desc_set_used(desc_first);
+ cpu_physical_memory_write(s->tx_desc_addr, (uint8_t *)desc_first,
+ sizeof(desc_first));
+ /* Advance the hardware current descriptor past this packet */
+ if (tx_desc_get_wrap(desc)) {
+ s->tx_desc_addr = s->regs[GEM_TXQBASE];
+ } else {
+ s->tx_desc_addr = packet_desc_addr + 8;
+ }
+ DB_PRINT("TX descriptor next: 0x%08x\n", s->tx_desc_addr);
+
+ s->regs[GEM_TXSTATUS] |= GEM_TXSTATUS_TXCMPL;
+ s->regs[GEM_ISR] |= GEM_INT_TXCMPL & ~(s->regs[GEM_IMR]);
+
+ /* Handle interrupt consequences */
+ gem_update_int_status(s);
+
+ /* Is checksum offload enabled? */
+ if (s->regs[GEM_DMACFG] & GEM_DMACFG_TXCSUM_OFFL) {
+ net_checksum_calculate(tx_packet, total_bytes);
+ }
+
+ /* Update MAC statistics */
+ gem_transmit_updatestats(s, tx_packet, total_bytes);
+
+ /* Send the packet somewhere */
+ if (s->phy_loop || (s->regs[GEM_NWCTRL] & GEM_NWCTRL_LOCALLOOP)) {
+ gem_receive(qemu_get_queue(s->nic), tx_packet, total_bytes);
+ } else {
+ qemu_send_packet(qemu_get_queue(s->nic), tx_packet,
+ total_bytes);
+ }
+
+ /* Prepare for next packet */
+ p = tx_packet;
+ total_bytes = 0;
+ }
+
+ /* read next descriptor */
+ if (tx_desc_get_wrap(desc)) {
+ packet_desc_addr = s->regs[GEM_TXQBASE];
+ } else {
+ packet_desc_addr += 8;
+ }
+ DB_PRINT("read descriptor 0x%" HWADDR_PRIx "\n", packet_desc_addr);
+ cpu_physical_memory_read(packet_desc_addr,
+ (uint8_t *)desc, sizeof(desc));
+ }
+
+ if (tx_desc_get_used(desc)) {
+ s->regs[GEM_TXSTATUS] |= GEM_TXSTATUS_USED;
+ s->regs[GEM_ISR] |= GEM_INT_TXUSED & ~(s->regs[GEM_IMR]);
+ gem_update_int_status(s);
+ }
+}
+
+static void gem_phy_reset(CadenceGEMState *s)
+{
+ memset(&s->phy_regs[0], 0, sizeof(s->phy_regs));
+ s->phy_regs[PHY_REG_CONTROL] = 0x1140;
+ s->phy_regs[PHY_REG_STATUS] = 0x7969;
+ s->phy_regs[PHY_REG_PHYID1] = 0x0141;
+ s->phy_regs[PHY_REG_PHYID2] = 0x0CC2;
+ s->phy_regs[PHY_REG_ANEGADV] = 0x01E1;
+ s->phy_regs[PHY_REG_LINKPABIL] = 0xCDE1;
+ s->phy_regs[PHY_REG_ANEGEXP] = 0x000F;
+ s->phy_regs[PHY_REG_NEXTP] = 0x2001;
+ s->phy_regs[PHY_REG_LINKPNEXTP] = 0x40E6;
+ s->phy_regs[PHY_REG_100BTCTRL] = 0x0300;
+ s->phy_regs[PHY_REG_1000BTSTAT] = 0x7C00;
+ s->phy_regs[PHY_REG_EXTSTAT] = 0x3000;
+ s->phy_regs[PHY_REG_PHYSPCFC_CTL] = 0x0078;
+ s->phy_regs[PHY_REG_PHYSPCFC_ST] = 0xBC00;
+ s->phy_regs[PHY_REG_EXT_PHYSPCFC_CTL] = 0x0C60;
+ s->phy_regs[PHY_REG_LED] = 0x4100;
+ s->phy_regs[PHY_REG_EXT_PHYSPCFC_CTL2] = 0x000A;
+ s->phy_regs[PHY_REG_EXT_PHYSPCFC_ST] = 0x848B;
+
+ phy_update_link(s);
+}
+
+static void gem_reset(DeviceState *d)
+{
+ int i;
+ CadenceGEMState *s = CADENCE_GEM(d);
+
+ DB_PRINT("\n");
+
+ /* Set post reset register values */
+ memset(&s->regs[0], 0, sizeof(s->regs));
+ s->regs[GEM_NWCFG] = 0x00080000;
+ s->regs[GEM_NWSTATUS] = 0x00000006;
+ s->regs[GEM_DMACFG] = 0x00020784;
+ s->regs[GEM_IMR] = 0x07ffffff;
+ s->regs[GEM_TXPAUSE] = 0x0000ffff;
+ s->regs[GEM_TXPARTIALSF] = 0x000003ff;
+ s->regs[GEM_RXPARTIALSF] = 0x000003ff;
+ s->regs[GEM_MODID] = 0x00020118;
+ s->regs[GEM_DESCONF] = 0x02500111;
+ s->regs[GEM_DESCONF2] = 0x2ab13fff;
+ s->regs[GEM_DESCONF5] = 0x002f2145;
+ s->regs[GEM_DESCONF6] = 0x00000200;
+
+ for (i = 0; i < 4; i++) {
+ s->sar_active[i] = false;
+ }
+
+ gem_phy_reset(s);
+
+ gem_update_int_status(s);
+}
+
+static uint16_t gem_phy_read(CadenceGEMState *s, unsigned reg_num)
+{
+ DB_PRINT("reg: %d value: 0x%04x\n", reg_num, s->phy_regs[reg_num]);
+ return s->phy_regs[reg_num];
+}
+
+static void gem_phy_write(CadenceGEMState *s, unsigned reg_num, uint16_t val)
+{
+ DB_PRINT("reg: %d value: 0x%04x\n", reg_num, val);
+
+ switch (reg_num) {
+ case PHY_REG_CONTROL:
+ if (val & PHY_REG_CONTROL_RST) {
+ /* Phy reset */
+ gem_phy_reset(s);
+ val &= ~(PHY_REG_CONTROL_RST | PHY_REG_CONTROL_LOOP);
+ s->phy_loop = 0;
+ }
+ if (val & PHY_REG_CONTROL_ANEG) {
+ /* Complete autonegotiation immediately */
+ val &= ~PHY_REG_CONTROL_ANEG;
+ s->phy_regs[PHY_REG_STATUS] |= PHY_REG_STATUS_ANEGCMPL;
+ }
+ if (val & PHY_REG_CONTROL_LOOP) {
+ DB_PRINT("PHY placed in loopback\n");
+ s->phy_loop = 1;
+ } else {
+ s->phy_loop = 0;
+ }
+ break;
+ }
+ s->phy_regs[reg_num] = val;
+}
+
+/*
+ * gem_read32:
+ * Read a GEM register.
+ */
+static uint64_t gem_read(void *opaque, hwaddr offset, unsigned size)
+{
+ CadenceGEMState *s;
+ uint32_t retval;
+
+ s = (CadenceGEMState *)opaque;
+
+ offset >>= 2;
+ retval = s->regs[offset];
+
+ DB_PRINT("offset: 0x%04x read: 0x%08x\n", (unsigned)offset*4, retval);
+
+ switch (offset) {
+ case GEM_ISR:
+ DB_PRINT("lowering irq on ISR read\n");
+ qemu_set_irq(s->irq, 0);
+ break;
+ case GEM_PHYMNTNC:
+ if (retval & GEM_PHYMNTNC_OP_R) {
+ uint32_t phy_addr, reg_num;
+
+ phy_addr = (retval & GEM_PHYMNTNC_ADDR) >> GEM_PHYMNTNC_ADDR_SHFT;
+ if (phy_addr == BOARD_PHY_ADDRESS || phy_addr == 0) {
+ reg_num = (retval & GEM_PHYMNTNC_REG) >> GEM_PHYMNTNC_REG_SHIFT;
+ retval &= 0xFFFF0000;
+ retval |= gem_phy_read(s, reg_num);
+ } else {
+ retval |= 0xFFFF; /* No device at this address */
+ }
+ }
+ break;
+ }
+
+ /* Squash read to clear bits */
+ s->regs[offset] &= ~(s->regs_rtc[offset]);
+
+ /* Do not provide write only bits */
+ retval &= ~(s->regs_wo[offset]);
+
+ DB_PRINT("0x%08x\n", retval);
+ return retval;
+}
+
+/*
+ * gem_write32:
+ * Write a GEM register.
+ */
+static void gem_write(void *opaque, hwaddr offset, uint64_t val,
+ unsigned size)
+{
+ CadenceGEMState *s = (CadenceGEMState *)opaque;
+ uint32_t readonly;
+
+ DB_PRINT("offset: 0x%04x write: 0x%08x ", (unsigned)offset, (unsigned)val);
+ offset >>= 2;
+
+ /* Squash bits which are read only in write value */
+ val &= ~(s->regs_ro[offset]);
+ /* Preserve (only) bits which are read only and wtc in register */
+ readonly = s->regs[offset] & (s->regs_ro[offset] | s->regs_w1c[offset]);
+
+ /* Copy register write to backing store */
+ s->regs[offset] = (val & ~s->regs_w1c[offset]) | readonly;
+
+ /* do w1c */
+ s->regs[offset] &= ~(s->regs_w1c[offset] & val);
+
+ /* Handle register write side effects */
+ switch (offset) {
+ case GEM_NWCTRL:
+ if (val & GEM_NWCTRL_RXENA) {
+ gem_get_rx_desc(s);
+ }
+ if (val & GEM_NWCTRL_TXSTART) {
+ gem_transmit(s);
+ }
+ if (!(val & GEM_NWCTRL_TXENA)) {
+ /* Reset to start of Q when transmit disabled. */
+ s->tx_desc_addr = s->regs[GEM_TXQBASE];
+ }
+ if (gem_can_receive(qemu_get_queue(s->nic))) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ break;
+
+ case GEM_TXSTATUS:
+ gem_update_int_status(s);
+ break;
+ case GEM_RXQBASE:
+ s->rx_desc_addr = val;
+ break;
+ case GEM_TXQBASE:
+ s->tx_desc_addr = val;
+ break;
+ case GEM_RXSTATUS:
+ gem_update_int_status(s);
+ break;
+ case GEM_IER:
+ s->regs[GEM_IMR] &= ~val;
+ gem_update_int_status(s);
+ break;
+ case GEM_IDR:
+ s->regs[GEM_IMR] |= val;
+ gem_update_int_status(s);
+ break;
+ case GEM_SPADDR1LO:
+ case GEM_SPADDR2LO:
+ case GEM_SPADDR3LO:
+ case GEM_SPADDR4LO:
+ s->sar_active[(offset - GEM_SPADDR1LO) / 2] = false;
+ break;
+ case GEM_SPADDR1HI:
+ case GEM_SPADDR2HI:
+ case GEM_SPADDR3HI:
+ case GEM_SPADDR4HI:
+ s->sar_active[(offset - GEM_SPADDR1HI) / 2] = true;
+ break;
+ case GEM_PHYMNTNC:
+ if (val & GEM_PHYMNTNC_OP_W) {
+ uint32_t phy_addr, reg_num;
+
+ phy_addr = (val & GEM_PHYMNTNC_ADDR) >> GEM_PHYMNTNC_ADDR_SHFT;
+ if (phy_addr == BOARD_PHY_ADDRESS || phy_addr == 0) {
+ reg_num = (val & GEM_PHYMNTNC_REG) >> GEM_PHYMNTNC_REG_SHIFT;
+ gem_phy_write(s, reg_num, val);
+ }
+ }
+ break;
+ }
+
+ DB_PRINT("newval: 0x%08x\n", s->regs[offset]);
+}
+
+static const MemoryRegionOps gem_ops = {
+ .read = gem_read,
+ .write = gem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void gem_set_link(NetClientState *nc)
+{
+ DB_PRINT("\n");
+ phy_update_link(qemu_get_nic_opaque(nc));
+}
+
+static NetClientInfo net_gem_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = gem_can_receive,
+ .receive = gem_receive,
+ .link_status_changed = gem_set_link,
+};
+
+static int gem_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ CadenceGEMState *s = CADENCE_GEM(dev);
+
+ DB_PRINT("\n");
+
+ gem_init_register_masks(s);
+ memory_region_init_io(&s->iomem, OBJECT(s), &gem_ops, s,
+ "enet", sizeof(s->regs));
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+
+ s->nic = qemu_new_nic(&net_gem_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_cadence_gem = {
+ .name = "cadence_gem",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, CadenceGEMState, CADENCE_GEM_MAXREG),
+ VMSTATE_UINT16_ARRAY(phy_regs, CadenceGEMState, 32),
+ VMSTATE_UINT8(phy_loop, CadenceGEMState),
+ VMSTATE_UINT32(rx_desc_addr, CadenceGEMState),
+ VMSTATE_UINT32(tx_desc_addr, CadenceGEMState),
+ VMSTATE_BOOL_ARRAY(sar_active, CadenceGEMState, 4),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property gem_properties[] = {
+ DEFINE_NIC_PROPERTIES(CadenceGEMState, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void gem_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = gem_init;
+ dc->props = gem_properties;
+ dc->vmsd = &vmstate_cadence_gem;
+ dc->reset = gem_reset;
+}
+
+static const TypeInfo gem_info = {
+ .name = TYPE_CADENCE_GEM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CadenceGEMState),
+ .class_init = gem_class_init,
+};
+
+static void gem_register_types(void)
+{
+ type_register_static(&gem_info);
+}
+
+type_init(gem_register_types)
diff --git a/hw/net/dp8393x.c b/hw/net/dp8393x.c
new file mode 100644
index 00000000..ab607e48
--- /dev/null
+++ b/hw/net/dp8393x.c
@@ -0,0 +1,910 @@
+/*
+ * QEMU NS SONIC DP8393x netcard
+ *
+ * Copyright (c) 2008-2009 Herve Poussineau
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "qemu/timer.h"
+#include <zlib.h>
+
+//#define DEBUG_SONIC
+
+#define SONIC_PROM_SIZE 0x1000
+
+#ifdef DEBUG_SONIC
+#define DPRINTF(fmt, ...) \
+do { printf("sonic: " fmt , ## __VA_ARGS__); } while (0)
+static const char* reg_names[] = {
+ "CR", "DCR", "RCR", "TCR", "IMR", "ISR", "UTDA", "CTDA",
+ "TPS", "TFC", "TSA0", "TSA1", "TFS", "URDA", "CRDA", "CRBA0",
+ "CRBA1", "RBWC0", "RBWC1", "EOBC", "URRA", "RSA", "REA", "RRP",
+ "RWP", "TRBA0", "TRBA1", "0x1b", "0x1c", "0x1d", "0x1e", "LLFA",
+ "TTDA", "CEP", "CAP2", "CAP1", "CAP0", "CE", "CDP", "CDC",
+ "SR", "WT0", "WT1", "RSC", "CRCT", "FAET", "MPT", "MDT",
+ "0x30", "0x31", "0x32", "0x33", "0x34", "0x35", "0x36", "0x37",
+ "0x38", "0x39", "0x3a", "0x3b", "0x3c", "0x3d", "0x3e", "DCR2" };
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define SONIC_ERROR(fmt, ...) \
+do { printf("sonic ERROR: %s: " fmt, __func__ , ## __VA_ARGS__); } while (0)
+
+#define SONIC_CR 0x00
+#define SONIC_DCR 0x01
+#define SONIC_RCR 0x02
+#define SONIC_TCR 0x03
+#define SONIC_IMR 0x04
+#define SONIC_ISR 0x05
+#define SONIC_UTDA 0x06
+#define SONIC_CTDA 0x07
+#define SONIC_TPS 0x08
+#define SONIC_TFC 0x09
+#define SONIC_TSA0 0x0a
+#define SONIC_TSA1 0x0b
+#define SONIC_TFS 0x0c
+#define SONIC_URDA 0x0d
+#define SONIC_CRDA 0x0e
+#define SONIC_CRBA0 0x0f
+#define SONIC_CRBA1 0x10
+#define SONIC_RBWC0 0x11
+#define SONIC_RBWC1 0x12
+#define SONIC_EOBC 0x13
+#define SONIC_URRA 0x14
+#define SONIC_RSA 0x15
+#define SONIC_REA 0x16
+#define SONIC_RRP 0x17
+#define SONIC_RWP 0x18
+#define SONIC_TRBA0 0x19
+#define SONIC_TRBA1 0x1a
+#define SONIC_LLFA 0x1f
+#define SONIC_TTDA 0x20
+#define SONIC_CEP 0x21
+#define SONIC_CAP2 0x22
+#define SONIC_CAP1 0x23
+#define SONIC_CAP0 0x24
+#define SONIC_CE 0x25
+#define SONIC_CDP 0x26
+#define SONIC_CDC 0x27
+#define SONIC_SR 0x28
+#define SONIC_WT0 0x29
+#define SONIC_WT1 0x2a
+#define SONIC_RSC 0x2b
+#define SONIC_CRCT 0x2c
+#define SONIC_FAET 0x2d
+#define SONIC_MPT 0x2e
+#define SONIC_MDT 0x2f
+#define SONIC_DCR2 0x3f
+
+#define SONIC_CR_HTX 0x0001
+#define SONIC_CR_TXP 0x0002
+#define SONIC_CR_RXDIS 0x0004
+#define SONIC_CR_RXEN 0x0008
+#define SONIC_CR_STP 0x0010
+#define SONIC_CR_ST 0x0020
+#define SONIC_CR_RST 0x0080
+#define SONIC_CR_RRRA 0x0100
+#define SONIC_CR_LCAM 0x0200
+#define SONIC_CR_MASK 0x03bf
+
+#define SONIC_DCR_DW 0x0020
+#define SONIC_DCR_LBR 0x2000
+#define SONIC_DCR_EXBUS 0x8000
+
+#define SONIC_RCR_PRX 0x0001
+#define SONIC_RCR_LBK 0x0002
+#define SONIC_RCR_FAER 0x0004
+#define SONIC_RCR_CRCR 0x0008
+#define SONIC_RCR_CRS 0x0020
+#define SONIC_RCR_LPKT 0x0040
+#define SONIC_RCR_BC 0x0080
+#define SONIC_RCR_MC 0x0100
+#define SONIC_RCR_LB0 0x0200
+#define SONIC_RCR_LB1 0x0400
+#define SONIC_RCR_AMC 0x0800
+#define SONIC_RCR_PRO 0x1000
+#define SONIC_RCR_BRD 0x2000
+#define SONIC_RCR_RNT 0x4000
+
+#define SONIC_TCR_PTX 0x0001
+#define SONIC_TCR_BCM 0x0002
+#define SONIC_TCR_FU 0x0004
+#define SONIC_TCR_EXC 0x0040
+#define SONIC_TCR_CRSL 0x0080
+#define SONIC_TCR_NCRS 0x0100
+#define SONIC_TCR_EXD 0x0400
+#define SONIC_TCR_CRCI 0x2000
+#define SONIC_TCR_PINT 0x8000
+
+#define SONIC_ISR_RBE 0x0020
+#define SONIC_ISR_RDE 0x0040
+#define SONIC_ISR_TC 0x0080
+#define SONIC_ISR_TXDN 0x0200
+#define SONIC_ISR_PKTRX 0x0400
+#define SONIC_ISR_PINT 0x0800
+#define SONIC_ISR_LCD 0x1000
+
+#define TYPE_DP8393X "dp8393x"
+#define DP8393X(obj) OBJECT_CHECK(dp8393xState, (obj), TYPE_DP8393X)
+
+typedef struct dp8393xState {
+ SysBusDevice parent_obj;
+
+ /* Hardware */
+ uint8_t it_shift;
+ qemu_irq irq;
+#ifdef DEBUG_SONIC
+ int irq_level;
+#endif
+ QEMUTimer *watchdog;
+ int64_t wt_last_update;
+ NICConf conf;
+ NICState *nic;
+ MemoryRegion mmio;
+ MemoryRegion prom;
+
+ /* Registers */
+ uint8_t cam[16][6];
+ uint16_t regs[0x40];
+
+ /* Temporaries */
+ uint8_t tx_buffer[0x10000];
+ int loopback_packet;
+
+ /* Memory access */
+ void *dma_mr;
+ AddressSpace as;
+} dp8393xState;
+
+static void dp8393x_update_irq(dp8393xState *s)
+{
+ int level = (s->regs[SONIC_IMR] & s->regs[SONIC_ISR]) ? 1 : 0;
+
+#ifdef DEBUG_SONIC
+ if (level != s->irq_level) {
+ s->irq_level = level;
+ if (level) {
+ DPRINTF("raise irq, isr is 0x%04x\n", s->regs[SONIC_ISR]);
+ } else {
+ DPRINTF("lower irq\n");
+ }
+ }
+#endif
+
+ qemu_set_irq(s->irq, level);
+}
+
+static void dp8393x_do_load_cam(dp8393xState *s)
+{
+ uint16_t data[8];
+ int width, size;
+ uint16_t index = 0;
+
+ width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
+ size = sizeof(uint16_t) * 4 * width;
+
+ while (s->regs[SONIC_CDC] & 0x1f) {
+ /* Fill current entry */
+ address_space_rw(&s->as,
+ (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_CDP],
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
+ s->cam[index][0] = data[1 * width] & 0xff;
+ s->cam[index][1] = data[1 * width] >> 8;
+ s->cam[index][2] = data[2 * width] & 0xff;
+ s->cam[index][3] = data[2 * width] >> 8;
+ s->cam[index][4] = data[3 * width] & 0xff;
+ s->cam[index][5] = data[3 * width] >> 8;
+ DPRINTF("load cam[%d] with %02x%02x%02x%02x%02x%02x\n", index,
+ s->cam[index][0], s->cam[index][1], s->cam[index][2],
+ s->cam[index][3], s->cam[index][4], s->cam[index][5]);
+ /* Move to next entry */
+ s->regs[SONIC_CDC]--;
+ s->regs[SONIC_CDP] += size;
+ index++;
+ }
+
+ /* Read CAM enable */
+ address_space_rw(&s->as,
+ (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_CDP],
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
+ s->regs[SONIC_CE] = data[0 * width];
+ DPRINTF("load cam done. cam enable mask 0x%04x\n", s->regs[SONIC_CE]);
+
+ /* Done */
+ s->regs[SONIC_CR] &= ~SONIC_CR_LCAM;
+ s->regs[SONIC_ISR] |= SONIC_ISR_LCD;
+ dp8393x_update_irq(s);
+}
+
+static void dp8393x_do_read_rra(dp8393xState *s)
+{
+ uint16_t data[8];
+ int width, size;
+
+ /* Read memory */
+ width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
+ size = sizeof(uint16_t) * 4 * width;
+ address_space_rw(&s->as,
+ (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_RRP],
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
+
+ /* Update SONIC registers */
+ s->regs[SONIC_CRBA0] = data[0 * width];
+ s->regs[SONIC_CRBA1] = data[1 * width];
+ s->regs[SONIC_RBWC0] = data[2 * width];
+ s->regs[SONIC_RBWC1] = data[3 * width];
+ DPRINTF("CRBA0/1: 0x%04x/0x%04x, RBWC0/1: 0x%04x/0x%04x\n",
+ s->regs[SONIC_CRBA0], s->regs[SONIC_CRBA1],
+ s->regs[SONIC_RBWC0], s->regs[SONIC_RBWC1]);
+
+ /* Go to next entry */
+ s->regs[SONIC_RRP] += size;
+
+ /* Handle wrap */
+ if (s->regs[SONIC_RRP] == s->regs[SONIC_REA]) {
+ s->regs[SONIC_RRP] = s->regs[SONIC_RSA];
+ }
+
+ /* Check resource exhaustion */
+ if (s->regs[SONIC_RRP] == s->regs[SONIC_RWP])
+ {
+ s->regs[SONIC_ISR] |= SONIC_ISR_RBE;
+ dp8393x_update_irq(s);
+ }
+
+ /* Done */
+ s->regs[SONIC_CR] &= ~SONIC_CR_RRRA;
+}
+
+static void dp8393x_do_software_reset(dp8393xState *s)
+{
+ timer_del(s->watchdog);
+
+ s->regs[SONIC_CR] &= ~(SONIC_CR_LCAM | SONIC_CR_RRRA | SONIC_CR_TXP | SONIC_CR_HTX);
+ s->regs[SONIC_CR] |= SONIC_CR_RST | SONIC_CR_RXDIS;
+}
+
+static void dp8393x_set_next_tick(dp8393xState *s)
+{
+ uint32_t ticks;
+ int64_t delay;
+
+ if (s->regs[SONIC_CR] & SONIC_CR_STP) {
+ timer_del(s->watchdog);
+ return;
+ }
+
+ ticks = s->regs[SONIC_WT1] << 16 | s->regs[SONIC_WT0];
+ s->wt_last_update = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ delay = get_ticks_per_sec() * ticks / 5000000;
+ timer_mod(s->watchdog, s->wt_last_update + delay);
+}
+
+static void dp8393x_update_wt_regs(dp8393xState *s)
+{
+ int64_t elapsed;
+ uint32_t val;
+
+ if (s->regs[SONIC_CR] & SONIC_CR_STP) {
+ timer_del(s->watchdog);
+ return;
+ }
+
+ elapsed = s->wt_last_update - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ val = s->regs[SONIC_WT1] << 16 | s->regs[SONIC_WT0];
+ val -= elapsed / 5000000;
+ s->regs[SONIC_WT1] = (val >> 16) & 0xffff;
+ s->regs[SONIC_WT0] = (val >> 0) & 0xffff;
+ dp8393x_set_next_tick(s);
+
+}
+
+static void dp8393x_do_start_timer(dp8393xState *s)
+{
+ s->regs[SONIC_CR] &= ~SONIC_CR_STP;
+ dp8393x_set_next_tick(s);
+}
+
+static void dp8393x_do_stop_timer(dp8393xState *s)
+{
+ s->regs[SONIC_CR] &= ~SONIC_CR_ST;
+ dp8393x_update_wt_regs(s);
+}
+
+static int dp8393x_can_receive(NetClientState *nc);
+
+static void dp8393x_do_receiver_enable(dp8393xState *s)
+{
+ s->regs[SONIC_CR] &= ~SONIC_CR_RXDIS;
+ if (dp8393x_can_receive(s->nic->ncs)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+}
+
+static void dp8393x_do_receiver_disable(dp8393xState *s)
+{
+ s->regs[SONIC_CR] &= ~SONIC_CR_RXEN;
+}
+
+static void dp8393x_do_transmit_packets(dp8393xState *s)
+{
+ NetClientState *nc = qemu_get_queue(s->nic);
+ uint16_t data[12];
+ int width, size;
+ int tx_len, len;
+ uint16_t i;
+
+ width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
+
+ while (1) {
+ /* Read memory */
+ DPRINTF("Transmit packet at %08x\n",
+ (s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_CTDA]);
+ size = sizeof(uint16_t) * 6 * width;
+ s->regs[SONIC_TTDA] = s->regs[SONIC_CTDA];
+ address_space_rw(&s->as,
+ ((s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA]) + sizeof(uint16_t) * width,
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
+ tx_len = 0;
+
+ /* Update registers */
+ s->regs[SONIC_TCR] = data[0 * width] & 0xf000;
+ s->regs[SONIC_TPS] = data[1 * width];
+ s->regs[SONIC_TFC] = data[2 * width];
+ s->regs[SONIC_TSA0] = data[3 * width];
+ s->regs[SONIC_TSA1] = data[4 * width];
+ s->regs[SONIC_TFS] = data[5 * width];
+
+ /* Handle programmable interrupt */
+ if (s->regs[SONIC_TCR] & SONIC_TCR_PINT) {
+ s->regs[SONIC_ISR] |= SONIC_ISR_PINT;
+ } else {
+ s->regs[SONIC_ISR] &= ~SONIC_ISR_PINT;
+ }
+
+ for (i = 0; i < s->regs[SONIC_TFC]; ) {
+ /* Append fragment */
+ len = s->regs[SONIC_TFS];
+ if (tx_len + len > sizeof(s->tx_buffer)) {
+ len = sizeof(s->tx_buffer) - tx_len;
+ }
+ address_space_rw(&s->as,
+ (s->regs[SONIC_TSA1] << 16) | s->regs[SONIC_TSA0],
+ MEMTXATTRS_UNSPECIFIED, &s->tx_buffer[tx_len], len, 0);
+ tx_len += len;
+
+ i++;
+ if (i != s->regs[SONIC_TFC]) {
+ /* Read next fragment details */
+ size = sizeof(uint16_t) * 3 * width;
+ address_space_rw(&s->as,
+ ((s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA]) + sizeof(uint16_t) * (4 + 3 * i) * width,
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
+ s->regs[SONIC_TSA0] = data[0 * width];
+ s->regs[SONIC_TSA1] = data[1 * width];
+ s->regs[SONIC_TFS] = data[2 * width];
+ }
+ }
+
+ /* Handle Ethernet checksum */
+ if (!(s->regs[SONIC_TCR] & SONIC_TCR_CRCI)) {
+ /* Don't append FCS there, to look like slirp packets
+ * which don't have one */
+ } else {
+ /* Remove existing FCS */
+ tx_len -= 4;
+ }
+
+ if (s->regs[SONIC_RCR] & (SONIC_RCR_LB1 | SONIC_RCR_LB0)) {
+ /* Loopback */
+ s->regs[SONIC_TCR] |= SONIC_TCR_CRSL;
+ if (nc->info->can_receive(nc)) {
+ s->loopback_packet = 1;
+ nc->info->receive(nc, s->tx_buffer, tx_len);
+ }
+ } else {
+ /* Transmit packet */
+ qemu_send_packet(nc, s->tx_buffer, tx_len);
+ }
+ s->regs[SONIC_TCR] |= SONIC_TCR_PTX;
+
+ /* Write status */
+ data[0 * width] = s->regs[SONIC_TCR] & 0x0fff; /* status */
+ size = sizeof(uint16_t) * width;
+ address_space_rw(&s->as,
+ (s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA],
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 1);
+
+ if (!(s->regs[SONIC_CR] & SONIC_CR_HTX)) {
+ /* Read footer of packet */
+ size = sizeof(uint16_t) * width;
+ address_space_rw(&s->as,
+ ((s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA]) + sizeof(uint16_t) * (4 + 3 * s->regs[SONIC_TFC]) * width,
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
+ s->regs[SONIC_CTDA] = data[0 * width] & ~0x1;
+ if (data[0 * width] & 0x1) {
+ /* EOL detected */
+ break;
+ }
+ }
+ }
+
+ /* Done */
+ s->regs[SONIC_CR] &= ~SONIC_CR_TXP;
+ s->regs[SONIC_ISR] |= SONIC_ISR_TXDN;
+ dp8393x_update_irq(s);
+}
+
+static void dp8393x_do_halt_transmission(dp8393xState *s)
+{
+ /* Nothing to do */
+}
+
+static void dp8393x_do_command(dp8393xState *s, uint16_t command)
+{
+ if ((s->regs[SONIC_CR] & SONIC_CR_RST) && !(command & SONIC_CR_RST)) {
+ s->regs[SONIC_CR] &= ~SONIC_CR_RST;
+ return;
+ }
+
+ s->regs[SONIC_CR] |= (command & SONIC_CR_MASK);
+
+ if (command & SONIC_CR_HTX)
+ dp8393x_do_halt_transmission(s);
+ if (command & SONIC_CR_TXP)
+ dp8393x_do_transmit_packets(s);
+ if (command & SONIC_CR_RXDIS)
+ dp8393x_do_receiver_disable(s);
+ if (command & SONIC_CR_RXEN)
+ dp8393x_do_receiver_enable(s);
+ if (command & SONIC_CR_STP)
+ dp8393x_do_stop_timer(s);
+ if (command & SONIC_CR_ST)
+ dp8393x_do_start_timer(s);
+ if (command & SONIC_CR_RST)
+ dp8393x_do_software_reset(s);
+ if (command & SONIC_CR_RRRA)
+ dp8393x_do_read_rra(s);
+ if (command & SONIC_CR_LCAM)
+ dp8393x_do_load_cam(s);
+}
+
+static uint64_t dp8393x_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ dp8393xState *s = opaque;
+ int reg = addr >> s->it_shift;
+ uint16_t val = 0;
+
+ switch (reg) {
+ /* Update data before reading it */
+ case SONIC_WT0:
+ case SONIC_WT1:
+ dp8393x_update_wt_regs(s);
+ val = s->regs[reg];
+ break;
+ /* Accept read to some registers only when in reset mode */
+ case SONIC_CAP2:
+ case SONIC_CAP1:
+ case SONIC_CAP0:
+ if (s->regs[SONIC_CR] & SONIC_CR_RST) {
+ val = s->cam[s->regs[SONIC_CEP] & 0xf][2* (SONIC_CAP0 - reg) + 1] << 8;
+ val |= s->cam[s->regs[SONIC_CEP] & 0xf][2* (SONIC_CAP0 - reg)];
+ }
+ break;
+ /* All other registers have no special contrainst */
+ default:
+ val = s->regs[reg];
+ }
+
+ DPRINTF("read 0x%04x from reg %s\n", val, reg_names[reg]);
+
+ return val;
+}
+
+static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ dp8393xState *s = opaque;
+ int reg = addr >> s->it_shift;
+
+ DPRINTF("write 0x%04x to reg %s\n", (uint16_t)data, reg_names[reg]);
+
+ switch (reg) {
+ /* Command register */
+ case SONIC_CR:
+ dp8393x_do_command(s, data);
+ break;
+ /* Prevent write to read-only registers */
+ case SONIC_CAP2:
+ case SONIC_CAP1:
+ case SONIC_CAP0:
+ case SONIC_SR:
+ case SONIC_MDT:
+ DPRINTF("writing to reg %d invalid\n", reg);
+ break;
+ /* Accept write to some registers only when in reset mode */
+ case SONIC_DCR:
+ if (s->regs[SONIC_CR] & SONIC_CR_RST) {
+ s->regs[reg] = data & 0xbfff;
+ } else {
+ DPRINTF("writing to DCR invalid\n");
+ }
+ break;
+ case SONIC_DCR2:
+ if (s->regs[SONIC_CR] & SONIC_CR_RST) {
+ s->regs[reg] = data & 0xf017;
+ } else {
+ DPRINTF("writing to DCR2 invalid\n");
+ }
+ break;
+ /* 12 lower bytes are Read Only */
+ case SONIC_TCR:
+ s->regs[reg] = data & 0xf000;
+ break;
+ /* 9 lower bytes are Read Only */
+ case SONIC_RCR:
+ s->regs[reg] = data & 0xffe0;
+ break;
+ /* Ignore most significant bit */
+ case SONIC_IMR:
+ s->regs[reg] = data & 0x7fff;
+ dp8393x_update_irq(s);
+ break;
+ /* Clear bits by writing 1 to them */
+ case SONIC_ISR:
+ data &= s->regs[reg];
+ s->regs[reg] &= ~data;
+ if (data & SONIC_ISR_RBE) {
+ dp8393x_do_read_rra(s);
+ }
+ dp8393x_update_irq(s);
+ if (dp8393x_can_receive(s->nic->ncs)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ break;
+ /* Ignore least significant bit */
+ case SONIC_RSA:
+ case SONIC_REA:
+ case SONIC_RRP:
+ case SONIC_RWP:
+ s->regs[reg] = data & 0xfffe;
+ break;
+ /* Invert written value for some registers */
+ case SONIC_CRCT:
+ case SONIC_FAET:
+ case SONIC_MPT:
+ s->regs[reg] = data ^ 0xffff;
+ break;
+ /* All other registers have no special contrainst */
+ default:
+ s->regs[reg] = data;
+ }
+
+ if (reg == SONIC_WT0 || reg == SONIC_WT1) {
+ dp8393x_set_next_tick(s);
+ }
+}
+
+static const MemoryRegionOps dp8393x_ops = {
+ .read = dp8393x_read,
+ .write = dp8393x_write,
+ .impl.min_access_size = 2,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void dp8393x_watchdog(void *opaque)
+{
+ dp8393xState *s = opaque;
+
+ if (s->regs[SONIC_CR] & SONIC_CR_STP) {
+ return;
+ }
+
+ s->regs[SONIC_WT1] = 0xffff;
+ s->regs[SONIC_WT0] = 0xffff;
+ dp8393x_set_next_tick(s);
+
+ /* Signal underflow */
+ s->regs[SONIC_ISR] |= SONIC_ISR_TC;
+ dp8393x_update_irq(s);
+}
+
+static int dp8393x_can_receive(NetClientState *nc)
+{
+ dp8393xState *s = qemu_get_nic_opaque(nc);
+
+ if (!(s->regs[SONIC_CR] & SONIC_CR_RXEN))
+ return 0;
+ if (s->regs[SONIC_ISR] & SONIC_ISR_RBE)
+ return 0;
+ return 1;
+}
+
+static int dp8393x_receive_filter(dp8393xState *s, const uint8_t * buf,
+ int size)
+{
+ static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ int i;
+
+ /* Check promiscuous mode */
+ if ((s->regs[SONIC_RCR] & SONIC_RCR_PRO) && (buf[0] & 1) == 0) {
+ return 0;
+ }
+
+ /* Check multicast packets */
+ if ((s->regs[SONIC_RCR] & SONIC_RCR_AMC) && (buf[0] & 1) == 1) {
+ return SONIC_RCR_MC;
+ }
+
+ /* Check broadcast */
+ if ((s->regs[SONIC_RCR] & SONIC_RCR_BRD) && !memcmp(buf, bcast, sizeof(bcast))) {
+ return SONIC_RCR_BC;
+ }
+
+ /* Check CAM */
+ for (i = 0; i < 16; i++) {
+ if (s->regs[SONIC_CE] & (1 << i)) {
+ /* Entry enabled */
+ if (!memcmp(buf, s->cam[i], sizeof(s->cam[i]))) {
+ return 0;
+ }
+ }
+ }
+
+ return -1;
+}
+
+static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
+ size_t size)
+{
+ dp8393xState *s = qemu_get_nic_opaque(nc);
+ uint16_t data[10];
+ int packet_type;
+ uint32_t available, address;
+ int width, rx_len = size;
+ uint32_t checksum;
+
+ width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
+
+ s->regs[SONIC_RCR] &= ~(SONIC_RCR_PRX | SONIC_RCR_LBK | SONIC_RCR_FAER |
+ SONIC_RCR_CRCR | SONIC_RCR_LPKT | SONIC_RCR_BC | SONIC_RCR_MC);
+
+ packet_type = dp8393x_receive_filter(s, buf, size);
+ if (packet_type < 0) {
+ DPRINTF("packet not for netcard\n");
+ return -1;
+ }
+
+ /* XXX: Check byte ordering */
+
+ /* Check for EOL */
+ if (s->regs[SONIC_LLFA] & 0x1) {
+ /* Are we still in resource exhaustion? */
+ size = sizeof(uint16_t) * 1 * width;
+ address = ((s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA]) + sizeof(uint16_t) * 5 * width;
+ address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
+ (uint8_t *)data, size, 0);
+ if (data[0 * width] & 0x1) {
+ /* Still EOL ; stop reception */
+ return -1;
+ } else {
+ s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
+ }
+ }
+
+ /* Save current position */
+ s->regs[SONIC_TRBA1] = s->regs[SONIC_CRBA1];
+ s->regs[SONIC_TRBA0] = s->regs[SONIC_CRBA0];
+
+ /* Calculate the ethernet checksum */
+ checksum = cpu_to_le32(crc32(0, buf, rx_len));
+
+ /* Put packet into RBA */
+ DPRINTF("Receive packet at %08x\n", (s->regs[SONIC_CRBA1] << 16) | s->regs[SONIC_CRBA0]);
+ address = (s->regs[SONIC_CRBA1] << 16) | s->regs[SONIC_CRBA0];
+ address_space_rw(&s->as, address,
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)buf, rx_len, 1);
+ address += rx_len;
+ address_space_rw(&s->as, address,
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)&checksum, 4, 1);
+ rx_len += 4;
+ s->regs[SONIC_CRBA1] = address >> 16;
+ s->regs[SONIC_CRBA0] = address & 0xffff;
+ available = (s->regs[SONIC_RBWC1] << 16) | s->regs[SONIC_RBWC0];
+ available -= rx_len / 2;
+ s->regs[SONIC_RBWC1] = available >> 16;
+ s->regs[SONIC_RBWC0] = available & 0xffff;
+
+ /* Update status */
+ if (((s->regs[SONIC_RBWC1] << 16) | s->regs[SONIC_RBWC0]) < s->regs[SONIC_EOBC]) {
+ s->regs[SONIC_RCR] |= SONIC_RCR_LPKT;
+ }
+ s->regs[SONIC_RCR] |= packet_type;
+ s->regs[SONIC_RCR] |= SONIC_RCR_PRX;
+ if (s->loopback_packet) {
+ s->regs[SONIC_RCR] |= SONIC_RCR_LBK;
+ s->loopback_packet = 0;
+ }
+
+ /* Write status to memory */
+ DPRINTF("Write status at %08x\n", (s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA]);
+ data[0 * width] = s->regs[SONIC_RCR]; /* status */
+ data[1 * width] = rx_len; /* byte count */
+ data[2 * width] = s->regs[SONIC_TRBA0]; /* pkt_ptr0 */
+ data[3 * width] = s->regs[SONIC_TRBA1]; /* pkt_ptr1 */
+ data[4 * width] = s->regs[SONIC_RSC]; /* seq_no */
+ size = sizeof(uint16_t) * 5 * width;
+ address_space_rw(&s->as, (s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA],
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 1);
+
+ /* Move to next descriptor */
+ size = sizeof(uint16_t) * width;
+ address_space_rw(&s->as,
+ ((s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA]) + sizeof(uint16_t) * 5 * width,
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
+ s->regs[SONIC_LLFA] = data[0 * width];
+ if (s->regs[SONIC_LLFA] & 0x1) {
+ /* EOL detected */
+ s->regs[SONIC_ISR] |= SONIC_ISR_RDE;
+ } else {
+ data[0 * width] = 0; /* in_use */
+ address_space_rw(&s->as,
+ ((s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA]) + sizeof(uint16_t) * 6 * width,
+ MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, sizeof(uint16_t), 1);
+ s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
+ s->regs[SONIC_ISR] |= SONIC_ISR_PKTRX;
+ s->regs[SONIC_RSC] = (s->regs[SONIC_RSC] & 0xff00) | (((s->regs[SONIC_RSC] & 0x00ff) + 1) & 0x00ff);
+
+ if (s->regs[SONIC_RCR] & SONIC_RCR_LPKT) {
+ /* Read next RRA */
+ dp8393x_do_read_rra(s);
+ }
+ }
+
+ /* Done */
+ dp8393x_update_irq(s);
+
+ return size;
+}
+
+static void dp8393x_reset(DeviceState *dev)
+{
+ dp8393xState *s = DP8393X(dev);
+ timer_del(s->watchdog);
+
+ memset(s->regs, 0, sizeof(s->regs));
+ s->regs[SONIC_CR] = SONIC_CR_RST | SONIC_CR_STP | SONIC_CR_RXDIS;
+ s->regs[SONIC_DCR] &= ~(SONIC_DCR_EXBUS | SONIC_DCR_LBR);
+ s->regs[SONIC_RCR] &= ~(SONIC_RCR_LB0 | SONIC_RCR_LB1 | SONIC_RCR_BRD | SONIC_RCR_RNT);
+ s->regs[SONIC_TCR] |= SONIC_TCR_NCRS | SONIC_TCR_PTX;
+ s->regs[SONIC_TCR] &= ~SONIC_TCR_BCM;
+ s->regs[SONIC_IMR] = 0;
+ s->regs[SONIC_ISR] = 0;
+ s->regs[SONIC_DCR2] = 0;
+ s->regs[SONIC_EOBC] = 0x02F8;
+ s->regs[SONIC_RSC] = 0;
+ s->regs[SONIC_CE] = 0;
+ s->regs[SONIC_RSC] = 0;
+
+ /* Network cable is connected */
+ s->regs[SONIC_RCR] |= SONIC_RCR_CRS;
+
+ dp8393x_update_irq(s);
+}
+
+static NetClientInfo net_dp83932_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = dp8393x_can_receive,
+ .receive = dp8393x_receive,
+};
+
+static void dp8393x_instance_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ dp8393xState *s = DP8393X(obj);
+
+ sysbus_init_mmio(sbd, &s->mmio);
+ sysbus_init_mmio(sbd, &s->prom);
+ sysbus_init_irq(sbd, &s->irq);
+}
+
+static void dp8393x_realize(DeviceState *dev, Error **errp)
+{
+ dp8393xState *s = DP8393X(dev);
+ int i, checksum;
+ uint8_t *prom;
+ Error *local_err = NULL;
+
+ address_space_init(&s->as, s->dma_mr, "dp8393x");
+ memory_region_init_io(&s->mmio, OBJECT(dev), &dp8393x_ops, s,
+ "dp8393x-regs", 0x40 << s->it_shift);
+
+ s->nic = qemu_new_nic(&net_dp83932_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ s->watchdog = timer_new_ns(QEMU_CLOCK_VIRTUAL, dp8393x_watchdog, s);
+ s->regs[SONIC_SR] = 0x0004; /* only revision recognized by Linux */
+
+ memory_region_init_ram(&s->prom, OBJECT(dev),
+ "dp8393x-prom", SONIC_PROM_SIZE, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ memory_region_set_readonly(&s->prom, true);
+ prom = memory_region_get_ram_ptr(&s->prom);
+ checksum = 0;
+ for (i = 0; i < 6; i++) {
+ prom[i] = s->conf.macaddr.a[i];
+ checksum += prom[i];
+ if (checksum > 0xff) {
+ checksum = (checksum + 1) & 0xff;
+ }
+ }
+ prom[7] = 0xff - checksum;
+}
+
+static const VMStateDescription vmstate_dp8393x = {
+ .name = "dp8393x",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField []) {
+ VMSTATE_BUFFER_UNSAFE(cam, dp8393xState, 0, 16 * 6),
+ VMSTATE_UINT16_ARRAY(regs, dp8393xState, 0x40),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property dp8393x_properties[] = {
+ DEFINE_NIC_PROPERTIES(dp8393xState, conf),
+ DEFINE_PROP_PTR("dma_mr", dp8393xState, dma_mr),
+ DEFINE_PROP_UINT8("it_shift", dp8393xState, it_shift, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void dp8393x_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->realize = dp8393x_realize;
+ dc->reset = dp8393x_reset;
+ dc->vmsd = &vmstate_dp8393x;
+ dc->props = dp8393x_properties;
+ /* Reason: dma_mr property can't be set */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo dp8393x_info = {
+ .name = TYPE_DP8393X,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(dp8393xState),
+ .instance_init = dp8393x_instance_init,
+ .class_init = dp8393x_class_init,
+};
+
+static void dp8393x_register_types(void)
+{
+ type_register_static(&dp8393x_info);
+}
+
+type_init(dp8393x_register_types)
diff --git a/hw/net/e1000.c b/hw/net/e1000.c
new file mode 100644
index 00000000..09c9e9d5
--- /dev/null
+++ b/hw/net/e1000.c
@@ -0,0 +1,1694 @@
+/*
+ * QEMU e1000 emulation
+ *
+ * Software developer's manual:
+ * http://download.intel.com/design/network/manuals/8254x_GBe_SDM.pdf
+ *
+ * Nir Peleg, Tutis Systems Ltd. for Qumranet Inc.
+ * Copyright (c) 2008 Qumranet
+ * Based on work done by:
+ * Copyright (c) 2007 Dan Aloni
+ * Copyright (c) 2004 Antony T Curtis
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "net/net.h"
+#include "net/checksum.h"
+#include "hw/loader.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/dma.h"
+#include "qemu/iov.h"
+#include "qemu/range.h"
+
+#include "e1000_regs.h"
+
+#define E1000_DEBUG
+
+#ifdef E1000_DEBUG
+enum {
+ DEBUG_GENERAL, DEBUG_IO, DEBUG_MMIO, DEBUG_INTERRUPT,
+ DEBUG_RX, DEBUG_TX, DEBUG_MDIC, DEBUG_EEPROM,
+ DEBUG_UNKNOWN, DEBUG_TXSUM, DEBUG_TXERR, DEBUG_RXERR,
+ DEBUG_RXFILTER, DEBUG_PHY, DEBUG_NOTYET,
+};
+#define DBGBIT(x) (1<<DEBUG_##x)
+static int debugflags = DBGBIT(TXERR) | DBGBIT(GENERAL);
+
+#define DBGOUT(what, fmt, ...) do { \
+ if (debugflags & DBGBIT(what)) \
+ fprintf(stderr, "e1000: " fmt, ## __VA_ARGS__); \
+ } while (0)
+#else
+#define DBGOUT(what, fmt, ...) do {} while (0)
+#endif
+
+#define IOPORT_SIZE 0x40
+#define PNPMMIO_SIZE 0x20000
+#define MIN_BUF_SIZE 60 /* Min. octets in an ethernet frame sans FCS */
+
+/* this is the size past which hardware will drop packets when setting LPE=0 */
+#define MAXIMUM_ETHERNET_VLAN_SIZE 1522
+/* this is the size past which hardware will drop packets when setting LPE=1 */
+#define MAXIMUM_ETHERNET_LPE_SIZE 16384
+
+#define MAXIMUM_ETHERNET_HDR_LEN (14+4)
+
+/*
+ * HW models:
+ * E1000_DEV_ID_82540EM works with Windows, Linux, and OS X <= 10.8
+ * E1000_DEV_ID_82544GC_COPPER appears to work; not well tested
+ * E1000_DEV_ID_82545EM_COPPER works with Linux and OS X >= 10.6
+ * Others never tested
+ */
+
+typedef struct E1000State_st {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ NICState *nic;
+ NICConf conf;
+ MemoryRegion mmio;
+ MemoryRegion io;
+
+ uint32_t mac_reg[0x8000];
+ uint16_t phy_reg[0x20];
+ uint16_t eeprom_data[64];
+
+ uint32_t rxbuf_size;
+ uint32_t rxbuf_min_shift;
+ struct e1000_tx {
+ unsigned char header[256];
+ unsigned char vlan_header[4];
+ /* Fields vlan and data must not be reordered or separated. */
+ unsigned char vlan[4];
+ unsigned char data[0x10000];
+ uint16_t size;
+ unsigned char sum_needed;
+ unsigned char vlan_needed;
+ uint8_t ipcss;
+ uint8_t ipcso;
+ uint16_t ipcse;
+ uint8_t tucss;
+ uint8_t tucso;
+ uint16_t tucse;
+ uint8_t hdr_len;
+ uint16_t mss;
+ uint32_t paylen;
+ uint16_t tso_frames;
+ char tse;
+ int8_t ip;
+ int8_t tcp;
+ char cptse; // current packet tse bit
+ } tx;
+
+ struct {
+ uint32_t val_in; // shifted in from guest driver
+ uint16_t bitnum_in;
+ uint16_t bitnum_out;
+ uint16_t reading;
+ uint32_t old_eecd;
+ } eecd_state;
+
+ QEMUTimer *autoneg_timer;
+
+ QEMUTimer *mit_timer; /* Mitigation timer. */
+ bool mit_timer_on; /* Mitigation timer is running. */
+ bool mit_irq_level; /* Tracks interrupt pin level. */
+ uint32_t mit_ide; /* Tracks E1000_TXD_CMD_IDE bit. */
+
+/* Compatibility flags for migration to/from qemu 1.3.0 and older */
+#define E1000_FLAG_AUTONEG_BIT 0
+#define E1000_FLAG_MIT_BIT 1
+#define E1000_FLAG_AUTONEG (1 << E1000_FLAG_AUTONEG_BIT)
+#define E1000_FLAG_MIT (1 << E1000_FLAG_MIT_BIT)
+ uint32_t compat_flags;
+} E1000State;
+
+typedef struct E1000BaseClass {
+ PCIDeviceClass parent_class;
+ uint16_t phy_id2;
+} E1000BaseClass;
+
+#define TYPE_E1000_BASE "e1000-base"
+
+#define E1000(obj) \
+ OBJECT_CHECK(E1000State, (obj), TYPE_E1000_BASE)
+
+#define E1000_DEVICE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(E1000BaseClass, (klass), TYPE_E1000_BASE)
+#define E1000_DEVICE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(E1000BaseClass, (obj), TYPE_E1000_BASE)
+
+#define defreg(x) x = (E1000_##x>>2)
+enum {
+ defreg(CTRL), defreg(EECD), defreg(EERD), defreg(GPRC),
+ defreg(GPTC), defreg(ICR), defreg(ICS), defreg(IMC),
+ defreg(IMS), defreg(LEDCTL), defreg(MANC), defreg(MDIC),
+ defreg(MPC), defreg(PBA), defreg(RCTL), defreg(RDBAH),
+ defreg(RDBAL), defreg(RDH), defreg(RDLEN), defreg(RDT),
+ defreg(STATUS), defreg(SWSM), defreg(TCTL), defreg(TDBAH),
+ defreg(TDBAL), defreg(TDH), defreg(TDLEN), defreg(TDT),
+ defreg(TORH), defreg(TORL), defreg(TOTH), defreg(TOTL),
+ defreg(TPR), defreg(TPT), defreg(TXDCTL), defreg(WUFC),
+ defreg(RA), defreg(MTA), defreg(CRCERRS),defreg(VFTA),
+ defreg(VET), defreg(RDTR), defreg(RADV), defreg(TADV),
+ defreg(ITR),
+};
+
+static void
+e1000_link_down(E1000State *s)
+{
+ s->mac_reg[STATUS] &= ~E1000_STATUS_LU;
+ s->phy_reg[PHY_STATUS] &= ~MII_SR_LINK_STATUS;
+ s->phy_reg[PHY_STATUS] &= ~MII_SR_AUTONEG_COMPLETE;
+ s->phy_reg[PHY_LP_ABILITY] &= ~MII_LPAR_LPACK;
+}
+
+static void
+e1000_link_up(E1000State *s)
+{
+ s->mac_reg[STATUS] |= E1000_STATUS_LU;
+ s->phy_reg[PHY_STATUS] |= MII_SR_LINK_STATUS;
+
+ /* E1000_STATUS_LU is tested by e1000_can_receive() */
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+}
+
+static bool
+have_autoneg(E1000State *s)
+{
+ return (s->compat_flags & E1000_FLAG_AUTONEG) &&
+ (s->phy_reg[PHY_CTRL] & MII_CR_AUTO_NEG_EN);
+}
+
+static void
+set_phy_ctrl(E1000State *s, int index, uint16_t val)
+{
+ /* bits 0-5 reserved; MII_CR_[RESTART_AUTO_NEG,RESET] are self clearing */
+ s->phy_reg[PHY_CTRL] = val & ~(0x3f |
+ MII_CR_RESET |
+ MII_CR_RESTART_AUTO_NEG);
+
+ /*
+ * QEMU 1.3 does not support link auto-negotiation emulation, so if we
+ * migrate during auto negotiation, after migration the link will be
+ * down.
+ */
+ if (have_autoneg(s) && (val & MII_CR_RESTART_AUTO_NEG)) {
+ e1000_link_down(s);
+ DBGOUT(PHY, "Start link auto negotiation\n");
+ timer_mod(s->autoneg_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 500);
+ }
+}
+
+static void (*phyreg_writeops[])(E1000State *, int, uint16_t) = {
+ [PHY_CTRL] = set_phy_ctrl,
+};
+
+enum { NPHYWRITEOPS = ARRAY_SIZE(phyreg_writeops) };
+
+enum { PHY_R = 1, PHY_W = 2, PHY_RW = PHY_R | PHY_W };
+static const char phy_regcap[0x20] = {
+ [PHY_STATUS] = PHY_R, [M88E1000_EXT_PHY_SPEC_CTRL] = PHY_RW,
+ [PHY_ID1] = PHY_R, [M88E1000_PHY_SPEC_CTRL] = PHY_RW,
+ [PHY_CTRL] = PHY_RW, [PHY_1000T_CTRL] = PHY_RW,
+ [PHY_LP_ABILITY] = PHY_R, [PHY_1000T_STATUS] = PHY_R,
+ [PHY_AUTONEG_ADV] = PHY_RW, [M88E1000_RX_ERR_CNTR] = PHY_R,
+ [PHY_ID2] = PHY_R, [M88E1000_PHY_SPEC_STATUS] = PHY_R,
+ [PHY_AUTONEG_EXP] = PHY_R,
+};
+
+/* PHY_ID2 documented in 8254x_GBe_SDM.pdf, pp. 250 */
+static const uint16_t phy_reg_init[] = {
+ [PHY_CTRL] = MII_CR_SPEED_SELECT_MSB |
+ MII_CR_FULL_DUPLEX |
+ MII_CR_AUTO_NEG_EN,
+
+ [PHY_STATUS] = MII_SR_EXTENDED_CAPS |
+ MII_SR_LINK_STATUS | /* link initially up */
+ MII_SR_AUTONEG_CAPS |
+ /* MII_SR_AUTONEG_COMPLETE: initially NOT completed */
+ MII_SR_PREAMBLE_SUPPRESS |
+ MII_SR_EXTENDED_STATUS |
+ MII_SR_10T_HD_CAPS |
+ MII_SR_10T_FD_CAPS |
+ MII_SR_100X_HD_CAPS |
+ MII_SR_100X_FD_CAPS,
+
+ [PHY_ID1] = 0x141,
+ /* [PHY_ID2] configured per DevId, from e1000_reset() */
+ [PHY_AUTONEG_ADV] = 0xde1,
+ [PHY_LP_ABILITY] = 0x1e0,
+ [PHY_1000T_CTRL] = 0x0e00,
+ [PHY_1000T_STATUS] = 0x3c00,
+ [M88E1000_PHY_SPEC_CTRL] = 0x360,
+ [M88E1000_PHY_SPEC_STATUS] = 0xac00,
+ [M88E1000_EXT_PHY_SPEC_CTRL] = 0x0d60,
+};
+
+static const uint32_t mac_reg_init[] = {
+ [PBA] = 0x00100030,
+ [LEDCTL] = 0x602,
+ [CTRL] = E1000_CTRL_SWDPIN2 | E1000_CTRL_SWDPIN0 |
+ E1000_CTRL_SPD_1000 | E1000_CTRL_SLU,
+ [STATUS] = 0x80000000 | E1000_STATUS_GIO_MASTER_ENABLE |
+ E1000_STATUS_ASDV | E1000_STATUS_MTXCKOK |
+ E1000_STATUS_SPEED_1000 | E1000_STATUS_FD |
+ E1000_STATUS_LU,
+ [MANC] = E1000_MANC_EN_MNG2HOST | E1000_MANC_RCV_TCO_EN |
+ E1000_MANC_ARP_EN | E1000_MANC_0298_EN |
+ E1000_MANC_RMCP_EN,
+};
+
+/* Helper function, *curr == 0 means the value is not set */
+static inline void
+mit_update_delay(uint32_t *curr, uint32_t value)
+{
+ if (value && (*curr == 0 || value < *curr)) {
+ *curr = value;
+ }
+}
+
+static void
+set_interrupt_cause(E1000State *s, int index, uint32_t val)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ uint32_t pending_ints;
+ uint32_t mit_delay;
+
+ s->mac_reg[ICR] = val;
+
+ /*
+ * Make sure ICR and ICS registers have the same value.
+ * The spec says that the ICS register is write-only. However in practice,
+ * on real hardware ICS is readable, and for reads it has the same value as
+ * ICR (except that ICS does not have the clear on read behaviour of ICR).
+ *
+ * The VxWorks PRO/1000 driver uses this behaviour.
+ */
+ s->mac_reg[ICS] = val;
+
+ pending_ints = (s->mac_reg[IMS] & s->mac_reg[ICR]);
+ if (!s->mit_irq_level && pending_ints) {
+ /*
+ * Here we detect a potential raising edge. We postpone raising the
+ * interrupt line if we are inside the mitigation delay window
+ * (s->mit_timer_on == 1).
+ * We provide a partial implementation of interrupt mitigation,
+ * emulating only RADV, TADV and ITR (lower 16 bits, 1024ns units for
+ * RADV and TADV, 256ns units for ITR). RDTR is only used to enable
+ * RADV; relative timers based on TIDV and RDTR are not implemented.
+ */
+ if (s->mit_timer_on) {
+ return;
+ }
+ if (s->compat_flags & E1000_FLAG_MIT) {
+ /* Compute the next mitigation delay according to pending
+ * interrupts and the current values of RADV (provided
+ * RDTR!=0), TADV and ITR.
+ * Then rearm the timer.
+ */
+ mit_delay = 0;
+ if (s->mit_ide &&
+ (pending_ints & (E1000_ICR_TXQE | E1000_ICR_TXDW))) {
+ mit_update_delay(&mit_delay, s->mac_reg[TADV] * 4);
+ }
+ if (s->mac_reg[RDTR] && (pending_ints & E1000_ICS_RXT0)) {
+ mit_update_delay(&mit_delay, s->mac_reg[RADV] * 4);
+ }
+ mit_update_delay(&mit_delay, s->mac_reg[ITR]);
+
+ if (mit_delay) {
+ s->mit_timer_on = 1;
+ timer_mod(s->mit_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ mit_delay * 256);
+ }
+ s->mit_ide = 0;
+ }
+ }
+
+ s->mit_irq_level = (pending_ints != 0);
+ pci_set_irq(d, s->mit_irq_level);
+}
+
+static void
+e1000_mit_timer(void *opaque)
+{
+ E1000State *s = opaque;
+
+ s->mit_timer_on = 0;
+ /* Call set_interrupt_cause to update the irq level (if necessary). */
+ set_interrupt_cause(s, 0, s->mac_reg[ICR]);
+}
+
+static void
+set_ics(E1000State *s, int index, uint32_t val)
+{
+ DBGOUT(INTERRUPT, "set_ics %x, ICR %x, IMR %x\n", val, s->mac_reg[ICR],
+ s->mac_reg[IMS]);
+ set_interrupt_cause(s, 0, val | s->mac_reg[ICR]);
+}
+
+static void
+e1000_autoneg_timer(void *opaque)
+{
+ E1000State *s = opaque;
+ if (!qemu_get_queue(s->nic)->link_down) {
+ e1000_link_up(s);
+ s->phy_reg[PHY_LP_ABILITY] |= MII_LPAR_LPACK;
+ s->phy_reg[PHY_STATUS] |= MII_SR_AUTONEG_COMPLETE;
+ DBGOUT(PHY, "Auto negotiation is completed\n");
+ set_ics(s, 0, E1000_ICS_LSC); /* signal link status change to guest */
+ }
+}
+
+static int
+rxbufsize(uint32_t v)
+{
+ v &= E1000_RCTL_BSEX | E1000_RCTL_SZ_16384 | E1000_RCTL_SZ_8192 |
+ E1000_RCTL_SZ_4096 | E1000_RCTL_SZ_2048 | E1000_RCTL_SZ_1024 |
+ E1000_RCTL_SZ_512 | E1000_RCTL_SZ_256;
+ switch (v) {
+ case E1000_RCTL_BSEX | E1000_RCTL_SZ_16384:
+ return 16384;
+ case E1000_RCTL_BSEX | E1000_RCTL_SZ_8192:
+ return 8192;
+ case E1000_RCTL_BSEX | E1000_RCTL_SZ_4096:
+ return 4096;
+ case E1000_RCTL_SZ_1024:
+ return 1024;
+ case E1000_RCTL_SZ_512:
+ return 512;
+ case E1000_RCTL_SZ_256:
+ return 256;
+ }
+ return 2048;
+}
+
+static void e1000_reset(void *opaque)
+{
+ E1000State *d = opaque;
+ E1000BaseClass *edc = E1000_DEVICE_GET_CLASS(d);
+ uint8_t *macaddr = d->conf.macaddr.a;
+ int i;
+
+ timer_del(d->autoneg_timer);
+ timer_del(d->mit_timer);
+ d->mit_timer_on = 0;
+ d->mit_irq_level = 0;
+ d->mit_ide = 0;
+ memset(d->phy_reg, 0, sizeof d->phy_reg);
+ memmove(d->phy_reg, phy_reg_init, sizeof phy_reg_init);
+ d->phy_reg[PHY_ID2] = edc->phy_id2;
+ memset(d->mac_reg, 0, sizeof d->mac_reg);
+ memmove(d->mac_reg, mac_reg_init, sizeof mac_reg_init);
+ d->rxbuf_min_shift = 1;
+ memset(&d->tx, 0, sizeof d->tx);
+
+ if (qemu_get_queue(d->nic)->link_down) {
+ e1000_link_down(d);
+ }
+
+ /* Some guests expect pre-initialized RAH/RAL (AddrValid flag + MACaddr) */
+ d->mac_reg[RA] = 0;
+ d->mac_reg[RA + 1] = E1000_RAH_AV;
+ for (i = 0; i < 4; i++) {
+ d->mac_reg[RA] |= macaddr[i] << (8 * i);
+ d->mac_reg[RA + 1] |= (i < 2) ? macaddr[i + 4] << (8 * i) : 0;
+ }
+ qemu_format_nic_info_str(qemu_get_queue(d->nic), macaddr);
+}
+
+static void
+set_ctrl(E1000State *s, int index, uint32_t val)
+{
+ /* RST is self clearing */
+ s->mac_reg[CTRL] = val & ~E1000_CTRL_RST;
+}
+
+static void
+set_rx_control(E1000State *s, int index, uint32_t val)
+{
+ s->mac_reg[RCTL] = val;
+ s->rxbuf_size = rxbufsize(val);
+ s->rxbuf_min_shift = ((val / E1000_RCTL_RDMTS_QUAT) & 3) + 1;
+ DBGOUT(RX, "RCTL: %d, mac_reg[RCTL] = 0x%x\n", s->mac_reg[RDT],
+ s->mac_reg[RCTL]);
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+}
+
+static void
+set_mdic(E1000State *s, int index, uint32_t val)
+{
+ uint32_t data = val & E1000_MDIC_DATA_MASK;
+ uint32_t addr = ((val & E1000_MDIC_REG_MASK) >> E1000_MDIC_REG_SHIFT);
+
+ if ((val & E1000_MDIC_PHY_MASK) >> E1000_MDIC_PHY_SHIFT != 1) // phy #
+ val = s->mac_reg[MDIC] | E1000_MDIC_ERROR;
+ else if (val & E1000_MDIC_OP_READ) {
+ DBGOUT(MDIC, "MDIC read reg 0x%x\n", addr);
+ if (!(phy_regcap[addr] & PHY_R)) {
+ DBGOUT(MDIC, "MDIC read reg %x unhandled\n", addr);
+ val |= E1000_MDIC_ERROR;
+ } else
+ val = (val ^ data) | s->phy_reg[addr];
+ } else if (val & E1000_MDIC_OP_WRITE) {
+ DBGOUT(MDIC, "MDIC write reg 0x%x, value 0x%x\n", addr, data);
+ if (!(phy_regcap[addr] & PHY_W)) {
+ DBGOUT(MDIC, "MDIC write reg %x unhandled\n", addr);
+ val |= E1000_MDIC_ERROR;
+ } else {
+ if (addr < NPHYWRITEOPS && phyreg_writeops[addr]) {
+ phyreg_writeops[addr](s, index, data);
+ } else {
+ s->phy_reg[addr] = data;
+ }
+ }
+ }
+ s->mac_reg[MDIC] = val | E1000_MDIC_READY;
+
+ if (val & E1000_MDIC_INT_EN) {
+ set_ics(s, 0, E1000_ICR_MDAC);
+ }
+}
+
+static uint32_t
+get_eecd(E1000State *s, int index)
+{
+ uint32_t ret = E1000_EECD_PRES|E1000_EECD_GNT | s->eecd_state.old_eecd;
+
+ DBGOUT(EEPROM, "reading eeprom bit %d (reading %d)\n",
+ s->eecd_state.bitnum_out, s->eecd_state.reading);
+ if (!s->eecd_state.reading ||
+ ((s->eeprom_data[(s->eecd_state.bitnum_out >> 4) & 0x3f] >>
+ ((s->eecd_state.bitnum_out & 0xf) ^ 0xf))) & 1)
+ ret |= E1000_EECD_DO;
+ return ret;
+}
+
+static void
+set_eecd(E1000State *s, int index, uint32_t val)
+{
+ uint32_t oldval = s->eecd_state.old_eecd;
+
+ s->eecd_state.old_eecd = val & (E1000_EECD_SK | E1000_EECD_CS |
+ E1000_EECD_DI|E1000_EECD_FWE_MASK|E1000_EECD_REQ);
+ if (!(E1000_EECD_CS & val)) // CS inactive; nothing to do
+ return;
+ if (E1000_EECD_CS & (val ^ oldval)) { // CS rise edge; reset state
+ s->eecd_state.val_in = 0;
+ s->eecd_state.bitnum_in = 0;
+ s->eecd_state.bitnum_out = 0;
+ s->eecd_state.reading = 0;
+ }
+ if (!(E1000_EECD_SK & (val ^ oldval))) // no clock edge
+ return;
+ if (!(E1000_EECD_SK & val)) { // falling edge
+ s->eecd_state.bitnum_out++;
+ return;
+ }
+ s->eecd_state.val_in <<= 1;
+ if (val & E1000_EECD_DI)
+ s->eecd_state.val_in |= 1;
+ if (++s->eecd_state.bitnum_in == 9 && !s->eecd_state.reading) {
+ s->eecd_state.bitnum_out = ((s->eecd_state.val_in & 0x3f)<<4)-1;
+ s->eecd_state.reading = (((s->eecd_state.val_in >> 6) & 7) ==
+ EEPROM_READ_OPCODE_MICROWIRE);
+ }
+ DBGOUT(EEPROM, "eeprom bitnum in %d out %d, reading %d\n",
+ s->eecd_state.bitnum_in, s->eecd_state.bitnum_out,
+ s->eecd_state.reading);
+}
+
+static uint32_t
+flash_eerd_read(E1000State *s, int x)
+{
+ unsigned int index, r = s->mac_reg[EERD] & ~E1000_EEPROM_RW_REG_START;
+
+ if ((s->mac_reg[EERD] & E1000_EEPROM_RW_REG_START) == 0)
+ return (s->mac_reg[EERD]);
+
+ if ((index = r >> E1000_EEPROM_RW_ADDR_SHIFT) > EEPROM_CHECKSUM_REG)
+ return (E1000_EEPROM_RW_REG_DONE | r);
+
+ return ((s->eeprom_data[index] << E1000_EEPROM_RW_REG_DATA) |
+ E1000_EEPROM_RW_REG_DONE | r);
+}
+
+static void
+putsum(uint8_t *data, uint32_t n, uint32_t sloc, uint32_t css, uint32_t cse)
+{
+ uint32_t sum;
+
+ if (cse && cse < n)
+ n = cse + 1;
+ if (sloc < n-1) {
+ sum = net_checksum_add(n-css, data+css);
+ stw_be_p(data + sloc, net_checksum_finish(sum));
+ }
+}
+
+static inline int
+vlan_enabled(E1000State *s)
+{
+ return ((s->mac_reg[CTRL] & E1000_CTRL_VME) != 0);
+}
+
+static inline int
+vlan_rx_filter_enabled(E1000State *s)
+{
+ return ((s->mac_reg[RCTL] & E1000_RCTL_VFE) != 0);
+}
+
+static inline int
+is_vlan_packet(E1000State *s, const uint8_t *buf)
+{
+ return (be16_to_cpup((uint16_t *)(buf + 12)) ==
+ le16_to_cpu(s->mac_reg[VET]));
+}
+
+static inline int
+is_vlan_txd(uint32_t txd_lower)
+{
+ return ((txd_lower & E1000_TXD_CMD_VLE) != 0);
+}
+
+/* FCS aka Ethernet CRC-32. We don't get it from backends and can't
+ * fill it in, just pad descriptor length by 4 bytes unless guest
+ * told us to strip it off the packet. */
+static inline int
+fcs_len(E1000State *s)
+{
+ return (s->mac_reg[RCTL] & E1000_RCTL_SECRC) ? 0 : 4;
+}
+
+static void
+e1000_send_packet(E1000State *s, const uint8_t *buf, int size)
+{
+ NetClientState *nc = qemu_get_queue(s->nic);
+ if (s->phy_reg[PHY_CTRL] & MII_CR_LOOPBACK) {
+ nc->info->receive(nc, buf, size);
+ } else {
+ qemu_send_packet(nc, buf, size);
+ }
+}
+
+static void
+xmit_seg(E1000State *s)
+{
+ uint16_t len, *sp;
+ unsigned int frames = s->tx.tso_frames, css, sofar, n;
+ struct e1000_tx *tp = &s->tx;
+
+ if (tp->tse && tp->cptse) {
+ css = tp->ipcss;
+ DBGOUT(TXSUM, "frames %d size %d ipcss %d\n",
+ frames, tp->size, css);
+ if (tp->ip) { // IPv4
+ stw_be_p(tp->data+css+2, tp->size - css);
+ stw_be_p(tp->data+css+4,
+ be16_to_cpup((uint16_t *)(tp->data+css+4))+frames);
+ } else // IPv6
+ stw_be_p(tp->data+css+4, tp->size - css);
+ css = tp->tucss;
+ len = tp->size - css;
+ DBGOUT(TXSUM, "tcp %d tucss %d len %d\n", tp->tcp, css, len);
+ if (tp->tcp) {
+ sofar = frames * tp->mss;
+ stl_be_p(tp->data+css+4, ldl_be_p(tp->data+css+4)+sofar); /* seq */
+ if (tp->paylen - sofar > tp->mss)
+ tp->data[css + 13] &= ~9; // PSH, FIN
+ } else // UDP
+ stw_be_p(tp->data+css+4, len);
+ if (tp->sum_needed & E1000_TXD_POPTS_TXSM) {
+ unsigned int phsum;
+ // add pseudo-header length before checksum calculation
+ sp = (uint16_t *)(tp->data + tp->tucso);
+ phsum = be16_to_cpup(sp) + len;
+ phsum = (phsum >> 16) + (phsum & 0xffff);
+ stw_be_p(sp, phsum);
+ }
+ tp->tso_frames++;
+ }
+
+ if (tp->sum_needed & E1000_TXD_POPTS_TXSM)
+ putsum(tp->data, tp->size, tp->tucso, tp->tucss, tp->tucse);
+ if (tp->sum_needed & E1000_TXD_POPTS_IXSM)
+ putsum(tp->data, tp->size, tp->ipcso, tp->ipcss, tp->ipcse);
+ if (tp->vlan_needed) {
+ memmove(tp->vlan, tp->data, 4);
+ memmove(tp->data, tp->data + 4, 8);
+ memcpy(tp->data + 8, tp->vlan_header, 4);
+ e1000_send_packet(s, tp->vlan, tp->size + 4);
+ } else
+ e1000_send_packet(s, tp->data, tp->size);
+ s->mac_reg[TPT]++;
+ s->mac_reg[GPTC]++;
+ n = s->mac_reg[TOTL];
+ if ((s->mac_reg[TOTL] += s->tx.size) < n)
+ s->mac_reg[TOTH]++;
+}
+
+static void
+process_tx_desc(E1000State *s, struct e1000_tx_desc *dp)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ uint32_t txd_lower = le32_to_cpu(dp->lower.data);
+ uint32_t dtype = txd_lower & (E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D);
+ unsigned int split_size = txd_lower & 0xffff, bytes, sz, op;
+ unsigned int msh = 0xfffff;
+ uint64_t addr;
+ struct e1000_context_desc *xp = (struct e1000_context_desc *)dp;
+ struct e1000_tx *tp = &s->tx;
+
+ s->mit_ide |= (txd_lower & E1000_TXD_CMD_IDE);
+ if (dtype == E1000_TXD_CMD_DEXT) { // context descriptor
+ op = le32_to_cpu(xp->cmd_and_length);
+ tp->ipcss = xp->lower_setup.ip_fields.ipcss;
+ tp->ipcso = xp->lower_setup.ip_fields.ipcso;
+ tp->ipcse = le16_to_cpu(xp->lower_setup.ip_fields.ipcse);
+ tp->tucss = xp->upper_setup.tcp_fields.tucss;
+ tp->tucso = xp->upper_setup.tcp_fields.tucso;
+ tp->tucse = le16_to_cpu(xp->upper_setup.tcp_fields.tucse);
+ tp->paylen = op & 0xfffff;
+ tp->hdr_len = xp->tcp_seg_setup.fields.hdr_len;
+ tp->mss = le16_to_cpu(xp->tcp_seg_setup.fields.mss);
+ tp->ip = (op & E1000_TXD_CMD_IP) ? 1 : 0;
+ tp->tcp = (op & E1000_TXD_CMD_TCP) ? 1 : 0;
+ tp->tse = (op & E1000_TXD_CMD_TSE) ? 1 : 0;
+ tp->tso_frames = 0;
+ if (tp->tucso == 0) { // this is probably wrong
+ DBGOUT(TXSUM, "TCP/UDP: cso 0!\n");
+ tp->tucso = tp->tucss + (tp->tcp ? 16 : 6);
+ }
+ return;
+ } else if (dtype == (E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D)) {
+ // data descriptor
+ if (tp->size == 0) {
+ tp->sum_needed = le32_to_cpu(dp->upper.data) >> 8;
+ }
+ tp->cptse = ( txd_lower & E1000_TXD_CMD_TSE ) ? 1 : 0;
+ } else {
+ // legacy descriptor
+ tp->cptse = 0;
+ }
+
+ if (vlan_enabled(s) && is_vlan_txd(txd_lower) &&
+ (tp->cptse || txd_lower & E1000_TXD_CMD_EOP)) {
+ tp->vlan_needed = 1;
+ stw_be_p(tp->vlan_header,
+ le16_to_cpu(s->mac_reg[VET]));
+ stw_be_p(tp->vlan_header + 2,
+ le16_to_cpu(dp->upper.fields.special));
+ }
+
+ addr = le64_to_cpu(dp->buffer_addr);
+ if (tp->tse && tp->cptse) {
+ msh = tp->hdr_len + tp->mss;
+ do {
+ bytes = split_size;
+ if (tp->size + bytes > msh)
+ bytes = msh - tp->size;
+
+ bytes = MIN(sizeof(tp->data) - tp->size, bytes);
+ pci_dma_read(d, addr, tp->data + tp->size, bytes);
+ sz = tp->size + bytes;
+ if (sz >= tp->hdr_len && tp->size < tp->hdr_len) {
+ memmove(tp->header, tp->data, tp->hdr_len);
+ }
+ tp->size = sz;
+ addr += bytes;
+ if (sz == msh) {
+ xmit_seg(s);
+ memmove(tp->data, tp->header, tp->hdr_len);
+ tp->size = tp->hdr_len;
+ }
+ split_size -= bytes;
+ } while (bytes && split_size);
+ } else if (!tp->tse && tp->cptse) {
+ // context descriptor TSE is not set, while data descriptor TSE is set
+ DBGOUT(TXERR, "TCP segmentation error\n");
+ } else {
+ split_size = MIN(sizeof(tp->data) - tp->size, split_size);
+ pci_dma_read(d, addr, tp->data + tp->size, split_size);
+ tp->size += split_size;
+ }
+
+ if (!(txd_lower & E1000_TXD_CMD_EOP))
+ return;
+ if (!(tp->tse && tp->cptse && tp->size < tp->hdr_len)) {
+ xmit_seg(s);
+ }
+ tp->tso_frames = 0;
+ tp->sum_needed = 0;
+ tp->vlan_needed = 0;
+ tp->size = 0;
+ tp->cptse = 0;
+}
+
+static uint32_t
+txdesc_writeback(E1000State *s, dma_addr_t base, struct e1000_tx_desc *dp)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ uint32_t txd_upper, txd_lower = le32_to_cpu(dp->lower.data);
+
+ if (!(txd_lower & (E1000_TXD_CMD_RS|E1000_TXD_CMD_RPS)))
+ return 0;
+ txd_upper = (le32_to_cpu(dp->upper.data) | E1000_TXD_STAT_DD) &
+ ~(E1000_TXD_STAT_EC | E1000_TXD_STAT_LC | E1000_TXD_STAT_TU);
+ dp->upper.data = cpu_to_le32(txd_upper);
+ pci_dma_write(d, base + ((char *)&dp->upper - (char *)dp),
+ &dp->upper, sizeof(dp->upper));
+ return E1000_ICR_TXDW;
+}
+
+static uint64_t tx_desc_base(E1000State *s)
+{
+ uint64_t bah = s->mac_reg[TDBAH];
+ uint64_t bal = s->mac_reg[TDBAL] & ~0xf;
+
+ return (bah << 32) + bal;
+}
+
+static void
+start_xmit(E1000State *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ dma_addr_t base;
+ struct e1000_tx_desc desc;
+ uint32_t tdh_start = s->mac_reg[TDH], cause = E1000_ICS_TXQE;
+
+ if (!(s->mac_reg[TCTL] & E1000_TCTL_EN)) {
+ DBGOUT(TX, "tx disabled\n");
+ return;
+ }
+
+ while (s->mac_reg[TDH] != s->mac_reg[TDT]) {
+ base = tx_desc_base(s) +
+ sizeof(struct e1000_tx_desc) * s->mac_reg[TDH];
+ pci_dma_read(d, base, &desc, sizeof(desc));
+
+ DBGOUT(TX, "index %d: %p : %x %x\n", s->mac_reg[TDH],
+ (void *)(intptr_t)desc.buffer_addr, desc.lower.data,
+ desc.upper.data);
+
+ process_tx_desc(s, &desc);
+ cause |= txdesc_writeback(s, base, &desc);
+
+ if (++s->mac_reg[TDH] * sizeof(desc) >= s->mac_reg[TDLEN])
+ s->mac_reg[TDH] = 0;
+ /*
+ * the following could happen only if guest sw assigns
+ * bogus values to TDT/TDLEN.
+ * there's nothing too intelligent we could do about this.
+ */
+ if (s->mac_reg[TDH] == tdh_start) {
+ DBGOUT(TXERR, "TDH wraparound @%x, TDT %x, TDLEN %x\n",
+ tdh_start, s->mac_reg[TDT], s->mac_reg[TDLEN]);
+ break;
+ }
+ }
+ set_ics(s, 0, cause);
+}
+
+static int
+receive_filter(E1000State *s, const uint8_t *buf, int size)
+{
+ static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ static const int mta_shift[] = {4, 3, 2, 0};
+ uint32_t f, rctl = s->mac_reg[RCTL], ra[2], *rp;
+
+ if (is_vlan_packet(s, buf) && vlan_rx_filter_enabled(s)) {
+ uint16_t vid = be16_to_cpup((uint16_t *)(buf + 14));
+ uint32_t vfta = le32_to_cpup((uint32_t *)(s->mac_reg + VFTA) +
+ ((vid >> 5) & 0x7f));
+ if ((vfta & (1 << (vid & 0x1f))) == 0)
+ return 0;
+ }
+
+ if (rctl & E1000_RCTL_UPE) // promiscuous
+ return 1;
+
+ if ((buf[0] & 1) && (rctl & E1000_RCTL_MPE)) // promiscuous mcast
+ return 1;
+
+ if ((rctl & E1000_RCTL_BAM) && !memcmp(buf, bcast, sizeof bcast))
+ return 1;
+
+ for (rp = s->mac_reg + RA; rp < s->mac_reg + RA + 32; rp += 2) {
+ if (!(rp[1] & E1000_RAH_AV))
+ continue;
+ ra[0] = cpu_to_le32(rp[0]);
+ ra[1] = cpu_to_le32(rp[1]);
+ if (!memcmp(buf, (uint8_t *)ra, 6)) {
+ DBGOUT(RXFILTER,
+ "unicast match[%d]: %02x:%02x:%02x:%02x:%02x:%02x\n",
+ (int)(rp - s->mac_reg - RA)/2,
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
+ return 1;
+ }
+ }
+ DBGOUT(RXFILTER, "unicast mismatch: %02x:%02x:%02x:%02x:%02x:%02x\n",
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
+
+ f = mta_shift[(rctl >> E1000_RCTL_MO_SHIFT) & 3];
+ f = (((buf[5] << 8) | buf[4]) >> f) & 0xfff;
+ if (s->mac_reg[MTA + (f >> 5)] & (1 << (f & 0x1f)))
+ return 1;
+ DBGOUT(RXFILTER,
+ "dropping, inexact filter mismatch: %02x:%02x:%02x:%02x:%02x:%02x MO %d MTA[%d] %x\n",
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
+ (rctl >> E1000_RCTL_MO_SHIFT) & 3, f >> 5,
+ s->mac_reg[MTA + (f >> 5)]);
+
+ return 0;
+}
+
+static void
+e1000_set_link_status(NetClientState *nc)
+{
+ E1000State *s = qemu_get_nic_opaque(nc);
+ uint32_t old_status = s->mac_reg[STATUS];
+
+ if (nc->link_down) {
+ e1000_link_down(s);
+ } else {
+ if (have_autoneg(s) &&
+ !(s->phy_reg[PHY_STATUS] & MII_SR_AUTONEG_COMPLETE)) {
+ /* emulate auto-negotiation if supported */
+ timer_mod(s->autoneg_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 500);
+ } else {
+ e1000_link_up(s);
+ }
+ }
+
+ if (s->mac_reg[STATUS] != old_status)
+ set_ics(s, 0, E1000_ICR_LSC);
+}
+
+static bool e1000_has_rxbufs(E1000State *s, size_t total_size)
+{
+ int bufs;
+ /* Fast-path short packets */
+ if (total_size <= s->rxbuf_size) {
+ return s->mac_reg[RDH] != s->mac_reg[RDT];
+ }
+ if (s->mac_reg[RDH] < s->mac_reg[RDT]) {
+ bufs = s->mac_reg[RDT] - s->mac_reg[RDH];
+ } else if (s->mac_reg[RDH] > s->mac_reg[RDT]) {
+ bufs = s->mac_reg[RDLEN] / sizeof(struct e1000_rx_desc) +
+ s->mac_reg[RDT] - s->mac_reg[RDH];
+ } else {
+ return false;
+ }
+ return total_size <= bufs * s->rxbuf_size;
+}
+
+static int
+e1000_can_receive(NetClientState *nc)
+{
+ E1000State *s = qemu_get_nic_opaque(nc);
+
+ return (s->mac_reg[STATUS] & E1000_STATUS_LU) &&
+ (s->mac_reg[RCTL] & E1000_RCTL_EN) &&
+ (s->parent_obj.config[PCI_COMMAND] & PCI_COMMAND_MASTER) &&
+ e1000_has_rxbufs(s, 1);
+}
+
+static uint64_t rx_desc_base(E1000State *s)
+{
+ uint64_t bah = s->mac_reg[RDBAH];
+ uint64_t bal = s->mac_reg[RDBAL] & ~0xf;
+
+ return (bah << 32) + bal;
+}
+
+static ssize_t
+e1000_receive_iov(NetClientState *nc, const struct iovec *iov, int iovcnt)
+{
+ E1000State *s = qemu_get_nic_opaque(nc);
+ PCIDevice *d = PCI_DEVICE(s);
+ struct e1000_rx_desc desc;
+ dma_addr_t base;
+ unsigned int n, rdt;
+ uint32_t rdh_start;
+ uint16_t vlan_special = 0;
+ uint8_t vlan_status = 0;
+ uint8_t min_buf[MIN_BUF_SIZE];
+ struct iovec min_iov;
+ uint8_t *filter_buf = iov->iov_base;
+ size_t size = iov_size(iov, iovcnt);
+ size_t iov_ofs = 0;
+ size_t desc_offset;
+ size_t desc_size;
+ size_t total_size;
+
+ if (!(s->mac_reg[STATUS] & E1000_STATUS_LU)) {
+ return -1;
+ }
+
+ if (!(s->mac_reg[RCTL] & E1000_RCTL_EN)) {
+ return -1;
+ }
+
+ /* Pad to minimum Ethernet frame length */
+ if (size < sizeof(min_buf)) {
+ iov_to_buf(iov, iovcnt, 0, min_buf, size);
+ memset(&min_buf[size], 0, sizeof(min_buf) - size);
+ min_iov.iov_base = filter_buf = min_buf;
+ min_iov.iov_len = size = sizeof(min_buf);
+ iovcnt = 1;
+ iov = &min_iov;
+ } else if (iov->iov_len < MAXIMUM_ETHERNET_HDR_LEN) {
+ /* This is very unlikely, but may happen. */
+ iov_to_buf(iov, iovcnt, 0, min_buf, MAXIMUM_ETHERNET_HDR_LEN);
+ filter_buf = min_buf;
+ }
+
+ /* Discard oversized packets if !LPE and !SBP. */
+ if ((size > MAXIMUM_ETHERNET_LPE_SIZE ||
+ (size > MAXIMUM_ETHERNET_VLAN_SIZE
+ && !(s->mac_reg[RCTL] & E1000_RCTL_LPE)))
+ && !(s->mac_reg[RCTL] & E1000_RCTL_SBP)) {
+ return size;
+ }
+
+ if (!receive_filter(s, filter_buf, size)) {
+ return size;
+ }
+
+ if (vlan_enabled(s) && is_vlan_packet(s, filter_buf)) {
+ vlan_special = cpu_to_le16(be16_to_cpup((uint16_t *)(filter_buf
+ + 14)));
+ iov_ofs = 4;
+ if (filter_buf == iov->iov_base) {
+ memmove(filter_buf + 4, filter_buf, 12);
+ } else {
+ iov_from_buf(iov, iovcnt, 4, filter_buf, 12);
+ while (iov->iov_len <= iov_ofs) {
+ iov_ofs -= iov->iov_len;
+ iov++;
+ }
+ }
+ vlan_status = E1000_RXD_STAT_VP;
+ size -= 4;
+ }
+
+ rdh_start = s->mac_reg[RDH];
+ desc_offset = 0;
+ total_size = size + fcs_len(s);
+ if (!e1000_has_rxbufs(s, total_size)) {
+ set_ics(s, 0, E1000_ICS_RXO);
+ return -1;
+ }
+ do {
+ desc_size = total_size - desc_offset;
+ if (desc_size > s->rxbuf_size) {
+ desc_size = s->rxbuf_size;
+ }
+ base = rx_desc_base(s) + sizeof(desc) * s->mac_reg[RDH];
+ pci_dma_read(d, base, &desc, sizeof(desc));
+ desc.special = vlan_special;
+ desc.status |= (vlan_status | E1000_RXD_STAT_DD);
+ if (desc.buffer_addr) {
+ if (desc_offset < size) {
+ size_t iov_copy;
+ hwaddr ba = le64_to_cpu(desc.buffer_addr);
+ size_t copy_size = size - desc_offset;
+ if (copy_size > s->rxbuf_size) {
+ copy_size = s->rxbuf_size;
+ }
+ do {
+ iov_copy = MIN(copy_size, iov->iov_len - iov_ofs);
+ pci_dma_write(d, ba, iov->iov_base + iov_ofs, iov_copy);
+ copy_size -= iov_copy;
+ ba += iov_copy;
+ iov_ofs += iov_copy;
+ if (iov_ofs == iov->iov_len) {
+ iov++;
+ iov_ofs = 0;
+ }
+ } while (copy_size);
+ }
+ desc_offset += desc_size;
+ desc.length = cpu_to_le16(desc_size);
+ if (desc_offset >= total_size) {
+ desc.status |= E1000_RXD_STAT_EOP | E1000_RXD_STAT_IXSM;
+ } else {
+ /* Guest zeroing out status is not a hardware requirement.
+ Clear EOP in case guest didn't do it. */
+ desc.status &= ~E1000_RXD_STAT_EOP;
+ }
+ } else { // as per intel docs; skip descriptors with null buf addr
+ DBGOUT(RX, "Null RX descriptor!!\n");
+ }
+ pci_dma_write(d, base, &desc, sizeof(desc));
+
+ if (++s->mac_reg[RDH] * sizeof(desc) >= s->mac_reg[RDLEN])
+ s->mac_reg[RDH] = 0;
+ /* see comment in start_xmit; same here */
+ if (s->mac_reg[RDH] == rdh_start) {
+ DBGOUT(RXERR, "RDH wraparound @%x, RDT %x, RDLEN %x\n",
+ rdh_start, s->mac_reg[RDT], s->mac_reg[RDLEN]);
+ set_ics(s, 0, E1000_ICS_RXO);
+ return -1;
+ }
+ } while (desc_offset < total_size);
+
+ s->mac_reg[GPRC]++;
+ s->mac_reg[TPR]++;
+ /* TOR - Total Octets Received:
+ * This register includes bytes received in a packet from the <Destination
+ * Address> field through the <CRC> field, inclusively.
+ */
+ n = s->mac_reg[TORL] + size + /* Always include FCS length. */ 4;
+ if (n < s->mac_reg[TORL])
+ s->mac_reg[TORH]++;
+ s->mac_reg[TORL] = n;
+
+ n = E1000_ICS_RXT0;
+ if ((rdt = s->mac_reg[RDT]) < s->mac_reg[RDH])
+ rdt += s->mac_reg[RDLEN] / sizeof(desc);
+ if (((rdt - s->mac_reg[RDH]) * sizeof(desc)) <= s->mac_reg[RDLEN] >>
+ s->rxbuf_min_shift)
+ n |= E1000_ICS_RXDMT0;
+
+ set_ics(s, 0, n);
+
+ return size;
+}
+
+static ssize_t
+e1000_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ const struct iovec iov = {
+ .iov_base = (uint8_t *)buf,
+ .iov_len = size
+ };
+
+ return e1000_receive_iov(nc, &iov, 1);
+}
+
+static uint32_t
+mac_readreg(E1000State *s, int index)
+{
+ return s->mac_reg[index];
+}
+
+static uint32_t
+mac_icr_read(E1000State *s, int index)
+{
+ uint32_t ret = s->mac_reg[ICR];
+
+ DBGOUT(INTERRUPT, "ICR read: %x\n", ret);
+ set_interrupt_cause(s, 0, 0);
+ return ret;
+}
+
+static uint32_t
+mac_read_clr4(E1000State *s, int index)
+{
+ uint32_t ret = s->mac_reg[index];
+
+ s->mac_reg[index] = 0;
+ return ret;
+}
+
+static uint32_t
+mac_read_clr8(E1000State *s, int index)
+{
+ uint32_t ret = s->mac_reg[index];
+
+ s->mac_reg[index] = 0;
+ s->mac_reg[index-1] = 0;
+ return ret;
+}
+
+static void
+mac_writereg(E1000State *s, int index, uint32_t val)
+{
+ uint32_t macaddr[2];
+
+ s->mac_reg[index] = val;
+
+ if (index == RA + 1) {
+ macaddr[0] = cpu_to_le32(s->mac_reg[RA]);
+ macaddr[1] = cpu_to_le32(s->mac_reg[RA + 1]);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), (uint8_t *)macaddr);
+ }
+}
+
+static void
+set_rdt(E1000State *s, int index, uint32_t val)
+{
+ s->mac_reg[index] = val & 0xffff;
+ if (e1000_has_rxbufs(s, 1)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+}
+
+static void
+set_16bit(E1000State *s, int index, uint32_t val)
+{
+ s->mac_reg[index] = val & 0xffff;
+}
+
+static void
+set_dlen(E1000State *s, int index, uint32_t val)
+{
+ s->mac_reg[index] = val & 0xfff80;
+}
+
+static void
+set_tctl(E1000State *s, int index, uint32_t val)
+{
+ s->mac_reg[index] = val;
+ s->mac_reg[TDT] &= 0xffff;
+ start_xmit(s);
+}
+
+static void
+set_icr(E1000State *s, int index, uint32_t val)
+{
+ DBGOUT(INTERRUPT, "set_icr %x\n", val);
+ set_interrupt_cause(s, 0, s->mac_reg[ICR] & ~val);
+}
+
+static void
+set_imc(E1000State *s, int index, uint32_t val)
+{
+ s->mac_reg[IMS] &= ~val;
+ set_ics(s, 0, 0);
+}
+
+static void
+set_ims(E1000State *s, int index, uint32_t val)
+{
+ s->mac_reg[IMS] |= val;
+ set_ics(s, 0, 0);
+}
+
+#define getreg(x) [x] = mac_readreg
+static uint32_t (*macreg_readops[])(E1000State *, int) = {
+ getreg(PBA), getreg(RCTL), getreg(TDH), getreg(TXDCTL),
+ getreg(WUFC), getreg(TDT), getreg(CTRL), getreg(LEDCTL),
+ getreg(MANC), getreg(MDIC), getreg(SWSM), getreg(STATUS),
+ getreg(TORL), getreg(TOTL), getreg(IMS), getreg(TCTL),
+ getreg(RDH), getreg(RDT), getreg(VET), getreg(ICS),
+ getreg(TDBAL), getreg(TDBAH), getreg(RDBAH), getreg(RDBAL),
+ getreg(TDLEN), getreg(RDLEN), getreg(RDTR), getreg(RADV),
+ getreg(TADV), getreg(ITR),
+
+ [TOTH] = mac_read_clr8, [TORH] = mac_read_clr8, [GPRC] = mac_read_clr4,
+ [GPTC] = mac_read_clr4, [TPR] = mac_read_clr4, [TPT] = mac_read_clr4,
+ [ICR] = mac_icr_read, [EECD] = get_eecd, [EERD] = flash_eerd_read,
+ [CRCERRS ... MPC] = &mac_readreg,
+ [RA ... RA+31] = &mac_readreg,
+ [MTA ... MTA+127] = &mac_readreg,
+ [VFTA ... VFTA+127] = &mac_readreg,
+};
+enum { NREADOPS = ARRAY_SIZE(macreg_readops) };
+
+#define putreg(x) [x] = mac_writereg
+static void (*macreg_writeops[])(E1000State *, int, uint32_t) = {
+ putreg(PBA), putreg(EERD), putreg(SWSM), putreg(WUFC),
+ putreg(TDBAL), putreg(TDBAH), putreg(TXDCTL), putreg(RDBAH),
+ putreg(RDBAL), putreg(LEDCTL), putreg(VET),
+ [TDLEN] = set_dlen, [RDLEN] = set_dlen, [TCTL] = set_tctl,
+ [TDT] = set_tctl, [MDIC] = set_mdic, [ICS] = set_ics,
+ [TDH] = set_16bit, [RDH] = set_16bit, [RDT] = set_rdt,
+ [IMC] = set_imc, [IMS] = set_ims, [ICR] = set_icr,
+ [EECD] = set_eecd, [RCTL] = set_rx_control, [CTRL] = set_ctrl,
+ [RDTR] = set_16bit, [RADV] = set_16bit, [TADV] = set_16bit,
+ [ITR] = set_16bit,
+ [RA ... RA+31] = &mac_writereg,
+ [MTA ... MTA+127] = &mac_writereg,
+ [VFTA ... VFTA+127] = &mac_writereg,
+};
+
+enum { NWRITEOPS = ARRAY_SIZE(macreg_writeops) };
+
+static void
+e1000_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ E1000State *s = opaque;
+ unsigned int index = (addr & 0x1ffff) >> 2;
+
+ if (index < NWRITEOPS && macreg_writeops[index]) {
+ macreg_writeops[index](s, index, val);
+ } else if (index < NREADOPS && macreg_readops[index]) {
+ DBGOUT(MMIO, "e1000_mmio_writel RO %x: 0x%04"PRIx64"\n", index<<2, val);
+ } else {
+ DBGOUT(UNKNOWN, "MMIO unknown write addr=0x%08x,val=0x%08"PRIx64"\n",
+ index<<2, val);
+ }
+}
+
+static uint64_t
+e1000_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ E1000State *s = opaque;
+ unsigned int index = (addr & 0x1ffff) >> 2;
+
+ if (index < NREADOPS && macreg_readops[index])
+ {
+ return macreg_readops[index](s, index);
+ }
+ DBGOUT(UNKNOWN, "MMIO unknown read addr=0x%08x\n", index<<2);
+ return 0;
+}
+
+static const MemoryRegionOps e1000_mmio_ops = {
+ .read = e1000_mmio_read,
+ .write = e1000_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t e1000_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ E1000State *s = opaque;
+
+ (void)s;
+ return 0;
+}
+
+static void e1000_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ E1000State *s = opaque;
+
+ (void)s;
+}
+
+static const MemoryRegionOps e1000_io_ops = {
+ .read = e1000_io_read,
+ .write = e1000_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static bool is_version_1(void *opaque, int version_id)
+{
+ return version_id == 1;
+}
+
+static void e1000_pre_save(void *opaque)
+{
+ E1000State *s = opaque;
+ NetClientState *nc = qemu_get_queue(s->nic);
+
+ /* If the mitigation timer is active, emulate a timeout now. */
+ if (s->mit_timer_on) {
+ e1000_mit_timer(s);
+ }
+
+ /*
+ * If link is down and auto-negotiation is supported and ongoing,
+ * complete auto-negotiation immediately. This allows us to look
+ * at MII_SR_AUTONEG_COMPLETE to infer link status on load.
+ */
+ if (nc->link_down && have_autoneg(s)) {
+ s->phy_reg[PHY_STATUS] |= MII_SR_AUTONEG_COMPLETE;
+ }
+}
+
+static int e1000_post_load(void *opaque, int version_id)
+{
+ E1000State *s = opaque;
+ NetClientState *nc = qemu_get_queue(s->nic);
+
+ if (!(s->compat_flags & E1000_FLAG_MIT)) {
+ s->mac_reg[ITR] = s->mac_reg[RDTR] = s->mac_reg[RADV] =
+ s->mac_reg[TADV] = 0;
+ s->mit_irq_level = false;
+ }
+ s->mit_ide = 0;
+ s->mit_timer_on = false;
+
+ /* nc.link_down can't be migrated, so infer link_down according
+ * to link status bit in mac_reg[STATUS].
+ * Alternatively, restart link negotiation if it was in progress. */
+ nc->link_down = (s->mac_reg[STATUS] & E1000_STATUS_LU) == 0;
+
+ if (have_autoneg(s) &&
+ !(s->phy_reg[PHY_STATUS] & MII_SR_AUTONEG_COMPLETE)) {
+ nc->link_down = false;
+ timer_mod(s->autoneg_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 500);
+ }
+
+ return 0;
+}
+
+static bool e1000_mit_state_needed(void *opaque)
+{
+ E1000State *s = opaque;
+
+ return s->compat_flags & E1000_FLAG_MIT;
+}
+
+static const VMStateDescription vmstate_e1000_mit_state = {
+ .name = "e1000/mit_state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = e1000_mit_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(mac_reg[RDTR], E1000State),
+ VMSTATE_UINT32(mac_reg[RADV], E1000State),
+ VMSTATE_UINT32(mac_reg[TADV], E1000State),
+ VMSTATE_UINT32(mac_reg[ITR], E1000State),
+ VMSTATE_BOOL(mit_irq_level, E1000State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_e1000 = {
+ .name = "e1000",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .pre_save = e1000_pre_save,
+ .post_load = e1000_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, E1000State),
+ VMSTATE_UNUSED_TEST(is_version_1, 4), /* was instance id */
+ VMSTATE_UNUSED(4), /* Was mmio_base. */
+ VMSTATE_UINT32(rxbuf_size, E1000State),
+ VMSTATE_UINT32(rxbuf_min_shift, E1000State),
+ VMSTATE_UINT32(eecd_state.val_in, E1000State),
+ VMSTATE_UINT16(eecd_state.bitnum_in, E1000State),
+ VMSTATE_UINT16(eecd_state.bitnum_out, E1000State),
+ VMSTATE_UINT16(eecd_state.reading, E1000State),
+ VMSTATE_UINT32(eecd_state.old_eecd, E1000State),
+ VMSTATE_UINT8(tx.ipcss, E1000State),
+ VMSTATE_UINT8(tx.ipcso, E1000State),
+ VMSTATE_UINT16(tx.ipcse, E1000State),
+ VMSTATE_UINT8(tx.tucss, E1000State),
+ VMSTATE_UINT8(tx.tucso, E1000State),
+ VMSTATE_UINT16(tx.tucse, E1000State),
+ VMSTATE_UINT32(tx.paylen, E1000State),
+ VMSTATE_UINT8(tx.hdr_len, E1000State),
+ VMSTATE_UINT16(tx.mss, E1000State),
+ VMSTATE_UINT16(tx.size, E1000State),
+ VMSTATE_UINT16(tx.tso_frames, E1000State),
+ VMSTATE_UINT8(tx.sum_needed, E1000State),
+ VMSTATE_INT8(tx.ip, E1000State),
+ VMSTATE_INT8(tx.tcp, E1000State),
+ VMSTATE_BUFFER(tx.header, E1000State),
+ VMSTATE_BUFFER(tx.data, E1000State),
+ VMSTATE_UINT16_ARRAY(eeprom_data, E1000State, 64),
+ VMSTATE_UINT16_ARRAY(phy_reg, E1000State, 0x20),
+ VMSTATE_UINT32(mac_reg[CTRL], E1000State),
+ VMSTATE_UINT32(mac_reg[EECD], E1000State),
+ VMSTATE_UINT32(mac_reg[EERD], E1000State),
+ VMSTATE_UINT32(mac_reg[GPRC], E1000State),
+ VMSTATE_UINT32(mac_reg[GPTC], E1000State),
+ VMSTATE_UINT32(mac_reg[ICR], E1000State),
+ VMSTATE_UINT32(mac_reg[ICS], E1000State),
+ VMSTATE_UINT32(mac_reg[IMC], E1000State),
+ VMSTATE_UINT32(mac_reg[IMS], E1000State),
+ VMSTATE_UINT32(mac_reg[LEDCTL], E1000State),
+ VMSTATE_UINT32(mac_reg[MANC], E1000State),
+ VMSTATE_UINT32(mac_reg[MDIC], E1000State),
+ VMSTATE_UINT32(mac_reg[MPC], E1000State),
+ VMSTATE_UINT32(mac_reg[PBA], E1000State),
+ VMSTATE_UINT32(mac_reg[RCTL], E1000State),
+ VMSTATE_UINT32(mac_reg[RDBAH], E1000State),
+ VMSTATE_UINT32(mac_reg[RDBAL], E1000State),
+ VMSTATE_UINT32(mac_reg[RDH], E1000State),
+ VMSTATE_UINT32(mac_reg[RDLEN], E1000State),
+ VMSTATE_UINT32(mac_reg[RDT], E1000State),
+ VMSTATE_UINT32(mac_reg[STATUS], E1000State),
+ VMSTATE_UINT32(mac_reg[SWSM], E1000State),
+ VMSTATE_UINT32(mac_reg[TCTL], E1000State),
+ VMSTATE_UINT32(mac_reg[TDBAH], E1000State),
+ VMSTATE_UINT32(mac_reg[TDBAL], E1000State),
+ VMSTATE_UINT32(mac_reg[TDH], E1000State),
+ VMSTATE_UINT32(mac_reg[TDLEN], E1000State),
+ VMSTATE_UINT32(mac_reg[TDT], E1000State),
+ VMSTATE_UINT32(mac_reg[TORH], E1000State),
+ VMSTATE_UINT32(mac_reg[TORL], E1000State),
+ VMSTATE_UINT32(mac_reg[TOTH], E1000State),
+ VMSTATE_UINT32(mac_reg[TOTL], E1000State),
+ VMSTATE_UINT32(mac_reg[TPR], E1000State),
+ VMSTATE_UINT32(mac_reg[TPT], E1000State),
+ VMSTATE_UINT32(mac_reg[TXDCTL], E1000State),
+ VMSTATE_UINT32(mac_reg[WUFC], E1000State),
+ VMSTATE_UINT32(mac_reg[VET], E1000State),
+ VMSTATE_UINT32_SUB_ARRAY(mac_reg, E1000State, RA, 32),
+ VMSTATE_UINT32_SUB_ARRAY(mac_reg, E1000State, MTA, 128),
+ VMSTATE_UINT32_SUB_ARRAY(mac_reg, E1000State, VFTA, 128),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_e1000_mit_state,
+ NULL
+ }
+};
+
+/*
+ * EEPROM contents documented in Tables 5-2 and 5-3, pp. 98-102.
+ * Note: A valid DevId will be inserted during pci_e1000_init().
+ */
+static const uint16_t e1000_eeprom_template[64] = {
+ 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000,
+ 0x3000, 0x1000, 0x6403, 0 /*DevId*/, 0x8086, 0 /*DevId*/, 0x8086, 0x3040,
+ 0x0008, 0x2000, 0x7e14, 0x0048, 0x1000, 0x00d8, 0x0000, 0x2700,
+ 0x6cc9, 0x3150, 0x0722, 0x040b, 0x0984, 0x0000, 0xc000, 0x0706,
+ 0x1008, 0x0000, 0x0f04, 0x7fff, 0x4d01, 0xffff, 0xffff, 0xffff,
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ 0x0100, 0x4000, 0x121c, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000,
+};
+
+/* PCI interface */
+
+static void
+e1000_mmio_setup(E1000State *d)
+{
+ int i;
+ const uint32_t excluded_regs[] = {
+ E1000_MDIC, E1000_ICR, E1000_ICS, E1000_IMS,
+ E1000_IMC, E1000_TCTL, E1000_TDT, PNPMMIO_SIZE
+ };
+
+ memory_region_init_io(&d->mmio, OBJECT(d), &e1000_mmio_ops, d,
+ "e1000-mmio", PNPMMIO_SIZE);
+ memory_region_add_coalescing(&d->mmio, 0, excluded_regs[0]);
+ for (i = 0; excluded_regs[i] != PNPMMIO_SIZE; i++)
+ memory_region_add_coalescing(&d->mmio, excluded_regs[i] + 4,
+ excluded_regs[i+1] - excluded_regs[i] - 4);
+ memory_region_init_io(&d->io, OBJECT(d), &e1000_io_ops, d, "e1000-io", IOPORT_SIZE);
+}
+
+static void
+pci_e1000_uninit(PCIDevice *dev)
+{
+ E1000State *d = E1000(dev);
+
+ timer_del(d->autoneg_timer);
+ timer_free(d->autoneg_timer);
+ timer_del(d->mit_timer);
+ timer_free(d->mit_timer);
+ qemu_del_nic(d->nic);
+}
+
+static NetClientInfo net_e1000_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = e1000_can_receive,
+ .receive = e1000_receive,
+ .receive_iov = e1000_receive_iov,
+ .link_status_changed = e1000_set_link_status,
+};
+
+static void e1000_write_config(PCIDevice *pci_dev, uint32_t address,
+ uint32_t val, int len)
+{
+ E1000State *s = E1000(pci_dev);
+
+ pci_default_write_config(pci_dev, address, val, len);
+
+ if (range_covers_byte(address, len, PCI_COMMAND) &&
+ (pci_dev->config[PCI_COMMAND] & PCI_COMMAND_MASTER)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+}
+
+
+static void pci_e1000_realize(PCIDevice *pci_dev, Error **errp)
+{
+ DeviceState *dev = DEVICE(pci_dev);
+ E1000State *d = E1000(pci_dev);
+ PCIDeviceClass *pdc = PCI_DEVICE_GET_CLASS(pci_dev);
+ uint8_t *pci_conf;
+ uint16_t checksum = 0;
+ int i;
+ uint8_t *macaddr;
+
+ pci_dev->config_write = e1000_write_config;
+
+ pci_conf = pci_dev->config;
+
+ /* TODO: RST# value should be 0, PCI spec 6.2.4 */
+ pci_conf[PCI_CACHE_LINE_SIZE] = 0x10;
+
+ pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
+
+ e1000_mmio_setup(d);
+
+ pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+
+ pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->io);
+
+ memmove(d->eeprom_data, e1000_eeprom_template,
+ sizeof e1000_eeprom_template);
+ qemu_macaddr_default_if_unset(&d->conf.macaddr);
+ macaddr = d->conf.macaddr.a;
+ for (i = 0; i < 3; i++)
+ d->eeprom_data[i] = (macaddr[2*i+1]<<8) | macaddr[2*i];
+ d->eeprom_data[11] = d->eeprom_data[13] = pdc->device_id;
+ for (i = 0; i < EEPROM_CHECKSUM_REG; i++)
+ checksum += d->eeprom_data[i];
+ checksum = (uint16_t) EEPROM_SUM - checksum;
+ d->eeprom_data[EEPROM_CHECKSUM_REG] = checksum;
+
+ d->nic = qemu_new_nic(&net_e1000_info, &d->conf,
+ object_get_typename(OBJECT(d)), dev->id, d);
+
+ qemu_format_nic_info_str(qemu_get_queue(d->nic), macaddr);
+
+ d->autoneg_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, e1000_autoneg_timer, d);
+ d->mit_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, e1000_mit_timer, d);
+}
+
+static void qdev_e1000_reset(DeviceState *dev)
+{
+ E1000State *d = E1000(dev);
+ e1000_reset(d);
+}
+
+static Property e1000_properties[] = {
+ DEFINE_NIC_PROPERTIES(E1000State, conf),
+ DEFINE_PROP_BIT("autonegotiation", E1000State,
+ compat_flags, E1000_FLAG_AUTONEG_BIT, true),
+ DEFINE_PROP_BIT("mitigation", E1000State,
+ compat_flags, E1000_FLAG_MIT_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+typedef struct E1000Info {
+ const char *name;
+ uint16_t device_id;
+ uint8_t revision;
+ uint16_t phy_id2;
+} E1000Info;
+
+static void e1000_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ E1000BaseClass *e = E1000_DEVICE_CLASS(klass);
+ const E1000Info *info = data;
+
+ k->realize = pci_e1000_realize;
+ k->exit = pci_e1000_uninit;
+ k->romfile = "efi-e1000.rom";
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = info->device_id;
+ k->revision = info->revision;
+ e->phy_id2 = info->phy_id2;
+ k->class_id = PCI_CLASS_NETWORK_ETHERNET;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->desc = "Intel Gigabit Ethernet";
+ dc->reset = qdev_e1000_reset;
+ dc->vmsd = &vmstate_e1000;
+ dc->props = e1000_properties;
+}
+
+static void e1000_instance_init(Object *obj)
+{
+ E1000State *n = E1000(obj);
+ device_add_bootindex_property(obj, &n->conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ DEVICE(n), NULL);
+}
+
+static const TypeInfo e1000_base_info = {
+ .name = TYPE_E1000_BASE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(E1000State),
+ .instance_init = e1000_instance_init,
+ .class_size = sizeof(E1000BaseClass),
+ .abstract = true,
+};
+
+static const E1000Info e1000_devices[] = {
+ {
+ .name = "e1000-82540em",
+ .device_id = E1000_DEV_ID_82540EM,
+ .revision = 0x03,
+ .phy_id2 = E1000_PHY_ID2_8254xx_DEFAULT,
+ },
+ {
+ .name = "e1000-82544gc",
+ .device_id = E1000_DEV_ID_82544GC_COPPER,
+ .revision = 0x03,
+ .phy_id2 = E1000_PHY_ID2_82544x,
+ },
+ {
+ .name = "e1000-82545em",
+ .device_id = E1000_DEV_ID_82545EM_COPPER,
+ .revision = 0x03,
+ .phy_id2 = E1000_PHY_ID2_8254xx_DEFAULT,
+ },
+};
+
+static const TypeInfo e1000_default_info = {
+ .name = "e1000",
+ .parent = "e1000-82540em",
+};
+
+static void e1000_register_types(void)
+{
+ int i;
+
+ type_register_static(&e1000_base_info);
+ for (i = 0; i < ARRAY_SIZE(e1000_devices); i++) {
+ const E1000Info *info = &e1000_devices[i];
+ TypeInfo type_info = {};
+
+ type_info.name = info->name;
+ type_info.parent = TYPE_E1000_BASE;
+ type_info.class_data = (void *)info;
+ type_info.class_init = e1000_class_init;
+ type_info.instance_init = e1000_instance_init;
+
+ type_register(&type_info);
+ }
+ type_register_static(&e1000_default_info);
+}
+
+type_init(e1000_register_types)
diff --git a/hw/net/e1000_regs.h b/hw/net/e1000_regs.h
new file mode 100644
index 00000000..60b96aaf
--- /dev/null
+++ b/hw/net/e1000_regs.h
@@ -0,0 +1,902 @@
+/*******************************************************************************
+
+ Intel PRO/1000 Linux driver
+ Copyright(c) 1999 - 2006 Intel Corporation.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms and conditions of the GNU General Public License,
+ version 2, as published by the Free Software Foundation.
+
+ This program is distributed in the hope it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program; if not, see <http://www.gnu.org/licenses/>.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+
+ Contact Information:
+ Linux NICS <linux.nics@intel.com>
+ e1000-devel Mailing List <e1000-devel@lists.sourceforge.net>
+ Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+
+*******************************************************************************/
+
+/* e1000_hw.h
+ * Structures, enums, and macros for the MAC
+ */
+
+#ifndef _E1000_HW_H_
+#define _E1000_HW_H_
+
+
+/* PCI Device IDs */
+#define E1000_DEV_ID_82542 0x1000
+#define E1000_DEV_ID_82543GC_FIBER 0x1001
+#define E1000_DEV_ID_82543GC_COPPER 0x1004
+#define E1000_DEV_ID_82544EI_COPPER 0x1008
+#define E1000_DEV_ID_82544EI_FIBER 0x1009
+#define E1000_DEV_ID_82544GC_COPPER 0x100C
+#define E1000_DEV_ID_82544GC_LOM 0x100D
+#define E1000_DEV_ID_82540EM 0x100E
+#define E1000_DEV_ID_82540EM_LOM 0x1015
+#define E1000_DEV_ID_82540EP_LOM 0x1016
+#define E1000_DEV_ID_82540EP 0x1017
+#define E1000_DEV_ID_82540EP_LP 0x101E
+#define E1000_DEV_ID_82545EM_COPPER 0x100F
+#define E1000_DEV_ID_82545EM_FIBER 0x1011
+#define E1000_DEV_ID_82545GM_COPPER 0x1026
+#define E1000_DEV_ID_82545GM_FIBER 0x1027
+#define E1000_DEV_ID_82545GM_SERDES 0x1028
+#define E1000_DEV_ID_82546EB_COPPER 0x1010
+#define E1000_DEV_ID_82546EB_FIBER 0x1012
+#define E1000_DEV_ID_82546EB_QUAD_COPPER 0x101D
+#define E1000_DEV_ID_82541EI 0x1013
+#define E1000_DEV_ID_82541EI_MOBILE 0x1018
+#define E1000_DEV_ID_82541ER_LOM 0x1014
+#define E1000_DEV_ID_82541ER 0x1078
+#define E1000_DEV_ID_82547GI 0x1075
+#define E1000_DEV_ID_82541GI 0x1076
+#define E1000_DEV_ID_82541GI_MOBILE 0x1077
+#define E1000_DEV_ID_82541GI_LF 0x107C
+#define E1000_DEV_ID_82546GB_COPPER 0x1079
+#define E1000_DEV_ID_82546GB_FIBER 0x107A
+#define E1000_DEV_ID_82546GB_SERDES 0x107B
+#define E1000_DEV_ID_82546GB_PCIE 0x108A
+#define E1000_DEV_ID_82546GB_QUAD_COPPER 0x1099
+#define E1000_DEV_ID_82547EI 0x1019
+#define E1000_DEV_ID_82547EI_MOBILE 0x101A
+#define E1000_DEV_ID_82571EB_COPPER 0x105E
+#define E1000_DEV_ID_82571EB_FIBER 0x105F
+#define E1000_DEV_ID_82571EB_SERDES 0x1060
+#define E1000_DEV_ID_82571EB_QUAD_COPPER 0x10A4
+#define E1000_DEV_ID_82571PT_QUAD_COPPER 0x10D5
+#define E1000_DEV_ID_82571EB_QUAD_FIBER 0x10A5
+#define E1000_DEV_ID_82571EB_QUAD_COPPER_LOWPROFILE 0x10BC
+#define E1000_DEV_ID_82571EB_SERDES_DUAL 0x10D9
+#define E1000_DEV_ID_82571EB_SERDES_QUAD 0x10DA
+#define E1000_DEV_ID_82572EI_COPPER 0x107D
+#define E1000_DEV_ID_82572EI_FIBER 0x107E
+#define E1000_DEV_ID_82572EI_SERDES 0x107F
+#define E1000_DEV_ID_82572EI 0x10B9
+#define E1000_DEV_ID_82573E 0x108B
+#define E1000_DEV_ID_82573E_IAMT 0x108C
+#define E1000_DEV_ID_82573L 0x109A
+#define E1000_DEV_ID_82546GB_QUAD_COPPER_KSP3 0x10B5
+#define E1000_DEV_ID_80003ES2LAN_COPPER_DPT 0x1096
+#define E1000_DEV_ID_80003ES2LAN_SERDES_DPT 0x1098
+#define E1000_DEV_ID_80003ES2LAN_COPPER_SPT 0x10BA
+#define E1000_DEV_ID_80003ES2LAN_SERDES_SPT 0x10BB
+
+#define E1000_DEV_ID_ICH8_IGP_M_AMT 0x1049
+#define E1000_DEV_ID_ICH8_IGP_AMT 0x104A
+#define E1000_DEV_ID_ICH8_IGP_C 0x104B
+#define E1000_DEV_ID_ICH8_IFE 0x104C
+#define E1000_DEV_ID_ICH8_IFE_GT 0x10C4
+#define E1000_DEV_ID_ICH8_IFE_G 0x10C5
+#define E1000_DEV_ID_ICH8_IGP_M 0x104D
+
+/* Device Specific Register Defaults */
+#define E1000_PHY_ID2_82541x 0x380
+#define E1000_PHY_ID2_82544x 0xC30
+#define E1000_PHY_ID2_8254xx_DEFAULT 0xC20 /* 82540x, 82545x, and 82546x */
+#define E1000_PHY_ID2_82573x 0xCC0
+
+/* Register Set. (82543, 82544)
+ *
+ * Registers are defined to be 32 bits and should be accessed as 32 bit values.
+ * These registers are physically located on the NIC, but are mapped into the
+ * host memory address space.
+ *
+ * RW - register is both readable and writable
+ * RO - register is read only
+ * WO - register is write only
+ * R/clr - register is read only and is cleared when read
+ * A - register array
+ */
+#define E1000_CTRL 0x00000 /* Device Control - RW */
+#define E1000_CTRL_DUP 0x00004 /* Device Control Duplicate (Shadow) - RW */
+#define E1000_STATUS 0x00008 /* Device Status - RO */
+#define E1000_EECD 0x00010 /* EEPROM/Flash Control - RW */
+#define E1000_EERD 0x00014 /* EEPROM Read - RW */
+#define E1000_CTRL_EXT 0x00018 /* Extended Device Control - RW */
+#define E1000_FLA 0x0001C /* Flash Access - RW */
+#define E1000_MDIC 0x00020 /* MDI Control - RW */
+#define E1000_SCTL 0x00024 /* SerDes Control - RW */
+#define E1000_FEXTNVM 0x00028 /* Future Extended NVM register */
+#define E1000_FCAL 0x00028 /* Flow Control Address Low - RW */
+#define E1000_FCAH 0x0002C /* Flow Control Address High -RW */
+#define E1000_FCT 0x00030 /* Flow Control Type - RW */
+#define E1000_VET 0x00038 /* VLAN Ether Type - RW */
+#define E1000_ICR 0x000C0 /* Interrupt Cause Read - R/clr */
+#define E1000_ITR 0x000C4 /* Interrupt Throttling Rate - RW */
+#define E1000_ICS 0x000C8 /* Interrupt Cause Set - WO */
+#define E1000_IMS 0x000D0 /* Interrupt Mask Set - RW */
+#define E1000_IMC 0x000D8 /* Interrupt Mask Clear - WO */
+#define E1000_IAM 0x000E0 /* Interrupt Acknowledge Auto Mask */
+#define E1000_RCTL 0x00100 /* RX Control - RW */
+#define E1000_RDTR1 0x02820 /* RX Delay Timer (1) - RW */
+#define E1000_RDBAL1 0x02900 /* RX Descriptor Base Address Low (1) - RW */
+#define E1000_RDBAH1 0x02904 /* RX Descriptor Base Address High (1) - RW */
+#define E1000_RDLEN1 0x02908 /* RX Descriptor Length (1) - RW */
+#define E1000_RDH1 0x02910 /* RX Descriptor Head (1) - RW */
+#define E1000_RDT1 0x02918 /* RX Descriptor Tail (1) - RW */
+#define E1000_FCTTV 0x00170 /* Flow Control Transmit Timer Value - RW */
+#define E1000_TXCW 0x00178 /* TX Configuration Word - RW */
+#define E1000_RXCW 0x00180 /* RX Configuration Word - RO */
+#define E1000_TCTL 0x00400 /* TX Control - RW */
+#define E1000_TCTL_EXT 0x00404 /* Extended TX Control - RW */
+#define E1000_TIPG 0x00410 /* TX Inter-packet gap -RW */
+#define E1000_TBT 0x00448 /* TX Burst Timer - RW */
+#define E1000_AIT 0x00458 /* Adaptive Interframe Spacing Throttle - RW */
+#define E1000_LEDCTL 0x00E00 /* LED Control - RW */
+#define E1000_EXTCNF_CTRL 0x00F00 /* Extended Configuration Control */
+#define E1000_EXTCNF_SIZE 0x00F08 /* Extended Configuration Size */
+#define E1000_PHY_CTRL 0x00F10 /* PHY Control Register in CSR */
+#define FEXTNVM_SW_CONFIG 0x0001
+#define E1000_PBA 0x01000 /* Packet Buffer Allocation - RW */
+#define E1000_PBS 0x01008 /* Packet Buffer Size */
+#define E1000_EEMNGCTL 0x01010 /* MNG EEprom Control */
+#define E1000_FLASH_UPDATES 1000
+#define E1000_EEARBC 0x01024 /* EEPROM Auto Read Bus Control */
+#define E1000_FLASHT 0x01028 /* FLASH Timer Register */
+#define E1000_EEWR 0x0102C /* EEPROM Write Register - RW */
+#define E1000_FLSWCTL 0x01030 /* FLASH control register */
+#define E1000_FLSWDATA 0x01034 /* FLASH data register */
+#define E1000_FLSWCNT 0x01038 /* FLASH Access Counter */
+#define E1000_FLOP 0x0103C /* FLASH Opcode Register */
+#define E1000_ERT 0x02008 /* Early Rx Threshold - RW */
+#define E1000_FCRTL 0x02160 /* Flow Control Receive Threshold Low - RW */
+#define E1000_FCRTH 0x02168 /* Flow Control Receive Threshold High - RW */
+#define E1000_PSRCTL 0x02170 /* Packet Split Receive Control - RW */
+#define E1000_RDBAL 0x02800 /* RX Descriptor Base Address Low - RW */
+#define E1000_RDBAH 0x02804 /* RX Descriptor Base Address High - RW */
+#define E1000_RDLEN 0x02808 /* RX Descriptor Length - RW */
+#define E1000_RDH 0x02810 /* RX Descriptor Head - RW */
+#define E1000_RDT 0x02818 /* RX Descriptor Tail - RW */
+#define E1000_RDTR 0x02820 /* RX Delay Timer - RW */
+#define E1000_RDBAL0 E1000_RDBAL /* RX Desc Base Address Low (0) - RW */
+#define E1000_RDBAH0 E1000_RDBAH /* RX Desc Base Address High (0) - RW */
+#define E1000_RDLEN0 E1000_RDLEN /* RX Desc Length (0) - RW */
+#define E1000_RDH0 E1000_RDH /* RX Desc Head (0) - RW */
+#define E1000_RDT0 E1000_RDT /* RX Desc Tail (0) - RW */
+#define E1000_RDTR0 E1000_RDTR /* RX Delay Timer (0) - RW */
+#define E1000_RXDCTL 0x02828 /* RX Descriptor Control queue 0 - RW */
+#define E1000_RXDCTL1 0x02928 /* RX Descriptor Control queue 1 - RW */
+#define E1000_RADV 0x0282C /* RX Interrupt Absolute Delay Timer - RW */
+#define E1000_RSRPD 0x02C00 /* RX Small Packet Detect - RW */
+#define E1000_RAID 0x02C08 /* Receive Ack Interrupt Delay - RW */
+#define E1000_TXDMAC 0x03000 /* TX DMA Control - RW */
+#define E1000_KABGTXD 0x03004 /* AFE Band Gap Transmit Ref Data */
+#define E1000_TDFH 0x03410 /* TX Data FIFO Head - RW */
+#define E1000_TDFT 0x03418 /* TX Data FIFO Tail - RW */
+#define E1000_TDFHS 0x03420 /* TX Data FIFO Head Saved - RW */
+#define E1000_TDFTS 0x03428 /* TX Data FIFO Tail Saved - RW */
+#define E1000_TDFPC 0x03430 /* TX Data FIFO Packet Count - RW */
+#define E1000_TDBAL 0x03800 /* TX Descriptor Base Address Low - RW */
+#define E1000_TDBAH 0x03804 /* TX Descriptor Base Address High - RW */
+#define E1000_TDLEN 0x03808 /* TX Descriptor Length - RW */
+#define E1000_TDH 0x03810 /* TX Descriptor Head - RW */
+#define E1000_TDT 0x03818 /* TX Descripotr Tail - RW */
+#define E1000_TIDV 0x03820 /* TX Interrupt Delay Value - RW */
+#define E1000_TXDCTL 0x03828 /* TX Descriptor Control - RW */
+#define E1000_TADV 0x0382C /* TX Interrupt Absolute Delay Val - RW */
+#define E1000_TSPMT 0x03830 /* TCP Segmentation PAD & Min Threshold - RW */
+#define E1000_TARC0 0x03840 /* TX Arbitration Count (0) */
+#define E1000_TDBAL1 0x03900 /* TX Desc Base Address Low (1) - RW */
+#define E1000_TDBAH1 0x03904 /* TX Desc Base Address High (1) - RW */
+#define E1000_TDLEN1 0x03908 /* TX Desc Length (1) - RW */
+#define E1000_TDH1 0x03910 /* TX Desc Head (1) - RW */
+#define E1000_TDT1 0x03918 /* TX Desc Tail (1) - RW */
+#define E1000_TXDCTL1 0x03928 /* TX Descriptor Control (1) - RW */
+#define E1000_TARC1 0x03940 /* TX Arbitration Count (1) */
+#define E1000_CRCERRS 0x04000 /* CRC Error Count - R/clr */
+#define E1000_ALGNERRC 0x04004 /* Alignment Error Count - R/clr */
+#define E1000_SYMERRS 0x04008 /* Symbol Error Count - R/clr */
+#define E1000_RXERRC 0x0400C /* Receive Error Count - R/clr */
+#define E1000_MPC 0x04010 /* Missed Packet Count - R/clr */
+#define E1000_SCC 0x04014 /* Single Collision Count - R/clr */
+#define E1000_ECOL 0x04018 /* Excessive Collision Count - R/clr */
+#define E1000_MCC 0x0401C /* Multiple Collision Count - R/clr */
+#define E1000_LATECOL 0x04020 /* Late Collision Count - R/clr */
+#define E1000_COLC 0x04028 /* Collision Count - R/clr */
+#define E1000_DC 0x04030 /* Defer Count - R/clr */
+#define E1000_TNCRS 0x04034 /* TX-No CRS - R/clr */
+#define E1000_SEC 0x04038 /* Sequence Error Count - R/clr */
+#define E1000_CEXTERR 0x0403C /* Carrier Extension Error Count - R/clr */
+#define E1000_RLEC 0x04040 /* Receive Length Error Count - R/clr */
+#define E1000_XONRXC 0x04048 /* XON RX Count - R/clr */
+#define E1000_XONTXC 0x0404C /* XON TX Count - R/clr */
+#define E1000_XOFFRXC 0x04050 /* XOFF RX Count - R/clr */
+#define E1000_XOFFTXC 0x04054 /* XOFF TX Count - R/clr */
+#define E1000_FCRUC 0x04058 /* Flow Control RX Unsupported Count- R/clr */
+#define E1000_PRC64 0x0405C /* Packets RX (64 bytes) - R/clr */
+#define E1000_PRC127 0x04060 /* Packets RX (65-127 bytes) - R/clr */
+#define E1000_PRC255 0x04064 /* Packets RX (128-255 bytes) - R/clr */
+#define E1000_PRC511 0x04068 /* Packets RX (255-511 bytes) - R/clr */
+#define E1000_PRC1023 0x0406C /* Packets RX (512-1023 bytes) - R/clr */
+#define E1000_PRC1522 0x04070 /* Packets RX (1024-1522 bytes) - R/clr */
+#define E1000_GPRC 0x04074 /* Good Packets RX Count - R/clr */
+#define E1000_BPRC 0x04078 /* Broadcast Packets RX Count - R/clr */
+#define E1000_MPRC 0x0407C /* Multicast Packets RX Count - R/clr */
+#define E1000_GPTC 0x04080 /* Good Packets TX Count - R/clr */
+#define E1000_GORCL 0x04088 /* Good Octets RX Count Low - R/clr */
+#define E1000_GORCH 0x0408C /* Good Octets RX Count High - R/clr */
+#define E1000_GOTCL 0x04090 /* Good Octets TX Count Low - R/clr */
+#define E1000_GOTCH 0x04094 /* Good Octets TX Count High - R/clr */
+#define E1000_RNBC 0x040A0 /* RX No Buffers Count - R/clr */
+#define E1000_RUC 0x040A4 /* RX Undersize Count - R/clr */
+#define E1000_RFC 0x040A8 /* RX Fragment Count - R/clr */
+#define E1000_ROC 0x040AC /* RX Oversize Count - R/clr */
+#define E1000_RJC 0x040B0 /* RX Jabber Count - R/clr */
+#define E1000_MGTPRC 0x040B4 /* Management Packets RX Count - R/clr */
+#define E1000_MGTPDC 0x040B8 /* Management Packets Dropped Count - R/clr */
+#define E1000_MGTPTC 0x040BC /* Management Packets TX Count - R/clr */
+#define E1000_TORL 0x040C0 /* Total Octets RX Low - R/clr */
+#define E1000_TORH 0x040C4 /* Total Octets RX High - R/clr */
+#define E1000_TOTL 0x040C8 /* Total Octets TX Low - R/clr */
+#define E1000_TOTH 0x040CC /* Total Octets TX High - R/clr */
+#define E1000_TPR 0x040D0 /* Total Packets RX - R/clr */
+#define E1000_TPT 0x040D4 /* Total Packets TX - R/clr */
+#define E1000_PTC64 0x040D8 /* Packets TX (64 bytes) - R/clr */
+#define E1000_PTC127 0x040DC /* Packets TX (65-127 bytes) - R/clr */
+#define E1000_PTC255 0x040E0 /* Packets TX (128-255 bytes) - R/clr */
+#define E1000_PTC511 0x040E4 /* Packets TX (256-511 bytes) - R/clr */
+#define E1000_PTC1023 0x040E8 /* Packets TX (512-1023 bytes) - R/clr */
+#define E1000_PTC1522 0x040EC /* Packets TX (1024-1522 Bytes) - R/clr */
+#define E1000_MPTC 0x040F0 /* Multicast Packets TX Count - R/clr */
+#define E1000_BPTC 0x040F4 /* Broadcast Packets TX Count - R/clr */
+#define E1000_TSCTC 0x040F8 /* TCP Segmentation Context TX - R/clr */
+#define E1000_TSCTFC 0x040FC /* TCP Segmentation Context TX Fail - R/clr */
+#define E1000_IAC 0x04100 /* Interrupt Assertion Count */
+#define E1000_ICRXPTC 0x04104 /* Interrupt Cause Rx Packet Timer Expire Count */
+#define E1000_ICRXATC 0x04108 /* Interrupt Cause Rx Absolute Timer Expire Count */
+#define E1000_ICTXPTC 0x0410C /* Interrupt Cause Tx Packet Timer Expire Count */
+#define E1000_ICTXATC 0x04110 /* Interrupt Cause Tx Absolute Timer Expire Count */
+#define E1000_ICTXQEC 0x04118 /* Interrupt Cause Tx Queue Empty Count */
+#define E1000_ICTXQMTC 0x0411C /* Interrupt Cause Tx Queue Minimum Threshold Count */
+#define E1000_ICRXDMTC 0x04120 /* Interrupt Cause Rx Descriptor Minimum Threshold Count */
+#define E1000_ICRXOC 0x04124 /* Interrupt Cause Receiver Overrun Count */
+#define E1000_RXCSUM 0x05000 /* RX Checksum Control - RW */
+#define E1000_RFCTL 0x05008 /* Receive Filter Control*/
+#define E1000_MTA 0x05200 /* Multicast Table Array - RW Array */
+#define E1000_RA 0x05400 /* Receive Address - RW Array */
+#define E1000_VFTA 0x05600 /* VLAN Filter Table Array - RW Array */
+#define E1000_WUC 0x05800 /* Wakeup Control - RW */
+#define E1000_WUFC 0x05808 /* Wakeup Filter Control - RW */
+#define E1000_WUS 0x05810 /* Wakeup Status - RO */
+#define E1000_MANC 0x05820 /* Management Control - RW */
+#define E1000_IPAV 0x05838 /* IP Address Valid - RW */
+#define E1000_IP4AT 0x05840 /* IPv4 Address Table - RW Array */
+#define E1000_IP6AT 0x05880 /* IPv6 Address Table - RW Array */
+#define E1000_WUPL 0x05900 /* Wakeup Packet Length - RW */
+#define E1000_WUPM 0x05A00 /* Wakeup Packet Memory - RO A */
+#define E1000_FFLT 0x05F00 /* Flexible Filter Length Table - RW Array */
+#define E1000_HOST_IF 0x08800 /* Host Interface */
+#define E1000_FFMT 0x09000 /* Flexible Filter Mask Table - RW Array */
+#define E1000_FFVT 0x09800 /* Flexible Filter Value Table - RW Array */
+
+#define E1000_KUMCTRLSTA 0x00034 /* MAC-PHY interface - RW */
+#define E1000_MDPHYA 0x0003C /* PHY address - RW */
+#define E1000_MANC2H 0x05860 /* Management Control To Host - RW */
+#define E1000_SW_FW_SYNC 0x05B5C /* Software-Firmware Synchronization - RW */
+
+#define E1000_GCR 0x05B00 /* PCI-Ex Control */
+#define E1000_GSCL_1 0x05B10 /* PCI-Ex Statistic Control #1 */
+#define E1000_GSCL_2 0x05B14 /* PCI-Ex Statistic Control #2 */
+#define E1000_GSCL_3 0x05B18 /* PCI-Ex Statistic Control #3 */
+#define E1000_GSCL_4 0x05B1C /* PCI-Ex Statistic Control #4 */
+#define E1000_FACTPS 0x05B30 /* Function Active and Power State to MNG */
+#define E1000_SWSM 0x05B50 /* SW Semaphore */
+#define E1000_FWSM 0x05B54 /* FW Semaphore */
+#define E1000_FFLT_DBG 0x05F04 /* Debug Register */
+#define E1000_HICR 0x08F00 /* Host Inteface Control */
+
+/* RSS registers */
+#define E1000_CPUVEC 0x02C10 /* CPU Vector Register - RW */
+#define E1000_MRQC 0x05818 /* Multiple Receive Control - RW */
+#define E1000_RETA 0x05C00 /* Redirection Table - RW Array */
+#define E1000_RSSRK 0x05C80 /* RSS Random Key - RW Array */
+#define E1000_RSSIM 0x05864 /* RSS Interrupt Mask */
+#define E1000_RSSIR 0x05868 /* RSS Interrupt Request */
+
+/* PHY 1000 MII Register/Bit Definitions */
+/* PHY Registers defined by IEEE */
+#define PHY_CTRL 0x00 /* Control Register */
+#define PHY_STATUS 0x01 /* Status Regiser */
+#define PHY_ID1 0x02 /* Phy Id Reg (word 1) */
+#define PHY_ID2 0x03 /* Phy Id Reg (word 2) */
+#define PHY_AUTONEG_ADV 0x04 /* Autoneg Advertisement */
+#define PHY_LP_ABILITY 0x05 /* Link Partner Ability (Base Page) */
+#define PHY_AUTONEG_EXP 0x06 /* Autoneg Expansion Reg */
+#define PHY_NEXT_PAGE_TX 0x07 /* Next Page TX */
+#define PHY_LP_NEXT_PAGE 0x08 /* Link Partner Next Page */
+#define PHY_1000T_CTRL 0x09 /* 1000Base-T Control Reg */
+#define PHY_1000T_STATUS 0x0A /* 1000Base-T Status Reg */
+#define PHY_EXT_STATUS 0x0F /* Extended Status Reg */
+
+#define MAX_PHY_REG_ADDRESS 0x1F /* 5 bit address bus (0-0x1F) */
+#define MAX_PHY_MULTI_PAGE_REG 0xF /* Registers equal on all pages */
+
+/* M88E1000 Specific Registers */
+#define M88E1000_PHY_SPEC_CTRL 0x10 /* PHY Specific Control Register */
+#define M88E1000_PHY_SPEC_STATUS 0x11 /* PHY Specific Status Register */
+#define M88E1000_INT_ENABLE 0x12 /* Interrupt Enable Register */
+#define M88E1000_INT_STATUS 0x13 /* Interrupt Status Register */
+#define M88E1000_EXT_PHY_SPEC_CTRL 0x14 /* Extended PHY Specific Control */
+#define M88E1000_RX_ERR_CNTR 0x15 /* Receive Error Counter */
+
+#define M88E1000_PHY_EXT_CTRL 0x1A /* PHY extend control register */
+#define M88E1000_PHY_PAGE_SELECT 0x1D /* Reg 29 for page number setting */
+#define M88E1000_PHY_GEN_CONTROL 0x1E /* Its meaning depends on reg 29 */
+#define M88E1000_PHY_VCO_REG_BIT8 0x100 /* Bits 8 & 11 are adjusted for */
+#define M88E1000_PHY_VCO_REG_BIT11 0x800 /* improved BER performance */
+
+/* PHY Control Register */
+#define MII_CR_SPEED_SELECT_MSB 0x0040 /* bits 6,13: 10=1000, 01=100, 00=10 */
+#define MII_CR_COLL_TEST_ENABLE 0x0080 /* Collision test enable */
+#define MII_CR_FULL_DUPLEX 0x0100 /* FDX =1, half duplex =0 */
+#define MII_CR_RESTART_AUTO_NEG 0x0200 /* Restart auto negotiation */
+#define MII_CR_ISOLATE 0x0400 /* Isolate PHY from MII */
+#define MII_CR_POWER_DOWN 0x0800 /* Power down */
+#define MII_CR_AUTO_NEG_EN 0x1000 /* Auto Neg Enable */
+#define MII_CR_SPEED_SELECT_LSB 0x2000 /* bits 6,13: 10=1000, 01=100, 00=10 */
+#define MII_CR_LOOPBACK 0x4000 /* 0 = normal, 1 = loopback */
+#define MII_CR_RESET 0x8000 /* 0 = normal, 1 = PHY reset */
+
+/* PHY Status Register */
+#define MII_SR_EXTENDED_CAPS 0x0001 /* Extended register capabilities */
+#define MII_SR_JABBER_DETECT 0x0002 /* Jabber Detected */
+#define MII_SR_LINK_STATUS 0x0004 /* Link Status 1 = link */
+#define MII_SR_AUTONEG_CAPS 0x0008 /* Auto Neg Capable */
+#define MII_SR_REMOTE_FAULT 0x0010 /* Remote Fault Detect */
+#define MII_SR_AUTONEG_COMPLETE 0x0020 /* Auto Neg Complete */
+#define MII_SR_PREAMBLE_SUPPRESS 0x0040 /* Preamble may be suppressed */
+#define MII_SR_EXTENDED_STATUS 0x0100 /* Ext. status info in Reg 0x0F */
+#define MII_SR_100T2_HD_CAPS 0x0200 /* 100T2 Half Duplex Capable */
+#define MII_SR_100T2_FD_CAPS 0x0400 /* 100T2 Full Duplex Capable */
+#define MII_SR_10T_HD_CAPS 0x0800 /* 10T Half Duplex Capable */
+#define MII_SR_10T_FD_CAPS 0x1000 /* 10T Full Duplex Capable */
+#define MII_SR_100X_HD_CAPS 0x2000 /* 100X Half Duplex Capable */
+#define MII_SR_100X_FD_CAPS 0x4000 /* 100X Full Duplex Capable */
+#define MII_SR_100T4_CAPS 0x8000 /* 100T4 Capable */
+
+/* PHY Link Partner Ability Register */
+#define MII_LPAR_LPACK 0x4000 /* Acked by link partner */
+
+/* Interrupt Cause Read */
+#define E1000_ICR_TXDW 0x00000001 /* Transmit desc written back */
+#define E1000_ICR_TXQE 0x00000002 /* Transmit Queue empty */
+#define E1000_ICR_LSC 0x00000004 /* Link Status Change */
+#define E1000_ICR_RXSEQ 0x00000008 /* rx sequence error */
+#define E1000_ICR_RXDMT0 0x00000010 /* rx desc min. threshold (0) */
+#define E1000_ICR_RXO 0x00000040 /* rx overrun */
+#define E1000_ICR_RXT0 0x00000080 /* rx timer intr (ring 0) */
+#define E1000_ICR_MDAC 0x00000200 /* MDIO access complete */
+#define E1000_ICR_RXCFG 0x00000400 /* RX /c/ ordered set */
+#define E1000_ICR_GPI_EN0 0x00000800 /* GP Int 0 */
+#define E1000_ICR_GPI_EN1 0x00001000 /* GP Int 1 */
+#define E1000_ICR_GPI_EN2 0x00002000 /* GP Int 2 */
+#define E1000_ICR_GPI_EN3 0x00004000 /* GP Int 3 */
+#define E1000_ICR_TXD_LOW 0x00008000
+#define E1000_ICR_SRPD 0x00010000
+#define E1000_ICR_ACK 0x00020000 /* Receive Ack frame */
+#define E1000_ICR_MNG 0x00040000 /* Manageability event */
+#define E1000_ICR_DOCK 0x00080000 /* Dock/Undock */
+#define E1000_ICR_INT_ASSERTED 0x80000000 /* If this bit asserted, the driver should claim the interrupt */
+#define E1000_ICR_RXD_FIFO_PAR0 0x00100000 /* queue 0 Rx descriptor FIFO parity error */
+#define E1000_ICR_TXD_FIFO_PAR0 0x00200000 /* queue 0 Tx descriptor FIFO parity error */
+#define E1000_ICR_HOST_ARB_PAR 0x00400000 /* host arb read buffer parity error */
+#define E1000_ICR_PB_PAR 0x00800000 /* packet buffer parity error */
+#define E1000_ICR_RXD_FIFO_PAR1 0x01000000 /* queue 1 Rx descriptor FIFO parity error */
+#define E1000_ICR_TXD_FIFO_PAR1 0x02000000 /* queue 1 Tx descriptor FIFO parity error */
+#define E1000_ICR_ALL_PARITY 0x03F00000 /* all parity error bits */
+#define E1000_ICR_DSW 0x00000020 /* FW changed the status of DISSW bit in the FWSM */
+#define E1000_ICR_PHYINT 0x00001000 /* LAN connected device generates an interrupt */
+#define E1000_ICR_EPRST 0x00100000 /* ME handware reset occurs */
+
+/* Interrupt Cause Set */
+#define E1000_ICS_TXDW E1000_ICR_TXDW /* Transmit desc written back */
+#define E1000_ICS_TXQE E1000_ICR_TXQE /* Transmit Queue empty */
+#define E1000_ICS_LSC E1000_ICR_LSC /* Link Status Change */
+#define E1000_ICS_RXSEQ E1000_ICR_RXSEQ /* rx sequence error */
+#define E1000_ICS_RXDMT0 E1000_ICR_RXDMT0 /* rx desc min. threshold */
+#define E1000_ICS_RXO E1000_ICR_RXO /* rx overrun */
+#define E1000_ICS_RXT0 E1000_ICR_RXT0 /* rx timer intr */
+#define E1000_ICS_MDAC E1000_ICR_MDAC /* MDIO access complete */
+#define E1000_ICS_RXCFG E1000_ICR_RXCFG /* RX /c/ ordered set */
+#define E1000_ICS_GPI_EN0 E1000_ICR_GPI_EN0 /* GP Int 0 */
+#define E1000_ICS_GPI_EN1 E1000_ICR_GPI_EN1 /* GP Int 1 */
+#define E1000_ICS_GPI_EN2 E1000_ICR_GPI_EN2 /* GP Int 2 */
+#define E1000_ICS_GPI_EN3 E1000_ICR_GPI_EN3 /* GP Int 3 */
+#define E1000_ICS_TXD_LOW E1000_ICR_TXD_LOW
+#define E1000_ICS_SRPD E1000_ICR_SRPD
+#define E1000_ICS_ACK E1000_ICR_ACK /* Receive Ack frame */
+#define E1000_ICS_MNG E1000_ICR_MNG /* Manageability event */
+#define E1000_ICS_DOCK E1000_ICR_DOCK /* Dock/Undock */
+#define E1000_ICS_RXD_FIFO_PAR0 E1000_ICR_RXD_FIFO_PAR0 /* queue 0 Rx descriptor FIFO parity error */
+#define E1000_ICS_TXD_FIFO_PAR0 E1000_ICR_TXD_FIFO_PAR0 /* queue 0 Tx descriptor FIFO parity error */
+#define E1000_ICS_HOST_ARB_PAR E1000_ICR_HOST_ARB_PAR /* host arb read buffer parity error */
+#define E1000_ICS_PB_PAR E1000_ICR_PB_PAR /* packet buffer parity error */
+#define E1000_ICS_RXD_FIFO_PAR1 E1000_ICR_RXD_FIFO_PAR1 /* queue 1 Rx descriptor FIFO parity error */
+#define E1000_ICS_TXD_FIFO_PAR1 E1000_ICR_TXD_FIFO_PAR1 /* queue 1 Tx descriptor FIFO parity error */
+#define E1000_ICS_DSW E1000_ICR_DSW
+#define E1000_ICS_PHYINT E1000_ICR_PHYINT
+#define E1000_ICS_EPRST E1000_ICR_EPRST
+
+/* Interrupt Mask Set */
+#define E1000_IMS_TXDW E1000_ICR_TXDW /* Transmit desc written back */
+#define E1000_IMS_TXQE E1000_ICR_TXQE /* Transmit Queue empty */
+#define E1000_IMS_LSC E1000_ICR_LSC /* Link Status Change */
+#define E1000_IMS_RXSEQ E1000_ICR_RXSEQ /* rx sequence error */
+#define E1000_IMS_RXDMT0 E1000_ICR_RXDMT0 /* rx desc min. threshold */
+#define E1000_IMS_RXO E1000_ICR_RXO /* rx overrun */
+#define E1000_IMS_RXT0 E1000_ICR_RXT0 /* rx timer intr */
+#define E1000_IMS_MDAC E1000_ICR_MDAC /* MDIO access complete */
+#define E1000_IMS_RXCFG E1000_ICR_RXCFG /* RX /c/ ordered set */
+#define E1000_IMS_GPI_EN0 E1000_ICR_GPI_EN0 /* GP Int 0 */
+#define E1000_IMS_GPI_EN1 E1000_ICR_GPI_EN1 /* GP Int 1 */
+#define E1000_IMS_GPI_EN2 E1000_ICR_GPI_EN2 /* GP Int 2 */
+#define E1000_IMS_GPI_EN3 E1000_ICR_GPI_EN3 /* GP Int 3 */
+#define E1000_IMS_TXD_LOW E1000_ICR_TXD_LOW
+#define E1000_IMS_SRPD E1000_ICR_SRPD
+#define E1000_IMS_ACK E1000_ICR_ACK /* Receive Ack frame */
+#define E1000_IMS_MNG E1000_ICR_MNG /* Manageability event */
+#define E1000_IMS_DOCK E1000_ICR_DOCK /* Dock/Undock */
+#define E1000_IMS_RXD_FIFO_PAR0 E1000_ICR_RXD_FIFO_PAR0 /* queue 0 Rx descriptor FIFO parity error */
+#define E1000_IMS_TXD_FIFO_PAR0 E1000_ICR_TXD_FIFO_PAR0 /* queue 0 Tx descriptor FIFO parity error */
+#define E1000_IMS_HOST_ARB_PAR E1000_ICR_HOST_ARB_PAR /* host arb read buffer parity error */
+#define E1000_IMS_PB_PAR E1000_ICR_PB_PAR /* packet buffer parity error */
+#define E1000_IMS_RXD_FIFO_PAR1 E1000_ICR_RXD_FIFO_PAR1 /* queue 1 Rx descriptor FIFO parity error */
+#define E1000_IMS_TXD_FIFO_PAR1 E1000_ICR_TXD_FIFO_PAR1 /* queue 1 Tx descriptor FIFO parity error */
+#define E1000_IMS_DSW E1000_ICR_DSW
+#define E1000_IMS_PHYINT E1000_ICR_PHYINT
+#define E1000_IMS_EPRST E1000_ICR_EPRST
+
+/* Interrupt Mask Clear */
+#define E1000_IMC_TXDW E1000_ICR_TXDW /* Transmit desc written back */
+#define E1000_IMC_TXQE E1000_ICR_TXQE /* Transmit Queue empty */
+#define E1000_IMC_LSC E1000_ICR_LSC /* Link Status Change */
+#define E1000_IMC_RXSEQ E1000_ICR_RXSEQ /* rx sequence error */
+#define E1000_IMC_RXDMT0 E1000_ICR_RXDMT0 /* rx desc min. threshold */
+#define E1000_IMC_RXO E1000_ICR_RXO /* rx overrun */
+#define E1000_IMC_RXT0 E1000_ICR_RXT0 /* rx timer intr */
+#define E1000_IMC_MDAC E1000_ICR_MDAC /* MDIO access complete */
+#define E1000_IMC_RXCFG E1000_ICR_RXCFG /* RX /c/ ordered set */
+#define E1000_IMC_GPI_EN0 E1000_ICR_GPI_EN0 /* GP Int 0 */
+#define E1000_IMC_GPI_EN1 E1000_ICR_GPI_EN1 /* GP Int 1 */
+#define E1000_IMC_GPI_EN2 E1000_ICR_GPI_EN2 /* GP Int 2 */
+#define E1000_IMC_GPI_EN3 E1000_ICR_GPI_EN3 /* GP Int 3 */
+#define E1000_IMC_TXD_LOW E1000_ICR_TXD_LOW
+#define E1000_IMC_SRPD E1000_ICR_SRPD
+#define E1000_IMC_ACK E1000_ICR_ACK /* Receive Ack frame */
+#define E1000_IMC_MNG E1000_ICR_MNG /* Manageability event */
+#define E1000_IMC_DOCK E1000_ICR_DOCK /* Dock/Undock */
+#define E1000_IMC_RXD_FIFO_PAR0 E1000_ICR_RXD_FIFO_PAR0 /* queue 0 Rx descriptor FIFO parity error */
+#define E1000_IMC_TXD_FIFO_PAR0 E1000_ICR_TXD_FIFO_PAR0 /* queue 0 Tx descriptor FIFO parity error */
+#define E1000_IMC_HOST_ARB_PAR E1000_ICR_HOST_ARB_PAR /* host arb read buffer parity error */
+#define E1000_IMC_PB_PAR E1000_ICR_PB_PAR /* packet buffer parity error */
+#define E1000_IMC_RXD_FIFO_PAR1 E1000_ICR_RXD_FIFO_PAR1 /* queue 1 Rx descriptor FIFO parity error */
+#define E1000_IMC_TXD_FIFO_PAR1 E1000_ICR_TXD_FIFO_PAR1 /* queue 1 Tx descriptor FIFO parity error */
+#define E1000_IMC_DSW E1000_ICR_DSW
+#define E1000_IMC_PHYINT E1000_ICR_PHYINT
+#define E1000_IMC_EPRST E1000_ICR_EPRST
+
+/* Receive Control */
+#define E1000_RCTL_RST 0x00000001 /* Software reset */
+#define E1000_RCTL_EN 0x00000002 /* enable */
+#define E1000_RCTL_SBP 0x00000004 /* store bad packet */
+#define E1000_RCTL_UPE 0x00000008 /* unicast promiscuous enable */
+#define E1000_RCTL_MPE 0x00000010 /* multicast promiscuous enab */
+#define E1000_RCTL_LPE 0x00000020 /* long packet enable */
+#define E1000_RCTL_LBM_NO 0x00000000 /* no loopback mode */
+#define E1000_RCTL_LBM_MAC 0x00000040 /* MAC loopback mode */
+#define E1000_RCTL_LBM_SLP 0x00000080 /* serial link loopback mode */
+#define E1000_RCTL_LBM_TCVR 0x000000C0 /* tcvr loopback mode */
+#define E1000_RCTL_DTYP_MASK 0x00000C00 /* Descriptor type mask */
+#define E1000_RCTL_DTYP_PS 0x00000400 /* Packet Split descriptor */
+#define E1000_RCTL_RDMTS_HALF 0x00000000 /* rx desc min threshold size */
+#define E1000_RCTL_RDMTS_QUAT 0x00000100 /* rx desc min threshold size */
+#define E1000_RCTL_RDMTS_EIGTH 0x00000200 /* rx desc min threshold size */
+#define E1000_RCTL_MO_SHIFT 12 /* multicast offset shift */
+#define E1000_RCTL_MO_0 0x00000000 /* multicast offset 11:0 */
+#define E1000_RCTL_MO_1 0x00001000 /* multicast offset 12:1 */
+#define E1000_RCTL_MO_2 0x00002000 /* multicast offset 13:2 */
+#define E1000_RCTL_MO_3 0x00003000 /* multicast offset 15:4 */
+#define E1000_RCTL_MDR 0x00004000 /* multicast desc ring 0 */
+#define E1000_RCTL_BAM 0x00008000 /* broadcast enable */
+/* these buffer sizes are valid if E1000_RCTL_BSEX is 0 */
+#define E1000_RCTL_SZ_2048 0x00000000 /* rx buffer size 2048 */
+#define E1000_RCTL_SZ_1024 0x00010000 /* rx buffer size 1024 */
+#define E1000_RCTL_SZ_512 0x00020000 /* rx buffer size 512 */
+#define E1000_RCTL_SZ_256 0x00030000 /* rx buffer size 256 */
+/* these buffer sizes are valid if E1000_RCTL_BSEX is 1 */
+#define E1000_RCTL_SZ_16384 0x00010000 /* rx buffer size 16384 */
+#define E1000_RCTL_SZ_8192 0x00020000 /* rx buffer size 8192 */
+#define E1000_RCTL_SZ_4096 0x00030000 /* rx buffer size 4096 */
+#define E1000_RCTL_VFE 0x00040000 /* vlan filter enable */
+#define E1000_RCTL_CFIEN 0x00080000 /* canonical form enable */
+#define E1000_RCTL_CFI 0x00100000 /* canonical form indicator */
+#define E1000_RCTL_DPF 0x00400000 /* discard pause frames */
+#define E1000_RCTL_PMCF 0x00800000 /* pass MAC control frames */
+#define E1000_RCTL_BSEX 0x02000000 /* Buffer size extension */
+#define E1000_RCTL_SECRC 0x04000000 /* Strip Ethernet CRC */
+#define E1000_RCTL_FLXBUF_MASK 0x78000000 /* Flexible buffer size */
+#define E1000_RCTL_FLXBUF_SHIFT 27 /* Flexible buffer shift */
+
+
+#define E1000_EEPROM_SWDPIN0 0x0001 /* SWDPIN 0 EEPROM Value */
+#define E1000_EEPROM_LED_LOGIC 0x0020 /* Led Logic Word */
+#define E1000_EEPROM_RW_REG_DATA 16 /* Offset to data in EEPROM read/write registers */
+#define E1000_EEPROM_RW_REG_DONE 0x10 /* Offset to READ/WRITE done bit */
+#define E1000_EEPROM_RW_REG_START 1 /* First bit for telling part to start operation */
+#define E1000_EEPROM_RW_ADDR_SHIFT 8 /* Shift to the address bits */
+#define E1000_EEPROM_POLL_WRITE 1 /* Flag for polling for write complete */
+#define E1000_EEPROM_POLL_READ 0 /* Flag for polling for read complete */
+/* Register Bit Masks */
+/* Device Control */
+#define E1000_CTRL_FD 0x00000001 /* Full duplex.0=half; 1=full */
+#define E1000_CTRL_BEM 0x00000002 /* Endian Mode.0=little,1=big */
+#define E1000_CTRL_PRIOR 0x00000004 /* Priority on PCI. 0=rx,1=fair */
+#define E1000_CTRL_GIO_MASTER_DISABLE 0x00000004 /*Blocks new Master requests */
+#define E1000_CTRL_LRST 0x00000008 /* Link reset. 0=normal,1=reset */
+#define E1000_CTRL_TME 0x00000010 /* Test mode. 0=normal,1=test */
+#define E1000_CTRL_SLE 0x00000020 /* Serial Link on 0=dis,1=en */
+#define E1000_CTRL_ASDE 0x00000020 /* Auto-speed detect enable */
+#define E1000_CTRL_SLU 0x00000040 /* Set link up (Force Link) */
+#define E1000_CTRL_ILOS 0x00000080 /* Invert Loss-Of Signal */
+#define E1000_CTRL_SPD_SEL 0x00000300 /* Speed Select Mask */
+#define E1000_CTRL_SPD_10 0x00000000 /* Force 10Mb */
+#define E1000_CTRL_SPD_100 0x00000100 /* Force 100Mb */
+#define E1000_CTRL_SPD_1000 0x00000200 /* Force 1Gb */
+#define E1000_CTRL_BEM32 0x00000400 /* Big Endian 32 mode */
+#define E1000_CTRL_FRCSPD 0x00000800 /* Force Speed */
+#define E1000_CTRL_FRCDPX 0x00001000 /* Force Duplex */
+#define E1000_CTRL_D_UD_EN 0x00002000 /* Dock/Undock enable */
+#define E1000_CTRL_D_UD_POLARITY 0x00004000 /* Defined polarity of Dock/Undock indication in SDP[0] */
+#define E1000_CTRL_FORCE_PHY_RESET 0x00008000 /* Reset both PHY ports, through PHYRST_N pin */
+#define E1000_CTRL_EXT_LINK_EN 0x00010000 /* enable link status from external LINK_0 and LINK_1 pins */
+#define E1000_CTRL_SWDPIN0 0x00040000 /* SWDPIN 0 value */
+#define E1000_CTRL_SWDPIN1 0x00080000 /* SWDPIN 1 value */
+#define E1000_CTRL_SWDPIN2 0x00100000 /* SWDPIN 2 value */
+#define E1000_CTRL_SWDPIN3 0x00200000 /* SWDPIN 3 value */
+#define E1000_CTRL_SWDPIO0 0x00400000 /* SWDPIN 0 Input or output */
+#define E1000_CTRL_SWDPIO1 0x00800000 /* SWDPIN 1 input or output */
+#define E1000_CTRL_SWDPIO2 0x01000000 /* SWDPIN 2 input or output */
+#define E1000_CTRL_SWDPIO3 0x02000000 /* SWDPIN 3 input or output */
+#define E1000_CTRL_RST 0x04000000 /* Global reset */
+#define E1000_CTRL_RFCE 0x08000000 /* Receive Flow Control enable */
+#define E1000_CTRL_TFCE 0x10000000 /* Transmit flow control enable */
+#define E1000_CTRL_RTE 0x20000000 /* Routing tag enable */
+#define E1000_CTRL_VME 0x40000000 /* IEEE VLAN mode enable */
+#define E1000_CTRL_PHY_RST 0x80000000 /* PHY Reset */
+#define E1000_CTRL_SW2FW_INT 0x02000000 /* Initiate an interrupt to manageability engine */
+
+/* Device Status */
+#define E1000_STATUS_FD 0x00000001 /* Full duplex.0=half,1=full */
+#define E1000_STATUS_LU 0x00000002 /* Link up.0=no,1=link */
+#define E1000_STATUS_FUNC_MASK 0x0000000C /* PCI Function Mask */
+#define E1000_STATUS_FUNC_SHIFT 2
+#define E1000_STATUS_FUNC_0 0x00000000 /* Function 0 */
+#define E1000_STATUS_FUNC_1 0x00000004 /* Function 1 */
+#define E1000_STATUS_TXOFF 0x00000010 /* transmission paused */
+#define E1000_STATUS_TBIMODE 0x00000020 /* TBI mode */
+#define E1000_STATUS_SPEED_MASK 0x000000C0
+#define E1000_STATUS_SPEED_10 0x00000000 /* Speed 10Mb/s */
+#define E1000_STATUS_SPEED_100 0x00000040 /* Speed 100Mb/s */
+#define E1000_STATUS_SPEED_1000 0x00000080 /* Speed 1000Mb/s */
+#define E1000_STATUS_LAN_INIT_DONE 0x00000200 /* Lan Init Completion
+ by EEPROM/Flash */
+#define E1000_STATUS_ASDV 0x00000300 /* Auto speed detect value */
+#define E1000_STATUS_DOCK_CI 0x00000800 /* Change in Dock/Undock state. Clear on write '0'. */
+#define E1000_STATUS_GIO_MASTER_ENABLE 0x00080000 /* Status of Master requests. */
+#define E1000_STATUS_MTXCKOK 0x00000400 /* MTX clock running OK */
+#define E1000_STATUS_PCI66 0x00000800 /* In 66Mhz slot */
+#define E1000_STATUS_BUS64 0x00001000 /* In 64 bit slot */
+#define E1000_STATUS_PCIX_MODE 0x00002000 /* PCI-X mode */
+#define E1000_STATUS_PCIX_SPEED 0x0000C000 /* PCI-X bus speed */
+#define E1000_STATUS_BMC_SKU_0 0x00100000 /* BMC USB redirect disabled */
+#define E1000_STATUS_BMC_SKU_1 0x00200000 /* BMC SRAM disabled */
+#define E1000_STATUS_BMC_SKU_2 0x00400000 /* BMC SDRAM disabled */
+#define E1000_STATUS_BMC_CRYPTO 0x00800000 /* BMC crypto disabled */
+#define E1000_STATUS_BMC_LITE 0x01000000 /* BMC external code execution disabled */
+#define E1000_STATUS_RGMII_ENABLE 0x02000000 /* RGMII disabled */
+#define E1000_STATUS_FUSE_8 0x04000000
+#define E1000_STATUS_FUSE_9 0x08000000
+#define E1000_STATUS_SERDES0_DIS 0x10000000 /* SERDES disabled on port 0 */
+#define E1000_STATUS_SERDES1_DIS 0x20000000 /* SERDES disabled on port 1 */
+
+/* EEPROM/Flash Control */
+#define E1000_EECD_SK 0x00000001 /* EEPROM Clock */
+#define E1000_EECD_CS 0x00000002 /* EEPROM Chip Select */
+#define E1000_EECD_DI 0x00000004 /* EEPROM Data In */
+#define E1000_EECD_DO 0x00000008 /* EEPROM Data Out */
+#define E1000_EECD_FWE_MASK 0x00000030
+#define E1000_EECD_FWE_DIS 0x00000010 /* Disable FLASH writes */
+#define E1000_EECD_FWE_EN 0x00000020 /* Enable FLASH writes */
+#define E1000_EECD_FWE_SHIFT 4
+#define E1000_EECD_REQ 0x00000040 /* EEPROM Access Request */
+#define E1000_EECD_GNT 0x00000080 /* EEPROM Access Grant */
+#define E1000_EECD_PRES 0x00000100 /* EEPROM Present */
+#define E1000_EECD_SIZE 0x00000200 /* EEPROM Size (0=64 word 1=256 word) */
+#define E1000_EECD_ADDR_BITS 0x00000400 /* EEPROM Addressing bits based on type
+ * (0-small, 1-large) */
+#define E1000_EECD_TYPE 0x00002000 /* EEPROM Type (1-SPI, 0-Microwire) */
+#ifndef E1000_EEPROM_GRANT_ATTEMPTS
+#define E1000_EEPROM_GRANT_ATTEMPTS 1000 /* EEPROM # attempts to gain grant */
+#endif
+#define E1000_EECD_AUTO_RD 0x00000200 /* EEPROM Auto Read done */
+#define E1000_EECD_SIZE_EX_MASK 0x00007800 /* EEprom Size */
+#define E1000_EECD_SIZE_EX_SHIFT 11
+#define E1000_EECD_NVADDS 0x00018000 /* NVM Address Size */
+#define E1000_EECD_SELSHAD 0x00020000 /* Select Shadow RAM */
+#define E1000_EECD_INITSRAM 0x00040000 /* Initialize Shadow RAM */
+#define E1000_EECD_FLUPD 0x00080000 /* Update FLASH */
+#define E1000_EECD_AUPDEN 0x00100000 /* Enable Autonomous FLASH update */
+#define E1000_EECD_SHADV 0x00200000 /* Shadow RAM Data Valid */
+#define E1000_EECD_SEC1VAL 0x00400000 /* Sector One Valid */
+#define E1000_EECD_SECVAL_SHIFT 22
+#define E1000_STM_OPCODE 0xDB00
+#define E1000_HICR_FW_RESET 0xC0
+
+#define E1000_SHADOW_RAM_WORDS 2048
+#define E1000_ICH_NVM_SIG_WORD 0x13
+#define E1000_ICH_NVM_SIG_MASK 0xC0
+
+/* MDI Control */
+#define E1000_MDIC_DATA_MASK 0x0000FFFF
+#define E1000_MDIC_REG_MASK 0x001F0000
+#define E1000_MDIC_REG_SHIFT 16
+#define E1000_MDIC_PHY_MASK 0x03E00000
+#define E1000_MDIC_PHY_SHIFT 21
+#define E1000_MDIC_OP_WRITE 0x04000000
+#define E1000_MDIC_OP_READ 0x08000000
+#define E1000_MDIC_READY 0x10000000
+#define E1000_MDIC_INT_EN 0x20000000
+#define E1000_MDIC_ERROR 0x40000000
+
+/* EEPROM Commands - Microwire */
+#define EEPROM_READ_OPCODE_MICROWIRE 0x6 /* EEPROM read opcode */
+#define EEPROM_WRITE_OPCODE_MICROWIRE 0x5 /* EEPROM write opcode */
+#define EEPROM_ERASE_OPCODE_MICROWIRE 0x7 /* EEPROM erase opcode */
+#define EEPROM_EWEN_OPCODE_MICROWIRE 0x13 /* EEPROM erase/write enable */
+#define EEPROM_EWDS_OPCODE_MICROWIRE 0x10 /* EEPROM erast/write disable */
+
+/* EEPROM Word Offsets */
+#define EEPROM_COMPAT 0x0003
+#define EEPROM_ID_LED_SETTINGS 0x0004
+#define EEPROM_VERSION 0x0005
+#define EEPROM_SERDES_AMPLITUDE 0x0006 /* For SERDES output amplitude adjustment. */
+#define EEPROM_PHY_CLASS_WORD 0x0007
+#define EEPROM_INIT_CONTROL1_REG 0x000A
+#define EEPROM_INIT_CONTROL2_REG 0x000F
+#define EEPROM_SWDEF_PINS_CTRL_PORT_1 0x0010
+#define EEPROM_INIT_CONTROL3_PORT_B 0x0014
+#define EEPROM_INIT_3GIO_3 0x001A
+#define EEPROM_SWDEF_PINS_CTRL_PORT_0 0x0020
+#define EEPROM_INIT_CONTROL3_PORT_A 0x0024
+#define EEPROM_CFG 0x0012
+#define EEPROM_FLASH_VERSION 0x0032
+#define EEPROM_CHECKSUM_REG 0x003F
+
+#define E1000_EEPROM_CFG_DONE 0x00040000 /* MNG config cycle done */
+#define E1000_EEPROM_CFG_DONE_PORT_1 0x00080000 /* ...for second port */
+
+/* Transmit Descriptor */
+struct e1000_tx_desc {
+ uint64_t buffer_addr; /* Address of the descriptor's data buffer */
+ union {
+ uint32_t data;
+ struct {
+ uint16_t length; /* Data buffer length */
+ uint8_t cso; /* Checksum offset */
+ uint8_t cmd; /* Descriptor control */
+ } flags;
+ } lower;
+ union {
+ uint32_t data;
+ struct {
+ uint8_t status; /* Descriptor status */
+ uint8_t css; /* Checksum start */
+ uint16_t special;
+ } fields;
+ } upper;
+};
+
+/* Transmit Descriptor bit definitions */
+#define E1000_TXD_DTYP_D 0x00100000 /* Data Descriptor */
+#define E1000_TXD_DTYP_C 0x00000000 /* Context Descriptor */
+#define E1000_TXD_POPTS_IXSM 0x01 /* Insert IP checksum */
+#define E1000_TXD_POPTS_TXSM 0x02 /* Insert TCP/UDP checksum */
+#define E1000_TXD_CMD_EOP 0x01000000 /* End of Packet */
+#define E1000_TXD_CMD_IFCS 0x02000000 /* Insert FCS (Ethernet CRC) */
+#define E1000_TXD_CMD_IC 0x04000000 /* Insert Checksum */
+#define E1000_TXD_CMD_RS 0x08000000 /* Report Status */
+#define E1000_TXD_CMD_RPS 0x10000000 /* Report Packet Sent */
+#define E1000_TXD_CMD_DEXT 0x20000000 /* Descriptor extension (0 = legacy) */
+#define E1000_TXD_CMD_VLE 0x40000000 /* Add VLAN tag */
+#define E1000_TXD_CMD_IDE 0x80000000 /* Enable Tidv register */
+#define E1000_TXD_STAT_DD 0x00000001 /* Descriptor Done */
+#define E1000_TXD_STAT_EC 0x00000002 /* Excess Collisions */
+#define E1000_TXD_STAT_LC 0x00000004 /* Late Collisions */
+#define E1000_TXD_STAT_TU 0x00000008 /* Transmit underrun */
+#define E1000_TXD_CMD_TCP 0x01000000 /* TCP packet */
+#define E1000_TXD_CMD_IP 0x02000000 /* IP packet */
+#define E1000_TXD_CMD_TSE 0x04000000 /* TCP Seg enable */
+#define E1000_TXD_STAT_TC 0x00000004 /* Tx Underrun */
+
+/* Transmit Control */
+#define E1000_TCTL_RST 0x00000001 /* software reset */
+#define E1000_TCTL_EN 0x00000002 /* enable tx */
+#define E1000_TCTL_BCE 0x00000004 /* busy check enable */
+#define E1000_TCTL_PSP 0x00000008 /* pad short packets */
+#define E1000_TCTL_CT 0x00000ff0 /* collision threshold */
+#define E1000_TCTL_COLD 0x003ff000 /* collision distance */
+#define E1000_TCTL_SWXOFF 0x00400000 /* SW Xoff transmission */
+#define E1000_TCTL_PBE 0x00800000 /* Packet Burst Enable */
+#define E1000_TCTL_RTLC 0x01000000 /* Re-transmit on late collision */
+#define E1000_TCTL_NRTU 0x02000000 /* No Re-transmit on underrun */
+#define E1000_TCTL_MULR 0x10000000 /* Multiple request support */
+
+/* Receive Descriptor */
+struct e1000_rx_desc {
+ uint64_t buffer_addr; /* Address of the descriptor's data buffer */
+ uint16_t length; /* Length of data DMAed into data buffer */
+ uint16_t csum; /* Packet checksum */
+ uint8_t status; /* Descriptor status */
+ uint8_t errors; /* Descriptor Errors */
+ uint16_t special;
+};
+
+/* Receive Descriptor bit definitions */
+#define E1000_RXD_STAT_DD 0x01 /* Descriptor Done */
+#define E1000_RXD_STAT_EOP 0x02 /* End of Packet */
+#define E1000_RXD_STAT_IXSM 0x04 /* Ignore checksum */
+#define E1000_RXD_STAT_VP 0x08 /* IEEE VLAN Packet */
+#define E1000_RXD_STAT_UDPCS 0x10 /* UDP xsum caculated */
+#define E1000_RXD_STAT_TCPCS 0x20 /* TCP xsum calculated */
+#define E1000_RXD_STAT_IPCS 0x40 /* IP xsum calculated */
+#define E1000_RXD_STAT_PIF 0x80 /* passed in-exact filter */
+#define E1000_RXD_STAT_IPIDV 0x200 /* IP identification valid */
+#define E1000_RXD_STAT_UDPV 0x400 /* Valid UDP checksum */
+#define E1000_RXD_STAT_ACK 0x8000 /* ACK Packet indication */
+#define E1000_RXD_ERR_CE 0x01 /* CRC Error */
+#define E1000_RXD_ERR_SE 0x02 /* Symbol Error */
+#define E1000_RXD_ERR_SEQ 0x04 /* Sequence Error */
+#define E1000_RXD_ERR_CXE 0x10 /* Carrier Extension Error */
+#define E1000_RXD_ERR_TCPE 0x20 /* TCP/UDP Checksum Error */
+#define E1000_RXD_ERR_IPE 0x40 /* IP Checksum Error */
+#define E1000_RXD_ERR_RXE 0x80 /* Rx Data Error */
+#define E1000_RXD_SPC_VLAN_MASK 0x0FFF /* VLAN ID is in lower 12 bits */
+#define E1000_RXD_SPC_PRI_MASK 0xE000 /* Priority is in upper 3 bits */
+#define E1000_RXD_SPC_PRI_SHIFT 13
+#define E1000_RXD_SPC_CFI_MASK 0x1000 /* CFI is bit 12 */
+#define E1000_RXD_SPC_CFI_SHIFT 12
+
+#define E1000_RXDEXT_STATERR_CE 0x01000000
+#define E1000_RXDEXT_STATERR_SE 0x02000000
+#define E1000_RXDEXT_STATERR_SEQ 0x04000000
+#define E1000_RXDEXT_STATERR_CXE 0x10000000
+#define E1000_RXDEXT_STATERR_TCPE 0x20000000
+#define E1000_RXDEXT_STATERR_IPE 0x40000000
+#define E1000_RXDEXT_STATERR_RXE 0x80000000
+
+#define E1000_RXDPS_HDRSTAT_HDRSP 0x00008000
+#define E1000_RXDPS_HDRSTAT_HDRLEN_MASK 0x000003FF
+
+/* Receive Address */
+#define E1000_RAH_AV 0x80000000 /* Receive descriptor valid */
+
+/* Offload Context Descriptor */
+struct e1000_context_desc {
+ union {
+ uint32_t ip_config;
+ struct {
+ uint8_t ipcss; /* IP checksum start */
+ uint8_t ipcso; /* IP checksum offset */
+ uint16_t ipcse; /* IP checksum end */
+ } ip_fields;
+ } lower_setup;
+ union {
+ uint32_t tcp_config;
+ struct {
+ uint8_t tucss; /* TCP checksum start */
+ uint8_t tucso; /* TCP checksum offset */
+ uint16_t tucse; /* TCP checksum end */
+ } tcp_fields;
+ } upper_setup;
+ uint32_t cmd_and_length; /* */
+ union {
+ uint32_t data;
+ struct {
+ uint8_t status; /* Descriptor status */
+ uint8_t hdr_len; /* Header length */
+ uint16_t mss; /* Maximum segment size */
+ } fields;
+ } tcp_seg_setup;
+};
+
+/* Offload data descriptor */
+struct e1000_data_desc {
+ uint64_t buffer_addr; /* Address of the descriptor's buffer address */
+ union {
+ uint32_t data;
+ struct {
+ uint16_t length; /* Data buffer length */
+ uint8_t typ_len_ext; /* */
+ uint8_t cmd; /* */
+ } flags;
+ } lower;
+ union {
+ uint32_t data;
+ struct {
+ uint8_t status; /* Descriptor status */
+ uint8_t popts; /* Packet Options */
+ uint16_t special; /* */
+ } fields;
+ } upper;
+};
+
+/* Management Control */
+#define E1000_MANC_SMBUS_EN 0x00000001 /* SMBus Enabled - RO */
+#define E1000_MANC_ASF_EN 0x00000002 /* ASF Enabled - RO */
+#define E1000_MANC_R_ON_FORCE 0x00000004 /* Reset on Force TCO - RO */
+#define E1000_MANC_RMCP_EN 0x00000100 /* Enable RCMP 026Fh Filtering */
+#define E1000_MANC_0298_EN 0x00000200 /* Enable RCMP 0298h Filtering */
+#define E1000_MANC_IPV4_EN 0x00000400 /* Enable IPv4 */
+#define E1000_MANC_IPV6_EN 0x00000800 /* Enable IPv6 */
+#define E1000_MANC_SNAP_EN 0x00001000 /* Accept LLC/SNAP */
+#define E1000_MANC_ARP_EN 0x00002000 /* Enable ARP Request Filtering */
+#define E1000_MANC_NEIGHBOR_EN 0x00004000 /* Enable Neighbor Discovery
+ * Filtering */
+#define E1000_MANC_ARP_RES_EN 0x00008000 /* Enable ARP response Filtering */
+#define E1000_MANC_TCO_RESET 0x00010000 /* TCO Reset Occurred */
+#define E1000_MANC_RCV_TCO_EN 0x00020000 /* Receive TCO Packets Enabled */
+#define E1000_MANC_REPORT_STATUS 0x00040000 /* Status Reporting Enabled */
+#define E1000_MANC_RCV_ALL 0x00080000 /* Receive All Enabled */
+#define E1000_MANC_BLK_PHY_RST_ON_IDE 0x00040000 /* Block phy resets */
+#define E1000_MANC_EN_MAC_ADDR_FILTER 0x00100000 /* Enable MAC address
+ * filtering */
+#define E1000_MANC_EN_MNG2HOST 0x00200000 /* Enable MNG packets to host
+ * memory */
+#define E1000_MANC_EN_IP_ADDR_FILTER 0x00400000 /* Enable IP address
+ * filtering */
+#define E1000_MANC_EN_XSUM_FILTER 0x00800000 /* Enable checksum filtering */
+#define E1000_MANC_BR_EN 0x01000000 /* Enable broadcast filtering */
+#define E1000_MANC_SMB_REQ 0x01000000 /* SMBus Request */
+#define E1000_MANC_SMB_GNT 0x02000000 /* SMBus Grant */
+#define E1000_MANC_SMB_CLK_IN 0x04000000 /* SMBus Clock In */
+#define E1000_MANC_SMB_DATA_IN 0x08000000 /* SMBus Data In */
+#define E1000_MANC_SMB_DATA_OUT 0x10000000 /* SMBus Data Out */
+#define E1000_MANC_SMB_CLK_OUT 0x20000000 /* SMBus Clock Out */
+
+#define E1000_MANC_SMB_DATA_OUT_SHIFT 28 /* SMBus Data Out Shift */
+#define E1000_MANC_SMB_CLK_OUT_SHIFT 29 /* SMBus Clock Out Shift */
+
+/* For checksumming, the sum of all words in the EEPROM should equal 0xBABA. */
+#define EEPROM_SUM 0xBABA
+
+#endif /* _E1000_HW_H_ */
diff --git a/hw/net/eepro100.c b/hw/net/eepro100.c
new file mode 100644
index 00000000..685a4781
--- /dev/null
+++ b/hw/net/eepro100.c
@@ -0,0 +1,2114 @@
+/*
+ * QEMU i8255x (PRO100) emulation
+ *
+ * Copyright (C) 2006-2011 Stefan Weil
+ *
+ * Portions of the code are copies from grub / etherboot eepro100.c
+ * and linux e100.c.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) version 3 or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Tested features (i82559):
+ * PXE boot (i386 guest, i386 / mips / mipsel / ppc host) ok
+ * Linux networking (i386) ok
+ *
+ * Untested:
+ * Windows networking
+ *
+ * References:
+ *
+ * Intel 8255x 10/100 Mbps Ethernet Controller Family
+ * Open Source Software Developer Manual
+ *
+ * TODO:
+ * * PHY emulation should be separated from nic emulation.
+ * Most nic emulations could share the same phy code.
+ * * i82550 is untested. It is programmed like the i82559.
+ * * i82562 is untested. It is programmed like the i82559.
+ * * Power management (i82558 and later) is not implemented.
+ * * Wake-on-LAN is not implemented.
+ */
+
+#include <stddef.h> /* offsetof */
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "net/net.h"
+#include "hw/nvram/eeprom93xx.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/dma.h"
+#include "qemu/bitops.h"
+
+/* QEMU sends frames smaller than 60 bytes to ethernet nics.
+ * Such frames are rejected by real nics and their emulations.
+ * To avoid this behaviour, other nic emulations pad received
+ * frames. The following definition enables this padding for
+ * eepro100, too. We keep the define around in case it might
+ * become useful the future if the core networking is ever
+ * changed to pad short packets itself. */
+#define CONFIG_PAD_RECEIVED_FRAMES
+
+#define KiB 1024
+
+/* Debug EEPRO100 card. */
+#if 0
+# define DEBUG_EEPRO100
+#endif
+
+#ifdef DEBUG_EEPRO100
+#define logout(fmt, ...) fprintf(stderr, "EE100\t%-24s" fmt, __func__, ## __VA_ARGS__)
+#else
+#define logout(fmt, ...) ((void)0)
+#endif
+
+/* Set flags to 0 to disable debug output. */
+#define INT 1 /* interrupt related actions */
+#define MDI 1 /* mdi related actions */
+#define OTHER 1
+#define RXTX 1
+#define EEPROM 1 /* eeprom related actions */
+
+#define TRACE(flag, command) ((flag) ? (command) : (void)0)
+
+#define missing(text) fprintf(stderr, "eepro100: feature is missing in this emulation: " text "\n")
+
+#define MAX_ETH_FRAME_SIZE 1514
+
+/* This driver supports several different devices which are declared here. */
+#define i82550 0x82550
+#define i82551 0x82551
+#define i82557A 0x82557a
+#define i82557B 0x82557b
+#define i82557C 0x82557c
+#define i82558A 0x82558a
+#define i82558B 0x82558b
+#define i82559A 0x82559a
+#define i82559B 0x82559b
+#define i82559C 0x82559c
+#define i82559ER 0x82559e
+#define i82562 0x82562
+#define i82801 0x82801
+
+/* Use 64 word EEPROM. TODO: could be a runtime option. */
+#define EEPROM_SIZE 64
+
+#define PCI_MEM_SIZE (4 * KiB)
+#define PCI_IO_SIZE 64
+#define PCI_FLASH_SIZE (128 * KiB)
+
+#define BITS(n, m) (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m)
+
+/* The SCB accepts the following controls for the Tx and Rx units: */
+#define CU_NOP 0x0000 /* No operation. */
+#define CU_START 0x0010 /* CU start. */
+#define CU_RESUME 0x0020 /* CU resume. */
+#define CU_STATSADDR 0x0040 /* Load dump counters address. */
+#define CU_SHOWSTATS 0x0050 /* Dump statistical counters. */
+#define CU_CMD_BASE 0x0060 /* Load CU base address. */
+#define CU_DUMPSTATS 0x0070 /* Dump and reset statistical counters. */
+#define CU_SRESUME 0x00a0 /* CU static resume. */
+
+#define RU_NOP 0x0000
+#define RX_START 0x0001
+#define RX_RESUME 0x0002
+#define RU_ABORT 0x0004
+#define RX_ADDR_LOAD 0x0006
+#define RX_RESUMENR 0x0007
+#define INT_MASK 0x0100
+#define DRVR_INT 0x0200 /* Driver generated interrupt. */
+
+typedef struct {
+ const char *name;
+ const char *desc;
+ uint16_t device_id;
+ uint8_t revision;
+ uint16_t subsystem_vendor_id;
+ uint16_t subsystem_id;
+
+ uint32_t device;
+ uint8_t stats_size;
+ bool has_extended_tcb_support;
+ bool power_management;
+} E100PCIDeviceInfo;
+
+/* Offsets to the various registers.
+ All accesses need not be longword aligned. */
+typedef enum {
+ SCBStatus = 0, /* Status Word. */
+ SCBAck = 1,
+ SCBCmd = 2, /* Rx/Command Unit command and status. */
+ SCBIntmask = 3,
+ SCBPointer = 4, /* General purpose pointer. */
+ SCBPort = 8, /* Misc. commands and operands. */
+ SCBflash = 12, /* Flash memory control. */
+ SCBeeprom = 14, /* EEPROM control. */
+ SCBCtrlMDI = 16, /* MDI interface control. */
+ SCBEarlyRx = 20, /* Early receive byte count. */
+ SCBFlow = 24, /* Flow Control. */
+ SCBpmdr = 27, /* Power Management Driver. */
+ SCBgctrl = 28, /* General Control. */
+ SCBgstat = 29, /* General Status. */
+} E100RegisterOffset;
+
+/* A speedo3 transmit buffer descriptor with two buffers... */
+typedef struct {
+ uint16_t status;
+ uint16_t command;
+ uint32_t link; /* void * */
+ uint32_t tbd_array_addr; /* transmit buffer descriptor array address. */
+ uint16_t tcb_bytes; /* transmit command block byte count (in lower 14 bits */
+ uint8_t tx_threshold; /* transmit threshold */
+ uint8_t tbd_count; /* TBD number */
+#if 0
+ /* This constitutes two "TBD" entries: hdr and data */
+ uint32_t tx_buf_addr0; /* void *, header of frame to be transmitted. */
+ int32_t tx_buf_size0; /* Length of Tx hdr. */
+ uint32_t tx_buf_addr1; /* void *, data to be transmitted. */
+ int32_t tx_buf_size1; /* Length of Tx data. */
+#endif
+} eepro100_tx_t;
+
+/* Receive frame descriptor. */
+typedef struct {
+ int16_t status;
+ uint16_t command;
+ uint32_t link; /* struct RxFD * */
+ uint32_t rx_buf_addr; /* void * */
+ uint16_t count;
+ uint16_t size;
+ /* Ethernet frame data follows. */
+} eepro100_rx_t;
+
+typedef enum {
+ COMMAND_EL = BIT(15),
+ COMMAND_S = BIT(14),
+ COMMAND_I = BIT(13),
+ COMMAND_NC = BIT(4),
+ COMMAND_SF = BIT(3),
+ COMMAND_CMD = BITS(2, 0),
+} scb_command_bit;
+
+typedef enum {
+ STATUS_C = BIT(15),
+ STATUS_OK = BIT(13),
+} scb_status_bit;
+
+typedef struct {
+ uint32_t tx_good_frames, tx_max_collisions, tx_late_collisions,
+ tx_underruns, tx_lost_crs, tx_deferred, tx_single_collisions,
+ tx_multiple_collisions, tx_total_collisions;
+ uint32_t rx_good_frames, rx_crc_errors, rx_alignment_errors,
+ rx_resource_errors, rx_overrun_errors, rx_cdt_errors,
+ rx_short_frame_errors;
+ uint32_t fc_xmt_pause, fc_rcv_pause, fc_rcv_unsupported;
+ uint16_t xmt_tco_frames, rcv_tco_frames;
+ /* TODO: i82559 has six reserved statistics but a total of 24 dwords. */
+ uint32_t reserved[4];
+} eepro100_stats_t;
+
+typedef enum {
+ cu_idle = 0,
+ cu_suspended = 1,
+ cu_active = 2,
+ cu_lpq_active = 2,
+ cu_hqp_active = 3
+} cu_state_t;
+
+typedef enum {
+ ru_idle = 0,
+ ru_suspended = 1,
+ ru_no_resources = 2,
+ ru_ready = 4
+} ru_state_t;
+
+typedef struct {
+ PCIDevice dev;
+ /* Hash register (multicast mask array, multiple individual addresses). */
+ uint8_t mult[8];
+ MemoryRegion mmio_bar;
+ MemoryRegion io_bar;
+ MemoryRegion flash_bar;
+ NICState *nic;
+ NICConf conf;
+ uint8_t scb_stat; /* SCB stat/ack byte */
+ uint8_t int_stat; /* PCI interrupt status */
+ /* region must not be saved by nic_save. */
+ uint16_t mdimem[32];
+ eeprom_t *eeprom;
+ uint32_t device; /* device variant */
+ /* (cu_base + cu_offset) address the next command block in the command block list. */
+ uint32_t cu_base; /* CU base address */
+ uint32_t cu_offset; /* CU address offset */
+ /* (ru_base + ru_offset) address the RFD in the Receive Frame Area. */
+ uint32_t ru_base; /* RU base address */
+ uint32_t ru_offset; /* RU address offset */
+ uint32_t statsaddr; /* pointer to eepro100_stats_t */
+
+ /* Temporary status information (no need to save these values),
+ * used while processing CU commands. */
+ eepro100_tx_t tx; /* transmit buffer descriptor */
+ uint32_t cb_address; /* = cu_base + cu_offset */
+
+ /* Statistical counters. Also used for wake-up packet (i82559). */
+ eepro100_stats_t statistics;
+
+ /* Data in mem is always in the byte order of the controller (le).
+ * It must be dword aligned to allow direct access to 32 bit values. */
+ uint8_t mem[PCI_MEM_SIZE] __attribute__((aligned(8)));
+
+ /* Configuration bytes. */
+ uint8_t configuration[22];
+
+ /* vmstate for each particular nic */
+ VMStateDescription *vmstate;
+
+ /* Quasi static device properties (no need to save them). */
+ uint16_t stats_size;
+ bool has_extended_tcb_support;
+} EEPRO100State;
+
+/* Word indices in EEPROM. */
+typedef enum {
+ EEPROM_CNFG_MDIX = 0x03,
+ EEPROM_ID = 0x05,
+ EEPROM_PHY_ID = 0x06,
+ EEPROM_VENDOR_ID = 0x0c,
+ EEPROM_CONFIG_ASF = 0x0d,
+ EEPROM_DEVICE_ID = 0x23,
+ EEPROM_SMBUS_ADDR = 0x90,
+} EEPROMOffset;
+
+/* Bit values for EEPROM ID word. */
+typedef enum {
+ EEPROM_ID_MDM = BIT(0), /* Modem */
+ EEPROM_ID_STB = BIT(1), /* Standby Enable */
+ EEPROM_ID_WMR = BIT(2), /* ??? */
+ EEPROM_ID_WOL = BIT(5), /* Wake on LAN */
+ EEPROM_ID_DPD = BIT(6), /* Deep Power Down */
+ EEPROM_ID_ALT = BIT(7), /* */
+ /* BITS(10, 8) device revision */
+ EEPROM_ID_BD = BIT(11), /* boot disable */
+ EEPROM_ID_ID = BIT(13), /* id bit */
+ /* BITS(15, 14) signature */
+ EEPROM_ID_VALID = BIT(14), /* signature for valid eeprom */
+} eeprom_id_bit;
+
+/* Default values for MDI (PHY) registers */
+static const uint16_t eepro100_mdi_default[] = {
+ /* MDI Registers 0 - 6, 7 */
+ 0x3000, 0x780d, 0x02a8, 0x0154, 0x05e1, 0x0000, 0x0000, 0x0000,
+ /* MDI Registers 8 - 15 */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* MDI Registers 16 - 31 */
+ 0x0003, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+};
+
+/* Readonly mask for MDI (PHY) registers */
+static const uint16_t eepro100_mdi_mask[] = {
+ 0x0000, 0xffff, 0xffff, 0xffff, 0xc01f, 0xffff, 0xffff, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0fff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+};
+
+#define POLYNOMIAL 0x04c11db6
+
+static E100PCIDeviceInfo *eepro100_get_class(EEPRO100State *s);
+
+/* From FreeBSD (locally modified). */
+static unsigned e100_compute_mcast_idx(const uint8_t *ep)
+{
+ uint32_t crc;
+ int carry, i, j;
+ uint8_t b;
+
+ crc = 0xffffffff;
+ for (i = 0; i < 6; i++) {
+ b = *ep++;
+ for (j = 0; j < 8; j++) {
+ carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01);
+ crc <<= 1;
+ b >>= 1;
+ if (carry) {
+ crc = ((crc ^ POLYNOMIAL) | carry);
+ }
+ }
+ }
+ return (crc & BITS(7, 2)) >> 2;
+}
+
+/* Read a 16 bit control/status (CSR) register. */
+static uint16_t e100_read_reg2(EEPRO100State *s, E100RegisterOffset addr)
+{
+ assert(!((uintptr_t)&s->mem[addr] & 1));
+ return le16_to_cpup((uint16_t *)&s->mem[addr]);
+}
+
+/* Read a 32 bit control/status (CSR) register. */
+static uint32_t e100_read_reg4(EEPRO100State *s, E100RegisterOffset addr)
+{
+ assert(!((uintptr_t)&s->mem[addr] & 3));
+ return le32_to_cpup((uint32_t *)&s->mem[addr]);
+}
+
+/* Write a 16 bit control/status (CSR) register. */
+static void e100_write_reg2(EEPRO100State *s, E100RegisterOffset addr,
+ uint16_t val)
+{
+ assert(!((uintptr_t)&s->mem[addr] & 1));
+ cpu_to_le16w((uint16_t *)&s->mem[addr], val);
+}
+
+/* Read a 32 bit control/status (CSR) register. */
+static void e100_write_reg4(EEPRO100State *s, E100RegisterOffset addr,
+ uint32_t val)
+{
+ assert(!((uintptr_t)&s->mem[addr] & 3));
+ cpu_to_le32w((uint32_t *)&s->mem[addr], val);
+}
+
+#if defined(DEBUG_EEPRO100)
+static const char *nic_dump(const uint8_t * buf, unsigned size)
+{
+ static char dump[3 * 16 + 1];
+ char *p = &dump[0];
+ if (size > 16) {
+ size = 16;
+ }
+ while (size-- > 0) {
+ p += sprintf(p, " %02x", *buf++);
+ }
+ return dump;
+}
+#endif /* DEBUG_EEPRO100 */
+
+enum scb_stat_ack {
+ stat_ack_not_ours = 0x00,
+ stat_ack_sw_gen = 0x04,
+ stat_ack_rnr = 0x10,
+ stat_ack_cu_idle = 0x20,
+ stat_ack_frame_rx = 0x40,
+ stat_ack_cu_cmd_done = 0x80,
+ stat_ack_not_present = 0xFF,
+ stat_ack_rx = (stat_ack_sw_gen | stat_ack_rnr | stat_ack_frame_rx),
+ stat_ack_tx = (stat_ack_cu_idle | stat_ack_cu_cmd_done),
+};
+
+static void disable_interrupt(EEPRO100State * s)
+{
+ if (s->int_stat) {
+ TRACE(INT, logout("interrupt disabled\n"));
+ pci_irq_deassert(&s->dev);
+ s->int_stat = 0;
+ }
+}
+
+static void enable_interrupt(EEPRO100State * s)
+{
+ if (!s->int_stat) {
+ TRACE(INT, logout("interrupt enabled\n"));
+ pci_irq_assert(&s->dev);
+ s->int_stat = 1;
+ }
+}
+
+static void eepro100_acknowledge(EEPRO100State * s)
+{
+ s->scb_stat &= ~s->mem[SCBAck];
+ s->mem[SCBAck] = s->scb_stat;
+ if (s->scb_stat == 0) {
+ disable_interrupt(s);
+ }
+}
+
+static void eepro100_interrupt(EEPRO100State * s, uint8_t status)
+{
+ uint8_t mask = ~s->mem[SCBIntmask];
+ s->mem[SCBAck] |= status;
+ status = s->scb_stat = s->mem[SCBAck];
+ status &= (mask | 0x0f);
+#if 0
+ status &= (~s->mem[SCBIntmask] | 0x0xf);
+#endif
+ if (status && (mask & 0x01)) {
+ /* SCB mask and SCB Bit M do not disable interrupt. */
+ enable_interrupt(s);
+ } else if (s->int_stat) {
+ disable_interrupt(s);
+ }
+}
+
+static void eepro100_cx_interrupt(EEPRO100State * s)
+{
+ /* CU completed action command. */
+ /* Transmit not ok (82557 only, not in emulation). */
+ eepro100_interrupt(s, 0x80);
+}
+
+static void eepro100_cna_interrupt(EEPRO100State * s)
+{
+ /* CU left the active state. */
+ eepro100_interrupt(s, 0x20);
+}
+
+static void eepro100_fr_interrupt(EEPRO100State * s)
+{
+ /* RU received a complete frame. */
+ eepro100_interrupt(s, 0x40);
+}
+
+static void eepro100_rnr_interrupt(EEPRO100State * s)
+{
+ /* RU is not ready. */
+ eepro100_interrupt(s, 0x10);
+}
+
+static void eepro100_mdi_interrupt(EEPRO100State * s)
+{
+ /* MDI completed read or write cycle. */
+ eepro100_interrupt(s, 0x08);
+}
+
+static void eepro100_swi_interrupt(EEPRO100State * s)
+{
+ /* Software has requested an interrupt. */
+ eepro100_interrupt(s, 0x04);
+}
+
+#if 0
+static void eepro100_fcp_interrupt(EEPRO100State * s)
+{
+ /* Flow control pause interrupt (82558 and later). */
+ eepro100_interrupt(s, 0x01);
+}
+#endif
+
+static void e100_pci_reset(EEPRO100State * s)
+{
+ E100PCIDeviceInfo *info = eepro100_get_class(s);
+ uint32_t device = s->device;
+ uint8_t *pci_conf = s->dev.config;
+
+ TRACE(OTHER, logout("%p\n", s));
+
+ /* PCI Status */
+ pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_DEVSEL_MEDIUM |
+ PCI_STATUS_FAST_BACK);
+ /* PCI Latency Timer */
+ pci_set_byte(pci_conf + PCI_LATENCY_TIMER, 0x20); /* latency timer = 32 clocks */
+ /* Capability Pointer is set by PCI framework. */
+ /* Interrupt Line */
+ /* Interrupt Pin */
+ pci_set_byte(pci_conf + PCI_INTERRUPT_PIN, 1); /* interrupt pin A */
+ /* Minimum Grant */
+ pci_set_byte(pci_conf + PCI_MIN_GNT, 0x08);
+ /* Maximum Latency */
+ pci_set_byte(pci_conf + PCI_MAX_LAT, 0x18);
+
+ s->stats_size = info->stats_size;
+ s->has_extended_tcb_support = info->has_extended_tcb_support;
+
+ switch (device) {
+ case i82550:
+ case i82551:
+ case i82557A:
+ case i82557B:
+ case i82557C:
+ case i82558A:
+ case i82558B:
+ case i82559A:
+ case i82559B:
+ case i82559ER:
+ case i82562:
+ case i82801:
+ case i82559C:
+ break;
+ default:
+ logout("Device %X is undefined!\n", device);
+ }
+
+ /* Standard TxCB. */
+ s->configuration[6] |= BIT(4);
+
+ /* Standard statistical counters. */
+ s->configuration[6] |= BIT(5);
+
+ if (s->stats_size == 80) {
+ /* TODO: check TCO Statistical Counters bit. Documentation not clear. */
+ if (s->configuration[6] & BIT(2)) {
+ /* TCO statistical counters. */
+ assert(s->configuration[6] & BIT(5));
+ } else {
+ if (s->configuration[6] & BIT(5)) {
+ /* No extended statistical counters, i82557 compatible. */
+ s->stats_size = 64;
+ } else {
+ /* i82558 compatible. */
+ s->stats_size = 76;
+ }
+ }
+ } else {
+ if (s->configuration[6] & BIT(5)) {
+ /* No extended statistical counters. */
+ s->stats_size = 64;
+ }
+ }
+ assert(s->stats_size > 0 && s->stats_size <= sizeof(s->statistics));
+
+ if (info->power_management) {
+ /* Power Management Capabilities */
+ int cfg_offset = 0xdc;
+ int r = pci_add_capability(&s->dev, PCI_CAP_ID_PM,
+ cfg_offset, PCI_PM_SIZEOF);
+ assert(r >= 0);
+ pci_set_word(pci_conf + cfg_offset + PCI_PM_PMC, 0x7e21);
+#if 0 /* TODO: replace dummy code for power management emulation. */
+ /* TODO: Power Management Control / Status. */
+ pci_set_word(pci_conf + cfg_offset + PCI_PM_CTRL, 0x0000);
+ /* TODO: Ethernet Power Consumption Registers (i82559 and later). */
+ pci_set_byte(pci_conf + cfg_offset + PCI_PM_PPB_EXTENSIONS, 0x0000);
+#endif
+ }
+
+#if EEPROM_SIZE > 0
+ if (device == i82557C || device == i82558B || device == i82559C) {
+ /*
+ TODO: get vendor id from EEPROM for i82557C or later.
+ TODO: get device id from EEPROM for i82557C or later.
+ TODO: status bit 4 can be disabled by EEPROM for i82558, i82559.
+ TODO: header type is determined by EEPROM for i82559.
+ TODO: get subsystem id from EEPROM for i82557C or later.
+ TODO: get subsystem vendor id from EEPROM for i82557C or later.
+ TODO: exp. rom baddr depends on a bit in EEPROM for i82558 or later.
+ TODO: capability pointer depends on EEPROM for i82558.
+ */
+ logout("Get device id and revision from EEPROM!!!\n");
+ }
+#endif /* EEPROM_SIZE > 0 */
+}
+
+static void nic_selective_reset(EEPRO100State * s)
+{
+ size_t i;
+ uint16_t *eeprom_contents = eeprom93xx_data(s->eeprom);
+#if 0
+ eeprom93xx_reset(s->eeprom);
+#endif
+ memcpy(eeprom_contents, s->conf.macaddr.a, 6);
+ eeprom_contents[EEPROM_ID] = EEPROM_ID_VALID;
+ if (s->device == i82557B || s->device == i82557C)
+ eeprom_contents[5] = 0x0100;
+ eeprom_contents[EEPROM_PHY_ID] = 1;
+ uint16_t sum = 0;
+ for (i = 0; i < EEPROM_SIZE - 1; i++) {
+ sum += eeprom_contents[i];
+ }
+ eeprom_contents[EEPROM_SIZE - 1] = 0xbaba - sum;
+ TRACE(EEPROM, logout("checksum=0x%04x\n", eeprom_contents[EEPROM_SIZE - 1]));
+
+ memset(s->mem, 0, sizeof(s->mem));
+ e100_write_reg4(s, SCBCtrlMDI, BIT(21));
+
+ assert(sizeof(s->mdimem) == sizeof(eepro100_mdi_default));
+ memcpy(&s->mdimem[0], &eepro100_mdi_default[0], sizeof(s->mdimem));
+}
+
+static void nic_reset(void *opaque)
+{
+ EEPRO100State *s = opaque;
+ TRACE(OTHER, logout("%p\n", s));
+ /* TODO: Clearing of hash register for selective reset, too? */
+ memset(&s->mult[0], 0, sizeof(s->mult));
+ nic_selective_reset(s);
+}
+
+#if defined(DEBUG_EEPRO100)
+static const char * const e100_reg[PCI_IO_SIZE / 4] = {
+ "Command/Status",
+ "General Pointer",
+ "Port",
+ "EEPROM/Flash Control",
+ "MDI Control",
+ "Receive DMA Byte Count",
+ "Flow Control",
+ "General Status/Control"
+};
+
+static char *regname(uint32_t addr)
+{
+ static char buf[32];
+ if (addr < PCI_IO_SIZE) {
+ const char *r = e100_reg[addr / 4];
+ if (r != 0) {
+ snprintf(buf, sizeof(buf), "%s+%u", r, addr % 4);
+ } else {
+ snprintf(buf, sizeof(buf), "0x%02x", addr);
+ }
+ } else {
+ snprintf(buf, sizeof(buf), "??? 0x%08x", addr);
+ }
+ return buf;
+}
+#endif /* DEBUG_EEPRO100 */
+
+/*****************************************************************************
+ *
+ * Command emulation.
+ *
+ ****************************************************************************/
+
+#if 0
+static uint16_t eepro100_read_command(EEPRO100State * s)
+{
+ uint16_t val = 0xffff;
+ TRACE(OTHER, logout("val=0x%04x\n", val));
+ return val;
+}
+#endif
+
+/* Commands that can be put in a command list entry. */
+enum commands {
+ CmdNOp = 0,
+ CmdIASetup = 1,
+ CmdConfigure = 2,
+ CmdMulticastList = 3,
+ CmdTx = 4,
+ CmdTDR = 5, /* load microcode */
+ CmdDump = 6,
+ CmdDiagnose = 7,
+
+ /* And some extra flags: */
+ CmdSuspend = 0x4000, /* Suspend after completion. */
+ CmdIntr = 0x2000, /* Interrupt after completion. */
+ CmdTxFlex = 0x0008, /* Use "Flexible mode" for CmdTx command. */
+};
+
+static cu_state_t get_cu_state(EEPRO100State * s)
+{
+ return ((s->mem[SCBStatus] & BITS(7, 6)) >> 6);
+}
+
+static void set_cu_state(EEPRO100State * s, cu_state_t state)
+{
+ s->mem[SCBStatus] = (s->mem[SCBStatus] & ~BITS(7, 6)) + (state << 6);
+}
+
+static ru_state_t get_ru_state(EEPRO100State * s)
+{
+ return ((s->mem[SCBStatus] & BITS(5, 2)) >> 2);
+}
+
+static void set_ru_state(EEPRO100State * s, ru_state_t state)
+{
+ s->mem[SCBStatus] = (s->mem[SCBStatus] & ~BITS(5, 2)) + (state << 2);
+}
+
+static void dump_statistics(EEPRO100State * s)
+{
+ /* Dump statistical data. Most data is never changed by the emulation
+ * and always 0, so we first just copy the whole block and then those
+ * values which really matter.
+ * Number of data should check configuration!!!
+ */
+ pci_dma_write(&s->dev, s->statsaddr, &s->statistics, s->stats_size);
+ stl_le_pci_dma(&s->dev, s->statsaddr + 0,
+ s->statistics.tx_good_frames);
+ stl_le_pci_dma(&s->dev, s->statsaddr + 36,
+ s->statistics.rx_good_frames);
+ stl_le_pci_dma(&s->dev, s->statsaddr + 48,
+ s->statistics.rx_resource_errors);
+ stl_le_pci_dma(&s->dev, s->statsaddr + 60,
+ s->statistics.rx_short_frame_errors);
+#if 0
+ stw_le_pci_dma(&s->dev, s->statsaddr + 76, s->statistics.xmt_tco_frames);
+ stw_le_pci_dma(&s->dev, s->statsaddr + 78, s->statistics.rcv_tco_frames);
+ missing("CU dump statistical counters");
+#endif
+}
+
+static void read_cb(EEPRO100State *s)
+{
+ pci_dma_read(&s->dev, s->cb_address, &s->tx, sizeof(s->tx));
+ s->tx.status = le16_to_cpu(s->tx.status);
+ s->tx.command = le16_to_cpu(s->tx.command);
+ s->tx.link = le32_to_cpu(s->tx.link);
+ s->tx.tbd_array_addr = le32_to_cpu(s->tx.tbd_array_addr);
+ s->tx.tcb_bytes = le16_to_cpu(s->tx.tcb_bytes);
+}
+
+static void tx_command(EEPRO100State *s)
+{
+ uint32_t tbd_array = le32_to_cpu(s->tx.tbd_array_addr);
+ uint16_t tcb_bytes = (le16_to_cpu(s->tx.tcb_bytes) & 0x3fff);
+ /* Sends larger than MAX_ETH_FRAME_SIZE are allowed, up to 2600 bytes. */
+ uint8_t buf[2600];
+ uint16_t size = 0;
+ uint32_t tbd_address = s->cb_address + 0x10;
+ TRACE(RXTX, logout
+ ("transmit, TBD array address 0x%08x, TCB byte count 0x%04x, TBD count %u\n",
+ tbd_array, tcb_bytes, s->tx.tbd_count));
+
+ if (tcb_bytes > 2600) {
+ logout("TCB byte count too large, using 2600\n");
+ tcb_bytes = 2600;
+ }
+ if (!((tcb_bytes > 0) || (tbd_array != 0xffffffff))) {
+ logout
+ ("illegal values of TBD array address and TCB byte count!\n");
+ }
+ assert(tcb_bytes <= sizeof(buf));
+ while (size < tcb_bytes) {
+ uint32_t tx_buffer_address = ldl_le_pci_dma(&s->dev, tbd_address);
+ uint16_t tx_buffer_size = lduw_le_pci_dma(&s->dev, tbd_address + 4);
+#if 0
+ uint16_t tx_buffer_el = lduw_le_pci_dma(&s->dev, tbd_address + 6);
+#endif
+ if (tx_buffer_size == 0) {
+ /* Prevent an endless loop. */
+ logout("loop in %s:%u\n", __FILE__, __LINE__);
+ break;
+ }
+ tbd_address += 8;
+ TRACE(RXTX, logout
+ ("TBD (simplified mode): buffer address 0x%08x, size 0x%04x\n",
+ tx_buffer_address, tx_buffer_size));
+ tx_buffer_size = MIN(tx_buffer_size, sizeof(buf) - size);
+ pci_dma_read(&s->dev, tx_buffer_address, &buf[size], tx_buffer_size);
+ size += tx_buffer_size;
+ }
+ if (tbd_array == 0xffffffff) {
+ /* Simplified mode. Was already handled by code above. */
+ } else {
+ /* Flexible mode. */
+ uint8_t tbd_count = 0;
+ if (s->has_extended_tcb_support && !(s->configuration[6] & BIT(4))) {
+ /* Extended Flexible TCB. */
+ for (; tbd_count < 2; tbd_count++) {
+ uint32_t tx_buffer_address = ldl_le_pci_dma(&s->dev,
+ tbd_address);
+ uint16_t tx_buffer_size = lduw_le_pci_dma(&s->dev,
+ tbd_address + 4);
+ uint16_t tx_buffer_el = lduw_le_pci_dma(&s->dev,
+ tbd_address + 6);
+ tbd_address += 8;
+ TRACE(RXTX, logout
+ ("TBD (extended flexible mode): buffer address 0x%08x, size 0x%04x\n",
+ tx_buffer_address, tx_buffer_size));
+ tx_buffer_size = MIN(tx_buffer_size, sizeof(buf) - size);
+ pci_dma_read(&s->dev, tx_buffer_address,
+ &buf[size], tx_buffer_size);
+ size += tx_buffer_size;
+ if (tx_buffer_el & 1) {
+ break;
+ }
+ }
+ }
+ tbd_address = tbd_array;
+ for (; tbd_count < s->tx.tbd_count; tbd_count++) {
+ uint32_t tx_buffer_address = ldl_le_pci_dma(&s->dev, tbd_address);
+ uint16_t tx_buffer_size = lduw_le_pci_dma(&s->dev, tbd_address + 4);
+ uint16_t tx_buffer_el = lduw_le_pci_dma(&s->dev, tbd_address + 6);
+ tbd_address += 8;
+ TRACE(RXTX, logout
+ ("TBD (flexible mode): buffer address 0x%08x, size 0x%04x\n",
+ tx_buffer_address, tx_buffer_size));
+ tx_buffer_size = MIN(tx_buffer_size, sizeof(buf) - size);
+ pci_dma_read(&s->dev, tx_buffer_address,
+ &buf[size], tx_buffer_size);
+ size += tx_buffer_size;
+ if (tx_buffer_el & 1) {
+ break;
+ }
+ }
+ }
+ TRACE(RXTX, logout("%p sending frame, len=%d,%s\n", s, size, nic_dump(buf, size)));
+ qemu_send_packet(qemu_get_queue(s->nic), buf, size);
+ s->statistics.tx_good_frames++;
+ /* Transmit with bad status would raise an CX/TNO interrupt.
+ * (82557 only). Emulation never has bad status. */
+#if 0
+ eepro100_cx_interrupt(s);
+#endif
+}
+
+static void set_multicast_list(EEPRO100State *s)
+{
+ uint16_t multicast_count = s->tx.tbd_array_addr & BITS(13, 0);
+ uint16_t i;
+ memset(&s->mult[0], 0, sizeof(s->mult));
+ TRACE(OTHER, logout("multicast list, multicast count = %u\n", multicast_count));
+ for (i = 0; i < multicast_count; i += 6) {
+ uint8_t multicast_addr[6];
+ pci_dma_read(&s->dev, s->cb_address + 10 + i, multicast_addr, 6);
+ TRACE(OTHER, logout("multicast entry %s\n", nic_dump(multicast_addr, 6)));
+ unsigned mcast_idx = e100_compute_mcast_idx(multicast_addr);
+ assert(mcast_idx < 64);
+ s->mult[mcast_idx >> 3] |= (1 << (mcast_idx & 7));
+ }
+}
+
+static void action_command(EEPRO100State *s)
+{
+ /* The loop below won't stop if it gets special handcrafted data.
+ Therefore we limit the number of iterations. */
+ unsigned max_loop_count = 16;
+
+ for (;;) {
+ bool bit_el;
+ bool bit_s;
+ bool bit_i;
+ bool bit_nc;
+ uint16_t ok_status = STATUS_OK;
+ s->cb_address = s->cu_base + s->cu_offset;
+ read_cb(s);
+ bit_el = ((s->tx.command & COMMAND_EL) != 0);
+ bit_s = ((s->tx.command & COMMAND_S) != 0);
+ bit_i = ((s->tx.command & COMMAND_I) != 0);
+ bit_nc = ((s->tx.command & COMMAND_NC) != 0);
+#if 0
+ bool bit_sf = ((s->tx.command & COMMAND_SF) != 0);
+#endif
+
+ if (max_loop_count-- == 0) {
+ /* Prevent an endless loop. */
+ logout("loop in %s:%u\n", __FILE__, __LINE__);
+ break;
+ }
+
+ s->cu_offset = s->tx.link;
+ TRACE(OTHER,
+ logout("val=(cu start), status=0x%04x, command=0x%04x, link=0x%08x\n",
+ s->tx.status, s->tx.command, s->tx.link));
+ switch (s->tx.command & COMMAND_CMD) {
+ case CmdNOp:
+ /* Do nothing. */
+ break;
+ case CmdIASetup:
+ pci_dma_read(&s->dev, s->cb_address + 8, &s->conf.macaddr.a[0], 6);
+ TRACE(OTHER, logout("macaddr: %s\n", nic_dump(&s->conf.macaddr.a[0], 6)));
+ break;
+ case CmdConfigure:
+ pci_dma_read(&s->dev, s->cb_address + 8,
+ &s->configuration[0], sizeof(s->configuration));
+ TRACE(OTHER, logout("configuration: %s\n",
+ nic_dump(&s->configuration[0], 16)));
+ TRACE(OTHER, logout("configuration: %s\n",
+ nic_dump(&s->configuration[16],
+ ARRAY_SIZE(s->configuration) - 16)));
+ if (s->configuration[20] & BIT(6)) {
+ TRACE(OTHER, logout("Multiple IA bit\n"));
+ }
+ break;
+ case CmdMulticastList:
+ set_multicast_list(s);
+ break;
+ case CmdTx:
+ if (bit_nc) {
+ missing("CmdTx: NC = 0");
+ ok_status = 0;
+ break;
+ }
+ tx_command(s);
+ break;
+ case CmdTDR:
+ TRACE(OTHER, logout("load microcode\n"));
+ /* Starting with offset 8, the command contains
+ * 64 dwords microcode which we just ignore here. */
+ break;
+ case CmdDiagnose:
+ TRACE(OTHER, logout("diagnose\n"));
+ /* Make sure error flag is not set. */
+ s->tx.status = 0;
+ break;
+ default:
+ missing("undefined command");
+ ok_status = 0;
+ break;
+ }
+ /* Write new status. */
+ stw_le_pci_dma(&s->dev, s->cb_address,
+ s->tx.status | ok_status | STATUS_C);
+ if (bit_i) {
+ /* CU completed action. */
+ eepro100_cx_interrupt(s);
+ }
+ if (bit_el) {
+ /* CU becomes idle. Terminate command loop. */
+ set_cu_state(s, cu_idle);
+ eepro100_cna_interrupt(s);
+ break;
+ } else if (bit_s) {
+ /* CU becomes suspended. Terminate command loop. */
+ set_cu_state(s, cu_suspended);
+ eepro100_cna_interrupt(s);
+ break;
+ } else {
+ /* More entries in list. */
+ TRACE(OTHER, logout("CU list with at least one more entry\n"));
+ }
+ }
+ TRACE(OTHER, logout("CU list empty\n"));
+ /* List is empty. Now CU is idle or suspended. */
+}
+
+static void eepro100_cu_command(EEPRO100State * s, uint8_t val)
+{
+ cu_state_t cu_state;
+ switch (val) {
+ case CU_NOP:
+ /* No operation. */
+ break;
+ case CU_START:
+ cu_state = get_cu_state(s);
+ if (cu_state != cu_idle && cu_state != cu_suspended) {
+ /* Intel documentation says that CU must be idle or suspended
+ * for the CU start command. */
+ logout("unexpected CU state is %u\n", cu_state);
+ }
+ set_cu_state(s, cu_active);
+ s->cu_offset = e100_read_reg4(s, SCBPointer);
+ action_command(s);
+ break;
+ case CU_RESUME:
+ if (get_cu_state(s) != cu_suspended) {
+ logout("bad CU resume from CU state %u\n", get_cu_state(s));
+ /* Workaround for bad Linux eepro100 driver which resumes
+ * from idle state. */
+#if 0
+ missing("cu resume");
+#endif
+ set_cu_state(s, cu_suspended);
+ }
+ if (get_cu_state(s) == cu_suspended) {
+ TRACE(OTHER, logout("CU resuming\n"));
+ set_cu_state(s, cu_active);
+ action_command(s);
+ }
+ break;
+ case CU_STATSADDR:
+ /* Load dump counters address. */
+ s->statsaddr = e100_read_reg4(s, SCBPointer);
+ TRACE(OTHER, logout("val=0x%02x (dump counters address)\n", val));
+ if (s->statsaddr & 3) {
+ /* Memory must be Dword aligned. */
+ logout("unaligned dump counters address\n");
+ /* Handling of misaligned addresses is undefined.
+ * Here we align the address by ignoring the lower bits. */
+ /* TODO: Test unaligned dump counter address on real hardware. */
+ s->statsaddr &= ~3;
+ }
+ break;
+ case CU_SHOWSTATS:
+ /* Dump statistical counters. */
+ TRACE(OTHER, logout("val=0x%02x (dump stats)\n", val));
+ dump_statistics(s);
+ stl_le_pci_dma(&s->dev, s->statsaddr + s->stats_size, 0xa005);
+ break;
+ case CU_CMD_BASE:
+ /* Load CU base. */
+ TRACE(OTHER, logout("val=0x%02x (CU base address)\n", val));
+ s->cu_base = e100_read_reg4(s, SCBPointer);
+ break;
+ case CU_DUMPSTATS:
+ /* Dump and reset statistical counters. */
+ TRACE(OTHER, logout("val=0x%02x (dump stats and reset)\n", val));
+ dump_statistics(s);
+ stl_le_pci_dma(&s->dev, s->statsaddr + s->stats_size, 0xa007);
+ memset(&s->statistics, 0, sizeof(s->statistics));
+ break;
+ case CU_SRESUME:
+ /* CU static resume. */
+ missing("CU static resume");
+ break;
+ default:
+ missing("Undefined CU command");
+ }
+}
+
+static void eepro100_ru_command(EEPRO100State * s, uint8_t val)
+{
+ switch (val) {
+ case RU_NOP:
+ /* No operation. */
+ break;
+ case RX_START:
+ /* RU start. */
+ if (get_ru_state(s) != ru_idle) {
+ logout("RU state is %u, should be %u\n", get_ru_state(s), ru_idle);
+#if 0
+ assert(!"wrong RU state");
+#endif
+ }
+ set_ru_state(s, ru_ready);
+ s->ru_offset = e100_read_reg4(s, SCBPointer);
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ TRACE(OTHER, logout("val=0x%02x (rx start)\n", val));
+ break;
+ case RX_RESUME:
+ /* Restart RU. */
+ if (get_ru_state(s) != ru_suspended) {
+ logout("RU state is %u, should be %u\n", get_ru_state(s),
+ ru_suspended);
+#if 0
+ assert(!"wrong RU state");
+#endif
+ }
+ set_ru_state(s, ru_ready);
+ break;
+ case RU_ABORT:
+ /* RU abort. */
+ if (get_ru_state(s) == ru_ready) {
+ eepro100_rnr_interrupt(s);
+ }
+ set_ru_state(s, ru_idle);
+ break;
+ case RX_ADDR_LOAD:
+ /* Load RU base. */
+ TRACE(OTHER, logout("val=0x%02x (RU base address)\n", val));
+ s->ru_base = e100_read_reg4(s, SCBPointer);
+ break;
+ default:
+ logout("val=0x%02x (undefined RU command)\n", val);
+ missing("Undefined SU command");
+ }
+}
+
+static void eepro100_write_command(EEPRO100State * s, uint8_t val)
+{
+ eepro100_ru_command(s, val & 0x0f);
+ eepro100_cu_command(s, val & 0xf0);
+ if ((val) == 0) {
+ TRACE(OTHER, logout("val=0x%02x\n", val));
+ }
+ /* Clear command byte after command was accepted. */
+ s->mem[SCBCmd] = 0;
+}
+
+/*****************************************************************************
+ *
+ * EEPROM emulation.
+ *
+ ****************************************************************************/
+
+#define EEPROM_CS 0x02
+#define EEPROM_SK 0x01
+#define EEPROM_DI 0x04
+#define EEPROM_DO 0x08
+
+static uint16_t eepro100_read_eeprom(EEPRO100State * s)
+{
+ uint16_t val = e100_read_reg2(s, SCBeeprom);
+ if (eeprom93xx_read(s->eeprom)) {
+ val |= EEPROM_DO;
+ } else {
+ val &= ~EEPROM_DO;
+ }
+ TRACE(EEPROM, logout("val=0x%04x\n", val));
+ return val;
+}
+
+static void eepro100_write_eeprom(eeprom_t * eeprom, uint8_t val)
+{
+ TRACE(EEPROM, logout("val=0x%02x\n", val));
+
+ /* mask unwritable bits */
+#if 0
+ val = SET_MASKED(val, 0x31, eeprom->value);
+#endif
+
+ int eecs = ((val & EEPROM_CS) != 0);
+ int eesk = ((val & EEPROM_SK) != 0);
+ int eedi = ((val & EEPROM_DI) != 0);
+ eeprom93xx_write(eeprom, eecs, eesk, eedi);
+}
+
+/*****************************************************************************
+ *
+ * MDI emulation.
+ *
+ ****************************************************************************/
+
+#if defined(DEBUG_EEPRO100)
+static const char * const mdi_op_name[] = {
+ "opcode 0",
+ "write",
+ "read",
+ "opcode 3"
+};
+
+static const char * const mdi_reg_name[] = {
+ "Control",
+ "Status",
+ "PHY Identification (Word 1)",
+ "PHY Identification (Word 2)",
+ "Auto-Negotiation Advertisement",
+ "Auto-Negotiation Link Partner Ability",
+ "Auto-Negotiation Expansion"
+};
+
+static const char *reg2name(uint8_t reg)
+{
+ static char buffer[10];
+ const char *p = buffer;
+ if (reg < ARRAY_SIZE(mdi_reg_name)) {
+ p = mdi_reg_name[reg];
+ } else {
+ snprintf(buffer, sizeof(buffer), "reg=0x%02x", reg);
+ }
+ return p;
+}
+#endif /* DEBUG_EEPRO100 */
+
+static uint32_t eepro100_read_mdi(EEPRO100State * s)
+{
+ uint32_t val = e100_read_reg4(s, SCBCtrlMDI);
+
+#ifdef DEBUG_EEPRO100
+ uint8_t raiseint = (val & BIT(29)) >> 29;
+ uint8_t opcode = (val & BITS(27, 26)) >> 26;
+ uint8_t phy = (val & BITS(25, 21)) >> 21;
+ uint8_t reg = (val & BITS(20, 16)) >> 16;
+ uint16_t data = (val & BITS(15, 0));
+#endif
+ /* Emulation takes no time to finish MDI transaction. */
+ val |= BIT(28);
+ TRACE(MDI, logout("val=0x%08x (int=%u, %s, phy=%u, %s, data=0x%04x\n",
+ val, raiseint, mdi_op_name[opcode], phy,
+ reg2name(reg), data));
+ return val;
+}
+
+static void eepro100_write_mdi(EEPRO100State *s)
+{
+ uint32_t val = e100_read_reg4(s, SCBCtrlMDI);
+ uint8_t raiseint = (val & BIT(29)) >> 29;
+ uint8_t opcode = (val & BITS(27, 26)) >> 26;
+ uint8_t phy = (val & BITS(25, 21)) >> 21;
+ uint8_t reg = (val & BITS(20, 16)) >> 16;
+ uint16_t data = (val & BITS(15, 0));
+ TRACE(MDI, logout("val=0x%08x (int=%u, %s, phy=%u, %s, data=0x%04x\n",
+ val, raiseint, mdi_op_name[opcode], phy, reg2name(reg), data));
+ if (phy != 1) {
+ /* Unsupported PHY address. */
+#if 0
+ logout("phy must be 1 but is %u\n", phy);
+#endif
+ data = 0;
+ } else if (opcode != 1 && opcode != 2) {
+ /* Unsupported opcode. */
+ logout("opcode must be 1 or 2 but is %u\n", opcode);
+ data = 0;
+ } else if (reg > 6) {
+ /* Unsupported register. */
+ logout("register must be 0...6 but is %u\n", reg);
+ data = 0;
+ } else {
+ TRACE(MDI, logout("val=0x%08x (int=%u, %s, phy=%u, %s, data=0x%04x\n",
+ val, raiseint, mdi_op_name[opcode], phy,
+ reg2name(reg), data));
+ if (opcode == 1) {
+ /* MDI write */
+ switch (reg) {
+ case 0: /* Control Register */
+ if (data & 0x8000) {
+ /* Reset status and control registers to default. */
+ s->mdimem[0] = eepro100_mdi_default[0];
+ s->mdimem[1] = eepro100_mdi_default[1];
+ data = s->mdimem[reg];
+ } else {
+ /* Restart Auto Configuration = Normal Operation */
+ data &= ~0x0200;
+ }
+ break;
+ case 1: /* Status Register */
+ missing("not writable");
+ break;
+ case 2: /* PHY Identification Register (Word 1) */
+ case 3: /* PHY Identification Register (Word 2) */
+ missing("not implemented");
+ break;
+ case 4: /* Auto-Negotiation Advertisement Register */
+ case 5: /* Auto-Negotiation Link Partner Ability Register */
+ break;
+ case 6: /* Auto-Negotiation Expansion Register */
+ default:
+ missing("not implemented");
+ }
+ s->mdimem[reg] &= eepro100_mdi_mask[reg];
+ s->mdimem[reg] |= data & ~eepro100_mdi_mask[reg];
+ } else if (opcode == 2) {
+ /* MDI read */
+ switch (reg) {
+ case 0: /* Control Register */
+ if (data & 0x8000) {
+ /* Reset status and control registers to default. */
+ s->mdimem[0] = eepro100_mdi_default[0];
+ s->mdimem[1] = eepro100_mdi_default[1];
+ }
+ break;
+ case 1: /* Status Register */
+ s->mdimem[reg] |= 0x0020;
+ break;
+ case 2: /* PHY Identification Register (Word 1) */
+ case 3: /* PHY Identification Register (Word 2) */
+ case 4: /* Auto-Negotiation Advertisement Register */
+ break;
+ case 5: /* Auto-Negotiation Link Partner Ability Register */
+ s->mdimem[reg] = 0x41fe;
+ break;
+ case 6: /* Auto-Negotiation Expansion Register */
+ s->mdimem[reg] = 0x0001;
+ break;
+ }
+ data = s->mdimem[reg];
+ }
+ /* Emulation takes no time to finish MDI transaction.
+ * Set MDI bit in SCB status register. */
+ s->mem[SCBAck] |= 0x08;
+ val |= BIT(28);
+ if (raiseint) {
+ eepro100_mdi_interrupt(s);
+ }
+ }
+ val = (val & 0xffff0000) + data;
+ e100_write_reg4(s, SCBCtrlMDI, val);
+}
+
+/*****************************************************************************
+ *
+ * Port emulation.
+ *
+ ****************************************************************************/
+
+#define PORT_SOFTWARE_RESET 0
+#define PORT_SELFTEST 1
+#define PORT_SELECTIVE_RESET 2
+#define PORT_DUMP 3
+#define PORT_SELECTION_MASK 3
+
+typedef struct {
+ uint32_t st_sign; /* Self Test Signature */
+ uint32_t st_result; /* Self Test Results */
+} eepro100_selftest_t;
+
+static uint32_t eepro100_read_port(EEPRO100State * s)
+{
+ return 0;
+}
+
+static void eepro100_write_port(EEPRO100State *s)
+{
+ uint32_t val = e100_read_reg4(s, SCBPort);
+ uint32_t address = (val & ~PORT_SELECTION_MASK);
+ uint8_t selection = (val & PORT_SELECTION_MASK);
+ switch (selection) {
+ case PORT_SOFTWARE_RESET:
+ nic_reset(s);
+ break;
+ case PORT_SELFTEST:
+ TRACE(OTHER, logout("selftest address=0x%08x\n", address));
+ eepro100_selftest_t data;
+ pci_dma_read(&s->dev, address, (uint8_t *) &data, sizeof(data));
+ data.st_sign = 0xffffffff;
+ data.st_result = 0;
+ pci_dma_write(&s->dev, address, (uint8_t *) &data, sizeof(data));
+ break;
+ case PORT_SELECTIVE_RESET:
+ TRACE(OTHER, logout("selective reset, selftest address=0x%08x\n", address));
+ nic_selective_reset(s);
+ break;
+ default:
+ logout("val=0x%08x\n", val);
+ missing("unknown port selection");
+ }
+}
+
+/*****************************************************************************
+ *
+ * General hardware emulation.
+ *
+ ****************************************************************************/
+
+static uint8_t eepro100_read1(EEPRO100State * s, uint32_t addr)
+{
+ uint8_t val = 0;
+ if (addr <= sizeof(s->mem) - sizeof(val)) {
+ val = s->mem[addr];
+ }
+
+ switch (addr) {
+ case SCBStatus:
+ case SCBAck:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBCmd:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+#if 0
+ val = eepro100_read_command(s);
+#endif
+ break;
+ case SCBIntmask:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBPort + 3:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBeeprom:
+ val = eepro100_read_eeprom(s);
+ break;
+ case SCBCtrlMDI:
+ case SCBCtrlMDI + 1:
+ case SCBCtrlMDI + 2:
+ case SCBCtrlMDI + 3:
+ val = (uint8_t)(eepro100_read_mdi(s) >> (8 * (addr & 3)));
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBpmdr: /* Power Management Driver Register */
+ val = 0;
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBgctrl: /* General Control Register */
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBgstat: /* General Status Register */
+ /* 100 Mbps full duplex, valid link */
+ val = 0x07;
+ TRACE(OTHER, logout("addr=General Status val=%02x\n", val));
+ break;
+ default:
+ logout("addr=%s val=0x%02x\n", regname(addr), val);
+ missing("unknown byte read");
+ }
+ return val;
+}
+
+static uint16_t eepro100_read2(EEPRO100State * s, uint32_t addr)
+{
+ uint16_t val = 0;
+ if (addr <= sizeof(s->mem) - sizeof(val)) {
+ val = e100_read_reg2(s, addr);
+ }
+
+ switch (addr) {
+ case SCBStatus:
+ case SCBCmd:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ break;
+ case SCBeeprom:
+ val = eepro100_read_eeprom(s);
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ break;
+ case SCBCtrlMDI:
+ case SCBCtrlMDI + 2:
+ val = (uint16_t)(eepro100_read_mdi(s) >> (8 * (addr & 3)));
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ break;
+ default:
+ logout("addr=%s val=0x%04x\n", regname(addr), val);
+ missing("unknown word read");
+ }
+ return val;
+}
+
+static uint32_t eepro100_read4(EEPRO100State * s, uint32_t addr)
+{
+ uint32_t val = 0;
+ if (addr <= sizeof(s->mem) - sizeof(val)) {
+ val = e100_read_reg4(s, addr);
+ }
+
+ switch (addr) {
+ case SCBStatus:
+ TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val));
+ break;
+ case SCBPointer:
+ TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val));
+ break;
+ case SCBPort:
+ val = eepro100_read_port(s);
+ TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val));
+ break;
+ case SCBflash:
+ val = eepro100_read_eeprom(s);
+ TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val));
+ break;
+ case SCBCtrlMDI:
+ val = eepro100_read_mdi(s);
+ break;
+ default:
+ logout("addr=%s val=0x%08x\n", regname(addr), val);
+ missing("unknown longword read");
+ }
+ return val;
+}
+
+static void eepro100_write1(EEPRO100State * s, uint32_t addr, uint8_t val)
+{
+ /* SCBStatus is readonly. */
+ if (addr > SCBStatus && addr <= sizeof(s->mem) - sizeof(val)) {
+ s->mem[addr] = val;
+ }
+
+ switch (addr) {
+ case SCBStatus:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBAck:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ eepro100_acknowledge(s);
+ break;
+ case SCBCmd:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ eepro100_write_command(s, val);
+ break;
+ case SCBIntmask:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ if (val & BIT(1)) {
+ eepro100_swi_interrupt(s);
+ }
+ eepro100_interrupt(s, 0);
+ break;
+ case SCBPointer:
+ case SCBPointer + 1:
+ case SCBPointer + 2:
+ case SCBPointer + 3:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBPort:
+ case SCBPort + 1:
+ case SCBPort + 2:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBPort + 3:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ eepro100_write_port(s);
+ break;
+ case SCBFlow: /* does not exist on 82557 */
+ case SCBFlow + 1:
+ case SCBFlow + 2:
+ case SCBpmdr: /* does not exist on 82557 */
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBeeprom:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ eepro100_write_eeprom(s->eeprom, val);
+ break;
+ case SCBCtrlMDI:
+ case SCBCtrlMDI + 1:
+ case SCBCtrlMDI + 2:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ break;
+ case SCBCtrlMDI + 3:
+ TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val));
+ eepro100_write_mdi(s);
+ break;
+ default:
+ logout("addr=%s val=0x%02x\n", regname(addr), val);
+ missing("unknown byte write");
+ }
+}
+
+static void eepro100_write2(EEPRO100State * s, uint32_t addr, uint16_t val)
+{
+ /* SCBStatus is readonly. */
+ if (addr > SCBStatus && addr <= sizeof(s->mem) - sizeof(val)) {
+ e100_write_reg2(s, addr, val);
+ }
+
+ switch (addr) {
+ case SCBStatus:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ s->mem[SCBAck] = (val >> 8);
+ eepro100_acknowledge(s);
+ break;
+ case SCBCmd:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ eepro100_write_command(s, val);
+ eepro100_write1(s, SCBIntmask, val >> 8);
+ break;
+ case SCBPointer:
+ case SCBPointer + 2:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ break;
+ case SCBPort:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ break;
+ case SCBPort + 2:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ eepro100_write_port(s);
+ break;
+ case SCBeeprom:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ eepro100_write_eeprom(s->eeprom, val);
+ break;
+ case SCBCtrlMDI:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ break;
+ case SCBCtrlMDI + 2:
+ TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val));
+ eepro100_write_mdi(s);
+ break;
+ default:
+ logout("addr=%s val=0x%04x\n", regname(addr), val);
+ missing("unknown word write");
+ }
+}
+
+static void eepro100_write4(EEPRO100State * s, uint32_t addr, uint32_t val)
+{
+ if (addr <= sizeof(s->mem) - sizeof(val)) {
+ e100_write_reg4(s, addr, val);
+ }
+
+ switch (addr) {
+ case SCBPointer:
+ TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val));
+ break;
+ case SCBPort:
+ TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val));
+ eepro100_write_port(s);
+ break;
+ case SCBflash:
+ TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val));
+ val = val >> 16;
+ eepro100_write_eeprom(s->eeprom, val);
+ break;
+ case SCBCtrlMDI:
+ TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val));
+ eepro100_write_mdi(s);
+ break;
+ default:
+ logout("addr=%s val=0x%08x\n", regname(addr), val);
+ missing("unknown longword write");
+ }
+}
+
+static uint64_t eepro100_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ EEPRO100State *s = opaque;
+
+ switch (size) {
+ case 1: return eepro100_read1(s, addr);
+ case 2: return eepro100_read2(s, addr);
+ case 4: return eepro100_read4(s, addr);
+ default: abort();
+ }
+}
+
+static void eepro100_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ EEPRO100State *s = opaque;
+
+ switch (size) {
+ case 1:
+ eepro100_write1(s, addr, data);
+ break;
+ case 2:
+ eepro100_write2(s, addr, data);
+ break;
+ case 4:
+ eepro100_write4(s, addr, data);
+ break;
+ default:
+ abort();
+ }
+}
+
+static const MemoryRegionOps eepro100_ops = {
+ .read = eepro100_read,
+ .write = eepro100_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static ssize_t nic_receive(NetClientState *nc, const uint8_t * buf, size_t size)
+{
+ /* TODO:
+ * - Magic packets should set bit 30 in power management driver register.
+ * - Interesting packets should set bit 29 in power management driver register.
+ */
+ EEPRO100State *s = qemu_get_nic_opaque(nc);
+ uint16_t rfd_status = 0xa000;
+#if defined(CONFIG_PAD_RECEIVED_FRAMES)
+ uint8_t min_buf[60];
+#endif
+ static const uint8_t broadcast_macaddr[6] =
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+#if defined(CONFIG_PAD_RECEIVED_FRAMES)
+ /* Pad to minimum Ethernet frame length */
+ if (size < sizeof(min_buf)) {
+ memcpy(min_buf, buf, size);
+ memset(&min_buf[size], 0, sizeof(min_buf) - size);
+ buf = min_buf;
+ size = sizeof(min_buf);
+ }
+#endif
+
+ if (s->configuration[8] & 0x80) {
+ /* CSMA is disabled. */
+ logout("%p received while CSMA is disabled\n", s);
+ return -1;
+#if !defined(CONFIG_PAD_RECEIVED_FRAMES)
+ } else if (size < 64 && (s->configuration[7] & BIT(0))) {
+ /* Short frame and configuration byte 7/0 (discard short receive) set:
+ * Short frame is discarded */
+ logout("%p received short frame (%zu byte)\n", s, size);
+ s->statistics.rx_short_frame_errors++;
+ return -1;
+#endif
+ } else if ((size > MAX_ETH_FRAME_SIZE + 4) && !(s->configuration[18] & BIT(3))) {
+ /* Long frame and configuration byte 18/3 (long receive ok) not set:
+ * Long frames are discarded. */
+ logout("%p received long frame (%zu byte), ignored\n", s, size);
+ return -1;
+ } else if (memcmp(buf, s->conf.macaddr.a, 6) == 0) { /* !!! */
+ /* Frame matches individual address. */
+ /* TODO: check configuration byte 15/4 (ignore U/L). */
+ TRACE(RXTX, logout("%p received frame for me, len=%zu\n", s, size));
+ } else if (memcmp(buf, broadcast_macaddr, 6) == 0) {
+ /* Broadcast frame. */
+ TRACE(RXTX, logout("%p received broadcast, len=%zu\n", s, size));
+ rfd_status |= 0x0002;
+ } else if (buf[0] & 0x01) {
+ /* Multicast frame. */
+ TRACE(RXTX, logout("%p received multicast, len=%zu,%s\n", s, size, nic_dump(buf, size)));
+ if (s->configuration[21] & BIT(3)) {
+ /* Multicast all bit is set, receive all multicast frames. */
+ } else {
+ unsigned mcast_idx = e100_compute_mcast_idx(buf);
+ assert(mcast_idx < 64);
+ if (s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7))) {
+ /* Multicast frame is allowed in hash table. */
+ } else if (s->configuration[15] & BIT(0)) {
+ /* Promiscuous: receive all. */
+ rfd_status |= 0x0004;
+ } else {
+ TRACE(RXTX, logout("%p multicast ignored\n", s));
+ return -1;
+ }
+ }
+ /* TODO: Next not for promiscuous mode? */
+ rfd_status |= 0x0002;
+ } else if (s->configuration[15] & BIT(0)) {
+ /* Promiscuous: receive all. */
+ TRACE(RXTX, logout("%p received frame in promiscuous mode, len=%zu\n", s, size));
+ rfd_status |= 0x0004;
+ } else if (s->configuration[20] & BIT(6)) {
+ /* Multiple IA bit set. */
+ unsigned mcast_idx = compute_mcast_idx(buf);
+ assert(mcast_idx < 64);
+ if (s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7))) {
+ TRACE(RXTX, logout("%p accepted, multiple IA bit set\n", s));
+ } else {
+ TRACE(RXTX, logout("%p frame ignored, multiple IA bit set\n", s));
+ return -1;
+ }
+ } else {
+ TRACE(RXTX, logout("%p received frame, ignored, len=%zu,%s\n", s, size,
+ nic_dump(buf, size)));
+ return size;
+ }
+
+ if (get_ru_state(s) != ru_ready) {
+ /* No resources available. */
+ logout("no resources, state=%u\n", get_ru_state(s));
+ /* TODO: RNR interrupt only at first failed frame? */
+ eepro100_rnr_interrupt(s);
+ s->statistics.rx_resource_errors++;
+#if 0
+ assert(!"no resources");
+#endif
+ return -1;
+ }
+ /* !!! */
+ eepro100_rx_t rx;
+ pci_dma_read(&s->dev, s->ru_base + s->ru_offset,
+ &rx, sizeof(eepro100_rx_t));
+ uint16_t rfd_command = le16_to_cpu(rx.command);
+ uint16_t rfd_size = le16_to_cpu(rx.size);
+
+ if (size > rfd_size) {
+ logout("Receive buffer (%" PRId16 " bytes) too small for data "
+ "(%zu bytes); data truncated\n", rfd_size, size);
+ size = rfd_size;
+ }
+#if !defined(CONFIG_PAD_RECEIVED_FRAMES)
+ if (size < 64) {
+ rfd_status |= 0x0080;
+ }
+#endif
+ TRACE(OTHER, logout("command 0x%04x, link 0x%08x, addr 0x%08x, size %u\n",
+ rfd_command, rx.link, rx.rx_buf_addr, rfd_size));
+ stw_le_pci_dma(&s->dev, s->ru_base + s->ru_offset +
+ offsetof(eepro100_rx_t, status), rfd_status);
+ stw_le_pci_dma(&s->dev, s->ru_base + s->ru_offset +
+ offsetof(eepro100_rx_t, count), size);
+ /* Early receive interrupt not supported. */
+#if 0
+ eepro100_er_interrupt(s);
+#endif
+ /* Receive CRC Transfer not supported. */
+ if (s->configuration[18] & BIT(2)) {
+ missing("Receive CRC Transfer");
+ return -1;
+ }
+ /* TODO: check stripping enable bit. */
+#if 0
+ assert(!(s->configuration[17] & BIT(0)));
+#endif
+ pci_dma_write(&s->dev, s->ru_base + s->ru_offset +
+ sizeof(eepro100_rx_t), buf, size);
+ s->statistics.rx_good_frames++;
+ eepro100_fr_interrupt(s);
+ s->ru_offset = le32_to_cpu(rx.link);
+ if (rfd_command & COMMAND_EL) {
+ /* EL bit is set, so this was the last frame. */
+ logout("receive: Running out of frames\n");
+ set_ru_state(s, ru_no_resources);
+ eepro100_rnr_interrupt(s);
+ }
+ if (rfd_command & COMMAND_S) {
+ /* S bit is set. */
+ set_ru_state(s, ru_suspended);
+ }
+ return size;
+}
+
+static const VMStateDescription vmstate_eepro100 = {
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, EEPRO100State),
+ VMSTATE_UNUSED(32),
+ VMSTATE_BUFFER(mult, EEPRO100State),
+ VMSTATE_BUFFER(mem, EEPRO100State),
+ /* Save all members of struct between scb_stat and mem. */
+ VMSTATE_UINT8(scb_stat, EEPRO100State),
+ VMSTATE_UINT8(int_stat, EEPRO100State),
+ VMSTATE_UNUSED(3*4),
+ VMSTATE_MACADDR(conf.macaddr, EEPRO100State),
+ VMSTATE_UNUSED(19*4),
+ VMSTATE_UINT16_ARRAY(mdimem, EEPRO100State, 32),
+ /* The eeprom should be saved and restored by its own routines. */
+ VMSTATE_UINT32(device, EEPRO100State),
+ /* TODO check device. */
+ VMSTATE_UINT32(cu_base, EEPRO100State),
+ VMSTATE_UINT32(cu_offset, EEPRO100State),
+ VMSTATE_UINT32(ru_base, EEPRO100State),
+ VMSTATE_UINT32(ru_offset, EEPRO100State),
+ VMSTATE_UINT32(statsaddr, EEPRO100State),
+ /* Save eepro100_stats_t statistics. */
+ VMSTATE_UINT32(statistics.tx_good_frames, EEPRO100State),
+ VMSTATE_UINT32(statistics.tx_max_collisions, EEPRO100State),
+ VMSTATE_UINT32(statistics.tx_late_collisions, EEPRO100State),
+ VMSTATE_UINT32(statistics.tx_underruns, EEPRO100State),
+ VMSTATE_UINT32(statistics.tx_lost_crs, EEPRO100State),
+ VMSTATE_UINT32(statistics.tx_deferred, EEPRO100State),
+ VMSTATE_UINT32(statistics.tx_single_collisions, EEPRO100State),
+ VMSTATE_UINT32(statistics.tx_multiple_collisions, EEPRO100State),
+ VMSTATE_UINT32(statistics.tx_total_collisions, EEPRO100State),
+ VMSTATE_UINT32(statistics.rx_good_frames, EEPRO100State),
+ VMSTATE_UINT32(statistics.rx_crc_errors, EEPRO100State),
+ VMSTATE_UINT32(statistics.rx_alignment_errors, EEPRO100State),
+ VMSTATE_UINT32(statistics.rx_resource_errors, EEPRO100State),
+ VMSTATE_UINT32(statistics.rx_overrun_errors, EEPRO100State),
+ VMSTATE_UINT32(statistics.rx_cdt_errors, EEPRO100State),
+ VMSTATE_UINT32(statistics.rx_short_frame_errors, EEPRO100State),
+ VMSTATE_UINT32(statistics.fc_xmt_pause, EEPRO100State),
+ VMSTATE_UINT32(statistics.fc_rcv_pause, EEPRO100State),
+ VMSTATE_UINT32(statistics.fc_rcv_unsupported, EEPRO100State),
+ VMSTATE_UINT16(statistics.xmt_tco_frames, EEPRO100State),
+ VMSTATE_UINT16(statistics.rcv_tco_frames, EEPRO100State),
+ /* Configuration bytes. */
+ VMSTATE_BUFFER(configuration, EEPRO100State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pci_nic_uninit(PCIDevice *pci_dev)
+{
+ EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, pci_dev);
+
+ vmstate_unregister(&pci_dev->qdev, s->vmstate, s);
+ eeprom93xx_free(&pci_dev->qdev, s->eeprom);
+ qemu_del_nic(s->nic);
+}
+
+static NetClientInfo net_eepro100_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = nic_receive,
+};
+
+static void e100_nic_realize(PCIDevice *pci_dev, Error **errp)
+{
+ EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, pci_dev);
+ E100PCIDeviceInfo *info = eepro100_get_class(s);
+
+ TRACE(OTHER, logout("\n"));
+
+ s->device = info->device;
+
+ e100_pci_reset(s);
+
+ /* Add 64 * 2 EEPROM. i82557 and i82558 support a 64 word EEPROM,
+ * i82559 and later support 64 or 256 word EEPROM. */
+ s->eeprom = eeprom93xx_new(&pci_dev->qdev, EEPROM_SIZE);
+
+ /* Handler for memory-mapped I/O */
+ memory_region_init_io(&s->mmio_bar, OBJECT(s), &eepro100_ops, s,
+ "eepro100-mmio", PCI_MEM_SIZE);
+ pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->mmio_bar);
+ memory_region_init_io(&s->io_bar, OBJECT(s), &eepro100_ops, s,
+ "eepro100-io", PCI_IO_SIZE);
+ pci_register_bar(&s->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar);
+ /* FIXME: flash aliases to mmio?! */
+ memory_region_init_io(&s->flash_bar, OBJECT(s), &eepro100_ops, s,
+ "eepro100-flash", PCI_FLASH_SIZE);
+ pci_register_bar(&s->dev, 2, 0, &s->flash_bar);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ logout("macaddr: %s\n", nic_dump(&s->conf.macaddr.a[0], 6));
+
+ nic_reset(s);
+
+ s->nic = qemu_new_nic(&net_eepro100_info, &s->conf,
+ object_get_typename(OBJECT(pci_dev)), pci_dev->qdev.id, s);
+
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+ TRACE(OTHER, logout("%s\n", qemu_get_queue(s->nic)->info_str));
+
+ qemu_register_reset(nic_reset, s);
+
+ s->vmstate = g_malloc(sizeof(vmstate_eepro100));
+ memcpy(s->vmstate, &vmstate_eepro100, sizeof(vmstate_eepro100));
+ s->vmstate->name = qemu_get_queue(s->nic)->model;
+ vmstate_register(&pci_dev->qdev, -1, s->vmstate, s);
+}
+
+static void eepro100_instance_init(Object *obj)
+{
+ EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, PCI_DEVICE(obj));
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ DEVICE(s), NULL);
+}
+
+static E100PCIDeviceInfo e100_devices[] = {
+ {
+ .name = "i82550",
+ .desc = "Intel i82550 Ethernet",
+ .device = i82550,
+ /* TODO: check device id. */
+ .device_id = PCI_DEVICE_ID_INTEL_82551IT,
+ /* Revision ID: 0x0c, 0x0d, 0x0e. */
+ .revision = 0x0e,
+ /* TODO: check size of statistical counters. */
+ .stats_size = 80,
+ /* TODO: check extended tcb support. */
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ .name = "i82551",
+ .desc = "Intel i82551 Ethernet",
+ .device = i82551,
+ .device_id = PCI_DEVICE_ID_INTEL_82551IT,
+ /* Revision ID: 0x0f, 0x10. */
+ .revision = 0x0f,
+ /* TODO: check size of statistical counters. */
+ .stats_size = 80,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ .name = "i82557a",
+ .desc = "Intel i82557A Ethernet",
+ .device = i82557A,
+ .device_id = PCI_DEVICE_ID_INTEL_82557,
+ .revision = 0x01,
+ .power_management = false,
+ },{
+ .name = "i82557b",
+ .desc = "Intel i82557B Ethernet",
+ .device = i82557B,
+ .device_id = PCI_DEVICE_ID_INTEL_82557,
+ .revision = 0x02,
+ .power_management = false,
+ },{
+ .name = "i82557c",
+ .desc = "Intel i82557C Ethernet",
+ .device = i82557C,
+ .device_id = PCI_DEVICE_ID_INTEL_82557,
+ .revision = 0x03,
+ .power_management = false,
+ },{
+ .name = "i82558a",
+ .desc = "Intel i82558A Ethernet",
+ .device = i82558A,
+ .device_id = PCI_DEVICE_ID_INTEL_82557,
+ .revision = 0x04,
+ .stats_size = 76,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ .name = "i82558b",
+ .desc = "Intel i82558B Ethernet",
+ .device = i82558B,
+ .device_id = PCI_DEVICE_ID_INTEL_82557,
+ .revision = 0x05,
+ .stats_size = 76,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ .name = "i82559a",
+ .desc = "Intel i82559A Ethernet",
+ .device = i82559A,
+ .device_id = PCI_DEVICE_ID_INTEL_82557,
+ .revision = 0x06,
+ .stats_size = 80,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ .name = "i82559b",
+ .desc = "Intel i82559B Ethernet",
+ .device = i82559B,
+ .device_id = PCI_DEVICE_ID_INTEL_82557,
+ .revision = 0x07,
+ .stats_size = 80,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ .name = "i82559c",
+ .desc = "Intel i82559C Ethernet",
+ .device = i82559C,
+ .device_id = PCI_DEVICE_ID_INTEL_82557,
+#if 0
+ .revision = 0x08,
+#endif
+ /* TODO: Windows wants revision id 0x0c. */
+ .revision = 0x0c,
+#if EEPROM_SIZE > 0
+ .subsystem_vendor_id = PCI_VENDOR_ID_INTEL,
+ .subsystem_id = 0x0040,
+#endif
+ .stats_size = 80,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ .name = "i82559er",
+ .desc = "Intel i82559ER Ethernet",
+ .device = i82559ER,
+ .device_id = PCI_DEVICE_ID_INTEL_82551IT,
+ .revision = 0x09,
+ .stats_size = 80,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ .name = "i82562",
+ .desc = "Intel i82562 Ethernet",
+ .device = i82562,
+ /* TODO: check device id. */
+ .device_id = PCI_DEVICE_ID_INTEL_82551IT,
+ /* TODO: wrong revision id. */
+ .revision = 0x0e,
+ .stats_size = 80,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ },{
+ /* Toshiba Tecra 8200. */
+ .name = "i82801",
+ .desc = "Intel i82801 Ethernet",
+ .device = i82801,
+ .device_id = 0x2449,
+ .revision = 0x03,
+ .stats_size = 80,
+ .has_extended_tcb_support = true,
+ .power_management = true,
+ }
+};
+
+static E100PCIDeviceInfo *eepro100_get_class_by_name(const char *typename)
+{
+ E100PCIDeviceInfo *info = NULL;
+ int i;
+
+ /* This is admittedly awkward but also temporary. QOM allows for
+ * parameterized typing and for subclassing both of which would suitable
+ * handle what's going on here. But class_data is already being used as
+ * a stop-gap hack to allow incremental qdev conversion so we cannot use it
+ * right now. Once we merge the final QOM series, we can come back here and
+ * do this in a much more elegant fashion.
+ */
+ for (i = 0; i < ARRAY_SIZE(e100_devices); i++) {
+ if (strcmp(e100_devices[i].name, typename) == 0) {
+ info = &e100_devices[i];
+ break;
+ }
+ }
+ assert(info != NULL);
+
+ return info;
+}
+
+static E100PCIDeviceInfo *eepro100_get_class(EEPRO100State *s)
+{
+ return eepro100_get_class_by_name(object_get_typename(OBJECT(s)));
+}
+
+static Property e100_properties[] = {
+ DEFINE_NIC_PROPERTIES(EEPRO100State, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void eepro100_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ E100PCIDeviceInfo *info;
+
+ info = eepro100_get_class_by_name(object_class_get_name(klass));
+
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->props = e100_properties;
+ dc->desc = info->desc;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->class_id = PCI_CLASS_NETWORK_ETHERNET;
+ k->romfile = "pxe-eepro100.rom";
+ k->realize = e100_nic_realize;
+ k->exit = pci_nic_uninit;
+ k->device_id = info->device_id;
+ k->revision = info->revision;
+ k->subsystem_vendor_id = info->subsystem_vendor_id;
+ k->subsystem_id = info->subsystem_id;
+}
+
+static void eepro100_register_types(void)
+{
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(e100_devices); i++) {
+ TypeInfo type_info = {};
+ E100PCIDeviceInfo *info = &e100_devices[i];
+
+ type_info.name = info->name;
+ type_info.parent = TYPE_PCI_DEVICE;
+ type_info.class_init = eepro100_class_init;
+ type_info.instance_size = sizeof(EEPRO100State);
+ type_info.instance_init = eepro100_instance_init;
+
+ type_register(&type_info);
+ }
+}
+
+type_init(eepro100_register_types)
diff --git a/hw/net/etraxfs_eth.c b/hw/net/etraxfs_eth.c
new file mode 100644
index 00000000..d6002750
--- /dev/null
+++ b/hw/net/etraxfs_eth.c
@@ -0,0 +1,646 @@
+/*
+ * QEMU ETRAX Ethernet Controller.
+ *
+ * Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "hw/cris/etraxfs.h"
+
+#define D(x)
+
+/* Advertisement control register. */
+#define ADVERTISE_10HALF 0x0020 /* Try for 10mbps half-duplex */
+#define ADVERTISE_10FULL 0x0040 /* Try for 10mbps full-duplex */
+#define ADVERTISE_100HALF 0x0080 /* Try for 100mbps half-duplex */
+#define ADVERTISE_100FULL 0x0100 /* Try for 100mbps full-duplex */
+
+/*
+ * The MDIO extensions in the TDK PHY model were reversed engineered from the
+ * linux driver (PHYID and Diagnostics reg).
+ * TODO: Add friendly names for the register nums.
+ */
+struct qemu_phy
+{
+ uint32_t regs[32];
+
+ int link;
+
+ unsigned int (*read)(struct qemu_phy *phy, unsigned int req);
+ void (*write)(struct qemu_phy *phy, unsigned int req, unsigned int data);
+};
+
+static unsigned int tdk_read(struct qemu_phy *phy, unsigned int req)
+{
+ int regnum;
+ unsigned r = 0;
+
+ regnum = req & 0x1f;
+
+ switch (regnum) {
+ case 1:
+ if (!phy->link) {
+ break;
+ }
+ /* MR1. */
+ /* Speeds and modes. */
+ r |= (1 << 13) | (1 << 14);
+ r |= (1 << 11) | (1 << 12);
+ r |= (1 << 5); /* Autoneg complete. */
+ r |= (1 << 3); /* Autoneg able. */
+ r |= (1 << 2); /* link. */
+ break;
+ case 5:
+ /* Link partner ability.
+ We are kind; always agree with whatever best mode
+ the guest advertises. */
+ r = 1 << 14; /* Success. */
+ /* Copy advertised modes. */
+ r |= phy->regs[4] & (15 << 5);
+ /* Autoneg support. */
+ r |= 1;
+ break;
+ case 18:
+ {
+ /* Diagnostics reg. */
+ int duplex = 0;
+ int speed_100 = 0;
+
+ if (!phy->link) {
+ break;
+ }
+
+ /* Are we advertising 100 half or 100 duplex ? */
+ speed_100 = !!(phy->regs[4] & ADVERTISE_100HALF);
+ speed_100 |= !!(phy->regs[4] & ADVERTISE_100FULL);
+
+ /* Are we advertising 10 duplex or 100 duplex ? */
+ duplex = !!(phy->regs[4] & ADVERTISE_100FULL);
+ duplex |= !!(phy->regs[4] & ADVERTISE_10FULL);
+ r = (speed_100 << 10) | (duplex << 11);
+ }
+ break;
+
+ default:
+ r = phy->regs[regnum];
+ break;
+ }
+ D(printf("\n%s %x = reg[%d]\n", __func__, r, regnum));
+ return r;
+}
+
+static void
+tdk_write(struct qemu_phy *phy, unsigned int req, unsigned int data)
+{
+ int regnum;
+
+ regnum = req & 0x1f;
+ D(printf("%s reg[%d] = %x\n", __func__, regnum, data));
+ switch (regnum) {
+ default:
+ phy->regs[regnum] = data;
+ break;
+ }
+}
+
+static void
+tdk_init(struct qemu_phy *phy)
+{
+ phy->regs[0] = 0x3100;
+ /* PHY Id. */
+ phy->regs[2] = 0x0300;
+ phy->regs[3] = 0xe400;
+ /* Autonegotiation advertisement reg. */
+ phy->regs[4] = 0x01E1;
+ phy->link = 1;
+
+ phy->read = tdk_read;
+ phy->write = tdk_write;
+}
+
+struct qemu_mdio
+{
+ /* bus. */
+ int mdc;
+ int mdio;
+
+ /* decoder. */
+ enum {
+ PREAMBLE,
+ SOF,
+ OPC,
+ ADDR,
+ REQ,
+ TURNAROUND,
+ DATA
+ } state;
+ unsigned int drive;
+
+ unsigned int cnt;
+ unsigned int addr;
+ unsigned int opc;
+ unsigned int req;
+ unsigned int data;
+
+ struct qemu_phy *devs[32];
+};
+
+static void
+mdio_attach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr)
+{
+ bus->devs[addr & 0x1f] = phy;
+}
+
+#ifdef USE_THIS_DEAD_CODE
+static void
+mdio_detach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr)
+{
+ bus->devs[addr & 0x1f] = NULL;
+}
+#endif
+
+static void mdio_read_req(struct qemu_mdio *bus)
+{
+ struct qemu_phy *phy;
+
+ phy = bus->devs[bus->addr];
+ if (phy && phy->read) {
+ bus->data = phy->read(phy, bus->req);
+ } else {
+ bus->data = 0xffff;
+ }
+}
+
+static void mdio_write_req(struct qemu_mdio *bus)
+{
+ struct qemu_phy *phy;
+
+ phy = bus->devs[bus->addr];
+ if (phy && phy->write) {
+ phy->write(phy, bus->req, bus->data);
+ }
+}
+
+static void mdio_cycle(struct qemu_mdio *bus)
+{
+ bus->cnt++;
+
+ D(printf("mdc=%d mdio=%d state=%d cnt=%d drv=%d\n",
+ bus->mdc, bus->mdio, bus->state, bus->cnt, bus->drive));
+#if 0
+ if (bus->mdc) {
+ printf("%d", bus->mdio);
+ }
+#endif
+ switch (bus->state) {
+ case PREAMBLE:
+ if (bus->mdc) {
+ if (bus->cnt >= (32 * 2) && !bus->mdio) {
+ bus->cnt = 0;
+ bus->state = SOF;
+ bus->data = 0;
+ }
+ }
+ break;
+ case SOF:
+ if (bus->mdc) {
+ if (bus->mdio != 1) {
+ printf("WARNING: no SOF\n");
+ }
+ if (bus->cnt == 1*2) {
+ bus->cnt = 0;
+ bus->opc = 0;
+ bus->state = OPC;
+ }
+ }
+ break;
+ case OPC:
+ if (bus->mdc) {
+ bus->opc <<= 1;
+ bus->opc |= bus->mdio & 1;
+ if (bus->cnt == 2*2) {
+ bus->cnt = 0;
+ bus->addr = 0;
+ bus->state = ADDR;
+ }
+ }
+ break;
+ case ADDR:
+ if (bus->mdc) {
+ bus->addr <<= 1;
+ bus->addr |= bus->mdio & 1;
+
+ if (bus->cnt == 5*2) {
+ bus->cnt = 0;
+ bus->req = 0;
+ bus->state = REQ;
+ }
+ }
+ break;
+ case REQ:
+ if (bus->mdc) {
+ bus->req <<= 1;
+ bus->req |= bus->mdio & 1;
+ if (bus->cnt == 5*2) {
+ bus->cnt = 0;
+ bus->state = TURNAROUND;
+ }
+ }
+ break;
+ case TURNAROUND:
+ if (bus->mdc && bus->cnt == 2*2) {
+ bus->mdio = 0;
+ bus->cnt = 0;
+
+ if (bus->opc == 2) {
+ bus->drive = 1;
+ mdio_read_req(bus);
+ bus->mdio = bus->data & 1;
+ }
+ bus->state = DATA;
+ }
+ break;
+ case DATA:
+ if (!bus->mdc) {
+ if (bus->drive) {
+ bus->mdio = !!(bus->data & (1 << 15));
+ bus->data <<= 1;
+ }
+ } else {
+ if (!bus->drive) {
+ bus->data <<= 1;
+ bus->data |= bus->mdio;
+ }
+ if (bus->cnt == 16 * 2) {
+ bus->cnt = 0;
+ bus->state = PREAMBLE;
+ if (!bus->drive) {
+ mdio_write_req(bus);
+ }
+ bus->drive = 0;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/* ETRAX-FS Ethernet MAC block starts here. */
+
+#define RW_MA0_LO 0x00
+#define RW_MA0_HI 0x01
+#define RW_MA1_LO 0x02
+#define RW_MA1_HI 0x03
+#define RW_GA_LO 0x04
+#define RW_GA_HI 0x05
+#define RW_GEN_CTRL 0x06
+#define RW_REC_CTRL 0x07
+#define RW_TR_CTRL 0x08
+#define RW_CLR_ERR 0x09
+#define RW_MGM_CTRL 0x0a
+#define R_STAT 0x0b
+#define FS_ETH_MAX_REGS 0x17
+
+#define TYPE_ETRAX_FS_ETH "etraxfs-eth"
+#define ETRAX_FS_ETH(obj) \
+ OBJECT_CHECK(ETRAXFSEthState, (obj), TYPE_ETRAX_FS_ETH)
+
+typedef struct ETRAXFSEthState
+{
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ NICState *nic;
+ NICConf conf;
+
+ /* Two addrs in the filter. */
+ uint8_t macaddr[2][6];
+ uint32_t regs[FS_ETH_MAX_REGS];
+
+ union {
+ void *vdma_out;
+ struct etraxfs_dma_client *dma_out;
+ };
+ union {
+ void *vdma_in;
+ struct etraxfs_dma_client *dma_in;
+ };
+
+ /* MDIO bus. */
+ struct qemu_mdio mdio_bus;
+ unsigned int phyaddr;
+ int duplex_mismatch;
+
+ /* PHY. */
+ struct qemu_phy phy;
+} ETRAXFSEthState;
+
+static void eth_validate_duplex(ETRAXFSEthState *eth)
+{
+ struct qemu_phy *phy;
+ unsigned int phy_duplex;
+ unsigned int mac_duplex;
+ int new_mm = 0;
+
+ phy = eth->mdio_bus.devs[eth->phyaddr];
+ phy_duplex = !!(phy->read(phy, 18) & (1 << 11));
+ mac_duplex = !!(eth->regs[RW_REC_CTRL] & 128);
+
+ if (mac_duplex != phy_duplex) {
+ new_mm = 1;
+ }
+
+ if (eth->regs[RW_GEN_CTRL] & 1) {
+ if (new_mm != eth->duplex_mismatch) {
+ if (new_mm) {
+ printf("HW: WARNING ETH duplex mismatch MAC=%d PHY=%d\n",
+ mac_duplex, phy_duplex);
+ } else {
+ printf("HW: ETH duplex ok.\n");
+ }
+ }
+ eth->duplex_mismatch = new_mm;
+ }
+}
+
+static uint64_t
+eth_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ ETRAXFSEthState *eth = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+
+ switch (addr) {
+ case R_STAT:
+ r = eth->mdio_bus.mdio & 1;
+ break;
+ default:
+ r = eth->regs[addr];
+ D(printf("%s %x\n", __func__, addr * 4));
+ break;
+ }
+ return r;
+}
+
+static void eth_update_ma(ETRAXFSEthState *eth, int ma)
+{
+ int reg;
+ int i = 0;
+
+ ma &= 1;
+
+ reg = RW_MA0_LO;
+ if (ma) {
+ reg = RW_MA1_LO;
+ }
+
+ eth->macaddr[ma][i++] = eth->regs[reg];
+ eth->macaddr[ma][i++] = eth->regs[reg] >> 8;
+ eth->macaddr[ma][i++] = eth->regs[reg] >> 16;
+ eth->macaddr[ma][i++] = eth->regs[reg] >> 24;
+ eth->macaddr[ma][i++] = eth->regs[reg + 1];
+ eth->macaddr[ma][i] = eth->regs[reg + 1] >> 8;
+
+ D(printf("set mac%d=%x.%x.%x.%x.%x.%x\n", ma,
+ eth->macaddr[ma][0], eth->macaddr[ma][1],
+ eth->macaddr[ma][2], eth->macaddr[ma][3],
+ eth->macaddr[ma][4], eth->macaddr[ma][5]));
+}
+
+static void
+eth_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ ETRAXFSEthState *eth = opaque;
+ uint32_t value = val64;
+
+ addr >>= 2;
+ switch (addr) {
+ case RW_MA0_LO:
+ case RW_MA0_HI:
+ eth->regs[addr] = value;
+ eth_update_ma(eth, 0);
+ break;
+ case RW_MA1_LO:
+ case RW_MA1_HI:
+ eth->regs[addr] = value;
+ eth_update_ma(eth, 1);
+ break;
+
+ case RW_MGM_CTRL:
+ /* Attach an MDIO/PHY abstraction. */
+ if (value & 2) {
+ eth->mdio_bus.mdio = value & 1;
+ }
+ if (eth->mdio_bus.mdc != (value & 4)) {
+ mdio_cycle(&eth->mdio_bus);
+ eth_validate_duplex(eth);
+ }
+ eth->mdio_bus.mdc = !!(value & 4);
+ eth->regs[addr] = value;
+ break;
+
+ case RW_REC_CTRL:
+ eth->regs[addr] = value;
+ eth_validate_duplex(eth);
+ break;
+
+ default:
+ eth->regs[addr] = value;
+ D(printf("%s %x %x\n", __func__, addr, value));
+ break;
+ }
+}
+
+/* The ETRAX FS has a groupt address table (GAT) which works like a k=1 bloom
+ filter dropping group addresses we have not joined. The filter has 64
+ bits (m). The has function is a simple nible xor of the group addr. */
+static int eth_match_groupaddr(ETRAXFSEthState *eth, const unsigned char *sa)
+{
+ unsigned int hsh;
+ int m_individual = eth->regs[RW_REC_CTRL] & 4;
+ int match;
+
+ /* First bit on the wire of a MAC address signals multicast or
+ physical address. */
+ if (!m_individual && !(sa[0] & 1)) {
+ return 0;
+ }
+
+ /* Calculate the hash index for the GA registers. */
+ hsh = 0;
+ hsh ^= (*sa) & 0x3f;
+ hsh ^= ((*sa) >> 6) & 0x03;
+ ++sa;
+ hsh ^= ((*sa) << 2) & 0x03c;
+ hsh ^= ((*sa) >> 4) & 0xf;
+ ++sa;
+ hsh ^= ((*sa) << 4) & 0x30;
+ hsh ^= ((*sa) >> 2) & 0x3f;
+ ++sa;
+ hsh ^= (*sa) & 0x3f;
+ hsh ^= ((*sa) >> 6) & 0x03;
+ ++sa;
+ hsh ^= ((*sa) << 2) & 0x03c;
+ hsh ^= ((*sa) >> 4) & 0xf;
+ ++sa;
+ hsh ^= ((*sa) << 4) & 0x30;
+ hsh ^= ((*sa) >> 2) & 0x3f;
+
+ hsh &= 63;
+ if (hsh > 31) {
+ match = eth->regs[RW_GA_HI] & (1 << (hsh - 32));
+ } else {
+ match = eth->regs[RW_GA_LO] & (1 << hsh);
+ }
+ D(printf("hsh=%x ga=%x.%x mtch=%d\n", hsh,
+ eth->regs[RW_GA_HI], eth->regs[RW_GA_LO], match));
+ return match;
+}
+
+static ssize_t eth_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ unsigned char sa_bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ ETRAXFSEthState *eth = qemu_get_nic_opaque(nc);
+ int use_ma0 = eth->regs[RW_REC_CTRL] & 1;
+ int use_ma1 = eth->regs[RW_REC_CTRL] & 2;
+ int r_bcast = eth->regs[RW_REC_CTRL] & 8;
+
+ if (size < 12) {
+ return -1;
+ }
+
+ D(printf("%x.%x.%x.%x.%x.%x ma=%d %d bc=%d\n",
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
+ use_ma0, use_ma1, r_bcast));
+
+ /* Does the frame get through the address filters? */
+ if ((!use_ma0 || memcmp(buf, eth->macaddr[0], 6))
+ && (!use_ma1 || memcmp(buf, eth->macaddr[1], 6))
+ && (!r_bcast || memcmp(buf, sa_bcast, 6))
+ && !eth_match_groupaddr(eth, buf)) {
+ return size;
+ }
+
+ /* FIXME: Find another way to pass on the fake csum. */
+ etraxfs_dmac_input(eth->dma_in, (void *)buf, size + 4, 1);
+
+ return size;
+}
+
+static int eth_tx_push(void *opaque, unsigned char *buf, int len, bool eop)
+{
+ ETRAXFSEthState *eth = opaque;
+
+ D(printf("%s buf=%p len=%d\n", __func__, buf, len));
+ qemu_send_packet(qemu_get_queue(eth->nic), buf, len);
+ return len;
+}
+
+static void eth_set_link(NetClientState *nc)
+{
+ ETRAXFSEthState *eth = qemu_get_nic_opaque(nc);
+ D(printf("%s %d\n", __func__, nc->link_down));
+ eth->phy.link = !nc->link_down;
+}
+
+static const MemoryRegionOps eth_ops = {
+ .read = eth_read,
+ .write = eth_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static NetClientInfo net_etraxfs_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = eth_receive,
+ .link_status_changed = eth_set_link,
+};
+
+static int fs_eth_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ ETRAXFSEthState *s = ETRAX_FS_ETH(dev);
+
+ if (!s->dma_out || !s->dma_in) {
+ hw_error("Unconnected ETRAX-FS Ethernet MAC.\n");
+ }
+
+ s->dma_out->client.push = eth_tx_push;
+ s->dma_out->client.opaque = s;
+ s->dma_in->client.opaque = s;
+ s->dma_in->client.pull = NULL;
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), &eth_ops, s,
+ "etraxfs-eth", 0x5c);
+ sysbus_init_mmio(sbd, &s->mmio);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(&net_etraxfs_info, &s->conf,
+ object_get_typename(OBJECT(s)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+
+ tdk_init(&s->phy);
+ mdio_attach(&s->mdio_bus, &s->phy, s->phyaddr);
+ return 0;
+}
+
+static Property etraxfs_eth_properties[] = {
+ DEFINE_PROP_UINT32("phyaddr", ETRAXFSEthState, phyaddr, 1),
+ DEFINE_PROP_PTR("dma_out", ETRAXFSEthState, vdma_out),
+ DEFINE_PROP_PTR("dma_in", ETRAXFSEthState, vdma_in),
+ DEFINE_NIC_PROPERTIES(ETRAXFSEthState, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void etraxfs_eth_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = fs_eth_init;
+ dc->props = etraxfs_eth_properties;
+ /* Reason: pointer properties "dma_out", "dma_in" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo etraxfs_eth_info = {
+ .name = TYPE_ETRAX_FS_ETH,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ETRAXFSEthState),
+ .class_init = etraxfs_eth_class_init,
+};
+
+static void etraxfs_eth_register_types(void)
+{
+ type_register_static(&etraxfs_eth_info);
+}
+
+type_init(etraxfs_eth_register_types)
diff --git a/hw/net/fsl_etsec/etsec.c b/hw/net/fsl_etsec/etsec.c
new file mode 100644
index 00000000..0f5cf447
--- /dev/null
+++ b/hw/net/fsl_etsec/etsec.c
@@ -0,0 +1,456 @@
+/*
+ * QEMU Freescale eTSEC Emulator
+ *
+ * Copyright (c) 2011-2013 AdaCore
+ *
+ * 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.
+ */
+
+/*
+ * This implementation doesn't include ring priority, TCP/IP Off-Load, QoS.
+ */
+
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "hw/ptimer.h"
+#include "etsec.h"
+#include "registers.h"
+
+/* #define HEX_DUMP */
+/* #define DEBUG_REGISTER */
+
+#ifdef DEBUG_REGISTER
+static const int debug_etsec = 1;
+#else
+static const int debug_etsec;
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (debug_etsec) { \
+ qemu_log(fmt , ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+static uint64_t etsec_read(void *opaque, hwaddr addr, unsigned size)
+{
+ eTSEC *etsec = opaque;
+ uint32_t reg_index = addr / 4;
+ eTSEC_Register *reg = NULL;
+ uint32_t ret = 0x0;
+
+ assert(reg_index < ETSEC_REG_NUMBER);
+
+ reg = &etsec->regs[reg_index];
+
+
+ switch (reg->access) {
+ case ACC_WO:
+ ret = 0x00000000;
+ break;
+
+ case ACC_RW:
+ case ACC_W1C:
+ case ACC_RO:
+ default:
+ ret = reg->value;
+ break;
+ }
+
+ DPRINTF("Read 0x%08x @ 0x" TARGET_FMT_plx
+ " : %s (%s)\n",
+ ret, addr, reg->name, reg->desc);
+
+ return ret;
+}
+
+static void write_tstat(eTSEC *etsec,
+ eTSEC_Register *reg,
+ uint32_t reg_index,
+ uint32_t value)
+{
+ int i = 0;
+
+ for (i = 0; i < 8; i++) {
+ /* Check THLTi flag in TSTAT */
+ if (value & (1 << (31 - i))) {
+ etsec_walk_tx_ring(etsec, i);
+ }
+ }
+
+ /* Write 1 to clear */
+ reg->value &= ~value;
+}
+
+static void write_rstat(eTSEC *etsec,
+ eTSEC_Register *reg,
+ uint32_t reg_index,
+ uint32_t value)
+{
+ int i = 0;
+
+ for (i = 0; i < 8; i++) {
+ /* Check QHLTi flag in RSTAT */
+ if (value & (1 << (23 - i)) && !(reg->value & (1 << (23 - i)))) {
+ etsec_walk_rx_ring(etsec, i);
+ }
+ }
+
+ /* Write 1 to clear */
+ reg->value &= ~value;
+}
+
+static void write_tbasex(eTSEC *etsec,
+ eTSEC_Register *reg,
+ uint32_t reg_index,
+ uint32_t value)
+{
+ reg->value = value & ~0x7;
+
+ /* Copy this value in the ring's TxBD pointer */
+ etsec->regs[TBPTR0 + (reg_index - TBASE0)].value = value & ~0x7;
+}
+
+static void write_rbasex(eTSEC *etsec,
+ eTSEC_Register *reg,
+ uint32_t reg_index,
+ uint32_t value)
+{
+ reg->value = value & ~0x7;
+
+ /* Copy this value in the ring's RxBD pointer */
+ etsec->regs[RBPTR0 + (reg_index - RBASE0)].value = value & ~0x7;
+}
+
+static void write_ievent(eTSEC *etsec,
+ eTSEC_Register *reg,
+ uint32_t reg_index,
+ uint32_t value)
+{
+ /* Write 1 to clear */
+ reg->value &= ~value;
+
+ if (!(reg->value & (IEVENT_TXF | IEVENT_TXF))) {
+ qemu_irq_lower(etsec->tx_irq);
+ }
+ if (!(reg->value & (IEVENT_RXF | IEVENT_RXF))) {
+ qemu_irq_lower(etsec->rx_irq);
+ }
+
+ if (!(reg->value & (IEVENT_MAG | IEVENT_GTSC | IEVENT_GRSC | IEVENT_TXC |
+ IEVENT_RXC | IEVENT_BABR | IEVENT_BABT | IEVENT_LC |
+ IEVENT_CRL | IEVENT_FGPI | IEVENT_FIR | IEVENT_FIQ |
+ IEVENT_DPE | IEVENT_PERR | IEVENT_EBERR | IEVENT_TXE |
+ IEVENT_XFUN | IEVENT_BSY | IEVENT_MSRO | IEVENT_MMRD |
+ IEVENT_MMRW))) {
+ qemu_irq_lower(etsec->err_irq);
+ }
+}
+
+static void write_dmactrl(eTSEC *etsec,
+ eTSEC_Register *reg,
+ uint32_t reg_index,
+ uint32_t value)
+{
+ reg->value = value;
+
+ if (value & DMACTRL_GRS) {
+
+ if (etsec->rx_buffer_len != 0) {
+ /* Graceful receive stop delayed until end of frame */
+ } else {
+ /* Graceful receive stop now */
+ etsec->regs[IEVENT].value |= IEVENT_GRSC;
+ if (etsec->regs[IMASK].value & IMASK_GRSCEN) {
+ qemu_irq_raise(etsec->err_irq);
+ }
+ }
+ }
+
+ if (value & DMACTRL_GTS) {
+
+ if (etsec->tx_buffer_len != 0) {
+ /* Graceful transmit stop delayed until end of frame */
+ } else {
+ /* Graceful transmit stop now */
+ etsec->regs[IEVENT].value |= IEVENT_GTSC;
+ if (etsec->regs[IMASK].value & IMASK_GTSCEN) {
+ qemu_irq_raise(etsec->err_irq);
+ }
+ }
+ }
+
+ if (!(value & DMACTRL_WOP)) {
+ /* Start polling */
+ ptimer_stop(etsec->ptimer);
+ ptimer_set_count(etsec->ptimer, 1);
+ ptimer_run(etsec->ptimer, 1);
+ }
+}
+
+static void etsec_write(void *opaque,
+ hwaddr addr,
+ uint64_t value,
+ unsigned size)
+{
+ eTSEC *etsec = opaque;
+ uint32_t reg_index = addr / 4;
+ eTSEC_Register *reg = NULL;
+ uint32_t before = 0x0;
+
+ assert(reg_index < ETSEC_REG_NUMBER);
+
+ reg = &etsec->regs[reg_index];
+ before = reg->value;
+
+ switch (reg_index) {
+ case IEVENT:
+ write_ievent(etsec, reg, reg_index, value);
+ break;
+
+ case DMACTRL:
+ write_dmactrl(etsec, reg, reg_index, value);
+ break;
+
+ case TSTAT:
+ write_tstat(etsec, reg, reg_index, value);
+ break;
+
+ case RSTAT:
+ write_rstat(etsec, reg, reg_index, value);
+ break;
+
+ case TBASE0 ... TBASE7:
+ write_tbasex(etsec, reg, reg_index, value);
+ break;
+
+ case RBASE0 ... RBASE7:
+ write_rbasex(etsec, reg, reg_index, value);
+ break;
+
+ case MIIMCFG ... MIIMIND:
+ etsec_write_miim(etsec, reg, reg_index, value);
+ break;
+
+ default:
+ /* Default handling */
+ switch (reg->access) {
+
+ case ACC_RW:
+ case ACC_WO:
+ reg->value = value;
+ break;
+
+ case ACC_W1C:
+ reg->value &= ~value;
+ break;
+
+ case ACC_RO:
+ default:
+ /* Read Only or Unknown register */
+ break;
+ }
+ }
+
+ DPRINTF("Write 0x%08x @ 0x" TARGET_FMT_plx
+ " val:0x%08x->0x%08x : %s (%s)\n",
+ (unsigned int)value, addr, before, reg->value,
+ reg->name, reg->desc);
+}
+
+static const MemoryRegionOps etsec_ops = {
+ .read = etsec_read,
+ .write = etsec_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void etsec_timer_hit(void *opaque)
+{
+ eTSEC *etsec = opaque;
+
+ ptimer_stop(etsec->ptimer);
+
+ if (!(etsec->regs[DMACTRL].value & DMACTRL_WOP)) {
+
+ if (!(etsec->regs[DMACTRL].value & DMACTRL_GTS)) {
+ etsec_walk_tx_ring(etsec, 0);
+ }
+ ptimer_set_count(etsec->ptimer, 1);
+ ptimer_run(etsec->ptimer, 1);
+ }
+}
+
+static void etsec_reset(DeviceState *d)
+{
+ eTSEC *etsec = ETSEC_COMMON(d);
+ int i = 0;
+ int reg_index = 0;
+
+ /* Default value for all registers */
+ for (i = 0; i < ETSEC_REG_NUMBER; i++) {
+ etsec->regs[i].name = "Reserved";
+ etsec->regs[i].desc = "";
+ etsec->regs[i].access = ACC_UNKNOWN;
+ etsec->regs[i].value = 0x00000000;
+ }
+
+ /* Set-up known registers */
+ for (i = 0; eTSEC_registers_def[i].name != NULL; i++) {
+
+ reg_index = eTSEC_registers_def[i].offset / 4;
+
+ etsec->regs[reg_index].name = eTSEC_registers_def[i].name;
+ etsec->regs[reg_index].desc = eTSEC_registers_def[i].desc;
+ etsec->regs[reg_index].access = eTSEC_registers_def[i].access;
+ etsec->regs[reg_index].value = eTSEC_registers_def[i].reset;
+ }
+
+ etsec->tx_buffer = NULL;
+ etsec->tx_buffer_len = 0;
+ etsec->rx_buffer = NULL;
+ etsec->rx_buffer_len = 0;
+
+ etsec->phy_status =
+ MII_SR_EXTENDED_CAPS | MII_SR_LINK_STATUS | MII_SR_AUTONEG_CAPS |
+ MII_SR_AUTONEG_COMPLETE | MII_SR_PREAMBLE_SUPPRESS |
+ MII_SR_EXTENDED_STATUS | MII_SR_100T2_HD_CAPS | MII_SR_100T2_FD_CAPS |
+ MII_SR_10T_HD_CAPS | MII_SR_10T_FD_CAPS | MII_SR_100X_HD_CAPS |
+ MII_SR_100X_FD_CAPS | MII_SR_100T4_CAPS;
+}
+
+static ssize_t etsec_receive(NetClientState *nc,
+ const uint8_t *buf,
+ size_t size)
+{
+ ssize_t ret;
+ eTSEC *etsec = qemu_get_nic_opaque(nc);
+
+#if defined(HEX_DUMP)
+ fprintf(stderr, "%s receive size:%d\n", etsec->nic->nc.name, size);
+ qemu_hexdump(buf, stderr, "", size);
+#endif
+ /* Flush is unnecessary as are already in receiving path */
+ etsec->need_flush = false;
+ ret = etsec_rx_ring_write(etsec, buf, size);
+ if (ret == 0) {
+ /* The packet will be queued, let's flush it when buffer is avilable
+ * again. */
+ etsec->need_flush = true;
+ }
+ return ret;
+}
+
+
+static void etsec_set_link_status(NetClientState *nc)
+{
+ eTSEC *etsec = qemu_get_nic_opaque(nc);
+
+ etsec_miim_link_status(etsec, nc);
+}
+
+static NetClientInfo net_etsec_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = etsec_receive,
+ .link_status_changed = etsec_set_link_status,
+};
+
+static void etsec_realize(DeviceState *dev, Error **errp)
+{
+ eTSEC *etsec = ETSEC_COMMON(dev);
+
+ etsec->nic = qemu_new_nic(&net_etsec_info, &etsec->conf,
+ object_get_typename(OBJECT(dev)), dev->id, etsec);
+ qemu_format_nic_info_str(qemu_get_queue(etsec->nic), etsec->conf.macaddr.a);
+
+
+ etsec->bh = qemu_bh_new(etsec_timer_hit, etsec);
+ etsec->ptimer = ptimer_init(etsec->bh);
+ ptimer_set_freq(etsec->ptimer, 100);
+}
+
+static void etsec_instance_init(Object *obj)
+{
+ eTSEC *etsec = ETSEC_COMMON(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&etsec->io_area, OBJECT(etsec), &etsec_ops, etsec,
+ "eTSEC", 0x1000);
+ sysbus_init_mmio(sbd, &etsec->io_area);
+
+ sysbus_init_irq(sbd, &etsec->tx_irq);
+ sysbus_init_irq(sbd, &etsec->rx_irq);
+ sysbus_init_irq(sbd, &etsec->err_irq);
+}
+
+static Property etsec_properties[] = {
+ DEFINE_NIC_PROPERTIES(eTSEC, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void etsec_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = etsec_realize;
+ dc->reset = etsec_reset;
+ dc->props = etsec_properties;
+}
+
+static TypeInfo etsec_info = {
+ .name = "eTSEC",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(eTSEC),
+ .class_init = etsec_class_init,
+ .instance_init = etsec_instance_init,
+};
+
+static void etsec_register_types(void)
+{
+ type_register_static(&etsec_info);
+}
+
+type_init(etsec_register_types)
+
+DeviceState *etsec_create(hwaddr base,
+ MemoryRegion * mr,
+ NICInfo * nd,
+ qemu_irq tx_irq,
+ qemu_irq rx_irq,
+ qemu_irq err_irq)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "eTSEC");
+ qdev_set_nic_properties(dev, nd);
+ qdev_init_nofail(dev);
+
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, tx_irq);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, rx_irq);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, err_irq);
+
+ memory_region_add_subregion(mr, base,
+ SYS_BUS_DEVICE(dev)->mmio[0].memory);
+
+ return dev;
+}
diff --git a/hw/net/fsl_etsec/etsec.h b/hw/net/fsl_etsec/etsec.h
new file mode 100644
index 00000000..e7dc0a4b
--- /dev/null
+++ b/hw/net/fsl_etsec/etsec.h
@@ -0,0 +1,176 @@
+/*
+ * QEMU Freescale eTSEC Emulator
+ *
+ * Copyright (c) 2011-2013 AdaCore
+ *
+ * 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.
+ */
+#ifndef _ETSEC_H_
+#define _ETSEC_H_
+
+#include "hw/qdev.h"
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "hw/ptimer.h"
+
+/* Buffer Descriptors */
+
+typedef struct eTSEC_rxtx_bd {
+ uint16_t flags;
+ uint16_t length;
+ uint32_t bufptr;
+} eTSEC_rxtx_bd;
+
+#define BD_WRAP (1 << 13)
+#define BD_INTERRUPT (1 << 12)
+#define BD_LAST (1 << 11)
+
+#define BD_TX_READY (1 << 15)
+#define BD_TX_PADCRC (1 << 14)
+#define BD_TX_TC (1 << 10)
+#define BD_TX_PREDEF (1 << 9)
+#define BD_TX_HFELC (1 << 7)
+#define BD_TX_CFRL (1 << 6)
+#define BD_TX_RC_MASK 0xF
+#define BD_TX_RC_OFFSET 0x2
+#define BD_TX_TOEUN (1 << 1)
+#define BD_TX_TR (1 << 0)
+
+#define BD_RX_EMPTY (1 << 15)
+#define BD_RX_RO1 (1 << 14)
+#define BD_RX_FIRST (1 << 10)
+#define BD_RX_MISS (1 << 8)
+#define BD_RX_BROADCAST (1 << 7)
+#define BD_RX_MULTICAST (1 << 6)
+#define BD_RX_LG (1 << 5)
+#define BD_RX_NO (1 << 4)
+#define BD_RX_SH (1 << 3)
+#define BD_RX_CR (1 << 2)
+#define BD_RX_OV (1 << 1)
+#define BD_RX_TR (1 << 0)
+
+/* Tx FCB flags */
+#define FCB_TX_VLN (1 << 7)
+#define FCB_TX_IP (1 << 6)
+#define FCB_TX_IP6 (1 << 5)
+#define FCB_TX_TUP (1 << 4)
+#define FCB_TX_UDP (1 << 3)
+#define FCB_TX_CIP (1 << 2)
+#define FCB_TX_CTU (1 << 1)
+#define FCB_TX_NPH (1 << 0)
+
+/* PHY Status Register */
+#define MII_SR_EXTENDED_CAPS 0x0001 /* Extended register capabilities */
+#define MII_SR_JABBER_DETECT 0x0002 /* Jabber Detected */
+#define MII_SR_LINK_STATUS 0x0004 /* Link Status 1 = link */
+#define MII_SR_AUTONEG_CAPS 0x0008 /* Auto Neg Capable */
+#define MII_SR_REMOTE_FAULT 0x0010 /* Remote Fault Detect */
+#define MII_SR_AUTONEG_COMPLETE 0x0020 /* Auto Neg Complete */
+#define MII_SR_PREAMBLE_SUPPRESS 0x0040 /* Preamble may be suppressed */
+#define MII_SR_EXTENDED_STATUS 0x0100 /* Ext. status info in Reg 0x0F */
+#define MII_SR_100T2_HD_CAPS 0x0200 /* 100T2 Half Duplex Capable */
+#define MII_SR_100T2_FD_CAPS 0x0400 /* 100T2 Full Duplex Capable */
+#define MII_SR_10T_HD_CAPS 0x0800 /* 10T Half Duplex Capable */
+#define MII_SR_10T_FD_CAPS 0x1000 /* 10T Full Duplex Capable */
+#define MII_SR_100X_HD_CAPS 0x2000 /* 100X Half Duplex Capable */
+#define MII_SR_100X_FD_CAPS 0x4000 /* 100X Full Duplex Capable */
+#define MII_SR_100T4_CAPS 0x8000 /* 100T4 Capable */
+
+/* eTSEC */
+
+/* Number of register in the device */
+#define ETSEC_REG_NUMBER 1024
+
+typedef struct eTSEC_Register {
+ const char *name;
+ const char *desc;
+ uint32_t access;
+ uint32_t value;
+} eTSEC_Register;
+
+typedef struct eTSEC {
+ SysBusDevice busdev;
+
+ MemoryRegion io_area;
+
+ eTSEC_Register regs[ETSEC_REG_NUMBER];
+
+ NICState *nic;
+ NICConf conf;
+
+ /* Tx */
+
+ uint8_t *tx_buffer;
+ uint32_t tx_buffer_len;
+ eTSEC_rxtx_bd first_bd;
+
+ /* Rx */
+
+ uint8_t *rx_buffer;
+ uint32_t rx_buffer_len;
+ uint32_t rx_remaining_data;
+ uint8_t rx_first_in_frame;
+ uint8_t rx_fcb_size;
+ eTSEC_rxtx_bd rx_first_bd;
+ uint8_t rx_fcb[10];
+ uint32_t rx_padding;
+
+ /* IRQs */
+ qemu_irq tx_irq;
+ qemu_irq rx_irq;
+ qemu_irq err_irq;
+
+
+ uint16_t phy_status;
+ uint16_t phy_control;
+
+ /* Polling */
+ QEMUBH *bh;
+ struct ptimer_state *ptimer;
+
+ /* Whether we should flush the rx queue when buffer becomes available. */
+ bool need_flush;
+} eTSEC;
+
+#define TYPE_ETSEC_COMMON "eTSEC"
+#define ETSEC_COMMON(obj) \
+ OBJECT_CHECK(eTSEC, (obj), TYPE_ETSEC_COMMON)
+
+#define eTSEC_TRANSMIT 1
+#define eTSEC_RECEIVE 2
+
+DeviceState *etsec_create(hwaddr base,
+ MemoryRegion *mr,
+ NICInfo *nd,
+ qemu_irq tx_irq,
+ qemu_irq rx_irq,
+ qemu_irq err_irq);
+
+void etsec_walk_tx_ring(eTSEC *etsec, int ring_nbr);
+void etsec_walk_rx_ring(eTSEC *etsec, int ring_nbr);
+ssize_t etsec_rx_ring_write(eTSEC *etsec, const uint8_t *buf, size_t size);
+
+void etsec_write_miim(eTSEC *etsec,
+ eTSEC_Register *reg,
+ uint32_t reg_index,
+ uint32_t value);
+
+void etsec_miim_link_status(eTSEC *etsec, NetClientState *nc);
+
+#endif /* ! _ETSEC_H_ */
diff --git a/hw/net/fsl_etsec/miim.c b/hw/net/fsl_etsec/miim.c
new file mode 100644
index 00000000..1931b74e
--- /dev/null
+++ b/hw/net/fsl_etsec/miim.c
@@ -0,0 +1,146 @@
+/*
+ * QEMU Freescale eTSEC Emulator
+ *
+ * Copyright (c) 2011-2013 AdaCore
+ *
+ * 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.
+ */
+
+#include "etsec.h"
+#include "registers.h"
+
+/* #define DEBUG_MIIM */
+
+#define MIIM_CONTROL 0
+#define MIIM_STATUS 1
+#define MIIM_PHY_ID_1 2
+#define MIIM_PHY_ID_2 3
+#define MIIM_T2_STATUS 10
+#define MIIM_EXT_STATUS 15
+
+static void miim_read_cycle(eTSEC *etsec)
+{
+ uint8_t phy;
+ uint8_t addr;
+ uint16_t value;
+
+ phy = (etsec->regs[MIIMADD].value >> 8) & 0x1F;
+ (void)phy; /* Unreferenced */
+ addr = etsec->regs[MIIMADD].value & 0x1F;
+
+ switch (addr) {
+ case MIIM_CONTROL:
+ value = etsec->phy_control;
+ break;
+ case MIIM_STATUS:
+ value = etsec->phy_status;
+ break;
+ case MIIM_T2_STATUS:
+ value = 0x1800; /* Local and remote receivers OK */
+ break;
+ default:
+ value = 0x0;
+ break;
+ };
+
+#ifdef DEBUG_MIIM
+ qemu_log("%s phy:%d addr:0x%x value:0x%x\n", __func__, phy, addr, value);
+#endif
+
+ etsec->regs[MIIMSTAT].value = value;
+}
+
+static void miim_write_cycle(eTSEC *etsec)
+{
+ uint8_t phy;
+ uint8_t addr;
+ uint16_t value;
+
+ phy = (etsec->regs[MIIMADD].value >> 8) & 0x1F;
+ (void)phy; /* Unreferenced */
+ addr = etsec->regs[MIIMADD].value & 0x1F;
+ value = etsec->regs[MIIMCON].value & 0xffff;
+
+#ifdef DEBUG_MIIM
+ qemu_log("%s phy:%d addr:0x%x value:0x%x\n", __func__, phy, addr, value);
+#endif
+
+ switch (addr) {
+ case MIIM_CONTROL:
+ etsec->phy_control = value & ~(0x8100);
+ break;
+ default:
+ break;
+ };
+}
+
+void etsec_write_miim(eTSEC *etsec,
+ eTSEC_Register *reg,
+ uint32_t reg_index,
+ uint32_t value)
+{
+
+ switch (reg_index) {
+
+ case MIIMCOM:
+ /* Read and scan cycle */
+
+ if ((!(reg->value & MIIMCOM_READ)) && (value & MIIMCOM_READ)) {
+ /* Read */
+ miim_read_cycle(etsec);
+ }
+ reg->value = value;
+ break;
+
+ case MIIMCON:
+ reg->value = value & 0xffff;
+ miim_write_cycle(etsec);
+ break;
+
+ default:
+ /* Default handling */
+ switch (reg->access) {
+
+ case ACC_RW:
+ case ACC_WO:
+ reg->value = value;
+ break;
+
+ case ACC_W1C:
+ reg->value &= ~value;
+ break;
+
+ case ACC_RO:
+ default:
+ /* Read Only or Unknown register */
+ break;
+ }
+ }
+
+}
+
+void etsec_miim_link_status(eTSEC *etsec, NetClientState *nc)
+{
+ /* Set link status */
+ if (nc->link_down) {
+ etsec->phy_status &= ~MII_SR_LINK_STATUS;
+ } else {
+ etsec->phy_status |= MII_SR_LINK_STATUS;
+ }
+}
diff --git a/hw/net/fsl_etsec/registers.c b/hw/net/fsl_etsec/registers.c
new file mode 100644
index 00000000..a7bbfa11
--- /dev/null
+++ b/hw/net/fsl_etsec/registers.c
@@ -0,0 +1,295 @@
+/*
+ * QEMU Freescale eTSEC Emulator
+ *
+ * Copyright (c) 2011-2013 AdaCore
+ *
+ * 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.
+ */
+#include "registers.h"
+
+const eTSEC_Register_Definition eTSEC_registers_def[] = {
+{0x000, "TSEC_ID", "Controller ID register", ACC_RO, 0x01240000},
+{0x004, "TSEC_ID2", "Controller ID register 2", ACC_RO, 0x003000F0},
+{0x010, "IEVENT", "Interrupt event register", ACC_W1C, 0x00000000},
+{0x014, "IMASK", "Interrupt mask register", ACC_RW, 0x00000000},
+{0x018, "EDIS", "Error disabled register", ACC_RW, 0x00000000},
+{0x020, "ECNTRL", "Ethernet control register", ACC_RW, 0x00000040},
+{0x028, "PTV", "Pause time value register", ACC_RW, 0x00000000},
+{0x02C, "DMACTRL", "DMA control register", ACC_RW, 0x00000000},
+{0x030, "TBIPA", "TBI PHY address register", ACC_RW, 0x00000000},
+
+/* eTSEC FIFO Control and Status Registers */
+
+{0x058, "FIFO_RX_ALARM", "FIFO receive alarm start threshold register", ACC_RW, 0x00000040},
+{0x05C, "FIFO_RX_ALARM_SHUTOFF", "FIFO receive alarm shut-off threshold register", ACC_RW, 0x00000080},
+{0x08C, "FIFO_TX_THR", "FIFO transmit threshold register", ACC_RW, 0x00000080},
+{0x098, "FIFO_TX_STARVE", "FIFO transmit starve register", ACC_RW, 0x00000040},
+{0x09C, "FIFO_TX_STARVE_SHUTOFF", "FIFO transmit starve shut-off register", ACC_RW, 0x00000080},
+
+/* eTSEC Transmit Control and Status Registers */
+
+{0x100, "TCTRL", "Transmit control register", ACC_RW, 0x00000000},
+{0x104, "TSTAT", "Transmit status register", ACC_W1C, 0x00000000},
+{0x108, "DFVLAN", "Default VLAN control word", ACC_RW, 0x81000000},
+{0x110, "TXIC", "Transmit interrupt coalescing register", ACC_RW, 0x00000000},
+{0x114, "TQUEUE", "Transmit queue control register", ACC_RW, 0x00008000},
+{0x140, "TR03WT", "TxBD Rings 0-3 round-robin weightings", ACC_RW, 0x00000000},
+{0x144, "TR47WT", "TxBD Rings 4-7 round-robin weightings", ACC_RW, 0x00000000},
+{0x180, "TBDBPH", "Tx data buffer pointer high bits", ACC_RW, 0x00000000},
+{0x184, "TBPTR0", "TxBD pointer for ring 0", ACC_RW, 0x00000000},
+{0x18C, "TBPTR1", "TxBD pointer for ring 1", ACC_RW, 0x00000000},
+{0x194, "TBPTR2", "TxBD pointer for ring 2", ACC_RW, 0x00000000},
+{0x19C, "TBPTR3", "TxBD pointer for ring 3", ACC_RW, 0x00000000},
+{0x1A4, "TBPTR4", "TxBD pointer for ring 4", ACC_RW, 0x00000000},
+{0x1AC, "TBPTR5", "TxBD pointer for ring 5", ACC_RW, 0x00000000},
+{0x1B4, "TBPTR6", "TxBD pointer for ring 6", ACC_RW, 0x00000000},
+{0x1BC, "TBPTR7", "TxBD pointer for ring 7", ACC_RW, 0x00000000},
+{0x200, "TBASEH", "TxBD base address high bits", ACC_RW, 0x00000000},
+{0x204, "TBASE0", "TxBD base address of ring 0", ACC_RW, 0x00000000},
+{0x20C, "TBASE1", "TxBD base address of ring 1", ACC_RW, 0x00000000},
+{0x214, "TBASE2", "TxBD base address of ring 2", ACC_RW, 0x00000000},
+{0x21C, "TBASE3", "TxBD base address of ring 3", ACC_RW, 0x00000000},
+{0x224, "TBASE4", "TxBD base address of ring 4", ACC_RW, 0x00000000},
+{0x22C, "TBASE5", "TxBD base address of ring 5", ACC_RW, 0x00000000},
+{0x234, "TBASE6", "TxBD base address of ring 6", ACC_RW, 0x00000000},
+{0x23C, "TBASE7", "TxBD base address of ring 7", ACC_RW, 0x00000000},
+{0x280, "TMR_TXTS1_ID", "Tx time stamp identification tag (set 1)", ACC_RO, 0x00000000},
+{0x284, "TMR_TXTS2_ID", "Tx time stamp identification tag (set 2)", ACC_RO, 0x00000000},
+{0x2C0, "TMR_TXTS1_H", "Tx time stamp high (set 1)", ACC_RO, 0x00000000},
+{0x2C4, "TMR_TXTS1_L", "Tx time stamp high (set 1)", ACC_RO, 0x00000000},
+{0x2C8, "TMR_TXTS2_H", "Tx time stamp high (set 2)", ACC_RO, 0x00000000},
+{0x2CC, "TMR_TXTS2_L", "Tx time stamp high (set 2)", ACC_RO, 0x00000000},
+
+/* eTSEC Receive Control and Status Registers */
+
+{0x300, "RCTRL", "Receive control register", ACC_RW, 0x00000000},
+{0x304, "RSTAT", "Receive status register", ACC_W1C, 0x00000000},
+{0x310, "RXIC", "Receive interrupt coalescing register", ACC_RW, 0x00000000},
+{0x314, "RQUEUE", "Receive queue control register.", ACC_RW, 0x00800080},
+{0x330, "RBIFX", "Receive bit field extract control register", ACC_RW, 0x00000000},
+{0x334, "RQFAR", "Receive queue filing table address register", ACC_RW, 0x00000000},
+{0x338, "RQFCR", "Receive queue filing table control register", ACC_RW, 0x00000000},
+{0x33C, "RQFPR", "Receive queue filing table property register", ACC_RW, 0x00000000},
+{0x340, "MRBLR", "Maximum receive buffer length register", ACC_RW, 0x00000000},
+{0x380, "RBDBPH", "Rx data buffer pointer high bits", ACC_RW, 0x00000000},
+{0x384, "RBPTR0", "RxBD pointer for ring 0", ACC_RW, 0x00000000},
+{0x38C, "RBPTR1", "RxBD pointer for ring 1", ACC_RW, 0x00000000},
+{0x394, "RBPTR2", "RxBD pointer for ring 2", ACC_RW, 0x00000000},
+{0x39C, "RBPTR3", "RxBD pointer for ring 3", ACC_RW, 0x00000000},
+{0x3A4, "RBPTR4", "RxBD pointer for ring 4", ACC_RW, 0x00000000},
+{0x3AC, "RBPTR5", "RxBD pointer for ring 5", ACC_RW, 0x00000000},
+{0x3B4, "RBPTR6", "RxBD pointer for ring 6", ACC_RW, 0x00000000},
+{0x3BC, "RBPTR7", "RxBD pointer for ring 7", ACC_RW, 0x00000000},
+{0x400, "RBASEH", "RxBD base address high bits", ACC_RW, 0x00000000},
+{0x404, "RBASE0", "RxBD base address of ring 0", ACC_RW, 0x00000000},
+{0x40C, "RBASE1", "RxBD base address of ring 1", ACC_RW, 0x00000000},
+{0x414, "RBASE2", "RxBD base address of ring 2", ACC_RW, 0x00000000},
+{0x41C, "RBASE3", "RxBD base address of ring 3", ACC_RW, 0x00000000},
+{0x424, "RBASE4", "RxBD base address of ring 4", ACC_RW, 0x00000000},
+{0x42C, "RBASE5", "RxBD base address of ring 5", ACC_RW, 0x00000000},
+{0x434, "RBASE6", "RxBD base address of ring 6", ACC_RW, 0x00000000},
+{0x43C, "RBASE7", "RxBD base address of ring 7", ACC_RW, 0x00000000},
+{0x4C0, "TMR_RXTS_H", "Rx timer time stamp register high", ACC_RW, 0x00000000},
+{0x4C4, "TMR_RXTS_L", "Rx timer time stamp register low", ACC_RW, 0x00000000},
+
+/* eTSEC MAC Registers */
+
+{0x500, "MACCFG1", "MAC configuration register 1", ACC_RW, 0x00000000},
+{0x504, "MACCFG2", "MAC configuration register 2", ACC_RW, 0x00007000},
+{0x508, "IPGIFG", "Inter-packet/inter-frame gap register", ACC_RW, 0x40605060},
+{0x50C, "HAFDUP", "Half-duplex control", ACC_RW, 0x00A1F037},
+{0x510, "MAXFRM", "Maximum frame length", ACC_RW, 0x00000600},
+{0x520, "MIIMCFG", "MII management configuration", ACC_RW, 0x00000007},
+{0x524, "MIIMCOM", "MII management command", ACC_RW, 0x00000000},
+{0x528, "MIIMADD", "MII management address", ACC_RW, 0x00000000},
+{0x52C, "MIIMCON", "MII management control", ACC_WO, 0x00000000},
+{0x530, "MIIMSTAT", "MII management status", ACC_RO, 0x00000000},
+{0x534, "MIIMIND", "MII management indicator", ACC_RO, 0x00000000},
+{0x53C, "IFSTAT", "Interface status", ACC_RO, 0x00000000},
+{0x540, "MACSTNADDR1", "MAC station address register 1", ACC_RW, 0x00000000},
+{0x544, "MACSTNADDR2", "MAC station address register 2", ACC_RW, 0x00000000},
+{0x548, "MAC01ADDR1", "MAC exact match address 1, part 1", ACC_RW, 0x00000000},
+{0x54C, "MAC01ADDR2", "MAC exact match address 1, part 2", ACC_RW, 0x00000000},
+{0x550, "MAC02ADDR1", "MAC exact match address 2, part 1", ACC_RW, 0x00000000},
+{0x554, "MAC02ADDR2", "MAC exact match address 2, part 2", ACC_RW, 0x00000000},
+{0x558, "MAC03ADDR1", "MAC exact match address 3, part 1", ACC_RW, 0x00000000},
+{0x55C, "MAC03ADDR2", "MAC exact match address 3, part 2", ACC_RW, 0x00000000},
+{0x560, "MAC04ADDR1", "MAC exact match address 4, part 1", ACC_RW, 0x00000000},
+{0x564, "MAC04ADDR2", "MAC exact match address 4, part 2", ACC_RW, 0x00000000},
+{0x568, "MAC05ADDR1", "MAC exact match address 5, part 1", ACC_RW, 0x00000000},
+{0x56C, "MAC05ADDR2", "MAC exact match address 5, part 2", ACC_RW, 0x00000000},
+{0x570, "MAC06ADDR1", "MAC exact match address 6, part 1", ACC_RW, 0x00000000},
+{0x574, "MAC06ADDR2", "MAC exact match address 6, part 2", ACC_RW, 0x00000000},
+{0x578, "MAC07ADDR1", "MAC exact match address 7, part 1", ACC_RW, 0x00000000},
+{0x57C, "MAC07ADDR2", "MAC exact match address 7, part 2", ACC_RW, 0x00000000},
+{0x580, "MAC08ADDR1", "MAC exact match address 8, part 1", ACC_RW, 0x00000000},
+{0x584, "MAC08ADDR2", "MAC exact match address 8, part 2", ACC_RW, 0x00000000},
+{0x588, "MAC09ADDR1", "MAC exact match address 9, part 1", ACC_RW, 0x00000000},
+{0x58C, "MAC09ADDR2", "MAC exact match address 9, part 2", ACC_RW, 0x00000000},
+{0x590, "MAC10ADDR1", "MAC exact match address 10, part 1", ACC_RW, 0x00000000},
+{0x594, "MAC10ADDR2", "MAC exact match address 10, part 2", ACC_RW, 0x00000000},
+{0x598, "MAC11ADDR1", "MAC exact match address 11, part 1", ACC_RW, 0x00000000},
+{0x59C, "MAC11ADDR2", "MAC exact match address 11, part 2", ACC_RW, 0x00000000},
+{0x5A0, "MAC12ADDR1", "MAC exact match address 12, part 1", ACC_RW, 0x00000000},
+{0x5A4, "MAC12ADDR2", "MAC exact match address 12, part 2", ACC_RW, 0x00000000},
+{0x5A8, "MAC13ADDR1", "MAC exact match address 13, part 1", ACC_RW, 0x00000000},
+{0x5AC, "MAC13ADDR2", "MAC exact match address 13, part 2", ACC_RW, 0x00000000},
+{0x5B0, "MAC14ADDR1", "MAC exact match address 14, part 1", ACC_RW, 0x00000000},
+{0x5B4, "MAC14ADDR2", "MAC exact match address 14, part 2", ACC_RW, 0x00000000},
+{0x5B8, "MAC15ADDR1", "MAC exact match address 15, part 1", ACC_RW, 0x00000000},
+{0x5BC, "MAC15ADDR2", "MAC exact match address 15, part 2", ACC_RW, 0x00000000},
+
+/* eTSEC, "Transmit", "and", Receive, Counters */
+
+{0x680, "TR64", "Transmit and receive 64-byte frame counter ", ACC_RW, 0x00000000},
+{0x684, "TR127", "Transmit and receive 65- to 127-byte frame counter", ACC_RW, 0x00000000},
+{0x688, "TR255", "Transmit and receive 128- to 255-byte frame counter", ACC_RW, 0x00000000},
+{0x68C, "TR511", "Transmit and receive 256- to 511-byte frame counter", ACC_RW, 0x00000000},
+{0x690, "TR1K", "Transmit and receive 512- to 1023-byte frame counter", ACC_RW, 0x00000000},
+{0x694, "TRMAX", "Transmit and receive 1024- to 1518-byte frame counter", ACC_RW, 0x00000000},
+{0x698, "TRMGV", "Transmit and receive 1519- to 1522-byte good VLAN frame count", ACC_RW, 0x00000000},
+
+/* eTSEC Receive Counters */
+
+{0x69C, "RBYT", "Receive byte counter", ACC_RW, 0x00000000},
+{0x6A0, "RPKT", "Receive packet counter", ACC_RW, 0x00000000},
+{0x6A4, "RFCS", "Receive FCS error counter", ACC_RW, 0x00000000},
+{0x6A8, "RMCA", "Receive multicast packet counter", ACC_RW, 0x00000000},
+{0x6AC, "RBCA", "Receive broadcast packet counter", ACC_RW, 0x00000000},
+{0x6B0, "RXCF", "Receive control frame packet counter ", ACC_RW, 0x00000000},
+{0x6B4, "RXPF", "Receive PAUSE frame packet counter", ACC_RW, 0x00000000},
+{0x6B8, "RXUO", "Receive unknown OP code counter ", ACC_RW, 0x00000000},
+{0x6BC, "RALN", "Receive alignment error counter ", ACC_RW, 0x00000000},
+{0x6C0, "RFLR", "Receive frame length error counter ", ACC_RW, 0x00000000},
+{0x6C4, "RCDE", "Receive code error counter ", ACC_RW, 0x00000000},
+{0x6C8, "RCSE", "Receive carrier sense error counter", ACC_RW, 0x00000000},
+{0x6CC, "RUND", "Receive undersize packet counter", ACC_RW, 0x00000000},
+{0x6D0, "ROVR", "Receive oversize packet counter ", ACC_RW, 0x00000000},
+{0x6D4, "RFRG", "Receive fragments counter", ACC_RW, 0x00000000},
+{0x6D8, "RJBR", "Receive jabber counter ", ACC_RW, 0x00000000},
+{0x6DC, "RDRP", "Receive drop counter", ACC_RW, 0x00000000},
+
+/* eTSEC Transmit Counters */
+
+{0x6E0, "TBYT", "Transmit byte counter", ACC_RW, 0x00000000},
+{0x6E4, "TPKT", "Transmit packet counter", ACC_RW, 0x00000000},
+{0x6E8, "TMCA", "Transmit multicast packet counter ", ACC_RW, 0x00000000},
+{0x6EC, "TBCA", "Transmit broadcast packet counter ", ACC_RW, 0x00000000},
+{0x6F0, "TXPF", "Transmit PAUSE control frame counter ", ACC_RW, 0x00000000},
+{0x6F4, "TDFR", "Transmit deferral packet counter ", ACC_RW, 0x00000000},
+{0x6F8, "TEDF", "Transmit excessive deferral packet counter ", ACC_RW, 0x00000000},
+{0x6FC, "TSCL", "Transmit single collision packet counter", ACC_RW, 0x00000000},
+{0x700, "TMCL", "Transmit multiple collision packet counter", ACC_RW, 0x00000000},
+{0x704, "TLCL", "Transmit late collision packet counter", ACC_RW, 0x00000000},
+{0x708, "TXCL", "Transmit excessive collision packet counter", ACC_RW, 0x00000000},
+{0x70C, "TNCL", "Transmit total collision counter ", ACC_RW, 0x00000000},
+{0x714, "TDRP", "Transmit drop frame counter", ACC_RW, 0x00000000},
+{0x718, "TJBR", "Transmit jabber frame counter ", ACC_RW, 0x00000000},
+{0x71C, "TFCS", "Transmit FCS error counter", ACC_RW, 0x00000000},
+{0x720, "TXCF", "Transmit control frame counter ", ACC_RW, 0x00000000},
+{0x724, "TOVR", "Transmit oversize frame counter", ACC_RW, 0x00000000},
+{0x728, "TUND", "Transmit undersize frame counter ", ACC_RW, 0x00000000},
+{0x72C, "TFRG", "Transmit fragments frame counter ", ACC_RW, 0x00000000},
+
+/* eTSEC Counter Control and TOE Statistics Registers */
+
+{0x730, "CAR1", "Carry register one register", ACC_W1C, 0x00000000},
+{0x734, "CAR2", "Carry register two register ", ACC_W1C, 0x00000000},
+{0x738, "CAM1", "Carry register one mask register ", ACC_RW, 0xFE03FFFF},
+{0x73C, "CAM2", "Carry register two mask register ", ACC_RW, 0x000FFFFD},
+{0x740, "RREJ", "Receive filer rejected packet counter", ACC_RW, 0x00000000},
+
+/* Hash Function Registers */
+
+{0x800, "IGADDR0", "Individual/group address register 0", ACC_RW, 0x00000000},
+{0x804, "IGADDR1", "Individual/group address register 1", ACC_RW, 0x00000000},
+{0x808, "IGADDR2", "Individual/group address register 2", ACC_RW, 0x00000000},
+{0x80C, "IGADDR3", "Individual/group address register 3", ACC_RW, 0x00000000},
+{0x810, "IGADDR4", "Individual/group address register 4", ACC_RW, 0x00000000},
+{0x814, "IGADDR5", "Individual/group address register 5", ACC_RW, 0x00000000},
+{0x818, "IGADDR6", "Individual/group address register 6", ACC_RW, 0x00000000},
+{0x81C, "IGADDR7", "Individual/group address register 7", ACC_RW, 0x00000000},
+{0x880, "GADDR0", "Group address register 0", ACC_RW, 0x00000000},
+{0x884, "GADDR1", "Group address register 1", ACC_RW, 0x00000000},
+{0x888, "GADDR2", "Group address register 2", ACC_RW, 0x00000000},
+{0x88C, "GADDR3", "Group address register 3", ACC_RW, 0x00000000},
+{0x890, "GADDR4", "Group address register 4", ACC_RW, 0x00000000},
+{0x894, "GADDR5", "Group address register 5", ACC_RW, 0x00000000},
+{0x898, "GADDR6", "Group address register 6", ACC_RW, 0x00000000},
+{0x89C, "GADDR7", "Group address register 7", ACC_RW, 0x00000000},
+
+/* eTSEC DMA Attribute Registers */
+
+{0xBF8, "ATTR", "Attribute register", ACC_RW, 0x00000000},
+{0xBFC, "ATTRELI", "Attribute extract length and extract index register", ACC_RW, 0x00000000},
+
+
+/* eTSEC Lossless Flow Control Registers */
+
+{0xC00, "RQPRM0", "Receive Queue Parameters register 0 ", ACC_RW, 0x00000000},
+{0xC04, "RQPRM1", "Receive Queue Parameters register 1 ", ACC_RW, 0x00000000},
+{0xC08, "RQPRM2", "Receive Queue Parameters register 2 ", ACC_RW, 0x00000000},
+{0xC0C, "RQPRM3", "Receive Queue Parameters register 3 ", ACC_RW, 0x00000000},
+{0xC10, "RQPRM4", "Receive Queue Parameters register 4 ", ACC_RW, 0x00000000},
+{0xC14, "RQPRM5", "Receive Queue Parameters register 5 ", ACC_RW, 0x00000000},
+{0xC18, "RQPRM6", "Receive Queue Parameters register 6 ", ACC_RW, 0x00000000},
+{0xC1C, "RQPRM7", "Receive Queue Parameters register 7 ", ACC_RW, 0x00000000},
+{0xC44, "RFBPTR0", "Last Free RxBD pointer for ring 0", ACC_RW, 0x00000000},
+{0xC4C, "RFBPTR1", "Last Free RxBD pointer for ring 1", ACC_RW, 0x00000000},
+{0xC54, "RFBPTR2", "Last Free RxBD pointer for ring 2", ACC_RW, 0x00000000},
+{0xC5C, "RFBPTR3", "Last Free RxBD pointer for ring 3", ACC_RW, 0x00000000},
+{0xC64, "RFBPTR4", "Last Free RxBD pointer for ring 4", ACC_RW, 0x00000000},
+{0xC6C, "RFBPTR5", "Last Free RxBD pointer for ring 5", ACC_RW, 0x00000000},
+{0xC74, "RFBPTR6", "Last Free RxBD pointer for ring 6", ACC_RW, 0x00000000},
+{0xC7C, "RFBPTR7", "Last Free RxBD pointer for ring 7", ACC_RW, 0x00000000},
+
+/* eTSEC Future Expansion Space */
+
+/* Reserved*/
+
+/* eTSEC IEEE 1588 Registers */
+
+{0xE00, "TMR_CTRL", "Timer control register", ACC_RW, 0x00010001},
+{0xE04, "TMR_TEVENT", "time stamp event register", ACC_W1C, 0x00000000},
+{0xE08, "TMR_TEMASK", "Timer event mask register", ACC_RW, 0x00000000},
+{0xE0C, "TMR_PEVENT", "time stamp event register", ACC_RW, 0x00000000},
+{0xE10, "TMR_PEMASK", "Timer event mask register", ACC_RW, 0x00000000},
+{0xE14, "TMR_STAT", "time stamp status register", ACC_RW, 0x00000000},
+{0xE18, "TMR_CNT_H", "timer counter high register", ACC_RW, 0x00000000},
+{0xE1C, "TMR_CNT_L", "timer counter low register", ACC_RW, 0x00000000},
+{0xE20, "TMR_ADD", "Timer drift compensation addend register", ACC_RW, 0x00000000},
+{0xE24, "TMR_ACC", "Timer accumulator register", ACC_RW, 0x00000000},
+{0xE28, "TMR_PRSC", "Timer prescale", ACC_RW, 0x00000002},
+{0xE30, "TMROFF_H", "Timer offset high", ACC_RW, 0x00000000},
+{0xE34, "TMROFF_L", "Timer offset low", ACC_RW, 0x00000000},
+{0xE40, "TMR_ALARM1_H", "Timer alarm 1 high register", ACC_RW, 0xFFFFFFFF},
+{0xE44, "TMR_ALARM1_L", "Timer alarm 1 high register", ACC_RW, 0xFFFFFFFF},
+{0xE48, "TMR_ALARM2_H", "Timer alarm 2 high register", ACC_RW, 0xFFFFFFFF},
+{0xE4C, "TMR_ALARM2_L", "Timer alarm 2 high register", ACC_RW, 0xFFFFFFFF},
+{0xE80, "TMR_FIPER1", "Timer fixed period interval", ACC_RW, 0xFFFFFFFF},
+{0xE84, "TMR_FIPER2", "Timer fixed period interval", ACC_RW, 0xFFFFFFFF},
+{0xE88, "TMR_FIPER3", "Timer fixed period interval", ACC_RW, 0xFFFFFFFF},
+{0xEA0, "TMR_ETTS1_H", "Time stamp of general purpose external trigger ", ACC_RW, 0x00000000},
+{0xEA4, "TMR_ETTS1_L", "Time stamp of general purpose external trigger", ACC_RW, 0x00000000},
+{0xEA8, "TMR_ETTS2_H", "Time stamp of general purpose external trigger ", ACC_RW, 0x00000000},
+{0xEAC, "TMR_ETTS2_L", "Time stamp of general purpose external trigger", ACC_RW, 0x00000000},
+
+/* End Of Table */
+{0x0, 0x0, 0x0, 0x0, 0x0}
+};
diff --git a/hw/net/fsl_etsec/registers.h b/hw/net/fsl_etsec/registers.h
new file mode 100644
index 00000000..7ad76864
--- /dev/null
+++ b/hw/net/fsl_etsec/registers.h
@@ -0,0 +1,320 @@
+/*
+ * QEMU Freescale eTSEC Emulator
+ *
+ * Copyright (c) 2011-2013 AdaCore
+ *
+ * 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.
+ */
+#ifndef _ETSEC_REGISTERS_H_
+#define _ETSEC_REGISTERS_H_
+
+#include <stdint.h>
+
+enum eTSEC_Register_Access_Type {
+ ACC_RW = 1, /* Read/Write */
+ ACC_RO = 2, /* Read Only */
+ ACC_WO = 3, /* Write Only */
+ ACC_W1C = 4, /* Write 1 to clear */
+ ACC_UNKNOWN = 5 /* Unknown register*/
+};
+
+typedef struct eTSEC_Register_Definition {
+ uint32_t offset;
+ const char *name;
+ const char *desc;
+ enum eTSEC_Register_Access_Type access;
+ uint32_t reset;
+} eTSEC_Register_Definition;
+
+extern const eTSEC_Register_Definition eTSEC_registers_def[];
+
+#define DMACTRL_LE (1 << 15)
+#define DMACTRL_GRS (1 << 4)
+#define DMACTRL_GTS (1 << 3)
+#define DMACTRL_WOP (1 << 0)
+
+#define IEVENT_PERR (1 << 0)
+#define IEVENT_DPE (1 << 1)
+#define IEVENT_FIQ (1 << 2)
+#define IEVENT_FIR (1 << 3)
+#define IEVENT_FGPI (1 << 4)
+#define IEVENT_RXF (1 << 7)
+#define IEVENT_GRSC (1 << 8)
+#define IEVENT_MMRW (1 << 9)
+#define IEVENT_MMRD (1 << 10)
+#define IEVENT_MAG (1 << 11)
+#define IEVENT_RXB (1 << 15)
+#define IEVENT_XFUN (1 << 16)
+#define IEVENT_CRL (1 << 17)
+#define IEVENT_LC (1 << 18)
+#define IEVENT_TXF (1 << 20)
+#define IEVENT_TXB (1 << 21)
+#define IEVENT_TXE (1 << 22)
+#define IEVENT_TXC (1 << 23)
+#define IEVENT_BABT (1 << 24)
+#define IEVENT_GTSC (1 << 25)
+#define IEVENT_MSRO (1 << 26)
+#define IEVENT_EBERR (1 << 28)
+#define IEVENT_BSY (1 << 29)
+#define IEVENT_RXC (1 << 30)
+#define IEVENT_BABR (1 << 31)
+
+#define IMASK_RXFEN (1 << 7)
+#define IMASK_GRSCEN (1 << 8)
+#define IMASK_RXBEN (1 << 15)
+#define IMASK_TXFEN (1 << 20)
+#define IMASK_TXBEN (1 << 21)
+#define IMASK_GTSCEN (1 << 25)
+
+#define MACCFG1_TX_EN (1 << 0)
+#define MACCFG1_RX_EN (1 << 2)
+
+#define MACCFG2_CRC_EN (1 << 1)
+#define MACCFG2_PADCRC (1 << 2)
+
+#define MIIMCOM_READ (1 << 0)
+#define MIIMCOM_SCAN (1 << 1)
+
+#define RCTRL_PRSDEP_MASK (0x3)
+#define RCTRL_PRSDEP_OFFSET (6)
+#define RCTRL_RSF (1 << 2)
+
+/* Index of each register */
+
+#define TSEC_ID (0x000 / 4)
+#define TSEC_ID2 (0x004 / 4)
+#define IEVENT (0x010 / 4)
+#define IMASK (0x014 / 4)
+#define EDIS (0x018 / 4)
+#define ECNTRL (0x020 / 4)
+#define PTV (0x028 / 4)
+#define DMACTRL (0x02C / 4)
+#define TBIPA (0x030 / 4)
+#define TCTRL (0x100 / 4)
+#define TSTAT (0x104 / 4)
+#define DFVLAN (0x108 / 4)
+#define TXIC (0x110 / 4)
+#define TQUEUE (0x114 / 4)
+#define TR03WT (0x140 / 4)
+#define TR47WT (0x144 / 4)
+#define TBDBPH (0x180 / 4)
+#define TBPTR0 (0x184 / 4)
+#define TBPTR1 (0x18C / 4)
+#define TBPTR2 (0x194 / 4)
+#define TBPTR3 (0x19C / 4)
+#define TBPTR4 (0x1A4 / 4)
+#define TBPTR5 (0x1AC / 4)
+#define TBPTR6 (0x1B4 / 4)
+#define TBPTR7 (0x1BC / 4)
+#define TBASEH (0x200 / 4)
+#define TBASE0 (0x204 / 4)
+#define TBASE1 (0x20C / 4)
+#define TBASE2 (0x214 / 4)
+#define TBASE3 (0x21C / 4)
+#define TBASE4 (0x224 / 4)
+#define TBASE5 (0x22C / 4)
+#define TBASE6 (0x234 / 4)
+#define TBASE7 (0x23C / 4)
+#define TMR_TXTS1_ID (0x280 / 4)
+#define TMR_TXTS2_ID (0x284 / 4)
+#define TMR_TXTS1_H (0x2C0 / 4)
+#define TMR_TXTS1_L (0x2C4 / 4)
+#define TMR_TXTS2_H (0x2C8 / 4)
+#define TMR_TXTS2_L (0x2CC / 4)
+#define RCTRL (0x300 / 4)
+#define RSTAT (0x304 / 4)
+#define RXIC (0x310 / 4)
+#define RQUEUE (0x314 / 4)
+#define RBIFX (0x330 / 4)
+#define RQFAR (0x334 / 4)
+#define RQFCR (0x338 / 4)
+#define RQFPR (0x33C / 4)
+#define MRBLR (0x340 / 4)
+#define RBDBPH (0x380 / 4)
+#define RBPTR0 (0x384 / 4)
+#define RBPTR1 (0x38C / 4)
+#define RBPTR2 (0x394 / 4)
+#define RBPTR3 (0x39C / 4)
+#define RBPTR4 (0x3A4 / 4)
+#define RBPTR5 (0x3AC / 4)
+#define RBPTR6 (0x3B4 / 4)
+#define RBPTR7 (0x3BC / 4)
+#define RBASEH (0x400 / 4)
+#define RBASE0 (0x404 / 4)
+#define RBASE1 (0x40C / 4)
+#define RBASE2 (0x414 / 4)
+#define RBASE3 (0x41C / 4)
+#define RBASE4 (0x424 / 4)
+#define RBASE5 (0x42C / 4)
+#define RBASE6 (0x434 / 4)
+#define RBASE7 (0x43C / 4)
+#define TMR_RXTS_H (0x4C0 / 4)
+#define TMR_RXTS_L (0x4C4 / 4)
+#define MACCFG1 (0x500 / 4)
+#define MACCFG2 (0x504 / 4)
+#define IPGIFG (0x508 / 4)
+#define HAFDUP (0x50C / 4)
+#define MAXFRM (0x510 / 4)
+#define MIIMCFG (0x520 / 4)
+#define MIIMCOM (0x524 / 4)
+#define MIIMADD (0x528 / 4)
+#define MIIMCON (0x52C / 4)
+#define MIIMSTAT (0x530 / 4)
+#define MIIMIND (0x534 / 4)
+#define IFSTAT (0x53C / 4)
+#define MACSTNADDR1 (0x540 / 4)
+#define MACSTNADDR2 (0x544 / 4)
+#define MAC01ADDR1 (0x548 / 4)
+#define MAC01ADDR2 (0x54C / 4)
+#define MAC02ADDR1 (0x550 / 4)
+#define MAC02ADDR2 (0x554 / 4)
+#define MAC03ADDR1 (0x558 / 4)
+#define MAC03ADDR2 (0x55C / 4)
+#define MAC04ADDR1 (0x560 / 4)
+#define MAC04ADDR2 (0x564 / 4)
+#define MAC05ADDR1 (0x568 / 4)
+#define MAC05ADDR2 (0x56C / 4)
+#define MAC06ADDR1 (0x570 / 4)
+#define MAC06ADDR2 (0x574 / 4)
+#define MAC07ADDR1 (0x578 / 4)
+#define MAC07ADDR2 (0x57C / 4)
+#define MAC08ADDR1 (0x580 / 4)
+#define MAC08ADDR2 (0x584 / 4)
+#define MAC09ADDR1 (0x588 / 4)
+#define MAC09ADDR2 (0x58C / 4)
+#define MAC10ADDR1 (0x590 / 4)
+#define MAC10ADDR2 (0x594 / 4)
+#define MAC11ADDR1 (0x598 / 4)
+#define MAC11ADDR2 (0x59C / 4)
+#define MAC12ADDR1 (0x5A0 / 4)
+#define MAC12ADDR2 (0x5A4 / 4)
+#define MAC13ADDR1 (0x5A8 / 4)
+#define MAC13ADDR2 (0x5AC / 4)
+#define MAC14ADDR1 (0x5B0 / 4)
+#define MAC14ADDR2 (0x5B4 / 4)
+#define MAC15ADDR1 (0x5B8 / 4)
+#define MAC15ADDR2 (0x5BC / 4)
+#define TR64 (0x680 / 4)
+#define TR127 (0x684 / 4)
+#define TR255 (0x688 / 4)
+#define TR511 (0x68C / 4)
+#define TR1K (0x690 / 4)
+#define TRMAX (0x694 / 4)
+#define TRMGV (0x698 / 4)
+#define RBYT (0x69C / 4)
+#define RPKT (0x6A0 / 4)
+#define RFCS (0x6A4 / 4)
+#define RMCA (0x6A8 / 4)
+#define RBCA (0x6AC / 4)
+#define RXCF (0x6B0 / 4)
+#define RXPF (0x6B4 / 4)
+#define RXUO (0x6B8 / 4)
+#define RALN (0x6BC / 4)
+#define RFLR (0x6C0 / 4)
+#define RCDE (0x6C4 / 4)
+#define RCSE (0x6C8 / 4)
+#define RUND (0x6CC / 4)
+#define ROVR (0x6D0 / 4)
+#define RFRG (0x6D4 / 4)
+#define RJBR (0x6D8 / 4)
+#define RDRP (0x6DC / 4)
+#define TBYT (0x6E0 / 4)
+#define TPKT (0x6E4 / 4)
+#define TMCA (0x6E8 / 4)
+#define TBCA (0x6EC / 4)
+#define TXPF (0x6F0 / 4)
+#define TDFR (0x6F4 / 4)
+#define TEDF (0x6F8 / 4)
+#define TSCL (0x6FC / 4)
+#define TMCL (0x700 / 4)
+#define TLCL (0x704 / 4)
+#define TXCL (0x708 / 4)
+#define TNCL (0x70C / 4)
+#define TDRP (0x714 / 4)
+#define TJBR (0x718 / 4)
+#define TFCS (0x71C / 4)
+#define TXCF (0x720 / 4)
+#define TOVR (0x724 / 4)
+#define TUND (0x728 / 4)
+#define TFRG (0x72C / 4)
+#define CAR1 (0x730 / 4)
+#define CAR2 (0x734 / 4)
+#define CAM1 (0x738 / 4)
+#define CAM2 (0x73C / 4)
+#define RREJ (0x740 / 4)
+#define IGADDR0 (0x800 / 4)
+#define IGADDR1 (0x804 / 4)
+#define IGADDR2 (0x808 / 4)
+#define IGADDR3 (0x80C / 4)
+#define IGADDR4 (0x810 / 4)
+#define IGADDR5 (0x814 / 4)
+#define IGADDR6 (0x818 / 4)
+#define IGADDR7 (0x81C / 4)
+#define GADDR0 (0x880 / 4)
+#define GADDR1 (0x884 / 4)
+#define GADDR2 (0x888 / 4)
+#define GADDR3 (0x88C / 4)
+#define GADDR4 (0x890 / 4)
+#define GADDR5 (0x894 / 4)
+#define GADDR6 (0x898 / 4)
+#define GADDR7 (0x89C / 4)
+#define ATTR (0xBF8 / 4)
+#define ATTRELI (0xBFC / 4)
+#define RQPRM0 (0xC00 / 4)
+#define RQPRM1 (0xC04 / 4)
+#define RQPRM2 (0xC08 / 4)
+#define RQPRM3 (0xC0C / 4)
+#define RQPRM4 (0xC10 / 4)
+#define RQPRM5 (0xC14 / 4)
+#define RQPRM6 (0xC18 / 4)
+#define RQPRM7 (0xC1C / 4)
+#define RFBPTR0 (0xC44 / 4)
+#define RFBPTR1 (0xC4C / 4)
+#define RFBPTR2 (0xC54 / 4)
+#define RFBPTR3 (0xC5C / 4)
+#define RFBPTR4 (0xC64 / 4)
+#define RFBPTR5 (0xC6C / 4)
+#define RFBPTR6 (0xC74 / 4)
+#define RFBPTR7 (0xC7C / 4)
+#define TMR_CTRL (0xE00 / 4)
+#define TMR_TEVENT (0xE04 / 4)
+#define TMR_TEMASK (0xE08 / 4)
+#define TMR_PEVENT (0xE0C / 4)
+#define TMR_PEMASK (0xE10 / 4)
+#define TMR_STAT (0xE14 / 4)
+#define TMR_CNT_H (0xE18 / 4)
+#define TMR_CNT_L (0xE1C / 4)
+#define TMR_ADD (0xE20 / 4)
+#define TMR_ACC (0xE24 / 4)
+#define TMR_PRSC (0xE28 / 4)
+#define TMROFF_H (0xE30 / 4)
+#define TMROFF_L (0xE34 / 4)
+#define TMR_ALARM1_H (0xE40 / 4)
+#define TMR_ALARM1_L (0xE44 / 4)
+#define TMR_ALARM2_H (0xE48 / 4)
+#define TMR_ALARM2_L (0xE4C / 4)
+#define TMR_FIPER1 (0xE80 / 4)
+#define TMR_FIPER2 (0xE84 / 4)
+#define TMR_FIPER3 (0xE88 / 4)
+#define TMR_ETTS1_H (0xEA0 / 4)
+#define TMR_ETTS1_L (0xEA4 / 4)
+#define TMR_ETTS2_H (0xEA8 / 4)
+#define TMR_ETTS2_L (0xEAC / 4)
+
+#endif /* ! _ETSEC_REGISTERS_H_ */
diff --git a/hw/net/fsl_etsec/rings.c b/hw/net/fsl_etsec/rings.c
new file mode 100644
index 00000000..68e7b6d1
--- /dev/null
+++ b/hw/net/fsl_etsec/rings.c
@@ -0,0 +1,655 @@
+/*
+ * QEMU Freescale eTSEC Emulator
+ *
+ * Copyright (c) 2011-2013 AdaCore
+ *
+ * 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.
+ */
+#include "net/checksum.h"
+
+#include "etsec.h"
+#include "registers.h"
+
+/* #define ETSEC_RING_DEBUG */
+/* #define HEX_DUMP */
+/* #define DEBUG_BD */
+
+#ifdef ETSEC_RING_DEBUG
+static const int debug_etsec = 1;
+#else
+static const int debug_etsec;
+#endif
+
+#define RING_DEBUG(fmt, ...) do { \
+ if (debug_etsec) { \
+ qemu_log(fmt , ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#ifdef DEBUG_BD
+
+static void print_tx_bd_flags(uint16_t flags)
+{
+ qemu_log(" Ready: %d\n", !!(flags & BD_TX_READY));
+ qemu_log(" PAD/CRC: %d\n", !!(flags & BD_TX_PADCRC));
+ qemu_log(" Wrap: %d\n", !!(flags & BD_WRAP));
+ qemu_log(" Interrupt: %d\n", !!(flags & BD_INTERRUPT));
+ qemu_log(" Last in frame: %d\n", !!(flags & BD_LAST));
+ qemu_log(" Tx CRC: %d\n", !!(flags & BD_TX_TC));
+ qemu_log(" User-defined preamble / defer: %d\n",
+ !!(flags & BD_TX_PREDEF));
+ qemu_log(" Huge frame enable / Late collision: %d\n",
+ !!(flags & BD_TX_HFELC));
+ qemu_log(" Control frame / Retransmission Limit: %d\n",
+ !!(flags & BD_TX_CFRL));
+ qemu_log(" Retry count: %d\n",
+ (flags >> BD_TX_RC_OFFSET) & BD_TX_RC_MASK);
+ qemu_log(" Underrun / TCP/IP off-load enable: %d\n",
+ !!(flags & BD_TX_TOEUN));
+ qemu_log(" Truncation: %d\n", !!(flags & BD_TX_TR));
+}
+
+static void print_rx_bd_flags(uint16_t flags)
+{
+ qemu_log(" Empty: %d\n", !!(flags & BD_RX_EMPTY));
+ qemu_log(" Receive software ownership: %d\n", !!(flags & BD_RX_RO1));
+ qemu_log(" Wrap: %d\n", !!(flags & BD_WRAP));
+ qemu_log(" Interrupt: %d\n", !!(flags & BD_INTERRUPT));
+ qemu_log(" Last in frame: %d\n", !!(flags & BD_LAST));
+ qemu_log(" First in frame: %d\n", !!(flags & BD_RX_FIRST));
+ qemu_log(" Miss: %d\n", !!(flags & BD_RX_MISS));
+ qemu_log(" Broadcast: %d\n", !!(flags & BD_RX_BROADCAST));
+ qemu_log(" Multicast: %d\n", !!(flags & BD_RX_MULTICAST));
+ qemu_log(" Rx frame length violation: %d\n", !!(flags & BD_RX_LG));
+ qemu_log(" Rx non-octet aligned frame: %d\n", !!(flags & BD_RX_NO));
+ qemu_log(" Short frame: %d\n", !!(flags & BD_RX_SH));
+ qemu_log(" Rx CRC Error: %d\n", !!(flags & BD_RX_CR));
+ qemu_log(" Overrun: %d\n", !!(flags & BD_RX_OV));
+ qemu_log(" Truncation: %d\n", !!(flags & BD_RX_TR));
+}
+
+
+static void print_bd(eTSEC_rxtx_bd bd, int mode, uint32_t index)
+{
+ qemu_log("eTSEC %s Data Buffer Descriptor (%u)\n",
+ mode == eTSEC_TRANSMIT ? "Transmit" : "Receive",
+ index);
+ qemu_log(" Flags : 0x%04x\n", bd.flags);
+ if (mode == eTSEC_TRANSMIT) {
+ print_tx_bd_flags(bd.flags);
+ } else {
+ print_rx_bd_flags(bd.flags);
+ }
+ qemu_log(" Length : 0x%04x\n", bd.length);
+ qemu_log(" Pointer : 0x%08x\n", bd.bufptr);
+}
+
+#endif /* DEBUG_BD */
+
+static void read_buffer_descriptor(eTSEC *etsec,
+ hwaddr addr,
+ eTSEC_rxtx_bd *bd)
+{
+ assert(bd != NULL);
+
+ RING_DEBUG("READ Buffer Descriptor @ 0x" TARGET_FMT_plx"\n", addr);
+ cpu_physical_memory_read(addr,
+ bd,
+ sizeof(eTSEC_rxtx_bd));
+
+ if (etsec->regs[DMACTRL].value & DMACTRL_LE) {
+ bd->flags = lduw_le_p(&bd->flags);
+ bd->length = lduw_le_p(&bd->length);
+ bd->bufptr = ldl_le_p(&bd->bufptr);
+ } else {
+ bd->flags = lduw_be_p(&bd->flags);
+ bd->length = lduw_be_p(&bd->length);
+ bd->bufptr = ldl_be_p(&bd->bufptr);
+ }
+}
+
+static void write_buffer_descriptor(eTSEC *etsec,
+ hwaddr addr,
+ eTSEC_rxtx_bd *bd)
+{
+ assert(bd != NULL);
+
+ if (etsec->regs[DMACTRL].value & DMACTRL_LE) {
+ stw_le_p(&bd->flags, bd->flags);
+ stw_le_p(&bd->length, bd->length);
+ stl_le_p(&bd->bufptr, bd->bufptr);
+ } else {
+ stw_be_p(&bd->flags, bd->flags);
+ stw_be_p(&bd->length, bd->length);
+ stl_be_p(&bd->bufptr, bd->bufptr);
+ }
+
+ RING_DEBUG("Write Buffer Descriptor @ 0x" TARGET_FMT_plx"\n", addr);
+ cpu_physical_memory_write(addr,
+ bd,
+ sizeof(eTSEC_rxtx_bd));
+}
+
+static void ievent_set(eTSEC *etsec,
+ uint32_t flags)
+{
+ etsec->regs[IEVENT].value |= flags;
+
+ if ((flags & IEVENT_TXB && etsec->regs[IMASK].value & IMASK_TXBEN)
+ || (flags & IEVENT_TXF && etsec->regs[IMASK].value & IMASK_TXFEN)) {
+ qemu_irq_raise(etsec->tx_irq);
+ RING_DEBUG("%s Raise Tx IRQ\n", __func__);
+ }
+
+ if ((flags & IEVENT_RXB && etsec->regs[IMASK].value & IMASK_RXBEN)
+ || (flags & IEVENT_RXF && etsec->regs[IMASK].value & IMASK_RXFEN)) {
+ qemu_irq_raise(etsec->rx_irq);
+ RING_DEBUG("%s Raise Rx IRQ\n", __func__);
+ }
+}
+
+static void tx_padding_and_crc(eTSEC *etsec, uint32_t min_frame_len)
+{
+ int add = min_frame_len - etsec->tx_buffer_len;
+
+ /* Padding */
+ if (add > 0) {
+ RING_DEBUG("pad:%u\n", add);
+ etsec->tx_buffer = g_realloc(etsec->tx_buffer,
+ etsec->tx_buffer_len + add);
+
+ memset(etsec->tx_buffer + etsec->tx_buffer_len, 0x0, add);
+ etsec->tx_buffer_len += add;
+ }
+
+ /* Never add CRC in QEMU */
+}
+
+static void process_tx_fcb(eTSEC *etsec)
+{
+ uint8_t flags = (uint8_t)(*etsec->tx_buffer);
+ /* L3 header offset from start of frame */
+ uint8_t l3_header_offset = (uint8_t)*(etsec->tx_buffer + 3);
+ /* L4 header offset from start of L3 header */
+ uint8_t l4_header_offset = (uint8_t)*(etsec->tx_buffer + 2);
+ /* L3 header */
+ uint8_t *l3_header = etsec->tx_buffer + 8 + l3_header_offset;
+ /* L4 header */
+ uint8_t *l4_header = l3_header + l4_header_offset;
+
+ /* if packet is IP4 and IP checksum is requested */
+ if (flags & FCB_TX_IP && flags & FCB_TX_CIP) {
+ /* do IP4 checksum (TODO This function does TCP/UDP checksum
+ * but not sure if it also does IP4 checksum.) */
+ net_checksum_calculate(etsec->tx_buffer + 8,
+ etsec->tx_buffer_len - 8);
+ }
+ /* TODO Check the correct usage of the PHCS field of the FCB in case the NPH
+ * flag is on */
+
+ /* if packet is IP4 and TCP or UDP */
+ if (flags & FCB_TX_IP && flags & FCB_TX_TUP) {
+ /* if UDP */
+ if (flags & FCB_TX_UDP) {
+ /* if checksum is requested */
+ if (flags & FCB_TX_CTU) {
+ /* do UDP checksum */
+
+ net_checksum_calculate(etsec->tx_buffer + 8,
+ etsec->tx_buffer_len - 8);
+ } else {
+ /* set checksum field to 0 */
+ l4_header[6] = 0;
+ l4_header[7] = 0;
+ }
+ } else if (flags & FCB_TX_CTU) { /* if TCP and checksum is requested */
+ /* do TCP checksum */
+ net_checksum_calculate(etsec->tx_buffer + 8,
+ etsec->tx_buffer_len - 8);
+ }
+ }
+}
+
+static void process_tx_bd(eTSEC *etsec,
+ eTSEC_rxtx_bd *bd)
+{
+ uint8_t *tmp_buff = NULL;
+ hwaddr tbdbth = (hwaddr)(etsec->regs[TBDBPH].value & 0xF) << 32;
+
+ if (bd->length == 0) {
+ /* ERROR */
+ return;
+ }
+
+ if (etsec->tx_buffer_len == 0) {
+ /* It's the first BD */
+ etsec->first_bd = *bd;
+ }
+
+ /* TODO: if TxBD[TOE/UN] skip the Tx Frame Control Block*/
+
+ /* Load this Data Buffer */
+ etsec->tx_buffer = g_realloc(etsec->tx_buffer,
+ etsec->tx_buffer_len + bd->length);
+ tmp_buff = etsec->tx_buffer + etsec->tx_buffer_len;
+ cpu_physical_memory_read(bd->bufptr + tbdbth, tmp_buff, bd->length);
+
+ /* Update buffer length */
+ etsec->tx_buffer_len += bd->length;
+
+
+ if (etsec->tx_buffer_len != 0 && (bd->flags & BD_LAST)) {
+ if (etsec->regs[MACCFG1].value & MACCFG1_TX_EN) {
+ /* MAC Transmit enabled */
+
+ /* Process offload Tx FCB */
+ if (etsec->first_bd.flags & BD_TX_TOEUN) {
+ process_tx_fcb(etsec);
+ }
+
+ if (etsec->first_bd.flags & BD_TX_PADCRC
+ || etsec->regs[MACCFG2].value & MACCFG2_PADCRC) {
+
+ /* Padding and CRC (Padding implies CRC) */
+ tx_padding_and_crc(etsec, 64);
+
+ } else if (etsec->first_bd.flags & BD_TX_TC
+ || etsec->regs[MACCFG2].value & MACCFG2_CRC_EN) {
+
+ /* Only CRC */
+ /* Never add CRC in QEMU */
+ }
+
+#if defined(HEX_DUMP)
+ qemu_log("eTSEC Send packet size:%d\n", etsec->tx_buffer_len);
+ qemu_hexdump(etsec->tx_buffer, stderr, "", etsec->tx_buffer_len);
+#endif /* ETSEC_RING_DEBUG */
+
+ if (etsec->first_bd.flags & BD_TX_TOEUN) {
+ qemu_send_packet(qemu_get_queue(etsec->nic),
+ etsec->tx_buffer + 8,
+ etsec->tx_buffer_len - 8);
+ } else {
+ qemu_send_packet(qemu_get_queue(etsec->nic),
+ etsec->tx_buffer,
+ etsec->tx_buffer_len);
+ }
+
+ }
+
+ etsec->tx_buffer_len = 0;
+
+ if (bd->flags & BD_INTERRUPT) {
+ ievent_set(etsec, IEVENT_TXF);
+ }
+ } else {
+ if (bd->flags & BD_INTERRUPT) {
+ ievent_set(etsec, IEVENT_TXB);
+ }
+ }
+
+ /* Update DB flags */
+
+ /* Clear Ready */
+ bd->flags &= ~BD_TX_READY;
+
+ /* Clear Defer */
+ bd->flags &= ~BD_TX_PREDEF;
+
+ /* Clear Late Collision */
+ bd->flags &= ~BD_TX_HFELC;
+
+ /* Clear Retransmission Limit */
+ bd->flags &= ~BD_TX_CFRL;
+
+ /* Clear Retry Count */
+ bd->flags &= ~(BD_TX_RC_MASK << BD_TX_RC_OFFSET);
+
+ /* Clear Underrun */
+ bd->flags &= ~BD_TX_TOEUN;
+
+ /* Clear Truncation */
+ bd->flags &= ~BD_TX_TR;
+}
+
+void etsec_walk_tx_ring(eTSEC *etsec, int ring_nbr)
+{
+ hwaddr ring_base = 0;
+ hwaddr bd_addr = 0;
+ eTSEC_rxtx_bd bd;
+ uint16_t bd_flags;
+
+ if (!(etsec->regs[MACCFG1].value & MACCFG1_TX_EN)) {
+ RING_DEBUG("%s: MAC Transmit not enabled\n", __func__);
+ return;
+ }
+
+ ring_base = (hwaddr)(etsec->regs[TBASEH].value & 0xF) << 32;
+ ring_base += etsec->regs[TBASE0 + ring_nbr].value & ~0x7;
+ bd_addr = etsec->regs[TBPTR0 + ring_nbr].value & ~0x7;
+
+ do {
+ read_buffer_descriptor(etsec, bd_addr, &bd);
+
+#ifdef DEBUG_BD
+ print_bd(bd,
+ eTSEC_TRANSMIT,
+ (bd_addr - ring_base) / sizeof(eTSEC_rxtx_bd));
+
+#endif /* DEBUG_BD */
+
+ /* Save flags before BD update */
+ bd_flags = bd.flags;
+
+ if (bd_flags & BD_TX_READY) {
+ process_tx_bd(etsec, &bd);
+
+ /* Write back BD after update */
+ write_buffer_descriptor(etsec, bd_addr, &bd);
+ }
+
+ /* Wrap or next BD */
+ if (bd_flags & BD_WRAP) {
+ bd_addr = ring_base;
+ } else {
+ bd_addr += sizeof(eTSEC_rxtx_bd);
+ }
+
+ } while (bd_addr != ring_base);
+
+ bd_addr = ring_base;
+
+ /* Save the Buffer Descriptor Pointers to current bd */
+ etsec->regs[TBPTR0 + ring_nbr].value = bd_addr;
+
+ /* Set transmit halt THLTx */
+ etsec->regs[TSTAT].value |= 1 << (31 - ring_nbr);
+}
+
+static void fill_rx_bd(eTSEC *etsec,
+ eTSEC_rxtx_bd *bd,
+ const uint8_t **buf,
+ size_t *size)
+{
+ uint16_t to_write;
+ hwaddr bufptr = bd->bufptr +
+ ((hwaddr)(etsec->regs[TBDBPH].value & 0xF) << 32);
+ uint8_t padd[etsec->rx_padding];
+ uint8_t rem;
+
+ RING_DEBUG("eTSEC fill Rx buffer @ 0x%016" HWADDR_PRIx
+ " size:%zu(padding + crc:%u) + fcb:%u\n",
+ bufptr, *size, etsec->rx_padding, etsec->rx_fcb_size);
+
+ bd->length = 0;
+
+ /* This operation will only write FCB */
+ if (etsec->rx_fcb_size != 0) {
+
+ cpu_physical_memory_write(bufptr, etsec->rx_fcb, etsec->rx_fcb_size);
+
+ bufptr += etsec->rx_fcb_size;
+ bd->length += etsec->rx_fcb_size;
+ etsec->rx_fcb_size = 0;
+
+ }
+
+ /* We remove padding from the computation of to_write because it is not
+ * allocated in the buffer.
+ */
+ to_write = MIN(*size - etsec->rx_padding,
+ etsec->regs[MRBLR].value - etsec->rx_fcb_size);
+
+ /* This operation can only write packet data and no padding */
+ if (to_write > 0) {
+ cpu_physical_memory_write(bufptr, *buf, to_write);
+
+ *buf += to_write;
+ bufptr += to_write;
+ *size -= to_write;
+
+ bd->flags &= ~BD_RX_EMPTY;
+ bd->length += to_write;
+ }
+
+ if (*size == etsec->rx_padding) {
+ /* The remaining bytes are only for padding which is not actually
+ * allocated in the data buffer.
+ */
+
+ rem = MIN(etsec->regs[MRBLR].value - bd->length, etsec->rx_padding);
+
+ if (rem > 0) {
+ memset(padd, 0x0, sizeof(padd));
+ etsec->rx_padding -= rem;
+ *size -= rem;
+ bd->length += rem;
+ cpu_physical_memory_write(bufptr, padd, rem);
+ }
+ }
+}
+
+static void rx_init_frame(eTSEC *etsec, const uint8_t *buf, size_t size)
+{
+ uint32_t fcb_size = 0;
+ uint8_t prsdep = (etsec->regs[RCTRL].value >> RCTRL_PRSDEP_OFFSET)
+ & RCTRL_PRSDEP_MASK;
+
+ if (prsdep != 0) {
+ /* Prepend FCB (FCB size + RCTRL[PAL]) */
+ fcb_size = 8 + ((etsec->regs[RCTRL].value >> 16) & 0x1F);
+
+ etsec->rx_fcb_size = fcb_size;
+
+ /* TODO: fill_FCB(etsec); */
+ memset(etsec->rx_fcb, 0x0, sizeof(etsec->rx_fcb));
+
+ } else {
+ etsec->rx_fcb_size = 0;
+ }
+
+ if (etsec->rx_buffer != NULL) {
+ g_free(etsec->rx_buffer);
+ }
+
+ /* Do not copy the frame for now */
+ etsec->rx_buffer = (uint8_t *)buf;
+ etsec->rx_buffer_len = size;
+
+ /* CRC padding (We don't have to compute the CRC) */
+ etsec->rx_padding = 4;
+
+ etsec->rx_first_in_frame = 1;
+ etsec->rx_remaining_data = etsec->rx_buffer_len;
+ RING_DEBUG("%s: rx_buffer_len:%u rx_padding+crc:%u\n", __func__,
+ etsec->rx_buffer_len, etsec->rx_padding);
+}
+
+ssize_t etsec_rx_ring_write(eTSEC *etsec, const uint8_t *buf, size_t size)
+{
+ int ring_nbr = 0; /* Always use ring0 (no filer) */
+
+ if (etsec->rx_buffer_len != 0) {
+ RING_DEBUG("%s: We can't receive now,"
+ " a buffer is already in the pipe\n", __func__);
+ return 0;
+ }
+
+ if (etsec->regs[RSTAT].value & 1 << (23 - ring_nbr)) {
+ RING_DEBUG("%s: The ring is halted\n", __func__);
+ return -1;
+ }
+
+ if (etsec->regs[DMACTRL].value & DMACTRL_GRS) {
+ RING_DEBUG("%s: Graceful receive stop\n", __func__);
+ return -1;
+ }
+
+ if (!(etsec->regs[MACCFG1].value & MACCFG1_RX_EN)) {
+ RING_DEBUG("%s: MAC Receive not enabled\n", __func__);
+ return -1;
+ }
+
+ if ((etsec->regs[RCTRL].value & RCTRL_RSF) && (size < 60)) {
+ /* CRC is not in the packet yet, so short frame is below 60 bytes */
+ RING_DEBUG("%s: Drop short frame\n", __func__);
+ return -1;
+ }
+
+ rx_init_frame(etsec, buf, size);
+
+ etsec_walk_rx_ring(etsec, ring_nbr);
+
+ return size;
+}
+
+void etsec_walk_rx_ring(eTSEC *etsec, int ring_nbr)
+{
+ hwaddr ring_base = 0;
+ hwaddr bd_addr = 0;
+ hwaddr start_bd_addr = 0;
+ eTSEC_rxtx_bd bd;
+ uint16_t bd_flags;
+ size_t remaining_data;
+ const uint8_t *buf;
+ uint8_t *tmp_buf;
+ size_t size;
+
+ if (etsec->rx_buffer_len == 0) {
+ /* No frame to send */
+ RING_DEBUG("No frame to send\n");
+ return;
+ }
+
+ remaining_data = etsec->rx_remaining_data + etsec->rx_padding;
+ buf = etsec->rx_buffer
+ + (etsec->rx_buffer_len - etsec->rx_remaining_data);
+ size = etsec->rx_buffer_len + etsec->rx_padding;
+
+ ring_base = (hwaddr)(etsec->regs[RBASEH].value & 0xF) << 32;
+ ring_base += etsec->regs[RBASE0 + ring_nbr].value & ~0x7;
+ start_bd_addr = bd_addr = etsec->regs[RBPTR0 + ring_nbr].value & ~0x7;
+
+ do {
+ read_buffer_descriptor(etsec, bd_addr, &bd);
+
+#ifdef DEBUG_BD
+ print_bd(bd,
+ eTSEC_RECEIVE,
+ (bd_addr - ring_base) / sizeof(eTSEC_rxtx_bd));
+
+#endif /* DEBUG_BD */
+
+ /* Save flags before BD update */
+ bd_flags = bd.flags;
+
+ if (bd_flags & BD_RX_EMPTY) {
+ fill_rx_bd(etsec, &bd, &buf, &remaining_data);
+
+ if (etsec->rx_first_in_frame) {
+ bd.flags |= BD_RX_FIRST;
+ etsec->rx_first_in_frame = 0;
+ etsec->rx_first_bd = bd;
+ }
+
+ /* Last in frame */
+ if (remaining_data == 0) {
+
+ /* Clear flags */
+
+ bd.flags &= ~0x7ff;
+
+ bd.flags |= BD_LAST;
+
+ /* NOTE: non-octet aligned frame is impossible in qemu */
+
+ if (size >= etsec->regs[MAXFRM].value) {
+ /* frame length violation */
+ qemu_log("%s frame length violation: size:%zu MAXFRM:%d\n",
+ __func__, size, etsec->regs[MAXFRM].value);
+
+ bd.flags |= BD_RX_LG;
+ }
+
+ if (size < 64) {
+ /* Short frame */
+ bd.flags |= BD_RX_SH;
+ }
+
+ /* TODO: Broadcast and Multicast */
+
+ if (bd.flags & BD_INTERRUPT) {
+ /* Set RXFx */
+ etsec->regs[RSTAT].value |= 1 << (7 - ring_nbr);
+
+ /* Set IEVENT */
+ ievent_set(etsec, IEVENT_RXF);
+ }
+
+ } else {
+ if (bd.flags & BD_INTERRUPT) {
+ /* Set IEVENT */
+ ievent_set(etsec, IEVENT_RXB);
+ }
+ }
+
+ /* Write back BD after update */
+ write_buffer_descriptor(etsec, bd_addr, &bd);
+ }
+
+ /* Wrap or next BD */
+ if (bd_flags & BD_WRAP) {
+ bd_addr = ring_base;
+ } else {
+ bd_addr += sizeof(eTSEC_rxtx_bd);
+ }
+ } while (remaining_data != 0
+ && (bd_flags & BD_RX_EMPTY)
+ && bd_addr != start_bd_addr);
+
+ /* Reset ring ptr */
+ etsec->regs[RBPTR0 + ring_nbr].value = bd_addr;
+
+ /* The frame is too large to fit in the Rx ring */
+ if (remaining_data > 0) {
+
+ /* Set RSTAT[QHLTx] */
+ etsec->regs[RSTAT].value |= 1 << (23 - ring_nbr);
+
+ /* Save remaining data to send the end of the frame when the ring will
+ * be restarted
+ */
+ etsec->rx_remaining_data = remaining_data;
+
+ /* Copy the frame */
+ tmp_buf = g_malloc(size);
+ memcpy(tmp_buf, etsec->rx_buffer, size);
+ etsec->rx_buffer = tmp_buf;
+
+ RING_DEBUG("no empty RxBD available any more\n");
+ } else {
+ etsec->rx_buffer_len = 0;
+ etsec->rx_buffer = NULL;
+ if (etsec->need_flush) {
+ qemu_flush_queued_packets(qemu_get_queue(etsec->nic));
+ }
+ }
+
+ RING_DEBUG("eTSEC End of ring_write: remaining_data:%zu\n", remaining_data);
+}
diff --git a/hw/net/lan9118.c b/hw/net/lan9118.c
new file mode 100644
index 00000000..4f0e840f
--- /dev/null
+++ b/hw/net/lan9118.c
@@ -0,0 +1,1392 @@
+/*
+ * SMSC LAN9118 Ethernet interface emulation
+ *
+ * Copyright (c) 2009 CodeSourcery, LLC.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU GPL v2
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "hw/devices.h"
+#include "sysemu/sysemu.h"
+#include "hw/ptimer.h"
+/* For crc32 */
+#include <zlib.h>
+
+//#define DEBUG_LAN9118
+
+#ifdef DEBUG_LAN9118
+#define DPRINTF(fmt, ...) \
+do { printf("lan9118: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { hw_error("lan9118: error: " fmt , ## __VA_ARGS__);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "lan9118: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+#define CSR_ID_REV 0x50
+#define CSR_IRQ_CFG 0x54
+#define CSR_INT_STS 0x58
+#define CSR_INT_EN 0x5c
+#define CSR_BYTE_TEST 0x64
+#define CSR_FIFO_INT 0x68
+#define CSR_RX_CFG 0x6c
+#define CSR_TX_CFG 0x70
+#define CSR_HW_CFG 0x74
+#define CSR_RX_DP_CTRL 0x78
+#define CSR_RX_FIFO_INF 0x7c
+#define CSR_TX_FIFO_INF 0x80
+#define CSR_PMT_CTRL 0x84
+#define CSR_GPIO_CFG 0x88
+#define CSR_GPT_CFG 0x8c
+#define CSR_GPT_CNT 0x90
+#define CSR_WORD_SWAP 0x98
+#define CSR_FREE_RUN 0x9c
+#define CSR_RX_DROP 0xa0
+#define CSR_MAC_CSR_CMD 0xa4
+#define CSR_MAC_CSR_DATA 0xa8
+#define CSR_AFC_CFG 0xac
+#define CSR_E2P_CMD 0xb0
+#define CSR_E2P_DATA 0xb4
+
+/* IRQ_CFG */
+#define IRQ_INT 0x00001000
+#define IRQ_EN 0x00000100
+#define IRQ_POL 0x00000010
+#define IRQ_TYPE 0x00000001
+
+/* INT_STS/INT_EN */
+#define SW_INT 0x80000000
+#define TXSTOP_INT 0x02000000
+#define RXSTOP_INT 0x01000000
+#define RXDFH_INT 0x00800000
+#define TX_IOC_INT 0x00200000
+#define RXD_INT 0x00100000
+#define GPT_INT 0x00080000
+#define PHY_INT 0x00040000
+#define PME_INT 0x00020000
+#define TXSO_INT 0x00010000
+#define RWT_INT 0x00008000
+#define RXE_INT 0x00004000
+#define TXE_INT 0x00002000
+#define TDFU_INT 0x00000800
+#define TDFO_INT 0x00000400
+#define TDFA_INT 0x00000200
+#define TSFF_INT 0x00000100
+#define TSFL_INT 0x00000080
+#define RXDF_INT 0x00000040
+#define RDFL_INT 0x00000020
+#define RSFF_INT 0x00000010
+#define RSFL_INT 0x00000008
+#define GPIO2_INT 0x00000004
+#define GPIO1_INT 0x00000002
+#define GPIO0_INT 0x00000001
+#define RESERVED_INT 0x7c001000
+
+#define MAC_CR 1
+#define MAC_ADDRH 2
+#define MAC_ADDRL 3
+#define MAC_HASHH 4
+#define MAC_HASHL 5
+#define MAC_MII_ACC 6
+#define MAC_MII_DATA 7
+#define MAC_FLOW 8
+#define MAC_VLAN1 9 /* TODO */
+#define MAC_VLAN2 10 /* TODO */
+#define MAC_WUFF 11 /* TODO */
+#define MAC_WUCSR 12 /* TODO */
+
+#define MAC_CR_RXALL 0x80000000
+#define MAC_CR_RCVOWN 0x00800000
+#define MAC_CR_LOOPBK 0x00200000
+#define MAC_CR_FDPX 0x00100000
+#define MAC_CR_MCPAS 0x00080000
+#define MAC_CR_PRMS 0x00040000
+#define MAC_CR_INVFILT 0x00020000
+#define MAC_CR_PASSBAD 0x00010000
+#define MAC_CR_HO 0x00008000
+#define MAC_CR_HPFILT 0x00002000
+#define MAC_CR_LCOLL 0x00001000
+#define MAC_CR_BCAST 0x00000800
+#define MAC_CR_DISRTY 0x00000400
+#define MAC_CR_PADSTR 0x00000100
+#define MAC_CR_BOLMT 0x000000c0
+#define MAC_CR_DFCHK 0x00000020
+#define MAC_CR_TXEN 0x00000008
+#define MAC_CR_RXEN 0x00000004
+#define MAC_CR_RESERVED 0x7f404213
+
+#define PHY_INT_ENERGYON 0x80
+#define PHY_INT_AUTONEG_COMPLETE 0x40
+#define PHY_INT_FAULT 0x20
+#define PHY_INT_DOWN 0x10
+#define PHY_INT_AUTONEG_LP 0x08
+#define PHY_INT_PARFAULT 0x04
+#define PHY_INT_AUTONEG_PAGE 0x02
+
+#define GPT_TIMER_EN 0x20000000
+
+enum tx_state {
+ TX_IDLE,
+ TX_B,
+ TX_DATA
+};
+
+typedef struct {
+ /* state is a tx_state but we can't put enums in VMStateDescriptions. */
+ uint32_t state;
+ uint32_t cmd_a;
+ uint32_t cmd_b;
+ int32_t buffer_size;
+ int32_t offset;
+ int32_t pad;
+ int32_t fifo_used;
+ int32_t len;
+ uint8_t data[2048];
+} LAN9118Packet;
+
+static const VMStateDescription vmstate_lan9118_packet = {
+ .name = "lan9118_packet",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(state, LAN9118Packet),
+ VMSTATE_UINT32(cmd_a, LAN9118Packet),
+ VMSTATE_UINT32(cmd_b, LAN9118Packet),
+ VMSTATE_INT32(buffer_size, LAN9118Packet),
+ VMSTATE_INT32(offset, LAN9118Packet),
+ VMSTATE_INT32(pad, LAN9118Packet),
+ VMSTATE_INT32(fifo_used, LAN9118Packet),
+ VMSTATE_INT32(len, LAN9118Packet),
+ VMSTATE_UINT8_ARRAY(data, LAN9118Packet, 2048),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define TYPE_LAN9118 "lan9118"
+#define LAN9118(obj) OBJECT_CHECK(lan9118_state, (obj), TYPE_LAN9118)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ NICState *nic;
+ NICConf conf;
+ qemu_irq irq;
+ MemoryRegion mmio;
+ ptimer_state *timer;
+
+ uint32_t irq_cfg;
+ uint32_t int_sts;
+ uint32_t int_en;
+ uint32_t fifo_int;
+ uint32_t rx_cfg;
+ uint32_t tx_cfg;
+ uint32_t hw_cfg;
+ uint32_t pmt_ctrl;
+ uint32_t gpio_cfg;
+ uint32_t gpt_cfg;
+ uint32_t word_swap;
+ uint32_t free_timer_start;
+ uint32_t mac_cmd;
+ uint32_t mac_data;
+ uint32_t afc_cfg;
+ uint32_t e2p_cmd;
+ uint32_t e2p_data;
+
+ uint32_t mac_cr;
+ uint32_t mac_hashh;
+ uint32_t mac_hashl;
+ uint32_t mac_mii_acc;
+ uint32_t mac_mii_data;
+ uint32_t mac_flow;
+
+ uint32_t phy_status;
+ uint32_t phy_control;
+ uint32_t phy_advertise;
+ uint32_t phy_int;
+ uint32_t phy_int_mask;
+
+ int32_t eeprom_writable;
+ uint8_t eeprom[128];
+
+ int32_t tx_fifo_size;
+ LAN9118Packet *txp;
+ LAN9118Packet tx_packet;
+
+ int32_t tx_status_fifo_used;
+ int32_t tx_status_fifo_head;
+ uint32_t tx_status_fifo[512];
+
+ int32_t rx_status_fifo_size;
+ int32_t rx_status_fifo_used;
+ int32_t rx_status_fifo_head;
+ uint32_t rx_status_fifo[896];
+ int32_t rx_fifo_size;
+ int32_t rx_fifo_used;
+ int32_t rx_fifo_head;
+ uint32_t rx_fifo[3360];
+ int32_t rx_packet_size_head;
+ int32_t rx_packet_size_tail;
+ int32_t rx_packet_size[1024];
+
+ int32_t rxp_offset;
+ int32_t rxp_size;
+ int32_t rxp_pad;
+
+ uint32_t write_word_prev_offset;
+ uint32_t write_word_n;
+ uint16_t write_word_l;
+ uint16_t write_word_h;
+ uint32_t read_word_prev_offset;
+ uint32_t read_word_n;
+ uint32_t read_long;
+
+ uint32_t mode_16bit;
+} lan9118_state;
+
+static const VMStateDescription vmstate_lan9118 = {
+ .name = "lan9118",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PTIMER(timer, lan9118_state),
+ VMSTATE_UINT32(irq_cfg, lan9118_state),
+ VMSTATE_UINT32(int_sts, lan9118_state),
+ VMSTATE_UINT32(int_en, lan9118_state),
+ VMSTATE_UINT32(fifo_int, lan9118_state),
+ VMSTATE_UINT32(rx_cfg, lan9118_state),
+ VMSTATE_UINT32(tx_cfg, lan9118_state),
+ VMSTATE_UINT32(hw_cfg, lan9118_state),
+ VMSTATE_UINT32(pmt_ctrl, lan9118_state),
+ VMSTATE_UINT32(gpio_cfg, lan9118_state),
+ VMSTATE_UINT32(gpt_cfg, lan9118_state),
+ VMSTATE_UINT32(word_swap, lan9118_state),
+ VMSTATE_UINT32(free_timer_start, lan9118_state),
+ VMSTATE_UINT32(mac_cmd, lan9118_state),
+ VMSTATE_UINT32(mac_data, lan9118_state),
+ VMSTATE_UINT32(afc_cfg, lan9118_state),
+ VMSTATE_UINT32(e2p_cmd, lan9118_state),
+ VMSTATE_UINT32(e2p_data, lan9118_state),
+ VMSTATE_UINT32(mac_cr, lan9118_state),
+ VMSTATE_UINT32(mac_hashh, lan9118_state),
+ VMSTATE_UINT32(mac_hashl, lan9118_state),
+ VMSTATE_UINT32(mac_mii_acc, lan9118_state),
+ VMSTATE_UINT32(mac_mii_data, lan9118_state),
+ VMSTATE_UINT32(mac_flow, lan9118_state),
+ VMSTATE_UINT32(phy_status, lan9118_state),
+ VMSTATE_UINT32(phy_control, lan9118_state),
+ VMSTATE_UINT32(phy_advertise, lan9118_state),
+ VMSTATE_UINT32(phy_int, lan9118_state),
+ VMSTATE_UINT32(phy_int_mask, lan9118_state),
+ VMSTATE_INT32(eeprom_writable, lan9118_state),
+ VMSTATE_UINT8_ARRAY(eeprom, lan9118_state, 128),
+ VMSTATE_INT32(tx_fifo_size, lan9118_state),
+ /* txp always points at tx_packet so need not be saved */
+ VMSTATE_STRUCT(tx_packet, lan9118_state, 0,
+ vmstate_lan9118_packet, LAN9118Packet),
+ VMSTATE_INT32(tx_status_fifo_used, lan9118_state),
+ VMSTATE_INT32(tx_status_fifo_head, lan9118_state),
+ VMSTATE_UINT32_ARRAY(tx_status_fifo, lan9118_state, 512),
+ VMSTATE_INT32(rx_status_fifo_size, lan9118_state),
+ VMSTATE_INT32(rx_status_fifo_used, lan9118_state),
+ VMSTATE_INT32(rx_status_fifo_head, lan9118_state),
+ VMSTATE_UINT32_ARRAY(rx_status_fifo, lan9118_state, 896),
+ VMSTATE_INT32(rx_fifo_size, lan9118_state),
+ VMSTATE_INT32(rx_fifo_used, lan9118_state),
+ VMSTATE_INT32(rx_fifo_head, lan9118_state),
+ VMSTATE_UINT32_ARRAY(rx_fifo, lan9118_state, 3360),
+ VMSTATE_INT32(rx_packet_size_head, lan9118_state),
+ VMSTATE_INT32(rx_packet_size_tail, lan9118_state),
+ VMSTATE_INT32_ARRAY(rx_packet_size, lan9118_state, 1024),
+ VMSTATE_INT32(rxp_offset, lan9118_state),
+ VMSTATE_INT32(rxp_size, lan9118_state),
+ VMSTATE_INT32(rxp_pad, lan9118_state),
+ VMSTATE_UINT32_V(write_word_prev_offset, lan9118_state, 2),
+ VMSTATE_UINT32_V(write_word_n, lan9118_state, 2),
+ VMSTATE_UINT16_V(write_word_l, lan9118_state, 2),
+ VMSTATE_UINT16_V(write_word_h, lan9118_state, 2),
+ VMSTATE_UINT32_V(read_word_prev_offset, lan9118_state, 2),
+ VMSTATE_UINT32_V(read_word_n, lan9118_state, 2),
+ VMSTATE_UINT32_V(read_long, lan9118_state, 2),
+ VMSTATE_UINT32_V(mode_16bit, lan9118_state, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void lan9118_update(lan9118_state *s)
+{
+ int level;
+
+ /* TODO: Implement FIFO level IRQs. */
+ level = (s->int_sts & s->int_en) != 0;
+ if (level) {
+ s->irq_cfg |= IRQ_INT;
+ } else {
+ s->irq_cfg &= ~IRQ_INT;
+ }
+ if ((s->irq_cfg & IRQ_EN) == 0) {
+ level = 0;
+ }
+ if ((s->irq_cfg & (IRQ_TYPE | IRQ_POL)) != (IRQ_TYPE | IRQ_POL)) {
+ /* Interrupt is active low unless we're configured as
+ * active-high polarity, push-pull type.
+ */
+ level = !level;
+ }
+ qemu_set_irq(s->irq, level);
+}
+
+static void lan9118_mac_changed(lan9118_state *s)
+{
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+}
+
+static void lan9118_reload_eeprom(lan9118_state *s)
+{
+ int i;
+ if (s->eeprom[0] != 0xa5) {
+ s->e2p_cmd &= ~0x10;
+ DPRINTF("MACADDR load failed\n");
+ return;
+ }
+ for (i = 0; i < 6; i++) {
+ s->conf.macaddr.a[i] = s->eeprom[i + 1];
+ }
+ s->e2p_cmd |= 0x10;
+ DPRINTF("MACADDR loaded from eeprom\n");
+ lan9118_mac_changed(s);
+}
+
+static void phy_update_irq(lan9118_state *s)
+{
+ if (s->phy_int & s->phy_int_mask) {
+ s->int_sts |= PHY_INT;
+ } else {
+ s->int_sts &= ~PHY_INT;
+ }
+ lan9118_update(s);
+}
+
+static void phy_update_link(lan9118_state *s)
+{
+ /* Autonegotiation status mirrors link status. */
+ if (qemu_get_queue(s->nic)->link_down) {
+ s->phy_status &= ~0x0024;
+ s->phy_int |= PHY_INT_DOWN;
+ } else {
+ s->phy_status |= 0x0024;
+ s->phy_int |= PHY_INT_ENERGYON;
+ s->phy_int |= PHY_INT_AUTONEG_COMPLETE;
+ }
+ phy_update_irq(s);
+}
+
+static void lan9118_set_link(NetClientState *nc)
+{
+ phy_update_link(qemu_get_nic_opaque(nc));
+}
+
+static void phy_reset(lan9118_state *s)
+{
+ s->phy_status = 0x7809;
+ s->phy_control = 0x3000;
+ s->phy_advertise = 0x01e1;
+ s->phy_int_mask = 0;
+ s->phy_int = 0;
+ phy_update_link(s);
+}
+
+static void lan9118_reset(DeviceState *d)
+{
+ lan9118_state *s = LAN9118(d);
+
+ s->irq_cfg &= (IRQ_TYPE | IRQ_POL);
+ s->int_sts = 0;
+ s->int_en = 0;
+ s->fifo_int = 0x48000000;
+ s->rx_cfg = 0;
+ s->tx_cfg = 0;
+ s->hw_cfg = s->mode_16bit ? 0x00050000 : 0x00050004;
+ s->pmt_ctrl &= 0x45;
+ s->gpio_cfg = 0;
+ s->txp->fifo_used = 0;
+ s->txp->state = TX_IDLE;
+ s->txp->cmd_a = 0xffffffffu;
+ s->txp->cmd_b = 0xffffffffu;
+ s->txp->len = 0;
+ s->txp->fifo_used = 0;
+ s->tx_fifo_size = 4608;
+ s->tx_status_fifo_used = 0;
+ s->rx_status_fifo_size = 704;
+ s->rx_fifo_size = 2640;
+ s->rx_fifo_used = 0;
+ s->rx_status_fifo_size = 176;
+ s->rx_status_fifo_used = 0;
+ s->rxp_offset = 0;
+ s->rxp_size = 0;
+ s->rxp_pad = 0;
+ s->rx_packet_size_tail = s->rx_packet_size_head;
+ s->rx_packet_size[s->rx_packet_size_head] = 0;
+ s->mac_cmd = 0;
+ s->mac_data = 0;
+ s->afc_cfg = 0;
+ s->e2p_cmd = 0;
+ s->e2p_data = 0;
+ s->free_timer_start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 40;
+
+ ptimer_stop(s->timer);
+ ptimer_set_count(s->timer, 0xffff);
+ s->gpt_cfg = 0xffff;
+
+ s->mac_cr = MAC_CR_PRMS;
+ s->mac_hashh = 0;
+ s->mac_hashl = 0;
+ s->mac_mii_acc = 0;
+ s->mac_mii_data = 0;
+ s->mac_flow = 0;
+
+ s->read_word_n = 0;
+ s->write_word_n = 0;
+
+ phy_reset(s);
+
+ s->eeprom_writable = 0;
+ lan9118_reload_eeprom(s);
+}
+
+static void rx_fifo_push(lan9118_state *s, uint32_t val)
+{
+ int fifo_pos;
+ fifo_pos = s->rx_fifo_head + s->rx_fifo_used;
+ if (fifo_pos >= s->rx_fifo_size)
+ fifo_pos -= s->rx_fifo_size;
+ s->rx_fifo[fifo_pos] = val;
+ s->rx_fifo_used++;
+}
+
+/* Return nonzero if the packet is accepted by the filter. */
+static int lan9118_filter(lan9118_state *s, const uint8_t *addr)
+{
+ int multicast;
+ uint32_t hash;
+
+ if (s->mac_cr & MAC_CR_PRMS) {
+ return 1;
+ }
+ if (addr[0] == 0xff && addr[1] == 0xff && addr[2] == 0xff &&
+ addr[3] == 0xff && addr[4] == 0xff && addr[5] == 0xff) {
+ return (s->mac_cr & MAC_CR_BCAST) == 0;
+ }
+
+ multicast = addr[0] & 1;
+ if (multicast &&s->mac_cr & MAC_CR_MCPAS) {
+ return 1;
+ }
+ if (multicast ? (s->mac_cr & MAC_CR_HPFILT) == 0
+ : (s->mac_cr & MAC_CR_HO) == 0) {
+ /* Exact matching. */
+ hash = memcmp(addr, s->conf.macaddr.a, 6);
+ if (s->mac_cr & MAC_CR_INVFILT) {
+ return hash != 0;
+ } else {
+ return hash == 0;
+ }
+ } else {
+ /* Hash matching */
+ hash = compute_mcast_idx(addr);
+ if (hash & 0x20) {
+ return (s->mac_hashh >> (hash & 0x1f)) & 1;
+ } else {
+ return (s->mac_hashl >> (hash & 0x1f)) & 1;
+ }
+ }
+}
+
+static ssize_t lan9118_receive(NetClientState *nc, const uint8_t *buf,
+ size_t size)
+{
+ lan9118_state *s = qemu_get_nic_opaque(nc);
+ int fifo_len;
+ int offset;
+ int src_pos;
+ int n;
+ int filter;
+ uint32_t val;
+ uint32_t crc;
+ uint32_t status;
+
+ if ((s->mac_cr & MAC_CR_RXEN) == 0) {
+ return -1;
+ }
+
+ if (size >= 2048 || size < 14) {
+ return -1;
+ }
+
+ /* TODO: Implement FIFO overflow notification. */
+ if (s->rx_status_fifo_used == s->rx_status_fifo_size) {
+ return -1;
+ }
+
+ filter = lan9118_filter(s, buf);
+ if (!filter && (s->mac_cr & MAC_CR_RXALL) == 0) {
+ return size;
+ }
+
+ offset = (s->rx_cfg >> 8) & 0x1f;
+ n = offset & 3;
+ fifo_len = (size + n + 3) >> 2;
+ /* Add a word for the CRC. */
+ fifo_len++;
+ if (s->rx_fifo_size - s->rx_fifo_used < fifo_len) {
+ return -1;
+ }
+
+ DPRINTF("Got packet len:%d fifo:%d filter:%s\n",
+ (int)size, fifo_len, filter ? "pass" : "fail");
+ val = 0;
+ crc = bswap32(crc32(~0, buf, size));
+ for (src_pos = 0; src_pos < size; src_pos++) {
+ val = (val >> 8) | ((uint32_t)buf[src_pos] << 24);
+ n++;
+ if (n == 4) {
+ n = 0;
+ rx_fifo_push(s, val);
+ val = 0;
+ }
+ }
+ if (n) {
+ val >>= ((4 - n) * 8);
+ val |= crc << (n * 8);
+ rx_fifo_push(s, val);
+ val = crc >> ((4 - n) * 8);
+ rx_fifo_push(s, val);
+ } else {
+ rx_fifo_push(s, crc);
+ }
+ n = s->rx_status_fifo_head + s->rx_status_fifo_used;
+ if (n >= s->rx_status_fifo_size) {
+ n -= s->rx_status_fifo_size;
+ }
+ s->rx_packet_size[s->rx_packet_size_tail] = fifo_len;
+ s->rx_packet_size_tail = (s->rx_packet_size_tail + 1023) & 1023;
+ s->rx_status_fifo_used++;
+
+ status = (size + 4) << 16;
+ if (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0xff &&
+ buf[3] == 0xff && buf[4] == 0xff && buf[5] == 0xff) {
+ status |= 0x00002000;
+ } else if (buf[0] & 1) {
+ status |= 0x00000400;
+ }
+ if (!filter) {
+ status |= 0x40000000;
+ }
+ s->rx_status_fifo[n] = status;
+
+ if (s->rx_status_fifo_used > (s->fifo_int & 0xff)) {
+ s->int_sts |= RSFL_INT;
+ }
+ lan9118_update(s);
+
+ return size;
+}
+
+static uint32_t rx_fifo_pop(lan9118_state *s)
+{
+ int n;
+ uint32_t val;
+
+ if (s->rxp_size == 0 && s->rxp_pad == 0) {
+ s->rxp_size = s->rx_packet_size[s->rx_packet_size_head];
+ s->rx_packet_size[s->rx_packet_size_head] = 0;
+ if (s->rxp_size != 0) {
+ s->rx_packet_size_head = (s->rx_packet_size_head + 1023) & 1023;
+ s->rxp_offset = (s->rx_cfg >> 10) & 7;
+ n = s->rxp_offset + s->rxp_size;
+ switch (s->rx_cfg >> 30) {
+ case 1:
+ n = (-n) & 3;
+ break;
+ case 2:
+ n = (-n) & 7;
+ break;
+ default:
+ n = 0;
+ break;
+ }
+ s->rxp_pad = n;
+ DPRINTF("Pop packet size:%d offset:%d pad: %d\n",
+ s->rxp_size, s->rxp_offset, s->rxp_pad);
+ }
+ }
+ if (s->rxp_offset > 0) {
+ s->rxp_offset--;
+ val = 0;
+ } else if (s->rxp_size > 0) {
+ s->rxp_size--;
+ val = s->rx_fifo[s->rx_fifo_head++];
+ if (s->rx_fifo_head >= s->rx_fifo_size) {
+ s->rx_fifo_head -= s->rx_fifo_size;
+ }
+ s->rx_fifo_used--;
+ } else if (s->rxp_pad > 0) {
+ s->rxp_pad--;
+ val = 0;
+ } else {
+ DPRINTF("RX underflow\n");
+ s->int_sts |= RXE_INT;
+ val = 0;
+ }
+ lan9118_update(s);
+ return val;
+}
+
+static void do_tx_packet(lan9118_state *s)
+{
+ int n;
+ uint32_t status;
+
+ /* FIXME: Honor TX disable, and allow queueing of packets. */
+ if (s->phy_control & 0x4000) {
+ /* This assumes the receive routine doesn't touch the VLANClient. */
+ lan9118_receive(qemu_get_queue(s->nic), s->txp->data, s->txp->len);
+ } else {
+ qemu_send_packet(qemu_get_queue(s->nic), s->txp->data, s->txp->len);
+ }
+ s->txp->fifo_used = 0;
+
+ if (s->tx_status_fifo_used == 512) {
+ /* Status FIFO full */
+ return;
+ }
+ /* Add entry to status FIFO. */
+ status = s->txp->cmd_b & 0xffff0000u;
+ DPRINTF("Sent packet tag:%04x len %d\n", status >> 16, s->txp->len);
+ n = (s->tx_status_fifo_head + s->tx_status_fifo_used) & 511;
+ s->tx_status_fifo[n] = status;
+ s->tx_status_fifo_used++;
+ if (s->tx_status_fifo_used == 512) {
+ s->int_sts |= TSFF_INT;
+ /* TODO: Stop transmission. */
+ }
+}
+
+static uint32_t rx_status_fifo_pop(lan9118_state *s)
+{
+ uint32_t val;
+
+ val = s->rx_status_fifo[s->rx_status_fifo_head];
+ if (s->rx_status_fifo_used != 0) {
+ s->rx_status_fifo_used--;
+ s->rx_status_fifo_head++;
+ if (s->rx_status_fifo_head >= s->rx_status_fifo_size) {
+ s->rx_status_fifo_head -= s->rx_status_fifo_size;
+ }
+ /* ??? What value should be returned when the FIFO is empty? */
+ DPRINTF("RX status pop 0x%08x\n", val);
+ }
+ return val;
+}
+
+static uint32_t tx_status_fifo_pop(lan9118_state *s)
+{
+ uint32_t val;
+
+ val = s->tx_status_fifo[s->tx_status_fifo_head];
+ if (s->tx_status_fifo_used != 0) {
+ s->tx_status_fifo_used--;
+ s->tx_status_fifo_head = (s->tx_status_fifo_head + 1) & 511;
+ /* ??? What value should be returned when the FIFO is empty? */
+ }
+ return val;
+}
+
+static void tx_fifo_push(lan9118_state *s, uint32_t val)
+{
+ int n;
+
+ if (s->txp->fifo_used == s->tx_fifo_size) {
+ s->int_sts |= TDFO_INT;
+ return;
+ }
+ switch (s->txp->state) {
+ case TX_IDLE:
+ s->txp->cmd_a = val & 0x831f37ff;
+ s->txp->fifo_used++;
+ s->txp->state = TX_B;
+ s->txp->buffer_size = extract32(s->txp->cmd_a, 0, 11);
+ s->txp->offset = extract32(s->txp->cmd_a, 16, 5);
+ break;
+ case TX_B:
+ if (s->txp->cmd_a & 0x2000) {
+ /* First segment */
+ s->txp->cmd_b = val;
+ s->txp->fifo_used++;
+ /* End alignment does not include command words. */
+ n = (s->txp->buffer_size + s->txp->offset + 3) >> 2;
+ switch ((n >> 24) & 3) {
+ case 1:
+ n = (-n) & 3;
+ break;
+ case 2:
+ n = (-n) & 7;
+ break;
+ default:
+ n = 0;
+ }
+ s->txp->pad = n;
+ s->txp->len = 0;
+ }
+ DPRINTF("Block len:%d offset:%d pad:%d cmd %08x\n",
+ s->txp->buffer_size, s->txp->offset, s->txp->pad,
+ s->txp->cmd_a);
+ s->txp->state = TX_DATA;
+ break;
+ case TX_DATA:
+ if (s->txp->offset >= 4) {
+ s->txp->offset -= 4;
+ break;
+ }
+ if (s->txp->buffer_size <= 0 && s->txp->pad != 0) {
+ s->txp->pad--;
+ } else {
+ n = MIN(4, s->txp->buffer_size + s->txp->offset);
+ while (s->txp->offset) {
+ val >>= 8;
+ n--;
+ s->txp->offset--;
+ }
+ /* Documentation is somewhat unclear on the ordering of bytes
+ in FIFO words. Empirical results show it to be little-endian.
+ */
+ /* TODO: FIFO overflow checking. */
+ while (n--) {
+ s->txp->data[s->txp->len] = val & 0xff;
+ s->txp->len++;
+ val >>= 8;
+ s->txp->buffer_size--;
+ }
+ s->txp->fifo_used++;
+ }
+ if (s->txp->buffer_size <= 0 && s->txp->pad == 0) {
+ if (s->txp->cmd_a & 0x1000) {
+ do_tx_packet(s);
+ }
+ if (s->txp->cmd_a & 0x80000000) {
+ s->int_sts |= TX_IOC_INT;
+ }
+ s->txp->state = TX_IDLE;
+ }
+ break;
+ }
+}
+
+static uint32_t do_phy_read(lan9118_state *s, int reg)
+{
+ uint32_t val;
+
+ switch (reg) {
+ case 0: /* Basic Control */
+ return s->phy_control;
+ case 1: /* Basic Status */
+ return s->phy_status;
+ case 2: /* ID1 */
+ return 0x0007;
+ case 3: /* ID2 */
+ return 0xc0d1;
+ case 4: /* Auto-neg advertisement */
+ return s->phy_advertise;
+ case 5: /* Auto-neg Link Partner Ability */
+ return 0x0f71;
+ case 6: /* Auto-neg Expansion */
+ return 1;
+ /* TODO 17, 18, 27, 29, 30, 31 */
+ case 29: /* Interrupt source. */
+ val = s->phy_int;
+ s->phy_int = 0;
+ phy_update_irq(s);
+ return val;
+ case 30: /* Interrupt mask */
+ return s->phy_int_mask;
+ default:
+ BADF("PHY read reg %d\n", reg);
+ return 0;
+ }
+}
+
+static void do_phy_write(lan9118_state *s, int reg, uint32_t val)
+{
+ switch (reg) {
+ case 0: /* Basic Control */
+ if (val & 0x8000) {
+ phy_reset(s);
+ break;
+ }
+ s->phy_control = val & 0x7980;
+ /* Complete autonegotiation immediately. */
+ if (val & 0x1000) {
+ s->phy_status |= 0x0020;
+ }
+ break;
+ case 4: /* Auto-neg advertisement */
+ s->phy_advertise = (val & 0x2d7f) | 0x80;
+ break;
+ /* TODO 17, 18, 27, 31 */
+ case 30: /* Interrupt mask */
+ s->phy_int_mask = val & 0xff;
+ phy_update_irq(s);
+ break;
+ default:
+ BADF("PHY write reg %d = 0x%04x\n", reg, val);
+ }
+}
+
+static void do_mac_write(lan9118_state *s, int reg, uint32_t val)
+{
+ switch (reg) {
+ case MAC_CR:
+ if ((s->mac_cr & MAC_CR_RXEN) != 0 && (val & MAC_CR_RXEN) == 0) {
+ s->int_sts |= RXSTOP_INT;
+ }
+ s->mac_cr = val & ~MAC_CR_RESERVED;
+ DPRINTF("MAC_CR: %08x\n", val);
+ break;
+ case MAC_ADDRH:
+ s->conf.macaddr.a[4] = val & 0xff;
+ s->conf.macaddr.a[5] = (val >> 8) & 0xff;
+ lan9118_mac_changed(s);
+ break;
+ case MAC_ADDRL:
+ s->conf.macaddr.a[0] = val & 0xff;
+ s->conf.macaddr.a[1] = (val >> 8) & 0xff;
+ s->conf.macaddr.a[2] = (val >> 16) & 0xff;
+ s->conf.macaddr.a[3] = (val >> 24) & 0xff;
+ lan9118_mac_changed(s);
+ break;
+ case MAC_HASHH:
+ s->mac_hashh = val;
+ break;
+ case MAC_HASHL:
+ s->mac_hashl = val;
+ break;
+ case MAC_MII_ACC:
+ s->mac_mii_acc = val & 0xffc2;
+ if (val & 2) {
+ DPRINTF("PHY write %d = 0x%04x\n",
+ (val >> 6) & 0x1f, s->mac_mii_data);
+ do_phy_write(s, (val >> 6) & 0x1f, s->mac_mii_data);
+ } else {
+ s->mac_mii_data = do_phy_read(s, (val >> 6) & 0x1f);
+ DPRINTF("PHY read %d = 0x%04x\n",
+ (val >> 6) & 0x1f, s->mac_mii_data);
+ }
+ break;
+ case MAC_MII_DATA:
+ s->mac_mii_data = val & 0xffff;
+ break;
+ case MAC_FLOW:
+ s->mac_flow = val & 0xffff0000;
+ break;
+ case MAC_VLAN1:
+ /* Writing to this register changes a condition for
+ * FrameTooLong bit in rx_status. Since we do not set
+ * FrameTooLong anyway, just ignore write to this.
+ */
+ break;
+ default:
+ hw_error("lan9118: Unimplemented MAC register write: %d = 0x%x\n",
+ s->mac_cmd & 0xf, val);
+ }
+}
+
+static uint32_t do_mac_read(lan9118_state *s, int reg)
+{
+ switch (reg) {
+ case MAC_CR:
+ return s->mac_cr;
+ case MAC_ADDRH:
+ return s->conf.macaddr.a[4] | (s->conf.macaddr.a[5] << 8);
+ case MAC_ADDRL:
+ return s->conf.macaddr.a[0] | (s->conf.macaddr.a[1] << 8)
+ | (s->conf.macaddr.a[2] << 16) | (s->conf.macaddr.a[3] << 24);
+ case MAC_HASHH:
+ return s->mac_hashh;
+ break;
+ case MAC_HASHL:
+ return s->mac_hashl;
+ break;
+ case MAC_MII_ACC:
+ return s->mac_mii_acc;
+ case MAC_MII_DATA:
+ return s->mac_mii_data;
+ case MAC_FLOW:
+ return s->mac_flow;
+ default:
+ hw_error("lan9118: Unimplemented MAC register read: %d\n",
+ s->mac_cmd & 0xf);
+ }
+}
+
+static void lan9118_eeprom_cmd(lan9118_state *s, int cmd, int addr)
+{
+ s->e2p_cmd = (s->e2p_cmd & 0x10) | (cmd << 28) | addr;
+ switch (cmd) {
+ case 0:
+ s->e2p_data = s->eeprom[addr];
+ DPRINTF("EEPROM Read %d = 0x%02x\n", addr, s->e2p_data);
+ break;
+ case 1:
+ s->eeprom_writable = 0;
+ DPRINTF("EEPROM Write Disable\n");
+ break;
+ case 2: /* EWEN */
+ s->eeprom_writable = 1;
+ DPRINTF("EEPROM Write Enable\n");
+ break;
+ case 3: /* WRITE */
+ if (s->eeprom_writable) {
+ s->eeprom[addr] &= s->e2p_data;
+ DPRINTF("EEPROM Write %d = 0x%02x\n", addr, s->e2p_data);
+ } else {
+ DPRINTF("EEPROM Write %d (ignored)\n", addr);
+ }
+ break;
+ case 4: /* WRAL */
+ if (s->eeprom_writable) {
+ for (addr = 0; addr < 128; addr++) {
+ s->eeprom[addr] &= s->e2p_data;
+ }
+ DPRINTF("EEPROM Write All 0x%02x\n", s->e2p_data);
+ } else {
+ DPRINTF("EEPROM Write All (ignored)\n");
+ }
+ break;
+ case 5: /* ERASE */
+ if (s->eeprom_writable) {
+ s->eeprom[addr] = 0xff;
+ DPRINTF("EEPROM Erase %d\n", addr);
+ } else {
+ DPRINTF("EEPROM Erase %d (ignored)\n", addr);
+ }
+ break;
+ case 6: /* ERAL */
+ if (s->eeprom_writable) {
+ memset(s->eeprom, 0xff, 128);
+ DPRINTF("EEPROM Erase All\n");
+ } else {
+ DPRINTF("EEPROM Erase All (ignored)\n");
+ }
+ break;
+ case 7: /* RELOAD */
+ lan9118_reload_eeprom(s);
+ break;
+ }
+}
+
+static void lan9118_tick(void *opaque)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+ if (s->int_en & GPT_INT) {
+ s->int_sts |= GPT_INT;
+ }
+ lan9118_update(s);
+}
+
+static void lan9118_writel(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+ offset &= 0xff;
+
+ //DPRINTF("Write reg 0x%02x = 0x%08x\n", (int)offset, val);
+ if (offset >= 0x20 && offset < 0x40) {
+ /* TX FIFO */
+ tx_fifo_push(s, val);
+ return;
+ }
+ switch (offset) {
+ case CSR_IRQ_CFG:
+ /* TODO: Implement interrupt deassertion intervals. */
+ val &= (IRQ_EN | IRQ_POL | IRQ_TYPE);
+ s->irq_cfg = (s->irq_cfg & IRQ_INT) | val;
+ break;
+ case CSR_INT_STS:
+ s->int_sts &= ~val;
+ break;
+ case CSR_INT_EN:
+ s->int_en = val & ~RESERVED_INT;
+ s->int_sts |= val & SW_INT;
+ break;
+ case CSR_FIFO_INT:
+ DPRINTF("FIFO INT levels %08x\n", val);
+ s->fifo_int = val;
+ break;
+ case CSR_RX_CFG:
+ if (val & 0x8000) {
+ /* RX_DUMP */
+ s->rx_fifo_used = 0;
+ s->rx_status_fifo_used = 0;
+ s->rx_packet_size_tail = s->rx_packet_size_head;
+ s->rx_packet_size[s->rx_packet_size_head] = 0;
+ }
+ s->rx_cfg = val & 0xcfff1ff0;
+ break;
+ case CSR_TX_CFG:
+ if (val & 0x8000) {
+ s->tx_status_fifo_used = 0;
+ }
+ if (val & 0x4000) {
+ s->txp->state = TX_IDLE;
+ s->txp->fifo_used = 0;
+ s->txp->cmd_a = 0xffffffff;
+ }
+ s->tx_cfg = val & 6;
+ break;
+ case CSR_HW_CFG:
+ if (val & 1) {
+ /* SRST */
+ lan9118_reset(DEVICE(s));
+ } else {
+ s->hw_cfg = (val & 0x003f300) | (s->hw_cfg & 0x4);
+ }
+ break;
+ case CSR_RX_DP_CTRL:
+ if (val & 0x80000000) {
+ /* Skip forward to next packet. */
+ s->rxp_pad = 0;
+ s->rxp_offset = 0;
+ if (s->rxp_size == 0) {
+ /* Pop a word to start the next packet. */
+ rx_fifo_pop(s);
+ s->rxp_pad = 0;
+ s->rxp_offset = 0;
+ }
+ s->rx_fifo_head += s->rxp_size;
+ if (s->rx_fifo_head >= s->rx_fifo_size) {
+ s->rx_fifo_head -= s->rx_fifo_size;
+ }
+ }
+ break;
+ case CSR_PMT_CTRL:
+ if (val & 0x400) {
+ phy_reset(s);
+ }
+ s->pmt_ctrl &= ~0x34e;
+ s->pmt_ctrl |= (val & 0x34e);
+ break;
+ case CSR_GPIO_CFG:
+ /* Probably just enabling LEDs. */
+ s->gpio_cfg = val & 0x7777071f;
+ break;
+ case CSR_GPT_CFG:
+ if ((s->gpt_cfg ^ val) & GPT_TIMER_EN) {
+ if (val & GPT_TIMER_EN) {
+ ptimer_set_count(s->timer, val & 0xffff);
+ ptimer_run(s->timer, 0);
+ } else {
+ ptimer_stop(s->timer);
+ ptimer_set_count(s->timer, 0xffff);
+ }
+ }
+ s->gpt_cfg = val & (GPT_TIMER_EN | 0xffff);
+ break;
+ case CSR_WORD_SWAP:
+ /* Ignored because we're in 32-bit mode. */
+ s->word_swap = val;
+ break;
+ case CSR_MAC_CSR_CMD:
+ s->mac_cmd = val & 0x4000000f;
+ if (val & 0x80000000) {
+ if (val & 0x40000000) {
+ s->mac_data = do_mac_read(s, val & 0xf);
+ DPRINTF("MAC read %d = 0x%08x\n", val & 0xf, s->mac_data);
+ } else {
+ DPRINTF("MAC write %d = 0x%08x\n", val & 0xf, s->mac_data);
+ do_mac_write(s, val & 0xf, s->mac_data);
+ }
+ }
+ break;
+ case CSR_MAC_CSR_DATA:
+ s->mac_data = val;
+ break;
+ case CSR_AFC_CFG:
+ s->afc_cfg = val & 0x00ffffff;
+ break;
+ case CSR_E2P_CMD:
+ lan9118_eeprom_cmd(s, (val >> 28) & 7, val & 0x7f);
+ break;
+ case CSR_E2P_DATA:
+ s->e2p_data = val & 0xff;
+ break;
+
+ default:
+ hw_error("lan9118_write: Bad reg 0x%x = %x\n", (int)offset, (int)val);
+ break;
+ }
+ lan9118_update(s);
+}
+
+static void lan9118_writew(void *opaque, hwaddr offset,
+ uint32_t val)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+ offset &= 0xff;
+
+ if (s->write_word_prev_offset != (offset & ~0x3)) {
+ /* New offset, reset word counter */
+ s->write_word_n = 0;
+ s->write_word_prev_offset = offset & ~0x3;
+ }
+
+ if (offset & 0x2) {
+ s->write_word_h = val;
+ } else {
+ s->write_word_l = val;
+ }
+
+ //DPRINTF("Writew reg 0x%02x = 0x%08x\n", (int)offset, val);
+ s->write_word_n++;
+ if (s->write_word_n == 2) {
+ s->write_word_n = 0;
+ lan9118_writel(s, offset & ~3, s->write_word_l +
+ (s->write_word_h << 16), 4);
+ }
+}
+
+static void lan9118_16bit_mode_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ switch (size) {
+ case 2:
+ lan9118_writew(opaque, offset, (uint32_t)val);
+ return;
+ case 4:
+ lan9118_writel(opaque, offset, val, size);
+ return;
+ }
+
+ hw_error("lan9118_write: Bad size 0x%x\n", size);
+}
+
+static uint64_t lan9118_readl(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+
+ //DPRINTF("Read reg 0x%02x\n", (int)offset);
+ if (offset < 0x20) {
+ /* RX FIFO */
+ return rx_fifo_pop(s);
+ }
+ switch (offset) {
+ case 0x40:
+ return rx_status_fifo_pop(s);
+ case 0x44:
+ return s->rx_status_fifo[s->tx_status_fifo_head];
+ case 0x48:
+ return tx_status_fifo_pop(s);
+ case 0x4c:
+ return s->tx_status_fifo[s->tx_status_fifo_head];
+ case CSR_ID_REV:
+ return 0x01180001;
+ case CSR_IRQ_CFG:
+ return s->irq_cfg;
+ case CSR_INT_STS:
+ return s->int_sts;
+ case CSR_INT_EN:
+ return s->int_en;
+ case CSR_BYTE_TEST:
+ return 0x87654321;
+ case CSR_FIFO_INT:
+ return s->fifo_int;
+ case CSR_RX_CFG:
+ return s->rx_cfg;
+ case CSR_TX_CFG:
+ return s->tx_cfg;
+ case CSR_HW_CFG:
+ return s->hw_cfg;
+ case CSR_RX_DP_CTRL:
+ return 0;
+ case CSR_RX_FIFO_INF:
+ return (s->rx_status_fifo_used << 16) | (s->rx_fifo_used << 2);
+ case CSR_TX_FIFO_INF:
+ return (s->tx_status_fifo_used << 16)
+ | (s->tx_fifo_size - s->txp->fifo_used);
+ case CSR_PMT_CTRL:
+ return s->pmt_ctrl;
+ case CSR_GPIO_CFG:
+ return s->gpio_cfg;
+ case CSR_GPT_CFG:
+ return s->gpt_cfg;
+ case CSR_GPT_CNT:
+ return ptimer_get_count(s->timer);
+ case CSR_WORD_SWAP:
+ return s->word_swap;
+ case CSR_FREE_RUN:
+ return (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 40) - s->free_timer_start;
+ case CSR_RX_DROP:
+ /* TODO: Implement dropped frames counter. */
+ return 0;
+ case CSR_MAC_CSR_CMD:
+ return s->mac_cmd;
+ case CSR_MAC_CSR_DATA:
+ return s->mac_data;
+ case CSR_AFC_CFG:
+ return s->afc_cfg;
+ case CSR_E2P_CMD:
+ return s->e2p_cmd;
+ case CSR_E2P_DATA:
+ return s->e2p_data;
+ }
+ hw_error("lan9118_read: Bad reg 0x%x\n", (int)offset);
+ return 0;
+}
+
+static uint32_t lan9118_readw(void *opaque, hwaddr offset)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+ uint32_t val;
+
+ if (s->read_word_prev_offset != (offset & ~0x3)) {
+ /* New offset, reset word counter */
+ s->read_word_n = 0;
+ s->read_word_prev_offset = offset & ~0x3;
+ }
+
+ s->read_word_n++;
+ if (s->read_word_n == 1) {
+ s->read_long = lan9118_readl(s, offset & ~3, 4);
+ } else {
+ s->read_word_n = 0;
+ }
+
+ if (offset & 2) {
+ val = s->read_long >> 16;
+ } else {
+ val = s->read_long & 0xFFFF;
+ }
+
+ //DPRINTF("Readw reg 0x%02x, val 0x%x\n", (int)offset, val);
+ return val;
+}
+
+static uint64_t lan9118_16bit_mode_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ switch (size) {
+ case 2:
+ return lan9118_readw(opaque, offset);
+ case 4:
+ return lan9118_readl(opaque, offset, size);
+ }
+
+ hw_error("lan9118_read: Bad size 0x%x\n", size);
+ return 0;
+}
+
+static const MemoryRegionOps lan9118_mem_ops = {
+ .read = lan9118_readl,
+ .write = lan9118_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps lan9118_16bit_mem_ops = {
+ .read = lan9118_16bit_mode_read,
+ .write = lan9118_16bit_mode_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static NetClientInfo net_lan9118_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = lan9118_receive,
+ .link_status_changed = lan9118_set_link,
+};
+
+static int lan9118_init1(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ lan9118_state *s = LAN9118(dev);
+ QEMUBH *bh;
+ int i;
+ const MemoryRegionOps *mem_ops =
+ s->mode_16bit ? &lan9118_16bit_mem_ops : &lan9118_mem_ops;
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), mem_ops, s,
+ "lan9118-mmio", 0x100);
+ sysbus_init_mmio(sbd, &s->mmio);
+ sysbus_init_irq(sbd, &s->irq);
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+
+ s->nic = qemu_new_nic(&net_lan9118_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+ s->eeprom[0] = 0xa5;
+ for (i = 0; i < 6; i++) {
+ s->eeprom[i + 1] = s->conf.macaddr.a[i];
+ }
+ s->pmt_ctrl = 1;
+ s->txp = &s->tx_packet;
+
+ bh = qemu_bh_new(lan9118_tick, s);
+ s->timer = ptimer_init(bh);
+ ptimer_set_freq(s->timer, 10000);
+ ptimer_set_limit(s->timer, 0xffff, 1);
+
+ return 0;
+}
+
+static Property lan9118_properties[] = {
+ DEFINE_NIC_PROPERTIES(lan9118_state, conf),
+ DEFINE_PROP_UINT32("mode_16bit", lan9118_state, mode_16bit, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void lan9118_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lan9118_init1;
+ dc->reset = lan9118_reset;
+ dc->props = lan9118_properties;
+ dc->vmsd = &vmstate_lan9118;
+}
+
+static const TypeInfo lan9118_info = {
+ .name = TYPE_LAN9118,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(lan9118_state),
+ .class_init = lan9118_class_init,
+};
+
+static void lan9118_register_types(void)
+{
+ type_register_static(&lan9118_info);
+}
+
+/* Legacy helper function. Should go away when machine config files are
+ implemented. */
+void lan9118_init(NICInfo *nd, uint32_t base, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ qemu_check_nic_model(nd, "lan9118");
+ dev = qdev_create(NULL, TYPE_LAN9118);
+ qdev_set_nic_properties(dev, nd);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, base);
+ sysbus_connect_irq(s, 0, irq);
+}
+
+type_init(lan9118_register_types)
diff --git a/hw/net/lance.c b/hw/net/lance.c
new file mode 100644
index 00000000..780b39d6
--- /dev/null
+++ b/hw/net/lance.c
@@ -0,0 +1,183 @@
+/*
+ * QEMU AMD PC-Net II (Am79C970A) emulation
+ *
+ * Copyright (c) 2004 Antony T Curtis
+ *
+ * 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.
+ */
+
+/* This software was written to be compatible with the specification:
+ * AMD Am79C970A PCnet-PCI II Ethernet Controller Data-Sheet
+ * AMD Publication# 19436 Rev:E Amendment/0 Issue Date: June 2000
+ */
+
+/*
+ * On Sparc32, this is the Lance (Am7990) part of chip STP2000 (Master I/O), also
+ * produced as NCR89C100. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
+ * and
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR92C990.txt
+ */
+
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "qemu/timer.h"
+#include "qemu/sockets.h"
+#include "hw/sparc/sun4m.h"
+#include "pcnet.h"
+#include "trace.h"
+#include "sysemu/sysemu.h"
+
+#define TYPE_LANCE "lance"
+#define SYSBUS_PCNET(obj) \
+ OBJECT_CHECK(SysBusPCNetState, (obj), TYPE_LANCE)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ PCNetState state;
+} SysBusPCNetState;
+
+static void parent_lance_reset(void *opaque, int irq, int level)
+{
+ SysBusPCNetState *d = opaque;
+ if (level)
+ pcnet_h_reset(&d->state);
+}
+
+static void lance_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ SysBusPCNetState *d = opaque;
+
+ trace_lance_mem_writew(addr, val & 0xffff);
+ pcnet_ioport_writew(&d->state, addr, val & 0xffff);
+}
+
+static uint64_t lance_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SysBusPCNetState *d = opaque;
+ uint32_t val;
+
+ val = pcnet_ioport_readw(&d->state, addr);
+ trace_lance_mem_readw(addr, val & 0xffff);
+ return val & 0xffff;
+}
+
+static const MemoryRegionOps lance_mem_ops = {
+ .read = lance_mem_read,
+ .write = lance_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 2,
+ .max_access_size = 2,
+ },
+};
+
+static NetClientInfo net_lance_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = pcnet_receive,
+ .link_status_changed = pcnet_set_link_status,
+};
+
+static const VMStateDescription vmstate_lance = {
+ .name = "pcnet",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, SysBusPCNetState, 0, vmstate_pcnet, PCNetState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int lance_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ SysBusPCNetState *d = SYSBUS_PCNET(dev);
+ PCNetState *s = &d->state;
+
+ memory_region_init_io(&s->mmio, OBJECT(d), &lance_mem_ops, d,
+ "lance-mmio", 4);
+
+ qdev_init_gpio_in(dev, parent_lance_reset, 1);
+
+ sysbus_init_mmio(sbd, &s->mmio);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->phys_mem_read = ledma_memory_read;
+ s->phys_mem_write = ledma_memory_write;
+ pcnet_common_init(dev, s, &net_lance_info);
+ return 0;
+}
+
+static void lance_reset(DeviceState *dev)
+{
+ SysBusPCNetState *d = SYSBUS_PCNET(dev);
+
+ pcnet_h_reset(&d->state);
+}
+
+static void lance_instance_init(Object *obj)
+{
+ SysBusPCNetState *d = SYSBUS_PCNET(obj);
+ PCNetState *s = &d->state;
+
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ DEVICE(obj), NULL);
+}
+
+static Property lance_properties[] = {
+ DEFINE_PROP_PTR("dma", SysBusPCNetState, state.dma_opaque),
+ DEFINE_NIC_PROPERTIES(SysBusPCNetState, state.conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void lance_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lance_init;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->fw_name = "ethernet";
+ dc->reset = lance_reset;
+ dc->vmsd = &vmstate_lance;
+ dc->props = lance_properties;
+ /* Reason: pointer property "dma" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo lance_info = {
+ .name = TYPE_LANCE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusPCNetState),
+ .class_init = lance_class_init,
+ .instance_init = lance_instance_init,
+};
+
+static void lance_register_types(void)
+{
+ type_register_static(&lance_info);
+}
+
+type_init(lance_register_types)
diff --git a/hw/net/mcf_fec.c b/hw/net/mcf_fec.c
new file mode 100644
index 00000000..21928f9f
--- /dev/null
+++ b/hw/net/mcf_fec.c
@@ -0,0 +1,534 @@
+/*
+ * ColdFire Fast Ethernet Controller emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+#include "hw/hw.h"
+#include "net/net.h"
+#include "hw/m68k/mcf.h"
+#include "hw/net/mii.h"
+/* For crc32 */
+#include <zlib.h>
+#include "exec/address-spaces.h"
+
+//#define DEBUG_FEC 1
+
+#ifdef DEBUG_FEC
+#define DPRINTF(fmt, ...) \
+do { printf("mcf_fec: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define FEC_MAX_FRAME_SIZE 2032
+
+typedef struct {
+ MemoryRegion *sysmem;
+ MemoryRegion iomem;
+ qemu_irq *irq;
+ NICState *nic;
+ NICConf conf;
+ uint32_t irq_state;
+ uint32_t eir;
+ uint32_t eimr;
+ int rx_enabled;
+ uint32_t rx_descriptor;
+ uint32_t tx_descriptor;
+ uint32_t ecr;
+ uint32_t mmfr;
+ uint32_t mscr;
+ uint32_t rcr;
+ uint32_t tcr;
+ uint32_t tfwr;
+ uint32_t rfsr;
+ uint32_t erdsr;
+ uint32_t etdsr;
+ uint32_t emrbr;
+} mcf_fec_state;
+
+#define FEC_INT_HB 0x80000000
+#define FEC_INT_BABR 0x40000000
+#define FEC_INT_BABT 0x20000000
+#define FEC_INT_GRA 0x10000000
+#define FEC_INT_TXF 0x08000000
+#define FEC_INT_TXB 0x04000000
+#define FEC_INT_RXF 0x02000000
+#define FEC_INT_RXB 0x01000000
+#define FEC_INT_MII 0x00800000
+#define FEC_INT_EB 0x00400000
+#define FEC_INT_LC 0x00200000
+#define FEC_INT_RL 0x00100000
+#define FEC_INT_UN 0x00080000
+
+#define FEC_EN 2
+#define FEC_RESET 1
+
+/* Map interrupt flags onto IRQ lines. */
+#define FEC_NUM_IRQ 13
+static const uint32_t mcf_fec_irq_map[FEC_NUM_IRQ] = {
+ FEC_INT_TXF,
+ FEC_INT_TXB,
+ FEC_INT_UN,
+ FEC_INT_RL,
+ FEC_INT_RXF,
+ FEC_INT_RXB,
+ FEC_INT_MII,
+ FEC_INT_LC,
+ FEC_INT_HB,
+ FEC_INT_GRA,
+ FEC_INT_EB,
+ FEC_INT_BABT,
+ FEC_INT_BABR
+};
+
+/* Buffer Descriptor. */
+typedef struct {
+ uint16_t flags;
+ uint16_t length;
+ uint32_t data;
+} mcf_fec_bd;
+
+#define FEC_BD_R 0x8000
+#define FEC_BD_E 0x8000
+#define FEC_BD_O1 0x4000
+#define FEC_BD_W 0x2000
+#define FEC_BD_O2 0x1000
+#define FEC_BD_L 0x0800
+#define FEC_BD_TC 0x0400
+#define FEC_BD_ABC 0x0200
+#define FEC_BD_M 0x0100
+#define FEC_BD_BC 0x0080
+#define FEC_BD_MC 0x0040
+#define FEC_BD_LG 0x0020
+#define FEC_BD_NO 0x0010
+#define FEC_BD_CR 0x0004
+#define FEC_BD_OV 0x0002
+#define FEC_BD_TR 0x0001
+
+static void mcf_fec_read_bd(mcf_fec_bd *bd, uint32_t addr)
+{
+ cpu_physical_memory_read(addr, bd, sizeof(*bd));
+ be16_to_cpus(&bd->flags);
+ be16_to_cpus(&bd->length);
+ be32_to_cpus(&bd->data);
+}
+
+static void mcf_fec_write_bd(mcf_fec_bd *bd, uint32_t addr)
+{
+ mcf_fec_bd tmp;
+ tmp.flags = cpu_to_be16(bd->flags);
+ tmp.length = cpu_to_be16(bd->length);
+ tmp.data = cpu_to_be32(bd->data);
+ cpu_physical_memory_write(addr, &tmp, sizeof(tmp));
+}
+
+static void mcf_fec_update(mcf_fec_state *s)
+{
+ uint32_t active;
+ uint32_t changed;
+ uint32_t mask;
+ int i;
+
+ active = s->eir & s->eimr;
+ changed = active ^s->irq_state;
+ for (i = 0; i < FEC_NUM_IRQ; i++) {
+ mask = mcf_fec_irq_map[i];
+ if (changed & mask) {
+ DPRINTF("IRQ %d = %d\n", i, (active & mask) != 0);
+ qemu_set_irq(s->irq[i], (active & mask) != 0);
+ }
+ }
+ s->irq_state = active;
+}
+
+static void mcf_fec_do_tx(mcf_fec_state *s)
+{
+ uint32_t addr;
+ mcf_fec_bd bd;
+ int frame_size;
+ int len;
+ uint8_t frame[FEC_MAX_FRAME_SIZE];
+ uint8_t *ptr;
+
+ DPRINTF("do_tx\n");
+ ptr = frame;
+ frame_size = 0;
+ addr = s->tx_descriptor;
+ while (1) {
+ mcf_fec_read_bd(&bd, addr);
+ DPRINTF("tx_bd %x flags %04x len %d data %08x\n",
+ addr, bd.flags, bd.length, bd.data);
+ if ((bd.flags & FEC_BD_R) == 0) {
+ /* Run out of descriptors to transmit. */
+ break;
+ }
+ len = bd.length;
+ if (frame_size + len > FEC_MAX_FRAME_SIZE) {
+ len = FEC_MAX_FRAME_SIZE - frame_size;
+ s->eir |= FEC_INT_BABT;
+ }
+ cpu_physical_memory_read(bd.data, ptr, len);
+ ptr += len;
+ frame_size += len;
+ if (bd.flags & FEC_BD_L) {
+ /* Last buffer in frame. */
+ DPRINTF("Sending packet\n");
+ qemu_send_packet(qemu_get_queue(s->nic), frame, len);
+ ptr = frame;
+ frame_size = 0;
+ s->eir |= FEC_INT_TXF;
+ }
+ s->eir |= FEC_INT_TXB;
+ bd.flags &= ~FEC_BD_R;
+ /* Write back the modified descriptor. */
+ mcf_fec_write_bd(&bd, addr);
+ /* Advance to the next descriptor. */
+ if ((bd.flags & FEC_BD_W) != 0) {
+ addr = s->etdsr;
+ } else {
+ addr += 8;
+ }
+ }
+ s->tx_descriptor = addr;
+}
+
+static void mcf_fec_enable_rx(mcf_fec_state *s)
+{
+ NetClientState *nc = qemu_get_queue(s->nic);
+ mcf_fec_bd bd;
+
+ mcf_fec_read_bd(&bd, s->rx_descriptor);
+ s->rx_enabled = ((bd.flags & FEC_BD_E) != 0);
+ if (s->rx_enabled) {
+ qemu_flush_queued_packets(nc);
+ }
+}
+
+static void mcf_fec_reset(mcf_fec_state *s)
+{
+ s->eir = 0;
+ s->eimr = 0;
+ s->rx_enabled = 0;
+ s->ecr = 0;
+ s->mscr = 0;
+ s->rcr = 0x05ee0001;
+ s->tcr = 0;
+ s->tfwr = 0;
+ s->rfsr = 0x500;
+}
+
+#define MMFR_WRITE_OP (1 << 28)
+#define MMFR_READ_OP (2 << 28)
+#define MMFR_PHYADDR(v) (((v) >> 23) & 0x1f)
+#define MMFR_REGNUM(v) (((v) >> 18) & 0x1f)
+
+static uint64_t mcf_fec_read_mdio(mcf_fec_state *s)
+{
+ uint64_t v;
+
+ if (s->mmfr & MMFR_WRITE_OP)
+ return s->mmfr;
+ if (MMFR_PHYADDR(s->mmfr) != 1)
+ return s->mmfr |= 0xffff;
+
+ switch (MMFR_REGNUM(s->mmfr)) {
+ case MII_BMCR:
+ v = MII_BMCR_SPEED | MII_BMCR_AUTOEN | MII_BMCR_FD;
+ break;
+ case MII_BMSR:
+ v = MII_BMSR_100TX_FD | MII_BMSR_100TX_HD | MII_BMSR_10T_FD |
+ MII_BMSR_10T_HD | MII_BMSR_MFPS | MII_BMSR_AN_COMP |
+ MII_BMSR_AUTONEG | MII_BMSR_LINK_ST;
+ break;
+ case MII_PHYID1:
+ v = DP83848_PHYID1;
+ break;
+ case MII_PHYID2:
+ v = DP83848_PHYID2;
+ break;
+ case MII_ANAR:
+ v = MII_ANAR_TXFD | MII_ANAR_TX | MII_ANAR_10FD |
+ MII_ANAR_10 | MII_ANAR_CSMACD;
+ break;
+ case MII_ANLPAR:
+ v = MII_ANLPAR_ACK | MII_ANLPAR_TXFD | MII_ANLPAR_TX |
+ MII_ANLPAR_10FD | MII_ANLPAR_10 | MII_ANLPAR_CSMACD;
+ break;
+ default:
+ v = 0xffff;
+ break;
+ }
+ s->mmfr = (s->mmfr & ~0xffff) | v;
+ return s->mmfr;
+}
+
+static uint64_t mcf_fec_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ mcf_fec_state *s = (mcf_fec_state *)opaque;
+ switch (addr & 0x3ff) {
+ case 0x004: return s->eir;
+ case 0x008: return s->eimr;
+ case 0x010: return s->rx_enabled ? (1 << 24) : 0; /* RDAR */
+ case 0x014: return 0; /* TDAR */
+ case 0x024: return s->ecr;
+ case 0x040: return mcf_fec_read_mdio(s);
+ case 0x044: return s->mscr;
+ case 0x064: return 0; /* MIBC */
+ case 0x084: return s->rcr;
+ case 0x0c4: return s->tcr;
+ case 0x0e4: /* PALR */
+ return (s->conf.macaddr.a[0] << 24) | (s->conf.macaddr.a[1] << 16)
+ | (s->conf.macaddr.a[2] << 8) | s->conf.macaddr.a[3];
+ break;
+ case 0x0e8: /* PAUR */
+ return (s->conf.macaddr.a[4] << 24) | (s->conf.macaddr.a[5] << 16) | 0x8808;
+ case 0x0ec: return 0x10000; /* OPD */
+ case 0x118: return 0;
+ case 0x11c: return 0;
+ case 0x120: return 0;
+ case 0x124: return 0;
+ case 0x144: return s->tfwr;
+ case 0x14c: return 0x600;
+ case 0x150: return s->rfsr;
+ case 0x180: return s->erdsr;
+ case 0x184: return s->etdsr;
+ case 0x188: return s->emrbr;
+ default:
+ hw_error("mcf_fec_read: Bad address 0x%x\n", (int)addr);
+ return 0;
+ }
+}
+
+static void mcf_fec_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ mcf_fec_state *s = (mcf_fec_state *)opaque;
+ switch (addr & 0x3ff) {
+ case 0x004:
+ s->eir &= ~value;
+ break;
+ case 0x008:
+ s->eimr = value;
+ break;
+ case 0x010: /* RDAR */
+ if ((s->ecr & FEC_EN) && !s->rx_enabled) {
+ DPRINTF("RX enable\n");
+ mcf_fec_enable_rx(s);
+ }
+ break;
+ case 0x014: /* TDAR */
+ if (s->ecr & FEC_EN) {
+ mcf_fec_do_tx(s);
+ }
+ break;
+ case 0x024:
+ s->ecr = value;
+ if (value & FEC_RESET) {
+ DPRINTF("Reset\n");
+ mcf_fec_reset(s);
+ }
+ if ((s->ecr & FEC_EN) == 0) {
+ s->rx_enabled = 0;
+ }
+ break;
+ case 0x040:
+ s->mmfr = value;
+ s->eir |= FEC_INT_MII;
+ break;
+ case 0x044:
+ s->mscr = value & 0xfe;
+ break;
+ case 0x064:
+ /* TODO: Implement MIB. */
+ break;
+ case 0x084:
+ s->rcr = value & 0x07ff003f;
+ /* TODO: Implement LOOP mode. */
+ break;
+ case 0x0c4: /* TCR */
+ /* We transmit immediately, so raise GRA immediately. */
+ s->tcr = value;
+ if (value & 1)
+ s->eir |= FEC_INT_GRA;
+ break;
+ case 0x0e4: /* PALR */
+ s->conf.macaddr.a[0] = value >> 24;
+ s->conf.macaddr.a[1] = value >> 16;
+ s->conf.macaddr.a[2] = value >> 8;
+ s->conf.macaddr.a[3] = value;
+ break;
+ case 0x0e8: /* PAUR */
+ s->conf.macaddr.a[4] = value >> 24;
+ s->conf.macaddr.a[5] = value >> 16;
+ break;
+ case 0x0ec:
+ /* OPD */
+ break;
+ case 0x118:
+ case 0x11c:
+ case 0x120:
+ case 0x124:
+ /* TODO: implement MAC hash filtering. */
+ break;
+ case 0x144:
+ s->tfwr = value & 3;
+ break;
+ case 0x14c:
+ /* FRBR writes ignored. */
+ break;
+ case 0x150:
+ s->rfsr = (value & 0x3fc) | 0x400;
+ break;
+ case 0x180:
+ s->erdsr = value & ~3;
+ s->rx_descriptor = s->erdsr;
+ break;
+ case 0x184:
+ s->etdsr = value & ~3;
+ s->tx_descriptor = s->etdsr;
+ break;
+ case 0x188:
+ s->emrbr = value & 0x7f0;
+ break;
+ default:
+ hw_error("mcf_fec_write Bad address 0x%x\n", (int)addr);
+ }
+ mcf_fec_update(s);
+}
+
+static int mcf_fec_have_receive_space(mcf_fec_state *s, size_t want)
+{
+ mcf_fec_bd bd;
+ uint32_t addr;
+
+ /* Walk descriptor list to determine if we have enough buffer */
+ addr = s->rx_descriptor;
+ while (want > 0) {
+ mcf_fec_read_bd(&bd, addr);
+ if ((bd.flags & FEC_BD_E) == 0) {
+ return 0;
+ }
+ if (want < s->emrbr) {
+ return 1;
+ }
+ want -= s->emrbr;
+ /* Advance to the next descriptor. */
+ if ((bd.flags & FEC_BD_W) != 0) {
+ addr = s->erdsr;
+ } else {
+ addr += 8;
+ }
+ }
+ return 0;
+}
+
+static ssize_t mcf_fec_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ mcf_fec_state *s = qemu_get_nic_opaque(nc);
+ mcf_fec_bd bd;
+ uint32_t flags = 0;
+ uint32_t addr;
+ uint32_t crc;
+ uint32_t buf_addr;
+ uint8_t *crc_ptr;
+ unsigned int buf_len;
+ size_t retsize;
+
+ DPRINTF("do_rx len %d\n", size);
+ if (!s->rx_enabled) {
+ return -1;
+ }
+ /* 4 bytes for the CRC. */
+ size += 4;
+ crc = cpu_to_be32(crc32(~0, buf, size));
+ crc_ptr = (uint8_t *)&crc;
+ /* Huge frames are truncted. */
+ if (size > FEC_MAX_FRAME_SIZE) {
+ size = FEC_MAX_FRAME_SIZE;
+ flags |= FEC_BD_TR | FEC_BD_LG;
+ }
+ /* Frames larger than the user limit just set error flags. */
+ if (size > (s->rcr >> 16)) {
+ flags |= FEC_BD_LG;
+ }
+ /* Check if we have enough space in current descriptors */
+ if (!mcf_fec_have_receive_space(s, size)) {
+ return 0;
+ }
+ addr = s->rx_descriptor;
+ retsize = size;
+ while (size > 0) {
+ mcf_fec_read_bd(&bd, addr);
+ buf_len = (size <= s->emrbr) ? size: s->emrbr;
+ bd.length = buf_len;
+ size -= buf_len;
+ DPRINTF("rx_bd %x length %d\n", addr, bd.length);
+ /* The last 4 bytes are the CRC. */
+ if (size < 4)
+ buf_len += size - 4;
+ buf_addr = bd.data;
+ cpu_physical_memory_write(buf_addr, buf, buf_len);
+ buf += buf_len;
+ if (size < 4) {
+ cpu_physical_memory_write(buf_addr + buf_len, crc_ptr, 4 - size);
+ crc_ptr += 4 - size;
+ }
+ bd.flags &= ~FEC_BD_E;
+ if (size == 0) {
+ /* Last buffer in frame. */
+ bd.flags |= flags | FEC_BD_L;
+ DPRINTF("rx frame flags %04x\n", bd.flags);
+ s->eir |= FEC_INT_RXF;
+ } else {
+ s->eir |= FEC_INT_RXB;
+ }
+ mcf_fec_write_bd(&bd, addr);
+ /* Advance to the next descriptor. */
+ if ((bd.flags & FEC_BD_W) != 0) {
+ addr = s->erdsr;
+ } else {
+ addr += 8;
+ }
+ }
+ s->rx_descriptor = addr;
+ mcf_fec_enable_rx(s);
+ mcf_fec_update(s);
+ return retsize;
+}
+
+static const MemoryRegionOps mcf_fec_ops = {
+ .read = mcf_fec_read,
+ .write = mcf_fec_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static NetClientInfo net_mcf_fec_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = mcf_fec_receive,
+};
+
+void mcf_fec_init(MemoryRegion *sysmem, NICInfo *nd,
+ hwaddr base, qemu_irq *irq)
+{
+ mcf_fec_state *s;
+
+ qemu_check_nic_model(nd, "mcf_fec");
+
+ s = (mcf_fec_state *)g_malloc0(sizeof(mcf_fec_state));
+ s->sysmem = sysmem;
+ s->irq = irq;
+
+ memory_region_init_io(&s->iomem, NULL, &mcf_fec_ops, s, "fec", 0x400);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ s->conf.macaddr = nd->macaddr;
+ s->conf.peers.ncs[0] = nd->netdev;
+
+ s->nic = qemu_new_nic(&net_mcf_fec_info, &s->conf, nd->model, nd->name, s);
+
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+}
diff --git a/hw/net/milkymist-minimac2.c b/hw/net/milkymist-minimac2.c
new file mode 100644
index 00000000..5d1cf085
--- /dev/null
+++ b/hw/net/milkymist-minimac2.c
@@ -0,0 +1,540 @@
+/*
+ * QEMU model of the Milkymist minimac2 block.
+ *
+ * Copyright (c) 2011 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * not available yet
+ *
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "net/net.h"
+#include "qemu/error-report.h"
+
+#include <zlib.h>
+
+enum {
+ R_SETUP = 0,
+ R_MDIO,
+ R_STATE0,
+ R_COUNT0,
+ R_STATE1,
+ R_COUNT1,
+ R_TXCOUNT,
+ R_MAX
+};
+
+enum {
+ SETUP_PHY_RST = (1<<0),
+};
+
+enum {
+ MDIO_DO = (1<<0),
+ MDIO_DI = (1<<1),
+ MDIO_OE = (1<<2),
+ MDIO_CLK = (1<<3),
+};
+
+enum {
+ STATE_EMPTY = 0,
+ STATE_LOADED = 1,
+ STATE_PENDING = 2,
+};
+
+enum {
+ MDIO_OP_WRITE = 1,
+ MDIO_OP_READ = 2,
+};
+
+enum mdio_state {
+ MDIO_STATE_IDLE,
+ MDIO_STATE_READING,
+ MDIO_STATE_WRITING,
+};
+
+enum {
+ R_PHY_ID1 = 2,
+ R_PHY_ID2 = 3,
+ R_PHY_MAX = 32
+};
+
+#define MINIMAC2_MTU 1530
+#define MINIMAC2_BUFFER_SIZE 2048
+
+struct MilkymistMinimac2MdioState {
+ int last_clk;
+ int count;
+ uint32_t data;
+ uint16_t data_out;
+ int state;
+
+ uint8_t phy_addr;
+ uint8_t reg_addr;
+};
+typedef struct MilkymistMinimac2MdioState MilkymistMinimac2MdioState;
+
+#define TYPE_MILKYMIST_MINIMAC2 "milkymist-minimac2"
+#define MILKYMIST_MINIMAC2(obj) \
+ OBJECT_CHECK(MilkymistMinimac2State, (obj), TYPE_MILKYMIST_MINIMAC2)
+
+struct MilkymistMinimac2State {
+ SysBusDevice parent_obj;
+
+ NICState *nic;
+ NICConf conf;
+ char *phy_model;
+ MemoryRegion buffers;
+ MemoryRegion regs_region;
+
+ qemu_irq rx_irq;
+ qemu_irq tx_irq;
+
+ uint32_t regs[R_MAX];
+
+ MilkymistMinimac2MdioState mdio;
+
+ uint16_t phy_regs[R_PHY_MAX];
+
+ uint8_t *rx0_buf;
+ uint8_t *rx1_buf;
+ uint8_t *tx_buf;
+};
+typedef struct MilkymistMinimac2State MilkymistMinimac2State;
+
+static const uint8_t preamble_sfd[] = {
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5
+};
+
+static void minimac2_mdio_write_reg(MilkymistMinimac2State *s,
+ uint8_t phy_addr, uint8_t reg_addr, uint16_t value)
+{
+ trace_milkymist_minimac2_mdio_write(phy_addr, reg_addr, value);
+
+ /* nop */
+}
+
+static uint16_t minimac2_mdio_read_reg(MilkymistMinimac2State *s,
+ uint8_t phy_addr, uint8_t reg_addr)
+{
+ uint16_t r = s->phy_regs[reg_addr];
+
+ trace_milkymist_minimac2_mdio_read(phy_addr, reg_addr, r);
+
+ return r;
+}
+
+static void minimac2_update_mdio(MilkymistMinimac2State *s)
+{
+ MilkymistMinimac2MdioState *m = &s->mdio;
+
+ /* detect rising clk edge */
+ if (m->last_clk == 0 && (s->regs[R_MDIO] & MDIO_CLK)) {
+ /* shift data in */
+ int bit = ((s->regs[R_MDIO] & MDIO_DO)
+ && (s->regs[R_MDIO] & MDIO_OE)) ? 1 : 0;
+ m->data = (m->data << 1) | bit;
+
+ /* check for sync */
+ if (m->data == 0xffffffff) {
+ m->count = 32;
+ }
+
+ if (m->count == 16) {
+ uint8_t start = (m->data >> 14) & 0x3;
+ uint8_t op = (m->data >> 12) & 0x3;
+ uint8_t ta = (m->data) & 0x3;
+
+ if (start == 1 && op == MDIO_OP_WRITE && ta == 2) {
+ m->state = MDIO_STATE_WRITING;
+ } else if (start == 1 && op == MDIO_OP_READ && (ta & 1) == 0) {
+ m->state = MDIO_STATE_READING;
+ } else {
+ m->state = MDIO_STATE_IDLE;
+ }
+
+ if (m->state != MDIO_STATE_IDLE) {
+ m->phy_addr = (m->data >> 7) & 0x1f;
+ m->reg_addr = (m->data >> 2) & 0x1f;
+ }
+
+ if (m->state == MDIO_STATE_READING) {
+ m->data_out = minimac2_mdio_read_reg(s, m->phy_addr,
+ m->reg_addr);
+ }
+ }
+
+ if (m->count < 16 && m->state == MDIO_STATE_READING) {
+ int bit = (m->data_out & 0x8000) ? 1 : 0;
+ m->data_out <<= 1;
+
+ if (bit) {
+ s->regs[R_MDIO] |= MDIO_DI;
+ } else {
+ s->regs[R_MDIO] &= ~MDIO_DI;
+ }
+ }
+
+ if (m->count == 0 && m->state) {
+ if (m->state == MDIO_STATE_WRITING) {
+ uint16_t data = m->data & 0xffff;
+ minimac2_mdio_write_reg(s, m->phy_addr, m->reg_addr, data);
+ }
+ m->state = MDIO_STATE_IDLE;
+ }
+ m->count--;
+ }
+
+ m->last_clk = (s->regs[R_MDIO] & MDIO_CLK) ? 1 : 0;
+}
+
+static size_t assemble_frame(uint8_t *buf, size_t size,
+ const uint8_t *payload, size_t payload_size)
+{
+ uint32_t crc;
+
+ if (size < payload_size + 12) {
+ error_report("milkymist_minimac2: received too big ethernet frame");
+ return 0;
+ }
+
+ /* prepend preamble and sfd */
+ memcpy(buf, preamble_sfd, 8);
+
+ /* now copy the payload */
+ memcpy(buf + 8, payload, payload_size);
+
+ /* pad frame if needed */
+ if (payload_size < 60) {
+ memset(buf + payload_size + 8, 0, 60 - payload_size);
+ payload_size = 60;
+ }
+
+ /* append fcs */
+ crc = cpu_to_le32(crc32(0, buf + 8, payload_size));
+ memcpy(buf + payload_size + 8, &crc, 4);
+
+ return payload_size + 12;
+}
+
+static void minimac2_tx(MilkymistMinimac2State *s)
+{
+ uint32_t txcount = s->regs[R_TXCOUNT];
+ uint8_t *buf = s->tx_buf;
+
+ if (txcount < 64) {
+ error_report("milkymist_minimac2: ethernet frame too small (%u < %u)",
+ txcount, 64);
+ goto err;
+ }
+
+ if (txcount > MINIMAC2_MTU) {
+ error_report("milkymist_minimac2: MTU exceeded (%u > %u)",
+ txcount, MINIMAC2_MTU);
+ goto err;
+ }
+
+ if (memcmp(buf, preamble_sfd, 8) != 0) {
+ error_report("milkymist_minimac2: frame doesn't contain the preamble "
+ "and/or the SFD (%02x %02x %02x %02x %02x %02x %02x %02x)",
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
+ goto err;
+ }
+
+ trace_milkymist_minimac2_tx_frame(txcount - 12);
+
+ /* send packet, skipping preamble and sfd */
+ qemu_send_packet_raw(qemu_get_queue(s->nic), buf + 8, txcount - 12);
+
+ s->regs[R_TXCOUNT] = 0;
+
+err:
+ trace_milkymist_minimac2_pulse_irq_tx();
+ qemu_irq_pulse(s->tx_irq);
+}
+
+static void update_rx_interrupt(MilkymistMinimac2State *s)
+{
+ if (s->regs[R_STATE0] == STATE_PENDING
+ || s->regs[R_STATE1] == STATE_PENDING) {
+ trace_milkymist_minimac2_raise_irq_rx();
+ qemu_irq_raise(s->rx_irq);
+ } else {
+ trace_milkymist_minimac2_lower_irq_rx();
+ qemu_irq_lower(s->rx_irq);
+ }
+}
+
+static ssize_t minimac2_rx(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ MilkymistMinimac2State *s = qemu_get_nic_opaque(nc);
+
+ uint32_t r_count;
+ uint32_t r_state;
+ uint8_t *rx_buf;
+
+ size_t frame_size;
+
+ trace_milkymist_minimac2_rx_frame(buf, size);
+
+ /* choose appropriate slot */
+ if (s->regs[R_STATE0] == STATE_LOADED) {
+ r_count = R_COUNT0;
+ r_state = R_STATE0;
+ rx_buf = s->rx0_buf;
+ } else if (s->regs[R_STATE1] == STATE_LOADED) {
+ r_count = R_COUNT1;
+ r_state = R_STATE1;
+ rx_buf = s->rx1_buf;
+ } else {
+ return 0;
+ }
+
+ /* assemble frame */
+ frame_size = assemble_frame(rx_buf, MINIMAC2_BUFFER_SIZE, buf, size);
+
+ if (frame_size == 0) {
+ return size;
+ }
+
+ trace_milkymist_minimac2_rx_transfer(rx_buf, frame_size);
+
+ /* update slot */
+ s->regs[r_count] = frame_size;
+ s->regs[r_state] = STATE_PENDING;
+
+ update_rx_interrupt(s);
+
+ return size;
+}
+
+static uint64_t
+minimac2_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MilkymistMinimac2State *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SETUP:
+ case R_MDIO:
+ case R_STATE0:
+ case R_COUNT0:
+ case R_STATE1:
+ case R_COUNT1:
+ case R_TXCOUNT:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_minimac2: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_minimac2_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static int minimac2_can_rx(MilkymistMinimac2State *s)
+{
+ if (s->regs[R_STATE0] == STATE_LOADED) {
+ return 1;
+ }
+ if (s->regs[R_STATE1] == STATE_LOADED) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+minimac2_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistMinimac2State *s = opaque;
+
+ trace_milkymist_minimac2_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_MDIO:
+ {
+ /* MDIO_DI is read only */
+ int mdio_di = (s->regs[R_MDIO] & MDIO_DI);
+ s->regs[R_MDIO] = value;
+ if (mdio_di) {
+ s->regs[R_MDIO] |= mdio_di;
+ } else {
+ s->regs[R_MDIO] &= ~mdio_di;
+ }
+
+ minimac2_update_mdio(s);
+ } break;
+ case R_TXCOUNT:
+ s->regs[addr] = value;
+ if (value > 0) {
+ minimac2_tx(s);
+ }
+ break;
+ case R_STATE0:
+ case R_STATE1:
+ s->regs[addr] = value;
+ update_rx_interrupt(s);
+ if (minimac2_can_rx(s)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ break;
+ case R_SETUP:
+ case R_COUNT0:
+ case R_COUNT1:
+ s->regs[addr] = value;
+ break;
+
+ default:
+ error_report("milkymist_minimac2: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps minimac2_ops = {
+ .read = minimac2_read,
+ .write = minimac2_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void milkymist_minimac2_reset(DeviceState *d)
+{
+ MilkymistMinimac2State *s = MILKYMIST_MINIMAC2(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+ for (i = 0; i < R_PHY_MAX; i++) {
+ s->phy_regs[i] = 0;
+ }
+
+ /* defaults */
+ s->phy_regs[R_PHY_ID1] = 0x0022; /* Micrel KSZ8001L */
+ s->phy_regs[R_PHY_ID2] = 0x161a;
+}
+
+static NetClientInfo net_milkymist_minimac2_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = minimac2_rx,
+};
+
+static int milkymist_minimac2_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ MilkymistMinimac2State *s = MILKYMIST_MINIMAC2(dev);
+ size_t buffers_size = TARGET_PAGE_ALIGN(3 * MINIMAC2_BUFFER_SIZE);
+
+ sysbus_init_irq(sbd, &s->rx_irq);
+ sysbus_init_irq(sbd, &s->tx_irq);
+
+ memory_region_init_io(&s->regs_region, OBJECT(dev), &minimac2_ops, s,
+ "milkymist-minimac2", R_MAX * 4);
+ sysbus_init_mmio(sbd, &s->regs_region);
+
+ /* register buffers memory */
+ memory_region_init_ram(&s->buffers, OBJECT(dev), "milkymist-minimac2.buffers",
+ buffers_size, &error_abort);
+ vmstate_register_ram_global(&s->buffers);
+ s->rx0_buf = memory_region_get_ram_ptr(&s->buffers);
+ s->rx1_buf = s->rx0_buf + MINIMAC2_BUFFER_SIZE;
+ s->tx_buf = s->rx1_buf + MINIMAC2_BUFFER_SIZE;
+
+ sysbus_init_mmio(sbd, &s->buffers);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(&net_milkymist_minimac2_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_minimac2_mdio = {
+ .name = "milkymist-minimac2-mdio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(last_clk, MilkymistMinimac2MdioState),
+ VMSTATE_INT32(count, MilkymistMinimac2MdioState),
+ VMSTATE_UINT32(data, MilkymistMinimac2MdioState),
+ VMSTATE_UINT16(data_out, MilkymistMinimac2MdioState),
+ VMSTATE_INT32(state, MilkymistMinimac2MdioState),
+ VMSTATE_UINT8(phy_addr, MilkymistMinimac2MdioState),
+ VMSTATE_UINT8(reg_addr, MilkymistMinimac2MdioState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_milkymist_minimac2 = {
+ .name = "milkymist-minimac2",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistMinimac2State, R_MAX),
+ VMSTATE_UINT16_ARRAY(phy_regs, MilkymistMinimac2State, R_PHY_MAX),
+ VMSTATE_STRUCT(mdio, MilkymistMinimac2State, 0,
+ vmstate_milkymist_minimac2_mdio, MilkymistMinimac2MdioState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property milkymist_minimac2_properties[] = {
+ DEFINE_NIC_PROPERTIES(MilkymistMinimac2State, conf),
+ DEFINE_PROP_STRING("phy_model", MilkymistMinimac2State, phy_model),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void milkymist_minimac2_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_minimac2_init;
+ dc->reset = milkymist_minimac2_reset;
+ dc->vmsd = &vmstate_milkymist_minimac2;
+ dc->props = milkymist_minimac2_properties;
+}
+
+static const TypeInfo milkymist_minimac2_info = {
+ .name = TYPE_MILKYMIST_MINIMAC2,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistMinimac2State),
+ .class_init = milkymist_minimac2_class_init,
+};
+
+static void milkymist_minimac2_register_types(void)
+{
+ type_register_static(&milkymist_minimac2_info);
+}
+
+type_init(milkymist_minimac2_register_types)
diff --git a/hw/net/mipsnet.c b/hw/net/mipsnet.c
new file mode 100644
index 00000000..f261011a
--- /dev/null
+++ b/hw/net/mipsnet.c
@@ -0,0 +1,286 @@
+#include "hw/hw.h"
+#include "net/net.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+
+/* MIPSnet register offsets */
+
+#define MIPSNET_DEV_ID 0x00
+#define MIPSNET_BUSY 0x08
+#define MIPSNET_RX_DATA_COUNT 0x0c
+#define MIPSNET_TX_DATA_COUNT 0x10
+#define MIPSNET_INT_CTL 0x14
+# define MIPSNET_INTCTL_TXDONE 0x00000001
+# define MIPSNET_INTCTL_RXDONE 0x00000002
+# define MIPSNET_INTCTL_TESTBIT 0x80000000
+#define MIPSNET_INTERRUPT_INFO 0x18
+#define MIPSNET_RX_DATA_BUFFER 0x1c
+#define MIPSNET_TX_DATA_BUFFER 0x20
+
+#define MAX_ETH_FRAME_SIZE 1514
+
+#define TYPE_MIPS_NET "mipsnet"
+#define MIPS_NET(obj) OBJECT_CHECK(MIPSnetState, (obj), TYPE_MIPS_NET)
+
+typedef struct MIPSnetState {
+ SysBusDevice parent_obj;
+
+ uint32_t busy;
+ uint32_t rx_count;
+ uint32_t rx_read;
+ uint32_t tx_count;
+ uint32_t tx_written;
+ uint32_t intctl;
+ uint8_t rx_buffer[MAX_ETH_FRAME_SIZE];
+ uint8_t tx_buffer[MAX_ETH_FRAME_SIZE];
+ MemoryRegion io;
+ qemu_irq irq;
+ NICState *nic;
+ NICConf conf;
+} MIPSnetState;
+
+static void mipsnet_reset(MIPSnetState *s)
+{
+ s->busy = 1;
+ s->rx_count = 0;
+ s->rx_read = 0;
+ s->tx_count = 0;
+ s->tx_written = 0;
+ s->intctl = 0;
+ memset(s->rx_buffer, 0, MAX_ETH_FRAME_SIZE);
+ memset(s->tx_buffer, 0, MAX_ETH_FRAME_SIZE);
+}
+
+static void mipsnet_update_irq(MIPSnetState *s)
+{
+ int isr = !!s->intctl;
+ trace_mipsnet_irq(isr, s->intctl);
+ qemu_set_irq(s->irq, isr);
+}
+
+static int mipsnet_buffer_full(MIPSnetState *s)
+{
+ if (s->rx_count >= MAX_ETH_FRAME_SIZE)
+ return 1;
+ return 0;
+}
+
+static int mipsnet_can_receive(NetClientState *nc)
+{
+ MIPSnetState *s = qemu_get_nic_opaque(nc);
+
+ if (s->busy)
+ return 0;
+ return !mipsnet_buffer_full(s);
+}
+
+static ssize_t mipsnet_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ MIPSnetState *s = qemu_get_nic_opaque(nc);
+
+ trace_mipsnet_receive(size);
+ if (!mipsnet_can_receive(nc))
+ return 0;
+
+ s->busy = 1;
+
+ /* Just accept everything. */
+
+ /* Write packet data. */
+ memcpy(s->rx_buffer, buf, size);
+
+ s->rx_count = size;
+ s->rx_read = 0;
+
+ /* Now we can signal we have received something. */
+ s->intctl |= MIPSNET_INTCTL_RXDONE;
+ mipsnet_update_irq(s);
+
+ return size;
+}
+
+static uint64_t mipsnet_ioport_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ MIPSnetState *s = opaque;
+ int ret = 0;
+
+ addr &= 0x3f;
+ switch (addr) {
+ case MIPSNET_DEV_ID:
+ ret = be32_to_cpu(0x4d495053); /* MIPS */
+ break;
+ case MIPSNET_DEV_ID + 4:
+ ret = be32_to_cpu(0x4e455430); /* NET0 */
+ break;
+ case MIPSNET_BUSY:
+ ret = s->busy;
+ break;
+ case MIPSNET_RX_DATA_COUNT:
+ ret = s->rx_count;
+ break;
+ case MIPSNET_TX_DATA_COUNT:
+ ret = s->tx_count;
+ break;
+ case MIPSNET_INT_CTL:
+ ret = s->intctl;
+ s->intctl &= ~MIPSNET_INTCTL_TESTBIT;
+ break;
+ case MIPSNET_INTERRUPT_INFO:
+ /* XXX: This seems to be a per-VPE interrupt number. */
+ ret = 0;
+ break;
+ case MIPSNET_RX_DATA_BUFFER:
+ if (s->rx_count) {
+ s->rx_count--;
+ ret = s->rx_buffer[s->rx_read++];
+ if (mipsnet_can_receive(s->nic->ncs)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ }
+ break;
+ /* Reads as zero. */
+ case MIPSNET_TX_DATA_BUFFER:
+ default:
+ break;
+ }
+ trace_mipsnet_read(addr, ret);
+ return ret;
+}
+
+static void mipsnet_ioport_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ MIPSnetState *s = opaque;
+
+ addr &= 0x3f;
+ trace_mipsnet_write(addr, val);
+ switch (addr) {
+ case MIPSNET_TX_DATA_COUNT:
+ s->tx_count = (val <= MAX_ETH_FRAME_SIZE) ? val : 0;
+ s->tx_written = 0;
+ break;
+ case MIPSNET_INT_CTL:
+ if (val & MIPSNET_INTCTL_TXDONE) {
+ s->intctl &= ~MIPSNET_INTCTL_TXDONE;
+ } else if (val & MIPSNET_INTCTL_RXDONE) {
+ s->intctl &= ~MIPSNET_INTCTL_RXDONE;
+ } else if (val & MIPSNET_INTCTL_TESTBIT) {
+ mipsnet_reset(s);
+ s->intctl |= MIPSNET_INTCTL_TESTBIT;
+ } else if (!val) {
+ /* ACK testbit interrupt, flag was cleared on read. */
+ }
+ s->busy = !!s->intctl;
+ mipsnet_update_irq(s);
+ if (mipsnet_can_receive(s->nic->ncs)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ break;
+ case MIPSNET_TX_DATA_BUFFER:
+ s->tx_buffer[s->tx_written++] = val;
+ if (s->tx_written == s->tx_count) {
+ /* Send buffer. */
+ trace_mipsnet_send(s->tx_count);
+ qemu_send_packet(qemu_get_queue(s->nic), s->tx_buffer, s->tx_count);
+ s->tx_count = s->tx_written = 0;
+ s->intctl |= MIPSNET_INTCTL_TXDONE;
+ s->busy = 1;
+ mipsnet_update_irq(s);
+ }
+ break;
+ /* Read-only registers */
+ case MIPSNET_DEV_ID:
+ case MIPSNET_BUSY:
+ case MIPSNET_RX_DATA_COUNT:
+ case MIPSNET_INTERRUPT_INFO:
+ case MIPSNET_RX_DATA_BUFFER:
+ default:
+ break;
+ }
+}
+
+static const VMStateDescription vmstate_mipsnet = {
+ .name = "mipsnet",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(busy, MIPSnetState),
+ VMSTATE_UINT32(rx_count, MIPSnetState),
+ VMSTATE_UINT32(rx_read, MIPSnetState),
+ VMSTATE_UINT32(tx_count, MIPSnetState),
+ VMSTATE_UINT32(tx_written, MIPSnetState),
+ VMSTATE_UINT32(intctl, MIPSnetState),
+ VMSTATE_BUFFER(rx_buffer, MIPSnetState),
+ VMSTATE_BUFFER(tx_buffer, MIPSnetState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static NetClientInfo net_mipsnet_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = mipsnet_receive,
+};
+
+static const MemoryRegionOps mipsnet_ioport_ops = {
+ .read = mipsnet_ioport_read,
+ .write = mipsnet_ioport_write,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static int mipsnet_sysbus_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ MIPSnetState *s = MIPS_NET(dev);
+
+ memory_region_init_io(&s->io, OBJECT(dev), &mipsnet_ioport_ops, s,
+ "mipsnet-io", 36);
+ sysbus_init_mmio(sbd, &s->io);
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->nic = qemu_new_nic(&net_mipsnet_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ return 0;
+}
+
+static void mipsnet_sysbus_reset(DeviceState *dev)
+{
+ MIPSnetState *s = MIPS_NET(dev);
+ mipsnet_reset(s);
+}
+
+static Property mipsnet_properties[] = {
+ DEFINE_NIC_PROPERTIES(MIPSnetState, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mipsnet_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mipsnet_sysbus_init;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->desc = "MIPS Simulator network device";
+ dc->reset = mipsnet_sysbus_reset;
+ dc->vmsd = &vmstate_mipsnet;
+ dc->props = mipsnet_properties;
+}
+
+static const TypeInfo mipsnet_info = {
+ .name = TYPE_MIPS_NET,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MIPSnetState),
+ .class_init = mipsnet_class_init,
+};
+
+static void mipsnet_register_types(void)
+{
+ type_register_static(&mipsnet_info);
+}
+
+type_init(mipsnet_register_types)
diff --git a/hw/net/ne2000-isa.c b/hw/net/ne2000-isa.c
new file mode 100644
index 00000000..17e7199f
--- /dev/null
+++ b/hw/net/ne2000-isa.c
@@ -0,0 +1,152 @@
+/*
+ * QEMU NE2000 emulation -- isa bus windup
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/isa.h"
+#include "hw/qdev.h"
+#include "net/net.h"
+#include "ne2000.h"
+#include "exec/address-spaces.h"
+#include "qapi/visitor.h"
+
+#define TYPE_ISA_NE2000 "ne2k_isa"
+#define ISA_NE2000(obj) OBJECT_CHECK(ISANE2000State, (obj), TYPE_ISA_NE2000)
+
+typedef struct ISANE2000State {
+ ISADevice parent_obj;
+
+ uint32_t iobase;
+ uint32_t isairq;
+ NE2000State ne2000;
+} ISANE2000State;
+
+static NetClientInfo net_ne2000_isa_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = ne2000_can_receive,
+ .receive = ne2000_receive,
+};
+
+static const VMStateDescription vmstate_isa_ne2000 = {
+ .name = "ne2000",
+ .version_id = 2,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(ne2000, ISANE2000State, 0, vmstate_ne2000, NE2000State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void isa_ne2000_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISANE2000State *isa = ISA_NE2000(dev);
+ NE2000State *s = &isa->ne2000;
+
+ ne2000_setup_io(s, DEVICE(isadev), 0x20);
+ isa_register_ioport(isadev, &s->io, isa->iobase);
+
+ isa_init_irq(isadev, &s->irq, isa->isairq);
+
+ qemu_macaddr_default_if_unset(&s->c.macaddr);
+ ne2000_reset(s);
+
+ s->nic = qemu_new_nic(&net_ne2000_isa_info, &s->c,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->c.macaddr.a);
+}
+
+static Property ne2000_isa_properties[] = {
+ DEFINE_PROP_UINT32("iobase", ISANE2000State, iobase, 0x300),
+ DEFINE_PROP_UINT32("irq", ISANE2000State, isairq, 9),
+ DEFINE_NIC_PROPERTIES(ISANE2000State, ne2000.c),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void isa_ne2000_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = isa_ne2000_realizefn;
+ dc->props = ne2000_isa_properties;
+ dc->vmsd = &vmstate_isa_ne2000;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static void isa_ne2000_get_bootindex(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ ISANE2000State *isa = ISA_NE2000(obj);
+ NE2000State *s = &isa->ne2000;
+
+ visit_type_int32(v, &s->c.bootindex, name, errp);
+}
+
+static void isa_ne2000_set_bootindex(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ ISANE2000State *isa = ISA_NE2000(obj);
+ NE2000State *s = &isa->ne2000;
+ int32_t boot_index;
+ Error *local_err = NULL;
+
+ visit_type_int32(v, &boot_index, name, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ /* check whether bootindex is present in fw_boot_order list */
+ check_boot_index(boot_index, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ /* change bootindex to a new one */
+ s->c.bootindex = boot_index;
+
+out:
+ if (local_err) {
+ error_propagate(errp, local_err);
+ }
+}
+
+static void isa_ne2000_instance_init(Object *obj)
+{
+ object_property_add(obj, "bootindex", "int32",
+ isa_ne2000_get_bootindex,
+ isa_ne2000_set_bootindex, NULL, NULL, NULL);
+ object_property_set_int(obj, -1, "bootindex", NULL);
+}
+static const TypeInfo ne2000_isa_info = {
+ .name = TYPE_ISA_NE2000,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISANE2000State),
+ .class_init = isa_ne2000_class_initfn,
+ .instance_init = isa_ne2000_instance_init,
+};
+
+static void ne2000_isa_register_types(void)
+{
+ type_register_static(&ne2000_isa_info);
+}
+
+type_init(ne2000_isa_register_types)
diff --git a/hw/net/ne2000.c b/hw/net/ne2000.c
new file mode 100644
index 00000000..364f226d
--- /dev/null
+++ b/hw/net/ne2000.c
@@ -0,0 +1,801 @@
+/*
+ * QEMU NE2000 emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "net/net.h"
+#include "ne2000.h"
+#include "hw/loader.h"
+#include "sysemu/sysemu.h"
+
+/* debug NE2000 card */
+//#define DEBUG_NE2000
+
+#define MAX_ETH_FRAME_SIZE 1514
+
+#define E8390_CMD 0x00 /* The command register (for all pages) */
+/* Page 0 register offsets. */
+#define EN0_CLDALO 0x01 /* Low byte of current local dma addr RD */
+#define EN0_STARTPG 0x01 /* Starting page of ring bfr WR */
+#define EN0_CLDAHI 0x02 /* High byte of current local dma addr RD */
+#define EN0_STOPPG 0x02 /* Ending page +1 of ring bfr WR */
+#define EN0_BOUNDARY 0x03 /* Boundary page of ring bfr RD WR */
+#define EN0_TSR 0x04 /* Transmit status reg RD */
+#define EN0_TPSR 0x04 /* Transmit starting page WR */
+#define EN0_NCR 0x05 /* Number of collision reg RD */
+#define EN0_TCNTLO 0x05 /* Low byte of tx byte count WR */
+#define EN0_FIFO 0x06 /* FIFO RD */
+#define EN0_TCNTHI 0x06 /* High byte of tx byte count WR */
+#define EN0_ISR 0x07 /* Interrupt status reg RD WR */
+#define EN0_CRDALO 0x08 /* low byte of current remote dma address RD */
+#define EN0_RSARLO 0x08 /* Remote start address reg 0 */
+#define EN0_CRDAHI 0x09 /* high byte, current remote dma address RD */
+#define EN0_RSARHI 0x09 /* Remote start address reg 1 */
+#define EN0_RCNTLO 0x0a /* Remote byte count reg WR */
+#define EN0_RTL8029ID0 0x0a /* Realtek ID byte #1 RD */
+#define EN0_RCNTHI 0x0b /* Remote byte count reg WR */
+#define EN0_RTL8029ID1 0x0b /* Realtek ID byte #2 RD */
+#define EN0_RSR 0x0c /* rx status reg RD */
+#define EN0_RXCR 0x0c /* RX configuration reg WR */
+#define EN0_TXCR 0x0d /* TX configuration reg WR */
+#define EN0_COUNTER0 0x0d /* Rcv alignment error counter RD */
+#define EN0_DCFG 0x0e /* Data configuration reg WR */
+#define EN0_COUNTER1 0x0e /* Rcv CRC error counter RD */
+#define EN0_IMR 0x0f /* Interrupt mask reg WR */
+#define EN0_COUNTER2 0x0f /* Rcv missed frame error counter RD */
+
+#define EN1_PHYS 0x11
+#define EN1_CURPAG 0x17
+#define EN1_MULT 0x18
+
+#define EN2_STARTPG 0x21 /* Starting page of ring bfr RD */
+#define EN2_STOPPG 0x22 /* Ending page +1 of ring bfr RD */
+
+#define EN3_CONFIG0 0x33
+#define EN3_CONFIG1 0x34
+#define EN3_CONFIG2 0x35
+#define EN3_CONFIG3 0x36
+
+/* Register accessed at EN_CMD, the 8390 base addr. */
+#define E8390_STOP 0x01 /* Stop and reset the chip */
+#define E8390_START 0x02 /* Start the chip, clear reset */
+#define E8390_TRANS 0x04 /* Transmit a frame */
+#define E8390_RREAD 0x08 /* Remote read */
+#define E8390_RWRITE 0x10 /* Remote write */
+#define E8390_NODMA 0x20 /* Remote DMA */
+#define E8390_PAGE0 0x00 /* Select page chip registers */
+#define E8390_PAGE1 0x40 /* using the two high-order bits */
+#define E8390_PAGE2 0x80 /* Page 3 is invalid. */
+
+/* Bits in EN0_ISR - Interrupt status register */
+#define ENISR_RX 0x01 /* Receiver, no error */
+#define ENISR_TX 0x02 /* Transmitter, no error */
+#define ENISR_RX_ERR 0x04 /* Receiver, with error */
+#define ENISR_TX_ERR 0x08 /* Transmitter, with error */
+#define ENISR_OVER 0x10 /* Receiver overwrote the ring */
+#define ENISR_COUNTERS 0x20 /* Counters need emptying */
+#define ENISR_RDC 0x40 /* remote dma complete */
+#define ENISR_RESET 0x80 /* Reset completed */
+#define ENISR_ALL 0x3f /* Interrupts we will enable */
+
+/* Bits in received packet status byte and EN0_RSR*/
+#define ENRSR_RXOK 0x01 /* Received a good packet */
+#define ENRSR_CRC 0x02 /* CRC error */
+#define ENRSR_FAE 0x04 /* frame alignment error */
+#define ENRSR_FO 0x08 /* FIFO overrun */
+#define ENRSR_MPA 0x10 /* missed pkt */
+#define ENRSR_PHY 0x20 /* physical/multicast address */
+#define ENRSR_DIS 0x40 /* receiver disable. set in monitor mode */
+#define ENRSR_DEF 0x80 /* deferring */
+
+/* Transmitted packet status, EN0_TSR. */
+#define ENTSR_PTX 0x01 /* Packet transmitted without error */
+#define ENTSR_ND 0x02 /* The transmit wasn't deferred. */
+#define ENTSR_COL 0x04 /* The transmit collided at least once. */
+#define ENTSR_ABT 0x08 /* The transmit collided 16 times, and was deferred. */
+#define ENTSR_CRS 0x10 /* The carrier sense was lost. */
+#define ENTSR_FU 0x20 /* A "FIFO underrun" occurred during transmit. */
+#define ENTSR_CDH 0x40 /* The collision detect "heartbeat" signal was lost. */
+#define ENTSR_OWC 0x80 /* There was an out-of-window collision. */
+
+typedef struct PCINE2000State {
+ PCIDevice dev;
+ NE2000State ne2000;
+} PCINE2000State;
+
+void ne2000_reset(NE2000State *s)
+{
+ int i;
+
+ s->isr = ENISR_RESET;
+ memcpy(s->mem, &s->c.macaddr, 6);
+ s->mem[14] = 0x57;
+ s->mem[15] = 0x57;
+
+ /* duplicate prom data */
+ for(i = 15;i >= 0; i--) {
+ s->mem[2 * i] = s->mem[i];
+ s->mem[2 * i + 1] = s->mem[i];
+ }
+}
+
+static void ne2000_update_irq(NE2000State *s)
+{
+ int isr;
+ isr = (s->isr & s->imr) & 0x7f;
+#if defined(DEBUG_NE2000)
+ printf("NE2000: Set IRQ to %d (%02x %02x)\n",
+ isr ? 1 : 0, s->isr, s->imr);
+#endif
+ qemu_set_irq(s->irq, (isr != 0));
+}
+
+static int ne2000_buffer_full(NE2000State *s)
+{
+ int avail, index, boundary;
+
+ index = s->curpag << 8;
+ boundary = s->boundary << 8;
+ if (index < boundary)
+ avail = boundary - index;
+ else
+ avail = (s->stop - s->start) - (index - boundary);
+ if (avail < (MAX_ETH_FRAME_SIZE + 4))
+ return 1;
+ return 0;
+}
+
+int ne2000_can_receive(NetClientState *nc)
+{
+ NE2000State *s = qemu_get_nic_opaque(nc);
+
+ if (s->cmd & E8390_STOP)
+ return 1;
+ return !ne2000_buffer_full(s);
+}
+
+#define MIN_BUF_SIZE 60
+
+ssize_t ne2000_receive(NetClientState *nc, const uint8_t *buf, size_t size_)
+{
+ NE2000State *s = qemu_get_nic_opaque(nc);
+ int size = size_;
+ uint8_t *p;
+ unsigned int total_len, next, avail, len, index, mcast_idx;
+ uint8_t buf1[60];
+ static const uint8_t broadcast_macaddr[6] =
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+#if defined(DEBUG_NE2000)
+ printf("NE2000: received len=%d\n", size);
+#endif
+
+ if (s->cmd & E8390_STOP || ne2000_buffer_full(s))
+ return -1;
+
+ /* XXX: check this */
+ if (s->rxcr & 0x10) {
+ /* promiscuous: receive all */
+ } else {
+ if (!memcmp(buf, broadcast_macaddr, 6)) {
+ /* broadcast address */
+ if (!(s->rxcr & 0x04))
+ return size;
+ } else if (buf[0] & 0x01) {
+ /* multicast */
+ if (!(s->rxcr & 0x08))
+ return size;
+ mcast_idx = compute_mcast_idx(buf);
+ if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7))))
+ return size;
+ } else if (s->mem[0] == buf[0] &&
+ s->mem[2] == buf[1] &&
+ s->mem[4] == buf[2] &&
+ s->mem[6] == buf[3] &&
+ s->mem[8] == buf[4] &&
+ s->mem[10] == buf[5]) {
+ /* match */
+ } else {
+ return size;
+ }
+ }
+
+
+ /* if too small buffer, then expand it */
+ if (size < MIN_BUF_SIZE) {
+ memcpy(buf1, buf, size);
+ memset(buf1 + size, 0, MIN_BUF_SIZE - size);
+ buf = buf1;
+ size = MIN_BUF_SIZE;
+ }
+
+ index = s->curpag << 8;
+ if (index >= NE2000_PMEM_END) {
+ index = s->start;
+ }
+ /* 4 bytes for header */
+ total_len = size + 4;
+ /* address for next packet (4 bytes for CRC) */
+ next = index + ((total_len + 4 + 255) & ~0xff);
+ if (next >= s->stop)
+ next -= (s->stop - s->start);
+ /* prepare packet header */
+ p = s->mem + index;
+ s->rsr = ENRSR_RXOK; /* receive status */
+ /* XXX: check this */
+ if (buf[0] & 0x01)
+ s->rsr |= ENRSR_PHY;
+ p[0] = s->rsr;
+ p[1] = next >> 8;
+ p[2] = total_len;
+ p[3] = total_len >> 8;
+ index += 4;
+
+ /* write packet data */
+ while (size > 0) {
+ if (index <= s->stop)
+ avail = s->stop - index;
+ else
+ break;
+ len = size;
+ if (len > avail)
+ len = avail;
+ memcpy(s->mem + index, buf, len);
+ buf += len;
+ index += len;
+ if (index == s->stop)
+ index = s->start;
+ size -= len;
+ }
+ s->curpag = next >> 8;
+
+ /* now we can signal we have received something */
+ s->isr |= ENISR_RX;
+ ne2000_update_irq(s);
+
+ return size_;
+}
+
+static void ne2000_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ NE2000State *s = opaque;
+ int offset, page, index;
+
+ addr &= 0xf;
+#ifdef DEBUG_NE2000
+ printf("NE2000: write addr=0x%x val=0x%02x\n", addr, val);
+#endif
+ if (addr == E8390_CMD) {
+ /* control register */
+ s->cmd = val;
+ if (!(val & E8390_STOP)) { /* START bit makes no sense on RTL8029... */
+ s->isr &= ~ENISR_RESET;
+ /* test specific case: zero length transfer */
+ if ((val & (E8390_RREAD | E8390_RWRITE)) &&
+ s->rcnt == 0) {
+ s->isr |= ENISR_RDC;
+ ne2000_update_irq(s);
+ }
+ if (val & E8390_TRANS) {
+ index = (s->tpsr << 8);
+ /* XXX: next 2 lines are a hack to make netware 3.11 work */
+ if (index >= NE2000_PMEM_END)
+ index -= NE2000_PMEM_SIZE;
+ /* fail safe: check range on the transmitted length */
+ if (index + s->tcnt <= NE2000_PMEM_END) {
+ qemu_send_packet(qemu_get_queue(s->nic), s->mem + index,
+ s->tcnt);
+ }
+ /* signal end of transfer */
+ s->tsr = ENTSR_PTX;
+ s->isr |= ENISR_TX;
+ s->cmd &= ~E8390_TRANS;
+ ne2000_update_irq(s);
+ }
+ }
+ } else {
+ page = s->cmd >> 6;
+ offset = addr | (page << 4);
+ switch(offset) {
+ case EN0_STARTPG:
+ if (val << 8 <= NE2000_PMEM_END) {
+ s->start = val << 8;
+ }
+ break;
+ case EN0_STOPPG:
+ if (val << 8 <= NE2000_PMEM_END) {
+ s->stop = val << 8;
+ }
+ break;
+ case EN0_BOUNDARY:
+ if (val << 8 < NE2000_PMEM_END) {
+ s->boundary = val;
+ }
+ break;
+ case EN0_IMR:
+ s->imr = val;
+ ne2000_update_irq(s);
+ break;
+ case EN0_TPSR:
+ s->tpsr = val;
+ break;
+ case EN0_TCNTLO:
+ s->tcnt = (s->tcnt & 0xff00) | val;
+ break;
+ case EN0_TCNTHI:
+ s->tcnt = (s->tcnt & 0x00ff) | (val << 8);
+ break;
+ case EN0_RSARLO:
+ s->rsar = (s->rsar & 0xff00) | val;
+ break;
+ case EN0_RSARHI:
+ s->rsar = (s->rsar & 0x00ff) | (val << 8);
+ break;
+ case EN0_RCNTLO:
+ s->rcnt = (s->rcnt & 0xff00) | val;
+ break;
+ case EN0_RCNTHI:
+ s->rcnt = (s->rcnt & 0x00ff) | (val << 8);
+ break;
+ case EN0_RXCR:
+ s->rxcr = val;
+ break;
+ case EN0_DCFG:
+ s->dcfg = val;
+ break;
+ case EN0_ISR:
+ s->isr &= ~(val & 0x7f);
+ ne2000_update_irq(s);
+ break;
+ case EN1_PHYS ... EN1_PHYS + 5:
+ s->phys[offset - EN1_PHYS] = val;
+ break;
+ case EN1_CURPAG:
+ if (val << 8 < NE2000_PMEM_END) {
+ s->curpag = val;
+ }
+ break;
+ case EN1_MULT ... EN1_MULT + 7:
+ s->mult[offset - EN1_MULT] = val;
+ break;
+ }
+ }
+}
+
+static uint32_t ne2000_ioport_read(void *opaque, uint32_t addr)
+{
+ NE2000State *s = opaque;
+ int offset, page, ret;
+
+ addr &= 0xf;
+ if (addr == E8390_CMD) {
+ ret = s->cmd;
+ } else {
+ page = s->cmd >> 6;
+ offset = addr | (page << 4);
+ switch(offset) {
+ case EN0_TSR:
+ ret = s->tsr;
+ break;
+ case EN0_BOUNDARY:
+ ret = s->boundary;
+ break;
+ case EN0_ISR:
+ ret = s->isr;
+ break;
+ case EN0_RSARLO:
+ ret = s->rsar & 0x00ff;
+ break;
+ case EN0_RSARHI:
+ ret = s->rsar >> 8;
+ break;
+ case EN1_PHYS ... EN1_PHYS + 5:
+ ret = s->phys[offset - EN1_PHYS];
+ break;
+ case EN1_CURPAG:
+ ret = s->curpag;
+ break;
+ case EN1_MULT ... EN1_MULT + 7:
+ ret = s->mult[offset - EN1_MULT];
+ break;
+ case EN0_RSR:
+ ret = s->rsr;
+ break;
+ case EN2_STARTPG:
+ ret = s->start >> 8;
+ break;
+ case EN2_STOPPG:
+ ret = s->stop >> 8;
+ break;
+ case EN0_RTL8029ID0:
+ ret = 0x50;
+ break;
+ case EN0_RTL8029ID1:
+ ret = 0x43;
+ break;
+ case EN3_CONFIG0:
+ ret = 0; /* 10baseT media */
+ break;
+ case EN3_CONFIG2:
+ ret = 0x40; /* 10baseT active */
+ break;
+ case EN3_CONFIG3:
+ ret = 0x40; /* Full duplex */
+ break;
+ default:
+ ret = 0x00;
+ break;
+ }
+ }
+#ifdef DEBUG_NE2000
+ printf("NE2000: read addr=0x%x val=%02x\n", addr, ret);
+#endif
+ return ret;
+}
+
+static inline void ne2000_mem_writeb(NE2000State *s, uint32_t addr,
+ uint32_t val)
+{
+ if (addr < 32 ||
+ (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) {
+ s->mem[addr] = val;
+ }
+}
+
+static inline void ne2000_mem_writew(NE2000State *s, uint32_t addr,
+ uint32_t val)
+{
+ addr &= ~1; /* XXX: check exact behaviour if not even */
+ if (addr < 32 ||
+ (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) {
+ *(uint16_t *)(s->mem + addr) = cpu_to_le16(val);
+ }
+}
+
+static inline void ne2000_mem_writel(NE2000State *s, uint32_t addr,
+ uint32_t val)
+{
+ addr &= ~1; /* XXX: check exact behaviour if not even */
+ if (addr < 32
+ || (addr >= NE2000_PMEM_START
+ && addr + sizeof(uint32_t) <= NE2000_MEM_SIZE)) {
+ stl_le_p(s->mem + addr, val);
+ }
+}
+
+static inline uint32_t ne2000_mem_readb(NE2000State *s, uint32_t addr)
+{
+ if (addr < 32 ||
+ (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) {
+ return s->mem[addr];
+ } else {
+ return 0xff;
+ }
+}
+
+static inline uint32_t ne2000_mem_readw(NE2000State *s, uint32_t addr)
+{
+ addr &= ~1; /* XXX: check exact behaviour if not even */
+ if (addr < 32 ||
+ (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) {
+ return le16_to_cpu(*(uint16_t *)(s->mem + addr));
+ } else {
+ return 0xffff;
+ }
+}
+
+static inline uint32_t ne2000_mem_readl(NE2000State *s, uint32_t addr)
+{
+ addr &= ~1; /* XXX: check exact behaviour if not even */
+ if (addr < 32
+ || (addr >= NE2000_PMEM_START
+ && addr + sizeof(uint32_t) <= NE2000_MEM_SIZE)) {
+ return ldl_le_p(s->mem + addr);
+ } else {
+ return 0xffffffff;
+ }
+}
+
+static inline void ne2000_dma_update(NE2000State *s, int len)
+{
+ s->rsar += len;
+ /* wrap */
+ /* XXX: check what to do if rsar > stop */
+ if (s->rsar == s->stop)
+ s->rsar = s->start;
+
+ if (s->rcnt <= len) {
+ s->rcnt = 0;
+ /* signal end of transfer */
+ s->isr |= ENISR_RDC;
+ ne2000_update_irq(s);
+ } else {
+ s->rcnt -= len;
+ }
+}
+
+static void ne2000_asic_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ NE2000State *s = opaque;
+
+#ifdef DEBUG_NE2000
+ printf("NE2000: asic write val=0x%04x\n", val);
+#endif
+ if (s->rcnt == 0)
+ return;
+ if (s->dcfg & 0x01) {
+ /* 16 bit access */
+ ne2000_mem_writew(s, s->rsar, val);
+ ne2000_dma_update(s, 2);
+ } else {
+ /* 8 bit access */
+ ne2000_mem_writeb(s, s->rsar, val);
+ ne2000_dma_update(s, 1);
+ }
+}
+
+static uint32_t ne2000_asic_ioport_read(void *opaque, uint32_t addr)
+{
+ NE2000State *s = opaque;
+ int ret;
+
+ if (s->dcfg & 0x01) {
+ /* 16 bit access */
+ ret = ne2000_mem_readw(s, s->rsar);
+ ne2000_dma_update(s, 2);
+ } else {
+ /* 8 bit access */
+ ret = ne2000_mem_readb(s, s->rsar);
+ ne2000_dma_update(s, 1);
+ }
+#ifdef DEBUG_NE2000
+ printf("NE2000: asic read val=0x%04x\n", ret);
+#endif
+ return ret;
+}
+
+static void ne2000_asic_ioport_writel(void *opaque, uint32_t addr, uint32_t val)
+{
+ NE2000State *s = opaque;
+
+#ifdef DEBUG_NE2000
+ printf("NE2000: asic writel val=0x%04x\n", val);
+#endif
+ if (s->rcnt == 0)
+ return;
+ /* 32 bit access */
+ ne2000_mem_writel(s, s->rsar, val);
+ ne2000_dma_update(s, 4);
+}
+
+static uint32_t ne2000_asic_ioport_readl(void *opaque, uint32_t addr)
+{
+ NE2000State *s = opaque;
+ int ret;
+
+ /* 32 bit access */
+ ret = ne2000_mem_readl(s, s->rsar);
+ ne2000_dma_update(s, 4);
+#ifdef DEBUG_NE2000
+ printf("NE2000: asic readl val=0x%04x\n", ret);
+#endif
+ return ret;
+}
+
+static void ne2000_reset_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ /* nothing to do (end of reset pulse) */
+}
+
+static uint32_t ne2000_reset_ioport_read(void *opaque, uint32_t addr)
+{
+ NE2000State *s = opaque;
+ ne2000_reset(s);
+ return 0;
+}
+
+static int ne2000_post_load(void* opaque, int version_id)
+{
+ NE2000State* s = opaque;
+
+ if (version_id < 2) {
+ s->rxcr = 0x0c;
+ }
+ return 0;
+}
+
+const VMStateDescription vmstate_ne2000 = {
+ .name = "ne2000",
+ .version_id = 2,
+ .minimum_version_id = 0,
+ .post_load = ne2000_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_V(rxcr, NE2000State, 2),
+ VMSTATE_UINT8(cmd, NE2000State),
+ VMSTATE_UINT32(start, NE2000State),
+ VMSTATE_UINT32(stop, NE2000State),
+ VMSTATE_UINT8(boundary, NE2000State),
+ VMSTATE_UINT8(tsr, NE2000State),
+ VMSTATE_UINT8(tpsr, NE2000State),
+ VMSTATE_UINT16(tcnt, NE2000State),
+ VMSTATE_UINT16(rcnt, NE2000State),
+ VMSTATE_UINT32(rsar, NE2000State),
+ VMSTATE_UINT8(rsr, NE2000State),
+ VMSTATE_UINT8(isr, NE2000State),
+ VMSTATE_UINT8(dcfg, NE2000State),
+ VMSTATE_UINT8(imr, NE2000State),
+ VMSTATE_BUFFER(phys, NE2000State),
+ VMSTATE_UINT8(curpag, NE2000State),
+ VMSTATE_BUFFER(mult, NE2000State),
+ VMSTATE_UNUSED(4), /* was irq */
+ VMSTATE_BUFFER(mem, NE2000State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_ne2000 = {
+ .name = "ne2000",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCINE2000State),
+ VMSTATE_STRUCT(ne2000, PCINE2000State, 0, vmstate_ne2000, NE2000State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint64_t ne2000_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ NE2000State *s = opaque;
+
+ if (addr < 0x10 && size == 1) {
+ return ne2000_ioport_read(s, addr);
+ } else if (addr == 0x10) {
+ if (size <= 2) {
+ return ne2000_asic_ioport_read(s, addr);
+ } else {
+ return ne2000_asic_ioport_readl(s, addr);
+ }
+ } else if (addr == 0x1f && size == 1) {
+ return ne2000_reset_ioport_read(s, addr);
+ }
+ return ((uint64_t)1 << (size * 8)) - 1;
+}
+
+static void ne2000_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ NE2000State *s = opaque;
+
+ if (addr < 0x10 && size == 1) {
+ ne2000_ioport_write(s, addr, data);
+ } else if (addr == 0x10) {
+ if (size <= 2) {
+ ne2000_asic_ioport_write(s, addr, data);
+ } else {
+ ne2000_asic_ioport_writel(s, addr, data);
+ }
+ } else if (addr == 0x1f && size == 1) {
+ ne2000_reset_ioport_write(s, addr, data);
+ }
+}
+
+static const MemoryRegionOps ne2000_ops = {
+ .read = ne2000_read,
+ .write = ne2000_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/***********************************************************/
+/* PCI NE2000 definitions */
+
+void ne2000_setup_io(NE2000State *s, DeviceState *dev, unsigned size)
+{
+ memory_region_init_io(&s->io, OBJECT(dev), &ne2000_ops, s, "ne2000", size);
+}
+
+static NetClientInfo net_ne2000_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = ne2000_can_receive,
+ .receive = ne2000_receive,
+};
+
+static void pci_ne2000_realize(PCIDevice *pci_dev, Error **errp)
+{
+ PCINE2000State *d = DO_UPCAST(PCINE2000State, dev, pci_dev);
+ NE2000State *s;
+ uint8_t *pci_conf;
+
+ pci_conf = d->dev.config;
+ pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
+
+ s = &d->ne2000;
+ ne2000_setup_io(s, DEVICE(pci_dev), 0x100);
+ pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+ s->irq = pci_allocate_irq(&d->dev);
+
+ qemu_macaddr_default_if_unset(&s->c.macaddr);
+ ne2000_reset(s);
+
+ s->nic = qemu_new_nic(&net_ne2000_info, &s->c,
+ object_get_typename(OBJECT(pci_dev)), pci_dev->qdev.id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->c.macaddr.a);
+}
+
+static void pci_ne2000_exit(PCIDevice *pci_dev)
+{
+ PCINE2000State *d = DO_UPCAST(PCINE2000State, dev, pci_dev);
+ NE2000State *s = &d->ne2000;
+
+ qemu_del_nic(s->nic);
+ qemu_free_irq(s->irq);
+}
+
+static void ne2000_instance_init(Object *obj)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(obj);
+ PCINE2000State *d = DO_UPCAST(PCINE2000State, dev, pci_dev);
+ NE2000State *s = &d->ne2000;
+
+ device_add_bootindex_property(obj, &s->c.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ &pci_dev->qdev, NULL);
+}
+
+static Property ne2000_properties[] = {
+ DEFINE_NIC_PROPERTIES(PCINE2000State, ne2000.c),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ne2000_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_ne2000_realize;
+ k->exit = pci_ne2000_exit;
+ k->romfile = "efi-ne2k_pci.rom",
+ k->vendor_id = PCI_VENDOR_ID_REALTEK;
+ k->device_id = PCI_DEVICE_ID_REALTEK_8029;
+ k->class_id = PCI_CLASS_NETWORK_ETHERNET;
+ dc->vmsd = &vmstate_pci_ne2000;
+ dc->props = ne2000_properties;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo ne2000_info = {
+ .name = "ne2k_pci",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCINE2000State),
+ .class_init = ne2000_class_init,
+ .instance_init = ne2000_instance_init,
+};
+
+static void ne2000_register_types(void)
+{
+ type_register_static(&ne2000_info);
+}
+
+type_init(ne2000_register_types)
diff --git a/hw/net/ne2000.h b/hw/net/ne2000.h
new file mode 100644
index 00000000..e500306a
--- /dev/null
+++ b/hw/net/ne2000.h
@@ -0,0 +1,40 @@
+#ifndef HW_NE2000_H
+#define HW_NE2000_H 1
+
+#define NE2000_PMEM_SIZE (32*1024)
+#define NE2000_PMEM_START (16*1024)
+#define NE2000_PMEM_END (NE2000_PMEM_SIZE+NE2000_PMEM_START)
+#define NE2000_MEM_SIZE NE2000_PMEM_END
+
+typedef struct NE2000State {
+ MemoryRegion io;
+ uint8_t cmd;
+ uint32_t start;
+ uint32_t stop;
+ uint8_t boundary;
+ uint8_t tsr;
+ uint8_t tpsr;
+ uint16_t tcnt;
+ uint16_t rcnt;
+ uint32_t rsar;
+ uint8_t rsr;
+ uint8_t rxcr;
+ uint8_t isr;
+ uint8_t dcfg;
+ uint8_t imr;
+ uint8_t phys[6]; /* mac address */
+ uint8_t curpag;
+ uint8_t mult[8]; /* multicast mask array */
+ qemu_irq irq;
+ NICState *nic;
+ NICConf c;
+ uint8_t mem[NE2000_MEM_SIZE];
+} NE2000State;
+
+void ne2000_setup_io(NE2000State *s, DeviceState *dev, unsigned size);
+extern const VMStateDescription vmstate_ne2000;
+void ne2000_reset(NE2000State *s);
+int ne2000_can_receive(NetClientState *nc);
+ssize_t ne2000_receive(NetClientState *nc, const uint8_t *buf, size_t size_);
+
+#endif
diff --git a/hw/net/opencores_eth.c b/hw/net/opencores_eth.c
new file mode 100644
index 00000000..3642046e
--- /dev/null
+++ b/hw/net/opencores_eth.c
@@ -0,0 +1,764 @@
+/*
+ * OpenCores Ethernet MAC 10/100 + subset of
+ * National Semiconductors DP83848C 10/100 PHY
+ *
+ * http://opencores.org/svnget,ethmac?file=%2Ftrunk%2F%2Fdoc%2Feth_speci.pdf
+ * http://cache.national.com/ds/DP/DP83848C.pdf
+ *
+ * Copyright (c) 2011, Max Filippov, Open Source and Linux Lab.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Open Source and Linux Lab nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+
+/* RECSMALL is not used because it breaks tap networking in linux:
+ * incoming ARP responses are too short
+ */
+#undef USE_RECSMALL
+
+#define GET_FIELD(v, field) (((v) & (field)) >> (field ## _LBN))
+#define GET_REGBIT(s, reg, field) ((s)->regs[reg] & (reg ## _ ## field))
+#define GET_REGFIELD(s, reg, field) \
+ GET_FIELD((s)->regs[reg], reg ## _ ## field)
+
+#define SET_FIELD(v, field, data) \
+ ((v) = (((v) & ~(field)) | (((data) << (field ## _LBN)) & (field))))
+#define SET_REGFIELD(s, reg, field, data) \
+ SET_FIELD((s)->regs[reg], reg ## _ ## field, data)
+
+/* PHY MII registers */
+enum {
+ MII_BMCR,
+ MII_BMSR,
+ MII_PHYIDR1,
+ MII_PHYIDR2,
+ MII_ANAR,
+ MII_ANLPAR,
+ MII_REG_MAX = 16,
+};
+
+typedef struct Mii {
+ uint16_t regs[MII_REG_MAX];
+ bool link_ok;
+} Mii;
+
+static void mii_set_link(Mii *s, bool link_ok)
+{
+ if (link_ok) {
+ s->regs[MII_BMSR] |= 0x4;
+ s->regs[MII_ANLPAR] |= 0x01e1;
+ } else {
+ s->regs[MII_BMSR] &= ~0x4;
+ s->regs[MII_ANLPAR] &= 0x01ff;
+ }
+ s->link_ok = link_ok;
+}
+
+static void mii_reset(Mii *s)
+{
+ memset(s->regs, 0, sizeof(s->regs));
+ s->regs[MII_BMCR] = 0x1000;
+ s->regs[MII_BMSR] = 0x7848; /* no ext regs */
+ s->regs[MII_PHYIDR1] = 0x2000;
+ s->regs[MII_PHYIDR2] = 0x5c90;
+ s->regs[MII_ANAR] = 0x01e1;
+ mii_set_link(s, s->link_ok);
+}
+
+static void mii_ro(Mii *s, uint16_t v)
+{
+}
+
+static void mii_write_bmcr(Mii *s, uint16_t v)
+{
+ if (v & 0x8000) {
+ mii_reset(s);
+ } else {
+ s->regs[MII_BMCR] = v;
+ }
+}
+
+static void mii_write_host(Mii *s, unsigned idx, uint16_t v)
+{
+ static void (*reg_write[MII_REG_MAX])(Mii *s, uint16_t v) = {
+ [MII_BMCR] = mii_write_bmcr,
+ [MII_BMSR] = mii_ro,
+ [MII_PHYIDR1] = mii_ro,
+ [MII_PHYIDR2] = mii_ro,
+ };
+
+ if (idx < MII_REG_MAX) {
+ trace_open_eth_mii_write(idx, v);
+ if (reg_write[idx]) {
+ reg_write[idx](s, v);
+ } else {
+ s->regs[idx] = v;
+ }
+ }
+}
+
+static uint16_t mii_read_host(Mii *s, unsigned idx)
+{
+ trace_open_eth_mii_read(idx, s->regs[idx]);
+ return s->regs[idx];
+}
+
+/* OpenCores Ethernet registers */
+enum {
+ MODER,
+ INT_SOURCE,
+ INT_MASK,
+ IPGT,
+ IPGR1,
+ IPGR2,
+ PACKETLEN,
+ COLLCONF,
+ TX_BD_NUM,
+ CTRLMODER,
+ MIIMODER,
+ MIICOMMAND,
+ MIIADDRESS,
+ MIITX_DATA,
+ MIIRX_DATA,
+ MIISTATUS,
+ MAC_ADDR0,
+ MAC_ADDR1,
+ HASH0,
+ HASH1,
+ TXCTRL,
+ REG_MAX,
+};
+
+enum {
+ MODER_RECSMALL = 0x10000,
+ MODER_PAD = 0x8000,
+ MODER_HUGEN = 0x4000,
+ MODER_RST = 0x800,
+ MODER_LOOPBCK = 0x80,
+ MODER_PRO = 0x20,
+ MODER_IAM = 0x10,
+ MODER_BRO = 0x8,
+ MODER_TXEN = 0x2,
+ MODER_RXEN = 0x1,
+};
+
+enum {
+ INT_SOURCE_BUSY = 0x10,
+ INT_SOURCE_RXB = 0x4,
+ INT_SOURCE_TXB = 0x1,
+};
+
+enum {
+ PACKETLEN_MINFL = 0xffff0000,
+ PACKETLEN_MINFL_LBN = 16,
+ PACKETLEN_MAXFL = 0xffff,
+ PACKETLEN_MAXFL_LBN = 0,
+};
+
+enum {
+ MIICOMMAND_WCTRLDATA = 0x4,
+ MIICOMMAND_RSTAT = 0x2,
+ MIICOMMAND_SCANSTAT = 0x1,
+};
+
+enum {
+ MIIADDRESS_RGAD = 0x1f00,
+ MIIADDRESS_RGAD_LBN = 8,
+ MIIADDRESS_FIAD = 0x1f,
+ MIIADDRESS_FIAD_LBN = 0,
+};
+
+enum {
+ MIITX_DATA_CTRLDATA = 0xffff,
+ MIITX_DATA_CTRLDATA_LBN = 0,
+};
+
+enum {
+ MIIRX_DATA_PRSD = 0xffff,
+ MIIRX_DATA_PRSD_LBN = 0,
+};
+
+enum {
+ MIISTATUS_LINKFAIL = 0x1,
+ MIISTATUS_LINKFAIL_LBN = 0,
+};
+
+enum {
+ MAC_ADDR0_BYTE2 = 0xff000000,
+ MAC_ADDR0_BYTE2_LBN = 24,
+ MAC_ADDR0_BYTE3 = 0xff0000,
+ MAC_ADDR0_BYTE3_LBN = 16,
+ MAC_ADDR0_BYTE4 = 0xff00,
+ MAC_ADDR0_BYTE4_LBN = 8,
+ MAC_ADDR0_BYTE5 = 0xff,
+ MAC_ADDR0_BYTE5_LBN = 0,
+};
+
+enum {
+ MAC_ADDR1_BYTE0 = 0xff00,
+ MAC_ADDR1_BYTE0_LBN = 8,
+ MAC_ADDR1_BYTE1 = 0xff,
+ MAC_ADDR1_BYTE1_LBN = 0,
+};
+
+enum {
+ TXD_LEN = 0xffff0000,
+ TXD_LEN_LBN = 16,
+ TXD_RD = 0x8000,
+ TXD_IRQ = 0x4000,
+ TXD_WR = 0x2000,
+ TXD_PAD = 0x1000,
+ TXD_CRC = 0x800,
+ TXD_UR = 0x100,
+ TXD_RTRY = 0xf0,
+ TXD_RTRY_LBN = 4,
+ TXD_RL = 0x8,
+ TXD_LC = 0x4,
+ TXD_DF = 0x2,
+ TXD_CS = 0x1,
+};
+
+enum {
+ RXD_LEN = 0xffff0000,
+ RXD_LEN_LBN = 16,
+ RXD_E = 0x8000,
+ RXD_IRQ = 0x4000,
+ RXD_WRAP = 0x2000,
+ RXD_CF = 0x100,
+ RXD_M = 0x80,
+ RXD_OR = 0x40,
+ RXD_IS = 0x20,
+ RXD_DN = 0x10,
+ RXD_TL = 0x8,
+ RXD_SF = 0x4,
+ RXD_CRC = 0x2,
+ RXD_LC = 0x1,
+};
+
+typedef struct desc {
+ uint32_t len_flags;
+ uint32_t buf_ptr;
+} desc;
+
+#define DEFAULT_PHY 1
+
+#define TYPE_OPEN_ETH "open_eth"
+#define OPEN_ETH(obj) OBJECT_CHECK(OpenEthState, (obj), TYPE_OPEN_ETH)
+
+typedef struct OpenEthState {
+ SysBusDevice parent_obj;
+
+ NICState *nic;
+ NICConf conf;
+ MemoryRegion reg_io;
+ MemoryRegion desc_io;
+ qemu_irq irq;
+
+ Mii mii;
+ uint32_t regs[REG_MAX];
+ unsigned tx_desc;
+ unsigned rx_desc;
+ desc desc[128];
+} OpenEthState;
+
+static desc *rx_desc(OpenEthState *s)
+{
+ return s->desc + s->rx_desc;
+}
+
+static desc *tx_desc(OpenEthState *s)
+{
+ return s->desc + s->tx_desc;
+}
+
+static void open_eth_update_irq(OpenEthState *s,
+ uint32_t old, uint32_t new)
+{
+ if (!old != !new) {
+ trace_open_eth_update_irq(new);
+ qemu_set_irq(s->irq, new);
+ }
+}
+
+static void open_eth_int_source_write(OpenEthState *s,
+ uint32_t val)
+{
+ uint32_t old_val = s->regs[INT_SOURCE];
+
+ s->regs[INT_SOURCE] = val;
+ open_eth_update_irq(s, old_val & s->regs[INT_MASK],
+ s->regs[INT_SOURCE] & s->regs[INT_MASK]);
+}
+
+static void open_eth_set_link_status(NetClientState *nc)
+{
+ OpenEthState *s = qemu_get_nic_opaque(nc);
+
+ if (GET_REGBIT(s, MIICOMMAND, SCANSTAT)) {
+ SET_REGFIELD(s, MIISTATUS, LINKFAIL, nc->link_down);
+ }
+ mii_set_link(&s->mii, !nc->link_down);
+}
+
+static void open_eth_reset(void *opaque)
+{
+ OpenEthState *s = opaque;
+
+ memset(s->regs, 0, sizeof(s->regs));
+ s->regs[MODER] = 0xa000;
+ s->regs[IPGT] = 0x12;
+ s->regs[IPGR1] = 0xc;
+ s->regs[IPGR2] = 0x12;
+ s->regs[PACKETLEN] = 0x400600;
+ s->regs[COLLCONF] = 0xf003f;
+ s->regs[TX_BD_NUM] = 0x40;
+ s->regs[MIIMODER] = 0x64;
+
+ s->tx_desc = 0;
+ s->rx_desc = 0x40;
+
+ mii_reset(&s->mii);
+ open_eth_set_link_status(qemu_get_queue(s->nic));
+}
+
+static int open_eth_can_receive(NetClientState *nc)
+{
+ OpenEthState *s = qemu_get_nic_opaque(nc);
+
+ return GET_REGBIT(s, MODER, RXEN) &&
+ (s->regs[TX_BD_NUM] < 0x80);
+}
+
+static ssize_t open_eth_receive(NetClientState *nc,
+ const uint8_t *buf, size_t size)
+{
+ OpenEthState *s = qemu_get_nic_opaque(nc);
+ size_t maxfl = GET_REGFIELD(s, PACKETLEN, MAXFL);
+ size_t minfl = GET_REGFIELD(s, PACKETLEN, MINFL);
+ size_t fcsl = 4;
+ bool miss = true;
+
+ trace_open_eth_receive((unsigned)size);
+
+ if (size >= 6) {
+ static const uint8_t bcast_addr[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+ if (memcmp(buf, bcast_addr, sizeof(bcast_addr)) == 0) {
+ miss = GET_REGBIT(s, MODER, BRO);
+ } else if ((buf[0] & 0x1) || GET_REGBIT(s, MODER, IAM)) {
+ unsigned mcast_idx = compute_mcast_idx(buf);
+ miss = !(s->regs[HASH0 + mcast_idx / 32] &
+ (1 << (mcast_idx % 32)));
+ trace_open_eth_receive_mcast(
+ mcast_idx, s->regs[HASH0], s->regs[HASH1]);
+ } else {
+ miss = GET_REGFIELD(s, MAC_ADDR1, BYTE0) != buf[0] ||
+ GET_REGFIELD(s, MAC_ADDR1, BYTE1) != buf[1] ||
+ GET_REGFIELD(s, MAC_ADDR0, BYTE2) != buf[2] ||
+ GET_REGFIELD(s, MAC_ADDR0, BYTE3) != buf[3] ||
+ GET_REGFIELD(s, MAC_ADDR0, BYTE4) != buf[4] ||
+ GET_REGFIELD(s, MAC_ADDR0, BYTE5) != buf[5];
+ }
+ }
+
+ if (miss && !GET_REGBIT(s, MODER, PRO)) {
+ trace_open_eth_receive_reject();
+ return size;
+ }
+
+#ifdef USE_RECSMALL
+ if (GET_REGBIT(s, MODER, RECSMALL) || size >= minfl) {
+#else
+ {
+#endif
+ static const uint8_t zero[64] = {0};
+ desc *desc = rx_desc(s);
+ size_t copy_size = GET_REGBIT(s, MODER, HUGEN) ? 65536 : maxfl;
+
+ if (!(desc->len_flags & RXD_E)) {
+ open_eth_int_source_write(s,
+ s->regs[INT_SOURCE] | INT_SOURCE_BUSY);
+ return size;
+ }
+
+ desc->len_flags &= ~(RXD_CF | RXD_M | RXD_OR |
+ RXD_IS | RXD_DN | RXD_TL | RXD_SF | RXD_CRC | RXD_LC);
+
+ if (copy_size > size) {
+ copy_size = size;
+ } else {
+ fcsl = 0;
+ }
+ if (miss) {
+ desc->len_flags |= RXD_M;
+ }
+ if (GET_REGBIT(s, MODER, HUGEN) && size > maxfl) {
+ desc->len_flags |= RXD_TL;
+ }
+#ifdef USE_RECSMALL
+ if (size < minfl) {
+ desc->len_flags |= RXD_SF;
+ }
+#endif
+
+ cpu_physical_memory_write(desc->buf_ptr, buf, copy_size);
+
+ if (GET_REGBIT(s, MODER, PAD) && copy_size < minfl) {
+ if (minfl - copy_size > fcsl) {
+ fcsl = 0;
+ } else {
+ fcsl -= minfl - copy_size;
+ }
+ while (copy_size < minfl) {
+ size_t zero_sz = minfl - copy_size < sizeof(zero) ?
+ minfl - copy_size : sizeof(zero);
+
+ cpu_physical_memory_write(desc->buf_ptr + copy_size,
+ zero, zero_sz);
+ copy_size += zero_sz;
+ }
+ }
+
+ /* There's no FCS in the frames handed to us by the QEMU, zero fill it.
+ * Don't do it if the frame is cut at the MAXFL or padded with 4 or
+ * more bytes to the MINFL.
+ */
+ cpu_physical_memory_write(desc->buf_ptr + copy_size, zero, fcsl);
+ copy_size += fcsl;
+
+ SET_FIELD(desc->len_flags, RXD_LEN, copy_size);
+
+ if ((desc->len_flags & RXD_WRAP) || s->rx_desc == 0x7f) {
+ s->rx_desc = s->regs[TX_BD_NUM];
+ } else {
+ ++s->rx_desc;
+ }
+ desc->len_flags &= ~RXD_E;
+
+ trace_open_eth_receive_desc(desc->buf_ptr, desc->len_flags);
+
+ if (desc->len_flags & RXD_IRQ) {
+ open_eth_int_source_write(s,
+ s->regs[INT_SOURCE] | INT_SOURCE_RXB);
+ }
+ }
+ return size;
+}
+
+static NetClientInfo net_open_eth_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = open_eth_can_receive,
+ .receive = open_eth_receive,
+ .link_status_changed = open_eth_set_link_status,
+};
+
+static void open_eth_start_xmit(OpenEthState *s, desc *tx)
+{
+ uint8_t buf[65536];
+ unsigned len = GET_FIELD(tx->len_flags, TXD_LEN);
+ unsigned tx_len = len;
+
+ if ((tx->len_flags & TXD_PAD) &&
+ tx_len < GET_REGFIELD(s, PACKETLEN, MINFL)) {
+ tx_len = GET_REGFIELD(s, PACKETLEN, MINFL);
+ }
+ if (!GET_REGBIT(s, MODER, HUGEN) &&
+ tx_len > GET_REGFIELD(s, PACKETLEN, MAXFL)) {
+ tx_len = GET_REGFIELD(s, PACKETLEN, MAXFL);
+ }
+
+ trace_open_eth_start_xmit(tx->buf_ptr, len, tx_len);
+
+ if (len > tx_len) {
+ len = tx_len;
+ }
+ cpu_physical_memory_read(tx->buf_ptr, buf, len);
+ if (tx_len > len) {
+ memset(buf + len, 0, tx_len - len);
+ }
+ qemu_send_packet(qemu_get_queue(s->nic), buf, tx_len);
+
+ if (tx->len_flags & TXD_WR) {
+ s->tx_desc = 0;
+ } else {
+ ++s->tx_desc;
+ if (s->tx_desc >= s->regs[TX_BD_NUM]) {
+ s->tx_desc = 0;
+ }
+ }
+ tx->len_flags &= ~(TXD_RD | TXD_UR |
+ TXD_RTRY | TXD_RL | TXD_LC | TXD_DF | TXD_CS);
+ if (tx->len_flags & TXD_IRQ) {
+ open_eth_int_source_write(s, s->regs[INT_SOURCE] | INT_SOURCE_TXB);
+ }
+
+}
+
+static void open_eth_check_start_xmit(OpenEthState *s)
+{
+ desc *tx = tx_desc(s);
+ if (GET_REGBIT(s, MODER, TXEN) && s->regs[TX_BD_NUM] > 0 &&
+ (tx->len_flags & TXD_RD) &&
+ GET_FIELD(tx->len_flags, TXD_LEN) > 4) {
+ open_eth_start_xmit(s, tx);
+ }
+}
+
+static uint64_t open_eth_reg_read(void *opaque,
+ hwaddr addr, unsigned int size)
+{
+ static uint32_t (*reg_read[REG_MAX])(OpenEthState *s) = {
+ };
+ OpenEthState *s = opaque;
+ unsigned idx = addr / 4;
+ uint64_t v = 0;
+
+ if (idx < REG_MAX) {
+ if (reg_read[idx]) {
+ v = reg_read[idx](s);
+ } else {
+ v = s->regs[idx];
+ }
+ }
+ trace_open_eth_reg_read((uint32_t)addr, (uint32_t)v);
+ return v;
+}
+
+static void open_eth_notify_can_receive(OpenEthState *s)
+{
+ NetClientState *nc = qemu_get_queue(s->nic);
+
+ if (open_eth_can_receive(nc)) {
+ qemu_flush_queued_packets(nc);
+ }
+}
+
+static void open_eth_ro(OpenEthState *s, uint32_t val)
+{
+}
+
+static void open_eth_moder_host_write(OpenEthState *s, uint32_t val)
+{
+ uint32_t set = val & ~s->regs[MODER];
+
+ if (set & MODER_RST) {
+ open_eth_reset(s);
+ }
+
+ s->regs[MODER] = val;
+
+ if (set & MODER_RXEN) {
+ s->rx_desc = s->regs[TX_BD_NUM];
+ open_eth_notify_can_receive(s);
+ }
+ if (set & MODER_TXEN) {
+ s->tx_desc = 0;
+ open_eth_check_start_xmit(s);
+ }
+}
+
+static void open_eth_int_source_host_write(OpenEthState *s, uint32_t val)
+{
+ uint32_t old = s->regs[INT_SOURCE];
+
+ s->regs[INT_SOURCE] &= ~val;
+ open_eth_update_irq(s, old & s->regs[INT_MASK],
+ s->regs[INT_SOURCE] & s->regs[INT_MASK]);
+}
+
+static void open_eth_int_mask_host_write(OpenEthState *s, uint32_t val)
+{
+ uint32_t old = s->regs[INT_MASK];
+
+ s->regs[INT_MASK] = val;
+ open_eth_update_irq(s, s->regs[INT_SOURCE] & old,
+ s->regs[INT_SOURCE] & s->regs[INT_MASK]);
+}
+
+static void open_eth_tx_bd_num_host_write(OpenEthState *s, uint32_t val)
+{
+ if (val < 0x80) {
+ bool enable = s->regs[TX_BD_NUM] == 0x80;
+
+ s->regs[TX_BD_NUM] = val;
+ if (enable) {
+ open_eth_notify_can_receive(s);
+ }
+ }
+}
+
+static void open_eth_mii_command_host_write(OpenEthState *s, uint32_t val)
+{
+ unsigned fiad = GET_REGFIELD(s, MIIADDRESS, FIAD);
+ unsigned rgad = GET_REGFIELD(s, MIIADDRESS, RGAD);
+
+ if (val & MIICOMMAND_WCTRLDATA) {
+ if (fiad == DEFAULT_PHY) {
+ mii_write_host(&s->mii, rgad,
+ GET_REGFIELD(s, MIITX_DATA, CTRLDATA));
+ }
+ }
+ if (val & MIICOMMAND_RSTAT) {
+ if (fiad == DEFAULT_PHY) {
+ SET_REGFIELD(s, MIIRX_DATA, PRSD,
+ mii_read_host(&s->mii, rgad));
+ } else {
+ s->regs[MIIRX_DATA] = 0xffff;
+ }
+ SET_REGFIELD(s, MIISTATUS, LINKFAIL, qemu_get_queue(s->nic)->link_down);
+ }
+}
+
+static void open_eth_mii_tx_host_write(OpenEthState *s, uint32_t val)
+{
+ SET_REGFIELD(s, MIITX_DATA, CTRLDATA, val);
+ if (GET_REGFIELD(s, MIIADDRESS, FIAD) == DEFAULT_PHY) {
+ mii_write_host(&s->mii, GET_REGFIELD(s, MIIADDRESS, RGAD),
+ GET_REGFIELD(s, MIITX_DATA, CTRLDATA));
+ }
+}
+
+static void open_eth_reg_write(void *opaque,
+ hwaddr addr, uint64_t val, unsigned int size)
+{
+ static void (*reg_write[REG_MAX])(OpenEthState *s, uint32_t val) = {
+ [MODER] = open_eth_moder_host_write,
+ [INT_SOURCE] = open_eth_int_source_host_write,
+ [INT_MASK] = open_eth_int_mask_host_write,
+ [TX_BD_NUM] = open_eth_tx_bd_num_host_write,
+ [MIICOMMAND] = open_eth_mii_command_host_write,
+ [MIITX_DATA] = open_eth_mii_tx_host_write,
+ [MIISTATUS] = open_eth_ro,
+ };
+ OpenEthState *s = opaque;
+ unsigned idx = addr / 4;
+
+ if (idx < REG_MAX) {
+ trace_open_eth_reg_write((uint32_t)addr, (uint32_t)val);
+ if (reg_write[idx]) {
+ reg_write[idx](s, val);
+ } else {
+ s->regs[idx] = val;
+ }
+ }
+}
+
+static uint64_t open_eth_desc_read(void *opaque,
+ hwaddr addr, unsigned int size)
+{
+ OpenEthState *s = opaque;
+ uint64_t v = 0;
+
+ addr &= 0x3ff;
+ memcpy(&v, (uint8_t *)s->desc + addr, size);
+ trace_open_eth_desc_read((uint32_t)addr, (uint32_t)v);
+ return v;
+}
+
+static void open_eth_desc_write(void *opaque,
+ hwaddr addr, uint64_t val, unsigned int size)
+{
+ OpenEthState *s = opaque;
+
+ addr &= 0x3ff;
+ trace_open_eth_desc_write((uint32_t)addr, (uint32_t)val);
+ memcpy((uint8_t *)s->desc + addr, &val, size);
+ open_eth_check_start_xmit(s);
+}
+
+
+static const MemoryRegionOps open_eth_reg_ops = {
+ .read = open_eth_reg_read,
+ .write = open_eth_reg_write,
+};
+
+static const MemoryRegionOps open_eth_desc_ops = {
+ .read = open_eth_desc_read,
+ .write = open_eth_desc_write,
+};
+
+static int sysbus_open_eth_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ OpenEthState *s = OPEN_ETH(dev);
+
+ memory_region_init_io(&s->reg_io, OBJECT(dev), &open_eth_reg_ops, s,
+ "open_eth.regs", 0x54);
+ sysbus_init_mmio(sbd, &s->reg_io);
+
+ memory_region_init_io(&s->desc_io, OBJECT(dev), &open_eth_desc_ops, s,
+ "open_eth.desc", 0x400);
+ sysbus_init_mmio(sbd, &s->desc_io);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->nic = qemu_new_nic(&net_open_eth_info, &s->conf,
+ object_get_typename(OBJECT(s)), dev->id, s);
+ return 0;
+}
+
+static void qdev_open_eth_reset(DeviceState *dev)
+{
+ OpenEthState *d = OPEN_ETH(dev);
+
+ open_eth_reset(d);
+}
+
+static Property open_eth_properties[] = {
+ DEFINE_NIC_PROPERTIES(OpenEthState, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void open_eth_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = sysbus_open_eth_init;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->desc = "Opencores 10/100 Mbit Ethernet";
+ dc->reset = qdev_open_eth_reset;
+ dc->props = open_eth_properties;
+}
+
+static const TypeInfo open_eth_info = {
+ .name = TYPE_OPEN_ETH,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OpenEthState),
+ .class_init = open_eth_class_init,
+};
+
+static void open_eth_register_types(void)
+{
+ type_register_static(&open_eth_info);
+}
+
+type_init(open_eth_register_types)
diff --git a/hw/net/pcnet-pci.c b/hw/net/pcnet-pci.c
new file mode 100644
index 00000000..b4d60b81
--- /dev/null
+++ b/hw/net/pcnet-pci.c
@@ -0,0 +1,374 @@
+/*
+ * QEMU AMD PC-Net II (Am79C970A) PCI emulation
+ *
+ * Copyright (c) 2004 Antony T Curtis
+ *
+ * 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.
+ */
+
+/* This software was written to be compatible with the specification:
+ * AMD Am79C970A PCnet-PCI II Ethernet Controller Data-Sheet
+ * AMD Publication# 19436 Rev:E Amendment/0 Issue Date: June 2000
+ */
+
+#include "hw/pci/pci.h"
+#include "net/net.h"
+#include "hw/loader.h"
+#include "qemu/timer.h"
+#include "sysemu/dma.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+
+#include "pcnet.h"
+
+//#define PCNET_DEBUG
+//#define PCNET_DEBUG_IO
+//#define PCNET_DEBUG_BCR
+//#define PCNET_DEBUG_CSR
+//#define PCNET_DEBUG_RMD
+//#define PCNET_DEBUG_TMD
+//#define PCNET_DEBUG_MATCH
+
+#define TYPE_PCI_PCNET "pcnet"
+
+#define PCI_PCNET(obj) \
+ OBJECT_CHECK(PCIPCNetState, (obj), TYPE_PCI_PCNET)
+
+typedef struct {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ PCNetState state;
+ MemoryRegion io_bar;
+} PCIPCNetState;
+
+static void pcnet_aprom_writeb(void *opaque, uint32_t addr, uint32_t val)
+{
+ PCNetState *s = opaque;
+
+ trace_pcnet_aprom_writeb(opaque, addr, val);
+ if (BCR_APROMWE(s)) {
+ s->prom[addr & 15] = val;
+ }
+}
+
+static uint32_t pcnet_aprom_readb(void *opaque, uint32_t addr)
+{
+ PCNetState *s = opaque;
+ uint32_t val = s->prom[addr & 15];
+
+ trace_pcnet_aprom_readb(opaque, addr, val);
+ return val;
+}
+
+static uint64_t pcnet_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCNetState *d = opaque;
+
+ trace_pcnet_ioport_read(opaque, addr, size);
+ if (addr < 0x10) {
+ if (!BCR_DWIO(d) && size == 1) {
+ return pcnet_aprom_readb(d, addr);
+ } else if (!BCR_DWIO(d) && (addr & 1) == 0 && size == 2) {
+ return pcnet_aprom_readb(d, addr) |
+ (pcnet_aprom_readb(d, addr + 1) << 8);
+ } else if (BCR_DWIO(d) && (addr & 3) == 0 && size == 4) {
+ return pcnet_aprom_readb(d, addr) |
+ (pcnet_aprom_readb(d, addr + 1) << 8) |
+ (pcnet_aprom_readb(d, addr + 2) << 16) |
+ (pcnet_aprom_readb(d, addr + 3) << 24);
+ }
+ } else {
+ if (size == 2) {
+ return pcnet_ioport_readw(d, addr);
+ } else if (size == 4) {
+ return pcnet_ioport_readl(d, addr);
+ }
+ }
+ return ((uint64_t)1 << (size * 8)) - 1;
+}
+
+static void pcnet_ioport_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ PCNetState *d = opaque;
+
+ trace_pcnet_ioport_write(opaque, addr, data, size);
+ if (addr < 0x10) {
+ if (!BCR_DWIO(d) && size == 1) {
+ pcnet_aprom_writeb(d, addr, data);
+ } else if (!BCR_DWIO(d) && (addr & 1) == 0 && size == 2) {
+ pcnet_aprom_writeb(d, addr, data & 0xff);
+ pcnet_aprom_writeb(d, addr + 1, data >> 8);
+ } else if (BCR_DWIO(d) && (addr & 3) == 0 && size == 4) {
+ pcnet_aprom_writeb(d, addr, data & 0xff);
+ pcnet_aprom_writeb(d, addr + 1, (data >> 8) & 0xff);
+ pcnet_aprom_writeb(d, addr + 2, (data >> 16) & 0xff);
+ pcnet_aprom_writeb(d, addr + 3, data >> 24);
+ }
+ } else {
+ if (size == 2) {
+ pcnet_ioport_writew(d, addr, data);
+ } else if (size == 4) {
+ pcnet_ioport_writel(d, addr, data);
+ }
+ }
+}
+
+static const MemoryRegionOps pcnet_io_ops = {
+ .read = pcnet_ioport_read,
+ .write = pcnet_ioport_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void pcnet_mmio_writeb(void *opaque, hwaddr addr, uint32_t val)
+{
+ PCNetState *d = opaque;
+
+ trace_pcnet_mmio_writeb(opaque, addr, val);
+ if (!(addr & 0x10))
+ pcnet_aprom_writeb(d, addr & 0x0f, val);
+}
+
+static uint32_t pcnet_mmio_readb(void *opaque, hwaddr addr)
+{
+ PCNetState *d = opaque;
+ uint32_t val = -1;
+
+ if (!(addr & 0x10))
+ val = pcnet_aprom_readb(d, addr & 0x0f);
+ trace_pcnet_mmio_readb(opaque, addr, val);
+ return val;
+}
+
+static void pcnet_mmio_writew(void *opaque, hwaddr addr, uint32_t val)
+{
+ PCNetState *d = opaque;
+
+ trace_pcnet_mmio_writew(opaque, addr, val);
+ if (addr & 0x10)
+ pcnet_ioport_writew(d, addr & 0x0f, val);
+ else {
+ addr &= 0x0f;
+ pcnet_aprom_writeb(d, addr, val & 0xff);
+ pcnet_aprom_writeb(d, addr+1, (val & 0xff00) >> 8);
+ }
+}
+
+static uint32_t pcnet_mmio_readw(void *opaque, hwaddr addr)
+{
+ PCNetState *d = opaque;
+ uint32_t val = -1;
+
+ if (addr & 0x10)
+ val = pcnet_ioport_readw(d, addr & 0x0f);
+ else {
+ addr &= 0x0f;
+ val = pcnet_aprom_readb(d, addr+1);
+ val <<= 8;
+ val |= pcnet_aprom_readb(d, addr);
+ }
+ trace_pcnet_mmio_readw(opaque, addr, val);
+ return val;
+}
+
+static void pcnet_mmio_writel(void *opaque, hwaddr addr, uint32_t val)
+{
+ PCNetState *d = opaque;
+
+ trace_pcnet_mmio_writel(opaque, addr, val);
+ if (addr & 0x10)
+ pcnet_ioport_writel(d, addr & 0x0f, val);
+ else {
+ addr &= 0x0f;
+ pcnet_aprom_writeb(d, addr, val & 0xff);
+ pcnet_aprom_writeb(d, addr+1, (val & 0xff00) >> 8);
+ pcnet_aprom_writeb(d, addr+2, (val & 0xff0000) >> 16);
+ pcnet_aprom_writeb(d, addr+3, (val & 0xff000000) >> 24);
+ }
+}
+
+static uint32_t pcnet_mmio_readl(void *opaque, hwaddr addr)
+{
+ PCNetState *d = opaque;
+ uint32_t val;
+
+ if (addr & 0x10)
+ val = pcnet_ioport_readl(d, addr & 0x0f);
+ else {
+ addr &= 0x0f;
+ val = pcnet_aprom_readb(d, addr+3);
+ val <<= 8;
+ val |= pcnet_aprom_readb(d, addr+2);
+ val <<= 8;
+ val |= pcnet_aprom_readb(d, addr+1);
+ val <<= 8;
+ val |= pcnet_aprom_readb(d, addr);
+ }
+ trace_pcnet_mmio_readl(opaque, addr, val);
+ return val;
+}
+
+static const VMStateDescription vmstate_pci_pcnet = {
+ .name = "pcnet",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PCIPCNetState),
+ VMSTATE_STRUCT(state, PCIPCNetState, 0, vmstate_pcnet, PCNetState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* PCI interface */
+
+static const MemoryRegionOps pcnet_mmio_ops = {
+ .old_mmio = {
+ .read = { pcnet_mmio_readb, pcnet_mmio_readw, pcnet_mmio_readl },
+ .write = { pcnet_mmio_writeb, pcnet_mmio_writew, pcnet_mmio_writel },
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void pci_physical_memory_write(void *dma_opaque, hwaddr addr,
+ uint8_t *buf, int len, int do_bswap)
+{
+ pci_dma_write(dma_opaque, addr, buf, len);
+}
+
+static void pci_physical_memory_read(void *dma_opaque, hwaddr addr,
+ uint8_t *buf, int len, int do_bswap)
+{
+ pci_dma_read(dma_opaque, addr, buf, len);
+}
+
+static void pci_pcnet_uninit(PCIDevice *dev)
+{
+ PCIPCNetState *d = PCI_PCNET(dev);
+
+ qemu_free_irq(d->state.irq);
+ timer_del(d->state.poll_timer);
+ timer_free(d->state.poll_timer);
+ qemu_del_nic(d->state.nic);
+}
+
+static NetClientInfo net_pci_pcnet_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = pcnet_receive,
+ .link_status_changed = pcnet_set_link_status,
+};
+
+static void pci_pcnet_realize(PCIDevice *pci_dev, Error **errp)
+{
+ PCIPCNetState *d = PCI_PCNET(pci_dev);
+ PCNetState *s = &d->state;
+ uint8_t *pci_conf;
+
+#if 0
+ printf("sizeof(RMD)=%d, sizeof(TMD)=%d\n",
+ sizeof(struct pcnet_RMD), sizeof(struct pcnet_TMD));
+#endif
+
+ pci_conf = pci_dev->config;
+
+ pci_set_word(pci_conf + PCI_STATUS,
+ PCI_STATUS_FAST_BACK | PCI_STATUS_DEVSEL_MEDIUM);
+
+ pci_set_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID, 0x0);
+ pci_set_word(pci_conf + PCI_SUBSYSTEM_ID, 0x0);
+
+ pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
+ pci_conf[PCI_MIN_GNT] = 0x06;
+ pci_conf[PCI_MAX_LAT] = 0xff;
+
+ /* Handler for memory-mapped I/O */
+ memory_region_init_io(&d->state.mmio, OBJECT(d), &pcnet_mmio_ops, s,
+ "pcnet-mmio", PCNET_PNPMMIO_SIZE);
+
+ memory_region_init_io(&d->io_bar, OBJECT(d), &pcnet_io_ops, s, "pcnet-io",
+ PCNET_IOPORT_SIZE);
+ pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->io_bar);
+
+ pci_register_bar(pci_dev, 1, 0, &s->mmio);
+
+ s->irq = pci_allocate_irq(pci_dev);
+ s->phys_mem_read = pci_physical_memory_read;
+ s->phys_mem_write = pci_physical_memory_write;
+ s->dma_opaque = pci_dev;
+
+ pcnet_common_init(DEVICE(pci_dev), s, &net_pci_pcnet_info);
+}
+
+static void pci_reset(DeviceState *dev)
+{
+ PCIPCNetState *d = PCI_PCNET(dev);
+
+ pcnet_h_reset(&d->state);
+}
+
+static void pcnet_instance_init(Object *obj)
+{
+ PCIPCNetState *d = PCI_PCNET(obj);
+ PCNetState *s = &d->state;
+
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ DEVICE(obj), NULL);
+}
+
+static Property pcnet_properties[] = {
+ DEFINE_NIC_PROPERTIES(PCIPCNetState, state.conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pcnet_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_pcnet_realize;
+ k->exit = pci_pcnet_uninit;
+ k->romfile = "efi-pcnet.rom",
+ k->vendor_id = PCI_VENDOR_ID_AMD;
+ k->device_id = PCI_DEVICE_ID_AMD_LANCE;
+ k->revision = 0x10;
+ k->class_id = PCI_CLASS_NETWORK_ETHERNET;
+ dc->reset = pci_reset;
+ dc->vmsd = &vmstate_pci_pcnet;
+ dc->props = pcnet_properties;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo pcnet_info = {
+ .name = TYPE_PCI_PCNET,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIPCNetState),
+ .class_init = pcnet_class_init,
+ .instance_init = pcnet_instance_init,
+};
+
+static void pci_pcnet_register_types(void)
+{
+ type_register_static(&pcnet_info);
+}
+
+type_init(pci_pcnet_register_types)
diff --git a/hw/net/pcnet.c b/hw/net/pcnet.c
new file mode 100644
index 00000000..27b7000d
--- /dev/null
+++ b/hw/net/pcnet.c
@@ -0,0 +1,1761 @@
+/*
+ * QEMU AMD PC-Net II (Am79C970A) emulation
+ *
+ * Copyright (c) 2004 Antony T Curtis
+ *
+ * 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.
+ */
+
+/* This software was written to be compatible with the specification:
+ * AMD Am79C970A PCnet-PCI II Ethernet Controller Data-Sheet
+ * AMD Publication# 19436 Rev:E Amendment/0 Issue Date: June 2000
+ */
+
+/*
+ * On Sparc32, this is the Lance (Am7990) part of chip STP2000 (Master I/O), also
+ * produced as NCR89C100. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
+ * and
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR92C990.txt
+ */
+
+#include "hw/qdev.h"
+#include "net/net.h"
+#include "qemu/timer.h"
+#include "qemu/sockets.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+
+#include "pcnet.h"
+
+//#define PCNET_DEBUG
+//#define PCNET_DEBUG_IO
+//#define PCNET_DEBUG_BCR
+//#define PCNET_DEBUG_CSR
+//#define PCNET_DEBUG_RMD
+//#define PCNET_DEBUG_TMD
+//#define PCNET_DEBUG_MATCH
+
+
+struct qemu_ether_header {
+ uint8_t ether_dhost[6];
+ uint8_t ether_shost[6];
+ uint16_t ether_type;
+};
+
+#define CSR_INIT(S) !!(((S)->csr[0])&0x0001)
+#define CSR_STRT(S) !!(((S)->csr[0])&0x0002)
+#define CSR_STOP(S) !!(((S)->csr[0])&0x0004)
+#define CSR_TDMD(S) !!(((S)->csr[0])&0x0008)
+#define CSR_TXON(S) !!(((S)->csr[0])&0x0010)
+#define CSR_RXON(S) !!(((S)->csr[0])&0x0020)
+#define CSR_INEA(S) !!(((S)->csr[0])&0x0040)
+#define CSR_BSWP(S) !!(((S)->csr[3])&0x0004)
+#define CSR_LAPPEN(S) !!(((S)->csr[3])&0x0020)
+#define CSR_DXSUFLO(S) !!(((S)->csr[3])&0x0040)
+#define CSR_ASTRP_RCV(S) !!(((S)->csr[4])&0x0800)
+#define CSR_DPOLL(S) !!(((S)->csr[4])&0x1000)
+#define CSR_SPND(S) !!(((S)->csr[5])&0x0001)
+#define CSR_LTINTEN(S) !!(((S)->csr[5])&0x4000)
+#define CSR_TOKINTD(S) !!(((S)->csr[5])&0x8000)
+#define CSR_DRX(S) !!(((S)->csr[15])&0x0001)
+#define CSR_DTX(S) !!(((S)->csr[15])&0x0002)
+#define CSR_LOOP(S) !!(((S)->csr[15])&0x0004)
+#define CSR_DXMTFCS(S) !!(((S)->csr[15])&0x0008)
+#define CSR_INTL(S) !!(((S)->csr[15])&0x0040)
+#define CSR_DRCVPA(S) !!(((S)->csr[15])&0x2000)
+#define CSR_DRCVBC(S) !!(((S)->csr[15])&0x4000)
+#define CSR_PROM(S) !!(((S)->csr[15])&0x8000)
+
+#define CSR_CRBC(S) ((S)->csr[40])
+#define CSR_CRST(S) ((S)->csr[41])
+#define CSR_CXBC(S) ((S)->csr[42])
+#define CSR_CXST(S) ((S)->csr[43])
+#define CSR_NRBC(S) ((S)->csr[44])
+#define CSR_NRST(S) ((S)->csr[45])
+#define CSR_POLL(S) ((S)->csr[46])
+#define CSR_PINT(S) ((S)->csr[47])
+#define CSR_RCVRC(S) ((S)->csr[72])
+#define CSR_XMTRC(S) ((S)->csr[74])
+#define CSR_RCVRL(S) ((S)->csr[76])
+#define CSR_XMTRL(S) ((S)->csr[78])
+#define CSR_MISSC(S) ((S)->csr[112])
+
+#define CSR_IADR(S) ((S)->csr[ 1] | ((uint32_t)(S)->csr[ 2] << 16))
+#define CSR_CRBA(S) ((S)->csr[18] | ((uint32_t)(S)->csr[19] << 16))
+#define CSR_CXBA(S) ((S)->csr[20] | ((uint32_t)(S)->csr[21] << 16))
+#define CSR_NRBA(S) ((S)->csr[22] | ((uint32_t)(S)->csr[23] << 16))
+#define CSR_BADR(S) ((S)->csr[24] | ((uint32_t)(S)->csr[25] << 16))
+#define CSR_NRDA(S) ((S)->csr[26] | ((uint32_t)(S)->csr[27] << 16))
+#define CSR_CRDA(S) ((S)->csr[28] | ((uint32_t)(S)->csr[29] << 16))
+#define CSR_BADX(S) ((S)->csr[30] | ((uint32_t)(S)->csr[31] << 16))
+#define CSR_NXDA(S) ((S)->csr[32] | ((uint32_t)(S)->csr[33] << 16))
+#define CSR_CXDA(S) ((S)->csr[34] | ((uint32_t)(S)->csr[35] << 16))
+#define CSR_NNRD(S) ((S)->csr[36] | ((uint32_t)(S)->csr[37] << 16))
+#define CSR_NNXD(S) ((S)->csr[38] | ((uint32_t)(S)->csr[39] << 16))
+#define CSR_PXDA(S) ((S)->csr[60] | ((uint32_t)(S)->csr[61] << 16))
+#define CSR_NXBA(S) ((S)->csr[64] | ((uint32_t)(S)->csr[65] << 16))
+
+#define PHYSADDR(S,A) \
+ (BCR_SSIZE32(S) ? (A) : (A) | ((0xff00 & (uint32_t)(S)->csr[2])<<16))
+
+struct pcnet_initblk16 {
+ uint16_t mode;
+ uint16_t padr[3];
+ uint16_t ladrf[4];
+ uint32_t rdra;
+ uint32_t tdra;
+};
+
+struct pcnet_initblk32 {
+ uint16_t mode;
+ uint8_t rlen;
+ uint8_t tlen;
+ uint16_t padr[3];
+ uint16_t _res;
+ uint16_t ladrf[4];
+ uint32_t rdra;
+ uint32_t tdra;
+};
+
+struct pcnet_TMD {
+ uint32_t tbadr;
+ int16_t length;
+ int16_t status;
+ uint32_t misc;
+ uint32_t res;
+};
+
+#define TMDL_BCNT_MASK 0x0fff
+#define TMDL_BCNT_SH 0
+#define TMDL_ONES_MASK 0xf000
+#define TMDL_ONES_SH 12
+
+#define TMDS_BPE_MASK 0x0080
+#define TMDS_BPE_SH 7
+#define TMDS_ENP_MASK 0x0100
+#define TMDS_ENP_SH 8
+#define TMDS_STP_MASK 0x0200
+#define TMDS_STP_SH 9
+#define TMDS_DEF_MASK 0x0400
+#define TMDS_DEF_SH 10
+#define TMDS_ONE_MASK 0x0800
+#define TMDS_ONE_SH 11
+#define TMDS_LTINT_MASK 0x1000
+#define TMDS_LTINT_SH 12
+#define TMDS_NOFCS_MASK 0x2000
+#define TMDS_NOFCS_SH 13
+#define TMDS_ADDFCS_MASK TMDS_NOFCS_MASK
+#define TMDS_ADDFCS_SH TMDS_NOFCS_SH
+#define TMDS_ERR_MASK 0x4000
+#define TMDS_ERR_SH 14
+#define TMDS_OWN_MASK 0x8000
+#define TMDS_OWN_SH 15
+
+#define TMDM_TRC_MASK 0x0000000f
+#define TMDM_TRC_SH 0
+#define TMDM_TDR_MASK 0x03ff0000
+#define TMDM_TDR_SH 16
+#define TMDM_RTRY_MASK 0x04000000
+#define TMDM_RTRY_SH 26
+#define TMDM_LCAR_MASK 0x08000000
+#define TMDM_LCAR_SH 27
+#define TMDM_LCOL_MASK 0x10000000
+#define TMDM_LCOL_SH 28
+#define TMDM_EXDEF_MASK 0x20000000
+#define TMDM_EXDEF_SH 29
+#define TMDM_UFLO_MASK 0x40000000
+#define TMDM_UFLO_SH 30
+#define TMDM_BUFF_MASK 0x80000000
+#define TMDM_BUFF_SH 31
+
+struct pcnet_RMD {
+ uint32_t rbadr;
+ int16_t buf_length;
+ int16_t status;
+ uint32_t msg_length;
+ uint32_t res;
+};
+
+#define RMDL_BCNT_MASK 0x0fff
+#define RMDL_BCNT_SH 0
+#define RMDL_ONES_MASK 0xf000
+#define RMDL_ONES_SH 12
+
+#define RMDS_BAM_MASK 0x0010
+#define RMDS_BAM_SH 4
+#define RMDS_LFAM_MASK 0x0020
+#define RMDS_LFAM_SH 5
+#define RMDS_PAM_MASK 0x0040
+#define RMDS_PAM_SH 6
+#define RMDS_BPE_MASK 0x0080
+#define RMDS_BPE_SH 7
+#define RMDS_ENP_MASK 0x0100
+#define RMDS_ENP_SH 8
+#define RMDS_STP_MASK 0x0200
+#define RMDS_STP_SH 9
+#define RMDS_BUFF_MASK 0x0400
+#define RMDS_BUFF_SH 10
+#define RMDS_CRC_MASK 0x0800
+#define RMDS_CRC_SH 11
+#define RMDS_OFLO_MASK 0x1000
+#define RMDS_OFLO_SH 12
+#define RMDS_FRAM_MASK 0x2000
+#define RMDS_FRAM_SH 13
+#define RMDS_ERR_MASK 0x4000
+#define RMDS_ERR_SH 14
+#define RMDS_OWN_MASK 0x8000
+#define RMDS_OWN_SH 15
+
+#define RMDM_MCNT_MASK 0x00000fff
+#define RMDM_MCNT_SH 0
+#define RMDM_ZEROS_MASK 0x0000f000
+#define RMDM_ZEROS_SH 12
+#define RMDM_RPC_MASK 0x00ff0000
+#define RMDM_RPC_SH 16
+#define RMDM_RCC_MASK 0xff000000
+#define RMDM_RCC_SH 24
+
+#define SET_FIELD(regp, name, field, value) \
+ (*(regp) = (*(regp) & ~(name ## _ ## field ## _MASK)) \
+ | ((value) << name ## _ ## field ## _SH))
+
+#define GET_FIELD(reg, name, field) \
+ (((reg) & name ## _ ## field ## _MASK) >> name ## _ ## field ## _SH)
+
+#define PRINT_TMD(T) printf( \
+ "TMD0 : TBADR=0x%08x\n" \
+ "TMD1 : OWN=%d, ERR=%d, FCS=%d, LTI=%d, " \
+ "ONE=%d, DEF=%d, STP=%d, ENP=%d,\n" \
+ " BPE=%d, BCNT=%d\n" \
+ "TMD2 : BUF=%d, UFL=%d, EXD=%d, LCO=%d, " \
+ "LCA=%d, RTR=%d,\n" \
+ " TDR=%d, TRC=%d\n", \
+ (T)->tbadr, \
+ GET_FIELD((T)->status, TMDS, OWN), \
+ GET_FIELD((T)->status, TMDS, ERR), \
+ GET_FIELD((T)->status, TMDS, NOFCS), \
+ GET_FIELD((T)->status, TMDS, LTINT), \
+ GET_FIELD((T)->status, TMDS, ONE), \
+ GET_FIELD((T)->status, TMDS, DEF), \
+ GET_FIELD((T)->status, TMDS, STP), \
+ GET_FIELD((T)->status, TMDS, ENP), \
+ GET_FIELD((T)->status, TMDS, BPE), \
+ 4096-GET_FIELD((T)->length, TMDL, BCNT), \
+ GET_FIELD((T)->misc, TMDM, BUFF), \
+ GET_FIELD((T)->misc, TMDM, UFLO), \
+ GET_FIELD((T)->misc, TMDM, EXDEF), \
+ GET_FIELD((T)->misc, TMDM, LCOL), \
+ GET_FIELD((T)->misc, TMDM, LCAR), \
+ GET_FIELD((T)->misc, TMDM, RTRY), \
+ GET_FIELD((T)->misc, TMDM, TDR), \
+ GET_FIELD((T)->misc, TMDM, TRC))
+
+#define PRINT_RMD(R) printf( \
+ "RMD0 : RBADR=0x%08x\n" \
+ "RMD1 : OWN=%d, ERR=%d, FRAM=%d, OFLO=%d, " \
+ "CRC=%d, BUFF=%d, STP=%d, ENP=%d,\n " \
+ "BPE=%d, PAM=%d, LAFM=%d, BAM=%d, ONES=%d, BCNT=%d\n" \
+ "RMD2 : RCC=%d, RPC=%d, MCNT=%d, ZEROS=%d\n", \
+ (R)->rbadr, \
+ GET_FIELD((R)->status, RMDS, OWN), \
+ GET_FIELD((R)->status, RMDS, ERR), \
+ GET_FIELD((R)->status, RMDS, FRAM), \
+ GET_FIELD((R)->status, RMDS, OFLO), \
+ GET_FIELD((R)->status, RMDS, CRC), \
+ GET_FIELD((R)->status, RMDS, BUFF), \
+ GET_FIELD((R)->status, RMDS, STP), \
+ GET_FIELD((R)->status, RMDS, ENP), \
+ GET_FIELD((R)->status, RMDS, BPE), \
+ GET_FIELD((R)->status, RMDS, PAM), \
+ GET_FIELD((R)->status, RMDS, LFAM), \
+ GET_FIELD((R)->status, RMDS, BAM), \
+ GET_FIELD((R)->buf_length, RMDL, ONES), \
+ 4096-GET_FIELD((R)->buf_length, RMDL, BCNT), \
+ GET_FIELD((R)->msg_length, RMDM, RCC), \
+ GET_FIELD((R)->msg_length, RMDM, RPC), \
+ GET_FIELD((R)->msg_length, RMDM, MCNT), \
+ GET_FIELD((R)->msg_length, RMDM, ZEROS))
+
+static inline void pcnet_tmd_load(PCNetState *s, struct pcnet_TMD *tmd,
+ hwaddr addr)
+{
+ if (!BCR_SSIZE32(s)) {
+ struct {
+ uint32_t tbadr;
+ int16_t length;
+ int16_t status;
+ } xda;
+ s->phys_mem_read(s->dma_opaque, addr, (void *)&xda, sizeof(xda), 0);
+ tmd->tbadr = le32_to_cpu(xda.tbadr) & 0xffffff;
+ tmd->length = le16_to_cpu(xda.length);
+ tmd->status = (le32_to_cpu(xda.tbadr) >> 16) & 0xff00;
+ tmd->misc = le16_to_cpu(xda.status) << 16;
+ tmd->res = 0;
+ } else {
+ s->phys_mem_read(s->dma_opaque, addr, (void *)tmd, sizeof(*tmd), 0);
+ le32_to_cpus(&tmd->tbadr);
+ le16_to_cpus((uint16_t *)&tmd->length);
+ le16_to_cpus((uint16_t *)&tmd->status);
+ le32_to_cpus(&tmd->misc);
+ le32_to_cpus(&tmd->res);
+ if (BCR_SWSTYLE(s) == 3) {
+ uint32_t tmp = tmd->tbadr;
+ tmd->tbadr = tmd->misc;
+ tmd->misc = tmp;
+ }
+ }
+}
+
+static inline void pcnet_tmd_store(PCNetState *s, const struct pcnet_TMD *tmd,
+ hwaddr addr)
+{
+ if (!BCR_SSIZE32(s)) {
+ struct {
+ uint32_t tbadr;
+ int16_t length;
+ int16_t status;
+ } xda;
+ xda.tbadr = cpu_to_le32((tmd->tbadr & 0xffffff) |
+ ((tmd->status & 0xff00) << 16));
+ xda.length = cpu_to_le16(tmd->length);
+ xda.status = cpu_to_le16(tmd->misc >> 16);
+ s->phys_mem_write(s->dma_opaque, addr, (void *)&xda, sizeof(xda), 0);
+ } else {
+ struct {
+ uint32_t tbadr;
+ int16_t length;
+ int16_t status;
+ uint32_t misc;
+ uint32_t res;
+ } xda;
+ xda.tbadr = cpu_to_le32(tmd->tbadr);
+ xda.length = cpu_to_le16(tmd->length);
+ xda.status = cpu_to_le16(tmd->status);
+ xda.misc = cpu_to_le32(tmd->misc);
+ xda.res = cpu_to_le32(tmd->res);
+ if (BCR_SWSTYLE(s) == 3) {
+ uint32_t tmp = xda.tbadr;
+ xda.tbadr = xda.misc;
+ xda.misc = tmp;
+ }
+ s->phys_mem_write(s->dma_opaque, addr, (void *)&xda, sizeof(xda), 0);
+ }
+}
+
+static inline void pcnet_rmd_load(PCNetState *s, struct pcnet_RMD *rmd,
+ hwaddr addr)
+{
+ if (!BCR_SSIZE32(s)) {
+ struct {
+ uint32_t rbadr;
+ int16_t buf_length;
+ int16_t msg_length;
+ } rda;
+ s->phys_mem_read(s->dma_opaque, addr, (void *)&rda, sizeof(rda), 0);
+ rmd->rbadr = le32_to_cpu(rda.rbadr) & 0xffffff;
+ rmd->buf_length = le16_to_cpu(rda.buf_length);
+ rmd->status = (le32_to_cpu(rda.rbadr) >> 16) & 0xff00;
+ rmd->msg_length = le16_to_cpu(rda.msg_length);
+ rmd->res = 0;
+ } else {
+ s->phys_mem_read(s->dma_opaque, addr, (void *)rmd, sizeof(*rmd), 0);
+ le32_to_cpus(&rmd->rbadr);
+ le16_to_cpus((uint16_t *)&rmd->buf_length);
+ le16_to_cpus((uint16_t *)&rmd->status);
+ le32_to_cpus(&rmd->msg_length);
+ le32_to_cpus(&rmd->res);
+ if (BCR_SWSTYLE(s) == 3) {
+ uint32_t tmp = rmd->rbadr;
+ rmd->rbadr = rmd->msg_length;
+ rmd->msg_length = tmp;
+ }
+ }
+}
+
+static inline void pcnet_rmd_store(PCNetState *s, struct pcnet_RMD *rmd,
+ hwaddr addr)
+{
+ if (!BCR_SSIZE32(s)) {
+ struct {
+ uint32_t rbadr;
+ int16_t buf_length;
+ int16_t msg_length;
+ } rda;
+ rda.rbadr = cpu_to_le32((rmd->rbadr & 0xffffff) |
+ ((rmd->status & 0xff00) << 16));
+ rda.buf_length = cpu_to_le16(rmd->buf_length);
+ rda.msg_length = cpu_to_le16(rmd->msg_length);
+ s->phys_mem_write(s->dma_opaque, addr, (void *)&rda, sizeof(rda), 0);
+ } else {
+ struct {
+ uint32_t rbadr;
+ int16_t buf_length;
+ int16_t status;
+ uint32_t msg_length;
+ uint32_t res;
+ } rda;
+ rda.rbadr = cpu_to_le32(rmd->rbadr);
+ rda.buf_length = cpu_to_le16(rmd->buf_length);
+ rda.status = cpu_to_le16(rmd->status);
+ rda.msg_length = cpu_to_le32(rmd->msg_length);
+ rda.res = cpu_to_le32(rmd->res);
+ if (BCR_SWSTYLE(s) == 3) {
+ uint32_t tmp = rda.rbadr;
+ rda.rbadr = rda.msg_length;
+ rda.msg_length = tmp;
+ }
+ s->phys_mem_write(s->dma_opaque, addr, (void *)&rda, sizeof(rda), 0);
+ }
+}
+
+
+#define TMDLOAD(TMD,ADDR) pcnet_tmd_load(s,TMD,ADDR)
+
+#define TMDSTORE(TMD,ADDR) pcnet_tmd_store(s,TMD,ADDR)
+
+#define RMDLOAD(RMD,ADDR) pcnet_rmd_load(s,RMD,ADDR)
+
+#define RMDSTORE(RMD,ADDR) pcnet_rmd_store(s,RMD,ADDR)
+
+#if 1
+
+#define CHECK_RMD(ADDR,RES) do { \
+ struct pcnet_RMD rmd; \
+ RMDLOAD(&rmd,(ADDR)); \
+ (RES) |= (GET_FIELD(rmd.buf_length, RMDL, ONES) != 15) \
+ || (GET_FIELD(rmd.msg_length, RMDM, ZEROS) != 0); \
+} while (0)
+
+#define CHECK_TMD(ADDR,RES) do { \
+ struct pcnet_TMD tmd; \
+ TMDLOAD(&tmd,(ADDR)); \
+ (RES) |= (GET_FIELD(tmd.length, TMDL, ONES) != 15); \
+} while (0)
+
+#else
+
+#define CHECK_RMD(ADDR,RES) do { \
+ switch (BCR_SWSTYLE(s)) { \
+ case 0x00: \
+ do { \
+ uint16_t rda[4]; \
+ s->phys_mem_read(s->dma_opaque, (ADDR), \
+ (void *)&rda[0], sizeof(rda), 0); \
+ (RES) |= (rda[2] & 0xf000)!=0xf000; \
+ (RES) |= (rda[3] & 0xf000)!=0x0000; \
+ } while (0); \
+ break; \
+ case 0x01: \
+ case 0x02: \
+ do { \
+ uint32_t rda[4]; \
+ s->phys_mem_read(s->dma_opaque, (ADDR), \
+ (void *)&rda[0], sizeof(rda), 0); \
+ (RES) |= (rda[1] & 0x0000f000L)!=0x0000f000L; \
+ (RES) |= (rda[2] & 0x0000f000L)!=0x00000000L; \
+ } while (0); \
+ break; \
+ case 0x03: \
+ do { \
+ uint32_t rda[4]; \
+ s->phys_mem_read(s->dma_opaque, (ADDR), \
+ (void *)&rda[0], sizeof(rda), 0); \
+ (RES) |= (rda[0] & 0x0000f000L)!=0x00000000L; \
+ (RES) |= (rda[1] & 0x0000f000L)!=0x0000f000L; \
+ } while (0); \
+ break; \
+ } \
+} while (0)
+
+#define CHECK_TMD(ADDR,RES) do { \
+ switch (BCR_SWSTYLE(s)) { \
+ case 0x00: \
+ do { \
+ uint16_t xda[4]; \
+ s->phys_mem_read(s->dma_opaque, (ADDR), \
+ (void *)&xda[0], sizeof(xda), 0); \
+ (RES) |= (xda[2] & 0xf000)!=0xf000; \
+ } while (0); \
+ break; \
+ case 0x01: \
+ case 0x02: \
+ case 0x03: \
+ do { \
+ uint32_t xda[4]; \
+ s->phys_mem_read(s->dma_opaque, (ADDR), \
+ (void *)&xda[0], sizeof(xda), 0); \
+ (RES) |= (xda[1] & 0x0000f000L)!=0x0000f000L; \
+ } while (0); \
+ break; \
+ } \
+} while (0)
+
+#endif
+
+#define PRINT_PKTHDR(BUF) do { \
+ struct qemu_ether_header *hdr = (void *)(BUF); \
+ printf("packet dhost=%02x:%02x:%02x:%02x:%02x:%02x, " \
+ "shost=%02x:%02x:%02x:%02x:%02x:%02x, " \
+ "type=0x%04x\n", \
+ hdr->ether_dhost[0],hdr->ether_dhost[1],hdr->ether_dhost[2], \
+ hdr->ether_dhost[3],hdr->ether_dhost[4],hdr->ether_dhost[5], \
+ hdr->ether_shost[0],hdr->ether_shost[1],hdr->ether_shost[2], \
+ hdr->ether_shost[3],hdr->ether_shost[4],hdr->ether_shost[5], \
+ be16_to_cpu(hdr->ether_type)); \
+} while (0)
+
+#define MULTICAST_FILTER_LEN 8
+
+static inline uint32_t lnc_mchash(const uint8_t *ether_addr)
+{
+#define LNC_POLYNOMIAL 0xEDB88320UL
+ uint32_t crc = 0xFFFFFFFF;
+ int idx, bit;
+ uint8_t data;
+
+ for (idx = 0; idx < 6; idx++) {
+ for (data = *ether_addr++, bit = 0; bit < MULTICAST_FILTER_LEN; bit++) {
+ crc = (crc >> 1) ^ (((crc ^ data) & 1) ? LNC_POLYNOMIAL : 0);
+ data >>= 1;
+ }
+ }
+ return crc;
+#undef LNC_POLYNOMIAL
+}
+
+#define CRC(crc, ch) (crc = (crc >> 8) ^ crctab[(crc ^ (ch)) & 0xff])
+
+/* generated using the AUTODIN II polynomial
+ * x^32 + x^26 + x^23 + x^22 + x^16 +
+ * x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
+ */
+static const uint32_t crctab[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+ 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+ 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+ 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+ 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+ 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+ 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+ 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+ 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+ 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+ 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+ 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+ 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+ 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+ 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+};
+
+static inline int padr_match(PCNetState *s, const uint8_t *buf, int size)
+{
+ struct qemu_ether_header *hdr = (void *)buf;
+ uint8_t padr[6] = {
+ s->csr[12] & 0xff, s->csr[12] >> 8,
+ s->csr[13] & 0xff, s->csr[13] >> 8,
+ s->csr[14] & 0xff, s->csr[14] >> 8
+ };
+ int result = (!CSR_DRCVPA(s)) && !memcmp(hdr->ether_dhost, padr, 6);
+#ifdef PCNET_DEBUG_MATCH
+ printf("packet dhost=%02x:%02x:%02x:%02x:%02x:%02x, "
+ "padr=%02x:%02x:%02x:%02x:%02x:%02x\n",
+ hdr->ether_dhost[0],hdr->ether_dhost[1],hdr->ether_dhost[2],
+ hdr->ether_dhost[3],hdr->ether_dhost[4],hdr->ether_dhost[5],
+ padr[0],padr[1],padr[2],padr[3],padr[4],padr[5]);
+ printf("padr_match result=%d\n", result);
+#endif
+ return result;
+}
+
+static inline int padr_bcast(PCNetState *s, const uint8_t *buf, int size)
+{
+ static const uint8_t BCAST[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ struct qemu_ether_header *hdr = (void *)buf;
+ int result = !CSR_DRCVBC(s) && !memcmp(hdr->ether_dhost, BCAST, 6);
+#ifdef PCNET_DEBUG_MATCH
+ printf("padr_bcast result=%d\n", result);
+#endif
+ return result;
+}
+
+static inline int ladr_match(PCNetState *s, const uint8_t *buf, int size)
+{
+ struct qemu_ether_header *hdr = (void *)buf;
+ if ((*(hdr->ether_dhost)&0x01) &&
+ ((uint64_t *)&s->csr[8])[0] != 0LL) {
+ uint8_t ladr[8] = {
+ s->csr[8] & 0xff, s->csr[8] >> 8,
+ s->csr[9] & 0xff, s->csr[9] >> 8,
+ s->csr[10] & 0xff, s->csr[10] >> 8,
+ s->csr[11] & 0xff, s->csr[11] >> 8
+ };
+ int index = lnc_mchash(hdr->ether_dhost) >> 26;
+ return !!(ladr[index >> 3] & (1 << (index & 7)));
+ }
+ return 0;
+}
+
+static inline hwaddr pcnet_rdra_addr(PCNetState *s, int idx)
+{
+ while (idx < 1) idx += CSR_RCVRL(s);
+ return s->rdra + ((CSR_RCVRL(s) - idx) * (BCR_SWSTYLE(s) ? 16 : 8));
+}
+
+static inline int64_t pcnet_get_next_poll_time(PCNetState *s, int64_t current_time)
+{
+ int64_t next_time = current_time +
+ muldiv64(65536 - (CSR_SPND(s) ? 0 : CSR_POLL(s)),
+ get_ticks_per_sec(), 33000000L);
+ if (next_time <= current_time)
+ next_time = current_time + 1;
+ return next_time;
+}
+
+static void pcnet_poll(PCNetState *s);
+static void pcnet_poll_timer(void *opaque);
+
+static uint32_t pcnet_csr_readw(PCNetState *s, uint32_t rap);
+static void pcnet_csr_writew(PCNetState *s, uint32_t rap, uint32_t new_value);
+static void pcnet_bcr_writew(PCNetState *s, uint32_t rap, uint32_t val);
+
+static void pcnet_s_reset(PCNetState *s)
+{
+ trace_pcnet_s_reset(s);
+
+ s->rdra = 0;
+ s->tdra = 0;
+ s->rap = 0;
+
+ s->bcr[BCR_BSBC] &= ~0x0080;
+
+ s->csr[0] = 0x0004;
+ s->csr[3] = 0x0000;
+ s->csr[4] = 0x0115;
+ s->csr[5] = 0x0000;
+ s->csr[6] = 0x0000;
+ s->csr[8] = 0;
+ s->csr[9] = 0;
+ s->csr[10] = 0;
+ s->csr[11] = 0;
+ s->csr[12] = le16_to_cpu(((uint16_t *)&s->prom[0])[0]);
+ s->csr[13] = le16_to_cpu(((uint16_t *)&s->prom[0])[1]);
+ s->csr[14] = le16_to_cpu(((uint16_t *)&s->prom[0])[2]);
+ s->csr[15] &= 0x21c4;
+ s->csr[72] = 1;
+ s->csr[74] = 1;
+ s->csr[76] = 1;
+ s->csr[78] = 1;
+ s->csr[80] = 0x1410;
+ s->csr[88] = 0x1003;
+ s->csr[89] = 0x0262;
+ s->csr[94] = 0x0000;
+ s->csr[100] = 0x0200;
+ s->csr[103] = 0x0105;
+ s->csr[112] = 0x0000;
+ s->csr[114] = 0x0000;
+ s->csr[122] = 0x0000;
+ s->csr[124] = 0x0000;
+
+ s->tx_busy = 0;
+}
+
+static void pcnet_update_irq(PCNetState *s)
+{
+ int isr = 0;
+ s->csr[0] &= ~0x0080;
+
+#if 1
+ if (((s->csr[0] & ~s->csr[3]) & 0x5f00) ||
+ (((s->csr[4]>>1) & ~s->csr[4]) & 0x0115) ||
+ (((s->csr[5]>>1) & s->csr[5]) & 0x0048))
+#else
+ if ((!(s->csr[3] & 0x4000) && !!(s->csr[0] & 0x4000)) /* BABL */ ||
+ (!(s->csr[3] & 0x1000) && !!(s->csr[0] & 0x1000)) /* MISS */ ||
+ (!(s->csr[3] & 0x0100) && !!(s->csr[0] & 0x0100)) /* IDON */ ||
+ (!(s->csr[3] & 0x0200) && !!(s->csr[0] & 0x0200)) /* TINT */ ||
+ (!(s->csr[3] & 0x0400) && !!(s->csr[0] & 0x0400)) /* RINT */ ||
+ (!(s->csr[3] & 0x0800) && !!(s->csr[0] & 0x0800)) /* MERR */ ||
+ (!(s->csr[4] & 0x0001) && !!(s->csr[4] & 0x0002)) /* JAB */ ||
+ (!(s->csr[4] & 0x0004) && !!(s->csr[4] & 0x0008)) /* TXSTRT */ ||
+ (!(s->csr[4] & 0x0010) && !!(s->csr[4] & 0x0020)) /* RCVO */ ||
+ (!(s->csr[4] & 0x0100) && !!(s->csr[4] & 0x0200)) /* MFCO */ ||
+ (!!(s->csr[5] & 0x0040) && !!(s->csr[5] & 0x0080)) /* EXDINT */ ||
+ (!!(s->csr[5] & 0x0008) && !!(s->csr[5] & 0x0010)) /* MPINT */)
+#endif
+ {
+
+ isr = CSR_INEA(s);
+ s->csr[0] |= 0x0080;
+ }
+
+ if (!!(s->csr[4] & 0x0080) && CSR_INEA(s)) { /* UINT */
+ s->csr[4] &= ~0x0080;
+ s->csr[4] |= 0x0040;
+ s->csr[0] |= 0x0080;
+ isr = 1;
+ trace_pcnet_user_int(s);
+ }
+
+#if 1
+ if (((s->csr[5]>>1) & s->csr[5]) & 0x0500)
+#else
+ if ((!!(s->csr[5] & 0x0400) && !!(s->csr[5] & 0x0800)) /* SINT */ ||
+ (!!(s->csr[5] & 0x0100) && !!(s->csr[5] & 0x0200)) /* SLPINT */ )
+#endif
+ {
+ isr = 1;
+ s->csr[0] |= 0x0080;
+ }
+
+ if (isr != s->isr) {
+ trace_pcnet_isr_change(s, isr, s->isr);
+ }
+ qemu_set_irq(s->irq, isr);
+ s->isr = isr;
+}
+
+static void pcnet_init(PCNetState *s)
+{
+ int rlen, tlen;
+ uint16_t padr[3], ladrf[4], mode;
+ uint32_t rdra, tdra;
+
+ trace_pcnet_init(s, PHYSADDR(s, CSR_IADR(s)));
+
+ if (BCR_SSIZE32(s)) {
+ struct pcnet_initblk32 initblk;
+ s->phys_mem_read(s->dma_opaque, PHYSADDR(s,CSR_IADR(s)),
+ (uint8_t *)&initblk, sizeof(initblk), 0);
+ mode = le16_to_cpu(initblk.mode);
+ rlen = initblk.rlen >> 4;
+ tlen = initblk.tlen >> 4;
+ ladrf[0] = le16_to_cpu(initblk.ladrf[0]);
+ ladrf[1] = le16_to_cpu(initblk.ladrf[1]);
+ ladrf[2] = le16_to_cpu(initblk.ladrf[2]);
+ ladrf[3] = le16_to_cpu(initblk.ladrf[3]);
+ padr[0] = le16_to_cpu(initblk.padr[0]);
+ padr[1] = le16_to_cpu(initblk.padr[1]);
+ padr[2] = le16_to_cpu(initblk.padr[2]);
+ rdra = le32_to_cpu(initblk.rdra);
+ tdra = le32_to_cpu(initblk.tdra);
+ } else {
+ struct pcnet_initblk16 initblk;
+ s->phys_mem_read(s->dma_opaque, PHYSADDR(s,CSR_IADR(s)),
+ (uint8_t *)&initblk, sizeof(initblk), 0);
+ mode = le16_to_cpu(initblk.mode);
+ ladrf[0] = le16_to_cpu(initblk.ladrf[0]);
+ ladrf[1] = le16_to_cpu(initblk.ladrf[1]);
+ ladrf[2] = le16_to_cpu(initblk.ladrf[2]);
+ ladrf[3] = le16_to_cpu(initblk.ladrf[3]);
+ padr[0] = le16_to_cpu(initblk.padr[0]);
+ padr[1] = le16_to_cpu(initblk.padr[1]);
+ padr[2] = le16_to_cpu(initblk.padr[2]);
+ rdra = le32_to_cpu(initblk.rdra);
+ tdra = le32_to_cpu(initblk.tdra);
+ rlen = rdra >> 29;
+ tlen = tdra >> 29;
+ rdra &= 0x00ffffff;
+ tdra &= 0x00ffffff;
+ }
+
+ trace_pcnet_rlen_tlen(s, rlen, tlen);
+
+ CSR_RCVRL(s) = (rlen < 9) ? (1 << rlen) : 512;
+ CSR_XMTRL(s) = (tlen < 9) ? (1 << tlen) : 512;
+ s->csr[ 6] = (tlen << 12) | (rlen << 8);
+ s->csr[15] = mode;
+ s->csr[ 8] = ladrf[0];
+ s->csr[ 9] = ladrf[1];
+ s->csr[10] = ladrf[2];
+ s->csr[11] = ladrf[3];
+ s->csr[12] = padr[0];
+ s->csr[13] = padr[1];
+ s->csr[14] = padr[2];
+ s->rdra = PHYSADDR(s, rdra);
+ s->tdra = PHYSADDR(s, tdra);
+
+ CSR_RCVRC(s) = CSR_RCVRL(s);
+ CSR_XMTRC(s) = CSR_XMTRL(s);
+
+ trace_pcnet_ss32_rdra_tdra(s, BCR_SSIZE32(s),
+ s->rdra, CSR_RCVRL(s), s->tdra, CSR_XMTRL(s));
+
+ s->csr[0] |= 0x0101;
+ s->csr[0] &= ~0x0004; /* clear STOP bit */
+
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+}
+
+static void pcnet_start(PCNetState *s)
+{
+#ifdef PCNET_DEBUG
+ printf("pcnet_start\n");
+#endif
+
+ if (!CSR_DTX(s))
+ s->csr[0] |= 0x0010; /* set TXON */
+
+ if (!CSR_DRX(s))
+ s->csr[0] |= 0x0020; /* set RXON */
+
+ s->csr[0] &= ~0x0004; /* clear STOP bit */
+ s->csr[0] |= 0x0002;
+ pcnet_poll_timer(s);
+
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+}
+
+static void pcnet_stop(PCNetState *s)
+{
+#ifdef PCNET_DEBUG
+ printf("pcnet_stop\n");
+#endif
+ s->csr[0] &= ~0xffeb;
+ s->csr[0] |= 0x0014;
+ s->csr[4] &= ~0x02c2;
+ s->csr[5] &= ~0x0011;
+ pcnet_poll_timer(s);
+}
+
+static void pcnet_rdte_poll(PCNetState *s)
+{
+ s->csr[28] = s->csr[29] = 0;
+ if (s->rdra) {
+ int bad = 0;
+#if 1
+ hwaddr crda = pcnet_rdra_addr(s, CSR_RCVRC(s));
+ hwaddr nrda = pcnet_rdra_addr(s, -1 + CSR_RCVRC(s));
+ hwaddr nnrd = pcnet_rdra_addr(s, -2 + CSR_RCVRC(s));
+#else
+ hwaddr crda = s->rdra +
+ (CSR_RCVRL(s) - CSR_RCVRC(s)) *
+ (BCR_SWSTYLE(s) ? 16 : 8 );
+ int nrdc = CSR_RCVRC(s)<=1 ? CSR_RCVRL(s) : CSR_RCVRC(s)-1;
+ hwaddr nrda = s->rdra +
+ (CSR_RCVRL(s) - nrdc) *
+ (BCR_SWSTYLE(s) ? 16 : 8 );
+ int nnrc = nrdc<=1 ? CSR_RCVRL(s) : nrdc-1;
+ hwaddr nnrd = s->rdra +
+ (CSR_RCVRL(s) - nnrc) *
+ (BCR_SWSTYLE(s) ? 16 : 8 );
+#endif
+
+ CHECK_RMD(crda, bad);
+ if (!bad) {
+ CHECK_RMD(nrda, bad);
+ if (bad || (nrda == crda)) nrda = 0;
+ CHECK_RMD(nnrd, bad);
+ if (bad || (nnrd == crda)) nnrd = 0;
+
+ s->csr[28] = crda & 0xffff;
+ s->csr[29] = crda >> 16;
+ s->csr[26] = nrda & 0xffff;
+ s->csr[27] = nrda >> 16;
+ s->csr[36] = nnrd & 0xffff;
+ s->csr[37] = nnrd >> 16;
+#ifdef PCNET_DEBUG
+ if (bad) {
+ printf("pcnet: BAD RMD RECORDS AFTER 0x" TARGET_FMT_plx "\n",
+ crda);
+ }
+ } else {
+ printf("pcnet: BAD RMD RDA=0x" TARGET_FMT_plx "\n",
+ crda);
+#endif
+ }
+ }
+
+ if (CSR_CRDA(s)) {
+ struct pcnet_RMD rmd;
+ RMDLOAD(&rmd, PHYSADDR(s,CSR_CRDA(s)));
+ CSR_CRBC(s) = GET_FIELD(rmd.buf_length, RMDL, BCNT);
+ CSR_CRST(s) = rmd.status;
+#ifdef PCNET_DEBUG_RMD_X
+ printf("CRDA=0x%08x CRST=0x%04x RCVRC=%d RMDL=0x%04x RMDS=0x%04x RMDM=0x%08x\n",
+ PHYSADDR(s,CSR_CRDA(s)), CSR_CRST(s), CSR_RCVRC(s),
+ rmd.buf_length, rmd.status, rmd.msg_length);
+ PRINT_RMD(&rmd);
+#endif
+ } else {
+ CSR_CRBC(s) = CSR_CRST(s) = 0;
+ }
+
+ if (CSR_NRDA(s)) {
+ struct pcnet_RMD rmd;
+ RMDLOAD(&rmd, PHYSADDR(s,CSR_NRDA(s)));
+ CSR_NRBC(s) = GET_FIELD(rmd.buf_length, RMDL, BCNT);
+ CSR_NRST(s) = rmd.status;
+ } else {
+ CSR_NRBC(s) = CSR_NRST(s) = 0;
+ }
+
+}
+
+static int pcnet_tdte_poll(PCNetState *s)
+{
+ s->csr[34] = s->csr[35] = 0;
+ if (s->tdra) {
+ hwaddr cxda = s->tdra +
+ (CSR_XMTRL(s) - CSR_XMTRC(s)) *
+ (BCR_SWSTYLE(s) ? 16 : 8);
+ int bad = 0;
+ CHECK_TMD(cxda, bad);
+ if (!bad) {
+ if (CSR_CXDA(s) != cxda) {
+ s->csr[60] = s->csr[34];
+ s->csr[61] = s->csr[35];
+ s->csr[62] = CSR_CXBC(s);
+ s->csr[63] = CSR_CXST(s);
+ }
+ s->csr[34] = cxda & 0xffff;
+ s->csr[35] = cxda >> 16;
+#ifdef PCNET_DEBUG_X
+ printf("pcnet: BAD TMD XDA=0x%08x\n", cxda);
+#endif
+ }
+ }
+
+ if (CSR_CXDA(s)) {
+ struct pcnet_TMD tmd;
+
+ TMDLOAD(&tmd, PHYSADDR(s,CSR_CXDA(s)));
+
+ CSR_CXBC(s) = GET_FIELD(tmd.length, TMDL, BCNT);
+ CSR_CXST(s) = tmd.status;
+ } else {
+ CSR_CXBC(s) = CSR_CXST(s) = 0;
+ }
+
+ return !!(CSR_CXST(s) & 0x8000);
+}
+
+#define MIN_BUF_SIZE 60
+
+ssize_t pcnet_receive(NetClientState *nc, const uint8_t *buf, size_t size_)
+{
+ PCNetState *s = qemu_get_nic_opaque(nc);
+ int is_padr = 0, is_bcast = 0, is_ladr = 0;
+ uint8_t buf1[60];
+ int remaining;
+ int crc_err = 0;
+ int size = size_;
+
+ if (CSR_DRX(s) || CSR_STOP(s) || CSR_SPND(s) || !size ||
+ (CSR_LOOP(s) && !s->looptest)) {
+ return -1;
+ }
+#ifdef PCNET_DEBUG
+ printf("pcnet_receive size=%d\n", size);
+#endif
+
+ /* if too small buffer, then expand it */
+ if (size < MIN_BUF_SIZE) {
+ memcpy(buf1, buf, size);
+ memset(buf1 + size, 0, MIN_BUF_SIZE - size);
+ buf = buf1;
+ size = MIN_BUF_SIZE;
+ }
+
+ if (CSR_PROM(s)
+ || (is_padr=padr_match(s, buf, size))
+ || (is_bcast=padr_bcast(s, buf, size))
+ || (is_ladr=ladr_match(s, buf, size))) {
+
+ pcnet_rdte_poll(s);
+
+ if (!(CSR_CRST(s) & 0x8000) && s->rdra) {
+ struct pcnet_RMD rmd;
+ int rcvrc = CSR_RCVRC(s)-1,i;
+ hwaddr nrda;
+ for (i = CSR_RCVRL(s)-1; i > 0; i--, rcvrc--) {
+ if (rcvrc <= 1)
+ rcvrc = CSR_RCVRL(s);
+ nrda = s->rdra +
+ (CSR_RCVRL(s) - rcvrc) *
+ (BCR_SWSTYLE(s) ? 16 : 8 );
+ RMDLOAD(&rmd, nrda);
+ if (GET_FIELD(rmd.status, RMDS, OWN)) {
+#ifdef PCNET_DEBUG_RMD
+ printf("pcnet - scan buffer: RCVRC=%d PREV_RCVRC=%d\n",
+ rcvrc, CSR_RCVRC(s));
+#endif
+ CSR_RCVRC(s) = rcvrc;
+ pcnet_rdte_poll(s);
+ break;
+ }
+ }
+ }
+
+ if (!(CSR_CRST(s) & 0x8000)) {
+#ifdef PCNET_DEBUG_RMD
+ printf("pcnet - no buffer: RCVRC=%d\n", CSR_RCVRC(s));
+#endif
+ s->csr[0] |= 0x1000; /* Set MISS flag */
+ CSR_MISSC(s)++;
+ } else {
+ uint8_t *src = s->buffer;
+ hwaddr crda = CSR_CRDA(s);
+ struct pcnet_RMD rmd;
+ int pktcount = 0;
+
+ if (!s->looptest) {
+ if (size > 4092) {
+#ifdef PCNET_DEBUG_RMD
+ fprintf(stderr, "pcnet: truncates rx packet.\n");
+#endif
+ size = 4092;
+ }
+ memcpy(src, buf, size);
+ /* no need to compute the CRC */
+ src[size] = 0;
+ src[size + 1] = 0;
+ src[size + 2] = 0;
+ src[size + 3] = 0;
+ size += 4;
+ } else if (s->looptest == PCNET_LOOPTEST_CRC ||
+ !CSR_DXMTFCS(s) || size < MIN_BUF_SIZE+4) {
+ uint32_t fcs = ~0;
+ uint8_t *p = src;
+
+ while (p != &src[size])
+ CRC(fcs, *p++);
+ *(uint32_t *)p = htonl(fcs);
+ size += 4;
+ } else {
+ uint32_t fcs = ~0;
+ uint8_t *p = src;
+
+ while (p != &src[size])
+ CRC(fcs, *p++);
+ crc_err = (*(uint32_t *)p != htonl(fcs));
+ }
+
+#ifdef PCNET_DEBUG_MATCH
+ PRINT_PKTHDR(buf);
+#endif
+
+ RMDLOAD(&rmd, PHYSADDR(s,crda));
+ /*if (!CSR_LAPPEN(s))*/
+ SET_FIELD(&rmd.status, RMDS, STP, 1);
+
+#define PCNET_RECV_STORE() do { \
+ int count = MIN(4096 - GET_FIELD(rmd.buf_length, RMDL, BCNT),remaining); \
+ hwaddr rbadr = PHYSADDR(s, rmd.rbadr); \
+ s->phys_mem_write(s->dma_opaque, rbadr, src, count, CSR_BSWP(s)); \
+ src += count; remaining -= count; \
+ SET_FIELD(&rmd.status, RMDS, OWN, 0); \
+ RMDSTORE(&rmd, PHYSADDR(s,crda)); \
+ pktcount++; \
+} while (0)
+
+ remaining = size;
+ PCNET_RECV_STORE();
+ if ((remaining > 0) && CSR_NRDA(s)) {
+ hwaddr nrda = CSR_NRDA(s);
+#ifdef PCNET_DEBUG_RMD
+ PRINT_RMD(&rmd);
+#endif
+ RMDLOAD(&rmd, PHYSADDR(s,nrda));
+ if (GET_FIELD(rmd.status, RMDS, OWN)) {
+ crda = nrda;
+ PCNET_RECV_STORE();
+#ifdef PCNET_DEBUG_RMD
+ PRINT_RMD(&rmd);
+#endif
+ if ((remaining > 0) && (nrda=CSR_NNRD(s))) {
+ RMDLOAD(&rmd, PHYSADDR(s,nrda));
+ if (GET_FIELD(rmd.status, RMDS, OWN)) {
+ crda = nrda;
+ PCNET_RECV_STORE();
+ }
+ }
+ }
+ }
+
+#undef PCNET_RECV_STORE
+
+ RMDLOAD(&rmd, PHYSADDR(s,crda));
+ if (remaining == 0) {
+ SET_FIELD(&rmd.msg_length, RMDM, MCNT, size);
+ SET_FIELD(&rmd.status, RMDS, ENP, 1);
+ SET_FIELD(&rmd.status, RMDS, PAM, !CSR_PROM(s) && is_padr);
+ SET_FIELD(&rmd.status, RMDS, LFAM, !CSR_PROM(s) && is_ladr);
+ SET_FIELD(&rmd.status, RMDS, BAM, !CSR_PROM(s) && is_bcast);
+ if (crc_err) {
+ SET_FIELD(&rmd.status, RMDS, CRC, 1);
+ SET_FIELD(&rmd.status, RMDS, ERR, 1);
+ }
+ } else {
+ SET_FIELD(&rmd.status, RMDS, OFLO, 1);
+ SET_FIELD(&rmd.status, RMDS, BUFF, 1);
+ SET_FIELD(&rmd.status, RMDS, ERR, 1);
+ }
+ RMDSTORE(&rmd, PHYSADDR(s,crda));
+ s->csr[0] |= 0x0400;
+
+#ifdef PCNET_DEBUG
+ printf("RCVRC=%d CRDA=0x%08x BLKS=%d\n",
+ CSR_RCVRC(s), PHYSADDR(s,CSR_CRDA(s)), pktcount);
+#endif
+#ifdef PCNET_DEBUG_RMD
+ PRINT_RMD(&rmd);
+#endif
+
+ while (pktcount--) {
+ if (CSR_RCVRC(s) <= 1)
+ CSR_RCVRC(s) = CSR_RCVRL(s);
+ else
+ CSR_RCVRC(s)--;
+ }
+
+ pcnet_rdte_poll(s);
+
+ }
+ }
+
+ pcnet_poll(s);
+ pcnet_update_irq(s);
+
+ return size_;
+}
+
+void pcnet_set_link_status(NetClientState *nc)
+{
+ PCNetState *d = qemu_get_nic_opaque(nc);
+
+ d->lnkst = nc->link_down ? 0 : 0x40;
+}
+
+static void pcnet_transmit(PCNetState *s)
+{
+ hwaddr xmit_cxda = 0;
+ int count = CSR_XMTRL(s)-1;
+ int add_crc = 0;
+ int bcnt;
+ s->xmit_pos = -1;
+
+ if (!CSR_TXON(s)) {
+ s->csr[0] &= ~0x0008;
+ return;
+ }
+
+ s->tx_busy = 1;
+
+ txagain:
+ if (pcnet_tdte_poll(s)) {
+ struct pcnet_TMD tmd;
+
+ TMDLOAD(&tmd, PHYSADDR(s,CSR_CXDA(s)));
+
+#ifdef PCNET_DEBUG_TMD
+ printf(" TMDLOAD 0x%08x\n", PHYSADDR(s,CSR_CXDA(s)));
+ PRINT_TMD(&tmd);
+#endif
+ if (GET_FIELD(tmd.status, TMDS, STP)) {
+ s->xmit_pos = 0;
+ xmit_cxda = PHYSADDR(s,CSR_CXDA(s));
+ if (BCR_SWSTYLE(s) != 1)
+ add_crc = GET_FIELD(tmd.status, TMDS, ADDFCS);
+ }
+ if (s->lnkst == 0 &&
+ (!CSR_LOOP(s) || (!CSR_INTL(s) && !BCR_TMAULOOP(s)))) {
+ SET_FIELD(&tmd.misc, TMDM, LCAR, 1);
+ SET_FIELD(&tmd.status, TMDS, ERR, 1);
+ SET_FIELD(&tmd.status, TMDS, OWN, 0);
+ s->csr[0] |= 0xa000; /* ERR | CERR */
+ s->xmit_pos = -1;
+ goto txdone;
+ }
+
+ if (s->xmit_pos < 0) {
+ goto txdone;
+ }
+
+ bcnt = 4096 - GET_FIELD(tmd.length, TMDL, BCNT);
+
+ /* if multi-tmd packet outsizes s->buffer then skip it silently.
+ * Note: this is not what real hw does.
+ * Last four bytes of s->buffer are used to store CRC FCS code.
+ */
+ if (s->xmit_pos + bcnt > sizeof(s->buffer) - 4) {
+ s->xmit_pos = -1;
+ goto txdone;
+ }
+
+ s->phys_mem_read(s->dma_opaque, PHYSADDR(s, tmd.tbadr),
+ s->buffer + s->xmit_pos, bcnt, CSR_BSWP(s));
+ s->xmit_pos += bcnt;
+
+ if (!GET_FIELD(tmd.status, TMDS, ENP)) {
+ goto txdone;
+ }
+
+#ifdef PCNET_DEBUG
+ printf("pcnet_transmit size=%d\n", s->xmit_pos);
+#endif
+ if (CSR_LOOP(s)) {
+ if (BCR_SWSTYLE(s) == 1)
+ add_crc = !GET_FIELD(tmd.status, TMDS, NOFCS);
+ s->looptest = add_crc ? PCNET_LOOPTEST_CRC : PCNET_LOOPTEST_NOCRC;
+ pcnet_receive(qemu_get_queue(s->nic), s->buffer, s->xmit_pos);
+ s->looptest = 0;
+ } else {
+ if (s->nic) {
+ qemu_send_packet(qemu_get_queue(s->nic), s->buffer,
+ s->xmit_pos);
+ }
+ }
+
+ s->csr[0] &= ~0x0008; /* clear TDMD */
+ s->csr[4] |= 0x0004; /* set TXSTRT */
+ s->xmit_pos = -1;
+
+ txdone:
+ SET_FIELD(&tmd.status, TMDS, OWN, 0);
+ TMDSTORE(&tmd, PHYSADDR(s,CSR_CXDA(s)));
+ if (!CSR_TOKINTD(s) || (CSR_LTINTEN(s) && GET_FIELD(tmd.status, TMDS, LTINT)))
+ s->csr[0] |= 0x0200; /* set TINT */
+
+ if (CSR_XMTRC(s)<=1)
+ CSR_XMTRC(s) = CSR_XMTRL(s);
+ else
+ CSR_XMTRC(s)--;
+ if (count--)
+ goto txagain;
+
+ } else
+ if (s->xmit_pos >= 0) {
+ struct pcnet_TMD tmd;
+ TMDLOAD(&tmd, xmit_cxda);
+ SET_FIELD(&tmd.misc, TMDM, BUFF, 1);
+ SET_FIELD(&tmd.misc, TMDM, UFLO, 1);
+ SET_FIELD(&tmd.status, TMDS, ERR, 1);
+ SET_FIELD(&tmd.status, TMDS, OWN, 0);
+ TMDSTORE(&tmd, xmit_cxda);
+ s->csr[0] |= 0x0200; /* set TINT */
+ if (!CSR_DXSUFLO(s)) {
+ s->csr[0] &= ~0x0010;
+ } else
+ if (count--)
+ goto txagain;
+ }
+
+ s->tx_busy = 0;
+}
+
+static void pcnet_poll(PCNetState *s)
+{
+ if (CSR_RXON(s)) {
+ pcnet_rdte_poll(s);
+ }
+
+ if (CSR_TDMD(s) ||
+ (CSR_TXON(s) && !CSR_DPOLL(s) && pcnet_tdte_poll(s)))
+ {
+ /* prevent recursion */
+ if (s->tx_busy)
+ return;
+
+ pcnet_transmit(s);
+ }
+}
+
+static void pcnet_poll_timer(void *opaque)
+{
+ PCNetState *s = opaque;
+
+ timer_del(s->poll_timer);
+
+ if (CSR_TDMD(s)) {
+ pcnet_transmit(s);
+ }
+
+ pcnet_update_irq(s);
+
+ if (!CSR_STOP(s) && !CSR_SPND(s) && !CSR_DPOLL(s)) {
+ uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) * 33;
+ if (!s->timer || !now)
+ s->timer = now;
+ else {
+ uint64_t t = now - s->timer + CSR_POLL(s);
+ if (t > 0xffffLL) {
+ pcnet_poll(s);
+ CSR_POLL(s) = CSR_PINT(s);
+ } else
+ CSR_POLL(s) = t;
+ }
+ timer_mod(s->poll_timer,
+ pcnet_get_next_poll_time(s,qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)));
+ }
+}
+
+
+static void pcnet_csr_writew(PCNetState *s, uint32_t rap, uint32_t new_value)
+{
+ uint16_t val = new_value;
+#ifdef PCNET_DEBUG_CSR
+ printf("pcnet_csr_writew rap=%d val=0x%04x\n", rap, val);
+#endif
+ switch (rap) {
+ case 0:
+ s->csr[0] &= ~(val & 0x7f00); /* Clear any interrupt flags */
+
+ s->csr[0] = (s->csr[0] & ~0x0040) | (val & 0x0048);
+
+ val = (val & 0x007f) | (s->csr[0] & 0x7f00);
+
+ /* IFF STOP, STRT and INIT are set, clear STRT and INIT */
+ if ((val&7) == 7)
+ val &= ~3;
+
+ if (!CSR_STOP(s) && (val & 4))
+ pcnet_stop(s);
+
+ if (!CSR_INIT(s) && (val & 1))
+ pcnet_init(s);
+
+ if (!CSR_STRT(s) && (val & 2))
+ pcnet_start(s);
+
+ if (CSR_TDMD(s))
+ pcnet_transmit(s);
+
+ return;
+ case 1:
+ case 2:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 18: /* CRBAL */
+ case 19: /* CRBAU */
+ case 20: /* CXBAL */
+ case 21: /* CXBAU */
+ case 22: /* NRBAU */
+ case 23: /* NRBAU */
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 28:
+ case 29:
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ case 38:
+ case 39:
+ case 40: /* CRBC */
+ case 41:
+ case 42: /* CXBC */
+ case 43:
+ case 44:
+ case 45:
+ case 46: /* POLL */
+ case 47: /* POLLINT */
+ case 72:
+ case 74:
+ case 76: /* RCVRL */
+ case 78: /* XMTRL */
+ case 112:
+ if (CSR_STOP(s) || CSR_SPND(s))
+ break;
+ return;
+ case 3:
+ break;
+ case 4:
+ s->csr[4] &= ~(val & 0x026a);
+ val &= ~0x026a; val |= s->csr[4] & 0x026a;
+ break;
+ case 5:
+ s->csr[5] &= ~(val & 0x0a90);
+ val &= ~0x0a90; val |= s->csr[5] & 0x0a90;
+ break;
+ case 16:
+ pcnet_csr_writew(s,1,val);
+ return;
+ case 17:
+ pcnet_csr_writew(s,2,val);
+ return;
+ case 58:
+ pcnet_bcr_writew(s,BCR_SWS,val);
+ break;
+ default:
+ return;
+ }
+ s->csr[rap] = val;
+}
+
+static uint32_t pcnet_csr_readw(PCNetState *s, uint32_t rap)
+{
+ uint32_t val;
+ switch (rap) {
+ case 0:
+ pcnet_update_irq(s);
+ val = s->csr[0];
+ val |= (val & 0x7800) ? 0x8000 : 0;
+ break;
+ case 16:
+ return pcnet_csr_readw(s,1);
+ case 17:
+ return pcnet_csr_readw(s,2);
+ case 58:
+ return pcnet_bcr_readw(s,BCR_SWS);
+ case 88:
+ val = s->csr[89];
+ val <<= 16;
+ val |= s->csr[88];
+ break;
+ default:
+ val = s->csr[rap];
+ }
+#ifdef PCNET_DEBUG_CSR
+ printf("pcnet_csr_readw rap=%d val=0x%04x\n", rap, val);
+#endif
+ return val;
+}
+
+static void pcnet_bcr_writew(PCNetState *s, uint32_t rap, uint32_t val)
+{
+ rap &= 127;
+#ifdef PCNET_DEBUG_BCR
+ printf("pcnet_bcr_writew rap=%d val=0x%04x\n", rap, val);
+#endif
+ switch (rap) {
+ case BCR_SWS:
+ if (!(CSR_STOP(s) || CSR_SPND(s)))
+ return;
+ val &= ~0x0300;
+ switch (val & 0x00ff) {
+ case 0:
+ val |= 0x0200;
+ break;
+ case 1:
+ val |= 0x0100;
+ break;
+ case 2:
+ case 3:
+ val |= 0x0300;
+ break;
+ default:
+ printf("Bad SWSTYLE=0x%02x\n", val & 0xff);
+ val = 0x0200;
+ break;
+ }
+#ifdef PCNET_DEBUG
+ printf("BCR_SWS=0x%04x\n", val);
+#endif
+ /* fall through */
+ case BCR_LNKST:
+ case BCR_LED1:
+ case BCR_LED2:
+ case BCR_LED3:
+ case BCR_MC:
+ case BCR_FDC:
+ case BCR_BSBC:
+ case BCR_EECAS:
+ case BCR_PLAT:
+ s->bcr[rap] = val;
+ break;
+ default:
+ break;
+ }
+}
+
+uint32_t pcnet_bcr_readw(PCNetState *s, uint32_t rap)
+{
+ uint32_t val;
+ rap &= 127;
+ switch (rap) {
+ case BCR_LNKST:
+ case BCR_LED1:
+ case BCR_LED2:
+ case BCR_LED3:
+ val = s->bcr[rap] & ~0x8000;
+ val |= (val & 0x017f & s->lnkst) ? 0x8000 : 0;
+ break;
+ default:
+ val = rap < 32 ? s->bcr[rap] : 0;
+ break;
+ }
+#ifdef PCNET_DEBUG_BCR
+ printf("pcnet_bcr_readw rap=%d val=0x%04x\n", rap, val);
+#endif
+ return val;
+}
+
+void pcnet_h_reset(void *opaque)
+{
+ PCNetState *s = opaque;
+
+ s->bcr[BCR_MSRDA] = 0x0005;
+ s->bcr[BCR_MSWRA] = 0x0005;
+ s->bcr[BCR_MC ] = 0x0002;
+ s->bcr[BCR_LNKST] = 0x00c0;
+ s->bcr[BCR_LED1 ] = 0x0084;
+ s->bcr[BCR_LED2 ] = 0x0088;
+ s->bcr[BCR_LED3 ] = 0x0090;
+ s->bcr[BCR_FDC ] = 0x0000;
+ s->bcr[BCR_BSBC ] = 0x9001;
+ s->bcr[BCR_EECAS] = 0x0002;
+ s->bcr[BCR_SWS ] = 0x0200;
+ s->bcr[BCR_PLAT ] = 0xff06;
+
+ pcnet_s_reset(s);
+ pcnet_update_irq(s);
+ pcnet_poll_timer(s);
+}
+
+void pcnet_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
+{
+ PCNetState *s = opaque;
+ pcnet_poll_timer(s);
+#ifdef PCNET_DEBUG_IO
+ printf("pcnet_ioport_writew addr=0x%08x val=0x%04x\n", addr, val);
+#endif
+ if (!BCR_DWIO(s)) {
+ switch (addr & 0x0f) {
+ case 0x00: /* RDP */
+ pcnet_csr_writew(s, s->rap, val);
+ break;
+ case 0x02:
+ s->rap = val & 0x7f;
+ break;
+ case 0x06:
+ pcnet_bcr_writew(s, s->rap, val);
+ break;
+ }
+ }
+ pcnet_update_irq(s);
+}
+
+uint32_t pcnet_ioport_readw(void *opaque, uint32_t addr)
+{
+ PCNetState *s = opaque;
+ uint32_t val = -1;
+ pcnet_poll_timer(s);
+ if (!BCR_DWIO(s)) {
+ switch (addr & 0x0f) {
+ case 0x00: /* RDP */
+ val = pcnet_csr_readw(s, s->rap);
+ break;
+ case 0x02:
+ val = s->rap;
+ break;
+ case 0x04:
+ pcnet_s_reset(s);
+ val = 0;
+ break;
+ case 0x06:
+ val = pcnet_bcr_readw(s, s->rap);
+ break;
+ }
+ }
+ pcnet_update_irq(s);
+#ifdef PCNET_DEBUG_IO
+ printf("pcnet_ioport_readw addr=0x%08x val=0x%04x\n", addr, val & 0xffff);
+#endif
+ return val;
+}
+
+void pcnet_ioport_writel(void *opaque, uint32_t addr, uint32_t val)
+{
+ PCNetState *s = opaque;
+ pcnet_poll_timer(s);
+#ifdef PCNET_DEBUG_IO
+ printf("pcnet_ioport_writel addr=0x%08x val=0x%08x\n", addr, val);
+#endif
+ if (BCR_DWIO(s)) {
+ switch (addr & 0x0f) {
+ case 0x00: /* RDP */
+ pcnet_csr_writew(s, s->rap, val & 0xffff);
+ break;
+ case 0x04:
+ s->rap = val & 0x7f;
+ break;
+ case 0x0c:
+ pcnet_bcr_writew(s, s->rap, val & 0xffff);
+ break;
+ }
+ } else
+ if ((addr & 0x0f) == 0) {
+ /* switch device to dword i/o mode */
+ pcnet_bcr_writew(s, BCR_BSBC, pcnet_bcr_readw(s, BCR_BSBC) | 0x0080);
+#ifdef PCNET_DEBUG_IO
+ printf("device switched into dword i/o mode\n");
+#endif
+ }
+ pcnet_update_irq(s);
+}
+
+uint32_t pcnet_ioport_readl(void *opaque, uint32_t addr)
+{
+ PCNetState *s = opaque;
+ uint32_t val = -1;
+ pcnet_poll_timer(s);
+ if (BCR_DWIO(s)) {
+ switch (addr & 0x0f) {
+ case 0x00: /* RDP */
+ val = pcnet_csr_readw(s, s->rap);
+ break;
+ case 0x04:
+ val = s->rap;
+ break;
+ case 0x08:
+ pcnet_s_reset(s);
+ val = 0;
+ break;
+ case 0x0c:
+ val = pcnet_bcr_readw(s, s->rap);
+ break;
+ }
+ }
+ pcnet_update_irq(s);
+#ifdef PCNET_DEBUG_IO
+ printf("pcnet_ioport_readl addr=0x%08x val=0x%08x\n", addr, val);
+#endif
+ return val;
+}
+
+static bool is_version_2(void *opaque, int version_id)
+{
+ return version_id == 2;
+}
+
+const VMStateDescription vmstate_pcnet = {
+ .name = "pcnet",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(rap, PCNetState),
+ VMSTATE_INT32(isr, PCNetState),
+ VMSTATE_INT32(lnkst, PCNetState),
+ VMSTATE_UINT32(rdra, PCNetState),
+ VMSTATE_UINT32(tdra, PCNetState),
+ VMSTATE_BUFFER(prom, PCNetState),
+ VMSTATE_UINT16_ARRAY(csr, PCNetState, 128),
+ VMSTATE_UINT16_ARRAY(bcr, PCNetState, 32),
+ VMSTATE_UINT64(timer, PCNetState),
+ VMSTATE_INT32(xmit_pos, PCNetState),
+ VMSTATE_BUFFER(buffer, PCNetState),
+ VMSTATE_UNUSED_TEST(is_version_2, 4),
+ VMSTATE_INT32(tx_busy, PCNetState),
+ VMSTATE_TIMER_PTR(poll_timer, PCNetState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+void pcnet_common_init(DeviceState *dev, PCNetState *s, NetClientInfo *info)
+{
+ int i;
+ uint16_t checksum;
+
+ s->poll_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, pcnet_poll_timer, s);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(info, &s->conf, object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ /* Initialize the PROM */
+
+ /*
+ Datasheet: http://pdfdata.datasheetsite.com/web/24528/AM79C970A.pdf
+ page 95
+ */
+ memcpy(s->prom, s->conf.macaddr.a, 6);
+ /* Reserved Location: must be 00h */
+ s->prom[6] = s->prom[7] = 0x00;
+ /* Reserved Location: must be 00h */
+ s->prom[8] = 0x00;
+ /* Hardware ID: must be 11h if compatibility to AMD drivers is desired */
+ s->prom[9] = 0x11;
+ /* User programmable space, init with 0 */
+ s->prom[10] = s->prom[11] = 0x00;
+ /* LSByte of two-byte checksum, which is the sum of bytes 00h-0Bh
+ and bytes 0Eh and 0Fh, must therefore be initialized with 0! */
+ s->prom[12] = s->prom[13] = 0x00;
+ /* Must be ASCII W (57h) if compatibility to AMD
+ driver software is desired */
+ s->prom[14] = s->prom[15] = 0x57;
+
+ for (i = 0, checksum = 0; i < 16; i++) {
+ checksum += s->prom[i];
+ }
+ *(uint16_t *)&s->prom[12] = cpu_to_le16(checksum);
+
+ s->lnkst = 0x40; /* initial link state: up */
+}
diff --git a/hw/net/pcnet.h b/hw/net/pcnet.h
new file mode 100644
index 00000000..dec8de83
--- /dev/null
+++ b/hw/net/pcnet.h
@@ -0,0 +1,67 @@
+#ifndef HW_PCNET_H
+#define HW_PCNET_H 1
+
+#define PCNET_IOPORT_SIZE 0x20
+#define PCNET_PNPMMIO_SIZE 0x20
+
+#define PCNET_LOOPTEST_CRC 1
+#define PCNET_LOOPTEST_NOCRC 2
+
+#include "exec/memory.h"
+
+/* BUS CONFIGURATION REGISTERS */
+#define BCR_MSRDA 0
+#define BCR_MSWRA 1
+#define BCR_MC 2
+#define BCR_LNKST 4
+#define BCR_LED1 5
+#define BCR_LED2 6
+#define BCR_LED3 7
+#define BCR_FDC 9
+#define BCR_BSBC 18
+#define BCR_EECAS 19
+#define BCR_SWS 20
+#define BCR_PLAT 22
+
+#define BCR_TMAULOOP(S) !!((S)->bcr[BCR_MC ] & 0x4000)
+#define BCR_APROMWE(S) !!((S)->bcr[BCR_MC ] & 0x0100)
+#define BCR_DWIO(S) !!((S)->bcr[BCR_BSBC] & 0x0080)
+#define BCR_SSIZE32(S) !!((S)->bcr[BCR_SWS ] & 0x0100)
+#define BCR_SWSTYLE(S) ((S)->bcr[BCR_SWS ] & 0x00FF)
+
+typedef struct PCNetState_st PCNetState;
+
+struct PCNetState_st {
+ NICState *nic;
+ NICConf conf;
+ QEMUTimer *poll_timer;
+ int rap, isr, lnkst;
+ uint32_t rdra, tdra;
+ uint8_t prom[16];
+ uint16_t csr[128];
+ uint16_t bcr[32];
+ int xmit_pos;
+ uint64_t timer;
+ MemoryRegion mmio;
+ uint8_t buffer[4096];
+ qemu_irq irq;
+ void (*phys_mem_read)(void *dma_opaque, hwaddr addr,
+ uint8_t *buf, int len, int do_bswap);
+ void (*phys_mem_write)(void *dma_opaque, hwaddr addr,
+ uint8_t *buf, int len, int do_bswap);
+ void *dma_opaque;
+ int tx_busy;
+ int looptest;
+};
+
+void pcnet_h_reset(void *opaque);
+void pcnet_ioport_writew(void *opaque, uint32_t addr, uint32_t val);
+uint32_t pcnet_ioport_readw(void *opaque, uint32_t addr);
+void pcnet_ioport_writel(void *opaque, uint32_t addr, uint32_t val);
+uint32_t pcnet_ioport_readl(void *opaque, uint32_t addr);
+uint32_t pcnet_bcr_readw(PCNetState *s, uint32_t rap);
+ssize_t pcnet_receive(NetClientState *nc, const uint8_t *buf, size_t size_);
+void pcnet_set_link_status(NetClientState *nc);
+void pcnet_common_init(DeviceState *dev, PCNetState *s, NetClientInfo *info);
+extern const VMStateDescription vmstate_pcnet;
+#endif
diff --git a/hw/net/rocker/qmp-norocker.c b/hw/net/rocker/qmp-norocker.c
new file mode 100644
index 00000000..49b498b6
--- /dev/null
+++ b/hw/net/rocker/qmp-norocker.c
@@ -0,0 +1,50 @@
+/*
+ * QMP Target options - Commands handled based on a target config
+ * versus a host config
+ *
+ * Copyright (c) 2015 David Ahern <dsahern@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "qemu-common.h"
+#include "qmp-commands.h"
+#include "qapi/qmp/qerror.h"
+
+RockerSwitch *qmp_query_rocker(const char *name, Error **errp)
+{
+ error_setg(errp, QERR_FEATURE_DISABLED, "rocker");
+ return NULL;
+};
+
+RockerPortList *qmp_query_rocker_ports(const char *name, Error **errp)
+{
+ error_setg(errp, QERR_FEATURE_DISABLED, "rocker");
+ return NULL;
+};
+
+RockerOfDpaFlowList *qmp_query_rocker_of_dpa_flows(const char *name,
+ bool has_tbl_id,
+ uint32_t tbl_id,
+ Error **errp)
+{
+ error_setg(errp, QERR_FEATURE_DISABLED, "rocker");
+ return NULL;
+};
+
+RockerOfDpaGroupList *qmp_query_rocker_of_dpa_groups(const char *name,
+ bool has_type,
+ uint8_t type,
+ Error **errp)
+{
+ error_setg(errp, QERR_FEATURE_DISABLED, "rocker");
+ return NULL;
+};
diff --git a/hw/net/rocker/rocker.c b/hw/net/rocker/rocker.c
new file mode 100644
index 00000000..7e4ec0a7
--- /dev/null
+++ b/hw/net/rocker/rocker.c
@@ -0,0 +1,1553 @@
+/*
+ * QEMU rocker switch emulation - PCI device
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/msix.h"
+#include "net/net.h"
+#include "net/eth.h"
+#include "qemu/iov.h"
+#include "qemu/bitops.h"
+#include "qmp-commands.h"
+
+#include "rocker.h"
+#include "rocker_hw.h"
+#include "rocker_fp.h"
+#include "rocker_desc.h"
+#include "rocker_tlv.h"
+#include "rocker_world.h"
+#include "rocker_of_dpa.h"
+
+struct rocker {
+ /* private */
+ PCIDevice parent_obj;
+ /* public */
+
+ MemoryRegion mmio;
+ MemoryRegion msix_bar;
+
+ /* switch configuration */
+ char *name; /* switch name */
+ uint32_t fp_ports; /* front-panel port count */
+ NICPeers *fp_ports_peers;
+ MACAddr fp_start_macaddr; /* front-panel port 0 mac addr */
+ uint64_t switch_id; /* switch id */
+
+ /* front-panel ports */
+ FpPort *fp_port[ROCKER_FP_PORTS_MAX];
+
+ /* register backings */
+ uint32_t test_reg;
+ uint64_t test_reg64;
+ dma_addr_t test_dma_addr;
+ uint32_t test_dma_size;
+ uint64_t lower32; /* lower 32-bit val in 2-part 64-bit access */
+
+ /* desc rings */
+ DescRing **rings;
+
+ /* switch worlds */
+ World *worlds[ROCKER_WORLD_TYPE_MAX];
+ World *world_dflt;
+
+ QLIST_ENTRY(rocker) next;
+};
+
+#define ROCKER "rocker"
+
+#define to_rocker(obj) \
+ OBJECT_CHECK(Rocker, (obj), ROCKER)
+
+static QLIST_HEAD(, rocker) rockers;
+
+Rocker *rocker_find(const char *name)
+{
+ Rocker *r;
+
+ QLIST_FOREACH(r, &rockers, next)
+ if (strcmp(r->name, name) == 0) {
+ return r;
+ }
+
+ return NULL;
+}
+
+World *rocker_get_world(Rocker *r, enum rocker_world_type type)
+{
+ if (type < ROCKER_WORLD_TYPE_MAX) {
+ return r->worlds[type];
+ }
+ return NULL;
+}
+
+RockerSwitch *qmp_query_rocker(const char *name, Error **errp)
+{
+ RockerSwitch *rocker;
+ Rocker *r;
+
+ r = rocker_find(name);
+ if (!r) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "rocker %s not found", name);
+ return NULL;
+ }
+
+ rocker = g_new0(RockerSwitch, 1);
+ rocker->name = g_strdup(r->name);
+ rocker->id = r->switch_id;
+ rocker->ports = r->fp_ports;
+
+ return rocker;
+}
+
+RockerPortList *qmp_query_rocker_ports(const char *name, Error **errp)
+{
+ RockerPortList *list = NULL;
+ Rocker *r;
+ int i;
+
+ r = rocker_find(name);
+ if (!r) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "rocker %s not found", name);
+ return NULL;
+ }
+
+ for (i = r->fp_ports - 1; i >= 0; i--) {
+ RockerPortList *info = g_malloc0(sizeof(*info));
+ info->value = g_malloc0(sizeof(*info->value));
+ struct fp_port *port = r->fp_port[i];
+
+ fp_port_get_info(port, info);
+ info->next = list;
+ list = info;
+ }
+
+ return list;
+}
+
+uint32_t rocker_fp_ports(Rocker *r)
+{
+ return r->fp_ports;
+}
+
+static uint32_t rocker_get_pport_by_tx_ring(Rocker *r,
+ DescRing *ring)
+{
+ return (desc_ring_index(ring) - 2) / 2 + 1;
+}
+
+static int tx_consume(Rocker *r, DescInfo *info)
+{
+ PCIDevice *dev = PCI_DEVICE(r);
+ char *buf = desc_get_buf(info, true);
+ RockerTlv *tlv_frag;
+ RockerTlv *tlvs[ROCKER_TLV_TX_MAX + 1];
+ struct iovec iov[ROCKER_TX_FRAGS_MAX] = { { 0, }, };
+ uint32_t pport;
+ uint32_t port;
+ uint16_t tx_offload = ROCKER_TX_OFFLOAD_NONE;
+ uint16_t tx_l3_csum_off = 0;
+ uint16_t tx_tso_mss = 0;
+ uint16_t tx_tso_hdr_len = 0;
+ int iovcnt = 0;
+ int err = ROCKER_OK;
+ int rem;
+ int i;
+
+ if (!buf) {
+ return -ROCKER_ENXIO;
+ }
+
+ rocker_tlv_parse(tlvs, ROCKER_TLV_TX_MAX, buf, desc_tlv_size(info));
+
+ if (!tlvs[ROCKER_TLV_TX_FRAGS]) {
+ return -ROCKER_EINVAL;
+ }
+
+ pport = rocker_get_pport_by_tx_ring(r, desc_get_ring(info));
+ if (!fp_port_from_pport(pport, &port)) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (tlvs[ROCKER_TLV_TX_OFFLOAD]) {
+ tx_offload = rocker_tlv_get_u8(tlvs[ROCKER_TLV_TX_OFFLOAD]);
+ }
+
+ switch (tx_offload) {
+ case ROCKER_TX_OFFLOAD_L3_CSUM:
+ if (!tlvs[ROCKER_TLV_TX_L3_CSUM_OFF]) {
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case ROCKER_TX_OFFLOAD_TSO:
+ if (!tlvs[ROCKER_TLV_TX_TSO_MSS] ||
+ !tlvs[ROCKER_TLV_TX_TSO_HDR_LEN]) {
+ return -ROCKER_EINVAL;
+ }
+ break;
+ }
+
+ if (tlvs[ROCKER_TLV_TX_L3_CSUM_OFF]) {
+ tx_l3_csum_off = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_L3_CSUM_OFF]);
+ }
+
+ if (tlvs[ROCKER_TLV_TX_TSO_MSS]) {
+ tx_tso_mss = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_TSO_MSS]);
+ }
+
+ if (tlvs[ROCKER_TLV_TX_TSO_HDR_LEN]) {
+ tx_tso_hdr_len = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_TSO_HDR_LEN]);
+ }
+
+ rocker_tlv_for_each_nested(tlv_frag, tlvs[ROCKER_TLV_TX_FRAGS], rem) {
+ hwaddr frag_addr;
+ uint16_t frag_len;
+
+ if (rocker_tlv_type(tlv_frag) != ROCKER_TLV_TX_FRAG) {
+ err = -ROCKER_EINVAL;
+ goto err_bad_attr;
+ }
+
+ rocker_tlv_parse_nested(tlvs, ROCKER_TLV_TX_FRAG_ATTR_MAX, tlv_frag);
+
+ if (!tlvs[ROCKER_TLV_TX_FRAG_ATTR_ADDR] ||
+ !tlvs[ROCKER_TLV_TX_FRAG_ATTR_LEN]) {
+ err = -ROCKER_EINVAL;
+ goto err_bad_attr;
+ }
+
+ frag_addr = rocker_tlv_get_le64(tlvs[ROCKER_TLV_TX_FRAG_ATTR_ADDR]);
+ frag_len = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_FRAG_ATTR_LEN]);
+
+ if (iovcnt >= ROCKER_TX_FRAGS_MAX) {
+ goto err_too_many_frags;
+ }
+ iov[iovcnt].iov_len = frag_len;
+ iov[iovcnt].iov_base = g_malloc(frag_len);
+ if (!iov[iovcnt].iov_base) {
+ err = -ROCKER_ENOMEM;
+ goto err_no_mem;
+ }
+
+ if (pci_dma_read(dev, frag_addr, iov[iovcnt].iov_base,
+ iov[iovcnt].iov_len)) {
+ err = -ROCKER_ENXIO;
+ goto err_bad_io;
+ }
+ iovcnt++;
+ }
+
+ if (iovcnt) {
+ /* XXX perform Tx offloads */
+ /* XXX silence compiler for now */
+ tx_l3_csum_off += tx_tso_mss = tx_tso_hdr_len = 0;
+ }
+
+ err = fp_port_eg(r->fp_port[port], iov, iovcnt);
+
+err_too_many_frags:
+err_bad_io:
+err_no_mem:
+err_bad_attr:
+ for (i = 0; i < ROCKER_TX_FRAGS_MAX; i++) {
+ if (iov[i].iov_base) {
+ g_free(iov[i].iov_base);
+ }
+ }
+
+ return err;
+}
+
+static int cmd_get_port_settings(Rocker *r,
+ DescInfo *info, char *buf,
+ RockerTlv *cmd_info_tlv)
+{
+ RockerTlv *tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_MAX + 1];
+ RockerTlv *nest;
+ FpPort *fp_port;
+ uint32_t pport;
+ uint32_t port;
+ uint32_t speed;
+ uint8_t duplex;
+ uint8_t autoneg;
+ uint8_t learning;
+ char *phys_name;
+ MACAddr macaddr;
+ enum rocker_world_type mode;
+ size_t tlv_size;
+ int pos;
+ int err;
+
+ rocker_tlv_parse_nested(tlvs, ROCKER_TLV_CMD_PORT_SETTINGS_MAX,
+ cmd_info_tlv);
+
+ if (!tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_PPORT]) {
+ return -ROCKER_EINVAL;
+ }
+
+ pport = rocker_tlv_get_le32(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_PPORT]);
+ if (!fp_port_from_pport(pport, &port)) {
+ return -ROCKER_EINVAL;
+ }
+ fp_port = r->fp_port[port];
+
+ err = fp_port_get_settings(fp_port, &speed, &duplex, &autoneg);
+ if (err) {
+ return err;
+ }
+
+ fp_port_get_macaddr(fp_port, &macaddr);
+ mode = world_type(fp_port_get_world(fp_port));
+ learning = fp_port_get_learning(fp_port);
+ phys_name = fp_port_get_name(fp_port);
+
+ tlv_size = rocker_tlv_total_size(0) + /* nest */
+ rocker_tlv_total_size(sizeof(uint32_t)) + /* pport */
+ rocker_tlv_total_size(sizeof(uint32_t)) + /* speed */
+ rocker_tlv_total_size(sizeof(uint8_t)) + /* duplex */
+ rocker_tlv_total_size(sizeof(uint8_t)) + /* autoneg */
+ rocker_tlv_total_size(sizeof(macaddr.a)) + /* macaddr */
+ rocker_tlv_total_size(sizeof(uint8_t)) + /* mode */
+ rocker_tlv_total_size(sizeof(uint8_t)) + /* learning */
+ rocker_tlv_total_size(strlen(phys_name));
+
+ if (tlv_size > desc_buf_size(info)) {
+ return -ROCKER_EMSGSIZE;
+ }
+
+ pos = 0;
+ nest = rocker_tlv_nest_start(buf, &pos, ROCKER_TLV_CMD_INFO);
+ rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_CMD_PORT_SETTINGS_PPORT, pport);
+ rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_CMD_PORT_SETTINGS_SPEED, speed);
+ rocker_tlv_put_u8(buf, &pos, ROCKER_TLV_CMD_PORT_SETTINGS_DUPLEX, duplex);
+ rocker_tlv_put_u8(buf, &pos, ROCKER_TLV_CMD_PORT_SETTINGS_AUTONEG, autoneg);
+ rocker_tlv_put(buf, &pos, ROCKER_TLV_CMD_PORT_SETTINGS_MACADDR,
+ sizeof(macaddr.a), macaddr.a);
+ rocker_tlv_put_u8(buf, &pos, ROCKER_TLV_CMD_PORT_SETTINGS_MODE, mode);
+ rocker_tlv_put_u8(buf, &pos, ROCKER_TLV_CMD_PORT_SETTINGS_LEARNING,
+ learning);
+ rocker_tlv_put(buf, &pos, ROCKER_TLV_CMD_PORT_SETTINGS_PHYS_NAME,
+ strlen(phys_name), phys_name);
+ rocker_tlv_nest_end(buf, &pos, nest);
+
+ return desc_set_buf(info, tlv_size);
+}
+
+static int cmd_set_port_settings(Rocker *r,
+ RockerTlv *cmd_info_tlv)
+{
+ RockerTlv *tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_MAX + 1];
+ FpPort *fp_port;
+ uint32_t pport;
+ uint32_t port;
+ uint32_t speed;
+ uint8_t duplex;
+ uint8_t autoneg;
+ uint8_t learning;
+ MACAddr macaddr;
+ enum rocker_world_type mode;
+ int err;
+
+ rocker_tlv_parse_nested(tlvs, ROCKER_TLV_CMD_PORT_SETTINGS_MAX,
+ cmd_info_tlv);
+
+ if (!tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_PPORT]) {
+ return -ROCKER_EINVAL;
+ }
+
+ pport = rocker_tlv_get_le32(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_PPORT]);
+ if (!fp_port_from_pport(pport, &port)) {
+ return -ROCKER_EINVAL;
+ }
+ fp_port = r->fp_port[port];
+
+ if (tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_SPEED] &&
+ tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_DUPLEX] &&
+ tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_AUTONEG]) {
+
+ speed = rocker_tlv_get_le32(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_SPEED]);
+ duplex = rocker_tlv_get_u8(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_DUPLEX]);
+ autoneg = rocker_tlv_get_u8(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_AUTONEG]);
+
+ err = fp_port_set_settings(fp_port, speed, duplex, autoneg);
+ if (err) {
+ return err;
+ }
+ }
+
+ if (tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_MACADDR]) {
+ if (rocker_tlv_len(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_MACADDR]) !=
+ sizeof(macaddr.a)) {
+ return -ROCKER_EINVAL;
+ }
+ memcpy(macaddr.a,
+ rocker_tlv_data(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_MACADDR]),
+ sizeof(macaddr.a));
+ fp_port_set_macaddr(fp_port, &macaddr);
+ }
+
+ if (tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_MODE]) {
+ mode = rocker_tlv_get_u8(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_MODE]);
+ fp_port_set_world(fp_port, r->worlds[mode]);
+ }
+
+ if (tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_LEARNING]) {
+ learning =
+ rocker_tlv_get_u8(tlvs[ROCKER_TLV_CMD_PORT_SETTINGS_LEARNING]);
+ fp_port_set_learning(fp_port, learning);
+ }
+
+ return ROCKER_OK;
+}
+
+static int cmd_consume(Rocker *r, DescInfo *info)
+{
+ char *buf = desc_get_buf(info, false);
+ RockerTlv *tlvs[ROCKER_TLV_CMD_MAX + 1];
+ RockerTlv *info_tlv;
+ World *world;
+ uint16_t cmd;
+ int err;
+
+ if (!buf) {
+ return -ROCKER_ENXIO;
+ }
+
+ rocker_tlv_parse(tlvs, ROCKER_TLV_CMD_MAX, buf, desc_tlv_size(info));
+
+ if (!tlvs[ROCKER_TLV_CMD_TYPE] || !tlvs[ROCKER_TLV_CMD_INFO]) {
+ return -ROCKER_EINVAL;
+ }
+
+ cmd = rocker_tlv_get_le16(tlvs[ROCKER_TLV_CMD_TYPE]);
+ info_tlv = tlvs[ROCKER_TLV_CMD_INFO];
+
+ /* This might be reworked to something like this:
+ * Every world will have an array of command handlers from
+ * ROCKER_TLV_CMD_TYPE_UNSPEC to ROCKER_TLV_CMD_TYPE_MAX. There is
+ * up to each world to implement whatever command it want.
+ * It can reference "generic" commands as cmd_set_port_settings or
+ * cmd_get_port_settings
+ */
+
+ switch (cmd) {
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_GET_STATS:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_GET_STATS:
+ world = r->worlds[ROCKER_WORLD_TYPE_OF_DPA];
+ err = world_do_cmd(world, info, buf, cmd, info_tlv);
+ break;
+ case ROCKER_TLV_CMD_TYPE_GET_PORT_SETTINGS:
+ err = cmd_get_port_settings(r, info, buf, info_tlv);
+ break;
+ case ROCKER_TLV_CMD_TYPE_SET_PORT_SETTINGS:
+ err = cmd_set_port_settings(r, info_tlv);
+ break;
+ default:
+ err = -ROCKER_EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static void rocker_msix_irq(Rocker *r, unsigned vector)
+{
+ PCIDevice *dev = PCI_DEVICE(r);
+
+ DPRINTF("MSI-X notify request for vector %d\n", vector);
+ if (vector >= ROCKER_MSIX_VEC_COUNT(r->fp_ports)) {
+ DPRINTF("incorrect vector %d\n", vector);
+ return;
+ }
+ msix_notify(dev, vector);
+}
+
+int rocker_event_link_changed(Rocker *r, uint32_t pport, bool link_up)
+{
+ DescRing *ring = r->rings[ROCKER_RING_EVENT];
+ DescInfo *info = desc_ring_fetch_desc(ring);
+ RockerTlv *nest;
+ char *buf;
+ size_t tlv_size;
+ int pos;
+ int err;
+
+ if (!info) {
+ return -ROCKER_ENOBUFS;
+ }
+
+ tlv_size = rocker_tlv_total_size(sizeof(uint16_t)) + /* event type */
+ rocker_tlv_total_size(0) + /* nest */
+ rocker_tlv_total_size(sizeof(uint32_t)) + /* pport */
+ rocker_tlv_total_size(sizeof(uint8_t)); /* link up */
+
+ if (tlv_size > desc_buf_size(info)) {
+ err = -ROCKER_EMSGSIZE;
+ goto err_too_big;
+ }
+
+ buf = desc_get_buf(info, false);
+ if (!buf) {
+ err = -ROCKER_ENOMEM;
+ goto err_no_mem;
+ }
+
+ pos = 0;
+ rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_EVENT_TYPE,
+ ROCKER_TLV_EVENT_TYPE_LINK_CHANGED);
+ nest = rocker_tlv_nest_start(buf, &pos, ROCKER_TLV_EVENT_INFO);
+ rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_EVENT_LINK_CHANGED_PPORT, pport);
+ rocker_tlv_put_u8(buf, &pos, ROCKER_TLV_EVENT_LINK_CHANGED_LINKUP,
+ link_up ? 1 : 0);
+ rocker_tlv_nest_end(buf, &pos, nest);
+
+ err = desc_set_buf(info, tlv_size);
+
+err_too_big:
+err_no_mem:
+ if (desc_ring_post_desc(ring, err)) {
+ rocker_msix_irq(r, ROCKER_MSIX_VEC_EVENT);
+ }
+
+ return err;
+}
+
+int rocker_event_mac_vlan_seen(Rocker *r, uint32_t pport, uint8_t *addr,
+ uint16_t vlan_id)
+{
+ DescRing *ring = r->rings[ROCKER_RING_EVENT];
+ DescInfo *info;
+ FpPort *fp_port;
+ uint32_t port;
+ RockerTlv *nest;
+ char *buf;
+ size_t tlv_size;
+ int pos;
+ int err;
+
+ if (!fp_port_from_pport(pport, &port)) {
+ return -ROCKER_EINVAL;
+ }
+ fp_port = r->fp_port[port];
+ if (!fp_port_get_learning(fp_port)) {
+ return ROCKER_OK;
+ }
+
+ info = desc_ring_fetch_desc(ring);
+ if (!info) {
+ return -ROCKER_ENOBUFS;
+ }
+
+ tlv_size = rocker_tlv_total_size(sizeof(uint16_t)) + /* event type */
+ rocker_tlv_total_size(0) + /* nest */
+ rocker_tlv_total_size(sizeof(uint32_t)) + /* pport */
+ rocker_tlv_total_size(ETH_ALEN) + /* mac addr */
+ rocker_tlv_total_size(sizeof(uint16_t)); /* vlan_id */
+
+ if (tlv_size > desc_buf_size(info)) {
+ err = -ROCKER_EMSGSIZE;
+ goto err_too_big;
+ }
+
+ buf = desc_get_buf(info, false);
+ if (!buf) {
+ err = -ROCKER_ENOMEM;
+ goto err_no_mem;
+ }
+
+ pos = 0;
+ rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_EVENT_TYPE,
+ ROCKER_TLV_EVENT_TYPE_MAC_VLAN_SEEN);
+ nest = rocker_tlv_nest_start(buf, &pos, ROCKER_TLV_EVENT_INFO);
+ rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_EVENT_MAC_VLAN_PPORT, pport);
+ rocker_tlv_put(buf, &pos, ROCKER_TLV_EVENT_MAC_VLAN_MAC, ETH_ALEN, addr);
+ rocker_tlv_put_u16(buf, &pos, ROCKER_TLV_EVENT_MAC_VLAN_VLAN_ID, vlan_id);
+ rocker_tlv_nest_end(buf, &pos, nest);
+
+ err = desc_set_buf(info, tlv_size);
+
+err_too_big:
+err_no_mem:
+ if (desc_ring_post_desc(ring, err)) {
+ rocker_msix_irq(r, ROCKER_MSIX_VEC_EVENT);
+ }
+
+ return err;
+}
+
+static DescRing *rocker_get_rx_ring_by_pport(Rocker *r,
+ uint32_t pport)
+{
+ return r->rings[(pport - 1) * 2 + 3];
+}
+
+int rx_produce(World *world, uint32_t pport,
+ const struct iovec *iov, int iovcnt, uint8_t copy_to_cpu)
+{
+ Rocker *r = world_rocker(world);
+ PCIDevice *dev = (PCIDevice *)r;
+ DescRing *ring = rocker_get_rx_ring_by_pport(r, pport);
+ DescInfo *info = desc_ring_fetch_desc(ring);
+ char *data;
+ size_t data_size = iov_size(iov, iovcnt);
+ char *buf;
+ uint16_t rx_flags = 0;
+ uint16_t rx_csum = 0;
+ size_t tlv_size;
+ RockerTlv *tlvs[ROCKER_TLV_RX_MAX + 1];
+ hwaddr frag_addr;
+ uint16_t frag_max_len;
+ int pos;
+ int err;
+
+ if (!info) {
+ return -ROCKER_ENOBUFS;
+ }
+
+ buf = desc_get_buf(info, false);
+ if (!buf) {
+ err = -ROCKER_ENXIO;
+ goto out;
+ }
+ rocker_tlv_parse(tlvs, ROCKER_TLV_RX_MAX, buf, desc_tlv_size(info));
+
+ if (!tlvs[ROCKER_TLV_RX_FRAG_ADDR] ||
+ !tlvs[ROCKER_TLV_RX_FRAG_MAX_LEN]) {
+ err = -ROCKER_EINVAL;
+ goto out;
+ }
+
+ frag_addr = rocker_tlv_get_le64(tlvs[ROCKER_TLV_RX_FRAG_ADDR]);
+ frag_max_len = rocker_tlv_get_le16(tlvs[ROCKER_TLV_RX_FRAG_MAX_LEN]);
+
+ if (data_size > frag_max_len) {
+ err = -ROCKER_EMSGSIZE;
+ goto out;
+ }
+
+ if (copy_to_cpu) {
+ rx_flags |= ROCKER_RX_FLAGS_FWD_OFFLOAD;
+ }
+
+ /* XXX calc rx flags/csum */
+
+ tlv_size = rocker_tlv_total_size(sizeof(uint16_t)) + /* flags */
+ rocker_tlv_total_size(sizeof(uint16_t)) + /* scum */
+ rocker_tlv_total_size(sizeof(uint64_t)) + /* frag addr */
+ rocker_tlv_total_size(sizeof(uint16_t)) + /* frag max len */
+ rocker_tlv_total_size(sizeof(uint16_t)); /* frag len */
+
+ if (tlv_size > desc_buf_size(info)) {
+ err = -ROCKER_EMSGSIZE;
+ goto out;
+ }
+
+ /* TODO:
+ * iov dma write can be optimized in similar way e1000 does it in
+ * e1000_receive_iov. But maybe if would make sense to introduce
+ * generic helper iov_dma_write.
+ */
+
+ data = g_malloc(data_size);
+ if (!data) {
+ err = -ROCKER_ENOMEM;
+ goto out;
+ }
+ iov_to_buf(iov, iovcnt, 0, data, data_size);
+ pci_dma_write(dev, frag_addr, data, data_size);
+ g_free(data);
+
+ pos = 0;
+ rocker_tlv_put_le16(buf, &pos, ROCKER_TLV_RX_FLAGS, rx_flags);
+ rocker_tlv_put_le16(buf, &pos, ROCKER_TLV_RX_CSUM, rx_csum);
+ rocker_tlv_put_le64(buf, &pos, ROCKER_TLV_RX_FRAG_ADDR, frag_addr);
+ rocker_tlv_put_le16(buf, &pos, ROCKER_TLV_RX_FRAG_MAX_LEN, frag_max_len);
+ rocker_tlv_put_le16(buf, &pos, ROCKER_TLV_RX_FRAG_LEN, data_size);
+
+ err = desc_set_buf(info, tlv_size);
+
+out:
+ if (desc_ring_post_desc(ring, err)) {
+ rocker_msix_irq(r, ROCKER_MSIX_VEC_RX(pport - 1));
+ }
+
+ return err;
+}
+
+int rocker_port_eg(Rocker *r, uint32_t pport,
+ const struct iovec *iov, int iovcnt)
+{
+ FpPort *fp_port;
+ uint32_t port;
+
+ if (!fp_port_from_pport(pport, &port)) {
+ return -ROCKER_EINVAL;
+ }
+
+ fp_port = r->fp_port[port];
+
+ return fp_port_eg(fp_port, iov, iovcnt);
+}
+
+static void rocker_test_dma_ctrl(Rocker *r, uint32_t val)
+{
+ PCIDevice *dev = PCI_DEVICE(r);
+ char *buf;
+ int i;
+
+ buf = g_malloc(r->test_dma_size);
+
+ if (!buf) {
+ DPRINTF("test dma buffer alloc failed");
+ return;
+ }
+
+ switch (val) {
+ case ROCKER_TEST_DMA_CTRL_CLEAR:
+ memset(buf, 0, r->test_dma_size);
+ break;
+ case ROCKER_TEST_DMA_CTRL_FILL:
+ memset(buf, 0x96, r->test_dma_size);
+ break;
+ case ROCKER_TEST_DMA_CTRL_INVERT:
+ pci_dma_read(dev, r->test_dma_addr, buf, r->test_dma_size);
+ for (i = 0; i < r->test_dma_size; i++) {
+ buf[i] = ~buf[i];
+ }
+ break;
+ default:
+ DPRINTF("not test dma control val=0x%08x\n", val);
+ goto err_out;
+ }
+ pci_dma_write(dev, r->test_dma_addr, buf, r->test_dma_size);
+
+ rocker_msix_irq(r, ROCKER_MSIX_VEC_TEST);
+
+err_out:
+ g_free(buf);
+}
+
+static void rocker_reset(DeviceState *dev);
+
+static void rocker_control(Rocker *r, uint32_t val)
+{
+ if (val & ROCKER_CONTROL_RESET) {
+ rocker_reset(DEVICE(r));
+ }
+}
+
+static int rocker_pci_ring_count(Rocker *r)
+{
+ /* There are:
+ * - command ring
+ * - event ring
+ * - tx and rx ring per each port
+ */
+ return 2 + (2 * r->fp_ports);
+}
+
+static bool rocker_addr_is_desc_reg(Rocker *r, hwaddr addr)
+{
+ hwaddr start = ROCKER_DMA_DESC_BASE;
+ hwaddr end = start + (ROCKER_DMA_DESC_SIZE * rocker_pci_ring_count(r));
+
+ return addr >= start && addr < end;
+}
+
+static void rocker_port_phys_enable_write(Rocker *r, uint64_t new)
+{
+ int i;
+ bool old_enabled;
+ bool new_enabled;
+ FpPort *fp_port;
+
+ for (i = 0; i < r->fp_ports; i++) {
+ fp_port = r->fp_port[i];
+ old_enabled = fp_port_enabled(fp_port);
+ new_enabled = (new >> (i + 1)) & 0x1;
+ if (new_enabled == old_enabled) {
+ continue;
+ }
+ if (new_enabled) {
+ fp_port_enable(r->fp_port[i]);
+ } else {
+ fp_port_disable(r->fp_port[i]);
+ }
+ }
+}
+
+static void rocker_io_writel(void *opaque, hwaddr addr, uint32_t val)
+{
+ Rocker *r = opaque;
+
+ if (rocker_addr_is_desc_reg(r, addr)) {
+ unsigned index = ROCKER_RING_INDEX(addr);
+ unsigned offset = addr & ROCKER_DMA_DESC_MASK;
+
+ switch (offset) {
+ case ROCKER_DMA_DESC_ADDR_OFFSET:
+ r->lower32 = (uint64_t)val;
+ break;
+ case ROCKER_DMA_DESC_ADDR_OFFSET + 4:
+ desc_ring_set_base_addr(r->rings[index],
+ ((uint64_t)val) << 32 | r->lower32);
+ r->lower32 = 0;
+ break;
+ case ROCKER_DMA_DESC_SIZE_OFFSET:
+ desc_ring_set_size(r->rings[index], val);
+ break;
+ case ROCKER_DMA_DESC_HEAD_OFFSET:
+ if (desc_ring_set_head(r->rings[index], val)) {
+ rocker_msix_irq(r, desc_ring_get_msix_vector(r->rings[index]));
+ }
+ break;
+ case ROCKER_DMA_DESC_CTRL_OFFSET:
+ desc_ring_set_ctrl(r->rings[index], val);
+ break;
+ case ROCKER_DMA_DESC_CREDITS_OFFSET:
+ if (desc_ring_ret_credits(r->rings[index], val)) {
+ rocker_msix_irq(r, desc_ring_get_msix_vector(r->rings[index]));
+ }
+ break;
+ default:
+ DPRINTF("not implemented dma reg write(l) addr=0x" TARGET_FMT_plx
+ " val=0x%08x (ring %d, addr=0x%02x)\n",
+ addr, val, index, offset);
+ break;
+ }
+ return;
+ }
+
+ switch (addr) {
+ case ROCKER_TEST_REG:
+ r->test_reg = val;
+ break;
+ case ROCKER_TEST_REG64:
+ case ROCKER_TEST_DMA_ADDR:
+ case ROCKER_PORT_PHYS_ENABLE:
+ r->lower32 = (uint64_t)val;
+ break;
+ case ROCKER_TEST_REG64 + 4:
+ r->test_reg64 = ((uint64_t)val) << 32 | r->lower32;
+ r->lower32 = 0;
+ break;
+ case ROCKER_TEST_IRQ:
+ rocker_msix_irq(r, val);
+ break;
+ case ROCKER_TEST_DMA_SIZE:
+ r->test_dma_size = val;
+ break;
+ case ROCKER_TEST_DMA_ADDR + 4:
+ r->test_dma_addr = ((uint64_t)val) << 32 | r->lower32;
+ r->lower32 = 0;
+ break;
+ case ROCKER_TEST_DMA_CTRL:
+ rocker_test_dma_ctrl(r, val);
+ break;
+ case ROCKER_CONTROL:
+ rocker_control(r, val);
+ break;
+ case ROCKER_PORT_PHYS_ENABLE + 4:
+ rocker_port_phys_enable_write(r, ((uint64_t)val) << 32 | r->lower32);
+ r->lower32 = 0;
+ break;
+ default:
+ DPRINTF("not implemented write(l) addr=0x" TARGET_FMT_plx
+ " val=0x%08x\n", addr, val);
+ break;
+ }
+}
+
+static void rocker_io_writeq(void *opaque, hwaddr addr, uint64_t val)
+{
+ Rocker *r = opaque;
+
+ if (rocker_addr_is_desc_reg(r, addr)) {
+ unsigned index = ROCKER_RING_INDEX(addr);
+ unsigned offset = addr & ROCKER_DMA_DESC_MASK;
+
+ switch (offset) {
+ case ROCKER_DMA_DESC_ADDR_OFFSET:
+ desc_ring_set_base_addr(r->rings[index], val);
+ break;
+ default:
+ DPRINTF("not implemented dma reg write(q) addr=0x" TARGET_FMT_plx
+ " val=0x" TARGET_FMT_plx " (ring %d, offset=0x%02x)\n",
+ addr, val, index, offset);
+ break;
+ }
+ return;
+ }
+
+ switch (addr) {
+ case ROCKER_TEST_REG64:
+ r->test_reg64 = val;
+ break;
+ case ROCKER_TEST_DMA_ADDR:
+ r->test_dma_addr = val;
+ break;
+ case ROCKER_PORT_PHYS_ENABLE:
+ rocker_port_phys_enable_write(r, val);
+ break;
+ default:
+ DPRINTF("not implemented write(q) addr=0x" TARGET_FMT_plx
+ " val=0x" TARGET_FMT_plx "\n", addr, val);
+ break;
+ }
+}
+
+#ifdef DEBUG_ROCKER
+#define regname(reg) case (reg): return #reg
+static const char *rocker_reg_name(void *opaque, hwaddr addr)
+{
+ Rocker *r = opaque;
+
+ if (rocker_addr_is_desc_reg(r, addr)) {
+ unsigned index = ROCKER_RING_INDEX(addr);
+ unsigned offset = addr & ROCKER_DMA_DESC_MASK;
+ static char buf[100];
+ char ring_name[10];
+
+ switch (index) {
+ case 0:
+ sprintf(ring_name, "cmd");
+ break;
+ case 1:
+ sprintf(ring_name, "event");
+ break;
+ default:
+ sprintf(ring_name, "%s-%d", index % 2 ? "rx" : "tx",
+ (index - 2) / 2);
+ }
+
+ switch (offset) {
+ case ROCKER_DMA_DESC_ADDR_OFFSET:
+ sprintf(buf, "Ring[%s] ADDR", ring_name);
+ return buf;
+ case ROCKER_DMA_DESC_ADDR_OFFSET+4:
+ sprintf(buf, "Ring[%s] ADDR+4", ring_name);
+ return buf;
+ case ROCKER_DMA_DESC_SIZE_OFFSET:
+ sprintf(buf, "Ring[%s] SIZE", ring_name);
+ return buf;
+ case ROCKER_DMA_DESC_HEAD_OFFSET:
+ sprintf(buf, "Ring[%s] HEAD", ring_name);
+ return buf;
+ case ROCKER_DMA_DESC_TAIL_OFFSET:
+ sprintf(buf, "Ring[%s] TAIL", ring_name);
+ return buf;
+ case ROCKER_DMA_DESC_CTRL_OFFSET:
+ sprintf(buf, "Ring[%s] CTRL", ring_name);
+ return buf;
+ case ROCKER_DMA_DESC_CREDITS_OFFSET:
+ sprintf(buf, "Ring[%s] CREDITS", ring_name);
+ return buf;
+ default:
+ sprintf(buf, "Ring[%s] ???", ring_name);
+ return buf;
+ }
+ } else {
+ switch (addr) {
+ regname(ROCKER_BOGUS_REG0);
+ regname(ROCKER_BOGUS_REG1);
+ regname(ROCKER_BOGUS_REG2);
+ regname(ROCKER_BOGUS_REG3);
+ regname(ROCKER_TEST_REG);
+ regname(ROCKER_TEST_REG64);
+ regname(ROCKER_TEST_REG64+4);
+ regname(ROCKER_TEST_IRQ);
+ regname(ROCKER_TEST_DMA_ADDR);
+ regname(ROCKER_TEST_DMA_ADDR+4);
+ regname(ROCKER_TEST_DMA_SIZE);
+ regname(ROCKER_TEST_DMA_CTRL);
+ regname(ROCKER_CONTROL);
+ regname(ROCKER_PORT_PHYS_COUNT);
+ regname(ROCKER_PORT_PHYS_LINK_STATUS);
+ regname(ROCKER_PORT_PHYS_LINK_STATUS+4);
+ regname(ROCKER_PORT_PHYS_ENABLE);
+ regname(ROCKER_PORT_PHYS_ENABLE+4);
+ regname(ROCKER_SWITCH_ID);
+ regname(ROCKER_SWITCH_ID+4);
+ }
+ }
+ return "???";
+}
+#else
+static const char *rocker_reg_name(void *opaque, hwaddr addr)
+{
+ return NULL;
+}
+#endif
+
+static void rocker_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ DPRINTF("Write %s addr " TARGET_FMT_plx
+ ", size %u, val " TARGET_FMT_plx "\n",
+ rocker_reg_name(opaque, addr), addr, size, val);
+
+ switch (size) {
+ case 4:
+ rocker_io_writel(opaque, addr, val);
+ break;
+ case 8:
+ rocker_io_writeq(opaque, addr, val);
+ break;
+ }
+}
+
+static uint64_t rocker_port_phys_link_status(Rocker *r)
+{
+ int i;
+ uint64_t status = 0;
+
+ for (i = 0; i < r->fp_ports; i++) {
+ FpPort *port = r->fp_port[i];
+
+ if (fp_port_get_link_up(port)) {
+ status |= 1 << (i + 1);
+ }
+ }
+ return status;
+}
+
+static uint64_t rocker_port_phys_enable_read(Rocker *r)
+{
+ int i;
+ uint64_t ret = 0;
+
+ for (i = 0; i < r->fp_ports; i++) {
+ FpPort *port = r->fp_port[i];
+
+ if (fp_port_enabled(port)) {
+ ret |= 1 << (i + 1);
+ }
+ }
+ return ret;
+}
+
+static uint32_t rocker_io_readl(void *opaque, hwaddr addr)
+{
+ Rocker *r = opaque;
+ uint32_t ret;
+
+ if (rocker_addr_is_desc_reg(r, addr)) {
+ unsigned index = ROCKER_RING_INDEX(addr);
+ unsigned offset = addr & ROCKER_DMA_DESC_MASK;
+
+ switch (offset) {
+ case ROCKER_DMA_DESC_ADDR_OFFSET:
+ ret = (uint32_t)desc_ring_get_base_addr(r->rings[index]);
+ break;
+ case ROCKER_DMA_DESC_ADDR_OFFSET + 4:
+ ret = (uint32_t)(desc_ring_get_base_addr(r->rings[index]) >> 32);
+ break;
+ case ROCKER_DMA_DESC_SIZE_OFFSET:
+ ret = desc_ring_get_size(r->rings[index]);
+ break;
+ case ROCKER_DMA_DESC_HEAD_OFFSET:
+ ret = desc_ring_get_head(r->rings[index]);
+ break;
+ case ROCKER_DMA_DESC_TAIL_OFFSET:
+ ret = desc_ring_get_tail(r->rings[index]);
+ break;
+ case ROCKER_DMA_DESC_CREDITS_OFFSET:
+ ret = desc_ring_get_credits(r->rings[index]);
+ break;
+ default:
+ DPRINTF("not implemented dma reg read(l) addr=0x" TARGET_FMT_plx
+ " (ring %d, addr=0x%02x)\n", addr, index, offset);
+ ret = 0;
+ break;
+ }
+ return ret;
+ }
+
+ switch (addr) {
+ case ROCKER_BOGUS_REG0:
+ case ROCKER_BOGUS_REG1:
+ case ROCKER_BOGUS_REG2:
+ case ROCKER_BOGUS_REG3:
+ ret = 0xDEADBABE;
+ break;
+ case ROCKER_TEST_REG:
+ ret = r->test_reg * 2;
+ break;
+ case ROCKER_TEST_REG64:
+ ret = (uint32_t)(r->test_reg64 * 2);
+ break;
+ case ROCKER_TEST_REG64 + 4:
+ ret = (uint32_t)((r->test_reg64 * 2) >> 32);
+ break;
+ case ROCKER_TEST_DMA_SIZE:
+ ret = r->test_dma_size;
+ break;
+ case ROCKER_TEST_DMA_ADDR:
+ ret = (uint32_t)r->test_dma_addr;
+ break;
+ case ROCKER_TEST_DMA_ADDR + 4:
+ ret = (uint32_t)(r->test_dma_addr >> 32);
+ break;
+ case ROCKER_PORT_PHYS_COUNT:
+ ret = r->fp_ports;
+ break;
+ case ROCKER_PORT_PHYS_LINK_STATUS:
+ ret = (uint32_t)rocker_port_phys_link_status(r);
+ break;
+ case ROCKER_PORT_PHYS_LINK_STATUS + 4:
+ ret = (uint32_t)(rocker_port_phys_link_status(r) >> 32);
+ break;
+ case ROCKER_PORT_PHYS_ENABLE:
+ ret = (uint32_t)rocker_port_phys_enable_read(r);
+ break;
+ case ROCKER_PORT_PHYS_ENABLE + 4:
+ ret = (uint32_t)(rocker_port_phys_enable_read(r) >> 32);
+ break;
+ case ROCKER_SWITCH_ID:
+ ret = (uint32_t)r->switch_id;
+ break;
+ case ROCKER_SWITCH_ID + 4:
+ ret = (uint32_t)(r->switch_id >> 32);
+ break;
+ default:
+ DPRINTF("not implemented read(l) addr=0x" TARGET_FMT_plx "\n", addr);
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static uint64_t rocker_io_readq(void *opaque, hwaddr addr)
+{
+ Rocker *r = opaque;
+ uint64_t ret;
+
+ if (rocker_addr_is_desc_reg(r, addr)) {
+ unsigned index = ROCKER_RING_INDEX(addr);
+ unsigned offset = addr & ROCKER_DMA_DESC_MASK;
+
+ switch (addr & ROCKER_DMA_DESC_MASK) {
+ case ROCKER_DMA_DESC_ADDR_OFFSET:
+ ret = desc_ring_get_base_addr(r->rings[index]);
+ break;
+ default:
+ DPRINTF("not implemented dma reg read(q) addr=0x" TARGET_FMT_plx
+ " (ring %d, addr=0x%02x)\n", addr, index, offset);
+ ret = 0;
+ break;
+ }
+ return ret;
+ }
+
+ switch (addr) {
+ case ROCKER_BOGUS_REG0:
+ case ROCKER_BOGUS_REG2:
+ ret = 0xDEADBABEDEADBABEULL;
+ break;
+ case ROCKER_TEST_REG64:
+ ret = r->test_reg64 * 2;
+ break;
+ case ROCKER_TEST_DMA_ADDR:
+ ret = r->test_dma_addr;
+ break;
+ case ROCKER_PORT_PHYS_LINK_STATUS:
+ ret = rocker_port_phys_link_status(r);
+ break;
+ case ROCKER_PORT_PHYS_ENABLE:
+ ret = rocker_port_phys_enable_read(r);
+ break;
+ case ROCKER_SWITCH_ID:
+ ret = r->switch_id;
+ break;
+ default:
+ DPRINTF("not implemented read(q) addr=0x" TARGET_FMT_plx "\n", addr);
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static uint64_t rocker_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ DPRINTF("Read %s addr " TARGET_FMT_plx ", size %u\n",
+ rocker_reg_name(opaque, addr), addr, size);
+
+ switch (size) {
+ case 4:
+ return rocker_io_readl(opaque, addr);
+ case 8:
+ return rocker_io_readq(opaque, addr);
+ }
+
+ return -1;
+}
+
+static const MemoryRegionOps rocker_mmio_ops = {
+ .read = rocker_mmio_read,
+ .write = rocker_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static void rocker_msix_vectors_unuse(Rocker *r,
+ unsigned int num_vectors)
+{
+ PCIDevice *dev = PCI_DEVICE(r);
+ int i;
+
+ for (i = 0; i < num_vectors; i++) {
+ msix_vector_unuse(dev, i);
+ }
+}
+
+static int rocker_msix_vectors_use(Rocker *r,
+ unsigned int num_vectors)
+{
+ PCIDevice *dev = PCI_DEVICE(r);
+ int err;
+ int i;
+
+ for (i = 0; i < num_vectors; i++) {
+ err = msix_vector_use(dev, i);
+ if (err) {
+ goto rollback;
+ }
+ }
+ return 0;
+
+rollback:
+ rocker_msix_vectors_unuse(r, i);
+ return err;
+}
+
+static int rocker_msix_init(Rocker *r)
+{
+ PCIDevice *dev = PCI_DEVICE(r);
+ int err;
+
+ err = msix_init(dev, ROCKER_MSIX_VEC_COUNT(r->fp_ports),
+ &r->msix_bar,
+ ROCKER_PCI_MSIX_BAR_IDX, ROCKER_PCI_MSIX_TABLE_OFFSET,
+ &r->msix_bar,
+ ROCKER_PCI_MSIX_BAR_IDX, ROCKER_PCI_MSIX_PBA_OFFSET,
+ 0);
+ if (err) {
+ return err;
+ }
+
+ err = rocker_msix_vectors_use(r, ROCKER_MSIX_VEC_COUNT(r->fp_ports));
+ if (err) {
+ goto err_msix_vectors_use;
+ }
+
+ return 0;
+
+err_msix_vectors_use:
+ msix_uninit(dev, &r->msix_bar, &r->msix_bar);
+ return err;
+}
+
+static void rocker_msix_uninit(Rocker *r)
+{
+ PCIDevice *dev = PCI_DEVICE(r);
+
+ msix_uninit(dev, &r->msix_bar, &r->msix_bar);
+ rocker_msix_vectors_unuse(r, ROCKER_MSIX_VEC_COUNT(r->fp_ports));
+}
+
+static int pci_rocker_init(PCIDevice *dev)
+{
+ Rocker *r = to_rocker(dev);
+ const MACAddr zero = { .a = { 0, 0, 0, 0, 0, 0 } };
+ const MACAddr dflt = { .a = { 0x52, 0x54, 0x00, 0x12, 0x35, 0x01 } };
+ static int sw_index;
+ int i, err = 0;
+
+ /* allocate worlds */
+
+ r->worlds[ROCKER_WORLD_TYPE_OF_DPA] = of_dpa_world_alloc(r);
+ r->world_dflt = r->worlds[ROCKER_WORLD_TYPE_OF_DPA];
+
+ for (i = 0; i < ROCKER_WORLD_TYPE_MAX; i++) {
+ if (!r->worlds[i]) {
+ goto err_world_alloc;
+ }
+ }
+
+ /* set up memory-mapped region at BAR0 */
+
+ memory_region_init_io(&r->mmio, OBJECT(r), &rocker_mmio_ops, r,
+ "rocker-mmio", ROCKER_PCI_BAR0_SIZE);
+ pci_register_bar(dev, ROCKER_PCI_BAR0_IDX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &r->mmio);
+
+ /* set up memory-mapped region for MSI-X */
+
+ memory_region_init(&r->msix_bar, OBJECT(r), "rocker-msix-bar",
+ ROCKER_PCI_MSIX_BAR_SIZE);
+ pci_register_bar(dev, ROCKER_PCI_MSIX_BAR_IDX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &r->msix_bar);
+
+ /* MSI-X init */
+
+ err = rocker_msix_init(r);
+ if (err) {
+ goto err_msix_init;
+ }
+
+ /* validate switch properties */
+
+ if (!r->name) {
+ r->name = g_strdup(ROCKER);
+ }
+
+ if (rocker_find(r->name)) {
+ err = -EEXIST;
+ goto err_duplicate;
+ }
+
+ /* Rocker name is passed in port name requests to OS with the intention
+ * that the name is used in interface names. Limit the length of the
+ * rocker name to avoid naming problems in the OS. Also, adding the
+ * port number as p# and unganged breakout b#, where # is at most 2
+ * digits, so leave room for it too (-1 for string terminator, -3 for
+ * p# and -3 for b#)
+ */
+#define ROCKER_IFNAMSIZ 16
+#define MAX_ROCKER_NAME_LEN (ROCKER_IFNAMSIZ - 1 - 3 - 3)
+ if (strlen(r->name) > MAX_ROCKER_NAME_LEN) {
+ fprintf(stderr,
+ "rocker: name too long; please shorten to at most %d chars\n",
+ MAX_ROCKER_NAME_LEN);
+ return -EINVAL;
+ }
+
+ if (memcmp(&r->fp_start_macaddr, &zero, sizeof(zero)) == 0) {
+ memcpy(&r->fp_start_macaddr, &dflt, sizeof(dflt));
+ r->fp_start_macaddr.a[4] += (sw_index++);
+ }
+
+ if (!r->switch_id) {
+ memcpy(&r->switch_id, &r->fp_start_macaddr,
+ sizeof(r->fp_start_macaddr));
+ }
+
+ if (r->fp_ports > ROCKER_FP_PORTS_MAX) {
+ r->fp_ports = ROCKER_FP_PORTS_MAX;
+ }
+
+ r->rings = g_malloc(sizeof(DescRing *) * rocker_pci_ring_count(r));
+ if (!r->rings) {
+ goto err_rings_alloc;
+ }
+
+ /* Rings are ordered like this:
+ * - command ring
+ * - event ring
+ * - port0 tx ring
+ * - port0 rx ring
+ * - port1 tx ring
+ * - port1 rx ring
+ * .....
+ */
+
+ err = -ENOMEM;
+ for (i = 0; i < rocker_pci_ring_count(r); i++) {
+ DescRing *ring = desc_ring_alloc(r, i);
+
+ if (!ring) {
+ goto err_ring_alloc;
+ }
+
+ if (i == ROCKER_RING_CMD) {
+ desc_ring_set_consume(ring, cmd_consume, ROCKER_MSIX_VEC_CMD);
+ } else if (i == ROCKER_RING_EVENT) {
+ desc_ring_set_consume(ring, NULL, ROCKER_MSIX_VEC_EVENT);
+ } else if (i % 2 == 0) {
+ desc_ring_set_consume(ring, tx_consume,
+ ROCKER_MSIX_VEC_TX((i - 2) / 2));
+ } else if (i % 2 == 1) {
+ desc_ring_set_consume(ring, NULL, ROCKER_MSIX_VEC_RX((i - 3) / 2));
+ }
+
+ r->rings[i] = ring;
+ }
+
+ for (i = 0; i < r->fp_ports; i++) {
+ FpPort *port =
+ fp_port_alloc(r, r->name, &r->fp_start_macaddr,
+ i, &r->fp_ports_peers[i]);
+
+ if (!port) {
+ goto err_port_alloc;
+ }
+
+ r->fp_port[i] = port;
+ fp_port_set_world(port, r->world_dflt);
+ }
+
+ QLIST_INSERT_HEAD(&rockers, r, next);
+
+ return 0;
+
+err_port_alloc:
+ for (--i; i >= 0; i--) {
+ FpPort *port = r->fp_port[i];
+ fp_port_free(port);
+ }
+ i = rocker_pci_ring_count(r);
+err_ring_alloc:
+ for (--i; i >= 0; i--) {
+ desc_ring_free(r->rings[i]);
+ }
+ g_free(r->rings);
+err_rings_alloc:
+err_duplicate:
+ rocker_msix_uninit(r);
+err_msix_init:
+ object_unparent(OBJECT(&r->msix_bar));
+ object_unparent(OBJECT(&r->mmio));
+err_world_alloc:
+ for (i = 0; i < ROCKER_WORLD_TYPE_MAX; i++) {
+ if (r->worlds[i]) {
+ world_free(r->worlds[i]);
+ }
+ }
+ return err;
+}
+
+static void pci_rocker_uninit(PCIDevice *dev)
+{
+ Rocker *r = to_rocker(dev);
+ int i;
+
+ QLIST_REMOVE(r, next);
+
+ for (i = 0; i < r->fp_ports; i++) {
+ FpPort *port = r->fp_port[i];
+
+ fp_port_free(port);
+ r->fp_port[i] = NULL;
+ }
+
+ for (i = 0; i < rocker_pci_ring_count(r); i++) {
+ if (r->rings[i]) {
+ desc_ring_free(r->rings[i]);
+ }
+ }
+ g_free(r->rings);
+
+ rocker_msix_uninit(r);
+ object_unparent(OBJECT(&r->msix_bar));
+ object_unparent(OBJECT(&r->mmio));
+
+ for (i = 0; i < ROCKER_WORLD_TYPE_MAX; i++) {
+ if (r->worlds[i]) {
+ world_free(r->worlds[i]);
+ }
+ }
+ g_free(r->fp_ports_peers);
+}
+
+static void rocker_reset(DeviceState *dev)
+{
+ Rocker *r = to_rocker(dev);
+ int i;
+
+ for (i = 0; i < ROCKER_WORLD_TYPE_MAX; i++) {
+ if (r->worlds[i]) {
+ world_reset(r->worlds[i]);
+ }
+ }
+ for (i = 0; i < r->fp_ports; i++) {
+ fp_port_reset(r->fp_port[i]);
+ fp_port_set_world(r->fp_port[i], r->world_dflt);
+ }
+
+ r->test_reg = 0;
+ r->test_reg64 = 0;
+ r->test_dma_addr = 0;
+ r->test_dma_size = 0;
+
+ for (i = 0; i < rocker_pci_ring_count(r); i++) {
+ desc_ring_reset(r->rings[i]);
+ }
+
+ DPRINTF("Reset done\n");
+}
+
+static Property rocker_properties[] = {
+ DEFINE_PROP_STRING("name", Rocker, name),
+ DEFINE_PROP_MACADDR("fp_start_macaddr", Rocker,
+ fp_start_macaddr),
+ DEFINE_PROP_UINT64("switch_id", Rocker,
+ switch_id, 0),
+ DEFINE_PROP_ARRAY("ports", Rocker, fp_ports,
+ fp_ports_peers, qdev_prop_netdev, NICPeers),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription rocker_vmsd = {
+ .name = ROCKER,
+ .unmigratable = 1,
+};
+
+static void rocker_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = pci_rocker_init;
+ k->exit = pci_rocker_uninit;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT;
+ k->device_id = PCI_DEVICE_ID_REDHAT_ROCKER;
+ k->revision = ROCKER_PCI_REVISION;
+ k->class_id = PCI_CLASS_NETWORK_OTHER;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->desc = "Rocker Switch";
+ dc->reset = rocker_reset;
+ dc->props = rocker_properties;
+ dc->vmsd = &rocker_vmsd;
+}
+
+static const TypeInfo rocker_info = {
+ .name = ROCKER,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(Rocker),
+ .class_init = rocker_class_init,
+};
+
+static void rocker_register_types(void)
+{
+ type_register_static(&rocker_info);
+}
+
+type_init(rocker_register_types)
diff --git a/hw/net/rocker/rocker.h b/hw/net/rocker/rocker.h
new file mode 100644
index 00000000..f9c80f80
--- /dev/null
+++ b/hw/net/rocker/rocker.h
@@ -0,0 +1,84 @@
+/*
+ * QEMU rocker switch emulation
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us>
+ * Copyright (c) 2014 Neil Horman <nhorman@tuxdriver.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKER_H_
+#define _ROCKER_H_
+
+#include "qemu/sockets.h"
+
+#if defined(DEBUG_ROCKER)
+# define DPRINTF(fmt, ...) \
+ do { \
+ struct timeval tv; \
+ char timestr[64]; \
+ time_t now; \
+ gettimeofday(&tv, NULL); \
+ now = tv.tv_sec; \
+ strftime(timestr, sizeof(timestr), "%T", localtime(&now)); \
+ fprintf(stderr, "%s.%06ld ", timestr, tv.tv_usec); \
+ fprintf(stderr, "ROCKER: " fmt, ## __VA_ARGS__); \
+ } while (0)
+#else
+static inline GCC_FMT_ATTR(1, 2) int DPRINTF(const char *fmt, ...)
+{
+ return 0;
+}
+#endif
+
+#define __le16 uint16_t
+#define __le32 uint32_t
+#define __le64 uint64_t
+
+#define __be16 uint16_t
+#define __be32 uint32_t
+#define __be64 uint64_t
+
+static inline bool ipv4_addr_is_multicast(__be32 addr)
+{
+ return (addr & htonl(0xf0000000)) == htonl(0xe0000000);
+}
+
+typedef struct ipv6_addr {
+ union {
+ uint8_t addr8[16];
+ __be16 addr16[8];
+ __be32 addr32[4];
+ };
+} Ipv6Addr;
+
+static inline bool ipv6_addr_is_multicast(const Ipv6Addr *addr)
+{
+ return (addr->addr32[0] & htonl(0xFF000000)) == htonl(0xFF000000);
+}
+
+typedef struct rocker Rocker;
+typedef struct world World;
+typedef struct desc_info DescInfo;
+typedef struct desc_ring DescRing;
+
+Rocker *rocker_find(const char *name);
+uint32_t rocker_fp_ports(Rocker *r);
+int rocker_event_link_changed(Rocker *r, uint32_t pport, bool link_up);
+int rocker_event_mac_vlan_seen(Rocker *r, uint32_t pport, uint8_t *addr,
+ uint16_t vlan_id);
+int rx_produce(World *world, uint32_t pport,
+ const struct iovec *iov, int iovcnt, uint8_t copy_to_cpu);
+int rocker_port_eg(Rocker *r, uint32_t pport,
+ const struct iovec *iov, int iovcnt);
+
+#endif /* _ROCKER_H_ */
diff --git a/hw/net/rocker/rocker_desc.c b/hw/net/rocker/rocker_desc.c
new file mode 100644
index 00000000..9d896fe4
--- /dev/null
+++ b/hw/net/rocker/rocker_desc.c
@@ -0,0 +1,377 @@
+/*
+ * QEMU rocker switch emulation - Descriptor ring support
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "net/net.h"
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+
+#include "rocker.h"
+#include "rocker_hw.h"
+#include "rocker_desc.h"
+
+struct desc_ring {
+ hwaddr base_addr;
+ uint32_t size;
+ uint32_t head;
+ uint32_t tail;
+ uint32_t ctrl;
+ uint32_t credits;
+ Rocker *r;
+ DescInfo *info;
+ int index;
+ desc_ring_consume *consume;
+ unsigned msix_vector;
+};
+
+struct desc_info {
+ DescRing *ring;
+ RockerDesc desc;
+ char *buf;
+ size_t buf_size;
+};
+
+uint16_t desc_buf_size(DescInfo *info)
+{
+ return le16_to_cpu(info->desc.buf_size);
+}
+
+uint16_t desc_tlv_size(DescInfo *info)
+{
+ return le16_to_cpu(info->desc.tlv_size);
+}
+
+char *desc_get_buf(DescInfo *info, bool read_only)
+{
+ PCIDevice *dev = PCI_DEVICE(info->ring->r);
+ size_t size = read_only ? le16_to_cpu(info->desc.tlv_size) :
+ le16_to_cpu(info->desc.buf_size);
+
+ if (size > info->buf_size) {
+ info->buf = g_realloc(info->buf, size);
+ info->buf_size = size;
+ }
+
+ if (!info->buf) {
+ return NULL;
+ }
+
+ if (pci_dma_read(dev, le64_to_cpu(info->desc.buf_addr), info->buf, size)) {
+ return NULL;
+ }
+
+ return info->buf;
+}
+
+int desc_set_buf(DescInfo *info, size_t tlv_size)
+{
+ PCIDevice *dev = PCI_DEVICE(info->ring->r);
+
+ if (tlv_size > info->buf_size) {
+ DPRINTF("ERROR: trying to write more to desc buf than it "
+ "can hold buf_size %zu tlv_size %zu\n",
+ info->buf_size, tlv_size);
+ return -ROCKER_EMSGSIZE;
+ }
+
+ info->desc.tlv_size = cpu_to_le16(tlv_size);
+ pci_dma_write(dev, le64_to_cpu(info->desc.buf_addr), info->buf, tlv_size);
+
+ return ROCKER_OK;
+}
+
+DescRing *desc_get_ring(DescInfo *info)
+{
+ return info->ring;
+}
+
+int desc_ring_index(DescRing *ring)
+{
+ return ring->index;
+}
+
+static bool desc_ring_empty(DescRing *ring)
+{
+ return ring->head == ring->tail;
+}
+
+bool desc_ring_set_base_addr(DescRing *ring, uint64_t base_addr)
+{
+ if (base_addr & 0x7) {
+ DPRINTF("ERROR: ring[%d] desc base addr (0x" TARGET_FMT_plx
+ ") not 8-byte aligned\n", ring->index, base_addr);
+ return false;
+ }
+
+ ring->base_addr = base_addr;
+
+ return true;
+}
+
+uint64_t desc_ring_get_base_addr(DescRing *ring)
+{
+ return ring->base_addr;
+}
+
+bool desc_ring_set_size(DescRing *ring, uint32_t size)
+{
+ int i;
+
+ if (size < 2 || size > 0x10000 || (size & (size - 1))) {
+ DPRINTF("ERROR: ring[%d] size (%d) not a power of 2 "
+ "or in range [2, 64K]\n", ring->index, size);
+ return false;
+ }
+
+ for (i = 0; i < ring->size; i++) {
+ if (ring->info[i].buf) {
+ g_free(ring->info[i].buf);
+ }
+ }
+
+ ring->size = size;
+ ring->head = ring->tail = 0;
+
+ ring->info = g_realloc(ring->info, size * sizeof(DescInfo));
+ if (!ring->info) {
+ return false;
+ }
+
+ memset(ring->info, 0, size * sizeof(DescInfo));
+
+ for (i = 0; i < size; i++) {
+ ring->info[i].ring = ring;
+ }
+
+ return true;
+}
+
+uint32_t desc_ring_get_size(DescRing *ring)
+{
+ return ring->size;
+}
+
+static DescInfo *desc_read(DescRing *ring, uint32_t index)
+{
+ PCIDevice *dev = PCI_DEVICE(ring->r);
+ DescInfo *info = &ring->info[index];
+ hwaddr addr = ring->base_addr + (sizeof(RockerDesc) * index);
+
+ pci_dma_read(dev, addr, &info->desc, sizeof(info->desc));
+
+ return info;
+}
+
+static void desc_write(DescRing *ring, uint32_t index)
+{
+ PCIDevice *dev = PCI_DEVICE(ring->r);
+ DescInfo *info = &ring->info[index];
+ hwaddr addr = ring->base_addr + (sizeof(RockerDesc) * index);
+
+ pci_dma_write(dev, addr, &info->desc, sizeof(info->desc));
+}
+
+static bool desc_ring_base_addr_check(DescRing *ring)
+{
+ if (!ring->base_addr) {
+ DPRINTF("ERROR: ring[%d] not-initialized desc base address!\n",
+ ring->index);
+ return false;
+ }
+ return true;
+}
+
+static DescInfo *__desc_ring_fetch_desc(DescRing *ring)
+{
+ return desc_read(ring, ring->tail);
+}
+
+DescInfo *desc_ring_fetch_desc(DescRing *ring)
+{
+ if (desc_ring_empty(ring) || !desc_ring_base_addr_check(ring)) {
+ return NULL;
+ }
+
+ return desc_read(ring, ring->tail);
+}
+
+static bool __desc_ring_post_desc(DescRing *ring, int err)
+{
+ uint16_t comp_err = 0x8000 | (uint16_t)-err;
+ DescInfo *info = &ring->info[ring->tail];
+
+ info->desc.comp_err = cpu_to_le16(comp_err);
+ desc_write(ring, ring->tail);
+ ring->tail = (ring->tail + 1) % ring->size;
+
+ /* return true if starting credit count */
+
+ return ring->credits++ == 0;
+}
+
+bool desc_ring_post_desc(DescRing *ring, int err)
+{
+ if (desc_ring_empty(ring)) {
+ DPRINTF("ERROR: ring[%d] trying to post desc to empty ring\n",
+ ring->index);
+ return false;
+ }
+
+ if (!desc_ring_base_addr_check(ring)) {
+ return false;
+ }
+
+ return __desc_ring_post_desc(ring, err);
+}
+
+static bool ring_pump(DescRing *ring)
+{
+ DescInfo *info;
+ bool primed = false;
+ int err;
+
+ /* If the ring has a consumer, call consumer for each
+ * desc starting at tail and stopping when tail reaches
+ * head (the empty ring condition).
+ */
+
+ if (ring->consume) {
+ while (ring->head != ring->tail) {
+ info = __desc_ring_fetch_desc(ring);
+ err = ring->consume(ring->r, info);
+ if (__desc_ring_post_desc(ring, err)) {
+ primed = true;
+ }
+ }
+ }
+
+ return primed;
+}
+
+bool desc_ring_set_head(DescRing *ring, uint32_t new)
+{
+ uint32_t tail = ring->tail;
+ uint32_t head = ring->head;
+
+ if (!desc_ring_base_addr_check(ring)) {
+ return false;
+ }
+
+ if (new >= ring->size) {
+ DPRINTF("ERROR: trying to set head (%d) past ring[%d] size (%d)\n",
+ new, ring->index, ring->size);
+ return false;
+ }
+
+ if (((head < tail) && ((new >= tail) || (new < head))) ||
+ ((head > tail) && ((new >= tail) && (new < head)))) {
+ DPRINTF("ERROR: trying to wrap ring[%d] "
+ "(head %d, tail %d, new head %d)\n",
+ ring->index, head, tail, new);
+ return false;
+ }
+
+ if (new == ring->head) {
+ DPRINTF("WARNING: setting head (%d) to current head position\n", new);
+ }
+
+ ring->head = new;
+
+ return ring_pump(ring);
+}
+
+uint32_t desc_ring_get_head(DescRing *ring)
+{
+ return ring->head;
+}
+
+uint32_t desc_ring_get_tail(DescRing *ring)
+{
+ return ring->tail;
+}
+
+void desc_ring_set_ctrl(DescRing *ring, uint32_t val)
+{
+ if (val & ROCKER_DMA_DESC_CTRL_RESET) {
+ DPRINTF("ring[%d] resetting\n", ring->index);
+ desc_ring_reset(ring);
+ }
+}
+
+bool desc_ring_ret_credits(DescRing *ring, uint32_t credits)
+{
+ if (credits > ring->credits) {
+ DPRINTF("ERROR: trying to return more credits (%d) "
+ "than are outstanding (%d)\n", credits, ring->credits);
+ ring->credits = 0;
+ return false;
+ }
+
+ ring->credits -= credits;
+
+ /* return true if credits are still outstanding */
+
+ return ring->credits > 0;
+}
+
+uint32_t desc_ring_get_credits(DescRing *ring)
+{
+ return ring->credits;
+}
+
+void desc_ring_set_consume(DescRing *ring, desc_ring_consume *consume,
+ unsigned vector)
+{
+ ring->consume = consume;
+ ring->msix_vector = vector;
+}
+
+unsigned desc_ring_get_msix_vector(DescRing *ring)
+{
+ return ring->msix_vector;
+}
+
+DescRing *desc_ring_alloc(Rocker *r, int index)
+{
+ DescRing *ring;
+
+ ring = g_malloc0(sizeof(DescRing));
+ if (!ring) {
+ return NULL;
+ }
+
+ ring->r = r;
+ ring->index = index;
+
+ return ring;
+}
+
+void desc_ring_free(DescRing *ring)
+{
+ if (ring->info) {
+ g_free(ring->info);
+ }
+ g_free(ring);
+}
+
+void desc_ring_reset(DescRing *ring)
+{
+ ring->base_addr = 0;
+ ring->size = 0;
+ ring->head = 0;
+ ring->tail = 0;
+ ring->ctrl = 0;
+ ring->credits = 0;
+}
diff --git a/hw/net/rocker/rocker_desc.h b/hw/net/rocker/rocker_desc.h
new file mode 100644
index 00000000..d4041f5c
--- /dev/null
+++ b/hw/net/rocker/rocker_desc.h
@@ -0,0 +1,53 @@
+/*
+ * QEMU rocker switch emulation - Descriptor ring support
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+
+#ifndef _ROCKER_DESC_H_
+#define _ROCKER_DESC_H_
+
+#include "rocker_hw.h"
+
+typedef int (desc_ring_consume)(Rocker *r, DescInfo *info);
+
+uint16_t desc_buf_size(DescInfo *info);
+uint16_t desc_tlv_size(DescInfo *info);
+char *desc_get_buf(DescInfo *info, bool read_only);
+int desc_set_buf(DescInfo *info, size_t tlv_size);
+DescRing *desc_get_ring(DescInfo *info);
+
+int desc_ring_index(DescRing *ring);
+bool desc_ring_set_base_addr(DescRing *ring, uint64_t base_addr);
+uint64_t desc_ring_get_base_addr(DescRing *ring);
+bool desc_ring_set_size(DescRing *ring, uint32_t size);
+uint32_t desc_ring_get_size(DescRing *ring);
+bool desc_ring_set_head(DescRing *ring, uint32_t new);
+uint32_t desc_ring_get_head(DescRing *ring);
+uint32_t desc_ring_get_tail(DescRing *ring);
+void desc_ring_set_ctrl(DescRing *ring, uint32_t val);
+bool desc_ring_ret_credits(DescRing *ring, uint32_t credits);
+uint32_t desc_ring_get_credits(DescRing *ring);
+
+DescInfo *desc_ring_fetch_desc(DescRing *ring);
+bool desc_ring_post_desc(DescRing *ring, int status);
+
+void desc_ring_set_consume(DescRing *ring, desc_ring_consume *consume,
+ unsigned vector);
+unsigned desc_ring_get_msix_vector(DescRing *ring);
+DescRing *desc_ring_alloc(Rocker *r, int index);
+void desc_ring_free(DescRing *ring);
+void desc_ring_reset(DescRing *ring);
+
+#endif
diff --git a/hw/net/rocker/rocker_fp.c b/hw/net/rocker/rocker_fp.c
new file mode 100644
index 00000000..c693ae50
--- /dev/null
+++ b/hw/net/rocker/rocker_fp.c
@@ -0,0 +1,263 @@
+/*
+ * QEMU rocker switch emulation - front-panel ports
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "net/clients.h"
+
+#include "rocker.h"
+#include "rocker_hw.h"
+#include "rocker_fp.h"
+#include "rocker_world.h"
+
+enum duplex {
+ DUPLEX_HALF = 0,
+ DUPLEX_FULL
+};
+
+struct fp_port {
+ Rocker *r;
+ World *world;
+ unsigned int index;
+ char *name;
+ uint32_t pport;
+ bool enabled;
+ uint32_t speed;
+ uint8_t duplex;
+ uint8_t autoneg;
+ uint8_t learning;
+ NICState *nic;
+ NICConf conf;
+};
+
+char *fp_port_get_name(FpPort *port)
+{
+ return port->name;
+}
+
+bool fp_port_get_link_up(FpPort *port)
+{
+ return !qemu_get_queue(port->nic)->link_down;
+}
+
+void fp_port_get_info(FpPort *port, RockerPortList *info)
+{
+ info->value->name = g_strdup(port->name);
+ info->value->enabled = port->enabled;
+ info->value->link_up = fp_port_get_link_up(port);
+ info->value->speed = port->speed;
+ info->value->duplex = port->duplex;
+ info->value->autoneg = port->autoneg;
+}
+
+void fp_port_get_macaddr(FpPort *port, MACAddr *macaddr)
+{
+ memcpy(macaddr->a, port->conf.macaddr.a, sizeof(macaddr->a));
+}
+
+void fp_port_set_macaddr(FpPort *port, MACAddr *macaddr)
+{
+/* XXX TODO implement and test setting mac addr
+ * XXX memcpy(port->conf.macaddr.a, macaddr.a, sizeof(port->conf.macaddr.a));
+ */
+}
+
+uint8_t fp_port_get_learning(FpPort *port)
+{
+ return port->learning;
+}
+
+void fp_port_set_learning(FpPort *port, uint8_t learning)
+{
+ port->learning = learning;
+}
+
+int fp_port_get_settings(FpPort *port, uint32_t *speed,
+ uint8_t *duplex, uint8_t *autoneg)
+{
+ *speed = port->speed;
+ *duplex = port->duplex;
+ *autoneg = port->autoneg;
+
+ return ROCKER_OK;
+}
+
+int fp_port_set_settings(FpPort *port, uint32_t speed,
+ uint8_t duplex, uint8_t autoneg)
+{
+ /* XXX validate inputs */
+
+ port->speed = speed;
+ port->duplex = duplex;
+ port->autoneg = autoneg;
+
+ return ROCKER_OK;
+}
+
+bool fp_port_from_pport(uint32_t pport, uint32_t *port)
+{
+ if (pport < 1 || pport > ROCKER_FP_PORTS_MAX) {
+ return false;
+ }
+ *port = pport - 1;
+ return true;
+}
+
+int fp_port_eg(FpPort *port, const struct iovec *iov, int iovcnt)
+{
+ NetClientState *nc = qemu_get_queue(port->nic);
+
+ if (port->enabled) {
+ qemu_sendv_packet(nc, iov, iovcnt);
+ }
+
+ return ROCKER_OK;
+}
+
+static ssize_t fp_port_receive_iov(NetClientState *nc, const struct iovec *iov,
+ int iovcnt)
+{
+ FpPort *port = qemu_get_nic_opaque(nc);
+
+ /* If the port is disabled, we want to drop this pkt
+ * now rather than queing it for later. We don't want
+ * any stale pkts getting into the device when the port
+ * transitions to enabled.
+ */
+
+ if (!port->enabled) {
+ return -1;
+ }
+
+ return world_ingress(port->world, port->pport, iov, iovcnt);
+}
+
+static ssize_t fp_port_receive(NetClientState *nc, const uint8_t *buf,
+ size_t size)
+{
+ const struct iovec iov = {
+ .iov_base = (uint8_t *)buf,
+ .iov_len = size
+ };
+
+ return fp_port_receive_iov(nc, &iov, 1);
+}
+
+static void fp_port_cleanup(NetClientState *nc)
+{
+}
+
+static void fp_port_set_link_status(NetClientState *nc)
+{
+ FpPort *port = qemu_get_nic_opaque(nc);
+
+ rocker_event_link_changed(port->r, port->pport, !nc->link_down);
+}
+
+static NetClientInfo fp_port_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = fp_port_receive,
+ .receive_iov = fp_port_receive_iov,
+ .cleanup = fp_port_cleanup,
+ .link_status_changed = fp_port_set_link_status,
+};
+
+World *fp_port_get_world(FpPort *port)
+{
+ return port->world;
+}
+
+void fp_port_set_world(FpPort *port, World *world)
+{
+ DPRINTF("port %d setting world \"%s\"\n", port->index, world_name(world));
+ port->world = world;
+}
+
+bool fp_port_enabled(FpPort *port)
+{
+ return port->enabled;
+}
+
+static void fp_port_set_link(FpPort *port, bool up)
+{
+ NetClientState *nc = qemu_get_queue(port->nic);
+
+ if (up == nc->link_down) {
+ nc->link_down = !up;
+ nc->info->link_status_changed(nc);
+ }
+}
+
+void fp_port_enable(FpPort *port)
+{
+ fp_port_set_link(port, true);
+ port->enabled = true;
+ DPRINTF("port %d enabled\n", port->index);
+}
+
+void fp_port_disable(FpPort *port)
+{
+ port->enabled = false;
+ fp_port_set_link(port, false);
+ DPRINTF("port %d disabled\n", port->index);
+}
+
+FpPort *fp_port_alloc(Rocker *r, char *sw_name,
+ MACAddr *start_mac, unsigned int index,
+ NICPeers *peers)
+{
+ FpPort *port = g_malloc0(sizeof(FpPort));
+
+ if (!port) {
+ return NULL;
+ }
+
+ port->r = r;
+ port->index = index;
+ port->pport = index + 1;
+
+ /* front-panel switch port names are 1-based */
+
+ port->name = g_strdup_printf("%sp%d", sw_name, port->pport);
+
+ memcpy(port->conf.macaddr.a, start_mac, sizeof(port->conf.macaddr.a));
+ port->conf.macaddr.a[5] += index;
+ port->conf.bootindex = -1;
+ port->conf.peers = *peers;
+
+ port->nic = qemu_new_nic(&fp_port_info, &port->conf,
+ sw_name, NULL, port);
+ qemu_format_nic_info_str(qemu_get_queue(port->nic),
+ port->conf.macaddr.a);
+
+ fp_port_reset(port);
+
+ return port;
+}
+
+void fp_port_free(FpPort *port)
+{
+ qemu_del_nic(port->nic);
+ g_free(port->name);
+ g_free(port);
+}
+
+void fp_port_reset(FpPort *port)
+{
+ fp_port_disable(port);
+ port->speed = 10000; /* 10Gbps */
+ port->duplex = DUPLEX_FULL;
+ port->autoneg = 0;
+}
diff --git a/hw/net/rocker/rocker_fp.h b/hw/net/rocker/rocker_fp.h
new file mode 100644
index 00000000..ab80fd83
--- /dev/null
+++ b/hw/net/rocker/rocker_fp.h
@@ -0,0 +1,53 @@
+/*
+ * QEMU rocker switch emulation - front-panel ports
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKER_FP_H_
+#define _ROCKER_FP_H_
+
+#include "net/net.h"
+#include "qemu/iov.h"
+
+#define ROCKER_FP_PORTS_MAX 62
+
+typedef struct fp_port FpPort;
+
+int fp_port_eg(FpPort *port, const struct iovec *iov, int iovcnt);
+
+char *fp_port_get_name(FpPort *port);
+bool fp_port_get_link_up(FpPort *port);
+void fp_port_get_info(FpPort *port, RockerPortList *info);
+void fp_port_get_macaddr(FpPort *port, MACAddr *macaddr);
+void fp_port_set_macaddr(FpPort *port, MACAddr *macaddr);
+uint8_t fp_port_get_learning(FpPort *port);
+void fp_port_set_learning(FpPort *port, uint8_t learning);
+int fp_port_get_settings(FpPort *port, uint32_t *speed,
+ uint8_t *duplex, uint8_t *autoneg);
+int fp_port_set_settings(FpPort *port, uint32_t speed,
+ uint8_t duplex, uint8_t autoneg);
+bool fp_port_from_pport(uint32_t pport, uint32_t *port);
+World *fp_port_get_world(FpPort *port);
+void fp_port_set_world(FpPort *port, World *world);
+bool fp_port_enabled(FpPort *port);
+void fp_port_enable(FpPort *port);
+void fp_port_disable(FpPort *port);
+
+FpPort *fp_port_alloc(Rocker *r, char *sw_name,
+ MACAddr *start_mac, unsigned int index,
+ NICPeers *peers);
+void fp_port_free(FpPort *port);
+void fp_port_reset(FpPort *port);
+
+#endif /* _ROCKER_FP_H_ */
diff --git a/hw/net/rocker/rocker_hw.h b/hw/net/rocker/rocker_hw.h
new file mode 100644
index 00000000..8c508303
--- /dev/null
+++ b/hw/net/rocker/rocker_hw.h
@@ -0,0 +1,493 @@
+/*
+ * Rocker switch hardware register and descriptor definitions.
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us>
+ *
+ */
+
+#ifndef _ROCKER_HW_
+#define _ROCKER_HW_
+
+#define __le16 uint16_t
+#define __le32 uint32_t
+#define __le64 uint64_t
+
+/*
+ * Return codes
+ */
+
+enum {
+ ROCKER_OK = 0,
+ ROCKER_ENOENT = 2,
+ ROCKER_ENXIO = 6,
+ ROCKER_ENOMEM = 12,
+ ROCKER_EEXIST = 17,
+ ROCKER_EINVAL = 22,
+ ROCKER_EMSGSIZE = 90,
+ ROCKER_ENOTSUP = 95,
+ ROCKER_ENOBUFS = 105,
+};
+
+/*
+ * PCI configuration space
+ */
+
+#define ROCKER_PCI_REVISION 0x1
+#define ROCKER_PCI_BAR0_IDX 0
+#define ROCKER_PCI_BAR0_SIZE 0x2000
+#define ROCKER_PCI_MSIX_BAR_IDX 1
+#define ROCKER_PCI_MSIX_BAR_SIZE 0x2000
+#define ROCKER_PCI_MSIX_TABLE_OFFSET 0x0000
+#define ROCKER_PCI_MSIX_PBA_OFFSET 0x1000
+
+/*
+ * MSI-X vectors
+ */
+
+enum {
+ ROCKER_MSIX_VEC_CMD,
+ ROCKER_MSIX_VEC_EVENT,
+ ROCKER_MSIX_VEC_TEST,
+ ROCKER_MSIX_VEC_RESERVED0,
+ __ROCKER_MSIX_VEC_TX,
+ __ROCKER_MSIX_VEC_RX,
+#define ROCKER_MSIX_VEC_TX(port) \
+ (__ROCKER_MSIX_VEC_TX + ((port) * 2))
+#define ROCKER_MSIX_VEC_RX(port) \
+ (__ROCKER_MSIX_VEC_RX + ((port) * 2))
+#define ROCKER_MSIX_VEC_COUNT(portcnt) \
+ (ROCKER_MSIX_VEC_RX((portcnt) - 1) + 1)
+};
+
+/*
+ * Rocker bogus registers
+ */
+#define ROCKER_BOGUS_REG0 0x0000
+#define ROCKER_BOGUS_REG1 0x0004
+#define ROCKER_BOGUS_REG2 0x0008
+#define ROCKER_BOGUS_REG3 0x000c
+
+/*
+ * Rocker test registers
+ */
+#define ROCKER_TEST_REG 0x0010
+#define ROCKER_TEST_REG64 0x0018 /* 8-byte */
+#define ROCKER_TEST_IRQ 0x0020
+#define ROCKER_TEST_DMA_ADDR 0x0028 /* 8-byte */
+#define ROCKER_TEST_DMA_SIZE 0x0030
+#define ROCKER_TEST_DMA_CTRL 0x0034
+
+/*
+ * Rocker test register ctrl
+ */
+#define ROCKER_TEST_DMA_CTRL_CLEAR (1 << 0)
+#define ROCKER_TEST_DMA_CTRL_FILL (1 << 1)
+#define ROCKER_TEST_DMA_CTRL_INVERT (1 << 2)
+
+/*
+ * Rocker DMA ring register offsets
+ */
+#define ROCKER_DMA_DESC_BASE 0x1000
+#define ROCKER_DMA_DESC_SIZE 32
+#define ROCKER_DMA_DESC_MASK 0x1F
+#define ROCKER_DMA_DESC_TOTAL_SIZE \
+ (ROCKER_DMA_DESC_SIZE * 64) /* 62 ports + event + cmd */
+#define ROCKER_DMA_DESC_ADDR_OFFSET 0x00 /* 8-byte */
+#define ROCKER_DMA_DESC_SIZE_OFFSET 0x08
+#define ROCKER_DMA_DESC_HEAD_OFFSET 0x0c
+#define ROCKER_DMA_DESC_TAIL_OFFSET 0x10
+#define ROCKER_DMA_DESC_CTRL_OFFSET 0x14
+#define ROCKER_DMA_DESC_CREDITS_OFFSET 0x18
+#define ROCKER_DMA_DESC_RSVD_OFFSET 0x1c
+
+/*
+ * Rocker dma ctrl register bits
+ */
+#define ROCKER_DMA_DESC_CTRL_RESET (1 << 0)
+
+/*
+ * Rocker ring indices
+ */
+#define ROCKER_RING_CMD 0
+#define ROCKER_RING_EVENT 1
+
+/*
+ * Helper macro to do convert a dma ring register
+ * to its index. Based on the fact that the register
+ * group stride is 32 bytes.
+ */
+#define ROCKER_RING_INDEX(reg) ((reg >> 5) & 0x7F)
+
+/*
+ * Rocker DMA Descriptor
+ */
+
+typedef struct rocker_desc {
+ __le64 buf_addr;
+ uint64_t cookie;
+ __le16 buf_size;
+ __le16 tlv_size;
+ __le16 rsvd[5]; /* pad to 32 bytes */
+ __le16 comp_err;
+} __attribute__((packed, aligned(8))) RockerDesc;
+
+/*
+ * Rocker TLV type fields
+ */
+
+typedef struct rocker_tlv {
+ __le32 type;
+ __le16 len;
+ __le16 rsvd;
+} __attribute__((packed, aligned(8))) RockerTlv;
+
+/* cmd msg */
+enum {
+ ROCKER_TLV_CMD_UNSPEC,
+ ROCKER_TLV_CMD_TYPE, /* u16 */
+ ROCKER_TLV_CMD_INFO, /* nest */
+
+ __ROCKER_TLV_CMD_MAX,
+ ROCKER_TLV_CMD_MAX = __ROCKER_TLV_CMD_MAX - 1,
+};
+
+enum {
+ ROCKER_TLV_CMD_TYPE_UNSPEC,
+ ROCKER_TLV_CMD_TYPE_GET_PORT_SETTINGS,
+ ROCKER_TLV_CMD_TYPE_SET_PORT_SETTINGS,
+ ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD,
+ ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD,
+ ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL,
+ ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_GET_STATS,
+ ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD,
+ ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD,
+ ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL,
+ ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_GET_STATS,
+
+ __ROCKER_TLV_CMD_TYPE_MAX,
+ ROCKER_TLV_CMD_TYPE_MAX = __ROCKER_TLV_CMD_TYPE_MAX - 1,
+};
+
+/* cmd info nested for set/get port settings */
+enum {
+ ROCKER_TLV_CMD_PORT_SETTINGS_UNSPEC,
+ ROCKER_TLV_CMD_PORT_SETTINGS_PPORT, /* u32 */
+ ROCKER_TLV_CMD_PORT_SETTINGS_SPEED, /* u32 */
+ ROCKER_TLV_CMD_PORT_SETTINGS_DUPLEX, /* u8 */
+ ROCKER_TLV_CMD_PORT_SETTINGS_AUTONEG, /* u8 */
+ ROCKER_TLV_CMD_PORT_SETTINGS_MACADDR, /* binary */
+ ROCKER_TLV_CMD_PORT_SETTINGS_MODE, /* u8 */
+ ROCKER_TLV_CMD_PORT_SETTINGS_LEARNING, /* u8 */
+ ROCKER_TLV_CMD_PORT_SETTINGS_PHYS_NAME, /* binary */
+
+ __ROCKER_TLV_CMD_PORT_SETTINGS_MAX,
+ ROCKER_TLV_CMD_PORT_SETTINGS_MAX = __ROCKER_TLV_CMD_PORT_SETTINGS_MAX - 1,
+};
+
+enum {
+ ROCKER_PORT_MODE_OF_DPA,
+};
+
+/* event msg */
+enum {
+ ROCKER_TLV_EVENT_UNSPEC,
+ ROCKER_TLV_EVENT_TYPE, /* u16 */
+ ROCKER_TLV_EVENT_INFO, /* nest */
+
+ __ROCKER_TLV_EVENT_MAX,
+ ROCKER_TLV_EVENT_MAX = __ROCKER_TLV_EVENT_MAX - 1,
+};
+
+enum {
+ ROCKER_TLV_EVENT_TYPE_UNSPEC,
+ ROCKER_TLV_EVENT_TYPE_LINK_CHANGED,
+ ROCKER_TLV_EVENT_TYPE_MAC_VLAN_SEEN,
+
+ __ROCKER_TLV_EVENT_TYPE_MAX,
+ ROCKER_TLV_EVENT_TYPE_MAX = __ROCKER_TLV_EVENT_TYPE_MAX - 1,
+};
+
+/* event info nested for link changed */
+enum {
+ ROCKER_TLV_EVENT_LINK_CHANGED_UNSPEC,
+ ROCKER_TLV_EVENT_LINK_CHANGED_PPORT, /* u32 */
+ ROCKER_TLV_EVENT_LINK_CHANGED_LINKUP, /* u8 */
+
+ __ROCKER_TLV_EVENT_LINK_CHANGED_MAX,
+ ROCKER_TLV_EVENT_LINK_CHANGED_MAX = __ROCKER_TLV_EVENT_LINK_CHANGED_MAX - 1,
+};
+
+/* event info nested for MAC/VLAN */
+enum {
+ ROCKER_TLV_EVENT_MAC_VLAN_UNSPEC,
+ ROCKER_TLV_EVENT_MAC_VLAN_PPORT, /* u32 */
+ ROCKER_TLV_EVENT_MAC_VLAN_MAC, /* binary */
+ ROCKER_TLV_EVENT_MAC_VLAN_VLAN_ID, /* __be16 */
+
+ __ROCKER_TLV_EVENT_MAC_VLAN_MAX,
+ ROCKER_TLV_EVENT_MAC_VLAN_MAX = __ROCKER_TLV_EVENT_MAC_VLAN_MAX - 1,
+};
+
+/* Rx msg */
+enum {
+ ROCKER_TLV_RX_UNSPEC,
+ ROCKER_TLV_RX_FLAGS, /* u16, see RX_FLAGS_ */
+ ROCKER_TLV_RX_CSUM, /* u16 */
+ ROCKER_TLV_RX_FRAG_ADDR, /* u64 */
+ ROCKER_TLV_RX_FRAG_MAX_LEN, /* u16 */
+ ROCKER_TLV_RX_FRAG_LEN, /* u16 */
+
+ __ROCKER_TLV_RX_MAX,
+ ROCKER_TLV_RX_MAX = __ROCKER_TLV_RX_MAX - 1,
+};
+
+#define ROCKER_RX_FLAGS_IPV4 (1 << 0)
+#define ROCKER_RX_FLAGS_IPV6 (1 << 1)
+#define ROCKER_RX_FLAGS_CSUM_CALC (1 << 2)
+#define ROCKER_RX_FLAGS_IPV4_CSUM_GOOD (1 << 3)
+#define ROCKER_RX_FLAGS_IP_FRAG (1 << 4)
+#define ROCKER_RX_FLAGS_TCP (1 << 5)
+#define ROCKER_RX_FLAGS_UDP (1 << 6)
+#define ROCKER_RX_FLAGS_TCP_UDP_CSUM_GOOD (1 << 7)
+#define ROCKER_RX_FLAGS_FWD_OFFLOAD (1 << 8)
+
+/* Tx msg */
+enum {
+ ROCKER_TLV_TX_UNSPEC,
+ ROCKER_TLV_TX_OFFLOAD, /* u8, see TX_OFFLOAD_ */
+ ROCKER_TLV_TX_L3_CSUM_OFF, /* u16 */
+ ROCKER_TLV_TX_TSO_MSS, /* u16 */
+ ROCKER_TLV_TX_TSO_HDR_LEN, /* u16 */
+ ROCKER_TLV_TX_FRAGS, /* array */
+
+ __ROCKER_TLV_TX_MAX,
+ ROCKER_TLV_TX_MAX = __ROCKER_TLV_TX_MAX - 1,
+};
+
+#define ROCKER_TX_OFFLOAD_NONE 0
+#define ROCKER_TX_OFFLOAD_IP_CSUM 1
+#define ROCKER_TX_OFFLOAD_TCP_UDP_CSUM 2
+#define ROCKER_TX_OFFLOAD_L3_CSUM 3
+#define ROCKER_TX_OFFLOAD_TSO 4
+
+#define ROCKER_TX_FRAGS_MAX 16
+
+enum {
+ ROCKER_TLV_TX_FRAG_UNSPEC,
+ ROCKER_TLV_TX_FRAG, /* nest */
+
+ __ROCKER_TLV_TX_FRAG_MAX,
+ ROCKER_TLV_TX_FRAG_MAX = __ROCKER_TLV_TX_FRAG_MAX - 1,
+};
+
+enum {
+ ROCKER_TLV_TX_FRAG_ATTR_UNSPEC,
+ ROCKER_TLV_TX_FRAG_ATTR_ADDR, /* u64 */
+ ROCKER_TLV_TX_FRAG_ATTR_LEN, /* u16 */
+
+ __ROCKER_TLV_TX_FRAG_ATTR_MAX,
+ ROCKER_TLV_TX_FRAG_ATTR_MAX = __ROCKER_TLV_TX_FRAG_ATTR_MAX - 1,
+};
+
+/*
+ * cmd info nested for OF-DPA msgs
+ */
+
+enum {
+ ROCKER_TLV_OF_DPA_UNSPEC,
+ ROCKER_TLV_OF_DPA_TABLE_ID, /* u16 */
+ ROCKER_TLV_OF_DPA_PRIORITY, /* u32 */
+ ROCKER_TLV_OF_DPA_HARDTIME, /* u32 */
+ ROCKER_TLV_OF_DPA_IDLETIME, /* u32 */
+ ROCKER_TLV_OF_DPA_COOKIE, /* u64 */
+ ROCKER_TLV_OF_DPA_IN_PPORT, /* u32 */
+ ROCKER_TLV_OF_DPA_IN_PPORT_MASK, /* u32 */
+ ROCKER_TLV_OF_DPA_OUT_PPORT, /* u32 */
+ ROCKER_TLV_OF_DPA_GOTO_TABLE_ID, /* u16 */
+ ROCKER_TLV_OF_DPA_GROUP_ID, /* u32 */
+ ROCKER_TLV_OF_DPA_GROUP_ID_LOWER, /* u32 */
+ ROCKER_TLV_OF_DPA_GROUP_COUNT, /* u16 */
+ ROCKER_TLV_OF_DPA_GROUP_IDS, /* u32 array */
+ ROCKER_TLV_OF_DPA_VLAN_ID, /* __be16 */
+ ROCKER_TLV_OF_DPA_VLAN_ID_MASK, /* __be16 */
+ ROCKER_TLV_OF_DPA_VLAN_PCP, /* __be16 */
+ ROCKER_TLV_OF_DPA_VLAN_PCP_MASK, /* __be16 */
+ ROCKER_TLV_OF_DPA_VLAN_PCP_ACTION, /* u8 */
+ ROCKER_TLV_OF_DPA_NEW_VLAN_ID, /* __be16 */
+ ROCKER_TLV_OF_DPA_NEW_VLAN_PCP, /* u8 */
+ ROCKER_TLV_OF_DPA_TUNNEL_ID, /* u32 */
+ ROCKER_TLV_OF_DPA_TUNNEL_LPORT, /* u32 */
+ ROCKER_TLV_OF_DPA_ETHERTYPE, /* __be16 */
+ ROCKER_TLV_OF_DPA_DST_MAC, /* binary */
+ ROCKER_TLV_OF_DPA_DST_MAC_MASK, /* binary */
+ ROCKER_TLV_OF_DPA_SRC_MAC, /* binary */
+ ROCKER_TLV_OF_DPA_SRC_MAC_MASK, /* binary */
+ ROCKER_TLV_OF_DPA_IP_PROTO, /* u8 */
+ ROCKER_TLV_OF_DPA_IP_PROTO_MASK, /* u8 */
+ ROCKER_TLV_OF_DPA_IP_DSCP, /* u8 */
+ ROCKER_TLV_OF_DPA_IP_DSCP_MASK, /* u8 */
+ ROCKER_TLV_OF_DPA_IP_DSCP_ACTION, /* u8 */
+ ROCKER_TLV_OF_DPA_NEW_IP_DSCP, /* u8 */
+ ROCKER_TLV_OF_DPA_IP_ECN, /* u8 */
+ ROCKER_TLV_OF_DPA_IP_ECN_MASK, /* u8 */
+ ROCKER_TLV_OF_DPA_DST_IP, /* __be32 */
+ ROCKER_TLV_OF_DPA_DST_IP_MASK, /* __be32 */
+ ROCKER_TLV_OF_DPA_SRC_IP, /* __be32 */
+ ROCKER_TLV_OF_DPA_SRC_IP_MASK, /* __be32 */
+ ROCKER_TLV_OF_DPA_DST_IPV6, /* binary */
+ ROCKER_TLV_OF_DPA_DST_IPV6_MASK, /* binary */
+ ROCKER_TLV_OF_DPA_SRC_IPV6, /* binary */
+ ROCKER_TLV_OF_DPA_SRC_IPV6_MASK, /* binary */
+ ROCKER_TLV_OF_DPA_SRC_ARP_IP, /* __be32 */
+ ROCKER_TLV_OF_DPA_SRC_ARP_IP_MASK, /* __be32 */
+ ROCKER_TLV_OF_DPA_L4_DST_PORT, /* __be16 */
+ ROCKER_TLV_OF_DPA_L4_DST_PORT_MASK, /* __be16 */
+ ROCKER_TLV_OF_DPA_L4_SRC_PORT, /* __be16 */
+ ROCKER_TLV_OF_DPA_L4_SRC_PORT_MASK, /* __be16 */
+ ROCKER_TLV_OF_DPA_ICMP_TYPE, /* u8 */
+ ROCKER_TLV_OF_DPA_ICMP_TYPE_MASK, /* u8 */
+ ROCKER_TLV_OF_DPA_ICMP_CODE, /* u8 */
+ ROCKER_TLV_OF_DPA_ICMP_CODE_MASK, /* u8 */
+ ROCKER_TLV_OF_DPA_IPV6_LABEL, /* __be32 */
+ ROCKER_TLV_OF_DPA_IPV6_LABEL_MASK, /* __be32 */
+ ROCKER_TLV_OF_DPA_QUEUE_ID_ACTION, /* u8 */
+ ROCKER_TLV_OF_DPA_NEW_QUEUE_ID, /* u8 */
+ ROCKER_TLV_OF_DPA_CLEAR_ACTIONS, /* u32 */
+ ROCKER_TLV_OF_DPA_POP_VLAN, /* u8 */
+ ROCKER_TLV_OF_DPA_TTL_CHECK, /* u8 */
+ ROCKER_TLV_OF_DPA_COPY_CPU_ACTION, /* u8 */
+
+ __ROCKER_TLV_OF_DPA_MAX,
+ ROCKER_TLV_OF_DPA_MAX = __ROCKER_TLV_OF_DPA_MAX - 1,
+};
+
+/*
+ * OF-DPA table IDs
+ */
+
+enum rocker_of_dpa_table_id {
+ ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT = 0,
+ ROCKER_OF_DPA_TABLE_ID_VLAN = 10,
+ ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC = 20,
+ ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING = 30,
+ ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING = 40,
+ ROCKER_OF_DPA_TABLE_ID_BRIDGING = 50,
+ ROCKER_OF_DPA_TABLE_ID_ACL_POLICY = 60,
+};
+
+/*
+ * OF-DPA flow stats
+ */
+
+enum {
+ ROCKER_TLV_OF_DPA_FLOW_STAT_UNSPEC,
+ ROCKER_TLV_OF_DPA_FLOW_STAT_DURATION, /* u32 */
+ ROCKER_TLV_OF_DPA_FLOW_STAT_RX_PKTS, /* u64 */
+ ROCKER_TLV_OF_DPA_FLOW_STAT_TX_PKTS, /* u64 */
+
+ __ROCKER_TLV_OF_DPA_FLOW_STAT_MAX,
+ ROCKER_TLV_OF_DPA_FLOW_STAT_MAX = __ROCKER_TLV_OF_DPA_FLOW_STAT_MAX - 1,
+};
+
+/*
+ * OF-DPA group types
+ */
+
+enum rocker_of_dpa_group_type {
+ ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE = 0,
+ ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE,
+ ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST,
+ ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST,
+ ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD,
+ ROCKER_OF_DPA_GROUP_TYPE_L3_INTERFACE,
+ ROCKER_OF_DPA_GROUP_TYPE_L3_MCAST,
+ ROCKER_OF_DPA_GROUP_TYPE_L3_ECMP,
+ ROCKER_OF_DPA_GROUP_TYPE_L2_OVERLAY,
+};
+
+/*
+ * OF-DPA group L2 overlay types
+ */
+
+enum rocker_of_dpa_overlay_type {
+ ROCKER_OF_DPA_OVERLAY_TYPE_FLOOD_UCAST = 0,
+ ROCKER_OF_DPA_OVERLAY_TYPE_FLOOD_MCAST,
+ ROCKER_OF_DPA_OVERLAY_TYPE_MCAST_UCAST,
+ ROCKER_OF_DPA_OVERLAY_TYPE_MCAST_MCAST,
+};
+
+/*
+ * OF-DPA group ID encoding
+ */
+
+#define ROCKER_GROUP_TYPE_SHIFT 28
+#define ROCKER_GROUP_TYPE_MASK 0xf0000000
+#define ROCKER_GROUP_VLAN_ID_SHIFT 16
+#define ROCKER_GROUP_VLAN_ID_MASK 0x0fff0000
+#define ROCKER_GROUP_PORT_SHIFT 0
+#define ROCKER_GROUP_PORT_MASK 0x0000ffff
+#define ROCKER_GROUP_TUNNEL_ID_SHIFT 12
+#define ROCKER_GROUP_TUNNEL_ID_MASK 0x0ffff000
+#define ROCKER_GROUP_SUBTYPE_SHIFT 10
+#define ROCKER_GROUP_SUBTYPE_MASK 0x00000c00
+#define ROCKER_GROUP_INDEX_SHIFT 0
+#define ROCKER_GROUP_INDEX_MASK 0x0000ffff
+#define ROCKER_GROUP_INDEX_LONG_SHIFT 0
+#define ROCKER_GROUP_INDEX_LONG_MASK 0x0fffffff
+
+#define ROCKER_GROUP_TYPE_GET(group_id) \
+ (((group_id) & ROCKER_GROUP_TYPE_MASK) >> ROCKER_GROUP_TYPE_SHIFT)
+#define ROCKER_GROUP_TYPE_SET(type) \
+ (((type) << ROCKER_GROUP_TYPE_SHIFT) & ROCKER_GROUP_TYPE_MASK)
+#define ROCKER_GROUP_VLAN_GET(group_id) \
+ (((group_id) & ROCKER_GROUP_VLAN_ID_MASK) >> ROCKER_GROUP_VLAN_ID_SHIFT)
+#define ROCKER_GROUP_VLAN_SET(vlan_id) \
+ (((vlan_id) << ROCKER_GROUP_VLAN_ID_SHIFT) & ROCKER_GROUP_VLAN_ID_MASK)
+#define ROCKER_GROUP_PORT_GET(group_id) \
+ (((group_id) & ROCKER_GROUP_PORT_MASK) >> ROCKER_GROUP_PORT_SHIFT)
+#define ROCKER_GROUP_PORT_SET(port) \
+ (((port) << ROCKER_GROUP_PORT_SHIFT) & ROCKER_GROUP_PORT_MASK)
+#define ROCKER_GROUP_INDEX_GET(group_id) \
+ (((group_id) & ROCKER_GROUP_INDEX_MASK) >> ROCKER_GROUP_INDEX_SHIFT)
+#define ROCKER_GROUP_INDEX_SET(index) \
+ (((index) << ROCKER_GROUP_INDEX_SHIFT) & ROCKER_GROUP_INDEX_MASK)
+#define ROCKER_GROUP_INDEX_LONG_GET(group_id) \
+ (((group_id) & ROCKER_GROUP_INDEX_LONG_MASK) >> \
+ ROCKER_GROUP_INDEX_LONG_SHIFT)
+#define ROCKER_GROUP_INDEX_LONG_SET(index) \
+ (((index) << ROCKER_GROUP_INDEX_LONG_SHIFT) & \
+ ROCKER_GROUP_INDEX_LONG_MASK)
+
+#define ROCKER_GROUP_NONE 0
+#define ROCKER_GROUP_L2_INTERFACE(vlan_id, port) \
+ (ROCKER_GROUP_TYPE_SET(ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) |\
+ ROCKER_GROUP_VLAN_SET(ntohs(vlan_id)) | ROCKER_GROUP_PORT_SET(port))
+#define ROCKER_GROUP_L2_REWRITE(index) \
+ (ROCKER_GROUP_TYPE_SET(ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE) |\
+ ROCKER_GROUP_INDEX_LONG_SET(index))
+#define ROCKER_GROUP_L2_MCAST(vlan_id, index) \
+ (ROCKER_GROUP_TYPE_SET(ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST) |\
+ ROCKER_GROUP_VLAN_SET(ntohs(vlan_id)) | ROCKER_GROUP_INDEX_SET(index))
+#define ROCKER_GROUP_L2_FLOOD(vlan_id, index) \
+ (ROCKER_GROUP_TYPE_SET(ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD) |\
+ ROCKER_GROUP_VLAN_SET(ntohs(vlan_id)) | ROCKER_GROUP_INDEX_SET(index))
+#define ROCKER_GROUP_L3_UNICAST(index) \
+ (ROCKER_GROUP_TYPE_SET(ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST) |\
+ ROCKER_GROUP_INDEX_LONG_SET(index))
+
+/*
+ * Rocker general purpose registers
+ */
+#define ROCKER_CONTROL 0x0300
+#define ROCKER_PORT_PHYS_COUNT 0x0304
+#define ROCKER_PORT_PHYS_LINK_STATUS 0x0310 /* 8-byte */
+#define ROCKER_PORT_PHYS_ENABLE 0x0318 /* 8-byte */
+#define ROCKER_SWITCH_ID 0x0320 /* 8-byte */
+
+/*
+ * Rocker control bits
+ */
+#define ROCKER_CONTROL_RESET (1 << 0)
+
+#endif /* _ROCKER_HW_ */
diff --git a/hw/net/rocker/rocker_of_dpa.c b/hw/net/rocker/rocker_of_dpa.c
new file mode 100644
index 00000000..874fb01d
--- /dev/null
+++ b/hw/net/rocker/rocker_of_dpa.c
@@ -0,0 +1,2630 @@
+/*
+ * QEMU rocker switch emulation - OF-DPA flow processing support
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "net/eth.h"
+#include "qemu/iov.h"
+#include "qemu/timer.h"
+#include "qmp-commands.h"
+
+#include "rocker.h"
+#include "rocker_hw.h"
+#include "rocker_fp.h"
+#include "rocker_tlv.h"
+#include "rocker_world.h"
+#include "rocker_desc.h"
+#include "rocker_of_dpa.h"
+
+static const MACAddr zero_mac = { .a = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
+static const MACAddr ff_mac = { .a = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };
+
+typedef struct of_dpa {
+ World *world;
+ GHashTable *flow_tbl;
+ GHashTable *group_tbl;
+ unsigned int flow_tbl_max_size;
+ unsigned int group_tbl_max_size;
+} OfDpa;
+
+/* flow_key stolen mostly from OVS
+ *
+ * Note: fields that compare with network packet header fields
+ * are stored in network order (BE) to avoid per-packet field
+ * byte-swaps.
+ */
+
+typedef struct of_dpa_flow_key {
+ uint32_t in_pport; /* ingress port */
+ uint32_t tunnel_id; /* overlay tunnel id */
+ uint32_t tbl_id; /* table id */
+ struct {
+ __be16 vlan_id; /* 0 if no VLAN */
+ MACAddr src; /* ethernet source address */
+ MACAddr dst; /* ethernet destination address */
+ __be16 type; /* ethernet frame type */
+ } eth;
+ struct {
+ uint8_t proto; /* IP protocol or ARP opcode */
+ uint8_t tos; /* IP ToS */
+ uint8_t ttl; /* IP TTL/hop limit */
+ uint8_t frag; /* one of FRAG_TYPE_* */
+ } ip;
+ union {
+ struct {
+ struct {
+ __be32 src; /* IP source address */
+ __be32 dst; /* IP destination address */
+ } addr;
+ union {
+ struct {
+ __be16 src; /* TCP/UDP/SCTP source port */
+ __be16 dst; /* TCP/UDP/SCTP destination port */
+ __be16 flags; /* TCP flags */
+ } tp;
+ struct {
+ MACAddr sha; /* ARP source hardware address */
+ MACAddr tha; /* ARP target hardware address */
+ } arp;
+ };
+ } ipv4;
+ struct {
+ struct {
+ Ipv6Addr src; /* IPv6 source address */
+ Ipv6Addr dst; /* IPv6 destination address */
+ } addr;
+ __be32 label; /* IPv6 flow label */
+ struct {
+ __be16 src; /* TCP/UDP/SCTP source port */
+ __be16 dst; /* TCP/UDP/SCTP destination port */
+ __be16 flags; /* TCP flags */
+ } tp;
+ struct {
+ Ipv6Addr target; /* ND target address */
+ MACAddr sll; /* ND source link layer address */
+ MACAddr tll; /* ND target link layer address */
+ } nd;
+ } ipv6;
+ };
+ int width; /* how many uint64_t's in key? */
+} OfDpaFlowKey;
+
+/* Width of key which includes field 'f' in u64s, rounded up */
+#define FLOW_KEY_WIDTH(f) \
+ ((offsetof(OfDpaFlowKey, f) + \
+ sizeof(((OfDpaFlowKey *)0)->f) + \
+ sizeof(uint64_t) - 1) / sizeof(uint64_t))
+
+typedef struct of_dpa_flow_action {
+ uint32_t goto_tbl;
+ struct {
+ uint32_t group_id;
+ uint32_t tun_log_lport;
+ __be16 vlan_id;
+ } write;
+ struct {
+ __be16 new_vlan_id;
+ uint32_t out_pport;
+ uint8_t copy_to_cpu;
+ __be16 vlan_id;
+ } apply;
+} OfDpaFlowAction;
+
+typedef struct of_dpa_flow {
+ uint32_t lpm;
+ uint32_t priority;
+ uint32_t hardtime;
+ uint32_t idletime;
+ uint64_t cookie;
+ OfDpaFlowKey key;
+ OfDpaFlowKey mask;
+ OfDpaFlowAction action;
+ struct {
+ uint64_t hits;
+ int64_t install_time;
+ int64_t refresh_time;
+ uint64_t rx_pkts;
+ uint64_t tx_pkts;
+ } stats;
+} OfDpaFlow;
+
+typedef struct of_dpa_flow_pkt_fields {
+ uint32_t tunnel_id;
+ struct eth_header *ethhdr;
+ __be16 *h_proto;
+ struct vlan_header *vlanhdr;
+ struct ip_header *ipv4hdr;
+ struct ip6_header *ipv6hdr;
+ Ipv6Addr *ipv6_src_addr;
+ Ipv6Addr *ipv6_dst_addr;
+} OfDpaFlowPktFields;
+
+typedef struct of_dpa_flow_context {
+ uint32_t in_pport;
+ uint32_t tunnel_id;
+ struct iovec *iov;
+ int iovcnt;
+ struct eth_header ethhdr_rewrite;
+ struct vlan_header vlanhdr_rewrite;
+ struct vlan_header vlanhdr;
+ OfDpa *of_dpa;
+ OfDpaFlowPktFields fields;
+ OfDpaFlowAction action_set;
+} OfDpaFlowContext;
+
+typedef struct of_dpa_flow_match {
+ OfDpaFlowKey value;
+ OfDpaFlow *best;
+} OfDpaFlowMatch;
+
+typedef struct of_dpa_group {
+ uint32_t id;
+ union {
+ struct {
+ uint32_t out_pport;
+ uint8_t pop_vlan;
+ } l2_interface;
+ struct {
+ uint32_t group_id;
+ MACAddr src_mac;
+ MACAddr dst_mac;
+ __be16 vlan_id;
+ } l2_rewrite;
+ struct {
+ uint16_t group_count;
+ uint32_t *group_ids;
+ } l2_flood;
+ struct {
+ uint32_t group_id;
+ MACAddr src_mac;
+ MACAddr dst_mac;
+ __be16 vlan_id;
+ uint8_t ttl_check;
+ } l3_unicast;
+ };
+} OfDpaGroup;
+
+static int of_dpa_mask2prefix(__be32 mask)
+{
+ int i;
+ int count = 32;
+
+ for (i = 0; i < 32; i++) {
+ if (!(ntohl(mask) & ((2 << i) - 1))) {
+ count--;
+ }
+ }
+
+ return count;
+}
+
+#if defined(DEBUG_ROCKER)
+static void of_dpa_flow_key_dump(OfDpaFlowKey *key, OfDpaFlowKey *mask)
+{
+ char buf[512], *b = buf, *mac;
+
+ b += sprintf(b, " tbl %2d", key->tbl_id);
+
+ if (key->in_pport || (mask && mask->in_pport)) {
+ b += sprintf(b, " in_pport %2d", key->in_pport);
+ if (mask && mask->in_pport != 0xffffffff) {
+ b += sprintf(b, "/0x%08x", key->in_pport);
+ }
+ }
+
+ if (key->tunnel_id || (mask && mask->tunnel_id)) {
+ b += sprintf(b, " tun %8d", key->tunnel_id);
+ if (mask && mask->tunnel_id != 0xffffffff) {
+ b += sprintf(b, "/0x%08x", key->tunnel_id);
+ }
+ }
+
+ if (key->eth.vlan_id || (mask && mask->eth.vlan_id)) {
+ b += sprintf(b, " vlan %4d", ntohs(key->eth.vlan_id));
+ if (mask && mask->eth.vlan_id != 0xffff) {
+ b += sprintf(b, "/0x%04x", ntohs(key->eth.vlan_id));
+ }
+ }
+
+ if (memcmp(key->eth.src.a, zero_mac.a, ETH_ALEN) ||
+ (mask && memcmp(mask->eth.src.a, zero_mac.a, ETH_ALEN))) {
+ mac = qemu_mac_strdup_printf(key->eth.src.a);
+ b += sprintf(b, " src %s", mac);
+ g_free(mac);
+ if (mask && memcmp(mask->eth.src.a, ff_mac.a, ETH_ALEN)) {
+ mac = qemu_mac_strdup_printf(mask->eth.src.a);
+ b += sprintf(b, "/%s", mac);
+ g_free(mac);
+ }
+ }
+
+ if (memcmp(key->eth.dst.a, zero_mac.a, ETH_ALEN) ||
+ (mask && memcmp(mask->eth.dst.a, zero_mac.a, ETH_ALEN))) {
+ mac = qemu_mac_strdup_printf(key->eth.dst.a);
+ b += sprintf(b, " dst %s", mac);
+ g_free(mac);
+ if (mask && memcmp(mask->eth.dst.a, ff_mac.a, ETH_ALEN)) {
+ mac = qemu_mac_strdup_printf(mask->eth.dst.a);
+ b += sprintf(b, "/%s", mac);
+ g_free(mac);
+ }
+ }
+
+ if (key->eth.type || (mask && mask->eth.type)) {
+ b += sprintf(b, " type 0x%04x", ntohs(key->eth.type));
+ if (mask && mask->eth.type != 0xffff) {
+ b += sprintf(b, "/0x%04x", ntohs(mask->eth.type));
+ }
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ case 0x86dd:
+ if (key->ip.proto || (mask && mask->ip.proto)) {
+ b += sprintf(b, " ip proto %2d", key->ip.proto);
+ if (mask && mask->ip.proto != 0xff) {
+ b += sprintf(b, "/0x%02x", mask->ip.proto);
+ }
+ }
+ if (key->ip.tos || (mask && mask->ip.tos)) {
+ b += sprintf(b, " ip tos %2d", key->ip.tos);
+ if (mask && mask->ip.tos != 0xff) {
+ b += sprintf(b, "/0x%02x", mask->ip.tos);
+ }
+ }
+ break;
+ }
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ if (key->ipv4.addr.dst || (mask && mask->ipv4.addr.dst)) {
+ b += sprintf(b, " dst %s",
+ inet_ntoa(*(struct in_addr *)&key->ipv4.addr.dst));
+ if (mask) {
+ b += sprintf(b, "/%d",
+ of_dpa_mask2prefix(mask->ipv4.addr.dst));
+ }
+ }
+ break;
+ }
+ }
+
+ DPRINTF("%s\n", buf);
+}
+#else
+#define of_dpa_flow_key_dump(k, m)
+#endif
+
+static void _of_dpa_flow_match(void *key, void *value, void *user_data)
+{
+ OfDpaFlow *flow = value;
+ OfDpaFlowMatch *match = user_data;
+ uint64_t *k = (uint64_t *)&flow->key;
+ uint64_t *m = (uint64_t *)&flow->mask;
+ uint64_t *v = (uint64_t *)&match->value;
+ int i;
+
+ if (flow->key.tbl_id == match->value.tbl_id) {
+ of_dpa_flow_key_dump(&flow->key, &flow->mask);
+ }
+
+ if (flow->key.width > match->value.width) {
+ return;
+ }
+
+ for (i = 0; i < flow->key.width; i++, k++, m++, v++) {
+ if ((~*k & *m & *v) | (*k & *m & ~*v)) {
+ return;
+ }
+ }
+
+ DPRINTF("match\n");
+
+ if (!match->best ||
+ flow->priority > match->best->priority ||
+ flow->lpm > match->best->lpm) {
+ match->best = flow;
+ }
+}
+
+static OfDpaFlow *of_dpa_flow_match(OfDpa *of_dpa, OfDpaFlowMatch *match)
+{
+ DPRINTF("\nnew search\n");
+ of_dpa_flow_key_dump(&match->value, NULL);
+
+ g_hash_table_foreach(of_dpa->flow_tbl, _of_dpa_flow_match, match);
+
+ return match->best;
+}
+
+static OfDpaFlow *of_dpa_flow_find(OfDpa *of_dpa, uint64_t cookie)
+{
+ return g_hash_table_lookup(of_dpa->flow_tbl, &cookie);
+}
+
+static int of_dpa_flow_add(OfDpa *of_dpa, OfDpaFlow *flow)
+{
+ g_hash_table_insert(of_dpa->flow_tbl, &flow->cookie, flow);
+
+ return ROCKER_OK;
+}
+
+static void of_dpa_flow_del(OfDpa *of_dpa, OfDpaFlow *flow)
+{
+ g_hash_table_remove(of_dpa->flow_tbl, &flow->cookie);
+}
+
+static OfDpaFlow *of_dpa_flow_alloc(uint64_t cookie)
+{
+ OfDpaFlow *flow;
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
+
+ flow = g_malloc0(sizeof(OfDpaFlow));
+ if (!flow) {
+ return NULL;
+ }
+
+ flow->cookie = cookie;
+ flow->mask.tbl_id = 0xffffffff;
+
+ flow->stats.install_time = flow->stats.refresh_time = now;
+
+ return flow;
+}
+
+static void of_dpa_flow_pkt_hdr_reset(OfDpaFlowContext *fc)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+
+ fc->iov[0].iov_base = fields->ethhdr;
+ fc->iov[0].iov_len = sizeof(struct eth_header);
+ fc->iov[1].iov_base = fields->vlanhdr;
+ fc->iov[1].iov_len = fields->vlanhdr ? sizeof(struct vlan_header) : 0;
+}
+
+static void of_dpa_flow_pkt_parse(OfDpaFlowContext *fc,
+ const struct iovec *iov, int iovcnt)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+ size_t sofar = 0;
+ int i;
+
+ sofar += sizeof(struct eth_header);
+ if (iov->iov_len < sofar) {
+ DPRINTF("flow_pkt_parse underrun on eth_header\n");
+ return;
+ }
+
+ fields->ethhdr = iov->iov_base;
+ fields->h_proto = &fields->ethhdr->h_proto;
+
+ if (ntohs(*fields->h_proto) == ETH_P_VLAN) {
+ sofar += sizeof(struct vlan_header);
+ if (iov->iov_len < sofar) {
+ DPRINTF("flow_pkt_parse underrun on vlan_header\n");
+ return;
+ }
+ fields->vlanhdr = (struct vlan_header *)(fields->ethhdr + 1);
+ fields->h_proto = &fields->vlanhdr->h_proto;
+ }
+
+ switch (ntohs(*fields->h_proto)) {
+ case ETH_P_IP:
+ sofar += sizeof(struct ip_header);
+ if (iov->iov_len < sofar) {
+ DPRINTF("flow_pkt_parse underrun on ip_header\n");
+ return;
+ }
+ fields->ipv4hdr = (struct ip_header *)(fields->h_proto + 1);
+ break;
+ case ETH_P_IPV6:
+ sofar += sizeof(struct ip6_header);
+ if (iov->iov_len < sofar) {
+ DPRINTF("flow_pkt_parse underrun on ip6_header\n");
+ return;
+ }
+ fields->ipv6hdr = (struct ip6_header *)(fields->h_proto + 1);
+ break;
+ }
+
+ /* To facilitate (potential) VLAN tag insertion, Make a
+ * copy of the iov and insert two new vectors at the
+ * beginning for eth hdr and vlan hdr. No data is copied,
+ * just the vectors.
+ */
+
+ of_dpa_flow_pkt_hdr_reset(fc);
+
+ fc->iov[2].iov_base = fields->h_proto + 1;
+ fc->iov[2].iov_len = iov->iov_len - fc->iov[0].iov_len - fc->iov[1].iov_len;
+
+ for (i = 1; i < iovcnt; i++) {
+ fc->iov[i+2] = iov[i];
+ }
+
+ fc->iovcnt = iovcnt + 2;
+}
+
+static void of_dpa_flow_pkt_insert_vlan(OfDpaFlowContext *fc, __be16 vlan_id)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+ uint16_t h_proto = fields->ethhdr->h_proto;
+
+ if (fields->vlanhdr) {
+ DPRINTF("flow_pkt_insert_vlan packet already has vlan\n");
+ return;
+ }
+
+ fields->ethhdr->h_proto = htons(ETH_P_VLAN);
+ fields->vlanhdr = &fc->vlanhdr;
+ fields->vlanhdr->h_tci = vlan_id;
+ fields->vlanhdr->h_proto = h_proto;
+ fields->h_proto = &fields->vlanhdr->h_proto;
+
+ fc->iov[1].iov_base = fields->vlanhdr;
+ fc->iov[1].iov_len = sizeof(struct vlan_header);
+}
+
+static void of_dpa_flow_pkt_strip_vlan(OfDpaFlowContext *fc)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+
+ if (!fields->vlanhdr) {
+ return;
+ }
+
+ fc->iov[0].iov_len -= sizeof(fields->ethhdr->h_proto);
+ fc->iov[1].iov_base = fields->h_proto;
+ fc->iov[1].iov_len = sizeof(fields->ethhdr->h_proto);
+}
+
+static void of_dpa_flow_pkt_hdr_rewrite(OfDpaFlowContext *fc,
+ uint8_t *src_mac, uint8_t *dst_mac,
+ __be16 vlan_id)
+{
+ OfDpaFlowPktFields *fields = &fc->fields;
+
+ if (src_mac || dst_mac) {
+ memcpy(&fc->ethhdr_rewrite, fields->ethhdr, sizeof(struct eth_header));
+ if (src_mac && memcmp(src_mac, zero_mac.a, ETH_ALEN)) {
+ memcpy(fc->ethhdr_rewrite.h_source, src_mac, ETH_ALEN);
+ }
+ if (dst_mac && memcmp(dst_mac, zero_mac.a, ETH_ALEN)) {
+ memcpy(fc->ethhdr_rewrite.h_dest, dst_mac, ETH_ALEN);
+ }
+ fc->iov[0].iov_base = &fc->ethhdr_rewrite;
+ }
+
+ if (vlan_id && fields->vlanhdr) {
+ fc->vlanhdr_rewrite = fc->vlanhdr;
+ fc->vlanhdr_rewrite.h_tci = vlan_id;
+ fc->iov[1].iov_base = &fc->vlanhdr_rewrite;
+ }
+}
+
+static void of_dpa_flow_ig_tbl(OfDpaFlowContext *fc, uint32_t tbl_id);
+
+static void of_dpa_ig_port_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT;
+ match->value.in_pport = fc->in_pport;
+ match->value.width = FLOW_KEY_WIDTH(tbl_id);
+}
+
+static void of_dpa_ig_port_miss(OfDpaFlowContext *fc)
+{
+ uint32_t port;
+
+ /* The default on miss is for packets from physical ports
+ * to go to the VLAN Flow Table. There is no default rule
+ * for packets from logical ports, which are dropped on miss.
+ */
+
+ if (fp_port_from_pport(fc->in_pport, &port)) {
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_VLAN);
+ }
+}
+
+static void of_dpa_vlan_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_VLAN;
+ match->value.in_pport = fc->in_pport;
+ if (fc->fields.vlanhdr) {
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ }
+ match->value.width = FLOW_KEY_WIDTH(eth.vlan_id);
+}
+
+static void of_dpa_vlan_insert(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.apply.new_vlan_id) {
+ of_dpa_flow_pkt_insert_vlan(fc, flow->action.apply.new_vlan_id);
+ }
+}
+
+static void of_dpa_term_mac_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC;
+ match->value.in_pport = fc->in_pport;
+ match->value.eth.type = *fc->fields.h_proto;
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
+ sizeof(match->value.eth.dst.a));
+ match->value.width = FLOW_KEY_WIDTH(eth.type);
+}
+
+static void of_dpa_term_mac_miss(OfDpaFlowContext *fc)
+{
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_BRIDGING);
+}
+
+static void of_dpa_apply_actions(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ fc->action_set.apply.copy_to_cpu = flow->action.apply.copy_to_cpu;
+ fc->action_set.apply.vlan_id = flow->key.eth.vlan_id;
+}
+
+static void of_dpa_bridging_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
+ if (fc->fields.vlanhdr) {
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ } else if (fc->tunnel_id) {
+ match->value.tunnel_id = fc->tunnel_id;
+ }
+ memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
+ sizeof(match->value.eth.dst.a));
+ match->value.width = FLOW_KEY_WIDTH(eth.dst);
+}
+
+static void of_dpa_bridging_learn(OfDpaFlowContext *fc,
+ OfDpaFlow *dst_flow)
+{
+ OfDpaFlowMatch match = { { 0, }, };
+ OfDpaFlow *flow;
+ uint8_t *addr;
+ uint16_t vlan_id;
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
+ int64_t refresh_delay = 1;
+
+ /* Do a lookup in bridge table by src_mac/vlan */
+
+ addr = fc->fields.ethhdr->h_source;
+ vlan_id = fc->fields.vlanhdr->h_tci;
+
+ match.value.tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
+ match.value.eth.vlan_id = vlan_id;
+ memcpy(match.value.eth.dst.a, addr, sizeof(match.value.eth.dst.a));
+ match.value.width = FLOW_KEY_WIDTH(eth.dst);
+
+ flow = of_dpa_flow_match(fc->of_dpa, &match);
+ if (flow) {
+ if (!memcmp(flow->mask.eth.dst.a, ff_mac.a,
+ sizeof(flow->mask.eth.dst.a))) {
+ /* src_mac/vlan already learned; if in_port and out_port
+ * don't match, the end station has moved and the port
+ * needs updating */
+ /* XXX implement the in_port/out_port check */
+ if (now - flow->stats.refresh_time < refresh_delay) {
+ return;
+ }
+ flow->stats.refresh_time = now;
+ }
+ }
+
+ /* Let driver know about mac/vlan. This may be a new mac/vlan
+ * or a refresh of existing mac/vlan that's been hit after the
+ * refresh_delay.
+ */
+
+ rocker_event_mac_vlan_seen(world_rocker(fc->of_dpa->world),
+ fc->in_pport, addr, vlan_id);
+}
+
+static void of_dpa_bridging_miss(OfDpaFlowContext *fc)
+{
+ of_dpa_bridging_learn(fc, NULL);
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
+}
+
+static void of_dpa_bridging_action_write(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
+ fc->action_set.write.group_id = flow->action.write.group_id;
+ }
+ fc->action_set.write.tun_log_lport = flow->action.write.tun_log_lport;
+}
+
+static void of_dpa_unicast_routing_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING;
+ match->value.eth.type = *fc->fields.h_proto;
+ if (fc->fields.ipv4hdr) {
+ match->value.ipv4.addr.dst = fc->fields.ipv4hdr->ip_dst;
+ }
+ if (fc->fields.ipv6_dst_addr) {
+ memcpy(&match->value.ipv6.addr.dst, fc->fields.ipv6_dst_addr,
+ sizeof(match->value.ipv6.addr.dst));
+ }
+ match->value.width = FLOW_KEY_WIDTH(ipv6.addr.dst);
+}
+
+static void of_dpa_unicast_routing_miss(OfDpaFlowContext *fc)
+{
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
+}
+
+static void of_dpa_unicast_routing_action_write(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
+ fc->action_set.write.group_id = flow->action.write.group_id;
+ }
+}
+
+static void
+of_dpa_multicast_routing_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING;
+ match->value.eth.type = *fc->fields.h_proto;
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ if (fc->fields.ipv4hdr) {
+ match->value.ipv4.addr.src = fc->fields.ipv4hdr->ip_src;
+ match->value.ipv4.addr.dst = fc->fields.ipv4hdr->ip_dst;
+ }
+ if (fc->fields.ipv6_src_addr) {
+ memcpy(&match->value.ipv6.addr.src, fc->fields.ipv6_src_addr,
+ sizeof(match->value.ipv6.addr.src));
+ }
+ if (fc->fields.ipv6_dst_addr) {
+ memcpy(&match->value.ipv6.addr.dst, fc->fields.ipv6_dst_addr,
+ sizeof(match->value.ipv6.addr.dst));
+ }
+ match->value.width = FLOW_KEY_WIDTH(ipv6.addr.dst);
+}
+
+static void of_dpa_multicast_routing_miss(OfDpaFlowContext *fc)
+{
+ of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
+}
+
+static void
+of_dpa_multicast_routing_action_write(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
+ fc->action_set.write.group_id = flow->action.write.group_id;
+ }
+ fc->action_set.write.vlan_id = flow->action.write.vlan_id;
+}
+
+static void of_dpa_acl_build_match(OfDpaFlowContext *fc,
+ OfDpaFlowMatch *match)
+{
+ match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY;
+ match->value.in_pport = fc->in_pport;
+ memcpy(match->value.eth.src.a, fc->fields.ethhdr->h_source,
+ sizeof(match->value.eth.src.a));
+ memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
+ sizeof(match->value.eth.dst.a));
+ match->value.eth.type = *fc->fields.h_proto;
+ match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
+ match->value.width = FLOW_KEY_WIDTH(eth.type);
+ if (fc->fields.ipv4hdr) {
+ match->value.ip.proto = fc->fields.ipv4hdr->ip_p;
+ match->value.ip.tos = fc->fields.ipv4hdr->ip_tos;
+ match->value.width = FLOW_KEY_WIDTH(ip.tos);
+ } else if (fc->fields.ipv6hdr) {
+ match->value.ip.proto =
+ fc->fields.ipv6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt;
+ match->value.ip.tos = 0; /* XXX what goes here? */
+ match->value.width = FLOW_KEY_WIDTH(ip.tos);
+ }
+}
+
+static void of_dpa_eg(OfDpaFlowContext *fc);
+static void of_dpa_acl_hit(OfDpaFlowContext *fc,
+ OfDpaFlow *dst_flow)
+{
+ of_dpa_eg(fc);
+}
+
+static void of_dpa_acl_action_write(OfDpaFlowContext *fc,
+ OfDpaFlow *flow)
+{
+ if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
+ fc->action_set.write.group_id = flow->action.write.group_id;
+ }
+}
+
+static void of_dpa_drop(OfDpaFlowContext *fc)
+{
+ /* drop packet */
+}
+
+static OfDpaGroup *of_dpa_group_find(OfDpa *of_dpa,
+ uint32_t group_id)
+{
+ return g_hash_table_lookup(of_dpa->group_tbl, &group_id);
+}
+
+static int of_dpa_group_add(OfDpa *of_dpa, OfDpaGroup *group)
+{
+ g_hash_table_insert(of_dpa->group_tbl, &group->id, group);
+
+ return 0;
+}
+
+#if 0
+static int of_dpa_group_mod(OfDpa *of_dpa, OfDpaGroup *group)
+{
+ OfDpaGroup *old_group = of_dpa_group_find(of_dpa, group->id);
+
+ if (!old_group) {
+ return -ENOENT;
+ }
+
+ /* XXX */
+
+ return 0;
+}
+#endif
+
+static int of_dpa_group_del(OfDpa *of_dpa, OfDpaGroup *group)
+{
+ g_hash_table_remove(of_dpa->group_tbl, &group->id);
+
+ return 0;
+}
+
+#if 0
+static int of_dpa_group_get_stats(OfDpa *of_dpa, uint32_t id)
+{
+ OfDpaGroup *group = of_dpa_group_find(of_dpa, id);
+
+ if (!group) {
+ return -ENOENT;
+ }
+
+ /* XXX get/return stats */
+
+ return 0;
+}
+#endif
+
+static OfDpaGroup *of_dpa_group_alloc(uint32_t id)
+{
+ OfDpaGroup *group = g_malloc0(sizeof(OfDpaGroup));
+
+ if (!group) {
+ return NULL;
+ }
+
+ group->id = id;
+
+ return group;
+}
+
+static void of_dpa_output_l2_interface(OfDpaFlowContext *fc,
+ OfDpaGroup *group)
+{
+ uint8_t copy_to_cpu = fc->action_set.apply.copy_to_cpu;
+
+ if (group->l2_interface.pop_vlan) {
+ of_dpa_flow_pkt_strip_vlan(fc);
+ }
+
+ /* Note: By default, and as per the OpenFlow 1.3.1
+ * specification, a packet cannot be forwarded back
+ * to the IN_PORT from which it came in. An action
+ * bucket that specifies the particular packet's
+ * egress port is not evaluated.
+ */
+
+ if (group->l2_interface.out_pport == 0) {
+ rx_produce(fc->of_dpa->world, fc->in_pport, fc->iov, fc->iovcnt,
+ copy_to_cpu);
+ } else if (group->l2_interface.out_pport != fc->in_pport) {
+ rocker_port_eg(world_rocker(fc->of_dpa->world),
+ group->l2_interface.out_pport,
+ fc->iov, fc->iovcnt);
+ }
+}
+
+static void of_dpa_output_l2_rewrite(OfDpaFlowContext *fc,
+ OfDpaGroup *group)
+{
+ OfDpaGroup *l2_group =
+ of_dpa_group_find(fc->of_dpa, group->l2_rewrite.group_id);
+
+ if (!l2_group) {
+ return;
+ }
+
+ of_dpa_flow_pkt_hdr_rewrite(fc, group->l2_rewrite.src_mac.a,
+ group->l2_rewrite.dst_mac.a,
+ group->l2_rewrite.vlan_id);
+ of_dpa_output_l2_interface(fc, l2_group);
+}
+
+static void of_dpa_output_l2_flood(OfDpaFlowContext *fc,
+ OfDpaGroup *group)
+{
+ OfDpaGroup *l2_group;
+ int i;
+
+ for (i = 0; i < group->l2_flood.group_count; i++) {
+ of_dpa_flow_pkt_hdr_reset(fc);
+ l2_group = of_dpa_group_find(fc->of_dpa, group->l2_flood.group_ids[i]);
+ if (!l2_group) {
+ continue;
+ }
+ switch (ROCKER_GROUP_TYPE_GET(l2_group->id)) {
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
+ of_dpa_output_l2_interface(fc, l2_group);
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
+ of_dpa_output_l2_rewrite(fc, l2_group);
+ break;
+ }
+ }
+}
+
+static void of_dpa_output_l3_unicast(OfDpaFlowContext *fc, OfDpaGroup *group)
+{
+ OfDpaGroup *l2_group =
+ of_dpa_group_find(fc->of_dpa, group->l3_unicast.group_id);
+
+ if (!l2_group) {
+ return;
+ }
+
+ of_dpa_flow_pkt_hdr_rewrite(fc, group->l3_unicast.src_mac.a,
+ group->l3_unicast.dst_mac.a,
+ group->l3_unicast.vlan_id);
+ /* XXX need ttl_check */
+ of_dpa_output_l2_interface(fc, l2_group);
+}
+
+static void of_dpa_eg(OfDpaFlowContext *fc)
+{
+ OfDpaFlowAction *set = &fc->action_set;
+ OfDpaGroup *group;
+ uint32_t group_id;
+
+ /* send a copy of pkt to CPU (controller)? */
+
+ if (set->apply.copy_to_cpu) {
+ group_id = ROCKER_GROUP_L2_INTERFACE(set->apply.vlan_id, 0);
+ group = of_dpa_group_find(fc->of_dpa, group_id);
+ if (group) {
+ of_dpa_output_l2_interface(fc, group);
+ of_dpa_flow_pkt_hdr_reset(fc);
+ }
+ }
+
+ /* process group write actions */
+
+ if (!set->write.group_id) {
+ return;
+ }
+
+ group = of_dpa_group_find(fc->of_dpa, set->write.group_id);
+ if (!group) {
+ return;
+ }
+
+ switch (ROCKER_GROUP_TYPE_GET(group->id)) {
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
+ of_dpa_output_l2_interface(fc, group);
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
+ of_dpa_output_l2_rewrite(fc, group);
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
+ of_dpa_output_l2_flood(fc, group);
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
+ of_dpa_output_l3_unicast(fc, group);
+ break;
+ }
+}
+
+typedef struct of_dpa_flow_tbl_ops {
+ void (*build_match)(OfDpaFlowContext *fc, OfDpaFlowMatch *match);
+ void (*hit)(OfDpaFlowContext *fc, OfDpaFlow *flow);
+ void (*miss)(OfDpaFlowContext *fc);
+ void (*hit_no_goto)(OfDpaFlowContext *fc);
+ void (*action_apply)(OfDpaFlowContext *fc, OfDpaFlow *flow);
+ void (*action_write)(OfDpaFlowContext *fc, OfDpaFlow *flow);
+} OfDpaFlowTblOps;
+
+static OfDpaFlowTblOps of_dpa_tbl_ops[] = {
+ [ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT] = {
+ .build_match = of_dpa_ig_port_build_match,
+ .miss = of_dpa_ig_port_miss,
+ .hit_no_goto = of_dpa_drop,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_VLAN] = {
+ .build_match = of_dpa_vlan_build_match,
+ .hit_no_goto = of_dpa_drop,
+ .action_apply = of_dpa_vlan_insert,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC] = {
+ .build_match = of_dpa_term_mac_build_match,
+ .miss = of_dpa_term_mac_miss,
+ .hit_no_goto = of_dpa_drop,
+ .action_apply = of_dpa_apply_actions,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_BRIDGING] = {
+ .build_match = of_dpa_bridging_build_match,
+ .hit = of_dpa_bridging_learn,
+ .miss = of_dpa_bridging_miss,
+ .hit_no_goto = of_dpa_drop,
+ .action_apply = of_dpa_apply_actions,
+ .action_write = of_dpa_bridging_action_write,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING] = {
+ .build_match = of_dpa_unicast_routing_build_match,
+ .miss = of_dpa_unicast_routing_miss,
+ .hit_no_goto = of_dpa_drop,
+ .action_write = of_dpa_unicast_routing_action_write,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING] = {
+ .build_match = of_dpa_multicast_routing_build_match,
+ .miss = of_dpa_multicast_routing_miss,
+ .hit_no_goto = of_dpa_drop,
+ .action_write = of_dpa_multicast_routing_action_write,
+ },
+ [ROCKER_OF_DPA_TABLE_ID_ACL_POLICY] = {
+ .build_match = of_dpa_acl_build_match,
+ .hit = of_dpa_acl_hit,
+ .miss = of_dpa_eg,
+ .action_apply = of_dpa_apply_actions,
+ .action_write = of_dpa_acl_action_write,
+ },
+};
+
+static void of_dpa_flow_ig_tbl(OfDpaFlowContext *fc, uint32_t tbl_id)
+{
+ OfDpaFlowTblOps *ops = &of_dpa_tbl_ops[tbl_id];
+ OfDpaFlowMatch match = { { 0, }, };
+ OfDpaFlow *flow;
+
+ if (ops->build_match) {
+ ops->build_match(fc, &match);
+ } else {
+ return;
+ }
+
+ flow = of_dpa_flow_match(fc->of_dpa, &match);
+ if (!flow) {
+ if (ops->miss) {
+ ops->miss(fc);
+ }
+ return;
+ }
+
+ flow->stats.hits++;
+
+ if (ops->action_apply) {
+ ops->action_apply(fc, flow);
+ }
+
+ if (ops->action_write) {
+ ops->action_write(fc, flow);
+ }
+
+ if (ops->hit) {
+ ops->hit(fc, flow);
+ }
+
+ if (flow->action.goto_tbl) {
+ of_dpa_flow_ig_tbl(fc, flow->action.goto_tbl);
+ } else if (ops->hit_no_goto) {
+ ops->hit_no_goto(fc);
+ }
+
+ /* drop packet */
+}
+
+static ssize_t of_dpa_ig(World *world, uint32_t pport,
+ const struct iovec *iov, int iovcnt)
+{
+ struct iovec iov_copy[iovcnt + 2];
+ OfDpaFlowContext fc = {
+ .of_dpa = world_private(world),
+ .in_pport = pport,
+ .iov = iov_copy,
+ .iovcnt = iovcnt + 2,
+ };
+
+ of_dpa_flow_pkt_parse(&fc, iov, iovcnt);
+ of_dpa_flow_ig_tbl(&fc, ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT);
+
+ return iov_size(iov, iovcnt);
+}
+
+#define ROCKER_TUNNEL_LPORT 0x00010000
+
+static int of_dpa_cmd_add_ig_port(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ bool overlay_tunnel;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT;
+ key->width = FLOW_KEY_WIDTH(tbl_id);
+
+ key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]) {
+ mask->in_pport =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
+ }
+
+ overlay_tunnel = !!(key->in_pport & ROCKER_TUNNEL_LPORT);
+
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+
+ if (!overlay_tunnel && action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_VLAN) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (overlay_tunnel && action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_BRIDGING) {
+ return -ROCKER_EINVAL;
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_vlan(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ uint32_t port;
+ bool untagged;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ DPRINTF("Must give in_pport and vlan_id to install VLAN tbl entry\n");
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_VLAN;
+ key->width = FLOW_KEY_WIDTH(eth.vlan_id);
+
+ key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
+ if (!fp_port_from_pport(key->in_pport, &port)) {
+ DPRINTF("in_pport (%d) not a front-panel port\n", key->in_pport);
+ return -ROCKER_EINVAL;
+ }
+ mask->in_pport = 0xffffffff;
+
+ key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
+ mask->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
+ }
+
+ if (key->eth.vlan_id) {
+ untagged = false; /* filtering */
+ } else {
+ untagged = true;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC) {
+ DPRINTF("Goto tbl (%d) must be TERM_MAC\n", action->goto_tbl);
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (untagged) {
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_NEW_VLAN_ID]) {
+ DPRINTF("Must specify new vlan_id if untagged\n");
+ return -ROCKER_EINVAL;
+ }
+ action->apply.new_vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_NEW_VLAN_ID]);
+ if (1 > ntohs(action->apply.new_vlan_id) ||
+ ntohs(action->apply.new_vlan_id) > 4095) {
+ DPRINTF("New vlan_id (%d) must be between 1 and 4095\n",
+ ntohs(action->apply.new_vlan_id));
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_term_mac(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ const MACAddr ipv4_mcast = { .a = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 } };
+ const MACAddr ipv4_mask = { .a = { 0xff, 0xff, 0xff, 0x80, 0x00, 0x00 } };
+ const MACAddr ipv6_mcast = { .a = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x00 } };
+ const MACAddr ipv6_mask = { .a = { 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } };
+ uint32_t port;
+ bool unicast = false;
+ bool multicast = false;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC;
+ key->width = FLOW_KEY_WIDTH(eth.type);
+
+ key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
+ if (!fp_port_from_pport(key->in_pport, &port)) {
+ return -ROCKER_EINVAL;
+ }
+ mask->in_pport =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
+
+ key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
+ if (key->eth.type != htons(0x0800) && key->eth.type != htons(0x86dd)) {
+ return -ROCKER_EINVAL;
+ }
+ mask->eth.type = htons(0xffff);
+
+ memcpy(key->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(key->eth.dst.a));
+ memcpy(mask->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
+ sizeof(mask->eth.dst.a));
+
+ if ((key->eth.dst.a[0] & 0x01) == 0x00) {
+ unicast = true;
+ }
+
+ /* only two wildcard rules are acceptable for IPv4 and IPv6 multicast */
+ if (memcmp(key->eth.dst.a, ipv4_mcast.a, sizeof(key->eth.dst.a)) == 0 &&
+ memcmp(mask->eth.dst.a, ipv4_mask.a, sizeof(mask->eth.dst.a)) == 0) {
+ multicast = true;
+ }
+ if (memcmp(key->eth.dst.a, ipv6_mcast.a, sizeof(key->eth.dst.a)) == 0 &&
+ memcmp(mask->eth.dst.a, ipv6_mask.a, sizeof(mask->eth.dst.a)) == 0) {
+ multicast = true;
+ }
+
+ if (!unicast && !multicast) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ mask->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING &&
+ action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (unicast &&
+ action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (multicast &&
+ action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
+ action->apply.copy_to_cpu =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_bridging(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ bool unicast = false;
+ bool dst_mac = false;
+ bool dst_mac_mask = false;
+ enum {
+ BRIDGING_MODE_UNKNOWN,
+ BRIDGING_MODE_VLAN_UCAST,
+ BRIDGING_MODE_VLAN_MCAST,
+ BRIDGING_MODE_VLAN_DFLT,
+ BRIDGING_MODE_TUNNEL_UCAST,
+ BRIDGING_MODE_TUNNEL_MCAST,
+ BRIDGING_MODE_TUNNEL_DFLT,
+ } mode = BRIDGING_MODE_UNKNOWN;
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ key->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ mask->eth.vlan_id = 0xffff;
+ key->width = FLOW_KEY_WIDTH(eth.vlan_id);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]) {
+ key->tunnel_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]);
+ mask->tunnel_id = 0xffffffff;
+ key->width = FLOW_KEY_WIDTH(tunnel_id);
+ }
+
+ /* can't do VLAN bridging and tunnel bridging at same time */
+ if (key->eth.vlan_id && key->tunnel_id) {
+ DPRINTF("can't do VLAN bridging and tunnel bridging at same time\n");
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(key->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(key->eth.dst.a));
+ key->width = FLOW_KEY_WIDTH(eth.dst);
+ dst_mac = true;
+ unicast = (key->eth.dst.a[0] & 0x01) == 0x00;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]) {
+ memcpy(mask->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
+ sizeof(mask->eth.dst.a));
+ key->width = FLOW_KEY_WIDTH(eth.dst);
+ dst_mac_mask = true;
+ } else if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(mask->eth.dst.a, ff_mac.a, sizeof(mask->eth.dst.a));
+ }
+
+ if (key->eth.vlan_id) {
+ if (dst_mac && !dst_mac_mask) {
+ mode = unicast ? BRIDGING_MODE_VLAN_UCAST :
+ BRIDGING_MODE_VLAN_MCAST;
+ } else if ((dst_mac && dst_mac_mask) || !dst_mac) {
+ mode = BRIDGING_MODE_VLAN_DFLT;
+ }
+ } else if (key->tunnel_id) {
+ if (dst_mac && !dst_mac_mask) {
+ mode = unicast ? BRIDGING_MODE_TUNNEL_UCAST :
+ BRIDGING_MODE_TUNNEL_MCAST;
+ } else if ((dst_mac && dst_mac_mask) || !dst_mac) {
+ mode = BRIDGING_MODE_TUNNEL_DFLT;
+ }
+ }
+
+ if (mode == BRIDGING_MODE_UNKNOWN) {
+ DPRINTF("Unknown bridging mode\n");
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
+ DPRINTF("Briding goto tbl must be ACL policy\n");
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ action->write.group_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+ switch (mode) {
+ case BRIDGING_MODE_VLAN_UCAST:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) {
+ DPRINTF("Bridging mode vlan ucast needs L2 "
+ "interface group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case BRIDGING_MODE_VLAN_MCAST:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST) {
+ DPRINTF("Bridging mode vlan mcast needs L2 "
+ "mcast group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case BRIDGING_MODE_VLAN_DFLT:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD) {
+ DPRINTF("Bridging mode vlan dflt needs L2 "
+ "flood group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case BRIDGING_MODE_TUNNEL_MCAST:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_OVERLAY) {
+ DPRINTF("Bridging mode tunnel mcast needs L2 "
+ "overlay group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ case BRIDGING_MODE_TUNNEL_DFLT:
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_OVERLAY) {
+ DPRINTF("Bridging mode tunnel dflt needs L2 "
+ "overlay group (0x%08x)\n",
+ action->write.group_id);
+ return -ROCKER_EINVAL;
+ }
+ break;
+ default:
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_LPORT]) {
+ action->write.tun_log_lport =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_LPORT]);
+ if (mode != BRIDGING_MODE_TUNNEL_UCAST) {
+ DPRINTF("Have tunnel logical port but not "
+ "in bridging tunnel mode\n");
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
+ action->apply.copy_to_cpu =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_unicast_routing(OfDpaFlow *flow,
+ RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ enum {
+ UNICAST_ROUTING_MODE_UNKNOWN,
+ UNICAST_ROUTING_MODE_IPV4,
+ UNICAST_ROUTING_MODE_IPV6,
+ } mode = UNICAST_ROUTING_MODE_UNKNOWN;
+ uint8_t type;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING;
+ key->width = FLOW_KEY_WIDTH(ipv6.addr.dst);
+
+ key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ mode = UNICAST_ROUTING_MODE_IPV4;
+ break;
+ case 0x86dd:
+ mode = UNICAST_ROUTING_MODE_IPV6;
+ break;
+ default:
+ return -ROCKER_EINVAL;
+ }
+ mask->eth.type = htons(0xffff);
+
+ switch (mode) {
+ case UNICAST_ROUTING_MODE_IPV4:
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]) {
+ return -ROCKER_EINVAL;
+ }
+ key->ipv4.addr.dst =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]);
+ if (ipv4_addr_is_multicast(key->ipv4.addr.dst)) {
+ return -ROCKER_EINVAL;
+ }
+ flow->lpm = of_dpa_mask2prefix(htonl(0xffffffff));
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP_MASK]) {
+ mask->ipv4.addr.dst =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP_MASK]);
+ flow->lpm = of_dpa_mask2prefix(mask->ipv4.addr.dst);
+ }
+ break;
+ case UNICAST_ROUTING_MODE_IPV6:
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]) {
+ return -ROCKER_EINVAL;
+ }
+ memcpy(&key->ipv6.addr.dst,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]),
+ sizeof(key->ipv6.addr.dst));
+ if (ipv6_addr_is_multicast(&key->ipv6.addr.dst)) {
+ return -ROCKER_EINVAL;
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6_MASK]) {
+ memcpy(&mask->ipv6.addr.dst,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6_MASK]),
+ sizeof(mask->ipv6.addr.dst));
+ }
+ break;
+ default:
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ action->write.group_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+ type = ROCKER_GROUP_TYPE_GET(action->write.group_id);
+ if (type != ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE &&
+ type != ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST &&
+ type != ROCKER_OF_DPA_GROUP_TYPE_L3_ECMP) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_multicast_routing(OfDpaFlow *flow,
+ RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ enum {
+ MULTICAST_ROUTING_MODE_UNKNOWN,
+ MULTICAST_ROUTING_MODE_IPV4,
+ MULTICAST_ROUTING_MODE_IPV6,
+ } mode = MULTICAST_ROUTING_MODE_UNKNOWN;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING;
+ key->width = FLOW_KEY_WIDTH(ipv6.addr.dst);
+
+ key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ mode = MULTICAST_ROUTING_MODE_IPV4;
+ break;
+ case 0x86dd:
+ mode = MULTICAST_ROUTING_MODE_IPV6;
+ break;
+ default:
+ return -ROCKER_EINVAL;
+ }
+
+ key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+
+ switch (mode) {
+ case MULTICAST_ROUTING_MODE_IPV4:
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]) {
+ key->ipv4.addr.src =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP_MASK]) {
+ mask->ipv4.addr.src =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP_MASK]);
+ }
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]) {
+ if (mask->ipv4.addr.src != 0) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->ipv4.addr.dst =
+ rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]);
+ if (!ipv4_addr_is_multicast(key->ipv4.addr.dst)) {
+ return -ROCKER_EINVAL;
+ }
+
+ break;
+
+ case MULTICAST_ROUTING_MODE_IPV6:
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]) {
+ memcpy(&key->ipv6.addr.src,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]),
+ sizeof(key->ipv6.addr.src));
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6_MASK]) {
+ memcpy(&mask->ipv6.addr.src,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6_MASK]),
+ sizeof(mask->ipv6.addr.src));
+ }
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]) {
+ if (mask->ipv6.addr.src.addr32[0] != 0 &&
+ mask->ipv6.addr.src.addr32[1] != 0 &&
+ mask->ipv6.addr.src.addr32[2] != 0 &&
+ mask->ipv6.addr.src.addr32[3] != 0) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]) {
+ return -ROCKER_EINVAL;
+ }
+
+ memcpy(&key->ipv6.addr.dst,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]),
+ sizeof(key->ipv6.addr.dst));
+ if (!ipv6_addr_is_multicast(&key->ipv6.addr.dst)) {
+ return -ROCKER_EINVAL;
+ }
+
+ break;
+
+ default:
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
+ action->goto_tbl =
+ rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
+ if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ action->write.group_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+ if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L3_MCAST) {
+ return -ROCKER_EINVAL;
+ }
+ action->write.vlan_id = key->eth.vlan_id;
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_acl_ip(OfDpaFlowKey *key, OfDpaFlowKey *mask,
+ RockerTlv **flow_tlvs)
+{
+ key->width = FLOW_KEY_WIDTH(ip.tos);
+
+ key->ip.proto = 0;
+ key->ip.tos = 0;
+ mask->ip.proto = 0;
+ mask->ip.tos = 0;
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO]) {
+ key->ip.proto =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO]);
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO_MASK]) {
+ mask->ip.proto =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO_MASK]);
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP]) {
+ key->ip.tos =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP]);
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP_MASK]) {
+ mask->ip.tos =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP_MASK]);
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN]) {
+ key->ip.tos |=
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN]) << 6;
+ }
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN_MASK]) {
+ mask->ip.tos |=
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN_MASK]) << 6;
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_acl(OfDpaFlow *flow, RockerTlv **flow_tlvs)
+{
+ OfDpaFlowKey *key = &flow->key;
+ OfDpaFlowKey *mask = &flow->mask;
+ OfDpaFlowAction *action = &flow->action;
+ enum {
+ ACL_MODE_UNKNOWN,
+ ACL_MODE_IPV4_VLAN,
+ ACL_MODE_IPV6_VLAN,
+ ACL_MODE_IPV4_TENANT,
+ ACL_MODE_IPV6_TENANT,
+ ACL_MODE_NON_IP_VLAN,
+ ACL_MODE_NON_IP_TENANT,
+ ACL_MODE_ANY_VLAN,
+ ACL_MODE_ANY_TENANT,
+ } mode = ACL_MODE_UNKNOWN;
+ int err = ROCKER_OK;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]) {
+ return -ROCKER_EINVAL;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID] &&
+ flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]) {
+ return -ROCKER_EINVAL;
+ }
+
+ key->tbl_id = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY;
+ key->width = FLOW_KEY_WIDTH(eth.type);
+
+ key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]) {
+ mask->in_pport =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
+ memcpy(key->eth.src.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
+ sizeof(key->eth.src.a));
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC_MASK]) {
+ memcpy(mask->eth.src.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC_MASK]),
+ sizeof(mask->eth.src.a));
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(key->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(key->eth.dst.a));
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]) {
+ memcpy(mask->eth.dst.a,
+ rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
+ sizeof(mask->eth.dst.a));
+ }
+
+ key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
+ if (key->eth.type) {
+ mask->eth.type = 0xffff;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ key->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
+ mask->eth.vlan_id =
+ rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
+ }
+
+ switch (ntohs(key->eth.type)) {
+ case 0x0000:
+ mode = (key->eth.vlan_id) ? ACL_MODE_ANY_VLAN : ACL_MODE_ANY_TENANT;
+ break;
+ case 0x0800:
+ mode = (key->eth.vlan_id) ? ACL_MODE_IPV4_VLAN : ACL_MODE_IPV4_TENANT;
+ break;
+ case 0x86dd:
+ mode = (key->eth.vlan_id) ? ACL_MODE_IPV6_VLAN : ACL_MODE_IPV6_TENANT;
+ break;
+ default:
+ mode = (key->eth.vlan_id) ? ACL_MODE_NON_IP_VLAN :
+ ACL_MODE_NON_IP_TENANT;
+ break;
+ }
+
+ /* XXX only supporting VLAN modes for now */
+ if (mode != ACL_MODE_IPV4_VLAN &&
+ mode != ACL_MODE_IPV6_VLAN &&
+ mode != ACL_MODE_NON_IP_VLAN &&
+ mode != ACL_MODE_ANY_VLAN) {
+ return -ROCKER_EINVAL;
+ }
+
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ case 0x86dd:
+ err = of_dpa_cmd_add_acl_ip(key, mask, flow_tlvs);
+ break;
+ }
+
+ if (err) {
+ return err;
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ action->write.group_id =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+ }
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
+ action->apply.copy_to_cpu =
+ rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_flow_add_mod(OfDpa *of_dpa, OfDpaFlow *flow,
+ RockerTlv **flow_tlvs)
+{
+ enum rocker_of_dpa_table_id tbl;
+ int err = ROCKER_OK;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_TABLE_ID] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_PRIORITY] ||
+ !flow_tlvs[ROCKER_TLV_OF_DPA_HARDTIME]) {
+ return -ROCKER_EINVAL;
+ }
+
+ tbl = rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_TABLE_ID]);
+ flow->priority = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_PRIORITY]);
+ flow->hardtime = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_HARDTIME]);
+
+ if (flow_tlvs[ROCKER_TLV_OF_DPA_IDLETIME]) {
+ if (tbl == ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT ||
+ tbl == ROCKER_OF_DPA_TABLE_ID_VLAN ||
+ tbl == ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC) {
+ return -ROCKER_EINVAL;
+ }
+ flow->idletime =
+ rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IDLETIME]);
+ }
+
+ switch (tbl) {
+ case ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT:
+ err = of_dpa_cmd_add_ig_port(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_VLAN:
+ err = of_dpa_cmd_add_vlan(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC:
+ err = of_dpa_cmd_add_term_mac(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_BRIDGING:
+ err = of_dpa_cmd_add_bridging(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING:
+ err = of_dpa_cmd_add_unicast_routing(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING:
+ err = of_dpa_cmd_add_multicast_routing(flow, flow_tlvs);
+ break;
+ case ROCKER_OF_DPA_TABLE_ID_ACL_POLICY:
+ err = of_dpa_cmd_add_acl(flow, flow_tlvs);
+ break;
+ }
+
+ return err;
+}
+
+static int of_dpa_cmd_flow_add(OfDpa *of_dpa, uint64_t cookie,
+ RockerTlv **flow_tlvs)
+{
+ OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
+ int err = ROCKER_OK;
+
+ if (flow) {
+ return -ROCKER_EEXIST;
+ }
+
+ flow = of_dpa_flow_alloc(cookie);
+ if (!flow) {
+ return -ROCKER_ENOMEM;
+ }
+
+ err = of_dpa_cmd_flow_add_mod(of_dpa, flow, flow_tlvs);
+ if (err) {
+ g_free(flow);
+ return err;
+ }
+
+ return of_dpa_flow_add(of_dpa, flow);
+}
+
+static int of_dpa_cmd_flow_mod(OfDpa *of_dpa, uint64_t cookie,
+ RockerTlv **flow_tlvs)
+{
+ OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
+
+ if (!flow) {
+ return -ROCKER_ENOENT;
+ }
+
+ return of_dpa_cmd_flow_add_mod(of_dpa, flow, flow_tlvs);
+}
+
+static int of_dpa_cmd_flow_del(OfDpa *of_dpa, uint64_t cookie)
+{
+ OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
+
+ if (!flow) {
+ return -ROCKER_ENOENT;
+ }
+
+ of_dpa_flow_del(of_dpa, flow);
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_flow_get_stats(OfDpa *of_dpa, uint64_t cookie,
+ struct desc_info *info, char *buf)
+{
+ OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
+ size_t tlv_size;
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
+ int pos;
+
+ if (!flow) {
+ return -ROCKER_ENOENT;
+ }
+
+ tlv_size = rocker_tlv_total_size(sizeof(uint32_t)) + /* duration */
+ rocker_tlv_total_size(sizeof(uint64_t)) + /* rx_pkts */
+ rocker_tlv_total_size(sizeof(uint64_t)); /* tx_ptks */
+
+ if (tlv_size > desc_buf_size(info)) {
+ return -ROCKER_EMSGSIZE;
+ }
+
+ pos = 0;
+ rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_DURATION,
+ (int32_t)(now - flow->stats.install_time));
+ rocker_tlv_put_le64(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_RX_PKTS,
+ flow->stats.rx_pkts);
+ rocker_tlv_put_le64(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_TX_PKTS,
+ flow->stats.tx_pkts);
+
+ return desc_set_buf(info, tlv_size);
+}
+
+static int of_dpa_flow_cmd(OfDpa *of_dpa, struct desc_info *info,
+ char *buf, uint16_t cmd,
+ RockerTlv **flow_tlvs)
+{
+ uint64_t cookie;
+
+ if (!flow_tlvs[ROCKER_TLV_OF_DPA_COOKIE]) {
+ return -ROCKER_EINVAL;
+ }
+
+ cookie = rocker_tlv_get_le64(flow_tlvs[ROCKER_TLV_OF_DPA_COOKIE]);
+
+ switch (cmd) {
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD:
+ return of_dpa_cmd_flow_add(of_dpa, cookie, flow_tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD:
+ return of_dpa_cmd_flow_mod(of_dpa, cookie, flow_tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL:
+ return of_dpa_cmd_flow_del(of_dpa, cookie);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_GET_STATS:
+ return of_dpa_cmd_flow_get_stats(of_dpa, cookie, info, buf);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+static int of_dpa_cmd_add_l2_interface(OfDpaGroup *group,
+ RockerTlv **group_tlvs)
+{
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_OUT_PPORT] ||
+ !group_tlvs[ROCKER_TLV_OF_DPA_POP_VLAN]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group->l2_interface.out_pport =
+ rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_OUT_PPORT]);
+ group->l2_interface.pop_vlan =
+ rocker_tlv_get_u8(group_tlvs[ROCKER_TLV_OF_DPA_POP_VLAN]);
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_l2_rewrite(OfDpa *of_dpa, OfDpaGroup *group,
+ RockerTlv **group_tlvs)
+{
+ OfDpaGroup *l2_interface_group;
+
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group->l2_rewrite.group_id =
+ rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]);
+
+ l2_interface_group = of_dpa_group_find(of_dpa, group->l2_rewrite.group_id);
+ if (!l2_interface_group ||
+ ROCKER_GROUP_TYPE_GET(l2_interface_group->id) !=
+ ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) {
+ DPRINTF("l2 rewrite group needs a valid l2 interface group\n");
+ return -ROCKER_EINVAL;
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
+ memcpy(group->l2_rewrite.src_mac.a,
+ rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
+ sizeof(group->l2_rewrite.src_mac.a));
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(group->l2_rewrite.dst_mac.a,
+ rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(group->l2_rewrite.dst_mac.a));
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ group->l2_rewrite.vlan_id =
+ rocker_tlv_get_u16(group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ if (ROCKER_GROUP_VLAN_GET(l2_interface_group->id) !=
+ (ntohs(group->l2_rewrite.vlan_id) & VLAN_VID_MASK)) {
+ DPRINTF("Set VLAN ID must be same as L2 interface group\n");
+ return -ROCKER_EINVAL;
+ }
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_add_l2_flood(OfDpa *of_dpa, OfDpaGroup *group,
+ RockerTlv **group_tlvs)
+{
+ OfDpaGroup *l2_group;
+ RockerTlv **tlvs;
+ int err;
+ int i;
+
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_COUNT] ||
+ !group_tlvs[ROCKER_TLV_OF_DPA_GROUP_IDS]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group->l2_flood.group_count =
+ rocker_tlv_get_le16(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_COUNT]);
+
+ tlvs = g_malloc0((group->l2_flood.group_count + 1) *
+ sizeof(RockerTlv *));
+ if (!tlvs) {
+ return -ROCKER_ENOMEM;
+ }
+
+ g_free(group->l2_flood.group_ids);
+ group->l2_flood.group_ids =
+ g_malloc0(group->l2_flood.group_count * sizeof(uint32_t));
+ if (!group->l2_flood.group_ids) {
+ err = -ROCKER_ENOMEM;
+ goto err_out;
+ }
+
+ rocker_tlv_parse_nested(tlvs, group->l2_flood.group_count,
+ group_tlvs[ROCKER_TLV_OF_DPA_GROUP_IDS]);
+
+ for (i = 0; i < group->l2_flood.group_count; i++) {
+ group->l2_flood.group_ids[i] = rocker_tlv_get_le32(tlvs[i + 1]);
+ }
+
+ /* All of the L2 interface groups referenced by the L2 flood
+ * must have same VLAN
+ */
+
+ for (i = 0; i < group->l2_flood.group_count; i++) {
+ l2_group = of_dpa_group_find(of_dpa, group->l2_flood.group_ids[i]);
+ if (!l2_group) {
+ continue;
+ }
+ if ((ROCKER_GROUP_TYPE_GET(l2_group->id) ==
+ ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) &&
+ (ROCKER_GROUP_VLAN_GET(l2_group->id) !=
+ ROCKER_GROUP_VLAN_GET(group->id))) {
+ DPRINTF("l2 interface group 0x%08x VLAN doesn't match l2 "
+ "flood group 0x%08x\n",
+ group->l2_flood.group_ids[i], group->id);
+ err = -ROCKER_EINVAL;
+ goto err_out;
+ }
+ }
+
+ g_free(tlvs);
+ return ROCKER_OK;
+
+err_out:
+ group->l2_flood.group_count = 0;
+ g_free(group->l2_flood.group_ids);
+ g_free(tlvs);
+
+ return err;
+}
+
+static int of_dpa_cmd_add_l3_unicast(OfDpaGroup *group, RockerTlv **group_tlvs)
+{
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group->l3_unicast.group_id =
+ rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]);
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
+ memcpy(group->l3_unicast.src_mac.a,
+ rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
+ sizeof(group->l3_unicast.src_mac.a));
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
+ memcpy(group->l3_unicast.dst_mac.a,
+ rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
+ sizeof(group->l3_unicast.dst_mac.a));
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
+ group->l3_unicast.vlan_id =
+ rocker_tlv_get_u16(group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
+ }
+
+ if (group_tlvs[ROCKER_TLV_OF_DPA_TTL_CHECK]) {
+ group->l3_unicast.ttl_check =
+ rocker_tlv_get_u8(group_tlvs[ROCKER_TLV_OF_DPA_TTL_CHECK]);
+ }
+
+ return ROCKER_OK;
+}
+
+static int of_dpa_cmd_group_do(OfDpa *of_dpa, uint32_t group_id,
+ OfDpaGroup *group, RockerTlv **group_tlvs)
+{
+ uint8_t type = ROCKER_GROUP_TYPE_GET(group_id);
+
+ switch (type) {
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
+ return of_dpa_cmd_add_l2_interface(group, group_tlvs);
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
+ return of_dpa_cmd_add_l2_rewrite(of_dpa, group, group_tlvs);
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
+ /* Treat L2 multicast group same as a L2 flood group */
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
+ return of_dpa_cmd_add_l2_flood(of_dpa, group, group_tlvs);
+ case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
+ return of_dpa_cmd_add_l3_unicast(group, group_tlvs);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+static int of_dpa_cmd_group_add(OfDpa *of_dpa, uint32_t group_id,
+ RockerTlv **group_tlvs)
+{
+ OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
+ int err;
+
+ if (group) {
+ return -ROCKER_EEXIST;
+ }
+
+ group = of_dpa_group_alloc(group_id);
+ if (!group) {
+ return -ROCKER_ENOMEM;
+ }
+
+ err = of_dpa_cmd_group_do(of_dpa, group_id, group, group_tlvs);
+ if (err) {
+ goto err_cmd_add;
+ }
+
+ err = of_dpa_group_add(of_dpa, group);
+ if (err) {
+ goto err_cmd_add;
+ }
+
+ return ROCKER_OK;
+
+err_cmd_add:
+ g_free(group);
+ return err;
+}
+
+static int of_dpa_cmd_group_mod(OfDpa *of_dpa, uint32_t group_id,
+ RockerTlv **group_tlvs)
+{
+ OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
+
+ if (!group) {
+ return -ROCKER_ENOENT;
+ }
+
+ return of_dpa_cmd_group_do(of_dpa, group_id, group, group_tlvs);
+}
+
+static int of_dpa_cmd_group_del(OfDpa *of_dpa, uint32_t group_id)
+{
+ OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
+
+ if (!group) {
+ return -ROCKER_ENOENT;
+ }
+
+ return of_dpa_group_del(of_dpa, group);
+}
+
+static int of_dpa_cmd_group_get_stats(OfDpa *of_dpa, uint32_t group_id,
+ struct desc_info *info, char *buf)
+{
+ return -ROCKER_ENOTSUP;
+}
+
+static int of_dpa_group_cmd(OfDpa *of_dpa, struct desc_info *info,
+ char *buf, uint16_t cmd, RockerTlv **group_tlvs)
+{
+ uint32_t group_id;
+
+ if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
+ return -ROCKER_EINVAL;
+ }
+
+ group_id = rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
+
+ switch (cmd) {
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD:
+ return of_dpa_cmd_group_add(of_dpa, group_id, group_tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD:
+ return of_dpa_cmd_group_mod(of_dpa, group_id, group_tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL:
+ return of_dpa_cmd_group_del(of_dpa, group_id);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_GET_STATS:
+ return of_dpa_cmd_group_get_stats(of_dpa, group_id, info, buf);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+static int of_dpa_cmd(World *world, struct desc_info *info,
+ char *buf, uint16_t cmd, RockerTlv *cmd_info_tlv)
+{
+ OfDpa *of_dpa = world_private(world);
+ RockerTlv *tlvs[ROCKER_TLV_OF_DPA_MAX + 1];
+
+ rocker_tlv_parse_nested(tlvs, ROCKER_TLV_OF_DPA_MAX, cmd_info_tlv);
+
+ switch (cmd) {
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_GET_STATS:
+ return of_dpa_flow_cmd(of_dpa, info, buf, cmd, tlvs);
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL:
+ case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_GET_STATS:
+ return of_dpa_group_cmd(of_dpa, info, buf, cmd, tlvs);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+static gboolean rocker_int64_equal(gconstpointer v1, gconstpointer v2)
+{
+ return *((const uint64_t *)v1) == *((const uint64_t *)v2);
+}
+
+static guint rocker_int64_hash(gconstpointer v)
+{
+ return (guint)*(const uint64_t *)v;
+}
+
+static int of_dpa_init(World *world)
+{
+ OfDpa *of_dpa = world_private(world);
+
+ of_dpa->world = world;
+
+ of_dpa->flow_tbl = g_hash_table_new_full(rocker_int64_hash,
+ rocker_int64_equal,
+ NULL, g_free);
+ if (!of_dpa->flow_tbl) {
+ return -ENOMEM;
+ }
+
+ of_dpa->group_tbl = g_hash_table_new_full(g_int_hash, g_int_equal,
+ NULL, g_free);
+ if (!of_dpa->group_tbl) {
+ goto err_group_tbl;
+ }
+
+ /* XXX hardcode some artificial table max values */
+ of_dpa->flow_tbl_max_size = 100;
+ of_dpa->group_tbl_max_size = 100;
+
+ return 0;
+
+err_group_tbl:
+ g_hash_table_destroy(of_dpa->flow_tbl);
+ return -ENOMEM;
+}
+
+static void of_dpa_uninit(World *world)
+{
+ OfDpa *of_dpa = world_private(world);
+
+ g_hash_table_destroy(of_dpa->group_tbl);
+ g_hash_table_destroy(of_dpa->flow_tbl);
+}
+
+struct of_dpa_flow_fill_context {
+ RockerOfDpaFlowList *list;
+ uint32_t tbl_id;
+};
+
+static void of_dpa_flow_fill(void *cookie, void *value, void *user_data)
+{
+ struct of_dpa_flow *flow = value;
+ struct of_dpa_flow_key *key = &flow->key;
+ struct of_dpa_flow_key *mask = &flow->mask;
+ struct of_dpa_flow_fill_context *flow_context = user_data;
+ RockerOfDpaFlowList *new;
+ RockerOfDpaFlow *nflow;
+ RockerOfDpaFlowKey *nkey;
+ RockerOfDpaFlowMask *nmask;
+ RockerOfDpaFlowAction *naction;
+
+ if (flow_context->tbl_id != -1 &&
+ flow_context->tbl_id != key->tbl_id) {
+ return;
+ }
+
+ new = g_malloc0(sizeof(*new));
+ nflow = new->value = g_malloc0(sizeof(*nflow));
+ nkey = nflow->key = g_malloc0(sizeof(*nkey));
+ nmask = nflow->mask = g_malloc0(sizeof(*nmask));
+ naction = nflow->action = g_malloc0(sizeof(*naction));
+
+ nflow->cookie = flow->cookie;
+ nflow->hits = flow->stats.hits;
+ nkey->priority = flow->priority;
+ nkey->tbl_id = key->tbl_id;
+
+ if (key->in_pport || mask->in_pport) {
+ nkey->has_in_pport = true;
+ nkey->in_pport = key->in_pport;
+ }
+
+ if (nkey->has_in_pport && mask->in_pport != 0xffffffff) {
+ nmask->has_in_pport = true;
+ nmask->in_pport = mask->in_pport;
+ }
+
+ if (key->eth.vlan_id || mask->eth.vlan_id) {
+ nkey->has_vlan_id = true;
+ nkey->vlan_id = ntohs(key->eth.vlan_id);
+ }
+
+ if (nkey->has_vlan_id && mask->eth.vlan_id != 0xffff) {
+ nmask->has_vlan_id = true;
+ nmask->vlan_id = ntohs(mask->eth.vlan_id);
+ }
+
+ if (key->tunnel_id || mask->tunnel_id) {
+ nkey->has_tunnel_id = true;
+ nkey->tunnel_id = key->tunnel_id;
+ }
+
+ if (nkey->has_tunnel_id && mask->tunnel_id != 0xffffffff) {
+ nmask->has_tunnel_id = true;
+ nmask->tunnel_id = mask->tunnel_id;
+ }
+
+ if (memcmp(key->eth.src.a, zero_mac.a, ETH_ALEN) ||
+ memcmp(mask->eth.src.a, zero_mac.a, ETH_ALEN)) {
+ nkey->has_eth_src = true;
+ nkey->eth_src = qemu_mac_strdup_printf(key->eth.src.a);
+ }
+
+ if (nkey->has_eth_src && memcmp(mask->eth.src.a, ff_mac.a, ETH_ALEN)) {
+ nmask->has_eth_src = true;
+ nmask->eth_src = qemu_mac_strdup_printf(mask->eth.src.a);
+ }
+
+ if (memcmp(key->eth.dst.a, zero_mac.a, ETH_ALEN) ||
+ memcmp(mask->eth.dst.a, zero_mac.a, ETH_ALEN)) {
+ nkey->has_eth_dst = true;
+ nkey->eth_dst = qemu_mac_strdup_printf(key->eth.dst.a);
+ }
+
+ if (nkey->has_eth_dst && memcmp(mask->eth.dst.a, ff_mac.a, ETH_ALEN)) {
+ nmask->has_eth_dst = true;
+ nmask->eth_dst = qemu_mac_strdup_printf(mask->eth.dst.a);
+ }
+
+ if (key->eth.type) {
+
+ nkey->has_eth_type = true;
+ nkey->eth_type = ntohs(key->eth.type);
+
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ case 0x86dd:
+ if (key->ip.proto || mask->ip.proto) {
+ nkey->has_ip_proto = true;
+ nkey->ip_proto = key->ip.proto;
+ }
+ if (nkey->has_ip_proto && mask->ip.proto != 0xff) {
+ nmask->has_ip_proto = true;
+ nmask->ip_proto = mask->ip.proto;
+ }
+ if (key->ip.tos || mask->ip.tos) {
+ nkey->has_ip_tos = true;
+ nkey->ip_tos = key->ip.tos;
+ }
+ if (nkey->has_ip_tos && mask->ip.tos != 0xff) {
+ nmask->has_ip_tos = true;
+ nmask->ip_tos = mask->ip.tos;
+ }
+ break;
+ }
+
+ switch (ntohs(key->eth.type)) {
+ case 0x0800:
+ if (key->ipv4.addr.dst || mask->ipv4.addr.dst) {
+ char *dst = inet_ntoa(*(struct in_addr *)&key->ipv4.addr.dst);
+ int dst_len = of_dpa_mask2prefix(mask->ipv4.addr.dst);
+ nkey->has_ip_dst = true;
+ nkey->ip_dst = g_strdup_printf("%s/%d", dst, dst_len);
+ }
+ break;
+ }
+ }
+
+ if (flow->action.goto_tbl) {
+ naction->has_goto_tbl = true;
+ naction->goto_tbl = flow->action.goto_tbl;
+ }
+
+ if (flow->action.write.group_id) {
+ naction->has_group_id = true;
+ naction->group_id = flow->action.write.group_id;
+ }
+
+ if (flow->action.apply.new_vlan_id) {
+ naction->has_new_vlan_id = true;
+ naction->new_vlan_id = flow->action.apply.new_vlan_id;
+ }
+
+ new->next = flow_context->list;
+ flow_context->list = new;
+}
+
+RockerOfDpaFlowList *qmp_query_rocker_of_dpa_flows(const char *name,
+ bool has_tbl_id,
+ uint32_t tbl_id,
+ Error **errp)
+{
+ struct rocker *r;
+ struct world *w;
+ struct of_dpa *of_dpa;
+ struct of_dpa_flow_fill_context fill_context = {
+ .list = NULL,
+ .tbl_id = tbl_id,
+ };
+
+ r = rocker_find(name);
+ if (!r) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "rocker %s not found", name);
+ return NULL;
+ }
+
+ w = rocker_get_world(r, ROCKER_WORLD_TYPE_OF_DPA);
+ if (!w) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "rocker %s doesn't have OF-DPA world", name);
+ return NULL;
+ }
+
+ of_dpa = world_private(w);
+
+ g_hash_table_foreach(of_dpa->flow_tbl, of_dpa_flow_fill, &fill_context);
+
+ return fill_context.list;
+}
+
+struct of_dpa_group_fill_context {
+ RockerOfDpaGroupList *list;
+ uint8_t type;
+};
+
+static void of_dpa_group_fill(void *key, void *value, void *user_data)
+{
+ struct of_dpa_group *group = value;
+ struct of_dpa_group_fill_context *flow_context = user_data;
+ RockerOfDpaGroupList *new;
+ RockerOfDpaGroup *ngroup;
+ struct uint32List *id;
+ int i;
+
+ if (flow_context->type != 9 &&
+ flow_context->type != ROCKER_GROUP_TYPE_GET(group->id)) {
+ return;
+ }
+
+ new = g_malloc0(sizeof(*new));
+ ngroup = new->value = g_malloc0(sizeof(*ngroup));
+
+ ngroup->id = group->id;
+
+ ngroup->type = ROCKER_GROUP_TYPE_GET(group->id);
+
+ switch (ngroup->type) {
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
+ ngroup->has_vlan_id = true;
+ ngroup->vlan_id = ROCKER_GROUP_VLAN_GET(group->id);
+ ngroup->has_pport = true;
+ ngroup->pport = ROCKER_GROUP_PORT_GET(group->id);
+ ngroup->has_out_pport = true;
+ ngroup->out_pport = group->l2_interface.out_pport;
+ ngroup->has_pop_vlan = true;
+ ngroup->pop_vlan = group->l2_interface.pop_vlan;
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
+ ngroup->has_index = true;
+ ngroup->index = ROCKER_GROUP_INDEX_LONG_GET(group->id);
+ ngroup->has_group_id = true;
+ ngroup->group_id = group->l2_rewrite.group_id;
+ if (group->l2_rewrite.vlan_id) {
+ ngroup->has_set_vlan_id = true;
+ ngroup->set_vlan_id = ntohs(group->l2_rewrite.vlan_id);
+ }
+ if (memcmp(group->l2_rewrite.src_mac.a, zero_mac.a, ETH_ALEN)) {
+ ngroup->has_set_eth_src = true;
+ ngroup->set_eth_src =
+ qemu_mac_strdup_printf(group->l2_rewrite.src_mac.a);
+ }
+ if (memcmp(group->l2_rewrite.dst_mac.a, zero_mac.a, ETH_ALEN)) {
+ ngroup->has_set_eth_dst = true;
+ ngroup->set_eth_dst =
+ qemu_mac_strdup_printf(group->l2_rewrite.dst_mac.a);
+ }
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
+ case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
+ ngroup->has_vlan_id = true;
+ ngroup->vlan_id = ROCKER_GROUP_VLAN_GET(group->id);
+ ngroup->has_index = true;
+ ngroup->index = ROCKER_GROUP_INDEX_GET(group->id);
+ for (i = 0; i < group->l2_flood.group_count; i++) {
+ ngroup->has_group_ids = true;
+ id = g_malloc0(sizeof(*id));
+ id->value = group->l2_flood.group_ids[i];
+ id->next = ngroup->group_ids;
+ ngroup->group_ids = id;
+ }
+ break;
+ case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
+ ngroup->has_index = true;
+ ngroup->index = ROCKER_GROUP_INDEX_LONG_GET(group->id);
+ ngroup->has_group_id = true;
+ ngroup->group_id = group->l3_unicast.group_id;
+ if (group->l3_unicast.vlan_id) {
+ ngroup->has_set_vlan_id = true;
+ ngroup->set_vlan_id = ntohs(group->l3_unicast.vlan_id);
+ }
+ if (memcmp(group->l3_unicast.src_mac.a, zero_mac.a, ETH_ALEN)) {
+ ngroup->has_set_eth_src = true;
+ ngroup->set_eth_src =
+ qemu_mac_strdup_printf(group->l3_unicast.src_mac.a);
+ }
+ if (memcmp(group->l3_unicast.dst_mac.a, zero_mac.a, ETH_ALEN)) {
+ ngroup->has_set_eth_dst = true;
+ ngroup->set_eth_dst =
+ qemu_mac_strdup_printf(group->l3_unicast.dst_mac.a);
+ }
+ if (group->l3_unicast.ttl_check) {
+ ngroup->has_ttl_check = true;
+ ngroup->ttl_check = group->l3_unicast.ttl_check;
+ }
+ break;
+ }
+
+ new->next = flow_context->list;
+ flow_context->list = new;
+}
+
+RockerOfDpaGroupList *qmp_query_rocker_of_dpa_groups(const char *name,
+ bool has_type,
+ uint8_t type,
+ Error **errp)
+{
+ struct rocker *r;
+ struct world *w;
+ struct of_dpa *of_dpa;
+ struct of_dpa_group_fill_context fill_context = {
+ .list = NULL,
+ .type = type,
+ };
+
+ r = rocker_find(name);
+ if (!r) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "rocker %s not found", name);
+ return NULL;
+ }
+
+ w = rocker_get_world(r, ROCKER_WORLD_TYPE_OF_DPA);
+ if (!w) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "rocker %s doesn't have OF-DPA world", name);
+ return NULL;
+ }
+
+ of_dpa = world_private(w);
+
+ g_hash_table_foreach(of_dpa->group_tbl, of_dpa_group_fill, &fill_context);
+
+ return fill_context.list;
+}
+
+static WorldOps of_dpa_ops = {
+ .init = of_dpa_init,
+ .uninit = of_dpa_uninit,
+ .ig = of_dpa_ig,
+ .cmd = of_dpa_cmd,
+};
+
+World *of_dpa_world_alloc(Rocker *r)
+{
+ return world_alloc(r, sizeof(OfDpa), ROCKER_WORLD_TYPE_OF_DPA, &of_dpa_ops);
+}
diff --git a/hw/net/rocker/rocker_of_dpa.h b/hw/net/rocker/rocker_of_dpa.h
new file mode 100644
index 00000000..f3f6d778
--- /dev/null
+++ b/hw/net/rocker/rocker_of_dpa.h
@@ -0,0 +1,22 @@
+/*
+ * QEMU rocker switch emulation - OF-DPA flow processing support
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKER_OF_DPA_H_
+#define _ROCKER_OF_DPA_H_
+
+World *of_dpa_world_alloc(Rocker *r);
+
+#endif /* _ROCKER_OF_DPA_H_ */
diff --git a/hw/net/rocker/rocker_tlv.h b/hw/net/rocker/rocker_tlv.h
new file mode 100644
index 00000000..e3c4ab67
--- /dev/null
+++ b/hw/net/rocker/rocker_tlv.h
@@ -0,0 +1,244 @@
+/*
+ * QEMU rocker switch emulation - TLV parsing and composing
+ *
+ * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKER_TLV_H_
+#define _ROCKER_TLV_H_
+
+#define ROCKER_TLV_ALIGNTO 8U
+#define ROCKER_TLV_ALIGN(len) \
+ (((len) + ROCKER_TLV_ALIGNTO - 1) & ~(ROCKER_TLV_ALIGNTO - 1))
+#define ROCKER_TLV_HDRLEN ROCKER_TLV_ALIGN(sizeof(RockerTlv))
+
+/*
+ * <------- ROCKER_TLV_HDRLEN -------> <--- ROCKER_TLV_ALIGN(payload) --->
+ * +-----------------------------+- - -+- - - - - - - - - - - - - - -+- - -+
+ * | Header | Pad | Payload | Pad |
+ * | (RockerTlv) | ing | | ing |
+ * +-----------------------------+- - -+- - - - - - - - - - - - - - -+- - -+
+ * <--------------------------- tlv->len -------------------------->
+ */
+
+static inline RockerTlv *rocker_tlv_next(const RockerTlv *tlv, int *remaining)
+{
+ int totlen = ROCKER_TLV_ALIGN(le16_to_cpu(tlv->len));
+
+ *remaining -= totlen;
+ return (RockerTlv *) ((char *) tlv + totlen);
+}
+
+static inline int rocker_tlv_ok(const RockerTlv *tlv, int remaining)
+{
+ return remaining >= (int) ROCKER_TLV_HDRLEN &&
+ le16_to_cpu(tlv->len) >= ROCKER_TLV_HDRLEN &&
+ le16_to_cpu(tlv->len) <= remaining;
+}
+
+#define rocker_tlv_for_each(pos, head, len, rem) \
+ for (pos = head, rem = len; \
+ rocker_tlv_ok(pos, rem); \
+ pos = rocker_tlv_next(pos, &(rem)))
+
+#define rocker_tlv_for_each_nested(pos, tlv, rem) \
+ rocker_tlv_for_each(pos, rocker_tlv_data(tlv), rocker_tlv_len(tlv), rem)
+
+static inline int rocker_tlv_size(int payload)
+{
+ return ROCKER_TLV_HDRLEN + payload;
+}
+
+static inline int rocker_tlv_total_size(int payload)
+{
+ return ROCKER_TLV_ALIGN(rocker_tlv_size(payload));
+}
+
+static inline int rocker_tlv_padlen(int payload)
+{
+ return rocker_tlv_total_size(payload) - rocker_tlv_size(payload);
+}
+
+static inline int rocker_tlv_type(const RockerTlv *tlv)
+{
+ return le32_to_cpu(tlv->type);
+}
+
+static inline void *rocker_tlv_data(const RockerTlv *tlv)
+{
+ return (char *) tlv + ROCKER_TLV_HDRLEN;
+}
+
+static inline int rocker_tlv_len(const RockerTlv *tlv)
+{
+ return le16_to_cpu(tlv->len) - ROCKER_TLV_HDRLEN;
+}
+
+static inline uint8_t rocker_tlv_get_u8(const RockerTlv *tlv)
+{
+ return *(uint8_t *) rocker_tlv_data(tlv);
+}
+
+static inline uint16_t rocker_tlv_get_u16(const RockerTlv *tlv)
+{
+ return *(uint16_t *) rocker_tlv_data(tlv);
+}
+
+static inline uint32_t rocker_tlv_get_u32(const RockerTlv *tlv)
+{
+ return *(uint32_t *) rocker_tlv_data(tlv);
+}
+
+static inline uint64_t rocker_tlv_get_u64(const RockerTlv *tlv)
+{
+ return *(uint64_t *) rocker_tlv_data(tlv);
+}
+
+static inline uint16_t rocker_tlv_get_le16(const RockerTlv *tlv)
+{
+ return le16_to_cpup((uint16_t *) rocker_tlv_data(tlv));
+}
+
+static inline uint32_t rocker_tlv_get_le32(const RockerTlv *tlv)
+{
+ return le32_to_cpup((uint32_t *) rocker_tlv_data(tlv));
+}
+
+static inline uint64_t rocker_tlv_get_le64(const RockerTlv *tlv)
+{
+ return le64_to_cpup((uint64_t *) rocker_tlv_data(tlv));
+}
+
+static inline void rocker_tlv_parse(RockerTlv **tb, int maxtype,
+ const char *buf, int buf_len)
+{
+ const RockerTlv *tlv;
+ const RockerTlv *head = (const RockerTlv *) buf;
+ int rem;
+
+ memset(tb, 0, sizeof(RockerTlv *) * (maxtype + 1));
+
+ rocker_tlv_for_each(tlv, head, buf_len, rem) {
+ uint32_t type = rocker_tlv_type(tlv);
+
+ if (type > 0 && type <= maxtype) {
+ tb[type] = (RockerTlv *) tlv;
+ }
+ }
+}
+
+static inline void rocker_tlv_parse_nested(RockerTlv **tb, int maxtype,
+ const RockerTlv *tlv)
+{
+ rocker_tlv_parse(tb, maxtype, rocker_tlv_data(tlv), rocker_tlv_len(tlv));
+}
+
+static inline RockerTlv *rocker_tlv_start(char *buf, int buf_pos)
+{
+ return (RockerTlv *) (buf + buf_pos);
+}
+
+static inline void rocker_tlv_put_iov(char *buf, int *buf_pos,
+ int type, const struct iovec *iov,
+ const unsigned int iovcnt)
+{
+ size_t len = iov_size(iov, iovcnt);
+ int total_size = rocker_tlv_total_size(len);
+ RockerTlv *tlv;
+
+ tlv = rocker_tlv_start(buf, *buf_pos);
+ *buf_pos += total_size;
+ tlv->type = cpu_to_le32(type);
+ tlv->len = cpu_to_le16(rocker_tlv_size(len));
+ iov_to_buf(iov, iovcnt, 0, rocker_tlv_data(tlv), len);
+ memset((char *) tlv + le16_to_cpu(tlv->len), 0, rocker_tlv_padlen(len));
+}
+
+static inline void rocker_tlv_put(char *buf, int *buf_pos,
+ int type, int len, void *data)
+{
+ struct iovec iov = {
+ .iov_base = data,
+ .iov_len = len,
+ };
+
+ rocker_tlv_put_iov(buf, buf_pos, type, &iov, 1);
+}
+
+static inline void rocker_tlv_put_u8(char *buf, int *buf_pos,
+ int type, uint8_t value)
+{
+ rocker_tlv_put(buf, buf_pos, type, sizeof(uint8_t), &value);
+}
+
+static inline void rocker_tlv_put_u16(char *buf, int *buf_pos,
+ int type, uint16_t value)
+{
+ rocker_tlv_put(buf, buf_pos, type, sizeof(uint16_t), &value);
+}
+
+static inline void rocker_tlv_put_u32(char *buf, int *buf_pos,
+ int type, uint32_t value)
+{
+ rocker_tlv_put(buf, buf_pos, type, sizeof(uint32_t), &value);
+}
+
+static inline void rocker_tlv_put_u64(char *buf, int *buf_pos,
+ int type, uint64_t value)
+{
+ rocker_tlv_put(buf, buf_pos, type, sizeof(uint64_t), &value);
+}
+
+static inline void rocker_tlv_put_le16(char *buf, int *buf_pos,
+ int type, uint16_t value)
+{
+ value = cpu_to_le16(value);
+ rocker_tlv_put(buf, buf_pos, type, sizeof(uint16_t), &value);
+}
+
+static inline void rocker_tlv_put_le32(char *buf, int *buf_pos,
+ int type, uint32_t value)
+{
+ value = cpu_to_le32(value);
+ rocker_tlv_put(buf, buf_pos, type, sizeof(uint32_t), &value);
+}
+
+static inline void rocker_tlv_put_le64(char *buf, int *buf_pos,
+ int type, uint64_t value)
+{
+ value = cpu_to_le64(value);
+ rocker_tlv_put(buf, buf_pos, type, sizeof(uint64_t), &value);
+}
+
+static inline RockerTlv *rocker_tlv_nest_start(char *buf, int *buf_pos,
+ int type)
+{
+ RockerTlv *start = rocker_tlv_start(buf, *buf_pos);
+
+ rocker_tlv_put(buf, buf_pos, type, 0, NULL);
+ return start;
+}
+
+static inline void rocker_tlv_nest_end(char *buf, int *buf_pos,
+ RockerTlv *start)
+{
+ start->len = (char *) rocker_tlv_start(buf, *buf_pos) - (char *) start;
+}
+
+static inline void rocker_tlv_nest_cancel(char *buf, int *buf_pos,
+ RockerTlv *start)
+{
+ *buf_pos = (char *) start - buf;
+}
+
+#endif
diff --git a/hw/net/rocker/rocker_world.c b/hw/net/rocker/rocker_world.c
new file mode 100644
index 00000000..a6b18f17
--- /dev/null
+++ b/hw/net/rocker/rocker_world.c
@@ -0,0 +1,106 @@
+/*
+ * QEMU rocker switch emulation - switch worlds
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "qemu/iov.h"
+
+#include "rocker.h"
+#include "rocker_world.h"
+
+struct world {
+ Rocker *r;
+ enum rocker_world_type type;
+ WorldOps *ops;
+};
+
+ssize_t world_ingress(World *world, uint32_t pport,
+ const struct iovec *iov, int iovcnt)
+{
+ if (world->ops->ig) {
+ return world->ops->ig(world, pport, iov, iovcnt);
+ }
+
+ return -1;
+}
+
+int world_do_cmd(World *world, DescInfo *info,
+ char *buf, uint16_t cmd, RockerTlv *cmd_info_tlv)
+{
+ if (world->ops->cmd) {
+ return world->ops->cmd(world, info, buf, cmd, cmd_info_tlv);
+ }
+
+ return -ROCKER_ENOTSUP;
+}
+
+World *world_alloc(Rocker *r, size_t sizeof_private,
+ enum rocker_world_type type, WorldOps *ops)
+{
+ World *w = g_malloc0(sizeof(World) + sizeof_private);
+
+ if (w) {
+ w->r = r;
+ w->type = type;
+ w->ops = ops;
+ if (w->ops->init) {
+ w->ops->init(w);
+ }
+ }
+
+ return w;
+}
+
+void world_free(World *world)
+{
+ if (world->ops->uninit) {
+ world->ops->uninit(world);
+ }
+ g_free(world);
+}
+
+void world_reset(World *world)
+{
+ if (world->ops->uninit) {
+ world->ops->uninit(world);
+ }
+ if (world->ops->init) {
+ world->ops->init(world);
+ }
+}
+
+void *world_private(World *world)
+{
+ return world + 1;
+}
+
+Rocker *world_rocker(World *world)
+{
+ return world->r;
+}
+
+enum rocker_world_type world_type(World *world)
+{
+ return world->type;
+}
+
+const char *world_name(World *world)
+{
+ switch (world->type) {
+ case ROCKER_WORLD_TYPE_OF_DPA:
+ return "OF_DPA";
+ default:
+ return "unknown";
+ }
+}
diff --git a/hw/net/rocker/rocker_world.h b/hw/net/rocker/rocker_world.h
new file mode 100644
index 00000000..18d277b9
--- /dev/null
+++ b/hw/net/rocker/rocker_world.h
@@ -0,0 +1,60 @@
+/*
+ * QEMU rocker switch emulation - switch worlds
+ *
+ * Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKER_WORLD_H_
+#define _ROCKER_WORLD_H_
+
+#include "rocker_hw.h"
+
+enum rocker_world_type {
+ ROCKER_WORLD_TYPE_OF_DPA = ROCKER_PORT_MODE_OF_DPA,
+ ROCKER_WORLD_TYPE_MAX,
+};
+
+typedef int (world_init)(World *world);
+typedef void (world_uninit)(World *world);
+typedef ssize_t (world_ig)(World *world, uint32_t pport,
+ const struct iovec *iov, int iovcnt);
+typedef int (world_cmd)(World *world, DescInfo *info,
+ char *buf, uint16_t cmd,
+ RockerTlv *cmd_info_tlv);
+
+typedef struct world_ops {
+ world_init *init;
+ world_uninit *uninit;
+ world_ig *ig;
+ world_cmd *cmd;
+} WorldOps;
+
+ssize_t world_ingress(World *world, uint32_t pport,
+ const struct iovec *iov, int iovcnt);
+int world_do_cmd(World *world, DescInfo *info,
+ char *buf, uint16_t cmd, RockerTlv *cmd_info_tlv);
+
+World *world_alloc(Rocker *r, size_t sizeof_private,
+ enum rocker_world_type type, WorldOps *ops);
+void world_free(World *world);
+void world_reset(World *world);
+
+void *world_private(World *world);
+Rocker *world_rocker(World *world);
+
+enum rocker_world_type world_type(World *world);
+const char *world_name(World *world);
+
+World *rocker_get_world(Rocker *r, enum rocker_world_type type);
+
+#endif /* _ROCKER_WORLD_H_ */
diff --git a/hw/net/rtl8139.c b/hw/net/rtl8139.c
new file mode 100644
index 00000000..cb51613e
--- /dev/null
+++ b/hw/net/rtl8139.c
@@ -0,0 +1,3562 @@
+/**
+ * QEMU RTL8139 emulation
+ *
+ * Copyright (c) 2006 Igor Kovalenko
+ *
+ * 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.
+
+ * Modifications:
+ * 2006-Jan-28 Mark Malakanov : TSAD and CSCR implementation (for Windows driver)
+ *
+ * 2006-Apr-28 Juergen Lock : EEPROM emulation changes for FreeBSD driver
+ * HW revision ID changes for FreeBSD driver
+ *
+ * 2006-Jul-01 Igor Kovalenko : Implemented loopback mode for FreeBSD driver
+ * Corrected packet transfer reassembly routine for 8139C+ mode
+ * Rearranged debugging print statements
+ * Implemented PCI timer interrupt (disabled by default)
+ * Implemented Tally Counters, increased VM load/save version
+ * Implemented IP/TCP/UDP checksum task offloading
+ *
+ * 2006-Jul-04 Igor Kovalenko : Implemented TCP segmentation offloading
+ * Fixed MTU=1500 for produced ethernet frames
+ *
+ * 2006-Jul-09 Igor Kovalenko : Fixed TCP header length calculation while processing
+ * segmentation offloading
+ * Removed slirp.h dependency
+ * Added rx/tx buffer reset when enabling rx/tx operation
+ *
+ * 2010-Feb-04 Frediano Ziglio: Rewrote timer support using QEMU timer only
+ * when strictly needed (required for for
+ * Darwin)
+ * 2011-Mar-22 Benjamin Poirier: Implemented VLAN offloading
+ */
+
+/* For crc32 */
+#include <zlib.h>
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "sysemu/dma.h"
+#include "qemu/timer.h"
+#include "net/net.h"
+#include "hw/loader.h"
+#include "sysemu/sysemu.h"
+#include "qemu/iov.h"
+
+/* debug RTL8139 card */
+//#define DEBUG_RTL8139 1
+
+#define PCI_FREQUENCY 33000000L
+
+#define SET_MASKED(input, mask, curr) \
+ ( ( (input) & ~(mask) ) | ( (curr) & (mask) ) )
+
+/* arg % size for size which is a power of 2 */
+#define MOD2(input, size) \
+ ( ( input ) & ( size - 1 ) )
+
+#define ETHER_ADDR_LEN 6
+#define ETHER_TYPE_LEN 2
+#define ETH_HLEN (ETHER_ADDR_LEN * 2 + ETHER_TYPE_LEN)
+#define ETH_P_IP 0x0800 /* Internet Protocol packet */
+#define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */
+#define ETH_MTU 1500
+
+#define VLAN_TCI_LEN 2
+#define VLAN_HLEN (ETHER_TYPE_LEN + VLAN_TCI_LEN)
+
+#if defined (DEBUG_RTL8139)
+# define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, "RTL8139: " fmt, ## __VA_ARGS__); } while (0)
+#else
+static inline GCC_FMT_ATTR(1, 2) int DPRINTF(const char *fmt, ...)
+{
+ return 0;
+}
+#endif
+
+#define TYPE_RTL8139 "rtl8139"
+
+#define RTL8139(obj) \
+ OBJECT_CHECK(RTL8139State, (obj), TYPE_RTL8139)
+
+/* Symbolic offsets to registers. */
+enum RTL8139_registers {
+ MAC0 = 0, /* Ethernet hardware address. */
+ MAR0 = 8, /* Multicast filter. */
+ TxStatus0 = 0x10,/* Transmit status (Four 32bit registers). C mode only */
+ /* Dump Tally Conter control register(64bit). C+ mode only */
+ TxAddr0 = 0x20, /* Tx descriptors (also four 32bit). */
+ RxBuf = 0x30,
+ ChipCmd = 0x37,
+ RxBufPtr = 0x38,
+ RxBufAddr = 0x3A,
+ IntrMask = 0x3C,
+ IntrStatus = 0x3E,
+ TxConfig = 0x40,
+ RxConfig = 0x44,
+ Timer = 0x48, /* A general-purpose counter. */
+ RxMissed = 0x4C, /* 24 bits valid, write clears. */
+ Cfg9346 = 0x50,
+ Config0 = 0x51,
+ Config1 = 0x52,
+ FlashReg = 0x54,
+ MediaStatus = 0x58,
+ Config3 = 0x59,
+ Config4 = 0x5A, /* absent on RTL-8139A */
+ HltClk = 0x5B,
+ MultiIntr = 0x5C,
+ PCIRevisionID = 0x5E,
+ TxSummary = 0x60, /* TSAD register. Transmit Status of All Descriptors*/
+ BasicModeCtrl = 0x62,
+ BasicModeStatus = 0x64,
+ NWayAdvert = 0x66,
+ NWayLPAR = 0x68,
+ NWayExpansion = 0x6A,
+ /* Undocumented registers, but required for proper operation. */
+ FIFOTMS = 0x70, /* FIFO Control and test. */
+ CSCR = 0x74, /* Chip Status and Configuration Register. */
+ PARA78 = 0x78,
+ PARA7c = 0x7c, /* Magic transceiver parameter register. */
+ Config5 = 0xD8, /* absent on RTL-8139A */
+ /* C+ mode */
+ TxPoll = 0xD9, /* Tell chip to check Tx descriptors for work */
+ RxMaxSize = 0xDA, /* Max size of an Rx packet (8169 only) */
+ CpCmd = 0xE0, /* C+ Command register (C+ mode only) */
+ IntrMitigate = 0xE2, /* rx/tx interrupt mitigation control */
+ RxRingAddrLO = 0xE4, /* 64-bit start addr of Rx ring */
+ RxRingAddrHI = 0xE8, /* 64-bit start addr of Rx ring */
+ TxThresh = 0xEC, /* Early Tx threshold */
+};
+
+enum ClearBitMasks {
+ MultiIntrClear = 0xF000,
+ ChipCmdClear = 0xE2,
+ Config1Clear = (1<<7)|(1<<6)|(1<<3)|(1<<2)|(1<<1),
+};
+
+enum ChipCmdBits {
+ CmdReset = 0x10,
+ CmdRxEnb = 0x08,
+ CmdTxEnb = 0x04,
+ RxBufEmpty = 0x01,
+};
+
+/* C+ mode */
+enum CplusCmdBits {
+ CPlusRxVLAN = 0x0040, /* enable receive VLAN detagging */
+ CPlusRxChkSum = 0x0020, /* enable receive checksum offloading */
+ CPlusRxEnb = 0x0002,
+ CPlusTxEnb = 0x0001,
+};
+
+/* Interrupt register bits, using my own meaningful names. */
+enum IntrStatusBits {
+ PCIErr = 0x8000,
+ PCSTimeout = 0x4000,
+ RxFIFOOver = 0x40,
+ RxUnderrun = 0x20, /* Packet Underrun / Link Change */
+ RxOverflow = 0x10,
+ TxErr = 0x08,
+ TxOK = 0x04,
+ RxErr = 0x02,
+ RxOK = 0x01,
+
+ RxAckBits = RxFIFOOver | RxOverflow | RxOK,
+};
+
+enum TxStatusBits {
+ TxHostOwns = 0x2000,
+ TxUnderrun = 0x4000,
+ TxStatOK = 0x8000,
+ TxOutOfWindow = 0x20000000,
+ TxAborted = 0x40000000,
+ TxCarrierLost = 0x80000000,
+};
+enum RxStatusBits {
+ RxMulticast = 0x8000,
+ RxPhysical = 0x4000,
+ RxBroadcast = 0x2000,
+ RxBadSymbol = 0x0020,
+ RxRunt = 0x0010,
+ RxTooLong = 0x0008,
+ RxCRCErr = 0x0004,
+ RxBadAlign = 0x0002,
+ RxStatusOK = 0x0001,
+};
+
+/* Bits in RxConfig. */
+enum rx_mode_bits {
+ AcceptErr = 0x20,
+ AcceptRunt = 0x10,
+ AcceptBroadcast = 0x08,
+ AcceptMulticast = 0x04,
+ AcceptMyPhys = 0x02,
+ AcceptAllPhys = 0x01,
+};
+
+/* Bits in TxConfig. */
+enum tx_config_bits {
+
+ /* Interframe Gap Time. Only TxIFG96 doesn't violate IEEE 802.3 */
+ TxIFGShift = 24,
+ TxIFG84 = (0 << TxIFGShift), /* 8.4us / 840ns (10 / 100Mbps) */
+ TxIFG88 = (1 << TxIFGShift), /* 8.8us / 880ns (10 / 100Mbps) */
+ TxIFG92 = (2 << TxIFGShift), /* 9.2us / 920ns (10 / 100Mbps) */
+ TxIFG96 = (3 << TxIFGShift), /* 9.6us / 960ns (10 / 100Mbps) */
+
+ TxLoopBack = (1 << 18) | (1 << 17), /* enable loopback test mode */
+ TxCRC = (1 << 16), /* DISABLE appending CRC to end of Tx packets */
+ TxClearAbt = (1 << 0), /* Clear abort (WO) */
+ TxDMAShift = 8, /* DMA burst value (0-7) is shifted this many bits */
+ TxRetryShift = 4, /* TXRR value (0-15) is shifted this many bits */
+
+ TxVersionMask = 0x7C800000, /* mask out version bits 30-26, 23 */
+};
+
+
+/* Transmit Status of All Descriptors (TSAD) Register */
+enum TSAD_bits {
+ TSAD_TOK3 = 1<<15, // TOK bit of Descriptor 3
+ TSAD_TOK2 = 1<<14, // TOK bit of Descriptor 2
+ TSAD_TOK1 = 1<<13, // TOK bit of Descriptor 1
+ TSAD_TOK0 = 1<<12, // TOK bit of Descriptor 0
+ TSAD_TUN3 = 1<<11, // TUN bit of Descriptor 3
+ TSAD_TUN2 = 1<<10, // TUN bit of Descriptor 2
+ TSAD_TUN1 = 1<<9, // TUN bit of Descriptor 1
+ TSAD_TUN0 = 1<<8, // TUN bit of Descriptor 0
+ TSAD_TABT3 = 1<<07, // TABT bit of Descriptor 3
+ TSAD_TABT2 = 1<<06, // TABT bit of Descriptor 2
+ TSAD_TABT1 = 1<<05, // TABT bit of Descriptor 1
+ TSAD_TABT0 = 1<<04, // TABT bit of Descriptor 0
+ TSAD_OWN3 = 1<<03, // OWN bit of Descriptor 3
+ TSAD_OWN2 = 1<<02, // OWN bit of Descriptor 2
+ TSAD_OWN1 = 1<<01, // OWN bit of Descriptor 1
+ TSAD_OWN0 = 1<<00, // OWN bit of Descriptor 0
+};
+
+
+/* Bits in Config1 */
+enum Config1Bits {
+ Cfg1_PM_Enable = 0x01,
+ Cfg1_VPD_Enable = 0x02,
+ Cfg1_PIO = 0x04,
+ Cfg1_MMIO = 0x08,
+ LWAKE = 0x10, /* not on 8139, 8139A */
+ Cfg1_Driver_Load = 0x20,
+ Cfg1_LED0 = 0x40,
+ Cfg1_LED1 = 0x80,
+ SLEEP = (1 << 1), /* only on 8139, 8139A */
+ PWRDN = (1 << 0), /* only on 8139, 8139A */
+};
+
+/* Bits in Config3 */
+enum Config3Bits {
+ Cfg3_FBtBEn = (1 << 0), /* 1 = Fast Back to Back */
+ Cfg3_FuncRegEn = (1 << 1), /* 1 = enable CardBus Function registers */
+ Cfg3_CLKRUN_En = (1 << 2), /* 1 = enable CLKRUN */
+ Cfg3_CardB_En = (1 << 3), /* 1 = enable CardBus registers */
+ Cfg3_LinkUp = (1 << 4), /* 1 = wake up on link up */
+ Cfg3_Magic = (1 << 5), /* 1 = wake up on Magic Packet (tm) */
+ Cfg3_PARM_En = (1 << 6), /* 0 = software can set twister parameters */
+ Cfg3_GNTSel = (1 << 7), /* 1 = delay 1 clock from PCI GNT signal */
+};
+
+/* Bits in Config4 */
+enum Config4Bits {
+ LWPTN = (1 << 2), /* not on 8139, 8139A */
+};
+
+/* Bits in Config5 */
+enum Config5Bits {
+ Cfg5_PME_STS = (1 << 0), /* 1 = PCI reset resets PME_Status */
+ Cfg5_LANWake = (1 << 1), /* 1 = enable LANWake signal */
+ Cfg5_LDPS = (1 << 2), /* 0 = save power when link is down */
+ Cfg5_FIFOAddrPtr = (1 << 3), /* Realtek internal SRAM testing */
+ Cfg5_UWF = (1 << 4), /* 1 = accept unicast wakeup frame */
+ Cfg5_MWF = (1 << 5), /* 1 = accept multicast wakeup frame */
+ Cfg5_BWF = (1 << 6), /* 1 = accept broadcast wakeup frame */
+};
+
+enum RxConfigBits {
+ /* rx fifo threshold */
+ RxCfgFIFOShift = 13,
+ RxCfgFIFONone = (7 << RxCfgFIFOShift),
+
+ /* Max DMA burst */
+ RxCfgDMAShift = 8,
+ RxCfgDMAUnlimited = (7 << RxCfgDMAShift),
+
+ /* rx ring buffer length */
+ RxCfgRcv8K = 0,
+ RxCfgRcv16K = (1 << 11),
+ RxCfgRcv32K = (1 << 12),
+ RxCfgRcv64K = (1 << 11) | (1 << 12),
+
+ /* Disable packet wrap at end of Rx buffer. (not possible with 64k) */
+ RxNoWrap = (1 << 7),
+};
+
+/* Twister tuning parameters from RealTek.
+ Completely undocumented, but required to tune bad links on some boards. */
+/*
+enum CSCRBits {
+ CSCR_LinkOKBit = 0x0400,
+ CSCR_LinkChangeBit = 0x0800,
+ CSCR_LinkStatusBits = 0x0f000,
+ CSCR_LinkDownOffCmd = 0x003c0,
+ CSCR_LinkDownCmd = 0x0f3c0,
+*/
+enum CSCRBits {
+ CSCR_Testfun = 1<<15, /* 1 = Auto-neg speeds up internal timer, WO, def 0 */
+ CSCR_LD = 1<<9, /* Active low TPI link disable signal. When low, TPI still transmits link pulses and TPI stays in good link state. def 1*/
+ CSCR_HEART_BIT = 1<<8, /* 1 = HEART BEAT enable, 0 = HEART BEAT disable. HEART BEAT function is only valid in 10Mbps mode. def 1*/
+ CSCR_JBEN = 1<<7, /* 1 = enable jabber function. 0 = disable jabber function, def 1*/
+ CSCR_F_LINK_100 = 1<<6, /* Used to login force good link in 100Mbps for diagnostic purposes. 1 = DISABLE, 0 = ENABLE. def 1*/
+ CSCR_F_Connect = 1<<5, /* Assertion of this bit forces the disconnect function to be bypassed. def 0*/
+ CSCR_Con_status = 1<<3, /* This bit indicates the status of the connection. 1 = valid connected link detected; 0 = disconnected link detected. RO def 0*/
+ CSCR_Con_status_En = 1<<2, /* Assertion of this bit configures LED1 pin to indicate connection status. def 0*/
+ CSCR_PASS_SCR = 1<<0, /* Bypass Scramble, def 0*/
+};
+
+enum Cfg9346Bits {
+ Cfg9346_Normal = 0x00,
+ Cfg9346_Autoload = 0x40,
+ Cfg9346_Programming = 0x80,
+ Cfg9346_ConfigWrite = 0xC0,
+};
+
+typedef enum {
+ CH_8139 = 0,
+ CH_8139_K,
+ CH_8139A,
+ CH_8139A_G,
+ CH_8139B,
+ CH_8130,
+ CH_8139C,
+ CH_8100,
+ CH_8100B_8139D,
+ CH_8101,
+} chip_t;
+
+enum chip_flags {
+ HasHltClk = (1 << 0),
+ HasLWake = (1 << 1),
+};
+
+#define HW_REVID(b30, b29, b28, b27, b26, b23, b22) \
+ (b30<<30 | b29<<29 | b28<<28 | b27<<27 | b26<<26 | b23<<23 | b22<<22)
+#define HW_REVID_MASK HW_REVID(1, 1, 1, 1, 1, 1, 1)
+
+#define RTL8139_PCI_REVID_8139 0x10
+#define RTL8139_PCI_REVID_8139CPLUS 0x20
+
+#define RTL8139_PCI_REVID RTL8139_PCI_REVID_8139CPLUS
+
+/* Size is 64 * 16bit words */
+#define EEPROM_9346_ADDR_BITS 6
+#define EEPROM_9346_SIZE (1 << EEPROM_9346_ADDR_BITS)
+#define EEPROM_9346_ADDR_MASK (EEPROM_9346_SIZE - 1)
+
+enum Chip9346Operation
+{
+ Chip9346_op_mask = 0xc0, /* 10 zzzzzz */
+ Chip9346_op_read = 0x80, /* 10 AAAAAA */
+ Chip9346_op_write = 0x40, /* 01 AAAAAA D(15)..D(0) */
+ Chip9346_op_ext_mask = 0xf0, /* 11 zzzzzz */
+ Chip9346_op_write_enable = 0x30, /* 00 11zzzz */
+ Chip9346_op_write_all = 0x10, /* 00 01zzzz */
+ Chip9346_op_write_disable = 0x00, /* 00 00zzzz */
+};
+
+enum Chip9346Mode
+{
+ Chip9346_none = 0,
+ Chip9346_enter_command_mode,
+ Chip9346_read_command,
+ Chip9346_data_read, /* from output register */
+ Chip9346_data_write, /* to input register, then to contents at specified address */
+ Chip9346_data_write_all, /* to input register, then filling contents */
+};
+
+typedef struct EEprom9346
+{
+ uint16_t contents[EEPROM_9346_SIZE];
+ int mode;
+ uint32_t tick;
+ uint8_t address;
+ uint16_t input;
+ uint16_t output;
+
+ uint8_t eecs;
+ uint8_t eesk;
+ uint8_t eedi;
+ uint8_t eedo;
+} EEprom9346;
+
+typedef struct RTL8139TallyCounters
+{
+ /* Tally counters */
+ uint64_t TxOk;
+ uint64_t RxOk;
+ uint64_t TxERR;
+ uint32_t RxERR;
+ uint16_t MissPkt;
+ uint16_t FAE;
+ uint32_t Tx1Col;
+ uint32_t TxMCol;
+ uint64_t RxOkPhy;
+ uint64_t RxOkBrd;
+ uint32_t RxOkMul;
+ uint16_t TxAbt;
+ uint16_t TxUndrn;
+} RTL8139TallyCounters;
+
+/* Clears all tally counters */
+static void RTL8139TallyCounters_clear(RTL8139TallyCounters* counters);
+
+typedef struct RTL8139State {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ uint8_t phys[8]; /* mac address */
+ uint8_t mult[8]; /* multicast mask array */
+
+ uint32_t TxStatus[4]; /* TxStatus0 in C mode*/ /* also DTCCR[0] and DTCCR[1] in C+ mode */
+ uint32_t TxAddr[4]; /* TxAddr0 */
+ uint32_t RxBuf; /* Receive buffer */
+ uint32_t RxBufferSize;/* internal variable, receive ring buffer size in C mode */
+ uint32_t RxBufPtr;
+ uint32_t RxBufAddr;
+
+ uint16_t IntrStatus;
+ uint16_t IntrMask;
+
+ uint32_t TxConfig;
+ uint32_t RxConfig;
+ uint32_t RxMissed;
+
+ uint16_t CSCR;
+
+ uint8_t Cfg9346;
+ uint8_t Config0;
+ uint8_t Config1;
+ uint8_t Config3;
+ uint8_t Config4;
+ uint8_t Config5;
+
+ uint8_t clock_enabled;
+ uint8_t bChipCmdState;
+
+ uint16_t MultiIntr;
+
+ uint16_t BasicModeCtrl;
+ uint16_t BasicModeStatus;
+ uint16_t NWayAdvert;
+ uint16_t NWayLPAR;
+ uint16_t NWayExpansion;
+
+ uint16_t CpCmd;
+ uint8_t TxThresh;
+
+ NICState *nic;
+ NICConf conf;
+
+ /* C ring mode */
+ uint32_t currTxDesc;
+
+ /* C+ mode */
+ uint32_t cplus_enabled;
+
+ uint32_t currCPlusRxDesc;
+ uint32_t currCPlusTxDesc;
+
+ uint32_t RxRingAddrLO;
+ uint32_t RxRingAddrHI;
+
+ EEprom9346 eeprom;
+
+ uint32_t TCTR;
+ uint32_t TimerInt;
+ int64_t TCTR_base;
+
+ /* Tally counters */
+ RTL8139TallyCounters tally_counters;
+
+ /* Non-persistent data */
+ uint8_t *cplus_txbuffer;
+ int cplus_txbuffer_len;
+ int cplus_txbuffer_offset;
+
+ /* PCI interrupt timer */
+ QEMUTimer *timer;
+
+ MemoryRegion bar_io;
+ MemoryRegion bar_mem;
+
+ /* Support migration to/from old versions */
+ int rtl8139_mmio_io_addr_dummy;
+} RTL8139State;
+
+/* Writes tally counters to memory via DMA */
+static void RTL8139TallyCounters_dma_write(RTL8139State *s, dma_addr_t tc_addr);
+
+static void rtl8139_set_next_tctr_time(RTL8139State *s);
+
+static void prom9346_decode_command(EEprom9346 *eeprom, uint8_t command)
+{
+ DPRINTF("eeprom command 0x%02x\n", command);
+
+ switch (command & Chip9346_op_mask)
+ {
+ case Chip9346_op_read:
+ {
+ eeprom->address = command & EEPROM_9346_ADDR_MASK;
+ eeprom->output = eeprom->contents[eeprom->address];
+ eeprom->eedo = 0;
+ eeprom->tick = 0;
+ eeprom->mode = Chip9346_data_read;
+ DPRINTF("eeprom read from address 0x%02x data=0x%04x\n",
+ eeprom->address, eeprom->output);
+ }
+ break;
+
+ case Chip9346_op_write:
+ {
+ eeprom->address = command & EEPROM_9346_ADDR_MASK;
+ eeprom->input = 0;
+ eeprom->tick = 0;
+ eeprom->mode = Chip9346_none; /* Chip9346_data_write */
+ DPRINTF("eeprom begin write to address 0x%02x\n",
+ eeprom->address);
+ }
+ break;
+ default:
+ eeprom->mode = Chip9346_none;
+ switch (command & Chip9346_op_ext_mask)
+ {
+ case Chip9346_op_write_enable:
+ DPRINTF("eeprom write enabled\n");
+ break;
+ case Chip9346_op_write_all:
+ DPRINTF("eeprom begin write all\n");
+ break;
+ case Chip9346_op_write_disable:
+ DPRINTF("eeprom write disabled\n");
+ break;
+ }
+ break;
+ }
+}
+
+static void prom9346_shift_clock(EEprom9346 *eeprom)
+{
+ int bit = eeprom->eedi?1:0;
+
+ ++ eeprom->tick;
+
+ DPRINTF("eeprom: tick %d eedi=%d eedo=%d\n", eeprom->tick, eeprom->eedi,
+ eeprom->eedo);
+
+ switch (eeprom->mode)
+ {
+ case Chip9346_enter_command_mode:
+ if (bit)
+ {
+ eeprom->mode = Chip9346_read_command;
+ eeprom->tick = 0;
+ eeprom->input = 0;
+ DPRINTF("eeprom: +++ synchronized, begin command read\n");
+ }
+ break;
+
+ case Chip9346_read_command:
+ eeprom->input = (eeprom->input << 1) | (bit & 1);
+ if (eeprom->tick == 8)
+ {
+ prom9346_decode_command(eeprom, eeprom->input & 0xff);
+ }
+ break;
+
+ case Chip9346_data_read:
+ eeprom->eedo = (eeprom->output & 0x8000)?1:0;
+ eeprom->output <<= 1;
+ if (eeprom->tick == 16)
+ {
+#if 1
+ // the FreeBSD drivers (rl and re) don't explicitly toggle
+ // CS between reads (or does setting Cfg9346 to 0 count too?),
+ // so we need to enter wait-for-command state here
+ eeprom->mode = Chip9346_enter_command_mode;
+ eeprom->input = 0;
+ eeprom->tick = 0;
+
+ DPRINTF("eeprom: +++ end of read, awaiting next command\n");
+#else
+ // original behaviour
+ ++eeprom->address;
+ eeprom->address &= EEPROM_9346_ADDR_MASK;
+ eeprom->output = eeprom->contents[eeprom->address];
+ eeprom->tick = 0;
+
+ DPRINTF("eeprom: +++ read next address 0x%02x data=0x%04x\n",
+ eeprom->address, eeprom->output);
+#endif
+ }
+ break;
+
+ case Chip9346_data_write:
+ eeprom->input = (eeprom->input << 1) | (bit & 1);
+ if (eeprom->tick == 16)
+ {
+ DPRINTF("eeprom write to address 0x%02x data=0x%04x\n",
+ eeprom->address, eeprom->input);
+
+ eeprom->contents[eeprom->address] = eeprom->input;
+ eeprom->mode = Chip9346_none; /* waiting for next command after CS cycle */
+ eeprom->tick = 0;
+ eeprom->input = 0;
+ }
+ break;
+
+ case Chip9346_data_write_all:
+ eeprom->input = (eeprom->input << 1) | (bit & 1);
+ if (eeprom->tick == 16)
+ {
+ int i;
+ for (i = 0; i < EEPROM_9346_SIZE; i++)
+ {
+ eeprom->contents[i] = eeprom->input;
+ }
+ DPRINTF("eeprom filled with data=0x%04x\n", eeprom->input);
+
+ eeprom->mode = Chip9346_enter_command_mode;
+ eeprom->tick = 0;
+ eeprom->input = 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int prom9346_get_wire(RTL8139State *s)
+{
+ EEprom9346 *eeprom = &s->eeprom;
+ if (!eeprom->eecs)
+ return 0;
+
+ return eeprom->eedo;
+}
+
+/* FIXME: This should be merged into/replaced by eeprom93xx.c. */
+static void prom9346_set_wire(RTL8139State *s, int eecs, int eesk, int eedi)
+{
+ EEprom9346 *eeprom = &s->eeprom;
+ uint8_t old_eecs = eeprom->eecs;
+ uint8_t old_eesk = eeprom->eesk;
+
+ eeprom->eecs = eecs;
+ eeprom->eesk = eesk;
+ eeprom->eedi = eedi;
+
+ DPRINTF("eeprom: +++ wires CS=%d SK=%d DI=%d DO=%d\n", eeprom->eecs,
+ eeprom->eesk, eeprom->eedi, eeprom->eedo);
+
+ if (!old_eecs && eecs)
+ {
+ /* Synchronize start */
+ eeprom->tick = 0;
+ eeprom->input = 0;
+ eeprom->output = 0;
+ eeprom->mode = Chip9346_enter_command_mode;
+
+ DPRINTF("=== eeprom: begin access, enter command mode\n");
+ }
+
+ if (!eecs)
+ {
+ DPRINTF("=== eeprom: end access\n");
+ return;
+ }
+
+ if (!old_eesk && eesk)
+ {
+ /* SK front rules */
+ prom9346_shift_clock(eeprom);
+ }
+}
+
+static void rtl8139_update_irq(RTL8139State *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int isr;
+ isr = (s->IntrStatus & s->IntrMask) & 0xffff;
+
+ DPRINTF("Set IRQ to %d (%04x %04x)\n", isr ? 1 : 0, s->IntrStatus,
+ s->IntrMask);
+
+ pci_set_irq(d, (isr != 0));
+}
+
+static int rtl8139_RxWrap(RTL8139State *s)
+{
+ /* wrapping enabled; assume 1.5k more buffer space if size < 65536 */
+ return (s->RxConfig & (1 << 7));
+}
+
+static int rtl8139_receiver_enabled(RTL8139State *s)
+{
+ return s->bChipCmdState & CmdRxEnb;
+}
+
+static int rtl8139_transmitter_enabled(RTL8139State *s)
+{
+ return s->bChipCmdState & CmdTxEnb;
+}
+
+static int rtl8139_cp_receiver_enabled(RTL8139State *s)
+{
+ return s->CpCmd & CPlusRxEnb;
+}
+
+static int rtl8139_cp_transmitter_enabled(RTL8139State *s)
+{
+ return s->CpCmd & CPlusTxEnb;
+}
+
+static void rtl8139_write_buffer(RTL8139State *s, const void *buf, int size)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ if (s->RxBufAddr + size > s->RxBufferSize)
+ {
+ int wrapped = MOD2(s->RxBufAddr + size, s->RxBufferSize);
+
+ /* write packet data */
+ if (wrapped && !(s->RxBufferSize < 65536 && rtl8139_RxWrap(s)))
+ {
+ DPRINTF(">>> rx packet wrapped in buffer at %d\n", size - wrapped);
+
+ if (size > wrapped)
+ {
+ pci_dma_write(d, s->RxBuf + s->RxBufAddr,
+ buf, size-wrapped);
+ }
+
+ /* reset buffer pointer */
+ s->RxBufAddr = 0;
+
+ pci_dma_write(d, s->RxBuf + s->RxBufAddr,
+ buf + (size-wrapped), wrapped);
+
+ s->RxBufAddr = wrapped;
+
+ return;
+ }
+ }
+
+ /* non-wrapping path or overwrapping enabled */
+ pci_dma_write(d, s->RxBuf + s->RxBufAddr, buf, size);
+
+ s->RxBufAddr += size;
+}
+
+#define MIN_BUF_SIZE 60
+static inline dma_addr_t rtl8139_addr64(uint32_t low, uint32_t high)
+{
+ return low | ((uint64_t)high << 32);
+}
+
+/* Workaround for buggy guest driver such as linux who allocates rx
+ * rings after the receiver were enabled. */
+static bool rtl8139_cp_rx_valid(RTL8139State *s)
+{
+ return !(s->RxRingAddrLO == 0 && s->RxRingAddrHI == 0);
+}
+
+static int rtl8139_can_receive(NetClientState *nc)
+{
+ RTL8139State *s = qemu_get_nic_opaque(nc);
+ int avail;
+
+ /* Receive (drop) packets if card is disabled. */
+ if (!s->clock_enabled)
+ return 1;
+ if (!rtl8139_receiver_enabled(s))
+ return 1;
+
+ if (rtl8139_cp_receiver_enabled(s) && rtl8139_cp_rx_valid(s)) {
+ /* ??? Flow control not implemented in c+ mode.
+ This is a hack to work around slirp deficiencies anyway. */
+ return 1;
+ } else {
+ avail = MOD2(s->RxBufferSize + s->RxBufPtr - s->RxBufAddr,
+ s->RxBufferSize);
+ return (avail == 0 || avail >= 1514 || (s->IntrMask & RxOverflow));
+ }
+}
+
+static ssize_t rtl8139_do_receive(NetClientState *nc, const uint8_t *buf, size_t size_, int do_interrupt)
+{
+ RTL8139State *s = qemu_get_nic_opaque(nc);
+ PCIDevice *d = PCI_DEVICE(s);
+ /* size is the length of the buffer passed to the driver */
+ int size = size_;
+ const uint8_t *dot1q_buf = NULL;
+
+ uint32_t packet_header = 0;
+
+ uint8_t buf1[MIN_BUF_SIZE + VLAN_HLEN];
+ static const uint8_t broadcast_macaddr[6] =
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+ DPRINTF(">>> received len=%d\n", size);
+
+ /* test if board clock is stopped */
+ if (!s->clock_enabled)
+ {
+ DPRINTF("stopped ==========================\n");
+ return -1;
+ }
+
+ /* first check if receiver is enabled */
+
+ if (!rtl8139_receiver_enabled(s))
+ {
+ DPRINTF("receiver disabled ================\n");
+ return -1;
+ }
+
+ /* XXX: check this */
+ if (s->RxConfig & AcceptAllPhys) {
+ /* promiscuous: receive all */
+ DPRINTF(">>> packet received in promiscuous mode\n");
+
+ } else {
+ if (!memcmp(buf, broadcast_macaddr, 6)) {
+ /* broadcast address */
+ if (!(s->RxConfig & AcceptBroadcast))
+ {
+ DPRINTF(">>> broadcast packet rejected\n");
+
+ /* update tally counter */
+ ++s->tally_counters.RxERR;
+
+ return size;
+ }
+
+ packet_header |= RxBroadcast;
+
+ DPRINTF(">>> broadcast packet received\n");
+
+ /* update tally counter */
+ ++s->tally_counters.RxOkBrd;
+
+ } else if (buf[0] & 0x01) {
+ /* multicast */
+ if (!(s->RxConfig & AcceptMulticast))
+ {
+ DPRINTF(">>> multicast packet rejected\n");
+
+ /* update tally counter */
+ ++s->tally_counters.RxERR;
+
+ return size;
+ }
+
+ int mcast_idx = compute_mcast_idx(buf);
+
+ if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7))))
+ {
+ DPRINTF(">>> multicast address mismatch\n");
+
+ /* update tally counter */
+ ++s->tally_counters.RxERR;
+
+ return size;
+ }
+
+ packet_header |= RxMulticast;
+
+ DPRINTF(">>> multicast packet received\n");
+
+ /* update tally counter */
+ ++s->tally_counters.RxOkMul;
+
+ } else if (s->phys[0] == buf[0] &&
+ s->phys[1] == buf[1] &&
+ s->phys[2] == buf[2] &&
+ s->phys[3] == buf[3] &&
+ s->phys[4] == buf[4] &&
+ s->phys[5] == buf[5]) {
+ /* match */
+ if (!(s->RxConfig & AcceptMyPhys))
+ {
+ DPRINTF(">>> rejecting physical address matching packet\n");
+
+ /* update tally counter */
+ ++s->tally_counters.RxERR;
+
+ return size;
+ }
+
+ packet_header |= RxPhysical;
+
+ DPRINTF(">>> physical address matching packet received\n");
+
+ /* update tally counter */
+ ++s->tally_counters.RxOkPhy;
+
+ } else {
+
+ DPRINTF(">>> unknown packet\n");
+
+ /* update tally counter */
+ ++s->tally_counters.RxERR;
+
+ return size;
+ }
+ }
+
+ /* if too small buffer, then expand it
+ * Include some tailroom in case a vlan tag is later removed. */
+ if (size < MIN_BUF_SIZE + VLAN_HLEN) {
+ memcpy(buf1, buf, size);
+ memset(buf1 + size, 0, MIN_BUF_SIZE + VLAN_HLEN - size);
+ buf = buf1;
+ if (size < MIN_BUF_SIZE) {
+ size = MIN_BUF_SIZE;
+ }
+ }
+
+ if (rtl8139_cp_receiver_enabled(s))
+ {
+ if (!rtl8139_cp_rx_valid(s)) {
+ return size;
+ }
+
+ DPRINTF("in C+ Rx mode ================\n");
+
+ /* begin C+ receiver mode */
+
+/* w0 ownership flag */
+#define CP_RX_OWN (1<<31)
+/* w0 end of ring flag */
+#define CP_RX_EOR (1<<30)
+/* w0 bits 0...12 : buffer size */
+#define CP_RX_BUFFER_SIZE_MASK ((1<<13) - 1)
+/* w1 tag available flag */
+#define CP_RX_TAVA (1<<16)
+/* w1 bits 0...15 : VLAN tag */
+#define CP_RX_VLAN_TAG_MASK ((1<<16) - 1)
+/* w2 low 32bit of Rx buffer ptr */
+/* w3 high 32bit of Rx buffer ptr */
+
+ int descriptor = s->currCPlusRxDesc;
+ dma_addr_t cplus_rx_ring_desc;
+
+ cplus_rx_ring_desc = rtl8139_addr64(s->RxRingAddrLO, s->RxRingAddrHI);
+ cplus_rx_ring_desc += 16 * descriptor;
+
+ DPRINTF("+++ C+ mode reading RX descriptor %d from host memory at "
+ "%08x %08x = "DMA_ADDR_FMT"\n", descriptor, s->RxRingAddrHI,
+ s->RxRingAddrLO, cplus_rx_ring_desc);
+
+ uint32_t val, rxdw0,rxdw1,rxbufLO,rxbufHI;
+
+ pci_dma_read(d, cplus_rx_ring_desc, &val, 4);
+ rxdw0 = le32_to_cpu(val);
+ pci_dma_read(d, cplus_rx_ring_desc+4, &val, 4);
+ rxdw1 = le32_to_cpu(val);
+ pci_dma_read(d, cplus_rx_ring_desc+8, &val, 4);
+ rxbufLO = le32_to_cpu(val);
+ pci_dma_read(d, cplus_rx_ring_desc+12, &val, 4);
+ rxbufHI = le32_to_cpu(val);
+
+ DPRINTF("+++ C+ mode RX descriptor %d %08x %08x %08x %08x\n",
+ descriptor, rxdw0, rxdw1, rxbufLO, rxbufHI);
+
+ if (!(rxdw0 & CP_RX_OWN))
+ {
+ DPRINTF("C+ Rx mode : descriptor %d is owned by host\n",
+ descriptor);
+
+ s->IntrStatus |= RxOverflow;
+ ++s->RxMissed;
+
+ /* update tally counter */
+ ++s->tally_counters.RxERR;
+ ++s->tally_counters.MissPkt;
+
+ rtl8139_update_irq(s);
+ return size_;
+ }
+
+ uint32_t rx_space = rxdw0 & CP_RX_BUFFER_SIZE_MASK;
+
+ /* write VLAN info to descriptor variables. */
+ if (s->CpCmd & CPlusRxVLAN && be16_to_cpup((uint16_t *)
+ &buf[ETHER_ADDR_LEN * 2]) == ETH_P_8021Q) {
+ dot1q_buf = &buf[ETHER_ADDR_LEN * 2];
+ size -= VLAN_HLEN;
+ /* if too small buffer, use the tailroom added duing expansion */
+ if (size < MIN_BUF_SIZE) {
+ size = MIN_BUF_SIZE;
+ }
+
+ rxdw1 &= ~CP_RX_VLAN_TAG_MASK;
+ /* BE + ~le_to_cpu()~ + cpu_to_le() = BE */
+ rxdw1 |= CP_RX_TAVA | le16_to_cpup((uint16_t *)
+ &dot1q_buf[ETHER_TYPE_LEN]);
+
+ DPRINTF("C+ Rx mode : extracted vlan tag with tci: ""%u\n",
+ be16_to_cpup((uint16_t *)&dot1q_buf[ETHER_TYPE_LEN]));
+ } else {
+ /* reset VLAN tag flag */
+ rxdw1 &= ~CP_RX_TAVA;
+ }
+
+ /* TODO: scatter the packet over available receive ring descriptors space */
+
+ if (size+4 > rx_space)
+ {
+ DPRINTF("C+ Rx mode : descriptor %d size %d received %d + 4\n",
+ descriptor, rx_space, size);
+
+ s->IntrStatus |= RxOverflow;
+ ++s->RxMissed;
+
+ /* update tally counter */
+ ++s->tally_counters.RxERR;
+ ++s->tally_counters.MissPkt;
+
+ rtl8139_update_irq(s);
+ return size_;
+ }
+
+ dma_addr_t rx_addr = rtl8139_addr64(rxbufLO, rxbufHI);
+
+ /* receive/copy to target memory */
+ if (dot1q_buf) {
+ pci_dma_write(d, rx_addr, buf, 2 * ETHER_ADDR_LEN);
+ pci_dma_write(d, rx_addr + 2 * ETHER_ADDR_LEN,
+ buf + 2 * ETHER_ADDR_LEN + VLAN_HLEN,
+ size - 2 * ETHER_ADDR_LEN);
+ } else {
+ pci_dma_write(d, rx_addr, buf, size);
+ }
+
+ if (s->CpCmd & CPlusRxChkSum)
+ {
+ /* do some packet checksumming */
+ }
+
+ /* write checksum */
+ val = cpu_to_le32(crc32(0, buf, size_));
+ pci_dma_write(d, rx_addr+size, (uint8_t *)&val, 4);
+
+/* first segment of received packet flag */
+#define CP_RX_STATUS_FS (1<<29)
+/* last segment of received packet flag */
+#define CP_RX_STATUS_LS (1<<28)
+/* multicast packet flag */
+#define CP_RX_STATUS_MAR (1<<26)
+/* physical-matching packet flag */
+#define CP_RX_STATUS_PAM (1<<25)
+/* broadcast packet flag */
+#define CP_RX_STATUS_BAR (1<<24)
+/* runt packet flag */
+#define CP_RX_STATUS_RUNT (1<<19)
+/* crc error flag */
+#define CP_RX_STATUS_CRC (1<<18)
+/* IP checksum error flag */
+#define CP_RX_STATUS_IPF (1<<15)
+/* UDP checksum error flag */
+#define CP_RX_STATUS_UDPF (1<<14)
+/* TCP checksum error flag */
+#define CP_RX_STATUS_TCPF (1<<13)
+
+ /* transfer ownership to target */
+ rxdw0 &= ~CP_RX_OWN;
+
+ /* set first segment bit */
+ rxdw0 |= CP_RX_STATUS_FS;
+
+ /* set last segment bit */
+ rxdw0 |= CP_RX_STATUS_LS;
+
+ /* set received packet type flags */
+ if (packet_header & RxBroadcast)
+ rxdw0 |= CP_RX_STATUS_BAR;
+ if (packet_header & RxMulticast)
+ rxdw0 |= CP_RX_STATUS_MAR;
+ if (packet_header & RxPhysical)
+ rxdw0 |= CP_RX_STATUS_PAM;
+
+ /* set received size */
+ rxdw0 &= ~CP_RX_BUFFER_SIZE_MASK;
+ rxdw0 |= (size+4);
+
+ /* update ring data */
+ val = cpu_to_le32(rxdw0);
+ pci_dma_write(d, cplus_rx_ring_desc, (uint8_t *)&val, 4);
+ val = cpu_to_le32(rxdw1);
+ pci_dma_write(d, cplus_rx_ring_desc+4, (uint8_t *)&val, 4);
+
+ /* update tally counter */
+ ++s->tally_counters.RxOk;
+
+ /* seek to next Rx descriptor */
+ if (rxdw0 & CP_RX_EOR)
+ {
+ s->currCPlusRxDesc = 0;
+ }
+ else
+ {
+ ++s->currCPlusRxDesc;
+ }
+
+ DPRINTF("done C+ Rx mode ----------------\n");
+
+ }
+ else
+ {
+ DPRINTF("in ring Rx mode ================\n");
+
+ /* begin ring receiver mode */
+ int avail = MOD2(s->RxBufferSize + s->RxBufPtr - s->RxBufAddr, s->RxBufferSize);
+
+ /* if receiver buffer is empty then avail == 0 */
+
+#define RX_ALIGN(x) (((x) + 3) & ~0x3)
+
+ if (avail != 0 && RX_ALIGN(size + 8) >= avail)
+ {
+ DPRINTF("rx overflow: rx buffer length %d head 0x%04x "
+ "read 0x%04x === available 0x%04x need 0x%04x\n",
+ s->RxBufferSize, s->RxBufAddr, s->RxBufPtr, avail, size + 8);
+
+ s->IntrStatus |= RxOverflow;
+ ++s->RxMissed;
+ rtl8139_update_irq(s);
+ return 0;
+ }
+
+ packet_header |= RxStatusOK;
+
+ packet_header |= (((size+4) << 16) & 0xffff0000);
+
+ /* write header */
+ uint32_t val = cpu_to_le32(packet_header);
+
+ rtl8139_write_buffer(s, (uint8_t *)&val, 4);
+
+ rtl8139_write_buffer(s, buf, size);
+
+ /* write checksum */
+ val = cpu_to_le32(crc32(0, buf, size));
+ rtl8139_write_buffer(s, (uint8_t *)&val, 4);
+
+ /* correct buffer write pointer */
+ s->RxBufAddr = MOD2(RX_ALIGN(s->RxBufAddr), s->RxBufferSize);
+
+ /* now we can signal we have received something */
+
+ DPRINTF("received: rx buffer length %d head 0x%04x read 0x%04x\n",
+ s->RxBufferSize, s->RxBufAddr, s->RxBufPtr);
+ }
+
+ s->IntrStatus |= RxOK;
+
+ if (do_interrupt)
+ {
+ rtl8139_update_irq(s);
+ }
+
+ return size_;
+}
+
+static ssize_t rtl8139_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ return rtl8139_do_receive(nc, buf, size, 1);
+}
+
+static void rtl8139_reset_rxring(RTL8139State *s, uint32_t bufferSize)
+{
+ s->RxBufferSize = bufferSize;
+ s->RxBufPtr = 0;
+ s->RxBufAddr = 0;
+}
+
+static void rtl8139_reset(DeviceState *d)
+{
+ RTL8139State *s = RTL8139(d);
+ int i;
+
+ /* restore MAC address */
+ memcpy(s->phys, s->conf.macaddr.a, 6);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->phys);
+
+ /* reset interrupt mask */
+ s->IntrStatus = 0;
+ s->IntrMask = 0;
+
+ rtl8139_update_irq(s);
+
+ /* mark all status registers as owned by host */
+ for (i = 0; i < 4; ++i)
+ {
+ s->TxStatus[i] = TxHostOwns;
+ }
+
+ s->currTxDesc = 0;
+ s->currCPlusRxDesc = 0;
+ s->currCPlusTxDesc = 0;
+
+ s->RxRingAddrLO = 0;
+ s->RxRingAddrHI = 0;
+
+ s->RxBuf = 0;
+
+ rtl8139_reset_rxring(s, 8192);
+
+ /* ACK the reset */
+ s->TxConfig = 0;
+
+#if 0
+// s->TxConfig |= HW_REVID(1, 0, 0, 0, 0, 0, 0); // RTL-8139 HasHltClk
+ s->clock_enabled = 0;
+#else
+ s->TxConfig |= HW_REVID(1, 1, 1, 0, 1, 1, 0); // RTL-8139C+ HasLWake
+ s->clock_enabled = 1;
+#endif
+
+ s->bChipCmdState = CmdReset; /* RxBufEmpty bit is calculated on read from ChipCmd */;
+
+ /* set initial state data */
+ s->Config0 = 0x0; /* No boot ROM */
+ s->Config1 = 0xC; /* IO mapped and MEM mapped registers available */
+ s->Config3 = 0x1; /* fast back-to-back compatible */
+ s->Config5 = 0x0;
+
+ s->CSCR = CSCR_F_LINK_100 | CSCR_HEART_BIT | CSCR_LD;
+
+ s->CpCmd = 0x0; /* reset C+ mode */
+ s->cplus_enabled = 0;
+
+
+// s->BasicModeCtrl = 0x3100; // 100Mbps, full duplex, autonegotiation
+// s->BasicModeCtrl = 0x2100; // 100Mbps, full duplex
+ s->BasicModeCtrl = 0x1000; // autonegotiation
+
+ s->BasicModeStatus = 0x7809;
+ //s->BasicModeStatus |= 0x0040; /* UTP medium */
+ s->BasicModeStatus |= 0x0020; /* autonegotiation completed */
+ /* preserve link state */
+ s->BasicModeStatus |= qemu_get_queue(s->nic)->link_down ? 0 : 0x04;
+
+ s->NWayAdvert = 0x05e1; /* all modes, full duplex */
+ s->NWayLPAR = 0x05e1; /* all modes, full duplex */
+ s->NWayExpansion = 0x0001; /* autonegotiation supported */
+
+ /* also reset timer and disable timer interrupt */
+ s->TCTR = 0;
+ s->TimerInt = 0;
+ s->TCTR_base = 0;
+ rtl8139_set_next_tctr_time(s);
+
+ /* reset tally counters */
+ RTL8139TallyCounters_clear(&s->tally_counters);
+}
+
+static void RTL8139TallyCounters_clear(RTL8139TallyCounters* counters)
+{
+ counters->TxOk = 0;
+ counters->RxOk = 0;
+ counters->TxERR = 0;
+ counters->RxERR = 0;
+ counters->MissPkt = 0;
+ counters->FAE = 0;
+ counters->Tx1Col = 0;
+ counters->TxMCol = 0;
+ counters->RxOkPhy = 0;
+ counters->RxOkBrd = 0;
+ counters->RxOkMul = 0;
+ counters->TxAbt = 0;
+ counters->TxUndrn = 0;
+}
+
+static void RTL8139TallyCounters_dma_write(RTL8139State *s, dma_addr_t tc_addr)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ RTL8139TallyCounters *tally_counters = &s->tally_counters;
+ uint16_t val16;
+ uint32_t val32;
+ uint64_t val64;
+
+ val64 = cpu_to_le64(tally_counters->TxOk);
+ pci_dma_write(d, tc_addr + 0, (uint8_t *)&val64, 8);
+
+ val64 = cpu_to_le64(tally_counters->RxOk);
+ pci_dma_write(d, tc_addr + 8, (uint8_t *)&val64, 8);
+
+ val64 = cpu_to_le64(tally_counters->TxERR);
+ pci_dma_write(d, tc_addr + 16, (uint8_t *)&val64, 8);
+
+ val32 = cpu_to_le32(tally_counters->RxERR);
+ pci_dma_write(d, tc_addr + 24, (uint8_t *)&val32, 4);
+
+ val16 = cpu_to_le16(tally_counters->MissPkt);
+ pci_dma_write(d, tc_addr + 28, (uint8_t *)&val16, 2);
+
+ val16 = cpu_to_le16(tally_counters->FAE);
+ pci_dma_write(d, tc_addr + 30, (uint8_t *)&val16, 2);
+
+ val32 = cpu_to_le32(tally_counters->Tx1Col);
+ pci_dma_write(d, tc_addr + 32, (uint8_t *)&val32, 4);
+
+ val32 = cpu_to_le32(tally_counters->TxMCol);
+ pci_dma_write(d, tc_addr + 36, (uint8_t *)&val32, 4);
+
+ val64 = cpu_to_le64(tally_counters->RxOkPhy);
+ pci_dma_write(d, tc_addr + 40, (uint8_t *)&val64, 8);
+
+ val64 = cpu_to_le64(tally_counters->RxOkBrd);
+ pci_dma_write(d, tc_addr + 48, (uint8_t *)&val64, 8);
+
+ val32 = cpu_to_le32(tally_counters->RxOkMul);
+ pci_dma_write(d, tc_addr + 56, (uint8_t *)&val32, 4);
+
+ val16 = cpu_to_le16(tally_counters->TxAbt);
+ pci_dma_write(d, tc_addr + 60, (uint8_t *)&val16, 2);
+
+ val16 = cpu_to_le16(tally_counters->TxUndrn);
+ pci_dma_write(d, tc_addr + 62, (uint8_t *)&val16, 2);
+}
+
+/* Loads values of tally counters from VM state file */
+
+static const VMStateDescription vmstate_tally_counters = {
+ .name = "tally_counters",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(TxOk, RTL8139TallyCounters),
+ VMSTATE_UINT64(RxOk, RTL8139TallyCounters),
+ VMSTATE_UINT64(TxERR, RTL8139TallyCounters),
+ VMSTATE_UINT32(RxERR, RTL8139TallyCounters),
+ VMSTATE_UINT16(MissPkt, RTL8139TallyCounters),
+ VMSTATE_UINT16(FAE, RTL8139TallyCounters),
+ VMSTATE_UINT32(Tx1Col, RTL8139TallyCounters),
+ VMSTATE_UINT32(TxMCol, RTL8139TallyCounters),
+ VMSTATE_UINT64(RxOkPhy, RTL8139TallyCounters),
+ VMSTATE_UINT64(RxOkBrd, RTL8139TallyCounters),
+ VMSTATE_UINT16(TxAbt, RTL8139TallyCounters),
+ VMSTATE_UINT16(TxUndrn, RTL8139TallyCounters),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rtl8139_ChipCmd_write(RTL8139State *s, uint32_t val)
+{
+ DeviceState *d = DEVICE(s);
+
+ val &= 0xff;
+
+ DPRINTF("ChipCmd write val=0x%08x\n", val);
+
+ if (val & CmdReset)
+ {
+ DPRINTF("ChipCmd reset\n");
+ rtl8139_reset(d);
+ }
+ if (val & CmdRxEnb)
+ {
+ DPRINTF("ChipCmd enable receiver\n");
+
+ s->currCPlusRxDesc = 0;
+ }
+ if (val & CmdTxEnb)
+ {
+ DPRINTF("ChipCmd enable transmitter\n");
+
+ s->currCPlusTxDesc = 0;
+ }
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0xe3, s->bChipCmdState);
+
+ /* Deassert reset pin before next read */
+ val &= ~CmdReset;
+
+ s->bChipCmdState = val;
+}
+
+static int rtl8139_RxBufferEmpty(RTL8139State *s)
+{
+ int unread = MOD2(s->RxBufferSize + s->RxBufAddr - s->RxBufPtr, s->RxBufferSize);
+
+ if (unread != 0)
+ {
+ DPRINTF("receiver buffer data available 0x%04x\n", unread);
+ return 0;
+ }
+
+ DPRINTF("receiver buffer is empty\n");
+
+ return 1;
+}
+
+static uint32_t rtl8139_ChipCmd_read(RTL8139State *s)
+{
+ uint32_t ret = s->bChipCmdState;
+
+ if (rtl8139_RxBufferEmpty(s))
+ ret |= RxBufEmpty;
+
+ DPRINTF("ChipCmd read val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_CpCmd_write(RTL8139State *s, uint32_t val)
+{
+ val &= 0xffff;
+
+ DPRINTF("C+ command register write(w) val=0x%04x\n", val);
+
+ s->cplus_enabled = 1;
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0xff84, s->CpCmd);
+
+ s->CpCmd = val;
+}
+
+static uint32_t rtl8139_CpCmd_read(RTL8139State *s)
+{
+ uint32_t ret = s->CpCmd;
+
+ DPRINTF("C+ command register read(w) val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_IntrMitigate_write(RTL8139State *s, uint32_t val)
+{
+ DPRINTF("C+ IntrMitigate register write(w) val=0x%04x\n", val);
+}
+
+static uint32_t rtl8139_IntrMitigate_read(RTL8139State *s)
+{
+ uint32_t ret = 0;
+
+ DPRINTF("C+ IntrMitigate register read(w) val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static int rtl8139_config_writable(RTL8139State *s)
+{
+ if ((s->Cfg9346 & Chip9346_op_mask) == Cfg9346_ConfigWrite)
+ {
+ return 1;
+ }
+
+ DPRINTF("Configuration registers are write-protected\n");
+
+ return 0;
+}
+
+static void rtl8139_BasicModeCtrl_write(RTL8139State *s, uint32_t val)
+{
+ val &= 0xffff;
+
+ DPRINTF("BasicModeCtrl register write(w) val=0x%04x\n", val);
+
+ /* mask unwritable bits */
+ uint32_t mask = 0x4cff;
+
+ if (1 || !rtl8139_config_writable(s))
+ {
+ /* Speed setting and autonegotiation enable bits are read-only */
+ mask |= 0x3000;
+ /* Duplex mode setting is read-only */
+ mask |= 0x0100;
+ }
+
+ val = SET_MASKED(val, mask, s->BasicModeCtrl);
+
+ s->BasicModeCtrl = val;
+}
+
+static uint32_t rtl8139_BasicModeCtrl_read(RTL8139State *s)
+{
+ uint32_t ret = s->BasicModeCtrl;
+
+ DPRINTF("BasicModeCtrl register read(w) val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_BasicModeStatus_write(RTL8139State *s, uint32_t val)
+{
+ val &= 0xffff;
+
+ DPRINTF("BasicModeStatus register write(w) val=0x%04x\n", val);
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0xff3f, s->BasicModeStatus);
+
+ s->BasicModeStatus = val;
+}
+
+static uint32_t rtl8139_BasicModeStatus_read(RTL8139State *s)
+{
+ uint32_t ret = s->BasicModeStatus;
+
+ DPRINTF("BasicModeStatus register read(w) val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_Cfg9346_write(RTL8139State *s, uint32_t val)
+{
+ DeviceState *d = DEVICE(s);
+
+ val &= 0xff;
+
+ DPRINTF("Cfg9346 write val=0x%02x\n", val);
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0x31, s->Cfg9346);
+
+ uint32_t opmode = val & 0xc0;
+ uint32_t eeprom_val = val & 0xf;
+
+ if (opmode == 0x80) {
+ /* eeprom access */
+ int eecs = (eeprom_val & 0x08)?1:0;
+ int eesk = (eeprom_val & 0x04)?1:0;
+ int eedi = (eeprom_val & 0x02)?1:0;
+ prom9346_set_wire(s, eecs, eesk, eedi);
+ } else if (opmode == 0x40) {
+ /* Reset. */
+ val = 0;
+ rtl8139_reset(d);
+ }
+
+ s->Cfg9346 = val;
+}
+
+static uint32_t rtl8139_Cfg9346_read(RTL8139State *s)
+{
+ uint32_t ret = s->Cfg9346;
+
+ uint32_t opmode = ret & 0xc0;
+
+ if (opmode == 0x80)
+ {
+ /* eeprom access */
+ int eedo = prom9346_get_wire(s);
+ if (eedo)
+ {
+ ret |= 0x01;
+ }
+ else
+ {
+ ret &= ~0x01;
+ }
+ }
+
+ DPRINTF("Cfg9346 read val=0x%02x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_Config0_write(RTL8139State *s, uint32_t val)
+{
+ val &= 0xff;
+
+ DPRINTF("Config0 write val=0x%02x\n", val);
+
+ if (!rtl8139_config_writable(s)) {
+ return;
+ }
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0xf8, s->Config0);
+
+ s->Config0 = val;
+}
+
+static uint32_t rtl8139_Config0_read(RTL8139State *s)
+{
+ uint32_t ret = s->Config0;
+
+ DPRINTF("Config0 read val=0x%02x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_Config1_write(RTL8139State *s, uint32_t val)
+{
+ val &= 0xff;
+
+ DPRINTF("Config1 write val=0x%02x\n", val);
+
+ if (!rtl8139_config_writable(s)) {
+ return;
+ }
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0xC, s->Config1);
+
+ s->Config1 = val;
+}
+
+static uint32_t rtl8139_Config1_read(RTL8139State *s)
+{
+ uint32_t ret = s->Config1;
+
+ DPRINTF("Config1 read val=0x%02x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_Config3_write(RTL8139State *s, uint32_t val)
+{
+ val &= 0xff;
+
+ DPRINTF("Config3 write val=0x%02x\n", val);
+
+ if (!rtl8139_config_writable(s)) {
+ return;
+ }
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0x8F, s->Config3);
+
+ s->Config3 = val;
+}
+
+static uint32_t rtl8139_Config3_read(RTL8139State *s)
+{
+ uint32_t ret = s->Config3;
+
+ DPRINTF("Config3 read val=0x%02x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_Config4_write(RTL8139State *s, uint32_t val)
+{
+ val &= 0xff;
+
+ DPRINTF("Config4 write val=0x%02x\n", val);
+
+ if (!rtl8139_config_writable(s)) {
+ return;
+ }
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0x0a, s->Config4);
+
+ s->Config4 = val;
+}
+
+static uint32_t rtl8139_Config4_read(RTL8139State *s)
+{
+ uint32_t ret = s->Config4;
+
+ DPRINTF("Config4 read val=0x%02x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_Config5_write(RTL8139State *s, uint32_t val)
+{
+ val &= 0xff;
+
+ DPRINTF("Config5 write val=0x%02x\n", val);
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0x80, s->Config5);
+
+ s->Config5 = val;
+}
+
+static uint32_t rtl8139_Config5_read(RTL8139State *s)
+{
+ uint32_t ret = s->Config5;
+
+ DPRINTF("Config5 read val=0x%02x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_TxConfig_write(RTL8139State *s, uint32_t val)
+{
+ if (!rtl8139_transmitter_enabled(s))
+ {
+ DPRINTF("transmitter disabled; no TxConfig write val=0x%08x\n", val);
+ return;
+ }
+
+ DPRINTF("TxConfig write val=0x%08x\n", val);
+
+ val = SET_MASKED(val, TxVersionMask | 0x8070f80f, s->TxConfig);
+
+ s->TxConfig = val;
+}
+
+static void rtl8139_TxConfig_writeb(RTL8139State *s, uint32_t val)
+{
+ DPRINTF("RTL8139C TxConfig via write(b) val=0x%02x\n", val);
+
+ uint32_t tc = s->TxConfig;
+ tc &= 0xFFFFFF00;
+ tc |= (val & 0x000000FF);
+ rtl8139_TxConfig_write(s, tc);
+}
+
+static uint32_t rtl8139_TxConfig_read(RTL8139State *s)
+{
+ uint32_t ret = s->TxConfig;
+
+ DPRINTF("TxConfig read val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_RxConfig_write(RTL8139State *s, uint32_t val)
+{
+ DPRINTF("RxConfig write val=0x%08x\n", val);
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0xf0fc0040, s->RxConfig);
+
+ s->RxConfig = val;
+
+ /* reset buffer size and read/write pointers */
+ rtl8139_reset_rxring(s, 8192 << ((s->RxConfig >> 11) & 0x3));
+
+ DPRINTF("RxConfig write reset buffer size to %d\n", s->RxBufferSize);
+}
+
+static uint32_t rtl8139_RxConfig_read(RTL8139State *s)
+{
+ uint32_t ret = s->RxConfig;
+
+ DPRINTF("RxConfig read val=0x%08x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_transfer_frame(RTL8139State *s, uint8_t *buf, int size,
+ int do_interrupt, const uint8_t *dot1q_buf)
+{
+ struct iovec *iov = NULL;
+ struct iovec vlan_iov[3];
+
+ if (!size)
+ {
+ DPRINTF("+++ empty ethernet frame\n");
+ return;
+ }
+
+ if (dot1q_buf && size >= ETHER_ADDR_LEN * 2) {
+ iov = (struct iovec[3]) {
+ { .iov_base = buf, .iov_len = ETHER_ADDR_LEN * 2 },
+ { .iov_base = (void *) dot1q_buf, .iov_len = VLAN_HLEN },
+ { .iov_base = buf + ETHER_ADDR_LEN * 2,
+ .iov_len = size - ETHER_ADDR_LEN * 2 },
+ };
+
+ memcpy(vlan_iov, iov, sizeof(vlan_iov));
+ iov = vlan_iov;
+ }
+
+ if (TxLoopBack == (s->TxConfig & TxLoopBack))
+ {
+ size_t buf2_size;
+ uint8_t *buf2;
+
+ if (iov) {
+ buf2_size = iov_size(iov, 3);
+ buf2 = g_malloc(buf2_size);
+ iov_to_buf(iov, 3, 0, buf2, buf2_size);
+ buf = buf2;
+ }
+
+ DPRINTF("+++ transmit loopback mode\n");
+ rtl8139_do_receive(qemu_get_queue(s->nic), buf, size, do_interrupt);
+
+ if (iov) {
+ g_free(buf2);
+ }
+ }
+ else
+ {
+ if (iov) {
+ qemu_sendv_packet(qemu_get_queue(s->nic), iov, 3);
+ } else {
+ qemu_send_packet(qemu_get_queue(s->nic), buf, size);
+ }
+ }
+}
+
+static int rtl8139_transmit_one(RTL8139State *s, int descriptor)
+{
+ if (!rtl8139_transmitter_enabled(s))
+ {
+ DPRINTF("+++ cannot transmit from descriptor %d: transmitter "
+ "disabled\n", descriptor);
+ return 0;
+ }
+
+ if (s->TxStatus[descriptor] & TxHostOwns)
+ {
+ DPRINTF("+++ cannot transmit from descriptor %d: owned by host "
+ "(%08x)\n", descriptor, s->TxStatus[descriptor]);
+ return 0;
+ }
+
+ DPRINTF("+++ transmitting from descriptor %d\n", descriptor);
+
+ PCIDevice *d = PCI_DEVICE(s);
+ int txsize = s->TxStatus[descriptor] & 0x1fff;
+ uint8_t txbuffer[0x2000];
+
+ DPRINTF("+++ transmit reading %d bytes from host memory at 0x%08x\n",
+ txsize, s->TxAddr[descriptor]);
+
+ pci_dma_read(d, s->TxAddr[descriptor], txbuffer, txsize);
+
+ /* Mark descriptor as transferred */
+ s->TxStatus[descriptor] |= TxHostOwns;
+ s->TxStatus[descriptor] |= TxStatOK;
+
+ rtl8139_transfer_frame(s, txbuffer, txsize, 0, NULL);
+
+ DPRINTF("+++ transmitted %d bytes from descriptor %d\n", txsize,
+ descriptor);
+
+ /* update interrupt */
+ s->IntrStatus |= TxOK;
+ rtl8139_update_irq(s);
+
+ return 1;
+}
+
+/* structures and macros for task offloading */
+typedef struct ip_header
+{
+ uint8_t ip_ver_len; /* version and header length */
+ uint8_t ip_tos; /* type of service */
+ uint16_t ip_len; /* total length */
+ uint16_t ip_id; /* identification */
+ uint16_t ip_off; /* fragment offset field */
+ uint8_t ip_ttl; /* time to live */
+ uint8_t ip_p; /* protocol */
+ uint16_t ip_sum; /* checksum */
+ uint32_t ip_src,ip_dst; /* source and dest address */
+} ip_header;
+
+#define IP_HEADER_VERSION_4 4
+#define IP_HEADER_VERSION(ip) ((ip->ip_ver_len >> 4)&0xf)
+#define IP_HEADER_LENGTH(ip) (((ip->ip_ver_len)&0xf) << 2)
+
+typedef struct tcp_header
+{
+ uint16_t th_sport; /* source port */
+ uint16_t th_dport; /* destination port */
+ uint32_t th_seq; /* sequence number */
+ uint32_t th_ack; /* acknowledgement number */
+ uint16_t th_offset_flags; /* data offset, reserved 6 bits, TCP protocol flags */
+ uint16_t th_win; /* window */
+ uint16_t th_sum; /* checksum */
+ uint16_t th_urp; /* urgent pointer */
+} tcp_header;
+
+typedef struct udp_header
+{
+ uint16_t uh_sport; /* source port */
+ uint16_t uh_dport; /* destination port */
+ uint16_t uh_ulen; /* udp length */
+ uint16_t uh_sum; /* udp checksum */
+} udp_header;
+
+typedef struct ip_pseudo_header
+{
+ uint32_t ip_src;
+ uint32_t ip_dst;
+ uint8_t zeros;
+ uint8_t ip_proto;
+ uint16_t ip_payload;
+} ip_pseudo_header;
+
+#define IP_PROTO_TCP 6
+#define IP_PROTO_UDP 17
+
+#define TCP_HEADER_DATA_OFFSET(tcp) (((be16_to_cpu(tcp->th_offset_flags) >> 12)&0xf) << 2)
+#define TCP_FLAGS_ONLY(flags) ((flags)&0x3f)
+#define TCP_HEADER_FLAGS(tcp) TCP_FLAGS_ONLY(be16_to_cpu(tcp->th_offset_flags))
+
+#define TCP_HEADER_CLEAR_FLAGS(tcp, off) ((tcp)->th_offset_flags &= cpu_to_be16(~TCP_FLAGS_ONLY(off)))
+
+#define TCP_FLAG_FIN 0x01
+#define TCP_FLAG_PUSH 0x08
+
+/* produces ones' complement sum of data */
+static uint16_t ones_complement_sum(uint8_t *data, size_t len)
+{
+ uint32_t result = 0;
+
+ for (; len > 1; data+=2, len-=2)
+ {
+ result += *(uint16_t*)data;
+ }
+
+ /* add the remainder byte */
+ if (len)
+ {
+ uint8_t odd[2] = {*data, 0};
+ result += *(uint16_t*)odd;
+ }
+
+ while (result>>16)
+ result = (result & 0xffff) + (result >> 16);
+
+ return result;
+}
+
+static uint16_t ip_checksum(void *data, size_t len)
+{
+ return ~ones_complement_sum((uint8_t*)data, len);
+}
+
+static int rtl8139_cplus_transmit_one(RTL8139State *s)
+{
+ if (!rtl8139_transmitter_enabled(s))
+ {
+ DPRINTF("+++ C+ mode: transmitter disabled\n");
+ return 0;
+ }
+
+ if (!rtl8139_cp_transmitter_enabled(s))
+ {
+ DPRINTF("+++ C+ mode: C+ transmitter disabled\n");
+ return 0 ;
+ }
+
+ PCIDevice *d = PCI_DEVICE(s);
+ int descriptor = s->currCPlusTxDesc;
+
+ dma_addr_t cplus_tx_ring_desc = rtl8139_addr64(s->TxAddr[0], s->TxAddr[1]);
+
+ /* Normal priority ring */
+ cplus_tx_ring_desc += 16 * descriptor;
+
+ DPRINTF("+++ C+ mode reading TX descriptor %d from host memory at "
+ "%08x %08x = 0x"DMA_ADDR_FMT"\n", descriptor, s->TxAddr[1],
+ s->TxAddr[0], cplus_tx_ring_desc);
+
+ uint32_t val, txdw0,txdw1,txbufLO,txbufHI;
+
+ pci_dma_read(d, cplus_tx_ring_desc, (uint8_t *)&val, 4);
+ txdw0 = le32_to_cpu(val);
+ pci_dma_read(d, cplus_tx_ring_desc+4, (uint8_t *)&val, 4);
+ txdw1 = le32_to_cpu(val);
+ pci_dma_read(d, cplus_tx_ring_desc+8, (uint8_t *)&val, 4);
+ txbufLO = le32_to_cpu(val);
+ pci_dma_read(d, cplus_tx_ring_desc+12, (uint8_t *)&val, 4);
+ txbufHI = le32_to_cpu(val);
+
+ DPRINTF("+++ C+ mode TX descriptor %d %08x %08x %08x %08x\n", descriptor,
+ txdw0, txdw1, txbufLO, txbufHI);
+
+/* w0 ownership flag */
+#define CP_TX_OWN (1<<31)
+/* w0 end of ring flag */
+#define CP_TX_EOR (1<<30)
+/* first segment of received packet flag */
+#define CP_TX_FS (1<<29)
+/* last segment of received packet flag */
+#define CP_TX_LS (1<<28)
+/* large send packet flag */
+#define CP_TX_LGSEN (1<<27)
+/* large send MSS mask, bits 16...25 */
+#define CP_TC_LGSEN_MSS_MASK ((1 << 12) - 1)
+
+/* IP checksum offload flag */
+#define CP_TX_IPCS (1<<18)
+/* UDP checksum offload flag */
+#define CP_TX_UDPCS (1<<17)
+/* TCP checksum offload flag */
+#define CP_TX_TCPCS (1<<16)
+
+/* w0 bits 0...15 : buffer size */
+#define CP_TX_BUFFER_SIZE (1<<16)
+#define CP_TX_BUFFER_SIZE_MASK (CP_TX_BUFFER_SIZE - 1)
+/* w1 add tag flag */
+#define CP_TX_TAGC (1<<17)
+/* w1 bits 0...15 : VLAN tag (big endian) */
+#define CP_TX_VLAN_TAG_MASK ((1<<16) - 1)
+/* w2 low 32bit of Rx buffer ptr */
+/* w3 high 32bit of Rx buffer ptr */
+
+/* set after transmission */
+/* FIFO underrun flag */
+#define CP_TX_STATUS_UNF (1<<25)
+/* transmit error summary flag, valid if set any of three below */
+#define CP_TX_STATUS_TES (1<<23)
+/* out-of-window collision flag */
+#define CP_TX_STATUS_OWC (1<<22)
+/* link failure flag */
+#define CP_TX_STATUS_LNKF (1<<21)
+/* excessive collisions flag */
+#define CP_TX_STATUS_EXC (1<<20)
+
+ if (!(txdw0 & CP_TX_OWN))
+ {
+ DPRINTF("C+ Tx mode : descriptor %d is owned by host\n", descriptor);
+ return 0 ;
+ }
+
+ DPRINTF("+++ C+ Tx mode : transmitting from descriptor %d\n", descriptor);
+
+ if (txdw0 & CP_TX_FS)
+ {
+ DPRINTF("+++ C+ Tx mode : descriptor %d is first segment "
+ "descriptor\n", descriptor);
+
+ /* reset internal buffer offset */
+ s->cplus_txbuffer_offset = 0;
+ }
+
+ int txsize = txdw0 & CP_TX_BUFFER_SIZE_MASK;
+ dma_addr_t tx_addr = rtl8139_addr64(txbufLO, txbufHI);
+
+ /* make sure we have enough space to assemble the packet */
+ if (!s->cplus_txbuffer)
+ {
+ s->cplus_txbuffer_len = CP_TX_BUFFER_SIZE;
+ s->cplus_txbuffer = g_malloc(s->cplus_txbuffer_len);
+ s->cplus_txbuffer_offset = 0;
+
+ DPRINTF("+++ C+ mode transmission buffer allocated space %d\n",
+ s->cplus_txbuffer_len);
+ }
+
+ if (s->cplus_txbuffer_offset + txsize >= s->cplus_txbuffer_len)
+ {
+ /* The spec didn't tell the maximum size, stick to CP_TX_BUFFER_SIZE */
+ txsize = s->cplus_txbuffer_len - s->cplus_txbuffer_offset;
+ DPRINTF("+++ C+ mode transmission buffer overrun, truncated descriptor"
+ "length to %d\n", txsize);
+ }
+
+ /* append more data to the packet */
+
+ DPRINTF("+++ C+ mode transmit reading %d bytes from host memory at "
+ DMA_ADDR_FMT" to offset %d\n", txsize, tx_addr,
+ s->cplus_txbuffer_offset);
+
+ pci_dma_read(d, tx_addr,
+ s->cplus_txbuffer + s->cplus_txbuffer_offset, txsize);
+ s->cplus_txbuffer_offset += txsize;
+
+ /* seek to next Rx descriptor */
+ if (txdw0 & CP_TX_EOR)
+ {
+ s->currCPlusTxDesc = 0;
+ }
+ else
+ {
+ ++s->currCPlusTxDesc;
+ if (s->currCPlusTxDesc >= 64)
+ s->currCPlusTxDesc = 0;
+ }
+
+ /* transfer ownership to target */
+ txdw0 &= ~CP_RX_OWN;
+
+ /* reset error indicator bits */
+ txdw0 &= ~CP_TX_STATUS_UNF;
+ txdw0 &= ~CP_TX_STATUS_TES;
+ txdw0 &= ~CP_TX_STATUS_OWC;
+ txdw0 &= ~CP_TX_STATUS_LNKF;
+ txdw0 &= ~CP_TX_STATUS_EXC;
+
+ /* update ring data */
+ val = cpu_to_le32(txdw0);
+ pci_dma_write(d, cplus_tx_ring_desc, (uint8_t *)&val, 4);
+
+ /* Now decide if descriptor being processed is holding the last segment of packet */
+ if (txdw0 & CP_TX_LS)
+ {
+ uint8_t dot1q_buffer_space[VLAN_HLEN];
+ uint16_t *dot1q_buffer;
+
+ DPRINTF("+++ C+ Tx mode : descriptor %d is last segment descriptor\n",
+ descriptor);
+
+ /* can transfer fully assembled packet */
+
+ uint8_t *saved_buffer = s->cplus_txbuffer;
+ int saved_size = s->cplus_txbuffer_offset;
+ int saved_buffer_len = s->cplus_txbuffer_len;
+
+ /* create vlan tag */
+ if (txdw1 & CP_TX_TAGC) {
+ /* the vlan tag is in BE byte order in the descriptor
+ * BE + le_to_cpu() + ~swap()~ = cpu */
+ DPRINTF("+++ C+ Tx mode : inserting vlan tag with ""tci: %u\n",
+ bswap16(txdw1 & CP_TX_VLAN_TAG_MASK));
+
+ dot1q_buffer = (uint16_t *) dot1q_buffer_space;
+ dot1q_buffer[0] = cpu_to_be16(ETH_P_8021Q);
+ /* BE + le_to_cpu() + ~cpu_to_le()~ = BE */
+ dot1q_buffer[1] = cpu_to_le16(txdw1 & CP_TX_VLAN_TAG_MASK);
+ } else {
+ dot1q_buffer = NULL;
+ }
+
+ /* reset the card space to protect from recursive call */
+ s->cplus_txbuffer = NULL;
+ s->cplus_txbuffer_offset = 0;
+ s->cplus_txbuffer_len = 0;
+
+ if (txdw0 & (CP_TX_IPCS | CP_TX_UDPCS | CP_TX_TCPCS | CP_TX_LGSEN))
+ {
+ DPRINTF("+++ C+ mode offloaded task checksum\n");
+
+ /* Large enough for Ethernet and IP headers? */
+ if (saved_size < ETH_HLEN + sizeof(ip_header)) {
+ goto skip_offload;
+ }
+
+ /* ip packet header */
+ ip_header *ip = NULL;
+ int hlen = 0;
+ uint8_t ip_protocol = 0;
+ uint16_t ip_data_len = 0;
+
+ uint8_t *eth_payload_data = NULL;
+ size_t eth_payload_len = 0;
+
+ int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12));
+ if (proto != ETH_P_IP)
+ {
+ goto skip_offload;
+ }
+
+ DPRINTF("+++ C+ mode has IP packet\n");
+
+ /* not aligned */
+ eth_payload_data = saved_buffer + ETH_HLEN;
+ eth_payload_len = saved_size - ETH_HLEN;
+
+ ip = (ip_header*)eth_payload_data;
+
+ if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
+ DPRINTF("+++ C+ mode packet has bad IP version %d "
+ "expected %d\n", IP_HEADER_VERSION(ip),
+ IP_HEADER_VERSION_4);
+ goto skip_offload;
+ }
+
+ hlen = IP_HEADER_LENGTH(ip);
+ if (hlen < sizeof(ip_header) || hlen > eth_payload_len) {
+ goto skip_offload;
+ }
+
+ ip_protocol = ip->ip_p;
+
+ ip_data_len = be16_to_cpu(ip->ip_len);
+ if (ip_data_len < hlen || ip_data_len > eth_payload_len) {
+ goto skip_offload;
+ }
+ ip_data_len -= hlen;
+
+ if (txdw0 & CP_TX_IPCS)
+ {
+ DPRINTF("+++ C+ mode need IP checksum\n");
+
+ ip->ip_sum = 0;
+ ip->ip_sum = ip_checksum(ip, hlen);
+ DPRINTF("+++ C+ mode IP header len=%d checksum=%04x\n",
+ hlen, ip->ip_sum);
+ }
+
+ if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IP_PROTO_TCP)
+ {
+ /* Large enough for the TCP header? */
+ if (ip_data_len < sizeof(tcp_header)) {
+ goto skip_offload;
+ }
+
+ int large_send_mss = (txdw0 >> 16) & CP_TC_LGSEN_MSS_MASK;
+
+ DPRINTF("+++ C+ mode offloaded task TSO MTU=%d IP data %d "
+ "frame data %d specified MSS=%d\n", ETH_MTU,
+ ip_data_len, saved_size - ETH_HLEN, large_send_mss);
+
+ int tcp_send_offset = 0;
+ int send_count = 0;
+
+ /* maximum IP header length is 60 bytes */
+ uint8_t saved_ip_header[60];
+
+ /* save IP header template; data area is used in tcp checksum calculation */
+ memcpy(saved_ip_header, eth_payload_data, hlen);
+
+ /* a placeholder for checksum calculation routine in tcp case */
+ uint8_t *data_to_checksum = eth_payload_data + hlen - 12;
+ // size_t data_to_checksum_len = eth_payload_len - hlen + 12;
+
+ /* pointer to TCP header */
+ tcp_header *p_tcp_hdr = (tcp_header*)(eth_payload_data + hlen);
+
+ int tcp_hlen = TCP_HEADER_DATA_OFFSET(p_tcp_hdr);
+
+ /* Invalid TCP data offset? */
+ if (tcp_hlen < sizeof(tcp_header) || tcp_hlen > ip_data_len) {
+ goto skip_offload;
+ }
+
+ /* ETH_MTU = ip header len + tcp header len + payload */
+ int tcp_data_len = ip_data_len - tcp_hlen;
+ int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;
+
+ DPRINTF("+++ C+ mode TSO IP data len %d TCP hlen %d TCP "
+ "data len %d TCP chunk size %d\n", ip_data_len,
+ tcp_hlen, tcp_data_len, tcp_chunk_size);
+
+ /* note the cycle below overwrites IP header data,
+ but restores it from saved_ip_header before sending packet */
+
+ int is_last_frame = 0;
+
+ for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size)
+ {
+ uint16_t chunk_size = tcp_chunk_size;
+
+ /* check if this is the last frame */
+ if (tcp_send_offset + tcp_chunk_size >= tcp_data_len)
+ {
+ is_last_frame = 1;
+ chunk_size = tcp_data_len - tcp_send_offset;
+ }
+
+ DPRINTF("+++ C+ mode TSO TCP seqno %08x\n",
+ be32_to_cpu(p_tcp_hdr->th_seq));
+
+ /* add 4 TCP pseudoheader fields */
+ /* copy IP source and destination fields */
+ memcpy(data_to_checksum, saved_ip_header + 12, 8);
+
+ DPRINTF("+++ C+ mode TSO calculating TCP checksum for "
+ "packet with %d bytes data\n", tcp_hlen +
+ chunk_size);
+
+ if (tcp_send_offset)
+ {
+ memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size);
+ }
+
+ /* keep PUSH and FIN flags only for the last frame */
+ if (!is_last_frame)
+ {
+ TCP_HEADER_CLEAR_FLAGS(p_tcp_hdr, TCP_FLAG_PUSH|TCP_FLAG_FIN);
+ }
+
+ /* recalculate TCP checksum */
+ ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
+ p_tcpip_hdr->zeros = 0;
+ p_tcpip_hdr->ip_proto = IP_PROTO_TCP;
+ p_tcpip_hdr->ip_payload = cpu_to_be16(tcp_hlen + chunk_size);
+
+ p_tcp_hdr->th_sum = 0;
+
+ int tcp_checksum = ip_checksum(data_to_checksum, tcp_hlen + chunk_size + 12);
+ DPRINTF("+++ C+ mode TSO TCP checksum %04x\n",
+ tcp_checksum);
+
+ p_tcp_hdr->th_sum = tcp_checksum;
+
+ /* restore IP header */
+ memcpy(eth_payload_data, saved_ip_header, hlen);
+
+ /* set IP data length and recalculate IP checksum */
+ ip->ip_len = cpu_to_be16(hlen + tcp_hlen + chunk_size);
+
+ /* increment IP id for subsequent frames */
+ ip->ip_id = cpu_to_be16(tcp_send_offset/tcp_chunk_size + be16_to_cpu(ip->ip_id));
+
+ ip->ip_sum = 0;
+ ip->ip_sum = ip_checksum(eth_payload_data, hlen);
+ DPRINTF("+++ C+ mode TSO IP header len=%d "
+ "checksum=%04x\n", hlen, ip->ip_sum);
+
+ int tso_send_size = ETH_HLEN + hlen + tcp_hlen + chunk_size;
+ DPRINTF("+++ C+ mode TSO transferring packet size "
+ "%d\n", tso_send_size);
+ rtl8139_transfer_frame(s, saved_buffer, tso_send_size,
+ 0, (uint8_t *) dot1q_buffer);
+
+ /* add transferred count to TCP sequence number */
+ p_tcp_hdr->th_seq = cpu_to_be32(chunk_size + be32_to_cpu(p_tcp_hdr->th_seq));
+ ++send_count;
+ }
+
+ /* Stop sending this frame */
+ saved_size = 0;
+ }
+ else if (txdw0 & (CP_TX_TCPCS|CP_TX_UDPCS))
+ {
+ DPRINTF("+++ C+ mode need TCP or UDP checksum\n");
+
+ /* maximum IP header length is 60 bytes */
+ uint8_t saved_ip_header[60];
+ memcpy(saved_ip_header, eth_payload_data, hlen);
+
+ uint8_t *data_to_checksum = eth_payload_data + hlen - 12;
+ // size_t data_to_checksum_len = eth_payload_len - hlen + 12;
+
+ /* add 4 TCP pseudoheader fields */
+ /* copy IP source and destination fields */
+ memcpy(data_to_checksum, saved_ip_header + 12, 8);
+
+ if ((txdw0 & CP_TX_TCPCS) && ip_protocol == IP_PROTO_TCP)
+ {
+ DPRINTF("+++ C+ mode calculating TCP checksum for "
+ "packet with %d bytes data\n", ip_data_len);
+
+ ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
+ p_tcpip_hdr->zeros = 0;
+ p_tcpip_hdr->ip_proto = IP_PROTO_TCP;
+ p_tcpip_hdr->ip_payload = cpu_to_be16(ip_data_len);
+
+ tcp_header* p_tcp_hdr = (tcp_header *) (data_to_checksum+12);
+
+ p_tcp_hdr->th_sum = 0;
+
+ int tcp_checksum = ip_checksum(data_to_checksum, ip_data_len + 12);
+ DPRINTF("+++ C+ mode TCP checksum %04x\n",
+ tcp_checksum);
+
+ p_tcp_hdr->th_sum = tcp_checksum;
+ }
+ else if ((txdw0 & CP_TX_UDPCS) && ip_protocol == IP_PROTO_UDP)
+ {
+ DPRINTF("+++ C+ mode calculating UDP checksum for "
+ "packet with %d bytes data\n", ip_data_len);
+
+ ip_pseudo_header *p_udpip_hdr = (ip_pseudo_header *)data_to_checksum;
+ p_udpip_hdr->zeros = 0;
+ p_udpip_hdr->ip_proto = IP_PROTO_UDP;
+ p_udpip_hdr->ip_payload = cpu_to_be16(ip_data_len);
+
+ udp_header *p_udp_hdr = (udp_header *) (data_to_checksum+12);
+
+ p_udp_hdr->uh_sum = 0;
+
+ int udp_checksum = ip_checksum(data_to_checksum, ip_data_len + 12);
+ DPRINTF("+++ C+ mode UDP checksum %04x\n",
+ udp_checksum);
+
+ p_udp_hdr->uh_sum = udp_checksum;
+ }
+
+ /* restore IP header */
+ memcpy(eth_payload_data, saved_ip_header, hlen);
+ }
+ }
+
+skip_offload:
+ /* update tally counter */
+ ++s->tally_counters.TxOk;
+
+ DPRINTF("+++ C+ mode transmitting %d bytes packet\n", saved_size);
+
+ rtl8139_transfer_frame(s, saved_buffer, saved_size, 1,
+ (uint8_t *) dot1q_buffer);
+
+ /* restore card space if there was no recursion and reset offset */
+ if (!s->cplus_txbuffer)
+ {
+ s->cplus_txbuffer = saved_buffer;
+ s->cplus_txbuffer_len = saved_buffer_len;
+ s->cplus_txbuffer_offset = 0;
+ }
+ else
+ {
+ g_free(saved_buffer);
+ }
+ }
+ else
+ {
+ DPRINTF("+++ C+ mode transmission continue to next descriptor\n");
+ }
+
+ return 1;
+}
+
+static void rtl8139_cplus_transmit(RTL8139State *s)
+{
+ int txcount = 0;
+
+ while (rtl8139_cplus_transmit_one(s))
+ {
+ ++txcount;
+ }
+
+ /* Mark transfer completed */
+ if (!txcount)
+ {
+ DPRINTF("C+ mode : transmitter queue stalled, current TxDesc = %d\n",
+ s->currCPlusTxDesc);
+ }
+ else
+ {
+ /* update interrupt status */
+ s->IntrStatus |= TxOK;
+ rtl8139_update_irq(s);
+ }
+}
+
+static void rtl8139_transmit(RTL8139State *s)
+{
+ int descriptor = s->currTxDesc, txcount = 0;
+
+ /*while*/
+ if (rtl8139_transmit_one(s, descriptor))
+ {
+ ++s->currTxDesc;
+ s->currTxDesc %= 4;
+ ++txcount;
+ }
+
+ /* Mark transfer completed */
+ if (!txcount)
+ {
+ DPRINTF("transmitter queue stalled, current TxDesc = %d\n",
+ s->currTxDesc);
+ }
+}
+
+static void rtl8139_TxStatus_write(RTL8139State *s, uint32_t txRegOffset, uint32_t val)
+{
+
+ int descriptor = txRegOffset/4;
+
+ /* handle C+ transmit mode register configuration */
+
+ if (s->cplus_enabled)
+ {
+ DPRINTF("RTL8139C+ DTCCR write offset=0x%x val=0x%08x "
+ "descriptor=%d\n", txRegOffset, val, descriptor);
+
+ /* handle Dump Tally Counters command */
+ s->TxStatus[descriptor] = val;
+
+ if (descriptor == 0 && (val & 0x8))
+ {
+ hwaddr tc_addr = rtl8139_addr64(s->TxStatus[0] & ~0x3f, s->TxStatus[1]);
+
+ /* dump tally counters to specified memory location */
+ RTL8139TallyCounters_dma_write(s, tc_addr);
+
+ /* mark dump completed */
+ s->TxStatus[0] &= ~0x8;
+ }
+
+ return;
+ }
+
+ DPRINTF("TxStatus write offset=0x%x val=0x%08x descriptor=%d\n",
+ txRegOffset, val, descriptor);
+
+ /* mask only reserved bits */
+ val &= ~0xff00c000; /* these bits are reset on write */
+ val = SET_MASKED(val, 0x00c00000, s->TxStatus[descriptor]);
+
+ s->TxStatus[descriptor] = val;
+
+ /* attempt to start transmission */
+ rtl8139_transmit(s);
+}
+
+static uint32_t rtl8139_TxStatus_TxAddr_read(RTL8139State *s, uint32_t regs[],
+ uint32_t base, uint8_t addr,
+ int size)
+{
+ uint32_t reg = (addr - base) / 4;
+ uint32_t offset = addr & 0x3;
+ uint32_t ret = 0;
+
+ if (addr & (size - 1)) {
+ DPRINTF("not implemented read for TxStatus/TxAddr "
+ "addr=0x%x size=0x%x\n", addr, size);
+ return ret;
+ }
+
+ switch (size) {
+ case 1: /* fall through */
+ case 2: /* fall through */
+ case 4:
+ ret = (regs[reg] >> offset * 8) & (((uint64_t)1 << (size * 8)) - 1);
+ DPRINTF("TxStatus/TxAddr[%d] read addr=0x%x size=0x%x val=0x%08x\n",
+ reg, addr, size, ret);
+ break;
+ default:
+ DPRINTF("unsupported size 0x%x of TxStatus/TxAddr reading\n", size);
+ break;
+ }
+
+ return ret;
+}
+
+static uint16_t rtl8139_TSAD_read(RTL8139State *s)
+{
+ uint16_t ret = 0;
+
+ /* Simulate TSAD, it is read only anyway */
+
+ ret = ((s->TxStatus[3] & TxStatOK )?TSAD_TOK3:0)
+ |((s->TxStatus[2] & TxStatOK )?TSAD_TOK2:0)
+ |((s->TxStatus[1] & TxStatOK )?TSAD_TOK1:0)
+ |((s->TxStatus[0] & TxStatOK )?TSAD_TOK0:0)
+
+ |((s->TxStatus[3] & TxUnderrun)?TSAD_TUN3:0)
+ |((s->TxStatus[2] & TxUnderrun)?TSAD_TUN2:0)
+ |((s->TxStatus[1] & TxUnderrun)?TSAD_TUN1:0)
+ |((s->TxStatus[0] & TxUnderrun)?TSAD_TUN0:0)
+
+ |((s->TxStatus[3] & TxAborted )?TSAD_TABT3:0)
+ |((s->TxStatus[2] & TxAborted )?TSAD_TABT2:0)
+ |((s->TxStatus[1] & TxAborted )?TSAD_TABT1:0)
+ |((s->TxStatus[0] & TxAborted )?TSAD_TABT0:0)
+
+ |((s->TxStatus[3] & TxHostOwns )?TSAD_OWN3:0)
+ |((s->TxStatus[2] & TxHostOwns )?TSAD_OWN2:0)
+ |((s->TxStatus[1] & TxHostOwns )?TSAD_OWN1:0)
+ |((s->TxStatus[0] & TxHostOwns )?TSAD_OWN0:0) ;
+
+
+ DPRINTF("TSAD read val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static uint16_t rtl8139_CSCR_read(RTL8139State *s)
+{
+ uint16_t ret = s->CSCR;
+
+ DPRINTF("CSCR read val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_TxAddr_write(RTL8139State *s, uint32_t txAddrOffset, uint32_t val)
+{
+ DPRINTF("TxAddr write offset=0x%x val=0x%08x\n", txAddrOffset, val);
+
+ s->TxAddr[txAddrOffset/4] = val;
+}
+
+static uint32_t rtl8139_TxAddr_read(RTL8139State *s, uint32_t txAddrOffset)
+{
+ uint32_t ret = s->TxAddr[txAddrOffset/4];
+
+ DPRINTF("TxAddr read offset=0x%x val=0x%08x\n", txAddrOffset, ret);
+
+ return ret;
+}
+
+static void rtl8139_RxBufPtr_write(RTL8139State *s, uint32_t val)
+{
+ DPRINTF("RxBufPtr write val=0x%04x\n", val);
+
+ /* this value is off by 16 */
+ s->RxBufPtr = MOD2(val + 0x10, s->RxBufferSize);
+
+ /* more buffer space may be available so try to receive */
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+
+ DPRINTF(" CAPR write: rx buffer length %d head 0x%04x read 0x%04x\n",
+ s->RxBufferSize, s->RxBufAddr, s->RxBufPtr);
+}
+
+static uint32_t rtl8139_RxBufPtr_read(RTL8139State *s)
+{
+ /* this value is off by 16 */
+ uint32_t ret = s->RxBufPtr - 0x10;
+
+ DPRINTF("RxBufPtr read val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static uint32_t rtl8139_RxBufAddr_read(RTL8139State *s)
+{
+ /* this value is NOT off by 16 */
+ uint32_t ret = s->RxBufAddr;
+
+ DPRINTF("RxBufAddr read val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_RxBuf_write(RTL8139State *s, uint32_t val)
+{
+ DPRINTF("RxBuf write val=0x%08x\n", val);
+
+ s->RxBuf = val;
+
+ /* may need to reset rxring here */
+}
+
+static uint32_t rtl8139_RxBuf_read(RTL8139State *s)
+{
+ uint32_t ret = s->RxBuf;
+
+ DPRINTF("RxBuf read val=0x%08x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_IntrMask_write(RTL8139State *s, uint32_t val)
+{
+ DPRINTF("IntrMask write(w) val=0x%04x\n", val);
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0x1e00, s->IntrMask);
+
+ s->IntrMask = val;
+
+ rtl8139_update_irq(s);
+
+}
+
+static uint32_t rtl8139_IntrMask_read(RTL8139State *s)
+{
+ uint32_t ret = s->IntrMask;
+
+ DPRINTF("IntrMask read(w) val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_IntrStatus_write(RTL8139State *s, uint32_t val)
+{
+ DPRINTF("IntrStatus write(w) val=0x%04x\n", val);
+
+#if 0
+
+ /* writing to ISR has no effect */
+
+ return;
+
+#else
+ uint16_t newStatus = s->IntrStatus & ~val;
+
+ /* mask unwritable bits */
+ newStatus = SET_MASKED(newStatus, 0x1e00, s->IntrStatus);
+
+ /* writing 1 to interrupt status register bit clears it */
+ s->IntrStatus = 0;
+ rtl8139_update_irq(s);
+
+ s->IntrStatus = newStatus;
+ rtl8139_set_next_tctr_time(s);
+ rtl8139_update_irq(s);
+
+#endif
+}
+
+static uint32_t rtl8139_IntrStatus_read(RTL8139State *s)
+{
+ uint32_t ret = s->IntrStatus;
+
+ DPRINTF("IntrStatus read(w) val=0x%04x\n", ret);
+
+#if 0
+
+ /* reading ISR clears all interrupts */
+ s->IntrStatus = 0;
+
+ rtl8139_update_irq(s);
+
+#endif
+
+ return ret;
+}
+
+static void rtl8139_MultiIntr_write(RTL8139State *s, uint32_t val)
+{
+ DPRINTF("MultiIntr write(w) val=0x%04x\n", val);
+
+ /* mask unwritable bits */
+ val = SET_MASKED(val, 0xf000, s->MultiIntr);
+
+ s->MultiIntr = val;
+}
+
+static uint32_t rtl8139_MultiIntr_read(RTL8139State *s)
+{
+ uint32_t ret = s->MultiIntr;
+
+ DPRINTF("MultiIntr read(w) val=0x%04x\n", ret);
+
+ return ret;
+}
+
+static void rtl8139_io_writeb(void *opaque, uint8_t addr, uint32_t val)
+{
+ RTL8139State *s = opaque;
+
+ switch (addr)
+ {
+ case MAC0 ... MAC0+4:
+ s->phys[addr - MAC0] = val;
+ break;
+ case MAC0+5:
+ s->phys[addr - MAC0] = val;
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->phys);
+ break;
+ case MAC0+6 ... MAC0+7:
+ /* reserved */
+ break;
+ case MAR0 ... MAR0+7:
+ s->mult[addr - MAR0] = val;
+ break;
+ case ChipCmd:
+ rtl8139_ChipCmd_write(s, val);
+ break;
+ case Cfg9346:
+ rtl8139_Cfg9346_write(s, val);
+ break;
+ case TxConfig: /* windows driver sometimes writes using byte-lenth call */
+ rtl8139_TxConfig_writeb(s, val);
+ break;
+ case Config0:
+ rtl8139_Config0_write(s, val);
+ break;
+ case Config1:
+ rtl8139_Config1_write(s, val);
+ break;
+ case Config3:
+ rtl8139_Config3_write(s, val);
+ break;
+ case Config4:
+ rtl8139_Config4_write(s, val);
+ break;
+ case Config5:
+ rtl8139_Config5_write(s, val);
+ break;
+ case MediaStatus:
+ /* ignore */
+ DPRINTF("not implemented write(b) to MediaStatus val=0x%02x\n",
+ val);
+ break;
+
+ case HltClk:
+ DPRINTF("HltClk write val=0x%08x\n", val);
+ if (val == 'R')
+ {
+ s->clock_enabled = 1;
+ }
+ else if (val == 'H')
+ {
+ s->clock_enabled = 0;
+ }
+ break;
+
+ case TxThresh:
+ DPRINTF("C+ TxThresh write(b) val=0x%02x\n", val);
+ s->TxThresh = val;
+ break;
+
+ case TxPoll:
+ DPRINTF("C+ TxPoll write(b) val=0x%02x\n", val);
+ if (val & (1 << 7))
+ {
+ DPRINTF("C+ TxPoll high priority transmission (not "
+ "implemented)\n");
+ //rtl8139_cplus_transmit(s);
+ }
+ if (val & (1 << 6))
+ {
+ DPRINTF("C+ TxPoll normal priority transmission\n");
+ rtl8139_cplus_transmit(s);
+ }
+
+ break;
+
+ default:
+ DPRINTF("not implemented write(b) addr=0x%x val=0x%02x\n", addr,
+ val);
+ break;
+ }
+}
+
+static void rtl8139_io_writew(void *opaque, uint8_t addr, uint32_t val)
+{
+ RTL8139State *s = opaque;
+
+ switch (addr)
+ {
+ case IntrMask:
+ rtl8139_IntrMask_write(s, val);
+ break;
+
+ case IntrStatus:
+ rtl8139_IntrStatus_write(s, val);
+ break;
+
+ case MultiIntr:
+ rtl8139_MultiIntr_write(s, val);
+ break;
+
+ case RxBufPtr:
+ rtl8139_RxBufPtr_write(s, val);
+ break;
+
+ case BasicModeCtrl:
+ rtl8139_BasicModeCtrl_write(s, val);
+ break;
+ case BasicModeStatus:
+ rtl8139_BasicModeStatus_write(s, val);
+ break;
+ case NWayAdvert:
+ DPRINTF("NWayAdvert write(w) val=0x%04x\n", val);
+ s->NWayAdvert = val;
+ break;
+ case NWayLPAR:
+ DPRINTF("forbidden NWayLPAR write(w) val=0x%04x\n", val);
+ break;
+ case NWayExpansion:
+ DPRINTF("NWayExpansion write(w) val=0x%04x\n", val);
+ s->NWayExpansion = val;
+ break;
+
+ case CpCmd:
+ rtl8139_CpCmd_write(s, val);
+ break;
+
+ case IntrMitigate:
+ rtl8139_IntrMitigate_write(s, val);
+ break;
+
+ default:
+ DPRINTF("ioport write(w) addr=0x%x val=0x%04x via write(b)\n",
+ addr, val);
+
+ rtl8139_io_writeb(opaque, addr, val & 0xff);
+ rtl8139_io_writeb(opaque, addr + 1, (val >> 8) & 0xff);
+ break;
+ }
+}
+
+static void rtl8139_set_next_tctr_time(RTL8139State *s)
+{
+ const uint64_t ns_per_period =
+ muldiv64(0x100000000LL, get_ticks_per_sec(), PCI_FREQUENCY);
+
+ DPRINTF("entered rtl8139_set_next_tctr_time\n");
+
+ /* This function is called at least once per period, so it is a good
+ * place to update the timer base.
+ *
+ * After one iteration of this loop the value in the Timer register does
+ * not change, but the device model is counting up by 2^32 ticks (approx.
+ * 130 seconds).
+ */
+ while (s->TCTR_base + ns_per_period <= qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) {
+ s->TCTR_base += ns_per_period;
+ }
+
+ if (!s->TimerInt) {
+ timer_del(s->timer);
+ } else {
+ uint64_t delta = muldiv64(s->TimerInt, get_ticks_per_sec(), PCI_FREQUENCY);
+ if (s->TCTR_base + delta <= qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) {
+ delta += ns_per_period;
+ }
+ timer_mod(s->timer, s->TCTR_base + delta);
+ }
+}
+
+static void rtl8139_io_writel(void *opaque, uint8_t addr, uint32_t val)
+{
+ RTL8139State *s = opaque;
+
+ switch (addr)
+ {
+ case RxMissed:
+ DPRINTF("RxMissed clearing on write\n");
+ s->RxMissed = 0;
+ break;
+
+ case TxConfig:
+ rtl8139_TxConfig_write(s, val);
+ break;
+
+ case RxConfig:
+ rtl8139_RxConfig_write(s, val);
+ break;
+
+ case TxStatus0 ... TxStatus0+4*4-1:
+ rtl8139_TxStatus_write(s, addr-TxStatus0, val);
+ break;
+
+ case TxAddr0 ... TxAddr0+4*4-1:
+ rtl8139_TxAddr_write(s, addr-TxAddr0, val);
+ break;
+
+ case RxBuf:
+ rtl8139_RxBuf_write(s, val);
+ break;
+
+ case RxRingAddrLO:
+ DPRINTF("C+ RxRing low bits write val=0x%08x\n", val);
+ s->RxRingAddrLO = val;
+ break;
+
+ case RxRingAddrHI:
+ DPRINTF("C+ RxRing high bits write val=0x%08x\n", val);
+ s->RxRingAddrHI = val;
+ break;
+
+ case Timer:
+ DPRINTF("TCTR Timer reset on write\n");
+ s->TCTR_base = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ rtl8139_set_next_tctr_time(s);
+ break;
+
+ case FlashReg:
+ DPRINTF("FlashReg TimerInt write val=0x%08x\n", val);
+ if (s->TimerInt != val) {
+ s->TimerInt = val;
+ rtl8139_set_next_tctr_time(s);
+ }
+ break;
+
+ default:
+ DPRINTF("ioport write(l) addr=0x%x val=0x%08x via write(b)\n",
+ addr, val);
+ rtl8139_io_writeb(opaque, addr, val & 0xff);
+ rtl8139_io_writeb(opaque, addr + 1, (val >> 8) & 0xff);
+ rtl8139_io_writeb(opaque, addr + 2, (val >> 16) & 0xff);
+ rtl8139_io_writeb(opaque, addr + 3, (val >> 24) & 0xff);
+ break;
+ }
+}
+
+static uint32_t rtl8139_io_readb(void *opaque, uint8_t addr)
+{
+ RTL8139State *s = opaque;
+ int ret;
+
+ switch (addr)
+ {
+ case MAC0 ... MAC0+5:
+ ret = s->phys[addr - MAC0];
+ break;
+ case MAC0+6 ... MAC0+7:
+ ret = 0;
+ break;
+ case MAR0 ... MAR0+7:
+ ret = s->mult[addr - MAR0];
+ break;
+ case TxStatus0 ... TxStatus0+4*4-1:
+ ret = rtl8139_TxStatus_TxAddr_read(s, s->TxStatus, TxStatus0,
+ addr, 1);
+ break;
+ case ChipCmd:
+ ret = rtl8139_ChipCmd_read(s);
+ break;
+ case Cfg9346:
+ ret = rtl8139_Cfg9346_read(s);
+ break;
+ case Config0:
+ ret = rtl8139_Config0_read(s);
+ break;
+ case Config1:
+ ret = rtl8139_Config1_read(s);
+ break;
+ case Config3:
+ ret = rtl8139_Config3_read(s);
+ break;
+ case Config4:
+ ret = rtl8139_Config4_read(s);
+ break;
+ case Config5:
+ ret = rtl8139_Config5_read(s);
+ break;
+
+ case MediaStatus:
+ /* The LinkDown bit of MediaStatus is inverse with link status */
+ ret = 0xd0 | (~s->BasicModeStatus & 0x04);
+ DPRINTF("MediaStatus read 0x%x\n", ret);
+ break;
+
+ case HltClk:
+ ret = s->clock_enabled;
+ DPRINTF("HltClk read 0x%x\n", ret);
+ break;
+
+ case PCIRevisionID:
+ ret = RTL8139_PCI_REVID;
+ DPRINTF("PCI Revision ID read 0x%x\n", ret);
+ break;
+
+ case TxThresh:
+ ret = s->TxThresh;
+ DPRINTF("C+ TxThresh read(b) val=0x%02x\n", ret);
+ break;
+
+ case 0x43: /* Part of TxConfig register. Windows driver tries to read it */
+ ret = s->TxConfig >> 24;
+ DPRINTF("RTL8139C TxConfig at 0x43 read(b) val=0x%02x\n", ret);
+ break;
+
+ default:
+ DPRINTF("not implemented read(b) addr=0x%x\n", addr);
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static uint32_t rtl8139_io_readw(void *opaque, uint8_t addr)
+{
+ RTL8139State *s = opaque;
+ uint32_t ret;
+
+ switch (addr)
+ {
+ case TxAddr0 ... TxAddr0+4*4-1:
+ ret = rtl8139_TxStatus_TxAddr_read(s, s->TxAddr, TxAddr0, addr, 2);
+ break;
+ case IntrMask:
+ ret = rtl8139_IntrMask_read(s);
+ break;
+
+ case IntrStatus:
+ ret = rtl8139_IntrStatus_read(s);
+ break;
+
+ case MultiIntr:
+ ret = rtl8139_MultiIntr_read(s);
+ break;
+
+ case RxBufPtr:
+ ret = rtl8139_RxBufPtr_read(s);
+ break;
+
+ case RxBufAddr:
+ ret = rtl8139_RxBufAddr_read(s);
+ break;
+
+ case BasicModeCtrl:
+ ret = rtl8139_BasicModeCtrl_read(s);
+ break;
+ case BasicModeStatus:
+ ret = rtl8139_BasicModeStatus_read(s);
+ break;
+ case NWayAdvert:
+ ret = s->NWayAdvert;
+ DPRINTF("NWayAdvert read(w) val=0x%04x\n", ret);
+ break;
+ case NWayLPAR:
+ ret = s->NWayLPAR;
+ DPRINTF("NWayLPAR read(w) val=0x%04x\n", ret);
+ break;
+ case NWayExpansion:
+ ret = s->NWayExpansion;
+ DPRINTF("NWayExpansion read(w) val=0x%04x\n", ret);
+ break;
+
+ case CpCmd:
+ ret = rtl8139_CpCmd_read(s);
+ break;
+
+ case IntrMitigate:
+ ret = rtl8139_IntrMitigate_read(s);
+ break;
+
+ case TxSummary:
+ ret = rtl8139_TSAD_read(s);
+ break;
+
+ case CSCR:
+ ret = rtl8139_CSCR_read(s);
+ break;
+
+ default:
+ DPRINTF("ioport read(w) addr=0x%x via read(b)\n", addr);
+
+ ret = rtl8139_io_readb(opaque, addr);
+ ret |= rtl8139_io_readb(opaque, addr + 1) << 8;
+
+ DPRINTF("ioport read(w) addr=0x%x val=0x%04x\n", addr, ret);
+ break;
+ }
+
+ return ret;
+}
+
+static uint32_t rtl8139_io_readl(void *opaque, uint8_t addr)
+{
+ RTL8139State *s = opaque;
+ uint32_t ret;
+
+ switch (addr)
+ {
+ case RxMissed:
+ ret = s->RxMissed;
+
+ DPRINTF("RxMissed read val=0x%08x\n", ret);
+ break;
+
+ case TxConfig:
+ ret = rtl8139_TxConfig_read(s);
+ break;
+
+ case RxConfig:
+ ret = rtl8139_RxConfig_read(s);
+ break;
+
+ case TxStatus0 ... TxStatus0+4*4-1:
+ ret = rtl8139_TxStatus_TxAddr_read(s, s->TxStatus, TxStatus0,
+ addr, 4);
+ break;
+
+ case TxAddr0 ... TxAddr0+4*4-1:
+ ret = rtl8139_TxAddr_read(s, addr-TxAddr0);
+ break;
+
+ case RxBuf:
+ ret = rtl8139_RxBuf_read(s);
+ break;
+
+ case RxRingAddrLO:
+ ret = s->RxRingAddrLO;
+ DPRINTF("C+ RxRing low bits read val=0x%08x\n", ret);
+ break;
+
+ case RxRingAddrHI:
+ ret = s->RxRingAddrHI;
+ DPRINTF("C+ RxRing high bits read val=0x%08x\n", ret);
+ break;
+
+ case Timer:
+ ret = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->TCTR_base,
+ PCI_FREQUENCY, get_ticks_per_sec());
+ DPRINTF("TCTR Timer read val=0x%08x\n", ret);
+ break;
+
+ case FlashReg:
+ ret = s->TimerInt;
+ DPRINTF("FlashReg TimerInt read val=0x%08x\n", ret);
+ break;
+
+ default:
+ DPRINTF("ioport read(l) addr=0x%x via read(b)\n", addr);
+
+ ret = rtl8139_io_readb(opaque, addr);
+ ret |= rtl8139_io_readb(opaque, addr + 1) << 8;
+ ret |= rtl8139_io_readb(opaque, addr + 2) << 16;
+ ret |= rtl8139_io_readb(opaque, addr + 3) << 24;
+
+ DPRINTF("read(l) addr=0x%x val=%08x\n", addr, ret);
+ break;
+ }
+
+ return ret;
+}
+
+/* */
+
+static void rtl8139_mmio_writeb(void *opaque, hwaddr addr, uint32_t val)
+{
+ rtl8139_io_writeb(opaque, addr & 0xFF, val);
+}
+
+static void rtl8139_mmio_writew(void *opaque, hwaddr addr, uint32_t val)
+{
+ rtl8139_io_writew(opaque, addr & 0xFF, val);
+}
+
+static void rtl8139_mmio_writel(void *opaque, hwaddr addr, uint32_t val)
+{
+ rtl8139_io_writel(opaque, addr & 0xFF, val);
+}
+
+static uint32_t rtl8139_mmio_readb(void *opaque, hwaddr addr)
+{
+ return rtl8139_io_readb(opaque, addr & 0xFF);
+}
+
+static uint32_t rtl8139_mmio_readw(void *opaque, hwaddr addr)
+{
+ uint32_t val = rtl8139_io_readw(opaque, addr & 0xFF);
+ return val;
+}
+
+static uint32_t rtl8139_mmio_readl(void *opaque, hwaddr addr)
+{
+ uint32_t val = rtl8139_io_readl(opaque, addr & 0xFF);
+ return val;
+}
+
+static int rtl8139_post_load(void *opaque, int version_id)
+{
+ RTL8139State* s = opaque;
+ rtl8139_set_next_tctr_time(s);
+ if (version_id < 4) {
+ s->cplus_enabled = s->CpCmd != 0;
+ }
+
+ /* nc.link_down can't be migrated, so infer link_down according
+ * to link status bit in BasicModeStatus */
+ qemu_get_queue(s->nic)->link_down = (s->BasicModeStatus & 0x04) == 0;
+
+ return 0;
+}
+
+static bool rtl8139_hotplug_ready_needed(void *opaque)
+{
+ return qdev_machine_modified();
+}
+
+static const VMStateDescription vmstate_rtl8139_hotplug_ready ={
+ .name = "rtl8139/hotplug_ready",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = rtl8139_hotplug_ready_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rtl8139_pre_save(void *opaque)
+{
+ RTL8139State* s = opaque;
+ int64_t current_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ /* for migration to older versions */
+ s->TCTR = muldiv64(current_time - s->TCTR_base, PCI_FREQUENCY,
+ get_ticks_per_sec());
+ s->rtl8139_mmio_io_addr_dummy = 0;
+}
+
+static const VMStateDescription vmstate_rtl8139 = {
+ .name = "rtl8139",
+ .version_id = 4,
+ .minimum_version_id = 3,
+ .post_load = rtl8139_post_load,
+ .pre_save = rtl8139_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, RTL8139State),
+ VMSTATE_PARTIAL_BUFFER(phys, RTL8139State, 6),
+ VMSTATE_BUFFER(mult, RTL8139State),
+ VMSTATE_UINT32_ARRAY(TxStatus, RTL8139State, 4),
+ VMSTATE_UINT32_ARRAY(TxAddr, RTL8139State, 4),
+
+ VMSTATE_UINT32(RxBuf, RTL8139State),
+ VMSTATE_UINT32(RxBufferSize, RTL8139State),
+ VMSTATE_UINT32(RxBufPtr, RTL8139State),
+ VMSTATE_UINT32(RxBufAddr, RTL8139State),
+
+ VMSTATE_UINT16(IntrStatus, RTL8139State),
+ VMSTATE_UINT16(IntrMask, RTL8139State),
+
+ VMSTATE_UINT32(TxConfig, RTL8139State),
+ VMSTATE_UINT32(RxConfig, RTL8139State),
+ VMSTATE_UINT32(RxMissed, RTL8139State),
+ VMSTATE_UINT16(CSCR, RTL8139State),
+
+ VMSTATE_UINT8(Cfg9346, RTL8139State),
+ VMSTATE_UINT8(Config0, RTL8139State),
+ VMSTATE_UINT8(Config1, RTL8139State),
+ VMSTATE_UINT8(Config3, RTL8139State),
+ VMSTATE_UINT8(Config4, RTL8139State),
+ VMSTATE_UINT8(Config5, RTL8139State),
+
+ VMSTATE_UINT8(clock_enabled, RTL8139State),
+ VMSTATE_UINT8(bChipCmdState, RTL8139State),
+
+ VMSTATE_UINT16(MultiIntr, RTL8139State),
+
+ VMSTATE_UINT16(BasicModeCtrl, RTL8139State),
+ VMSTATE_UINT16(BasicModeStatus, RTL8139State),
+ VMSTATE_UINT16(NWayAdvert, RTL8139State),
+ VMSTATE_UINT16(NWayLPAR, RTL8139State),
+ VMSTATE_UINT16(NWayExpansion, RTL8139State),
+
+ VMSTATE_UINT16(CpCmd, RTL8139State),
+ VMSTATE_UINT8(TxThresh, RTL8139State),
+
+ VMSTATE_UNUSED(4),
+ VMSTATE_MACADDR(conf.macaddr, RTL8139State),
+ VMSTATE_INT32(rtl8139_mmio_io_addr_dummy, RTL8139State),
+
+ VMSTATE_UINT32(currTxDesc, RTL8139State),
+ VMSTATE_UINT32(currCPlusRxDesc, RTL8139State),
+ VMSTATE_UINT32(currCPlusTxDesc, RTL8139State),
+ VMSTATE_UINT32(RxRingAddrLO, RTL8139State),
+ VMSTATE_UINT32(RxRingAddrHI, RTL8139State),
+
+ VMSTATE_UINT16_ARRAY(eeprom.contents, RTL8139State, EEPROM_9346_SIZE),
+ VMSTATE_INT32(eeprom.mode, RTL8139State),
+ VMSTATE_UINT32(eeprom.tick, RTL8139State),
+ VMSTATE_UINT8(eeprom.address, RTL8139State),
+ VMSTATE_UINT16(eeprom.input, RTL8139State),
+ VMSTATE_UINT16(eeprom.output, RTL8139State),
+
+ VMSTATE_UINT8(eeprom.eecs, RTL8139State),
+ VMSTATE_UINT8(eeprom.eesk, RTL8139State),
+ VMSTATE_UINT8(eeprom.eedi, RTL8139State),
+ VMSTATE_UINT8(eeprom.eedo, RTL8139State),
+
+ VMSTATE_UINT32(TCTR, RTL8139State),
+ VMSTATE_UINT32(TimerInt, RTL8139State),
+ VMSTATE_INT64(TCTR_base, RTL8139State),
+
+ VMSTATE_STRUCT(tally_counters, RTL8139State, 0,
+ vmstate_tally_counters, RTL8139TallyCounters),
+
+ VMSTATE_UINT32_V(cplus_enabled, RTL8139State, 4),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_rtl8139_hotplug_ready,
+ NULL
+ }
+};
+
+/***********************************************************/
+/* PCI RTL8139 definitions */
+
+static void rtl8139_ioport_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ switch (size) {
+ case 1:
+ rtl8139_io_writeb(opaque, addr, val);
+ break;
+ case 2:
+ rtl8139_io_writew(opaque, addr, val);
+ break;
+ case 4:
+ rtl8139_io_writel(opaque, addr, val);
+ break;
+ }
+}
+
+static uint64_t rtl8139_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ switch (size) {
+ case 1:
+ return rtl8139_io_readb(opaque, addr);
+ case 2:
+ return rtl8139_io_readw(opaque, addr);
+ case 4:
+ return rtl8139_io_readl(opaque, addr);
+ }
+
+ return -1;
+}
+
+static const MemoryRegionOps rtl8139_io_ops = {
+ .read = rtl8139_ioport_read,
+ .write = rtl8139_ioport_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps rtl8139_mmio_ops = {
+ .old_mmio = {
+ .read = {
+ rtl8139_mmio_readb,
+ rtl8139_mmio_readw,
+ rtl8139_mmio_readl,
+ },
+ .write = {
+ rtl8139_mmio_writeb,
+ rtl8139_mmio_writew,
+ rtl8139_mmio_writel,
+ },
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void rtl8139_timer(void *opaque)
+{
+ RTL8139State *s = opaque;
+
+ if (!s->clock_enabled)
+ {
+ DPRINTF(">>> timer: clock is not running\n");
+ return;
+ }
+
+ s->IntrStatus |= PCSTimeout;
+ rtl8139_update_irq(s);
+ rtl8139_set_next_tctr_time(s);
+}
+
+static void pci_rtl8139_uninit(PCIDevice *dev)
+{
+ RTL8139State *s = RTL8139(dev);
+
+ if (s->cplus_txbuffer) {
+ g_free(s->cplus_txbuffer);
+ s->cplus_txbuffer = NULL;
+ }
+ timer_del(s->timer);
+ timer_free(s->timer);
+ qemu_del_nic(s->nic);
+}
+
+static void rtl8139_set_link_status(NetClientState *nc)
+{
+ RTL8139State *s = qemu_get_nic_opaque(nc);
+
+ if (nc->link_down) {
+ s->BasicModeStatus &= ~0x04;
+ } else {
+ s->BasicModeStatus |= 0x04;
+ }
+
+ s->IntrStatus |= RxUnderrun;
+ rtl8139_update_irq(s);
+}
+
+static NetClientInfo net_rtl8139_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = rtl8139_can_receive,
+ .receive = rtl8139_receive,
+ .link_status_changed = rtl8139_set_link_status,
+};
+
+static void pci_rtl8139_realize(PCIDevice *dev, Error **errp)
+{
+ RTL8139State *s = RTL8139(dev);
+ DeviceState *d = DEVICE(dev);
+ uint8_t *pci_conf;
+
+ pci_conf = dev->config;
+ pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
+ /* TODO: start of capability list, but no capability
+ * list bit in status register, and offset 0xdc seems unused. */
+ pci_conf[PCI_CAPABILITY_LIST] = 0xdc;
+
+ memory_region_init_io(&s->bar_io, OBJECT(s), &rtl8139_io_ops, s,
+ "rtl8139", 0x100);
+ memory_region_init_io(&s->bar_mem, OBJECT(s), &rtl8139_mmio_ops, s,
+ "rtl8139", 0x100);
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->bar_io);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar_mem);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+
+ /* prepare eeprom */
+ s->eeprom.contents[0] = 0x8129;
+#if 1
+ /* PCI vendor and device ID should be mirrored here */
+ s->eeprom.contents[1] = PCI_VENDOR_ID_REALTEK;
+ s->eeprom.contents[2] = PCI_DEVICE_ID_REALTEK_8139;
+#endif
+ s->eeprom.contents[7] = s->conf.macaddr.a[0] | s->conf.macaddr.a[1] << 8;
+ s->eeprom.contents[8] = s->conf.macaddr.a[2] | s->conf.macaddr.a[3] << 8;
+ s->eeprom.contents[9] = s->conf.macaddr.a[4] | s->conf.macaddr.a[5] << 8;
+
+ s->nic = qemu_new_nic(&net_rtl8139_info, &s->conf,
+ object_get_typename(OBJECT(dev)), d->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ s->cplus_txbuffer = NULL;
+ s->cplus_txbuffer_len = 0;
+ s->cplus_txbuffer_offset = 0;
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, rtl8139_timer, s);
+}
+
+static void rtl8139_instance_init(Object *obj)
+{
+ RTL8139State *s = RTL8139(obj);
+
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ DEVICE(obj), NULL);
+}
+
+static Property rtl8139_properties[] = {
+ DEFINE_NIC_PROPERTIES(RTL8139State, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void rtl8139_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_rtl8139_realize;
+ k->exit = pci_rtl8139_uninit;
+ k->romfile = "efi-rtl8139.rom";
+ k->vendor_id = PCI_VENDOR_ID_REALTEK;
+ k->device_id = PCI_DEVICE_ID_REALTEK_8139;
+ k->revision = RTL8139_PCI_REVID; /* >=0x20 is for 8139C+ */
+ k->class_id = PCI_CLASS_NETWORK_ETHERNET;
+ dc->reset = rtl8139_reset;
+ dc->vmsd = &vmstate_rtl8139;
+ dc->props = rtl8139_properties;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo rtl8139_info = {
+ .name = TYPE_RTL8139,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(RTL8139State),
+ .class_init = rtl8139_class_init,
+ .instance_init = rtl8139_instance_init,
+};
+
+static void rtl8139_register_types(void)
+{
+ type_register_static(&rtl8139_info);
+}
+
+type_init(rtl8139_register_types)
diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c
new file mode 100644
index 00000000..74e06e6c
--- /dev/null
+++ b/hw/net/smc91c111.c
@@ -0,0 +1,807 @@
+/*
+ * SMSC 91C111 Ethernet interface emulation
+ *
+ * Copyright (c) 2005 CodeSourcery, LLC.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL
+ */
+
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "hw/devices.h"
+/* For crc32 */
+#include <zlib.h>
+
+/* Number of 2k memory pages available. */
+#define NUM_PACKETS 4
+
+#define TYPE_SMC91C111 "smc91c111"
+#define SMC91C111(obj) OBJECT_CHECK(smc91c111_state, (obj), TYPE_SMC91C111)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ NICState *nic;
+ NICConf conf;
+ uint16_t tcr;
+ uint16_t rcr;
+ uint16_t cr;
+ uint16_t ctr;
+ uint16_t gpr;
+ uint16_t ptr;
+ uint16_t ercv;
+ qemu_irq irq;
+ int bank;
+ int packet_num;
+ int tx_alloc;
+ /* Bitmask of allocated packets. */
+ int allocated;
+ int tx_fifo_len;
+ int tx_fifo[NUM_PACKETS];
+ int rx_fifo_len;
+ int rx_fifo[NUM_PACKETS];
+ int tx_fifo_done_len;
+ int tx_fifo_done[NUM_PACKETS];
+ /* Packet buffer memory. */
+ uint8_t data[NUM_PACKETS][2048];
+ uint8_t int_level;
+ uint8_t int_mask;
+ MemoryRegion mmio;
+} smc91c111_state;
+
+static const VMStateDescription vmstate_smc91c111 = {
+ .name = "smc91c111",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(tcr, smc91c111_state),
+ VMSTATE_UINT16(rcr, smc91c111_state),
+ VMSTATE_UINT16(cr, smc91c111_state),
+ VMSTATE_UINT16(ctr, smc91c111_state),
+ VMSTATE_UINT16(gpr, smc91c111_state),
+ VMSTATE_UINT16(ptr, smc91c111_state),
+ VMSTATE_UINT16(ercv, smc91c111_state),
+ VMSTATE_INT32(bank, smc91c111_state),
+ VMSTATE_INT32(packet_num, smc91c111_state),
+ VMSTATE_INT32(tx_alloc, smc91c111_state),
+ VMSTATE_INT32(allocated, smc91c111_state),
+ VMSTATE_INT32(tx_fifo_len, smc91c111_state),
+ VMSTATE_INT32_ARRAY(tx_fifo, smc91c111_state, NUM_PACKETS),
+ VMSTATE_INT32(rx_fifo_len, smc91c111_state),
+ VMSTATE_INT32_ARRAY(rx_fifo, smc91c111_state, NUM_PACKETS),
+ VMSTATE_INT32(tx_fifo_done_len, smc91c111_state),
+ VMSTATE_INT32_ARRAY(tx_fifo_done, smc91c111_state, NUM_PACKETS),
+ VMSTATE_BUFFER_UNSAFE(data, smc91c111_state, 0, NUM_PACKETS * 2048),
+ VMSTATE_UINT8(int_level, smc91c111_state),
+ VMSTATE_UINT8(int_mask, smc91c111_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define RCR_SOFT_RST 0x8000
+#define RCR_STRIP_CRC 0x0200
+#define RCR_RXEN 0x0100
+
+#define TCR_EPH_LOOP 0x2000
+#define TCR_NOCRC 0x0100
+#define TCR_PAD_EN 0x0080
+#define TCR_FORCOL 0x0004
+#define TCR_LOOP 0x0002
+#define TCR_TXEN 0x0001
+
+#define INT_MD 0x80
+#define INT_ERCV 0x40
+#define INT_EPH 0x20
+#define INT_RX_OVRN 0x10
+#define INT_ALLOC 0x08
+#define INT_TX_EMPTY 0x04
+#define INT_TX 0x02
+#define INT_RCV 0x01
+
+#define CTR_AUTO_RELEASE 0x0800
+#define CTR_RELOAD 0x0002
+#define CTR_STORE 0x0001
+
+#define RS_ALGNERR 0x8000
+#define RS_BRODCAST 0x4000
+#define RS_BADCRC 0x2000
+#define RS_ODDFRAME 0x1000
+#define RS_TOOLONG 0x0800
+#define RS_TOOSHORT 0x0400
+#define RS_MULTICAST 0x0001
+
+/* Update interrupt status. */
+static void smc91c111_update(smc91c111_state *s)
+{
+ int level;
+
+ if (s->tx_fifo_len == 0)
+ s->int_level |= INT_TX_EMPTY;
+ if (s->tx_fifo_done_len != 0)
+ s->int_level |= INT_TX;
+ level = (s->int_level & s->int_mask) != 0;
+ qemu_set_irq(s->irq, level);
+}
+
+/* Try to allocate a packet. Returns 0x80 on failure. */
+static int smc91c111_allocate_packet(smc91c111_state *s)
+{
+ int i;
+ if (s->allocated == (1 << NUM_PACKETS) - 1) {
+ return 0x80;
+ }
+
+ for (i = 0; i < NUM_PACKETS; i++) {
+ if ((s->allocated & (1 << i)) == 0)
+ break;
+ }
+ s->allocated |= 1 << i;
+ return i;
+}
+
+
+/* Process a pending TX allocate. */
+static void smc91c111_tx_alloc(smc91c111_state *s)
+{
+ s->tx_alloc = smc91c111_allocate_packet(s);
+ if (s->tx_alloc == 0x80)
+ return;
+ s->int_level |= INT_ALLOC;
+ smc91c111_update(s);
+}
+
+/* Remove and item from the RX FIFO. */
+static void smc91c111_pop_rx_fifo(smc91c111_state *s)
+{
+ int i;
+
+ s->rx_fifo_len--;
+ if (s->rx_fifo_len) {
+ for (i = 0; i < s->rx_fifo_len; i++)
+ s->rx_fifo[i] = s->rx_fifo[i + 1];
+ s->int_level |= INT_RCV;
+ } else {
+ s->int_level &= ~INT_RCV;
+ }
+ smc91c111_update(s);
+}
+
+/* Remove an item from the TX completion FIFO. */
+static void smc91c111_pop_tx_fifo_done(smc91c111_state *s)
+{
+ int i;
+
+ if (s->tx_fifo_done_len == 0)
+ return;
+ s->tx_fifo_done_len--;
+ for (i = 0; i < s->tx_fifo_done_len; i++)
+ s->tx_fifo_done[i] = s->tx_fifo_done[i + 1];
+}
+
+/* Release the memory allocated to a packet. */
+static void smc91c111_release_packet(smc91c111_state *s, int packet)
+{
+ s->allocated &= ~(1 << packet);
+ if (s->tx_alloc == 0x80)
+ smc91c111_tx_alloc(s);
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+}
+
+/* Flush the TX FIFO. */
+static void smc91c111_do_tx(smc91c111_state *s)
+{
+ int i;
+ int len;
+ int control;
+ int packetnum;
+ uint8_t *p;
+
+ if ((s->tcr & TCR_TXEN) == 0)
+ return;
+ if (s->tx_fifo_len == 0)
+ return;
+ for (i = 0; i < s->tx_fifo_len; i++) {
+ packetnum = s->tx_fifo[i];
+ p = &s->data[packetnum][0];
+ /* Set status word. */
+ *(p++) = 0x01;
+ *(p++) = 0x40;
+ len = *(p++);
+ len |= ((int)*(p++)) << 8;
+ len -= 6;
+ control = p[len + 1];
+ if (control & 0x20)
+ len++;
+ /* ??? This overwrites the data following the buffer.
+ Don't know what real hardware does. */
+ if (len < 64 && (s->tcr & TCR_PAD_EN)) {
+ memset(p + len, 0, 64 - len);
+ len = 64;
+ }
+#if 0
+ {
+ int add_crc;
+
+ /* The card is supposed to append the CRC to the frame.
+ However none of the other network traffic has the CRC
+ appended. Suspect this is low level ethernet detail we
+ don't need to worry about. */
+ add_crc = (control & 0x10) || (s->tcr & TCR_NOCRC) == 0;
+ if (add_crc) {
+ uint32_t crc;
+
+ crc = crc32(~0, p, len);
+ memcpy(p + len, &crc, 4);
+ len += 4;
+ }
+ }
+#endif
+ if (s->ctr & CTR_AUTO_RELEASE)
+ /* Race? */
+ smc91c111_release_packet(s, packetnum);
+ else if (s->tx_fifo_done_len < NUM_PACKETS)
+ s->tx_fifo_done[s->tx_fifo_done_len++] = packetnum;
+ qemu_send_packet(qemu_get_queue(s->nic), p, len);
+ }
+ s->tx_fifo_len = 0;
+ smc91c111_update(s);
+}
+
+/* Add a packet to the TX FIFO. */
+static void smc91c111_queue_tx(smc91c111_state *s, int packet)
+{
+ if (s->tx_fifo_len == NUM_PACKETS)
+ return;
+ s->tx_fifo[s->tx_fifo_len++] = packet;
+ smc91c111_do_tx(s);
+}
+
+static void smc91c111_reset(DeviceState *dev)
+{
+ smc91c111_state *s = SMC91C111(dev);
+
+ s->bank = 0;
+ s->tx_fifo_len = 0;
+ s->tx_fifo_done_len = 0;
+ s->rx_fifo_len = 0;
+ s->allocated = 0;
+ s->packet_num = 0;
+ s->tx_alloc = 0;
+ s->tcr = 0;
+ s->rcr = 0;
+ s->cr = 0xa0b1;
+ s->ctr = 0x1210;
+ s->ptr = 0;
+ s->ercv = 0x1f;
+ s->int_level = INT_TX_EMPTY;
+ s->int_mask = 0;
+ smc91c111_update(s);
+}
+
+#define SET_LOW(name, val) s->name = (s->name & 0xff00) | val
+#define SET_HIGH(name, val) s->name = (s->name & 0xff) | (val << 8)
+
+static void smc91c111_writeb(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ smc91c111_state *s = (smc91c111_state *)opaque;
+
+ offset = offset & 0xf;
+ if (offset == 14) {
+ s->bank = value;
+ return;
+ }
+ if (offset == 15)
+ return;
+ switch (s->bank) {
+ case 0:
+ switch (offset) {
+ case 0: /* TCR */
+ SET_LOW(tcr, value);
+ return;
+ case 1:
+ SET_HIGH(tcr, value);
+ return;
+ case 4: /* RCR */
+ SET_LOW(rcr, value);
+ return;
+ case 5:
+ SET_HIGH(rcr, value);
+ if (s->rcr & RCR_SOFT_RST) {
+ smc91c111_reset(DEVICE(s));
+ }
+ return;
+ case 10: case 11: /* RPCR */
+ /* Ignored */
+ return;
+ case 12: case 13: /* Reserved */
+ return;
+ }
+ break;
+
+ case 1:
+ switch (offset) {
+ case 0: /* CONFIG */
+ SET_LOW(cr, value);
+ return;
+ case 1:
+ SET_HIGH(cr,value);
+ return;
+ case 2: case 3: /* BASE */
+ case 4: case 5: case 6: case 7: case 8: case 9: /* IA */
+ /* Not implemented. */
+ return;
+ case 10: /* Genral Purpose */
+ SET_LOW(gpr, value);
+ return;
+ case 11:
+ SET_HIGH(gpr, value);
+ return;
+ case 12: /* Control */
+ if (value & 1)
+ fprintf(stderr, "smc91c111:EEPROM store not implemented\n");
+ if (value & 2)
+ fprintf(stderr, "smc91c111:EEPROM reload not implemented\n");
+ value &= ~3;
+ SET_LOW(ctr, value);
+ return;
+ case 13:
+ SET_HIGH(ctr, value);
+ return;
+ }
+ break;
+
+ case 2:
+ switch (offset) {
+ case 0: /* MMU Command */
+ switch (value >> 5) {
+ case 0: /* no-op */
+ break;
+ case 1: /* Allocate for TX. */
+ s->tx_alloc = 0x80;
+ s->int_level &= ~INT_ALLOC;
+ smc91c111_update(s);
+ smc91c111_tx_alloc(s);
+ break;
+ case 2: /* Reset MMU. */
+ s->allocated = 0;
+ s->tx_fifo_len = 0;
+ s->tx_fifo_done_len = 0;
+ s->rx_fifo_len = 0;
+ s->tx_alloc = 0;
+ break;
+ case 3: /* Remove from RX FIFO. */
+ smc91c111_pop_rx_fifo(s);
+ break;
+ case 4: /* Remove from RX FIFO and release. */
+ if (s->rx_fifo_len > 0) {
+ smc91c111_release_packet(s, s->rx_fifo[0]);
+ }
+ smc91c111_pop_rx_fifo(s);
+ break;
+ case 5: /* Release. */
+ smc91c111_release_packet(s, s->packet_num);
+ break;
+ case 6: /* Add to TX FIFO. */
+ smc91c111_queue_tx(s, s->packet_num);
+ break;
+ case 7: /* Reset TX FIFO. */
+ s->tx_fifo_len = 0;
+ s->tx_fifo_done_len = 0;
+ break;
+ }
+ return;
+ case 1:
+ /* Ignore. */
+ return;
+ case 2: /* Packet Number Register */
+ s->packet_num = value;
+ return;
+ case 3: case 4: case 5:
+ /* Should be readonly, but linux writes to them anyway. Ignore. */
+ return;
+ case 6: /* Pointer */
+ SET_LOW(ptr, value);
+ return;
+ case 7:
+ SET_HIGH(ptr, value);
+ return;
+ case 8: case 9: case 10: case 11: /* Data */
+ {
+ int p;
+ int n;
+
+ if (s->ptr & 0x8000)
+ n = s->rx_fifo[0];
+ else
+ n = s->packet_num;
+ p = s->ptr & 0x07ff;
+ if (s->ptr & 0x4000) {
+ s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x7ff);
+ } else {
+ p += (offset & 3);
+ }
+ s->data[n][p] = value;
+ }
+ return;
+ case 12: /* Interrupt ACK. */
+ s->int_level &= ~(value & 0xd6);
+ if (value & INT_TX)
+ smc91c111_pop_tx_fifo_done(s);
+ smc91c111_update(s);
+ return;
+ case 13: /* Interrupt mask. */
+ s->int_mask = value;
+ smc91c111_update(s);
+ return;
+ }
+ break;
+
+ case 3:
+ switch (offset) {
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
+ /* Multicast table. */
+ /* Not implemented. */
+ return;
+ case 8: case 9: /* Management Interface. */
+ /* Not implemented. */
+ return;
+ case 12: /* Early receive. */
+ s->ercv = value & 0x1f;
+ return;
+ case 13:
+ /* Ignore. */
+ return;
+ }
+ break;
+ }
+ hw_error("smc91c111_write: Bad reg %d:%x\n", s->bank, (int)offset);
+}
+
+static uint32_t smc91c111_readb(void *opaque, hwaddr offset)
+{
+ smc91c111_state *s = (smc91c111_state *)opaque;
+
+ offset = offset & 0xf;
+ if (offset == 14) {
+ return s->bank;
+ }
+ if (offset == 15)
+ return 0x33;
+ switch (s->bank) {
+ case 0:
+ switch (offset) {
+ case 0: /* TCR */
+ return s->tcr & 0xff;
+ case 1:
+ return s->tcr >> 8;
+ case 2: /* EPH Status */
+ return 0;
+ case 3:
+ return 0x40;
+ case 4: /* RCR */
+ return s->rcr & 0xff;
+ case 5:
+ return s->rcr >> 8;
+ case 6: /* Counter */
+ case 7:
+ /* Not implemented. */
+ return 0;
+ case 8: /* Memory size. */
+ return NUM_PACKETS;
+ case 9: /* Free memory available. */
+ {
+ int i;
+ int n;
+ n = 0;
+ for (i = 0; i < NUM_PACKETS; i++) {
+ if (s->allocated & (1 << i))
+ n++;
+ }
+ return n;
+ }
+ case 10: case 11: /* RPCR */
+ /* Not implemented. */
+ return 0;
+ case 12: case 13: /* Reserved */
+ return 0;
+ }
+ break;
+
+ case 1:
+ switch (offset) {
+ case 0: /* CONFIG */
+ return s->cr & 0xff;
+ case 1:
+ return s->cr >> 8;
+ case 2: case 3: /* BASE */
+ /* Not implemented. */
+ return 0;
+ case 4: case 5: case 6: case 7: case 8: case 9: /* IA */
+ return s->conf.macaddr.a[offset - 4];
+ case 10: /* General Purpose */
+ return s->gpr & 0xff;
+ case 11:
+ return s->gpr >> 8;
+ case 12: /* Control */
+ return s->ctr & 0xff;
+ case 13:
+ return s->ctr >> 8;
+ }
+ break;
+
+ case 2:
+ switch (offset) {
+ case 0: case 1: /* MMUCR Busy bit. */
+ return 0;
+ case 2: /* Packet Number. */
+ return s->packet_num;
+ case 3: /* Allocation Result. */
+ return s->tx_alloc;
+ case 4: /* TX FIFO */
+ if (s->tx_fifo_done_len == 0)
+ return 0x80;
+ else
+ return s->tx_fifo_done[0];
+ case 5: /* RX FIFO */
+ if (s->rx_fifo_len == 0)
+ return 0x80;
+ else
+ return s->rx_fifo[0];
+ case 6: /* Pointer */
+ return s->ptr & 0xff;
+ case 7:
+ return (s->ptr >> 8) & 0xf7;
+ case 8: case 9: case 10: case 11: /* Data */
+ {
+ int p;
+ int n;
+
+ if (s->ptr & 0x8000)
+ n = s->rx_fifo[0];
+ else
+ n = s->packet_num;
+ p = s->ptr & 0x07ff;
+ if (s->ptr & 0x4000) {
+ s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x07ff);
+ } else {
+ p += (offset & 3);
+ }
+ return s->data[n][p];
+ }
+ case 12: /* Interrupt status. */
+ return s->int_level;
+ case 13: /* Interrupt mask. */
+ return s->int_mask;
+ }
+ break;
+
+ case 3:
+ switch (offset) {
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
+ /* Multicast table. */
+ /* Not implemented. */
+ return 0;
+ case 8: /* Management Interface. */
+ /* Not implemented. */
+ return 0x30;
+ case 9:
+ return 0x33;
+ case 10: /* Revision. */
+ return 0x91;
+ case 11:
+ return 0x33;
+ case 12:
+ return s->ercv;
+ case 13:
+ return 0;
+ }
+ break;
+ }
+ hw_error("smc91c111_read: Bad reg %d:%x\n", s->bank, (int)offset);
+ return 0;
+}
+
+static void smc91c111_writew(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ smc91c111_writeb(opaque, offset, value & 0xff);
+ smc91c111_writeb(opaque, offset + 1, value >> 8);
+}
+
+static void smc91c111_writel(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ /* 32-bit writes to offset 0xc only actually write to the bank select
+ register (offset 0xe) */
+ if (offset != 0xc)
+ smc91c111_writew(opaque, offset, value & 0xffff);
+ smc91c111_writew(opaque, offset + 2, value >> 16);
+}
+
+static uint32_t smc91c111_readw(void *opaque, hwaddr offset)
+{
+ uint32_t val;
+ val = smc91c111_readb(opaque, offset);
+ val |= smc91c111_readb(opaque, offset + 1) << 8;
+ return val;
+}
+
+static uint32_t smc91c111_readl(void *opaque, hwaddr offset)
+{
+ uint32_t val;
+ val = smc91c111_readw(opaque, offset);
+ val |= smc91c111_readw(opaque, offset + 2) << 16;
+ return val;
+}
+
+static int smc91c111_can_receive(NetClientState *nc)
+{
+ smc91c111_state *s = qemu_get_nic_opaque(nc);
+
+ if ((s->rcr & RCR_RXEN) == 0 || (s->rcr & RCR_SOFT_RST))
+ return 1;
+ if (s->allocated == (1 << NUM_PACKETS) - 1)
+ return 0;
+ return 1;
+}
+
+static ssize_t smc91c111_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ smc91c111_state *s = qemu_get_nic_opaque(nc);
+ int status;
+ int packetsize;
+ uint32_t crc;
+ int packetnum;
+ uint8_t *p;
+
+ if ((s->rcr & RCR_RXEN) == 0 || (s->rcr & RCR_SOFT_RST))
+ return -1;
+ /* Short packets are padded with zeros. Receiving a packet
+ < 64 bytes long is considered an error condition. */
+ if (size < 64)
+ packetsize = 64;
+ else
+ packetsize = (size & ~1);
+ packetsize += 6;
+ crc = (s->rcr & RCR_STRIP_CRC) == 0;
+ if (crc)
+ packetsize += 4;
+ /* TODO: Flag overrun and receive errors. */
+ if (packetsize > 2048)
+ return -1;
+ packetnum = smc91c111_allocate_packet(s);
+ if (packetnum == 0x80)
+ return -1;
+ s->rx_fifo[s->rx_fifo_len++] = packetnum;
+
+ p = &s->data[packetnum][0];
+ /* ??? Multicast packets? */
+ status = 0;
+ if (size > 1518)
+ status |= RS_TOOLONG;
+ if (size & 1)
+ status |= RS_ODDFRAME;
+ *(p++) = status & 0xff;
+ *(p++) = status >> 8;
+ *(p++) = packetsize & 0xff;
+ *(p++) = packetsize >> 8;
+ memcpy(p, buf, size & ~1);
+ p += (size & ~1);
+ /* Pad short packets. */
+ if (size < 64) {
+ int pad;
+
+ if (size & 1)
+ *(p++) = buf[size - 1];
+ pad = 64 - size;
+ memset(p, 0, pad);
+ p += pad;
+ size = 64;
+ }
+ /* It's not clear if the CRC should go before or after the last byte in
+ odd sized packets. Linux disables the CRC, so that's no help.
+ The pictures in the documentation show the CRC aligned on a 16-bit
+ boundary before the last odd byte, so that's what we do. */
+ if (crc) {
+ crc = crc32(~0, buf, size);
+ *(p++) = crc & 0xff; crc >>= 8;
+ *(p++) = crc & 0xff; crc >>= 8;
+ *(p++) = crc & 0xff; crc >>= 8;
+ *(p++) = crc & 0xff;
+ }
+ if (size & 1) {
+ *(p++) = buf[size - 1];
+ *p = 0x60;
+ } else {
+ *(p++) = 0;
+ *p = 0x40;
+ }
+ /* TODO: Raise early RX interrupt? */
+ s->int_level |= INT_RCV;
+ smc91c111_update(s);
+
+ return size;
+}
+
+static const MemoryRegionOps smc91c111_mem_ops = {
+ /* The special case for 32 bit writes to 0xc means we can't just
+ * set .impl.min/max_access_size to 1, unfortunately
+ */
+ .old_mmio = {
+ .read = { smc91c111_readb, smc91c111_readw, smc91c111_readl, },
+ .write = { smc91c111_writeb, smc91c111_writew, smc91c111_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static NetClientInfo net_smc91c111_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = smc91c111_can_receive,
+ .receive = smc91c111_receive,
+};
+
+static int smc91c111_init1(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ smc91c111_state *s = SMC91C111(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &smc91c111_mem_ops, s,
+ "smc91c111-mmio", 16);
+ sysbus_init_mmio(sbd, &s->mmio);
+ sysbus_init_irq(sbd, &s->irq);
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(&net_smc91c111_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+ /* ??? Save/restore. */
+ return 0;
+}
+
+static Property smc91c111_properties[] = {
+ DEFINE_NIC_PROPERTIES(smc91c111_state, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void smc91c111_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = smc91c111_init1;
+ dc->reset = smc91c111_reset;
+ dc->vmsd = &vmstate_smc91c111;
+ dc->props = smc91c111_properties;
+}
+
+static const TypeInfo smc91c111_info = {
+ .name = TYPE_SMC91C111,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(smc91c111_state),
+ .class_init = smc91c111_class_init,
+};
+
+static void smc91c111_register_types(void)
+{
+ type_register_static(&smc91c111_info);
+}
+
+/* Legacy helper function. Should go away when machine config files are
+ implemented. */
+void smc91c111_init(NICInfo *nd, uint32_t base, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ qemu_check_nic_model(nd, "smc91c111");
+ dev = qdev_create(NULL, TYPE_SMC91C111);
+ qdev_set_nic_properties(dev, nd);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, base);
+ sysbus_connect_irq(s, 0, irq);
+}
+
+type_init(smc91c111_register_types)
diff --git a/hw/net/spapr_llan.c b/hw/net/spapr_llan.c
new file mode 100644
index 00000000..1ca5e9ce
--- /dev/null
+++ b/hw/net/spapr_llan.c
@@ -0,0 +1,569 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * PAPR Inter-VM Logical Lan, aka ibmveth
+ *
+ * Copyright (c) 2010,2011 David Gibson, IBM Corporation.
+ *
+ * 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.
+ *
+ */
+#include "hw/hw.h"
+#include "net/net.h"
+#include "hw/qdev.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "sysemu/sysemu.h"
+
+#include <libfdt.h>
+
+#define ETH_ALEN 6
+#define MAX_PACKET_SIZE 65536
+
+/*#define DEBUG*/
+
+#ifdef DEBUG
+#define DPRINTF(fmt...) do { fprintf(stderr, fmt); } while (0)
+#else
+#define DPRINTF(fmt...)
+#endif
+
+/*
+ * Virtual LAN device
+ */
+
+typedef uint64_t vlan_bd_t;
+
+#define VLAN_BD_VALID 0x8000000000000000ULL
+#define VLAN_BD_TOGGLE 0x4000000000000000ULL
+#define VLAN_BD_NO_CSUM 0x0200000000000000ULL
+#define VLAN_BD_CSUM_GOOD 0x0100000000000000ULL
+#define VLAN_BD_LEN_MASK 0x00ffffff00000000ULL
+#define VLAN_BD_LEN(bd) (((bd) & VLAN_BD_LEN_MASK) >> 32)
+#define VLAN_BD_ADDR_MASK 0x00000000ffffffffULL
+#define VLAN_BD_ADDR(bd) ((bd) & VLAN_BD_ADDR_MASK)
+
+#define VLAN_VALID_BD(addr, len) (VLAN_BD_VALID | \
+ (((len) << 32) & VLAN_BD_LEN_MASK) | \
+ (addr & VLAN_BD_ADDR_MASK))
+
+#define VLAN_RXQC_TOGGLE 0x80
+#define VLAN_RXQC_VALID 0x40
+#define VLAN_RXQC_NO_CSUM 0x02
+#define VLAN_RXQC_CSUM_GOOD 0x01
+
+#define VLAN_RQ_ALIGNMENT 16
+#define VLAN_RXQ_BD_OFF 0
+#define VLAN_FILTER_BD_OFF 8
+#define VLAN_RX_BDS_OFF 16
+/*
+ * The final 8 bytes of the buffer list is a counter of frames dropped
+ * because there was not a buffer in the buffer list capable of holding
+ * the frame. We must avoid it, or the operating system will report garbage
+ * for this statistic.
+ */
+#define VLAN_RX_BDS_LEN (SPAPR_TCE_PAGE_SIZE - VLAN_RX_BDS_OFF - 8)
+#define VLAN_MAX_BUFS (VLAN_RX_BDS_LEN / 8)
+
+#define TYPE_VIO_SPAPR_VLAN_DEVICE "spapr-vlan"
+#define VIO_SPAPR_VLAN_DEVICE(obj) \
+ OBJECT_CHECK(VIOsPAPRVLANDevice, (obj), TYPE_VIO_SPAPR_VLAN_DEVICE)
+
+typedef struct VIOsPAPRVLANDevice {
+ VIOsPAPRDevice sdev;
+ NICConf nicconf;
+ NICState *nic;
+ bool isopen;
+ target_ulong buf_list;
+ uint32_t add_buf_ptr, use_buf_ptr, rx_bufs;
+ target_ulong rxq_ptr;
+} VIOsPAPRVLANDevice;
+
+static int spapr_vlan_can_receive(NetClientState *nc)
+{
+ VIOsPAPRVLANDevice *dev = qemu_get_nic_opaque(nc);
+
+ return (dev->isopen && dev->rx_bufs > 0);
+}
+
+static ssize_t spapr_vlan_receive(NetClientState *nc, const uint8_t *buf,
+ size_t size)
+{
+ VIOsPAPRVLANDevice *dev = qemu_get_nic_opaque(nc);
+ VIOsPAPRDevice *sdev = VIO_SPAPR_DEVICE(dev);
+ vlan_bd_t rxq_bd = vio_ldq(sdev, dev->buf_list + VLAN_RXQ_BD_OFF);
+ vlan_bd_t bd;
+ int buf_ptr = dev->use_buf_ptr;
+ uint64_t handle;
+ uint8_t control;
+
+ DPRINTF("spapr_vlan_receive() [%s] rx_bufs=%d\n", sdev->qdev.id,
+ dev->rx_bufs);
+
+ if (!dev->isopen) {
+ return -1;
+ }
+
+ if (!dev->rx_bufs) {
+ return -1;
+ }
+
+ do {
+ buf_ptr += 8;
+ if (buf_ptr >= (VLAN_RX_BDS_LEN + VLAN_RX_BDS_OFF)) {
+ buf_ptr = VLAN_RX_BDS_OFF;
+ }
+
+ bd = vio_ldq(sdev, dev->buf_list + buf_ptr);
+ DPRINTF("use_buf_ptr=%d bd=0x%016llx\n",
+ buf_ptr, (unsigned long long)bd);
+ } while ((!(bd & VLAN_BD_VALID) || (VLAN_BD_LEN(bd) < (size + 8)))
+ && (buf_ptr != dev->use_buf_ptr));
+
+ if (!(bd & VLAN_BD_VALID) || (VLAN_BD_LEN(bd) < (size + 8))) {
+ /* Failed to find a suitable buffer */
+ return -1;
+ }
+
+ /* Remove the buffer from the pool */
+ dev->rx_bufs--;
+ dev->use_buf_ptr = buf_ptr;
+ vio_stq(sdev, dev->buf_list + dev->use_buf_ptr, 0);
+
+ DPRINTF("Found buffer: ptr=%d num=%d\n", dev->use_buf_ptr, dev->rx_bufs);
+
+ /* Transfer the packet data */
+ if (spapr_vio_dma_write(sdev, VLAN_BD_ADDR(bd) + 8, buf, size) < 0) {
+ return -1;
+ }
+
+ DPRINTF("spapr_vlan_receive: DMA write completed\n");
+
+ /* Update the receive queue */
+ control = VLAN_RXQC_TOGGLE | VLAN_RXQC_VALID;
+ if (rxq_bd & VLAN_BD_TOGGLE) {
+ control ^= VLAN_RXQC_TOGGLE;
+ }
+
+ handle = vio_ldq(sdev, VLAN_BD_ADDR(bd));
+ vio_stq(sdev, VLAN_BD_ADDR(rxq_bd) + dev->rxq_ptr + 8, handle);
+ vio_stl(sdev, VLAN_BD_ADDR(rxq_bd) + dev->rxq_ptr + 4, size);
+ vio_sth(sdev, VLAN_BD_ADDR(rxq_bd) + dev->rxq_ptr + 2, 8);
+ vio_stb(sdev, VLAN_BD_ADDR(rxq_bd) + dev->rxq_ptr, control);
+
+ DPRINTF("wrote rxq entry (ptr=0x%llx): 0x%016llx 0x%016llx\n",
+ (unsigned long long)dev->rxq_ptr,
+ (unsigned long long)vio_ldq(sdev, VLAN_BD_ADDR(rxq_bd) +
+ dev->rxq_ptr),
+ (unsigned long long)vio_ldq(sdev, VLAN_BD_ADDR(rxq_bd) +
+ dev->rxq_ptr + 8));
+
+ dev->rxq_ptr += 16;
+ if (dev->rxq_ptr >= VLAN_BD_LEN(rxq_bd)) {
+ dev->rxq_ptr = 0;
+ vio_stq(sdev, dev->buf_list + VLAN_RXQ_BD_OFF, rxq_bd ^ VLAN_BD_TOGGLE);
+ }
+
+ if (sdev->signal_state & 1) {
+ qemu_irq_pulse(spapr_vio_qirq(sdev));
+ }
+
+ return size;
+}
+
+static NetClientInfo net_spapr_vlan_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = spapr_vlan_can_receive,
+ .receive = spapr_vlan_receive,
+};
+
+static void spapr_vlan_reset(VIOsPAPRDevice *sdev)
+{
+ VIOsPAPRVLANDevice *dev = VIO_SPAPR_VLAN_DEVICE(sdev);
+
+ dev->buf_list = 0;
+ dev->rx_bufs = 0;
+ dev->isopen = 0;
+}
+
+static void spapr_vlan_realize(VIOsPAPRDevice *sdev, Error **errp)
+{
+ VIOsPAPRVLANDevice *dev = VIO_SPAPR_VLAN_DEVICE(sdev);
+
+ qemu_macaddr_default_if_unset(&dev->nicconf.macaddr);
+
+ dev->nic = qemu_new_nic(&net_spapr_vlan_info, &dev->nicconf,
+ object_get_typename(OBJECT(sdev)), sdev->qdev.id, dev);
+ qemu_format_nic_info_str(qemu_get_queue(dev->nic), dev->nicconf.macaddr.a);
+}
+
+static void spapr_vlan_instance_init(Object *obj)
+{
+ VIOsPAPRVLANDevice *dev = VIO_SPAPR_VLAN_DEVICE(obj);
+
+ device_add_bootindex_property(obj, &dev->nicconf.bootindex,
+ "bootindex", "",
+ DEVICE(dev), NULL);
+}
+
+void spapr_vlan_create(VIOsPAPRBus *bus, NICInfo *nd)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->bus, "spapr-vlan");
+
+ qdev_set_nic_properties(dev, nd);
+
+ qdev_init_nofail(dev);
+}
+
+static int spapr_vlan_devnode(VIOsPAPRDevice *dev, void *fdt, int node_off)
+{
+ VIOsPAPRVLANDevice *vdev = VIO_SPAPR_VLAN_DEVICE(dev);
+ uint8_t padded_mac[8] = {0, 0};
+ int ret;
+
+ /* Some old phyp versions give the mac address in an 8-byte
+ * property. The kernel driver has an insane workaround for this;
+ * rather than doing the obvious thing and checking the property
+ * length, it checks whether the first byte has 0b10 in the low
+ * bits. If a correct 6-byte property has a different first byte
+ * the kernel will get the wrong mac address, overrunning its
+ * buffer in the process (read only, thank goodness).
+ *
+ * Here we workaround the kernel workaround by always supplying an
+ * 8-byte property, with the mac address in the last six bytes */
+ memcpy(&padded_mac[2], &vdev->nicconf.macaddr, ETH_ALEN);
+ ret = fdt_setprop(fdt, node_off, "local-mac-address",
+ padded_mac, sizeof(padded_mac));
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = fdt_setprop_cell(fdt, node_off, "ibm,mac-address-filters", 0);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static int check_bd(VIOsPAPRVLANDevice *dev, vlan_bd_t bd,
+ target_ulong alignment)
+{
+ if ((VLAN_BD_ADDR(bd) % alignment)
+ || (VLAN_BD_LEN(bd) % alignment)) {
+ return -1;
+ }
+
+ if (!spapr_vio_dma_valid(&dev->sdev, VLAN_BD_ADDR(bd),
+ VLAN_BD_LEN(bd), DMA_DIRECTION_FROM_DEVICE)
+ || !spapr_vio_dma_valid(&dev->sdev, VLAN_BD_ADDR(bd),
+ VLAN_BD_LEN(bd), DMA_DIRECTION_TO_DEVICE)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static target_ulong h_register_logical_lan(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ target_ulong opcode,
+ target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong buf_list = args[1];
+ target_ulong rec_queue = args[2];
+ target_ulong filter_list = args[3];
+ VIOsPAPRDevice *sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ VIOsPAPRVLANDevice *dev = VIO_SPAPR_VLAN_DEVICE(sdev);
+ vlan_bd_t filter_list_bd;
+
+ if (!dev) {
+ return H_PARAMETER;
+ }
+
+ if (dev->isopen) {
+ hcall_dprintf("H_REGISTER_LOGICAL_LAN called twice without "
+ "H_FREE_LOGICAL_LAN\n");
+ return H_RESOURCE;
+ }
+
+ if (check_bd(dev, VLAN_VALID_BD(buf_list, SPAPR_TCE_PAGE_SIZE),
+ SPAPR_TCE_PAGE_SIZE) < 0) {
+ hcall_dprintf("Bad buf_list 0x" TARGET_FMT_lx "\n", buf_list);
+ return H_PARAMETER;
+ }
+
+ filter_list_bd = VLAN_VALID_BD(filter_list, SPAPR_TCE_PAGE_SIZE);
+ if (check_bd(dev, filter_list_bd, SPAPR_TCE_PAGE_SIZE) < 0) {
+ hcall_dprintf("Bad filter_list 0x" TARGET_FMT_lx "\n", filter_list);
+ return H_PARAMETER;
+ }
+
+ if (!(rec_queue & VLAN_BD_VALID)
+ || (check_bd(dev, rec_queue, VLAN_RQ_ALIGNMENT) < 0)) {
+ hcall_dprintf("Bad receive queue\n");
+ return H_PARAMETER;
+ }
+
+ dev->buf_list = buf_list;
+ sdev->signal_state = 0;
+
+ rec_queue &= ~VLAN_BD_TOGGLE;
+
+ /* Initialize the buffer list */
+ vio_stq(sdev, buf_list, rec_queue);
+ vio_stq(sdev, buf_list + 8, filter_list_bd);
+ spapr_vio_dma_set(sdev, buf_list + VLAN_RX_BDS_OFF, 0,
+ SPAPR_TCE_PAGE_SIZE - VLAN_RX_BDS_OFF);
+ dev->add_buf_ptr = VLAN_RX_BDS_OFF - 8;
+ dev->use_buf_ptr = VLAN_RX_BDS_OFF - 8;
+ dev->rx_bufs = 0;
+ dev->rxq_ptr = 0;
+
+ /* Initialize the receive queue */
+ spapr_vio_dma_set(sdev, VLAN_BD_ADDR(rec_queue), 0, VLAN_BD_LEN(rec_queue));
+
+ dev->isopen = 1;
+ qemu_flush_queued_packets(qemu_get_queue(dev->nic));
+
+ return H_SUCCESS;
+}
+
+
+static target_ulong h_free_logical_lan(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ VIOsPAPRDevice *sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ VIOsPAPRVLANDevice *dev = VIO_SPAPR_VLAN_DEVICE(sdev);
+
+ if (!dev) {
+ return H_PARAMETER;
+ }
+
+ if (!dev->isopen) {
+ hcall_dprintf("H_FREE_LOGICAL_LAN called without "
+ "H_REGISTER_LOGICAL_LAN\n");
+ return H_RESOURCE;
+ }
+
+ spapr_vlan_reset(sdev);
+ return H_SUCCESS;
+}
+
+static target_ulong h_add_logical_lan_buffer(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ target_ulong opcode,
+ target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong buf = args[1];
+ VIOsPAPRDevice *sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ VIOsPAPRVLANDevice *dev = VIO_SPAPR_VLAN_DEVICE(sdev);
+ vlan_bd_t bd;
+
+ DPRINTF("H_ADD_LOGICAL_LAN_BUFFER(0x" TARGET_FMT_lx
+ ", 0x" TARGET_FMT_lx ")\n", reg, buf);
+
+ if (!sdev) {
+ hcall_dprintf("Bad device\n");
+ return H_PARAMETER;
+ }
+
+ if ((check_bd(dev, buf, 4) < 0)
+ || (VLAN_BD_LEN(buf) < 16)) {
+ hcall_dprintf("Bad buffer enqueued\n");
+ return H_PARAMETER;
+ }
+
+ if (!dev->isopen || dev->rx_bufs >= VLAN_MAX_BUFS) {
+ return H_RESOURCE;
+ }
+
+ do {
+ dev->add_buf_ptr += 8;
+ if (dev->add_buf_ptr >= (VLAN_RX_BDS_LEN + VLAN_RX_BDS_OFF)) {
+ dev->add_buf_ptr = VLAN_RX_BDS_OFF;
+ }
+
+ bd = vio_ldq(sdev, dev->buf_list + dev->add_buf_ptr);
+ } while (bd & VLAN_BD_VALID);
+
+ vio_stq(sdev, dev->buf_list + dev->add_buf_ptr, buf);
+
+ dev->rx_bufs++;
+
+ qemu_flush_queued_packets(qemu_get_queue(dev->nic));
+
+ DPRINTF("h_add_logical_lan_buffer(): Added buf ptr=%d rx_bufs=%d"
+ " bd=0x%016llx\n", dev->add_buf_ptr, dev->rx_bufs,
+ (unsigned long long)buf);
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_send_logical_lan(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong *bufs = args + 1;
+ target_ulong continue_token = args[7];
+ VIOsPAPRDevice *sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ VIOsPAPRVLANDevice *dev = VIO_SPAPR_VLAN_DEVICE(sdev);
+ unsigned total_len;
+ uint8_t *lbuf, *p;
+ int i, nbufs;
+ int ret;
+
+ DPRINTF("H_SEND_LOGICAL_LAN(0x" TARGET_FMT_lx ", <bufs>, 0x"
+ TARGET_FMT_lx ")\n", reg, continue_token);
+
+ if (!sdev) {
+ return H_PARAMETER;
+ }
+
+ DPRINTF("rxbufs = %d\n", dev->rx_bufs);
+
+ if (!dev->isopen) {
+ return H_DROPPED;
+ }
+
+ if (continue_token) {
+ return H_HARDWARE; /* FIXME actually handle this */
+ }
+
+ total_len = 0;
+ for (i = 0; i < 6; i++) {
+ DPRINTF(" buf desc: 0x" TARGET_FMT_lx "\n", bufs[i]);
+ if (!(bufs[i] & VLAN_BD_VALID)) {
+ break;
+ }
+ total_len += VLAN_BD_LEN(bufs[i]);
+ }
+
+ nbufs = i;
+ DPRINTF("h_send_logical_lan() %d buffers, total length 0x%x\n",
+ nbufs, total_len);
+
+ if (total_len == 0) {
+ return H_SUCCESS;
+ }
+
+ if (total_len > MAX_PACKET_SIZE) {
+ /* Don't let the guest force too large an allocation */
+ return H_RESOURCE;
+ }
+
+ lbuf = alloca(total_len);
+ p = lbuf;
+ for (i = 0; i < nbufs; i++) {
+ ret = spapr_vio_dma_read(sdev, VLAN_BD_ADDR(bufs[i]),
+ p, VLAN_BD_LEN(bufs[i]));
+ if (ret < 0) {
+ return ret;
+ }
+
+ p += VLAN_BD_LEN(bufs[i]);
+ }
+
+ qemu_send_packet(qemu_get_queue(dev->nic), lbuf, total_len);
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_multicast_ctrl(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+
+ if (!dev) {
+ return H_PARAMETER;
+ }
+
+ return H_SUCCESS;
+}
+
+static Property spapr_vlan_properties[] = {
+ DEFINE_SPAPR_PROPERTIES(VIOsPAPRVLANDevice, sdev),
+ DEFINE_NIC_PROPERTIES(VIOsPAPRVLANDevice, nicconf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_spapr_llan = {
+ .name = "spapr_llan",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_SPAPR_VIO(sdev, VIOsPAPRVLANDevice),
+ /* LLAN state */
+ VMSTATE_BOOL(isopen, VIOsPAPRVLANDevice),
+ VMSTATE_UINTTL(buf_list, VIOsPAPRVLANDevice),
+ VMSTATE_UINT32(add_buf_ptr, VIOsPAPRVLANDevice),
+ VMSTATE_UINT32(use_buf_ptr, VIOsPAPRVLANDevice),
+ VMSTATE_UINT32(rx_bufs, VIOsPAPRVLANDevice),
+ VMSTATE_UINTTL(rxq_ptr, VIOsPAPRVLANDevice),
+
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void spapr_vlan_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VIOsPAPRDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
+
+ k->realize = spapr_vlan_realize;
+ k->reset = spapr_vlan_reset;
+ k->devnode = spapr_vlan_devnode;
+ k->dt_name = "l-lan";
+ k->dt_type = "network";
+ k->dt_compatible = "IBM,l-lan";
+ k->signal_mask = 0x1;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->props = spapr_vlan_properties;
+ k->rtce_window_size = 0x10000000;
+ dc->vmsd = &vmstate_spapr_llan;
+}
+
+static const TypeInfo spapr_vlan_info = {
+ .name = TYPE_VIO_SPAPR_VLAN_DEVICE,
+ .parent = TYPE_VIO_SPAPR_DEVICE,
+ .instance_size = sizeof(VIOsPAPRVLANDevice),
+ .class_init = spapr_vlan_class_init,
+ .instance_init = spapr_vlan_instance_init,
+};
+
+static void spapr_vlan_register_types(void)
+{
+ spapr_register_hypercall(H_REGISTER_LOGICAL_LAN, h_register_logical_lan);
+ spapr_register_hypercall(H_FREE_LOGICAL_LAN, h_free_logical_lan);
+ spapr_register_hypercall(H_SEND_LOGICAL_LAN, h_send_logical_lan);
+ spapr_register_hypercall(H_ADD_LOGICAL_LAN_BUFFER,
+ h_add_logical_lan_buffer);
+ spapr_register_hypercall(H_MULTICAST_CTRL, h_multicast_ctrl);
+ type_register_static(&spapr_vlan_info);
+}
+
+type_init(spapr_vlan_register_types)
diff --git a/hw/net/stellaris_enet.c b/hw/net/stellaris_enet.c
new file mode 100644
index 00000000..21a47735
--- /dev/null
+++ b/hw/net/stellaris_enet.c
@@ -0,0 +1,503 @@
+/*
+ * Luminary Micro Stellaris Ethernet Controller
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include <zlib.h>
+
+//#define DEBUG_STELLARIS_ENET 1
+
+#ifdef DEBUG_STELLARIS_ENET
+#define DPRINTF(fmt, ...) \
+do { printf("stellaris_enet: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "stellaris_enet: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "stellaris_enet: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+#define SE_INT_RX 0x01
+#define SE_INT_TXER 0x02
+#define SE_INT_TXEMP 0x04
+#define SE_INT_FOV 0x08
+#define SE_INT_RXER 0x10
+#define SE_INT_MD 0x20
+#define SE_INT_PHY 0x40
+
+#define SE_RCTL_RXEN 0x01
+#define SE_RCTL_AMUL 0x02
+#define SE_RCTL_PRMS 0x04
+#define SE_RCTL_BADCRC 0x08
+#define SE_RCTL_RSTFIFO 0x10
+
+#define SE_TCTL_TXEN 0x01
+#define SE_TCTL_PADEN 0x02
+#define SE_TCTL_CRC 0x04
+#define SE_TCTL_DUPLEX 0x08
+
+#define TYPE_STELLARIS_ENET "stellaris_enet"
+#define STELLARIS_ENET(obj) \
+ OBJECT_CHECK(stellaris_enet_state, (obj), TYPE_STELLARIS_ENET)
+
+typedef struct {
+ uint8_t data[2048];
+ uint32_t len;
+} StellarisEnetRxFrame;
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ uint32_t ris;
+ uint32_t im;
+ uint32_t rctl;
+ uint32_t tctl;
+ uint32_t thr;
+ uint32_t mctl;
+ uint32_t mdv;
+ uint32_t mtxd;
+ uint32_t mrxd;
+ uint32_t np;
+ uint32_t tx_fifo_len;
+ uint8_t tx_fifo[2048];
+ /* Real hardware has a 2k fifo, which works out to be at most 31 packets.
+ We implement a full 31 packet fifo. */
+ StellarisEnetRxFrame rx[31];
+ uint32_t rx_fifo_offset;
+ uint32_t next_packet;
+ NICState *nic;
+ NICConf conf;
+ qemu_irq irq;
+ MemoryRegion mmio;
+} stellaris_enet_state;
+
+static const VMStateDescription vmstate_rx_frame = {
+ .name = "stellaris_enet/rx_frame",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(data, StellarisEnetRxFrame, 2048),
+ VMSTATE_UINT32(len, StellarisEnetRxFrame),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int stellaris_enet_post_load(void *opaque, int version_id)
+{
+ stellaris_enet_state *s = opaque;
+ int i;
+
+ /* Sanitize inbound state. Note that next_packet is an index but
+ * np is a size; hence their valid upper bounds differ.
+ */
+ if (s->next_packet >= ARRAY_SIZE(s->rx)) {
+ return -1;
+ }
+
+ if (s->np > ARRAY_SIZE(s->rx)) {
+ return -1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(s->rx); i++) {
+ if (s->rx[i].len > ARRAY_SIZE(s->rx[i].data)) {
+ return -1;
+ }
+ }
+
+ if (s->rx_fifo_offset > ARRAY_SIZE(s->rx[0].data) - 4) {
+ return -1;
+ }
+
+ if (s->tx_fifo_len > ARRAY_SIZE(s->tx_fifo)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_stellaris_enet = {
+ .name = "stellaris_enet",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = stellaris_enet_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ris, stellaris_enet_state),
+ VMSTATE_UINT32(im, stellaris_enet_state),
+ VMSTATE_UINT32(rctl, stellaris_enet_state),
+ VMSTATE_UINT32(tctl, stellaris_enet_state),
+ VMSTATE_UINT32(thr, stellaris_enet_state),
+ VMSTATE_UINT32(mctl, stellaris_enet_state),
+ VMSTATE_UINT32(mdv, stellaris_enet_state),
+ VMSTATE_UINT32(mtxd, stellaris_enet_state),
+ VMSTATE_UINT32(mrxd, stellaris_enet_state),
+ VMSTATE_UINT32(np, stellaris_enet_state),
+ VMSTATE_UINT32(tx_fifo_len, stellaris_enet_state),
+ VMSTATE_UINT8_ARRAY(tx_fifo, stellaris_enet_state, 2048),
+ VMSTATE_STRUCT_ARRAY(rx, stellaris_enet_state, 31, 1,
+ vmstate_rx_frame, StellarisEnetRxFrame),
+ VMSTATE_UINT32(rx_fifo_offset, stellaris_enet_state),
+ VMSTATE_UINT32(next_packet, stellaris_enet_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void stellaris_enet_update(stellaris_enet_state *s)
+{
+ qemu_set_irq(s->irq, (s->ris & s->im) != 0);
+}
+
+/* Return the data length of the packet currently being assembled
+ * in the TX fifo.
+ */
+static inline int stellaris_txpacket_datalen(stellaris_enet_state *s)
+{
+ return s->tx_fifo[0] | (s->tx_fifo[1] << 8);
+}
+
+/* Return true if the packet currently in the TX FIFO is complete,
+* ie the FIFO holds enough bytes for the data length, ethernet header,
+* payload and optionally CRC.
+*/
+static inline bool stellaris_txpacket_complete(stellaris_enet_state *s)
+{
+ int framelen = stellaris_txpacket_datalen(s);
+ framelen += 16;
+ if (!(s->tctl & SE_TCTL_CRC)) {
+ framelen += 4;
+ }
+ /* Cover the corner case of a 2032 byte payload with auto-CRC disabled:
+ * this requires more bytes than will fit in the FIFO. It's not totally
+ * clear how the h/w handles this, but if using threshold-based TX
+ * it will definitely try to transmit something.
+ */
+ framelen = MIN(framelen, ARRAY_SIZE(s->tx_fifo));
+ return s->tx_fifo_len >= framelen;
+}
+
+/* Return true if the TX FIFO threshold is enabled and the FIFO
+ * has filled enough to reach it.
+ */
+static inline bool stellaris_tx_thr_reached(stellaris_enet_state *s)
+{
+ return (s->thr < 0x3f &&
+ (s->tx_fifo_len >= 4 * (s->thr * 8 + 1)));
+}
+
+/* Send the packet currently in the TX FIFO */
+static void stellaris_enet_send(stellaris_enet_state *s)
+{
+ int framelen = stellaris_txpacket_datalen(s);
+
+ /* Ethernet header is in the FIFO but not in the datacount.
+ * We don't implement explicit CRC, so just ignore any
+ * CRC value in the FIFO.
+ */
+ framelen += 14;
+ if ((s->tctl & SE_TCTL_PADEN) && framelen < 60) {
+ memset(&s->tx_fifo[framelen + 2], 0, 60 - framelen);
+ framelen = 60;
+ }
+ /* This MIN will have no effect unless the FIFO data is corrupt
+ * (eg bad data from an incoming migration); otherwise the check
+ * on the datalen at the start of writing the data into the FIFO
+ * will have caught this. Silently write a corrupt half-packet,
+ * which is what the hardware does in FIFO underrun situations.
+ */
+ framelen = MIN(framelen, ARRAY_SIZE(s->tx_fifo) - 2);
+ qemu_send_packet(qemu_get_queue(s->nic), s->tx_fifo + 2, framelen);
+ s->tx_fifo_len = 0;
+ s->ris |= SE_INT_TXEMP;
+ stellaris_enet_update(s);
+ DPRINTF("Done TX\n");
+}
+
+/* TODO: Implement MAC address filtering. */
+static ssize_t stellaris_enet_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ stellaris_enet_state *s = qemu_get_nic_opaque(nc);
+ int n;
+ uint8_t *p;
+ uint32_t crc;
+
+ if ((s->rctl & SE_RCTL_RXEN) == 0)
+ return -1;
+ if (s->np >= 31) {
+ return 0;
+ }
+
+ DPRINTF("Received packet len=%zu\n", size);
+ n = s->next_packet + s->np;
+ if (n >= 31)
+ n -= 31;
+ s->np++;
+
+ s->rx[n].len = size + 6;
+ p = s->rx[n].data;
+ *(p++) = (size + 6);
+ *(p++) = (size + 6) >> 8;
+ memcpy (p, buf, size);
+ p += size;
+ crc = crc32(~0, buf, size);
+ *(p++) = crc;
+ *(p++) = crc >> 8;
+ *(p++) = crc >> 16;
+ *(p++) = crc >> 24;
+ /* Clear the remaining bytes in the last word. */
+ if ((size & 3) != 2) {
+ memset(p, 0, (6 - size) & 3);
+ }
+
+ s->ris |= SE_INT_RX;
+ stellaris_enet_update(s);
+
+ return size;
+}
+
+static int stellaris_enet_can_receive(stellaris_enet_state *s)
+{
+ return (s->np < 31);
+}
+
+static uint64_t stellaris_enet_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ stellaris_enet_state *s = (stellaris_enet_state *)opaque;
+ uint32_t val;
+
+ switch (offset) {
+ case 0x00: /* RIS */
+ DPRINTF("IRQ status %02x\n", s->ris);
+ return s->ris;
+ case 0x04: /* IM */
+ return s->im;
+ case 0x08: /* RCTL */
+ return s->rctl;
+ case 0x0c: /* TCTL */
+ return s->tctl;
+ case 0x10: /* DATA */
+ {
+ uint8_t *rx_fifo;
+
+ if (s->np == 0) {
+ BADF("RX underflow\n");
+ return 0;
+ }
+
+ rx_fifo = s->rx[s->next_packet].data + s->rx_fifo_offset;
+
+ val = rx_fifo[0] | (rx_fifo[1] << 8) | (rx_fifo[2] << 16)
+ | (rx_fifo[3] << 24);
+ s->rx_fifo_offset += 4;
+ if (s->rx_fifo_offset >= s->rx[s->next_packet].len) {
+ s->rx_fifo_offset = 0;
+ s->next_packet++;
+ if (s->next_packet >= 31)
+ s->next_packet = 0;
+ s->np--;
+ DPRINTF("RX done np=%d\n", s->np);
+ if (!s->np && stellaris_enet_can_receive(s)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ }
+ return val;
+ }
+ case 0x14: /* IA0 */
+ return s->conf.macaddr.a[0] | (s->conf.macaddr.a[1] << 8)
+ | (s->conf.macaddr.a[2] << 16)
+ | ((uint32_t)s->conf.macaddr.a[3] << 24);
+ case 0x18: /* IA1 */
+ return s->conf.macaddr.a[4] | (s->conf.macaddr.a[5] << 8);
+ case 0x1c: /* THR */
+ return s->thr;
+ case 0x20: /* MCTL */
+ return s->mctl;
+ case 0x24: /* MDV */
+ return s->mdv;
+ case 0x28: /* MADD */
+ return 0;
+ case 0x2c: /* MTXD */
+ return s->mtxd;
+ case 0x30: /* MRXD */
+ return s->mrxd;
+ case 0x34: /* NP */
+ return s->np;
+ case 0x38: /* TR */
+ return 0;
+ case 0x3c: /* Undocuented: Timestamp? */
+ return 0;
+ default:
+ hw_error("stellaris_enet_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void stellaris_enet_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ stellaris_enet_state *s = (stellaris_enet_state *)opaque;
+
+ switch (offset) {
+ case 0x00: /* IACK */
+ s->ris &= ~value;
+ DPRINTF("IRQ ack %02" PRIx64 "/%02x\n", value, s->ris);
+ stellaris_enet_update(s);
+ /* Clearing TXER also resets the TX fifo. */
+ if (value & SE_INT_TXER) {
+ s->tx_fifo_len = 0;
+ }
+ break;
+ case 0x04: /* IM */
+ DPRINTF("IRQ mask %02" PRIx64 "/%02x\n", value, s->ris);
+ s->im = value;
+ stellaris_enet_update(s);
+ break;
+ case 0x08: /* RCTL */
+ s->rctl = value;
+ if (value & SE_RCTL_RSTFIFO) {
+ s->np = 0;
+ s->rx_fifo_offset = 0;
+ stellaris_enet_update(s);
+ }
+ break;
+ case 0x0c: /* TCTL */
+ s->tctl = value;
+ break;
+ case 0x10: /* DATA */
+ if (s->tx_fifo_len == 0) {
+ /* The first word is special, it contains the data length */
+ int framelen = value & 0xffff;
+ if (framelen > 2032) {
+ DPRINTF("TX frame too long (%d)\n", framelen);
+ s->ris |= SE_INT_TXER;
+ stellaris_enet_update(s);
+ break;
+ }
+ }
+
+ if (s->tx_fifo_len + 4 <= ARRAY_SIZE(s->tx_fifo)) {
+ s->tx_fifo[s->tx_fifo_len++] = value;
+ s->tx_fifo[s->tx_fifo_len++] = value >> 8;
+ s->tx_fifo[s->tx_fifo_len++] = value >> 16;
+ s->tx_fifo[s->tx_fifo_len++] = value >> 24;
+ }
+
+ if (stellaris_tx_thr_reached(s) && stellaris_txpacket_complete(s)) {
+ stellaris_enet_send(s);
+ }
+ break;
+ case 0x14: /* IA0 */
+ s->conf.macaddr.a[0] = value;
+ s->conf.macaddr.a[1] = value >> 8;
+ s->conf.macaddr.a[2] = value >> 16;
+ s->conf.macaddr.a[3] = value >> 24;
+ break;
+ case 0x18: /* IA1 */
+ s->conf.macaddr.a[4] = value;
+ s->conf.macaddr.a[5] = value >> 8;
+ break;
+ case 0x1c: /* THR */
+ s->thr = value;
+ break;
+ case 0x20: /* MCTL */
+ s->mctl = value;
+ break;
+ case 0x24: /* MDV */
+ s->mdv = value;
+ break;
+ case 0x28: /* MADD */
+ /* ignored. */
+ break;
+ case 0x2c: /* MTXD */
+ s->mtxd = value & 0xff;
+ break;
+ case 0x38: /* TR */
+ if (value & 1) {
+ stellaris_enet_send(s);
+ }
+ break;
+ case 0x30: /* MRXD */
+ case 0x34: /* NP */
+ /* Ignored. */
+ case 0x3c: /* Undocuented: Timestamp? */
+ /* Ignored. */
+ break;
+ default:
+ hw_error("stellaris_enet_write: Bad offset %x\n", (int)offset);
+ }
+}
+
+static const MemoryRegionOps stellaris_enet_ops = {
+ .read = stellaris_enet_read,
+ .write = stellaris_enet_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void stellaris_enet_reset(stellaris_enet_state *s)
+{
+ s->mdv = 0x80;
+ s->rctl = SE_RCTL_BADCRC;
+ s->im = SE_INT_PHY | SE_INT_MD | SE_INT_RXER | SE_INT_FOV | SE_INT_TXEMP
+ | SE_INT_TXER | SE_INT_RX;
+ s->thr = 0x3f;
+ s->tx_fifo_len = 0;
+}
+
+static NetClientInfo net_stellaris_enet_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = stellaris_enet_receive,
+};
+
+static int stellaris_enet_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ stellaris_enet_state *s = STELLARIS_ENET(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &stellaris_enet_ops, s,
+ "stellaris_enet", 0x1000);
+ sysbus_init_mmio(sbd, &s->mmio);
+ sysbus_init_irq(sbd, &s->irq);
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+
+ s->nic = qemu_new_nic(&net_stellaris_enet_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ stellaris_enet_reset(s);
+ return 0;
+}
+
+static Property stellaris_enet_properties[] = {
+ DEFINE_NIC_PROPERTIES(stellaris_enet_state, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void stellaris_enet_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = stellaris_enet_init;
+ dc->props = stellaris_enet_properties;
+ dc->vmsd = &vmstate_stellaris_enet;
+}
+
+static const TypeInfo stellaris_enet_info = {
+ .name = TYPE_STELLARIS_ENET,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(stellaris_enet_state),
+ .class_init = stellaris_enet_class_init,
+};
+
+static void stellaris_enet_register_types(void)
+{
+ type_register_static(&stellaris_enet_info);
+}
+
+type_init(stellaris_enet_register_types)
diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c
new file mode 100644
index 00000000..1d76b94c
--- /dev/null
+++ b/hw/net/vhost_net.c
@@ -0,0 +1,459 @@
+/*
+ * vhost-net support
+ *
+ * Copyright Red Hat, Inc. 2010
+ *
+ * Authors:
+ * Michael S. Tsirkin <mst@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "net/net.h"
+#include "net/tap.h"
+#include "net/vhost-user.h"
+
+#include "hw/virtio/virtio-net.h"
+#include "net/vhost_net.h"
+#include "qemu/error-report.h"
+
+#include "config.h"
+
+#ifdef CONFIG_VHOST_NET
+#include <linux/vhost.h>
+#include <sys/socket.h>
+#include <linux/kvm.h>
+#include <fcntl.h>
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <stdio.h>
+
+#include "standard-headers/linux/virtio_ring.h"
+#include "hw/virtio/vhost.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+
+struct vhost_net {
+ struct vhost_dev dev;
+ struct vhost_virtqueue vqs[2];
+ int backend;
+ NetClientState *nc;
+};
+
+/* Features supported by host kernel. */
+static const int kernel_feature_bits[] = {
+ VIRTIO_F_NOTIFY_ON_EMPTY,
+ VIRTIO_RING_F_INDIRECT_DESC,
+ VIRTIO_RING_F_EVENT_IDX,
+ VIRTIO_NET_F_MRG_RXBUF,
+ VIRTIO_F_VERSION_1,
+ VHOST_INVALID_FEATURE_BIT
+};
+
+/* Features supported by others. */
+static const int user_feature_bits[] = {
+ VIRTIO_F_NOTIFY_ON_EMPTY,
+ VIRTIO_RING_F_INDIRECT_DESC,
+ VIRTIO_RING_F_EVENT_IDX,
+
+ VIRTIO_F_ANY_LAYOUT,
+ VIRTIO_F_VERSION_1,
+ VIRTIO_NET_F_CSUM,
+ VIRTIO_NET_F_GUEST_CSUM,
+ VIRTIO_NET_F_GSO,
+ VIRTIO_NET_F_GUEST_TSO4,
+ VIRTIO_NET_F_GUEST_TSO6,
+ VIRTIO_NET_F_GUEST_ECN,
+ VIRTIO_NET_F_GUEST_UFO,
+ VIRTIO_NET_F_HOST_TSO4,
+ VIRTIO_NET_F_HOST_TSO6,
+ VIRTIO_NET_F_HOST_ECN,
+ VIRTIO_NET_F_HOST_UFO,
+ VIRTIO_NET_F_MRG_RXBUF,
+ VIRTIO_NET_F_STATUS,
+ VIRTIO_NET_F_CTRL_VQ,
+ VIRTIO_NET_F_CTRL_RX,
+ VIRTIO_NET_F_CTRL_VLAN,
+ VIRTIO_NET_F_CTRL_RX_EXTRA,
+ VIRTIO_NET_F_CTRL_MAC_ADDR,
+ VIRTIO_NET_F_CTRL_GUEST_OFFLOADS,
+
+ VIRTIO_NET_F_MQ,
+
+ VHOST_INVALID_FEATURE_BIT
+};
+
+static const int *vhost_net_get_feature_bits(struct vhost_net *net)
+{
+ const int *feature_bits = 0;
+
+ switch (net->nc->info->type) {
+ case NET_CLIENT_OPTIONS_KIND_TAP:
+ feature_bits = kernel_feature_bits;
+ break;
+ case NET_CLIENT_OPTIONS_KIND_VHOST_USER:
+ feature_bits = user_feature_bits;
+ break;
+ default:
+ error_report("Feature bits not defined for this type: %d",
+ net->nc->info->type);
+ break;
+ }
+
+ return feature_bits;
+}
+
+uint64_t vhost_net_get_features(struct vhost_net *net, uint64_t features)
+{
+ return vhost_get_features(&net->dev, vhost_net_get_feature_bits(net),
+ features);
+}
+
+void vhost_net_ack_features(struct vhost_net *net, uint64_t features)
+{
+ net->dev.acked_features = net->dev.backend_features;
+ vhost_ack_features(&net->dev, vhost_net_get_feature_bits(net), features);
+}
+
+static int vhost_net_get_fd(NetClientState *backend)
+{
+ switch (backend->info->type) {
+ case NET_CLIENT_OPTIONS_KIND_TAP:
+ return tap_get_fd(backend);
+ default:
+ fprintf(stderr, "vhost-net requires tap backend\n");
+ return -EBADFD;
+ }
+}
+
+struct vhost_net *vhost_net_init(VhostNetOptions *options)
+{
+ int r;
+ bool backend_kernel = options->backend_type == VHOST_BACKEND_TYPE_KERNEL;
+ struct vhost_net *net = g_malloc(sizeof *net);
+
+ if (!options->net_backend) {
+ fprintf(stderr, "vhost-net requires net backend to be setup\n");
+ goto fail;
+ }
+
+ if (backend_kernel) {
+ r = vhost_net_get_fd(options->net_backend);
+ if (r < 0) {
+ goto fail;
+ }
+ net->dev.backend_features = qemu_has_vnet_hdr(options->net_backend)
+ ? 0 : (1ULL << VHOST_NET_F_VIRTIO_NET_HDR);
+ net->backend = r;
+ } else {
+ net->dev.backend_features = 0;
+ net->backend = -1;
+ }
+ net->nc = options->net_backend;
+
+ net->dev.nvqs = 2;
+ net->dev.vqs = net->vqs;
+
+ r = vhost_dev_init(&net->dev, options->opaque,
+ options->backend_type);
+ if (r < 0) {
+ goto fail;
+ }
+ if (backend_kernel) {
+ if (!qemu_has_vnet_hdr_len(options->net_backend,
+ sizeof(struct virtio_net_hdr_mrg_rxbuf))) {
+ net->dev.features &= ~(1ULL << VIRTIO_NET_F_MRG_RXBUF);
+ }
+ if (~net->dev.features & net->dev.backend_features) {
+ fprintf(stderr, "vhost lacks feature mask %" PRIu64
+ " for backend\n",
+ (uint64_t)(~net->dev.features & net->dev.backend_features));
+ vhost_dev_cleanup(&net->dev);
+ goto fail;
+ }
+ }
+ /* Set sane init value. Override when guest acks. */
+ vhost_net_ack_features(net, 0);
+ return net;
+fail:
+ g_free(net);
+ return NULL;
+}
+
+static void vhost_net_set_vq_index(struct vhost_net *net, int vq_index)
+{
+ net->dev.vq_index = vq_index;
+}
+
+static int vhost_net_set_vnet_endian(VirtIODevice *dev, NetClientState *peer,
+ bool set)
+{
+ int r = 0;
+
+ if (virtio_vdev_has_feature(dev, VIRTIO_F_VERSION_1) ||
+ (virtio_legacy_is_cross_endian(dev) && !virtio_is_big_endian(dev))) {
+ r = qemu_set_vnet_le(peer, set);
+ if (r) {
+ error_report("backend does not support LE vnet headers");
+ }
+ } else if (virtio_legacy_is_cross_endian(dev)) {
+ r = qemu_set_vnet_be(peer, set);
+ if (r) {
+ error_report("backend does not support BE vnet headers");
+ }
+ }
+
+ return r;
+}
+
+static int vhost_net_start_one(struct vhost_net *net,
+ VirtIODevice *dev)
+{
+ struct vhost_vring_file file = { };
+ int r;
+
+ net->dev.nvqs = 2;
+ net->dev.vqs = net->vqs;
+
+ r = vhost_dev_enable_notifiers(&net->dev, dev);
+ if (r < 0) {
+ goto fail_notifiers;
+ }
+
+ r = vhost_dev_start(&net->dev, dev);
+ if (r < 0) {
+ goto fail_start;
+ }
+
+ if (net->nc->info->poll) {
+ net->nc->info->poll(net->nc, false);
+ }
+
+ if (net->nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP) {
+ qemu_set_fd_handler(net->backend, NULL, NULL, NULL);
+ file.fd = net->backend;
+ for (file.index = 0; file.index < net->dev.nvqs; ++file.index) {
+ const VhostOps *vhost_ops = net->dev.vhost_ops;
+ r = vhost_ops->vhost_call(&net->dev, VHOST_NET_SET_BACKEND,
+ &file);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+ }
+ }
+ return 0;
+fail:
+ file.fd = -1;
+ if (net->nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP) {
+ while (file.index-- > 0) {
+ const VhostOps *vhost_ops = net->dev.vhost_ops;
+ int r = vhost_ops->vhost_call(&net->dev, VHOST_NET_SET_BACKEND,
+ &file);
+ assert(r >= 0);
+ }
+ }
+ if (net->nc->info->poll) {
+ net->nc->info->poll(net->nc, true);
+ }
+ vhost_dev_stop(&net->dev, dev);
+fail_start:
+ vhost_dev_disable_notifiers(&net->dev, dev);
+fail_notifiers:
+ return r;
+}
+
+static void vhost_net_stop_one(struct vhost_net *net,
+ VirtIODevice *dev)
+{
+ struct vhost_vring_file file = { .fd = -1 };
+
+ if (net->nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP) {
+ for (file.index = 0; file.index < net->dev.nvqs; ++file.index) {
+ const VhostOps *vhost_ops = net->dev.vhost_ops;
+ int r = vhost_ops->vhost_call(&net->dev, VHOST_NET_SET_BACKEND,
+ &file);
+ assert(r >= 0);
+ }
+ } else if (net->nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER) {
+ for (file.index = 0; file.index < net->dev.nvqs; ++file.index) {
+ const VhostOps *vhost_ops = net->dev.vhost_ops;
+ int r = vhost_ops->vhost_call(&net->dev, VHOST_RESET_OWNER,
+ NULL);
+ assert(r >= 0);
+ }
+ }
+ if (net->nc->info->poll) {
+ net->nc->info->poll(net->nc, true);
+ }
+ vhost_dev_stop(&net->dev, dev);
+ vhost_dev_disable_notifiers(&net->dev, dev);
+}
+
+int vhost_net_start(VirtIODevice *dev, NetClientState *ncs,
+ int total_queues)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(dev)));
+ VirtioBusState *vbus = VIRTIO_BUS(qbus);
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(vbus);
+ int r, e, i;
+
+ if (!k->set_guest_notifiers) {
+ error_report("binding does not support guest notifiers");
+ r = -ENOSYS;
+ goto err;
+ }
+
+ r = vhost_net_set_vnet_endian(dev, ncs[0].peer, true);
+ if (r < 0) {
+ goto err;
+ }
+
+ for (i = 0; i < total_queues; i++) {
+ vhost_net_set_vq_index(get_vhost_net(ncs[i].peer), i * 2);
+ }
+
+ r = k->set_guest_notifiers(qbus->parent, total_queues * 2, true);
+ if (r < 0) {
+ error_report("Error binding guest notifier: %d", -r);
+ goto err_endian;
+ }
+
+ for (i = 0; i < total_queues; i++) {
+ r = vhost_net_start_one(get_vhost_net(ncs[i].peer), dev);
+
+ if (r < 0) {
+ goto err_start;
+ }
+ }
+
+ return 0;
+
+err_start:
+ while (--i >= 0) {
+ vhost_net_stop_one(get_vhost_net(ncs[i].peer), dev);
+ }
+ e = k->set_guest_notifiers(qbus->parent, total_queues * 2, false);
+ if (e < 0) {
+ fprintf(stderr, "vhost guest notifier cleanup failed: %d\n", e);
+ fflush(stderr);
+ }
+err_endian:
+ vhost_net_set_vnet_endian(dev, ncs[0].peer, false);
+err:
+ return r;
+}
+
+void vhost_net_stop(VirtIODevice *dev, NetClientState *ncs,
+ int total_queues)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(dev)));
+ VirtioBusState *vbus = VIRTIO_BUS(qbus);
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(vbus);
+ int i, r;
+
+ for (i = 0; i < total_queues; i++) {
+ vhost_net_stop_one(get_vhost_net(ncs[i].peer), dev);
+ }
+
+ r = k->set_guest_notifiers(qbus->parent, total_queues * 2, false);
+ if (r < 0) {
+ fprintf(stderr, "vhost guest notifier cleanup failed: %d\n", r);
+ fflush(stderr);
+ }
+ assert(r >= 0);
+
+ assert(vhost_net_set_vnet_endian(dev, ncs[0].peer, false) >= 0);
+}
+
+void vhost_net_cleanup(struct vhost_net *net)
+{
+ vhost_dev_cleanup(&net->dev);
+ g_free(net);
+}
+
+bool vhost_net_virtqueue_pending(VHostNetState *net, int idx)
+{
+ return vhost_virtqueue_pending(&net->dev, idx);
+}
+
+void vhost_net_virtqueue_mask(VHostNetState *net, VirtIODevice *dev,
+ int idx, bool mask)
+{
+ vhost_virtqueue_mask(&net->dev, dev, idx, mask);
+}
+
+VHostNetState *get_vhost_net(NetClientState *nc)
+{
+ VHostNetState *vhost_net = 0;
+
+ if (!nc) {
+ return 0;
+ }
+
+ switch (nc->info->type) {
+ case NET_CLIENT_OPTIONS_KIND_TAP:
+ vhost_net = tap_get_vhost_net(nc);
+ break;
+ case NET_CLIENT_OPTIONS_KIND_VHOST_USER:
+ vhost_net = vhost_user_get_vhost_net(nc);
+ break;
+ default:
+ break;
+ }
+
+ return vhost_net;
+}
+#else
+struct vhost_net *vhost_net_init(VhostNetOptions *options)
+{
+ error_report("vhost-net support is not compiled in");
+ return NULL;
+}
+
+int vhost_net_start(VirtIODevice *dev,
+ NetClientState *ncs,
+ int total_queues)
+{
+ return -ENOSYS;
+}
+void vhost_net_stop(VirtIODevice *dev,
+ NetClientState *ncs,
+ int total_queues)
+{
+}
+
+void vhost_net_cleanup(struct vhost_net *net)
+{
+}
+
+uint64_t vhost_net_get_features(struct vhost_net *net, uint64_t features)
+{
+ return features;
+}
+void vhost_net_ack_features(struct vhost_net *net, uint64_t features)
+{
+}
+
+bool vhost_net_virtqueue_pending(VHostNetState *net, int idx)
+{
+ return false;
+}
+
+void vhost_net_virtqueue_mask(VHostNetState *net, VirtIODevice *dev,
+ int idx, bool mask)
+{
+}
+
+VHostNetState *get_vhost_net(NetClientState *nc)
+{
+ return 0;
+}
+#endif
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
new file mode 100644
index 00000000..4aa93fb9
--- /dev/null
+++ b/hw/net/virtio-net.c
@@ -0,0 +1,1864 @@
+/*
+ * Virtio Network Device
+ *
+ * Copyright IBM, Corp. 2007
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/iov.h"
+#include "hw/virtio/virtio.h"
+#include "net/net.h"
+#include "net/checksum.h"
+#include "net/tap.h"
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "hw/virtio/virtio-net.h"
+#include "net/vhost_net.h"
+#include "hw/virtio/virtio-bus.h"
+#include "qapi/qmp/qjson.h"
+#include "qapi-event.h"
+#include "hw/virtio/virtio-access.h"
+
+#define VIRTIO_NET_VM_VERSION 11
+
+#define MAC_TABLE_ENTRIES 64
+#define MAX_VLAN (1 << 12) /* Per 802.1Q definition */
+
+/*
+ * Calculate the number of bytes up to and including the given 'field' of
+ * 'container'.
+ */
+#define endof(container, field) \
+ (offsetof(container, field) + sizeof(((container *)0)->field))
+
+typedef struct VirtIOFeature {
+ uint32_t flags;
+ size_t end;
+} VirtIOFeature;
+
+static VirtIOFeature feature_sizes[] = {
+ {.flags = 1 << VIRTIO_NET_F_MAC,
+ .end = endof(struct virtio_net_config, mac)},
+ {.flags = 1 << VIRTIO_NET_F_STATUS,
+ .end = endof(struct virtio_net_config, status)},
+ {.flags = 1 << VIRTIO_NET_F_MQ,
+ .end = endof(struct virtio_net_config, max_virtqueue_pairs)},
+ {}
+};
+
+static VirtIONetQueue *virtio_net_get_subqueue(NetClientState *nc)
+{
+ VirtIONet *n = qemu_get_nic_opaque(nc);
+
+ return &n->vqs[nc->queue_index];
+}
+
+static int vq2q(int queue_index)
+{
+ return queue_index / 2;
+}
+
+/* TODO
+ * - we could suppress RX interrupt if we were so inclined.
+ */
+
+static void virtio_net_get_config(VirtIODevice *vdev, uint8_t *config)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ struct virtio_net_config netcfg;
+
+ virtio_stw_p(vdev, &netcfg.status, n->status);
+ virtio_stw_p(vdev, &netcfg.max_virtqueue_pairs, n->max_queues);
+ memcpy(netcfg.mac, n->mac, ETH_ALEN);
+ memcpy(config, &netcfg, n->config_size);
+}
+
+static void virtio_net_set_config(VirtIODevice *vdev, const uint8_t *config)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ struct virtio_net_config netcfg = {};
+
+ memcpy(&netcfg, config, n->config_size);
+
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_NET_F_CTRL_MAC_ADDR) &&
+ !virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1) &&
+ memcmp(netcfg.mac, n->mac, ETH_ALEN)) {
+ memcpy(n->mac, netcfg.mac, ETH_ALEN);
+ qemu_format_nic_info_str(qemu_get_queue(n->nic), n->mac);
+ }
+}
+
+static bool virtio_net_started(VirtIONet *n, uint8_t status)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ return (status & VIRTIO_CONFIG_S_DRIVER_OK) &&
+ (n->status & VIRTIO_NET_S_LINK_UP) && vdev->vm_running;
+}
+
+static void virtio_net_announce_timer(void *opaque)
+{
+ VirtIONet *n = opaque;
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+
+ n->announce_counter--;
+ n->status |= VIRTIO_NET_S_ANNOUNCE;
+ virtio_notify_config(vdev);
+}
+
+static void virtio_net_vhost_status(VirtIONet *n, uint8_t status)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ NetClientState *nc = qemu_get_queue(n->nic);
+ int queues = n->multiqueue ? n->max_queues : 1;
+
+ if (!get_vhost_net(nc->peer)) {
+ return;
+ }
+
+ if ((virtio_net_started(n, status) && !nc->peer->link_down) ==
+ !!n->vhost_started) {
+ return;
+ }
+ if (!n->vhost_started) {
+ int r, i;
+
+ /* Any packets outstanding? Purge them to avoid touching rings
+ * when vhost is running.
+ */
+ for (i = 0; i < queues; i++) {
+ NetClientState *qnc = qemu_get_subqueue(n->nic, i);
+
+ /* Purge both directions: TX and RX. */
+ qemu_net_queue_purge(qnc->peer->incoming_queue, qnc);
+ qemu_net_queue_purge(qnc->incoming_queue, qnc->peer);
+ }
+
+ n->vhost_started = 1;
+ r = vhost_net_start(vdev, n->nic->ncs, queues);
+ if (r < 0) {
+ error_report("unable to start vhost net: %d: "
+ "falling back on userspace virtio", -r);
+ n->vhost_started = 0;
+ }
+ } else {
+ vhost_net_stop(vdev, n->nic->ncs, queues);
+ n->vhost_started = 0;
+ }
+}
+
+static void virtio_net_set_status(struct VirtIODevice *vdev, uint8_t status)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ VirtIONetQueue *q;
+ int i;
+ uint8_t queue_status;
+
+ virtio_net_vhost_status(n, status);
+
+ for (i = 0; i < n->max_queues; i++) {
+ NetClientState *ncs = qemu_get_subqueue(n->nic, i);
+ bool queue_started;
+ q = &n->vqs[i];
+
+ if ((!n->multiqueue && i != 0) || i >= n->curr_queues) {
+ queue_status = 0;
+ } else {
+ queue_status = status;
+ }
+ queue_started =
+ virtio_net_started(n, queue_status) && !n->vhost_started;
+
+ if (queue_started) {
+ qemu_flush_queued_packets(ncs);
+ }
+
+ if (!q->tx_waiting) {
+ continue;
+ }
+
+ if (queue_started) {
+ if (q->tx_timer) {
+ timer_mod(q->tx_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + n->tx_timeout);
+ } else {
+ qemu_bh_schedule(q->tx_bh);
+ }
+ } else {
+ if (q->tx_timer) {
+ timer_del(q->tx_timer);
+ } else {
+ qemu_bh_cancel(q->tx_bh);
+ }
+ }
+ }
+}
+
+static void virtio_net_set_link_status(NetClientState *nc)
+{
+ VirtIONet *n = qemu_get_nic_opaque(nc);
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ uint16_t old_status = n->status;
+
+ if (nc->link_down)
+ n->status &= ~VIRTIO_NET_S_LINK_UP;
+ else
+ n->status |= VIRTIO_NET_S_LINK_UP;
+
+ if (n->status != old_status)
+ virtio_notify_config(vdev);
+
+ virtio_net_set_status(vdev, vdev->status);
+}
+
+static void rxfilter_notify(NetClientState *nc)
+{
+ VirtIONet *n = qemu_get_nic_opaque(nc);
+
+ if (nc->rxfilter_notify_enabled) {
+ gchar *path = object_get_canonical_path(OBJECT(n->qdev));
+ qapi_event_send_nic_rx_filter_changed(!!n->netclient_name,
+ n->netclient_name, path, &error_abort);
+ g_free(path);
+
+ /* disable event notification to avoid events flooding */
+ nc->rxfilter_notify_enabled = 0;
+ }
+}
+
+static intList *get_vlan_table(VirtIONet *n)
+{
+ intList *list, *entry;
+ int i, j;
+
+ list = NULL;
+ for (i = 0; i < MAX_VLAN >> 5; i++) {
+ for (j = 0; n->vlans[i] && j <= 0x1f; j++) {
+ if (n->vlans[i] & (1U << j)) {
+ entry = g_malloc0(sizeof(*entry));
+ entry->value = (i << 5) + j;
+ entry->next = list;
+ list = entry;
+ }
+ }
+ }
+
+ return list;
+}
+
+static RxFilterInfo *virtio_net_query_rxfilter(NetClientState *nc)
+{
+ VirtIONet *n = qemu_get_nic_opaque(nc);
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ RxFilterInfo *info;
+ strList *str_list, *entry;
+ int i;
+
+ info = g_malloc0(sizeof(*info));
+ info->name = g_strdup(nc->name);
+ info->promiscuous = n->promisc;
+
+ if (n->nouni) {
+ info->unicast = RX_STATE_NONE;
+ } else if (n->alluni) {
+ info->unicast = RX_STATE_ALL;
+ } else {
+ info->unicast = RX_STATE_NORMAL;
+ }
+
+ if (n->nomulti) {
+ info->multicast = RX_STATE_NONE;
+ } else if (n->allmulti) {
+ info->multicast = RX_STATE_ALL;
+ } else {
+ info->multicast = RX_STATE_NORMAL;
+ }
+
+ info->broadcast_allowed = n->nobcast;
+ info->multicast_overflow = n->mac_table.multi_overflow;
+ info->unicast_overflow = n->mac_table.uni_overflow;
+
+ info->main_mac = qemu_mac_strdup_printf(n->mac);
+
+ str_list = NULL;
+ for (i = 0; i < n->mac_table.first_multi; i++) {
+ entry = g_malloc0(sizeof(*entry));
+ entry->value = qemu_mac_strdup_printf(n->mac_table.macs + i * ETH_ALEN);
+ entry->next = str_list;
+ str_list = entry;
+ }
+ info->unicast_table = str_list;
+
+ str_list = NULL;
+ for (i = n->mac_table.first_multi; i < n->mac_table.in_use; i++) {
+ entry = g_malloc0(sizeof(*entry));
+ entry->value = qemu_mac_strdup_printf(n->mac_table.macs + i * ETH_ALEN);
+ entry->next = str_list;
+ str_list = entry;
+ }
+ info->multicast_table = str_list;
+ info->vlan_table = get_vlan_table(n);
+
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_NET_F_CTRL_VLAN)) {
+ info->vlan = RX_STATE_ALL;
+ } else if (!info->vlan_table) {
+ info->vlan = RX_STATE_NONE;
+ } else {
+ info->vlan = RX_STATE_NORMAL;
+ }
+
+ /* enable event notification after query */
+ nc->rxfilter_notify_enabled = 1;
+
+ return info;
+}
+
+static void virtio_net_reset(VirtIODevice *vdev)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+
+ /* Reset back to compatibility mode */
+ n->promisc = 1;
+ n->allmulti = 0;
+ n->alluni = 0;
+ n->nomulti = 0;
+ n->nouni = 0;
+ n->nobcast = 0;
+ /* multiqueue is disabled by default */
+ n->curr_queues = 1;
+ timer_del(n->announce_timer);
+ n->announce_counter = 0;
+ n->status &= ~VIRTIO_NET_S_ANNOUNCE;
+
+ /* Flush any MAC and VLAN filter table state */
+ n->mac_table.in_use = 0;
+ n->mac_table.first_multi = 0;
+ n->mac_table.multi_overflow = 0;
+ n->mac_table.uni_overflow = 0;
+ memset(n->mac_table.macs, 0, MAC_TABLE_ENTRIES * ETH_ALEN);
+ memcpy(&n->mac[0], &n->nic->conf->macaddr, sizeof(n->mac));
+ qemu_format_nic_info_str(qemu_get_queue(n->nic), n->mac);
+ memset(n->vlans, 0, MAX_VLAN >> 3);
+}
+
+static void peer_test_vnet_hdr(VirtIONet *n)
+{
+ NetClientState *nc = qemu_get_queue(n->nic);
+ if (!nc->peer) {
+ return;
+ }
+
+ n->has_vnet_hdr = qemu_has_vnet_hdr(nc->peer);
+}
+
+static int peer_has_vnet_hdr(VirtIONet *n)
+{
+ return n->has_vnet_hdr;
+}
+
+static int peer_has_ufo(VirtIONet *n)
+{
+ if (!peer_has_vnet_hdr(n))
+ return 0;
+
+ n->has_ufo = qemu_has_ufo(qemu_get_queue(n->nic)->peer);
+
+ return n->has_ufo;
+}
+
+static void virtio_net_set_mrg_rx_bufs(VirtIONet *n, int mergeable_rx_bufs,
+ int version_1)
+{
+ int i;
+ NetClientState *nc;
+
+ n->mergeable_rx_bufs = mergeable_rx_bufs;
+
+ if (version_1) {
+ n->guest_hdr_len = sizeof(struct virtio_net_hdr_mrg_rxbuf);
+ } else {
+ n->guest_hdr_len = n->mergeable_rx_bufs ?
+ sizeof(struct virtio_net_hdr_mrg_rxbuf) :
+ sizeof(struct virtio_net_hdr);
+ }
+
+ for (i = 0; i < n->max_queues; i++) {
+ nc = qemu_get_subqueue(n->nic, i);
+
+ if (peer_has_vnet_hdr(n) &&
+ qemu_has_vnet_hdr_len(nc->peer, n->guest_hdr_len)) {
+ qemu_set_vnet_hdr_len(nc->peer, n->guest_hdr_len);
+ n->host_hdr_len = n->guest_hdr_len;
+ }
+ }
+}
+
+static int peer_attach(VirtIONet *n, int index)
+{
+ NetClientState *nc = qemu_get_subqueue(n->nic, index);
+
+ if (!nc->peer) {
+ return 0;
+ }
+
+ if (nc->peer->info->type != NET_CLIENT_OPTIONS_KIND_TAP) {
+ return 0;
+ }
+
+ return tap_enable(nc->peer);
+}
+
+static int peer_detach(VirtIONet *n, int index)
+{
+ NetClientState *nc = qemu_get_subqueue(n->nic, index);
+
+ if (!nc->peer) {
+ return 0;
+ }
+
+ if (nc->peer->info->type != NET_CLIENT_OPTIONS_KIND_TAP) {
+ return 0;
+ }
+
+ return tap_disable(nc->peer);
+}
+
+static void virtio_net_set_queues(VirtIONet *n)
+{
+ int i;
+ int r;
+
+ for (i = 0; i < n->max_queues; i++) {
+ if (i < n->curr_queues) {
+ r = peer_attach(n, i);
+ assert(!r);
+ } else {
+ r = peer_detach(n, i);
+ assert(!r);
+ }
+ }
+}
+
+static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue);
+
+static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ NetClientState *nc = qemu_get_queue(n->nic);
+
+ /* Firstly sync all virtio-net possible supported features */
+ features |= n->host_features;
+
+ virtio_add_feature(&features, VIRTIO_NET_F_MAC);
+
+ if (!peer_has_vnet_hdr(n)) {
+ virtio_clear_feature(&features, VIRTIO_NET_F_CSUM);
+ virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO4);
+ virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO6);
+ virtio_clear_feature(&features, VIRTIO_NET_F_HOST_ECN);
+
+ virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_CSUM);
+ virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO4);
+ virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO6);
+ virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ECN);
+ }
+
+ if (!peer_has_vnet_hdr(n) || !peer_has_ufo(n)) {
+ virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_UFO);
+ virtio_clear_feature(&features, VIRTIO_NET_F_HOST_UFO);
+ }
+
+ if (!get_vhost_net(nc->peer)) {
+ return features;
+ }
+ return vhost_net_get_features(get_vhost_net(nc->peer), features);
+}
+
+static uint64_t virtio_net_bad_features(VirtIODevice *vdev)
+{
+ uint64_t features = 0;
+
+ /* Linux kernel 2.6.25. It understood MAC (as everyone must),
+ * but also these: */
+ virtio_add_feature(&features, VIRTIO_NET_F_MAC);
+ virtio_add_feature(&features, VIRTIO_NET_F_CSUM);
+ virtio_add_feature(&features, VIRTIO_NET_F_HOST_TSO4);
+ virtio_add_feature(&features, VIRTIO_NET_F_HOST_TSO6);
+ virtio_add_feature(&features, VIRTIO_NET_F_HOST_ECN);
+
+ return features;
+}
+
+static void virtio_net_apply_guest_offloads(VirtIONet *n)
+{
+ qemu_set_offload(qemu_get_queue(n->nic)->peer,
+ !!(n->curr_guest_offloads & (1ULL << VIRTIO_NET_F_GUEST_CSUM)),
+ !!(n->curr_guest_offloads & (1ULL << VIRTIO_NET_F_GUEST_TSO4)),
+ !!(n->curr_guest_offloads & (1ULL << VIRTIO_NET_F_GUEST_TSO6)),
+ !!(n->curr_guest_offloads & (1ULL << VIRTIO_NET_F_GUEST_ECN)),
+ !!(n->curr_guest_offloads & (1ULL << VIRTIO_NET_F_GUEST_UFO)));
+}
+
+static uint64_t virtio_net_guest_offloads_by_features(uint32_t features)
+{
+ static const uint64_t guest_offloads_mask =
+ (1ULL << VIRTIO_NET_F_GUEST_CSUM) |
+ (1ULL << VIRTIO_NET_F_GUEST_TSO4) |
+ (1ULL << VIRTIO_NET_F_GUEST_TSO6) |
+ (1ULL << VIRTIO_NET_F_GUEST_ECN) |
+ (1ULL << VIRTIO_NET_F_GUEST_UFO);
+
+ return guest_offloads_mask & features;
+}
+
+static inline uint64_t virtio_net_supported_guest_offloads(VirtIONet *n)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ return virtio_net_guest_offloads_by_features(vdev->guest_features);
+}
+
+static void virtio_net_set_features(VirtIODevice *vdev, uint64_t features)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ int i;
+
+ virtio_net_set_multiqueue(n,
+ virtio_has_feature(features, VIRTIO_NET_F_MQ));
+
+ virtio_net_set_mrg_rx_bufs(n,
+ virtio_has_feature(features,
+ VIRTIO_NET_F_MRG_RXBUF),
+ virtio_has_feature(features,
+ VIRTIO_F_VERSION_1));
+
+ if (n->has_vnet_hdr) {
+ n->curr_guest_offloads =
+ virtio_net_guest_offloads_by_features(features);
+ virtio_net_apply_guest_offloads(n);
+ }
+
+ for (i = 0; i < n->max_queues; i++) {
+ NetClientState *nc = qemu_get_subqueue(n->nic, i);
+
+ if (!get_vhost_net(nc->peer)) {
+ continue;
+ }
+ vhost_net_ack_features(get_vhost_net(nc->peer), features);
+ }
+
+ if (virtio_has_feature(features, VIRTIO_NET_F_CTRL_VLAN)) {
+ memset(n->vlans, 0, MAX_VLAN >> 3);
+ } else {
+ memset(n->vlans, 0xff, MAX_VLAN >> 3);
+ }
+}
+
+static int virtio_net_handle_rx_mode(VirtIONet *n, uint8_t cmd,
+ struct iovec *iov, unsigned int iov_cnt)
+{
+ uint8_t on;
+ size_t s;
+ NetClientState *nc = qemu_get_queue(n->nic);
+
+ s = iov_to_buf(iov, iov_cnt, 0, &on, sizeof(on));
+ if (s != sizeof(on)) {
+ return VIRTIO_NET_ERR;
+ }
+
+ if (cmd == VIRTIO_NET_CTRL_RX_PROMISC) {
+ n->promisc = on;
+ } else if (cmd == VIRTIO_NET_CTRL_RX_ALLMULTI) {
+ n->allmulti = on;
+ } else if (cmd == VIRTIO_NET_CTRL_RX_ALLUNI) {
+ n->alluni = on;
+ } else if (cmd == VIRTIO_NET_CTRL_RX_NOMULTI) {
+ n->nomulti = on;
+ } else if (cmd == VIRTIO_NET_CTRL_RX_NOUNI) {
+ n->nouni = on;
+ } else if (cmd == VIRTIO_NET_CTRL_RX_NOBCAST) {
+ n->nobcast = on;
+ } else {
+ return VIRTIO_NET_ERR;
+ }
+
+ rxfilter_notify(nc);
+
+ return VIRTIO_NET_OK;
+}
+
+static int virtio_net_handle_offloads(VirtIONet *n, uint8_t cmd,
+ struct iovec *iov, unsigned int iov_cnt)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ uint64_t offloads;
+ size_t s;
+
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_NET_F_CTRL_GUEST_OFFLOADS)) {
+ return VIRTIO_NET_ERR;
+ }
+
+ s = iov_to_buf(iov, iov_cnt, 0, &offloads, sizeof(offloads));
+ if (s != sizeof(offloads)) {
+ return VIRTIO_NET_ERR;
+ }
+
+ if (cmd == VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET) {
+ uint64_t supported_offloads;
+
+ if (!n->has_vnet_hdr) {
+ return VIRTIO_NET_ERR;
+ }
+
+ supported_offloads = virtio_net_supported_guest_offloads(n);
+ if (offloads & ~supported_offloads) {
+ return VIRTIO_NET_ERR;
+ }
+
+ n->curr_guest_offloads = offloads;
+ virtio_net_apply_guest_offloads(n);
+
+ return VIRTIO_NET_OK;
+ } else {
+ return VIRTIO_NET_ERR;
+ }
+}
+
+static int virtio_net_handle_mac(VirtIONet *n, uint8_t cmd,
+ struct iovec *iov, unsigned int iov_cnt)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ struct virtio_net_ctrl_mac mac_data;
+ size_t s;
+ NetClientState *nc = qemu_get_queue(n->nic);
+
+ if (cmd == VIRTIO_NET_CTRL_MAC_ADDR_SET) {
+ if (iov_size(iov, iov_cnt) != sizeof(n->mac)) {
+ return VIRTIO_NET_ERR;
+ }
+ s = iov_to_buf(iov, iov_cnt, 0, &n->mac, sizeof(n->mac));
+ assert(s == sizeof(n->mac));
+ qemu_format_nic_info_str(qemu_get_queue(n->nic), n->mac);
+ rxfilter_notify(nc);
+
+ return VIRTIO_NET_OK;
+ }
+
+ if (cmd != VIRTIO_NET_CTRL_MAC_TABLE_SET) {
+ return VIRTIO_NET_ERR;
+ }
+
+ int in_use = 0;
+ int first_multi = 0;
+ uint8_t uni_overflow = 0;
+ uint8_t multi_overflow = 0;
+ uint8_t *macs = g_malloc0(MAC_TABLE_ENTRIES * ETH_ALEN);
+
+ s = iov_to_buf(iov, iov_cnt, 0, &mac_data.entries,
+ sizeof(mac_data.entries));
+ mac_data.entries = virtio_ldl_p(vdev, &mac_data.entries);
+ if (s != sizeof(mac_data.entries)) {
+ goto error;
+ }
+ iov_discard_front(&iov, &iov_cnt, s);
+
+ if (mac_data.entries * ETH_ALEN > iov_size(iov, iov_cnt)) {
+ goto error;
+ }
+
+ if (mac_data.entries <= MAC_TABLE_ENTRIES) {
+ s = iov_to_buf(iov, iov_cnt, 0, macs,
+ mac_data.entries * ETH_ALEN);
+ if (s != mac_data.entries * ETH_ALEN) {
+ goto error;
+ }
+ in_use += mac_data.entries;
+ } else {
+ uni_overflow = 1;
+ }
+
+ iov_discard_front(&iov, &iov_cnt, mac_data.entries * ETH_ALEN);
+
+ first_multi = in_use;
+
+ s = iov_to_buf(iov, iov_cnt, 0, &mac_data.entries,
+ sizeof(mac_data.entries));
+ mac_data.entries = virtio_ldl_p(vdev, &mac_data.entries);
+ if (s != sizeof(mac_data.entries)) {
+ goto error;
+ }
+
+ iov_discard_front(&iov, &iov_cnt, s);
+
+ if (mac_data.entries * ETH_ALEN != iov_size(iov, iov_cnt)) {
+ goto error;
+ }
+
+ if (mac_data.entries <= MAC_TABLE_ENTRIES - in_use) {
+ s = iov_to_buf(iov, iov_cnt, 0, &macs[in_use * ETH_ALEN],
+ mac_data.entries * ETH_ALEN);
+ if (s != mac_data.entries * ETH_ALEN) {
+ goto error;
+ }
+ in_use += mac_data.entries;
+ } else {
+ multi_overflow = 1;
+ }
+
+ n->mac_table.in_use = in_use;
+ n->mac_table.first_multi = first_multi;
+ n->mac_table.uni_overflow = uni_overflow;
+ n->mac_table.multi_overflow = multi_overflow;
+ memcpy(n->mac_table.macs, macs, MAC_TABLE_ENTRIES * ETH_ALEN);
+ g_free(macs);
+ rxfilter_notify(nc);
+
+ return VIRTIO_NET_OK;
+
+error:
+ g_free(macs);
+ return VIRTIO_NET_ERR;
+}
+
+static int virtio_net_handle_vlan_table(VirtIONet *n, uint8_t cmd,
+ struct iovec *iov, unsigned int iov_cnt)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ uint16_t vid;
+ size_t s;
+ NetClientState *nc = qemu_get_queue(n->nic);
+
+ s = iov_to_buf(iov, iov_cnt, 0, &vid, sizeof(vid));
+ vid = virtio_lduw_p(vdev, &vid);
+ if (s != sizeof(vid)) {
+ return VIRTIO_NET_ERR;
+ }
+
+ if (vid >= MAX_VLAN)
+ return VIRTIO_NET_ERR;
+
+ if (cmd == VIRTIO_NET_CTRL_VLAN_ADD)
+ n->vlans[vid >> 5] |= (1U << (vid & 0x1f));
+ else if (cmd == VIRTIO_NET_CTRL_VLAN_DEL)
+ n->vlans[vid >> 5] &= ~(1U << (vid & 0x1f));
+ else
+ return VIRTIO_NET_ERR;
+
+ rxfilter_notify(nc);
+
+ return VIRTIO_NET_OK;
+}
+
+static int virtio_net_handle_announce(VirtIONet *n, uint8_t cmd,
+ struct iovec *iov, unsigned int iov_cnt)
+{
+ if (cmd == VIRTIO_NET_CTRL_ANNOUNCE_ACK &&
+ n->status & VIRTIO_NET_S_ANNOUNCE) {
+ n->status &= ~VIRTIO_NET_S_ANNOUNCE;
+ if (n->announce_counter) {
+ timer_mod(n->announce_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+ self_announce_delay(n->announce_counter));
+ }
+ return VIRTIO_NET_OK;
+ } else {
+ return VIRTIO_NET_ERR;
+ }
+}
+
+static int virtio_net_handle_mq(VirtIONet *n, uint8_t cmd,
+ struct iovec *iov, unsigned int iov_cnt)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ struct virtio_net_ctrl_mq mq;
+ size_t s;
+ uint16_t queues;
+
+ s = iov_to_buf(iov, iov_cnt, 0, &mq, sizeof(mq));
+ if (s != sizeof(mq)) {
+ return VIRTIO_NET_ERR;
+ }
+
+ if (cmd != VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET) {
+ return VIRTIO_NET_ERR;
+ }
+
+ queues = virtio_lduw_p(vdev, &mq.virtqueue_pairs);
+
+ if (queues < VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN ||
+ queues > VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX ||
+ queues > n->max_queues ||
+ !n->multiqueue) {
+ return VIRTIO_NET_ERR;
+ }
+
+ n->curr_queues = queues;
+ /* stop the backend before changing the number of queues to avoid handling a
+ * disabled queue */
+ virtio_net_set_status(vdev, vdev->status);
+ virtio_net_set_queues(n);
+
+ return VIRTIO_NET_OK;
+}
+static void virtio_net_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ struct virtio_net_ctrl_hdr ctrl;
+ virtio_net_ctrl_ack status = VIRTIO_NET_ERR;
+ VirtQueueElement elem;
+ size_t s;
+ struct iovec *iov, *iov2;
+ unsigned int iov_cnt;
+
+ while (virtqueue_pop(vq, &elem)) {
+ if (iov_size(elem.in_sg, elem.in_num) < sizeof(status) ||
+ iov_size(elem.out_sg, elem.out_num) < sizeof(ctrl)) {
+ error_report("virtio-net ctrl missing headers");
+ exit(1);
+ }
+
+ iov_cnt = elem.out_num;
+ iov2 = iov = g_memdup(elem.out_sg, sizeof(struct iovec) * elem.out_num);
+ s = iov_to_buf(iov, iov_cnt, 0, &ctrl, sizeof(ctrl));
+ iov_discard_front(&iov, &iov_cnt, sizeof(ctrl));
+ if (s != sizeof(ctrl)) {
+ status = VIRTIO_NET_ERR;
+ } else if (ctrl.class == VIRTIO_NET_CTRL_RX) {
+ status = virtio_net_handle_rx_mode(n, ctrl.cmd, iov, iov_cnt);
+ } else if (ctrl.class == VIRTIO_NET_CTRL_MAC) {
+ status = virtio_net_handle_mac(n, ctrl.cmd, iov, iov_cnt);
+ } else if (ctrl.class == VIRTIO_NET_CTRL_VLAN) {
+ status = virtio_net_handle_vlan_table(n, ctrl.cmd, iov, iov_cnt);
+ } else if (ctrl.class == VIRTIO_NET_CTRL_ANNOUNCE) {
+ status = virtio_net_handle_announce(n, ctrl.cmd, iov, iov_cnt);
+ } else if (ctrl.class == VIRTIO_NET_CTRL_MQ) {
+ status = virtio_net_handle_mq(n, ctrl.cmd, iov, iov_cnt);
+ } else if (ctrl.class == VIRTIO_NET_CTRL_GUEST_OFFLOADS) {
+ status = virtio_net_handle_offloads(n, ctrl.cmd, iov, iov_cnt);
+ }
+
+ s = iov_from_buf(elem.in_sg, elem.in_num, 0, &status, sizeof(status));
+ assert(s == sizeof(status));
+
+ virtqueue_push(vq, &elem, sizeof(status));
+ virtio_notify(vdev, vq);
+ g_free(iov2);
+ }
+}
+
+/* RX */
+
+static void virtio_net_handle_rx(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ int queue_index = vq2q(virtio_get_queue_index(vq));
+
+ qemu_flush_queued_packets(qemu_get_subqueue(n->nic, queue_index));
+}
+
+static int virtio_net_can_receive(NetClientState *nc)
+{
+ VirtIONet *n = qemu_get_nic_opaque(nc);
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ VirtIONetQueue *q = virtio_net_get_subqueue(nc);
+
+ if (!vdev->vm_running) {
+ return 0;
+ }
+
+ if (nc->queue_index >= n->curr_queues) {
+ return 0;
+ }
+
+ if (!virtio_queue_ready(q->rx_vq) ||
+ !(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int virtio_net_has_buffers(VirtIONetQueue *q, int bufsize)
+{
+ VirtIONet *n = q->n;
+ if (virtio_queue_empty(q->rx_vq) ||
+ (n->mergeable_rx_bufs &&
+ !virtqueue_avail_bytes(q->rx_vq, bufsize, 0))) {
+ virtio_queue_set_notification(q->rx_vq, 1);
+
+ /* To avoid a race condition where the guest has made some buffers
+ * available after the above check but before notification was
+ * enabled, check for available buffers again.
+ */
+ if (virtio_queue_empty(q->rx_vq) ||
+ (n->mergeable_rx_bufs &&
+ !virtqueue_avail_bytes(q->rx_vq, bufsize, 0))) {
+ return 0;
+ }
+ }
+
+ virtio_queue_set_notification(q->rx_vq, 0);
+ return 1;
+}
+
+static void virtio_net_hdr_swap(VirtIODevice *vdev, struct virtio_net_hdr *hdr)
+{
+ virtio_tswap16s(vdev, &hdr->hdr_len);
+ virtio_tswap16s(vdev, &hdr->gso_size);
+ virtio_tswap16s(vdev, &hdr->csum_start);
+ virtio_tswap16s(vdev, &hdr->csum_offset);
+}
+
+/* dhclient uses AF_PACKET but doesn't pass auxdata to the kernel so
+ * it never finds out that the packets don't have valid checksums. This
+ * causes dhclient to get upset. Fedora's carried a patch for ages to
+ * fix this with Xen but it hasn't appeared in an upstream release of
+ * dhclient yet.
+ *
+ * To avoid breaking existing guests, we catch udp packets and add
+ * checksums. This is terrible but it's better than hacking the guest
+ * kernels.
+ *
+ * N.B. if we introduce a zero-copy API, this operation is no longer free so
+ * we should provide a mechanism to disable it to avoid polluting the host
+ * cache.
+ */
+static void work_around_broken_dhclient(struct virtio_net_hdr *hdr,
+ uint8_t *buf, size_t size)
+{
+ if ((hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) && /* missing csum */
+ (size > 27 && size < 1500) && /* normal sized MTU */
+ (buf[12] == 0x08 && buf[13] == 0x00) && /* ethertype == IPv4 */
+ (buf[23] == 17) && /* ip.protocol == UDP */
+ (buf[34] == 0 && buf[35] == 67)) { /* udp.srcport == bootps */
+ net_checksum_calculate(buf, size);
+ hdr->flags &= ~VIRTIO_NET_HDR_F_NEEDS_CSUM;
+ }
+}
+
+static void receive_header(VirtIONet *n, const struct iovec *iov, int iov_cnt,
+ const void *buf, size_t size)
+{
+ if (n->has_vnet_hdr) {
+ /* FIXME this cast is evil */
+ void *wbuf = (void *)buf;
+ work_around_broken_dhclient(wbuf, wbuf + n->host_hdr_len,
+ size - n->host_hdr_len);
+ virtio_net_hdr_swap(VIRTIO_DEVICE(n), wbuf);
+ iov_from_buf(iov, iov_cnt, 0, buf, sizeof(struct virtio_net_hdr));
+ } else {
+ struct virtio_net_hdr hdr = {
+ .flags = 0,
+ .gso_type = VIRTIO_NET_HDR_GSO_NONE
+ };
+ iov_from_buf(iov, iov_cnt, 0, &hdr, sizeof hdr);
+ }
+}
+
+static int receive_filter(VirtIONet *n, const uint8_t *buf, int size)
+{
+ static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ static const uint8_t vlan[] = {0x81, 0x00};
+ uint8_t *ptr = (uint8_t *)buf;
+ int i;
+
+ if (n->promisc)
+ return 1;
+
+ ptr += n->host_hdr_len;
+
+ if (!memcmp(&ptr[12], vlan, sizeof(vlan))) {
+ int vid = be16_to_cpup((uint16_t *)(ptr + 14)) & 0xfff;
+ if (!(n->vlans[vid >> 5] & (1U << (vid & 0x1f))))
+ return 0;
+ }
+
+ if (ptr[0] & 1) { // multicast
+ if (!memcmp(ptr, bcast, sizeof(bcast))) {
+ return !n->nobcast;
+ } else if (n->nomulti) {
+ return 0;
+ } else if (n->allmulti || n->mac_table.multi_overflow) {
+ return 1;
+ }
+
+ for (i = n->mac_table.first_multi; i < n->mac_table.in_use; i++) {
+ if (!memcmp(ptr, &n->mac_table.macs[i * ETH_ALEN], ETH_ALEN)) {
+ return 1;
+ }
+ }
+ } else { // unicast
+ if (n->nouni) {
+ return 0;
+ } else if (n->alluni || n->mac_table.uni_overflow) {
+ return 1;
+ } else if (!memcmp(ptr, n->mac, ETH_ALEN)) {
+ return 1;
+ }
+
+ for (i = 0; i < n->mac_table.first_multi; i++) {
+ if (!memcmp(ptr, &n->mac_table.macs[i * ETH_ALEN], ETH_ALEN)) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ VirtIONet *n = qemu_get_nic_opaque(nc);
+ VirtIONetQueue *q = virtio_net_get_subqueue(nc);
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ struct iovec mhdr_sg[VIRTQUEUE_MAX_SIZE];
+ struct virtio_net_hdr_mrg_rxbuf mhdr;
+ unsigned mhdr_cnt = 0;
+ size_t offset, i, guest_offset;
+
+ if (!virtio_net_can_receive(nc)) {
+ return -1;
+ }
+
+ /* hdr_len refers to the header we supply to the guest */
+ if (!virtio_net_has_buffers(q, size + n->guest_hdr_len - n->host_hdr_len)) {
+ return 0;
+ }
+
+ if (!receive_filter(n, buf, size))
+ return size;
+
+ offset = i = 0;
+
+ while (offset < size) {
+ VirtQueueElement elem;
+ int len, total;
+ const struct iovec *sg = elem.in_sg;
+
+ total = 0;
+
+ if (virtqueue_pop(q->rx_vq, &elem) == 0) {
+ if (i == 0)
+ return -1;
+ error_report("virtio-net unexpected empty queue: "
+ "i %zd mergeable %d offset %zd, size %zd, "
+ "guest hdr len %zd, host hdr len %zd "
+ "guest features 0x%" PRIx64,
+ i, n->mergeable_rx_bufs, offset, size,
+ n->guest_hdr_len, n->host_hdr_len,
+ vdev->guest_features);
+ exit(1);
+ }
+
+ if (elem.in_num < 1) {
+ error_report("virtio-net receive queue contains no in buffers");
+ exit(1);
+ }
+
+ if (i == 0) {
+ assert(offset == 0);
+ if (n->mergeable_rx_bufs) {
+ mhdr_cnt = iov_copy(mhdr_sg, ARRAY_SIZE(mhdr_sg),
+ sg, elem.in_num,
+ offsetof(typeof(mhdr), num_buffers),
+ sizeof(mhdr.num_buffers));
+ }
+
+ receive_header(n, sg, elem.in_num, buf, size);
+ offset = n->host_hdr_len;
+ total += n->guest_hdr_len;
+ guest_offset = n->guest_hdr_len;
+ } else {
+ guest_offset = 0;
+ }
+
+ /* copy in packet. ugh */
+ len = iov_from_buf(sg, elem.in_num, guest_offset,
+ buf + offset, size - offset);
+ total += len;
+ offset += len;
+ /* If buffers can't be merged, at this point we
+ * must have consumed the complete packet.
+ * Otherwise, drop it. */
+ if (!n->mergeable_rx_bufs && offset < size) {
+ virtqueue_discard(q->rx_vq, &elem, total);
+ return size;
+ }
+
+ /* signal other side */
+ virtqueue_fill(q->rx_vq, &elem, total, i++);
+ }
+
+ if (mhdr_cnt) {
+ virtio_stw_p(vdev, &mhdr.num_buffers, i);
+ iov_from_buf(mhdr_sg, mhdr_cnt,
+ 0,
+ &mhdr.num_buffers, sizeof mhdr.num_buffers);
+ }
+
+ virtqueue_flush(q->rx_vq, i);
+ virtio_notify(vdev, q->rx_vq);
+
+ return size;
+}
+
+static int32_t virtio_net_flush_tx(VirtIONetQueue *q);
+
+static void virtio_net_tx_complete(NetClientState *nc, ssize_t len)
+{
+ VirtIONet *n = qemu_get_nic_opaque(nc);
+ VirtIONetQueue *q = virtio_net_get_subqueue(nc);
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+
+ virtqueue_push(q->tx_vq, &q->async_tx.elem, 0);
+ virtio_notify(vdev, q->tx_vq);
+
+ q->async_tx.elem.out_num = q->async_tx.len = 0;
+
+ virtio_queue_set_notification(q->tx_vq, 1);
+ virtio_net_flush_tx(q);
+}
+
+/* TX */
+static int32_t virtio_net_flush_tx(VirtIONetQueue *q)
+{
+ VirtIONet *n = q->n;
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ VirtQueueElement elem;
+ int32_t num_packets = 0;
+ int queue_index = vq2q(virtio_get_queue_index(q->tx_vq));
+ if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ return num_packets;
+ }
+
+ if (q->async_tx.elem.out_num) {
+ virtio_queue_set_notification(q->tx_vq, 0);
+ return num_packets;
+ }
+
+ while (virtqueue_pop(q->tx_vq, &elem)) {
+ ssize_t ret, len;
+ unsigned int out_num = elem.out_num;
+ struct iovec *out_sg = &elem.out_sg[0];
+ struct iovec sg[VIRTQUEUE_MAX_SIZE], sg2[VIRTQUEUE_MAX_SIZE + 1];
+ struct virtio_net_hdr_mrg_rxbuf mhdr;
+
+ if (out_num < 1) {
+ error_report("virtio-net header not in first element");
+ exit(1);
+ }
+
+ if (n->has_vnet_hdr) {
+ if (iov_to_buf(out_sg, out_num, 0, &mhdr, n->guest_hdr_len) <
+ n->guest_hdr_len) {
+ error_report("virtio-net header incorrect");
+ exit(1);
+ }
+ if (virtio_needs_swap(vdev)) {
+ virtio_net_hdr_swap(vdev, (void *) &mhdr);
+ sg2[0].iov_base = &mhdr;
+ sg2[0].iov_len = n->guest_hdr_len;
+ out_num = iov_copy(&sg2[1], ARRAY_SIZE(sg2) - 1,
+ out_sg, out_num,
+ n->guest_hdr_len, -1);
+ if (out_num == VIRTQUEUE_MAX_SIZE) {
+ goto drop;
+ }
+ out_num += 1;
+ out_sg = sg2;
+ }
+ }
+ /*
+ * If host wants to see the guest header as is, we can
+ * pass it on unchanged. Otherwise, copy just the parts
+ * that host is interested in.
+ */
+ assert(n->host_hdr_len <= n->guest_hdr_len);
+ if (n->host_hdr_len != n->guest_hdr_len) {
+ unsigned sg_num = iov_copy(sg, ARRAY_SIZE(sg),
+ out_sg, out_num,
+ 0, n->host_hdr_len);
+ sg_num += iov_copy(sg + sg_num, ARRAY_SIZE(sg) - sg_num,
+ out_sg, out_num,
+ n->guest_hdr_len, -1);
+ out_num = sg_num;
+ out_sg = sg;
+ }
+
+ len = n->guest_hdr_len;
+
+ ret = qemu_sendv_packet_async(qemu_get_subqueue(n->nic, queue_index),
+ out_sg, out_num, virtio_net_tx_complete);
+ if (ret == 0) {
+ virtio_queue_set_notification(q->tx_vq, 0);
+ q->async_tx.elem = elem;
+ q->async_tx.len = len;
+ return -EBUSY;
+ }
+
+ len += ret;
+drop:
+ virtqueue_push(q->tx_vq, &elem, 0);
+ virtio_notify(vdev, q->tx_vq);
+
+ if (++num_packets >= n->tx_burst) {
+ break;
+ }
+ }
+ return num_packets;
+}
+
+static void virtio_net_handle_tx_timer(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ VirtIONetQueue *q = &n->vqs[vq2q(virtio_get_queue_index(vq))];
+
+ /* This happens when device was stopped but VCPU wasn't. */
+ if (!vdev->vm_running) {
+ q->tx_waiting = 1;
+ return;
+ }
+
+ if (q->tx_waiting) {
+ virtio_queue_set_notification(vq, 1);
+ timer_del(q->tx_timer);
+ q->tx_waiting = 0;
+ virtio_net_flush_tx(q);
+ } else {
+ timer_mod(q->tx_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + n->tx_timeout);
+ q->tx_waiting = 1;
+ virtio_queue_set_notification(vq, 0);
+ }
+}
+
+static void virtio_net_handle_tx_bh(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ VirtIONetQueue *q = &n->vqs[vq2q(virtio_get_queue_index(vq))];
+
+ if (unlikely(q->tx_waiting)) {
+ return;
+ }
+ q->tx_waiting = 1;
+ /* This happens when device was stopped but VCPU wasn't. */
+ if (!vdev->vm_running) {
+ return;
+ }
+ virtio_queue_set_notification(vq, 0);
+ qemu_bh_schedule(q->tx_bh);
+}
+
+static void virtio_net_tx_timer(void *opaque)
+{
+ VirtIONetQueue *q = opaque;
+ VirtIONet *n = q->n;
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ /* This happens when device was stopped but BH wasn't. */
+ if (!vdev->vm_running) {
+ /* Make sure tx waiting is set, so we'll run when restarted. */
+ assert(q->tx_waiting);
+ return;
+ }
+
+ q->tx_waiting = 0;
+
+ /* Just in case the driver is not ready on more */
+ if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ return;
+ }
+
+ virtio_queue_set_notification(q->tx_vq, 1);
+ virtio_net_flush_tx(q);
+}
+
+static void virtio_net_tx_bh(void *opaque)
+{
+ VirtIONetQueue *q = opaque;
+ VirtIONet *n = q->n;
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ int32_t ret;
+
+ /* This happens when device was stopped but BH wasn't. */
+ if (!vdev->vm_running) {
+ /* Make sure tx waiting is set, so we'll run when restarted. */
+ assert(q->tx_waiting);
+ return;
+ }
+
+ q->tx_waiting = 0;
+
+ /* Just in case the driver is not ready on more */
+ if (unlikely(!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK))) {
+ return;
+ }
+
+ ret = virtio_net_flush_tx(q);
+ if (ret == -EBUSY) {
+ return; /* Notification re-enable handled by tx_complete */
+ }
+
+ /* If we flush a full burst of packets, assume there are
+ * more coming and immediately reschedule */
+ if (ret >= n->tx_burst) {
+ qemu_bh_schedule(q->tx_bh);
+ q->tx_waiting = 1;
+ return;
+ }
+
+ /* If less than a full burst, re-enable notification and flush
+ * anything that may have come in while we weren't looking. If
+ * we find something, assume the guest is still active and reschedule */
+ virtio_queue_set_notification(q->tx_vq, 1);
+ if (virtio_net_flush_tx(q) > 0) {
+ virtio_queue_set_notification(q->tx_vq, 0);
+ qemu_bh_schedule(q->tx_bh);
+ q->tx_waiting = 1;
+ }
+}
+
+static void virtio_net_add_queue(VirtIONet *n, int index)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+
+ n->vqs[index].rx_vq = virtio_add_queue(vdev, 256, virtio_net_handle_rx);
+ if (n->net_conf.tx && !strcmp(n->net_conf.tx, "timer")) {
+ n->vqs[index].tx_vq =
+ virtio_add_queue(vdev, 256, virtio_net_handle_tx_timer);
+ n->vqs[index].tx_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ virtio_net_tx_timer,
+ &n->vqs[index]);
+ } else {
+ n->vqs[index].tx_vq =
+ virtio_add_queue(vdev, 256, virtio_net_handle_tx_bh);
+ n->vqs[index].tx_bh = qemu_bh_new(virtio_net_tx_bh, &n->vqs[index]);
+ }
+
+ n->vqs[index].tx_waiting = 0;
+ n->vqs[index].n = n;
+}
+
+static void virtio_net_del_queue(VirtIONet *n, int index)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ VirtIONetQueue *q = &n->vqs[index];
+ NetClientState *nc = qemu_get_subqueue(n->nic, index);
+
+ qemu_purge_queued_packets(nc);
+
+ virtio_del_queue(vdev, index * 2);
+ if (q->tx_timer) {
+ timer_del(q->tx_timer);
+ timer_free(q->tx_timer);
+ } else {
+ qemu_bh_delete(q->tx_bh);
+ }
+ virtio_del_queue(vdev, index * 2 + 1);
+}
+
+static void virtio_net_change_num_queues(VirtIONet *n, int new_max_queues)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ int old_num_queues = virtio_get_num_queues(vdev);
+ int new_num_queues = new_max_queues * 2 + 1;
+ int i;
+
+ assert(old_num_queues >= 3);
+ assert(old_num_queues % 2 == 1);
+
+ if (old_num_queues == new_num_queues) {
+ return;
+ }
+
+ /*
+ * We always need to remove and add ctrl vq if
+ * old_num_queues != new_num_queues. Remove ctrl_vq first,
+ * and then we only enter one of the following too loops.
+ */
+ virtio_del_queue(vdev, old_num_queues - 1);
+
+ for (i = new_num_queues - 1; i < old_num_queues - 1; i += 2) {
+ /* new_num_queues < old_num_queues */
+ virtio_net_del_queue(n, i / 2);
+ }
+
+ for (i = old_num_queues - 1; i < new_num_queues - 1; i += 2) {
+ /* new_num_queues > old_num_queues */
+ virtio_net_add_queue(n, i / 2);
+ }
+
+ /* add ctrl_vq last */
+ n->ctrl_vq = virtio_add_queue(vdev, 64, virtio_net_handle_ctrl);
+}
+
+static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue)
+{
+ int max = multiqueue ? n->max_queues : 1;
+
+ n->multiqueue = multiqueue;
+ virtio_net_change_num_queues(n, max);
+
+ virtio_net_set_queues(n);
+}
+
+static void virtio_net_save(QEMUFile *f, void *opaque)
+{
+ VirtIONet *n = opaque;
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+
+ /* At this point, backend must be stopped, otherwise
+ * it might keep writing to memory. */
+ assert(!n->vhost_started);
+ virtio_save(vdev, f);
+}
+
+static void virtio_net_save_device(VirtIODevice *vdev, QEMUFile *f)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ int i;
+
+ qemu_put_buffer(f, n->mac, ETH_ALEN);
+ qemu_put_be32(f, n->vqs[0].tx_waiting);
+ qemu_put_be32(f, n->mergeable_rx_bufs);
+ qemu_put_be16(f, n->status);
+ qemu_put_byte(f, n->promisc);
+ qemu_put_byte(f, n->allmulti);
+ qemu_put_be32(f, n->mac_table.in_use);
+ qemu_put_buffer(f, n->mac_table.macs, n->mac_table.in_use * ETH_ALEN);
+ qemu_put_buffer(f, (uint8_t *)n->vlans, MAX_VLAN >> 3);
+ qemu_put_be32(f, n->has_vnet_hdr);
+ qemu_put_byte(f, n->mac_table.multi_overflow);
+ qemu_put_byte(f, n->mac_table.uni_overflow);
+ qemu_put_byte(f, n->alluni);
+ qemu_put_byte(f, n->nomulti);
+ qemu_put_byte(f, n->nouni);
+ qemu_put_byte(f, n->nobcast);
+ qemu_put_byte(f, n->has_ufo);
+ if (n->max_queues > 1) {
+ qemu_put_be16(f, n->max_queues);
+ qemu_put_be16(f, n->curr_queues);
+ for (i = 1; i < n->curr_queues; i++) {
+ qemu_put_be32(f, n->vqs[i].tx_waiting);
+ }
+ }
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_NET_F_CTRL_GUEST_OFFLOADS)) {
+ qemu_put_be64(f, n->curr_guest_offloads);
+ }
+}
+
+static int virtio_net_load(QEMUFile *f, void *opaque, int version_id)
+{
+ VirtIONet *n = opaque;
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+ int ret;
+
+ if (version_id < 2 || version_id > VIRTIO_NET_VM_VERSION)
+ return -EINVAL;
+
+ ret = virtio_load(vdev, f, version_id);
+ if (ret) {
+ return ret;
+ }
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_NET_F_CTRL_GUEST_OFFLOADS)) {
+ n->curr_guest_offloads = qemu_get_be64(f);
+ } else {
+ n->curr_guest_offloads = virtio_net_supported_guest_offloads(n);
+ }
+
+ if (peer_has_vnet_hdr(n)) {
+ virtio_net_apply_guest_offloads(n);
+ }
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_NET_F_GUEST_ANNOUNCE) &&
+ virtio_vdev_has_feature(vdev, VIRTIO_NET_F_CTRL_VQ)) {
+ n->announce_counter = SELF_ANNOUNCE_ROUNDS;
+ timer_mod(n->announce_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL));
+ }
+
+ return 0;
+}
+
+static int virtio_net_load_device(VirtIODevice *vdev, QEMUFile *f,
+ int version_id)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ int i, link_down;
+
+ qemu_get_buffer(f, n->mac, ETH_ALEN);
+ n->vqs[0].tx_waiting = qemu_get_be32(f);
+
+ virtio_net_set_mrg_rx_bufs(n, qemu_get_be32(f),
+ virtio_vdev_has_feature(vdev,
+ VIRTIO_F_VERSION_1));
+
+ if (version_id >= 3)
+ n->status = qemu_get_be16(f);
+
+ if (version_id >= 4) {
+ if (version_id < 8) {
+ n->promisc = qemu_get_be32(f);
+ n->allmulti = qemu_get_be32(f);
+ } else {
+ n->promisc = qemu_get_byte(f);
+ n->allmulti = qemu_get_byte(f);
+ }
+ }
+
+ if (version_id >= 5) {
+ n->mac_table.in_use = qemu_get_be32(f);
+ /* MAC_TABLE_ENTRIES may be different from the saved image */
+ if (n->mac_table.in_use <= MAC_TABLE_ENTRIES) {
+ qemu_get_buffer(f, n->mac_table.macs,
+ n->mac_table.in_use * ETH_ALEN);
+ } else {
+ int64_t i;
+
+ /* Overflow detected - can happen if source has a larger MAC table.
+ * We simply set overflow flag so there's no need to maintain the
+ * table of addresses, discard them all.
+ * Note: 64 bit math to avoid integer overflow.
+ */
+ for (i = 0; i < (int64_t)n->mac_table.in_use * ETH_ALEN; ++i) {
+ qemu_get_byte(f);
+ }
+ n->mac_table.multi_overflow = n->mac_table.uni_overflow = 1;
+ n->mac_table.in_use = 0;
+ }
+ }
+
+ if (version_id >= 6)
+ qemu_get_buffer(f, (uint8_t *)n->vlans, MAX_VLAN >> 3);
+
+ if (version_id >= 7) {
+ if (qemu_get_be32(f) && !peer_has_vnet_hdr(n)) {
+ error_report("virtio-net: saved image requires vnet_hdr=on");
+ return -1;
+ }
+ }
+
+ if (version_id >= 9) {
+ n->mac_table.multi_overflow = qemu_get_byte(f);
+ n->mac_table.uni_overflow = qemu_get_byte(f);
+ }
+
+ if (version_id >= 10) {
+ n->alluni = qemu_get_byte(f);
+ n->nomulti = qemu_get_byte(f);
+ n->nouni = qemu_get_byte(f);
+ n->nobcast = qemu_get_byte(f);
+ }
+
+ if (version_id >= 11) {
+ if (qemu_get_byte(f) && !peer_has_ufo(n)) {
+ error_report("virtio-net: saved image requires TUN_F_UFO support");
+ return -1;
+ }
+ }
+
+ if (n->max_queues > 1) {
+ if (n->max_queues != qemu_get_be16(f)) {
+ error_report("virtio-net: different max_queues ");
+ return -1;
+ }
+
+ n->curr_queues = qemu_get_be16(f);
+ if (n->curr_queues > n->max_queues) {
+ error_report("virtio-net: curr_queues %x > max_queues %x",
+ n->curr_queues, n->max_queues);
+ return -1;
+ }
+ for (i = 1; i < n->curr_queues; i++) {
+ n->vqs[i].tx_waiting = qemu_get_be32(f);
+ }
+ }
+
+ virtio_net_set_queues(n);
+
+ /* Find the first multicast entry in the saved MAC filter */
+ for (i = 0; i < n->mac_table.in_use; i++) {
+ if (n->mac_table.macs[i * ETH_ALEN] & 1) {
+ break;
+ }
+ }
+ n->mac_table.first_multi = i;
+
+ /* nc.link_down can't be migrated, so infer link_down according
+ * to link status bit in n->status */
+ link_down = (n->status & VIRTIO_NET_S_LINK_UP) == 0;
+ for (i = 0; i < n->max_queues; i++) {
+ qemu_get_subqueue(n->nic, i)->link_down = link_down;
+ }
+
+ return 0;
+}
+
+static NetClientInfo net_virtio_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = virtio_net_can_receive,
+ .receive = virtio_net_receive,
+ .link_status_changed = virtio_net_set_link_status,
+ .query_rx_filter = virtio_net_query_rxfilter,
+};
+
+static bool virtio_net_guest_notifier_pending(VirtIODevice *vdev, int idx)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ NetClientState *nc = qemu_get_subqueue(n->nic, vq2q(idx));
+ assert(n->vhost_started);
+ return vhost_net_virtqueue_pending(get_vhost_net(nc->peer), idx);
+}
+
+static void virtio_net_guest_notifier_mask(VirtIODevice *vdev, int idx,
+ bool mask)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ NetClientState *nc = qemu_get_subqueue(n->nic, vq2q(idx));
+ assert(n->vhost_started);
+ vhost_net_virtqueue_mask(get_vhost_net(nc->peer),
+ vdev, idx, mask);
+}
+
+static void virtio_net_set_config_size(VirtIONet *n, uint64_t host_features)
+{
+ int i, config_size = 0;
+ virtio_add_feature(&host_features, VIRTIO_NET_F_MAC);
+ for (i = 0; feature_sizes[i].flags != 0; i++) {
+ if (host_features & feature_sizes[i].flags) {
+ config_size = MAX(feature_sizes[i].end, config_size);
+ }
+ }
+ n->config_size = config_size;
+}
+
+void virtio_net_set_netclient_name(VirtIONet *n, const char *name,
+ const char *type)
+{
+ /*
+ * The name can be NULL, the netclient name will be type.x.
+ */
+ assert(type != NULL);
+
+ g_free(n->netclient_name);
+ g_free(n->netclient_type);
+ n->netclient_name = g_strdup(name);
+ n->netclient_type = g_strdup(type);
+}
+
+static void virtio_net_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIONet *n = VIRTIO_NET(dev);
+ NetClientState *nc;
+ int i;
+
+ virtio_net_set_config_size(n, n->host_features);
+ virtio_init(vdev, "virtio-net", VIRTIO_ID_NET, n->config_size);
+
+ n->max_queues = MAX(n->nic_conf.peers.queues, 1);
+ if (n->max_queues * 2 + 1 > VIRTIO_QUEUE_MAX) {
+ error_setg(errp, "Invalid number of queues (= %" PRIu32 "), "
+ "must be a positive integer less than %d.",
+ n->max_queues, (VIRTIO_QUEUE_MAX - 1) / 2);
+ virtio_cleanup(vdev);
+ return;
+ }
+ n->vqs = g_malloc0(sizeof(VirtIONetQueue) * n->max_queues);
+ n->curr_queues = 1;
+ n->tx_timeout = n->net_conf.txtimer;
+
+ if (n->net_conf.tx && strcmp(n->net_conf.tx, "timer")
+ && strcmp(n->net_conf.tx, "bh")) {
+ error_report("virtio-net: "
+ "Unknown option tx=%s, valid options: \"timer\" \"bh\"",
+ n->net_conf.tx);
+ error_report("Defaulting to \"bh\"");
+ }
+
+ for (i = 0; i < n->max_queues; i++) {
+ virtio_net_add_queue(n, i);
+ }
+
+ n->ctrl_vq = virtio_add_queue(vdev, 64, virtio_net_handle_ctrl);
+ qemu_macaddr_default_if_unset(&n->nic_conf.macaddr);
+ memcpy(&n->mac[0], &n->nic_conf.macaddr, sizeof(n->mac));
+ n->status = VIRTIO_NET_S_LINK_UP;
+ n->announce_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ virtio_net_announce_timer, n);
+
+ if (n->netclient_type) {
+ /*
+ * Happen when virtio_net_set_netclient_name has been called.
+ */
+ n->nic = qemu_new_nic(&net_virtio_info, &n->nic_conf,
+ n->netclient_type, n->netclient_name, n);
+ } else {
+ n->nic = qemu_new_nic(&net_virtio_info, &n->nic_conf,
+ object_get_typename(OBJECT(dev)), dev->id, n);
+ }
+
+ peer_test_vnet_hdr(n);
+ if (peer_has_vnet_hdr(n)) {
+ for (i = 0; i < n->max_queues; i++) {
+ qemu_using_vnet_hdr(qemu_get_subqueue(n->nic, i)->peer, true);
+ }
+ n->host_hdr_len = sizeof(struct virtio_net_hdr);
+ } else {
+ n->host_hdr_len = 0;
+ }
+
+ qemu_format_nic_info_str(qemu_get_queue(n->nic), n->nic_conf.macaddr.a);
+
+ n->vqs[0].tx_waiting = 0;
+ n->tx_burst = n->net_conf.txburst;
+ virtio_net_set_mrg_rx_bufs(n, 0, 0);
+ n->promisc = 1; /* for compatibility */
+
+ n->mac_table.macs = g_malloc0(MAC_TABLE_ENTRIES * ETH_ALEN);
+
+ n->vlans = g_malloc0(MAX_VLAN >> 3);
+
+ nc = qemu_get_queue(n->nic);
+ nc->rxfilter_notify_enabled = 1;
+
+ n->qdev = dev;
+ register_savevm(dev, "virtio-net", -1, VIRTIO_NET_VM_VERSION,
+ virtio_net_save, virtio_net_load, n);
+}
+
+static void virtio_net_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIONet *n = VIRTIO_NET(dev);
+ int i, max_queues;
+
+ /* This will stop vhost backend if appropriate. */
+ virtio_net_set_status(vdev, 0);
+
+ unregister_savevm(dev, "virtio-net", n);
+
+ g_free(n->netclient_name);
+ n->netclient_name = NULL;
+ g_free(n->netclient_type);
+ n->netclient_type = NULL;
+
+ g_free(n->mac_table.macs);
+ g_free(n->vlans);
+
+ max_queues = n->multiqueue ? n->max_queues : 1;
+ for (i = 0; i < max_queues; i++) {
+ virtio_net_del_queue(n, i);
+ }
+
+ timer_del(n->announce_timer);
+ timer_free(n->announce_timer);
+ g_free(n->vqs);
+ qemu_del_nic(n->nic);
+ virtio_cleanup(vdev);
+}
+
+static void virtio_net_instance_init(Object *obj)
+{
+ VirtIONet *n = VIRTIO_NET(obj);
+
+ /*
+ * The default config_size is sizeof(struct virtio_net_config).
+ * Can be overriden with virtio_net_set_config_size.
+ */
+ n->config_size = sizeof(struct virtio_net_config);
+ device_add_bootindex_property(obj, &n->nic_conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ DEVICE(n), NULL);
+}
+
+static Property virtio_net_properties[] = {
+ DEFINE_PROP_BIT("csum", VirtIONet, host_features, VIRTIO_NET_F_CSUM, true),
+ DEFINE_PROP_BIT("guest_csum", VirtIONet, host_features,
+ VIRTIO_NET_F_GUEST_CSUM, true),
+ DEFINE_PROP_BIT("gso", VirtIONet, host_features, VIRTIO_NET_F_GSO, true),
+ DEFINE_PROP_BIT("guest_tso4", VirtIONet, host_features,
+ VIRTIO_NET_F_GUEST_TSO4, true),
+ DEFINE_PROP_BIT("guest_tso6", VirtIONet, host_features,
+ VIRTIO_NET_F_GUEST_TSO6, true),
+ DEFINE_PROP_BIT("guest_ecn", VirtIONet, host_features,
+ VIRTIO_NET_F_GUEST_ECN, true),
+ DEFINE_PROP_BIT("guest_ufo", VirtIONet, host_features,
+ VIRTIO_NET_F_GUEST_UFO, true),
+ DEFINE_PROP_BIT("guest_announce", VirtIONet, host_features,
+ VIRTIO_NET_F_GUEST_ANNOUNCE, true),
+ DEFINE_PROP_BIT("host_tso4", VirtIONet, host_features,
+ VIRTIO_NET_F_HOST_TSO4, true),
+ DEFINE_PROP_BIT("host_tso6", VirtIONet, host_features,
+ VIRTIO_NET_F_HOST_TSO6, true),
+ DEFINE_PROP_BIT("host_ecn", VirtIONet, host_features,
+ VIRTIO_NET_F_HOST_ECN, true),
+ DEFINE_PROP_BIT("host_ufo", VirtIONet, host_features,
+ VIRTIO_NET_F_HOST_UFO, true),
+ DEFINE_PROP_BIT("mrg_rxbuf", VirtIONet, host_features,
+ VIRTIO_NET_F_MRG_RXBUF, true),
+ DEFINE_PROP_BIT("status", VirtIONet, host_features,
+ VIRTIO_NET_F_STATUS, true),
+ DEFINE_PROP_BIT("ctrl_vq", VirtIONet, host_features,
+ VIRTIO_NET_F_CTRL_VQ, true),
+ DEFINE_PROP_BIT("ctrl_rx", VirtIONet, host_features,
+ VIRTIO_NET_F_CTRL_RX, true),
+ DEFINE_PROP_BIT("ctrl_vlan", VirtIONet, host_features,
+ VIRTIO_NET_F_CTRL_VLAN, true),
+ DEFINE_PROP_BIT("ctrl_rx_extra", VirtIONet, host_features,
+ VIRTIO_NET_F_CTRL_RX_EXTRA, true),
+ DEFINE_PROP_BIT("ctrl_mac_addr", VirtIONet, host_features,
+ VIRTIO_NET_F_CTRL_MAC_ADDR, true),
+ DEFINE_PROP_BIT("ctrl_guest_offloads", VirtIONet, host_features,
+ VIRTIO_NET_F_CTRL_GUEST_OFFLOADS, true),
+ DEFINE_PROP_BIT("mq", VirtIONet, host_features, VIRTIO_NET_F_MQ, false),
+ DEFINE_NIC_PROPERTIES(VirtIONet, nic_conf),
+ DEFINE_PROP_UINT32("x-txtimer", VirtIONet, net_conf.txtimer,
+ TX_TIMER_INTERVAL),
+ DEFINE_PROP_INT32("x-txburst", VirtIONet, net_conf.txburst, TX_BURST),
+ DEFINE_PROP_STRING("tx", VirtIONet, net_conf.tx),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_net_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ dc->props = virtio_net_properties;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ vdc->realize = virtio_net_device_realize;
+ vdc->unrealize = virtio_net_device_unrealize;
+ vdc->get_config = virtio_net_get_config;
+ vdc->set_config = virtio_net_set_config;
+ vdc->get_features = virtio_net_get_features;
+ vdc->set_features = virtio_net_set_features;
+ vdc->bad_features = virtio_net_bad_features;
+ vdc->reset = virtio_net_reset;
+ vdc->set_status = virtio_net_set_status;
+ vdc->guest_notifier_mask = virtio_net_guest_notifier_mask;
+ vdc->guest_notifier_pending = virtio_net_guest_notifier_pending;
+ vdc->load = virtio_net_load_device;
+ vdc->save = virtio_net_save_device;
+}
+
+static const TypeInfo virtio_net_info = {
+ .name = TYPE_VIRTIO_NET,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIONet),
+ .instance_init = virtio_net_instance_init,
+ .class_init = virtio_net_class_init,
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_net_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/net/vmware_utils.h b/hw/net/vmware_utils.h
new file mode 100644
index 00000000..1099df66
--- /dev/null
+++ b/hw/net/vmware_utils.h
@@ -0,0 +1,143 @@
+/*
+ * QEMU VMWARE paravirtual devices - auxiliary code
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef VMWARE_UTILS_H
+#define VMWARE_UTILS_H
+
+#include "qemu/range.h"
+
+#ifndef VMW_SHPRN
+#define VMW_SHPRN(fmt, ...) do {} while (0)
+#endif
+
+/*
+ * Shared memory access functions with byte swap support
+ * Each function contains printout for reverse-engineering needs
+ *
+ */
+static inline void
+vmw_shmem_read(hwaddr addr, void *buf, int len)
+{
+ VMW_SHPRN("SHMEM r: %" PRIx64 ", len: %d to %p", addr, len, buf);
+ cpu_physical_memory_read(addr, buf, len);
+}
+
+static inline void
+vmw_shmem_write(hwaddr addr, void *buf, int len)
+{
+ VMW_SHPRN("SHMEM w: %" PRIx64 ", len: %d to %p", addr, len, buf);
+ cpu_physical_memory_write(addr, buf, len);
+}
+
+static inline void
+vmw_shmem_rw(hwaddr addr, void *buf, int len, int is_write)
+{
+ VMW_SHPRN("SHMEM r/w: %" PRIx64 ", len: %d (to %p), is write: %d",
+ addr, len, buf, is_write);
+
+ cpu_physical_memory_rw(addr, buf, len, is_write);
+}
+
+static inline void
+vmw_shmem_set(hwaddr addr, uint8 val, int len)
+{
+ int i;
+ VMW_SHPRN("SHMEM set: %" PRIx64 ", len: %d (value 0x%X)", addr, len, val);
+
+ for (i = 0; i < len; i++) {
+ cpu_physical_memory_write(addr + i, &val, 1);
+ }
+}
+
+static inline uint32_t
+vmw_shmem_ld8(hwaddr addr)
+{
+ uint8_t res = ldub_phys(&address_space_memory, addr);
+ VMW_SHPRN("SHMEM load8: %" PRIx64 " (value 0x%X)", addr, res);
+ return res;
+}
+
+static inline void
+vmw_shmem_st8(hwaddr addr, uint8_t value)
+{
+ VMW_SHPRN("SHMEM store8: %" PRIx64 " (value 0x%X)", addr, value);
+ stb_phys(&address_space_memory, addr, value);
+}
+
+static inline uint32_t
+vmw_shmem_ld16(hwaddr addr)
+{
+ uint16_t res = lduw_le_phys(&address_space_memory, addr);
+ VMW_SHPRN("SHMEM load16: %" PRIx64 " (value 0x%X)", addr, res);
+ return res;
+}
+
+static inline void
+vmw_shmem_st16(hwaddr addr, uint16_t value)
+{
+ VMW_SHPRN("SHMEM store16: %" PRIx64 " (value 0x%X)", addr, value);
+ stw_le_phys(&address_space_memory, addr, value);
+}
+
+static inline uint32_t
+vmw_shmem_ld32(hwaddr addr)
+{
+ uint32_t res = ldl_le_phys(&address_space_memory, addr);
+ VMW_SHPRN("SHMEM load32: %" PRIx64 " (value 0x%X)", addr, res);
+ return res;
+}
+
+static inline void
+vmw_shmem_st32(hwaddr addr, uint32_t value)
+{
+ VMW_SHPRN("SHMEM store32: %" PRIx64 " (value 0x%X)", addr, value);
+ stl_le_phys(&address_space_memory, addr, value);
+}
+
+static inline uint64_t
+vmw_shmem_ld64(hwaddr addr)
+{
+ uint64_t res = ldq_le_phys(&address_space_memory, addr);
+ VMW_SHPRN("SHMEM load64: %" PRIx64 " (value %" PRIx64 ")", addr, res);
+ return res;
+}
+
+static inline void
+vmw_shmem_st64(hwaddr addr, uint64_t value)
+{
+ VMW_SHPRN("SHMEM store64: %" PRIx64 " (value %" PRIx64 ")", addr, value);
+ stq_le_phys(&address_space_memory, addr, value);
+}
+
+/* Macros for simplification of operations on array-style registers */
+
+/*
+ * Whether <addr> lies inside of array-style register defined by <base>,
+ * number of elements (<cnt>) and element size (<regsize>)
+ *
+*/
+#define VMW_IS_MULTIREG_ADDR(addr, base, cnt, regsize) \
+ range_covers_byte(base, cnt * regsize, addr)
+
+/*
+ * Returns index of given register (<addr>) in array-style register defined by
+ * <base> and element size (<regsize>)
+ *
+*/
+#define VMW_MULTIREG_IDX_BY_ADDR(addr, base, regsize) \
+ (((addr) - (base)) / (regsize))
+
+#endif
diff --git a/hw/net/vmxnet3.c b/hw/net/vmxnet3.c
new file mode 100644
index 00000000..25ca3448
--- /dev/null
+++ b/hw/net/vmxnet3.c
@@ -0,0 +1,2587 @@
+/*
+ * QEMU VMWARE VMXNET3 paravirtual NIC
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Tamir Shomer <tamirs@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "net/net.h"
+#include "net/tap.h"
+#include "net/checksum.h"
+#include "sysemu/sysemu.h"
+#include "qemu-common.h"
+#include "qemu/bswap.h"
+#include "hw/pci/msix.h"
+#include "hw/pci/msi.h"
+
+#include "vmxnet3.h"
+#include "vmxnet_debug.h"
+#include "vmware_utils.h"
+#include "vmxnet_tx_pkt.h"
+#include "vmxnet_rx_pkt.h"
+
+#define PCI_DEVICE_ID_VMWARE_VMXNET3_REVISION 0x1
+#define VMXNET3_MSIX_BAR_SIZE 0x2000
+#define MIN_BUF_SIZE 60
+
+#define VMXNET3_BAR0_IDX (0)
+#define VMXNET3_BAR1_IDX (1)
+#define VMXNET3_MSIX_BAR_IDX (2)
+
+#define VMXNET3_OFF_MSIX_TABLE (0x000)
+#define VMXNET3_OFF_MSIX_PBA (0x800)
+
+/* Link speed in Mbps should be shifted by 16 */
+#define VMXNET3_LINK_SPEED (1000 << 16)
+
+/* Link status: 1 - up, 0 - down. */
+#define VMXNET3_LINK_STATUS_UP 0x1
+
+/* Least significant bit should be set for revision and version */
+#define VMXNET3_DEVICE_VERSION 0x1
+#define VMXNET3_DEVICE_REVISION 0x1
+
+/* Number of interrupt vectors for non-MSIx modes */
+#define VMXNET3_MAX_NMSIX_INTRS (1)
+
+/* Macros for rings descriptors access */
+#define VMXNET3_READ_TX_QUEUE_DESCR8(dpa, field) \
+ (vmw_shmem_ld8(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field)))
+
+#define VMXNET3_WRITE_TX_QUEUE_DESCR8(dpa, field, value) \
+ (vmw_shmem_st8(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field, value)))
+
+#define VMXNET3_READ_TX_QUEUE_DESCR32(dpa, field) \
+ (vmw_shmem_ld32(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field)))
+
+#define VMXNET3_WRITE_TX_QUEUE_DESCR32(dpa, field, value) \
+ (vmw_shmem_st32(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field), value))
+
+#define VMXNET3_READ_TX_QUEUE_DESCR64(dpa, field) \
+ (vmw_shmem_ld64(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field)))
+
+#define VMXNET3_WRITE_TX_QUEUE_DESCR64(dpa, field, value) \
+ (vmw_shmem_st64(dpa + offsetof(struct Vmxnet3_TxQueueDesc, field), value))
+
+#define VMXNET3_READ_RX_QUEUE_DESCR64(dpa, field) \
+ (vmw_shmem_ld64(dpa + offsetof(struct Vmxnet3_RxQueueDesc, field)))
+
+#define VMXNET3_READ_RX_QUEUE_DESCR32(dpa, field) \
+ (vmw_shmem_ld32(dpa + offsetof(struct Vmxnet3_RxQueueDesc, field)))
+
+#define VMXNET3_WRITE_RX_QUEUE_DESCR64(dpa, field, value) \
+ (vmw_shmem_st64(dpa + offsetof(struct Vmxnet3_RxQueueDesc, field), value))
+
+#define VMXNET3_WRITE_RX_QUEUE_DESCR8(dpa, field, value) \
+ (vmw_shmem_st8(dpa + offsetof(struct Vmxnet3_RxQueueDesc, field), value))
+
+/* Macros for guest driver shared area access */
+#define VMXNET3_READ_DRV_SHARED64(shpa, field) \
+ (vmw_shmem_ld64(shpa + offsetof(struct Vmxnet3_DriverShared, field)))
+
+#define VMXNET3_READ_DRV_SHARED32(shpa, field) \
+ (vmw_shmem_ld32(shpa + offsetof(struct Vmxnet3_DriverShared, field)))
+
+#define VMXNET3_WRITE_DRV_SHARED32(shpa, field, val) \
+ (vmw_shmem_st32(shpa + offsetof(struct Vmxnet3_DriverShared, field), val))
+
+#define VMXNET3_READ_DRV_SHARED16(shpa, field) \
+ (vmw_shmem_ld16(shpa + offsetof(struct Vmxnet3_DriverShared, field)))
+
+#define VMXNET3_READ_DRV_SHARED8(shpa, field) \
+ (vmw_shmem_ld8(shpa + offsetof(struct Vmxnet3_DriverShared, field)))
+
+#define VMXNET3_READ_DRV_SHARED(shpa, field, b, l) \
+ (vmw_shmem_read(shpa + offsetof(struct Vmxnet3_DriverShared, field), b, l))
+
+#define VMXNET_FLAG_IS_SET(field, flag) (((field) & (flag)) == (flag))
+
+#define TYPE_VMXNET3 "vmxnet3"
+#define VMXNET3(obj) OBJECT_CHECK(VMXNET3State, (obj), TYPE_VMXNET3)
+
+/* Cyclic ring abstraction */
+typedef struct {
+ hwaddr pa;
+ size_t size;
+ size_t cell_size;
+ size_t next;
+ uint8_t gen;
+} Vmxnet3Ring;
+
+static inline void vmxnet3_ring_init(Vmxnet3Ring *ring,
+ hwaddr pa,
+ size_t size,
+ size_t cell_size,
+ bool zero_region)
+{
+ ring->pa = pa;
+ ring->size = size;
+ ring->cell_size = cell_size;
+ ring->gen = VMXNET3_INIT_GEN;
+ ring->next = 0;
+
+ if (zero_region) {
+ vmw_shmem_set(pa, 0, size * cell_size);
+ }
+}
+
+#define VMXNET3_RING_DUMP(macro, ring_name, ridx, r) \
+ macro("%s#%d: base %" PRIx64 " size %lu cell_size %lu gen %d next %lu", \
+ (ring_name), (ridx), \
+ (r)->pa, (r)->size, (r)->cell_size, (r)->gen, (r)->next)
+
+static inline void vmxnet3_ring_inc(Vmxnet3Ring *ring)
+{
+ if (++ring->next >= ring->size) {
+ ring->next = 0;
+ ring->gen ^= 1;
+ }
+}
+
+static inline void vmxnet3_ring_dec(Vmxnet3Ring *ring)
+{
+ if (ring->next-- == 0) {
+ ring->next = ring->size - 1;
+ ring->gen ^= 1;
+ }
+}
+
+static inline hwaddr vmxnet3_ring_curr_cell_pa(Vmxnet3Ring *ring)
+{
+ return ring->pa + ring->next * ring->cell_size;
+}
+
+static inline void vmxnet3_ring_read_curr_cell(Vmxnet3Ring *ring, void *buff)
+{
+ vmw_shmem_read(vmxnet3_ring_curr_cell_pa(ring), buff, ring->cell_size);
+}
+
+static inline void vmxnet3_ring_write_curr_cell(Vmxnet3Ring *ring, void *buff)
+{
+ vmw_shmem_write(vmxnet3_ring_curr_cell_pa(ring), buff, ring->cell_size);
+}
+
+static inline size_t vmxnet3_ring_curr_cell_idx(Vmxnet3Ring *ring)
+{
+ return ring->next;
+}
+
+static inline uint8_t vmxnet3_ring_curr_gen(Vmxnet3Ring *ring)
+{
+ return ring->gen;
+}
+
+/* Debug trace-related functions */
+static inline void
+vmxnet3_dump_tx_descr(struct Vmxnet3_TxDesc *descr)
+{
+ VMW_PKPRN("TX DESCR: "
+ "addr %" PRIx64 ", len: %d, gen: %d, rsvd: %d, "
+ "dtype: %d, ext1: %d, msscof: %d, hlen: %d, om: %d, "
+ "eop: %d, cq: %d, ext2: %d, ti: %d, tci: %d",
+ le64_to_cpu(descr->addr), descr->len, descr->gen, descr->rsvd,
+ descr->dtype, descr->ext1, descr->msscof, descr->hlen, descr->om,
+ descr->eop, descr->cq, descr->ext2, descr->ti, descr->tci);
+}
+
+static inline void
+vmxnet3_dump_virt_hdr(struct virtio_net_hdr *vhdr)
+{
+ VMW_PKPRN("VHDR: flags 0x%x, gso_type: 0x%x, hdr_len: %d, gso_size: %d, "
+ "csum_start: %d, csum_offset: %d",
+ vhdr->flags, vhdr->gso_type, vhdr->hdr_len, vhdr->gso_size,
+ vhdr->csum_start, vhdr->csum_offset);
+}
+
+static inline void
+vmxnet3_dump_rx_descr(struct Vmxnet3_RxDesc *descr)
+{
+ VMW_PKPRN("RX DESCR: addr %" PRIx64 ", len: %d, gen: %d, rsvd: %d, "
+ "dtype: %d, ext1: %d, btype: %d",
+ le64_to_cpu(descr->addr), descr->len, descr->gen,
+ descr->rsvd, descr->dtype, descr->ext1, descr->btype);
+}
+
+/* Device state and helper functions */
+#define VMXNET3_RX_RINGS_PER_QUEUE (2)
+
+typedef struct {
+ Vmxnet3Ring tx_ring;
+ Vmxnet3Ring comp_ring;
+
+ uint8_t intr_idx;
+ hwaddr tx_stats_pa;
+ struct UPT1_TxStats txq_stats;
+} Vmxnet3TxqDescr;
+
+typedef struct {
+ Vmxnet3Ring rx_ring[VMXNET3_RX_RINGS_PER_QUEUE];
+ Vmxnet3Ring comp_ring;
+ uint8_t intr_idx;
+ hwaddr rx_stats_pa;
+ struct UPT1_RxStats rxq_stats;
+} Vmxnet3RxqDescr;
+
+typedef struct {
+ bool is_masked;
+ bool is_pending;
+ bool is_asserted;
+} Vmxnet3IntState;
+
+typedef struct {
+ PCIDevice parent_obj;
+ NICState *nic;
+ NICConf conf;
+ MemoryRegion bar0;
+ MemoryRegion bar1;
+ MemoryRegion msix_bar;
+
+ Vmxnet3RxqDescr rxq_descr[VMXNET3_DEVICE_MAX_RX_QUEUES];
+ Vmxnet3TxqDescr txq_descr[VMXNET3_DEVICE_MAX_TX_QUEUES];
+
+ /* Whether MSI-X support was installed successfully */
+ bool msix_used;
+ /* Whether MSI support was installed successfully */
+ bool msi_used;
+ hwaddr drv_shmem;
+ hwaddr temp_shared_guest_driver_memory;
+
+ uint8_t txq_num;
+
+ /* This boolean tells whether RX packet being indicated has to */
+ /* be split into head and body chunks from different RX rings */
+ bool rx_packets_compound;
+
+ bool rx_vlan_stripping;
+ bool lro_supported;
+
+ uint8_t rxq_num;
+
+ /* Network MTU */
+ uint32_t mtu;
+
+ /* Maximum number of fragments for indicated TX packets */
+ uint32_t max_tx_frags;
+
+ /* Maximum number of fragments for indicated RX packets */
+ uint16_t max_rx_frags;
+
+ /* Index for events interrupt */
+ uint8_t event_int_idx;
+
+ /* Whether automatic interrupts masking enabled */
+ bool auto_int_masking;
+
+ bool peer_has_vhdr;
+
+ /* TX packets to QEMU interface */
+ struct VmxnetTxPkt *tx_pkt;
+ uint32_t offload_mode;
+ uint32_t cso_or_gso_size;
+ uint16_t tci;
+ bool needs_vlan;
+
+ struct VmxnetRxPkt *rx_pkt;
+
+ bool tx_sop;
+ bool skip_current_tx_pkt;
+
+ uint32_t device_active;
+ uint32_t last_command;
+
+ uint32_t link_status_and_speed;
+
+ Vmxnet3IntState interrupt_states[VMXNET3_MAX_INTRS];
+
+ uint32_t temp_mac; /* To store the low part first */
+
+ MACAddr perm_mac;
+ uint32_t vlan_table[VMXNET3_VFT_SIZE];
+ uint32_t rx_mode;
+ MACAddr *mcast_list;
+ uint32_t mcast_list_len;
+ uint32_t mcast_list_buff_size; /* needed for live migration. */
+} VMXNET3State;
+
+/* Interrupt management */
+
+/*
+ *This function returns sign whether interrupt line is in asserted state
+ * This depends on the type of interrupt used. For INTX interrupt line will
+ * be asserted until explicit deassertion, for MSI(X) interrupt line will
+ * be deasserted automatically due to notification semantics of the MSI(X)
+ * interrupts
+ */
+static bool _vmxnet3_assert_interrupt_line(VMXNET3State *s, uint32_t int_idx)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ if (s->msix_used && msix_enabled(d)) {
+ VMW_IRPRN("Sending MSI-X notification for vector %u", int_idx);
+ msix_notify(d, int_idx);
+ return false;
+ }
+ if (s->msi_used && msi_enabled(d)) {
+ VMW_IRPRN("Sending MSI notification for vector %u", int_idx);
+ msi_notify(d, int_idx);
+ return false;
+ }
+
+ VMW_IRPRN("Asserting line for interrupt %u", int_idx);
+ pci_irq_assert(d);
+ return true;
+}
+
+static void _vmxnet3_deassert_interrupt_line(VMXNET3State *s, int lidx)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ /*
+ * This function should never be called for MSI(X) interrupts
+ * because deassertion never required for message interrupts
+ */
+ assert(!s->msix_used || !msix_enabled(d));
+ /*
+ * This function should never be called for MSI(X) interrupts
+ * because deassertion never required for message interrupts
+ */
+ assert(!s->msi_used || !msi_enabled(d));
+
+ VMW_IRPRN("Deasserting line for interrupt %u", lidx);
+ pci_irq_deassert(d);
+}
+
+static void vmxnet3_update_interrupt_line_state(VMXNET3State *s, int lidx)
+{
+ if (!s->interrupt_states[lidx].is_pending &&
+ s->interrupt_states[lidx].is_asserted) {
+ VMW_IRPRN("New interrupt line state for index %d is DOWN", lidx);
+ _vmxnet3_deassert_interrupt_line(s, lidx);
+ s->interrupt_states[lidx].is_asserted = false;
+ return;
+ }
+
+ if (s->interrupt_states[lidx].is_pending &&
+ !s->interrupt_states[lidx].is_masked &&
+ !s->interrupt_states[lidx].is_asserted) {
+ VMW_IRPRN("New interrupt line state for index %d is UP", lidx);
+ s->interrupt_states[lidx].is_asserted =
+ _vmxnet3_assert_interrupt_line(s, lidx);
+ s->interrupt_states[lidx].is_pending = false;
+ return;
+ }
+}
+
+static void vmxnet3_trigger_interrupt(VMXNET3State *s, int lidx)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ s->interrupt_states[lidx].is_pending = true;
+ vmxnet3_update_interrupt_line_state(s, lidx);
+
+ if (s->msix_used && msix_enabled(d) && s->auto_int_masking) {
+ goto do_automask;
+ }
+
+ if (s->msi_used && msi_enabled(d) && s->auto_int_masking) {
+ goto do_automask;
+ }
+
+ return;
+
+do_automask:
+ s->interrupt_states[lidx].is_masked = true;
+ vmxnet3_update_interrupt_line_state(s, lidx);
+}
+
+static bool vmxnet3_interrupt_asserted(VMXNET3State *s, int lidx)
+{
+ return s->interrupt_states[lidx].is_asserted;
+}
+
+static void vmxnet3_clear_interrupt(VMXNET3State *s, int int_idx)
+{
+ s->interrupt_states[int_idx].is_pending = false;
+ if (s->auto_int_masking) {
+ s->interrupt_states[int_idx].is_masked = true;
+ }
+ vmxnet3_update_interrupt_line_state(s, int_idx);
+}
+
+static void
+vmxnet3_on_interrupt_mask_changed(VMXNET3State *s, int lidx, bool is_masked)
+{
+ s->interrupt_states[lidx].is_masked = is_masked;
+ vmxnet3_update_interrupt_line_state(s, lidx);
+}
+
+static bool vmxnet3_verify_driver_magic(hwaddr dshmem)
+{
+ return (VMXNET3_READ_DRV_SHARED32(dshmem, magic) == VMXNET3_REV1_MAGIC);
+}
+
+#define VMXNET3_GET_BYTE(x, byte_num) (((x) >> (byte_num)*8) & 0xFF)
+#define VMXNET3_MAKE_BYTE(byte_num, val) \
+ (((uint32_t)((val) & 0xFF)) << (byte_num)*8)
+
+static void vmxnet3_set_variable_mac(VMXNET3State *s, uint32_t h, uint32_t l)
+{
+ s->conf.macaddr.a[0] = VMXNET3_GET_BYTE(l, 0);
+ s->conf.macaddr.a[1] = VMXNET3_GET_BYTE(l, 1);
+ s->conf.macaddr.a[2] = VMXNET3_GET_BYTE(l, 2);
+ s->conf.macaddr.a[3] = VMXNET3_GET_BYTE(l, 3);
+ s->conf.macaddr.a[4] = VMXNET3_GET_BYTE(h, 0);
+ s->conf.macaddr.a[5] = VMXNET3_GET_BYTE(h, 1);
+
+ VMW_CFPRN("Variable MAC: " VMXNET_MF, VMXNET_MA(s->conf.macaddr.a));
+
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+}
+
+static uint64_t vmxnet3_get_mac_low(MACAddr *addr)
+{
+ return VMXNET3_MAKE_BYTE(0, addr->a[0]) |
+ VMXNET3_MAKE_BYTE(1, addr->a[1]) |
+ VMXNET3_MAKE_BYTE(2, addr->a[2]) |
+ VMXNET3_MAKE_BYTE(3, addr->a[3]);
+}
+
+static uint64_t vmxnet3_get_mac_high(MACAddr *addr)
+{
+ return VMXNET3_MAKE_BYTE(0, addr->a[4]) |
+ VMXNET3_MAKE_BYTE(1, addr->a[5]);
+}
+
+static void
+vmxnet3_inc_tx_consumption_counter(VMXNET3State *s, int qidx)
+{
+ vmxnet3_ring_inc(&s->txq_descr[qidx].tx_ring);
+}
+
+static inline void
+vmxnet3_inc_rx_consumption_counter(VMXNET3State *s, int qidx, int ridx)
+{
+ vmxnet3_ring_inc(&s->rxq_descr[qidx].rx_ring[ridx]);
+}
+
+static inline void
+vmxnet3_inc_tx_completion_counter(VMXNET3State *s, int qidx)
+{
+ vmxnet3_ring_inc(&s->txq_descr[qidx].comp_ring);
+}
+
+static void
+vmxnet3_inc_rx_completion_counter(VMXNET3State *s, int qidx)
+{
+ vmxnet3_ring_inc(&s->rxq_descr[qidx].comp_ring);
+}
+
+static void
+vmxnet3_dec_rx_completion_counter(VMXNET3State *s, int qidx)
+{
+ vmxnet3_ring_dec(&s->rxq_descr[qidx].comp_ring);
+}
+
+static void vmxnet3_complete_packet(VMXNET3State *s, int qidx, uint32 tx_ridx)
+{
+ struct Vmxnet3_TxCompDesc txcq_descr;
+
+ VMXNET3_RING_DUMP(VMW_RIPRN, "TXC", qidx, &s->txq_descr[qidx].comp_ring);
+
+ txcq_descr.txdIdx = tx_ridx;
+ txcq_descr.gen = vmxnet3_ring_curr_gen(&s->txq_descr[qidx].comp_ring);
+
+ vmxnet3_ring_write_curr_cell(&s->txq_descr[qidx].comp_ring, &txcq_descr);
+
+ /* Flush changes in TX descriptor before changing the counter value */
+ smp_wmb();
+
+ vmxnet3_inc_tx_completion_counter(s, qidx);
+ vmxnet3_trigger_interrupt(s, s->txq_descr[qidx].intr_idx);
+}
+
+static bool
+vmxnet3_setup_tx_offloads(VMXNET3State *s)
+{
+ switch (s->offload_mode) {
+ case VMXNET3_OM_NONE:
+ vmxnet_tx_pkt_build_vheader(s->tx_pkt, false, false, 0);
+ break;
+
+ case VMXNET3_OM_CSUM:
+ vmxnet_tx_pkt_build_vheader(s->tx_pkt, false, true, 0);
+ VMW_PKPRN("L4 CSO requested\n");
+ break;
+
+ case VMXNET3_OM_TSO:
+ vmxnet_tx_pkt_build_vheader(s->tx_pkt, true, true,
+ s->cso_or_gso_size);
+ vmxnet_tx_pkt_update_ip_checksums(s->tx_pkt);
+ VMW_PKPRN("GSO offload requested.");
+ break;
+
+ default:
+ g_assert_not_reached();
+ return false;
+ }
+
+ return true;
+}
+
+static void
+vmxnet3_tx_retrieve_metadata(VMXNET3State *s,
+ const struct Vmxnet3_TxDesc *txd)
+{
+ s->offload_mode = txd->om;
+ s->cso_or_gso_size = txd->msscof;
+ s->tci = txd->tci;
+ s->needs_vlan = txd->ti;
+}
+
+typedef enum {
+ VMXNET3_PKT_STATUS_OK,
+ VMXNET3_PKT_STATUS_ERROR,
+ VMXNET3_PKT_STATUS_DISCARD,/* only for tx */
+ VMXNET3_PKT_STATUS_OUT_OF_BUF /* only for rx */
+} Vmxnet3PktStatus;
+
+static void
+vmxnet3_on_tx_done_update_stats(VMXNET3State *s, int qidx,
+ Vmxnet3PktStatus status)
+{
+ size_t tot_len = vmxnet_tx_pkt_get_total_len(s->tx_pkt);
+ struct UPT1_TxStats *stats = &s->txq_descr[qidx].txq_stats;
+
+ switch (status) {
+ case VMXNET3_PKT_STATUS_OK:
+ switch (vmxnet_tx_pkt_get_packet_type(s->tx_pkt)) {
+ case ETH_PKT_BCAST:
+ stats->bcastPktsTxOK++;
+ stats->bcastBytesTxOK += tot_len;
+ break;
+ case ETH_PKT_MCAST:
+ stats->mcastPktsTxOK++;
+ stats->mcastBytesTxOK += tot_len;
+ break;
+ case ETH_PKT_UCAST:
+ stats->ucastPktsTxOK++;
+ stats->ucastBytesTxOK += tot_len;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ if (s->offload_mode == VMXNET3_OM_TSO) {
+ /*
+ * According to VMWARE headers this statistic is a number
+ * of packets after segmentation but since we don't have
+ * this information in QEMU model, the best we can do is to
+ * provide number of non-segmented packets
+ */
+ stats->TSOPktsTxOK++;
+ stats->TSOBytesTxOK += tot_len;
+ }
+ break;
+
+ case VMXNET3_PKT_STATUS_DISCARD:
+ stats->pktsTxDiscard++;
+ break;
+
+ case VMXNET3_PKT_STATUS_ERROR:
+ stats->pktsTxError++;
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void
+vmxnet3_on_rx_done_update_stats(VMXNET3State *s,
+ int qidx,
+ Vmxnet3PktStatus status)
+{
+ struct UPT1_RxStats *stats = &s->rxq_descr[qidx].rxq_stats;
+ size_t tot_len = vmxnet_rx_pkt_get_total_len(s->rx_pkt);
+
+ switch (status) {
+ case VMXNET3_PKT_STATUS_OUT_OF_BUF:
+ stats->pktsRxOutOfBuf++;
+ break;
+
+ case VMXNET3_PKT_STATUS_ERROR:
+ stats->pktsRxError++;
+ break;
+ case VMXNET3_PKT_STATUS_OK:
+ switch (vmxnet_rx_pkt_get_packet_type(s->rx_pkt)) {
+ case ETH_PKT_BCAST:
+ stats->bcastPktsRxOK++;
+ stats->bcastBytesRxOK += tot_len;
+ break;
+ case ETH_PKT_MCAST:
+ stats->mcastPktsRxOK++;
+ stats->mcastBytesRxOK += tot_len;
+ break;
+ case ETH_PKT_UCAST:
+ stats->ucastPktsRxOK++;
+ stats->ucastBytesRxOK += tot_len;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ if (tot_len > s->mtu) {
+ stats->LROPktsRxOK++;
+ stats->LROBytesRxOK += tot_len;
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static inline bool
+vmxnet3_pop_next_tx_descr(VMXNET3State *s,
+ int qidx,
+ struct Vmxnet3_TxDesc *txd,
+ uint32_t *descr_idx)
+{
+ Vmxnet3Ring *ring = &s->txq_descr[qidx].tx_ring;
+
+ vmxnet3_ring_read_curr_cell(ring, txd);
+ if (txd->gen == vmxnet3_ring_curr_gen(ring)) {
+ /* Only read after generation field verification */
+ smp_rmb();
+ /* Re-read to be sure we got the latest version */
+ vmxnet3_ring_read_curr_cell(ring, txd);
+ VMXNET3_RING_DUMP(VMW_RIPRN, "TX", qidx, ring);
+ *descr_idx = vmxnet3_ring_curr_cell_idx(ring);
+ vmxnet3_inc_tx_consumption_counter(s, qidx);
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+vmxnet3_send_packet(VMXNET3State *s, uint32_t qidx)
+{
+ Vmxnet3PktStatus status = VMXNET3_PKT_STATUS_OK;
+
+ if (!vmxnet3_setup_tx_offloads(s)) {
+ status = VMXNET3_PKT_STATUS_ERROR;
+ goto func_exit;
+ }
+
+ /* debug prints */
+ vmxnet3_dump_virt_hdr(vmxnet_tx_pkt_get_vhdr(s->tx_pkt));
+ vmxnet_tx_pkt_dump(s->tx_pkt);
+
+ if (!vmxnet_tx_pkt_send(s->tx_pkt, qemu_get_queue(s->nic))) {
+ status = VMXNET3_PKT_STATUS_DISCARD;
+ goto func_exit;
+ }
+
+func_exit:
+ vmxnet3_on_tx_done_update_stats(s, qidx, status);
+ return (status == VMXNET3_PKT_STATUS_OK);
+}
+
+static void vmxnet3_process_tx_queue(VMXNET3State *s, int qidx)
+{
+ struct Vmxnet3_TxDesc txd;
+ uint32_t txd_idx;
+ uint32_t data_len;
+ hwaddr data_pa;
+
+ for (;;) {
+ if (!vmxnet3_pop_next_tx_descr(s, qidx, &txd, &txd_idx)) {
+ break;
+ }
+
+ vmxnet3_dump_tx_descr(&txd);
+
+ if (!s->skip_current_tx_pkt) {
+ data_len = (txd.len > 0) ? txd.len : VMXNET3_MAX_TX_BUF_SIZE;
+ data_pa = le64_to_cpu(txd.addr);
+
+ if (!vmxnet_tx_pkt_add_raw_fragment(s->tx_pkt,
+ data_pa,
+ data_len)) {
+ s->skip_current_tx_pkt = true;
+ }
+ }
+
+ if (s->tx_sop) {
+ vmxnet3_tx_retrieve_metadata(s, &txd);
+ s->tx_sop = false;
+ }
+
+ if (txd.eop) {
+ if (!s->skip_current_tx_pkt && vmxnet_tx_pkt_parse(s->tx_pkt)) {
+ if (s->needs_vlan) {
+ vmxnet_tx_pkt_setup_vlan_header(s->tx_pkt, s->tci);
+ }
+
+ vmxnet3_send_packet(s, qidx);
+ } else {
+ vmxnet3_on_tx_done_update_stats(s, qidx,
+ VMXNET3_PKT_STATUS_ERROR);
+ }
+
+ vmxnet3_complete_packet(s, qidx, txd_idx);
+ s->tx_sop = true;
+ s->skip_current_tx_pkt = false;
+ vmxnet_tx_pkt_reset(s->tx_pkt);
+ }
+ }
+}
+
+static inline void
+vmxnet3_read_next_rx_descr(VMXNET3State *s, int qidx, int ridx,
+ struct Vmxnet3_RxDesc *dbuf, uint32_t *didx)
+{
+ Vmxnet3Ring *ring = &s->rxq_descr[qidx].rx_ring[ridx];
+ *didx = vmxnet3_ring_curr_cell_idx(ring);
+ vmxnet3_ring_read_curr_cell(ring, dbuf);
+}
+
+static inline uint8_t
+vmxnet3_get_rx_ring_gen(VMXNET3State *s, int qidx, int ridx)
+{
+ return s->rxq_descr[qidx].rx_ring[ridx].gen;
+}
+
+static inline hwaddr
+vmxnet3_pop_rxc_descr(VMXNET3State *s, int qidx, uint32_t *descr_gen)
+{
+ uint8_t ring_gen;
+ struct Vmxnet3_RxCompDesc rxcd;
+
+ hwaddr daddr =
+ vmxnet3_ring_curr_cell_pa(&s->rxq_descr[qidx].comp_ring);
+
+ cpu_physical_memory_read(daddr, &rxcd, sizeof(struct Vmxnet3_RxCompDesc));
+ ring_gen = vmxnet3_ring_curr_gen(&s->rxq_descr[qidx].comp_ring);
+
+ if (rxcd.gen != ring_gen) {
+ *descr_gen = ring_gen;
+ vmxnet3_inc_rx_completion_counter(s, qidx);
+ return daddr;
+ }
+
+ return 0;
+}
+
+static inline void
+vmxnet3_revert_rxc_descr(VMXNET3State *s, int qidx)
+{
+ vmxnet3_dec_rx_completion_counter(s, qidx);
+}
+
+#define RXQ_IDX (0)
+#define RX_HEAD_BODY_RING (0)
+#define RX_BODY_ONLY_RING (1)
+
+static bool
+vmxnet3_get_next_head_rx_descr(VMXNET3State *s,
+ struct Vmxnet3_RxDesc *descr_buf,
+ uint32_t *descr_idx,
+ uint32_t *ridx)
+{
+ for (;;) {
+ uint32_t ring_gen;
+ vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_HEAD_BODY_RING,
+ descr_buf, descr_idx);
+
+ /* If no more free descriptors - return */
+ ring_gen = vmxnet3_get_rx_ring_gen(s, RXQ_IDX, RX_HEAD_BODY_RING);
+ if (descr_buf->gen != ring_gen) {
+ return false;
+ }
+
+ /* Only read after generation field verification */
+ smp_rmb();
+ /* Re-read to be sure we got the latest version */
+ vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_HEAD_BODY_RING,
+ descr_buf, descr_idx);
+
+ /* Mark current descriptor as used/skipped */
+ vmxnet3_inc_rx_consumption_counter(s, RXQ_IDX, RX_HEAD_BODY_RING);
+
+ /* If this is what we are looking for - return */
+ if (descr_buf->btype == VMXNET3_RXD_BTYPE_HEAD) {
+ *ridx = RX_HEAD_BODY_RING;
+ return true;
+ }
+ }
+}
+
+static bool
+vmxnet3_get_next_body_rx_descr(VMXNET3State *s,
+ struct Vmxnet3_RxDesc *d,
+ uint32_t *didx,
+ uint32_t *ridx)
+{
+ vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_HEAD_BODY_RING, d, didx);
+
+ /* Try to find corresponding descriptor in head/body ring */
+ if (d->gen == vmxnet3_get_rx_ring_gen(s, RXQ_IDX, RX_HEAD_BODY_RING)) {
+ /* Only read after generation field verification */
+ smp_rmb();
+ /* Re-read to be sure we got the latest version */
+ vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_HEAD_BODY_RING, d, didx);
+ if (d->btype == VMXNET3_RXD_BTYPE_BODY) {
+ vmxnet3_inc_rx_consumption_counter(s, RXQ_IDX, RX_HEAD_BODY_RING);
+ *ridx = RX_HEAD_BODY_RING;
+ return true;
+ }
+ }
+
+ /*
+ * If there is no free descriptors on head/body ring or next free
+ * descriptor is a head descriptor switch to body only ring
+ */
+ vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_BODY_ONLY_RING, d, didx);
+
+ /* If no more free descriptors - return */
+ if (d->gen == vmxnet3_get_rx_ring_gen(s, RXQ_IDX, RX_BODY_ONLY_RING)) {
+ /* Only read after generation field verification */
+ smp_rmb();
+ /* Re-read to be sure we got the latest version */
+ vmxnet3_read_next_rx_descr(s, RXQ_IDX, RX_BODY_ONLY_RING, d, didx);
+ assert(d->btype == VMXNET3_RXD_BTYPE_BODY);
+ *ridx = RX_BODY_ONLY_RING;
+ vmxnet3_inc_rx_consumption_counter(s, RXQ_IDX, RX_BODY_ONLY_RING);
+ return true;
+ }
+
+ return false;
+}
+
+static inline bool
+vmxnet3_get_next_rx_descr(VMXNET3State *s, bool is_head,
+ struct Vmxnet3_RxDesc *descr_buf,
+ uint32_t *descr_idx,
+ uint32_t *ridx)
+{
+ if (is_head || !s->rx_packets_compound) {
+ return vmxnet3_get_next_head_rx_descr(s, descr_buf, descr_idx, ridx);
+ } else {
+ return vmxnet3_get_next_body_rx_descr(s, descr_buf, descr_idx, ridx);
+ }
+}
+
+/* In case packet was csum offloaded (either NEEDS_CSUM or DATA_VALID),
+ * the implementation always passes an RxCompDesc with a "Checksum
+ * calculated and found correct" to the OS (cnc=0 and tuc=1, see
+ * vmxnet3_rx_update_descr). This emulates the observed ESXi behavior.
+ *
+ * Therefore, if packet has the NEEDS_CSUM set, we must calculate
+ * and place a fully computed checksum into the tcp/udp header.
+ * Otherwise, the OS driver will receive a checksum-correct indication
+ * (CHECKSUM_UNNECESSARY), but with the actual tcp/udp checksum field
+ * having just the pseudo header csum value.
+ *
+ * While this is not a problem if packet is destined for local delivery,
+ * in the case the host OS performs forwarding, it will forward an
+ * incorrectly checksummed packet.
+ */
+static void vmxnet3_rx_need_csum_calculate(struct VmxnetRxPkt *pkt,
+ const void *pkt_data,
+ size_t pkt_len)
+{
+ struct virtio_net_hdr *vhdr;
+ bool isip4, isip6, istcp, isudp;
+ uint8_t *data;
+ int len;
+
+ if (!vmxnet_rx_pkt_has_virt_hdr(pkt)) {
+ return;
+ }
+
+ vhdr = vmxnet_rx_pkt_get_vhdr(pkt);
+ if (!VMXNET_FLAG_IS_SET(vhdr->flags, VIRTIO_NET_HDR_F_NEEDS_CSUM)) {
+ return;
+ }
+
+ vmxnet_rx_pkt_get_protocols(pkt, &isip4, &isip6, &isudp, &istcp);
+ if (!(isip4 || isip6) || !(istcp || isudp)) {
+ return;
+ }
+
+ vmxnet3_dump_virt_hdr(vhdr);
+
+ /* Validate packet len: csum_start + scum_offset + length of csum field */
+ if (pkt_len < (vhdr->csum_start + vhdr->csum_offset + 2)) {
+ VMW_PKPRN("packet len:%d < csum_start(%d) + csum_offset(%d) + 2, "
+ "cannot calculate checksum",
+ len, vhdr->csum_start, vhdr->csum_offset);
+ return;
+ }
+
+ data = (uint8_t *)pkt_data + vhdr->csum_start;
+ len = pkt_len - vhdr->csum_start;
+ /* Put the checksum obtained into the packet */
+ stw_be_p(data + vhdr->csum_offset, net_raw_checksum(data, len));
+
+ vhdr->flags &= ~VIRTIO_NET_HDR_F_NEEDS_CSUM;
+ vhdr->flags |= VIRTIO_NET_HDR_F_DATA_VALID;
+}
+
+static void vmxnet3_rx_update_descr(struct VmxnetRxPkt *pkt,
+ struct Vmxnet3_RxCompDesc *rxcd)
+{
+ int csum_ok, is_gso;
+ bool isip4, isip6, istcp, isudp;
+ struct virtio_net_hdr *vhdr;
+ uint8_t offload_type;
+
+ if (vmxnet_rx_pkt_is_vlan_stripped(pkt)) {
+ rxcd->ts = 1;
+ rxcd->tci = vmxnet_rx_pkt_get_vlan_tag(pkt);
+ }
+
+ if (!vmxnet_rx_pkt_has_virt_hdr(pkt)) {
+ goto nocsum;
+ }
+
+ vhdr = vmxnet_rx_pkt_get_vhdr(pkt);
+ /*
+ * Checksum is valid when lower level tell so or when lower level
+ * requires checksum offload telling that packet produced/bridged
+ * locally and did travel over network after last checksum calculation
+ * or production
+ */
+ csum_ok = VMXNET_FLAG_IS_SET(vhdr->flags, VIRTIO_NET_HDR_F_DATA_VALID) ||
+ VMXNET_FLAG_IS_SET(vhdr->flags, VIRTIO_NET_HDR_F_NEEDS_CSUM);
+
+ offload_type = vhdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN;
+ is_gso = (offload_type != VIRTIO_NET_HDR_GSO_NONE) ? 1 : 0;
+
+ if (!csum_ok && !is_gso) {
+ goto nocsum;
+ }
+
+ vmxnet_rx_pkt_get_protocols(pkt, &isip4, &isip6, &isudp, &istcp);
+ if ((!istcp && !isudp) || (!isip4 && !isip6)) {
+ goto nocsum;
+ }
+
+ rxcd->cnc = 0;
+ rxcd->v4 = isip4 ? 1 : 0;
+ rxcd->v6 = isip6 ? 1 : 0;
+ rxcd->tcp = istcp ? 1 : 0;
+ rxcd->udp = isudp ? 1 : 0;
+ rxcd->fcs = rxcd->tuc = rxcd->ipc = 1;
+ return;
+
+nocsum:
+ rxcd->cnc = 1;
+ return;
+}
+
+static void
+vmxnet3_physical_memory_writev(const struct iovec *iov,
+ size_t start_iov_off,
+ hwaddr target_addr,
+ size_t bytes_to_copy)
+{
+ size_t curr_off = 0;
+ size_t copied = 0;
+
+ while (bytes_to_copy) {
+ if (start_iov_off < (curr_off + iov->iov_len)) {
+ size_t chunk_len =
+ MIN((curr_off + iov->iov_len) - start_iov_off, bytes_to_copy);
+
+ cpu_physical_memory_write(target_addr + copied,
+ iov->iov_base + start_iov_off - curr_off,
+ chunk_len);
+
+ copied += chunk_len;
+ start_iov_off += chunk_len;
+ curr_off = start_iov_off;
+ bytes_to_copy -= chunk_len;
+ } else {
+ curr_off += iov->iov_len;
+ }
+ iov++;
+ }
+}
+
+static bool
+vmxnet3_indicate_packet(VMXNET3State *s)
+{
+ struct Vmxnet3_RxDesc rxd;
+ bool is_head = true;
+ uint32_t rxd_idx;
+ uint32_t rx_ridx = 0;
+
+ struct Vmxnet3_RxCompDesc rxcd;
+ uint32_t new_rxcd_gen = VMXNET3_INIT_GEN;
+ hwaddr new_rxcd_pa = 0;
+ hwaddr ready_rxcd_pa = 0;
+ struct iovec *data = vmxnet_rx_pkt_get_iovec(s->rx_pkt);
+ size_t bytes_copied = 0;
+ size_t bytes_left = vmxnet_rx_pkt_get_total_len(s->rx_pkt);
+ uint16_t num_frags = 0;
+ size_t chunk_size;
+
+ vmxnet_rx_pkt_dump(s->rx_pkt);
+
+ while (bytes_left > 0) {
+
+ /* cannot add more frags to packet */
+ if (num_frags == s->max_rx_frags) {
+ break;
+ }
+
+ new_rxcd_pa = vmxnet3_pop_rxc_descr(s, RXQ_IDX, &new_rxcd_gen);
+ if (!new_rxcd_pa) {
+ break;
+ }
+
+ if (!vmxnet3_get_next_rx_descr(s, is_head, &rxd, &rxd_idx, &rx_ridx)) {
+ break;
+ }
+
+ chunk_size = MIN(bytes_left, rxd.len);
+ vmxnet3_physical_memory_writev(data, bytes_copied,
+ le64_to_cpu(rxd.addr), chunk_size);
+ bytes_copied += chunk_size;
+ bytes_left -= chunk_size;
+
+ vmxnet3_dump_rx_descr(&rxd);
+
+ if (ready_rxcd_pa != 0) {
+ cpu_physical_memory_write(ready_rxcd_pa, &rxcd, sizeof(rxcd));
+ }
+
+ memset(&rxcd, 0, sizeof(struct Vmxnet3_RxCompDesc));
+ rxcd.rxdIdx = rxd_idx;
+ rxcd.len = chunk_size;
+ rxcd.sop = is_head;
+ rxcd.gen = new_rxcd_gen;
+ rxcd.rqID = RXQ_IDX + rx_ridx * s->rxq_num;
+
+ if (bytes_left == 0) {
+ vmxnet3_rx_update_descr(s->rx_pkt, &rxcd);
+ }
+
+ VMW_RIPRN("RX Completion descriptor: rxRing: %lu rxIdx %lu len %lu "
+ "sop %d csum_correct %lu",
+ (unsigned long) rx_ridx,
+ (unsigned long) rxcd.rxdIdx,
+ (unsigned long) rxcd.len,
+ (int) rxcd.sop,
+ (unsigned long) rxcd.tuc);
+
+ is_head = false;
+ ready_rxcd_pa = new_rxcd_pa;
+ new_rxcd_pa = 0;
+ num_frags++;
+ }
+
+ if (ready_rxcd_pa != 0) {
+ rxcd.eop = 1;
+ rxcd.err = (bytes_left != 0);
+ cpu_physical_memory_write(ready_rxcd_pa, &rxcd, sizeof(rxcd));
+
+ /* Flush RX descriptor changes */
+ smp_wmb();
+ }
+
+ if (new_rxcd_pa != 0) {
+ vmxnet3_revert_rxc_descr(s, RXQ_IDX);
+ }
+
+ vmxnet3_trigger_interrupt(s, s->rxq_descr[RXQ_IDX].intr_idx);
+
+ if (bytes_left == 0) {
+ vmxnet3_on_rx_done_update_stats(s, RXQ_IDX, VMXNET3_PKT_STATUS_OK);
+ return true;
+ } else if (num_frags == s->max_rx_frags) {
+ vmxnet3_on_rx_done_update_stats(s, RXQ_IDX, VMXNET3_PKT_STATUS_ERROR);
+ return false;
+ } else {
+ vmxnet3_on_rx_done_update_stats(s, RXQ_IDX,
+ VMXNET3_PKT_STATUS_OUT_OF_BUF);
+ return false;
+ }
+}
+
+static void
+vmxnet3_io_bar0_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VMXNET3State *s = opaque;
+
+ if (VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_TXPROD,
+ VMXNET3_DEVICE_MAX_TX_QUEUES, VMXNET3_REG_ALIGN)) {
+ int tx_queue_idx =
+ VMW_MULTIREG_IDX_BY_ADDR(addr, VMXNET3_REG_TXPROD,
+ VMXNET3_REG_ALIGN);
+ assert(tx_queue_idx <= s->txq_num);
+ vmxnet3_process_tx_queue(s, tx_queue_idx);
+ return;
+ }
+
+ if (VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_IMR,
+ VMXNET3_MAX_INTRS, VMXNET3_REG_ALIGN)) {
+ int l = VMW_MULTIREG_IDX_BY_ADDR(addr, VMXNET3_REG_IMR,
+ VMXNET3_REG_ALIGN);
+
+ VMW_CBPRN("Interrupt mask for line %d written: 0x%" PRIx64, l, val);
+
+ vmxnet3_on_interrupt_mask_changed(s, l, val);
+ return;
+ }
+
+ if (VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_RXPROD,
+ VMXNET3_DEVICE_MAX_RX_QUEUES, VMXNET3_REG_ALIGN) ||
+ VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_RXPROD2,
+ VMXNET3_DEVICE_MAX_RX_QUEUES, VMXNET3_REG_ALIGN)) {
+ return;
+ }
+
+ VMW_WRPRN("BAR0 unknown write [%" PRIx64 "] = %" PRIx64 ", size %d",
+ (uint64_t) addr, val, size);
+}
+
+static uint64_t
+vmxnet3_io_bar0_read(void *opaque, hwaddr addr, unsigned size)
+{
+ VMXNET3State *s = opaque;
+
+ if (VMW_IS_MULTIREG_ADDR(addr, VMXNET3_REG_IMR,
+ VMXNET3_MAX_INTRS, VMXNET3_REG_ALIGN)) {
+ int l = VMW_MULTIREG_IDX_BY_ADDR(addr, VMXNET3_REG_IMR,
+ VMXNET3_REG_ALIGN);
+ return s->interrupt_states[l].is_masked;
+ }
+
+ VMW_CBPRN("BAR0 unknown read [%" PRIx64 "], size %d", addr, size);
+ return 0;
+}
+
+static void vmxnet3_reset_interrupt_states(VMXNET3State *s)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(s->interrupt_states); i++) {
+ s->interrupt_states[i].is_asserted = false;
+ s->interrupt_states[i].is_pending = false;
+ s->interrupt_states[i].is_masked = true;
+ }
+}
+
+static void vmxnet3_reset_mac(VMXNET3State *s)
+{
+ memcpy(&s->conf.macaddr.a, &s->perm_mac.a, sizeof(s->perm_mac.a));
+ VMW_CFPRN("MAC address set to: " VMXNET_MF, VMXNET_MA(s->conf.macaddr.a));
+}
+
+static void vmxnet3_deactivate_device(VMXNET3State *s)
+{
+ if (s->device_active) {
+ VMW_CBPRN("Deactivating vmxnet3...");
+ vmxnet_tx_pkt_reset(s->tx_pkt);
+ vmxnet_tx_pkt_uninit(s->tx_pkt);
+ vmxnet_rx_pkt_uninit(s->rx_pkt);
+ s->device_active = false;
+ }
+}
+
+static void vmxnet3_reset(VMXNET3State *s)
+{
+ VMW_CBPRN("Resetting vmxnet3...");
+
+ vmxnet3_deactivate_device(s);
+ vmxnet3_reset_interrupt_states(s);
+ s->drv_shmem = 0;
+ s->tx_sop = true;
+ s->skip_current_tx_pkt = false;
+}
+
+static void vmxnet3_update_rx_mode(VMXNET3State *s)
+{
+ s->rx_mode = VMXNET3_READ_DRV_SHARED32(s->drv_shmem,
+ devRead.rxFilterConf.rxMode);
+ VMW_CFPRN("RX mode: 0x%08X", s->rx_mode);
+}
+
+static void vmxnet3_update_vlan_filters(VMXNET3State *s)
+{
+ int i;
+
+ /* Copy configuration from shared memory */
+ VMXNET3_READ_DRV_SHARED(s->drv_shmem,
+ devRead.rxFilterConf.vfTable,
+ s->vlan_table,
+ sizeof(s->vlan_table));
+
+ /* Invert byte order when needed */
+ for (i = 0; i < ARRAY_SIZE(s->vlan_table); i++) {
+ s->vlan_table[i] = le32_to_cpu(s->vlan_table[i]);
+ }
+
+ /* Dump configuration for debugging purposes */
+ VMW_CFPRN("Configured VLANs:");
+ for (i = 0; i < sizeof(s->vlan_table) * 8; i++) {
+ if (VMXNET3_VFTABLE_ENTRY_IS_SET(s->vlan_table, i)) {
+ VMW_CFPRN("\tVLAN %d is present", i);
+ }
+ }
+}
+
+static void vmxnet3_update_mcast_filters(VMXNET3State *s)
+{
+ uint16_t list_bytes =
+ VMXNET3_READ_DRV_SHARED16(s->drv_shmem,
+ devRead.rxFilterConf.mfTableLen);
+
+ s->mcast_list_len = list_bytes / sizeof(s->mcast_list[0]);
+
+ s->mcast_list = g_realloc(s->mcast_list, list_bytes);
+ if (!s->mcast_list) {
+ if (s->mcast_list_len == 0) {
+ VMW_CFPRN("Current multicast list is empty");
+ } else {
+ VMW_ERPRN("Failed to allocate multicast list of %d elements",
+ s->mcast_list_len);
+ }
+ s->mcast_list_len = 0;
+ } else {
+ int i;
+ hwaddr mcast_list_pa =
+ VMXNET3_READ_DRV_SHARED64(s->drv_shmem,
+ devRead.rxFilterConf.mfTablePA);
+
+ cpu_physical_memory_read(mcast_list_pa, s->mcast_list, list_bytes);
+ VMW_CFPRN("Current multicast list len is %d:", s->mcast_list_len);
+ for (i = 0; i < s->mcast_list_len; i++) {
+ VMW_CFPRN("\t" VMXNET_MF, VMXNET_MA(s->mcast_list[i].a));
+ }
+ }
+}
+
+static void vmxnet3_setup_rx_filtering(VMXNET3State *s)
+{
+ vmxnet3_update_rx_mode(s);
+ vmxnet3_update_vlan_filters(s);
+ vmxnet3_update_mcast_filters(s);
+}
+
+static uint32_t vmxnet3_get_interrupt_config(VMXNET3State *s)
+{
+ uint32_t interrupt_mode = VMXNET3_IT_AUTO | (VMXNET3_IMM_AUTO << 2);
+ VMW_CFPRN("Interrupt config is 0x%X", interrupt_mode);
+ return interrupt_mode;
+}
+
+static void vmxnet3_fill_stats(VMXNET3State *s)
+{
+ int i;
+ for (i = 0; i < s->txq_num; i++) {
+ cpu_physical_memory_write(s->txq_descr[i].tx_stats_pa,
+ &s->txq_descr[i].txq_stats,
+ sizeof(s->txq_descr[i].txq_stats));
+ }
+
+ for (i = 0; i < s->rxq_num; i++) {
+ cpu_physical_memory_write(s->rxq_descr[i].rx_stats_pa,
+ &s->rxq_descr[i].rxq_stats,
+ sizeof(s->rxq_descr[i].rxq_stats));
+ }
+}
+
+static void vmxnet3_adjust_by_guest_type(VMXNET3State *s)
+{
+ struct Vmxnet3_GOSInfo gos;
+
+ VMXNET3_READ_DRV_SHARED(s->drv_shmem, devRead.misc.driverInfo.gos,
+ &gos, sizeof(gos));
+ s->rx_packets_compound =
+ (gos.gosType == VMXNET3_GOS_TYPE_WIN) ? false : true;
+
+ VMW_CFPRN("Guest type specifics: RXCOMPOUND: %d", s->rx_packets_compound);
+}
+
+static void
+vmxnet3_dump_conf_descr(const char *name,
+ struct Vmxnet3_VariableLenConfDesc *pm_descr)
+{
+ VMW_CFPRN("%s descriptor dump: Version %u, Length %u",
+ name, pm_descr->confVer, pm_descr->confLen);
+
+};
+
+static void vmxnet3_update_pm_state(VMXNET3State *s)
+{
+ struct Vmxnet3_VariableLenConfDesc pm_descr;
+
+ pm_descr.confLen =
+ VMXNET3_READ_DRV_SHARED32(s->drv_shmem, devRead.pmConfDesc.confLen);
+ pm_descr.confVer =
+ VMXNET3_READ_DRV_SHARED32(s->drv_shmem, devRead.pmConfDesc.confVer);
+ pm_descr.confPA =
+ VMXNET3_READ_DRV_SHARED64(s->drv_shmem, devRead.pmConfDesc.confPA);
+
+ vmxnet3_dump_conf_descr("PM State", &pm_descr);
+}
+
+static void vmxnet3_update_features(VMXNET3State *s)
+{
+ uint32_t guest_features;
+ int rxcso_supported;
+
+ guest_features = VMXNET3_READ_DRV_SHARED32(s->drv_shmem,
+ devRead.misc.uptFeatures);
+
+ rxcso_supported = VMXNET_FLAG_IS_SET(guest_features, UPT1_F_RXCSUM);
+ s->rx_vlan_stripping = VMXNET_FLAG_IS_SET(guest_features, UPT1_F_RXVLAN);
+ s->lro_supported = VMXNET_FLAG_IS_SET(guest_features, UPT1_F_LRO);
+
+ VMW_CFPRN("Features configuration: LRO: %d, RXCSUM: %d, VLANSTRIP: %d",
+ s->lro_supported, rxcso_supported,
+ s->rx_vlan_stripping);
+ if (s->peer_has_vhdr) {
+ qemu_set_offload(qemu_get_queue(s->nic)->peer,
+ rxcso_supported,
+ s->lro_supported,
+ s->lro_supported,
+ 0,
+ 0);
+ }
+}
+
+static bool vmxnet3_verify_intx(VMXNET3State *s, int intx)
+{
+ return s->msix_used || s->msi_used || (intx ==
+ (pci_get_byte(s->parent_obj.config + PCI_INTERRUPT_PIN) - 1));
+}
+
+static void vmxnet3_validate_interrupt_idx(bool is_msix, int idx)
+{
+ int max_ints = is_msix ? VMXNET3_MAX_INTRS : VMXNET3_MAX_NMSIX_INTRS;
+ if (idx >= max_ints) {
+ hw_error("Bad interrupt index: %d\n", idx);
+ }
+}
+
+static void vmxnet3_validate_interrupts(VMXNET3State *s)
+{
+ int i;
+
+ VMW_CFPRN("Verifying event interrupt index (%d)", s->event_int_idx);
+ vmxnet3_validate_interrupt_idx(s->msix_used, s->event_int_idx);
+
+ for (i = 0; i < s->txq_num; i++) {
+ int idx = s->txq_descr[i].intr_idx;
+ VMW_CFPRN("Verifying TX queue %d interrupt index (%d)", i, idx);
+ vmxnet3_validate_interrupt_idx(s->msix_used, idx);
+ }
+
+ for (i = 0; i < s->rxq_num; i++) {
+ int idx = s->rxq_descr[i].intr_idx;
+ VMW_CFPRN("Verifying RX queue %d interrupt index (%d)", i, idx);
+ vmxnet3_validate_interrupt_idx(s->msix_used, idx);
+ }
+}
+
+static void vmxnet3_validate_queues(VMXNET3State *s)
+{
+ /*
+ * txq_num and rxq_num are total number of queues
+ * configured by guest. These numbers must not
+ * exceed corresponding maximal values.
+ */
+
+ if (s->txq_num > VMXNET3_DEVICE_MAX_TX_QUEUES) {
+ hw_error("Bad TX queues number: %d\n", s->txq_num);
+ }
+
+ if (s->rxq_num > VMXNET3_DEVICE_MAX_RX_QUEUES) {
+ hw_error("Bad RX queues number: %d\n", s->rxq_num);
+ }
+}
+
+static void vmxnet3_activate_device(VMXNET3State *s)
+{
+ int i;
+ static const uint32_t VMXNET3_DEF_TX_THRESHOLD = 1;
+ hwaddr qdescr_table_pa;
+ uint64_t pa;
+ uint32_t size;
+
+ /* Verify configuration consistency */
+ if (!vmxnet3_verify_driver_magic(s->drv_shmem)) {
+ VMW_ERPRN("Device configuration received from driver is invalid");
+ return;
+ }
+
+ /* Verify if device is active */
+ if (s->device_active) {
+ VMW_CFPRN("Vmxnet3 device is active");
+ return;
+ }
+
+ vmxnet3_adjust_by_guest_type(s);
+ vmxnet3_update_features(s);
+ vmxnet3_update_pm_state(s);
+ vmxnet3_setup_rx_filtering(s);
+ /* Cache fields from shared memory */
+ s->mtu = VMXNET3_READ_DRV_SHARED32(s->drv_shmem, devRead.misc.mtu);
+ VMW_CFPRN("MTU is %u", s->mtu);
+
+ s->max_rx_frags =
+ VMXNET3_READ_DRV_SHARED16(s->drv_shmem, devRead.misc.maxNumRxSG);
+
+ if (s->max_rx_frags == 0) {
+ s->max_rx_frags = 1;
+ }
+
+ VMW_CFPRN("Max RX fragments is %u", s->max_rx_frags);
+
+ s->event_int_idx =
+ VMXNET3_READ_DRV_SHARED8(s->drv_shmem, devRead.intrConf.eventIntrIdx);
+ assert(vmxnet3_verify_intx(s, s->event_int_idx));
+ VMW_CFPRN("Events interrupt line is %u", s->event_int_idx);
+
+ s->auto_int_masking =
+ VMXNET3_READ_DRV_SHARED8(s->drv_shmem, devRead.intrConf.autoMask);
+ VMW_CFPRN("Automatic interrupt masking is %d", (int)s->auto_int_masking);
+
+ s->txq_num =
+ VMXNET3_READ_DRV_SHARED8(s->drv_shmem, devRead.misc.numTxQueues);
+ s->rxq_num =
+ VMXNET3_READ_DRV_SHARED8(s->drv_shmem, devRead.misc.numRxQueues);
+
+ VMW_CFPRN("Number of TX/RX queues %u/%u", s->txq_num, s->rxq_num);
+ vmxnet3_validate_queues(s);
+
+ qdescr_table_pa =
+ VMXNET3_READ_DRV_SHARED64(s->drv_shmem, devRead.misc.queueDescPA);
+ VMW_CFPRN("TX queues descriptors table is at 0x%" PRIx64, qdescr_table_pa);
+
+ /*
+ * Worst-case scenario is a packet that holds all TX rings space so
+ * we calculate total size of all TX rings for max TX fragments number
+ */
+ s->max_tx_frags = 0;
+
+ /* TX queues */
+ for (i = 0; i < s->txq_num; i++) {
+ hwaddr qdescr_pa =
+ qdescr_table_pa + i * sizeof(struct Vmxnet3_TxQueueDesc);
+
+ /* Read interrupt number for this TX queue */
+ s->txq_descr[i].intr_idx =
+ VMXNET3_READ_TX_QUEUE_DESCR8(qdescr_pa, conf.intrIdx);
+ assert(vmxnet3_verify_intx(s, s->txq_descr[i].intr_idx));
+
+ VMW_CFPRN("TX Queue %d interrupt: %d", i, s->txq_descr[i].intr_idx);
+
+ /* Read rings memory locations for TX queues */
+ pa = VMXNET3_READ_TX_QUEUE_DESCR64(qdescr_pa, conf.txRingBasePA);
+ size = VMXNET3_READ_TX_QUEUE_DESCR32(qdescr_pa, conf.txRingSize);
+
+ vmxnet3_ring_init(&s->txq_descr[i].tx_ring, pa, size,
+ sizeof(struct Vmxnet3_TxDesc), false);
+ VMXNET3_RING_DUMP(VMW_CFPRN, "TX", i, &s->txq_descr[i].tx_ring);
+
+ s->max_tx_frags += size;
+
+ /* TXC ring */
+ pa = VMXNET3_READ_TX_QUEUE_DESCR64(qdescr_pa, conf.compRingBasePA);
+ size = VMXNET3_READ_TX_QUEUE_DESCR32(qdescr_pa, conf.compRingSize);
+ vmxnet3_ring_init(&s->txq_descr[i].comp_ring, pa, size,
+ sizeof(struct Vmxnet3_TxCompDesc), true);
+ VMXNET3_RING_DUMP(VMW_CFPRN, "TXC", i, &s->txq_descr[i].comp_ring);
+
+ s->txq_descr[i].tx_stats_pa =
+ qdescr_pa + offsetof(struct Vmxnet3_TxQueueDesc, stats);
+
+ memset(&s->txq_descr[i].txq_stats, 0,
+ sizeof(s->txq_descr[i].txq_stats));
+
+ /* Fill device-managed parameters for queues */
+ VMXNET3_WRITE_TX_QUEUE_DESCR32(qdescr_pa,
+ ctrl.txThreshold,
+ VMXNET3_DEF_TX_THRESHOLD);
+ }
+
+ /* Preallocate TX packet wrapper */
+ VMW_CFPRN("Max TX fragments is %u", s->max_tx_frags);
+ vmxnet_tx_pkt_init(&s->tx_pkt, s->max_tx_frags, s->peer_has_vhdr);
+ vmxnet_rx_pkt_init(&s->rx_pkt, s->peer_has_vhdr);
+
+ /* Read rings memory locations for RX queues */
+ for (i = 0; i < s->rxq_num; i++) {
+ int j;
+ hwaddr qd_pa =
+ qdescr_table_pa + s->txq_num * sizeof(struct Vmxnet3_TxQueueDesc) +
+ i * sizeof(struct Vmxnet3_RxQueueDesc);
+
+ /* Read interrupt number for this RX queue */
+ s->rxq_descr[i].intr_idx =
+ VMXNET3_READ_TX_QUEUE_DESCR8(qd_pa, conf.intrIdx);
+ assert(vmxnet3_verify_intx(s, s->rxq_descr[i].intr_idx));
+
+ VMW_CFPRN("RX Queue %d interrupt: %d", i, s->rxq_descr[i].intr_idx);
+
+ /* Read rings memory locations */
+ for (j = 0; j < VMXNET3_RX_RINGS_PER_QUEUE; j++) {
+ /* RX rings */
+ pa = VMXNET3_READ_RX_QUEUE_DESCR64(qd_pa, conf.rxRingBasePA[j]);
+ size = VMXNET3_READ_RX_QUEUE_DESCR32(qd_pa, conf.rxRingSize[j]);
+ vmxnet3_ring_init(&s->rxq_descr[i].rx_ring[j], pa, size,
+ sizeof(struct Vmxnet3_RxDesc), false);
+ VMW_CFPRN("RX queue %d:%d: Base: %" PRIx64 ", Size: %d",
+ i, j, pa, size);
+ }
+
+ /* RXC ring */
+ pa = VMXNET3_READ_RX_QUEUE_DESCR64(qd_pa, conf.compRingBasePA);
+ size = VMXNET3_READ_RX_QUEUE_DESCR32(qd_pa, conf.compRingSize);
+ vmxnet3_ring_init(&s->rxq_descr[i].comp_ring, pa, size,
+ sizeof(struct Vmxnet3_RxCompDesc), true);
+ VMW_CFPRN("RXC queue %d: Base: %" PRIx64 ", Size: %d", i, pa, size);
+
+ s->rxq_descr[i].rx_stats_pa =
+ qd_pa + offsetof(struct Vmxnet3_RxQueueDesc, stats);
+ memset(&s->rxq_descr[i].rxq_stats, 0,
+ sizeof(s->rxq_descr[i].rxq_stats));
+ }
+
+ vmxnet3_validate_interrupts(s);
+
+ /* Make sure everything is in place before device activation */
+ smp_wmb();
+
+ vmxnet3_reset_mac(s);
+
+ s->device_active = true;
+}
+
+static void vmxnet3_handle_command(VMXNET3State *s, uint64_t cmd)
+{
+ s->last_command = cmd;
+
+ switch (cmd) {
+ case VMXNET3_CMD_GET_PERM_MAC_HI:
+ VMW_CBPRN("Set: Get upper part of permanent MAC");
+ break;
+
+ case VMXNET3_CMD_GET_PERM_MAC_LO:
+ VMW_CBPRN("Set: Get lower part of permanent MAC");
+ break;
+
+ case VMXNET3_CMD_GET_STATS:
+ VMW_CBPRN("Set: Get device statistics");
+ vmxnet3_fill_stats(s);
+ break;
+
+ case VMXNET3_CMD_ACTIVATE_DEV:
+ VMW_CBPRN("Set: Activating vmxnet3 device");
+ vmxnet3_activate_device(s);
+ break;
+
+ case VMXNET3_CMD_UPDATE_RX_MODE:
+ VMW_CBPRN("Set: Update rx mode");
+ vmxnet3_update_rx_mode(s);
+ break;
+
+ case VMXNET3_CMD_UPDATE_VLAN_FILTERS:
+ VMW_CBPRN("Set: Update VLAN filters");
+ vmxnet3_update_vlan_filters(s);
+ break;
+
+ case VMXNET3_CMD_UPDATE_MAC_FILTERS:
+ VMW_CBPRN("Set: Update MAC filters");
+ vmxnet3_update_mcast_filters(s);
+ break;
+
+ case VMXNET3_CMD_UPDATE_FEATURE:
+ VMW_CBPRN("Set: Update features");
+ vmxnet3_update_features(s);
+ break;
+
+ case VMXNET3_CMD_UPDATE_PMCFG:
+ VMW_CBPRN("Set: Update power management config");
+ vmxnet3_update_pm_state(s);
+ break;
+
+ case VMXNET3_CMD_GET_LINK:
+ VMW_CBPRN("Set: Get link");
+ break;
+
+ case VMXNET3_CMD_RESET_DEV:
+ VMW_CBPRN("Set: Reset device");
+ vmxnet3_reset(s);
+ break;
+
+ case VMXNET3_CMD_QUIESCE_DEV:
+ VMW_CBPRN("Set: VMXNET3_CMD_QUIESCE_DEV - deactivate the device");
+ vmxnet3_deactivate_device(s);
+ break;
+
+ case VMXNET3_CMD_GET_CONF_INTR:
+ VMW_CBPRN("Set: VMXNET3_CMD_GET_CONF_INTR - interrupt configuration");
+ break;
+
+ default:
+ VMW_CBPRN("Received unknown command: %" PRIx64, cmd);
+ break;
+ }
+}
+
+static uint64_t vmxnet3_get_command_status(VMXNET3State *s)
+{
+ uint64_t ret;
+
+ switch (s->last_command) {
+ case VMXNET3_CMD_ACTIVATE_DEV:
+ ret = (s->device_active) ? 0 : -1;
+ VMW_CFPRN("Device active: %" PRIx64, ret);
+ break;
+
+ case VMXNET3_CMD_RESET_DEV:
+ case VMXNET3_CMD_QUIESCE_DEV:
+ case VMXNET3_CMD_GET_QUEUE_STATUS:
+ ret = 0;
+ break;
+
+ case VMXNET3_CMD_GET_LINK:
+ ret = s->link_status_and_speed;
+ VMW_CFPRN("Link and speed: %" PRIx64, ret);
+ break;
+
+ case VMXNET3_CMD_GET_PERM_MAC_LO:
+ ret = vmxnet3_get_mac_low(&s->perm_mac);
+ break;
+
+ case VMXNET3_CMD_GET_PERM_MAC_HI:
+ ret = vmxnet3_get_mac_high(&s->perm_mac);
+ break;
+
+ case VMXNET3_CMD_GET_CONF_INTR:
+ ret = vmxnet3_get_interrupt_config(s);
+ break;
+
+ default:
+ VMW_WRPRN("Received request for unknown command: %x", s->last_command);
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+static void vmxnet3_set_events(VMXNET3State *s, uint32_t val)
+{
+ uint32_t events;
+
+ VMW_CBPRN("Setting events: 0x%x", val);
+ events = VMXNET3_READ_DRV_SHARED32(s->drv_shmem, ecr) | val;
+ VMXNET3_WRITE_DRV_SHARED32(s->drv_shmem, ecr, events);
+}
+
+static void vmxnet3_ack_events(VMXNET3State *s, uint32_t val)
+{
+ uint32_t events;
+
+ VMW_CBPRN("Clearing events: 0x%x", val);
+ events = VMXNET3_READ_DRV_SHARED32(s->drv_shmem, ecr) & ~val;
+ VMXNET3_WRITE_DRV_SHARED32(s->drv_shmem, ecr, events);
+}
+
+static void
+vmxnet3_io_bar1_write(void *opaque,
+ hwaddr addr,
+ uint64_t val,
+ unsigned size)
+{
+ VMXNET3State *s = opaque;
+
+ switch (addr) {
+ /* Vmxnet3 Revision Report Selection */
+ case VMXNET3_REG_VRRS:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_VRRS] = %" PRIx64 ", size %d",
+ val, size);
+ break;
+
+ /* UPT Version Report Selection */
+ case VMXNET3_REG_UVRS:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_UVRS] = %" PRIx64 ", size %d",
+ val, size);
+ break;
+
+ /* Driver Shared Address Low */
+ case VMXNET3_REG_DSAL:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_DSAL] = %" PRIx64 ", size %d",
+ val, size);
+ /*
+ * Guest driver will first write the low part of the shared
+ * memory address. We save it to temp variable and set the
+ * shared address only after we get the high part
+ */
+ if (val == 0) {
+ vmxnet3_deactivate_device(s);
+ }
+ s->temp_shared_guest_driver_memory = val;
+ s->drv_shmem = 0;
+ break;
+
+ /* Driver Shared Address High */
+ case VMXNET3_REG_DSAH:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_DSAH] = %" PRIx64 ", size %d",
+ val, size);
+ /*
+ * Set the shared memory between guest driver and device.
+ * We already should have low address part.
+ */
+ s->drv_shmem = s->temp_shared_guest_driver_memory | (val << 32);
+ break;
+
+ /* Command */
+ case VMXNET3_REG_CMD:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_CMD] = %" PRIx64 ", size %d",
+ val, size);
+ vmxnet3_handle_command(s, val);
+ break;
+
+ /* MAC Address Low */
+ case VMXNET3_REG_MACL:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_MACL] = %" PRIx64 ", size %d",
+ val, size);
+ s->temp_mac = val;
+ break;
+
+ /* MAC Address High */
+ case VMXNET3_REG_MACH:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_MACH] = %" PRIx64 ", size %d",
+ val, size);
+ vmxnet3_set_variable_mac(s, val, s->temp_mac);
+ break;
+
+ /* Interrupt Cause Register */
+ case VMXNET3_REG_ICR:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_ICR] = %" PRIx64 ", size %d",
+ val, size);
+ g_assert_not_reached();
+ break;
+
+ /* Event Cause Register */
+ case VMXNET3_REG_ECR:
+ VMW_CBPRN("Write BAR1 [VMXNET3_REG_ECR] = %" PRIx64 ", size %d",
+ val, size);
+ vmxnet3_ack_events(s, val);
+ break;
+
+ default:
+ VMW_CBPRN("Unknown Write to BAR1 [%" PRIx64 "] = %" PRIx64 ", size %d",
+ addr, val, size);
+ break;
+ }
+}
+
+static uint64_t
+vmxnet3_io_bar1_read(void *opaque, hwaddr addr, unsigned size)
+{
+ VMXNET3State *s = opaque;
+ uint64_t ret = 0;
+
+ switch (addr) {
+ /* Vmxnet3 Revision Report Selection */
+ case VMXNET3_REG_VRRS:
+ VMW_CBPRN("Read BAR1 [VMXNET3_REG_VRRS], size %d", size);
+ ret = VMXNET3_DEVICE_REVISION;
+ break;
+
+ /* UPT Version Report Selection */
+ case VMXNET3_REG_UVRS:
+ VMW_CBPRN("Read BAR1 [VMXNET3_REG_UVRS], size %d", size);
+ ret = VMXNET3_DEVICE_VERSION;
+ break;
+
+ /* Command */
+ case VMXNET3_REG_CMD:
+ VMW_CBPRN("Read BAR1 [VMXNET3_REG_CMD], size %d", size);
+ ret = vmxnet3_get_command_status(s);
+ break;
+
+ /* MAC Address Low */
+ case VMXNET3_REG_MACL:
+ VMW_CBPRN("Read BAR1 [VMXNET3_REG_MACL], size %d", size);
+ ret = vmxnet3_get_mac_low(&s->conf.macaddr);
+ break;
+
+ /* MAC Address High */
+ case VMXNET3_REG_MACH:
+ VMW_CBPRN("Read BAR1 [VMXNET3_REG_MACH], size %d", size);
+ ret = vmxnet3_get_mac_high(&s->conf.macaddr);
+ break;
+
+ /*
+ * Interrupt Cause Register
+ * Used for legacy interrupts only so interrupt index always 0
+ */
+ case VMXNET3_REG_ICR:
+ VMW_CBPRN("Read BAR1 [VMXNET3_REG_ICR], size %d", size);
+ if (vmxnet3_interrupt_asserted(s, 0)) {
+ vmxnet3_clear_interrupt(s, 0);
+ ret = true;
+ } else {
+ ret = false;
+ }
+ break;
+
+ default:
+ VMW_CBPRN("Unknow read BAR1[%" PRIx64 "], %d bytes", addr, size);
+ break;
+ }
+
+ return ret;
+}
+
+static int
+vmxnet3_can_receive(NetClientState *nc)
+{
+ VMXNET3State *s = qemu_get_nic_opaque(nc);
+ return s->device_active &&
+ VMXNET_FLAG_IS_SET(s->link_status_and_speed, VMXNET3_LINK_STATUS_UP);
+}
+
+static inline bool
+vmxnet3_is_registered_vlan(VMXNET3State *s, const void *data)
+{
+ uint16_t vlan_tag = eth_get_pkt_tci(data) & VLAN_VID_MASK;
+ if (IS_SPECIAL_VLAN_ID(vlan_tag)) {
+ return true;
+ }
+
+ return VMXNET3_VFTABLE_ENTRY_IS_SET(s->vlan_table, vlan_tag);
+}
+
+static bool
+vmxnet3_is_allowed_mcast_group(VMXNET3State *s, const uint8_t *group_mac)
+{
+ int i;
+ for (i = 0; i < s->mcast_list_len; i++) {
+ if (!memcmp(group_mac, s->mcast_list[i].a, sizeof(s->mcast_list[i]))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool
+vmxnet3_rx_filter_may_indicate(VMXNET3State *s, const void *data,
+ size_t size)
+{
+ struct eth_header *ehdr = PKT_GET_ETH_HDR(data);
+
+ if (VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_PROMISC)) {
+ return true;
+ }
+
+ if (!vmxnet3_is_registered_vlan(s, data)) {
+ return false;
+ }
+
+ switch (vmxnet_rx_pkt_get_packet_type(s->rx_pkt)) {
+ case ETH_PKT_UCAST:
+ if (!VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_UCAST)) {
+ return false;
+ }
+ if (memcmp(s->conf.macaddr.a, ehdr->h_dest, ETH_ALEN)) {
+ return false;
+ }
+ break;
+
+ case ETH_PKT_BCAST:
+ if (!VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_BCAST)) {
+ return false;
+ }
+ break;
+
+ case ETH_PKT_MCAST:
+ if (VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_ALL_MULTI)) {
+ return true;
+ }
+ if (!VMXNET_FLAG_IS_SET(s->rx_mode, VMXNET3_RXM_MCAST)) {
+ return false;
+ }
+ if (!vmxnet3_is_allowed_mcast_group(s, ehdr->h_dest)) {
+ return false;
+ }
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ return true;
+}
+
+static ssize_t
+vmxnet3_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ VMXNET3State *s = qemu_get_nic_opaque(nc);
+ size_t bytes_indicated;
+ uint8_t min_buf[MIN_BUF_SIZE];
+
+ if (!vmxnet3_can_receive(nc)) {
+ VMW_PKPRN("Cannot receive now");
+ return -1;
+ }
+
+ if (s->peer_has_vhdr) {
+ vmxnet_rx_pkt_set_vhdr(s->rx_pkt, (struct virtio_net_hdr *)buf);
+ buf += sizeof(struct virtio_net_hdr);
+ size -= sizeof(struct virtio_net_hdr);
+ }
+
+ /* Pad to minimum Ethernet frame length */
+ if (size < sizeof(min_buf)) {
+ memcpy(min_buf, buf, size);
+ memset(&min_buf[size], 0, sizeof(min_buf) - size);
+ buf = min_buf;
+ size = sizeof(min_buf);
+ }
+
+ vmxnet_rx_pkt_set_packet_type(s->rx_pkt,
+ get_eth_packet_type(PKT_GET_ETH_HDR(buf)));
+
+ if (vmxnet3_rx_filter_may_indicate(s, buf, size)) {
+ vmxnet_rx_pkt_set_protocols(s->rx_pkt, buf, size);
+ vmxnet3_rx_need_csum_calculate(s->rx_pkt, buf, size);
+ vmxnet_rx_pkt_attach_data(s->rx_pkt, buf, size, s->rx_vlan_stripping);
+ bytes_indicated = vmxnet3_indicate_packet(s) ? size : -1;
+ if (bytes_indicated < size) {
+ VMW_PKPRN("RX: %lu of %lu bytes indicated", bytes_indicated, size);
+ }
+ } else {
+ VMW_PKPRN("Packet dropped by RX filter");
+ bytes_indicated = size;
+ }
+
+ assert(size > 0);
+ assert(bytes_indicated != 0);
+ return bytes_indicated;
+}
+
+static void vmxnet3_set_link_status(NetClientState *nc)
+{
+ VMXNET3State *s = qemu_get_nic_opaque(nc);
+
+ if (nc->link_down) {
+ s->link_status_and_speed &= ~VMXNET3_LINK_STATUS_UP;
+ } else {
+ s->link_status_and_speed |= VMXNET3_LINK_STATUS_UP;
+ }
+
+ vmxnet3_set_events(s, VMXNET3_ECR_LINK);
+ vmxnet3_trigger_interrupt(s, s->event_int_idx);
+}
+
+static NetClientInfo net_vmxnet3_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = vmxnet3_receive,
+ .link_status_changed = vmxnet3_set_link_status,
+};
+
+static bool vmxnet3_peer_has_vnet_hdr(VMXNET3State *s)
+{
+ NetClientState *nc = qemu_get_queue(s->nic);
+
+ if (qemu_has_vnet_hdr(nc->peer)) {
+ return true;
+ }
+
+ VMW_WRPRN("Peer has no virtio extension. Task offloads will be emulated.");
+ return false;
+}
+
+static void vmxnet3_net_uninit(VMXNET3State *s)
+{
+ g_free(s->mcast_list);
+ vmxnet3_deactivate_device(s);
+ qemu_del_nic(s->nic);
+}
+
+static void vmxnet3_net_init(VMXNET3State *s)
+{
+ DeviceState *d = DEVICE(s);
+
+ VMW_CBPRN("vmxnet3_net_init called...");
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+
+ /* Windows guest will query the address that was set on init */
+ memcpy(&s->perm_mac.a, &s->conf.macaddr.a, sizeof(s->perm_mac.a));
+
+ s->mcast_list = NULL;
+ s->mcast_list_len = 0;
+
+ s->link_status_and_speed = VMXNET3_LINK_SPEED | VMXNET3_LINK_STATUS_UP;
+
+ VMW_CFPRN("Permanent MAC: " MAC_FMT, MAC_ARG(s->perm_mac.a));
+
+ s->nic = qemu_new_nic(&net_vmxnet3_info, &s->conf,
+ object_get_typename(OBJECT(s)),
+ d->id, s);
+
+ s->peer_has_vhdr = vmxnet3_peer_has_vnet_hdr(s);
+ s->tx_sop = true;
+ s->skip_current_tx_pkt = false;
+ s->tx_pkt = NULL;
+ s->rx_pkt = NULL;
+ s->rx_vlan_stripping = false;
+ s->lro_supported = false;
+
+ if (s->peer_has_vhdr) {
+ qemu_set_vnet_hdr_len(qemu_get_queue(s->nic)->peer,
+ sizeof(struct virtio_net_hdr));
+
+ qemu_using_vnet_hdr(qemu_get_queue(s->nic)->peer, 1);
+ }
+
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+}
+
+static void
+vmxnet3_unuse_msix_vectors(VMXNET3State *s, int num_vectors)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int i;
+ for (i = 0; i < num_vectors; i++) {
+ msix_vector_unuse(d, i);
+ }
+}
+
+static bool
+vmxnet3_use_msix_vectors(VMXNET3State *s, int num_vectors)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int i;
+ for (i = 0; i < num_vectors; i++) {
+ int res = msix_vector_use(d, i);
+ if (0 > res) {
+ VMW_WRPRN("Failed to use MSI-X vector %d, error %d", i, res);
+ vmxnet3_unuse_msix_vectors(s, i);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+vmxnet3_init_msix(VMXNET3State *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int res = msix_init(d, VMXNET3_MAX_INTRS,
+ &s->msix_bar,
+ VMXNET3_MSIX_BAR_IDX, VMXNET3_OFF_MSIX_TABLE,
+ &s->msix_bar,
+ VMXNET3_MSIX_BAR_IDX, VMXNET3_OFF_MSIX_PBA,
+ 0);
+
+ if (0 > res) {
+ VMW_WRPRN("Failed to initialize MSI-X, error %d", res);
+ s->msix_used = false;
+ } else {
+ if (!vmxnet3_use_msix_vectors(s, VMXNET3_MAX_INTRS)) {
+ VMW_WRPRN("Failed to use MSI-X vectors, error %d", res);
+ msix_uninit(d, &s->msix_bar, &s->msix_bar);
+ s->msix_used = false;
+ } else {
+ s->msix_used = true;
+ }
+ }
+ return s->msix_used;
+}
+
+static void
+vmxnet3_cleanup_msix(VMXNET3State *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ if (s->msix_used) {
+ vmxnet3_unuse_msix_vectors(s, VMXNET3_MAX_INTRS);
+ msix_uninit(d, &s->msix_bar, &s->msix_bar);
+ }
+}
+
+#define VMXNET3_MSI_OFFSET (0x50)
+#define VMXNET3_USE_64BIT (true)
+#define VMXNET3_PER_VECTOR_MASK (false)
+
+static bool
+vmxnet3_init_msi(VMXNET3State *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int res;
+
+ res = msi_init(d, VMXNET3_MSI_OFFSET, VMXNET3_MAX_NMSIX_INTRS,
+ VMXNET3_USE_64BIT, VMXNET3_PER_VECTOR_MASK);
+ if (0 > res) {
+ VMW_WRPRN("Failed to initialize MSI, error %d", res);
+ s->msi_used = false;
+ } else {
+ s->msi_used = true;
+ }
+
+ return s->msi_used;
+}
+
+static void
+vmxnet3_cleanup_msi(VMXNET3State *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ if (s->msi_used) {
+ msi_uninit(d);
+ }
+}
+
+static void
+vmxnet3_msix_save(QEMUFile *f, void *opaque)
+{
+ PCIDevice *d = PCI_DEVICE(opaque);
+ msix_save(d, f);
+}
+
+static int
+vmxnet3_msix_load(QEMUFile *f, void *opaque, int version_id)
+{
+ PCIDevice *d = PCI_DEVICE(opaque);
+ msix_load(d, f);
+ return 0;
+}
+
+static const MemoryRegionOps b0_ops = {
+ .read = vmxnet3_io_bar0_read,
+ .write = vmxnet3_io_bar0_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps b1_ops = {
+ .read = vmxnet3_io_bar1_read,
+ .write = vmxnet3_io_bar1_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void vmxnet3_pci_realize(PCIDevice *pci_dev, Error **errp)
+{
+ DeviceState *dev = DEVICE(pci_dev);
+ VMXNET3State *s = VMXNET3(pci_dev);
+
+ VMW_CBPRN("Starting init...");
+
+ memory_region_init_io(&s->bar0, OBJECT(s), &b0_ops, s,
+ "vmxnet3-b0", VMXNET3_PT_REG_SIZE);
+ pci_register_bar(pci_dev, VMXNET3_BAR0_IDX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
+
+ memory_region_init_io(&s->bar1, OBJECT(s), &b1_ops, s,
+ "vmxnet3-b1", VMXNET3_VD_REG_SIZE);
+ pci_register_bar(pci_dev, VMXNET3_BAR1_IDX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar1);
+
+ memory_region_init(&s->msix_bar, OBJECT(s), "vmxnet3-msix-bar",
+ VMXNET3_MSIX_BAR_SIZE);
+ pci_register_bar(pci_dev, VMXNET3_MSIX_BAR_IDX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &s->msix_bar);
+
+ vmxnet3_reset_interrupt_states(s);
+
+ /* Interrupt pin A */
+ pci_dev->config[PCI_INTERRUPT_PIN] = 0x01;
+
+ if (!vmxnet3_init_msix(s)) {
+ VMW_WRPRN("Failed to initialize MSI-X, configuration is inconsistent.");
+ }
+
+ if (!vmxnet3_init_msi(s)) {
+ VMW_WRPRN("Failed to initialize MSI, configuration is inconsistent.");
+ }
+
+ vmxnet3_net_init(s);
+
+ register_savevm(dev, "vmxnet3-msix", -1, 1,
+ vmxnet3_msix_save, vmxnet3_msix_load, s);
+}
+
+static void vmxnet3_instance_init(Object *obj)
+{
+ VMXNET3State *s = VMXNET3(obj);
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ DEVICE(obj), NULL);
+}
+
+static void vmxnet3_pci_uninit(PCIDevice *pci_dev)
+{
+ DeviceState *dev = DEVICE(pci_dev);
+ VMXNET3State *s = VMXNET3(pci_dev);
+
+ VMW_CBPRN("Starting uninit...");
+
+ unregister_savevm(dev, "vmxnet3-msix", s);
+
+ vmxnet3_net_uninit(s);
+
+ vmxnet3_cleanup_msix(s);
+
+ vmxnet3_cleanup_msi(s);
+}
+
+static void vmxnet3_qdev_reset(DeviceState *dev)
+{
+ PCIDevice *d = PCI_DEVICE(dev);
+ VMXNET3State *s = VMXNET3(d);
+
+ VMW_CBPRN("Starting QDEV reset...");
+ vmxnet3_reset(s);
+}
+
+static bool vmxnet3_mc_list_needed(void *opaque)
+{
+ return true;
+}
+
+static int vmxnet3_mcast_list_pre_load(void *opaque)
+{
+ VMXNET3State *s = opaque;
+
+ s->mcast_list = g_malloc(s->mcast_list_buff_size);
+
+ return 0;
+}
+
+
+static void vmxnet3_pre_save(void *opaque)
+{
+ VMXNET3State *s = opaque;
+
+ s->mcast_list_buff_size = s->mcast_list_len * sizeof(MACAddr);
+}
+
+static const VMStateDescription vmxstate_vmxnet3_mcast_list = {
+ .name = "vmxnet3/mcast_list",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_load = vmxnet3_mcast_list_pre_load,
+ .needed = vmxnet3_mc_list_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_VBUFFER_UINT32(mcast_list, VMXNET3State, 0, NULL, 0,
+ mcast_list_buff_size),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void vmxnet3_get_ring_from_file(QEMUFile *f, Vmxnet3Ring *r)
+{
+ r->pa = qemu_get_be64(f);
+ r->size = qemu_get_be32(f);
+ r->cell_size = qemu_get_be32(f);
+ r->next = qemu_get_be32(f);
+ r->gen = qemu_get_byte(f);
+}
+
+static void vmxnet3_put_ring_to_file(QEMUFile *f, Vmxnet3Ring *r)
+{
+ qemu_put_be64(f, r->pa);
+ qemu_put_be32(f, r->size);
+ qemu_put_be32(f, r->cell_size);
+ qemu_put_be32(f, r->next);
+ qemu_put_byte(f, r->gen);
+}
+
+static void vmxnet3_get_tx_stats_from_file(QEMUFile *f,
+ struct UPT1_TxStats *tx_stat)
+{
+ tx_stat->TSOPktsTxOK = qemu_get_be64(f);
+ tx_stat->TSOBytesTxOK = qemu_get_be64(f);
+ tx_stat->ucastPktsTxOK = qemu_get_be64(f);
+ tx_stat->ucastBytesTxOK = qemu_get_be64(f);
+ tx_stat->mcastPktsTxOK = qemu_get_be64(f);
+ tx_stat->mcastBytesTxOK = qemu_get_be64(f);
+ tx_stat->bcastPktsTxOK = qemu_get_be64(f);
+ tx_stat->bcastBytesTxOK = qemu_get_be64(f);
+ tx_stat->pktsTxError = qemu_get_be64(f);
+ tx_stat->pktsTxDiscard = qemu_get_be64(f);
+}
+
+static void vmxnet3_put_tx_stats_to_file(QEMUFile *f,
+ struct UPT1_TxStats *tx_stat)
+{
+ qemu_put_be64(f, tx_stat->TSOPktsTxOK);
+ qemu_put_be64(f, tx_stat->TSOBytesTxOK);
+ qemu_put_be64(f, tx_stat->ucastPktsTxOK);
+ qemu_put_be64(f, tx_stat->ucastBytesTxOK);
+ qemu_put_be64(f, tx_stat->mcastPktsTxOK);
+ qemu_put_be64(f, tx_stat->mcastBytesTxOK);
+ qemu_put_be64(f, tx_stat->bcastPktsTxOK);
+ qemu_put_be64(f, tx_stat->bcastBytesTxOK);
+ qemu_put_be64(f, tx_stat->pktsTxError);
+ qemu_put_be64(f, tx_stat->pktsTxDiscard);
+}
+
+static int vmxnet3_get_txq_descr(QEMUFile *f, void *pv, size_t size)
+{
+ Vmxnet3TxqDescr *r = pv;
+
+ vmxnet3_get_ring_from_file(f, &r->tx_ring);
+ vmxnet3_get_ring_from_file(f, &r->comp_ring);
+ r->intr_idx = qemu_get_byte(f);
+ r->tx_stats_pa = qemu_get_be64(f);
+
+ vmxnet3_get_tx_stats_from_file(f, &r->txq_stats);
+
+ return 0;
+}
+
+static void vmxnet3_put_txq_descr(QEMUFile *f, void *pv, size_t size)
+{
+ Vmxnet3TxqDescr *r = pv;
+
+ vmxnet3_put_ring_to_file(f, &r->tx_ring);
+ vmxnet3_put_ring_to_file(f, &r->comp_ring);
+ qemu_put_byte(f, r->intr_idx);
+ qemu_put_be64(f, r->tx_stats_pa);
+ vmxnet3_put_tx_stats_to_file(f, &r->txq_stats);
+}
+
+static const VMStateInfo txq_descr_info = {
+ .name = "txq_descr",
+ .get = vmxnet3_get_txq_descr,
+ .put = vmxnet3_put_txq_descr
+};
+
+static void vmxnet3_get_rx_stats_from_file(QEMUFile *f,
+ struct UPT1_RxStats *rx_stat)
+{
+ rx_stat->LROPktsRxOK = qemu_get_be64(f);
+ rx_stat->LROBytesRxOK = qemu_get_be64(f);
+ rx_stat->ucastPktsRxOK = qemu_get_be64(f);
+ rx_stat->ucastBytesRxOK = qemu_get_be64(f);
+ rx_stat->mcastPktsRxOK = qemu_get_be64(f);
+ rx_stat->mcastBytesRxOK = qemu_get_be64(f);
+ rx_stat->bcastPktsRxOK = qemu_get_be64(f);
+ rx_stat->bcastBytesRxOK = qemu_get_be64(f);
+ rx_stat->pktsRxOutOfBuf = qemu_get_be64(f);
+ rx_stat->pktsRxError = qemu_get_be64(f);
+}
+
+static void vmxnet3_put_rx_stats_to_file(QEMUFile *f,
+ struct UPT1_RxStats *rx_stat)
+{
+ qemu_put_be64(f, rx_stat->LROPktsRxOK);
+ qemu_put_be64(f, rx_stat->LROBytesRxOK);
+ qemu_put_be64(f, rx_stat->ucastPktsRxOK);
+ qemu_put_be64(f, rx_stat->ucastBytesRxOK);
+ qemu_put_be64(f, rx_stat->mcastPktsRxOK);
+ qemu_put_be64(f, rx_stat->mcastBytesRxOK);
+ qemu_put_be64(f, rx_stat->bcastPktsRxOK);
+ qemu_put_be64(f, rx_stat->bcastBytesRxOK);
+ qemu_put_be64(f, rx_stat->pktsRxOutOfBuf);
+ qemu_put_be64(f, rx_stat->pktsRxError);
+}
+
+static int vmxnet3_get_rxq_descr(QEMUFile *f, void *pv, size_t size)
+{
+ Vmxnet3RxqDescr *r = pv;
+ int i;
+
+ for (i = 0; i < VMXNET3_RX_RINGS_PER_QUEUE; i++) {
+ vmxnet3_get_ring_from_file(f, &r->rx_ring[i]);
+ }
+
+ vmxnet3_get_ring_from_file(f, &r->comp_ring);
+ r->intr_idx = qemu_get_byte(f);
+ r->rx_stats_pa = qemu_get_be64(f);
+
+ vmxnet3_get_rx_stats_from_file(f, &r->rxq_stats);
+
+ return 0;
+}
+
+static void vmxnet3_put_rxq_descr(QEMUFile *f, void *pv, size_t size)
+{
+ Vmxnet3RxqDescr *r = pv;
+ int i;
+
+ for (i = 0; i < VMXNET3_RX_RINGS_PER_QUEUE; i++) {
+ vmxnet3_put_ring_to_file(f, &r->rx_ring[i]);
+ }
+
+ vmxnet3_put_ring_to_file(f, &r->comp_ring);
+ qemu_put_byte(f, r->intr_idx);
+ qemu_put_be64(f, r->rx_stats_pa);
+ vmxnet3_put_rx_stats_to_file(f, &r->rxq_stats);
+}
+
+static int vmxnet3_post_load(void *opaque, int version_id)
+{
+ VMXNET3State *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+
+ vmxnet_tx_pkt_init(&s->tx_pkt, s->max_tx_frags, s->peer_has_vhdr);
+ vmxnet_rx_pkt_init(&s->rx_pkt, s->peer_has_vhdr);
+
+ if (s->msix_used) {
+ if (!vmxnet3_use_msix_vectors(s, VMXNET3_MAX_INTRS)) {
+ VMW_WRPRN("Failed to re-use MSI-X vectors");
+ msix_uninit(d, &s->msix_bar, &s->msix_bar);
+ s->msix_used = false;
+ return -1;
+ }
+ }
+
+ vmxnet3_validate_queues(s);
+ vmxnet3_validate_interrupts(s);
+
+ return 0;
+}
+
+static const VMStateInfo rxq_descr_info = {
+ .name = "rxq_descr",
+ .get = vmxnet3_get_rxq_descr,
+ .put = vmxnet3_put_rxq_descr
+};
+
+static int vmxnet3_get_int_state(QEMUFile *f, void *pv, size_t size)
+{
+ Vmxnet3IntState *r = pv;
+
+ r->is_masked = qemu_get_byte(f);
+ r->is_pending = qemu_get_byte(f);
+ r->is_asserted = qemu_get_byte(f);
+
+ return 0;
+}
+
+static void vmxnet3_put_int_state(QEMUFile *f, void *pv, size_t size)
+{
+ Vmxnet3IntState *r = pv;
+
+ qemu_put_byte(f, r->is_masked);
+ qemu_put_byte(f, r->is_pending);
+ qemu_put_byte(f, r->is_asserted);
+}
+
+static const VMStateInfo int_state_info = {
+ .name = "int_state",
+ .get = vmxnet3_get_int_state,
+ .put = vmxnet3_put_int_state
+};
+
+static const VMStateDescription vmstate_vmxnet3 = {
+ .name = "vmxnet3",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = vmxnet3_pre_save,
+ .post_load = vmxnet3_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, VMXNET3State),
+ VMSTATE_BOOL(rx_packets_compound, VMXNET3State),
+ VMSTATE_BOOL(rx_vlan_stripping, VMXNET3State),
+ VMSTATE_BOOL(lro_supported, VMXNET3State),
+ VMSTATE_UINT32(rx_mode, VMXNET3State),
+ VMSTATE_UINT32(mcast_list_len, VMXNET3State),
+ VMSTATE_UINT32(mcast_list_buff_size, VMXNET3State),
+ VMSTATE_UINT32_ARRAY(vlan_table, VMXNET3State, VMXNET3_VFT_SIZE),
+ VMSTATE_UINT32(mtu, VMXNET3State),
+ VMSTATE_UINT16(max_rx_frags, VMXNET3State),
+ VMSTATE_UINT32(max_tx_frags, VMXNET3State),
+ VMSTATE_UINT8(event_int_idx, VMXNET3State),
+ VMSTATE_BOOL(auto_int_masking, VMXNET3State),
+ VMSTATE_UINT8(txq_num, VMXNET3State),
+ VMSTATE_UINT8(rxq_num, VMXNET3State),
+ VMSTATE_UINT32(device_active, VMXNET3State),
+ VMSTATE_UINT32(last_command, VMXNET3State),
+ VMSTATE_UINT32(link_status_and_speed, VMXNET3State),
+ VMSTATE_UINT32(temp_mac, VMXNET3State),
+ VMSTATE_UINT64(drv_shmem, VMXNET3State),
+ VMSTATE_UINT64(temp_shared_guest_driver_memory, VMXNET3State),
+
+ VMSTATE_ARRAY(txq_descr, VMXNET3State,
+ VMXNET3_DEVICE_MAX_TX_QUEUES, 0, txq_descr_info,
+ Vmxnet3TxqDescr),
+ VMSTATE_ARRAY(rxq_descr, VMXNET3State,
+ VMXNET3_DEVICE_MAX_RX_QUEUES, 0, rxq_descr_info,
+ Vmxnet3RxqDescr),
+ VMSTATE_ARRAY(interrupt_states, VMXNET3State, VMXNET3_MAX_INTRS,
+ 0, int_state_info, Vmxnet3IntState),
+
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmxstate_vmxnet3_mcast_list,
+ NULL
+ }
+};
+
+static Property vmxnet3_properties[] = {
+ DEFINE_NIC_PROPERTIES(VMXNET3State, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vmxnet3_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ PCIDeviceClass *c = PCI_DEVICE_CLASS(class);
+
+ c->realize = vmxnet3_pci_realize;
+ c->exit = vmxnet3_pci_uninit;
+ c->vendor_id = PCI_VENDOR_ID_VMWARE;
+ c->device_id = PCI_DEVICE_ID_VMWARE_VMXNET3;
+ c->revision = PCI_DEVICE_ID_VMWARE_VMXNET3_REVISION;
+ c->class_id = PCI_CLASS_NETWORK_ETHERNET;
+ c->subsystem_vendor_id = PCI_VENDOR_ID_VMWARE;
+ c->subsystem_id = PCI_DEVICE_ID_VMWARE_VMXNET3;
+ dc->desc = "VMWare Paravirtualized Ethernet v3";
+ dc->reset = vmxnet3_qdev_reset;
+ dc->vmsd = &vmstate_vmxnet3;
+ dc->props = vmxnet3_properties;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo vmxnet3_info = {
+ .name = TYPE_VMXNET3,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(VMXNET3State),
+ .class_init = vmxnet3_class_init,
+ .instance_init = vmxnet3_instance_init,
+};
+
+static void vmxnet3_register_types(void)
+{
+ VMW_CBPRN("vmxnet3_register_types called...");
+ type_register_static(&vmxnet3_info);
+}
+
+type_init(vmxnet3_register_types)
diff --git a/hw/net/vmxnet3.h b/hw/net/vmxnet3.h
new file mode 100644
index 00000000..f987d712
--- /dev/null
+++ b/hw/net/vmxnet3.h
@@ -0,0 +1,755 @@
+/*
+ * QEMU VMWARE VMXNET3 paravirtual NIC interface definitions
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Tamir Shomer <tamirs@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef _QEMU_VMXNET3_H
+#define _QEMU_VMXNET3_H
+
+#define VMXNET3_DEVICE_MAX_TX_QUEUES 8
+#define VMXNET3_DEVICE_MAX_RX_QUEUES 8 /* Keep this value as a power of 2 */
+
+/*
+ * VMWARE headers we got from Linux kernel do not fully comply QEMU coding
+ * standards in sense of types and defines used.
+ * Since we didn't want to change VMWARE code, following set of typedefs
+ * and defines needed to compile these headers with QEMU introduced.
+ */
+#define u64 uint64_t
+#define u32 uint32_t
+#define u16 uint16_t
+#define u8 uint8_t
+#define __le16 uint16_t
+#define __le32 uint32_t
+#define __le64 uint64_t
+
+#if defined(HOST_WORDS_BIGENDIAN)
+#define __BIG_ENDIAN_BITFIELD
+#else
+#endif
+
+/*
+ * Following is an interface definition for
+ * VMXNET3 device as provided by VMWARE
+ * See original copyright from Linux kernel v3.2.8
+ * header file drivers/net/vmxnet3/vmxnet3_defs.h below.
+ */
+
+/*
+ * Linux driver for VMware's vmxnet3 ethernet NIC.
+ *
+ * Copyright (C) 2008-2009, VMware, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; version 2 of the License and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * The full GNU General Public License is included in this distribution in
+ * the file called "COPYING".
+ *
+ * Maintained by: Shreyas Bhatewara <pv-drivers@vmware.com>
+ *
+ */
+
+struct UPT1_TxStats {
+ u64 TSOPktsTxOK; /* TSO pkts post-segmentation */
+ u64 TSOBytesTxOK;
+ u64 ucastPktsTxOK;
+ u64 ucastBytesTxOK;
+ u64 mcastPktsTxOK;
+ u64 mcastBytesTxOK;
+ u64 bcastPktsTxOK;
+ u64 bcastBytesTxOK;
+ u64 pktsTxError;
+ u64 pktsTxDiscard;
+};
+
+struct UPT1_RxStats {
+ u64 LROPktsRxOK; /* LRO pkts */
+ u64 LROBytesRxOK; /* bytes from LRO pkts */
+ /* the following counters are for pkts from the wire, i.e., pre-LRO */
+ u64 ucastPktsRxOK;
+ u64 ucastBytesRxOK;
+ u64 mcastPktsRxOK;
+ u64 mcastBytesRxOK;
+ u64 bcastPktsRxOK;
+ u64 bcastBytesRxOK;
+ u64 pktsRxOutOfBuf;
+ u64 pktsRxError;
+};
+
+/* interrupt moderation level */
+enum {
+ UPT1_IML_NONE = 0, /* no interrupt moderation */
+ UPT1_IML_HIGHEST = 7, /* least intr generated */
+ UPT1_IML_ADAPTIVE = 8, /* adpative intr moderation */
+};
+/* values for UPT1_RSSConf.hashFunc */
+enum {
+ UPT1_RSS_HASH_TYPE_NONE = 0x0,
+ UPT1_RSS_HASH_TYPE_IPV4 = 0x01,
+ UPT1_RSS_HASH_TYPE_TCP_IPV4 = 0x02,
+ UPT1_RSS_HASH_TYPE_IPV6 = 0x04,
+ UPT1_RSS_HASH_TYPE_TCP_IPV6 = 0x08,
+};
+
+enum {
+ UPT1_RSS_HASH_FUNC_NONE = 0x0,
+ UPT1_RSS_HASH_FUNC_TOEPLITZ = 0x01,
+};
+
+#define UPT1_RSS_MAX_KEY_SIZE 40
+#define UPT1_RSS_MAX_IND_TABLE_SIZE 128
+
+struct UPT1_RSSConf {
+ u16 hashType;
+ u16 hashFunc;
+ u16 hashKeySize;
+ u16 indTableSize;
+ u8 hashKey[UPT1_RSS_MAX_KEY_SIZE];
+ u8 indTable[UPT1_RSS_MAX_IND_TABLE_SIZE];
+};
+
+/* features */
+enum {
+ UPT1_F_RXCSUM = 0x0001, /* rx csum verification */
+ UPT1_F_RSS = 0x0002,
+ UPT1_F_RXVLAN = 0x0004, /* VLAN tag stripping */
+ UPT1_F_LRO = 0x0008,
+};
+
+/* all registers are 32 bit wide */
+/* BAR 1 */
+enum {
+ VMXNET3_REG_VRRS = 0x0, /* Vmxnet3 Revision Report Selection */
+ VMXNET3_REG_UVRS = 0x8, /* UPT Version Report Selection */
+ VMXNET3_REG_DSAL = 0x10, /* Driver Shared Address Low */
+ VMXNET3_REG_DSAH = 0x18, /* Driver Shared Address High */
+ VMXNET3_REG_CMD = 0x20, /* Command */
+ VMXNET3_REG_MACL = 0x28, /* MAC Address Low */
+ VMXNET3_REG_MACH = 0x30, /* MAC Address High */
+ VMXNET3_REG_ICR = 0x38, /* Interrupt Cause Register */
+ VMXNET3_REG_ECR = 0x40 /* Event Cause Register */
+};
+
+/* BAR 0 */
+enum {
+ VMXNET3_REG_IMR = 0x0, /* Interrupt Mask Register */
+ VMXNET3_REG_TXPROD = 0x600, /* Tx Producer Index */
+ VMXNET3_REG_RXPROD = 0x800, /* Rx Producer Index for ring 1 */
+ VMXNET3_REG_RXPROD2 = 0xA00 /* Rx Producer Index for ring 2 */
+};
+
+#define VMXNET3_PT_REG_SIZE 4096 /* BAR 0 */
+#define VMXNET3_VD_REG_SIZE 4096 /* BAR 1 */
+
+#define VMXNET3_REG_ALIGN 8 /* All registers are 8-byte aligned. */
+#define VMXNET3_REG_ALIGN_MASK 0x7
+
+/* I/O Mapped access to registers */
+#define VMXNET3_IO_TYPE_PT 0
+#define VMXNET3_IO_TYPE_VD 1
+#define VMXNET3_IO_ADDR(type, reg) (((type) << 24) | ((reg) & 0xFFFFFF))
+#define VMXNET3_IO_TYPE(addr) ((addr) >> 24)
+#define VMXNET3_IO_REG(addr) ((addr) & 0xFFFFFF)
+
+enum {
+ VMXNET3_CMD_FIRST_SET = 0xCAFE0000,
+ VMXNET3_CMD_ACTIVATE_DEV = VMXNET3_CMD_FIRST_SET, /* 0xCAFE0000 */
+ VMXNET3_CMD_QUIESCE_DEV, /* 0xCAFE0001 */
+ VMXNET3_CMD_RESET_DEV, /* 0xCAFE0002 */
+ VMXNET3_CMD_UPDATE_RX_MODE, /* 0xCAFE0003 */
+ VMXNET3_CMD_UPDATE_MAC_FILTERS, /* 0xCAFE0004 */
+ VMXNET3_CMD_UPDATE_VLAN_FILTERS, /* 0xCAFE0005 */
+ VMXNET3_CMD_UPDATE_RSSIDT, /* 0xCAFE0006 */
+ VMXNET3_CMD_UPDATE_IML, /* 0xCAFE0007 */
+ VMXNET3_CMD_UPDATE_PMCFG, /* 0xCAFE0008 */
+ VMXNET3_CMD_UPDATE_FEATURE, /* 0xCAFE0009 */
+ VMXNET3_CMD_LOAD_PLUGIN, /* 0xCAFE000A */
+
+ VMXNET3_CMD_FIRST_GET = 0xF00D0000,
+ VMXNET3_CMD_GET_QUEUE_STATUS = VMXNET3_CMD_FIRST_GET, /* 0xF00D0000 */
+ VMXNET3_CMD_GET_STATS, /* 0xF00D0001 */
+ VMXNET3_CMD_GET_LINK, /* 0xF00D0002 */
+ VMXNET3_CMD_GET_PERM_MAC_LO, /* 0xF00D0003 */
+ VMXNET3_CMD_GET_PERM_MAC_HI, /* 0xF00D0004 */
+ VMXNET3_CMD_GET_DID_LO, /* 0xF00D0005 */
+ VMXNET3_CMD_GET_DID_HI, /* 0xF00D0006 */
+ VMXNET3_CMD_GET_DEV_EXTRA_INFO, /* 0xF00D0007 */
+ VMXNET3_CMD_GET_CONF_INTR /* 0xF00D0008 */
+};
+
+/*
+ * Little Endian layout of bitfields -
+ * Byte 0 : 7.....len.....0
+ * Byte 1 : rsvd gen 13.len.8
+ * Byte 2 : 5.msscof.0 ext1 dtype
+ * Byte 3 : 13...msscof...6
+ *
+ * Big Endian layout of bitfields -
+ * Byte 0: 13...msscof...6
+ * Byte 1 : 5.msscof.0 ext1 dtype
+ * Byte 2 : rsvd gen 13.len.8
+ * Byte 3 : 7.....len.....0
+ *
+ * Thus, le32_to_cpu on the dword will allow the big endian driver to read
+ * the bit fields correctly. And cpu_to_le32 will convert bitfields
+ * bit fields written by big endian driver to format required by device.
+ */
+
+struct Vmxnet3_TxDesc {
+ __le64 addr;
+
+#ifdef __BIG_ENDIAN_BITFIELD
+ u32 msscof:14; /* MSS, checksum offset, flags */
+ u32 ext1:1;
+ u32 dtype:1; /* descriptor type */
+ u32 rsvd:1;
+ u32 gen:1; /* generation bit */
+ u32 len:14;
+#else
+ u32 len:14;
+ u32 gen:1; /* generation bit */
+ u32 rsvd:1;
+ u32 dtype:1; /* descriptor type */
+ u32 ext1:1;
+ u32 msscof:14; /* MSS, checksum offset, flags */
+#endif /* __BIG_ENDIAN_BITFIELD */
+
+#ifdef __BIG_ENDIAN_BITFIELD
+ u32 tci:16; /* Tag to Insert */
+ u32 ti:1; /* VLAN Tag Insertion */
+ u32 ext2:1;
+ u32 cq:1; /* completion request */
+ u32 eop:1; /* End Of Packet */
+ u32 om:2; /* offload mode */
+ u32 hlen:10; /* header len */
+#else
+ u32 hlen:10; /* header len */
+ u32 om:2; /* offload mode */
+ u32 eop:1; /* End Of Packet */
+ u32 cq:1; /* completion request */
+ u32 ext2:1;
+ u32 ti:1; /* VLAN Tag Insertion */
+ u32 tci:16; /* Tag to Insert */
+#endif /* __BIG_ENDIAN_BITFIELD */
+};
+
+/* TxDesc.OM values */
+#define VMXNET3_OM_NONE 0
+#define VMXNET3_OM_CSUM 2
+#define VMXNET3_OM_TSO 3
+
+/* fields in TxDesc we access w/o using bit fields */
+#define VMXNET3_TXD_EOP_SHIFT 12
+#define VMXNET3_TXD_CQ_SHIFT 13
+#define VMXNET3_TXD_GEN_SHIFT 14
+#define VMXNET3_TXD_EOP_DWORD_SHIFT 3
+#define VMXNET3_TXD_GEN_DWORD_SHIFT 2
+
+#define VMXNET3_TXD_CQ (1 << VMXNET3_TXD_CQ_SHIFT)
+#define VMXNET3_TXD_EOP (1 << VMXNET3_TXD_EOP_SHIFT)
+#define VMXNET3_TXD_GEN (1 << VMXNET3_TXD_GEN_SHIFT)
+
+#define VMXNET3_HDR_COPY_SIZE 128
+
+
+struct Vmxnet3_TxDataDesc {
+ u8 data[VMXNET3_HDR_COPY_SIZE];
+};
+
+#define VMXNET3_TCD_GEN_SHIFT 31
+#define VMXNET3_TCD_GEN_SIZE 1
+#define VMXNET3_TCD_TXIDX_SHIFT 0
+#define VMXNET3_TCD_TXIDX_SIZE 12
+#define VMXNET3_TCD_GEN_DWORD_SHIFT 3
+
+struct Vmxnet3_TxCompDesc {
+ u32 txdIdx:12; /* Index of the EOP TxDesc */
+ u32 ext1:20;
+
+ __le32 ext2;
+ __le32 ext3;
+
+ u32 rsvd:24;
+ u32 type:7; /* completion type */
+ u32 gen:1; /* generation bit */
+};
+
+struct Vmxnet3_RxDesc {
+ __le64 addr;
+
+#ifdef __BIG_ENDIAN_BITFIELD
+ u32 gen:1; /* Generation bit */
+ u32 rsvd:15;
+ u32 dtype:1; /* Descriptor type */
+ u32 btype:1; /* Buffer Type */
+ u32 len:14;
+#else
+ u32 len:14;
+ u32 btype:1; /* Buffer Type */
+ u32 dtype:1; /* Descriptor type */
+ u32 rsvd:15;
+ u32 gen:1; /* Generation bit */
+#endif
+ u32 ext1;
+};
+
+/* values of RXD.BTYPE */
+#define VMXNET3_RXD_BTYPE_HEAD 0 /* head only */
+#define VMXNET3_RXD_BTYPE_BODY 1 /* body only */
+
+/* fields in RxDesc we access w/o using bit fields */
+#define VMXNET3_RXD_BTYPE_SHIFT 14
+#define VMXNET3_RXD_GEN_SHIFT 31
+
+struct Vmxnet3_RxCompDesc {
+#ifdef __BIG_ENDIAN_BITFIELD
+ u32 ext2:1;
+ u32 cnc:1; /* Checksum Not Calculated */
+ u32 rssType:4; /* RSS hash type used */
+ u32 rqID:10; /* rx queue/ring ID */
+ u32 sop:1; /* Start of Packet */
+ u32 eop:1; /* End of Packet */
+ u32 ext1:2;
+ u32 rxdIdx:12; /* Index of the RxDesc */
+#else
+ u32 rxdIdx:12; /* Index of the RxDesc */
+ u32 ext1:2;
+ u32 eop:1; /* End of Packet */
+ u32 sop:1; /* Start of Packet */
+ u32 rqID:10; /* rx queue/ring ID */
+ u32 rssType:4; /* RSS hash type used */
+ u32 cnc:1; /* Checksum Not Calculated */
+ u32 ext2:1;
+#endif /* __BIG_ENDIAN_BITFIELD */
+
+ __le32 rssHash; /* RSS hash value */
+
+#ifdef __BIG_ENDIAN_BITFIELD
+ u32 tci:16; /* Tag stripped */
+ u32 ts:1; /* Tag is stripped */
+ u32 err:1; /* Error */
+ u32 len:14; /* data length */
+#else
+ u32 len:14; /* data length */
+ u32 err:1; /* Error */
+ u32 ts:1; /* Tag is stripped */
+ u32 tci:16; /* Tag stripped */
+#endif /* __BIG_ENDIAN_BITFIELD */
+
+
+#ifdef __BIG_ENDIAN_BITFIELD
+ u32 gen:1; /* generation bit */
+ u32 type:7; /* completion type */
+ u32 fcs:1; /* Frame CRC correct */
+ u32 frg:1; /* IP Fragment */
+ u32 v4:1; /* IPv4 */
+ u32 v6:1; /* IPv6 */
+ u32 ipc:1; /* IP Checksum Correct */
+ u32 tcp:1; /* TCP packet */
+ u32 udp:1; /* UDP packet */
+ u32 tuc:1; /* TCP/UDP Checksum Correct */
+ u32 csum:16;
+#else
+ u32 csum:16;
+ u32 tuc:1; /* TCP/UDP Checksum Correct */
+ u32 udp:1; /* UDP packet */
+ u32 tcp:1; /* TCP packet */
+ u32 ipc:1; /* IP Checksum Correct */
+ u32 v6:1; /* IPv6 */
+ u32 v4:1; /* IPv4 */
+ u32 frg:1; /* IP Fragment */
+ u32 fcs:1; /* Frame CRC correct */
+ u32 type:7; /* completion type */
+ u32 gen:1; /* generation bit */
+#endif /* __BIG_ENDIAN_BITFIELD */
+};
+
+/* fields in RxCompDesc we access via Vmxnet3_GenericDesc.dword[3] */
+#define VMXNET3_RCD_TUC_SHIFT 16
+#define VMXNET3_RCD_IPC_SHIFT 19
+
+/* fields in RxCompDesc we access via Vmxnet3_GenericDesc.qword[1] */
+#define VMXNET3_RCD_TYPE_SHIFT 56
+#define VMXNET3_RCD_GEN_SHIFT 63
+
+/* csum OK for TCP/UDP pkts over IP */
+#define VMXNET3_RCD_CSUM_OK (1 << VMXNET3_RCD_TUC_SHIFT | \
+ 1 << VMXNET3_RCD_IPC_SHIFT)
+#define VMXNET3_TXD_GEN_SIZE 1
+#define VMXNET3_TXD_EOP_SIZE 1
+
+/* value of RxCompDesc.rssType */
+enum {
+ VMXNET3_RCD_RSS_TYPE_NONE = 0,
+ VMXNET3_RCD_RSS_TYPE_IPV4 = 1,
+ VMXNET3_RCD_RSS_TYPE_TCPIPV4 = 2,
+ VMXNET3_RCD_RSS_TYPE_IPV6 = 3,
+ VMXNET3_RCD_RSS_TYPE_TCPIPV6 = 4,
+};
+
+
+/* a union for accessing all cmd/completion descriptors */
+union Vmxnet3_GenericDesc {
+ __le64 qword[2];
+ __le32 dword[4];
+ __le16 word[8];
+ struct Vmxnet3_TxDesc txd;
+ struct Vmxnet3_RxDesc rxd;
+ struct Vmxnet3_TxCompDesc tcd;
+ struct Vmxnet3_RxCompDesc rcd;
+};
+
+#define VMXNET3_INIT_GEN 1
+
+/* Max size of a single tx buffer */
+#define VMXNET3_MAX_TX_BUF_SIZE (1 << 14)
+
+/* # of tx desc needed for a tx buffer size */
+#define VMXNET3_TXD_NEEDED(size) (((size) + VMXNET3_MAX_TX_BUF_SIZE - 1) / \
+ VMXNET3_MAX_TX_BUF_SIZE)
+
+/* max # of tx descs for a non-tso pkt */
+#define VMXNET3_MAX_TXD_PER_PKT 16
+
+/* Max size of a single rx buffer */
+#define VMXNET3_MAX_RX_BUF_SIZE ((1 << 14) - 1)
+/* Minimum size of a type 0 buffer */
+#define VMXNET3_MIN_T0_BUF_SIZE 128
+#define VMXNET3_MAX_CSUM_OFFSET 1024
+
+/* Ring base address alignment */
+#define VMXNET3_RING_BA_ALIGN 512
+#define VMXNET3_RING_BA_MASK (VMXNET3_RING_BA_ALIGN - 1)
+
+/* Ring size must be a multiple of 32 */
+#define VMXNET3_RING_SIZE_ALIGN 32
+#define VMXNET3_RING_SIZE_MASK (VMXNET3_RING_SIZE_ALIGN - 1)
+
+/* Max ring size */
+#define VMXNET3_TX_RING_MAX_SIZE 4096
+#define VMXNET3_TC_RING_MAX_SIZE 4096
+#define VMXNET3_RX_RING_MAX_SIZE 4096
+#define VMXNET3_RC_RING_MAX_SIZE 8192
+
+/* a list of reasons for queue stop */
+
+enum {
+ VMXNET3_ERR_NOEOP = 0x80000000, /* cannot find the EOP desc of a pkt */
+ VMXNET3_ERR_TXD_REUSE = 0x80000001, /* reuse TxDesc before tx completion */
+ VMXNET3_ERR_BIG_PKT = 0x80000002, /* too many TxDesc for a pkt */
+ VMXNET3_ERR_DESC_NOT_SPT = 0x80000003, /* descriptor type not supported */
+ VMXNET3_ERR_SMALL_BUF = 0x80000004, /* type 0 buffer too small */
+ VMXNET3_ERR_STRESS = 0x80000005, /* stress option firing in vmkernel */
+ VMXNET3_ERR_SWITCH = 0x80000006, /* mode switch failure */
+ VMXNET3_ERR_TXD_INVALID = 0x80000007, /* invalid TxDesc */
+};
+
+/* completion descriptor types */
+#define VMXNET3_CDTYPE_TXCOMP 0 /* Tx Completion Descriptor */
+#define VMXNET3_CDTYPE_RXCOMP 3 /* Rx Completion Descriptor */
+
+enum {
+ VMXNET3_GOS_BITS_UNK = 0, /* unknown */
+ VMXNET3_GOS_BITS_32 = 1,
+ VMXNET3_GOS_BITS_64 = 2,
+};
+
+#define VMXNET3_GOS_TYPE_UNK 0 /* unknown */
+#define VMXNET3_GOS_TYPE_LINUX 1
+#define VMXNET3_GOS_TYPE_WIN 2
+#define VMXNET3_GOS_TYPE_SOLARIS 3
+#define VMXNET3_GOS_TYPE_FREEBSD 4
+#define VMXNET3_GOS_TYPE_PXE 5
+
+struct Vmxnet3_GOSInfo {
+#ifdef __BIG_ENDIAN_BITFIELD
+ u32 gosMisc:10; /* other info about gos */
+ u32 gosVer:16; /* gos version */
+ u32 gosType:4; /* which guest */
+ u32 gosBits:2; /* 32-bit or 64-bit? */
+#else
+ u32 gosBits:2; /* 32-bit or 64-bit? */
+ u32 gosType:4; /* which guest */
+ u32 gosVer:16; /* gos version */
+ u32 gosMisc:10; /* other info about gos */
+#endif /* __BIG_ENDIAN_BITFIELD */
+};
+
+struct Vmxnet3_DriverInfo {
+ __le32 version;
+ struct Vmxnet3_GOSInfo gos;
+ __le32 vmxnet3RevSpt;
+ __le32 uptVerSpt;
+};
+
+
+#define VMXNET3_REV1_MAGIC 0xbabefee1
+
+/*
+ * QueueDescPA must be 128 bytes aligned. It points to an array of
+ * Vmxnet3_TxQueueDesc followed by an array of Vmxnet3_RxQueueDesc.
+ * The number of Vmxnet3_TxQueueDesc/Vmxnet3_RxQueueDesc are specified by
+ * Vmxnet3_MiscConf.numTxQueues/numRxQueues, respectively.
+ */
+#define VMXNET3_QUEUE_DESC_ALIGN 128
+
+
+struct Vmxnet3_MiscConf {
+ struct Vmxnet3_DriverInfo driverInfo;
+ __le64 uptFeatures;
+ __le64 ddPA; /* driver data PA */
+ __le64 queueDescPA; /* queue descriptor table PA */
+ __le32 ddLen; /* driver data len */
+ __le32 queueDescLen; /* queue desc. table len in bytes */
+ __le32 mtu;
+ __le16 maxNumRxSG;
+ u8 numTxQueues;
+ u8 numRxQueues;
+ __le32 reserved[4];
+};
+
+
+struct Vmxnet3_TxQueueConf {
+ __le64 txRingBasePA;
+ __le64 dataRingBasePA;
+ __le64 compRingBasePA;
+ __le64 ddPA; /* driver data */
+ __le64 reserved;
+ __le32 txRingSize; /* # of tx desc */
+ __le32 dataRingSize; /* # of data desc */
+ __le32 compRingSize; /* # of comp desc */
+ __le32 ddLen; /* size of driver data */
+ u8 intrIdx;
+ u8 _pad[7];
+};
+
+
+struct Vmxnet3_RxQueueConf {
+ __le64 rxRingBasePA[2];
+ __le64 compRingBasePA;
+ __le64 ddPA; /* driver data */
+ __le64 reserved;
+ __le32 rxRingSize[2]; /* # of rx desc */
+ __le32 compRingSize; /* # of rx comp desc */
+ __le32 ddLen; /* size of driver data */
+ u8 intrIdx;
+ u8 _pad[7];
+};
+
+
+enum vmxnet3_intr_mask_mode {
+ VMXNET3_IMM_AUTO = 0,
+ VMXNET3_IMM_ACTIVE = 1,
+ VMXNET3_IMM_LAZY = 2
+};
+
+enum vmxnet3_intr_type {
+ VMXNET3_IT_AUTO = 0,
+ VMXNET3_IT_INTX = 1,
+ VMXNET3_IT_MSI = 2,
+ VMXNET3_IT_MSIX = 3
+};
+
+#define VMXNET3_MAX_TX_QUEUES 8
+#define VMXNET3_MAX_RX_QUEUES 16
+/* addition 1 for events */
+#define VMXNET3_MAX_INTRS 25
+
+/* value of intrCtrl */
+#define VMXNET3_IC_DISABLE_ALL 0x1 /* bit 0 */
+
+
+struct Vmxnet3_IntrConf {
+ bool autoMask;
+ u8 numIntrs; /* # of interrupts */
+ u8 eventIntrIdx;
+ u8 modLevels[VMXNET3_MAX_INTRS]; /* moderation level for
+ * each intr */
+ __le32 intrCtrl;
+ __le32 reserved[2];
+};
+
+/* one bit per VLAN ID, the size is in the units of u32 */
+#define VMXNET3_VFT_SIZE (4096/(sizeof(uint32_t)*8))
+
+
+struct Vmxnet3_QueueStatus {
+ bool stopped;
+ u8 _pad[3];
+ __le32 error;
+};
+
+
+struct Vmxnet3_TxQueueCtrl {
+ __le32 txNumDeferred;
+ __le32 txThreshold;
+ __le64 reserved;
+};
+
+
+struct Vmxnet3_RxQueueCtrl {
+ bool updateRxProd;
+ u8 _pad[7];
+ __le64 reserved;
+};
+
+enum {
+ VMXNET3_RXM_UCAST = 0x01, /* unicast only */
+ VMXNET3_RXM_MCAST = 0x02, /* multicast passing the filters */
+ VMXNET3_RXM_BCAST = 0x04, /* broadcast only */
+ VMXNET3_RXM_ALL_MULTI = 0x08, /* all multicast */
+ VMXNET3_RXM_PROMISC = 0x10 /* promiscuous */
+};
+
+struct Vmxnet3_RxFilterConf {
+ __le32 rxMode; /* VMXNET3_RXM_xxx */
+ __le16 mfTableLen; /* size of the multicast filter table */
+ __le16 _pad1;
+ __le64 mfTablePA; /* PA of the multicast filters table */
+ __le32 vfTable[VMXNET3_VFT_SIZE]; /* vlan filter */
+};
+
+
+#define VMXNET3_PM_MAX_FILTERS 6
+#define VMXNET3_PM_MAX_PATTERN_SIZE 128
+#define VMXNET3_PM_MAX_MASK_SIZE (VMXNET3_PM_MAX_PATTERN_SIZE / 8)
+
+#define VMXNET3_PM_WAKEUP_MAGIC cpu_to_le16(0x01) /* wake up on magic pkts */
+#define VMXNET3_PM_WAKEUP_FILTER cpu_to_le16(0x02) /* wake up on pkts matching
+ * filters */
+
+
+struct Vmxnet3_PM_PktFilter {
+ u8 maskSize;
+ u8 patternSize;
+ u8 mask[VMXNET3_PM_MAX_MASK_SIZE];
+ u8 pattern[VMXNET3_PM_MAX_PATTERN_SIZE];
+ u8 pad[6];
+};
+
+
+struct Vmxnet3_PMConf {
+ __le16 wakeUpEvents; /* VMXNET3_PM_WAKEUP_xxx */
+ u8 numFilters;
+ u8 pad[5];
+ struct Vmxnet3_PM_PktFilter filters[VMXNET3_PM_MAX_FILTERS];
+};
+
+
+struct Vmxnet3_VariableLenConfDesc {
+ __le32 confVer;
+ __le32 confLen;
+ __le64 confPA;
+};
+
+
+struct Vmxnet3_TxQueueDesc {
+ struct Vmxnet3_TxQueueCtrl ctrl;
+ struct Vmxnet3_TxQueueConf conf;
+
+ /* Driver read after a GET command */
+ struct Vmxnet3_QueueStatus status;
+ struct UPT1_TxStats stats;
+ u8 _pad[88]; /* 128 aligned */
+};
+
+
+struct Vmxnet3_RxQueueDesc {
+ struct Vmxnet3_RxQueueCtrl ctrl;
+ struct Vmxnet3_RxQueueConf conf;
+ /* Driver read after a GET commad */
+ struct Vmxnet3_QueueStatus status;
+ struct UPT1_RxStats stats;
+ u8 __pad[88]; /* 128 aligned */
+};
+
+
+struct Vmxnet3_DSDevRead {
+ /* read-only region for device, read by dev in response to a SET cmd */
+ struct Vmxnet3_MiscConf misc;
+ struct Vmxnet3_IntrConf intrConf;
+ struct Vmxnet3_RxFilterConf rxFilterConf;
+ struct Vmxnet3_VariableLenConfDesc rssConfDesc;
+ struct Vmxnet3_VariableLenConfDesc pmConfDesc;
+ struct Vmxnet3_VariableLenConfDesc pluginConfDesc;
+};
+
+/* All structures in DriverShared are padded to multiples of 8 bytes */
+struct Vmxnet3_DriverShared {
+ __le32 magic;
+ /* make devRead start at 64bit boundaries */
+ __le32 pad;
+ struct Vmxnet3_DSDevRead devRead;
+ __le32 ecr;
+ __le32 reserved[5];
+};
+
+
+#define VMXNET3_ECR_RQERR (1 << 0)
+#define VMXNET3_ECR_TQERR (1 << 1)
+#define VMXNET3_ECR_LINK (1 << 2)
+#define VMXNET3_ECR_DIC (1 << 3)
+#define VMXNET3_ECR_DEBUG (1 << 4)
+
+/* flip the gen bit of a ring */
+#define VMXNET3_FLIP_RING_GEN(gen) ((gen) = (gen) ^ 0x1)
+
+/* only use this if moving the idx won't affect the gen bit */
+#define VMXNET3_INC_RING_IDX_ONLY(idx, ring_size) \
+ do {\
+ (idx)++;\
+ if (unlikely((idx) == (ring_size))) {\
+ (idx) = 0;\
+ } \
+ } while (0)
+
+#define VMXNET3_SET_VFTABLE_ENTRY(vfTable, vid) \
+ (vfTable[vid >> 5] |= (1 << (vid & 31)))
+#define VMXNET3_CLEAR_VFTABLE_ENTRY(vfTable, vid) \
+ (vfTable[vid >> 5] &= ~(1 << (vid & 31)))
+
+#define VMXNET3_VFTABLE_ENTRY_IS_SET(vfTable, vid) \
+ ((vfTable[vid >> 5] & (1 << (vid & 31))) != 0)
+
+#define VMXNET3_MAX_MTU 9000
+#define VMXNET3_MIN_MTU 60
+
+#define VMXNET3_LINK_UP (10000 << 16 | 1) /* 10 Gbps, up */
+#define VMXNET3_LINK_DOWN 0
+
+#undef u64
+#undef u32
+#undef u16
+#undef u8
+#undef __le16
+#undef __le32
+#undef __le64
+#if defined(HOST_WORDS_BIGENDIAN)
+#undef __BIG_ENDIAN_BITFIELD
+#endif
+
+#endif
diff --git a/hw/net/vmxnet_debug.h b/hw/net/vmxnet_debug.h
new file mode 100644
index 00000000..96dae0f9
--- /dev/null
+++ b/hw/net/vmxnet_debug.h
@@ -0,0 +1,115 @@
+/*
+ * QEMU VMWARE VMXNET* paravirtual NICs - debugging facilities
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Tamir Shomer <tamirs@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef _QEMU_VMXNET_DEBUG_H
+#define _QEMU_VMXNET_DEBUG_H
+
+#define VMXNET_DEVICE_NAME "vmxnet3"
+
+/* #define VMXNET_DEBUG_CB */
+#define VMXNET_DEBUG_WARNINGS
+#define VMXNET_DEBUG_ERRORS
+/* #define VMXNET_DEBUG_INTERRUPTS */
+/* #define VMXNET_DEBUG_CONFIG */
+/* #define VMXNET_DEBUG_RINGS */
+/* #define VMXNET_DEBUG_PACKETS */
+/* #define VMXNET_DEBUG_SHMEM_ACCESS */
+
+#ifdef VMXNET_DEBUG_SHMEM_ACCESS
+#define VMW_SHPRN(fmt, ...) \
+ do { \
+ printf("[%s][SH][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \
+ ## __VA_ARGS__); \
+ } while (0)
+#else
+#define VMW_SHPRN(fmt, ...) do {} while (0)
+#endif
+
+#ifdef VMXNET_DEBUG_CB
+#define VMW_CBPRN(fmt, ...) \
+ do { \
+ printf("[%s][CB][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \
+ ## __VA_ARGS__); \
+ } while (0)
+#else
+#define VMW_CBPRN(fmt, ...) do {} while (0)
+#endif
+
+#ifdef VMXNET_DEBUG_PACKETS
+#define VMW_PKPRN(fmt, ...) \
+ do { \
+ printf("[%s][PK][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \
+ ## __VA_ARGS__); \
+ } while (0)
+#else
+#define VMW_PKPRN(fmt, ...) do {} while (0)
+#endif
+
+#ifdef VMXNET_DEBUG_WARNINGS
+#define VMW_WRPRN(fmt, ...) \
+ do { \
+ printf("[%s][WR][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \
+ ## __VA_ARGS__); \
+ } while (0)
+#else
+#define VMW_WRPRN(fmt, ...) do {} while (0)
+#endif
+
+#ifdef VMXNET_DEBUG_ERRORS
+#define VMW_ERPRN(fmt, ...) \
+ do { \
+ printf("[%s][ER][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \
+ ## __VA_ARGS__); \
+ } while (0)
+#else
+#define VMW_ERPRN(fmt, ...) do {} while (0)
+#endif
+
+#ifdef VMXNET_DEBUG_INTERRUPTS
+#define VMW_IRPRN(fmt, ...) \
+ do { \
+ printf("[%s][IR][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \
+ ## __VA_ARGS__); \
+ } while (0)
+#else
+#define VMW_IRPRN(fmt, ...) do {} while (0)
+#endif
+
+#ifdef VMXNET_DEBUG_CONFIG
+#define VMW_CFPRN(fmt, ...) \
+ do { \
+ printf("[%s][CF][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \
+ ## __VA_ARGS__); \
+ } while (0)
+#else
+#define VMW_CFPRN(fmt, ...) do {} while (0)
+#endif
+
+#ifdef VMXNET_DEBUG_RINGS
+#define VMW_RIPRN(fmt, ...) \
+ do { \
+ printf("[%s][RI][%s]: " fmt "\n", VMXNET_DEVICE_NAME, __func__, \
+ ## __VA_ARGS__); \
+ } while (0)
+#else
+#define VMW_RIPRN(fmt, ...) do {} while (0)
+#endif
+
+#define VMXNET_MF "%02X:%02X:%02X:%02X:%02X:%02X"
+#define VMXNET_MA(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
+
+#endif /* _QEMU_VMXNET3_DEBUG_H */
diff --git a/hw/net/vmxnet_rx_pkt.c b/hw/net/vmxnet_rx_pkt.c
new file mode 100644
index 00000000..aa546293
--- /dev/null
+++ b/hw/net/vmxnet_rx_pkt.c
@@ -0,0 +1,186 @@
+/*
+ * QEMU VMWARE VMXNET* paravirtual NICs - RX packets abstractions
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Tamir Shomer <tamirs@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "vmxnet_rx_pkt.h"
+#include "net/eth.h"
+#include "qemu-common.h"
+#include "qemu/iov.h"
+#include "net/checksum.h"
+#include "net/tap.h"
+
+/*
+ * RX packet may contain up to 2 fragments - rebuilt eth header
+ * in case of VLAN tag stripping
+ * and payload received from QEMU - in any case
+ */
+#define VMXNET_MAX_RX_PACKET_FRAGMENTS (2)
+
+struct VmxnetRxPkt {
+ struct virtio_net_hdr virt_hdr;
+ uint8_t ehdr_buf[ETH_MAX_L2_HDR_LEN];
+ struct iovec vec[VMXNET_MAX_RX_PACKET_FRAGMENTS];
+ uint16_t vec_len;
+ uint32_t tot_len;
+ uint16_t tci;
+ bool vlan_stripped;
+ bool has_virt_hdr;
+ eth_pkt_types_e packet_type;
+
+ /* Analysis results */
+ bool isip4;
+ bool isip6;
+ bool isudp;
+ bool istcp;
+};
+
+void vmxnet_rx_pkt_init(struct VmxnetRxPkt **pkt, bool has_virt_hdr)
+{
+ struct VmxnetRxPkt *p = g_malloc0(sizeof *p);
+ p->has_virt_hdr = has_virt_hdr;
+ *pkt = p;
+}
+
+void vmxnet_rx_pkt_uninit(struct VmxnetRxPkt *pkt)
+{
+ g_free(pkt);
+}
+
+struct virtio_net_hdr *vmxnet_rx_pkt_get_vhdr(struct VmxnetRxPkt *pkt)
+{
+ assert(pkt);
+ return &pkt->virt_hdr;
+}
+
+void vmxnet_rx_pkt_attach_data(struct VmxnetRxPkt *pkt, const void *data,
+ size_t len, bool strip_vlan)
+{
+ uint16_t tci = 0;
+ uint16_t ploff;
+ assert(pkt);
+ pkt->vlan_stripped = false;
+
+ if (strip_vlan) {
+ pkt->vlan_stripped = eth_strip_vlan(data, pkt->ehdr_buf, &ploff, &tci);
+ }
+
+ if (pkt->vlan_stripped) {
+ pkt->vec[0].iov_base = pkt->ehdr_buf;
+ pkt->vec[0].iov_len = ploff - sizeof(struct vlan_header);
+ pkt->vec[1].iov_base = (uint8_t *) data + ploff;
+ pkt->vec[1].iov_len = len - ploff;
+ pkt->vec_len = 2;
+ pkt->tot_len = len - ploff + sizeof(struct eth_header);
+ } else {
+ pkt->vec[0].iov_base = (void *)data;
+ pkt->vec[0].iov_len = len;
+ pkt->vec_len = 1;
+ pkt->tot_len = len;
+ }
+
+ pkt->tci = tci;
+}
+
+void vmxnet_rx_pkt_dump(struct VmxnetRxPkt *pkt)
+{
+#ifdef VMXNET_RX_PKT_DEBUG
+ VmxnetRxPkt *pkt = (VmxnetRxPkt *)pkt;
+ assert(pkt);
+
+ printf("RX PKT: tot_len: %d, vlan_stripped: %d, vlan_tag: %d\n",
+ pkt->tot_len, pkt->vlan_stripped, pkt->tci);
+#endif
+}
+
+void vmxnet_rx_pkt_set_packet_type(struct VmxnetRxPkt *pkt,
+ eth_pkt_types_e packet_type)
+{
+ assert(pkt);
+
+ pkt->packet_type = packet_type;
+
+}
+
+eth_pkt_types_e vmxnet_rx_pkt_get_packet_type(struct VmxnetRxPkt *pkt)
+{
+ assert(pkt);
+
+ return pkt->packet_type;
+}
+
+size_t vmxnet_rx_pkt_get_total_len(struct VmxnetRxPkt *pkt)
+{
+ assert(pkt);
+
+ return pkt->tot_len;
+}
+
+void vmxnet_rx_pkt_set_protocols(struct VmxnetRxPkt *pkt, const void *data,
+ size_t len)
+{
+ assert(pkt);
+
+ eth_get_protocols(data, len, &pkt->isip4, &pkt->isip6,
+ &pkt->isudp, &pkt->istcp);
+}
+
+void vmxnet_rx_pkt_get_protocols(struct VmxnetRxPkt *pkt,
+ bool *isip4, bool *isip6,
+ bool *isudp, bool *istcp)
+{
+ assert(pkt);
+
+ *isip4 = pkt->isip4;
+ *isip6 = pkt->isip6;
+ *isudp = pkt->isudp;
+ *istcp = pkt->istcp;
+}
+
+struct iovec *vmxnet_rx_pkt_get_iovec(struct VmxnetRxPkt *pkt)
+{
+ assert(pkt);
+
+ return pkt->vec;
+}
+
+void vmxnet_rx_pkt_set_vhdr(struct VmxnetRxPkt *pkt,
+ struct virtio_net_hdr *vhdr)
+{
+ assert(pkt);
+
+ memcpy(&pkt->virt_hdr, vhdr, sizeof pkt->virt_hdr);
+}
+
+bool vmxnet_rx_pkt_is_vlan_stripped(struct VmxnetRxPkt *pkt)
+{
+ assert(pkt);
+
+ return pkt->vlan_stripped;
+}
+
+bool vmxnet_rx_pkt_has_virt_hdr(struct VmxnetRxPkt *pkt)
+{
+ assert(pkt);
+
+ return pkt->has_virt_hdr;
+}
+
+uint16_t vmxnet_rx_pkt_get_vlan_tag(struct VmxnetRxPkt *pkt)
+{
+ assert(pkt);
+
+ return pkt->tci;
+}
diff --git a/hw/net/vmxnet_rx_pkt.h b/hw/net/vmxnet_rx_pkt.h
new file mode 100644
index 00000000..a425846b
--- /dev/null
+++ b/hw/net/vmxnet_rx_pkt.h
@@ -0,0 +1,176 @@
+/*
+ * QEMU VMWARE VMXNET* paravirtual NICs - RX packets abstraction
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Tamir Shomer <tamirs@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef VMXNET_RX_PKT_H
+#define VMXNET_RX_PKT_H
+
+#include "stdint.h"
+#include "stdbool.h"
+#include "net/eth.h"
+
+/* defines to enable packet dump functions */
+/*#define VMXNET_RX_PKT_DEBUG*/
+
+struct VmxnetRxPkt;
+
+/**
+ * Clean all rx packet resources
+ *
+ * @pkt: packet
+ *
+ */
+void vmxnet_rx_pkt_uninit(struct VmxnetRxPkt *pkt);
+
+/**
+ * Init function for rx packet functionality
+ *
+ * @pkt: packet pointer
+ * @has_virt_hdr: device uses virtio header
+ *
+ */
+void vmxnet_rx_pkt_init(struct VmxnetRxPkt **pkt, bool has_virt_hdr);
+
+/**
+ * returns total length of data attached to rx context
+ *
+ * @pkt: packet
+ *
+ * Return: nothing
+ *
+ */
+size_t vmxnet_rx_pkt_get_total_len(struct VmxnetRxPkt *pkt);
+
+/**
+ * parse and set packet analysis results
+ *
+ * @pkt: packet
+ * @data: pointer to the data buffer to be parsed
+ * @len: data length
+ *
+ */
+void vmxnet_rx_pkt_set_protocols(struct VmxnetRxPkt *pkt, const void *data,
+ size_t len);
+
+/**
+ * fetches packet analysis results
+ *
+ * @pkt: packet
+ * @isip4: whether the packet given is IPv4
+ * @isip6: whether the packet given is IPv6
+ * @isudp: whether the packet given is UDP
+ * @istcp: whether the packet given is TCP
+ *
+ */
+void vmxnet_rx_pkt_get_protocols(struct VmxnetRxPkt *pkt,
+ bool *isip4, bool *isip6,
+ bool *isudp, bool *istcp);
+
+/**
+ * returns virtio header stored in rx context
+ *
+ * @pkt: packet
+ * @ret: virtio header
+ *
+ */
+struct virtio_net_hdr *vmxnet_rx_pkt_get_vhdr(struct VmxnetRxPkt *pkt);
+
+/**
+ * returns packet type
+ *
+ * @pkt: packet
+ * @ret: packet type
+ *
+ */
+eth_pkt_types_e vmxnet_rx_pkt_get_packet_type(struct VmxnetRxPkt *pkt);
+
+/**
+ * returns vlan tag
+ *
+ * @pkt: packet
+ * @ret: VLAN tag
+ *
+ */
+uint16_t vmxnet_rx_pkt_get_vlan_tag(struct VmxnetRxPkt *pkt);
+
+/**
+ * tells whether vlan was stripped from the packet
+ *
+ * @pkt: packet
+ * @ret: VLAN stripped sign
+ *
+ */
+bool vmxnet_rx_pkt_is_vlan_stripped(struct VmxnetRxPkt *pkt);
+
+/**
+ * notifies caller if the packet has virtio header
+ *
+ * @pkt: packet
+ * @ret: true if packet has virtio header, false otherwize
+ *
+ */
+bool vmxnet_rx_pkt_has_virt_hdr(struct VmxnetRxPkt *pkt);
+
+/**
+ * attach data to rx packet
+ *
+ * @pkt: packet
+ * @data: pointer to the data buffer
+ * @len: data length
+ * @strip_vlan: should the module strip vlan from data
+ *
+ */
+void vmxnet_rx_pkt_attach_data(struct VmxnetRxPkt *pkt, const void *data,
+ size_t len, bool strip_vlan);
+
+/**
+ * returns io vector that holds the attached data
+ *
+ * @pkt: packet
+ * @ret: pointer to IOVec
+ *
+ */
+struct iovec *vmxnet_rx_pkt_get_iovec(struct VmxnetRxPkt *pkt);
+
+/**
+ * prints rx packet data if debug is enabled
+ *
+ * @pkt: packet
+ *
+ */
+void vmxnet_rx_pkt_dump(struct VmxnetRxPkt *pkt);
+
+/**
+ * copy passed vhdr data to packet context
+ *
+ * @pkt: packet
+ * @vhdr: VHDR buffer
+ *
+ */
+void vmxnet_rx_pkt_set_vhdr(struct VmxnetRxPkt *pkt,
+ struct virtio_net_hdr *vhdr);
+
+/**
+ * save packet type in packet context
+ *
+ * @pkt: packet
+ * @packet_type: the packet type
+ *
+ */
+void vmxnet_rx_pkt_set_packet_type(struct VmxnetRxPkt *pkt,
+ eth_pkt_types_e packet_type);
+
+#endif
diff --git a/hw/net/vmxnet_tx_pkt.c b/hw/net/vmxnet_tx_pkt.c
new file mode 100644
index 00000000..eb88ddf2
--- /dev/null
+++ b/hw/net/vmxnet_tx_pkt.c
@@ -0,0 +1,580 @@
+/*
+ * QEMU VMWARE VMXNET* paravirtual NICs - TX packets abstractions
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Tamir Shomer <tamirs@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/hw.h"
+#include "vmxnet_tx_pkt.h"
+#include "net/eth.h"
+#include "qemu-common.h"
+#include "qemu/iov.h"
+#include "net/checksum.h"
+#include "net/tap.h"
+#include "net/net.h"
+
+enum {
+ VMXNET_TX_PKT_VHDR_FRAG = 0,
+ VMXNET_TX_PKT_L2HDR_FRAG,
+ VMXNET_TX_PKT_L3HDR_FRAG,
+ VMXNET_TX_PKT_PL_START_FRAG
+};
+
+/* TX packet private context */
+struct VmxnetTxPkt {
+ struct virtio_net_hdr virt_hdr;
+ bool has_virt_hdr;
+
+ struct iovec *raw;
+ uint32_t raw_frags;
+ uint32_t max_raw_frags;
+
+ struct iovec *vec;
+
+ uint8_t l2_hdr[ETH_MAX_L2_HDR_LEN];
+
+ uint32_t payload_len;
+
+ uint32_t payload_frags;
+ uint32_t max_payload_frags;
+
+ uint16_t hdr_len;
+ eth_pkt_types_e packet_type;
+ uint8_t l4proto;
+};
+
+void vmxnet_tx_pkt_init(struct VmxnetTxPkt **pkt, uint32_t max_frags,
+ bool has_virt_hdr)
+{
+ struct VmxnetTxPkt *p = g_malloc0(sizeof *p);
+
+ p->vec = g_malloc((sizeof *p->vec) *
+ (max_frags + VMXNET_TX_PKT_PL_START_FRAG));
+
+ p->raw = g_malloc((sizeof *p->raw) * max_frags);
+
+ p->max_payload_frags = max_frags;
+ p->max_raw_frags = max_frags;
+ p->has_virt_hdr = has_virt_hdr;
+ p->vec[VMXNET_TX_PKT_VHDR_FRAG].iov_base = &p->virt_hdr;
+ p->vec[VMXNET_TX_PKT_VHDR_FRAG].iov_len =
+ p->has_virt_hdr ? sizeof p->virt_hdr : 0;
+ p->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_base = &p->l2_hdr;
+ p->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base = NULL;
+ p->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len = 0;
+
+ *pkt = p;
+}
+
+void vmxnet_tx_pkt_uninit(struct VmxnetTxPkt *pkt)
+{
+ if (pkt) {
+ g_free(pkt->vec);
+ g_free(pkt->raw);
+ g_free(pkt);
+ }
+}
+
+void vmxnet_tx_pkt_update_ip_checksums(struct VmxnetTxPkt *pkt)
+{
+ uint16_t csum;
+ uint32_t ph_raw_csum;
+ assert(pkt);
+ uint8_t gso_type = pkt->virt_hdr.gso_type & ~VIRTIO_NET_HDR_GSO_ECN;
+ struct ip_header *ip_hdr;
+
+ if (VIRTIO_NET_HDR_GSO_TCPV4 != gso_type &&
+ VIRTIO_NET_HDR_GSO_UDP != gso_type) {
+ return;
+ }
+
+ ip_hdr = pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base;
+
+ if (pkt->payload_len + pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len >
+ ETH_MAX_IP_DGRAM_LEN) {
+ return;
+ }
+
+ ip_hdr->ip_len = cpu_to_be16(pkt->payload_len +
+ pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len);
+
+ /* Calculate IP header checksum */
+ ip_hdr->ip_sum = 0;
+ csum = net_raw_checksum((uint8_t *)ip_hdr,
+ pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len);
+ ip_hdr->ip_sum = cpu_to_be16(csum);
+
+ /* Calculate IP pseudo header checksum */
+ ph_raw_csum = eth_calc_pseudo_hdr_csum(ip_hdr, pkt->payload_len);
+ csum = cpu_to_be16(~net_checksum_finish(ph_raw_csum));
+ iov_from_buf(&pkt->vec[VMXNET_TX_PKT_PL_START_FRAG], pkt->payload_frags,
+ pkt->virt_hdr.csum_offset, &csum, sizeof(csum));
+}
+
+static void vmxnet_tx_pkt_calculate_hdr_len(struct VmxnetTxPkt *pkt)
+{
+ pkt->hdr_len = pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len +
+ pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len;
+}
+
+static bool vmxnet_tx_pkt_parse_headers(struct VmxnetTxPkt *pkt)
+{
+ struct iovec *l2_hdr, *l3_hdr;
+ size_t bytes_read;
+ size_t full_ip6hdr_len;
+ uint16_t l3_proto;
+
+ assert(pkt);
+
+ l2_hdr = &pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG];
+ l3_hdr = &pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG];
+
+ bytes_read = iov_to_buf(pkt->raw, pkt->raw_frags, 0, l2_hdr->iov_base,
+ ETH_MAX_L2_HDR_LEN);
+ if (bytes_read < sizeof(struct eth_header)) {
+ l2_hdr->iov_len = 0;
+ return false;
+ }
+
+ l2_hdr->iov_len = sizeof(struct eth_header);
+ switch (be16_to_cpu(PKT_GET_ETH_HDR(l2_hdr->iov_base)->h_proto)) {
+ case ETH_P_VLAN:
+ l2_hdr->iov_len += sizeof(struct vlan_header);
+ break;
+ case ETH_P_DVLAN:
+ l2_hdr->iov_len += 2 * sizeof(struct vlan_header);
+ break;
+ }
+
+ if (bytes_read < l2_hdr->iov_len) {
+ l2_hdr->iov_len = 0;
+ return false;
+ }
+
+ l3_proto = eth_get_l3_proto(l2_hdr->iov_base, l2_hdr->iov_len);
+
+ switch (l3_proto) {
+ case ETH_P_IP:
+ l3_hdr->iov_base = g_malloc(ETH_MAX_IP4_HDR_LEN);
+
+ bytes_read = iov_to_buf(pkt->raw, pkt->raw_frags, l2_hdr->iov_len,
+ l3_hdr->iov_base, sizeof(struct ip_header));
+
+ if (bytes_read < sizeof(struct ip_header)) {
+ l3_hdr->iov_len = 0;
+ return false;
+ }
+
+ l3_hdr->iov_len = IP_HDR_GET_LEN(l3_hdr->iov_base);
+ pkt->l4proto = ((struct ip_header *) l3_hdr->iov_base)->ip_p;
+
+ /* copy optional IPv4 header data */
+ bytes_read = iov_to_buf(pkt->raw, pkt->raw_frags,
+ l2_hdr->iov_len + sizeof(struct ip_header),
+ l3_hdr->iov_base + sizeof(struct ip_header),
+ l3_hdr->iov_len - sizeof(struct ip_header));
+ if (bytes_read < l3_hdr->iov_len - sizeof(struct ip_header)) {
+ l3_hdr->iov_len = 0;
+ return false;
+ }
+ break;
+
+ case ETH_P_IPV6:
+ if (!eth_parse_ipv6_hdr(pkt->raw, pkt->raw_frags, l2_hdr->iov_len,
+ &pkt->l4proto, &full_ip6hdr_len)) {
+ l3_hdr->iov_len = 0;
+ return false;
+ }
+
+ l3_hdr->iov_base = g_malloc(full_ip6hdr_len);
+
+ bytes_read = iov_to_buf(pkt->raw, pkt->raw_frags, l2_hdr->iov_len,
+ l3_hdr->iov_base, full_ip6hdr_len);
+
+ if (bytes_read < full_ip6hdr_len) {
+ l3_hdr->iov_len = 0;
+ return false;
+ } else {
+ l3_hdr->iov_len = full_ip6hdr_len;
+ }
+ break;
+
+ default:
+ l3_hdr->iov_len = 0;
+ break;
+ }
+
+ vmxnet_tx_pkt_calculate_hdr_len(pkt);
+ pkt->packet_type = get_eth_packet_type(l2_hdr->iov_base);
+ return true;
+}
+
+static bool vmxnet_tx_pkt_rebuild_payload(struct VmxnetTxPkt *pkt)
+{
+ size_t payload_len = iov_size(pkt->raw, pkt->raw_frags) - pkt->hdr_len;
+
+ pkt->payload_frags = iov_copy(&pkt->vec[VMXNET_TX_PKT_PL_START_FRAG],
+ pkt->max_payload_frags,
+ pkt->raw, pkt->raw_frags,
+ pkt->hdr_len, payload_len);
+
+ if (pkt->payload_frags != (uint32_t) -1) {
+ pkt->payload_len = payload_len;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool vmxnet_tx_pkt_parse(struct VmxnetTxPkt *pkt)
+{
+ return vmxnet_tx_pkt_parse_headers(pkt) &&
+ vmxnet_tx_pkt_rebuild_payload(pkt);
+}
+
+struct virtio_net_hdr *vmxnet_tx_pkt_get_vhdr(struct VmxnetTxPkt *pkt)
+{
+ assert(pkt);
+ return &pkt->virt_hdr;
+}
+
+static uint8_t vmxnet_tx_pkt_get_gso_type(struct VmxnetTxPkt *pkt,
+ bool tso_enable)
+{
+ uint8_t rc = VIRTIO_NET_HDR_GSO_NONE;
+ uint16_t l3_proto;
+
+ l3_proto = eth_get_l3_proto(pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_base,
+ pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len);
+
+ if (!tso_enable) {
+ goto func_exit;
+ }
+
+ rc = eth_get_gso_type(l3_proto, pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base,
+ pkt->l4proto);
+
+func_exit:
+ return rc;
+}
+
+void vmxnet_tx_pkt_build_vheader(struct VmxnetTxPkt *pkt, bool tso_enable,
+ bool csum_enable, uint32_t gso_size)
+{
+ struct tcp_hdr l4hdr;
+ assert(pkt);
+
+ /* csum has to be enabled if tso is. */
+ assert(csum_enable || !tso_enable);
+
+ pkt->virt_hdr.gso_type = vmxnet_tx_pkt_get_gso_type(pkt, tso_enable);
+
+ switch (pkt->virt_hdr.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
+ case VIRTIO_NET_HDR_GSO_NONE:
+ pkt->virt_hdr.hdr_len = 0;
+ pkt->virt_hdr.gso_size = 0;
+ break;
+
+ case VIRTIO_NET_HDR_GSO_UDP:
+ pkt->virt_hdr.gso_size = IP_FRAG_ALIGN_SIZE(gso_size);
+ pkt->virt_hdr.hdr_len = pkt->hdr_len + sizeof(struct udp_header);
+ break;
+
+ case VIRTIO_NET_HDR_GSO_TCPV4:
+ case VIRTIO_NET_HDR_GSO_TCPV6:
+ iov_to_buf(&pkt->vec[VMXNET_TX_PKT_PL_START_FRAG], pkt->payload_frags,
+ 0, &l4hdr, sizeof(l4hdr));
+ pkt->virt_hdr.hdr_len = pkt->hdr_len + l4hdr.th_off * sizeof(uint32_t);
+ pkt->virt_hdr.gso_size = IP_FRAG_ALIGN_SIZE(gso_size);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ if (csum_enable) {
+ switch (pkt->l4proto) {
+ case IP_PROTO_TCP:
+ pkt->virt_hdr.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
+ pkt->virt_hdr.csum_start = pkt->hdr_len;
+ pkt->virt_hdr.csum_offset = offsetof(struct tcp_hdr, th_sum);
+ break;
+ case IP_PROTO_UDP:
+ pkt->virt_hdr.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
+ pkt->virt_hdr.csum_start = pkt->hdr_len;
+ pkt->virt_hdr.csum_offset = offsetof(struct udp_hdr, uh_sum);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void vmxnet_tx_pkt_setup_vlan_header(struct VmxnetTxPkt *pkt, uint16_t vlan)
+{
+ bool is_new;
+ assert(pkt);
+
+ eth_setup_vlan_headers(pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_base,
+ vlan, &is_new);
+
+ /* update l2hdrlen */
+ if (is_new) {
+ pkt->hdr_len += sizeof(struct vlan_header);
+ pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len +=
+ sizeof(struct vlan_header);
+ }
+}
+
+bool vmxnet_tx_pkt_add_raw_fragment(struct VmxnetTxPkt *pkt, hwaddr pa,
+ size_t len)
+{
+ hwaddr mapped_len = 0;
+ struct iovec *ventry;
+ assert(pkt);
+ assert(pkt->max_raw_frags > pkt->raw_frags);
+
+ if (!len) {
+ return true;
+ }
+
+ ventry = &pkt->raw[pkt->raw_frags];
+ mapped_len = len;
+
+ ventry->iov_base = cpu_physical_memory_map(pa, &mapped_len, false);
+ ventry->iov_len = mapped_len;
+ pkt->raw_frags += !!ventry->iov_base;
+
+ if ((ventry->iov_base == NULL) || (len != mapped_len)) {
+ return false;
+ }
+
+ return true;
+}
+
+eth_pkt_types_e vmxnet_tx_pkt_get_packet_type(struct VmxnetTxPkt *pkt)
+{
+ assert(pkt);
+
+ return pkt->packet_type;
+}
+
+size_t vmxnet_tx_pkt_get_total_len(struct VmxnetTxPkt *pkt)
+{
+ assert(pkt);
+
+ return pkt->hdr_len + pkt->payload_len;
+}
+
+void vmxnet_tx_pkt_dump(struct VmxnetTxPkt *pkt)
+{
+#ifdef VMXNET_TX_PKT_DEBUG
+ assert(pkt);
+
+ printf("TX PKT: hdr_len: %d, pkt_type: 0x%X, l2hdr_len: %lu, "
+ "l3hdr_len: %lu, payload_len: %u\n", pkt->hdr_len, pkt->packet_type,
+ pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len,
+ pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len, pkt->payload_len);
+#endif
+}
+
+void vmxnet_tx_pkt_reset(struct VmxnetTxPkt *pkt)
+{
+ int i;
+
+ /* no assert, as reset can be called before tx_pkt_init */
+ if (!pkt) {
+ return;
+ }
+
+ memset(&pkt->virt_hdr, 0, sizeof(pkt->virt_hdr));
+
+ g_free(pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base);
+ pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base = NULL;
+
+ assert(pkt->vec);
+ for (i = VMXNET_TX_PKT_L2HDR_FRAG;
+ i < pkt->payload_frags + VMXNET_TX_PKT_PL_START_FRAG; i++) {
+ pkt->vec[i].iov_len = 0;
+ }
+ pkt->payload_len = 0;
+ pkt->payload_frags = 0;
+
+ assert(pkt->raw);
+ for (i = 0; i < pkt->raw_frags; i++) {
+ assert(pkt->raw[i].iov_base);
+ cpu_physical_memory_unmap(pkt->raw[i].iov_base, pkt->raw[i].iov_len,
+ false, pkt->raw[i].iov_len);
+ pkt->raw[i].iov_len = 0;
+ }
+ pkt->raw_frags = 0;
+
+ pkt->hdr_len = 0;
+ pkt->packet_type = 0;
+ pkt->l4proto = 0;
+}
+
+static void vmxnet_tx_pkt_do_sw_csum(struct VmxnetTxPkt *pkt)
+{
+ struct iovec *iov = &pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG];
+ uint32_t csum_cntr;
+ uint16_t csum = 0;
+ /* num of iovec without vhdr */
+ uint32_t iov_len = pkt->payload_frags + VMXNET_TX_PKT_PL_START_FRAG - 1;
+ uint16_t csl;
+ struct ip_header *iphdr;
+ size_t csum_offset = pkt->virt_hdr.csum_start + pkt->virt_hdr.csum_offset;
+
+ /* Put zero to checksum field */
+ iov_from_buf(iov, iov_len, csum_offset, &csum, sizeof csum);
+
+ /* Calculate L4 TCP/UDP checksum */
+ csl = pkt->payload_len;
+
+ /* data checksum */
+ csum_cntr =
+ net_checksum_add_iov(iov, iov_len, pkt->virt_hdr.csum_start, csl);
+ /* add pseudo header to csum */
+ iphdr = pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base;
+ csum_cntr += eth_calc_pseudo_hdr_csum(iphdr, csl);
+
+ /* Put the checksum obtained into the packet */
+ csum = cpu_to_be16(net_checksum_finish(csum_cntr));
+ iov_from_buf(iov, iov_len, csum_offset, &csum, sizeof csum);
+}
+
+enum {
+ VMXNET_TX_PKT_FRAGMENT_L2_HDR_POS = 0,
+ VMXNET_TX_PKT_FRAGMENT_L3_HDR_POS,
+ VMXNET_TX_PKT_FRAGMENT_HEADER_NUM
+};
+
+#define VMXNET_MAX_FRAG_SG_LIST (64)
+
+static size_t vmxnet_tx_pkt_fetch_fragment(struct VmxnetTxPkt *pkt,
+ int *src_idx, size_t *src_offset, struct iovec *dst, int *dst_idx)
+{
+ size_t fetched = 0;
+ struct iovec *src = pkt->vec;
+
+ *dst_idx = VMXNET_TX_PKT_FRAGMENT_HEADER_NUM;
+
+ while (fetched < pkt->virt_hdr.gso_size) {
+
+ /* no more place in fragment iov */
+ if (*dst_idx == VMXNET_MAX_FRAG_SG_LIST) {
+ break;
+ }
+
+ /* no more data in iovec */
+ if (*src_idx == (pkt->payload_frags + VMXNET_TX_PKT_PL_START_FRAG)) {
+ break;
+ }
+
+
+ dst[*dst_idx].iov_base = src[*src_idx].iov_base + *src_offset;
+ dst[*dst_idx].iov_len = MIN(src[*src_idx].iov_len - *src_offset,
+ pkt->virt_hdr.gso_size - fetched);
+
+ *src_offset += dst[*dst_idx].iov_len;
+ fetched += dst[*dst_idx].iov_len;
+
+ if (*src_offset == src[*src_idx].iov_len) {
+ *src_offset = 0;
+ (*src_idx)++;
+ }
+
+ (*dst_idx)++;
+ }
+
+ return fetched;
+}
+
+static bool vmxnet_tx_pkt_do_sw_fragmentation(struct VmxnetTxPkt *pkt,
+ NetClientState *nc)
+{
+ struct iovec fragment[VMXNET_MAX_FRAG_SG_LIST];
+ size_t fragment_len = 0;
+ bool more_frags = false;
+
+ /* some pointers for shorter code */
+ void *l2_iov_base, *l3_iov_base;
+ size_t l2_iov_len, l3_iov_len;
+ int src_idx = VMXNET_TX_PKT_PL_START_FRAG, dst_idx;
+ size_t src_offset = 0;
+ size_t fragment_offset = 0;
+
+ l2_iov_base = pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_base;
+ l2_iov_len = pkt->vec[VMXNET_TX_PKT_L2HDR_FRAG].iov_len;
+ l3_iov_base = pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_base;
+ l3_iov_len = pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len;
+
+ /* Copy headers */
+ fragment[VMXNET_TX_PKT_FRAGMENT_L2_HDR_POS].iov_base = l2_iov_base;
+ fragment[VMXNET_TX_PKT_FRAGMENT_L2_HDR_POS].iov_len = l2_iov_len;
+ fragment[VMXNET_TX_PKT_FRAGMENT_L3_HDR_POS].iov_base = l3_iov_base;
+ fragment[VMXNET_TX_PKT_FRAGMENT_L3_HDR_POS].iov_len = l3_iov_len;
+
+
+ /* Put as much data as possible and send */
+ do {
+ fragment_len = vmxnet_tx_pkt_fetch_fragment(pkt, &src_idx, &src_offset,
+ fragment, &dst_idx);
+
+ more_frags = (fragment_offset + fragment_len < pkt->payload_len);
+
+ eth_setup_ip4_fragmentation(l2_iov_base, l2_iov_len, l3_iov_base,
+ l3_iov_len, fragment_len, fragment_offset, more_frags);
+
+ eth_fix_ip4_checksum(l3_iov_base, l3_iov_len);
+
+ qemu_sendv_packet(nc, fragment, dst_idx);
+
+ fragment_offset += fragment_len;
+
+ } while (more_frags);
+
+ return true;
+}
+
+bool vmxnet_tx_pkt_send(struct VmxnetTxPkt *pkt, NetClientState *nc)
+{
+ assert(pkt);
+
+ if (!pkt->has_virt_hdr &&
+ pkt->virt_hdr.flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) {
+ vmxnet_tx_pkt_do_sw_csum(pkt);
+ }
+
+ /*
+ * Since underlying infrastructure does not support IP datagrams longer
+ * than 64K we should drop such packets and don't even try to send
+ */
+ if (VIRTIO_NET_HDR_GSO_NONE != pkt->virt_hdr.gso_type) {
+ if (pkt->payload_len >
+ ETH_MAX_IP_DGRAM_LEN -
+ pkt->vec[VMXNET_TX_PKT_L3HDR_FRAG].iov_len) {
+ return false;
+ }
+ }
+
+ if (pkt->has_virt_hdr ||
+ pkt->virt_hdr.gso_type == VIRTIO_NET_HDR_GSO_NONE) {
+ qemu_sendv_packet(nc, pkt->vec,
+ pkt->payload_frags + VMXNET_TX_PKT_PL_START_FRAG);
+ return true;
+ }
+
+ return vmxnet_tx_pkt_do_sw_fragmentation(pkt, nc);
+}
diff --git a/hw/net/vmxnet_tx_pkt.h b/hw/net/vmxnet_tx_pkt.h
new file mode 100644
index 00000000..57121a6f
--- /dev/null
+++ b/hw/net/vmxnet_tx_pkt.h
@@ -0,0 +1,148 @@
+/*
+ * QEMU VMWARE VMXNET* paravirtual NICs - TX packets abstraction
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Tamir Shomer <tamirs@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef VMXNET_TX_PKT_H
+#define VMXNET_TX_PKT_H
+
+#include "stdint.h"
+#include "stdbool.h"
+#include "net/eth.h"
+#include "exec/hwaddr.h"
+
+/* define to enable packet dump functions */
+/*#define VMXNET_TX_PKT_DEBUG*/
+
+struct VmxnetTxPkt;
+
+/**
+ * Init function for tx packet functionality
+ *
+ * @pkt: packet pointer
+ * @max_frags: max tx ip fragments
+ * @has_virt_hdr: device uses virtio header.
+ */
+void vmxnet_tx_pkt_init(struct VmxnetTxPkt **pkt, uint32_t max_frags,
+ bool has_virt_hdr);
+
+/**
+ * Clean all tx packet resources.
+ *
+ * @pkt: packet.
+ */
+void vmxnet_tx_pkt_uninit(struct VmxnetTxPkt *pkt);
+
+/**
+ * get virtio header
+ *
+ * @pkt: packet
+ * @ret: virtio header
+ */
+struct virtio_net_hdr *vmxnet_tx_pkt_get_vhdr(struct VmxnetTxPkt *pkt);
+
+/**
+ * build virtio header (will be stored in module context)
+ *
+ * @pkt: packet
+ * @tso_enable: TSO enabled
+ * @csum_enable: CSO enabled
+ * @gso_size: MSS size for TSO
+ *
+ */
+void vmxnet_tx_pkt_build_vheader(struct VmxnetTxPkt *pkt, bool tso_enable,
+ bool csum_enable, uint32_t gso_size);
+
+/**
+ * updates vlan tag, and adds vlan header in case it is missing
+ *
+ * @pkt: packet
+ * @vlan: VLAN tag
+ *
+ */
+void vmxnet_tx_pkt_setup_vlan_header(struct VmxnetTxPkt *pkt, uint16_t vlan);
+
+/**
+ * populate data fragment into pkt context.
+ *
+ * @pkt: packet
+ * @pa: physical address of fragment
+ * @len: length of fragment
+ *
+ */
+bool vmxnet_tx_pkt_add_raw_fragment(struct VmxnetTxPkt *pkt, hwaddr pa,
+ size_t len);
+
+/**
+ * fix ip header fields and calculate checksums needed.
+ *
+ * @pkt: packet
+ *
+ */
+void vmxnet_tx_pkt_update_ip_checksums(struct VmxnetTxPkt *pkt);
+
+/**
+ * get length of all populated data.
+ *
+ * @pkt: packet
+ * @ret: total data length
+ *
+ */
+size_t vmxnet_tx_pkt_get_total_len(struct VmxnetTxPkt *pkt);
+
+/**
+ * get packet type
+ *
+ * @pkt: packet
+ * @ret: packet type
+ *
+ */
+eth_pkt_types_e vmxnet_tx_pkt_get_packet_type(struct VmxnetTxPkt *pkt);
+
+/**
+ * prints packet data if debug is enabled
+ *
+ * @pkt: packet
+ *
+ */
+void vmxnet_tx_pkt_dump(struct VmxnetTxPkt *pkt);
+
+/**
+ * reset tx packet private context (needed to be called between packets)
+ *
+ * @pkt: packet
+ *
+ */
+void vmxnet_tx_pkt_reset(struct VmxnetTxPkt *pkt);
+
+/**
+ * Send packet to qemu. handles sw offloads if vhdr is not supported.
+ *
+ * @pkt: packet
+ * @nc: NetClientState
+ * @ret: operation result
+ *
+ */
+bool vmxnet_tx_pkt_send(struct VmxnetTxPkt *pkt, NetClientState *nc);
+
+/**
+ * parse raw packet data and analyze offload requirements.
+ *
+ * @pkt: packet
+ *
+ */
+bool vmxnet_tx_pkt_parse(struct VmxnetTxPkt *pkt);
+
+#endif
diff --git a/hw/net/xen_nic.c b/hw/net/xen_nic.c
new file mode 100644
index 00000000..d7cbfc10
--- /dev/null
+++ b/hw/net/xen_nic.c
@@ -0,0 +1,422 @@
+/*
+ * xen paravirt network card backend
+ *
+ * (c) Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include "hw/hw.h"
+#include "net/net.h"
+#include "net/checksum.h"
+#include "net/util.h"
+#include "hw/xen/xen_backend.h"
+
+#include <xen/io/netif.h>
+
+/* ------------------------------------------------------------- */
+
+struct XenNetDev {
+ struct XenDevice xendev; /* must be first */
+ char *mac;
+ int tx_work;
+ int tx_ring_ref;
+ int rx_ring_ref;
+ struct netif_tx_sring *txs;
+ struct netif_rx_sring *rxs;
+ netif_tx_back_ring_t tx_ring;
+ netif_rx_back_ring_t rx_ring;
+ NICConf conf;
+ NICState *nic;
+};
+
+/* ------------------------------------------------------------- */
+
+static void net_tx_response(struct XenNetDev *netdev, netif_tx_request_t *txp, int8_t st)
+{
+ RING_IDX i = netdev->tx_ring.rsp_prod_pvt;
+ netif_tx_response_t *resp;
+ int notify;
+
+ resp = RING_GET_RESPONSE(&netdev->tx_ring, i);
+ resp->id = txp->id;
+ resp->status = st;
+
+#if 0
+ if (txp->flags & NETTXF_extra_info) {
+ RING_GET_RESPONSE(&netdev->tx_ring, ++i)->status = NETIF_RSP_NULL;
+ }
+#endif
+
+ netdev->tx_ring.rsp_prod_pvt = ++i;
+ RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&netdev->tx_ring, notify);
+ if (notify) {
+ xen_be_send_notify(&netdev->xendev);
+ }
+
+ if (i == netdev->tx_ring.req_cons) {
+ int more_to_do;
+ RING_FINAL_CHECK_FOR_REQUESTS(&netdev->tx_ring, more_to_do);
+ if (more_to_do) {
+ netdev->tx_work++;
+ }
+ }
+}
+
+static void net_tx_error(struct XenNetDev *netdev, netif_tx_request_t *txp, RING_IDX end)
+{
+#if 0
+ /*
+ * Hmm, why netback fails everything in the ring?
+ * Should we do that even when not supporting SG and TSO?
+ */
+ RING_IDX cons = netdev->tx_ring.req_cons;
+
+ do {
+ make_tx_response(netif, txp, NETIF_RSP_ERROR);
+ if (cons >= end) {
+ break;
+ }
+ txp = RING_GET_REQUEST(&netdev->tx_ring, cons++);
+ } while (1);
+ netdev->tx_ring.req_cons = cons;
+ netif_schedule_work(netif);
+ netif_put(netif);
+#else
+ net_tx_response(netdev, txp, NETIF_RSP_ERROR);
+#endif
+}
+
+static void net_tx_packets(struct XenNetDev *netdev)
+{
+ netif_tx_request_t txreq;
+ RING_IDX rc, rp;
+ void *page;
+ void *tmpbuf = NULL;
+
+ for (;;) {
+ rc = netdev->tx_ring.req_cons;
+ rp = netdev->tx_ring.sring->req_prod;
+ xen_rmb(); /* Ensure we see queued requests up to 'rp'. */
+
+ while ((rc != rp)) {
+ if (RING_REQUEST_CONS_OVERFLOW(&netdev->tx_ring, rc)) {
+ break;
+ }
+ memcpy(&txreq, RING_GET_REQUEST(&netdev->tx_ring, rc), sizeof(txreq));
+ netdev->tx_ring.req_cons = ++rc;
+
+#if 1
+ /* should not happen in theory, we don't announce the *
+ * feature-{sg,gso,whatelse} flags in xenstore (yet?) */
+ if (txreq.flags & NETTXF_extra_info) {
+ xen_be_printf(&netdev->xendev, 0, "FIXME: extra info flag\n");
+ net_tx_error(netdev, &txreq, rc);
+ continue;
+ }
+ if (txreq.flags & NETTXF_more_data) {
+ xen_be_printf(&netdev->xendev, 0, "FIXME: more data flag\n");
+ net_tx_error(netdev, &txreq, rc);
+ continue;
+ }
+#endif
+
+ if (txreq.size < 14) {
+ xen_be_printf(&netdev->xendev, 0, "bad packet size: %d\n", txreq.size);
+ net_tx_error(netdev, &txreq, rc);
+ continue;
+ }
+
+ if ((txreq.offset + txreq.size) > XC_PAGE_SIZE) {
+ xen_be_printf(&netdev->xendev, 0, "error: page crossing\n");
+ net_tx_error(netdev, &txreq, rc);
+ continue;
+ }
+
+ xen_be_printf(&netdev->xendev, 3, "tx packet ref %d, off %d, len %d, flags 0x%x%s%s%s%s\n",
+ txreq.gref, txreq.offset, txreq.size, txreq.flags,
+ (txreq.flags & NETTXF_csum_blank) ? " csum_blank" : "",
+ (txreq.flags & NETTXF_data_validated) ? " data_validated" : "",
+ (txreq.flags & NETTXF_more_data) ? " more_data" : "",
+ (txreq.flags & NETTXF_extra_info) ? " extra_info" : "");
+
+ page = xc_gnttab_map_grant_ref(netdev->xendev.gnttabdev,
+ netdev->xendev.dom,
+ txreq.gref, PROT_READ);
+ if (page == NULL) {
+ xen_be_printf(&netdev->xendev, 0, "error: tx gref dereference failed (%d)\n",
+ txreq.gref);
+ net_tx_error(netdev, &txreq, rc);
+ continue;
+ }
+ if (txreq.flags & NETTXF_csum_blank) {
+ /* have read-only mapping -> can't fill checksum in-place */
+ if (!tmpbuf) {
+ tmpbuf = g_malloc(XC_PAGE_SIZE);
+ }
+ memcpy(tmpbuf, page + txreq.offset, txreq.size);
+ net_checksum_calculate(tmpbuf, txreq.size);
+ qemu_send_packet(qemu_get_queue(netdev->nic), tmpbuf,
+ txreq.size);
+ } else {
+ qemu_send_packet(qemu_get_queue(netdev->nic),
+ page + txreq.offset, txreq.size);
+ }
+ xc_gnttab_munmap(netdev->xendev.gnttabdev, page, 1);
+ net_tx_response(netdev, &txreq, NETIF_RSP_OKAY);
+ }
+ if (!netdev->tx_work) {
+ break;
+ }
+ netdev->tx_work = 0;
+ }
+ g_free(tmpbuf);
+}
+
+/* ------------------------------------------------------------- */
+
+static void net_rx_response(struct XenNetDev *netdev,
+ netif_rx_request_t *req, int8_t st,
+ uint16_t offset, uint16_t size,
+ uint16_t flags)
+{
+ RING_IDX i = netdev->rx_ring.rsp_prod_pvt;
+ netif_rx_response_t *resp;
+ int notify;
+
+ resp = RING_GET_RESPONSE(&netdev->rx_ring, i);
+ resp->offset = offset;
+ resp->flags = flags;
+ resp->id = req->id;
+ resp->status = (int16_t)size;
+ if (st < 0) {
+ resp->status = (int16_t)st;
+ }
+
+ xen_be_printf(&netdev->xendev, 3, "rx response: idx %d, status %d, flags 0x%x\n",
+ i, resp->status, resp->flags);
+
+ netdev->rx_ring.rsp_prod_pvt = ++i;
+ RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&netdev->rx_ring, notify);
+ if (notify) {
+ xen_be_send_notify(&netdev->xendev);
+ }
+}
+
+#define NET_IP_ALIGN 2
+
+static ssize_t net_rx_packet(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ struct XenNetDev *netdev = qemu_get_nic_opaque(nc);
+ netif_rx_request_t rxreq;
+ RING_IDX rc, rp;
+ void *page;
+
+ if (netdev->xendev.be_state != XenbusStateConnected) {
+ return -1;
+ }
+
+ rc = netdev->rx_ring.req_cons;
+ rp = netdev->rx_ring.sring->req_prod;
+ xen_rmb(); /* Ensure we see queued requests up to 'rp'. */
+
+ if (rc == rp || RING_REQUEST_CONS_OVERFLOW(&netdev->rx_ring, rc)) {
+ return 0;
+ }
+ if (size > XC_PAGE_SIZE - NET_IP_ALIGN) {
+ xen_be_printf(&netdev->xendev, 0, "packet too big (%lu > %ld)",
+ (unsigned long)size, XC_PAGE_SIZE - NET_IP_ALIGN);
+ return -1;
+ }
+
+ memcpy(&rxreq, RING_GET_REQUEST(&netdev->rx_ring, rc), sizeof(rxreq));
+ netdev->rx_ring.req_cons = ++rc;
+
+ page = xc_gnttab_map_grant_ref(netdev->xendev.gnttabdev,
+ netdev->xendev.dom,
+ rxreq.gref, PROT_WRITE);
+ if (page == NULL) {
+ xen_be_printf(&netdev->xendev, 0, "error: rx gref dereference failed (%d)\n",
+ rxreq.gref);
+ net_rx_response(netdev, &rxreq, NETIF_RSP_ERROR, 0, 0, 0);
+ return -1;
+ }
+ memcpy(page + NET_IP_ALIGN, buf, size);
+ xc_gnttab_munmap(netdev->xendev.gnttabdev, page, 1);
+ net_rx_response(netdev, &rxreq, NETIF_RSP_OKAY, NET_IP_ALIGN, size, 0);
+
+ return size;
+}
+
+/* ------------------------------------------------------------- */
+
+static NetClientInfo net_xen_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = net_rx_packet,
+};
+
+static int net_init(struct XenDevice *xendev)
+{
+ struct XenNetDev *netdev = container_of(xendev, struct XenNetDev, xendev);
+
+ /* read xenstore entries */
+ if (netdev->mac == NULL) {
+ netdev->mac = xenstore_read_be_str(&netdev->xendev, "mac");
+ }
+
+ /* do we have all we need? */
+ if (netdev->mac == NULL) {
+ return -1;
+ }
+
+ if (net_parse_macaddr(netdev->conf.macaddr.a, netdev->mac) < 0) {
+ return -1;
+ }
+
+ netdev->nic = qemu_new_nic(&net_xen_info, &netdev->conf,
+ "xen", NULL, netdev);
+
+ snprintf(qemu_get_queue(netdev->nic)->info_str,
+ sizeof(qemu_get_queue(netdev->nic)->info_str),
+ "nic: xenbus vif macaddr=%s", netdev->mac);
+
+ /* fill info */
+ xenstore_write_be_int(&netdev->xendev, "feature-rx-copy", 1);
+ xenstore_write_be_int(&netdev->xendev, "feature-rx-flip", 0);
+
+ return 0;
+}
+
+static int net_connect(struct XenDevice *xendev)
+{
+ struct XenNetDev *netdev = container_of(xendev, struct XenNetDev, xendev);
+ int rx_copy;
+
+ if (xenstore_read_fe_int(&netdev->xendev, "tx-ring-ref",
+ &netdev->tx_ring_ref) == -1) {
+ return -1;
+ }
+ if (xenstore_read_fe_int(&netdev->xendev, "rx-ring-ref",
+ &netdev->rx_ring_ref) == -1) {
+ return 1;
+ }
+ if (xenstore_read_fe_int(&netdev->xendev, "event-channel",
+ &netdev->xendev.remote_port) == -1) {
+ return -1;
+ }
+
+ if (xenstore_read_fe_int(&netdev->xendev, "request-rx-copy", &rx_copy) == -1) {
+ rx_copy = 0;
+ }
+ if (rx_copy == 0) {
+ xen_be_printf(&netdev->xendev, 0, "frontend doesn't support rx-copy.\n");
+ return -1;
+ }
+
+ netdev->txs = xc_gnttab_map_grant_ref(netdev->xendev.gnttabdev,
+ netdev->xendev.dom,
+ netdev->tx_ring_ref,
+ PROT_READ | PROT_WRITE);
+ if (!netdev->txs) {
+ return -1;
+ }
+ netdev->rxs = xc_gnttab_map_grant_ref(netdev->xendev.gnttabdev,
+ netdev->xendev.dom,
+ netdev->rx_ring_ref,
+ PROT_READ | PROT_WRITE);
+ if (!netdev->rxs) {
+ xc_gnttab_munmap(netdev->xendev.gnttabdev, netdev->txs, 1);
+ netdev->txs = NULL;
+ return -1;
+ }
+ BACK_RING_INIT(&netdev->tx_ring, netdev->txs, XC_PAGE_SIZE);
+ BACK_RING_INIT(&netdev->rx_ring, netdev->rxs, XC_PAGE_SIZE);
+
+ xen_be_bind_evtchn(&netdev->xendev);
+
+ xen_be_printf(&netdev->xendev, 1, "ok: tx-ring-ref %d, rx-ring-ref %d, "
+ "remote port %d, local port %d\n",
+ netdev->tx_ring_ref, netdev->rx_ring_ref,
+ netdev->xendev.remote_port, netdev->xendev.local_port);
+
+ net_tx_packets(netdev);
+ return 0;
+}
+
+static void net_disconnect(struct XenDevice *xendev)
+{
+ struct XenNetDev *netdev = container_of(xendev, struct XenNetDev, xendev);
+
+ xen_be_unbind_evtchn(&netdev->xendev);
+
+ if (netdev->txs) {
+ xc_gnttab_munmap(netdev->xendev.gnttabdev, netdev->txs, 1);
+ netdev->txs = NULL;
+ }
+ if (netdev->rxs) {
+ xc_gnttab_munmap(netdev->xendev.gnttabdev, netdev->rxs, 1);
+ netdev->rxs = NULL;
+ }
+}
+
+static void net_event(struct XenDevice *xendev)
+{
+ struct XenNetDev *netdev = container_of(xendev, struct XenNetDev, xendev);
+ net_tx_packets(netdev);
+ qemu_flush_queued_packets(qemu_get_queue(netdev->nic));
+}
+
+static int net_free(struct XenDevice *xendev)
+{
+ struct XenNetDev *netdev = container_of(xendev, struct XenNetDev, xendev);
+
+ if (netdev->nic) {
+ qemu_del_nic(netdev->nic);
+ netdev->nic = NULL;
+ }
+ g_free(netdev->mac);
+ netdev->mac = NULL;
+ return 0;
+}
+
+/* ------------------------------------------------------------- */
+
+struct XenDevOps xen_netdev_ops = {
+ .size = sizeof(struct XenNetDev),
+ .flags = DEVOPS_FLAG_NEED_GNTDEV,
+ .init = net_init,
+ .initialise = net_connect,
+ .event = net_event,
+ .disconnect = net_disconnect,
+ .free = net_free,
+};
diff --git a/hw/net/xgmac.c b/hw/net/xgmac.c
new file mode 100644
index 00000000..15fb6819
--- /dev/null
+++ b/hw/net/xgmac.c
@@ -0,0 +1,432 @@
+/*
+ * QEMU model of XGMAC Ethernet.
+ *
+ * derived from the Xilinx AXI-Ethernet by Edgar E. Iglesias.
+ *
+ * Copyright (c) 2011 Calxeda, Inc.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+#include "qemu/log.h"
+#include "net/net.h"
+#include "net/checksum.h"
+
+#ifdef DEBUG_XGMAC
+#define DEBUGF_BRK(message, args...) do { \
+ fprintf(stderr, (message), ## args); \
+ } while (0)
+#else
+#define DEBUGF_BRK(message, args...) do { } while (0)
+#endif
+
+#define XGMAC_CONTROL 0x00000000 /* MAC Configuration */
+#define XGMAC_FRAME_FILTER 0x00000001 /* MAC Frame Filter */
+#define XGMAC_FLOW_CTRL 0x00000006 /* MAC Flow Control */
+#define XGMAC_VLAN_TAG 0x00000007 /* VLAN Tags */
+#define XGMAC_VERSION 0x00000008 /* Version */
+/* VLAN tag for insertion or replacement into tx frames */
+#define XGMAC_VLAN_INCL 0x00000009
+#define XGMAC_LPI_CTRL 0x0000000a /* LPI Control and Status */
+#define XGMAC_LPI_TIMER 0x0000000b /* LPI Timers Control */
+#define XGMAC_TX_PACE 0x0000000c /* Transmit Pace and Stretch */
+#define XGMAC_VLAN_HASH 0x0000000d /* VLAN Hash Table */
+#define XGMAC_DEBUG 0x0000000e /* Debug */
+#define XGMAC_INT_STATUS 0x0000000f /* Interrupt and Control */
+/* HASH table registers */
+#define XGMAC_HASH(n) ((0x00000300/4) + (n))
+#define XGMAC_NUM_HASH 16
+/* Operation Mode */
+#define XGMAC_OPMODE (0x00000400/4)
+/* Remote Wake-Up Frame Filter */
+#define XGMAC_REMOTE_WAKE (0x00000700/4)
+/* PMT Control and Status */
+#define XGMAC_PMT (0x00000704/4)
+
+#define XGMAC_ADDR_HIGH(reg) (0x00000010+((reg) * 2))
+#define XGMAC_ADDR_LOW(reg) (0x00000011+((reg) * 2))
+
+#define DMA_BUS_MODE 0x000003c0 /* Bus Mode */
+#define DMA_XMT_POLL_DEMAND 0x000003c1 /* Transmit Poll Demand */
+#define DMA_RCV_POLL_DEMAND 0x000003c2 /* Received Poll Demand */
+#define DMA_RCV_BASE_ADDR 0x000003c3 /* Receive List Base */
+#define DMA_TX_BASE_ADDR 0x000003c4 /* Transmit List Base */
+#define DMA_STATUS 0x000003c5 /* Status Register */
+#define DMA_CONTROL 0x000003c6 /* Ctrl (Operational Mode) */
+#define DMA_INTR_ENA 0x000003c7 /* Interrupt Enable */
+#define DMA_MISSED_FRAME_CTR 0x000003c8 /* Missed Frame Counter */
+/* Receive Interrupt Watchdog Timer */
+#define DMA_RI_WATCHDOG_TIMER 0x000003c9
+#define DMA_AXI_BUS 0x000003ca /* AXI Bus Mode */
+#define DMA_AXI_STATUS 0x000003cb /* AXI Status */
+#define DMA_CUR_TX_DESC_ADDR 0x000003d2 /* Current Host Tx Descriptor */
+#define DMA_CUR_RX_DESC_ADDR 0x000003d3 /* Current Host Rx Descriptor */
+#define DMA_CUR_TX_BUF_ADDR 0x000003d4 /* Current Host Tx Buffer */
+#define DMA_CUR_RX_BUF_ADDR 0x000003d5 /* Current Host Rx Buffer */
+#define DMA_HW_FEATURE 0x000003d6 /* Enabled Hardware Features */
+
+/* DMA Status register defines */
+#define DMA_STATUS_GMI 0x08000000 /* MMC interrupt */
+#define DMA_STATUS_GLI 0x04000000 /* GMAC Line interface int */
+#define DMA_STATUS_EB_MASK 0x00380000 /* Error Bits Mask */
+#define DMA_STATUS_EB_TX_ABORT 0x00080000 /* Error Bits - TX Abort */
+#define DMA_STATUS_EB_RX_ABORT 0x00100000 /* Error Bits - RX Abort */
+#define DMA_STATUS_TS_MASK 0x00700000 /* Transmit Process State */
+#define DMA_STATUS_TS_SHIFT 20
+#define DMA_STATUS_RS_MASK 0x000e0000 /* Receive Process State */
+#define DMA_STATUS_RS_SHIFT 17
+#define DMA_STATUS_NIS 0x00010000 /* Normal Interrupt Summary */
+#define DMA_STATUS_AIS 0x00008000 /* Abnormal Interrupt Summary */
+#define DMA_STATUS_ERI 0x00004000 /* Early Receive Interrupt */
+#define DMA_STATUS_FBI 0x00002000 /* Fatal Bus Error Interrupt */
+#define DMA_STATUS_ETI 0x00000400 /* Early Transmit Interrupt */
+#define DMA_STATUS_RWT 0x00000200 /* Receive Watchdog Timeout */
+#define DMA_STATUS_RPS 0x00000100 /* Receive Process Stopped */
+#define DMA_STATUS_RU 0x00000080 /* Receive Buffer Unavailable */
+#define DMA_STATUS_RI 0x00000040 /* Receive Interrupt */
+#define DMA_STATUS_UNF 0x00000020 /* Transmit Underflow */
+#define DMA_STATUS_OVF 0x00000010 /* Receive Overflow */
+#define DMA_STATUS_TJT 0x00000008 /* Transmit Jabber Timeout */
+#define DMA_STATUS_TU 0x00000004 /* Transmit Buffer Unavailable */
+#define DMA_STATUS_TPS 0x00000002 /* Transmit Process Stopped */
+#define DMA_STATUS_TI 0x00000001 /* Transmit Interrupt */
+
+/* DMA Control register defines */
+#define DMA_CONTROL_ST 0x00002000 /* Start/Stop Transmission */
+#define DMA_CONTROL_SR 0x00000002 /* Start/Stop Receive */
+#define DMA_CONTROL_DFF 0x01000000 /* Disable flush of rx frames */
+
+struct desc {
+ uint32_t ctl_stat;
+ uint16_t buffer1_size;
+ uint16_t buffer2_size;
+ uint32_t buffer1_addr;
+ uint32_t buffer2_addr;
+ uint32_t ext_stat;
+ uint32_t res[3];
+};
+
+#define R_MAX 0x400
+
+typedef struct RxTxStats {
+ uint64_t rx_bytes;
+ uint64_t tx_bytes;
+
+ uint64_t rx;
+ uint64_t rx_bcast;
+ uint64_t rx_mcast;
+} RxTxStats;
+
+#define TYPE_XGMAC "xgmac"
+#define XGMAC(obj) OBJECT_CHECK(XgmacState, (obj), TYPE_XGMAC)
+
+typedef struct XgmacState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq sbd_irq;
+ qemu_irq pmt_irq;
+ qemu_irq mci_irq;
+ NICState *nic;
+ NICConf conf;
+
+ struct RxTxStats stats;
+ uint32_t regs[R_MAX];
+} XgmacState;
+
+static const VMStateDescription vmstate_rxtx_stats = {
+ .name = "xgmac_stats",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(rx_bytes, RxTxStats),
+ VMSTATE_UINT64(tx_bytes, RxTxStats),
+ VMSTATE_UINT64(rx, RxTxStats),
+ VMSTATE_UINT64(rx_bcast, RxTxStats),
+ VMSTATE_UINT64(rx_mcast, RxTxStats),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_xgmac = {
+ .name = "xgmac",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(stats, XgmacState, 0, vmstate_rxtx_stats, RxTxStats),
+ VMSTATE_UINT32_ARRAY(regs, XgmacState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void xgmac_read_desc(XgmacState *s, struct desc *d, int rx)
+{
+ uint32_t addr = rx ? s->regs[DMA_CUR_RX_DESC_ADDR] :
+ s->regs[DMA_CUR_TX_DESC_ADDR];
+ cpu_physical_memory_read(addr, d, sizeof(*d));
+}
+
+static void xgmac_write_desc(XgmacState *s, struct desc *d, int rx)
+{
+ int reg = rx ? DMA_CUR_RX_DESC_ADDR : DMA_CUR_TX_DESC_ADDR;
+ uint32_t addr = s->regs[reg];
+
+ if (!rx && (d->ctl_stat & 0x00200000)) {
+ s->regs[reg] = s->regs[DMA_TX_BASE_ADDR];
+ } else if (rx && (d->buffer1_size & 0x8000)) {
+ s->regs[reg] = s->regs[DMA_RCV_BASE_ADDR];
+ } else {
+ s->regs[reg] += sizeof(*d);
+ }
+ cpu_physical_memory_write(addr, d, sizeof(*d));
+}
+
+static void xgmac_enet_send(XgmacState *s)
+{
+ struct desc bd;
+ int frame_size;
+ int len;
+ uint8_t frame[8192];
+ uint8_t *ptr;
+
+ ptr = frame;
+ frame_size = 0;
+ while (1) {
+ xgmac_read_desc(s, &bd, 0);
+ if ((bd.ctl_stat & 0x80000000) == 0) {
+ /* Run out of descriptors to transmit. */
+ break;
+ }
+ len = (bd.buffer1_size & 0xfff) + (bd.buffer2_size & 0xfff);
+
+ if ((bd.buffer1_size & 0xfff) > 2048) {
+ DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- "
+ "xgmac buffer 1 len on send > 2048 (0x%x)\n",
+ __func__, bd.buffer1_size & 0xfff);
+ }
+ if ((bd.buffer2_size & 0xfff) != 0) {
+ DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- "
+ "xgmac buffer 2 len on send != 0 (0x%x)\n",
+ __func__, bd.buffer2_size & 0xfff);
+ }
+ if (len >= sizeof(frame)) {
+ DEBUGF_BRK("qemu:%s: buffer overflow %d read into %zu "
+ "buffer\n" , __func__, len, sizeof(frame));
+ DEBUGF_BRK("qemu:%s: buffer1.size=%d; buffer2.size=%d\n",
+ __func__, bd.buffer1_size, bd.buffer2_size);
+ }
+
+ cpu_physical_memory_read(bd.buffer1_addr, ptr, len);
+ ptr += len;
+ frame_size += len;
+ if (bd.ctl_stat & 0x20000000) {
+ /* Last buffer in frame. */
+ qemu_send_packet(qemu_get_queue(s->nic), frame, len);
+ ptr = frame;
+ frame_size = 0;
+ s->regs[DMA_STATUS] |= DMA_STATUS_TI | DMA_STATUS_NIS;
+ }
+ bd.ctl_stat &= ~0x80000000;
+ /* Write back the modified descriptor. */
+ xgmac_write_desc(s, &bd, 0);
+ }
+}
+
+static void enet_update_irq(XgmacState *s)
+{
+ int stat = s->regs[DMA_STATUS] & s->regs[DMA_INTR_ENA];
+ qemu_set_irq(s->sbd_irq, !!stat);
+}
+
+static uint64_t enet_read(void *opaque, hwaddr addr, unsigned size)
+{
+ XgmacState *s = opaque;
+ uint64_t r = 0;
+ addr >>= 2;
+
+ switch (addr) {
+ case XGMAC_VERSION:
+ r = 0x1012;
+ break;
+ default:
+ if (addr < ARRAY_SIZE(s->regs)) {
+ r = s->regs[addr];
+ }
+ break;
+ }
+ return r;
+}
+
+static void enet_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ XgmacState *s = opaque;
+
+ addr >>= 2;
+ switch (addr) {
+ case DMA_BUS_MODE:
+ s->regs[DMA_BUS_MODE] = value & ~0x1;
+ break;
+ case DMA_XMT_POLL_DEMAND:
+ xgmac_enet_send(s);
+ break;
+ case DMA_STATUS:
+ s->regs[DMA_STATUS] = s->regs[DMA_STATUS] & ~value;
+ break;
+ case DMA_RCV_BASE_ADDR:
+ s->regs[DMA_RCV_BASE_ADDR] = s->regs[DMA_CUR_RX_DESC_ADDR] = value;
+ break;
+ case DMA_TX_BASE_ADDR:
+ s->regs[DMA_TX_BASE_ADDR] = s->regs[DMA_CUR_TX_DESC_ADDR] = value;
+ break;
+ default:
+ if (addr < ARRAY_SIZE(s->regs)) {
+ s->regs[addr] = value;
+ }
+ break;
+ }
+ enet_update_irq(s);
+}
+
+static const MemoryRegionOps enet_mem_ops = {
+ .read = enet_read,
+ .write = enet_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static int eth_can_rx(XgmacState *s)
+{
+ /* RX enabled? */
+ return s->regs[DMA_CONTROL] & DMA_CONTROL_SR;
+}
+
+static ssize_t eth_rx(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ XgmacState *s = qemu_get_nic_opaque(nc);
+ static const unsigned char sa_bcast[6] = {0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff};
+ int unicast, broadcast, multicast;
+ struct desc bd;
+ ssize_t ret;
+
+ if (!eth_can_rx(s)) {
+ return -1;
+ }
+ unicast = ~buf[0] & 0x1;
+ broadcast = memcmp(buf, sa_bcast, 6) == 0;
+ multicast = !unicast && !broadcast;
+ if (size < 12) {
+ s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS;
+ ret = -1;
+ goto out;
+ }
+
+ xgmac_read_desc(s, &bd, 1);
+ if ((bd.ctl_stat & 0x80000000) == 0) {
+ s->regs[DMA_STATUS] |= DMA_STATUS_RU | DMA_STATUS_AIS;
+ ret = size;
+ goto out;
+ }
+
+ cpu_physical_memory_write(bd.buffer1_addr, buf, size);
+
+ /* Add in the 4 bytes for crc (the real hw returns length incl crc) */
+ size += 4;
+ bd.ctl_stat = (size << 16) | 0x300;
+ xgmac_write_desc(s, &bd, 1);
+
+ s->stats.rx_bytes += size;
+ s->stats.rx++;
+ if (multicast) {
+ s->stats.rx_mcast++;
+ } else if (broadcast) {
+ s->stats.rx_bcast++;
+ }
+
+ s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS;
+ ret = size;
+
+out:
+ enet_update_irq(s);
+ return ret;
+}
+
+static NetClientInfo net_xgmac_enet_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = eth_rx,
+};
+
+static int xgmac_enet_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ XgmacState *s = XGMAC(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &enet_mem_ops, s,
+ "xgmac", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->sbd_irq);
+ sysbus_init_irq(sbd, &s->pmt_irq);
+ sysbus_init_irq(sbd, &s->mci_irq);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(&net_xgmac_enet_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ s->regs[XGMAC_ADDR_HIGH(0)] = (s->conf.macaddr.a[5] << 8) |
+ s->conf.macaddr.a[4];
+ s->regs[XGMAC_ADDR_LOW(0)] = (s->conf.macaddr.a[3] << 24) |
+ (s->conf.macaddr.a[2] << 16) |
+ (s->conf.macaddr.a[1] << 8) |
+ s->conf.macaddr.a[0];
+
+ return 0;
+}
+
+static Property xgmac_properties[] = {
+ DEFINE_NIC_PROPERTIES(XgmacState, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xgmac_enet_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ sbc->init = xgmac_enet_init;
+ dc->vmsd = &vmstate_xgmac;
+ dc->props = xgmac_properties;
+}
+
+static const TypeInfo xgmac_enet_info = {
+ .name = TYPE_XGMAC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XgmacState),
+ .class_init = xgmac_enet_class_init,
+};
+
+static void xgmac_enet_register_types(void)
+{
+ type_register_static(&xgmac_enet_info);
+}
+
+type_init(xgmac_enet_register_types)
diff --git a/hw/net/xilinx_axienet.c b/hw/net/xilinx_axienet.c
new file mode 100644
index 00000000..d63c4232
--- /dev/null
+++ b/hw/net/xilinx_axienet.c
@@ -0,0 +1,1082 @@
+/*
+ * QEMU model of Xilinx AXI-Ethernet.
+ *
+ * Copyright (c) 2011 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/log.h"
+#include "net/net.h"
+#include "net/checksum.h"
+
+#include "hw/stream.h"
+
+#define DPHY(x)
+
+#define TYPE_XILINX_AXI_ENET "xlnx.axi-ethernet"
+#define TYPE_XILINX_AXI_ENET_DATA_STREAM "xilinx-axienet-data-stream"
+#define TYPE_XILINX_AXI_ENET_CONTROL_STREAM "xilinx-axienet-control-stream"
+
+#define XILINX_AXI_ENET(obj) \
+ OBJECT_CHECK(XilinxAXIEnet, (obj), TYPE_XILINX_AXI_ENET)
+
+#define XILINX_AXI_ENET_DATA_STREAM(obj) \
+ OBJECT_CHECK(XilinxAXIEnetStreamSlave, (obj),\
+ TYPE_XILINX_AXI_ENET_DATA_STREAM)
+
+#define XILINX_AXI_ENET_CONTROL_STREAM(obj) \
+ OBJECT_CHECK(XilinxAXIEnetStreamSlave, (obj),\
+ TYPE_XILINX_AXI_ENET_CONTROL_STREAM)
+
+/* Advertisement control register. */
+#define ADVERTISE_10HALF 0x0020 /* Try for 10mbps half-duplex */
+#define ADVERTISE_10FULL 0x0040 /* Try for 10mbps full-duplex */
+#define ADVERTISE_100HALF 0x0080 /* Try for 100mbps half-duplex */
+#define ADVERTISE_100FULL 0x0100 /* Try for 100mbps full-duplex */
+
+#define CONTROL_PAYLOAD_WORDS 5
+#define CONTROL_PAYLOAD_SIZE (CONTROL_PAYLOAD_WORDS * (sizeof(uint32_t)))
+
+struct PHY {
+ uint32_t regs[32];
+
+ int link;
+
+ unsigned int (*read)(struct PHY *phy, unsigned int req);
+ void (*write)(struct PHY *phy, unsigned int req,
+ unsigned int data);
+};
+
+static unsigned int tdk_read(struct PHY *phy, unsigned int req)
+{
+ int regnum;
+ unsigned r = 0;
+
+ regnum = req & 0x1f;
+
+ switch (regnum) {
+ case 1:
+ if (!phy->link) {
+ break;
+ }
+ /* MR1. */
+ /* Speeds and modes. */
+ r |= (1 << 13) | (1 << 14);
+ r |= (1 << 11) | (1 << 12);
+ r |= (1 << 5); /* Autoneg complete. */
+ r |= (1 << 3); /* Autoneg able. */
+ r |= (1 << 2); /* link. */
+ r |= (1 << 1); /* link. */
+ break;
+ case 5:
+ /* Link partner ability.
+ We are kind; always agree with whatever best mode
+ the guest advertises. */
+ r = 1 << 14; /* Success. */
+ /* Copy advertised modes. */
+ r |= phy->regs[4] & (15 << 5);
+ /* Autoneg support. */
+ r |= 1;
+ break;
+ case 17:
+ /* Marvell PHY on many xilinx boards. */
+ r = 0x8000; /* 1000Mb */
+ break;
+ case 18:
+ {
+ /* Diagnostics reg. */
+ int duplex = 0;
+ int speed_100 = 0;
+
+ if (!phy->link) {
+ break;
+ }
+
+ /* Are we advertising 100 half or 100 duplex ? */
+ speed_100 = !!(phy->regs[4] & ADVERTISE_100HALF);
+ speed_100 |= !!(phy->regs[4] & ADVERTISE_100FULL);
+
+ /* Are we advertising 10 duplex or 100 duplex ? */
+ duplex = !!(phy->regs[4] & ADVERTISE_100FULL);
+ duplex |= !!(phy->regs[4] & ADVERTISE_10FULL);
+ r = (speed_100 << 10) | (duplex << 11);
+ }
+ break;
+
+ default:
+ r = phy->regs[regnum];
+ break;
+ }
+ DPHY(qemu_log("\n%s %x = reg[%d]\n", __func__, r, regnum));
+ return r;
+}
+
+static void
+tdk_write(struct PHY *phy, unsigned int req, unsigned int data)
+{
+ int regnum;
+
+ regnum = req & 0x1f;
+ DPHY(qemu_log("%s reg[%d] = %x\n", __func__, regnum, data));
+ switch (regnum) {
+ default:
+ phy->regs[regnum] = data;
+ break;
+ }
+
+ /* Unconditionally clear regs[BMCR][BMCR_RESET] */
+ phy->regs[0] &= ~0x8000;
+}
+
+static void
+tdk_init(struct PHY *phy)
+{
+ phy->regs[0] = 0x3100;
+ /* PHY Id. */
+ phy->regs[2] = 0x0300;
+ phy->regs[3] = 0xe400;
+ /* Autonegotiation advertisement reg. */
+ phy->regs[4] = 0x01E1;
+ phy->link = 1;
+
+ phy->read = tdk_read;
+ phy->write = tdk_write;
+}
+
+struct MDIOBus {
+ /* bus. */
+ int mdc;
+ int mdio;
+
+ /* decoder. */
+ enum {
+ PREAMBLE,
+ SOF,
+ OPC,
+ ADDR,
+ REQ,
+ TURNAROUND,
+ DATA
+ } state;
+ unsigned int drive;
+
+ unsigned int cnt;
+ unsigned int addr;
+ unsigned int opc;
+ unsigned int req;
+ unsigned int data;
+
+ struct PHY *devs[32];
+};
+
+static void
+mdio_attach(struct MDIOBus *bus, struct PHY *phy, unsigned int addr)
+{
+ bus->devs[addr & 0x1f] = phy;
+}
+
+#ifdef USE_THIS_DEAD_CODE
+static void
+mdio_detach(struct MDIOBus *bus, struct PHY *phy, unsigned int addr)
+{
+ bus->devs[addr & 0x1f] = NULL;
+}
+#endif
+
+static uint16_t mdio_read_req(struct MDIOBus *bus, unsigned int addr,
+ unsigned int reg)
+{
+ struct PHY *phy;
+ uint16_t data;
+
+ phy = bus->devs[addr];
+ if (phy && phy->read) {
+ data = phy->read(phy, reg);
+ } else {
+ data = 0xffff;
+ }
+ DPHY(qemu_log("%s addr=%d reg=%d data=%x\n", __func__, addr, reg, data));
+ return data;
+}
+
+static void mdio_write_req(struct MDIOBus *bus, unsigned int addr,
+ unsigned int reg, uint16_t data)
+{
+ struct PHY *phy;
+
+ DPHY(qemu_log("%s addr=%d reg=%d data=%x\n", __func__, addr, reg, data));
+ phy = bus->devs[addr];
+ if (phy && phy->write) {
+ phy->write(phy, reg, data);
+ }
+}
+
+#define DENET(x)
+
+#define R_RAF (0x000 / 4)
+enum {
+ RAF_MCAST_REJ = (1 << 1),
+ RAF_BCAST_REJ = (1 << 2),
+ RAF_EMCF_EN = (1 << 12),
+ RAF_NEWFUNC_EN = (1 << 11)
+};
+
+#define R_IS (0x00C / 4)
+enum {
+ IS_HARD_ACCESS_COMPLETE = 1,
+ IS_AUTONEG = (1 << 1),
+ IS_RX_COMPLETE = (1 << 2),
+ IS_RX_REJECT = (1 << 3),
+ IS_TX_COMPLETE = (1 << 5),
+ IS_RX_DCM_LOCK = (1 << 6),
+ IS_MGM_RDY = (1 << 7),
+ IS_PHY_RST_DONE = (1 << 8),
+};
+
+#define R_IP (0x010 / 4)
+#define R_IE (0x014 / 4)
+#define R_UAWL (0x020 / 4)
+#define R_UAWU (0x024 / 4)
+#define R_PPST (0x030 / 4)
+enum {
+ PPST_LINKSTATUS = (1 << 0),
+ PPST_PHY_LINKSTATUS = (1 << 7),
+};
+
+#define R_STATS_RX_BYTESL (0x200 / 4)
+#define R_STATS_RX_BYTESH (0x204 / 4)
+#define R_STATS_TX_BYTESL (0x208 / 4)
+#define R_STATS_TX_BYTESH (0x20C / 4)
+#define R_STATS_RXL (0x290 / 4)
+#define R_STATS_RXH (0x294 / 4)
+#define R_STATS_RX_BCASTL (0x2a0 / 4)
+#define R_STATS_RX_BCASTH (0x2a4 / 4)
+#define R_STATS_RX_MCASTL (0x2a8 / 4)
+#define R_STATS_RX_MCASTH (0x2ac / 4)
+
+#define R_RCW0 (0x400 / 4)
+#define R_RCW1 (0x404 / 4)
+enum {
+ RCW1_VLAN = (1 << 27),
+ RCW1_RX = (1 << 28),
+ RCW1_FCS = (1 << 29),
+ RCW1_JUM = (1 << 30),
+ RCW1_RST = (1 << 31),
+};
+
+#define R_TC (0x408 / 4)
+enum {
+ TC_VLAN = (1 << 27),
+ TC_TX = (1 << 28),
+ TC_FCS = (1 << 29),
+ TC_JUM = (1 << 30),
+ TC_RST = (1 << 31),
+};
+
+#define R_EMMC (0x410 / 4)
+enum {
+ EMMC_LINKSPEED_10MB = (0 << 30),
+ EMMC_LINKSPEED_100MB = (1 << 30),
+ EMMC_LINKSPEED_1000MB = (2 << 30),
+};
+
+#define R_PHYC (0x414 / 4)
+
+#define R_MC (0x500 / 4)
+#define MC_EN (1 << 6)
+
+#define R_MCR (0x504 / 4)
+#define R_MWD (0x508 / 4)
+#define R_MRD (0x50c / 4)
+#define R_MIS (0x600 / 4)
+#define R_MIP (0x620 / 4)
+#define R_MIE (0x640 / 4)
+#define R_MIC (0x640 / 4)
+
+#define R_UAW0 (0x700 / 4)
+#define R_UAW1 (0x704 / 4)
+#define R_FMI (0x708 / 4)
+#define R_AF0 (0x710 / 4)
+#define R_AF1 (0x714 / 4)
+#define R_MAX (0x34 / 4)
+
+/* Indirect registers. */
+struct TEMAC {
+ struct MDIOBus mdio_bus;
+ struct PHY phy;
+
+ void *parent;
+};
+
+typedef struct XilinxAXIEnetStreamSlave XilinxAXIEnetStreamSlave;
+typedef struct XilinxAXIEnet XilinxAXIEnet;
+
+struct XilinxAXIEnetStreamSlave {
+ Object parent;
+
+ struct XilinxAXIEnet *enet;
+} ;
+
+struct XilinxAXIEnet {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ qemu_irq irq;
+ StreamSlave *tx_data_dev;
+ StreamSlave *tx_control_dev;
+ XilinxAXIEnetStreamSlave rx_data_dev;
+ XilinxAXIEnetStreamSlave rx_control_dev;
+ NICState *nic;
+ NICConf conf;
+
+
+ uint32_t c_rxmem;
+ uint32_t c_txmem;
+ uint32_t c_phyaddr;
+
+ struct TEMAC TEMAC;
+
+ /* MII regs. */
+ union {
+ uint32_t regs[4];
+ struct {
+ uint32_t mc;
+ uint32_t mcr;
+ uint32_t mwd;
+ uint32_t mrd;
+ };
+ } mii;
+
+ struct {
+ uint64_t rx_bytes;
+ uint64_t tx_bytes;
+
+ uint64_t rx;
+ uint64_t rx_bcast;
+ uint64_t rx_mcast;
+ } stats;
+
+ /* Receive configuration words. */
+ uint32_t rcw[2];
+ /* Transmit config. */
+ uint32_t tc;
+ uint32_t emmc;
+ uint32_t phyc;
+
+ /* Unicast Address Word. */
+ uint32_t uaw[2];
+ /* Unicast address filter used with extended mcast. */
+ uint32_t ext_uaw[2];
+ uint32_t fmi;
+
+ uint32_t regs[R_MAX];
+
+ /* Multicast filter addrs. */
+ uint32_t maddr[4][2];
+ /* 32K x 1 lookup filter. */
+ uint32_t ext_mtable[1024];
+
+ uint32_t hdr[CONTROL_PAYLOAD_WORDS];
+
+ uint8_t *rxmem;
+ uint32_t rxsize;
+ uint32_t rxpos;
+
+ uint8_t rxapp[CONTROL_PAYLOAD_SIZE];
+ uint32_t rxappsize;
+
+ /* Whether axienet_eth_rx_notify should flush incoming queue. */
+ bool need_flush;
+};
+
+static void axienet_rx_reset(XilinxAXIEnet *s)
+{
+ s->rcw[1] = RCW1_JUM | RCW1_FCS | RCW1_RX | RCW1_VLAN;
+}
+
+static void axienet_tx_reset(XilinxAXIEnet *s)
+{
+ s->tc = TC_JUM | TC_TX | TC_VLAN;
+}
+
+static inline int axienet_rx_resetting(XilinxAXIEnet *s)
+{
+ return s->rcw[1] & RCW1_RST;
+}
+
+static inline int axienet_rx_enabled(XilinxAXIEnet *s)
+{
+ return s->rcw[1] & RCW1_RX;
+}
+
+static inline int axienet_extmcf_enabled(XilinxAXIEnet *s)
+{
+ return !!(s->regs[R_RAF] & RAF_EMCF_EN);
+}
+
+static inline int axienet_newfunc_enabled(XilinxAXIEnet *s)
+{
+ return !!(s->regs[R_RAF] & RAF_NEWFUNC_EN);
+}
+
+static void xilinx_axienet_reset(DeviceState *d)
+{
+ XilinxAXIEnet *s = XILINX_AXI_ENET(d);
+
+ axienet_rx_reset(s);
+ axienet_tx_reset(s);
+
+ s->regs[R_PPST] = PPST_LINKSTATUS | PPST_PHY_LINKSTATUS;
+ s->regs[R_IS] = IS_AUTONEG | IS_RX_DCM_LOCK | IS_MGM_RDY | IS_PHY_RST_DONE;
+
+ s->emmc = EMMC_LINKSPEED_100MB;
+}
+
+static void enet_update_irq(XilinxAXIEnet *s)
+{
+ s->regs[R_IP] = s->regs[R_IS] & s->regs[R_IE];
+ qemu_set_irq(s->irq, !!s->regs[R_IP]);
+}
+
+static uint64_t enet_read(void *opaque, hwaddr addr, unsigned size)
+{
+ XilinxAXIEnet *s = opaque;
+ uint32_t r = 0;
+ addr >>= 2;
+
+ switch (addr) {
+ case R_RCW0:
+ case R_RCW1:
+ r = s->rcw[addr & 1];
+ break;
+
+ case R_TC:
+ r = s->tc;
+ break;
+
+ case R_EMMC:
+ r = s->emmc;
+ break;
+
+ case R_PHYC:
+ r = s->phyc;
+ break;
+
+ case R_MCR:
+ r = s->mii.regs[addr & 3] | (1 << 7); /* Always ready. */
+ break;
+
+ case R_STATS_RX_BYTESL:
+ case R_STATS_RX_BYTESH:
+ r = s->stats.rx_bytes >> (32 * (addr & 1));
+ break;
+
+ case R_STATS_TX_BYTESL:
+ case R_STATS_TX_BYTESH:
+ r = s->stats.tx_bytes >> (32 * (addr & 1));
+ break;
+
+ case R_STATS_RXL:
+ case R_STATS_RXH:
+ r = s->stats.rx >> (32 * (addr & 1));
+ break;
+ case R_STATS_RX_BCASTL:
+ case R_STATS_RX_BCASTH:
+ r = s->stats.rx_bcast >> (32 * (addr & 1));
+ break;
+ case R_STATS_RX_MCASTL:
+ case R_STATS_RX_MCASTH:
+ r = s->stats.rx_mcast >> (32 * (addr & 1));
+ break;
+
+ case R_MC:
+ case R_MWD:
+ case R_MRD:
+ r = s->mii.regs[addr & 3];
+ break;
+
+ case R_UAW0:
+ case R_UAW1:
+ r = s->uaw[addr & 1];
+ break;
+
+ case R_UAWU:
+ case R_UAWL:
+ r = s->ext_uaw[addr & 1];
+ break;
+
+ case R_FMI:
+ r = s->fmi;
+ break;
+
+ case R_AF0:
+ case R_AF1:
+ r = s->maddr[s->fmi & 3][addr & 1];
+ break;
+
+ case 0x8000 ... 0x83ff:
+ r = s->ext_mtable[addr - 0x8000];
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(s->regs)) {
+ r = s->regs[addr];
+ }
+ DENET(qemu_log("%s addr=" TARGET_FMT_plx " v=%x\n",
+ __func__, addr * 4, r));
+ break;
+ }
+ return r;
+}
+
+static void enet_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ XilinxAXIEnet *s = opaque;
+ struct TEMAC *t = &s->TEMAC;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RCW0:
+ case R_RCW1:
+ s->rcw[addr & 1] = value;
+ if ((addr & 1) && value & RCW1_RST) {
+ axienet_rx_reset(s);
+ } else {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ break;
+
+ case R_TC:
+ s->tc = value;
+ if (value & TC_RST) {
+ axienet_tx_reset(s);
+ }
+ break;
+
+ case R_EMMC:
+ s->emmc = value;
+ break;
+
+ case R_PHYC:
+ s->phyc = value;
+ break;
+
+ case R_MC:
+ value &= ((1 << 7) - 1);
+
+ /* Enable the MII. */
+ if (value & MC_EN) {
+ unsigned int miiclkdiv = value & ((1 << 6) - 1);
+ if (!miiclkdiv) {
+ qemu_log("AXIENET: MDIO enabled but MDIOCLK is zero!\n");
+ }
+ }
+ s->mii.mc = value;
+ break;
+
+ case R_MCR: {
+ unsigned int phyaddr = (value >> 24) & 0x1f;
+ unsigned int regaddr = (value >> 16) & 0x1f;
+ unsigned int op = (value >> 14) & 3;
+ unsigned int initiate = (value >> 11) & 1;
+
+ if (initiate) {
+ if (op == 1) {
+ mdio_write_req(&t->mdio_bus, phyaddr, regaddr, s->mii.mwd);
+ } else if (op == 2) {
+ s->mii.mrd = mdio_read_req(&t->mdio_bus, phyaddr, regaddr);
+ } else {
+ qemu_log("AXIENET: invalid MDIOBus OP=%d\n", op);
+ }
+ }
+ s->mii.mcr = value;
+ break;
+ }
+
+ case R_MWD:
+ case R_MRD:
+ s->mii.regs[addr & 3] = value;
+ break;
+
+
+ case R_UAW0:
+ case R_UAW1:
+ s->uaw[addr & 1] = value;
+ break;
+
+ case R_UAWL:
+ case R_UAWU:
+ s->ext_uaw[addr & 1] = value;
+ break;
+
+ case R_FMI:
+ s->fmi = value;
+ break;
+
+ case R_AF0:
+ case R_AF1:
+ s->maddr[s->fmi & 3][addr & 1] = value;
+ break;
+
+ case R_IS:
+ s->regs[addr] &= ~value;
+ break;
+
+ case 0x8000 ... 0x83ff:
+ s->ext_mtable[addr - 0x8000] = value;
+ break;
+
+ default:
+ DENET(qemu_log("%s addr=" TARGET_FMT_plx " v=%x\n",
+ __func__, addr * 4, (unsigned)value));
+ if (addr < ARRAY_SIZE(s->regs)) {
+ s->regs[addr] = value;
+ }
+ break;
+ }
+ enet_update_irq(s);
+}
+
+static const MemoryRegionOps enet_ops = {
+ .read = enet_read,
+ .write = enet_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static int eth_can_rx(XilinxAXIEnet *s)
+{
+ /* RX enabled? */
+ return !s->rxsize && !axienet_rx_resetting(s) && axienet_rx_enabled(s);
+}
+
+static int enet_match_addr(const uint8_t *buf, uint32_t f0, uint32_t f1)
+{
+ int match = 1;
+
+ if (memcmp(buf, &f0, 4)) {
+ match = 0;
+ }
+
+ if (buf[4] != (f1 & 0xff) || buf[5] != ((f1 >> 8) & 0xff)) {
+ match = 0;
+ }
+
+ return match;
+}
+
+static void axienet_eth_rx_notify(void *opaque)
+{
+ XilinxAXIEnet *s = XILINX_AXI_ENET(opaque);
+
+ while (s->rxappsize && stream_can_push(s->tx_control_dev,
+ axienet_eth_rx_notify, s)) {
+ size_t ret = stream_push(s->tx_control_dev,
+ (void *)s->rxapp + CONTROL_PAYLOAD_SIZE
+ - s->rxappsize, s->rxappsize);
+ s->rxappsize -= ret;
+ }
+
+ while (s->rxsize && stream_can_push(s->tx_data_dev,
+ axienet_eth_rx_notify, s)) {
+ size_t ret = stream_push(s->tx_data_dev, (void *)s->rxmem + s->rxpos,
+ s->rxsize);
+ s->rxsize -= ret;
+ s->rxpos += ret;
+ if (!s->rxsize) {
+ s->regs[R_IS] |= IS_RX_COMPLETE;
+ if (s->need_flush) {
+ s->need_flush = false;
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ }
+ }
+ enet_update_irq(s);
+}
+
+static ssize_t eth_rx(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ XilinxAXIEnet *s = qemu_get_nic_opaque(nc);
+ static const unsigned char sa_bcast[6] = {0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff};
+ static const unsigned char sa_ipmcast[3] = {0x01, 0x00, 0x52};
+ uint32_t app[CONTROL_PAYLOAD_WORDS] = {0};
+ int promisc = s->fmi & (1 << 31);
+ int unicast, broadcast, multicast, ip_multicast = 0;
+ uint32_t csum32;
+ uint16_t csum16;
+ int i;
+
+ DENET(qemu_log("%s: %zd bytes\n", __func__, size));
+
+ if (!eth_can_rx(s)) {
+ s->need_flush = true;
+ return 0;
+ }
+
+ unicast = ~buf[0] & 0x1;
+ broadcast = memcmp(buf, sa_bcast, 6) == 0;
+ multicast = !unicast && !broadcast;
+ if (multicast && (memcmp(sa_ipmcast, buf, sizeof sa_ipmcast) == 0)) {
+ ip_multicast = 1;
+ }
+
+ /* Jumbo or vlan sizes ? */
+ if (!(s->rcw[1] & RCW1_JUM)) {
+ if (size > 1518 && size <= 1522 && !(s->rcw[1] & RCW1_VLAN)) {
+ return size;
+ }
+ }
+
+ /* Basic Address filters. If you want to use the extended filters
+ you'll generally have to place the ethernet mac into promiscuous mode
+ to avoid the basic filtering from dropping most frames. */
+ if (!promisc) {
+ if (unicast) {
+ if (!enet_match_addr(buf, s->uaw[0], s->uaw[1])) {
+ return size;
+ }
+ } else {
+ if (broadcast) {
+ /* Broadcast. */
+ if (s->regs[R_RAF] & RAF_BCAST_REJ) {
+ return size;
+ }
+ } else {
+ int drop = 1;
+
+ /* Multicast. */
+ if (s->regs[R_RAF] & RAF_MCAST_REJ) {
+ return size;
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (enet_match_addr(buf, s->maddr[i][0], s->maddr[i][1])) {
+ drop = 0;
+ break;
+ }
+ }
+
+ if (drop) {
+ return size;
+ }
+ }
+ }
+ }
+
+ /* Extended mcast filtering enabled? */
+ if (axienet_newfunc_enabled(s) && axienet_extmcf_enabled(s)) {
+ if (unicast) {
+ if (!enet_match_addr(buf, s->ext_uaw[0], s->ext_uaw[1])) {
+ return size;
+ }
+ } else {
+ if (broadcast) {
+ /* Broadcast. ??? */
+ if (s->regs[R_RAF] & RAF_BCAST_REJ) {
+ return size;
+ }
+ } else {
+ int idx, bit;
+
+ /* Multicast. */
+ if (!memcmp(buf, sa_ipmcast, 3)) {
+ return size;
+ }
+
+ idx = (buf[4] & 0x7f) << 8;
+ idx |= buf[5];
+
+ bit = 1 << (idx & 0x1f);
+ idx >>= 5;
+
+ if (!(s->ext_mtable[idx] & bit)) {
+ return size;
+ }
+ }
+ }
+ }
+
+ if (size < 12) {
+ s->regs[R_IS] |= IS_RX_REJECT;
+ enet_update_irq(s);
+ return -1;
+ }
+
+ if (size > (s->c_rxmem - 4)) {
+ size = s->c_rxmem - 4;
+ }
+
+ memcpy(s->rxmem, buf, size);
+ memset(s->rxmem + size, 0, 4); /* Clear the FCS. */
+
+ if (s->rcw[1] & RCW1_FCS) {
+ size += 4; /* fcs is inband. */
+ }
+
+ app[0] = 5 << 28;
+ csum32 = net_checksum_add(size - 14, (uint8_t *)s->rxmem + 14);
+ /* Fold it once. */
+ csum32 = (csum32 & 0xffff) + (csum32 >> 16);
+ /* And twice to get rid of possible carries. */
+ csum16 = (csum32 & 0xffff) + (csum32 >> 16);
+ app[3] = csum16;
+ app[4] = size & 0xffff;
+
+ s->stats.rx_bytes += size;
+ s->stats.rx++;
+ if (multicast) {
+ s->stats.rx_mcast++;
+ app[2] |= 1 | (ip_multicast << 1);
+ } else if (broadcast) {
+ s->stats.rx_bcast++;
+ app[2] |= 1 << 3;
+ }
+
+ /* Good frame. */
+ app[2] |= 1 << 6;
+
+ s->rxsize = size;
+ s->rxpos = 0;
+ for (i = 0; i < ARRAY_SIZE(app); ++i) {
+ app[i] = cpu_to_le32(app[i]);
+ }
+ s->rxappsize = CONTROL_PAYLOAD_SIZE;
+ memcpy(s->rxapp, app, s->rxappsize);
+ axienet_eth_rx_notify(s);
+
+ enet_update_irq(s);
+ return size;
+}
+
+static size_t
+xilinx_axienet_control_stream_push(StreamSlave *obj, uint8_t *buf, size_t len)
+{
+ int i;
+ XilinxAXIEnetStreamSlave *cs = XILINX_AXI_ENET_CONTROL_STREAM(obj);
+ XilinxAXIEnet *s = cs->enet;
+
+ if (len != CONTROL_PAYLOAD_SIZE) {
+ hw_error("AXI Enet requires %d byte control stream payload\n",
+ (int)CONTROL_PAYLOAD_SIZE);
+ }
+
+ memcpy(s->hdr, buf, len);
+
+ for (i = 0; i < ARRAY_SIZE(s->hdr); ++i) {
+ s->hdr[i] = le32_to_cpu(s->hdr[i]);
+ }
+ return len;
+}
+
+static size_t
+xilinx_axienet_data_stream_push(StreamSlave *obj, uint8_t *buf, size_t size)
+{
+ XilinxAXIEnetStreamSlave *ds = XILINX_AXI_ENET_DATA_STREAM(obj);
+ XilinxAXIEnet *s = ds->enet;
+
+ /* TX enable ? */
+ if (!(s->tc & TC_TX)) {
+ return size;
+ }
+
+ /* Jumbo or vlan sizes ? */
+ if (!(s->tc & TC_JUM)) {
+ if (size > 1518 && size <= 1522 && !(s->tc & TC_VLAN)) {
+ return size;
+ }
+ }
+
+ if (s->hdr[0] & 1) {
+ unsigned int start_off = s->hdr[1] >> 16;
+ unsigned int write_off = s->hdr[1] & 0xffff;
+ uint32_t tmp_csum;
+ uint16_t csum;
+
+ tmp_csum = net_checksum_add(size - start_off,
+ (uint8_t *)buf + start_off);
+ /* Accumulate the seed. */
+ tmp_csum += s->hdr[2] & 0xffff;
+
+ /* Fold the 32bit partial checksum. */
+ csum = net_checksum_finish(tmp_csum);
+
+ /* Writeback. */
+ buf[write_off] = csum >> 8;
+ buf[write_off + 1] = csum & 0xff;
+ }
+
+ qemu_send_packet(qemu_get_queue(s->nic), buf, size);
+
+ s->stats.tx_bytes += size;
+ s->regs[R_IS] |= IS_TX_COMPLETE;
+ enet_update_irq(s);
+
+ return size;
+}
+
+static NetClientInfo net_xilinx_enet_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = eth_rx,
+};
+
+static void xilinx_enet_realize(DeviceState *dev, Error **errp)
+{
+ XilinxAXIEnet *s = XILINX_AXI_ENET(dev);
+ XilinxAXIEnetStreamSlave *ds = XILINX_AXI_ENET_DATA_STREAM(&s->rx_data_dev);
+ XilinxAXIEnetStreamSlave *cs = XILINX_AXI_ENET_CONTROL_STREAM(
+ &s->rx_control_dev);
+ Error *local_err = NULL;
+
+ object_property_add_link(OBJECT(ds), "enet", "xlnx.axi-ethernet",
+ (Object **) &ds->enet,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &local_err);
+ object_property_add_link(OBJECT(cs), "enet", "xlnx.axi-ethernet",
+ (Object **) &cs->enet,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &local_err);
+ if (local_err) {
+ goto xilinx_enet_realize_fail;
+ }
+ object_property_set_link(OBJECT(ds), OBJECT(s), "enet", &local_err);
+ object_property_set_link(OBJECT(cs), OBJECT(s), "enet", &local_err);
+ if (local_err) {
+ goto xilinx_enet_realize_fail;
+ }
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(&net_xilinx_enet_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ tdk_init(&s->TEMAC.phy);
+ mdio_attach(&s->TEMAC.mdio_bus, &s->TEMAC.phy, s->c_phyaddr);
+
+ s->TEMAC.parent = s;
+
+ s->rxmem = g_malloc(s->c_rxmem);
+ return;
+
+xilinx_enet_realize_fail:
+ if (!*errp) {
+ *errp = local_err;
+ }
+}
+
+static void xilinx_enet_init(Object *obj)
+{
+ XilinxAXIEnet *s = XILINX_AXI_ENET(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ object_property_add_link(obj, "axistream-connected", TYPE_STREAM_SLAVE,
+ (Object **) &s->tx_data_dev,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &error_abort);
+ object_property_add_link(obj, "axistream-control-connected",
+ TYPE_STREAM_SLAVE,
+ (Object **) &s->tx_control_dev,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE,
+ &error_abort);
+
+ object_initialize(&s->rx_data_dev, sizeof(s->rx_data_dev),
+ TYPE_XILINX_AXI_ENET_DATA_STREAM);
+ object_initialize(&s->rx_control_dev, sizeof(s->rx_control_dev),
+ TYPE_XILINX_AXI_ENET_CONTROL_STREAM);
+ object_property_add_child(OBJECT(s), "axistream-connected-target",
+ (Object *)&s->rx_data_dev, &error_abort);
+ object_property_add_child(OBJECT(s), "axistream-control-connected-target",
+ (Object *)&s->rx_control_dev, &error_abort);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &enet_ops, s, "enet", 0x40000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static Property xilinx_enet_properties[] = {
+ DEFINE_PROP_UINT32("phyaddr", XilinxAXIEnet, c_phyaddr, 7),
+ DEFINE_PROP_UINT32("rxmem", XilinxAXIEnet, c_rxmem, 0x1000),
+ DEFINE_PROP_UINT32("txmem", XilinxAXIEnet, c_txmem, 0x1000),
+ DEFINE_NIC_PROPERTIES(XilinxAXIEnet, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_enet_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = xilinx_enet_realize;
+ dc->props = xilinx_enet_properties;
+ dc->reset = xilinx_axienet_reset;
+}
+
+static void xilinx_enet_stream_class_init(ObjectClass *klass, void *data)
+{
+ StreamSlaveClass *ssc = STREAM_SLAVE_CLASS(klass);
+
+ ssc->push = data;
+}
+
+static const TypeInfo xilinx_enet_info = {
+ .name = TYPE_XILINX_AXI_ENET,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XilinxAXIEnet),
+ .class_init = xilinx_enet_class_init,
+ .instance_init = xilinx_enet_init,
+};
+
+static const TypeInfo xilinx_enet_data_stream_info = {
+ .name = TYPE_XILINX_AXI_ENET_DATA_STREAM,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(struct XilinxAXIEnetStreamSlave),
+ .class_init = xilinx_enet_stream_class_init,
+ .class_data = xilinx_axienet_data_stream_push,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_STREAM_SLAVE },
+ { }
+ }
+};
+
+static const TypeInfo xilinx_enet_control_stream_info = {
+ .name = TYPE_XILINX_AXI_ENET_CONTROL_STREAM,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(struct XilinxAXIEnetStreamSlave),
+ .class_init = xilinx_enet_stream_class_init,
+ .class_data = xilinx_axienet_control_stream_push,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_STREAM_SLAVE },
+ { }
+ }
+};
+
+static void xilinx_enet_register_types(void)
+{
+ type_register_static(&xilinx_enet_info);
+ type_register_static(&xilinx_enet_data_stream_info);
+ type_register_static(&xilinx_enet_control_stream_info);
+}
+
+type_init(xilinx_enet_register_types)
diff --git a/hw/net/xilinx_ethlite.c b/hw/net/xilinx_ethlite.c
new file mode 100644
index 00000000..ad6b5530
--- /dev/null
+++ b/hw/net/xilinx_ethlite.c
@@ -0,0 +1,273 @@
+/*
+ * QEMU model of the Xilinx Ethernet Lite MAC.
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "net/net.h"
+
+#define D(x)
+#define R_TX_BUF0 0
+#define R_TX_LEN0 (0x07f4 / 4)
+#define R_TX_GIE0 (0x07f8 / 4)
+#define R_TX_CTRL0 (0x07fc / 4)
+#define R_TX_BUF1 (0x0800 / 4)
+#define R_TX_LEN1 (0x0ff4 / 4)
+#define R_TX_CTRL1 (0x0ffc / 4)
+
+#define R_RX_BUF0 (0x1000 / 4)
+#define R_RX_CTRL0 (0x17fc / 4)
+#define R_RX_BUF1 (0x1800 / 4)
+#define R_RX_CTRL1 (0x1ffc / 4)
+#define R_MAX (0x2000 / 4)
+
+#define GIE_GIE 0x80000000
+
+#define CTRL_I 0x8
+#define CTRL_P 0x2
+#define CTRL_S 0x1
+
+#define TYPE_XILINX_ETHLITE "xlnx.xps-ethernetlite"
+#define XILINX_ETHLITE(obj) \
+ OBJECT_CHECK(struct xlx_ethlite, (obj), TYPE_XILINX_ETHLITE)
+
+struct xlx_ethlite
+{
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ qemu_irq irq;
+ NICState *nic;
+ NICConf conf;
+
+ uint32_t c_tx_pingpong;
+ uint32_t c_rx_pingpong;
+ unsigned int txbuf;
+ unsigned int rxbuf;
+
+ uint32_t regs[R_MAX];
+};
+
+static inline void eth_pulse_irq(struct xlx_ethlite *s)
+{
+ /* Only the first gie reg is active. */
+ if (s->regs[R_TX_GIE0] & GIE_GIE) {
+ qemu_irq_pulse(s->irq);
+ }
+}
+
+static uint64_t
+eth_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ struct xlx_ethlite *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+
+ switch (addr)
+ {
+ case R_TX_GIE0:
+ case R_TX_LEN0:
+ case R_TX_LEN1:
+ case R_TX_CTRL1:
+ case R_TX_CTRL0:
+ case R_RX_CTRL1:
+ case R_RX_CTRL0:
+ r = s->regs[addr];
+ D(qemu_log("%s " TARGET_FMT_plx "=%x\n", __func__, addr * 4, r));
+ break;
+
+ default:
+ r = tswap32(s->regs[addr]);
+ break;
+ }
+ return r;
+}
+
+static void
+eth_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ struct xlx_ethlite *s = opaque;
+ unsigned int base = 0;
+ uint32_t value = val64;
+
+ addr >>= 2;
+ switch (addr)
+ {
+ case R_TX_CTRL0:
+ case R_TX_CTRL1:
+ if (addr == R_TX_CTRL1)
+ base = 0x800 / 4;
+
+ D(qemu_log("%s addr=" TARGET_FMT_plx " val=%x\n",
+ __func__, addr * 4, value));
+ if ((value & (CTRL_P | CTRL_S)) == CTRL_S) {
+ qemu_send_packet(qemu_get_queue(s->nic),
+ (void *) &s->regs[base],
+ s->regs[base + R_TX_LEN0]);
+ D(qemu_log("eth_tx %d\n", s->regs[base + R_TX_LEN0]));
+ if (s->regs[base + R_TX_CTRL0] & CTRL_I)
+ eth_pulse_irq(s);
+ } else if ((value & (CTRL_P | CTRL_S)) == (CTRL_P | CTRL_S)) {
+ memcpy(&s->conf.macaddr.a[0], &s->regs[base], 6);
+ if (s->regs[base + R_TX_CTRL0] & CTRL_I)
+ eth_pulse_irq(s);
+ }
+
+ /* We are fast and get ready pretty much immediately so
+ we actually never flip the S nor P bits to one. */
+ s->regs[addr] = value & ~(CTRL_P | CTRL_S);
+ break;
+
+ /* Keep these native. */
+ case R_RX_CTRL0:
+ case R_RX_CTRL1:
+ if (!(value & CTRL_S)) {
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ }
+ /* fall through */
+ case R_TX_LEN0:
+ case R_TX_LEN1:
+ case R_TX_GIE0:
+ D(qemu_log("%s addr=" TARGET_FMT_plx " val=%x\n",
+ __func__, addr * 4, value));
+ s->regs[addr] = value;
+ break;
+
+ default:
+ s->regs[addr] = tswap32(value);
+ break;
+ }
+}
+
+static const MemoryRegionOps eth_ops = {
+ .read = eth_read,
+ .write = eth_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static int eth_can_rx(NetClientState *nc)
+{
+ struct xlx_ethlite *s = qemu_get_nic_opaque(nc);
+ unsigned int rxbase = s->rxbuf * (0x800 / 4);
+
+ return !(s->regs[rxbase + R_RX_CTRL0] & CTRL_S);
+}
+
+static ssize_t eth_rx(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ struct xlx_ethlite *s = qemu_get_nic_opaque(nc);
+ unsigned int rxbase = s->rxbuf * (0x800 / 4);
+
+ /* DA filter. */
+ if (!(buf[0] & 0x80) && memcmp(&s->conf.macaddr.a[0], buf, 6))
+ return size;
+
+ if (s->regs[rxbase + R_RX_CTRL0] & CTRL_S) {
+ D(qemu_log("ethlite lost packet %x\n", s->regs[R_RX_CTRL0]));
+ return -1;
+ }
+
+ D(qemu_log("%s %zd rxbase=%x\n", __func__, size, rxbase));
+ memcpy(&s->regs[rxbase + R_RX_BUF0], buf, size);
+
+ s->regs[rxbase + R_RX_CTRL0] |= CTRL_S;
+ if (s->regs[R_RX_CTRL0] & CTRL_I) {
+ eth_pulse_irq(s);
+ }
+
+ /* If c_rx_pingpong was set flip buffers. */
+ s->rxbuf ^= s->c_rx_pingpong;
+ return size;
+}
+
+static void xilinx_ethlite_reset(DeviceState *dev)
+{
+ struct xlx_ethlite *s = XILINX_ETHLITE(dev);
+
+ s->rxbuf = 0;
+}
+
+static NetClientInfo net_xilinx_ethlite_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .can_receive = eth_can_rx,
+ .receive = eth_rx,
+};
+
+static void xilinx_ethlite_realize(DeviceState *dev, Error **errp)
+{
+ struct xlx_ethlite *s = XILINX_ETHLITE(dev);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(&net_xilinx_ethlite_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+}
+
+static void xilinx_ethlite_init(Object *obj)
+{
+ struct xlx_ethlite *s = XILINX_ETHLITE(obj);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+ memory_region_init_io(&s->mmio, obj, &eth_ops, s,
+ "xlnx.xps-ethernetlite", R_MAX * 4);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static Property xilinx_ethlite_properties[] = {
+ DEFINE_PROP_UINT32("tx-ping-pong", struct xlx_ethlite, c_tx_pingpong, 1),
+ DEFINE_PROP_UINT32("rx-ping-pong", struct xlx_ethlite, c_rx_pingpong, 1),
+ DEFINE_NIC_PROPERTIES(struct xlx_ethlite, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_ethlite_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = xilinx_ethlite_realize;
+ dc->reset = xilinx_ethlite_reset;
+ dc->props = xilinx_ethlite_properties;
+}
+
+static const TypeInfo xilinx_ethlite_info = {
+ .name = TYPE_XILINX_ETHLITE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct xlx_ethlite),
+ .instance_init = xilinx_ethlite_init,
+ .class_init = xilinx_ethlite_class_init,
+};
+
+static void xilinx_ethlite_register_types(void)
+{
+ type_register_static(&xilinx_ethlite_info);
+}
+
+type_init(xilinx_ethlite_register_types)
diff --git a/hw/nvram/Makefile.objs b/hw/nvram/Makefile.objs
new file mode 100644
index 00000000..e9a66940
--- /dev/null
+++ b/hw/nvram/Makefile.objs
@@ -0,0 +1,5 @@
+common-obj-$(CONFIG_DS1225Y) += ds1225y.o
+common-obj-y += eeprom93xx.o
+common-obj-y += fw_cfg.o
+common-obj-$(CONFIG_MAC_NVRAM) += mac_nvram.o
+obj-$(CONFIG_PSERIES) += spapr_nvram.o
diff --git a/hw/nvram/ds1225y.c b/hw/nvram/ds1225y.c
new file mode 100644
index 00000000..332598b2
--- /dev/null
+++ b/hw/nvram/ds1225y.c
@@ -0,0 +1,169 @@
+/*
+ * QEMU NVRAM emulation for DS1225Y chip
+ *
+ * Copyright (c) 2007-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.
+ */
+
+#include "hw/sysbus.h"
+#include "trace.h"
+
+typedef struct {
+ MemoryRegion iomem;
+ uint32_t chip_size;
+ char *filename;
+ FILE *file;
+ uint8_t *contents;
+} NvRamState;
+
+static uint64_t nvram_read(void *opaque, hwaddr addr, unsigned size)
+{
+ NvRamState *s = opaque;
+ uint32_t val;
+
+ val = s->contents[addr];
+ trace_nvram_read(addr, val);
+ return val;
+}
+
+static void nvram_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ NvRamState *s = opaque;
+
+ val &= 0xff;
+ trace_nvram_write(addr, s->contents[addr], val);
+
+ s->contents[addr] = val;
+ if (s->file) {
+ fseek(s->file, addr, SEEK_SET);
+ fputc(val, s->file);
+ fflush(s->file);
+ }
+}
+
+static const MemoryRegionOps nvram_ops = {
+ .read = nvram_read,
+ .write = nvram_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static int nvram_post_load(void *opaque, int version_id)
+{
+ NvRamState *s = opaque;
+
+ /* Close file, as filename may has changed in load/store process */
+ if (s->file) {
+ fclose(s->file);
+ }
+
+ /* Write back nvram contents */
+ s->file = fopen(s->filename, "wb");
+ if (s->file) {
+ /* Write back contents, as 'wb' mode cleaned the file */
+ if (fwrite(s->contents, s->chip_size, 1, s->file) != 1) {
+ printf("nvram_post_load: short write\n");
+ }
+ fflush(s->file);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_nvram = {
+ .name = "nvram",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = nvram_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_VARRAY_UINT32(contents, NvRamState, chip_size, 0,
+ vmstate_info_uint8, uint8_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define TYPE_DS1225Y "ds1225y"
+#define DS1225Y(obj) OBJECT_CHECK(SysBusNvRamState, (obj), TYPE_DS1225Y)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ NvRamState nvram;
+} SysBusNvRamState;
+
+static int nvram_sysbus_initfn(SysBusDevice *dev)
+{
+ SysBusNvRamState *sys = DS1225Y(dev);
+ NvRamState *s = &sys->nvram;
+ FILE *file;
+
+ s->contents = g_malloc0(s->chip_size);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &nvram_ops, s,
+ "nvram", s->chip_size);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ /* Read current file */
+ file = fopen(s->filename, "rb");
+ if (file) {
+ /* Read nvram contents */
+ if (fread(s->contents, s->chip_size, 1, file) != 1) {
+ printf("nvram_sysbus_initfn: short read\n");
+ }
+ fclose(file);
+ }
+ nvram_post_load(s, 0);
+
+ return 0;
+}
+
+static Property nvram_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("size", SysBusNvRamState, nvram.chip_size, 0x2000),
+ DEFINE_PROP_STRING("filename", SysBusNvRamState, nvram.filename),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void nvram_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = nvram_sysbus_initfn;
+ dc->vmsd = &vmstate_nvram;
+ dc->props = nvram_sysbus_properties;
+}
+
+static const TypeInfo nvram_sysbus_info = {
+ .name = TYPE_DS1225Y,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusNvRamState),
+ .class_init = nvram_sysbus_class_init,
+};
+
+static void nvram_register_types(void)
+{
+ type_register_static(&nvram_sysbus_info);
+}
+
+type_init(nvram_register_types)
diff --git a/hw/nvram/eeprom93xx.c b/hw/nvram/eeprom93xx.c
new file mode 100644
index 00000000..0af4d670
--- /dev/null
+++ b/hw/nvram/eeprom93xx.c
@@ -0,0 +1,336 @@
+/*
+ * QEMU EEPROM 93xx emulation
+ *
+ * Copyright (c) 2006-2007 Stefan Weil
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Emulation for serial EEPROMs:
+ * NMC93C06 256-Bit (16 x 16)
+ * NMC93C46 1024-Bit (64 x 16)
+ * NMC93C56 2028 Bit (128 x 16)
+ * NMC93C66 4096 Bit (256 x 16)
+ * Compatible devices include FM93C46 and others.
+ *
+ * Other drivers use these interface functions:
+ * eeprom93xx_new - add a new EEPROM (with 16, 64 or 256 words)
+ * eeprom93xx_free - destroy EEPROM
+ * eeprom93xx_read - read data from the EEPROM
+ * eeprom93xx_write - write data to the EEPROM
+ * eeprom93xx_data - get EEPROM data array for external manipulation
+ *
+ * Todo list:
+ * - No emulation of EEPROM timings.
+ */
+
+#include "hw/hw.h"
+#include "hw/nvram/eeprom93xx.h"
+
+/* Debug EEPROM emulation. */
+//~ #define DEBUG_EEPROM
+
+#ifdef DEBUG_EEPROM
+#define logout(fmt, ...) fprintf(stderr, "EEPROM\t%-24s" fmt, __func__, ## __VA_ARGS__)
+#else
+#define logout(fmt, ...) ((void)0)
+#endif
+
+#define EEPROM_INSTANCE 0
+#define OLD_EEPROM_VERSION 20061112
+#define EEPROM_VERSION (OLD_EEPROM_VERSION + 1)
+
+#if 0
+typedef enum {
+ eeprom_read = 0x80, /* read register xx */
+ eeprom_write = 0x40, /* write register xx */
+ eeprom_erase = 0xc0, /* erase register xx */
+ eeprom_ewen = 0x30, /* erase / write enable */
+ eeprom_ewds = 0x00, /* erase / write disable */
+ eeprom_eral = 0x20, /* erase all registers */
+ eeprom_wral = 0x10, /* write all registers */
+ eeprom_amask = 0x0f,
+ eeprom_imask = 0xf0
+} eeprom_instruction_t;
+#endif
+
+#ifdef DEBUG_EEPROM
+static const char *opstring[] = {
+ "extended", "write", "read", "erase"
+};
+#endif
+
+struct _eeprom_t {
+ uint8_t tick;
+ uint8_t address;
+ uint8_t command;
+ uint8_t writable;
+
+ uint8_t eecs;
+ uint8_t eesk;
+ uint8_t eedo;
+
+ uint8_t addrbits;
+ uint16_t size;
+ uint16_t data;
+ uint16_t contents[0];
+};
+
+/* Code for saving and restoring of EEPROM state. */
+
+/* Restore an uint16_t from an uint8_t
+ This is a Big hack, but it is how the old state did it.
+ */
+
+static int get_uint16_from_uint8(QEMUFile *f, void *pv, size_t size)
+{
+ uint16_t *v = pv;
+ *v = qemu_get_ubyte(f);
+ return 0;
+}
+
+static void put_unused(QEMUFile *f, void *pv, size_t size)
+{
+ fprintf(stderr, "uint16_from_uint8 is used only for backwards compatibility.\n");
+ fprintf(stderr, "Never should be used to write a new state.\n");
+ exit(0);
+}
+
+static const VMStateInfo vmstate_hack_uint16_from_uint8 = {
+ .name = "uint16_from_uint8",
+ .get = get_uint16_from_uint8,
+ .put = put_unused,
+};
+
+#define VMSTATE_UINT16_HACK_TEST(_f, _s, _t) \
+ VMSTATE_SINGLE_TEST(_f, _s, _t, 0, vmstate_hack_uint16_from_uint8, uint16_t)
+
+static bool is_old_eeprom_version(void *opaque, int version_id)
+{
+ return version_id == OLD_EEPROM_VERSION;
+}
+
+static const VMStateDescription vmstate_eeprom = {
+ .name = "eeprom",
+ .version_id = EEPROM_VERSION,
+ .minimum_version_id = OLD_EEPROM_VERSION,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(tick, eeprom_t),
+ VMSTATE_UINT8(address, eeprom_t),
+ VMSTATE_UINT8(command, eeprom_t),
+ VMSTATE_UINT8(writable, eeprom_t),
+
+ VMSTATE_UINT8(eecs, eeprom_t),
+ VMSTATE_UINT8(eesk, eeprom_t),
+ VMSTATE_UINT8(eedo, eeprom_t),
+
+ VMSTATE_UINT8(addrbits, eeprom_t),
+ VMSTATE_UINT16_HACK_TEST(size, eeprom_t, is_old_eeprom_version),
+ VMSTATE_UNUSED_TEST(is_old_eeprom_version, 1),
+ VMSTATE_UINT16_EQUAL_V(size, eeprom_t, EEPROM_VERSION),
+ VMSTATE_UINT16(data, eeprom_t),
+ VMSTATE_VARRAY_UINT16_UNSAFE(contents, eeprom_t, size, 0,
+ vmstate_info_uint16, uint16_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+void eeprom93xx_write(eeprom_t *eeprom, int eecs, int eesk, int eedi)
+{
+ uint8_t tick = eeprom->tick;
+ uint8_t eedo = eeprom->eedo;
+ uint16_t address = eeprom->address;
+ uint8_t command = eeprom->command;
+
+ logout("CS=%u SK=%u DI=%u DO=%u, tick = %u\n",
+ eecs, eesk, eedi, eedo, tick);
+
+ if (!eeprom->eecs && eecs) {
+ /* Start chip select cycle. */
+ logout("Cycle start, waiting for 1st start bit (0)\n");
+ tick = 0;
+ command = 0x0;
+ address = 0x0;
+ } else if (eeprom->eecs && !eecs) {
+ /* End chip select cycle. This triggers write / erase. */
+ if (eeprom->writable) {
+ uint8_t subcommand = address >> (eeprom->addrbits - 2);
+ if (command == 0 && subcommand == 2) {
+ /* Erase all. */
+ for (address = 0; address < eeprom->size; address++) {
+ eeprom->contents[address] = 0xffff;
+ }
+ } else if (command == 3) {
+ /* Erase word. */
+ eeprom->contents[address] = 0xffff;
+ } else if (tick >= 2 + 2 + eeprom->addrbits + 16) {
+ if (command == 1) {
+ /* Write word. */
+ eeprom->contents[address] &= eeprom->data;
+ } else if (command == 0 && subcommand == 1) {
+ /* Write all. */
+ for (address = 0; address < eeprom->size; address++) {
+ eeprom->contents[address] &= eeprom->data;
+ }
+ }
+ }
+ }
+ /* Output DO is tristate, read results in 1. */
+ eedo = 1;
+ } else if (eecs && !eeprom->eesk && eesk) {
+ /* Raising edge of clock shifts data in. */
+ if (tick == 0) {
+ /* Wait for 1st start bit. */
+ if (eedi == 0) {
+ logout("Got correct 1st start bit, waiting for 2nd start bit (1)\n");
+ tick++;
+ } else {
+ logout("wrong 1st start bit (is 1, should be 0)\n");
+ tick = 2;
+ //~ assert(!"wrong start bit");
+ }
+ } else if (tick == 1) {
+ /* Wait for 2nd start bit. */
+ if (eedi != 0) {
+ logout("Got correct 2nd start bit, getting command + address\n");
+ tick++;
+ } else {
+ logout("1st start bit is longer than needed\n");
+ }
+ } else if (tick < 2 + 2) {
+ /* Got 2 start bits, transfer 2 opcode bits. */
+ tick++;
+ command <<= 1;
+ if (eedi) {
+ command += 1;
+ }
+ } else if (tick < 2 + 2 + eeprom->addrbits) {
+ /* Got 2 start bits and 2 opcode bits, transfer all address bits. */
+ tick++;
+ address = ((address << 1) | eedi);
+ if (tick == 2 + 2 + eeprom->addrbits) {
+ logout("%s command, address = 0x%02x (value 0x%04x)\n",
+ opstring[command], address, eeprom->contents[address]);
+ if (command == 2) {
+ eedo = 0;
+ }
+ address = address % eeprom->size;
+ if (command == 0) {
+ /* Command code in upper 2 bits of address. */
+ switch (address >> (eeprom->addrbits - 2)) {
+ case 0:
+ logout("write disable command\n");
+ eeprom->writable = 0;
+ break;
+ case 1:
+ logout("write all command\n");
+ break;
+ case 2:
+ logout("erase all command\n");
+ break;
+ case 3:
+ logout("write enable command\n");
+ eeprom->writable = 1;
+ break;
+ }
+ } else {
+ /* Read, write or erase word. */
+ eeprom->data = eeprom->contents[address];
+ }
+ }
+ } else if (tick < 2 + 2 + eeprom->addrbits + 16) {
+ /* Transfer 16 data bits. */
+ tick++;
+ if (command == 2) {
+ /* Read word. */
+ eedo = ((eeprom->data & 0x8000) != 0);
+ }
+ eeprom->data <<= 1;
+ eeprom->data += eedi;
+ } else {
+ logout("additional unneeded tick, not processed\n");
+ }
+ }
+ /* Save status of EEPROM. */
+ eeprom->tick = tick;
+ eeprom->eecs = eecs;
+ eeprom->eesk = eesk;
+ eeprom->eedo = eedo;
+ eeprom->address = address;
+ eeprom->command = command;
+}
+
+uint16_t eeprom93xx_read(eeprom_t *eeprom)
+{
+ /* Return status of pin DO (0 or 1). */
+ logout("CS=%u DO=%u\n", eeprom->eecs, eeprom->eedo);
+ return eeprom->eedo;
+}
+
+#if 0
+void eeprom93xx_reset(eeprom_t *eeprom)
+{
+ /* prepare eeprom */
+ logout("eeprom = 0x%p\n", eeprom);
+ eeprom->tick = 0;
+ eeprom->command = 0;
+}
+#endif
+
+eeprom_t *eeprom93xx_new(DeviceState *dev, uint16_t nwords)
+{
+ /* Add a new EEPROM (with 16, 64 or 256 words). */
+ eeprom_t *eeprom;
+ uint8_t addrbits;
+
+ switch (nwords) {
+ case 16:
+ case 64:
+ addrbits = 6;
+ break;
+ case 128:
+ case 256:
+ addrbits = 8;
+ break;
+ default:
+ assert(!"Unsupported EEPROM size, fallback to 64 words!");
+ nwords = 64;
+ addrbits = 6;
+ }
+
+ eeprom = (eeprom_t *)g_malloc0(sizeof(*eeprom) + nwords * 2);
+ eeprom->size = nwords;
+ eeprom->addrbits = addrbits;
+ /* Output DO is tristate, read results in 1. */
+ eeprom->eedo = 1;
+ logout("eeprom = 0x%p, nwords = %u\n", eeprom, nwords);
+ vmstate_register(dev, 0, &vmstate_eeprom, eeprom);
+ return eeprom;
+}
+
+void eeprom93xx_free(DeviceState *dev, eeprom_t *eeprom)
+{
+ /* Destroy EEPROM. */
+ logout("eeprom = 0x%p\n", eeprom);
+ vmstate_unregister(dev, &vmstate_eeprom, eeprom);
+ g_free(eeprom);
+}
+
+uint16_t *eeprom93xx_data(eeprom_t *eeprom)
+{
+ /* Get EEPROM data array. */
+ return &eeprom->contents[0];
+}
+
+/* eof */
diff --git a/hw/nvram/fw_cfg.c b/hw/nvram/fw_cfg.c
new file mode 100644
index 00000000..88481b78
--- /dev/null
+++ b/hw/nvram/fw_cfg.c
@@ -0,0 +1,753 @@
+/*
+ * QEMU Firmware configuration device emulation
+ *
+ * Copyright (c) 2008 Gleb Natapov
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "hw/isa/isa.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "qemu/error-report.h"
+#include "qemu/config-file.h"
+
+#define FW_CFG_SIZE 2
+#define FW_CFG_NAME "fw_cfg"
+#define FW_CFG_PATH "/machine/" FW_CFG_NAME
+
+#define TYPE_FW_CFG "fw_cfg"
+#define TYPE_FW_CFG_IO "fw_cfg_io"
+#define TYPE_FW_CFG_MEM "fw_cfg_mem"
+
+#define FW_CFG(obj) OBJECT_CHECK(FWCfgState, (obj), TYPE_FW_CFG)
+#define FW_CFG_IO(obj) OBJECT_CHECK(FWCfgIoState, (obj), TYPE_FW_CFG_IO)
+#define FW_CFG_MEM(obj) OBJECT_CHECK(FWCfgMemState, (obj), TYPE_FW_CFG_MEM)
+
+typedef struct FWCfgEntry {
+ uint32_t len;
+ uint8_t *data;
+ void *callback_opaque;
+ FWCfgReadCallback read_callback;
+} FWCfgEntry;
+
+struct FWCfgState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ FWCfgEntry entries[2][FW_CFG_MAX_ENTRY];
+ FWCfgFiles *files;
+ uint16_t cur_entry;
+ uint32_t cur_offset;
+ Notifier machine_ready;
+};
+
+struct FWCfgIoState {
+ /*< private >*/
+ FWCfgState parent_obj;
+ /*< public >*/
+
+ MemoryRegion comb_iomem;
+ uint32_t iobase;
+};
+
+struct FWCfgMemState {
+ /*< private >*/
+ FWCfgState parent_obj;
+ /*< public >*/
+
+ MemoryRegion ctl_iomem, data_iomem;
+ uint32_t data_width;
+ MemoryRegionOps wide_data_ops;
+};
+
+#define JPG_FILE 0
+#define BMP_FILE 1
+
+static char *read_splashfile(char *filename, gsize *file_sizep,
+ int *file_typep)
+{
+ GError *err = NULL;
+ gboolean res;
+ gchar *content;
+ int file_type;
+ unsigned int filehead;
+ int bmp_bpp;
+
+ res = g_file_get_contents(filename, &content, file_sizep, &err);
+ if (res == FALSE) {
+ error_report("failed to read splash file '%s'", filename);
+ g_error_free(err);
+ return NULL;
+ }
+
+ /* check file size */
+ if (*file_sizep < 30) {
+ goto error;
+ }
+
+ /* check magic ID */
+ filehead = ((content[0] & 0xff) + (content[1] << 8)) & 0xffff;
+ if (filehead == 0xd8ff) {
+ file_type = JPG_FILE;
+ } else if (filehead == 0x4d42) {
+ file_type = BMP_FILE;
+ } else {
+ goto error;
+ }
+
+ /* check BMP bpp */
+ if (file_type == BMP_FILE) {
+ bmp_bpp = (content[28] + (content[29] << 8)) & 0xffff;
+ if (bmp_bpp != 24) {
+ goto error;
+ }
+ }
+
+ /* return values */
+ *file_typep = file_type;
+
+ return content;
+
+error:
+ error_report("splash file '%s' format not recognized; must be JPEG "
+ "or 24 bit BMP", filename);
+ g_free(content);
+ return NULL;
+}
+
+static void fw_cfg_bootsplash(FWCfgState *s)
+{
+ int boot_splash_time = -1;
+ const char *boot_splash_filename = NULL;
+ char *p;
+ char *filename, *file_data;
+ gsize file_size;
+ int file_type;
+ const char *temp;
+
+ /* get user configuration */
+ QemuOptsList *plist = qemu_find_opts("boot-opts");
+ QemuOpts *opts = QTAILQ_FIRST(&plist->head);
+ if (opts != NULL) {
+ temp = qemu_opt_get(opts, "splash");
+ if (temp != NULL) {
+ boot_splash_filename = temp;
+ }
+ temp = qemu_opt_get(opts, "splash-time");
+ if (temp != NULL) {
+ p = (char *)temp;
+ boot_splash_time = strtol(p, (char **)&p, 10);
+ }
+ }
+
+ /* insert splash time if user configurated */
+ if (boot_splash_time >= 0) {
+ /* validate the input */
+ if (boot_splash_time > 0xffff) {
+ error_report("splash time is big than 65535, force it to 65535.");
+ boot_splash_time = 0xffff;
+ }
+ /* use little endian format */
+ qemu_extra_params_fw[0] = (uint8_t)(boot_splash_time & 0xff);
+ qemu_extra_params_fw[1] = (uint8_t)((boot_splash_time >> 8) & 0xff);
+ fw_cfg_add_file(s, "etc/boot-menu-wait", qemu_extra_params_fw, 2);
+ }
+
+ /* insert splash file if user configurated */
+ if (boot_splash_filename != NULL) {
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, boot_splash_filename);
+ if (filename == NULL) {
+ error_report("failed to find file '%s'.", boot_splash_filename);
+ return;
+ }
+
+ /* loading file data */
+ file_data = read_splashfile(filename, &file_size, &file_type);
+ if (file_data == NULL) {
+ g_free(filename);
+ return;
+ }
+ if (boot_splash_filedata != NULL) {
+ g_free(boot_splash_filedata);
+ }
+ boot_splash_filedata = (uint8_t *)file_data;
+ boot_splash_filedata_size = file_size;
+
+ /* insert data */
+ if (file_type == JPG_FILE) {
+ fw_cfg_add_file(s, "bootsplash.jpg",
+ boot_splash_filedata, boot_splash_filedata_size);
+ } else {
+ fw_cfg_add_file(s, "bootsplash.bmp",
+ boot_splash_filedata, boot_splash_filedata_size);
+ }
+ g_free(filename);
+ }
+}
+
+static void fw_cfg_reboot(FWCfgState *s)
+{
+ int reboot_timeout = -1;
+ char *p;
+ const char *temp;
+
+ /* get user configuration */
+ QemuOptsList *plist = qemu_find_opts("boot-opts");
+ QemuOpts *opts = QTAILQ_FIRST(&plist->head);
+ if (opts != NULL) {
+ temp = qemu_opt_get(opts, "reboot-timeout");
+ if (temp != NULL) {
+ p = (char *)temp;
+ reboot_timeout = strtol(p, (char **)&p, 10);
+ }
+ }
+ /* validate the input */
+ if (reboot_timeout > 0xffff) {
+ error_report("reboot timeout is larger than 65535, force it to 65535.");
+ reboot_timeout = 0xffff;
+ }
+ fw_cfg_add_file(s, "etc/boot-fail-wait", g_memdup(&reboot_timeout, 4), 4);
+}
+
+static void fw_cfg_write(FWCfgState *s, uint8_t value)
+{
+ /* nothing, write support removed in QEMU v2.4+ */
+}
+
+static int fw_cfg_select(FWCfgState *s, uint16_t key)
+{
+ int ret;
+
+ s->cur_offset = 0;
+ if ((key & FW_CFG_ENTRY_MASK) >= FW_CFG_MAX_ENTRY) {
+ s->cur_entry = FW_CFG_INVALID;
+ ret = 0;
+ } else {
+ s->cur_entry = key;
+ ret = 1;
+ }
+
+ trace_fw_cfg_select(s, key, ret);
+ return ret;
+}
+
+static uint8_t fw_cfg_read(FWCfgState *s)
+{
+ int arch = !!(s->cur_entry & FW_CFG_ARCH_LOCAL);
+ FWCfgEntry *e = &s->entries[arch][s->cur_entry & FW_CFG_ENTRY_MASK];
+ uint8_t ret;
+
+ if (s->cur_entry == FW_CFG_INVALID || !e->data || s->cur_offset >= e->len)
+ ret = 0;
+ else {
+ if (e->read_callback) {
+ e->read_callback(e->callback_opaque, s->cur_offset);
+ }
+ ret = e->data[s->cur_offset++];
+ }
+
+ trace_fw_cfg_read(s, ret);
+ return ret;
+}
+
+static uint64_t fw_cfg_data_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ FWCfgState *s = opaque;
+ uint64_t value = 0;
+ unsigned i;
+
+ for (i = 0; i < size; ++i) {
+ value = (value << 8) | fw_cfg_read(s);
+ }
+ return value;
+}
+
+static void fw_cfg_data_mem_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ FWCfgState *s = opaque;
+ unsigned i = size;
+
+ do {
+ fw_cfg_write(s, value >> (8 * --i));
+ } while (i);
+}
+
+static bool fw_cfg_data_mem_valid(void *opaque, hwaddr addr,
+ unsigned size, bool is_write)
+{
+ return addr == 0;
+}
+
+static void fw_cfg_ctl_mem_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ fw_cfg_select(opaque, (uint16_t)value);
+}
+
+static bool fw_cfg_ctl_mem_valid(void *opaque, hwaddr addr,
+ unsigned size, bool is_write)
+{
+ return is_write && size == 2;
+}
+
+static uint64_t fw_cfg_comb_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return fw_cfg_read(opaque);
+}
+
+static void fw_cfg_comb_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ switch (size) {
+ case 1:
+ fw_cfg_write(opaque, (uint8_t)value);
+ break;
+ case 2:
+ fw_cfg_select(opaque, (uint16_t)value);
+ break;
+ }
+}
+
+static bool fw_cfg_comb_valid(void *opaque, hwaddr addr,
+ unsigned size, bool is_write)
+{
+ return (size == 1) || (is_write && size == 2);
+}
+
+static const MemoryRegionOps fw_cfg_ctl_mem_ops = {
+ .write = fw_cfg_ctl_mem_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid.accepts = fw_cfg_ctl_mem_valid,
+};
+
+static const MemoryRegionOps fw_cfg_data_mem_ops = {
+ .read = fw_cfg_data_mem_read,
+ .write = fw_cfg_data_mem_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ .accepts = fw_cfg_data_mem_valid,
+ },
+};
+
+static const MemoryRegionOps fw_cfg_comb_mem_ops = {
+ .read = fw_cfg_comb_read,
+ .write = fw_cfg_comb_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.accepts = fw_cfg_comb_valid,
+};
+
+static void fw_cfg_reset(DeviceState *d)
+{
+ FWCfgState *s = FW_CFG(d);
+
+ fw_cfg_select(s, 0);
+}
+
+/* Save restore 32 bit int as uint16_t
+ This is a Big hack, but it is how the old state did it.
+ Or we broke compatibility in the state, or we can't use struct tm
+ */
+
+static int get_uint32_as_uint16(QEMUFile *f, void *pv, size_t size)
+{
+ uint32_t *v = pv;
+ *v = qemu_get_be16(f);
+ return 0;
+}
+
+static void put_unused(QEMUFile *f, void *pv, size_t size)
+{
+ fprintf(stderr, "uint32_as_uint16 is only used for backward compatibility.\n");
+ fprintf(stderr, "This functions shouldn't be called.\n");
+}
+
+static const VMStateInfo vmstate_hack_uint32_as_uint16 = {
+ .name = "int32_as_uint16",
+ .get = get_uint32_as_uint16,
+ .put = put_unused,
+};
+
+#define VMSTATE_UINT16_HACK(_f, _s, _t) \
+ VMSTATE_SINGLE_TEST(_f, _s, _t, 0, vmstate_hack_uint32_as_uint16, uint32_t)
+
+
+static bool is_version_1(void *opaque, int version_id)
+{
+ return version_id == 1;
+}
+
+static const VMStateDescription vmstate_fw_cfg = {
+ .name = "fw_cfg",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(cur_entry, FWCfgState),
+ VMSTATE_UINT16_HACK(cur_offset, FWCfgState, is_version_1),
+ VMSTATE_UINT32_V(cur_offset, FWCfgState, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void fw_cfg_add_bytes_read_callback(FWCfgState *s, uint16_t key,
+ FWCfgReadCallback callback,
+ void *callback_opaque,
+ void *data, size_t len)
+{
+ int arch = !!(key & FW_CFG_ARCH_LOCAL);
+
+ key &= FW_CFG_ENTRY_MASK;
+
+ assert(key < FW_CFG_MAX_ENTRY && len < UINT32_MAX);
+ assert(s->entries[arch][key].data == NULL); /* avoid key conflict */
+
+ s->entries[arch][key].data = data;
+ s->entries[arch][key].len = (uint32_t)len;
+ s->entries[arch][key].read_callback = callback;
+ s->entries[arch][key].callback_opaque = callback_opaque;
+}
+
+static void *fw_cfg_modify_bytes_read(FWCfgState *s, uint16_t key,
+ void *data, size_t len)
+{
+ void *ptr;
+ int arch = !!(key & FW_CFG_ARCH_LOCAL);
+
+ key &= FW_CFG_ENTRY_MASK;
+
+ assert(key < FW_CFG_MAX_ENTRY && len < UINT32_MAX);
+
+ /* return the old data to the function caller, avoid memory leak */
+ ptr = s->entries[arch][key].data;
+ s->entries[arch][key].data = data;
+ s->entries[arch][key].len = len;
+ s->entries[arch][key].callback_opaque = NULL;
+
+ return ptr;
+}
+
+void fw_cfg_add_bytes(FWCfgState *s, uint16_t key, void *data, size_t len)
+{
+ fw_cfg_add_bytes_read_callback(s, key, NULL, NULL, data, len);
+}
+
+void fw_cfg_add_string(FWCfgState *s, uint16_t key, const char *value)
+{
+ size_t sz = strlen(value) + 1;
+
+ fw_cfg_add_bytes(s, key, g_memdup(value, sz), sz);
+}
+
+void fw_cfg_add_i16(FWCfgState *s, uint16_t key, uint16_t value)
+{
+ uint16_t *copy;
+
+ copy = g_malloc(sizeof(value));
+ *copy = cpu_to_le16(value);
+ fw_cfg_add_bytes(s, key, copy, sizeof(value));
+}
+
+void fw_cfg_modify_i16(FWCfgState *s, uint16_t key, uint16_t value)
+{
+ uint16_t *copy, *old;
+
+ copy = g_malloc(sizeof(value));
+ *copy = cpu_to_le16(value);
+ old = fw_cfg_modify_bytes_read(s, key, copy, sizeof(value));
+ g_free(old);
+}
+
+void fw_cfg_add_i32(FWCfgState *s, uint16_t key, uint32_t value)
+{
+ uint32_t *copy;
+
+ copy = g_malloc(sizeof(value));
+ *copy = cpu_to_le32(value);
+ fw_cfg_add_bytes(s, key, copy, sizeof(value));
+}
+
+void fw_cfg_add_i64(FWCfgState *s, uint16_t key, uint64_t value)
+{
+ uint64_t *copy;
+
+ copy = g_malloc(sizeof(value));
+ *copy = cpu_to_le64(value);
+ fw_cfg_add_bytes(s, key, copy, sizeof(value));
+}
+
+void fw_cfg_add_file_callback(FWCfgState *s, const char *filename,
+ FWCfgReadCallback callback, void *callback_opaque,
+ void *data, size_t len)
+{
+ int i, index;
+ size_t dsize;
+
+ if (!s->files) {
+ dsize = sizeof(uint32_t) + sizeof(FWCfgFile) * FW_CFG_FILE_SLOTS;
+ s->files = g_malloc0(dsize);
+ fw_cfg_add_bytes(s, FW_CFG_FILE_DIR, s->files, dsize);
+ }
+
+ index = be32_to_cpu(s->files->count);
+ assert(index < FW_CFG_FILE_SLOTS);
+
+ pstrcpy(s->files->f[index].name, sizeof(s->files->f[index].name),
+ filename);
+ for (i = 0; i < index; i++) {
+ if (strcmp(s->files->f[index].name, s->files->f[i].name) == 0) {
+ error_report("duplicate fw_cfg file name: %s",
+ s->files->f[index].name);
+ exit(1);
+ }
+ }
+
+ fw_cfg_add_bytes_read_callback(s, FW_CFG_FILE_FIRST + index,
+ callback, callback_opaque, data, len);
+
+ s->files->f[index].size = cpu_to_be32(len);
+ s->files->f[index].select = cpu_to_be16(FW_CFG_FILE_FIRST + index);
+ trace_fw_cfg_add_file(s, index, s->files->f[index].name, len);
+
+ s->files->count = cpu_to_be32(index+1);
+}
+
+void fw_cfg_add_file(FWCfgState *s, const char *filename,
+ void *data, size_t len)
+{
+ fw_cfg_add_file_callback(s, filename, NULL, NULL, data, len);
+}
+
+void *fw_cfg_modify_file(FWCfgState *s, const char *filename,
+ void *data, size_t len)
+{
+ int i, index;
+ void *ptr = NULL;
+
+ assert(s->files);
+
+ index = be32_to_cpu(s->files->count);
+ assert(index < FW_CFG_FILE_SLOTS);
+
+ for (i = 0; i < index; i++) {
+ if (strcmp(filename, s->files->f[i].name) == 0) {
+ ptr = fw_cfg_modify_bytes_read(s, FW_CFG_FILE_FIRST + i,
+ data, len);
+ s->files->f[i].size = cpu_to_be32(len);
+ return ptr;
+ }
+ }
+ /* add new one */
+ fw_cfg_add_file_callback(s, filename, NULL, NULL, data, len);
+ return NULL;
+}
+
+static void fw_cfg_machine_reset(void *opaque)
+{
+ void *ptr;
+ size_t len;
+ FWCfgState *s = opaque;
+ char *bootindex = get_boot_devices_list(&len, false);
+
+ ptr = fw_cfg_modify_file(s, "bootorder", (uint8_t *)bootindex, len);
+ g_free(ptr);
+}
+
+static void fw_cfg_machine_ready(struct Notifier *n, void *data)
+{
+ FWCfgState *s = container_of(n, FWCfgState, machine_ready);
+ qemu_register_reset(fw_cfg_machine_reset, s);
+}
+
+
+
+static void fw_cfg_init1(DeviceState *dev)
+{
+ FWCfgState *s = FW_CFG(dev);
+
+ assert(!object_resolve_path(FW_CFG_PATH, NULL));
+
+ object_property_add_child(qdev_get_machine(), FW_CFG_NAME, OBJECT(s), NULL);
+
+ qdev_init_nofail(dev);
+
+ fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (char *)"QEMU", 4);
+ fw_cfg_add_i32(s, FW_CFG_ID, 1);
+ fw_cfg_add_bytes(s, FW_CFG_UUID, qemu_uuid, 16);
+ fw_cfg_add_i16(s, FW_CFG_NOGRAPHIC, (uint16_t)(display_type == DT_NOGRAPHIC));
+ fw_cfg_add_i16(s, FW_CFG_NB_CPUS, (uint16_t)smp_cpus);
+ fw_cfg_add_i16(s, FW_CFG_BOOT_MENU, (uint16_t)boot_menu);
+ fw_cfg_bootsplash(s);
+ fw_cfg_reboot(s);
+
+ s->machine_ready.notify = fw_cfg_machine_ready;
+ qemu_add_machine_init_done_notifier(&s->machine_ready);
+}
+
+FWCfgState *fw_cfg_init_io(uint32_t iobase)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, TYPE_FW_CFG_IO);
+ qdev_prop_set_uint32(dev, "iobase", iobase);
+ fw_cfg_init1(dev);
+
+ return FW_CFG(dev);
+}
+
+FWCfgState *fw_cfg_init_mem_wide(hwaddr ctl_addr, hwaddr data_addr,
+ uint32_t data_width)
+{
+ DeviceState *dev;
+ SysBusDevice *sbd;
+
+ dev = qdev_create(NULL, TYPE_FW_CFG_MEM);
+ qdev_prop_set_uint32(dev, "data_width", data_width);
+
+ fw_cfg_init1(dev);
+
+ sbd = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(sbd, 0, ctl_addr);
+ sysbus_mmio_map(sbd, 1, data_addr);
+
+ return FW_CFG(dev);
+}
+
+FWCfgState *fw_cfg_init_mem(hwaddr ctl_addr, hwaddr data_addr)
+{
+ return fw_cfg_init_mem_wide(ctl_addr, data_addr,
+ fw_cfg_data_mem_ops.valid.max_access_size);
+}
+
+
+FWCfgState *fw_cfg_find(void)
+{
+ return FW_CFG(object_resolve_path(FW_CFG_PATH, NULL));
+}
+
+static void fw_cfg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = fw_cfg_reset;
+ dc->vmsd = &vmstate_fw_cfg;
+}
+
+static const TypeInfo fw_cfg_info = {
+ .name = TYPE_FW_CFG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(FWCfgState),
+ .class_init = fw_cfg_class_init,
+};
+
+
+static Property fw_cfg_io_properties[] = {
+ DEFINE_PROP_UINT32("iobase", FWCfgIoState, iobase, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void fw_cfg_io_realize(DeviceState *dev, Error **errp)
+{
+ FWCfgIoState *s = FW_CFG_IO(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->comb_iomem, OBJECT(s), &fw_cfg_comb_mem_ops,
+ FW_CFG(s), "fwcfg", FW_CFG_SIZE);
+ sysbus_add_io(sbd, s->iobase, &s->comb_iomem);
+}
+
+static void fw_cfg_io_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = fw_cfg_io_realize;
+ dc->props = fw_cfg_io_properties;
+}
+
+static const TypeInfo fw_cfg_io_info = {
+ .name = TYPE_FW_CFG_IO,
+ .parent = TYPE_FW_CFG,
+ .instance_size = sizeof(FWCfgIoState),
+ .class_init = fw_cfg_io_class_init,
+};
+
+
+static Property fw_cfg_mem_properties[] = {
+ DEFINE_PROP_UINT32("data_width", FWCfgMemState, data_width, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void fw_cfg_mem_realize(DeviceState *dev, Error **errp)
+{
+ FWCfgMemState *s = FW_CFG_MEM(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ const MemoryRegionOps *data_ops = &fw_cfg_data_mem_ops;
+
+ memory_region_init_io(&s->ctl_iomem, OBJECT(s), &fw_cfg_ctl_mem_ops,
+ FW_CFG(s), "fwcfg.ctl", FW_CFG_SIZE);
+ sysbus_init_mmio(sbd, &s->ctl_iomem);
+
+ if (s->data_width > data_ops->valid.max_access_size) {
+ /* memberwise copy because the "old_mmio" member is const */
+ s->wide_data_ops.read = data_ops->read;
+ s->wide_data_ops.write = data_ops->write;
+ s->wide_data_ops.endianness = data_ops->endianness;
+ s->wide_data_ops.valid = data_ops->valid;
+ s->wide_data_ops.impl = data_ops->impl;
+
+ s->wide_data_ops.valid.max_access_size = s->data_width;
+ s->wide_data_ops.impl.max_access_size = s->data_width;
+ data_ops = &s->wide_data_ops;
+ }
+ memory_region_init_io(&s->data_iomem, OBJECT(s), data_ops, FW_CFG(s),
+ "fwcfg.data", data_ops->valid.max_access_size);
+ sysbus_init_mmio(sbd, &s->data_iomem);
+}
+
+static void fw_cfg_mem_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = fw_cfg_mem_realize;
+ dc->props = fw_cfg_mem_properties;
+}
+
+static const TypeInfo fw_cfg_mem_info = {
+ .name = TYPE_FW_CFG_MEM,
+ .parent = TYPE_FW_CFG,
+ .instance_size = sizeof(FWCfgMemState),
+ .class_init = fw_cfg_mem_class_init,
+};
+
+
+static void fw_cfg_register_types(void)
+{
+ type_register_static(&fw_cfg_info);
+ type_register_static(&fw_cfg_io_info);
+ type_register_static(&fw_cfg_mem_info);
+}
+
+type_init(fw_cfg_register_types)
diff --git a/hw/nvram/mac_nvram.c b/hw/nvram/mac_nvram.c
new file mode 100644
index 00000000..d35f8a31
--- /dev/null
+++ b/hw/nvram/mac_nvram.c
@@ -0,0 +1,213 @@
+/*
+ * PowerMac NVRAM emulation
+ *
+ * Copyright (c) 2005-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/nvram/openbios_firmware_abi.h"
+#include "sysemu/sysemu.h"
+#include "hw/ppc/mac.h"
+#include <zlib.h>
+
+/* debug NVR */
+//#define DEBUG_NVR
+
+#ifdef DEBUG_NVR
+#define NVR_DPRINTF(fmt, ...) \
+ do { printf("NVR: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define NVR_DPRINTF(fmt, ...)
+#endif
+
+#define DEF_SYSTEM_SIZE 0xc10
+
+/* macio style NVRAM device */
+static void macio_nvram_writeb(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ MacIONVRAMState *s = opaque;
+
+ addr = (addr >> s->it_shift) & (s->size - 1);
+ s->data[addr] = value;
+ NVR_DPRINTF("writeb addr %04" PHYS_PRIx " val %" PRIx64 "\n", addr, value);
+}
+
+static uint64_t macio_nvram_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MacIONVRAMState *s = opaque;
+ uint32_t value;
+
+ addr = (addr >> s->it_shift) & (s->size - 1);
+ value = s->data[addr];
+ NVR_DPRINTF("readb addr %04x val %x\n", (int)addr, value);
+
+ return value;
+}
+
+static const MemoryRegionOps macio_nvram_ops = {
+ .read = macio_nvram_readb,
+ .write = macio_nvram_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static const VMStateDescription vmstate_macio_nvram = {
+ .name = "macio_nvram",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_VBUFFER_UINT32(data, MacIONVRAMState, 0, NULL, 0, size),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+static void macio_nvram_reset(DeviceState *dev)
+{
+}
+
+static void macio_nvram_realizefn(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(dev);
+ MacIONVRAMState *s = MACIO_NVRAM(dev);
+
+ s->data = g_malloc0(s->size);
+
+ memory_region_init_io(&s->mem, OBJECT(s), &macio_nvram_ops, s,
+ "macio-nvram", s->size << s->it_shift);
+ sysbus_init_mmio(d, &s->mem);
+}
+
+static void macio_nvram_unrealizefn(DeviceState *dev, Error **errp)
+{
+ MacIONVRAMState *s = MACIO_NVRAM(dev);
+
+ g_free(s->data);
+}
+
+static Property macio_nvram_properties[] = {
+ DEFINE_PROP_UINT32("size", MacIONVRAMState, size, 0),
+ DEFINE_PROP_UINT32("it_shift", MacIONVRAMState, it_shift, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void macio_nvram_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = macio_nvram_realizefn;
+ dc->unrealize = macio_nvram_unrealizefn;
+ dc->reset = macio_nvram_reset;
+ dc->vmsd = &vmstate_macio_nvram;
+ dc->props = macio_nvram_properties;
+}
+
+static const TypeInfo macio_nvram_type_info = {
+ .name = TYPE_MACIO_NVRAM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MacIONVRAMState),
+ .class_init = macio_nvram_class_init,
+};
+
+static void macio_nvram_register_types(void)
+{
+ type_register_static(&macio_nvram_type_info);
+}
+
+/* Set up a system OpenBIOS NVRAM partition */
+static void pmac_format_nvram_partition_of(MacIONVRAMState *nvr, int off,
+ int len)
+{
+ unsigned int i;
+ uint32_t start = off, end;
+ struct OpenBIOS_nvpart_v1 *part_header;
+
+ // OpenBIOS nvram variables
+ // Variable partition
+ part_header = (struct OpenBIOS_nvpart_v1 *)&nvr->data[start];
+ part_header->signature = OPENBIOS_PART_SYSTEM;
+ pstrcpy(part_header->name, sizeof(part_header->name), "system");
+
+ end = start + sizeof(struct OpenBIOS_nvpart_v1);
+ for (i = 0; i < nb_prom_envs; i++)
+ end = OpenBIOS_set_var(nvr->data, end, prom_envs[i]);
+
+ // End marker
+ nvr->data[end++] = '\0';
+
+ end = start + ((end - start + 15) & ~15);
+ /* XXX: OpenBIOS is not able to grow up a partition. Leave some space for
+ new variables. */
+ if (end < DEF_SYSTEM_SIZE)
+ end = DEF_SYSTEM_SIZE;
+ OpenBIOS_finish_partition(part_header, end - start);
+
+ // free partition
+ start = end;
+ part_header = (struct OpenBIOS_nvpart_v1 *)&nvr->data[start];
+ part_header->signature = OPENBIOS_PART_FREE;
+ pstrcpy(part_header->name, sizeof(part_header->name), "free");
+
+ end = len;
+ OpenBIOS_finish_partition(part_header, end - start);
+}
+
+#define OSX_NVRAM_SIGNATURE (0x5A)
+
+/* Set up a Mac OS X NVRAM partition */
+static void pmac_format_nvram_partition_osx(MacIONVRAMState *nvr, int off,
+ int len)
+{
+ uint32_t start = off;
+ struct OpenBIOS_nvpart_v1 *part_header;
+ unsigned char *data = &nvr->data[start];
+
+ /* empty partition */
+ part_header = (struct OpenBIOS_nvpart_v1 *)data;
+ part_header->signature = OSX_NVRAM_SIGNATURE;
+ pstrcpy(part_header->name, sizeof(part_header->name), "wwwwwwwwwwww");
+
+ OpenBIOS_finish_partition(part_header, len);
+
+ /* Generation */
+ stl_be_p(&data[20], 2);
+
+ /* Adler32 checksum */
+ stl_be_p(&data[16], adler32(0, &data[20], len - 20));
+}
+
+/* Set up NVRAM with OF and OSX partitions */
+void pmac_format_nvram_partition(MacIONVRAMState *nvr, int len)
+{
+ /*
+ * Mac OS X expects side "B" of the flash at the second half of NVRAM,
+ * so we use half of the chip for OF and the other half for a free OSX
+ * partition.
+ */
+ pmac_format_nvram_partition_of(nvr, 0, len / 2);
+ pmac_format_nvram_partition_osx(nvr, len / 2, len / 2);
+}
+type_init(macio_nvram_register_types)
diff --git a/hw/nvram/spapr_nvram.c b/hw/nvram/spapr_nvram.c
new file mode 100644
index 00000000..fcaa77dd
--- /dev/null
+++ b/hw/nvram/spapr_nvram.c
@@ -0,0 +1,248 @@
+/*
+ * QEMU sPAPR NVRAM emulation
+ *
+ * Copyright (C) 2012 David Gibson, IBM Corporation.
+ *
+ * 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.
+ */
+
+#include <libfdt.h>
+
+#include "sysemu/block-backend.h"
+#include "sysemu/device_tree.h"
+#include "hw/sysbus.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+
+typedef struct sPAPRNVRAM {
+ VIOsPAPRDevice sdev;
+ uint32_t size;
+ uint8_t *buf;
+ BlockBackend *blk;
+} sPAPRNVRAM;
+
+#define TYPE_VIO_SPAPR_NVRAM "spapr-nvram"
+#define VIO_SPAPR_NVRAM(obj) \
+ OBJECT_CHECK(sPAPRNVRAM, (obj), TYPE_VIO_SPAPR_NVRAM)
+
+#define MIN_NVRAM_SIZE 8192
+#define DEFAULT_NVRAM_SIZE 65536
+#define MAX_NVRAM_SIZE 1048576
+
+static void rtas_nvram_fetch(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ sPAPRNVRAM *nvram = spapr->nvram;
+ hwaddr offset, buffer, len;
+ void *membuf;
+
+ if ((nargs != 3) || (nret != 2)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ if (!nvram) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ rtas_st(rets, 1, 0);
+ return;
+ }
+
+ offset = rtas_ld(args, 0);
+ buffer = rtas_ld(args, 1);
+ len = rtas_ld(args, 2);
+
+ if (((offset + len) < offset)
+ || ((offset + len) > nvram->size)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ rtas_st(rets, 1, 0);
+ return;
+ }
+
+ assert(nvram->buf);
+
+ membuf = cpu_physical_memory_map(buffer, &len, 1);
+ memcpy(membuf, nvram->buf + offset, len);
+ cpu_physical_memory_unmap(membuf, len, 1, len);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, len);
+}
+
+static void rtas_nvram_store(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ sPAPRNVRAM *nvram = spapr->nvram;
+ hwaddr offset, buffer, len;
+ int alen;
+ void *membuf;
+
+ if ((nargs != 3) || (nret != 2)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ if (!nvram) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ offset = rtas_ld(args, 0);
+ buffer = rtas_ld(args, 1);
+ len = rtas_ld(args, 2);
+
+ if (((offset + len) < offset)
+ || ((offset + len) > nvram->size)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ membuf = cpu_physical_memory_map(buffer, &len, 0);
+
+ alen = len;
+ if (nvram->blk) {
+ alen = blk_pwrite(nvram->blk, offset, membuf, len);
+ }
+
+ assert(nvram->buf);
+ memcpy(nvram->buf + offset, membuf, len);
+
+ cpu_physical_memory_unmap(membuf, len, 0, len);
+
+ rtas_st(rets, 0, (alen < len) ? RTAS_OUT_HW_ERROR : RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, (alen < 0) ? 0 : alen);
+}
+
+static void spapr_nvram_realize(VIOsPAPRDevice *dev, Error **errp)
+{
+ sPAPRNVRAM *nvram = VIO_SPAPR_NVRAM(dev);
+
+ if (nvram->blk) {
+ nvram->size = blk_getlength(nvram->blk);
+ } else {
+ nvram->size = DEFAULT_NVRAM_SIZE;
+ }
+
+ nvram->buf = g_malloc0(nvram->size);
+
+ if ((nvram->size < MIN_NVRAM_SIZE) || (nvram->size > MAX_NVRAM_SIZE)) {
+ error_setg(errp, "spapr-nvram must be between %d and %d bytes in size",
+ MIN_NVRAM_SIZE, MAX_NVRAM_SIZE);
+ return;
+ }
+
+ if (nvram->blk) {
+ int alen = blk_pread(nvram->blk, 0, nvram->buf, nvram->size);
+
+ if (alen != nvram->size) {
+ error_setg(errp, "can't read spapr-nvram contents");
+ return;
+ }
+ }
+
+ spapr_rtas_register(RTAS_NVRAM_FETCH, "nvram-fetch", rtas_nvram_fetch);
+ spapr_rtas_register(RTAS_NVRAM_STORE, "nvram-store", rtas_nvram_store);
+}
+
+static int spapr_nvram_devnode(VIOsPAPRDevice *dev, void *fdt, int node_off)
+{
+ sPAPRNVRAM *nvram = VIO_SPAPR_NVRAM(dev);
+
+ return fdt_setprop_cell(fdt, node_off, "#bytes", nvram->size);
+}
+
+static int spapr_nvram_pre_load(void *opaque)
+{
+ sPAPRNVRAM *nvram = VIO_SPAPR_NVRAM(opaque);
+
+ g_free(nvram->buf);
+ nvram->buf = NULL;
+ nvram->size = 0;
+
+ return 0;
+}
+
+static int spapr_nvram_post_load(void *opaque, int version_id)
+{
+ sPAPRNVRAM *nvram = VIO_SPAPR_NVRAM(opaque);
+
+ if (nvram->blk) {
+ int alen = blk_pwrite(nvram->blk, 0, nvram->buf, nvram->size);
+
+ if (alen < 0) {
+ return alen;
+ }
+ if (alen != nvram->size) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_spapr_nvram = {
+ .name = "spapr_nvram",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_load = spapr_nvram_pre_load,
+ .post_load = spapr_nvram_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(size, sPAPRNVRAM),
+ VMSTATE_VBUFFER_ALLOC_UINT32(buf, sPAPRNVRAM, 1, NULL, 0, size),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static Property spapr_nvram_properties[] = {
+ DEFINE_SPAPR_PROPERTIES(sPAPRNVRAM, sdev),
+ DEFINE_PROP_DRIVE("drive", sPAPRNVRAM, blk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void spapr_nvram_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VIOsPAPRDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
+
+ k->realize = spapr_nvram_realize;
+ k->devnode = spapr_nvram_devnode;
+ k->dt_name = "nvram";
+ k->dt_type = "nvram";
+ k->dt_compatible = "qemu,spapr-nvram";
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->props = spapr_nvram_properties;
+ dc->vmsd = &vmstate_spapr_nvram;
+}
+
+static const TypeInfo spapr_nvram_type_info = {
+ .name = TYPE_VIO_SPAPR_NVRAM,
+ .parent = TYPE_VIO_SPAPR_DEVICE,
+ .instance_size = sizeof(sPAPRNVRAM),
+ .class_init = spapr_nvram_class_init,
+};
+
+static void spapr_nvram_register_types(void)
+{
+ type_register_static(&spapr_nvram_type_info);
+}
+
+type_init(spapr_nvram_register_types)
diff --git a/hw/openrisc/Makefile.objs b/hw/openrisc/Makefile.objs
new file mode 100644
index 00000000..61246b14
--- /dev/null
+++ b/hw/openrisc/Makefile.objs
@@ -0,0 +1,2 @@
+obj-y = pic_cpu.o cputimer.o
+obj-y += openrisc_sim.o
diff --git a/hw/openrisc/cputimer.c b/hw/openrisc/cputimer.c
new file mode 100644
index 00000000..9c549451
--- /dev/null
+++ b/hw/openrisc/cputimer.c
@@ -0,0 +1,112 @@
+/*
+ * QEMU OpenRISC timer support
+ *
+ * Copyright (c) 2011-2012 Jia Liu <proljc@gmail.com>
+ * Zhizhou Zhang <etouzh@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cpu.h"
+#include "hw/hw.h"
+#include "qemu/timer.h"
+
+#define TIMER_FREQ (20 * 1000 * 1000) /* 20MHz */
+
+/* The time when TTCR changes */
+static uint64_t last_clk;
+static int is_counting;
+
+void cpu_openrisc_count_update(OpenRISCCPU *cpu)
+{
+ uint64_t now;
+
+ if (!is_counting) {
+ return;
+ }
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ cpu->env.ttcr += (uint32_t)muldiv64(now - last_clk, TIMER_FREQ,
+ get_ticks_per_sec());
+ last_clk = now;
+}
+
+void cpu_openrisc_timer_update(OpenRISCCPU *cpu)
+{
+ uint32_t wait;
+ uint64_t now, next;
+
+ if (!is_counting) {
+ return;
+ }
+
+ cpu_openrisc_count_update(cpu);
+ now = last_clk;
+
+ if ((cpu->env.ttmr & TTMR_TP) <= (cpu->env.ttcr & TTMR_TP)) {
+ wait = TTMR_TP - (cpu->env.ttcr & TTMR_TP) + 1;
+ wait += cpu->env.ttmr & TTMR_TP;
+ } else {
+ wait = (cpu->env.ttmr & TTMR_TP) - (cpu->env.ttcr & TTMR_TP);
+ }
+ next = now + muldiv64(wait, get_ticks_per_sec(), TIMER_FREQ);
+ timer_mod(cpu->env.timer, next);
+}
+
+void cpu_openrisc_count_start(OpenRISCCPU *cpu)
+{
+ is_counting = 1;
+ cpu_openrisc_count_update(cpu);
+}
+
+void cpu_openrisc_count_stop(OpenRISCCPU *cpu)
+{
+ timer_del(cpu->env.timer);
+ cpu_openrisc_count_update(cpu);
+ is_counting = 0;
+}
+
+static void openrisc_timer_cb(void *opaque)
+{
+ OpenRISCCPU *cpu = opaque;
+
+ if ((cpu->env.ttmr & TTMR_IE) &&
+ timer_expired(cpu->env.timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL))) {
+ CPUState *cs = CPU(cpu);
+
+ cpu->env.ttmr |= TTMR_IP;
+ cs->interrupt_request |= CPU_INTERRUPT_TIMER;
+ }
+
+ switch (cpu->env.ttmr & TTMR_M) {
+ case TIMER_NONE:
+ break;
+ case TIMER_INTR:
+ cpu->env.ttcr = 0;
+ break;
+ case TIMER_SHOT:
+ cpu_openrisc_count_stop(cpu);
+ break;
+ case TIMER_CONT:
+ break;
+ }
+
+ cpu_openrisc_timer_update(cpu);
+}
+
+void cpu_openrisc_clock_init(OpenRISCCPU *cpu)
+{
+ cpu->env.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &openrisc_timer_cb, cpu);
+ cpu->env.ttmr = 0x00000000;
+ cpu->env.ttcr = 0x00000000;
+}
diff --git a/hw/openrisc/openrisc_sim.c b/hw/openrisc/openrisc_sim.c
new file mode 100644
index 00000000..1da0657d
--- /dev/null
+++ b/hw/openrisc/openrisc_sim.c
@@ -0,0 +1,148 @@
+/*
+ * OpenRISC simulator for use as an IIS.
+ *
+ * Copyright (c) 2011-2012 Jia Liu <proljc@gmail.com>
+ * Feng Gao <gf91597@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/boards.h"
+#include "elf.h"
+#include "hw/char/serial.h"
+#include "net/net.h"
+#include "hw/loader.h"
+#include "exec/address-spaces.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+#include "sysemu/qtest.h"
+
+#define KERNEL_LOAD_ADDR 0x100
+
+static void main_cpu_reset(void *opaque)
+{
+ OpenRISCCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+static void openrisc_sim_net_init(MemoryRegion *address_space,
+ hwaddr base,
+ hwaddr descriptors,
+ qemu_irq irq, NICInfo *nd)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "open_eth");
+ qdev_set_nic_properties(dev, nd);
+ qdev_init_nofail(dev);
+
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irq);
+ memory_region_add_subregion(address_space, base,
+ sysbus_mmio_get_region(s, 0));
+ memory_region_add_subregion(address_space, descriptors,
+ sysbus_mmio_get_region(s, 1));
+}
+
+static void cpu_openrisc_load_kernel(ram_addr_t ram_size,
+ const char *kernel_filename,
+ OpenRISCCPU *cpu)
+{
+ long kernel_size;
+ uint64_t elf_entry;
+ hwaddr entry;
+
+ if (kernel_filename && !qtest_enabled()) {
+ kernel_size = load_elf(kernel_filename, NULL, NULL,
+ &elf_entry, NULL, NULL, 1, ELF_MACHINE, 1);
+ entry = elf_entry;
+ if (kernel_size < 0) {
+ kernel_size = load_uimage(kernel_filename,
+ &entry, NULL, NULL, NULL, NULL);
+ }
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename,
+ KERNEL_LOAD_ADDR,
+ ram_size - KERNEL_LOAD_ADDR);
+ entry = KERNEL_LOAD_ADDR;
+ }
+
+ if (kernel_size < 0) {
+ fprintf(stderr, "QEMU: couldn't load the kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ cpu->env.pc = entry;
+ }
+}
+
+static void openrisc_sim_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ OpenRISCCPU *cpu = NULL;
+ MemoryRegion *ram;
+ int n;
+
+ if (!cpu_model) {
+ cpu_model = "or1200";
+ }
+
+ for (n = 0; n < smp_cpus; n++) {
+ cpu = cpu_openrisc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition!\n");
+ exit(1);
+ }
+ qemu_register_reset(main_cpu_reset, cpu);
+ main_cpu_reset(cpu);
+ }
+
+ ram = g_malloc(sizeof(*ram));
+ memory_region_init_ram(ram, NULL, "openrisc.ram", ram_size, &error_abort);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(get_system_memory(), 0, ram);
+
+ cpu_openrisc_pic_init(cpu);
+ cpu_openrisc_clock_init(cpu);
+
+ serial_mm_init(get_system_memory(), 0x90000000, 0, cpu->env.irq[2],
+ 115200, serial_hds[0], DEVICE_NATIVE_ENDIAN);
+
+ if (nd_table[0].used) {
+ openrisc_sim_net_init(get_system_memory(), 0x92000000,
+ 0x92000400, cpu->env.irq[4], nd_table);
+ }
+
+ cpu_openrisc_load_kernel(ram_size, kernel_filename, cpu);
+}
+
+static QEMUMachine openrisc_sim_machine = {
+ .name = "or32-sim",
+ .desc = "or32 simulation",
+ .init = openrisc_sim_init,
+ .max_cpus = 1,
+ .is_default = 1,
+};
+
+static void openrisc_sim_machine_init(void)
+{
+ qemu_register_machine(&openrisc_sim_machine);
+}
+
+machine_init(openrisc_sim_machine_init);
diff --git a/hw/openrisc/pic_cpu.c b/hw/openrisc/pic_cpu.c
new file mode 100644
index 00000000..2af1d601
--- /dev/null
+++ b/hw/openrisc/pic_cpu.c
@@ -0,0 +1,60 @@
+/*
+ * OpenRISC Programmable Interrupt Controller support.
+ *
+ * Copyright (c) 2011-2012 Jia Liu <proljc@gmail.com>
+ * Feng Gao <gf91597@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "cpu.h"
+
+/* OpenRISC pic handler */
+static void openrisc_pic_cpu_handler(void *opaque, int irq, int level)
+{
+ OpenRISCCPU *cpu = (OpenRISCCPU *)opaque;
+ CPUState *cs = CPU(cpu);
+ uint32_t irq_bit;
+
+ if (irq > 31 || irq < 0) {
+ return;
+ }
+
+ irq_bit = 1U << irq;
+
+ if (level) {
+ cpu->env.picsr |= irq_bit;
+ } else {
+ cpu->env.picsr &= ~irq_bit;
+ }
+
+ if (cpu->env.picsr & cpu->env.picmr) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ cpu->env.picsr = 0;
+ }
+}
+
+void cpu_openrisc_pic_init(OpenRISCCPU *cpu)
+{
+ int i;
+ qemu_irq *qi;
+ qi = qemu_allocate_irqs(openrisc_pic_cpu_handler, cpu, NR_IRQS);
+
+ for (i = 0; i < NR_IRQS; i++) {
+ cpu->env.irq[i] = qi[i];
+ }
+}
diff --git a/hw/pci-bridge/Makefile.objs b/hw/pci-bridge/Makefile.objs
new file mode 100644
index 00000000..f2adfe34
--- /dev/null
+++ b/hw/pci-bridge/Makefile.objs
@@ -0,0 +1,7 @@
+common-obj-y += pci_bridge_dev.o
+common-obj-y += pci_expander_bridge.o
+common-obj-$(CONFIG_XIO3130) += xio3130_upstream.o xio3130_downstream.o
+common-obj-$(CONFIG_IOH3420) += ioh3420.o
+common-obj-$(CONFIG_I82801B11) += i82801b11.o
+# NewWorld PowerMac
+common-obj-$(CONFIG_DEC_PCI) += dec.o
diff --git a/hw/pci-bridge/dec.c b/hw/pci-bridge/dec.c
new file mode 100644
index 00000000..28d0ff9c
--- /dev/null
+++ b/hw/pci-bridge/dec.c
@@ -0,0 +1,161 @@
+/*
+ * QEMU DEC 21154 PCI bridge
+ *
+ * Copyright (c) 2006-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+
+#include "dec.h"
+#include "hw/sysbus.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_bus.h"
+
+/* debug DEC */
+//#define DEBUG_DEC
+
+#ifdef DEBUG_DEC
+#define DEC_DPRINTF(fmt, ...) \
+ do { printf("DEC: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DEC_DPRINTF(fmt, ...)
+#endif
+
+#define DEC_21154(obj) OBJECT_CHECK(DECState, (obj), TYPE_DEC_21154)
+
+typedef struct DECState {
+ PCIHostState parent_obj;
+} DECState;
+
+static int dec_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ return irq_num;
+}
+
+static int dec_pci_bridge_initfn(PCIDevice *pci_dev)
+{
+ return pci_bridge_initfn(pci_dev, TYPE_PCI_BUS);
+}
+
+static void dec_21154_pci_bridge_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = dec_pci_bridge_initfn;
+ k->exit = pci_bridge_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_DEC;
+ k->device_id = PCI_DEVICE_ID_DEC_21154;
+ k->config_write = pci_bridge_write_config;
+ k->is_bridge = 1;
+ dc->desc = "DEC 21154 PCI-PCI bridge";
+ dc->reset = pci_bridge_reset;
+ dc->vmsd = &vmstate_pci_device;
+}
+
+static const TypeInfo dec_21154_pci_bridge_info = {
+ .name = "dec-21154-p2p-bridge",
+ .parent = TYPE_PCI_BRIDGE,
+ .instance_size = sizeof(PCIBridge),
+ .class_init = dec_21154_pci_bridge_class_init,
+};
+
+PCIBus *pci_dec_21154_init(PCIBus *parent_bus, int devfn)
+{
+ PCIDevice *dev;
+ PCIBridge *br;
+
+ dev = pci_create_multifunction(parent_bus, devfn, false,
+ "dec-21154-p2p-bridge");
+ br = PCI_BRIDGE(dev);
+ pci_bridge_map_irq(br, "DEC 21154 PCI-PCI bridge", dec_map_irq);
+ qdev_init_nofail(&dev->qdev);
+ return pci_bridge_get_sec_bus(br);
+}
+
+static int pci_dec_21154_device_init(SysBusDevice *dev)
+{
+ PCIHostState *phb;
+
+ phb = PCI_HOST_BRIDGE(dev);
+
+ memory_region_init_io(&phb->conf_mem, OBJECT(dev), &pci_host_conf_le_ops,
+ dev, "pci-conf-idx", 0x1000);
+ memory_region_init_io(&phb->data_mem, OBJECT(dev), &pci_host_data_le_ops,
+ dev, "pci-data-idx", 0x1000);
+ sysbus_init_mmio(dev, &phb->conf_mem);
+ sysbus_init_mmio(dev, &phb->data_mem);
+ return 0;
+}
+
+static void dec_21154_pci_host_realize(PCIDevice *d, Error **errp)
+{
+ /* PCI2PCI bridge same values as PearPC - check this */
+}
+
+static void dec_21154_pci_host_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = dec_21154_pci_host_realize;
+ k->vendor_id = PCI_VENDOR_ID_DEC;
+ k->device_id = PCI_DEVICE_ID_DEC_21154;
+ k->revision = 0x02;
+ k->class_id = PCI_CLASS_BRIDGE_PCI;
+ k->is_bridge = 1;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo dec_21154_pci_host_info = {
+ .name = "dec-21154",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = dec_21154_pci_host_class_init,
+};
+
+static void pci_dec_21154_device_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = pci_dec_21154_device_init;
+}
+
+static const TypeInfo pci_dec_21154_device_info = {
+ .name = TYPE_DEC_21154,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(DECState),
+ .class_init = pci_dec_21154_device_class_init,
+};
+
+static void dec_register_types(void)
+{
+ type_register_static(&pci_dec_21154_device_info);
+ type_register_static(&dec_21154_pci_host_info);
+ type_register_static(&dec_21154_pci_bridge_info);
+}
+
+type_init(dec_register_types)
diff --git a/hw/pci-bridge/dec.h b/hw/pci-bridge/dec.h
new file mode 100644
index 00000000..17dc0c2b
--- /dev/null
+++ b/hw/pci-bridge/dec.h
@@ -0,0 +1,10 @@
+#ifndef DEC_PCI_H
+#define DEC_PCI_H
+
+#include "qemu-common.h"
+
+#define TYPE_DEC_21154 "dec-21154-sysbus"
+
+PCIBus *pci_dec_21154_init(PCIBus *parent_bus, int devfn);
+
+#endif
diff --git a/hw/pci-bridge/i82801b11.c b/hw/pci-bridge/i82801b11.c
new file mode 100644
index 00000000..7e79bc01
--- /dev/null
+++ b/hw/pci-bridge/i82801b11.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * 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.
+ */
+/*
+ * QEMU i82801b11 dmi-to-pci Bridge Emulation
+ *
+ * Copyright (c) 2009, 2010, 2011
+ * Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "hw/pci/pci.h"
+#include "hw/i386/ich9.h"
+
+
+/*****************************************************************************/
+/* ICH9 DMI-to-PCI bridge */
+#define I82801ba_SSVID_OFFSET 0x50
+#define I82801ba_SSVID_SVID 0
+#define I82801ba_SSVID_SSID 0
+
+typedef struct I82801b11Bridge {
+ /*< private >*/
+ PCIBridge parent_obj;
+ /*< public >*/
+} I82801b11Bridge;
+
+static int i82801b11_bridge_initfn(PCIDevice *d)
+{
+ int rc;
+
+ rc = pci_bridge_initfn(d, TYPE_PCI_BUS);
+ if (rc < 0) {
+ return rc;
+ }
+
+ rc = pci_bridge_ssvid_init(d, I82801ba_SSVID_OFFSET,
+ I82801ba_SSVID_SVID, I82801ba_SSVID_SSID);
+ if (rc < 0) {
+ goto err_bridge;
+ }
+ pci_config_set_prog_interface(d->config, PCI_CLASS_BRIDGE_PCI_INF_SUB);
+ return 0;
+
+err_bridge:
+ pci_bridge_exitfn(d);
+
+ return rc;
+}
+
+static void i82801b11_bridge_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->is_bridge = 1;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82801BA_11;
+ k->revision = ICH9_D2P_A2_REVISION;
+ k->init = i82801b11_bridge_initfn;
+ k->config_write = pci_bridge_write_config;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo i82801b11_bridge_info = {
+ .name = "i82801b11-bridge",
+ .parent = TYPE_PCI_BRIDGE,
+ .instance_size = sizeof(I82801b11Bridge),
+ .class_init = i82801b11_bridge_class_init,
+};
+
+static void d2pbr_register(void)
+{
+ type_register_static(&i82801b11_bridge_info);
+}
+
+type_init(d2pbr_register);
diff --git a/hw/pci-bridge/ioh3420.c b/hw/pci-bridge/ioh3420.c
new file mode 100644
index 00000000..cce2fdd8
--- /dev/null
+++ b/hw/pci-bridge/ioh3420.c
@@ -0,0 +1,221 @@
+/*
+ * ioh3420.c
+ * Intel X58 north bridge IOH
+ * PCI Express root port device id 3420
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/pci/pci_ids.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/pcie.h"
+#include "ioh3420.h"
+
+#define PCI_DEVICE_ID_IOH_EPORT 0x3420 /* D0:F0 express mode */
+#define PCI_DEVICE_ID_IOH_REV 0x2
+#define IOH_EP_SSVID_OFFSET 0x40
+#define IOH_EP_SSVID_SVID PCI_VENDOR_ID_INTEL
+#define IOH_EP_SSVID_SSID 0
+#define IOH_EP_MSI_OFFSET 0x60
+#define IOH_EP_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_MASKBIT
+#define IOH_EP_MSI_NR_VECTOR 2
+#define IOH_EP_EXP_OFFSET 0x90
+#define IOH_EP_AER_OFFSET 0x100
+
+/*
+ * If two MSI vector are allocated, Advanced Error Interrupt Message Number
+ * is 1. otherwise 0.
+ * 17.12.5.10 RPERRSTS, 32:27 bit Advanced Error Interrupt Message Number.
+ */
+static uint8_t ioh3420_aer_vector(const PCIDevice *d)
+{
+ switch (msi_nr_vectors_allocated(d)) {
+ case 1:
+ return 0;
+ case 2:
+ return 1;
+ case 4:
+ case 8:
+ case 16:
+ case 32:
+ default:
+ break;
+ }
+ abort();
+ return 0;
+}
+
+static void ioh3420_aer_vector_update(PCIDevice *d)
+{
+ pcie_aer_root_set_vector(d, ioh3420_aer_vector(d));
+}
+
+static void ioh3420_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ uint32_t root_cmd =
+ pci_get_long(d->config + d->exp.aer_cap + PCI_ERR_ROOT_COMMAND);
+
+ pci_bridge_write_config(d, address, val, len);
+ ioh3420_aer_vector_update(d);
+ pcie_cap_slot_write_config(d, address, val, len);
+ pcie_aer_write_config(d, address, val, len);
+ pcie_aer_root_write_config(d, address, val, len, root_cmd);
+}
+
+static void ioh3420_reset(DeviceState *qdev)
+{
+ PCIDevice *d = PCI_DEVICE(qdev);
+
+ ioh3420_aer_vector_update(d);
+ pcie_cap_root_reset(d);
+ pcie_cap_deverr_reset(d);
+ pcie_cap_slot_reset(d);
+ pcie_cap_arifwd_reset(d);
+ pcie_aer_root_reset(d);
+ pci_bridge_reset(qdev);
+ pci_bridge_disable_base_limit(d);
+}
+
+static int ioh3420_initfn(PCIDevice *d)
+{
+ PCIEPort *p = PCIE_PORT(d);
+ PCIESlot *s = PCIE_SLOT(d);
+ int rc;
+
+ rc = pci_bridge_initfn(d, TYPE_PCIE_BUS);
+ if (rc < 0) {
+ return rc;
+ }
+
+ pcie_port_init_reg(d);
+
+ rc = pci_bridge_ssvid_init(d, IOH_EP_SSVID_OFFSET,
+ IOH_EP_SSVID_SVID, IOH_EP_SSVID_SSID);
+ if (rc < 0) {
+ goto err_bridge;
+ }
+ rc = msi_init(d, IOH_EP_MSI_OFFSET, IOH_EP_MSI_NR_VECTOR,
+ IOH_EP_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT,
+ IOH_EP_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT);
+ if (rc < 0) {
+ goto err_bridge;
+ }
+ rc = pcie_cap_init(d, IOH_EP_EXP_OFFSET, PCI_EXP_TYPE_ROOT_PORT, p->port);
+ if (rc < 0) {
+ goto err_msi;
+ }
+
+ pcie_cap_arifwd_init(d);
+ pcie_cap_deverr_init(d);
+ pcie_cap_slot_init(d, s->slot);
+ pcie_chassis_create(s->chassis);
+ rc = pcie_chassis_add_slot(s);
+ if (rc < 0) {
+ goto err_pcie_cap;
+ }
+ pcie_cap_root_init(d);
+ rc = pcie_aer_init(d, IOH_EP_AER_OFFSET);
+ if (rc < 0) {
+ goto err;
+ }
+ pcie_aer_root_init(d);
+ ioh3420_aer_vector_update(d);
+ return 0;
+
+err:
+ pcie_chassis_del_slot(s);
+err_pcie_cap:
+ pcie_cap_exit(d);
+err_msi:
+ msi_uninit(d);
+err_bridge:
+ pci_bridge_exitfn(d);
+ return rc;
+}
+
+static void ioh3420_exitfn(PCIDevice *d)
+{
+ PCIESlot *s = PCIE_SLOT(d);
+
+ pcie_aer_exit(d);
+ pcie_chassis_del_slot(s);
+ pcie_cap_exit(d);
+ msi_uninit(d);
+ pci_bridge_exitfn(d);
+}
+
+static Property ioh3420_props[] = {
+ DEFINE_PROP_BIT(COMPAT_PROP_PCP, PCIDevice, cap_present,
+ QEMU_PCIE_SLTCAP_PCP_BITNR, true),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static const VMStateDescription vmstate_ioh3420 = {
+ .name = "ioh-3240-express-root-port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pcie_cap_slot_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCIE_DEVICE(parent_obj.parent_obj.parent_obj, PCIESlot),
+ VMSTATE_STRUCT(parent_obj.parent_obj.parent_obj.exp.aer_log,
+ PCIESlot, 0, vmstate_pcie_aer_log, PCIEAERLog),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ioh3420_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->is_express = 1;
+ k->is_bridge = 1;
+ k->config_write = ioh3420_write_config;
+ k->init = ioh3420_initfn;
+ k->exit = ioh3420_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_IOH_EPORT;
+ k->revision = PCI_DEVICE_ID_IOH_REV;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->desc = "Intel IOH device id 3420 PCIE Root Port";
+ dc->reset = ioh3420_reset;
+ dc->vmsd = &vmstate_ioh3420;
+ dc->props = ioh3420_props;
+}
+
+static const TypeInfo ioh3420_info = {
+ .name = "ioh3420",
+ .parent = TYPE_PCIE_SLOT,
+ .class_init = ioh3420_class_init,
+};
+
+static void ioh3420_register_types(void)
+{
+ type_register_static(&ioh3420_info);
+}
+
+type_init(ioh3420_register_types)
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tab-mode: nil
+ * End:
+ */
diff --git a/hw/pci-bridge/ioh3420.h b/hw/pci-bridge/ioh3420.h
new file mode 100644
index 00000000..ea423cb9
--- /dev/null
+++ b/hw/pci-bridge/ioh3420.h
@@ -0,0 +1,6 @@
+#ifndef QEMU_IOH3420_H
+#define QEMU_IOH3420_H
+
+#include "hw/pci/pcie_port.h"
+
+#endif /* QEMU_IOH3420_H */
diff --git a/hw/pci-bridge/pci_bridge_dev.c b/hw/pci-bridge/pci_bridge_dev.c
new file mode 100644
index 00000000..26aded9f
--- /dev/null
+++ b/hw/pci-bridge/pci_bridge_dev.c
@@ -0,0 +1,257 @@
+/*
+ * Standard PCI Bridge Device
+ *
+ * Copyright (c) 2011 Red Hat Inc. Author: Michael S. Tsirkin <mst@redhat.com>
+ *
+ * http://www.pcisig.com/specifications/conventional/pci_to_pci_bridge_architecture/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_ids.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/shpc.h"
+#include "hw/pci/slotid_cap.h"
+#include "exec/memory.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/hotplug.h"
+
+#define TYPE_PCI_BRIDGE_DEV "pci-bridge"
+#define TYPE_PCI_BRIDGE_SEAT_DEV "pci-bridge-seat"
+#define PCI_BRIDGE_DEV(obj) \
+ OBJECT_CHECK(PCIBridgeDev, (obj), TYPE_PCI_BRIDGE_DEV)
+
+struct PCIBridgeDev {
+ /*< private >*/
+ PCIBridge parent_obj;
+ /*< public >*/
+
+ MemoryRegion bar;
+ uint8_t chassis_nr;
+#define PCI_BRIDGE_DEV_F_MSI_REQ 0
+#define PCI_BRIDGE_DEV_F_SHPC_REQ 1
+ uint32_t flags;
+};
+typedef struct PCIBridgeDev PCIBridgeDev;
+
+static int pci_bridge_dev_initfn(PCIDevice *dev)
+{
+ PCIBridge *br = PCI_BRIDGE(dev);
+ PCIBridgeDev *bridge_dev = PCI_BRIDGE_DEV(dev);
+ int err;
+
+ err = pci_bridge_initfn(dev, TYPE_PCI_BUS);
+ if (err) {
+ goto bridge_error;
+ }
+ if (bridge_dev->flags & (1 << PCI_BRIDGE_DEV_F_SHPC_REQ)) {
+ dev->config[PCI_INTERRUPT_PIN] = 0x1;
+ memory_region_init(&bridge_dev->bar, OBJECT(dev), "shpc-bar",
+ shpc_bar_size(dev));
+ err = shpc_init(dev, &br->sec_bus, &bridge_dev->bar, 0);
+ if (err) {
+ goto shpc_error;
+ }
+ } else {
+ /* MSI is not applicable without SHPC */
+ bridge_dev->flags &= ~(1 << PCI_BRIDGE_DEV_F_MSI_REQ);
+ }
+ err = slotid_cap_init(dev, 0, bridge_dev->chassis_nr, 0);
+ if (err) {
+ goto slotid_error;
+ }
+ if ((bridge_dev->flags & (1 << PCI_BRIDGE_DEV_F_MSI_REQ)) &&
+ msi_supported) {
+ err = msi_init(dev, 0, 1, true, true);
+ if (err < 0) {
+ goto msi_error;
+ }
+ }
+ if (shpc_present(dev)) {
+ /* TODO: spec recommends using 64 bit prefetcheable BAR.
+ * Check whether that works well. */
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_TYPE_64, &bridge_dev->bar);
+ }
+ return 0;
+msi_error:
+ slotid_cap_cleanup(dev);
+slotid_error:
+ if (shpc_present(dev)) {
+ shpc_cleanup(dev, &bridge_dev->bar);
+ }
+shpc_error:
+ pci_bridge_exitfn(dev);
+bridge_error:
+ return err;
+}
+
+static void pci_bridge_dev_exitfn(PCIDevice *dev)
+{
+ PCIBridgeDev *bridge_dev = PCI_BRIDGE_DEV(dev);
+ if (msi_present(dev)) {
+ msi_uninit(dev);
+ }
+ slotid_cap_cleanup(dev);
+ if (shpc_present(dev)) {
+ shpc_cleanup(dev, &bridge_dev->bar);
+ }
+ pci_bridge_exitfn(dev);
+}
+
+static void pci_bridge_dev_instance_finalize(Object *obj)
+{
+ /* this function is idempotent and handles (PCIDevice.shpc == NULL) */
+ shpc_free(PCI_DEVICE(obj));
+}
+
+static void pci_bridge_dev_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ pci_bridge_write_config(d, address, val, len);
+ if (msi_present(d)) {
+ msi_write_config(d, address, val, len);
+ }
+ if (shpc_present(d)) {
+ shpc_cap_write_config(d, address, val, len);
+ }
+}
+
+static void qdev_pci_bridge_dev_reset(DeviceState *qdev)
+{
+ PCIDevice *dev = PCI_DEVICE(qdev);
+
+ pci_bridge_reset(qdev);
+ if (shpc_present(dev)) {
+ shpc_reset(dev);
+ }
+}
+
+static Property pci_bridge_dev_properties[] = {
+ /* Note: 0 is not a legal chassis number. */
+ DEFINE_PROP_UINT8(PCI_BRIDGE_DEV_PROP_CHASSIS_NR, PCIBridgeDev, chassis_nr,
+ 0),
+ DEFINE_PROP_BIT(PCI_BRIDGE_DEV_PROP_MSI, PCIBridgeDev, flags,
+ PCI_BRIDGE_DEV_F_MSI_REQ, true),
+ DEFINE_PROP_BIT(PCI_BRIDGE_DEV_PROP_SHPC, PCIBridgeDev, flags,
+ PCI_BRIDGE_DEV_F_SHPC_REQ, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static bool pci_device_shpc_present(void *opaque, int version_id)
+{
+ PCIDevice *dev = opaque;
+
+ return shpc_present(dev);
+}
+
+static const VMStateDescription pci_bridge_dev_vmstate = {
+ .name = "pci_bridge",
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PCIBridge),
+ SHPC_VMSTATE(shpc, PCIDevice, pci_device_shpc_present),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pci_bridge_dev_hotplug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
+
+ if (!shpc_present(pci_hotplug_dev)) {
+ error_setg(errp, "standard hotplug controller has been disabled for "
+ "this %s", TYPE_PCI_BRIDGE_DEV);
+ return;
+ }
+ shpc_device_hotplug_cb(hotplug_dev, dev, errp);
+}
+
+static void pci_bridge_dev_hot_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev,
+ Error **errp)
+{
+ PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
+
+ if (!shpc_present(pci_hotplug_dev)) {
+ error_setg(errp, "standard hotplug controller has been disabled for "
+ "this %s", TYPE_PCI_BRIDGE_DEV);
+ return;
+ }
+ shpc_device_hot_unplug_request_cb(hotplug_dev, dev, errp);
+}
+
+static void pci_bridge_dev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ k->init = pci_bridge_dev_initfn;
+ k->exit = pci_bridge_dev_exitfn;
+ k->config_write = pci_bridge_dev_write_config;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT;
+ k->device_id = PCI_DEVICE_ID_REDHAT_BRIDGE;
+ k->class_id = PCI_CLASS_BRIDGE_PCI;
+ k->is_bridge = 1,
+ dc->desc = "Standard PCI Bridge";
+ dc->reset = qdev_pci_bridge_dev_reset;
+ dc->props = pci_bridge_dev_properties;
+ dc->vmsd = &pci_bridge_dev_vmstate;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ hc->plug = pci_bridge_dev_hotplug_cb;
+ hc->unplug_request = pci_bridge_dev_hot_unplug_request_cb;
+}
+
+static const TypeInfo pci_bridge_dev_info = {
+ .name = TYPE_PCI_BRIDGE_DEV,
+ .parent = TYPE_PCI_BRIDGE,
+ .instance_size = sizeof(PCIBridgeDev),
+ .class_init = pci_bridge_dev_class_init,
+ .instance_finalize = pci_bridge_dev_instance_finalize,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+/*
+ * Multiseat bridge. Same as the standard pci bridge, only with a
+ * different pci id, so we can match it easily in the guest for
+ * automagic multiseat configuration. See docs/multiseat.txt for more.
+ */
+static void pci_bridge_dev_seat_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->device_id = PCI_DEVICE_ID_REDHAT_BRIDGE_SEAT;
+ dc->desc = "Standard PCI Bridge (multiseat)";
+}
+
+static const TypeInfo pci_bridge_dev_seat_info = {
+ .name = TYPE_PCI_BRIDGE_SEAT_DEV,
+ .parent = TYPE_PCI_BRIDGE_DEV,
+ .instance_size = sizeof(PCIBridgeDev),
+ .class_init = pci_bridge_dev_seat_class_init,
+};
+
+static void pci_bridge_dev_register(void)
+{
+ type_register_static(&pci_bridge_dev_info);
+ type_register_static(&pci_bridge_dev_seat_info);
+}
+
+type_init(pci_bridge_dev_register);
diff --git a/hw/pci-bridge/pci_expander_bridge.c b/hw/pci-bridge/pci_expander_bridge.c
new file mode 100644
index 00000000..57f8a376
--- /dev/null
+++ b/hw/pci-bridge/pci_expander_bridge.c
@@ -0,0 +1,286 @@
+/*
+ * PCI Expander Bridge Device Emulation
+ *
+ * Copyright (C) 2015 Red Hat Inc
+ *
+ * Authors:
+ * Marcel Apfelbaum <marcel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pci_host.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/i386/pc.h"
+#include "qemu/range.h"
+#include "qemu/error-report.h"
+#include "sysemu/numa.h"
+
+#define TYPE_PXB_BUS "pxb-bus"
+#define PXB_BUS(obj) OBJECT_CHECK(PXBBus, (obj), TYPE_PXB_BUS)
+
+typedef struct PXBBus {
+ /*< private >*/
+ PCIBus parent_obj;
+ /*< public >*/
+
+ char bus_path[8];
+} PXBBus;
+
+#define TYPE_PXB_DEVICE "pxb"
+#define PXB_DEV(obj) OBJECT_CHECK(PXBDev, (obj), TYPE_PXB_DEVICE)
+
+typedef struct PXBDev {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ uint8_t bus_nr;
+ uint16_t numa_node;
+} PXBDev;
+
+static GList *pxb_dev_list;
+
+#define TYPE_PXB_HOST "pxb-host"
+
+static int pxb_bus_num(PCIBus *bus)
+{
+ PXBDev *pxb = PXB_DEV(bus->parent_dev);
+
+ return pxb->bus_nr;
+}
+
+static bool pxb_is_root(PCIBus *bus)
+{
+ return true; /* by definition */
+}
+
+static uint16_t pxb_bus_numa_node(PCIBus *bus)
+{
+ PXBDev *pxb = PXB_DEV(bus->parent_dev);
+
+ return pxb->numa_node;
+}
+
+static void pxb_bus_class_init(ObjectClass *class, void *data)
+{
+ PCIBusClass *pbc = PCI_BUS_CLASS(class);
+
+ pbc->bus_num = pxb_bus_num;
+ pbc->is_root = pxb_is_root;
+ pbc->numa_node = pxb_bus_numa_node;
+}
+
+static const TypeInfo pxb_bus_info = {
+ .name = TYPE_PXB_BUS,
+ .parent = TYPE_PCI_BUS,
+ .instance_size = sizeof(PXBBus),
+ .class_init = pxb_bus_class_init,
+};
+
+static const char *pxb_host_root_bus_path(PCIHostState *host_bridge,
+ PCIBus *rootbus)
+{
+ PXBBus *bus = PXB_BUS(rootbus);
+
+ snprintf(bus->bus_path, 8, "0000:%02x", pxb_bus_num(rootbus));
+ return bus->bus_path;
+}
+
+static char *pxb_host_ofw_unit_address(const SysBusDevice *dev)
+{
+ const PCIHostState *pxb_host;
+ const PCIBus *pxb_bus;
+ const PXBDev *pxb_dev;
+ int position;
+ const DeviceState *pxb_dev_base;
+ const PCIHostState *main_host;
+ const SysBusDevice *main_host_sbd;
+
+ pxb_host = PCI_HOST_BRIDGE(dev);
+ pxb_bus = pxb_host->bus;
+ pxb_dev = PXB_DEV(pxb_bus->parent_dev);
+ position = g_list_index(pxb_dev_list, pxb_dev);
+ assert(position >= 0);
+
+ pxb_dev_base = DEVICE(pxb_dev);
+ main_host = PCI_HOST_BRIDGE(pxb_dev_base->parent_bus->parent);
+ main_host_sbd = SYS_BUS_DEVICE(main_host);
+
+ if (main_host_sbd->num_mmio > 0) {
+ return g_strdup_printf(TARGET_FMT_plx ",%x",
+ main_host_sbd->mmio[0].addr, position + 1);
+ }
+ if (main_host_sbd->num_pio > 0) {
+ return g_strdup_printf("i%04x,%x",
+ main_host_sbd->pio[0], position + 1);
+ }
+ return NULL;
+}
+
+static void pxb_host_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(class);
+ PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(class);
+
+ dc->fw_name = "pci";
+ sbc->explicit_ofw_unit_address = pxb_host_ofw_unit_address;
+ hc->root_bus_path = pxb_host_root_bus_path;
+}
+
+static const TypeInfo pxb_host_info = {
+ .name = TYPE_PXB_HOST,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .class_init = pxb_host_class_init,
+};
+
+/*
+ * Registers the PXB bus as a child of the i440fx root bus.
+ *
+ * Returns 0 on successs, -1 if i440fx host was not
+ * found or the bus number is already in use.
+ */
+static int pxb_register_bus(PCIDevice *dev, PCIBus *pxb_bus)
+{
+ PCIBus *bus = dev->bus;
+ int pxb_bus_num = pci_bus_num(pxb_bus);
+
+ if (bus->parent_dev) {
+ error_report("PXB devices can be attached only to root bus.");
+ return -1;
+ }
+
+ QLIST_FOREACH(bus, &bus->child, sibling) {
+ if (pci_bus_num(bus) == pxb_bus_num) {
+ error_report("Bus %d is already in use.", pxb_bus_num);
+ return -1;
+ }
+ }
+ QLIST_INSERT_HEAD(&dev->bus->child, pxb_bus, sibling);
+
+ return 0;
+}
+
+static int pxb_map_irq_fn(PCIDevice *pci_dev, int pin)
+{
+ PCIDevice *pxb = pci_dev->bus->parent_dev;
+
+ /*
+ * The bios does not index the pxb slot number when
+ * it computes the IRQ because it resides on bus 0
+ * and not on the current bus.
+ * However QEMU routes the irq through bus 0 and adds
+ * the pxb slot to the IRQ computation of the PXB
+ * device.
+ *
+ * Synchronize between bios and QEMU by canceling
+ * pxb's effect.
+ */
+ return pin - PCI_SLOT(pxb->devfn);
+}
+
+static gint pxb_compare(gconstpointer a, gconstpointer b)
+{
+ const PXBDev *pxb_a = a, *pxb_b = b;
+
+ return pxb_a->bus_nr < pxb_b->bus_nr ? -1 :
+ pxb_a->bus_nr > pxb_b->bus_nr ? 1 :
+ 0;
+}
+
+static int pxb_dev_initfn(PCIDevice *dev)
+{
+ PXBDev *pxb = PXB_DEV(dev);
+ DeviceState *ds, *bds;
+ PCIBus *bus;
+ const char *dev_name = NULL;
+
+ if (pxb->numa_node != NUMA_NODE_UNASSIGNED &&
+ pxb->numa_node >= nb_numa_nodes) {
+ error_report("Illegal numa node %d.", pxb->numa_node);
+ return -EINVAL;
+ }
+
+ if (dev->qdev.id && *dev->qdev.id) {
+ dev_name = dev->qdev.id;
+ }
+
+ ds = qdev_create(NULL, TYPE_PXB_HOST);
+ bus = pci_bus_new(ds, "pxb-internal", NULL, NULL, 0, TYPE_PXB_BUS);
+
+ bus->parent_dev = dev;
+ bus->address_space_mem = dev->bus->address_space_mem;
+ bus->address_space_io = dev->bus->address_space_io;
+ bus->map_irq = pxb_map_irq_fn;
+
+ bds = qdev_create(BUS(bus), "pci-bridge");
+ bds->id = dev_name;
+ qdev_prop_set_uint8(bds, PCI_BRIDGE_DEV_PROP_CHASSIS_NR, pxb->bus_nr);
+ qdev_prop_set_bit(bds, PCI_BRIDGE_DEV_PROP_SHPC, false);
+
+ PCI_HOST_BRIDGE(ds)->bus = bus;
+
+ if (pxb_register_bus(dev, bus)) {
+ return -EINVAL;
+ }
+
+ qdev_init_nofail(ds);
+ qdev_init_nofail(bds);
+
+ pci_word_test_and_set_mask(dev->config + PCI_STATUS,
+ PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK);
+ pci_config_set_class(dev->config, PCI_CLASS_BRIDGE_HOST);
+
+ pxb_dev_list = g_list_insert_sorted(pxb_dev_list, pxb, pxb_compare);
+ return 0;
+}
+
+static void pxb_dev_exitfn(PCIDevice *pci_dev)
+{
+ PXBDev *pxb = PXB_DEV(pci_dev);
+
+ pxb_dev_list = g_list_remove(pxb_dev_list, pxb);
+}
+
+static Property pxb_dev_properties[] = {
+ /* Note: 0 is not a legal a PXB bus number. */
+ DEFINE_PROP_UINT8("bus_nr", PXBDev, bus_nr, 0),
+ DEFINE_PROP_UINT16("numa_node", PXBDev, numa_node, NUMA_NODE_UNASSIGNED),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxb_dev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = pxb_dev_initfn;
+ k->exit = pxb_dev_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT;
+ k->device_id = PCI_DEVICE_ID_REDHAT_PXB;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+
+ dc->desc = "PCI Expander Bridge";
+ dc->props = pxb_dev_properties;
+}
+
+static const TypeInfo pxb_dev_info = {
+ .name = TYPE_PXB_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PXBDev),
+ .class_init = pxb_dev_class_init,
+};
+
+static void pxb_register_types(void)
+{
+ type_register_static(&pxb_bus_info);
+ type_register_static(&pxb_host_info);
+ type_register_static(&pxb_dev_info);
+}
+
+type_init(pxb_register_types)
diff --git a/hw/pci-bridge/xio3130_downstream.c b/hw/pci-bridge/xio3130_downstream.c
new file mode 100644
index 00000000..b3a64792
--- /dev/null
+++ b/hw/pci-bridge/xio3130_downstream.c
@@ -0,0 +1,209 @@
+/*
+ * x3130_downstream.c
+ * TI X3130 pci express downstream port switch
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/pci/pci_ids.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/pcie.h"
+#include "xio3130_downstream.h"
+
+#define PCI_DEVICE_ID_TI_XIO3130D 0x8233 /* downstream port */
+#define XIO3130_REVISION 0x1
+#define XIO3130_MSI_OFFSET 0x70
+#define XIO3130_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_64BIT
+#define XIO3130_MSI_NR_VECTOR 1
+#define XIO3130_SSVID_OFFSET 0x80
+#define XIO3130_SSVID_SVID 0
+#define XIO3130_SSVID_SSID 0
+#define XIO3130_EXP_OFFSET 0x90
+#define XIO3130_AER_OFFSET 0x100
+
+static void xio3130_downstream_write_config(PCIDevice *d, uint32_t address,
+ uint32_t val, int len)
+{
+ pci_bridge_write_config(d, address, val, len);
+ pcie_cap_flr_write_config(d, address, val, len);
+ pcie_cap_slot_write_config(d, address, val, len);
+ pcie_aer_write_config(d, address, val, len);
+}
+
+static void xio3130_downstream_reset(DeviceState *qdev)
+{
+ PCIDevice *d = PCI_DEVICE(qdev);
+
+ pcie_cap_deverr_reset(d);
+ pcie_cap_slot_reset(d);
+ pcie_cap_arifwd_reset(d);
+ pci_bridge_reset(qdev);
+}
+
+static int xio3130_downstream_initfn(PCIDevice *d)
+{
+ PCIEPort *p = PCIE_PORT(d);
+ PCIESlot *s = PCIE_SLOT(d);
+ int rc;
+
+ rc = pci_bridge_initfn(d, TYPE_PCIE_BUS);
+ if (rc < 0) {
+ return rc;
+ }
+
+ pcie_port_init_reg(d);
+
+ rc = msi_init(d, XIO3130_MSI_OFFSET, XIO3130_MSI_NR_VECTOR,
+ XIO3130_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT,
+ XIO3130_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT);
+ if (rc < 0) {
+ goto err_bridge;
+ }
+ rc = pci_bridge_ssvid_init(d, XIO3130_SSVID_OFFSET,
+ XIO3130_SSVID_SVID, XIO3130_SSVID_SSID);
+ if (rc < 0) {
+ goto err_bridge;
+ }
+ rc = pcie_cap_init(d, XIO3130_EXP_OFFSET, PCI_EXP_TYPE_DOWNSTREAM,
+ p->port);
+ if (rc < 0) {
+ goto err_msi;
+ }
+ pcie_cap_flr_init(d);
+ pcie_cap_deverr_init(d);
+ pcie_cap_slot_init(d, s->slot);
+ pcie_chassis_create(s->chassis);
+ rc = pcie_chassis_add_slot(s);
+ if (rc < 0) {
+ goto err_pcie_cap;
+ }
+ pcie_cap_arifwd_init(d);
+ rc = pcie_aer_init(d, XIO3130_AER_OFFSET);
+ if (rc < 0) {
+ goto err;
+ }
+
+ return 0;
+
+err:
+ pcie_chassis_del_slot(s);
+err_pcie_cap:
+ pcie_cap_exit(d);
+err_msi:
+ msi_uninit(d);
+err_bridge:
+ pci_bridge_exitfn(d);
+ return rc;
+}
+
+static void xio3130_downstream_exitfn(PCIDevice *d)
+{
+ PCIESlot *s = PCIE_SLOT(d);
+
+ pcie_aer_exit(d);
+ pcie_chassis_del_slot(s);
+ pcie_cap_exit(d);
+ msi_uninit(d);
+ pci_bridge_exitfn(d);
+}
+
+PCIESlot *xio3130_downstream_init(PCIBus *bus, int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port, uint8_t chassis,
+ uint16_t slot)
+{
+ PCIDevice *d;
+ PCIBridge *br;
+ DeviceState *qdev;
+
+ d = pci_create_multifunction(bus, devfn, multifunction,
+ "xio3130-downstream");
+ if (!d) {
+ return NULL;
+ }
+ br = PCI_BRIDGE(d);
+
+ qdev = DEVICE(d);
+ pci_bridge_map_irq(br, bus_name, map_irq);
+ qdev_prop_set_uint8(qdev, "port", port);
+ qdev_prop_set_uint8(qdev, "chassis", chassis);
+ qdev_prop_set_uint16(qdev, "slot", slot);
+ qdev_init_nofail(qdev);
+
+ return PCIE_SLOT(d);
+}
+
+static Property xio3130_downstream_props[] = {
+ DEFINE_PROP_BIT(COMPAT_PROP_PCP, PCIDevice, cap_present,
+ QEMU_PCIE_SLTCAP_PCP_BITNR, true),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static const VMStateDescription vmstate_xio3130_downstream = {
+ .name = "xio3130-express-downstream-port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pcie_cap_slot_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCIE_DEVICE(parent_obj.parent_obj.parent_obj, PCIESlot),
+ VMSTATE_STRUCT(parent_obj.parent_obj.parent_obj.exp.aer_log,
+ PCIESlot, 0, vmstate_pcie_aer_log, PCIEAERLog),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void xio3130_downstream_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->is_express = 1;
+ k->is_bridge = 1;
+ k->config_write = xio3130_downstream_write_config;
+ k->init = xio3130_downstream_initfn;
+ k->exit = xio3130_downstream_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_TI;
+ k->device_id = PCI_DEVICE_ID_TI_XIO3130D;
+ k->revision = XIO3130_REVISION;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->desc = "TI X3130 Downstream Port of PCI Express Switch";
+ dc->reset = xio3130_downstream_reset;
+ dc->vmsd = &vmstate_xio3130_downstream;
+ dc->props = xio3130_downstream_props;
+}
+
+static const TypeInfo xio3130_downstream_info = {
+ .name = "xio3130-downstream",
+ .parent = TYPE_PCIE_SLOT,
+ .class_init = xio3130_downstream_class_init,
+};
+
+static void xio3130_downstream_register_types(void)
+{
+ type_register_static(&xio3130_downstream_info);
+}
+
+type_init(xio3130_downstream_register_types)
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tab-mode: nil
+ * End:
+ */
diff --git a/hw/pci-bridge/xio3130_downstream.h b/hw/pci-bridge/xio3130_downstream.h
new file mode 100644
index 00000000..8426d9ff
--- /dev/null
+++ b/hw/pci-bridge/xio3130_downstream.h
@@ -0,0 +1,11 @@
+#ifndef QEMU_XIO3130_DOWNSTREAM_H
+#define QEMU_XIO3130_DOWNSTREAM_H
+
+#include "hw/pci/pcie_port.h"
+
+PCIESlot *xio3130_downstream_init(PCIBus *bus, int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port, uint8_t chassis,
+ uint16_t slot);
+
+#endif /* QEMU_XIO3130_DOWNSTREAM_H */
diff --git a/hw/pci-bridge/xio3130_upstream.c b/hw/pci-bridge/xio3130_upstream.c
new file mode 100644
index 00000000..eada5828
--- /dev/null
+++ b/hw/pci-bridge/xio3130_upstream.c
@@ -0,0 +1,182 @@
+/*
+ * xio3130_upstream.c
+ * TI X3130 pci express upstream port switch
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/pci/pci_ids.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/pcie.h"
+#include "xio3130_upstream.h"
+
+#define PCI_DEVICE_ID_TI_XIO3130U 0x8232 /* upstream port */
+#define XIO3130_REVISION 0x2
+#define XIO3130_MSI_OFFSET 0x70
+#define XIO3130_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_64BIT
+#define XIO3130_MSI_NR_VECTOR 1
+#define XIO3130_SSVID_OFFSET 0x80
+#define XIO3130_SSVID_SVID 0
+#define XIO3130_SSVID_SSID 0
+#define XIO3130_EXP_OFFSET 0x90
+#define XIO3130_AER_OFFSET 0x100
+
+static void xio3130_upstream_write_config(PCIDevice *d, uint32_t address,
+ uint32_t val, int len)
+{
+ pci_bridge_write_config(d, address, val, len);
+ pcie_cap_flr_write_config(d, address, val, len);
+ pcie_aer_write_config(d, address, val, len);
+}
+
+static void xio3130_upstream_reset(DeviceState *qdev)
+{
+ PCIDevice *d = PCI_DEVICE(qdev);
+
+ pci_bridge_reset(qdev);
+ pcie_cap_deverr_reset(d);
+}
+
+static int xio3130_upstream_initfn(PCIDevice *d)
+{
+ PCIEPort *p = PCIE_PORT(d);
+ int rc;
+
+ rc = pci_bridge_initfn(d, TYPE_PCIE_BUS);
+ if (rc < 0) {
+ return rc;
+ }
+
+ pcie_port_init_reg(d);
+
+ rc = msi_init(d, XIO3130_MSI_OFFSET, XIO3130_MSI_NR_VECTOR,
+ XIO3130_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT,
+ XIO3130_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT);
+ if (rc < 0) {
+ goto err_bridge;
+ }
+ rc = pci_bridge_ssvid_init(d, XIO3130_SSVID_OFFSET,
+ XIO3130_SSVID_SVID, XIO3130_SSVID_SSID);
+ if (rc < 0) {
+ goto err_bridge;
+ }
+ rc = pcie_cap_init(d, XIO3130_EXP_OFFSET, PCI_EXP_TYPE_UPSTREAM,
+ p->port);
+ if (rc < 0) {
+ goto err_msi;
+ }
+ pcie_cap_flr_init(d);
+ pcie_cap_deverr_init(d);
+ rc = pcie_aer_init(d, XIO3130_AER_OFFSET);
+ if (rc < 0) {
+ goto err;
+ }
+
+ return 0;
+
+err:
+ pcie_cap_exit(d);
+err_msi:
+ msi_uninit(d);
+err_bridge:
+ pci_bridge_exitfn(d);
+ return rc;
+}
+
+static void xio3130_upstream_exitfn(PCIDevice *d)
+{
+ pcie_aer_exit(d);
+ pcie_cap_exit(d);
+ msi_uninit(d);
+ pci_bridge_exitfn(d);
+}
+
+PCIEPort *xio3130_upstream_init(PCIBus *bus, int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port)
+{
+ PCIDevice *d;
+ PCIBridge *br;
+ DeviceState *qdev;
+
+ d = pci_create_multifunction(bus, devfn, multifunction, "x3130-upstream");
+ if (!d) {
+ return NULL;
+ }
+ br = PCI_BRIDGE(d);
+
+ qdev = DEVICE(d);
+ pci_bridge_map_irq(br, bus_name, map_irq);
+ qdev_prop_set_uint8(qdev, "port", port);
+ qdev_init_nofail(qdev);
+
+ return PCIE_PORT(d);
+}
+
+static const VMStateDescription vmstate_xio3130_upstream = {
+ .name = "xio3130-express-upstream-port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCIE_DEVICE(parent_obj.parent_obj, PCIEPort),
+ VMSTATE_STRUCT(parent_obj.parent_obj.exp.aer_log, PCIEPort, 0,
+ vmstate_pcie_aer_log, PCIEAERLog),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void xio3130_upstream_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->is_express = 1;
+ k->is_bridge = 1;
+ k->config_write = xio3130_upstream_write_config;
+ k->init = xio3130_upstream_initfn;
+ k->exit = xio3130_upstream_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_TI;
+ k->device_id = PCI_DEVICE_ID_TI_XIO3130U;
+ k->revision = XIO3130_REVISION;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->desc = "TI X3130 Upstream Port of PCI Express Switch";
+ dc->reset = xio3130_upstream_reset;
+ dc->vmsd = &vmstate_xio3130_upstream;
+}
+
+static const TypeInfo xio3130_upstream_info = {
+ .name = "x3130-upstream",
+ .parent = TYPE_PCIE_PORT,
+ .class_init = xio3130_upstream_class_init,
+};
+
+static void xio3130_upstream_register_types(void)
+{
+ type_register_static(&xio3130_upstream_info);
+}
+
+type_init(xio3130_upstream_register_types)
+
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tab-mode: nil
+ * End:
+ */
diff --git a/hw/pci-bridge/xio3130_upstream.h b/hw/pci-bridge/xio3130_upstream.h
new file mode 100644
index 00000000..08c1d5f7
--- /dev/null
+++ b/hw/pci-bridge/xio3130_upstream.h
@@ -0,0 +1,10 @@
+#ifndef QEMU_XIO3130_UPSTREAM_H
+#define QEMU_XIO3130_UPSTREAM_H
+
+#include "hw/pci/pcie_port.h"
+
+PCIEPort *xio3130_upstream_init(PCIBus *bus, int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port);
+
+#endif /* QEMU_XIO3130_H */
diff --git a/hw/pci-host/Makefile.objs b/hw/pci-host/Makefile.objs
new file mode 100644
index 00000000..45f1f0eb
--- /dev/null
+++ b/hw/pci-host/Makefile.objs
@@ -0,0 +1,18 @@
+common-obj-y += pam.o
+
+# PPC devices
+common-obj-$(CONFIG_PREP_PCI) += prep.o
+common-obj-$(CONFIG_GRACKLE_PCI) += grackle.o
+# NewWorld PowerMac
+common-obj-$(CONFIG_UNIN_PCI) += uninorth.o
+# PowerPC E500 boards
+common-obj-$(CONFIG_PPCE500_PCI) += ppce500.o
+
+# ARM devices
+common-obj-$(CONFIG_VERSATILE_PCI) += versatile.o
+
+common-obj-$(CONFIG_PCI_APB) += apb.o
+common-obj-$(CONFIG_FULONG) += bonito.o
+common-obj-$(CONFIG_PCI_PIIX) += piix.o
+common-obj-$(CONFIG_PCI_Q35) += q35.o
+common-obj-$(CONFIG_PCI_GENERIC) += gpex.o
diff --git a/hw/pci-host/apb.c b/hw/pci-host/apb.c
new file mode 100644
index 00000000..599768e2
--- /dev/null
+++ b/hw/pci-host/apb.c
@@ -0,0 +1,875 @@
+/*
+ * QEMU Ultrasparc APB PCI host
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2012,2013 Artyom Tarasenko
+ *
+ * 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.
+ */
+
+/* XXX This file and most of its contents are somewhat misnamed. The
+ Ultrasparc PCI host is called the PCI Bus Module (PBM). The APB is
+ the secondary PCI bridge. */
+
+#include "hw/sysbus.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci-host/apb.h"
+#include "sysemu/sysemu.h"
+#include "exec/address-spaces.h"
+
+/* debug APB */
+//#define DEBUG_APB
+
+#ifdef DEBUG_APB
+#define APB_DPRINTF(fmt, ...) \
+do { printf("APB: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define APB_DPRINTF(fmt, ...)
+#endif
+
+/* debug IOMMU */
+//#define DEBUG_IOMMU
+
+#ifdef DEBUG_IOMMU
+#define IOMMU_DPRINTF(fmt, ...) \
+do { printf("IOMMU: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define IOMMU_DPRINTF(fmt, ...)
+#endif
+
+/*
+ * Chipset docs:
+ * PBM: "UltraSPARC IIi User's Manual",
+ * http://www.sun.com/processors/manuals/805-0087.pdf
+ *
+ * APB: "Advanced PCI Bridge (APB) User's Manual",
+ * http://www.sun.com/processors/manuals/805-1251.pdf
+ */
+
+#define PBM_PCI_IMR_MASK 0x7fffffff
+#define PBM_PCI_IMR_ENABLED 0x80000000
+
+#define POR (1U << 31)
+#define SOFT_POR (1U << 30)
+#define SOFT_XIR (1U << 29)
+#define BTN_POR (1U << 28)
+#define BTN_XIR (1U << 27)
+#define RESET_MASK 0xf8000000
+#define RESET_WCMASK 0x98000000
+#define RESET_WMASK 0x60000000
+
+#define MAX_IVEC 0x40
+#define NO_IRQ_REQUEST (MAX_IVEC + 1)
+
+#define IOMMU_PAGE_SIZE_8K (1ULL << 13)
+#define IOMMU_PAGE_MASK_8K (~(IOMMU_PAGE_SIZE_8K - 1))
+#define IOMMU_PAGE_SIZE_64K (1ULL << 16)
+#define IOMMU_PAGE_MASK_64K (~(IOMMU_PAGE_SIZE_64K - 1))
+
+#define IOMMU_NREGS 3
+
+#define IOMMU_CTRL 0x0
+#define IOMMU_CTRL_TBW_SIZE (1ULL << 2)
+#define IOMMU_CTRL_MMU_EN (1ULL)
+
+#define IOMMU_CTRL_TSB_SHIFT 16
+
+#define IOMMU_BASE 0x8
+#define IOMMU_FLUSH 0x10
+
+#define IOMMU_TTE_DATA_V (1ULL << 63)
+#define IOMMU_TTE_DATA_SIZE (1ULL << 61)
+#define IOMMU_TTE_DATA_W (1ULL << 1)
+
+#define IOMMU_TTE_PHYS_MASK_8K 0x1ffffffe000ULL
+#define IOMMU_TTE_PHYS_MASK_64K 0x1ffffff8000ULL
+
+#define IOMMU_TSB_8K_OFFSET_MASK_8M 0x00000000007fe000ULL
+#define IOMMU_TSB_8K_OFFSET_MASK_16M 0x0000000000ffe000ULL
+#define IOMMU_TSB_8K_OFFSET_MASK_32M 0x0000000001ffe000ULL
+#define IOMMU_TSB_8K_OFFSET_MASK_64M 0x0000000003ffe000ULL
+#define IOMMU_TSB_8K_OFFSET_MASK_128M 0x0000000007ffe000ULL
+#define IOMMU_TSB_8K_OFFSET_MASK_256M 0x000000000fffe000ULL
+#define IOMMU_TSB_8K_OFFSET_MASK_512M 0x000000001fffe000ULL
+#define IOMMU_TSB_8K_OFFSET_MASK_1G 0x000000003fffe000ULL
+
+#define IOMMU_TSB_64K_OFFSET_MASK_64M 0x0000000003ff0000ULL
+#define IOMMU_TSB_64K_OFFSET_MASK_128M 0x0000000007ff0000ULL
+#define IOMMU_TSB_64K_OFFSET_MASK_256M 0x000000000fff0000ULL
+#define IOMMU_TSB_64K_OFFSET_MASK_512M 0x000000001fff0000ULL
+#define IOMMU_TSB_64K_OFFSET_MASK_1G 0x000000003fff0000ULL
+#define IOMMU_TSB_64K_OFFSET_MASK_2G 0x000000007fff0000ULL
+
+typedef struct IOMMUState {
+ AddressSpace iommu_as;
+ MemoryRegion iommu;
+
+ uint64_t regs[IOMMU_NREGS];
+} IOMMUState;
+
+#define TYPE_APB "pbm"
+
+#define APB_DEVICE(obj) \
+ OBJECT_CHECK(APBState, (obj), TYPE_APB)
+
+typedef struct APBState {
+ PCIHostState parent_obj;
+
+ MemoryRegion apb_config;
+ MemoryRegion pci_config;
+ MemoryRegion pci_mmio;
+ MemoryRegion pci_ioport;
+ uint64_t pci_irq_in;
+ IOMMUState iommu;
+ uint32_t pci_control[16];
+ uint32_t pci_irq_map[8];
+ uint32_t pci_err_irq_map[4];
+ uint32_t obio_irq_map[32];
+ qemu_irq *pbm_irqs;
+ qemu_irq *ivec_irqs;
+ unsigned int irq_request;
+ uint32_t reset_control;
+ unsigned int nr_resets;
+} APBState;
+
+static inline void pbm_set_request(APBState *s, unsigned int irq_num)
+{
+ APB_DPRINTF("%s: request irq %d\n", __func__, irq_num);
+
+ s->irq_request = irq_num;
+ qemu_set_irq(s->ivec_irqs[irq_num], 1);
+}
+
+static inline void pbm_check_irqs(APBState *s)
+{
+
+ unsigned int i;
+
+ /* Previous request is not acknowledged, resubmit */
+ if (s->irq_request != NO_IRQ_REQUEST) {
+ pbm_set_request(s, s->irq_request);
+ return;
+ }
+ /* no request pending */
+ if (s->pci_irq_in == 0ULL) {
+ return;
+ }
+ for (i = 0; i < 32; i++) {
+ if (s->pci_irq_in & (1ULL << i)) {
+ if (s->pci_irq_map[i >> 2] & PBM_PCI_IMR_ENABLED) {
+ pbm_set_request(s, i);
+ return;
+ }
+ }
+ }
+ for (i = 32; i < 64; i++) {
+ if (s->pci_irq_in & (1ULL << i)) {
+ if (s->obio_irq_map[i - 32] & PBM_PCI_IMR_ENABLED) {
+ pbm_set_request(s, i);
+ break;
+ }
+ }
+ }
+}
+
+static inline void pbm_clear_request(APBState *s, unsigned int irq_num)
+{
+ APB_DPRINTF("%s: clear request irq %d\n", __func__, irq_num);
+ qemu_set_irq(s->ivec_irqs[irq_num], 0);
+ s->irq_request = NO_IRQ_REQUEST;
+}
+
+static AddressSpace *pbm_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn)
+{
+ IOMMUState *is = opaque;
+
+ return &is->iommu_as;
+}
+
+/* Called from RCU critical section */
+static IOMMUTLBEntry pbm_translate_iommu(MemoryRegion *iommu, hwaddr addr,
+ bool is_write)
+{
+ IOMMUState *is = container_of(iommu, IOMMUState, iommu);
+ hwaddr baseaddr, offset;
+ uint64_t tte;
+ uint32_t tsbsize;
+ IOMMUTLBEntry ret = {
+ .target_as = &address_space_memory,
+ .iova = 0,
+ .translated_addr = 0,
+ .addr_mask = ~(hwaddr)0,
+ .perm = IOMMU_NONE,
+ };
+
+ if (!(is->regs[IOMMU_CTRL >> 3] & IOMMU_CTRL_MMU_EN)) {
+ /* IOMMU disabled, passthrough using standard 8K page */
+ ret.iova = addr & IOMMU_PAGE_MASK_8K;
+ ret.translated_addr = addr;
+ ret.addr_mask = IOMMU_PAGE_MASK_8K;
+ ret.perm = IOMMU_RW;
+
+ return ret;
+ }
+
+ baseaddr = is->regs[IOMMU_BASE >> 3];
+ tsbsize = (is->regs[IOMMU_CTRL >> 3] >> IOMMU_CTRL_TSB_SHIFT) & 0x7;
+
+ if (is->regs[IOMMU_CTRL >> 3] & IOMMU_CTRL_TBW_SIZE) {
+ /* 64K */
+ switch (tsbsize) {
+ case 0:
+ offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_64M) >> 13;
+ break;
+ case 1:
+ offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_128M) >> 13;
+ break;
+ case 2:
+ offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_256M) >> 13;
+ break;
+ case 3:
+ offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_512M) >> 13;
+ break;
+ case 4:
+ offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_1G) >> 13;
+ break;
+ case 5:
+ offset = (addr & IOMMU_TSB_64K_OFFSET_MASK_2G) >> 13;
+ break;
+ default:
+ /* Not implemented, error */
+ return ret;
+ }
+ } else {
+ /* 8K */
+ switch (tsbsize) {
+ case 0:
+ offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_8M) >> 10;
+ break;
+ case 1:
+ offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_16M) >> 10;
+ break;
+ case 2:
+ offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_32M) >> 10;
+ break;
+ case 3:
+ offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_64M) >> 10;
+ break;
+ case 4:
+ offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_128M) >> 10;
+ break;
+ case 5:
+ offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_256M) >> 10;
+ break;
+ case 6:
+ offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_512M) >> 10;
+ break;
+ case 7:
+ offset = (addr & IOMMU_TSB_8K_OFFSET_MASK_1G) >> 10;
+ break;
+ }
+ }
+
+ tte = address_space_ldq_be(&address_space_memory, baseaddr + offset,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+
+ if (!(tte & IOMMU_TTE_DATA_V)) {
+ /* Invalid mapping */
+ return ret;
+ }
+
+ if (tte & IOMMU_TTE_DATA_W) {
+ /* Writeable */
+ ret.perm = IOMMU_RW;
+ } else {
+ ret.perm = IOMMU_RO;
+ }
+
+ /* Extract phys */
+ if (tte & IOMMU_TTE_DATA_SIZE) {
+ /* 64K */
+ ret.iova = addr & IOMMU_PAGE_MASK_64K;
+ ret.translated_addr = tte & IOMMU_TTE_PHYS_MASK_64K;
+ ret.addr_mask = (IOMMU_PAGE_SIZE_64K - 1);
+ } else {
+ /* 8K */
+ ret.iova = addr & IOMMU_PAGE_MASK_8K;
+ ret.translated_addr = tte & IOMMU_TTE_PHYS_MASK_8K;
+ ret.addr_mask = (IOMMU_PAGE_SIZE_8K - 1);
+ }
+
+ return ret;
+}
+
+static MemoryRegionIOMMUOps pbm_iommu_ops = {
+ .translate = pbm_translate_iommu,
+};
+
+static void iommu_config_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ IOMMUState *is = opaque;
+
+ IOMMU_DPRINTF("IOMMU config write: 0x%" HWADDR_PRIx " val: %" PRIx64
+ " size: %d\n", addr, val, size);
+
+ switch (addr) {
+ case IOMMU_CTRL:
+ if (size == 4) {
+ is->regs[IOMMU_CTRL >> 3] &= 0xffffffffULL;
+ is->regs[IOMMU_CTRL >> 3] |= val << 32;
+ } else {
+ is->regs[IOMMU_CTRL >> 3] = val;
+ }
+ break;
+ case IOMMU_CTRL + 0x4:
+ is->regs[IOMMU_CTRL >> 3] &= 0xffffffff00000000ULL;
+ is->regs[IOMMU_CTRL >> 3] |= val & 0xffffffffULL;
+ break;
+ case IOMMU_BASE:
+ if (size == 4) {
+ is->regs[IOMMU_BASE >> 3] &= 0xffffffffULL;
+ is->regs[IOMMU_BASE >> 3] |= val << 32;
+ } else {
+ is->regs[IOMMU_BASE >> 3] = val;
+ }
+ break;
+ case IOMMU_BASE + 0x4:
+ is->regs[IOMMU_BASE >> 3] &= 0xffffffff00000000ULL;
+ is->regs[IOMMU_BASE >> 3] |= val & 0xffffffffULL;
+ break;
+ case IOMMU_FLUSH:
+ case IOMMU_FLUSH + 0x4:
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "apb iommu: Unimplemented register write "
+ "reg 0x%" HWADDR_PRIx " size 0x%x value 0x%" PRIx64 "\n",
+ addr, size, val);
+ break;
+ }
+}
+
+static uint64_t iommu_config_read(void *opaque, hwaddr addr, unsigned size)
+{
+ IOMMUState *is = opaque;
+ uint64_t val;
+
+ switch (addr) {
+ case IOMMU_CTRL:
+ if (size == 4) {
+ val = is->regs[IOMMU_CTRL >> 3] >> 32;
+ } else {
+ val = is->regs[IOMMU_CTRL >> 3];
+ }
+ break;
+ case IOMMU_CTRL + 0x4:
+ val = is->regs[IOMMU_CTRL >> 3] & 0xffffffffULL;
+ break;
+ case IOMMU_BASE:
+ if (size == 4) {
+ val = is->regs[IOMMU_BASE >> 3] >> 32;
+ } else {
+ val = is->regs[IOMMU_BASE >> 3];
+ }
+ break;
+ case IOMMU_BASE + 0x4:
+ val = is->regs[IOMMU_BASE >> 3] & 0xffffffffULL;
+ break;
+ case IOMMU_FLUSH:
+ case IOMMU_FLUSH + 0x4:
+ val = 0;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "apb iommu: Unimplemented register read "
+ "reg 0x%" HWADDR_PRIx " size 0x%x\n",
+ addr, size);
+ val = 0;
+ break;
+ }
+
+ IOMMU_DPRINTF("IOMMU config read: 0x%" HWADDR_PRIx " val: %" PRIx64
+ " size: %d\n", addr, val, size);
+
+ return val;
+}
+
+static void apb_config_writel (void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ APBState *s = opaque;
+ IOMMUState *is = &s->iommu;
+
+ APB_DPRINTF("%s: addr " TARGET_FMT_plx " val %" PRIx64 "\n", __func__, addr, val);
+
+ switch (addr & 0xffff) {
+ case 0x30 ... 0x4f: /* DMA error registers */
+ /* XXX: not implemented yet */
+ break;
+ case 0x200 ... 0x217: /* IOMMU */
+ iommu_config_write(is, (addr & 0x1f), val, size);
+ break;
+ case 0xc00 ... 0xc3f: /* PCI interrupt control */
+ if (addr & 4) {
+ unsigned int ino = (addr & 0x3f) >> 3;
+ s->pci_irq_map[ino] &= PBM_PCI_IMR_MASK;
+ s->pci_irq_map[ino] |= val & ~PBM_PCI_IMR_MASK;
+ if ((s->irq_request == ino) && !(val & ~PBM_PCI_IMR_MASK)) {
+ pbm_clear_request(s, ino);
+ }
+ pbm_check_irqs(s);
+ }
+ break;
+ case 0x1000 ... 0x107f: /* OBIO interrupt control */
+ if (addr & 4) {
+ unsigned int ino = ((addr & 0xff) >> 3);
+ s->obio_irq_map[ino] &= PBM_PCI_IMR_MASK;
+ s->obio_irq_map[ino] |= val & ~PBM_PCI_IMR_MASK;
+ if ((s->irq_request == (ino | 0x20))
+ && !(val & ~PBM_PCI_IMR_MASK)) {
+ pbm_clear_request(s, ino | 0x20);
+ }
+ pbm_check_irqs(s);
+ }
+ break;
+ case 0x1400 ... 0x14ff: /* PCI interrupt clear */
+ if (addr & 4) {
+ unsigned int ino = (addr & 0xff) >> 5;
+ if ((s->irq_request / 4) == ino) {
+ pbm_clear_request(s, s->irq_request);
+ pbm_check_irqs(s);
+ }
+ }
+ break;
+ case 0x1800 ... 0x1860: /* OBIO interrupt clear */
+ if (addr & 4) {
+ unsigned int ino = ((addr & 0xff) >> 3) | 0x20;
+ if (s->irq_request == ino) {
+ pbm_clear_request(s, ino);
+ pbm_check_irqs(s);
+ }
+ }
+ break;
+ case 0x2000 ... 0x202f: /* PCI control */
+ s->pci_control[(addr & 0x3f) >> 2] = val;
+ break;
+ case 0xf020 ... 0xf027: /* Reset control */
+ if (addr & 4) {
+ val &= RESET_MASK;
+ s->reset_control &= ~(val & RESET_WCMASK);
+ s->reset_control |= val & RESET_WMASK;
+ if (val & SOFT_POR) {
+ s->nr_resets = 0;
+ qemu_system_reset_request();
+ } else if (val & SOFT_XIR) {
+ qemu_system_reset_request();
+ }
+ }
+ break;
+ case 0x5000 ... 0x51cf: /* PIO/DMA diagnostics */
+ case 0xa400 ... 0xa67f: /* IOMMU diagnostics */
+ case 0xa800 ... 0xa80f: /* Interrupt diagnostics */
+ case 0xf000 ... 0xf01f: /* FFB config, memory control */
+ /* we don't care */
+ default:
+ break;
+ }
+}
+
+static uint64_t apb_config_readl (void *opaque,
+ hwaddr addr, unsigned size)
+{
+ APBState *s = opaque;
+ IOMMUState *is = &s->iommu;
+ uint32_t val;
+
+ switch (addr & 0xffff) {
+ case 0x30 ... 0x4f: /* DMA error registers */
+ val = 0;
+ /* XXX: not implemented yet */
+ break;
+ case 0x200 ... 0x217: /* IOMMU */
+ val = iommu_config_read(is, (addr & 0x1f), size);
+ break;
+ case 0xc00 ... 0xc3f: /* PCI interrupt control */
+ if (addr & 4) {
+ val = s->pci_irq_map[(addr & 0x3f) >> 3];
+ } else {
+ val = 0;
+ }
+ break;
+ case 0x1000 ... 0x107f: /* OBIO interrupt control */
+ if (addr & 4) {
+ val = s->obio_irq_map[(addr & 0xff) >> 3];
+ } else {
+ val = 0;
+ }
+ break;
+ case 0x1080 ... 0x108f: /* PCI bus error */
+ if (addr & 4) {
+ val = s->pci_err_irq_map[(addr & 0xf) >> 3];
+ } else {
+ val = 0;
+ }
+ break;
+ case 0x2000 ... 0x202f: /* PCI control */
+ val = s->pci_control[(addr & 0x3f) >> 2];
+ break;
+ case 0xf020 ... 0xf027: /* Reset control */
+ if (addr & 4) {
+ val = s->reset_control;
+ } else {
+ val = 0;
+ }
+ break;
+ case 0x5000 ... 0x51cf: /* PIO/DMA diagnostics */
+ case 0xa400 ... 0xa67f: /* IOMMU diagnostics */
+ case 0xa800 ... 0xa80f: /* Interrupt diagnostics */
+ case 0xf000 ... 0xf01f: /* FFB config, memory control */
+ /* we don't care */
+ default:
+ val = 0;
+ break;
+ }
+ APB_DPRINTF("%s: addr " TARGET_FMT_plx " -> %x\n", __func__, addr, val);
+
+ return val;
+}
+
+static const MemoryRegionOps apb_config_ops = {
+ .read = apb_config_readl,
+ .write = apb_config_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void apb_pci_config_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ APBState *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+
+ val = qemu_bswap_len(val, size);
+ APB_DPRINTF("%s: addr " TARGET_FMT_plx " val %" PRIx64 "\n", __func__, addr, val);
+ pci_data_write(phb->bus, addr, val, size);
+}
+
+static uint64_t apb_pci_config_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t ret;
+ APBState *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+
+ ret = pci_data_read(phb->bus, addr, size);
+ ret = qemu_bswap_len(ret, size);
+ APB_DPRINTF("%s: addr " TARGET_FMT_plx " -> %x\n", __func__, addr, ret);
+ return ret;
+}
+
+/* The APB host has an IRQ line for each IRQ line of each slot. */
+static int pci_apb_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ return ((pci_dev->devfn & 0x18) >> 1) + irq_num;
+}
+
+static int pci_pbm_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ int bus_offset;
+ if (pci_dev->devfn & 1)
+ bus_offset = 16;
+ else
+ bus_offset = 0;
+ return (bus_offset + (PCI_SLOT(pci_dev->devfn) << 2) + irq_num) & 0x1f;
+}
+
+static void pci_apb_set_irq(void *opaque, int irq_num, int level)
+{
+ APBState *s = opaque;
+
+ APB_DPRINTF("%s: set irq_in %d level %d\n", __func__, irq_num, level);
+ /* PCI IRQ map onto the first 32 INO. */
+ if (irq_num < 32) {
+ if (level) {
+ s->pci_irq_in |= 1ULL << irq_num;
+ if (s->pci_irq_map[irq_num >> 2] & PBM_PCI_IMR_ENABLED) {
+ pbm_set_request(s, irq_num);
+ }
+ } else {
+ s->pci_irq_in &= ~(1ULL << irq_num);
+ }
+ } else {
+ /* OBIO IRQ map onto the next 32 INO. */
+ if (level) {
+ APB_DPRINTF("%s: set irq %d level %d\n", __func__, irq_num, level);
+ s->pci_irq_in |= 1ULL << irq_num;
+ if ((s->irq_request == NO_IRQ_REQUEST)
+ && (s->obio_irq_map[irq_num - 32] & PBM_PCI_IMR_ENABLED)) {
+ pbm_set_request(s, irq_num);
+ }
+ } else {
+ s->pci_irq_in &= ~(1ULL << irq_num);
+ }
+ }
+}
+
+static int apb_pci_bridge_initfn(PCIDevice *dev)
+{
+ int rc;
+
+ rc = pci_bridge_initfn(dev, TYPE_PCI_BUS);
+ if (rc < 0) {
+ return rc;
+ }
+
+ /*
+ * command register:
+ * According to PCI bridge spec, after reset
+ * bus master bit is off
+ * memory space enable bit is off
+ * According to manual (805-1251.pdf).
+ * the reset value should be zero unless the boot pin is tied high
+ * (which is true) and thus it should be PCI_COMMAND_MEMORY.
+ */
+ pci_set_word(dev->config + PCI_COMMAND,
+ PCI_COMMAND_MEMORY);
+ pci_set_word(dev->config + PCI_STATUS,
+ PCI_STATUS_FAST_BACK | PCI_STATUS_66MHZ |
+ PCI_STATUS_DEVSEL_MEDIUM);
+ return 0;
+}
+
+PCIBus *pci_apb_init(hwaddr special_base,
+ hwaddr mem_base,
+ qemu_irq *ivec_irqs, PCIBus **bus2, PCIBus **bus3,
+ qemu_irq **pbm_irqs)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ PCIHostState *phb;
+ APBState *d;
+ IOMMUState *is;
+ PCIDevice *pci_dev;
+ PCIBridge *br;
+
+ /* Ultrasparc PBM main bus */
+ dev = qdev_create(NULL, TYPE_APB);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ /* apb_config */
+ sysbus_mmio_map(s, 0, special_base);
+ /* PCI configuration space */
+ sysbus_mmio_map(s, 1, special_base + 0x1000000ULL);
+ /* pci_ioport */
+ sysbus_mmio_map(s, 2, special_base + 0x2000000ULL);
+ d = APB_DEVICE(dev);
+
+ memory_region_init(&d->pci_mmio, OBJECT(s), "pci-mmio", 0x100000000ULL);
+ memory_region_add_subregion(get_system_memory(), mem_base, &d->pci_mmio);
+
+ phb = PCI_HOST_BRIDGE(dev);
+ phb->bus = pci_register_bus(DEVICE(phb), "pci",
+ pci_apb_set_irq, pci_pbm_map_irq, d,
+ &d->pci_mmio,
+ get_system_io(),
+ 0, 32, TYPE_PCI_BUS);
+
+ *pbm_irqs = d->pbm_irqs;
+ d->ivec_irqs = ivec_irqs;
+
+ pci_create_simple(phb->bus, 0, "pbm-pci");
+
+ /* APB IOMMU */
+ is = &d->iommu;
+ memset(is, 0, sizeof(IOMMUState));
+
+ memory_region_init_iommu(&is->iommu, OBJECT(dev), &pbm_iommu_ops,
+ "iommu-apb", UINT64_MAX);
+ address_space_init(&is->iommu_as, &is->iommu, "pbm-as");
+ pci_setup_iommu(phb->bus, pbm_pci_dma_iommu, is);
+
+ /* APB secondary busses */
+ pci_dev = pci_create_multifunction(phb->bus, PCI_DEVFN(1, 0), true,
+ "pbm-bridge");
+ br = PCI_BRIDGE(pci_dev);
+ pci_bridge_map_irq(br, "Advanced PCI Bus secondary bridge 1",
+ pci_apb_map_irq);
+ qdev_init_nofail(&pci_dev->qdev);
+ *bus2 = pci_bridge_get_sec_bus(br);
+
+ pci_dev = pci_create_multifunction(phb->bus, PCI_DEVFN(1, 1), true,
+ "pbm-bridge");
+ br = PCI_BRIDGE(pci_dev);
+ pci_bridge_map_irq(br, "Advanced PCI Bus secondary bridge 2",
+ pci_apb_map_irq);
+ qdev_init_nofail(&pci_dev->qdev);
+ *bus3 = pci_bridge_get_sec_bus(br);
+
+ return phb->bus;
+}
+
+static void pci_pbm_reset(DeviceState *d)
+{
+ unsigned int i;
+ APBState *s = APB_DEVICE(d);
+
+ for (i = 0; i < 8; i++) {
+ s->pci_irq_map[i] &= PBM_PCI_IMR_MASK;
+ }
+ for (i = 0; i < 32; i++) {
+ s->obio_irq_map[i] &= PBM_PCI_IMR_MASK;
+ }
+
+ s->irq_request = NO_IRQ_REQUEST;
+ s->pci_irq_in = 0ULL;
+
+ if (s->nr_resets++ == 0) {
+ /* Power on reset */
+ s->reset_control = POR;
+ }
+}
+
+static const MemoryRegionOps pci_config_ops = {
+ .read = apb_pci_config_read,
+ .write = apb_pci_config_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pci_pbm_init_device(SysBusDevice *dev)
+{
+ APBState *s;
+ unsigned int i;
+
+ s = APB_DEVICE(dev);
+ for (i = 0; i < 8; i++) {
+ s->pci_irq_map[i] = (0x1f << 6) | (i << 2);
+ }
+ for (i = 0; i < 2; i++) {
+ s->pci_err_irq_map[i] = (0x1f << 6) | 0x30;
+ }
+ for (i = 0; i < 32; i++) {
+ s->obio_irq_map[i] = ((0x1f << 6) | 0x20) + i;
+ }
+ s->pbm_irqs = qemu_allocate_irqs(pci_apb_set_irq, s, MAX_IVEC);
+ s->irq_request = NO_IRQ_REQUEST;
+ s->pci_irq_in = 0ULL;
+
+ /* apb_config */
+ memory_region_init_io(&s->apb_config, OBJECT(s), &apb_config_ops, s,
+ "apb-config", 0x10000);
+ /* at region 0 */
+ sysbus_init_mmio(dev, &s->apb_config);
+
+ memory_region_init_io(&s->pci_config, OBJECT(s), &pci_config_ops, s,
+ "apb-pci-config", 0x1000000);
+ /* at region 1 */
+ sysbus_init_mmio(dev, &s->pci_config);
+
+ /* pci_ioport */
+ memory_region_init_alias(&s->pci_ioport, OBJECT(s), "apb-pci-ioport",
+ get_system_io(), 0, 0x10000);
+ /* at region 2 */
+ sysbus_init_mmio(dev, &s->pci_ioport);
+
+ return 0;
+}
+
+static void pbm_pci_host_realize(PCIDevice *d, Error **errp)
+{
+ pci_set_word(d->config + PCI_COMMAND,
+ PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+ pci_set_word(d->config + PCI_STATUS,
+ PCI_STATUS_FAST_BACK | PCI_STATUS_66MHZ |
+ PCI_STATUS_DEVSEL_MEDIUM);
+}
+
+static void pbm_pci_host_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = pbm_pci_host_realize;
+ k->vendor_id = PCI_VENDOR_ID_SUN;
+ k->device_id = PCI_DEVICE_ID_SUN_SABRE;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo pbm_pci_host_info = {
+ .name = "pbm-pci",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = pbm_pci_host_class_init,
+};
+
+static void pbm_host_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pci_pbm_init_device;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->reset = pci_pbm_reset;
+}
+
+static const TypeInfo pbm_host_info = {
+ .name = TYPE_APB,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(APBState),
+ .class_init = pbm_host_class_init,
+};
+
+static void pbm_pci_bridge_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = apb_pci_bridge_initfn;
+ k->exit = pci_bridge_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_SUN;
+ k->device_id = PCI_DEVICE_ID_SUN_SIMBA;
+ k->revision = 0x11;
+ k->config_write = pci_bridge_write_config;
+ k->is_bridge = 1;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->reset = pci_bridge_reset;
+ dc->vmsd = &vmstate_pci_device;
+}
+
+static const TypeInfo pbm_pci_bridge_info = {
+ .name = "pbm-bridge",
+ .parent = TYPE_PCI_BRIDGE,
+ .class_init = pbm_pci_bridge_class_init,
+};
+
+static void pbm_register_types(void)
+{
+ type_register_static(&pbm_host_info);
+ type_register_static(&pbm_pci_host_info);
+ type_register_static(&pbm_pci_bridge_info);
+}
+
+type_init(pbm_register_types)
diff --git a/hw/pci-host/bonito.c b/hw/pci-host/bonito.c
new file mode 100644
index 00000000..3a731fe1
--- /dev/null
+++ b/hw/pci-host/bonito.c
@@ -0,0 +1,841 @@
+/*
+ * bonito north bridge support
+ *
+ * Copyright (c) 2008 yajin (yajin@vm-kernel.org)
+ * Copyright (c) 2010 Huacai Chen (zltjiangshi@gmail.com)
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+/*
+ * fulong 2e mini pc has a bonito north bridge.
+ */
+
+/* what is the meaning of devfn in qemu and IDSEL in bonito northbridge?
+ *
+ * devfn pci_slot<<3 + funno
+ * one pci bus can have 32 devices and each device can have 8 functions.
+ *
+ * In bonito north bridge, pci slot = IDSEL bit - 12.
+ * For example, PCI_IDSEL_VIA686B = 17,
+ * pci slot = 17-12=5
+ *
+ * so
+ * VT686B_FUN0's devfn = (5<<3)+0
+ * VT686B_FUN1's devfn = (5<<3)+1
+ *
+ * qemu also uses pci address for north bridge to access pci config register.
+ * bus_no [23:16]
+ * dev_no [15:11]
+ * fun_no [10:8]
+ * reg_no [7:2]
+ *
+ * so function bonito_sbridge_pciaddr for the translation from
+ * north bridge address to pci address.
+ */
+
+#include <assert.h>
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/i386/pc.h"
+#include "hw/mips/mips.h"
+#include "hw/pci/pci_host.h"
+#include "sysemu/sysemu.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_BONITO
+
+#ifdef DEBUG_BONITO
+#define DPRINTF(fmt, ...) fprintf(stderr, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+/* from linux soure code. include/asm-mips/mips-boards/bonito64.h*/
+#define BONITO_BOOT_BASE 0x1fc00000
+#define BONITO_BOOT_SIZE 0x00100000
+#define BONITO_BOOT_TOP (BONITO_BOOT_BASE+BONITO_BOOT_SIZE-1)
+#define BONITO_FLASH_BASE 0x1c000000
+#define BONITO_FLASH_SIZE 0x03000000
+#define BONITO_FLASH_TOP (BONITO_FLASH_BASE+BONITO_FLASH_SIZE-1)
+#define BONITO_SOCKET_BASE 0x1f800000
+#define BONITO_SOCKET_SIZE 0x00400000
+#define BONITO_SOCKET_TOP (BONITO_SOCKET_BASE+BONITO_SOCKET_SIZE-1)
+#define BONITO_REG_BASE 0x1fe00000
+#define BONITO_REG_SIZE 0x00040000
+#define BONITO_REG_TOP (BONITO_REG_BASE+BONITO_REG_SIZE-1)
+#define BONITO_DEV_BASE 0x1ff00000
+#define BONITO_DEV_SIZE 0x00100000
+#define BONITO_DEV_TOP (BONITO_DEV_BASE+BONITO_DEV_SIZE-1)
+#define BONITO_PCILO_BASE 0x10000000
+#define BONITO_PCILO_BASE_VA 0xb0000000
+#define BONITO_PCILO_SIZE 0x0c000000
+#define BONITO_PCILO_TOP (BONITO_PCILO_BASE+BONITO_PCILO_SIZE-1)
+#define BONITO_PCILO0_BASE 0x10000000
+#define BONITO_PCILO1_BASE 0x14000000
+#define BONITO_PCILO2_BASE 0x18000000
+#define BONITO_PCIHI_BASE 0x20000000
+#define BONITO_PCIHI_SIZE 0x20000000
+#define BONITO_PCIHI_TOP (BONITO_PCIHI_BASE+BONITO_PCIHI_SIZE-1)
+#define BONITO_PCIIO_BASE 0x1fd00000
+#define BONITO_PCIIO_BASE_VA 0xbfd00000
+#define BONITO_PCIIO_SIZE 0x00010000
+#define BONITO_PCIIO_TOP (BONITO_PCIIO_BASE+BONITO_PCIIO_SIZE-1)
+#define BONITO_PCICFG_BASE 0x1fe80000
+#define BONITO_PCICFG_SIZE 0x00080000
+#define BONITO_PCICFG_TOP (BONITO_PCICFG_BASE+BONITO_PCICFG_SIZE-1)
+
+
+#define BONITO_PCICONFIGBASE 0x00
+#define BONITO_REGBASE 0x100
+
+#define BONITO_PCICONFIG_BASE (BONITO_PCICONFIGBASE+BONITO_REG_BASE)
+#define BONITO_PCICONFIG_SIZE (0x100)
+
+#define BONITO_INTERNAL_REG_BASE (BONITO_REGBASE+BONITO_REG_BASE)
+#define BONITO_INTERNAL_REG_SIZE (0x70)
+
+#define BONITO_SPCICONFIG_BASE (BONITO_PCICFG_BASE)
+#define BONITO_SPCICONFIG_SIZE (BONITO_PCICFG_SIZE)
+
+
+
+/* 1. Bonito h/w Configuration */
+/* Power on register */
+
+#define BONITO_BONPONCFG (0x00 >> 2) /* 0x100 */
+#define BONITO_BONGENCFG_OFFSET 0x4
+#define BONITO_BONGENCFG (BONITO_BONGENCFG_OFFSET>>2) /*0x104 */
+
+/* 2. IO & IDE configuration */
+#define BONITO_IODEVCFG (0x08 >> 2) /* 0x108 */
+
+/* 3. IO & IDE configuration */
+#define BONITO_SDCFG (0x0c >> 2) /* 0x10c */
+
+/* 4. PCI address map control */
+#define BONITO_PCIMAP (0x10 >> 2) /* 0x110 */
+#define BONITO_PCIMEMBASECFG (0x14 >> 2) /* 0x114 */
+#define BONITO_PCIMAP_CFG (0x18 >> 2) /* 0x118 */
+
+/* 5. ICU & GPIO regs */
+/* GPIO Regs - r/w */
+#define BONITO_GPIODATA_OFFSET 0x1c
+#define BONITO_GPIODATA (BONITO_GPIODATA_OFFSET >> 2) /* 0x11c */
+#define BONITO_GPIOIE (0x20 >> 2) /* 0x120 */
+
+/* ICU Configuration Regs - r/w */
+#define BONITO_INTEDGE (0x24 >> 2) /* 0x124 */
+#define BONITO_INTSTEER (0x28 >> 2) /* 0x128 */
+#define BONITO_INTPOL (0x2c >> 2) /* 0x12c */
+
+/* ICU Enable Regs - IntEn & IntISR are r/o. */
+#define BONITO_INTENSET (0x30 >> 2) /* 0x130 */
+#define BONITO_INTENCLR (0x34 >> 2) /* 0x134 */
+#define BONITO_INTEN (0x38 >> 2) /* 0x138 */
+#define BONITO_INTISR (0x3c >> 2) /* 0x13c */
+
+/* PCI mail boxes */
+#define BONITO_PCIMAIL0_OFFSET 0x40
+#define BONITO_PCIMAIL1_OFFSET 0x44
+#define BONITO_PCIMAIL2_OFFSET 0x48
+#define BONITO_PCIMAIL3_OFFSET 0x4c
+#define BONITO_PCIMAIL0 (0x40 >> 2) /* 0x140 */
+#define BONITO_PCIMAIL1 (0x44 >> 2) /* 0x144 */
+#define BONITO_PCIMAIL2 (0x48 >> 2) /* 0x148 */
+#define BONITO_PCIMAIL3 (0x4c >> 2) /* 0x14c */
+
+/* 6. PCI cache */
+#define BONITO_PCICACHECTRL (0x50 >> 2) /* 0x150 */
+#define BONITO_PCICACHETAG (0x54 >> 2) /* 0x154 */
+#define BONITO_PCIBADADDR (0x58 >> 2) /* 0x158 */
+#define BONITO_PCIMSTAT (0x5c >> 2) /* 0x15c */
+
+/* 7. other*/
+#define BONITO_TIMECFG (0x60 >> 2) /* 0x160 */
+#define BONITO_CPUCFG (0x64 >> 2) /* 0x164 */
+#define BONITO_DQCFG (0x68 >> 2) /* 0x168 */
+#define BONITO_MEMSIZE (0x6C >> 2) /* 0x16c */
+
+#define BONITO_REGS (0x70 >> 2)
+
+/* PCI config for south bridge. type 0 */
+#define BONITO_PCICONF_IDSEL_MASK 0xfffff800 /* [31:11] */
+#define BONITO_PCICONF_IDSEL_OFFSET 11
+#define BONITO_PCICONF_FUN_MASK 0x700 /* [10:8] */
+#define BONITO_PCICONF_FUN_OFFSET 8
+#define BONITO_PCICONF_REG_MASK 0xFC
+#define BONITO_PCICONF_REG_OFFSET 0
+
+
+/* idsel BIT = pci slot number +12 */
+#define PCI_SLOT_BASE 12
+#define PCI_IDSEL_VIA686B_BIT (17)
+#define PCI_IDSEL_VIA686B (1<<PCI_IDSEL_VIA686B_BIT)
+
+#define PCI_ADDR(busno,devno,funno,regno) \
+ ((((busno)<<16)&0xff0000) + (((devno)<<11)&0xf800) + (((funno)<<8)&0x700) + (regno))
+
+#define TYPE_BONITO_PCI_HOST_BRIDGE "Bonito-pcihost"
+
+typedef struct BonitoState BonitoState;
+
+typedef struct PCIBonitoState
+{
+ PCIDevice dev;
+
+ BonitoState *pcihost;
+ uint32_t regs[BONITO_REGS];
+
+ struct bonldma {
+ uint32_t ldmactrl;
+ uint32_t ldmastat;
+ uint32_t ldmaaddr;
+ uint32_t ldmago;
+ } bonldma;
+
+ /* Based at 1fe00300, bonito Copier */
+ struct boncop {
+ uint32_t copctrl;
+ uint32_t copstat;
+ uint32_t coppaddr;
+ uint32_t copgo;
+ } boncop;
+
+ /* Bonito registers */
+ MemoryRegion iomem;
+ MemoryRegion iomem_ldma;
+ MemoryRegion iomem_cop;
+ MemoryRegion bonito_pciio;
+ MemoryRegion bonito_localio;
+
+} PCIBonitoState;
+
+#define BONITO_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(BonitoState, (obj), TYPE_BONITO_PCI_HOST_BRIDGE)
+
+struct BonitoState {
+ PCIHostState parent_obj;
+
+ qemu_irq *pic;
+
+ PCIBonitoState *pci_dev;
+};
+
+static void bonito_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIBonitoState *s = opaque;
+ uint32_t saddr;
+ int reset = 0;
+
+ saddr = addr >> 2;
+
+ DPRINTF("bonito_writel "TARGET_FMT_plx" val %x saddr %x\n", addr, val, saddr);
+ switch (saddr) {
+ case BONITO_BONPONCFG:
+ case BONITO_IODEVCFG:
+ case BONITO_SDCFG:
+ case BONITO_PCIMAP:
+ case BONITO_PCIMEMBASECFG:
+ case BONITO_PCIMAP_CFG:
+ case BONITO_GPIODATA:
+ case BONITO_GPIOIE:
+ case BONITO_INTEDGE:
+ case BONITO_INTSTEER:
+ case BONITO_INTPOL:
+ case BONITO_PCIMAIL0:
+ case BONITO_PCIMAIL1:
+ case BONITO_PCIMAIL2:
+ case BONITO_PCIMAIL3:
+ case BONITO_PCICACHECTRL:
+ case BONITO_PCICACHETAG:
+ case BONITO_PCIBADADDR:
+ case BONITO_PCIMSTAT:
+ case BONITO_TIMECFG:
+ case BONITO_CPUCFG:
+ case BONITO_DQCFG:
+ case BONITO_MEMSIZE:
+ s->regs[saddr] = val;
+ break;
+ case BONITO_BONGENCFG:
+ if (!(s->regs[saddr] & 0x04) && (val & 0x04)) {
+ reset = 1; /* bit 2 jump from 0 to 1 cause reset */
+ }
+ s->regs[saddr] = val;
+ if (reset) {
+ qemu_system_reset_request();
+ }
+ break;
+ case BONITO_INTENSET:
+ s->regs[BONITO_INTENSET] = val;
+ s->regs[BONITO_INTEN] |= val;
+ break;
+ case BONITO_INTENCLR:
+ s->regs[BONITO_INTENCLR] = val;
+ s->regs[BONITO_INTEN] &= ~val;
+ break;
+ case BONITO_INTEN:
+ case BONITO_INTISR:
+ DPRINTF("write to readonly bonito register %x\n", saddr);
+ break;
+ default:
+ DPRINTF("write to unknown bonito register %x\n", saddr);
+ break;
+ }
+}
+
+static uint64_t bonito_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCIBonitoState *s = opaque;
+ uint32_t saddr;
+
+ saddr = addr >> 2;
+
+ DPRINTF("bonito_readl "TARGET_FMT_plx"\n", addr);
+ switch (saddr) {
+ case BONITO_INTISR:
+ return s->regs[saddr];
+ default:
+ return s->regs[saddr];
+ }
+}
+
+static const MemoryRegionOps bonito_ops = {
+ .read = bonito_readl,
+ .write = bonito_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void bonito_pciconf_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIBonitoState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+
+ DPRINTF("bonito_pciconf_writel "TARGET_FMT_plx" val %x\n", addr, val);
+ d->config_write(d, addr, val, 4);
+}
+
+static uint64_t bonito_pciconf_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+
+ PCIBonitoState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+
+ DPRINTF("bonito_pciconf_readl "TARGET_FMT_plx"\n", addr);
+ return d->config_read(d, addr, 4);
+}
+
+/* north bridge PCI configure space. 0x1fe0 0000 - 0x1fe0 00ff */
+
+static const MemoryRegionOps bonito_pciconf_ops = {
+ .read = bonito_pciconf_readl,
+ .write = bonito_pciconf_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t bonito_ldma_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t val;
+ PCIBonitoState *s = opaque;
+
+ val = ((uint32_t *)(&s->bonldma))[addr/sizeof(uint32_t)];
+
+ return val;
+}
+
+static void bonito_ldma_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIBonitoState *s = opaque;
+
+ ((uint32_t *)(&s->bonldma))[addr/sizeof(uint32_t)] = val & 0xffffffff;
+}
+
+static const MemoryRegionOps bonito_ldma_ops = {
+ .read = bonito_ldma_readl,
+ .write = bonito_ldma_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t bonito_cop_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t val;
+ PCIBonitoState *s = opaque;
+
+ val = ((uint32_t *)(&s->boncop))[addr/sizeof(uint32_t)];
+
+ return val;
+}
+
+static void bonito_cop_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIBonitoState *s = opaque;
+
+ ((uint32_t *)(&s->boncop))[addr/sizeof(uint32_t)] = val & 0xffffffff;
+}
+
+static const MemoryRegionOps bonito_cop_ops = {
+ .read = bonito_cop_readl,
+ .write = bonito_cop_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint32_t bonito_sbridge_pciaddr(void *opaque, hwaddr addr)
+{
+ PCIBonitoState *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s->pcihost);
+ uint32_t cfgaddr;
+ uint32_t idsel;
+ uint32_t devno;
+ uint32_t funno;
+ uint32_t regno;
+ uint32_t pciaddr;
+
+ /* support type0 pci config */
+ if ((s->regs[BONITO_PCIMAP_CFG] & 0x10000) != 0x0) {
+ return 0xffffffff;
+ }
+
+ cfgaddr = addr & 0xffff;
+ cfgaddr |= (s->regs[BONITO_PCIMAP_CFG] & 0xffff) << 16;
+
+ idsel = (cfgaddr & BONITO_PCICONF_IDSEL_MASK) >> BONITO_PCICONF_IDSEL_OFFSET;
+ devno = ctz32(idsel);
+ funno = (cfgaddr & BONITO_PCICONF_FUN_MASK) >> BONITO_PCICONF_FUN_OFFSET;
+ regno = (cfgaddr & BONITO_PCICONF_REG_MASK) >> BONITO_PCICONF_REG_OFFSET;
+
+ if (idsel == 0) {
+ fprintf(stderr, "error in bonito pci config address " TARGET_FMT_plx
+ ",pcimap_cfg=%x\n", addr, s->regs[BONITO_PCIMAP_CFG]);
+ exit(1);
+ }
+ pciaddr = PCI_ADDR(pci_bus_num(phb->bus), devno, funno, regno);
+ DPRINTF("cfgaddr %x pciaddr %x busno %x devno %d funno %d regno %d\n",
+ cfgaddr, pciaddr, pci_bus_num(phb->bus), devno, funno, regno);
+
+ return pciaddr;
+}
+
+static void bonito_spciconf_writeb(void *opaque, hwaddr addr,
+ uint32_t val)
+{
+ PCIBonitoState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+ PCIHostState *phb = PCI_HOST_BRIDGE(s->pcihost);
+ uint32_t pciaddr;
+ uint16_t status;
+
+ DPRINTF("bonito_spciconf_writeb "TARGET_FMT_plx" val %x\n", addr, val);
+ pciaddr = bonito_sbridge_pciaddr(s, addr);
+
+ if (pciaddr == 0xffffffff) {
+ return;
+ }
+
+ /* set the pci address in s->config_reg */
+ phb->config_reg = (pciaddr) | (1u << 31);
+ pci_data_write(phb->bus, phb->config_reg, val & 0xff, 1);
+
+ /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */
+ status = pci_get_word(d->config + PCI_STATUS);
+ status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT);
+ pci_set_word(d->config + PCI_STATUS, status);
+}
+
+static void bonito_spciconf_writew(void *opaque, hwaddr addr,
+ uint32_t val)
+{
+ PCIBonitoState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+ PCIHostState *phb = PCI_HOST_BRIDGE(s->pcihost);
+ uint32_t pciaddr;
+ uint16_t status;
+
+ DPRINTF("bonito_spciconf_writew "TARGET_FMT_plx" val %x\n", addr, val);
+ assert((addr & 0x1) == 0);
+
+ pciaddr = bonito_sbridge_pciaddr(s, addr);
+
+ if (pciaddr == 0xffffffff) {
+ return;
+ }
+
+ /* set the pci address in s->config_reg */
+ phb->config_reg = (pciaddr) | (1u << 31);
+ pci_data_write(phb->bus, phb->config_reg, val, 2);
+
+ /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */
+ status = pci_get_word(d->config + PCI_STATUS);
+ status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT);
+ pci_set_word(d->config + PCI_STATUS, status);
+}
+
+static void bonito_spciconf_writel(void *opaque, hwaddr addr,
+ uint32_t val)
+{
+ PCIBonitoState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+ PCIHostState *phb = PCI_HOST_BRIDGE(s->pcihost);
+ uint32_t pciaddr;
+ uint16_t status;
+
+ DPRINTF("bonito_spciconf_writel "TARGET_FMT_plx" val %x\n", addr, val);
+ assert((addr & 0x3) == 0);
+
+ pciaddr = bonito_sbridge_pciaddr(s, addr);
+
+ if (pciaddr == 0xffffffff) {
+ return;
+ }
+
+ /* set the pci address in s->config_reg */
+ phb->config_reg = (pciaddr) | (1u << 31);
+ pci_data_write(phb->bus, phb->config_reg, val, 4);
+
+ /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */
+ status = pci_get_word(d->config + PCI_STATUS);
+ status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT);
+ pci_set_word(d->config + PCI_STATUS, status);
+}
+
+static uint32_t bonito_spciconf_readb(void *opaque, hwaddr addr)
+{
+ PCIBonitoState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+ PCIHostState *phb = PCI_HOST_BRIDGE(s->pcihost);
+ uint32_t pciaddr;
+ uint16_t status;
+
+ DPRINTF("bonito_spciconf_readb "TARGET_FMT_plx"\n", addr);
+ pciaddr = bonito_sbridge_pciaddr(s, addr);
+
+ if (pciaddr == 0xffffffff) {
+ return 0xff;
+ }
+
+ /* set the pci address in s->config_reg */
+ phb->config_reg = (pciaddr) | (1u << 31);
+
+ /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */
+ status = pci_get_word(d->config + PCI_STATUS);
+ status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT);
+ pci_set_word(d->config + PCI_STATUS, status);
+
+ return pci_data_read(phb->bus, phb->config_reg, 1);
+}
+
+static uint32_t bonito_spciconf_readw(void *opaque, hwaddr addr)
+{
+ PCIBonitoState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+ PCIHostState *phb = PCI_HOST_BRIDGE(s->pcihost);
+ uint32_t pciaddr;
+ uint16_t status;
+
+ DPRINTF("bonito_spciconf_readw "TARGET_FMT_plx"\n", addr);
+ assert((addr & 0x1) == 0);
+
+ pciaddr = bonito_sbridge_pciaddr(s, addr);
+
+ if (pciaddr == 0xffffffff) {
+ return 0xffff;
+ }
+
+ /* set the pci address in s->config_reg */
+ phb->config_reg = (pciaddr) | (1u << 31);
+
+ /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */
+ status = pci_get_word(d->config + PCI_STATUS);
+ status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT);
+ pci_set_word(d->config + PCI_STATUS, status);
+
+ return pci_data_read(phb->bus, phb->config_reg, 2);
+}
+
+static uint32_t bonito_spciconf_readl(void *opaque, hwaddr addr)
+{
+ PCIBonitoState *s = opaque;
+ PCIDevice *d = PCI_DEVICE(s);
+ PCIHostState *phb = PCI_HOST_BRIDGE(s->pcihost);
+ uint32_t pciaddr;
+ uint16_t status;
+
+ DPRINTF("bonito_spciconf_readl "TARGET_FMT_plx"\n", addr);
+ assert((addr & 0x3) == 0);
+
+ pciaddr = bonito_sbridge_pciaddr(s, addr);
+
+ if (pciaddr == 0xffffffff) {
+ return 0xffffffff;
+ }
+
+ /* set the pci address in s->config_reg */
+ phb->config_reg = (pciaddr) | (1u << 31);
+
+ /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */
+ status = pci_get_word(d->config + PCI_STATUS);
+ status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT);
+ pci_set_word(d->config + PCI_STATUS, status);
+
+ return pci_data_read(phb->bus, phb->config_reg, 4);
+}
+
+/* south bridge PCI configure space. 0x1fe8 0000 - 0x1fef ffff */
+static const MemoryRegionOps bonito_spciconf_ops = {
+ .old_mmio = {
+ .read = {
+ bonito_spciconf_readb,
+ bonito_spciconf_readw,
+ bonito_spciconf_readl,
+ },
+ .write = {
+ bonito_spciconf_writeb,
+ bonito_spciconf_writew,
+ bonito_spciconf_writel,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+#define BONITO_IRQ_BASE 32
+
+static void pci_bonito_set_irq(void *opaque, int irq_num, int level)
+{
+ BonitoState *s = opaque;
+ qemu_irq *pic = s->pic;
+ PCIBonitoState *bonito_state = s->pci_dev;
+ int internal_irq = irq_num - BONITO_IRQ_BASE;
+
+ if (bonito_state->regs[BONITO_INTEDGE] & (1 << internal_irq)) {
+ qemu_irq_pulse(*pic);
+ } else { /* level triggered */
+ if (bonito_state->regs[BONITO_INTPOL] & (1 << internal_irq)) {
+ qemu_irq_raise(*pic);
+ } else {
+ qemu_irq_lower(*pic);
+ }
+ }
+}
+
+/* map the original irq (0~3) to bonito irq (16~47, but 16~31 are unused) */
+static int pci_bonito_map_irq(PCIDevice * pci_dev, int irq_num)
+{
+ int slot;
+
+ slot = (pci_dev->devfn >> 3);
+
+ switch (slot) {
+ case 5: /* FULONG2E_VIA_SLOT, SouthBridge, IDE, USB, ACPI, AC97, MC97 */
+ return irq_num % 4 + BONITO_IRQ_BASE;
+ case 6: /* FULONG2E_ATI_SLOT, VGA */
+ return 4 + BONITO_IRQ_BASE;
+ case 7: /* FULONG2E_RTL_SLOT, RTL8139 */
+ return 5 + BONITO_IRQ_BASE;
+ case 8 ... 12: /* PCI slot 1 to 4 */
+ return (slot - 8 + irq_num) + 6 + BONITO_IRQ_BASE;
+ default: /* Unknown device, don't do any translation */
+ return irq_num;
+ }
+}
+
+static void bonito_reset(void *opaque)
+{
+ PCIBonitoState *s = opaque;
+
+ /* set the default value of north bridge registers */
+
+ s->regs[BONITO_BONPONCFG] = 0xc40;
+ s->regs[BONITO_BONGENCFG] = 0x1384;
+ s->regs[BONITO_IODEVCFG] = 0x2bff8010;
+ s->regs[BONITO_SDCFG] = 0x255e0091;
+
+ s->regs[BONITO_GPIODATA] = 0x1ff;
+ s->regs[BONITO_GPIOIE] = 0x1ff;
+ s->regs[BONITO_DQCFG] = 0x8;
+ s->regs[BONITO_MEMSIZE] = 0x10000000;
+ s->regs[BONITO_PCIMAP] = 0x6140;
+}
+
+static const VMStateDescription vmstate_bonito = {
+ .name = "Bonito",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCIBonitoState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int bonito_pcihost_initfn(SysBusDevice *dev)
+{
+ PCIHostState *phb = PCI_HOST_BRIDGE(dev);
+
+ phb->bus = pci_register_bus(DEVICE(dev), "pci",
+ pci_bonito_set_irq, pci_bonito_map_irq, dev,
+ get_system_memory(), get_system_io(),
+ 0x28, 32, TYPE_PCI_BUS);
+
+ return 0;
+}
+
+static void bonito_realize(PCIDevice *dev, Error **errp)
+{
+ PCIBonitoState *s = DO_UPCAST(PCIBonitoState, dev, dev);
+ SysBusDevice *sysbus = SYS_BUS_DEVICE(s->pcihost);
+ PCIHostState *phb = PCI_HOST_BRIDGE(s->pcihost);
+
+ /* Bonito North Bridge, built on FPGA, VENDOR_ID/DEVICE_ID are "undefined" */
+ pci_config_set_prog_interface(dev->config, 0x00);
+
+ /* set the north bridge register mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &bonito_ops, s,
+ "north-bridge-register", BONITO_INTERNAL_REG_SIZE);
+ sysbus_init_mmio(sysbus, &s->iomem);
+ sysbus_mmio_map(sysbus, 0, BONITO_INTERNAL_REG_BASE);
+
+ /* set the north bridge pci configure mapping */
+ memory_region_init_io(&phb->conf_mem, OBJECT(s), &bonito_pciconf_ops, s,
+ "north-bridge-pci-config", BONITO_PCICONFIG_SIZE);
+ sysbus_init_mmio(sysbus, &phb->conf_mem);
+ sysbus_mmio_map(sysbus, 1, BONITO_PCICONFIG_BASE);
+
+ /* set the south bridge pci configure mapping */
+ memory_region_init_io(&phb->data_mem, OBJECT(s), &bonito_spciconf_ops, s,
+ "south-bridge-pci-config", BONITO_SPCICONFIG_SIZE);
+ sysbus_init_mmio(sysbus, &phb->data_mem);
+ sysbus_mmio_map(sysbus, 2, BONITO_SPCICONFIG_BASE);
+
+ memory_region_init_io(&s->iomem_ldma, OBJECT(s), &bonito_ldma_ops, s,
+ "ldma", 0x100);
+ sysbus_init_mmio(sysbus, &s->iomem_ldma);
+ sysbus_mmio_map(sysbus, 3, 0xbfe00200);
+
+ memory_region_init_io(&s->iomem_cop, OBJECT(s), &bonito_cop_ops, s,
+ "cop", 0x100);
+ sysbus_init_mmio(sysbus, &s->iomem_cop);
+ sysbus_mmio_map(sysbus, 4, 0xbfe00300);
+
+ /* Map PCI IO Space 0x1fd0 0000 - 0x1fd1 0000 */
+ memory_region_init_alias(&s->bonito_pciio, OBJECT(s), "isa_mmio",
+ get_system_io(), 0, BONITO_PCIIO_SIZE);
+ sysbus_init_mmio(sysbus, &s->bonito_pciio);
+ sysbus_mmio_map(sysbus, 5, BONITO_PCIIO_BASE);
+
+ /* add pci local io mapping */
+ memory_region_init_alias(&s->bonito_localio, OBJECT(s), "isa_mmio",
+ get_system_io(), 0, BONITO_DEV_SIZE);
+ sysbus_init_mmio(sysbus, &s->bonito_localio);
+ sysbus_mmio_map(sysbus, 6, BONITO_DEV_BASE);
+
+ /* set the default value of north bridge pci config */
+ pci_set_word(dev->config + PCI_COMMAND, 0x0000);
+ pci_set_word(dev->config + PCI_STATUS, 0x0000);
+ pci_set_word(dev->config + PCI_SUBSYSTEM_VENDOR_ID, 0x0000);
+ pci_set_word(dev->config + PCI_SUBSYSTEM_ID, 0x0000);
+
+ pci_set_byte(dev->config + PCI_INTERRUPT_LINE, 0x00);
+ pci_set_byte(dev->config + PCI_INTERRUPT_PIN, 0x01);
+ pci_set_byte(dev->config + PCI_MIN_GNT, 0x3c);
+ pci_set_byte(dev->config + PCI_MAX_LAT, 0x00);
+
+ qemu_register_reset(bonito_reset, s);
+}
+
+PCIBus *bonito_init(qemu_irq *pic)
+{
+ DeviceState *dev;
+ BonitoState *pcihost;
+ PCIHostState *phb;
+ PCIBonitoState *s;
+ PCIDevice *d;
+
+ dev = qdev_create(NULL, TYPE_BONITO_PCI_HOST_BRIDGE);
+ phb = PCI_HOST_BRIDGE(dev);
+ pcihost = BONITO_PCI_HOST_BRIDGE(dev);
+ pcihost->pic = pic;
+ qdev_init_nofail(dev);
+
+ /* set the pcihost pointer before bonito_initfn is called */
+ d = pci_create(phb->bus, PCI_DEVFN(0, 0), "Bonito");
+ s = DO_UPCAST(PCIBonitoState, dev, d);
+ s->pcihost = pcihost;
+ pcihost->pci_dev = s;
+ qdev_init_nofail(DEVICE(d));
+
+ return phb->bus;
+}
+
+static void bonito_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = bonito_realize;
+ k->vendor_id = 0xdf53;
+ k->device_id = 0x00d5;
+ k->revision = 0x01;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ dc->desc = "Host bridge";
+ dc->vmsd = &vmstate_bonito;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo bonito_info = {
+ .name = "Bonito",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIBonitoState),
+ .class_init = bonito_class_init,
+};
+
+static void bonito_pcihost_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = bonito_pcihost_initfn;
+}
+
+static const TypeInfo bonito_pcihost_info = {
+ .name = TYPE_BONITO_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(BonitoState),
+ .class_init = bonito_pcihost_class_init,
+};
+
+static void bonito_register_types(void)
+{
+ type_register_static(&bonito_pcihost_info);
+ type_register_static(&bonito_info);
+}
+
+type_init(bonito_register_types)
diff --git a/hw/pci-host/gpex.c b/hw/pci-host/gpex.c
new file mode 100644
index 00000000..9d8fb5a4
--- /dev/null
+++ b/hw/pci-host/gpex.c
@@ -0,0 +1,154 @@
+/*
+ * QEMU Generic PCI Express Bridge Emulation
+ *
+ * Copyright (C) 2015 Alexander Graf <agraf@suse.de>
+ *
+ * Code loosely based on q35.c.
+ *
+ * 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.
+ *
+ * Check out these documents for more information on the device:
+ *
+ * http://www.kernel.org/doc/Documentation/devicetree/bindings/pci/host-generic-pci.txt
+ * http://www.firmware.org/1275/practice/imap/imap0_9d.pdf
+ */
+#include "hw/hw.h"
+#include "hw/pci-host/gpex.h"
+
+/****************************************************************************
+ * GPEX host
+ */
+
+static void gpex_set_irq(void *opaque, int irq_num, int level)
+{
+ GPEXHost *s = opaque;
+
+ qemu_set_irq(s->irq[irq_num], level);
+}
+
+static void gpex_host_realize(DeviceState *dev, Error **errp)
+{
+ PCIHostState *pci = PCI_HOST_BRIDGE(dev);
+ GPEXHost *s = GPEX_HOST(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ PCIExpressHost *pex = PCIE_HOST_BRIDGE(dev);
+ int i;
+
+ pcie_host_mmcfg_init(pex, PCIE_MMCFG_SIZE_MAX);
+ memory_region_init(&s->io_mmio, OBJECT(s), "gpex_mmio", UINT64_MAX);
+ memory_region_init(&s->io_ioport, OBJECT(s), "gpex_ioport", 64 * 1024);
+
+ sysbus_init_mmio(sbd, &pex->mmio);
+ sysbus_init_mmio(sbd, &s->io_mmio);
+ sysbus_init_mmio(sbd, &s->io_ioport);
+ for (i = 0; i < GPEX_NUM_IRQS; i++) {
+ sysbus_init_irq(sbd, &s->irq[i]);
+ }
+
+ pci->bus = pci_register_bus(dev, "pcie.0", gpex_set_irq,
+ pci_swizzle_map_irq_fn, s, &s->io_mmio,
+ &s->io_ioport, 0, 4, TYPE_PCIE_BUS);
+
+ qdev_set_parent_bus(DEVICE(&s->gpex_root), BUS(pci->bus));
+ qdev_init_nofail(DEVICE(&s->gpex_root));
+}
+
+static const char *gpex_host_root_bus_path(PCIHostState *host_bridge,
+ PCIBus *rootbus)
+{
+ return "0000:00";
+}
+
+static void gpex_host_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass);
+
+ hc->root_bus_path = gpex_host_root_bus_path;
+ dc->realize = gpex_host_realize;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->fw_name = "pci";
+}
+
+static void gpex_host_initfn(Object *obj)
+{
+ GPEXHost *s = GPEX_HOST(obj);
+ GPEXRootState *root = &s->gpex_root;
+
+ object_initialize(root, sizeof(*root), TYPE_GPEX_ROOT_DEVICE);
+ object_property_add_child(obj, "gpex_root", OBJECT(root), NULL);
+ qdev_prop_set_uint32(DEVICE(root), "addr", PCI_DEVFN(0, 0));
+ qdev_prop_set_bit(DEVICE(root), "multifunction", false);
+}
+
+static const TypeInfo gpex_host_info = {
+ .name = TYPE_GPEX_HOST,
+ .parent = TYPE_PCIE_HOST_BRIDGE,
+ .instance_size = sizeof(GPEXHost),
+ .instance_init = gpex_host_initfn,
+ .class_init = gpex_host_class_init,
+};
+
+/****************************************************************************
+ * GPEX Root D0:F0
+ */
+
+static const VMStateDescription vmstate_gpex_root = {
+ .name = "gpex_root",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, GPEXRootState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void gpex_root_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->desc = "QEMU generic PCIe host bridge";
+ dc->vmsd = &vmstate_gpex_root;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT;
+ k->device_id = PCI_DEVICE_ID_REDHAT_PCIE_HOST;
+ k->revision = 0;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo gpex_root_info = {
+ .name = TYPE_GPEX_ROOT_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(GPEXRootState),
+ .class_init = gpex_root_class_init,
+};
+
+static void gpex_register(void)
+{
+ type_register_static(&gpex_root_info);
+ type_register_static(&gpex_host_info);
+}
+
+type_init(gpex_register)
diff --git a/hw/pci-host/grackle.c b/hw/pci-host/grackle.c
new file mode 100644
index 00000000..bfe707a1
--- /dev/null
+++ b/hw/pci-host/grackle.c
@@ -0,0 +1,166 @@
+/*
+ * QEMU Grackle PCI host (heathrow OldWorld PowerMac)
+ *
+ * Copyright (c) 2006-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+
+#include "hw/pci/pci_host.h"
+#include "hw/ppc/mac.h"
+#include "hw/pci/pci.h"
+
+/* debug Grackle */
+//#define DEBUG_GRACKLE
+
+#ifdef DEBUG_GRACKLE
+#define GRACKLE_DPRINTF(fmt, ...) \
+ do { printf("GRACKLE: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define GRACKLE_DPRINTF(fmt, ...)
+#endif
+
+#define GRACKLE_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(GrackleState, (obj), TYPE_GRACKLE_PCI_HOST_BRIDGE)
+
+typedef struct GrackleState {
+ PCIHostState parent_obj;
+
+ MemoryRegion pci_mmio;
+ MemoryRegion pci_hole;
+} GrackleState;
+
+/* Don't know if this matches real hardware, but it agrees with OHW. */
+static int pci_grackle_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ return (irq_num + (pci_dev->devfn >> 3)) & 3;
+}
+
+static void pci_grackle_set_irq(void *opaque, int irq_num, int level)
+{
+ qemu_irq *pic = opaque;
+
+ GRACKLE_DPRINTF("set_irq num %d level %d\n", irq_num, level);
+ qemu_set_irq(pic[irq_num + 0x15], level);
+}
+
+PCIBus *pci_grackle_init(uint32_t base, qemu_irq *pic,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ PCIHostState *phb;
+ GrackleState *d;
+
+ dev = qdev_create(NULL, TYPE_GRACKLE_PCI_HOST_BRIDGE);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ phb = PCI_HOST_BRIDGE(dev);
+ d = GRACKLE_PCI_HOST_BRIDGE(dev);
+
+ memory_region_init(&d->pci_mmio, OBJECT(s), "pci-mmio", 0x100000000ULL);
+ memory_region_init_alias(&d->pci_hole, OBJECT(s), "pci-hole", &d->pci_mmio,
+ 0x80000000ULL, 0x7e000000ULL);
+ memory_region_add_subregion(address_space_mem, 0x80000000ULL,
+ &d->pci_hole);
+
+ phb->bus = pci_register_bus(dev, NULL,
+ pci_grackle_set_irq,
+ pci_grackle_map_irq,
+ pic,
+ &d->pci_mmio,
+ address_space_io,
+ 0, 4, TYPE_PCI_BUS);
+
+ pci_create_simple(phb->bus, 0, "grackle");
+
+ sysbus_mmio_map(s, 0, base);
+ sysbus_mmio_map(s, 1, base + 0x00200000);
+
+ return phb->bus;
+}
+
+static int pci_grackle_init_device(SysBusDevice *dev)
+{
+ PCIHostState *phb;
+
+ phb = PCI_HOST_BRIDGE(dev);
+
+ memory_region_init_io(&phb->conf_mem, OBJECT(dev), &pci_host_conf_le_ops,
+ dev, "pci-conf-idx", 0x1000);
+ memory_region_init_io(&phb->data_mem, OBJECT(dev), &pci_host_data_le_ops,
+ dev, "pci-data-idx", 0x1000);
+ sysbus_init_mmio(dev, &phb->conf_mem);
+ sysbus_init_mmio(dev, &phb->data_mem);
+
+ return 0;
+}
+
+static void grackle_pci_host_realize(PCIDevice *d, Error **errp)
+{
+ d->config[0x09] = 0x01;
+}
+
+static void grackle_pci_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = grackle_pci_host_realize;
+ k->vendor_id = PCI_VENDOR_ID_MOTOROLA;
+ k->device_id = PCI_DEVICE_ID_MOTOROLA_MPC106;
+ k->revision = 0x00;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo grackle_pci_info = {
+ .name = "grackle",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = grackle_pci_class_init,
+};
+
+static void pci_grackle_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pci_grackle_init_device;
+}
+
+static const TypeInfo grackle_pci_host_info = {
+ .name = TYPE_GRACKLE_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(GrackleState),
+ .class_init = pci_grackle_class_init,
+};
+
+static void grackle_register_types(void)
+{
+ type_register_static(&grackle_pci_info);
+ type_register_static(&grackle_pci_host_info);
+}
+
+type_init(grackle_register_types)
diff --git a/hw/pci-host/pam.c b/hw/pci-host/pam.c
new file mode 100644
index 00000000..17d826cb
--- /dev/null
+++ b/hw/pci-host/pam.c
@@ -0,0 +1,69 @@
+/*
+ * QEMU Smram/pam logic implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2011 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (c) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * Split out from piix.c
+ *
+ * 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.
+ */
+
+#include "qom/object.h"
+#include "sysemu/sysemu.h"
+#include "hw/pci-host/pam.h"
+
+void init_pam(DeviceState *dev, MemoryRegion *ram_memory,
+ MemoryRegion *system_memory, MemoryRegion *pci_address_space,
+ PAMMemoryRegion *mem, uint32_t start, uint32_t size)
+{
+ int i;
+
+ /* RAM */
+ memory_region_init_alias(&mem->alias[3], OBJECT(dev), "pam-ram", ram_memory,
+ start, size);
+ /* ROM (XXX: not quite correct) */
+ memory_region_init_alias(&mem->alias[1], OBJECT(dev), "pam-rom", ram_memory,
+ start, size);
+ memory_region_set_readonly(&mem->alias[1], true);
+
+ /* XXX: should distinguish read/write cases */
+ memory_region_init_alias(&mem->alias[0], OBJECT(dev), "pam-pci", pci_address_space,
+ start, size);
+ memory_region_init_alias(&mem->alias[2], OBJECT(dev), "pam-pci", ram_memory,
+ start, size);
+
+ for (i = 0; i < 4; ++i) {
+ memory_region_set_enabled(&mem->alias[i], false);
+ memory_region_add_subregion_overlap(system_memory, start,
+ &mem->alias[i], 1);
+ }
+ mem->current = 0;
+}
+
+void pam_update(PAMMemoryRegion *pam, int idx, uint8_t val)
+{
+ assert(0 <= idx && idx <= 12);
+
+ memory_region_set_enabled(&pam->alias[pam->current], false);
+ pam->current = (val >> ((!(idx & 1)) * 4)) & PAM_ATTR_MASK;
+ memory_region_set_enabled(&pam->alias[pam->current], true);
+}
diff --git a/hw/pci-host/piix.c b/hw/pci-host/piix.c
new file mode 100644
index 00000000..ad55f996
--- /dev/null
+++ b/hw/pci-host/piix.c
@@ -0,0 +1,785 @@
+/*
+ * QEMU i440FX/PIIX3 PCI Bridge Emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "hw/isa/isa.h"
+#include "hw/sysbus.h"
+#include "qemu/range.h"
+#include "hw/xen/xen.h"
+#include "hw/pci-host/pam.h"
+#include "sysemu/sysemu.h"
+#include "hw/i386/ioapic.h"
+#include "qapi/visitor.h"
+
+/*
+ * I440FX chipset data sheet.
+ * http://download.intel.com/design/chipsets/datashts/29054901.pdf
+ */
+
+#define TYPE_I440FX_PCI_HOST_BRIDGE "i440FX-pcihost"
+#define I440FX_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(I440FXState, (obj), TYPE_I440FX_PCI_HOST_BRIDGE)
+
+typedef struct I440FXState {
+ PCIHostState parent_obj;
+ PcPciInfo pci_info;
+ uint64_t pci_hole64_size;
+ uint32_t short_root_bus;
+} I440FXState;
+
+#define PIIX_NUM_PIC_IRQS 16 /* i8259 * 2 */
+#define PIIX_NUM_PIRQS 4ULL /* PIRQ[A-D] */
+#define XEN_PIIX_NUM_PIRQS 128ULL
+#define PIIX_PIRQC 0x60
+
+/*
+ * Reset Control Register: PCI-accessible ISA-Compatible Register at address
+ * 0xcf9, provided by the PCI/ISA bridge (PIIX3 PCI function 0, 8086:7000).
+ */
+#define RCR_IOPORT 0xcf9
+
+typedef struct PIIX3State {
+ PCIDevice dev;
+
+ /*
+ * bitmap to track pic levels.
+ * The pic level is the logical OR of all the PCI irqs mapped to it
+ * So one PIC level is tracked by PIIX_NUM_PIRQS bits.
+ *
+ * PIRQ is mapped to PIC pins, we track it by
+ * PIIX_NUM_PIRQS * PIIX_NUM_PIC_IRQS = 64 bits with
+ * pic_irq * PIIX_NUM_PIRQS + pirq
+ */
+#if PIIX_NUM_PIC_IRQS * PIIX_NUM_PIRQS > 64
+#error "unable to encode pic state in 64bit in pic_levels."
+#endif
+ uint64_t pic_levels;
+
+ qemu_irq *pic;
+
+ /* This member isn't used. Just for save/load compatibility */
+ int32_t pci_irq_levels_vmstate[PIIX_NUM_PIRQS];
+
+ /* Reset Control Register contents */
+ uint8_t rcr;
+
+ /* IO memory region for Reset Control Register (RCR_IOPORT) */
+ MemoryRegion rcr_mem;
+} PIIX3State;
+
+#define TYPE_PIIX3_PCI_DEVICE "pci-piix3"
+#define PIIX3_PCI_DEVICE(obj) \
+ OBJECT_CHECK(PIIX3State, (obj), TYPE_PIIX3_PCI_DEVICE)
+
+#define TYPE_I440FX_PCI_DEVICE "i440FX"
+#define I440FX_PCI_DEVICE(obj) \
+ OBJECT_CHECK(PCII440FXState, (obj), TYPE_I440FX_PCI_DEVICE)
+
+struct PCII440FXState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion *system_memory;
+ MemoryRegion *pci_address_space;
+ MemoryRegion *ram_memory;
+ PAMMemoryRegion pam_regions[13];
+ MemoryRegion smram_region;
+ MemoryRegion smram, low_smram;
+};
+
+
+#define I440FX_PAM 0x59
+#define I440FX_PAM_SIZE 7
+#define I440FX_SMRAM 0x72
+
+static void piix3_set_irq(void *opaque, int pirq, int level);
+static PCIINTxRoute piix3_route_intx_pin_to_irq(void *opaque, int pci_intx);
+static void piix3_write_config_xen(PCIDevice *dev,
+ uint32_t address, uint32_t val, int len);
+
+/* return the global irq number corresponding to a given device irq
+ pin. We could also use the bus number to have a more precise
+ mapping. */
+static int pci_slot_get_pirq(PCIDevice *pci_dev, int pci_intx)
+{
+ int slot_addend;
+ slot_addend = (pci_dev->devfn >> 3) - 1;
+ return (pci_intx + slot_addend) & 3;
+}
+
+static void i440fx_update_memory_mappings(PCII440FXState *d)
+{
+ int i;
+ PCIDevice *pd = PCI_DEVICE(d);
+
+ memory_region_transaction_begin();
+ for (i = 0; i < 13; i++) {
+ pam_update(&d->pam_regions[i], i,
+ pd->config[I440FX_PAM + ((i + 1) / 2)]);
+ }
+ memory_region_set_enabled(&d->smram_region,
+ !(pd->config[I440FX_SMRAM] & SMRAM_D_OPEN));
+ memory_region_set_enabled(&d->smram,
+ pd->config[I440FX_SMRAM] & SMRAM_G_SMRAME);
+ memory_region_transaction_commit();
+}
+
+
+static void i440fx_write_config(PCIDevice *dev,
+ uint32_t address, uint32_t val, int len)
+{
+ PCII440FXState *d = I440FX_PCI_DEVICE(dev);
+
+ /* XXX: implement SMRAM.D_LOCK */
+ pci_default_write_config(dev, address, val, len);
+ if (ranges_overlap(address, len, I440FX_PAM, I440FX_PAM_SIZE) ||
+ range_covers_byte(address, len, I440FX_SMRAM)) {
+ i440fx_update_memory_mappings(d);
+ }
+}
+
+static int i440fx_load_old(QEMUFile* f, void *opaque, int version_id)
+{
+ PCII440FXState *d = opaque;
+ PCIDevice *pd = PCI_DEVICE(d);
+ int ret, i;
+ uint8_t smm_enabled;
+
+ ret = pci_device_load(pd, f);
+ if (ret < 0)
+ return ret;
+ i440fx_update_memory_mappings(d);
+ qemu_get_8s(f, &smm_enabled);
+
+ if (version_id == 2) {
+ for (i = 0; i < PIIX_NUM_PIRQS; i++) {
+ qemu_get_be32(f); /* dummy load for compatibility */
+ }
+ }
+
+ return 0;
+}
+
+static int i440fx_post_load(void *opaque, int version_id)
+{
+ PCII440FXState *d = opaque;
+
+ i440fx_update_memory_mappings(d);
+ return 0;
+}
+
+static const VMStateDescription vmstate_i440fx = {
+ .name = "I440FX",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .minimum_version_id_old = 1,
+ .load_state_old = i440fx_load_old,
+ .post_load = i440fx_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PCII440FXState),
+ /* Used to be smm_enabled, which was basically always zero because
+ * SeaBIOS hardly uses SMM. SMRAM is now handled by CPU code.
+ */
+ VMSTATE_UNUSED(1),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void i440fx_pcihost_get_pci_hole_start(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ I440FXState *s = I440FX_PCI_HOST_BRIDGE(obj);
+ uint32_t value = s->pci_info.w32.begin;
+
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static void i440fx_pcihost_get_pci_hole_end(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ I440FXState *s = I440FX_PCI_HOST_BRIDGE(obj);
+ uint32_t value = s->pci_info.w32.end;
+
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static void i440fx_pcihost_get_pci_hole64_start(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ PCIHostState *h = PCI_HOST_BRIDGE(obj);
+ Range w64;
+
+ pci_bus_get_w64_range(h->bus, &w64);
+
+ visit_type_uint64(v, &w64.begin, name, errp);
+}
+
+static void i440fx_pcihost_get_pci_hole64_end(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ PCIHostState *h = PCI_HOST_BRIDGE(obj);
+ Range w64;
+
+ pci_bus_get_w64_range(h->bus, &w64);
+
+ visit_type_uint64(v, &w64.end, name, errp);
+}
+
+static void i440fx_pcihost_initfn(Object *obj)
+{
+ PCIHostState *s = PCI_HOST_BRIDGE(obj);
+ I440FXState *d = I440FX_PCI_HOST_BRIDGE(obj);
+
+ memory_region_init_io(&s->conf_mem, obj, &pci_host_conf_le_ops, s,
+ "pci-conf-idx", 4);
+ memory_region_init_io(&s->data_mem, obj, &pci_host_data_le_ops, s,
+ "pci-conf-data", 4);
+
+ object_property_add(obj, PCI_HOST_PROP_PCI_HOLE_START, "int",
+ i440fx_pcihost_get_pci_hole_start,
+ NULL, NULL, NULL, NULL);
+
+ object_property_add(obj, PCI_HOST_PROP_PCI_HOLE_END, "int",
+ i440fx_pcihost_get_pci_hole_end,
+ NULL, NULL, NULL, NULL);
+
+ object_property_add(obj, PCI_HOST_PROP_PCI_HOLE64_START, "int",
+ i440fx_pcihost_get_pci_hole64_start,
+ NULL, NULL, NULL, NULL);
+
+ object_property_add(obj, PCI_HOST_PROP_PCI_HOLE64_END, "int",
+ i440fx_pcihost_get_pci_hole64_end,
+ NULL, NULL, NULL, NULL);
+
+ d->pci_info.w32.end = IO_APIC_DEFAULT_ADDRESS;
+}
+
+static void i440fx_pcihost_realize(DeviceState *dev, Error **errp)
+{
+ PCIHostState *s = PCI_HOST_BRIDGE(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ sysbus_add_io(sbd, 0xcf8, &s->conf_mem);
+ sysbus_init_ioports(sbd, 0xcf8, 4);
+
+ sysbus_add_io(sbd, 0xcfc, &s->data_mem);
+ sysbus_init_ioports(sbd, 0xcfc, 4);
+}
+
+static void i440fx_realize(PCIDevice *dev, Error **errp)
+{
+ dev->config[I440FX_SMRAM] = 0x02;
+}
+
+PCIBus *i440fx_init(PCII440FXState **pi440fx_state,
+ int *piix3_devfn,
+ ISABus **isa_bus, qemu_irq *pic,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io,
+ ram_addr_t ram_size,
+ ram_addr_t below_4g_mem_size,
+ ram_addr_t above_4g_mem_size,
+ MemoryRegion *pci_address_space,
+ MemoryRegion *ram_memory)
+{
+ DeviceState *dev;
+ PCIBus *b;
+ PCIDevice *d;
+ PCIHostState *s;
+ PIIX3State *piix3;
+ PCII440FXState *f;
+ unsigned i;
+ I440FXState *i440fx;
+
+ dev = qdev_create(NULL, TYPE_I440FX_PCI_HOST_BRIDGE);
+ s = PCI_HOST_BRIDGE(dev);
+ b = pci_bus_new(dev, NULL, pci_address_space,
+ address_space_io, 0, TYPE_PCI_BUS);
+ s->bus = b;
+ object_property_add_child(qdev_get_machine(), "i440fx", OBJECT(dev), NULL);
+ qdev_init_nofail(dev);
+
+ d = pci_create_simple(b, 0, TYPE_I440FX_PCI_DEVICE);
+ *pi440fx_state = I440FX_PCI_DEVICE(d);
+ f = *pi440fx_state;
+ f->system_memory = address_space_mem;
+ f->pci_address_space = pci_address_space;
+ f->ram_memory = ram_memory;
+
+ i440fx = I440FX_PCI_HOST_BRIDGE(dev);
+ i440fx->pci_info.w32.begin = below_4g_mem_size;
+
+ /* setup pci memory mapping */
+ pc_pci_as_mapping_init(OBJECT(f), f->system_memory,
+ f->pci_address_space);
+
+ /* if *disabled* show SMRAM to all CPUs */
+ memory_region_init_alias(&f->smram_region, OBJECT(d), "smram-region",
+ f->pci_address_space, 0xa0000, 0x20000);
+ memory_region_add_subregion_overlap(f->system_memory, 0xa0000,
+ &f->smram_region, 1);
+ memory_region_set_enabled(&f->smram_region, true);
+
+ /* smram, as seen by SMM CPUs */
+ memory_region_init(&f->smram, OBJECT(d), "smram", 1ull << 32);
+ memory_region_set_enabled(&f->smram, true);
+ memory_region_init_alias(&f->low_smram, OBJECT(d), "smram-low",
+ f->ram_memory, 0xa0000, 0x20000);
+ memory_region_set_enabled(&f->low_smram, true);
+ memory_region_add_subregion(&f->smram, 0xa0000, &f->low_smram);
+ object_property_add_const_link(qdev_get_machine(), "smram",
+ OBJECT(&f->smram), &error_abort);
+
+ init_pam(dev, f->ram_memory, f->system_memory, f->pci_address_space,
+ &f->pam_regions[0], PAM_BIOS_BASE, PAM_BIOS_SIZE);
+ for (i = 0; i < 12; ++i) {
+ init_pam(dev, f->ram_memory, f->system_memory, f->pci_address_space,
+ &f->pam_regions[i+1], PAM_EXPAN_BASE + i * PAM_EXPAN_SIZE,
+ PAM_EXPAN_SIZE);
+ }
+
+ /* Xen supports additional interrupt routes from the PCI devices to
+ * the IOAPIC: the four pins of each PCI device on the bus are also
+ * connected to the IOAPIC directly.
+ * These additional routes can be discovered through ACPI. */
+ if (xen_enabled()) {
+ PCIDevice *pci_dev = pci_create_simple_multifunction(b,
+ -1, true, "PIIX3-xen");
+ piix3 = PIIX3_PCI_DEVICE(pci_dev);
+ pci_bus_irqs(b, xen_piix3_set_irq, xen_pci_slot_get_pirq,
+ piix3, XEN_PIIX_NUM_PIRQS);
+ } else {
+ PCIDevice *pci_dev = pci_create_simple_multifunction(b,
+ -1, true, "PIIX3");
+ piix3 = PIIX3_PCI_DEVICE(pci_dev);
+ pci_bus_irqs(b, piix3_set_irq, pci_slot_get_pirq, piix3,
+ PIIX_NUM_PIRQS);
+ pci_bus_set_route_irq_fn(b, piix3_route_intx_pin_to_irq);
+ }
+ piix3->pic = pic;
+ *isa_bus = ISA_BUS(qdev_get_child_bus(DEVICE(piix3), "isa.0"));
+
+ *piix3_devfn = piix3->dev.devfn;
+
+ ram_size = ram_size / 8 / 1024 / 1024;
+ if (ram_size > 255) {
+ ram_size = 255;
+ }
+ d->config[0x57] = ram_size;
+
+ i440fx_update_memory_mappings(f);
+
+ return b;
+}
+
+PCIBus *find_i440fx(void)
+{
+ PCIHostState *s = OBJECT_CHECK(PCIHostState,
+ object_resolve_path("/machine/i440fx", NULL),
+ TYPE_PCI_HOST_BRIDGE);
+ return s ? s->bus : NULL;
+}
+
+/* PIIX3 PCI to ISA bridge */
+static void piix3_set_irq_pic(PIIX3State *piix3, int pic_irq)
+{
+ qemu_set_irq(piix3->pic[pic_irq],
+ !!(piix3->pic_levels &
+ (((1ULL << PIIX_NUM_PIRQS) - 1) <<
+ (pic_irq * PIIX_NUM_PIRQS))));
+}
+
+static void piix3_set_irq_level_internal(PIIX3State *piix3, int pirq, int level)
+{
+ int pic_irq;
+ uint64_t mask;
+
+ pic_irq = piix3->dev.config[PIIX_PIRQC + pirq];
+ if (pic_irq >= PIIX_NUM_PIC_IRQS) {
+ return;
+ }
+
+ mask = 1ULL << ((pic_irq * PIIX_NUM_PIRQS) + pirq);
+ piix3->pic_levels &= ~mask;
+ piix3->pic_levels |= mask * !!level;
+}
+
+static void piix3_set_irq_level(PIIX3State *piix3, int pirq, int level)
+{
+ int pic_irq;
+
+ pic_irq = piix3->dev.config[PIIX_PIRQC + pirq];
+ if (pic_irq >= PIIX_NUM_PIC_IRQS) {
+ return;
+ }
+
+ piix3_set_irq_level_internal(piix3, pirq, level);
+
+ piix3_set_irq_pic(piix3, pic_irq);
+}
+
+static void piix3_set_irq(void *opaque, int pirq, int level)
+{
+ PIIX3State *piix3 = opaque;
+ piix3_set_irq_level(piix3, pirq, level);
+}
+
+static PCIINTxRoute piix3_route_intx_pin_to_irq(void *opaque, int pin)
+{
+ PIIX3State *piix3 = opaque;
+ int irq = piix3->dev.config[PIIX_PIRQC + pin];
+ PCIINTxRoute route;
+
+ if (irq < PIIX_NUM_PIC_IRQS) {
+ route.mode = PCI_INTX_ENABLED;
+ route.irq = irq;
+ } else {
+ route.mode = PCI_INTX_DISABLED;
+ route.irq = -1;
+ }
+ return route;
+}
+
+/* irq routing is changed. so rebuild bitmap */
+static void piix3_update_irq_levels(PIIX3State *piix3)
+{
+ int pirq;
+
+ piix3->pic_levels = 0;
+ for (pirq = 0; pirq < PIIX_NUM_PIRQS; pirq++) {
+ piix3_set_irq_level(piix3, pirq,
+ pci_bus_get_irq_level(piix3->dev.bus, pirq));
+ }
+}
+
+static void piix3_write_config(PCIDevice *dev,
+ uint32_t address, uint32_t val, int len)
+{
+ pci_default_write_config(dev, address, val, len);
+ if (ranges_overlap(address, len, PIIX_PIRQC, 4)) {
+ PIIX3State *piix3 = PIIX3_PCI_DEVICE(dev);
+ int pic_irq;
+
+ pci_bus_fire_intx_routing_notifier(piix3->dev.bus);
+ piix3_update_irq_levels(piix3);
+ for (pic_irq = 0; pic_irq < PIIX_NUM_PIC_IRQS; pic_irq++) {
+ piix3_set_irq_pic(piix3, pic_irq);
+ }
+ }
+}
+
+static void piix3_write_config_xen(PCIDevice *dev,
+ uint32_t address, uint32_t val, int len)
+{
+ xen_piix_pci_write_config_client(address, val, len);
+ piix3_write_config(dev, address, val, len);
+}
+
+static void piix3_reset(void *opaque)
+{
+ PIIX3State *d = opaque;
+ uint8_t *pci_conf = d->dev.config;
+
+ pci_conf[0x04] = 0x07; /* master, memory and I/O */
+ pci_conf[0x05] = 0x00;
+ pci_conf[0x06] = 0x00;
+ pci_conf[0x07] = 0x02; /* PCI_status_devsel_medium */
+ pci_conf[0x4c] = 0x4d;
+ pci_conf[0x4e] = 0x03;
+ pci_conf[0x4f] = 0x00;
+ pci_conf[0x60] = 0x80;
+ pci_conf[0x61] = 0x80;
+ pci_conf[0x62] = 0x80;
+ pci_conf[0x63] = 0x80;
+ pci_conf[0x69] = 0x02;
+ pci_conf[0x70] = 0x80;
+ pci_conf[0x76] = 0x0c;
+ pci_conf[0x77] = 0x0c;
+ pci_conf[0x78] = 0x02;
+ pci_conf[0x79] = 0x00;
+ pci_conf[0x80] = 0x00;
+ pci_conf[0x82] = 0x00;
+ pci_conf[0xa0] = 0x08;
+ pci_conf[0xa2] = 0x00;
+ pci_conf[0xa3] = 0x00;
+ pci_conf[0xa4] = 0x00;
+ pci_conf[0xa5] = 0x00;
+ pci_conf[0xa6] = 0x00;
+ pci_conf[0xa7] = 0x00;
+ pci_conf[0xa8] = 0x0f;
+ pci_conf[0xaa] = 0x00;
+ pci_conf[0xab] = 0x00;
+ pci_conf[0xac] = 0x00;
+ pci_conf[0xae] = 0x00;
+
+ d->pic_levels = 0;
+ d->rcr = 0;
+}
+
+static int piix3_post_load(void *opaque, int version_id)
+{
+ PIIX3State *piix3 = opaque;
+ int pirq;
+
+ /* Because the i8259 has not been deserialized yet, qemu_irq_raise
+ * might bring the system to a different state than the saved one;
+ * for example, the interrupt could be masked but the i8259 would
+ * not know that yet and would trigger an interrupt in the CPU.
+ *
+ * Here, we update irq levels without raising the interrupt.
+ * Interrupt state will be deserialized separately through the i8259.
+ */
+ piix3->pic_levels = 0;
+ for (pirq = 0; pirq < PIIX_NUM_PIRQS; pirq++) {
+ piix3_set_irq_level_internal(piix3, pirq,
+ pci_bus_get_irq_level(piix3->dev.bus, pirq));
+ }
+ return 0;
+}
+
+static void piix3_pre_save(void *opaque)
+{
+ int i;
+ PIIX3State *piix3 = opaque;
+
+ for (i = 0; i < ARRAY_SIZE(piix3->pci_irq_levels_vmstate); i++) {
+ piix3->pci_irq_levels_vmstate[i] =
+ pci_bus_get_irq_level(piix3->dev.bus, i);
+ }
+}
+
+static bool piix3_rcr_needed(void *opaque)
+{
+ PIIX3State *piix3 = opaque;
+
+ return (piix3->rcr != 0);
+}
+
+static const VMStateDescription vmstate_piix3_rcr = {
+ .name = "PIIX3/rcr",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = piix3_rcr_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(rcr, PIIX3State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_piix3 = {
+ .name = "PIIX3",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .post_load = piix3_post_load,
+ .pre_save = piix3_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PIIX3State),
+ VMSTATE_INT32_ARRAY_V(pci_irq_levels_vmstate, PIIX3State,
+ PIIX_NUM_PIRQS, 3),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_piix3_rcr,
+ NULL
+ }
+};
+
+
+static void rcr_write(void *opaque, hwaddr addr, uint64_t val, unsigned len)
+{
+ PIIX3State *d = opaque;
+
+ if (val & 4) {
+ qemu_system_reset_request();
+ return;
+ }
+ d->rcr = val & 2; /* keep System Reset type only */
+}
+
+static uint64_t rcr_read(void *opaque, hwaddr addr, unsigned len)
+{
+ PIIX3State *d = opaque;
+
+ return d->rcr;
+}
+
+static const MemoryRegionOps rcr_ops = {
+ .read = rcr_read,
+ .write = rcr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN
+};
+
+static void piix3_realize(PCIDevice *dev, Error **errp)
+{
+ PIIX3State *d = PIIX3_PCI_DEVICE(dev);
+
+ isa_bus_new(DEVICE(d), get_system_memory(),
+ pci_address_space_io(dev));
+
+ memory_region_init_io(&d->rcr_mem, OBJECT(dev), &rcr_ops, d,
+ "piix3-reset-control", 1);
+ memory_region_add_subregion_overlap(pci_address_space_io(dev), RCR_IOPORT,
+ &d->rcr_mem, 1);
+
+ qemu_register_reset(piix3_reset, d);
+}
+
+static void pci_piix3_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ dc->desc = "ISA bridge";
+ dc->vmsd = &vmstate_piix3;
+ dc->hotpluggable = false;
+ k->realize = piix3_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ /* 82371SB PIIX3 PCI-to-ISA bridge (Step A1) */
+ k->device_id = PCI_DEVICE_ID_INTEL_82371SB_0;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ /*
+ * Reason: part of PIIX3 southbridge, needs to be wired up by
+ * pc_piix.c's pc_init1()
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo piix3_pci_type_info = {
+ .name = TYPE_PIIX3_PCI_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PIIX3State),
+ .abstract = true,
+ .class_init = pci_piix3_class_init,
+};
+
+static void piix3_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->config_write = piix3_write_config;
+}
+
+static const TypeInfo piix3_info = {
+ .name = "PIIX3",
+ .parent = TYPE_PIIX3_PCI_DEVICE,
+ .class_init = piix3_class_init,
+};
+
+static void piix3_xen_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->config_write = piix3_write_config_xen;
+};
+
+static const TypeInfo piix3_xen_info = {
+ .name = "PIIX3-xen",
+ .parent = TYPE_PIIX3_PCI_DEVICE,
+ .class_init = piix3_xen_class_init,
+};
+
+static void i440fx_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = i440fx_realize;
+ k->config_write = i440fx_write_config;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82441;
+ k->revision = 0x02;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ dc->desc = "Host bridge";
+ dc->vmsd = &vmstate_i440fx;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo i440fx_info = {
+ .name = TYPE_I440FX_PCI_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCII440FXState),
+ .class_init = i440fx_class_init,
+};
+
+static const char *i440fx_pcihost_root_bus_path(PCIHostState *host_bridge,
+ PCIBus *rootbus)
+{
+ I440FXState *s = I440FX_PCI_HOST_BRIDGE(host_bridge);
+
+ /* For backwards compat with old device paths */
+ if (s->short_root_bus) {
+ return "0000";
+ }
+ return "0000:00";
+}
+
+static Property i440fx_props[] = {
+ DEFINE_PROP_SIZE(PCI_HOST_PROP_PCI_HOLE64_SIZE, I440FXState,
+ pci_hole64_size, DEFAULT_PCI_HOLE64_SIZE),
+ DEFINE_PROP_UINT32("short_root_bus", I440FXState, short_root_bus, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void i440fx_pcihost_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass);
+
+ hc->root_bus_path = i440fx_pcihost_root_bus_path;
+ dc->realize = i440fx_pcihost_realize;
+ dc->fw_name = "pci";
+ dc->props = i440fx_props;
+}
+
+static const TypeInfo i440fx_pcihost_info = {
+ .name = TYPE_I440FX_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(I440FXState),
+ .instance_init = i440fx_pcihost_initfn,
+ .class_init = i440fx_pcihost_class_init,
+};
+
+static void i440fx_register_types(void)
+{
+ type_register_static(&i440fx_info);
+ type_register_static(&piix3_pci_type_info);
+ type_register_static(&piix3_info);
+ type_register_static(&piix3_xen_info);
+ type_register_static(&i440fx_pcihost_info);
+}
+
+type_init(i440fx_register_types)
diff --git a/hw/pci-host/ppce500.c b/hw/pci-host/ppce500.c
new file mode 100644
index 00000000..613ba73c
--- /dev/null
+++ b/hw/pci-host/ppce500.c
@@ -0,0 +1,550 @@
+/*
+ * QEMU PowerPC E500 embedded processors pci controller emulation
+ *
+ * Copyright (C) 2009 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Yu Liu, <yu.liu@freescale.com>
+ *
+ * This file is derived from hw/ppc4xx_pci.c,
+ * the copyright for that material belongs to the original owners.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/ppc/e500-ccsr.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "qemu/bswap.h"
+#include "hw/pci-host/ppce500.h"
+
+#ifdef DEBUG_PCI
+#define pci_debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
+#else
+#define pci_debug(fmt, ...)
+#endif
+
+#define PCIE500_CFGADDR 0x0
+#define PCIE500_CFGDATA 0x4
+#define PCIE500_REG_BASE 0xC00
+#define PCIE500_ALL_SIZE 0x1000
+#define PCIE500_REG_SIZE (PCIE500_ALL_SIZE - PCIE500_REG_BASE)
+
+#define PCIE500_PCI_IOLEN 0x10000ULL
+
+#define PPCE500_PCI_CONFIG_ADDR 0x0
+#define PPCE500_PCI_CONFIG_DATA 0x4
+#define PPCE500_PCI_INTACK 0x8
+
+#define PPCE500_PCI_OW1 (0xC20 - PCIE500_REG_BASE)
+#define PPCE500_PCI_OW2 (0xC40 - PCIE500_REG_BASE)
+#define PPCE500_PCI_OW3 (0xC60 - PCIE500_REG_BASE)
+#define PPCE500_PCI_OW4 (0xC80 - PCIE500_REG_BASE)
+#define PPCE500_PCI_IW3 (0xDA0 - PCIE500_REG_BASE)
+#define PPCE500_PCI_IW2 (0xDC0 - PCIE500_REG_BASE)
+#define PPCE500_PCI_IW1 (0xDE0 - PCIE500_REG_BASE)
+
+#define PPCE500_PCI_GASKET_TIMR (0xE20 - PCIE500_REG_BASE)
+
+#define PCI_POTAR 0x0
+#define PCI_POTEAR 0x4
+#define PCI_POWBAR 0x8
+#define PCI_POWAR 0x10
+
+#define PCI_PITAR 0x0
+#define PCI_PIWBAR 0x8
+#define PCI_PIWBEAR 0xC
+#define PCI_PIWAR 0x10
+
+#define PPCE500_PCI_NR_POBS 5
+#define PPCE500_PCI_NR_PIBS 3
+
+#define PIWAR_EN 0x80000000 /* Enable */
+#define PIWAR_PF 0x20000000 /* prefetch */
+#define PIWAR_TGI_LOCAL 0x00f00000 /* target - local memory */
+#define PIWAR_READ_SNOOP 0x00050000
+#define PIWAR_WRITE_SNOOP 0x00005000
+#define PIWAR_SZ_MASK 0x0000003f
+
+struct pci_outbound {
+ uint32_t potar;
+ uint32_t potear;
+ uint32_t powbar;
+ uint32_t powar;
+ MemoryRegion mem;
+};
+
+struct pci_inbound {
+ uint32_t pitar;
+ uint32_t piwbar;
+ uint32_t piwbear;
+ uint32_t piwar;
+ MemoryRegion mem;
+};
+
+#define TYPE_PPC_E500_PCI_HOST_BRIDGE "e500-pcihost"
+
+#define PPC_E500_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(PPCE500PCIState, (obj), TYPE_PPC_E500_PCI_HOST_BRIDGE)
+
+struct PPCE500PCIState {
+ PCIHostState parent_obj;
+
+ struct pci_outbound pob[PPCE500_PCI_NR_POBS];
+ struct pci_inbound pib[PPCE500_PCI_NR_PIBS];
+ uint32_t gasket_time;
+ qemu_irq irq[PCI_NUM_PINS];
+ uint32_t irq_num[PCI_NUM_PINS];
+ uint32_t first_slot;
+ uint32_t first_pin_irq;
+ AddressSpace bm_as;
+ MemoryRegion bm;
+ /* mmio maps */
+ MemoryRegion container;
+ MemoryRegion iomem;
+ MemoryRegion pio;
+ MemoryRegion busmem;
+};
+
+#define TYPE_PPC_E500_PCI_BRIDGE "e500-host-bridge"
+#define PPC_E500_PCI_BRIDGE(obj) \
+ OBJECT_CHECK(PPCE500PCIBridgeState, (obj), TYPE_PPC_E500_PCI_BRIDGE)
+
+struct PPCE500PCIBridgeState {
+ /*< private >*/
+ PCIDevice parent;
+ /*< public >*/
+
+ MemoryRegion bar0;
+};
+
+typedef struct PPCE500PCIBridgeState PPCE500PCIBridgeState;
+typedef struct PPCE500PCIState PPCE500PCIState;
+
+static uint64_t pci_reg_read4(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PPCE500PCIState *pci = opaque;
+ unsigned long win;
+ uint32_t value = 0;
+ int idx;
+
+ win = addr & 0xfe0;
+
+ switch (win) {
+ case PPCE500_PCI_OW1:
+ case PPCE500_PCI_OW2:
+ case PPCE500_PCI_OW3:
+ case PPCE500_PCI_OW4:
+ idx = (addr >> 5) & 0x7;
+ switch (addr & 0xC) {
+ case PCI_POTAR:
+ value = pci->pob[idx].potar;
+ break;
+ case PCI_POTEAR:
+ value = pci->pob[idx].potear;
+ break;
+ case PCI_POWBAR:
+ value = pci->pob[idx].powbar;
+ break;
+ case PCI_POWAR:
+ value = pci->pob[idx].powar;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case PPCE500_PCI_IW3:
+ case PPCE500_PCI_IW2:
+ case PPCE500_PCI_IW1:
+ idx = ((addr >> 5) & 0x3) - 1;
+ switch (addr & 0xC) {
+ case PCI_PITAR:
+ value = pci->pib[idx].pitar;
+ break;
+ case PCI_PIWBAR:
+ value = pci->pib[idx].piwbar;
+ break;
+ case PCI_PIWBEAR:
+ value = pci->pib[idx].piwbear;
+ break;
+ case PCI_PIWAR:
+ value = pci->pib[idx].piwar;
+ break;
+ default:
+ break;
+ };
+ break;
+
+ case PPCE500_PCI_GASKET_TIMR:
+ value = pci->gasket_time;
+ break;
+
+ default:
+ break;
+ }
+
+ pci_debug("%s: win:%lx(addr:" TARGET_FMT_plx ") -> value:%x\n", __func__,
+ win, addr, value);
+ return value;
+}
+
+/* DMA mapping */
+static void e500_update_piw(PPCE500PCIState *pci, int idx)
+{
+ uint64_t tar = ((uint64_t)pci->pib[idx].pitar) << 12;
+ uint64_t wbar = ((uint64_t)pci->pib[idx].piwbar) << 12;
+ uint64_t war = pci->pib[idx].piwar;
+ uint64_t size = 2ULL << (war & PIWAR_SZ_MASK);
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *mem = &pci->pib[idx].mem;
+ MemoryRegion *bm = &pci->bm;
+ char *name;
+
+ if (memory_region_is_mapped(mem)) {
+ /* Before we modify anything, unmap and destroy the region */
+ memory_region_del_subregion(bm, mem);
+ object_unparent(OBJECT(mem));
+ }
+
+ if (!(war & PIWAR_EN)) {
+ /* Not enabled, nothing to do */
+ return;
+ }
+
+ name = g_strdup_printf("PCI Inbound Window %d", idx);
+ memory_region_init_alias(mem, OBJECT(pci), name, address_space_mem, tar,
+ size);
+ memory_region_add_subregion_overlap(bm, wbar, mem, -1);
+ g_free(name);
+
+ pci_debug("%s: Added window of size=%#lx from PCI=%#lx to CPU=%#lx\n",
+ __func__, size, wbar, tar);
+}
+
+/* BAR mapping */
+static void e500_update_pow(PPCE500PCIState *pci, int idx)
+{
+ uint64_t tar = ((uint64_t)pci->pob[idx].potar) << 12;
+ uint64_t wbar = ((uint64_t)pci->pob[idx].powbar) << 12;
+ uint64_t war = pci->pob[idx].powar;
+ uint64_t size = 2ULL << (war & PIWAR_SZ_MASK);
+ MemoryRegion *mem = &pci->pob[idx].mem;
+ MemoryRegion *address_space_mem = get_system_memory();
+ char *name;
+
+ if (memory_region_is_mapped(mem)) {
+ /* Before we modify anything, unmap and destroy the region */
+ memory_region_del_subregion(address_space_mem, mem);
+ object_unparent(OBJECT(mem));
+ }
+
+ if (!(war & PIWAR_EN)) {
+ /* Not enabled, nothing to do */
+ return;
+ }
+
+ name = g_strdup_printf("PCI Outbound Window %d", idx);
+ memory_region_init_alias(mem, OBJECT(pci), name, &pci->busmem, tar,
+ size);
+ memory_region_add_subregion(address_space_mem, wbar, mem);
+ g_free(name);
+
+ pci_debug("%s: Added window of size=%#lx from CPU=%#lx to PCI=%#lx\n",
+ __func__, size, wbar, tar);
+}
+
+static void pci_reg_write4(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ PPCE500PCIState *pci = opaque;
+ unsigned long win;
+ int idx;
+
+ win = addr & 0xfe0;
+
+ pci_debug("%s: value:%x -> win:%lx(addr:" TARGET_FMT_plx ")\n",
+ __func__, (unsigned)value, win, addr);
+
+ switch (win) {
+ case PPCE500_PCI_OW1:
+ case PPCE500_PCI_OW2:
+ case PPCE500_PCI_OW3:
+ case PPCE500_PCI_OW4:
+ idx = (addr >> 5) & 0x7;
+ switch (addr & 0x1F) {
+ case PCI_POTAR:
+ pci->pob[idx].potar = value;
+ e500_update_pow(pci, idx);
+ break;
+ case PCI_POTEAR:
+ pci->pob[idx].potear = value;
+ e500_update_pow(pci, idx);
+ break;
+ case PCI_POWBAR:
+ pci->pob[idx].powbar = value;
+ e500_update_pow(pci, idx);
+ break;
+ case PCI_POWAR:
+ pci->pob[idx].powar = value;
+ e500_update_pow(pci, idx);
+ break;
+ default:
+ break;
+ };
+ break;
+
+ case PPCE500_PCI_IW3:
+ case PPCE500_PCI_IW2:
+ case PPCE500_PCI_IW1:
+ idx = ((addr >> 5) & 0x3) - 1;
+ switch (addr & 0x1F) {
+ case PCI_PITAR:
+ pci->pib[idx].pitar = value;
+ e500_update_piw(pci, idx);
+ break;
+ case PCI_PIWBAR:
+ pci->pib[idx].piwbar = value;
+ e500_update_piw(pci, idx);
+ break;
+ case PCI_PIWBEAR:
+ pci->pib[idx].piwbear = value;
+ e500_update_piw(pci, idx);
+ break;
+ case PCI_PIWAR:
+ pci->pib[idx].piwar = value;
+ e500_update_piw(pci, idx);
+ break;
+ default:
+ break;
+ };
+ break;
+
+ case PPCE500_PCI_GASKET_TIMR:
+ pci->gasket_time = value;
+ break;
+
+ default:
+ break;
+ };
+}
+
+static const MemoryRegionOps e500_pci_reg_ops = {
+ .read = pci_reg_read4,
+ .write = pci_reg_write4,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static int mpc85xx_pci_map_irq(PCIDevice *pci_dev, int pin)
+{
+ int devno = pci_dev->devfn >> 3;
+ int ret;
+
+ ret = ppce500_pci_map_irq_slot(devno, pin);
+
+ pci_debug("%s: devfn %x irq %d -> %d devno:%x\n", __func__,
+ pci_dev->devfn, pin, ret, devno);
+
+ return ret;
+}
+
+static void mpc85xx_pci_set_irq(void *opaque, int pin, int level)
+{
+ PPCE500PCIState *s = opaque;
+ qemu_irq *pic = s->irq;
+
+ pci_debug("%s: PCI irq %d, level:%d\n", __func__, pin , level);
+
+ qemu_set_irq(pic[pin], level);
+}
+
+static PCIINTxRoute e500_route_intx_pin_to_irq(void *opaque, int pin)
+{
+ PCIINTxRoute route;
+ PPCE500PCIState *s = opaque;
+
+ route.mode = PCI_INTX_ENABLED;
+ route.irq = s->irq_num[pin];
+
+ pci_debug("%s: PCI irq-pin = %d, irq_num= %d\n", __func__, pin, route.irq);
+ return route;
+}
+
+static const VMStateDescription vmstate_pci_outbound = {
+ .name = "pci_outbound",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(potar, struct pci_outbound),
+ VMSTATE_UINT32(potear, struct pci_outbound),
+ VMSTATE_UINT32(powbar, struct pci_outbound),
+ VMSTATE_UINT32(powar, struct pci_outbound),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_inbound = {
+ .name = "pci_inbound",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(pitar, struct pci_inbound),
+ VMSTATE_UINT32(piwbar, struct pci_inbound),
+ VMSTATE_UINT32(piwbear, struct pci_inbound),
+ VMSTATE_UINT32(piwar, struct pci_inbound),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ppce500_pci = {
+ .name = "ppce500_pci",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(pob, PPCE500PCIState, PPCE500_PCI_NR_POBS, 1,
+ vmstate_pci_outbound, struct pci_outbound),
+ VMSTATE_STRUCT_ARRAY(pib, PPCE500PCIState, PPCE500_PCI_NR_PIBS, 1,
+ vmstate_pci_inbound, struct pci_inbound),
+ VMSTATE_UINT32(gasket_time, PPCE500PCIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#include "exec/address-spaces.h"
+
+static void e500_pcihost_bridge_realize(PCIDevice *d, Error **errp)
+{
+ PPCE500PCIBridgeState *b = PPC_E500_PCI_BRIDGE(d);
+ PPCE500CCSRState *ccsr = CCSR(container_get(qdev_get_machine(),
+ "/e500-ccsr"));
+
+ pci_config_set_class(d->config, PCI_CLASS_BRIDGE_PCI);
+ d->config[PCI_HEADER_TYPE] =
+ (d->config[PCI_HEADER_TYPE] & PCI_HEADER_TYPE_MULTI_FUNCTION) |
+ PCI_HEADER_TYPE_BRIDGE;
+
+ memory_region_init_alias(&b->bar0, OBJECT(ccsr), "e500-pci-bar0", &ccsr->ccsr_space,
+ 0, int128_get64(ccsr->ccsr_space.size));
+ pci_register_bar(d, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &b->bar0);
+}
+
+static AddressSpace *e500_pcihost_set_iommu(PCIBus *bus, void *opaque,
+ int devfn)
+{
+ PPCE500PCIState *s = opaque;
+
+ return &s->bm_as;
+}
+
+static int e500_pcihost_initfn(SysBusDevice *dev)
+{
+ PCIHostState *h;
+ PPCE500PCIState *s;
+ PCIBus *b;
+ int i;
+
+ h = PCI_HOST_BRIDGE(dev);
+ s = PPC_E500_PCI_HOST_BRIDGE(dev);
+
+ for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
+ sysbus_init_irq(dev, &s->irq[i]);
+ }
+
+ for (i = 0; i < PCI_NUM_PINS; i++) {
+ s->irq_num[i] = s->first_pin_irq + i;
+ }
+
+ memory_region_init(&s->pio, OBJECT(s), "pci-pio", PCIE500_PCI_IOLEN);
+ memory_region_init(&s->busmem, OBJECT(s), "pci bus memory", UINT64_MAX);
+
+ /* PIO lives at the bottom of our bus space */
+ memory_region_add_subregion_overlap(&s->busmem, 0, &s->pio, -2);
+
+ b = pci_register_bus(DEVICE(dev), NULL, mpc85xx_pci_set_irq,
+ mpc85xx_pci_map_irq, s, &s->busmem, &s->pio,
+ PCI_DEVFN(s->first_slot, 0), 4, TYPE_PCI_BUS);
+ h->bus = b;
+
+ /* Set up PCI view of memory */
+ memory_region_init(&s->bm, OBJECT(s), "bm-e500", UINT64_MAX);
+ memory_region_add_subregion(&s->bm, 0x0, &s->busmem);
+ address_space_init(&s->bm_as, &s->bm, "pci-bm");
+ pci_setup_iommu(b, e500_pcihost_set_iommu, s);
+
+ pci_create_simple(b, 0, "e500-host-bridge");
+
+ memory_region_init(&s->container, OBJECT(h), "pci-container", PCIE500_ALL_SIZE);
+ memory_region_init_io(&h->conf_mem, OBJECT(h), &pci_host_conf_be_ops, h,
+ "pci-conf-idx", 4);
+ memory_region_init_io(&h->data_mem, OBJECT(h), &pci_host_data_le_ops, h,
+ "pci-conf-data", 4);
+ memory_region_init_io(&s->iomem, OBJECT(s), &e500_pci_reg_ops, s,
+ "pci.reg", PCIE500_REG_SIZE);
+ memory_region_add_subregion(&s->container, PCIE500_CFGADDR, &h->conf_mem);
+ memory_region_add_subregion(&s->container, PCIE500_CFGDATA, &h->data_mem);
+ memory_region_add_subregion(&s->container, PCIE500_REG_BASE, &s->iomem);
+ sysbus_init_mmio(dev, &s->container);
+ pci_bus_set_route_irq_fn(b, e500_route_intx_pin_to_irq);
+
+ return 0;
+}
+
+static void e500_host_bridge_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = e500_pcihost_bridge_realize;
+ k->vendor_id = PCI_VENDOR_ID_FREESCALE;
+ k->device_id = PCI_DEVICE_ID_MPC8533E;
+ k->class_id = PCI_CLASS_PROCESSOR_POWERPC;
+ dc->desc = "Host bridge";
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo e500_host_bridge_info = {
+ .name = "e500-host-bridge",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PPCE500PCIBridgeState),
+ .class_init = e500_host_bridge_class_init,
+};
+
+static Property pcihost_properties[] = {
+ DEFINE_PROP_UINT32("first_slot", PPCE500PCIState, first_slot, 0x11),
+ DEFINE_PROP_UINT32("first_pin_irq", PPCE500PCIState, first_pin_irq, 0x1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void e500_pcihost_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = e500_pcihost_initfn;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->props = pcihost_properties;
+ dc->vmsd = &vmstate_ppce500_pci;
+}
+
+static const TypeInfo e500_pcihost_info = {
+ .name = TYPE_PPC_E500_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(PPCE500PCIState),
+ .class_init = e500_pcihost_class_init,
+};
+
+static void e500_pci_register_types(void)
+{
+ type_register_static(&e500_pcihost_info);
+ type_register_static(&e500_host_bridge_info);
+}
+
+type_init(e500_pci_register_types)
diff --git a/hw/pci-host/prep.c b/hw/pci-host/prep.c
new file mode 100644
index 00000000..c63f45d2
--- /dev/null
+++ b/hw/pci-host/prep.c
@@ -0,0 +1,404 @@
+/*
+ * QEMU PREP PCI host
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2011-2013 Andreas Färber
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pci_host.h"
+#include "hw/i386/pc.h"
+#include "hw/loader.h"
+#include "exec/address-spaces.h"
+#include "elf.h"
+
+#define TYPE_RAVEN_PCI_DEVICE "raven"
+#define TYPE_RAVEN_PCI_HOST_BRIDGE "raven-pcihost"
+
+#define RAVEN_PCI_DEVICE(obj) \
+ OBJECT_CHECK(RavenPCIState, (obj), TYPE_RAVEN_PCI_DEVICE)
+
+typedef struct RavenPCIState {
+ PCIDevice dev;
+
+ uint32_t elf_machine;
+ char *bios_name;
+ MemoryRegion bios;
+} RavenPCIState;
+
+#define RAVEN_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(PREPPCIState, (obj), TYPE_RAVEN_PCI_HOST_BRIDGE)
+
+typedef struct PRePPCIState {
+ PCIHostState parent_obj;
+
+ qemu_irq irq[PCI_NUM_PINS];
+ PCIBus pci_bus;
+ AddressSpace pci_io_as;
+ MemoryRegion pci_io;
+ MemoryRegion pci_io_non_contiguous;
+ MemoryRegion pci_memory;
+ MemoryRegion pci_intack;
+ MemoryRegion bm;
+ MemoryRegion bm_ram_alias;
+ MemoryRegion bm_pci_memory_alias;
+ AddressSpace bm_as;
+ RavenPCIState pci_dev;
+
+ int contiguous_map;
+} PREPPCIState;
+
+#define BIOS_SIZE (1024 * 1024)
+
+static inline uint32_t raven_pci_io_config(hwaddr addr)
+{
+ int i;
+
+ for (i = 0; i < 11; i++) {
+ if ((addr & (1 << (11 + i))) != 0) {
+ break;
+ }
+ }
+ return (addr & 0x7ff) | (i << 11);
+}
+
+static void raven_pci_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ PREPPCIState *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+ pci_data_write(phb->bus, raven_pci_io_config(addr), val, size);
+}
+
+static uint64_t raven_pci_io_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ PREPPCIState *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+ return pci_data_read(phb->bus, raven_pci_io_config(addr), size);
+}
+
+static const MemoryRegionOps raven_pci_io_ops = {
+ .read = raven_pci_io_read,
+ .write = raven_pci_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t raven_intack_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ return pic_read_irq(isa_pic);
+}
+
+static const MemoryRegionOps raven_intack_ops = {
+ .read = raven_intack_read,
+ .valid = {
+ .max_access_size = 1,
+ },
+};
+
+static inline hwaddr raven_io_address(PREPPCIState *s,
+ hwaddr addr)
+{
+ if (s->contiguous_map == 0) {
+ /* 64 KB contiguous space for IOs */
+ addr &= 0xFFFF;
+ } else {
+ /* 8 MB non-contiguous space for IOs */
+ addr = (addr & 0x1F) | ((addr & 0x007FFF000) >> 7);
+ }
+
+ /* FIXME: handle endianness switch */
+
+ return addr;
+}
+
+static uint64_t raven_io_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ PREPPCIState *s = opaque;
+ uint8_t buf[4];
+
+ addr = raven_io_address(s, addr);
+ address_space_read(&s->pci_io_as, addr + 0x80000000,
+ MEMTXATTRS_UNSPECIFIED, buf, size);
+
+ if (size == 1) {
+ return buf[0];
+ } else if (size == 2) {
+ return lduw_le_p(buf);
+ } else if (size == 4) {
+ return ldl_le_p(buf);
+ } else {
+ g_assert_not_reached();
+ }
+}
+
+static void raven_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ PREPPCIState *s = opaque;
+ uint8_t buf[4];
+
+ addr = raven_io_address(s, addr);
+
+ if (size == 1) {
+ buf[0] = val;
+ } else if (size == 2) {
+ stw_le_p(buf, val);
+ } else if (size == 4) {
+ stl_le_p(buf, val);
+ } else {
+ g_assert_not_reached();
+ }
+
+ address_space_write(&s->pci_io_as, addr + 0x80000000,
+ MEMTXATTRS_UNSPECIFIED, buf, size);
+}
+
+static const MemoryRegionOps raven_io_ops = {
+ .read = raven_io_read,
+ .write = raven_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.max_access_size = 4,
+ .valid.unaligned = true,
+};
+
+static int raven_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ return (irq_num + (pci_dev->devfn >> 3)) & 1;
+}
+
+static void raven_set_irq(void *opaque, int irq_num, int level)
+{
+ qemu_irq *pic = opaque;
+
+ qemu_set_irq(pic[irq_num] , level);
+}
+
+static AddressSpace *raven_pcihost_set_iommu(PCIBus *bus, void *opaque,
+ int devfn)
+{
+ PREPPCIState *s = opaque;
+
+ return &s->bm_as;
+}
+
+static void raven_change_gpio(void *opaque, int n, int level)
+{
+ PREPPCIState *s = opaque;
+
+ s->contiguous_map = level;
+}
+
+static void raven_pcihost_realizefn(DeviceState *d, Error **errp)
+{
+ SysBusDevice *dev = SYS_BUS_DEVICE(d);
+ PCIHostState *h = PCI_HOST_BRIDGE(dev);
+ PREPPCIState *s = RAVEN_PCI_HOST_BRIDGE(dev);
+ MemoryRegion *address_space_mem = get_system_memory();
+ int i;
+
+ for (i = 0; i < PCI_NUM_PINS; i++) {
+ sysbus_init_irq(dev, &s->irq[i]);
+ }
+
+ qdev_init_gpio_in(d, raven_change_gpio, 1);
+
+ pci_bus_irqs(&s->pci_bus, raven_set_irq, raven_map_irq, s->irq,
+ PCI_NUM_PINS);
+
+ memory_region_init_io(&h->conf_mem, OBJECT(h), &pci_host_conf_le_ops, s,
+ "pci-conf-idx", 4);
+ memory_region_add_subregion(&s->pci_io, 0xcf8, &h->conf_mem);
+
+ memory_region_init_io(&h->data_mem, OBJECT(h), &pci_host_data_le_ops, s,
+ "pci-conf-data", 4);
+ memory_region_add_subregion(&s->pci_io, 0xcfc, &h->data_mem);
+
+ memory_region_init_io(&h->mmcfg, OBJECT(s), &raven_pci_io_ops, s,
+ "pciio", 0x00400000);
+ memory_region_add_subregion(address_space_mem, 0x80800000, &h->mmcfg);
+
+ memory_region_init_io(&s->pci_intack, OBJECT(s), &raven_intack_ops, s,
+ "pci-intack", 1);
+ memory_region_add_subregion(address_space_mem, 0xbffffff0, &s->pci_intack);
+
+ /* TODO Remove once realize propagates to child devices. */
+ object_property_set_bool(OBJECT(&s->pci_dev), true, "realized", errp);
+}
+
+static void raven_pcihost_initfn(Object *obj)
+{
+ PCIHostState *h = PCI_HOST_BRIDGE(obj);
+ PREPPCIState *s = RAVEN_PCI_HOST_BRIDGE(obj);
+ MemoryRegion *address_space_mem = get_system_memory();
+ DeviceState *pci_dev;
+
+ memory_region_init(&s->pci_io, obj, "pci-io", 0x3f800000);
+ memory_region_init_io(&s->pci_io_non_contiguous, obj, &raven_io_ops, s,
+ "pci-io-non-contiguous", 0x00800000);
+ memory_region_init(&s->pci_memory, obj, "pci-memory", 0x3f000000);
+ address_space_init(&s->pci_io_as, &s->pci_io, "raven-io");
+
+ /* CPU address space */
+ memory_region_add_subregion(address_space_mem, 0x80000000, &s->pci_io);
+ memory_region_add_subregion_overlap(address_space_mem, 0x80000000,
+ &s->pci_io_non_contiguous, 1);
+ memory_region_add_subregion(address_space_mem, 0xc0000000, &s->pci_memory);
+ pci_bus_new_inplace(&s->pci_bus, sizeof(s->pci_bus), DEVICE(obj), NULL,
+ &s->pci_memory, &s->pci_io, 0, TYPE_PCI_BUS);
+
+ /* Bus master address space */
+ memory_region_init(&s->bm, obj, "bm-raven", UINT32_MAX);
+ memory_region_init_alias(&s->bm_pci_memory_alias, obj, "bm-pci-memory",
+ &s->pci_memory, 0,
+ memory_region_size(&s->pci_memory));
+ memory_region_init_alias(&s->bm_ram_alias, obj, "bm-system",
+ get_system_memory(), 0, 0x80000000);
+ memory_region_add_subregion(&s->bm, 0 , &s->bm_pci_memory_alias);
+ memory_region_add_subregion(&s->bm, 0x80000000, &s->bm_ram_alias);
+ address_space_init(&s->bm_as, &s->bm, "raven-bm");
+ pci_setup_iommu(&s->pci_bus, raven_pcihost_set_iommu, s);
+
+ h->bus = &s->pci_bus;
+
+ object_initialize(&s->pci_dev, sizeof(s->pci_dev), TYPE_RAVEN_PCI_DEVICE);
+ pci_dev = DEVICE(&s->pci_dev);
+ qdev_set_parent_bus(pci_dev, BUS(&s->pci_bus));
+ object_property_set_int(OBJECT(&s->pci_dev), PCI_DEVFN(0, 0), "addr",
+ NULL);
+ qdev_prop_set_bit(pci_dev, "multifunction", false);
+}
+
+static void raven_realize(PCIDevice *d, Error **errp)
+{
+ RavenPCIState *s = RAVEN_PCI_DEVICE(d);
+ char *filename;
+ int bios_size = -1;
+
+ d->config[0x0C] = 0x08; // cache_line_size
+ d->config[0x0D] = 0x10; // latency_timer
+ d->config[0x34] = 0x00; // capabilities_pointer
+
+ memory_region_init_ram(&s->bios, OBJECT(s), "bios", BIOS_SIZE,
+ &error_abort);
+ memory_region_set_readonly(&s->bios, true);
+ memory_region_add_subregion(get_system_memory(), (uint32_t)(-BIOS_SIZE),
+ &s->bios);
+ vmstate_register_ram_global(&s->bios);
+ if (s->bios_name) {
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->bios_name);
+ if (filename) {
+ if (s->elf_machine != EM_NONE) {
+ bios_size = load_elf(filename, NULL, NULL, NULL,
+ NULL, NULL, 1, s->elf_machine, 0);
+ }
+ if (bios_size < 0) {
+ bios_size = get_image_size(filename);
+ if (bios_size > 0 && bios_size <= BIOS_SIZE) {
+ hwaddr bios_addr;
+ bios_size = (bios_size + 0xfff) & ~0xfff;
+ bios_addr = (uint32_t)(-BIOS_SIZE);
+ bios_size = load_image_targphys(filename, bios_addr,
+ bios_size);
+ }
+ }
+ }
+ if (bios_size < 0 || bios_size > BIOS_SIZE) {
+ hw_error("qemu: could not load bios image '%s'\n", s->bios_name);
+ }
+ if (filename) {
+ g_free(filename);
+ }
+ }
+}
+
+static const VMStateDescription vmstate_raven = {
+ .name = "raven",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, RavenPCIState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void raven_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = raven_realize;
+ k->vendor_id = PCI_VENDOR_ID_MOTOROLA;
+ k->device_id = PCI_DEVICE_ID_MOTOROLA_RAVEN;
+ k->revision = 0x00;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ dc->desc = "PReP Host Bridge - Motorola Raven";
+ dc->vmsd = &vmstate_raven;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo raven_info = {
+ .name = TYPE_RAVEN_PCI_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(RavenPCIState),
+ .class_init = raven_class_init,
+};
+
+static Property raven_pcihost_properties[] = {
+ DEFINE_PROP_UINT32("elf-machine", PREPPCIState, pci_dev.elf_machine,
+ EM_NONE),
+ DEFINE_PROP_STRING("bios-name", PREPPCIState, pci_dev.bios_name),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void raven_pcihost_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->realize = raven_pcihost_realizefn;
+ dc->props = raven_pcihost_properties;
+ dc->fw_name = "pci";
+}
+
+static const TypeInfo raven_pcihost_info = {
+ .name = TYPE_RAVEN_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(PREPPCIState),
+ .instance_init = raven_pcihost_initfn,
+ .class_init = raven_pcihost_class_init,
+};
+
+static void raven_register_types(void)
+{
+ type_register_static(&raven_pcihost_info);
+ type_register_static(&raven_info);
+}
+
+type_init(raven_register_types)
diff --git a/hw/pci-host/q35.c b/hw/pci-host/q35.c
new file mode 100644
index 00000000..bd740945
--- /dev/null
+++ b/hw/pci-host/q35.c
@@ -0,0 +1,578 @@
+/*
+ * QEMU MCH/ICH9 PCI Bridge Emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2009, 2010, 2011
+ * Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This is based on piix.c, but heavily modified.
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/pci-host/q35.h"
+#include "qapi/visitor.h"
+
+/****************************************************************************
+ * Q35 host
+ */
+
+static void q35_host_realize(DeviceState *dev, Error **errp)
+{
+ PCIHostState *pci = PCI_HOST_BRIDGE(dev);
+ Q35PCIHost *s = Q35_HOST_DEVICE(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ sysbus_add_io(sbd, MCH_HOST_BRIDGE_CONFIG_ADDR, &pci->conf_mem);
+ sysbus_init_ioports(sbd, MCH_HOST_BRIDGE_CONFIG_ADDR, 4);
+
+ sysbus_add_io(sbd, MCH_HOST_BRIDGE_CONFIG_DATA, &pci->data_mem);
+ sysbus_init_ioports(sbd, MCH_HOST_BRIDGE_CONFIG_DATA, 4);
+
+ pci->bus = pci_bus_new(DEVICE(s), "pcie.0",
+ s->mch.pci_address_space, s->mch.address_space_io,
+ 0, TYPE_PCIE_BUS);
+ qdev_set_parent_bus(DEVICE(&s->mch), BUS(pci->bus));
+ qdev_init_nofail(DEVICE(&s->mch));
+}
+
+static const char *q35_host_root_bus_path(PCIHostState *host_bridge,
+ PCIBus *rootbus)
+{
+ Q35PCIHost *s = Q35_HOST_DEVICE(host_bridge);
+
+ /* For backwards compat with old device paths */
+ if (s->mch.short_root_bus) {
+ return "0000";
+ }
+ return "0000:00";
+}
+
+static void q35_host_get_pci_hole_start(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ Q35PCIHost *s = Q35_HOST_DEVICE(obj);
+ uint32_t value = s->mch.pci_info.w32.begin;
+
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static void q35_host_get_pci_hole_end(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ Q35PCIHost *s = Q35_HOST_DEVICE(obj);
+ uint32_t value = s->mch.pci_info.w32.end;
+
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static void q35_host_get_pci_hole64_start(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ PCIHostState *h = PCI_HOST_BRIDGE(obj);
+ Range w64;
+
+ pci_bus_get_w64_range(h->bus, &w64);
+
+ visit_type_uint64(v, &w64.begin, name, errp);
+}
+
+static void q35_host_get_pci_hole64_end(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ PCIHostState *h = PCI_HOST_BRIDGE(obj);
+ Range w64;
+
+ pci_bus_get_w64_range(h->bus, &w64);
+
+ visit_type_uint64(v, &w64.end, name, errp);
+}
+
+static void q35_host_get_mmcfg_size(Object *obj, Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ PCIExpressHost *e = PCIE_HOST_BRIDGE(obj);
+ uint32_t value = e->size;
+
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static Property mch_props[] = {
+ DEFINE_PROP_UINT64(PCIE_HOST_MCFG_BASE, Q35PCIHost, parent_obj.base_addr,
+ MCH_HOST_BRIDGE_PCIEXBAR_DEFAULT),
+ DEFINE_PROP_SIZE(PCI_HOST_PROP_PCI_HOLE64_SIZE, Q35PCIHost,
+ mch.pci_hole64_size, DEFAULT_PCI_HOLE64_SIZE),
+ DEFINE_PROP_UINT32("short_root_bus", Q35PCIHost, mch.short_root_bus, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void q35_host_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass);
+
+ hc->root_bus_path = q35_host_root_bus_path;
+ dc->realize = q35_host_realize;
+ dc->props = mch_props;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->fw_name = "pci";
+}
+
+static void q35_host_initfn(Object *obj)
+{
+ Q35PCIHost *s = Q35_HOST_DEVICE(obj);
+ PCIHostState *phb = PCI_HOST_BRIDGE(obj);
+
+ memory_region_init_io(&phb->conf_mem, obj, &pci_host_conf_le_ops, phb,
+ "pci-conf-idx", 4);
+ memory_region_init_io(&phb->data_mem, obj, &pci_host_data_le_ops, phb,
+ "pci-conf-data", 4);
+
+ object_initialize(&s->mch, sizeof(s->mch), TYPE_MCH_PCI_DEVICE);
+ object_property_add_child(OBJECT(s), "mch", OBJECT(&s->mch), NULL);
+ qdev_prop_set_uint32(DEVICE(&s->mch), "addr", PCI_DEVFN(0, 0));
+ qdev_prop_set_bit(DEVICE(&s->mch), "multifunction", false);
+
+ object_property_add(obj, PCI_HOST_PROP_PCI_HOLE_START, "int",
+ q35_host_get_pci_hole_start,
+ NULL, NULL, NULL, NULL);
+
+ object_property_add(obj, PCI_HOST_PROP_PCI_HOLE_END, "int",
+ q35_host_get_pci_hole_end,
+ NULL, NULL, NULL, NULL);
+
+ object_property_add(obj, PCI_HOST_PROP_PCI_HOLE64_START, "int",
+ q35_host_get_pci_hole64_start,
+ NULL, NULL, NULL, NULL);
+
+ object_property_add(obj, PCI_HOST_PROP_PCI_HOLE64_END, "int",
+ q35_host_get_pci_hole64_end,
+ NULL, NULL, NULL, NULL);
+
+ object_property_add(obj, PCIE_HOST_MCFG_SIZE, "int",
+ q35_host_get_mmcfg_size,
+ NULL, NULL, NULL, NULL);
+
+ /* Leave enough space for the biggest MCFG BAR */
+ /* TODO: this matches current bios behaviour, but
+ * it's not a power of two, which means an MTRR
+ * can't cover it exactly.
+ */
+ s->mch.pci_info.w32.begin = MCH_HOST_BRIDGE_PCIEXBAR_DEFAULT +
+ MCH_HOST_BRIDGE_PCIEXBAR_MAX;
+ s->mch.pci_info.w32.end = IO_APIC_DEFAULT_ADDRESS;
+}
+
+static const TypeInfo q35_host_info = {
+ .name = TYPE_Q35_HOST_DEVICE,
+ .parent = TYPE_PCIE_HOST_BRIDGE,
+ .instance_size = sizeof(Q35PCIHost),
+ .instance_init = q35_host_initfn,
+ .class_init = q35_host_class_init,
+};
+
+/****************************************************************************
+ * MCH D0:F0
+ */
+
+static uint64_t tseg_blackhole_read(void *ptr, hwaddr reg, unsigned size)
+{
+ return 0xffffffff;
+}
+
+static void tseg_blackhole_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ /* nothing */
+}
+
+static const MemoryRegionOps tseg_blackhole_ops = {
+ .read = tseg_blackhole_read,
+ .write = tseg_blackhole_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/* PCIe MMCFG */
+static void mch_update_pciexbar(MCHPCIState *mch)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(mch);
+ BusState *bus = qdev_get_parent_bus(DEVICE(mch));
+ PCIExpressHost *pehb = PCIE_HOST_BRIDGE(bus->parent);
+
+ uint64_t pciexbar;
+ int enable;
+ uint64_t addr;
+ uint64_t addr_mask;
+ uint32_t length;
+
+ pciexbar = pci_get_quad(pci_dev->config + MCH_HOST_BRIDGE_PCIEXBAR);
+ enable = pciexbar & MCH_HOST_BRIDGE_PCIEXBAREN;
+ addr_mask = MCH_HOST_BRIDGE_PCIEXBAR_ADMSK;
+ switch (pciexbar & MCH_HOST_BRIDGE_PCIEXBAR_LENGTH_MASK) {
+ case MCH_HOST_BRIDGE_PCIEXBAR_LENGTH_256M:
+ length = 256 * 1024 * 1024;
+ break;
+ case MCH_HOST_BRIDGE_PCIEXBAR_LENGTH_128M:
+ length = 128 * 1024 * 1024;
+ addr_mask |= MCH_HOST_BRIDGE_PCIEXBAR_128ADMSK |
+ MCH_HOST_BRIDGE_PCIEXBAR_64ADMSK;
+ break;
+ case MCH_HOST_BRIDGE_PCIEXBAR_LENGTH_64M:
+ length = 64 * 1024 * 1024;
+ addr_mask |= MCH_HOST_BRIDGE_PCIEXBAR_64ADMSK;
+ break;
+ case MCH_HOST_BRIDGE_PCIEXBAR_LENGTH_RVD:
+ default:
+ enable = 0;
+ length = 0;
+ abort();
+ break;
+ }
+ addr = pciexbar & addr_mask;
+ pcie_host_mmcfg_update(pehb, enable, addr, length);
+ /* Leave enough space for the MCFG BAR */
+ /*
+ * TODO: this matches current bios behaviour, but it's not a power of two,
+ * which means an MTRR can't cover it exactly.
+ */
+ if (enable) {
+ mch->pci_info.w32.begin = addr + length;
+ } else {
+ mch->pci_info.w32.begin = MCH_HOST_BRIDGE_PCIEXBAR_DEFAULT;
+ }
+}
+
+/* PAM */
+static void mch_update_pam(MCHPCIState *mch)
+{
+ PCIDevice *pd = PCI_DEVICE(mch);
+ int i;
+
+ memory_region_transaction_begin();
+ for (i = 0; i < 13; i++) {
+ pam_update(&mch->pam_regions[i], i,
+ pd->config[MCH_HOST_BRIDGE_PAM0 + ((i + 1) / 2)]);
+ }
+ memory_region_transaction_commit();
+}
+
+/* SMRAM */
+static void mch_update_smram(MCHPCIState *mch)
+{
+ PCIDevice *pd = PCI_DEVICE(mch);
+ bool h_smrame = (pd->config[MCH_HOST_BRIDGE_ESMRAMC] & MCH_HOST_BRIDGE_ESMRAMC_H_SMRAME);
+ uint32_t tseg_size;
+
+ /* implement SMRAM.D_LCK */
+ if (pd->config[MCH_HOST_BRIDGE_SMRAM] & MCH_HOST_BRIDGE_SMRAM_D_LCK) {
+ pd->config[MCH_HOST_BRIDGE_SMRAM] &= ~MCH_HOST_BRIDGE_SMRAM_D_OPEN;
+ pd->wmask[MCH_HOST_BRIDGE_SMRAM] = MCH_HOST_BRIDGE_SMRAM_WMASK_LCK;
+ pd->wmask[MCH_HOST_BRIDGE_ESMRAMC] = MCH_HOST_BRIDGE_ESMRAMC_WMASK_LCK;
+ }
+
+ memory_region_transaction_begin();
+
+ if (pd->config[MCH_HOST_BRIDGE_SMRAM] & SMRAM_D_OPEN) {
+ /* Hide (!) low SMRAM if H_SMRAME = 1 */
+ memory_region_set_enabled(&mch->smram_region, h_smrame);
+ /* Show high SMRAM if H_SMRAME = 1 */
+ memory_region_set_enabled(&mch->open_high_smram, h_smrame);
+ } else {
+ /* Hide high SMRAM and low SMRAM */
+ memory_region_set_enabled(&mch->smram_region, true);
+ memory_region_set_enabled(&mch->open_high_smram, false);
+ }
+
+ if (pd->config[MCH_HOST_BRIDGE_SMRAM] & SMRAM_G_SMRAME) {
+ memory_region_set_enabled(&mch->low_smram, !h_smrame);
+ memory_region_set_enabled(&mch->high_smram, h_smrame);
+ } else {
+ memory_region_set_enabled(&mch->low_smram, false);
+ memory_region_set_enabled(&mch->high_smram, false);
+ }
+
+ if (pd->config[MCH_HOST_BRIDGE_ESMRAMC] & MCH_HOST_BRIDGE_ESMRAMC_T_EN) {
+ switch (pd->config[MCH_HOST_BRIDGE_ESMRAMC] &
+ MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_MASK) {
+ case MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_1MB:
+ tseg_size = 1024 * 1024;
+ break;
+ case MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_2MB:
+ tseg_size = 1024 * 1024 * 2;
+ break;
+ case MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_8MB:
+ tseg_size = 1024 * 1024 * 8;
+ break;
+ default:
+ tseg_size = 0;
+ break;
+ }
+ } else {
+ tseg_size = 0;
+ }
+ memory_region_del_subregion(mch->system_memory, &mch->tseg_blackhole);
+ memory_region_set_enabled(&mch->tseg_blackhole, tseg_size);
+ memory_region_set_size(&mch->tseg_blackhole, tseg_size);
+ memory_region_add_subregion_overlap(mch->system_memory,
+ mch->below_4g_mem_size - tseg_size,
+ &mch->tseg_blackhole, 1);
+
+ memory_region_set_enabled(&mch->tseg_window, tseg_size);
+ memory_region_set_size(&mch->tseg_window, tseg_size);
+ memory_region_set_address(&mch->tseg_window,
+ mch->below_4g_mem_size - tseg_size);
+ memory_region_set_alias_offset(&mch->tseg_window,
+ mch->below_4g_mem_size - tseg_size);
+
+ memory_region_transaction_commit();
+}
+
+static void mch_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ MCHPCIState *mch = MCH_PCI_DEVICE(d);
+
+ pci_default_write_config(d, address, val, len);
+
+ if (ranges_overlap(address, len, MCH_HOST_BRIDGE_PAM0,
+ MCH_HOST_BRIDGE_PAM_SIZE)) {
+ mch_update_pam(mch);
+ }
+
+ if (ranges_overlap(address, len, MCH_HOST_BRIDGE_PCIEXBAR,
+ MCH_HOST_BRIDGE_PCIEXBAR_SIZE)) {
+ mch_update_pciexbar(mch);
+ }
+
+ if (ranges_overlap(address, len, MCH_HOST_BRIDGE_SMRAM,
+ MCH_HOST_BRIDGE_SMRAM_SIZE)) {
+ mch_update_smram(mch);
+ }
+}
+
+static void mch_update(MCHPCIState *mch)
+{
+ mch_update_pciexbar(mch);
+ mch_update_pam(mch);
+ mch_update_smram(mch);
+}
+
+static int mch_post_load(void *opaque, int version_id)
+{
+ MCHPCIState *mch = opaque;
+ mch_update(mch);
+ return 0;
+}
+
+static const VMStateDescription vmstate_mch = {
+ .name = "mch",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = mch_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, MCHPCIState),
+ /* Used to be smm_enabled, which was basically always zero because
+ * SeaBIOS hardly uses SMM. SMRAM is now handled by CPU code.
+ */
+ VMSTATE_UNUSED(1),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mch_reset(DeviceState *qdev)
+{
+ PCIDevice *d = PCI_DEVICE(qdev);
+ MCHPCIState *mch = MCH_PCI_DEVICE(d);
+
+ pci_set_quad(d->config + MCH_HOST_BRIDGE_PCIEXBAR,
+ MCH_HOST_BRIDGE_PCIEXBAR_DEFAULT);
+
+ d->config[MCH_HOST_BRIDGE_SMRAM] = MCH_HOST_BRIDGE_SMRAM_DEFAULT;
+ d->config[MCH_HOST_BRIDGE_ESMRAMC] = MCH_HOST_BRIDGE_ESMRAMC_DEFAULT;
+ d->wmask[MCH_HOST_BRIDGE_SMRAM] = MCH_HOST_BRIDGE_SMRAM_WMASK;
+ d->wmask[MCH_HOST_BRIDGE_ESMRAMC] = MCH_HOST_BRIDGE_ESMRAMC_WMASK;
+
+ mch_update(mch);
+}
+
+static AddressSpace *q35_host_dma_iommu(PCIBus *bus, void *opaque, int devfn)
+{
+ IntelIOMMUState *s = opaque;
+ VTDAddressSpace **pvtd_as;
+ int bus_num = pci_bus_num(bus);
+
+ assert(0 <= bus_num && bus_num <= VTD_PCI_BUS_MAX);
+ assert(0 <= devfn && devfn <= VTD_PCI_DEVFN_MAX);
+
+ pvtd_as = s->address_spaces[bus_num];
+ if (!pvtd_as) {
+ /* No corresponding free() */
+ pvtd_as = g_malloc0(sizeof(VTDAddressSpace *) * VTD_PCI_DEVFN_MAX);
+ s->address_spaces[bus_num] = pvtd_as;
+ }
+ if (!pvtd_as[devfn]) {
+ pvtd_as[devfn] = g_malloc0(sizeof(VTDAddressSpace));
+
+ pvtd_as[devfn]->bus_num = (uint8_t)bus_num;
+ pvtd_as[devfn]->devfn = (uint8_t)devfn;
+ pvtd_as[devfn]->iommu_state = s;
+ pvtd_as[devfn]->context_cache_entry.context_cache_gen = 0;
+ memory_region_init_iommu(&pvtd_as[devfn]->iommu, OBJECT(s),
+ &s->iommu_ops, "intel_iommu", UINT64_MAX);
+ address_space_init(&pvtd_as[devfn]->as,
+ &pvtd_as[devfn]->iommu, "intel_iommu");
+ }
+ return &pvtd_as[devfn]->as;
+}
+
+static void mch_init_dmar(MCHPCIState *mch)
+{
+ PCIBus *pci_bus = PCI_BUS(qdev_get_parent_bus(DEVICE(mch)));
+
+ mch->iommu = INTEL_IOMMU_DEVICE(qdev_create(NULL, TYPE_INTEL_IOMMU_DEVICE));
+ object_property_add_child(OBJECT(mch), "intel-iommu",
+ OBJECT(mch->iommu), NULL);
+ qdev_init_nofail(DEVICE(mch->iommu));
+ sysbus_mmio_map(SYS_BUS_DEVICE(mch->iommu), 0, Q35_HOST_BRIDGE_IOMMU_ADDR);
+
+ pci_setup_iommu(pci_bus, q35_host_dma_iommu, mch->iommu);
+}
+
+static void mch_realize(PCIDevice *d, Error **errp)
+{
+ int i;
+ MCHPCIState *mch = MCH_PCI_DEVICE(d);
+
+ /* setup pci memory mapping */
+ pc_pci_as_mapping_init(OBJECT(mch), mch->system_memory,
+ mch->pci_address_space);
+
+ /* if *disabled* show SMRAM to all CPUs */
+ memory_region_init_alias(&mch->smram_region, OBJECT(mch), "smram-region",
+ mch->pci_address_space, 0xa0000, 0x20000);
+ memory_region_add_subregion_overlap(mch->system_memory, 0xa0000,
+ &mch->smram_region, 1);
+ memory_region_set_enabled(&mch->smram_region, true);
+
+ memory_region_init_alias(&mch->open_high_smram, OBJECT(mch), "smram-open-high",
+ mch->ram_memory, 0xa0000, 0x20000);
+ memory_region_add_subregion_overlap(mch->system_memory, 0xfeda0000,
+ &mch->open_high_smram, 1);
+ memory_region_set_enabled(&mch->open_high_smram, false);
+
+ /* smram, as seen by SMM CPUs */
+ memory_region_init(&mch->smram, OBJECT(mch), "smram", 1ull << 32);
+ memory_region_set_enabled(&mch->smram, true);
+ memory_region_init_alias(&mch->low_smram, OBJECT(mch), "smram-low",
+ mch->ram_memory, 0xa0000, 0x20000);
+ memory_region_set_enabled(&mch->low_smram, true);
+ memory_region_add_subregion(&mch->smram, 0xa0000, &mch->low_smram);
+ memory_region_init_alias(&mch->high_smram, OBJECT(mch), "smram-high",
+ mch->ram_memory, 0xa0000, 0x20000);
+ memory_region_set_enabled(&mch->high_smram, true);
+ memory_region_add_subregion(&mch->smram, 0xfeda0000, &mch->high_smram);
+
+ memory_region_init_io(&mch->tseg_blackhole, OBJECT(mch),
+ &tseg_blackhole_ops, NULL,
+ "tseg-blackhole", 0);
+ memory_region_set_enabled(&mch->tseg_blackhole, false);
+ memory_region_add_subregion_overlap(mch->system_memory,
+ mch->below_4g_mem_size,
+ &mch->tseg_blackhole, 1);
+
+ memory_region_init_alias(&mch->tseg_window, OBJECT(mch), "tseg-window",
+ mch->ram_memory, mch->below_4g_mem_size, 0);
+ memory_region_set_enabled(&mch->tseg_window, false);
+ memory_region_add_subregion(&mch->smram, mch->below_4g_mem_size,
+ &mch->tseg_window);
+ object_property_add_const_link(qdev_get_machine(), "smram",
+ OBJECT(&mch->smram), &error_abort);
+
+ init_pam(DEVICE(mch), mch->ram_memory, mch->system_memory,
+ mch->pci_address_space, &mch->pam_regions[0],
+ PAM_BIOS_BASE, PAM_BIOS_SIZE);
+ for (i = 0; i < 12; ++i) {
+ init_pam(DEVICE(mch), mch->ram_memory, mch->system_memory,
+ mch->pci_address_space, &mch->pam_regions[i+1],
+ PAM_EXPAN_BASE + i * PAM_EXPAN_SIZE, PAM_EXPAN_SIZE);
+ }
+ /* Intel IOMMU (VT-d) */
+ if (machine_iommu(current_machine)) {
+ mch_init_dmar(mch);
+ }
+}
+
+uint64_t mch_mcfg_base(void)
+{
+ bool ambiguous;
+ Object *o = object_resolve_path_type("", TYPE_MCH_PCI_DEVICE, &ambiguous);
+ if (!o) {
+ return 0;
+ }
+ return MCH_HOST_BRIDGE_PCIEXBAR_DEFAULT;
+}
+
+static void mch_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = mch_realize;
+ k->config_write = mch_write_config;
+ dc->reset = mch_reset;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->desc = "Host bridge";
+ dc->vmsd = &vmstate_mch;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_Q35_MCH;
+ k->revision = MCH_HOST_BRIDGE_REVISION_DEFAULT;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo mch_info = {
+ .name = TYPE_MCH_PCI_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(MCHPCIState),
+ .class_init = mch_class_init,
+};
+
+static void q35_register(void)
+{
+ type_register_static(&mch_info);
+ type_register_static(&q35_host_info);
+}
+
+type_init(q35_register);
diff --git a/hw/pci-host/uninorth.c b/hw/pci-host/uninorth.c
new file mode 100644
index 00000000..f0144eb7
--- /dev/null
+++ b/hw/pci-host/uninorth.c
@@ -0,0 +1,515 @@
+/*
+ * QEMU Uninorth PCI host (for all Mac99 and newer machines)
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/mac.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+
+/* debug UniNorth */
+//#define DEBUG_UNIN
+
+#ifdef DEBUG_UNIN
+#define UNIN_DPRINTF(fmt, ...) \
+ do { printf("UNIN: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define UNIN_DPRINTF(fmt, ...)
+#endif
+
+static const int unin_irq_line[] = { 0x1b, 0x1c, 0x1d, 0x1e };
+
+#define TYPE_UNI_NORTH_PCI_HOST_BRIDGE "uni-north-pci-pcihost"
+#define TYPE_UNI_NORTH_AGP_HOST_BRIDGE "uni-north-agp-pcihost"
+#define TYPE_UNI_NORTH_INTERNAL_PCI_HOST_BRIDGE "uni-north-internal-pci-pcihost"
+#define TYPE_U3_AGP_HOST_BRIDGE "u3-agp-pcihost"
+
+#define UNI_NORTH_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(UNINState, (obj), TYPE_UNI_NORTH_PCI_HOST_BRIDGE)
+#define UNI_NORTH_AGP_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(UNINState, (obj), TYPE_UNI_NORTH_AGP_HOST_BRIDGE)
+#define UNI_NORTH_INTERNAL_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(UNINState, (obj), TYPE_UNI_NORTH_INTERNAL_PCI_HOST_BRIDGE)
+#define U3_AGP_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(UNINState, (obj), TYPE_U3_AGP_HOST_BRIDGE)
+
+typedef struct UNINState {
+ PCIHostState parent_obj;
+
+ MemoryRegion pci_mmio;
+ MemoryRegion pci_hole;
+} UNINState;
+
+static int pci_unin_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ int retval;
+ int devfn = pci_dev->devfn & 0x00FFFFFF;
+
+ retval = (((devfn >> 11) & 0x1F) + irq_num) & 3;
+
+ return retval;
+}
+
+static void pci_unin_set_irq(void *opaque, int irq_num, int level)
+{
+ qemu_irq *pic = opaque;
+
+ UNIN_DPRINTF("%s: setting INT %d = %d\n", __func__,
+ unin_irq_line[irq_num], level);
+ qemu_set_irq(pic[unin_irq_line[irq_num]], level);
+}
+
+static uint32_t unin_get_config_reg(uint32_t reg, uint32_t addr)
+{
+ uint32_t retval;
+
+ if (reg & (1u << 31)) {
+ /* XXX OpenBIOS compatibility hack */
+ retval = reg | (addr & 3);
+ } else if (reg & 1) {
+ /* CFA1 style */
+ retval = (reg & ~7u) | (addr & 7);
+ } else {
+ uint32_t slot, func;
+
+ /* Grab CFA0 style values */
+ slot = ctz32(reg & 0xfffff800);
+ if (slot == 32) {
+ slot = -1; /* XXX: should this be 0? */
+ }
+ func = (reg >> 8) & 7;
+
+ /* ... and then convert them to x86 format */
+ /* config pointer */
+ retval = (reg & (0xff - 7)) | (addr & 7);
+ /* slot */
+ retval |= slot << 11;
+ /* fn */
+ retval |= func << 8;
+ }
+
+
+ UNIN_DPRINTF("Converted config space accessor %08x/%08x -> %08x\n",
+ reg, addr, retval);
+
+ return retval;
+}
+
+static void unin_data_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned len)
+{
+ UNINState *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+ UNIN_DPRINTF("write addr %" TARGET_FMT_plx " len %d val %"PRIx64"\n",
+ addr, len, val);
+ pci_data_write(phb->bus,
+ unin_get_config_reg(phb->config_reg, addr),
+ val, len);
+}
+
+static uint64_t unin_data_read(void *opaque, hwaddr addr,
+ unsigned len)
+{
+ UNINState *s = opaque;
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+ uint32_t val;
+
+ val = pci_data_read(phb->bus,
+ unin_get_config_reg(phb->config_reg, addr),
+ len);
+ UNIN_DPRINTF("read addr %" TARGET_FMT_plx " len %d val %x\n",
+ addr, len, val);
+ return val;
+}
+
+static const MemoryRegionOps unin_data_ops = {
+ .read = unin_data_read,
+ .write = unin_data_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static int pci_unin_main_init_device(SysBusDevice *dev)
+{
+ PCIHostState *h;
+
+ /* Use values found on a real PowerMac */
+ /* Uninorth main bus */
+ h = PCI_HOST_BRIDGE(dev);
+
+ memory_region_init_io(&h->conf_mem, OBJECT(h), &pci_host_conf_le_ops,
+ dev, "pci-conf-idx", 0x1000);
+ memory_region_init_io(&h->data_mem, OBJECT(h), &unin_data_ops, dev,
+ "pci-conf-data", 0x1000);
+ sysbus_init_mmio(dev, &h->conf_mem);
+ sysbus_init_mmio(dev, &h->data_mem);
+
+ return 0;
+}
+
+
+static int pci_u3_agp_init_device(SysBusDevice *dev)
+{
+ PCIHostState *h;
+
+ /* Uninorth U3 AGP bus */
+ h = PCI_HOST_BRIDGE(dev);
+
+ memory_region_init_io(&h->conf_mem, OBJECT(h), &pci_host_conf_le_ops,
+ dev, "pci-conf-idx", 0x1000);
+ memory_region_init_io(&h->data_mem, OBJECT(h), &unin_data_ops, dev,
+ "pci-conf-data", 0x1000);
+ sysbus_init_mmio(dev, &h->conf_mem);
+ sysbus_init_mmio(dev, &h->data_mem);
+
+ return 0;
+}
+
+static int pci_unin_agp_init_device(SysBusDevice *dev)
+{
+ PCIHostState *h;
+
+ /* Uninorth AGP bus */
+ h = PCI_HOST_BRIDGE(dev);
+
+ memory_region_init_io(&h->conf_mem, OBJECT(h), &pci_host_conf_le_ops,
+ dev, "pci-conf-idx", 0x1000);
+ memory_region_init_io(&h->data_mem, OBJECT(h), &pci_host_data_le_ops,
+ dev, "pci-conf-data", 0x1000);
+ sysbus_init_mmio(dev, &h->conf_mem);
+ sysbus_init_mmio(dev, &h->data_mem);
+ return 0;
+}
+
+static int pci_unin_internal_init_device(SysBusDevice *dev)
+{
+ PCIHostState *h;
+
+ /* Uninorth internal bus */
+ h = PCI_HOST_BRIDGE(dev);
+
+ memory_region_init_io(&h->conf_mem, OBJECT(h), &pci_host_conf_le_ops,
+ dev, "pci-conf-idx", 0x1000);
+ memory_region_init_io(&h->data_mem, OBJECT(h), &pci_host_data_le_ops,
+ dev, "pci-conf-data", 0x1000);
+ sysbus_init_mmio(dev, &h->conf_mem);
+ sysbus_init_mmio(dev, &h->data_mem);
+ return 0;
+}
+
+PCIBus *pci_pmac_init(qemu_irq *pic,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ PCIHostState *h;
+ UNINState *d;
+
+ /* Use values found on a real PowerMac */
+ /* Uninorth main bus */
+ dev = qdev_create(NULL, TYPE_UNI_NORTH_PCI_HOST_BRIDGE);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ h = PCI_HOST_BRIDGE(s);
+ d = UNI_NORTH_PCI_HOST_BRIDGE(dev);
+ memory_region_init(&d->pci_mmio, OBJECT(d), "pci-mmio", 0x100000000ULL);
+ memory_region_init_alias(&d->pci_hole, OBJECT(d), "pci-hole", &d->pci_mmio,
+ 0x80000000ULL, 0x10000000ULL);
+ memory_region_add_subregion(address_space_mem, 0x80000000ULL,
+ &d->pci_hole);
+
+ h->bus = pci_register_bus(dev, NULL,
+ pci_unin_set_irq, pci_unin_map_irq,
+ pic,
+ &d->pci_mmio,
+ address_space_io,
+ PCI_DEVFN(11, 0), 4, TYPE_PCI_BUS);
+
+#if 0
+ pci_create_simple(h->bus, PCI_DEVFN(11, 0), "uni-north");
+#endif
+
+ sysbus_mmio_map(s, 0, 0xf2800000);
+ sysbus_mmio_map(s, 1, 0xf2c00000);
+
+ /* DEC 21154 bridge */
+#if 0
+ /* XXX: not activated as PPC BIOS doesn't handle multiple buses properly */
+ pci_create_simple(h->bus, PCI_DEVFN(12, 0), "dec-21154");
+#endif
+
+ /* Uninorth AGP bus */
+ pci_create_simple(h->bus, PCI_DEVFN(11, 0), "uni-north-agp");
+ dev = qdev_create(NULL, TYPE_UNI_NORTH_AGP_HOST_BRIDGE);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, 0xf0800000);
+ sysbus_mmio_map(s, 1, 0xf0c00000);
+
+ /* Uninorth internal bus */
+#if 0
+ /* XXX: not needed for now */
+ pci_create_simple(h->bus, PCI_DEVFN(14, 0),
+ "uni-north-internal-pci");
+ dev = qdev_create(NULL, TYPE_UNI_NORTH_INTERNAL_PCI_HOST_BRIDGE);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, 0xf4800000);
+ sysbus_mmio_map(s, 1, 0xf4c00000);
+#endif
+
+ return h->bus;
+}
+
+PCIBus *pci_pmac_u3_init(qemu_irq *pic,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ PCIHostState *h;
+ UNINState *d;
+
+ /* Uninorth AGP bus */
+
+ dev = qdev_create(NULL, TYPE_U3_AGP_HOST_BRIDGE);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ h = PCI_HOST_BRIDGE(dev);
+ d = U3_AGP_HOST_BRIDGE(dev);
+
+ memory_region_init(&d->pci_mmio, OBJECT(d), "pci-mmio", 0x100000000ULL);
+ memory_region_init_alias(&d->pci_hole, OBJECT(d), "pci-hole", &d->pci_mmio,
+ 0x80000000ULL, 0x70000000ULL);
+ memory_region_add_subregion(address_space_mem, 0x80000000ULL,
+ &d->pci_hole);
+
+ h->bus = pci_register_bus(dev, NULL,
+ pci_unin_set_irq, pci_unin_map_irq,
+ pic,
+ &d->pci_mmio,
+ address_space_io,
+ PCI_DEVFN(11, 0), 4, TYPE_PCI_BUS);
+
+ sysbus_mmio_map(s, 0, 0xf0800000);
+ sysbus_mmio_map(s, 1, 0xf0c00000);
+
+ pci_create_simple(h->bus, 11 << 3, "u3-agp");
+
+ return h->bus;
+}
+
+static void unin_main_pci_host_realize(PCIDevice *d, Error **errp)
+{
+ d->config[0x0C] = 0x08; // cache_line_size
+ d->config[0x0D] = 0x10; // latency_timer
+ d->config[0x34] = 0x00; // capabilities_pointer
+}
+
+static void unin_agp_pci_host_realize(PCIDevice *d, Error **errp)
+{
+ d->config[0x0C] = 0x08; // cache_line_size
+ d->config[0x0D] = 0x10; // latency_timer
+ // d->config[0x34] = 0x80; // capabilities_pointer
+}
+
+static void u3_agp_pci_host_realize(PCIDevice *d, Error **errp)
+{
+ /* cache line size */
+ d->config[0x0C] = 0x08;
+ /* latency timer */
+ d->config[0x0D] = 0x10;
+}
+
+static void unin_internal_pci_host_realize(PCIDevice *d, Error **errp)
+{
+ d->config[0x0C] = 0x08; // cache_line_size
+ d->config[0x0D] = 0x10; // latency_timer
+ d->config[0x34] = 0x00; // capabilities_pointer
+}
+
+static void unin_main_pci_host_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = unin_main_pci_host_realize;
+ k->vendor_id = PCI_VENDOR_ID_APPLE;
+ k->device_id = PCI_DEVICE_ID_APPLE_UNI_N_PCI;
+ k->revision = 0x00;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo unin_main_pci_host_info = {
+ .name = "uni-north-pci",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = unin_main_pci_host_class_init,
+};
+
+static void u3_agp_pci_host_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = u3_agp_pci_host_realize;
+ k->vendor_id = PCI_VENDOR_ID_APPLE;
+ k->device_id = PCI_DEVICE_ID_APPLE_U3_AGP;
+ k->revision = 0x00;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo u3_agp_pci_host_info = {
+ .name = "u3-agp",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = u3_agp_pci_host_class_init,
+};
+
+static void unin_agp_pci_host_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = unin_agp_pci_host_realize;
+ k->vendor_id = PCI_VENDOR_ID_APPLE;
+ k->device_id = PCI_DEVICE_ID_APPLE_UNI_N_AGP;
+ k->revision = 0x00;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo unin_agp_pci_host_info = {
+ .name = "uni-north-agp",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = unin_agp_pci_host_class_init,
+};
+
+static void unin_internal_pci_host_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = unin_internal_pci_host_realize;
+ k->vendor_id = PCI_VENDOR_ID_APPLE;
+ k->device_id = PCI_DEVICE_ID_APPLE_UNI_N_I_PCI;
+ k->revision = 0x00;
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo unin_internal_pci_host_info = {
+ .name = "uni-north-internal-pci",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = unin_internal_pci_host_class_init,
+};
+
+static void pci_unin_main_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sbc->init = pci_unin_main_init_device;
+}
+
+static const TypeInfo pci_unin_main_info = {
+ .name = TYPE_UNI_NORTH_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(UNINState),
+ .class_init = pci_unin_main_class_init,
+};
+
+static void pci_u3_agp_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sbc->init = pci_u3_agp_init_device;
+}
+
+static const TypeInfo pci_u3_agp_info = {
+ .name = TYPE_U3_AGP_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(UNINState),
+ .class_init = pci_u3_agp_class_init,
+};
+
+static void pci_unin_agp_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sbc->init = pci_unin_agp_init_device;
+}
+
+static const TypeInfo pci_unin_agp_info = {
+ .name = TYPE_UNI_NORTH_AGP_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(UNINState),
+ .class_init = pci_unin_agp_class_init,
+};
+
+static void pci_unin_internal_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sbc->init = pci_unin_internal_init_device;
+}
+
+static const TypeInfo pci_unin_internal_info = {
+ .name = TYPE_UNI_NORTH_INTERNAL_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(UNINState),
+ .class_init = pci_unin_internal_class_init,
+};
+
+static void unin_register_types(void)
+{
+ type_register_static(&unin_main_pci_host_info);
+ type_register_static(&u3_agp_pci_host_info);
+ type_register_static(&unin_agp_pci_host_info);
+ type_register_static(&unin_internal_pci_host_info);
+
+ type_register_static(&pci_unin_main_info);
+ type_register_static(&pci_u3_agp_info);
+ type_register_static(&pci_unin_agp_info);
+ type_register_static(&pci_unin_internal_info);
+}
+
+type_init(unin_register_types)
diff --git a/hw/pci-host/versatile.c b/hw/pci-host/versatile.c
new file mode 100644
index 00000000..7172b909
--- /dev/null
+++ b/hw/pci-host/versatile.c
@@ -0,0 +1,548 @@
+/*
+ * ARM Versatile/PB PCI host controller
+ *
+ * Copyright (c) 2006-2009 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pci_host.h"
+#include "exec/address-spaces.h"
+
+/* Old and buggy versions of QEMU used the wrong mapping from
+ * PCI IRQs to system interrupt lines. Unfortunately the Linux
+ * kernel also had the corresponding bug in setting up interrupts
+ * (so older kernels work on QEMU and not on real hardware).
+ * We automatically detect these broken kernels and flip back
+ * to the broken irq mapping by spotting guest writes to the
+ * PCI_INTERRUPT_LINE register to see where the guest thinks
+ * interrupts are going to be routed. So we start in state
+ * ASSUME_OK on reset, and transition to either BROKEN or
+ * FORCE_OK at the first write to an INTERRUPT_LINE register for
+ * a slot where broken and correct interrupt mapping would differ.
+ * Once in either BROKEN or FORCE_OK we never transition again;
+ * this allows a newer kernel to use the INTERRUPT_LINE
+ * registers arbitrarily once it has indicated that it isn't
+ * broken in its init code somewhere.
+ *
+ * Unfortunately we have to cope with multiple different
+ * variants on the broken kernel behaviour:
+ * phase I (before kernel commit 1bc39ac5d) kernels assume old
+ * QEMU behaviour, so they use IRQ 27 for all slots
+ * phase II (1bc39ac5d and later, but before e3e92a7be6) kernels
+ * swizzle IRQs between slots, but do it wrongly, so they
+ * work only for every fourth PCI card, and only if (like old
+ * QEMU) the PCI host device is at slot 0 rather than where
+ * the h/w actually puts it
+ * phase III (e3e92a7be6 and later) kernels still swizzle IRQs between
+ * slots wrongly, but add a fixed offset of 64 to everything
+ * they write to PCI_INTERRUPT_LINE.
+ *
+ * We live in hope of a mythical phase IV kernel which might
+ * actually behave in ways that work on the hardware. Such a
+ * kernel should probably start off by writing some value neither
+ * 27 nor 91 to slot zero's PCI_INTERRUPT_LINE register to
+ * disable the autodetection. After that it can do what it likes.
+ *
+ * Slot % 4 | hw | I | II | III
+ * -------------------------------
+ * 0 | 29 | 27 | 27 | 91
+ * 1 | 30 | 27 | 28 | 92
+ * 2 | 27 | 27 | 29 | 93
+ * 3 | 28 | 27 | 30 | 94
+ *
+ * Since our autodetection is not perfect we also provide a
+ * property so the user can make us start in BROKEN or FORCE_OK
+ * on reset if they know they have a bad or good kernel.
+ */
+enum {
+ PCI_VPB_IRQMAP_ASSUME_OK,
+ PCI_VPB_IRQMAP_BROKEN,
+ PCI_VPB_IRQMAP_FORCE_OK,
+};
+
+typedef struct {
+ PCIHostState parent_obj;
+
+ qemu_irq irq[4];
+ MemoryRegion controlregs;
+ MemoryRegion mem_config;
+ MemoryRegion mem_config2;
+ /* Containers representing the PCI address spaces */
+ MemoryRegion pci_io_space;
+ MemoryRegion pci_mem_space;
+ /* Alias regions into PCI address spaces which we expose as sysbus regions.
+ * The offsets into pci_mem_space are controlled by the imap registers.
+ */
+ MemoryRegion pci_io_window;
+ MemoryRegion pci_mem_window[3];
+ PCIBus pci_bus;
+ PCIDevice pci_dev;
+
+ /* Constant for life of device: */
+ int realview;
+ uint32_t mem_win_size[3];
+ uint8_t irq_mapping_prop;
+
+ /* Variable state: */
+ uint32_t imap[3];
+ uint32_t smap[3];
+ uint32_t selfid;
+ uint32_t flags;
+ uint8_t irq_mapping;
+} PCIVPBState;
+
+static void pci_vpb_update_window(PCIVPBState *s, int i)
+{
+ /* Adjust the offset of the alias region we use for
+ * the memory window i to account for a change in the
+ * value of the corresponding IMAP register.
+ * Note that the semantics of the IMAP register differ
+ * for realview and versatile variants of the controller.
+ */
+ hwaddr offset;
+ if (s->realview) {
+ /* Top bits of register (masked according to window size) provide
+ * top bits of PCI address.
+ */
+ offset = s->imap[i] & ~(s->mem_win_size[i] - 1);
+ } else {
+ /* Bottom 4 bits of register provide top 4 bits of PCI address */
+ offset = s->imap[i] << 28;
+ }
+ memory_region_set_alias_offset(&s->pci_mem_window[i], offset);
+}
+
+static void pci_vpb_update_all_windows(PCIVPBState *s)
+{
+ /* Update all alias windows based on the current register state */
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ pci_vpb_update_window(s, i);
+ }
+}
+
+static int pci_vpb_post_load(void *opaque, int version_id)
+{
+ PCIVPBState *s = opaque;
+ pci_vpb_update_all_windows(s);
+ return 0;
+}
+
+static const VMStateDescription pci_vpb_vmstate = {
+ .name = "versatile-pci",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pci_vpb_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(imap, PCIVPBState, 3),
+ VMSTATE_UINT32_ARRAY(smap, PCIVPBState, 3),
+ VMSTATE_UINT32(selfid, PCIVPBState),
+ VMSTATE_UINT32(flags, PCIVPBState),
+ VMSTATE_UINT8(irq_mapping, PCIVPBState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define TYPE_VERSATILE_PCI "versatile_pci"
+#define PCI_VPB(obj) \
+ OBJECT_CHECK(PCIVPBState, (obj), TYPE_VERSATILE_PCI)
+
+#define TYPE_VERSATILE_PCI_HOST "versatile_pci_host"
+#define PCI_VPB_HOST(obj) \
+ OBJECT_CHECK(PCIDevice, (obj), TYPE_VERSATILE_PCIHOST)
+
+typedef enum {
+ PCI_IMAP0 = 0x0,
+ PCI_IMAP1 = 0x4,
+ PCI_IMAP2 = 0x8,
+ PCI_SELFID = 0xc,
+ PCI_FLAGS = 0x10,
+ PCI_SMAP0 = 0x14,
+ PCI_SMAP1 = 0x18,
+ PCI_SMAP2 = 0x1c,
+} PCIVPBControlRegs;
+
+static void pci_vpb_reg_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIVPBState *s = opaque;
+
+ switch (addr) {
+ case PCI_IMAP0:
+ case PCI_IMAP1:
+ case PCI_IMAP2:
+ {
+ int win = (addr - PCI_IMAP0) >> 2;
+ s->imap[win] = val;
+ pci_vpb_update_window(s, win);
+ break;
+ }
+ case PCI_SELFID:
+ s->selfid = val;
+ break;
+ case PCI_FLAGS:
+ s->flags = val;
+ break;
+ case PCI_SMAP0:
+ case PCI_SMAP1:
+ case PCI_SMAP2:
+ {
+ int win = (addr - PCI_SMAP0) >> 2;
+ s->smap[win] = val;
+ break;
+ }
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pci_vpb_reg_write: Bad offset %x\n", (int)addr);
+ break;
+ }
+}
+
+static uint64_t pci_vpb_reg_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCIVPBState *s = opaque;
+
+ switch (addr) {
+ case PCI_IMAP0:
+ case PCI_IMAP1:
+ case PCI_IMAP2:
+ {
+ int win = (addr - PCI_IMAP0) >> 2;
+ return s->imap[win];
+ }
+ case PCI_SELFID:
+ return s->selfid;
+ case PCI_FLAGS:
+ return s->flags;
+ case PCI_SMAP0:
+ case PCI_SMAP1:
+ case PCI_SMAP2:
+ {
+ int win = (addr - PCI_SMAP0) >> 2;
+ return s->smap[win];
+ }
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pci_vpb_reg_read: Bad offset %x\n", (int)addr);
+ return 0;
+ }
+}
+
+static const MemoryRegionOps pci_vpb_reg_ops = {
+ .read = pci_vpb_reg_read,
+ .write = pci_vpb_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static int pci_vpb_broken_irq(int slot, int irq)
+{
+ /* Determine whether this IRQ value for this slot represents a
+ * known broken Linux kernel behaviour for this slot.
+ * Return one of the PCI_VPB_IRQMAP_ constants:
+ * BROKEN : if this definitely looks like a broken kernel
+ * FORCE_OK : if this definitely looks good
+ * ASSUME_OK : if we can't tell
+ */
+ slot %= PCI_NUM_PINS;
+
+ if (irq == 27) {
+ if (slot == 2) {
+ /* Might be a Phase I kernel, or might be a fixed kernel,
+ * since slot 2 is where we expect this IRQ.
+ */
+ return PCI_VPB_IRQMAP_ASSUME_OK;
+ }
+ /* Phase I kernel */
+ return PCI_VPB_IRQMAP_BROKEN;
+ }
+ if (irq == slot + 27) {
+ /* Phase II kernel */
+ return PCI_VPB_IRQMAP_BROKEN;
+ }
+ if (irq == slot + 27 + 64) {
+ /* Phase III kernel */
+ return PCI_VPB_IRQMAP_BROKEN;
+ }
+ /* Anything else must be a fixed kernel, possibly using an
+ * arbitrary irq map.
+ */
+ return PCI_VPB_IRQMAP_FORCE_OK;
+}
+
+static void pci_vpb_config_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIVPBState *s = opaque;
+ if (!s->realview && (addr & 0xff) == PCI_INTERRUPT_LINE
+ && s->irq_mapping == PCI_VPB_IRQMAP_ASSUME_OK) {
+ uint8_t devfn = addr >> 8;
+ s->irq_mapping = pci_vpb_broken_irq(PCI_SLOT(devfn), val);
+ }
+ pci_data_write(&s->pci_bus, addr, val, size);
+}
+
+static uint64_t pci_vpb_config_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCIVPBState *s = opaque;
+ uint32_t val;
+ val = pci_data_read(&s->pci_bus, addr, size);
+ return val;
+}
+
+static const MemoryRegionOps pci_vpb_config_ops = {
+ .read = pci_vpb_config_read,
+ .write = pci_vpb_config_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pci_vpb_map_irq(PCIDevice *d, int irq_num)
+{
+ PCIVPBState *s = container_of(d->bus, PCIVPBState, pci_bus);
+
+ if (s->irq_mapping == PCI_VPB_IRQMAP_BROKEN) {
+ /* Legacy broken IRQ mapping for compatibility with old and
+ * buggy Linux guests
+ */
+ return irq_num;
+ }
+
+ /* Slot to IRQ mapping for RealView Platform Baseboard 926 backplane
+ * name slot IntA IntB IntC IntD
+ * A 31 IRQ28 IRQ29 IRQ30 IRQ27
+ * B 30 IRQ27 IRQ28 IRQ29 IRQ30
+ * C 29 IRQ30 IRQ27 IRQ28 IRQ29
+ * Slot C is for the host bridge; A and B the peripherals.
+ * Our output irqs 0..3 correspond to the baseboard's 27..30.
+ *
+ * This mapping function takes account of an oddity in the PB926
+ * board wiring, where the FPGA's P_nINTA input is connected to
+ * the INTB connection on the board PCI edge connector, P_nINTB
+ * is connected to INTC, and so on, so everything is one number
+ * further round from where you might expect.
+ */
+ return pci_swizzle_map_irq_fn(d, irq_num + 2);
+}
+
+static int pci_vpb_rv_map_irq(PCIDevice *d, int irq_num)
+{
+ /* Slot to IRQ mapping for RealView EB and PB1176 backplane
+ * name slot IntA IntB IntC IntD
+ * A 31 IRQ50 IRQ51 IRQ48 IRQ49
+ * B 30 IRQ49 IRQ50 IRQ51 IRQ48
+ * C 29 IRQ48 IRQ49 IRQ50 IRQ51
+ * Slot C is for the host bridge; A and B the peripherals.
+ * Our output irqs 0..3 correspond to the baseboard's 48..51.
+ *
+ * The PB1176 and EB boards don't have the PB926 wiring oddity
+ * described above; P_nINTA connects to INTA, P_nINTB to INTB
+ * and so on, which is why this mapping function is different.
+ */
+ return pci_swizzle_map_irq_fn(d, irq_num + 3);
+}
+
+static void pci_vpb_set_irq(void *opaque, int irq_num, int level)
+{
+ qemu_irq *pic = opaque;
+
+ qemu_set_irq(pic[irq_num], level);
+}
+
+static void pci_vpb_reset(DeviceState *d)
+{
+ PCIVPBState *s = PCI_VPB(d);
+
+ s->imap[0] = 0;
+ s->imap[1] = 0;
+ s->imap[2] = 0;
+ s->smap[0] = 0;
+ s->smap[1] = 0;
+ s->smap[2] = 0;
+ s->selfid = 0;
+ s->flags = 0;
+ s->irq_mapping = s->irq_mapping_prop;
+
+ pci_vpb_update_all_windows(s);
+}
+
+static void pci_vpb_init(Object *obj)
+{
+ PCIHostState *h = PCI_HOST_BRIDGE(obj);
+ PCIVPBState *s = PCI_VPB(obj);
+
+ memory_region_init(&s->pci_io_space, OBJECT(s), "pci_io", 1ULL << 32);
+ memory_region_init(&s->pci_mem_space, OBJECT(s), "pci_mem", 1ULL << 32);
+
+ pci_bus_new_inplace(&s->pci_bus, sizeof(s->pci_bus), DEVICE(obj), "pci",
+ &s->pci_mem_space, &s->pci_io_space,
+ PCI_DEVFN(11, 0), TYPE_PCI_BUS);
+ h->bus = &s->pci_bus;
+
+ object_initialize(&s->pci_dev, sizeof(s->pci_dev), TYPE_VERSATILE_PCI_HOST);
+ qdev_set_parent_bus(DEVICE(&s->pci_dev), BUS(&s->pci_bus));
+
+ /* Window sizes for VersatilePB; realview_pci's init will override */
+ s->mem_win_size[0] = 0x0c000000;
+ s->mem_win_size[1] = 0x10000000;
+ s->mem_win_size[2] = 0x10000000;
+}
+
+static void pci_vpb_realize(DeviceState *dev, Error **errp)
+{
+ PCIVPBState *s = PCI_VPB(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ pci_map_irq_fn mapfn;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ sysbus_init_irq(sbd, &s->irq[i]);
+ }
+
+ if (s->realview) {
+ mapfn = pci_vpb_rv_map_irq;
+ } else {
+ mapfn = pci_vpb_map_irq;
+ }
+
+ pci_bus_irqs(&s->pci_bus, pci_vpb_set_irq, mapfn, s->irq, 4);
+
+ /* Our memory regions are:
+ * 0 : our control registers
+ * 1 : PCI self config window
+ * 2 : PCI config window
+ * 3 : PCI IO window
+ * 4..6 : PCI memory windows
+ */
+ memory_region_init_io(&s->controlregs, OBJECT(s), &pci_vpb_reg_ops, s,
+ "pci-vpb-regs", 0x1000);
+ sysbus_init_mmio(sbd, &s->controlregs);
+ memory_region_init_io(&s->mem_config, OBJECT(s), &pci_vpb_config_ops, s,
+ "pci-vpb-selfconfig", 0x1000000);
+ sysbus_init_mmio(sbd, &s->mem_config);
+ memory_region_init_io(&s->mem_config2, OBJECT(s), &pci_vpb_config_ops, s,
+ "pci-vpb-config", 0x1000000);
+ sysbus_init_mmio(sbd, &s->mem_config2);
+
+ /* The window into I/O space is always into a fixed base address;
+ * its size is the same for both realview and versatile.
+ */
+ memory_region_init_alias(&s->pci_io_window, OBJECT(s), "pci-vbp-io-window",
+ &s->pci_io_space, 0, 0x100000);
+
+ sysbus_init_mmio(sbd, &s->pci_io_space);
+
+ /* Create the alias regions corresponding to our three windows onto
+ * PCI memory space. The sizes vary from board to board; the base
+ * offsets are guest controllable via the IMAP registers.
+ */
+ for (i = 0; i < 3; i++) {
+ memory_region_init_alias(&s->pci_mem_window[i], OBJECT(s), "pci-vbp-window",
+ &s->pci_mem_space, 0, s->mem_win_size[i]);
+ sysbus_init_mmio(sbd, &s->pci_mem_window[i]);
+ }
+
+ /* TODO Remove once realize propagates to child devices. */
+ object_property_set_bool(OBJECT(&s->pci_dev), true, "realized", errp);
+}
+
+static void versatile_pci_host_realize(PCIDevice *d, Error **errp)
+{
+ pci_set_word(d->config + PCI_STATUS,
+ PCI_STATUS_66MHZ | PCI_STATUS_DEVSEL_MEDIUM);
+ pci_set_byte(d->config + PCI_LATENCY_TIMER, 0x10);
+}
+
+static void versatile_pci_host_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = versatile_pci_host_realize;
+ k->vendor_id = PCI_VENDOR_ID_XILINX;
+ k->device_id = PCI_DEVICE_ID_XILINX_XC2VP30;
+ k->class_id = PCI_CLASS_PROCESSOR_CO;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo versatile_pci_host_info = {
+ .name = TYPE_VERSATILE_PCI_HOST,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = versatile_pci_host_class_init,
+};
+
+static Property pci_vpb_properties[] = {
+ DEFINE_PROP_UINT8("broken-irq-mapping", PCIVPBState, irq_mapping_prop,
+ PCI_VPB_IRQMAP_ASSUME_OK),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void pci_vpb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pci_vpb_realize;
+ dc->reset = pci_vpb_reset;
+ dc->vmsd = &pci_vpb_vmstate;
+ dc->props = pci_vpb_properties;
+ /* Reason: object_unref() hangs */
+ dc->cannot_destroy_with_object_finalize_yet = true;
+}
+
+static const TypeInfo pci_vpb_info = {
+ .name = TYPE_VERSATILE_PCI,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(PCIVPBState),
+ .instance_init = pci_vpb_init,
+ .class_init = pci_vpb_class_init,
+};
+
+static void pci_realview_init(Object *obj)
+{
+ PCIVPBState *s = PCI_VPB(obj);
+
+ s->realview = 1;
+ /* The PCI window sizes are different on Realview boards */
+ s->mem_win_size[0] = 0x01000000;
+ s->mem_win_size[1] = 0x04000000;
+ s->mem_win_size[2] = 0x08000000;
+}
+
+static void pci_realview_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+
+ /* Reason: object_unref() hangs */
+ dc->cannot_destroy_with_object_finalize_yet = true;
+}
+
+static const TypeInfo pci_realview_info = {
+ .name = "realview_pci",
+ .parent = TYPE_VERSATILE_PCI,
+ .instance_init = pci_realview_init,
+ .class_init = pci_realview_class_init,
+};
+
+static void versatile_pci_register_types(void)
+{
+ type_register_static(&pci_vpb_info);
+ type_register_static(&pci_realview_info);
+ type_register_static(&versatile_pci_host_info);
+}
+
+type_init(versatile_pci_register_types)
diff --git a/hw/pci/Makefile.objs b/hw/pci/Makefile.objs
new file mode 100644
index 00000000..9f905e63
--- /dev/null
+++ b/hw/pci/Makefile.objs
@@ -0,0 +1,9 @@
+common-obj-$(CONFIG_PCI) += pci.o pci_bridge.o
+common-obj-$(CONFIG_PCI) += msix.o msi.o
+common-obj-$(CONFIG_PCI) += shpc.o
+common-obj-$(CONFIG_PCI) += slotid_cap.o
+common-obj-$(CONFIG_PCI) += pci_host.o pcie_host.o
+common-obj-$(CONFIG_PCI) += pcie.o pcie_aer.o pcie_port.o
+
+common-obj-$(call lnot,$(CONFIG_PCI)) += pci-stub.o
+common-obj-$(CONFIG_ALL) += pci-stub.o
diff --git a/hw/pci/msi.c b/hw/pci/msi.c
new file mode 100644
index 00000000..f9c04844
--- /dev/null
+++ b/hw/pci/msi.c
@@ -0,0 +1,400 @@
+/*
+ * msi.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/pci/msi.h"
+#include "qemu/range.h"
+
+/* PCI_MSI_ADDRESS_LO */
+#define PCI_MSI_ADDRESS_LO_MASK (~0x3)
+
+/* If we get rid of cap allocator, we won't need those. */
+#define PCI_MSI_32_SIZEOF 0x0a
+#define PCI_MSI_64_SIZEOF 0x0e
+#define PCI_MSI_32M_SIZEOF 0x14
+#define PCI_MSI_64M_SIZEOF 0x18
+
+#define PCI_MSI_VECTORS_MAX 32
+
+/* Flag for interrupt controller to declare MSI/MSI-X support */
+bool msi_supported;
+
+/* If we get rid of cap allocator, we won't need this. */
+static inline uint8_t msi_cap_sizeof(uint16_t flags)
+{
+ switch (flags & (PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT)) {
+ case PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT:
+ return PCI_MSI_64M_SIZEOF;
+ case PCI_MSI_FLAGS_64BIT:
+ return PCI_MSI_64_SIZEOF;
+ case PCI_MSI_FLAGS_MASKBIT:
+ return PCI_MSI_32M_SIZEOF;
+ case 0:
+ return PCI_MSI_32_SIZEOF;
+ default:
+ abort();
+ break;
+ }
+ return 0;
+}
+
+//#define MSI_DEBUG
+
+#ifdef MSI_DEBUG
+# define MSI_DPRINTF(fmt, ...) \
+ fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
+#else
+# define MSI_DPRINTF(fmt, ...) do { } while (0)
+#endif
+#define MSI_DEV_PRINTF(dev, fmt, ...) \
+ MSI_DPRINTF("%s:%x " fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
+
+static inline unsigned int msi_nr_vectors(uint16_t flags)
+{
+ return 1U <<
+ ((flags & PCI_MSI_FLAGS_QSIZE) >> ctz32(PCI_MSI_FLAGS_QSIZE));
+}
+
+static inline uint8_t msi_flags_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_FLAGS;
+}
+
+static inline uint8_t msi_address_lo_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_ADDRESS_LO;
+}
+
+static inline uint8_t msi_address_hi_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_ADDRESS_HI;
+}
+
+static inline uint8_t msi_data_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_DATA_64 : PCI_MSI_DATA_32);
+}
+
+static inline uint8_t msi_mask_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_MASK_64 : PCI_MSI_MASK_32);
+}
+
+static inline uint8_t msi_pending_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_PENDING_64 : PCI_MSI_PENDING_32);
+}
+
+/*
+ * Special API for POWER to configure the vectors through
+ * a side channel. Should never be used by devices.
+ */
+void msi_set_message(PCIDevice *dev, MSIMessage msg)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+
+ if (msi64bit) {
+ pci_set_quad(dev->config + msi_address_lo_off(dev), msg.address);
+ } else {
+ pci_set_long(dev->config + msi_address_lo_off(dev), msg.address);
+ }
+ pci_set_word(dev->config + msi_data_off(dev, msi64bit), msg.data);
+}
+
+MSIMessage msi_get_message(PCIDevice *dev, unsigned int vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ unsigned int nr_vectors = msi_nr_vectors(flags);
+ MSIMessage msg;
+
+ assert(vector < nr_vectors);
+
+ if (msi64bit) {
+ msg.address = pci_get_quad(dev->config + msi_address_lo_off(dev));
+ } else {
+ msg.address = pci_get_long(dev->config + msi_address_lo_off(dev));
+ }
+
+ /* upper bit 31:16 is zero */
+ msg.data = pci_get_word(dev->config + msi_data_off(dev, msi64bit));
+ if (nr_vectors > 1) {
+ msg.data &= ~(nr_vectors - 1);
+ msg.data |= vector;
+ }
+
+ return msg;
+}
+
+bool msi_enabled(const PCIDevice *dev)
+{
+ return msi_present(dev) &&
+ (pci_get_word(dev->config + msi_flags_off(dev)) &
+ PCI_MSI_FLAGS_ENABLE);
+}
+
+int msi_init(struct PCIDevice *dev, uint8_t offset,
+ unsigned int nr_vectors, bool msi64bit, bool msi_per_vector_mask)
+{
+ unsigned int vectors_order;
+ uint16_t flags;
+ uint8_t cap_size;
+ int config_offset;
+
+ if (!msi_supported) {
+ return -ENOTSUP;
+ }
+
+ MSI_DEV_PRINTF(dev,
+ "init offset: 0x%"PRIx8" vector: %"PRId8
+ " 64bit %d mask %d\n",
+ offset, nr_vectors, msi64bit, msi_per_vector_mask);
+
+ assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */
+ assert(nr_vectors > 0);
+ assert(nr_vectors <= PCI_MSI_VECTORS_MAX);
+ /* the nr of MSI vectors is up to 32 */
+ vectors_order = ctz32(nr_vectors);
+
+ flags = vectors_order << ctz32(PCI_MSI_FLAGS_QMASK);
+ if (msi64bit) {
+ flags |= PCI_MSI_FLAGS_64BIT;
+ }
+ if (msi_per_vector_mask) {
+ flags |= PCI_MSI_FLAGS_MASKBIT;
+ }
+
+ cap_size = msi_cap_sizeof(flags);
+ config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset, cap_size);
+ if (config_offset < 0) {
+ return config_offset;
+ }
+
+ dev->msi_cap = config_offset;
+ dev->cap_present |= QEMU_PCI_CAP_MSI;
+
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ pci_set_word(dev->wmask + msi_flags_off(dev),
+ PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
+ pci_set_long(dev->wmask + msi_address_lo_off(dev),
+ PCI_MSI_ADDRESS_LO_MASK);
+ if (msi64bit) {
+ pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff);
+ }
+ pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff);
+
+ if (msi_per_vector_mask) {
+ /* Make mask bits 0 to nr_vectors - 1 writable. */
+ pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit),
+ 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors));
+ }
+ return config_offset;
+}
+
+void msi_uninit(struct PCIDevice *dev)
+{
+ uint16_t flags;
+ uint8_t cap_size;
+
+ if (!msi_present(dev)) {
+ return;
+ }
+ flags = pci_get_word(dev->config + msi_flags_off(dev));
+ cap_size = msi_cap_sizeof(flags);
+ pci_del_capability(dev, PCI_CAP_ID_MSI, cap_size);
+ dev->cap_present &= ~QEMU_PCI_CAP_MSI;
+
+ MSI_DEV_PRINTF(dev, "uninit\n");
+}
+
+void msi_reset(PCIDevice *dev)
+{
+ uint16_t flags;
+ bool msi64bit;
+
+ if (!msi_present(dev)) {
+ return;
+ }
+
+ flags = pci_get_word(dev->config + msi_flags_off(dev));
+ flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
+ msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ pci_set_long(dev->config + msi_address_lo_off(dev), 0);
+ if (msi64bit) {
+ pci_set_long(dev->config + msi_address_hi_off(dev), 0);
+ }
+ pci_set_word(dev->config + msi_data_off(dev, msi64bit), 0);
+ if (flags & PCI_MSI_FLAGS_MASKBIT) {
+ pci_set_long(dev->config + msi_mask_off(dev, msi64bit), 0);
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit), 0);
+ }
+ MSI_DEV_PRINTF(dev, "reset\n");
+}
+
+static bool msi_is_masked(const PCIDevice *dev, unsigned int vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ uint32_t mask;
+ assert(vector < PCI_MSI_VECTORS_MAX);
+
+ if (!(flags & PCI_MSI_FLAGS_MASKBIT)) {
+ return false;
+ }
+
+ mask = pci_get_long(dev->config +
+ msi_mask_off(dev, flags & PCI_MSI_FLAGS_64BIT));
+ return mask & (1U << vector);
+}
+
+void msi_notify(PCIDevice *dev, unsigned int vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ unsigned int nr_vectors = msi_nr_vectors(flags);
+ MSIMessage msg;
+
+ assert(vector < nr_vectors);
+ if (msi_is_masked(dev, vector)) {
+ assert(flags & PCI_MSI_FLAGS_MASKBIT);
+ pci_long_test_and_set_mask(
+ dev->config + msi_pending_off(dev, msi64bit), 1U << vector);
+ MSI_DEV_PRINTF(dev, "pending vector 0x%x\n", vector);
+ return;
+ }
+
+ msg = msi_get_message(dev, vector);
+
+ MSI_DEV_PRINTF(dev,
+ "notify vector 0x%x"
+ " address: 0x%"PRIx64" data: 0x%"PRIx32"\n",
+ vector, msg.address, msg.data);
+ msi_send_message(dev, msg);
+}
+
+void msi_send_message(PCIDevice *dev, MSIMessage msg)
+{
+ MemTxAttrs attrs = {};
+
+ attrs.stream_id = (pci_bus_num(dev->bus) << 8) | dev->devfn;
+ address_space_stl_le(&dev->bus_master_as, msg.address, msg.data,
+ attrs, NULL);
+}
+
+/* Normally called by pci_default_write_config(). */
+void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ bool msi_per_vector_mask = flags & PCI_MSI_FLAGS_MASKBIT;
+ unsigned int nr_vectors;
+ uint8_t log_num_vecs;
+ uint8_t log_max_vecs;
+ unsigned int vector;
+ uint32_t pending;
+
+ if (!msi_present(dev) ||
+ !ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
+ return;
+ }
+
+#ifdef MSI_DEBUG
+ MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n",
+ addr, val, len);
+ MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32,
+ flags,
+ pci_get_long(dev->config + msi_address_lo_off(dev)));
+ if (msi64bit) {
+ fprintf(stderr, " address-hi: 0x%"PRIx32,
+ pci_get_long(dev->config + msi_address_hi_off(dev)));
+ }
+ fprintf(stderr, " data: 0x%"PRIx16,
+ pci_get_word(dev->config + msi_data_off(dev, msi64bit)));
+ if (flags & PCI_MSI_FLAGS_MASKBIT) {
+ fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32,
+ pci_get_long(dev->config + msi_mask_off(dev, msi64bit)),
+ pci_get_long(dev->config + msi_pending_off(dev, msi64bit)));
+ }
+ fprintf(stderr, "\n");
+#endif
+
+ if (!(flags & PCI_MSI_FLAGS_ENABLE)) {
+ return;
+ }
+
+ /*
+ * Now MSI is enabled, clear INTx# interrupts.
+ * the driver is prohibited from writing enable bit to mask
+ * a service request. But the guest OS could do this.
+ * So we just discard the interrupts as moderate fallback.
+ *
+ * 6.8.3.3. Enabling Operation
+ * While enabled for MSI or MSI-X operation, a function is prohibited
+ * from using its INTx# pin (if implemented) to request
+ * service (MSI, MSI-X, and INTx# are mutually exclusive).
+ */
+ pci_device_deassert_intx(dev);
+
+ /*
+ * nr_vectors might be set bigger than capable. So clamp it.
+ * This is not legal by spec, so we can do anything we like,
+ * just don't crash the host
+ */
+ log_num_vecs =
+ (flags & PCI_MSI_FLAGS_QSIZE) >> ctz32(PCI_MSI_FLAGS_QSIZE);
+ log_max_vecs =
+ (flags & PCI_MSI_FLAGS_QMASK) >> ctz32(PCI_MSI_FLAGS_QMASK);
+ if (log_num_vecs > log_max_vecs) {
+ flags &= ~PCI_MSI_FLAGS_QSIZE;
+ flags |= log_max_vecs << ctz32(PCI_MSI_FLAGS_QSIZE);
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ }
+
+ if (!msi_per_vector_mask) {
+ /* if per vector masking isn't supported,
+ there is no pending interrupt. */
+ return;
+ }
+
+ nr_vectors = msi_nr_vectors(flags);
+
+ /* This will discard pending interrupts, if any. */
+ pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
+ pending &= 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors);
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
+
+ /* deliver pending interrupts which are unmasked */
+ for (vector = 0; vector < nr_vectors; ++vector) {
+ if (msi_is_masked(dev, vector) || !(pending & (1U << vector))) {
+ continue;
+ }
+
+ pci_long_test_and_clear_mask(
+ dev->config + msi_pending_off(dev, msi64bit), 1U << vector);
+ msi_notify(dev, vector);
+ }
+}
+
+unsigned int msi_nr_vectors_allocated(const PCIDevice *dev)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ return msi_nr_vectors(flags);
+}
diff --git a/hw/pci/msix.c b/hw/pci/msix.c
new file mode 100644
index 00000000..e91b2cb7
--- /dev/null
+++ b/hw/pci/msix.c
@@ -0,0 +1,614 @@
+/*
+ * MSI-X device support
+ *
+ * This module includes support for MSI-X in pci devices.
+ *
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ *
+ * Copyright (c) 2009, Red Hat Inc, Michael S. Tsirkin (mst@redhat.com)
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "hw/pci/pci.h"
+#include "qemu/range.h"
+
+#define MSIX_CAP_LENGTH 12
+
+/* MSI enable bit and maskall bit are in byte 1 in FLAGS register */
+#define MSIX_CONTROL_OFFSET (PCI_MSIX_FLAGS + 1)
+#define MSIX_ENABLE_MASK (PCI_MSIX_FLAGS_ENABLE >> 8)
+#define MSIX_MASKALL_MASK (PCI_MSIX_FLAGS_MASKALL >> 8)
+
+MSIMessage msix_get_message(PCIDevice *dev, unsigned vector)
+{
+ uint8_t *table_entry = dev->msix_table + vector * PCI_MSIX_ENTRY_SIZE;
+ MSIMessage msg;
+
+ msg.address = pci_get_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR);
+ msg.data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA);
+ return msg;
+}
+
+/*
+ * Special API for POWER to configure the vectors through
+ * a side channel. Should never be used by devices.
+ */
+void msix_set_message(PCIDevice *dev, int vector, struct MSIMessage msg)
+{
+ uint8_t *table_entry = dev->msix_table + vector * PCI_MSIX_ENTRY_SIZE;
+
+ pci_set_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR, msg.address);
+ pci_set_long(table_entry + PCI_MSIX_ENTRY_DATA, msg.data);
+ table_entry[PCI_MSIX_ENTRY_VECTOR_CTRL] &= ~PCI_MSIX_ENTRY_CTRL_MASKBIT;
+}
+
+static uint8_t msix_pending_mask(int vector)
+{
+ return 1 << (vector % 8);
+}
+
+static uint8_t *msix_pending_byte(PCIDevice *dev, int vector)
+{
+ return dev->msix_pba + vector / 8;
+}
+
+static int msix_is_pending(PCIDevice *dev, int vector)
+{
+ return *msix_pending_byte(dev, vector) & msix_pending_mask(vector);
+}
+
+void msix_set_pending(PCIDevice *dev, unsigned int vector)
+{
+ *msix_pending_byte(dev, vector) |= msix_pending_mask(vector);
+}
+
+static void msix_clr_pending(PCIDevice *dev, int vector)
+{
+ *msix_pending_byte(dev, vector) &= ~msix_pending_mask(vector);
+}
+
+static bool msix_vector_masked(PCIDevice *dev, unsigned int vector, bool fmask)
+{
+ unsigned offset = vector * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL;
+ return fmask || dev->msix_table[offset] & PCI_MSIX_ENTRY_CTRL_MASKBIT;
+}
+
+bool msix_is_masked(PCIDevice *dev, unsigned int vector)
+{
+ return msix_vector_masked(dev, vector, dev->msix_function_masked);
+}
+
+static void msix_fire_vector_notifier(PCIDevice *dev,
+ unsigned int vector, bool is_masked)
+{
+ MSIMessage msg;
+ int ret;
+
+ if (!dev->msix_vector_use_notifier) {
+ return;
+ }
+ if (is_masked) {
+ dev->msix_vector_release_notifier(dev, vector);
+ } else {
+ msg = msix_get_message(dev, vector);
+ ret = dev->msix_vector_use_notifier(dev, vector, msg);
+ assert(ret >= 0);
+ }
+}
+
+static void msix_handle_mask_update(PCIDevice *dev, int vector, bool was_masked)
+{
+ bool is_masked = msix_is_masked(dev, vector);
+
+ if (is_masked == was_masked) {
+ return;
+ }
+
+ msix_fire_vector_notifier(dev, vector, is_masked);
+
+ if (!is_masked && msix_is_pending(dev, vector)) {
+ msix_clr_pending(dev, vector);
+ msix_notify(dev, vector);
+ }
+}
+
+static void msix_update_function_masked(PCIDevice *dev)
+{
+ dev->msix_function_masked = !msix_enabled(dev) ||
+ (dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] & MSIX_MASKALL_MASK);
+}
+
+/* Handle MSI-X capability config write. */
+void msix_write_config(PCIDevice *dev, uint32_t addr,
+ uint32_t val, int len)
+{
+ unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET;
+ int vector;
+ bool was_masked;
+
+ if (!msix_present(dev) || !range_covers_byte(addr, len, enable_pos)) {
+ return;
+ }
+
+ was_masked = dev->msix_function_masked;
+ msix_update_function_masked(dev);
+
+ if (!msix_enabled(dev)) {
+ return;
+ }
+
+ pci_device_deassert_intx(dev);
+
+ if (dev->msix_function_masked == was_masked) {
+ return;
+ }
+
+ for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
+ msix_handle_mask_update(dev, vector,
+ msix_vector_masked(dev, vector, was_masked));
+ }
+}
+
+static uint64_t msix_table_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCIDevice *dev = opaque;
+
+ return pci_get_long(dev->msix_table + addr);
+}
+
+static void msix_table_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIDevice *dev = opaque;
+ int vector = addr / PCI_MSIX_ENTRY_SIZE;
+ bool was_masked;
+
+ was_masked = msix_is_masked(dev, vector);
+ pci_set_long(dev->msix_table + addr, val);
+ msix_handle_mask_update(dev, vector, was_masked);
+}
+
+static const MemoryRegionOps msix_table_mmio_ops = {
+ .read = msix_table_mmio_read,
+ .write = msix_table_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t msix_pba_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCIDevice *dev = opaque;
+ if (dev->msix_vector_poll_notifier) {
+ unsigned vector_start = addr * 8;
+ unsigned vector_end = MIN(addr + size * 8, dev->msix_entries_nr);
+ dev->msix_vector_poll_notifier(dev, vector_start, vector_end);
+ }
+
+ return pci_get_long(dev->msix_pba + addr);
+}
+
+static void msix_pba_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+}
+
+static const MemoryRegionOps msix_pba_mmio_ops = {
+ .read = msix_pba_mmio_read,
+ .write = msix_pba_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void msix_mask_all(struct PCIDevice *dev, unsigned nentries)
+{
+ int vector;
+
+ for (vector = 0; vector < nentries; ++vector) {
+ unsigned offset =
+ vector * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL;
+ bool was_masked = msix_is_masked(dev, vector);
+
+ dev->msix_table[offset] |= PCI_MSIX_ENTRY_CTRL_MASKBIT;
+ msix_handle_mask_update(dev, vector, was_masked);
+ }
+}
+
+/* Initialize the MSI-X structures */
+int msix_init(struct PCIDevice *dev, unsigned short nentries,
+ MemoryRegion *table_bar, uint8_t table_bar_nr,
+ unsigned table_offset, MemoryRegion *pba_bar,
+ uint8_t pba_bar_nr, unsigned pba_offset, uint8_t cap_pos)
+{
+ int cap;
+ unsigned table_size, pba_size;
+ uint8_t *config;
+
+ /* Nothing to do if MSI is not supported by interrupt controller */
+ if (!msi_supported) {
+ return -ENOTSUP;
+ }
+
+ if (nentries < 1 || nentries > PCI_MSIX_FLAGS_QSIZE + 1) {
+ return -EINVAL;
+ }
+
+ table_size = nentries * PCI_MSIX_ENTRY_SIZE;
+ pba_size = QEMU_ALIGN_UP(nentries, 64) / 8;
+
+ /* Sanity test: table & pba don't overlap, fit within BARs, min aligned */
+ if ((table_bar_nr == pba_bar_nr &&
+ ranges_overlap(table_offset, table_size, pba_offset, pba_size)) ||
+ table_offset + table_size > memory_region_size(table_bar) ||
+ pba_offset + pba_size > memory_region_size(pba_bar) ||
+ (table_offset | pba_offset) & PCI_MSIX_FLAGS_BIRMASK) {
+ return -EINVAL;
+ }
+
+ cap = pci_add_capability(dev, PCI_CAP_ID_MSIX, cap_pos, MSIX_CAP_LENGTH);
+ if (cap < 0) {
+ return cap;
+ }
+
+ dev->msix_cap = cap;
+ dev->cap_present |= QEMU_PCI_CAP_MSIX;
+ config = dev->config + cap;
+
+ pci_set_word(config + PCI_MSIX_FLAGS, nentries - 1);
+ dev->msix_entries_nr = nentries;
+ dev->msix_function_masked = true;
+
+ pci_set_long(config + PCI_MSIX_TABLE, table_offset | table_bar_nr);
+ pci_set_long(config + PCI_MSIX_PBA, pba_offset | pba_bar_nr);
+
+ /* Make flags bit writable. */
+ dev->wmask[cap + MSIX_CONTROL_OFFSET] |= MSIX_ENABLE_MASK |
+ MSIX_MASKALL_MASK;
+
+ dev->msix_table = g_malloc0(table_size);
+ dev->msix_pba = g_malloc0(pba_size);
+ dev->msix_entry_used = g_malloc0(nentries * sizeof *dev->msix_entry_used);
+
+ msix_mask_all(dev, nentries);
+
+ memory_region_init_io(&dev->msix_table_mmio, OBJECT(dev), &msix_table_mmio_ops, dev,
+ "msix-table", table_size);
+ memory_region_add_subregion(table_bar, table_offset, &dev->msix_table_mmio);
+ memory_region_init_io(&dev->msix_pba_mmio, OBJECT(dev), &msix_pba_mmio_ops, dev,
+ "msix-pba", pba_size);
+ memory_region_add_subregion(pba_bar, pba_offset, &dev->msix_pba_mmio);
+
+ return 0;
+}
+
+int msix_init_exclusive_bar(PCIDevice *dev, unsigned short nentries,
+ uint8_t bar_nr)
+{
+ int ret;
+ char *name;
+ uint32_t bar_size = 4096;
+ uint32_t bar_pba_offset = bar_size / 2;
+ uint32_t bar_pba_size = (nentries / 8 + 1) * 8;
+
+ /*
+ * Migration compatibility dictates that this remains a 4k
+ * BAR with the vector table in the lower half and PBA in
+ * the upper half for nentries which is lower or equal to 128.
+ * No need to care about using more than 65 entries for legacy
+ * machine types who has at most 64 queues.
+ */
+ if (nentries * PCI_MSIX_ENTRY_SIZE > bar_pba_offset) {
+ bar_pba_offset = nentries * PCI_MSIX_ENTRY_SIZE;
+ }
+
+ if (bar_pba_offset + bar_pba_size > 4096) {
+ bar_size = bar_pba_offset + bar_pba_size;
+ }
+
+ if (bar_size & (bar_size - 1)) {
+ bar_size = 1 << qemu_fls(bar_size);
+ }
+
+ name = g_strdup_printf("%s-msix", dev->name);
+ memory_region_init(&dev->msix_exclusive_bar, OBJECT(dev), name, bar_size);
+ g_free(name);
+
+ ret = msix_init(dev, nentries, &dev->msix_exclusive_bar, bar_nr,
+ 0, &dev->msix_exclusive_bar,
+ bar_nr, bar_pba_offset,
+ 0);
+ if (ret) {
+ return ret;
+ }
+
+ pci_register_bar(dev, bar_nr, PCI_BASE_ADDRESS_SPACE_MEMORY,
+ &dev->msix_exclusive_bar);
+
+ return 0;
+}
+
+static void msix_free_irq_entries(PCIDevice *dev)
+{
+ int vector;
+
+ for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
+ dev->msix_entry_used[vector] = 0;
+ msix_clr_pending(dev, vector);
+ }
+}
+
+static void msix_clear_all_vectors(PCIDevice *dev)
+{
+ int vector;
+
+ for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
+ msix_clr_pending(dev, vector);
+ }
+}
+
+/* Clean up resources for the device. */
+void msix_uninit(PCIDevice *dev, MemoryRegion *table_bar, MemoryRegion *pba_bar)
+{
+ if (!msix_present(dev)) {
+ return;
+ }
+ pci_del_capability(dev, PCI_CAP_ID_MSIX, MSIX_CAP_LENGTH);
+ dev->msix_cap = 0;
+ msix_free_irq_entries(dev);
+ dev->msix_entries_nr = 0;
+ memory_region_del_subregion(pba_bar, &dev->msix_pba_mmio);
+ g_free(dev->msix_pba);
+ dev->msix_pba = NULL;
+ memory_region_del_subregion(table_bar, &dev->msix_table_mmio);
+ g_free(dev->msix_table);
+ dev->msix_table = NULL;
+ g_free(dev->msix_entry_used);
+ dev->msix_entry_used = NULL;
+ dev->cap_present &= ~QEMU_PCI_CAP_MSIX;
+}
+
+void msix_uninit_exclusive_bar(PCIDevice *dev)
+{
+ if (msix_present(dev)) {
+ msix_uninit(dev, &dev->msix_exclusive_bar, &dev->msix_exclusive_bar);
+ }
+}
+
+void msix_save(PCIDevice *dev, QEMUFile *f)
+{
+ unsigned n = dev->msix_entries_nr;
+
+ if (!msix_present(dev)) {
+ return;
+ }
+
+ qemu_put_buffer(f, dev->msix_table, n * PCI_MSIX_ENTRY_SIZE);
+ qemu_put_buffer(f, dev->msix_pba, (n + 7) / 8);
+}
+
+/* Should be called after restoring the config space. */
+void msix_load(PCIDevice *dev, QEMUFile *f)
+{
+ unsigned n = dev->msix_entries_nr;
+ unsigned int vector;
+
+ if (!msix_present(dev)) {
+ return;
+ }
+
+ msix_clear_all_vectors(dev);
+ qemu_get_buffer(f, dev->msix_table, n * PCI_MSIX_ENTRY_SIZE);
+ qemu_get_buffer(f, dev->msix_pba, (n + 7) / 8);
+ msix_update_function_masked(dev);
+
+ for (vector = 0; vector < n; vector++) {
+ msix_handle_mask_update(dev, vector, true);
+ }
+}
+
+/* Does device support MSI-X? */
+int msix_present(PCIDevice *dev)
+{
+ return dev->cap_present & QEMU_PCI_CAP_MSIX;
+}
+
+/* Is MSI-X enabled? */
+int msix_enabled(PCIDevice *dev)
+{
+ return (dev->cap_present & QEMU_PCI_CAP_MSIX) &&
+ (dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &
+ MSIX_ENABLE_MASK);
+}
+
+/* Send an MSI-X message */
+void msix_notify(PCIDevice *dev, unsigned vector)
+{
+ MSIMessage msg;
+
+ if (vector >= dev->msix_entries_nr || !dev->msix_entry_used[vector])
+ return;
+ if (msix_is_masked(dev, vector)) {
+ msix_set_pending(dev, vector);
+ return;
+ }
+
+ msg = msix_get_message(dev, vector);
+
+ msi_send_message(dev, msg);
+}
+
+void msix_reset(PCIDevice *dev)
+{
+ if (!msix_present(dev)) {
+ return;
+ }
+ msix_clear_all_vectors(dev);
+ dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &=
+ ~dev->wmask[dev->msix_cap + MSIX_CONTROL_OFFSET];
+ memset(dev->msix_table, 0, dev->msix_entries_nr * PCI_MSIX_ENTRY_SIZE);
+ memset(dev->msix_pba, 0, QEMU_ALIGN_UP(dev->msix_entries_nr, 64) / 8);
+ msix_mask_all(dev, dev->msix_entries_nr);
+}
+
+/* PCI spec suggests that devices make it possible for software to configure
+ * less vectors than supported by the device, but does not specify a standard
+ * mechanism for devices to do so.
+ *
+ * We support this by asking devices to declare vectors software is going to
+ * actually use, and checking this on the notification path. Devices that
+ * don't want to follow the spec suggestion can declare all vectors as used. */
+
+/* Mark vector as used. */
+int msix_vector_use(PCIDevice *dev, unsigned vector)
+{
+ if (vector >= dev->msix_entries_nr)
+ return -EINVAL;
+ dev->msix_entry_used[vector]++;
+ return 0;
+}
+
+/* Mark vector as unused. */
+void msix_vector_unuse(PCIDevice *dev, unsigned vector)
+{
+ if (vector >= dev->msix_entries_nr || !dev->msix_entry_used[vector]) {
+ return;
+ }
+ if (--dev->msix_entry_used[vector]) {
+ return;
+ }
+ msix_clr_pending(dev, vector);
+}
+
+void msix_unuse_all_vectors(PCIDevice *dev)
+{
+ if (!msix_present(dev)) {
+ return;
+ }
+ msix_free_irq_entries(dev);
+}
+
+unsigned int msix_nr_vectors_allocated(const PCIDevice *dev)
+{
+ return dev->msix_entries_nr;
+}
+
+static int msix_set_notifier_for_vector(PCIDevice *dev, unsigned int vector)
+{
+ MSIMessage msg;
+
+ if (msix_is_masked(dev, vector)) {
+ return 0;
+ }
+ msg = msix_get_message(dev, vector);
+ return dev->msix_vector_use_notifier(dev, vector, msg);
+}
+
+static void msix_unset_notifier_for_vector(PCIDevice *dev, unsigned int vector)
+{
+ if (msix_is_masked(dev, vector)) {
+ return;
+ }
+ dev->msix_vector_release_notifier(dev, vector);
+}
+
+int msix_set_vector_notifiers(PCIDevice *dev,
+ MSIVectorUseNotifier use_notifier,
+ MSIVectorReleaseNotifier release_notifier,
+ MSIVectorPollNotifier poll_notifier)
+{
+ int vector, ret;
+
+ assert(use_notifier && release_notifier);
+
+ dev->msix_vector_use_notifier = use_notifier;
+ dev->msix_vector_release_notifier = release_notifier;
+ dev->msix_vector_poll_notifier = poll_notifier;
+
+ if ((dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &
+ (MSIX_ENABLE_MASK | MSIX_MASKALL_MASK)) == MSIX_ENABLE_MASK) {
+ for (vector = 0; vector < dev->msix_entries_nr; vector++) {
+ ret = msix_set_notifier_for_vector(dev, vector);
+ if (ret < 0) {
+ goto undo;
+ }
+ }
+ }
+ if (dev->msix_vector_poll_notifier) {
+ dev->msix_vector_poll_notifier(dev, 0, dev->msix_entries_nr);
+ }
+ return 0;
+
+undo:
+ while (--vector >= 0) {
+ msix_unset_notifier_for_vector(dev, vector);
+ }
+ dev->msix_vector_use_notifier = NULL;
+ dev->msix_vector_release_notifier = NULL;
+ return ret;
+}
+
+void msix_unset_vector_notifiers(PCIDevice *dev)
+{
+ int vector;
+
+ assert(dev->msix_vector_use_notifier &&
+ dev->msix_vector_release_notifier);
+
+ if ((dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &
+ (MSIX_ENABLE_MASK | MSIX_MASKALL_MASK)) == MSIX_ENABLE_MASK) {
+ for (vector = 0; vector < dev->msix_entries_nr; vector++) {
+ msix_unset_notifier_for_vector(dev, vector);
+ }
+ }
+ dev->msix_vector_use_notifier = NULL;
+ dev->msix_vector_release_notifier = NULL;
+ dev->msix_vector_poll_notifier = NULL;
+}
+
+static void put_msix_state(QEMUFile *f, void *pv, size_t size)
+{
+ msix_save(pv, f);
+}
+
+static int get_msix_state(QEMUFile *f, void *pv, size_t size)
+{
+ msix_load(pv, f);
+ return 0;
+}
+
+static VMStateInfo vmstate_info_msix = {
+ .name = "msix state",
+ .get = get_msix_state,
+ .put = put_msix_state,
+};
+
+const VMStateDescription vmstate_msix = {
+ .name = "msix",
+ .fields = (VMStateField[]) {
+ {
+ .name = "msix",
+ .version_id = 0,
+ .field_exists = NULL,
+ .size = 0, /* ouch */
+ .info = &vmstate_info_msix,
+ .flags = VMS_SINGLE,
+ .offset = 0,
+ },
+ VMSTATE_END_OF_LIST()
+ }
+};
diff --git a/hw/pci/pci-stub.c b/hw/pci/pci-stub.c
new file mode 100644
index 00000000..063a7c24
--- /dev/null
+++ b/hw/pci/pci-stub.c
@@ -0,0 +1,36 @@
+/*
+ * PCI stubs for platforms that don't support pci bus.
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysemu/sysemu.h"
+#include "monitor/monitor.h"
+#include "qapi/qmp/qerror.h"
+#include "hw/pci/pci.h"
+#include "qmp-commands.h"
+
+PciInfoList *qmp_query_pci(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+void hmp_pcie_aer_inject_error(Monitor *mon, const QDict *qdict)
+{
+ monitor_printf(mon, "PCI devices not supported\n");
+}
diff --git a/hw/pci/pci.c b/hw/pci/pci.c
new file mode 100644
index 00000000..a017614d
--- /dev/null
+++ b/hw/pci/pci.c
@@ -0,0 +1,2472 @@
+/*
+ * QEMU PCI bus manager
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pci_host.h"
+#include "monitor/monitor.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/loader.h"
+#include "qemu/error-report.h"
+#include "qemu/range.h"
+#include "qmp-commands.h"
+#include "trace.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "exec/address-spaces.h"
+#include "hw/hotplug.h"
+
+//#define DEBUG_PCI
+#ifdef DEBUG_PCI
+# define PCI_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define PCI_DPRINTF(format, ...) do { } while (0)
+#endif
+
+static void pcibus_dev_print(Monitor *mon, DeviceState *dev, int indent);
+static char *pcibus_get_dev_path(DeviceState *dev);
+static char *pcibus_get_fw_dev_path(DeviceState *dev);
+static void pcibus_reset(BusState *qbus);
+
+static Property pci_props[] = {
+ DEFINE_PROP_PCI_DEVFN("addr", PCIDevice, devfn, -1),
+ DEFINE_PROP_STRING("romfile", PCIDevice, romfile),
+ DEFINE_PROP_UINT32("rombar", PCIDevice, rom_bar, 1),
+ DEFINE_PROP_BIT("multifunction", PCIDevice, cap_present,
+ QEMU_PCI_CAP_MULTIFUNCTION_BITNR, false),
+ DEFINE_PROP_BIT("command_serr_enable", PCIDevice, cap_present,
+ QEMU_PCI_CAP_SERR_BITNR, true),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static const VMStateDescription vmstate_pcibus = {
+ .name = "PCIBUS",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_EQUAL(nirq, PCIBus),
+ VMSTATE_VARRAY_INT32(irq_count, PCIBus,
+ nirq, 0, vmstate_info_int32,
+ int32_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pci_bus_realize(BusState *qbus, Error **errp)
+{
+ PCIBus *bus = PCI_BUS(qbus);
+
+ vmstate_register(NULL, -1, &vmstate_pcibus, bus);
+}
+
+static void pci_bus_unrealize(BusState *qbus, Error **errp)
+{
+ PCIBus *bus = PCI_BUS(qbus);
+
+ vmstate_unregister(NULL, &vmstate_pcibus, bus);
+}
+
+static bool pcibus_is_root(PCIBus *bus)
+{
+ return !bus->parent_dev;
+}
+
+static int pcibus_num(PCIBus *bus)
+{
+ if (pcibus_is_root(bus)) {
+ return 0; /* pci host bridge */
+ }
+ return bus->parent_dev->config[PCI_SECONDARY_BUS];
+}
+
+static uint16_t pcibus_numa_node(PCIBus *bus)
+{
+ return NUMA_NODE_UNASSIGNED;
+}
+
+static void pci_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+ PCIBusClass *pbc = PCI_BUS_CLASS(klass);
+
+ k->print_dev = pcibus_dev_print;
+ k->get_dev_path = pcibus_get_dev_path;
+ k->get_fw_dev_path = pcibus_get_fw_dev_path;
+ k->realize = pci_bus_realize;
+ k->unrealize = pci_bus_unrealize;
+ k->reset = pcibus_reset;
+
+ pbc->is_root = pcibus_is_root;
+ pbc->bus_num = pcibus_num;
+ pbc->numa_node = pcibus_numa_node;
+}
+
+static const TypeInfo pci_bus_info = {
+ .name = TYPE_PCI_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(PCIBus),
+ .class_size = sizeof(PCIBusClass),
+ .class_init = pci_bus_class_init,
+};
+
+static const TypeInfo pcie_bus_info = {
+ .name = TYPE_PCIE_BUS,
+ .parent = TYPE_PCI_BUS,
+};
+
+static PCIBus *pci_find_bus_nr(PCIBus *bus, int bus_num);
+static void pci_update_mappings(PCIDevice *d);
+static void pci_irq_handler(void *opaque, int irq_num, int level);
+static void pci_add_option_rom(PCIDevice *pdev, bool is_default_rom, Error **);
+static void pci_del_option_rom(PCIDevice *pdev);
+
+static uint16_t pci_default_sub_vendor_id = PCI_SUBVENDOR_ID_REDHAT_QUMRANET;
+static uint16_t pci_default_sub_device_id = PCI_SUBDEVICE_ID_QEMU;
+
+static QLIST_HEAD(, PCIHostState) pci_host_bridges;
+
+int pci_bar(PCIDevice *d, int reg)
+{
+ uint8_t type;
+
+ if (reg != PCI_ROM_SLOT)
+ return PCI_BASE_ADDRESS_0 + reg * 4;
+
+ type = d->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;
+ return type == PCI_HEADER_TYPE_BRIDGE ? PCI_ROM_ADDRESS1 : PCI_ROM_ADDRESS;
+}
+
+static inline int pci_irq_state(PCIDevice *d, int irq_num)
+{
+ return (d->irq_state >> irq_num) & 0x1;
+}
+
+static inline void pci_set_irq_state(PCIDevice *d, int irq_num, int level)
+{
+ d->irq_state &= ~(0x1 << irq_num);
+ d->irq_state |= level << irq_num;
+}
+
+static void pci_change_irq_level(PCIDevice *pci_dev, int irq_num, int change)
+{
+ PCIBus *bus;
+ for (;;) {
+ bus = pci_dev->bus;
+ irq_num = bus->map_irq(pci_dev, irq_num);
+ if (bus->set_irq)
+ break;
+ pci_dev = bus->parent_dev;
+ }
+ bus->irq_count[irq_num] += change;
+ bus->set_irq(bus->irq_opaque, irq_num, bus->irq_count[irq_num] != 0);
+}
+
+int pci_bus_get_irq_level(PCIBus *bus, int irq_num)
+{
+ assert(irq_num >= 0);
+ assert(irq_num < bus->nirq);
+ return !!bus->irq_count[irq_num];
+}
+
+/* Update interrupt status bit in config space on interrupt
+ * state change. */
+static void pci_update_irq_status(PCIDevice *dev)
+{
+ if (dev->irq_state) {
+ dev->config[PCI_STATUS] |= PCI_STATUS_INTERRUPT;
+ } else {
+ dev->config[PCI_STATUS] &= ~PCI_STATUS_INTERRUPT;
+ }
+}
+
+void pci_device_deassert_intx(PCIDevice *dev)
+{
+ int i;
+ for (i = 0; i < PCI_NUM_PINS; ++i) {
+ pci_irq_handler(dev, i, 0);
+ }
+}
+
+static void pci_do_device_reset(PCIDevice *dev)
+{
+ int r;
+
+ pci_device_deassert_intx(dev);
+ assert(dev->irq_state == 0);
+
+ /* Clear all writable bits */
+ pci_word_test_and_clear_mask(dev->config + PCI_COMMAND,
+ pci_get_word(dev->wmask + PCI_COMMAND) |
+ pci_get_word(dev->w1cmask + PCI_COMMAND));
+ pci_word_test_and_clear_mask(dev->config + PCI_STATUS,
+ pci_get_word(dev->wmask + PCI_STATUS) |
+ pci_get_word(dev->w1cmask + PCI_STATUS));
+ dev->config[PCI_CACHE_LINE_SIZE] = 0x0;
+ dev->config[PCI_INTERRUPT_LINE] = 0x0;
+ for (r = 0; r < PCI_NUM_REGIONS; ++r) {
+ PCIIORegion *region = &dev->io_regions[r];
+ if (!region->size) {
+ continue;
+ }
+
+ if (!(region->type & PCI_BASE_ADDRESS_SPACE_IO) &&
+ region->type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ pci_set_quad(dev->config + pci_bar(dev, r), region->type);
+ } else {
+ pci_set_long(dev->config + pci_bar(dev, r), region->type);
+ }
+ }
+ pci_update_mappings(dev);
+
+ msi_reset(dev);
+ msix_reset(dev);
+}
+
+/*
+ * This function is called on #RST and FLR.
+ * FLR if PCI_EXP_DEVCTL_BCR_FLR is set
+ */
+void pci_device_reset(PCIDevice *dev)
+{
+ qdev_reset_all(&dev->qdev);
+ pci_do_device_reset(dev);
+}
+
+/*
+ * Trigger pci bus reset under a given bus.
+ * Called via qbus_reset_all on RST# assert, after the devices
+ * have been reset qdev_reset_all-ed already.
+ */
+static void pcibus_reset(BusState *qbus)
+{
+ PCIBus *bus = DO_UPCAST(PCIBus, qbus, qbus);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bus->devices); ++i) {
+ if (bus->devices[i]) {
+ pci_do_device_reset(bus->devices[i]);
+ }
+ }
+
+ for (i = 0; i < bus->nirq; i++) {
+ assert(bus->irq_count[i] == 0);
+ }
+}
+
+static void pci_host_bus_register(PCIBus *bus, DeviceState *parent)
+{
+ PCIHostState *host_bridge = PCI_HOST_BRIDGE(parent);
+
+ QLIST_INSERT_HEAD(&pci_host_bridges, host_bridge, next);
+}
+
+PCIBus *pci_find_primary_bus(void)
+{
+ PCIBus *primary_bus = NULL;
+ PCIHostState *host;
+
+ QLIST_FOREACH(host, &pci_host_bridges, next) {
+ if (primary_bus) {
+ /* We have multiple root buses, refuse to select a primary */
+ return NULL;
+ }
+ primary_bus = host->bus;
+ }
+
+ return primary_bus;
+}
+
+PCIBus *pci_device_root_bus(const PCIDevice *d)
+{
+ PCIBus *bus = d->bus;
+
+ while (!pci_bus_is_root(bus)) {
+ d = bus->parent_dev;
+ assert(d != NULL);
+
+ bus = d->bus;
+ }
+
+ return bus;
+}
+
+const char *pci_root_bus_path(PCIDevice *dev)
+{
+ PCIBus *rootbus = pci_device_root_bus(dev);
+ PCIHostState *host_bridge = PCI_HOST_BRIDGE(rootbus->qbus.parent);
+ PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_GET_CLASS(host_bridge);
+
+ assert(host_bridge->bus == rootbus);
+
+ if (hc->root_bus_path) {
+ return (*hc->root_bus_path)(host_bridge, rootbus);
+ }
+
+ return rootbus->qbus.name;
+}
+
+static void pci_bus_init(PCIBus *bus, DeviceState *parent,
+ const char *name,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io,
+ uint8_t devfn_min)
+{
+ assert(PCI_FUNC(devfn_min) == 0);
+ bus->devfn_min = devfn_min;
+ bus->address_space_mem = address_space_mem;
+ bus->address_space_io = address_space_io;
+
+ /* host bridge */
+ QLIST_INIT(&bus->child);
+
+ pci_host_bus_register(bus, parent);
+}
+
+bool pci_bus_is_express(PCIBus *bus)
+{
+ return object_dynamic_cast(OBJECT(bus), TYPE_PCIE_BUS);
+}
+
+bool pci_bus_is_root(PCIBus *bus)
+{
+ return PCI_BUS_GET_CLASS(bus)->is_root(bus);
+}
+
+void pci_bus_new_inplace(PCIBus *bus, size_t bus_size, DeviceState *parent,
+ const char *name,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io,
+ uint8_t devfn_min, const char *typename)
+{
+ qbus_create_inplace(bus, bus_size, typename, parent, name);
+ pci_bus_init(bus, parent, name, address_space_mem,
+ address_space_io, devfn_min);
+}
+
+PCIBus *pci_bus_new(DeviceState *parent, const char *name,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io,
+ uint8_t devfn_min, const char *typename)
+{
+ PCIBus *bus;
+
+ bus = PCI_BUS(qbus_create(typename, parent, name));
+ pci_bus_init(bus, parent, name, address_space_mem,
+ address_space_io, devfn_min);
+ return bus;
+}
+
+void pci_bus_irqs(PCIBus *bus, pci_set_irq_fn set_irq, pci_map_irq_fn map_irq,
+ void *irq_opaque, int nirq)
+{
+ bus->set_irq = set_irq;
+ bus->map_irq = map_irq;
+ bus->irq_opaque = irq_opaque;
+ bus->nirq = nirq;
+ bus->irq_count = g_malloc0(nirq * sizeof(bus->irq_count[0]));
+}
+
+PCIBus *pci_register_bus(DeviceState *parent, const char *name,
+ pci_set_irq_fn set_irq, pci_map_irq_fn map_irq,
+ void *irq_opaque,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io,
+ uint8_t devfn_min, int nirq, const char *typename)
+{
+ PCIBus *bus;
+
+ bus = pci_bus_new(parent, name, address_space_mem,
+ address_space_io, devfn_min, typename);
+ pci_bus_irqs(bus, set_irq, map_irq, irq_opaque, nirq);
+ return bus;
+}
+
+int pci_bus_num(PCIBus *s)
+{
+ return PCI_BUS_GET_CLASS(s)->bus_num(s);
+}
+
+int pci_bus_numa_node(PCIBus *bus)
+{
+ return PCI_BUS_GET_CLASS(bus)->numa_node(bus);
+}
+
+static int get_pci_config_device(QEMUFile *f, void *pv, size_t size)
+{
+ PCIDevice *s = container_of(pv, PCIDevice, config);
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(s);
+ uint8_t *config;
+ int i;
+
+ assert(size == pci_config_size(s));
+ config = g_malloc(size);
+
+ qemu_get_buffer(f, config, size);
+ for (i = 0; i < size; ++i) {
+ if ((config[i] ^ s->config[i]) &
+ s->cmask[i] & ~s->wmask[i] & ~s->w1cmask[i]) {
+ error_report("%s: Bad config data: i=0x%x read: %x device: %x "
+ "cmask: %x wmask: %x w1cmask:%x", __func__,
+ i, config[i], s->config[i],
+ s->cmask[i], s->wmask[i], s->w1cmask[i]);
+ g_free(config);
+ return -EINVAL;
+ }
+ }
+ memcpy(s->config, config, size);
+
+ pci_update_mappings(s);
+ if (pc->is_bridge) {
+ PCIBridge *b = PCI_BRIDGE(s);
+ pci_bridge_update_mappings(b);
+ }
+
+ memory_region_set_enabled(&s->bus_master_enable_region,
+ pci_get_word(s->config + PCI_COMMAND)
+ & PCI_COMMAND_MASTER);
+
+ g_free(config);
+ return 0;
+}
+
+/* just put buffer */
+static void put_pci_config_device(QEMUFile *f, void *pv, size_t size)
+{
+ const uint8_t **v = pv;
+ assert(size == pci_config_size(container_of(pv, PCIDevice, config)));
+ qemu_put_buffer(f, *v, size);
+}
+
+static VMStateInfo vmstate_info_pci_config = {
+ .name = "pci config",
+ .get = get_pci_config_device,
+ .put = put_pci_config_device,
+};
+
+static int get_pci_irq_state(QEMUFile *f, void *pv, size_t size)
+{
+ PCIDevice *s = container_of(pv, PCIDevice, irq_state);
+ uint32_t irq_state[PCI_NUM_PINS];
+ int i;
+ for (i = 0; i < PCI_NUM_PINS; ++i) {
+ irq_state[i] = qemu_get_be32(f);
+ if (irq_state[i] != 0x1 && irq_state[i] != 0) {
+ fprintf(stderr, "irq state %d: must be 0 or 1.\n",
+ irq_state[i]);
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; i < PCI_NUM_PINS; ++i) {
+ pci_set_irq_state(s, i, irq_state[i]);
+ }
+
+ return 0;
+}
+
+static void put_pci_irq_state(QEMUFile *f, void *pv, size_t size)
+{
+ int i;
+ PCIDevice *s = container_of(pv, PCIDevice, irq_state);
+
+ for (i = 0; i < PCI_NUM_PINS; ++i) {
+ qemu_put_be32(f, pci_irq_state(s, i));
+ }
+}
+
+static VMStateInfo vmstate_info_pci_irq_state = {
+ .name = "pci irq state",
+ .get = get_pci_irq_state,
+ .put = put_pci_irq_state,
+};
+
+const VMStateDescription vmstate_pci_device = {
+ .name = "PCIDevice",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_POSITIVE_LE(version_id, PCIDevice),
+ VMSTATE_BUFFER_UNSAFE_INFO(config, PCIDevice, 0,
+ vmstate_info_pci_config,
+ PCI_CONFIG_SPACE_SIZE),
+ VMSTATE_BUFFER_UNSAFE_INFO(irq_state, PCIDevice, 2,
+ vmstate_info_pci_irq_state,
+ PCI_NUM_PINS * sizeof(int32_t)),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_pcie_device = {
+ .name = "PCIEDevice",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_POSITIVE_LE(version_id, PCIDevice),
+ VMSTATE_BUFFER_UNSAFE_INFO(config, PCIDevice, 0,
+ vmstate_info_pci_config,
+ PCIE_CONFIG_SPACE_SIZE),
+ VMSTATE_BUFFER_UNSAFE_INFO(irq_state, PCIDevice, 2,
+ vmstate_info_pci_irq_state,
+ PCI_NUM_PINS * sizeof(int32_t)),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static inline const VMStateDescription *pci_get_vmstate(PCIDevice *s)
+{
+ return pci_is_express(s) ? &vmstate_pcie_device : &vmstate_pci_device;
+}
+
+void pci_device_save(PCIDevice *s, QEMUFile *f)
+{
+ /* Clear interrupt status bit: it is implicit
+ * in irq_state which we are saving.
+ * This makes us compatible with old devices
+ * which never set or clear this bit. */
+ s->config[PCI_STATUS] &= ~PCI_STATUS_INTERRUPT;
+ vmstate_save_state(f, pci_get_vmstate(s), s, NULL);
+ /* Restore the interrupt status bit. */
+ pci_update_irq_status(s);
+}
+
+int pci_device_load(PCIDevice *s, QEMUFile *f)
+{
+ int ret;
+ ret = vmstate_load_state(f, pci_get_vmstate(s), s, s->version_id);
+ /* Restore the interrupt status bit. */
+ pci_update_irq_status(s);
+ return ret;
+}
+
+static void pci_set_default_subsystem_id(PCIDevice *pci_dev)
+{
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_VENDOR_ID,
+ pci_default_sub_vendor_id);
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_ID,
+ pci_default_sub_device_id);
+}
+
+/*
+ * Parse [[<domain>:]<bus>:]<slot>, return -1 on error if funcp == NULL
+ * [[<domain>:]<bus>:]<slot>.<func>, return -1 on error
+ */
+static int pci_parse_devaddr(const char *addr, int *domp, int *busp,
+ unsigned int *slotp, unsigned int *funcp)
+{
+ const char *p;
+ char *e;
+ unsigned long val;
+ unsigned long dom = 0, bus = 0;
+ unsigned int slot = 0;
+ unsigned int func = 0;
+
+ p = addr;
+ val = strtoul(p, &e, 16);
+ if (e == p)
+ return -1;
+ if (*e == ':') {
+ bus = val;
+ p = e + 1;
+ val = strtoul(p, &e, 16);
+ if (e == p)
+ return -1;
+ if (*e == ':') {
+ dom = bus;
+ bus = val;
+ p = e + 1;
+ val = strtoul(p, &e, 16);
+ if (e == p)
+ return -1;
+ }
+ }
+
+ slot = val;
+
+ if (funcp != NULL) {
+ if (*e != '.')
+ return -1;
+
+ p = e + 1;
+ val = strtoul(p, &e, 16);
+ if (e == p)
+ return -1;
+
+ func = val;
+ }
+
+ /* if funcp == NULL func is 0 */
+ if (dom > 0xffff || bus > 0xff || slot > 0x1f || func > 7)
+ return -1;
+
+ if (*e)
+ return -1;
+
+ *domp = dom;
+ *busp = bus;
+ *slotp = slot;
+ if (funcp != NULL)
+ *funcp = func;
+ return 0;
+}
+
+static PCIBus *pci_get_bus_devfn(int *devfnp, PCIBus *root,
+ const char *devaddr)
+{
+ int dom, bus;
+ unsigned slot;
+
+ if (!root) {
+ fprintf(stderr, "No primary PCI bus\n");
+ return NULL;
+ }
+
+ assert(!root->parent_dev);
+
+ if (!devaddr) {
+ *devfnp = -1;
+ return pci_find_bus_nr(root, 0);
+ }
+
+ if (pci_parse_devaddr(devaddr, &dom, &bus, &slot, NULL) < 0) {
+ return NULL;
+ }
+
+ if (dom != 0) {
+ fprintf(stderr, "No support for non-zero PCI domains\n");
+ return NULL;
+ }
+
+ *devfnp = PCI_DEVFN(slot, 0);
+ return pci_find_bus_nr(root, bus);
+}
+
+static void pci_init_cmask(PCIDevice *dev)
+{
+ pci_set_word(dev->cmask + PCI_VENDOR_ID, 0xffff);
+ pci_set_word(dev->cmask + PCI_DEVICE_ID, 0xffff);
+ dev->cmask[PCI_STATUS] = PCI_STATUS_CAP_LIST;
+ dev->cmask[PCI_REVISION_ID] = 0xff;
+ dev->cmask[PCI_CLASS_PROG] = 0xff;
+ pci_set_word(dev->cmask + PCI_CLASS_DEVICE, 0xffff);
+ dev->cmask[PCI_HEADER_TYPE] = 0xff;
+ dev->cmask[PCI_CAPABILITY_LIST] = 0xff;
+}
+
+static void pci_init_wmask(PCIDevice *dev)
+{
+ int config_size = pci_config_size(dev);
+
+ dev->wmask[PCI_CACHE_LINE_SIZE] = 0xff;
+ dev->wmask[PCI_INTERRUPT_LINE] = 0xff;
+ pci_set_word(dev->wmask + PCI_COMMAND,
+ PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
+ PCI_COMMAND_INTX_DISABLE);
+ if (dev->cap_present & QEMU_PCI_CAP_SERR) {
+ pci_word_test_and_set_mask(dev->wmask + PCI_COMMAND, PCI_COMMAND_SERR);
+ }
+
+ memset(dev->wmask + PCI_CONFIG_HEADER_SIZE, 0xff,
+ config_size - PCI_CONFIG_HEADER_SIZE);
+}
+
+static void pci_init_w1cmask(PCIDevice *dev)
+{
+ /*
+ * Note: It's okay to set w1cmask even for readonly bits as
+ * long as their value is hardwired to 0.
+ */
+ pci_set_word(dev->w1cmask + PCI_STATUS,
+ PCI_STATUS_PARITY | PCI_STATUS_SIG_TARGET_ABORT |
+ PCI_STATUS_REC_TARGET_ABORT | PCI_STATUS_REC_MASTER_ABORT |
+ PCI_STATUS_SIG_SYSTEM_ERROR | PCI_STATUS_DETECTED_PARITY);
+}
+
+static void pci_init_mask_bridge(PCIDevice *d)
+{
+ /* PCI_PRIMARY_BUS, PCI_SECONDARY_BUS, PCI_SUBORDINATE_BUS and
+ PCI_SEC_LETENCY_TIMER */
+ memset(d->wmask + PCI_PRIMARY_BUS, 0xff, 4);
+
+ /* base and limit */
+ d->wmask[PCI_IO_BASE] = PCI_IO_RANGE_MASK & 0xff;
+ d->wmask[PCI_IO_LIMIT] = PCI_IO_RANGE_MASK & 0xff;
+ pci_set_word(d->wmask + PCI_MEMORY_BASE,
+ PCI_MEMORY_RANGE_MASK & 0xffff);
+ pci_set_word(d->wmask + PCI_MEMORY_LIMIT,
+ PCI_MEMORY_RANGE_MASK & 0xffff);
+ pci_set_word(d->wmask + PCI_PREF_MEMORY_BASE,
+ PCI_PREF_RANGE_MASK & 0xffff);
+ pci_set_word(d->wmask + PCI_PREF_MEMORY_LIMIT,
+ PCI_PREF_RANGE_MASK & 0xffff);
+
+ /* PCI_PREF_BASE_UPPER32 and PCI_PREF_LIMIT_UPPER32 */
+ memset(d->wmask + PCI_PREF_BASE_UPPER32, 0xff, 8);
+
+ /* Supported memory and i/o types */
+ d->config[PCI_IO_BASE] |= PCI_IO_RANGE_TYPE_16;
+ d->config[PCI_IO_LIMIT] |= PCI_IO_RANGE_TYPE_16;
+ pci_word_test_and_set_mask(d->config + PCI_PREF_MEMORY_BASE,
+ PCI_PREF_RANGE_TYPE_64);
+ pci_word_test_and_set_mask(d->config + PCI_PREF_MEMORY_LIMIT,
+ PCI_PREF_RANGE_TYPE_64);
+
+ /*
+ * TODO: Bridges default to 10-bit VGA decoding but we currently only
+ * implement 16-bit decoding (no alias support).
+ */
+ pci_set_word(d->wmask + PCI_BRIDGE_CONTROL,
+ PCI_BRIDGE_CTL_PARITY |
+ PCI_BRIDGE_CTL_SERR |
+ PCI_BRIDGE_CTL_ISA |
+ PCI_BRIDGE_CTL_VGA |
+ PCI_BRIDGE_CTL_VGA_16BIT |
+ PCI_BRIDGE_CTL_MASTER_ABORT |
+ PCI_BRIDGE_CTL_BUS_RESET |
+ PCI_BRIDGE_CTL_FAST_BACK |
+ PCI_BRIDGE_CTL_DISCARD |
+ PCI_BRIDGE_CTL_SEC_DISCARD |
+ PCI_BRIDGE_CTL_DISCARD_SERR);
+ /* Below does not do anything as we never set this bit, put here for
+ * completeness. */
+ pci_set_word(d->w1cmask + PCI_BRIDGE_CONTROL,
+ PCI_BRIDGE_CTL_DISCARD_STATUS);
+ d->cmask[PCI_IO_BASE] |= PCI_IO_RANGE_TYPE_MASK;
+ d->cmask[PCI_IO_LIMIT] |= PCI_IO_RANGE_TYPE_MASK;
+ pci_word_test_and_set_mask(d->cmask + PCI_PREF_MEMORY_BASE,
+ PCI_PREF_RANGE_TYPE_MASK);
+ pci_word_test_and_set_mask(d->cmask + PCI_PREF_MEMORY_LIMIT,
+ PCI_PREF_RANGE_TYPE_MASK);
+}
+
+static void pci_init_multifunction(PCIBus *bus, PCIDevice *dev, Error **errp)
+{
+ uint8_t slot = PCI_SLOT(dev->devfn);
+ uint8_t func;
+
+ if (dev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
+ dev->config[PCI_HEADER_TYPE] |= PCI_HEADER_TYPE_MULTI_FUNCTION;
+ }
+
+ /*
+ * multifunction bit is interpreted in two ways as follows.
+ * - all functions must set the bit to 1.
+ * Example: Intel X53
+ * - function 0 must set the bit, but the rest function (> 0)
+ * is allowed to leave the bit to 0.
+ * Example: PIIX3(also in qemu), PIIX4(also in qemu), ICH10,
+ *
+ * So OS (at least Linux) checks the bit of only function 0,
+ * and doesn't see the bit of function > 0.
+ *
+ * The below check allows both interpretation.
+ */
+ if (PCI_FUNC(dev->devfn)) {
+ PCIDevice *f0 = bus->devices[PCI_DEVFN(slot, 0)];
+ if (f0 && !(f0->cap_present & QEMU_PCI_CAP_MULTIFUNCTION)) {
+ /* function 0 should set multifunction bit */
+ error_setg(errp, "PCI: single function device can't be populated "
+ "in function %x.%x", slot, PCI_FUNC(dev->devfn));
+ return;
+ }
+ return;
+ }
+
+ if (dev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
+ return;
+ }
+ /* function 0 indicates single function, so function > 0 must be NULL */
+ for (func = 1; func < PCI_FUNC_MAX; ++func) {
+ if (bus->devices[PCI_DEVFN(slot, func)]) {
+ error_setg(errp, "PCI: %x.0 indicates single function, "
+ "but %x.%x is already populated.",
+ slot, slot, func);
+ return;
+ }
+ }
+}
+
+static void pci_config_alloc(PCIDevice *pci_dev)
+{
+ int config_size = pci_config_size(pci_dev);
+
+ pci_dev->config = g_malloc0(config_size);
+ pci_dev->cmask = g_malloc0(config_size);
+ pci_dev->wmask = g_malloc0(config_size);
+ pci_dev->w1cmask = g_malloc0(config_size);
+ pci_dev->used = g_malloc0(config_size);
+}
+
+static void pci_config_free(PCIDevice *pci_dev)
+{
+ g_free(pci_dev->config);
+ g_free(pci_dev->cmask);
+ g_free(pci_dev->wmask);
+ g_free(pci_dev->w1cmask);
+ g_free(pci_dev->used);
+}
+
+static void do_pci_unregister_device(PCIDevice *pci_dev)
+{
+ pci_dev->bus->devices[pci_dev->devfn] = NULL;
+ pci_config_free(pci_dev);
+
+ address_space_destroy(&pci_dev->bus_master_as);
+}
+
+/* -1 for devfn means auto assign */
+static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, PCIBus *bus,
+ const char *name, int devfn,
+ Error **errp)
+{
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pci_dev);
+ PCIConfigReadFunc *config_read = pc->config_read;
+ PCIConfigWriteFunc *config_write = pc->config_write;
+ Error *local_err = NULL;
+ AddressSpace *dma_as;
+
+ if (devfn < 0) {
+ for(devfn = bus->devfn_min ; devfn < ARRAY_SIZE(bus->devices);
+ devfn += PCI_FUNC_MAX) {
+ if (!bus->devices[devfn])
+ goto found;
+ }
+ error_setg(errp, "PCI: no slot/function available for %s, all in use",
+ name);
+ return NULL;
+ found: ;
+ } else if (bus->devices[devfn]) {
+ error_setg(errp, "PCI: slot %d function %d not available for %s,"
+ " in use by %s",
+ PCI_SLOT(devfn), PCI_FUNC(devfn), name,
+ bus->devices[devfn]->name);
+ return NULL;
+ }
+
+ pci_dev->bus = bus;
+ pci_dev->devfn = devfn;
+ dma_as = pci_device_iommu_address_space(pci_dev);
+
+ memory_region_init_alias(&pci_dev->bus_master_enable_region,
+ OBJECT(pci_dev), "bus master",
+ dma_as->root, 0, memory_region_size(dma_as->root));
+ memory_region_set_enabled(&pci_dev->bus_master_enable_region, false);
+ address_space_init(&pci_dev->bus_master_as, &pci_dev->bus_master_enable_region,
+ name);
+
+ pstrcpy(pci_dev->name, sizeof(pci_dev->name), name);
+ pci_dev->irq_state = 0;
+ pci_config_alloc(pci_dev);
+
+ pci_config_set_vendor_id(pci_dev->config, pc->vendor_id);
+ pci_config_set_device_id(pci_dev->config, pc->device_id);
+ pci_config_set_revision(pci_dev->config, pc->revision);
+ pci_config_set_class(pci_dev->config, pc->class_id);
+
+ if (!pc->is_bridge) {
+ if (pc->subsystem_vendor_id || pc->subsystem_id) {
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_VENDOR_ID,
+ pc->subsystem_vendor_id);
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_ID,
+ pc->subsystem_id);
+ } else {
+ pci_set_default_subsystem_id(pci_dev);
+ }
+ } else {
+ /* subsystem_vendor_id/subsystem_id are only for header type 0 */
+ assert(!pc->subsystem_vendor_id);
+ assert(!pc->subsystem_id);
+ }
+ pci_init_cmask(pci_dev);
+ pci_init_wmask(pci_dev);
+ pci_init_w1cmask(pci_dev);
+ if (pc->is_bridge) {
+ pci_init_mask_bridge(pci_dev);
+ }
+ pci_init_multifunction(bus, pci_dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ do_pci_unregister_device(pci_dev);
+ return NULL;
+ }
+
+ if (!config_read)
+ config_read = pci_default_read_config;
+ if (!config_write)
+ config_write = pci_default_write_config;
+ pci_dev->config_read = config_read;
+ pci_dev->config_write = config_write;
+ bus->devices[devfn] = pci_dev;
+ pci_dev->version_id = 2; /* Current pci device vmstate version */
+ return pci_dev;
+}
+
+static void pci_unregister_io_regions(PCIDevice *pci_dev)
+{
+ PCIIORegion *r;
+ int i;
+
+ for(i = 0; i < PCI_NUM_REGIONS; i++) {
+ r = &pci_dev->io_regions[i];
+ if (!r->size || r->addr == PCI_BAR_UNMAPPED)
+ continue;
+ memory_region_del_subregion(r->address_space, r->memory);
+ }
+
+ pci_unregister_vga(pci_dev);
+}
+
+static void pci_qdev_unrealize(DeviceState *dev, Error **errp)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(dev);
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pci_dev);
+
+ pci_unregister_io_regions(pci_dev);
+ pci_del_option_rom(pci_dev);
+
+ if (pc->exit) {
+ pc->exit(pci_dev);
+ }
+
+ do_pci_unregister_device(pci_dev);
+}
+
+void pci_register_bar(PCIDevice *pci_dev, int region_num,
+ uint8_t type, MemoryRegion *memory)
+{
+ PCIIORegion *r;
+ uint32_t addr;
+ uint64_t wmask;
+ pcibus_t size = memory_region_size(memory);
+
+ assert(region_num >= 0);
+ assert(region_num < PCI_NUM_REGIONS);
+ if (size & (size-1)) {
+ fprintf(stderr, "ERROR: PCI region size must be pow2 "
+ "type=0x%x, size=0x%"FMT_PCIBUS"\n", type, size);
+ exit(1);
+ }
+
+ r = &pci_dev->io_regions[region_num];
+ r->addr = PCI_BAR_UNMAPPED;
+ r->size = size;
+ r->type = type;
+ r->memory = NULL;
+
+ wmask = ~(size - 1);
+ addr = pci_bar(pci_dev, region_num);
+ if (region_num == PCI_ROM_SLOT) {
+ /* ROM enable bit is writable */
+ wmask |= PCI_ROM_ADDRESS_ENABLE;
+ }
+ pci_set_long(pci_dev->config + addr, type);
+ if (!(r->type & PCI_BASE_ADDRESS_SPACE_IO) &&
+ r->type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ pci_set_quad(pci_dev->wmask + addr, wmask);
+ pci_set_quad(pci_dev->cmask + addr, ~0ULL);
+ } else {
+ pci_set_long(pci_dev->wmask + addr, wmask & 0xffffffff);
+ pci_set_long(pci_dev->cmask + addr, 0xffffffff);
+ }
+ pci_dev->io_regions[region_num].memory = memory;
+ pci_dev->io_regions[region_num].address_space
+ = type & PCI_BASE_ADDRESS_SPACE_IO
+ ? pci_dev->bus->address_space_io
+ : pci_dev->bus->address_space_mem;
+}
+
+static void pci_update_vga(PCIDevice *pci_dev)
+{
+ uint16_t cmd;
+
+ if (!pci_dev->has_vga) {
+ return;
+ }
+
+ cmd = pci_get_word(pci_dev->config + PCI_COMMAND);
+
+ memory_region_set_enabled(pci_dev->vga_regions[QEMU_PCI_VGA_MEM],
+ cmd & PCI_COMMAND_MEMORY);
+ memory_region_set_enabled(pci_dev->vga_regions[QEMU_PCI_VGA_IO_LO],
+ cmd & PCI_COMMAND_IO);
+ memory_region_set_enabled(pci_dev->vga_regions[QEMU_PCI_VGA_IO_HI],
+ cmd & PCI_COMMAND_IO);
+}
+
+void pci_register_vga(PCIDevice *pci_dev, MemoryRegion *mem,
+ MemoryRegion *io_lo, MemoryRegion *io_hi)
+{
+ assert(!pci_dev->has_vga);
+
+ assert(memory_region_size(mem) == QEMU_PCI_VGA_MEM_SIZE);
+ pci_dev->vga_regions[QEMU_PCI_VGA_MEM] = mem;
+ memory_region_add_subregion_overlap(pci_dev->bus->address_space_mem,
+ QEMU_PCI_VGA_MEM_BASE, mem, 1);
+
+ assert(memory_region_size(io_lo) == QEMU_PCI_VGA_IO_LO_SIZE);
+ pci_dev->vga_regions[QEMU_PCI_VGA_IO_LO] = io_lo;
+ memory_region_add_subregion_overlap(pci_dev->bus->address_space_io,
+ QEMU_PCI_VGA_IO_LO_BASE, io_lo, 1);
+
+ assert(memory_region_size(io_hi) == QEMU_PCI_VGA_IO_HI_SIZE);
+ pci_dev->vga_regions[QEMU_PCI_VGA_IO_HI] = io_hi;
+ memory_region_add_subregion_overlap(pci_dev->bus->address_space_io,
+ QEMU_PCI_VGA_IO_HI_BASE, io_hi, 1);
+ pci_dev->has_vga = true;
+
+ pci_update_vga(pci_dev);
+}
+
+void pci_unregister_vga(PCIDevice *pci_dev)
+{
+ if (!pci_dev->has_vga) {
+ return;
+ }
+
+ memory_region_del_subregion(pci_dev->bus->address_space_mem,
+ pci_dev->vga_regions[QEMU_PCI_VGA_MEM]);
+ memory_region_del_subregion(pci_dev->bus->address_space_io,
+ pci_dev->vga_regions[QEMU_PCI_VGA_IO_LO]);
+ memory_region_del_subregion(pci_dev->bus->address_space_io,
+ pci_dev->vga_regions[QEMU_PCI_VGA_IO_HI]);
+ pci_dev->has_vga = false;
+}
+
+pcibus_t pci_get_bar_addr(PCIDevice *pci_dev, int region_num)
+{
+ return pci_dev->io_regions[region_num].addr;
+}
+
+static pcibus_t pci_bar_address(PCIDevice *d,
+ int reg, uint8_t type, pcibus_t size)
+{
+ pcibus_t new_addr, last_addr;
+ int bar = pci_bar(d, reg);
+ uint16_t cmd = pci_get_word(d->config + PCI_COMMAND);
+
+ if (type & PCI_BASE_ADDRESS_SPACE_IO) {
+ if (!(cmd & PCI_COMMAND_IO)) {
+ return PCI_BAR_UNMAPPED;
+ }
+ new_addr = pci_get_long(d->config + bar) & ~(size - 1);
+ last_addr = new_addr + size - 1;
+ /* Check if 32 bit BAR wraps around explicitly.
+ * TODO: make priorities correct and remove this work around.
+ */
+ if (last_addr <= new_addr || new_addr == 0 || last_addr >= UINT32_MAX) {
+ return PCI_BAR_UNMAPPED;
+ }
+ return new_addr;
+ }
+
+ if (!(cmd & PCI_COMMAND_MEMORY)) {
+ return PCI_BAR_UNMAPPED;
+ }
+ if (type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ new_addr = pci_get_quad(d->config + bar);
+ } else {
+ new_addr = pci_get_long(d->config + bar);
+ }
+ /* the ROM slot has a specific enable bit */
+ if (reg == PCI_ROM_SLOT && !(new_addr & PCI_ROM_ADDRESS_ENABLE)) {
+ return PCI_BAR_UNMAPPED;
+ }
+ new_addr &= ~(size - 1);
+ last_addr = new_addr + size - 1;
+ /* NOTE: we do not support wrapping */
+ /* XXX: as we cannot support really dynamic
+ mappings, we handle specific values as invalid
+ mappings. */
+ if (last_addr <= new_addr || new_addr == 0 ||
+ last_addr == PCI_BAR_UNMAPPED) {
+ return PCI_BAR_UNMAPPED;
+ }
+
+ /* Now pcibus_t is 64bit.
+ * Check if 32 bit BAR wraps around explicitly.
+ * Without this, PC ide doesn't work well.
+ * TODO: remove this work around.
+ */
+ if (!(type & PCI_BASE_ADDRESS_MEM_TYPE_64) && last_addr >= UINT32_MAX) {
+ return PCI_BAR_UNMAPPED;
+ }
+
+ /*
+ * OS is allowed to set BAR beyond its addressable
+ * bits. For example, 32 bit OS can set 64bit bar
+ * to >4G. Check it. TODO: we might need to support
+ * it in the future for e.g. PAE.
+ */
+ if (last_addr >= HWADDR_MAX) {
+ return PCI_BAR_UNMAPPED;
+ }
+
+ return new_addr;
+}
+
+static void pci_update_mappings(PCIDevice *d)
+{
+ PCIIORegion *r;
+ int i;
+ pcibus_t new_addr;
+
+ for(i = 0; i < PCI_NUM_REGIONS; i++) {
+ r = &d->io_regions[i];
+
+ /* this region isn't registered */
+ if (!r->size)
+ continue;
+
+ new_addr = pci_bar_address(d, i, r->type, r->size);
+
+ /* This bar isn't changed */
+ if (new_addr == r->addr)
+ continue;
+
+ /* now do the real mapping */
+ if (r->addr != PCI_BAR_UNMAPPED) {
+ trace_pci_update_mappings_del(d, pci_bus_num(d->bus),
+ PCI_FUNC(d->devfn),
+ PCI_SLOT(d->devfn),
+ i, r->addr, r->size);
+ memory_region_del_subregion(r->address_space, r->memory);
+ }
+ r->addr = new_addr;
+ if (r->addr != PCI_BAR_UNMAPPED) {
+ trace_pci_update_mappings_add(d, pci_bus_num(d->bus),
+ PCI_FUNC(d->devfn),
+ PCI_SLOT(d->devfn),
+ i, r->addr, r->size);
+ memory_region_add_subregion_overlap(r->address_space,
+ r->addr, r->memory, 1);
+ }
+ }
+
+ pci_update_vga(d);
+}
+
+static inline int pci_irq_disabled(PCIDevice *d)
+{
+ return pci_get_word(d->config + PCI_COMMAND) & PCI_COMMAND_INTX_DISABLE;
+}
+
+/* Called after interrupt disabled field update in config space,
+ * assert/deassert interrupts if necessary.
+ * Gets original interrupt disable bit value (before update). */
+static void pci_update_irq_disabled(PCIDevice *d, int was_irq_disabled)
+{
+ int i, disabled = pci_irq_disabled(d);
+ if (disabled == was_irq_disabled)
+ return;
+ for (i = 0; i < PCI_NUM_PINS; ++i) {
+ int state = pci_irq_state(d, i);
+ pci_change_irq_level(d, i, disabled ? -state : state);
+ }
+}
+
+uint32_t pci_default_read_config(PCIDevice *d,
+ uint32_t address, int len)
+{
+ uint32_t val = 0;
+
+ memcpy(&val, d->config + address, len);
+ return le32_to_cpu(val);
+}
+
+void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val_in, int l)
+{
+ int i, was_irq_disabled = pci_irq_disabled(d);
+ uint32_t val = val_in;
+
+ for (i = 0; i < l; val >>= 8, ++i) {
+ uint8_t wmask = d->wmask[addr + i];
+ uint8_t w1cmask = d->w1cmask[addr + i];
+ assert(!(wmask & w1cmask));
+ d->config[addr + i] = (d->config[addr + i] & ~wmask) | (val & wmask);
+ d->config[addr + i] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */
+ }
+ if (ranges_overlap(addr, l, PCI_BASE_ADDRESS_0, 24) ||
+ ranges_overlap(addr, l, PCI_ROM_ADDRESS, 4) ||
+ ranges_overlap(addr, l, PCI_ROM_ADDRESS1, 4) ||
+ range_covers_byte(addr, l, PCI_COMMAND))
+ pci_update_mappings(d);
+
+ if (range_covers_byte(addr, l, PCI_COMMAND)) {
+ pci_update_irq_disabled(d, was_irq_disabled);
+ memory_region_set_enabled(&d->bus_master_enable_region,
+ pci_get_word(d->config + PCI_COMMAND)
+ & PCI_COMMAND_MASTER);
+ }
+
+ msi_write_config(d, addr, val_in, l);
+ msix_write_config(d, addr, val_in, l);
+}
+
+/***********************************************************/
+/* generic PCI irq support */
+
+/* 0 <= irq_num <= 3. level must be 0 or 1 */
+static void pci_irq_handler(void *opaque, int irq_num, int level)
+{
+ PCIDevice *pci_dev = opaque;
+ int change;
+
+ change = level - pci_irq_state(pci_dev, irq_num);
+ if (!change)
+ return;
+
+ pci_set_irq_state(pci_dev, irq_num, level);
+ pci_update_irq_status(pci_dev);
+ if (pci_irq_disabled(pci_dev))
+ return;
+ pci_change_irq_level(pci_dev, irq_num, change);
+}
+
+static inline int pci_intx(PCIDevice *pci_dev)
+{
+ return pci_get_byte(pci_dev->config + PCI_INTERRUPT_PIN) - 1;
+}
+
+qemu_irq pci_allocate_irq(PCIDevice *pci_dev)
+{
+ int intx = pci_intx(pci_dev);
+
+ return qemu_allocate_irq(pci_irq_handler, pci_dev, intx);
+}
+
+void pci_set_irq(PCIDevice *pci_dev, int level)
+{
+ int intx = pci_intx(pci_dev);
+ pci_irq_handler(pci_dev, intx, level);
+}
+
+/* Special hooks used by device assignment */
+void pci_bus_set_route_irq_fn(PCIBus *bus, pci_route_irq_fn route_intx_to_irq)
+{
+ assert(pci_bus_is_root(bus));
+ bus->route_intx_to_irq = route_intx_to_irq;
+}
+
+PCIINTxRoute pci_device_route_intx_to_irq(PCIDevice *dev, int pin)
+{
+ PCIBus *bus;
+
+ do {
+ bus = dev->bus;
+ pin = bus->map_irq(dev, pin);
+ dev = bus->parent_dev;
+ } while (dev);
+
+ if (!bus->route_intx_to_irq) {
+ error_report("PCI: Bug - unimplemented PCI INTx routing (%s)",
+ object_get_typename(OBJECT(bus->qbus.parent)));
+ return (PCIINTxRoute) { PCI_INTX_DISABLED, -1 };
+ }
+
+ return bus->route_intx_to_irq(bus->irq_opaque, pin);
+}
+
+bool pci_intx_route_changed(PCIINTxRoute *old, PCIINTxRoute *new)
+{
+ return old->mode != new->mode || old->irq != new->irq;
+}
+
+void pci_bus_fire_intx_routing_notifier(PCIBus *bus)
+{
+ PCIDevice *dev;
+ PCIBus *sec;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bus->devices); ++i) {
+ dev = bus->devices[i];
+ if (dev && dev->intx_routing_notifier) {
+ dev->intx_routing_notifier(dev);
+ }
+ }
+
+ QLIST_FOREACH(sec, &bus->child, sibling) {
+ pci_bus_fire_intx_routing_notifier(sec);
+ }
+}
+
+void pci_device_set_intx_routing_notifier(PCIDevice *dev,
+ PCIINTxRoutingNotifier notifier)
+{
+ dev->intx_routing_notifier = notifier;
+}
+
+/*
+ * PCI-to-PCI bridge specification
+ * 9.1: Interrupt routing. Table 9-1
+ *
+ * the PCI Express Base Specification, Revision 2.1
+ * 2.2.8.1: INTx interrutp signaling - Rules
+ * the Implementation Note
+ * Table 2-20
+ */
+/*
+ * 0 <= pin <= 3 0 = INTA, 1 = INTB, 2 = INTC, 3 = INTD
+ * 0-origin unlike PCI interrupt pin register.
+ */
+int pci_swizzle_map_irq_fn(PCIDevice *pci_dev, int pin)
+{
+ return (pin + PCI_SLOT(pci_dev->devfn)) % PCI_NUM_PINS;
+}
+
+/***********************************************************/
+/* monitor info on PCI */
+
+typedef struct {
+ uint16_t class;
+ const char *desc;
+ const char *fw_name;
+ uint16_t fw_ign_bits;
+} pci_class_desc;
+
+static const pci_class_desc pci_class_descriptions[] =
+{
+ { 0x0001, "VGA controller", "display"},
+ { 0x0100, "SCSI controller", "scsi"},
+ { 0x0101, "IDE controller", "ide"},
+ { 0x0102, "Floppy controller", "fdc"},
+ { 0x0103, "IPI controller", "ipi"},
+ { 0x0104, "RAID controller", "raid"},
+ { 0x0106, "SATA controller"},
+ { 0x0107, "SAS controller"},
+ { 0x0180, "Storage controller"},
+ { 0x0200, "Ethernet controller", "ethernet"},
+ { 0x0201, "Token Ring controller", "token-ring"},
+ { 0x0202, "FDDI controller", "fddi"},
+ { 0x0203, "ATM controller", "atm"},
+ { 0x0280, "Network controller"},
+ { 0x0300, "VGA controller", "display", 0x00ff},
+ { 0x0301, "XGA controller"},
+ { 0x0302, "3D controller"},
+ { 0x0380, "Display controller"},
+ { 0x0400, "Video controller", "video"},
+ { 0x0401, "Audio controller", "sound"},
+ { 0x0402, "Phone"},
+ { 0x0403, "Audio controller", "sound"},
+ { 0x0480, "Multimedia controller"},
+ { 0x0500, "RAM controller", "memory"},
+ { 0x0501, "Flash controller", "flash"},
+ { 0x0580, "Memory controller"},
+ { 0x0600, "Host bridge", "host"},
+ { 0x0601, "ISA bridge", "isa"},
+ { 0x0602, "EISA bridge", "eisa"},
+ { 0x0603, "MC bridge", "mca"},
+ { 0x0604, "PCI bridge", "pci-bridge"},
+ { 0x0605, "PCMCIA bridge", "pcmcia"},
+ { 0x0606, "NUBUS bridge", "nubus"},
+ { 0x0607, "CARDBUS bridge", "cardbus"},
+ { 0x0608, "RACEWAY bridge"},
+ { 0x0680, "Bridge"},
+ { 0x0700, "Serial port", "serial"},
+ { 0x0701, "Parallel port", "parallel"},
+ { 0x0800, "Interrupt controller", "interrupt-controller"},
+ { 0x0801, "DMA controller", "dma-controller"},
+ { 0x0802, "Timer", "timer"},
+ { 0x0803, "RTC", "rtc"},
+ { 0x0900, "Keyboard", "keyboard"},
+ { 0x0901, "Pen", "pen"},
+ { 0x0902, "Mouse", "mouse"},
+ { 0x0A00, "Dock station", "dock", 0x00ff},
+ { 0x0B00, "i386 cpu", "cpu", 0x00ff},
+ { 0x0c00, "Fireware contorller", "fireware"},
+ { 0x0c01, "Access bus controller", "access-bus"},
+ { 0x0c02, "SSA controller", "ssa"},
+ { 0x0c03, "USB controller", "usb"},
+ { 0x0c04, "Fibre channel controller", "fibre-channel"},
+ { 0x0c05, "SMBus"},
+ { 0, NULL}
+};
+
+static void pci_for_each_device_under_bus(PCIBus *bus,
+ void (*fn)(PCIBus *b, PCIDevice *d,
+ void *opaque),
+ void *opaque)
+{
+ PCIDevice *d;
+ int devfn;
+
+ for(devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
+ d = bus->devices[devfn];
+ if (d) {
+ fn(bus, d, opaque);
+ }
+ }
+}
+
+void pci_for_each_device(PCIBus *bus, int bus_num,
+ void (*fn)(PCIBus *b, PCIDevice *d, void *opaque),
+ void *opaque)
+{
+ bus = pci_find_bus_nr(bus, bus_num);
+
+ if (bus) {
+ pci_for_each_device_under_bus(bus, fn, opaque);
+ }
+}
+
+static const pci_class_desc *get_class_desc(int class)
+{
+ const pci_class_desc *desc;
+
+ desc = pci_class_descriptions;
+ while (desc->desc && class != desc->class) {
+ desc++;
+ }
+
+ return desc;
+}
+
+static PciDeviceInfoList *qmp_query_pci_devices(PCIBus *bus, int bus_num);
+
+static PciMemoryRegionList *qmp_query_pci_regions(const PCIDevice *dev)
+{
+ PciMemoryRegionList *head = NULL, *cur_item = NULL;
+ int i;
+
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ const PCIIORegion *r = &dev->io_regions[i];
+ PciMemoryRegionList *region;
+
+ if (!r->size) {
+ continue;
+ }
+
+ region = g_malloc0(sizeof(*region));
+ region->value = g_malloc0(sizeof(*region->value));
+
+ if (r->type & PCI_BASE_ADDRESS_SPACE_IO) {
+ region->value->type = g_strdup("io");
+ } else {
+ region->value->type = g_strdup("memory");
+ region->value->has_prefetch = true;
+ region->value->prefetch = !!(r->type & PCI_BASE_ADDRESS_MEM_PREFETCH);
+ region->value->has_mem_type_64 = true;
+ region->value->mem_type_64 = !!(r->type & PCI_BASE_ADDRESS_MEM_TYPE_64);
+ }
+
+ region->value->bar = i;
+ region->value->address = r->addr;
+ region->value->size = r->size;
+
+ /* XXX: waiting for the qapi to support GSList */
+ if (!cur_item) {
+ head = cur_item = region;
+ } else {
+ cur_item->next = region;
+ cur_item = region;
+ }
+ }
+
+ return head;
+}
+
+static PciBridgeInfo *qmp_query_pci_bridge(PCIDevice *dev, PCIBus *bus,
+ int bus_num)
+{
+ PciBridgeInfo *info;
+ PciMemoryRange *range;
+
+ info = g_new0(PciBridgeInfo, 1);
+
+ info->bus = g_new0(PciBusInfo, 1);
+ info->bus->number = dev->config[PCI_PRIMARY_BUS];
+ info->bus->secondary = dev->config[PCI_SECONDARY_BUS];
+ info->bus->subordinate = dev->config[PCI_SUBORDINATE_BUS];
+
+ range = info->bus->io_range = g_new0(PciMemoryRange, 1);
+ range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO);
+ range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO);
+
+ range = info->bus->memory_range = g_new0(PciMemoryRange, 1);
+ range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
+ range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
+
+ range = info->bus->prefetchable_range = g_new0(PciMemoryRange, 1);
+ range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+ range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+
+ if (dev->config[PCI_SECONDARY_BUS] != 0) {
+ PCIBus *child_bus = pci_find_bus_nr(bus, dev->config[PCI_SECONDARY_BUS]);
+ if (child_bus) {
+ info->has_devices = true;
+ info->devices = qmp_query_pci_devices(child_bus, dev->config[PCI_SECONDARY_BUS]);
+ }
+ }
+
+ return info;
+}
+
+static PciDeviceInfo *qmp_query_pci_device(PCIDevice *dev, PCIBus *bus,
+ int bus_num)
+{
+ const pci_class_desc *desc;
+ PciDeviceInfo *info;
+ uint8_t type;
+ int class;
+
+ info = g_new0(PciDeviceInfo, 1);
+ info->bus = bus_num;
+ info->slot = PCI_SLOT(dev->devfn);
+ info->function = PCI_FUNC(dev->devfn);
+
+ info->class_info = g_new0(PciDeviceClass, 1);
+ class = pci_get_word(dev->config + PCI_CLASS_DEVICE);
+ info->class_info->q_class = class;
+ desc = get_class_desc(class);
+ if (desc->desc) {
+ info->class_info->has_desc = true;
+ info->class_info->desc = g_strdup(desc->desc);
+ }
+
+ info->id = g_new0(PciDeviceId, 1);
+ info->id->vendor = pci_get_word(dev->config + PCI_VENDOR_ID);
+ info->id->device = pci_get_word(dev->config + PCI_DEVICE_ID);
+ info->regions = qmp_query_pci_regions(dev);
+ info->qdev_id = g_strdup(dev->qdev.id ? dev->qdev.id : "");
+
+ if (dev->config[PCI_INTERRUPT_PIN] != 0) {
+ info->has_irq = true;
+ info->irq = dev->config[PCI_INTERRUPT_LINE];
+ }
+
+ type = dev->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;
+ if (type == PCI_HEADER_TYPE_BRIDGE) {
+ info->has_pci_bridge = true;
+ info->pci_bridge = qmp_query_pci_bridge(dev, bus, bus_num);
+ }
+
+ return info;
+}
+
+static PciDeviceInfoList *qmp_query_pci_devices(PCIBus *bus, int bus_num)
+{
+ PciDeviceInfoList *info, *head = NULL, *cur_item = NULL;
+ PCIDevice *dev;
+ int devfn;
+
+ for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
+ dev = bus->devices[devfn];
+ if (dev) {
+ info = g_malloc0(sizeof(*info));
+ info->value = qmp_query_pci_device(dev, bus, bus_num);
+
+ /* XXX: waiting for the qapi to support GSList */
+ if (!cur_item) {
+ head = cur_item = info;
+ } else {
+ cur_item->next = info;
+ cur_item = info;
+ }
+ }
+ }
+
+ return head;
+}
+
+static PciInfo *qmp_query_pci_bus(PCIBus *bus, int bus_num)
+{
+ PciInfo *info = NULL;
+
+ bus = pci_find_bus_nr(bus, bus_num);
+ if (bus) {
+ info = g_malloc0(sizeof(*info));
+ info->bus = bus_num;
+ info->devices = qmp_query_pci_devices(bus, bus_num);
+ }
+
+ return info;
+}
+
+PciInfoList *qmp_query_pci(Error **errp)
+{
+ PciInfoList *info, *head = NULL, *cur_item = NULL;
+ PCIHostState *host_bridge;
+
+ QLIST_FOREACH(host_bridge, &pci_host_bridges, next) {
+ info = g_malloc0(sizeof(*info));
+ info->value = qmp_query_pci_bus(host_bridge->bus,
+ pci_bus_num(host_bridge->bus));
+
+ /* XXX: waiting for the qapi to support GSList */
+ if (!cur_item) {
+ head = cur_item = info;
+ } else {
+ cur_item->next = info;
+ cur_item = info;
+ }
+ }
+
+ return head;
+}
+
+static const char * const pci_nic_models[] = {
+ "ne2k_pci",
+ "i82551",
+ "i82557b",
+ "i82559er",
+ "rtl8139",
+ "e1000",
+ "pcnet",
+ "virtio",
+ NULL
+};
+
+static const char * const pci_nic_names[] = {
+ "ne2k_pci",
+ "i82551",
+ "i82557b",
+ "i82559er",
+ "rtl8139",
+ "e1000",
+ "pcnet",
+ "virtio-net-pci",
+ NULL
+};
+
+/* Initialize a PCI NIC. */
+PCIDevice *pci_nic_init_nofail(NICInfo *nd, PCIBus *rootbus,
+ const char *default_model,
+ const char *default_devaddr)
+{
+ const char *devaddr = nd->devaddr ? nd->devaddr : default_devaddr;
+ Error *err = NULL;
+ PCIBus *bus;
+ PCIDevice *pci_dev;
+ DeviceState *dev;
+ int devfn;
+ int i;
+
+ if (qemu_show_nic_models(nd->model, pci_nic_models)) {
+ exit(0);
+ }
+
+ i = qemu_find_nic_model(nd, pci_nic_models, default_model);
+ if (i < 0) {
+ exit(1);
+ }
+
+ bus = pci_get_bus_devfn(&devfn, rootbus, devaddr);
+ if (!bus) {
+ error_report("Invalid PCI device address %s for device %s",
+ devaddr, pci_nic_names[i]);
+ exit(1);
+ }
+
+ pci_dev = pci_create(bus, devfn, pci_nic_names[i]);
+ dev = &pci_dev->qdev;
+ qdev_set_nic_properties(dev, nd);
+
+ object_property_set_bool(OBJECT(dev), true, "realized", &err);
+ if (err) {
+ error_report_err(err);
+ object_unparent(OBJECT(dev));
+ exit(1);
+ }
+
+ return pci_dev;
+}
+
+PCIDevice *pci_vga_init(PCIBus *bus)
+{
+ switch (vga_interface_type) {
+ case VGA_CIRRUS:
+ return pci_create_simple(bus, -1, "cirrus-vga");
+ case VGA_QXL:
+ return pci_create_simple(bus, -1, "qxl-vga");
+ case VGA_STD:
+ return pci_create_simple(bus, -1, "VGA");
+ case VGA_VMWARE:
+ return pci_create_simple(bus, -1, "vmware-svga");
+ case VGA_VIRTIO:
+ return pci_create_simple(bus, -1, "virtio-vga");
+ case VGA_NONE:
+ default: /* Other non-PCI types. Checking for unsupported types is already
+ done in vl.c. */
+ return NULL;
+ }
+}
+
+/* Whether a given bus number is in range of the secondary
+ * bus of the given bridge device. */
+static bool pci_secondary_bus_in_range(PCIDevice *dev, int bus_num)
+{
+ return !(pci_get_word(dev->config + PCI_BRIDGE_CONTROL) &
+ PCI_BRIDGE_CTL_BUS_RESET) /* Don't walk the bus if it's reset. */ &&
+ dev->config[PCI_SECONDARY_BUS] <= bus_num &&
+ bus_num <= dev->config[PCI_SUBORDINATE_BUS];
+}
+
+/* Whether a given bus number is in a range of a root bus */
+static bool pci_root_bus_in_range(PCIBus *bus, int bus_num)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bus->devices); ++i) {
+ PCIDevice *dev = bus->devices[i];
+
+ if (dev && PCI_DEVICE_GET_CLASS(dev)->is_bridge) {
+ if (pci_secondary_bus_in_range(dev, bus_num)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static PCIBus *pci_find_bus_nr(PCIBus *bus, int bus_num)
+{
+ PCIBus *sec;
+
+ if (!bus) {
+ return NULL;
+ }
+
+ if (pci_bus_num(bus) == bus_num) {
+ return bus;
+ }
+
+ /* Consider all bus numbers in range for the host pci bridge. */
+ if (!pci_bus_is_root(bus) &&
+ !pci_secondary_bus_in_range(bus->parent_dev, bus_num)) {
+ return NULL;
+ }
+
+ /* try child bus */
+ for (; bus; bus = sec) {
+ QLIST_FOREACH(sec, &bus->child, sibling) {
+ if (pci_bus_num(sec) == bus_num) {
+ return sec;
+ }
+ /* PXB buses assumed to be children of bus 0 */
+ if (pci_bus_is_root(sec)) {
+ if (pci_root_bus_in_range(sec, bus_num)) {
+ break;
+ }
+ } else {
+ if (pci_secondary_bus_in_range(sec->parent_dev, bus_num)) {
+ break;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void pci_for_each_bus_depth_first(PCIBus *bus,
+ void *(*begin)(PCIBus *bus, void *parent_state),
+ void (*end)(PCIBus *bus, void *state),
+ void *parent_state)
+{
+ PCIBus *sec;
+ void *state;
+
+ if (!bus) {
+ return;
+ }
+
+ if (begin) {
+ state = begin(bus, parent_state);
+ } else {
+ state = parent_state;
+ }
+
+ QLIST_FOREACH(sec, &bus->child, sibling) {
+ pci_for_each_bus_depth_first(sec, begin, end, state);
+ }
+
+ if (end) {
+ end(bus, state);
+ }
+}
+
+
+PCIDevice *pci_find_device(PCIBus *bus, int bus_num, uint8_t devfn)
+{
+ bus = pci_find_bus_nr(bus, bus_num);
+
+ if (!bus)
+ return NULL;
+
+ return bus->devices[devfn];
+}
+
+static void pci_qdev_realize(DeviceState *qdev, Error **errp)
+{
+ PCIDevice *pci_dev = (PCIDevice *)qdev;
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pci_dev);
+ Error *local_err = NULL;
+ PCIBus *bus;
+ bool is_default_rom;
+
+ /* initialize cap_present for pci_is_express() and pci_config_size() */
+ if (pc->is_express) {
+ pci_dev->cap_present |= QEMU_PCI_CAP_EXPRESS;
+ }
+
+ bus = PCI_BUS(qdev_get_parent_bus(qdev));
+ pci_dev = do_pci_register_device(pci_dev, bus,
+ object_get_typename(OBJECT(qdev)),
+ pci_dev->devfn, errp);
+ if (pci_dev == NULL)
+ return;
+
+ if (pc->realize) {
+ pc->realize(pci_dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ do_pci_unregister_device(pci_dev);
+ return;
+ }
+ }
+
+ /* rom loading */
+ is_default_rom = false;
+ if (pci_dev->romfile == NULL && pc->romfile != NULL) {
+ pci_dev->romfile = g_strdup(pc->romfile);
+ is_default_rom = true;
+ }
+
+ pci_add_option_rom(pci_dev, is_default_rom, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ pci_qdev_unrealize(DEVICE(pci_dev), NULL);
+ return;
+ }
+}
+
+static void pci_default_realize(PCIDevice *dev, Error **errp)
+{
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+
+ if (pc->init) {
+ if (pc->init(dev) < 0) {
+ error_setg(errp, "Device initialization failed");
+ return;
+ }
+ }
+}
+
+PCIDevice *pci_create_multifunction(PCIBus *bus, int devfn, bool multifunction,
+ const char *name)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->qbus, name);
+ qdev_prop_set_int32(dev, "addr", devfn);
+ qdev_prop_set_bit(dev, "multifunction", multifunction);
+ return PCI_DEVICE(dev);
+}
+
+PCIDevice *pci_create_simple_multifunction(PCIBus *bus, int devfn,
+ bool multifunction,
+ const char *name)
+{
+ PCIDevice *dev = pci_create_multifunction(bus, devfn, multifunction, name);
+ qdev_init_nofail(&dev->qdev);
+ return dev;
+}
+
+PCIDevice *pci_create(PCIBus *bus, int devfn, const char *name)
+{
+ return pci_create_multifunction(bus, devfn, false, name);
+}
+
+PCIDevice *pci_create_simple(PCIBus *bus, int devfn, const char *name)
+{
+ return pci_create_simple_multifunction(bus, devfn, false, name);
+}
+
+static uint8_t pci_find_space(PCIDevice *pdev, uint8_t size)
+{
+ int offset = PCI_CONFIG_HEADER_SIZE;
+ int i;
+ for (i = PCI_CONFIG_HEADER_SIZE; i < PCI_CONFIG_SPACE_SIZE; ++i) {
+ if (pdev->used[i])
+ offset = i + 1;
+ else if (i - offset + 1 == size)
+ return offset;
+ }
+ return 0;
+}
+
+static uint8_t pci_find_capability_list(PCIDevice *pdev, uint8_t cap_id,
+ uint8_t *prev_p)
+{
+ uint8_t next, prev;
+
+ if (!(pdev->config[PCI_STATUS] & PCI_STATUS_CAP_LIST))
+ return 0;
+
+ for (prev = PCI_CAPABILITY_LIST; (next = pdev->config[prev]);
+ prev = next + PCI_CAP_LIST_NEXT)
+ if (pdev->config[next + PCI_CAP_LIST_ID] == cap_id)
+ break;
+
+ if (prev_p)
+ *prev_p = prev;
+ return next;
+}
+
+static uint8_t pci_find_capability_at_offset(PCIDevice *pdev, uint8_t offset)
+{
+ uint8_t next, prev, found = 0;
+
+ if (!(pdev->used[offset])) {
+ return 0;
+ }
+
+ assert(pdev->config[PCI_STATUS] & PCI_STATUS_CAP_LIST);
+
+ for (prev = PCI_CAPABILITY_LIST; (next = pdev->config[prev]);
+ prev = next + PCI_CAP_LIST_NEXT) {
+ if (next <= offset && next > found) {
+ found = next;
+ }
+ }
+ return found;
+}
+
+/* Patch the PCI vendor and device ids in a PCI rom image if necessary.
+ This is needed for an option rom which is used for more than one device. */
+static void pci_patch_ids(PCIDevice *pdev, uint8_t *ptr, int size)
+{
+ uint16_t vendor_id;
+ uint16_t device_id;
+ uint16_t rom_vendor_id;
+ uint16_t rom_device_id;
+ uint16_t rom_magic;
+ uint16_t pcir_offset;
+ uint8_t checksum;
+
+ /* Words in rom data are little endian (like in PCI configuration),
+ so they can be read / written with pci_get_word / pci_set_word. */
+
+ /* Only a valid rom will be patched. */
+ rom_magic = pci_get_word(ptr);
+ if (rom_magic != 0xaa55) {
+ PCI_DPRINTF("Bad ROM magic %04x\n", rom_magic);
+ return;
+ }
+ pcir_offset = pci_get_word(ptr + 0x18);
+ if (pcir_offset + 8 >= size || memcmp(ptr + pcir_offset, "PCIR", 4)) {
+ PCI_DPRINTF("Bad PCIR offset 0x%x or signature\n", pcir_offset);
+ return;
+ }
+
+ vendor_id = pci_get_word(pdev->config + PCI_VENDOR_ID);
+ device_id = pci_get_word(pdev->config + PCI_DEVICE_ID);
+ rom_vendor_id = pci_get_word(ptr + pcir_offset + 4);
+ rom_device_id = pci_get_word(ptr + pcir_offset + 6);
+
+ PCI_DPRINTF("%s: ROM id %04x%04x / PCI id %04x%04x\n", pdev->romfile,
+ vendor_id, device_id, rom_vendor_id, rom_device_id);
+
+ checksum = ptr[6];
+
+ if (vendor_id != rom_vendor_id) {
+ /* Patch vendor id and checksum (at offset 6 for etherboot roms). */
+ checksum += (uint8_t)rom_vendor_id + (uint8_t)(rom_vendor_id >> 8);
+ checksum -= (uint8_t)vendor_id + (uint8_t)(vendor_id >> 8);
+ PCI_DPRINTF("ROM checksum %02x / %02x\n", ptr[6], checksum);
+ ptr[6] = checksum;
+ pci_set_word(ptr + pcir_offset + 4, vendor_id);
+ }
+
+ if (device_id != rom_device_id) {
+ /* Patch device id and checksum (at offset 6 for etherboot roms). */
+ checksum += (uint8_t)rom_device_id + (uint8_t)(rom_device_id >> 8);
+ checksum -= (uint8_t)device_id + (uint8_t)(device_id >> 8);
+ PCI_DPRINTF("ROM checksum %02x / %02x\n", ptr[6], checksum);
+ ptr[6] = checksum;
+ pci_set_word(ptr + pcir_offset + 6, device_id);
+ }
+}
+
+/* Add an option rom for the device */
+static void pci_add_option_rom(PCIDevice *pdev, bool is_default_rom,
+ Error **errp)
+{
+ int size;
+ char *path;
+ void *ptr;
+ char name[32];
+ const VMStateDescription *vmsd;
+
+ if (!pdev->romfile)
+ return;
+ if (strlen(pdev->romfile) == 0)
+ return;
+
+ if (!pdev->rom_bar) {
+ /*
+ * Load rom via fw_cfg instead of creating a rom bar,
+ * for 0.11 compatibility.
+ */
+ int class = pci_get_word(pdev->config + PCI_CLASS_DEVICE);
+
+ /*
+ * Hot-plugged devices can't use the option ROM
+ * if the rom bar is disabled.
+ */
+ if (DEVICE(pdev)->hotplugged) {
+ error_setg(errp, "Hot-plugged device without ROM bar"
+ " can't have an option ROM");
+ return;
+ }
+
+ if (class == 0x0300) {
+ rom_add_vga(pdev->romfile);
+ } else {
+ rom_add_option(pdev->romfile, -1);
+ }
+ return;
+ }
+
+ path = qemu_find_file(QEMU_FILE_TYPE_BIOS, pdev->romfile);
+ if (path == NULL) {
+ path = g_strdup(pdev->romfile);
+ }
+
+ size = get_image_size(path);
+ if (size < 0) {
+ error_setg(errp, "failed to find romfile \"%s\"", pdev->romfile);
+ g_free(path);
+ return;
+ } else if (size == 0) {
+ error_setg(errp, "romfile \"%s\" is empty", pdev->romfile);
+ g_free(path);
+ return;
+ }
+ if (size & (size - 1)) {
+ size = 1 << qemu_fls(size);
+ }
+
+ vmsd = qdev_get_vmsd(DEVICE(pdev));
+
+ if (vmsd) {
+ snprintf(name, sizeof(name), "%s.rom", vmsd->name);
+ } else {
+ snprintf(name, sizeof(name), "%s.rom", object_get_typename(OBJECT(pdev)));
+ }
+ pdev->has_rom = true;
+ memory_region_init_ram(&pdev->rom, OBJECT(pdev), name, size, &error_abort);
+ vmstate_register_ram(&pdev->rom, &pdev->qdev);
+ ptr = memory_region_get_ram_ptr(&pdev->rom);
+ load_image(path, ptr);
+ g_free(path);
+
+ if (is_default_rom) {
+ /* Only the default rom images will be patched (if needed). */
+ pci_patch_ids(pdev, ptr, size);
+ }
+
+ pci_register_bar(pdev, PCI_ROM_SLOT, 0, &pdev->rom);
+}
+
+static void pci_del_option_rom(PCIDevice *pdev)
+{
+ if (!pdev->has_rom)
+ return;
+
+ vmstate_unregister_ram(&pdev->rom, &pdev->qdev);
+ pdev->has_rom = false;
+}
+
+/*
+ * if offset = 0,
+ * Find and reserve space and add capability to the linked list
+ * in pci config space
+ */
+int pci_add_capability(PCIDevice *pdev, uint8_t cap_id,
+ uint8_t offset, uint8_t size)
+{
+ int ret;
+ Error *local_err = NULL;
+
+ ret = pci_add_capability2(pdev, cap_id, offset, size, &local_err);
+ if (local_err) {
+ assert(ret < 0);
+ error_report_err(local_err);
+ } else {
+ /* success implies a positive offset in config space */
+ assert(ret > 0);
+ }
+ return ret;
+}
+
+int pci_add_capability2(PCIDevice *pdev, uint8_t cap_id,
+ uint8_t offset, uint8_t size,
+ Error **errp)
+{
+ uint8_t *config;
+ int i, overlapping_cap;
+
+ if (!offset) {
+ offset = pci_find_space(pdev, size);
+ if (!offset) {
+ error_setg(errp, "out of PCI config space");
+ return -ENOSPC;
+ }
+ } else {
+ /* Verify that capabilities don't overlap. Note: device assignment
+ * depends on this check to verify that the device is not broken.
+ * Should never trigger for emulated devices, but it's helpful
+ * for debugging these. */
+ for (i = offset; i < offset + size; i++) {
+ overlapping_cap = pci_find_capability_at_offset(pdev, i);
+ if (overlapping_cap) {
+ error_setg(errp, "%s:%02x:%02x.%x "
+ "Attempt to add PCI capability %x at offset "
+ "%x overlaps existing capability %x at offset %x",
+ pci_root_bus_path(pdev), pci_bus_num(pdev->bus),
+ PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn),
+ cap_id, offset, overlapping_cap, i);
+ return -EINVAL;
+ }
+ }
+ }
+
+ config = pdev->config + offset;
+ config[PCI_CAP_LIST_ID] = cap_id;
+ config[PCI_CAP_LIST_NEXT] = pdev->config[PCI_CAPABILITY_LIST];
+ pdev->config[PCI_CAPABILITY_LIST] = offset;
+ pdev->config[PCI_STATUS] |= PCI_STATUS_CAP_LIST;
+ memset(pdev->used + offset, 0xFF, QEMU_ALIGN_UP(size, 4));
+ /* Make capability read-only by default */
+ memset(pdev->wmask + offset, 0, size);
+ /* Check capability by default */
+ memset(pdev->cmask + offset, 0xFF, size);
+ return offset;
+}
+
+/* Unlink capability from the pci config space. */
+void pci_del_capability(PCIDevice *pdev, uint8_t cap_id, uint8_t size)
+{
+ uint8_t prev, offset = pci_find_capability_list(pdev, cap_id, &prev);
+ if (!offset)
+ return;
+ pdev->config[prev] = pdev->config[offset + PCI_CAP_LIST_NEXT];
+ /* Make capability writable again */
+ memset(pdev->wmask + offset, 0xff, size);
+ memset(pdev->w1cmask + offset, 0, size);
+ /* Clear cmask as device-specific registers can't be checked */
+ memset(pdev->cmask + offset, 0, size);
+ memset(pdev->used + offset, 0, QEMU_ALIGN_UP(size, 4));
+
+ if (!pdev->config[PCI_CAPABILITY_LIST])
+ pdev->config[PCI_STATUS] &= ~PCI_STATUS_CAP_LIST;
+}
+
+uint8_t pci_find_capability(PCIDevice *pdev, uint8_t cap_id)
+{
+ return pci_find_capability_list(pdev, cap_id, NULL);
+}
+
+static void pcibus_dev_print(Monitor *mon, DeviceState *dev, int indent)
+{
+ PCIDevice *d = (PCIDevice *)dev;
+ const pci_class_desc *desc;
+ char ctxt[64];
+ PCIIORegion *r;
+ int i, class;
+
+ class = pci_get_word(d->config + PCI_CLASS_DEVICE);
+ desc = pci_class_descriptions;
+ while (desc->desc && class != desc->class)
+ desc++;
+ if (desc->desc) {
+ snprintf(ctxt, sizeof(ctxt), "%s", desc->desc);
+ } else {
+ snprintf(ctxt, sizeof(ctxt), "Class %04x", class);
+ }
+
+ monitor_printf(mon, "%*sclass %s, addr %02x:%02x.%x, "
+ "pci id %04x:%04x (sub %04x:%04x)\n",
+ indent, "", ctxt, pci_bus_num(d->bus),
+ PCI_SLOT(d->devfn), PCI_FUNC(d->devfn),
+ pci_get_word(d->config + PCI_VENDOR_ID),
+ pci_get_word(d->config + PCI_DEVICE_ID),
+ pci_get_word(d->config + PCI_SUBSYSTEM_VENDOR_ID),
+ pci_get_word(d->config + PCI_SUBSYSTEM_ID));
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ r = &d->io_regions[i];
+ if (!r->size)
+ continue;
+ monitor_printf(mon, "%*sbar %d: %s at 0x%"FMT_PCIBUS
+ " [0x%"FMT_PCIBUS"]\n",
+ indent, "",
+ i, r->type & PCI_BASE_ADDRESS_SPACE_IO ? "i/o" : "mem",
+ r->addr, r->addr + r->size - 1);
+ }
+}
+
+static char *pci_dev_fw_name(DeviceState *dev, char *buf, int len)
+{
+ PCIDevice *d = (PCIDevice *)dev;
+ const char *name = NULL;
+ const pci_class_desc *desc = pci_class_descriptions;
+ int class = pci_get_word(d->config + PCI_CLASS_DEVICE);
+
+ while (desc->desc &&
+ (class & ~desc->fw_ign_bits) !=
+ (desc->class & ~desc->fw_ign_bits)) {
+ desc++;
+ }
+
+ if (desc->desc) {
+ name = desc->fw_name;
+ }
+
+ if (name) {
+ pstrcpy(buf, len, name);
+ } else {
+ snprintf(buf, len, "pci%04x,%04x",
+ pci_get_word(d->config + PCI_VENDOR_ID),
+ pci_get_word(d->config + PCI_DEVICE_ID));
+ }
+
+ return buf;
+}
+
+static char *pcibus_get_fw_dev_path(DeviceState *dev)
+{
+ PCIDevice *d = (PCIDevice *)dev;
+ char path[50], name[33];
+ int off;
+
+ off = snprintf(path, sizeof(path), "%s@%x",
+ pci_dev_fw_name(dev, name, sizeof name),
+ PCI_SLOT(d->devfn));
+ if (PCI_FUNC(d->devfn))
+ snprintf(path + off, sizeof(path) + off, ",%x", PCI_FUNC(d->devfn));
+ return g_strdup(path);
+}
+
+static char *pcibus_get_dev_path(DeviceState *dev)
+{
+ PCIDevice *d = container_of(dev, PCIDevice, qdev);
+ PCIDevice *t;
+ int slot_depth;
+ /* Path format: Domain:00:Slot.Function:Slot.Function....:Slot.Function.
+ * 00 is added here to make this format compatible with
+ * domain:Bus:Slot.Func for systems without nested PCI bridges.
+ * Slot.Function list specifies the slot and function numbers for all
+ * devices on the path from root to the specific device. */
+ const char *root_bus_path;
+ int root_bus_len;
+ char slot[] = ":SS.F";
+ int slot_len = sizeof slot - 1 /* For '\0' */;
+ int path_len;
+ char *path, *p;
+ int s;
+
+ root_bus_path = pci_root_bus_path(d);
+ root_bus_len = strlen(root_bus_path);
+
+ /* Calculate # of slots on path between device and root. */;
+ slot_depth = 0;
+ for (t = d; t; t = t->bus->parent_dev) {
+ ++slot_depth;
+ }
+
+ path_len = root_bus_len + slot_len * slot_depth;
+
+ /* Allocate memory, fill in the terminating null byte. */
+ path = g_malloc(path_len + 1 /* For '\0' */);
+ path[path_len] = '\0';
+
+ memcpy(path, root_bus_path, root_bus_len);
+
+ /* Fill in slot numbers. We walk up from device to root, so need to print
+ * them in the reverse order, last to first. */
+ p = path + path_len;
+ for (t = d; t; t = t->bus->parent_dev) {
+ p -= slot_len;
+ s = snprintf(slot, sizeof slot, ":%02x.%x",
+ PCI_SLOT(t->devfn), PCI_FUNC(t->devfn));
+ assert(s == slot_len);
+ memcpy(p, slot, slot_len);
+ }
+
+ return path;
+}
+
+static int pci_qdev_find_recursive(PCIBus *bus,
+ const char *id, PCIDevice **pdev)
+{
+ DeviceState *qdev = qdev_find_recursive(&bus->qbus, id);
+ if (!qdev) {
+ return -ENODEV;
+ }
+
+ /* roughly check if given qdev is pci device */
+ if (object_dynamic_cast(OBJECT(qdev), TYPE_PCI_DEVICE)) {
+ *pdev = PCI_DEVICE(qdev);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+int pci_qdev_find_device(const char *id, PCIDevice **pdev)
+{
+ PCIHostState *host_bridge;
+ int rc = -ENODEV;
+
+ QLIST_FOREACH(host_bridge, &pci_host_bridges, next) {
+ int tmp = pci_qdev_find_recursive(host_bridge->bus, id, pdev);
+ if (!tmp) {
+ rc = 0;
+ break;
+ }
+ if (tmp != -ENODEV) {
+ rc = tmp;
+ }
+ }
+
+ return rc;
+}
+
+MemoryRegion *pci_address_space(PCIDevice *dev)
+{
+ return dev->bus->address_space_mem;
+}
+
+MemoryRegion *pci_address_space_io(PCIDevice *dev)
+{
+ return dev->bus->address_space_io;
+}
+
+static void pci_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_qdev_realize;
+ k->unrealize = pci_qdev_unrealize;
+ k->bus_type = TYPE_PCI_BUS;
+ k->props = pci_props;
+ pc->realize = pci_default_realize;
+}
+
+AddressSpace *pci_device_iommu_address_space(PCIDevice *dev)
+{
+ PCIBus *bus = PCI_BUS(dev->bus);
+
+ if (bus->iommu_fn) {
+ return bus->iommu_fn(bus, bus->iommu_opaque, dev->devfn);
+ }
+
+ if (bus->parent_dev) {
+ /** We are ignoring the bus master DMA bit of the bridge
+ * as it would complicate things such as VFIO for no good reason */
+ return pci_device_iommu_address_space(bus->parent_dev);
+ }
+
+ return &address_space_memory;
+}
+
+void pci_setup_iommu(PCIBus *bus, PCIIOMMUFunc fn, void *opaque)
+{
+ bus->iommu_fn = fn;
+ bus->iommu_opaque = opaque;
+}
+
+static void pci_dev_get_w64(PCIBus *b, PCIDevice *dev, void *opaque)
+{
+ Range *range = opaque;
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+ uint16_t cmd = pci_get_word(dev->config + PCI_COMMAND);
+ int i;
+
+ if (!(cmd & PCI_COMMAND_MEMORY)) {
+ return;
+ }
+
+ if (pc->is_bridge) {
+ pcibus_t base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+ pcibus_t limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+
+ base = MAX(base, 0x1ULL << 32);
+
+ if (limit >= base) {
+ Range pref_range;
+ pref_range.begin = base;
+ pref_range.end = limit + 1;
+ range_extend(range, &pref_range);
+ }
+ }
+ for (i = 0; i < PCI_NUM_REGIONS; ++i) {
+ PCIIORegion *r = &dev->io_regions[i];
+ Range region_range;
+
+ if (!r->size ||
+ (r->type & PCI_BASE_ADDRESS_SPACE_IO) ||
+ !(r->type & PCI_BASE_ADDRESS_MEM_TYPE_64)) {
+ continue;
+ }
+ region_range.begin = pci_bar_address(dev, i, r->type, r->size);
+ region_range.end = region_range.begin + r->size;
+
+ if (region_range.begin == PCI_BAR_UNMAPPED) {
+ continue;
+ }
+
+ region_range.begin = MAX(region_range.begin, 0x1ULL << 32);
+
+ if (region_range.end - 1 >= region_range.begin) {
+ range_extend(range, &region_range);
+ }
+ }
+}
+
+void pci_bus_get_w64_range(PCIBus *bus, Range *range)
+{
+ range->begin = range->end = 0;
+ pci_for_each_device_under_bus(bus, pci_dev_get_w64, range);
+}
+
+static const TypeInfo pci_device_type_info = {
+ .name = TYPE_PCI_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .abstract = true,
+ .class_size = sizeof(PCIDeviceClass),
+ .class_init = pci_device_class_init,
+};
+
+static void pci_register_types(void)
+{
+ type_register_static(&pci_bus_info);
+ type_register_static(&pcie_bus_info);
+ type_register_static(&pci_device_type_info);
+}
+
+type_init(pci_register_types)
diff --git a/hw/pci/pci_bridge.c b/hw/pci/pci_bridge.c
new file mode 100644
index 00000000..40c97b15
--- /dev/null
+++ b/hw/pci/pci_bridge.c
@@ -0,0 +1,419 @@
+/*
+ * QEMU PCI bus manager
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to dea
+
+ * 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.
+ */
+/*
+ * split out from pci.c
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ */
+
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_bus.h"
+#include "qemu/range.h"
+
+/* PCI bridge subsystem vendor ID helper functions */
+#define PCI_SSVID_SIZEOF 8
+#define PCI_SSVID_SVID 4
+#define PCI_SSVID_SSID 6
+
+int pci_bridge_ssvid_init(PCIDevice *dev, uint8_t offset,
+ uint16_t svid, uint16_t ssid)
+{
+ int pos;
+ pos = pci_add_capability(dev, PCI_CAP_ID_SSVID, offset, PCI_SSVID_SIZEOF);
+ if (pos < 0) {
+ return pos;
+ }
+
+ pci_set_word(dev->config + pos + PCI_SSVID_SVID, svid);
+ pci_set_word(dev->config + pos + PCI_SSVID_SSID, ssid);
+ return pos;
+}
+
+/* Accessor function to get parent bridge device from pci bus. */
+PCIDevice *pci_bridge_get_device(PCIBus *bus)
+{
+ return bus->parent_dev;
+}
+
+/* Accessor function to get secondary bus from pci-to-pci bridge device */
+PCIBus *pci_bridge_get_sec_bus(PCIBridge *br)
+{
+ return &br->sec_bus;
+}
+
+static uint32_t pci_config_get_io_base(const PCIDevice *d,
+ uint32_t base, uint32_t base_upper16)
+{
+ uint32_t val;
+
+ val = ((uint32_t)d->config[base] & PCI_IO_RANGE_MASK) << 8;
+ if (d->config[base] & PCI_IO_RANGE_TYPE_32) {
+ val |= (uint32_t)pci_get_word(d->config + base_upper16) << 16;
+ }
+ return val;
+}
+
+static pcibus_t pci_config_get_memory_base(const PCIDevice *d, uint32_t base)
+{
+ return ((pcibus_t)pci_get_word(d->config + base) & PCI_MEMORY_RANGE_MASK)
+ << 16;
+}
+
+static pcibus_t pci_config_get_pref_base(const PCIDevice *d,
+ uint32_t base, uint32_t upper)
+{
+ pcibus_t tmp;
+ pcibus_t val;
+
+ tmp = (pcibus_t)pci_get_word(d->config + base);
+ val = (tmp & PCI_PREF_RANGE_MASK) << 16;
+ if (tmp & PCI_PREF_RANGE_TYPE_64) {
+ val |= (pcibus_t)pci_get_long(d->config + upper) << 32;
+ }
+ return val;
+}
+
+/* accessor function to get bridge filtering base address */
+pcibus_t pci_bridge_get_base(const PCIDevice *bridge, uint8_t type)
+{
+ pcibus_t base;
+ if (type & PCI_BASE_ADDRESS_SPACE_IO) {
+ base = pci_config_get_io_base(bridge,
+ PCI_IO_BASE, PCI_IO_BASE_UPPER16);
+ } else {
+ if (type & PCI_BASE_ADDRESS_MEM_PREFETCH) {
+ base = pci_config_get_pref_base(
+ bridge, PCI_PREF_MEMORY_BASE, PCI_PREF_BASE_UPPER32);
+ } else {
+ base = pci_config_get_memory_base(bridge, PCI_MEMORY_BASE);
+ }
+ }
+
+ return base;
+}
+
+/* accessor funciton to get bridge filtering limit */
+pcibus_t pci_bridge_get_limit(const PCIDevice *bridge, uint8_t type)
+{
+ pcibus_t limit;
+ if (type & PCI_BASE_ADDRESS_SPACE_IO) {
+ limit = pci_config_get_io_base(bridge,
+ PCI_IO_LIMIT, PCI_IO_LIMIT_UPPER16);
+ limit |= 0xfff; /* PCI bridge spec 3.2.5.6. */
+ } else {
+ if (type & PCI_BASE_ADDRESS_MEM_PREFETCH) {
+ limit = pci_config_get_pref_base(
+ bridge, PCI_PREF_MEMORY_LIMIT, PCI_PREF_LIMIT_UPPER32);
+ } else {
+ limit = pci_config_get_memory_base(bridge, PCI_MEMORY_LIMIT);
+ }
+ limit |= 0xfffff; /* PCI bridge spec 3.2.5.{1, 8}. */
+ }
+ return limit;
+}
+
+static void pci_bridge_init_alias(PCIBridge *bridge, MemoryRegion *alias,
+ uint8_t type, const char *name,
+ MemoryRegion *space,
+ MemoryRegion *parent_space,
+ bool enabled)
+{
+ PCIDevice *bridge_dev = PCI_DEVICE(bridge);
+ pcibus_t base = pci_bridge_get_base(bridge_dev, type);
+ pcibus_t limit = pci_bridge_get_limit(bridge_dev, type);
+ /* TODO: this doesn't handle base = 0 limit = 2^64 - 1 correctly.
+ * Apparently no way to do this with existing memory APIs. */
+ pcibus_t size = enabled && limit >= base ? limit + 1 - base : 0;
+
+ memory_region_init_alias(alias, OBJECT(bridge), name, space, base, size);
+ memory_region_add_subregion_overlap(parent_space, base, alias, 1);
+}
+
+static void pci_bridge_init_vga_aliases(PCIBridge *br, PCIBus *parent,
+ MemoryRegion *alias_vga)
+{
+ PCIDevice *pd = PCI_DEVICE(br);
+ uint16_t brctl = pci_get_word(pd->config + PCI_BRIDGE_CONTROL);
+
+ memory_region_init_alias(&alias_vga[QEMU_PCI_VGA_IO_LO], OBJECT(br),
+ "pci_bridge_vga_io_lo", &br->address_space_io,
+ QEMU_PCI_VGA_IO_LO_BASE, QEMU_PCI_VGA_IO_LO_SIZE);
+ memory_region_init_alias(&alias_vga[QEMU_PCI_VGA_IO_HI], OBJECT(br),
+ "pci_bridge_vga_io_hi", &br->address_space_io,
+ QEMU_PCI_VGA_IO_HI_BASE, QEMU_PCI_VGA_IO_HI_SIZE);
+ memory_region_init_alias(&alias_vga[QEMU_PCI_VGA_MEM], OBJECT(br),
+ "pci_bridge_vga_mem", &br->address_space_mem,
+ QEMU_PCI_VGA_MEM_BASE, QEMU_PCI_VGA_MEM_SIZE);
+
+ if (brctl & PCI_BRIDGE_CTL_VGA) {
+ pci_register_vga(pd, &alias_vga[QEMU_PCI_VGA_MEM],
+ &alias_vga[QEMU_PCI_VGA_IO_LO],
+ &alias_vga[QEMU_PCI_VGA_IO_HI]);
+ }
+}
+
+static PCIBridgeWindows *pci_bridge_region_init(PCIBridge *br)
+{
+ PCIDevice *pd = PCI_DEVICE(br);
+ PCIBus *parent = pd->bus;
+ PCIBridgeWindows *w = g_new(PCIBridgeWindows, 1);
+ uint16_t cmd = pci_get_word(pd->config + PCI_COMMAND);
+
+ pci_bridge_init_alias(br, &w->alias_pref_mem,
+ PCI_BASE_ADDRESS_MEM_PREFETCH,
+ "pci_bridge_pref_mem",
+ &br->address_space_mem,
+ parent->address_space_mem,
+ cmd & PCI_COMMAND_MEMORY);
+ pci_bridge_init_alias(br, &w->alias_mem,
+ PCI_BASE_ADDRESS_SPACE_MEMORY,
+ "pci_bridge_mem",
+ &br->address_space_mem,
+ parent->address_space_mem,
+ cmd & PCI_COMMAND_MEMORY);
+ pci_bridge_init_alias(br, &w->alias_io,
+ PCI_BASE_ADDRESS_SPACE_IO,
+ "pci_bridge_io",
+ &br->address_space_io,
+ parent->address_space_io,
+ cmd & PCI_COMMAND_IO);
+
+ pci_bridge_init_vga_aliases(br, parent, w->alias_vga);
+
+ return w;
+}
+
+static void pci_bridge_region_del(PCIBridge *br, PCIBridgeWindows *w)
+{
+ PCIDevice *pd = PCI_DEVICE(br);
+ PCIBus *parent = pd->bus;
+
+ memory_region_del_subregion(parent->address_space_io, &w->alias_io);
+ memory_region_del_subregion(parent->address_space_mem, &w->alias_mem);
+ memory_region_del_subregion(parent->address_space_mem, &w->alias_pref_mem);
+ pci_unregister_vga(pd);
+}
+
+static void pci_bridge_region_cleanup(PCIBridge *br, PCIBridgeWindows *w)
+{
+ object_unparent(OBJECT(&w->alias_io));
+ object_unparent(OBJECT(&w->alias_mem));
+ object_unparent(OBJECT(&w->alias_pref_mem));
+ object_unparent(OBJECT(&w->alias_vga[QEMU_PCI_VGA_IO_LO]));
+ object_unparent(OBJECT(&w->alias_vga[QEMU_PCI_VGA_IO_HI]));
+ object_unparent(OBJECT(&w->alias_vga[QEMU_PCI_VGA_MEM]));
+ g_free(w);
+}
+
+void pci_bridge_update_mappings(PCIBridge *br)
+{
+ PCIBridgeWindows *w = br->windows;
+
+ /* Make updates atomic to: handle the case of one VCPU updating the bridge
+ * while another accesses an unaffected region. */
+ memory_region_transaction_begin();
+ pci_bridge_region_del(br, br->windows);
+ br->windows = pci_bridge_region_init(br);
+ memory_region_transaction_commit();
+ pci_bridge_region_cleanup(br, w);
+}
+
+/* default write_config function for PCI-to-PCI bridge */
+void pci_bridge_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ PCIBridge *s = PCI_BRIDGE(d);
+ uint16_t oldctl = pci_get_word(d->config + PCI_BRIDGE_CONTROL);
+ uint16_t newctl;
+
+ pci_default_write_config(d, address, val, len);
+
+ if (ranges_overlap(address, len, PCI_COMMAND, 2) ||
+
+ /* io base/limit */
+ ranges_overlap(address, len, PCI_IO_BASE, 2) ||
+
+ /* memory base/limit, prefetchable base/limit and
+ io base/limit upper 16 */
+ ranges_overlap(address, len, PCI_MEMORY_BASE, 20) ||
+
+ /* vga enable */
+ ranges_overlap(address, len, PCI_BRIDGE_CONTROL, 2)) {
+ pci_bridge_update_mappings(s);
+ }
+
+ newctl = pci_get_word(d->config + PCI_BRIDGE_CONTROL);
+ if (~oldctl & newctl & PCI_BRIDGE_CTL_BUS_RESET) {
+ /* Trigger hot reset on 0->1 transition. */
+ qbus_reset_all(&s->sec_bus.qbus);
+ }
+}
+
+void pci_bridge_disable_base_limit(PCIDevice *dev)
+{
+ uint8_t *conf = dev->config;
+
+ pci_byte_test_and_set_mask(conf + PCI_IO_BASE,
+ PCI_IO_RANGE_MASK & 0xff);
+ pci_byte_test_and_clear_mask(conf + PCI_IO_LIMIT,
+ PCI_IO_RANGE_MASK & 0xff);
+ pci_word_test_and_set_mask(conf + PCI_MEMORY_BASE,
+ PCI_MEMORY_RANGE_MASK & 0xffff);
+ pci_word_test_and_clear_mask(conf + PCI_MEMORY_LIMIT,
+ PCI_MEMORY_RANGE_MASK & 0xffff);
+ pci_word_test_and_set_mask(conf + PCI_PREF_MEMORY_BASE,
+ PCI_PREF_RANGE_MASK & 0xffff);
+ pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_LIMIT,
+ PCI_PREF_RANGE_MASK & 0xffff);
+ pci_set_long(conf + PCI_PREF_BASE_UPPER32, 0);
+ pci_set_long(conf + PCI_PREF_LIMIT_UPPER32, 0);
+}
+
+/* reset bridge specific configuration registers */
+void pci_bridge_reset(DeviceState *qdev)
+{
+ PCIDevice *dev = PCI_DEVICE(qdev);
+ uint8_t *conf = dev->config;
+
+ conf[PCI_PRIMARY_BUS] = 0;
+ conf[PCI_SECONDARY_BUS] = 0;
+ conf[PCI_SUBORDINATE_BUS] = 0;
+ conf[PCI_SEC_LATENCY_TIMER] = 0;
+
+ /*
+ * the default values for base/limit registers aren't specified
+ * in the PCI-to-PCI-bridge spec. So we don't thouch them here.
+ * Each implementation can override it.
+ * typical implementation does
+ * zero base/limit registers or
+ * disable forwarding: pci_bridge_disable_base_limit()
+ * If disable forwarding is wanted, call pci_bridge_disable_base_limit()
+ * after this function.
+ */
+ pci_byte_test_and_clear_mask(conf + PCI_IO_BASE,
+ PCI_IO_RANGE_MASK & 0xff);
+ pci_byte_test_and_clear_mask(conf + PCI_IO_LIMIT,
+ PCI_IO_RANGE_MASK & 0xff);
+ pci_word_test_and_clear_mask(conf + PCI_MEMORY_BASE,
+ PCI_MEMORY_RANGE_MASK & 0xffff);
+ pci_word_test_and_clear_mask(conf + PCI_MEMORY_LIMIT,
+ PCI_MEMORY_RANGE_MASK & 0xffff);
+ pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_BASE,
+ PCI_PREF_RANGE_MASK & 0xffff);
+ pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_LIMIT,
+ PCI_PREF_RANGE_MASK & 0xffff);
+ pci_set_long(conf + PCI_PREF_BASE_UPPER32, 0);
+ pci_set_long(conf + PCI_PREF_LIMIT_UPPER32, 0);
+
+ pci_set_word(conf + PCI_BRIDGE_CONTROL, 0);
+}
+
+/* default qdev initialization function for PCI-to-PCI bridge */
+int pci_bridge_initfn(PCIDevice *dev, const char *typename)
+{
+ PCIBus *parent = dev->bus;
+ PCIBridge *br = PCI_BRIDGE(dev);
+ PCIBus *sec_bus = &br->sec_bus;
+
+ pci_word_test_and_set_mask(dev->config + PCI_STATUS,
+ PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK);
+
+ /*
+ * TODO: We implement VGA Enable in the Bridge Control Register
+ * therefore per the PCI to PCI bridge spec we must also implement
+ * VGA Palette Snooping. When done, set this bit writable:
+ *
+ * pci_word_test_and_set_mask(dev->wmask + PCI_COMMAND,
+ * PCI_COMMAND_VGA_PALETTE);
+ */
+
+ pci_config_set_class(dev->config, PCI_CLASS_BRIDGE_PCI);
+ dev->config[PCI_HEADER_TYPE] =
+ (dev->config[PCI_HEADER_TYPE] & PCI_HEADER_TYPE_MULTI_FUNCTION) |
+ PCI_HEADER_TYPE_BRIDGE;
+ pci_set_word(dev->config + PCI_SEC_STATUS,
+ PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK);
+
+ /*
+ * If we don't specify the name, the bus will be addressed as <id>.0, where
+ * id is the device id.
+ * Since PCI Bridge devices have a single bus each, we don't need the index:
+ * let users address the bus using the device name.
+ */
+ if (!br->bus_name && dev->qdev.id && *dev->qdev.id) {
+ br->bus_name = dev->qdev.id;
+ }
+
+ qbus_create_inplace(sec_bus, sizeof(br->sec_bus), typename, DEVICE(dev),
+ br->bus_name);
+ sec_bus->parent_dev = dev;
+ sec_bus->map_irq = br->map_irq ? br->map_irq : pci_swizzle_map_irq_fn;
+ sec_bus->address_space_mem = &br->address_space_mem;
+ memory_region_init(&br->address_space_mem, OBJECT(br), "pci_bridge_pci", UINT64_MAX);
+ sec_bus->address_space_io = &br->address_space_io;
+ memory_region_init(&br->address_space_io, OBJECT(br), "pci_bridge_io", 65536);
+ br->windows = pci_bridge_region_init(br);
+ QLIST_INIT(&sec_bus->child);
+ QLIST_INSERT_HEAD(&parent->child, sec_bus, sibling);
+ return 0;
+}
+
+/* default qdev clean up function for PCI-to-PCI bridge */
+void pci_bridge_exitfn(PCIDevice *pci_dev)
+{
+ PCIBridge *s = PCI_BRIDGE(pci_dev);
+ assert(QLIST_EMPTY(&s->sec_bus.child));
+ QLIST_REMOVE(&s->sec_bus, sibling);
+ pci_bridge_region_del(s, s->windows);
+ pci_bridge_region_cleanup(s, s->windows);
+ /* object_unparent() is called automatically during device deletion */
+}
+
+/*
+ * before qdev initialization(qdev_init()), this function sets bus_name and
+ * map_irq callback which are necessry for pci_bridge_initfn() to
+ * initialize bus.
+ */
+void pci_bridge_map_irq(PCIBridge *br, const char* bus_name,
+ pci_map_irq_fn map_irq)
+{
+ br->map_irq = map_irq;
+ br->bus_name = bus_name;
+}
+
+static const TypeInfo pci_bridge_type_info = {
+ .name = TYPE_PCI_BRIDGE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIBridge),
+ .abstract = true,
+};
+
+static void pci_bridge_register_types(void)
+{
+ type_register_static(&pci_bridge_type_info);
+}
+
+type_init(pci_bridge_register_types)
diff --git a/hw/pci/pci_host.c b/hw/pci/pci_host.c
new file mode 100644
index 00000000..3e26f925
--- /dev/null
+++ b/hw/pci/pci_host.c
@@ -0,0 +1,191 @@
+/*
+ * pci_host.c
+ *
+ * Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "trace.h"
+
+/* debug PCI */
+//#define DEBUG_PCI
+
+#ifdef DEBUG_PCI
+#define PCI_DPRINTF(fmt, ...) \
+do { printf("pci_host_data: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define PCI_DPRINTF(fmt, ...)
+#endif
+
+/*
+ * PCI address
+ * bit 16 - 24: bus number
+ * bit 8 - 15: devfun number
+ * bit 0 - 7: offset in configuration space of a given pci device
+ */
+
+/* the helper function to get a PCIDevice* for a given pci address */
+static inline PCIDevice *pci_dev_find_by_addr(PCIBus *bus, uint32_t addr)
+{
+ uint8_t bus_num = addr >> 16;
+ uint8_t devfn = addr >> 8;
+
+ return pci_find_device(bus, bus_num, devfn);
+}
+
+void pci_host_config_write_common(PCIDevice *pci_dev, uint32_t addr,
+ uint32_t limit, uint32_t val, uint32_t len)
+{
+ assert(len <= 4);
+ trace_pci_cfg_write(pci_dev->name, PCI_SLOT(pci_dev->devfn),
+ PCI_FUNC(pci_dev->devfn), addr, val);
+ pci_dev->config_write(pci_dev, addr, val, MIN(len, limit - addr));
+}
+
+uint32_t pci_host_config_read_common(PCIDevice *pci_dev, uint32_t addr,
+ uint32_t limit, uint32_t len)
+{
+ uint32_t ret;
+
+ assert(len <= 4);
+ ret = pci_dev->config_read(pci_dev, addr, MIN(len, limit - addr));
+ trace_pci_cfg_read(pci_dev->name, PCI_SLOT(pci_dev->devfn),
+ PCI_FUNC(pci_dev->devfn), addr, ret);
+
+ return ret;
+}
+
+void pci_data_write(PCIBus *s, uint32_t addr, uint32_t val, int len)
+{
+ PCIDevice *pci_dev = pci_dev_find_by_addr(s, addr);
+ uint32_t config_addr = addr & (PCI_CONFIG_SPACE_SIZE - 1);
+
+ if (!pci_dev) {
+ return;
+ }
+
+ PCI_DPRINTF("%s: %s: addr=%02" PRIx32 " val=%08" PRIx32 " len=%d\n",
+ __func__, pci_dev->name, config_addr, val, len);
+ pci_host_config_write_common(pci_dev, config_addr, PCI_CONFIG_SPACE_SIZE,
+ val, len);
+}
+
+uint32_t pci_data_read(PCIBus *s, uint32_t addr, int len)
+{
+ PCIDevice *pci_dev = pci_dev_find_by_addr(s, addr);
+ uint32_t config_addr = addr & (PCI_CONFIG_SPACE_SIZE - 1);
+ uint32_t val;
+
+ if (!pci_dev) {
+ return ~0x0;
+ }
+
+ val = pci_host_config_read_common(pci_dev, config_addr,
+ PCI_CONFIG_SPACE_SIZE, len);
+ PCI_DPRINTF("%s: %s: addr=%02"PRIx32" val=%08"PRIx32" len=%d\n",
+ __func__, pci_dev->name, config_addr, val, len);
+
+ return val;
+}
+
+static void pci_host_config_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned len)
+{
+ PCIHostState *s = opaque;
+
+ PCI_DPRINTF("%s addr " TARGET_FMT_plx " len %d val %"PRIx64"\n",
+ __func__, addr, len, val);
+ if (addr != 0 || len != 4) {
+ return;
+ }
+ s->config_reg = val;
+}
+
+static uint64_t pci_host_config_read(void *opaque, hwaddr addr,
+ unsigned len)
+{
+ PCIHostState *s = opaque;
+ uint32_t val = s->config_reg;
+
+ PCI_DPRINTF("%s addr " TARGET_FMT_plx " len %d val %"PRIx32"\n",
+ __func__, addr, len, val);
+ return val;
+}
+
+static void pci_host_data_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned len)
+{
+ PCIHostState *s = opaque;
+ PCI_DPRINTF("write addr " TARGET_FMT_plx " len %d val %x\n",
+ addr, len, (unsigned)val);
+ if (s->config_reg & (1u << 31))
+ pci_data_write(s->bus, s->config_reg | (addr & 3), val, len);
+}
+
+static uint64_t pci_host_data_read(void *opaque,
+ hwaddr addr, unsigned len)
+{
+ PCIHostState *s = opaque;
+ uint32_t val;
+ if (!(s->config_reg & (1U << 31))) {
+ return 0xffffffff;
+ }
+ val = pci_data_read(s->bus, s->config_reg | (addr & 3), len);
+ PCI_DPRINTF("read addr " TARGET_FMT_plx " len %d val %x\n",
+ addr, len, val);
+ return val;
+}
+
+const MemoryRegionOps pci_host_conf_le_ops = {
+ .read = pci_host_config_read,
+ .write = pci_host_config_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+const MemoryRegionOps pci_host_conf_be_ops = {
+ .read = pci_host_config_read,
+ .write = pci_host_config_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+const MemoryRegionOps pci_host_data_le_ops = {
+ .read = pci_host_data_read,
+ .write = pci_host_data_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+const MemoryRegionOps pci_host_data_be_ops = {
+ .read = pci_host_data_read,
+ .write = pci_host_data_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static const TypeInfo pci_host_type_info = {
+ .name = TYPE_PCI_HOST_BRIDGE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .abstract = true,
+ .class_size = sizeof(PCIHostBridgeClass),
+ .instance_size = sizeof(PCIHostState),
+};
+
+static void pci_host_register_types(void)
+{
+ type_register_static(&pci_host_type_info);
+}
+
+type_init(pci_host_register_types)
diff --git a/hw/pci/pcie.c b/hw/pci/pcie.c
new file mode 100644
index 00000000..6e28985b
--- /dev/null
+++ b/hw/pci/pcie.c
@@ -0,0 +1,634 @@
+/*
+ * pcie.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pcie.h"
+#include "hw/pci/msix.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pcie_regs.h"
+#include "qemu/range.h"
+
+//#define DEBUG_PCIE
+#ifdef DEBUG_PCIE
+# define PCIE_DPRINTF(fmt, ...) \
+ fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
+#else
+# define PCIE_DPRINTF(fmt, ...) do {} while (0)
+#endif
+#define PCIE_DEV_PRINTF(dev, fmt, ...) \
+ PCIE_DPRINTF("%s:%x "fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
+
+
+/***************************************************************************
+ * pci express capability helper functions
+ */
+int pcie_cap_init(PCIDevice *dev, uint8_t offset, uint8_t type, uint8_t port)
+{
+ int pos;
+ uint8_t *exp_cap;
+
+ assert(pci_is_express(dev));
+
+ pos = pci_add_capability(dev, PCI_CAP_ID_EXP, offset,
+ PCI_EXP_VER2_SIZEOF);
+ if (pos < 0) {
+ return pos;
+ }
+ dev->exp.exp_cap = pos;
+ exp_cap = dev->config + pos;
+
+ /* capability register
+ interrupt message number defaults to 0 */
+ pci_set_word(exp_cap + PCI_EXP_FLAGS,
+ ((type << PCI_EXP_FLAGS_TYPE_SHIFT) & PCI_EXP_FLAGS_TYPE) |
+ PCI_EXP_FLAGS_VER2);
+
+ /* device capability register
+ * table 7-12:
+ * roll based error reporting bit must be set by all
+ * Functions conforming to the ECN, PCI Express Base
+ * Specification, Revision 1.1., or subsequent PCI Express Base
+ * Specification revisions.
+ */
+ pci_set_long(exp_cap + PCI_EXP_DEVCAP, PCI_EXP_DEVCAP_RBER);
+
+ pci_set_long(exp_cap + PCI_EXP_LNKCAP,
+ (port << PCI_EXP_LNKCAP_PN_SHIFT) |
+ PCI_EXP_LNKCAP_ASPMS_0S |
+ PCI_EXP_LNK_MLW_1 |
+ PCI_EXP_LNK_LS_25);
+
+ pci_set_word(exp_cap + PCI_EXP_LNKSTA,
+ PCI_EXP_LNK_MLW_1 | PCI_EXP_LNK_LS_25 |PCI_EXP_LNKSTA_DLLLA);
+
+ pci_set_long(exp_cap + PCI_EXP_DEVCAP2,
+ PCI_EXP_DEVCAP2_EFF | PCI_EXP_DEVCAP2_EETLPP);
+
+ pci_set_word(dev->wmask + pos + PCI_EXP_DEVCTL2, PCI_EXP_DEVCTL2_EETLPPB);
+ return pos;
+}
+
+int pcie_endpoint_cap_init(PCIDevice *dev, uint8_t offset)
+{
+ uint8_t type = PCI_EXP_TYPE_ENDPOINT;
+
+ /*
+ * Windows guests will report Code 10, device cannot start, if
+ * a regular Endpoint type is exposed on a root complex. These
+ * should instead be Root Complex Integrated Endpoints.
+ */
+ if (pci_bus_is_express(dev->bus) && pci_bus_is_root(dev->bus)) {
+ type = PCI_EXP_TYPE_RC_END;
+ }
+
+ return pcie_cap_init(dev, offset, type, 0);
+}
+
+void pcie_cap_exit(PCIDevice *dev)
+{
+ pci_del_capability(dev, PCI_CAP_ID_EXP, PCI_EXP_VER2_SIZEOF);
+}
+
+uint8_t pcie_cap_get_type(const PCIDevice *dev)
+{
+ uint32_t pos = dev->exp.exp_cap;
+ assert(pos > 0);
+ return (pci_get_word(dev->config + pos + PCI_EXP_FLAGS) &
+ PCI_EXP_FLAGS_TYPE) >> PCI_EXP_FLAGS_TYPE_SHIFT;
+}
+
+/* MSI/MSI-X */
+/* pci express interrupt message number */
+/* 7.8.2 PCI Express Capabilities Register: Interrupt Message Number */
+void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector)
+{
+ uint8_t *exp_cap = dev->config + dev->exp.exp_cap;
+ assert(vector < 32);
+ pci_word_test_and_clear_mask(exp_cap + PCI_EXP_FLAGS, PCI_EXP_FLAGS_IRQ);
+ pci_word_test_and_set_mask(exp_cap + PCI_EXP_FLAGS,
+ vector << PCI_EXP_FLAGS_IRQ_SHIFT);
+}
+
+uint8_t pcie_cap_flags_get_vector(PCIDevice *dev)
+{
+ return (pci_get_word(dev->config + dev->exp.exp_cap + PCI_EXP_FLAGS) &
+ PCI_EXP_FLAGS_IRQ) >> PCI_EXP_FLAGS_IRQ_SHIFT;
+}
+
+void pcie_cap_deverr_init(PCIDevice *dev)
+{
+ uint32_t pos = dev->exp.exp_cap;
+ pci_long_test_and_set_mask(dev->config + pos + PCI_EXP_DEVCAP,
+ PCI_EXP_DEVCAP_RBER);
+ pci_long_test_and_set_mask(dev->wmask + pos + PCI_EXP_DEVCTL,
+ PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE |
+ PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE);
+ pci_long_test_and_set_mask(dev->w1cmask + pos + PCI_EXP_DEVSTA,
+ PCI_EXP_DEVSTA_CED | PCI_EXP_DEVSTA_NFED |
+ PCI_EXP_DEVSTA_FED | PCI_EXP_DEVSTA_URD);
+}
+
+void pcie_cap_deverr_reset(PCIDevice *dev)
+{
+ uint8_t *devctl = dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL;
+ pci_long_test_and_clear_mask(devctl,
+ PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE |
+ PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE);
+}
+
+static void hotplug_event_update_event_status(PCIDevice *dev)
+{
+ uint32_t pos = dev->exp.exp_cap;
+ uint8_t *exp_cap = dev->config + pos;
+ uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL);
+ uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA);
+
+ dev->exp.hpev_notified = (sltctl & PCI_EXP_SLTCTL_HPIE) &&
+ (sltsta & sltctl & PCI_EXP_HP_EV_SUPPORTED);
+}
+
+static void hotplug_event_notify(PCIDevice *dev)
+{
+ bool prev = dev->exp.hpev_notified;
+
+ hotplug_event_update_event_status(dev);
+
+ if (prev == dev->exp.hpev_notified) {
+ return;
+ }
+
+ /* Note: the logic above does not take into account whether interrupts
+ * are masked. The result is that interrupt will be sent when it is
+ * subsequently unmasked. This appears to be legal: Section 6.7.3.4:
+ * The Port may optionally send an MSI when there are hot-plug events that
+ * occur while interrupt generation is disabled, and interrupt generation is
+ * subsequently enabled. */
+ if (msix_enabled(dev)) {
+ msix_notify(dev, pcie_cap_flags_get_vector(dev));
+ } else if (msi_enabled(dev)) {
+ msi_notify(dev, pcie_cap_flags_get_vector(dev));
+ } else {
+ pci_set_irq(dev, dev->exp.hpev_notified);
+ }
+}
+
+static void hotplug_event_clear(PCIDevice *dev)
+{
+ hotplug_event_update_event_status(dev);
+ if (!msix_enabled(dev) && !msi_enabled(dev) && !dev->exp.hpev_notified) {
+ pci_irq_deassert(dev);
+ }
+}
+
+/*
+ * A PCI Express Hot-Plug Event has occurred, so update slot status register
+ * and notify OS of the event if necessary.
+ *
+ * 6.7.3 PCI Express Hot-Plug Events
+ * 6.7.3.4 Software Notification of Hot-Plug Events
+ */
+static void pcie_cap_slot_event(PCIDevice *dev, PCIExpressHotPlugEvent event)
+{
+ /* Minor optimization: if nothing changed - no event is needed. */
+ if (pci_word_test_and_set_mask(dev->config + dev->exp.exp_cap +
+ PCI_EXP_SLTSTA, event)) {
+ return;
+ }
+ hotplug_event_notify(dev);
+}
+
+static void pcie_cap_slot_hotplug_common(PCIDevice *hotplug_dev,
+ DeviceState *dev,
+ uint8_t **exp_cap, Error **errp)
+{
+ *exp_cap = hotplug_dev->config + hotplug_dev->exp.exp_cap;
+ uint16_t sltsta = pci_get_word(*exp_cap + PCI_EXP_SLTSTA);
+
+ PCIE_DEV_PRINTF(PCI_DEVICE(dev), "hotplug state: 0x%x\n", sltsta);
+ if (sltsta & PCI_EXP_SLTSTA_EIS) {
+ /* the slot is electromechanically locked.
+ * This error is propagated up to qdev and then to HMP/QMP.
+ */
+ error_setg_errno(errp, EBUSY, "slot is electromechanically locked");
+ }
+}
+
+void pcie_cap_slot_hotplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ uint8_t *exp_cap;
+ PCIDevice *pci_dev = PCI_DEVICE(dev);
+
+ pcie_cap_slot_hotplug_common(PCI_DEVICE(hotplug_dev), dev, &exp_cap, errp);
+
+ /* Don't send event when device is enabled during qemu machine creation:
+ * it is present on boot, no hotplug event is necessary. We do send an
+ * event when the device is disabled later. */
+ if (!dev->hotplugged) {
+ pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_PDS);
+ return;
+ }
+
+ /* TODO: multifunction hot-plug.
+ * Right now, only a device of function = 0 is allowed to be
+ * hot plugged/unplugged.
+ */
+ assert(PCI_FUNC(pci_dev->devfn) == 0);
+
+ pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_PDS);
+ pcie_cap_slot_event(PCI_DEVICE(hotplug_dev),
+ PCI_EXP_HP_EV_PDC | PCI_EXP_HP_EV_ABP);
+}
+
+void pcie_cap_slot_hot_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ uint8_t *exp_cap;
+
+ pcie_cap_slot_hotplug_common(PCI_DEVICE(hotplug_dev), dev, &exp_cap, errp);
+
+ pcie_cap_slot_push_attention_button(PCI_DEVICE(hotplug_dev));
+}
+
+/* pci express slot for pci express root/downstream port
+ PCI express capability slot registers */
+void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot)
+{
+ uint32_t pos = dev->exp.exp_cap;
+
+ pci_word_test_and_set_mask(dev->config + pos + PCI_EXP_FLAGS,
+ PCI_EXP_FLAGS_SLOT);
+
+ pci_long_test_and_clear_mask(dev->config + pos + PCI_EXP_SLTCAP,
+ ~PCI_EXP_SLTCAP_PSN);
+ pci_long_test_and_set_mask(dev->config + pos + PCI_EXP_SLTCAP,
+ (slot << PCI_EXP_SLTCAP_PSN_SHIFT) |
+ PCI_EXP_SLTCAP_EIP |
+ PCI_EXP_SLTCAP_HPS |
+ PCI_EXP_SLTCAP_HPC |
+ PCI_EXP_SLTCAP_PIP |
+ PCI_EXP_SLTCAP_AIP |
+ PCI_EXP_SLTCAP_ABP);
+
+ if (dev->cap_present & QEMU_PCIE_SLTCAP_PCP) {
+ pci_long_test_and_set_mask(dev->config + pos + PCI_EXP_SLTCAP,
+ PCI_EXP_SLTCAP_PCP);
+ pci_word_test_and_clear_mask(dev->config + pos + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_PCC);
+ pci_word_test_and_set_mask(dev->wmask + pos + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_PCC);
+ }
+
+ pci_word_test_and_clear_mask(dev->config + pos + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_PIC |
+ PCI_EXP_SLTCTL_AIC);
+ pci_word_test_and_set_mask(dev->config + pos + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_PIC_OFF |
+ PCI_EXP_SLTCTL_AIC_OFF);
+ pci_word_test_and_set_mask(dev->wmask + pos + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_PIC |
+ PCI_EXP_SLTCTL_AIC |
+ PCI_EXP_SLTCTL_HPIE |
+ PCI_EXP_SLTCTL_CCIE |
+ PCI_EXP_SLTCTL_PDCE |
+ PCI_EXP_SLTCTL_ABPE);
+ /* Although reading PCI_EXP_SLTCTL_EIC returns always 0,
+ * make the bit writable here in order to detect 1b is written.
+ * pcie_cap_slot_write_config() test-and-clear the bit, so
+ * this bit always returns 0 to the guest.
+ */
+ pci_word_test_and_set_mask(dev->wmask + pos + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_EIC);
+
+ pci_word_test_and_set_mask(dev->w1cmask + pos + PCI_EXP_SLTSTA,
+ PCI_EXP_HP_EV_SUPPORTED);
+
+ dev->exp.hpev_notified = false;
+
+ qbus_set_hotplug_handler(BUS(pci_bridge_get_sec_bus(PCI_BRIDGE(dev))),
+ DEVICE(dev), NULL);
+}
+
+void pcie_cap_slot_reset(PCIDevice *dev)
+{
+ uint8_t *exp_cap = dev->config + dev->exp.exp_cap;
+ uint8_t port_type = pcie_cap_get_type(dev);
+
+ assert(port_type == PCI_EXP_TYPE_DOWNSTREAM ||
+ port_type == PCI_EXP_TYPE_ROOT_PORT);
+
+ PCIE_DEV_PRINTF(dev, "reset\n");
+
+ pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_EIC |
+ PCI_EXP_SLTCTL_PIC |
+ PCI_EXP_SLTCTL_AIC |
+ PCI_EXP_SLTCTL_HPIE |
+ PCI_EXP_SLTCTL_CCIE |
+ PCI_EXP_SLTCTL_PDCE |
+ PCI_EXP_SLTCTL_ABPE);
+ pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_AIC_OFF);
+
+ if (dev->cap_present & QEMU_PCIE_SLTCAP_PCP) {
+ /* Downstream ports enforce device number 0. */
+ bool populated = pci_bridge_get_sec_bus(PCI_BRIDGE(dev))->devices[0];
+ uint16_t pic;
+
+ if (populated) {
+ pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_PCC);
+ } else {
+ pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_PCC);
+ }
+
+ pic = populated ? PCI_EXP_SLTCTL_PIC_ON : PCI_EXP_SLTCTL_PIC_OFF;
+ pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTCTL, pic);
+ }
+
+ pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_EIS |/* on reset,
+ the lock is released */
+ PCI_EXP_SLTSTA_CC |
+ PCI_EXP_SLTSTA_PDC |
+ PCI_EXP_SLTSTA_ABP);
+
+ hotplug_event_update_event_status(dev);
+}
+
+static void pcie_unplug_device(PCIBus *bus, PCIDevice *dev, void *opaque)
+{
+ object_unparent(OBJECT(dev));
+}
+
+void pcie_cap_slot_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len)
+{
+ uint32_t pos = dev->exp.exp_cap;
+ uint8_t *exp_cap = dev->config + pos;
+ uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA);
+
+ if (ranges_overlap(addr, len, pos + PCI_EXP_SLTSTA, 2)) {
+ hotplug_event_clear(dev);
+ }
+
+ if (!ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) {
+ return;
+ }
+
+ if (pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_EIC)) {
+ sltsta ^= PCI_EXP_SLTSTA_EIS; /* toggle PCI_EXP_SLTSTA_EIS bit */
+ pci_set_word(exp_cap + PCI_EXP_SLTSTA, sltsta);
+ PCIE_DEV_PRINTF(dev, "PCI_EXP_SLTCTL_EIC: "
+ "sltsta -> 0x%02"PRIx16"\n",
+ sltsta);
+ }
+
+ /*
+ * If the slot is polulated, power indicator is off and power
+ * controller is off, it is safe to detach the devices.
+ */
+ if ((sltsta & PCI_EXP_SLTSTA_PDS) && (val & PCI_EXP_SLTCTL_PCC) &&
+ ((val & PCI_EXP_SLTCTL_PIC_OFF) == PCI_EXP_SLTCTL_PIC_OFF)) {
+ PCIBus *sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(dev));
+ pci_for_each_device(sec_bus, pci_bus_num(sec_bus),
+ pcie_unplug_device, NULL);
+
+ pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_PDS);
+ pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_PDC);
+ }
+
+ hotplug_event_notify(dev);
+
+ /*
+ * 6.7.3.2 Command Completed Events
+ *
+ * Software issues a command to a hot-plug capable Downstream Port by
+ * issuing a write transaction that targets any portion of the Port’s Slot
+ * Control register. A single write to the Slot Control register is
+ * considered to be a single command, even if the write affects more than
+ * one field in the Slot Control register. In response to this transaction,
+ * the Port must carry out the requested actions and then set the
+ * associated status field for the command completed event. */
+
+ /* Real hardware might take a while to complete requested command because
+ * physical movement would be involved like locking the electromechanical
+ * lock. However in our case, command is completed instantaneously above,
+ * so send a command completion event right now.
+ */
+ pcie_cap_slot_event(dev, PCI_EXP_HP_EV_CCI);
+}
+
+int pcie_cap_slot_post_load(void *opaque, int version_id)
+{
+ PCIDevice *dev = opaque;
+ hotplug_event_update_event_status(dev);
+ return 0;
+}
+
+void pcie_cap_slot_push_attention_button(PCIDevice *dev)
+{
+ pcie_cap_slot_event(dev, PCI_EXP_HP_EV_ABP);
+}
+
+/* root control/capabilities/status. PME isn't emulated for now */
+void pcie_cap_root_init(PCIDevice *dev)
+{
+ pci_set_word(dev->wmask + dev->exp.exp_cap + PCI_EXP_RTCTL,
+ PCI_EXP_RTCTL_SECEE | PCI_EXP_RTCTL_SENFEE |
+ PCI_EXP_RTCTL_SEFEE);
+}
+
+void pcie_cap_root_reset(PCIDevice *dev)
+{
+ pci_set_word(dev->config + dev->exp.exp_cap + PCI_EXP_RTCTL, 0);
+}
+
+/* function level reset(FLR) */
+void pcie_cap_flr_init(PCIDevice *dev)
+{
+ pci_long_test_and_set_mask(dev->config + dev->exp.exp_cap + PCI_EXP_DEVCAP,
+ PCI_EXP_DEVCAP_FLR);
+
+ /* Although reading BCR_FLR returns always 0,
+ * the bit is made writable here in order to detect the 1b is written
+ * pcie_cap_flr_write_config() test-and-clear the bit, so
+ * this bit always returns 0 to the guest.
+ */
+ pci_word_test_and_set_mask(dev->wmask + dev->exp.exp_cap + PCI_EXP_DEVCTL,
+ PCI_EXP_DEVCTL_BCR_FLR);
+}
+
+void pcie_cap_flr_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len)
+{
+ uint8_t *devctl = dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL;
+ if (pci_get_word(devctl) & PCI_EXP_DEVCTL_BCR_FLR) {
+ /* Clear PCI_EXP_DEVCTL_BCR_FLR after invoking the reset handler
+ so the handler can detect FLR by looking at this bit. */
+ pci_device_reset(dev);
+ pci_word_test_and_clear_mask(devctl, PCI_EXP_DEVCTL_BCR_FLR);
+ }
+}
+
+/* Alternative Routing-ID Interpretation (ARI)
+ * forwarding support for root and downstream ports
+ */
+void pcie_cap_arifwd_init(PCIDevice *dev)
+{
+ uint32_t pos = dev->exp.exp_cap;
+ pci_long_test_and_set_mask(dev->config + pos + PCI_EXP_DEVCAP2,
+ PCI_EXP_DEVCAP2_ARI);
+ pci_long_test_and_set_mask(dev->wmask + pos + PCI_EXP_DEVCTL2,
+ PCI_EXP_DEVCTL2_ARI);
+}
+
+void pcie_cap_arifwd_reset(PCIDevice *dev)
+{
+ uint8_t *devctl2 = dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL2;
+ pci_long_test_and_clear_mask(devctl2, PCI_EXP_DEVCTL2_ARI);
+}
+
+bool pcie_cap_is_arifwd_enabled(const PCIDevice *dev)
+{
+ if (!pci_is_express(dev)) {
+ return false;
+ }
+ if (!dev->exp.exp_cap) {
+ return false;
+ }
+
+ return pci_get_long(dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL2) &
+ PCI_EXP_DEVCTL2_ARI;
+}
+
+/**************************************************************************
+ * pci express extended capability list management functions
+ * uint16_t ext_cap_id (16 bit)
+ * uint8_t cap_ver (4 bit)
+ * uint16_t cap_offset (12 bit)
+ * uint16_t ext_cap_size
+ */
+
+static uint16_t pcie_find_capability_list(PCIDevice *dev, uint16_t cap_id,
+ uint16_t *prev_p)
+{
+ uint16_t prev = 0;
+ uint16_t next;
+ uint32_t header = pci_get_long(dev->config + PCI_CONFIG_SPACE_SIZE);
+
+ if (!header) {
+ /* no extended capability */
+ next = 0;
+ goto out;
+ }
+ for (next = PCI_CONFIG_SPACE_SIZE; next;
+ prev = next, next = PCI_EXT_CAP_NEXT(header)) {
+
+ assert(next >= PCI_CONFIG_SPACE_SIZE);
+ assert(next <= PCIE_CONFIG_SPACE_SIZE - 8);
+
+ header = pci_get_long(dev->config + next);
+ if (PCI_EXT_CAP_ID(header) == cap_id) {
+ break;
+ }
+ }
+
+out:
+ if (prev_p) {
+ *prev_p = prev;
+ }
+ return next;
+}
+
+uint16_t pcie_find_capability(PCIDevice *dev, uint16_t cap_id)
+{
+ return pcie_find_capability_list(dev, cap_id, NULL);
+}
+
+static void pcie_ext_cap_set_next(PCIDevice *dev, uint16_t pos, uint16_t next)
+{
+ uint32_t header = pci_get_long(dev->config + pos);
+ assert(!(next & (PCI_EXT_CAP_ALIGN - 1)));
+ header = (header & ~PCI_EXT_CAP_NEXT_MASK) |
+ ((next << PCI_EXT_CAP_NEXT_SHIFT) & PCI_EXT_CAP_NEXT_MASK);
+ pci_set_long(dev->config + pos, header);
+}
+
+/*
+ * caller must supply valid (offset, size) * such that the range shouldn't
+ * overlap with other capability or other registers.
+ * This function doesn't check it.
+ */
+void pcie_add_capability(PCIDevice *dev,
+ uint16_t cap_id, uint8_t cap_ver,
+ uint16_t offset, uint16_t size)
+{
+ uint32_t header;
+ uint16_t next;
+
+ assert(offset >= PCI_CONFIG_SPACE_SIZE);
+ assert(offset < offset + size);
+ assert(offset + size < PCIE_CONFIG_SPACE_SIZE);
+ assert(size >= 8);
+ assert(pci_is_express(dev));
+
+ if (offset == PCI_CONFIG_SPACE_SIZE) {
+ header = pci_get_long(dev->config + offset);
+ next = PCI_EXT_CAP_NEXT(header);
+ } else {
+ uint16_t prev;
+
+ /* 0 is reserved cap id. use internally to find the last capability
+ in the linked list */
+ next = pcie_find_capability_list(dev, 0, &prev);
+
+ assert(prev >= PCI_CONFIG_SPACE_SIZE);
+ assert(next == 0);
+ pcie_ext_cap_set_next(dev, prev, offset);
+ }
+ pci_set_long(dev->config + offset, PCI_EXT_CAP(cap_id, cap_ver, next));
+
+ /* Make capability read-only by default */
+ memset(dev->wmask + offset, 0, size);
+ memset(dev->w1cmask + offset, 0, size);
+ /* Check capability by default */
+ memset(dev->cmask + offset, 0xFF, size);
+}
+
+/**************************************************************************
+ * pci express extended capability helper functions
+ */
+
+/* ARI */
+void pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn)
+{
+ pcie_add_capability(dev, PCI_EXT_CAP_ID_ARI, PCI_ARI_VER,
+ offset, PCI_ARI_SIZEOF);
+ pci_set_long(dev->config + offset + PCI_ARI_CAP, (nextfn & 0xff) << 8);
+}
diff --git a/hw/pci/pcie_aer.c b/hw/pci/pcie_aer.c
new file mode 100644
index 00000000..f1847ac2
--- /dev/null
+++ b/hw/pci/pcie_aer.c
@@ -0,0 +1,1042 @@
+/*
+ * pcie_aer.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysemu/sysemu.h"
+#include "qapi/qmp/types.h"
+#include "monitor/monitor.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pcie.h"
+#include "hw/pci/msix.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pcie_regs.h"
+
+//#define DEBUG_PCIE
+#ifdef DEBUG_PCIE
+# define PCIE_DPRINTF(fmt, ...) \
+ fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
+#else
+# define PCIE_DPRINTF(fmt, ...) do {} while (0)
+#endif
+#define PCIE_DEV_PRINTF(dev, fmt, ...) \
+ PCIE_DPRINTF("%s:%x "fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
+
+#define PCI_ERR_SRC_COR_OFFS 0
+#define PCI_ERR_SRC_UNCOR_OFFS 2
+
+/* From 6.2.7 Error Listing and Rules. Table 6-2, 6-3 and 6-4 */
+static uint32_t pcie_aer_uncor_default_severity(uint32_t status)
+{
+ switch (status) {
+ case PCI_ERR_UNC_INTN:
+ case PCI_ERR_UNC_DLP:
+ case PCI_ERR_UNC_SDN:
+ case PCI_ERR_UNC_RX_OVER:
+ case PCI_ERR_UNC_FCP:
+ case PCI_ERR_UNC_MALF_TLP:
+ return PCI_ERR_ROOT_CMD_FATAL_EN;
+ case PCI_ERR_UNC_POISON_TLP:
+ case PCI_ERR_UNC_ECRC:
+ case PCI_ERR_UNC_UNSUP:
+ case PCI_ERR_UNC_COMP_TIME:
+ case PCI_ERR_UNC_COMP_ABORT:
+ case PCI_ERR_UNC_UNX_COMP:
+ case PCI_ERR_UNC_ACSV:
+ case PCI_ERR_UNC_MCBTLP:
+ case PCI_ERR_UNC_ATOP_EBLOCKED:
+ case PCI_ERR_UNC_TLP_PRF_BLOCKED:
+ return PCI_ERR_ROOT_CMD_NONFATAL_EN;
+ default:
+ abort();
+ break;
+ }
+ return PCI_ERR_ROOT_CMD_FATAL_EN;
+}
+
+static int aer_log_add_err(PCIEAERLog *aer_log, const PCIEAERErr *err)
+{
+ if (aer_log->log_num == aer_log->log_max) {
+ return -1;
+ }
+ memcpy(&aer_log->log[aer_log->log_num], err, sizeof *err);
+ aer_log->log_num++;
+ return 0;
+}
+
+static void aer_log_del_err(PCIEAERLog *aer_log, PCIEAERErr *err)
+{
+ assert(aer_log->log_num);
+ *err = aer_log->log[0];
+ aer_log->log_num--;
+ memmove(&aer_log->log[0], &aer_log->log[1],
+ aer_log->log_num * sizeof *err);
+}
+
+static void aer_log_clear_all_err(PCIEAERLog *aer_log)
+{
+ aer_log->log_num = 0;
+}
+
+int pcie_aer_init(PCIDevice *dev, uint16_t offset)
+{
+ PCIExpressDevice *exp;
+
+ pcie_add_capability(dev, PCI_EXT_CAP_ID_ERR, PCI_ERR_VER,
+ offset, PCI_ERR_SIZEOF);
+ exp = &dev->exp;
+ exp->aer_cap = offset;
+
+ /* log_max is property */
+ if (dev->exp.aer_log.log_max == PCIE_AER_LOG_MAX_UNSET) {
+ dev->exp.aer_log.log_max = PCIE_AER_LOG_MAX_DEFAULT;
+ }
+ /* clip down the value to avoid unreasobale memory usage */
+ if (dev->exp.aer_log.log_max > PCIE_AER_LOG_MAX_LIMIT) {
+ return -EINVAL;
+ }
+ dev->exp.aer_log.log = g_malloc0(sizeof dev->exp.aer_log.log[0] *
+ dev->exp.aer_log.log_max);
+
+ pci_set_long(dev->w1cmask + offset + PCI_ERR_UNCOR_STATUS,
+ PCI_ERR_UNC_SUPPORTED);
+
+ pci_set_long(dev->config + offset + PCI_ERR_UNCOR_SEVER,
+ PCI_ERR_UNC_SEVERITY_DEFAULT);
+ pci_set_long(dev->wmask + offset + PCI_ERR_UNCOR_SEVER,
+ PCI_ERR_UNC_SUPPORTED);
+
+ pci_long_test_and_set_mask(dev->w1cmask + offset + PCI_ERR_COR_STATUS,
+ PCI_ERR_COR_SUPPORTED);
+
+ pci_set_long(dev->config + offset + PCI_ERR_COR_MASK,
+ PCI_ERR_COR_MASK_DEFAULT);
+ pci_set_long(dev->wmask + offset + PCI_ERR_COR_MASK,
+ PCI_ERR_COR_SUPPORTED);
+
+ /* capabilities and control. multiple header logging is supported */
+ if (dev->exp.aer_log.log_max > 0) {
+ pci_set_long(dev->config + offset + PCI_ERR_CAP,
+ PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC |
+ PCI_ERR_CAP_MHRC);
+ pci_set_long(dev->wmask + offset + PCI_ERR_CAP,
+ PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE |
+ PCI_ERR_CAP_MHRE);
+ } else {
+ pci_set_long(dev->config + offset + PCI_ERR_CAP,
+ PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC);
+ pci_set_long(dev->wmask + offset + PCI_ERR_CAP,
+ PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE);
+ }
+
+ switch (pcie_cap_get_type(dev)) {
+ case PCI_EXP_TYPE_ROOT_PORT:
+ /* this case will be set by pcie_aer_root_init() */
+ /* fallthrough */
+ case PCI_EXP_TYPE_DOWNSTREAM:
+ case PCI_EXP_TYPE_UPSTREAM:
+ pci_word_test_and_set_mask(dev->wmask + PCI_BRIDGE_CONTROL,
+ PCI_BRIDGE_CTL_SERR);
+ pci_long_test_and_set_mask(dev->w1cmask + PCI_STATUS,
+ PCI_SEC_STATUS_RCV_SYSTEM_ERROR);
+ break;
+ default:
+ /* nothing */
+ break;
+ }
+ return 0;
+}
+
+void pcie_aer_exit(PCIDevice *dev)
+{
+ g_free(dev->exp.aer_log.log);
+}
+
+static void pcie_aer_update_uncor_status(PCIDevice *dev)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+ PCIEAERLog *aer_log = &dev->exp.aer_log;
+
+ uint16_t i;
+ for (i = 0; i < aer_log->log_num; i++) {
+ pci_long_test_and_set_mask(aer_cap + PCI_ERR_UNCOR_STATUS,
+ dev->exp.aer_log.log[i].status);
+ }
+}
+
+/*
+ * return value:
+ * true: error message needs to be sent up
+ * false: error message is masked
+ *
+ * 6.2.6 Error Message Control
+ * Figure 6-3
+ * all pci express devices part
+ */
+static bool
+pcie_aer_msg_alldev(PCIDevice *dev, const PCIEAERMsg *msg)
+{
+ if (!(pcie_aer_msg_is_uncor(msg) &&
+ (pci_get_word(dev->config + PCI_COMMAND) & PCI_COMMAND_SERR))) {
+ return false;
+ }
+
+ /* Signaled System Error
+ *
+ * 7.5.1.1 Command register
+ * Bit 8 SERR# Enable
+ *
+ * When Set, this bit enables reporting of Non-fatal and Fatal
+ * errors detected by the Function to the Root Complex. Note that
+ * errors are reported if enabled either through this bit or through
+ * the PCI Express specific bits in the Device Control register (see
+ * Section 7.8.4).
+ */
+ pci_word_test_and_set_mask(dev->config + PCI_STATUS,
+ PCI_STATUS_SIG_SYSTEM_ERROR);
+
+ if (!(msg->severity &
+ pci_get_word(dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL))) {
+ return false;
+ }
+
+ /* send up error message */
+ return true;
+}
+
+/*
+ * return value:
+ * true: error message is sent up
+ * false: error message is masked
+ *
+ * 6.2.6 Error Message Control
+ * Figure 6-3
+ * virtual pci bridge part
+ */
+static bool pcie_aer_msg_vbridge(PCIDevice *dev, const PCIEAERMsg *msg)
+{
+ uint16_t bridge_control = pci_get_word(dev->config + PCI_BRIDGE_CONTROL);
+
+ if (pcie_aer_msg_is_uncor(msg)) {
+ /* Received System Error */
+ pci_word_test_and_set_mask(dev->config + PCI_SEC_STATUS,
+ PCI_SEC_STATUS_RCV_SYSTEM_ERROR);
+ }
+
+ if (!(bridge_control & PCI_BRIDGE_CTL_SERR)) {
+ return false;
+ }
+ return true;
+}
+
+void pcie_aer_root_set_vector(PCIDevice *dev, unsigned int vector)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+ assert(vector < PCI_ERR_ROOT_IRQ_MAX);
+ pci_long_test_and_clear_mask(aer_cap + PCI_ERR_ROOT_STATUS,
+ PCI_ERR_ROOT_IRQ);
+ pci_long_test_and_set_mask(aer_cap + PCI_ERR_ROOT_STATUS,
+ vector << PCI_ERR_ROOT_IRQ_SHIFT);
+}
+
+static unsigned int pcie_aer_root_get_vector(PCIDevice *dev)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+ uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
+ return (root_status & PCI_ERR_ROOT_IRQ) >> PCI_ERR_ROOT_IRQ_SHIFT;
+}
+
+/* Given a status register, get corresponding bits in the command register */
+static uint32_t pcie_aer_status_to_cmd(uint32_t status)
+{
+ uint32_t cmd = 0;
+ if (status & PCI_ERR_ROOT_COR_RCV) {
+ cmd |= PCI_ERR_ROOT_CMD_COR_EN;
+ }
+ if (status & PCI_ERR_ROOT_NONFATAL_RCV) {
+ cmd |= PCI_ERR_ROOT_CMD_NONFATAL_EN;
+ }
+ if (status & PCI_ERR_ROOT_FATAL_RCV) {
+ cmd |= PCI_ERR_ROOT_CMD_FATAL_EN;
+ }
+ return cmd;
+}
+
+static void pcie_aer_root_notify(PCIDevice *dev)
+{
+ if (msix_enabled(dev)) {
+ msix_notify(dev, pcie_aer_root_get_vector(dev));
+ } else if (msi_enabled(dev)) {
+ msi_notify(dev, pcie_aer_root_get_vector(dev));
+ } else {
+ pci_irq_assert(dev);
+ }
+}
+
+/*
+ * 6.2.6 Error Message Control
+ * Figure 6-3
+ * root port part
+ */
+static void pcie_aer_msg_root_port(PCIDevice *dev, const PCIEAERMsg *msg)
+{
+ uint16_t cmd;
+ uint8_t *aer_cap;
+ uint32_t root_cmd;
+ uint32_t root_status, prev_status;
+
+ cmd = pci_get_word(dev->config + PCI_COMMAND);
+ aer_cap = dev->config + dev->exp.aer_cap;
+ root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
+ prev_status = root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
+
+ if (cmd & PCI_COMMAND_SERR) {
+ /* System Error.
+ *
+ * The way to report System Error is platform specific and
+ * it isn't implemented in qemu right now.
+ * So just discard the error for now.
+ * OS which cares of aer would receive errors via
+ * native aer mechanims, so this wouldn't matter.
+ */
+ }
+
+ /* Errro Message Received: Root Error Status register */
+ switch (msg->severity) {
+ case PCI_ERR_ROOT_CMD_COR_EN:
+ if (root_status & PCI_ERR_ROOT_COR_RCV) {
+ root_status |= PCI_ERR_ROOT_MULTI_COR_RCV;
+ } else {
+ pci_set_word(aer_cap + PCI_ERR_ROOT_ERR_SRC + PCI_ERR_SRC_COR_OFFS,
+ msg->source_id);
+ }
+ root_status |= PCI_ERR_ROOT_COR_RCV;
+ break;
+ case PCI_ERR_ROOT_CMD_NONFATAL_EN:
+ root_status |= PCI_ERR_ROOT_NONFATAL_RCV;
+ break;
+ case PCI_ERR_ROOT_CMD_FATAL_EN:
+ if (!(root_status & PCI_ERR_ROOT_UNCOR_RCV)) {
+ root_status |= PCI_ERR_ROOT_FIRST_FATAL;
+ }
+ root_status |= PCI_ERR_ROOT_FATAL_RCV;
+ break;
+ default:
+ abort();
+ break;
+ }
+ if (pcie_aer_msg_is_uncor(msg)) {
+ if (root_status & PCI_ERR_ROOT_UNCOR_RCV) {
+ root_status |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
+ } else {
+ pci_set_word(aer_cap + PCI_ERR_ROOT_ERR_SRC +
+ PCI_ERR_SRC_UNCOR_OFFS, msg->source_id);
+ }
+ root_status |= PCI_ERR_ROOT_UNCOR_RCV;
+ }
+ pci_set_long(aer_cap + PCI_ERR_ROOT_STATUS, root_status);
+
+ /* 6.2.4.1.2 Interrupt Generation */
+ /* All the above did was set some bits in the status register.
+ * Specifically these that match message severity.
+ * The below code relies on this fact. */
+ if (!(root_cmd & msg->severity) ||
+ (pcie_aer_status_to_cmd(prev_status) & root_cmd)) {
+ /* Condition is not being set or was already true so nothing to do. */
+ return;
+ }
+
+ pcie_aer_root_notify(dev);
+}
+
+/*
+ * 6.2.6 Error Message Control Figure 6-3
+ *
+ * Walk up the bus tree from the device, propagate the error message.
+ */
+static void pcie_aer_msg(PCIDevice *dev, const PCIEAERMsg *msg)
+{
+ uint8_t type;
+
+ while (dev) {
+ if (!pci_is_express(dev)) {
+ /* just ignore it */
+ /* TODO: Shouldn't we set PCI_STATUS_SIG_SYSTEM_ERROR?
+ * Consider e.g. a PCI bridge above a PCI Express device. */
+ return;
+ }
+
+ type = pcie_cap_get_type(dev);
+ if ((type == PCI_EXP_TYPE_ROOT_PORT ||
+ type == PCI_EXP_TYPE_UPSTREAM ||
+ type == PCI_EXP_TYPE_DOWNSTREAM) &&
+ !pcie_aer_msg_vbridge(dev, msg)) {
+ return;
+ }
+ if (!pcie_aer_msg_alldev(dev, msg)) {
+ return;
+ }
+ if (type == PCI_EXP_TYPE_ROOT_PORT) {
+ pcie_aer_msg_root_port(dev, msg);
+ /* Root port can notify system itself,
+ or send the error message to root complex event collector. */
+ /*
+ * if root port is associated with an event collector,
+ * return the root complex event collector here.
+ * For now root complex event collector isn't supported.
+ */
+ return;
+ }
+ dev = pci_bridge_get_device(dev->bus);
+ }
+}
+
+static void pcie_aer_update_log(PCIDevice *dev, const PCIEAERErr *err)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+ uint8_t first_bit = ctz32(err->status);
+ uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
+ int i;
+
+ assert(err->status);
+ assert(!(err->status & (err->status - 1)));
+
+ errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
+ errcap |= PCI_ERR_CAP_FEP(first_bit);
+
+ if (err->flags & PCIE_AER_ERR_HEADER_VALID) {
+ for (i = 0; i < ARRAY_SIZE(err->header); ++i) {
+ /* 7.10.8 Header Log Register */
+ uint8_t *header_log =
+ aer_cap + PCI_ERR_HEADER_LOG + i * sizeof err->header[0];
+ stl_be_p(header_log, err->header[i]);
+ }
+ } else {
+ assert(!(err->flags & PCIE_AER_ERR_TLP_PREFIX_PRESENT));
+ memset(aer_cap + PCI_ERR_HEADER_LOG, 0, PCI_ERR_HEADER_LOG_SIZE);
+ }
+
+ if ((err->flags & PCIE_AER_ERR_TLP_PREFIX_PRESENT) &&
+ (pci_get_long(dev->config + dev->exp.exp_cap + PCI_EXP_DEVCAP2) &
+ PCI_EXP_DEVCAP2_EETLPP)) {
+ for (i = 0; i < ARRAY_SIZE(err->prefix); ++i) {
+ /* 7.10.12 tlp prefix log register */
+ uint8_t *prefix_log =
+ aer_cap + PCI_ERR_TLP_PREFIX_LOG + i * sizeof err->prefix[0];
+ stl_be_p(prefix_log, err->prefix[i]);
+ }
+ errcap |= PCI_ERR_CAP_TLP;
+ } else {
+ memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0,
+ PCI_ERR_TLP_PREFIX_LOG_SIZE);
+ }
+ pci_set_long(aer_cap + PCI_ERR_CAP, errcap);
+}
+
+static void pcie_aer_clear_log(PCIDevice *dev)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+
+ pci_long_test_and_clear_mask(aer_cap + PCI_ERR_CAP,
+ PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
+
+ memset(aer_cap + PCI_ERR_HEADER_LOG, 0, PCI_ERR_HEADER_LOG_SIZE);
+ memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0, PCI_ERR_TLP_PREFIX_LOG_SIZE);
+}
+
+static void pcie_aer_clear_error(PCIDevice *dev)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+ uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
+ PCIEAERLog *aer_log = &dev->exp.aer_log;
+ PCIEAERErr err;
+
+ if (!(errcap & PCI_ERR_CAP_MHRE) || !aer_log->log_num) {
+ pcie_aer_clear_log(dev);
+ return;
+ }
+
+ /*
+ * If more errors are queued, set corresponding bits in uncorrectable
+ * error status.
+ * We emulate uncorrectable error status register as W1CS.
+ * So set bit in uncorrectable error status here again for multiple
+ * error recording support.
+ *
+ * 6.2.4.2 Multiple Error Handling(Advanced Error Reporting Capability)
+ */
+ pcie_aer_update_uncor_status(dev);
+
+ aer_log_del_err(aer_log, &err);
+ pcie_aer_update_log(dev, &err);
+}
+
+static int pcie_aer_record_error(PCIDevice *dev,
+ const PCIEAERErr *err)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+ uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
+ int fep = PCI_ERR_CAP_FEP(errcap);
+
+ assert(err->status);
+ assert(!(err->status & (err->status - 1)));
+
+ if (errcap & PCI_ERR_CAP_MHRE &&
+ (pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & (1U << fep))) {
+ /* Not first error. queue error */
+ if (aer_log_add_err(&dev->exp.aer_log, err) < 0) {
+ /* overflow */
+ return -1;
+ }
+ return 0;
+ }
+
+ pcie_aer_update_log(dev, err);
+ return 0;
+}
+
+typedef struct PCIEAERInject {
+ PCIDevice *dev;
+ uint8_t *aer_cap;
+ const PCIEAERErr *err;
+ uint16_t devctl;
+ uint16_t devsta;
+ uint32_t error_status;
+ bool unsupported_request;
+ bool log_overflow;
+ PCIEAERMsg msg;
+} PCIEAERInject;
+
+static bool pcie_aer_inject_cor_error(PCIEAERInject *inj,
+ uint32_t uncor_status,
+ bool is_advisory_nonfatal)
+{
+ PCIDevice *dev = inj->dev;
+
+ inj->devsta |= PCI_EXP_DEVSTA_CED;
+ if (inj->unsupported_request) {
+ inj->devsta |= PCI_EXP_DEVSTA_URD;
+ }
+ pci_set_word(dev->config + dev->exp.exp_cap + PCI_EXP_DEVSTA, inj->devsta);
+
+ if (inj->aer_cap) {
+ uint32_t mask;
+ pci_long_test_and_set_mask(inj->aer_cap + PCI_ERR_COR_STATUS,
+ inj->error_status);
+ mask = pci_get_long(inj->aer_cap + PCI_ERR_COR_MASK);
+ if (mask & inj->error_status) {
+ return false;
+ }
+ if (is_advisory_nonfatal) {
+ uint32_t uncor_mask =
+ pci_get_long(inj->aer_cap + PCI_ERR_UNCOR_MASK);
+ if (!(uncor_mask & uncor_status)) {
+ inj->log_overflow = !!pcie_aer_record_error(dev, inj->err);
+ }
+ pci_long_test_and_set_mask(inj->aer_cap + PCI_ERR_UNCOR_STATUS,
+ uncor_status);
+ }
+ }
+
+ if (inj->unsupported_request && !(inj->devctl & PCI_EXP_DEVCTL_URRE)) {
+ return false;
+ }
+ if (!(inj->devctl & PCI_EXP_DEVCTL_CERE)) {
+ return false;
+ }
+
+ inj->msg.severity = PCI_ERR_ROOT_CMD_COR_EN;
+ return true;
+}
+
+static bool pcie_aer_inject_uncor_error(PCIEAERInject *inj, bool is_fatal)
+{
+ PCIDevice *dev = inj->dev;
+ uint16_t cmd;
+
+ if (is_fatal) {
+ inj->devsta |= PCI_EXP_DEVSTA_FED;
+ } else {
+ inj->devsta |= PCI_EXP_DEVSTA_NFED;
+ }
+ if (inj->unsupported_request) {
+ inj->devsta |= PCI_EXP_DEVSTA_URD;
+ }
+ pci_set_long(dev->config + dev->exp.exp_cap + PCI_EXP_DEVSTA, inj->devsta);
+
+ if (inj->aer_cap) {
+ uint32_t mask = pci_get_long(inj->aer_cap + PCI_ERR_UNCOR_MASK);
+ if (mask & inj->error_status) {
+ pci_long_test_and_set_mask(inj->aer_cap + PCI_ERR_UNCOR_STATUS,
+ inj->error_status);
+ return false;
+ }
+
+ inj->log_overflow = !!pcie_aer_record_error(dev, inj->err);
+ pci_long_test_and_set_mask(inj->aer_cap + PCI_ERR_UNCOR_STATUS,
+ inj->error_status);
+ }
+
+ cmd = pci_get_word(dev->config + PCI_COMMAND);
+ if (inj->unsupported_request &&
+ !(inj->devctl & PCI_EXP_DEVCTL_URRE) && !(cmd & PCI_COMMAND_SERR)) {
+ return false;
+ }
+ if (is_fatal) {
+ if (!((cmd & PCI_COMMAND_SERR) ||
+ (inj->devctl & PCI_EXP_DEVCTL_FERE))) {
+ return false;
+ }
+ inj->msg.severity = PCI_ERR_ROOT_CMD_FATAL_EN;
+ } else {
+ if (!((cmd & PCI_COMMAND_SERR) ||
+ (inj->devctl & PCI_EXP_DEVCTL_NFERE))) {
+ return false;
+ }
+ inj->msg.severity = PCI_ERR_ROOT_CMD_NONFATAL_EN;
+ }
+ return true;
+}
+
+/*
+ * non-Function specific error must be recorded in all functions.
+ * It is the responsibility of the caller of this function.
+ * It is also caller's responsibility to determine which function should
+ * report the error.
+ *
+ * 6.2.4 Error Logging
+ * 6.2.5 Sequence of Device Error Signaling and Logging Operations
+ * Figure 6-2: Flowchart Showing Sequence of Device Error Signaling and Logging
+ * Operations
+ */
+int pcie_aer_inject_error(PCIDevice *dev, const PCIEAERErr *err)
+{
+ uint8_t *aer_cap = NULL;
+ uint16_t devctl = 0;
+ uint16_t devsta = 0;
+ uint32_t error_status = err->status;
+ PCIEAERInject inj;
+
+ if (!pci_is_express(dev)) {
+ return -ENOSYS;
+ }
+
+ if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
+ error_status &= PCI_ERR_COR_SUPPORTED;
+ } else {
+ error_status &= PCI_ERR_UNC_SUPPORTED;
+ }
+
+ /* invalid status bit. one and only one bit must be set */
+ if (!error_status || (error_status & (error_status - 1))) {
+ return -EINVAL;
+ }
+
+ if (dev->exp.aer_cap) {
+ uint8_t *exp_cap = dev->config + dev->exp.exp_cap;
+ aer_cap = dev->config + dev->exp.aer_cap;
+ devctl = pci_get_long(exp_cap + PCI_EXP_DEVCTL);
+ devsta = pci_get_long(exp_cap + PCI_EXP_DEVSTA);
+ }
+
+ inj.dev = dev;
+ inj.aer_cap = aer_cap;
+ inj.err = err;
+ inj.devctl = devctl;
+ inj.devsta = devsta;
+ inj.error_status = error_status;
+ inj.unsupported_request = !(err->flags & PCIE_AER_ERR_IS_CORRECTABLE) &&
+ err->status == PCI_ERR_UNC_UNSUP;
+ inj.log_overflow = false;
+
+ if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
+ if (!pcie_aer_inject_cor_error(&inj, 0, false)) {
+ return 0;
+ }
+ } else {
+ bool is_fatal =
+ pcie_aer_uncor_default_severity(error_status) ==
+ PCI_ERR_ROOT_CMD_FATAL_EN;
+ if (aer_cap) {
+ is_fatal =
+ error_status & pci_get_long(aer_cap + PCI_ERR_UNCOR_SEVER);
+ }
+ if (!is_fatal && (err->flags & PCIE_AER_ERR_MAYBE_ADVISORY)) {
+ inj.error_status = PCI_ERR_COR_ADV_NONFATAL;
+ if (!pcie_aer_inject_cor_error(&inj, error_status, true)) {
+ return 0;
+ }
+ } else {
+ if (!pcie_aer_inject_uncor_error(&inj, is_fatal)) {
+ return 0;
+ }
+ }
+ }
+
+ /* send up error message */
+ inj.msg.source_id = err->source_id;
+ pcie_aer_msg(dev, &inj.msg);
+
+ if (inj.log_overflow) {
+ PCIEAERErr header_log_overflow = {
+ .status = PCI_ERR_COR_HL_OVERFLOW,
+ .flags = PCIE_AER_ERR_IS_CORRECTABLE,
+ };
+ int ret = pcie_aer_inject_error(dev, &header_log_overflow);
+ assert(!ret);
+ }
+ return 0;
+}
+
+void pcie_aer_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+ uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
+ uint32_t first_error = 1U << PCI_ERR_CAP_FEP(errcap);
+ uint32_t uncorsta = pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS);
+
+ /* uncorrectable error */
+ if (!(uncorsta & first_error)) {
+ /* the bit that corresponds to the first error is cleared */
+ pcie_aer_clear_error(dev);
+ } else if (errcap & PCI_ERR_CAP_MHRE) {
+ /* When PCI_ERR_CAP_MHRE is enabled and the first error isn't cleared
+ * nothing should happen. So we have to revert the modification to
+ * the register.
+ */
+ pcie_aer_update_uncor_status(dev);
+ } else {
+ /* capability & control
+ * PCI_ERR_CAP_MHRE might be cleared, so clear of header log.
+ */
+ aer_log_clear_all_err(&dev->exp.aer_log);
+ }
+}
+
+void pcie_aer_root_init(PCIDevice *dev)
+{
+ uint16_t pos = dev->exp.aer_cap;
+
+ pci_set_long(dev->wmask + pos + PCI_ERR_ROOT_COMMAND,
+ PCI_ERR_ROOT_CMD_EN_MASK);
+ pci_set_long(dev->w1cmask + pos + PCI_ERR_ROOT_STATUS,
+ PCI_ERR_ROOT_STATUS_REPORT_MASK);
+ /* PCI_ERR_ROOT_IRQ is RO but devices change it using a
+ * device-specific method.
+ */
+ pci_set_long(dev->cmask + pos + PCI_ERR_ROOT_STATUS,
+ ~PCI_ERR_ROOT_IRQ);
+}
+
+void pcie_aer_root_reset(PCIDevice *dev)
+{
+ uint8_t* aer_cap = dev->config + dev->exp.aer_cap;
+
+ pci_set_long(aer_cap + PCI_ERR_ROOT_COMMAND, 0);
+
+ /*
+ * Advanced Error Interrupt Message Number in Root Error Status Register
+ * must be updated by chip dependent code because it's chip dependent
+ * which number is used.
+ */
+}
+
+void pcie_aer_root_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len,
+ uint32_t root_cmd_prev)
+{
+ uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
+ uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
+ uint32_t enabled_cmd = pcie_aer_status_to_cmd(root_status);
+ uint32_t root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
+ /* 6.2.4.1.2 Interrupt Generation */
+ if (!msix_enabled(dev) && !msi_enabled(dev)) {
+ pci_set_irq(dev, !!(root_cmd & enabled_cmd));
+ return;
+ }
+
+ if ((root_cmd_prev & enabled_cmd) || !(root_cmd & enabled_cmd)) {
+ /* Send MSI on transition from false to true. */
+ return;
+ }
+
+ pcie_aer_root_notify(dev);
+}
+
+static const VMStateDescription vmstate_pcie_aer_err = {
+ .name = "PCIE_AER_ERROR",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(status, PCIEAERErr),
+ VMSTATE_UINT16(source_id, PCIEAERErr),
+ VMSTATE_UINT16(flags, PCIEAERErr),
+ VMSTATE_UINT32_ARRAY(header, PCIEAERErr, 4),
+ VMSTATE_UINT32_ARRAY(prefix, PCIEAERErr, 4),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool pcie_aer_state_log_num_valid(void *opaque, int version_id)
+{
+ PCIEAERLog *s = opaque;
+
+ return s->log_num <= s->log_max;
+}
+
+const VMStateDescription vmstate_pcie_aer_log = {
+ .name = "PCIE_AER_ERROR_LOG",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(log_num, PCIEAERLog),
+ VMSTATE_UINT16_EQUAL(log_max, PCIEAERLog),
+ VMSTATE_VALIDATE("log_num <= log_max", pcie_aer_state_log_num_valid),
+ VMSTATE_STRUCT_VARRAY_POINTER_UINT16(log, PCIEAERLog, log_num,
+ vmstate_pcie_aer_err, PCIEAERErr),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef struct PCIEAERErrorName {
+ const char *name;
+ uint32_t val;
+ bool correctable;
+} PCIEAERErrorName;
+
+/*
+ * AER error name -> value conversion table
+ * This naming scheme is same to linux aer-injection tool.
+ */
+static const struct PCIEAERErrorName pcie_aer_error_list[] = {
+ {
+ .name = "TRAIN",
+ .val = PCI_ERR_UNC_TRAIN,
+ .correctable = false,
+ }, {
+ .name = "DLP",
+ .val = PCI_ERR_UNC_DLP,
+ .correctable = false,
+ }, {
+ .name = "SDN",
+ .val = PCI_ERR_UNC_SDN,
+ .correctable = false,
+ }, {
+ .name = "POISON_TLP",
+ .val = PCI_ERR_UNC_POISON_TLP,
+ .correctable = false,
+ }, {
+ .name = "FCP",
+ .val = PCI_ERR_UNC_FCP,
+ .correctable = false,
+ }, {
+ .name = "COMP_TIME",
+ .val = PCI_ERR_UNC_COMP_TIME,
+ .correctable = false,
+ }, {
+ .name = "COMP_ABORT",
+ .val = PCI_ERR_UNC_COMP_ABORT,
+ .correctable = false,
+ }, {
+ .name = "UNX_COMP",
+ .val = PCI_ERR_UNC_UNX_COMP,
+ .correctable = false,
+ }, {
+ .name = "RX_OVER",
+ .val = PCI_ERR_UNC_RX_OVER,
+ .correctable = false,
+ }, {
+ .name = "MALF_TLP",
+ .val = PCI_ERR_UNC_MALF_TLP,
+ .correctable = false,
+ }, {
+ .name = "ECRC",
+ .val = PCI_ERR_UNC_ECRC,
+ .correctable = false,
+ }, {
+ .name = "UNSUP",
+ .val = PCI_ERR_UNC_UNSUP,
+ .correctable = false,
+ }, {
+ .name = "ACSV",
+ .val = PCI_ERR_UNC_ACSV,
+ .correctable = false,
+ }, {
+ .name = "INTN",
+ .val = PCI_ERR_UNC_INTN,
+ .correctable = false,
+ }, {
+ .name = "MCBTLP",
+ .val = PCI_ERR_UNC_MCBTLP,
+ .correctable = false,
+ }, {
+ .name = "ATOP_EBLOCKED",
+ .val = PCI_ERR_UNC_ATOP_EBLOCKED,
+ .correctable = false,
+ }, {
+ .name = "TLP_PRF_BLOCKED",
+ .val = PCI_ERR_UNC_TLP_PRF_BLOCKED,
+ .correctable = false,
+ }, {
+ .name = "RCVR",
+ .val = PCI_ERR_COR_RCVR,
+ .correctable = true,
+ }, {
+ .name = "BAD_TLP",
+ .val = PCI_ERR_COR_BAD_TLP,
+ .correctable = true,
+ }, {
+ .name = "BAD_DLLP",
+ .val = PCI_ERR_COR_BAD_DLLP,
+ .correctable = true,
+ }, {
+ .name = "REP_ROLL",
+ .val = PCI_ERR_COR_REP_ROLL,
+ .correctable = true,
+ }, {
+ .name = "REP_TIMER",
+ .val = PCI_ERR_COR_REP_TIMER,
+ .correctable = true,
+ }, {
+ .name = "ADV_NONFATAL",
+ .val = PCI_ERR_COR_ADV_NONFATAL,
+ .correctable = true,
+ }, {
+ .name = "INTERNAL",
+ .val = PCI_ERR_COR_INTERNAL,
+ .correctable = true,
+ }, {
+ .name = "HL_OVERFLOW",
+ .val = PCI_ERR_COR_HL_OVERFLOW,
+ .correctable = true,
+ },
+};
+
+static int pcie_aer_parse_error_string(const char *error_name,
+ uint32_t *status, bool *correctable)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pcie_aer_error_list); i++) {
+ const PCIEAERErrorName *e = &pcie_aer_error_list[i];
+ if (strcmp(error_name, e->name)) {
+ continue;
+ }
+
+ *status = e->val;
+ *correctable = e->correctable;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int do_pcie_aer_inject_error(Monitor *mon,
+ const QDict *qdict, QObject **ret_data)
+{
+ const char *id = qdict_get_str(qdict, "id");
+ const char *error_name;
+ uint32_t error_status;
+ bool correctable;
+ PCIDevice *dev;
+ PCIEAERErr err;
+ int ret;
+
+ ret = pci_qdev_find_device(id, &dev);
+ if (ret < 0) {
+ monitor_printf(mon,
+ "id or pci device path is invalid or device not "
+ "found. %s\n", id);
+ return ret;
+ }
+ if (!pci_is_express(dev)) {
+ monitor_printf(mon, "the device doesn't support pci express. %s\n",
+ id);
+ return -ENOSYS;
+ }
+
+ error_name = qdict_get_str(qdict, "error_status");
+ if (pcie_aer_parse_error_string(error_name, &error_status, &correctable)) {
+ char *e = NULL;
+ error_status = strtoul(error_name, &e, 0);
+ correctable = qdict_get_try_bool(qdict, "correctable", false);
+ if (!e || *e != '\0') {
+ monitor_printf(mon, "invalid error status value. \"%s\"",
+ error_name);
+ return -EINVAL;
+ }
+ }
+ err.status = error_status;
+ err.source_id = (pci_bus_num(dev->bus) << 8) | dev->devfn;
+
+ err.flags = 0;
+ if (correctable) {
+ err.flags |= PCIE_AER_ERR_IS_CORRECTABLE;
+ }
+ if (qdict_get_try_bool(qdict, "advisory_non_fatal", false)) {
+ err.flags |= PCIE_AER_ERR_MAYBE_ADVISORY;
+ }
+ if (qdict_haskey(qdict, "header0")) {
+ err.flags |= PCIE_AER_ERR_HEADER_VALID;
+ }
+ if (qdict_haskey(qdict, "prefix0")) {
+ err.flags |= PCIE_AER_ERR_TLP_PREFIX_PRESENT;
+ }
+
+ err.header[0] = qdict_get_try_int(qdict, "header0", 0);
+ err.header[1] = qdict_get_try_int(qdict, "header1", 0);
+ err.header[2] = qdict_get_try_int(qdict, "header2", 0);
+ err.header[3] = qdict_get_try_int(qdict, "header3", 0);
+
+ err.prefix[0] = qdict_get_try_int(qdict, "prefix0", 0);
+ err.prefix[1] = qdict_get_try_int(qdict, "prefix1", 0);
+ err.prefix[2] = qdict_get_try_int(qdict, "prefix2", 0);
+ err.prefix[3] = qdict_get_try_int(qdict, "prefix3", 0);
+
+ ret = pcie_aer_inject_error(dev, &err);
+ *ret_data = qobject_from_jsonf("{'id': %s, "
+ "'root_bus': %s, 'bus': %d, 'devfn': %d, "
+ "'ret': %d}",
+ id, pci_root_bus_path(dev),
+ pci_bus_num(dev->bus), dev->devfn,
+ ret);
+ assert(*ret_data);
+
+ return 0;
+}
+
+void hmp_pcie_aer_inject_error(Monitor *mon, const QDict *qdict)
+{
+ QObject *data;
+ int devfn;
+
+ if (do_pcie_aer_inject_error(mon, qdict, &data) < 0) {
+ return;
+ }
+
+ assert(qobject_type(data) == QTYPE_QDICT);
+ qdict = qobject_to_qdict(data);
+
+ devfn = (int)qdict_get_int(qdict, "devfn");
+ monitor_printf(mon, "OK id: %s root bus: %s, bus: %x devfn: %x.%x\n",
+ qdict_get_str(qdict, "id"),
+ qdict_get_str(qdict, "root_bus"),
+ (int) qdict_get_int(qdict, "bus"),
+ PCI_SLOT(devfn), PCI_FUNC(devfn));
+}
diff --git a/hw/pci/pcie_host.c b/hw/pci/pcie_host.c
new file mode 100644
index 00000000..d8afba86
--- /dev/null
+++ b/hw/pci/pcie_host.c
@@ -0,0 +1,146 @@
+/*
+ * pcie_host.c
+ * utility functions for pci express host bridge.
+ *
+ * Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pcie_host.h"
+#include "exec/address-spaces.h"
+
+/* a helper function to get a PCIDevice for a given mmconfig address */
+static inline PCIDevice *pcie_dev_find_by_mmcfg_addr(PCIBus *s,
+ uint32_t mmcfg_addr)
+{
+ return pci_find_device(s, PCIE_MMCFG_BUS(mmcfg_addr),
+ PCIE_MMCFG_DEVFN(mmcfg_addr));
+}
+
+static void pcie_mmcfg_data_write(void *opaque, hwaddr mmcfg_addr,
+ uint64_t val, unsigned len)
+{
+ PCIExpressHost *e = opaque;
+ PCIBus *s = e->pci.bus;
+ PCIDevice *pci_dev = pcie_dev_find_by_mmcfg_addr(s, mmcfg_addr);
+ uint32_t addr;
+ uint32_t limit;
+
+ if (!pci_dev) {
+ return;
+ }
+ addr = PCIE_MMCFG_CONFOFFSET(mmcfg_addr);
+ limit = pci_config_size(pci_dev);
+ if (limit <= addr) {
+ /* conventional pci device can be behind pcie-to-pci bridge.
+ 256 <= addr < 4K has no effects. */
+ return;
+ }
+ pci_host_config_write_common(pci_dev, addr, limit, val, len);
+}
+
+static uint64_t pcie_mmcfg_data_read(void *opaque,
+ hwaddr mmcfg_addr,
+ unsigned len)
+{
+ PCIExpressHost *e = opaque;
+ PCIBus *s = e->pci.bus;
+ PCIDevice *pci_dev = pcie_dev_find_by_mmcfg_addr(s, mmcfg_addr);
+ uint32_t addr;
+ uint32_t limit;
+
+ if (!pci_dev) {
+ return ~0x0;
+ }
+ addr = PCIE_MMCFG_CONFOFFSET(mmcfg_addr);
+ limit = pci_config_size(pci_dev);
+ if (limit <= addr) {
+ /* conventional pci device can be behind pcie-to-pci bridge.
+ 256 <= addr < 4K has no effects. */
+ return ~0x0;
+ }
+ return pci_host_config_read_common(pci_dev, addr, limit, len);
+}
+
+static const MemoryRegionOps pcie_mmcfg_ops = {
+ .read = pcie_mmcfg_data_read,
+ .write = pcie_mmcfg_data_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pcie_host_init(Object *obj)
+{
+ PCIExpressHost *e = PCIE_HOST_BRIDGE(obj);
+
+ e->base_addr = PCIE_BASE_ADDR_UNMAPPED;
+ memory_region_init_io(&e->mmio, OBJECT(e), &pcie_mmcfg_ops, e, "pcie-mmcfg-mmio",
+ PCIE_MMCFG_SIZE_MAX);
+}
+
+void pcie_host_mmcfg_unmap(PCIExpressHost *e)
+{
+ if (e->base_addr != PCIE_BASE_ADDR_UNMAPPED) {
+ memory_region_del_subregion(get_system_memory(), &e->mmio);
+ e->base_addr = PCIE_BASE_ADDR_UNMAPPED;
+ }
+}
+
+void pcie_host_mmcfg_init(PCIExpressHost *e, uint32_t size)
+{
+ assert(!(size & (size - 1))); /* power of 2 */
+ assert(size >= PCIE_MMCFG_SIZE_MIN);
+ assert(size <= PCIE_MMCFG_SIZE_MAX);
+ e->size = size;
+ memory_region_set_size(&e->mmio, e->size);
+}
+
+void pcie_host_mmcfg_map(PCIExpressHost *e, hwaddr addr,
+ uint32_t size)
+{
+ pcie_host_mmcfg_init(e, size);
+ e->base_addr = addr;
+ memory_region_add_subregion(get_system_memory(), e->base_addr, &e->mmio);
+}
+
+void pcie_host_mmcfg_update(PCIExpressHost *e,
+ int enable,
+ hwaddr addr,
+ uint32_t size)
+{
+ memory_region_transaction_begin();
+ pcie_host_mmcfg_unmap(e);
+ if (enable) {
+ pcie_host_mmcfg_map(e, addr, size);
+ }
+ memory_region_transaction_commit();
+}
+
+static const TypeInfo pcie_host_type_info = {
+ .name = TYPE_PCIE_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .abstract = true,
+ .instance_size = sizeof(PCIExpressHost),
+ .instance_init = pcie_host_init,
+};
+
+static void pcie_host_register_types(void)
+{
+ type_register_static(&pcie_host_type_info);
+}
+
+type_init(pcie_host_register_types)
diff --git a/hw/pci/pcie_port.c b/hw/pci/pcie_port.c
new file mode 100644
index 00000000..40ca8d5d
--- /dev/null
+++ b/hw/pci/pcie_port.c
@@ -0,0 +1,178 @@
+/*
+ * pcie_port.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/pci/pcie_port.h"
+#include "hw/hotplug.h"
+
+void pcie_port_init_reg(PCIDevice *d)
+{
+ /* Unlike pci bridge,
+ 66MHz and fast back to back don't apply to pci express port. */
+ pci_set_word(d->config + PCI_STATUS, 0);
+ pci_set_word(d->config + PCI_SEC_STATUS, 0);
+
+ /*
+ * Unlike conventional pci bridge, for some bits the spec states:
+ * Does not apply to PCI Express and must be hardwired to 0.
+ */
+ pci_word_test_and_clear_mask(d->wmask + PCI_BRIDGE_CONTROL,
+ PCI_BRIDGE_CTL_MASTER_ABORT |
+ PCI_BRIDGE_CTL_FAST_BACK |
+ PCI_BRIDGE_CTL_DISCARD |
+ PCI_BRIDGE_CTL_SEC_DISCARD |
+ PCI_BRIDGE_CTL_DISCARD_STATUS |
+ PCI_BRIDGE_CTL_DISCARD_SERR);
+}
+
+/**************************************************************************
+ * (chassis number, pcie physical slot number) -> pcie slot conversion
+ */
+struct PCIEChassis {
+ uint8_t number;
+
+ QLIST_HEAD(, PCIESlot) slots;
+ QLIST_ENTRY(PCIEChassis) next;
+};
+
+static QLIST_HEAD(, PCIEChassis) chassis = QLIST_HEAD_INITIALIZER(chassis);
+
+static struct PCIEChassis *pcie_chassis_find(uint8_t chassis_number)
+{
+ struct PCIEChassis *c;
+ QLIST_FOREACH(c, &chassis, next) {
+ if (c->number == chassis_number) {
+ break;
+ }
+ }
+ return c;
+}
+
+void pcie_chassis_create(uint8_t chassis_number)
+{
+ struct PCIEChassis *c;
+ c = pcie_chassis_find(chassis_number);
+ if (c) {
+ return;
+ }
+ c = g_malloc0(sizeof(*c));
+ c->number = chassis_number;
+ QLIST_INIT(&c->slots);
+ QLIST_INSERT_HEAD(&chassis, c, next);
+}
+
+static PCIESlot *pcie_chassis_find_slot_with_chassis(struct PCIEChassis *c,
+ uint8_t slot)
+{
+ PCIESlot *s;
+ QLIST_FOREACH(s, &c->slots, next) {
+ if (s->slot == slot) {
+ break;
+ }
+ }
+ return s;
+}
+
+PCIESlot *pcie_chassis_find_slot(uint8_t chassis_number, uint16_t slot)
+{
+ struct PCIEChassis *c;
+ c = pcie_chassis_find(chassis_number);
+ if (!c) {
+ return NULL;
+ }
+ return pcie_chassis_find_slot_with_chassis(c, slot);
+}
+
+int pcie_chassis_add_slot(struct PCIESlot *slot)
+{
+ struct PCIEChassis *c;
+ c = pcie_chassis_find(slot->chassis);
+ if (!c) {
+ return -ENODEV;
+ }
+ if (pcie_chassis_find_slot_with_chassis(c, slot->slot)) {
+ return -EBUSY;
+ }
+ QLIST_INSERT_HEAD(&c->slots, slot, next);
+ return 0;
+}
+
+void pcie_chassis_del_slot(PCIESlot *s)
+{
+ QLIST_REMOVE(s, next);
+}
+
+static Property pcie_port_props[] = {
+ DEFINE_PROP_UINT8("port", PCIEPort, port, 0),
+ DEFINE_PROP_UINT16("aer_log_max", PCIEPort,
+ parent_obj.parent_obj.exp.aer_log.log_max,
+ PCIE_AER_LOG_MAX_DEFAULT),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void pcie_port_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->props = pcie_port_props;
+}
+
+static const TypeInfo pcie_port_type_info = {
+ .name = TYPE_PCIE_PORT,
+ .parent = TYPE_PCI_BRIDGE,
+ .instance_size = sizeof(PCIEPort),
+ .abstract = true,
+ .class_init = pcie_port_class_init,
+};
+
+static Property pcie_slot_props[] = {
+ DEFINE_PROP_UINT8("chassis", PCIESlot, chassis, 0),
+ DEFINE_PROP_UINT16("slot", PCIESlot, slot, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void pcie_slot_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc);
+
+ dc->props = pcie_slot_props;
+ hc->plug = pcie_cap_slot_hotplug_cb;
+ hc->unplug_request = pcie_cap_slot_hot_unplug_request_cb;
+}
+
+static const TypeInfo pcie_slot_type_info = {
+ .name = TYPE_PCIE_SLOT,
+ .parent = TYPE_PCIE_PORT,
+ .instance_size = sizeof(PCIESlot),
+ .abstract = true,
+ .class_init = pcie_slot_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static void pcie_port_register_types(void)
+{
+ type_register_static(&pcie_port_type_info);
+ type_register_static(&pcie_slot_type_info);
+}
+
+type_init(pcie_port_register_types)
diff --git a/hw/pci/shpc.c b/hw/pci/shpc.c
new file mode 100644
index 00000000..bfb4d31b
--- /dev/null
+++ b/hw/pci/shpc.c
@@ -0,0 +1,721 @@
+#include "qemu-common.h"
+#include <strings.h>
+#include <stdint.h>
+#include "qemu/range.h"
+#include "qemu/error-report.h"
+#include "hw/pci/shpc.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/msi.h"
+
+/* TODO: model power only and disabled slot states. */
+/* TODO: handle SERR and wakeups */
+/* TODO: consider enabling 66MHz support */
+
+/* TODO: remove fully only on state DISABLED and LED off.
+ * track state to properly record this. */
+
+/* SHPC Working Register Set */
+#define SHPC_BASE_OFFSET 0x00 /* 4 bytes */
+#define SHPC_SLOTS_33 0x04 /* 4 bytes. Also encodes PCI-X slots. */
+#define SHPC_SLOTS_66 0x08 /* 4 bytes. */
+#define SHPC_NSLOTS 0x0C /* 1 byte */
+#define SHPC_FIRST_DEV 0x0D /* 1 byte */
+#define SHPC_PHYS_SLOT 0x0E /* 2 byte */
+#define SHPC_PHYS_NUM_MAX 0x7ff
+#define SHPC_PHYS_NUM_UP 0x2000
+#define SHPC_PHYS_MRL 0x4000
+#define SHPC_PHYS_BUTTON 0x8000
+#define SHPC_SEC_BUS 0x10 /* 2 bytes */
+#define SHPC_SEC_BUS_33 0x0
+#define SHPC_SEC_BUS_66 0x1 /* Unused */
+#define SHPC_SEC_BUS_MASK 0x7
+#define SHPC_MSI_CTL 0x12 /* 1 byte */
+#define SHPC_PROG_IFC 0x13 /* 1 byte */
+#define SHPC_PROG_IFC_1_0 0x1
+#define SHPC_CMD_CODE 0x14 /* 1 byte */
+#define SHPC_CMD_TRGT 0x15 /* 1 byte */
+#define SHPC_CMD_TRGT_MIN 0x1
+#define SHPC_CMD_TRGT_MAX 0x1f
+#define SHPC_CMD_STATUS 0x16 /* 2 bytes */
+#define SHPC_CMD_STATUS_BUSY 0x1
+#define SHPC_CMD_STATUS_MRL_OPEN 0x2
+#define SHPC_CMD_STATUS_INVALID_CMD 0x4
+#define SHPC_CMD_STATUS_INVALID_MODE 0x8
+#define SHPC_INT_LOCATOR 0x18 /* 4 bytes */
+#define SHPC_INT_COMMAND 0x1
+#define SHPC_SERR_LOCATOR 0x1C /* 4 bytes */
+#define SHPC_SERR_INT 0x20 /* 4 bytes */
+#define SHPC_INT_DIS 0x1
+#define SHPC_SERR_DIS 0x2
+#define SHPC_CMD_INT_DIS 0x4
+#define SHPC_ARB_SERR_DIS 0x8
+#define SHPC_CMD_DETECTED 0x10000
+#define SHPC_ARB_DETECTED 0x20000
+ /* 4 bytes * slot # (start from 0) */
+#define SHPC_SLOT_REG(s) (0x24 + (s) * 4)
+ /* 2 bytes */
+#define SHPC_SLOT_STATUS(s) (0x0 + SHPC_SLOT_REG(s))
+
+/* Same slot state masks are used for command and status registers */
+#define SHPC_SLOT_STATE_MASK 0x03
+#define SHPC_SLOT_STATE_SHIFT \
+ ctz32(SHPC_SLOT_STATE_MASK)
+
+#define SHPC_STATE_NO 0x0
+#define SHPC_STATE_PWRONLY 0x1
+#define SHPC_STATE_ENABLED 0x2
+#define SHPC_STATE_DISABLED 0x3
+
+#define SHPC_SLOT_PWR_LED_MASK 0xC
+#define SHPC_SLOT_PWR_LED_SHIFT \
+ ctz32(SHPC_SLOT_PWR_LED_MASK)
+#define SHPC_SLOT_ATTN_LED_MASK 0x30
+#define SHPC_SLOT_ATTN_LED_SHIFT \
+ ctz32(SHPC_SLOT_ATTN_LED_MASK)
+
+#define SHPC_LED_NO 0x0
+#define SHPC_LED_ON 0x1
+#define SHPC_LED_BLINK 0x2
+#define SHPC_LED_OFF 0x3
+
+#define SHPC_SLOT_STATUS_PWR_FAULT 0x40
+#define SHPC_SLOT_STATUS_BUTTON 0x80
+#define SHPC_SLOT_STATUS_MRL_OPEN 0x100
+#define SHPC_SLOT_STATUS_66 0x200
+#define SHPC_SLOT_STATUS_PRSNT_MASK 0xC00
+#define SHPC_SLOT_STATUS_PRSNT_EMPTY 0x3
+#define SHPC_SLOT_STATUS_PRSNT_25W 0x1
+#define SHPC_SLOT_STATUS_PRSNT_15W 0x2
+#define SHPC_SLOT_STATUS_PRSNT_7_5W 0x0
+
+#define SHPC_SLOT_STATUS_PRSNT_PCIX 0x3000
+
+
+ /* 1 byte */
+#define SHPC_SLOT_EVENT_LATCH(s) (0x2 + SHPC_SLOT_REG(s))
+ /* 1 byte */
+#define SHPC_SLOT_EVENT_SERR_INT_DIS(d, s) (0x3 + SHPC_SLOT_REG(s))
+#define SHPC_SLOT_EVENT_PRESENCE 0x01
+#define SHPC_SLOT_EVENT_ISOLATED_FAULT 0x02
+#define SHPC_SLOT_EVENT_BUTTON 0x04
+#define SHPC_SLOT_EVENT_MRL 0x08
+#define SHPC_SLOT_EVENT_CONNECTED_FAULT 0x10
+/* Bits below are used for Serr/Int disable only */
+#define SHPC_SLOT_EVENT_MRL_SERR_DIS 0x20
+#define SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS 0x40
+
+#define SHPC_MIN_SLOTS 1
+#define SHPC_MAX_SLOTS 31
+#define SHPC_SIZEOF(d) SHPC_SLOT_REG((d)->shpc->nslots)
+
+/* SHPC Slot identifiers */
+
+/* Hotplug supported at 31 slots out of the total 32. We reserve slot 0,
+ and give the rest of them physical *and* pci numbers starting from 1, so
+ they match logical numbers. Note: this means that multiple slots must have
+ different chassis number values, to make chassis+physical slot unique.
+ TODO: make this configurable? */
+#define SHPC_IDX_TO_LOGICAL(slot) ((slot) + 1)
+#define SHPC_LOGICAL_TO_IDX(target) ((target) - 1)
+#define SHPC_IDX_TO_PCI(slot) ((slot) + 1)
+#define SHPC_PCI_TO_IDX(pci_slot) ((pci_slot) - 1)
+#define SHPC_IDX_TO_PHYSICAL(slot) ((slot) + 1)
+
+static int roundup_pow_of_two(int x)
+{
+ x |= (x >> 1);
+ x |= (x >> 2);
+ x |= (x >> 4);
+ x |= (x >> 8);
+ x |= (x >> 16);
+ return x + 1;
+}
+
+static uint16_t shpc_get_status(SHPCDevice *shpc, int slot, uint16_t msk)
+{
+ uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
+ return (pci_get_word(status) & msk) >> ctz32(msk);
+}
+
+static void shpc_set_status(SHPCDevice *shpc,
+ int slot, uint8_t value, uint16_t msk)
+{
+ uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
+ pci_word_test_and_clear_mask(status, msk);
+ pci_word_test_and_set_mask(status, value << ctz32(msk));
+}
+
+static void shpc_interrupt_update(PCIDevice *d)
+{
+ SHPCDevice *shpc = d->shpc;
+ int slot;
+ int level = 0;
+ uint32_t serr_int;
+ uint32_t int_locator = 0;
+
+ /* Update interrupt locator register */
+ for (slot = 0; slot < shpc->nslots; ++slot) {
+ uint8_t event = shpc->config[SHPC_SLOT_EVENT_LATCH(slot)];
+ uint8_t disable = shpc->config[SHPC_SLOT_EVENT_SERR_INT_DIS(d, slot)];
+ uint32_t mask = 1U << SHPC_IDX_TO_LOGICAL(slot);
+ if (event & ~disable) {
+ int_locator |= mask;
+ }
+ }
+ serr_int = pci_get_long(shpc->config + SHPC_SERR_INT);
+ if ((serr_int & SHPC_CMD_DETECTED) && !(serr_int & SHPC_CMD_INT_DIS)) {
+ int_locator |= SHPC_INT_COMMAND;
+ }
+ pci_set_long(shpc->config + SHPC_INT_LOCATOR, int_locator);
+ level = (!(serr_int & SHPC_INT_DIS) && int_locator) ? 1 : 0;
+ if (msi_enabled(d) && shpc->msi_requested != level)
+ msi_notify(d, 0);
+ else
+ pci_set_irq(d, level);
+ shpc->msi_requested = level;
+}
+
+static void shpc_set_sec_bus_speed(SHPCDevice *shpc, uint8_t speed)
+{
+ switch (speed) {
+ case SHPC_SEC_BUS_33:
+ shpc->config[SHPC_SEC_BUS] &= ~SHPC_SEC_BUS_MASK;
+ shpc->config[SHPC_SEC_BUS] |= speed;
+ break;
+ default:
+ pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS,
+ SHPC_CMD_STATUS_INVALID_MODE);
+ }
+}
+
+void shpc_reset(PCIDevice *d)
+{
+ SHPCDevice *shpc = d->shpc;
+ int nslots = shpc->nslots;
+ int i;
+ memset(shpc->config, 0, SHPC_SIZEOF(d));
+ pci_set_byte(shpc->config + SHPC_NSLOTS, nslots);
+ pci_set_long(shpc->config + SHPC_SLOTS_33, nslots);
+ pci_set_long(shpc->config + SHPC_SLOTS_66, 0);
+ pci_set_byte(shpc->config + SHPC_FIRST_DEV, SHPC_IDX_TO_PCI(0));
+ pci_set_word(shpc->config + SHPC_PHYS_SLOT,
+ SHPC_IDX_TO_PHYSICAL(0) |
+ SHPC_PHYS_NUM_UP |
+ SHPC_PHYS_MRL |
+ SHPC_PHYS_BUTTON);
+ pci_set_long(shpc->config + SHPC_SERR_INT, SHPC_INT_DIS |
+ SHPC_SERR_DIS |
+ SHPC_CMD_INT_DIS |
+ SHPC_ARB_SERR_DIS);
+ pci_set_byte(shpc->config + SHPC_PROG_IFC, SHPC_PROG_IFC_1_0);
+ pci_set_word(shpc->config + SHPC_SEC_BUS, SHPC_SEC_BUS_33);
+ for (i = 0; i < shpc->nslots; ++i) {
+ pci_set_byte(shpc->config + SHPC_SLOT_EVENT_SERR_INT_DIS(d, i),
+ SHPC_SLOT_EVENT_PRESENCE |
+ SHPC_SLOT_EVENT_ISOLATED_FAULT |
+ SHPC_SLOT_EVENT_BUTTON |
+ SHPC_SLOT_EVENT_MRL |
+ SHPC_SLOT_EVENT_CONNECTED_FAULT |
+ SHPC_SLOT_EVENT_MRL_SERR_DIS |
+ SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS);
+ if (shpc->sec_bus->devices[PCI_DEVFN(SHPC_IDX_TO_PCI(i), 0)]) {
+ shpc_set_status(shpc, i, SHPC_STATE_ENABLED, SHPC_SLOT_STATE_MASK);
+ shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_MRL_OPEN);
+ shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_7_5W,
+ SHPC_SLOT_STATUS_PRSNT_MASK);
+ shpc_set_status(shpc, i, SHPC_LED_ON, SHPC_SLOT_PWR_LED_MASK);
+ } else {
+ shpc_set_status(shpc, i, SHPC_STATE_DISABLED, SHPC_SLOT_STATE_MASK);
+ shpc_set_status(shpc, i, 1, SHPC_SLOT_STATUS_MRL_OPEN);
+ shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_EMPTY,
+ SHPC_SLOT_STATUS_PRSNT_MASK);
+ shpc_set_status(shpc, i, SHPC_LED_OFF, SHPC_SLOT_PWR_LED_MASK);
+ }
+ shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_66);
+ }
+ shpc_set_sec_bus_speed(shpc, SHPC_SEC_BUS_33);
+ shpc->msi_requested = 0;
+ shpc_interrupt_update(d);
+}
+
+static void shpc_invalid_command(SHPCDevice *shpc)
+{
+ pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS,
+ SHPC_CMD_STATUS_INVALID_CMD);
+}
+
+static void shpc_free_devices_in_slot(SHPCDevice *shpc, int slot)
+{
+ int devfn;
+ int pci_slot = SHPC_IDX_TO_PCI(slot);
+ for (devfn = PCI_DEVFN(pci_slot, 0);
+ devfn <= PCI_DEVFN(pci_slot, PCI_FUNC_MAX - 1);
+ ++devfn) {
+ PCIDevice *affected_dev = shpc->sec_bus->devices[devfn];
+ if (affected_dev) {
+ object_unparent(OBJECT(affected_dev));
+ }
+ }
+}
+
+static void shpc_slot_command(SHPCDevice *shpc, uint8_t target,
+ uint8_t state, uint8_t power, uint8_t attn)
+{
+ uint8_t current_state;
+ int slot = SHPC_LOGICAL_TO_IDX(target);
+ if (target < SHPC_CMD_TRGT_MIN || slot >= shpc->nslots) {
+ shpc_invalid_command(shpc);
+ return;
+ }
+ current_state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK);
+ if (current_state == SHPC_STATE_ENABLED && state == SHPC_STATE_PWRONLY) {
+ shpc_invalid_command(shpc);
+ return;
+ }
+
+ switch (power) {
+ case SHPC_LED_NO:
+ break;
+ default:
+ /* TODO: send event to monitor */
+ shpc_set_status(shpc, slot, power, SHPC_SLOT_PWR_LED_MASK);
+ }
+ switch (attn) {
+ case SHPC_LED_NO:
+ break;
+ default:
+ /* TODO: send event to monitor */
+ shpc_set_status(shpc, slot, attn, SHPC_SLOT_ATTN_LED_MASK);
+ }
+
+ if ((current_state == SHPC_STATE_DISABLED && state == SHPC_STATE_PWRONLY) ||
+ (current_state == SHPC_STATE_DISABLED && state == SHPC_STATE_ENABLED)) {
+ shpc_set_status(shpc, slot, state, SHPC_SLOT_STATE_MASK);
+ } else if ((current_state == SHPC_STATE_ENABLED ||
+ current_state == SHPC_STATE_PWRONLY) &&
+ state == SHPC_STATE_DISABLED) {
+ shpc_set_status(shpc, slot, state, SHPC_SLOT_STATE_MASK);
+ power = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK);
+ /* TODO: track what monitor requested. */
+ /* Look at LED to figure out whether it's ok to remove the device. */
+ if (power == SHPC_LED_OFF) {
+ shpc_free_devices_in_slot(shpc, slot);
+ shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN);
+ shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY,
+ SHPC_SLOT_STATUS_PRSNT_MASK);
+ shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
+ SHPC_SLOT_EVENT_BUTTON |
+ SHPC_SLOT_EVENT_MRL |
+ SHPC_SLOT_EVENT_PRESENCE;
+ }
+ }
+}
+
+static void shpc_command(SHPCDevice *shpc)
+{
+ uint8_t code = pci_get_byte(shpc->config + SHPC_CMD_CODE);
+ uint8_t speed;
+ uint8_t target;
+ uint8_t attn;
+ uint8_t power;
+ uint8_t state;
+ int i;
+
+ /* Clear status from the previous command. */
+ pci_word_test_and_clear_mask(shpc->config + SHPC_CMD_STATUS,
+ SHPC_CMD_STATUS_BUSY |
+ SHPC_CMD_STATUS_MRL_OPEN |
+ SHPC_CMD_STATUS_INVALID_CMD |
+ SHPC_CMD_STATUS_INVALID_MODE);
+ switch (code) {
+ case 0x00 ... 0x3f:
+ target = shpc->config[SHPC_CMD_TRGT] & SHPC_CMD_TRGT_MAX;
+ state = (code & SHPC_SLOT_STATE_MASK) >> SHPC_SLOT_STATE_SHIFT;
+ power = (code & SHPC_SLOT_PWR_LED_MASK) >> SHPC_SLOT_PWR_LED_SHIFT;
+ attn = (code & SHPC_SLOT_ATTN_LED_MASK) >> SHPC_SLOT_ATTN_LED_SHIFT;
+ shpc_slot_command(shpc, target, state, power, attn);
+ break;
+ case 0x40 ... 0x47:
+ speed = code & SHPC_SEC_BUS_MASK;
+ shpc_set_sec_bus_speed(shpc, speed);
+ break;
+ case 0x48:
+ /* Power only all slots */
+ /* first verify no slots are enabled */
+ for (i = 0; i < shpc->nslots; ++i) {
+ state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK);
+ if (state == SHPC_STATE_ENABLED) {
+ shpc_invalid_command(shpc);
+ goto done;
+ }
+ }
+ for (i = 0; i < shpc->nslots; ++i) {
+ if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) {
+ shpc_slot_command(shpc, i + SHPC_CMD_TRGT_MIN,
+ SHPC_STATE_PWRONLY, SHPC_LED_ON, SHPC_LED_NO);
+ } else {
+ shpc_slot_command(shpc, i + SHPC_CMD_TRGT_MIN,
+ SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO);
+ }
+ }
+ break;
+ case 0x49:
+ /* Enable all slots */
+ /* TODO: Spec says this shall fail if some are already enabled.
+ * This doesn't make sense - why not? a spec bug? */
+ for (i = 0; i < shpc->nslots; ++i) {
+ state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK);
+ if (state == SHPC_STATE_ENABLED) {
+ shpc_invalid_command(shpc);
+ goto done;
+ }
+ }
+ for (i = 0; i < shpc->nslots; ++i) {
+ if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) {
+ shpc_slot_command(shpc, i + SHPC_CMD_TRGT_MIN,
+ SHPC_STATE_ENABLED, SHPC_LED_ON, SHPC_LED_NO);
+ } else {
+ shpc_slot_command(shpc, i + SHPC_CMD_TRGT_MIN,
+ SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO);
+ }
+ }
+ break;
+ default:
+ shpc_invalid_command(shpc);
+ break;
+ }
+done:
+ pci_long_test_and_set_mask(shpc->config + SHPC_SERR_INT, SHPC_CMD_DETECTED);
+}
+
+static void shpc_write(PCIDevice *d, unsigned addr, uint64_t val, int l)
+{
+ SHPCDevice *shpc = d->shpc;
+ int i;
+ if (addr >= SHPC_SIZEOF(d)) {
+ return;
+ }
+ l = MIN(l, SHPC_SIZEOF(d) - addr);
+
+ /* TODO: code duplicated from pci.c */
+ for (i = 0; i < l; val >>= 8, ++i) {
+ unsigned a = addr + i;
+ uint8_t wmask = shpc->wmask[a];
+ uint8_t w1cmask = shpc->w1cmask[a];
+ assert(!(wmask & w1cmask));
+ shpc->config[a] = (shpc->config[a] & ~wmask) | (val & wmask);
+ shpc->config[a] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */
+ }
+ if (ranges_overlap(addr, l, SHPC_CMD_CODE, 2)) {
+ shpc_command(shpc);
+ }
+ shpc_interrupt_update(d);
+}
+
+static uint64_t shpc_read(PCIDevice *d, unsigned addr, int l)
+{
+ uint64_t val = 0x0;
+ if (addr >= SHPC_SIZEOF(d)) {
+ return val;
+ }
+ l = MIN(l, SHPC_SIZEOF(d) - addr);
+ memcpy(&val, d->shpc->config + addr, l);
+ return val;
+}
+
+/* SHPC Bridge Capability */
+#define SHPC_CAP_LENGTH 0x08
+#define SHPC_CAP_DWORD_SELECT 0x2 /* 1 byte */
+#define SHPC_CAP_CxP 0x3 /* 1 byte: CSP, CIP */
+#define SHPC_CAP_DWORD_DATA 0x4 /* 4 bytes */
+#define SHPC_CAP_CSP_MASK 0x4
+#define SHPC_CAP_CIP_MASK 0x8
+
+static uint8_t shpc_cap_dword(PCIDevice *d)
+{
+ return pci_get_byte(d->config + d->shpc->cap + SHPC_CAP_DWORD_SELECT);
+}
+
+/* Update dword data capability register */
+static void shpc_cap_update_dword(PCIDevice *d)
+{
+ unsigned data;
+ data = shpc_read(d, shpc_cap_dword(d) * 4, 4);
+ pci_set_long(d->config + d->shpc->cap + SHPC_CAP_DWORD_DATA, data);
+}
+
+/* Add SHPC capability to the config space for the device. */
+static int shpc_cap_add_config(PCIDevice *d)
+{
+ uint8_t *config;
+ int config_offset;
+ config_offset = pci_add_capability(d, PCI_CAP_ID_SHPC,
+ 0, SHPC_CAP_LENGTH);
+ if (config_offset < 0) {
+ return config_offset;
+ }
+ config = d->config + config_offset;
+
+ pci_set_byte(config + SHPC_CAP_DWORD_SELECT, 0);
+ pci_set_byte(config + SHPC_CAP_CxP, 0);
+ pci_set_long(config + SHPC_CAP_DWORD_DATA, 0);
+ d->shpc->cap = config_offset;
+ /* Make dword select and data writeable. */
+ pci_set_byte(d->wmask + config_offset + SHPC_CAP_DWORD_SELECT, 0xff);
+ pci_set_long(d->wmask + config_offset + SHPC_CAP_DWORD_DATA, 0xffffffff);
+ return 0;
+}
+
+static uint64_t shpc_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return shpc_read(opaque, addr, size);
+}
+
+static void shpc_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ shpc_write(opaque, addr, val, size);
+}
+
+static const MemoryRegionOps shpc_mmio_ops = {
+ .read = shpc_mmio_read,
+ .write = shpc_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ /* SHPC ECN requires dword accesses, but the original 1.0 spec doesn't.
+ * It's easier to suppport all sizes than worry about it. */
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+static void shpc_device_hotplug_common(PCIDevice *affected_dev, int *slot,
+ SHPCDevice *shpc, Error **errp)
+{
+ int pci_slot = PCI_SLOT(affected_dev->devfn);
+ *slot = SHPC_PCI_TO_IDX(pci_slot);
+
+ if (pci_slot < SHPC_IDX_TO_PCI(0) || *slot >= shpc->nslots) {
+ error_setg(errp, "Unsupported PCI slot %d for standard hotplug "
+ "controller. Valid slots are between %d and %d.",
+ pci_slot, SHPC_IDX_TO_PCI(0),
+ SHPC_IDX_TO_PCI(shpc->nslots) - 1);
+ return;
+ }
+}
+
+void shpc_device_hotplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ Error *local_err = NULL;
+ PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
+ SHPCDevice *shpc = pci_hotplug_dev->shpc;
+ int slot;
+
+ shpc_device_hotplug_common(PCI_DEVICE(dev), &slot, shpc, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ /* Don't send event when device is enabled during qemu machine creation:
+ * it is present on boot, no hotplug event is necessary. We do send an
+ * event when the device is disabled later. */
+ if (!dev->hotplugged) {
+ shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN);
+ shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W,
+ SHPC_SLOT_STATUS_PRSNT_MASK);
+ return;
+ }
+
+ /* This could be a cancellation of the previous removal.
+ * We check MRL state to figure out. */
+ if (shpc_get_status(shpc, slot, SHPC_SLOT_STATUS_MRL_OPEN)) {
+ shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN);
+ shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W,
+ SHPC_SLOT_STATUS_PRSNT_MASK);
+ shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
+ SHPC_SLOT_EVENT_BUTTON |
+ SHPC_SLOT_EVENT_MRL |
+ SHPC_SLOT_EVENT_PRESENCE;
+ } else {
+ /* Press attention button to cancel removal */
+ shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
+ SHPC_SLOT_EVENT_BUTTON;
+ }
+ shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66);
+ shpc_interrupt_update(pci_hotplug_dev);
+}
+
+void shpc_device_hot_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ Error *local_err = NULL;
+ PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
+ SHPCDevice *shpc = pci_hotplug_dev->shpc;
+ uint8_t state;
+ uint8_t led;
+ int slot;
+
+ shpc_device_hotplug_common(PCI_DEVICE(dev), &slot, shpc, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= SHPC_SLOT_EVENT_BUTTON;
+ state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK);
+ led = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK);
+ if (state == SHPC_STATE_DISABLED && led == SHPC_LED_OFF) {
+ shpc_free_devices_in_slot(shpc, slot);
+ shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN);
+ shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY,
+ SHPC_SLOT_STATUS_PRSNT_MASK);
+ shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
+ SHPC_SLOT_EVENT_MRL |
+ SHPC_SLOT_EVENT_PRESENCE;
+ }
+ shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66);
+ shpc_interrupt_update(pci_hotplug_dev);
+}
+
+/* Initialize the SHPC structure in bridge's BAR. */
+int shpc_init(PCIDevice *d, PCIBus *sec_bus, MemoryRegion *bar, unsigned offset)
+{
+ int i, ret;
+ int nslots = SHPC_MAX_SLOTS; /* TODO: qdev property? */
+ SHPCDevice *shpc = d->shpc = g_malloc0(sizeof(*d->shpc));
+ shpc->sec_bus = sec_bus;
+ ret = shpc_cap_add_config(d);
+ if (ret) {
+ g_free(d->shpc);
+ return ret;
+ }
+ if (nslots < SHPC_MIN_SLOTS) {
+ return 0;
+ }
+ if (nslots > SHPC_MAX_SLOTS ||
+ SHPC_IDX_TO_PCI(nslots) > PCI_SLOT_MAX) {
+ /* TODO: report an error mesage that makes sense. */
+ return -EINVAL;
+ }
+ shpc->nslots = nslots;
+ shpc->config = g_malloc0(SHPC_SIZEOF(d));
+ shpc->cmask = g_malloc0(SHPC_SIZEOF(d));
+ shpc->wmask = g_malloc0(SHPC_SIZEOF(d));
+ shpc->w1cmask = g_malloc0(SHPC_SIZEOF(d));
+
+ shpc_reset(d);
+
+ pci_set_long(shpc->config + SHPC_BASE_OFFSET, offset);
+
+ pci_set_byte(shpc->wmask + SHPC_CMD_CODE, 0xff);
+ pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX);
+ pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX);
+ pci_set_long(shpc->wmask + SHPC_SERR_INT,
+ SHPC_INT_DIS |
+ SHPC_SERR_DIS |
+ SHPC_CMD_INT_DIS |
+ SHPC_ARB_SERR_DIS);
+ pci_set_long(shpc->w1cmask + SHPC_SERR_INT,
+ SHPC_CMD_DETECTED |
+ SHPC_ARB_DETECTED);
+ for (i = 0; i < nslots; ++i) {
+ pci_set_byte(shpc->wmask +
+ SHPC_SLOT_EVENT_SERR_INT_DIS(d, i),
+ SHPC_SLOT_EVENT_PRESENCE |
+ SHPC_SLOT_EVENT_ISOLATED_FAULT |
+ SHPC_SLOT_EVENT_BUTTON |
+ SHPC_SLOT_EVENT_MRL |
+ SHPC_SLOT_EVENT_CONNECTED_FAULT |
+ SHPC_SLOT_EVENT_MRL_SERR_DIS |
+ SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS);
+ pci_set_byte(shpc->w1cmask +
+ SHPC_SLOT_EVENT_LATCH(i),
+ SHPC_SLOT_EVENT_PRESENCE |
+ SHPC_SLOT_EVENT_ISOLATED_FAULT |
+ SHPC_SLOT_EVENT_BUTTON |
+ SHPC_SLOT_EVENT_MRL |
+ SHPC_SLOT_EVENT_CONNECTED_FAULT);
+ }
+
+ /* TODO: init cmask */
+ memory_region_init_io(&shpc->mmio, OBJECT(d), &shpc_mmio_ops,
+ d, "shpc-mmio", SHPC_SIZEOF(d));
+ shpc_cap_update_dword(d);
+ memory_region_add_subregion(bar, offset, &shpc->mmio);
+
+ qbus_set_hotplug_handler(BUS(sec_bus), DEVICE(d), NULL);
+
+ d->cap_present |= QEMU_PCI_CAP_SHPC;
+ return 0;
+}
+
+int shpc_bar_size(PCIDevice *d)
+{
+ return roundup_pow_of_two(SHPC_SLOT_REG(SHPC_MAX_SLOTS));
+}
+
+void shpc_cleanup(PCIDevice *d, MemoryRegion *bar)
+{
+ SHPCDevice *shpc = d->shpc;
+ d->cap_present &= ~QEMU_PCI_CAP_SHPC;
+ memory_region_del_subregion(bar, &shpc->mmio);
+ /* TODO: cleanup config space changes? */
+}
+
+void shpc_free(PCIDevice *d)
+{
+ SHPCDevice *shpc = d->shpc;
+ if (!shpc) {
+ return;
+ }
+ object_unparent(OBJECT(&shpc->mmio));
+ g_free(shpc->config);
+ g_free(shpc->cmask);
+ g_free(shpc->wmask);
+ g_free(shpc->w1cmask);
+ g_free(shpc);
+ d->shpc = NULL;
+}
+
+void shpc_cap_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l)
+{
+ if (!ranges_overlap(addr, l, d->shpc->cap, SHPC_CAP_LENGTH)) {
+ return;
+ }
+ if (ranges_overlap(addr, l, d->shpc->cap + SHPC_CAP_DWORD_DATA, 4)) {
+ unsigned dword_data;
+ dword_data = pci_get_long(d->shpc->config + d->shpc->cap
+ + SHPC_CAP_DWORD_DATA);
+ shpc_write(d, shpc_cap_dword(d) * 4, dword_data, 4);
+ }
+ /* Update cap dword data in case guest is going to read it. */
+ shpc_cap_update_dword(d);
+}
+
+static void shpc_save(QEMUFile *f, void *pv, size_t size)
+{
+ PCIDevice *d = container_of(pv, PCIDevice, shpc);
+ qemu_put_buffer(f, d->shpc->config, SHPC_SIZEOF(d));
+}
+
+static int shpc_load(QEMUFile *f, void *pv, size_t size)
+{
+ PCIDevice *d = container_of(pv, PCIDevice, shpc);
+ int ret = qemu_get_buffer(f, d->shpc->config, SHPC_SIZEOF(d));
+ if (ret != SHPC_SIZEOF(d)) {
+ return -EINVAL;
+ }
+ /* Make sure we don't lose notifications. An extra interrupt is harmless. */
+ d->shpc->msi_requested = 0;
+ shpc_interrupt_update(d);
+ return 0;
+}
+
+VMStateInfo shpc_vmstate_info = {
+ .name = "shpc",
+ .get = shpc_load,
+ .put = shpc_save,
+};
diff --git a/hw/pci/slotid_cap.c b/hw/pci/slotid_cap.c
new file mode 100644
index 00000000..1c01d346
--- /dev/null
+++ b/hw/pci/slotid_cap.c
@@ -0,0 +1,45 @@
+#include "hw/pci/slotid_cap.h"
+#include "hw/pci/pci.h"
+#include "qemu/error-report.h"
+
+#define SLOTID_CAP_LENGTH 4
+#define SLOTID_NSLOTS_SHIFT ctz32(PCI_SID_ESR_NSLOTS)
+
+int slotid_cap_init(PCIDevice *d, int nslots,
+ uint8_t chassis,
+ unsigned offset)
+{
+ int cap;
+ if (!chassis) {
+ error_report("Bridge chassis not specified. Each bridge is required "
+ "to be assigned a unique chassis id > 0.");
+ return -EINVAL;
+ }
+ if (nslots < 0 || nslots > (PCI_SID_ESR_NSLOTS >> SLOTID_NSLOTS_SHIFT)) {
+ /* TODO: error report? */
+ return -EINVAL;
+ }
+
+ cap = pci_add_capability(d, PCI_CAP_ID_SLOTID, offset, SLOTID_CAP_LENGTH);
+ if (cap < 0) {
+ return cap;
+ }
+ /* We make each chassis unique, this way each bridge is First in Chassis */
+ d->config[cap + PCI_SID_ESR] = PCI_SID_ESR_FIC |
+ (nslots << SLOTID_NSLOTS_SHIFT);
+ d->cmask[cap + PCI_SID_ESR] = 0xff;
+ d->config[cap + PCI_SID_CHASSIS_NR] = chassis;
+ /* Note: Chassis number register is non-volatile,
+ so we don't reset it. */
+ /* TODO: store in eeprom? */
+ d->wmask[cap + PCI_SID_CHASSIS_NR] = 0xff;
+
+ d->cap_present |= QEMU_PCI_CAP_SLOTID;
+ return 0;
+}
+
+void slotid_cap_cleanup(PCIDevice *d)
+{
+ /* TODO: cleanup config space? */
+ d->cap_present &= ~QEMU_PCI_CAP_SLOTID;
+}
diff --git a/hw/pcmcia/Makefile.objs b/hw/pcmcia/Makefile.objs
new file mode 100644
index 00000000..4eac060c
--- /dev/null
+++ b/hw/pcmcia/Makefile.objs
@@ -0,0 +1,2 @@
+common-obj-y += pcmcia.o
+obj-$(CONFIG_PXA2XX) += pxa2xx.o
diff --git a/hw/pcmcia/pcmcia.c b/hw/pcmcia/pcmcia.c
new file mode 100644
index 00000000..78efe5a6
--- /dev/null
+++ b/hw/pcmcia/pcmcia.c
@@ -0,0 +1,24 @@
+/*
+ * PCMCIA emulation
+ *
+ * Copyright 2013 SUSE LINUX Products GmbH
+ */
+
+#include "qemu-common.h"
+#include "hw/hw.h"
+#include "hw/pcmcia.h"
+
+static const TypeInfo pcmcia_card_type_info = {
+ .name = TYPE_PCMCIA_CARD,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(PCMCIACardState),
+ .abstract = true,
+ .class_size = sizeof(PCMCIACardClass),
+};
+
+static void pcmcia_register_types(void)
+{
+ type_register_static(&pcmcia_card_type_info);
+}
+
+type_init(pcmcia_register_types)
diff --git a/hw/pcmcia/pxa2xx.c b/hw/pcmcia/pxa2xx.c
new file mode 100644
index 00000000..812716e1
--- /dev/null
+++ b/hw/pcmcia/pxa2xx.c
@@ -0,0 +1,264 @@
+/*
+ * Intel XScale PXA255/270 PC Card and CompactFlash Interface.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/pcmcia.h"
+#include "hw/arm/pxa.h"
+
+#define TYPE_PXA2XX_PCMCIA "pxa2xx-pcmcia"
+#define PXA2XX_PCMCIA(obj) \
+ OBJECT_CHECK(PXA2xxPCMCIAState, obj, TYPE_PXA2XX_PCMCIA)
+
+struct PXA2xxPCMCIAState {
+ SysBusDevice parent_obj;
+
+ PCMCIASocket slot;
+ MemoryRegion container_mem;
+ MemoryRegion common_iomem;
+ MemoryRegion attr_iomem;
+ MemoryRegion iomem;
+
+ qemu_irq irq;
+ qemu_irq cd_irq;
+
+ PCMCIACardState *card;
+};
+
+static uint64_t pxa2xx_pcmcia_common_read(void *opaque,
+ hwaddr offset, unsigned size)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ PCMCIACardClass *pcc;
+
+ if (s->slot.attached) {
+ pcc = PCMCIA_CARD_GET_CLASS(s->card);
+ return pcc->common_read(s->card, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_pcmcia_common_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ PCMCIACardClass *pcc;
+
+ if (s->slot.attached) {
+ pcc = PCMCIA_CARD_GET_CLASS(s->card);
+ pcc->common_write(s->card, offset, value);
+ }
+}
+
+static uint64_t pxa2xx_pcmcia_attr_read(void *opaque,
+ hwaddr offset, unsigned size)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ PCMCIACardClass *pcc;
+
+ if (s->slot.attached) {
+ pcc = PCMCIA_CARD_GET_CLASS(s->card);
+ return pcc->attr_read(s->card, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_pcmcia_attr_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ PCMCIACardClass *pcc;
+
+ if (s->slot.attached) {
+ pcc = PCMCIA_CARD_GET_CLASS(s->card);
+ pcc->attr_write(s->card, offset, value);
+ }
+}
+
+static uint64_t pxa2xx_pcmcia_io_read(void *opaque,
+ hwaddr offset, unsigned size)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ PCMCIACardClass *pcc;
+
+ if (s->slot.attached) {
+ pcc = PCMCIA_CARD_GET_CLASS(s->card);
+ return pcc->io_read(s->card, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_pcmcia_io_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ PCMCIACardClass *pcc;
+
+ if (s->slot.attached) {
+ pcc = PCMCIA_CARD_GET_CLASS(s->card);
+ pcc->io_write(s->card, offset, value);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_pcmcia_common_ops = {
+ .read = pxa2xx_pcmcia_common_read,
+ .write = pxa2xx_pcmcia_common_write,
+ .endianness = DEVICE_NATIVE_ENDIAN
+};
+
+static const MemoryRegionOps pxa2xx_pcmcia_attr_ops = {
+ .read = pxa2xx_pcmcia_attr_read,
+ .write = pxa2xx_pcmcia_attr_write,
+ .endianness = DEVICE_NATIVE_ENDIAN
+};
+
+static const MemoryRegionOps pxa2xx_pcmcia_io_ops = {
+ .read = pxa2xx_pcmcia_io_read,
+ .write = pxa2xx_pcmcia_io_write,
+ .endianness = DEVICE_NATIVE_ENDIAN
+};
+
+static void pxa2xx_pcmcia_set_irq(void *opaque, int line, int level)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ if (!s->irq)
+ return;
+
+ qemu_set_irq(s->irq, level);
+}
+
+PXA2xxPCMCIAState *pxa2xx_pcmcia_init(MemoryRegion *sysmem,
+ hwaddr base)
+{
+ DeviceState *dev;
+ PXA2xxPCMCIAState *s;
+
+ dev = qdev_create(NULL, TYPE_PXA2XX_PCMCIA);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+ s = PXA2XX_PCMCIA(dev);
+
+ qdev_init_nofail(dev);
+
+ return s;
+}
+
+static void pxa2xx_pcmcia_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ PXA2xxPCMCIAState *s = PXA2XX_PCMCIA(obj);
+
+ memory_region_init(&s->container_mem, obj, "container", 0x10000000);
+ sysbus_init_mmio(sbd, &s->container_mem);
+
+ /* Socket I/O Memory Space */
+ memory_region_init_io(&s->iomem, obj, &pxa2xx_pcmcia_io_ops, s,
+ "pxa2xx-pcmcia-io", 0x04000000);
+ memory_region_add_subregion(&s->container_mem, 0x00000000,
+ &s->iomem);
+
+ /* Then next 64 MB is reserved */
+
+ /* Socket Attribute Memory Space */
+ memory_region_init_io(&s->attr_iomem, obj, &pxa2xx_pcmcia_attr_ops, s,
+ "pxa2xx-pcmcia-attribute", 0x04000000);
+ memory_region_add_subregion(&s->container_mem, 0x08000000,
+ &s->attr_iomem);
+
+ /* Socket Common Memory Space */
+ memory_region_init_io(&s->common_iomem, obj, &pxa2xx_pcmcia_common_ops, s,
+ "pxa2xx-pcmcia-common", 0x04000000);
+ memory_region_add_subregion(&s->container_mem, 0x0c000000,
+ &s->common_iomem);
+
+ s->slot.irq = qemu_allocate_irq(pxa2xx_pcmcia_set_irq, s, 0);
+
+ object_property_add_link(obj, "card", TYPE_PCMCIA_CARD,
+ (Object **)&s->card,
+ NULL, /* read-only property */
+ 0, NULL);
+}
+
+/* Insert a new card into a slot */
+int pxa2xx_pcmcia_attach(void *opaque, PCMCIACardState *card)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ PCMCIACardClass *pcc;
+
+ if (s->slot.attached) {
+ return -EEXIST;
+ }
+
+ if (s->cd_irq) {
+ qemu_irq_raise(s->cd_irq);
+ }
+
+ s->card = card;
+ pcc = PCMCIA_CARD_GET_CLASS(s->card);
+
+ s->slot.attached = true;
+ s->card->slot = &s->slot;
+ pcc->attach(s->card);
+
+ return 0;
+}
+
+/* Eject card from the slot */
+int pxa2xx_pcmcia_detach(void *opaque)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ PCMCIACardClass *pcc;
+
+ if (!s->slot.attached) {
+ return -ENOENT;
+ }
+
+ pcc = PCMCIA_CARD_GET_CLASS(s->card);
+ pcc->detach(s->card);
+ s->card->slot = NULL;
+ s->card = NULL;
+
+ s->slot.attached = false;
+
+ if (s->irq) {
+ qemu_irq_lower(s->irq);
+ }
+ if (s->cd_irq) {
+ qemu_irq_lower(s->cd_irq);
+ }
+
+ return 0;
+}
+
+/* Who to notify on card events */
+void pxa2xx_pcmcia_set_irq_cb(void *opaque, qemu_irq irq, qemu_irq cd_irq)
+{
+ PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque;
+ s->irq = irq;
+ s->cd_irq = cd_irq;
+}
+
+static const TypeInfo pxa2xx_pcmcia_type_info = {
+ .name = TYPE_PXA2XX_PCMCIA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxPCMCIAState),
+ .instance_init = pxa2xx_pcmcia_initfn,
+};
+
+static void pxa2xx_pcmcia_register_types(void)
+{
+ type_register_static(&pxa2xx_pcmcia_type_info);
+}
+
+type_init(pxa2xx_pcmcia_register_types)
diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs
new file mode 100644
index 00000000..c8ab06e7
--- /dev/null
+++ b/hw/ppc/Makefile.objs
@@ -0,0 +1,23 @@
+# shared objects
+obj-y += ppc.o ppc_booke.o
+# IBM pSeries (sPAPR)
+obj-$(CONFIG_PSERIES) += spapr.o spapr_vio.o spapr_events.o
+obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_iommu.o spapr_rtas.o
+obj-$(CONFIG_PSERIES) += spapr_pci.o spapr_rtc.o spapr_drc.o
+ifeq ($(CONFIG_PCI)$(CONFIG_PSERIES)$(CONFIG_LINUX), yyy)
+obj-y += spapr_pci_vfio.o
+endif
+# PowerPC 4xx boards
+obj-y += ppc405_boards.o ppc4xx_devs.o ppc405_uc.o ppc440_bamboo.o
+obj-y += ppc4xx_pci.o
+# PReP
+obj-$(CONFIG_PREP) += prep.o
+# OldWorld PowerMac
+obj-$(CONFIG_MAC) += mac_oldworld.o
+# NewWorld PowerMac
+obj-$(CONFIG_MAC) += mac_newworld.o
+# e500
+obj-$(CONFIG_E500) += e500.o mpc8544ds.o e500plat.o
+obj-$(CONFIG_E500) += mpc8544_guts.o ppce500_spin.o
+# PowerPC 440 Xilinx ML507 reference board.
+obj-$(CONFIG_XILINX) += virtex_ml507.o
diff --git a/hw/ppc/e500-ccsr.h b/hw/ppc/e500-ccsr.h
new file mode 100644
index 00000000..12a2ba4b
--- /dev/null
+++ b/hw/ppc/e500-ccsr.h
@@ -0,0 +1,17 @@
+#ifndef E500_CCSR_H
+#define E500_CCSR_H
+
+#include "hw/sysbus.h"
+
+typedef struct PPCE500CCSRState {
+ /*< private >*/
+ SysBusDevice parent;
+ /*< public >*/
+
+ MemoryRegion ccsr_space;
+} PPCE500CCSRState;
+
+#define TYPE_CCSR "e500-ccsr"
+#define CCSR(obj) OBJECT_CHECK(PPCE500CCSRState, (obj), TYPE_CCSR)
+
+#endif /* E500_CCSR_H */
diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c
new file mode 100644
index 00000000..d300846c
--- /dev/null
+++ b/hw/ppc/e500.c
@@ -0,0 +1,1085 @@
+/*
+ * QEMU PowerPC e500-based platforms
+ *
+ * Copyright (C) 2009 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Yu Liu, <yu.liu@freescale.com>
+ *
+ * This file is derived from hw/ppc440_bamboo.c,
+ * the copyright for that material belongs to the original owners.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "config.h"
+#include "qemu-common.h"
+#include "e500.h"
+#include "e500-ccsr.h"
+#include "net/net.h"
+#include "qemu/config-file.h"
+#include "hw/hw.h"
+#include "hw/char/serial.h"
+#include "hw/pci/pci.h"
+#include "hw/boards.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+#include "sysemu/device_tree.h"
+#include "hw/ppc/openpic.h"
+#include "hw/ppc/ppc.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "qemu/host-utils.h"
+#include "hw/pci-host/ppce500.h"
+#include "qemu/error-report.h"
+#include "hw/platform-bus.h"
+#include "hw/net/fsl_etsec/etsec.h"
+
+#define EPAPR_MAGIC (0x45504150)
+#define BINARY_DEVICE_TREE_FILE "mpc8544ds.dtb"
+#define DTC_LOAD_PAD 0x1800000
+#define DTC_PAD_MASK 0xFFFFF
+#define DTB_MAX_SIZE (8 * 1024 * 1024)
+#define INITRD_LOAD_PAD 0x2000000
+#define INITRD_PAD_MASK 0xFFFFFF
+
+#define RAM_SIZES_ALIGN (64UL << 20)
+
+/* TODO: parameterize */
+#define MPC8544_CCSRBAR_SIZE 0x00100000ULL
+#define MPC8544_MPIC_REGS_OFFSET 0x40000ULL
+#define MPC8544_MSI_REGS_OFFSET 0x41600ULL
+#define MPC8544_SERIAL0_REGS_OFFSET 0x4500ULL
+#define MPC8544_SERIAL1_REGS_OFFSET 0x4600ULL
+#define MPC8544_PCI_REGS_OFFSET 0x8000ULL
+#define MPC8544_PCI_REGS_SIZE 0x1000ULL
+#define MPC8544_UTIL_OFFSET 0xe0000ULL
+#define MPC8XXX_GPIO_OFFSET 0x000FF000ULL
+#define MPC8XXX_GPIO_IRQ 47
+
+struct boot_info
+{
+ uint32_t dt_base;
+ uint32_t dt_size;
+ uint32_t entry;
+};
+
+static uint32_t *pci_map_create(void *fdt, uint32_t mpic, int first_slot,
+ int nr_slots, int *len)
+{
+ int i = 0;
+ int slot;
+ int pci_irq;
+ int host_irq;
+ int last_slot = first_slot + nr_slots;
+ uint32_t *pci_map;
+
+ *len = nr_slots * 4 * 7 * sizeof(uint32_t);
+ pci_map = g_malloc(*len);
+
+ for (slot = first_slot; slot < last_slot; slot++) {
+ for (pci_irq = 0; pci_irq < 4; pci_irq++) {
+ pci_map[i++] = cpu_to_be32(slot << 11);
+ pci_map[i++] = cpu_to_be32(0x0);
+ pci_map[i++] = cpu_to_be32(0x0);
+ pci_map[i++] = cpu_to_be32(pci_irq + 1);
+ pci_map[i++] = cpu_to_be32(mpic);
+ host_irq = ppce500_pci_map_irq_slot(slot, pci_irq);
+ pci_map[i++] = cpu_to_be32(host_irq + 1);
+ pci_map[i++] = cpu_to_be32(0x1);
+ }
+ }
+
+ assert((i * sizeof(uint32_t)) == *len);
+
+ return pci_map;
+}
+
+static void dt_serial_create(void *fdt, unsigned long long offset,
+ const char *soc, const char *mpic,
+ const char *alias, int idx, bool defcon)
+{
+ char ser[128];
+
+ snprintf(ser, sizeof(ser), "%s/serial@%llx", soc, offset);
+ qemu_fdt_add_subnode(fdt, ser);
+ qemu_fdt_setprop_string(fdt, ser, "device_type", "serial");
+ qemu_fdt_setprop_string(fdt, ser, "compatible", "ns16550");
+ qemu_fdt_setprop_cells(fdt, ser, "reg", offset, 0x100);
+ qemu_fdt_setprop_cell(fdt, ser, "cell-index", idx);
+ qemu_fdt_setprop_cell(fdt, ser, "clock-frequency", 0);
+ qemu_fdt_setprop_cells(fdt, ser, "interrupts", 42, 2);
+ qemu_fdt_setprop_phandle(fdt, ser, "interrupt-parent", mpic);
+ qemu_fdt_setprop_string(fdt, "/aliases", alias, ser);
+
+ if (defcon) {
+ qemu_fdt_setprop_string(fdt, "/chosen", "linux,stdout-path", ser);
+ }
+}
+
+static void create_dt_mpc8xxx_gpio(void *fdt, const char *soc, const char *mpic)
+{
+ hwaddr mmio0 = MPC8XXX_GPIO_OFFSET;
+ int irq0 = MPC8XXX_GPIO_IRQ;
+ gchar *node = g_strdup_printf("%s/gpio@%"PRIx64, soc, mmio0);
+ gchar *poweroff = g_strdup_printf("%s/power-off", soc);
+ int gpio_ph;
+
+ qemu_fdt_add_subnode(fdt, node);
+ qemu_fdt_setprop_string(fdt, node, "compatible", "fsl,qoriq-gpio");
+ qemu_fdt_setprop_cells(fdt, node, "reg", mmio0, 0x1000);
+ qemu_fdt_setprop_cells(fdt, node, "interrupts", irq0, 0x2);
+ qemu_fdt_setprop_phandle(fdt, node, "interrupt-parent", mpic);
+ qemu_fdt_setprop_cells(fdt, node, "#gpio-cells", 2);
+ qemu_fdt_setprop(fdt, node, "gpio-controller", NULL, 0);
+ gpio_ph = qemu_fdt_alloc_phandle(fdt);
+ qemu_fdt_setprop_cell(fdt, node, "phandle", gpio_ph);
+ qemu_fdt_setprop_cell(fdt, node, "linux,phandle", gpio_ph);
+
+ /* Power Off Pin */
+ qemu_fdt_add_subnode(fdt, poweroff);
+ qemu_fdt_setprop_string(fdt, poweroff, "compatible", "gpio-poweroff");
+ qemu_fdt_setprop_cells(fdt, poweroff, "gpios", gpio_ph, 0, 0);
+
+ g_free(node);
+ g_free(poweroff);
+}
+
+typedef struct PlatformDevtreeData {
+ void *fdt;
+ const char *mpic;
+ int irq_start;
+ const char *node;
+ PlatformBusDevice *pbus;
+} PlatformDevtreeData;
+
+static int create_devtree_etsec(SysBusDevice *sbdev, PlatformDevtreeData *data)
+{
+ eTSEC *etsec = ETSEC_COMMON(sbdev);
+ PlatformBusDevice *pbus = data->pbus;
+ hwaddr mmio0 = platform_bus_get_mmio_addr(pbus, sbdev, 0);
+ int irq0 = platform_bus_get_irqn(pbus, sbdev, 0);
+ int irq1 = platform_bus_get_irqn(pbus, sbdev, 1);
+ int irq2 = platform_bus_get_irqn(pbus, sbdev, 2);
+ gchar *node = g_strdup_printf("/platform/ethernet@%"PRIx64, mmio0);
+ gchar *group = g_strdup_printf("%s/queue-group", node);
+ void *fdt = data->fdt;
+
+ assert((int64_t)mmio0 >= 0);
+ assert(irq0 >= 0);
+ assert(irq1 >= 0);
+ assert(irq2 >= 0);
+
+ qemu_fdt_add_subnode(fdt, node);
+ qemu_fdt_setprop_string(fdt, node, "device_type", "network");
+ qemu_fdt_setprop_string(fdt, node, "compatible", "fsl,etsec2");
+ qemu_fdt_setprop_string(fdt, node, "model", "eTSEC");
+ qemu_fdt_setprop(fdt, node, "local-mac-address", etsec->conf.macaddr.a, 6);
+ qemu_fdt_setprop_cells(fdt, node, "fixed-link", 0, 1, 1000, 0, 0);
+
+ qemu_fdt_add_subnode(fdt, group);
+ qemu_fdt_setprop_cells(fdt, group, "reg", mmio0, 0x1000);
+ qemu_fdt_setprop_cells(fdt, group, "interrupts",
+ data->irq_start + irq0, 0x2,
+ data->irq_start + irq1, 0x2,
+ data->irq_start + irq2, 0x2);
+
+ g_free(node);
+ g_free(group);
+
+ return 0;
+}
+
+static int sysbus_device_create_devtree(SysBusDevice *sbdev, void *opaque)
+{
+ PlatformDevtreeData *data = opaque;
+ bool matched = false;
+
+ if (object_dynamic_cast(OBJECT(sbdev), TYPE_ETSEC_COMMON)) {
+ create_devtree_etsec(sbdev, data);
+ matched = true;
+ }
+
+ if (!matched) {
+ error_report("Device %s is not supported by this machine yet.",
+ qdev_fw_name(DEVICE(sbdev)));
+ exit(1);
+ }
+
+ return 0;
+}
+
+static void platform_bus_create_devtree(PPCE500Params *params, void *fdt,
+ const char *mpic)
+{
+ gchar *node = g_strdup_printf("/platform@%"PRIx64, params->platform_bus_base);
+ const char platcomp[] = "qemu,platform\0simple-bus";
+ uint64_t addr = params->platform_bus_base;
+ uint64_t size = params->platform_bus_size;
+ int irq_start = params->platform_bus_first_irq;
+ PlatformBusDevice *pbus;
+ DeviceState *dev;
+
+ /* Create a /platform node that we can put all devices into */
+
+ qemu_fdt_add_subnode(fdt, node);
+ qemu_fdt_setprop(fdt, node, "compatible", platcomp, sizeof(platcomp));
+
+ /* Our platform bus region is less than 32bit big, so 1 cell is enough for
+ address and size */
+ qemu_fdt_setprop_cells(fdt, node, "#size-cells", 1);
+ qemu_fdt_setprop_cells(fdt, node, "#address-cells", 1);
+ qemu_fdt_setprop_cells(fdt, node, "ranges", 0, addr >> 32, addr, size);
+
+ qemu_fdt_setprop_phandle(fdt, node, "interrupt-parent", mpic);
+
+ dev = qdev_find_recursive(sysbus_get_default(), TYPE_PLATFORM_BUS_DEVICE);
+ pbus = PLATFORM_BUS_DEVICE(dev);
+
+ /* We can only create dt nodes for dynamic devices when they're ready */
+ if (pbus->done_gathering) {
+ PlatformDevtreeData data = {
+ .fdt = fdt,
+ .mpic = mpic,
+ .irq_start = irq_start,
+ .node = node,
+ .pbus = pbus,
+ };
+
+ /* Loop through all dynamic sysbus devices and create nodes for them */
+ foreach_dynamic_sysbus_device(sysbus_device_create_devtree, &data);
+ }
+
+ g_free(node);
+}
+
+static int ppce500_load_device_tree(MachineState *machine,
+ PPCE500Params *params,
+ hwaddr addr,
+ hwaddr initrd_base,
+ hwaddr initrd_size,
+ hwaddr kernel_base,
+ hwaddr kernel_size,
+ bool dry_run)
+{
+ CPUPPCState *env = first_cpu->env_ptr;
+ int ret = -1;
+ uint64_t mem_reg_property[] = { 0, cpu_to_be64(machine->ram_size) };
+ int fdt_size;
+ void *fdt;
+ uint8_t hypercall[16];
+ uint32_t clock_freq = 400000000;
+ uint32_t tb_freq = 400000000;
+ int i;
+ char compatible_sb[] = "fsl,mpc8544-immr\0simple-bus";
+ char soc[128];
+ char mpic[128];
+ uint32_t mpic_ph;
+ uint32_t msi_ph;
+ char gutil[128];
+ char pci[128];
+ char msi[128];
+ uint32_t *pci_map = NULL;
+ int len;
+ uint32_t pci_ranges[14] =
+ {
+ 0x2000000, 0x0, params->pci_mmio_bus_base,
+ params->pci_mmio_base >> 32, params->pci_mmio_base,
+ 0x0, 0x20000000,
+
+ 0x1000000, 0x0, 0x0,
+ params->pci_pio_base >> 32, params->pci_pio_base,
+ 0x0, 0x10000,
+ };
+ QemuOpts *machine_opts = qemu_get_machine_opts();
+ const char *dtb_file = qemu_opt_get(machine_opts, "dtb");
+ const char *toplevel_compat = qemu_opt_get(machine_opts, "dt_compatible");
+
+ if (dtb_file) {
+ char *filename;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, dtb_file);
+ if (!filename) {
+ goto out;
+ }
+
+ fdt = load_device_tree(filename, &fdt_size);
+ g_free(filename);
+ if (!fdt) {
+ goto out;
+ }
+ goto done;
+ }
+
+ fdt = create_device_tree(&fdt_size);
+ if (fdt == NULL) {
+ goto out;
+ }
+
+ /* Manipulate device tree in memory. */
+ qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 2);
+ qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 2);
+
+ qemu_fdt_add_subnode(fdt, "/memory");
+ qemu_fdt_setprop_string(fdt, "/memory", "device_type", "memory");
+ qemu_fdt_setprop(fdt, "/memory", "reg", mem_reg_property,
+ sizeof(mem_reg_property));
+
+ qemu_fdt_add_subnode(fdt, "/chosen");
+ if (initrd_size) {
+ ret = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-start",
+ initrd_base);
+ if (ret < 0) {
+ fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n");
+ }
+
+ ret = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-end",
+ (initrd_base + initrd_size));
+ if (ret < 0) {
+ fprintf(stderr, "couldn't set /chosen/linux,initrd-end\n");
+ }
+
+ }
+
+ if (kernel_base != -1ULL) {
+ qemu_fdt_setprop_cells(fdt, "/chosen", "qemu,boot-kernel",
+ kernel_base >> 32, kernel_base,
+ kernel_size >> 32, kernel_size);
+ }
+
+ ret = qemu_fdt_setprop_string(fdt, "/chosen", "bootargs",
+ machine->kernel_cmdline);
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /chosen/bootargs\n");
+
+ if (kvm_enabled()) {
+ /* Read out host's frequencies */
+ clock_freq = kvmppc_get_clockfreq();
+ tb_freq = kvmppc_get_tbfreq();
+
+ /* indicate KVM hypercall interface */
+ qemu_fdt_add_subnode(fdt, "/hypervisor");
+ qemu_fdt_setprop_string(fdt, "/hypervisor", "compatible",
+ "linux,kvm");
+ kvmppc_get_hypercall(env, hypercall, sizeof(hypercall));
+ qemu_fdt_setprop(fdt, "/hypervisor", "hcall-instructions",
+ hypercall, sizeof(hypercall));
+ /* if KVM supports the idle hcall, set property indicating this */
+ if (kvmppc_get_hasidle(env)) {
+ qemu_fdt_setprop(fdt, "/hypervisor", "has-idle", NULL, 0);
+ }
+ }
+
+ /* Create CPU nodes */
+ qemu_fdt_add_subnode(fdt, "/cpus");
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 1);
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0);
+
+ /* We need to generate the cpu nodes in reverse order, so Linux can pick
+ the first node as boot node and be happy */
+ for (i = smp_cpus - 1; i >= 0; i--) {
+ CPUState *cpu;
+ PowerPCCPU *pcpu;
+ char cpu_name[128];
+ uint64_t cpu_release_addr = params->spin_base + (i * 0x20);
+
+ cpu = qemu_get_cpu(i);
+ if (cpu == NULL) {
+ continue;
+ }
+ env = cpu->env_ptr;
+ pcpu = POWERPC_CPU(cpu);
+
+ snprintf(cpu_name, sizeof(cpu_name), "/cpus/PowerPC,8544@%x",
+ ppc_get_vcpu_dt_id(pcpu));
+ qemu_fdt_add_subnode(fdt, cpu_name);
+ qemu_fdt_setprop_cell(fdt, cpu_name, "clock-frequency", clock_freq);
+ qemu_fdt_setprop_cell(fdt, cpu_name, "timebase-frequency", tb_freq);
+ qemu_fdt_setprop_string(fdt, cpu_name, "device_type", "cpu");
+ qemu_fdt_setprop_cell(fdt, cpu_name, "reg",
+ ppc_get_vcpu_dt_id(pcpu));
+ qemu_fdt_setprop_cell(fdt, cpu_name, "d-cache-line-size",
+ env->dcache_line_size);
+ qemu_fdt_setprop_cell(fdt, cpu_name, "i-cache-line-size",
+ env->icache_line_size);
+ qemu_fdt_setprop_cell(fdt, cpu_name, "d-cache-size", 0x8000);
+ qemu_fdt_setprop_cell(fdt, cpu_name, "i-cache-size", 0x8000);
+ qemu_fdt_setprop_cell(fdt, cpu_name, "bus-frequency", 0);
+ if (cpu->cpu_index) {
+ qemu_fdt_setprop_string(fdt, cpu_name, "status", "disabled");
+ qemu_fdt_setprop_string(fdt, cpu_name, "enable-method",
+ "spin-table");
+ qemu_fdt_setprop_u64(fdt, cpu_name, "cpu-release-addr",
+ cpu_release_addr);
+ } else {
+ qemu_fdt_setprop_string(fdt, cpu_name, "status", "okay");
+ }
+ }
+
+ qemu_fdt_add_subnode(fdt, "/aliases");
+ /* XXX These should go into their respective devices' code */
+ snprintf(soc, sizeof(soc), "/soc@%"PRIx64, params->ccsrbar_base);
+ qemu_fdt_add_subnode(fdt, soc);
+ qemu_fdt_setprop_string(fdt, soc, "device_type", "soc");
+ qemu_fdt_setprop(fdt, soc, "compatible", compatible_sb,
+ sizeof(compatible_sb));
+ qemu_fdt_setprop_cell(fdt, soc, "#address-cells", 1);
+ qemu_fdt_setprop_cell(fdt, soc, "#size-cells", 1);
+ qemu_fdt_setprop_cells(fdt, soc, "ranges", 0x0,
+ params->ccsrbar_base >> 32, params->ccsrbar_base,
+ MPC8544_CCSRBAR_SIZE);
+ /* XXX should contain a reasonable value */
+ qemu_fdt_setprop_cell(fdt, soc, "bus-frequency", 0);
+
+ snprintf(mpic, sizeof(mpic), "%s/pic@%llx", soc, MPC8544_MPIC_REGS_OFFSET);
+ qemu_fdt_add_subnode(fdt, mpic);
+ qemu_fdt_setprop_string(fdt, mpic, "device_type", "open-pic");
+ qemu_fdt_setprop_string(fdt, mpic, "compatible", "fsl,mpic");
+ qemu_fdt_setprop_cells(fdt, mpic, "reg", MPC8544_MPIC_REGS_OFFSET,
+ 0x40000);
+ qemu_fdt_setprop_cell(fdt, mpic, "#address-cells", 0);
+ qemu_fdt_setprop_cell(fdt, mpic, "#interrupt-cells", 2);
+ mpic_ph = qemu_fdt_alloc_phandle(fdt);
+ qemu_fdt_setprop_cell(fdt, mpic, "phandle", mpic_ph);
+ qemu_fdt_setprop_cell(fdt, mpic, "linux,phandle", mpic_ph);
+ qemu_fdt_setprop(fdt, mpic, "interrupt-controller", NULL, 0);
+
+ /*
+ * We have to generate ser1 first, because Linux takes the first
+ * device it finds in the dt as serial output device. And we generate
+ * devices in reverse order to the dt.
+ */
+ if (serial_hds[1]) {
+ dt_serial_create(fdt, MPC8544_SERIAL1_REGS_OFFSET,
+ soc, mpic, "serial1", 1, false);
+ }
+
+ if (serial_hds[0]) {
+ dt_serial_create(fdt, MPC8544_SERIAL0_REGS_OFFSET,
+ soc, mpic, "serial0", 0, true);
+ }
+
+ snprintf(gutil, sizeof(gutil), "%s/global-utilities@%llx", soc,
+ MPC8544_UTIL_OFFSET);
+ qemu_fdt_add_subnode(fdt, gutil);
+ qemu_fdt_setprop_string(fdt, gutil, "compatible", "fsl,mpc8544-guts");
+ qemu_fdt_setprop_cells(fdt, gutil, "reg", MPC8544_UTIL_OFFSET, 0x1000);
+ qemu_fdt_setprop(fdt, gutil, "fsl,has-rstcr", NULL, 0);
+
+ snprintf(msi, sizeof(msi), "/%s/msi@%llx", soc, MPC8544_MSI_REGS_OFFSET);
+ qemu_fdt_add_subnode(fdt, msi);
+ qemu_fdt_setprop_string(fdt, msi, "compatible", "fsl,mpic-msi");
+ qemu_fdt_setprop_cells(fdt, msi, "reg", MPC8544_MSI_REGS_OFFSET, 0x200);
+ msi_ph = qemu_fdt_alloc_phandle(fdt);
+ qemu_fdt_setprop_cells(fdt, msi, "msi-available-ranges", 0x0, 0x100);
+ qemu_fdt_setprop_phandle(fdt, msi, "interrupt-parent", mpic);
+ qemu_fdt_setprop_cells(fdt, msi, "interrupts",
+ 0xe0, 0x0,
+ 0xe1, 0x0,
+ 0xe2, 0x0,
+ 0xe3, 0x0,
+ 0xe4, 0x0,
+ 0xe5, 0x0,
+ 0xe6, 0x0,
+ 0xe7, 0x0);
+ qemu_fdt_setprop_cell(fdt, msi, "phandle", msi_ph);
+ qemu_fdt_setprop_cell(fdt, msi, "linux,phandle", msi_ph);
+
+ snprintf(pci, sizeof(pci), "/pci@%llx",
+ params->ccsrbar_base + MPC8544_PCI_REGS_OFFSET);
+ qemu_fdt_add_subnode(fdt, pci);
+ qemu_fdt_setprop_cell(fdt, pci, "cell-index", 0);
+ qemu_fdt_setprop_string(fdt, pci, "compatible", "fsl,mpc8540-pci");
+ qemu_fdt_setprop_string(fdt, pci, "device_type", "pci");
+ qemu_fdt_setprop_cells(fdt, pci, "interrupt-map-mask", 0xf800, 0x0,
+ 0x0, 0x7);
+ pci_map = pci_map_create(fdt, qemu_fdt_get_phandle(fdt, mpic),
+ params->pci_first_slot, params->pci_nr_slots,
+ &len);
+ qemu_fdt_setprop(fdt, pci, "interrupt-map", pci_map, len);
+ qemu_fdt_setprop_phandle(fdt, pci, "interrupt-parent", mpic);
+ qemu_fdt_setprop_cells(fdt, pci, "interrupts", 24, 2);
+ qemu_fdt_setprop_cells(fdt, pci, "bus-range", 0, 255);
+ for (i = 0; i < 14; i++) {
+ pci_ranges[i] = cpu_to_be32(pci_ranges[i]);
+ }
+ qemu_fdt_setprop_cell(fdt, pci, "fsl,msi", msi_ph);
+ qemu_fdt_setprop(fdt, pci, "ranges", pci_ranges, sizeof(pci_ranges));
+ qemu_fdt_setprop_cells(fdt, pci, "reg",
+ (params->ccsrbar_base + MPC8544_PCI_REGS_OFFSET) >> 32,
+ (params->ccsrbar_base + MPC8544_PCI_REGS_OFFSET),
+ 0, 0x1000);
+ qemu_fdt_setprop_cell(fdt, pci, "clock-frequency", 66666666);
+ qemu_fdt_setprop_cell(fdt, pci, "#interrupt-cells", 1);
+ qemu_fdt_setprop_cell(fdt, pci, "#size-cells", 2);
+ qemu_fdt_setprop_cell(fdt, pci, "#address-cells", 3);
+ qemu_fdt_setprop_string(fdt, "/aliases", "pci0", pci);
+
+ if (params->has_mpc8xxx_gpio) {
+ create_dt_mpc8xxx_gpio(fdt, soc, mpic);
+ }
+
+ if (params->has_platform_bus) {
+ platform_bus_create_devtree(params, fdt, mpic);
+ }
+
+ params->fixup_devtree(params, fdt);
+
+ if (toplevel_compat) {
+ qemu_fdt_setprop(fdt, "/", "compatible", toplevel_compat,
+ strlen(toplevel_compat) + 1);
+ }
+
+done:
+ if (!dry_run) {
+ qemu_fdt_dumpdtb(fdt, fdt_size);
+ cpu_physical_memory_write(addr, fdt, fdt_size);
+ }
+ ret = fdt_size;
+
+out:
+ g_free(pci_map);
+
+ return ret;
+}
+
+typedef struct DeviceTreeParams {
+ MachineState *machine;
+ PPCE500Params params;
+ hwaddr addr;
+ hwaddr initrd_base;
+ hwaddr initrd_size;
+ hwaddr kernel_base;
+ hwaddr kernel_size;
+ Notifier notifier;
+} DeviceTreeParams;
+
+static void ppce500_reset_device_tree(void *opaque)
+{
+ DeviceTreeParams *p = opaque;
+ ppce500_load_device_tree(p->machine, &p->params, p->addr, p->initrd_base,
+ p->initrd_size, p->kernel_base, p->kernel_size,
+ false);
+}
+
+static void ppce500_init_notify(Notifier *notifier, void *data)
+{
+ DeviceTreeParams *p = container_of(notifier, DeviceTreeParams, notifier);
+ ppce500_reset_device_tree(p);
+}
+
+static int ppce500_prep_device_tree(MachineState *machine,
+ PPCE500Params *params,
+ hwaddr addr,
+ hwaddr initrd_base,
+ hwaddr initrd_size,
+ hwaddr kernel_base,
+ hwaddr kernel_size)
+{
+ DeviceTreeParams *p = g_new(DeviceTreeParams, 1);
+ p->machine = machine;
+ p->params = *params;
+ p->addr = addr;
+ p->initrd_base = initrd_base;
+ p->initrd_size = initrd_size;
+ p->kernel_base = kernel_base;
+ p->kernel_size = kernel_size;
+
+ qemu_register_reset(ppce500_reset_device_tree, p);
+ p->notifier.notify = ppce500_init_notify;
+ qemu_add_machine_init_done_notifier(&p->notifier);
+
+ /* Issue the device tree loader once, so that we get the size of the blob */
+ return ppce500_load_device_tree(machine, params, addr, initrd_base,
+ initrd_size, kernel_base, kernel_size,
+ true);
+}
+
+/* Create -kernel TLB entries for BookE. */
+static inline hwaddr booke206_page_size_to_tlb(uint64_t size)
+{
+ return 63 - clz64(size >> 10);
+}
+
+static int booke206_initial_map_tsize(CPUPPCState *env)
+{
+ struct boot_info *bi = env->load_info;
+ hwaddr dt_end;
+ int ps;
+
+ /* Our initial TLB entry needs to cover everything from 0 to
+ the device tree top */
+ dt_end = bi->dt_base + bi->dt_size;
+ ps = booke206_page_size_to_tlb(dt_end) + 1;
+ if (ps & 1) {
+ /* e500v2 can only do even TLB size bits */
+ ps++;
+ }
+ return ps;
+}
+
+static uint64_t mmubooke_initial_mapsize(CPUPPCState *env)
+{
+ int tsize;
+
+ tsize = booke206_initial_map_tsize(env);
+ return (1ULL << 10 << tsize);
+}
+
+static void mmubooke_create_initial_mapping(CPUPPCState *env)
+{
+ ppcmas_tlb_t *tlb = booke206_get_tlbm(env, 1, 0, 0);
+ hwaddr size;
+ int ps;
+
+ ps = booke206_initial_map_tsize(env);
+ size = (ps << MAS1_TSIZE_SHIFT);
+ tlb->mas1 = MAS1_VALID | size;
+ tlb->mas2 = 0;
+ tlb->mas7_3 = 0;
+ tlb->mas7_3 |= MAS3_UR | MAS3_UW | MAS3_UX | MAS3_SR | MAS3_SW | MAS3_SX;
+
+ env->tlb_dirty = true;
+}
+
+static void ppce500_cpu_reset_sec(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+
+ cpu_reset(cs);
+
+ /* Secondary CPU starts in halted state for now. Needs to change when
+ implementing non-kernel boot. */
+ cs->halted = 1;
+ cs->exception_index = EXCP_HLT;
+}
+
+static void ppce500_cpu_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+ CPUPPCState *env = &cpu->env;
+ struct boot_info *bi = env->load_info;
+
+ cpu_reset(cs);
+
+ /* Set initial guest state. */
+ cs->halted = 0;
+ env->gpr[1] = (16<<20) - 8;
+ env->gpr[3] = bi->dt_base;
+ env->gpr[4] = 0;
+ env->gpr[5] = 0;
+ env->gpr[6] = EPAPR_MAGIC;
+ env->gpr[7] = mmubooke_initial_mapsize(env);
+ env->gpr[8] = 0;
+ env->gpr[9] = 0;
+ env->nip = bi->entry;
+ mmubooke_create_initial_mapping(env);
+}
+
+static DeviceState *ppce500_init_mpic_qemu(PPCE500Params *params,
+ qemu_irq **irqs)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ int i, j, k;
+
+ dev = qdev_create(NULL, TYPE_OPENPIC);
+ qdev_prop_set_uint32(dev, "model", params->mpic_version);
+ qdev_prop_set_uint32(dev, "nb_cpus", smp_cpus);
+
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ k = 0;
+ for (i = 0; i < smp_cpus; i++) {
+ for (j = 0; j < OPENPIC_OUTPUT_NB; j++) {
+ sysbus_connect_irq(s, k++, irqs[i][j]);
+ }
+ }
+
+ return dev;
+}
+
+static DeviceState *ppce500_init_mpic_kvm(PPCE500Params *params,
+ qemu_irq **irqs, Error **errp)
+{
+ Error *err = NULL;
+ DeviceState *dev;
+ CPUState *cs;
+
+ dev = qdev_create(NULL, TYPE_KVM_OPENPIC);
+ qdev_prop_set_uint32(dev, "model", params->mpic_version);
+
+ object_property_set_bool(OBJECT(dev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+
+ CPU_FOREACH(cs) {
+ if (kvm_openpic_connect_vcpu(dev, cs)) {
+ fprintf(stderr, "%s: failed to connect vcpu to irqchip\n",
+ __func__);
+ abort();
+ }
+ }
+
+ return dev;
+}
+
+static qemu_irq *ppce500_init_mpic(MachineState *machine, PPCE500Params *params,
+ MemoryRegion *ccsr, qemu_irq **irqs)
+{
+ qemu_irq *mpic;
+ DeviceState *dev = NULL;
+ SysBusDevice *s;
+ int i;
+
+ mpic = g_new0(qemu_irq, 256);
+
+ if (kvm_enabled()) {
+ Error *err = NULL;
+
+ if (machine_kernel_irqchip_allowed(machine)) {
+ dev = ppce500_init_mpic_kvm(params, irqs, &err);
+ }
+ if (machine_kernel_irqchip_required(machine) && !dev) {
+ error_report("kernel_irqchip requested but unavailable: %s",
+ error_get_pretty(err));
+ exit(1);
+ }
+ }
+
+ if (!dev) {
+ dev = ppce500_init_mpic_qemu(params, irqs);
+ }
+
+ for (i = 0; i < 256; i++) {
+ mpic[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ s = SYS_BUS_DEVICE(dev);
+ memory_region_add_subregion(ccsr, MPC8544_MPIC_REGS_OFFSET,
+ s->mmio[0].memory);
+
+ return mpic;
+}
+
+static void ppce500_power_off(void *opaque, int line, int on)
+{
+ if (on) {
+ qemu_system_shutdown_request();
+ }
+}
+
+void ppce500_init(MachineState *machine, PPCE500Params *params)
+{
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ PCIBus *pci_bus;
+ CPUPPCState *env = NULL;
+ uint64_t loadaddr;
+ hwaddr kernel_base = -1LL;
+ int kernel_size = 0;
+ hwaddr dt_base = 0;
+ hwaddr initrd_base = 0;
+ int initrd_size = 0;
+ hwaddr cur_base = 0;
+ char *filename;
+ hwaddr bios_entry = 0;
+ target_long bios_size;
+ struct boot_info *boot_info;
+ int dt_size;
+ int i;
+ /* irq num for pin INTA, INTB, INTC and INTD is 1, 2, 3 and
+ * 4 respectively */
+ unsigned int pci_irq_nrs[PCI_NUM_PINS] = {1, 2, 3, 4};
+ qemu_irq **irqs, *mpic;
+ DeviceState *dev;
+ CPUPPCState *firstenv = NULL;
+ MemoryRegion *ccsr_addr_space;
+ SysBusDevice *s;
+ PPCE500CCSRState *ccsr;
+
+ /* Setup CPUs */
+ if (machine->cpu_model == NULL) {
+ machine->cpu_model = "e500v2_v30";
+ }
+
+ irqs = g_malloc0(smp_cpus * sizeof(qemu_irq *));
+ irqs[0] = g_malloc0(smp_cpus * sizeof(qemu_irq) * OPENPIC_OUTPUT_NB);
+ for (i = 0; i < smp_cpus; i++) {
+ PowerPCCPU *cpu;
+ CPUState *cs;
+ qemu_irq *input;
+
+ cpu = cpu_ppc_init(machine->cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to initialize CPU!\n");
+ exit(1);
+ }
+ env = &cpu->env;
+ cs = CPU(cpu);
+
+ if (!firstenv) {
+ firstenv = env;
+ }
+
+ irqs[i] = irqs[0] + (i * OPENPIC_OUTPUT_NB);
+ input = (qemu_irq *)env->irq_inputs;
+ irqs[i][OPENPIC_OUTPUT_INT] = input[PPCE500_INPUT_INT];
+ irqs[i][OPENPIC_OUTPUT_CINT] = input[PPCE500_INPUT_CINT];
+ env->spr_cb[SPR_BOOKE_PIR].default_value = cs->cpu_index = i;
+ env->mpic_iack = params->ccsrbar_base +
+ MPC8544_MPIC_REGS_OFFSET + 0xa0;
+
+ ppc_booke_timers_init(cpu, 400000000, PPC_TIMER_E500);
+
+ /* Register reset handler */
+ if (!i) {
+ /* Primary CPU */
+ struct boot_info *boot_info;
+ boot_info = g_malloc0(sizeof(struct boot_info));
+ qemu_register_reset(ppce500_cpu_reset, cpu);
+ env->load_info = boot_info;
+ } else {
+ /* Secondary CPUs */
+ qemu_register_reset(ppce500_cpu_reset_sec, cpu);
+ }
+ }
+
+ env = firstenv;
+
+ /* Fixup Memory size on a alignment boundary */
+ ram_size &= ~(RAM_SIZES_ALIGN - 1);
+ machine->ram_size = ram_size;
+
+ /* Register Memory */
+ memory_region_allocate_system_memory(ram, NULL, "mpc8544ds.ram", ram_size);
+ memory_region_add_subregion(address_space_mem, 0, ram);
+
+ dev = qdev_create(NULL, "e500-ccsr");
+ object_property_add_child(qdev_get_machine(), "e500-ccsr",
+ OBJECT(dev), NULL);
+ qdev_init_nofail(dev);
+ ccsr = CCSR(dev);
+ ccsr_addr_space = &ccsr->ccsr_space;
+ memory_region_add_subregion(address_space_mem, params->ccsrbar_base,
+ ccsr_addr_space);
+
+ mpic = ppce500_init_mpic(machine, params, ccsr_addr_space, irqs);
+
+ /* Serial */
+ if (serial_hds[0]) {
+ serial_mm_init(ccsr_addr_space, MPC8544_SERIAL0_REGS_OFFSET,
+ 0, mpic[42], 399193,
+ serial_hds[0], DEVICE_BIG_ENDIAN);
+ }
+
+ if (serial_hds[1]) {
+ serial_mm_init(ccsr_addr_space, MPC8544_SERIAL1_REGS_OFFSET,
+ 0, mpic[42], 399193,
+ serial_hds[1], DEVICE_BIG_ENDIAN);
+ }
+
+ /* General Utility device */
+ dev = qdev_create(NULL, "mpc8544-guts");
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ memory_region_add_subregion(ccsr_addr_space, MPC8544_UTIL_OFFSET,
+ sysbus_mmio_get_region(s, 0));
+
+ /* PCI */
+ dev = qdev_create(NULL, "e500-pcihost");
+ qdev_prop_set_uint32(dev, "first_slot", params->pci_first_slot);
+ qdev_prop_set_uint32(dev, "first_pin_irq", pci_irq_nrs[0]);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ for (i = 0; i < PCI_NUM_PINS; i++) {
+ sysbus_connect_irq(s, i, mpic[pci_irq_nrs[i]]);
+ }
+
+ memory_region_add_subregion(ccsr_addr_space, MPC8544_PCI_REGS_OFFSET,
+ sysbus_mmio_get_region(s, 0));
+
+ pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci.0");
+ if (!pci_bus)
+ printf("couldn't create PCI controller!\n");
+
+ if (pci_bus) {
+ /* Register network interfaces. */
+ for (i = 0; i < nb_nics; i++) {
+ pci_nic_init_nofail(&nd_table[i], pci_bus, "virtio", NULL);
+ }
+ }
+
+ /* Register spinning region */
+ sysbus_create_simple("e500-spin", params->spin_base, NULL);
+
+ if (cur_base < (32 * 1024 * 1024)) {
+ /* u-boot occupies memory up to 32MB, so load blobs above */
+ cur_base = (32 * 1024 * 1024);
+ }
+
+ if (params->has_mpc8xxx_gpio) {
+ qemu_irq poweroff_irq;
+
+ dev = qdev_create(NULL, "mpc8xxx_gpio");
+ s = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ sysbus_connect_irq(s, 0, mpic[MPC8XXX_GPIO_IRQ]);
+ memory_region_add_subregion(ccsr_addr_space, MPC8XXX_GPIO_OFFSET,
+ sysbus_mmio_get_region(s, 0));
+
+ /* Power Off GPIO at Pin 0 */
+ poweroff_irq = qemu_allocate_irq(ppce500_power_off, NULL, 0);
+ qdev_connect_gpio_out(dev, 0, poweroff_irq);
+ }
+
+ /* Platform Bus Device */
+ if (params->has_platform_bus) {
+ dev = qdev_create(NULL, TYPE_PLATFORM_BUS_DEVICE);
+ dev->id = TYPE_PLATFORM_BUS_DEVICE;
+ qdev_prop_set_uint32(dev, "num_irqs", params->platform_bus_num_irqs);
+ qdev_prop_set_uint32(dev, "mmio_size", params->platform_bus_size);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ for (i = 0; i < params->platform_bus_num_irqs; i++) {
+ int irqn = params->platform_bus_first_irq + i;
+ sysbus_connect_irq(s, i, mpic[irqn]);
+ }
+
+ memory_region_add_subregion(address_space_mem,
+ params->platform_bus_base,
+ sysbus_mmio_get_region(s, 0));
+ }
+
+ /* Load kernel. */
+ if (machine->kernel_filename) {
+ kernel_base = cur_base;
+ kernel_size = load_image_targphys(machine->kernel_filename,
+ cur_base,
+ ram_size - cur_base);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ machine->kernel_filename);
+ exit(1);
+ }
+
+ cur_base += kernel_size;
+ }
+
+ /* Load initrd. */
+ if (machine->initrd_filename) {
+ initrd_base = (cur_base + INITRD_LOAD_PAD) & ~INITRD_PAD_MASK;
+ initrd_size = load_image_targphys(machine->initrd_filename, initrd_base,
+ ram_size - initrd_base);
+
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ machine->initrd_filename);
+ exit(1);
+ }
+
+ cur_base = initrd_base + initrd_size;
+ }
+
+ /*
+ * Smart firmware defaults ahead!
+ *
+ * We follow the following table to select which payload we execute.
+ *
+ * -kernel | -bios | payload
+ * ---------+-------+---------
+ * N | Y | u-boot
+ * N | N | u-boot
+ * Y | Y | u-boot
+ * Y | N | kernel
+ *
+ * This ensures backwards compatibility with how we used to expose
+ * -kernel to users but allows them to run through u-boot as well.
+ */
+ if (bios_name == NULL) {
+ if (machine->kernel_filename) {
+ bios_name = machine->kernel_filename;
+ } else {
+ bios_name = "u-boot.e500";
+ }
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+
+ bios_size = load_elf(filename, NULL, NULL, &bios_entry, &loadaddr, NULL,
+ 1, ELF_MACHINE, 0);
+ if (bios_size < 0) {
+ /*
+ * Hrm. No ELF image? Try a uImage, maybe someone is giving us an
+ * ePAPR compliant kernel
+ */
+ kernel_size = load_uimage(filename, &bios_entry, &loadaddr, NULL,
+ NULL, NULL);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load firmware '%s'\n", filename);
+ exit(1);
+ }
+ }
+ g_free(filename);
+
+ /* Reserve space for dtb */
+ dt_base = (loadaddr + bios_size + DTC_LOAD_PAD) & ~DTC_PAD_MASK;
+
+ dt_size = ppce500_prep_device_tree(machine, params, dt_base,
+ initrd_base, initrd_size,
+ kernel_base, kernel_size);
+ if (dt_size < 0) {
+ fprintf(stderr, "couldn't load device tree\n");
+ exit(1);
+ }
+ assert(dt_size < DTB_MAX_SIZE);
+
+ boot_info = env->load_info;
+ boot_info->entry = bios_entry;
+ boot_info->dt_base = dt_base;
+ boot_info->dt_size = dt_size;
+
+ if (kvm_enabled()) {
+ kvmppc_init();
+ }
+}
+
+static int e500_ccsr_initfn(SysBusDevice *dev)
+{
+ PPCE500CCSRState *ccsr;
+
+ ccsr = CCSR(dev);
+ memory_region_init(&ccsr->ccsr_space, OBJECT(ccsr), "e500-ccsr",
+ MPC8544_CCSRBAR_SIZE);
+ return 0;
+}
+
+static void e500_ccsr_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ k->init = e500_ccsr_initfn;
+}
+
+static const TypeInfo e500_ccsr_info = {
+ .name = TYPE_CCSR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PPCE500CCSRState),
+ .class_init = e500_ccsr_class_init,
+};
+
+static void e500_register_types(void)
+{
+ type_register_static(&e500_ccsr_info);
+}
+
+type_init(e500_register_types)
diff --git a/hw/ppc/e500.h b/hw/ppc/e500.h
new file mode 100644
index 00000000..ef224ea5
--- /dev/null
+++ b/hw/ppc/e500.h
@@ -0,0 +1,29 @@
+#ifndef PPCE500_H
+#define PPCE500_H
+
+#include "hw/boards.h"
+
+typedef struct PPCE500Params {
+ int pci_first_slot;
+ int pci_nr_slots;
+
+ /* required -- must at least add toplevel board compatible */
+ void (*fixup_devtree)(struct PPCE500Params *params, void *fdt);
+
+ int mpic_version;
+ bool has_mpc8xxx_gpio;
+ bool has_platform_bus;
+ hwaddr platform_bus_base;
+ hwaddr platform_bus_size;
+ int platform_bus_first_irq;
+ int platform_bus_num_irqs;
+ hwaddr ccsrbar_base;
+ hwaddr pci_pio_base;
+ hwaddr pci_mmio_base;
+ hwaddr pci_mmio_bus_base;
+ hwaddr spin_base;
+} PPCE500Params;
+
+void ppce500_init(MachineState *machine, PPCE500Params *params);
+
+#endif
diff --git a/hw/ppc/e500plat.c b/hw/ppc/e500plat.c
new file mode 100644
index 00000000..14b14eaa
--- /dev/null
+++ b/hw/ppc/e500plat.c
@@ -0,0 +1,73 @@
+/*
+ * Generic device-tree-driven paravirt PPC e500 platform
+ *
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "config.h"
+#include "qemu-common.h"
+#include "e500.h"
+#include "hw/boards.h"
+#include "sysemu/device_tree.h"
+#include "hw/pci/pci.h"
+#include "hw/ppc/openpic.h"
+#include "kvm_ppc.h"
+
+static void e500plat_fixup_devtree(PPCE500Params *params, void *fdt)
+{
+ const char model[] = "QEMU ppce500";
+ const char compatible[] = "fsl,qemu-e500";
+
+ qemu_fdt_setprop(fdt, "/", "model", model, sizeof(model));
+ qemu_fdt_setprop(fdt, "/", "compatible", compatible,
+ sizeof(compatible));
+}
+
+static void e500plat_init(MachineState *machine)
+{
+ PPCE500Params params = {
+ .pci_first_slot = 0x1,
+ .pci_nr_slots = PCI_SLOT_MAX - 1,
+ .fixup_devtree = e500plat_fixup_devtree,
+ .mpic_version = OPENPIC_MODEL_FSL_MPIC_42,
+ .has_mpc8xxx_gpio = true,
+ .has_platform_bus = true,
+ .platform_bus_base = 0xf00000000ULL,
+ .platform_bus_size = (128ULL * 1024 * 1024),
+ .platform_bus_first_irq = 5,
+ .platform_bus_num_irqs = 10,
+ .ccsrbar_base = 0xFE0000000ULL,
+ .pci_pio_base = 0xFE1000000ULL,
+ .pci_mmio_base = 0xC00000000ULL,
+ .pci_mmio_bus_base = 0xE0000000ULL,
+ .spin_base = 0xFEF000000ULL,
+ };
+
+ /* Older KVM versions don't support EPR which breaks guests when we announce
+ MPIC variants that support EPR. Revert to an older one for those */
+ if (kvm_enabled() && !kvmppc_has_cap_epr()) {
+ params.mpic_version = OPENPIC_MODEL_FSL_MPIC_20;
+ }
+
+ ppce500_init(machine, &params);
+}
+
+static QEMUMachine e500plat_machine = {
+ .name = "ppce500",
+ .desc = "generic paravirt e500 platform",
+ .init = e500plat_init,
+ .max_cpus = 32,
+ .has_dynamic_sysbus = true,
+};
+
+static void e500plat_machine_init(void)
+{
+ qemu_register_machine(&e500plat_machine);
+}
+
+machine_init(e500plat_machine_init);
diff --git a/hw/ppc/mac.h b/hw/ppc/mac.h
new file mode 100644
index 00000000..8bdba30c
--- /dev/null
+++ b/hw/ppc/mac.h
@@ -0,0 +1,183 @@
+/*
+ * QEMU PowerMac emulation shared definitions and prototypes
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#if !defined(__PPC_MAC_H__)
+#define __PPC_MAC_H__
+
+#include "exec/memory.h"
+#include "hw/sysbus.h"
+#include "hw/ide/internal.h"
+#include "hw/input/adb.h"
+
+/* SMP is not enabled, for now */
+#define MAX_CPUS 1
+
+#define BIOS_SIZE (1024 * 1024)
+#define NVRAM_SIZE 0x2000
+#define PROM_FILENAME "openbios-ppc"
+#define PROM_ADDR 0xfff00000
+
+#define KERNEL_LOAD_ADDR 0x01000000
+#define KERNEL_GAP 0x00100000
+
+#define ESCC_CLOCK 3686400
+
+/* Cuda */
+#define TYPE_CUDA "cuda"
+#define CUDA(obj) OBJECT_CHECK(CUDAState, (obj), TYPE_CUDA)
+
+/**
+ * CUDATimer:
+ * @counter_value: counter value at load time
+ */
+typedef struct CUDATimer {
+ int index;
+ uint16_t latch;
+ uint16_t counter_value;
+ int64_t load_time;
+ int64_t next_irq_time;
+ uint64_t frequency;
+ QEMUTimer *timer;
+} CUDATimer;
+
+/**
+ * CUDAState:
+ * @b: B-side data
+ * @a: A-side data
+ * @dirb: B-side direction (1=output)
+ * @dira: A-side direction (1=output)
+ * @sr: Shift register
+ * @acr: Auxiliary control register
+ * @pcr: Peripheral control register
+ * @ifr: Interrupt flag register
+ * @ier: Interrupt enable register
+ * @anh: A-side data, no handshake
+ * @last_b: last value of B register
+ * @last_acr: last value of ACR register
+ */
+typedef struct CUDAState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion mem;
+ /* cuda registers */
+ uint8_t b;
+ uint8_t a;
+ uint8_t dirb;
+ uint8_t dira;
+ uint8_t sr;
+ uint8_t acr;
+ uint8_t pcr;
+ uint8_t ifr;
+ uint8_t ier;
+ uint8_t anh;
+
+ ADBBusState adb_bus;
+ CUDATimer timers[2];
+
+ uint32_t tick_offset;
+ uint64_t frequency;
+
+ uint8_t last_b;
+ uint8_t last_acr;
+
+ int data_in_size;
+ int data_in_index;
+ int data_out_index;
+
+ qemu_irq irq;
+ uint8_t autopoll;
+ uint8_t data_in[128];
+ uint8_t data_out[16];
+ QEMUTimer *adb_poll_timer;
+} CUDAState;
+
+/* MacIO */
+#define TYPE_OLDWORLD_MACIO "macio-oldworld"
+#define TYPE_NEWWORLD_MACIO "macio-newworld"
+
+#define TYPE_MACIO_IDE "macio-ide"
+#define MACIO_IDE(obj) OBJECT_CHECK(MACIOIDEState, (obj), TYPE_MACIO_IDE)
+
+typedef struct MACIOIDEState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ qemu_irq irq;
+ qemu_irq dma_irq;
+
+ MemoryRegion mem;
+ IDEBus bus;
+ BlockAIOCB *aiocb;
+ IDEDMA dma;
+ void *dbdma;
+ bool dma_active;
+} MACIOIDEState;
+
+void macio_ide_init_drives(MACIOIDEState *ide, DriveInfo **hd_table);
+void macio_ide_register_dma(MACIOIDEState *ide, void *dbdma, int channel);
+
+void macio_init(PCIDevice *dev,
+ MemoryRegion *pic_mem,
+ MemoryRegion *escc_mem);
+
+/* Heathrow PIC */
+qemu_irq *heathrow_pic_init(MemoryRegion **pmem,
+ int nb_cpus, qemu_irq **irqs);
+
+/* Grackle PCI */
+#define TYPE_GRACKLE_PCI_HOST_BRIDGE "grackle-pcihost"
+PCIBus *pci_grackle_init(uint32_t base, qemu_irq *pic,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io);
+
+/* UniNorth PCI */
+PCIBus *pci_pmac_init(qemu_irq *pic,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io);
+PCIBus *pci_pmac_u3_init(qemu_irq *pic,
+ MemoryRegion *address_space_mem,
+ MemoryRegion *address_space_io);
+
+/* Mac NVRAM */
+#define TYPE_MACIO_NVRAM "macio-nvram"
+#define MACIO_NVRAM(obj) \
+ OBJECT_CHECK(MacIONVRAMState, (obj), TYPE_MACIO_NVRAM)
+
+typedef struct MacIONVRAMState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ uint32_t size;
+ uint32_t it_shift;
+
+ MemoryRegion mem;
+ uint8_t *data;
+} MacIONVRAMState;
+
+void pmac_format_nvram_partition (MacIONVRAMState *nvr, int len);
+#endif /* !defined(__PPC_MAC_H__) */
diff --git a/hw/ppc/mac_newworld.c b/hw/ppc/mac_newworld.c
new file mode 100644
index 00000000..77d5c819
--- /dev/null
+++ b/hw/ppc/mac_newworld.c
@@ -0,0 +1,530 @@
+/*
+ * QEMU PowerPC CHRP (currently NewWorld PowerMac) hardware System Emulator
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ *
+ * PCI bus layout on a real G5 (U3 based):
+ *
+ * 0000:f0:0b.0 Host bridge [0600]: Apple Computer Inc. U3 AGP [106b:004b]
+ * 0000:f0:10.0 VGA compatible controller [0300]: ATI Technologies Inc RV350 AP [Radeon 9600] [1002:4150]
+ * 0001:00:00.0 Host bridge [0600]: Apple Computer Inc. CPC945 HT Bridge [106b:004a]
+ * 0001:00:01.0 PCI bridge [0604]: Advanced Micro Devices [AMD] AMD-8131 PCI-X Bridge [1022:7450] (rev 12)
+ * 0001:00:02.0 PCI bridge [0604]: Advanced Micro Devices [AMD] AMD-8131 PCI-X Bridge [1022:7450] (rev 12)
+ * 0001:00:03.0 PCI bridge [0604]: Apple Computer Inc. K2 HT-PCI Bridge [106b:0045]
+ * 0001:00:04.0 PCI bridge [0604]: Apple Computer Inc. K2 HT-PCI Bridge [106b:0046]
+ * 0001:00:05.0 PCI bridge [0604]: Apple Computer Inc. K2 HT-PCI Bridge [106b:0047]
+ * 0001:00:06.0 PCI bridge [0604]: Apple Computer Inc. K2 HT-PCI Bridge [106b:0048]
+ * 0001:00:07.0 PCI bridge [0604]: Apple Computer Inc. K2 HT-PCI Bridge [106b:0049]
+ * 0001:01:07.0 Class [ff00]: Apple Computer Inc. K2 KeyLargo Mac/IO [106b:0041] (rev 20)
+ * 0001:01:08.0 USB Controller [0c03]: Apple Computer Inc. K2 KeyLargo USB [106b:0040]
+ * 0001:01:09.0 USB Controller [0c03]: Apple Computer Inc. K2 KeyLargo USB [106b:0040]
+ * 0001:02:0b.0 USB Controller [0c03]: NEC Corporation USB [1033:0035] (rev 43)
+ * 0001:02:0b.1 USB Controller [0c03]: NEC Corporation USB [1033:0035] (rev 43)
+ * 0001:02:0b.2 USB Controller [0c03]: NEC Corporation USB 2.0 [1033:00e0] (rev 04)
+ * 0001:03:0d.0 Class [ff00]: Apple Computer Inc. K2 ATA/100 [106b:0043]
+ * 0001:03:0e.0 FireWire (IEEE 1394) [0c00]: Apple Computer Inc. K2 FireWire [106b:0042]
+ * 0001:04:0f.0 Ethernet controller [0200]: Apple Computer Inc. K2 GMAC (Sun GEM) [106b:004c]
+ * 0001:05:0c.0 IDE interface [0101]: Broadcom K2 SATA [1166:0240]
+ *
+ */
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "hw/ppc/mac.h"
+#include "hw/input/adb.h"
+#include "hw/ppc/mac_dbdma.h"
+#include "hw/timer/m48t59.h"
+#include "hw/pci/pci.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/char/escc.h"
+#include "hw/ppc/openpic.h"
+#include "hw/ide.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+#include "hw/usb.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "hw/sysbus.h"
+
+#define MAX_IDE_BUS 2
+#define CFG_ADDR 0xf0000510
+#define TBFREQ (100UL * 1000UL * 1000UL)
+#define CLOCKFREQ (266UL * 1000UL * 1000UL)
+#define BUSFREQ (100UL * 1000UL * 1000UL)
+
+/* debug UniNorth */
+//#define DEBUG_UNIN
+
+#ifdef DEBUG_UNIN
+#define UNIN_DPRINTF(fmt, ...) \
+ do { printf("UNIN: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define UNIN_DPRINTF(fmt, ...)
+#endif
+
+/* UniN device */
+static void unin_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ UNIN_DPRINTF("write addr " TARGET_FMT_plx " val %"PRIx64"\n", addr, value);
+ if (addr == 0x0) {
+ *(int*)opaque = value;
+ }
+}
+
+static uint64_t unin_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint32_t value;
+
+ value = 0;
+ switch (addr) {
+ case 0:
+ value = *(int*)opaque;
+ }
+
+ UNIN_DPRINTF("readl addr " TARGET_FMT_plx " val %x\n", addr, value);
+
+ return value;
+}
+
+static const MemoryRegionOps unin_ops = {
+ .read = unin_read,
+ .write = unin_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void fw_cfg_boot_set(void *opaque, const char *boot_device,
+ Error **errp)
+{
+ fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]);
+}
+
+static uint64_t translate_kernel_address(void *opaque, uint64_t addr)
+{
+ return (addr & 0x0fffffff) + KERNEL_LOAD_ADDR;
+}
+
+static hwaddr round_page(hwaddr addr)
+{
+ return (addr + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK;
+}
+
+static void ppc_core99_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+ /* 970 CPUs want to get their initial IP as part of their boot protocol */
+ cpu->env.nip = PROM_ADDR + 0x100;
+}
+
+/* PowerPC Mac99 hardware initialisation */
+static void ppc_core99_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ const char *boot_device = machine->boot_order;
+ PowerPCCPU *cpu = NULL;
+ CPUPPCState *env = NULL;
+ char *filename;
+ qemu_irq *pic, **openpic_irqs;
+ MemoryRegion *isa = g_new(MemoryRegion, 1);
+ MemoryRegion *unin_memory = g_new(MemoryRegion, 1);
+ MemoryRegion *unin2_memory = g_new(MemoryRegion, 1);
+ int linux_boot, i, j, k;
+ MemoryRegion *ram = g_new(MemoryRegion, 1), *bios = g_new(MemoryRegion, 1);
+ hwaddr kernel_base, initrd_base, cmdline_base = 0;
+ long kernel_size, initrd_size;
+ PCIBus *pci_bus;
+ PCIDevice *macio;
+ MACIOIDEState *macio_ide;
+ BusState *adb_bus;
+ MacIONVRAMState *nvr;
+ int bios_size;
+ MemoryRegion *pic_mem, *escc_mem;
+ MemoryRegion *escc_bar = g_new(MemoryRegion, 1);
+ int ppc_boot_device;
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+ void *fw_cfg;
+ int machine_arch;
+ SysBusDevice *s;
+ DeviceState *dev;
+ int *token = g_new(int, 1);
+ hwaddr nvram_addr = 0xFFF04000;
+ uint64_t tbfreq;
+
+ linux_boot = (kernel_filename != NULL);
+
+ /* init CPUs */
+ if (machine->cpu_model == NULL) {
+#ifdef TARGET_PPC64
+ machine->cpu_model = "970fx";
+#else
+ machine->cpu_model = "G4";
+#endif
+ }
+ for (i = 0; i < smp_cpus; i++) {
+ cpu = cpu_ppc_init(machine->cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find PowerPC CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ /* Set time-base frequency to 100 Mhz */
+ cpu_ppc_tb_init(env, TBFREQ);
+ qemu_register_reset(ppc_core99_reset, cpu);
+ }
+
+ /* allocate RAM */
+ memory_region_allocate_system_memory(ram, NULL, "ppc_core99.ram", ram_size);
+ memory_region_add_subregion(get_system_memory(), 0, ram);
+
+ /* allocate and load BIOS */
+ memory_region_init_ram(bios, NULL, "ppc_core99.bios", BIOS_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(bios);
+
+ if (bios_name == NULL)
+ bios_name = PROM_FILENAME;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ memory_region_set_readonly(bios, true);
+ memory_region_add_subregion(get_system_memory(), PROM_ADDR, bios);
+
+ /* Load OpenBIOS (ELF) */
+ if (filename) {
+ bios_size = load_elf(filename, NULL, NULL, NULL,
+ NULL, NULL, 1, ELF_MACHINE, 0);
+
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+ if (bios_size < 0 || bios_size > BIOS_SIZE) {
+ hw_error("qemu: could not load PowerPC bios '%s'\n", bios_name);
+ exit(1);
+ }
+
+ if (linux_boot) {
+ uint64_t lowaddr = 0;
+ int bswap_needed;
+
+#ifdef BSWAP_NEEDED
+ bswap_needed = 1;
+#else
+ bswap_needed = 0;
+#endif
+ kernel_base = KERNEL_LOAD_ADDR;
+
+ kernel_size = load_elf(kernel_filename, translate_kernel_address, NULL,
+ NULL, &lowaddr, NULL, 1, ELF_MACHINE, 0);
+ if (kernel_size < 0)
+ kernel_size = load_aout(kernel_filename, kernel_base,
+ ram_size - kernel_base, bswap_needed,
+ TARGET_PAGE_SIZE);
+ if (kernel_size < 0)
+ kernel_size = load_image_targphys(kernel_filename,
+ kernel_base,
+ ram_size - kernel_base);
+ if (kernel_size < 0) {
+ hw_error("qemu: could not load kernel '%s'\n", kernel_filename);
+ exit(1);
+ }
+ /* load initrd */
+ if (initrd_filename) {
+ initrd_base = round_page(kernel_base + kernel_size + KERNEL_GAP);
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+ if (initrd_size < 0) {
+ hw_error("qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ cmdline_base = round_page(initrd_base + initrd_size);
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ cmdline_base = round_page(kernel_base + kernel_size + KERNEL_GAP);
+ }
+ ppc_boot_device = 'm';
+ } else {
+ kernel_base = 0;
+ kernel_size = 0;
+ initrd_base = 0;
+ initrd_size = 0;
+ ppc_boot_device = '\0';
+ /* We consider that NewWorld PowerMac never have any floppy drive
+ * For now, OHW cannot boot from the network.
+ */
+ for (i = 0; boot_device[i] != '\0'; i++) {
+ if (boot_device[i] >= 'c' && boot_device[i] <= 'f') {
+ ppc_boot_device = boot_device[i];
+ break;
+ }
+ }
+ if (ppc_boot_device == '\0') {
+ fprintf(stderr, "No valid boot device for Mac99 machine\n");
+ exit(1);
+ }
+ }
+
+ /* Register 8 MB of ISA IO space */
+ memory_region_init_alias(isa, NULL, "isa_mmio",
+ get_system_io(), 0, 0x00800000);
+ memory_region_add_subregion(get_system_memory(), 0xf2000000, isa);
+
+ /* UniN init: XXX should be a real device */
+ memory_region_init_io(unin_memory, NULL, &unin_ops, token, "unin", 0x1000);
+ memory_region_add_subregion(get_system_memory(), 0xf8000000, unin_memory);
+
+ memory_region_init_io(unin2_memory, NULL, &unin_ops, token, "unin", 0x1000);
+ memory_region_add_subregion(get_system_memory(), 0xf3000000, unin2_memory);
+
+ openpic_irqs = g_malloc0(smp_cpus * sizeof(qemu_irq *));
+ openpic_irqs[0] =
+ g_malloc0(smp_cpus * sizeof(qemu_irq) * OPENPIC_OUTPUT_NB);
+ for (i = 0; i < smp_cpus; i++) {
+ /* Mac99 IRQ connection between OpenPIC outputs pins
+ * and PowerPC input pins
+ */
+ switch (PPC_INPUT(env)) {
+ case PPC_FLAGS_INPUT_6xx:
+ openpic_irqs[i] = openpic_irqs[0] + (i * OPENPIC_OUTPUT_NB);
+ openpic_irqs[i][OPENPIC_OUTPUT_INT] =
+ ((qemu_irq *)env->irq_inputs)[PPC6xx_INPUT_INT];
+ openpic_irqs[i][OPENPIC_OUTPUT_CINT] =
+ ((qemu_irq *)env->irq_inputs)[PPC6xx_INPUT_INT];
+ openpic_irqs[i][OPENPIC_OUTPUT_MCK] =
+ ((qemu_irq *)env->irq_inputs)[PPC6xx_INPUT_MCP];
+ /* Not connected ? */
+ openpic_irqs[i][OPENPIC_OUTPUT_DEBUG] = NULL;
+ /* Check this */
+ openpic_irqs[i][OPENPIC_OUTPUT_RESET] =
+ ((qemu_irq *)env->irq_inputs)[PPC6xx_INPUT_HRESET];
+ break;
+#if defined(TARGET_PPC64)
+ case PPC_FLAGS_INPUT_970:
+ openpic_irqs[i] = openpic_irqs[0] + (i * OPENPIC_OUTPUT_NB);
+ openpic_irqs[i][OPENPIC_OUTPUT_INT] =
+ ((qemu_irq *)env->irq_inputs)[PPC970_INPUT_INT];
+ openpic_irqs[i][OPENPIC_OUTPUT_CINT] =
+ ((qemu_irq *)env->irq_inputs)[PPC970_INPUT_INT];
+ openpic_irqs[i][OPENPIC_OUTPUT_MCK] =
+ ((qemu_irq *)env->irq_inputs)[PPC970_INPUT_MCP];
+ /* Not connected ? */
+ openpic_irqs[i][OPENPIC_OUTPUT_DEBUG] = NULL;
+ /* Check this */
+ openpic_irqs[i][OPENPIC_OUTPUT_RESET] =
+ ((qemu_irq *)env->irq_inputs)[PPC970_INPUT_HRESET];
+ break;
+#endif /* defined(TARGET_PPC64) */
+ default:
+ hw_error("Bus model not supported on mac99 machine\n");
+ exit(1);
+ }
+ }
+
+ pic = g_new0(qemu_irq, 64);
+
+ dev = qdev_create(NULL, TYPE_OPENPIC);
+ qdev_prop_set_uint32(dev, "model", OPENPIC_MODEL_RAVEN);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ pic_mem = s->mmio[0].memory;
+ k = 0;
+ for (i = 0; i < smp_cpus; i++) {
+ for (j = 0; j < OPENPIC_OUTPUT_NB; j++) {
+ sysbus_connect_irq(s, k++, openpic_irqs[i][j]);
+ }
+ }
+
+ for (i = 0; i < 64; i++) {
+ pic[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ if (PPC_INPUT(env) == PPC_FLAGS_INPUT_970) {
+ /* 970 gets a U3 bus */
+ pci_bus = pci_pmac_u3_init(pic, get_system_memory(), get_system_io());
+ machine_arch = ARCH_MAC99_U3;
+ machine->usb |= defaults_enabled() && !machine->usb_disabled;
+ } else {
+ pci_bus = pci_pmac_init(pic, get_system_memory(), get_system_io());
+ machine_arch = ARCH_MAC99;
+ }
+
+ /* Timebase Frequency */
+ if (kvm_enabled()) {
+ tbfreq = kvmppc_get_tbfreq();
+ } else {
+ tbfreq = TBFREQ;
+ }
+
+ /* init basic PC hardware */
+ escc_mem = escc_init(0, pic[0x25], pic[0x24],
+ serial_hds[0], serial_hds[1], ESCC_CLOCK, 4);
+ memory_region_init_alias(escc_bar, NULL, "escc-bar",
+ escc_mem, 0, memory_region_size(escc_mem));
+
+ macio = pci_create(pci_bus, -1, TYPE_NEWWORLD_MACIO);
+ dev = DEVICE(macio);
+ qdev_connect_gpio_out(dev, 0, pic[0x19]); /* CUDA */
+ qdev_connect_gpio_out(dev, 1, pic[0x0d]); /* IDE */
+ qdev_connect_gpio_out(dev, 2, pic[0x02]); /* IDE DMA */
+ qdev_connect_gpio_out(dev, 3, pic[0x0e]); /* IDE */
+ qdev_connect_gpio_out(dev, 4, pic[0x03]); /* IDE DMA */
+ qdev_prop_set_uint64(dev, "frequency", tbfreq);
+ macio_init(macio, pic_mem, escc_bar);
+
+ /* We only emulate 2 out of 3 IDE controllers for now */
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+
+ macio_ide = MACIO_IDE(object_resolve_path_component(OBJECT(macio),
+ "ide[0]"));
+ macio_ide_init_drives(macio_ide, hd);
+
+ macio_ide = MACIO_IDE(object_resolve_path_component(OBJECT(macio),
+ "ide[1]"));
+ macio_ide_init_drives(macio_ide, &hd[MAX_IDE_DEVS]);
+
+ dev = DEVICE(object_resolve_path_component(OBJECT(macio), "cuda"));
+ adb_bus = qdev_get_child_bus(dev, "adb.0");
+ dev = qdev_create(adb_bus, TYPE_ADB_KEYBOARD);
+ qdev_init_nofail(dev);
+ dev = qdev_create(adb_bus, TYPE_ADB_MOUSE);
+ qdev_init_nofail(dev);
+
+ if (machine->usb) {
+ pci_create_simple(pci_bus, -1, "pci-ohci");
+
+ /* U3 needs to use USB for input because Linux doesn't support via-cuda
+ on PPC64 */
+ if (machine_arch == ARCH_MAC99_U3) {
+ USBBus *usb_bus = usb_bus_find(-1);
+
+ usb_create_simple(usb_bus, "usb-kbd");
+ usb_create_simple(usb_bus, "usb-mouse");
+ }
+ }
+
+ pci_vga_init(pci_bus);
+
+ if (graphic_depth != 15 && graphic_depth != 32 && graphic_depth != 8) {
+ graphic_depth = 15;
+ }
+
+ for (i = 0; i < nb_nics; i++) {
+ pci_nic_init_nofail(&nd_table[i], pci_bus, "ne2k_pci", NULL);
+ }
+
+ /* The NewWorld NVRAM is not located in the MacIO device */
+#ifdef CONFIG_KVM
+ if (kvm_enabled() && getpagesize() > 4096) {
+ /* We can't combine read-write and read-only in a single page, so
+ move the NVRAM out of ROM again for KVM */
+ nvram_addr = 0xFFE00000;
+ }
+#endif
+ dev = qdev_create(NULL, TYPE_MACIO_NVRAM);
+ qdev_prop_set_uint32(dev, "size", 0x2000);
+ qdev_prop_set_uint32(dev, "it_shift", 1);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, nvram_addr);
+ nvr = MACIO_NVRAM(dev);
+ pmac_format_nvram_partition(nvr, 0x2000);
+ /* No PCI init: the BIOS will do it */
+
+ fw_cfg = fw_cfg_init_mem(CFG_ADDR, CFG_ADDR + 2);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, (uint16_t)max_cpus);
+ fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MACHINE_ID, machine_arch);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, kernel_base);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_SIZE, kernel_size);
+ if (kernel_cmdline) {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, cmdline_base);
+ pstrcpy_targphys("cmdline", cmdline_base, TARGET_PAGE_SIZE, kernel_cmdline);
+ } else {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, 0);
+ }
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, initrd_base);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, initrd_size);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_BOOT_DEVICE, ppc_boot_device);
+
+ fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_WIDTH, graphic_width);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_HEIGHT, graphic_height);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_DEPTH, graphic_depth);
+
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_IS_KVM, kvm_enabled());
+ if (kvm_enabled()) {
+#ifdef CONFIG_KVM
+ uint8_t *hypercall;
+
+ hypercall = g_malloc(16);
+ kvmppc_get_hypercall(env, hypercall, 16);
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_PPC_KVM_HC, hypercall, 16);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_KVM_PID, getpid());
+#endif
+ }
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_TBFREQ, tbfreq);
+ /* Mac OS X requires a "known good" clock-frequency value; pass it one. */
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_CLOCKFREQ, CLOCKFREQ);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_BUSFREQ, BUSFREQ);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_NVRAM_ADDR, nvram_addr);
+
+ qemu_register_boot_set(fw_cfg_boot_set, fw_cfg);
+}
+
+static int core99_kvm_type(const char *arg)
+{
+ /* Always force PR KVM */
+ return 2;
+}
+
+static void core99_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->name = "mac99";
+ mc->desc = "Mac99 based PowerMAC";
+ mc->init = ppc_core99_init;
+ mc->max_cpus = MAX_CPUS;
+ mc->default_boot_order = "cd";
+ mc->kvm_type = core99_kvm_type;
+}
+
+static const TypeInfo core99_machine_info = {
+ .name = "mac99-machine",
+ .parent = TYPE_MACHINE,
+ .class_init = core99_machine_class_init,
+};
+
+static void mac_machine_register_types(void)
+{
+ type_register_static(&core99_machine_info);
+}
+
+type_init(mac_machine_register_types)
diff --git a/hw/ppc/mac_oldworld.c b/hw/ppc/mac_oldworld.c
new file mode 100644
index 00000000..06fdbaf5
--- /dev/null
+++ b/hw/ppc/mac_oldworld.c
@@ -0,0 +1,377 @@
+
+/*
+ * QEMU OldWorld PowerMac (currently ~G3 Beige) hardware System Emulator
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "mac.h"
+#include "hw/input/adb.h"
+#include "hw/timer/m48t59.h"
+#include "sysemu/sysemu.h"
+#include "net/net.h"
+#include "hw/isa/isa.h"
+#include "hw/pci/pci.h"
+#include "hw/boards.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/char/escc.h"
+#include "hw/ide.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+
+#define MAX_IDE_BUS 2
+#define CFG_ADDR 0xf0000510
+#define TBFREQ 16600000UL
+#define CLOCKFREQ 266000000UL
+#define BUSFREQ 66000000UL
+
+static void fw_cfg_boot_set(void *opaque, const char *boot_device,
+ Error **errp)
+{
+ fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]);
+}
+
+static uint64_t translate_kernel_address(void *opaque, uint64_t addr)
+{
+ return (addr & 0x0fffffff) + KERNEL_LOAD_ADDR;
+}
+
+static hwaddr round_page(hwaddr addr)
+{
+ return (addr + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK;
+}
+
+static void ppc_heathrow_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+static void ppc_heathrow_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ const char *boot_device = machine->boot_order;
+ MemoryRegion *sysmem = get_system_memory();
+ PowerPCCPU *cpu = NULL;
+ CPUPPCState *env = NULL;
+ char *filename;
+ qemu_irq *pic, **heathrow_irqs;
+ int linux_boot, i;
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *bios = g_new(MemoryRegion, 1);
+ MemoryRegion *isa = g_new(MemoryRegion, 1);
+ uint32_t kernel_base, initrd_base, cmdline_base = 0;
+ int32_t kernel_size, initrd_size;
+ PCIBus *pci_bus;
+ PCIDevice *macio;
+ MACIOIDEState *macio_ide;
+ DeviceState *dev;
+ BusState *adb_bus;
+ int bios_size;
+ MemoryRegion *pic_mem;
+ MemoryRegion *escc_mem, *escc_bar = g_new(MemoryRegion, 1);
+ uint16_t ppc_boot_device;
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+ void *fw_cfg;
+ uint64_t tbfreq;
+
+ linux_boot = (kernel_filename != NULL);
+
+ /* init CPUs */
+ if (machine->cpu_model == NULL)
+ machine->cpu_model = "G3";
+ for (i = 0; i < smp_cpus; i++) {
+ cpu = cpu_ppc_init(machine->cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find PowerPC CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ /* Set time-base frequency to 16.6 Mhz */
+ cpu_ppc_tb_init(env, TBFREQ);
+ qemu_register_reset(ppc_heathrow_reset, cpu);
+ }
+
+ /* allocate RAM */
+ if (ram_size > (2047 << 20)) {
+ fprintf(stderr,
+ "qemu: Too much memory for this machine: %d MB, maximum 2047 MB\n",
+ ((unsigned int)ram_size / (1 << 20)));
+ exit(1);
+ }
+
+ memory_region_allocate_system_memory(ram, NULL, "ppc_heathrow.ram",
+ ram_size);
+ memory_region_add_subregion(sysmem, 0, ram);
+
+ /* allocate and load BIOS */
+ memory_region_init_ram(bios, NULL, "ppc_heathrow.bios", BIOS_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(bios);
+
+ if (bios_name == NULL)
+ bios_name = PROM_FILENAME;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ memory_region_set_readonly(bios, true);
+ memory_region_add_subregion(sysmem, PROM_ADDR, bios);
+
+ /* Load OpenBIOS (ELF) */
+ if (filename) {
+ bios_size = load_elf(filename, 0, NULL, NULL, NULL, NULL,
+ 1, ELF_MACHINE, 0);
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+ if (bios_size < 0 || bios_size > BIOS_SIZE) {
+ hw_error("qemu: could not load PowerPC bios '%s'\n", bios_name);
+ exit(1);
+ }
+
+ if (linux_boot) {
+ uint64_t lowaddr = 0;
+ int bswap_needed;
+
+#ifdef BSWAP_NEEDED
+ bswap_needed = 1;
+#else
+ bswap_needed = 0;
+#endif
+ kernel_base = KERNEL_LOAD_ADDR;
+ kernel_size = load_elf(kernel_filename, translate_kernel_address, NULL,
+ NULL, &lowaddr, NULL, 1, ELF_MACHINE, 0);
+ if (kernel_size < 0)
+ kernel_size = load_aout(kernel_filename, kernel_base,
+ ram_size - kernel_base, bswap_needed,
+ TARGET_PAGE_SIZE);
+ if (kernel_size < 0)
+ kernel_size = load_image_targphys(kernel_filename,
+ kernel_base,
+ ram_size - kernel_base);
+ if (kernel_size < 0) {
+ hw_error("qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ /* load initrd */
+ if (initrd_filename) {
+ initrd_base = round_page(kernel_base + kernel_size + KERNEL_GAP);
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+ if (initrd_size < 0) {
+ hw_error("qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ cmdline_base = round_page(initrd_base + initrd_size);
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ cmdline_base = round_page(kernel_base + kernel_size + KERNEL_GAP);
+ }
+ ppc_boot_device = 'm';
+ } else {
+ kernel_base = 0;
+ kernel_size = 0;
+ initrd_base = 0;
+ initrd_size = 0;
+ ppc_boot_device = '\0';
+ for (i = 0; boot_device[i] != '\0'; i++) {
+ /* TOFIX: for now, the second IDE channel is not properly
+ * used by OHW. The Mac floppy disk are not emulated.
+ * For now, OHW cannot boot from the network.
+ */
+#if 0
+ if (boot_device[i] >= 'a' && boot_device[i] <= 'f') {
+ ppc_boot_device = boot_device[i];
+ break;
+ }
+#else
+ if (boot_device[i] >= 'c' && boot_device[i] <= 'd') {
+ ppc_boot_device = boot_device[i];
+ break;
+ }
+#endif
+ }
+ if (ppc_boot_device == '\0') {
+ fprintf(stderr, "No valid boot device for G3 Beige machine\n");
+ exit(1);
+ }
+ }
+
+ /* Register 2 MB of ISA IO space */
+ memory_region_init_alias(isa, NULL, "isa_mmio",
+ get_system_io(), 0, 0x00200000);
+ memory_region_add_subregion(sysmem, 0xfe000000, isa);
+
+ /* XXX: we register only 1 output pin for heathrow PIC */
+ heathrow_irqs = g_malloc0(smp_cpus * sizeof(qemu_irq *));
+ heathrow_irqs[0] =
+ g_malloc0(smp_cpus * sizeof(qemu_irq) * 1);
+ /* Connect the heathrow PIC outputs to the 6xx bus */
+ for (i = 0; i < smp_cpus; i++) {
+ switch (PPC_INPUT(env)) {
+ case PPC_FLAGS_INPUT_6xx:
+ heathrow_irqs[i] = heathrow_irqs[0] + (i * 1);
+ heathrow_irqs[i][0] =
+ ((qemu_irq *)env->irq_inputs)[PPC6xx_INPUT_INT];
+ break;
+ default:
+ hw_error("Bus model not supported on OldWorld Mac machine\n");
+ }
+ }
+
+ /* Timebase Frequency */
+ if (kvm_enabled()) {
+ tbfreq = kvmppc_get_tbfreq();
+ } else {
+ tbfreq = TBFREQ;
+ }
+
+ /* init basic PC hardware */
+ if (PPC_INPUT(env) != PPC_FLAGS_INPUT_6xx) {
+ hw_error("Only 6xx bus is supported on heathrow machine\n");
+ }
+ pic = heathrow_pic_init(&pic_mem, 1, heathrow_irqs);
+ pci_bus = pci_grackle_init(0xfec00000, pic,
+ get_system_memory(),
+ get_system_io());
+ pci_vga_init(pci_bus);
+
+ escc_mem = escc_init(0, pic[0x0f], pic[0x10], serial_hds[0],
+ serial_hds[1], ESCC_CLOCK, 4);
+ memory_region_init_alias(escc_bar, NULL, "escc-bar",
+ escc_mem, 0, memory_region_size(escc_mem));
+
+ for(i = 0; i < nb_nics; i++)
+ pci_nic_init_nofail(&nd_table[i], pci_bus, "ne2k_pci", NULL);
+
+
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+
+ macio = pci_create(pci_bus, -1, TYPE_OLDWORLD_MACIO);
+ dev = DEVICE(macio);
+ qdev_connect_gpio_out(dev, 0, pic[0x12]); /* CUDA */
+ qdev_connect_gpio_out(dev, 1, pic[0x0D]); /* IDE-0 */
+ qdev_connect_gpio_out(dev, 2, pic[0x02]); /* IDE-0 DMA */
+ qdev_connect_gpio_out(dev, 3, pic[0x0E]); /* IDE-1 */
+ qdev_connect_gpio_out(dev, 4, pic[0x03]); /* IDE-1 DMA */
+ qdev_prop_set_uint64(dev, "frequency", tbfreq);
+ macio_init(macio, pic_mem, escc_bar);
+
+ macio_ide = MACIO_IDE(object_resolve_path_component(OBJECT(macio),
+ "ide[0]"));
+ macio_ide_init_drives(macio_ide, hd);
+
+ macio_ide = MACIO_IDE(object_resolve_path_component(OBJECT(macio),
+ "ide[1]"));
+ macio_ide_init_drives(macio_ide, &hd[MAX_IDE_DEVS]);
+
+ dev = DEVICE(object_resolve_path_component(OBJECT(macio), "cuda"));
+ adb_bus = qdev_get_child_bus(dev, "adb.0");
+ dev = qdev_create(adb_bus, TYPE_ADB_KEYBOARD);
+ qdev_init_nofail(dev);
+ dev = qdev_create(adb_bus, TYPE_ADB_MOUSE);
+ qdev_init_nofail(dev);
+
+ if (usb_enabled()) {
+ pci_create_simple(pci_bus, -1, "pci-ohci");
+ }
+
+ if (graphic_depth != 15 && graphic_depth != 32 && graphic_depth != 8)
+ graphic_depth = 15;
+
+ /* No PCI init: the BIOS will do it */
+
+ fw_cfg = fw_cfg_init_mem(CFG_ADDR, CFG_ADDR + 2);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, (uint16_t)max_cpus);
+ fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MACHINE_ID, ARCH_HEATHROW);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, kernel_base);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_SIZE, kernel_size);
+ if (kernel_cmdline) {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, cmdline_base);
+ pstrcpy_targphys("cmdline", cmdline_base, TARGET_PAGE_SIZE, kernel_cmdline);
+ } else {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, 0);
+ }
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, initrd_base);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, initrd_size);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_BOOT_DEVICE, ppc_boot_device);
+
+ fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_WIDTH, graphic_width);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_HEIGHT, graphic_height);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_DEPTH, graphic_depth);
+
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_IS_KVM, kvm_enabled());
+ if (kvm_enabled()) {
+#ifdef CONFIG_KVM
+ uint8_t *hypercall;
+
+ hypercall = g_malloc(16);
+ kvmppc_get_hypercall(env, hypercall, 16);
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_PPC_KVM_HC, hypercall, 16);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_KVM_PID, getpid());
+#endif
+ }
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_TBFREQ, tbfreq);
+ /* Mac OS X requires a "known good" clock-frequency value; pass it one. */
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_CLOCKFREQ, CLOCKFREQ);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_BUSFREQ, BUSFREQ);
+
+ qemu_register_boot_set(fw_cfg_boot_set, fw_cfg);
+}
+
+static int heathrow_kvm_type(const char *arg)
+{
+ /* Always force PR KVM */
+ return 2;
+}
+
+static QEMUMachine heathrow_machine = {
+ .name = "g3beige",
+ .desc = "Heathrow based PowerMAC",
+ .init = ppc_heathrow_init,
+ .max_cpus = MAX_CPUS,
+#ifndef TARGET_PPC64
+ .is_default = 1,
+#endif
+ .default_boot_order = "cd", /* TOFIX "cad" when Mac floppy is implemented */
+ .kvm_type = heathrow_kvm_type,
+};
+
+static void heathrow_machine_init(void)
+{
+ qemu_register_machine(&heathrow_machine);
+}
+
+machine_init(heathrow_machine_init);
diff --git a/hw/ppc/mpc8544_guts.c b/hw/ppc/mpc8544_guts.c
new file mode 100644
index 00000000..a10abe97
--- /dev/null
+++ b/hw/ppc/mpc8544_guts.c
@@ -0,0 +1,140 @@
+/*
+ * QEMU PowerPC MPC8544 global util pseudo-device
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <alex@csgraf.de>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * *****************************************************************
+ *
+ * The documentation for this device is noted in the MPC8544 documentation,
+ * file name "MPC8544ERM.pdf". You can easily find it on the web.
+ *
+ */
+
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+
+#define MPC8544_GUTS_MMIO_SIZE 0x1000
+#define MPC8544_GUTS_RSTCR_RESET 0x02
+
+#define MPC8544_GUTS_ADDR_PORPLLSR 0x00
+#define MPC8544_GUTS_ADDR_PORBMSR 0x04
+#define MPC8544_GUTS_ADDR_PORIMPSCR 0x08
+#define MPC8544_GUTS_ADDR_PORDEVSR 0x0C
+#define MPC8544_GUTS_ADDR_PORDBGMSR 0x10
+#define MPC8544_GUTS_ADDR_PORDEVSR2 0x14
+#define MPC8544_GUTS_ADDR_GPPORCR 0x20
+#define MPC8544_GUTS_ADDR_GPIOCR 0x30
+#define MPC8544_GUTS_ADDR_GPOUTDR 0x40
+#define MPC8544_GUTS_ADDR_GPINDR 0x50
+#define MPC8544_GUTS_ADDR_PMUXCR 0x60
+#define MPC8544_GUTS_ADDR_DEVDISR 0x70
+#define MPC8544_GUTS_ADDR_POWMGTCSR 0x80
+#define MPC8544_GUTS_ADDR_MCPSUMR 0x90
+#define MPC8544_GUTS_ADDR_RSTRSCR 0x94
+#define MPC8544_GUTS_ADDR_PVR 0xA0
+#define MPC8544_GUTS_ADDR_SVR 0xA4
+#define MPC8544_GUTS_ADDR_RSTCR 0xB0
+#define MPC8544_GUTS_ADDR_IOVSELSR 0xC0
+#define MPC8544_GUTS_ADDR_DDRCSR 0xB20
+#define MPC8544_GUTS_ADDR_DDRCDR 0xB24
+#define MPC8544_GUTS_ADDR_DDRCLKDR 0xB28
+#define MPC8544_GUTS_ADDR_CLKOCR 0xE00
+#define MPC8544_GUTS_ADDR_SRDS1CR1 0xF04
+#define MPC8544_GUTS_ADDR_SRDS2CR1 0xF10
+#define MPC8544_GUTS_ADDR_SRDS2CR3 0xF18
+
+#define TYPE_MPC8544_GUTS "mpc8544-guts"
+#define MPC8544_GUTS(obj) OBJECT_CHECK(GutsState, (obj), TYPE_MPC8544_GUTS)
+
+struct GutsState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+};
+
+typedef struct GutsState GutsState;
+
+static uint64_t mpc8544_guts_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t value = 0;
+ PowerPCCPU *cpu = POWERPC_CPU(current_cpu);
+ CPUPPCState *env = &cpu->env;
+
+ addr &= MPC8544_GUTS_MMIO_SIZE - 1;
+ switch (addr) {
+ case MPC8544_GUTS_ADDR_PVR:
+ value = env->spr[SPR_PVR];
+ break;
+ case MPC8544_GUTS_ADDR_SVR:
+ value = env->spr[SPR_E500_SVR];
+ break;
+ default:
+ fprintf(stderr, "guts: Unknown register read: %x\n", (int)addr);
+ break;
+ }
+
+ return value;
+}
+
+static void mpc8544_guts_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ addr &= MPC8544_GUTS_MMIO_SIZE - 1;
+
+ switch (addr) {
+ case MPC8544_GUTS_ADDR_RSTCR:
+ if (value & MPC8544_GUTS_RSTCR_RESET) {
+ qemu_system_reset_request();
+ }
+ break;
+ default:
+ fprintf(stderr, "guts: Unknown register write: %x = %x\n",
+ (int)addr, (unsigned)value);
+ break;
+ }
+}
+
+static const MemoryRegionOps mpc8544_guts_ops = {
+ .read = mpc8544_guts_read,
+ .write = mpc8544_guts_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void mpc8544_guts_initfn(Object *obj)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(obj);
+ GutsState *s = MPC8544_GUTS(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &mpc8544_guts_ops, s,
+ "mpc8544.guts", MPC8544_GUTS_MMIO_SIZE);
+ sysbus_init_mmio(d, &s->iomem);
+}
+
+static const TypeInfo mpc8544_guts_info = {
+ .name = TYPE_MPC8544_GUTS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(GutsState),
+ .instance_init = mpc8544_guts_initfn,
+};
+
+static void mpc8544_guts_register_types(void)
+{
+ type_register_static(&mpc8544_guts_info);
+}
+
+type_init(mpc8544_guts_register_types)
diff --git a/hw/ppc/mpc8544ds.c b/hw/ppc/mpc8544ds.c
new file mode 100644
index 00000000..3a3b141e
--- /dev/null
+++ b/hw/ppc/mpc8544ds.c
@@ -0,0 +1,65 @@
+/*
+ * Support for the PPC e500-based mpc8544ds board
+ *
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "config.h"
+#include "qemu-common.h"
+#include "e500.h"
+#include "hw/boards.h"
+#include "sysemu/device_tree.h"
+#include "hw/ppc/openpic.h"
+#include "qemu/error-report.h"
+
+static void mpc8544ds_fixup_devtree(PPCE500Params *params, void *fdt)
+{
+ const char model[] = "MPC8544DS";
+ const char compatible[] = "MPC8544DS\0MPC85xxDS";
+
+ qemu_fdt_setprop(fdt, "/", "model", model, sizeof(model));
+ qemu_fdt_setprop(fdt, "/", "compatible", compatible,
+ sizeof(compatible));
+}
+
+static void mpc8544ds_init(MachineState *machine)
+{
+ PPCE500Params params = {
+ .pci_first_slot = 0x11,
+ .pci_nr_slots = 2,
+ .fixup_devtree = mpc8544ds_fixup_devtree,
+ .mpic_version = OPENPIC_MODEL_FSL_MPIC_20,
+ .ccsrbar_base = 0xE0000000ULL,
+ .pci_mmio_base = 0xC0000000ULL,
+ .pci_mmio_bus_base = 0xC0000000ULL,
+ .pci_pio_base = 0xE1000000ULL,
+ .spin_base = 0xEF000000ULL,
+ };
+
+ if (machine->ram_size > 0xc0000000) {
+ error_report("The MPC8544DS board only supports up to 3GB of RAM");
+ exit(1);
+ }
+
+ ppce500_init(machine, &params);
+}
+
+
+static QEMUMachine ppce500_machine = {
+ .name = "mpc8544ds",
+ .desc = "mpc8544ds",
+ .init = mpc8544ds_init,
+ .max_cpus = 15,
+};
+
+static void ppce500_machine_init(void)
+{
+ qemu_register_machine(&ppce500_machine);
+}
+
+machine_init(ppce500_machine_init);
diff --git a/hw/ppc/ppc.c b/hw/ppc/ppc.c
new file mode 100644
index 00000000..b77e3035
--- /dev/null
+++ b/hw/ppc/ppc.c
@@ -0,0 +1,1339 @@
+/*
+ * QEMU generic PowerPC hardware System Emulator
+ *
+ * Copyright (c) 2003-2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "hw/ppc/ppc_e500.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/cpus.h"
+#include "hw/timer/m48t59.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "hw/loader.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+#include "trace.h"
+
+//#define PPC_DEBUG_IRQ
+//#define PPC_DEBUG_TB
+
+#ifdef PPC_DEBUG_IRQ
+# define LOG_IRQ(...) qemu_log_mask(CPU_LOG_INT, ## __VA_ARGS__)
+#else
+# define LOG_IRQ(...) do { } while (0)
+#endif
+
+
+#ifdef PPC_DEBUG_TB
+# define LOG_TB(...) qemu_log(__VA_ARGS__)
+#else
+# define LOG_TB(...) do { } while (0)
+#endif
+
+static void cpu_ppc_tb_stop (CPUPPCState *env);
+static void cpu_ppc_tb_start (CPUPPCState *env);
+
+void ppc_set_irq(PowerPCCPU *cpu, int n_IRQ, int level)
+{
+ CPUState *cs = CPU(cpu);
+ CPUPPCState *env = &cpu->env;
+ unsigned int old_pending = env->pending_interrupts;
+
+ if (level) {
+ env->pending_interrupts |= 1 << n_IRQ;
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ env->pending_interrupts &= ~(1 << n_IRQ);
+ if (env->pending_interrupts == 0) {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ }
+
+ if (old_pending != env->pending_interrupts) {
+#ifdef CONFIG_KVM
+ kvmppc_set_interrupt(cpu, n_IRQ, level);
+#endif
+ }
+
+ LOG_IRQ("%s: %p n_IRQ %d level %d => pending %08" PRIx32
+ "req %08x\n", __func__, env, n_IRQ, level,
+ env->pending_interrupts, CPU(cpu)->interrupt_request);
+}
+
+/* PowerPC 6xx / 7xx internal IRQ controller */
+static void ppc6xx_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ int cur_level;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+ cur_level = (env->irq_input_state >> pin) & 1;
+ /* Don't generate spurious events */
+ if ((cur_level == 1 && level == 0) || (cur_level == 0 && level != 0)) {
+ CPUState *cs = CPU(cpu);
+
+ switch (pin) {
+ case PPC6xx_INPUT_TBEN:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: %s the time base\n",
+ __func__, level ? "start" : "stop");
+ if (level) {
+ cpu_ppc_tb_start(env);
+ } else {
+ cpu_ppc_tb_stop(env);
+ }
+ case PPC6xx_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the external IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ case PPC6xx_INPUT_SMI:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the SMI IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_SMI, level);
+ break;
+ case PPC6xx_INPUT_MCP:
+ /* Negative edge sensitive */
+ /* XXX: TODO: actual reaction may depends on HID0 status
+ * 603/604/740/750: check HID0[EMCP]
+ */
+ if (cur_level == 1 && level == 0) {
+ LOG_IRQ("%s: raise machine check state\n",
+ __func__);
+ ppc_set_irq(cpu, PPC_INTERRUPT_MCK, 1);
+ }
+ break;
+ case PPC6xx_INPUT_CKSTP_IN:
+ /* Level sensitive - active low */
+ /* XXX: TODO: relay the signal to CKSTP_OUT pin */
+ /* XXX: Note that the only way to restart the CPU is to reset it */
+ if (level) {
+ LOG_IRQ("%s: stop the CPU\n", __func__);
+ cs->halted = 1;
+ }
+ break;
+ case PPC6xx_INPUT_HRESET:
+ /* Level sensitive - active low */
+ if (level) {
+ LOG_IRQ("%s: reset the CPU\n", __func__);
+ cpu_interrupt(cs, CPU_INTERRUPT_RESET);
+ }
+ break;
+ case PPC6xx_INPUT_SRESET:
+ LOG_IRQ("%s: set the RESET IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_RESET, level);
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level)
+ env->irq_input_state |= 1 << pin;
+ else
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppc6xx_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&ppc6xx_set_irq, cpu,
+ PPC6xx_INPUT_NB);
+}
+
+#if defined(TARGET_PPC64)
+/* PowerPC 970 internal IRQ controller */
+static void ppc970_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ int cur_level;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+ cur_level = (env->irq_input_state >> pin) & 1;
+ /* Don't generate spurious events */
+ if ((cur_level == 1 && level == 0) || (cur_level == 0 && level != 0)) {
+ CPUState *cs = CPU(cpu);
+
+ switch (pin) {
+ case PPC970_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the external IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ case PPC970_INPUT_THINT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the SMI IRQ state to %d\n", __func__,
+ level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_THERM, level);
+ break;
+ case PPC970_INPUT_MCP:
+ /* Negative edge sensitive */
+ /* XXX: TODO: actual reaction may depends on HID0 status
+ * 603/604/740/750: check HID0[EMCP]
+ */
+ if (cur_level == 1 && level == 0) {
+ LOG_IRQ("%s: raise machine check state\n",
+ __func__);
+ ppc_set_irq(cpu, PPC_INTERRUPT_MCK, 1);
+ }
+ break;
+ case PPC970_INPUT_CKSTP:
+ /* Level sensitive - active low */
+ /* XXX: TODO: relay the signal to CKSTP_OUT pin */
+ if (level) {
+ LOG_IRQ("%s: stop the CPU\n", __func__);
+ cs->halted = 1;
+ } else {
+ LOG_IRQ("%s: restart the CPU\n", __func__);
+ cs->halted = 0;
+ qemu_cpu_kick(cs);
+ }
+ break;
+ case PPC970_INPUT_HRESET:
+ /* Level sensitive - active low */
+ if (level) {
+ cpu_interrupt(cs, CPU_INTERRUPT_RESET);
+ }
+ break;
+ case PPC970_INPUT_SRESET:
+ LOG_IRQ("%s: set the RESET IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_RESET, level);
+ break;
+ case PPC970_INPUT_TBEN:
+ LOG_IRQ("%s: set the TBEN state to %d\n", __func__,
+ level);
+ /* XXX: TODO */
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level)
+ env->irq_input_state |= 1 << pin;
+ else
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppc970_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&ppc970_set_irq, cpu,
+ PPC970_INPUT_NB);
+}
+
+/* POWER7 internal IRQ controller */
+static void power7_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+
+ switch (pin) {
+ case POWER7_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the external IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level) {
+ env->irq_input_state |= 1 << pin;
+ } else {
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppcPOWER7_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&power7_set_irq, cpu,
+ POWER7_INPUT_NB);
+}
+#endif /* defined(TARGET_PPC64) */
+
+/* PowerPC 40x internal IRQ controller */
+static void ppc40x_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ int cur_level;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+ cur_level = (env->irq_input_state >> pin) & 1;
+ /* Don't generate spurious events */
+ if ((cur_level == 1 && level == 0) || (cur_level == 0 && level != 0)) {
+ CPUState *cs = CPU(cpu);
+
+ switch (pin) {
+ case PPC40x_INPUT_RESET_SYS:
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC system\n",
+ __func__);
+ ppc40x_system_reset(cpu);
+ }
+ break;
+ case PPC40x_INPUT_RESET_CHIP:
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC chip\n", __func__);
+ ppc40x_chip_reset(cpu);
+ }
+ break;
+ case PPC40x_INPUT_RESET_CORE:
+ /* XXX: TODO: update DBSR[MRR] */
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC core\n", __func__);
+ ppc40x_core_reset(cpu);
+ }
+ break;
+ case PPC40x_INPUT_CINT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the critical IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_CEXT, level);
+ break;
+ case PPC40x_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the external IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ case PPC40x_INPUT_HALT:
+ /* Level sensitive - active low */
+ if (level) {
+ LOG_IRQ("%s: stop the CPU\n", __func__);
+ cs->halted = 1;
+ } else {
+ LOG_IRQ("%s: restart the CPU\n", __func__);
+ cs->halted = 0;
+ qemu_cpu_kick(cs);
+ }
+ break;
+ case PPC40x_INPUT_DEBUG:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the debug pin state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_DEBUG, level);
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level)
+ env->irq_input_state |= 1 << pin;
+ else
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppc40x_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&ppc40x_set_irq,
+ cpu, PPC40x_INPUT_NB);
+}
+
+/* PowerPC E500 internal IRQ controller */
+static void ppce500_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ int cur_level;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+ cur_level = (env->irq_input_state >> pin) & 1;
+ /* Don't generate spurious events */
+ if ((cur_level == 1 && level == 0) || (cur_level == 0 && level != 0)) {
+ switch (pin) {
+ case PPCE500_INPUT_MCK:
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC system\n",
+ __func__);
+ qemu_system_reset_request();
+ }
+ break;
+ case PPCE500_INPUT_RESET_CORE:
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC core\n", __func__);
+ ppc_set_irq(cpu, PPC_INTERRUPT_MCK, level);
+ }
+ break;
+ case PPCE500_INPUT_CINT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the critical IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_CEXT, level);
+ break;
+ case PPCE500_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the core IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ case PPCE500_INPUT_DEBUG:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the debug pin state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_DEBUG, level);
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level)
+ env->irq_input_state |= 1 << pin;
+ else
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppce500_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&ppce500_set_irq,
+ cpu, PPCE500_INPUT_NB);
+}
+
+/* Enable or Disable the E500 EPR capability */
+void ppce500_set_mpic_proxy(bool enabled)
+{
+ CPUState *cs;
+
+ CPU_FOREACH(cs) {
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+
+ cpu->env.mpic_proxy = enabled;
+ if (kvm_enabled()) {
+ kvmppc_set_mpic_proxy(cpu, enabled);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* PowerPC time base and decrementer emulation */
+
+uint64_t cpu_ppc_get_tb(ppc_tb_t *tb_env, uint64_t vmclk, int64_t tb_offset)
+{
+ /* TB time in tb periods */
+ return muldiv64(vmclk, tb_env->tb_freq, get_ticks_per_sec()) + tb_offset;
+}
+
+uint64_t cpu_ppc_load_tbl (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ if (kvm_enabled()) {
+ return env->spr[SPR_TBL];
+ }
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tb_env->tb_offset);
+ LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb);
+
+ return tb;
+}
+
+static inline uint32_t _cpu_ppc_load_tbu(CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tb_env->tb_offset);
+ LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb);
+
+ return tb >> 32;
+}
+
+uint32_t cpu_ppc_load_tbu (CPUPPCState *env)
+{
+ if (kvm_enabled()) {
+ return env->spr[SPR_TBU];
+ }
+
+ return _cpu_ppc_load_tbu(env);
+}
+
+static inline void cpu_ppc_store_tb(ppc_tb_t *tb_env, uint64_t vmclk,
+ int64_t *tb_offsetp, uint64_t value)
+{
+ *tb_offsetp = value - muldiv64(vmclk, tb_env->tb_freq, get_ticks_per_sec());
+ LOG_TB("%s: tb %016" PRIx64 " offset %08" PRIx64 "\n",
+ __func__, value, *tb_offsetp);
+}
+
+void cpu_ppc_store_tbl (CPUPPCState *env, uint32_t value)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tb_env->tb_offset);
+ tb &= 0xFFFFFFFF00000000ULL;
+ cpu_ppc_store_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ &tb_env->tb_offset, tb | (uint64_t)value);
+}
+
+static inline void _cpu_ppc_store_tbu(CPUPPCState *env, uint32_t value)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tb_env->tb_offset);
+ tb &= 0x00000000FFFFFFFFULL;
+ cpu_ppc_store_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ &tb_env->tb_offset, ((uint64_t)value << 32) | tb);
+}
+
+void cpu_ppc_store_tbu (CPUPPCState *env, uint32_t value)
+{
+ _cpu_ppc_store_tbu(env, value);
+}
+
+uint64_t cpu_ppc_load_atbl (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tb_env->atb_offset);
+ LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb);
+
+ return tb;
+}
+
+uint32_t cpu_ppc_load_atbu (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tb_env->atb_offset);
+ LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb);
+
+ return tb >> 32;
+}
+
+void cpu_ppc_store_atbl (CPUPPCState *env, uint32_t value)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tb_env->atb_offset);
+ tb &= 0xFFFFFFFF00000000ULL;
+ cpu_ppc_store_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ &tb_env->atb_offset, tb | (uint64_t)value);
+}
+
+void cpu_ppc_store_atbu (CPUPPCState *env, uint32_t value)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tb_env->atb_offset);
+ tb &= 0x00000000FFFFFFFFULL;
+ cpu_ppc_store_tb(tb_env, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ &tb_env->atb_offset, ((uint64_t)value << 32) | tb);
+}
+
+static void cpu_ppc_tb_stop (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb, atb, vmclk;
+
+ /* If the time base is already frozen, do nothing */
+ if (tb_env->tb_freq != 0) {
+ vmclk = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ /* Get the time base */
+ tb = cpu_ppc_get_tb(tb_env, vmclk, tb_env->tb_offset);
+ /* Get the alternate time base */
+ atb = cpu_ppc_get_tb(tb_env, vmclk, tb_env->atb_offset);
+ /* Store the time base value (ie compute the current offset) */
+ cpu_ppc_store_tb(tb_env, vmclk, &tb_env->tb_offset, tb);
+ /* Store the alternate time base value (compute the current offset) */
+ cpu_ppc_store_tb(tb_env, vmclk, &tb_env->atb_offset, atb);
+ /* Set the time base frequency to zero */
+ tb_env->tb_freq = 0;
+ /* Now, the time bases are frozen to tb_offset / atb_offset value */
+ }
+}
+
+static void cpu_ppc_tb_start (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb, atb, vmclk;
+
+ /* If the time base is not frozen, do nothing */
+ if (tb_env->tb_freq == 0) {
+ vmclk = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ /* Get the time base from tb_offset */
+ tb = tb_env->tb_offset;
+ /* Get the alternate time base from atb_offset */
+ atb = tb_env->atb_offset;
+ /* Restore the tb frequency from the decrementer frequency */
+ tb_env->tb_freq = tb_env->decr_freq;
+ /* Store the time base value */
+ cpu_ppc_store_tb(tb_env, vmclk, &tb_env->tb_offset, tb);
+ /* Store the alternate time base value */
+ cpu_ppc_store_tb(tb_env, vmclk, &tb_env->atb_offset, atb);
+ }
+}
+
+bool ppc_decr_clear_on_delivery(CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ int flags = PPC_DECR_UNDERFLOW_TRIGGERED | PPC_DECR_UNDERFLOW_LEVEL;
+ return ((tb_env->flags & flags) == PPC_DECR_UNDERFLOW_TRIGGERED);
+}
+
+static inline uint32_t _cpu_ppc_load_decr(CPUPPCState *env, uint64_t next)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint32_t decr;
+ int64_t diff;
+
+ diff = next - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ if (diff >= 0) {
+ decr = muldiv64(diff, tb_env->decr_freq, get_ticks_per_sec());
+ } else if (tb_env->flags & PPC_TIMER_BOOKE) {
+ decr = 0;
+ } else {
+ decr = -muldiv64(-diff, tb_env->decr_freq, get_ticks_per_sec());
+ }
+ LOG_TB("%s: %08" PRIx32 "\n", __func__, decr);
+
+ return decr;
+}
+
+uint32_t cpu_ppc_load_decr (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+
+ if (kvm_enabled()) {
+ return env->spr[SPR_DECR];
+ }
+
+ return _cpu_ppc_load_decr(env, tb_env->decr_next);
+}
+
+uint32_t cpu_ppc_load_hdecr (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+
+ return _cpu_ppc_load_decr(env, tb_env->hdecr_next);
+}
+
+uint64_t cpu_ppc_load_purr (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t diff;
+
+ diff = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - tb_env->purr_start;
+
+ return tb_env->purr_load + muldiv64(diff, tb_env->tb_freq, get_ticks_per_sec());
+}
+
+/* When decrementer expires,
+ * all we need to do is generate or queue a CPU exception
+ */
+static inline void cpu_ppc_decr_excp(PowerPCCPU *cpu)
+{
+ /* Raise it */
+ LOG_TB("raise decrementer exception\n");
+ ppc_set_irq(cpu, PPC_INTERRUPT_DECR, 1);
+}
+
+static inline void cpu_ppc_decr_lower(PowerPCCPU *cpu)
+{
+ ppc_set_irq(cpu, PPC_INTERRUPT_DECR, 0);
+}
+
+static inline void cpu_ppc_hdecr_excp(PowerPCCPU *cpu)
+{
+ /* Raise it */
+ LOG_TB("raise decrementer exception\n");
+ ppc_set_irq(cpu, PPC_INTERRUPT_HDECR, 1);
+}
+
+static inline void cpu_ppc_hdecr_lower(PowerPCCPU *cpu)
+{
+ ppc_set_irq(cpu, PPC_INTERRUPT_HDECR, 0);
+}
+
+static void __cpu_ppc_store_decr(PowerPCCPU *cpu, uint64_t *nextp,
+ QEMUTimer *timer,
+ void (*raise_excp)(void *),
+ void (*lower_excp)(PowerPCCPU *),
+ uint32_t decr, uint32_t value)
+{
+ CPUPPCState *env = &cpu->env;
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t now, next;
+
+ LOG_TB("%s: %08" PRIx32 " => %08" PRIx32 "\n", __func__,
+ decr, value);
+
+ if (kvm_enabled()) {
+ /* KVM handles decrementer exceptions, we don't need our own timer */
+ return;
+ }
+
+ /*
+ * Going from 2 -> 1, 1 -> 0 or 0 -> -1 is the event to generate a DEC
+ * interrupt.
+ *
+ * If we get a really small DEC value, we can assume that by the time we
+ * handled it we should inject an interrupt already.
+ *
+ * On MSB level based DEC implementations the MSB always means the interrupt
+ * is pending, so raise it on those.
+ *
+ * On MSB edge based DEC implementations the MSB going from 0 -> 1 triggers
+ * an edge interrupt, so raise it here too.
+ */
+ if ((value < 3) ||
+ ((tb_env->flags & PPC_DECR_UNDERFLOW_LEVEL) && (value & 0x80000000)) ||
+ ((tb_env->flags & PPC_DECR_UNDERFLOW_TRIGGERED) && (value & 0x80000000)
+ && !(decr & 0x80000000))) {
+ (*raise_excp)(cpu);
+ return;
+ }
+
+ /* On MSB level based systems a 0 for the MSB stops interrupt delivery */
+ if (!(value & 0x80000000) && (tb_env->flags & PPC_DECR_UNDERFLOW_LEVEL)) {
+ (*lower_excp)(cpu);
+ }
+
+ /* Calculate the next timer event */
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ next = now + muldiv64(value, get_ticks_per_sec(), tb_env->decr_freq);
+ *nextp = next;
+
+ /* Adjust timer */
+ timer_mod(timer, next);
+}
+
+static inline void _cpu_ppc_store_decr(PowerPCCPU *cpu, uint32_t decr,
+ uint32_t value)
+{
+ ppc_tb_t *tb_env = cpu->env.tb_env;
+
+ __cpu_ppc_store_decr(cpu, &tb_env->decr_next, tb_env->decr_timer,
+ tb_env->decr_timer->cb, &cpu_ppc_decr_lower, decr,
+ value);
+}
+
+void cpu_ppc_store_decr (CPUPPCState *env, uint32_t value)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ _cpu_ppc_store_decr(cpu, cpu_ppc_load_decr(env), value);
+}
+
+static void cpu_ppc_decr_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ cpu_ppc_decr_excp(cpu);
+}
+
+static inline void _cpu_ppc_store_hdecr(PowerPCCPU *cpu, uint32_t hdecr,
+ uint32_t value)
+{
+ ppc_tb_t *tb_env = cpu->env.tb_env;
+
+ if (tb_env->hdecr_timer != NULL) {
+ __cpu_ppc_store_decr(cpu, &tb_env->hdecr_next, tb_env->hdecr_timer,
+ tb_env->hdecr_timer->cb, &cpu_ppc_hdecr_lower,
+ hdecr, value);
+ }
+}
+
+void cpu_ppc_store_hdecr (CPUPPCState *env, uint32_t value)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ _cpu_ppc_store_hdecr(cpu, cpu_ppc_load_hdecr(env), value);
+}
+
+static void cpu_ppc_hdecr_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ cpu_ppc_hdecr_excp(cpu);
+}
+
+static void cpu_ppc_store_purr(PowerPCCPU *cpu, uint64_t value)
+{
+ ppc_tb_t *tb_env = cpu->env.tb_env;
+
+ tb_env->purr_load = value;
+ tb_env->purr_start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+static void cpu_ppc_set_tb_clk (void *opaque, uint32_t freq)
+{
+ CPUPPCState *env = opaque;
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+ ppc_tb_t *tb_env = env->tb_env;
+
+ tb_env->tb_freq = freq;
+ tb_env->decr_freq = freq;
+ /* There is a bug in Linux 2.4 kernels:
+ * if a decrementer exception is pending when it enables msr_ee at startup,
+ * it's not ready to handle it...
+ */
+ _cpu_ppc_store_decr(cpu, 0xFFFFFFFF, 0xFFFFFFFF);
+ _cpu_ppc_store_hdecr(cpu, 0xFFFFFFFF, 0xFFFFFFFF);
+ cpu_ppc_store_purr(cpu, 0x0000000000000000ULL);
+}
+
+static void timebase_pre_save(void *opaque)
+{
+ PPCTimebase *tb = opaque;
+ uint64_t ticks = cpu_get_real_ticks();
+ PowerPCCPU *first_ppc_cpu = POWERPC_CPU(first_cpu);
+
+ if (!first_ppc_cpu->env.tb_env) {
+ error_report("No timebase object");
+ return;
+ }
+
+ tb->time_of_the_day_ns = qemu_clock_get_ns(QEMU_CLOCK_HOST);
+ /*
+ * tb_offset is only expected to be changed by migration so
+ * there is no need to update it from KVM here
+ */
+ tb->guest_timebase = ticks + first_ppc_cpu->env.tb_env->tb_offset;
+}
+
+static int timebase_post_load(void *opaque, int version_id)
+{
+ PPCTimebase *tb_remote = opaque;
+ CPUState *cpu;
+ PowerPCCPU *first_ppc_cpu = POWERPC_CPU(first_cpu);
+ int64_t tb_off_adj, tb_off, ns_diff;
+ int64_t migration_duration_ns, migration_duration_tb, guest_tb, host_ns;
+ unsigned long freq;
+
+ if (!first_ppc_cpu->env.tb_env) {
+ error_report("No timebase object");
+ return -1;
+ }
+
+ freq = first_ppc_cpu->env.tb_env->tb_freq;
+ /*
+ * Calculate timebase on the destination side of migration.
+ * The destination timebase must be not less than the source timebase.
+ * We try to adjust timebase by downtime if host clocks are not
+ * too much out of sync (1 second for now).
+ */
+ host_ns = qemu_clock_get_ns(QEMU_CLOCK_HOST);
+ ns_diff = MAX(0, host_ns - tb_remote->time_of_the_day_ns);
+ migration_duration_ns = MIN(NANOSECONDS_PER_SECOND, ns_diff);
+ migration_duration_tb = muldiv64(migration_duration_ns, freq,
+ NANOSECONDS_PER_SECOND);
+ guest_tb = tb_remote->guest_timebase + MIN(0, migration_duration_tb);
+
+ tb_off_adj = guest_tb - cpu_get_real_ticks();
+
+ tb_off = first_ppc_cpu->env.tb_env->tb_offset;
+ trace_ppc_tb_adjust(tb_off, tb_off_adj, tb_off_adj - tb_off,
+ (tb_off_adj - tb_off) / freq);
+
+ /* Set new offset to all CPUs */
+ CPU_FOREACH(cpu) {
+ PowerPCCPU *pcpu = POWERPC_CPU(cpu);
+ pcpu->env.tb_env->tb_offset = tb_off_adj;
+ }
+
+ return 0;
+}
+
+const VMStateDescription vmstate_ppc_timebase = {
+ .name = "timebase",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .pre_save = timebase_pre_save,
+ .post_load = timebase_post_load,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT64(guest_timebase, PPCTimebase),
+ VMSTATE_INT64(time_of_the_day_ns, PPCTimebase),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+/* Set up (once) timebase frequency (in Hz) */
+clk_setup_cb cpu_ppc_tb_init (CPUPPCState *env, uint32_t freq)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+ ppc_tb_t *tb_env;
+
+ tb_env = g_malloc0(sizeof(ppc_tb_t));
+ env->tb_env = tb_env;
+ tb_env->flags = PPC_DECR_UNDERFLOW_TRIGGERED;
+ if (env->insns_flags & PPC_SEGMENT_64B) {
+ /* All Book3S 64bit CPUs implement level based DEC logic */
+ tb_env->flags |= PPC_DECR_UNDERFLOW_LEVEL;
+ }
+ /* Create new timer */
+ tb_env->decr_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &cpu_ppc_decr_cb, cpu);
+ if (0) {
+ /* XXX: find a suitable condition to enable the hypervisor decrementer
+ */
+ tb_env->hdecr_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &cpu_ppc_hdecr_cb,
+ cpu);
+ } else {
+ tb_env->hdecr_timer = NULL;
+ }
+ cpu_ppc_set_tb_clk(env, freq);
+
+ return &cpu_ppc_set_tb_clk;
+}
+
+/* Specific helpers for POWER & PowerPC 601 RTC */
+#if 0
+static clk_setup_cb cpu_ppc601_rtc_init (CPUPPCState *env)
+{
+ return cpu_ppc_tb_init(env, 7812500);
+}
+#endif
+
+void cpu_ppc601_store_rtcu (CPUPPCState *env, uint32_t value)
+{
+ _cpu_ppc_store_tbu(env, value);
+}
+
+uint32_t cpu_ppc601_load_rtcu (CPUPPCState *env)
+{
+ return _cpu_ppc_load_tbu(env);
+}
+
+void cpu_ppc601_store_rtcl (CPUPPCState *env, uint32_t value)
+{
+ cpu_ppc_store_tbl(env, value & 0x3FFFFF80);
+}
+
+uint32_t cpu_ppc601_load_rtcl (CPUPPCState *env)
+{
+ return cpu_ppc_load_tbl(env) & 0x3FFFFF80;
+}
+
+/*****************************************************************************/
+/* PowerPC 40x timers */
+
+/* PIT, FIT & WDT */
+typedef struct ppc40x_timer_t ppc40x_timer_t;
+struct ppc40x_timer_t {
+ uint64_t pit_reload; /* PIT auto-reload value */
+ uint64_t fit_next; /* Tick for next FIT interrupt */
+ QEMUTimer *fit_timer;
+ uint64_t wdt_next; /* Tick for next WDT interrupt */
+ QEMUTimer *wdt_timer;
+
+ /* 405 have the PIT, 440 have a DECR. */
+ unsigned int decr_excp;
+};
+
+/* Fixed interval timer */
+static void cpu_4xx_fit_cb (void *opaque)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+ uint64_t now, next;
+
+ env = opaque;
+ cpu = ppc_env_get_cpu(env);
+ tb_env = env->tb_env;
+ ppc40x_timer = tb_env->opaque;
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ switch ((env->spr[SPR_40x_TCR] >> 24) & 0x3) {
+ case 0:
+ next = 1 << 9;
+ break;
+ case 1:
+ next = 1 << 13;
+ break;
+ case 2:
+ next = 1 << 17;
+ break;
+ case 3:
+ next = 1 << 21;
+ break;
+ default:
+ /* Cannot occur, but makes gcc happy */
+ return;
+ }
+ next = now + muldiv64(next, get_ticks_per_sec(), tb_env->tb_freq);
+ if (next == now)
+ next++;
+ timer_mod(ppc40x_timer->fit_timer, next);
+ env->spr[SPR_40x_TSR] |= 1 << 26;
+ if ((env->spr[SPR_40x_TCR] >> 23) & 0x1) {
+ ppc_set_irq(cpu, PPC_INTERRUPT_FIT, 1);
+ }
+ LOG_TB("%s: ir %d TCR " TARGET_FMT_lx " TSR " TARGET_FMT_lx "\n", __func__,
+ (int)((env->spr[SPR_40x_TCR] >> 23) & 0x1),
+ env->spr[SPR_40x_TCR], env->spr[SPR_40x_TSR]);
+}
+
+/* Programmable interval timer */
+static void start_stop_pit (CPUPPCState *env, ppc_tb_t *tb_env, int is_excp)
+{
+ ppc40x_timer_t *ppc40x_timer;
+ uint64_t now, next;
+
+ ppc40x_timer = tb_env->opaque;
+ if (ppc40x_timer->pit_reload <= 1 ||
+ !((env->spr[SPR_40x_TCR] >> 26) & 0x1) ||
+ (is_excp && !((env->spr[SPR_40x_TCR] >> 22) & 0x1))) {
+ /* Stop PIT */
+ LOG_TB("%s: stop PIT\n", __func__);
+ timer_del(tb_env->decr_timer);
+ } else {
+ LOG_TB("%s: start PIT %016" PRIx64 "\n",
+ __func__, ppc40x_timer->pit_reload);
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ next = now + muldiv64(ppc40x_timer->pit_reload,
+ get_ticks_per_sec(), tb_env->decr_freq);
+ if (is_excp)
+ next += tb_env->decr_next - now;
+ if (next == now)
+ next++;
+ timer_mod(tb_env->decr_timer, next);
+ tb_env->decr_next = next;
+ }
+}
+
+static void cpu_4xx_pit_cb (void *opaque)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+
+ env = opaque;
+ cpu = ppc_env_get_cpu(env);
+ tb_env = env->tb_env;
+ ppc40x_timer = tb_env->opaque;
+ env->spr[SPR_40x_TSR] |= 1 << 27;
+ if ((env->spr[SPR_40x_TCR] >> 26) & 0x1) {
+ ppc_set_irq(cpu, ppc40x_timer->decr_excp, 1);
+ }
+ start_stop_pit(env, tb_env, 1);
+ LOG_TB("%s: ar %d ir %d TCR " TARGET_FMT_lx " TSR " TARGET_FMT_lx " "
+ "%016" PRIx64 "\n", __func__,
+ (int)((env->spr[SPR_40x_TCR] >> 22) & 0x1),
+ (int)((env->spr[SPR_40x_TCR] >> 26) & 0x1),
+ env->spr[SPR_40x_TCR], env->spr[SPR_40x_TSR],
+ ppc40x_timer->pit_reload);
+}
+
+/* Watchdog timer */
+static void cpu_4xx_wdt_cb (void *opaque)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+ uint64_t now, next;
+
+ env = opaque;
+ cpu = ppc_env_get_cpu(env);
+ tb_env = env->tb_env;
+ ppc40x_timer = tb_env->opaque;
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ switch ((env->spr[SPR_40x_TCR] >> 30) & 0x3) {
+ case 0:
+ next = 1 << 17;
+ break;
+ case 1:
+ next = 1 << 21;
+ break;
+ case 2:
+ next = 1 << 25;
+ break;
+ case 3:
+ next = 1 << 29;
+ break;
+ default:
+ /* Cannot occur, but makes gcc happy */
+ return;
+ }
+ next = now + muldiv64(next, get_ticks_per_sec(), tb_env->decr_freq);
+ if (next == now)
+ next++;
+ LOG_TB("%s: TCR " TARGET_FMT_lx " TSR " TARGET_FMT_lx "\n", __func__,
+ env->spr[SPR_40x_TCR], env->spr[SPR_40x_TSR]);
+ switch ((env->spr[SPR_40x_TSR] >> 30) & 0x3) {
+ case 0x0:
+ case 0x1:
+ timer_mod(ppc40x_timer->wdt_timer, next);
+ ppc40x_timer->wdt_next = next;
+ env->spr[SPR_40x_TSR] |= 1U << 31;
+ break;
+ case 0x2:
+ timer_mod(ppc40x_timer->wdt_timer, next);
+ ppc40x_timer->wdt_next = next;
+ env->spr[SPR_40x_TSR] |= 1 << 30;
+ if ((env->spr[SPR_40x_TCR] >> 27) & 0x1) {
+ ppc_set_irq(cpu, PPC_INTERRUPT_WDT, 1);
+ }
+ break;
+ case 0x3:
+ env->spr[SPR_40x_TSR] &= ~0x30000000;
+ env->spr[SPR_40x_TSR] |= env->spr[SPR_40x_TCR] & 0x30000000;
+ switch ((env->spr[SPR_40x_TCR] >> 28) & 0x3) {
+ case 0x0:
+ /* No reset */
+ break;
+ case 0x1: /* Core reset */
+ ppc40x_core_reset(cpu);
+ break;
+ case 0x2: /* Chip reset */
+ ppc40x_chip_reset(cpu);
+ break;
+ case 0x3: /* System reset */
+ ppc40x_system_reset(cpu);
+ break;
+ }
+ }
+}
+
+void store_40x_pit (CPUPPCState *env, target_ulong val)
+{
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+
+ tb_env = env->tb_env;
+ ppc40x_timer = tb_env->opaque;
+ LOG_TB("%s val" TARGET_FMT_lx "\n", __func__, val);
+ ppc40x_timer->pit_reload = val;
+ start_stop_pit(env, tb_env, 0);
+}
+
+target_ulong load_40x_pit (CPUPPCState *env)
+{
+ return cpu_ppc_load_decr(env);
+}
+
+static void ppc_40x_set_tb_clk (void *opaque, uint32_t freq)
+{
+ CPUPPCState *env = opaque;
+ ppc_tb_t *tb_env = env->tb_env;
+
+ LOG_TB("%s set new frequency to %" PRIu32 "\n", __func__,
+ freq);
+ tb_env->tb_freq = freq;
+ tb_env->decr_freq = freq;
+ /* XXX: we should also update all timers */
+}
+
+clk_setup_cb ppc_40x_timers_init (CPUPPCState *env, uint32_t freq,
+ unsigned int decr_excp)
+{
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+
+ tb_env = g_malloc0(sizeof(ppc_tb_t));
+ env->tb_env = tb_env;
+ tb_env->flags = PPC_DECR_UNDERFLOW_TRIGGERED;
+ ppc40x_timer = g_malloc0(sizeof(ppc40x_timer_t));
+ tb_env->tb_freq = freq;
+ tb_env->decr_freq = freq;
+ tb_env->opaque = ppc40x_timer;
+ LOG_TB("%s freq %" PRIu32 "\n", __func__, freq);
+ if (ppc40x_timer != NULL) {
+ /* We use decr timer for PIT */
+ tb_env->decr_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &cpu_4xx_pit_cb, env);
+ ppc40x_timer->fit_timer =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, &cpu_4xx_fit_cb, env);
+ ppc40x_timer->wdt_timer =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, &cpu_4xx_wdt_cb, env);
+ ppc40x_timer->decr_excp = decr_excp;
+ }
+
+ return &ppc_40x_set_tb_clk;
+}
+
+/*****************************************************************************/
+/* Embedded PowerPC Device Control Registers */
+typedef struct ppc_dcrn_t ppc_dcrn_t;
+struct ppc_dcrn_t {
+ dcr_read_cb dcr_read;
+ dcr_write_cb dcr_write;
+ void *opaque;
+};
+
+/* XXX: on 460, DCR addresses are 32 bits wide,
+ * using DCRIPR to get the 22 upper bits of the DCR address
+ */
+#define DCRN_NB 1024
+struct ppc_dcr_t {
+ ppc_dcrn_t dcrn[DCRN_NB];
+ int (*read_error)(int dcrn);
+ int (*write_error)(int dcrn);
+};
+
+int ppc_dcr_read (ppc_dcr_t *dcr_env, int dcrn, uint32_t *valp)
+{
+ ppc_dcrn_t *dcr;
+
+ if (dcrn < 0 || dcrn >= DCRN_NB)
+ goto error;
+ dcr = &dcr_env->dcrn[dcrn];
+ if (dcr->dcr_read == NULL)
+ goto error;
+ *valp = (*dcr->dcr_read)(dcr->opaque, dcrn);
+
+ return 0;
+
+ error:
+ if (dcr_env->read_error != NULL)
+ return (*dcr_env->read_error)(dcrn);
+
+ return -1;
+}
+
+int ppc_dcr_write (ppc_dcr_t *dcr_env, int dcrn, uint32_t val)
+{
+ ppc_dcrn_t *dcr;
+
+ if (dcrn < 0 || dcrn >= DCRN_NB)
+ goto error;
+ dcr = &dcr_env->dcrn[dcrn];
+ if (dcr->dcr_write == NULL)
+ goto error;
+ (*dcr->dcr_write)(dcr->opaque, dcrn, val);
+
+ return 0;
+
+ error:
+ if (dcr_env->write_error != NULL)
+ return (*dcr_env->write_error)(dcrn);
+
+ return -1;
+}
+
+int ppc_dcr_register (CPUPPCState *env, int dcrn, void *opaque,
+ dcr_read_cb dcr_read, dcr_write_cb dcr_write)
+{
+ ppc_dcr_t *dcr_env;
+ ppc_dcrn_t *dcr;
+
+ dcr_env = env->dcr_env;
+ if (dcr_env == NULL)
+ return -1;
+ if (dcrn < 0 || dcrn >= DCRN_NB)
+ return -1;
+ dcr = &dcr_env->dcrn[dcrn];
+ if (dcr->opaque != NULL ||
+ dcr->dcr_read != NULL ||
+ dcr->dcr_write != NULL)
+ return -1;
+ dcr->opaque = opaque;
+ dcr->dcr_read = dcr_read;
+ dcr->dcr_write = dcr_write;
+
+ return 0;
+}
+
+int ppc_dcr_init (CPUPPCState *env, int (*read_error)(int dcrn),
+ int (*write_error)(int dcrn))
+{
+ ppc_dcr_t *dcr_env;
+
+ dcr_env = g_malloc0(sizeof(ppc_dcr_t));
+ dcr_env->read_error = read_error;
+ dcr_env->write_error = write_error;
+ env->dcr_env = dcr_env;
+
+ return 0;
+}
+
+/*****************************************************************************/
+/* Debug port */
+void PPC_debug_write (void *opaque, uint32_t addr, uint32_t val)
+{
+ addr &= 0xF;
+ switch (addr) {
+ case 0:
+ printf("%c", val);
+ break;
+ case 1:
+ printf("\n");
+ fflush(stdout);
+ break;
+ case 2:
+ printf("Set loglevel to %04" PRIx32 "\n", val);
+ qemu_set_log(val | 0x100);
+ break;
+ }
+}
+
+/* CPU device-tree ID helpers */
+int ppc_get_vcpu_dt_id(PowerPCCPU *cpu)
+{
+ return cpu->cpu_dt_id;
+}
+
+PowerPCCPU *ppc_get_vcpu_by_dt_id(int cpu_dt_id)
+{
+ CPUState *cs;
+
+ CPU_FOREACH(cs) {
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+
+ if (cpu->cpu_dt_id == cpu_dt_id) {
+ return cpu;
+ }
+ }
+
+ return NULL;
+}
diff --git a/hw/ppc/ppc405.h b/hw/ppc/ppc405.h
new file mode 100644
index 00000000..1c5f04fa
--- /dev/null
+++ b/hw/ppc/ppc405.h
@@ -0,0 +1,81 @@
+/*
+ * QEMU PowerPC 405 shared definitions
+ *
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+
+#if !defined(PPC_405_H)
+#define PPC_405_H
+
+#include "hw/ppc/ppc4xx.h"
+
+/* Bootinfo as set-up by u-boot */
+typedef struct ppc4xx_bd_info_t ppc4xx_bd_info_t;
+struct ppc4xx_bd_info_t {
+ uint32_t bi_memstart;
+ uint32_t bi_memsize;
+ uint32_t bi_flashstart;
+ uint32_t bi_flashsize;
+ uint32_t bi_flashoffset; /* 0x10 */
+ uint32_t bi_sramstart;
+ uint32_t bi_sramsize;
+ uint32_t bi_bootflags;
+ uint32_t bi_ipaddr; /* 0x20 */
+ uint8_t bi_enetaddr[6];
+ uint16_t bi_ethspeed;
+ uint32_t bi_intfreq;
+ uint32_t bi_busfreq; /* 0x30 */
+ uint32_t bi_baudrate;
+ uint8_t bi_s_version[4];
+ uint8_t bi_r_version[32];
+ uint32_t bi_procfreq;
+ uint32_t bi_plb_busfreq;
+ uint32_t bi_pci_busfreq;
+ uint8_t bi_pci_enetaddr[6];
+ uint32_t bi_pci_enetaddr2[6];
+ uint32_t bi_opbfreq;
+ uint32_t bi_iic_fast[2];
+};
+
+/* PowerPC 405 core */
+ram_addr_t ppc405_set_bootinfo (CPUPPCState *env, ppc4xx_bd_info_t *bd,
+ uint32_t flags);
+
+CPUPPCState *ppc405cr_init(MemoryRegion *address_space_mem,
+ MemoryRegion ram_memories[4],
+ hwaddr ram_bases[4],
+ hwaddr ram_sizes[4],
+ uint32_t sysclk, qemu_irq **picp,
+ int do_init);
+CPUPPCState *ppc405ep_init(MemoryRegion *address_space_mem,
+ MemoryRegion ram_memories[2],
+ hwaddr ram_bases[2],
+ hwaddr ram_sizes[2],
+ uint32_t sysclk, qemu_irq **picp,
+ int do_init);
+/* IBM STBxxx microcontrollers */
+CPUPPCState *ppc_stb025_init (MemoryRegion ram_memories[2],
+ hwaddr ram_bases[2],
+ hwaddr ram_sizes[2],
+ uint32_t sysclk, qemu_irq **picp,
+ ram_addr_t *offsetp);
+
+#endif /* !defined(PPC_405_H) */
diff --git a/hw/ppc/ppc405_boards.c b/hw/ppc/ppc405_boards.c
new file mode 100644
index 00000000..ec6c4cba
--- /dev/null
+++ b/hw/ppc/ppc405_boards.c
@@ -0,0 +1,679 @@
+/*
+ * QEMU PowerPC 405 evaluation boards emulation
+ *
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "ppc405.h"
+#include "hw/timer/m48t59.h"
+#include "hw/block/flash.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+#include "sysemu/block-backend.h"
+#include "hw/boards.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "hw/loader.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "exec/address-spaces.h"
+
+#define BIOS_FILENAME "ppc405_rom.bin"
+#define BIOS_SIZE (2048 * 1024)
+
+#define KERNEL_LOAD_ADDR 0x00000000
+#define INITRD_LOAD_ADDR 0x01800000
+
+#define USE_FLASH_BIOS
+
+//#define DEBUG_BOARD_INIT
+
+/*****************************************************************************/
+/* PPC405EP reference board (IBM) */
+/* Standalone board with:
+ * - PowerPC 405EP CPU
+ * - SDRAM (0x00000000)
+ * - Flash (0xFFF80000)
+ * - SRAM (0xFFF00000)
+ * - NVRAM (0xF0000000)
+ * - FPGA (0xF0300000)
+ */
+typedef struct ref405ep_fpga_t ref405ep_fpga_t;
+struct ref405ep_fpga_t {
+ uint8_t reg0;
+ uint8_t reg1;
+};
+
+static uint32_t ref405ep_fpga_readb (void *opaque, hwaddr addr)
+{
+ ref405ep_fpga_t *fpga;
+ uint32_t ret;
+
+ fpga = opaque;
+ switch (addr) {
+ case 0x0:
+ ret = fpga->reg0;
+ break;
+ case 0x1:
+ ret = fpga->reg1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void ref405ep_fpga_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ref405ep_fpga_t *fpga;
+
+ fpga = opaque;
+ switch (addr) {
+ case 0x0:
+ /* Read only */
+ break;
+ case 0x1:
+ fpga->reg1 = value;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint32_t ref405ep_fpga_readw (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ ret = ref405ep_fpga_readb(opaque, addr) << 8;
+ ret |= ref405ep_fpga_readb(opaque, addr + 1);
+
+ return ret;
+}
+
+static void ref405ep_fpga_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ref405ep_fpga_writeb(opaque, addr, (value >> 8) & 0xFF);
+ ref405ep_fpga_writeb(opaque, addr + 1, value & 0xFF);
+}
+
+static uint32_t ref405ep_fpga_readl (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ ret = ref405ep_fpga_readb(opaque, addr) << 24;
+ ret |= ref405ep_fpga_readb(opaque, addr + 1) << 16;
+ ret |= ref405ep_fpga_readb(opaque, addr + 2) << 8;
+ ret |= ref405ep_fpga_readb(opaque, addr + 3);
+
+ return ret;
+}
+
+static void ref405ep_fpga_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ref405ep_fpga_writeb(opaque, addr, (value >> 24) & 0xFF);
+ ref405ep_fpga_writeb(opaque, addr + 1, (value >> 16) & 0xFF);
+ ref405ep_fpga_writeb(opaque, addr + 2, (value >> 8) & 0xFF);
+ ref405ep_fpga_writeb(opaque, addr + 3, value & 0xFF);
+}
+
+static const MemoryRegionOps ref405ep_fpga_ops = {
+ .old_mmio = {
+ .read = {
+ ref405ep_fpga_readb, ref405ep_fpga_readw, ref405ep_fpga_readl,
+ },
+ .write = {
+ ref405ep_fpga_writeb, ref405ep_fpga_writew, ref405ep_fpga_writel,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ref405ep_fpga_reset (void *opaque)
+{
+ ref405ep_fpga_t *fpga;
+
+ fpga = opaque;
+ fpga->reg0 = 0x00;
+ fpga->reg1 = 0x0F;
+}
+
+static void ref405ep_fpga_init(MemoryRegion *sysmem, uint32_t base)
+{
+ ref405ep_fpga_t *fpga;
+ MemoryRegion *fpga_memory = g_new(MemoryRegion, 1);
+
+ fpga = g_malloc0(sizeof(ref405ep_fpga_t));
+ memory_region_init_io(fpga_memory, NULL, &ref405ep_fpga_ops, fpga,
+ "fpga", 0x00000100);
+ memory_region_add_subregion(sysmem, base, fpga_memory);
+ qemu_register_reset(&ref405ep_fpga_reset, fpga);
+}
+
+static void ref405ep_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ char *filename;
+ ppc4xx_bd_info_t bd;
+ CPUPPCState *env;
+ qemu_irq *pic;
+ MemoryRegion *bios;
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+ ram_addr_t bdloc;
+ MemoryRegion *ram_memories = g_malloc(2 * sizeof(*ram_memories));
+ hwaddr ram_bases[2], ram_sizes[2];
+ target_ulong sram_size;
+ long bios_size;
+ //int phy_addr = 0;
+ //static int phy_addr = 1;
+ target_ulong kernel_base, initrd_base;
+ long kernel_size, initrd_size;
+ int linux_boot;
+ int fl_idx, fl_sectors, len;
+ DriveInfo *dinfo;
+ MemoryRegion *sysmem = get_system_memory();
+
+ /* XXX: fix this */
+ memory_region_allocate_system_memory(&ram_memories[0], NULL, "ef405ep.ram",
+ 0x08000000);
+ ram_bases[0] = 0;
+ ram_sizes[0] = 0x08000000;
+ memory_region_init(&ram_memories[1], NULL, "ef405ep.ram1", 0);
+ ram_bases[1] = 0x00000000;
+ ram_sizes[1] = 0x00000000;
+ ram_size = 128 * 1024 * 1024;
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register cpu\n", __func__);
+#endif
+ env = ppc405ep_init(sysmem, ram_memories, ram_bases, ram_sizes,
+ 33333333, &pic, kernel_filename == NULL ? 0 : 1);
+ /* allocate SRAM */
+ sram_size = 512 * 1024;
+ memory_region_init_ram(sram, NULL, "ef405ep.sram", sram_size, &error_abort);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(sysmem, 0xFFF00000, sram);
+ /* allocate and load BIOS */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register BIOS\n", __func__);
+#endif
+ fl_idx = 0;
+#ifdef USE_FLASH_BIOS
+ dinfo = drive_get(IF_PFLASH, 0, fl_idx);
+ if (dinfo) {
+ BlockBackend *blk = blk_by_legacy_dinfo(dinfo);
+
+ bios_size = blk_getlength(blk);
+ fl_sectors = (bios_size + 65535) >> 16;
+#ifdef DEBUG_BOARD_INIT
+ printf("Register parallel flash %d size %lx"
+ " at addr %lx '%s' %d\n",
+ fl_idx, bios_size, -bios_size,
+ blk_name(blk), fl_sectors);
+#endif
+ pflash_cfi02_register((uint32_t)(-bios_size),
+ NULL, "ef405ep.bios", bios_size,
+ blk, 65536, fl_sectors, 1,
+ 2, 0x0001, 0x22DA, 0x0000, 0x0000, 0x555, 0x2AA,
+ 1);
+ fl_idx++;
+ } else
+#endif
+ {
+#ifdef DEBUG_BOARD_INIT
+ printf("Load BIOS from file\n");
+#endif
+ bios = g_new(MemoryRegion, 1);
+ memory_region_init_ram(bios, NULL, "ef405ep.bios", BIOS_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(bios);
+
+ if (bios_name == NULL)
+ bios_name = BIOS_FILENAME;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = load_image(filename, memory_region_get_ram_ptr(bios));
+ g_free(filename);
+ if (bios_size < 0 || bios_size > BIOS_SIZE) {
+ error_report("Could not load PowerPC BIOS '%s'", bios_name);
+ exit(1);
+ }
+ bios_size = (bios_size + 0xfff) & ~0xfff;
+ memory_region_add_subregion(sysmem, (uint32_t)(-bios_size), bios);
+ } else if (!qtest_enabled() || kernel_filename != NULL) {
+ error_report("Could not load PowerPC BIOS '%s'", bios_name);
+ exit(1);
+ } else {
+ /* Avoid an uninitialized variable warning */
+ bios_size = -1;
+ }
+ memory_region_set_readonly(bios, true);
+ }
+ /* Register FPGA */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register FPGA\n", __func__);
+#endif
+ ref405ep_fpga_init(sysmem, 0xF0300000);
+ /* Register NVRAM */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register NVRAM\n", __func__);
+#endif
+ m48t59_init(NULL, 0xF0000000, 0, 8192, 1968, 8);
+ /* Load kernel */
+ linux_boot = (kernel_filename != NULL);
+ if (linux_boot) {
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: load kernel\n", __func__);
+#endif
+ memset(&bd, 0, sizeof(bd));
+ bd.bi_memstart = 0x00000000;
+ bd.bi_memsize = ram_size;
+ bd.bi_flashstart = -bios_size;
+ bd.bi_flashsize = -bios_size;
+ bd.bi_flashoffset = 0;
+ bd.bi_sramstart = 0xFFF00000;
+ bd.bi_sramsize = sram_size;
+ bd.bi_bootflags = 0;
+ bd.bi_intfreq = 133333333;
+ bd.bi_busfreq = 33333333;
+ bd.bi_baudrate = 115200;
+ bd.bi_s_version[0] = 'Q';
+ bd.bi_s_version[1] = 'M';
+ bd.bi_s_version[2] = 'U';
+ bd.bi_s_version[3] = '\0';
+ bd.bi_r_version[0] = 'Q';
+ bd.bi_r_version[1] = 'E';
+ bd.bi_r_version[2] = 'M';
+ bd.bi_r_version[3] = 'U';
+ bd.bi_r_version[4] = '\0';
+ bd.bi_procfreq = 133333333;
+ bd.bi_plb_busfreq = 33333333;
+ bd.bi_pci_busfreq = 33333333;
+ bd.bi_opbfreq = 33333333;
+ bdloc = ppc405_set_bootinfo(env, &bd, 0x00000001);
+ env->gpr[3] = bdloc;
+ kernel_base = KERNEL_LOAD_ADDR;
+ /* now we can load the kernel */
+ kernel_size = load_image_targphys(kernel_filename, kernel_base,
+ ram_size - kernel_base);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ printf("Load kernel size %ld at " TARGET_FMT_lx,
+ kernel_size, kernel_base);
+ /* load initrd */
+ if (initrd_filename) {
+ initrd_base = INITRD_LOAD_ADDR;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+ env->gpr[4] = initrd_base;
+ env->gpr[5] = initrd_size;
+ if (kernel_cmdline != NULL) {
+ len = strlen(kernel_cmdline);
+ bdloc -= ((len + 255) & ~255);
+ cpu_physical_memory_write(bdloc, kernel_cmdline, len + 1);
+ env->gpr[6] = bdloc;
+ env->gpr[7] = bdloc + len;
+ } else {
+ env->gpr[6] = 0;
+ env->gpr[7] = 0;
+ }
+ env->nip = KERNEL_LOAD_ADDR;
+ } else {
+ kernel_base = 0;
+ kernel_size = 0;
+ initrd_base = 0;
+ initrd_size = 0;
+ bdloc = 0;
+ }
+#ifdef DEBUG_BOARD_INIT
+ printf("bdloc " RAM_ADDR_FMT "\n", bdloc);
+ printf("%s: Done\n", __func__);
+#endif
+}
+
+static QEMUMachine ref405ep_machine = {
+ .name = "ref405ep",
+ .desc = "ref405ep",
+ .init = ref405ep_init,
+};
+
+/*****************************************************************************/
+/* AMCC Taihu evaluation board */
+/* - PowerPC 405EP processor
+ * - SDRAM 128 MB at 0x00000000
+ * - Boot flash 2 MB at 0xFFE00000
+ * - Application flash 32 MB at 0xFC000000
+ * - 2 serial ports
+ * - 2 ethernet PHY
+ * - 1 USB 1.1 device 0x50000000
+ * - 1 LCD display 0x50100000
+ * - 1 CPLD 0x50100000
+ * - 1 I2C EEPROM
+ * - 1 I2C thermal sensor
+ * - a set of LEDs
+ * - bit-bang SPI port using GPIOs
+ * - 1 EBC interface connector 0 0x50200000
+ * - 1 cardbus controller + expansion slot.
+ * - 1 PCI expansion slot.
+ */
+typedef struct taihu_cpld_t taihu_cpld_t;
+struct taihu_cpld_t {
+ uint8_t reg0;
+ uint8_t reg1;
+};
+
+static uint32_t taihu_cpld_readb (void *opaque, hwaddr addr)
+{
+ taihu_cpld_t *cpld;
+ uint32_t ret;
+
+ cpld = opaque;
+ switch (addr) {
+ case 0x0:
+ ret = cpld->reg0;
+ break;
+ case 0x1:
+ ret = cpld->reg1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void taihu_cpld_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ taihu_cpld_t *cpld;
+
+ cpld = opaque;
+ switch (addr) {
+ case 0x0:
+ /* Read only */
+ break;
+ case 0x1:
+ cpld->reg1 = value;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint32_t taihu_cpld_readw (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ ret = taihu_cpld_readb(opaque, addr) << 8;
+ ret |= taihu_cpld_readb(opaque, addr + 1);
+
+ return ret;
+}
+
+static void taihu_cpld_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ taihu_cpld_writeb(opaque, addr, (value >> 8) & 0xFF);
+ taihu_cpld_writeb(opaque, addr + 1, value & 0xFF);
+}
+
+static uint32_t taihu_cpld_readl (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ ret = taihu_cpld_readb(opaque, addr) << 24;
+ ret |= taihu_cpld_readb(opaque, addr + 1) << 16;
+ ret |= taihu_cpld_readb(opaque, addr + 2) << 8;
+ ret |= taihu_cpld_readb(opaque, addr + 3);
+
+ return ret;
+}
+
+static void taihu_cpld_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ taihu_cpld_writel(opaque, addr, (value >> 24) & 0xFF);
+ taihu_cpld_writel(opaque, addr + 1, (value >> 16) & 0xFF);
+ taihu_cpld_writel(opaque, addr + 2, (value >> 8) & 0xFF);
+ taihu_cpld_writeb(opaque, addr + 3, value & 0xFF);
+}
+
+static const MemoryRegionOps taihu_cpld_ops = {
+ .old_mmio = {
+ .read = { taihu_cpld_readb, taihu_cpld_readw, taihu_cpld_readl, },
+ .write = { taihu_cpld_writeb, taihu_cpld_writew, taihu_cpld_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void taihu_cpld_reset (void *opaque)
+{
+ taihu_cpld_t *cpld;
+
+ cpld = opaque;
+ cpld->reg0 = 0x01;
+ cpld->reg1 = 0x80;
+}
+
+static void taihu_cpld_init(MemoryRegion *sysmem, uint32_t base)
+{
+ taihu_cpld_t *cpld;
+ MemoryRegion *cpld_memory = g_new(MemoryRegion, 1);
+
+ cpld = g_malloc0(sizeof(taihu_cpld_t));
+ memory_region_init_io(cpld_memory, NULL, &taihu_cpld_ops, cpld, "cpld", 0x100);
+ memory_region_add_subregion(sysmem, base, cpld_memory);
+ qemu_register_reset(&taihu_cpld_reset, cpld);
+}
+
+static void taihu_405ep_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *initrd_filename = machine->initrd_filename;
+ char *filename;
+ qemu_irq *pic;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *bios;
+ MemoryRegion *ram_memories = g_malloc(2 * sizeof(*ram_memories));
+ MemoryRegion *ram = g_malloc0(sizeof(*ram));
+ hwaddr ram_bases[2], ram_sizes[2];
+ long bios_size;
+ target_ulong kernel_base, initrd_base;
+ long kernel_size, initrd_size;
+ int linux_boot;
+ int fl_idx, fl_sectors;
+ DriveInfo *dinfo;
+
+ /* RAM is soldered to the board so the size cannot be changed */
+ ram_size = 0x08000000;
+ memory_region_allocate_system_memory(ram, NULL, "taihu_405ep.ram",
+ ram_size);
+
+ ram_bases[0] = 0;
+ ram_sizes[0] = 0x04000000;
+ memory_region_init_alias(&ram_memories[0], NULL,
+ "taihu_405ep.ram-0", ram, ram_bases[0],
+ ram_sizes[0]);
+ ram_bases[1] = 0x04000000;
+ ram_sizes[1] = 0x04000000;
+ memory_region_init_alias(&ram_memories[1], NULL,
+ "taihu_405ep.ram-1", ram, ram_bases[1],
+ ram_sizes[1]);
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register cpu\n", __func__);
+#endif
+ ppc405ep_init(sysmem, ram_memories, ram_bases, ram_sizes,
+ 33333333, &pic, kernel_filename == NULL ? 0 : 1);
+ /* allocate and load BIOS */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register BIOS\n", __func__);
+#endif
+ fl_idx = 0;
+#if defined(USE_FLASH_BIOS)
+ dinfo = drive_get(IF_PFLASH, 0, fl_idx);
+ if (dinfo) {
+ BlockBackend *blk = blk_by_legacy_dinfo(dinfo);
+
+ bios_size = blk_getlength(blk);
+ /* XXX: should check that size is 2MB */
+ // bios_size = 2 * 1024 * 1024;
+ fl_sectors = (bios_size + 65535) >> 16;
+#ifdef DEBUG_BOARD_INIT
+ printf("Register parallel flash %d size %lx"
+ " at addr %lx '%s' %d\n",
+ fl_idx, bios_size, -bios_size,
+ blk_name(blk), fl_sectors);
+#endif
+ pflash_cfi02_register((uint32_t)(-bios_size),
+ NULL, "taihu_405ep.bios", bios_size,
+ blk, 65536, fl_sectors, 1,
+ 4, 0x0001, 0x22DA, 0x0000, 0x0000, 0x555, 0x2AA,
+ 1);
+ fl_idx++;
+ } else
+#endif
+ {
+#ifdef DEBUG_BOARD_INIT
+ printf("Load BIOS from file\n");
+#endif
+ if (bios_name == NULL)
+ bios_name = BIOS_FILENAME;
+ bios = g_new(MemoryRegion, 1);
+ memory_region_init_ram(bios, NULL, "taihu_405ep.bios", BIOS_SIZE,
+ &error_abort);
+ vmstate_register_ram_global(bios);
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = load_image(filename, memory_region_get_ram_ptr(bios));
+ g_free(filename);
+ if (bios_size < 0 || bios_size > BIOS_SIZE) {
+ error_report("Could not load PowerPC BIOS '%s'", bios_name);
+ exit(1);
+ }
+ bios_size = (bios_size + 0xfff) & ~0xfff;
+ memory_region_add_subregion(sysmem, (uint32_t)(-bios_size), bios);
+ } else if (!qtest_enabled()) {
+ error_report("Could not load PowerPC BIOS '%s'", bios_name);
+ exit(1);
+ }
+ memory_region_set_readonly(bios, true);
+ }
+ /* Register Linux flash */
+ dinfo = drive_get(IF_PFLASH, 0, fl_idx);
+ if (dinfo) {
+ BlockBackend *blk = blk_by_legacy_dinfo(dinfo);
+
+ bios_size = blk_getlength(blk);
+ /* XXX: should check that size is 32MB */
+ bios_size = 32 * 1024 * 1024;
+ fl_sectors = (bios_size + 65535) >> 16;
+#ifdef DEBUG_BOARD_INIT
+ printf("Register parallel flash %d size %lx"
+ " at addr " TARGET_FMT_lx " '%s'\n",
+ fl_idx, bios_size, (target_ulong)0xfc000000,
+ blk_name(blk));
+#endif
+ pflash_cfi02_register(0xfc000000, NULL, "taihu_405ep.flash", bios_size,
+ blk, 65536, fl_sectors, 1,
+ 4, 0x0001, 0x22DA, 0x0000, 0x0000, 0x555, 0x2AA,
+ 1);
+ fl_idx++;
+ }
+ /* Register CLPD & LCD display */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register CPLD\n", __func__);
+#endif
+ taihu_cpld_init(sysmem, 0x50100000);
+ /* Load kernel */
+ linux_boot = (kernel_filename != NULL);
+ if (linux_boot) {
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: load kernel\n", __func__);
+#endif
+ kernel_base = KERNEL_LOAD_ADDR;
+ /* now we can load the kernel */
+ kernel_size = load_image_targphys(kernel_filename, kernel_base,
+ ram_size - kernel_base);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ /* load initrd */
+ if (initrd_filename) {
+ initrd_base = INITRD_LOAD_ADDR;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+ if (initrd_size < 0) {
+ fprintf(stderr,
+ "qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+ } else {
+ kernel_base = 0;
+ kernel_size = 0;
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: Done\n", __func__);
+#endif
+}
+
+static QEMUMachine taihu_machine = {
+ .name = "taihu",
+ .desc = "taihu",
+ .init = taihu_405ep_init,
+};
+
+static void ppc405_machine_init(void)
+{
+ qemu_register_machine(&ref405ep_machine);
+ qemu_register_machine(&taihu_machine);
+}
+
+machine_init(ppc405_machine_init);
diff --git a/hw/ppc/ppc405_uc.c b/hw/ppc/ppc405_uc.c
new file mode 100644
index 00000000..c77434ae
--- /dev/null
+++ b/hw/ppc/ppc405_uc.c
@@ -0,0 +1,2551 @@
+/*
+ * QEMU PowerPC 405 embedded processors emulation
+ *
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "hw/boards.h"
+#include "ppc405.h"
+#include "hw/char/serial.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "qemu/log.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_OPBA
+//#define DEBUG_SDRAM
+//#define DEBUG_GPIO
+//#define DEBUG_SERIAL
+//#define DEBUG_OCM
+//#define DEBUG_I2C
+//#define DEBUG_GPT
+//#define DEBUG_MAL
+//#define DEBUG_CLOCKS
+//#define DEBUG_CLOCKS_LL
+
+ram_addr_t ppc405_set_bootinfo (CPUPPCState *env, ppc4xx_bd_info_t *bd,
+ uint32_t flags)
+{
+ CPUState *cs = CPU(ppc_env_get_cpu(env));
+ ram_addr_t bdloc;
+ int i, n;
+
+ /* We put the bd structure at the top of memory */
+ if (bd->bi_memsize >= 0x01000000UL)
+ bdloc = 0x01000000UL - sizeof(struct ppc4xx_bd_info_t);
+ else
+ bdloc = bd->bi_memsize - sizeof(struct ppc4xx_bd_info_t);
+ stl_be_phys(cs->as, bdloc + 0x00, bd->bi_memstart);
+ stl_be_phys(cs->as, bdloc + 0x04, bd->bi_memsize);
+ stl_be_phys(cs->as, bdloc + 0x08, bd->bi_flashstart);
+ stl_be_phys(cs->as, bdloc + 0x0C, bd->bi_flashsize);
+ stl_be_phys(cs->as, bdloc + 0x10, bd->bi_flashoffset);
+ stl_be_phys(cs->as, bdloc + 0x14, bd->bi_sramstart);
+ stl_be_phys(cs->as, bdloc + 0x18, bd->bi_sramsize);
+ stl_be_phys(cs->as, bdloc + 0x1C, bd->bi_bootflags);
+ stl_be_phys(cs->as, bdloc + 0x20, bd->bi_ipaddr);
+ for (i = 0; i < 6; i++) {
+ stb_phys(cs->as, bdloc + 0x24 + i, bd->bi_enetaddr[i]);
+ }
+ stw_be_phys(cs->as, bdloc + 0x2A, bd->bi_ethspeed);
+ stl_be_phys(cs->as, bdloc + 0x2C, bd->bi_intfreq);
+ stl_be_phys(cs->as, bdloc + 0x30, bd->bi_busfreq);
+ stl_be_phys(cs->as, bdloc + 0x34, bd->bi_baudrate);
+ for (i = 0; i < 4; i++) {
+ stb_phys(cs->as, bdloc + 0x38 + i, bd->bi_s_version[i]);
+ }
+ for (i = 0; i < 32; i++) {
+ stb_phys(cs->as, bdloc + 0x3C + i, bd->bi_r_version[i]);
+ }
+ stl_be_phys(cs->as, bdloc + 0x5C, bd->bi_plb_busfreq);
+ stl_be_phys(cs->as, bdloc + 0x60, bd->bi_pci_busfreq);
+ for (i = 0; i < 6; i++) {
+ stb_phys(cs->as, bdloc + 0x64 + i, bd->bi_pci_enetaddr[i]);
+ }
+ n = 0x6A;
+ if (flags & 0x00000001) {
+ for (i = 0; i < 6; i++)
+ stb_phys(cs->as, bdloc + n++, bd->bi_pci_enetaddr2[i]);
+ }
+ stl_be_phys(cs->as, bdloc + n, bd->bi_opbfreq);
+ n += 4;
+ for (i = 0; i < 2; i++) {
+ stl_be_phys(cs->as, bdloc + n, bd->bi_iic_fast[i]);
+ n += 4;
+ }
+
+ return bdloc;
+}
+
+/*****************************************************************************/
+/* Shared peripherals */
+
+/*****************************************************************************/
+/* Peripheral local bus arbitrer */
+enum {
+ PLB0_BESR = 0x084,
+ PLB0_BEAR = 0x086,
+ PLB0_ACR = 0x087,
+};
+
+typedef struct ppc4xx_plb_t ppc4xx_plb_t;
+struct ppc4xx_plb_t {
+ uint32_t acr;
+ uint32_t bear;
+ uint32_t besr;
+};
+
+static uint32_t dcr_read_plb (void *opaque, int dcrn)
+{
+ ppc4xx_plb_t *plb;
+ uint32_t ret;
+
+ plb = opaque;
+ switch (dcrn) {
+ case PLB0_ACR:
+ ret = plb->acr;
+ break;
+ case PLB0_BEAR:
+ ret = plb->bear;
+ break;
+ case PLB0_BESR:
+ ret = plb->besr;
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_plb (void *opaque, int dcrn, uint32_t val)
+{
+ ppc4xx_plb_t *plb;
+
+ plb = opaque;
+ switch (dcrn) {
+ case PLB0_ACR:
+ /* We don't care about the actual parameters written as
+ * we don't manage any priorities on the bus
+ */
+ plb->acr = val & 0xF8000000;
+ break;
+ case PLB0_BEAR:
+ /* Read only */
+ break;
+ case PLB0_BESR:
+ /* Write-clear */
+ plb->besr &= ~val;
+ break;
+ }
+}
+
+static void ppc4xx_plb_reset (void *opaque)
+{
+ ppc4xx_plb_t *plb;
+
+ plb = opaque;
+ plb->acr = 0x00000000;
+ plb->bear = 0x00000000;
+ plb->besr = 0x00000000;
+}
+
+static void ppc4xx_plb_init(CPUPPCState *env)
+{
+ ppc4xx_plb_t *plb;
+
+ plb = g_malloc0(sizeof(ppc4xx_plb_t));
+ ppc_dcr_register(env, PLB0_ACR, plb, &dcr_read_plb, &dcr_write_plb);
+ ppc_dcr_register(env, PLB0_BEAR, plb, &dcr_read_plb, &dcr_write_plb);
+ ppc_dcr_register(env, PLB0_BESR, plb, &dcr_read_plb, &dcr_write_plb);
+ qemu_register_reset(ppc4xx_plb_reset, plb);
+}
+
+/*****************************************************************************/
+/* PLB to OPB bridge */
+enum {
+ POB0_BESR0 = 0x0A0,
+ POB0_BESR1 = 0x0A2,
+ POB0_BEAR = 0x0A4,
+};
+
+typedef struct ppc4xx_pob_t ppc4xx_pob_t;
+struct ppc4xx_pob_t {
+ uint32_t bear;
+ uint32_t besr0;
+ uint32_t besr1;
+};
+
+static uint32_t dcr_read_pob (void *opaque, int dcrn)
+{
+ ppc4xx_pob_t *pob;
+ uint32_t ret;
+
+ pob = opaque;
+ switch (dcrn) {
+ case POB0_BEAR:
+ ret = pob->bear;
+ break;
+ case POB0_BESR0:
+ ret = pob->besr0;
+ break;
+ case POB0_BESR1:
+ ret = pob->besr1;
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_pob (void *opaque, int dcrn, uint32_t val)
+{
+ ppc4xx_pob_t *pob;
+
+ pob = opaque;
+ switch (dcrn) {
+ case POB0_BEAR:
+ /* Read only */
+ break;
+ case POB0_BESR0:
+ /* Write-clear */
+ pob->besr0 &= ~val;
+ break;
+ case POB0_BESR1:
+ /* Write-clear */
+ pob->besr1 &= ~val;
+ break;
+ }
+}
+
+static void ppc4xx_pob_reset (void *opaque)
+{
+ ppc4xx_pob_t *pob;
+
+ pob = opaque;
+ /* No error */
+ pob->bear = 0x00000000;
+ pob->besr0 = 0x0000000;
+ pob->besr1 = 0x0000000;
+}
+
+static void ppc4xx_pob_init(CPUPPCState *env)
+{
+ ppc4xx_pob_t *pob;
+
+ pob = g_malloc0(sizeof(ppc4xx_pob_t));
+ ppc_dcr_register(env, POB0_BEAR, pob, &dcr_read_pob, &dcr_write_pob);
+ ppc_dcr_register(env, POB0_BESR0, pob, &dcr_read_pob, &dcr_write_pob);
+ ppc_dcr_register(env, POB0_BESR1, pob, &dcr_read_pob, &dcr_write_pob);
+ qemu_register_reset(ppc4xx_pob_reset, pob);
+}
+
+/*****************************************************************************/
+/* OPB arbitrer */
+typedef struct ppc4xx_opba_t ppc4xx_opba_t;
+struct ppc4xx_opba_t {
+ MemoryRegion io;
+ uint8_t cr;
+ uint8_t pr;
+};
+
+static uint32_t opba_readb (void *opaque, hwaddr addr)
+{
+ ppc4xx_opba_t *opba;
+ uint32_t ret;
+
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ opba = opaque;
+ switch (addr) {
+ case 0x00:
+ ret = opba->cr;
+ break;
+ case 0x01:
+ ret = opba->pr;
+ break;
+ default:
+ ret = 0x00;
+ break;
+ }
+
+ return ret;
+}
+
+static void opba_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ppc4xx_opba_t *opba;
+
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ opba = opaque;
+ switch (addr) {
+ case 0x00:
+ opba->cr = value & 0xF8;
+ break;
+ case 0x01:
+ opba->pr = value & 0xFF;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint32_t opba_readw (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ ret = opba_readb(opaque, addr) << 8;
+ ret |= opba_readb(opaque, addr + 1);
+
+ return ret;
+}
+
+static void opba_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ opba_writeb(opaque, addr, value >> 8);
+ opba_writeb(opaque, addr + 1, value);
+}
+
+static uint32_t opba_readl (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ ret = opba_readb(opaque, addr) << 24;
+ ret |= opba_readb(opaque, addr + 1) << 16;
+
+ return ret;
+}
+
+static void opba_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ opba_writeb(opaque, addr, value >> 24);
+ opba_writeb(opaque, addr + 1, value >> 16);
+}
+
+static const MemoryRegionOps opba_ops = {
+ .old_mmio = {
+ .read = { opba_readb, opba_readw, opba_readl, },
+ .write = { opba_writeb, opba_writew, opba_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc4xx_opba_reset (void *opaque)
+{
+ ppc4xx_opba_t *opba;
+
+ opba = opaque;
+ opba->cr = 0x00; /* No dynamic priorities - park disabled */
+ opba->pr = 0x11;
+}
+
+static void ppc4xx_opba_init(hwaddr base)
+{
+ ppc4xx_opba_t *opba;
+
+ opba = g_malloc0(sizeof(ppc4xx_opba_t));
+#ifdef DEBUG_OPBA
+ printf("%s: offset " TARGET_FMT_plx "\n", __func__, base);
+#endif
+ memory_region_init_io(&opba->io, NULL, &opba_ops, opba, "opba", 0x002);
+ memory_region_add_subregion(get_system_memory(), base, &opba->io);
+ qemu_register_reset(ppc4xx_opba_reset, opba);
+}
+
+/*****************************************************************************/
+/* Code decompression controller */
+/* XXX: TODO */
+
+/*****************************************************************************/
+/* Peripheral controller */
+typedef struct ppc4xx_ebc_t ppc4xx_ebc_t;
+struct ppc4xx_ebc_t {
+ uint32_t addr;
+ uint32_t bcr[8];
+ uint32_t bap[8];
+ uint32_t bear;
+ uint32_t besr0;
+ uint32_t besr1;
+ uint32_t cfg;
+};
+
+enum {
+ EBC0_CFGADDR = 0x012,
+ EBC0_CFGDATA = 0x013,
+};
+
+static uint32_t dcr_read_ebc (void *opaque, int dcrn)
+{
+ ppc4xx_ebc_t *ebc;
+ uint32_t ret;
+
+ ebc = opaque;
+ switch (dcrn) {
+ case EBC0_CFGADDR:
+ ret = ebc->addr;
+ break;
+ case EBC0_CFGDATA:
+ switch (ebc->addr) {
+ case 0x00: /* B0CR */
+ ret = ebc->bcr[0];
+ break;
+ case 0x01: /* B1CR */
+ ret = ebc->bcr[1];
+ break;
+ case 0x02: /* B2CR */
+ ret = ebc->bcr[2];
+ break;
+ case 0x03: /* B3CR */
+ ret = ebc->bcr[3];
+ break;
+ case 0x04: /* B4CR */
+ ret = ebc->bcr[4];
+ break;
+ case 0x05: /* B5CR */
+ ret = ebc->bcr[5];
+ break;
+ case 0x06: /* B6CR */
+ ret = ebc->bcr[6];
+ break;
+ case 0x07: /* B7CR */
+ ret = ebc->bcr[7];
+ break;
+ case 0x10: /* B0AP */
+ ret = ebc->bap[0];
+ break;
+ case 0x11: /* B1AP */
+ ret = ebc->bap[1];
+ break;
+ case 0x12: /* B2AP */
+ ret = ebc->bap[2];
+ break;
+ case 0x13: /* B3AP */
+ ret = ebc->bap[3];
+ break;
+ case 0x14: /* B4AP */
+ ret = ebc->bap[4];
+ break;
+ case 0x15: /* B5AP */
+ ret = ebc->bap[5];
+ break;
+ case 0x16: /* B6AP */
+ ret = ebc->bap[6];
+ break;
+ case 0x17: /* B7AP */
+ ret = ebc->bap[7];
+ break;
+ case 0x20: /* BEAR */
+ ret = ebc->bear;
+ break;
+ case 0x21: /* BESR0 */
+ ret = ebc->besr0;
+ break;
+ case 0x22: /* BESR1 */
+ ret = ebc->besr1;
+ break;
+ case 0x23: /* CFG */
+ ret = ebc->cfg;
+ break;
+ default:
+ ret = 0x00000000;
+ break;
+ }
+ break;
+ default:
+ ret = 0x00000000;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_ebc (void *opaque, int dcrn, uint32_t val)
+{
+ ppc4xx_ebc_t *ebc;
+
+ ebc = opaque;
+ switch (dcrn) {
+ case EBC0_CFGADDR:
+ ebc->addr = val;
+ break;
+ case EBC0_CFGDATA:
+ switch (ebc->addr) {
+ case 0x00: /* B0CR */
+ break;
+ case 0x01: /* B1CR */
+ break;
+ case 0x02: /* B2CR */
+ break;
+ case 0x03: /* B3CR */
+ break;
+ case 0x04: /* B4CR */
+ break;
+ case 0x05: /* B5CR */
+ break;
+ case 0x06: /* B6CR */
+ break;
+ case 0x07: /* B7CR */
+ break;
+ case 0x10: /* B0AP */
+ break;
+ case 0x11: /* B1AP */
+ break;
+ case 0x12: /* B2AP */
+ break;
+ case 0x13: /* B3AP */
+ break;
+ case 0x14: /* B4AP */
+ break;
+ case 0x15: /* B5AP */
+ break;
+ case 0x16: /* B6AP */
+ break;
+ case 0x17: /* B7AP */
+ break;
+ case 0x20: /* BEAR */
+ break;
+ case 0x21: /* BESR0 */
+ break;
+ case 0x22: /* BESR1 */
+ break;
+ case 0x23: /* CFG */
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void ebc_reset (void *opaque)
+{
+ ppc4xx_ebc_t *ebc;
+ int i;
+
+ ebc = opaque;
+ ebc->addr = 0x00000000;
+ ebc->bap[0] = 0x7F8FFE80;
+ ebc->bcr[0] = 0xFFE28000;
+ for (i = 0; i < 8; i++) {
+ ebc->bap[i] = 0x00000000;
+ ebc->bcr[i] = 0x00000000;
+ }
+ ebc->besr0 = 0x00000000;
+ ebc->besr1 = 0x00000000;
+ ebc->cfg = 0x80400000;
+}
+
+static void ppc405_ebc_init(CPUPPCState *env)
+{
+ ppc4xx_ebc_t *ebc;
+
+ ebc = g_malloc0(sizeof(ppc4xx_ebc_t));
+ qemu_register_reset(&ebc_reset, ebc);
+ ppc_dcr_register(env, EBC0_CFGADDR,
+ ebc, &dcr_read_ebc, &dcr_write_ebc);
+ ppc_dcr_register(env, EBC0_CFGDATA,
+ ebc, &dcr_read_ebc, &dcr_write_ebc);
+}
+
+/*****************************************************************************/
+/* DMA controller */
+enum {
+ DMA0_CR0 = 0x100,
+ DMA0_CT0 = 0x101,
+ DMA0_DA0 = 0x102,
+ DMA0_SA0 = 0x103,
+ DMA0_SG0 = 0x104,
+ DMA0_CR1 = 0x108,
+ DMA0_CT1 = 0x109,
+ DMA0_DA1 = 0x10A,
+ DMA0_SA1 = 0x10B,
+ DMA0_SG1 = 0x10C,
+ DMA0_CR2 = 0x110,
+ DMA0_CT2 = 0x111,
+ DMA0_DA2 = 0x112,
+ DMA0_SA2 = 0x113,
+ DMA0_SG2 = 0x114,
+ DMA0_CR3 = 0x118,
+ DMA0_CT3 = 0x119,
+ DMA0_DA3 = 0x11A,
+ DMA0_SA3 = 0x11B,
+ DMA0_SG3 = 0x11C,
+ DMA0_SR = 0x120,
+ DMA0_SGC = 0x123,
+ DMA0_SLP = 0x125,
+ DMA0_POL = 0x126,
+};
+
+typedef struct ppc405_dma_t ppc405_dma_t;
+struct ppc405_dma_t {
+ qemu_irq irqs[4];
+ uint32_t cr[4];
+ uint32_t ct[4];
+ uint32_t da[4];
+ uint32_t sa[4];
+ uint32_t sg[4];
+ uint32_t sr;
+ uint32_t sgc;
+ uint32_t slp;
+ uint32_t pol;
+};
+
+static uint32_t dcr_read_dma (void *opaque, int dcrn)
+{
+ return 0;
+}
+
+static void dcr_write_dma (void *opaque, int dcrn, uint32_t val)
+{
+}
+
+static void ppc405_dma_reset (void *opaque)
+{
+ ppc405_dma_t *dma;
+ int i;
+
+ dma = opaque;
+ for (i = 0; i < 4; i++) {
+ dma->cr[i] = 0x00000000;
+ dma->ct[i] = 0x00000000;
+ dma->da[i] = 0x00000000;
+ dma->sa[i] = 0x00000000;
+ dma->sg[i] = 0x00000000;
+ }
+ dma->sr = 0x00000000;
+ dma->sgc = 0x00000000;
+ dma->slp = 0x7C000000;
+ dma->pol = 0x00000000;
+}
+
+static void ppc405_dma_init(CPUPPCState *env, qemu_irq irqs[4])
+{
+ ppc405_dma_t *dma;
+
+ dma = g_malloc0(sizeof(ppc405_dma_t));
+ memcpy(dma->irqs, irqs, 4 * sizeof(qemu_irq));
+ qemu_register_reset(&ppc405_dma_reset, dma);
+ ppc_dcr_register(env, DMA0_CR0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CT0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_DA0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SA0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SG0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CR1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CT1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_DA1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SA1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SG1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CR2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CT2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_DA2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SA2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SG2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CR3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CT3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_DA3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SA3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SG3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SR,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SGC,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SLP,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_POL,
+ dma, &dcr_read_dma, &dcr_write_dma);
+}
+
+/*****************************************************************************/
+/* GPIO */
+typedef struct ppc405_gpio_t ppc405_gpio_t;
+struct ppc405_gpio_t {
+ MemoryRegion io;
+ uint32_t or;
+ uint32_t tcr;
+ uint32_t osrh;
+ uint32_t osrl;
+ uint32_t tsrh;
+ uint32_t tsrl;
+ uint32_t odr;
+ uint32_t ir;
+ uint32_t rr1;
+ uint32_t isr1h;
+ uint32_t isr1l;
+};
+
+static uint32_t ppc405_gpio_readb (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+
+ return 0;
+}
+
+static void ppc405_gpio_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+}
+
+static uint32_t ppc405_gpio_readw (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+
+ return 0;
+}
+
+static void ppc405_gpio_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+}
+
+static uint32_t ppc405_gpio_readl (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+
+ return 0;
+}
+
+static void ppc405_gpio_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+}
+
+static const MemoryRegionOps ppc405_gpio_ops = {
+ .old_mmio = {
+ .read = { ppc405_gpio_readb, ppc405_gpio_readw, ppc405_gpio_readl, },
+ .write = { ppc405_gpio_writeb, ppc405_gpio_writew, ppc405_gpio_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc405_gpio_reset (void *opaque)
+{
+}
+
+static void ppc405_gpio_init(hwaddr base)
+{
+ ppc405_gpio_t *gpio;
+
+ gpio = g_malloc0(sizeof(ppc405_gpio_t));
+#ifdef DEBUG_GPIO
+ printf("%s: offset " TARGET_FMT_plx "\n", __func__, base);
+#endif
+ memory_region_init_io(&gpio->io, NULL, &ppc405_gpio_ops, gpio, "pgio", 0x038);
+ memory_region_add_subregion(get_system_memory(), base, &gpio->io);
+ qemu_register_reset(&ppc405_gpio_reset, gpio);
+}
+
+/*****************************************************************************/
+/* On Chip Memory */
+enum {
+ OCM0_ISARC = 0x018,
+ OCM0_ISACNTL = 0x019,
+ OCM0_DSARC = 0x01A,
+ OCM0_DSACNTL = 0x01B,
+};
+
+typedef struct ppc405_ocm_t ppc405_ocm_t;
+struct ppc405_ocm_t {
+ MemoryRegion ram;
+ MemoryRegion isarc_ram;
+ MemoryRegion dsarc_ram;
+ uint32_t isarc;
+ uint32_t isacntl;
+ uint32_t dsarc;
+ uint32_t dsacntl;
+};
+
+static void ocm_update_mappings (ppc405_ocm_t *ocm,
+ uint32_t isarc, uint32_t isacntl,
+ uint32_t dsarc, uint32_t dsacntl)
+{
+#ifdef DEBUG_OCM
+ printf("OCM update ISA %08" PRIx32 " %08" PRIx32 " (%08" PRIx32
+ " %08" PRIx32 ") DSA %08" PRIx32 " %08" PRIx32
+ " (%08" PRIx32 " %08" PRIx32 ")\n",
+ isarc, isacntl, dsarc, dsacntl,
+ ocm->isarc, ocm->isacntl, ocm->dsarc, ocm->dsacntl);
+#endif
+ if (ocm->isarc != isarc ||
+ (ocm->isacntl & 0x80000000) != (isacntl & 0x80000000)) {
+ if (ocm->isacntl & 0x80000000) {
+ /* Unmap previously assigned memory region */
+ printf("OCM unmap ISA %08" PRIx32 "\n", ocm->isarc);
+ memory_region_del_subregion(get_system_memory(), &ocm->isarc_ram);
+ }
+ if (isacntl & 0x80000000) {
+ /* Map new instruction memory region */
+#ifdef DEBUG_OCM
+ printf("OCM map ISA %08" PRIx32 "\n", isarc);
+#endif
+ memory_region_add_subregion(get_system_memory(), isarc,
+ &ocm->isarc_ram);
+ }
+ }
+ if (ocm->dsarc != dsarc ||
+ (ocm->dsacntl & 0x80000000) != (dsacntl & 0x80000000)) {
+ if (ocm->dsacntl & 0x80000000) {
+ /* Beware not to unmap the region we just mapped */
+ if (!(isacntl & 0x80000000) || ocm->dsarc != isarc) {
+ /* Unmap previously assigned memory region */
+#ifdef DEBUG_OCM
+ printf("OCM unmap DSA %08" PRIx32 "\n", ocm->dsarc);
+#endif
+ memory_region_del_subregion(get_system_memory(),
+ &ocm->dsarc_ram);
+ }
+ }
+ if (dsacntl & 0x80000000) {
+ /* Beware not to remap the region we just mapped */
+ if (!(isacntl & 0x80000000) || dsarc != isarc) {
+ /* Map new data memory region */
+#ifdef DEBUG_OCM
+ printf("OCM map DSA %08" PRIx32 "\n", dsarc);
+#endif
+ memory_region_add_subregion(get_system_memory(), dsarc,
+ &ocm->dsarc_ram);
+ }
+ }
+ }
+}
+
+static uint32_t dcr_read_ocm (void *opaque, int dcrn)
+{
+ ppc405_ocm_t *ocm;
+ uint32_t ret;
+
+ ocm = opaque;
+ switch (dcrn) {
+ case OCM0_ISARC:
+ ret = ocm->isarc;
+ break;
+ case OCM0_ISACNTL:
+ ret = ocm->isacntl;
+ break;
+ case OCM0_DSARC:
+ ret = ocm->dsarc;
+ break;
+ case OCM0_DSACNTL:
+ ret = ocm->dsacntl;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_ocm (void *opaque, int dcrn, uint32_t val)
+{
+ ppc405_ocm_t *ocm;
+ uint32_t isarc, dsarc, isacntl, dsacntl;
+
+ ocm = opaque;
+ isarc = ocm->isarc;
+ dsarc = ocm->dsarc;
+ isacntl = ocm->isacntl;
+ dsacntl = ocm->dsacntl;
+ switch (dcrn) {
+ case OCM0_ISARC:
+ isarc = val & 0xFC000000;
+ break;
+ case OCM0_ISACNTL:
+ isacntl = val & 0xC0000000;
+ break;
+ case OCM0_DSARC:
+ isarc = val & 0xFC000000;
+ break;
+ case OCM0_DSACNTL:
+ isacntl = val & 0xC0000000;
+ break;
+ }
+ ocm_update_mappings(ocm, isarc, isacntl, dsarc, dsacntl);
+ ocm->isarc = isarc;
+ ocm->dsarc = dsarc;
+ ocm->isacntl = isacntl;
+ ocm->dsacntl = dsacntl;
+}
+
+static void ocm_reset (void *opaque)
+{
+ ppc405_ocm_t *ocm;
+ uint32_t isarc, dsarc, isacntl, dsacntl;
+
+ ocm = opaque;
+ isarc = 0x00000000;
+ isacntl = 0x00000000;
+ dsarc = 0x00000000;
+ dsacntl = 0x00000000;
+ ocm_update_mappings(ocm, isarc, isacntl, dsarc, dsacntl);
+ ocm->isarc = isarc;
+ ocm->dsarc = dsarc;
+ ocm->isacntl = isacntl;
+ ocm->dsacntl = dsacntl;
+}
+
+static void ppc405_ocm_init(CPUPPCState *env)
+{
+ ppc405_ocm_t *ocm;
+
+ ocm = g_malloc0(sizeof(ppc405_ocm_t));
+ /* XXX: Size is 4096 or 0x04000000 */
+ memory_region_init_ram(&ocm->isarc_ram, NULL, "ppc405.ocm", 4096,
+ &error_abort);
+ vmstate_register_ram_global(&ocm->isarc_ram);
+ memory_region_init_alias(&ocm->dsarc_ram, NULL, "ppc405.dsarc", &ocm->isarc_ram,
+ 0, 4096);
+ qemu_register_reset(&ocm_reset, ocm);
+ ppc_dcr_register(env, OCM0_ISARC,
+ ocm, &dcr_read_ocm, &dcr_write_ocm);
+ ppc_dcr_register(env, OCM0_ISACNTL,
+ ocm, &dcr_read_ocm, &dcr_write_ocm);
+ ppc_dcr_register(env, OCM0_DSARC,
+ ocm, &dcr_read_ocm, &dcr_write_ocm);
+ ppc_dcr_register(env, OCM0_DSACNTL,
+ ocm, &dcr_read_ocm, &dcr_write_ocm);
+}
+
+/*****************************************************************************/
+/* I2C controller */
+typedef struct ppc4xx_i2c_t ppc4xx_i2c_t;
+struct ppc4xx_i2c_t {
+ qemu_irq irq;
+ MemoryRegion iomem;
+ uint8_t mdata;
+ uint8_t lmadr;
+ uint8_t hmadr;
+ uint8_t cntl;
+ uint8_t mdcntl;
+ uint8_t sts;
+ uint8_t extsts;
+ uint8_t sdata;
+ uint8_t lsadr;
+ uint8_t hsadr;
+ uint8_t clkdiv;
+ uint8_t intrmsk;
+ uint8_t xfrcnt;
+ uint8_t xtcntlss;
+ uint8_t directcntl;
+};
+
+static uint32_t ppc4xx_i2c_readb (void *opaque, hwaddr addr)
+{
+ ppc4xx_i2c_t *i2c;
+ uint32_t ret;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ i2c = opaque;
+ switch (addr) {
+ case 0x00:
+ // i2c_readbyte(&i2c->mdata);
+ ret = i2c->mdata;
+ break;
+ case 0x02:
+ ret = i2c->sdata;
+ break;
+ case 0x04:
+ ret = i2c->lmadr;
+ break;
+ case 0x05:
+ ret = i2c->hmadr;
+ break;
+ case 0x06:
+ ret = i2c->cntl;
+ break;
+ case 0x07:
+ ret = i2c->mdcntl;
+ break;
+ case 0x08:
+ ret = i2c->sts;
+ break;
+ case 0x09:
+ ret = i2c->extsts;
+ break;
+ case 0x0A:
+ ret = i2c->lsadr;
+ break;
+ case 0x0B:
+ ret = i2c->hsadr;
+ break;
+ case 0x0C:
+ ret = i2c->clkdiv;
+ break;
+ case 0x0D:
+ ret = i2c->intrmsk;
+ break;
+ case 0x0E:
+ ret = i2c->xfrcnt;
+ break;
+ case 0x0F:
+ ret = i2c->xtcntlss;
+ break;
+ case 0x10:
+ ret = i2c->directcntl;
+ break;
+ default:
+ ret = 0x00;
+ break;
+ }
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " %02" PRIx32 "\n", __func__, addr, ret);
+#endif
+
+ return ret;
+}
+
+static void ppc4xx_i2c_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ppc4xx_i2c_t *i2c;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ i2c = opaque;
+ switch (addr) {
+ case 0x00:
+ i2c->mdata = value;
+ // i2c_sendbyte(&i2c->mdata);
+ break;
+ case 0x02:
+ i2c->sdata = value;
+ break;
+ case 0x04:
+ i2c->lmadr = value;
+ break;
+ case 0x05:
+ i2c->hmadr = value;
+ break;
+ case 0x06:
+ i2c->cntl = value;
+ break;
+ case 0x07:
+ i2c->mdcntl = value & 0xDF;
+ break;
+ case 0x08:
+ i2c->sts &= ~(value & 0x0A);
+ break;
+ case 0x09:
+ i2c->extsts &= ~(value & 0x8F);
+ break;
+ case 0x0A:
+ i2c->lsadr = value;
+ break;
+ case 0x0B:
+ i2c->hsadr = value;
+ break;
+ case 0x0C:
+ i2c->clkdiv = value;
+ break;
+ case 0x0D:
+ i2c->intrmsk = value;
+ break;
+ case 0x0E:
+ i2c->xfrcnt = value & 0x77;
+ break;
+ case 0x0F:
+ i2c->xtcntlss = value;
+ break;
+ case 0x10:
+ i2c->directcntl = value & 0x7;
+ break;
+ }
+}
+
+static uint32_t ppc4xx_i2c_readw (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ ret = ppc4xx_i2c_readb(opaque, addr) << 8;
+ ret |= ppc4xx_i2c_readb(opaque, addr + 1);
+
+ return ret;
+}
+
+static void ppc4xx_i2c_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ ppc4xx_i2c_writeb(opaque, addr, value >> 8);
+ ppc4xx_i2c_writeb(opaque, addr + 1, value);
+}
+
+static uint32_t ppc4xx_i2c_readl (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ ret = ppc4xx_i2c_readb(opaque, addr) << 24;
+ ret |= ppc4xx_i2c_readb(opaque, addr + 1) << 16;
+ ret |= ppc4xx_i2c_readb(opaque, addr + 2) << 8;
+ ret |= ppc4xx_i2c_readb(opaque, addr + 3);
+
+ return ret;
+}
+
+static void ppc4xx_i2c_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ ppc4xx_i2c_writeb(opaque, addr, value >> 24);
+ ppc4xx_i2c_writeb(opaque, addr + 1, value >> 16);
+ ppc4xx_i2c_writeb(opaque, addr + 2, value >> 8);
+ ppc4xx_i2c_writeb(opaque, addr + 3, value);
+}
+
+static const MemoryRegionOps i2c_ops = {
+ .old_mmio = {
+ .read = { ppc4xx_i2c_readb, ppc4xx_i2c_readw, ppc4xx_i2c_readl, },
+ .write = { ppc4xx_i2c_writeb, ppc4xx_i2c_writew, ppc4xx_i2c_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc4xx_i2c_reset (void *opaque)
+{
+ ppc4xx_i2c_t *i2c;
+
+ i2c = opaque;
+ i2c->mdata = 0x00;
+ i2c->sdata = 0x00;
+ i2c->cntl = 0x00;
+ i2c->mdcntl = 0x00;
+ i2c->sts = 0x00;
+ i2c->extsts = 0x00;
+ i2c->clkdiv = 0x00;
+ i2c->xfrcnt = 0x00;
+ i2c->directcntl = 0x0F;
+}
+
+static void ppc405_i2c_init(hwaddr base, qemu_irq irq)
+{
+ ppc4xx_i2c_t *i2c;
+
+ i2c = g_malloc0(sizeof(ppc4xx_i2c_t));
+ i2c->irq = irq;
+#ifdef DEBUG_I2C
+ printf("%s: offset " TARGET_FMT_plx "\n", __func__, base);
+#endif
+ memory_region_init_io(&i2c->iomem, NULL, &i2c_ops, i2c, "i2c", 0x011);
+ memory_region_add_subregion(get_system_memory(), base, &i2c->iomem);
+ qemu_register_reset(ppc4xx_i2c_reset, i2c);
+}
+
+/*****************************************************************************/
+/* General purpose timers */
+typedef struct ppc4xx_gpt_t ppc4xx_gpt_t;
+struct ppc4xx_gpt_t {
+ MemoryRegion iomem;
+ int64_t tb_offset;
+ uint32_t tb_freq;
+ QEMUTimer *timer;
+ qemu_irq irqs[5];
+ uint32_t oe;
+ uint32_t ol;
+ uint32_t im;
+ uint32_t is;
+ uint32_t ie;
+ uint32_t comp[5];
+ uint32_t mask[5];
+};
+
+static uint32_t ppc4xx_gpt_readb (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPT
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ /* XXX: generate a bus fault */
+ return -1;
+}
+
+static void ppc4xx_gpt_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ /* XXX: generate a bus fault */
+}
+
+static uint32_t ppc4xx_gpt_readw (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPT
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ /* XXX: generate a bus fault */
+ return -1;
+}
+
+static void ppc4xx_gpt_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ /* XXX: generate a bus fault */
+}
+
+static int ppc4xx_gpt_compare (ppc4xx_gpt_t *gpt, int n)
+{
+ /* XXX: TODO */
+ return 0;
+}
+
+static void ppc4xx_gpt_set_output (ppc4xx_gpt_t *gpt, int n, int level)
+{
+ /* XXX: TODO */
+}
+
+static void ppc4xx_gpt_set_outputs (ppc4xx_gpt_t *gpt)
+{
+ uint32_t mask;
+ int i;
+
+ mask = 0x80000000;
+ for (i = 0; i < 5; i++) {
+ if (gpt->oe & mask) {
+ /* Output is enabled */
+ if (ppc4xx_gpt_compare(gpt, i)) {
+ /* Comparison is OK */
+ ppc4xx_gpt_set_output(gpt, i, gpt->ol & mask);
+ } else {
+ /* Comparison is KO */
+ ppc4xx_gpt_set_output(gpt, i, gpt->ol & mask ? 0 : 1);
+ }
+ }
+ mask = mask >> 1;
+ }
+}
+
+static void ppc4xx_gpt_set_irqs (ppc4xx_gpt_t *gpt)
+{
+ uint32_t mask;
+ int i;
+
+ mask = 0x00008000;
+ for (i = 0; i < 5; i++) {
+ if (gpt->is & gpt->im & mask)
+ qemu_irq_raise(gpt->irqs[i]);
+ else
+ qemu_irq_lower(gpt->irqs[i]);
+ mask = mask >> 1;
+ }
+}
+
+static void ppc4xx_gpt_compute_timer (ppc4xx_gpt_t *gpt)
+{
+ /* XXX: TODO */
+}
+
+static uint32_t ppc4xx_gpt_readl (void *opaque, hwaddr addr)
+{
+ ppc4xx_gpt_t *gpt;
+ uint32_t ret;
+ int idx;
+
+#ifdef DEBUG_GPT
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ gpt = opaque;
+ switch (addr) {
+ case 0x00:
+ /* Time base counter */
+ ret = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + gpt->tb_offset,
+ gpt->tb_freq, get_ticks_per_sec());
+ break;
+ case 0x10:
+ /* Output enable */
+ ret = gpt->oe;
+ break;
+ case 0x14:
+ /* Output level */
+ ret = gpt->ol;
+ break;
+ case 0x18:
+ /* Interrupt mask */
+ ret = gpt->im;
+ break;
+ case 0x1C:
+ case 0x20:
+ /* Interrupt status */
+ ret = gpt->is;
+ break;
+ case 0x24:
+ /* Interrupt enable */
+ ret = gpt->ie;
+ break;
+ case 0x80 ... 0x90:
+ /* Compare timer */
+ idx = (addr - 0x80) >> 2;
+ ret = gpt->comp[idx];
+ break;
+ case 0xC0 ... 0xD0:
+ /* Compare mask */
+ idx = (addr - 0xC0) >> 2;
+ ret = gpt->mask[idx];
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+static void ppc4xx_gpt_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ppc4xx_gpt_t *gpt;
+ int idx;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ gpt = opaque;
+ switch (addr) {
+ case 0x00:
+ /* Time base counter */
+ gpt->tb_offset = muldiv64(value, get_ticks_per_sec(), gpt->tb_freq)
+ - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ppc4xx_gpt_compute_timer(gpt);
+ break;
+ case 0x10:
+ /* Output enable */
+ gpt->oe = value & 0xF8000000;
+ ppc4xx_gpt_set_outputs(gpt);
+ break;
+ case 0x14:
+ /* Output level */
+ gpt->ol = value & 0xF8000000;
+ ppc4xx_gpt_set_outputs(gpt);
+ break;
+ case 0x18:
+ /* Interrupt mask */
+ gpt->im = value & 0x0000F800;
+ break;
+ case 0x1C:
+ /* Interrupt status set */
+ gpt->is |= value & 0x0000F800;
+ ppc4xx_gpt_set_irqs(gpt);
+ break;
+ case 0x20:
+ /* Interrupt status clear */
+ gpt->is &= ~(value & 0x0000F800);
+ ppc4xx_gpt_set_irqs(gpt);
+ break;
+ case 0x24:
+ /* Interrupt enable */
+ gpt->ie = value & 0x0000F800;
+ ppc4xx_gpt_set_irqs(gpt);
+ break;
+ case 0x80 ... 0x90:
+ /* Compare timer */
+ idx = (addr - 0x80) >> 2;
+ gpt->comp[idx] = value & 0xF8000000;
+ ppc4xx_gpt_compute_timer(gpt);
+ break;
+ case 0xC0 ... 0xD0:
+ /* Compare mask */
+ idx = (addr - 0xC0) >> 2;
+ gpt->mask[idx] = value & 0xF8000000;
+ ppc4xx_gpt_compute_timer(gpt);
+ break;
+ }
+}
+
+static const MemoryRegionOps gpt_ops = {
+ .old_mmio = {
+ .read = { ppc4xx_gpt_readb, ppc4xx_gpt_readw, ppc4xx_gpt_readl, },
+ .write = { ppc4xx_gpt_writeb, ppc4xx_gpt_writew, ppc4xx_gpt_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc4xx_gpt_cb (void *opaque)
+{
+ ppc4xx_gpt_t *gpt;
+
+ gpt = opaque;
+ ppc4xx_gpt_set_irqs(gpt);
+ ppc4xx_gpt_set_outputs(gpt);
+ ppc4xx_gpt_compute_timer(gpt);
+}
+
+static void ppc4xx_gpt_reset (void *opaque)
+{
+ ppc4xx_gpt_t *gpt;
+ int i;
+
+ gpt = opaque;
+ timer_del(gpt->timer);
+ gpt->oe = 0x00000000;
+ gpt->ol = 0x00000000;
+ gpt->im = 0x00000000;
+ gpt->is = 0x00000000;
+ gpt->ie = 0x00000000;
+ for (i = 0; i < 5; i++) {
+ gpt->comp[i] = 0x00000000;
+ gpt->mask[i] = 0x00000000;
+ }
+}
+
+static void ppc4xx_gpt_init(hwaddr base, qemu_irq irqs[5])
+{
+ ppc4xx_gpt_t *gpt;
+ int i;
+
+ gpt = g_malloc0(sizeof(ppc4xx_gpt_t));
+ for (i = 0; i < 5; i++) {
+ gpt->irqs[i] = irqs[i];
+ }
+ gpt->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &ppc4xx_gpt_cb, gpt);
+#ifdef DEBUG_GPT
+ printf("%s: offset " TARGET_FMT_plx "\n", __func__, base);
+#endif
+ memory_region_init_io(&gpt->iomem, NULL, &gpt_ops, gpt, "gpt", 0x0d4);
+ memory_region_add_subregion(get_system_memory(), base, &gpt->iomem);
+ qemu_register_reset(ppc4xx_gpt_reset, gpt);
+}
+
+/*****************************************************************************/
+/* MAL */
+enum {
+ MAL0_CFG = 0x180,
+ MAL0_ESR = 0x181,
+ MAL0_IER = 0x182,
+ MAL0_TXCASR = 0x184,
+ MAL0_TXCARR = 0x185,
+ MAL0_TXEOBISR = 0x186,
+ MAL0_TXDEIR = 0x187,
+ MAL0_RXCASR = 0x190,
+ MAL0_RXCARR = 0x191,
+ MAL0_RXEOBISR = 0x192,
+ MAL0_RXDEIR = 0x193,
+ MAL0_TXCTP0R = 0x1A0,
+ MAL0_TXCTP1R = 0x1A1,
+ MAL0_TXCTP2R = 0x1A2,
+ MAL0_TXCTP3R = 0x1A3,
+ MAL0_RXCTP0R = 0x1C0,
+ MAL0_RXCTP1R = 0x1C1,
+ MAL0_RCBS0 = 0x1E0,
+ MAL0_RCBS1 = 0x1E1,
+};
+
+typedef struct ppc40x_mal_t ppc40x_mal_t;
+struct ppc40x_mal_t {
+ qemu_irq irqs[4];
+ uint32_t cfg;
+ uint32_t esr;
+ uint32_t ier;
+ uint32_t txcasr;
+ uint32_t txcarr;
+ uint32_t txeobisr;
+ uint32_t txdeir;
+ uint32_t rxcasr;
+ uint32_t rxcarr;
+ uint32_t rxeobisr;
+ uint32_t rxdeir;
+ uint32_t txctpr[4];
+ uint32_t rxctpr[2];
+ uint32_t rcbs[2];
+};
+
+static void ppc40x_mal_reset (void *opaque);
+
+static uint32_t dcr_read_mal (void *opaque, int dcrn)
+{
+ ppc40x_mal_t *mal;
+ uint32_t ret;
+
+ mal = opaque;
+ switch (dcrn) {
+ case MAL0_CFG:
+ ret = mal->cfg;
+ break;
+ case MAL0_ESR:
+ ret = mal->esr;
+ break;
+ case MAL0_IER:
+ ret = mal->ier;
+ break;
+ case MAL0_TXCASR:
+ ret = mal->txcasr;
+ break;
+ case MAL0_TXCARR:
+ ret = mal->txcarr;
+ break;
+ case MAL0_TXEOBISR:
+ ret = mal->txeobisr;
+ break;
+ case MAL0_TXDEIR:
+ ret = mal->txdeir;
+ break;
+ case MAL0_RXCASR:
+ ret = mal->rxcasr;
+ break;
+ case MAL0_RXCARR:
+ ret = mal->rxcarr;
+ break;
+ case MAL0_RXEOBISR:
+ ret = mal->rxeobisr;
+ break;
+ case MAL0_RXDEIR:
+ ret = mal->rxdeir;
+ break;
+ case MAL0_TXCTP0R:
+ ret = mal->txctpr[0];
+ break;
+ case MAL0_TXCTP1R:
+ ret = mal->txctpr[1];
+ break;
+ case MAL0_TXCTP2R:
+ ret = mal->txctpr[2];
+ break;
+ case MAL0_TXCTP3R:
+ ret = mal->txctpr[3];
+ break;
+ case MAL0_RXCTP0R:
+ ret = mal->rxctpr[0];
+ break;
+ case MAL0_RXCTP1R:
+ ret = mal->rxctpr[1];
+ break;
+ case MAL0_RCBS0:
+ ret = mal->rcbs[0];
+ break;
+ case MAL0_RCBS1:
+ ret = mal->rcbs[1];
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_mal (void *opaque, int dcrn, uint32_t val)
+{
+ ppc40x_mal_t *mal;
+ int idx;
+
+ mal = opaque;
+ switch (dcrn) {
+ case MAL0_CFG:
+ if (val & 0x80000000)
+ ppc40x_mal_reset(mal);
+ mal->cfg = val & 0x00FFC087;
+ break;
+ case MAL0_ESR:
+ /* Read/clear */
+ mal->esr &= ~val;
+ break;
+ case MAL0_IER:
+ mal->ier = val & 0x0000001F;
+ break;
+ case MAL0_TXCASR:
+ mal->txcasr = val & 0xF0000000;
+ break;
+ case MAL0_TXCARR:
+ mal->txcarr = val & 0xF0000000;
+ break;
+ case MAL0_TXEOBISR:
+ /* Read/clear */
+ mal->txeobisr &= ~val;
+ break;
+ case MAL0_TXDEIR:
+ /* Read/clear */
+ mal->txdeir &= ~val;
+ break;
+ case MAL0_RXCASR:
+ mal->rxcasr = val & 0xC0000000;
+ break;
+ case MAL0_RXCARR:
+ mal->rxcarr = val & 0xC0000000;
+ break;
+ case MAL0_RXEOBISR:
+ /* Read/clear */
+ mal->rxeobisr &= ~val;
+ break;
+ case MAL0_RXDEIR:
+ /* Read/clear */
+ mal->rxdeir &= ~val;
+ break;
+ case MAL0_TXCTP0R:
+ idx = 0;
+ goto update_tx_ptr;
+ case MAL0_TXCTP1R:
+ idx = 1;
+ goto update_tx_ptr;
+ case MAL0_TXCTP2R:
+ idx = 2;
+ goto update_tx_ptr;
+ case MAL0_TXCTP3R:
+ idx = 3;
+ update_tx_ptr:
+ mal->txctpr[idx] = val;
+ break;
+ case MAL0_RXCTP0R:
+ idx = 0;
+ goto update_rx_ptr;
+ case MAL0_RXCTP1R:
+ idx = 1;
+ update_rx_ptr:
+ mal->rxctpr[idx] = val;
+ break;
+ case MAL0_RCBS0:
+ idx = 0;
+ goto update_rx_size;
+ case MAL0_RCBS1:
+ idx = 1;
+ update_rx_size:
+ mal->rcbs[idx] = val & 0x000000FF;
+ break;
+ }
+}
+
+static void ppc40x_mal_reset (void *opaque)
+{
+ ppc40x_mal_t *mal;
+
+ mal = opaque;
+ mal->cfg = 0x0007C000;
+ mal->esr = 0x00000000;
+ mal->ier = 0x00000000;
+ mal->rxcasr = 0x00000000;
+ mal->rxdeir = 0x00000000;
+ mal->rxeobisr = 0x00000000;
+ mal->txcasr = 0x00000000;
+ mal->txdeir = 0x00000000;
+ mal->txeobisr = 0x00000000;
+}
+
+static void ppc405_mal_init(CPUPPCState *env, qemu_irq irqs[4])
+{
+ ppc40x_mal_t *mal;
+ int i;
+
+ mal = g_malloc0(sizeof(ppc40x_mal_t));
+ for (i = 0; i < 4; i++)
+ mal->irqs[i] = irqs[i];
+ qemu_register_reset(&ppc40x_mal_reset, mal);
+ ppc_dcr_register(env, MAL0_CFG,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_ESR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_IER,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCASR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCARR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXEOBISR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXDEIR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXCASR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXCARR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXEOBISR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXDEIR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCTP0R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCTP1R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCTP2R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCTP3R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXCTP0R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXCTP1R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RCBS0,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RCBS1,
+ mal, &dcr_read_mal, &dcr_write_mal);
+}
+
+/*****************************************************************************/
+/* SPR */
+void ppc40x_core_reset(PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong dbsr;
+
+ printf("Reset PowerPC core\n");
+ cpu_interrupt(CPU(cpu), CPU_INTERRUPT_RESET);
+ dbsr = env->spr[SPR_40x_DBSR];
+ dbsr &= ~0x00000300;
+ dbsr |= 0x00000100;
+ env->spr[SPR_40x_DBSR] = dbsr;
+}
+
+void ppc40x_chip_reset(PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong dbsr;
+
+ printf("Reset PowerPC chip\n");
+ cpu_interrupt(CPU(cpu), CPU_INTERRUPT_RESET);
+ /* XXX: TODO reset all internal peripherals */
+ dbsr = env->spr[SPR_40x_DBSR];
+ dbsr &= ~0x00000300;
+ dbsr |= 0x00000200;
+ env->spr[SPR_40x_DBSR] = dbsr;
+}
+
+void ppc40x_system_reset(PowerPCCPU *cpu)
+{
+ printf("Reset PowerPC system\n");
+ qemu_system_reset_request();
+}
+
+void store_40x_dbcr0 (CPUPPCState *env, uint32_t val)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ switch ((val >> 28) & 0x3) {
+ case 0x0:
+ /* No action */
+ break;
+ case 0x1:
+ /* Core reset */
+ ppc40x_core_reset(cpu);
+ break;
+ case 0x2:
+ /* Chip reset */
+ ppc40x_chip_reset(cpu);
+ break;
+ case 0x3:
+ /* System reset */
+ ppc40x_system_reset(cpu);
+ break;
+ }
+}
+
+/*****************************************************************************/
+/* PowerPC 405CR */
+enum {
+ PPC405CR_CPC0_PLLMR = 0x0B0,
+ PPC405CR_CPC0_CR0 = 0x0B1,
+ PPC405CR_CPC0_CR1 = 0x0B2,
+ PPC405CR_CPC0_PSR = 0x0B4,
+ PPC405CR_CPC0_JTAGID = 0x0B5,
+ PPC405CR_CPC0_ER = 0x0B9,
+ PPC405CR_CPC0_FR = 0x0BA,
+ PPC405CR_CPC0_SR = 0x0BB,
+};
+
+enum {
+ PPC405CR_CPU_CLK = 0,
+ PPC405CR_TMR_CLK = 1,
+ PPC405CR_PLB_CLK = 2,
+ PPC405CR_SDRAM_CLK = 3,
+ PPC405CR_OPB_CLK = 4,
+ PPC405CR_EXT_CLK = 5,
+ PPC405CR_UART_CLK = 6,
+ PPC405CR_CLK_NB = 7,
+};
+
+typedef struct ppc405cr_cpc_t ppc405cr_cpc_t;
+struct ppc405cr_cpc_t {
+ clk_setup_t clk_setup[PPC405CR_CLK_NB];
+ uint32_t sysclk;
+ uint32_t psr;
+ uint32_t cr0;
+ uint32_t cr1;
+ uint32_t jtagid;
+ uint32_t pllmr;
+ uint32_t er;
+ uint32_t fr;
+};
+
+static void ppc405cr_clk_setup (ppc405cr_cpc_t *cpc)
+{
+ uint64_t VCO_out, PLL_out;
+ uint32_t CPU_clk, TMR_clk, SDRAM_clk, PLB_clk, OPB_clk, EXT_clk, UART_clk;
+ int M, D0, D1, D2;
+
+ D0 = ((cpc->pllmr >> 26) & 0x3) + 1; /* CBDV */
+ if (cpc->pllmr & 0x80000000) {
+ D1 = (((cpc->pllmr >> 20) - 1) & 0xF) + 1; /* FBDV */
+ D2 = 8 - ((cpc->pllmr >> 16) & 0x7); /* FWDVA */
+ M = D0 * D1 * D2;
+ VCO_out = cpc->sysclk * M;
+ if (VCO_out < 400000000 || VCO_out > 800000000) {
+ /* PLL cannot lock */
+ cpc->pllmr &= ~0x80000000;
+ goto bypass_pll;
+ }
+ PLL_out = VCO_out / D2;
+ } else {
+ /* Bypass PLL */
+ bypass_pll:
+ M = D0;
+ PLL_out = cpc->sysclk * M;
+ }
+ CPU_clk = PLL_out;
+ if (cpc->cr1 & 0x00800000)
+ TMR_clk = cpc->sysclk; /* Should have a separate clock */
+ else
+ TMR_clk = CPU_clk;
+ PLB_clk = CPU_clk / D0;
+ SDRAM_clk = PLB_clk;
+ D0 = ((cpc->pllmr >> 10) & 0x3) + 1;
+ OPB_clk = PLB_clk / D0;
+ D0 = ((cpc->pllmr >> 24) & 0x3) + 2;
+ EXT_clk = PLB_clk / D0;
+ D0 = ((cpc->cr0 >> 1) & 0x1F) + 1;
+ UART_clk = CPU_clk / D0;
+ /* Setup CPU clocks */
+ clk_setup(&cpc->clk_setup[PPC405CR_CPU_CLK], CPU_clk);
+ /* Setup time-base clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_TMR_CLK], TMR_clk);
+ /* Setup PLB clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_PLB_CLK], PLB_clk);
+ /* Setup SDRAM clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_SDRAM_CLK], SDRAM_clk);
+ /* Setup OPB clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_OPB_CLK], OPB_clk);
+ /* Setup external clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_EXT_CLK], EXT_clk);
+ /* Setup UART clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_UART_CLK], UART_clk);
+}
+
+static uint32_t dcr_read_crcpc (void *opaque, int dcrn)
+{
+ ppc405cr_cpc_t *cpc;
+ uint32_t ret;
+
+ cpc = opaque;
+ switch (dcrn) {
+ case PPC405CR_CPC0_PLLMR:
+ ret = cpc->pllmr;
+ break;
+ case PPC405CR_CPC0_CR0:
+ ret = cpc->cr0;
+ break;
+ case PPC405CR_CPC0_CR1:
+ ret = cpc->cr1;
+ break;
+ case PPC405CR_CPC0_PSR:
+ ret = cpc->psr;
+ break;
+ case PPC405CR_CPC0_JTAGID:
+ ret = cpc->jtagid;
+ break;
+ case PPC405CR_CPC0_ER:
+ ret = cpc->er;
+ break;
+ case PPC405CR_CPC0_FR:
+ ret = cpc->fr;
+ break;
+ case PPC405CR_CPC0_SR:
+ ret = ~(cpc->er | cpc->fr) & 0xFFFF0000;
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_crcpc (void *opaque, int dcrn, uint32_t val)
+{
+ ppc405cr_cpc_t *cpc;
+
+ cpc = opaque;
+ switch (dcrn) {
+ case PPC405CR_CPC0_PLLMR:
+ cpc->pllmr = val & 0xFFF77C3F;
+ break;
+ case PPC405CR_CPC0_CR0:
+ cpc->cr0 = val & 0x0FFFFFFE;
+ break;
+ case PPC405CR_CPC0_CR1:
+ cpc->cr1 = val & 0x00800000;
+ break;
+ case PPC405CR_CPC0_PSR:
+ /* Read-only */
+ break;
+ case PPC405CR_CPC0_JTAGID:
+ /* Read-only */
+ break;
+ case PPC405CR_CPC0_ER:
+ cpc->er = val & 0xBFFC0000;
+ break;
+ case PPC405CR_CPC0_FR:
+ cpc->fr = val & 0xBFFC0000;
+ break;
+ case PPC405CR_CPC0_SR:
+ /* Read-only */
+ break;
+ }
+}
+
+static void ppc405cr_cpc_reset (void *opaque)
+{
+ ppc405cr_cpc_t *cpc;
+ int D;
+
+ cpc = opaque;
+ /* Compute PLLMR value from PSR settings */
+ cpc->pllmr = 0x80000000;
+ /* PFWD */
+ switch ((cpc->psr >> 30) & 3) {
+ case 0:
+ /* Bypass */
+ cpc->pllmr &= ~0x80000000;
+ break;
+ case 1:
+ /* Divide by 3 */
+ cpc->pllmr |= 5 << 16;
+ break;
+ case 2:
+ /* Divide by 4 */
+ cpc->pllmr |= 4 << 16;
+ break;
+ case 3:
+ /* Divide by 6 */
+ cpc->pllmr |= 2 << 16;
+ break;
+ }
+ /* PFBD */
+ D = (cpc->psr >> 28) & 3;
+ cpc->pllmr |= (D + 1) << 20;
+ /* PT */
+ D = (cpc->psr >> 25) & 7;
+ switch (D) {
+ case 0x2:
+ cpc->pllmr |= 0x13;
+ break;
+ case 0x4:
+ cpc->pllmr |= 0x15;
+ break;
+ case 0x5:
+ cpc->pllmr |= 0x16;
+ break;
+ default:
+ break;
+ }
+ /* PDC */
+ D = (cpc->psr >> 23) & 3;
+ cpc->pllmr |= D << 26;
+ /* ODP */
+ D = (cpc->psr >> 21) & 3;
+ cpc->pllmr |= D << 10;
+ /* EBPD */
+ D = (cpc->psr >> 17) & 3;
+ cpc->pllmr |= D << 24;
+ cpc->cr0 = 0x0000003C;
+ cpc->cr1 = 0x2B0D8800;
+ cpc->er = 0x00000000;
+ cpc->fr = 0x00000000;
+ ppc405cr_clk_setup(cpc);
+}
+
+static void ppc405cr_clk_init (ppc405cr_cpc_t *cpc)
+{
+ int D;
+
+ /* XXX: this should be read from IO pins */
+ cpc->psr = 0x00000000; /* 8 bits ROM */
+ /* PFWD */
+ D = 0x2; /* Divide by 4 */
+ cpc->psr |= D << 30;
+ /* PFBD */
+ D = 0x1; /* Divide by 2 */
+ cpc->psr |= D << 28;
+ /* PDC */
+ D = 0x1; /* Divide by 2 */
+ cpc->psr |= D << 23;
+ /* PT */
+ D = 0x5; /* M = 16 */
+ cpc->psr |= D << 25;
+ /* ODP */
+ D = 0x1; /* Divide by 2 */
+ cpc->psr |= D << 21;
+ /* EBDP */
+ D = 0x2; /* Divide by 4 */
+ cpc->psr |= D << 17;
+}
+
+static void ppc405cr_cpc_init (CPUPPCState *env, clk_setup_t clk_setup[7],
+ uint32_t sysclk)
+{
+ ppc405cr_cpc_t *cpc;
+
+ cpc = g_malloc0(sizeof(ppc405cr_cpc_t));
+ memcpy(cpc->clk_setup, clk_setup,
+ PPC405CR_CLK_NB * sizeof(clk_setup_t));
+ cpc->sysclk = sysclk;
+ cpc->jtagid = 0x42051049;
+ ppc_dcr_register(env, PPC405CR_CPC0_PSR, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_CR0, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_CR1, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_JTAGID, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_PLLMR, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_ER, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_FR, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_SR, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc405cr_clk_init(cpc);
+ qemu_register_reset(ppc405cr_cpc_reset, cpc);
+}
+
+CPUPPCState *ppc405cr_init(MemoryRegion *address_space_mem,
+ MemoryRegion ram_memories[4],
+ hwaddr ram_bases[4],
+ hwaddr ram_sizes[4],
+ uint32_t sysclk, qemu_irq **picp,
+ int do_init)
+{
+ clk_setup_t clk_setup[PPC405CR_CLK_NB];
+ qemu_irq dma_irqs[4];
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ qemu_irq *pic, *irqs;
+
+ memset(clk_setup, 0, sizeof(clk_setup));
+ cpu = ppc4xx_init("405cr", &clk_setup[PPC405CR_CPU_CLK],
+ &clk_setup[PPC405CR_TMR_CLK], sysclk);
+ env = &cpu->env;
+ /* Memory mapped devices registers */
+ /* PLB arbitrer */
+ ppc4xx_plb_init(env);
+ /* PLB to OPB bridge */
+ ppc4xx_pob_init(env);
+ /* OBP arbitrer */
+ ppc4xx_opba_init(0xef600600);
+ /* Universal interrupt controller */
+ irqs = g_malloc0(sizeof(qemu_irq) * PPCUIC_OUTPUT_NB);
+ irqs[PPCUIC_OUTPUT_INT] =
+ ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_INT];
+ irqs[PPCUIC_OUTPUT_CINT] =
+ ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_CINT];
+ pic = ppcuic_init(env, irqs, 0x0C0, 0, 1);
+ *picp = pic;
+ /* SDRAM controller */
+ ppc4xx_sdram_init(env, pic[14], 1, ram_memories,
+ ram_bases, ram_sizes, do_init);
+ /* External bus controller */
+ ppc405_ebc_init(env);
+ /* DMA controller */
+ dma_irqs[0] = pic[26];
+ dma_irqs[1] = pic[25];
+ dma_irqs[2] = pic[24];
+ dma_irqs[3] = pic[23];
+ ppc405_dma_init(env, dma_irqs);
+ /* Serial ports */
+ if (serial_hds[0] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600300, 0, pic[0],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[0],
+ DEVICE_BIG_ENDIAN);
+ }
+ if (serial_hds[1] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600400, 0, pic[1],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[1],
+ DEVICE_BIG_ENDIAN);
+ }
+ /* IIC controller */
+ ppc405_i2c_init(0xef600500, pic[2]);
+ /* GPIO */
+ ppc405_gpio_init(0xef600700);
+ /* CPU control */
+ ppc405cr_cpc_init(env, clk_setup, sysclk);
+
+ return env;
+}
+
+/*****************************************************************************/
+/* PowerPC 405EP */
+/* CPU control */
+enum {
+ PPC405EP_CPC0_PLLMR0 = 0x0F0,
+ PPC405EP_CPC0_BOOT = 0x0F1,
+ PPC405EP_CPC0_EPCTL = 0x0F3,
+ PPC405EP_CPC0_PLLMR1 = 0x0F4,
+ PPC405EP_CPC0_UCR = 0x0F5,
+ PPC405EP_CPC0_SRR = 0x0F6,
+ PPC405EP_CPC0_JTAGID = 0x0F7,
+ PPC405EP_CPC0_PCI = 0x0F9,
+#if 0
+ PPC405EP_CPC0_ER = xxx,
+ PPC405EP_CPC0_FR = xxx,
+ PPC405EP_CPC0_SR = xxx,
+#endif
+};
+
+enum {
+ PPC405EP_CPU_CLK = 0,
+ PPC405EP_PLB_CLK = 1,
+ PPC405EP_OPB_CLK = 2,
+ PPC405EP_EBC_CLK = 3,
+ PPC405EP_MAL_CLK = 4,
+ PPC405EP_PCI_CLK = 5,
+ PPC405EP_UART0_CLK = 6,
+ PPC405EP_UART1_CLK = 7,
+ PPC405EP_CLK_NB = 8,
+};
+
+typedef struct ppc405ep_cpc_t ppc405ep_cpc_t;
+struct ppc405ep_cpc_t {
+ uint32_t sysclk;
+ clk_setup_t clk_setup[PPC405EP_CLK_NB];
+ uint32_t boot;
+ uint32_t epctl;
+ uint32_t pllmr[2];
+ uint32_t ucr;
+ uint32_t srr;
+ uint32_t jtagid;
+ uint32_t pci;
+ /* Clock and power management */
+ uint32_t er;
+ uint32_t fr;
+ uint32_t sr;
+};
+
+static void ppc405ep_compute_clocks (ppc405ep_cpc_t *cpc)
+{
+ uint32_t CPU_clk, PLB_clk, OPB_clk, EBC_clk, MAL_clk, PCI_clk;
+ uint32_t UART0_clk, UART1_clk;
+ uint64_t VCO_out, PLL_out;
+ int M, D;
+
+ VCO_out = 0;
+ if ((cpc->pllmr[1] & 0x80000000) && !(cpc->pllmr[1] & 0x40000000)) {
+ M = (((cpc->pllmr[1] >> 20) - 1) & 0xF) + 1; /* FBMUL */
+#ifdef DEBUG_CLOCKS_LL
+ printf("FBMUL %01" PRIx32 " %d\n", (cpc->pllmr[1] >> 20) & 0xF, M);
+#endif
+ D = 8 - ((cpc->pllmr[1] >> 16) & 0x7); /* FWDA */
+#ifdef DEBUG_CLOCKS_LL
+ printf("FWDA %01" PRIx32 " %d\n", (cpc->pllmr[1] >> 16) & 0x7, D);
+#endif
+ VCO_out = cpc->sysclk * M * D;
+ if (VCO_out < 500000000UL || VCO_out > 1000000000UL) {
+ /* Error - unlock the PLL */
+ printf("VCO out of range %" PRIu64 "\n", VCO_out);
+#if 0
+ cpc->pllmr[1] &= ~0x80000000;
+ goto pll_bypass;
+#endif
+ }
+ PLL_out = VCO_out / D;
+ /* Pretend the PLL is locked */
+ cpc->boot |= 0x00000001;
+ } else {
+#if 0
+ pll_bypass:
+#endif
+ PLL_out = cpc->sysclk;
+ if (cpc->pllmr[1] & 0x40000000) {
+ /* Pretend the PLL is not locked */
+ cpc->boot &= ~0x00000001;
+ }
+ }
+ /* Now, compute all other clocks */
+ D = ((cpc->pllmr[0] >> 20) & 0x3) + 1; /* CCDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("CCDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 20) & 0x3, D);
+#endif
+ CPU_clk = PLL_out / D;
+ D = ((cpc->pllmr[0] >> 16) & 0x3) + 1; /* CBDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("CBDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 16) & 0x3, D);
+#endif
+ PLB_clk = CPU_clk / D;
+ D = ((cpc->pllmr[0] >> 12) & 0x3) + 1; /* OPDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("OPDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 12) & 0x3, D);
+#endif
+ OPB_clk = PLB_clk / D;
+ D = ((cpc->pllmr[0] >> 8) & 0x3) + 2; /* EPDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("EPDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 8) & 0x3, D);
+#endif
+ EBC_clk = PLB_clk / D;
+ D = ((cpc->pllmr[0] >> 4) & 0x3) + 1; /* MPDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("MPDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 4) & 0x3, D);
+#endif
+ MAL_clk = PLB_clk / D;
+ D = (cpc->pllmr[0] & 0x3) + 1; /* PPDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("PPDV %01" PRIx32 " %d\n", cpc->pllmr[0] & 0x3, D);
+#endif
+ PCI_clk = PLB_clk / D;
+ D = ((cpc->ucr - 1) & 0x7F) + 1; /* U0DIV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("U0DIV %01" PRIx32 " %d\n", cpc->ucr & 0x7F, D);
+#endif
+ UART0_clk = PLL_out / D;
+ D = (((cpc->ucr >> 8) - 1) & 0x7F) + 1; /* U1DIV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("U1DIV %01" PRIx32 " %d\n", (cpc->ucr >> 8) & 0x7F, D);
+#endif
+ UART1_clk = PLL_out / D;
+#ifdef DEBUG_CLOCKS
+ printf("Setup PPC405EP clocks - sysclk %" PRIu32 " VCO %" PRIu64
+ " PLL out %" PRIu64 " Hz\n", cpc->sysclk, VCO_out, PLL_out);
+ printf("CPU %" PRIu32 " PLB %" PRIu32 " OPB %" PRIu32 " EBC %" PRIu32
+ " MAL %" PRIu32 " PCI %" PRIu32 " UART0 %" PRIu32
+ " UART1 %" PRIu32 "\n",
+ CPU_clk, PLB_clk, OPB_clk, EBC_clk, MAL_clk, PCI_clk,
+ UART0_clk, UART1_clk);
+#endif
+ /* Setup CPU clocks */
+ clk_setup(&cpc->clk_setup[PPC405EP_CPU_CLK], CPU_clk);
+ /* Setup PLB clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_PLB_CLK], PLB_clk);
+ /* Setup OPB clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_OPB_CLK], OPB_clk);
+ /* Setup external clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_EBC_CLK], EBC_clk);
+ /* Setup MAL clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_MAL_CLK], MAL_clk);
+ /* Setup PCI clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_PCI_CLK], PCI_clk);
+ /* Setup UART0 clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_UART0_CLK], UART0_clk);
+ /* Setup UART1 clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_UART1_CLK], UART1_clk);
+}
+
+static uint32_t dcr_read_epcpc (void *opaque, int dcrn)
+{
+ ppc405ep_cpc_t *cpc;
+ uint32_t ret;
+
+ cpc = opaque;
+ switch (dcrn) {
+ case PPC405EP_CPC0_BOOT:
+ ret = cpc->boot;
+ break;
+ case PPC405EP_CPC0_EPCTL:
+ ret = cpc->epctl;
+ break;
+ case PPC405EP_CPC0_PLLMR0:
+ ret = cpc->pllmr[0];
+ break;
+ case PPC405EP_CPC0_PLLMR1:
+ ret = cpc->pllmr[1];
+ break;
+ case PPC405EP_CPC0_UCR:
+ ret = cpc->ucr;
+ break;
+ case PPC405EP_CPC0_SRR:
+ ret = cpc->srr;
+ break;
+ case PPC405EP_CPC0_JTAGID:
+ ret = cpc->jtagid;
+ break;
+ case PPC405EP_CPC0_PCI:
+ ret = cpc->pci;
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_epcpc (void *opaque, int dcrn, uint32_t val)
+{
+ ppc405ep_cpc_t *cpc;
+
+ cpc = opaque;
+ switch (dcrn) {
+ case PPC405EP_CPC0_BOOT:
+ /* Read-only register */
+ break;
+ case PPC405EP_CPC0_EPCTL:
+ /* Don't care for now */
+ cpc->epctl = val & 0xC00000F3;
+ break;
+ case PPC405EP_CPC0_PLLMR0:
+ cpc->pllmr[0] = val & 0x00633333;
+ ppc405ep_compute_clocks(cpc);
+ break;
+ case PPC405EP_CPC0_PLLMR1:
+ cpc->pllmr[1] = val & 0xC0F73FFF;
+ ppc405ep_compute_clocks(cpc);
+ break;
+ case PPC405EP_CPC0_UCR:
+ /* UART control - don't care for now */
+ cpc->ucr = val & 0x003F7F7F;
+ break;
+ case PPC405EP_CPC0_SRR:
+ cpc->srr = val;
+ break;
+ case PPC405EP_CPC0_JTAGID:
+ /* Read-only */
+ break;
+ case PPC405EP_CPC0_PCI:
+ cpc->pci = val;
+ break;
+ }
+}
+
+static void ppc405ep_cpc_reset (void *opaque)
+{
+ ppc405ep_cpc_t *cpc = opaque;
+
+ cpc->boot = 0x00000010; /* Boot from PCI - IIC EEPROM disabled */
+ cpc->epctl = 0x00000000;
+ cpc->pllmr[0] = 0x00011010;
+ cpc->pllmr[1] = 0x40000000;
+ cpc->ucr = 0x00000000;
+ cpc->srr = 0x00040000;
+ cpc->pci = 0x00000000;
+ cpc->er = 0x00000000;
+ cpc->fr = 0x00000000;
+ cpc->sr = 0x00000000;
+ ppc405ep_compute_clocks(cpc);
+}
+
+/* XXX: sysclk should be between 25 and 100 MHz */
+static void ppc405ep_cpc_init (CPUPPCState *env, clk_setup_t clk_setup[8],
+ uint32_t sysclk)
+{
+ ppc405ep_cpc_t *cpc;
+
+ cpc = g_malloc0(sizeof(ppc405ep_cpc_t));
+ memcpy(cpc->clk_setup, clk_setup,
+ PPC405EP_CLK_NB * sizeof(clk_setup_t));
+ cpc->jtagid = 0x20267049;
+ cpc->sysclk = sysclk;
+ qemu_register_reset(&ppc405ep_cpc_reset, cpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_BOOT, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_EPCTL, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_PLLMR0, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_PLLMR1, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_UCR, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_SRR, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_JTAGID, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_PCI, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+#if 0
+ ppc_dcr_register(env, PPC405EP_CPC0_ER, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_FR, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_SR, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+#endif
+}
+
+CPUPPCState *ppc405ep_init(MemoryRegion *address_space_mem,
+ MemoryRegion ram_memories[2],
+ hwaddr ram_bases[2],
+ hwaddr ram_sizes[2],
+ uint32_t sysclk, qemu_irq **picp,
+ int do_init)
+{
+ clk_setup_t clk_setup[PPC405EP_CLK_NB], tlb_clk_setup;
+ qemu_irq dma_irqs[4], gpt_irqs[5], mal_irqs[4];
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ qemu_irq *pic, *irqs;
+
+ memset(clk_setup, 0, sizeof(clk_setup));
+ /* init CPUs */
+ cpu = ppc4xx_init("405ep", &clk_setup[PPC405EP_CPU_CLK],
+ &tlb_clk_setup, sysclk);
+ env = &cpu->env;
+ clk_setup[PPC405EP_CPU_CLK].cb = tlb_clk_setup.cb;
+ clk_setup[PPC405EP_CPU_CLK].opaque = tlb_clk_setup.opaque;
+ /* Internal devices init */
+ /* Memory mapped devices registers */
+ /* PLB arbitrer */
+ ppc4xx_plb_init(env);
+ /* PLB to OPB bridge */
+ ppc4xx_pob_init(env);
+ /* OBP arbitrer */
+ ppc4xx_opba_init(0xef600600);
+ /* Initialize timers */
+ ppc_booke_timers_init(cpu, sysclk, 0);
+ /* Universal interrupt controller */
+ irqs = g_malloc0(sizeof(qemu_irq) * PPCUIC_OUTPUT_NB);
+ irqs[PPCUIC_OUTPUT_INT] =
+ ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_INT];
+ irqs[PPCUIC_OUTPUT_CINT] =
+ ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_CINT];
+ pic = ppcuic_init(env, irqs, 0x0C0, 0, 1);
+ *picp = pic;
+ /* SDRAM controller */
+ /* XXX 405EP has no ECC interrupt */
+ ppc4xx_sdram_init(env, pic[17], 2, ram_memories,
+ ram_bases, ram_sizes, do_init);
+ /* External bus controller */
+ ppc405_ebc_init(env);
+ /* DMA controller */
+ dma_irqs[0] = pic[5];
+ dma_irqs[1] = pic[6];
+ dma_irqs[2] = pic[7];
+ dma_irqs[3] = pic[8];
+ ppc405_dma_init(env, dma_irqs);
+ /* IIC controller */
+ ppc405_i2c_init(0xef600500, pic[2]);
+ /* GPIO */
+ ppc405_gpio_init(0xef600700);
+ /* Serial ports */
+ if (serial_hds[0] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600300, 0, pic[0],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[0],
+ DEVICE_BIG_ENDIAN);
+ }
+ if (serial_hds[1] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600400, 0, pic[1],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[1],
+ DEVICE_BIG_ENDIAN);
+ }
+ /* OCM */
+ ppc405_ocm_init(env);
+ /* GPT */
+ gpt_irqs[0] = pic[19];
+ gpt_irqs[1] = pic[20];
+ gpt_irqs[2] = pic[21];
+ gpt_irqs[3] = pic[22];
+ gpt_irqs[4] = pic[23];
+ ppc4xx_gpt_init(0xef600000, gpt_irqs);
+ /* PCI */
+ /* Uses pic[3], pic[16], pic[18] */
+ /* MAL */
+ mal_irqs[0] = pic[11];
+ mal_irqs[1] = pic[12];
+ mal_irqs[2] = pic[13];
+ mal_irqs[3] = pic[14];
+ ppc405_mal_init(env, mal_irqs);
+ /* Ethernet */
+ /* Uses pic[9], pic[15], pic[17] */
+ /* CPU control */
+ ppc405ep_cpc_init(env, clk_setup, sysclk);
+
+ return env;
+}
diff --git a/hw/ppc/ppc440_bamboo.c b/hw/ppc/ppc440_bamboo.c
new file mode 100644
index 00000000..032fa803
--- /dev/null
+++ b/hw/ppc/ppc440_bamboo.c
@@ -0,0 +1,307 @@
+/*
+ * QEMU PowerPC 440 Bamboo board emulation
+ *
+ * Copyright 2007 IBM Corporation.
+ * Authors:
+ * Jerone Young <jyoung5@us.ibm.com>
+ * Christian Ehrhardt <ehrhardt@linux.vnet.ibm.com>
+ * Hollis Blanchard <hollisb@us.ibm.com>
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ *
+ */
+
+#include "config.h"
+#include "qemu-common.h"
+#include "net/net.h"
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/boards.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+#include "sysemu/device_tree.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "exec/address-spaces.h"
+#include "hw/char/serial.h"
+#include "hw/ppc/ppc.h"
+#include "ppc405.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+
+#define BINARY_DEVICE_TREE_FILE "bamboo.dtb"
+
+/* from u-boot */
+#define KERNEL_ADDR 0x1000000
+#define FDT_ADDR 0x1800000
+#define RAMDISK_ADDR 0x1900000
+
+#define PPC440EP_PCI_CONFIG 0xeec00000
+#define PPC440EP_PCI_INTACK 0xeed00000
+#define PPC440EP_PCI_SPECIAL 0xeed00000
+#define PPC440EP_PCI_REGS 0xef400000
+#define PPC440EP_PCI_IO 0xe8000000
+#define PPC440EP_PCI_IOLEN 0x00010000
+
+#define PPC440EP_SDRAM_NR_BANKS 4
+
+static const unsigned int ppc440ep_sdram_bank_sizes[] = {
+ 256<<20, 128<<20, 64<<20, 32<<20, 16<<20, 8<<20, 0
+};
+
+static hwaddr entry;
+
+static int bamboo_load_device_tree(hwaddr addr,
+ uint32_t ramsize,
+ hwaddr initrd_base,
+ hwaddr initrd_size,
+ const char *kernel_cmdline)
+{
+ int ret = -1;
+ uint32_t mem_reg_property[] = { 0, 0, cpu_to_be32(ramsize) };
+ char *filename;
+ int fdt_size;
+ void *fdt;
+ uint32_t tb_freq = 400000000;
+ uint32_t clock_freq = 400000000;
+
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE);
+ if (!filename) {
+ goto out;
+ }
+ fdt = load_device_tree(filename, &fdt_size);
+ g_free(filename);
+ if (fdt == NULL) {
+ goto out;
+ }
+
+ /* Manipulate device tree in memory. */
+
+ ret = qemu_fdt_setprop(fdt, "/memory", "reg", mem_reg_property,
+ sizeof(mem_reg_property));
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /memory/reg\n");
+
+ ret = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-start",
+ initrd_base);
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n");
+
+ ret = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-end",
+ (initrd_base + initrd_size));
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /chosen/linux,initrd-end\n");
+
+ ret = qemu_fdt_setprop_string(fdt, "/chosen", "bootargs",
+ kernel_cmdline);
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /chosen/bootargs\n");
+
+ /* Copy data from the host device tree into the guest. Since the guest can
+ * directly access the timebase without host involvement, we must expose
+ * the correct frequencies. */
+ if (kvm_enabled()) {
+ tb_freq = kvmppc_get_tbfreq();
+ clock_freq = kvmppc_get_clockfreq();
+ }
+
+ qemu_fdt_setprop_cell(fdt, "/cpus/cpu@0", "clock-frequency",
+ clock_freq);
+ qemu_fdt_setprop_cell(fdt, "/cpus/cpu@0", "timebase-frequency",
+ tb_freq);
+
+ rom_add_blob_fixed(BINARY_DEVICE_TREE_FILE, fdt, fdt_size, addr);
+ g_free(fdt);
+ return 0;
+
+out:
+
+ return ret;
+}
+
+/* Create reset TLB entries for BookE, spanning the 32bit addr space. */
+static void mmubooke_create_initial_mapping(CPUPPCState *env,
+ target_ulong va,
+ hwaddr pa)
+{
+ ppcemb_tlb_t *tlb = &env->tlb.tlbe[0];
+
+ tlb->attr = 0;
+ tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4);
+ tlb->size = 1U << 31; /* up to 0x80000000 */
+ tlb->EPN = va & TARGET_PAGE_MASK;
+ tlb->RPN = pa & TARGET_PAGE_MASK;
+ tlb->PID = 0;
+
+ tlb = &env->tlb.tlbe[1];
+ tlb->attr = 0;
+ tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4);
+ tlb->size = 1U << 31; /* up to 0xffffffff */
+ tlb->EPN = 0x80000000 & TARGET_PAGE_MASK;
+ tlb->RPN = 0x80000000 & TARGET_PAGE_MASK;
+ tlb->PID = 0;
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ cpu_reset(CPU(cpu));
+ env->gpr[1] = (16<<20) - 8;
+ env->gpr[3] = FDT_ADDR;
+ env->nip = entry;
+
+ /* Create a mapping for the kernel. */
+ mmubooke_create_initial_mapping(env, 0, 0);
+}
+
+static void bamboo_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ unsigned int pci_irq_nrs[4] = { 28, 27, 26, 25 };
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *isa = g_new(MemoryRegion, 1);
+ MemoryRegion *ram_memories
+ = g_malloc(PPC440EP_SDRAM_NR_BANKS * sizeof(*ram_memories));
+ hwaddr ram_bases[PPC440EP_SDRAM_NR_BANKS];
+ hwaddr ram_sizes[PPC440EP_SDRAM_NR_BANKS];
+ qemu_irq *pic;
+ qemu_irq *irqs;
+ PCIBus *pcibus;
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ uint64_t elf_entry;
+ uint64_t elf_lowaddr;
+ hwaddr loadaddr = 0;
+ target_long initrd_size = 0;
+ DeviceState *dev;
+ int success;
+ int i;
+
+ /* Setup CPU. */
+ if (machine->cpu_model == NULL) {
+ machine->cpu_model = "440EP";
+ }
+ cpu = cpu_ppc_init(machine->cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to initialize CPU!\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ qemu_register_reset(main_cpu_reset, cpu);
+ ppc_booke_timers_init(cpu, 400000000, 0);
+ ppc_dcr_init(env, NULL, NULL);
+
+ /* interrupt controller */
+ irqs = g_malloc0(sizeof(qemu_irq) * PPCUIC_OUTPUT_NB);
+ irqs[PPCUIC_OUTPUT_INT] = ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_INT];
+ irqs[PPCUIC_OUTPUT_CINT] = ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_CINT];
+ pic = ppcuic_init(env, irqs, 0x0C0, 0, 1);
+
+ /* SDRAM controller */
+ memset(ram_bases, 0, sizeof(ram_bases));
+ memset(ram_sizes, 0, sizeof(ram_sizes));
+ ram_size = ppc4xx_sdram_adjust(ram_size, PPC440EP_SDRAM_NR_BANKS,
+ ram_memories,
+ ram_bases, ram_sizes,
+ ppc440ep_sdram_bank_sizes);
+ /* XXX 440EP's ECC interrupts are on UIC1, but we've only created UIC0. */
+ ppc4xx_sdram_init(env, pic[14], PPC440EP_SDRAM_NR_BANKS, ram_memories,
+ ram_bases, ram_sizes, 1);
+
+ /* PCI */
+ dev = sysbus_create_varargs(TYPE_PPC4xx_PCI_HOST_BRIDGE,
+ PPC440EP_PCI_CONFIG,
+ pic[pci_irq_nrs[0]], pic[pci_irq_nrs[1]],
+ pic[pci_irq_nrs[2]], pic[pci_irq_nrs[3]],
+ NULL);
+ pcibus = (PCIBus *)qdev_get_child_bus(dev, "pci.0");
+ if (!pcibus) {
+ fprintf(stderr, "couldn't create PCI controller!\n");
+ exit(1);
+ }
+
+ memory_region_init_alias(isa, NULL, "isa_mmio",
+ get_system_io(), 0, PPC440EP_PCI_IOLEN);
+ memory_region_add_subregion(get_system_memory(), PPC440EP_PCI_IO, isa);
+
+ if (serial_hds[0] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600300, 0, pic[0],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[0],
+ DEVICE_BIG_ENDIAN);
+ }
+ if (serial_hds[1] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600400, 0, pic[1],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[1],
+ DEVICE_BIG_ENDIAN);
+ }
+
+ if (pcibus) {
+ /* Register network interfaces. */
+ for (i = 0; i < nb_nics; i++) {
+ /* There are no PCI NICs on the Bamboo board, but there are
+ * PCI slots, so we can pick whatever default model we want. */
+ pci_nic_init_nofail(&nd_table[i], pcibus, "e1000", NULL);
+ }
+ }
+
+ /* Load kernel. */
+ if (kernel_filename) {
+ success = load_uimage(kernel_filename, &entry, &loadaddr, NULL,
+ NULL, NULL);
+ if (success < 0) {
+ success = load_elf(kernel_filename, NULL, NULL, &elf_entry,
+ &elf_lowaddr, NULL, 1, ELF_MACHINE, 0);
+ entry = elf_entry;
+ loadaddr = elf_lowaddr;
+ }
+ /* XXX try again as binary */
+ if (success < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ }
+
+ /* Load initrd. */
+ if (initrd_filename) {
+ initrd_size = load_image_targphys(initrd_filename, RAMDISK_ADDR,
+ ram_size - RAMDISK_ADDR);
+
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load ram disk '%s' at %x\n",
+ initrd_filename, RAMDISK_ADDR);
+ exit(1);
+ }
+ }
+
+ /* If we're loading a kernel directly, we must load the device tree too. */
+ if (kernel_filename) {
+ if (bamboo_load_device_tree(FDT_ADDR, ram_size, RAMDISK_ADDR,
+ initrd_size, kernel_cmdline) < 0) {
+ fprintf(stderr, "couldn't load device tree\n");
+ exit(1);
+ }
+ }
+
+ if (kvm_enabled())
+ kvmppc_init();
+}
+
+static QEMUMachine bamboo_machine = {
+ .name = "bamboo",
+ .desc = "bamboo",
+ .init = bamboo_init,
+};
+
+static void bamboo_machine_init(void)
+{
+ qemu_register_machine(&bamboo_machine);
+}
+
+machine_init(bamboo_machine_init);
diff --git a/hw/ppc/ppc4xx_devs.c b/hw/ppc/ppc4xx_devs.c
new file mode 100644
index 00000000..2f38ff7d
--- /dev/null
+++ b/hw/ppc/ppc4xx_devs.c
@@ -0,0 +1,734 @@
+/*
+ * QEMU PowerPC 4xx embedded processors shared devices emulation
+ *
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "hw/ppc/ppc4xx.h"
+#include "hw/boards.h"
+#include "qemu/log.h"
+#include "exec/address-spaces.h"
+
+#define DEBUG_UIC
+
+
+#ifdef DEBUG_UIC
+# define LOG_UIC(...) qemu_log_mask(CPU_LOG_INT, ## __VA_ARGS__)
+#else
+# define LOG_UIC(...) do { } while (0)
+#endif
+
+static void ppc4xx_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+/*****************************************************************************/
+/* Generic PowerPC 4xx processor instantiation */
+PowerPCCPU *ppc4xx_init(const char *cpu_model,
+ clk_setup_t *cpu_clk, clk_setup_t *tb_clk,
+ uint32_t sysclk)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+
+ /* init CPUs */
+ cpu = cpu_ppc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find PowerPC %s CPU definition\n",
+ cpu_model);
+ exit(1);
+ }
+ env = &cpu->env;
+
+ cpu_clk->cb = NULL; /* We don't care about CPU clock frequency changes */
+ cpu_clk->opaque = env;
+ /* Set time-base frequency to sysclk */
+ tb_clk->cb = ppc_40x_timers_init(env, sysclk, PPC_INTERRUPT_PIT);
+ tb_clk->opaque = env;
+ ppc_dcr_init(env, NULL, NULL);
+ /* Register qemu callbacks */
+ qemu_register_reset(ppc4xx_reset, cpu);
+
+ return cpu;
+}
+
+/*****************************************************************************/
+/* "Universal" Interrupt controller */
+enum {
+ DCR_UICSR = 0x000,
+ DCR_UICSRS = 0x001,
+ DCR_UICER = 0x002,
+ DCR_UICCR = 0x003,
+ DCR_UICPR = 0x004,
+ DCR_UICTR = 0x005,
+ DCR_UICMSR = 0x006,
+ DCR_UICVR = 0x007,
+ DCR_UICVCR = 0x008,
+ DCR_UICMAX = 0x009,
+};
+
+#define UIC_MAX_IRQ 32
+typedef struct ppcuic_t ppcuic_t;
+struct ppcuic_t {
+ uint32_t dcr_base;
+ int use_vectors;
+ uint32_t level; /* Remembers the state of level-triggered interrupts. */
+ uint32_t uicsr; /* Status register */
+ uint32_t uicer; /* Enable register */
+ uint32_t uiccr; /* Critical register */
+ uint32_t uicpr; /* Polarity register */
+ uint32_t uictr; /* Triggering register */
+ uint32_t uicvcr; /* Vector configuration register */
+ uint32_t uicvr;
+ qemu_irq *irqs;
+};
+
+static void ppcuic_trigger_irq (ppcuic_t *uic)
+{
+ uint32_t ir, cr;
+ int start, end, inc, i;
+
+ /* Trigger interrupt if any is pending */
+ ir = uic->uicsr & uic->uicer & (~uic->uiccr);
+ cr = uic->uicsr & uic->uicer & uic->uiccr;
+ LOG_UIC("%s: uicsr %08" PRIx32 " uicer %08" PRIx32
+ " uiccr %08" PRIx32 "\n"
+ " %08" PRIx32 " ir %08" PRIx32 " cr %08" PRIx32 "\n",
+ __func__, uic->uicsr, uic->uicer, uic->uiccr,
+ uic->uicsr & uic->uicer, ir, cr);
+ if (ir != 0x0000000) {
+ LOG_UIC("Raise UIC interrupt\n");
+ qemu_irq_raise(uic->irqs[PPCUIC_OUTPUT_INT]);
+ } else {
+ LOG_UIC("Lower UIC interrupt\n");
+ qemu_irq_lower(uic->irqs[PPCUIC_OUTPUT_INT]);
+ }
+ /* Trigger critical interrupt if any is pending and update vector */
+ if (cr != 0x0000000) {
+ qemu_irq_raise(uic->irqs[PPCUIC_OUTPUT_CINT]);
+ if (uic->use_vectors) {
+ /* Compute critical IRQ vector */
+ if (uic->uicvcr & 1) {
+ start = 31;
+ end = 0;
+ inc = -1;
+ } else {
+ start = 0;
+ end = 31;
+ inc = 1;
+ }
+ uic->uicvr = uic->uicvcr & 0xFFFFFFFC;
+ for (i = start; i <= end; i += inc) {
+ if (cr & (1 << i)) {
+ uic->uicvr += (i - start) * 512 * inc;
+ break;
+ }
+ }
+ }
+ LOG_UIC("Raise UIC critical interrupt - "
+ "vector %08" PRIx32 "\n", uic->uicvr);
+ } else {
+ LOG_UIC("Lower UIC critical interrupt\n");
+ qemu_irq_lower(uic->irqs[PPCUIC_OUTPUT_CINT]);
+ uic->uicvr = 0x00000000;
+ }
+}
+
+static void ppcuic_set_irq (void *opaque, int irq_num, int level)
+{
+ ppcuic_t *uic;
+ uint32_t mask, sr;
+
+ uic = opaque;
+ mask = 1U << (31-irq_num);
+ LOG_UIC("%s: irq %d level %d uicsr %08" PRIx32
+ " mask %08" PRIx32 " => %08" PRIx32 " %08" PRIx32 "\n",
+ __func__, irq_num, level,
+ uic->uicsr, mask, uic->uicsr & mask, level << irq_num);
+ if (irq_num < 0 || irq_num > 31)
+ return;
+ sr = uic->uicsr;
+
+ /* Update status register */
+ if (uic->uictr & mask) {
+ /* Edge sensitive interrupt */
+ if (level == 1)
+ uic->uicsr |= mask;
+ } else {
+ /* Level sensitive interrupt */
+ if (level == 1) {
+ uic->uicsr |= mask;
+ uic->level |= mask;
+ } else {
+ uic->uicsr &= ~mask;
+ uic->level &= ~mask;
+ }
+ }
+ LOG_UIC("%s: irq %d level %d sr %" PRIx32 " => "
+ "%08" PRIx32 "\n", __func__, irq_num, level, uic->uicsr, sr);
+ if (sr != uic->uicsr)
+ ppcuic_trigger_irq(uic);
+}
+
+static uint32_t dcr_read_uic (void *opaque, int dcrn)
+{
+ ppcuic_t *uic;
+ uint32_t ret;
+
+ uic = opaque;
+ dcrn -= uic->dcr_base;
+ switch (dcrn) {
+ case DCR_UICSR:
+ case DCR_UICSRS:
+ ret = uic->uicsr;
+ break;
+ case DCR_UICER:
+ ret = uic->uicer;
+ break;
+ case DCR_UICCR:
+ ret = uic->uiccr;
+ break;
+ case DCR_UICPR:
+ ret = uic->uicpr;
+ break;
+ case DCR_UICTR:
+ ret = uic->uictr;
+ break;
+ case DCR_UICMSR:
+ ret = uic->uicsr & uic->uicer;
+ break;
+ case DCR_UICVR:
+ if (!uic->use_vectors)
+ goto no_read;
+ ret = uic->uicvr;
+ break;
+ case DCR_UICVCR:
+ if (!uic->use_vectors)
+ goto no_read;
+ ret = uic->uicvcr;
+ break;
+ default:
+ no_read:
+ ret = 0x00000000;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_uic (void *opaque, int dcrn, uint32_t val)
+{
+ ppcuic_t *uic;
+
+ uic = opaque;
+ dcrn -= uic->dcr_base;
+ LOG_UIC("%s: dcr %d val 0x%x\n", __func__, dcrn, val);
+ switch (dcrn) {
+ case DCR_UICSR:
+ uic->uicsr &= ~val;
+ uic->uicsr |= uic->level;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICSRS:
+ uic->uicsr |= val;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICER:
+ uic->uicer = val;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICCR:
+ uic->uiccr = val;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICPR:
+ uic->uicpr = val;
+ break;
+ case DCR_UICTR:
+ uic->uictr = val;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICMSR:
+ break;
+ case DCR_UICVR:
+ break;
+ case DCR_UICVCR:
+ uic->uicvcr = val & 0xFFFFFFFD;
+ ppcuic_trigger_irq(uic);
+ break;
+ }
+}
+
+static void ppcuic_reset (void *opaque)
+{
+ ppcuic_t *uic;
+
+ uic = opaque;
+ uic->uiccr = 0x00000000;
+ uic->uicer = 0x00000000;
+ uic->uicpr = 0x00000000;
+ uic->uicsr = 0x00000000;
+ uic->uictr = 0x00000000;
+ if (uic->use_vectors) {
+ uic->uicvcr = 0x00000000;
+ uic->uicvr = 0x0000000;
+ }
+}
+
+qemu_irq *ppcuic_init (CPUPPCState *env, qemu_irq *irqs,
+ uint32_t dcr_base, int has_ssr, int has_vr)
+{
+ ppcuic_t *uic;
+ int i;
+
+ uic = g_malloc0(sizeof(ppcuic_t));
+ uic->dcr_base = dcr_base;
+ uic->irqs = irqs;
+ if (has_vr)
+ uic->use_vectors = 1;
+ for (i = 0; i < DCR_UICMAX; i++) {
+ ppc_dcr_register(env, dcr_base + i, uic,
+ &dcr_read_uic, &dcr_write_uic);
+ }
+ qemu_register_reset(ppcuic_reset, uic);
+
+ return qemu_allocate_irqs(&ppcuic_set_irq, uic, UIC_MAX_IRQ);
+}
+
+/*****************************************************************************/
+/* SDRAM controller */
+typedef struct ppc4xx_sdram_t ppc4xx_sdram_t;
+struct ppc4xx_sdram_t {
+ uint32_t addr;
+ int nbanks;
+ MemoryRegion containers[4]; /* used for clipping */
+ MemoryRegion *ram_memories;
+ hwaddr ram_bases[4];
+ hwaddr ram_sizes[4];
+ uint32_t besr0;
+ uint32_t besr1;
+ uint32_t bear;
+ uint32_t cfg;
+ uint32_t status;
+ uint32_t rtr;
+ uint32_t pmit;
+ uint32_t bcr[4];
+ uint32_t tr;
+ uint32_t ecccfg;
+ uint32_t eccesr;
+ qemu_irq irq;
+};
+
+enum {
+ SDRAM0_CFGADDR = 0x010,
+ SDRAM0_CFGDATA = 0x011,
+};
+
+/* XXX: TOFIX: some patches have made this code become inconsistent:
+ * there are type inconsistencies, mixing hwaddr, target_ulong
+ * and uint32_t
+ */
+static uint32_t sdram_bcr (hwaddr ram_base,
+ hwaddr ram_size)
+{
+ uint32_t bcr;
+
+ switch (ram_size) {
+ case (4 * 1024 * 1024):
+ bcr = 0x00000000;
+ break;
+ case (8 * 1024 * 1024):
+ bcr = 0x00020000;
+ break;
+ case (16 * 1024 * 1024):
+ bcr = 0x00040000;
+ break;
+ case (32 * 1024 * 1024):
+ bcr = 0x00060000;
+ break;
+ case (64 * 1024 * 1024):
+ bcr = 0x00080000;
+ break;
+ case (128 * 1024 * 1024):
+ bcr = 0x000A0000;
+ break;
+ case (256 * 1024 * 1024):
+ bcr = 0x000C0000;
+ break;
+ default:
+ printf("%s: invalid RAM size " TARGET_FMT_plx "\n", __func__,
+ ram_size);
+ return 0x00000000;
+ }
+ bcr |= ram_base & 0xFF800000;
+ bcr |= 1;
+
+ return bcr;
+}
+
+static inline hwaddr sdram_base(uint32_t bcr)
+{
+ return bcr & 0xFF800000;
+}
+
+static target_ulong sdram_size (uint32_t bcr)
+{
+ target_ulong size;
+ int sh;
+
+ sh = (bcr >> 17) & 0x7;
+ if (sh == 7)
+ size = -1;
+ else
+ size = (4 * 1024 * 1024) << sh;
+
+ return size;
+}
+
+static void sdram_set_bcr(ppc4xx_sdram_t *sdram,
+ uint32_t *bcrp, uint32_t bcr, int enabled)
+{
+ unsigned n = bcrp - sdram->bcr;
+
+ if (*bcrp & 0x00000001) {
+ /* Unmap RAM */
+#ifdef DEBUG_SDRAM
+ printf("%s: unmap RAM area " TARGET_FMT_plx " " TARGET_FMT_lx "\n",
+ __func__, sdram_base(*bcrp), sdram_size(*bcrp));
+#endif
+ memory_region_del_subregion(get_system_memory(),
+ &sdram->containers[n]);
+ memory_region_del_subregion(&sdram->containers[n],
+ &sdram->ram_memories[n]);
+ object_unparent(OBJECT(&sdram->containers[n]));
+ }
+ *bcrp = bcr & 0xFFDEE001;
+ if (enabled && (bcr & 0x00000001)) {
+#ifdef DEBUG_SDRAM
+ printf("%s: Map RAM area " TARGET_FMT_plx " " TARGET_FMT_lx "\n",
+ __func__, sdram_base(bcr), sdram_size(bcr));
+#endif
+ memory_region_init(&sdram->containers[n], NULL, "sdram-containers",
+ sdram_size(bcr));
+ memory_region_add_subregion(&sdram->containers[n], 0,
+ &sdram->ram_memories[n]);
+ memory_region_add_subregion(get_system_memory(),
+ sdram_base(bcr),
+ &sdram->containers[n]);
+ }
+}
+
+static void sdram_map_bcr (ppc4xx_sdram_t *sdram)
+{
+ int i;
+
+ for (i = 0; i < sdram->nbanks; i++) {
+ if (sdram->ram_sizes[i] != 0) {
+ sdram_set_bcr(sdram,
+ &sdram->bcr[i],
+ sdram_bcr(sdram->ram_bases[i], sdram->ram_sizes[i]),
+ 1);
+ } else {
+ sdram_set_bcr(sdram, &sdram->bcr[i], 0x00000000, 0);
+ }
+ }
+}
+
+static void sdram_unmap_bcr (ppc4xx_sdram_t *sdram)
+{
+ int i;
+
+ for (i = 0; i < sdram->nbanks; i++) {
+#ifdef DEBUG_SDRAM
+ printf("%s: Unmap RAM area " TARGET_FMT_plx " " TARGET_FMT_lx "\n",
+ __func__, sdram_base(sdram->bcr[i]), sdram_size(sdram->bcr[i]));
+#endif
+ memory_region_del_subregion(get_system_memory(),
+ &sdram->ram_memories[i]);
+ }
+}
+
+static uint32_t dcr_read_sdram (void *opaque, int dcrn)
+{
+ ppc4xx_sdram_t *sdram;
+ uint32_t ret;
+
+ sdram = opaque;
+ switch (dcrn) {
+ case SDRAM0_CFGADDR:
+ ret = sdram->addr;
+ break;
+ case SDRAM0_CFGDATA:
+ switch (sdram->addr) {
+ case 0x00: /* SDRAM_BESR0 */
+ ret = sdram->besr0;
+ break;
+ case 0x08: /* SDRAM_BESR1 */
+ ret = sdram->besr1;
+ break;
+ case 0x10: /* SDRAM_BEAR */
+ ret = sdram->bear;
+ break;
+ case 0x20: /* SDRAM_CFG */
+ ret = sdram->cfg;
+ break;
+ case 0x24: /* SDRAM_STATUS */
+ ret = sdram->status;
+ break;
+ case 0x30: /* SDRAM_RTR */
+ ret = sdram->rtr;
+ break;
+ case 0x34: /* SDRAM_PMIT */
+ ret = sdram->pmit;
+ break;
+ case 0x40: /* SDRAM_B0CR */
+ ret = sdram->bcr[0];
+ break;
+ case 0x44: /* SDRAM_B1CR */
+ ret = sdram->bcr[1];
+ break;
+ case 0x48: /* SDRAM_B2CR */
+ ret = sdram->bcr[2];
+ break;
+ case 0x4C: /* SDRAM_B3CR */
+ ret = sdram->bcr[3];
+ break;
+ case 0x80: /* SDRAM_TR */
+ ret = -1; /* ? */
+ break;
+ case 0x94: /* SDRAM_ECCCFG */
+ ret = sdram->ecccfg;
+ break;
+ case 0x98: /* SDRAM_ECCESR */
+ ret = sdram->eccesr;
+ break;
+ default: /* Error */
+ ret = -1;
+ break;
+ }
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0x00000000;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_sdram (void *opaque, int dcrn, uint32_t val)
+{
+ ppc4xx_sdram_t *sdram;
+
+ sdram = opaque;
+ switch (dcrn) {
+ case SDRAM0_CFGADDR:
+ sdram->addr = val;
+ break;
+ case SDRAM0_CFGDATA:
+ switch (sdram->addr) {
+ case 0x00: /* SDRAM_BESR0 */
+ sdram->besr0 &= ~val;
+ break;
+ case 0x08: /* SDRAM_BESR1 */
+ sdram->besr1 &= ~val;
+ break;
+ case 0x10: /* SDRAM_BEAR */
+ sdram->bear = val;
+ break;
+ case 0x20: /* SDRAM_CFG */
+ val &= 0xFFE00000;
+ if (!(sdram->cfg & 0x80000000) && (val & 0x80000000)) {
+#ifdef DEBUG_SDRAM
+ printf("%s: enable SDRAM controller\n", __func__);
+#endif
+ /* validate all RAM mappings */
+ sdram_map_bcr(sdram);
+ sdram->status &= ~0x80000000;
+ } else if ((sdram->cfg & 0x80000000) && !(val & 0x80000000)) {
+#ifdef DEBUG_SDRAM
+ printf("%s: disable SDRAM controller\n", __func__);
+#endif
+ /* invalidate all RAM mappings */
+ sdram_unmap_bcr(sdram);
+ sdram->status |= 0x80000000;
+ }
+ if (!(sdram->cfg & 0x40000000) && (val & 0x40000000))
+ sdram->status |= 0x40000000;
+ else if ((sdram->cfg & 0x40000000) && !(val & 0x40000000))
+ sdram->status &= ~0x40000000;
+ sdram->cfg = val;
+ break;
+ case 0x24: /* SDRAM_STATUS */
+ /* Read-only register */
+ break;
+ case 0x30: /* SDRAM_RTR */
+ sdram->rtr = val & 0x3FF80000;
+ break;
+ case 0x34: /* SDRAM_PMIT */
+ sdram->pmit = (val & 0xF8000000) | 0x07C00000;
+ break;
+ case 0x40: /* SDRAM_B0CR */
+ sdram_set_bcr(sdram, &sdram->bcr[0], val, sdram->cfg & 0x80000000);
+ break;
+ case 0x44: /* SDRAM_B1CR */
+ sdram_set_bcr(sdram, &sdram->bcr[1], val, sdram->cfg & 0x80000000);
+ break;
+ case 0x48: /* SDRAM_B2CR */
+ sdram_set_bcr(sdram, &sdram->bcr[2], val, sdram->cfg & 0x80000000);
+ break;
+ case 0x4C: /* SDRAM_B3CR */
+ sdram_set_bcr(sdram, &sdram->bcr[3], val, sdram->cfg & 0x80000000);
+ break;
+ case 0x80: /* SDRAM_TR */
+ sdram->tr = val & 0x018FC01F;
+ break;
+ case 0x94: /* SDRAM_ECCCFG */
+ sdram->ecccfg = val & 0x00F00000;
+ break;
+ case 0x98: /* SDRAM_ECCESR */
+ val &= 0xFFF0F000;
+ if (sdram->eccesr == 0 && val != 0)
+ qemu_irq_raise(sdram->irq);
+ else if (sdram->eccesr != 0 && val == 0)
+ qemu_irq_lower(sdram->irq);
+ sdram->eccesr = val;
+ break;
+ default: /* Error */
+ break;
+ }
+ break;
+ }
+}
+
+static void sdram_reset (void *opaque)
+{
+ ppc4xx_sdram_t *sdram;
+
+ sdram = opaque;
+ sdram->addr = 0x00000000;
+ sdram->bear = 0x00000000;
+ sdram->besr0 = 0x00000000; /* No error */
+ sdram->besr1 = 0x00000000; /* No error */
+ sdram->cfg = 0x00000000;
+ sdram->ecccfg = 0x00000000; /* No ECC */
+ sdram->eccesr = 0x00000000; /* No error */
+ sdram->pmit = 0x07C00000;
+ sdram->rtr = 0x05F00000;
+ sdram->tr = 0x00854009;
+ /* We pre-initialize RAM banks */
+ sdram->status = 0x00000000;
+ sdram->cfg = 0x00800000;
+}
+
+void ppc4xx_sdram_init (CPUPPCState *env, qemu_irq irq, int nbanks,
+ MemoryRegion *ram_memories,
+ hwaddr *ram_bases,
+ hwaddr *ram_sizes,
+ int do_init)
+{
+ ppc4xx_sdram_t *sdram;
+
+ sdram = g_malloc0(sizeof(ppc4xx_sdram_t));
+ sdram->irq = irq;
+ sdram->nbanks = nbanks;
+ sdram->ram_memories = ram_memories;
+ memset(sdram->ram_bases, 0, 4 * sizeof(hwaddr));
+ memcpy(sdram->ram_bases, ram_bases,
+ nbanks * sizeof(hwaddr));
+ memset(sdram->ram_sizes, 0, 4 * sizeof(hwaddr));
+ memcpy(sdram->ram_sizes, ram_sizes,
+ nbanks * sizeof(hwaddr));
+ qemu_register_reset(&sdram_reset, sdram);
+ ppc_dcr_register(env, SDRAM0_CFGADDR,
+ sdram, &dcr_read_sdram, &dcr_write_sdram);
+ ppc_dcr_register(env, SDRAM0_CFGDATA,
+ sdram, &dcr_read_sdram, &dcr_write_sdram);
+ if (do_init)
+ sdram_map_bcr(sdram);
+}
+
+/* Fill in consecutive SDRAM banks with 'ram_size' bytes of memory.
+ *
+ * sdram_bank_sizes[] must be 0-terminated.
+ *
+ * The 4xx SDRAM controller supports a small number of banks, and each bank
+ * must be one of a small set of sizes. The number of banks and the supported
+ * sizes varies by SoC. */
+ram_addr_t ppc4xx_sdram_adjust(ram_addr_t ram_size, int nr_banks,
+ MemoryRegion ram_memories[],
+ hwaddr ram_bases[],
+ hwaddr ram_sizes[],
+ const unsigned int sdram_bank_sizes[])
+{
+ MemoryRegion *ram = g_malloc0(sizeof(*ram));
+ ram_addr_t size_left = ram_size;
+ ram_addr_t base = 0;
+ unsigned int bank_size;
+ int i;
+ int j;
+
+ for (i = 0; i < nr_banks; i++) {
+ for (j = 0; sdram_bank_sizes[j] != 0; j++) {
+ bank_size = sdram_bank_sizes[j];
+ if (bank_size <= size_left) {
+ size_left -= bank_size;
+ }
+ }
+ if (!size_left) {
+ /* No need to use the remaining banks. */
+ break;
+ }
+ }
+
+ ram_size -= size_left;
+ if (size_left) {
+ printf("Truncating memory to %d MiB to fit SDRAM controller limits.\n",
+ (int)(ram_size >> 20));
+ }
+
+ memory_region_allocate_system_memory(ram, NULL, "ppc4xx.sdram", ram_size);
+
+ size_left = ram_size;
+ for (i = 0; i < nr_banks && size_left; i++) {
+ for (j = 0; sdram_bank_sizes[j] != 0; j++) {
+ bank_size = sdram_bank_sizes[j];
+
+ if (bank_size <= size_left) {
+ char name[32];
+ snprintf(name, sizeof(name), "ppc4xx.sdram%d", i);
+ memory_region_init_alias(&ram_memories[i], NULL, name, ram,
+ base, bank_size);
+ ram_bases[i] = base;
+ ram_sizes[i] = bank_size;
+ base += bank_size;
+ size_left -= bank_size;
+ break;
+ }
+ }
+ }
+
+ return ram_size;
+}
diff --git a/hw/ppc/ppc4xx_pci.c b/hw/ppc/ppc4xx_pci.c
new file mode 100644
index 00000000..0bb3cdb4
--- /dev/null
+++ b/hw/ppc/ppc4xx_pci.c
@@ -0,0 +1,392 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright IBM Corp. 2008
+ *
+ * Authors: Hollis Blanchard <hollisb@us.ibm.com>
+ */
+
+/* This file implements emulation of the 32-bit PCI controller found in some
+ * 4xx SoCs, such as the 440EP. */
+
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "hw/ppc/ppc4xx.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "exec/address-spaces.h"
+
+#undef DEBUG
+#ifdef DEBUG
+#define DPRINTF(fmt, ...) do { printf(fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...)
+#endif /* DEBUG */
+
+struct PCIMasterMap {
+ uint32_t la;
+ uint32_t ma;
+ uint32_t pcila;
+ uint32_t pciha;
+};
+
+struct PCITargetMap {
+ uint32_t ms;
+ uint32_t la;
+};
+
+#define PPC4xx_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(PPC4xxPCIState, (obj), TYPE_PPC4xx_PCI_HOST_BRIDGE)
+
+#define PPC4xx_PCI_NR_PMMS 3
+#define PPC4xx_PCI_NR_PTMS 2
+
+struct PPC4xxPCIState {
+ PCIHostState parent_obj;
+
+ struct PCIMasterMap pmm[PPC4xx_PCI_NR_PMMS];
+ struct PCITargetMap ptm[PPC4xx_PCI_NR_PTMS];
+ qemu_irq irq[4];
+
+ MemoryRegion container;
+ MemoryRegion iomem;
+};
+typedef struct PPC4xxPCIState PPC4xxPCIState;
+
+#define PCIC0_CFGADDR 0x0
+#define PCIC0_CFGDATA 0x4
+
+/* PLB Memory Map (PMM) registers specify which PLB addresses are translated to
+ * PCI accesses. */
+#define PCIL0_PMM0LA 0x0
+#define PCIL0_PMM0MA 0x4
+#define PCIL0_PMM0PCILA 0x8
+#define PCIL0_PMM0PCIHA 0xc
+#define PCIL0_PMM1LA 0x10
+#define PCIL0_PMM1MA 0x14
+#define PCIL0_PMM1PCILA 0x18
+#define PCIL0_PMM1PCIHA 0x1c
+#define PCIL0_PMM2LA 0x20
+#define PCIL0_PMM2MA 0x24
+#define PCIL0_PMM2PCILA 0x28
+#define PCIL0_PMM2PCIHA 0x2c
+
+/* PCI Target Map (PTM) registers specify which PCI addresses are translated to
+ * PLB accesses. */
+#define PCIL0_PTM1MS 0x30
+#define PCIL0_PTM1LA 0x34
+#define PCIL0_PTM2MS 0x38
+#define PCIL0_PTM2LA 0x3c
+#define PCI_REG_BASE 0x800000
+#define PCI_REG_SIZE 0x40
+
+#define PCI_ALL_SIZE (PCI_REG_BASE + PCI_REG_SIZE)
+
+static void ppc4xx_pci_reg_write4(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ struct PPC4xxPCIState *pci = opaque;
+
+ /* We ignore all target attempts at PCI configuration, effectively
+ * assuming a bidirectional 1:1 mapping of PLB and PCI space. */
+
+ switch (offset) {
+ case PCIL0_PMM0LA:
+ pci->pmm[0].la = value;
+ break;
+ case PCIL0_PMM0MA:
+ pci->pmm[0].ma = value;
+ break;
+ case PCIL0_PMM0PCIHA:
+ pci->pmm[0].pciha = value;
+ break;
+ case PCIL0_PMM0PCILA:
+ pci->pmm[0].pcila = value;
+ break;
+
+ case PCIL0_PMM1LA:
+ pci->pmm[1].la = value;
+ break;
+ case PCIL0_PMM1MA:
+ pci->pmm[1].ma = value;
+ break;
+ case PCIL0_PMM1PCIHA:
+ pci->pmm[1].pciha = value;
+ break;
+ case PCIL0_PMM1PCILA:
+ pci->pmm[1].pcila = value;
+ break;
+
+ case PCIL0_PMM2LA:
+ pci->pmm[2].la = value;
+ break;
+ case PCIL0_PMM2MA:
+ pci->pmm[2].ma = value;
+ break;
+ case PCIL0_PMM2PCIHA:
+ pci->pmm[2].pciha = value;
+ break;
+ case PCIL0_PMM2PCILA:
+ pci->pmm[2].pcila = value;
+ break;
+
+ case PCIL0_PTM1MS:
+ pci->ptm[0].ms = value;
+ break;
+ case PCIL0_PTM1LA:
+ pci->ptm[0].la = value;
+ break;
+ case PCIL0_PTM2MS:
+ pci->ptm[1].ms = value;
+ break;
+ case PCIL0_PTM2LA:
+ pci->ptm[1].la = value;
+ break;
+
+ default:
+ printf("%s: unhandled PCI internal register 0x%lx\n", __func__,
+ (unsigned long)offset);
+ break;
+ }
+}
+
+static uint64_t ppc4xx_pci_reg_read4(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ struct PPC4xxPCIState *pci = opaque;
+ uint32_t value;
+
+ switch (offset) {
+ case PCIL0_PMM0LA:
+ value = pci->pmm[0].la;
+ break;
+ case PCIL0_PMM0MA:
+ value = pci->pmm[0].ma;
+ break;
+ case PCIL0_PMM0PCIHA:
+ value = pci->pmm[0].pciha;
+ break;
+ case PCIL0_PMM0PCILA:
+ value = pci->pmm[0].pcila;
+ break;
+
+ case PCIL0_PMM1LA:
+ value = pci->pmm[1].la;
+ break;
+ case PCIL0_PMM1MA:
+ value = pci->pmm[1].ma;
+ break;
+ case PCIL0_PMM1PCIHA:
+ value = pci->pmm[1].pciha;
+ break;
+ case PCIL0_PMM1PCILA:
+ value = pci->pmm[1].pcila;
+ break;
+
+ case PCIL0_PMM2LA:
+ value = pci->pmm[2].la;
+ break;
+ case PCIL0_PMM2MA:
+ value = pci->pmm[2].ma;
+ break;
+ case PCIL0_PMM2PCIHA:
+ value = pci->pmm[2].pciha;
+ break;
+ case PCIL0_PMM2PCILA:
+ value = pci->pmm[2].pcila;
+ break;
+
+ case PCIL0_PTM1MS:
+ value = pci->ptm[0].ms;
+ break;
+ case PCIL0_PTM1LA:
+ value = pci->ptm[0].la;
+ break;
+ case PCIL0_PTM2MS:
+ value = pci->ptm[1].ms;
+ break;
+ case PCIL0_PTM2LA:
+ value = pci->ptm[1].la;
+ break;
+
+ default:
+ printf("%s: invalid PCI internal register 0x%lx\n", __func__,
+ (unsigned long)offset);
+ value = 0;
+ }
+
+ return value;
+}
+
+static const MemoryRegionOps pci_reg_ops = {
+ .read = ppc4xx_pci_reg_read4,
+ .write = ppc4xx_pci_reg_write4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ppc4xx_pci_reset(void *opaque)
+{
+ struct PPC4xxPCIState *pci = opaque;
+
+ memset(pci->pmm, 0, sizeof(pci->pmm));
+ memset(pci->ptm, 0, sizeof(pci->ptm));
+}
+
+/* On Bamboo, all pins from each slot are tied to a single board IRQ. This
+ * may need further refactoring for other boards. */
+static int ppc4xx_pci_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ int slot = pci_dev->devfn >> 3;
+
+ DPRINTF("%s: devfn %x irq %d -> %d\n", __func__,
+ pci_dev->devfn, irq_num, slot);
+
+ return slot - 1;
+}
+
+static void ppc4xx_pci_set_irq(void *opaque, int irq_num, int level)
+{
+ qemu_irq *pci_irqs = opaque;
+
+ DPRINTF("%s: PCI irq %d\n", __func__, irq_num);
+ if (irq_num < 0) {
+ fprintf(stderr, "%s: PCI irq %d\n", __func__, irq_num);
+ return;
+ }
+ qemu_set_irq(pci_irqs[irq_num], level);
+}
+
+static const VMStateDescription vmstate_pci_master_map = {
+ .name = "pci_master_map",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(la, struct PCIMasterMap),
+ VMSTATE_UINT32(ma, struct PCIMasterMap),
+ VMSTATE_UINT32(pcila, struct PCIMasterMap),
+ VMSTATE_UINT32(pciha, struct PCIMasterMap),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_target_map = {
+ .name = "pci_target_map",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ms, struct PCITargetMap),
+ VMSTATE_UINT32(la, struct PCITargetMap),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ppc4xx_pci = {
+ .name = "ppc4xx_pci",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(pmm, PPC4xxPCIState, PPC4xx_PCI_NR_PMMS, 1,
+ vmstate_pci_master_map,
+ struct PCIMasterMap),
+ VMSTATE_STRUCT_ARRAY(ptm, PPC4xxPCIState, PPC4xx_PCI_NR_PTMS, 1,
+ vmstate_pci_target_map,
+ struct PCITargetMap),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* XXX Interrupt acknowledge cycles not supported. */
+static int ppc4xx_pcihost_initfn(SysBusDevice *dev)
+{
+ PPC4xxPCIState *s;
+ PCIHostState *h;
+ PCIBus *b;
+ int i;
+
+ h = PCI_HOST_BRIDGE(dev);
+ s = PPC4xx_PCI_HOST_BRIDGE(dev);
+
+ for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
+ sysbus_init_irq(dev, &s->irq[i]);
+ }
+
+ b = pci_register_bus(DEVICE(dev), NULL, ppc4xx_pci_set_irq,
+ ppc4xx_pci_map_irq, s->irq, get_system_memory(),
+ get_system_io(), 0, 4, TYPE_PCI_BUS);
+ h->bus = b;
+
+ pci_create_simple(b, 0, "ppc4xx-host-bridge");
+
+ /* XXX split into 2 memory regions, one for config space, one for regs */
+ memory_region_init(&s->container, OBJECT(s), "pci-container", PCI_ALL_SIZE);
+ memory_region_init_io(&h->conf_mem, OBJECT(s), &pci_host_conf_le_ops, h,
+ "pci-conf-idx", 4);
+ memory_region_init_io(&h->data_mem, OBJECT(s), &pci_host_data_le_ops, h,
+ "pci-conf-data", 4);
+ memory_region_init_io(&s->iomem, OBJECT(s), &pci_reg_ops, s,
+ "pci.reg", PCI_REG_SIZE);
+ memory_region_add_subregion(&s->container, PCIC0_CFGADDR, &h->conf_mem);
+ memory_region_add_subregion(&s->container, PCIC0_CFGDATA, &h->data_mem);
+ memory_region_add_subregion(&s->container, PCI_REG_BASE, &s->iomem);
+ sysbus_init_mmio(dev, &s->container);
+ qemu_register_reset(ppc4xx_pci_reset, s);
+
+ return 0;
+}
+
+static void ppc4xx_host_bridge_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Host bridge";
+ k->vendor_id = PCI_VENDOR_ID_IBM;
+ k->device_id = PCI_DEVICE_ID_IBM_440GX;
+ k->class_id = PCI_CLASS_BRIDGE_OTHER;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo ppc4xx_host_bridge_info = {
+ .name = "ppc4xx-host-bridge",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = ppc4xx_host_bridge_class_init,
+};
+
+static void ppc4xx_pcihost_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->init = ppc4xx_pcihost_initfn;
+ dc->vmsd = &vmstate_ppc4xx_pci;
+}
+
+static const TypeInfo ppc4xx_pcihost_info = {
+ .name = TYPE_PPC4xx_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(PPC4xxPCIState),
+ .class_init = ppc4xx_pcihost_class_init,
+};
+
+static void ppc4xx_pci_register_types(void)
+{
+ type_register_static(&ppc4xx_pcihost_info);
+ type_register_static(&ppc4xx_host_bridge_info);
+}
+
+type_init(ppc4xx_pci_register_types)
diff --git a/hw/ppc/ppc_booke.c b/hw/ppc/ppc_booke.c
new file mode 100644
index 00000000..8b94da6b
--- /dev/null
+++ b/hw/ppc/ppc_booke.c
@@ -0,0 +1,365 @@
+/*
+ * QEMU PowerPC Booke hardware System Emulator
+ *
+ * Copyright (c) 2011 AdaCore
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/timer/m48t59.h"
+#include "qemu/log.h"
+#include "hw/loader.h"
+#include "kvm_ppc.h"
+
+
+/* Timer Control Register */
+
+#define TCR_WP_SHIFT 30 /* Watchdog Timer Period */
+#define TCR_WP_MASK (0x3U << TCR_WP_SHIFT)
+#define TCR_WRC_SHIFT 28 /* Watchdog Timer Reset Control */
+#define TCR_WRC_MASK (0x3U << TCR_WRC_SHIFT)
+#define TCR_WIE (1U << 27) /* Watchdog Timer Interrupt Enable */
+#define TCR_DIE (1U << 26) /* Decrementer Interrupt Enable */
+#define TCR_FP_SHIFT 24 /* Fixed-Interval Timer Period */
+#define TCR_FP_MASK (0x3U << TCR_FP_SHIFT)
+#define TCR_FIE (1U << 23) /* Fixed-Interval Timer Interrupt Enable */
+#define TCR_ARE (1U << 22) /* Auto-Reload Enable */
+
+/* Timer Control Register (e500 specific fields) */
+
+#define TCR_E500_FPEXT_SHIFT 13 /* Fixed-Interval Timer Period Extension */
+#define TCR_E500_FPEXT_MASK (0xf << TCR_E500_FPEXT_SHIFT)
+#define TCR_E500_WPEXT_SHIFT 17 /* Watchdog Timer Period Extension */
+#define TCR_E500_WPEXT_MASK (0xf << TCR_E500_WPEXT_SHIFT)
+
+/* Timer Status Register */
+
+#define TSR_FIS (1U << 26) /* Fixed-Interval Timer Interrupt Status */
+#define TSR_DIS (1U << 27) /* Decrementer Interrupt Status */
+#define TSR_WRS_SHIFT 28 /* Watchdog Timer Reset Status */
+#define TSR_WRS_MASK (0x3U << TSR_WRS_SHIFT)
+#define TSR_WIS (1U << 30) /* Watchdog Timer Interrupt Status */
+#define TSR_ENW (1U << 31) /* Enable Next Watchdog Timer */
+
+typedef struct booke_timer_t booke_timer_t;
+struct booke_timer_t {
+
+ uint64_t fit_next;
+ QEMUTimer *fit_timer;
+
+ uint64_t wdt_next;
+ QEMUTimer *wdt_timer;
+
+ uint32_t flags;
+};
+
+static void booke_update_irq(PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+
+ ppc_set_irq(cpu, PPC_INTERRUPT_DECR,
+ (env->spr[SPR_BOOKE_TSR] & TSR_DIS
+ && env->spr[SPR_BOOKE_TCR] & TCR_DIE));
+
+ ppc_set_irq(cpu, PPC_INTERRUPT_WDT,
+ (env->spr[SPR_BOOKE_TSR] & TSR_WIS
+ && env->spr[SPR_BOOKE_TCR] & TCR_WIE));
+
+ ppc_set_irq(cpu, PPC_INTERRUPT_FIT,
+ (env->spr[SPR_BOOKE_TSR] & TSR_FIS
+ && env->spr[SPR_BOOKE_TCR] & TCR_FIE));
+}
+
+/* Return the location of the bit of time base at which the FIT will raise an
+ interrupt */
+static uint8_t booke_get_fit_target(CPUPPCState *env, ppc_tb_t *tb_env)
+{
+ uint8_t fp = (env->spr[SPR_BOOKE_TCR] & TCR_FP_MASK) >> TCR_FP_SHIFT;
+
+ if (tb_env->flags & PPC_TIMER_E500) {
+ /* e500 Fixed-interval timer period extension */
+ uint32_t fpext = (env->spr[SPR_BOOKE_TCR] & TCR_E500_FPEXT_MASK)
+ >> TCR_E500_FPEXT_SHIFT;
+ fp = 63 - (fp | fpext << 2);
+ } else {
+ fp = env->fit_period[fp];
+ }
+
+ return fp;
+}
+
+/* Return the location of the bit of time base at which the WDT will raise an
+ interrupt */
+static uint8_t booke_get_wdt_target(CPUPPCState *env, ppc_tb_t *tb_env)
+{
+ uint8_t wp = (env->spr[SPR_BOOKE_TCR] & TCR_WP_MASK) >> TCR_WP_SHIFT;
+
+ if (tb_env->flags & PPC_TIMER_E500) {
+ /* e500 Watchdog timer period extension */
+ uint32_t wpext = (env->spr[SPR_BOOKE_TCR] & TCR_E500_WPEXT_MASK)
+ >> TCR_E500_WPEXT_SHIFT;
+ wp = 63 - (wp | wpext << 2);
+ } else {
+ wp = env->wdt_period[wp];
+ }
+
+ return wp;
+}
+
+static void booke_update_fixed_timer(CPUPPCState *env,
+ uint8_t target_bit,
+ uint64_t *next,
+ QEMUTimer *timer,
+ int tsr_bit)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t delta_tick, ticks = 0;
+ uint64_t tb;
+ uint64_t period;
+ uint64_t now;
+
+ if (!(env->spr[SPR_BOOKE_TSR] & tsr_bit)) {
+ /*
+ * Don't arm the timer again when the guest has the current
+ * interrupt still pending. Wait for it to ack it.
+ */
+ return;
+ }
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ tb = cpu_ppc_get_tb(tb_env, now, tb_env->tb_offset);
+ period = 1ULL << target_bit;
+ delta_tick = period - (tb & (period - 1));
+
+ /* the timer triggers only when the selected bit toggles from 0 to 1 */
+ if (tb & period) {
+ ticks = period;
+ }
+
+ if (ticks + delta_tick < ticks) {
+ /* Overflow, so assume the biggest number we can express. */
+ ticks = UINT64_MAX;
+ } else {
+ ticks += delta_tick;
+ }
+
+ *next = now + muldiv64(ticks, get_ticks_per_sec(), tb_env->tb_freq);
+ if ((*next < now) || (*next > INT64_MAX)) {
+ /* Overflow, so assume the biggest number the qemu timer supports. */
+ *next = INT64_MAX;
+ }
+
+ /* XXX: If expire time is now. We can't run the callback because we don't
+ * have access to it. So we just set the timer one nanosecond later.
+ */
+
+ if (*next == now) {
+ (*next)++;
+ } else {
+ /*
+ * There's no point to fake any granularity that's more fine grained
+ * than milliseconds. Anything beyond that just overloads the system.
+ */
+ *next = MAX(*next, now + SCALE_MS);
+ }
+
+ /* Fire the next timer */
+ timer_mod(timer, *next);
+}
+
+static void booke_decr_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ env->spr[SPR_BOOKE_TSR] |= TSR_DIS;
+ booke_update_irq(cpu);
+
+ if (env->spr[SPR_BOOKE_TCR] & TCR_ARE) {
+ /* Auto Reload */
+ cpu_ppc_store_decr(env, env->spr[SPR_BOOKE_DECAR]);
+ }
+}
+
+static void booke_fit_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ ppc_tb_t *tb_env;
+ booke_timer_t *booke_timer;
+
+ tb_env = env->tb_env;
+ booke_timer = tb_env->opaque;
+ env->spr[SPR_BOOKE_TSR] |= TSR_FIS;
+
+ booke_update_irq(cpu);
+
+ booke_update_fixed_timer(env,
+ booke_get_fit_target(env, tb_env),
+ &booke_timer->fit_next,
+ booke_timer->fit_timer,
+ TSR_FIS);
+}
+
+static void booke_wdt_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ ppc_tb_t *tb_env;
+ booke_timer_t *booke_timer;
+
+ tb_env = env->tb_env;
+ booke_timer = tb_env->opaque;
+
+ /* TODO: There's lots of complicated stuff to do here */
+
+ booke_update_irq(cpu);
+
+ booke_update_fixed_timer(env,
+ booke_get_wdt_target(env, tb_env),
+ &booke_timer->wdt_next,
+ booke_timer->wdt_timer,
+ TSR_WIS);
+}
+
+void store_booke_tsr(CPUPPCState *env, target_ulong val)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+ ppc_tb_t *tb_env = env->tb_env;
+ booke_timer_t *booke_timer = tb_env->opaque;
+
+ env->spr[SPR_BOOKE_TSR] &= ~val;
+ kvmppc_clear_tsr_bits(cpu, val);
+
+ if (val & TSR_FIS) {
+ booke_update_fixed_timer(env,
+ booke_get_fit_target(env, tb_env),
+ &booke_timer->fit_next,
+ booke_timer->fit_timer,
+ TSR_FIS);
+ }
+
+ if (val & TSR_WIS) {
+ booke_update_fixed_timer(env,
+ booke_get_wdt_target(env, tb_env),
+ &booke_timer->wdt_next,
+ booke_timer->wdt_timer,
+ TSR_WIS);
+ }
+
+ booke_update_irq(cpu);
+}
+
+void store_booke_tcr(CPUPPCState *env, target_ulong val)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+ ppc_tb_t *tb_env = env->tb_env;
+ booke_timer_t *booke_timer = tb_env->opaque;
+
+ tb_env = env->tb_env;
+ env->spr[SPR_BOOKE_TCR] = val;
+ kvmppc_set_tcr(cpu);
+
+ booke_update_irq(cpu);
+
+ booke_update_fixed_timer(env,
+ booke_get_fit_target(env, tb_env),
+ &booke_timer->fit_next,
+ booke_timer->fit_timer,
+ TSR_FIS);
+
+ booke_update_fixed_timer(env,
+ booke_get_wdt_target(env, tb_env),
+ &booke_timer->wdt_next,
+ booke_timer->wdt_timer,
+ TSR_WIS);
+}
+
+static void ppc_booke_timer_reset_handle(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ store_booke_tcr(env, 0);
+ store_booke_tsr(env, -1);
+}
+
+/*
+ * This function will be called whenever the CPU state changes.
+ * CPU states are defined "typedef enum RunState".
+ * Regarding timer, When CPU state changes to running after debug halt
+ * or similar cases which takes time then in between final watchdog
+ * expiry happenes. This will cause exit to QEMU and configured watchdog
+ * action will be taken. To avoid this we always clear the watchdog state when
+ * state changes to running.
+ */
+static void cpu_state_change_handler(void *opaque, int running, RunState state)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ if (!running) {
+ return;
+ }
+
+ /*
+ * Clear watchdog interrupt condition by clearing TSR.
+ */
+ store_booke_tsr(env, TSR_ENW | TSR_WIS | TSR_WRS_MASK);
+}
+
+void ppc_booke_timers_init(PowerPCCPU *cpu, uint32_t freq, uint32_t flags)
+{
+ ppc_tb_t *tb_env;
+ booke_timer_t *booke_timer;
+ int ret = 0;
+
+ tb_env = g_malloc0(sizeof(ppc_tb_t));
+ booke_timer = g_malloc0(sizeof(booke_timer_t));
+
+ cpu->env.tb_env = tb_env;
+ tb_env->flags = flags | PPC_TIMER_BOOKE | PPC_DECR_ZERO_TRIGGERED;
+
+ tb_env->tb_freq = freq;
+ tb_env->decr_freq = freq;
+ tb_env->opaque = booke_timer;
+ tb_env->decr_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &booke_decr_cb, cpu);
+
+ booke_timer->fit_timer =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, &booke_fit_cb, cpu);
+ booke_timer->wdt_timer =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, &booke_wdt_cb, cpu);
+
+ ret = kvmppc_booke_watchdog_enable(cpu);
+
+ if (ret) {
+ /* TODO: Start the QEMU emulated watchdog if not running on KVM.
+ * Also start the QEMU emulated watchdog if KVM does not support
+ * emulated watchdog or somehow it is not enabled (supported but
+ * not enabled is though some bug and requires debugging :)).
+ */
+ }
+
+ qemu_add_vm_change_state_handler(cpu_state_change_handler, cpu);
+
+ qemu_register_reset(ppc_booke_timer_reset_handle, cpu);
+}
diff --git a/hw/ppc/ppce500_spin.c b/hw/ppc/ppce500_spin.c
new file mode 100644
index 00000000..a99f7b03
--- /dev/null
+++ b/hw/ppc/ppce500_spin.c
@@ -0,0 +1,224 @@
+/*
+ * QEMU PowerPC e500v2 ePAPR spinning code
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is not really a device, but models an interface that usually
+ * firmware takes care of. It's used when QEMU plays the role of firmware.
+ *
+ * Specification:
+ *
+ * https://www.power.org/resources/downloads/Power_ePAPR_APPROVED_v1.1.pdf
+ *
+ */
+
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+
+#define MAX_CPUS 32
+
+typedef struct spin_info {
+ uint64_t addr;
+ uint64_t r3;
+ uint32_t resv;
+ uint32_t pir;
+ uint64_t reserved;
+} QEMU_PACKED SpinInfo;
+
+#define TYPE_E500_SPIN "e500-spin"
+#define E500_SPIN(obj) OBJECT_CHECK(SpinState, (obj), TYPE_E500_SPIN)
+
+typedef struct SpinState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ SpinInfo spin[MAX_CPUS];
+} SpinState;
+
+typedef struct spin_kick {
+ PowerPCCPU *cpu;
+ SpinInfo *spin;
+} SpinKick;
+
+static void spin_reset(void *opaque)
+{
+ SpinState *s = opaque;
+ int i;
+
+ for (i = 0; i < MAX_CPUS; i++) {
+ SpinInfo *info = &s->spin[i];
+
+ stl_p(&info->pir, i);
+ stq_p(&info->r3, i);
+ stq_p(&info->addr, 1);
+ }
+}
+
+/* Create -kernel TLB entries for BookE, linearly spanning 256MB. */
+static inline hwaddr booke206_page_size_to_tlb(uint64_t size)
+{
+ return ctz32(size >> 10) >> 1;
+}
+
+static void mmubooke_create_initial_mapping(CPUPPCState *env,
+ target_ulong va,
+ hwaddr pa,
+ hwaddr len)
+{
+ ppcmas_tlb_t *tlb = booke206_get_tlbm(env, 1, 0, 1);
+ hwaddr size;
+
+ size = (booke206_page_size_to_tlb(len) << MAS1_TSIZE_SHIFT);
+ tlb->mas1 = MAS1_VALID | size;
+ tlb->mas2 = (va & TARGET_PAGE_MASK) | MAS2_M;
+ tlb->mas7_3 = pa & TARGET_PAGE_MASK;
+ tlb->mas7_3 |= MAS3_UR | MAS3_UW | MAS3_UX | MAS3_SR | MAS3_SW | MAS3_SX;
+ env->tlb_dirty = true;
+}
+
+static void spin_kick(void *data)
+{
+ SpinKick *kick = data;
+ CPUState *cpu = CPU(kick->cpu);
+ CPUPPCState *env = &kick->cpu->env;
+ SpinInfo *curspin = kick->spin;
+ hwaddr map_size = 64 * 1024 * 1024;
+ hwaddr map_start;
+
+ cpu_synchronize_state(cpu);
+ stl_p(&curspin->pir, env->spr[SPR_PIR]);
+ env->nip = ldq_p(&curspin->addr) & (map_size - 1);
+ env->gpr[3] = ldq_p(&curspin->r3);
+ env->gpr[4] = 0;
+ env->gpr[5] = 0;
+ env->gpr[6] = 0;
+ env->gpr[7] = map_size;
+ env->gpr[8] = 0;
+ env->gpr[9] = 0;
+
+ map_start = ldq_p(&curspin->addr) & ~(map_size - 1);
+ mmubooke_create_initial_mapping(env, 0, map_start, map_size);
+
+ cpu->halted = 0;
+ cpu->exception_index = -1;
+ cpu->stopped = false;
+ qemu_cpu_kick(cpu);
+}
+
+static void spin_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned len)
+{
+ SpinState *s = opaque;
+ int env_idx = addr / sizeof(SpinInfo);
+ CPUState *cpu;
+ SpinInfo *curspin = &s->spin[env_idx];
+ uint8_t *curspin_p = (uint8_t*)curspin;
+
+ cpu = qemu_get_cpu(env_idx);
+ if (cpu == NULL) {
+ /* Unknown CPU */
+ return;
+ }
+
+ if (cpu->cpu_index == 0) {
+ /* primary CPU doesn't spin */
+ return;
+ }
+
+ curspin_p = &curspin_p[addr % sizeof(SpinInfo)];
+ switch (len) {
+ case 1:
+ stb_p(curspin_p, value);
+ break;
+ case 2:
+ stw_p(curspin_p, value);
+ break;
+ case 4:
+ stl_p(curspin_p, value);
+ break;
+ }
+
+ if (!(ldq_p(&curspin->addr) & 1)) {
+ /* run CPU */
+ SpinKick kick = {
+ .cpu = POWERPC_CPU(cpu),
+ .spin = curspin,
+ };
+
+ run_on_cpu(cpu, spin_kick, &kick);
+ }
+}
+
+static uint64_t spin_read(void *opaque, hwaddr addr, unsigned len)
+{
+ SpinState *s = opaque;
+ uint8_t *spin_p = &((uint8_t*)s->spin)[addr];
+
+ switch (len) {
+ case 1:
+ return ldub_p(spin_p);
+ case 2:
+ return lduw_p(spin_p);
+ case 4:
+ return ldl_p(spin_p);
+ default:
+ hw_error("ppce500: unexpected %s with len = %u", __func__, len);
+ }
+}
+
+static const MemoryRegionOps spin_rw_ops = {
+ .read = spin_read,
+ .write = spin_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static int ppce500_spin_initfn(SysBusDevice *dev)
+{
+ SpinState *s = E500_SPIN(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &spin_rw_ops, s,
+ "e500 spin pv device", sizeof(SpinInfo) * MAX_CPUS);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ qemu_register_reset(spin_reset, s);
+
+ return 0;
+}
+
+static void ppce500_spin_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = ppce500_spin_initfn;
+}
+
+static const TypeInfo ppce500_spin_info = {
+ .name = TYPE_E500_SPIN,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SpinState),
+ .class_init = ppce500_spin_class_init,
+};
+
+static void ppce500_spin_register_types(void)
+{
+ type_register_static(&ppce500_spin_info);
+}
+
+type_init(ppce500_spin_register_types)
diff --git a/hw/ppc/prep.c b/hw/ppc/prep.c
new file mode 100644
index 00000000..45b5f62d
--- /dev/null
+++ b/hw/ppc/prep.c
@@ -0,0 +1,714 @@
+/*
+ * QEMU PPC PREP hardware System Emulator
+ *
+ * Copyright (c) 2003-2007 Jocelyn Mayer
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/timer/m48t59.h"
+#include "hw/i386/pc.h"
+#include "hw/char/serial.h"
+#include "hw/block/fdc.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/isa/isa.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "hw/ppc/ppc.h"
+#include "hw/boards.h"
+#include "qemu/log.h"
+#include "hw/ide.h"
+#include "hw/loader.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/isa/pc87312.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/arch_init.h"
+#include "sysemu/qtest.h"
+#include "exec/address-spaces.h"
+#include "elf.h"
+
+//#define HARD_DEBUG_PPC_IO
+//#define DEBUG_PPC_IO
+
+/* SMP is not enabled, for now */
+#define MAX_CPUS 1
+
+#define MAX_IDE_BUS 2
+
+#define BIOS_SIZE (1024 * 1024)
+#define BIOS_FILENAME "ppc_rom.bin"
+#define KERNEL_LOAD_ADDR 0x01000000
+#define INITRD_LOAD_ADDR 0x01800000
+
+#if defined (HARD_DEBUG_PPC_IO) && !defined (DEBUG_PPC_IO)
+#define DEBUG_PPC_IO
+#endif
+
+#if defined (HARD_DEBUG_PPC_IO)
+#define PPC_IO_DPRINTF(fmt, ...) \
+do { \
+ if (qemu_loglevel_mask(CPU_LOG_IOPORT)) { \
+ qemu_log("%s: " fmt, __func__ , ## __VA_ARGS__); \
+ } else { \
+ printf("%s : " fmt, __func__ , ## __VA_ARGS__); \
+ } \
+} while (0)
+#elif defined (DEBUG_PPC_IO)
+#define PPC_IO_DPRINTF(fmt, ...) \
+qemu_log_mask(CPU_LOG_IOPORT, fmt, ## __VA_ARGS__)
+#else
+#define PPC_IO_DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+/* Constants for devices init */
+static const int ide_iobase[2] = { 0x1f0, 0x170 };
+static const int ide_iobase2[2] = { 0x3f6, 0x376 };
+static const int ide_irq[2] = { 13, 13 };
+
+#define NE2000_NB_MAX 6
+
+static uint32_t ne2000_io[NE2000_NB_MAX] = { 0x300, 0x320, 0x340, 0x360, 0x280, 0x380 };
+static int ne2000_irq[NE2000_NB_MAX] = { 9, 10, 11, 3, 4, 5 };
+
+/* ISA IO ports bridge */
+#define PPC_IO_BASE 0x80000000
+
+/* PowerPC control and status registers */
+#if 0 // Not used
+static struct {
+ /* IDs */
+ uint32_t veni_devi;
+ uint32_t revi;
+ /* Control and status */
+ uint32_t gcsr;
+ uint32_t xcfr;
+ uint32_t ct32;
+ uint32_t mcsr;
+ /* General purpose registers */
+ uint32_t gprg[6];
+ /* Exceptions */
+ uint32_t feen;
+ uint32_t fest;
+ uint32_t fema;
+ uint32_t fecl;
+ uint32_t eeen;
+ uint32_t eest;
+ uint32_t eecl;
+ uint32_t eeint;
+ uint32_t eemck0;
+ uint32_t eemck1;
+ /* Error diagnostic */
+} XCSR;
+
+static void PPC_XCSR_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ printf("%s: 0x" TARGET_FMT_plx " => 0x%08" PRIx32 "\n", __func__, addr,
+ value);
+}
+
+static void PPC_XCSR_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ printf("%s: 0x" TARGET_FMT_plx " => 0x%08" PRIx32 "\n", __func__, addr,
+ value);
+}
+
+static void PPC_XCSR_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ printf("%s: 0x" TARGET_FMT_plx " => 0x%08" PRIx32 "\n", __func__, addr,
+ value);
+}
+
+static uint32_t PPC_XCSR_readb (void *opaque, hwaddr addr)
+{
+ uint32_t retval = 0;
+
+ printf("%s: 0x" TARGET_FMT_plx " <= %08" PRIx32 "\n", __func__, addr,
+ retval);
+
+ return retval;
+}
+
+static uint32_t PPC_XCSR_readw (void *opaque, hwaddr addr)
+{
+ uint32_t retval = 0;
+
+ printf("%s: 0x" TARGET_FMT_plx " <= %08" PRIx32 "\n", __func__, addr,
+ retval);
+
+ return retval;
+}
+
+static uint32_t PPC_XCSR_readl (void *opaque, hwaddr addr)
+{
+ uint32_t retval = 0;
+
+ printf("%s: 0x" TARGET_FMT_plx " <= %08" PRIx32 "\n", __func__, addr,
+ retval);
+
+ return retval;
+}
+
+static const MemoryRegionOps PPC_XCSR_ops = {
+ .old_mmio = {
+ .read = { PPC_XCSR_readb, PPC_XCSR_readw, PPC_XCSR_readl, },
+ .write = { PPC_XCSR_writeb, PPC_XCSR_writew, PPC_XCSR_writel, },
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+#endif
+
+/* Fake super-io ports for PREP platform (Intel 82378ZB) */
+typedef struct sysctrl_t {
+ qemu_irq reset_irq;
+ Nvram *nvram;
+ uint8_t state;
+ uint8_t syscontrol;
+ int contiguous_map;
+ qemu_irq contiguous_map_irq;
+ int endian;
+} sysctrl_t;
+
+enum {
+ STATE_HARDFILE = 0x01,
+};
+
+static sysctrl_t *sysctrl;
+
+static void PREP_io_800_writeb (void *opaque, uint32_t addr, uint32_t val)
+{
+ sysctrl_t *sysctrl = opaque;
+
+ PPC_IO_DPRINTF("0x%08" PRIx32 " => 0x%02" PRIx32 "\n",
+ addr - PPC_IO_BASE, val);
+ switch (addr) {
+ case 0x0092:
+ /* Special port 92 */
+ /* Check soft reset asked */
+ if (val & 0x01) {
+ qemu_irq_raise(sysctrl->reset_irq);
+ } else {
+ qemu_irq_lower(sysctrl->reset_irq);
+ }
+ /* Check LE mode */
+ if (val & 0x02) {
+ sysctrl->endian = 1;
+ } else {
+ sysctrl->endian = 0;
+ }
+ break;
+ case 0x0800:
+ /* Motorola CPU configuration register : read-only */
+ break;
+ case 0x0802:
+ /* Motorola base module feature register : read-only */
+ break;
+ case 0x0803:
+ /* Motorola base module status register : read-only */
+ break;
+ case 0x0808:
+ /* Hardfile light register */
+ if (val & 1)
+ sysctrl->state |= STATE_HARDFILE;
+ else
+ sysctrl->state &= ~STATE_HARDFILE;
+ break;
+ case 0x0810:
+ /* Password protect 1 register */
+ if (sysctrl->nvram != NULL) {
+ NvramClass *k = NVRAM_GET_CLASS(sysctrl->nvram);
+ (k->toggle_lock)(sysctrl->nvram, 1);
+ }
+ break;
+ case 0x0812:
+ /* Password protect 2 register */
+ if (sysctrl->nvram != NULL) {
+ NvramClass *k = NVRAM_GET_CLASS(sysctrl->nvram);
+ (k->toggle_lock)(sysctrl->nvram, 2);
+ }
+ break;
+ case 0x0814:
+ /* L2 invalidate register */
+ // tlb_flush(first_cpu, 1);
+ break;
+ case 0x081C:
+ /* system control register */
+ sysctrl->syscontrol = val & 0x0F;
+ break;
+ case 0x0850:
+ /* I/O map type register */
+ sysctrl->contiguous_map = val & 0x01;
+ qemu_set_irq(sysctrl->contiguous_map_irq, sysctrl->contiguous_map);
+ break;
+ default:
+ printf("ERROR: unaffected IO port write: %04" PRIx32
+ " => %02" PRIx32"\n", addr, val);
+ break;
+ }
+}
+
+static uint32_t PREP_io_800_readb (void *opaque, uint32_t addr)
+{
+ sysctrl_t *sysctrl = opaque;
+ uint32_t retval = 0xFF;
+
+ switch (addr) {
+ case 0x0092:
+ /* Special port 92 */
+ retval = sysctrl->endian << 1;
+ break;
+ case 0x0800:
+ /* Motorola CPU configuration register */
+ retval = 0xEF; /* MPC750 */
+ break;
+ case 0x0802:
+ /* Motorola Base module feature register */
+ retval = 0xAD; /* No ESCC, PMC slot neither ethernet */
+ break;
+ case 0x0803:
+ /* Motorola base module status register */
+ retval = 0xE0; /* Standard MPC750 */
+ break;
+ case 0x080C:
+ /* Equipment present register:
+ * no L2 cache
+ * no upgrade processor
+ * no cards in PCI slots
+ * SCSI fuse is bad
+ */
+ retval = 0x3C;
+ break;
+ case 0x0810:
+ /* Motorola base module extended feature register */
+ retval = 0x39; /* No USB, CF and PCI bridge. NVRAM present */
+ break;
+ case 0x0814:
+ /* L2 invalidate: don't care */
+ break;
+ case 0x0818:
+ /* Keylock */
+ retval = 0x00;
+ break;
+ case 0x081C:
+ /* system control register
+ * 7 - 6 / 1 - 0: L2 cache enable
+ */
+ retval = sysctrl->syscontrol;
+ break;
+ case 0x0823:
+ /* */
+ retval = 0x03; /* no L2 cache */
+ break;
+ case 0x0850:
+ /* I/O map type register */
+ retval = sysctrl->contiguous_map;
+ break;
+ default:
+ printf("ERROR: unaffected IO port: %04" PRIx32 " read\n", addr);
+ break;
+ }
+ PPC_IO_DPRINTF("0x%08" PRIx32 " <= 0x%02" PRIx32 "\n",
+ addr - PPC_IO_BASE, retval);
+
+ return retval;
+}
+
+
+#define NVRAM_SIZE 0x2000
+
+static void cpu_request_exit(void *opaque, int irq, int level)
+{
+ CPUState *cpu = current_cpu;
+
+ if (cpu && level) {
+ cpu_exit(cpu);
+ }
+}
+
+static void ppc_prep_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+static const MemoryRegionPortio prep_portio_list[] = {
+ /* System control ports */
+ { 0x0092, 1, 1, .read = PREP_io_800_readb, .write = PREP_io_800_writeb, },
+ { 0x0800, 0x52, 1,
+ .read = PREP_io_800_readb, .write = PREP_io_800_writeb, },
+ /* Special port to get debug messages from Open-Firmware */
+ { 0x0F00, 4, 1, .write = PPC_debug_write, },
+ PORTIO_END_OF_LIST(),
+};
+
+static PortioList prep_port_list;
+
+/*****************************************************************************/
+/* NVRAM helpers */
+static inline uint32_t nvram_read(Nvram *nvram, uint32_t addr)
+{
+ NvramClass *k = NVRAM_GET_CLASS(sysctrl->nvram);
+ return (k->read)(nvram, addr);
+}
+
+static inline void nvram_write(Nvram *nvram, uint32_t addr, uint32_t val)
+{
+ NvramClass *k = NVRAM_GET_CLASS(sysctrl->nvram);
+ (k->write)(nvram, addr, val);
+}
+
+static void NVRAM_set_byte(Nvram *nvram, uint32_t addr, uint8_t value)
+{
+ nvram_write(nvram, addr, value);
+}
+
+static uint8_t NVRAM_get_byte(Nvram *nvram, uint32_t addr)
+{
+ return nvram_read(nvram, addr);
+}
+
+static void NVRAM_set_word(Nvram *nvram, uint32_t addr, uint16_t value)
+{
+ nvram_write(nvram, addr, value >> 8);
+ nvram_write(nvram, addr + 1, value & 0xFF);
+}
+
+static uint16_t NVRAM_get_word(Nvram *nvram, uint32_t addr)
+{
+ uint16_t tmp;
+
+ tmp = nvram_read(nvram, addr) << 8;
+ tmp |= nvram_read(nvram, addr + 1);
+
+ return tmp;
+}
+
+static void NVRAM_set_lword(Nvram *nvram, uint32_t addr, uint32_t value)
+{
+ nvram_write(nvram, addr, value >> 24);
+ nvram_write(nvram, addr + 1, (value >> 16) & 0xFF);
+ nvram_write(nvram, addr + 2, (value >> 8) & 0xFF);
+ nvram_write(nvram, addr + 3, value & 0xFF);
+}
+
+static void NVRAM_set_string(Nvram *nvram, uint32_t addr, const char *str,
+ uint32_t max)
+{
+ int i;
+
+ for (i = 0; i < max && str[i] != '\0'; i++) {
+ nvram_write(nvram, addr + i, str[i]);
+ }
+ nvram_write(nvram, addr + i, str[i]);
+ nvram_write(nvram, addr + max - 1, '\0');
+}
+
+static uint16_t NVRAM_crc_update (uint16_t prev, uint16_t value)
+{
+ uint16_t tmp;
+ uint16_t pd, pd1, pd2;
+
+ tmp = prev >> 8;
+ pd = prev ^ value;
+ pd1 = pd & 0x000F;
+ pd2 = ((pd >> 4) & 0x000F) ^ pd1;
+ tmp ^= (pd1 << 3) | (pd1 << 8);
+ tmp ^= pd2 | (pd2 << 7) | (pd2 << 12);
+
+ return tmp;
+}
+
+static uint16_t NVRAM_compute_crc (Nvram *nvram, uint32_t start, uint32_t count)
+{
+ uint32_t i;
+ uint16_t crc = 0xFFFF;
+ int odd;
+
+ odd = count & 1;
+ count &= ~1;
+ for (i = 0; i != count; i++) {
+ crc = NVRAM_crc_update(crc, NVRAM_get_word(nvram, start + i));
+ }
+ if (odd) {
+ crc = NVRAM_crc_update(crc, NVRAM_get_byte(nvram, start + i) << 8);
+ }
+
+ return crc;
+}
+
+#define CMDLINE_ADDR 0x017ff000
+
+static int PPC_NVRAM_set_params (Nvram *nvram, uint16_t NVRAM_size,
+ const char *arch,
+ uint32_t RAM_size, int boot_device,
+ uint32_t kernel_image, uint32_t kernel_size,
+ const char *cmdline,
+ uint32_t initrd_image, uint32_t initrd_size,
+ uint32_t NVRAM_image,
+ int width, int height, int depth)
+{
+ uint16_t crc;
+
+ /* Set parameters for Open Hack'Ware BIOS */
+ NVRAM_set_string(nvram, 0x00, "QEMU_BIOS", 16);
+ NVRAM_set_lword(nvram, 0x10, 0x00000002); /* structure v2 */
+ NVRAM_set_word(nvram, 0x14, NVRAM_size);
+ NVRAM_set_string(nvram, 0x20, arch, 16);
+ NVRAM_set_lword(nvram, 0x30, RAM_size);
+ NVRAM_set_byte(nvram, 0x34, boot_device);
+ NVRAM_set_lword(nvram, 0x38, kernel_image);
+ NVRAM_set_lword(nvram, 0x3C, kernel_size);
+ if (cmdline) {
+ /* XXX: put the cmdline in NVRAM too ? */
+ pstrcpy_targphys("cmdline", CMDLINE_ADDR, RAM_size - CMDLINE_ADDR,
+ cmdline);
+ NVRAM_set_lword(nvram, 0x40, CMDLINE_ADDR);
+ NVRAM_set_lword(nvram, 0x44, strlen(cmdline));
+ } else {
+ NVRAM_set_lword(nvram, 0x40, 0);
+ NVRAM_set_lword(nvram, 0x44, 0);
+ }
+ NVRAM_set_lword(nvram, 0x48, initrd_image);
+ NVRAM_set_lword(nvram, 0x4C, initrd_size);
+ NVRAM_set_lword(nvram, 0x50, NVRAM_image);
+
+ NVRAM_set_word(nvram, 0x54, width);
+ NVRAM_set_word(nvram, 0x56, height);
+ NVRAM_set_word(nvram, 0x58, depth);
+ crc = NVRAM_compute_crc(nvram, 0x00, 0xF8);
+ NVRAM_set_word(nvram, 0xFC, crc);
+
+ return 0;
+}
+
+/* PowerPC PREP hardware initialisation */
+static void ppc_prep_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ const char *boot_device = machine->boot_order;
+ MemoryRegion *sysmem = get_system_memory();
+ PowerPCCPU *cpu = NULL;
+ CPUPPCState *env = NULL;
+ Nvram *m48t59;
+#if 0
+ MemoryRegion *xcsr = g_new(MemoryRegion, 1);
+#endif
+ int linux_boot, i, nb_nics1;
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ uint32_t kernel_base, initrd_base;
+ long kernel_size, initrd_size;
+ DeviceState *dev;
+ PCIHostState *pcihost;
+ PCIBus *pci_bus;
+ PCIDevice *pci;
+ ISABus *isa_bus;
+ ISADevice *isa;
+ int ppc_boot_device;
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+
+ sysctrl = g_malloc0(sizeof(sysctrl_t));
+
+ linux_boot = (kernel_filename != NULL);
+
+ /* init CPUs */
+ if (machine->cpu_model == NULL)
+ machine->cpu_model = "602";
+ for (i = 0; i < smp_cpus; i++) {
+ cpu = cpu_ppc_init(machine->cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find PowerPC CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ if (env->flags & POWERPC_FLAG_RTC_CLK) {
+ /* POWER / PowerPC 601 RTC clock frequency is 7.8125 MHz */
+ cpu_ppc_tb_init(env, 7812500UL);
+ } else {
+ /* Set time-base frequency to 100 Mhz */
+ cpu_ppc_tb_init(env, 100UL * 1000UL * 1000UL);
+ }
+ qemu_register_reset(ppc_prep_reset, cpu);
+ }
+
+ /* allocate RAM */
+ memory_region_allocate_system_memory(ram, NULL, "ppc_prep.ram", ram_size);
+ memory_region_add_subregion(sysmem, 0, ram);
+
+ if (linux_boot) {
+ kernel_base = KERNEL_LOAD_ADDR;
+ /* now we can load the kernel */
+ kernel_size = load_image_targphys(kernel_filename, kernel_base,
+ ram_size - kernel_base);
+ if (kernel_size < 0) {
+ hw_error("qemu: could not load kernel '%s'\n", kernel_filename);
+ exit(1);
+ }
+ /* load initrd */
+ if (initrd_filename) {
+ initrd_base = INITRD_LOAD_ADDR;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+ if (initrd_size < 0) {
+ hw_error("qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ }
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+ ppc_boot_device = 'm';
+ } else {
+ kernel_base = 0;
+ kernel_size = 0;
+ initrd_base = 0;
+ initrd_size = 0;
+ ppc_boot_device = '\0';
+ /* For now, OHW cannot boot from the network. */
+ for (i = 0; boot_device[i] != '\0'; i++) {
+ if (boot_device[i] >= 'a' && boot_device[i] <= 'f') {
+ ppc_boot_device = boot_device[i];
+ break;
+ }
+ }
+ if (ppc_boot_device == '\0') {
+ fprintf(stderr, "No valid boot device for Mac99 machine\n");
+ exit(1);
+ }
+ }
+
+ if (PPC_INPUT(env) != PPC_FLAGS_INPUT_6xx) {
+ hw_error("Only 6xx bus is supported on PREP machine\n");
+ }
+
+ dev = qdev_create(NULL, "raven-pcihost");
+ if (bios_name == NULL) {
+ bios_name = BIOS_FILENAME;
+ }
+ qdev_prop_set_string(dev, "bios-name", bios_name);
+ qdev_prop_set_uint32(dev, "elf-machine", ELF_MACHINE);
+ pcihost = PCI_HOST_BRIDGE(dev);
+ object_property_add_child(qdev_get_machine(), "raven", OBJECT(dev), NULL);
+ qdev_init_nofail(dev);
+ pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci.0");
+ if (pci_bus == NULL) {
+ fprintf(stderr, "Couldn't create PCI host controller.\n");
+ exit(1);
+ }
+ sysctrl->contiguous_map_irq = qdev_get_gpio_in(dev, 0);
+
+ /* PCI -> ISA bridge */
+ pci = pci_create_simple(pci_bus, PCI_DEVFN(1, 0), "i82378");
+ cpu = POWERPC_CPU(first_cpu);
+ qdev_connect_gpio_out(&pci->qdev, 0,
+ cpu->env.irq_inputs[PPC6xx_INPUT_INT]);
+ qdev_connect_gpio_out(&pci->qdev, 1,
+ qemu_allocate_irq(cpu_request_exit, NULL, 0));
+ sysbus_connect_irq(&pcihost->busdev, 0, qdev_get_gpio_in(&pci->qdev, 9));
+ sysbus_connect_irq(&pcihost->busdev, 1, qdev_get_gpio_in(&pci->qdev, 11));
+ sysbus_connect_irq(&pcihost->busdev, 2, qdev_get_gpio_in(&pci->qdev, 9));
+ sysbus_connect_irq(&pcihost->busdev, 3, qdev_get_gpio_in(&pci->qdev, 11));
+ isa_bus = ISA_BUS(qdev_get_child_bus(DEVICE(pci), "isa.0"));
+
+ /* Super I/O (parallel + serial ports) */
+ isa = isa_create(isa_bus, TYPE_PC87312);
+ dev = DEVICE(isa);
+ qdev_prop_set_uint8(dev, "config", 13); /* fdc, ser0, ser1, par0 */
+ qdev_init_nofail(dev);
+
+ /* init basic PC hardware */
+ pci_vga_init(pci_bus);
+
+ nb_nics1 = nb_nics;
+ if (nb_nics1 > NE2000_NB_MAX)
+ nb_nics1 = NE2000_NB_MAX;
+ for(i = 0; i < nb_nics1; i++) {
+ if (nd_table[i].model == NULL) {
+ nd_table[i].model = g_strdup("ne2k_isa");
+ }
+ if (strcmp(nd_table[i].model, "ne2k_isa") == 0) {
+ isa_ne2000_init(isa_bus, ne2000_io[i], ne2000_irq[i],
+ &nd_table[i]);
+ } else {
+ pci_nic_init_nofail(&nd_table[i], pci_bus, "ne2k_pci", NULL);
+ }
+ }
+
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+ for(i = 0; i < MAX_IDE_BUS; i++) {
+ isa_ide_init(isa_bus, ide_iobase[i], ide_iobase2[i], ide_irq[i],
+ hd[2 * i],
+ hd[2 * i + 1]);
+ }
+ isa_create_simple(isa_bus, "i8042");
+
+ cpu = POWERPC_CPU(first_cpu);
+ sysctrl->reset_irq = cpu->env.irq_inputs[PPC6xx_INPUT_HRESET];
+
+ portio_list_init(&prep_port_list, NULL, prep_portio_list, sysctrl, "prep");
+ portio_list_add(&prep_port_list, isa_address_space_io(isa), 0x0);
+
+ /* PowerPC control and status register group */
+#if 0
+ memory_region_init_io(xcsr, NULL, &PPC_XCSR_ops, NULL, "ppc-xcsr", 0x1000);
+ memory_region_add_subregion(sysmem, 0xFEFF0000, xcsr);
+#endif
+
+ if (usb_enabled()) {
+ pci_create_simple(pci_bus, -1, "pci-ohci");
+ }
+
+ m48t59 = m48t59_init_isa(isa_bus, 0x0074, NVRAM_SIZE, 2000, 59);
+ if (m48t59 == NULL)
+ return;
+ sysctrl->nvram = m48t59;
+
+ /* Initialise NVRAM */
+ PPC_NVRAM_set_params(m48t59, NVRAM_SIZE, "PREP", ram_size,
+ ppc_boot_device,
+ kernel_base, kernel_size,
+ kernel_cmdline,
+ initrd_base, initrd_size,
+ /* XXX: need an option to load a NVRAM image */
+ 0,
+ graphic_width, graphic_height, graphic_depth);
+}
+
+static QEMUMachine prep_machine = {
+ .name = "prep",
+ .desc = "PowerPC PREP platform",
+ .init = ppc_prep_init,
+ .max_cpus = MAX_CPUS,
+ .default_boot_order = "cad",
+};
+
+static void prep_machine_init(void)
+{
+ qemu_register_machine(&prep_machine);
+}
+
+machine_init(prep_machine_init);
diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c
new file mode 100644
index 00000000..a6f19473
--- /dev/null
+++ b/hw/ppc/spapr.c
@@ -0,0 +1,1998 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2010 David Gibson, IBM Corporation.
+ *
+ * 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.
+ *
+ */
+#include "sysemu/sysemu.h"
+#include "sysemu/numa.h"
+#include "hw/hw.h"
+#include "hw/fw-path-provider.h"
+#include "elf.h"
+#include "net/net.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/cpus.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+#include "migration/migration.h"
+#include "mmu-hash64.h"
+#include "qom/cpu.h"
+
+#include "hw/boards.h"
+#include "hw/ppc/ppc.h"
+#include "hw/loader.h"
+
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "hw/pci-host/spapr.h"
+#include "hw/ppc/xics.h"
+#include "hw/pci/msi.h"
+
+#include "hw/pci/pci.h"
+#include "hw/scsi/scsi.h"
+#include "hw/virtio/virtio-scsi.h"
+
+#include "exec/address-spaces.h"
+#include "hw/usb.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+#include "trace.h"
+#include "hw/nmi.h"
+
+#include "hw/compat.h"
+
+#include <libfdt.h>
+
+/* SLOF memory layout:
+ *
+ * SLOF raw image loaded at 0, copies its romfs right below the flat
+ * device-tree, then position SLOF itself 31M below that
+ *
+ * So we set FW_OVERHEAD to 40MB which should account for all of that
+ * and more
+ *
+ * We load our kernel at 4M, leaving space for SLOF initial image
+ */
+#define FDT_MAX_SIZE 0x40000
+#define RTAS_MAX_SIZE 0x10000
+#define RTAS_MAX_ADDR 0x80000000 /* RTAS must stay below that */
+#define FW_MAX_SIZE 0x400000
+#define FW_FILE_NAME "slof.bin"
+#define FW_OVERHEAD 0x2800000
+#define KERNEL_LOAD_ADDR FW_MAX_SIZE
+
+#define MIN_RMA_SLOF 128UL
+
+#define TIMEBASE_FREQ 512000000ULL
+
+#define MAX_CPUS 255
+
+#define PHANDLE_XICP 0x00001111
+
+#define HTAB_SIZE(spapr) (1ULL << ((spapr)->htab_shift))
+
+static XICSState *try_create_xics(const char *type, int nr_servers,
+ int nr_irqs, Error **errp)
+{
+ Error *err = NULL;
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, type);
+ qdev_prop_set_uint32(dev, "nr_servers", nr_servers);
+ qdev_prop_set_uint32(dev, "nr_irqs", nr_irqs);
+ object_property_set_bool(OBJECT(dev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+ return XICS_COMMON(dev);
+}
+
+static XICSState *xics_system_init(MachineState *machine,
+ int nr_servers, int nr_irqs)
+{
+ XICSState *icp = NULL;
+
+ if (kvm_enabled()) {
+ Error *err = NULL;
+
+ if (machine_kernel_irqchip_allowed(machine)) {
+ icp = try_create_xics(TYPE_KVM_XICS, nr_servers, nr_irqs, &err);
+ }
+ if (machine_kernel_irqchip_required(machine) && !icp) {
+ error_report("kernel_irqchip requested but unavailable: %s",
+ error_get_pretty(err));
+ }
+ }
+
+ if (!icp) {
+ icp = try_create_xics(TYPE_XICS, nr_servers, nr_irqs, &error_abort);
+ }
+
+ return icp;
+}
+
+static int spapr_fixup_cpu_smt_dt(void *fdt, int offset, PowerPCCPU *cpu,
+ int smt_threads)
+{
+ int i, ret = 0;
+ uint32_t servers_prop[smt_threads];
+ uint32_t gservers_prop[smt_threads * 2];
+ int index = ppc_get_vcpu_dt_id(cpu);
+
+ if (cpu->cpu_version) {
+ ret = fdt_setprop_cell(fdt, offset, "cpu-version", cpu->cpu_version);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ /* Build interrupt servers and gservers properties */
+ for (i = 0; i < smt_threads; i++) {
+ servers_prop[i] = cpu_to_be32(index + i);
+ /* Hack, direct the group queues back to cpu 0 */
+ gservers_prop[i*2] = cpu_to_be32(index + i);
+ gservers_prop[i*2 + 1] = 0;
+ }
+ ret = fdt_setprop(fdt, offset, "ibm,ppc-interrupt-server#s",
+ servers_prop, sizeof(servers_prop));
+ if (ret < 0) {
+ return ret;
+ }
+ ret = fdt_setprop(fdt, offset, "ibm,ppc-interrupt-gserver#s",
+ gservers_prop, sizeof(gservers_prop));
+
+ return ret;
+}
+
+static int spapr_fixup_cpu_numa_dt(void *fdt, int offset, CPUState *cs)
+{
+ int ret = 0;
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+ int index = ppc_get_vcpu_dt_id(cpu);
+ uint32_t associativity[] = {cpu_to_be32(0x5),
+ cpu_to_be32(0x0),
+ cpu_to_be32(0x0),
+ cpu_to_be32(0x0),
+ cpu_to_be32(cs->numa_node),
+ cpu_to_be32(index)};
+
+ /* Advertise NUMA via ibm,associativity */
+ if (nb_numa_nodes > 1) {
+ ret = fdt_setprop(fdt, offset, "ibm,associativity", associativity,
+ sizeof(associativity));
+ }
+
+ return ret;
+}
+
+static int spapr_fixup_cpu_dt(void *fdt, sPAPRMachineState *spapr)
+{
+ int ret = 0, offset, cpus_offset;
+ CPUState *cs;
+ char cpu_model[32];
+ int smt = kvmppc_smt_threads();
+ uint32_t pft_size_prop[] = {0, cpu_to_be32(spapr->htab_shift)};
+
+ CPU_FOREACH(cs) {
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+ DeviceClass *dc = DEVICE_GET_CLASS(cs);
+ int index = ppc_get_vcpu_dt_id(cpu);
+
+ if ((index % smt) != 0) {
+ continue;
+ }
+
+ snprintf(cpu_model, 32, "%s@%x", dc->fw_name, index);
+
+ cpus_offset = fdt_path_offset(fdt, "/cpus");
+ if (cpus_offset < 0) {
+ cpus_offset = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"),
+ "cpus");
+ if (cpus_offset < 0) {
+ return cpus_offset;
+ }
+ }
+ offset = fdt_subnode_offset(fdt, cpus_offset, cpu_model);
+ if (offset < 0) {
+ offset = fdt_add_subnode(fdt, cpus_offset, cpu_model);
+ if (offset < 0) {
+ return offset;
+ }
+ }
+
+ ret = fdt_setprop(fdt, offset, "ibm,pft-size",
+ pft_size_prop, sizeof(pft_size_prop));
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = spapr_fixup_cpu_numa_dt(fdt, offset, cs);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = spapr_fixup_cpu_smt_dt(fdt, offset, cpu,
+ ppc_get_compat_smt_threads(cpu));
+ if (ret < 0) {
+ return ret;
+ }
+ }
+ return ret;
+}
+
+
+static size_t create_page_sizes_prop(CPUPPCState *env, uint32_t *prop,
+ size_t maxsize)
+{
+ size_t maxcells = maxsize / sizeof(uint32_t);
+ int i, j, count;
+ uint32_t *p = prop;
+
+ for (i = 0; i < PPC_PAGE_SIZES_MAX_SZ; i++) {
+ struct ppc_one_seg_page_size *sps = &env->sps.sps[i];
+
+ if (!sps->page_shift) {
+ break;
+ }
+ for (count = 0; count < PPC_PAGE_SIZES_MAX_SZ; count++) {
+ if (sps->enc[count].page_shift == 0) {
+ break;
+ }
+ }
+ if ((p - prop) >= (maxcells - 3 - count * 2)) {
+ break;
+ }
+ *(p++) = cpu_to_be32(sps->page_shift);
+ *(p++) = cpu_to_be32(sps->slb_enc);
+ *(p++) = cpu_to_be32(count);
+ for (j = 0; j < count; j++) {
+ *(p++) = cpu_to_be32(sps->enc[j].page_shift);
+ *(p++) = cpu_to_be32(sps->enc[j].pte_enc);
+ }
+ }
+
+ return (p - prop) * sizeof(uint32_t);
+}
+
+static hwaddr spapr_node0_size(void)
+{
+ MachineState *machine = MACHINE(qdev_get_machine());
+
+ if (nb_numa_nodes) {
+ int i;
+ for (i = 0; i < nb_numa_nodes; ++i) {
+ if (numa_info[i].node_mem) {
+ return MIN(pow2floor(numa_info[i].node_mem),
+ machine->ram_size);
+ }
+ }
+ }
+ return machine->ram_size;
+}
+
+#define _FDT(exp) \
+ do { \
+ int ret = (exp); \
+ if (ret < 0) { \
+ fprintf(stderr, "qemu: error creating device tree: %s: %s\n", \
+ #exp, fdt_strerror(ret)); \
+ exit(1); \
+ } \
+ } while (0)
+
+static void add_str(GString *s, const gchar *s1)
+{
+ g_string_append_len(s, s1, strlen(s1) + 1);
+}
+
+static void *spapr_create_fdt_skel(hwaddr initrd_base,
+ hwaddr initrd_size,
+ hwaddr kernel_size,
+ bool little_endian,
+ const char *kernel_cmdline,
+ uint32_t epow_irq)
+{
+ void *fdt;
+ uint32_t start_prop = cpu_to_be32(initrd_base);
+ uint32_t end_prop = cpu_to_be32(initrd_base + initrd_size);
+ GString *hypertas = g_string_sized_new(256);
+ GString *qemu_hypertas = g_string_sized_new(256);
+ uint32_t refpoints[] = {cpu_to_be32(0x4), cpu_to_be32(0x4)};
+ uint32_t interrupt_server_ranges_prop[] = {0, cpu_to_be32(max_cpus)};
+ unsigned char vec5[] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x80};
+ char *buf;
+
+ add_str(hypertas, "hcall-pft");
+ add_str(hypertas, "hcall-term");
+ add_str(hypertas, "hcall-dabr");
+ add_str(hypertas, "hcall-interrupt");
+ add_str(hypertas, "hcall-tce");
+ add_str(hypertas, "hcall-vio");
+ add_str(hypertas, "hcall-splpar");
+ add_str(hypertas, "hcall-bulk");
+ add_str(hypertas, "hcall-set-mode");
+ add_str(qemu_hypertas, "hcall-memop1");
+
+ fdt = g_malloc0(FDT_MAX_SIZE);
+ _FDT((fdt_create(fdt, FDT_MAX_SIZE)));
+
+ if (kernel_size) {
+ _FDT((fdt_add_reservemap_entry(fdt, KERNEL_LOAD_ADDR, kernel_size)));
+ }
+ if (initrd_size) {
+ _FDT((fdt_add_reservemap_entry(fdt, initrd_base, initrd_size)));
+ }
+ _FDT((fdt_finish_reservemap(fdt)));
+
+ /* Root node */
+ _FDT((fdt_begin_node(fdt, "")));
+ _FDT((fdt_property_string(fdt, "device_type", "chrp")));
+ _FDT((fdt_property_string(fdt, "model", "IBM pSeries (emulated by qemu)")));
+ _FDT((fdt_property_string(fdt, "compatible", "qemu,pseries")));
+
+ /*
+ * Add info to guest to indentify which host is it being run on
+ * and what is the uuid of the guest
+ */
+ if (kvmppc_get_host_model(&buf)) {
+ _FDT((fdt_property_string(fdt, "host-model", buf)));
+ g_free(buf);
+ }
+ if (kvmppc_get_host_serial(&buf)) {
+ _FDT((fdt_property_string(fdt, "host-serial", buf)));
+ g_free(buf);
+ }
+
+ buf = g_strdup_printf(UUID_FMT, qemu_uuid[0], qemu_uuid[1],
+ qemu_uuid[2], qemu_uuid[3], qemu_uuid[4],
+ qemu_uuid[5], qemu_uuid[6], qemu_uuid[7],
+ qemu_uuid[8], qemu_uuid[9], qemu_uuid[10],
+ qemu_uuid[11], qemu_uuid[12], qemu_uuid[13],
+ qemu_uuid[14], qemu_uuid[15]);
+
+ _FDT((fdt_property_string(fdt, "vm,uuid", buf)));
+ g_free(buf);
+
+ _FDT((fdt_property_cell(fdt, "#address-cells", 0x2)));
+ _FDT((fdt_property_cell(fdt, "#size-cells", 0x2)));
+
+ /* /chosen */
+ _FDT((fdt_begin_node(fdt, "chosen")));
+
+ /* Set Form1_affinity */
+ _FDT((fdt_property(fdt, "ibm,architecture-vec-5", vec5, sizeof(vec5))));
+
+ _FDT((fdt_property_string(fdt, "bootargs", kernel_cmdline)));
+ _FDT((fdt_property(fdt, "linux,initrd-start",
+ &start_prop, sizeof(start_prop))));
+ _FDT((fdt_property(fdt, "linux,initrd-end",
+ &end_prop, sizeof(end_prop))));
+ if (kernel_size) {
+ uint64_t kprop[2] = { cpu_to_be64(KERNEL_LOAD_ADDR),
+ cpu_to_be64(kernel_size) };
+
+ _FDT((fdt_property(fdt, "qemu,boot-kernel", &kprop, sizeof(kprop))));
+ if (little_endian) {
+ _FDT((fdt_property(fdt, "qemu,boot-kernel-le", NULL, 0)));
+ }
+ }
+ if (boot_menu) {
+ _FDT((fdt_property_cell(fdt, "qemu,boot-menu", boot_menu)));
+ }
+ _FDT((fdt_property_cell(fdt, "qemu,graphic-width", graphic_width)));
+ _FDT((fdt_property_cell(fdt, "qemu,graphic-height", graphic_height)));
+ _FDT((fdt_property_cell(fdt, "qemu,graphic-depth", graphic_depth)));
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* RTAS */
+ _FDT((fdt_begin_node(fdt, "rtas")));
+
+ if (!kvm_enabled() || kvmppc_spapr_use_multitce()) {
+ add_str(hypertas, "hcall-multi-tce");
+ }
+ _FDT((fdt_property(fdt, "ibm,hypertas-functions", hypertas->str,
+ hypertas->len)));
+ g_string_free(hypertas, TRUE);
+ _FDT((fdt_property(fdt, "qemu,hypertas-functions", qemu_hypertas->str,
+ qemu_hypertas->len)));
+ g_string_free(qemu_hypertas, TRUE);
+
+ _FDT((fdt_property(fdt, "ibm,associativity-reference-points",
+ refpoints, sizeof(refpoints))));
+
+ _FDT((fdt_property_cell(fdt, "rtas-error-log-max", RTAS_ERROR_LOG_MAX)));
+ _FDT((fdt_property_cell(fdt, "rtas-event-scan-rate",
+ RTAS_EVENT_SCAN_RATE)));
+
+ /*
+ * According to PAPR, rtas ibm,os-term does not guarantee a return
+ * back to the guest cpu.
+ *
+ * While an additional ibm,extended-os-term property indicates that
+ * rtas call return will always occur. Set this property.
+ */
+ _FDT((fdt_property(fdt, "ibm,extended-os-term", NULL, 0)));
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* interrupt controller */
+ _FDT((fdt_begin_node(fdt, "interrupt-controller")));
+
+ _FDT((fdt_property_string(fdt, "device_type",
+ "PowerPC-External-Interrupt-Presentation")));
+ _FDT((fdt_property_string(fdt, "compatible", "IBM,ppc-xicp")));
+ _FDT((fdt_property(fdt, "interrupt-controller", NULL, 0)));
+ _FDT((fdt_property(fdt, "ibm,interrupt-server-ranges",
+ interrupt_server_ranges_prop,
+ sizeof(interrupt_server_ranges_prop))));
+ _FDT((fdt_property_cell(fdt, "#interrupt-cells", 2)));
+ _FDT((fdt_property_cell(fdt, "linux,phandle", PHANDLE_XICP)));
+ _FDT((fdt_property_cell(fdt, "phandle", PHANDLE_XICP)));
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* vdevice */
+ _FDT((fdt_begin_node(fdt, "vdevice")));
+
+ _FDT((fdt_property_string(fdt, "device_type", "vdevice")));
+ _FDT((fdt_property_string(fdt, "compatible", "IBM,vdevice")));
+ _FDT((fdt_property_cell(fdt, "#address-cells", 0x1)));
+ _FDT((fdt_property_cell(fdt, "#size-cells", 0x0)));
+ _FDT((fdt_property_cell(fdt, "#interrupt-cells", 0x2)));
+ _FDT((fdt_property(fdt, "interrupt-controller", NULL, 0)));
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* event-sources */
+ spapr_events_fdt_skel(fdt, epow_irq);
+
+ /* /hypervisor node */
+ if (kvm_enabled()) {
+ uint8_t hypercall[16];
+
+ /* indicate KVM hypercall interface */
+ _FDT((fdt_begin_node(fdt, "hypervisor")));
+ _FDT((fdt_property_string(fdt, "compatible", "linux,kvm")));
+ if (kvmppc_has_cap_fixup_hcalls()) {
+ /*
+ * Older KVM versions with older guest kernels were broken with the
+ * magic page, don't allow the guest to map it.
+ */
+ kvmppc_get_hypercall(first_cpu->env_ptr, hypercall,
+ sizeof(hypercall));
+ _FDT((fdt_property(fdt, "hcall-instructions", hypercall,
+ sizeof(hypercall))));
+ }
+ _FDT((fdt_end_node(fdt)));
+ }
+
+ _FDT((fdt_end_node(fdt))); /* close root node */
+ _FDT((fdt_finish(fdt)));
+
+ return fdt;
+}
+
+int spapr_h_cas_compose_response(sPAPRMachineState *spapr,
+ target_ulong addr, target_ulong size)
+{
+ void *fdt, *fdt_skel;
+ sPAPRDeviceTreeUpdateHeader hdr = { .version_id = 1 };
+
+ size -= sizeof(hdr);
+
+ /* Create sceleton */
+ fdt_skel = g_malloc0(size);
+ _FDT((fdt_create(fdt_skel, size)));
+ _FDT((fdt_begin_node(fdt_skel, "")));
+ _FDT((fdt_end_node(fdt_skel)));
+ _FDT((fdt_finish(fdt_skel)));
+ fdt = g_malloc0(size);
+ _FDT((fdt_open_into(fdt_skel, fdt, size)));
+ g_free(fdt_skel);
+
+ /* Fix skeleton up */
+ _FDT((spapr_fixup_cpu_dt(fdt, spapr)));
+
+ /* Pack resulting tree */
+ _FDT((fdt_pack(fdt)));
+
+ if (fdt_totalsize(fdt) + sizeof(hdr) > size) {
+ trace_spapr_cas_failed(size);
+ return -1;
+ }
+
+ cpu_physical_memory_write(addr, &hdr, sizeof(hdr));
+ cpu_physical_memory_write(addr + sizeof(hdr), fdt, fdt_totalsize(fdt));
+ trace_spapr_cas_continue(fdt_totalsize(fdt) + sizeof(hdr));
+ g_free(fdt);
+
+ return 0;
+}
+
+static void spapr_populate_memory_node(void *fdt, int nodeid, hwaddr start,
+ hwaddr size)
+{
+ uint32_t associativity[] = {
+ cpu_to_be32(0x4), /* length */
+ cpu_to_be32(0x0), cpu_to_be32(0x0),
+ cpu_to_be32(0x0), cpu_to_be32(nodeid)
+ };
+ char mem_name[32];
+ uint64_t mem_reg_property[2];
+ int off;
+
+ mem_reg_property[0] = cpu_to_be64(start);
+ mem_reg_property[1] = cpu_to_be64(size);
+
+ sprintf(mem_name, "memory@" TARGET_FMT_lx, start);
+ off = fdt_add_subnode(fdt, 0, mem_name);
+ _FDT(off);
+ _FDT((fdt_setprop_string(fdt, off, "device_type", "memory")));
+ _FDT((fdt_setprop(fdt, off, "reg", mem_reg_property,
+ sizeof(mem_reg_property))));
+ _FDT((fdt_setprop(fdt, off, "ibm,associativity", associativity,
+ sizeof(associativity))));
+}
+
+static int spapr_populate_memory(sPAPRMachineState *spapr, void *fdt)
+{
+ MachineState *machine = MACHINE(spapr);
+ hwaddr mem_start, node_size;
+ int i, nb_nodes = nb_numa_nodes;
+ NodeInfo *nodes = numa_info;
+ NodeInfo ramnode;
+
+ /* No NUMA nodes, assume there is just one node with whole RAM */
+ if (!nb_numa_nodes) {
+ nb_nodes = 1;
+ ramnode.node_mem = machine->ram_size;
+ nodes = &ramnode;
+ }
+
+ for (i = 0, mem_start = 0; i < nb_nodes; ++i) {
+ if (!nodes[i].node_mem) {
+ continue;
+ }
+ if (mem_start >= machine->ram_size) {
+ node_size = 0;
+ } else {
+ node_size = nodes[i].node_mem;
+ if (node_size > machine->ram_size - mem_start) {
+ node_size = machine->ram_size - mem_start;
+ }
+ }
+ if (!mem_start) {
+ /* ppc_spapr_init() checks for rma_size <= node0_size already */
+ spapr_populate_memory_node(fdt, i, 0, spapr->rma_size);
+ mem_start += spapr->rma_size;
+ node_size -= spapr->rma_size;
+ }
+ for ( ; node_size; ) {
+ hwaddr sizetmp = pow2floor(node_size);
+
+ /* mem_start != 0 here */
+ if (ctzl(mem_start) < ctzl(sizetmp)) {
+ sizetmp = 1ULL << ctzl(mem_start);
+ }
+
+ spapr_populate_memory_node(fdt, i, mem_start, sizetmp);
+ node_size -= sizetmp;
+ mem_start += sizetmp;
+ }
+ }
+
+ return 0;
+}
+
+static void spapr_populate_cpu_dt(CPUState *cs, void *fdt, int offset,
+ sPAPRMachineState *spapr)
+{
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+ CPUPPCState *env = &cpu->env;
+ PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cs);
+ int index = ppc_get_vcpu_dt_id(cpu);
+ uint32_t segs[] = {cpu_to_be32(28), cpu_to_be32(40),
+ 0xffffffff, 0xffffffff};
+ uint32_t tbfreq = kvm_enabled() ? kvmppc_get_tbfreq() : TIMEBASE_FREQ;
+ uint32_t cpufreq = kvm_enabled() ? kvmppc_get_clockfreq() : 1000000000;
+ uint32_t page_sizes_prop[64];
+ size_t page_sizes_prop_size;
+ QemuOpts *opts = qemu_opts_find(qemu_find_opts("smp-opts"), NULL);
+ unsigned sockets = opts ? qemu_opt_get_number(opts, "sockets", 0) : 0;
+ uint32_t cpus_per_socket = sockets ? (smp_cpus / sockets) : 1;
+ uint32_t pft_size_prop[] = {0, cpu_to_be32(spapr->htab_shift)};
+
+ _FDT((fdt_setprop_cell(fdt, offset, "reg", index)));
+ _FDT((fdt_setprop_string(fdt, offset, "device_type", "cpu")));
+
+ _FDT((fdt_setprop_cell(fdt, offset, "cpu-version", env->spr[SPR_PVR])));
+ _FDT((fdt_setprop_cell(fdt, offset, "d-cache-block-size",
+ env->dcache_line_size)));
+ _FDT((fdt_setprop_cell(fdt, offset, "d-cache-line-size",
+ env->dcache_line_size)));
+ _FDT((fdt_setprop_cell(fdt, offset, "i-cache-block-size",
+ env->icache_line_size)));
+ _FDT((fdt_setprop_cell(fdt, offset, "i-cache-line-size",
+ env->icache_line_size)));
+
+ if (pcc->l1_dcache_size) {
+ _FDT((fdt_setprop_cell(fdt, offset, "d-cache-size",
+ pcc->l1_dcache_size)));
+ } else {
+ fprintf(stderr, "Warning: Unknown L1 dcache size for cpu\n");
+ }
+ if (pcc->l1_icache_size) {
+ _FDT((fdt_setprop_cell(fdt, offset, "i-cache-size",
+ pcc->l1_icache_size)));
+ } else {
+ fprintf(stderr, "Warning: Unknown L1 icache size for cpu\n");
+ }
+
+ _FDT((fdt_setprop_cell(fdt, offset, "timebase-frequency", tbfreq)));
+ _FDT((fdt_setprop_cell(fdt, offset, "clock-frequency", cpufreq)));
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,slb-size", env->slb_nr)));
+ _FDT((fdt_setprop_string(fdt, offset, "status", "okay")));
+ _FDT((fdt_setprop(fdt, offset, "64-bit", NULL, 0)));
+
+ if (env->spr_cb[SPR_PURR].oea_read) {
+ _FDT((fdt_setprop(fdt, offset, "ibm,purr", NULL, 0)));
+ }
+
+ if (env->mmu_model & POWERPC_MMU_1TSEG) {
+ _FDT((fdt_setprop(fdt, offset, "ibm,processor-segment-sizes",
+ segs, sizeof(segs))));
+ }
+
+ /* Advertise VMX/VSX (vector extensions) if available
+ * 0 / no property == no vector extensions
+ * 1 == VMX / Altivec available
+ * 2 == VSX available */
+ if (env->insns_flags & PPC_ALTIVEC) {
+ uint32_t vmx = (env->insns_flags2 & PPC2_VSX) ? 2 : 1;
+
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,vmx", vmx)));
+ }
+
+ /* Advertise DFP (Decimal Floating Point) if available
+ * 0 / no property == no DFP
+ * 1 == DFP available */
+ if (env->insns_flags2 & PPC2_DFP) {
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,dfp", 1)));
+ }
+
+ page_sizes_prop_size = create_page_sizes_prop(env, page_sizes_prop,
+ sizeof(page_sizes_prop));
+ if (page_sizes_prop_size) {
+ _FDT((fdt_setprop(fdt, offset, "ibm,segment-page-sizes",
+ page_sizes_prop, page_sizes_prop_size)));
+ }
+
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,chip-id",
+ cs->cpu_index / cpus_per_socket)));
+
+ _FDT((fdt_setprop(fdt, offset, "ibm,pft-size",
+ pft_size_prop, sizeof(pft_size_prop))));
+
+ _FDT(spapr_fixup_cpu_numa_dt(fdt, offset, cs));
+
+ _FDT(spapr_fixup_cpu_smt_dt(fdt, offset, cpu,
+ ppc_get_compat_smt_threads(cpu)));
+}
+
+static void spapr_populate_cpus_dt_node(void *fdt, sPAPRMachineState *spapr)
+{
+ CPUState *cs;
+ int cpus_offset;
+ char *nodename;
+ int smt = kvmppc_smt_threads();
+
+ cpus_offset = fdt_add_subnode(fdt, 0, "cpus");
+ _FDT(cpus_offset);
+ _FDT((fdt_setprop_cell(fdt, cpus_offset, "#address-cells", 0x1)));
+ _FDT((fdt_setprop_cell(fdt, cpus_offset, "#size-cells", 0x0)));
+
+ /*
+ * We walk the CPUs in reverse order to ensure that CPU DT nodes
+ * created by fdt_add_subnode() end up in the right order in FDT
+ * for the guest kernel the enumerate the CPUs correctly.
+ */
+ CPU_FOREACH_REVERSE(cs) {
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+ int index = ppc_get_vcpu_dt_id(cpu);
+ DeviceClass *dc = DEVICE_GET_CLASS(cs);
+ int offset;
+
+ if ((index % smt) != 0) {
+ continue;
+ }
+
+ nodename = g_strdup_printf("%s@%x", dc->fw_name, index);
+ offset = fdt_add_subnode(fdt, cpus_offset, nodename);
+ g_free(nodename);
+ _FDT(offset);
+ spapr_populate_cpu_dt(cs, fdt, offset, spapr);
+ }
+
+}
+
+static void spapr_finalize_fdt(sPAPRMachineState *spapr,
+ hwaddr fdt_addr,
+ hwaddr rtas_addr,
+ hwaddr rtas_size)
+{
+ MachineState *machine = MACHINE(qdev_get_machine());
+ const char *boot_device = machine->boot_order;
+ int ret, i;
+ size_t cb = 0;
+ char *bootlist;
+ void *fdt;
+ sPAPRPHBState *phb;
+
+ fdt = g_malloc(FDT_MAX_SIZE);
+
+ /* open out the base tree into a temp buffer for the final tweaks */
+ _FDT((fdt_open_into(spapr->fdt_skel, fdt, FDT_MAX_SIZE)));
+
+ ret = spapr_populate_memory(spapr, fdt);
+ if (ret < 0) {
+ fprintf(stderr, "couldn't setup memory nodes in fdt\n");
+ exit(1);
+ }
+
+ ret = spapr_populate_vdevice(spapr->vio_bus, fdt);
+ if (ret < 0) {
+ fprintf(stderr, "couldn't setup vio devices in fdt\n");
+ exit(1);
+ }
+
+ QLIST_FOREACH(phb, &spapr->phbs, list) {
+ ret = spapr_populate_pci_dt(phb, PHANDLE_XICP, fdt);
+ }
+
+ if (ret < 0) {
+ fprintf(stderr, "couldn't setup PCI devices in fdt\n");
+ exit(1);
+ }
+
+ /* RTAS */
+ ret = spapr_rtas_device_tree_setup(fdt, rtas_addr, rtas_size);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't set up RTAS device tree properties\n");
+ }
+
+ /* cpus */
+ spapr_populate_cpus_dt_node(fdt, spapr);
+
+ bootlist = get_boot_devices_list(&cb, true);
+ if (cb && bootlist) {
+ int offset = fdt_path_offset(fdt, "/chosen");
+ if (offset < 0) {
+ exit(1);
+ }
+ for (i = 0; i < cb; i++) {
+ if (bootlist[i] == '\n') {
+ bootlist[i] = ' ';
+ }
+
+ }
+ ret = fdt_setprop_string(fdt, offset, "qemu,boot-list", bootlist);
+ }
+
+ if (boot_device && strlen(boot_device)) {
+ int offset = fdt_path_offset(fdt, "/chosen");
+
+ if (offset < 0) {
+ exit(1);
+ }
+ fdt_setprop_string(fdt, offset, "qemu,boot-device", boot_device);
+ }
+
+ if (!spapr->has_graphics) {
+ spapr_populate_chosen_stdout(fdt, spapr->vio_bus);
+ }
+
+ _FDT((fdt_pack(fdt)));
+
+ if (fdt_totalsize(fdt) > FDT_MAX_SIZE) {
+ error_report("FDT too big ! 0x%x bytes (max is 0x%x)",
+ fdt_totalsize(fdt), FDT_MAX_SIZE);
+ exit(1);
+ }
+
+ cpu_physical_memory_write(fdt_addr, fdt, fdt_totalsize(fdt));
+
+ g_free(bootlist);
+ g_free(fdt);
+}
+
+static uint64_t translate_kernel_address(void *opaque, uint64_t addr)
+{
+ return (addr & 0x0fffffff) + KERNEL_LOAD_ADDR;
+}
+
+static void emulate_spapr_hypercall(PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+
+ if (msr_pr) {
+ hcall_dprintf("Hypercall made with MSR[PR]=1\n");
+ env->gpr[3] = H_PRIVILEGE;
+ } else {
+ env->gpr[3] = spapr_hypercall(cpu, env->gpr[3], &env->gpr[4]);
+ }
+}
+
+#define HPTE(_table, _i) (void *)(((uint64_t *)(_table)) + ((_i) * 2))
+#define HPTE_VALID(_hpte) (tswap64(*((uint64_t *)(_hpte))) & HPTE64_V_VALID)
+#define HPTE_DIRTY(_hpte) (tswap64(*((uint64_t *)(_hpte))) & HPTE64_V_HPTE_DIRTY)
+#define CLEAN_HPTE(_hpte) ((*(uint64_t *)(_hpte)) &= tswap64(~HPTE64_V_HPTE_DIRTY))
+#define DIRTY_HPTE(_hpte) ((*(uint64_t *)(_hpte)) |= tswap64(HPTE64_V_HPTE_DIRTY))
+
+static void spapr_reset_htab(sPAPRMachineState *spapr)
+{
+ long shift;
+ int index;
+
+ /* allocate hash page table. For now we always make this 16mb,
+ * later we should probably make it scale to the size of guest
+ * RAM */
+
+ shift = kvmppc_reset_htab(spapr->htab_shift);
+
+ if (shift > 0) {
+ /* Kernel handles htab, we don't need to allocate one */
+ spapr->htab_shift = shift;
+ kvmppc_kern_htab = true;
+
+ /* Tell readers to update their file descriptor */
+ if (spapr->htab_fd >= 0) {
+ spapr->htab_fd_stale = true;
+ }
+ } else {
+ if (!spapr->htab) {
+ /* Allocate an htab if we don't yet have one */
+ spapr->htab = qemu_memalign(HTAB_SIZE(spapr), HTAB_SIZE(spapr));
+ }
+
+ /* And clear it */
+ memset(spapr->htab, 0, HTAB_SIZE(spapr));
+
+ for (index = 0; index < HTAB_SIZE(spapr) / HASH_PTE_SIZE_64; index++) {
+ DIRTY_HPTE(HPTE(spapr->htab, index));
+ }
+ }
+
+ /* Update the RMA size if necessary */
+ if (spapr->vrma_adjust) {
+ spapr->rma_size = kvmppc_rma_size(spapr_node0_size(),
+ spapr->htab_shift);
+ }
+}
+
+static int find_unknown_sysbus_device(SysBusDevice *sbdev, void *opaque)
+{
+ bool matched = false;
+
+ if (object_dynamic_cast(OBJECT(sbdev), TYPE_SPAPR_PCI_HOST_BRIDGE)) {
+ matched = true;
+ }
+
+ if (!matched) {
+ error_report("Device %s is not supported by this machine yet.",
+ qdev_fw_name(DEVICE(sbdev)));
+ exit(1);
+ }
+
+ return 0;
+}
+
+/*
+ * A guest reset will cause spapr->htab_fd to become stale if being used.
+ * Reopen the file descriptor to make sure the whole HTAB is properly read.
+ */
+static int spapr_check_htab_fd(sPAPRMachineState *spapr)
+{
+ int rc = 0;
+
+ if (spapr->htab_fd_stale) {
+ close(spapr->htab_fd);
+ spapr->htab_fd = kvmppc_get_htab_fd(false);
+ if (spapr->htab_fd < 0) {
+ error_report("Unable to open fd for reading hash table from KVM: "
+ "%s", strerror(errno));
+ rc = -1;
+ }
+ spapr->htab_fd_stale = false;
+ }
+
+ return rc;
+}
+
+static void ppc_spapr_reset(void)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ PowerPCCPU *first_ppc_cpu;
+ uint32_t rtas_limit;
+
+ /* Check for unknown sysbus devices */
+ foreach_dynamic_sysbus_device(find_unknown_sysbus_device, NULL);
+
+ /* Reset the hash table & recalc the RMA */
+ spapr_reset_htab(spapr);
+
+ qemu_devices_reset();
+
+ /*
+ * We place the device tree and RTAS just below either the top of the RMA,
+ * or just below 2GB, whichever is lowere, so that it can be
+ * processed with 32-bit real mode code if necessary
+ */
+ rtas_limit = MIN(spapr->rma_size, RTAS_MAX_ADDR);
+ spapr->rtas_addr = rtas_limit - RTAS_MAX_SIZE;
+ spapr->fdt_addr = spapr->rtas_addr - FDT_MAX_SIZE;
+
+ /* Load the fdt */
+ spapr_finalize_fdt(spapr, spapr->fdt_addr, spapr->rtas_addr,
+ spapr->rtas_size);
+
+ /* Copy RTAS over */
+ cpu_physical_memory_write(spapr->rtas_addr, spapr->rtas_blob,
+ spapr->rtas_size);
+
+ /* Set up the entry state */
+ first_ppc_cpu = POWERPC_CPU(first_cpu);
+ first_ppc_cpu->env.gpr[3] = spapr->fdt_addr;
+ first_ppc_cpu->env.gpr[5] = 0;
+ first_cpu->halted = 0;
+ first_ppc_cpu->env.nip = SPAPR_ENTRY_POINT;
+
+}
+
+static void spapr_cpu_reset(void *opaque)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ PowerPCCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+ CPUPPCState *env = &cpu->env;
+
+ cpu_reset(cs);
+
+ /* All CPUs start halted. CPU0 is unhalted from the machine level
+ * reset code and the rest are explicitly started up by the guest
+ * using an RTAS call */
+ cs->halted = 1;
+
+ env->spr[SPR_HIOR] = 0;
+
+ env->external_htab = (uint8_t *)spapr->htab;
+ if (kvm_enabled() && !env->external_htab) {
+ /*
+ * HV KVM, set external_htab to 1 so our ppc_hash64_load_hpte*
+ * functions do the right thing.
+ */
+ env->external_htab = (void *)1;
+ }
+ env->htab_base = -1;
+ /*
+ * htab_mask is the mask used to normalize hash value to PTEG index.
+ * htab_shift is log2 of hash table size.
+ * We have 8 hpte per group, and each hpte is 16 bytes.
+ * ie have 128 bytes per hpte entry.
+ */
+ env->htab_mask = (1ULL << (spapr->htab_shift - 7)) - 1;
+ env->spr[SPR_SDR1] = (target_ulong)(uintptr_t)spapr->htab |
+ (spapr->htab_shift - 18);
+}
+
+static void spapr_create_nvram(sPAPRMachineState *spapr)
+{
+ DeviceState *dev = qdev_create(&spapr->vio_bus->bus, "spapr-nvram");
+ DriveInfo *dinfo = drive_get(IF_PFLASH, 0, 0);
+
+ if (dinfo) {
+ qdev_prop_set_drive_nofail(dev, "drive", blk_by_legacy_dinfo(dinfo));
+ }
+
+ qdev_init_nofail(dev);
+
+ spapr->nvram = (struct sPAPRNVRAM *)dev;
+}
+
+static void spapr_rtc_create(sPAPRMachineState *spapr)
+{
+ DeviceState *dev = qdev_create(NULL, TYPE_SPAPR_RTC);
+
+ qdev_init_nofail(dev);
+ spapr->rtc = dev;
+
+ object_property_add_alias(qdev_get_machine(), "rtc-time",
+ OBJECT(spapr->rtc), "date", NULL);
+}
+
+/* Returns whether we want to use VGA or not */
+static int spapr_vga_init(PCIBus *pci_bus)
+{
+ switch (vga_interface_type) {
+ case VGA_NONE:
+ return false;
+ case VGA_DEVICE:
+ return true;
+ case VGA_STD:
+ return pci_vga_init(pci_bus) != NULL;
+ default:
+ fprintf(stderr, "This vga model is not supported,"
+ "currently it only supports -vga std\n");
+ exit(0);
+ }
+}
+
+static int spapr_post_load(void *opaque, int version_id)
+{
+ sPAPRMachineState *spapr = (sPAPRMachineState *)opaque;
+ int err = 0;
+
+ /* In earlier versions, there was no separate qdev for the PAPR
+ * RTC, so the RTC offset was stored directly in sPAPREnvironment.
+ * So when migrating from those versions, poke the incoming offset
+ * value into the RTC device */
+ if (version_id < 3) {
+ err = spapr_rtc_import_offset(spapr->rtc, spapr->rtc_offset);
+ }
+
+ return err;
+}
+
+static bool version_before_3(void *opaque, int version_id)
+{
+ return version_id < 3;
+}
+
+static const VMStateDescription vmstate_spapr = {
+ .name = "spapr",
+ .version_id = 3,
+ .minimum_version_id = 1,
+ .post_load = spapr_post_load,
+ .fields = (VMStateField[]) {
+ /* used to be @next_irq */
+ VMSTATE_UNUSED_BUFFER(version_before_3, 0, 4),
+
+ /* RTC offset */
+ VMSTATE_UINT64_TEST(rtc_offset, sPAPRMachineState, version_before_3),
+
+ VMSTATE_PPC_TIMEBASE_V(tb, sPAPRMachineState, 2),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static int htab_save_setup(QEMUFile *f, void *opaque)
+{
+ sPAPRMachineState *spapr = opaque;
+
+ /* "Iteration" header */
+ qemu_put_be32(f, spapr->htab_shift);
+
+ if (spapr->htab) {
+ spapr->htab_save_index = 0;
+ spapr->htab_first_pass = true;
+ } else {
+ assert(kvm_enabled());
+
+ spapr->htab_fd = kvmppc_get_htab_fd(false);
+ spapr->htab_fd_stale = false;
+ if (spapr->htab_fd < 0) {
+ fprintf(stderr, "Unable to open fd for reading hash table from KVM: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ }
+
+
+ return 0;
+}
+
+static void htab_save_first_pass(QEMUFile *f, sPAPRMachineState *spapr,
+ int64_t max_ns)
+{
+ int htabslots = HTAB_SIZE(spapr) / HASH_PTE_SIZE_64;
+ int index = spapr->htab_save_index;
+ int64_t starttime = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+
+ assert(spapr->htab_first_pass);
+
+ do {
+ int chunkstart;
+
+ /* Consume invalid HPTEs */
+ while ((index < htabslots)
+ && !HPTE_VALID(HPTE(spapr->htab, index))) {
+ index++;
+ CLEAN_HPTE(HPTE(spapr->htab, index));
+ }
+
+ /* Consume valid HPTEs */
+ chunkstart = index;
+ while ((index < htabslots) && (index - chunkstart < USHRT_MAX)
+ && HPTE_VALID(HPTE(spapr->htab, index))) {
+ index++;
+ CLEAN_HPTE(HPTE(spapr->htab, index));
+ }
+
+ if (index > chunkstart) {
+ int n_valid = index - chunkstart;
+
+ qemu_put_be32(f, chunkstart);
+ qemu_put_be16(f, n_valid);
+ qemu_put_be16(f, 0);
+ qemu_put_buffer(f, HPTE(spapr->htab, chunkstart),
+ HASH_PTE_SIZE_64 * n_valid);
+
+ if ((qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - starttime) > max_ns) {
+ break;
+ }
+ }
+ } while ((index < htabslots) && !qemu_file_rate_limit(f));
+
+ if (index >= htabslots) {
+ assert(index == htabslots);
+ index = 0;
+ spapr->htab_first_pass = false;
+ }
+ spapr->htab_save_index = index;
+}
+
+static int htab_save_later_pass(QEMUFile *f, sPAPRMachineState *spapr,
+ int64_t max_ns)
+{
+ bool final = max_ns < 0;
+ int htabslots = HTAB_SIZE(spapr) / HASH_PTE_SIZE_64;
+ int examined = 0, sent = 0;
+ int index = spapr->htab_save_index;
+ int64_t starttime = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+
+ assert(!spapr->htab_first_pass);
+
+ do {
+ int chunkstart, invalidstart;
+
+ /* Consume non-dirty HPTEs */
+ while ((index < htabslots)
+ && !HPTE_DIRTY(HPTE(spapr->htab, index))) {
+ index++;
+ examined++;
+ }
+
+ chunkstart = index;
+ /* Consume valid dirty HPTEs */
+ while ((index < htabslots) && (index - chunkstart < USHRT_MAX)
+ && HPTE_DIRTY(HPTE(spapr->htab, index))
+ && HPTE_VALID(HPTE(spapr->htab, index))) {
+ CLEAN_HPTE(HPTE(spapr->htab, index));
+ index++;
+ examined++;
+ }
+
+ invalidstart = index;
+ /* Consume invalid dirty HPTEs */
+ while ((index < htabslots) && (index - invalidstart < USHRT_MAX)
+ && HPTE_DIRTY(HPTE(spapr->htab, index))
+ && !HPTE_VALID(HPTE(spapr->htab, index))) {
+ CLEAN_HPTE(HPTE(spapr->htab, index));
+ index++;
+ examined++;
+ }
+
+ if (index > chunkstart) {
+ int n_valid = invalidstart - chunkstart;
+ int n_invalid = index - invalidstart;
+
+ qemu_put_be32(f, chunkstart);
+ qemu_put_be16(f, n_valid);
+ qemu_put_be16(f, n_invalid);
+ qemu_put_buffer(f, HPTE(spapr->htab, chunkstart),
+ HASH_PTE_SIZE_64 * n_valid);
+ sent += index - chunkstart;
+
+ if (!final && (qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - starttime) > max_ns) {
+ break;
+ }
+ }
+
+ if (examined >= htabslots) {
+ break;
+ }
+
+ if (index >= htabslots) {
+ assert(index == htabslots);
+ index = 0;
+ }
+ } while ((examined < htabslots) && (!qemu_file_rate_limit(f) || final));
+
+ if (index >= htabslots) {
+ assert(index == htabslots);
+ index = 0;
+ }
+
+ spapr->htab_save_index = index;
+
+ return (examined >= htabslots) && (sent == 0) ? 1 : 0;
+}
+
+#define MAX_ITERATION_NS 5000000 /* 5 ms */
+#define MAX_KVM_BUF_SIZE 2048
+
+static int htab_save_iterate(QEMUFile *f, void *opaque)
+{
+ sPAPRMachineState *spapr = opaque;
+ int rc = 0;
+
+ /* Iteration header */
+ qemu_put_be32(f, 0);
+
+ if (!spapr->htab) {
+ assert(kvm_enabled());
+
+ rc = spapr_check_htab_fd(spapr);
+ if (rc < 0) {
+ return rc;
+ }
+
+ rc = kvmppc_save_htab(f, spapr->htab_fd,
+ MAX_KVM_BUF_SIZE, MAX_ITERATION_NS);
+ if (rc < 0) {
+ return rc;
+ }
+ } else if (spapr->htab_first_pass) {
+ htab_save_first_pass(f, spapr, MAX_ITERATION_NS);
+ } else {
+ rc = htab_save_later_pass(f, spapr, MAX_ITERATION_NS);
+ }
+
+ /* End marker */
+ qemu_put_be32(f, 0);
+ qemu_put_be16(f, 0);
+ qemu_put_be16(f, 0);
+
+ return rc;
+}
+
+static int htab_save_complete(QEMUFile *f, void *opaque)
+{
+ sPAPRMachineState *spapr = opaque;
+
+ /* Iteration header */
+ qemu_put_be32(f, 0);
+
+ if (!spapr->htab) {
+ int rc;
+
+ assert(kvm_enabled());
+
+ rc = spapr_check_htab_fd(spapr);
+ if (rc < 0) {
+ return rc;
+ }
+
+ rc = kvmppc_save_htab(f, spapr->htab_fd, MAX_KVM_BUF_SIZE, -1);
+ if (rc < 0) {
+ return rc;
+ }
+ close(spapr->htab_fd);
+ spapr->htab_fd = -1;
+ } else {
+ htab_save_later_pass(f, spapr, -1);
+ }
+
+ /* End marker */
+ qemu_put_be32(f, 0);
+ qemu_put_be16(f, 0);
+ qemu_put_be16(f, 0);
+
+ return 0;
+}
+
+static int htab_load(QEMUFile *f, void *opaque, int version_id)
+{
+ sPAPRMachineState *spapr = opaque;
+ uint32_t section_hdr;
+ int fd = -1;
+
+ if (version_id < 1 || version_id > 1) {
+ fprintf(stderr, "htab_load() bad version\n");
+ return -EINVAL;
+ }
+
+ section_hdr = qemu_get_be32(f);
+
+ if (section_hdr) {
+ /* First section, just the hash shift */
+ if (spapr->htab_shift != section_hdr) {
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ if (!spapr->htab) {
+ assert(kvm_enabled());
+
+ fd = kvmppc_get_htab_fd(true);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to open fd to restore KVM hash table: %s\n",
+ strerror(errno));
+ }
+ }
+
+ while (true) {
+ uint32_t index;
+ uint16_t n_valid, n_invalid;
+
+ index = qemu_get_be32(f);
+ n_valid = qemu_get_be16(f);
+ n_invalid = qemu_get_be16(f);
+
+ if ((index == 0) && (n_valid == 0) && (n_invalid == 0)) {
+ /* End of Stream */
+ break;
+ }
+
+ if ((index + n_valid + n_invalid) >
+ (HTAB_SIZE(spapr) / HASH_PTE_SIZE_64)) {
+ /* Bad index in stream */
+ fprintf(stderr, "htab_load() bad index %d (%hd+%hd entries) "
+ "in htab stream (htab_shift=%d)\n", index, n_valid, n_invalid,
+ spapr->htab_shift);
+ return -EINVAL;
+ }
+
+ if (spapr->htab) {
+ if (n_valid) {
+ qemu_get_buffer(f, HPTE(spapr->htab, index),
+ HASH_PTE_SIZE_64 * n_valid);
+ }
+ if (n_invalid) {
+ memset(HPTE(spapr->htab, index + n_valid), 0,
+ HASH_PTE_SIZE_64 * n_invalid);
+ }
+ } else {
+ int rc;
+
+ assert(fd >= 0);
+
+ rc = kvmppc_load_htab_chunk(f, fd, index, n_valid, n_invalid);
+ if (rc < 0) {
+ return rc;
+ }
+ }
+ }
+
+ if (!spapr->htab) {
+ assert(fd >= 0);
+ close(fd);
+ }
+
+ return 0;
+}
+
+static SaveVMHandlers savevm_htab_handlers = {
+ .save_live_setup = htab_save_setup,
+ .save_live_iterate = htab_save_iterate,
+ .save_live_complete = htab_save_complete,
+ .load_state = htab_load,
+};
+
+static void spapr_boot_set(void *opaque, const char *boot_device,
+ Error **errp)
+{
+ MachineState *machine = MACHINE(qdev_get_machine());
+ machine->boot_order = g_strdup(boot_device);
+}
+
+static void spapr_cpu_init(sPAPRMachineState *spapr, PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+
+ /* Set time-base frequency to 512 MHz */
+ cpu_ppc_tb_init(env, TIMEBASE_FREQ);
+
+ /* PAPR always has exception vectors in RAM not ROM. To ensure this,
+ * MSR[IP] should never be set.
+ */
+ env->msr_mask &= ~(1 << 6);
+
+ /* Tell KVM that we're in PAPR mode */
+ if (kvm_enabled()) {
+ kvmppc_set_papr(cpu);
+ }
+
+ if (cpu->max_compat) {
+ if (ppc_set_compat(cpu, cpu->max_compat) < 0) {
+ exit(1);
+ }
+ }
+
+ xics_cpu_setup(spapr->icp, cpu);
+
+ qemu_register_reset(spapr_cpu_reset, cpu);
+}
+
+/* pSeries LPAR / sPAPR hardware init */
+static void ppc_spapr_init(MachineState *machine)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(machine);
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ PowerPCCPU *cpu;
+ PCIHostState *phb;
+ int i;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *rma_region;
+ void *rma = NULL;
+ hwaddr rma_alloc_size;
+ hwaddr node0_size = spapr_node0_size();
+ uint32_t initrd_base = 0;
+ long kernel_size = 0, initrd_size = 0;
+ long load_limit, fw_size;
+ bool kernel_le = false;
+ char *filename;
+
+ msi_supported = true;
+
+ QLIST_INIT(&spapr->phbs);
+
+ cpu_ppc_hypercall = emulate_spapr_hypercall;
+
+ /* Allocate RMA if necessary */
+ rma_alloc_size = kvmppc_alloc_rma(&rma);
+
+ if (rma_alloc_size == -1) {
+ error_report("Unable to create RMA");
+ exit(1);
+ }
+
+ if (rma_alloc_size && (rma_alloc_size < node0_size)) {
+ spapr->rma_size = rma_alloc_size;
+ } else {
+ spapr->rma_size = node0_size;
+
+ /* With KVM, we don't actually know whether KVM supports an
+ * unbounded RMA (PR KVM) or is limited by the hash table size
+ * (HV KVM using VRMA), so we always assume the latter
+ *
+ * In that case, we also limit the initial allocations for RTAS
+ * etc... to 256M since we have no way to know what the VRMA size
+ * is going to be as it depends on the size of the hash table
+ * isn't determined yet.
+ */
+ if (kvm_enabled()) {
+ spapr->vrma_adjust = 1;
+ spapr->rma_size = MIN(spapr->rma_size, 0x10000000);
+ }
+ }
+
+ if (spapr->rma_size > node0_size) {
+ fprintf(stderr, "Error: Numa node 0 has to span the RMA (%#08"HWADDR_PRIx")\n",
+ spapr->rma_size);
+ exit(1);
+ }
+
+ /* Setup a load limit for the ramdisk leaving room for SLOF and FDT */
+ load_limit = MIN(spapr->rma_size, RTAS_MAX_ADDR) - FW_OVERHEAD;
+
+ /* We aim for a hash table of size 1/128 the size of RAM. The
+ * normal rule of thumb is 1/64 the size of RAM, but that's much
+ * more than needed for the Linux guests we support. */
+ spapr->htab_shift = 18; /* Minimum architected size */
+ while (spapr->htab_shift <= 46) {
+ if ((1ULL << (spapr->htab_shift + 7)) >= machine->ram_size) {
+ break;
+ }
+ spapr->htab_shift++;
+ }
+
+ /* Set up Interrupt Controller before we create the VCPUs */
+ spapr->icp = xics_system_init(machine,
+ DIV_ROUND_UP(max_cpus * kvmppc_smt_threads(),
+ smp_threads),
+ XICS_IRQS);
+
+ /* init CPUs */
+ if (machine->cpu_model == NULL) {
+ machine->cpu_model = kvm_enabled() ? "host" : "POWER7";
+ }
+ for (i = 0; i < smp_cpus; i++) {
+ cpu = cpu_ppc_init(machine->cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find PowerPC CPU definition\n");
+ exit(1);
+ }
+ spapr_cpu_init(spapr, cpu);
+ }
+
+ if (kvm_enabled()) {
+ /* Enable H_LOGICAL_CI_* so SLOF can talk to in-kernel devices */
+ kvmppc_enable_logical_ci_hcalls();
+ }
+
+ /* allocate RAM */
+ memory_region_allocate_system_memory(ram, NULL, "ppc_spapr.ram",
+ machine->ram_size);
+ memory_region_add_subregion(sysmem, 0, ram);
+
+ if (rma_alloc_size && rma) {
+ rma_region = g_new(MemoryRegion, 1);
+ memory_region_init_ram_ptr(rma_region, NULL, "ppc_spapr.rma",
+ rma_alloc_size, rma);
+ vmstate_register_ram_global(rma_region);
+ memory_region_add_subregion(sysmem, 0, rma_region);
+ }
+
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "spapr-rtas.bin");
+ if (!filename) {
+ error_report("Could not find LPAR rtas '%s'", "spapr-rtas.bin");
+ exit(1);
+ }
+ spapr->rtas_size = get_image_size(filename);
+ spapr->rtas_blob = g_malloc(spapr->rtas_size);
+ if (load_image_size(filename, spapr->rtas_blob, spapr->rtas_size) < 0) {
+ error_report("Could not load LPAR rtas '%s'", filename);
+ exit(1);
+ }
+ if (spapr->rtas_size > RTAS_MAX_SIZE) {
+ error_report("RTAS too big ! 0x%zx bytes (max is 0x%x)",
+ (size_t)spapr->rtas_size, RTAS_MAX_SIZE);
+ exit(1);
+ }
+ g_free(filename);
+
+ /* Set up EPOW events infrastructure */
+ spapr_events_init(spapr);
+
+ /* Set up the RTC RTAS interfaces */
+ spapr_rtc_create(spapr);
+
+ /* Set up VIO bus */
+ spapr->vio_bus = spapr_vio_bus_init();
+
+ for (i = 0; i < MAX_SERIAL_PORTS; i++) {
+ if (serial_hds[i]) {
+ spapr_vty_create(spapr->vio_bus, serial_hds[i]);
+ }
+ }
+
+ /* We always have at least the nvram device on VIO */
+ spapr_create_nvram(spapr);
+
+ /* Set up PCI */
+ spapr_pci_rtas_init();
+
+ phb = spapr_create_phb(spapr, 0);
+
+ for (i = 0; i < nb_nics; i++) {
+ NICInfo *nd = &nd_table[i];
+
+ if (!nd->model) {
+ nd->model = g_strdup("ibmveth");
+ }
+
+ if (strcmp(nd->model, "ibmveth") == 0) {
+ spapr_vlan_create(spapr->vio_bus, nd);
+ } else {
+ pci_nic_init_nofail(&nd_table[i], phb->bus, nd->model, NULL);
+ }
+ }
+
+ for (i = 0; i <= drive_get_max_bus(IF_SCSI); i++) {
+ spapr_vscsi_create(spapr->vio_bus);
+ }
+
+ /* Graphics */
+ if (spapr_vga_init(phb->bus)) {
+ spapr->has_graphics = true;
+ machine->usb |= defaults_enabled() && !machine->usb_disabled;
+ }
+
+ if (machine->usb) {
+ pci_create_simple(phb->bus, -1, "pci-ohci");
+
+ if (spapr->has_graphics) {
+ USBBus *usb_bus = usb_bus_find(-1);
+
+ usb_create_simple(usb_bus, "usb-kbd");
+ usb_create_simple(usb_bus, "usb-mouse");
+ }
+ }
+
+ if (spapr->rma_size < (MIN_RMA_SLOF << 20)) {
+ fprintf(stderr, "qemu: pSeries SLOF firmware requires >= "
+ "%ldM guest RMA (Real Mode Area memory)\n", MIN_RMA_SLOF);
+ exit(1);
+ }
+
+ if (kernel_filename) {
+ uint64_t lowaddr = 0;
+
+ kernel_size = load_elf(kernel_filename, translate_kernel_address, NULL,
+ NULL, &lowaddr, NULL, 1, ELF_MACHINE, 0);
+ if (kernel_size == ELF_LOAD_WRONG_ENDIAN) {
+ kernel_size = load_elf(kernel_filename,
+ translate_kernel_address, NULL,
+ NULL, &lowaddr, NULL, 0, ELF_MACHINE, 0);
+ kernel_le = kernel_size > 0;
+ }
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: error loading %s: %s\n",
+ kernel_filename, load_elf_strerror(kernel_size));
+ exit(1);
+ }
+
+ /* load initrd */
+ if (initrd_filename) {
+ /* Try to locate the initrd in the gap between the kernel
+ * and the firmware. Add a bit of space just in case
+ */
+ initrd_base = (KERNEL_LOAD_ADDR + kernel_size + 0x1ffff) & ~0xffff;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ load_limit - initrd_base);
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+ }
+
+ if (bios_name == NULL) {
+ bios_name = FW_FILE_NAME;
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (!filename) {
+ error_report("Could not find LPAR firmware '%s'", bios_name);
+ exit(1);
+ }
+ fw_size = load_image_targphys(filename, 0, FW_MAX_SIZE);
+ if (fw_size <= 0) {
+ error_report("Could not load LPAR firmware '%s'", filename);
+ exit(1);
+ }
+ g_free(filename);
+
+ /* FIXME: Should register things through the MachineState's qdev
+ * interface, this is a legacy from the sPAPREnvironment structure
+ * which predated MachineState but had a similar function */
+ vmstate_register(NULL, 0, &vmstate_spapr, spapr);
+ register_savevm_live(NULL, "spapr/htab", -1, 1,
+ &savevm_htab_handlers, spapr);
+
+ /* Prepare the device tree */
+ spapr->fdt_skel = spapr_create_fdt_skel(initrd_base, initrd_size,
+ kernel_size, kernel_le,
+ kernel_cmdline,
+ spapr->check_exception_irq);
+ assert(spapr->fdt_skel != NULL);
+
+ /* used by RTAS */
+ QTAILQ_INIT(&spapr->ccs_list);
+ qemu_register_reset(spapr_ccs_reset_hook, spapr);
+
+ qemu_register_boot_set(spapr_boot_set, spapr);
+}
+
+static int spapr_kvm_type(const char *vm_type)
+{
+ if (!vm_type) {
+ return 0;
+ }
+
+ if (!strcmp(vm_type, "HV")) {
+ return 1;
+ }
+
+ if (!strcmp(vm_type, "PR")) {
+ return 2;
+ }
+
+ error_report("Unknown kvm-type specified '%s'", vm_type);
+ exit(1);
+}
+
+/*
+ * Implementation of an interface to adjust firmware path
+ * for the bootindex property handling.
+ */
+static char *spapr_get_fw_dev_path(FWPathProvider *p, BusState *bus,
+ DeviceState *dev)
+{
+#define CAST(type, obj, name) \
+ ((type *)object_dynamic_cast(OBJECT(obj), (name)))
+ SCSIDevice *d = CAST(SCSIDevice, dev, TYPE_SCSI_DEVICE);
+ sPAPRPHBState *phb = CAST(sPAPRPHBState, dev, TYPE_SPAPR_PCI_HOST_BRIDGE);
+
+ if (d) {
+ void *spapr = CAST(void, bus->parent, "spapr-vscsi");
+ VirtIOSCSI *virtio = CAST(VirtIOSCSI, bus->parent, TYPE_VIRTIO_SCSI);
+ USBDevice *usb = CAST(USBDevice, bus->parent, TYPE_USB_DEVICE);
+
+ if (spapr) {
+ /*
+ * Replace "channel@0/disk@0,0" with "disk@8000000000000000":
+ * We use SRP luns of the form 8000 | (bus << 8) | (id << 5) | lun
+ * in the top 16 bits of the 64-bit LUN
+ */
+ unsigned id = 0x8000 | (d->id << 8) | d->lun;
+ return g_strdup_printf("%s@%"PRIX64, qdev_fw_name(dev),
+ (uint64_t)id << 48);
+ } else if (virtio) {
+ /*
+ * We use SRP luns of the form 01000000 | (target << 8) | lun
+ * in the top 32 bits of the 64-bit LUN
+ * Note: the quote above is from SLOF and it is wrong,
+ * the actual binding is:
+ * swap 0100 or 10 << or 20 << ( target lun-id -- srplun )
+ */
+ unsigned id = 0x1000000 | (d->id << 16) | d->lun;
+ return g_strdup_printf("%s@%"PRIX64, qdev_fw_name(dev),
+ (uint64_t)id << 32);
+ } else if (usb) {
+ /*
+ * We use SRP luns of the form 01000000 | (usb-port << 16) | lun
+ * in the top 32 bits of the 64-bit LUN
+ */
+ unsigned usb_port = atoi(usb->port->path);
+ unsigned id = 0x1000000 | (usb_port << 16) | d->lun;
+ return g_strdup_printf("%s@%"PRIX64, qdev_fw_name(dev),
+ (uint64_t)id << 32);
+ }
+ }
+
+ if (phb) {
+ /* Replace "pci" with "pci@800000020000000" */
+ return g_strdup_printf("pci@%"PRIX64, phb->buid);
+ }
+
+ return NULL;
+}
+
+static char *spapr_get_kvm_type(Object *obj, Error **errp)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(obj);
+
+ return g_strdup(spapr->kvm_type);
+}
+
+static void spapr_set_kvm_type(Object *obj, const char *value, Error **errp)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(obj);
+
+ g_free(spapr->kvm_type);
+ spapr->kvm_type = g_strdup(value);
+}
+
+static void spapr_machine_initfn(Object *obj)
+{
+ object_property_add_str(obj, "kvm-type",
+ spapr_get_kvm_type, spapr_set_kvm_type, NULL);
+ object_property_set_description(obj, "kvm-type",
+ "Specifies the KVM virtualization mode (HV, PR)",
+ NULL);
+}
+
+static void ppc_cpu_do_nmi_on_cpu(void *arg)
+{
+ CPUState *cs = arg;
+
+ cpu_synchronize_state(cs);
+ ppc_cpu_do_system_reset(cs);
+}
+
+static void spapr_nmi(NMIState *n, int cpu_index, Error **errp)
+{
+ CPUState *cs;
+
+ CPU_FOREACH(cs) {
+ async_run_on_cpu(cs, ppc_cpu_do_nmi_on_cpu, cs);
+ }
+}
+
+static void spapr_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ FWPathProviderClass *fwc = FW_PATH_PROVIDER_CLASS(oc);
+ NMIClass *nc = NMI_CLASS(oc);
+
+ mc->init = ppc_spapr_init;
+ mc->reset = ppc_spapr_reset;
+ mc->block_default_type = IF_SCSI;
+ mc->max_cpus = MAX_CPUS;
+ mc->no_parallel = 1;
+ mc->default_boot_order = "";
+ mc->default_ram_size = 512 * M_BYTE;
+ mc->kvm_type = spapr_kvm_type;
+ mc->has_dynamic_sysbus = true;
+
+ fwc->get_dev_path = spapr_get_fw_dev_path;
+ nc->nmi_monitor_handler = spapr_nmi;
+}
+
+static const TypeInfo spapr_machine_info = {
+ .name = TYPE_SPAPR_MACHINE,
+ .parent = TYPE_MACHINE,
+ .abstract = true,
+ .instance_size = sizeof(sPAPRMachineState),
+ .instance_init = spapr_machine_initfn,
+ .class_size = sizeof(sPAPRMachineClass),
+ .class_init = spapr_machine_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_FW_PATH_PROVIDER },
+ { TYPE_NMI },
+ { }
+ },
+};
+
+#define SPAPR_COMPAT_2_3 \
+ HW_COMPAT_2_3 \
+ {\
+ .driver = "spapr-pci-host-bridge",\
+ .property = "dynamic-reconfiguration",\
+ .value = "off",\
+ },
+
+#define SPAPR_COMPAT_2_2 \
+ SPAPR_COMPAT_2_3 \
+ HW_COMPAT_2_2 \
+ {\
+ .driver = TYPE_SPAPR_PCI_HOST_BRIDGE,\
+ .property = "mem_win_size",\
+ .value = "0x20000000",\
+ },
+
+#define SPAPR_COMPAT_2_1 \
+ SPAPR_COMPAT_2_2 \
+ HW_COMPAT_2_1
+
+static void spapr_compat_2_3(Object *obj)
+{
+ savevm_skip_section_footers();
+ global_state_set_optional();
+}
+
+static void spapr_compat_2_2(Object *obj)
+{
+ spapr_compat_2_3(obj);
+}
+
+static void spapr_compat_2_1(Object *obj)
+{
+ spapr_compat_2_2(obj);
+}
+
+static void spapr_machine_2_3_instance_init(Object *obj)
+{
+ spapr_compat_2_3(obj);
+ spapr_machine_initfn(obj);
+}
+
+static void spapr_machine_2_2_instance_init(Object *obj)
+{
+ spapr_compat_2_2(obj);
+ spapr_machine_initfn(obj);
+}
+
+static void spapr_machine_2_1_instance_init(Object *obj)
+{
+ spapr_compat_2_1(obj);
+ spapr_machine_initfn(obj);
+}
+
+static void spapr_machine_2_1_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ static GlobalProperty compat_props[] = {
+ SPAPR_COMPAT_2_1
+ { /* end of list */ }
+ };
+
+ mc->name = "pseries-2.1";
+ mc->desc = "pSeries Logical Partition (PAPR compliant) v2.1";
+ mc->compat_props = compat_props;
+}
+
+static const TypeInfo spapr_machine_2_1_info = {
+ .name = TYPE_SPAPR_MACHINE "2.1",
+ .parent = TYPE_SPAPR_MACHINE,
+ .class_init = spapr_machine_2_1_class_init,
+ .instance_init = spapr_machine_2_1_instance_init,
+};
+
+static void spapr_machine_2_2_class_init(ObjectClass *oc, void *data)
+{
+ static GlobalProperty compat_props[] = {
+ SPAPR_COMPAT_2_2
+ { /* end of list */ }
+ };
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->name = "pseries-2.2";
+ mc->desc = "pSeries Logical Partition (PAPR compliant) v2.2";
+ mc->compat_props = compat_props;
+}
+
+static const TypeInfo spapr_machine_2_2_info = {
+ .name = TYPE_SPAPR_MACHINE "2.2",
+ .parent = TYPE_SPAPR_MACHINE,
+ .class_init = spapr_machine_2_2_class_init,
+ .instance_init = spapr_machine_2_2_instance_init,
+};
+
+static void spapr_machine_2_3_class_init(ObjectClass *oc, void *data)
+{
+ static GlobalProperty compat_props[] = {
+ SPAPR_COMPAT_2_3
+ { /* end of list */ }
+ };
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->name = "pseries-2.3";
+ mc->desc = "pSeries Logical Partition (PAPR compliant) v2.3";
+ mc->compat_props = compat_props;
+}
+
+static const TypeInfo spapr_machine_2_3_info = {
+ .name = TYPE_SPAPR_MACHINE "2.3",
+ .parent = TYPE_SPAPR_MACHINE,
+ .class_init = spapr_machine_2_3_class_init,
+ .instance_init = spapr_machine_2_3_instance_init,
+};
+
+static void spapr_machine_2_4_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->name = "pseries-2.4";
+ mc->desc = "pSeries Logical Partition (PAPR compliant) v2.4";
+ mc->alias = "pseries";
+ mc->is_default = 1;
+}
+
+static const TypeInfo spapr_machine_2_4_info = {
+ .name = TYPE_SPAPR_MACHINE "2.4",
+ .parent = TYPE_SPAPR_MACHINE,
+ .class_init = spapr_machine_2_4_class_init,
+};
+
+static void spapr_machine_register_types(void)
+{
+ type_register_static(&spapr_machine_info);
+ type_register_static(&spapr_machine_2_1_info);
+ type_register_static(&spapr_machine_2_2_info);
+ type_register_static(&spapr_machine_2_3_info);
+ type_register_static(&spapr_machine_2_4_info);
+}
+
+type_init(spapr_machine_register_types)
diff --git a/hw/ppc/spapr_drc.c b/hw/ppc/spapr_drc.c
new file mode 100644
index 00000000..ee874326
--- /dev/null
+++ b/hw/ppc/spapr_drc.c
@@ -0,0 +1,745 @@
+/*
+ * QEMU SPAPR Dynamic Reconfiguration Connector Implementation
+ *
+ * Copyright IBM Corp. 2014
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "hw/ppc/spapr_drc.h"
+#include "qom/object.h"
+#include "hw/qdev.h"
+#include "qapi/visitor.h"
+#include "qemu/error-report.h"
+
+/* #define DEBUG_SPAPR_DRC */
+
+#ifdef DEBUG_SPAPR_DRC
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#define DPRINTFN(fmt, ...) \
+ do { DPRINTF(fmt, ## __VA_ARGS__); fprintf(stderr, "\n"); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#define DPRINTFN(fmt, ...) \
+ do { } while (0)
+#endif
+
+#define DRC_CONTAINER_PATH "/dr-connector"
+#define DRC_INDEX_TYPE_SHIFT 28
+#define DRC_INDEX_ID_MASK (~(~0 << DRC_INDEX_TYPE_SHIFT))
+
+static sPAPRDRConnectorTypeShift get_type_shift(sPAPRDRConnectorType type)
+{
+ uint32_t shift = 0;
+
+ /* make sure this isn't SPAPR_DR_CONNECTOR_TYPE_ANY, or some
+ * other wonky value.
+ */
+ g_assert(is_power_of_2(type));
+
+ while (type != (1 << shift)) {
+ shift++;
+ }
+ return shift;
+}
+
+static uint32_t get_index(sPAPRDRConnector *drc)
+{
+ /* no set format for a drc index: it only needs to be globally
+ * unique. this is how we encode the DRC type on bare-metal
+ * however, so might as well do that here
+ */
+ return (get_type_shift(drc->type) << DRC_INDEX_TYPE_SHIFT) |
+ (drc->id & DRC_INDEX_ID_MASK);
+}
+
+static int set_isolation_state(sPAPRDRConnector *drc,
+ sPAPRDRIsolationState state)
+{
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+
+ DPRINTFN("drc: %x, set_isolation_state: %x", get_index(drc), state);
+
+ drc->isolation_state = state;
+
+ if (drc->isolation_state == SPAPR_DR_ISOLATION_STATE_ISOLATED) {
+ /* if we're awaiting release, but still in an unconfigured state,
+ * it's likely the guest is still in the process of configuring
+ * the device and is transitioning the devices to an ISOLATED
+ * state as a part of that process. so we only complete the
+ * removal when this transition happens for a device in a
+ * configured state, as suggested by the state diagram from
+ * PAPR+ 2.7, 13.4
+ */
+ if (drc->awaiting_release) {
+ if (drc->configured) {
+ DPRINTFN("finalizing device removal");
+ drck->detach(drc, DEVICE(drc->dev), drc->detach_cb,
+ drc->detach_cb_opaque, NULL);
+ } else {
+ DPRINTFN("deferring device removal on unconfigured device\n");
+ }
+ }
+ drc->configured = false;
+ }
+
+ return 0;
+}
+
+static int set_indicator_state(sPAPRDRConnector *drc,
+ sPAPRDRIndicatorState state)
+{
+ DPRINTFN("drc: %x, set_indicator_state: %x", get_index(drc), state);
+ drc->indicator_state = state;
+ return 0;
+}
+
+static int set_allocation_state(sPAPRDRConnector *drc,
+ sPAPRDRAllocationState state)
+{
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+
+ DPRINTFN("drc: %x, set_allocation_state: %x", get_index(drc), state);
+
+ if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI) {
+ drc->allocation_state = state;
+ if (drc->awaiting_release &&
+ drc->allocation_state == SPAPR_DR_ALLOCATION_STATE_UNUSABLE) {
+ DPRINTFN("finalizing device removal");
+ drck->detach(drc, DEVICE(drc->dev), drc->detach_cb,
+ drc->detach_cb_opaque, NULL);
+ }
+ }
+ return 0;
+}
+
+static uint32_t get_type(sPAPRDRConnector *drc)
+{
+ return drc->type;
+}
+
+static const char *get_name(sPAPRDRConnector *drc)
+{
+ return drc->name;
+}
+
+static const void *get_fdt(sPAPRDRConnector *drc, int *fdt_start_offset)
+{
+ if (fdt_start_offset) {
+ *fdt_start_offset = drc->fdt_start_offset;
+ }
+ return drc->fdt;
+}
+
+static void set_configured(sPAPRDRConnector *drc)
+{
+ DPRINTFN("drc: %x, set_configured", get_index(drc));
+
+ if (drc->isolation_state != SPAPR_DR_ISOLATION_STATE_UNISOLATED) {
+ /* guest should be not configuring an isolated device */
+ DPRINTFN("drc: %x, set_configured: skipping isolated device",
+ get_index(drc));
+ return;
+ }
+ drc->configured = true;
+}
+
+/*
+ * dr-entity-sense sensor value
+ * returned via get-sensor-state RTAS calls
+ * as expected by state diagram in PAPR+ 2.7, 13.4
+ * based on the current allocation/indicator/power states
+ * for the DR connector.
+ */
+static sPAPRDREntitySense entity_sense(sPAPRDRConnector *drc)
+{
+ sPAPRDREntitySense state;
+
+ if (drc->dev) {
+ if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI &&
+ drc->allocation_state == SPAPR_DR_ALLOCATION_STATE_UNUSABLE) {
+ /* for logical DR, we return a state of UNUSABLE
+ * iff the allocation state UNUSABLE.
+ * Otherwise, report the state as USABLE/PRESENT,
+ * as we would for PCI.
+ */
+ state = SPAPR_DR_ENTITY_SENSE_UNUSABLE;
+ } else {
+ /* this assumes all PCI devices are assigned to
+ * a 'live insertion' power domain, where QEMU
+ * manages power state automatically as opposed
+ * to the guest. present, non-PCI resources are
+ * unaffected by power state.
+ */
+ state = SPAPR_DR_ENTITY_SENSE_PRESENT;
+ }
+ } else {
+ if (drc->type == SPAPR_DR_CONNECTOR_TYPE_PCI) {
+ /* PCI devices, and only PCI devices, use EMPTY
+ * in cases where we'd otherwise use UNUSABLE
+ */
+ state = SPAPR_DR_ENTITY_SENSE_EMPTY;
+ } else {
+ state = SPAPR_DR_ENTITY_SENSE_UNUSABLE;
+ }
+ }
+
+ DPRINTFN("drc: %x, entity_sense: %x", get_index(drc), state);
+ return state;
+}
+
+static void prop_get_index(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(obj);
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ uint32_t value = (uint32_t)drck->get_index(drc);
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static void prop_get_type(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(obj);
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ uint32_t value = (uint32_t)drck->get_type(drc);
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static char *prop_get_name(Object *obj, Error **errp)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(obj);
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ return g_strdup(drck->get_name(drc));
+}
+
+static void prop_get_entity_sense(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(obj);
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ uint32_t value = (uint32_t)drck->entity_sense(drc);
+ visit_type_uint32(v, &value, name, errp);
+}
+
+static void prop_get_fdt(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(obj);
+ int fdt_offset_next, fdt_offset, fdt_depth;
+ void *fdt;
+
+ if (!drc->fdt) {
+ return;
+ }
+
+ fdt = drc->fdt;
+ fdt_offset = drc->fdt_start_offset;
+ fdt_depth = 0;
+
+ do {
+ const char *name = NULL;
+ const struct fdt_property *prop = NULL;
+ int prop_len = 0, name_len = 0;
+ uint32_t tag;
+
+ tag = fdt_next_tag(fdt, fdt_offset, &fdt_offset_next);
+ switch (tag) {
+ case FDT_BEGIN_NODE:
+ fdt_depth++;
+ name = fdt_get_name(fdt, fdt_offset, &name_len);
+ visit_start_struct(v, NULL, NULL, name, 0, NULL);
+ break;
+ case FDT_END_NODE:
+ /* shouldn't ever see an FDT_END_NODE before FDT_BEGIN_NODE */
+ g_assert(fdt_depth > 0);
+ visit_end_struct(v, NULL);
+ fdt_depth--;
+ break;
+ case FDT_PROP: {
+ int i;
+ prop = fdt_get_property_by_offset(fdt, fdt_offset, &prop_len);
+ name = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
+ visit_start_list(v, name, NULL);
+ for (i = 0; i < prop_len; i++) {
+ visit_type_uint8(v, (uint8_t *)&prop->data[i], NULL, NULL);
+
+ }
+ visit_end_list(v, NULL);
+ break;
+ }
+ default:
+ error_setg(&error_abort, "device FDT in unexpected state: %d", tag);
+ }
+ fdt_offset = fdt_offset_next;
+ } while (fdt_depth != 0);
+}
+
+static void attach(sPAPRDRConnector *drc, DeviceState *d, void *fdt,
+ int fdt_start_offset, bool coldplug, Error **errp)
+{
+ DPRINTFN("drc: %x, attach", get_index(drc));
+
+ if (drc->isolation_state != SPAPR_DR_ISOLATION_STATE_ISOLATED) {
+ error_setg(errp, "an attached device is still awaiting release");
+ return;
+ }
+ if (drc->type == SPAPR_DR_CONNECTOR_TYPE_PCI) {
+ g_assert(drc->allocation_state == SPAPR_DR_ALLOCATION_STATE_USABLE);
+ }
+ g_assert(fdt || coldplug);
+
+ /* NOTE: setting initial isolation state to UNISOLATED means we can't
+ * detach unless guest has a userspace/kernel that moves this state
+ * back to ISOLATED in response to an unplug event, or this is done
+ * manually by the admin prior. if we force things while the guest
+ * may be accessing the device, we can easily crash the guest, so we
+ * we defer completion of removal in such cases to the reset() hook.
+ */
+ if (drc->type == SPAPR_DR_CONNECTOR_TYPE_PCI) {
+ drc->isolation_state = SPAPR_DR_ISOLATION_STATE_UNISOLATED;
+ }
+ drc->indicator_state = SPAPR_DR_INDICATOR_STATE_ACTIVE;
+
+ drc->dev = d;
+ drc->fdt = fdt;
+ drc->fdt_start_offset = fdt_start_offset;
+ drc->configured = false;
+
+ object_property_add_link(OBJECT(drc), "device",
+ object_get_typename(OBJECT(drc->dev)),
+ (Object **)(&drc->dev),
+ NULL, 0, NULL);
+}
+
+static void detach(sPAPRDRConnector *drc, DeviceState *d,
+ spapr_drc_detach_cb *detach_cb,
+ void *detach_cb_opaque, Error **errp)
+{
+ DPRINTFN("drc: %x, detach", get_index(drc));
+
+ drc->detach_cb = detach_cb;
+ drc->detach_cb_opaque = detach_cb_opaque;
+
+ if (drc->isolation_state != SPAPR_DR_ISOLATION_STATE_ISOLATED) {
+ DPRINTFN("awaiting transition to isolated state before removal");
+ drc->awaiting_release = true;
+ return;
+ }
+
+ if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI &&
+ drc->allocation_state != SPAPR_DR_ALLOCATION_STATE_UNUSABLE) {
+ DPRINTFN("awaiting transition to unusable state before removal");
+ drc->awaiting_release = true;
+ return;
+ }
+
+ drc->indicator_state = SPAPR_DR_INDICATOR_STATE_INACTIVE;
+
+ if (drc->detach_cb) {
+ drc->detach_cb(drc->dev, drc->detach_cb_opaque);
+ }
+
+ drc->awaiting_release = false;
+ g_free(drc->fdt);
+ drc->fdt = NULL;
+ drc->fdt_start_offset = 0;
+ object_property_del(OBJECT(drc), "device", NULL);
+ drc->dev = NULL;
+ drc->detach_cb = NULL;
+ drc->detach_cb_opaque = NULL;
+}
+
+static bool release_pending(sPAPRDRConnector *drc)
+{
+ return drc->awaiting_release;
+}
+
+static void reset(DeviceState *d)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(d);
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+
+ DPRINTFN("drc reset: %x", drck->get_index(drc));
+ /* immediately upon reset we can safely assume DRCs whose devices
+ * are pending removal can be safely removed, and that they will
+ * subsequently be left in an ISOLATED state. move the DRC to this
+ * state in these cases (which will in turn complete any pending
+ * device removals)
+ */
+ if (drc->awaiting_release) {
+ drck->set_isolation_state(drc, SPAPR_DR_ISOLATION_STATE_ISOLATED);
+ /* generally this should also finalize the removal, but if the device
+ * hasn't yet been configured we normally defer removal under the
+ * assumption that this transition is taking place as part of device
+ * configuration. so check if we're still waiting after this, and
+ * force removal if we are
+ */
+ if (drc->awaiting_release) {
+ drck->detach(drc, DEVICE(drc->dev), drc->detach_cb,
+ drc->detach_cb_opaque, NULL);
+ }
+
+ /* non-PCI devices may be awaiting a transition to UNUSABLE */
+ if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI &&
+ drc->awaiting_release) {
+ drck->set_allocation_state(drc, SPAPR_DR_ALLOCATION_STATE_UNUSABLE);
+ }
+ }
+}
+
+static void realize(DeviceState *d, Error **errp)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(d);
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ Object *root_container;
+ char link_name[256];
+ gchar *child_name;
+ Error *err = NULL;
+
+ DPRINTFN("drc realize: %x", drck->get_index(drc));
+ /* NOTE: we do this as part of realize/unrealize due to the fact
+ * that the guest will communicate with the DRC via RTAS calls
+ * referencing the global DRC index. By unlinking the DRC
+ * from DRC_CONTAINER_PATH/<drc_index> we effectively make it
+ * inaccessible by the guest, since lookups rely on this path
+ * existing in the composition tree
+ */
+ root_container = container_get(object_get_root(), DRC_CONTAINER_PATH);
+ snprintf(link_name, sizeof(link_name), "%x", drck->get_index(drc));
+ child_name = object_get_canonical_path_component(OBJECT(drc));
+ DPRINTFN("drc child name: %s", child_name);
+ object_property_add_alias(root_container, link_name,
+ drc->owner, child_name, &err);
+ if (err) {
+ error_report("%s", error_get_pretty(err));
+ error_free(err);
+ object_unref(OBJECT(drc));
+ }
+ g_free(child_name);
+ DPRINTFN("drc realize complete");
+}
+
+static void unrealize(DeviceState *d, Error **errp)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(d);
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ Object *root_container;
+ char name[256];
+ Error *err = NULL;
+
+ DPRINTFN("drc unrealize: %x", drck->get_index(drc));
+ root_container = container_get(object_get_root(), DRC_CONTAINER_PATH);
+ snprintf(name, sizeof(name), "%x", drck->get_index(drc));
+ object_property_del(root_container, name, &err);
+ if (err) {
+ error_report("%s", error_get_pretty(err));
+ error_free(err);
+ object_unref(OBJECT(drc));
+ }
+}
+
+sPAPRDRConnector *spapr_dr_connector_new(Object *owner,
+ sPAPRDRConnectorType type,
+ uint32_t id)
+{
+ sPAPRDRConnector *drc =
+ SPAPR_DR_CONNECTOR(object_new(TYPE_SPAPR_DR_CONNECTOR));
+
+ g_assert(type);
+
+ drc->type = type;
+ drc->id = id;
+ drc->owner = owner;
+ object_property_add_child(owner, "dr-connector[*]", OBJECT(drc), NULL);
+ object_property_set_bool(OBJECT(drc), true, "realized", NULL);
+
+ /* human-readable name for a DRC to encode into the DT
+ * description. this is mainly only used within a guest in place
+ * of the unique DRC index.
+ *
+ * in the case of VIO/PCI devices, it corresponds to a
+ * "location code" that maps a logical device/function (DRC index)
+ * to a physical (or virtual in the case of VIO) location in the
+ * system by chaining together the "location label" for each
+ * encapsulating component.
+ *
+ * since this is more to do with diagnosing physical hardware
+ * issues than guest compatibility, we choose location codes/DRC
+ * names that adhere to the documented format, but avoid encoding
+ * the entire topology information into the label/code, instead
+ * just using the location codes based on the labels for the
+ * endpoints (VIO/PCI adaptor connectors), which is basically
+ * just "C" followed by an integer ID.
+ *
+ * DRC names as documented by PAPR+ v2.7, 13.5.2.4
+ * location codes as documented by PAPR+ v2.7, 12.3.1.5
+ */
+ switch (drc->type) {
+ case SPAPR_DR_CONNECTOR_TYPE_CPU:
+ drc->name = g_strdup_printf("CPU %d", id);
+ break;
+ case SPAPR_DR_CONNECTOR_TYPE_PHB:
+ drc->name = g_strdup_printf("PHB %d", id);
+ break;
+ case SPAPR_DR_CONNECTOR_TYPE_VIO:
+ case SPAPR_DR_CONNECTOR_TYPE_PCI:
+ drc->name = g_strdup_printf("C%d", id);
+ break;
+ case SPAPR_DR_CONNECTOR_TYPE_LMB:
+ drc->name = g_strdup_printf("LMB %d", id);
+ break;
+ default:
+ g_assert(false);
+ }
+
+ /* PCI slot always start in a USABLE state, and stay there */
+ if (drc->type == SPAPR_DR_CONNECTOR_TYPE_PCI) {
+ drc->allocation_state = SPAPR_DR_ALLOCATION_STATE_USABLE;
+ }
+
+ return drc;
+}
+
+static void spapr_dr_connector_instance_init(Object *obj)
+{
+ sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(obj);
+
+ object_property_add_uint32_ptr(obj, "isolation-state",
+ &drc->isolation_state, NULL);
+ object_property_add_uint32_ptr(obj, "indicator-state",
+ &drc->indicator_state, NULL);
+ object_property_add_uint32_ptr(obj, "allocation-state",
+ &drc->allocation_state, NULL);
+ object_property_add_uint32_ptr(obj, "id", &drc->id, NULL);
+ object_property_add(obj, "index", "uint32", prop_get_index,
+ NULL, NULL, NULL, NULL);
+ object_property_add(obj, "connector_type", "uint32", prop_get_type,
+ NULL, NULL, NULL, NULL);
+ object_property_add_str(obj, "name", prop_get_name, NULL, NULL);
+ object_property_add(obj, "entity-sense", "uint32", prop_get_entity_sense,
+ NULL, NULL, NULL, NULL);
+ object_property_add(obj, "fdt", "struct", prop_get_fdt,
+ NULL, NULL, NULL, NULL);
+}
+
+static void spapr_dr_connector_class_init(ObjectClass *k, void *data)
+{
+ DeviceClass *dk = DEVICE_CLASS(k);
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_CLASS(k);
+
+ dk->reset = reset;
+ dk->realize = realize;
+ dk->unrealize = unrealize;
+ drck->set_isolation_state = set_isolation_state;
+ drck->set_indicator_state = set_indicator_state;
+ drck->set_allocation_state = set_allocation_state;
+ drck->get_index = get_index;
+ drck->get_type = get_type;
+ drck->get_name = get_name;
+ drck->get_fdt = get_fdt;
+ drck->set_configured = set_configured;
+ drck->entity_sense = entity_sense;
+ drck->attach = attach;
+ drck->detach = detach;
+ drck->release_pending = release_pending;
+}
+
+static const TypeInfo spapr_dr_connector_info = {
+ .name = TYPE_SPAPR_DR_CONNECTOR,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(sPAPRDRConnector),
+ .instance_init = spapr_dr_connector_instance_init,
+ .class_size = sizeof(sPAPRDRConnectorClass),
+ .class_init = spapr_dr_connector_class_init,
+};
+
+static void spapr_drc_register_types(void)
+{
+ type_register_static(&spapr_dr_connector_info);
+}
+
+type_init(spapr_drc_register_types)
+
+/* helper functions for external users */
+
+sPAPRDRConnector *spapr_dr_connector_by_index(uint32_t index)
+{
+ Object *obj;
+ char name[256];
+
+ snprintf(name, sizeof(name), "%s/%x", DRC_CONTAINER_PATH, index);
+ obj = object_resolve_path(name, NULL);
+
+ return !obj ? NULL : SPAPR_DR_CONNECTOR(obj);
+}
+
+sPAPRDRConnector *spapr_dr_connector_by_id(sPAPRDRConnectorType type,
+ uint32_t id)
+{
+ return spapr_dr_connector_by_index(
+ (get_type_shift(type) << DRC_INDEX_TYPE_SHIFT) |
+ (id & DRC_INDEX_ID_MASK));
+}
+
+/* generate a string the describes the DRC to encode into the
+ * device tree.
+ *
+ * as documented by PAPR+ v2.7, 13.5.2.6 and C.6.1
+ */
+static const char *spapr_drc_get_type_str(sPAPRDRConnectorType type)
+{
+ switch (type) {
+ case SPAPR_DR_CONNECTOR_TYPE_CPU:
+ return "CPU";
+ case SPAPR_DR_CONNECTOR_TYPE_PHB:
+ return "PHB";
+ case SPAPR_DR_CONNECTOR_TYPE_VIO:
+ return "SLOT";
+ case SPAPR_DR_CONNECTOR_TYPE_PCI:
+ return "28";
+ case SPAPR_DR_CONNECTOR_TYPE_LMB:
+ return "MEM";
+ default:
+ g_assert(false);
+ }
+
+ return NULL;
+}
+
+/**
+ * spapr_drc_populate_dt
+ *
+ * @fdt: libfdt device tree
+ * @path: path in the DT to generate properties
+ * @owner: parent Object/DeviceState for which to generate DRC
+ * descriptions for
+ * @drc_type_mask: mask of sPAPRDRConnectorType values corresponding
+ * to the types of DRCs to generate entries for
+ *
+ * generate OF properties to describe DRC topology/indices to guests
+ *
+ * as documented in PAPR+ v2.1, 13.5.2
+ */
+int spapr_drc_populate_dt(void *fdt, int fdt_offset, Object *owner,
+ uint32_t drc_type_mask)
+{
+ Object *root_container;
+ ObjectProperty *prop;
+ uint32_t drc_count = 0;
+ GArray *drc_indexes, *drc_power_domains;
+ GString *drc_names, *drc_types;
+ int ret;
+
+ /* the first entry of each properties is a 32-bit integer encoding
+ * the number of elements in the array. we won't know this until
+ * we complete the iteration through all the matching DRCs, but
+ * reserve the space now and set the offsets accordingly so we
+ * can fill them in later.
+ */
+ drc_indexes = g_array_new(false, true, sizeof(uint32_t));
+ drc_indexes = g_array_set_size(drc_indexes, 1);
+ drc_power_domains = g_array_new(false, true, sizeof(uint32_t));
+ drc_power_domains = g_array_set_size(drc_power_domains, 1);
+ drc_names = g_string_set_size(g_string_new(NULL), sizeof(uint32_t));
+ drc_types = g_string_set_size(g_string_new(NULL), sizeof(uint32_t));
+
+ /* aliases for all DRConnector objects will be rooted in QOM
+ * composition tree at DRC_CONTAINER_PATH
+ */
+ root_container = container_get(object_get_root(), DRC_CONTAINER_PATH);
+
+ QTAILQ_FOREACH(prop, &root_container->properties, node) {
+ Object *obj;
+ sPAPRDRConnector *drc;
+ sPAPRDRConnectorClass *drck;
+ uint32_t drc_index, drc_power_domain;
+
+ if (!strstart(prop->type, "link<", NULL)) {
+ continue;
+ }
+
+ obj = object_property_get_link(root_container, prop->name, NULL);
+ drc = SPAPR_DR_CONNECTOR(obj);
+ drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+
+ if (owner && (drc->owner != owner)) {
+ continue;
+ }
+
+ if ((drc->type & drc_type_mask) == 0) {
+ continue;
+ }
+
+ drc_count++;
+
+ /* ibm,drc-indexes */
+ drc_index = cpu_to_be32(drck->get_index(drc));
+ g_array_append_val(drc_indexes, drc_index);
+
+ /* ibm,drc-power-domains */
+ drc_power_domain = cpu_to_be32(-1);
+ g_array_append_val(drc_power_domains, drc_power_domain);
+
+ /* ibm,drc-names */
+ drc_names = g_string_append(drc_names, drck->get_name(drc));
+ drc_names = g_string_insert_len(drc_names, -1, "\0", 1);
+
+ /* ibm,drc-types */
+ drc_types = g_string_append(drc_types,
+ spapr_drc_get_type_str(drc->type));
+ drc_types = g_string_insert_len(drc_types, -1, "\0", 1);
+ }
+
+ /* now write the drc count into the space we reserved at the
+ * beginning of the arrays previously
+ */
+ *(uint32_t *)drc_indexes->data = cpu_to_be32(drc_count);
+ *(uint32_t *)drc_power_domains->data = cpu_to_be32(drc_count);
+ *(uint32_t *)drc_names->str = cpu_to_be32(drc_count);
+ *(uint32_t *)drc_types->str = cpu_to_be32(drc_count);
+
+ ret = fdt_setprop(fdt, fdt_offset, "ibm,drc-indexes",
+ drc_indexes->data,
+ drc_indexes->len * sizeof(uint32_t));
+ if (ret) {
+ fprintf(stderr, "Couldn't create ibm,drc-indexes property\n");
+ goto out;
+ }
+
+ ret = fdt_setprop(fdt, fdt_offset, "ibm,drc-power-domains",
+ drc_power_domains->data,
+ drc_power_domains->len * sizeof(uint32_t));
+ if (ret) {
+ fprintf(stderr, "Couldn't finalize ibm,drc-power-domains property\n");
+ goto out;
+ }
+
+ ret = fdt_setprop(fdt, fdt_offset, "ibm,drc-names",
+ drc_names->str, drc_names->len);
+ if (ret) {
+ fprintf(stderr, "Couldn't finalize ibm,drc-names property\n");
+ goto out;
+ }
+
+ ret = fdt_setprop(fdt, fdt_offset, "ibm,drc-types",
+ drc_types->str, drc_types->len);
+ if (ret) {
+ fprintf(stderr, "Couldn't finalize ibm,drc-types property\n");
+ goto out;
+ }
+
+out:
+ g_array_free(drc_indexes, true);
+ g_array_free(drc_power_domains, true);
+ g_string_free(drc_names, true);
+ g_string_free(drc_types, true);
+
+ return ret;
+}
diff --git a/hw/ppc/spapr_events.c b/hw/ppc/spapr_events.c
new file mode 100644
index 00000000..f626eb7b
--- /dev/null
+++ b/hw/ppc/spapr_events.c
@@ -0,0 +1,565 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * RTAS events handling
+ *
+ * Copyright (c) 2012 David Gibson, IBM Corporation.
+ *
+ * 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.
+ *
+ */
+#include "cpu.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/char.h"
+#include "hw/qdev.h"
+#include "sysemu/device_tree.h"
+
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "hw/pci/pci.h"
+#include "hw/pci-host/spapr.h"
+#include "hw/ppc/spapr_drc.h"
+
+#include <libfdt.h>
+
+struct rtas_error_log {
+ uint32_t summary;
+#define RTAS_LOG_VERSION_MASK 0xff000000
+#define RTAS_LOG_VERSION_6 0x06000000
+#define RTAS_LOG_SEVERITY_MASK 0x00e00000
+#define RTAS_LOG_SEVERITY_ALREADY_REPORTED 0x00c00000
+#define RTAS_LOG_SEVERITY_FATAL 0x00a00000
+#define RTAS_LOG_SEVERITY_ERROR 0x00800000
+#define RTAS_LOG_SEVERITY_ERROR_SYNC 0x00600000
+#define RTAS_LOG_SEVERITY_WARNING 0x00400000
+#define RTAS_LOG_SEVERITY_EVENT 0x00200000
+#define RTAS_LOG_SEVERITY_NO_ERROR 0x00000000
+#define RTAS_LOG_DISPOSITION_MASK 0x00180000
+#define RTAS_LOG_DISPOSITION_FULLY_RECOVERED 0x00000000
+#define RTAS_LOG_DISPOSITION_LIMITED_RECOVERY 0x00080000
+#define RTAS_LOG_DISPOSITION_NOT_RECOVERED 0x00100000
+#define RTAS_LOG_OPTIONAL_PART_PRESENT 0x00040000
+#define RTAS_LOG_INITIATOR_MASK 0x0000f000
+#define RTAS_LOG_INITIATOR_UNKNOWN 0x00000000
+#define RTAS_LOG_INITIATOR_CPU 0x00001000
+#define RTAS_LOG_INITIATOR_PCI 0x00002000
+#define RTAS_LOG_INITIATOR_MEMORY 0x00004000
+#define RTAS_LOG_INITIATOR_HOTPLUG 0x00006000
+#define RTAS_LOG_TARGET_MASK 0x00000f00
+#define RTAS_LOG_TARGET_UNKNOWN 0x00000000
+#define RTAS_LOG_TARGET_CPU 0x00000100
+#define RTAS_LOG_TARGET_PCI 0x00000200
+#define RTAS_LOG_TARGET_MEMORY 0x00000400
+#define RTAS_LOG_TARGET_HOTPLUG 0x00000600
+#define RTAS_LOG_TYPE_MASK 0x000000ff
+#define RTAS_LOG_TYPE_OTHER 0x00000000
+#define RTAS_LOG_TYPE_RETRY 0x00000001
+#define RTAS_LOG_TYPE_TCE_ERR 0x00000002
+#define RTAS_LOG_TYPE_INTERN_DEV_FAIL 0x00000003
+#define RTAS_LOG_TYPE_TIMEOUT 0x00000004
+#define RTAS_LOG_TYPE_DATA_PARITY 0x00000005
+#define RTAS_LOG_TYPE_ADDR_PARITY 0x00000006
+#define RTAS_LOG_TYPE_CACHE_PARITY 0x00000007
+#define RTAS_LOG_TYPE_ADDR_INVALID 0x00000008
+#define RTAS_LOG_TYPE_ECC_UNCORR 0x00000009
+#define RTAS_LOG_TYPE_ECC_CORR 0x0000000a
+#define RTAS_LOG_TYPE_EPOW 0x00000040
+#define RTAS_LOG_TYPE_HOTPLUG 0x000000e5
+ uint32_t extended_length;
+} QEMU_PACKED;
+
+struct rtas_event_log_v6 {
+ uint8_t b0;
+#define RTAS_LOG_V6_B0_VALID 0x80
+#define RTAS_LOG_V6_B0_UNRECOVERABLE_ERROR 0x40
+#define RTAS_LOG_V6_B0_RECOVERABLE_ERROR 0x20
+#define RTAS_LOG_V6_B0_DEGRADED_OPERATION 0x10
+#define RTAS_LOG_V6_B0_PREDICTIVE_ERROR 0x08
+#define RTAS_LOG_V6_B0_NEW_LOG 0x04
+#define RTAS_LOG_V6_B0_BIGENDIAN 0x02
+ uint8_t _resv1;
+ uint8_t b2;
+#define RTAS_LOG_V6_B2_POWERPC_FORMAT 0x80
+#define RTAS_LOG_V6_B2_LOG_FORMAT_MASK 0x0f
+#define RTAS_LOG_V6_B2_LOG_FORMAT_PLATFORM_EVENT 0x0e
+ uint8_t _resv2[9];
+ uint32_t company;
+#define RTAS_LOG_V6_COMPANY_IBM 0x49424d00 /* IBM<null> */
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_section_header {
+ uint16_t section_id;
+ uint16_t section_length;
+ uint8_t section_version;
+ uint8_t section_subtype;
+ uint16_t creator_component_id;
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_maina {
+#define RTAS_LOG_V6_SECTION_ID_MAINA 0x5048 /* PH */
+ struct rtas_event_log_v6_section_header hdr;
+ uint32_t creation_date; /* BCD: YYYYMMDD */
+ uint32_t creation_time; /* BCD: HHMMSS00 */
+ uint8_t _platform1[8];
+ char creator_id;
+ uint8_t _resv1[2];
+ uint8_t section_count;
+ uint8_t _resv2[4];
+ uint8_t _platform2[8];
+ uint32_t plid;
+ uint8_t _platform3[4];
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_mainb {
+#define RTAS_LOG_V6_SECTION_ID_MAINB 0x5548 /* UH */
+ struct rtas_event_log_v6_section_header hdr;
+ uint8_t subsystem_id;
+ uint8_t _platform1;
+ uint8_t event_severity;
+ uint8_t event_subtype;
+ uint8_t _platform2[4];
+ uint8_t _resv1[2];
+ uint16_t action_flags;
+ uint8_t _resv2[4];
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_epow {
+#define RTAS_LOG_V6_SECTION_ID_EPOW 0x4550 /* EP */
+ struct rtas_event_log_v6_section_header hdr;
+ uint8_t sensor_value;
+#define RTAS_LOG_V6_EPOW_ACTION_RESET 0
+#define RTAS_LOG_V6_EPOW_ACTION_WARN_COOLING 1
+#define RTAS_LOG_V6_EPOW_ACTION_WARN_POWER 2
+#define RTAS_LOG_V6_EPOW_ACTION_SYSTEM_SHUTDOWN 3
+#define RTAS_LOG_V6_EPOW_ACTION_SYSTEM_HALT 4
+#define RTAS_LOG_V6_EPOW_ACTION_MAIN_ENCLOSURE 5
+#define RTAS_LOG_V6_EPOW_ACTION_POWER_OFF 7
+ uint8_t event_modifier;
+#define RTAS_LOG_V6_EPOW_MODIFIER_NORMAL 1
+#define RTAS_LOG_V6_EPOW_MODIFIER_ON_UPS 2
+#define RTAS_LOG_V6_EPOW_MODIFIER_CRITICAL 3
+#define RTAS_LOG_V6_EPOW_MODIFIER_TEMPERATURE 4
+ uint8_t extended_modifier;
+#define RTAS_LOG_V6_EPOW_XMODIFIER_SYSTEM_WIDE 0
+#define RTAS_LOG_V6_EPOW_XMODIFIER_PARTITION_SPECIFIC 1
+ uint8_t _resv;
+ uint64_t reason_code;
+} QEMU_PACKED;
+
+struct epow_log_full {
+ struct rtas_error_log hdr;
+ struct rtas_event_log_v6 v6hdr;
+ struct rtas_event_log_v6_maina maina;
+ struct rtas_event_log_v6_mainb mainb;
+ struct rtas_event_log_v6_epow epow;
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_hp {
+#define RTAS_LOG_V6_SECTION_ID_HOTPLUG 0x4850 /* HP */
+ struct rtas_event_log_v6_section_header hdr;
+ uint8_t hotplug_type;
+#define RTAS_LOG_V6_HP_TYPE_CPU 1
+#define RTAS_LOG_V6_HP_TYPE_MEMORY 2
+#define RTAS_LOG_V6_HP_TYPE_SLOT 3
+#define RTAS_LOG_V6_HP_TYPE_PHB 4
+#define RTAS_LOG_V6_HP_TYPE_PCI 5
+ uint8_t hotplug_action;
+#define RTAS_LOG_V6_HP_ACTION_ADD 1
+#define RTAS_LOG_V6_HP_ACTION_REMOVE 2
+ uint8_t hotplug_identifier;
+#define RTAS_LOG_V6_HP_ID_DRC_NAME 1
+#define RTAS_LOG_V6_HP_ID_DRC_INDEX 2
+#define RTAS_LOG_V6_HP_ID_DRC_COUNT 3
+ uint8_t reserved;
+ union {
+ uint32_t index;
+ uint32_t count;
+ char name[1];
+ } drc;
+} QEMU_PACKED;
+
+struct hp_log_full {
+ struct rtas_error_log hdr;
+ struct rtas_event_log_v6 v6hdr;
+ struct rtas_event_log_v6_maina maina;
+ struct rtas_event_log_v6_mainb mainb;
+ struct rtas_event_log_v6_hp hp;
+} QEMU_PACKED;
+
+#define EVENT_MASK_INTERNAL_ERRORS 0x80000000
+#define EVENT_MASK_EPOW 0x40000000
+#define EVENT_MASK_HOTPLUG 0x10000000
+#define EVENT_MASK_IO 0x08000000
+
+#define _FDT(exp) \
+ do { \
+ int ret = (exp); \
+ if (ret < 0) { \
+ fprintf(stderr, "qemu: error creating device tree: %s: %s\n", \
+ #exp, fdt_strerror(ret)); \
+ exit(1); \
+ } \
+ } while (0)
+
+void spapr_events_fdt_skel(void *fdt, uint32_t check_exception_irq)
+{
+ uint32_t irq_ranges[] = {cpu_to_be32(check_exception_irq), cpu_to_be32(1)};
+ uint32_t interrupts[] = {cpu_to_be32(check_exception_irq), 0};
+
+ _FDT((fdt_begin_node(fdt, "event-sources")));
+
+ _FDT((fdt_property(fdt, "interrupt-controller", NULL, 0)));
+ _FDT((fdt_property_cell(fdt, "#interrupt-cells", 2)));
+ _FDT((fdt_property(fdt, "interrupt-ranges",
+ irq_ranges, sizeof(irq_ranges))));
+
+ _FDT((fdt_begin_node(fdt, "epow-events")));
+ _FDT((fdt_property(fdt, "interrupts", interrupts, sizeof(interrupts))));
+ _FDT((fdt_end_node(fdt)));
+
+ _FDT((fdt_end_node(fdt)));
+}
+
+static void rtas_event_log_queue(int log_type, void *data, bool exception)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ sPAPREventLogEntry *entry = g_new(sPAPREventLogEntry, 1);
+
+ g_assert(data);
+ entry->log_type = log_type;
+ entry->exception = exception;
+ entry->data = data;
+ QTAILQ_INSERT_TAIL(&spapr->pending_events, entry, next);
+}
+
+static sPAPREventLogEntry *rtas_event_log_dequeue(uint32_t event_mask,
+ bool exception)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ sPAPREventLogEntry *entry = NULL;
+
+ /* we only queue EPOW events atm. */
+ if ((event_mask & EVENT_MASK_EPOW) == 0) {
+ return NULL;
+ }
+
+ QTAILQ_FOREACH(entry, &spapr->pending_events, next) {
+ if (entry->exception != exception) {
+ continue;
+ }
+
+ /* EPOW and hotplug events are surfaced in the same manner */
+ if (entry->log_type == RTAS_LOG_TYPE_EPOW ||
+ entry->log_type == RTAS_LOG_TYPE_HOTPLUG) {
+ break;
+ }
+ }
+
+ if (entry) {
+ QTAILQ_REMOVE(&spapr->pending_events, entry, next);
+ }
+
+ return entry;
+}
+
+static bool rtas_event_log_contains(uint32_t event_mask, bool exception)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ sPAPREventLogEntry *entry = NULL;
+
+ /* we only queue EPOW events atm. */
+ if ((event_mask & EVENT_MASK_EPOW) == 0) {
+ return false;
+ }
+
+ QTAILQ_FOREACH(entry, &spapr->pending_events, next) {
+ if (entry->exception != exception) {
+ continue;
+ }
+
+ /* EPOW and hotplug events are surfaced in the same manner */
+ if (entry->log_type == RTAS_LOG_TYPE_EPOW ||
+ entry->log_type == RTAS_LOG_TYPE_HOTPLUG) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static uint32_t next_plid;
+
+static void spapr_init_v6hdr(struct rtas_event_log_v6 *v6hdr)
+{
+ v6hdr->b0 = RTAS_LOG_V6_B0_VALID | RTAS_LOG_V6_B0_NEW_LOG
+ | RTAS_LOG_V6_B0_BIGENDIAN;
+ v6hdr->b2 = RTAS_LOG_V6_B2_POWERPC_FORMAT
+ | RTAS_LOG_V6_B2_LOG_FORMAT_PLATFORM_EVENT;
+ v6hdr->company = cpu_to_be32(RTAS_LOG_V6_COMPANY_IBM);
+}
+
+static void spapr_init_maina(struct rtas_event_log_v6_maina *maina,
+ int section_count)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ struct tm tm;
+ int year;
+
+ maina->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_MAINA);
+ maina->hdr.section_length = cpu_to_be16(sizeof(*maina));
+ /* FIXME: section version, subtype and creator id? */
+ spapr_rtc_read(spapr->rtc, &tm, NULL);
+ year = tm.tm_year + 1900;
+ maina->creation_date = cpu_to_be32((to_bcd(year / 100) << 24)
+ | (to_bcd(year % 100) << 16)
+ | (to_bcd(tm.tm_mon + 1) << 8)
+ | to_bcd(tm.tm_mday));
+ maina->creation_time = cpu_to_be32((to_bcd(tm.tm_hour) << 24)
+ | (to_bcd(tm.tm_min) << 16)
+ | (to_bcd(tm.tm_sec) << 8));
+ maina->creator_id = 'H'; /* Hypervisor */
+ maina->section_count = section_count;
+ maina->plid = next_plid++;
+}
+
+static void spapr_powerdown_req(Notifier *n, void *opaque)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ struct rtas_error_log *hdr;
+ struct rtas_event_log_v6 *v6hdr;
+ struct rtas_event_log_v6_maina *maina;
+ struct rtas_event_log_v6_mainb *mainb;
+ struct rtas_event_log_v6_epow *epow;
+ struct epow_log_full *new_epow;
+
+ new_epow = g_malloc0(sizeof(*new_epow));
+ hdr = &new_epow->hdr;
+ v6hdr = &new_epow->v6hdr;
+ maina = &new_epow->maina;
+ mainb = &new_epow->mainb;
+ epow = &new_epow->epow;
+
+ hdr->summary = cpu_to_be32(RTAS_LOG_VERSION_6
+ | RTAS_LOG_SEVERITY_EVENT
+ | RTAS_LOG_DISPOSITION_NOT_RECOVERED
+ | RTAS_LOG_OPTIONAL_PART_PRESENT
+ | RTAS_LOG_TYPE_EPOW);
+ hdr->extended_length = cpu_to_be32(sizeof(*new_epow)
+ - sizeof(new_epow->hdr));
+
+ spapr_init_v6hdr(v6hdr);
+ spapr_init_maina(maina, 3 /* Main-A, Main-B and EPOW */);
+
+ mainb->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_MAINB);
+ mainb->hdr.section_length = cpu_to_be16(sizeof(*mainb));
+ /* FIXME: section version, subtype and creator id? */
+ mainb->subsystem_id = 0xa0; /* External environment */
+ mainb->event_severity = 0x00; /* Informational / non-error */
+ mainb->event_subtype = 0xd0; /* Normal shutdown */
+
+ epow->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_EPOW);
+ epow->hdr.section_length = cpu_to_be16(sizeof(*epow));
+ epow->hdr.section_version = 2; /* includes extended modifier */
+ /* FIXME: section subtype and creator id? */
+ epow->sensor_value = RTAS_LOG_V6_EPOW_ACTION_SYSTEM_SHUTDOWN;
+ epow->event_modifier = RTAS_LOG_V6_EPOW_MODIFIER_NORMAL;
+ epow->extended_modifier = RTAS_LOG_V6_EPOW_XMODIFIER_PARTITION_SPECIFIC;
+
+ rtas_event_log_queue(RTAS_LOG_TYPE_EPOW, new_epow, true);
+
+ qemu_irq_pulse(xics_get_qirq(spapr->icp, spapr->check_exception_irq));
+}
+
+static void spapr_hotplug_req_event(sPAPRDRConnector *drc, uint8_t hp_action)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ struct hp_log_full *new_hp;
+ struct rtas_error_log *hdr;
+ struct rtas_event_log_v6 *v6hdr;
+ struct rtas_event_log_v6_maina *maina;
+ struct rtas_event_log_v6_mainb *mainb;
+ struct rtas_event_log_v6_hp *hp;
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ sPAPRDRConnectorType drc_type = drck->get_type(drc);
+
+ new_hp = g_malloc0(sizeof(struct hp_log_full));
+ hdr = &new_hp->hdr;
+ v6hdr = &new_hp->v6hdr;
+ maina = &new_hp->maina;
+ mainb = &new_hp->mainb;
+ hp = &new_hp->hp;
+
+ hdr->summary = cpu_to_be32(RTAS_LOG_VERSION_6
+ | RTAS_LOG_SEVERITY_EVENT
+ | RTAS_LOG_DISPOSITION_NOT_RECOVERED
+ | RTAS_LOG_OPTIONAL_PART_PRESENT
+ | RTAS_LOG_INITIATOR_HOTPLUG
+ | RTAS_LOG_TYPE_HOTPLUG);
+ hdr->extended_length = cpu_to_be32(sizeof(*new_hp)
+ - sizeof(new_hp->hdr));
+
+ spapr_init_v6hdr(v6hdr);
+ spapr_init_maina(maina, 3 /* Main-A, Main-B, HP */);
+
+ mainb->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_MAINB);
+ mainb->hdr.section_length = cpu_to_be16(sizeof(*mainb));
+ mainb->subsystem_id = 0x80; /* External environment */
+ mainb->event_severity = 0x00; /* Informational / non-error */
+ mainb->event_subtype = 0x00; /* Normal shutdown */
+
+ hp->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_HOTPLUG);
+ hp->hdr.section_length = cpu_to_be16(sizeof(*hp));
+ hp->hdr.section_version = 1; /* includes extended modifier */
+ hp->hotplug_action = hp_action;
+
+
+ switch (drc_type) {
+ case SPAPR_DR_CONNECTOR_TYPE_PCI:
+ hp->drc.index = cpu_to_be32(drck->get_index(drc));
+ hp->hotplug_identifier = RTAS_LOG_V6_HP_ID_DRC_INDEX;
+ hp->hotplug_type = RTAS_LOG_V6_HP_TYPE_PCI;
+ break;
+ default:
+ /* we shouldn't be signaling hotplug events for resources
+ * that don't support them
+ */
+ g_assert(false);
+ return;
+ }
+
+ rtas_event_log_queue(RTAS_LOG_TYPE_HOTPLUG, new_hp, true);
+
+ qemu_irq_pulse(xics_get_qirq(spapr->icp, spapr->check_exception_irq));
+}
+
+void spapr_hotplug_req_add_event(sPAPRDRConnector *drc)
+{
+ spapr_hotplug_req_event(drc, RTAS_LOG_V6_HP_ACTION_ADD);
+}
+
+void spapr_hotplug_req_remove_event(sPAPRDRConnector *drc)
+{
+ spapr_hotplug_req_event(drc, RTAS_LOG_V6_HP_ACTION_REMOVE);
+}
+
+static void check_exception(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint32_t mask, buf, len, event_len;
+ uint64_t xinfo;
+ sPAPREventLogEntry *event;
+ struct rtas_error_log *hdr;
+
+ if ((nargs < 6) || (nargs > 7) || nret != 1) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ xinfo = rtas_ld(args, 1);
+ mask = rtas_ld(args, 2);
+ buf = rtas_ld(args, 4);
+ len = rtas_ld(args, 5);
+ if (nargs == 7) {
+ xinfo |= (uint64_t)rtas_ld(args, 6) << 32;
+ }
+
+ event = rtas_event_log_dequeue(mask, true);
+ if (!event) {
+ goto out_no_events;
+ }
+
+ hdr = event->data;
+ event_len = be32_to_cpu(hdr->extended_length) + sizeof(*hdr);
+
+ if (event_len < len) {
+ len = event_len;
+ }
+
+ cpu_physical_memory_write(buf, event->data, len);
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ g_free(event->data);
+ g_free(event);
+
+ /* according to PAPR+, the IRQ must be left asserted, or re-asserted, if
+ * there are still pending events to be fetched via check-exception. We
+ * do the latter here, since our code relies on edge-triggered
+ * interrupts.
+ */
+ if (rtas_event_log_contains(mask, true)) {
+ qemu_irq_pulse(xics_get_qirq(spapr->icp, spapr->check_exception_irq));
+ }
+
+ return;
+
+out_no_events:
+ rtas_st(rets, 0, RTAS_OUT_NO_ERRORS_FOUND);
+}
+
+static void event_scan(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint32_t mask, buf, len, event_len;
+ sPAPREventLogEntry *event;
+ struct rtas_error_log *hdr;
+
+ if (nargs != 4 || nret != 1) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ mask = rtas_ld(args, 0);
+ buf = rtas_ld(args, 2);
+ len = rtas_ld(args, 3);
+
+ event = rtas_event_log_dequeue(mask, false);
+ if (!event) {
+ goto out_no_events;
+ }
+
+ hdr = event->data;
+ event_len = be32_to_cpu(hdr->extended_length) + sizeof(*hdr);
+
+ if (event_len < len) {
+ len = event_len;
+ }
+
+ cpu_physical_memory_write(buf, event->data, len);
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ g_free(event->data);
+ g_free(event);
+ return;
+
+out_no_events:
+ rtas_st(rets, 0, RTAS_OUT_NO_ERRORS_FOUND);
+}
+
+void spapr_events_init(sPAPRMachineState *spapr)
+{
+ QTAILQ_INIT(&spapr->pending_events);
+ spapr->check_exception_irq = xics_alloc(spapr->icp, 0, 0, false);
+ spapr->epow_notifier.notify = spapr_powerdown_req;
+ qemu_register_powerdown_notifier(&spapr->epow_notifier);
+ spapr_rtas_register(RTAS_CHECK_EXCEPTION, "check-exception",
+ check_exception);
+ spapr_rtas_register(RTAS_EVENT_SCAN, "event-scan", event_scan);
+}
diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c
new file mode 100644
index 00000000..652ddf6e
--- /dev/null
+++ b/hw/ppc/spapr_hcall.c
@@ -0,0 +1,1018 @@
+#include "sysemu/sysemu.h"
+#include "cpu.h"
+#include "helper_regs.h"
+#include "hw/ppc/spapr.h"
+#include "mmu-hash64.h"
+#include "cpu-models.h"
+#include "trace.h"
+#include "kvm_ppc.h"
+
+struct SPRSyncState {
+ CPUState *cs;
+ int spr;
+ target_ulong value;
+ target_ulong mask;
+};
+
+static void do_spr_sync(void *arg)
+{
+ struct SPRSyncState *s = arg;
+ PowerPCCPU *cpu = POWERPC_CPU(s->cs);
+ CPUPPCState *env = &cpu->env;
+
+ cpu_synchronize_state(s->cs);
+ env->spr[s->spr] &= ~s->mask;
+ env->spr[s->spr] |= s->value;
+}
+
+static void set_spr(CPUState *cs, int spr, target_ulong value,
+ target_ulong mask)
+{
+ struct SPRSyncState s = {
+ .cs = cs,
+ .spr = spr,
+ .value = value,
+ .mask = mask
+ };
+ run_on_cpu(cs, do_spr_sync, &s);
+}
+
+static target_ulong compute_tlbie_rb(target_ulong v, target_ulong r,
+ target_ulong pte_index)
+{
+ target_ulong rb, va_low;
+
+ rb = (v & ~0x7fULL) << 16; /* AVA field */
+ va_low = pte_index >> 3;
+ if (v & HPTE64_V_SECONDARY) {
+ va_low = ~va_low;
+ }
+ /* xor vsid from AVA */
+ if (!(v & HPTE64_V_1TB_SEG)) {
+ va_low ^= v >> 12;
+ } else {
+ va_low ^= v >> 24;
+ }
+ va_low &= 0x7ff;
+ if (v & HPTE64_V_LARGE) {
+ rb |= 1; /* L field */
+#if 0 /* Disable that P7 specific bit for now */
+ if (r & 0xff000) {
+ /* non-16MB large page, must be 64k */
+ /* (masks depend on page size) */
+ rb |= 0x1000; /* page encoding in LP field */
+ rb |= (va_low & 0x7f) << 16; /* 7b of VA in AVA/LP field */
+ rb |= (va_low & 0xfe); /* AVAL field */
+ }
+#endif
+ } else {
+ /* 4kB page */
+ rb |= (va_low & 0x7ff) << 12; /* remaining 11b of AVA */
+ }
+ rb |= (v >> 54) & 0x300; /* B field */
+ return rb;
+}
+
+static inline bool valid_pte_index(CPUPPCState *env, target_ulong pte_index)
+{
+ /*
+ * hash value/pteg group index is normalized by htab_mask
+ */
+ if (((pte_index & ~7ULL) / HPTES_PER_GROUP) & ~env->htab_mask) {
+ return false;
+ }
+ return true;
+}
+
+static target_ulong h_enter(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ MachineState *machine = MACHINE(spapr);
+ CPUPPCState *env = &cpu->env;
+ target_ulong flags = args[0];
+ target_ulong pte_index = args[1];
+ target_ulong pteh = args[2];
+ target_ulong ptel = args[3];
+ target_ulong page_shift = 12;
+ target_ulong raddr;
+ target_ulong index;
+ uint64_t token;
+
+ /* only handle 4k and 16M pages for now */
+ if (pteh & HPTE64_V_LARGE) {
+#if 0 /* We don't support 64k pages yet */
+ if ((ptel & 0xf000) == 0x1000) {
+ /* 64k page */
+ } else
+#endif
+ if ((ptel & 0xff000) == 0) {
+ /* 16M page */
+ page_shift = 24;
+ /* lowest AVA bit must be 0 for 16M pages */
+ if (pteh & 0x80) {
+ return H_PARAMETER;
+ }
+ } else {
+ return H_PARAMETER;
+ }
+ }
+
+ raddr = (ptel & HPTE64_R_RPN) & ~((1ULL << page_shift) - 1);
+
+ if (raddr < machine->ram_size) {
+ /* Regular RAM - should have WIMG=0010 */
+ if ((ptel & HPTE64_R_WIMG) != HPTE64_R_M) {
+ return H_PARAMETER;
+ }
+ } else {
+ /* Looks like an IO address */
+ /* FIXME: What WIMG combinations could be sensible for IO?
+ * For now we allow WIMG=010x, but are there others? */
+ /* FIXME: Should we check against registered IO addresses? */
+ if ((ptel & (HPTE64_R_W | HPTE64_R_I | HPTE64_R_M)) != HPTE64_R_I) {
+ return H_PARAMETER;
+ }
+ }
+
+ pteh &= ~0x60ULL;
+
+ if (!valid_pte_index(env, pte_index)) {
+ return H_PARAMETER;
+ }
+
+ index = 0;
+ if (likely((flags & H_EXACT) == 0)) {
+ pte_index &= ~7ULL;
+ token = ppc_hash64_start_access(cpu, pte_index);
+ for (; index < 8; index++) {
+ if ((ppc_hash64_load_hpte0(env, token, index) & HPTE64_V_VALID) == 0) {
+ break;
+ }
+ }
+ ppc_hash64_stop_access(token);
+ if (index == 8) {
+ return H_PTEG_FULL;
+ }
+ } else {
+ token = ppc_hash64_start_access(cpu, pte_index);
+ if (ppc_hash64_load_hpte0(env, token, 0) & HPTE64_V_VALID) {
+ ppc_hash64_stop_access(token);
+ return H_PTEG_FULL;
+ }
+ ppc_hash64_stop_access(token);
+ }
+
+ ppc_hash64_store_hpte(env, pte_index + index,
+ pteh | HPTE64_V_HPTE_DIRTY, ptel);
+
+ args[0] = pte_index + index;
+ return H_SUCCESS;
+}
+
+typedef enum {
+ REMOVE_SUCCESS = 0,
+ REMOVE_NOT_FOUND = 1,
+ REMOVE_PARM = 2,
+ REMOVE_HW = 3,
+} RemoveResult;
+
+static RemoveResult remove_hpte(CPUPPCState *env, target_ulong ptex,
+ target_ulong avpn,
+ target_ulong flags,
+ target_ulong *vp, target_ulong *rp)
+{
+ uint64_t token;
+ target_ulong v, r, rb;
+
+ if (!valid_pte_index(env, ptex)) {
+ return REMOVE_PARM;
+ }
+
+ token = ppc_hash64_start_access(ppc_env_get_cpu(env), ptex);
+ v = ppc_hash64_load_hpte0(env, token, 0);
+ r = ppc_hash64_load_hpte1(env, token, 0);
+ ppc_hash64_stop_access(token);
+
+ if ((v & HPTE64_V_VALID) == 0 ||
+ ((flags & H_AVPN) && (v & ~0x7fULL) != avpn) ||
+ ((flags & H_ANDCOND) && (v & avpn) != 0)) {
+ return REMOVE_NOT_FOUND;
+ }
+ *vp = v;
+ *rp = r;
+ ppc_hash64_store_hpte(env, ptex, HPTE64_V_HPTE_DIRTY, 0);
+ rb = compute_tlbie_rb(v, r, ptex);
+ ppc_tlb_invalidate_one(env, rb);
+ return REMOVE_SUCCESS;
+}
+
+static target_ulong h_remove(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong flags = args[0];
+ target_ulong pte_index = args[1];
+ target_ulong avpn = args[2];
+ RemoveResult ret;
+
+ ret = remove_hpte(env, pte_index, avpn, flags,
+ &args[0], &args[1]);
+
+ switch (ret) {
+ case REMOVE_SUCCESS:
+ return H_SUCCESS;
+
+ case REMOVE_NOT_FOUND:
+ return H_NOT_FOUND;
+
+ case REMOVE_PARM:
+ return H_PARAMETER;
+
+ case REMOVE_HW:
+ return H_HARDWARE;
+ }
+
+ g_assert_not_reached();
+}
+
+#define H_BULK_REMOVE_TYPE 0xc000000000000000ULL
+#define H_BULK_REMOVE_REQUEST 0x4000000000000000ULL
+#define H_BULK_REMOVE_RESPONSE 0x8000000000000000ULL
+#define H_BULK_REMOVE_END 0xc000000000000000ULL
+#define H_BULK_REMOVE_CODE 0x3000000000000000ULL
+#define H_BULK_REMOVE_SUCCESS 0x0000000000000000ULL
+#define H_BULK_REMOVE_NOT_FOUND 0x1000000000000000ULL
+#define H_BULK_REMOVE_PARM 0x2000000000000000ULL
+#define H_BULK_REMOVE_HW 0x3000000000000000ULL
+#define H_BULK_REMOVE_RC 0x0c00000000000000ULL
+#define H_BULK_REMOVE_FLAGS 0x0300000000000000ULL
+#define H_BULK_REMOVE_ABSOLUTE 0x0000000000000000ULL
+#define H_BULK_REMOVE_ANDCOND 0x0100000000000000ULL
+#define H_BULK_REMOVE_AVPN 0x0200000000000000ULL
+#define H_BULK_REMOVE_PTEX 0x00ffffffffffffffULL
+
+#define H_BULK_REMOVE_MAX_BATCH 4
+
+static target_ulong h_bulk_remove(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ int i;
+
+ for (i = 0; i < H_BULK_REMOVE_MAX_BATCH; i++) {
+ target_ulong *tsh = &args[i*2];
+ target_ulong tsl = args[i*2 + 1];
+ target_ulong v, r, ret;
+
+ if ((*tsh & H_BULK_REMOVE_TYPE) == H_BULK_REMOVE_END) {
+ break;
+ } else if ((*tsh & H_BULK_REMOVE_TYPE) != H_BULK_REMOVE_REQUEST) {
+ return H_PARAMETER;
+ }
+
+ *tsh &= H_BULK_REMOVE_PTEX | H_BULK_REMOVE_FLAGS;
+ *tsh |= H_BULK_REMOVE_RESPONSE;
+
+ if ((*tsh & H_BULK_REMOVE_ANDCOND) && (*tsh & H_BULK_REMOVE_AVPN)) {
+ *tsh |= H_BULK_REMOVE_PARM;
+ return H_PARAMETER;
+ }
+
+ ret = remove_hpte(env, *tsh & H_BULK_REMOVE_PTEX, tsl,
+ (*tsh & H_BULK_REMOVE_FLAGS) >> 26,
+ &v, &r);
+
+ *tsh |= ret << 60;
+
+ switch (ret) {
+ case REMOVE_SUCCESS:
+ *tsh |= (r & (HPTE64_R_C | HPTE64_R_R)) << 43;
+ break;
+
+ case REMOVE_PARM:
+ return H_PARAMETER;
+
+ case REMOVE_HW:
+ return H_HARDWARE;
+ }
+ }
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_protect(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong flags = args[0];
+ target_ulong pte_index = args[1];
+ target_ulong avpn = args[2];
+ uint64_t token;
+ target_ulong v, r, rb;
+
+ if (!valid_pte_index(env, pte_index)) {
+ return H_PARAMETER;
+ }
+
+ token = ppc_hash64_start_access(cpu, pte_index);
+ v = ppc_hash64_load_hpte0(env, token, 0);
+ r = ppc_hash64_load_hpte1(env, token, 0);
+ ppc_hash64_stop_access(token);
+
+ if ((v & HPTE64_V_VALID) == 0 ||
+ ((flags & H_AVPN) && (v & ~0x7fULL) != avpn)) {
+ return H_NOT_FOUND;
+ }
+
+ r &= ~(HPTE64_R_PP0 | HPTE64_R_PP | HPTE64_R_N |
+ HPTE64_R_KEY_HI | HPTE64_R_KEY_LO);
+ r |= (flags << 55) & HPTE64_R_PP0;
+ r |= (flags << 48) & HPTE64_R_KEY_HI;
+ r |= flags & (HPTE64_R_PP | HPTE64_R_N | HPTE64_R_KEY_LO);
+ rb = compute_tlbie_rb(v, r, pte_index);
+ ppc_hash64_store_hpte(env, pte_index,
+ (v & ~HPTE64_V_VALID) | HPTE64_V_HPTE_DIRTY, 0);
+ ppc_tlb_invalidate_one(env, rb);
+ /* Don't need a memory barrier, due to qemu's global lock */
+ ppc_hash64_store_hpte(env, pte_index, v | HPTE64_V_HPTE_DIRTY, r);
+ return H_SUCCESS;
+}
+
+static target_ulong h_read(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong flags = args[0];
+ target_ulong pte_index = args[1];
+ uint8_t *hpte;
+ int i, ridx, n_entries = 1;
+
+ if (!valid_pte_index(env, pte_index)) {
+ return H_PARAMETER;
+ }
+
+ if (flags & H_READ_4) {
+ /* Clear the two low order bits */
+ pte_index &= ~(3ULL);
+ n_entries = 4;
+ }
+
+ hpte = env->external_htab + (pte_index * HASH_PTE_SIZE_64);
+
+ for (i = 0, ridx = 0; i < n_entries; i++) {
+ args[ridx++] = ldq_p(hpte);
+ args[ridx++] = ldq_p(hpte + (HASH_PTE_SIZE_64/2));
+ hpte += HASH_PTE_SIZE_64;
+ }
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_set_dabr(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ /* FIXME: actually implement this */
+ return H_HARDWARE;
+}
+
+#define FLAGS_REGISTER_VPA 0x0000200000000000ULL
+#define FLAGS_REGISTER_DTL 0x0000400000000000ULL
+#define FLAGS_REGISTER_SLBSHADOW 0x0000600000000000ULL
+#define FLAGS_DEREGISTER_VPA 0x0000a00000000000ULL
+#define FLAGS_DEREGISTER_DTL 0x0000c00000000000ULL
+#define FLAGS_DEREGISTER_SLBSHADOW 0x0000e00000000000ULL
+
+#define VPA_MIN_SIZE 640
+#define VPA_SIZE_OFFSET 0x4
+#define VPA_SHARED_PROC_OFFSET 0x9
+#define VPA_SHARED_PROC_VAL 0x2
+
+static target_ulong register_vpa(CPUPPCState *env, target_ulong vpa)
+{
+ CPUState *cs = CPU(ppc_env_get_cpu(env));
+ uint16_t size;
+ uint8_t tmp;
+
+ if (vpa == 0) {
+ hcall_dprintf("Can't cope with registering a VPA at logical 0\n");
+ return H_HARDWARE;
+ }
+
+ if (vpa % env->dcache_line_size) {
+ return H_PARAMETER;
+ }
+ /* FIXME: bounds check the address */
+
+ size = lduw_be_phys(cs->as, vpa + 0x4);
+
+ if (size < VPA_MIN_SIZE) {
+ return H_PARAMETER;
+ }
+
+ /* VPA is not allowed to cross a page boundary */
+ if ((vpa / 4096) != ((vpa + size - 1) / 4096)) {
+ return H_PARAMETER;
+ }
+
+ env->vpa_addr = vpa;
+
+ tmp = ldub_phys(cs->as, env->vpa_addr + VPA_SHARED_PROC_OFFSET);
+ tmp |= VPA_SHARED_PROC_VAL;
+ stb_phys(cs->as, env->vpa_addr + VPA_SHARED_PROC_OFFSET, tmp);
+
+ return H_SUCCESS;
+}
+
+static target_ulong deregister_vpa(CPUPPCState *env, target_ulong vpa)
+{
+ if (env->slb_shadow_addr) {
+ return H_RESOURCE;
+ }
+
+ if (env->dtl_addr) {
+ return H_RESOURCE;
+ }
+
+ env->vpa_addr = 0;
+ return H_SUCCESS;
+}
+
+static target_ulong register_slb_shadow(CPUPPCState *env, target_ulong addr)
+{
+ CPUState *cs = CPU(ppc_env_get_cpu(env));
+ uint32_t size;
+
+ if (addr == 0) {
+ hcall_dprintf("Can't cope with SLB shadow at logical 0\n");
+ return H_HARDWARE;
+ }
+
+ size = ldl_be_phys(cs->as, addr + 0x4);
+ if (size < 0x8) {
+ return H_PARAMETER;
+ }
+
+ if ((addr / 4096) != ((addr + size - 1) / 4096)) {
+ return H_PARAMETER;
+ }
+
+ if (!env->vpa_addr) {
+ return H_RESOURCE;
+ }
+
+ env->slb_shadow_addr = addr;
+ env->slb_shadow_size = size;
+
+ return H_SUCCESS;
+}
+
+static target_ulong deregister_slb_shadow(CPUPPCState *env, target_ulong addr)
+{
+ env->slb_shadow_addr = 0;
+ env->slb_shadow_size = 0;
+ return H_SUCCESS;
+}
+
+static target_ulong register_dtl(CPUPPCState *env, target_ulong addr)
+{
+ CPUState *cs = CPU(ppc_env_get_cpu(env));
+ uint32_t size;
+
+ if (addr == 0) {
+ hcall_dprintf("Can't cope with DTL at logical 0\n");
+ return H_HARDWARE;
+ }
+
+ size = ldl_be_phys(cs->as, addr + 0x4);
+
+ if (size < 48) {
+ return H_PARAMETER;
+ }
+
+ if (!env->vpa_addr) {
+ return H_RESOURCE;
+ }
+
+ env->dtl_addr = addr;
+ env->dtl_size = size;
+
+ return H_SUCCESS;
+}
+
+static target_ulong deregister_dtl(CPUPPCState *env, target_ulong addr)
+{
+ env->dtl_addr = 0;
+ env->dtl_size = 0;
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_register_vpa(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong flags = args[0];
+ target_ulong procno = args[1];
+ target_ulong vpa = args[2];
+ target_ulong ret = H_PARAMETER;
+ CPUPPCState *tenv;
+ PowerPCCPU *tcpu;
+
+ tcpu = ppc_get_vcpu_by_dt_id(procno);
+ if (!tcpu) {
+ return H_PARAMETER;
+ }
+ tenv = &tcpu->env;
+
+ switch (flags) {
+ case FLAGS_REGISTER_VPA:
+ ret = register_vpa(tenv, vpa);
+ break;
+
+ case FLAGS_DEREGISTER_VPA:
+ ret = deregister_vpa(tenv, vpa);
+ break;
+
+ case FLAGS_REGISTER_SLBSHADOW:
+ ret = register_slb_shadow(tenv, vpa);
+ break;
+
+ case FLAGS_DEREGISTER_SLBSHADOW:
+ ret = deregister_slb_shadow(tenv, vpa);
+ break;
+
+ case FLAGS_REGISTER_DTL:
+ ret = register_dtl(tenv, vpa);
+ break;
+
+ case FLAGS_DEREGISTER_DTL:
+ ret = deregister_dtl(tenv, vpa);
+ break;
+ }
+
+ return ret;
+}
+
+static target_ulong h_cede(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ CPUState *cs = CPU(cpu);
+
+ env->msr |= (1ULL << MSR_EE);
+ hreg_compute_hflags(env);
+ if (!cpu_has_work(cs)) {
+ cs->halted = 1;
+ cs->exception_index = EXCP_HLT;
+ cs->exit_request = 1;
+ }
+ return H_SUCCESS;
+}
+
+static target_ulong h_rtas(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong rtas_r3 = args[0];
+ uint32_t token = rtas_ld(rtas_r3, 0);
+ uint32_t nargs = rtas_ld(rtas_r3, 1);
+ uint32_t nret = rtas_ld(rtas_r3, 2);
+
+ return spapr_rtas_call(cpu, spapr, token, nargs, rtas_r3 + 12,
+ nret, rtas_r3 + 12 + 4*nargs);
+}
+
+static target_ulong h_logical_load(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ target_ulong size = args[0];
+ target_ulong addr = args[1];
+
+ switch (size) {
+ case 1:
+ args[0] = ldub_phys(cs->as, addr);
+ return H_SUCCESS;
+ case 2:
+ args[0] = lduw_phys(cs->as, addr);
+ return H_SUCCESS;
+ case 4:
+ args[0] = ldl_phys(cs->as, addr);
+ return H_SUCCESS;
+ case 8:
+ args[0] = ldq_phys(cs->as, addr);
+ return H_SUCCESS;
+ }
+ return H_PARAMETER;
+}
+
+static target_ulong h_logical_store(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+
+ target_ulong size = args[0];
+ target_ulong addr = args[1];
+ target_ulong val = args[2];
+
+ switch (size) {
+ case 1:
+ stb_phys(cs->as, addr, val);
+ return H_SUCCESS;
+ case 2:
+ stw_phys(cs->as, addr, val);
+ return H_SUCCESS;
+ case 4:
+ stl_phys(cs->as, addr, val);
+ return H_SUCCESS;
+ case 8:
+ stq_phys(cs->as, addr, val);
+ return H_SUCCESS;
+ }
+ return H_PARAMETER;
+}
+
+static target_ulong h_logical_memop(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+
+ target_ulong dst = args[0]; /* Destination address */
+ target_ulong src = args[1]; /* Source address */
+ target_ulong esize = args[2]; /* Element size (0=1,1=2,2=4,3=8) */
+ target_ulong count = args[3]; /* Element count */
+ target_ulong op = args[4]; /* 0 = copy, 1 = invert */
+ uint64_t tmp;
+ unsigned int mask = (1 << esize) - 1;
+ int step = 1 << esize;
+
+ if (count > 0x80000000) {
+ return H_PARAMETER;
+ }
+
+ if ((dst & mask) || (src & mask) || (op > 1)) {
+ return H_PARAMETER;
+ }
+
+ if (dst >= src && dst < (src + (count << esize))) {
+ dst = dst + ((count - 1) << esize);
+ src = src + ((count - 1) << esize);
+ step = -step;
+ }
+
+ while (count--) {
+ switch (esize) {
+ case 0:
+ tmp = ldub_phys(cs->as, src);
+ break;
+ case 1:
+ tmp = lduw_phys(cs->as, src);
+ break;
+ case 2:
+ tmp = ldl_phys(cs->as, src);
+ break;
+ case 3:
+ tmp = ldq_phys(cs->as, src);
+ break;
+ default:
+ return H_PARAMETER;
+ }
+ if (op == 1) {
+ tmp = ~tmp;
+ }
+ switch (esize) {
+ case 0:
+ stb_phys(cs->as, dst, tmp);
+ break;
+ case 1:
+ stw_phys(cs->as, dst, tmp);
+ break;
+ case 2:
+ stl_phys(cs->as, dst, tmp);
+ break;
+ case 3:
+ stq_phys(cs->as, dst, tmp);
+ break;
+ }
+ dst = dst + step;
+ src = src + step;
+ }
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_logical_icbi(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ /* Nothing to do on emulation, KVM will trap this in the kernel */
+ return H_SUCCESS;
+}
+
+static target_ulong h_logical_dcbf(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ /* Nothing to do on emulation, KVM will trap this in the kernel */
+ return H_SUCCESS;
+}
+
+static target_ulong h_set_mode_resource_le(PowerPCCPU *cpu,
+ target_ulong mflags,
+ target_ulong value1,
+ target_ulong value2)
+{
+ CPUState *cs;
+
+ if (value1) {
+ return H_P3;
+ }
+ if (value2) {
+ return H_P4;
+ }
+
+ switch (mflags) {
+ case H_SET_MODE_ENDIAN_BIG:
+ CPU_FOREACH(cs) {
+ set_spr(cs, SPR_LPCR, 0, LPCR_ILE);
+ }
+ spapr_pci_switch_vga(true);
+ return H_SUCCESS;
+
+ case H_SET_MODE_ENDIAN_LITTLE:
+ CPU_FOREACH(cs) {
+ set_spr(cs, SPR_LPCR, LPCR_ILE, LPCR_ILE);
+ }
+ spapr_pci_switch_vga(false);
+ return H_SUCCESS;
+ }
+
+ return H_UNSUPPORTED_FLAG;
+}
+
+static target_ulong h_set_mode_resource_addr_trans_mode(PowerPCCPU *cpu,
+ target_ulong mflags,
+ target_ulong value1,
+ target_ulong value2)
+{
+ CPUState *cs;
+ PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cpu);
+ target_ulong prefix;
+
+ if (!(pcc->insns_flags2 & PPC2_ISA207S)) {
+ return H_P2;
+ }
+ if (value1) {
+ return H_P3;
+ }
+ if (value2) {
+ return H_P4;
+ }
+
+ switch (mflags) {
+ case H_SET_MODE_ADDR_TRANS_NONE:
+ prefix = 0;
+ break;
+ case H_SET_MODE_ADDR_TRANS_0001_8000:
+ prefix = 0x18000;
+ break;
+ case H_SET_MODE_ADDR_TRANS_C000_0000_0000_4000:
+ prefix = 0xC000000000004000ULL;
+ break;
+ default:
+ return H_UNSUPPORTED_FLAG;
+ }
+
+ CPU_FOREACH(cs) {
+ CPUPPCState *env = &POWERPC_CPU(cpu)->env;
+
+ set_spr(cs, SPR_LPCR, mflags << LPCR_AIL_SHIFT, LPCR_AIL);
+ env->excp_prefix = prefix;
+ }
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_set_mode(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong resource = args[1];
+ target_ulong ret = H_P2;
+
+ switch (resource) {
+ case H_SET_MODE_RESOURCE_LE:
+ ret = h_set_mode_resource_le(cpu, args[0], args[2], args[3]);
+ break;
+ case H_SET_MODE_RESOURCE_ADDR_TRANS_MODE:
+ ret = h_set_mode_resource_addr_trans_mode(cpu, args[0],
+ args[2], args[3]);
+ break;
+ }
+
+ return ret;
+}
+
+typedef struct {
+ PowerPCCPU *cpu;
+ uint32_t cpu_version;
+ int ret;
+} SetCompatState;
+
+static void do_set_compat(void *arg)
+{
+ SetCompatState *s = arg;
+
+ cpu_synchronize_state(CPU(s->cpu));
+ s->ret = ppc_set_compat(s->cpu, s->cpu_version);
+}
+
+#define get_compat_level(cpuver) ( \
+ ((cpuver) == CPU_POWERPC_LOGICAL_2_05) ? 2050 : \
+ ((cpuver) == CPU_POWERPC_LOGICAL_2_06) ? 2060 : \
+ ((cpuver) == CPU_POWERPC_LOGICAL_2_06_PLUS) ? 2061 : \
+ ((cpuver) == CPU_POWERPC_LOGICAL_2_07) ? 2070 : 0)
+
+static target_ulong h_client_architecture_support(PowerPCCPU *cpu_,
+ sPAPRMachineState *spapr,
+ target_ulong opcode,
+ target_ulong *args)
+{
+ target_ulong list = args[0];
+ PowerPCCPUClass *pcc_ = POWERPC_CPU_GET_CLASS(cpu_);
+ CPUState *cs;
+ bool cpu_match = false;
+ unsigned old_cpu_version = cpu_->cpu_version;
+ unsigned compat_lvl = 0, cpu_version = 0;
+ unsigned max_lvl = get_compat_level(cpu_->max_compat);
+ int counter;
+
+ /* Parse PVR list */
+ for (counter = 0; counter < 512; ++counter) {
+ uint32_t pvr, pvr_mask;
+
+ pvr_mask = rtas_ld(list, 0);
+ list += 4;
+ pvr = rtas_ld(list, 0);
+ list += 4;
+
+ trace_spapr_cas_pvr_try(pvr);
+ if (!max_lvl &&
+ ((cpu_->env.spr[SPR_PVR] & pvr_mask) == (pvr & pvr_mask))) {
+ cpu_match = true;
+ cpu_version = 0;
+ } else if (pvr == cpu_->cpu_version) {
+ cpu_match = true;
+ cpu_version = cpu_->cpu_version;
+ } else if (!cpu_match) {
+ /* If it is a logical PVR, try to determine the highest level */
+ unsigned lvl = get_compat_level(pvr);
+ if (lvl) {
+ bool is205 = (pcc_->pcr_mask & PCR_COMPAT_2_05) &&
+ (lvl == get_compat_level(CPU_POWERPC_LOGICAL_2_05));
+ bool is206 = (pcc_->pcr_mask & PCR_COMPAT_2_06) &&
+ ((lvl == get_compat_level(CPU_POWERPC_LOGICAL_2_06)) ||
+ (lvl == get_compat_level(CPU_POWERPC_LOGICAL_2_06_PLUS)));
+
+ if (is205 || is206) {
+ if (!max_lvl) {
+ /* User did not set the level, choose the highest */
+ if (compat_lvl <= lvl) {
+ compat_lvl = lvl;
+ cpu_version = pvr;
+ }
+ } else if (max_lvl >= lvl) {
+ /* User chose the level, don't set higher than this */
+ compat_lvl = lvl;
+ cpu_version = pvr;
+ }
+ }
+ }
+ }
+ /* Terminator record */
+ if (~pvr_mask & pvr) {
+ break;
+ }
+ }
+
+ /* For the future use: here @list points to the first capability */
+
+ /* Parsing finished */
+ trace_spapr_cas_pvr(cpu_->cpu_version, cpu_match,
+ cpu_version, pcc_->pcr_mask);
+
+ /* Update CPUs */
+ if (old_cpu_version != cpu_version) {
+ CPU_FOREACH(cs) {
+ SetCompatState s = {
+ .cpu = POWERPC_CPU(cs),
+ .cpu_version = cpu_version,
+ .ret = 0
+ };
+
+ run_on_cpu(cs, do_set_compat, &s);
+
+ if (s.ret < 0) {
+ fprintf(stderr, "Unable to set compatibility mode\n");
+ return H_HARDWARE;
+ }
+ }
+ }
+
+ if (!cpu_version) {
+ return H_SUCCESS;
+ }
+
+ if (!list) {
+ return H_SUCCESS;
+ }
+
+ if (spapr_h_cas_compose_response(spapr, args[1], args[2])) {
+ qemu_system_reset_request();
+ }
+
+ return H_SUCCESS;
+}
+
+static spapr_hcall_fn papr_hypercall_table[(MAX_HCALL_OPCODE / 4) + 1];
+static spapr_hcall_fn kvmppc_hypercall_table[KVMPPC_HCALL_MAX - KVMPPC_HCALL_BASE + 1];
+
+void spapr_register_hypercall(target_ulong opcode, spapr_hcall_fn fn)
+{
+ spapr_hcall_fn *slot;
+
+ if (opcode <= MAX_HCALL_OPCODE) {
+ assert((opcode & 0x3) == 0);
+
+ slot = &papr_hypercall_table[opcode / 4];
+ } else {
+ assert((opcode >= KVMPPC_HCALL_BASE) && (opcode <= KVMPPC_HCALL_MAX));
+
+ slot = &kvmppc_hypercall_table[opcode - KVMPPC_HCALL_BASE];
+ }
+
+ assert(!(*slot));
+ *slot = fn;
+}
+
+target_ulong spapr_hypercall(PowerPCCPU *cpu, target_ulong opcode,
+ target_ulong *args)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+
+ if ((opcode <= MAX_HCALL_OPCODE)
+ && ((opcode & 0x3) == 0)) {
+ spapr_hcall_fn fn = papr_hypercall_table[opcode / 4];
+
+ if (fn) {
+ return fn(cpu, spapr, opcode, args);
+ }
+ } else if ((opcode >= KVMPPC_HCALL_BASE) &&
+ (opcode <= KVMPPC_HCALL_MAX)) {
+ spapr_hcall_fn fn = kvmppc_hypercall_table[opcode - KVMPPC_HCALL_BASE];
+
+ if (fn) {
+ return fn(cpu, spapr, opcode, args);
+ }
+ }
+
+ hcall_dprintf("Unimplemented hcall 0x" TARGET_FMT_lx "\n", opcode);
+ return H_FUNCTION;
+}
+
+static void hypercall_register_types(void)
+{
+ /* hcall-pft */
+ spapr_register_hypercall(H_ENTER, h_enter);
+ spapr_register_hypercall(H_REMOVE, h_remove);
+ spapr_register_hypercall(H_PROTECT, h_protect);
+ spapr_register_hypercall(H_READ, h_read);
+
+ /* hcall-bulk */
+ spapr_register_hypercall(H_BULK_REMOVE, h_bulk_remove);
+
+ /* hcall-dabr */
+ spapr_register_hypercall(H_SET_DABR, h_set_dabr);
+
+ /* hcall-splpar */
+ spapr_register_hypercall(H_REGISTER_VPA, h_register_vpa);
+ spapr_register_hypercall(H_CEDE, h_cede);
+
+ /* "debugger" hcalls (also used by SLOF). Note: We do -not- differenciate
+ * here between the "CI" and the "CACHE" variants, they will use whatever
+ * mapping attributes qemu is using. When using KVM, the kernel will
+ * enforce the attributes more strongly
+ */
+ spapr_register_hypercall(H_LOGICAL_CI_LOAD, h_logical_load);
+ spapr_register_hypercall(H_LOGICAL_CI_STORE, h_logical_store);
+ spapr_register_hypercall(H_LOGICAL_CACHE_LOAD, h_logical_load);
+ spapr_register_hypercall(H_LOGICAL_CACHE_STORE, h_logical_store);
+ spapr_register_hypercall(H_LOGICAL_ICBI, h_logical_icbi);
+ spapr_register_hypercall(H_LOGICAL_DCBF, h_logical_dcbf);
+ spapr_register_hypercall(KVMPPC_H_LOGICAL_MEMOP, h_logical_memop);
+
+ /* qemu/KVM-PPC specific hcalls */
+ spapr_register_hypercall(KVMPPC_H_RTAS, h_rtas);
+
+ spapr_register_hypercall(H_SET_MODE, h_set_mode);
+
+ /* ibm,client-architecture-support support */
+ spapr_register_hypercall(KVMPPC_H_CAS, h_client_architecture_support);
+}
+
+type_init(hypercall_register_types)
diff --git a/hw/ppc/spapr_iommu.c b/hw/ppc/spapr_iommu.c
new file mode 100644
index 00000000..f61504e0
--- /dev/null
+++ b/hw/ppc/spapr_iommu.c
@@ -0,0 +1,479 @@
+/*
+ * QEMU sPAPR IOMMU (TCE) code
+ *
+ * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "sysemu/kvm.h"
+#include "hw/qdev.h"
+#include "kvm_ppc.h"
+#include "sysemu/dma.h"
+#include "exec/address-spaces.h"
+#include "trace.h"
+
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+
+#include <libfdt.h>
+
+enum sPAPRTCEAccess {
+ SPAPR_TCE_FAULT = 0,
+ SPAPR_TCE_RO = 1,
+ SPAPR_TCE_WO = 2,
+ SPAPR_TCE_RW = 3,
+};
+
+#define IOMMU_PAGE_SIZE(shift) (1ULL << (shift))
+#define IOMMU_PAGE_MASK(shift) (~(IOMMU_PAGE_SIZE(shift) - 1))
+
+static QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables;
+
+sPAPRTCETable *spapr_tce_find_by_liobn(target_ulong liobn)
+{
+ sPAPRTCETable *tcet;
+
+ if (liobn & 0xFFFFFFFF00000000ULL) {
+ hcall_dprintf("Request for out-of-bounds LIOBN 0x" TARGET_FMT_lx "\n",
+ liobn);
+ return NULL;
+ }
+
+ QLIST_FOREACH(tcet, &spapr_tce_tables, list) {
+ if (tcet->liobn == (uint32_t)liobn) {
+ return tcet;
+ }
+ }
+
+ return NULL;
+}
+
+static IOMMUAccessFlags spapr_tce_iommu_access_flags(uint64_t tce)
+{
+ switch (tce & SPAPR_TCE_RW) {
+ case SPAPR_TCE_FAULT:
+ return IOMMU_NONE;
+ case SPAPR_TCE_RO:
+ return IOMMU_RO;
+ case SPAPR_TCE_WO:
+ return IOMMU_WO;
+ default: /* SPAPR_TCE_RW */
+ return IOMMU_RW;
+ }
+}
+
+/* Called from RCU critical section */
+static IOMMUTLBEntry spapr_tce_translate_iommu(MemoryRegion *iommu, hwaddr addr,
+ bool is_write)
+{
+ sPAPRTCETable *tcet = container_of(iommu, sPAPRTCETable, iommu);
+ uint64_t tce;
+ IOMMUTLBEntry ret = {
+ .target_as = &address_space_memory,
+ .iova = 0,
+ .translated_addr = 0,
+ .addr_mask = ~(hwaddr)0,
+ .perm = IOMMU_NONE,
+ };
+
+ if ((addr >> tcet->page_shift) < tcet->nb_table) {
+ /* Check if we are in bound */
+ hwaddr page_mask = IOMMU_PAGE_MASK(tcet->page_shift);
+
+ tce = tcet->table[addr >> tcet->page_shift];
+ ret.iova = addr & page_mask;
+ ret.translated_addr = tce & page_mask;
+ ret.addr_mask = ~page_mask;
+ ret.perm = spapr_tce_iommu_access_flags(tce);
+ }
+ trace_spapr_iommu_xlate(tcet->liobn, addr, ret.iova, ret.perm,
+ ret.addr_mask);
+
+ return ret;
+}
+
+static int spapr_tce_table_post_load(void *opaque, int version_id)
+{
+ sPAPRTCETable *tcet = SPAPR_TCE_TABLE(opaque);
+
+ if (tcet->vdev) {
+ spapr_vio_set_bypass(tcet->vdev, tcet->bypass);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_spapr_tce_table = {
+ .name = "spapr_iommu",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = spapr_tce_table_post_load,
+ .fields = (VMStateField []) {
+ /* Sanity check */
+ VMSTATE_UINT32_EQUAL(liobn, sPAPRTCETable),
+ VMSTATE_UINT32_EQUAL(nb_table, sPAPRTCETable),
+
+ /* IOMMU state */
+ VMSTATE_BOOL(bypass, sPAPRTCETable),
+ VMSTATE_VARRAY_UINT32(table, sPAPRTCETable, nb_table, 0, vmstate_info_uint64, uint64_t),
+
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static MemoryRegionIOMMUOps spapr_iommu_ops = {
+ .translate = spapr_tce_translate_iommu,
+};
+
+static int spapr_tce_table_realize(DeviceState *dev)
+{
+ sPAPRTCETable *tcet = SPAPR_TCE_TABLE(dev);
+ uint64_t window_size = (uint64_t)tcet->nb_table << tcet->page_shift;
+
+ if (kvm_enabled() && !(window_size >> 32)) {
+ tcet->table = kvmppc_create_spapr_tce(tcet->liobn,
+ window_size,
+ &tcet->fd,
+ tcet->vfio_accel);
+ }
+
+ if (!tcet->table) {
+ size_t table_size = tcet->nb_table * sizeof(uint64_t);
+ tcet->table = g_malloc0(table_size);
+ }
+
+ trace_spapr_iommu_new_table(tcet->liobn, tcet, tcet->table, tcet->fd);
+
+ memory_region_init_iommu(&tcet->iommu, OBJECT(dev), &spapr_iommu_ops,
+ "iommu-spapr",
+ (uint64_t)tcet->nb_table << tcet->page_shift);
+
+ QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);
+
+ vmstate_register(DEVICE(tcet), tcet->liobn, &vmstate_spapr_tce_table,
+ tcet);
+
+ return 0;
+}
+
+sPAPRTCETable *spapr_tce_new_table(DeviceState *owner, uint32_t liobn,
+ uint64_t bus_offset,
+ uint32_t page_shift,
+ uint32_t nb_table,
+ bool vfio_accel)
+{
+ sPAPRTCETable *tcet;
+ char tmp[64];
+
+ if (spapr_tce_find_by_liobn(liobn)) {
+ fprintf(stderr, "Attempted to create TCE table with duplicate"
+ " LIOBN 0x%x\n", liobn);
+ return NULL;
+ }
+
+ if (!nb_table) {
+ return NULL;
+ }
+
+ tcet = SPAPR_TCE_TABLE(object_new(TYPE_SPAPR_TCE_TABLE));
+ tcet->liobn = liobn;
+ tcet->bus_offset = bus_offset;
+ tcet->page_shift = page_shift;
+ tcet->nb_table = nb_table;
+ tcet->vfio_accel = vfio_accel;
+
+ snprintf(tmp, sizeof(tmp), "tce-table-%x", liobn);
+ object_property_add_child(OBJECT(owner), tmp, OBJECT(tcet), NULL);
+
+ object_property_set_bool(OBJECT(tcet), true, "realized", NULL);
+
+ return tcet;
+}
+
+static void spapr_tce_table_unrealize(DeviceState *dev, Error **errp)
+{
+ sPAPRTCETable *tcet = SPAPR_TCE_TABLE(dev);
+
+ QLIST_REMOVE(tcet, list);
+
+ if (!kvm_enabled() ||
+ (kvmppc_remove_spapr_tce(tcet->table, tcet->fd,
+ tcet->nb_table) != 0)) {
+ g_free(tcet->table);
+ }
+}
+
+MemoryRegion *spapr_tce_get_iommu(sPAPRTCETable *tcet)
+{
+ return &tcet->iommu;
+}
+
+static void spapr_tce_reset(DeviceState *dev)
+{
+ sPAPRTCETable *tcet = SPAPR_TCE_TABLE(dev);
+ size_t table_size = tcet->nb_table * sizeof(uint64_t);
+
+ memset(tcet->table, 0, table_size);
+}
+
+static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
+ target_ulong tce)
+{
+ IOMMUTLBEntry entry;
+ hwaddr page_mask = IOMMU_PAGE_MASK(tcet->page_shift);
+ unsigned long index = (ioba - tcet->bus_offset) >> tcet->page_shift;
+
+ if (index >= tcet->nb_table) {
+ hcall_dprintf("spapr_vio_put_tce on out-of-bounds IOBA 0x"
+ TARGET_FMT_lx "\n", ioba);
+ return H_PARAMETER;
+ }
+
+ tcet->table[index] = tce;
+
+ entry.target_as = &address_space_memory,
+ entry.iova = ioba & page_mask;
+ entry.translated_addr = tce & page_mask;
+ entry.addr_mask = ~page_mask;
+ entry.perm = spapr_tce_iommu_access_flags(tce);
+ memory_region_notify_iommu(&tcet->iommu, entry);
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_put_tce_indirect(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ int i;
+ target_ulong liobn = args[0];
+ target_ulong ioba = args[1];
+ target_ulong ioba1 = ioba;
+ target_ulong tce_list = args[2];
+ target_ulong npages = args[3];
+ target_ulong ret = H_PARAMETER, tce = 0;
+ sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
+ CPUState *cs = CPU(cpu);
+ hwaddr page_mask, page_size;
+
+ if (!tcet) {
+ return H_PARAMETER;
+ }
+
+ if ((npages > 512) || (tce_list & SPAPR_TCE_PAGE_MASK)) {
+ return H_PARAMETER;
+ }
+
+ page_mask = IOMMU_PAGE_MASK(tcet->page_shift);
+ page_size = IOMMU_PAGE_SIZE(tcet->page_shift);
+ ioba &= page_mask;
+
+ for (i = 0; i < npages; ++i, ioba += page_size) {
+ tce = ldq_be_phys(cs->as, tce_list + i * sizeof(target_ulong));
+
+ ret = put_tce_emu(tcet, ioba, tce);
+ if (ret) {
+ break;
+ }
+ }
+
+ /* Trace last successful or the first problematic entry */
+ i = i ? (i - 1) : 0;
+ if (SPAPR_IS_PCI_LIOBN(liobn)) {
+ trace_spapr_iommu_pci_indirect(liobn, ioba1, tce_list, i, tce, ret);
+ } else {
+ trace_spapr_iommu_indirect(liobn, ioba1, tce_list, i, tce, ret);
+ }
+ return ret;
+}
+
+static target_ulong h_stuff_tce(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ int i;
+ target_ulong liobn = args[0];
+ target_ulong ioba = args[1];
+ target_ulong tce_value = args[2];
+ target_ulong npages = args[3];
+ target_ulong ret = H_PARAMETER;
+ sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
+ hwaddr page_mask, page_size;
+
+ if (!tcet) {
+ return H_PARAMETER;
+ }
+
+ if (npages > tcet->nb_table) {
+ return H_PARAMETER;
+ }
+
+ page_mask = IOMMU_PAGE_MASK(tcet->page_shift);
+ page_size = IOMMU_PAGE_SIZE(tcet->page_shift);
+ ioba &= page_mask;
+
+ for (i = 0; i < npages; ++i, ioba += page_size) {
+ ret = put_tce_emu(tcet, ioba, tce_value);
+ if (ret) {
+ break;
+ }
+ }
+ if (SPAPR_IS_PCI_LIOBN(liobn)) {
+ trace_spapr_iommu_pci_stuff(liobn, ioba, tce_value, npages, ret);
+ } else {
+ trace_spapr_iommu_stuff(liobn, ioba, tce_value, npages, ret);
+ }
+
+ return ret;
+}
+
+static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong liobn = args[0];
+ target_ulong ioba = args[1];
+ target_ulong tce = args[2];
+ target_ulong ret = H_PARAMETER;
+ sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
+
+ if (tcet) {
+ hwaddr page_mask = IOMMU_PAGE_MASK(tcet->page_shift);
+
+ ioba &= page_mask;
+
+ ret = put_tce_emu(tcet, ioba, tce);
+ }
+ if (SPAPR_IS_PCI_LIOBN(liobn)) {
+ trace_spapr_iommu_pci_put(liobn, ioba, tce, ret);
+ } else {
+ trace_spapr_iommu_put(liobn, ioba, tce, ret);
+ }
+
+ return ret;
+}
+
+static target_ulong get_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
+ target_ulong *tce)
+{
+ unsigned long index = (ioba - tcet->bus_offset) >> tcet->page_shift;
+
+ if (index >= tcet->nb_table) {
+ hcall_dprintf("spapr_iommu_get_tce on out-of-bounds IOBA 0x"
+ TARGET_FMT_lx "\n", ioba);
+ return H_PARAMETER;
+ }
+
+ *tce = tcet->table[index];
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_get_tce(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong liobn = args[0];
+ target_ulong ioba = args[1];
+ target_ulong tce = 0;
+ target_ulong ret = H_PARAMETER;
+ sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
+
+ if (tcet) {
+ hwaddr page_mask = IOMMU_PAGE_MASK(tcet->page_shift);
+
+ ioba &= page_mask;
+
+ ret = get_tce_emu(tcet, ioba, &tce);
+ if (!ret) {
+ args[0] = tce;
+ }
+ }
+ if (SPAPR_IS_PCI_LIOBN(liobn)) {
+ trace_spapr_iommu_pci_get(liobn, ioba, ret, tce);
+ } else {
+ trace_spapr_iommu_get(liobn, ioba, ret, tce);
+ }
+
+ return ret;
+}
+
+int spapr_dma_dt(void *fdt, int node_off, const char *propname,
+ uint32_t liobn, uint64_t window, uint32_t size)
+{
+ uint32_t dma_prop[5];
+ int ret;
+
+ dma_prop[0] = cpu_to_be32(liobn);
+ dma_prop[1] = cpu_to_be32(window >> 32);
+ dma_prop[2] = cpu_to_be32(window & 0xFFFFFFFF);
+ dma_prop[3] = 0; /* window size is 32 bits */
+ dma_prop[4] = cpu_to_be32(size);
+
+ ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-address-cells", 2);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-size-cells", 2);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = fdt_setprop(fdt, node_off, propname, dma_prop, sizeof(dma_prop));
+ if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname,
+ sPAPRTCETable *tcet)
+{
+ if (!tcet) {
+ return 0;
+ }
+
+ return spapr_dma_dt(fdt, node_off, propname,
+ tcet->liobn, 0, tcet->nb_table << tcet->page_shift);
+}
+
+static void spapr_tce_table_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->init = spapr_tce_table_realize;
+ dc->reset = spapr_tce_reset;
+ dc->unrealize = spapr_tce_table_unrealize;
+
+ QLIST_INIT(&spapr_tce_tables);
+
+ /* hcall-tce */
+ spapr_register_hypercall(H_PUT_TCE, h_put_tce);
+ spapr_register_hypercall(H_GET_TCE, h_get_tce);
+ spapr_register_hypercall(H_PUT_TCE_INDIRECT, h_put_tce_indirect);
+ spapr_register_hypercall(H_STUFF_TCE, h_stuff_tce);
+}
+
+static TypeInfo spapr_tce_table_info = {
+ .name = TYPE_SPAPR_TCE_TABLE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(sPAPRTCETable),
+ .class_init = spapr_tce_table_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&spapr_tce_table_info);
+}
+
+type_init(register_types);
diff --git a/hw/ppc/spapr_pci.c b/hw/ppc/spapr_pci.c
new file mode 100644
index 00000000..119fa5e4
--- /dev/null
+++ b/hw/ppc/spapr_pci.c
@@ -0,0 +1,1843 @@
+/*
+ * QEMU sPAPR PCI host originated from Uninorth PCI host
+ *
+ * Copyright (c) 2011 Alexey Kardashevskiy, IBM Corporation.
+ * Copyright (C) 2011 David Gibson, IBM Corporation.
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "hw/pci/pci_host.h"
+#include "hw/ppc/spapr.h"
+#include "hw/pci-host/spapr.h"
+#include "exec/address-spaces.h"
+#include <libfdt.h>
+#include "trace.h"
+#include "qemu/error-report.h"
+#include "qapi/qmp/qerror.h"
+
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/ppc/spapr_drc.h"
+#include "sysemu/device_tree.h"
+
+/* Copied from the kernel arch/powerpc/platforms/pseries/msi.c */
+#define RTAS_QUERY_FN 0
+#define RTAS_CHANGE_FN 1
+#define RTAS_RESET_FN 2
+#define RTAS_CHANGE_MSI_FN 3
+#define RTAS_CHANGE_MSIX_FN 4
+
+/* Interrupt types to return on RTAS_CHANGE_* */
+#define RTAS_TYPE_MSI 1
+#define RTAS_TYPE_MSIX 2
+
+#define FDT_NAME_MAX 128
+
+#define _FDT(exp) \
+ do { \
+ int ret = (exp); \
+ if (ret < 0) { \
+ return ret; \
+ } \
+ } while (0)
+
+sPAPRPHBState *spapr_pci_find_phb(sPAPRMachineState *spapr, uint64_t buid)
+{
+ sPAPRPHBState *sphb;
+
+ QLIST_FOREACH(sphb, &spapr->phbs, list) {
+ if (sphb->buid != buid) {
+ continue;
+ }
+ return sphb;
+ }
+
+ return NULL;
+}
+
+PCIDevice *spapr_pci_find_dev(sPAPRMachineState *spapr, uint64_t buid,
+ uint32_t config_addr)
+{
+ sPAPRPHBState *sphb = spapr_pci_find_phb(spapr, buid);
+ PCIHostState *phb = PCI_HOST_BRIDGE(sphb);
+ int bus_num = (config_addr >> 16) & 0xFF;
+ int devfn = (config_addr >> 8) & 0xFF;
+
+ if (!phb) {
+ return NULL;
+ }
+
+ return pci_find_device(phb->bus, bus_num, devfn);
+}
+
+static uint32_t rtas_pci_cfgaddr(uint32_t arg)
+{
+ /* This handles the encoding of extended config space addresses */
+ return ((arg >> 20) & 0xf00) | (arg & 0xff);
+}
+
+static void finish_read_pci_config(sPAPRMachineState *spapr, uint64_t buid,
+ uint32_t addr, uint32_t size,
+ target_ulong rets)
+{
+ PCIDevice *pci_dev;
+ uint32_t val;
+
+ if ((size != 1) && (size != 2) && (size != 4)) {
+ /* access must be 1, 2 or 4 bytes */
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ pci_dev = spapr_pci_find_dev(spapr, buid, addr);
+ addr = rtas_pci_cfgaddr(addr);
+
+ if (!pci_dev || (addr % size) || (addr >= pci_config_size(pci_dev))) {
+ /* Access must be to a valid device, within bounds and
+ * naturally aligned */
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ val = pci_host_config_read_common(pci_dev, addr,
+ pci_config_size(pci_dev), size);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, val);
+}
+
+static void rtas_ibm_read_pci_config(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint64_t buid;
+ uint32_t size, addr;
+
+ if ((nargs != 4) || (nret != 2)) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ size = rtas_ld(args, 3);
+ addr = rtas_ld(args, 0);
+
+ finish_read_pci_config(spapr, buid, addr, size, rets);
+}
+
+static void rtas_read_pci_config(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint32_t size, addr;
+
+ if ((nargs != 2) || (nret != 2)) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ size = rtas_ld(args, 1);
+ addr = rtas_ld(args, 0);
+
+ finish_read_pci_config(spapr, 0, addr, size, rets);
+}
+
+static void finish_write_pci_config(sPAPRMachineState *spapr, uint64_t buid,
+ uint32_t addr, uint32_t size,
+ uint32_t val, target_ulong rets)
+{
+ PCIDevice *pci_dev;
+
+ if ((size != 1) && (size != 2) && (size != 4)) {
+ /* access must be 1, 2 or 4 bytes */
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ pci_dev = spapr_pci_find_dev(spapr, buid, addr);
+ addr = rtas_pci_cfgaddr(addr);
+
+ if (!pci_dev || (addr % size) || (addr >= pci_config_size(pci_dev))) {
+ /* Access must be to a valid device, within bounds and
+ * naturally aligned */
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ pci_host_config_write_common(pci_dev, addr, pci_config_size(pci_dev),
+ val, size);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+static void rtas_ibm_write_pci_config(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint64_t buid;
+ uint32_t val, size, addr;
+
+ if ((nargs != 5) || (nret != 1)) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ val = rtas_ld(args, 4);
+ size = rtas_ld(args, 3);
+ addr = rtas_ld(args, 0);
+
+ finish_write_pci_config(spapr, buid, addr, size, val, rets);
+}
+
+static void rtas_write_pci_config(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint32_t val, size, addr;
+
+ if ((nargs != 3) || (nret != 1)) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+
+ val = rtas_ld(args, 2);
+ size = rtas_ld(args, 1);
+ addr = rtas_ld(args, 0);
+
+ finish_write_pci_config(spapr, 0, addr, size, val, rets);
+}
+
+/*
+ * Set MSI/MSIX message data.
+ * This is required for msi_notify()/msix_notify() which
+ * will write at the addresses via spapr_msi_write().
+ *
+ * If hwaddr == 0, all entries will have .data == first_irq i.e.
+ * table will be reset.
+ */
+static void spapr_msi_setmsg(PCIDevice *pdev, hwaddr addr, bool msix,
+ unsigned first_irq, unsigned req_num)
+{
+ unsigned i;
+ MSIMessage msg = { .address = addr, .data = first_irq };
+
+ if (!msix) {
+ msi_set_message(pdev, msg);
+ trace_spapr_pci_msi_setup(pdev->name, 0, msg.address);
+ return;
+ }
+
+ for (i = 0; i < req_num; ++i) {
+ msix_set_message(pdev, i, msg);
+ trace_spapr_pci_msi_setup(pdev->name, i, msg.address);
+ if (addr) {
+ ++msg.data;
+ }
+ }
+}
+
+static void rtas_ibm_change_msi(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ uint32_t config_addr = rtas_ld(args, 0);
+ uint64_t buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ unsigned int func = rtas_ld(args, 3);
+ unsigned int req_num = rtas_ld(args, 4); /* 0 == remove all */
+ unsigned int seq_num = rtas_ld(args, 5);
+ unsigned int ret_intr_type;
+ unsigned int irq, max_irqs = 0, num = 0;
+ sPAPRPHBState *phb = NULL;
+ PCIDevice *pdev = NULL;
+ spapr_pci_msi *msi;
+ int *config_addr_key;
+
+ switch (func) {
+ case RTAS_CHANGE_MSI_FN:
+ case RTAS_CHANGE_FN:
+ ret_intr_type = RTAS_TYPE_MSI;
+ break;
+ case RTAS_CHANGE_MSIX_FN:
+ ret_intr_type = RTAS_TYPE_MSIX;
+ break;
+ default:
+ error_report("rtas_ibm_change_msi(%u) is not implemented", func);
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ /* Fins sPAPRPHBState */
+ phb = spapr_pci_find_phb(spapr, buid);
+ if (phb) {
+ pdev = spapr_pci_find_dev(spapr, buid, config_addr);
+ }
+ if (!phb || !pdev) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ /* Releasing MSIs */
+ if (!req_num) {
+ msi = (spapr_pci_msi *) g_hash_table_lookup(phb->msi, &config_addr);
+ if (!msi) {
+ trace_spapr_pci_msi("Releasing wrong config", config_addr);
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ xics_free(spapr->icp, msi->first_irq, msi->num);
+ if (msi_present(pdev)) {
+ spapr_msi_setmsg(pdev, 0, false, 0, num);
+ }
+ if (msix_present(pdev)) {
+ spapr_msi_setmsg(pdev, 0, true, 0, num);
+ }
+ g_hash_table_remove(phb->msi, &config_addr);
+
+ trace_spapr_pci_msi("Released MSIs", config_addr);
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, 0);
+ return;
+ }
+
+ /* Enabling MSI */
+
+ /* Check if the device supports as many IRQs as requested */
+ if (ret_intr_type == RTAS_TYPE_MSI) {
+ max_irqs = msi_nr_vectors_allocated(pdev);
+ } else if (ret_intr_type == RTAS_TYPE_MSIX) {
+ max_irqs = pdev->msix_entries_nr;
+ }
+ if (!max_irqs) {
+ error_report("Requested interrupt type %d is not enabled for device %x",
+ ret_intr_type, config_addr);
+ rtas_st(rets, 0, -1); /* Hardware error */
+ return;
+ }
+ /* Correct the number if the guest asked for too many */
+ if (req_num > max_irqs) {
+ trace_spapr_pci_msi_retry(config_addr, req_num, max_irqs);
+ req_num = max_irqs;
+ irq = 0; /* to avoid misleading trace */
+ goto out;
+ }
+
+ /* Allocate MSIs */
+ irq = xics_alloc_block(spapr->icp, 0, req_num, false,
+ ret_intr_type == RTAS_TYPE_MSI);
+ if (!irq) {
+ error_report("Cannot allocate MSIs for device %x", config_addr);
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ /* Setup MSI/MSIX vectors in the device (via cfgspace or MSIX BAR) */
+ spapr_msi_setmsg(pdev, SPAPR_PCI_MSI_WINDOW, ret_intr_type == RTAS_TYPE_MSIX,
+ irq, req_num);
+
+ /* Add MSI device to cache */
+ msi = g_new(spapr_pci_msi, 1);
+ msi->first_irq = irq;
+ msi->num = req_num;
+ config_addr_key = g_new(int, 1);
+ *config_addr_key = config_addr;
+ g_hash_table_insert(phb->msi, config_addr_key, msi);
+
+out:
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, req_num);
+ rtas_st(rets, 2, ++seq_num);
+ rtas_st(rets, 3, ret_intr_type);
+
+ trace_spapr_pci_rtas_ibm_change_msi(config_addr, func, req_num, irq);
+}
+
+static void rtas_ibm_query_interrupt_source_number(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token,
+ uint32_t nargs,
+ target_ulong args,
+ uint32_t nret,
+ target_ulong rets)
+{
+ uint32_t config_addr = rtas_ld(args, 0);
+ uint64_t buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ unsigned int intr_src_num = -1, ioa_intr_num = rtas_ld(args, 3);
+ sPAPRPHBState *phb = NULL;
+ PCIDevice *pdev = NULL;
+ spapr_pci_msi *msi;
+
+ /* Find sPAPRPHBState */
+ phb = spapr_pci_find_phb(spapr, buid);
+ if (phb) {
+ pdev = spapr_pci_find_dev(spapr, buid, config_addr);
+ }
+ if (!phb || !pdev) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ /* Find device descriptor and start IRQ */
+ msi = (spapr_pci_msi *) g_hash_table_lookup(phb->msi, &config_addr);
+ if (!msi || !msi->first_irq || !msi->num || (ioa_intr_num >= msi->num)) {
+ trace_spapr_pci_msi("Failed to return vector", config_addr);
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+ intr_src_num = msi->first_irq + ioa_intr_num;
+ trace_spapr_pci_rtas_ibm_query_interrupt_source_number(ioa_intr_num,
+ intr_src_num);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, intr_src_num);
+ rtas_st(rets, 2, 1);/* 0 == level; 1 == edge */
+}
+
+static void rtas_ibm_set_eeh_option(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ sPAPRPHBState *sphb;
+ sPAPRPHBClass *spc;
+ PCIDevice *pdev;
+ uint32_t addr, option;
+ uint64_t buid;
+ int ret;
+
+ if ((nargs != 4) || (nret != 1)) {
+ goto param_error_exit;
+ }
+
+ buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ addr = rtas_ld(args, 0);
+ option = rtas_ld(args, 3);
+
+ sphb = spapr_pci_find_phb(spapr, buid);
+ if (!sphb) {
+ goto param_error_exit;
+ }
+
+ pdev = pci_find_device(PCI_HOST_BRIDGE(sphb)->bus,
+ (addr >> 16) & 0xFF, (addr >> 8) & 0xFF);
+ if (!pdev || !object_dynamic_cast(OBJECT(pdev), "vfio-pci")) {
+ goto param_error_exit;
+ }
+
+ spc = SPAPR_PCI_HOST_BRIDGE_GET_CLASS(sphb);
+ if (!spc->eeh_set_option) {
+ goto param_error_exit;
+ }
+
+ ret = spc->eeh_set_option(sphb, addr, option);
+ rtas_st(rets, 0, ret);
+ return;
+
+param_error_exit:
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
+static void rtas_ibm_get_config_addr_info2(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ sPAPRPHBState *sphb;
+ sPAPRPHBClass *spc;
+ PCIDevice *pdev;
+ uint32_t addr, option;
+ uint64_t buid;
+
+ if ((nargs != 4) || (nret != 2)) {
+ goto param_error_exit;
+ }
+
+ buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ sphb = spapr_pci_find_phb(spapr, buid);
+ if (!sphb) {
+ goto param_error_exit;
+ }
+
+ spc = SPAPR_PCI_HOST_BRIDGE_GET_CLASS(sphb);
+ if (!spc->eeh_set_option) {
+ goto param_error_exit;
+ }
+
+ /*
+ * We always have PE address of form "00BB0001". "BB"
+ * represents the bus number of PE's primary bus.
+ */
+ option = rtas_ld(args, 3);
+ switch (option) {
+ case RTAS_GET_PE_ADDR:
+ addr = rtas_ld(args, 0);
+ pdev = spapr_pci_find_dev(spapr, buid, addr);
+ if (!pdev) {
+ goto param_error_exit;
+ }
+
+ rtas_st(rets, 1, (pci_bus_num(pdev->bus) << 16) + 1);
+ break;
+ case RTAS_GET_PE_MODE:
+ rtas_st(rets, 1, RTAS_PE_MODE_SHARED);
+ break;
+ default:
+ goto param_error_exit;
+ }
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ return;
+
+param_error_exit:
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
+static void rtas_ibm_read_slot_reset_state2(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ sPAPRPHBState *sphb;
+ sPAPRPHBClass *spc;
+ uint64_t buid;
+ int state, ret;
+
+ if ((nargs != 3) || (nret != 4 && nret != 5)) {
+ goto param_error_exit;
+ }
+
+ buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ sphb = spapr_pci_find_phb(spapr, buid);
+ if (!sphb) {
+ goto param_error_exit;
+ }
+
+ spc = SPAPR_PCI_HOST_BRIDGE_GET_CLASS(sphb);
+ if (!spc->eeh_get_state) {
+ goto param_error_exit;
+ }
+
+ ret = spc->eeh_get_state(sphb, &state);
+ rtas_st(rets, 0, ret);
+ if (ret != RTAS_OUT_SUCCESS) {
+ return;
+ }
+
+ rtas_st(rets, 1, state);
+ rtas_st(rets, 2, RTAS_EEH_SUPPORT);
+ rtas_st(rets, 3, RTAS_EEH_PE_UNAVAIL_INFO);
+ if (nret >= 5) {
+ rtas_st(rets, 4, RTAS_EEH_PE_RECOVER_INFO);
+ }
+ return;
+
+param_error_exit:
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
+static void rtas_ibm_set_slot_reset(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ sPAPRPHBState *sphb;
+ sPAPRPHBClass *spc;
+ uint32_t option;
+ uint64_t buid;
+ int ret;
+
+ if ((nargs != 4) || (nret != 1)) {
+ goto param_error_exit;
+ }
+
+ buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ option = rtas_ld(args, 3);
+ sphb = spapr_pci_find_phb(spapr, buid);
+ if (!sphb) {
+ goto param_error_exit;
+ }
+
+ spc = SPAPR_PCI_HOST_BRIDGE_GET_CLASS(sphb);
+ if (!spc->eeh_reset) {
+ goto param_error_exit;
+ }
+
+ ret = spc->eeh_reset(sphb, option);
+ rtas_st(rets, 0, ret);
+ return;
+
+param_error_exit:
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
+static void rtas_ibm_configure_pe(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ sPAPRPHBState *sphb;
+ sPAPRPHBClass *spc;
+ uint64_t buid;
+ int ret;
+
+ if ((nargs != 3) || (nret != 1)) {
+ goto param_error_exit;
+ }
+
+ buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ sphb = spapr_pci_find_phb(spapr, buid);
+ if (!sphb) {
+ goto param_error_exit;
+ }
+
+ spc = SPAPR_PCI_HOST_BRIDGE_GET_CLASS(sphb);
+ if (!spc->eeh_configure) {
+ goto param_error_exit;
+ }
+
+ ret = spc->eeh_configure(sphb);
+ rtas_st(rets, 0, ret);
+ return;
+
+param_error_exit:
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
+/* To support it later */
+static void rtas_ibm_slot_error_detail(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ sPAPRPHBState *sphb;
+ sPAPRPHBClass *spc;
+ int option;
+ uint64_t buid;
+
+ if ((nargs != 8) || (nret != 1)) {
+ goto param_error_exit;
+ }
+
+ buid = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 2);
+ sphb = spapr_pci_find_phb(spapr, buid);
+ if (!sphb) {
+ goto param_error_exit;
+ }
+
+ spc = SPAPR_PCI_HOST_BRIDGE_GET_CLASS(sphb);
+ if (!spc->eeh_set_option) {
+ goto param_error_exit;
+ }
+
+ option = rtas_ld(args, 7);
+ switch (option) {
+ case RTAS_SLOT_TEMP_ERR_LOG:
+ case RTAS_SLOT_PERM_ERR_LOG:
+ break;
+ default:
+ goto param_error_exit;
+ }
+
+ /* We don't have error log yet */
+ rtas_st(rets, 0, RTAS_OUT_NO_ERRORS_FOUND);
+ return;
+
+param_error_exit:
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
+static int pci_spapr_swizzle(int slot, int pin)
+{
+ return (slot + pin) % PCI_NUM_PINS;
+}
+
+static int pci_spapr_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ /*
+ * Here we need to convert pci_dev + irq_num to some unique value
+ * which is less than number of IRQs on the specific bus (4). We
+ * use standard PCI swizzling, that is (slot number + pin number)
+ * % 4.
+ */
+ return pci_spapr_swizzle(PCI_SLOT(pci_dev->devfn), irq_num);
+}
+
+static void pci_spapr_set_irq(void *opaque, int irq_num, int level)
+{
+ /*
+ * Here we use the number returned by pci_spapr_map_irq to find a
+ * corresponding qemu_irq.
+ */
+ sPAPRPHBState *phb = opaque;
+
+ trace_spapr_pci_lsi_set(phb->dtbusname, irq_num, phb->lsi_table[irq_num].irq);
+ qemu_set_irq(spapr_phb_lsi_qirq(phb, irq_num), level);
+}
+
+static PCIINTxRoute spapr_route_intx_pin_to_irq(void *opaque, int pin)
+{
+ sPAPRPHBState *sphb = SPAPR_PCI_HOST_BRIDGE(opaque);
+ PCIINTxRoute route;
+
+ route.mode = PCI_INTX_ENABLED;
+ route.irq = sphb->lsi_table[pin].irq;
+
+ return route;
+}
+
+/*
+ * MSI/MSIX memory region implementation.
+ * The handler handles both MSI and MSIX.
+ * For MSI-X, the vector number is encoded as a part of the address,
+ * data is set to 0.
+ * For MSI, the vector number is encoded in least bits in data.
+ */
+static void spapr_msi_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ uint32_t irq = data;
+
+ trace_spapr_pci_msi_write(addr, data, irq);
+
+ qemu_irq_pulse(xics_get_qirq(spapr->icp, irq));
+}
+
+static const MemoryRegionOps spapr_msi_ops = {
+ /* There is no .read as the read result is undefined by PCI spec */
+ .read = NULL,
+ .write = spapr_msi_write,
+ .endianness = DEVICE_LITTLE_ENDIAN
+};
+
+/*
+ * PHB PCI device
+ */
+static AddressSpace *spapr_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn)
+{
+ sPAPRPHBState *phb = opaque;
+
+ return &phb->iommu_as;
+}
+
+static char *spapr_phb_vfio_get_loc_code(sPAPRPHBState *sphb, PCIDevice *pdev)
+{
+ char *path = NULL, *buf = NULL, *host = NULL;
+
+ /* Get the PCI VFIO host id */
+ host = object_property_get_str(OBJECT(pdev), "host", NULL);
+ if (!host) {
+ goto err_out;
+ }
+
+ /* Construct the path of the file that will give us the DT location */
+ path = g_strdup_printf("/sys/bus/pci/devices/%s/devspec", host);
+ g_free(host);
+ if (!path || !g_file_get_contents(path, &buf, NULL, NULL)) {
+ goto err_out;
+ }
+ g_free(path);
+
+ /* Construct and read from host device tree the loc-code */
+ path = g_strdup_printf("/proc/device-tree%s/ibm,loc-code", buf);
+ g_free(buf);
+ if (!path || !g_file_get_contents(path, &buf, NULL, NULL)) {
+ goto err_out;
+ }
+ return buf;
+
+err_out:
+ g_free(path);
+ return NULL;
+}
+
+static char *spapr_phb_get_loc_code(sPAPRPHBState *sphb, PCIDevice *pdev)
+{
+ char *buf;
+ const char *devtype = "qemu";
+ uint32_t busnr = pci_bus_num(PCI_BUS(qdev_get_parent_bus(DEVICE(pdev))));
+
+ if (object_dynamic_cast(OBJECT(pdev), "vfio-pci")) {
+ buf = spapr_phb_vfio_get_loc_code(sphb, pdev);
+ if (buf) {
+ return buf;
+ }
+ devtype = "vfio";
+ }
+ /*
+ * For emulated devices and VFIO-failure case, make up
+ * the loc-code.
+ */
+ buf = g_strdup_printf("%s_%s:%04x:%02x:%02x.%x",
+ devtype, pdev->name, sphb->index, busnr,
+ PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
+ return buf;
+}
+
+/* Macros to operate with address in OF binding to PCI */
+#define b_x(x, p, l) (((x) & ((1<<(l))-1)) << (p))
+#define b_n(x) b_x((x), 31, 1) /* 0 if relocatable */
+#define b_p(x) b_x((x), 30, 1) /* 1 if prefetchable */
+#define b_t(x) b_x((x), 29, 1) /* 1 if the address is aliased */
+#define b_ss(x) b_x((x), 24, 2) /* the space code */
+#define b_bbbbbbbb(x) b_x((x), 16, 8) /* bus number */
+#define b_ddddd(x) b_x((x), 11, 5) /* device number */
+#define b_fff(x) b_x((x), 8, 3) /* function number */
+#define b_rrrrrrrr(x) b_x((x), 0, 8) /* register number */
+
+/* for 'reg'/'assigned-addresses' OF properties */
+#define RESOURCE_CELLS_SIZE 2
+#define RESOURCE_CELLS_ADDRESS 3
+
+typedef struct ResourceFields {
+ uint32_t phys_hi;
+ uint32_t phys_mid;
+ uint32_t phys_lo;
+ uint32_t size_hi;
+ uint32_t size_lo;
+} QEMU_PACKED ResourceFields;
+
+typedef struct ResourceProps {
+ ResourceFields reg[8];
+ ResourceFields assigned[7];
+ uint32_t reg_len;
+ uint32_t assigned_len;
+} ResourceProps;
+
+/* fill in the 'reg'/'assigned-resources' OF properties for
+ * a PCI device. 'reg' describes resource requirements for a
+ * device's IO/MEM regions, 'assigned-addresses' describes the
+ * actual resource assignments.
+ *
+ * the properties are arrays of ('phys-addr', 'size') pairs describing
+ * the addressable regions of the PCI device, where 'phys-addr' is a
+ * RESOURCE_CELLS_ADDRESS-tuple of 32-bit integers corresponding to
+ * (phys.hi, phys.mid, phys.lo), and 'size' is a
+ * RESOURCE_CELLS_SIZE-tuple corresponding to (size.hi, size.lo).
+ *
+ * phys.hi = 0xYYXXXXZZ, where:
+ * 0xYY = npt000ss
+ * ||| |
+ * ||| +-- space code
+ * ||| |
+ * ||| + 00 if configuration space
+ * ||| + 01 if IO region,
+ * ||| + 10 if 32-bit MEM region
+ * ||| + 11 if 64-bit MEM region
+ * |||
+ * ||+------ for non-relocatable IO: 1 if aliased
+ * || for relocatable IO: 1 if below 64KB
+ * || for MEM: 1 if below 1MB
+ * |+------- 1 if region is prefetchable
+ * +-------- 1 if region is non-relocatable
+ * 0xXXXX = bbbbbbbb dddddfff, encoding bus, slot, and function
+ * bits respectively
+ * 0xZZ = rrrrrrrr, the register number of the BAR corresponding
+ * to the region
+ *
+ * phys.mid and phys.lo correspond respectively to the hi/lo portions
+ * of the actual address of the region.
+ *
+ * how the phys-addr/size values are used differ slightly between
+ * 'reg' and 'assigned-addresses' properties. namely, 'reg' has
+ * an additional description for the config space region of the
+ * device, and in the case of QEMU has n=0 and phys.mid=phys.lo=0
+ * to describe the region as relocatable, with an address-mapping
+ * that corresponds directly to the PHB's address space for the
+ * resource. 'assigned-addresses' always has n=1 set with an absolute
+ * address assigned for the resource. in general, 'assigned-addresses'
+ * won't be populated, since addresses for PCI devices are generally
+ * unmapped initially and left to the guest to assign.
+ *
+ * note also that addresses defined in these properties are, at least
+ * for PAPR guests, relative to the PHBs IO/MEM windows, and
+ * correspond directly to the addresses in the BARs.
+ *
+ * in accordance with PCI Bus Binding to Open Firmware,
+ * IEEE Std 1275-1994, section 4.1.1, as implemented by PAPR+ v2.7,
+ * Appendix C.
+ */
+static void populate_resource_props(PCIDevice *d, ResourceProps *rp)
+{
+ int bus_num = pci_bus_num(PCI_BUS(qdev_get_parent_bus(DEVICE(d))));
+ uint32_t dev_id = (b_bbbbbbbb(bus_num) |
+ b_ddddd(PCI_SLOT(d->devfn)) |
+ b_fff(PCI_FUNC(d->devfn)));
+ ResourceFields *reg, *assigned;
+ int i, reg_idx = 0, assigned_idx = 0;
+
+ /* config space region */
+ reg = &rp->reg[reg_idx++];
+ reg->phys_hi = cpu_to_be32(dev_id);
+ reg->phys_mid = 0;
+ reg->phys_lo = 0;
+ reg->size_hi = 0;
+ reg->size_lo = 0;
+
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ if (!d->io_regions[i].size) {
+ continue;
+ }
+
+ reg = &rp->reg[reg_idx++];
+
+ reg->phys_hi = cpu_to_be32(dev_id | b_rrrrrrrr(pci_bar(d, i)));
+ if (d->io_regions[i].type & PCI_BASE_ADDRESS_SPACE_IO) {
+ reg->phys_hi |= cpu_to_be32(b_ss(1));
+ } else if (d->io_regions[i].type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ reg->phys_hi |= cpu_to_be32(b_ss(3));
+ } else {
+ reg->phys_hi |= cpu_to_be32(b_ss(2));
+ }
+ reg->phys_mid = 0;
+ reg->phys_lo = 0;
+ reg->size_hi = cpu_to_be32(d->io_regions[i].size >> 32);
+ reg->size_lo = cpu_to_be32(d->io_regions[i].size);
+
+ if (d->io_regions[i].addr == PCI_BAR_UNMAPPED) {
+ continue;
+ }
+
+ assigned = &rp->assigned[assigned_idx++];
+ assigned->phys_hi = cpu_to_be32(reg->phys_hi | b_n(1));
+ assigned->phys_mid = cpu_to_be32(d->io_regions[i].addr >> 32);
+ assigned->phys_lo = cpu_to_be32(d->io_regions[i].addr);
+ assigned->size_hi = reg->size_hi;
+ assigned->size_lo = reg->size_lo;
+ }
+
+ rp->reg_len = reg_idx * sizeof(ResourceFields);
+ rp->assigned_len = assigned_idx * sizeof(ResourceFields);
+}
+
+static uint32_t spapr_phb_get_pci_drc_index(sPAPRPHBState *phb,
+ PCIDevice *pdev);
+
+static int spapr_populate_pci_child_dt(PCIDevice *dev, void *fdt, int offset,
+ sPAPRPHBState *sphb)
+{
+ ResourceProps rp;
+ bool is_bridge = false;
+ int pci_status, err;
+ char *buf = NULL;
+ uint32_t drc_index = spapr_phb_get_pci_drc_index(sphb, dev);
+ uint32_t max_msi, max_msix;
+
+ if (pci_default_read_config(dev, PCI_HEADER_TYPE, 1) ==
+ PCI_HEADER_TYPE_BRIDGE) {
+ is_bridge = true;
+ }
+
+ /* in accordance with PAPR+ v2.7 13.6.3, Table 181 */
+ _FDT(fdt_setprop_cell(fdt, offset, "vendor-id",
+ pci_default_read_config(dev, PCI_VENDOR_ID, 2)));
+ _FDT(fdt_setprop_cell(fdt, offset, "device-id",
+ pci_default_read_config(dev, PCI_DEVICE_ID, 2)));
+ _FDT(fdt_setprop_cell(fdt, offset, "revision-id",
+ pci_default_read_config(dev, PCI_REVISION_ID, 1)));
+ _FDT(fdt_setprop_cell(fdt, offset, "class-code",
+ pci_default_read_config(dev, PCI_CLASS_PROG, 3)));
+ if (pci_default_read_config(dev, PCI_INTERRUPT_PIN, 1)) {
+ _FDT(fdt_setprop_cell(fdt, offset, "interrupts",
+ pci_default_read_config(dev, PCI_INTERRUPT_PIN, 1)));
+ }
+
+ if (!is_bridge) {
+ _FDT(fdt_setprop_cell(fdt, offset, "min-grant",
+ pci_default_read_config(dev, PCI_MIN_GNT, 1)));
+ _FDT(fdt_setprop_cell(fdt, offset, "max-latency",
+ pci_default_read_config(dev, PCI_MAX_LAT, 1)));
+ }
+
+ if (pci_default_read_config(dev, PCI_SUBSYSTEM_ID, 2)) {
+ _FDT(fdt_setprop_cell(fdt, offset, "subsystem-id",
+ pci_default_read_config(dev, PCI_SUBSYSTEM_ID, 2)));
+ }
+
+ if (pci_default_read_config(dev, PCI_SUBSYSTEM_VENDOR_ID, 2)) {
+ _FDT(fdt_setprop_cell(fdt, offset, "subsystem-vendor-id",
+ pci_default_read_config(dev, PCI_SUBSYSTEM_VENDOR_ID, 2)));
+ }
+
+ _FDT(fdt_setprop_cell(fdt, offset, "cache-line-size",
+ pci_default_read_config(dev, PCI_CACHE_LINE_SIZE, 1)));
+
+ /* the following fdt cells are masked off the pci status register */
+ pci_status = pci_default_read_config(dev, PCI_STATUS, 2);
+ _FDT(fdt_setprop_cell(fdt, offset, "devsel-speed",
+ PCI_STATUS_DEVSEL_MASK & pci_status));
+
+ if (pci_status & PCI_STATUS_FAST_BACK) {
+ _FDT(fdt_setprop(fdt, offset, "fast-back-to-back", NULL, 0));
+ }
+ if (pci_status & PCI_STATUS_66MHZ) {
+ _FDT(fdt_setprop(fdt, offset, "66mhz-capable", NULL, 0));
+ }
+ if (pci_status & PCI_STATUS_UDF) {
+ _FDT(fdt_setprop(fdt, offset, "udf-supported", NULL, 0));
+ }
+
+ /* NOTE: this is normally generated by firmware via path/unit name,
+ * but in our case we must set it manually since it does not get
+ * processed by OF beforehand
+ */
+ _FDT(fdt_setprop_string(fdt, offset, "name", "pci"));
+ buf = spapr_phb_get_loc_code(sphb, dev);
+ if (!buf) {
+ error_report("Failed setting the ibm,loc-code");
+ return -1;
+ }
+
+ err = fdt_setprop_string(fdt, offset, "ibm,loc-code", buf);
+ g_free(buf);
+ if (err < 0) {
+ return err;
+ }
+
+ if (drc_index) {
+ _FDT(fdt_setprop_cell(fdt, offset, "ibm,my-drc-index", drc_index));
+ }
+
+ _FDT(fdt_setprop_cell(fdt, offset, "#address-cells",
+ RESOURCE_CELLS_ADDRESS));
+ _FDT(fdt_setprop_cell(fdt, offset, "#size-cells",
+ RESOURCE_CELLS_SIZE));
+
+ max_msi = msi_nr_vectors_allocated(dev);
+ if (max_msi) {
+ _FDT(fdt_setprop_cell(fdt, offset, "ibm,req#msi", max_msi));
+ }
+ max_msix = dev->msix_entries_nr;
+ if (max_msix) {
+ _FDT(fdt_setprop_cell(fdt, offset, "ibm,req#msi-x", max_msix));
+ }
+
+ populate_resource_props(dev, &rp);
+ _FDT(fdt_setprop(fdt, offset, "reg", (uint8_t *)rp.reg, rp.reg_len));
+ _FDT(fdt_setprop(fdt, offset, "assigned-addresses",
+ (uint8_t *)rp.assigned, rp.assigned_len));
+
+ return 0;
+}
+
+/* create OF node for pci device and required OF DT properties */
+static int spapr_create_pci_child_dt(sPAPRPHBState *phb, PCIDevice *dev,
+ void *fdt, int node_offset)
+{
+ int offset, ret;
+ int slot = PCI_SLOT(dev->devfn);
+ int func = PCI_FUNC(dev->devfn);
+ char nodename[FDT_NAME_MAX];
+
+ if (func != 0) {
+ snprintf(nodename, FDT_NAME_MAX, "pci@%x,%x", slot, func);
+ } else {
+ snprintf(nodename, FDT_NAME_MAX, "pci@%x", slot);
+ }
+ offset = fdt_add_subnode(fdt, node_offset, nodename);
+ ret = spapr_populate_pci_child_dt(dev, fdt, offset, phb);
+
+ g_assert(!ret);
+ if (ret) {
+ return 0;
+ }
+ return offset;
+}
+
+static void spapr_phb_add_pci_device(sPAPRDRConnector *drc,
+ sPAPRPHBState *phb,
+ PCIDevice *pdev,
+ Error **errp)
+{
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ DeviceState *dev = DEVICE(pdev);
+ void *fdt = NULL;
+ int fdt_start_offset = 0, fdt_size;
+
+ if (dev->hotplugged) {
+ fdt = create_device_tree(&fdt_size);
+ fdt_start_offset = spapr_create_pci_child_dt(phb, pdev, fdt, 0);
+ if (!fdt_start_offset) {
+ error_setg(errp, "Failed to create pci child device tree node");
+ goto out;
+ }
+ }
+
+ drck->attach(drc, DEVICE(pdev),
+ fdt, fdt_start_offset, !dev->hotplugged, errp);
+out:
+ if (*errp) {
+ g_free(fdt);
+ }
+}
+
+static void spapr_phb_remove_pci_device_cb(DeviceState *dev, void *opaque)
+{
+ /* some version guests do not wait for completion of a device
+ * cleanup (generally done asynchronously by the kernel) before
+ * signaling to QEMU that the device is safe, but instead sleep
+ * for some 'safe' period of time. unfortunately on a busy host
+ * this sleep isn't guaranteed to be long enough, resulting in
+ * bad things like IRQ lines being left asserted during final
+ * device removal. to deal with this we call reset just prior
+ * to finalizing the device, which will put the device back into
+ * an 'idle' state, as the device cleanup code expects.
+ */
+ pci_device_reset(PCI_DEVICE(dev));
+ object_unparent(OBJECT(dev));
+}
+
+static void spapr_phb_remove_pci_device(sPAPRDRConnector *drc,
+ sPAPRPHBState *phb,
+ PCIDevice *pdev,
+ Error **errp)
+{
+ sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+
+ drck->detach(drc, DEVICE(pdev), spapr_phb_remove_pci_device_cb, phb, errp);
+}
+
+static sPAPRDRConnector *spapr_phb_get_pci_drc(sPAPRPHBState *phb,
+ PCIDevice *pdev)
+{
+ uint32_t busnr = pci_bus_num(PCI_BUS(qdev_get_parent_bus(DEVICE(pdev))));
+ return spapr_dr_connector_by_id(SPAPR_DR_CONNECTOR_TYPE_PCI,
+ (phb->index << 16) |
+ (busnr << 8) |
+ pdev->devfn);
+}
+
+static uint32_t spapr_phb_get_pci_drc_index(sPAPRPHBState *phb,
+ PCIDevice *pdev)
+{
+ sPAPRDRConnector *drc = spapr_phb_get_pci_drc(phb, pdev);
+ sPAPRDRConnectorClass *drck;
+
+ if (!drc) {
+ return 0;
+ }
+
+ drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ return drck->get_index(drc);
+}
+
+static void spapr_phb_hot_plug_child(HotplugHandler *plug_handler,
+ DeviceState *plugged_dev, Error **errp)
+{
+ sPAPRPHBState *phb = SPAPR_PCI_HOST_BRIDGE(DEVICE(plug_handler));
+ PCIDevice *pdev = PCI_DEVICE(plugged_dev);
+ sPAPRDRConnector *drc = spapr_phb_get_pci_drc(phb, pdev);
+ Error *local_err = NULL;
+
+ /* if DR is disabled we don't need to do anything in the case of
+ * hotplug or coldplug callbacks
+ */
+ if (!phb->dr_enabled) {
+ /* if this is a hotplug operation initiated by the user
+ * we need to let them know it's not enabled
+ */
+ if (plugged_dev->hotplugged) {
+ error_setg(errp, QERR_BUS_NO_HOTPLUG,
+ object_get_typename(OBJECT(phb)));
+ }
+ return;
+ }
+
+ g_assert(drc);
+
+ spapr_phb_add_pci_device(drc, phb, pdev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ if (plugged_dev->hotplugged) {
+ spapr_hotplug_req_add_event(drc);
+ }
+}
+
+static void spapr_phb_hot_unplug_child(HotplugHandler *plug_handler,
+ DeviceState *plugged_dev, Error **errp)
+{
+ sPAPRPHBState *phb = SPAPR_PCI_HOST_BRIDGE(DEVICE(plug_handler));
+ PCIDevice *pdev = PCI_DEVICE(plugged_dev);
+ sPAPRDRConnectorClass *drck;
+ sPAPRDRConnector *drc = spapr_phb_get_pci_drc(phb, pdev);
+ Error *local_err = NULL;
+
+ if (!phb->dr_enabled) {
+ error_setg(errp, QERR_BUS_NO_HOTPLUG,
+ object_get_typename(OBJECT(phb)));
+ return;
+ }
+
+ g_assert(drc);
+
+ drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ if (!drck->release_pending(drc)) {
+ spapr_phb_remove_pci_device(drc, phb, pdev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ spapr_hotplug_req_remove_event(drc);
+ }
+}
+
+static void spapr_phb_realize(DeviceState *dev, Error **errp)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ SysBusDevice *s = SYS_BUS_DEVICE(dev);
+ sPAPRPHBState *sphb = SPAPR_PCI_HOST_BRIDGE(s);
+ PCIHostState *phb = PCI_HOST_BRIDGE(s);
+ sPAPRPHBClass *info = SPAPR_PCI_HOST_BRIDGE_GET_CLASS(s);
+ char *namebuf;
+ int i;
+ PCIBus *bus;
+ uint64_t msi_window_size = 4096;
+
+ if (sphb->index != (uint32_t)-1) {
+ hwaddr windows_base;
+
+ if ((sphb->buid != (uint64_t)-1) || (sphb->dma_liobn != (uint32_t)-1)
+ || (sphb->mem_win_addr != (hwaddr)-1)
+ || (sphb->io_win_addr != (hwaddr)-1)) {
+ error_setg(errp, "Either \"index\" or other parameters must"
+ " be specified for PAPR PHB, not both");
+ return;
+ }
+
+ if (sphb->index > SPAPR_PCI_MAX_INDEX) {
+ error_setg(errp, "\"index\" for PAPR PHB is too large (max %u)",
+ SPAPR_PCI_MAX_INDEX);
+ return;
+ }
+
+ sphb->buid = SPAPR_PCI_BASE_BUID + sphb->index;
+ sphb->dma_liobn = SPAPR_PCI_LIOBN(sphb->index, 0);
+
+ windows_base = SPAPR_PCI_WINDOW_BASE
+ + sphb->index * SPAPR_PCI_WINDOW_SPACING;
+ sphb->mem_win_addr = windows_base + SPAPR_PCI_MMIO_WIN_OFF;
+ sphb->io_win_addr = windows_base + SPAPR_PCI_IO_WIN_OFF;
+ }
+
+ if (sphb->buid == (uint64_t)-1) {
+ error_setg(errp, "BUID not specified for PHB");
+ return;
+ }
+
+ if (sphb->dma_liobn == (uint32_t)-1) {
+ error_setg(errp, "LIOBN not specified for PHB");
+ return;
+ }
+
+ if (sphb->mem_win_addr == (hwaddr)-1) {
+ error_setg(errp, "Memory window address not specified for PHB");
+ return;
+ }
+
+ if (sphb->io_win_addr == (hwaddr)-1) {
+ error_setg(errp, "IO window address not specified for PHB");
+ return;
+ }
+
+ if (spapr_pci_find_phb(spapr, sphb->buid)) {
+ error_setg(errp, "PCI host bridges must have unique BUIDs");
+ return;
+ }
+
+ sphb->dtbusname = g_strdup_printf("pci@%" PRIx64, sphb->buid);
+
+ namebuf = alloca(strlen(sphb->dtbusname) + 32);
+
+ /* Initialize memory regions */
+ sprintf(namebuf, "%s.mmio", sphb->dtbusname);
+ memory_region_init(&sphb->memspace, OBJECT(sphb), namebuf, UINT64_MAX);
+
+ sprintf(namebuf, "%s.mmio-alias", sphb->dtbusname);
+ memory_region_init_alias(&sphb->memwindow, OBJECT(sphb),
+ namebuf, &sphb->memspace,
+ SPAPR_PCI_MEM_WIN_BUS_OFFSET, sphb->mem_win_size);
+ memory_region_add_subregion(get_system_memory(), sphb->mem_win_addr,
+ &sphb->memwindow);
+
+ /* Initialize IO regions */
+ sprintf(namebuf, "%s.io", sphb->dtbusname);
+ memory_region_init(&sphb->iospace, OBJECT(sphb),
+ namebuf, SPAPR_PCI_IO_WIN_SIZE);
+
+ sprintf(namebuf, "%s.io-alias", sphb->dtbusname);
+ memory_region_init_alias(&sphb->iowindow, OBJECT(sphb), namebuf,
+ &sphb->iospace, 0, SPAPR_PCI_IO_WIN_SIZE);
+ memory_region_add_subregion(get_system_memory(), sphb->io_win_addr,
+ &sphb->iowindow);
+
+ bus = pci_register_bus(dev, NULL,
+ pci_spapr_set_irq, pci_spapr_map_irq, sphb,
+ &sphb->memspace, &sphb->iospace,
+ PCI_DEVFN(0, 0), PCI_NUM_PINS, TYPE_PCI_BUS);
+ phb->bus = bus;
+ qbus_set_hotplug_handler(BUS(phb->bus), DEVICE(sphb), NULL);
+
+ /*
+ * Initialize PHB address space.
+ * By default there will be at least one subregion for default
+ * 32bit DMA window.
+ * Later the guest might want to create another DMA window
+ * which will become another memory subregion.
+ */
+ sprintf(namebuf, "%s.iommu-root", sphb->dtbusname);
+
+ memory_region_init(&sphb->iommu_root, OBJECT(sphb),
+ namebuf, UINT64_MAX);
+ address_space_init(&sphb->iommu_as, &sphb->iommu_root,
+ sphb->dtbusname);
+
+ /*
+ * As MSI/MSIX interrupts trigger by writing at MSI/MSIX vectors,
+ * we need to allocate some memory to catch those writes coming
+ * from msi_notify()/msix_notify().
+ * As MSIMessage:addr is going to be the same and MSIMessage:data
+ * is going to be a VIRQ number, 4 bytes of the MSI MR will only
+ * be used.
+ *
+ * For KVM we want to ensure that this memory is a full page so that
+ * our memory slot is of page size granularity.
+ */
+#ifdef CONFIG_KVM
+ if (kvm_enabled()) {
+ msi_window_size = getpagesize();
+ }
+#endif
+
+ memory_region_init_io(&sphb->msiwindow, NULL, &spapr_msi_ops, spapr,
+ "msi", msi_window_size);
+ memory_region_add_subregion(&sphb->iommu_root, SPAPR_PCI_MSI_WINDOW,
+ &sphb->msiwindow);
+
+ pci_setup_iommu(bus, spapr_pci_dma_iommu, sphb);
+
+ pci_bus_set_route_irq_fn(bus, spapr_route_intx_pin_to_irq);
+
+ QLIST_INSERT_HEAD(&spapr->phbs, sphb, list);
+
+ /* Initialize the LSI table */
+ for (i = 0; i < PCI_NUM_PINS; i++) {
+ uint32_t irq;
+
+ irq = xics_alloc_block(spapr->icp, 0, 1, true, false);
+ if (!irq) {
+ error_setg(errp, "spapr_allocate_lsi failed");
+ return;
+ }
+
+ sphb->lsi_table[i].irq = irq;
+ }
+
+ /* allocate connectors for child PCI devices */
+ if (sphb->dr_enabled) {
+ for (i = 0; i < PCI_SLOT_MAX * 8; i++) {
+ spapr_dr_connector_new(OBJECT(phb),
+ SPAPR_DR_CONNECTOR_TYPE_PCI,
+ (sphb->index << 16) | i);
+ }
+ }
+
+ if (!info->finish_realize) {
+ error_setg(errp, "finish_realize not defined");
+ return;
+ }
+
+ info->finish_realize(sphb, errp);
+
+ sphb->msi = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_free);
+}
+
+static void spapr_phb_finish_realize(sPAPRPHBState *sphb, Error **errp)
+{
+ sPAPRTCETable *tcet;
+ uint32_t nb_table;
+
+ nb_table = SPAPR_PCI_DMA32_SIZE >> SPAPR_TCE_PAGE_SHIFT;
+ tcet = spapr_tce_new_table(DEVICE(sphb), sphb->dma_liobn,
+ 0, SPAPR_TCE_PAGE_SHIFT, nb_table, false);
+ if (!tcet) {
+ error_setg(errp, "Unable to create TCE table for %s",
+ sphb->dtbusname);
+ return ;
+ }
+
+ /* Register default 32bit DMA window */
+ memory_region_add_subregion(&sphb->iommu_root, 0,
+ spapr_tce_get_iommu(tcet));
+}
+
+static int spapr_phb_children_reset(Object *child, void *opaque)
+{
+ DeviceState *dev = (DeviceState *) object_dynamic_cast(child, TYPE_DEVICE);
+
+ if (dev) {
+ device_reset(dev);
+ }
+
+ return 0;
+}
+
+static void spapr_phb_reset(DeviceState *qdev)
+{
+ /* Reset the IOMMU state */
+ object_child_foreach(OBJECT(qdev), spapr_phb_children_reset, NULL);
+}
+
+static Property spapr_phb_properties[] = {
+ DEFINE_PROP_UINT32("index", sPAPRPHBState, index, -1),
+ DEFINE_PROP_UINT64("buid", sPAPRPHBState, buid, -1),
+ DEFINE_PROP_UINT32("liobn", sPAPRPHBState, dma_liobn, -1),
+ DEFINE_PROP_UINT64("mem_win_addr", sPAPRPHBState, mem_win_addr, -1),
+ DEFINE_PROP_UINT64("mem_win_size", sPAPRPHBState, mem_win_size,
+ SPAPR_PCI_MMIO_WIN_SIZE),
+ DEFINE_PROP_UINT64("io_win_addr", sPAPRPHBState, io_win_addr, -1),
+ DEFINE_PROP_UINT64("io_win_size", sPAPRPHBState, io_win_size,
+ SPAPR_PCI_IO_WIN_SIZE),
+ DEFINE_PROP_BOOL("dynamic-reconfiguration", sPAPRPHBState, dr_enabled,
+ true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_spapr_pci_lsi = {
+ .name = "spapr_pci/lsi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_EQUAL(irq, struct spapr_pci_lsi),
+
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const VMStateDescription vmstate_spapr_pci_msi = {
+ .name = "spapr_pci/msi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT32(key, spapr_pci_msi_mig),
+ VMSTATE_UINT32(value.first_irq, spapr_pci_msi_mig),
+ VMSTATE_UINT32(value.num, spapr_pci_msi_mig),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void spapr_pci_pre_save(void *opaque)
+{
+ sPAPRPHBState *sphb = opaque;
+ GHashTableIter iter;
+ gpointer key, value;
+ int i;
+
+ if (sphb->msi_devs) {
+ g_free(sphb->msi_devs);
+ sphb->msi_devs = NULL;
+ }
+ sphb->msi_devs_num = g_hash_table_size(sphb->msi);
+ if (!sphb->msi_devs_num) {
+ return;
+ }
+ sphb->msi_devs = g_malloc(sphb->msi_devs_num * sizeof(spapr_pci_msi_mig));
+
+ g_hash_table_iter_init(&iter, sphb->msi);
+ for (i = 0; g_hash_table_iter_next(&iter, &key, &value); ++i) {
+ sphb->msi_devs[i].key = *(uint32_t *) key;
+ sphb->msi_devs[i].value = *(spapr_pci_msi *) value;
+ }
+}
+
+static int spapr_pci_post_load(void *opaque, int version_id)
+{
+ sPAPRPHBState *sphb = opaque;
+ gpointer key, value;
+ int i;
+
+ for (i = 0; i < sphb->msi_devs_num; ++i) {
+ key = g_memdup(&sphb->msi_devs[i].key,
+ sizeof(sphb->msi_devs[i].key));
+ value = g_memdup(&sphb->msi_devs[i].value,
+ sizeof(sphb->msi_devs[i].value));
+ g_hash_table_insert(sphb->msi, key, value);
+ }
+ if (sphb->msi_devs) {
+ g_free(sphb->msi_devs);
+ sphb->msi_devs = NULL;
+ }
+ sphb->msi_devs_num = 0;
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_spapr_pci = {
+ .name = "spapr_pci",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .pre_save = spapr_pci_pre_save,
+ .post_load = spapr_pci_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64_EQUAL(buid, sPAPRPHBState),
+ VMSTATE_UINT32_EQUAL(dma_liobn, sPAPRPHBState),
+ VMSTATE_UINT64_EQUAL(mem_win_addr, sPAPRPHBState),
+ VMSTATE_UINT64_EQUAL(mem_win_size, sPAPRPHBState),
+ VMSTATE_UINT64_EQUAL(io_win_addr, sPAPRPHBState),
+ VMSTATE_UINT64_EQUAL(io_win_size, sPAPRPHBState),
+ VMSTATE_STRUCT_ARRAY(lsi_table, sPAPRPHBState, PCI_NUM_PINS, 0,
+ vmstate_spapr_pci_lsi, struct spapr_pci_lsi),
+ VMSTATE_INT32(msi_devs_num, sPAPRPHBState),
+ VMSTATE_STRUCT_VARRAY_ALLOC(msi_devs, sPAPRPHBState, msi_devs_num, 0,
+ vmstate_spapr_pci_msi, spapr_pci_msi_mig),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const char *spapr_phb_root_bus_path(PCIHostState *host_bridge,
+ PCIBus *rootbus)
+{
+ sPAPRPHBState *sphb = SPAPR_PCI_HOST_BRIDGE(host_bridge);
+
+ return sphb->dtbusname;
+}
+
+static void spapr_phb_class_init(ObjectClass *klass, void *data)
+{
+ PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ sPAPRPHBClass *spc = SPAPR_PCI_HOST_BRIDGE_CLASS(klass);
+ HotplugHandlerClass *hp = HOTPLUG_HANDLER_CLASS(klass);
+
+ hc->root_bus_path = spapr_phb_root_bus_path;
+ dc->realize = spapr_phb_realize;
+ dc->props = spapr_phb_properties;
+ dc->reset = spapr_phb_reset;
+ dc->vmsd = &vmstate_spapr_pci;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->cannot_instantiate_with_device_add_yet = false;
+ spc->finish_realize = spapr_phb_finish_realize;
+ hp->plug = spapr_phb_hot_plug_child;
+ hp->unplug = spapr_phb_hot_unplug_child;
+}
+
+static const TypeInfo spapr_phb_info = {
+ .name = TYPE_SPAPR_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(sPAPRPHBState),
+ .class_init = spapr_phb_class_init,
+ .class_size = sizeof(sPAPRPHBClass),
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+PCIHostState *spapr_create_phb(sPAPRMachineState *spapr, int index)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, TYPE_SPAPR_PCI_HOST_BRIDGE);
+ qdev_prop_set_uint32(dev, "index", index);
+ qdev_init_nofail(dev);
+
+ return PCI_HOST_BRIDGE(dev);
+}
+
+typedef struct sPAPRFDT {
+ void *fdt;
+ int node_off;
+ sPAPRPHBState *sphb;
+} sPAPRFDT;
+
+static void spapr_populate_pci_devices_dt(PCIBus *bus, PCIDevice *pdev,
+ void *opaque)
+{
+ PCIBus *sec_bus;
+ sPAPRFDT *p = opaque;
+ int offset;
+ sPAPRFDT s_fdt;
+
+ offset = spapr_create_pci_child_dt(p->sphb, pdev, p->fdt, p->node_off);
+ if (!offset) {
+ error_report("Failed to create pci child device tree node");
+ return;
+ }
+
+ if ((pci_default_read_config(pdev, PCI_HEADER_TYPE, 1) !=
+ PCI_HEADER_TYPE_BRIDGE)) {
+ return;
+ }
+
+ sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(pdev));
+ if (!sec_bus) {
+ return;
+ }
+
+ s_fdt.fdt = p->fdt;
+ s_fdt.node_off = offset;
+ s_fdt.sphb = p->sphb;
+ pci_for_each_device(sec_bus, pci_bus_num(sec_bus),
+ spapr_populate_pci_devices_dt,
+ &s_fdt);
+}
+
+static void spapr_phb_pci_enumerate_bridge(PCIBus *bus, PCIDevice *pdev,
+ void *opaque)
+{
+ unsigned int *bus_no = opaque;
+ unsigned int primary = *bus_no;
+ unsigned int subordinate = 0xff;
+ PCIBus *sec_bus = NULL;
+
+ if ((pci_default_read_config(pdev, PCI_HEADER_TYPE, 1) !=
+ PCI_HEADER_TYPE_BRIDGE)) {
+ return;
+ }
+
+ (*bus_no)++;
+ pci_default_write_config(pdev, PCI_PRIMARY_BUS, primary, 1);
+ pci_default_write_config(pdev, PCI_SECONDARY_BUS, *bus_no, 1);
+ pci_default_write_config(pdev, PCI_SUBORDINATE_BUS, *bus_no, 1);
+
+ sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(pdev));
+ if (!sec_bus) {
+ return;
+ }
+
+ pci_default_write_config(pdev, PCI_SUBORDINATE_BUS, subordinate, 1);
+ pci_for_each_device(sec_bus, pci_bus_num(sec_bus),
+ spapr_phb_pci_enumerate_bridge, bus_no);
+ pci_default_write_config(pdev, PCI_SUBORDINATE_BUS, *bus_no, 1);
+}
+
+static void spapr_phb_pci_enumerate(sPAPRPHBState *phb)
+{
+ PCIBus *bus = PCI_HOST_BRIDGE(phb)->bus;
+ unsigned int bus_no = 0;
+
+ pci_for_each_device(bus, pci_bus_num(bus),
+ spapr_phb_pci_enumerate_bridge,
+ &bus_no);
+
+}
+
+int spapr_populate_pci_dt(sPAPRPHBState *phb,
+ uint32_t xics_phandle,
+ void *fdt)
+{
+ int bus_off, i, j, ret;
+ char nodename[FDT_NAME_MAX];
+ uint32_t bus_range[] = { cpu_to_be32(0), cpu_to_be32(0xff) };
+ const uint64_t mmiosize = memory_region_size(&phb->memwindow);
+ const uint64_t w32max = (1ULL << 32) - SPAPR_PCI_MEM_WIN_BUS_OFFSET;
+ const uint64_t w32size = MIN(w32max, mmiosize);
+ const uint64_t w64size = (mmiosize > w32size) ? (mmiosize - w32size) : 0;
+ struct {
+ uint32_t hi;
+ uint64_t child;
+ uint64_t parent;
+ uint64_t size;
+ } QEMU_PACKED ranges[] = {
+ {
+ cpu_to_be32(b_ss(1)), cpu_to_be64(0),
+ cpu_to_be64(phb->io_win_addr),
+ cpu_to_be64(memory_region_size(&phb->iospace)),
+ },
+ {
+ cpu_to_be32(b_ss(2)), cpu_to_be64(SPAPR_PCI_MEM_WIN_BUS_OFFSET),
+ cpu_to_be64(phb->mem_win_addr),
+ cpu_to_be64(w32size),
+ },
+ {
+ cpu_to_be32(b_ss(3)), cpu_to_be64(1ULL << 32),
+ cpu_to_be64(phb->mem_win_addr + w32size),
+ cpu_to_be64(w64size)
+ },
+ };
+ const unsigned sizeof_ranges = (w64size ? 3 : 2) * sizeof(ranges[0]);
+ uint64_t bus_reg[] = { cpu_to_be64(phb->buid), 0 };
+ uint32_t interrupt_map_mask[] = {
+ cpu_to_be32(b_ddddd(-1)|b_fff(0)), 0x0, 0x0, cpu_to_be32(-1)};
+ uint32_t interrupt_map[PCI_SLOT_MAX * PCI_NUM_PINS][7];
+ sPAPRTCETable *tcet;
+ PCIBus *bus = PCI_HOST_BRIDGE(phb)->bus;
+ sPAPRFDT s_fdt;
+
+ /* Start populating the FDT */
+ snprintf(nodename, FDT_NAME_MAX, "pci@%" PRIx64, phb->buid);
+ bus_off = fdt_add_subnode(fdt, 0, nodename);
+ if (bus_off < 0) {
+ return bus_off;
+ }
+
+ /* Write PHB properties */
+ _FDT(fdt_setprop_string(fdt, bus_off, "device_type", "pci"));
+ _FDT(fdt_setprop_string(fdt, bus_off, "compatible", "IBM,Logical_PHB"));
+ _FDT(fdt_setprop_cell(fdt, bus_off, "#address-cells", 0x3));
+ _FDT(fdt_setprop_cell(fdt, bus_off, "#size-cells", 0x2));
+ _FDT(fdt_setprop_cell(fdt, bus_off, "#interrupt-cells", 0x1));
+ _FDT(fdt_setprop(fdt, bus_off, "used-by-rtas", NULL, 0));
+ _FDT(fdt_setprop(fdt, bus_off, "bus-range", &bus_range, sizeof(bus_range)));
+ _FDT(fdt_setprop(fdt, bus_off, "ranges", &ranges, sizeof_ranges));
+ _FDT(fdt_setprop(fdt, bus_off, "reg", &bus_reg, sizeof(bus_reg)));
+ _FDT(fdt_setprop_cell(fdt, bus_off, "ibm,pci-config-space-type", 0x1));
+ _FDT(fdt_setprop_cell(fdt, bus_off, "ibm,pe-total-#msi", XICS_IRQS));
+
+ /* Build the interrupt-map, this must matches what is done
+ * in pci_spapr_map_irq
+ */
+ _FDT(fdt_setprop(fdt, bus_off, "interrupt-map-mask",
+ &interrupt_map_mask, sizeof(interrupt_map_mask)));
+ for (i = 0; i < PCI_SLOT_MAX; i++) {
+ for (j = 0; j < PCI_NUM_PINS; j++) {
+ uint32_t *irqmap = interrupt_map[i*PCI_NUM_PINS + j];
+ int lsi_num = pci_spapr_swizzle(i, j);
+
+ irqmap[0] = cpu_to_be32(b_ddddd(i)|b_fff(0));
+ irqmap[1] = 0;
+ irqmap[2] = 0;
+ irqmap[3] = cpu_to_be32(j+1);
+ irqmap[4] = cpu_to_be32(xics_phandle);
+ irqmap[5] = cpu_to_be32(phb->lsi_table[lsi_num].irq);
+ irqmap[6] = cpu_to_be32(0x8);
+ }
+ }
+ /* Write interrupt map */
+ _FDT(fdt_setprop(fdt, bus_off, "interrupt-map", &interrupt_map,
+ sizeof(interrupt_map)));
+
+ tcet = spapr_tce_find_by_liobn(SPAPR_PCI_LIOBN(phb->index, 0));
+ spapr_dma_dt(fdt, bus_off, "ibm,dma-window",
+ tcet->liobn, tcet->bus_offset,
+ tcet->nb_table << tcet->page_shift);
+
+ /* Walk the bridges and program the bus numbers*/
+ spapr_phb_pci_enumerate(phb);
+ _FDT(fdt_setprop_cell(fdt, bus_off, "qemu,phb-enumerated", 0x1));
+
+ /* Populate tree nodes with PCI devices attached */
+ s_fdt.fdt = fdt;
+ s_fdt.node_off = bus_off;
+ s_fdt.sphb = phb;
+ pci_for_each_device(bus, pci_bus_num(bus),
+ spapr_populate_pci_devices_dt,
+ &s_fdt);
+
+ ret = spapr_drc_populate_dt(fdt, bus_off, OBJECT(phb),
+ SPAPR_DR_CONNECTOR_TYPE_PCI);
+ if (ret) {
+ return ret;
+ }
+
+ return 0;
+}
+
+void spapr_pci_rtas_init(void)
+{
+ spapr_rtas_register(RTAS_READ_PCI_CONFIG, "read-pci-config",
+ rtas_read_pci_config);
+ spapr_rtas_register(RTAS_WRITE_PCI_CONFIG, "write-pci-config",
+ rtas_write_pci_config);
+ spapr_rtas_register(RTAS_IBM_READ_PCI_CONFIG, "ibm,read-pci-config",
+ rtas_ibm_read_pci_config);
+ spapr_rtas_register(RTAS_IBM_WRITE_PCI_CONFIG, "ibm,write-pci-config",
+ rtas_ibm_write_pci_config);
+ if (msi_supported) {
+ spapr_rtas_register(RTAS_IBM_QUERY_INTERRUPT_SOURCE_NUMBER,
+ "ibm,query-interrupt-source-number",
+ rtas_ibm_query_interrupt_source_number);
+ spapr_rtas_register(RTAS_IBM_CHANGE_MSI, "ibm,change-msi",
+ rtas_ibm_change_msi);
+ }
+
+ spapr_rtas_register(RTAS_IBM_SET_EEH_OPTION,
+ "ibm,set-eeh-option",
+ rtas_ibm_set_eeh_option);
+ spapr_rtas_register(RTAS_IBM_GET_CONFIG_ADDR_INFO2,
+ "ibm,get-config-addr-info2",
+ rtas_ibm_get_config_addr_info2);
+ spapr_rtas_register(RTAS_IBM_READ_SLOT_RESET_STATE2,
+ "ibm,read-slot-reset-state2",
+ rtas_ibm_read_slot_reset_state2);
+ spapr_rtas_register(RTAS_IBM_SET_SLOT_RESET,
+ "ibm,set-slot-reset",
+ rtas_ibm_set_slot_reset);
+ spapr_rtas_register(RTAS_IBM_CONFIGURE_PE,
+ "ibm,configure-pe",
+ rtas_ibm_configure_pe);
+ spapr_rtas_register(RTAS_IBM_SLOT_ERROR_DETAIL,
+ "ibm,slot-error-detail",
+ rtas_ibm_slot_error_detail);
+}
+
+static void spapr_pci_register_types(void)
+{
+ type_register_static(&spapr_phb_info);
+}
+
+type_init(spapr_pci_register_types)
+
+static int spapr_switch_one_vga(DeviceState *dev, void *opaque)
+{
+ bool be = *(bool *)opaque;
+
+ if (object_dynamic_cast(OBJECT(dev), "VGA")
+ || object_dynamic_cast(OBJECT(dev), "secondary-vga")) {
+ object_property_set_bool(OBJECT(dev), be, "big-endian-framebuffer",
+ &error_abort);
+ }
+ return 0;
+}
+
+void spapr_pci_switch_vga(bool big_endian)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ sPAPRPHBState *sphb;
+
+ /*
+ * For backward compatibility with existing guests, we switch
+ * the endianness of the VGA controller when changing the guest
+ * interrupt mode
+ */
+ QLIST_FOREACH(sphb, &spapr->phbs, list) {
+ BusState *bus = &PCI_HOST_BRIDGE(sphb)->bus->qbus;
+ qbus_walk_children(bus, spapr_switch_one_vga, NULL, NULL, NULL,
+ &big_endian);
+ }
+}
diff --git a/hw/ppc/spapr_pci_vfio.c b/hw/ppc/spapr_pci_vfio.c
new file mode 100644
index 00000000..cca45ed3
--- /dev/null
+++ b/hw/ppc/spapr_pci_vfio.c
@@ -0,0 +1,280 @@
+/*
+ * QEMU sPAPR PCI host for VFIO
+ *
+ * Copyright (c) 2011-2014 Alexey Kardashevskiy, IBM Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/ppc/spapr.h"
+#include "hw/pci-host/spapr.h"
+#include "hw/pci/msix.h"
+#include "linux/vfio.h"
+#include "hw/vfio/vfio.h"
+
+static Property spapr_phb_vfio_properties[] = {
+ DEFINE_PROP_INT32("iommu", sPAPRPHBVFIOState, iommugroupid, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void spapr_phb_vfio_finish_realize(sPAPRPHBState *sphb, Error **errp)
+{
+ sPAPRPHBVFIOState *svphb = SPAPR_PCI_VFIO_HOST_BRIDGE(sphb);
+ struct vfio_iommu_spapr_tce_info info = { .argsz = sizeof(info) };
+ int ret;
+ sPAPRTCETable *tcet;
+ uint32_t liobn = svphb->phb.dma_liobn;
+
+ if (svphb->iommugroupid == -1) {
+ error_setg(errp, "Wrong IOMMU group ID %d", svphb->iommugroupid);
+ return;
+ }
+
+ ret = vfio_container_ioctl(&svphb->phb.iommu_as, svphb->iommugroupid,
+ VFIO_CHECK_EXTENSION,
+ (void *) VFIO_SPAPR_TCE_IOMMU);
+ if (ret != 1) {
+ error_setg_errno(errp, -ret,
+ "spapr-vfio: SPAPR extension is not supported");
+ return;
+ }
+
+ ret = vfio_container_ioctl(&svphb->phb.iommu_as, svphb->iommugroupid,
+ VFIO_IOMMU_SPAPR_TCE_GET_INFO, &info);
+ if (ret) {
+ error_setg_errno(errp, -ret,
+ "spapr-vfio: get info from container failed");
+ return;
+ }
+
+ tcet = spapr_tce_new_table(DEVICE(sphb), liobn, info.dma32_window_start,
+ SPAPR_TCE_PAGE_SHIFT,
+ info.dma32_window_size >> SPAPR_TCE_PAGE_SHIFT,
+ true);
+ if (!tcet) {
+ error_setg(errp, "spapr-vfio: failed to create VFIO TCE table");
+ return;
+ }
+
+ /* Register default 32bit DMA window */
+ memory_region_add_subregion(&sphb->iommu_root, tcet->bus_offset,
+ spapr_tce_get_iommu(tcet));
+}
+
+static void spapr_phb_vfio_eeh_reenable(sPAPRPHBVFIOState *svphb)
+{
+ struct vfio_eeh_pe_op op = {
+ .argsz = sizeof(op),
+ .op = VFIO_EEH_PE_ENABLE
+ };
+
+ vfio_container_ioctl(&svphb->phb.iommu_as,
+ svphb->iommugroupid, VFIO_EEH_PE_OP, &op);
+}
+
+static void spapr_phb_vfio_reset(DeviceState *qdev)
+{
+ /*
+ * The PE might be in frozen state. To reenable the EEH
+ * functionality on it will clean the frozen state, which
+ * ensures that the contained PCI devices will work properly
+ * after reboot.
+ */
+ spapr_phb_vfio_eeh_reenable(SPAPR_PCI_VFIO_HOST_BRIDGE(qdev));
+}
+
+static int spapr_phb_vfio_eeh_set_option(sPAPRPHBState *sphb,
+ unsigned int addr, int option)
+{
+ sPAPRPHBVFIOState *svphb = SPAPR_PCI_VFIO_HOST_BRIDGE(sphb);
+ struct vfio_eeh_pe_op op = { .argsz = sizeof(op) };
+ int ret;
+
+ switch (option) {
+ case RTAS_EEH_DISABLE:
+ op.op = VFIO_EEH_PE_DISABLE;
+ break;
+ case RTAS_EEH_ENABLE: {
+ PCIHostState *phb;
+ PCIDevice *pdev;
+
+ /*
+ * The EEH functionality is enabled on basis of PCI device,
+ * instead of PE. We need check the validity of the PCI
+ * device address.
+ */
+ phb = PCI_HOST_BRIDGE(sphb);
+ pdev = pci_find_device(phb->bus,
+ (addr >> 16) & 0xFF, (addr >> 8) & 0xFF);
+ if (!pdev) {
+ return RTAS_OUT_PARAM_ERROR;
+ }
+
+ op.op = VFIO_EEH_PE_ENABLE;
+ break;
+ }
+ case RTAS_EEH_THAW_IO:
+ op.op = VFIO_EEH_PE_UNFREEZE_IO;
+ break;
+ case RTAS_EEH_THAW_DMA:
+ op.op = VFIO_EEH_PE_UNFREEZE_DMA;
+ break;
+ default:
+ return RTAS_OUT_PARAM_ERROR;
+ }
+
+ ret = vfio_container_ioctl(&svphb->phb.iommu_as, svphb->iommugroupid,
+ VFIO_EEH_PE_OP, &op);
+ if (ret < 0) {
+ return RTAS_OUT_HW_ERROR;
+ }
+
+ return RTAS_OUT_SUCCESS;
+}
+
+static int spapr_phb_vfio_eeh_get_state(sPAPRPHBState *sphb, int *state)
+{
+ sPAPRPHBVFIOState *svphb = SPAPR_PCI_VFIO_HOST_BRIDGE(sphb);
+ struct vfio_eeh_pe_op op = { .argsz = sizeof(op) };
+ int ret;
+
+ op.op = VFIO_EEH_PE_GET_STATE;
+ ret = vfio_container_ioctl(&svphb->phb.iommu_as, svphb->iommugroupid,
+ VFIO_EEH_PE_OP, &op);
+ if (ret < 0) {
+ return RTAS_OUT_PARAM_ERROR;
+ }
+
+ *state = ret;
+ return RTAS_OUT_SUCCESS;
+}
+
+static void spapr_phb_vfio_eeh_clear_dev_msix(PCIBus *bus,
+ PCIDevice *pdev,
+ void *opaque)
+{
+ /* Check if the device is VFIO PCI device */
+ if (!object_dynamic_cast(OBJECT(pdev), "vfio-pci")) {
+ return;
+ }
+
+ /*
+ * The MSIx table will be cleaned out by reset. We need
+ * disable it so that it can be reenabled properly. Also,
+ * the cached MSIx table should be cleared as it's not
+ * reflecting the contents in hardware.
+ */
+ if (msix_enabled(pdev)) {
+ uint16_t flags;
+
+ flags = pci_host_config_read_common(pdev,
+ pdev->msix_cap + PCI_MSIX_FLAGS,
+ pci_config_size(pdev), 2);
+ flags &= ~PCI_MSIX_FLAGS_ENABLE;
+ pci_host_config_write_common(pdev,
+ pdev->msix_cap + PCI_MSIX_FLAGS,
+ pci_config_size(pdev), flags, 2);
+ }
+
+ msix_reset(pdev);
+}
+
+static void spapr_phb_vfio_eeh_clear_bus_msix(PCIBus *bus, void *opaque)
+{
+ pci_for_each_device(bus, pci_bus_num(bus),
+ spapr_phb_vfio_eeh_clear_dev_msix, NULL);
+}
+
+static void spapr_phb_vfio_eeh_pre_reset(sPAPRPHBState *sphb)
+{
+ PCIHostState *phb = PCI_HOST_BRIDGE(sphb);
+
+ pci_for_each_bus(phb->bus, spapr_phb_vfio_eeh_clear_bus_msix, NULL);
+}
+
+static int spapr_phb_vfio_eeh_reset(sPAPRPHBState *sphb, int option)
+{
+ sPAPRPHBVFIOState *svphb = SPAPR_PCI_VFIO_HOST_BRIDGE(sphb);
+ struct vfio_eeh_pe_op op = { .argsz = sizeof(op) };
+ int ret;
+
+ switch (option) {
+ case RTAS_SLOT_RESET_DEACTIVATE:
+ op.op = VFIO_EEH_PE_RESET_DEACTIVATE;
+ break;
+ case RTAS_SLOT_RESET_HOT:
+ spapr_phb_vfio_eeh_pre_reset(sphb);
+ op.op = VFIO_EEH_PE_RESET_HOT;
+ break;
+ case RTAS_SLOT_RESET_FUNDAMENTAL:
+ spapr_phb_vfio_eeh_pre_reset(sphb);
+ op.op = VFIO_EEH_PE_RESET_FUNDAMENTAL;
+ break;
+ default:
+ return RTAS_OUT_PARAM_ERROR;
+ }
+
+ ret = vfio_container_ioctl(&svphb->phb.iommu_as, svphb->iommugroupid,
+ VFIO_EEH_PE_OP, &op);
+ if (ret < 0) {
+ return RTAS_OUT_HW_ERROR;
+ }
+
+ return RTAS_OUT_SUCCESS;
+}
+
+static int spapr_phb_vfio_eeh_configure(sPAPRPHBState *sphb)
+{
+ sPAPRPHBVFIOState *svphb = SPAPR_PCI_VFIO_HOST_BRIDGE(sphb);
+ struct vfio_eeh_pe_op op = { .argsz = sizeof(op) };
+ int ret;
+
+ op.op = VFIO_EEH_PE_CONFIGURE;
+ ret = vfio_container_ioctl(&svphb->phb.iommu_as, svphb->iommugroupid,
+ VFIO_EEH_PE_OP, &op);
+ if (ret < 0) {
+ return RTAS_OUT_PARAM_ERROR;
+ }
+
+ return RTAS_OUT_SUCCESS;
+}
+
+static void spapr_phb_vfio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ sPAPRPHBClass *spc = SPAPR_PCI_HOST_BRIDGE_CLASS(klass);
+
+ dc->props = spapr_phb_vfio_properties;
+ dc->reset = spapr_phb_vfio_reset;
+ spc->finish_realize = spapr_phb_vfio_finish_realize;
+ spc->eeh_set_option = spapr_phb_vfio_eeh_set_option;
+ spc->eeh_get_state = spapr_phb_vfio_eeh_get_state;
+ spc->eeh_reset = spapr_phb_vfio_eeh_reset;
+ spc->eeh_configure = spapr_phb_vfio_eeh_configure;
+}
+
+static const TypeInfo spapr_phb_vfio_info = {
+ .name = TYPE_SPAPR_PCI_VFIO_HOST_BRIDGE,
+ .parent = TYPE_SPAPR_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(sPAPRPHBVFIOState),
+ .class_init = spapr_phb_vfio_class_init,
+ .class_size = sizeof(sPAPRPHBClass),
+};
+
+static void spapr_pci_vfio_register_types(void)
+{
+ type_register_static(&spapr_phb_vfio_info);
+}
+
+type_init(spapr_pci_vfio_register_types)
diff --git a/hw/ppc/spapr_rtas.c b/hw/ppc/spapr_rtas.c
new file mode 100644
index 00000000..2986f94f
--- /dev/null
+++ b/hw/ppc/spapr_rtas.c
@@ -0,0 +1,752 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * Hypercall based emulated RTAS
+ *
+ * Copyright (c) 2010-2011 David Gibson, IBM Corporation.
+ *
+ * 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.
+ *
+ */
+#include "cpu.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/char.h"
+#include "hw/qdev.h"
+#include "sysemu/device_tree.h"
+#include "sysemu/cpus.h"
+
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "qapi-event.h"
+
+#include <libfdt.h>
+#include "hw/ppc/spapr_drc.h"
+
+/* #define DEBUG_SPAPR */
+
+#ifdef DEBUG_SPAPR
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+static sPAPRConfigureConnectorState *spapr_ccs_find(sPAPRMachineState *spapr,
+ uint32_t drc_index)
+{
+ sPAPRConfigureConnectorState *ccs = NULL;
+
+ QTAILQ_FOREACH(ccs, &spapr->ccs_list, next) {
+ if (ccs->drc_index == drc_index) {
+ break;
+ }
+ }
+
+ return ccs;
+}
+
+static void spapr_ccs_add(sPAPRMachineState *spapr,
+ sPAPRConfigureConnectorState *ccs)
+{
+ g_assert(!spapr_ccs_find(spapr, ccs->drc_index));
+ QTAILQ_INSERT_HEAD(&spapr->ccs_list, ccs, next);
+}
+
+static void spapr_ccs_remove(sPAPRMachineState *spapr,
+ sPAPRConfigureConnectorState *ccs)
+{
+ QTAILQ_REMOVE(&spapr->ccs_list, ccs, next);
+ g_free(ccs);
+}
+
+void spapr_ccs_reset_hook(void *opaque)
+{
+ sPAPRMachineState *spapr = opaque;
+ sPAPRConfigureConnectorState *ccs, *ccs_tmp;
+
+ QTAILQ_FOREACH_SAFE(ccs, &spapr->ccs_list, next, ccs_tmp) {
+ spapr_ccs_remove(spapr, ccs);
+ }
+}
+
+static void rtas_display_character(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint8_t c = rtas_ld(args, 0);
+ VIOsPAPRDevice *sdev = vty_lookup(spapr, 0);
+
+ if (!sdev) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ } else {
+ vty_putchars(sdev, &c, sizeof(c));
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ }
+}
+
+static void rtas_power_off(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ if (nargs != 2 || nret != 1) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+ qemu_system_shutdown_request();
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+static void rtas_system_reboot(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ if (nargs != 0 || nret != 1) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+ qemu_system_reset_request();
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+static void rtas_query_cpu_stopped_state(PowerPCCPU *cpu_,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ target_ulong id;
+ PowerPCCPU *cpu;
+
+ if (nargs != 1 || nret != 2) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ id = rtas_ld(args, 0);
+ cpu = ppc_get_vcpu_by_dt_id(id);
+ if (cpu != NULL) {
+ if (CPU(cpu)->halted) {
+ rtas_st(rets, 1, 0);
+ } else {
+ rtas_st(rets, 1, 2);
+ }
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ return;
+ }
+
+ /* Didn't find a matching cpu */
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
+static void rtas_start_cpu(PowerPCCPU *cpu_, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ target_ulong id, start, r3;
+ PowerPCCPU *cpu;
+
+ if (nargs != 3 || nret != 1) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ id = rtas_ld(args, 0);
+ start = rtas_ld(args, 1);
+ r3 = rtas_ld(args, 2);
+
+ cpu = ppc_get_vcpu_by_dt_id(id);
+ if (cpu != NULL) {
+ CPUState *cs = CPU(cpu);
+ CPUPPCState *env = &cpu->env;
+
+ if (!cs->halted) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ /* This will make sure qemu state is up to date with kvm, and
+ * mark it dirty so our changes get flushed back before the
+ * new cpu enters */
+ kvm_cpu_synchronize_state(cs);
+
+ env->msr = (1ULL << MSR_SF) | (1ULL << MSR_ME);
+ env->nip = start;
+ env->gpr[3] = r3;
+ cs->halted = 0;
+
+ qemu_cpu_kick(cs);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ return;
+ }
+
+ /* Didn't find a matching cpu */
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+}
+
+static void rtas_stop_self(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ CPUState *cs = CPU(cpu);
+ CPUPPCState *env = &cpu->env;
+
+ cs->halted = 1;
+ cpu_exit(cs);
+ /*
+ * While stopping a CPU, the guest calls H_CPPR which
+ * effectively disables interrupts on XICS level.
+ * However decrementer interrupts in TCG can still
+ * wake the CPU up so here we disable interrupts in MSR
+ * as well.
+ * As rtas_start_cpu() resets the whole MSR anyway, there is
+ * no need to bother with specific bits, we just clear it.
+ */
+ env->msr = 0;
+}
+
+static void rtas_ibm_get_system_parameter(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ target_ulong parameter = rtas_ld(args, 0);
+ target_ulong buffer = rtas_ld(args, 1);
+ target_ulong length = rtas_ld(args, 2);
+ target_ulong ret = RTAS_OUT_SUCCESS;
+
+ switch (parameter) {
+ case RTAS_SYSPARM_SPLPAR_CHARACTERISTICS: {
+ char *param_val = g_strdup_printf("MaxEntCap=%d,MaxPlatProcs=%d",
+ max_cpus, smp_cpus);
+ rtas_st_buffer(buffer, length, (uint8_t *)param_val, strlen(param_val));
+ g_free(param_val);
+ break;
+ }
+ case RTAS_SYSPARM_DIAGNOSTICS_RUN_MODE: {
+ uint8_t param_val = DIAGNOSTICS_RUN_MODE_DISABLED;
+
+ rtas_st_buffer(buffer, length, &param_val, sizeof(param_val));
+ break;
+ }
+ case RTAS_SYSPARM_UUID:
+ rtas_st_buffer(buffer, length, qemu_uuid, (qemu_uuid_set ? 16 : 0));
+ break;
+ default:
+ ret = RTAS_OUT_NOT_SUPPORTED;
+ }
+
+ rtas_st(rets, 0, ret);
+}
+
+static void rtas_ibm_set_system_parameter(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ target_ulong parameter = rtas_ld(args, 0);
+ target_ulong ret = RTAS_OUT_NOT_SUPPORTED;
+
+ switch (parameter) {
+ case RTAS_SYSPARM_SPLPAR_CHARACTERISTICS:
+ case RTAS_SYSPARM_DIAGNOSTICS_RUN_MODE:
+ case RTAS_SYSPARM_UUID:
+ ret = RTAS_OUT_NOT_AUTHORIZED;
+ break;
+ }
+
+ rtas_st(rets, 0, ret);
+}
+
+static void rtas_ibm_os_term(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ target_ulong ret = 0;
+
+ qapi_event_send_guest_panicked(GUEST_PANIC_ACTION_PAUSE, &error_abort);
+
+ rtas_st(rets, 0, ret);
+}
+
+static void rtas_set_power_level(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ int32_t power_domain;
+
+ if (nargs != 2 || nret != 2) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ /* we currently only use a single, "live insert" powerdomain for
+ * hotplugged/dlpar'd resources, so the power is always live/full (100)
+ */
+ power_domain = rtas_ld(args, 0);
+ if (power_domain != -1) {
+ rtas_st(rets, 0, RTAS_OUT_NOT_SUPPORTED);
+ return;
+ }
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, 100);
+}
+
+static void rtas_get_power_level(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ int32_t power_domain;
+
+ if (nargs != 1 || nret != 2) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ /* we currently only use a single, "live insert" powerdomain for
+ * hotplugged/dlpar'd resources, so the power is always live/full (100)
+ */
+ power_domain = rtas_ld(args, 0);
+ if (power_domain != -1) {
+ rtas_st(rets, 0, RTAS_OUT_NOT_SUPPORTED);
+ return;
+ }
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, 100);
+}
+
+static bool sensor_type_is_dr(uint32_t sensor_type)
+{
+ switch (sensor_type) {
+ case RTAS_SENSOR_TYPE_ISOLATION_STATE:
+ case RTAS_SENSOR_TYPE_DR:
+ case RTAS_SENSOR_TYPE_ALLOCATION_STATE:
+ return true;
+ }
+
+ return false;
+}
+
+static void rtas_set_indicator(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ uint32_t sensor_type;
+ uint32_t sensor_index;
+ uint32_t sensor_state;
+ sPAPRDRConnector *drc;
+ sPAPRDRConnectorClass *drck;
+
+ if (nargs != 3 || nret != 1) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ sensor_type = rtas_ld(args, 0);
+ sensor_index = rtas_ld(args, 1);
+ sensor_state = rtas_ld(args, 2);
+
+ if (!sensor_type_is_dr(sensor_type)) {
+ goto out_unimplemented;
+ }
+
+ /* if this is a DR sensor we can assume sensor_index == drc_index */
+ drc = spapr_dr_connector_by_index(sensor_index);
+ if (!drc) {
+ DPRINTF("rtas_set_indicator: invalid sensor/DRC index: %xh\n",
+ sensor_index);
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+ drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+
+ switch (sensor_type) {
+ case RTAS_SENSOR_TYPE_ISOLATION_STATE:
+ /* if the guest is configuring a device attached to this
+ * DRC, we should reset the configuration state at this
+ * point since it may no longer be reliable (guest released
+ * device and needs to start over, or unplug occurred so
+ * the FDT is no longer valid)
+ */
+ if (sensor_state == SPAPR_DR_ISOLATION_STATE_ISOLATED) {
+ sPAPRConfigureConnectorState *ccs = spapr_ccs_find(spapr,
+ sensor_index);
+ if (ccs) {
+ spapr_ccs_remove(spapr, ccs);
+ }
+ }
+ drck->set_isolation_state(drc, sensor_state);
+ break;
+ case RTAS_SENSOR_TYPE_DR:
+ drck->set_indicator_state(drc, sensor_state);
+ break;
+ case RTAS_SENSOR_TYPE_ALLOCATION_STATE:
+ drck->set_allocation_state(drc, sensor_state);
+ break;
+ default:
+ goto out_unimplemented;
+ }
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ return;
+
+out_unimplemented:
+ /* currently only DR-related sensors are implemented */
+ DPRINTF("rtas_set_indicator: sensor/indicator not implemented: %d\n",
+ sensor_type);
+ rtas_st(rets, 0, RTAS_OUT_NOT_SUPPORTED);
+}
+
+static void rtas_get_sensor_state(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ uint32_t sensor_type;
+ uint32_t sensor_index;
+ sPAPRDRConnector *drc;
+ sPAPRDRConnectorClass *drck;
+ uint32_t entity_sense;
+
+ if (nargs != 2 || nret != 2) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ sensor_type = rtas_ld(args, 0);
+ sensor_index = rtas_ld(args, 1);
+
+ if (sensor_type != RTAS_SENSOR_TYPE_ENTITY_SENSE) {
+ /* currently only DR-related sensors are implemented */
+ DPRINTF("rtas_get_sensor_state: sensor/indicator not implemented: %d\n",
+ sensor_type);
+ rtas_st(rets, 0, RTAS_OUT_NOT_SUPPORTED);
+ return;
+ }
+
+ drc = spapr_dr_connector_by_index(sensor_index);
+ if (!drc) {
+ DPRINTF("rtas_get_sensor_state: invalid sensor/DRC index: %xh\n",
+ sensor_index);
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+ drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ entity_sense = drck->entity_sense(drc);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, entity_sense);
+}
+
+/* configure-connector work area offsets, int32_t units for field
+ * indexes, bytes for field offset/len values.
+ *
+ * as documented by PAPR+ v2.7, 13.5.3.5
+ */
+#define CC_IDX_NODE_NAME_OFFSET 2
+#define CC_IDX_PROP_NAME_OFFSET 2
+#define CC_IDX_PROP_LEN 3
+#define CC_IDX_PROP_DATA_OFFSET 4
+#define CC_VAL_DATA_OFFSET ((CC_IDX_PROP_DATA_OFFSET + 1) * 4)
+#define CC_WA_LEN 4096
+
+static void rtas_ibm_configure_connector(PowerPCCPU *cpu,
+ sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args, uint32_t nret,
+ target_ulong rets)
+{
+ uint64_t wa_addr;
+ uint64_t wa_offset;
+ uint32_t drc_index;
+ sPAPRDRConnector *drc;
+ sPAPRDRConnectorClass *drck;
+ sPAPRConfigureConnectorState *ccs;
+ sPAPRDRCCResponse resp = SPAPR_DR_CC_RESPONSE_CONTINUE;
+ int rc;
+ const void *fdt;
+
+ if (nargs != 2 || nret != 1) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ wa_addr = ((uint64_t)rtas_ld(args, 1) << 32) | rtas_ld(args, 0);
+
+ drc_index = rtas_ld(wa_addr, 0);
+ drc = spapr_dr_connector_by_index(drc_index);
+ if (!drc) {
+ DPRINTF("rtas_ibm_configure_connector: invalid DRC index: %xh\n",
+ drc_index);
+ rc = RTAS_OUT_PARAM_ERROR;
+ goto out;
+ }
+
+ drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
+ fdt = drck->get_fdt(drc, NULL);
+
+ ccs = spapr_ccs_find(spapr, drc_index);
+ if (!ccs) {
+ ccs = g_new0(sPAPRConfigureConnectorState, 1);
+ (void)drck->get_fdt(drc, &ccs->fdt_offset);
+ ccs->drc_index = drc_index;
+ spapr_ccs_add(spapr, ccs);
+ }
+
+ do {
+ uint32_t tag;
+ const char *name;
+ const struct fdt_property *prop;
+ int fdt_offset_next, prop_len;
+
+ tag = fdt_next_tag(fdt, ccs->fdt_offset, &fdt_offset_next);
+
+ switch (tag) {
+ case FDT_BEGIN_NODE:
+ ccs->fdt_depth++;
+ name = fdt_get_name(fdt, ccs->fdt_offset, NULL);
+
+ /* provide the name of the next OF node */
+ wa_offset = CC_VAL_DATA_OFFSET;
+ rtas_st(wa_addr, CC_IDX_NODE_NAME_OFFSET, wa_offset);
+ rtas_st_buffer_direct(wa_addr + wa_offset, CC_WA_LEN - wa_offset,
+ (uint8_t *)name, strlen(name) + 1);
+ resp = SPAPR_DR_CC_RESPONSE_NEXT_CHILD;
+ break;
+ case FDT_END_NODE:
+ ccs->fdt_depth--;
+ if (ccs->fdt_depth == 0) {
+ /* done sending the device tree, don't need to track
+ * the state anymore
+ */
+ drck->set_configured(drc);
+ spapr_ccs_remove(spapr, ccs);
+ ccs = NULL;
+ resp = SPAPR_DR_CC_RESPONSE_SUCCESS;
+ } else {
+ resp = SPAPR_DR_CC_RESPONSE_PREV_PARENT;
+ }
+ break;
+ case FDT_PROP:
+ prop = fdt_get_property_by_offset(fdt, ccs->fdt_offset,
+ &prop_len);
+ name = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
+
+ /* provide the name of the next OF property */
+ wa_offset = CC_VAL_DATA_OFFSET;
+ rtas_st(wa_addr, CC_IDX_PROP_NAME_OFFSET, wa_offset);
+ rtas_st_buffer_direct(wa_addr + wa_offset, CC_WA_LEN - wa_offset,
+ (uint8_t *)name, strlen(name) + 1);
+
+ /* provide the length and value of the OF property. data gets
+ * placed immediately after NULL terminator of the OF property's
+ * name string
+ */
+ wa_offset += strlen(name) + 1,
+ rtas_st(wa_addr, CC_IDX_PROP_LEN, prop_len);
+ rtas_st(wa_addr, CC_IDX_PROP_DATA_OFFSET, wa_offset);
+ rtas_st_buffer_direct(wa_addr + wa_offset, CC_WA_LEN - wa_offset,
+ (uint8_t *)((struct fdt_property *)prop)->data,
+ prop_len);
+ resp = SPAPR_DR_CC_RESPONSE_NEXT_PROPERTY;
+ break;
+ case FDT_END:
+ resp = SPAPR_DR_CC_RESPONSE_ERROR;
+ default:
+ /* keep seeking for an actionable tag */
+ break;
+ }
+ if (ccs) {
+ ccs->fdt_offset = fdt_offset_next;
+ }
+ } while (resp == SPAPR_DR_CC_RESPONSE_CONTINUE);
+
+ rc = resp;
+out:
+ rtas_st(rets, 0, rc);
+}
+
+static struct rtas_call {
+ const char *name;
+ spapr_rtas_fn fn;
+} rtas_table[RTAS_TOKEN_MAX - RTAS_TOKEN_BASE];
+
+target_ulong spapr_rtas_call(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ if ((token >= RTAS_TOKEN_BASE) && (token < RTAS_TOKEN_MAX)) {
+ struct rtas_call *call = rtas_table + (token - RTAS_TOKEN_BASE);
+
+ if (call->fn) {
+ call->fn(cpu, spapr, token, nargs, args, nret, rets);
+ return H_SUCCESS;
+ }
+ }
+
+ /* HACK: Some Linux early debug code uses RTAS display-character,
+ * but assumes the token value is 0xa (which it is on some real
+ * machines) without looking it up in the device tree. This
+ * special case makes this work */
+ if (token == 0xa) {
+ rtas_display_character(cpu, spapr, 0xa, nargs, args, nret, rets);
+ return H_SUCCESS;
+ }
+
+ hcall_dprintf("Unknown RTAS token 0x%x\n", token);
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return H_PARAMETER;
+}
+
+void spapr_rtas_register(int token, const char *name, spapr_rtas_fn fn)
+{
+ if (!((token >= RTAS_TOKEN_BASE) && (token < RTAS_TOKEN_MAX))) {
+ fprintf(stderr, "RTAS invalid token 0x%x\n", token);
+ exit(1);
+ }
+
+ token -= RTAS_TOKEN_BASE;
+ if (rtas_table[token].name) {
+ fprintf(stderr, "RTAS call \"%s\" is registered already as 0x%x\n",
+ rtas_table[token].name, token);
+ exit(1);
+ }
+
+ rtas_table[token].name = name;
+ rtas_table[token].fn = fn;
+}
+
+int spapr_rtas_device_tree_setup(void *fdt, hwaddr rtas_addr,
+ hwaddr rtas_size)
+{
+ int ret;
+ int i;
+ uint32_t lrdr_capacity[5];
+ MachineState *machine = MACHINE(qdev_get_machine());
+
+ ret = fdt_add_mem_rsv(fdt, rtas_addr, rtas_size);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add RTAS reserve entry: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+
+ ret = qemu_fdt_setprop_cell(fdt, "/rtas", "linux,rtas-base",
+ rtas_addr);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add linux,rtas-base property: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+
+ ret = qemu_fdt_setprop_cell(fdt, "/rtas", "linux,rtas-entry",
+ rtas_addr);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add linux,rtas-entry property: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+
+ ret = qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-size",
+ rtas_size);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add rtas-size property: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+
+ for (i = 0; i < RTAS_TOKEN_MAX - RTAS_TOKEN_BASE; i++) {
+ struct rtas_call *call = &rtas_table[i];
+
+ if (!call->name) {
+ continue;
+ }
+
+ ret = qemu_fdt_setprop_cell(fdt, "/rtas", call->name,
+ i + RTAS_TOKEN_BASE);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add rtas token for %s: %s\n",
+ call->name, fdt_strerror(ret));
+ return ret;
+ }
+
+ }
+
+ lrdr_capacity[0] = cpu_to_be32(((uint64_t)machine->maxram_size) >> 32);
+ lrdr_capacity[1] = cpu_to_be32(machine->maxram_size & 0xffffffff);
+ lrdr_capacity[2] = 0;
+ lrdr_capacity[3] = cpu_to_be32(SPAPR_MEMORY_BLOCK_SIZE);
+ lrdr_capacity[4] = cpu_to_be32(max_cpus/smp_threads);
+ ret = qemu_fdt_setprop(fdt, "/rtas", "ibm,lrdr-capacity", lrdr_capacity,
+ sizeof(lrdr_capacity));
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add ibm,lrdr-capacity rtas property\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void core_rtas_register_types(void)
+{
+ spapr_rtas_register(RTAS_DISPLAY_CHARACTER, "display-character",
+ rtas_display_character);
+ spapr_rtas_register(RTAS_POWER_OFF, "power-off", rtas_power_off);
+ spapr_rtas_register(RTAS_SYSTEM_REBOOT, "system-reboot",
+ rtas_system_reboot);
+ spapr_rtas_register(RTAS_QUERY_CPU_STOPPED_STATE, "query-cpu-stopped-state",
+ rtas_query_cpu_stopped_state);
+ spapr_rtas_register(RTAS_START_CPU, "start-cpu", rtas_start_cpu);
+ spapr_rtas_register(RTAS_STOP_SELF, "stop-self", rtas_stop_self);
+ spapr_rtas_register(RTAS_IBM_GET_SYSTEM_PARAMETER,
+ "ibm,get-system-parameter",
+ rtas_ibm_get_system_parameter);
+ spapr_rtas_register(RTAS_IBM_SET_SYSTEM_PARAMETER,
+ "ibm,set-system-parameter",
+ rtas_ibm_set_system_parameter);
+ spapr_rtas_register(RTAS_IBM_OS_TERM, "ibm,os-term",
+ rtas_ibm_os_term);
+ spapr_rtas_register(RTAS_SET_POWER_LEVEL, "set-power-level",
+ rtas_set_power_level);
+ spapr_rtas_register(RTAS_GET_POWER_LEVEL, "get-power-level",
+ rtas_get_power_level);
+ spapr_rtas_register(RTAS_SET_INDICATOR, "set-indicator",
+ rtas_set_indicator);
+ spapr_rtas_register(RTAS_GET_SENSOR_STATE, "get-sensor-state",
+ rtas_get_sensor_state);
+ spapr_rtas_register(RTAS_IBM_CONFIGURE_CONNECTOR, "ibm,configure-connector",
+ rtas_ibm_configure_connector);
+}
+
+type_init(core_rtas_register_types)
diff --git a/hw/ppc/spapr_rtc.c b/hw/ppc/spapr_rtc.c
new file mode 100644
index 00000000..34b27db7
--- /dev/null
+++ b/hw/ppc/spapr_rtc.c
@@ -0,0 +1,211 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * RTAS Real Time Clock
+ *
+ * Copyright (c) 2010-2011 David Gibson, IBM Corporation.
+ * Copyright 2014 David Gibson, Red Hat.
+ *
+ * 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.
+ *
+ */
+#include "cpu.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/ppc/spapr.h"
+#include "qapi-event.h"
+
+#define SPAPR_RTC(obj) \
+ OBJECT_CHECK(sPAPRRTCState, (obj), TYPE_SPAPR_RTC)
+
+typedef struct sPAPRRTCState sPAPRRTCState;
+struct sPAPRRTCState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ int64_t ns_offset;
+};
+
+void spapr_rtc_read(DeviceState *dev, struct tm *tm, uint32_t *ns)
+{
+ sPAPRRTCState *rtc = SPAPR_RTC(dev);
+ int64_t host_ns = qemu_clock_get_ns(rtc_clock);
+ int64_t guest_ns;
+ time_t guest_s;
+
+ assert(rtc);
+
+ guest_ns = host_ns + rtc->ns_offset;
+ guest_s = guest_ns / NANOSECONDS_PER_SECOND;
+
+ if (tm) {
+ gmtime_r(&guest_s, tm);
+ }
+ if (ns) {
+ *ns = guest_ns;
+ }
+}
+
+int spapr_rtc_import_offset(DeviceState *dev, int64_t legacy_offset)
+{
+ sPAPRRTCState *rtc;
+
+ if (!dev) {
+ return -ENODEV;
+ }
+
+ rtc = SPAPR_RTC(dev);
+
+ rtc->ns_offset = legacy_offset * NANOSECONDS_PER_SECOND;
+
+ return 0;
+}
+
+static void rtas_get_time_of_day(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ struct tm tm;
+ uint32_t ns;
+
+ if ((nargs != 0) || (nret != 8)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ if (!spapr->rtc) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ spapr_rtc_read(spapr->rtc, &tm, &ns);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+ rtas_st(rets, 1, tm.tm_year + 1900);
+ rtas_st(rets, 2, tm.tm_mon + 1);
+ rtas_st(rets, 3, tm.tm_mday);
+ rtas_st(rets, 4, tm.tm_hour);
+ rtas_st(rets, 5, tm.tm_min);
+ rtas_st(rets, 6, tm.tm_sec);
+ rtas_st(rets, 7, ns);
+}
+
+static void rtas_set_time_of_day(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ sPAPRRTCState *rtc;
+ struct tm tm;
+ time_t new_s;
+ int64_t host_ns;
+
+ if ((nargs != 7) || (nret != 1)) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ if (!spapr->rtc) {
+ rtas_st(rets, 0, RTAS_OUT_HW_ERROR);
+ return;
+ }
+
+ tm.tm_year = rtas_ld(args, 0) - 1900;
+ tm.tm_mon = rtas_ld(args, 1) - 1;
+ tm.tm_mday = rtas_ld(args, 2);
+ tm.tm_hour = rtas_ld(args, 3);
+ tm.tm_min = rtas_ld(args, 4);
+ tm.tm_sec = rtas_ld(args, 5);
+
+ new_s = mktimegm(&tm);
+ if (new_s == -1) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ /* Generate a monitor event for the change */
+ qapi_event_send_rtc_change(qemu_timedate_diff(&tm), &error_abort);
+
+ rtc = SPAPR_RTC(spapr->rtc);
+
+ host_ns = qemu_clock_get_ns(rtc_clock);
+
+ rtc->ns_offset = (new_s * NANOSECONDS_PER_SECOND) - host_ns;
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+static void spapr_rtc_qom_date(Object *obj, struct tm *current_tm, Error **errp)
+{
+ spapr_rtc_read(DEVICE(obj), current_tm, NULL);
+}
+
+static void spapr_rtc_realize(DeviceState *dev, Error **errp)
+{
+ sPAPRRTCState *rtc = SPAPR_RTC(dev);
+ struct tm tm;
+ time_t host_s;
+ int64_t rtc_ns;
+
+ /* Initialize the RTAS RTC from host time */
+
+ qemu_get_timedate(&tm, 0);
+ host_s = mktimegm(&tm);
+ rtc_ns = qemu_clock_get_ns(rtc_clock);
+ rtc->ns_offset = host_s * NANOSECONDS_PER_SECOND - rtc_ns;
+
+ object_property_add_tm(OBJECT(rtc), "date", spapr_rtc_qom_date, NULL);
+}
+
+static const VMStateDescription vmstate_spapr_rtc = {
+ .name = "spapr/rtc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT64(ns_offset, sPAPRRTCState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void spapr_rtc_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = spapr_rtc_realize;
+ dc->vmsd = &vmstate_spapr_rtc;
+
+ spapr_rtas_register(RTAS_GET_TIME_OF_DAY, "get-time-of-day",
+ rtas_get_time_of_day);
+ spapr_rtas_register(RTAS_SET_TIME_OF_DAY, "set-time-of-day",
+ rtas_set_time_of_day);
+}
+
+static const TypeInfo spapr_rtc_info = {
+ .name = TYPE_SPAPR_RTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(sPAPRRTCState),
+ .class_size = sizeof(XICSStateClass),
+ .class_init = spapr_rtc_class_init,
+};
+
+static void spapr_rtc_register_types(void)
+{
+ type_register_static(&spapr_rtc_info);
+}
+type_init(spapr_rtc_register_types)
diff --git a/hw/ppc/spapr_vio.c b/hw/ppc/spapr_vio.c
new file mode 100644
index 00000000..c51eb8e2
--- /dev/null
+++ b/hw/ppc/spapr_vio.c
@@ -0,0 +1,705 @@
+/*
+ * QEMU sPAPR VIO code
+ *
+ * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com>
+ * Based on the s390 virtio bus code:
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+#include "sysemu/device_tree.h"
+#include "kvm_ppc.h"
+
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "hw/ppc/xics.h"
+
+#include <libfdt.h>
+
+/* #define DEBUG_SPAPR */
+
+#ifdef DEBUG_SPAPR
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+static Property spapr_vio_props[] = {
+ DEFINE_PROP_UINT32("irq", VIOsPAPRDevice, irq, 0), \
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static char *spapr_vio_get_dev_name(DeviceState *qdev)
+{
+ VIOsPAPRDevice *dev = VIO_SPAPR_DEVICE(qdev);
+ VIOsPAPRDeviceClass *pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+ char *name;
+
+ /* Device tree style name device@reg */
+ name = g_strdup_printf("%s@%x", pc->dt_name, dev->reg);
+
+ return name;
+}
+
+static void spapr_vio_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+
+ k->get_dev_path = spapr_vio_get_dev_name;
+ k->get_fw_dev_path = spapr_vio_get_dev_name;
+}
+
+static const TypeInfo spapr_vio_bus_info = {
+ .name = TYPE_SPAPR_VIO_BUS,
+ .parent = TYPE_BUS,
+ .class_init = spapr_vio_bus_class_init,
+ .instance_size = sizeof(VIOsPAPRBus),
+};
+
+VIOsPAPRDevice *spapr_vio_find_by_reg(VIOsPAPRBus *bus, uint32_t reg)
+{
+ BusChild *kid;
+ VIOsPAPRDevice *dev = NULL;
+
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ dev = (VIOsPAPRDevice *)kid->child;
+ if (dev->reg == reg) {
+ return dev;
+ }
+ }
+
+ return NULL;
+}
+
+static int vio_make_devnode(VIOsPAPRDevice *dev,
+ void *fdt)
+{
+ VIOsPAPRDeviceClass *pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+ int vdevice_off, node_off, ret;
+ char *dt_name;
+
+ vdevice_off = fdt_path_offset(fdt, "/vdevice");
+ if (vdevice_off < 0) {
+ return vdevice_off;
+ }
+
+ dt_name = spapr_vio_get_dev_name(DEVICE(dev));
+ node_off = fdt_add_subnode(fdt, vdevice_off, dt_name);
+ g_free(dt_name);
+ if (node_off < 0) {
+ return node_off;
+ }
+
+ ret = fdt_setprop_cell(fdt, node_off, "reg", dev->reg);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (pc->dt_type) {
+ ret = fdt_setprop_string(fdt, node_off, "device_type",
+ pc->dt_type);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ if (pc->dt_compatible) {
+ ret = fdt_setprop_string(fdt, node_off, "compatible",
+ pc->dt_compatible);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ if (dev->irq) {
+ uint32_t ints_prop[] = {cpu_to_be32(dev->irq), 0};
+
+ ret = fdt_setprop(fdt, node_off, "interrupts", ints_prop,
+ sizeof(ints_prop));
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ ret = spapr_tcet_dma_dt(fdt, node_off, "ibm,my-dma-window", dev->tcet);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (pc->devnode) {
+ ret = (pc->devnode)(dev, fdt, node_off);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ return node_off;
+}
+
+/*
+ * CRQ handling
+ */
+static target_ulong h_reg_crq(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong queue_addr = args[1];
+ target_ulong queue_len = args[2];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+
+ if (!dev) {
+ hcall_dprintf("Unit 0x" TARGET_FMT_lx " does not exist\n", reg);
+ return H_PARAMETER;
+ }
+
+ /* We can't grok a queue size bigger than 256M for now */
+ if (queue_len < 0x1000 || queue_len > 0x10000000) {
+ hcall_dprintf("Queue size too small or too big (0x" TARGET_FMT_lx
+ ")\n", queue_len);
+ return H_PARAMETER;
+ }
+
+ /* Check queue alignment */
+ if (queue_addr & 0xfff) {
+ hcall_dprintf("Queue not aligned (0x" TARGET_FMT_lx ")\n", queue_addr);
+ return H_PARAMETER;
+ }
+
+ /* Check if device supports CRQs */
+ if (!dev->crq.SendFunc) {
+ hcall_dprintf("Device does not support CRQ\n");
+ return H_NOT_FOUND;
+ }
+
+ /* Already a queue ? */
+ if (dev->crq.qsize) {
+ hcall_dprintf("CRQ already registered\n");
+ return H_RESOURCE;
+ }
+ dev->crq.qladdr = queue_addr;
+ dev->crq.qsize = queue_len;
+ dev->crq.qnext = 0;
+
+ DPRINTF("CRQ for dev 0x" TARGET_FMT_lx " registered at 0x"
+ TARGET_FMT_lx "/0x" TARGET_FMT_lx "\n",
+ reg, queue_addr, queue_len);
+ return H_SUCCESS;
+}
+
+static target_ulong free_crq(VIOsPAPRDevice *dev)
+{
+ dev->crq.qladdr = 0;
+ dev->crq.qsize = 0;
+ dev->crq.qnext = 0;
+
+ DPRINTF("CRQ for dev 0x%" PRIx32 " freed\n", dev->reg);
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_free_crq(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+
+ if (!dev) {
+ hcall_dprintf("Unit 0x" TARGET_FMT_lx " does not exist\n", reg);
+ return H_PARAMETER;
+ }
+
+ return free_crq(dev);
+}
+
+static target_ulong h_send_crq(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong msg_hi = args[1];
+ target_ulong msg_lo = args[2];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ uint64_t crq_mangle[2];
+
+ if (!dev) {
+ hcall_dprintf("Unit 0x" TARGET_FMT_lx " does not exist\n", reg);
+ return H_PARAMETER;
+ }
+ crq_mangle[0] = cpu_to_be64(msg_hi);
+ crq_mangle[1] = cpu_to_be64(msg_lo);
+
+ if (dev->crq.SendFunc) {
+ return dev->crq.SendFunc(dev, (uint8_t *)crq_mangle);
+ }
+
+ return H_HARDWARE;
+}
+
+static target_ulong h_enable_crq(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+
+ if (!dev) {
+ hcall_dprintf("Unit 0x" TARGET_FMT_lx " does not exist\n", reg);
+ return H_PARAMETER;
+ }
+
+ return 0;
+}
+
+/* Returns negative error, 0 success, or positive: queue full */
+int spapr_vio_send_crq(VIOsPAPRDevice *dev, uint8_t *crq)
+{
+ int rc;
+ uint8_t byte;
+
+ if (!dev->crq.qsize) {
+ fprintf(stderr, "spapr_vio_send_creq on uninitialized queue\n");
+ return -1;
+ }
+
+ /* Maybe do a fast path for KVM just writing to the pages */
+ rc = spapr_vio_dma_read(dev, dev->crq.qladdr + dev->crq.qnext, &byte, 1);
+ if (rc) {
+ return rc;
+ }
+ if (byte != 0) {
+ return 1;
+ }
+
+ rc = spapr_vio_dma_write(dev, dev->crq.qladdr + dev->crq.qnext + 8,
+ &crq[8], 8);
+ if (rc) {
+ return rc;
+ }
+
+ kvmppc_eieio();
+
+ rc = spapr_vio_dma_write(dev, dev->crq.qladdr + dev->crq.qnext, crq, 8);
+ if (rc) {
+ return rc;
+ }
+
+ dev->crq.qnext = (dev->crq.qnext + 16) % dev->crq.qsize;
+
+ if (dev->signal_state & 1) {
+ qemu_irq_pulse(spapr_vio_qirq(dev));
+ }
+
+ return 0;
+}
+
+/* "quiesce" handling */
+
+static void spapr_vio_quiesce_one(VIOsPAPRDevice *dev)
+{
+ if (dev->tcet) {
+ device_reset(DEVICE(dev->tcet));
+ }
+ free_crq(dev);
+}
+
+void spapr_vio_set_bypass(VIOsPAPRDevice *dev, bool bypass)
+{
+ if (!dev->tcet) {
+ return;
+ }
+
+ memory_region_set_enabled(&dev->mrbypass, bypass);
+ memory_region_set_enabled(spapr_tce_get_iommu(dev->tcet), !bypass);
+
+ dev->tcet->bypass = bypass;
+}
+
+static void rtas_set_tce_bypass(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ VIOsPAPRBus *bus = spapr->vio_bus;
+ VIOsPAPRDevice *dev;
+ uint32_t unit, enable;
+
+ if (nargs != 2) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+ unit = rtas_ld(args, 0);
+ enable = rtas_ld(args, 1);
+ dev = spapr_vio_find_by_reg(bus, unit);
+ if (!dev) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ if (!dev->tcet) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ spapr_vio_set_bypass(dev, !!enable);
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+static void rtas_quiesce(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ VIOsPAPRBus *bus = spapr->vio_bus;
+ BusChild *kid;
+ VIOsPAPRDevice *dev = NULL;
+
+ if (nargs != 0) {
+ rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
+ return;
+ }
+
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ dev = (VIOsPAPRDevice *)kid->child;
+ spapr_vio_quiesce_one(dev);
+ }
+
+ rtas_st(rets, 0, RTAS_OUT_SUCCESS);
+}
+
+static VIOsPAPRDevice *reg_conflict(VIOsPAPRDevice *dev)
+{
+ VIOsPAPRBus *bus = DO_UPCAST(VIOsPAPRBus, bus, dev->qdev.parent_bus);
+ BusChild *kid;
+ VIOsPAPRDevice *other;
+
+ /*
+ * Check for a device other than the given one which is already
+ * using the requested address. We have to open code this because
+ * the given dev might already be in the list.
+ */
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ other = VIO_SPAPR_DEVICE(kid->child);
+
+ if (other != dev && other->reg == dev->reg) {
+ return other;
+ }
+ }
+
+ return 0;
+}
+
+static void spapr_vio_busdev_reset(DeviceState *qdev)
+{
+ VIOsPAPRDevice *dev = VIO_SPAPR_DEVICE(qdev);
+ VIOsPAPRDeviceClass *pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+
+ /* Shut down the request queue and TCEs if necessary */
+ spapr_vio_quiesce_one(dev);
+
+ dev->signal_state = 0;
+
+ spapr_vio_set_bypass(dev, false);
+ if (pc->reset) {
+ pc->reset(dev);
+ }
+}
+
+static void spapr_vio_busdev_realize(DeviceState *qdev, Error **errp)
+{
+ sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ VIOsPAPRDevice *dev = (VIOsPAPRDevice *)qdev;
+ VIOsPAPRDeviceClass *pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+ char *id;
+
+ if (dev->reg != -1) {
+ /*
+ * Explicitly assigned address, just verify that no-one else
+ * is using it. other mechanism). We have to open code this
+ * rather than using spapr_vio_find_by_reg() because sdev
+ * itself is already in the list.
+ */
+ VIOsPAPRDevice *other = reg_conflict(dev);
+
+ if (other) {
+ error_setg(errp, "%s and %s devices conflict at address %#x",
+ object_get_typename(OBJECT(qdev)),
+ object_get_typename(OBJECT(&other->qdev)),
+ dev->reg);
+ return;
+ }
+ } else {
+ /* Need to assign an address */
+ VIOsPAPRBus *bus = DO_UPCAST(VIOsPAPRBus, bus, dev->qdev.parent_bus);
+
+ do {
+ dev->reg = bus->next_reg++;
+ } while (reg_conflict(dev));
+ }
+
+ /* Don't overwrite ids assigned on the command line */
+ if (!dev->qdev.id) {
+ id = spapr_vio_get_dev_name(DEVICE(dev));
+ dev->qdev.id = id;
+ }
+
+ dev->irq = xics_alloc(spapr->icp, 0, dev->irq, false);
+ if (!dev->irq) {
+ error_setg(errp, "can't allocate IRQ");
+ return;
+ }
+
+ if (pc->rtce_window_size) {
+ uint32_t liobn = SPAPR_VIO_LIOBN(dev->reg);
+
+ memory_region_init(&dev->mrroot, OBJECT(dev), "iommu-spapr-root",
+ ram_size);
+ memory_region_init_alias(&dev->mrbypass, OBJECT(dev),
+ "iommu-spapr-bypass", get_system_memory(),
+ 0, ram_size);
+ memory_region_add_subregion_overlap(&dev->mrroot, 0, &dev->mrbypass, 1);
+ address_space_init(&dev->as, &dev->mrroot, qdev->id);
+
+ dev->tcet = spapr_tce_new_table(qdev, liobn,
+ 0,
+ SPAPR_TCE_PAGE_SHIFT,
+ pc->rtce_window_size >>
+ SPAPR_TCE_PAGE_SHIFT, false);
+ dev->tcet->vdev = dev;
+ memory_region_add_subregion_overlap(&dev->mrroot, 0,
+ spapr_tce_get_iommu(dev->tcet), 2);
+ }
+
+ pc->realize(dev, errp);
+}
+
+static target_ulong h_vio_signal(PowerPCCPU *cpu, sPAPRMachineState *spapr,
+ target_ulong opcode,
+ target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong mode = args[1];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ VIOsPAPRDeviceClass *pc;
+
+ if (!dev) {
+ return H_PARAMETER;
+ }
+
+ pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+
+ if (mode & ~pc->signal_mask) {
+ return H_PARAMETER;
+ }
+
+ dev->signal_state = mode;
+
+ return H_SUCCESS;
+}
+
+VIOsPAPRBus *spapr_vio_bus_init(void)
+{
+ VIOsPAPRBus *bus;
+ BusState *qbus;
+ DeviceState *dev;
+
+ /* Create bridge device */
+ dev = qdev_create(NULL, "spapr-vio-bridge");
+ qdev_init_nofail(dev);
+
+ /* Create bus on bridge device */
+
+ qbus = qbus_create(TYPE_SPAPR_VIO_BUS, dev, "spapr-vio");
+ bus = DO_UPCAST(VIOsPAPRBus, bus, qbus);
+ bus->next_reg = 0x71000000;
+
+ /* hcall-vio */
+ spapr_register_hypercall(H_VIO_SIGNAL, h_vio_signal);
+
+ /* hcall-crq */
+ spapr_register_hypercall(H_REG_CRQ, h_reg_crq);
+ spapr_register_hypercall(H_FREE_CRQ, h_free_crq);
+ spapr_register_hypercall(H_SEND_CRQ, h_send_crq);
+ spapr_register_hypercall(H_ENABLE_CRQ, h_enable_crq);
+
+ /* RTAS calls */
+ spapr_rtas_register(RTAS_IBM_SET_TCE_BYPASS, "ibm,set-tce-bypass",
+ rtas_set_tce_bypass);
+ spapr_rtas_register(RTAS_QUIESCE, "quiesce", rtas_quiesce);
+
+ return bus;
+}
+
+/* Represents sPAPR hcall VIO devices */
+
+static int spapr_vio_bridge_init(SysBusDevice *dev)
+{
+ /* nothing */
+ return 0;
+}
+
+static void spapr_vio_bridge_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->fw_name = "vdevice";
+ k->init = spapr_vio_bridge_init;
+}
+
+static const TypeInfo spapr_vio_bridge_info = {
+ .name = "spapr-vio-bridge",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .class_init = spapr_vio_bridge_class_init,
+};
+
+const VMStateDescription vmstate_spapr_vio = {
+ .name = "spapr_vio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ /* Sanity check */
+ VMSTATE_UINT32_EQUAL(reg, VIOsPAPRDevice),
+ VMSTATE_UINT32_EQUAL(irq, VIOsPAPRDevice),
+
+ /* General VIO device state */
+ VMSTATE_UINTTL(signal_state, VIOsPAPRDevice),
+ VMSTATE_UINT64(crq.qladdr, VIOsPAPRDevice),
+ VMSTATE_UINT32(crq.qsize, VIOsPAPRDevice),
+ VMSTATE_UINT32(crq.qnext, VIOsPAPRDevice),
+
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void vio_spapr_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->realize = spapr_vio_busdev_realize;
+ k->reset = spapr_vio_busdev_reset;
+ k->bus_type = TYPE_SPAPR_VIO_BUS;
+ k->props = spapr_vio_props;
+}
+
+static const TypeInfo spapr_vio_type_info = {
+ .name = TYPE_VIO_SPAPR_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(VIOsPAPRDevice),
+ .abstract = true,
+ .class_size = sizeof(VIOsPAPRDeviceClass),
+ .class_init = vio_spapr_device_class_init,
+};
+
+static void spapr_vio_register_types(void)
+{
+ type_register_static(&spapr_vio_bus_info);
+ type_register_static(&spapr_vio_bridge_info);
+ type_register_static(&spapr_vio_type_info);
+}
+
+type_init(spapr_vio_register_types)
+
+static int compare_reg(const void *p1, const void *p2)
+{
+ VIOsPAPRDevice const *dev1, *dev2;
+
+ dev1 = (VIOsPAPRDevice *)*(DeviceState **)p1;
+ dev2 = (VIOsPAPRDevice *)*(DeviceState **)p2;
+
+ if (dev1->reg < dev2->reg) {
+ return -1;
+ }
+ if (dev1->reg == dev2->reg) {
+ return 0;
+ }
+
+ /* dev1->reg > dev2->reg */
+ return 1;
+}
+
+int spapr_populate_vdevice(VIOsPAPRBus *bus, void *fdt)
+{
+ DeviceState *qdev, **qdevs;
+ BusChild *kid;
+ int i, num, ret = 0;
+
+ /* Count qdevs on the bus list */
+ num = 0;
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ num++;
+ }
+
+ /* Copy out into an array of pointers */
+ qdevs = g_malloc(sizeof(qdev) * num);
+ num = 0;
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ qdevs[num++] = kid->child;
+ }
+
+ /* Sort the array */
+ qsort(qdevs, num, sizeof(qdev), compare_reg);
+
+ /* Hack alert. Give the devices to libfdt in reverse order, we happen
+ * to know that will mean they are in forward order in the tree. */
+ for (i = num - 1; i >= 0; i--) {
+ VIOsPAPRDevice *dev = (VIOsPAPRDevice *)(qdevs[i]);
+
+ ret = vio_make_devnode(dev, fdt);
+
+ if (ret < 0) {
+ goto out;
+ }
+ }
+
+ ret = 0;
+out:
+ g_free(qdevs);
+
+ return ret;
+}
+
+int spapr_populate_chosen_stdout(void *fdt, VIOsPAPRBus *bus)
+{
+ VIOsPAPRDevice *dev;
+ char *name, *path;
+ int ret, offset;
+
+ dev = spapr_vty_get_default(bus);
+ if (!dev)
+ return 0;
+
+ offset = fdt_path_offset(fdt, "/chosen");
+ if (offset < 0) {
+ return offset;
+ }
+
+ name = spapr_vio_get_dev_name(DEVICE(dev));
+ path = g_strdup_printf("/vdevice/%s", name);
+
+ ret = fdt_setprop_string(fdt, offset, "linux,stdout-path", path);
+
+ g_free(name);
+ g_free(path);
+
+ return ret;
+}
diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c
new file mode 100644
index 00000000..de86f7c6
--- /dev/null
+++ b/hw/ppc/virtex_ml507.c
@@ -0,0 +1,311 @@
+/*
+ * Model of Xilinx Virtex5 ML507 PPC-440 refdesign.
+ *
+ * Copyright (c) 2010 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/char/serial.h"
+#include "hw/block/flash.h"
+#include "sysemu/sysemu.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "sysemu/device_tree.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "exec/address-spaces.h"
+
+#include "hw/ppc/ppc.h"
+#include "hw/ppc/ppc4xx.h"
+#include "ppc405.h"
+
+#include "sysemu/block-backend.h"
+
+#define EPAPR_MAGIC (0x45504150)
+#define FLASH_SIZE (16 * 1024 * 1024)
+
+#define INTC_BASEADDR 0x81800000
+#define UART16550_BASEADDR 0x83e01003
+#define TIMER_BASEADDR 0x83c00000
+#define PFLASH_BASEADDR 0xfc000000
+
+#define TIMER_IRQ 3
+#define UART16550_IRQ 9
+
+static struct boot_info
+{
+ uint32_t bootstrap_pc;
+ uint32_t cmdline;
+ uint32_t fdt;
+ uint32_t ima_size;
+ void *vfdt;
+} boot_info;
+
+/* Create reset TLB entries for BookE, spanning the 32bit addr space. */
+static void mmubooke_create_initial_mapping(CPUPPCState *env,
+ target_ulong va,
+ hwaddr pa)
+{
+ ppcemb_tlb_t *tlb = &env->tlb.tlbe[0];
+
+ tlb->attr = 0;
+ tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4);
+ tlb->size = 1U << 31; /* up to 0x80000000 */
+ tlb->EPN = va & TARGET_PAGE_MASK;
+ tlb->RPN = pa & TARGET_PAGE_MASK;
+ tlb->PID = 0;
+
+ tlb = &env->tlb.tlbe[1];
+ tlb->attr = 0;
+ tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4);
+ tlb->size = 1U << 31; /* up to 0xffffffff */
+ tlb->EPN = 0x80000000 & TARGET_PAGE_MASK;
+ tlb->RPN = 0x80000000 & TARGET_PAGE_MASK;
+ tlb->PID = 0;
+}
+
+static PowerPCCPU *ppc440_init_xilinx(ram_addr_t *ram_size,
+ int do_init,
+ const char *cpu_model,
+ uint32_t sysclk)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ qemu_irq *irqs;
+
+ cpu = cpu_ppc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to initialize CPU!\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ ppc_booke_timers_init(cpu, sysclk, 0/* no flags */);
+
+ ppc_dcr_init(env, NULL, NULL);
+
+ /* interrupt controller */
+ irqs = g_malloc0(sizeof(qemu_irq) * PPCUIC_OUTPUT_NB);
+ irqs[PPCUIC_OUTPUT_INT] = ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_INT];
+ irqs[PPCUIC_OUTPUT_CINT] = ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_CINT];
+ ppcuic_init(env, irqs, 0x0C0, 0, 1);
+ return cpu;
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ struct boot_info *bi = env->load_info;
+
+ cpu_reset(CPU(cpu));
+ /* Linux Kernel Parameters (passing device tree):
+ * r3: pointer to the fdt
+ * r4: 0
+ * r5: 0
+ * r6: epapr magic
+ * r7: size of IMA in bytes
+ * r8: 0
+ * r9: 0
+ */
+ env->gpr[1] = (16<<20) - 8;
+ /* Provide a device-tree. */
+ env->gpr[3] = bi->fdt;
+ env->nip = bi->bootstrap_pc;
+
+ /* Create a mapping for the kernel. */
+ mmubooke_create_initial_mapping(env, 0, 0);
+ env->gpr[6] = tswap32(EPAPR_MAGIC);
+ env->gpr[7] = bi->ima_size;
+}
+
+#define BINARY_DEVICE_TREE_FILE "virtex-ml507.dtb"
+static int xilinx_load_device_tree(hwaddr addr,
+ uint32_t ramsize,
+ hwaddr initrd_base,
+ hwaddr initrd_size,
+ const char *kernel_cmdline)
+{
+ char *path;
+ int fdt_size;
+ void *fdt = NULL;
+ int r;
+ const char *dtb_filename;
+
+ dtb_filename = qemu_opt_get(qemu_get_machine_opts(), "dtb");
+ if (dtb_filename) {
+ fdt = load_device_tree(dtb_filename, &fdt_size);
+ if (!fdt) {
+ error_report("Error while loading device tree file '%s'",
+ dtb_filename);
+ }
+ } else {
+ /* Try the local "ppc.dtb" override. */
+ fdt = load_device_tree("ppc.dtb", &fdt_size);
+ if (!fdt) {
+ path = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE);
+ if (path) {
+ fdt = load_device_tree(path, &fdt_size);
+ g_free(path);
+ }
+ }
+ }
+ if (!fdt) {
+ return 0;
+ }
+
+ r = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-start",
+ initrd_base);
+ if (r < 0) {
+ error_report("couldn't set /chosen/linux,initrd-start");
+ }
+
+ r = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-end",
+ (initrd_base + initrd_size));
+ if (r < 0) {
+ error_report("couldn't set /chosen/linux,initrd-end");
+ }
+
+ r = qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", kernel_cmdline);
+ if (r < 0)
+ fprintf(stderr, "couldn't set /chosen/bootargs\n");
+ cpu_physical_memory_write(addr, fdt, fdt_size);
+ return fdt_size;
+}
+
+static void virtex_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ hwaddr initrd_base = 0;
+ int initrd_size = 0;
+ MemoryRegion *address_space_mem = get_system_memory();
+ DeviceState *dev;
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ hwaddr ram_base = 0;
+ DriveInfo *dinfo;
+ MemoryRegion *phys_ram = g_new(MemoryRegion, 1);
+ qemu_irq irq[32], *cpu_irq;
+ int kernel_size;
+ int i;
+
+ /* init CPUs */
+ if (machine->cpu_model == NULL) {
+ machine->cpu_model = "440-Xilinx";
+ }
+
+ cpu = ppc440_init_xilinx(&ram_size, 1, machine->cpu_model, 400000000);
+ env = &cpu->env;
+ qemu_register_reset(main_cpu_reset, cpu);
+
+ memory_region_allocate_system_memory(phys_ram, NULL, "ram", ram_size);
+ memory_region_add_subregion(address_space_mem, ram_base, phys_ram);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ pflash_cfi01_register(PFLASH_BASEADDR, NULL, "virtex.flash", FLASH_SIZE,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ (64 * 1024), FLASH_SIZE >> 16,
+ 1, 0x89, 0x18, 0x0000, 0x0, 1);
+
+ cpu_irq = (qemu_irq *) &env->irq_inputs[PPC40x_INPUT_INT];
+ dev = qdev_create(NULL, "xlnx.xps-intc");
+ qdev_prop_set_uint32(dev, "kind-of-intr", 0);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, INTC_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, cpu_irq[0]);
+ for (i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ serial_mm_init(address_space_mem, UART16550_BASEADDR, 2, irq[UART16550_IRQ],
+ 115200, serial_hds[0], DEVICE_LITTLE_ENDIAN);
+
+ /* 2 timers at irq 2 @ 62 Mhz. */
+ dev = qdev_create(NULL, "xlnx.xps-timer");
+ qdev_prop_set_uint32(dev, "one-timer-only", 0);
+ qdev_prop_set_uint32(dev, "clock-frequency", 62 * 1000000);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, TIMER_BASEADDR);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq[TIMER_IRQ]);
+
+ if (kernel_filename) {
+ uint64_t entry, low, high;
+ hwaddr boot_offset;
+
+ /* Boots a kernel elf binary. */
+ kernel_size = load_elf(kernel_filename, NULL, NULL,
+ &entry, &low, &high, 1, ELF_MACHINE, 0);
+ boot_info.bootstrap_pc = entry & 0x00ffffff;
+
+ if (kernel_size < 0) {
+ boot_offset = 0x1200000;
+ /* If we failed loading ELF's try a raw image. */
+ kernel_size = load_image_targphys(kernel_filename,
+ boot_offset,
+ ram_size);
+ boot_info.bootstrap_pc = boot_offset;
+ high = boot_info.bootstrap_pc + kernel_size + 8192;
+ }
+
+ boot_info.ima_size = kernel_size;
+
+ /* Load initrd. */
+ if (machine->initrd_filename) {
+ initrd_base = high = ROUND_UP(high, 4);
+ initrd_size = load_image_targphys(machine->initrd_filename,
+ high, ram_size - high);
+
+ if (initrd_size < 0) {
+ error_report("couldn't load ram disk '%s'",
+ machine->initrd_filename);
+ exit(1);
+ }
+ high = ROUND_UP(high + initrd_size, 4);
+ }
+
+ /* Provide a device-tree. */
+ boot_info.fdt = high + (8192 * 2);
+ boot_info.fdt &= ~8191;
+
+ xilinx_load_device_tree(boot_info.fdt, ram_size,
+ initrd_base, initrd_size,
+ kernel_cmdline);
+ }
+ env->load_info = &boot_info;
+}
+
+static QEMUMachine virtex_machine = {
+ .name = "virtex-ml507",
+ .desc = "Xilinx Virtex ML507 reference design",
+ .init = virtex_init,
+};
+
+static void virtex_machine_init(void)
+{
+ qemu_register_machine(&virtex_machine);
+}
+
+machine_init(virtex_machine_init);
diff --git a/hw/s390x/Makefile.objs b/hw/s390x/Makefile.objs
new file mode 100644
index 00000000..27cd75a9
--- /dev/null
+++ b/hw/s390x/Makefile.objs
@@ -0,0 +1,11 @@
+obj-y = s390-virtio-bus.o s390-virtio.o
+obj-y += s390-virtio-hcall.o
+obj-y += sclp.o
+obj-y += event-facility.o
+obj-y += sclpquiesce.o
+obj-y += sclpcpu.o
+obj-y += ipl.o
+obj-y += css.o
+obj-y += s390-virtio-ccw.o
+obj-y += virtio-ccw.o
+obj-y += s390-pci-bus.o s390-pci-inst.o
diff --git a/hw/s390x/css.c b/hw/s390x/css.c
new file mode 100644
index 00000000..97d93d56
--- /dev/null
+++ b/hw/s390x/css.c
@@ -0,0 +1,1558 @@
+/*
+ * Channel subsystem base support.
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include <hw/qdev.h>
+#include "qemu/bitops.h"
+#include "exec/address-spaces.h"
+#include "cpu.h"
+#include "ioinst.h"
+#include "css.h"
+#include "trace.h"
+#include "hw/s390x/s390_flic.h"
+
+typedef struct CrwContainer {
+ CRW crw;
+ QTAILQ_ENTRY(CrwContainer) sibling;
+} CrwContainer;
+
+typedef struct ChpInfo {
+ uint8_t in_use;
+ uint8_t type;
+ uint8_t is_virtual;
+} ChpInfo;
+
+typedef struct SubchSet {
+ SubchDev *sch[MAX_SCHID + 1];
+ unsigned long schids_used[BITS_TO_LONGS(MAX_SCHID + 1)];
+ unsigned long devnos_used[BITS_TO_LONGS(MAX_SCHID + 1)];
+} SubchSet;
+
+typedef struct CssImage {
+ SubchSet *sch_set[MAX_SSID + 1];
+ ChpInfo chpids[MAX_CHPID + 1];
+} CssImage;
+
+typedef struct IoAdapter {
+ uint32_t id;
+ uint8_t type;
+ uint8_t isc;
+ QTAILQ_ENTRY(IoAdapter) sibling;
+} IoAdapter;
+
+typedef struct ChannelSubSys {
+ QTAILQ_HEAD(, CrwContainer) pending_crws;
+ bool do_crw_mchk;
+ bool crws_lost;
+ uint8_t max_cssid;
+ uint8_t max_ssid;
+ bool chnmon_active;
+ uint64_t chnmon_area;
+ CssImage *css[MAX_CSSID + 1];
+ uint8_t default_cssid;
+ QTAILQ_HEAD(, IoAdapter) io_adapters;
+} ChannelSubSys;
+
+static ChannelSubSys *channel_subsys;
+
+int css_create_css_image(uint8_t cssid, bool default_image)
+{
+ trace_css_new_image(cssid, default_image ? "(default)" : "");
+ if (cssid > MAX_CSSID) {
+ return -EINVAL;
+ }
+ if (channel_subsys->css[cssid]) {
+ return -EBUSY;
+ }
+ channel_subsys->css[cssid] = g_malloc0(sizeof(CssImage));
+ if (default_image) {
+ channel_subsys->default_cssid = cssid;
+ }
+ return 0;
+}
+
+int css_register_io_adapter(uint8_t type, uint8_t isc, bool swap,
+ bool maskable, uint32_t *id)
+{
+ IoAdapter *adapter;
+ bool found = false;
+ int ret;
+ S390FLICState *fs = s390_get_flic();
+ S390FLICStateClass *fsc = S390_FLIC_COMMON_GET_CLASS(fs);
+
+ *id = 0;
+ QTAILQ_FOREACH(adapter, &channel_subsys->io_adapters, sibling) {
+ if ((adapter->type == type) && (adapter->isc == isc)) {
+ *id = adapter->id;
+ found = true;
+ ret = 0;
+ break;
+ }
+ if (adapter->id >= *id) {
+ *id = adapter->id + 1;
+ }
+ }
+ if (found) {
+ goto out;
+ }
+ adapter = g_new0(IoAdapter, 1);
+ ret = fsc->register_io_adapter(fs, *id, isc, swap, maskable);
+ if (ret == 0) {
+ adapter->id = *id;
+ adapter->isc = isc;
+ adapter->type = type;
+ QTAILQ_INSERT_TAIL(&channel_subsys->io_adapters, adapter, sibling);
+ } else {
+ g_free(adapter);
+ fprintf(stderr, "Unexpected error %d when registering adapter %d\n",
+ ret, *id);
+ }
+out:
+ return ret;
+}
+
+uint16_t css_build_subchannel_id(SubchDev *sch)
+{
+ if (channel_subsys->max_cssid > 0) {
+ return (sch->cssid << 8) | (1 << 3) | (sch->ssid << 1) | 1;
+ }
+ return (sch->ssid << 1) | 1;
+}
+
+static void css_inject_io_interrupt(SubchDev *sch)
+{
+ uint8_t isc = (sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_ISC) >> 11;
+
+ trace_css_io_interrupt(sch->cssid, sch->ssid, sch->schid,
+ sch->curr_status.pmcw.intparm, isc, "");
+ s390_io_interrupt(css_build_subchannel_id(sch),
+ sch->schid,
+ sch->curr_status.pmcw.intparm,
+ isc << 27);
+}
+
+void css_conditional_io_interrupt(SubchDev *sch)
+{
+ /*
+ * If the subchannel is not currently status pending, make it pending
+ * with alert status.
+ */
+ if (!(sch->curr_status.scsw.ctrl & SCSW_STCTL_STATUS_PEND)) {
+ uint8_t isc = (sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_ISC) >> 11;
+
+ trace_css_io_interrupt(sch->cssid, sch->ssid, sch->schid,
+ sch->curr_status.pmcw.intparm, isc,
+ "(unsolicited)");
+ sch->curr_status.scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL;
+ sch->curr_status.scsw.ctrl |=
+ SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+ /* Inject an I/O interrupt. */
+ s390_io_interrupt(css_build_subchannel_id(sch),
+ sch->schid,
+ sch->curr_status.pmcw.intparm,
+ isc << 27);
+ }
+}
+
+void css_adapter_interrupt(uint8_t isc)
+{
+ uint32_t io_int_word = (isc << 27) | IO_INT_WORD_AI;
+
+ trace_css_adapter_interrupt(isc);
+ s390_io_interrupt(0, 0, 0, io_int_word);
+}
+
+static void sch_handle_clear_func(SubchDev *sch)
+{
+ PMCW *p = &sch->curr_status.pmcw;
+ SCSW *s = &sch->curr_status.scsw;
+ int path;
+
+ /* Path management: In our simple css, we always choose the only path. */
+ path = 0x80;
+
+ /* Reset values prior to 'issuing the clear signal'. */
+ p->lpum = 0;
+ p->pom = 0xff;
+ s->flags &= ~SCSW_FLAGS_MASK_PNO;
+
+ /* We always 'attempt to issue the clear signal', and we always succeed. */
+ sch->channel_prog = 0x0;
+ sch->last_cmd_valid = false;
+ s->ctrl &= ~SCSW_ACTL_CLEAR_PEND;
+ s->ctrl |= SCSW_STCTL_STATUS_PEND;
+
+ s->dstat = 0;
+ s->cstat = 0;
+ p->lpum = path;
+
+}
+
+static void sch_handle_halt_func(SubchDev *sch)
+{
+
+ PMCW *p = &sch->curr_status.pmcw;
+ SCSW *s = &sch->curr_status.scsw;
+ hwaddr curr_ccw = sch->channel_prog;
+ int path;
+
+ /* Path management: In our simple css, we always choose the only path. */
+ path = 0x80;
+
+ /* We always 'attempt to issue the halt signal', and we always succeed. */
+ sch->channel_prog = 0x0;
+ sch->last_cmd_valid = false;
+ s->ctrl &= ~SCSW_ACTL_HALT_PEND;
+ s->ctrl |= SCSW_STCTL_STATUS_PEND;
+
+ if ((s->ctrl & (SCSW_ACTL_SUBCH_ACTIVE | SCSW_ACTL_DEVICE_ACTIVE)) ||
+ !((s->ctrl & SCSW_ACTL_START_PEND) ||
+ (s->ctrl & SCSW_ACTL_SUSP))) {
+ s->dstat = SCSW_DSTAT_DEVICE_END;
+ }
+ if ((s->ctrl & (SCSW_ACTL_SUBCH_ACTIVE | SCSW_ACTL_DEVICE_ACTIVE)) ||
+ (s->ctrl & SCSW_ACTL_SUSP)) {
+ s->cpa = curr_ccw + 8;
+ }
+ s->cstat = 0;
+ p->lpum = path;
+
+}
+
+static void copy_sense_id_to_guest(SenseId *dest, SenseId *src)
+{
+ int i;
+
+ dest->reserved = src->reserved;
+ dest->cu_type = cpu_to_be16(src->cu_type);
+ dest->cu_model = src->cu_model;
+ dest->dev_type = cpu_to_be16(src->dev_type);
+ dest->dev_model = src->dev_model;
+ dest->unused = src->unused;
+ for (i = 0; i < ARRAY_SIZE(dest->ciw); i++) {
+ dest->ciw[i].type = src->ciw[i].type;
+ dest->ciw[i].command = src->ciw[i].command;
+ dest->ciw[i].count = cpu_to_be16(src->ciw[i].count);
+ }
+}
+
+static CCW1 copy_ccw_from_guest(hwaddr addr, bool fmt1)
+{
+ CCW0 tmp0;
+ CCW1 tmp1;
+ CCW1 ret;
+
+ if (fmt1) {
+ cpu_physical_memory_read(addr, &tmp1, sizeof(tmp1));
+ ret.cmd_code = tmp1.cmd_code;
+ ret.flags = tmp1.flags;
+ ret.count = be16_to_cpu(tmp1.count);
+ ret.cda = be32_to_cpu(tmp1.cda);
+ } else {
+ cpu_physical_memory_read(addr, &tmp0, sizeof(tmp0));
+ ret.cmd_code = tmp0.cmd_code;
+ ret.flags = tmp0.flags;
+ ret.count = be16_to_cpu(tmp0.count);
+ ret.cda = be16_to_cpu(tmp0.cda1) | (tmp0.cda0 << 16);
+ }
+ return ret;
+}
+
+static int css_interpret_ccw(SubchDev *sch, hwaddr ccw_addr)
+{
+ int ret;
+ bool check_len;
+ int len;
+ CCW1 ccw;
+
+ if (!ccw_addr) {
+ return -EIO;
+ }
+
+ /* Translate everything to format-1 ccws - the information is the same. */
+ ccw = copy_ccw_from_guest(ccw_addr, sch->ccw_fmt_1);
+
+ /* Check for invalid command codes. */
+ if ((ccw.cmd_code & 0x0f) == 0) {
+ return -EINVAL;
+ }
+ if (((ccw.cmd_code & 0x0f) == CCW_CMD_TIC) &&
+ ((ccw.cmd_code & 0xf0) != 0)) {
+ return -EINVAL;
+ }
+
+ if (ccw.flags & CCW_FLAG_SUSPEND) {
+ return -EINPROGRESS;
+ }
+
+ check_len = !((ccw.flags & CCW_FLAG_SLI) && !(ccw.flags & CCW_FLAG_DC));
+
+ if (!ccw.cda) {
+ if (sch->ccw_no_data_cnt == 255) {
+ return -EINVAL;
+ }
+ sch->ccw_no_data_cnt++;
+ }
+
+ /* Look at the command. */
+ switch (ccw.cmd_code) {
+ case CCW_CMD_NOOP:
+ /* Nothing to do. */
+ ret = 0;
+ break;
+ case CCW_CMD_BASIC_SENSE:
+ if (check_len) {
+ if (ccw.count != sizeof(sch->sense_data)) {
+ ret = -EINVAL;
+ break;
+ }
+ }
+ len = MIN(ccw.count, sizeof(sch->sense_data));
+ cpu_physical_memory_write(ccw.cda, sch->sense_data, len);
+ sch->curr_status.scsw.count = ccw.count - len;
+ memset(sch->sense_data, 0, sizeof(sch->sense_data));
+ ret = 0;
+ break;
+ case CCW_CMD_SENSE_ID:
+ {
+ SenseId sense_id;
+
+ copy_sense_id_to_guest(&sense_id, &sch->id);
+ /* Sense ID information is device specific. */
+ if (check_len) {
+ if (ccw.count != sizeof(sense_id)) {
+ ret = -EINVAL;
+ break;
+ }
+ }
+ len = MIN(ccw.count, sizeof(sense_id));
+ /*
+ * Only indicate 0xff in the first sense byte if we actually
+ * have enough place to store at least bytes 0-3.
+ */
+ if (len >= 4) {
+ sense_id.reserved = 0xff;
+ } else {
+ sense_id.reserved = 0;
+ }
+ cpu_physical_memory_write(ccw.cda, &sense_id, len);
+ sch->curr_status.scsw.count = ccw.count - len;
+ ret = 0;
+ break;
+ }
+ case CCW_CMD_TIC:
+ if (sch->last_cmd_valid && (sch->last_cmd.cmd_code == CCW_CMD_TIC)) {
+ ret = -EINVAL;
+ break;
+ }
+ if (ccw.flags & (CCW_FLAG_CC | CCW_FLAG_DC)) {
+ ret = -EINVAL;
+ break;
+ }
+ sch->channel_prog = ccw.cda;
+ ret = -EAGAIN;
+ break;
+ default:
+ if (sch->ccw_cb) {
+ /* Handle device specific commands. */
+ ret = sch->ccw_cb(sch, ccw);
+ } else {
+ ret = -ENOSYS;
+ }
+ break;
+ }
+ sch->last_cmd = ccw;
+ sch->last_cmd_valid = true;
+ if (ret == 0) {
+ if (ccw.flags & CCW_FLAG_CC) {
+ sch->channel_prog += 8;
+ ret = -EAGAIN;
+ }
+ }
+
+ return ret;
+}
+
+static void sch_handle_start_func(SubchDev *sch, ORB *orb)
+{
+
+ PMCW *p = &sch->curr_status.pmcw;
+ SCSW *s = &sch->curr_status.scsw;
+ int path;
+ int ret;
+
+ /* Path management: In our simple css, we always choose the only path. */
+ path = 0x80;
+
+ if (!(s->ctrl & SCSW_ACTL_SUSP)) {
+ s->cstat = 0;
+ s->dstat = 0;
+ /* Look at the orb and try to execute the channel program. */
+ assert(orb != NULL); /* resume does not pass an orb */
+ p->intparm = orb->intparm;
+ if (!(orb->lpm & path)) {
+ /* Generate a deferred cc 3 condition. */
+ s->flags |= SCSW_FLAGS_MASK_CC;
+ s->ctrl &= ~SCSW_CTRL_MASK_STCTL;
+ s->ctrl |= (SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND);
+ return;
+ }
+ sch->ccw_fmt_1 = !!(orb->ctrl0 & ORB_CTRL0_MASK_FMT);
+ sch->ccw_no_data_cnt = 0;
+ } else {
+ s->ctrl &= ~(SCSW_ACTL_SUSP | SCSW_ACTL_RESUME_PEND);
+ }
+ sch->last_cmd_valid = false;
+ do {
+ ret = css_interpret_ccw(sch, sch->channel_prog);
+ switch (ret) {
+ case -EAGAIN:
+ /* ccw chain, continue processing */
+ break;
+ case 0:
+ /* success */
+ s->ctrl &= ~SCSW_ACTL_START_PEND;
+ s->ctrl &= ~SCSW_CTRL_MASK_STCTL;
+ s->ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
+ SCSW_STCTL_STATUS_PEND;
+ s->dstat = SCSW_DSTAT_CHANNEL_END | SCSW_DSTAT_DEVICE_END;
+ s->cpa = sch->channel_prog + 8;
+ break;
+ case -ENOSYS:
+ /* unsupported command, generate unit check (command reject) */
+ s->ctrl &= ~SCSW_ACTL_START_PEND;
+ s->dstat = SCSW_DSTAT_UNIT_CHECK;
+ /* Set sense bit 0 in ecw0. */
+ sch->sense_data[0] = 0x80;
+ s->ctrl &= ~SCSW_CTRL_MASK_STCTL;
+ s->ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
+ SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+ s->cpa = sch->channel_prog + 8;
+ break;
+ case -EFAULT:
+ /* memory problem, generate channel data check */
+ s->ctrl &= ~SCSW_ACTL_START_PEND;
+ s->cstat = SCSW_CSTAT_DATA_CHECK;
+ s->ctrl &= ~SCSW_CTRL_MASK_STCTL;
+ s->ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
+ SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+ s->cpa = sch->channel_prog + 8;
+ break;
+ case -EBUSY:
+ /* subchannel busy, generate deferred cc 1 */
+ s->flags &= ~SCSW_FLAGS_MASK_CC;
+ s->flags |= (1 << 8);
+ s->ctrl &= ~SCSW_CTRL_MASK_STCTL;
+ s->ctrl |= SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+ break;
+ case -EINPROGRESS:
+ /* channel program has been suspended */
+ s->ctrl &= ~SCSW_ACTL_START_PEND;
+ s->ctrl |= SCSW_ACTL_SUSP;
+ break;
+ default:
+ /* error, generate channel program check */
+ s->ctrl &= ~SCSW_ACTL_START_PEND;
+ s->cstat = SCSW_CSTAT_PROG_CHECK;
+ s->ctrl &= ~SCSW_CTRL_MASK_STCTL;
+ s->ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
+ SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+ s->cpa = sch->channel_prog + 8;
+ break;
+ }
+ } while (ret == -EAGAIN);
+
+}
+
+/*
+ * On real machines, this would run asynchronously to the main vcpus.
+ * We might want to make some parts of the ssch handling (interpreting
+ * read/writes) asynchronous later on if we start supporting more than
+ * our current very simple devices.
+ */
+static void do_subchannel_work(SubchDev *sch, ORB *orb)
+{
+
+ SCSW *s = &sch->curr_status.scsw;
+
+ if (s->ctrl & SCSW_FCTL_CLEAR_FUNC) {
+ sch_handle_clear_func(sch);
+ } else if (s->ctrl & SCSW_FCTL_HALT_FUNC) {
+ sch_handle_halt_func(sch);
+ } else if (s->ctrl & SCSW_FCTL_START_FUNC) {
+ sch_handle_start_func(sch, orb);
+ } else {
+ /* Cannot happen. */
+ return;
+ }
+ css_inject_io_interrupt(sch);
+}
+
+static void copy_pmcw_to_guest(PMCW *dest, const PMCW *src)
+{
+ int i;
+
+ dest->intparm = cpu_to_be32(src->intparm);
+ dest->flags = cpu_to_be16(src->flags);
+ dest->devno = cpu_to_be16(src->devno);
+ dest->lpm = src->lpm;
+ dest->pnom = src->pnom;
+ dest->lpum = src->lpum;
+ dest->pim = src->pim;
+ dest->mbi = cpu_to_be16(src->mbi);
+ dest->pom = src->pom;
+ dest->pam = src->pam;
+ for (i = 0; i < ARRAY_SIZE(dest->chpid); i++) {
+ dest->chpid[i] = src->chpid[i];
+ }
+ dest->chars = cpu_to_be32(src->chars);
+}
+
+static void copy_scsw_to_guest(SCSW *dest, const SCSW *src)
+{
+ dest->flags = cpu_to_be16(src->flags);
+ dest->ctrl = cpu_to_be16(src->ctrl);
+ dest->cpa = cpu_to_be32(src->cpa);
+ dest->dstat = src->dstat;
+ dest->cstat = src->cstat;
+ dest->count = cpu_to_be16(src->count);
+}
+
+static void copy_schib_to_guest(SCHIB *dest, const SCHIB *src)
+{
+ int i;
+
+ copy_pmcw_to_guest(&dest->pmcw, &src->pmcw);
+ copy_scsw_to_guest(&dest->scsw, &src->scsw);
+ dest->mba = cpu_to_be64(src->mba);
+ for (i = 0; i < ARRAY_SIZE(dest->mda); i++) {
+ dest->mda[i] = src->mda[i];
+ }
+}
+
+int css_do_stsch(SubchDev *sch, SCHIB *schib)
+{
+ /* Use current status. */
+ copy_schib_to_guest(schib, &sch->curr_status);
+ return 0;
+}
+
+static void copy_pmcw_from_guest(PMCW *dest, const PMCW *src)
+{
+ int i;
+
+ dest->intparm = be32_to_cpu(src->intparm);
+ dest->flags = be16_to_cpu(src->flags);
+ dest->devno = be16_to_cpu(src->devno);
+ dest->lpm = src->lpm;
+ dest->pnom = src->pnom;
+ dest->lpum = src->lpum;
+ dest->pim = src->pim;
+ dest->mbi = be16_to_cpu(src->mbi);
+ dest->pom = src->pom;
+ dest->pam = src->pam;
+ for (i = 0; i < ARRAY_SIZE(dest->chpid); i++) {
+ dest->chpid[i] = src->chpid[i];
+ }
+ dest->chars = be32_to_cpu(src->chars);
+}
+
+static void copy_scsw_from_guest(SCSW *dest, const SCSW *src)
+{
+ dest->flags = be16_to_cpu(src->flags);
+ dest->ctrl = be16_to_cpu(src->ctrl);
+ dest->cpa = be32_to_cpu(src->cpa);
+ dest->dstat = src->dstat;
+ dest->cstat = src->cstat;
+ dest->count = be16_to_cpu(src->count);
+}
+
+static void copy_schib_from_guest(SCHIB *dest, const SCHIB *src)
+{
+ int i;
+
+ copy_pmcw_from_guest(&dest->pmcw, &src->pmcw);
+ copy_scsw_from_guest(&dest->scsw, &src->scsw);
+ dest->mba = be64_to_cpu(src->mba);
+ for (i = 0; i < ARRAY_SIZE(dest->mda); i++) {
+ dest->mda[i] = src->mda[i];
+ }
+}
+
+int css_do_msch(SubchDev *sch, const SCHIB *orig_schib)
+{
+ SCSW *s = &sch->curr_status.scsw;
+ PMCW *p = &sch->curr_status.pmcw;
+ uint16_t oldflags;
+ int ret;
+ SCHIB schib;
+
+ if (!(sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_DNV)) {
+ ret = 0;
+ goto out;
+ }
+
+ if (s->ctrl & SCSW_STCTL_STATUS_PEND) {
+ ret = -EINPROGRESS;
+ goto out;
+ }
+
+ if (s->ctrl &
+ (SCSW_FCTL_START_FUNC|SCSW_FCTL_HALT_FUNC|SCSW_FCTL_CLEAR_FUNC)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ copy_schib_from_guest(&schib, orig_schib);
+ /* Only update the program-modifiable fields. */
+ p->intparm = schib.pmcw.intparm;
+ oldflags = p->flags;
+ p->flags &= ~(PMCW_FLAGS_MASK_ISC | PMCW_FLAGS_MASK_ENA |
+ PMCW_FLAGS_MASK_LM | PMCW_FLAGS_MASK_MME |
+ PMCW_FLAGS_MASK_MP);
+ p->flags |= schib.pmcw.flags &
+ (PMCW_FLAGS_MASK_ISC | PMCW_FLAGS_MASK_ENA |
+ PMCW_FLAGS_MASK_LM | PMCW_FLAGS_MASK_MME |
+ PMCW_FLAGS_MASK_MP);
+ p->lpm = schib.pmcw.lpm;
+ p->mbi = schib.pmcw.mbi;
+ p->pom = schib.pmcw.pom;
+ p->chars &= ~(PMCW_CHARS_MASK_MBFC | PMCW_CHARS_MASK_CSENSE);
+ p->chars |= schib.pmcw.chars &
+ (PMCW_CHARS_MASK_MBFC | PMCW_CHARS_MASK_CSENSE);
+ sch->curr_status.mba = schib.mba;
+
+ /* Has the channel been disabled? */
+ if (sch->disable_cb && (oldflags & PMCW_FLAGS_MASK_ENA) != 0
+ && (p->flags & PMCW_FLAGS_MASK_ENA) == 0) {
+ sch->disable_cb(sch);
+ }
+
+ ret = 0;
+
+out:
+ return ret;
+}
+
+int css_do_xsch(SubchDev *sch)
+{
+ SCSW *s = &sch->curr_status.scsw;
+ PMCW *p = &sch->curr_status.pmcw;
+ int ret;
+
+ if (!(p->flags & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA))) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (!(s->ctrl & SCSW_CTRL_MASK_FCTL) ||
+ ((s->ctrl & SCSW_CTRL_MASK_FCTL) != SCSW_FCTL_START_FUNC) ||
+ (!(s->ctrl &
+ (SCSW_ACTL_RESUME_PEND | SCSW_ACTL_START_PEND | SCSW_ACTL_SUSP))) ||
+ (s->ctrl & SCSW_ACTL_SUBCH_ACTIVE)) {
+ ret = -EINPROGRESS;
+ goto out;
+ }
+
+ if (s->ctrl & SCSW_CTRL_MASK_STCTL) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /* Cancel the current operation. */
+ s->ctrl &= ~(SCSW_FCTL_START_FUNC |
+ SCSW_ACTL_RESUME_PEND |
+ SCSW_ACTL_START_PEND |
+ SCSW_ACTL_SUSP);
+ sch->channel_prog = 0x0;
+ sch->last_cmd_valid = false;
+ s->dstat = 0;
+ s->cstat = 0;
+ ret = 0;
+
+out:
+ return ret;
+}
+
+int css_do_csch(SubchDev *sch)
+{
+ SCSW *s = &sch->curr_status.scsw;
+ PMCW *p = &sch->curr_status.pmcw;
+ int ret;
+
+ if (!(p->flags & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA))) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ /* Trigger the clear function. */
+ s->ctrl &= ~(SCSW_CTRL_MASK_FCTL | SCSW_CTRL_MASK_ACTL);
+ s->ctrl |= SCSW_FCTL_CLEAR_FUNC | SCSW_FCTL_CLEAR_FUNC;
+
+ do_subchannel_work(sch, NULL);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+int css_do_hsch(SubchDev *sch)
+{
+ SCSW *s = &sch->curr_status.scsw;
+ PMCW *p = &sch->curr_status.pmcw;
+ int ret;
+
+ if (!(p->flags & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA))) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (((s->ctrl & SCSW_CTRL_MASK_STCTL) == SCSW_STCTL_STATUS_PEND) ||
+ (s->ctrl & (SCSW_STCTL_PRIMARY |
+ SCSW_STCTL_SECONDARY |
+ SCSW_STCTL_ALERT))) {
+ ret = -EINPROGRESS;
+ goto out;
+ }
+
+ if (s->ctrl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /* Trigger the halt function. */
+ s->ctrl |= SCSW_FCTL_HALT_FUNC;
+ s->ctrl &= ~SCSW_FCTL_START_FUNC;
+ if (((s->ctrl & SCSW_CTRL_MASK_ACTL) ==
+ (SCSW_ACTL_SUBCH_ACTIVE | SCSW_ACTL_DEVICE_ACTIVE)) &&
+ ((s->ctrl & SCSW_CTRL_MASK_STCTL) == SCSW_STCTL_INTERMEDIATE)) {
+ s->ctrl &= ~SCSW_STCTL_STATUS_PEND;
+ }
+ s->ctrl |= SCSW_ACTL_HALT_PEND;
+
+ do_subchannel_work(sch, NULL);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static void css_update_chnmon(SubchDev *sch)
+{
+ if (!(sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_MME)) {
+ /* Not active. */
+ return;
+ }
+ /* The counter is conveniently located at the beginning of the struct. */
+ if (sch->curr_status.pmcw.chars & PMCW_CHARS_MASK_MBFC) {
+ /* Format 1, per-subchannel area. */
+ uint32_t count;
+
+ count = address_space_ldl(&address_space_memory,
+ sch->curr_status.mba,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ count++;
+ address_space_stl(&address_space_memory, sch->curr_status.mba, count,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ } else {
+ /* Format 0, global area. */
+ uint32_t offset;
+ uint16_t count;
+
+ offset = sch->curr_status.pmcw.mbi << 5;
+ count = address_space_lduw(&address_space_memory,
+ channel_subsys->chnmon_area + offset,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ count++;
+ address_space_stw(&address_space_memory,
+ channel_subsys->chnmon_area + offset, count,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ }
+}
+
+int css_do_ssch(SubchDev *sch, ORB *orb)
+{
+ SCSW *s = &sch->curr_status.scsw;
+ PMCW *p = &sch->curr_status.pmcw;
+ int ret;
+
+ if (!(p->flags & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA))) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (s->ctrl & SCSW_STCTL_STATUS_PEND) {
+ ret = -EINPROGRESS;
+ goto out;
+ }
+
+ if (s->ctrl & (SCSW_FCTL_START_FUNC |
+ SCSW_FCTL_HALT_FUNC |
+ SCSW_FCTL_CLEAR_FUNC)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /* If monitoring is active, update counter. */
+ if (channel_subsys->chnmon_active) {
+ css_update_chnmon(sch);
+ }
+ sch->channel_prog = orb->cpa;
+ /* Trigger the start function. */
+ s->ctrl |= (SCSW_FCTL_START_FUNC | SCSW_ACTL_START_PEND);
+ s->flags &= ~SCSW_FLAGS_MASK_PNO;
+
+ do_subchannel_work(sch, orb);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static void copy_irb_to_guest(IRB *dest, const IRB *src, PMCW *pmcw,
+ int *irb_len)
+{
+ int i;
+ uint16_t stctl = src->scsw.ctrl & SCSW_CTRL_MASK_STCTL;
+ uint16_t actl = src->scsw.ctrl & SCSW_CTRL_MASK_ACTL;
+
+ copy_scsw_to_guest(&dest->scsw, &src->scsw);
+
+ for (i = 0; i < ARRAY_SIZE(dest->esw); i++) {
+ dest->esw[i] = cpu_to_be32(src->esw[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(dest->ecw); i++) {
+ dest->ecw[i] = cpu_to_be32(src->ecw[i]);
+ }
+ *irb_len = sizeof(*dest) - sizeof(dest->emw);
+
+ /* extended measurements enabled? */
+ if ((src->scsw.flags & SCSW_FLAGS_MASK_ESWF) ||
+ !(pmcw->flags & PMCW_FLAGS_MASK_TF) ||
+ !(pmcw->chars & PMCW_CHARS_MASK_XMWME)) {
+ return;
+ }
+ /* extended measurements pending? */
+ if (!(stctl & SCSW_STCTL_STATUS_PEND)) {
+ return;
+ }
+ if ((stctl & SCSW_STCTL_PRIMARY) ||
+ (stctl == SCSW_STCTL_SECONDARY) ||
+ ((stctl & SCSW_STCTL_INTERMEDIATE) && (actl & SCSW_ACTL_SUSP))) {
+ for (i = 0; i < ARRAY_SIZE(dest->emw); i++) {
+ dest->emw[i] = cpu_to_be32(src->emw[i]);
+ }
+ }
+ *irb_len = sizeof(*dest);
+}
+
+int css_do_tsch_get_irb(SubchDev *sch, IRB *target_irb, int *irb_len)
+{
+ SCSW *s = &sch->curr_status.scsw;
+ PMCW *p = &sch->curr_status.pmcw;
+ uint16_t stctl;
+ IRB irb;
+
+ if (!(p->flags & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA))) {
+ return 3;
+ }
+
+ stctl = s->ctrl & SCSW_CTRL_MASK_STCTL;
+
+ /* Prepare the irb for the guest. */
+ memset(&irb, 0, sizeof(IRB));
+
+ /* Copy scsw from current status. */
+ memcpy(&irb.scsw, s, sizeof(SCSW));
+ if (stctl & SCSW_STCTL_STATUS_PEND) {
+ if (s->cstat & (SCSW_CSTAT_DATA_CHECK |
+ SCSW_CSTAT_CHN_CTRL_CHK |
+ SCSW_CSTAT_INTF_CTRL_CHK)) {
+ irb.scsw.flags |= SCSW_FLAGS_MASK_ESWF;
+ irb.esw[0] = 0x04804000;
+ } else {
+ irb.esw[0] = 0x00800000;
+ }
+ /* If a unit check is pending, copy sense data. */
+ if ((s->dstat & SCSW_DSTAT_UNIT_CHECK) &&
+ (p->chars & PMCW_CHARS_MASK_CSENSE)) {
+ irb.scsw.flags |= SCSW_FLAGS_MASK_ESWF | SCSW_FLAGS_MASK_ECTL;
+ memcpy(irb.ecw, sch->sense_data, sizeof(sch->sense_data));
+ irb.esw[1] = 0x01000000 | (sizeof(sch->sense_data) << 8);
+ }
+ }
+ /* Store the irb to the guest. */
+ copy_irb_to_guest(target_irb, &irb, p, irb_len);
+
+ return ((stctl & SCSW_STCTL_STATUS_PEND) == 0);
+}
+
+void css_do_tsch_update_subch(SubchDev *sch)
+{
+ SCSW *s = &sch->curr_status.scsw;
+ PMCW *p = &sch->curr_status.pmcw;
+ uint16_t stctl;
+ uint16_t fctl;
+ uint16_t actl;
+
+ stctl = s->ctrl & SCSW_CTRL_MASK_STCTL;
+ fctl = s->ctrl & SCSW_CTRL_MASK_FCTL;
+ actl = s->ctrl & SCSW_CTRL_MASK_ACTL;
+
+ /* Clear conditions on subchannel, if applicable. */
+ if (stctl & SCSW_STCTL_STATUS_PEND) {
+ s->ctrl &= ~SCSW_CTRL_MASK_STCTL;
+ if ((stctl != (SCSW_STCTL_INTERMEDIATE | SCSW_STCTL_STATUS_PEND)) ||
+ ((fctl & SCSW_FCTL_HALT_FUNC) &&
+ (actl & SCSW_ACTL_SUSP))) {
+ s->ctrl &= ~SCSW_CTRL_MASK_FCTL;
+ }
+ if (stctl != (SCSW_STCTL_INTERMEDIATE | SCSW_STCTL_STATUS_PEND)) {
+ s->flags &= ~SCSW_FLAGS_MASK_PNO;
+ s->ctrl &= ~(SCSW_ACTL_RESUME_PEND |
+ SCSW_ACTL_START_PEND |
+ SCSW_ACTL_HALT_PEND |
+ SCSW_ACTL_CLEAR_PEND |
+ SCSW_ACTL_SUSP);
+ } else {
+ if ((actl & SCSW_ACTL_SUSP) &&
+ (fctl & SCSW_FCTL_START_FUNC)) {
+ s->flags &= ~SCSW_FLAGS_MASK_PNO;
+ if (fctl & SCSW_FCTL_HALT_FUNC) {
+ s->ctrl &= ~(SCSW_ACTL_RESUME_PEND |
+ SCSW_ACTL_START_PEND |
+ SCSW_ACTL_HALT_PEND |
+ SCSW_ACTL_CLEAR_PEND |
+ SCSW_ACTL_SUSP);
+ } else {
+ s->ctrl &= ~SCSW_ACTL_RESUME_PEND;
+ }
+ }
+ }
+ /* Clear pending sense data. */
+ if (p->chars & PMCW_CHARS_MASK_CSENSE) {
+ memset(sch->sense_data, 0 , sizeof(sch->sense_data));
+ }
+ }
+}
+
+static void copy_crw_to_guest(CRW *dest, const CRW *src)
+{
+ dest->flags = cpu_to_be16(src->flags);
+ dest->rsid = cpu_to_be16(src->rsid);
+}
+
+int css_do_stcrw(CRW *crw)
+{
+ CrwContainer *crw_cont;
+ int ret;
+
+ crw_cont = QTAILQ_FIRST(&channel_subsys->pending_crws);
+ if (crw_cont) {
+ QTAILQ_REMOVE(&channel_subsys->pending_crws, crw_cont, sibling);
+ copy_crw_to_guest(crw, &crw_cont->crw);
+ g_free(crw_cont);
+ ret = 0;
+ } else {
+ /* List was empty, turn crw machine checks on again. */
+ memset(crw, 0, sizeof(*crw));
+ channel_subsys->do_crw_mchk = true;
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static void copy_crw_from_guest(CRW *dest, const CRW *src)
+{
+ dest->flags = be16_to_cpu(src->flags);
+ dest->rsid = be16_to_cpu(src->rsid);
+}
+
+void css_undo_stcrw(CRW *crw)
+{
+ CrwContainer *crw_cont;
+
+ crw_cont = g_try_malloc0(sizeof(CrwContainer));
+ if (!crw_cont) {
+ channel_subsys->crws_lost = true;
+ return;
+ }
+ copy_crw_from_guest(&crw_cont->crw, crw);
+
+ QTAILQ_INSERT_HEAD(&channel_subsys->pending_crws, crw_cont, sibling);
+}
+
+int css_do_tpi(IOIntCode *int_code, int lowcore)
+{
+ /* No pending interrupts for !KVM. */
+ return 0;
+ }
+
+int css_collect_chp_desc(int m, uint8_t cssid, uint8_t f_chpid, uint8_t l_chpid,
+ int rfmt, void *buf)
+{
+ int i, desc_size;
+ uint32_t words[8];
+ uint32_t chpid_type_word;
+ CssImage *css;
+
+ if (!m && !cssid) {
+ css = channel_subsys->css[channel_subsys->default_cssid];
+ } else {
+ css = channel_subsys->css[cssid];
+ }
+ if (!css) {
+ return 0;
+ }
+ desc_size = 0;
+ for (i = f_chpid; i <= l_chpid; i++) {
+ if (css->chpids[i].in_use) {
+ chpid_type_word = 0x80000000 | (css->chpids[i].type << 8) | i;
+ if (rfmt == 0) {
+ words[0] = cpu_to_be32(chpid_type_word);
+ words[1] = 0;
+ memcpy(buf + desc_size, words, 8);
+ desc_size += 8;
+ } else if (rfmt == 1) {
+ words[0] = cpu_to_be32(chpid_type_word);
+ words[1] = 0;
+ words[2] = 0;
+ words[3] = 0;
+ words[4] = 0;
+ words[5] = 0;
+ words[6] = 0;
+ words[7] = 0;
+ memcpy(buf + desc_size, words, 32);
+ desc_size += 32;
+ }
+ }
+ }
+ return desc_size;
+}
+
+void css_do_schm(uint8_t mbk, int update, int dct, uint64_t mbo)
+{
+ /* dct is currently ignored (not really meaningful for our devices) */
+ /* TODO: Don't ignore mbk. */
+ if (update && !channel_subsys->chnmon_active) {
+ /* Enable measuring. */
+ channel_subsys->chnmon_area = mbo;
+ channel_subsys->chnmon_active = true;
+ }
+ if (!update && channel_subsys->chnmon_active) {
+ /* Disable measuring. */
+ channel_subsys->chnmon_area = 0;
+ channel_subsys->chnmon_active = false;
+ }
+}
+
+int css_do_rsch(SubchDev *sch)
+{
+ SCSW *s = &sch->curr_status.scsw;
+ PMCW *p = &sch->curr_status.pmcw;
+ int ret;
+
+ if (!(p->flags & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA))) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (s->ctrl & SCSW_STCTL_STATUS_PEND) {
+ ret = -EINPROGRESS;
+ goto out;
+ }
+
+ if (((s->ctrl & SCSW_CTRL_MASK_FCTL) != SCSW_FCTL_START_FUNC) ||
+ (s->ctrl & SCSW_ACTL_RESUME_PEND) ||
+ (!(s->ctrl & SCSW_ACTL_SUSP))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* If monitoring is active, update counter. */
+ if (channel_subsys->chnmon_active) {
+ css_update_chnmon(sch);
+ }
+
+ s->ctrl |= SCSW_ACTL_RESUME_PEND;
+ do_subchannel_work(sch, NULL);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+int css_do_rchp(uint8_t cssid, uint8_t chpid)
+{
+ uint8_t real_cssid;
+
+ if (cssid > channel_subsys->max_cssid) {
+ return -EINVAL;
+ }
+ if (channel_subsys->max_cssid == 0) {
+ real_cssid = channel_subsys->default_cssid;
+ } else {
+ real_cssid = cssid;
+ }
+ if (!channel_subsys->css[real_cssid]) {
+ return -EINVAL;
+ }
+
+ if (!channel_subsys->css[real_cssid]->chpids[chpid].in_use) {
+ return -ENODEV;
+ }
+
+ if (!channel_subsys->css[real_cssid]->chpids[chpid].is_virtual) {
+ fprintf(stderr,
+ "rchp unsupported for non-virtual chpid %x.%02x!\n",
+ real_cssid, chpid);
+ return -ENODEV;
+ }
+
+ /* We don't really use a channel path, so we're done here. */
+ css_queue_crw(CRW_RSC_CHP, CRW_ERC_INIT,
+ channel_subsys->max_cssid > 0 ? 1 : 0, chpid);
+ if (channel_subsys->max_cssid > 0) {
+ css_queue_crw(CRW_RSC_CHP, CRW_ERC_INIT, 0, real_cssid << 8);
+ }
+ return 0;
+}
+
+bool css_schid_final(int m, uint8_t cssid, uint8_t ssid, uint16_t schid)
+{
+ SubchSet *set;
+ uint8_t real_cssid;
+
+ real_cssid = (!m && (cssid == 0)) ? channel_subsys->default_cssid : cssid;
+ if (real_cssid > MAX_CSSID || ssid > MAX_SSID ||
+ !channel_subsys->css[real_cssid] ||
+ !channel_subsys->css[real_cssid]->sch_set[ssid]) {
+ return true;
+ }
+ set = channel_subsys->css[real_cssid]->sch_set[ssid];
+ return schid > find_last_bit(set->schids_used,
+ (MAX_SCHID + 1) / sizeof(unsigned long));
+}
+
+static int css_add_virtual_chpid(uint8_t cssid, uint8_t chpid, uint8_t type)
+{
+ CssImage *css;
+
+ trace_css_chpid_add(cssid, chpid, type);
+ if (cssid > MAX_CSSID) {
+ return -EINVAL;
+ }
+ css = channel_subsys->css[cssid];
+ if (!css) {
+ return -EINVAL;
+ }
+ if (css->chpids[chpid].in_use) {
+ return -EEXIST;
+ }
+ css->chpids[chpid].in_use = 1;
+ css->chpids[chpid].type = type;
+ css->chpids[chpid].is_virtual = 1;
+
+ css_generate_chp_crws(cssid, chpid);
+
+ return 0;
+}
+
+void css_sch_build_virtual_schib(SubchDev *sch, uint8_t chpid, uint8_t type)
+{
+ PMCW *p = &sch->curr_status.pmcw;
+ SCSW *s = &sch->curr_status.scsw;
+ int i;
+ CssImage *css = channel_subsys->css[sch->cssid];
+
+ assert(css != NULL);
+ memset(p, 0, sizeof(PMCW));
+ p->flags |= PMCW_FLAGS_MASK_DNV;
+ p->devno = sch->devno;
+ /* single path */
+ p->pim = 0x80;
+ p->pom = 0xff;
+ p->pam = 0x80;
+ p->chpid[0] = chpid;
+ if (!css->chpids[chpid].in_use) {
+ css_add_virtual_chpid(sch->cssid, chpid, type);
+ }
+
+ memset(s, 0, sizeof(SCSW));
+ sch->curr_status.mba = 0;
+ for (i = 0; i < ARRAY_SIZE(sch->curr_status.mda); i++) {
+ sch->curr_status.mda[i] = 0;
+ }
+}
+
+SubchDev *css_find_subch(uint8_t m, uint8_t cssid, uint8_t ssid, uint16_t schid)
+{
+ uint8_t real_cssid;
+
+ real_cssid = (!m && (cssid == 0)) ? channel_subsys->default_cssid : cssid;
+
+ if (!channel_subsys->css[real_cssid]) {
+ return NULL;
+ }
+
+ if (!channel_subsys->css[real_cssid]->sch_set[ssid]) {
+ return NULL;
+ }
+
+ return channel_subsys->css[real_cssid]->sch_set[ssid]->sch[schid];
+}
+
+bool css_subch_visible(SubchDev *sch)
+{
+ if (sch->ssid > channel_subsys->max_ssid) {
+ return false;
+ }
+
+ if (sch->cssid != channel_subsys->default_cssid) {
+ return (channel_subsys->max_cssid > 0);
+ }
+
+ return true;
+}
+
+bool css_present(uint8_t cssid)
+{
+ return (channel_subsys->css[cssid] != NULL);
+}
+
+bool css_devno_used(uint8_t cssid, uint8_t ssid, uint16_t devno)
+{
+ if (!channel_subsys->css[cssid]) {
+ return false;
+ }
+ if (!channel_subsys->css[cssid]->sch_set[ssid]) {
+ return false;
+ }
+
+ return !!test_bit(devno,
+ channel_subsys->css[cssid]->sch_set[ssid]->devnos_used);
+}
+
+void css_subch_assign(uint8_t cssid, uint8_t ssid, uint16_t schid,
+ uint16_t devno, SubchDev *sch)
+{
+ CssImage *css;
+ SubchSet *s_set;
+
+ trace_css_assign_subch(sch ? "assign" : "deassign", cssid, ssid, schid,
+ devno);
+ if (!channel_subsys->css[cssid]) {
+ fprintf(stderr,
+ "Suspicious call to %s (%x.%x.%04x) for non-existing css!\n",
+ __func__, cssid, ssid, schid);
+ return;
+ }
+ css = channel_subsys->css[cssid];
+
+ if (!css->sch_set[ssid]) {
+ css->sch_set[ssid] = g_malloc0(sizeof(SubchSet));
+ }
+ s_set = css->sch_set[ssid];
+
+ s_set->sch[schid] = sch;
+ if (sch) {
+ set_bit(schid, s_set->schids_used);
+ set_bit(devno, s_set->devnos_used);
+ } else {
+ clear_bit(schid, s_set->schids_used);
+ clear_bit(devno, s_set->devnos_used);
+ }
+}
+
+void css_queue_crw(uint8_t rsc, uint8_t erc, int chain, uint16_t rsid)
+{
+ CrwContainer *crw_cont;
+
+ trace_css_crw(rsc, erc, rsid, chain ? "(chained)" : "");
+ /* TODO: Maybe use a static crw pool? */
+ crw_cont = g_try_malloc0(sizeof(CrwContainer));
+ if (!crw_cont) {
+ channel_subsys->crws_lost = true;
+ return;
+ }
+ crw_cont->crw.flags = (rsc << 8) | erc;
+ if (chain) {
+ crw_cont->crw.flags |= CRW_FLAGS_MASK_C;
+ }
+ crw_cont->crw.rsid = rsid;
+ if (channel_subsys->crws_lost) {
+ crw_cont->crw.flags |= CRW_FLAGS_MASK_R;
+ channel_subsys->crws_lost = false;
+ }
+
+ QTAILQ_INSERT_TAIL(&channel_subsys->pending_crws, crw_cont, sibling);
+
+ if (channel_subsys->do_crw_mchk) {
+ channel_subsys->do_crw_mchk = false;
+ /* Inject crw pending machine check. */
+ s390_crw_mchk();
+ }
+}
+
+void css_generate_sch_crws(uint8_t cssid, uint8_t ssid, uint16_t schid,
+ int hotplugged, int add)
+{
+ uint8_t guest_cssid;
+ bool chain_crw;
+
+ if (add && !hotplugged) {
+ return;
+ }
+ if (channel_subsys->max_cssid == 0) {
+ /* Default cssid shows up as 0. */
+ guest_cssid = (cssid == channel_subsys->default_cssid) ? 0 : cssid;
+ } else {
+ /* Show real cssid to the guest. */
+ guest_cssid = cssid;
+ }
+ /*
+ * Only notify for higher subchannel sets/channel subsystems if the
+ * guest has enabled it.
+ */
+ if ((ssid > channel_subsys->max_ssid) ||
+ (guest_cssid > channel_subsys->max_cssid) ||
+ ((channel_subsys->max_cssid == 0) &&
+ (cssid != channel_subsys->default_cssid))) {
+ return;
+ }
+ chain_crw = (channel_subsys->max_ssid > 0) ||
+ (channel_subsys->max_cssid > 0);
+ css_queue_crw(CRW_RSC_SUBCH, CRW_ERC_IPI, chain_crw ? 1 : 0, schid);
+ if (chain_crw) {
+ css_queue_crw(CRW_RSC_SUBCH, CRW_ERC_IPI, 0,
+ (guest_cssid << 8) | (ssid << 4));
+ }
+}
+
+void css_generate_chp_crws(uint8_t cssid, uint8_t chpid)
+{
+ /* TODO */
+}
+
+void css_generate_css_crws(uint8_t cssid)
+{
+ css_queue_crw(CRW_RSC_CSS, 0, 0, cssid);
+}
+
+int css_enable_mcsse(void)
+{
+ trace_css_enable_facility("mcsse");
+ channel_subsys->max_cssid = MAX_CSSID;
+ return 0;
+}
+
+int css_enable_mss(void)
+{
+ trace_css_enable_facility("mss");
+ channel_subsys->max_ssid = MAX_SSID;
+ return 0;
+}
+
+void subch_device_save(SubchDev *s, QEMUFile *f)
+{
+ int i;
+
+ qemu_put_byte(f, s->cssid);
+ qemu_put_byte(f, s->ssid);
+ qemu_put_be16(f, s->schid);
+ qemu_put_be16(f, s->devno);
+ qemu_put_byte(f, s->thinint_active);
+ /* SCHIB */
+ /* PMCW */
+ qemu_put_be32(f, s->curr_status.pmcw.intparm);
+ qemu_put_be16(f, s->curr_status.pmcw.flags);
+ qemu_put_be16(f, s->curr_status.pmcw.devno);
+ qemu_put_byte(f, s->curr_status.pmcw.lpm);
+ qemu_put_byte(f, s->curr_status.pmcw.pnom);
+ qemu_put_byte(f, s->curr_status.pmcw.lpum);
+ qemu_put_byte(f, s->curr_status.pmcw.pim);
+ qemu_put_be16(f, s->curr_status.pmcw.mbi);
+ qemu_put_byte(f, s->curr_status.pmcw.pom);
+ qemu_put_byte(f, s->curr_status.pmcw.pam);
+ qemu_put_buffer(f, s->curr_status.pmcw.chpid, 8);
+ qemu_put_be32(f, s->curr_status.pmcw.chars);
+ /* SCSW */
+ qemu_put_be16(f, s->curr_status.scsw.flags);
+ qemu_put_be16(f, s->curr_status.scsw.ctrl);
+ qemu_put_be32(f, s->curr_status.scsw.cpa);
+ qemu_put_byte(f, s->curr_status.scsw.dstat);
+ qemu_put_byte(f, s->curr_status.scsw.cstat);
+ qemu_put_be16(f, s->curr_status.scsw.count);
+ qemu_put_be64(f, s->curr_status.mba);
+ qemu_put_buffer(f, s->curr_status.mda, 4);
+ /* end SCHIB */
+ qemu_put_buffer(f, s->sense_data, 32);
+ qemu_put_be64(f, s->channel_prog);
+ /* last cmd */
+ qemu_put_byte(f, s->last_cmd.cmd_code);
+ qemu_put_byte(f, s->last_cmd.flags);
+ qemu_put_be16(f, s->last_cmd.count);
+ qemu_put_be32(f, s->last_cmd.cda);
+ qemu_put_byte(f, s->last_cmd_valid);
+ qemu_put_byte(f, s->id.reserved);
+ qemu_put_be16(f, s->id.cu_type);
+ qemu_put_byte(f, s->id.cu_model);
+ qemu_put_be16(f, s->id.dev_type);
+ qemu_put_byte(f, s->id.dev_model);
+ qemu_put_byte(f, s->id.unused);
+ for (i = 0; i < ARRAY_SIZE(s->id.ciw); i++) {
+ qemu_put_byte(f, s->id.ciw[i].type);
+ qemu_put_byte(f, s->id.ciw[i].command);
+ qemu_put_be16(f, s->id.ciw[i].count);
+ }
+ qemu_put_byte(f, s->ccw_fmt_1);
+ qemu_put_byte(f, s->ccw_no_data_cnt);
+ return;
+}
+
+int subch_device_load(SubchDev *s, QEMUFile *f)
+{
+ int i;
+
+ s->cssid = qemu_get_byte(f);
+ s->ssid = qemu_get_byte(f);
+ s->schid = qemu_get_be16(f);
+ s->devno = qemu_get_be16(f);
+ s->thinint_active = qemu_get_byte(f);
+ /* SCHIB */
+ /* PMCW */
+ s->curr_status.pmcw.intparm = qemu_get_be32(f);
+ s->curr_status.pmcw.flags = qemu_get_be16(f);
+ s->curr_status.pmcw.devno = qemu_get_be16(f);
+ s->curr_status.pmcw.lpm = qemu_get_byte(f);
+ s->curr_status.pmcw.pnom = qemu_get_byte(f);
+ s->curr_status.pmcw.lpum = qemu_get_byte(f);
+ s->curr_status.pmcw.pim = qemu_get_byte(f);
+ s->curr_status.pmcw.mbi = qemu_get_be16(f);
+ s->curr_status.pmcw.pom = qemu_get_byte(f);
+ s->curr_status.pmcw.pam = qemu_get_byte(f);
+ qemu_get_buffer(f, s->curr_status.pmcw.chpid, 8);
+ s->curr_status.pmcw.chars = qemu_get_be32(f);
+ /* SCSW */
+ s->curr_status.scsw.flags = qemu_get_be16(f);
+ s->curr_status.scsw.ctrl = qemu_get_be16(f);
+ s->curr_status.scsw.cpa = qemu_get_be32(f);
+ s->curr_status.scsw.dstat = qemu_get_byte(f);
+ s->curr_status.scsw.cstat = qemu_get_byte(f);
+ s->curr_status.scsw.count = qemu_get_be16(f);
+ s->curr_status.mba = qemu_get_be64(f);
+ qemu_get_buffer(f, s->curr_status.mda, 4);
+ /* end SCHIB */
+ qemu_get_buffer(f, s->sense_data, 32);
+ s->channel_prog = qemu_get_be64(f);
+ /* last cmd */
+ s->last_cmd.cmd_code = qemu_get_byte(f);
+ s->last_cmd.flags = qemu_get_byte(f);
+ s->last_cmd.count = qemu_get_be16(f);
+ s->last_cmd.cda = qemu_get_be32(f);
+ s->last_cmd_valid = qemu_get_byte(f);
+ s->id.reserved = qemu_get_byte(f);
+ s->id.cu_type = qemu_get_be16(f);
+ s->id.cu_model = qemu_get_byte(f);
+ s->id.dev_type = qemu_get_be16(f);
+ s->id.dev_model = qemu_get_byte(f);
+ s->id.unused = qemu_get_byte(f);
+ for (i = 0; i < ARRAY_SIZE(s->id.ciw); i++) {
+ s->id.ciw[i].type = qemu_get_byte(f);
+ s->id.ciw[i].command = qemu_get_byte(f);
+ s->id.ciw[i].count = qemu_get_be16(f);
+ }
+ s->ccw_fmt_1 = qemu_get_byte(f);
+ s->ccw_no_data_cnt = qemu_get_byte(f);
+ /*
+ * Hack alert. We don't migrate the channel subsystem status (no
+ * device!), but we need to find out if the guest enabled mss/mcss-e.
+ * If the subchannel is enabled, it certainly was able to access it,
+ * so adjust the max_ssid/max_cssid values for relevant ssid/cssid
+ * values. This is not watertight, but better than nothing.
+ */
+ if (s->curr_status.pmcw.flags & PMCW_FLAGS_MASK_ENA) {
+ if (s->ssid) {
+ channel_subsys->max_ssid = MAX_SSID;
+ }
+ if (s->cssid != channel_subsys->default_cssid) {
+ channel_subsys->max_cssid = MAX_CSSID;
+ }
+ }
+ return 0;
+}
+
+
+static void css_init(void)
+{
+ channel_subsys = g_malloc0(sizeof(*channel_subsys));
+ QTAILQ_INIT(&channel_subsys->pending_crws);
+ channel_subsys->do_crw_mchk = true;
+ channel_subsys->crws_lost = false;
+ channel_subsys->chnmon_active = false;
+ QTAILQ_INIT(&channel_subsys->io_adapters);
+}
+machine_init(css_init);
+
+void css_reset_sch(SubchDev *sch)
+{
+ PMCW *p = &sch->curr_status.pmcw;
+
+ if ((p->flags & PMCW_FLAGS_MASK_ENA) != 0 && sch->disable_cb) {
+ sch->disable_cb(sch);
+ }
+
+ p->intparm = 0;
+ p->flags &= ~(PMCW_FLAGS_MASK_ISC | PMCW_FLAGS_MASK_ENA |
+ PMCW_FLAGS_MASK_LM | PMCW_FLAGS_MASK_MME |
+ PMCW_FLAGS_MASK_MP | PMCW_FLAGS_MASK_TF);
+ p->flags |= PMCW_FLAGS_MASK_DNV;
+ p->devno = sch->devno;
+ p->pim = 0x80;
+ p->lpm = p->pim;
+ p->pnom = 0;
+ p->lpum = 0;
+ p->mbi = 0;
+ p->pom = 0xff;
+ p->pam = 0x80;
+ p->chars &= ~(PMCW_CHARS_MASK_MBFC | PMCW_CHARS_MASK_XMWME |
+ PMCW_CHARS_MASK_CSENSE);
+
+ memset(&sch->curr_status.scsw, 0, sizeof(sch->curr_status.scsw));
+ sch->curr_status.mba = 0;
+
+ sch->channel_prog = 0x0;
+ sch->last_cmd_valid = false;
+ sch->thinint_active = false;
+}
+
+void css_reset(void)
+{
+ CrwContainer *crw_cont;
+
+ /* Clean up monitoring. */
+ channel_subsys->chnmon_active = false;
+ channel_subsys->chnmon_area = 0;
+
+ /* Clear pending CRWs. */
+ while ((crw_cont = QTAILQ_FIRST(&channel_subsys->pending_crws))) {
+ QTAILQ_REMOVE(&channel_subsys->pending_crws, crw_cont, sibling);
+ g_free(crw_cont);
+ }
+ channel_subsys->do_crw_mchk = true;
+ channel_subsys->crws_lost = false;
+
+ /* Reset maximum ids. */
+ channel_subsys->max_cssid = 0;
+ channel_subsys->max_ssid = 0;
+}
diff --git a/hw/s390x/css.h b/hw/s390x/css.h
new file mode 100644
index 00000000..a09bb1f8
--- /dev/null
+++ b/hw/s390x/css.h
@@ -0,0 +1,111 @@
+/*
+ * Channel subsystem structures and definitions.
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef CSS_H
+#define CSS_H
+
+#include "ioinst.h"
+
+/* Channel subsystem constants. */
+#define MAX_SCHID 65535
+#define MAX_SSID 3
+#define MAX_CSSID 254 /* 255 is reserved */
+#define MAX_CHPID 255
+
+#define MAX_CIWS 62
+
+typedef struct CIW {
+ uint8_t type;
+ uint8_t command;
+ uint16_t count;
+} QEMU_PACKED CIW;
+
+typedef struct SenseId {
+ /* common part */
+ uint8_t reserved; /* always 0x'FF' */
+ uint16_t cu_type; /* control unit type */
+ uint8_t cu_model; /* control unit model */
+ uint16_t dev_type; /* device type */
+ uint8_t dev_model; /* device model */
+ uint8_t unused; /* padding byte */
+ /* extended part */
+ CIW ciw[MAX_CIWS]; /* variable # of CIWs */
+} QEMU_PACKED SenseId;
+
+/* Channel measurements, from linux/drivers/s390/cio/cmf.c. */
+typedef struct CMB {
+ uint16_t ssch_rsch_count;
+ uint16_t sample_count;
+ uint32_t device_connect_time;
+ uint32_t function_pending_time;
+ uint32_t device_disconnect_time;
+ uint32_t control_unit_queuing_time;
+ uint32_t device_active_only_time;
+ uint32_t reserved[2];
+} QEMU_PACKED CMB;
+
+typedef struct CMBE {
+ uint32_t ssch_rsch_count;
+ uint32_t sample_count;
+ uint32_t device_connect_time;
+ uint32_t function_pending_time;
+ uint32_t device_disconnect_time;
+ uint32_t control_unit_queuing_time;
+ uint32_t device_active_only_time;
+ uint32_t device_busy_time;
+ uint32_t initial_command_response_time;
+ uint32_t reserved[7];
+} QEMU_PACKED CMBE;
+
+struct SubchDev {
+ /* channel-subsystem related things: */
+ uint8_t cssid;
+ uint8_t ssid;
+ uint16_t schid;
+ uint16_t devno;
+ SCHIB curr_status;
+ uint8_t sense_data[32];
+ hwaddr channel_prog;
+ CCW1 last_cmd;
+ bool last_cmd_valid;
+ bool ccw_fmt_1;
+ bool thinint_active;
+ uint8_t ccw_no_data_cnt;
+ /* transport-provided data: */
+ int (*ccw_cb) (SubchDev *, CCW1);
+ void (*disable_cb)(SubchDev *);
+ SenseId id;
+ void *driver_data;
+};
+
+typedef SubchDev *(*css_subch_cb_func)(uint8_t m, uint8_t cssid, uint8_t ssid,
+ uint16_t schid);
+void subch_device_save(SubchDev *s, QEMUFile *f);
+int subch_device_load(SubchDev *s, QEMUFile *f);
+int css_create_css_image(uint8_t cssid, bool default_image);
+bool css_devno_used(uint8_t cssid, uint8_t ssid, uint16_t devno);
+void css_subch_assign(uint8_t cssid, uint8_t ssid, uint16_t schid,
+ uint16_t devno, SubchDev *sch);
+void css_sch_build_virtual_schib(SubchDev *sch, uint8_t chpid, uint8_t type);
+uint16_t css_build_subchannel_id(SubchDev *sch);
+void css_reset(void);
+void css_reset_sch(SubchDev *sch);
+void css_queue_crw(uint8_t rsc, uint8_t erc, int chain, uint16_t rsid);
+void css_generate_sch_crws(uint8_t cssid, uint8_t ssid, uint16_t schid,
+ int hotplugged, int add);
+void css_generate_chp_crws(uint8_t cssid, uint8_t chpid);
+void css_generate_css_crws(uint8_t cssid);
+void css_adapter_interrupt(uint8_t isc);
+
+#define CSS_IO_ADAPTER_VIRTIO 1
+int css_register_io_adapter(uint8_t type, uint8_t isc, bool swap,
+ bool maskable, uint32_t *id);
+#endif
diff --git a/hw/s390x/event-facility.c b/hw/s390x/event-facility.c
new file mode 100644
index 00000000..0c700eff
--- /dev/null
+++ b/hw/s390x/event-facility.c
@@ -0,0 +1,430 @@
+/*
+ * SCLP
+ * Event Facility
+ * handles SCLP event types
+ * - Signal Quiesce - system power down
+ * - ASCII Console Data - VT220 read and write
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Heinz Graalfs <graalfs@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "sysemu/sysemu.h"
+
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/event-facility.h"
+
+typedef struct SCLPEventsBus {
+ BusState qbus;
+} SCLPEventsBus;
+
+struct SCLPEventFacility {
+ SysBusDevice parent_obj;
+ SCLPEventsBus sbus;
+ /* guest' receive mask */
+ unsigned int receive_mask;
+};
+
+static SCLPEvent cpu_hotplug;
+
+/* return true if any child has event pending set */
+static bool event_pending(SCLPEventFacility *ef)
+{
+ BusChild *kid;
+ SCLPEvent *event;
+ SCLPEventClass *event_class;
+
+ QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ event = DO_UPCAST(SCLPEvent, qdev, qdev);
+ event_class = SCLP_EVENT_GET_CLASS(event);
+ if (event->event_pending &&
+ event_class->get_send_mask() & ef->receive_mask) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static unsigned int get_host_send_mask(SCLPEventFacility *ef)
+{
+ unsigned int mask;
+ BusChild *kid;
+ SCLPEventClass *child;
+
+ mask = 0;
+
+ QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ child = SCLP_EVENT_GET_CLASS((SCLPEvent *) qdev);
+ mask |= child->get_send_mask();
+ }
+ return mask;
+}
+
+static unsigned int get_host_receive_mask(SCLPEventFacility *ef)
+{
+ unsigned int mask;
+ BusChild *kid;
+ SCLPEventClass *child;
+
+ mask = 0;
+
+ QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ child = SCLP_EVENT_GET_CLASS((SCLPEvent *) qdev);
+ mask |= child->get_receive_mask();
+ }
+ return mask;
+}
+
+static uint16_t write_event_length_check(SCCB *sccb)
+{
+ int slen;
+ unsigned elen = 0;
+ EventBufferHeader *event;
+ WriteEventData *wed = (WriteEventData *) sccb;
+
+ event = (EventBufferHeader *) &wed->ebh;
+ for (slen = sccb_data_len(sccb); slen > 0; slen -= elen) {
+ elen = be16_to_cpu(event->length);
+ if (elen < sizeof(*event) || elen > slen) {
+ return SCLP_RC_EVENT_BUFFER_SYNTAX_ERROR;
+ }
+ event = (void *) event + elen;
+ }
+ if (slen) {
+ return SCLP_RC_INCONSISTENT_LENGTHS;
+ }
+ return SCLP_RC_NORMAL_COMPLETION;
+}
+
+static uint16_t handle_write_event_buf(SCLPEventFacility *ef,
+ EventBufferHeader *event_buf, SCCB *sccb)
+{
+ uint16_t rc;
+ BusChild *kid;
+ SCLPEvent *event;
+ SCLPEventClass *ec;
+
+ rc = SCLP_RC_INVALID_FUNCTION;
+
+ QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ event = (SCLPEvent *) qdev;
+ ec = SCLP_EVENT_GET_CLASS(event);
+
+ if (ec->write_event_data &&
+ ec->can_handle_event(event_buf->type)) {
+ rc = ec->write_event_data(event, event_buf);
+ break;
+ }
+ }
+ return rc;
+}
+
+static uint16_t handle_sccb_write_events(SCLPEventFacility *ef, SCCB *sccb)
+{
+ uint16_t rc;
+ int slen;
+ unsigned elen = 0;
+ EventBufferHeader *event_buf;
+ WriteEventData *wed = (WriteEventData *) sccb;
+
+ event_buf = &wed->ebh;
+ rc = SCLP_RC_NORMAL_COMPLETION;
+
+ /* loop over all contained event buffers */
+ for (slen = sccb_data_len(sccb); slen > 0; slen -= elen) {
+ elen = be16_to_cpu(event_buf->length);
+
+ /* in case of a previous error mark all trailing buffers
+ * as not accepted */
+ if (rc != SCLP_RC_NORMAL_COMPLETION) {
+ event_buf->flags &= ~(SCLP_EVENT_BUFFER_ACCEPTED);
+ } else {
+ rc = handle_write_event_buf(ef, event_buf, sccb);
+ }
+ event_buf = (void *) event_buf + elen;
+ }
+ return rc;
+}
+
+static void write_event_data(SCLPEventFacility *ef, SCCB *sccb)
+{
+ if (sccb->h.function_code != SCLP_FC_NORMAL_WRITE) {
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_FUNCTION);
+ goto out;
+ }
+ if (be16_to_cpu(sccb->h.length) < 8) {
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_INSUFFICIENT_SCCB_LENGTH);
+ goto out;
+ }
+ /* first do a sanity check of the write events */
+ sccb->h.response_code = cpu_to_be16(write_event_length_check(sccb));
+
+ /* if no early error, then execute */
+ if (sccb->h.response_code == be16_to_cpu(SCLP_RC_NORMAL_COMPLETION)) {
+ sccb->h.response_code =
+ cpu_to_be16(handle_sccb_write_events(ef, sccb));
+ }
+
+out:
+ return;
+}
+
+static uint16_t handle_sccb_read_events(SCLPEventFacility *ef, SCCB *sccb,
+ unsigned int mask)
+{
+ uint16_t rc;
+ int slen;
+ unsigned elen;
+ BusChild *kid;
+ SCLPEvent *event;
+ SCLPEventClass *ec;
+ EventBufferHeader *event_buf;
+ ReadEventData *red = (ReadEventData *) sccb;
+
+ event_buf = &red->ebh;
+ event_buf->length = 0;
+ slen = sizeof(sccb->data);
+
+ rc = SCLP_RC_NO_EVENT_BUFFERS_STORED;
+
+ QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ event = (SCLPEvent *) qdev;
+ ec = SCLP_EVENT_GET_CLASS(event);
+
+ if (mask & ec->get_send_mask()) {
+ if (ec->read_event_data(event, event_buf, &slen)) {
+ elen = be16_to_cpu(event_buf->length);
+ event_buf = (EventBufferHeader *) ((char *)event_buf + elen);
+ rc = SCLP_RC_NORMAL_COMPLETION;
+ }
+ }
+ }
+
+ if (sccb->h.control_mask[2] & SCLP_VARIABLE_LENGTH_RESPONSE) {
+ /* architecture suggests to reset variable-length-response bit */
+ sccb->h.control_mask[2] &= ~SCLP_VARIABLE_LENGTH_RESPONSE;
+ /* with a new length value */
+ sccb->h.length = cpu_to_be16(SCCB_SIZE - slen);
+ }
+ return rc;
+}
+
+static void read_event_data(SCLPEventFacility *ef, SCCB *sccb)
+{
+ unsigned int sclp_active_selection_mask;
+ unsigned int sclp_cp_receive_mask;
+
+ ReadEventData *red = (ReadEventData *) sccb;
+
+ if (be16_to_cpu(sccb->h.length) != SCCB_SIZE) {
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_INSUFFICIENT_SCCB_LENGTH);
+ goto out;
+ }
+
+ sclp_cp_receive_mask = ef->receive_mask;
+
+ /* get active selection mask */
+ switch (sccb->h.function_code) {
+ case SCLP_UNCONDITIONAL_READ:
+ sclp_active_selection_mask = sclp_cp_receive_mask;
+ break;
+ case SCLP_SELECTIVE_READ:
+ if (!(sclp_cp_receive_mask & be32_to_cpu(red->mask))) {
+ sccb->h.response_code =
+ cpu_to_be16(SCLP_RC_INVALID_SELECTION_MASK);
+ goto out;
+ }
+ sclp_active_selection_mask = be32_to_cpu(red->mask);
+ break;
+ default:
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_FUNCTION);
+ goto out;
+ }
+ sccb->h.response_code = cpu_to_be16(
+ handle_sccb_read_events(ef, sccb, sclp_active_selection_mask));
+
+out:
+ return;
+}
+
+static void write_event_mask(SCLPEventFacility *ef, SCCB *sccb)
+{
+ WriteEventMask *we_mask = (WriteEventMask *) sccb;
+
+ /* Attention: We assume that Linux uses 4-byte masks, what it actually
+ does. Architecture allows for masks of variable size, though */
+ if (be16_to_cpu(we_mask->mask_length) != 4) {
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_MASK_LENGTH);
+ goto out;
+ }
+
+ /* keep track of the guest's capability masks */
+ ef->receive_mask = be32_to_cpu(we_mask->cp_receive_mask);
+
+ /* return the SCLP's capability masks to the guest */
+ we_mask->send_mask = cpu_to_be32(get_host_send_mask(ef));
+ we_mask->receive_mask = cpu_to_be32(get_host_receive_mask(ef));
+
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_COMPLETION);
+
+out:
+ return;
+}
+
+/* qemu object creation and initialization functions */
+
+#define TYPE_SCLP_EVENTS_BUS "s390-sclp-events-bus"
+
+static void sclp_events_bus_class_init(ObjectClass *klass, void *data)
+{
+}
+
+static const TypeInfo sclp_events_bus_info = {
+ .name = TYPE_SCLP_EVENTS_BUS,
+ .parent = TYPE_BUS,
+ .class_init = sclp_events_bus_class_init,
+};
+
+static void command_handler(SCLPEventFacility *ef, SCCB *sccb, uint64_t code)
+{
+ switch (code & SCLP_CMD_CODE_MASK) {
+ case SCLP_CMD_READ_EVENT_DATA:
+ read_event_data(ef, sccb);
+ break;
+ case SCLP_CMD_WRITE_EVENT_DATA:
+ write_event_data(ef, sccb);
+ break;
+ case SCLP_CMD_WRITE_EVENT_MASK:
+ write_event_mask(ef, sccb);
+ break;
+ default:
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_SCLP_COMMAND);
+ break;
+ }
+}
+
+static const VMStateDescription vmstate_event_facility = {
+ .name = "vmstate-event-facility",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(receive_mask, SCLPEventFacility),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int init_event_facility(SCLPEventFacility *event_facility)
+{
+ DeviceState *sdev = DEVICE(event_facility);
+ DeviceState *quiesce;
+
+ /* Spawn a new bus for SCLP events */
+ qbus_create_inplace(&event_facility->sbus, sizeof(event_facility->sbus),
+ TYPE_SCLP_EVENTS_BUS, sdev, NULL);
+
+ quiesce = qdev_create(&event_facility->sbus.qbus, "sclpquiesce");
+ if (!quiesce) {
+ return -1;
+ }
+ qdev_init_nofail(quiesce);
+
+ object_initialize(&cpu_hotplug, sizeof(cpu_hotplug), TYPE_SCLP_CPU_HOTPLUG);
+ qdev_set_parent_bus(DEVICE(&cpu_hotplug), BUS(&event_facility->sbus));
+ object_property_set_bool(OBJECT(&cpu_hotplug), true, "realized", NULL);
+
+ return 0;
+}
+
+static void reset_event_facility(DeviceState *dev)
+{
+ SCLPEventFacility *sdev = EVENT_FACILITY(dev);
+
+ sdev->receive_mask = 0;
+}
+
+static void init_event_facility_class(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(sbdc);
+ SCLPEventFacilityClass *k = EVENT_FACILITY_CLASS(dc);
+
+ dc->reset = reset_event_facility;
+ dc->vmsd = &vmstate_event_facility;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ k->init = init_event_facility;
+ k->command_handler = command_handler;
+ k->event_pending = event_pending;
+}
+
+static const TypeInfo sclp_event_facility_info = {
+ .name = TYPE_SCLP_EVENT_FACILITY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SCLPEventFacility),
+ .class_init = init_event_facility_class,
+ .class_size = sizeof(SCLPEventFacilityClass),
+};
+
+static void event_realize(DeviceState *qdev, Error **errp)
+{
+ SCLPEvent *event = SCLP_EVENT(qdev);
+ SCLPEventClass *child = SCLP_EVENT_GET_CLASS(event);
+
+ if (child->init) {
+ int rc = child->init(event);
+ if (rc < 0) {
+ error_setg(errp, "SCLP event initialization failed.");
+ return;
+ }
+ }
+}
+
+static void event_unrealize(DeviceState *qdev, Error **errp)
+{
+ SCLPEvent *event = SCLP_EVENT(qdev);
+ SCLPEventClass *child = SCLP_EVENT_GET_CLASS(event);
+ if (child->exit) {
+ int rc = child->exit(event);
+ if (rc < 0) {
+ error_setg(errp, "SCLP event exit failed.");
+ return;
+ }
+ }
+}
+
+static void event_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->bus_type = TYPE_SCLP_EVENTS_BUS;
+ dc->realize = event_realize;
+ dc->unrealize = event_unrealize;
+}
+
+static const TypeInfo sclp_event_type_info = {
+ .name = TYPE_SCLP_EVENT,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(SCLPEvent),
+ .class_init = event_class_init,
+ .class_size = sizeof(SCLPEventClass),
+ .abstract = true,
+};
+
+static void register_types(void)
+{
+ type_register_static(&sclp_events_bus_info);
+ type_register_static(&sclp_event_facility_info);
+ type_register_static(&sclp_event_type_info);
+}
+
+type_init(register_types)
diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
new file mode 100644
index 00000000..2e0a8b6e
--- /dev/null
+++ b/hw/s390x/ipl.c
@@ -0,0 +1,333 @@
+/*
+ * bootloader support
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Christian Borntraeger <borntraeger@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "sysemu/sysemu.h"
+#include "cpu.h"
+#include "elf.h"
+#include "hw/loader.h"
+#include "hw/sysbus.h"
+#include "hw/s390x/virtio-ccw.h"
+#include "hw/s390x/css.h"
+#include "ipl.h"
+
+#define KERN_IMAGE_START 0x010000UL
+#define KERN_PARM_AREA 0x010480UL
+#define INITRD_START 0x800000UL
+#define INITRD_PARM_START 0x010408UL
+#define INITRD_PARM_SIZE 0x010410UL
+#define PARMFILE_START 0x001000UL
+#define ZIPL_IMAGE_START 0x009000UL
+#define IPL_PSW_MASK (PSW_MASK_32 | PSW_MASK_64)
+
+#define TYPE_S390_IPL "s390-ipl"
+#define S390_IPL(obj) \
+ OBJECT_CHECK(S390IPLState, (obj), TYPE_S390_IPL)
+#if 0
+#define S390_IPL_CLASS(klass) \
+ OBJECT_CLASS_CHECK(S390IPLState, (klass), TYPE_S390_IPL)
+#define S390_IPL_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(S390IPLState, (obj), TYPE_S390_IPL)
+#endif
+
+typedef struct S390IPLClass {
+ /*< private >*/
+ SysBusDeviceClass parent_class;
+ /*< public >*/
+
+ void (*parent_reset) (SysBusDevice *dev);
+} S390IPLClass;
+
+typedef struct S390IPLState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ uint64_t start_addr;
+ uint64_t bios_start_addr;
+ bool enforce_bios;
+ IplParameterBlock iplb;
+ bool iplb_valid;
+ bool reipl_requested;
+
+ /*< public >*/
+ char *kernel;
+ char *initrd;
+ char *cmdline;
+ char *firmware;
+ uint8_t cssid;
+ uint8_t ssid;
+ uint16_t devno;
+} S390IPLState;
+
+static const VMStateDescription vmstate_iplb = {
+ .name = "ipl/iplb",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(reserved1, IplParameterBlock, 110),
+ VMSTATE_UINT16(devno, IplParameterBlock),
+ VMSTATE_UINT8_ARRAY(reserved2, IplParameterBlock, 88),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ipl = {
+ .name = "ipl",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(start_addr, S390IPLState),
+ VMSTATE_UINT64(bios_start_addr, S390IPLState),
+ VMSTATE_STRUCT(iplb, S390IPLState, 0, vmstate_iplb, IplParameterBlock),
+ VMSTATE_BOOL(iplb_valid, S390IPLState),
+ VMSTATE_UINT8(cssid, S390IPLState),
+ VMSTATE_UINT8(ssid, S390IPLState),
+ VMSTATE_UINT16(devno, S390IPLState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint64_t bios_translate_addr(void *opaque, uint64_t srcaddr)
+{
+ uint64_t dstaddr = *(uint64_t *) opaque;
+ /*
+ * Assuming that our s390-ccw.img was linked for starting at address 0,
+ * we can simply add the destination address for the final location
+ */
+ return srcaddr + dstaddr;
+}
+
+static int s390_ipl_init(SysBusDevice *dev)
+{
+ S390IPLState *ipl = S390_IPL(dev);
+ uint64_t pentry = KERN_IMAGE_START;
+ int kernel_size;
+
+ int bios_size;
+ char *bios_filename;
+
+ /*
+ * Always load the bios if it was enforced,
+ * even if an external kernel has been defined.
+ */
+ if (!ipl->kernel || ipl->enforce_bios) {
+ uint64_t fwbase = (MIN(ram_size, 0x80000000U) - 0x200000) & ~0xffffUL;
+
+ if (bios_name == NULL) {
+ bios_name = ipl->firmware;
+ }
+
+ bios_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (bios_filename == NULL) {
+ hw_error("could not find stage1 bootloader\n");
+ }
+
+ bios_size = load_elf(bios_filename, bios_translate_addr, &fwbase,
+ &ipl->bios_start_addr, NULL, NULL, 1,
+ ELF_MACHINE, 0);
+ if (bios_size > 0) {
+ /* Adjust ELF start address to final location */
+ ipl->bios_start_addr += fwbase;
+ } else {
+ /* Try to load non-ELF file (e.g. s390-zipl.rom) */
+ bios_size = load_image_targphys(bios_filename, ZIPL_IMAGE_START,
+ 4096);
+ ipl->bios_start_addr = ZIPL_IMAGE_START;
+ }
+ g_free(bios_filename);
+
+ if (bios_size == -1) {
+ hw_error("could not load bootloader '%s'\n", bios_name);
+ }
+
+ /* default boot target is the bios */
+ ipl->start_addr = ipl->bios_start_addr;
+ }
+
+ if (ipl->kernel) {
+ kernel_size = load_elf(ipl->kernel, NULL, NULL, &pentry, NULL,
+ NULL, 1, ELF_MACHINE, 0);
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(ipl->kernel, 0, ram_size);
+ }
+ if (kernel_size < 0) {
+ fprintf(stderr, "could not load kernel '%s'\n", ipl->kernel);
+ return -1;
+ }
+ /*
+ * Is it a Linux kernel (starting at 0x10000)? If yes, we fill in the
+ * kernel parameters here as well. Note: For old kernels (up to 3.2)
+ * we can not rely on the ELF entry point - it was 0x800 (the SALIPL
+ * loader) and it won't work. For this case we force it to 0x10000, too.
+ */
+ if (pentry == KERN_IMAGE_START || pentry == 0x800) {
+ ipl->start_addr = KERN_IMAGE_START;
+ /* Overwrite parameters in the kernel image, which are "rom" */
+ strcpy(rom_ptr(KERN_PARM_AREA), ipl->cmdline);
+ } else {
+ ipl->start_addr = pentry;
+ }
+
+ if (ipl->initrd) {
+ ram_addr_t initrd_offset;
+ int initrd_size;
+
+ initrd_offset = INITRD_START;
+ while (kernel_size + 0x100000 > initrd_offset) {
+ initrd_offset += 0x100000;
+ }
+ initrd_size = load_image_targphys(ipl->initrd, initrd_offset,
+ ram_size - initrd_offset);
+ if (initrd_size == -1) {
+ fprintf(stderr, "qemu: could not load initrd '%s'\n",
+ ipl->initrd);
+ exit(1);
+ }
+
+ /*
+ * we have to overwrite values in the kernel image,
+ * which are "rom"
+ */
+ stq_p(rom_ptr(INITRD_PARM_START), initrd_offset);
+ stq_p(rom_ptr(INITRD_PARM_SIZE), initrd_size);
+ }
+ }
+ return 0;
+}
+
+static Property s390_ipl_properties[] = {
+ DEFINE_PROP_STRING("kernel", S390IPLState, kernel),
+ DEFINE_PROP_STRING("initrd", S390IPLState, initrd),
+ DEFINE_PROP_STRING("cmdline", S390IPLState, cmdline),
+ DEFINE_PROP_STRING("firmware", S390IPLState, firmware),
+ DEFINE_PROP_BOOL("enforce_bios", S390IPLState, enforce_bios, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+/*
+ * In addition to updating the iplstate, this function returns:
+ * - 0 if system was ipled with external kernel
+ * - -1 if no valid boot device was found
+ * - ccw id of the boot device otherwise
+ */
+static uint64_t s390_update_iplstate(CPUS390XState *env, S390IPLState *ipl)
+{
+ DeviceState *dev_st;
+
+ if (ipl->iplb_valid) {
+ ipl->cssid = 0;
+ ipl->ssid = 0;
+ ipl->devno = ipl->iplb.devno;
+ goto out;
+ }
+
+ if (ipl->kernel) {
+ return 0;
+ }
+
+ dev_st = get_boot_device(0);
+ if (dev_st) {
+ VirtioCcwDevice *ccw_dev = (VirtioCcwDevice *) object_dynamic_cast(
+ OBJECT(qdev_get_parent_bus(dev_st)->parent),
+ TYPE_VIRTIO_CCW_DEVICE);
+ if (ccw_dev) {
+ ipl->cssid = ccw_dev->sch->cssid;
+ ipl->ssid = ccw_dev->sch->ssid;
+ ipl->devno = ccw_dev->sch->devno;
+ goto out;
+ }
+ }
+
+ return -1;
+out:
+ return (uint32_t) (ipl->cssid << 24 | ipl->ssid << 16 | ipl->devno);
+}
+
+int s390_ipl_update_diag308(IplParameterBlock *iplb)
+{
+ S390IPLState *ipl;
+
+ ipl = S390_IPL(object_resolve_path(TYPE_S390_IPL, NULL));
+ if (ipl) {
+ ipl->iplb = *iplb;
+ ipl->iplb_valid = true;
+ return 0;
+ }
+ return -1;
+}
+
+IplParameterBlock *s390_ipl_get_iplb(void)
+{
+ S390IPLState *ipl;
+
+ ipl = S390_IPL(object_resolve_path(TYPE_S390_IPL, NULL));
+ if (!ipl || !ipl->iplb_valid) {
+ return NULL;
+ }
+ return &ipl->iplb;
+}
+
+void s390_reipl_request(void)
+{
+ S390IPLState *ipl;
+
+ ipl = S390_IPL(object_resolve_path(TYPE_S390_IPL, NULL));
+ ipl->reipl_requested = true;
+ qemu_system_reset_request();
+}
+
+static void s390_ipl_reset(DeviceState *dev)
+{
+ S390IPLState *ipl = S390_IPL(dev);
+ S390CPU *cpu = S390_CPU(qemu_get_cpu(0));
+ CPUS390XState *env = &cpu->env;
+
+ env->psw.addr = ipl->start_addr;
+ env->psw.mask = IPL_PSW_MASK;
+
+ if (!ipl->reipl_requested) {
+ ipl->iplb_valid = false;
+ }
+ ipl->reipl_requested = false;
+
+ if (!ipl->kernel || ipl->iplb_valid) {
+ env->psw.addr = ipl->bios_start_addr;
+ env->regs[7] = s390_update_iplstate(env, ipl);
+ }
+
+ s390_cpu_set_state(CPU_STATE_OPERATING, cpu);
+}
+
+static void s390_ipl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = s390_ipl_init;
+ dc->props = s390_ipl_properties;
+ dc->reset = s390_ipl_reset;
+ dc->vmsd = &vmstate_ipl;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo s390_ipl_info = {
+ .class_init = s390_ipl_class_init,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .name = "s390-ipl",
+ .instance_size = sizeof(S390IPLState),
+};
+
+static void s390_ipl_register_types(void)
+{
+ type_register_static(&s390_ipl_info);
+}
+
+type_init(s390_ipl_register_types)
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
new file mode 100644
index 00000000..70497bc6
--- /dev/null
+++ b/hw/s390x/ipl.h
@@ -0,0 +1,25 @@
+/*
+ * s390 IPL device
+ *
+ * Copyright 2015 IBM Corp.
+ * Author(s): Zhang Fan <bjfanzh@cn.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef HW_S390_IPL_H
+#define HW_S390_IPL_H
+
+typedef struct IplParameterBlock {
+ uint8_t reserved1[110];
+ uint16_t devno;
+ uint8_t reserved2[88];
+} IplParameterBlock;
+
+int s390_ipl_update_diag308(IplParameterBlock *iplb);
+IplParameterBlock *s390_ipl_get_iplb(void);
+void s390_reipl_request(void);
+
+#endif
diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c
new file mode 100644
index 00000000..560b66a5
--- /dev/null
+++ b/hw/s390x/s390-pci-bus.c
@@ -0,0 +1,596 @@
+/*
+ * s390 PCI BUS
+ *
+ * Copyright 2014 IBM Corp.
+ * Author(s): Frank Blaschka <frank.blaschka@de.ibm.com>
+ * Hong Bo Li <lihbbj@cn.ibm.com>
+ * Yi Min Zhao <zyimin@cn.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "s390-pci-bus.h"
+#include <hw/pci/pci_bus.h>
+#include <hw/pci/msi.h>
+#include <qemu/error-report.h>
+
+/* #define DEBUG_S390PCI_BUS */
+#ifdef DEBUG_S390PCI_BUS
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, "S390pci-bus: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+int chsc_sei_nt2_get_event(void *res)
+{
+ ChscSeiNt2Res *nt2_res = (ChscSeiNt2Res *)res;
+ PciCcdfAvail *accdf;
+ PciCcdfErr *eccdf;
+ int rc = 1;
+ SeiContainer *sei_cont;
+ S390pciState *s = S390_PCI_HOST_BRIDGE(
+ object_resolve_path(TYPE_S390_PCI_HOST_BRIDGE, NULL));
+
+ if (!s) {
+ return rc;
+ }
+
+ sei_cont = QTAILQ_FIRST(&s->pending_sei);
+ if (sei_cont) {
+ QTAILQ_REMOVE(&s->pending_sei, sei_cont, link);
+ nt2_res->nt = 2;
+ nt2_res->cc = sei_cont->cc;
+ nt2_res->length = cpu_to_be16(sizeof(ChscSeiNt2Res));
+ switch (sei_cont->cc) {
+ case 1: /* error event */
+ eccdf = (PciCcdfErr *)nt2_res->ccdf;
+ eccdf->fid = cpu_to_be32(sei_cont->fid);
+ eccdf->fh = cpu_to_be32(sei_cont->fh);
+ eccdf->e = cpu_to_be32(sei_cont->e);
+ eccdf->faddr = cpu_to_be64(sei_cont->faddr);
+ eccdf->pec = cpu_to_be16(sei_cont->pec);
+ break;
+ case 2: /* availability event */
+ accdf = (PciCcdfAvail *)nt2_res->ccdf;
+ accdf->fid = cpu_to_be32(sei_cont->fid);
+ accdf->fh = cpu_to_be32(sei_cont->fh);
+ accdf->pec = cpu_to_be16(sei_cont->pec);
+ break;
+ default:
+ abort();
+ }
+ g_free(sei_cont);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+int chsc_sei_nt2_have_event(void)
+{
+ S390pciState *s = S390_PCI_HOST_BRIDGE(
+ object_resolve_path(TYPE_S390_PCI_HOST_BRIDGE, NULL));
+
+ if (!s) {
+ return 0;
+ }
+
+ return !QTAILQ_EMPTY(&s->pending_sei);
+}
+
+S390PCIBusDevice *s390_pci_find_dev_by_fid(uint32_t fid)
+{
+ S390PCIBusDevice *pbdev;
+ int i;
+ S390pciState *s = S390_PCI_HOST_BRIDGE(
+ object_resolve_path(TYPE_S390_PCI_HOST_BRIDGE, NULL));
+
+ if (!s) {
+ return NULL;
+ }
+
+ for (i = 0; i < PCI_SLOT_MAX; i++) {
+ pbdev = &s->pbdev[i];
+ if ((pbdev->fh != 0) && (pbdev->fid == fid)) {
+ return pbdev;
+ }
+ }
+
+ return NULL;
+}
+
+void s390_pci_sclp_configure(int configure, SCCB *sccb)
+{
+ PciCfgSccb *psccb = (PciCfgSccb *)sccb;
+ S390PCIBusDevice *pbdev = s390_pci_find_dev_by_fid(be32_to_cpu(psccb->aid));
+ uint16_t rc;
+
+ if (pbdev) {
+ if ((configure == 1 && pbdev->configured == true) ||
+ (configure == 0 && pbdev->configured == false)) {
+ rc = SCLP_RC_NO_ACTION_REQUIRED;
+ } else {
+ pbdev->configured = !pbdev->configured;
+ rc = SCLP_RC_NORMAL_COMPLETION;
+ }
+ } else {
+ DPRINTF("sclp config %d no dev found\n", configure);
+ rc = SCLP_RC_ADAPTER_ID_NOT_RECOGNIZED;
+ }
+
+ psccb->header.response_code = cpu_to_be16(rc);
+ return;
+}
+
+static uint32_t s390_pci_get_pfid(PCIDevice *pdev)
+{
+ return PCI_SLOT(pdev->devfn);
+}
+
+static uint32_t s390_pci_get_pfh(PCIDevice *pdev)
+{
+ return PCI_SLOT(pdev->devfn) | FH_VIRT;
+}
+
+S390PCIBusDevice *s390_pci_find_dev_by_idx(uint32_t idx)
+{
+ S390PCIBusDevice *pbdev;
+ int i;
+ int j = 0;
+ S390pciState *s = S390_PCI_HOST_BRIDGE(
+ object_resolve_path(TYPE_S390_PCI_HOST_BRIDGE, NULL));
+
+ if (!s) {
+ return NULL;
+ }
+
+ for (i = 0; i < PCI_SLOT_MAX; i++) {
+ pbdev = &s->pbdev[i];
+
+ if (pbdev->fh == 0) {
+ continue;
+ }
+
+ if (j == idx) {
+ return pbdev;
+ }
+ j++;
+ }
+
+ return NULL;
+}
+
+S390PCIBusDevice *s390_pci_find_dev_by_fh(uint32_t fh)
+{
+ S390PCIBusDevice *pbdev;
+ int i;
+ S390pciState *s = S390_PCI_HOST_BRIDGE(
+ object_resolve_path(TYPE_S390_PCI_HOST_BRIDGE, NULL));
+
+ if (!s || !fh) {
+ return NULL;
+ }
+
+ for (i = 0; i < PCI_SLOT_MAX; i++) {
+ pbdev = &s->pbdev[i];
+ if (pbdev->fh == fh) {
+ return pbdev;
+ }
+ }
+
+ return NULL;
+}
+
+static void s390_pci_generate_event(uint8_t cc, uint16_t pec, uint32_t fh,
+ uint32_t fid, uint64_t faddr, uint32_t e)
+{
+ SeiContainer *sei_cont;
+ S390pciState *s = S390_PCI_HOST_BRIDGE(
+ object_resolve_path(TYPE_S390_PCI_HOST_BRIDGE, NULL));
+
+ if (!s) {
+ return;
+ }
+
+ sei_cont = g_malloc0(sizeof(SeiContainer));
+ sei_cont->fh = fh;
+ sei_cont->fid = fid;
+ sei_cont->cc = cc;
+ sei_cont->pec = pec;
+ sei_cont->faddr = faddr;
+ sei_cont->e = e;
+
+ QTAILQ_INSERT_TAIL(&s->pending_sei, sei_cont, link);
+ css_generate_css_crws(0);
+}
+
+static void s390_pci_generate_plug_event(uint16_t pec, uint32_t fh,
+ uint32_t fid)
+{
+ s390_pci_generate_event(2, pec, fh, fid, 0, 0);
+}
+
+static void s390_pci_generate_error_event(uint16_t pec, uint32_t fh,
+ uint32_t fid, uint64_t faddr,
+ uint32_t e)
+{
+ s390_pci_generate_event(1, pec, fh, fid, faddr, e);
+}
+
+static void s390_pci_set_irq(void *opaque, int irq, int level)
+{
+ /* nothing to do */
+}
+
+static int s390_pci_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+ /* nothing to do */
+ return 0;
+}
+
+static uint64_t s390_pci_get_table_origin(uint64_t iota)
+{
+ return iota & ~ZPCI_IOTA_RTTO_FLAG;
+}
+
+static unsigned int calc_rtx(dma_addr_t ptr)
+{
+ return ((unsigned long) ptr >> ZPCI_RT_SHIFT) & ZPCI_INDEX_MASK;
+}
+
+static unsigned int calc_sx(dma_addr_t ptr)
+{
+ return ((unsigned long) ptr >> ZPCI_ST_SHIFT) & ZPCI_INDEX_MASK;
+}
+
+static unsigned int calc_px(dma_addr_t ptr)
+{
+ return ((unsigned long) ptr >> PAGE_SHIFT) & ZPCI_PT_MASK;
+}
+
+static uint64_t get_rt_sto(uint64_t entry)
+{
+ return ((entry & ZPCI_TABLE_TYPE_MASK) == ZPCI_TABLE_TYPE_RTX)
+ ? (entry & ZPCI_RTE_ADDR_MASK)
+ : 0;
+}
+
+static uint64_t get_st_pto(uint64_t entry)
+{
+ return ((entry & ZPCI_TABLE_TYPE_MASK) == ZPCI_TABLE_TYPE_SX)
+ ? (entry & ZPCI_STE_ADDR_MASK)
+ : 0;
+}
+
+static uint64_t s390_guest_io_table_walk(uint64_t guest_iota,
+ uint64_t guest_dma_address)
+{
+ uint64_t sto_a, pto_a, px_a;
+ uint64_t sto, pto, pte;
+ uint32_t rtx, sx, px;
+
+ rtx = calc_rtx(guest_dma_address);
+ sx = calc_sx(guest_dma_address);
+ px = calc_px(guest_dma_address);
+
+ sto_a = guest_iota + rtx * sizeof(uint64_t);
+ sto = address_space_ldq(&address_space_memory, sto_a,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ sto = get_rt_sto(sto);
+ if (!sto) {
+ pte = 0;
+ goto out;
+ }
+
+ pto_a = sto + sx * sizeof(uint64_t);
+ pto = address_space_ldq(&address_space_memory, pto_a,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ pto = get_st_pto(pto);
+ if (!pto) {
+ pte = 0;
+ goto out;
+ }
+
+ px_a = pto + px * sizeof(uint64_t);
+ pte = address_space_ldq(&address_space_memory, px_a,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+
+out:
+ return pte;
+}
+
+static IOMMUTLBEntry s390_translate_iommu(MemoryRegion *iommu, hwaddr addr,
+ bool is_write)
+{
+ uint64_t pte;
+ uint32_t flags;
+ S390PCIBusDevice *pbdev = container_of(iommu, S390PCIBusDevice, mr);
+ S390pciState *s = S390_PCI_HOST_BRIDGE(pci_device_root_bus(pbdev->pdev)
+ ->qbus.parent);
+ IOMMUTLBEntry ret = {
+ .target_as = &address_space_memory,
+ .iova = 0,
+ .translated_addr = 0,
+ .addr_mask = ~(hwaddr)0,
+ .perm = IOMMU_NONE,
+ };
+
+ DPRINTF("iommu trans addr 0x%" PRIx64 "\n", addr);
+
+ /* s390 does not have an APIC mapped to main storage so we use
+ * a separate AddressSpace only for msix notifications
+ */
+ if (addr == ZPCI_MSI_ADDR) {
+ ret.target_as = &s->msix_notify_as;
+ ret.iova = addr;
+ ret.translated_addr = addr;
+ ret.addr_mask = 0xfff;
+ ret.perm = IOMMU_RW;
+ return ret;
+ }
+
+ if (!pbdev->g_iota) {
+ pbdev->error_state = true;
+ pbdev->lgstg_blocked = true;
+ s390_pci_generate_error_event(ERR_EVENT_INVALAS, pbdev->fh, pbdev->fid,
+ addr, 0);
+ return ret;
+ }
+
+ if (addr < pbdev->pba || addr > pbdev->pal) {
+ pbdev->error_state = true;
+ pbdev->lgstg_blocked = true;
+ s390_pci_generate_error_event(ERR_EVENT_OORANGE, pbdev->fh, pbdev->fid,
+ addr, 0);
+ return ret;
+ }
+
+ pte = s390_guest_io_table_walk(s390_pci_get_table_origin(pbdev->g_iota),
+ addr);
+
+ if (!pte) {
+ pbdev->error_state = true;
+ pbdev->lgstg_blocked = true;
+ s390_pci_generate_error_event(ERR_EVENT_SERR, pbdev->fh, pbdev->fid,
+ addr, ERR_EVENT_Q_BIT);
+ return ret;
+ }
+
+ flags = pte & ZPCI_PTE_FLAG_MASK;
+ ret.iova = addr;
+ ret.translated_addr = pte & ZPCI_PTE_ADDR_MASK;
+ ret.addr_mask = 0xfff;
+
+ if (flags & ZPCI_PTE_INVALID) {
+ ret.perm = IOMMU_NONE;
+ } else {
+ ret.perm = IOMMU_RW;
+ }
+
+ return ret;
+}
+
+static const MemoryRegionIOMMUOps s390_iommu_ops = {
+ .translate = s390_translate_iommu,
+};
+
+static AddressSpace *s390_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn)
+{
+ S390pciState *s = opaque;
+
+ return &s->pbdev[PCI_SLOT(devfn)].as;
+}
+
+static uint8_t set_ind_atomic(uint64_t ind_loc, uint8_t to_be_set)
+{
+ uint8_t ind_old, ind_new;
+ hwaddr len = 1;
+ uint8_t *ind_addr;
+
+ ind_addr = cpu_physical_memory_map(ind_loc, &len, 1);
+ if (!ind_addr) {
+ s390_pci_generate_error_event(ERR_EVENT_AIRERR, 0, 0, 0, 0);
+ return -1;
+ }
+ do {
+ ind_old = *ind_addr;
+ ind_new = ind_old | to_be_set;
+ } while (atomic_cmpxchg(ind_addr, ind_old, ind_new) != ind_old);
+ cpu_physical_memory_unmap(ind_addr, len, 1, len);
+
+ return ind_old;
+}
+
+static void s390_msi_ctrl_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ S390PCIBusDevice *pbdev;
+ uint32_t io_int_word;
+ uint32_t fid = data >> ZPCI_MSI_VEC_BITS;
+ uint32_t vec = data & ZPCI_MSI_VEC_MASK;
+ uint64_t ind_bit;
+ uint32_t sum_bit;
+ uint32_t e = 0;
+
+ DPRINTF("write_msix data 0x%" PRIx64 " fid %d vec 0x%x\n", data, fid, vec);
+
+ pbdev = s390_pci_find_dev_by_fid(fid);
+ if (!pbdev) {
+ e |= (vec << ERR_EVENT_MVN_OFFSET);
+ s390_pci_generate_error_event(ERR_EVENT_NOMSI, 0, fid, addr, e);
+ return;
+ }
+
+ ind_bit = pbdev->routes.adapter.ind_offset;
+ sum_bit = pbdev->routes.adapter.summary_offset;
+
+ set_ind_atomic(pbdev->routes.adapter.ind_addr + (ind_bit + vec) / 8,
+ 0x80 >> ((ind_bit + vec) % 8));
+ if (!set_ind_atomic(pbdev->routes.adapter.summary_addr + sum_bit / 8,
+ 0x80 >> (sum_bit % 8))) {
+ io_int_word = (pbdev->isc << 27) | IO_INT_WORD_AI;
+ s390_io_interrupt(0, 0, 0, io_int_word);
+ }
+
+ return;
+}
+
+static uint64_t s390_msi_ctrl_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0xffffffff;
+}
+
+static const MemoryRegionOps s390_msi_ctrl_ops = {
+ .write = s390_msi_ctrl_write,
+ .read = s390_msi_ctrl_read,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void s390_pcihost_init_as(S390pciState *s)
+{
+ int i;
+
+ for (i = 0; i < PCI_SLOT_MAX; i++) {
+ memory_region_init_iommu(&s->pbdev[i].mr, OBJECT(s),
+ &s390_iommu_ops, "iommu-s390", UINT64_MAX);
+ address_space_init(&s->pbdev[i].as, &s->pbdev[i].mr, "iommu-pci");
+ }
+
+ memory_region_init_io(&s->msix_notify_mr, OBJECT(s),
+ &s390_msi_ctrl_ops, s, "msix-s390", UINT64_MAX);
+ address_space_init(&s->msix_notify_as, &s->msix_notify_mr, "msix-pci");
+}
+
+static int s390_pcihost_init(SysBusDevice *dev)
+{
+ PCIBus *b;
+ BusState *bus;
+ PCIHostState *phb = PCI_HOST_BRIDGE(dev);
+ S390pciState *s = S390_PCI_HOST_BRIDGE(dev);
+
+ DPRINTF("host_init\n");
+
+ b = pci_register_bus(DEVICE(dev), NULL,
+ s390_pci_set_irq, s390_pci_map_irq, NULL,
+ get_system_memory(), get_system_io(), 0, 64,
+ TYPE_PCI_BUS);
+ s390_pcihost_init_as(s);
+ pci_setup_iommu(b, s390_pci_dma_iommu, s);
+
+ bus = BUS(b);
+ qbus_set_hotplug_handler(bus, DEVICE(dev), NULL);
+ phb->bus = b;
+ QTAILQ_INIT(&s->pending_sei);
+ return 0;
+}
+
+static int s390_pcihost_setup_msix(S390PCIBusDevice *pbdev)
+{
+ uint8_t pos;
+ uint16_t ctrl;
+ uint32_t table, pba;
+
+ pos = pci_find_capability(pbdev->pdev, PCI_CAP_ID_MSIX);
+ if (!pos) {
+ pbdev->msix.available = false;
+ return 0;
+ }
+
+ ctrl = pci_host_config_read_common(pbdev->pdev, pos + PCI_CAP_FLAGS,
+ pci_config_size(pbdev->pdev), sizeof(ctrl));
+ table = pci_host_config_read_common(pbdev->pdev, pos + PCI_MSIX_TABLE,
+ pci_config_size(pbdev->pdev), sizeof(table));
+ pba = pci_host_config_read_common(pbdev->pdev, pos + PCI_MSIX_PBA,
+ pci_config_size(pbdev->pdev), sizeof(pba));
+
+ pbdev->msix.table_bar = table & PCI_MSIX_FLAGS_BIRMASK;
+ pbdev->msix.table_offset = table & ~PCI_MSIX_FLAGS_BIRMASK;
+ pbdev->msix.pba_bar = pba & PCI_MSIX_FLAGS_BIRMASK;
+ pbdev->msix.pba_offset = pba & ~PCI_MSIX_FLAGS_BIRMASK;
+ pbdev->msix.entries = (ctrl & PCI_MSIX_FLAGS_QSIZE) + 1;
+ pbdev->msix.available = true;
+ return 0;
+}
+
+static void s390_pcihost_hot_plug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(dev);
+ S390PCIBusDevice *pbdev;
+ S390pciState *s = S390_PCI_HOST_BRIDGE(pci_device_root_bus(pci_dev)
+ ->qbus.parent);
+
+ pbdev = &s->pbdev[PCI_SLOT(pci_dev->devfn)];
+
+ pbdev->fid = s390_pci_get_pfid(pci_dev);
+ pbdev->pdev = pci_dev;
+ pbdev->configured = true;
+ pbdev->fh = s390_pci_get_pfh(pci_dev);
+
+ s390_pcihost_setup_msix(pbdev);
+
+ if (dev->hotplugged) {
+ s390_pci_generate_plug_event(HP_EVENT_RESERVED_TO_STANDBY,
+ pbdev->fh, pbdev->fid);
+ s390_pci_generate_plug_event(HP_EVENT_TO_CONFIGURED,
+ pbdev->fh, pbdev->fid);
+ }
+ return;
+}
+
+static void s390_pcihost_hot_unplug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(dev);
+ S390pciState *s = S390_PCI_HOST_BRIDGE(pci_device_root_bus(pci_dev)
+ ->qbus.parent);
+ S390PCIBusDevice *pbdev = &s->pbdev[PCI_SLOT(pci_dev->devfn)];
+
+ if (pbdev->configured) {
+ pbdev->configured = false;
+ s390_pci_generate_plug_event(HP_EVENT_CONFIGURED_TO_STBRES,
+ pbdev->fh, pbdev->fid);
+ }
+
+ s390_pci_generate_plug_event(HP_EVENT_STANDBY_TO_RESERVED,
+ pbdev->fh, pbdev->fid);
+ pbdev->fh = 0;
+ pbdev->fid = 0;
+ pbdev->pdev = NULL;
+ object_unparent(OBJECT(pci_dev));
+}
+
+static void s390_pcihost_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ dc->cannot_instantiate_with_device_add_yet = true;
+ k->init = s390_pcihost_init;
+ hc->plug = s390_pcihost_hot_plug;
+ hc->unplug = s390_pcihost_hot_unplug;
+ msi_supported = true;
+}
+
+static const TypeInfo s390_pcihost_info = {
+ .name = TYPE_S390_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(S390pciState),
+ .class_init = s390_pcihost_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static void s390_pci_register_types(void)
+{
+ type_register_static(&s390_pcihost_info);
+}
+
+type_init(s390_pci_register_types)
diff --git a/hw/s390x/s390-pci-bus.h b/hw/s390x/s390-pci-bus.h
new file mode 100644
index 00000000..464a92ee
--- /dev/null
+++ b/hw/s390x/s390-pci-bus.h
@@ -0,0 +1,251 @@
+/*
+ * s390 PCI BUS definitions
+ *
+ * Copyright 2014 IBM Corp.
+ * Author(s): Frank Blaschka <frank.blaschka@de.ibm.com>
+ * Hong Bo Li <lihbbj@cn.ibm.com>
+ * Yi Min Zhao <zyimin@cn.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef HW_S390_PCI_BUS_H
+#define HW_S390_PCI_BUS_H
+
+#include <hw/pci/pci.h>
+#include <hw/pci/pci_host.h>
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/s390_flic.h"
+#include "hw/s390x/css.h"
+
+#define TYPE_S390_PCI_HOST_BRIDGE "s390-pcihost"
+#define FH_VIRT 0x00ff0000
+#define ENABLE_BIT_OFFSET 31
+#define S390_PCIPT_ADAPTER 2
+
+#define S390_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(S390pciState, (obj), TYPE_S390_PCI_HOST_BRIDGE)
+
+#define HP_EVENT_TO_CONFIGURED 0x0301
+#define HP_EVENT_RESERVED_TO_STANDBY 0x0302
+#define HP_EVENT_CONFIGURED_TO_STBRES 0x0304
+#define HP_EVENT_STANDBY_TO_RESERVED 0x0308
+
+#define ERR_EVENT_INVALAS 0x1
+#define ERR_EVENT_OORANGE 0x2
+#define ERR_EVENT_INVALTF 0x3
+#define ERR_EVENT_TPROTE 0x4
+#define ERR_EVENT_APROTE 0x5
+#define ERR_EVENT_KEYE 0x6
+#define ERR_EVENT_INVALTE 0x7
+#define ERR_EVENT_INVALTL 0x8
+#define ERR_EVENT_TT 0x9
+#define ERR_EVENT_INVALMS 0xa
+#define ERR_EVENT_SERR 0xb
+#define ERR_EVENT_NOMSI 0x10
+#define ERR_EVENT_INVALBV 0x11
+#define ERR_EVENT_AIBV 0x12
+#define ERR_EVENT_AIRERR 0x13
+#define ERR_EVENT_FMBA 0x2a
+#define ERR_EVENT_FMBUP 0x2b
+#define ERR_EVENT_FMBPRO 0x2c
+#define ERR_EVENT_CCONF 0x30
+#define ERR_EVENT_SERVAC 0x3a
+#define ERR_EVENT_PERMERR 0x3b
+
+#define ERR_EVENT_Q_BIT 0x2
+#define ERR_EVENT_MVN_OFFSET 16
+
+#define ZPCI_MSI_VEC_BITS 11
+#define ZPCI_MSI_VEC_MASK 0x7ff
+
+#define ZPCI_MSI_ADDR 0xfe00000000000000ULL
+#define ZPCI_SDMA_ADDR 0x100000000ULL
+#define ZPCI_EDMA_ADDR 0x1ffffffffffffffULL
+
+#define PAGE_SHIFT 12
+#define PAGE_MASK (~(PAGE_SIZE-1))
+#define PAGE_DEFAULT_ACC 0
+#define PAGE_DEFAULT_KEY (PAGE_DEFAULT_ACC << 4)
+
+/* I/O Translation Anchor (IOTA) */
+enum ZpciIoatDtype {
+ ZPCI_IOTA_STO = 0,
+ ZPCI_IOTA_RTTO = 1,
+ ZPCI_IOTA_RSTO = 2,
+ ZPCI_IOTA_RFTO = 3,
+ ZPCI_IOTA_PFAA = 4,
+ ZPCI_IOTA_IOPFAA = 5,
+ ZPCI_IOTA_IOPTO = 7
+};
+
+#define ZPCI_IOTA_IOT_ENABLED 0x800ULL
+#define ZPCI_IOTA_DT_ST (ZPCI_IOTA_STO << 2)
+#define ZPCI_IOTA_DT_RT (ZPCI_IOTA_RTTO << 2)
+#define ZPCI_IOTA_DT_RS (ZPCI_IOTA_RSTO << 2)
+#define ZPCI_IOTA_DT_RF (ZPCI_IOTA_RFTO << 2)
+#define ZPCI_IOTA_DT_PF (ZPCI_IOTA_PFAA << 2)
+#define ZPCI_IOTA_FS_4K 0
+#define ZPCI_IOTA_FS_1M 1
+#define ZPCI_IOTA_FS_2G 2
+#define ZPCI_KEY (PAGE_DEFAULT_KEY << 5)
+
+#define ZPCI_IOTA_STO_FLAG (ZPCI_IOTA_IOT_ENABLED | ZPCI_KEY | ZPCI_IOTA_DT_ST)
+#define ZPCI_IOTA_RTTO_FLAG (ZPCI_IOTA_IOT_ENABLED | ZPCI_KEY | ZPCI_IOTA_DT_RT)
+#define ZPCI_IOTA_RSTO_FLAG (ZPCI_IOTA_IOT_ENABLED | ZPCI_KEY | ZPCI_IOTA_DT_RS)
+#define ZPCI_IOTA_RFTO_FLAG (ZPCI_IOTA_IOT_ENABLED | ZPCI_KEY | ZPCI_IOTA_DT_RF)
+#define ZPCI_IOTA_RFAA_FLAG (ZPCI_IOTA_IOT_ENABLED | ZPCI_KEY |\
+ ZPCI_IOTA_DT_PF | ZPCI_IOTA_FS_2G)
+
+/* I/O Region and segment tables */
+#define ZPCI_INDEX_MASK 0x7ffULL
+
+#define ZPCI_TABLE_TYPE_MASK 0xc
+#define ZPCI_TABLE_TYPE_RFX 0xc
+#define ZPCI_TABLE_TYPE_RSX 0x8
+#define ZPCI_TABLE_TYPE_RTX 0x4
+#define ZPCI_TABLE_TYPE_SX 0x0
+
+#define ZPCI_TABLE_LEN_RFX 0x3
+#define ZPCI_TABLE_LEN_RSX 0x3
+#define ZPCI_TABLE_LEN_RTX 0x3
+
+#define ZPCI_TABLE_OFFSET_MASK 0xc0
+#define ZPCI_TABLE_SIZE 0x4000
+#define ZPCI_TABLE_ALIGN ZPCI_TABLE_SIZE
+#define ZPCI_TABLE_ENTRY_SIZE (sizeof(unsigned long))
+#define ZPCI_TABLE_ENTRIES (ZPCI_TABLE_SIZE / ZPCI_TABLE_ENTRY_SIZE)
+
+#define ZPCI_TABLE_BITS 11
+#define ZPCI_PT_BITS 8
+#define ZPCI_ST_SHIFT (ZPCI_PT_BITS + PAGE_SHIFT)
+#define ZPCI_RT_SHIFT (ZPCI_ST_SHIFT + ZPCI_TABLE_BITS)
+
+#define ZPCI_RTE_FLAG_MASK 0x3fffULL
+#define ZPCI_RTE_ADDR_MASK (~ZPCI_RTE_FLAG_MASK)
+#define ZPCI_STE_FLAG_MASK 0x7ffULL
+#define ZPCI_STE_ADDR_MASK (~ZPCI_STE_FLAG_MASK)
+
+/* I/O Page tables */
+#define ZPCI_PTE_VALID_MASK 0x400
+#define ZPCI_PTE_INVALID 0x400
+#define ZPCI_PTE_VALID 0x000
+#define ZPCI_PT_SIZE 0x800
+#define ZPCI_PT_ALIGN ZPCI_PT_SIZE
+#define ZPCI_PT_ENTRIES (ZPCI_PT_SIZE / ZPCI_TABLE_ENTRY_SIZE)
+#define ZPCI_PT_MASK (ZPCI_PT_ENTRIES - 1)
+
+#define ZPCI_PTE_FLAG_MASK 0xfffULL
+#define ZPCI_PTE_ADDR_MASK (~ZPCI_PTE_FLAG_MASK)
+
+/* Shared bits */
+#define ZPCI_TABLE_VALID 0x00
+#define ZPCI_TABLE_INVALID 0x20
+#define ZPCI_TABLE_PROTECTED 0x200
+#define ZPCI_TABLE_UNPROTECTED 0x000
+
+#define ZPCI_TABLE_VALID_MASK 0x20
+#define ZPCI_TABLE_PROT_MASK 0x200
+
+typedef struct SeiContainer {
+ QTAILQ_ENTRY(SeiContainer) link;
+ uint32_t fid;
+ uint32_t fh;
+ uint8_t cc;
+ uint16_t pec;
+ uint64_t faddr;
+ uint32_t e;
+} SeiContainer;
+
+typedef struct PciCcdfErr {
+ uint32_t reserved1;
+ uint32_t fh;
+ uint32_t fid;
+ uint32_t e;
+ uint64_t faddr;
+ uint32_t reserved3;
+ uint16_t reserved4;
+ uint16_t pec;
+} QEMU_PACKED PciCcdfErr;
+
+typedef struct PciCcdfAvail {
+ uint32_t reserved1;
+ uint32_t fh;
+ uint32_t fid;
+ uint32_t reserved2;
+ uint32_t reserved3;
+ uint32_t reserved4;
+ uint32_t reserved5;
+ uint16_t reserved6;
+ uint16_t pec;
+} QEMU_PACKED PciCcdfAvail;
+
+typedef struct ChscSeiNt2Res {
+ uint16_t length;
+ uint16_t code;
+ uint16_t reserved1;
+ uint8_t reserved2;
+ uint8_t nt;
+ uint8_t flags;
+ uint8_t reserved3;
+ uint8_t reserved4;
+ uint8_t cc;
+ uint32_t reserved5[13];
+ uint8_t ccdf[4016];
+} QEMU_PACKED ChscSeiNt2Res;
+
+typedef struct PciCfgSccb {
+ SCCBHeader header;
+ uint8_t atype;
+ uint8_t reserved1;
+ uint16_t reserved2;
+ uint32_t aid;
+} QEMU_PACKED PciCfgSccb;
+
+typedef struct S390MsixInfo {
+ bool available;
+ uint8_t table_bar;
+ uint8_t pba_bar;
+ uint16_t entries;
+ uint32_t table_offset;
+ uint32_t pba_offset;
+} S390MsixInfo;
+
+typedef struct S390PCIBusDevice {
+ PCIDevice *pdev;
+ bool configured;
+ bool error_state;
+ bool lgstg_blocked;
+ uint32_t fh;
+ uint32_t fid;
+ uint64_t g_iota;
+ uint64_t pba;
+ uint64_t pal;
+ uint64_t fmb_addr;
+ uint8_t isc;
+ uint16_t noi;
+ uint8_t sum;
+ S390MsixInfo msix;
+ AdapterRoutes routes;
+ AddressSpace as;
+ MemoryRegion mr;
+} S390PCIBusDevice;
+
+typedef struct S390pciState {
+ PCIHostState parent_obj;
+ S390PCIBusDevice pbdev[PCI_SLOT_MAX];
+ AddressSpace msix_notify_as;
+ MemoryRegion msix_notify_mr;
+ QTAILQ_HEAD(, SeiContainer) pending_sei;
+} S390pciState;
+
+int chsc_sei_nt2_get_event(void *res);
+int chsc_sei_nt2_have_event(void);
+void s390_pci_sclp_configure(int configure, SCCB *sccb);
+S390PCIBusDevice *s390_pci_find_dev_by_idx(uint32_t idx);
+S390PCIBusDevice *s390_pci_find_dev_by_fh(uint32_t fh);
+S390PCIBusDevice *s390_pci_find_dev_by_fid(uint32_t fid);
+
+#endif
diff --git a/hw/s390x/s390-pci-inst.c b/hw/s390x/s390-pci-inst.c
new file mode 100644
index 00000000..f9151a9a
--- /dev/null
+++ b/hw/s390x/s390-pci-inst.c
@@ -0,0 +1,839 @@
+/*
+ * s390 PCI instructions
+ *
+ * Copyright 2014 IBM Corp.
+ * Author(s): Frank Blaschka <frank.blaschka@de.ibm.com>
+ * Hong Bo Li <lihbbj@cn.ibm.com>
+ * Yi Min Zhao <zyimin@cn.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "s390-pci-inst.h"
+#include "s390-pci-bus.h"
+#include <exec/memory-internal.h>
+#include <qemu/error-report.h>
+
+/* #define DEBUG_S390PCI_INST */
+#ifdef DEBUG_S390PCI_INST
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, "s390pci-inst: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+static void s390_set_status_code(CPUS390XState *env,
+ uint8_t r, uint64_t status_code)
+{
+ env->regs[r] &= ~0xff000000ULL;
+ env->regs[r] |= (status_code & 0xff) << 24;
+}
+
+static int list_pci(ClpReqRspListPci *rrb, uint8_t *cc)
+{
+ S390PCIBusDevice *pbdev;
+ uint32_t res_code, initial_l2, g_l2, finish;
+ int rc, idx;
+ uint64_t resume_token;
+
+ rc = 0;
+ if (lduw_p(&rrb->request.hdr.len) != 32) {
+ res_code = CLP_RC_LEN;
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if ((ldl_p(&rrb->request.fmt) & CLP_MASK_FMT) != 0) {
+ res_code = CLP_RC_FMT;
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if ((ldl_p(&rrb->request.fmt) & ~CLP_MASK_FMT) != 0 ||
+ ldq_p(&rrb->request.reserved1) != 0 ||
+ ldq_p(&rrb->request.reserved2) != 0) {
+ res_code = CLP_RC_RESNOT0;
+ rc = -EINVAL;
+ goto out;
+ }
+
+ resume_token = ldq_p(&rrb->request.resume_token);
+
+ if (resume_token) {
+ pbdev = s390_pci_find_dev_by_idx(resume_token);
+ if (!pbdev) {
+ res_code = CLP_RC_LISTPCI_BADRT;
+ rc = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (lduw_p(&rrb->response.hdr.len) < 48) {
+ res_code = CLP_RC_8K;
+ rc = -EINVAL;
+ goto out;
+ }
+
+ initial_l2 = lduw_p(&rrb->response.hdr.len);
+ if ((initial_l2 - LIST_PCI_HDR_LEN) % sizeof(ClpFhListEntry)
+ != 0) {
+ res_code = CLP_RC_LEN;
+ rc = -EINVAL;
+ *cc = 3;
+ goto out;
+ }
+
+ stl_p(&rrb->response.fmt, 0);
+ stq_p(&rrb->response.reserved1, 0);
+ stq_p(&rrb->response.reserved2, 0);
+ stl_p(&rrb->response.mdd, FH_VIRT);
+ stw_p(&rrb->response.max_fn, PCI_MAX_FUNCTIONS);
+ rrb->response.entry_size = sizeof(ClpFhListEntry);
+ finish = 0;
+ idx = resume_token;
+ g_l2 = LIST_PCI_HDR_LEN;
+ do {
+ pbdev = s390_pci_find_dev_by_idx(idx);
+ if (!pbdev) {
+ finish = 1;
+ break;
+ }
+ stw_p(&rrb->response.fh_list[idx - resume_token].device_id,
+ pci_get_word(pbdev->pdev->config + PCI_DEVICE_ID));
+ stw_p(&rrb->response.fh_list[idx - resume_token].vendor_id,
+ pci_get_word(pbdev->pdev->config + PCI_VENDOR_ID));
+ stl_p(&rrb->response.fh_list[idx - resume_token].config, 0x80000000);
+ stl_p(&rrb->response.fh_list[idx - resume_token].fid, pbdev->fid);
+ stl_p(&rrb->response.fh_list[idx - resume_token].fh, pbdev->fh);
+
+ g_l2 += sizeof(ClpFhListEntry);
+ /* Add endian check for DPRINTF? */
+ DPRINTF("g_l2 %d vendor id 0x%x device id 0x%x fid 0x%x fh 0x%x\n",
+ g_l2,
+ lduw_p(&rrb->response.fh_list[idx - resume_token].vendor_id),
+ lduw_p(&rrb->response.fh_list[idx - resume_token].device_id),
+ ldl_p(&rrb->response.fh_list[idx - resume_token].fid),
+ ldl_p(&rrb->response.fh_list[idx - resume_token].fh));
+ idx++;
+ } while (g_l2 < initial_l2);
+
+ if (finish == 1) {
+ resume_token = 0;
+ } else {
+ resume_token = idx;
+ }
+ stq_p(&rrb->response.resume_token, resume_token);
+ stw_p(&rrb->response.hdr.len, g_l2);
+ stw_p(&rrb->response.hdr.rsp, CLP_RC_OK);
+out:
+ if (rc) {
+ DPRINTF("list pci failed rc 0x%x\n", rc);
+ stw_p(&rrb->response.hdr.rsp, res_code);
+ }
+ return rc;
+}
+
+int clp_service_call(S390CPU *cpu, uint8_t r2)
+{
+ ClpReqHdr *reqh;
+ ClpRspHdr *resh;
+ S390PCIBusDevice *pbdev;
+ uint32_t req_len;
+ uint32_t res_len;
+ uint8_t buffer[4096 * 2];
+ uint8_t cc = 0;
+ CPUS390XState *env = &cpu->env;
+ int i;
+
+ cpu_synchronize_state(CPU(cpu));
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ program_interrupt(env, PGM_PRIVILEGED, 4);
+ return 0;
+ }
+
+ if (s390_cpu_virt_mem_read(cpu, env->regs[r2], r2, buffer, sizeof(*reqh))) {
+ return 0;
+ }
+ reqh = (ClpReqHdr *)buffer;
+ req_len = lduw_p(&reqh->len);
+ if (req_len < 16 || req_len > 8184 || (req_len % 8 != 0)) {
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+
+ if (s390_cpu_virt_mem_read(cpu, env->regs[r2], r2, buffer,
+ req_len + sizeof(*resh))) {
+ return 0;
+ }
+ resh = (ClpRspHdr *)(buffer + req_len);
+ res_len = lduw_p(&resh->len);
+ if (res_len < 8 || res_len > 8176 || (res_len % 8 != 0)) {
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+ if ((req_len + res_len) > 8192) {
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+
+ if (s390_cpu_virt_mem_read(cpu, env->regs[r2], r2, buffer,
+ req_len + res_len)) {
+ return 0;
+ }
+
+ if (req_len != 32) {
+ stw_p(&resh->rsp, CLP_RC_LEN);
+ goto out;
+ }
+
+ switch (lduw_p(&reqh->cmd)) {
+ case CLP_LIST_PCI: {
+ ClpReqRspListPci *rrb = (ClpReqRspListPci *)buffer;
+ list_pci(rrb, &cc);
+ break;
+ }
+ case CLP_SET_PCI_FN: {
+ ClpReqSetPci *reqsetpci = (ClpReqSetPci *)reqh;
+ ClpRspSetPci *ressetpci = (ClpRspSetPci *)resh;
+
+ pbdev = s390_pci_find_dev_by_fh(ldl_p(&reqsetpci->fh));
+ if (!pbdev) {
+ stw_p(&ressetpci->hdr.rsp, CLP_RC_SETPCIFN_FH);
+ goto out;
+ }
+
+ switch (reqsetpci->oc) {
+ case CLP_SET_ENABLE_PCI_FN:
+ pbdev->fh = pbdev->fh | 1 << ENABLE_BIT_OFFSET;
+ stl_p(&ressetpci->fh, pbdev->fh);
+ stw_p(&ressetpci->hdr.rsp, CLP_RC_OK);
+ break;
+ case CLP_SET_DISABLE_PCI_FN:
+ pbdev->fh = pbdev->fh & ~(1 << ENABLE_BIT_OFFSET);
+ pbdev->error_state = false;
+ pbdev->lgstg_blocked = false;
+ stl_p(&ressetpci->fh, pbdev->fh);
+ stw_p(&ressetpci->hdr.rsp, CLP_RC_OK);
+ break;
+ default:
+ DPRINTF("unknown set pci command\n");
+ stw_p(&ressetpci->hdr.rsp, CLP_RC_SETPCIFN_FHOP);
+ break;
+ }
+ break;
+ }
+ case CLP_QUERY_PCI_FN: {
+ ClpReqQueryPci *reqquery = (ClpReqQueryPci *)reqh;
+ ClpRspQueryPci *resquery = (ClpRspQueryPci *)resh;
+
+ pbdev = s390_pci_find_dev_by_fh(ldl_p(&reqquery->fh));
+ if (!pbdev) {
+ DPRINTF("query pci no pci dev\n");
+ stw_p(&resquery->hdr.rsp, CLP_RC_SETPCIFN_FH);
+ goto out;
+ }
+
+ for (i = 0; i < PCI_BAR_COUNT; i++) {
+ uint32_t data = pci_get_long(pbdev->pdev->config +
+ PCI_BASE_ADDRESS_0 + (i * 4));
+
+ stl_p(&resquery->bar[i], data);
+ resquery->bar_size[i] = pbdev->pdev->io_regions[i].size ?
+ ctz64(pbdev->pdev->io_regions[i].size) : 0;
+ DPRINTF("bar %d addr 0x%x size 0x%" PRIx64 "barsize 0x%x\n", i,
+ ldl_p(&resquery->bar[i]),
+ pbdev->pdev->io_regions[i].size,
+ resquery->bar_size[i]);
+ }
+
+ stq_p(&resquery->sdma, ZPCI_SDMA_ADDR);
+ stq_p(&resquery->edma, ZPCI_EDMA_ADDR);
+ stw_p(&resquery->pchid, 0);
+ stw_p(&resquery->ug, 1);
+ stl_p(&resquery->uid, pbdev->fid);
+ stw_p(&resquery->hdr.rsp, CLP_RC_OK);
+ break;
+ }
+ case CLP_QUERY_PCI_FNGRP: {
+ ClpRspQueryPciGrp *resgrp = (ClpRspQueryPciGrp *)resh;
+ resgrp->fr = 1;
+ stq_p(&resgrp->dasm, 0);
+ stq_p(&resgrp->msia, ZPCI_MSI_ADDR);
+ stw_p(&resgrp->mui, 0);
+ stw_p(&resgrp->i, 128);
+ resgrp->version = 0;
+
+ stw_p(&resgrp->hdr.rsp, CLP_RC_OK);
+ break;
+ }
+ default:
+ DPRINTF("unknown clp command\n");
+ stw_p(&resh->rsp, CLP_RC_CMD);
+ break;
+ }
+
+out:
+ if (s390_cpu_virt_mem_write(cpu, env->regs[r2], r2, buffer,
+ req_len + res_len)) {
+ return 0;
+ }
+ setcc(cpu, cc);
+ return 0;
+}
+
+int pcilg_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2)
+{
+ CPUS390XState *env = &cpu->env;
+ S390PCIBusDevice *pbdev;
+ uint64_t offset;
+ uint64_t data;
+ uint8_t len;
+ uint32_t fh;
+ uint8_t pcias;
+
+ cpu_synchronize_state(CPU(cpu));
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ program_interrupt(env, PGM_PRIVILEGED, 4);
+ return 0;
+ }
+
+ if (r2 & 0x1) {
+ program_interrupt(env, PGM_SPECIFICATION, 4);
+ return 0;
+ }
+
+ fh = env->regs[r2] >> 32;
+ pcias = (env->regs[r2] >> 16) & 0xf;
+ len = env->regs[r2] & 0xf;
+ offset = env->regs[r2 + 1];
+
+ pbdev = s390_pci_find_dev_by_fh(fh);
+ if (!pbdev) {
+ DPRINTF("pcilg no pci dev\n");
+ setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
+ return 0;
+ }
+
+ if (pbdev->lgstg_blocked) {
+ setcc(cpu, ZPCI_PCI_LS_ERR);
+ s390_set_status_code(env, r2, ZPCI_PCI_ST_BLOCKED);
+ return 0;
+ }
+
+ if (pcias < 6) {
+ if ((8 - (offset & 0x7)) < len) {
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+ MemoryRegion *mr = pbdev->pdev->io_regions[pcias].memory;
+ memory_region_dispatch_read(mr, offset, &data, len,
+ MEMTXATTRS_UNSPECIFIED);
+ } else if (pcias == 15) {
+ if ((4 - (offset & 0x3)) < len) {
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+ data = pci_host_config_read_common(
+ pbdev->pdev, offset, pci_config_size(pbdev->pdev), len);
+
+ switch (len) {
+ case 1:
+ break;
+ case 2:
+ data = bswap16(data);
+ break;
+ case 4:
+ data = bswap32(data);
+ break;
+ case 8:
+ data = bswap64(data);
+ break;
+ default:
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+ } else {
+ DPRINTF("invalid space\n");
+ setcc(cpu, ZPCI_PCI_LS_ERR);
+ s390_set_status_code(env, r2, ZPCI_PCI_ST_INVAL_AS);
+ return 0;
+ }
+
+ env->regs[r1] = data;
+ setcc(cpu, ZPCI_PCI_LS_OK);
+ return 0;
+}
+
+static void update_msix_table_msg_data(S390PCIBusDevice *pbdev, uint64_t offset,
+ uint64_t *data, uint8_t len)
+{
+ uint32_t val;
+ uint8_t *msg_data;
+
+ if (offset % PCI_MSIX_ENTRY_SIZE != 8) {
+ return;
+ }
+
+ if (len != 4) {
+ DPRINTF("access msix table msg data but len is %d\n", len);
+ return;
+ }
+
+ msg_data = (uint8_t *)data - offset % PCI_MSIX_ENTRY_SIZE +
+ PCI_MSIX_ENTRY_VECTOR_CTRL;
+ val = pci_get_long(msg_data) | (pbdev->fid << ZPCI_MSI_VEC_BITS);
+ pci_set_long(msg_data, val);
+ DPRINTF("update msix msg_data to 0x%" PRIx64 "\n", *data);
+}
+
+static int trap_msix(S390PCIBusDevice *pbdev, uint64_t offset, uint8_t pcias)
+{
+ if (pbdev->msix.available && pbdev->msix.table_bar == pcias &&
+ offset >= pbdev->msix.table_offset &&
+ offset <= pbdev->msix.table_offset +
+ (pbdev->msix.entries - 1) * PCI_MSIX_ENTRY_SIZE) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int pcistg_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2)
+{
+ CPUS390XState *env = &cpu->env;
+ uint64_t offset, data;
+ S390PCIBusDevice *pbdev;
+ uint8_t len;
+ uint32_t fh;
+ uint8_t pcias;
+
+ cpu_synchronize_state(CPU(cpu));
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ program_interrupt(env, PGM_PRIVILEGED, 4);
+ return 0;
+ }
+
+ if (r2 & 0x1) {
+ program_interrupt(env, PGM_SPECIFICATION, 4);
+ return 0;
+ }
+
+ fh = env->regs[r2] >> 32;
+ pcias = (env->regs[r2] >> 16) & 0xf;
+ len = env->regs[r2] & 0xf;
+ offset = env->regs[r2 + 1];
+
+ pbdev = s390_pci_find_dev_by_fh(fh);
+ if (!pbdev) {
+ DPRINTF("pcistg no pci dev\n");
+ setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
+ return 0;
+ }
+
+ if (pbdev->lgstg_blocked) {
+ setcc(cpu, ZPCI_PCI_LS_ERR);
+ s390_set_status_code(env, r2, ZPCI_PCI_ST_BLOCKED);
+ return 0;
+ }
+
+ data = env->regs[r1];
+ if (pcias < 6) {
+ if ((8 - (offset & 0x7)) < len) {
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+ MemoryRegion *mr;
+ if (trap_msix(pbdev, offset, pcias)) {
+ offset = offset - pbdev->msix.table_offset;
+ mr = &pbdev->pdev->msix_table_mmio;
+ update_msix_table_msg_data(pbdev, offset, &data, len);
+ } else {
+ mr = pbdev->pdev->io_regions[pcias].memory;
+ }
+
+ memory_region_dispatch_write(mr, offset, data, len,
+ MEMTXATTRS_UNSPECIFIED);
+ } else if (pcias == 15) {
+ if ((4 - (offset & 0x3)) < len) {
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+ switch (len) {
+ case 1:
+ break;
+ case 2:
+ data = bswap16(data);
+ break;
+ case 4:
+ data = bswap32(data);
+ break;
+ case 8:
+ data = bswap64(data);
+ break;
+ default:
+ program_interrupt(env, PGM_OPERAND, 4);
+ return 0;
+ }
+
+ pci_host_config_write_common(pbdev->pdev, offset,
+ pci_config_size(pbdev->pdev),
+ data, len);
+ } else {
+ DPRINTF("pcistg invalid space\n");
+ setcc(cpu, ZPCI_PCI_LS_ERR);
+ s390_set_status_code(env, r2, ZPCI_PCI_ST_INVAL_AS);
+ return 0;
+ }
+
+ setcc(cpu, ZPCI_PCI_LS_OK);
+ return 0;
+}
+
+int rpcit_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2)
+{
+ CPUS390XState *env = &cpu->env;
+ uint32_t fh;
+ S390PCIBusDevice *pbdev;
+ hwaddr start, end;
+ IOMMUTLBEntry entry;
+ MemoryRegion *mr;
+
+ cpu_synchronize_state(CPU(cpu));
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ program_interrupt(env, PGM_PRIVILEGED, 4);
+ goto out;
+ }
+
+ if (r2 & 0x1) {
+ program_interrupt(env, PGM_SPECIFICATION, 4);
+ goto out;
+ }
+
+ fh = env->regs[r1] >> 32;
+ start = env->regs[r2];
+ end = start + env->regs[r2 + 1];
+
+ pbdev = s390_pci_find_dev_by_fh(fh);
+
+ if (!pbdev) {
+ DPRINTF("rpcit no pci dev\n");
+ setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
+ goto out;
+ }
+
+ mr = pci_device_iommu_address_space(pbdev->pdev)->root;
+ while (start < end) {
+ entry = mr->iommu_ops->translate(mr, start, 0);
+
+ if (!entry.translated_addr) {
+ setcc(cpu, ZPCI_PCI_LS_ERR);
+ goto out;
+ }
+
+ memory_region_notify_iommu(mr, entry);
+ start += entry.addr_mask + 1;
+ }
+
+ setcc(cpu, ZPCI_PCI_LS_OK);
+out:
+ return 0;
+}
+
+int pcistb_service_call(S390CPU *cpu, uint8_t r1, uint8_t r3, uint64_t gaddr,
+ uint8_t ar)
+{
+ CPUS390XState *env = &cpu->env;
+ S390PCIBusDevice *pbdev;
+ MemoryRegion *mr;
+ int i;
+ uint32_t fh;
+ uint8_t pcias;
+ uint8_t len;
+ uint8_t buffer[128];
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ program_interrupt(env, PGM_PRIVILEGED, 6);
+ return 0;
+ }
+
+ fh = env->regs[r1] >> 32;
+ pcias = (env->regs[r1] >> 16) & 0xf;
+ len = env->regs[r1] & 0xff;
+
+ if (pcias > 5) {
+ DPRINTF("pcistb invalid space\n");
+ setcc(cpu, ZPCI_PCI_LS_ERR);
+ s390_set_status_code(env, r1, ZPCI_PCI_ST_INVAL_AS);
+ return 0;
+ }
+
+ switch (len) {
+ case 16:
+ case 32:
+ case 64:
+ case 128:
+ break;
+ default:
+ program_interrupt(env, PGM_SPECIFICATION, 6);
+ return 0;
+ }
+
+ pbdev = s390_pci_find_dev_by_fh(fh);
+ if (!pbdev) {
+ DPRINTF("pcistb no pci dev fh 0x%x\n", fh);
+ setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
+ return 0;
+ }
+
+ if (pbdev->lgstg_blocked) {
+ setcc(cpu, ZPCI_PCI_LS_ERR);
+ s390_set_status_code(env, r1, ZPCI_PCI_ST_BLOCKED);
+ return 0;
+ }
+
+ mr = pbdev->pdev->io_regions[pcias].memory;
+ if (!memory_region_access_valid(mr, env->regs[r3], len, true)) {
+ program_interrupt(env, PGM_ADDRESSING, 6);
+ return 0;
+ }
+
+ if (s390_cpu_virt_mem_read(cpu, gaddr, ar, buffer, len)) {
+ return 0;
+ }
+
+ for (i = 0; i < len / 8; i++) {
+ memory_region_dispatch_write(mr, env->regs[r3] + i * 8,
+ ldq_p(buffer + i * 8), 8,
+ MEMTXATTRS_UNSPECIFIED);
+ }
+
+ setcc(cpu, ZPCI_PCI_LS_OK);
+ return 0;
+}
+
+static int reg_irqs(CPUS390XState *env, S390PCIBusDevice *pbdev, ZpciFib fib)
+{
+ int ret;
+ S390FLICState *fs = s390_get_flic();
+ S390FLICStateClass *fsc = S390_FLIC_COMMON_GET_CLASS(fs);
+
+ ret = css_register_io_adapter(S390_PCIPT_ADAPTER,
+ FIB_DATA_ISC(ldl_p(&fib.data)), true, false,
+ &pbdev->routes.adapter.adapter_id);
+ assert(ret == 0);
+
+ fsc->io_adapter_map(fs, pbdev->routes.adapter.adapter_id,
+ ldq_p(&fib.aisb), true);
+ fsc->io_adapter_map(fs, pbdev->routes.adapter.adapter_id,
+ ldq_p(&fib.aibv), true);
+
+ pbdev->routes.adapter.summary_addr = ldq_p(&fib.aisb);
+ pbdev->routes.adapter.summary_offset = FIB_DATA_AISBO(ldl_p(&fib.data));
+ pbdev->routes.adapter.ind_addr = ldq_p(&fib.aibv);
+ pbdev->routes.adapter.ind_offset = FIB_DATA_AIBVO(ldl_p(&fib.data));
+ pbdev->isc = FIB_DATA_ISC(ldl_p(&fib.data));
+ pbdev->noi = FIB_DATA_NOI(ldl_p(&fib.data));
+ pbdev->sum = FIB_DATA_SUM(ldl_p(&fib.data));
+
+ DPRINTF("reg_irqs adapter id %d\n", pbdev->routes.adapter.adapter_id);
+ return 0;
+}
+
+static int dereg_irqs(S390PCIBusDevice *pbdev)
+{
+ S390FLICState *fs = s390_get_flic();
+ S390FLICStateClass *fsc = S390_FLIC_COMMON_GET_CLASS(fs);
+
+ fsc->io_adapter_map(fs, pbdev->routes.adapter.adapter_id,
+ pbdev->routes.adapter.ind_addr, false);
+
+ pbdev->routes.adapter.summary_addr = 0;
+ pbdev->routes.adapter.summary_offset = 0;
+ pbdev->routes.adapter.ind_addr = 0;
+ pbdev->routes.adapter.ind_offset = 0;
+ pbdev->isc = 0;
+ pbdev->noi = 0;
+ pbdev->sum = 0;
+
+ DPRINTF("dereg_irqs adapter id %d\n", pbdev->routes.adapter.adapter_id);
+ return 0;
+}
+
+static int reg_ioat(CPUS390XState *env, S390PCIBusDevice *pbdev, ZpciFib fib)
+{
+ uint64_t pba = ldq_p(&fib.pba);
+ uint64_t pal = ldq_p(&fib.pal);
+ uint64_t g_iota = ldq_p(&fib.iota);
+ uint8_t dt = (g_iota >> 2) & 0x7;
+ uint8_t t = (g_iota >> 11) & 0x1;
+
+ if (pba > pal || pba < ZPCI_SDMA_ADDR || pal > ZPCI_EDMA_ADDR) {
+ program_interrupt(env, PGM_OPERAND, 6);
+ return -EINVAL;
+ }
+
+ /* currently we only support designation type 1 with translation */
+ if (!(dt == ZPCI_IOTA_RTTO && t)) {
+ error_report("unsupported ioat dt %d t %d", dt, t);
+ program_interrupt(env, PGM_OPERAND, 6);
+ return -EINVAL;
+ }
+
+ pbdev->pba = pba;
+ pbdev->pal = pal;
+ pbdev->g_iota = g_iota;
+ return 0;
+}
+
+static void dereg_ioat(S390PCIBusDevice *pbdev)
+{
+ pbdev->pba = 0;
+ pbdev->pal = 0;
+ pbdev->g_iota = 0;
+}
+
+int mpcifc_service_call(S390CPU *cpu, uint8_t r1, uint64_t fiba, uint8_t ar)
+{
+ CPUS390XState *env = &cpu->env;
+ uint8_t oc;
+ uint32_t fh;
+ ZpciFib fib;
+ S390PCIBusDevice *pbdev;
+ uint64_t cc = ZPCI_PCI_LS_OK;
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ program_interrupt(env, PGM_PRIVILEGED, 6);
+ return 0;
+ }
+
+ oc = env->regs[r1] & 0xff;
+ fh = env->regs[r1] >> 32;
+
+ if (fiba & 0x7) {
+ program_interrupt(env, PGM_SPECIFICATION, 6);
+ return 0;
+ }
+
+ pbdev = s390_pci_find_dev_by_fh(fh);
+ if (!pbdev) {
+ DPRINTF("mpcifc no pci dev fh 0x%x\n", fh);
+ setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
+ return 0;
+ }
+
+ if (s390_cpu_virt_mem_read(cpu, fiba, ar, (uint8_t *)&fib, sizeof(fib))) {
+ return 0;
+ }
+
+ switch (oc) {
+ case ZPCI_MOD_FC_REG_INT:
+ if (reg_irqs(env, pbdev, fib)) {
+ cc = ZPCI_PCI_LS_ERR;
+ }
+ break;
+ case ZPCI_MOD_FC_DEREG_INT:
+ dereg_irqs(pbdev);
+ break;
+ case ZPCI_MOD_FC_REG_IOAT:
+ if (reg_ioat(env, pbdev, fib)) {
+ cc = ZPCI_PCI_LS_ERR;
+ }
+ break;
+ case ZPCI_MOD_FC_DEREG_IOAT:
+ dereg_ioat(pbdev);
+ break;
+ case ZPCI_MOD_FC_REREG_IOAT:
+ dereg_ioat(pbdev);
+ if (reg_ioat(env, pbdev, fib)) {
+ cc = ZPCI_PCI_LS_ERR;
+ }
+ break;
+ case ZPCI_MOD_FC_RESET_ERROR:
+ pbdev->error_state = false;
+ pbdev->lgstg_blocked = false;
+ break;
+ case ZPCI_MOD_FC_RESET_BLOCK:
+ pbdev->lgstg_blocked = false;
+ break;
+ case ZPCI_MOD_FC_SET_MEASURE:
+ pbdev->fmb_addr = ldq_p(&fib.fmb_addr);
+ break;
+ default:
+ program_interrupt(&cpu->env, PGM_OPERAND, 6);
+ cc = ZPCI_PCI_LS_ERR;
+ }
+
+ setcc(cpu, cc);
+ return 0;
+}
+
+int stpcifc_service_call(S390CPU *cpu, uint8_t r1, uint64_t fiba, uint8_t ar)
+{
+ CPUS390XState *env = &cpu->env;
+ uint32_t fh;
+ ZpciFib fib;
+ S390PCIBusDevice *pbdev;
+ uint32_t data;
+ uint64_t cc = ZPCI_PCI_LS_OK;
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ program_interrupt(env, PGM_PRIVILEGED, 6);
+ return 0;
+ }
+
+ fh = env->regs[r1] >> 32;
+
+ if (fiba & 0x7) {
+ program_interrupt(env, PGM_SPECIFICATION, 6);
+ return 0;
+ }
+
+ pbdev = s390_pci_find_dev_by_fh(fh);
+ if (!pbdev) {
+ setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
+ return 0;
+ }
+
+ memset(&fib, 0, sizeof(fib));
+ stq_p(&fib.pba, pbdev->pba);
+ stq_p(&fib.pal, pbdev->pal);
+ stq_p(&fib.iota, pbdev->g_iota);
+ stq_p(&fib.aibv, pbdev->routes.adapter.ind_addr);
+ stq_p(&fib.aisb, pbdev->routes.adapter.summary_addr);
+ stq_p(&fib.fmb_addr, pbdev->fmb_addr);
+
+ data = ((uint32_t)pbdev->isc << 28) | ((uint32_t)pbdev->noi << 16) |
+ ((uint32_t)pbdev->routes.adapter.ind_offset << 8) |
+ ((uint32_t)pbdev->sum << 7) | pbdev->routes.adapter.summary_offset;
+ stl_p(&fib.data, data);
+
+ if (pbdev->fh >> ENABLE_BIT_OFFSET) {
+ fib.fc |= 0x80;
+ }
+
+ if (pbdev->error_state) {
+ fib.fc |= 0x40;
+ }
+
+ if (pbdev->lgstg_blocked) {
+ fib.fc |= 0x20;
+ }
+
+ if (pbdev->g_iota) {
+ fib.fc |= 0x10;
+ }
+
+ if (s390_cpu_virt_mem_write(cpu, fiba, ar, (uint8_t *)&fib, sizeof(fib))) {
+ return 0;
+ }
+
+ setcc(cpu, cc);
+ return 0;
+}
diff --git a/hw/s390x/s390-pci-inst.h b/hw/s390x/s390-pci-inst.h
new file mode 100644
index 00000000..70fa7139
--- /dev/null
+++ b/hw/s390x/s390-pci-inst.h
@@ -0,0 +1,289 @@
+/*
+ * s390 PCI instruction definitions
+ *
+ * Copyright 2014 IBM Corp.
+ * Author(s): Frank Blaschka <frank.blaschka@de.ibm.com>
+ * Hong Bo Li <lihbbj@cn.ibm.com>
+ * Yi Min Zhao <zyimin@cn.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef HW_S390_PCI_INST_H
+#define HW_S390_PCI_INST_H
+
+#include <sysemu/dma.h>
+
+/* CLP common request & response block size */
+#define CLP_BLK_SIZE 4096
+#define PCI_BAR_COUNT 6
+#define PCI_MAX_FUNCTIONS 4096
+
+typedef struct ClpReqHdr {
+ uint16_t len;
+ uint16_t cmd;
+} QEMU_PACKED ClpReqHdr;
+
+typedef struct ClpRspHdr {
+ uint16_t len;
+ uint16_t rsp;
+} QEMU_PACKED ClpRspHdr;
+
+/* CLP Response Codes */
+#define CLP_RC_OK 0x0010 /* Command request successfully */
+#define CLP_RC_CMD 0x0020 /* Command code not recognized */
+#define CLP_RC_PERM 0x0030 /* Command not authorized */
+#define CLP_RC_FMT 0x0040 /* Invalid command request format */
+#define CLP_RC_LEN 0x0050 /* Invalid command request length */
+#define CLP_RC_8K 0x0060 /* Command requires 8K LPCB */
+#define CLP_RC_RESNOT0 0x0070 /* Reserved field not zero */
+#define CLP_RC_NODATA 0x0080 /* No data available */
+#define CLP_RC_FC_UNKNOWN 0x0100 /* Function code not recognized */
+
+/*
+ * Call Logical Processor - Command Codes
+ */
+#define CLP_LIST_PCI 0x0002
+#define CLP_QUERY_PCI_FN 0x0003
+#define CLP_QUERY_PCI_FNGRP 0x0004
+#define CLP_SET_PCI_FN 0x0005
+
+/* PCI function handle list entry */
+typedef struct ClpFhListEntry {
+ uint16_t device_id;
+ uint16_t vendor_id;
+#define CLP_FHLIST_MASK_CONFIG 0x80000000
+ uint32_t config;
+ uint32_t fid;
+ uint32_t fh;
+} QEMU_PACKED ClpFhListEntry;
+
+#define CLP_RC_SETPCIFN_FH 0x0101 /* Invalid PCI fn handle */
+#define CLP_RC_SETPCIFN_FHOP 0x0102 /* Fn handle not valid for op */
+#define CLP_RC_SETPCIFN_DMAAS 0x0103 /* Invalid DMA addr space */
+#define CLP_RC_SETPCIFN_RES 0x0104 /* Insufficient resources */
+#define CLP_RC_SETPCIFN_ALRDY 0x0105 /* Fn already in requested state */
+#define CLP_RC_SETPCIFN_ERR 0x0106 /* Fn in permanent error state */
+#define CLP_RC_SETPCIFN_RECPND 0x0107 /* Error recovery pending */
+#define CLP_RC_SETPCIFN_BUSY 0x0108 /* Fn busy */
+#define CLP_RC_LISTPCI_BADRT 0x010a /* Resume token not recognized */
+#define CLP_RC_QUERYPCIFG_PFGID 0x010b /* Unrecognized PFGID */
+
+/* request or response block header length */
+#define LIST_PCI_HDR_LEN 32
+
+/* Number of function handles fitting in response block */
+#define CLP_FH_LIST_NR_ENTRIES \
+ ((CLP_BLK_SIZE - 2 * LIST_PCI_HDR_LEN) \
+ / sizeof(ClpFhListEntry))
+
+#define CLP_SET_ENABLE_PCI_FN 0 /* Yes, 0 enables it */
+#define CLP_SET_DISABLE_PCI_FN 1 /* Yes, 1 disables it */
+
+#define CLP_UTIL_STR_LEN 64
+
+#define CLP_MASK_FMT 0xf0000000
+
+/* List PCI functions request */
+typedef struct ClpReqListPci {
+ ClpReqHdr hdr;
+ uint32_t fmt;
+ uint64_t reserved1;
+ uint64_t resume_token;
+ uint64_t reserved2;
+} QEMU_PACKED ClpReqListPci;
+
+/* List PCI functions response */
+typedef struct ClpRspListPci {
+ ClpRspHdr hdr;
+ uint32_t fmt;
+ uint64_t reserved1;
+ uint64_t resume_token;
+ uint32_t mdd;
+ uint16_t max_fn;
+ uint8_t reserved2;
+ uint8_t entry_size;
+ ClpFhListEntry fh_list[CLP_FH_LIST_NR_ENTRIES];
+} QEMU_PACKED ClpRspListPci;
+
+/* Query PCI function request */
+typedef struct ClpReqQueryPci {
+ ClpReqHdr hdr;
+ uint32_t fmt;
+ uint64_t reserved1;
+ uint32_t fh; /* function handle */
+ uint32_t reserved2;
+ uint64_t reserved3;
+} QEMU_PACKED ClpReqQueryPci;
+
+/* Query PCI function response */
+typedef struct ClpRspQueryPci {
+ ClpRspHdr hdr;
+ uint32_t fmt;
+ uint64_t reserved1;
+ uint16_t vfn; /* virtual fn number */
+#define CLP_RSP_QPCI_MASK_UTIL 0x100
+#define CLP_RSP_QPCI_MASK_PFGID 0xff
+ uint16_t ug;
+ uint32_t fid; /* pci function id */
+ uint8_t bar_size[PCI_BAR_COUNT];
+ uint16_t pchid;
+ uint32_t bar[PCI_BAR_COUNT];
+ uint64_t reserved2;
+ uint64_t sdma; /* start dma as */
+ uint64_t edma; /* end dma as */
+ uint32_t reserved3[11];
+ uint32_t uid;
+ uint8_t util_str[CLP_UTIL_STR_LEN]; /* utility string */
+} QEMU_PACKED ClpRspQueryPci;
+
+/* Query PCI function group request */
+typedef struct ClpReqQueryPciGrp {
+ ClpReqHdr hdr;
+ uint32_t fmt;
+ uint64_t reserved1;
+#define CLP_REQ_QPCIG_MASK_PFGID 0xff
+ uint32_t g;
+ uint32_t reserved2;
+ uint64_t reserved3;
+} QEMU_PACKED ClpReqQueryPciGrp;
+
+/* Query PCI function group response */
+typedef struct ClpRspQueryPciGrp {
+ ClpRspHdr hdr;
+ uint32_t fmt;
+ uint64_t reserved1;
+#define CLP_RSP_QPCIG_MASK_NOI 0xfff
+ uint16_t i;
+ uint8_t version;
+#define CLP_RSP_QPCIG_MASK_FRAME 0x2
+#define CLP_RSP_QPCIG_MASK_REFRESH 0x1
+ uint8_t fr;
+ uint16_t reserved2;
+ uint16_t mui;
+ uint64_t reserved3;
+ uint64_t dasm; /* dma address space mask */
+ uint64_t msia; /* MSI address */
+ uint64_t reserved4;
+ uint64_t reserved5;
+} QEMU_PACKED ClpRspQueryPciGrp;
+
+/* Set PCI function request */
+typedef struct ClpReqSetPci {
+ ClpReqHdr hdr;
+ uint32_t fmt;
+ uint64_t reserved1;
+ uint32_t fh; /* function handle */
+ uint16_t reserved2;
+ uint8_t oc; /* operation controls */
+ uint8_t ndas; /* number of dma spaces */
+ uint64_t reserved3;
+} QEMU_PACKED ClpReqSetPci;
+
+/* Set PCI function response */
+typedef struct ClpRspSetPci {
+ ClpRspHdr hdr;
+ uint32_t fmt;
+ uint64_t reserved1;
+ uint32_t fh; /* function handle */
+ uint32_t reserved3;
+ uint64_t reserved4;
+} QEMU_PACKED ClpRspSetPci;
+
+typedef struct ClpReqRspListPci {
+ ClpReqListPci request;
+ ClpRspListPci response;
+} QEMU_PACKED ClpReqRspListPci;
+
+typedef struct ClpReqRspSetPci {
+ ClpReqSetPci request;
+ ClpRspSetPci response;
+} QEMU_PACKED ClpReqRspSetPci;
+
+typedef struct ClpReqRspQueryPci {
+ ClpReqQueryPci request;
+ ClpRspQueryPci response;
+} QEMU_PACKED ClpReqRspQueryPci;
+
+typedef struct ClpReqRspQueryPciGrp {
+ ClpReqQueryPciGrp request;
+ ClpRspQueryPciGrp response;
+} QEMU_PACKED ClpReqRspQueryPciGrp;
+
+/* Load/Store status codes */
+#define ZPCI_PCI_ST_FUNC_NOT_ENABLED 4
+#define ZPCI_PCI_ST_FUNC_IN_ERR 8
+#define ZPCI_PCI_ST_BLOCKED 12
+#define ZPCI_PCI_ST_INSUF_RES 16
+#define ZPCI_PCI_ST_INVAL_AS 20
+#define ZPCI_PCI_ST_FUNC_ALREADY_ENABLED 24
+#define ZPCI_PCI_ST_DMA_AS_NOT_ENABLED 28
+#define ZPCI_PCI_ST_2ND_OP_IN_INV_AS 36
+#define ZPCI_PCI_ST_FUNC_NOT_AVAIL 40
+#define ZPCI_PCI_ST_ALREADY_IN_RQ_STATE 44
+
+/* Load/Store return codes */
+#define ZPCI_PCI_LS_OK 0
+#define ZPCI_PCI_LS_ERR 1
+#define ZPCI_PCI_LS_BUSY 2
+#define ZPCI_PCI_LS_INVAL_HANDLE 3
+
+/* Modify PCI Function Controls */
+#define ZPCI_MOD_FC_REG_INT 2
+#define ZPCI_MOD_FC_DEREG_INT 3
+#define ZPCI_MOD_FC_REG_IOAT 4
+#define ZPCI_MOD_FC_DEREG_IOAT 5
+#define ZPCI_MOD_FC_REREG_IOAT 6
+#define ZPCI_MOD_FC_RESET_ERROR 7
+#define ZPCI_MOD_FC_RESET_BLOCK 9
+#define ZPCI_MOD_FC_SET_MEASURE 10
+
+/* FIB function controls */
+#define ZPCI_FIB_FC_ENABLED 0x80
+#define ZPCI_FIB_FC_ERROR 0x40
+#define ZPCI_FIB_FC_LS_BLOCKED 0x20
+#define ZPCI_FIB_FC_DMAAS_REG 0x10
+
+/* FIB function controls */
+#define ZPCI_FIB_FC_ENABLED 0x80
+#define ZPCI_FIB_FC_ERROR 0x40
+#define ZPCI_FIB_FC_LS_BLOCKED 0x20
+#define ZPCI_FIB_FC_DMAAS_REG 0x10
+
+/* Function Information Block */
+typedef struct ZpciFib {
+ uint8_t fmt; /* format */
+ uint8_t reserved1[7];
+ uint8_t fc; /* function controls */
+ uint8_t reserved2;
+ uint16_t reserved3;
+ uint32_t reserved4;
+ uint64_t pba; /* PCI base address */
+ uint64_t pal; /* PCI address limit */
+ uint64_t iota; /* I/O Translation Anchor */
+#define FIB_DATA_ISC(x) (((x) >> 28) & 0x7)
+#define FIB_DATA_NOI(x) (((x) >> 16) & 0xfff)
+#define FIB_DATA_AIBVO(x) (((x) >> 8) & 0x3f)
+#define FIB_DATA_SUM(x) (((x) >> 7) & 0x1)
+#define FIB_DATA_AISBO(x) ((x) & 0x3f)
+ uint32_t data;
+ uint32_t reserved5;
+ uint64_t aibv; /* Adapter int bit vector address */
+ uint64_t aisb; /* Adapter int summary bit address */
+ uint64_t fmb_addr; /* Function measurement address and key */
+ uint32_t reserved6;
+ uint32_t gd;
+} QEMU_PACKED ZpciFib;
+
+int clp_service_call(S390CPU *cpu, uint8_t r2);
+int pcilg_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2);
+int pcistg_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2);
+int rpcit_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2);
+int pcistb_service_call(S390CPU *cpu, uint8_t r1, uint8_t r3, uint64_t gaddr,
+ uint8_t ar);
+int mpcifc_service_call(S390CPU *cpu, uint8_t r1, uint64_t fiba, uint8_t ar);
+int stpcifc_service_call(S390CPU *cpu, uint8_t r1, uint64_t fiba, uint8_t ar);
+
+#endif
diff --git a/hw/s390x/s390-virtio-bus.c b/hw/s390x/s390-virtio-bus.c
new file mode 100644
index 00000000..77aec8a5
--- /dev/null
+++ b/hw/s390x/s390-virtio-bus.c
@@ -0,0 +1,763 @@
+/*
+ * QEMU S390 virtio target
+ *
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-rng.h"
+#include "hw/virtio/virtio-serial.h"
+#include "hw/virtio/virtio-net.h"
+#include "hw/virtio/vhost-scsi.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+
+#include "hw/s390x/s390-virtio-bus.h"
+#include "hw/virtio/virtio-bus.h"
+
+/* #define DEBUG_S390 */
+
+#ifdef DEBUG_S390
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+#define VIRTIO_S390_QUEUE_MAX 64
+
+static void virtio_s390_bus_new(VirtioBusState *bus, size_t bus_size,
+ VirtIOS390Device *dev);
+
+static const TypeInfo s390_virtio_bus_info = {
+ .name = TYPE_S390_VIRTIO_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(VirtIOS390Bus),
+};
+
+static ram_addr_t s390_virtio_device_num_vq(VirtIOS390Device *dev);
+
+/* length of VirtIO device pages */
+const hwaddr virtio_size = S390_DEVICE_PAGES * TARGET_PAGE_SIZE;
+
+static void s390_virtio_bus_reset(void *opaque)
+{
+ VirtIOS390Bus *bus = opaque;
+ bus->next_ring = bus->dev_page + TARGET_PAGE_SIZE;
+}
+
+void s390_virtio_reset_idx(VirtIOS390Device *dev)
+{
+ int i;
+ hwaddr idx_addr;
+ uint8_t num_vq;
+
+ num_vq = s390_virtio_device_num_vq(dev);
+ for (i = 0; i < num_vq; i++) {
+ idx_addr = virtio_queue_get_avail_addr(dev->vdev, i) +
+ VIRTIO_VRING_AVAIL_IDX_OFFS;
+ address_space_stw(&address_space_memory, idx_addr, 0,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ idx_addr = virtio_queue_get_avail_addr(dev->vdev, i) +
+ virtio_queue_get_avail_size(dev->vdev, i);
+ address_space_stw(&address_space_memory, idx_addr, 0,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ idx_addr = virtio_queue_get_used_addr(dev->vdev, i) +
+ VIRTIO_VRING_USED_IDX_OFFS;
+ address_space_stw(&address_space_memory, idx_addr, 0,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ idx_addr = virtio_queue_get_used_addr(dev->vdev, i) +
+ virtio_queue_get_used_size(dev->vdev, i);
+ address_space_stw(&address_space_memory, idx_addr, 0,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ }
+}
+
+VirtIOS390Bus *s390_virtio_bus_init(ram_addr_t *ram_size)
+{
+ VirtIOS390Bus *bus;
+ BusState *_bus;
+ DeviceState *dev;
+
+ /* Create bridge device */
+ dev = qdev_create(NULL, "s390-virtio-bridge");
+ qdev_init_nofail(dev);
+
+ /* Create bus on bridge device */
+
+ _bus = qbus_create(TYPE_S390_VIRTIO_BUS, dev, "s390-virtio");
+ bus = DO_UPCAST(VirtIOS390Bus, bus, _bus);
+
+ bus->dev_page = *ram_size;
+ bus->dev_offs = bus->dev_page;
+ bus->next_ring = bus->dev_page + TARGET_PAGE_SIZE;
+
+ /* Enable hotplugging */
+ qbus_set_hotplug_handler(_bus, dev, &error_abort);
+
+ /* Allocate RAM for VirtIO device pages (descriptors, queues, rings) */
+ *ram_size += S390_DEVICE_PAGES * TARGET_PAGE_SIZE;
+
+ qemu_register_reset(s390_virtio_bus_reset, bus);
+ return bus;
+}
+
+static void s390_virtio_device_init(VirtIOS390Device *dev,
+ VirtIODevice *vdev)
+{
+ VirtIOS390Bus *bus;
+ int dev_len;
+
+ bus = DO_UPCAST(VirtIOS390Bus, bus, dev->qdev.parent_bus);
+ dev->vdev = vdev;
+ dev->dev_offs = bus->dev_offs;
+ dev->feat_len = sizeof(uint32_t); /* always keep 32 bits features */
+
+ dev_len = VIRTIO_DEV_OFFS_CONFIG;
+ dev_len += s390_virtio_device_num_vq(dev) * VIRTIO_VQCONFIG_LEN;
+ dev_len += dev->feat_len * 2;
+ dev_len += virtio_bus_get_vdev_config_len(&dev->bus);
+
+ bus->dev_offs += dev_len;
+
+ s390_virtio_device_sync(dev);
+ s390_virtio_reset_idx(dev);
+ if (dev->qdev.hotplugged) {
+ s390_virtio_irq(VIRTIO_PARAM_DEV_ADD, dev->dev_offs);
+ }
+}
+
+static void s390_virtio_net_realize(VirtIOS390Device *s390_dev, Error **errp)
+{
+ DeviceState *qdev = DEVICE(s390_dev);
+ VirtIONetS390 *dev = VIRTIO_NET_S390(s390_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ virtio_net_set_netclient_name(&dev->vdev, qdev->id,
+ object_get_typename(OBJECT(qdev)));
+ qdev_set_parent_bus(vdev, BUS(&s390_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ s390_virtio_device_init(s390_dev, VIRTIO_DEVICE(vdev));
+}
+
+static void s390_virtio_net_instance_init(Object *obj)
+{
+ VirtIONetS390 *dev = VIRTIO_NET_S390(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_NET);
+ object_property_add_alias(obj, "bootindex", OBJECT(&dev->vdev),
+ "bootindex", &error_abort);
+}
+
+static void s390_virtio_blk_realize(VirtIOS390Device *s390_dev, Error **errp)
+{
+ VirtIOBlkS390 *dev = VIRTIO_BLK_S390(s390_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&s390_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ s390_virtio_device_init(s390_dev, VIRTIO_DEVICE(vdev));
+}
+
+static void s390_virtio_blk_instance_init(Object *obj)
+{
+ VirtIOBlkS390 *dev = VIRTIO_BLK_S390(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_BLK);
+ object_property_add_alias(obj, "iothread", OBJECT(&dev->vdev),"iothread",
+ &error_abort);
+ object_property_add_alias(obj, "bootindex", OBJECT(&dev->vdev),
+ "bootindex", &error_abort);
+}
+
+static void s390_virtio_serial_realize(VirtIOS390Device *s390_dev, Error **errp)
+{
+ VirtIOSerialS390 *dev = VIRTIO_SERIAL_S390(s390_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ DeviceState *qdev = DEVICE(s390_dev);
+ Error *err = NULL;
+ VirtIOS390Bus *bus;
+ char *bus_name;
+
+ bus = DO_UPCAST(VirtIOS390Bus, bus, qdev->parent_bus);
+
+ /*
+ * For command line compatibility, this sets the virtio-serial-device bus
+ * name as before.
+ */
+ if (qdev->id) {
+ bus_name = g_strdup_printf("%s.0", qdev->id);
+ virtio_device_set_child_bus_name(VIRTIO_DEVICE(vdev), bus_name);
+ g_free(bus_name);
+ }
+
+ qdev_set_parent_bus(vdev, BUS(&s390_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ s390_virtio_device_init(s390_dev, VIRTIO_DEVICE(vdev));
+ bus->console = s390_dev;
+}
+
+static void s390_virtio_serial_instance_init(Object *obj)
+{
+ VirtIOSerialS390 *dev = VIRTIO_SERIAL_S390(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_SERIAL);
+}
+
+static void s390_virtio_scsi_realize(VirtIOS390Device *s390_dev, Error **errp)
+{
+ VirtIOSCSIS390 *dev = VIRTIO_SCSI_S390(s390_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ DeviceState *qdev = DEVICE(s390_dev);
+ Error *err = NULL;
+ char *bus_name;
+
+ /*
+ * For command line compatibility, this sets the virtio-scsi-device bus
+ * name as before.
+ */
+ if (qdev->id) {
+ bus_name = g_strdup_printf("%s.0", qdev->id);
+ virtio_device_set_child_bus_name(VIRTIO_DEVICE(vdev), bus_name);
+ g_free(bus_name);
+ }
+
+ qdev_set_parent_bus(vdev, BUS(&s390_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ s390_virtio_device_init(s390_dev, VIRTIO_DEVICE(vdev));
+}
+
+static void s390_virtio_scsi_instance_init(Object *obj)
+{
+ VirtIOSCSIS390 *dev = VIRTIO_SCSI_S390(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_SCSI);
+}
+
+#ifdef CONFIG_VHOST_SCSI
+static void s390_vhost_scsi_realize(VirtIOS390Device *s390_dev, Error **errp)
+{
+ VHostSCSIS390 *dev = VHOST_SCSI_S390(s390_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&s390_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ s390_virtio_device_init(s390_dev, VIRTIO_DEVICE(vdev));
+}
+
+static void s390_vhost_scsi_instance_init(Object *obj)
+{
+ VHostSCSIS390 *dev = VHOST_SCSI_S390(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VHOST_SCSI);
+}
+#endif
+
+
+static void s390_virtio_rng_realize(VirtIOS390Device *s390_dev, Error **errp)
+{
+ VirtIORNGS390 *dev = VIRTIO_RNG_S390(s390_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&s390_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ object_property_set_link(OBJECT(dev),
+ OBJECT(dev->vdev.conf.rng), "rng",
+ NULL);
+
+ s390_virtio_device_init(s390_dev, VIRTIO_DEVICE(vdev));
+}
+
+static void s390_virtio_rng_instance_init(Object *obj)
+{
+ VirtIORNGS390 *dev = VIRTIO_RNG_S390(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_RNG);
+ object_property_add_alias(obj, "rng", OBJECT(&dev->vdev),
+ "rng", &error_abort);
+}
+
+static uint64_t s390_virtio_device_vq_token(VirtIOS390Device *dev, int vq)
+{
+ ram_addr_t token_off;
+
+ token_off = (dev->dev_offs + VIRTIO_DEV_OFFS_CONFIG) +
+ (vq * VIRTIO_VQCONFIG_LEN) +
+ VIRTIO_VQCONFIG_OFFS_TOKEN;
+
+ return address_space_ldq_be(&address_space_memory, token_off,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+}
+
+static ram_addr_t s390_virtio_device_num_vq(VirtIOS390Device *dev)
+{
+ VirtIODevice *vdev = dev->vdev;
+ int num_vq;
+
+ for (num_vq = 0; num_vq < VIRTIO_S390_QUEUE_MAX; num_vq++) {
+ if (!virtio_queue_get_num(vdev, num_vq)) {
+ break;
+ }
+ }
+
+ return num_vq;
+}
+
+static ram_addr_t s390_virtio_next_ring(VirtIOS390Bus *bus)
+{
+ ram_addr_t r = bus->next_ring;
+
+ bus->next_ring += VIRTIO_RING_LEN;
+ return r;
+}
+
+void s390_virtio_device_sync(VirtIOS390Device *dev)
+{
+ VirtIOS390Bus *bus = DO_UPCAST(VirtIOS390Bus, bus, dev->qdev.parent_bus);
+ ram_addr_t cur_offs;
+ uint8_t num_vq;
+ int i;
+
+ virtio_reset(dev->vdev);
+
+ /* Sync dev space */
+ address_space_stb(&address_space_memory,
+ dev->dev_offs + VIRTIO_DEV_OFFS_TYPE,
+ dev->vdev->device_id,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+
+ address_space_stb(&address_space_memory,
+ dev->dev_offs + VIRTIO_DEV_OFFS_NUM_VQ,
+ s390_virtio_device_num_vq(dev),
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ address_space_stb(&address_space_memory,
+ dev->dev_offs + VIRTIO_DEV_OFFS_FEATURE_LEN,
+ dev->feat_len,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+
+ address_space_stb(&address_space_memory,
+ dev->dev_offs + VIRTIO_DEV_OFFS_CONFIG_LEN,
+ dev->vdev->config_len,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+
+ num_vq = s390_virtio_device_num_vq(dev);
+ address_space_stb(&address_space_memory,
+ dev->dev_offs + VIRTIO_DEV_OFFS_NUM_VQ, num_vq,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+
+ /* Sync virtqueues */
+ for (i = 0; i < num_vq; i++) {
+ ram_addr_t vq = (dev->dev_offs + VIRTIO_DEV_OFFS_CONFIG) +
+ (i * VIRTIO_VQCONFIG_LEN);
+ ram_addr_t vring;
+
+ vring = s390_virtio_next_ring(bus);
+ virtio_queue_set_addr(dev->vdev, i, vring);
+ virtio_queue_set_vector(dev->vdev, i, i);
+ address_space_stq_be(&address_space_memory,
+ vq + VIRTIO_VQCONFIG_OFFS_ADDRESS, vring,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ address_space_stw_be(&address_space_memory,
+ vq + VIRTIO_VQCONFIG_OFFS_NUM,
+ virtio_queue_get_num(dev->vdev, i),
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ }
+
+ cur_offs = dev->dev_offs;
+ cur_offs += VIRTIO_DEV_OFFS_CONFIG;
+ cur_offs += num_vq * VIRTIO_VQCONFIG_LEN;
+
+ /* Sync feature bitmap */
+ address_space_stl_le(&address_space_memory, cur_offs,
+ dev->vdev->host_features,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+
+ dev->feat_offs = cur_offs + dev->feat_len;
+ cur_offs += dev->feat_len * 2;
+
+ /* Sync config space */
+ virtio_bus_get_vdev_config(&dev->bus, dev->vdev->config);
+
+ cpu_physical_memory_write(cur_offs,
+ dev->vdev->config, dev->vdev->config_len);
+ cur_offs += dev->vdev->config_len;
+}
+
+void s390_virtio_device_update_status(VirtIOS390Device *dev)
+{
+ VirtIODevice *vdev = dev->vdev;
+ uint32_t features;
+
+ virtio_set_status(vdev,
+ address_space_ldub(&address_space_memory,
+ dev->dev_offs + VIRTIO_DEV_OFFS_STATUS,
+ MEMTXATTRS_UNSPECIFIED, NULL));
+
+ /* Update guest supported feature bitmap */
+
+ features = bswap32(address_space_ldl_be(&address_space_memory,
+ dev->feat_offs,
+ MEMTXATTRS_UNSPECIFIED, NULL));
+ virtio_set_features(vdev, features);
+}
+
+/* Find a device by vring address */
+VirtIOS390Device *s390_virtio_bus_find_vring(VirtIOS390Bus *bus,
+ ram_addr_t mem,
+ int *vq_num)
+{
+ BusChild *kid;
+ int i;
+
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ VirtIOS390Device *dev = (VirtIOS390Device *)kid->child;
+
+ for (i = 0; i < VIRTIO_S390_QUEUE_MAX; i++) {
+ if (!virtio_queue_get_addr(dev->vdev, i))
+ break;
+ if (virtio_queue_get_addr(dev->vdev, i) == mem) {
+ if (vq_num) {
+ *vq_num = i;
+ }
+ return dev;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/* Find a device by device descriptor location */
+VirtIOS390Device *s390_virtio_bus_find_mem(VirtIOS390Bus *bus, ram_addr_t mem)
+{
+ BusChild *kid;
+
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ VirtIOS390Device *dev = (VirtIOS390Device *)kid->child;
+ if (dev->dev_offs == mem) {
+ return dev;
+ }
+ }
+
+ return NULL;
+}
+
+/* DeviceState to VirtIOS390Device. Note: used on datapath,
+ * be careful and test performance if you change this.
+ */
+static inline VirtIOS390Device *to_virtio_s390_device_fast(DeviceState *d)
+{
+ return container_of(d, VirtIOS390Device, qdev);
+}
+
+/* DeviceState to VirtIOS390Device. TODO: use QOM. */
+static inline VirtIOS390Device *to_virtio_s390_device(DeviceState *d)
+{
+ return container_of(d, VirtIOS390Device, qdev);
+}
+
+static void virtio_s390_notify(DeviceState *d, uint16_t vector)
+{
+ VirtIOS390Device *dev = to_virtio_s390_device_fast(d);
+ uint64_t token = s390_virtio_device_vq_token(dev, vector);
+
+ s390_virtio_irq(0, token);
+}
+
+static void virtio_s390_device_plugged(DeviceState *d, Error **errp)
+{
+ VirtIOS390Device *dev = to_virtio_s390_device(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ int n = virtio_get_num_queues(vdev);
+
+ if (n > VIRTIO_S390_QUEUE_MAX) {
+ error_setg(errp, "The nubmer of virtqueues %d "
+ "exceeds s390 limit %d", n,
+ VIRTIO_S390_QUEUE_MAX);
+ }
+}
+
+/**************** S390 Virtio Bus Device Descriptions *******************/
+
+static void s390_virtio_net_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOS390DeviceClass *k = VIRTIO_S390_DEVICE_CLASS(klass);
+
+ k->realize = s390_virtio_net_realize;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo s390_virtio_net = {
+ .name = TYPE_VIRTIO_NET_S390,
+ .parent = TYPE_VIRTIO_S390_DEVICE,
+ .instance_size = sizeof(VirtIONetS390),
+ .instance_init = s390_virtio_net_instance_init,
+ .class_init = s390_virtio_net_class_init,
+};
+
+static void s390_virtio_blk_class_init(ObjectClass *klass, void *data)
+{
+ VirtIOS390DeviceClass *k = VIRTIO_S390_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = s390_virtio_blk_realize;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo s390_virtio_blk = {
+ .name = "virtio-blk-s390",
+ .parent = TYPE_VIRTIO_S390_DEVICE,
+ .instance_size = sizeof(VirtIOBlkS390),
+ .instance_init = s390_virtio_blk_instance_init,
+ .class_init = s390_virtio_blk_class_init,
+};
+
+static Property s390_virtio_serial_properties[] = {
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void s390_virtio_serial_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOS390DeviceClass *k = VIRTIO_S390_DEVICE_CLASS(klass);
+
+ k->realize = s390_virtio_serial_realize;
+ dc->props = s390_virtio_serial_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo s390_virtio_serial = {
+ .name = TYPE_VIRTIO_SERIAL_S390,
+ .parent = TYPE_VIRTIO_S390_DEVICE,
+ .instance_size = sizeof(VirtIOSerialS390),
+ .instance_init = s390_virtio_serial_instance_init,
+ .class_init = s390_virtio_serial_class_init,
+};
+
+static void s390_virtio_rng_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOS390DeviceClass *k = VIRTIO_S390_DEVICE_CLASS(klass);
+
+ k->realize = s390_virtio_rng_realize;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo s390_virtio_rng = {
+ .name = TYPE_VIRTIO_RNG_S390,
+ .parent = TYPE_VIRTIO_S390_DEVICE,
+ .instance_size = sizeof(VirtIORNGS390),
+ .instance_init = s390_virtio_rng_instance_init,
+ .class_init = s390_virtio_rng_class_init,
+};
+
+static void s390_virtio_busdev_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOS390Device *_dev = (VirtIOS390Device *)dev;
+ VirtIOS390DeviceClass *_info = VIRTIO_S390_DEVICE_GET_CLASS(dev);
+
+ virtio_s390_bus_new(&_dev->bus, sizeof(_dev->bus), _dev);
+
+ _info->realize(_dev, errp);
+}
+
+static void s390_virtio_busdev_reset(DeviceState *dev)
+{
+ VirtIOS390Device *_dev = (VirtIOS390Device *)dev;
+
+ virtio_reset(_dev->vdev);
+}
+
+static void virtio_s390_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = s390_virtio_busdev_realize;
+ dc->bus_type = TYPE_S390_VIRTIO_BUS;
+ dc->reset = s390_virtio_busdev_reset;
+}
+
+static const TypeInfo virtio_s390_device_info = {
+ .name = TYPE_VIRTIO_S390_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(VirtIOS390Device),
+ .class_init = virtio_s390_device_class_init,
+ .class_size = sizeof(VirtIOS390DeviceClass),
+ .abstract = true,
+};
+
+static void s390_virtio_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOS390DeviceClass *k = VIRTIO_S390_DEVICE_CLASS(klass);
+
+ k->realize = s390_virtio_scsi_realize;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo s390_virtio_scsi = {
+ .name = TYPE_VIRTIO_SCSI_S390,
+ .parent = TYPE_VIRTIO_S390_DEVICE,
+ .instance_size = sizeof(VirtIOSCSIS390),
+ .instance_init = s390_virtio_scsi_instance_init,
+ .class_init = s390_virtio_scsi_class_init,
+};
+
+#ifdef CONFIG_VHOST_SCSI
+static void s390_vhost_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOS390DeviceClass *k = VIRTIO_S390_DEVICE_CLASS(klass);
+
+ k->realize = s390_vhost_scsi_realize;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo s390_vhost_scsi = {
+ .name = TYPE_VHOST_SCSI_S390,
+ .parent = TYPE_VIRTIO_S390_DEVICE,
+ .instance_size = sizeof(VHostSCSIS390),
+ .instance_init = s390_vhost_scsi_instance_init,
+ .class_init = s390_vhost_scsi_class_init,
+};
+#endif
+
+/***************** S390 Virtio Bus Bridge Device *******************/
+/* Only required to have the virtio bus as child in the system bus */
+
+static int s390_virtio_bridge_init(SysBusDevice *dev)
+{
+ /* nothing */
+ return 0;
+}
+
+static void s390_virtio_bridge_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->init = s390_virtio_bridge_init;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo s390_virtio_bridge_info = {
+ .name = "s390-virtio-bridge",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .class_init = s390_virtio_bridge_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+/* virtio-s390-bus */
+
+static void virtio_s390_bus_new(VirtioBusState *bus, size_t bus_size,
+ VirtIOS390Device *dev)
+{
+ DeviceState *qdev = DEVICE(dev);
+ char virtio_bus_name[] = "virtio-bus";
+
+ qbus_create_inplace(bus, bus_size, TYPE_VIRTIO_S390_BUS,
+ qdev, virtio_bus_name);
+}
+
+static void virtio_s390_bus_class_init(ObjectClass *klass, void *data)
+{
+ VirtioBusClass *k = VIRTIO_BUS_CLASS(klass);
+ BusClass *bus_class = BUS_CLASS(klass);
+ bus_class->max_dev = 1;
+ k->notify = virtio_s390_notify;
+ k->device_plugged = virtio_s390_device_plugged;
+}
+
+static const TypeInfo virtio_s390_bus_info = {
+ .name = TYPE_VIRTIO_S390_BUS,
+ .parent = TYPE_VIRTIO_BUS,
+ .instance_size = sizeof(VirtioS390BusState),
+ .class_init = virtio_s390_bus_class_init,
+};
+
+static void s390_virtio_register_types(void)
+{
+ type_register_static(&virtio_s390_bus_info);
+ type_register_static(&s390_virtio_bus_info);
+ type_register_static(&virtio_s390_device_info);
+ type_register_static(&s390_virtio_serial);
+ type_register_static(&s390_virtio_blk);
+ type_register_static(&s390_virtio_net);
+ type_register_static(&s390_virtio_scsi);
+#ifdef CONFIG_VHOST_SCSI
+ type_register_static(&s390_vhost_scsi);
+#endif
+ type_register_static(&s390_virtio_rng);
+ type_register_static(&s390_virtio_bridge_info);
+}
+
+type_init(s390_virtio_register_types)
diff --git a/hw/s390x/s390-virtio-bus.h b/hw/s390x/s390-virtio-bus.h
new file mode 100644
index 00000000..7ad295e6
--- /dev/null
+++ b/hw/s390x/s390-virtio-bus.h
@@ -0,0 +1,186 @@
+/*
+ * QEMU S390x VirtIO BUS definitions
+ *
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef HW_S390_VIRTIO_BUS_H
+#define HW_S390_VIRTIO_BUS_H 1
+
+#include <stddef.h>
+
+#include "standard-headers/asm-s390/kvm_virtio.h"
+#include "standard-headers/linux/virtio_ring.h"
+#include "hw/virtio/virtio-blk.h"
+#include "hw/virtio/virtio-net.h"
+#include "hw/virtio/virtio-rng.h"
+#include "hw/virtio/virtio-serial.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "hw/virtio/virtio-bus.h"
+#ifdef CONFIG_VHOST_SCSI
+#include "hw/virtio/vhost-scsi.h"
+#endif
+
+typedef struct kvm_device_desc KvmDeviceDesc;
+
+#define VIRTIO_DEV_OFFS_TYPE offsetof(KvmDeviceDesc, type)
+#define VIRTIO_DEV_OFFS_NUM_VQ offsetof(KvmDeviceDesc, num_vq)
+#define VIRTIO_DEV_OFFS_FEATURE_LEN offsetof(KvmDeviceDesc, feature_len)
+#define VIRTIO_DEV_OFFS_CONFIG_LEN offsetof(KvmDeviceDesc, config_len)
+#define VIRTIO_DEV_OFFS_STATUS offsetof(KvmDeviceDesc, status)
+#define VIRTIO_DEV_OFFS_CONFIG offsetof(KvmDeviceDesc, config)
+
+typedef struct kvm_vqconfig KvmVqConfig;
+#define VIRTIO_VQCONFIG_OFFS_TOKEN offsetof(KvmVqConfig,token) /* 64 bit */
+#define VIRTIO_VQCONFIG_OFFS_ADDRESS offsetof(KvmVqConfig, address) /* 64 bit */
+#define VIRTIO_VQCONFIG_OFFS_NUM offsetof(KvmVqConfig, num) /* 16 bit */
+#define VIRTIO_VQCONFIG_LEN sizeof(KvmVqConfig)
+
+#define VIRTIO_RING_LEN (TARGET_PAGE_SIZE * 3)
+#define VIRTIO_VRING_AVAIL_IDX_OFFS offsetof(struct vring_avail, idx)
+#define VIRTIO_VRING_USED_IDX_OFFS offsetof(struct vring_used, idx)
+#define S390_DEVICE_PAGES 512
+
+#define TYPE_VIRTIO_S390_DEVICE "virtio-s390-device"
+#define VIRTIO_S390_DEVICE(obj) \
+ OBJECT_CHECK(VirtIOS390Device, (obj), TYPE_VIRTIO_S390_DEVICE)
+#define VIRTIO_S390_DEVICE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtIOS390DeviceClass, (klass), TYPE_VIRTIO_S390_DEVICE)
+#define VIRTIO_S390_DEVICE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VirtIOS390DeviceClass, (obj), TYPE_VIRTIO_S390_DEVICE)
+
+#define TYPE_S390_VIRTIO_BUS "s390-virtio-bus"
+#define S390_VIRTIO_BUS(obj) \
+ OBJECT_CHECK(VirtIOS390Bus, (obj), TYPE_S390_VIRTIO_BUS)
+
+/* virtio-s390-bus */
+
+typedef struct VirtioBusState VirtioS390BusState;
+typedef struct VirtioBusClass VirtioS390BusClass;
+
+#define TYPE_VIRTIO_S390_BUS "virtio-s390-bus"
+#define VIRTIO_S390_BUS(obj) \
+ OBJECT_CHECK(VirtioS390BusState, (obj), TYPE_VIRTIO_S390_BUS)
+#define VIRTIO_S390_BUS_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VirtioS390BusClass, obj, TYPE_VIRTIO_S390_BUS)
+#define VIRTIO_S390_BUS_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtioS390BusClass, klass, TYPE_VIRTIO_S390_BUS)
+
+
+typedef struct VirtIOS390Device VirtIOS390Device;
+
+typedef struct VirtIOS390DeviceClass {
+ DeviceClass qdev;
+ void (*realize)(VirtIOS390Device *dev, Error **errp);
+} VirtIOS390DeviceClass;
+
+struct VirtIOS390Device {
+ DeviceState qdev;
+ ram_addr_t dev_offs;
+ ram_addr_t feat_offs;
+ uint8_t feat_len;
+ VirtIODevice *vdev;
+ VirtioBusState bus;
+};
+
+typedef struct VirtIOS390Bus {
+ BusState bus;
+
+ VirtIOS390Device *console;
+ ram_addr_t dev_page;
+ ram_addr_t dev_offs;
+ ram_addr_t next_ring;
+} VirtIOS390Bus;
+
+
+void s390_virtio_device_update_status(VirtIOS390Device *dev);
+
+VirtIOS390Bus *s390_virtio_bus_init(ram_addr_t *ram_size);
+
+VirtIOS390Device *s390_virtio_bus_find_vring(VirtIOS390Bus *bus,
+ ram_addr_t mem, int *vq_num);
+VirtIOS390Device *s390_virtio_bus_find_mem(VirtIOS390Bus *bus, ram_addr_t mem);
+void s390_virtio_device_sync(VirtIOS390Device *dev);
+void s390_virtio_reset_idx(VirtIOS390Device *dev);
+
+/* virtio-blk-s390 */
+
+#define TYPE_VIRTIO_BLK_S390 "virtio-blk-s390"
+#define VIRTIO_BLK_S390(obj) \
+ OBJECT_CHECK(VirtIOBlkS390, (obj), TYPE_VIRTIO_BLK_S390)
+
+typedef struct VirtIOBlkS390 {
+ VirtIOS390Device parent_obj;
+ VirtIOBlock vdev;
+} VirtIOBlkS390;
+
+/* virtio-scsi-s390 */
+
+#define TYPE_VIRTIO_SCSI_S390 "virtio-scsi-s390"
+#define VIRTIO_SCSI_S390(obj) \
+ OBJECT_CHECK(VirtIOSCSIS390, (obj), TYPE_VIRTIO_SCSI_S390)
+
+typedef struct VirtIOSCSIS390 {
+ VirtIOS390Device parent_obj;
+ VirtIOSCSI vdev;
+} VirtIOSCSIS390;
+
+/* virtio-serial-s390 */
+
+#define TYPE_VIRTIO_SERIAL_S390 "virtio-serial-s390"
+#define VIRTIO_SERIAL_S390(obj) \
+ OBJECT_CHECK(VirtIOSerialS390, (obj), TYPE_VIRTIO_SERIAL_S390)
+
+typedef struct VirtIOSerialS390 {
+ VirtIOS390Device parent_obj;
+ VirtIOSerial vdev;
+} VirtIOSerialS390;
+
+/* virtio-net-s390 */
+
+#define TYPE_VIRTIO_NET_S390 "virtio-net-s390"
+#define VIRTIO_NET_S390(obj) \
+ OBJECT_CHECK(VirtIONetS390, (obj), TYPE_VIRTIO_NET_S390)
+
+typedef struct VirtIONetS390 {
+ VirtIOS390Device parent_obj;
+ VirtIONet vdev;
+} VirtIONetS390;
+
+/* vhost-scsi-s390 */
+
+#ifdef CONFIG_VHOST_SCSI
+#define TYPE_VHOST_SCSI_S390 "vhost-scsi-s390"
+#define VHOST_SCSI_S390(obj) \
+ OBJECT_CHECK(VHostSCSIS390, (obj), TYPE_VHOST_SCSI_S390)
+
+typedef struct VHostSCSIS390 {
+ VirtIOS390Device parent_obj;
+ VHostSCSI vdev;
+} VHostSCSIS390;
+#endif
+
+/* virtio-rng-s390 */
+
+#define TYPE_VIRTIO_RNG_S390 "virtio-rng-s390"
+#define VIRTIO_RNG_S390(obj) \
+ OBJECT_CHECK(VirtIORNGS390, (obj), TYPE_VIRTIO_RNG_S390)
+
+typedef struct VirtIORNGS390 {
+ VirtIOS390Device parent_obj;
+ VirtIORNG vdev;
+} VirtIORNGS390;
+
+#endif
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
new file mode 100644
index 00000000..4c51d1a5
--- /dev/null
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -0,0 +1,307 @@
+/*
+ * virtio ccw machine
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "hw/boards.h"
+#include "exec/address-spaces.h"
+#include "s390-virtio.h"
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/s390_flic.h"
+#include "ioinst.h"
+#include "css.h"
+#include "virtio-ccw.h"
+#include "qemu/config-file.h"
+#include "s390-pci-bus.h"
+
+#define TYPE_S390_CCW_MACHINE "s390-ccw-machine"
+
+#define S390_CCW_MACHINE(obj) \
+ OBJECT_CHECK(S390CcwMachineState, (obj), TYPE_S390_CCW_MACHINE)
+
+typedef struct S390CcwMachineState {
+ /*< private >*/
+ MachineState parent_obj;
+
+ /*< public >*/
+ bool aes_key_wrap;
+ bool dea_key_wrap;
+} S390CcwMachineState;
+
+void io_subsystem_reset(void)
+{
+ DeviceState *css, *sclp, *flic, *diag288;
+
+ css = DEVICE(object_resolve_path_type("", "virtual-css-bridge", NULL));
+ if (css) {
+ qdev_reset_all(css);
+ }
+ sclp = DEVICE(object_resolve_path_type("",
+ "s390-sclp-event-facility", NULL));
+ if (sclp) {
+ qdev_reset_all(sclp);
+ }
+ flic = DEVICE(object_resolve_path_type("", "s390-flic", NULL));
+ if (flic) {
+ qdev_reset_all(flic);
+ }
+ diag288 = DEVICE(object_resolve_path_type("", "diag288", NULL));
+ if (diag288) {
+ qdev_reset_all(diag288);
+ }
+}
+
+static int virtio_ccw_hcall_notify(const uint64_t *args)
+{
+ uint64_t subch_id = args[0];
+ uint64_t queue = args[1];
+ SubchDev *sch;
+ int cssid, ssid, schid, m;
+
+ if (ioinst_disassemble_sch_ident(subch_id, &m, &cssid, &ssid, &schid)) {
+ return -EINVAL;
+ }
+ sch = css_find_subch(m, cssid, ssid, schid);
+ if (!sch || !css_subch_visible(sch)) {
+ return -EINVAL;
+ }
+ if (queue >= VIRTIO_CCW_QUEUE_MAX) {
+ return -EINVAL;
+ }
+ virtio_queue_notify(virtio_ccw_get_vdev(sch), queue);
+ return 0;
+
+}
+
+static int virtio_ccw_hcall_early_printk(const uint64_t *args)
+{
+ uint64_t mem = args[0];
+
+ if (mem < ram_size) {
+ /* Early printk */
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static void virtio_ccw_register_hcalls(void)
+{
+ s390_register_virtio_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY,
+ virtio_ccw_hcall_notify);
+ /* Tolerate early printk. */
+ s390_register_virtio_hypercall(KVM_S390_VIRTIO_NOTIFY,
+ virtio_ccw_hcall_early_printk);
+}
+
+static void ccw_init(MachineState *machine)
+{
+ ram_addr_t my_ram_size = machine->ram_size;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ sclpMemoryHotplugDev *mhd = init_sclp_memory_hotplug_dev();
+ uint8_t *storage_keys;
+ int ret;
+ VirtualCssBus *css_bus;
+ DeviceState *dev;
+ QemuOpts *opts = qemu_opts_find(qemu_find_opts("memory"), NULL);
+ ram_addr_t pad_size = 0;
+ ram_addr_t maxmem = qemu_opt_get_size(opts, "maxmem", my_ram_size);
+ ram_addr_t standby_mem_size = maxmem - my_ram_size;
+ uint64_t kvm_limit;
+
+ /* The storage increment size is a multiple of 1M and is a power of 2.
+ * The number of storage increments must be MAX_STORAGE_INCREMENTS or fewer.
+ * The variable 'mhd->increment_size' is an exponent of 2 that can be
+ * used to calculate the size (in bytes) of an increment. */
+ mhd->increment_size = 20;
+ while ((my_ram_size >> mhd->increment_size) > MAX_STORAGE_INCREMENTS) {
+ mhd->increment_size++;
+ }
+ while ((standby_mem_size >> mhd->increment_size) > MAX_STORAGE_INCREMENTS) {
+ mhd->increment_size++;
+ }
+
+ /* The core and standby memory areas need to be aligned with
+ * the increment size. In effect, this can cause the
+ * user-specified memory size to be rounded down to align
+ * with the nearest increment boundary. */
+ standby_mem_size = standby_mem_size >> mhd->increment_size
+ << mhd->increment_size;
+ my_ram_size = my_ram_size >> mhd->increment_size
+ << mhd->increment_size;
+
+ /* let's propagate the changed ram size into the global variable. */
+ ram_size = my_ram_size;
+ machine->maxram_size = my_ram_size + standby_mem_size;
+
+ ret = s390_set_memory_limit(machine->maxram_size, &kvm_limit);
+ if (ret == -E2BIG) {
+ hw_error("qemu: host supports a maximum of %" PRIu64 " GB",
+ kvm_limit >> 30);
+ } else if (ret) {
+ hw_error("qemu: setting the guest size failed");
+ }
+
+ /* get a BUS */
+ css_bus = virtual_css_bus_init();
+ s390_sclp_init();
+ s390_init_ipl_dev(machine->kernel_filename, machine->kernel_cmdline,
+ machine->initrd_filename, "s390-ccw.img", true);
+ s390_flic_init();
+
+ dev = qdev_create(NULL, TYPE_S390_PCI_HOST_BRIDGE);
+ object_property_add_child(qdev_get_machine(), TYPE_S390_PCI_HOST_BRIDGE,
+ OBJECT(dev), NULL);
+ qdev_init_nofail(dev);
+
+ /* register hypercalls */
+ virtio_ccw_register_hcalls();
+
+ /* allocate RAM for core */
+ memory_region_init_ram(ram, NULL, "s390.ram", my_ram_size, &error_abort);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(sysmem, 0, ram);
+
+ /* If the size of ram is not on a MEM_SECTION_SIZE boundary,
+ calculate the pad size necessary to force this boundary. */
+ if (standby_mem_size) {
+ if (my_ram_size % MEM_SECTION_SIZE) {
+ pad_size = MEM_SECTION_SIZE - my_ram_size % MEM_SECTION_SIZE;
+ }
+ my_ram_size += standby_mem_size + pad_size;
+ mhd->pad_size = pad_size;
+ mhd->standby_mem_size = standby_mem_size;
+ }
+
+ /* allocate storage keys */
+ storage_keys = g_malloc0(my_ram_size / TARGET_PAGE_SIZE);
+
+ /* init CPUs */
+ s390_init_cpus(machine->cpu_model, storage_keys);
+
+ if (kvm_enabled()) {
+ kvm_s390_enable_css_support(s390_cpu_addr2state(0));
+ }
+ /*
+ * Create virtual css and set it as default so that non mcss-e
+ * enabled guests only see virtio devices.
+ */
+ ret = css_create_css_image(VIRTUAL_CSSID, true);
+ assert(ret == 0);
+
+ /* Create VirtIO network adapters */
+ s390_create_virtio_net(BUS(css_bus), "virtio-net-ccw");
+
+ /* Register savevm handler for guest TOD clock */
+ register_savevm(NULL, "todclock", 0, 1,
+ gtod_save, gtod_load, kvm_state);
+}
+
+static void ccw_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ NMIClass *nc = NMI_CLASS(oc);
+
+ mc->init = ccw_init;
+ mc->block_default_type = IF_VIRTIO;
+ mc->no_cdrom = 1;
+ mc->no_floppy = 1;
+ mc->no_serial = 1;
+ mc->no_parallel = 1;
+ mc->no_sdcard = 1;
+ mc->use_sclp = 1;
+ mc->max_cpus = 255;
+ nc->nmi_monitor_handler = s390_nmi;
+}
+
+static inline bool machine_get_aes_key_wrap(Object *obj, Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ return ms->aes_key_wrap;
+}
+
+static inline void machine_set_aes_key_wrap(Object *obj, bool value,
+ Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ ms->aes_key_wrap = value;
+}
+
+static inline bool machine_get_dea_key_wrap(Object *obj, Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ return ms->dea_key_wrap;
+}
+
+static inline void machine_set_dea_key_wrap(Object *obj, bool value,
+ Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ ms->dea_key_wrap = value;
+}
+
+static inline void s390_machine_initfn(Object *obj)
+{
+ object_property_add_bool(obj, "aes-key-wrap",
+ machine_get_aes_key_wrap,
+ machine_set_aes_key_wrap, NULL);
+ object_property_set_description(obj, "aes-key-wrap",
+ "enable/disable AES key wrapping using the CPACF wrapping key",
+ NULL);
+ object_property_set_bool(obj, true, "aes-key-wrap", NULL);
+
+ object_property_add_bool(obj, "dea-key-wrap",
+ machine_get_dea_key_wrap,
+ machine_set_dea_key_wrap, NULL);
+ object_property_set_description(obj, "dea-key-wrap",
+ "enable/disable DEA key wrapping using the CPACF wrapping key",
+ NULL);
+ object_property_set_bool(obj, true, "dea-key-wrap", NULL);
+}
+
+static const TypeInfo ccw_machine_info = {
+ .name = TYPE_S390_CCW_MACHINE,
+ .parent = TYPE_MACHINE,
+ .abstract = true,
+ .instance_size = sizeof(S390CcwMachineState),
+ .instance_init = s390_machine_initfn,
+ .class_init = ccw_machine_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_NMI },
+ { }
+ },
+};
+
+static void ccw_machine_2_4_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->name = "s390-ccw-virtio-2.4";
+ mc->alias = "s390-ccw-virtio";
+ mc->desc = "VirtIO-ccw based S390 machine v2.4";
+ mc->is_default = 1;
+}
+
+static const TypeInfo ccw_machine_2_4_info = {
+ .name = TYPE_S390_CCW_MACHINE "2.4",
+ .parent = TYPE_S390_CCW_MACHINE,
+ .class_init = ccw_machine_2_4_class_init,
+};
+
+static void ccw_machine_register_types(void)
+{
+ type_register_static(&ccw_machine_info);
+ type_register_static(&ccw_machine_2_4_info);
+}
+
+type_init(ccw_machine_register_types)
diff --git a/hw/s390x/s390-virtio-hcall.c b/hw/s390x/s390-virtio-hcall.c
new file mode 100644
index 00000000..c7bdc200
--- /dev/null
+++ b/hw/s390x/s390-virtio-hcall.c
@@ -0,0 +1,40 @@
+/*
+ * Support for virtio hypercalls on s390
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "cpu.h"
+#include "hw/s390x/s390-virtio.h"
+
+#define MAX_DIAG_SUBCODES 255
+
+static s390_virtio_fn s390_diag500_table[MAX_DIAG_SUBCODES];
+
+void s390_register_virtio_hypercall(uint64_t code, s390_virtio_fn fn)
+{
+ assert(code < MAX_DIAG_SUBCODES);
+ assert(!s390_diag500_table[code]);
+
+ s390_diag500_table[code] = fn;
+}
+
+int s390_virtio_hypercall(CPUS390XState *env)
+{
+ s390_virtio_fn fn;
+
+ if (env->regs[1] < MAX_DIAG_SUBCODES) {
+ fn = s390_diag500_table[env->regs[1]];
+ if (fn) {
+ env->regs[2] = fn(&env->regs[2]);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
diff --git a/hw/s390x/s390-virtio.c b/hw/s390x/s390-virtio.c
new file mode 100644
index 00000000..1284e77b
--- /dev/null
+++ b/hw/s390x/s390-virtio.c
@@ -0,0 +1,366 @@
+/*
+ * QEMU S390 virtio target
+ *
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
+ * Copyright IBM Corp 2012
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * Contributions after 2012-10-29 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ *
+ * You should have received a copy of the GNU (Lesser) General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "qapi/qmp/qerror.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/sysemu.h"
+#include "net/net.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "hw/virtio/virtio.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+#include "exec/address-spaces.h"
+
+#include "hw/s390x/s390-virtio-bus.h"
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/s390_flic.h"
+#include "hw/s390x/s390-virtio.h"
+#include "cpu.h"
+
+//#define DEBUG_S390
+
+#ifdef DEBUG_S390
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+#define MAX_BLK_DEVS 10
+#define ZIPL_FILENAME "s390-zipl.rom"
+#define TYPE_S390_MACHINE "s390-machine"
+
+#define S390_TOD_CLOCK_VALUE_MISSING 0x00
+#define S390_TOD_CLOCK_VALUE_PRESENT 0x01
+
+static VirtIOS390Bus *s390_bus;
+static S390CPU **ipi_states;
+
+S390CPU *s390_cpu_addr2state(uint16_t cpu_addr)
+{
+ if (cpu_addr >= smp_cpus) {
+ return NULL;
+ }
+
+ return ipi_states[cpu_addr];
+}
+
+static int s390_virtio_hcall_notify(const uint64_t *args)
+{
+ uint64_t mem = args[0];
+ int r = 0, i;
+
+ if (mem > ram_size) {
+ VirtIOS390Device *dev = s390_virtio_bus_find_vring(s390_bus, mem, &i);
+ if (dev) {
+ /*
+ * Older kernels will use the virtqueue before setting DRIVER_OK.
+ * In this case the feature bits are not yet up to date, meaning
+ * that several funny things can happen, e.g. the guest thinks
+ * EVENT_IDX is on and QEMU thinks it is off. Let's force a feature
+ * and status sync.
+ */
+ if (!(dev->vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ s390_virtio_device_update_status(dev);
+ }
+ virtio_queue_notify(dev->vdev, i);
+ } else {
+ r = -EINVAL;
+ }
+ } else {
+ /* Early printk */
+ }
+ return r;
+}
+
+static int s390_virtio_hcall_reset(const uint64_t *args)
+{
+ uint64_t mem = args[0];
+ VirtIOS390Device *dev;
+
+ dev = s390_virtio_bus_find_mem(s390_bus, mem);
+ if (dev == NULL) {
+ return -EINVAL;
+ }
+ virtio_reset(dev->vdev);
+ address_space_stb(&address_space_memory,
+ dev->dev_offs + VIRTIO_DEV_OFFS_STATUS, 0,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ s390_virtio_device_sync(dev);
+ s390_virtio_reset_idx(dev);
+
+ return 0;
+}
+
+static int s390_virtio_hcall_set_status(const uint64_t *args)
+{
+ uint64_t mem = args[0];
+ int r = 0;
+ VirtIOS390Device *dev;
+
+ dev = s390_virtio_bus_find_mem(s390_bus, mem);
+ if (dev) {
+ s390_virtio_device_update_status(dev);
+ } else {
+ r = -EINVAL;
+ }
+ return r;
+}
+
+static void s390_virtio_register_hcalls(void)
+{
+ s390_register_virtio_hypercall(KVM_S390_VIRTIO_NOTIFY,
+ s390_virtio_hcall_notify);
+ s390_register_virtio_hypercall(KVM_S390_VIRTIO_RESET,
+ s390_virtio_hcall_reset);
+ s390_register_virtio_hypercall(KVM_S390_VIRTIO_SET_STATUS,
+ s390_virtio_hcall_set_status);
+}
+
+void s390_init_ipl_dev(const char *kernel_filename,
+ const char *kernel_cmdline,
+ const char *initrd_filename,
+ const char *firmware,
+ bool enforce_bios)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(NULL, "s390-ipl");
+ if (kernel_filename) {
+ qdev_prop_set_string(dev, "kernel", kernel_filename);
+ }
+ if (initrd_filename) {
+ qdev_prop_set_string(dev, "initrd", initrd_filename);
+ }
+ qdev_prop_set_string(dev, "cmdline", kernel_cmdline);
+ qdev_prop_set_string(dev, "firmware", firmware);
+ qdev_prop_set_bit(dev, "enforce_bios", enforce_bios);
+ object_property_add_child(qdev_get_machine(), "s390-ipl",
+ OBJECT(dev), NULL);
+ qdev_init_nofail(dev);
+}
+
+void s390_init_cpus(const char *cpu_model, uint8_t *storage_keys)
+{
+ int i;
+
+ if (cpu_model == NULL) {
+ cpu_model = "host";
+ }
+
+ ipi_states = g_malloc(sizeof(S390CPU *) * smp_cpus);
+
+ for (i = 0; i < smp_cpus; i++) {
+ S390CPU *cpu;
+ CPUState *cs;
+
+ cpu = cpu_s390x_init(cpu_model);
+ cs = CPU(cpu);
+
+ ipi_states[i] = cpu;
+ cs->halted = 1;
+ cs->exception_index = EXCP_HLT;
+ cpu->env.storage_keys = storage_keys;
+ }
+}
+
+
+void s390_create_virtio_net(BusState *bus, const char *name)
+{
+ int i;
+
+ for (i = 0; i < nb_nics; i++) {
+ NICInfo *nd = &nd_table[i];
+ DeviceState *dev;
+
+ if (!nd->model) {
+ nd->model = g_strdup("virtio");
+ }
+
+ if (strcmp(nd->model, "virtio")) {
+ fprintf(stderr, "S390 only supports VirtIO nics\n");
+ exit(1);
+ }
+
+ dev = qdev_create(bus, name);
+ qdev_set_nic_properties(dev, nd);
+ qdev_init_nofail(dev);
+ }
+}
+
+void gtod_save(QEMUFile *f, void *opaque)
+{
+ uint64_t tod_low;
+ uint8_t tod_high;
+ int r;
+
+ r = s390_get_clock(&tod_high, &tod_low);
+ if (r) {
+ fprintf(stderr, "WARNING: Unable to get guest clock for migration. "
+ "Error code %d. Guest clock will not be migrated "
+ "which could cause the guest to hang.\n", r);
+ qemu_put_byte(f, S390_TOD_CLOCK_VALUE_MISSING);
+ return;
+ }
+
+ qemu_put_byte(f, S390_TOD_CLOCK_VALUE_PRESENT);
+ qemu_put_byte(f, tod_high);
+ qemu_put_be64(f, tod_low);
+}
+
+int gtod_load(QEMUFile *f, void *opaque, int version_id)
+{
+ uint64_t tod_low;
+ uint8_t tod_high;
+ int r;
+
+ if (qemu_get_byte(f) == S390_TOD_CLOCK_VALUE_MISSING) {
+ fprintf(stderr, "WARNING: Guest clock was not migrated. This could "
+ "cause the guest to hang.\n");
+ return 0;
+ }
+
+ tod_high = qemu_get_byte(f);
+ tod_low = qemu_get_be64(f);
+
+ r = s390_set_clock(&tod_high, &tod_low);
+ if (r) {
+ fprintf(stderr, "WARNING: Unable to set guest clock value. "
+ "s390_get_clock returned error %d. This could cause "
+ "the guest to hang.\n", r);
+ }
+
+ return 0;
+}
+
+/* PC hardware initialisation */
+static void s390_init(MachineState *machine)
+{
+ ram_addr_t my_ram_size = machine->ram_size;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ int increment_size = 20;
+ uint8_t *storage_keys;
+ void *virtio_region;
+ hwaddr virtio_region_len;
+ hwaddr virtio_region_start;
+
+ /*
+ * The storage increment size is a multiple of 1M and is a power of 2.
+ * The number of storage increments must be MAX_STORAGE_INCREMENTS or
+ * fewer.
+ */
+ while ((my_ram_size >> increment_size) > MAX_STORAGE_INCREMENTS) {
+ increment_size++;
+ }
+ my_ram_size = my_ram_size >> increment_size << increment_size;
+
+ /* let's propagate the changed ram size into the global variable. */
+ ram_size = my_ram_size;
+
+ /* get a BUS */
+ s390_bus = s390_virtio_bus_init(&my_ram_size);
+ s390_sclp_init();
+ s390_init_ipl_dev(machine->kernel_filename, machine->kernel_cmdline,
+ machine->initrd_filename, ZIPL_FILENAME, false);
+ s390_flic_init();
+
+ /* register hypercalls */
+ s390_virtio_register_hcalls();
+
+ /* allocate RAM */
+ memory_region_init_ram(ram, NULL, "s390.ram", my_ram_size, &error_abort);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(sysmem, 0, ram);
+
+ /* clear virtio region */
+ virtio_region_len = my_ram_size - ram_size;
+ virtio_region_start = ram_size;
+ virtio_region = cpu_physical_memory_map(virtio_region_start,
+ &virtio_region_len, true);
+ memset(virtio_region, 0, virtio_region_len);
+ cpu_physical_memory_unmap(virtio_region, virtio_region_len, 1,
+ virtio_region_len);
+
+ /* allocate storage keys */
+ storage_keys = g_malloc0(my_ram_size / TARGET_PAGE_SIZE);
+
+ /* init CPUs */
+ s390_init_cpus(machine->cpu_model, storage_keys);
+
+ /* Create VirtIO network adapters */
+ s390_create_virtio_net((BusState *)s390_bus, "virtio-net-s390");
+
+ /* Register savevm handler for guest TOD clock */
+ register_savevm(NULL, "todclock", 0, 1, gtod_save, gtod_load, NULL);
+}
+
+void s390_nmi(NMIState *n, int cpu_index, Error **errp)
+{
+ CPUState *cs = qemu_get_cpu(cpu_index);
+
+ if (s390_cpu_restart(S390_CPU(cs))) {
+ error_setg(errp, QERR_UNSUPPORTED);
+ }
+}
+
+static void s390_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ NMIClass *nc = NMI_CLASS(oc);
+
+ mc->name = "s390-virtio";
+ mc->alias = "s390";
+ mc->desc = "VirtIO based S390 machine";
+ mc->init = s390_init;
+ mc->block_default_type = IF_VIRTIO;
+ mc->max_cpus = 255;
+ mc->no_serial = 1;
+ mc->no_parallel = 1;
+ mc->use_virtcon = 1;
+ mc->no_floppy = 1;
+ mc->no_cdrom = 1;
+ mc->no_sdcard = 1;
+ nc->nmi_monitor_handler = s390_nmi;
+}
+
+static const TypeInfo s390_machine_info = {
+ .name = TYPE_S390_MACHINE,
+ .parent = TYPE_MACHINE,
+ .class_init = s390_machine_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_NMI },
+ { }
+ },
+};
+
+static void s390_machine_register_types(void)
+{
+ type_register_static(&s390_machine_info);
+}
+
+type_init(s390_machine_register_types)
diff --git a/hw/s390x/s390-virtio.h b/hw/s390x/s390-virtio.h
new file mode 100644
index 00000000..c8478539
--- /dev/null
+++ b/hw/s390x/s390-virtio.h
@@ -0,0 +1,30 @@
+/*
+ * Virtio interfaces for s390
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef HW_S390_VIRTIO_H
+#define HW_S390_VIRTIO_H 1
+
+#include "hw/nmi.h"
+#include "standard-headers/asm-s390/kvm_virtio.h"
+#include "standard-headers/asm-s390/virtio-ccw.h"
+
+typedef int (*s390_virtio_fn)(const uint64_t *args);
+void s390_register_virtio_hypercall(uint64_t code, s390_virtio_fn fn);
+
+void s390_init_cpus(const char *cpu_model, uint8_t *storage_keys);
+void s390_init_ipl_dev(const char *kernel_filename,
+ const char *kernel_cmdline,
+ const char *initrd_filename,
+ const char *firmware,
+ bool enforce_bios);
+void s390_create_virtio_net(BusState *bus, const char *name);
+void s390_nmi(NMIState *n, int cpu_index, Error **errp);
+#endif
diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c
new file mode 100644
index 00000000..b3a6c5e5
--- /dev/null
+++ b/hw/s390x/sclp.c
@@ -0,0 +1,479 @@
+/*
+ * SCLP Support
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Christian Borntraeger <borntraeger@de.ibm.com>
+ * Heinz Graalfs <graalfs@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "cpu.h"
+#include "sysemu/kvm.h"
+#include "exec/memory.h"
+#include "sysemu/sysemu.h"
+#include "exec/address-spaces.h"
+#include "qemu/config-file.h"
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/event-facility.h"
+#include "hw/s390x/s390-pci-bus.h"
+
+static inline SCLPEventFacility *get_event_facility(void)
+{
+ ObjectProperty *op = object_property_find(qdev_get_machine(),
+ TYPE_SCLP_EVENT_FACILITY,
+ NULL);
+ assert(op);
+ return op->opaque;
+}
+
+/* Provide information about the configuration, CPUs and storage */
+static void read_SCP_info(SCCB *sccb)
+{
+ ReadInfo *read_info = (ReadInfo *) sccb;
+ sclpMemoryHotplugDev *mhd = get_sclp_memory_hotplug_dev();
+ CPUState *cpu;
+ int cpu_count = 0;
+ int i = 0;
+ int increment_size = 20;
+ int rnsize, rnmax;
+ QemuOpts *opts = qemu_opts_find(qemu_find_opts("memory"), NULL);
+ int slots = qemu_opt_get_number(opts, "slots", 0);
+ int max_avail_slots = s390_get_memslot_count(kvm_state);
+
+ if (slots > max_avail_slots) {
+ slots = max_avail_slots;
+ }
+
+ CPU_FOREACH(cpu) {
+ cpu_count++;
+ }
+
+ /* CPU information */
+ read_info->entries_cpu = cpu_to_be16(cpu_count);
+ read_info->offset_cpu = cpu_to_be16(offsetof(ReadInfo, entries));
+ read_info->highest_cpu = cpu_to_be16(max_cpus);
+
+ for (i = 0; i < cpu_count; i++) {
+ read_info->entries[i].address = i;
+ read_info->entries[i].type = 0;
+ }
+
+ read_info->facilities = cpu_to_be64(SCLP_HAS_CPU_INFO |
+ SCLP_HAS_PCI_RECONFIG);
+
+ /*
+ * The storage increment size is a multiple of 1M and is a power of 2.
+ * The number of storage increments must be MAX_STORAGE_INCREMENTS or fewer.
+ */
+ while ((ram_size >> increment_size) > MAX_STORAGE_INCREMENTS) {
+ increment_size++;
+ }
+ rnmax = ram_size >> increment_size;
+
+ /* Memory Hotplug is only supported for the ccw machine type */
+ if (mhd) {
+ while ((mhd->standby_mem_size >> increment_size) >
+ MAX_STORAGE_INCREMENTS) {
+ increment_size++;
+ }
+ assert(increment_size == mhd->increment_size);
+
+ mhd->standby_subregion_size = MEM_SECTION_SIZE;
+ /* Deduct the memory slot already used for core */
+ if (slots > 0) {
+ while ((mhd->standby_subregion_size * (slots - 1)
+ < mhd->standby_mem_size)) {
+ mhd->standby_subregion_size = mhd->standby_subregion_size << 1;
+ }
+ }
+ /*
+ * Initialize mapping of guest standby memory sections indicating which
+ * are and are not online. Assume all standby memory begins offline.
+ */
+ if (mhd->standby_state_map == 0) {
+ if (mhd->standby_mem_size % mhd->standby_subregion_size) {
+ mhd->standby_state_map = g_malloc0((mhd->standby_mem_size /
+ mhd->standby_subregion_size + 1) *
+ (mhd->standby_subregion_size /
+ MEM_SECTION_SIZE));
+ } else {
+ mhd->standby_state_map = g_malloc0(mhd->standby_mem_size /
+ MEM_SECTION_SIZE);
+ }
+ }
+ mhd->padded_ram_size = ram_size + mhd->pad_size;
+ mhd->rzm = 1 << mhd->increment_size;
+ rnmax = ((ram_size + mhd->standby_mem_size + mhd->pad_size)
+ >> mhd->increment_size);
+
+ read_info->facilities |= cpu_to_be64(SCLP_FC_ASSIGN_ATTACH_READ_STOR);
+ }
+
+ rnsize = 1 << (increment_size - 20);
+ if (rnsize <= 128) {
+ read_info->rnsize = rnsize;
+ } else {
+ read_info->rnsize = 0;
+ read_info->rnsize2 = cpu_to_be32(rnsize);
+ }
+
+ if (rnmax < 0x10000) {
+ read_info->rnmax = cpu_to_be16(rnmax);
+ } else {
+ read_info->rnmax = cpu_to_be16(0);
+ read_info->rnmax2 = cpu_to_be64(rnmax);
+ }
+
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_READ_COMPLETION);
+}
+
+static void read_storage_element0_info(SCCB *sccb)
+{
+ int i, assigned;
+ int subincrement_id = SCLP_STARTING_SUBINCREMENT_ID;
+ ReadStorageElementInfo *storage_info = (ReadStorageElementInfo *) sccb;
+ sclpMemoryHotplugDev *mhd = get_sclp_memory_hotplug_dev();
+
+ assert(mhd);
+
+ if ((ram_size >> mhd->increment_size) >= 0x10000) {
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_SCCB_BOUNDARY_VIOLATION);
+ return;
+ }
+
+ /* Return information regarding core memory */
+ storage_info->max_id = cpu_to_be16(mhd->standby_mem_size ? 1 : 0);
+ assigned = ram_size >> mhd->increment_size;
+ storage_info->assigned = cpu_to_be16(assigned);
+
+ for (i = 0; i < assigned; i++) {
+ storage_info->entries[i] = cpu_to_be32(subincrement_id);
+ subincrement_id += SCLP_INCREMENT_UNIT;
+ }
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_READ_COMPLETION);
+}
+
+static void read_storage_element1_info(SCCB *sccb)
+{
+ ReadStorageElementInfo *storage_info = (ReadStorageElementInfo *) sccb;
+ sclpMemoryHotplugDev *mhd = get_sclp_memory_hotplug_dev();
+
+ assert(mhd);
+
+ if ((mhd->standby_mem_size >> mhd->increment_size) >= 0x10000) {
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_SCCB_BOUNDARY_VIOLATION);
+ return;
+ }
+
+ /* Return information regarding standby memory */
+ storage_info->max_id = cpu_to_be16(mhd->standby_mem_size ? 1 : 0);
+ storage_info->assigned = cpu_to_be16(mhd->standby_mem_size >>
+ mhd->increment_size);
+ storage_info->standby = cpu_to_be16(mhd->standby_mem_size >>
+ mhd->increment_size);
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_STANDBY_READ_COMPLETION);
+}
+
+static void attach_storage_element(SCCB *sccb, uint16_t element)
+{
+ int i, assigned, subincrement_id;
+ AttachStorageElement *attach_info = (AttachStorageElement *) sccb;
+ sclpMemoryHotplugDev *mhd = get_sclp_memory_hotplug_dev();
+
+ assert(mhd);
+
+ if (element != 1) {
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_SCLP_COMMAND);
+ return;
+ }
+
+ assigned = mhd->standby_mem_size >> mhd->increment_size;
+ attach_info->assigned = cpu_to_be16(assigned);
+ subincrement_id = ((ram_size >> mhd->increment_size) << 16)
+ + SCLP_STARTING_SUBINCREMENT_ID;
+ for (i = 0; i < assigned; i++) {
+ attach_info->entries[i] = cpu_to_be32(subincrement_id);
+ subincrement_id += SCLP_INCREMENT_UNIT;
+ }
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_COMPLETION);
+}
+
+static void assign_storage(SCCB *sccb)
+{
+ MemoryRegion *mr = NULL;
+ uint64_t this_subregion_size;
+ AssignStorage *assign_info = (AssignStorage *) sccb;
+ sclpMemoryHotplugDev *mhd = get_sclp_memory_hotplug_dev();
+ assert(mhd);
+ ram_addr_t assign_addr = (assign_info->rn - 1) * mhd->rzm;
+ MemoryRegion *sysmem = get_system_memory();
+
+ if ((assign_addr % MEM_SECTION_SIZE == 0) &&
+ (assign_addr >= mhd->padded_ram_size)) {
+ /* Re-use existing memory region if found */
+ mr = memory_region_find(sysmem, assign_addr, 1).mr;
+ if (!mr) {
+
+ MemoryRegion *standby_ram = g_new(MemoryRegion, 1);
+
+ /* offset to align to standby_subregion_size for allocation */
+ ram_addr_t offset = assign_addr -
+ (assign_addr - mhd->padded_ram_size)
+ % mhd->standby_subregion_size;
+
+ /* strlen("standby.ram") + 4 (Max of KVM_MEMORY_SLOTS) + NULL */
+ char id[16];
+ snprintf(id, 16, "standby.ram%d",
+ (int)((offset - mhd->padded_ram_size) /
+ mhd->standby_subregion_size) + 1);
+
+ /* Allocate a subregion of the calculated standby_subregion_size */
+ if (offset + mhd->standby_subregion_size >
+ mhd->padded_ram_size + mhd->standby_mem_size) {
+ this_subregion_size = mhd->padded_ram_size +
+ mhd->standby_mem_size - offset;
+ } else {
+ this_subregion_size = mhd->standby_subregion_size;
+ }
+
+ memory_region_init_ram(standby_ram, NULL, id, this_subregion_size, &error_abort);
+ vmstate_register_ram_global(standby_ram);
+ memory_region_add_subregion(sysmem, offset, standby_ram);
+ }
+ /* The specified subregion is no longer in standby */
+ mhd->standby_state_map[(assign_addr - mhd->padded_ram_size)
+ / MEM_SECTION_SIZE] = 1;
+ }
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_COMPLETION);
+}
+
+static void unassign_storage(SCCB *sccb)
+{
+ MemoryRegion *mr = NULL;
+ AssignStorage *assign_info = (AssignStorage *) sccb;
+ sclpMemoryHotplugDev *mhd = get_sclp_memory_hotplug_dev();
+ assert(mhd);
+ ram_addr_t unassign_addr = (assign_info->rn - 1) * mhd->rzm;
+ MemoryRegion *sysmem = get_system_memory();
+
+ /* if the addr is a multiple of 256 MB */
+ if ((unassign_addr % MEM_SECTION_SIZE == 0) &&
+ (unassign_addr >= mhd->padded_ram_size)) {
+ mhd->standby_state_map[(unassign_addr -
+ mhd->padded_ram_size) / MEM_SECTION_SIZE] = 0;
+
+ /* find the specified memory region and destroy it */
+ mr = memory_region_find(sysmem, unassign_addr, 1).mr;
+ if (mr) {
+ int i;
+ int is_removable = 1;
+ ram_addr_t map_offset = (unassign_addr - mhd->padded_ram_size -
+ (unassign_addr - mhd->padded_ram_size)
+ % mhd->standby_subregion_size);
+ /* Mark all affected subregions as 'standby' once again */
+ for (i = 0;
+ i < (mhd->standby_subregion_size / MEM_SECTION_SIZE);
+ i++) {
+
+ if (mhd->standby_state_map[i + map_offset / MEM_SECTION_SIZE]) {
+ is_removable = 0;
+ break;
+ }
+ }
+ if (is_removable) {
+ memory_region_del_subregion(sysmem, mr);
+ object_unparent(OBJECT(mr));
+ g_free(mr);
+ }
+ }
+ }
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_COMPLETION);
+}
+
+/* Provide information about the CPU */
+static void sclp_read_cpu_info(SCCB *sccb)
+{
+ ReadCpuInfo *cpu_info = (ReadCpuInfo *) sccb;
+ CPUState *cpu;
+ int cpu_count = 0;
+ int i = 0;
+
+ CPU_FOREACH(cpu) {
+ cpu_count++;
+ }
+
+ cpu_info->nr_configured = cpu_to_be16(cpu_count);
+ cpu_info->offset_configured = cpu_to_be16(offsetof(ReadCpuInfo, entries));
+ cpu_info->nr_standby = cpu_to_be16(0);
+
+ /* The standby offset is 16-byte for each CPU */
+ cpu_info->offset_standby = cpu_to_be16(cpu_info->offset_configured
+ + cpu_info->nr_configured*sizeof(CPUEntry));
+
+ for (i = 0; i < cpu_count; i++) {
+ cpu_info->entries[i].address = i;
+ cpu_info->entries[i].type = 0;
+ }
+
+ sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_READ_COMPLETION);
+}
+
+static void sclp_execute(SCCB *sccb, uint32_t code)
+{
+ SCLPEventFacility *ef = get_event_facility();
+ SCLPEventFacilityClass *efc = EVENT_FACILITY_GET_CLASS(ef);
+
+ switch (code & SCLP_CMD_CODE_MASK) {
+ case SCLP_CMDW_READ_SCP_INFO:
+ case SCLP_CMDW_READ_SCP_INFO_FORCED:
+ read_SCP_info(sccb);
+ break;
+ case SCLP_CMDW_READ_CPU_INFO:
+ sclp_read_cpu_info(sccb);
+ break;
+ case SCLP_READ_STORAGE_ELEMENT_INFO:
+ if (code & 0xff00) {
+ read_storage_element1_info(sccb);
+ } else {
+ read_storage_element0_info(sccb);
+ }
+ break;
+ case SCLP_ATTACH_STORAGE_ELEMENT:
+ attach_storage_element(sccb, (code & 0xff00) >> 8);
+ break;
+ case SCLP_ASSIGN_STORAGE:
+ assign_storage(sccb);
+ break;
+ case SCLP_UNASSIGN_STORAGE:
+ unassign_storage(sccb);
+ break;
+ case SCLP_CMDW_CONFIGURE_PCI:
+ s390_pci_sclp_configure(1, sccb);
+ break;
+ case SCLP_CMDW_DECONFIGURE_PCI:
+ s390_pci_sclp_configure(0, sccb);
+ break;
+ default:
+ efc->command_handler(ef, sccb, code);
+ break;
+ }
+}
+
+int sclp_service_call(CPUS390XState *env, uint64_t sccb, uint32_t code)
+{
+ int r = 0;
+ SCCB work_sccb;
+
+ hwaddr sccb_len = sizeof(SCCB);
+
+ /* first some basic checks on program checks */
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ r = -PGM_PRIVILEGED;
+ goto out;
+ }
+ if (cpu_physical_memory_is_io(sccb)) {
+ r = -PGM_ADDRESSING;
+ goto out;
+ }
+ if ((sccb & ~0x1fffUL) == 0 || (sccb & ~0x1fffUL) == env->psa
+ || (sccb & ~0x7ffffff8UL) != 0) {
+ r = -PGM_SPECIFICATION;
+ goto out;
+ }
+
+ /*
+ * we want to work on a private copy of the sccb, to prevent guests
+ * from playing dirty tricks by modifying the memory content after
+ * the host has checked the values
+ */
+ cpu_physical_memory_read(sccb, &work_sccb, sccb_len);
+
+ /* Valid sccb sizes */
+ if (be16_to_cpu(work_sccb.h.length) < sizeof(SCCBHeader) ||
+ be16_to_cpu(work_sccb.h.length) > SCCB_SIZE) {
+ r = -PGM_SPECIFICATION;
+ goto out;
+ }
+
+ sclp_execute((SCCB *)&work_sccb, code);
+
+ cpu_physical_memory_write(sccb, &work_sccb,
+ be16_to_cpu(work_sccb.h.length));
+
+ sclp_service_interrupt(sccb);
+
+out:
+ return r;
+}
+
+void sclp_service_interrupt(uint32_t sccb)
+{
+ SCLPEventFacility *ef = get_event_facility();
+ SCLPEventFacilityClass *efc = EVENT_FACILITY_GET_CLASS(ef);
+
+ uint32_t param = sccb & ~3;
+
+ /* Indicate whether an event is still pending */
+ param |= efc->event_pending(ef) ? 1 : 0;
+
+ if (!param) {
+ /* No need to send an interrupt, there's nothing to be notified about */
+ return;
+ }
+ s390_sclp_extint(param);
+}
+
+/* qemu object creation and initialization functions */
+
+void s390_sclp_init(void)
+{
+ DeviceState *dev = qdev_create(NULL, TYPE_SCLP_EVENT_FACILITY);
+
+ object_property_add_child(qdev_get_machine(), TYPE_SCLP_EVENT_FACILITY,
+ OBJECT(dev), NULL);
+ qdev_init_nofail(dev);
+}
+
+sclpMemoryHotplugDev *init_sclp_memory_hotplug_dev(void)
+{
+ DeviceState *dev;
+ dev = qdev_create(NULL, TYPE_SCLP_MEMORY_HOTPLUG_DEV);
+ object_property_add_child(qdev_get_machine(),
+ TYPE_SCLP_MEMORY_HOTPLUG_DEV,
+ OBJECT(dev), NULL);
+ qdev_init_nofail(dev);
+ return SCLP_MEMORY_HOTPLUG_DEV(object_resolve_path(
+ TYPE_SCLP_MEMORY_HOTPLUG_DEV, NULL));
+}
+
+sclpMemoryHotplugDev *get_sclp_memory_hotplug_dev(void)
+{
+ return SCLP_MEMORY_HOTPLUG_DEV(object_resolve_path(
+ TYPE_SCLP_MEMORY_HOTPLUG_DEV, NULL));
+}
+
+static void sclp_memory_hotplug_dev_class_init(ObjectClass *klass,
+ void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static TypeInfo sclp_memory_hotplug_dev_info = {
+ .name = TYPE_SCLP_MEMORY_HOTPLUG_DEV,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(sclpMemoryHotplugDev),
+ .class_init = sclp_memory_hotplug_dev_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&sclp_memory_hotplug_dev_info);
+}
+type_init(register_types);
diff --git a/hw/s390x/sclpcpu.c b/hw/s390x/sclpcpu.c
new file mode 100644
index 00000000..2fe8b5aa
--- /dev/null
+++ b/hw/s390x/sclpcpu.c
@@ -0,0 +1,114 @@
+/*
+ * SCLP event type
+ * Signal CPU - Trigger SCLP interrupt for system CPU configure or
+ * de-configure
+ *
+ * Copyright IBM, Corp. 2013
+ *
+ * Authors:
+ * Thang Pham <thang.pham@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+#include "sysemu/sysemu.h"
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/event-facility.h"
+#include "cpu.h"
+#include "sysemu/cpus.h"
+#include "sysemu/kvm.h"
+
+typedef struct ConfigMgtData {
+ EventBufferHeader ebh;
+ uint8_t reserved;
+ uint8_t event_qualifier;
+} QEMU_PACKED ConfigMgtData;
+
+static qemu_irq *irq_cpu_hotplug; /* Only used in this file */
+
+#define EVENT_QUAL_CPU_CHANGE 1
+
+void raise_irq_cpu_hotplug(void)
+{
+ qemu_irq_raise(*irq_cpu_hotplug);
+}
+
+static unsigned int send_mask(void)
+{
+ return SCLP_EVENT_MASK_CONFIG_MGT_DATA;
+}
+
+static unsigned int receive_mask(void)
+{
+ return 0;
+}
+
+static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
+ int *slen)
+{
+ ConfigMgtData *cdata = (ConfigMgtData *) evt_buf_hdr;
+ if (*slen < sizeof(ConfigMgtData)) {
+ return 0;
+ }
+
+ /* Event is no longer pending */
+ if (!event->event_pending) {
+ return 0;
+ }
+ event->event_pending = false;
+
+ /* Event header data */
+ cdata->ebh.length = cpu_to_be16(sizeof(ConfigMgtData));
+ cdata->ebh.type = SCLP_EVENT_CONFIG_MGT_DATA;
+ cdata->ebh.flags |= SCLP_EVENT_BUFFER_ACCEPTED;
+
+ /* Trigger a rescan of CPUs by setting event qualifier */
+ cdata->event_qualifier = EVENT_QUAL_CPU_CHANGE;
+ *slen -= sizeof(ConfigMgtData);
+
+ return 1;
+}
+
+static void trigger_signal(void *opaque, int n, int level)
+{
+ SCLPEvent *event = opaque;
+ event->event_pending = true;
+
+ /* Trigger SCLP read operation */
+ sclp_service_interrupt(0);
+}
+
+static int irq_cpu_hotplug_init(SCLPEvent *event)
+{
+ irq_cpu_hotplug = qemu_allocate_irqs(trigger_signal, event, 1);
+ return 0;
+}
+
+static void cpu_class_init(ObjectClass *oc, void *data)
+{
+ SCLPEventClass *k = SCLP_EVENT_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ k->init = irq_cpu_hotplug_init;
+ k->get_send_mask = send_mask;
+ k->get_receive_mask = receive_mask;
+ k->read_event_data = read_event_data;
+ k->write_event_data = NULL;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo sclp_cpu_info = {
+ .name = "sclp-cpu-hotplug",
+ .parent = TYPE_SCLP_EVENT,
+ .instance_size = sizeof(SCLPEvent),
+ .class_init = cpu_class_init,
+ .class_size = sizeof(SCLPEventClass),
+};
+
+static void sclp_cpu_register_types(void)
+{
+ type_register_static(&sclp_cpu_info);
+}
+
+type_init(sclp_cpu_register_types)
diff --git a/hw/s390x/sclpquiesce.c b/hw/s390x/sclpquiesce.c
new file mode 100644
index 00000000..ffa55531
--- /dev/null
+++ b/hw/s390x/sclpquiesce.c
@@ -0,0 +1,142 @@
+/*
+ * SCLP event type
+ * Signal Quiesce - trigger system powerdown request
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Heinz Graalfs <graalfs@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+#include <hw/qdev.h>
+#include "sysemu/sysemu.h"
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/event-facility.h"
+
+typedef struct SignalQuiesce {
+ EventBufferHeader ebh;
+ uint16_t timeout;
+ uint8_t unit;
+} QEMU_PACKED SignalQuiesce;
+
+static bool can_handle_event(uint8_t type)
+{
+ return type == SCLP_EVENT_SIGNAL_QUIESCE;
+}
+
+static unsigned int send_mask(void)
+{
+ return SCLP_EVENT_MASK_SIGNAL_QUIESCE;
+}
+
+static unsigned int receive_mask(void)
+{
+ return 0;
+}
+
+static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
+ int *slen)
+{
+ SignalQuiesce *sq = (SignalQuiesce *) evt_buf_hdr;
+
+ if (*slen < sizeof(SignalQuiesce)) {
+ return 0;
+ }
+
+ if (!event->event_pending) {
+ return 0;
+ }
+ event->event_pending = false;
+
+ sq->ebh.length = cpu_to_be16(sizeof(SignalQuiesce));
+ sq->ebh.type = SCLP_EVENT_SIGNAL_QUIESCE;
+ sq->ebh.flags |= SCLP_EVENT_BUFFER_ACCEPTED;
+ /*
+ * system_powerdown does not have a timeout. Fortunately the
+ * timeout value is currently ignored by Linux, anyway
+ */
+ sq->timeout = cpu_to_be16(0);
+ sq->unit = cpu_to_be16(0);
+ *slen -= sizeof(SignalQuiesce);
+
+ return 1;
+}
+
+static const VMStateDescription vmstate_sclpquiesce = {
+ .name = "sclpquiesce",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(event_pending, SCLPEvent),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef struct QuiesceNotifier QuiesceNotifier;
+
+static struct QuiesceNotifier {
+ Notifier notifier;
+ SCLPEvent *event;
+} qn;
+
+static void quiesce_powerdown_req(Notifier *n, void *opaque)
+{
+ QuiesceNotifier *qn = container_of(n, QuiesceNotifier, notifier);
+ SCLPEvent *event = qn->event;
+
+ event->event_pending = true;
+ /* trigger SCLP read operation */
+ sclp_service_interrupt(0);
+}
+
+static int quiesce_init(SCLPEvent *event)
+{
+ qn.notifier.notify = quiesce_powerdown_req;
+ qn.event = event;
+
+ qemu_register_powerdown_notifier(&qn.notifier);
+
+ return 0;
+}
+
+static void quiesce_reset(DeviceState *dev)
+{
+ SCLPEvent *event = SCLP_EVENT(dev);
+
+ event->event_pending = false;
+}
+
+static void quiesce_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCLPEventClass *k = SCLP_EVENT_CLASS(klass);
+
+ dc->reset = quiesce_reset;
+ dc->vmsd = &vmstate_sclpquiesce;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ k->init = quiesce_init;
+
+ k->get_send_mask = send_mask;
+ k->get_receive_mask = receive_mask;
+ k->can_handle_event = can_handle_event;
+ k->read_event_data = read_event_data;
+ k->write_event_data = NULL;
+}
+
+static const TypeInfo sclp_quiesce_info = {
+ .name = "sclpquiesce",
+ .parent = TYPE_SCLP_EVENT,
+ .instance_size = sizeof(SCLPEvent),
+ .class_init = quiesce_class_init,
+ .class_size = sizeof(SCLPEventClass),
+};
+
+static void register_types(void)
+{
+ type_register_static(&sclp_quiesce_info);
+}
+
+type_init(register_types)
diff --git a/hw/s390x/virtio-ccw.c b/hw/s390x/virtio-ccw.c
new file mode 100644
index 00000000..d36373e8
--- /dev/null
+++ b/hw/s390x/virtio-ccw.c
@@ -0,0 +1,1948 @@
+/*
+ * virtio ccw target implementation
+ *
+ * Copyright 2012,2015 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ * Pierre Morel <pmorel@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/sysemu.h"
+#include "net/net.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-serial.h"
+#include "hw/virtio/virtio-net.h"
+#include "hw/sysbus.h"
+#include "qemu/bitops.h"
+#include "qemu/error-report.h"
+#include "hw/virtio/virtio-access.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/s390x/adapter.h"
+#include "hw/s390x/s390_flic.h"
+
+#include "ioinst.h"
+#include "css.h"
+#include "virtio-ccw.h"
+#include "trace.h"
+
+static QTAILQ_HEAD(, IndAddr) indicator_addresses =
+ QTAILQ_HEAD_INITIALIZER(indicator_addresses);
+
+static IndAddr *get_indicator(hwaddr ind_addr, int len)
+{
+ IndAddr *indicator;
+
+ QTAILQ_FOREACH(indicator, &indicator_addresses, sibling) {
+ if (indicator->addr == ind_addr) {
+ indicator->refcnt++;
+ return indicator;
+ }
+ }
+ indicator = g_new0(IndAddr, 1);
+ indicator->addr = ind_addr;
+ indicator->len = len;
+ indicator->refcnt = 1;
+ QTAILQ_INSERT_TAIL(&indicator_addresses, indicator, sibling);
+ return indicator;
+}
+
+static int s390_io_adapter_map(AdapterInfo *adapter, uint64_t map_addr,
+ bool do_map)
+{
+ S390FLICState *fs = s390_get_flic();
+ S390FLICStateClass *fsc = S390_FLIC_COMMON_GET_CLASS(fs);
+
+ return fsc->io_adapter_map(fs, adapter->adapter_id, map_addr, do_map);
+}
+
+static void release_indicator(AdapterInfo *adapter, IndAddr *indicator)
+{
+ assert(indicator->refcnt > 0);
+ indicator->refcnt--;
+ if (indicator->refcnt > 0) {
+ return;
+ }
+ QTAILQ_REMOVE(&indicator_addresses, indicator, sibling);
+ if (indicator->map) {
+ s390_io_adapter_map(adapter, indicator->map, false);
+ }
+ g_free(indicator);
+}
+
+static int map_indicator(AdapterInfo *adapter, IndAddr *indicator)
+{
+ int ret;
+
+ if (indicator->map) {
+ return 0; /* already mapped is not an error */
+ }
+ indicator->map = indicator->addr;
+ ret = s390_io_adapter_map(adapter, indicator->map, true);
+ if ((ret != 0) && (ret != -ENOSYS)) {
+ goto out_err;
+ }
+ return 0;
+
+out_err:
+ indicator->map = 0;
+ return ret;
+}
+
+static void virtio_ccw_bus_new(VirtioBusState *bus, size_t bus_size,
+ VirtioCcwDevice *dev);
+
+static void virtual_css_bus_reset(BusState *qbus)
+{
+ /* This should actually be modelled via the generic css */
+ css_reset();
+}
+
+
+static void virtual_css_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+
+ k->reset = virtual_css_bus_reset;
+}
+
+static const TypeInfo virtual_css_bus_info = {
+ .name = TYPE_VIRTUAL_CSS_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(VirtualCssBus),
+ .class_init = virtual_css_bus_class_init,
+};
+
+VirtIODevice *virtio_ccw_get_vdev(SubchDev *sch)
+{
+ VirtIODevice *vdev = NULL;
+ VirtioCcwDevice *dev = sch->driver_data;
+
+ if (dev) {
+ vdev = virtio_bus_get_device(&dev->bus);
+ }
+ return vdev;
+}
+
+static int virtio_ccw_set_guest2host_notifier(VirtioCcwDevice *dev, int n,
+ bool assign, bool set_handler)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_host_notifier(vq);
+ int r = 0;
+ SubchDev *sch = dev->sch;
+ uint32_t sch_id = (css_build_subchannel_id(sch) << 16) | sch->schid;
+
+ if (assign) {
+ r = event_notifier_init(notifier, 1);
+ if (r < 0) {
+ error_report("%s: unable to init event notifier: %d", __func__, r);
+ return r;
+ }
+ virtio_queue_set_host_notifier_fd_handler(vq, true, set_handler);
+ r = s390_assign_subch_ioeventfd(notifier, sch_id, n, assign);
+ if (r < 0) {
+ error_report("%s: unable to assign ioeventfd: %d", __func__, r);
+ virtio_queue_set_host_notifier_fd_handler(vq, false, false);
+ event_notifier_cleanup(notifier);
+ return r;
+ }
+ } else {
+ virtio_queue_set_host_notifier_fd_handler(vq, false, false);
+ s390_assign_subch_ioeventfd(notifier, sch_id, n, assign);
+ event_notifier_cleanup(notifier);
+ }
+ return r;
+}
+
+static void virtio_ccw_start_ioeventfd(VirtioCcwDevice *dev)
+{
+ VirtIODevice *vdev;
+ int n, r;
+
+ if (!(dev->flags & VIRTIO_CCW_FLAG_USE_IOEVENTFD) ||
+ dev->ioeventfd_disabled ||
+ dev->ioeventfd_started) {
+ return;
+ }
+ vdev = virtio_bus_get_device(&dev->bus);
+ for (n = 0; n < VIRTIO_CCW_QUEUE_MAX; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+ r = virtio_ccw_set_guest2host_notifier(dev, n, true, true);
+ if (r < 0) {
+ goto assign_error;
+ }
+ }
+ dev->ioeventfd_started = true;
+ return;
+
+ assign_error:
+ while (--n >= 0) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+ r = virtio_ccw_set_guest2host_notifier(dev, n, false, false);
+ assert(r >= 0);
+ }
+ dev->ioeventfd_started = false;
+ /* Disable ioeventfd for this device. */
+ dev->flags &= ~VIRTIO_CCW_FLAG_USE_IOEVENTFD;
+ error_report("%s: failed. Fallback to userspace (slower).", __func__);
+}
+
+static void virtio_ccw_stop_ioeventfd(VirtioCcwDevice *dev)
+{
+ VirtIODevice *vdev;
+ int n, r;
+
+ if (!dev->ioeventfd_started) {
+ return;
+ }
+ vdev = virtio_bus_get_device(&dev->bus);
+ for (n = 0; n < VIRTIO_CCW_QUEUE_MAX; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+ r = virtio_ccw_set_guest2host_notifier(dev, n, false, false);
+ assert(r >= 0);
+ }
+ dev->ioeventfd_started = false;
+}
+
+VirtualCssBus *virtual_css_bus_init(void)
+{
+ VirtualCssBus *cbus;
+ BusState *bus;
+ DeviceState *dev;
+
+ /* Create bridge device */
+ dev = qdev_create(NULL, "virtual-css-bridge");
+ qdev_init_nofail(dev);
+
+ /* Create bus on bridge device */
+ bus = qbus_create(TYPE_VIRTUAL_CSS_BUS, dev, "virtual-css");
+ cbus = VIRTUAL_CSS_BUS(bus);
+
+ /* Enable hotplugging */
+ qbus_set_hotplug_handler(bus, dev, &error_abort);
+
+ return cbus;
+}
+
+/* Communication blocks used by several channel commands. */
+typedef struct VqInfoBlockLegacy {
+ uint64_t queue;
+ uint32_t align;
+ uint16_t index;
+ uint16_t num;
+} QEMU_PACKED VqInfoBlockLegacy;
+
+typedef struct VqInfoBlock {
+ uint64_t desc;
+ uint32_t res0;
+ uint16_t index;
+ uint16_t num;
+ uint64_t avail;
+ uint64_t used;
+} QEMU_PACKED VqInfoBlock;
+
+typedef struct VqConfigBlock {
+ uint16_t index;
+ uint16_t num_max;
+} QEMU_PACKED VqConfigBlock;
+
+typedef struct VirtioFeatDesc {
+ uint32_t features;
+ uint8_t index;
+} QEMU_PACKED VirtioFeatDesc;
+
+typedef struct VirtioThinintInfo {
+ hwaddr summary_indicator;
+ hwaddr device_indicator;
+ uint64_t ind_bit;
+ uint8_t isc;
+} QEMU_PACKED VirtioThinintInfo;
+
+typedef struct VirtioRevInfo {
+ uint16_t revision;
+ uint16_t length;
+ uint8_t data[0];
+} QEMU_PACKED VirtioRevInfo;
+
+/* Specify where the virtqueues for the subchannel are in guest memory. */
+static int virtio_ccw_set_vqs(SubchDev *sch, VqInfoBlock *info,
+ VqInfoBlockLegacy *linfo)
+{
+ VirtIODevice *vdev = virtio_ccw_get_vdev(sch);
+ uint16_t index = info ? info->index : linfo->index;
+ uint16_t num = info ? info->num : linfo->num;
+ uint64_t desc = info ? info->desc : linfo->queue;
+
+ if (index >= VIRTIO_CCW_QUEUE_MAX) {
+ return -EINVAL;
+ }
+
+ /* Current code in virtio.c relies on 4K alignment. */
+ if (linfo && desc && (linfo->align != 4096)) {
+ return -EINVAL;
+ }
+
+ if (!vdev) {
+ return -EINVAL;
+ }
+
+ if (info) {
+ virtio_queue_set_rings(vdev, index, desc, info->avail, info->used);
+ } else {
+ virtio_queue_set_addr(vdev, index, desc);
+ }
+ if (!desc) {
+ virtio_queue_set_vector(vdev, index, VIRTIO_NO_VECTOR);
+ } else {
+ /* Fail if we don't have a big enough queue. */
+ /* TODO: Add interface to handle vring.num changing */
+ if (virtio_queue_get_num(vdev, index) > num) {
+ return -EINVAL;
+ }
+ virtio_queue_set_vector(vdev, index, index);
+ }
+ /* tell notify handler in case of config change */
+ vdev->config_vector = VIRTIO_CCW_QUEUE_MAX;
+ return 0;
+}
+
+static void virtio_ccw_reset_virtio(VirtioCcwDevice *dev, VirtIODevice *vdev)
+{
+ virtio_ccw_stop_ioeventfd(dev);
+ virtio_reset(vdev);
+ if (dev->indicators) {
+ release_indicator(&dev->routes.adapter, dev->indicators);
+ dev->indicators = NULL;
+ }
+ if (dev->indicators2) {
+ release_indicator(&dev->routes.adapter, dev->indicators2);
+ dev->indicators2 = NULL;
+ }
+ if (dev->summary_indicator) {
+ release_indicator(&dev->routes.adapter, dev->summary_indicator);
+ dev->summary_indicator = NULL;
+ }
+ dev->sch->thinint_active = false;
+}
+
+static int virtio_ccw_handle_set_vq(SubchDev *sch, CCW1 ccw, bool check_len,
+ bool is_legacy)
+{
+ int ret;
+ VqInfoBlock info;
+ VqInfoBlockLegacy linfo;
+ size_t info_len = is_legacy ? sizeof(linfo) : sizeof(info);
+
+ if (check_len) {
+ if (ccw.count != info_len) {
+ return -EINVAL;
+ }
+ } else if (ccw.count < info_len) {
+ /* Can't execute command. */
+ return -EINVAL;
+ }
+ if (!ccw.cda) {
+ return -EFAULT;
+ }
+ if (is_legacy) {
+ linfo.queue = address_space_ldq_be(&address_space_memory, ccw.cda,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ linfo.align = address_space_ldl_be(&address_space_memory,
+ ccw.cda + sizeof(linfo.queue),
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ linfo.index = address_space_lduw_be(&address_space_memory,
+ ccw.cda + sizeof(linfo.queue)
+ + sizeof(linfo.align),
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ linfo.num = address_space_lduw_be(&address_space_memory,
+ ccw.cda + sizeof(linfo.queue)
+ + sizeof(linfo.align)
+ + sizeof(linfo.index),
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ ret = virtio_ccw_set_vqs(sch, NULL, &linfo);
+ } else {
+ info.desc = address_space_ldq_be(&address_space_memory, ccw.cda,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ info.index = address_space_lduw_be(&address_space_memory,
+ ccw.cda + sizeof(info.desc)
+ + sizeof(info.res0),
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ info.num = address_space_lduw_be(&address_space_memory,
+ ccw.cda + sizeof(info.desc)
+ + sizeof(info.res0)
+ + sizeof(info.index),
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ info.avail = address_space_ldq_be(&address_space_memory,
+ ccw.cda + sizeof(info.desc)
+ + sizeof(info.res0)
+ + sizeof(info.index)
+ + sizeof(info.num),
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ info.used = address_space_ldq_be(&address_space_memory,
+ ccw.cda + sizeof(info.desc)
+ + sizeof(info.res0)
+ + sizeof(info.index)
+ + sizeof(info.num)
+ + sizeof(info.avail),
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ ret = virtio_ccw_set_vqs(sch, &info, NULL);
+ }
+ sch->curr_status.scsw.count = 0;
+ return ret;
+}
+
+static int virtio_ccw_cb(SubchDev *sch, CCW1 ccw)
+{
+ int ret;
+ VirtioRevInfo revinfo;
+ uint8_t status;
+ VirtioFeatDesc features;
+ void *config;
+ hwaddr indicators;
+ VqConfigBlock vq_config;
+ VirtioCcwDevice *dev = sch->driver_data;
+ VirtIODevice *vdev = virtio_ccw_get_vdev(sch);
+ bool check_len;
+ int len;
+ hwaddr hw_len;
+ VirtioThinintInfo *thinint;
+
+ if (!dev) {
+ return -EINVAL;
+ }
+
+ trace_virtio_ccw_interpret_ccw(sch->cssid, sch->ssid, sch->schid,
+ ccw.cmd_code);
+ check_len = !((ccw.flags & CCW_FLAG_SLI) && !(ccw.flags & CCW_FLAG_DC));
+
+ /* Look at the command. */
+ switch (ccw.cmd_code) {
+ case CCW_CMD_SET_VQ:
+ ret = virtio_ccw_handle_set_vq(sch, ccw, check_len, dev->revision < 1);
+ break;
+ case CCW_CMD_VDEV_RESET:
+ virtio_ccw_reset_virtio(dev, vdev);
+ ret = 0;
+ break;
+ case CCW_CMD_READ_FEAT:
+ if (check_len) {
+ if (ccw.count != sizeof(features)) {
+ ret = -EINVAL;
+ break;
+ }
+ } else if (ccw.count < sizeof(features)) {
+ /* Can't execute command. */
+ ret = -EINVAL;
+ break;
+ }
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else {
+ features.index = address_space_ldub(&address_space_memory,
+ ccw.cda
+ + sizeof(features.features),
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ if (features.index == 0) {
+ features.features = (uint32_t)vdev->host_features;
+ } else if (features.index == 1) {
+ features.features = (uint32_t)(vdev->host_features >> 32);
+ /*
+ * Don't offer version 1 to the guest if it did not
+ * negotiate at least revision 1.
+ */
+ if (dev->revision <= 0) {
+ features.features &= ~(1 << (VIRTIO_F_VERSION_1 - 32));
+ }
+ } else {
+ /* Return zeroes if the guest supports more feature bits. */
+ features.features = 0;
+ }
+ address_space_stl_le(&address_space_memory, ccw.cda,
+ features.features, MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ sch->curr_status.scsw.count = ccw.count - sizeof(features);
+ ret = 0;
+ }
+ break;
+ case CCW_CMD_WRITE_FEAT:
+ if (check_len) {
+ if (ccw.count != sizeof(features)) {
+ ret = -EINVAL;
+ break;
+ }
+ } else if (ccw.count < sizeof(features)) {
+ /* Can't execute command. */
+ ret = -EINVAL;
+ break;
+ }
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else {
+ features.index = address_space_ldub(&address_space_memory,
+ ccw.cda
+ + sizeof(features.features),
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ features.features = address_space_ldl_le(&address_space_memory,
+ ccw.cda,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ if (features.index == 0) {
+ virtio_set_features(vdev,
+ (vdev->guest_features & 0xffffffff00000000ULL) |
+ features.features);
+ } else if (features.index == 1) {
+ /*
+ * The guest should not set version 1 if it didn't
+ * negotiate a revision >= 1.
+ */
+ if (dev->revision <= 0) {
+ features.features &= ~(1 << (VIRTIO_F_VERSION_1 - 32));
+ }
+ virtio_set_features(vdev,
+ (vdev->guest_features & 0x00000000ffffffffULL) |
+ ((uint64_t)features.features << 32));
+ } else {
+ /*
+ * If the guest supports more feature bits, assert that it
+ * passes us zeroes for those we don't support.
+ */
+ if (features.features) {
+ fprintf(stderr, "Guest bug: features[%i]=%x (expected 0)\n",
+ features.index, features.features);
+ /* XXX: do a unit check here? */
+ }
+ }
+ sch->curr_status.scsw.count = ccw.count - sizeof(features);
+ ret = 0;
+ }
+ break;
+ case CCW_CMD_READ_CONF:
+ if (check_len) {
+ if (ccw.count > vdev->config_len) {
+ ret = -EINVAL;
+ break;
+ }
+ }
+ len = MIN(ccw.count, vdev->config_len);
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else {
+ virtio_bus_get_vdev_config(&dev->bus, vdev->config);
+ /* XXX config space endianness */
+ cpu_physical_memory_write(ccw.cda, vdev->config, len);
+ sch->curr_status.scsw.count = ccw.count - len;
+ ret = 0;
+ }
+ break;
+ case CCW_CMD_WRITE_CONF:
+ if (check_len) {
+ if (ccw.count > vdev->config_len) {
+ ret = -EINVAL;
+ break;
+ }
+ }
+ len = MIN(ccw.count, vdev->config_len);
+ hw_len = len;
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else {
+ config = cpu_physical_memory_map(ccw.cda, &hw_len, 0);
+ if (!config) {
+ ret = -EFAULT;
+ } else {
+ len = hw_len;
+ /* XXX config space endianness */
+ memcpy(vdev->config, config, len);
+ cpu_physical_memory_unmap(config, hw_len, 0, hw_len);
+ virtio_bus_set_vdev_config(&dev->bus, vdev->config);
+ sch->curr_status.scsw.count = ccw.count - len;
+ ret = 0;
+ }
+ }
+ break;
+ case CCW_CMD_WRITE_STATUS:
+ if (check_len) {
+ if (ccw.count != sizeof(status)) {
+ ret = -EINVAL;
+ break;
+ }
+ } else if (ccw.count < sizeof(status)) {
+ /* Can't execute command. */
+ ret = -EINVAL;
+ break;
+ }
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else {
+ status = address_space_ldub(&address_space_memory, ccw.cda,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ if (!(status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ virtio_ccw_stop_ioeventfd(dev);
+ }
+ if (virtio_set_status(vdev, status) == 0) {
+ if (vdev->status == 0) {
+ virtio_ccw_reset_virtio(dev, vdev);
+ }
+ if (status & VIRTIO_CONFIG_S_DRIVER_OK) {
+ virtio_ccw_start_ioeventfd(dev);
+ }
+ sch->curr_status.scsw.count = ccw.count - sizeof(status);
+ ret = 0;
+ } else {
+ /* Trigger a command reject. */
+ ret = -ENOSYS;
+ }
+ }
+ break;
+ case CCW_CMD_SET_IND:
+ if (check_len) {
+ if (ccw.count != sizeof(indicators)) {
+ ret = -EINVAL;
+ break;
+ }
+ } else if (ccw.count < sizeof(indicators)) {
+ /* Can't execute command. */
+ ret = -EINVAL;
+ break;
+ }
+ if (sch->thinint_active) {
+ /* Trigger a command reject. */
+ ret = -ENOSYS;
+ break;
+ }
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else {
+ indicators = address_space_ldq_be(&address_space_memory, ccw.cda,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ dev->indicators = get_indicator(indicators, sizeof(uint64_t));
+ sch->curr_status.scsw.count = ccw.count - sizeof(indicators);
+ ret = 0;
+ }
+ break;
+ case CCW_CMD_SET_CONF_IND:
+ if (check_len) {
+ if (ccw.count != sizeof(indicators)) {
+ ret = -EINVAL;
+ break;
+ }
+ } else if (ccw.count < sizeof(indicators)) {
+ /* Can't execute command. */
+ ret = -EINVAL;
+ break;
+ }
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else {
+ indicators = address_space_ldq_be(&address_space_memory, ccw.cda,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ dev->indicators2 = get_indicator(indicators, sizeof(uint64_t));
+ sch->curr_status.scsw.count = ccw.count - sizeof(indicators);
+ ret = 0;
+ }
+ break;
+ case CCW_CMD_READ_VQ_CONF:
+ if (check_len) {
+ if (ccw.count != sizeof(vq_config)) {
+ ret = -EINVAL;
+ break;
+ }
+ } else if (ccw.count < sizeof(vq_config)) {
+ /* Can't execute command. */
+ ret = -EINVAL;
+ break;
+ }
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else {
+ vq_config.index = address_space_lduw_be(&address_space_memory,
+ ccw.cda,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ if (vq_config.index >= VIRTIO_CCW_QUEUE_MAX) {
+ ret = -EINVAL;
+ break;
+ }
+ vq_config.num_max = virtio_queue_get_num(vdev,
+ vq_config.index);
+ address_space_stw_be(&address_space_memory,
+ ccw.cda + sizeof(vq_config.index),
+ vq_config.num_max,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ sch->curr_status.scsw.count = ccw.count - sizeof(vq_config);
+ ret = 0;
+ }
+ break;
+ case CCW_CMD_SET_IND_ADAPTER:
+ if (check_len) {
+ if (ccw.count != sizeof(*thinint)) {
+ ret = -EINVAL;
+ break;
+ }
+ } else if (ccw.count < sizeof(*thinint)) {
+ /* Can't execute command. */
+ ret = -EINVAL;
+ break;
+ }
+ len = sizeof(*thinint);
+ hw_len = len;
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ } else if (dev->indicators && !sch->thinint_active) {
+ /* Trigger a command reject. */
+ ret = -ENOSYS;
+ } else {
+ thinint = cpu_physical_memory_map(ccw.cda, &hw_len, 0);
+ if (!thinint) {
+ ret = -EFAULT;
+ } else {
+ uint64_t ind_bit = ldq_be_p(&thinint->ind_bit);
+
+ len = hw_len;
+ dev->summary_indicator =
+ get_indicator(ldq_be_p(&thinint->summary_indicator),
+ sizeof(uint8_t));
+ dev->indicators =
+ get_indicator(ldq_be_p(&thinint->device_indicator),
+ ind_bit / 8 + 1);
+ dev->thinint_isc = thinint->isc;
+ dev->routes.adapter.ind_offset = ind_bit;
+ dev->routes.adapter.summary_offset = 7;
+ cpu_physical_memory_unmap(thinint, hw_len, 0, hw_len);
+ ret = css_register_io_adapter(CSS_IO_ADAPTER_VIRTIO,
+ dev->thinint_isc, true, false,
+ &dev->routes.adapter.adapter_id);
+ assert(ret == 0);
+ sch->thinint_active = ((dev->indicators != NULL) &&
+ (dev->summary_indicator != NULL));
+ sch->curr_status.scsw.count = ccw.count - len;
+ ret = 0;
+ }
+ }
+ break;
+ case CCW_CMD_SET_VIRTIO_REV:
+ len = sizeof(revinfo);
+ if (ccw.count < len) {
+ ret = -EINVAL;
+ break;
+ }
+ if (!ccw.cda) {
+ ret = -EFAULT;
+ break;
+ }
+ revinfo.revision =
+ address_space_lduw_be(&address_space_memory, ccw.cda,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ revinfo.length =
+ address_space_lduw_be(&address_space_memory,
+ ccw.cda + sizeof(revinfo.revision),
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ if (ccw.count < len + revinfo.length ||
+ (check_len && ccw.count > len + revinfo.length)) {
+ ret = -EINVAL;
+ break;
+ }
+ /*
+ * Once we start to support revisions with additional data, we'll
+ * need to fetch it here. Nothing to do for now, though.
+ */
+ if (dev->revision >= 0 ||
+ revinfo.revision > virtio_ccw_rev_max(vdev)) {
+ ret = -ENOSYS;
+ break;
+ }
+ ret = 0;
+ dev->revision = revinfo.revision;
+ break;
+ default:
+ ret = -ENOSYS;
+ break;
+ }
+ return ret;
+}
+
+static void virtio_sch_disable_cb(SubchDev *sch)
+{
+ VirtioCcwDevice *dev = sch->driver_data;
+
+ dev->revision = -1;
+}
+
+static void virtio_ccw_device_realize(VirtioCcwDevice *dev, Error **errp)
+{
+ unsigned int cssid = 0;
+ unsigned int ssid = 0;
+ unsigned int schid;
+ unsigned int devno;
+ bool have_devno = false;
+ bool found = false;
+ SubchDev *sch;
+ int num;
+ Error *err = NULL;
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_GET_CLASS(dev);
+
+ sch = g_malloc0(sizeof(SubchDev));
+
+ sch->driver_data = dev;
+ dev->sch = sch;
+
+ dev->indicators = NULL;
+
+ /* Initialize subchannel structure. */
+ sch->channel_prog = 0x0;
+ sch->last_cmd_valid = false;
+ sch->thinint_active = false;
+ /*
+ * Use a device number if provided. Otherwise, fall back to subchannel
+ * number.
+ */
+ if (dev->bus_id) {
+ num = sscanf(dev->bus_id, "%x.%x.%04x", &cssid, &ssid, &devno);
+ if (num == 3) {
+ if ((cssid > MAX_CSSID) || (ssid > MAX_SSID)) {
+ error_setg(errp, "Invalid cssid or ssid: cssid %x, ssid %x",
+ cssid, ssid);
+ goto out_err;
+ }
+ /* Enforce use of virtual cssid. */
+ if (cssid != VIRTUAL_CSSID) {
+ error_setg(errp, "cssid %x not valid for virtio devices",
+ cssid);
+ goto out_err;
+ }
+ if (css_devno_used(cssid, ssid, devno)) {
+ error_setg(errp, "Device %x.%x.%04x already exists",
+ cssid, ssid, devno);
+ goto out_err;
+ }
+ sch->cssid = cssid;
+ sch->ssid = ssid;
+ sch->devno = devno;
+ have_devno = true;
+ } else {
+ error_setg(errp, "Malformed devno parameter '%s'", dev->bus_id);
+ goto out_err;
+ }
+ }
+
+ /* Find the next free id. */
+ if (have_devno) {
+ for (schid = 0; schid <= MAX_SCHID; schid++) {
+ if (!css_find_subch(1, cssid, ssid, schid)) {
+ sch->schid = schid;
+ css_subch_assign(cssid, ssid, schid, devno, sch);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ error_setg(errp, "No free subchannel found for %x.%x.%04x",
+ cssid, ssid, devno);
+ goto out_err;
+ }
+ trace_virtio_ccw_new_device(cssid, ssid, schid, devno,
+ "user-configured");
+ } else {
+ cssid = VIRTUAL_CSSID;
+ for (ssid = 0; ssid <= MAX_SSID; ssid++) {
+ for (schid = 0; schid <= MAX_SCHID; schid++) {
+ if (!css_find_subch(1, cssid, ssid, schid)) {
+ sch->cssid = cssid;
+ sch->ssid = ssid;
+ sch->schid = schid;
+ devno = schid;
+ /*
+ * If the devno is already taken, look further in this
+ * subchannel set.
+ */
+ while (css_devno_used(cssid, ssid, devno)) {
+ if (devno == MAX_SCHID) {
+ devno = 0;
+ } else if (devno == schid - 1) {
+ error_setg(errp, "No free devno found");
+ goto out_err;
+ } else {
+ devno++;
+ }
+ }
+ sch->devno = devno;
+ css_subch_assign(cssid, ssid, schid, devno, sch);
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+ if (!found) {
+ error_setg(errp, "Virtual channel subsystem is full!");
+ goto out_err;
+ }
+ trace_virtio_ccw_new_device(cssid, ssid, schid, devno,
+ "auto-configured");
+ }
+
+ /* Build initial schib. */
+ css_sch_build_virtual_schib(sch, 0, VIRTIO_CCW_CHPID_TYPE);
+
+ sch->ccw_cb = virtio_ccw_cb;
+ sch->disable_cb = virtio_sch_disable_cb;
+
+ /* Build senseid data. */
+ memset(&sch->id, 0, sizeof(SenseId));
+ sch->id.reserved = 0xff;
+ sch->id.cu_type = VIRTIO_CCW_CU_TYPE;
+
+ dev->revision = -1;
+
+ if (k->realize) {
+ k->realize(dev, &err);
+ }
+ if (err) {
+ error_propagate(errp, err);
+ css_subch_assign(cssid, ssid, schid, devno, NULL);
+ goto out_err;
+ }
+
+ return;
+
+out_err:
+ dev->sch = NULL;
+ g_free(sch);
+}
+
+static int virtio_ccw_exit(VirtioCcwDevice *dev)
+{
+ SubchDev *sch = dev->sch;
+
+ if (sch) {
+ css_subch_assign(sch->cssid, sch->ssid, sch->schid, sch->devno, NULL);
+ g_free(sch);
+ }
+ if (dev->indicators) {
+ release_indicator(&dev->routes.adapter, dev->indicators);
+ dev->indicators = NULL;
+ }
+ return 0;
+}
+
+static void virtio_ccw_net_realize(VirtioCcwDevice *ccw_dev, Error **errp)
+{
+ DeviceState *qdev = DEVICE(ccw_dev);
+ VirtIONetCcw *dev = VIRTIO_NET_CCW(ccw_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ virtio_net_set_netclient_name(&dev->vdev, qdev->id,
+ object_get_typename(OBJECT(qdev)));
+ qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ }
+}
+
+static void virtio_ccw_net_instance_init(Object *obj)
+{
+ VirtIONetCcw *dev = VIRTIO_NET_CCW(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_NET);
+ object_property_add_alias(obj, "bootindex", OBJECT(&dev->vdev),
+ "bootindex", &error_abort);
+}
+
+static void virtio_ccw_blk_realize(VirtioCcwDevice *ccw_dev, Error **errp)
+{
+ VirtIOBlkCcw *dev = VIRTIO_BLK_CCW(ccw_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ }
+}
+
+static void virtio_ccw_blk_instance_init(Object *obj)
+{
+ VirtIOBlkCcw *dev = VIRTIO_BLK_CCW(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_BLK);
+ object_property_add_alias(obj, "iothread", OBJECT(&dev->vdev),"iothread",
+ &error_abort);
+ object_property_add_alias(obj, "bootindex", OBJECT(&dev->vdev),
+ "bootindex", &error_abort);
+}
+
+static void virtio_ccw_serial_realize(VirtioCcwDevice *ccw_dev, Error **errp)
+{
+ VirtioSerialCcw *dev = VIRTIO_SERIAL_CCW(ccw_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ DeviceState *proxy = DEVICE(ccw_dev);
+ Error *err = NULL;
+ char *bus_name;
+
+ /*
+ * For command line compatibility, this sets the virtio-serial-device bus
+ * name as before.
+ */
+ if (proxy->id) {
+ bus_name = g_strdup_printf("%s.0", proxy->id);
+ virtio_device_set_child_bus_name(VIRTIO_DEVICE(vdev), bus_name);
+ g_free(bus_name);
+ }
+
+ qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ }
+}
+
+
+static void virtio_ccw_serial_instance_init(Object *obj)
+{
+ VirtioSerialCcw *dev = VIRTIO_SERIAL_CCW(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_SERIAL);
+}
+
+static void virtio_ccw_balloon_realize(VirtioCcwDevice *ccw_dev, Error **errp)
+{
+ VirtIOBalloonCcw *dev = VIRTIO_BALLOON_CCW(ccw_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ }
+}
+
+static void virtio_ccw_balloon_instance_init(Object *obj)
+{
+ VirtIOBalloonCcw *dev = VIRTIO_BALLOON_CCW(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_BALLOON);
+ object_property_add_alias(obj, "guest-stats", OBJECT(&dev->vdev),
+ "guest-stats", &error_abort);
+ object_property_add_alias(obj, "guest-stats-polling-interval",
+ OBJECT(&dev->vdev),
+ "guest-stats-polling-interval", &error_abort);
+}
+
+static void virtio_ccw_scsi_realize(VirtioCcwDevice *ccw_dev, Error **errp)
+{
+ VirtIOSCSICcw *dev = VIRTIO_SCSI_CCW(ccw_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ DeviceState *qdev = DEVICE(ccw_dev);
+ Error *err = NULL;
+ char *bus_name;
+
+ /*
+ * For command line compatibility, this sets the virtio-scsi-device bus
+ * name as before.
+ */
+ if (qdev->id) {
+ bus_name = g_strdup_printf("%s.0", qdev->id);
+ virtio_device_set_child_bus_name(VIRTIO_DEVICE(vdev), bus_name);
+ g_free(bus_name);
+ }
+
+ qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ }
+}
+
+static void virtio_ccw_scsi_instance_init(Object *obj)
+{
+ VirtIOSCSICcw *dev = VIRTIO_SCSI_CCW(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_SCSI);
+ object_property_add_alias(obj, "iothread", OBJECT(&dev->vdev), "iothread",
+ &error_abort);
+}
+
+#ifdef CONFIG_VHOST_SCSI
+static void vhost_ccw_scsi_realize(VirtioCcwDevice *ccw_dev, Error **errp)
+{
+ VHostSCSICcw *dev = VHOST_SCSI_CCW(ccw_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ }
+}
+
+static void vhost_ccw_scsi_instance_init(Object *obj)
+{
+ VHostSCSICcw *dev = VHOST_SCSI_CCW(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VHOST_SCSI);
+}
+#endif
+
+static void virtio_ccw_rng_realize(VirtioCcwDevice *ccw_dev, Error **errp)
+{
+ VirtIORNGCcw *dev = VIRTIO_RNG_CCW(ccw_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ object_property_set_link(OBJECT(dev),
+ OBJECT(dev->vdev.conf.rng), "rng",
+ NULL);
+}
+
+/* DeviceState to VirtioCcwDevice. Note: used on datapath,
+ * be careful and test performance if you change this.
+ */
+static inline VirtioCcwDevice *to_virtio_ccw_dev_fast(DeviceState *d)
+{
+ return container_of(d, VirtioCcwDevice, parent_obj);
+}
+
+static uint8_t virtio_set_ind_atomic(SubchDev *sch, uint64_t ind_loc,
+ uint8_t to_be_set)
+{
+ uint8_t ind_old, ind_new;
+ hwaddr len = 1;
+ uint8_t *ind_addr;
+
+ ind_addr = cpu_physical_memory_map(ind_loc, &len, 1);
+ if (!ind_addr) {
+ error_report("%s(%x.%x.%04x): unable to access indicator",
+ __func__, sch->cssid, sch->ssid, sch->schid);
+ return -1;
+ }
+ do {
+ ind_old = *ind_addr;
+ ind_new = ind_old | to_be_set;
+ } while (atomic_cmpxchg(ind_addr, ind_old, ind_new) != ind_old);
+ cpu_physical_memory_unmap(ind_addr, len, 1, len);
+
+ return ind_old;
+}
+
+static void virtio_ccw_notify(DeviceState *d, uint16_t vector)
+{
+ VirtioCcwDevice *dev = to_virtio_ccw_dev_fast(d);
+ SubchDev *sch = dev->sch;
+ uint64_t indicators;
+
+ if (vector >= 128) {
+ return;
+ }
+
+ if (vector < VIRTIO_CCW_QUEUE_MAX) {
+ if (!dev->indicators) {
+ return;
+ }
+ if (sch->thinint_active) {
+ /*
+ * In the adapter interrupt case, indicators points to a
+ * memory area that may be (way) larger than 64 bit and
+ * ind_bit indicates the start of the indicators in a big
+ * endian notation.
+ */
+ uint64_t ind_bit = dev->routes.adapter.ind_offset;
+
+ virtio_set_ind_atomic(sch, dev->indicators->addr +
+ (ind_bit + vector) / 8,
+ 0x80 >> ((ind_bit + vector) % 8));
+ if (!virtio_set_ind_atomic(sch, dev->summary_indicator->addr,
+ 0x01)) {
+ css_adapter_interrupt(dev->thinint_isc);
+ }
+ } else {
+ indicators = address_space_ldq(&address_space_memory,
+ dev->indicators->addr,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ indicators |= 1ULL << vector;
+ address_space_stq(&address_space_memory, dev->indicators->addr,
+ indicators, MEMTXATTRS_UNSPECIFIED, NULL);
+ css_conditional_io_interrupt(sch);
+ }
+ } else {
+ if (!dev->indicators2) {
+ return;
+ }
+ vector = 0;
+ indicators = address_space_ldq(&address_space_memory,
+ dev->indicators2->addr,
+ MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ indicators |= 1ULL << vector;
+ address_space_stq(&address_space_memory, dev->indicators2->addr,
+ indicators, MEMTXATTRS_UNSPECIFIED, NULL);
+ css_conditional_io_interrupt(sch);
+ }
+}
+
+static void virtio_ccw_reset(DeviceState *d)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+
+ virtio_ccw_reset_virtio(dev, vdev);
+ css_reset_sch(dev->sch);
+}
+
+static void virtio_ccw_vmstate_change(DeviceState *d, bool running)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+
+ if (running) {
+ virtio_ccw_start_ioeventfd(dev);
+ } else {
+ virtio_ccw_stop_ioeventfd(dev);
+ }
+}
+
+static bool virtio_ccw_query_guest_notifiers(DeviceState *d)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+
+ return !!(dev->sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_ENA);
+}
+
+static int virtio_ccw_set_host_notifier(DeviceState *d, int n, bool assign)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+
+ /* Stop using the generic ioeventfd, we are doing eventfd handling
+ * ourselves below */
+ dev->ioeventfd_disabled = assign;
+ if (assign) {
+ virtio_ccw_stop_ioeventfd(dev);
+ }
+ return virtio_ccw_set_guest2host_notifier(dev, n, assign, false);
+}
+
+static int virtio_ccw_get_mappings(VirtioCcwDevice *dev)
+{
+ int r;
+
+ if (!dev->sch->thinint_active) {
+ return -EINVAL;
+ }
+
+ r = map_indicator(&dev->routes.adapter, dev->summary_indicator);
+ if (r) {
+ return r;
+ }
+ r = map_indicator(&dev->routes.adapter, dev->indicators);
+ if (r) {
+ return r;
+ }
+ dev->routes.adapter.summary_addr = dev->summary_indicator->map;
+ dev->routes.adapter.ind_addr = dev->indicators->map;
+
+ return 0;
+}
+
+static int virtio_ccw_setup_irqroutes(VirtioCcwDevice *dev, int nvqs)
+{
+ int i;
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ int ret;
+ S390FLICState *fs = s390_get_flic();
+ S390FLICStateClass *fsc = S390_FLIC_COMMON_GET_CLASS(fs);
+
+ ret = virtio_ccw_get_mappings(dev);
+ if (ret) {
+ return ret;
+ }
+ for (i = 0; i < nvqs; i++) {
+ if (!virtio_queue_get_num(vdev, i)) {
+ break;
+ }
+ }
+ dev->routes.num_routes = i;
+ return fsc->add_adapter_routes(fs, &dev->routes);
+}
+
+static void virtio_ccw_release_irqroutes(VirtioCcwDevice *dev, int nvqs)
+{
+ S390FLICState *fs = s390_get_flic();
+ S390FLICStateClass *fsc = S390_FLIC_COMMON_GET_CLASS(fs);
+
+ fsc->release_adapter_routes(fs, &dev->routes);
+}
+
+static int virtio_ccw_add_irqfd(VirtioCcwDevice *dev, int n)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_guest_notifier(vq);
+
+ return kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, notifier, NULL,
+ dev->routes.gsi[n]);
+}
+
+static void virtio_ccw_remove_irqfd(VirtioCcwDevice *dev, int n)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_guest_notifier(vq);
+ int ret;
+
+ ret = kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, notifier,
+ dev->routes.gsi[n]);
+ assert(ret == 0);
+}
+
+static int virtio_ccw_set_guest_notifier(VirtioCcwDevice *dev, int n,
+ bool assign, bool with_irqfd)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_guest_notifier(vq);
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+
+ if (assign) {
+ int r = event_notifier_init(notifier, 0);
+
+ if (r < 0) {
+ return r;
+ }
+ virtio_queue_set_guest_notifier_fd_handler(vq, true, with_irqfd);
+ if (with_irqfd) {
+ r = virtio_ccw_add_irqfd(dev, n);
+ if (r) {
+ virtio_queue_set_guest_notifier_fd_handler(vq, false,
+ with_irqfd);
+ return r;
+ }
+ }
+ /*
+ * We do not support individual masking for channel devices, so we
+ * need to manually trigger any guest masking callbacks here.
+ */
+ if (k->guest_notifier_mask) {
+ k->guest_notifier_mask(vdev, n, false);
+ }
+ /* get lost events and re-inject */
+ if (k->guest_notifier_pending &&
+ k->guest_notifier_pending(vdev, n)) {
+ event_notifier_set(notifier);
+ }
+ } else {
+ if (k->guest_notifier_mask) {
+ k->guest_notifier_mask(vdev, n, true);
+ }
+ if (with_irqfd) {
+ virtio_ccw_remove_irqfd(dev, n);
+ }
+ virtio_queue_set_guest_notifier_fd_handler(vq, false, with_irqfd);
+ event_notifier_cleanup(notifier);
+ }
+ return 0;
+}
+
+static int virtio_ccw_set_guest_notifiers(DeviceState *d, int nvqs,
+ bool assigned)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ bool with_irqfd = dev->sch->thinint_active && kvm_irqfds_enabled();
+ int r, n;
+
+ if (with_irqfd && assigned) {
+ /* irq routes need to be set up before assigning irqfds */
+ r = virtio_ccw_setup_irqroutes(dev, nvqs);
+ if (r < 0) {
+ goto irqroute_error;
+ }
+ }
+ for (n = 0; n < nvqs; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ break;
+ }
+ r = virtio_ccw_set_guest_notifier(dev, n, assigned, with_irqfd);
+ if (r < 0) {
+ goto assign_error;
+ }
+ }
+ if (with_irqfd && !assigned) {
+ /* release irq routes after irqfds have been released */
+ virtio_ccw_release_irqroutes(dev, nvqs);
+ }
+ return 0;
+
+assign_error:
+ while (--n >= 0) {
+ virtio_ccw_set_guest_notifier(dev, n, !assigned, false);
+ }
+irqroute_error:
+ if (with_irqfd && assigned) {
+ virtio_ccw_release_irqroutes(dev, nvqs);
+ }
+ return r;
+}
+
+static void virtio_ccw_save_queue(DeviceState *d, int n, QEMUFile *f)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+
+ qemu_put_be16(f, virtio_queue_vector(vdev, n));
+}
+
+static int virtio_ccw_load_queue(DeviceState *d, int n, QEMUFile *f)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ uint16_t vector;
+
+ qemu_get_be16s(f, &vector);
+ virtio_queue_set_vector(vdev, n , vector);
+
+ return 0;
+}
+
+static void virtio_ccw_save_config(DeviceState *d, QEMUFile *f)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+ SubchDev *s = dev->sch;
+ VirtIODevice *vdev = virtio_ccw_get_vdev(s);
+
+ subch_device_save(s, f);
+ if (dev->indicators != NULL) {
+ qemu_put_be32(f, dev->indicators->len);
+ qemu_put_be64(f, dev->indicators->addr);
+ } else {
+ qemu_put_be32(f, 0);
+ qemu_put_be64(f, 0UL);
+ }
+ if (dev->indicators2 != NULL) {
+ qemu_put_be32(f, dev->indicators2->len);
+ qemu_put_be64(f, dev->indicators2->addr);
+ } else {
+ qemu_put_be32(f, 0);
+ qemu_put_be64(f, 0UL);
+ }
+ if (dev->summary_indicator != NULL) {
+ qemu_put_be32(f, dev->summary_indicator->len);
+ qemu_put_be64(f, dev->summary_indicator->addr);
+ } else {
+ qemu_put_be32(f, 0);
+ qemu_put_be64(f, 0UL);
+ }
+ qemu_put_be16(f, vdev->config_vector);
+ qemu_put_be64(f, dev->routes.adapter.ind_offset);
+ qemu_put_byte(f, dev->thinint_isc);
+ qemu_put_be32(f, dev->revision);
+}
+
+static int virtio_ccw_load_config(DeviceState *d, QEMUFile *f)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+ SubchDev *s = dev->sch;
+ VirtIODevice *vdev = virtio_ccw_get_vdev(s);
+ int len;
+
+ s->driver_data = dev;
+ subch_device_load(s, f);
+ len = qemu_get_be32(f);
+ if (len != 0) {
+ dev->indicators = get_indicator(qemu_get_be64(f), len);
+ } else {
+ qemu_get_be64(f);
+ dev->indicators = NULL;
+ }
+ len = qemu_get_be32(f);
+ if (len != 0) {
+ dev->indicators2 = get_indicator(qemu_get_be64(f), len);
+ } else {
+ qemu_get_be64(f);
+ dev->indicators2 = NULL;
+ }
+ len = qemu_get_be32(f);
+ if (len != 0) {
+ dev->summary_indicator = get_indicator(qemu_get_be64(f), len);
+ } else {
+ qemu_get_be64(f);
+ dev->summary_indicator = NULL;
+ }
+ qemu_get_be16s(f, &vdev->config_vector);
+ dev->routes.adapter.ind_offset = qemu_get_be64(f);
+ dev->thinint_isc = qemu_get_byte(f);
+ dev->revision = qemu_get_be32(f);
+ if (s->thinint_active) {
+ return css_register_io_adapter(CSS_IO_ADAPTER_VIRTIO,
+ dev->thinint_isc, true, false,
+ &dev->routes.adapter.adapter_id);
+ }
+
+ return 0;
+}
+
+/* This is called by virtio-bus just after the device is plugged. */
+static void virtio_ccw_device_plugged(DeviceState *d, Error **errp)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&dev->bus);
+ SubchDev *sch = dev->sch;
+ int n = virtio_get_num_queues(vdev);
+
+ if (virtio_get_num_queues(vdev) > VIRTIO_CCW_QUEUE_MAX) {
+ error_setg(errp, "The nubmer of virtqueues %d "
+ "exceeds ccw limit %d", n,
+ VIRTIO_CCW_QUEUE_MAX);
+ return;
+ }
+
+ if (!kvm_eventfds_enabled()) {
+ dev->flags &= ~VIRTIO_CCW_FLAG_USE_IOEVENTFD;
+ }
+
+ sch->id.cu_model = virtio_bus_get_vdev_id(&dev->bus);
+
+ css_generate_sch_crws(sch->cssid, sch->ssid, sch->schid,
+ d->hotplugged, 1);
+}
+
+static void virtio_ccw_device_unplugged(DeviceState *d)
+{
+ VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
+
+ virtio_ccw_stop_ioeventfd(dev);
+}
+/**************** Virtio-ccw Bus Device Descriptions *******************/
+
+static Property virtio_ccw_net_properties[] = {
+ DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id),
+ DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags,
+ VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_ccw_net_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass);
+
+ k->realize = virtio_ccw_net_realize;
+ k->exit = virtio_ccw_exit;
+ dc->reset = virtio_ccw_reset;
+ dc->props = virtio_ccw_net_properties;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo virtio_ccw_net = {
+ .name = TYPE_VIRTIO_NET_CCW,
+ .parent = TYPE_VIRTIO_CCW_DEVICE,
+ .instance_size = sizeof(VirtIONetCcw),
+ .instance_init = virtio_ccw_net_instance_init,
+ .class_init = virtio_ccw_net_class_init,
+};
+
+static Property virtio_ccw_blk_properties[] = {
+ DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id),
+ DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags,
+ VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_ccw_blk_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass);
+
+ k->realize = virtio_ccw_blk_realize;
+ k->exit = virtio_ccw_exit;
+ dc->reset = virtio_ccw_reset;
+ dc->props = virtio_ccw_blk_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo virtio_ccw_blk = {
+ .name = TYPE_VIRTIO_BLK_CCW,
+ .parent = TYPE_VIRTIO_CCW_DEVICE,
+ .instance_size = sizeof(VirtIOBlkCcw),
+ .instance_init = virtio_ccw_blk_instance_init,
+ .class_init = virtio_ccw_blk_class_init,
+};
+
+static Property virtio_ccw_serial_properties[] = {
+ DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id),
+ DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags,
+ VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_ccw_serial_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass);
+
+ k->realize = virtio_ccw_serial_realize;
+ k->exit = virtio_ccw_exit;
+ dc->reset = virtio_ccw_reset;
+ dc->props = virtio_ccw_serial_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo virtio_ccw_serial = {
+ .name = TYPE_VIRTIO_SERIAL_CCW,
+ .parent = TYPE_VIRTIO_CCW_DEVICE,
+ .instance_size = sizeof(VirtioSerialCcw),
+ .instance_init = virtio_ccw_serial_instance_init,
+ .class_init = virtio_ccw_serial_class_init,
+};
+
+static Property virtio_ccw_balloon_properties[] = {
+ DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id),
+ DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags,
+ VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_ccw_balloon_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass);
+
+ k->realize = virtio_ccw_balloon_realize;
+ k->exit = virtio_ccw_exit;
+ dc->reset = virtio_ccw_reset;
+ dc->props = virtio_ccw_balloon_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo virtio_ccw_balloon = {
+ .name = TYPE_VIRTIO_BALLOON_CCW,
+ .parent = TYPE_VIRTIO_CCW_DEVICE,
+ .instance_size = sizeof(VirtIOBalloonCcw),
+ .instance_init = virtio_ccw_balloon_instance_init,
+ .class_init = virtio_ccw_balloon_class_init,
+};
+
+static Property virtio_ccw_scsi_properties[] = {
+ DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id),
+ DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags,
+ VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_ccw_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass);
+
+ k->realize = virtio_ccw_scsi_realize;
+ k->exit = virtio_ccw_exit;
+ dc->reset = virtio_ccw_reset;
+ dc->props = virtio_ccw_scsi_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo virtio_ccw_scsi = {
+ .name = TYPE_VIRTIO_SCSI_CCW,
+ .parent = TYPE_VIRTIO_CCW_DEVICE,
+ .instance_size = sizeof(VirtIOSCSICcw),
+ .instance_init = virtio_ccw_scsi_instance_init,
+ .class_init = virtio_ccw_scsi_class_init,
+};
+
+#ifdef CONFIG_VHOST_SCSI
+static Property vhost_ccw_scsi_properties[] = {
+ DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vhost_ccw_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass);
+
+ k->realize = vhost_ccw_scsi_realize;
+ k->exit = virtio_ccw_exit;
+ dc->reset = virtio_ccw_reset;
+ dc->props = vhost_ccw_scsi_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo vhost_ccw_scsi = {
+ .name = TYPE_VHOST_SCSI_CCW,
+ .parent = TYPE_VIRTIO_CCW_DEVICE,
+ .instance_size = sizeof(VHostSCSICcw),
+ .instance_init = vhost_ccw_scsi_instance_init,
+ .class_init = vhost_ccw_scsi_class_init,
+};
+#endif
+
+static void virtio_ccw_rng_instance_init(Object *obj)
+{
+ VirtIORNGCcw *dev = VIRTIO_RNG_CCW(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_RNG);
+ object_property_add_alias(obj, "rng", OBJECT(&dev->vdev),
+ "rng", &error_abort);
+}
+
+static Property virtio_ccw_rng_properties[] = {
+ DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id),
+ DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags,
+ VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_ccw_rng_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass);
+
+ k->realize = virtio_ccw_rng_realize;
+ k->exit = virtio_ccw_exit;
+ dc->reset = virtio_ccw_reset;
+ dc->props = virtio_ccw_rng_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo virtio_ccw_rng = {
+ .name = TYPE_VIRTIO_RNG_CCW,
+ .parent = TYPE_VIRTIO_CCW_DEVICE,
+ .instance_size = sizeof(VirtIORNGCcw),
+ .instance_init = virtio_ccw_rng_instance_init,
+ .class_init = virtio_ccw_rng_class_init,
+};
+
+static void virtio_ccw_busdev_realize(DeviceState *dev, Error **errp)
+{
+ VirtioCcwDevice *_dev = (VirtioCcwDevice *)dev;
+
+ virtio_ccw_bus_new(&_dev->bus, sizeof(_dev->bus), _dev);
+ virtio_ccw_device_realize(_dev, errp);
+}
+
+static int virtio_ccw_busdev_exit(DeviceState *dev)
+{
+ VirtioCcwDevice *_dev = (VirtioCcwDevice *)dev;
+ VirtIOCCWDeviceClass *_info = VIRTIO_CCW_DEVICE_GET_CLASS(dev);
+
+ return _info->exit(_dev);
+}
+
+static void virtio_ccw_busdev_unplug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ VirtioCcwDevice *_dev = (VirtioCcwDevice *)dev;
+ SubchDev *sch = _dev->sch;
+
+ virtio_ccw_stop_ioeventfd(_dev);
+
+ /*
+ * We should arrive here only for device_del, since we don't support
+ * direct hot(un)plug of channels, but only through virtio.
+ */
+ assert(sch != NULL);
+ /* Subchannel is now disabled and no longer valid. */
+ sch->curr_status.pmcw.flags &= ~(PMCW_FLAGS_MASK_ENA |
+ PMCW_FLAGS_MASK_DNV);
+
+ css_generate_sch_crws(sch->cssid, sch->ssid, sch->schid, 1, 0);
+
+ object_unparent(OBJECT(dev));
+}
+
+static void virtio_ccw_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = virtio_ccw_busdev_realize;
+ dc->exit = virtio_ccw_busdev_exit;
+ dc->bus_type = TYPE_VIRTUAL_CSS_BUS;
+}
+
+static const TypeInfo virtio_ccw_device_info = {
+ .name = TYPE_VIRTIO_CCW_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(VirtioCcwDevice),
+ .class_init = virtio_ccw_device_class_init,
+ .class_size = sizeof(VirtIOCCWDeviceClass),
+ .abstract = true,
+};
+
+/***************** Virtual-css Bus Bridge Device ********************/
+/* Only required to have the virtio bus as child in the system bus */
+
+static int virtual_css_bridge_init(SysBusDevice *dev)
+{
+ /* nothing */
+ return 0;
+}
+
+static void virtual_css_bridge_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->init = virtual_css_bridge_init;
+ hc->unplug = virtio_ccw_busdev_unplug;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo virtual_css_bridge_info = {
+ .name = "virtual-css-bridge",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .class_init = virtual_css_bridge_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+/* virtio-ccw-bus */
+
+static void virtio_ccw_bus_new(VirtioBusState *bus, size_t bus_size,
+ VirtioCcwDevice *dev)
+{
+ DeviceState *qdev = DEVICE(dev);
+ char virtio_bus_name[] = "virtio-bus";
+
+ qbus_create_inplace(bus, bus_size, TYPE_VIRTIO_CCW_BUS,
+ qdev, virtio_bus_name);
+}
+
+static void virtio_ccw_bus_class_init(ObjectClass *klass, void *data)
+{
+ VirtioBusClass *k = VIRTIO_BUS_CLASS(klass);
+ BusClass *bus_class = BUS_CLASS(klass);
+
+ bus_class->max_dev = 1;
+ k->notify = virtio_ccw_notify;
+ k->vmstate_change = virtio_ccw_vmstate_change;
+ k->query_guest_notifiers = virtio_ccw_query_guest_notifiers;
+ k->set_host_notifier = virtio_ccw_set_host_notifier;
+ k->set_guest_notifiers = virtio_ccw_set_guest_notifiers;
+ k->save_queue = virtio_ccw_save_queue;
+ k->load_queue = virtio_ccw_load_queue;
+ k->save_config = virtio_ccw_save_config;
+ k->load_config = virtio_ccw_load_config;
+ k->device_plugged = virtio_ccw_device_plugged;
+ k->device_unplugged = virtio_ccw_device_unplugged;
+}
+
+static const TypeInfo virtio_ccw_bus_info = {
+ .name = TYPE_VIRTIO_CCW_BUS,
+ .parent = TYPE_VIRTIO_BUS,
+ .instance_size = sizeof(VirtioCcwBusState),
+ .class_init = virtio_ccw_bus_class_init,
+};
+
+#ifdef CONFIG_VIRTFS
+static Property virtio_ccw_9p_properties[] = {
+ DEFINE_PROP_STRING("devno", VirtioCcwDevice, bus_id),
+ DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags,
+ VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_ccw_9p_realize(VirtioCcwDevice *ccw_dev, Error **errp)
+{
+ V9fsCCWState *dev = VIRTIO_9P_CCW(ccw_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&ccw_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ }
+}
+
+static void virtio_ccw_9p_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass);
+
+ k->exit = virtio_ccw_exit;
+ k->realize = virtio_ccw_9p_realize;
+ dc->reset = virtio_ccw_reset;
+ dc->props = virtio_ccw_9p_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static void virtio_ccw_9p_instance_init(Object *obj)
+{
+ V9fsCCWState *dev = VIRTIO_9P_CCW(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_9P);
+}
+
+static const TypeInfo virtio_ccw_9p_info = {
+ .name = TYPE_VIRTIO_9P_CCW,
+ .parent = TYPE_VIRTIO_CCW_DEVICE,
+ .instance_size = sizeof(V9fsCCWState),
+ .instance_init = virtio_ccw_9p_instance_init,
+ .class_init = virtio_ccw_9p_class_init,
+};
+#endif
+
+static void virtio_ccw_register(void)
+{
+ type_register_static(&virtio_ccw_bus_info);
+ type_register_static(&virtual_css_bus_info);
+ type_register_static(&virtio_ccw_device_info);
+ type_register_static(&virtio_ccw_serial);
+ type_register_static(&virtio_ccw_blk);
+ type_register_static(&virtio_ccw_net);
+ type_register_static(&virtio_ccw_balloon);
+ type_register_static(&virtio_ccw_scsi);
+#ifdef CONFIG_VHOST_SCSI
+ type_register_static(&vhost_ccw_scsi);
+#endif
+ type_register_static(&virtio_ccw_rng);
+ type_register_static(&virtual_css_bridge_info);
+#ifdef CONFIG_VIRTFS
+ type_register_static(&virtio_ccw_9p_info);
+#endif
+}
+
+type_init(virtio_ccw_register)
diff --git a/hw/s390x/virtio-ccw.h b/hw/s390x/virtio-ccw.h
new file mode 100644
index 00000000..692ddd73
--- /dev/null
+++ b/hw/s390x/virtio-ccw.h
@@ -0,0 +1,216 @@
+/*
+ * virtio ccw target definitions
+ *
+ * Copyright 2012,2015 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ * Pierre Morel <pmorel@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef HW_S390X_VIRTIO_CCW_H
+#define HW_S390X_VIRTIO_CCW_H
+
+#include <hw/virtio/virtio-blk.h>
+#include <hw/virtio/virtio-net.h>
+#include <hw/virtio/virtio-serial.h>
+#include <hw/virtio/virtio-scsi.h>
+#ifdef CONFIG_VHOST_SCSI
+#include <hw/virtio/vhost-scsi.h>
+#endif
+#include <hw/virtio/virtio-balloon.h>
+#include <hw/virtio/virtio-rng.h>
+#include <hw/virtio/virtio-bus.h>
+#include <hw/s390x/s390_flic.h>
+
+#define VIRTUAL_CSSID 0xfe
+
+#define VIRTIO_CCW_CU_TYPE 0x3832
+#define VIRTIO_CCW_CHPID_TYPE 0x32
+
+#define CCW_CMD_SET_VQ 0x13
+#define CCW_CMD_VDEV_RESET 0x33
+#define CCW_CMD_READ_FEAT 0x12
+#define CCW_CMD_WRITE_FEAT 0x11
+#define CCW_CMD_READ_CONF 0x22
+#define CCW_CMD_WRITE_CONF 0x21
+#define CCW_CMD_WRITE_STATUS 0x31
+#define CCW_CMD_SET_IND 0x43
+#define CCW_CMD_SET_CONF_IND 0x53
+#define CCW_CMD_READ_VQ_CONF 0x32
+#define CCW_CMD_SET_IND_ADAPTER 0x73
+#define CCW_CMD_SET_VIRTIO_REV 0x83
+
+#define TYPE_VIRTIO_CCW_DEVICE "virtio-ccw-device"
+#define VIRTIO_CCW_DEVICE(obj) \
+ OBJECT_CHECK(VirtioCcwDevice, (obj), TYPE_VIRTIO_CCW_DEVICE)
+#define VIRTIO_CCW_DEVICE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtIOCCWDeviceClass, (klass), TYPE_VIRTIO_CCW_DEVICE)
+#define VIRTIO_CCW_DEVICE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VirtIOCCWDeviceClass, (obj), TYPE_VIRTIO_CCW_DEVICE)
+
+typedef struct VirtioBusState VirtioCcwBusState;
+typedef struct VirtioBusClass VirtioCcwBusClass;
+
+#define TYPE_VIRTIO_CCW_BUS "virtio-ccw-bus"
+#define VIRTIO_CCW_BUS(obj) \
+ OBJECT_CHECK(VirtioCcwBus, (obj), TYPE_VIRTIO_CCW_BUS)
+#define VIRTIO_CCW_BUS_GET_CLASS(obj) \
+ OBJECT_CHECK(VirtioCcwBusState, (obj), TYPE_VIRTIO_CCW_BUS)
+#define VIRTIO_CCW_BUS_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtioCcwBusClass, klass, TYPE_VIRTIO_CCW_BUS)
+
+typedef struct VirtioCcwDevice VirtioCcwDevice;
+
+typedef struct VirtIOCCWDeviceClass {
+ DeviceClass parent_class;
+ void (*realize)(VirtioCcwDevice *dev, Error **errp);
+ int (*exit)(VirtioCcwDevice *dev);
+} VirtIOCCWDeviceClass;
+
+/* Performance improves when virtqueue kick processing is decoupled from the
+ * vcpu thread using ioeventfd for some devices. */
+#define VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT 1
+#define VIRTIO_CCW_FLAG_USE_IOEVENTFD (1 << VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT)
+
+typedef struct IndAddr {
+ hwaddr addr;
+ uint64_t map;
+ unsigned long refcnt;
+ int len;
+ QTAILQ_ENTRY(IndAddr) sibling;
+} IndAddr;
+
+struct VirtioCcwDevice {
+ DeviceState parent_obj;
+ SubchDev *sch;
+ char *bus_id;
+ int revision;
+ VirtioBusState bus;
+ bool ioeventfd_started;
+ bool ioeventfd_disabled;
+ uint32_t flags;
+ uint8_t thinint_isc;
+ AdapterRoutes routes;
+ /* Guest provided values: */
+ IndAddr *indicators;
+ IndAddr *indicators2;
+ IndAddr *summary_indicator;
+ uint64_t ind_bit;
+};
+
+/* The maximum virtio revision we support. */
+static inline int virtio_ccw_rev_max(VirtIODevice *vdev)
+{
+ return 0;
+}
+
+/* virtual css bus type */
+typedef struct VirtualCssBus {
+ BusState parent_obj;
+} VirtualCssBus;
+
+#define TYPE_VIRTUAL_CSS_BUS "virtual-css-bus"
+#define VIRTUAL_CSS_BUS(obj) \
+ OBJECT_CHECK(VirtualCssBus, (obj), TYPE_VIRTUAL_CSS_BUS)
+
+/* virtio-scsi-ccw */
+
+#define TYPE_VIRTIO_SCSI_CCW "virtio-scsi-ccw"
+#define VIRTIO_SCSI_CCW(obj) \
+ OBJECT_CHECK(VirtIOSCSICcw, (obj), TYPE_VIRTIO_SCSI_CCW)
+
+typedef struct VirtIOSCSICcw {
+ VirtioCcwDevice parent_obj;
+ VirtIOSCSI vdev;
+} VirtIOSCSICcw;
+
+#ifdef CONFIG_VHOST_SCSI
+/* vhost-scsi-ccw */
+
+#define TYPE_VHOST_SCSI_CCW "vhost-scsi-ccw"
+#define VHOST_SCSI_CCW(obj) \
+ OBJECT_CHECK(VHostSCSICcw, (obj), TYPE_VHOST_SCSI_CCW)
+
+typedef struct VHostSCSICcw {
+ VirtioCcwDevice parent_obj;
+ VHostSCSI vdev;
+} VHostSCSICcw;
+#endif
+
+/* virtio-blk-ccw */
+
+#define TYPE_VIRTIO_BLK_CCW "virtio-blk-ccw"
+#define VIRTIO_BLK_CCW(obj) \
+ OBJECT_CHECK(VirtIOBlkCcw, (obj), TYPE_VIRTIO_BLK_CCW)
+
+typedef struct VirtIOBlkCcw {
+ VirtioCcwDevice parent_obj;
+ VirtIOBlock vdev;
+} VirtIOBlkCcw;
+
+/* virtio-balloon-ccw */
+
+#define TYPE_VIRTIO_BALLOON_CCW "virtio-balloon-ccw"
+#define VIRTIO_BALLOON_CCW(obj) \
+ OBJECT_CHECK(VirtIOBalloonCcw, (obj), TYPE_VIRTIO_BALLOON_CCW)
+
+typedef struct VirtIOBalloonCcw {
+ VirtioCcwDevice parent_obj;
+ VirtIOBalloon vdev;
+} VirtIOBalloonCcw;
+
+/* virtio-serial-ccw */
+
+#define TYPE_VIRTIO_SERIAL_CCW "virtio-serial-ccw"
+#define VIRTIO_SERIAL_CCW(obj) \
+ OBJECT_CHECK(VirtioSerialCcw, (obj), TYPE_VIRTIO_SERIAL_CCW)
+
+typedef struct VirtioSerialCcw {
+ VirtioCcwDevice parent_obj;
+ VirtIOSerial vdev;
+} VirtioSerialCcw;
+
+/* virtio-net-ccw */
+
+#define TYPE_VIRTIO_NET_CCW "virtio-net-ccw"
+#define VIRTIO_NET_CCW(obj) \
+ OBJECT_CHECK(VirtIONetCcw, (obj), TYPE_VIRTIO_NET_CCW)
+
+typedef struct VirtIONetCcw {
+ VirtioCcwDevice parent_obj;
+ VirtIONet vdev;
+} VirtIONetCcw;
+
+/* virtio-rng-ccw */
+
+#define TYPE_VIRTIO_RNG_CCW "virtio-rng-ccw"
+#define VIRTIO_RNG_CCW(obj) \
+ OBJECT_CHECK(VirtIORNGCcw, (obj), TYPE_VIRTIO_RNG_CCW)
+
+typedef struct VirtIORNGCcw {
+ VirtioCcwDevice parent_obj;
+ VirtIORNG vdev;
+} VirtIORNGCcw;
+
+VirtualCssBus *virtual_css_bus_init(void);
+void virtio_ccw_device_update_status(SubchDev *sch);
+VirtIODevice *virtio_ccw_get_vdev(SubchDev *sch);
+
+#ifdef CONFIG_VIRTFS
+#include "hw/9pfs/virtio-9p.h"
+
+#define TYPE_VIRTIO_9P_CCW "virtio-9p-ccw"
+#define VIRTIO_9P_CCW(obj) \
+ OBJECT_CHECK(V9fsCCWState, (obj), TYPE_VIRTIO_9P_CCW)
+
+typedef struct V9fsCCWState {
+ VirtioCcwDevice parent_obj;
+ V9fsState vdev;
+} V9fsCCWState;
+
+#endif /* CONFIG_VIRTFS */
+
+#endif
diff --git a/hw/scsi/Makefile.objs b/hw/scsi/Makefile.objs
new file mode 100644
index 00000000..40c79d34
--- /dev/null
+++ b/hw/scsi/Makefile.objs
@@ -0,0 +1,13 @@
+common-obj-y += scsi-disk.o
+common-obj-y += scsi-generic.o scsi-bus.o
+common-obj-$(CONFIG_LSI_SCSI_PCI) += lsi53c895a.o
+common-obj-$(CONFIG_MEGASAS_SCSI_PCI) += megasas.o
+common-obj-$(CONFIG_VMW_PVSCSI_SCSI_PCI) += vmw_pvscsi.o
+common-obj-$(CONFIG_ESP) += esp.o
+common-obj-$(CONFIG_ESP_PCI) += esp-pci.o
+obj-$(CONFIG_PSERIES) += spapr_vscsi.o
+
+ifeq ($(CONFIG_VIRTIO),y)
+obj-y += virtio-scsi.o virtio-scsi-dataplane.o
+obj-$(CONFIG_VHOST_SCSI) += vhost-scsi.o
+endif
diff --git a/hw/scsi/esp-pci.c b/hw/scsi/esp-pci.c
new file mode 100644
index 00000000..8d2242d0
--- /dev/null
+++ b/hw/scsi/esp-pci.c
@@ -0,0 +1,529 @@
+/*
+ * QEMU ESP/NCR53C9x emulation
+ *
+ * Copyright (c) 2005-2006 Fabrice Bellard
+ * Copyright (c) 2012 Herve 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.
+ */
+
+#include "hw/pci/pci.h"
+#include "hw/nvram/eeprom93xx.h"
+#include "hw/scsi/esp.h"
+#include "trace.h"
+#include "qemu/log.h"
+
+#define TYPE_AM53C974_DEVICE "am53c974"
+
+#define PCI_ESP(obj) \
+ OBJECT_CHECK(PCIESPState, (obj), TYPE_AM53C974_DEVICE)
+
+#define DMA_CMD 0x0
+#define DMA_STC 0x1
+#define DMA_SPA 0x2
+#define DMA_WBC 0x3
+#define DMA_WAC 0x4
+#define DMA_STAT 0x5
+#define DMA_SMDLA 0x6
+#define DMA_WMAC 0x7
+
+#define DMA_CMD_MASK 0x03
+#define DMA_CMD_DIAG 0x04
+#define DMA_CMD_MDL 0x10
+#define DMA_CMD_INTE_P 0x20
+#define DMA_CMD_INTE_D 0x40
+#define DMA_CMD_DIR 0x80
+
+#define DMA_STAT_PWDN 0x01
+#define DMA_STAT_ERROR 0x02
+#define DMA_STAT_ABORT 0x04
+#define DMA_STAT_DONE 0x08
+#define DMA_STAT_SCSIINT 0x10
+#define DMA_STAT_BCMBLT 0x20
+
+#define SBAC_STATUS 0x1000
+
+typedef struct PCIESPState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion io;
+ uint32_t dma_regs[8];
+ uint32_t sbac;
+ ESPState esp;
+} PCIESPState;
+
+static void esp_pci_handle_idle(PCIESPState *pci, uint32_t val)
+{
+ trace_esp_pci_dma_idle(val);
+ esp_dma_enable(&pci->esp, 0, 0);
+}
+
+static void esp_pci_handle_blast(PCIESPState *pci, uint32_t val)
+{
+ trace_esp_pci_dma_blast(val);
+ qemu_log_mask(LOG_UNIMP, "am53c974: cmd BLAST not implemented\n");
+}
+
+static void esp_pci_handle_abort(PCIESPState *pci, uint32_t val)
+{
+ trace_esp_pci_dma_abort(val);
+ if (pci->esp.current_req) {
+ scsi_req_cancel(pci->esp.current_req);
+ }
+}
+
+static void esp_pci_handle_start(PCIESPState *pci, uint32_t val)
+{
+ trace_esp_pci_dma_start(val);
+
+ pci->dma_regs[DMA_WBC] = pci->dma_regs[DMA_STC];
+ pci->dma_regs[DMA_WAC] = pci->dma_regs[DMA_SPA];
+ pci->dma_regs[DMA_WMAC] = pci->dma_regs[DMA_SMDLA];
+
+ pci->dma_regs[DMA_STAT] &= ~(DMA_STAT_BCMBLT | DMA_STAT_SCSIINT
+ | DMA_STAT_DONE | DMA_STAT_ABORT
+ | DMA_STAT_ERROR | DMA_STAT_PWDN);
+
+ esp_dma_enable(&pci->esp, 0, 1);
+}
+
+static void esp_pci_dma_write(PCIESPState *pci, uint32_t saddr, uint32_t val)
+{
+ trace_esp_pci_dma_write(saddr, pci->dma_regs[saddr], val);
+ switch (saddr) {
+ case DMA_CMD:
+ pci->dma_regs[saddr] = val;
+ switch (val & DMA_CMD_MASK) {
+ case 0x0: /* IDLE */
+ esp_pci_handle_idle(pci, val);
+ break;
+ case 0x1: /* BLAST */
+ esp_pci_handle_blast(pci, val);
+ break;
+ case 0x2: /* ABORT */
+ esp_pci_handle_abort(pci, val);
+ break;
+ case 0x3: /* START */
+ esp_pci_handle_start(pci, val);
+ break;
+ default: /* can't happen */
+ abort();
+ }
+ break;
+ case DMA_STC:
+ case DMA_SPA:
+ case DMA_SMDLA:
+ pci->dma_regs[saddr] = val;
+ break;
+ case DMA_STAT:
+ if (!(pci->sbac & SBAC_STATUS)) {
+ /* clear some bits on write */
+ uint32_t mask = DMA_STAT_ERROR | DMA_STAT_ABORT | DMA_STAT_DONE;
+ pci->dma_regs[DMA_STAT] &= ~(val & mask);
+ }
+ break;
+ default:
+ trace_esp_pci_error_invalid_write_dma(val, saddr);
+ return;
+ }
+}
+
+static uint32_t esp_pci_dma_read(PCIESPState *pci, uint32_t saddr)
+{
+ uint32_t val;
+
+ val = pci->dma_regs[saddr];
+ if (saddr == DMA_STAT) {
+ if (pci->esp.rregs[ESP_RSTAT] & STAT_INT) {
+ val |= DMA_STAT_SCSIINT;
+ }
+ if (pci->sbac & SBAC_STATUS) {
+ pci->dma_regs[DMA_STAT] &= ~(DMA_STAT_ERROR | DMA_STAT_ABORT |
+ DMA_STAT_DONE);
+ }
+ }
+
+ trace_esp_pci_dma_read(saddr, val);
+ return val;
+}
+
+static void esp_pci_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ PCIESPState *pci = opaque;
+
+ if (size < 4 || addr & 3) {
+ /* need to upgrade request: we only support 4-bytes accesses */
+ uint32_t current = 0, mask;
+ int shift;
+
+ if (addr < 0x40) {
+ current = pci->esp.wregs[addr >> 2];
+ } else if (addr < 0x60) {
+ current = pci->dma_regs[(addr - 0x40) >> 2];
+ } else if (addr < 0x74) {
+ current = pci->sbac;
+ }
+
+ shift = (4 - size) * 8;
+ mask = (~(uint32_t)0 << shift) >> shift;
+
+ shift = ((4 - (addr & 3)) & 3) * 8;
+ val <<= shift;
+ val |= current & ~(mask << shift);
+ addr &= ~3;
+ size = 4;
+ }
+
+ if (addr < 0x40) {
+ /* SCSI core reg */
+ esp_reg_write(&pci->esp, addr >> 2, val);
+ } else if (addr < 0x60) {
+ /* PCI DMA CCB */
+ esp_pci_dma_write(pci, (addr - 0x40) >> 2, val);
+ } else if (addr == 0x70) {
+ /* DMA SCSI Bus and control */
+ trace_esp_pci_sbac_write(pci->sbac, val);
+ pci->sbac = val;
+ } else {
+ trace_esp_pci_error_invalid_write((int)addr);
+ }
+}
+
+static uint64_t esp_pci_io_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ PCIESPState *pci = opaque;
+ uint32_t ret;
+
+ if (addr < 0x40) {
+ /* SCSI core reg */
+ ret = esp_reg_read(&pci->esp, addr >> 2);
+ } else if (addr < 0x60) {
+ /* PCI DMA CCB */
+ ret = esp_pci_dma_read(pci, (addr - 0x40) >> 2);
+ } else if (addr == 0x70) {
+ /* DMA SCSI Bus and control */
+ trace_esp_pci_sbac_read(pci->sbac);
+ ret = pci->sbac;
+ } else {
+ /* Invalid region */
+ trace_esp_pci_error_invalid_read((int)addr);
+ ret = 0;
+ }
+
+ /* give only requested data */
+ ret >>= (addr & 3) * 8;
+ ret &= ~(~(uint64_t)0 << (8 * size));
+
+ return ret;
+}
+
+static void esp_pci_dma_memory_rw(PCIESPState *pci, uint8_t *buf, int len,
+ DMADirection dir)
+{
+ dma_addr_t addr;
+ DMADirection expected_dir;
+
+ if (pci->dma_regs[DMA_CMD] & DMA_CMD_DIR) {
+ expected_dir = DMA_DIRECTION_FROM_DEVICE;
+ } else {
+ expected_dir = DMA_DIRECTION_TO_DEVICE;
+ }
+
+ if (dir != expected_dir) {
+ trace_esp_pci_error_invalid_dma_direction();
+ return;
+ }
+
+ if (pci->dma_regs[DMA_STAT] & DMA_CMD_MDL) {
+ qemu_log_mask(LOG_UNIMP, "am53c974: MDL transfer not implemented\n");
+ }
+
+ addr = pci->dma_regs[DMA_SPA];
+ if (pci->dma_regs[DMA_WBC] < len) {
+ len = pci->dma_regs[DMA_WBC];
+ }
+
+ pci_dma_rw(PCI_DEVICE(pci), addr, buf, len, dir);
+
+ /* update status registers */
+ pci->dma_regs[DMA_WBC] -= len;
+ pci->dma_regs[DMA_WAC] += len;
+ if (pci->dma_regs[DMA_WBC] == 0) {
+ pci->dma_regs[DMA_STAT] |= DMA_STAT_DONE;
+ }
+}
+
+static void esp_pci_dma_memory_read(void *opaque, uint8_t *buf, int len)
+{
+ PCIESPState *pci = opaque;
+ esp_pci_dma_memory_rw(pci, buf, len, DMA_DIRECTION_TO_DEVICE);
+}
+
+static void esp_pci_dma_memory_write(void *opaque, uint8_t *buf, int len)
+{
+ PCIESPState *pci = opaque;
+ esp_pci_dma_memory_rw(pci, buf, len, DMA_DIRECTION_FROM_DEVICE);
+}
+
+static const MemoryRegionOps esp_pci_io_ops = {
+ .read = esp_pci_io_read,
+ .write = esp_pci_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static void esp_pci_hard_reset(DeviceState *dev)
+{
+ PCIESPState *pci = PCI_ESP(dev);
+ esp_hard_reset(&pci->esp);
+ pci->dma_regs[DMA_CMD] &= ~(DMA_CMD_DIR | DMA_CMD_INTE_D | DMA_CMD_INTE_P
+ | DMA_CMD_MDL | DMA_CMD_DIAG | DMA_CMD_MASK);
+ pci->dma_regs[DMA_WBC] &= ~0xffff;
+ pci->dma_regs[DMA_WAC] = 0xffffffff;
+ pci->dma_regs[DMA_STAT] &= ~(DMA_STAT_BCMBLT | DMA_STAT_SCSIINT
+ | DMA_STAT_DONE | DMA_STAT_ABORT
+ | DMA_STAT_ERROR);
+ pci->dma_regs[DMA_WMAC] = 0xfffffffd;
+}
+
+static const VMStateDescription vmstate_esp_pci_scsi = {
+ .name = "pciespscsi",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PCIESPState),
+ VMSTATE_BUFFER_UNSAFE(dma_regs, PCIESPState, 0, 8 * sizeof(uint32_t)),
+ VMSTATE_STRUCT(esp, PCIESPState, 0, vmstate_esp, ESPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void esp_pci_command_complete(SCSIRequest *req, uint32_t status,
+ size_t resid)
+{
+ ESPState *s = req->hba_private;
+ PCIESPState *pci = container_of(s, PCIESPState, esp);
+
+ esp_command_complete(req, status, resid);
+ pci->dma_regs[DMA_WBC] = 0;
+ pci->dma_regs[DMA_STAT] |= DMA_STAT_DONE;
+}
+
+static const struct SCSIBusInfo esp_pci_scsi_info = {
+ .tcq = false,
+ .max_target = ESP_MAX_DEVS,
+ .max_lun = 7,
+
+ .transfer_data = esp_transfer_data,
+ .complete = esp_pci_command_complete,
+ .cancel = esp_request_cancelled,
+};
+
+static void esp_pci_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ PCIESPState *pci = PCI_ESP(dev);
+ DeviceState *d = DEVICE(dev);
+ ESPState *s = &pci->esp;
+ uint8_t *pci_conf;
+
+ pci_conf = dev->config;
+
+ /* Interrupt pin A */
+ pci_conf[PCI_INTERRUPT_PIN] = 0x01;
+
+ s->dma_memory_read = esp_pci_dma_memory_read;
+ s->dma_memory_write = esp_pci_dma_memory_write;
+ s->dma_opaque = pci;
+ s->chip_id = TCHI_AM53C974;
+ memory_region_init_io(&pci->io, OBJECT(pci), &esp_pci_io_ops, pci,
+ "esp-io", 0x80);
+
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->io);
+ s->irq = pci_allocate_irq(dev);
+
+ scsi_bus_new(&s->bus, sizeof(s->bus), d, &esp_pci_scsi_info, NULL);
+ if (!d->hotplugged) {
+ scsi_bus_legacy_handle_cmdline(&s->bus, errp);
+ }
+}
+
+static void esp_pci_scsi_uninit(PCIDevice *d)
+{
+ PCIESPState *pci = PCI_ESP(d);
+
+ qemu_free_irq(pci->esp.irq);
+}
+
+static void esp_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = esp_pci_scsi_realize;
+ k->exit = esp_pci_scsi_uninit;
+ k->vendor_id = PCI_VENDOR_ID_AMD;
+ k->device_id = PCI_DEVICE_ID_AMD_SCSI;
+ k->revision = 0x10;
+ k->class_id = PCI_CLASS_STORAGE_SCSI;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->desc = "AMD Am53c974 PCscsi-PCI SCSI adapter";
+ dc->reset = esp_pci_hard_reset;
+ dc->vmsd = &vmstate_esp_pci_scsi;
+}
+
+static const TypeInfo esp_pci_info = {
+ .name = TYPE_AM53C974_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIESPState),
+ .class_init = esp_pci_class_init,
+};
+
+typedef struct {
+ PCIESPState pci;
+ eeprom_t *eeprom;
+} DC390State;
+
+#define TYPE_DC390_DEVICE "dc390"
+#define DC390(obj) \
+ OBJECT_CHECK(DC390State, obj, TYPE_DC390_DEVICE)
+
+#define EE_ADAPT_SCSI_ID 64
+#define EE_MODE2 65
+#define EE_DELAY 66
+#define EE_TAG_CMD_NUM 67
+#define EE_ADAPT_OPTIONS 68
+#define EE_BOOT_SCSI_ID 69
+#define EE_BOOT_SCSI_LUN 70
+#define EE_CHKSUM1 126
+#define EE_CHKSUM2 127
+
+#define EE_ADAPT_OPTION_F6_F8_AT_BOOT 0x01
+#define EE_ADAPT_OPTION_BOOT_FROM_CDROM 0x02
+#define EE_ADAPT_OPTION_INT13 0x04
+#define EE_ADAPT_OPTION_SCAM_SUPPORT 0x08
+
+
+static uint32_t dc390_read_config(PCIDevice *dev, uint32_t addr, int l)
+{
+ DC390State *pci = DC390(dev);
+ uint32_t val;
+
+ val = pci_default_read_config(dev, addr, l);
+
+ if (addr == 0x00 && l == 1) {
+ /* First byte of address space is AND-ed with EEPROM DO line */
+ if (!eeprom93xx_read(pci->eeprom)) {
+ val &= ~0xff;
+ }
+ }
+
+ return val;
+}
+
+static void dc390_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int l)
+{
+ DC390State *pci = DC390(dev);
+ if (addr == 0x80) {
+ /* EEPROM write */
+ int eesk = val & 0x80 ? 1 : 0;
+ int eedi = val & 0x40 ? 1 : 0;
+ eeprom93xx_write(pci->eeprom, 1, eesk, eedi);
+ } else if (addr == 0xc0) {
+ /* EEPROM CS low */
+ eeprom93xx_write(pci->eeprom, 0, 0, 0);
+ } else {
+ pci_default_write_config(dev, addr, val, l);
+ }
+}
+
+static void dc390_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ DC390State *pci = DC390(dev);
+ Error *err = NULL;
+ uint8_t *contents;
+ uint16_t chksum = 0;
+ int i;
+
+ /* init base class */
+ esp_pci_scsi_realize(dev, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* EEPROM */
+ pci->eeprom = eeprom93xx_new(DEVICE(dev), 64);
+
+ /* set default eeprom values */
+ contents = (uint8_t *)eeprom93xx_data(pci->eeprom);
+
+ for (i = 0; i < 16; i++) {
+ contents[i * 2] = 0x57;
+ contents[i * 2 + 1] = 0x00;
+ }
+ contents[EE_ADAPT_SCSI_ID] = 7;
+ contents[EE_MODE2] = 0x0f;
+ contents[EE_TAG_CMD_NUM] = 0x04;
+ contents[EE_ADAPT_OPTIONS] = EE_ADAPT_OPTION_F6_F8_AT_BOOT
+ | EE_ADAPT_OPTION_BOOT_FROM_CDROM
+ | EE_ADAPT_OPTION_INT13;
+
+ /* update eeprom checksum */
+ for (i = 0; i < EE_CHKSUM1; i += 2) {
+ chksum += contents[i] + (((uint16_t)contents[i + 1]) << 8);
+ }
+ chksum = 0x1234 - chksum;
+ contents[EE_CHKSUM1] = chksum & 0xff;
+ contents[EE_CHKSUM2] = chksum >> 8;
+}
+
+static void dc390_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = dc390_scsi_realize;
+ k->config_read = dc390_read_config;
+ k->config_write = dc390_write_config;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->desc = "Tekram DC-390 SCSI adapter";
+}
+
+static const TypeInfo dc390_info = {
+ .name = "dc390",
+ .parent = TYPE_AM53C974_DEVICE,
+ .instance_size = sizeof(DC390State),
+ .class_init = dc390_class_init,
+};
+
+static void esp_pci_register_types(void)
+{
+ type_register_static(&esp_pci_info);
+ type_register_static(&dc390_info);
+}
+
+type_init(esp_pci_register_types)
diff --git a/hw/scsi/esp.c b/hw/scsi/esp.c
new file mode 100644
index 00000000..272d13d6
--- /dev/null
+++ b/hw/scsi/esp.c
@@ -0,0 +1,743 @@
+/*
+ * QEMU ESP/NCR53C9x emulation
+ *
+ * Copyright (c) 2005-2006 Fabrice Bellard
+ * Copyright (c) 2012 Herve 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/scsi/esp.h"
+#include "trace.h"
+#include "qemu/log.h"
+
+/*
+ * On Sparc32, this is the ESP (NCR53C90) part of chip STP2000 (Master I/O),
+ * also produced as NCR89C100. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
+ * and
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR53C9X.txt
+ */
+
+static void esp_raise_irq(ESPState *s)
+{
+ if (!(s->rregs[ESP_RSTAT] & STAT_INT)) {
+ s->rregs[ESP_RSTAT] |= STAT_INT;
+ qemu_irq_raise(s->irq);
+ trace_esp_raise_irq();
+ }
+}
+
+static void esp_lower_irq(ESPState *s)
+{
+ if (s->rregs[ESP_RSTAT] & STAT_INT) {
+ s->rregs[ESP_RSTAT] &= ~STAT_INT;
+ qemu_irq_lower(s->irq);
+ trace_esp_lower_irq();
+ }
+}
+
+void esp_dma_enable(ESPState *s, int irq, int level)
+{
+ if (level) {
+ s->dma_enabled = 1;
+ trace_esp_dma_enable();
+ if (s->dma_cb) {
+ s->dma_cb(s);
+ s->dma_cb = NULL;
+ }
+ } else {
+ trace_esp_dma_disable();
+ s->dma_enabled = 0;
+ }
+}
+
+void esp_request_cancelled(SCSIRequest *req)
+{
+ ESPState *s = req->hba_private;
+
+ if (req == s->current_req) {
+ scsi_req_unref(s->current_req);
+ s->current_req = NULL;
+ s->current_dev = NULL;
+ }
+}
+
+static uint32_t get_cmd(ESPState *s, uint8_t *buf)
+{
+ uint32_t dmalen;
+ int target;
+
+ target = s->wregs[ESP_WBUSID] & BUSID_DID;
+ if (s->dma) {
+ dmalen = s->rregs[ESP_TCLO];
+ dmalen |= s->rregs[ESP_TCMID] << 8;
+ dmalen |= s->rregs[ESP_TCHI] << 16;
+ s->dma_memory_read(s->dma_opaque, buf, dmalen);
+ } else {
+ dmalen = s->ti_size;
+ memcpy(buf, s->ti_buf, dmalen);
+ buf[0] = buf[2] >> 5;
+ }
+ trace_esp_get_cmd(dmalen, target);
+
+ s->ti_size = 0;
+ s->ti_rptr = 0;
+ s->ti_wptr = 0;
+
+ if (s->current_req) {
+ /* Started a new command before the old one finished. Cancel it. */
+ scsi_req_cancel(s->current_req);
+ s->async_len = 0;
+ }
+
+ s->current_dev = scsi_device_find(&s->bus, 0, target, 0);
+ if (!s->current_dev) {
+ // No such drive
+ s->rregs[ESP_RSTAT] = 0;
+ s->rregs[ESP_RINTR] = INTR_DC;
+ s->rregs[ESP_RSEQ] = SEQ_0;
+ esp_raise_irq(s);
+ return 0;
+ }
+ return dmalen;
+}
+
+static void do_busid_cmd(ESPState *s, uint8_t *buf, uint8_t busid)
+{
+ int32_t datalen;
+ int lun;
+ SCSIDevice *current_lun;
+
+ trace_esp_do_busid_cmd(busid);
+ lun = busid & 7;
+ current_lun = scsi_device_find(&s->bus, 0, s->current_dev->id, lun);
+ s->current_req = scsi_req_new(current_lun, 0, lun, buf, s);
+ datalen = scsi_req_enqueue(s->current_req);
+ s->ti_size = datalen;
+ if (datalen != 0) {
+ s->rregs[ESP_RSTAT] = STAT_TC;
+ s->dma_left = 0;
+ s->dma_counter = 0;
+ if (datalen > 0) {
+ s->rregs[ESP_RSTAT] |= STAT_DI;
+ } else {
+ s->rregs[ESP_RSTAT] |= STAT_DO;
+ }
+ scsi_req_continue(s->current_req);
+ }
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ esp_raise_irq(s);
+}
+
+static void do_cmd(ESPState *s, uint8_t *buf)
+{
+ uint8_t busid = buf[0];
+
+ do_busid_cmd(s, &buf[1], busid);
+}
+
+static void handle_satn(ESPState *s)
+{
+ uint8_t buf[32];
+ int len;
+
+ if (s->dma && !s->dma_enabled) {
+ s->dma_cb = handle_satn;
+ return;
+ }
+ len = get_cmd(s, buf);
+ if (len)
+ do_cmd(s, buf);
+}
+
+static void handle_s_without_atn(ESPState *s)
+{
+ uint8_t buf[32];
+ int len;
+
+ if (s->dma && !s->dma_enabled) {
+ s->dma_cb = handle_s_without_atn;
+ return;
+ }
+ len = get_cmd(s, buf);
+ if (len) {
+ do_busid_cmd(s, buf, 0);
+ }
+}
+
+static void handle_satn_stop(ESPState *s)
+{
+ if (s->dma && !s->dma_enabled) {
+ s->dma_cb = handle_satn_stop;
+ return;
+ }
+ s->cmdlen = get_cmd(s, s->cmdbuf);
+ if (s->cmdlen) {
+ trace_esp_handle_satn_stop(s->cmdlen);
+ s->do_cmd = 1;
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_CD;
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ esp_raise_irq(s);
+ }
+}
+
+static void write_response(ESPState *s)
+{
+ trace_esp_write_response(s->status);
+ s->ti_buf[0] = s->status;
+ s->ti_buf[1] = 0;
+ if (s->dma) {
+ s->dma_memory_write(s->dma_opaque, s->ti_buf, 2);
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ } else {
+ s->ti_size = 2;
+ s->ti_rptr = 0;
+ s->ti_wptr = 0;
+ s->rregs[ESP_RFLAGS] = 2;
+ }
+ esp_raise_irq(s);
+}
+
+static void esp_dma_done(ESPState *s)
+{
+ s->rregs[ESP_RSTAT] |= STAT_TC;
+ s->rregs[ESP_RINTR] = INTR_BS;
+ s->rregs[ESP_RSEQ] = 0;
+ s->rregs[ESP_RFLAGS] = 0;
+ s->rregs[ESP_TCLO] = 0;
+ s->rregs[ESP_TCMID] = 0;
+ s->rregs[ESP_TCHI] = 0;
+ esp_raise_irq(s);
+}
+
+static void esp_do_dma(ESPState *s)
+{
+ uint32_t len;
+ int to_device;
+
+ to_device = (s->ti_size < 0);
+ len = s->dma_left;
+ if (s->do_cmd) {
+ trace_esp_do_dma(s->cmdlen, len);
+ s->dma_memory_read(s->dma_opaque, &s->cmdbuf[s->cmdlen], len);
+ s->ti_size = 0;
+ s->cmdlen = 0;
+ s->do_cmd = 0;
+ do_cmd(s, s->cmdbuf);
+ return;
+ }
+ if (s->async_len == 0) {
+ /* Defer until data is available. */
+ return;
+ }
+ if (len > s->async_len) {
+ len = s->async_len;
+ }
+ if (to_device) {
+ s->dma_memory_read(s->dma_opaque, s->async_buf, len);
+ } else {
+ s->dma_memory_write(s->dma_opaque, s->async_buf, len);
+ }
+ s->dma_left -= len;
+ s->async_buf += len;
+ s->async_len -= len;
+ if (to_device)
+ s->ti_size += len;
+ else
+ s->ti_size -= len;
+ if (s->async_len == 0) {
+ scsi_req_continue(s->current_req);
+ /* If there is still data to be read from the device then
+ complete the DMA operation immediately. Otherwise defer
+ until the scsi layer has completed. */
+ if (to_device || s->dma_left != 0 || s->ti_size == 0) {
+ return;
+ }
+ }
+
+ /* Partially filled a scsi buffer. Complete immediately. */
+ esp_dma_done(s);
+}
+
+void esp_command_complete(SCSIRequest *req, uint32_t status,
+ size_t resid)
+{
+ ESPState *s = req->hba_private;
+
+ trace_esp_command_complete();
+ if (s->ti_size != 0) {
+ trace_esp_command_complete_unexpected();
+ }
+ s->ti_size = 0;
+ s->dma_left = 0;
+ s->async_len = 0;
+ if (status) {
+ trace_esp_command_complete_fail();
+ }
+ s->status = status;
+ s->rregs[ESP_RSTAT] = STAT_ST;
+ esp_dma_done(s);
+ if (s->current_req) {
+ scsi_req_unref(s->current_req);
+ s->current_req = NULL;
+ s->current_dev = NULL;
+ }
+}
+
+void esp_transfer_data(SCSIRequest *req, uint32_t len)
+{
+ ESPState *s = req->hba_private;
+
+ trace_esp_transfer_data(s->dma_left, s->ti_size);
+ s->async_len = len;
+ s->async_buf = scsi_req_get_buf(req);
+ if (s->dma_left) {
+ esp_do_dma(s);
+ } else if (s->dma_counter != 0 && s->ti_size <= 0) {
+ /* If this was the last part of a DMA transfer then the
+ completion interrupt is deferred to here. */
+ esp_dma_done(s);
+ }
+}
+
+static void handle_ti(ESPState *s)
+{
+ uint32_t dmalen, minlen;
+
+ if (s->dma && !s->dma_enabled) {
+ s->dma_cb = handle_ti;
+ return;
+ }
+
+ dmalen = s->rregs[ESP_TCLO];
+ dmalen |= s->rregs[ESP_TCMID] << 8;
+ dmalen |= s->rregs[ESP_TCHI] << 16;
+ if (dmalen==0) {
+ dmalen=0x10000;
+ }
+ s->dma_counter = dmalen;
+
+ if (s->do_cmd)
+ minlen = (dmalen < 32) ? dmalen : 32;
+ else if (s->ti_size < 0)
+ minlen = (dmalen < -s->ti_size) ? dmalen : -s->ti_size;
+ else
+ minlen = (dmalen < s->ti_size) ? dmalen : s->ti_size;
+ trace_esp_handle_ti(minlen);
+ if (s->dma) {
+ s->dma_left = minlen;
+ s->rregs[ESP_RSTAT] &= ~STAT_TC;
+ esp_do_dma(s);
+ } else if (s->do_cmd) {
+ trace_esp_handle_ti_cmd(s->cmdlen);
+ s->ti_size = 0;
+ s->cmdlen = 0;
+ s->do_cmd = 0;
+ do_cmd(s, s->cmdbuf);
+ return;
+ }
+}
+
+void esp_hard_reset(ESPState *s)
+{
+ memset(s->rregs, 0, ESP_REGS);
+ memset(s->wregs, 0, ESP_REGS);
+ s->tchi_written = 0;
+ s->ti_size = 0;
+ s->ti_rptr = 0;
+ s->ti_wptr = 0;
+ s->dma = 0;
+ s->do_cmd = 0;
+ s->dma_cb = NULL;
+
+ s->rregs[ESP_CFG1] = 7;
+}
+
+static void esp_soft_reset(ESPState *s)
+{
+ qemu_irq_lower(s->irq);
+ esp_hard_reset(s);
+}
+
+static void parent_esp_reset(ESPState *s, int irq, int level)
+{
+ if (level) {
+ esp_soft_reset(s);
+ }
+}
+
+uint64_t esp_reg_read(ESPState *s, uint32_t saddr)
+{
+ uint32_t old_val;
+
+ trace_esp_mem_readb(saddr, s->rregs[saddr]);
+ switch (saddr) {
+ case ESP_FIFO:
+ if (s->ti_size > 0) {
+ s->ti_size--;
+ if ((s->rregs[ESP_RSTAT] & STAT_PIO_MASK) == 0) {
+ /* Data out. */
+ qemu_log_mask(LOG_UNIMP,
+ "esp: PIO data read not implemented\n");
+ s->rregs[ESP_FIFO] = 0;
+ } else {
+ s->rregs[ESP_FIFO] = s->ti_buf[s->ti_rptr++];
+ }
+ esp_raise_irq(s);
+ }
+ if (s->ti_size == 0) {
+ s->ti_rptr = 0;
+ s->ti_wptr = 0;
+ }
+ break;
+ case ESP_RINTR:
+ /* Clear sequence step, interrupt register and all status bits
+ except TC */
+ old_val = s->rregs[ESP_RINTR];
+ s->rregs[ESP_RINTR] = 0;
+ s->rregs[ESP_RSTAT] &= ~STAT_TC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ esp_lower_irq(s);
+
+ return old_val;
+ case ESP_TCHI:
+ /* Return the unique id if the value has never been written */
+ if (!s->tchi_written) {
+ return s->chip_id;
+ }
+ default:
+ break;
+ }
+ return s->rregs[saddr];
+}
+
+void esp_reg_write(ESPState *s, uint32_t saddr, uint64_t val)
+{
+ trace_esp_mem_writeb(saddr, s->wregs[saddr], val);
+ switch (saddr) {
+ case ESP_TCHI:
+ s->tchi_written = true;
+ /* fall through */
+ case ESP_TCLO:
+ case ESP_TCMID:
+ s->rregs[ESP_RSTAT] &= ~STAT_TC;
+ break;
+ case ESP_FIFO:
+ if (s->do_cmd) {
+ s->cmdbuf[s->cmdlen++] = val & 0xff;
+ } else if (s->ti_size == TI_BUFSZ - 1) {
+ trace_esp_error_fifo_overrun();
+ } else {
+ s->ti_size++;
+ s->ti_buf[s->ti_wptr++] = val & 0xff;
+ }
+ break;
+ case ESP_CMD:
+ s->rregs[saddr] = val;
+ if (val & CMD_DMA) {
+ s->dma = 1;
+ /* Reload DMA counter. */
+ s->rregs[ESP_TCLO] = s->wregs[ESP_TCLO];
+ s->rregs[ESP_TCMID] = s->wregs[ESP_TCMID];
+ s->rregs[ESP_TCHI] = s->wregs[ESP_TCHI];
+ } else {
+ s->dma = 0;
+ }
+ switch(val & CMD_CMD) {
+ case CMD_NOP:
+ trace_esp_mem_writeb_cmd_nop(val);
+ break;
+ case CMD_FLUSH:
+ trace_esp_mem_writeb_cmd_flush(val);
+ //s->ti_size = 0;
+ s->rregs[ESP_RINTR] = INTR_FC;
+ s->rregs[ESP_RSEQ] = 0;
+ s->rregs[ESP_RFLAGS] = 0;
+ break;
+ case CMD_RESET:
+ trace_esp_mem_writeb_cmd_reset(val);
+ esp_soft_reset(s);
+ break;
+ case CMD_BUSRESET:
+ trace_esp_mem_writeb_cmd_bus_reset(val);
+ s->rregs[ESP_RINTR] = INTR_RST;
+ if (!(s->wregs[ESP_CFG1] & CFG1_RESREPT)) {
+ esp_raise_irq(s);
+ }
+ break;
+ case CMD_TI:
+ handle_ti(s);
+ break;
+ case CMD_ICCS:
+ trace_esp_mem_writeb_cmd_iccs(val);
+ write_response(s);
+ s->rregs[ESP_RINTR] = INTR_FC;
+ s->rregs[ESP_RSTAT] |= STAT_MI;
+ break;
+ case CMD_MSGACC:
+ trace_esp_mem_writeb_cmd_msgacc(val);
+ s->rregs[ESP_RINTR] = INTR_DC;
+ s->rregs[ESP_RSEQ] = 0;
+ s->rregs[ESP_RFLAGS] = 0;
+ esp_raise_irq(s);
+ break;
+ case CMD_PAD:
+ trace_esp_mem_writeb_cmd_pad(val);
+ s->rregs[ESP_RSTAT] = STAT_TC;
+ s->rregs[ESP_RINTR] = INTR_FC;
+ s->rregs[ESP_RSEQ] = 0;
+ break;
+ case CMD_SATN:
+ trace_esp_mem_writeb_cmd_satn(val);
+ break;
+ case CMD_RSTATN:
+ trace_esp_mem_writeb_cmd_rstatn(val);
+ break;
+ case CMD_SEL:
+ trace_esp_mem_writeb_cmd_sel(val);
+ handle_s_without_atn(s);
+ break;
+ case CMD_SELATN:
+ trace_esp_mem_writeb_cmd_selatn(val);
+ handle_satn(s);
+ break;
+ case CMD_SELATNS:
+ trace_esp_mem_writeb_cmd_selatns(val);
+ handle_satn_stop(s);
+ break;
+ case CMD_ENSEL:
+ trace_esp_mem_writeb_cmd_ensel(val);
+ s->rregs[ESP_RINTR] = 0;
+ break;
+ case CMD_DISSEL:
+ trace_esp_mem_writeb_cmd_dissel(val);
+ s->rregs[ESP_RINTR] = 0;
+ esp_raise_irq(s);
+ break;
+ default:
+ trace_esp_error_unhandled_command(val);
+ break;
+ }
+ break;
+ case ESP_WBUSID ... ESP_WSYNO:
+ break;
+ case ESP_CFG1:
+ case ESP_CFG2: case ESP_CFG3:
+ case ESP_RES3: case ESP_RES4:
+ s->rregs[saddr] = val;
+ break;
+ case ESP_WCCF ... ESP_WTEST:
+ break;
+ default:
+ trace_esp_error_invalid_write(val, saddr);
+ return;
+ }
+ s->wregs[saddr] = val;
+}
+
+static bool esp_mem_accepts(void *opaque, hwaddr addr,
+ unsigned size, bool is_write)
+{
+ return (size == 1) || (is_write && size == 4);
+}
+
+const VMStateDescription vmstate_esp = {
+ .name ="esp",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(rregs, ESPState),
+ VMSTATE_BUFFER(wregs, ESPState),
+ VMSTATE_INT32(ti_size, ESPState),
+ VMSTATE_UINT32(ti_rptr, ESPState),
+ VMSTATE_UINT32(ti_wptr, ESPState),
+ VMSTATE_BUFFER(ti_buf, ESPState),
+ VMSTATE_UINT32(status, ESPState),
+ VMSTATE_UINT32(dma, ESPState),
+ VMSTATE_BUFFER(cmdbuf, ESPState),
+ VMSTATE_UINT32(cmdlen, ESPState),
+ VMSTATE_UINT32(do_cmd, ESPState),
+ VMSTATE_UINT32(dma_left, ESPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define TYPE_ESP "esp"
+#define ESP(obj) OBJECT_CHECK(SysBusESPState, (obj), TYPE_ESP)
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint32_t it_shift;
+ ESPState esp;
+} SysBusESPState;
+
+static void sysbus_esp_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ uint32_t saddr;
+
+ saddr = addr >> sysbus->it_shift;
+ esp_reg_write(&sysbus->esp, saddr, val);
+}
+
+static uint64_t sysbus_esp_mem_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ uint32_t saddr;
+
+ saddr = addr >> sysbus->it_shift;
+ return esp_reg_read(&sysbus->esp, saddr);
+}
+
+static const MemoryRegionOps sysbus_esp_mem_ops = {
+ .read = sysbus_esp_mem_read,
+ .write = sysbus_esp_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.accepts = esp_mem_accepts,
+};
+
+void esp_init(hwaddr espaddr, int it_shift,
+ ESPDMAMemoryReadWriteFunc dma_memory_read,
+ ESPDMAMemoryReadWriteFunc dma_memory_write,
+ void *dma_opaque, qemu_irq irq, qemu_irq *reset,
+ qemu_irq *dma_enable)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ SysBusESPState *sysbus;
+ ESPState *esp;
+
+ dev = qdev_create(NULL, TYPE_ESP);
+ sysbus = ESP(dev);
+ esp = &sysbus->esp;
+ esp->dma_memory_read = dma_memory_read;
+ esp->dma_memory_write = dma_memory_write;
+ esp->dma_opaque = dma_opaque;
+ sysbus->it_shift = it_shift;
+ /* XXX for now until rc4030 has been changed to use DMA enable signal */
+ esp->dma_enabled = 1;
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irq);
+ sysbus_mmio_map(s, 0, espaddr);
+ *reset = qdev_get_gpio_in(dev, 0);
+ *dma_enable = qdev_get_gpio_in(dev, 1);
+}
+
+static const struct SCSIBusInfo esp_scsi_info = {
+ .tcq = false,
+ .max_target = ESP_MAX_DEVS,
+ .max_lun = 7,
+
+ .transfer_data = esp_transfer_data,
+ .complete = esp_command_complete,
+ .cancel = esp_request_cancelled
+};
+
+static void sysbus_esp_gpio_demux(void *opaque, int irq, int level)
+{
+ SysBusESPState *sysbus = ESP(opaque);
+ ESPState *s = &sysbus->esp;
+
+ switch (irq) {
+ case 0:
+ parent_esp_reset(s, irq, level);
+ break;
+ case 1:
+ esp_dma_enable(opaque, irq, level);
+ break;
+ }
+}
+
+static void sysbus_esp_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ SysBusESPState *sysbus = ESP(dev);
+ ESPState *s = &sysbus->esp;
+ Error *err = NULL;
+
+ sysbus_init_irq(sbd, &s->irq);
+ assert(sysbus->it_shift != -1);
+
+ s->chip_id = TCHI_FAS100A;
+ memory_region_init_io(&sysbus->iomem, OBJECT(sysbus), &sysbus_esp_mem_ops,
+ sysbus, "esp", ESP_REGS << sysbus->it_shift);
+ sysbus_init_mmio(sbd, &sysbus->iomem);
+
+ qdev_init_gpio_in(dev, sysbus_esp_gpio_demux, 2);
+
+ scsi_bus_new(&s->bus, sizeof(s->bus), dev, &esp_scsi_info, NULL);
+ scsi_bus_legacy_handle_cmdline(&s->bus, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+}
+
+static void sysbus_esp_hard_reset(DeviceState *dev)
+{
+ SysBusESPState *sysbus = ESP(dev);
+ esp_hard_reset(&sysbus->esp);
+}
+
+static const VMStateDescription vmstate_sysbus_esp_scsi = {
+ .name = "sysbusespscsi",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(esp, SysBusESPState, 0, vmstate_esp, ESPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void sysbus_esp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = sysbus_esp_realize;
+ dc->reset = sysbus_esp_hard_reset;
+ dc->vmsd = &vmstate_sysbus_esp_scsi;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo sysbus_esp_info = {
+ .name = TYPE_ESP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusESPState),
+ .class_init = sysbus_esp_class_init,
+};
+
+static void esp_register_types(void)
+{
+ type_register_static(&sysbus_esp_info);
+}
+
+type_init(esp_register_types)
diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c
new file mode 100644
index 00000000..c5b0cc5c
--- /dev/null
+++ b/hw/scsi/lsi53c895a.c
@@ -0,0 +1,2163 @@
+/*
+ * QEMU LSI53C895A SCSI Host Bus Adapter emulation
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+/* Note:
+ * LSI53C810 emulation is incorrect, in the sense that it supports
+ * features added in later evolutions. This should not be a problem,
+ * as well-behaved operating systems will not try to use them.
+ */
+
+#include <assert.h>
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/scsi/scsi.h"
+#include "sysemu/dma.h"
+
+//#define DEBUG_LSI
+//#define DEBUG_LSI_REG
+
+#ifdef DEBUG_LSI
+#define DPRINTF(fmt, ...) \
+do { printf("lsi_scsi: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "lsi_scsi: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "lsi_scsi: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+#define LSI_MAX_DEVS 7
+
+#define LSI_SCNTL0_TRG 0x01
+#define LSI_SCNTL0_AAP 0x02
+#define LSI_SCNTL0_EPC 0x08
+#define LSI_SCNTL0_WATN 0x10
+#define LSI_SCNTL0_START 0x20
+
+#define LSI_SCNTL1_SST 0x01
+#define LSI_SCNTL1_IARB 0x02
+#define LSI_SCNTL1_AESP 0x04
+#define LSI_SCNTL1_RST 0x08
+#define LSI_SCNTL1_CON 0x10
+#define LSI_SCNTL1_DHP 0x20
+#define LSI_SCNTL1_ADB 0x40
+#define LSI_SCNTL1_EXC 0x80
+
+#define LSI_SCNTL2_WSR 0x01
+#define LSI_SCNTL2_VUE0 0x02
+#define LSI_SCNTL2_VUE1 0x04
+#define LSI_SCNTL2_WSS 0x08
+#define LSI_SCNTL2_SLPHBEN 0x10
+#define LSI_SCNTL2_SLPMD 0x20
+#define LSI_SCNTL2_CHM 0x40
+#define LSI_SCNTL2_SDU 0x80
+
+#define LSI_ISTAT0_DIP 0x01
+#define LSI_ISTAT0_SIP 0x02
+#define LSI_ISTAT0_INTF 0x04
+#define LSI_ISTAT0_CON 0x08
+#define LSI_ISTAT0_SEM 0x10
+#define LSI_ISTAT0_SIGP 0x20
+#define LSI_ISTAT0_SRST 0x40
+#define LSI_ISTAT0_ABRT 0x80
+
+#define LSI_ISTAT1_SI 0x01
+#define LSI_ISTAT1_SRUN 0x02
+#define LSI_ISTAT1_FLSH 0x04
+
+#define LSI_SSTAT0_SDP0 0x01
+#define LSI_SSTAT0_RST 0x02
+#define LSI_SSTAT0_WOA 0x04
+#define LSI_SSTAT0_LOA 0x08
+#define LSI_SSTAT0_AIP 0x10
+#define LSI_SSTAT0_OLF 0x20
+#define LSI_SSTAT0_ORF 0x40
+#define LSI_SSTAT0_ILF 0x80
+
+#define LSI_SIST0_PAR 0x01
+#define LSI_SIST0_RST 0x02
+#define LSI_SIST0_UDC 0x04
+#define LSI_SIST0_SGE 0x08
+#define LSI_SIST0_RSL 0x10
+#define LSI_SIST0_SEL 0x20
+#define LSI_SIST0_CMP 0x40
+#define LSI_SIST0_MA 0x80
+
+#define LSI_SIST1_HTH 0x01
+#define LSI_SIST1_GEN 0x02
+#define LSI_SIST1_STO 0x04
+#define LSI_SIST1_SBMC 0x10
+
+#define LSI_SOCL_IO 0x01
+#define LSI_SOCL_CD 0x02
+#define LSI_SOCL_MSG 0x04
+#define LSI_SOCL_ATN 0x08
+#define LSI_SOCL_SEL 0x10
+#define LSI_SOCL_BSY 0x20
+#define LSI_SOCL_ACK 0x40
+#define LSI_SOCL_REQ 0x80
+
+#define LSI_DSTAT_IID 0x01
+#define LSI_DSTAT_SIR 0x04
+#define LSI_DSTAT_SSI 0x08
+#define LSI_DSTAT_ABRT 0x10
+#define LSI_DSTAT_BF 0x20
+#define LSI_DSTAT_MDPE 0x40
+#define LSI_DSTAT_DFE 0x80
+
+#define LSI_DCNTL_COM 0x01
+#define LSI_DCNTL_IRQD 0x02
+#define LSI_DCNTL_STD 0x04
+#define LSI_DCNTL_IRQM 0x08
+#define LSI_DCNTL_SSM 0x10
+#define LSI_DCNTL_PFEN 0x20
+#define LSI_DCNTL_PFF 0x40
+#define LSI_DCNTL_CLSE 0x80
+
+#define LSI_DMODE_MAN 0x01
+#define LSI_DMODE_BOF 0x02
+#define LSI_DMODE_ERMP 0x04
+#define LSI_DMODE_ERL 0x08
+#define LSI_DMODE_DIOM 0x10
+#define LSI_DMODE_SIOM 0x20
+
+#define LSI_CTEST2_DACK 0x01
+#define LSI_CTEST2_DREQ 0x02
+#define LSI_CTEST2_TEOP 0x04
+#define LSI_CTEST2_PCICIE 0x08
+#define LSI_CTEST2_CM 0x10
+#define LSI_CTEST2_CIO 0x20
+#define LSI_CTEST2_SIGP 0x40
+#define LSI_CTEST2_DDIR 0x80
+
+#define LSI_CTEST5_BL2 0x04
+#define LSI_CTEST5_DDIR 0x08
+#define LSI_CTEST5_MASR 0x10
+#define LSI_CTEST5_DFSN 0x20
+#define LSI_CTEST5_BBCK 0x40
+#define LSI_CTEST5_ADCK 0x80
+
+#define LSI_CCNTL0_DILS 0x01
+#define LSI_CCNTL0_DISFC 0x10
+#define LSI_CCNTL0_ENNDJ 0x20
+#define LSI_CCNTL0_PMJCTL 0x40
+#define LSI_CCNTL0_ENPMJ 0x80
+
+#define LSI_CCNTL1_EN64DBMV 0x01
+#define LSI_CCNTL1_EN64TIBMV 0x02
+#define LSI_CCNTL1_64TIMOD 0x04
+#define LSI_CCNTL1_DDAC 0x08
+#define LSI_CCNTL1_ZMOD 0x80
+
+/* Enable Response to Reselection */
+#define LSI_SCID_RRE 0x60
+
+#define LSI_CCNTL1_40BIT (LSI_CCNTL1_EN64TIBMV|LSI_CCNTL1_64TIMOD)
+
+#define PHASE_DO 0
+#define PHASE_DI 1
+#define PHASE_CMD 2
+#define PHASE_ST 3
+#define PHASE_MO 6
+#define PHASE_MI 7
+#define PHASE_MASK 7
+
+/* Maximum length of MSG IN data. */
+#define LSI_MAX_MSGIN_LEN 8
+
+/* Flag set if this is a tagged command. */
+#define LSI_TAG_VALID (1 << 16)
+
+typedef struct lsi_request {
+ SCSIRequest *req;
+ uint32_t tag;
+ uint32_t dma_len;
+ uint8_t *dma_buf;
+ uint32_t pending;
+ int out;
+ QTAILQ_ENTRY(lsi_request) next;
+} lsi_request;
+
+typedef struct {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion mmio_io;
+ MemoryRegion ram_io;
+ MemoryRegion io_io;
+
+ int carry; /* ??? Should this be an a visible register somewhere? */
+ int status;
+ /* Action to take at the end of a MSG IN phase.
+ 0 = COMMAND, 1 = disconnect, 2 = DATA OUT, 3 = DATA IN. */
+ int msg_action;
+ int msg_len;
+ uint8_t msg[LSI_MAX_MSGIN_LEN];
+ /* 0 if SCRIPTS are running or stopped.
+ * 1 if a Wait Reselect instruction has been issued.
+ * 2 if processing DMA from lsi_execute_script.
+ * 3 if a DMA operation is in progress. */
+ int waiting;
+ SCSIBus bus;
+ int current_lun;
+ /* The tag is a combination of the device ID and the SCSI tag. */
+ uint32_t select_tag;
+ int command_complete;
+ QTAILQ_HEAD(, lsi_request) queue;
+ lsi_request *current;
+
+ uint32_t dsa;
+ uint32_t temp;
+ uint32_t dnad;
+ uint32_t dbc;
+ uint8_t istat0;
+ uint8_t istat1;
+ uint8_t dcmd;
+ uint8_t dstat;
+ uint8_t dien;
+ uint8_t sist0;
+ uint8_t sist1;
+ uint8_t sien0;
+ uint8_t sien1;
+ uint8_t mbox0;
+ uint8_t mbox1;
+ uint8_t dfifo;
+ uint8_t ctest2;
+ uint8_t ctest3;
+ uint8_t ctest4;
+ uint8_t ctest5;
+ uint8_t ccntl0;
+ uint8_t ccntl1;
+ uint32_t dsp;
+ uint32_t dsps;
+ uint8_t dmode;
+ uint8_t dcntl;
+ uint8_t scntl0;
+ uint8_t scntl1;
+ uint8_t scntl2;
+ uint8_t scntl3;
+ uint8_t sstat0;
+ uint8_t sstat1;
+ uint8_t scid;
+ uint8_t sxfer;
+ uint8_t socl;
+ uint8_t sdid;
+ uint8_t ssid;
+ uint8_t sfbr;
+ uint8_t stest1;
+ uint8_t stest2;
+ uint8_t stest3;
+ uint8_t sidl;
+ uint8_t stime0;
+ uint8_t respid0;
+ uint8_t respid1;
+ uint32_t mmrs;
+ uint32_t mmws;
+ uint32_t sfs;
+ uint32_t drs;
+ uint32_t sbms;
+ uint32_t dbms;
+ uint32_t dnad64;
+ uint32_t pmjad1;
+ uint32_t pmjad2;
+ uint32_t rbc;
+ uint32_t ua;
+ uint32_t ia;
+ uint32_t sbc;
+ uint32_t csbc;
+ uint32_t scratch[18]; /* SCRATCHA-SCRATCHR */
+ uint8_t sbr;
+ uint32_t adder;
+
+ /* Script ram is stored as 32-bit words in host byteorder. */
+ uint32_t script_ram[2048];
+} LSIState;
+
+#define TYPE_LSI53C810 "lsi53c810"
+#define TYPE_LSI53C895A "lsi53c895a"
+
+#define LSI53C895A(obj) \
+ OBJECT_CHECK(LSIState, (obj), TYPE_LSI53C895A)
+
+static inline int lsi_irq_on_rsl(LSIState *s)
+{
+ return (s->sien0 & LSI_SIST0_RSL) && (s->scid & LSI_SCID_RRE);
+}
+
+static void lsi_soft_reset(LSIState *s)
+{
+ DPRINTF("Reset\n");
+ s->carry = 0;
+
+ s->msg_action = 0;
+ s->msg_len = 0;
+ s->waiting = 0;
+ s->dsa = 0;
+ s->dnad = 0;
+ s->dbc = 0;
+ s->temp = 0;
+ memset(s->scratch, 0, sizeof(s->scratch));
+ s->istat0 = 0;
+ s->istat1 = 0;
+ s->dcmd = 0x40;
+ s->dstat = LSI_DSTAT_DFE;
+ s->dien = 0;
+ s->sist0 = 0;
+ s->sist1 = 0;
+ s->sien0 = 0;
+ s->sien1 = 0;
+ s->mbox0 = 0;
+ s->mbox1 = 0;
+ s->dfifo = 0;
+ s->ctest2 = LSI_CTEST2_DACK;
+ s->ctest3 = 0;
+ s->ctest4 = 0;
+ s->ctest5 = 0;
+ s->ccntl0 = 0;
+ s->ccntl1 = 0;
+ s->dsp = 0;
+ s->dsps = 0;
+ s->dmode = 0;
+ s->dcntl = 0;
+ s->scntl0 = 0xc0;
+ s->scntl1 = 0;
+ s->scntl2 = 0;
+ s->scntl3 = 0;
+ s->sstat0 = 0;
+ s->sstat1 = 0;
+ s->scid = 7;
+ s->sxfer = 0;
+ s->socl = 0;
+ s->sdid = 0;
+ s->ssid = 0;
+ s->stest1 = 0;
+ s->stest2 = 0;
+ s->stest3 = 0;
+ s->sidl = 0;
+ s->stime0 = 0;
+ s->respid0 = 0x80;
+ s->respid1 = 0;
+ s->mmrs = 0;
+ s->mmws = 0;
+ s->sfs = 0;
+ s->drs = 0;
+ s->sbms = 0;
+ s->dbms = 0;
+ s->dnad64 = 0;
+ s->pmjad1 = 0;
+ s->pmjad2 = 0;
+ s->rbc = 0;
+ s->ua = 0;
+ s->ia = 0;
+ s->sbc = 0;
+ s->csbc = 0;
+ s->sbr = 0;
+ assert(QTAILQ_EMPTY(&s->queue));
+ assert(!s->current);
+}
+
+static int lsi_dma_40bit(LSIState *s)
+{
+ if ((s->ccntl1 & LSI_CCNTL1_40BIT) == LSI_CCNTL1_40BIT)
+ return 1;
+ return 0;
+}
+
+static int lsi_dma_ti64bit(LSIState *s)
+{
+ if ((s->ccntl1 & LSI_CCNTL1_EN64TIBMV) == LSI_CCNTL1_EN64TIBMV)
+ return 1;
+ return 0;
+}
+
+static int lsi_dma_64bit(LSIState *s)
+{
+ if ((s->ccntl1 & LSI_CCNTL1_EN64DBMV) == LSI_CCNTL1_EN64DBMV)
+ return 1;
+ return 0;
+}
+
+static uint8_t lsi_reg_readb(LSIState *s, int offset);
+static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val);
+static void lsi_execute_script(LSIState *s);
+static void lsi_reselect(LSIState *s, lsi_request *p);
+
+static inline uint32_t read_dword(LSIState *s, uint32_t addr)
+{
+ uint32_t buf;
+
+ pci_dma_read(PCI_DEVICE(s), addr, &buf, 4);
+ return cpu_to_le32(buf);
+}
+
+static void lsi_stop_script(LSIState *s)
+{
+ s->istat1 &= ~LSI_ISTAT1_SRUN;
+}
+
+static void lsi_update_irq(LSIState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int level;
+ static int last_level;
+ lsi_request *p;
+
+ /* It's unclear whether the DIP/SIP bits should be cleared when the
+ Interrupt Status Registers are cleared or when istat0 is read.
+ We currently do the formwer, which seems to work. */
+ level = 0;
+ if (s->dstat) {
+ if (s->dstat & s->dien)
+ level = 1;
+ s->istat0 |= LSI_ISTAT0_DIP;
+ } else {
+ s->istat0 &= ~LSI_ISTAT0_DIP;
+ }
+
+ if (s->sist0 || s->sist1) {
+ if ((s->sist0 & s->sien0) || (s->sist1 & s->sien1))
+ level = 1;
+ s->istat0 |= LSI_ISTAT0_SIP;
+ } else {
+ s->istat0 &= ~LSI_ISTAT0_SIP;
+ }
+ if (s->istat0 & LSI_ISTAT0_INTF)
+ level = 1;
+
+ if (level != last_level) {
+ DPRINTF("Update IRQ level %d dstat %02x sist %02x%02x\n",
+ level, s->dstat, s->sist1, s->sist0);
+ last_level = level;
+ }
+ pci_set_irq(d, level);
+
+ if (!level && lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON)) {
+ DPRINTF("Handled IRQs & disconnected, looking for pending "
+ "processes\n");
+ QTAILQ_FOREACH(p, &s->queue, next) {
+ if (p->pending) {
+ lsi_reselect(s, p);
+ break;
+ }
+ }
+ }
+}
+
+/* Stop SCRIPTS execution and raise a SCSI interrupt. */
+static void lsi_script_scsi_interrupt(LSIState *s, int stat0, int stat1)
+{
+ uint32_t mask0;
+ uint32_t mask1;
+
+ DPRINTF("SCSI Interrupt 0x%02x%02x prev 0x%02x%02x\n",
+ stat1, stat0, s->sist1, s->sist0);
+ s->sist0 |= stat0;
+ s->sist1 |= stat1;
+ /* Stop processor on fatal or unmasked interrupt. As a special hack
+ we don't stop processing when raising STO. Instead continue
+ execution and stop at the next insn that accesses the SCSI bus. */
+ mask0 = s->sien0 | ~(LSI_SIST0_CMP | LSI_SIST0_SEL | LSI_SIST0_RSL);
+ mask1 = s->sien1 | ~(LSI_SIST1_GEN | LSI_SIST1_HTH);
+ mask1 &= ~LSI_SIST1_STO;
+ if (s->sist0 & mask0 || s->sist1 & mask1) {
+ lsi_stop_script(s);
+ }
+ lsi_update_irq(s);
+}
+
+/* Stop SCRIPTS execution and raise a DMA interrupt. */
+static void lsi_script_dma_interrupt(LSIState *s, int stat)
+{
+ DPRINTF("DMA Interrupt 0x%x prev 0x%x\n", stat, s->dstat);
+ s->dstat |= stat;
+ lsi_update_irq(s);
+ lsi_stop_script(s);
+}
+
+static inline void lsi_set_phase(LSIState *s, int phase)
+{
+ s->sstat1 = (s->sstat1 & ~PHASE_MASK) | phase;
+}
+
+static void lsi_bad_phase(LSIState *s, int out, int new_phase)
+{
+ /* Trigger a phase mismatch. */
+ if (s->ccntl0 & LSI_CCNTL0_ENPMJ) {
+ if ((s->ccntl0 & LSI_CCNTL0_PMJCTL)) {
+ s->dsp = out ? s->pmjad1 : s->pmjad2;
+ } else {
+ s->dsp = (s->scntl2 & LSI_SCNTL2_WSR ? s->pmjad2 : s->pmjad1);
+ }
+ DPRINTF("Data phase mismatch jump to %08x\n", s->dsp);
+ } else {
+ DPRINTF("Phase mismatch interrupt\n");
+ lsi_script_scsi_interrupt(s, LSI_SIST0_MA, 0);
+ lsi_stop_script(s);
+ }
+ lsi_set_phase(s, new_phase);
+}
+
+
+/* Resume SCRIPTS execution after a DMA operation. */
+static void lsi_resume_script(LSIState *s)
+{
+ if (s->waiting != 2) {
+ s->waiting = 0;
+ lsi_execute_script(s);
+ } else {
+ s->waiting = 0;
+ }
+}
+
+static void lsi_disconnect(LSIState *s)
+{
+ s->scntl1 &= ~LSI_SCNTL1_CON;
+ s->sstat1 &= ~PHASE_MASK;
+}
+
+static void lsi_bad_selection(LSIState *s, uint32_t id)
+{
+ DPRINTF("Selected absent target %d\n", id);
+ lsi_script_scsi_interrupt(s, 0, LSI_SIST1_STO);
+ lsi_disconnect(s);
+}
+
+/* Initiate a SCSI layer data transfer. */
+static void lsi_do_dma(LSIState *s, int out)
+{
+ PCIDevice *pci_dev;
+ uint32_t count;
+ dma_addr_t addr;
+ SCSIDevice *dev;
+
+ assert(s->current);
+ if (!s->current->dma_len) {
+ /* Wait until data is available. */
+ DPRINTF("DMA no data available\n");
+ return;
+ }
+
+ pci_dev = PCI_DEVICE(s);
+ dev = s->current->req->dev;
+ assert(dev);
+
+ count = s->dbc;
+ if (count > s->current->dma_len)
+ count = s->current->dma_len;
+
+ addr = s->dnad;
+ /* both 40 and Table Indirect 64-bit DMAs store upper bits in dnad64 */
+ if (lsi_dma_40bit(s) || lsi_dma_ti64bit(s))
+ addr |= ((uint64_t)s->dnad64 << 32);
+ else if (s->dbms)
+ addr |= ((uint64_t)s->dbms << 32);
+ else if (s->sbms)
+ addr |= ((uint64_t)s->sbms << 32);
+
+ DPRINTF("DMA addr=0x" DMA_ADDR_FMT " len=%d\n", addr, count);
+ s->csbc += count;
+ s->dnad += count;
+ s->dbc -= count;
+ if (s->current->dma_buf == NULL) {
+ s->current->dma_buf = scsi_req_get_buf(s->current->req);
+ }
+ /* ??? Set SFBR to first data byte. */
+ if (out) {
+ pci_dma_read(pci_dev, addr, s->current->dma_buf, count);
+ } else {
+ pci_dma_write(pci_dev, addr, s->current->dma_buf, count);
+ }
+ s->current->dma_len -= count;
+ if (s->current->dma_len == 0) {
+ s->current->dma_buf = NULL;
+ scsi_req_continue(s->current->req);
+ } else {
+ s->current->dma_buf += count;
+ lsi_resume_script(s);
+ }
+}
+
+
+/* Add a command to the queue. */
+static void lsi_queue_command(LSIState *s)
+{
+ lsi_request *p = s->current;
+
+ DPRINTF("Queueing tag=0x%x\n", p->tag);
+ assert(s->current != NULL);
+ assert(s->current->dma_len == 0);
+ QTAILQ_INSERT_TAIL(&s->queue, s->current, next);
+ s->current = NULL;
+
+ p->pending = 0;
+ p->out = (s->sstat1 & PHASE_MASK) == PHASE_DO;
+}
+
+/* Queue a byte for a MSG IN phase. */
+static void lsi_add_msg_byte(LSIState *s, uint8_t data)
+{
+ if (s->msg_len >= LSI_MAX_MSGIN_LEN) {
+ BADF("MSG IN data too long\n");
+ } else {
+ DPRINTF("MSG IN 0x%02x\n", data);
+ s->msg[s->msg_len++] = data;
+ }
+}
+
+/* Perform reselection to continue a command. */
+static void lsi_reselect(LSIState *s, lsi_request *p)
+{
+ int id;
+
+ assert(s->current == NULL);
+ QTAILQ_REMOVE(&s->queue, p, next);
+ s->current = p;
+
+ id = (p->tag >> 8) & 0xf;
+ s->ssid = id | 0x80;
+ /* LSI53C700 Family Compatibility, see LSI53C895A 4-73 */
+ if (!(s->dcntl & LSI_DCNTL_COM)) {
+ s->sfbr = 1 << (id & 0x7);
+ }
+ DPRINTF("Reselected target %d\n", id);
+ s->scntl1 |= LSI_SCNTL1_CON;
+ lsi_set_phase(s, PHASE_MI);
+ s->msg_action = p->out ? 2 : 3;
+ s->current->dma_len = p->pending;
+ lsi_add_msg_byte(s, 0x80);
+ if (s->current->tag & LSI_TAG_VALID) {
+ lsi_add_msg_byte(s, 0x20);
+ lsi_add_msg_byte(s, p->tag & 0xff);
+ }
+
+ if (lsi_irq_on_rsl(s)) {
+ lsi_script_scsi_interrupt(s, LSI_SIST0_RSL, 0);
+ }
+}
+
+static lsi_request *lsi_find_by_tag(LSIState *s, uint32_t tag)
+{
+ lsi_request *p;
+
+ QTAILQ_FOREACH(p, &s->queue, next) {
+ if (p->tag == tag) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+static void lsi_request_free(LSIState *s, lsi_request *p)
+{
+ if (p == s->current) {
+ s->current = NULL;
+ } else {
+ QTAILQ_REMOVE(&s->queue, p, next);
+ }
+ g_free(p);
+}
+
+static void lsi_request_cancelled(SCSIRequest *req)
+{
+ LSIState *s = LSI53C895A(req->bus->qbus.parent);
+ lsi_request *p = req->hba_private;
+
+ req->hba_private = NULL;
+ lsi_request_free(s, p);
+ scsi_req_unref(req);
+}
+
+/* Record that data is available for a queued command. Returns zero if
+ the device was reselected, nonzero if the IO is deferred. */
+static int lsi_queue_req(LSIState *s, SCSIRequest *req, uint32_t len)
+{
+ lsi_request *p = req->hba_private;
+
+ if (p->pending) {
+ BADF("Multiple IO pending for request %p\n", p);
+ }
+ p->pending = len;
+ /* Reselect if waiting for it, or if reselection triggers an IRQ
+ and the bus is free.
+ Since no interrupt stacking is implemented in the emulation, it
+ is also required that there are no pending interrupts waiting
+ for service from the device driver. */
+ if (s->waiting == 1 ||
+ (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON) &&
+ !(s->istat0 & (LSI_ISTAT0_SIP | LSI_ISTAT0_DIP)))) {
+ /* Reselect device. */
+ lsi_reselect(s, p);
+ return 0;
+ } else {
+ DPRINTF("Queueing IO tag=0x%x\n", p->tag);
+ p->pending = len;
+ return 1;
+ }
+}
+
+ /* Callback to indicate that the SCSI layer has completed a command. */
+static void lsi_command_complete(SCSIRequest *req, uint32_t status, size_t resid)
+{
+ LSIState *s = LSI53C895A(req->bus->qbus.parent);
+ int out;
+
+ out = (s->sstat1 & PHASE_MASK) == PHASE_DO;
+ DPRINTF("Command complete status=%d\n", (int)status);
+ s->status = status;
+ s->command_complete = 2;
+ if (s->waiting && s->dbc != 0) {
+ /* Raise phase mismatch for short transfers. */
+ lsi_bad_phase(s, out, PHASE_ST);
+ } else {
+ lsi_set_phase(s, PHASE_ST);
+ }
+
+ if (req->hba_private == s->current) {
+ req->hba_private = NULL;
+ lsi_request_free(s, s->current);
+ scsi_req_unref(req);
+ }
+ lsi_resume_script(s);
+}
+
+ /* Callback to indicate that the SCSI layer has completed a transfer. */
+static void lsi_transfer_data(SCSIRequest *req, uint32_t len)
+{
+ LSIState *s = LSI53C895A(req->bus->qbus.parent);
+ int out;
+
+ assert(req->hba_private);
+ if (s->waiting == 1 || req->hba_private != s->current ||
+ (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON))) {
+ if (lsi_queue_req(s, req, len)) {
+ return;
+ }
+ }
+
+ out = (s->sstat1 & PHASE_MASK) == PHASE_DO;
+
+ /* host adapter (re)connected */
+ DPRINTF("Data ready tag=0x%x len=%d\n", req->tag, len);
+ s->current->dma_len = len;
+ s->command_complete = 1;
+ if (s->waiting) {
+ if (s->waiting == 1 || s->dbc == 0) {
+ lsi_resume_script(s);
+ } else {
+ lsi_do_dma(s, out);
+ }
+ }
+}
+
+static void lsi_do_command(LSIState *s)
+{
+ SCSIDevice *dev;
+ uint8_t buf[16];
+ uint32_t id;
+ int n;
+
+ DPRINTF("Send command len=%d\n", s->dbc);
+ if (s->dbc > 16)
+ s->dbc = 16;
+ pci_dma_read(PCI_DEVICE(s), s->dnad, buf, s->dbc);
+ s->sfbr = buf[0];
+ s->command_complete = 0;
+
+ id = (s->select_tag >> 8) & 0xf;
+ dev = scsi_device_find(&s->bus, 0, id, s->current_lun);
+ if (!dev) {
+ lsi_bad_selection(s, id);
+ return;
+ }
+
+ assert(s->current == NULL);
+ s->current = g_new0(lsi_request, 1);
+ s->current->tag = s->select_tag;
+ s->current->req = scsi_req_new(dev, s->current->tag, s->current_lun, buf,
+ s->current);
+
+ n = scsi_req_enqueue(s->current->req);
+ if (n) {
+ if (n > 0) {
+ lsi_set_phase(s, PHASE_DI);
+ } else if (n < 0) {
+ lsi_set_phase(s, PHASE_DO);
+ }
+ scsi_req_continue(s->current->req);
+ }
+ if (!s->command_complete) {
+ if (n) {
+ /* Command did not complete immediately so disconnect. */
+ lsi_add_msg_byte(s, 2); /* SAVE DATA POINTER */
+ lsi_add_msg_byte(s, 4); /* DISCONNECT */
+ /* wait data */
+ lsi_set_phase(s, PHASE_MI);
+ s->msg_action = 1;
+ lsi_queue_command(s);
+ } else {
+ /* wait command complete */
+ lsi_set_phase(s, PHASE_DI);
+ }
+ }
+}
+
+static void lsi_do_status(LSIState *s)
+{
+ uint8_t status;
+ DPRINTF("Get status len=%d status=%d\n", s->dbc, s->status);
+ if (s->dbc != 1)
+ BADF("Bad Status move\n");
+ s->dbc = 1;
+ status = s->status;
+ s->sfbr = status;
+ pci_dma_write(PCI_DEVICE(s), s->dnad, &status, 1);
+ lsi_set_phase(s, PHASE_MI);
+ s->msg_action = 1;
+ lsi_add_msg_byte(s, 0); /* COMMAND COMPLETE */
+}
+
+static void lsi_do_msgin(LSIState *s)
+{
+ int len;
+ DPRINTF("Message in len=%d/%d\n", s->dbc, s->msg_len);
+ s->sfbr = s->msg[0];
+ len = s->msg_len;
+ if (len > s->dbc)
+ len = s->dbc;
+ pci_dma_write(PCI_DEVICE(s), s->dnad, s->msg, len);
+ /* Linux drivers rely on the last byte being in the SIDL. */
+ s->sidl = s->msg[len - 1];
+ s->msg_len -= len;
+ if (s->msg_len) {
+ memmove(s->msg, s->msg + len, s->msg_len);
+ } else {
+ /* ??? Check if ATN (not yet implemented) is asserted and maybe
+ switch to PHASE_MO. */
+ switch (s->msg_action) {
+ case 0:
+ lsi_set_phase(s, PHASE_CMD);
+ break;
+ case 1:
+ lsi_disconnect(s);
+ break;
+ case 2:
+ lsi_set_phase(s, PHASE_DO);
+ break;
+ case 3:
+ lsi_set_phase(s, PHASE_DI);
+ break;
+ default:
+ abort();
+ }
+ }
+}
+
+/* Read the next byte during a MSGOUT phase. */
+static uint8_t lsi_get_msgbyte(LSIState *s)
+{
+ uint8_t data;
+ pci_dma_read(PCI_DEVICE(s), s->dnad, &data, 1);
+ s->dnad++;
+ s->dbc--;
+ return data;
+}
+
+/* Skip the next n bytes during a MSGOUT phase. */
+static void lsi_skip_msgbytes(LSIState *s, unsigned int n)
+{
+ s->dnad += n;
+ s->dbc -= n;
+}
+
+static void lsi_do_msgout(LSIState *s)
+{
+ uint8_t msg;
+ int len;
+ uint32_t current_tag;
+ lsi_request *current_req, *p, *p_next;
+
+ if (s->current) {
+ current_tag = s->current->tag;
+ current_req = s->current;
+ } else {
+ current_tag = s->select_tag;
+ current_req = lsi_find_by_tag(s, current_tag);
+ }
+
+ DPRINTF("MSG out len=%d\n", s->dbc);
+ while (s->dbc) {
+ msg = lsi_get_msgbyte(s);
+ s->sfbr = msg;
+
+ switch (msg) {
+ case 0x04:
+ DPRINTF("MSG: Disconnect\n");
+ lsi_disconnect(s);
+ break;
+ case 0x08:
+ DPRINTF("MSG: No Operation\n");
+ lsi_set_phase(s, PHASE_CMD);
+ break;
+ case 0x01:
+ len = lsi_get_msgbyte(s);
+ msg = lsi_get_msgbyte(s);
+ (void)len; /* avoid a warning about unused variable*/
+ DPRINTF("Extended message 0x%x (len %d)\n", msg, len);
+ switch (msg) {
+ case 1:
+ DPRINTF("SDTR (ignored)\n");
+ lsi_skip_msgbytes(s, 2);
+ break;
+ case 3:
+ DPRINTF("WDTR (ignored)\n");
+ lsi_skip_msgbytes(s, 1);
+ break;
+ default:
+ goto bad;
+ }
+ break;
+ case 0x20: /* SIMPLE queue */
+ s->select_tag |= lsi_get_msgbyte(s) | LSI_TAG_VALID;
+ DPRINTF("SIMPLE queue tag=0x%x\n", s->select_tag & 0xff);
+ break;
+ case 0x21: /* HEAD of queue */
+ BADF("HEAD queue not implemented\n");
+ s->select_tag |= lsi_get_msgbyte(s) | LSI_TAG_VALID;
+ break;
+ case 0x22: /* ORDERED queue */
+ BADF("ORDERED queue not implemented\n");
+ s->select_tag |= lsi_get_msgbyte(s) | LSI_TAG_VALID;
+ break;
+ case 0x0d:
+ /* The ABORT TAG message clears the current I/O process only. */
+ DPRINTF("MSG: ABORT TAG tag=0x%x\n", current_tag);
+ if (current_req) {
+ scsi_req_cancel(current_req->req);
+ }
+ lsi_disconnect(s);
+ break;
+ case 0x06:
+ case 0x0e:
+ case 0x0c:
+ /* The ABORT message clears all I/O processes for the selecting
+ initiator on the specified logical unit of the target. */
+ if (msg == 0x06) {
+ DPRINTF("MSG: ABORT tag=0x%x\n", current_tag);
+ }
+ /* The CLEAR QUEUE message clears all I/O processes for all
+ initiators on the specified logical unit of the target. */
+ if (msg == 0x0e) {
+ DPRINTF("MSG: CLEAR QUEUE tag=0x%x\n", current_tag);
+ }
+ /* The BUS DEVICE RESET message clears all I/O processes for all
+ initiators on all logical units of the target. */
+ if (msg == 0x0c) {
+ DPRINTF("MSG: BUS DEVICE RESET tag=0x%x\n", current_tag);
+ }
+
+ /* clear the current I/O process */
+ if (s->current) {
+ scsi_req_cancel(s->current->req);
+ }
+
+ /* As the current implemented devices scsi_disk and scsi_generic
+ only support one LUN, we don't need to keep track of LUNs.
+ Clearing I/O processes for other initiators could be possible
+ for scsi_generic by sending a SG_SCSI_RESET to the /dev/sgX
+ device, but this is currently not implemented (and seems not
+ to be really necessary). So let's simply clear all queued
+ commands for the current device: */
+ QTAILQ_FOREACH_SAFE(p, &s->queue, next, p_next) {
+ if ((p->tag & 0x0000ff00) == (current_tag & 0x0000ff00)) {
+ scsi_req_cancel(p->req);
+ }
+ }
+
+ lsi_disconnect(s);
+ break;
+ default:
+ if ((msg & 0x80) == 0) {
+ goto bad;
+ }
+ s->current_lun = msg & 7;
+ DPRINTF("Select LUN %d\n", s->current_lun);
+ lsi_set_phase(s, PHASE_CMD);
+ break;
+ }
+ }
+ return;
+bad:
+ BADF("Unimplemented message 0x%02x\n", msg);
+ lsi_set_phase(s, PHASE_MI);
+ lsi_add_msg_byte(s, 7); /* MESSAGE REJECT */
+ s->msg_action = 0;
+}
+
+#define LSI_BUF_SIZE 4096
+static void lsi_memcpy(LSIState *s, uint32_t dest, uint32_t src, int count)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int n;
+ uint8_t buf[LSI_BUF_SIZE];
+
+ DPRINTF("memcpy dest 0x%08x src 0x%08x count %d\n", dest, src, count);
+ while (count) {
+ n = (count > LSI_BUF_SIZE) ? LSI_BUF_SIZE : count;
+ pci_dma_read(d, src, buf, n);
+ pci_dma_write(d, dest, buf, n);
+ src += n;
+ dest += n;
+ count -= n;
+ }
+}
+
+static void lsi_wait_reselect(LSIState *s)
+{
+ lsi_request *p;
+
+ DPRINTF("Wait Reselect\n");
+
+ QTAILQ_FOREACH(p, &s->queue, next) {
+ if (p->pending) {
+ lsi_reselect(s, p);
+ break;
+ }
+ }
+ if (s->current == NULL) {
+ s->waiting = 1;
+ }
+}
+
+static void lsi_execute_script(LSIState *s)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ uint32_t insn;
+ uint32_t addr, addr_high;
+ int opcode;
+ int insn_processed = 0;
+
+ s->istat1 |= LSI_ISTAT1_SRUN;
+again:
+ insn_processed++;
+ insn = read_dword(s, s->dsp);
+ if (!insn) {
+ /* If we receive an empty opcode increment the DSP by 4 bytes
+ instead of 8 and execute the next opcode at that location */
+ s->dsp += 4;
+ goto again;
+ }
+ addr = read_dword(s, s->dsp + 4);
+ addr_high = 0;
+ DPRINTF("SCRIPTS dsp=%08x opcode %08x arg %08x\n", s->dsp, insn, addr);
+ s->dsps = addr;
+ s->dcmd = insn >> 24;
+ s->dsp += 8;
+ switch (insn >> 30) {
+ case 0: /* Block move. */
+ if (s->sist1 & LSI_SIST1_STO) {
+ DPRINTF("Delayed select timeout\n");
+ lsi_stop_script(s);
+ break;
+ }
+ s->dbc = insn & 0xffffff;
+ s->rbc = s->dbc;
+ /* ??? Set ESA. */
+ s->ia = s->dsp - 8;
+ if (insn & (1 << 29)) {
+ /* Indirect addressing. */
+ addr = read_dword(s, addr);
+ } else if (insn & (1 << 28)) {
+ uint32_t buf[2];
+ int32_t offset;
+ /* Table indirect addressing. */
+
+ /* 32-bit Table indirect */
+ offset = sextract32(addr, 0, 24);
+ pci_dma_read(pci_dev, s->dsa + offset, buf, 8);
+ /* byte count is stored in bits 0:23 only */
+ s->dbc = cpu_to_le32(buf[0]) & 0xffffff;
+ s->rbc = s->dbc;
+ addr = cpu_to_le32(buf[1]);
+
+ /* 40-bit DMA, upper addr bits [39:32] stored in first DWORD of
+ * table, bits [31:24] */
+ if (lsi_dma_40bit(s))
+ addr_high = cpu_to_le32(buf[0]) >> 24;
+ else if (lsi_dma_ti64bit(s)) {
+ int selector = (cpu_to_le32(buf[0]) >> 24) & 0x1f;
+ switch (selector) {
+ case 0 ... 0x0f:
+ /* offset index into scratch registers since
+ * TI64 mode can use registers C to R */
+ addr_high = s->scratch[2 + selector];
+ break;
+ case 0x10:
+ addr_high = s->mmrs;
+ break;
+ case 0x11:
+ addr_high = s->mmws;
+ break;
+ case 0x12:
+ addr_high = s->sfs;
+ break;
+ case 0x13:
+ addr_high = s->drs;
+ break;
+ case 0x14:
+ addr_high = s->sbms;
+ break;
+ case 0x15:
+ addr_high = s->dbms;
+ break;
+ default:
+ BADF("Illegal selector specified (0x%x > 0x15)"
+ " for 64-bit DMA block move", selector);
+ break;
+ }
+ }
+ } else if (lsi_dma_64bit(s)) {
+ /* fetch a 3rd dword if 64-bit direct move is enabled and
+ only if we're not doing table indirect or indirect addressing */
+ s->dbms = read_dword(s, s->dsp);
+ s->dsp += 4;
+ s->ia = s->dsp - 12;
+ }
+ if ((s->sstat1 & PHASE_MASK) != ((insn >> 24) & 7)) {
+ DPRINTF("Wrong phase got %d expected %d\n",
+ s->sstat1 & PHASE_MASK, (insn >> 24) & 7);
+ lsi_script_scsi_interrupt(s, LSI_SIST0_MA, 0);
+ break;
+ }
+ s->dnad = addr;
+ s->dnad64 = addr_high;
+ switch (s->sstat1 & 0x7) {
+ case PHASE_DO:
+ s->waiting = 2;
+ lsi_do_dma(s, 1);
+ if (s->waiting)
+ s->waiting = 3;
+ break;
+ case PHASE_DI:
+ s->waiting = 2;
+ lsi_do_dma(s, 0);
+ if (s->waiting)
+ s->waiting = 3;
+ break;
+ case PHASE_CMD:
+ lsi_do_command(s);
+ break;
+ case PHASE_ST:
+ lsi_do_status(s);
+ break;
+ case PHASE_MO:
+ lsi_do_msgout(s);
+ break;
+ case PHASE_MI:
+ lsi_do_msgin(s);
+ break;
+ default:
+ BADF("Unimplemented phase %d\n", s->sstat1 & PHASE_MASK);
+ exit(1);
+ }
+ s->dfifo = s->dbc & 0xff;
+ s->ctest5 = (s->ctest5 & 0xfc) | ((s->dbc >> 8) & 3);
+ s->sbc = s->dbc;
+ s->rbc -= s->dbc;
+ s->ua = addr + s->dbc;
+ break;
+
+ case 1: /* IO or Read/Write instruction. */
+ opcode = (insn >> 27) & 7;
+ if (opcode < 5) {
+ uint32_t id;
+
+ if (insn & (1 << 25)) {
+ id = read_dword(s, s->dsa + sextract32(insn, 0, 24));
+ } else {
+ id = insn;
+ }
+ id = (id >> 16) & 0xf;
+ if (insn & (1 << 26)) {
+ addr = s->dsp + sextract32(addr, 0, 24);
+ }
+ s->dnad = addr;
+ switch (opcode) {
+ case 0: /* Select */
+ s->sdid = id;
+ if (s->scntl1 & LSI_SCNTL1_CON) {
+ DPRINTF("Already reselected, jumping to alternative address\n");
+ s->dsp = s->dnad;
+ break;
+ }
+ s->sstat0 |= LSI_SSTAT0_WOA;
+ s->scntl1 &= ~LSI_SCNTL1_IARB;
+ if (!scsi_device_find(&s->bus, 0, id, 0)) {
+ lsi_bad_selection(s, id);
+ break;
+ }
+ DPRINTF("Selected target %d%s\n",
+ id, insn & (1 << 3) ? " ATN" : "");
+ /* ??? Linux drivers compain when this is set. Maybe
+ it only applies in low-level mode (unimplemented).
+ lsi_script_scsi_interrupt(s, LSI_SIST0_CMP, 0); */
+ s->select_tag = id << 8;
+ s->scntl1 |= LSI_SCNTL1_CON;
+ if (insn & (1 << 3)) {
+ s->socl |= LSI_SOCL_ATN;
+ }
+ lsi_set_phase(s, PHASE_MO);
+ break;
+ case 1: /* Disconnect */
+ DPRINTF("Wait Disconnect\n");
+ s->scntl1 &= ~LSI_SCNTL1_CON;
+ break;
+ case 2: /* Wait Reselect */
+ if (!lsi_irq_on_rsl(s)) {
+ lsi_wait_reselect(s);
+ }
+ break;
+ case 3: /* Set */
+ DPRINTF("Set%s%s%s%s\n",
+ insn & (1 << 3) ? " ATN" : "",
+ insn & (1 << 6) ? " ACK" : "",
+ insn & (1 << 9) ? " TM" : "",
+ insn & (1 << 10) ? " CC" : "");
+ if (insn & (1 << 3)) {
+ s->socl |= LSI_SOCL_ATN;
+ lsi_set_phase(s, PHASE_MO);
+ }
+ if (insn & (1 << 9)) {
+ BADF("Target mode not implemented\n");
+ exit(1);
+ }
+ if (insn & (1 << 10))
+ s->carry = 1;
+ break;
+ case 4: /* Clear */
+ DPRINTF("Clear%s%s%s%s\n",
+ insn & (1 << 3) ? " ATN" : "",
+ insn & (1 << 6) ? " ACK" : "",
+ insn & (1 << 9) ? " TM" : "",
+ insn & (1 << 10) ? " CC" : "");
+ if (insn & (1 << 3)) {
+ s->socl &= ~LSI_SOCL_ATN;
+ }
+ if (insn & (1 << 10))
+ s->carry = 0;
+ break;
+ }
+ } else {
+ uint8_t op0;
+ uint8_t op1;
+ uint8_t data8;
+ int reg;
+ int operator;
+#ifdef DEBUG_LSI
+ static const char *opcode_names[3] =
+ {"Write", "Read", "Read-Modify-Write"};
+ static const char *operator_names[8] =
+ {"MOV", "SHL", "OR", "XOR", "AND", "SHR", "ADD", "ADC"};
+#endif
+
+ reg = ((insn >> 16) & 0x7f) | (insn & 0x80);
+ data8 = (insn >> 8) & 0xff;
+ opcode = (insn >> 27) & 7;
+ operator = (insn >> 24) & 7;
+ DPRINTF("%s reg 0x%x %s data8=0x%02x sfbr=0x%02x%s\n",
+ opcode_names[opcode - 5], reg,
+ operator_names[operator], data8, s->sfbr,
+ (insn & (1 << 23)) ? " SFBR" : "");
+ op0 = op1 = 0;
+ switch (opcode) {
+ case 5: /* From SFBR */
+ op0 = s->sfbr;
+ op1 = data8;
+ break;
+ case 6: /* To SFBR */
+ if (operator)
+ op0 = lsi_reg_readb(s, reg);
+ op1 = data8;
+ break;
+ case 7: /* Read-modify-write */
+ if (operator)
+ op0 = lsi_reg_readb(s, reg);
+ if (insn & (1 << 23)) {
+ op1 = s->sfbr;
+ } else {
+ op1 = data8;
+ }
+ break;
+ }
+
+ switch (operator) {
+ case 0: /* move */
+ op0 = op1;
+ break;
+ case 1: /* Shift left */
+ op1 = op0 >> 7;
+ op0 = (op0 << 1) | s->carry;
+ s->carry = op1;
+ break;
+ case 2: /* OR */
+ op0 |= op1;
+ break;
+ case 3: /* XOR */
+ op0 ^= op1;
+ break;
+ case 4: /* AND */
+ op0 &= op1;
+ break;
+ case 5: /* SHR */
+ op1 = op0 & 1;
+ op0 = (op0 >> 1) | (s->carry << 7);
+ s->carry = op1;
+ break;
+ case 6: /* ADD */
+ op0 += op1;
+ s->carry = op0 < op1;
+ break;
+ case 7: /* ADC */
+ op0 += op1 + s->carry;
+ if (s->carry)
+ s->carry = op0 <= op1;
+ else
+ s->carry = op0 < op1;
+ break;
+ }
+
+ switch (opcode) {
+ case 5: /* From SFBR */
+ case 7: /* Read-modify-write */
+ lsi_reg_writeb(s, reg, op0);
+ break;
+ case 6: /* To SFBR */
+ s->sfbr = op0;
+ break;
+ }
+ }
+ break;
+
+ case 2: /* Transfer Control. */
+ {
+ int cond;
+ int jmp;
+
+ if ((insn & 0x002e0000) == 0) {
+ DPRINTF("NOP\n");
+ break;
+ }
+ if (s->sist1 & LSI_SIST1_STO) {
+ DPRINTF("Delayed select timeout\n");
+ lsi_stop_script(s);
+ break;
+ }
+ cond = jmp = (insn & (1 << 19)) != 0;
+ if (cond == jmp && (insn & (1 << 21))) {
+ DPRINTF("Compare carry %d\n", s->carry == jmp);
+ cond = s->carry != 0;
+ }
+ if (cond == jmp && (insn & (1 << 17))) {
+ DPRINTF("Compare phase %d %c= %d\n",
+ (s->sstat1 & PHASE_MASK),
+ jmp ? '=' : '!',
+ ((insn >> 24) & 7));
+ cond = (s->sstat1 & PHASE_MASK) == ((insn >> 24) & 7);
+ }
+ if (cond == jmp && (insn & (1 << 18))) {
+ uint8_t mask;
+
+ mask = (~insn >> 8) & 0xff;
+ DPRINTF("Compare data 0x%x & 0x%x %c= 0x%x\n",
+ s->sfbr, mask, jmp ? '=' : '!', insn & mask);
+ cond = (s->sfbr & mask) == (insn & mask);
+ }
+ if (cond == jmp) {
+ if (insn & (1 << 23)) {
+ /* Relative address. */
+ addr = s->dsp + sextract32(addr, 0, 24);
+ }
+ switch ((insn >> 27) & 7) {
+ case 0: /* Jump */
+ DPRINTF("Jump to 0x%08x\n", addr);
+ s->adder = addr;
+ s->dsp = addr;
+ break;
+ case 1: /* Call */
+ DPRINTF("Call 0x%08x\n", addr);
+ s->temp = s->dsp;
+ s->dsp = addr;
+ break;
+ case 2: /* Return */
+ DPRINTF("Return to 0x%08x\n", s->temp);
+ s->dsp = s->temp;
+ break;
+ case 3: /* Interrupt */
+ DPRINTF("Interrupt 0x%08x\n", s->dsps);
+ if ((insn & (1 << 20)) != 0) {
+ s->istat0 |= LSI_ISTAT0_INTF;
+ lsi_update_irq(s);
+ } else {
+ lsi_script_dma_interrupt(s, LSI_DSTAT_SIR);
+ }
+ break;
+ default:
+ DPRINTF("Illegal transfer control\n");
+ lsi_script_dma_interrupt(s, LSI_DSTAT_IID);
+ break;
+ }
+ } else {
+ DPRINTF("Control condition failed\n");
+ }
+ }
+ break;
+
+ case 3:
+ if ((insn & (1 << 29)) == 0) {
+ /* Memory move. */
+ uint32_t dest;
+ /* ??? The docs imply the destination address is loaded into
+ the TEMP register. However the Linux drivers rely on
+ the value being presrved. */
+ dest = read_dword(s, s->dsp);
+ s->dsp += 4;
+ lsi_memcpy(s, dest, addr, insn & 0xffffff);
+ } else {
+ uint8_t data[7];
+ int reg;
+ int n;
+ int i;
+
+ if (insn & (1 << 28)) {
+ addr = s->dsa + sextract32(addr, 0, 24);
+ }
+ n = (insn & 7);
+ reg = (insn >> 16) & 0xff;
+ if (insn & (1 << 24)) {
+ pci_dma_read(pci_dev, addr, data, n);
+ DPRINTF("Load reg 0x%x size %d addr 0x%08x = %08x\n", reg, n,
+ addr, *(int *)data);
+ for (i = 0; i < n; i++) {
+ lsi_reg_writeb(s, reg + i, data[i]);
+ }
+ } else {
+ DPRINTF("Store reg 0x%x size %d addr 0x%08x\n", reg, n, addr);
+ for (i = 0; i < n; i++) {
+ data[i] = lsi_reg_readb(s, reg + i);
+ }
+ pci_dma_write(pci_dev, addr, data, n);
+ }
+ }
+ }
+ if (insn_processed > 10000 && !s->waiting) {
+ /* Some windows drivers make the device spin waiting for a memory
+ location to change. If we have been executed a lot of code then
+ assume this is the case and force an unexpected device disconnect.
+ This is apparently sufficient to beat the drivers into submission.
+ */
+ if (!(s->sien0 & LSI_SIST0_UDC))
+ fprintf(stderr, "inf. loop with UDC masked\n");
+ lsi_script_scsi_interrupt(s, LSI_SIST0_UDC, 0);
+ lsi_disconnect(s);
+ } else if (s->istat1 & LSI_ISTAT1_SRUN && !s->waiting) {
+ if (s->dcntl & LSI_DCNTL_SSM) {
+ lsi_script_dma_interrupt(s, LSI_DSTAT_SSI);
+ } else {
+ goto again;
+ }
+ }
+ DPRINTF("SCRIPTS execution stopped\n");
+}
+
+static uint8_t lsi_reg_readb(LSIState *s, int offset)
+{
+ uint8_t tmp;
+#define CASE_GET_REG24(name, addr) \
+ case addr: return s->name & 0xff; \
+ case addr + 1: return (s->name >> 8) & 0xff; \
+ case addr + 2: return (s->name >> 16) & 0xff;
+
+#define CASE_GET_REG32(name, addr) \
+ case addr: return s->name & 0xff; \
+ case addr + 1: return (s->name >> 8) & 0xff; \
+ case addr + 2: return (s->name >> 16) & 0xff; \
+ case addr + 3: return (s->name >> 24) & 0xff;
+
+#ifdef DEBUG_LSI_REG
+ DPRINTF("Read reg %x\n", offset);
+#endif
+ switch (offset) {
+ case 0x00: /* SCNTL0 */
+ return s->scntl0;
+ case 0x01: /* SCNTL1 */
+ return s->scntl1;
+ case 0x02: /* SCNTL2 */
+ return s->scntl2;
+ case 0x03: /* SCNTL3 */
+ return s->scntl3;
+ case 0x04: /* SCID */
+ return s->scid;
+ case 0x05: /* SXFER */
+ return s->sxfer;
+ case 0x06: /* SDID */
+ return s->sdid;
+ case 0x07: /* GPREG0 */
+ return 0x7f;
+ case 0x08: /* Revision ID */
+ return 0x00;
+ case 0x09: /* SOCL */
+ return s->socl;
+ case 0xa: /* SSID */
+ return s->ssid;
+ case 0xb: /* SBCL */
+ /* ??? This is not correct. However it's (hopefully) only
+ used for diagnostics, so should be ok. */
+ return 0;
+ case 0xc: /* DSTAT */
+ tmp = s->dstat | LSI_DSTAT_DFE;
+ if ((s->istat0 & LSI_ISTAT0_INTF) == 0)
+ s->dstat = 0;
+ lsi_update_irq(s);
+ return tmp;
+ case 0x0d: /* SSTAT0 */
+ return s->sstat0;
+ case 0x0e: /* SSTAT1 */
+ return s->sstat1;
+ case 0x0f: /* SSTAT2 */
+ return s->scntl1 & LSI_SCNTL1_CON ? 0 : 2;
+ CASE_GET_REG32(dsa, 0x10)
+ case 0x14: /* ISTAT0 */
+ return s->istat0;
+ case 0x15: /* ISTAT1 */
+ return s->istat1;
+ case 0x16: /* MBOX0 */
+ return s->mbox0;
+ case 0x17: /* MBOX1 */
+ return s->mbox1;
+ case 0x18: /* CTEST0 */
+ return 0xff;
+ case 0x19: /* CTEST1 */
+ return 0;
+ case 0x1a: /* CTEST2 */
+ tmp = s->ctest2 | LSI_CTEST2_DACK | LSI_CTEST2_CM;
+ if (s->istat0 & LSI_ISTAT0_SIGP) {
+ s->istat0 &= ~LSI_ISTAT0_SIGP;
+ tmp |= LSI_CTEST2_SIGP;
+ }
+ return tmp;
+ case 0x1b: /* CTEST3 */
+ return s->ctest3;
+ CASE_GET_REG32(temp, 0x1c)
+ case 0x20: /* DFIFO */
+ return 0;
+ case 0x21: /* CTEST4 */
+ return s->ctest4;
+ case 0x22: /* CTEST5 */
+ return s->ctest5;
+ case 0x23: /* CTEST6 */
+ return 0;
+ CASE_GET_REG24(dbc, 0x24)
+ case 0x27: /* DCMD */
+ return s->dcmd;
+ CASE_GET_REG32(dnad, 0x28)
+ CASE_GET_REG32(dsp, 0x2c)
+ CASE_GET_REG32(dsps, 0x30)
+ CASE_GET_REG32(scratch[0], 0x34)
+ case 0x38: /* DMODE */
+ return s->dmode;
+ case 0x39: /* DIEN */
+ return s->dien;
+ case 0x3a: /* SBR */
+ return s->sbr;
+ case 0x3b: /* DCNTL */
+ return s->dcntl;
+ /* ADDER Output (Debug of relative jump address) */
+ CASE_GET_REG32(adder, 0x3c)
+ case 0x40: /* SIEN0 */
+ return s->sien0;
+ case 0x41: /* SIEN1 */
+ return s->sien1;
+ case 0x42: /* SIST0 */
+ tmp = s->sist0;
+ s->sist0 = 0;
+ lsi_update_irq(s);
+ return tmp;
+ case 0x43: /* SIST1 */
+ tmp = s->sist1;
+ s->sist1 = 0;
+ lsi_update_irq(s);
+ return tmp;
+ case 0x46: /* MACNTL */
+ return 0x0f;
+ case 0x47: /* GPCNTL0 */
+ return 0x0f;
+ case 0x48: /* STIME0 */
+ return s->stime0;
+ case 0x4a: /* RESPID0 */
+ return s->respid0;
+ case 0x4b: /* RESPID1 */
+ return s->respid1;
+ case 0x4d: /* STEST1 */
+ return s->stest1;
+ case 0x4e: /* STEST2 */
+ return s->stest2;
+ case 0x4f: /* STEST3 */
+ return s->stest3;
+ case 0x50: /* SIDL */
+ /* This is needed by the linux drivers. We currently only update it
+ during the MSG IN phase. */
+ return s->sidl;
+ case 0x52: /* STEST4 */
+ return 0xe0;
+ case 0x56: /* CCNTL0 */
+ return s->ccntl0;
+ case 0x57: /* CCNTL1 */
+ return s->ccntl1;
+ case 0x58: /* SBDL */
+ /* Some drivers peek at the data bus during the MSG IN phase. */
+ if ((s->sstat1 & PHASE_MASK) == PHASE_MI)
+ return s->msg[0];
+ return 0;
+ case 0x59: /* SBDL high */
+ return 0;
+ CASE_GET_REG32(mmrs, 0xa0)
+ CASE_GET_REG32(mmws, 0xa4)
+ CASE_GET_REG32(sfs, 0xa8)
+ CASE_GET_REG32(drs, 0xac)
+ CASE_GET_REG32(sbms, 0xb0)
+ CASE_GET_REG32(dbms, 0xb4)
+ CASE_GET_REG32(dnad64, 0xb8)
+ CASE_GET_REG32(pmjad1, 0xc0)
+ CASE_GET_REG32(pmjad2, 0xc4)
+ CASE_GET_REG32(rbc, 0xc8)
+ CASE_GET_REG32(ua, 0xcc)
+ CASE_GET_REG32(ia, 0xd4)
+ CASE_GET_REG32(sbc, 0xd8)
+ CASE_GET_REG32(csbc, 0xdc)
+ }
+ if (offset >= 0x5c && offset < 0xa0) {
+ int n;
+ int shift;
+ n = (offset - 0x58) >> 2;
+ shift = (offset & 3) * 8;
+ return (s->scratch[n] >> shift) & 0xff;
+ }
+ BADF("readb 0x%x\n", offset);
+ exit(1);
+#undef CASE_GET_REG24
+#undef CASE_GET_REG32
+}
+
+static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val)
+{
+#define CASE_SET_REG24(name, addr) \
+ case addr : s->name &= 0xffffff00; s->name |= val; break; \
+ case addr + 1: s->name &= 0xffff00ff; s->name |= val << 8; break; \
+ case addr + 2: s->name &= 0xff00ffff; s->name |= val << 16; break;
+
+#define CASE_SET_REG32(name, addr) \
+ case addr : s->name &= 0xffffff00; s->name |= val; break; \
+ case addr + 1: s->name &= 0xffff00ff; s->name |= val << 8; break; \
+ case addr + 2: s->name &= 0xff00ffff; s->name |= val << 16; break; \
+ case addr + 3: s->name &= 0x00ffffff; s->name |= val << 24; break;
+
+#ifdef DEBUG_LSI_REG
+ DPRINTF("Write reg %x = %02x\n", offset, val);
+#endif
+ switch (offset) {
+ case 0x00: /* SCNTL0 */
+ s->scntl0 = val;
+ if (val & LSI_SCNTL0_START) {
+ BADF("Start sequence not implemented\n");
+ }
+ break;
+ case 0x01: /* SCNTL1 */
+ s->scntl1 = val & ~LSI_SCNTL1_SST;
+ if (val & LSI_SCNTL1_IARB) {
+ BADF("Immediate Arbritration not implemented\n");
+ }
+ if (val & LSI_SCNTL1_RST) {
+ if (!(s->sstat0 & LSI_SSTAT0_RST)) {
+ qbus_reset_all(&s->bus.qbus);
+ s->sstat0 |= LSI_SSTAT0_RST;
+ lsi_script_scsi_interrupt(s, LSI_SIST0_RST, 0);
+ }
+ } else {
+ s->sstat0 &= ~LSI_SSTAT0_RST;
+ }
+ break;
+ case 0x02: /* SCNTL2 */
+ val &= ~(LSI_SCNTL2_WSR | LSI_SCNTL2_WSS);
+ s->scntl2 = val;
+ break;
+ case 0x03: /* SCNTL3 */
+ s->scntl3 = val;
+ break;
+ case 0x04: /* SCID */
+ s->scid = val;
+ break;
+ case 0x05: /* SXFER */
+ s->sxfer = val;
+ break;
+ case 0x06: /* SDID */
+ if ((s->ssid & 0x80) && (val & 0xf) != (s->ssid & 0xf)) {
+ BADF("Destination ID does not match SSID\n");
+ }
+ s->sdid = val & 0xf;
+ break;
+ case 0x07: /* GPREG0 */
+ break;
+ case 0x08: /* SFBR */
+ /* The CPU is not allowed to write to this register. However the
+ SCRIPTS register move instructions are. */
+ s->sfbr = val;
+ break;
+ case 0x0a: case 0x0b:
+ /* Openserver writes to these readonly registers on startup */
+ return;
+ case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+ /* Linux writes to these readonly registers on startup. */
+ return;
+ CASE_SET_REG32(dsa, 0x10)
+ case 0x14: /* ISTAT0 */
+ s->istat0 = (s->istat0 & 0x0f) | (val & 0xf0);
+ if (val & LSI_ISTAT0_ABRT) {
+ lsi_script_dma_interrupt(s, LSI_DSTAT_ABRT);
+ }
+ if (val & LSI_ISTAT0_INTF) {
+ s->istat0 &= ~LSI_ISTAT0_INTF;
+ lsi_update_irq(s);
+ }
+ if (s->waiting == 1 && val & LSI_ISTAT0_SIGP) {
+ DPRINTF("Woken by SIGP\n");
+ s->waiting = 0;
+ s->dsp = s->dnad;
+ lsi_execute_script(s);
+ }
+ if (val & LSI_ISTAT0_SRST) {
+ qdev_reset_all(DEVICE(s));
+ }
+ break;
+ case 0x16: /* MBOX0 */
+ s->mbox0 = val;
+ break;
+ case 0x17: /* MBOX1 */
+ s->mbox1 = val;
+ break;
+ case 0x18: /* CTEST0 */
+ /* nothing to do */
+ break;
+ case 0x1a: /* CTEST2 */
+ s->ctest2 = val & LSI_CTEST2_PCICIE;
+ break;
+ case 0x1b: /* CTEST3 */
+ s->ctest3 = val & 0x0f;
+ break;
+ CASE_SET_REG32(temp, 0x1c)
+ case 0x21: /* CTEST4 */
+ if (val & 7) {
+ BADF("Unimplemented CTEST4-FBL 0x%x\n", val);
+ }
+ s->ctest4 = val;
+ break;
+ case 0x22: /* CTEST5 */
+ if (val & (LSI_CTEST5_ADCK | LSI_CTEST5_BBCK)) {
+ BADF("CTEST5 DMA increment not implemented\n");
+ }
+ s->ctest5 = val;
+ break;
+ CASE_SET_REG24(dbc, 0x24)
+ CASE_SET_REG32(dnad, 0x28)
+ case 0x2c: /* DSP[0:7] */
+ s->dsp &= 0xffffff00;
+ s->dsp |= val;
+ break;
+ case 0x2d: /* DSP[8:15] */
+ s->dsp &= 0xffff00ff;
+ s->dsp |= val << 8;
+ break;
+ case 0x2e: /* DSP[16:23] */
+ s->dsp &= 0xff00ffff;
+ s->dsp |= val << 16;
+ break;
+ case 0x2f: /* DSP[24:31] */
+ s->dsp &= 0x00ffffff;
+ s->dsp |= val << 24;
+ if ((s->dmode & LSI_DMODE_MAN) == 0
+ && (s->istat1 & LSI_ISTAT1_SRUN) == 0)
+ lsi_execute_script(s);
+ break;
+ CASE_SET_REG32(dsps, 0x30)
+ CASE_SET_REG32(scratch[0], 0x34)
+ case 0x38: /* DMODE */
+ if (val & (LSI_DMODE_SIOM | LSI_DMODE_DIOM)) {
+ BADF("IO mappings not implemented\n");
+ }
+ s->dmode = val;
+ break;
+ case 0x39: /* DIEN */
+ s->dien = val;
+ lsi_update_irq(s);
+ break;
+ case 0x3a: /* SBR */
+ s->sbr = val;
+ break;
+ case 0x3b: /* DCNTL */
+ s->dcntl = val & ~(LSI_DCNTL_PFF | LSI_DCNTL_STD);
+ if ((val & LSI_DCNTL_STD) && (s->istat1 & LSI_ISTAT1_SRUN) == 0)
+ lsi_execute_script(s);
+ break;
+ case 0x40: /* SIEN0 */
+ s->sien0 = val;
+ lsi_update_irq(s);
+ break;
+ case 0x41: /* SIEN1 */
+ s->sien1 = val;
+ lsi_update_irq(s);
+ break;
+ case 0x47: /* GPCNTL0 */
+ break;
+ case 0x48: /* STIME0 */
+ s->stime0 = val;
+ break;
+ case 0x49: /* STIME1 */
+ if (val & 0xf) {
+ DPRINTF("General purpose timer not implemented\n");
+ /* ??? Raising the interrupt immediately seems to be sufficient
+ to keep the FreeBSD driver happy. */
+ lsi_script_scsi_interrupt(s, 0, LSI_SIST1_GEN);
+ }
+ break;
+ case 0x4a: /* RESPID0 */
+ s->respid0 = val;
+ break;
+ case 0x4b: /* RESPID1 */
+ s->respid1 = val;
+ break;
+ case 0x4d: /* STEST1 */
+ s->stest1 = val;
+ break;
+ case 0x4e: /* STEST2 */
+ if (val & 1) {
+ BADF("Low level mode not implemented\n");
+ }
+ s->stest2 = val;
+ break;
+ case 0x4f: /* STEST3 */
+ if (val & 0x41) {
+ BADF("SCSI FIFO test mode not implemented\n");
+ }
+ s->stest3 = val;
+ break;
+ case 0x56: /* CCNTL0 */
+ s->ccntl0 = val;
+ break;
+ case 0x57: /* CCNTL1 */
+ s->ccntl1 = val;
+ break;
+ CASE_SET_REG32(mmrs, 0xa0)
+ CASE_SET_REG32(mmws, 0xa4)
+ CASE_SET_REG32(sfs, 0xa8)
+ CASE_SET_REG32(drs, 0xac)
+ CASE_SET_REG32(sbms, 0xb0)
+ CASE_SET_REG32(dbms, 0xb4)
+ CASE_SET_REG32(dnad64, 0xb8)
+ CASE_SET_REG32(pmjad1, 0xc0)
+ CASE_SET_REG32(pmjad2, 0xc4)
+ CASE_SET_REG32(rbc, 0xc8)
+ CASE_SET_REG32(ua, 0xcc)
+ CASE_SET_REG32(ia, 0xd4)
+ CASE_SET_REG32(sbc, 0xd8)
+ CASE_SET_REG32(csbc, 0xdc)
+ default:
+ if (offset >= 0x5c && offset < 0xa0) {
+ int n;
+ int shift;
+ n = (offset - 0x58) >> 2;
+ shift = (offset & 3) * 8;
+ s->scratch[n] = deposit32(s->scratch[n], shift, 8, val);
+ } else {
+ BADF("Unhandled writeb 0x%x = 0x%x\n", offset, val);
+ }
+ }
+#undef CASE_SET_REG24
+#undef CASE_SET_REG32
+}
+
+static void lsi_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ LSIState *s = opaque;
+
+ lsi_reg_writeb(s, addr & 0xff, val);
+}
+
+static uint64_t lsi_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LSIState *s = opaque;
+
+ return lsi_reg_readb(s, addr & 0xff);
+}
+
+static const MemoryRegionOps lsi_mmio_ops = {
+ .read = lsi_mmio_read,
+ .write = lsi_mmio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void lsi_ram_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ LSIState *s = opaque;
+ uint32_t newval;
+ uint32_t mask;
+ int shift;
+
+ newval = s->script_ram[addr >> 2];
+ shift = (addr & 3) * 8;
+ mask = ((uint64_t)1 << (size * 8)) - 1;
+ newval &= ~(mask << shift);
+ newval |= val << shift;
+ s->script_ram[addr >> 2] = newval;
+}
+
+static uint64_t lsi_ram_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LSIState *s = opaque;
+ uint32_t val;
+ uint32_t mask;
+
+ val = s->script_ram[addr >> 2];
+ mask = ((uint64_t)1 << (size * 8)) - 1;
+ val >>= (addr & 3) * 8;
+ return val & mask;
+}
+
+static const MemoryRegionOps lsi_ram_ops = {
+ .read = lsi_ram_read,
+ .write = lsi_ram_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t lsi_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LSIState *s = opaque;
+ return lsi_reg_readb(s, addr & 0xff);
+}
+
+static void lsi_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ LSIState *s = opaque;
+ lsi_reg_writeb(s, addr & 0xff, val);
+}
+
+static const MemoryRegionOps lsi_io_ops = {
+ .read = lsi_io_read,
+ .write = lsi_io_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void lsi_scsi_reset(DeviceState *dev)
+{
+ LSIState *s = LSI53C895A(dev);
+
+ lsi_soft_reset(s);
+}
+
+static void lsi_pre_save(void *opaque)
+{
+ LSIState *s = opaque;
+
+ if (s->current) {
+ assert(s->current->dma_buf == NULL);
+ assert(s->current->dma_len == 0);
+ }
+ assert(QTAILQ_EMPTY(&s->queue));
+}
+
+static const VMStateDescription vmstate_lsi_scsi = {
+ .name = "lsiscsi",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_save = lsi_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, LSIState),
+
+ VMSTATE_INT32(carry, LSIState),
+ VMSTATE_INT32(status, LSIState),
+ VMSTATE_INT32(msg_action, LSIState),
+ VMSTATE_INT32(msg_len, LSIState),
+ VMSTATE_BUFFER(msg, LSIState),
+ VMSTATE_INT32(waiting, LSIState),
+
+ VMSTATE_UINT32(dsa, LSIState),
+ VMSTATE_UINT32(temp, LSIState),
+ VMSTATE_UINT32(dnad, LSIState),
+ VMSTATE_UINT32(dbc, LSIState),
+ VMSTATE_UINT8(istat0, LSIState),
+ VMSTATE_UINT8(istat1, LSIState),
+ VMSTATE_UINT8(dcmd, LSIState),
+ VMSTATE_UINT8(dstat, LSIState),
+ VMSTATE_UINT8(dien, LSIState),
+ VMSTATE_UINT8(sist0, LSIState),
+ VMSTATE_UINT8(sist1, LSIState),
+ VMSTATE_UINT8(sien0, LSIState),
+ VMSTATE_UINT8(sien1, LSIState),
+ VMSTATE_UINT8(mbox0, LSIState),
+ VMSTATE_UINT8(mbox1, LSIState),
+ VMSTATE_UINT8(dfifo, LSIState),
+ VMSTATE_UINT8(ctest2, LSIState),
+ VMSTATE_UINT8(ctest3, LSIState),
+ VMSTATE_UINT8(ctest4, LSIState),
+ VMSTATE_UINT8(ctest5, LSIState),
+ VMSTATE_UINT8(ccntl0, LSIState),
+ VMSTATE_UINT8(ccntl1, LSIState),
+ VMSTATE_UINT32(dsp, LSIState),
+ VMSTATE_UINT32(dsps, LSIState),
+ VMSTATE_UINT8(dmode, LSIState),
+ VMSTATE_UINT8(dcntl, LSIState),
+ VMSTATE_UINT8(scntl0, LSIState),
+ VMSTATE_UINT8(scntl1, LSIState),
+ VMSTATE_UINT8(scntl2, LSIState),
+ VMSTATE_UINT8(scntl3, LSIState),
+ VMSTATE_UINT8(sstat0, LSIState),
+ VMSTATE_UINT8(sstat1, LSIState),
+ VMSTATE_UINT8(scid, LSIState),
+ VMSTATE_UINT8(sxfer, LSIState),
+ VMSTATE_UINT8(socl, LSIState),
+ VMSTATE_UINT8(sdid, LSIState),
+ VMSTATE_UINT8(ssid, LSIState),
+ VMSTATE_UINT8(sfbr, LSIState),
+ VMSTATE_UINT8(stest1, LSIState),
+ VMSTATE_UINT8(stest2, LSIState),
+ VMSTATE_UINT8(stest3, LSIState),
+ VMSTATE_UINT8(sidl, LSIState),
+ VMSTATE_UINT8(stime0, LSIState),
+ VMSTATE_UINT8(respid0, LSIState),
+ VMSTATE_UINT8(respid1, LSIState),
+ VMSTATE_UINT32(mmrs, LSIState),
+ VMSTATE_UINT32(mmws, LSIState),
+ VMSTATE_UINT32(sfs, LSIState),
+ VMSTATE_UINT32(drs, LSIState),
+ VMSTATE_UINT32(sbms, LSIState),
+ VMSTATE_UINT32(dbms, LSIState),
+ VMSTATE_UINT32(dnad64, LSIState),
+ VMSTATE_UINT32(pmjad1, LSIState),
+ VMSTATE_UINT32(pmjad2, LSIState),
+ VMSTATE_UINT32(rbc, LSIState),
+ VMSTATE_UINT32(ua, LSIState),
+ VMSTATE_UINT32(ia, LSIState),
+ VMSTATE_UINT32(sbc, LSIState),
+ VMSTATE_UINT32(csbc, LSIState),
+ VMSTATE_BUFFER_UNSAFE(scratch, LSIState, 0, 18 * sizeof(uint32_t)),
+ VMSTATE_UINT8(sbr, LSIState),
+
+ VMSTATE_BUFFER_UNSAFE(script_ram, LSIState, 0, 2048 * sizeof(uint32_t)),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const struct SCSIBusInfo lsi_scsi_info = {
+ .tcq = true,
+ .max_target = LSI_MAX_DEVS,
+ .max_lun = 0, /* LUN support is buggy */
+
+ .transfer_data = lsi_transfer_data,
+ .complete = lsi_command_complete,
+ .cancel = lsi_request_cancelled
+};
+
+static void lsi_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ LSIState *s = LSI53C895A(dev);
+ DeviceState *d = DEVICE(dev);
+ uint8_t *pci_conf;
+
+ pci_conf = dev->config;
+
+ /* PCI latency timer = 255 */
+ pci_conf[PCI_LATENCY_TIMER] = 0xff;
+ /* Interrupt pin A */
+ pci_conf[PCI_INTERRUPT_PIN] = 0x01;
+
+ memory_region_init_io(&s->mmio_io, OBJECT(s), &lsi_mmio_ops, s,
+ "lsi-mmio", 0x400);
+ memory_region_init_io(&s->ram_io, OBJECT(s), &lsi_ram_ops, s,
+ "lsi-ram", 0x2000);
+ memory_region_init_io(&s->io_io, OBJECT(s), &lsi_io_ops, s,
+ "lsi-io", 256);
+
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io_io);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio_io);
+ pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->ram_io);
+ QTAILQ_INIT(&s->queue);
+
+ scsi_bus_new(&s->bus, sizeof(s->bus), d, &lsi_scsi_info, NULL);
+ if (!d->hotplugged) {
+ scsi_bus_legacy_handle_cmdline(&s->bus, errp);
+ }
+}
+
+static void lsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = lsi_scsi_realize;
+ k->vendor_id = PCI_VENDOR_ID_LSI_LOGIC;
+ k->device_id = PCI_DEVICE_ID_LSI_53C895A;
+ k->class_id = PCI_CLASS_STORAGE_SCSI;
+ k->subsystem_id = 0x1000;
+ dc->reset = lsi_scsi_reset;
+ dc->vmsd = &vmstate_lsi_scsi;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo lsi_info = {
+ .name = TYPE_LSI53C895A,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(LSIState),
+ .class_init = lsi_class_init,
+};
+
+static void lsi53c810_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->device_id = PCI_DEVICE_ID_LSI_53C810;
+}
+
+static TypeInfo lsi53c810_info = {
+ .name = TYPE_LSI53C810,
+ .parent = TYPE_LSI53C895A,
+ .class_init = lsi53c810_class_init,
+};
+
+static void lsi53c895a_register_types(void)
+{
+ type_register_static(&lsi_info);
+ type_register_static(&lsi53c810_info);
+}
+
+type_init(lsi53c895a_register_types)
diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c
new file mode 100644
index 00000000..d9b99c0b
--- /dev/null
+++ b/hw/scsi/megasas.c
@@ -0,0 +1,2547 @@
+/*
+ * QEMU MegaRAID SAS 8708EM2 Host Bus Adapter emulation
+ * Based on the linux driver code at drivers/scsi/megaraid
+ *
+ * Copyright (c) 2009-2012 Hannes Reinecke, SUSE Labs
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "sysemu/dma.h"
+#include "sysemu/block-backend.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "qemu/iov.h"
+#include "hw/scsi/scsi.h"
+#include "block/scsi.h"
+#include "trace.h"
+
+#include "mfi.h"
+
+#define MEGASAS_VERSION_GEN1 "1.70"
+#define MEGASAS_VERSION_GEN2 "1.80"
+#define MEGASAS_MAX_FRAMES 2048 /* Firmware limit at 65535 */
+#define MEGASAS_DEFAULT_FRAMES 1000 /* Windows requires this */
+#define MEGASAS_GEN2_DEFAULT_FRAMES 1008 /* Windows requires this */
+#define MEGASAS_MAX_SGE 128 /* Firmware limit */
+#define MEGASAS_DEFAULT_SGE 80
+#define MEGASAS_MAX_SECTORS 0xFFFF /* No real limit */
+#define MEGASAS_MAX_ARRAYS 128
+
+#define MEGASAS_HBA_SERIAL "QEMU123456"
+#define NAA_LOCALLY_ASSIGNED_ID 0x3ULL
+#define IEEE_COMPANY_LOCALLY_ASSIGNED 0x525400
+
+#define MEGASAS_FLAG_USE_JBOD 0
+#define MEGASAS_MASK_USE_JBOD (1 << MEGASAS_FLAG_USE_JBOD)
+#define MEGASAS_FLAG_USE_MSI 1
+#define MEGASAS_MASK_USE_MSI (1 << MEGASAS_FLAG_USE_MSI)
+#define MEGASAS_FLAG_USE_MSIX 2
+#define MEGASAS_MASK_USE_MSIX (1 << MEGASAS_FLAG_USE_MSIX)
+#define MEGASAS_FLAG_USE_QUEUE64 3
+#define MEGASAS_MASK_USE_QUEUE64 (1 << MEGASAS_FLAG_USE_QUEUE64)
+
+static const char *mfi_frame_desc[] = {
+ "MFI init", "LD Read", "LD Write", "LD SCSI", "PD SCSI",
+ "MFI Doorbell", "MFI Abort", "MFI SMP", "MFI Stop"};
+
+typedef struct MegasasCmd {
+ uint32_t index;
+ uint16_t flags;
+ uint16_t count;
+ uint64_t context;
+
+ hwaddr pa;
+ hwaddr pa_size;
+ union mfi_frame *frame;
+ SCSIRequest *req;
+ QEMUSGList qsg;
+ void *iov_buf;
+ size_t iov_size;
+ size_t iov_offset;
+ struct MegasasState *state;
+} MegasasCmd;
+
+typedef struct MegasasState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion mmio_io;
+ MemoryRegion port_io;
+ MemoryRegion queue_io;
+ uint32_t frame_hi;
+
+ int fw_state;
+ uint32_t fw_sge;
+ uint32_t fw_cmds;
+ uint32_t flags;
+ int fw_luns;
+ int intr_mask;
+ int doorbell;
+ int busy;
+ int diag;
+ int adp_reset;
+
+ MegasasCmd *event_cmd;
+ int event_locale;
+ int event_class;
+ int event_count;
+ int shutdown_event;
+ int boot_event;
+
+ uint64_t sas_addr;
+ char *hba_serial;
+
+ uint64_t reply_queue_pa;
+ void *reply_queue;
+ int reply_queue_len;
+ int reply_queue_head;
+ int reply_queue_tail;
+ uint64_t consumer_pa;
+ uint64_t producer_pa;
+
+ MegasasCmd frames[MEGASAS_MAX_FRAMES];
+ DECLARE_BITMAP(frame_map, MEGASAS_MAX_FRAMES);
+ SCSIBus bus;
+} MegasasState;
+
+typedef struct MegasasBaseClass {
+ PCIDeviceClass parent_class;
+ const char *product_name;
+ const char *product_version;
+ int mmio_bar;
+ int ioport_bar;
+ int osts;
+} MegasasBaseClass;
+
+#define TYPE_MEGASAS_BASE "megasas-base"
+#define TYPE_MEGASAS_GEN1 "megasas"
+#define TYPE_MEGASAS_GEN2 "megasas-gen2"
+
+#define MEGASAS(obj) \
+ OBJECT_CHECK(MegasasState, (obj), TYPE_MEGASAS_BASE)
+
+#define MEGASAS_DEVICE_CLASS(oc) \
+ OBJECT_CLASS_CHECK(MegasasBaseClass, (oc), TYPE_MEGASAS_BASE)
+#define MEGASAS_DEVICE_GET_CLASS(oc) \
+ OBJECT_GET_CLASS(MegasasBaseClass, (oc), TYPE_MEGASAS_BASE)
+
+#define MEGASAS_INTR_DISABLED_MASK 0xFFFFFFFF
+
+static bool megasas_intr_enabled(MegasasState *s)
+{
+ if ((s->intr_mask & MEGASAS_INTR_DISABLED_MASK) !=
+ MEGASAS_INTR_DISABLED_MASK) {
+ return true;
+ }
+ return false;
+}
+
+static bool megasas_use_queue64(MegasasState *s)
+{
+ return s->flags & MEGASAS_MASK_USE_QUEUE64;
+}
+
+static bool megasas_use_msi(MegasasState *s)
+{
+ return s->flags & MEGASAS_MASK_USE_MSI;
+}
+
+static bool megasas_use_msix(MegasasState *s)
+{
+ return s->flags & MEGASAS_MASK_USE_MSIX;
+}
+
+static bool megasas_is_jbod(MegasasState *s)
+{
+ return s->flags & MEGASAS_MASK_USE_JBOD;
+}
+
+static void megasas_frame_set_cmd_status(MegasasState *s,
+ unsigned long frame, uint8_t v)
+{
+ PCIDevice *pci = &s->parent_obj;
+ stb_pci_dma(pci, frame + offsetof(struct mfi_frame_header, cmd_status), v);
+}
+
+static void megasas_frame_set_scsi_status(MegasasState *s,
+ unsigned long frame, uint8_t v)
+{
+ PCIDevice *pci = &s->parent_obj;
+ stb_pci_dma(pci, frame + offsetof(struct mfi_frame_header, scsi_status), v);
+}
+
+/*
+ * Context is considered opaque, but the HBA firmware is running
+ * in little endian mode. So convert it to little endian, too.
+ */
+static uint64_t megasas_frame_get_context(MegasasState *s,
+ unsigned long frame)
+{
+ PCIDevice *pci = &s->parent_obj;
+ return ldq_le_pci_dma(pci, frame + offsetof(struct mfi_frame_header, context));
+}
+
+static bool megasas_frame_is_ieee_sgl(MegasasCmd *cmd)
+{
+ return cmd->flags & MFI_FRAME_IEEE_SGL;
+}
+
+static bool megasas_frame_is_sgl64(MegasasCmd *cmd)
+{
+ return cmd->flags & MFI_FRAME_SGL64;
+}
+
+static bool megasas_frame_is_sense64(MegasasCmd *cmd)
+{
+ return cmd->flags & MFI_FRAME_SENSE64;
+}
+
+static uint64_t megasas_sgl_get_addr(MegasasCmd *cmd,
+ union mfi_sgl *sgl)
+{
+ uint64_t addr;
+
+ if (megasas_frame_is_ieee_sgl(cmd)) {
+ addr = le64_to_cpu(sgl->sg_skinny->addr);
+ } else if (megasas_frame_is_sgl64(cmd)) {
+ addr = le64_to_cpu(sgl->sg64->addr);
+ } else {
+ addr = le32_to_cpu(sgl->sg32->addr);
+ }
+ return addr;
+}
+
+static uint32_t megasas_sgl_get_len(MegasasCmd *cmd,
+ union mfi_sgl *sgl)
+{
+ uint32_t len;
+
+ if (megasas_frame_is_ieee_sgl(cmd)) {
+ len = le32_to_cpu(sgl->sg_skinny->len);
+ } else if (megasas_frame_is_sgl64(cmd)) {
+ len = le32_to_cpu(sgl->sg64->len);
+ } else {
+ len = le32_to_cpu(sgl->sg32->len);
+ }
+ return len;
+}
+
+static union mfi_sgl *megasas_sgl_next(MegasasCmd *cmd,
+ union mfi_sgl *sgl)
+{
+ uint8_t *next = (uint8_t *)sgl;
+
+ if (megasas_frame_is_ieee_sgl(cmd)) {
+ next += sizeof(struct mfi_sg_skinny);
+ } else if (megasas_frame_is_sgl64(cmd)) {
+ next += sizeof(struct mfi_sg64);
+ } else {
+ next += sizeof(struct mfi_sg32);
+ }
+
+ if (next >= (uint8_t *)cmd->frame + cmd->pa_size) {
+ return NULL;
+ }
+ return (union mfi_sgl *)next;
+}
+
+static void megasas_soft_reset(MegasasState *s);
+
+static int megasas_map_sgl(MegasasState *s, MegasasCmd *cmd, union mfi_sgl *sgl)
+{
+ int i;
+ int iov_count = 0;
+ size_t iov_size = 0;
+
+ cmd->flags = le16_to_cpu(cmd->frame->header.flags);
+ iov_count = cmd->frame->header.sge_count;
+ if (iov_count > MEGASAS_MAX_SGE) {
+ trace_megasas_iovec_sgl_overflow(cmd->index, iov_count,
+ MEGASAS_MAX_SGE);
+ return iov_count;
+ }
+ pci_dma_sglist_init(&cmd->qsg, PCI_DEVICE(s), iov_count);
+ for (i = 0; i < iov_count; i++) {
+ dma_addr_t iov_pa, iov_size_p;
+
+ if (!sgl) {
+ trace_megasas_iovec_sgl_underflow(cmd->index, i);
+ goto unmap;
+ }
+ iov_pa = megasas_sgl_get_addr(cmd, sgl);
+ iov_size_p = megasas_sgl_get_len(cmd, sgl);
+ if (!iov_pa || !iov_size_p) {
+ trace_megasas_iovec_sgl_invalid(cmd->index, i,
+ iov_pa, iov_size_p);
+ goto unmap;
+ }
+ qemu_sglist_add(&cmd->qsg, iov_pa, iov_size_p);
+ sgl = megasas_sgl_next(cmd, sgl);
+ iov_size += (size_t)iov_size_p;
+ }
+ if (cmd->iov_size > iov_size) {
+ trace_megasas_iovec_overflow(cmd->index, iov_size, cmd->iov_size);
+ } else if (cmd->iov_size < iov_size) {
+ trace_megasas_iovec_underflow(cmd->iov_size, iov_size, cmd->iov_size);
+ }
+ cmd->iov_offset = 0;
+ return 0;
+unmap:
+ qemu_sglist_destroy(&cmd->qsg);
+ return iov_count - i;
+}
+
+static void megasas_unmap_sgl(MegasasCmd *cmd)
+{
+ qemu_sglist_destroy(&cmd->qsg);
+ cmd->iov_offset = 0;
+}
+
+/*
+ * passthrough sense and io sense are at the same offset
+ */
+static int megasas_build_sense(MegasasCmd *cmd, uint8_t *sense_ptr,
+ uint8_t sense_len)
+{
+ PCIDevice *pcid = PCI_DEVICE(cmd->state);
+ uint32_t pa_hi = 0, pa_lo;
+ hwaddr pa;
+
+ if (sense_len > cmd->frame->header.sense_len) {
+ sense_len = cmd->frame->header.sense_len;
+ }
+ if (sense_len) {
+ pa_lo = le32_to_cpu(cmd->frame->pass.sense_addr_lo);
+ if (megasas_frame_is_sense64(cmd)) {
+ pa_hi = le32_to_cpu(cmd->frame->pass.sense_addr_hi);
+ }
+ pa = ((uint64_t) pa_hi << 32) | pa_lo;
+ pci_dma_write(pcid, pa, sense_ptr, sense_len);
+ cmd->frame->header.sense_len = sense_len;
+ }
+ return sense_len;
+}
+
+static void megasas_write_sense(MegasasCmd *cmd, SCSISense sense)
+{
+ uint8_t sense_buf[SCSI_SENSE_BUF_SIZE];
+ uint8_t sense_len = 18;
+
+ memset(sense_buf, 0, sense_len);
+ sense_buf[0] = 0xf0;
+ sense_buf[2] = sense.key;
+ sense_buf[7] = 10;
+ sense_buf[12] = sense.asc;
+ sense_buf[13] = sense.ascq;
+ megasas_build_sense(cmd, sense_buf, sense_len);
+}
+
+static void megasas_copy_sense(MegasasCmd *cmd)
+{
+ uint8_t sense_buf[SCSI_SENSE_BUF_SIZE];
+ uint8_t sense_len;
+
+ sense_len = scsi_req_get_sense(cmd->req, sense_buf,
+ SCSI_SENSE_BUF_SIZE);
+ megasas_build_sense(cmd, sense_buf, sense_len);
+}
+
+/*
+ * Format an INQUIRY CDB
+ */
+static int megasas_setup_inquiry(uint8_t *cdb, int pg, int len)
+{
+ memset(cdb, 0, 6);
+ cdb[0] = INQUIRY;
+ if (pg > 0) {
+ cdb[1] = 0x1;
+ cdb[2] = pg;
+ }
+ cdb[3] = (len >> 8) & 0xff;
+ cdb[4] = (len & 0xff);
+ return len;
+}
+
+/*
+ * Encode lba and len into a READ_16/WRITE_16 CDB
+ */
+static void megasas_encode_lba(uint8_t *cdb, uint64_t lba,
+ uint32_t len, bool is_write)
+{
+ memset(cdb, 0x0, 16);
+ if (is_write) {
+ cdb[0] = WRITE_16;
+ } else {
+ cdb[0] = READ_16;
+ }
+ cdb[2] = (lba >> 56) & 0xff;
+ cdb[3] = (lba >> 48) & 0xff;
+ cdb[4] = (lba >> 40) & 0xff;
+ cdb[5] = (lba >> 32) & 0xff;
+ cdb[6] = (lba >> 24) & 0xff;
+ cdb[7] = (lba >> 16) & 0xff;
+ cdb[8] = (lba >> 8) & 0xff;
+ cdb[9] = (lba) & 0xff;
+ cdb[10] = (len >> 24) & 0xff;
+ cdb[11] = (len >> 16) & 0xff;
+ cdb[12] = (len >> 8) & 0xff;
+ cdb[13] = (len) & 0xff;
+}
+
+/*
+ * Utility functions
+ */
+static uint64_t megasas_fw_time(void)
+{
+ struct tm curtime;
+ uint64_t bcd_time;
+
+ qemu_get_timedate(&curtime, 0);
+ bcd_time = ((uint64_t)curtime.tm_sec & 0xff) << 48 |
+ ((uint64_t)curtime.tm_min & 0xff) << 40 |
+ ((uint64_t)curtime.tm_hour & 0xff) << 32 |
+ ((uint64_t)curtime.tm_mday & 0xff) << 24 |
+ ((uint64_t)curtime.tm_mon & 0xff) << 16 |
+ ((uint64_t)(curtime.tm_year + 1900) & 0xffff);
+
+ return bcd_time;
+}
+
+/*
+ * Default disk sata address
+ * 0x1221 is the magic number as
+ * present in real hardware,
+ * so use it here, too.
+ */
+static uint64_t megasas_get_sata_addr(uint16_t id)
+{
+ uint64_t addr = (0x1221ULL << 48);
+ return addr & (id << 24);
+}
+
+/*
+ * Frame handling
+ */
+static int megasas_next_index(MegasasState *s, int index, int limit)
+{
+ index++;
+ if (index == limit) {
+ index = 0;
+ }
+ return index;
+}
+
+static MegasasCmd *megasas_lookup_frame(MegasasState *s,
+ hwaddr frame)
+{
+ MegasasCmd *cmd = NULL;
+ int num = 0, index;
+
+ index = s->reply_queue_head;
+
+ while (num < s->fw_cmds) {
+ if (s->frames[index].pa && s->frames[index].pa == frame) {
+ cmd = &s->frames[index];
+ break;
+ }
+ index = megasas_next_index(s, index, s->fw_cmds);
+ num++;
+ }
+
+ return cmd;
+}
+
+static void megasas_unmap_frame(MegasasState *s, MegasasCmd *cmd)
+{
+ PCIDevice *p = PCI_DEVICE(s);
+
+ pci_dma_unmap(p, cmd->frame, cmd->pa_size, 0, 0);
+ cmd->frame = NULL;
+ cmd->pa = 0;
+ clear_bit(cmd->index, s->frame_map);
+}
+
+/*
+ * This absolutely needs to be locked if
+ * qemu ever goes multithreaded.
+ */
+static MegasasCmd *megasas_enqueue_frame(MegasasState *s,
+ hwaddr frame, uint64_t context, int count)
+{
+ PCIDevice *pcid = PCI_DEVICE(s);
+ MegasasCmd *cmd = NULL;
+ int frame_size = MFI_FRAME_SIZE * 16;
+ hwaddr frame_size_p = frame_size;
+ unsigned long index;
+
+ index = 0;
+ while (index < s->fw_cmds) {
+ index = find_next_zero_bit(s->frame_map, s->fw_cmds, index);
+ if (!s->frames[index].pa)
+ break;
+ /* Busy frame found */
+ trace_megasas_qf_mapped(index);
+ }
+ if (index >= s->fw_cmds) {
+ /* All frames busy */
+ trace_megasas_qf_busy(frame);
+ return NULL;
+ }
+ cmd = &s->frames[index];
+ set_bit(index, s->frame_map);
+ trace_megasas_qf_new(index, frame);
+
+ cmd->pa = frame;
+ /* Map all possible frames */
+ cmd->frame = pci_dma_map(pcid, frame, &frame_size_p, 0);
+ if (frame_size_p != frame_size) {
+ trace_megasas_qf_map_failed(cmd->index, (unsigned long)frame);
+ if (cmd->frame) {
+ megasas_unmap_frame(s, cmd);
+ }
+ s->event_count++;
+ return NULL;
+ }
+ cmd->pa_size = frame_size_p;
+ cmd->context = context;
+ if (!megasas_use_queue64(s)) {
+ cmd->context &= (uint64_t)0xFFFFFFFF;
+ }
+ cmd->count = count;
+ s->busy++;
+
+ if (s->consumer_pa) {
+ s->reply_queue_tail = ldl_le_pci_dma(pcid, s->consumer_pa);
+ }
+ trace_megasas_qf_enqueue(cmd->index, cmd->count, cmd->context,
+ s->reply_queue_head, s->reply_queue_tail, s->busy);
+
+ return cmd;
+}
+
+static void megasas_complete_frame(MegasasState *s, uint64_t context)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ int tail, queue_offset;
+
+ /* Decrement busy count */
+ s->busy--;
+ if (s->reply_queue_pa) {
+ /*
+ * Put command on the reply queue.
+ * Context is opaque, but emulation is running in
+ * little endian. So convert it.
+ */
+ if (megasas_use_queue64(s)) {
+ queue_offset = s->reply_queue_head * sizeof(uint64_t);
+ stq_le_pci_dma(pci_dev, s->reply_queue_pa + queue_offset, context);
+ } else {
+ queue_offset = s->reply_queue_head * sizeof(uint32_t);
+ stl_le_pci_dma(pci_dev, s->reply_queue_pa + queue_offset, context);
+ }
+ s->reply_queue_tail = ldl_le_pci_dma(pci_dev, s->consumer_pa);
+ trace_megasas_qf_complete(context, s->reply_queue_head,
+ s->reply_queue_tail, s->busy);
+ }
+
+ if (megasas_intr_enabled(s)) {
+ /* Update reply queue pointer */
+ s->reply_queue_tail = ldl_le_pci_dma(pci_dev, s->consumer_pa);
+ tail = s->reply_queue_head;
+ s->reply_queue_head = megasas_next_index(s, tail, s->fw_cmds);
+ trace_megasas_qf_update(s->reply_queue_head, s->reply_queue_tail,
+ s->busy);
+ stl_le_pci_dma(pci_dev, s->producer_pa, s->reply_queue_head);
+ /* Notify HBA */
+ if (msix_enabled(pci_dev)) {
+ trace_megasas_msix_raise(0);
+ msix_notify(pci_dev, 0);
+ } else if (msi_enabled(pci_dev)) {
+ trace_megasas_msi_raise(0);
+ msi_notify(pci_dev, 0);
+ } else {
+ s->doorbell++;
+ if (s->doorbell == 1) {
+ trace_megasas_irq_raise();
+ pci_irq_assert(pci_dev);
+ }
+ }
+ } else {
+ trace_megasas_qf_complete_noirq(context);
+ }
+}
+
+static void megasas_reset_frames(MegasasState *s)
+{
+ int i;
+ MegasasCmd *cmd;
+
+ for (i = 0; i < s->fw_cmds; i++) {
+ cmd = &s->frames[i];
+ if (cmd->pa) {
+ megasas_unmap_frame(s, cmd);
+ }
+ }
+ bitmap_zero(s->frame_map, MEGASAS_MAX_FRAMES);
+}
+
+static void megasas_abort_command(MegasasCmd *cmd)
+{
+ if (cmd->req) {
+ scsi_req_cancel(cmd->req);
+ cmd->req = NULL;
+ }
+}
+
+static int megasas_init_firmware(MegasasState *s, MegasasCmd *cmd)
+{
+ PCIDevice *pcid = PCI_DEVICE(s);
+ uint32_t pa_hi, pa_lo;
+ hwaddr iq_pa, initq_size = sizeof(struct mfi_init_qinfo);
+ struct mfi_init_qinfo *initq = NULL;
+ uint32_t flags;
+ int ret = MFI_STAT_OK;
+
+ if (s->reply_queue_pa) {
+ trace_megasas_initq_mapped(s->reply_queue_pa);
+ goto out;
+ }
+ pa_lo = le32_to_cpu(cmd->frame->init.qinfo_new_addr_lo);
+ pa_hi = le32_to_cpu(cmd->frame->init.qinfo_new_addr_hi);
+ iq_pa = (((uint64_t) pa_hi << 32) | pa_lo);
+ trace_megasas_init_firmware((uint64_t)iq_pa);
+ initq = pci_dma_map(pcid, iq_pa, &initq_size, 0);
+ if (!initq || initq_size != sizeof(*initq)) {
+ trace_megasas_initq_map_failed(cmd->index);
+ s->event_count++;
+ ret = MFI_STAT_MEMORY_NOT_AVAILABLE;
+ goto out;
+ }
+ s->reply_queue_len = le32_to_cpu(initq->rq_entries) & 0xFFFF;
+ if (s->reply_queue_len > s->fw_cmds) {
+ trace_megasas_initq_mismatch(s->reply_queue_len, s->fw_cmds);
+ s->event_count++;
+ ret = MFI_STAT_INVALID_PARAMETER;
+ goto out;
+ }
+ pa_lo = le32_to_cpu(initq->rq_addr_lo);
+ pa_hi = le32_to_cpu(initq->rq_addr_hi);
+ s->reply_queue_pa = ((uint64_t) pa_hi << 32) | pa_lo;
+ pa_lo = le32_to_cpu(initq->ci_addr_lo);
+ pa_hi = le32_to_cpu(initq->ci_addr_hi);
+ s->consumer_pa = ((uint64_t) pa_hi << 32) | pa_lo;
+ pa_lo = le32_to_cpu(initq->pi_addr_lo);
+ pa_hi = le32_to_cpu(initq->pi_addr_hi);
+ s->producer_pa = ((uint64_t) pa_hi << 32) | pa_lo;
+ s->reply_queue_head = ldl_le_pci_dma(pcid, s->producer_pa);
+ s->reply_queue_tail = ldl_le_pci_dma(pcid, s->consumer_pa);
+ flags = le32_to_cpu(initq->flags);
+ if (flags & MFI_QUEUE_FLAG_CONTEXT64) {
+ s->flags |= MEGASAS_MASK_USE_QUEUE64;
+ }
+ trace_megasas_init_queue((unsigned long)s->reply_queue_pa,
+ s->reply_queue_len, s->reply_queue_head,
+ s->reply_queue_tail, flags);
+ megasas_reset_frames(s);
+ s->fw_state = MFI_FWSTATE_OPERATIONAL;
+out:
+ if (initq) {
+ pci_dma_unmap(pcid, initq, initq_size, 0, 0);
+ }
+ return ret;
+}
+
+static int megasas_map_dcmd(MegasasState *s, MegasasCmd *cmd)
+{
+ dma_addr_t iov_pa, iov_size;
+
+ cmd->flags = le16_to_cpu(cmd->frame->header.flags);
+ if (!cmd->frame->header.sge_count) {
+ trace_megasas_dcmd_zero_sge(cmd->index);
+ cmd->iov_size = 0;
+ return 0;
+ } else if (cmd->frame->header.sge_count > 1) {
+ trace_megasas_dcmd_invalid_sge(cmd->index,
+ cmd->frame->header.sge_count);
+ cmd->iov_size = 0;
+ return -1;
+ }
+ iov_pa = megasas_sgl_get_addr(cmd, &cmd->frame->dcmd.sgl);
+ iov_size = megasas_sgl_get_len(cmd, &cmd->frame->dcmd.sgl);
+ pci_dma_sglist_init(&cmd->qsg, PCI_DEVICE(s), 1);
+ qemu_sglist_add(&cmd->qsg, iov_pa, iov_size);
+ cmd->iov_size = iov_size;
+ return cmd->iov_size;
+}
+
+static void megasas_finish_dcmd(MegasasCmd *cmd, uint32_t iov_size)
+{
+ trace_megasas_finish_dcmd(cmd->index, iov_size);
+
+ if (cmd->frame->header.sge_count) {
+ qemu_sglist_destroy(&cmd->qsg);
+ }
+ if (iov_size > cmd->iov_size) {
+ if (megasas_frame_is_ieee_sgl(cmd)) {
+ cmd->frame->dcmd.sgl.sg_skinny->len = cpu_to_le32(iov_size);
+ } else if (megasas_frame_is_sgl64(cmd)) {
+ cmd->frame->dcmd.sgl.sg64->len = cpu_to_le32(iov_size);
+ } else {
+ cmd->frame->dcmd.sgl.sg32->len = cpu_to_le32(iov_size);
+ }
+ }
+ cmd->iov_size = 0;
+}
+
+static int megasas_ctrl_get_info(MegasasState *s, MegasasCmd *cmd)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ PCIDeviceClass *pci_class = PCI_DEVICE_GET_CLASS(pci_dev);
+ MegasasBaseClass *base_class = MEGASAS_DEVICE_GET_CLASS(s);
+ struct mfi_ctrl_info info;
+ size_t dcmd_size = sizeof(info);
+ BusChild *kid;
+ int num_pd_disks = 0;
+
+ memset(&info, 0x0, dcmd_size);
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ info.pci.vendor = cpu_to_le16(pci_class->vendor_id);
+ info.pci.device = cpu_to_le16(pci_class->device_id);
+ info.pci.subvendor = cpu_to_le16(pci_class->subsystem_vendor_id);
+ info.pci.subdevice = cpu_to_le16(pci_class->subsystem_id);
+
+ /*
+ * For some reason the firmware supports
+ * only up to 8 device ports.
+ * Despite supporting a far larger number
+ * of devices for the physical devices.
+ * So just display the first 8 devices
+ * in the device port list, independent
+ * of how many logical devices are actually
+ * present.
+ */
+ info.host.type = MFI_INFO_HOST_PCIE;
+ info.device.type = MFI_INFO_DEV_SAS3G;
+ info.device.port_count = 8;
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = DO_UPCAST(SCSIDevice, qdev, kid->child);
+ uint16_t pd_id;
+
+ if (num_pd_disks < 8) {
+ pd_id = ((sdev->id & 0xFF) << 8) | (sdev->lun & 0xFF);
+ info.device.port_addr[num_pd_disks] =
+ cpu_to_le64(megasas_get_sata_addr(pd_id));
+ }
+ num_pd_disks++;
+ }
+
+ memcpy(info.product_name, base_class->product_name, 24);
+ snprintf(info.serial_number, 32, "%s", s->hba_serial);
+ snprintf(info.package_version, 0x60, "%s-QEMU", QEMU_VERSION);
+ memcpy(info.image_component[0].name, "APP", 3);
+ snprintf(info.image_component[0].version, 10, "%s-QEMU",
+ base_class->product_version);
+ memcpy(info.image_component[0].build_date, "Apr 1 2014", 11);
+ memcpy(info.image_component[0].build_time, "12:34:56", 8);
+ info.image_component_count = 1;
+ if (pci_dev->has_rom) {
+ uint8_t biosver[32];
+ uint8_t *ptr;
+
+ ptr = memory_region_get_ram_ptr(&pci_dev->rom);
+ memcpy(biosver, ptr + 0x41, 31);
+ memcpy(info.image_component[1].name, "BIOS", 4);
+ memcpy(info.image_component[1].version, biosver,
+ strlen((const char *)biosver));
+ info.image_component_count++;
+ }
+ info.current_fw_time = cpu_to_le32(megasas_fw_time());
+ info.max_arms = 32;
+ info.max_spans = 8;
+ info.max_arrays = MEGASAS_MAX_ARRAYS;
+ info.max_lds = MFI_MAX_LD;
+ info.max_cmds = cpu_to_le16(s->fw_cmds);
+ info.max_sg_elements = cpu_to_le16(s->fw_sge);
+ info.max_request_size = cpu_to_le32(MEGASAS_MAX_SECTORS);
+ if (!megasas_is_jbod(s))
+ info.lds_present = cpu_to_le16(num_pd_disks);
+ info.pd_present = cpu_to_le16(num_pd_disks);
+ info.pd_disks_present = cpu_to_le16(num_pd_disks);
+ info.hw_present = cpu_to_le32(MFI_INFO_HW_NVRAM |
+ MFI_INFO_HW_MEM |
+ MFI_INFO_HW_FLASH);
+ info.memory_size = cpu_to_le16(512);
+ info.nvram_size = cpu_to_le16(32);
+ info.flash_size = cpu_to_le16(16);
+ info.raid_levels = cpu_to_le32(MFI_INFO_RAID_0);
+ info.adapter_ops = cpu_to_le32(MFI_INFO_AOPS_RBLD_RATE |
+ MFI_INFO_AOPS_SELF_DIAGNOSTIC |
+ MFI_INFO_AOPS_MIXED_ARRAY);
+ info.ld_ops = cpu_to_le32(MFI_INFO_LDOPS_DISK_CACHE_POLICY |
+ MFI_INFO_LDOPS_ACCESS_POLICY |
+ MFI_INFO_LDOPS_IO_POLICY |
+ MFI_INFO_LDOPS_WRITE_POLICY |
+ MFI_INFO_LDOPS_READ_POLICY);
+ info.max_strips_per_io = cpu_to_le16(s->fw_sge);
+ info.stripe_sz_ops.min = 3;
+ info.stripe_sz_ops.max = ctz32(MEGASAS_MAX_SECTORS + 1);
+ info.properties.pred_fail_poll_interval = cpu_to_le16(300);
+ info.properties.intr_throttle_cnt = cpu_to_le16(16);
+ info.properties.intr_throttle_timeout = cpu_to_le16(50);
+ info.properties.rebuild_rate = 30;
+ info.properties.patrol_read_rate = 30;
+ info.properties.bgi_rate = 30;
+ info.properties.cc_rate = 30;
+ info.properties.recon_rate = 30;
+ info.properties.cache_flush_interval = 4;
+ info.properties.spinup_drv_cnt = 2;
+ info.properties.spinup_delay = 6;
+ info.properties.ecc_bucket_size = 15;
+ info.properties.ecc_bucket_leak_rate = cpu_to_le16(1440);
+ info.properties.expose_encl_devices = 1;
+ info.properties.OnOffProperties = cpu_to_le32(MFI_CTRL_PROP_EnableJBOD);
+ info.pd_ops = cpu_to_le32(MFI_INFO_PDOPS_FORCE_ONLINE |
+ MFI_INFO_PDOPS_FORCE_OFFLINE);
+ info.pd_mix_support = cpu_to_le32(MFI_INFO_PDMIX_SAS |
+ MFI_INFO_PDMIX_SATA |
+ MFI_INFO_PDMIX_LD);
+
+ cmd->iov_size -= dma_buf_read((uint8_t *)&info, dcmd_size, &cmd->qsg);
+ return MFI_STAT_OK;
+}
+
+static int megasas_mfc_get_defaults(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_defaults info;
+ size_t dcmd_size = sizeof(struct mfi_defaults);
+
+ memset(&info, 0x0, dcmd_size);
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ info.sas_addr = cpu_to_le64(s->sas_addr);
+ info.stripe_size = 3;
+ info.flush_time = 4;
+ info.background_rate = 30;
+ info.allow_mix_in_enclosure = 1;
+ info.allow_mix_in_ld = 1;
+ info.direct_pd_mapping = 1;
+ /* Enable for BIOS support */
+ info.bios_enumerate_lds = 1;
+ info.disable_ctrl_r = 1;
+ info.expose_enclosure_devices = 1;
+ info.disable_preboot_cli = 1;
+ info.cluster_disable = 1;
+
+ cmd->iov_size -= dma_buf_read((uint8_t *)&info, dcmd_size, &cmd->qsg);
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_get_bios_info(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_bios_data info;
+ size_t dcmd_size = sizeof(info);
+
+ memset(&info, 0x0, dcmd_size);
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ info.continue_on_error = 1;
+ info.verbose = 1;
+ if (megasas_is_jbod(s)) {
+ info.expose_all_drives = 1;
+ }
+
+ cmd->iov_size -= dma_buf_read((uint8_t *)&info, dcmd_size, &cmd->qsg);
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_get_fw_time(MegasasState *s, MegasasCmd *cmd)
+{
+ uint64_t fw_time;
+ size_t dcmd_size = sizeof(fw_time);
+
+ fw_time = cpu_to_le64(megasas_fw_time());
+
+ cmd->iov_size -= dma_buf_read((uint8_t *)&fw_time, dcmd_size, &cmd->qsg);
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_set_fw_time(MegasasState *s, MegasasCmd *cmd)
+{
+ uint64_t fw_time;
+
+ /* This is a dummy; setting of firmware time is not allowed */
+ memcpy(&fw_time, cmd->frame->dcmd.mbox, sizeof(fw_time));
+
+ trace_megasas_dcmd_set_fw_time(cmd->index, fw_time);
+ fw_time = cpu_to_le64(megasas_fw_time());
+ return MFI_STAT_OK;
+}
+
+static int megasas_event_info(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_evt_log_state info;
+ size_t dcmd_size = sizeof(info);
+
+ memset(&info, 0, dcmd_size);
+
+ info.newest_seq_num = cpu_to_le32(s->event_count);
+ info.shutdown_seq_num = cpu_to_le32(s->shutdown_event);
+ info.boot_seq_num = cpu_to_le32(s->boot_event);
+
+ cmd->iov_size -= dma_buf_read((uint8_t *)&info, dcmd_size, &cmd->qsg);
+ return MFI_STAT_OK;
+}
+
+static int megasas_event_wait(MegasasState *s, MegasasCmd *cmd)
+{
+ union mfi_evt event;
+
+ if (cmd->iov_size < sizeof(struct mfi_evt_detail)) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ sizeof(struct mfi_evt_detail));
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ s->event_count = cpu_to_le32(cmd->frame->dcmd.mbox[0]);
+ event.word = cpu_to_le32(cmd->frame->dcmd.mbox[4]);
+ s->event_locale = event.members.locale;
+ s->event_class = event.members.class;
+ s->event_cmd = cmd;
+ /* Decrease busy count; event frame doesn't count here */
+ s->busy--;
+ cmd->iov_size = sizeof(struct mfi_evt_detail);
+ return MFI_STAT_INVALID_STATUS;
+}
+
+static int megasas_dcmd_pd_get_list(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_pd_list info;
+ size_t dcmd_size = sizeof(info);
+ BusChild *kid;
+ uint32_t offset, dcmd_limit, num_pd_disks = 0, max_pd_disks;
+
+ memset(&info, 0, dcmd_size);
+ offset = 8;
+ dcmd_limit = offset + sizeof(struct mfi_pd_address);
+ if (cmd->iov_size < dcmd_limit) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_limit);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ max_pd_disks = (cmd->iov_size - offset) / sizeof(struct mfi_pd_address);
+ if (max_pd_disks > MFI_MAX_SYS_PDS) {
+ max_pd_disks = MFI_MAX_SYS_PDS;
+ }
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = DO_UPCAST(SCSIDevice, qdev, kid->child);
+ uint16_t pd_id;
+
+ if (num_pd_disks >= max_pd_disks)
+ break;
+
+ pd_id = ((sdev->id & 0xFF) << 8) | (sdev->lun & 0xFF);
+ info.addr[num_pd_disks].device_id = cpu_to_le16(pd_id);
+ info.addr[num_pd_disks].encl_device_id = 0xFFFF;
+ info.addr[num_pd_disks].encl_index = 0;
+ info.addr[num_pd_disks].slot_number = sdev->id & 0xFF;
+ info.addr[num_pd_disks].scsi_dev_type = sdev->type;
+ info.addr[num_pd_disks].connect_port_bitmap = 0x1;
+ info.addr[num_pd_disks].sas_addr[0] =
+ cpu_to_le64(megasas_get_sata_addr(pd_id));
+ num_pd_disks++;
+ offset += sizeof(struct mfi_pd_address);
+ }
+ trace_megasas_dcmd_pd_get_list(cmd->index, num_pd_disks,
+ max_pd_disks, offset);
+
+ info.size = cpu_to_le32(offset);
+ info.count = cpu_to_le32(num_pd_disks);
+
+ cmd->iov_size -= dma_buf_read((uint8_t *)&info, offset, &cmd->qsg);
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_pd_list_query(MegasasState *s, MegasasCmd *cmd)
+{
+ uint16_t flags;
+
+ /* mbox0 contains flags */
+ flags = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ trace_megasas_dcmd_pd_list_query(cmd->index, flags);
+ if (flags == MR_PD_QUERY_TYPE_ALL ||
+ megasas_is_jbod(s)) {
+ return megasas_dcmd_pd_get_list(s, cmd);
+ }
+
+ return MFI_STAT_OK;
+}
+
+static int megasas_pd_get_info_submit(SCSIDevice *sdev, int lun,
+ MegasasCmd *cmd)
+{
+ struct mfi_pd_info *info = cmd->iov_buf;
+ size_t dcmd_size = sizeof(struct mfi_pd_info);
+ uint64_t pd_size;
+ uint16_t pd_id = ((sdev->id & 0xFF) << 8) | (lun & 0xFF);
+ uint8_t cmdbuf[6];
+ SCSIRequest *req;
+ size_t len, resid;
+
+ if (!cmd->iov_buf) {
+ cmd->iov_buf = g_malloc0(dcmd_size);
+ info = cmd->iov_buf;
+ info->inquiry_data[0] = 0x7f; /* Force PQual 0x3, PType 0x1f */
+ info->vpd_page83[0] = 0x7f;
+ megasas_setup_inquiry(cmdbuf, 0, sizeof(info->inquiry_data));
+ req = scsi_req_new(sdev, cmd->index, lun, cmdbuf, cmd);
+ if (!req) {
+ trace_megasas_dcmd_req_alloc_failed(cmd->index,
+ "PD get info std inquiry");
+ g_free(cmd->iov_buf);
+ cmd->iov_buf = NULL;
+ return MFI_STAT_FLASH_ALLOC_FAIL;
+ }
+ trace_megasas_dcmd_internal_submit(cmd->index,
+ "PD get info std inquiry", lun);
+ len = scsi_req_enqueue(req);
+ if (len > 0) {
+ cmd->iov_size = len;
+ scsi_req_continue(req);
+ }
+ return MFI_STAT_INVALID_STATUS;
+ } else if (info->inquiry_data[0] != 0x7f && info->vpd_page83[0] == 0x7f) {
+ megasas_setup_inquiry(cmdbuf, 0x83, sizeof(info->vpd_page83));
+ req = scsi_req_new(sdev, cmd->index, lun, cmdbuf, cmd);
+ if (!req) {
+ trace_megasas_dcmd_req_alloc_failed(cmd->index,
+ "PD get info vpd inquiry");
+ return MFI_STAT_FLASH_ALLOC_FAIL;
+ }
+ trace_megasas_dcmd_internal_submit(cmd->index,
+ "PD get info vpd inquiry", lun);
+ len = scsi_req_enqueue(req);
+ if (len > 0) {
+ cmd->iov_size = len;
+ scsi_req_continue(req);
+ }
+ return MFI_STAT_INVALID_STATUS;
+ }
+ /* Finished, set FW state */
+ if ((info->inquiry_data[0] >> 5) == 0) {
+ if (megasas_is_jbod(cmd->state)) {
+ info->fw_state = cpu_to_le16(MFI_PD_STATE_SYSTEM);
+ } else {
+ info->fw_state = cpu_to_le16(MFI_PD_STATE_ONLINE);
+ }
+ } else {
+ info->fw_state = cpu_to_le16(MFI_PD_STATE_OFFLINE);
+ }
+
+ info->ref.v.device_id = cpu_to_le16(pd_id);
+ info->state.ddf.pd_type = cpu_to_le16(MFI_PD_DDF_TYPE_IN_VD|
+ MFI_PD_DDF_TYPE_INTF_SAS);
+ blk_get_geometry(sdev->conf.blk, &pd_size);
+ info->raw_size = cpu_to_le64(pd_size);
+ info->non_coerced_size = cpu_to_le64(pd_size);
+ info->coerced_size = cpu_to_le64(pd_size);
+ info->encl_device_id = 0xFFFF;
+ info->slot_number = (sdev->id & 0xFF);
+ info->path_info.count = 1;
+ info->path_info.sas_addr[0] =
+ cpu_to_le64(megasas_get_sata_addr(pd_id));
+ info->connected_port_bitmap = 0x1;
+ info->device_speed = 1;
+ info->link_speed = 1;
+ resid = dma_buf_read(cmd->iov_buf, dcmd_size, &cmd->qsg);
+ g_free(cmd->iov_buf);
+ cmd->iov_size = dcmd_size - resid;
+ cmd->iov_buf = NULL;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_pd_get_info(MegasasState *s, MegasasCmd *cmd)
+{
+ size_t dcmd_size = sizeof(struct mfi_pd_info);
+ uint16_t pd_id;
+ uint8_t target_id, lun_id;
+ SCSIDevice *sdev = NULL;
+ int retval = MFI_STAT_DEVICE_NOT_FOUND;
+
+ if (cmd->iov_size < dcmd_size) {
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ /* mbox0 has the ID */
+ pd_id = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ target_id = (pd_id >> 8) & 0xFF;
+ lun_id = pd_id & 0xFF;
+ sdev = scsi_device_find(&s->bus, 0, target_id, lun_id);
+ trace_megasas_dcmd_pd_get_info(cmd->index, pd_id);
+
+ if (sdev) {
+ /* Submit inquiry */
+ retval = megasas_pd_get_info_submit(sdev, pd_id, cmd);
+ }
+
+ return retval;
+}
+
+static int megasas_dcmd_ld_get_list(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_ld_list info;
+ size_t dcmd_size = sizeof(info), resid;
+ uint32_t num_ld_disks = 0, max_ld_disks;
+ uint64_t ld_size;
+ BusChild *kid;
+
+ memset(&info, 0, dcmd_size);
+ if (cmd->iov_size > dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ max_ld_disks = (cmd->iov_size - 8) / 16;
+ if (megasas_is_jbod(s)) {
+ max_ld_disks = 0;
+ }
+ if (max_ld_disks > MFI_MAX_LD) {
+ max_ld_disks = MFI_MAX_LD;
+ }
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = DO_UPCAST(SCSIDevice, qdev, kid->child);
+
+ if (num_ld_disks >= max_ld_disks) {
+ break;
+ }
+ /* Logical device size is in blocks */
+ blk_get_geometry(sdev->conf.blk, &ld_size);
+ info.ld_list[num_ld_disks].ld.v.target_id = sdev->id;
+ info.ld_list[num_ld_disks].state = MFI_LD_STATE_OPTIMAL;
+ info.ld_list[num_ld_disks].size = cpu_to_le64(ld_size);
+ num_ld_disks++;
+ }
+ info.ld_count = cpu_to_le32(num_ld_disks);
+ trace_megasas_dcmd_ld_get_list(cmd->index, num_ld_disks, max_ld_disks);
+
+ resid = dma_buf_read((uint8_t *)&info, dcmd_size, &cmd->qsg);
+ cmd->iov_size = dcmd_size - resid;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_ld_list_query(MegasasState *s, MegasasCmd *cmd)
+{
+ uint16_t flags;
+ struct mfi_ld_targetid_list info;
+ size_t dcmd_size = sizeof(info), resid;
+ uint32_t num_ld_disks = 0, max_ld_disks = s->fw_luns;
+ BusChild *kid;
+
+ /* mbox0 contains flags */
+ flags = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ trace_megasas_dcmd_ld_list_query(cmd->index, flags);
+ if (flags != MR_LD_QUERY_TYPE_ALL &&
+ flags != MR_LD_QUERY_TYPE_EXPOSED_TO_HOST) {
+ max_ld_disks = 0;
+ }
+
+ memset(&info, 0, dcmd_size);
+ if (cmd->iov_size < 12) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ dcmd_size = sizeof(uint32_t) * 2 + 3;
+ max_ld_disks = cmd->iov_size - dcmd_size;
+ if (megasas_is_jbod(s)) {
+ max_ld_disks = 0;
+ }
+ if (max_ld_disks > MFI_MAX_LD) {
+ max_ld_disks = MFI_MAX_LD;
+ }
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = DO_UPCAST(SCSIDevice, qdev, kid->child);
+
+ if (num_ld_disks >= max_ld_disks) {
+ break;
+ }
+ info.targetid[num_ld_disks] = sdev->lun;
+ num_ld_disks++;
+ dcmd_size++;
+ }
+ info.ld_count = cpu_to_le32(num_ld_disks);
+ info.size = dcmd_size;
+ trace_megasas_dcmd_ld_get_list(cmd->index, num_ld_disks, max_ld_disks);
+
+ resid = dma_buf_read((uint8_t *)&info, dcmd_size, &cmd->qsg);
+ cmd->iov_size = dcmd_size - resid;
+ return MFI_STAT_OK;
+}
+
+static int megasas_ld_get_info_submit(SCSIDevice *sdev, int lun,
+ MegasasCmd *cmd)
+{
+ struct mfi_ld_info *info = cmd->iov_buf;
+ size_t dcmd_size = sizeof(struct mfi_ld_info);
+ uint8_t cdb[6];
+ SCSIRequest *req;
+ ssize_t len, resid;
+ uint16_t sdev_id = ((sdev->id & 0xFF) << 8) | (lun & 0xFF);
+ uint64_t ld_size;
+
+ if (!cmd->iov_buf) {
+ cmd->iov_buf = g_malloc0(dcmd_size);
+ info = cmd->iov_buf;
+ megasas_setup_inquiry(cdb, 0x83, sizeof(info->vpd_page83));
+ req = scsi_req_new(sdev, cmd->index, lun, cdb, cmd);
+ if (!req) {
+ trace_megasas_dcmd_req_alloc_failed(cmd->index,
+ "LD get info vpd inquiry");
+ g_free(cmd->iov_buf);
+ cmd->iov_buf = NULL;
+ return MFI_STAT_FLASH_ALLOC_FAIL;
+ }
+ trace_megasas_dcmd_internal_submit(cmd->index,
+ "LD get info vpd inquiry", lun);
+ len = scsi_req_enqueue(req);
+ if (len > 0) {
+ cmd->iov_size = len;
+ scsi_req_continue(req);
+ }
+ return MFI_STAT_INVALID_STATUS;
+ }
+
+ info->ld_config.params.state = MFI_LD_STATE_OPTIMAL;
+ info->ld_config.properties.ld.v.target_id = lun;
+ info->ld_config.params.stripe_size = 3;
+ info->ld_config.params.num_drives = 1;
+ info->ld_config.params.is_consistent = 1;
+ /* Logical device size is in blocks */
+ blk_get_geometry(sdev->conf.blk, &ld_size);
+ info->size = cpu_to_le64(ld_size);
+ memset(info->ld_config.span, 0, sizeof(info->ld_config.span));
+ info->ld_config.span[0].start_block = 0;
+ info->ld_config.span[0].num_blocks = info->size;
+ info->ld_config.span[0].array_ref = cpu_to_le16(sdev_id);
+
+ resid = dma_buf_read(cmd->iov_buf, dcmd_size, &cmd->qsg);
+ g_free(cmd->iov_buf);
+ cmd->iov_size = dcmd_size - resid;
+ cmd->iov_buf = NULL;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_ld_get_info(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_ld_info info;
+ size_t dcmd_size = sizeof(info);
+ uint16_t ld_id;
+ uint32_t max_ld_disks = s->fw_luns;
+ SCSIDevice *sdev = NULL;
+ int retval = MFI_STAT_DEVICE_NOT_FOUND;
+
+ if (cmd->iov_size < dcmd_size) {
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ /* mbox0 has the ID */
+ ld_id = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ trace_megasas_dcmd_ld_get_info(cmd->index, ld_id);
+
+ if (megasas_is_jbod(s)) {
+ return MFI_STAT_DEVICE_NOT_FOUND;
+ }
+
+ if (ld_id < max_ld_disks) {
+ sdev = scsi_device_find(&s->bus, 0, ld_id, 0);
+ }
+
+ if (sdev) {
+ retval = megasas_ld_get_info_submit(sdev, ld_id, cmd);
+ }
+
+ return retval;
+}
+
+static int megasas_dcmd_cfg_read(MegasasState *s, MegasasCmd *cmd)
+{
+ uint8_t data[4096];
+ struct mfi_config_data *info;
+ int num_pd_disks = 0, array_offset, ld_offset;
+ BusChild *kid;
+
+ if (cmd->iov_size > 4096) {
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ num_pd_disks++;
+ }
+ info = (struct mfi_config_data *)&data;
+ /*
+ * Array mapping:
+ * - One array per SCSI device
+ * - One logical drive per SCSI device
+ * spanning the entire device
+ */
+ info->array_count = num_pd_disks;
+ info->array_size = sizeof(struct mfi_array) * num_pd_disks;
+ info->log_drv_count = num_pd_disks;
+ info->log_drv_size = sizeof(struct mfi_ld_config) * num_pd_disks;
+ info->spares_count = 0;
+ info->spares_size = sizeof(struct mfi_spare);
+ info->size = sizeof(struct mfi_config_data) + info->array_size +
+ info->log_drv_size;
+ if (info->size > 4096) {
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ array_offset = sizeof(struct mfi_config_data);
+ ld_offset = array_offset + sizeof(struct mfi_array) * num_pd_disks;
+
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = DO_UPCAST(SCSIDevice, qdev, kid->child);
+ uint16_t sdev_id = ((sdev->id & 0xFF) << 8) | (sdev->lun & 0xFF);
+ struct mfi_array *array;
+ struct mfi_ld_config *ld;
+ uint64_t pd_size;
+ int i;
+
+ array = (struct mfi_array *)(data + array_offset);
+ blk_get_geometry(sdev->conf.blk, &pd_size);
+ array->size = cpu_to_le64(pd_size);
+ array->num_drives = 1;
+ array->array_ref = cpu_to_le16(sdev_id);
+ array->pd[0].ref.v.device_id = cpu_to_le16(sdev_id);
+ array->pd[0].ref.v.seq_num = 0;
+ array->pd[0].fw_state = MFI_PD_STATE_ONLINE;
+ array->pd[0].encl.pd = 0xFF;
+ array->pd[0].encl.slot = (sdev->id & 0xFF);
+ for (i = 1; i < MFI_MAX_ROW_SIZE; i++) {
+ array->pd[i].ref.v.device_id = 0xFFFF;
+ array->pd[i].ref.v.seq_num = 0;
+ array->pd[i].fw_state = MFI_PD_STATE_UNCONFIGURED_GOOD;
+ array->pd[i].encl.pd = 0xFF;
+ array->pd[i].encl.slot = 0xFF;
+ }
+ array_offset += sizeof(struct mfi_array);
+ ld = (struct mfi_ld_config *)(data + ld_offset);
+ memset(ld, 0, sizeof(struct mfi_ld_config));
+ ld->properties.ld.v.target_id = sdev->id;
+ ld->properties.default_cache_policy = MR_LD_CACHE_READ_AHEAD |
+ MR_LD_CACHE_READ_ADAPTIVE;
+ ld->properties.current_cache_policy = MR_LD_CACHE_READ_AHEAD |
+ MR_LD_CACHE_READ_ADAPTIVE;
+ ld->params.state = MFI_LD_STATE_OPTIMAL;
+ ld->params.stripe_size = 3;
+ ld->params.num_drives = 1;
+ ld->params.span_depth = 1;
+ ld->params.is_consistent = 1;
+ ld->span[0].start_block = 0;
+ ld->span[0].num_blocks = cpu_to_le64(pd_size);
+ ld->span[0].array_ref = cpu_to_le16(sdev_id);
+ ld_offset += sizeof(struct mfi_ld_config);
+ }
+
+ cmd->iov_size -= dma_buf_read((uint8_t *)data, info->size, &cmd->qsg);
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_get_properties(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_ctrl_props info;
+ size_t dcmd_size = sizeof(info);
+
+ memset(&info, 0x0, dcmd_size);
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ info.pred_fail_poll_interval = cpu_to_le16(300);
+ info.intr_throttle_cnt = cpu_to_le16(16);
+ info.intr_throttle_timeout = cpu_to_le16(50);
+ info.rebuild_rate = 30;
+ info.patrol_read_rate = 30;
+ info.bgi_rate = 30;
+ info.cc_rate = 30;
+ info.recon_rate = 30;
+ info.cache_flush_interval = 4;
+ info.spinup_drv_cnt = 2;
+ info.spinup_delay = 6;
+ info.ecc_bucket_size = 15;
+ info.ecc_bucket_leak_rate = cpu_to_le16(1440);
+ info.expose_encl_devices = 1;
+
+ cmd->iov_size -= dma_buf_read((uint8_t *)&info, dcmd_size, &cmd->qsg);
+ return MFI_STAT_OK;
+}
+
+static int megasas_cache_flush(MegasasState *s, MegasasCmd *cmd)
+{
+ blk_drain_all();
+ return MFI_STAT_OK;
+}
+
+static int megasas_ctrl_shutdown(MegasasState *s, MegasasCmd *cmd)
+{
+ s->fw_state = MFI_FWSTATE_READY;
+ return MFI_STAT_OK;
+}
+
+/* Some implementations use CLUSTER RESET LD to simulate a device reset */
+static int megasas_cluster_reset_ld(MegasasState *s, MegasasCmd *cmd)
+{
+ uint16_t target_id;
+ int i;
+
+ /* mbox0 contains the device index */
+ target_id = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ trace_megasas_dcmd_reset_ld(cmd->index, target_id);
+ for (i = 0; i < s->fw_cmds; i++) {
+ MegasasCmd *tmp_cmd = &s->frames[i];
+ if (tmp_cmd->req && tmp_cmd->req->dev->id == target_id) {
+ SCSIDevice *d = tmp_cmd->req->dev;
+ qdev_reset_all(&d->qdev);
+ }
+ }
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_set_properties(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_ctrl_props info;
+ size_t dcmd_size = sizeof(info);
+
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ dma_buf_write((uint8_t *)&info, cmd->iov_size, &cmd->qsg);
+ trace_megasas_dcmd_unsupported(cmd->index, cmd->iov_size);
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_dummy(MegasasState *s, MegasasCmd *cmd)
+{
+ trace_megasas_dcmd_dummy(cmd->index, cmd->iov_size);
+ return MFI_STAT_OK;
+}
+
+static const struct dcmd_cmd_tbl_t {
+ int opcode;
+ const char *desc;
+ int (*func)(MegasasState *s, MegasasCmd *cmd);
+} dcmd_cmd_tbl[] = {
+ { MFI_DCMD_CTRL_MFI_HOST_MEM_ALLOC, "CTRL_HOST_MEM_ALLOC",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_GET_INFO, "CTRL_GET_INFO",
+ megasas_ctrl_get_info },
+ { MFI_DCMD_CTRL_GET_PROPERTIES, "CTRL_GET_PROPERTIES",
+ megasas_dcmd_get_properties },
+ { MFI_DCMD_CTRL_SET_PROPERTIES, "CTRL_SET_PROPERTIES",
+ megasas_dcmd_set_properties },
+ { MFI_DCMD_CTRL_ALARM_GET, "CTRL_ALARM_GET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_ALARM_ENABLE, "CTRL_ALARM_ENABLE",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_ALARM_DISABLE, "CTRL_ALARM_DISABLE",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_ALARM_SILENCE, "CTRL_ALARM_SILENCE",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_ALARM_TEST, "CTRL_ALARM_TEST",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_EVENT_GETINFO, "CTRL_EVENT_GETINFO",
+ megasas_event_info },
+ { MFI_DCMD_CTRL_EVENT_GET, "CTRL_EVENT_GET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_EVENT_WAIT, "CTRL_EVENT_WAIT",
+ megasas_event_wait },
+ { MFI_DCMD_CTRL_SHUTDOWN, "CTRL_SHUTDOWN",
+ megasas_ctrl_shutdown },
+ { MFI_DCMD_HIBERNATE_STANDBY, "CTRL_STANDBY",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_GET_TIME, "CTRL_GET_TIME",
+ megasas_dcmd_get_fw_time },
+ { MFI_DCMD_CTRL_SET_TIME, "CTRL_SET_TIME",
+ megasas_dcmd_set_fw_time },
+ { MFI_DCMD_CTRL_BIOS_DATA_GET, "CTRL_BIOS_DATA_GET",
+ megasas_dcmd_get_bios_info },
+ { MFI_DCMD_CTRL_FACTORY_DEFAULTS, "CTRL_FACTORY_DEFAULTS",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_MFC_DEFAULTS_GET, "CTRL_MFC_DEFAULTS_GET",
+ megasas_mfc_get_defaults },
+ { MFI_DCMD_CTRL_MFC_DEFAULTS_SET, "CTRL_MFC_DEFAULTS_SET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_CACHE_FLUSH, "CTRL_CACHE_FLUSH",
+ megasas_cache_flush },
+ { MFI_DCMD_PD_GET_LIST, "PD_GET_LIST",
+ megasas_dcmd_pd_get_list },
+ { MFI_DCMD_PD_LIST_QUERY, "PD_LIST_QUERY",
+ megasas_dcmd_pd_list_query },
+ { MFI_DCMD_PD_GET_INFO, "PD_GET_INFO",
+ megasas_dcmd_pd_get_info },
+ { MFI_DCMD_PD_STATE_SET, "PD_STATE_SET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_PD_REBUILD, "PD_REBUILD",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_PD_BLINK, "PD_BLINK",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_PD_UNBLINK, "PD_UNBLINK",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_LD_GET_LIST, "LD_GET_LIST",
+ megasas_dcmd_ld_get_list},
+ { MFI_DCMD_LD_LIST_QUERY, "LD_LIST_QUERY",
+ megasas_dcmd_ld_list_query },
+ { MFI_DCMD_LD_GET_INFO, "LD_GET_INFO",
+ megasas_dcmd_ld_get_info },
+ { MFI_DCMD_LD_GET_PROP, "LD_GET_PROP",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_LD_SET_PROP, "LD_SET_PROP",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_LD_DELETE, "LD_DELETE",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CFG_READ, "CFG_READ",
+ megasas_dcmd_cfg_read },
+ { MFI_DCMD_CFG_ADD, "CFG_ADD",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CFG_CLEAR, "CFG_CLEAR",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CFG_FOREIGN_READ, "CFG_FOREIGN_READ",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CFG_FOREIGN_IMPORT, "CFG_FOREIGN_IMPORT",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_BBU_STATUS, "BBU_STATUS",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_BBU_CAPACITY_INFO, "BBU_CAPACITY_INFO",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_BBU_DESIGN_INFO, "BBU_DESIGN_INFO",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_BBU_PROP_GET, "BBU_PROP_GET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CLUSTER, "CLUSTER",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CLUSTER_RESET_ALL, "CLUSTER_RESET_ALL",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CLUSTER_RESET_LD, "CLUSTER_RESET_LD",
+ megasas_cluster_reset_ld },
+ { -1, NULL, NULL }
+};
+
+static int megasas_handle_dcmd(MegasasState *s, MegasasCmd *cmd)
+{
+ int opcode, len;
+ int retval = 0;
+ const struct dcmd_cmd_tbl_t *cmdptr = dcmd_cmd_tbl;
+
+ opcode = le32_to_cpu(cmd->frame->dcmd.opcode);
+ trace_megasas_handle_dcmd(cmd->index, opcode);
+ len = megasas_map_dcmd(s, cmd);
+ if (len < 0) {
+ return MFI_STAT_MEMORY_NOT_AVAILABLE;
+ }
+ while (cmdptr->opcode != -1 && cmdptr->opcode != opcode) {
+ cmdptr++;
+ }
+ if (cmdptr->opcode == -1) {
+ trace_megasas_dcmd_unhandled(cmd->index, opcode, len);
+ retval = megasas_dcmd_dummy(s, cmd);
+ } else {
+ trace_megasas_dcmd_enter(cmd->index, cmdptr->desc, len);
+ retval = cmdptr->func(s, cmd);
+ }
+ if (retval != MFI_STAT_INVALID_STATUS) {
+ megasas_finish_dcmd(cmd, len);
+ }
+ return retval;
+}
+
+static int megasas_finish_internal_dcmd(MegasasCmd *cmd,
+ SCSIRequest *req)
+{
+ int opcode;
+ int retval = MFI_STAT_OK;
+ int lun = req->lun;
+
+ opcode = le32_to_cpu(cmd->frame->dcmd.opcode);
+ scsi_req_unref(req);
+ trace_megasas_dcmd_internal_finish(cmd->index, opcode, lun);
+ switch (opcode) {
+ case MFI_DCMD_PD_GET_INFO:
+ retval = megasas_pd_get_info_submit(req->dev, lun, cmd);
+ break;
+ case MFI_DCMD_LD_GET_INFO:
+ retval = megasas_ld_get_info_submit(req->dev, lun, cmd);
+ break;
+ default:
+ trace_megasas_dcmd_internal_invalid(cmd->index, opcode);
+ retval = MFI_STAT_INVALID_DCMD;
+ break;
+ }
+ if (retval != MFI_STAT_INVALID_STATUS) {
+ megasas_finish_dcmd(cmd, cmd->iov_size);
+ }
+ return retval;
+}
+
+static int megasas_enqueue_req(MegasasCmd *cmd, bool is_write)
+{
+ int len;
+
+ len = scsi_req_enqueue(cmd->req);
+ if (len < 0) {
+ len = -len;
+ }
+ if (len > 0) {
+ if (len > cmd->iov_size) {
+ if (is_write) {
+ trace_megasas_iov_write_overflow(cmd->index, len,
+ cmd->iov_size);
+ } else {
+ trace_megasas_iov_read_overflow(cmd->index, len,
+ cmd->iov_size);
+ }
+ }
+ if (len < cmd->iov_size) {
+ if (is_write) {
+ trace_megasas_iov_write_underflow(cmd->index, len,
+ cmd->iov_size);
+ } else {
+ trace_megasas_iov_read_underflow(cmd->index, len,
+ cmd->iov_size);
+ }
+ cmd->iov_size = len;
+ }
+ scsi_req_continue(cmd->req);
+ }
+ return len;
+}
+
+static int megasas_handle_scsi(MegasasState *s, MegasasCmd *cmd,
+ bool is_logical)
+{
+ uint8_t *cdb;
+ bool is_write;
+ struct SCSIDevice *sdev = NULL;
+
+ cdb = cmd->frame->pass.cdb;
+
+ if (is_logical) {
+ if (cmd->frame->header.target_id >= MFI_MAX_LD ||
+ cmd->frame->header.lun_id != 0) {
+ trace_megasas_scsi_target_not_present(
+ mfi_frame_desc[cmd->frame->header.frame_cmd], is_logical,
+ cmd->frame->header.target_id, cmd->frame->header.lun_id);
+ return MFI_STAT_DEVICE_NOT_FOUND;
+ }
+ }
+ sdev = scsi_device_find(&s->bus, 0, cmd->frame->header.target_id,
+ cmd->frame->header.lun_id);
+
+ cmd->iov_size = le32_to_cpu(cmd->frame->header.data_len);
+ trace_megasas_handle_scsi(mfi_frame_desc[cmd->frame->header.frame_cmd],
+ is_logical, cmd->frame->header.target_id,
+ cmd->frame->header.lun_id, sdev, cmd->iov_size);
+
+ if (!sdev || (megasas_is_jbod(s) && is_logical)) {
+ trace_megasas_scsi_target_not_present(
+ mfi_frame_desc[cmd->frame->header.frame_cmd], is_logical,
+ cmd->frame->header.target_id, cmd->frame->header.lun_id);
+ return MFI_STAT_DEVICE_NOT_FOUND;
+ }
+
+ if (cmd->frame->header.cdb_len > 16) {
+ trace_megasas_scsi_invalid_cdb_len(
+ mfi_frame_desc[cmd->frame->header.frame_cmd], is_logical,
+ cmd->frame->header.target_id, cmd->frame->header.lun_id,
+ cmd->frame->header.cdb_len);
+ megasas_write_sense(cmd, SENSE_CODE(INVALID_OPCODE));
+ cmd->frame->header.scsi_status = CHECK_CONDITION;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ if (megasas_map_sgl(s, cmd, &cmd->frame->pass.sgl)) {
+ megasas_write_sense(cmd, SENSE_CODE(TARGET_FAILURE));
+ cmd->frame->header.scsi_status = CHECK_CONDITION;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ cmd->req = scsi_req_new(sdev, cmd->index,
+ cmd->frame->header.lun_id, cdb, cmd);
+ if (!cmd->req) {
+ trace_megasas_scsi_req_alloc_failed(
+ mfi_frame_desc[cmd->frame->header.frame_cmd],
+ cmd->frame->header.target_id, cmd->frame->header.lun_id);
+ megasas_write_sense(cmd, SENSE_CODE(NO_SENSE));
+ cmd->frame->header.scsi_status = BUSY;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ is_write = (cmd->req->cmd.mode == SCSI_XFER_TO_DEV);
+ if (cmd->iov_size) {
+ if (is_write) {
+ trace_megasas_scsi_write_start(cmd->index, cmd->iov_size);
+ } else {
+ trace_megasas_scsi_read_start(cmd->index, cmd->iov_size);
+ }
+ } else {
+ trace_megasas_scsi_nodata(cmd->index);
+ }
+ megasas_enqueue_req(cmd, is_write);
+ return MFI_STAT_INVALID_STATUS;
+}
+
+static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd)
+{
+ uint32_t lba_count, lba_start_hi, lba_start_lo;
+ uint64_t lba_start;
+ bool is_write = (cmd->frame->header.frame_cmd == MFI_CMD_LD_WRITE);
+ uint8_t cdb[16];
+ int len;
+ struct SCSIDevice *sdev = NULL;
+
+ lba_count = le32_to_cpu(cmd->frame->io.header.data_len);
+ lba_start_lo = le32_to_cpu(cmd->frame->io.lba_lo);
+ lba_start_hi = le32_to_cpu(cmd->frame->io.lba_hi);
+ lba_start = ((uint64_t)lba_start_hi << 32) | lba_start_lo;
+
+ if (cmd->frame->header.target_id < MFI_MAX_LD &&
+ cmd->frame->header.lun_id == 0) {
+ sdev = scsi_device_find(&s->bus, 0, cmd->frame->header.target_id,
+ cmd->frame->header.lun_id);
+ }
+
+ trace_megasas_handle_io(cmd->index,
+ mfi_frame_desc[cmd->frame->header.frame_cmd],
+ cmd->frame->header.target_id,
+ cmd->frame->header.lun_id,
+ (unsigned long)lba_start, (unsigned long)lba_count);
+ if (!sdev) {
+ trace_megasas_io_target_not_present(cmd->index,
+ mfi_frame_desc[cmd->frame->header.frame_cmd],
+ cmd->frame->header.target_id, cmd->frame->header.lun_id);
+ return MFI_STAT_DEVICE_NOT_FOUND;
+ }
+
+ if (cmd->frame->header.cdb_len > 16) {
+ trace_megasas_scsi_invalid_cdb_len(
+ mfi_frame_desc[cmd->frame->header.frame_cmd], 1,
+ cmd->frame->header.target_id, cmd->frame->header.lun_id,
+ cmd->frame->header.cdb_len);
+ megasas_write_sense(cmd, SENSE_CODE(INVALID_OPCODE));
+ cmd->frame->header.scsi_status = CHECK_CONDITION;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ cmd->iov_size = lba_count * sdev->blocksize;
+ if (megasas_map_sgl(s, cmd, &cmd->frame->io.sgl)) {
+ megasas_write_sense(cmd, SENSE_CODE(TARGET_FAILURE));
+ cmd->frame->header.scsi_status = CHECK_CONDITION;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ megasas_encode_lba(cdb, lba_start, lba_count, is_write);
+ cmd->req = scsi_req_new(sdev, cmd->index,
+ cmd->frame->header.lun_id, cdb, cmd);
+ if (!cmd->req) {
+ trace_megasas_scsi_req_alloc_failed(
+ mfi_frame_desc[cmd->frame->header.frame_cmd],
+ cmd->frame->header.target_id, cmd->frame->header.lun_id);
+ megasas_write_sense(cmd, SENSE_CODE(NO_SENSE));
+ cmd->frame->header.scsi_status = BUSY;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+ len = megasas_enqueue_req(cmd, is_write);
+ if (len > 0) {
+ if (is_write) {
+ trace_megasas_io_write_start(cmd->index, lba_start, lba_count, len);
+ } else {
+ trace_megasas_io_read_start(cmd->index, lba_start, lba_count, len);
+ }
+ }
+ return MFI_STAT_INVALID_STATUS;
+}
+
+static int megasas_finish_internal_command(MegasasCmd *cmd,
+ SCSIRequest *req, size_t resid)
+{
+ int retval = MFI_STAT_INVALID_CMD;
+
+ if (cmd->frame->header.frame_cmd == MFI_CMD_DCMD) {
+ cmd->iov_size -= resid;
+ retval = megasas_finish_internal_dcmd(cmd, req);
+ }
+ return retval;
+}
+
+static QEMUSGList *megasas_get_sg_list(SCSIRequest *req)
+{
+ MegasasCmd *cmd = req->hba_private;
+
+ if (cmd->frame->header.frame_cmd == MFI_CMD_DCMD) {
+ return NULL;
+ } else {
+ return &cmd->qsg;
+ }
+}
+
+static void megasas_xfer_complete(SCSIRequest *req, uint32_t len)
+{
+ MegasasCmd *cmd = req->hba_private;
+ uint8_t *buf;
+ uint32_t opcode;
+
+ trace_megasas_io_complete(cmd->index, len);
+
+ if (cmd->frame->header.frame_cmd != MFI_CMD_DCMD) {
+ scsi_req_continue(req);
+ return;
+ }
+
+ buf = scsi_req_get_buf(req);
+ opcode = le32_to_cpu(cmd->frame->dcmd.opcode);
+ if (opcode == MFI_DCMD_PD_GET_INFO && cmd->iov_buf) {
+ struct mfi_pd_info *info = cmd->iov_buf;
+
+ if (info->inquiry_data[0] == 0x7f) {
+ memset(info->inquiry_data, 0, sizeof(info->inquiry_data));
+ memcpy(info->inquiry_data, buf, len);
+ } else if (info->vpd_page83[0] == 0x7f) {
+ memset(info->vpd_page83, 0, sizeof(info->vpd_page83));
+ memcpy(info->vpd_page83, buf, len);
+ }
+ scsi_req_continue(req);
+ } else if (opcode == MFI_DCMD_LD_GET_INFO) {
+ struct mfi_ld_info *info = cmd->iov_buf;
+
+ if (cmd->iov_buf) {
+ memcpy(info->vpd_page83, buf, sizeof(info->vpd_page83));
+ scsi_req_continue(req);
+ }
+ }
+}
+
+static void megasas_command_complete(SCSIRequest *req, uint32_t status,
+ size_t resid)
+{
+ MegasasCmd *cmd = req->hba_private;
+ uint8_t cmd_status = MFI_STAT_OK;
+
+ trace_megasas_command_complete(cmd->index, status, resid);
+
+ if (cmd->req != req) {
+ /*
+ * Internal command complete
+ */
+ cmd_status = megasas_finish_internal_command(cmd, req, resid);
+ if (cmd_status == MFI_STAT_INVALID_STATUS) {
+ return;
+ }
+ } else {
+ req->status = status;
+ trace_megasas_scsi_complete(cmd->index, req->status,
+ cmd->iov_size, req->cmd.xfer);
+ if (req->status != GOOD) {
+ cmd_status = MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+ if (req->status == CHECK_CONDITION) {
+ megasas_copy_sense(cmd);
+ }
+
+ megasas_unmap_sgl(cmd);
+ cmd->frame->header.scsi_status = req->status;
+ scsi_req_unref(cmd->req);
+ cmd->req = NULL;
+ }
+ cmd->frame->header.cmd_status = cmd_status;
+ megasas_unmap_frame(cmd->state, cmd);
+ megasas_complete_frame(cmd->state, cmd->context);
+}
+
+static void megasas_command_cancel(SCSIRequest *req)
+{
+ MegasasCmd *cmd = req->hba_private;
+
+ if (cmd) {
+ megasas_abort_command(cmd);
+ } else {
+ scsi_req_unref(req);
+ }
+}
+
+static int megasas_handle_abort(MegasasState *s, MegasasCmd *cmd)
+{
+ uint64_t abort_ctx = le64_to_cpu(cmd->frame->abort.abort_context);
+ hwaddr abort_addr, addr_hi, addr_lo;
+ MegasasCmd *abort_cmd;
+
+ addr_hi = le32_to_cpu(cmd->frame->abort.abort_mfi_addr_hi);
+ addr_lo = le32_to_cpu(cmd->frame->abort.abort_mfi_addr_lo);
+ abort_addr = ((uint64_t)addr_hi << 32) | addr_lo;
+
+ abort_cmd = megasas_lookup_frame(s, abort_addr);
+ if (!abort_cmd) {
+ trace_megasas_abort_no_cmd(cmd->index, abort_ctx);
+ s->event_count++;
+ return MFI_STAT_OK;
+ }
+ if (!megasas_use_queue64(s)) {
+ abort_ctx &= (uint64_t)0xFFFFFFFF;
+ }
+ if (abort_cmd->context != abort_ctx) {
+ trace_megasas_abort_invalid_context(cmd->index, abort_cmd->index,
+ abort_cmd->context);
+ s->event_count++;
+ return MFI_STAT_ABORT_NOT_POSSIBLE;
+ }
+ trace_megasas_abort_frame(cmd->index, abort_cmd->index);
+ megasas_abort_command(abort_cmd);
+ if (!s->event_cmd || abort_cmd != s->event_cmd) {
+ s->event_cmd = NULL;
+ }
+ s->event_count++;
+ return MFI_STAT_OK;
+}
+
+static void megasas_handle_frame(MegasasState *s, uint64_t frame_addr,
+ uint32_t frame_count)
+{
+ uint8_t frame_status = MFI_STAT_INVALID_CMD;
+ uint64_t frame_context;
+ MegasasCmd *cmd;
+
+ /*
+ * Always read 64bit context, top bits will be
+ * masked out if required in megasas_enqueue_frame()
+ */
+ frame_context = megasas_frame_get_context(s, frame_addr);
+
+ cmd = megasas_enqueue_frame(s, frame_addr, frame_context, frame_count);
+ if (!cmd) {
+ /* reply queue full */
+ trace_megasas_frame_busy(frame_addr);
+ megasas_frame_set_scsi_status(s, frame_addr, BUSY);
+ megasas_frame_set_cmd_status(s, frame_addr, MFI_STAT_SCSI_DONE_WITH_ERROR);
+ megasas_complete_frame(s, frame_context);
+ s->event_count++;
+ return;
+ }
+ switch (cmd->frame->header.frame_cmd) {
+ case MFI_CMD_INIT:
+ frame_status = megasas_init_firmware(s, cmd);
+ break;
+ case MFI_CMD_DCMD:
+ frame_status = megasas_handle_dcmd(s, cmd);
+ break;
+ case MFI_CMD_ABORT:
+ frame_status = megasas_handle_abort(s, cmd);
+ break;
+ case MFI_CMD_PD_SCSI_IO:
+ frame_status = megasas_handle_scsi(s, cmd, 0);
+ break;
+ case MFI_CMD_LD_SCSI_IO:
+ frame_status = megasas_handle_scsi(s, cmd, 1);
+ break;
+ case MFI_CMD_LD_READ:
+ case MFI_CMD_LD_WRITE:
+ frame_status = megasas_handle_io(s, cmd);
+ break;
+ default:
+ trace_megasas_unhandled_frame_cmd(cmd->index,
+ cmd->frame->header.frame_cmd);
+ s->event_count++;
+ break;
+ }
+ if (frame_status != MFI_STAT_INVALID_STATUS) {
+ if (cmd->frame) {
+ cmd->frame->header.cmd_status = frame_status;
+ } else {
+ megasas_frame_set_cmd_status(s, frame_addr, frame_status);
+ }
+ megasas_unmap_frame(s, cmd);
+ megasas_complete_frame(s, cmd->context);
+ }
+}
+
+static uint64_t megasas_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MegasasState *s = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ MegasasBaseClass *base_class = MEGASAS_DEVICE_GET_CLASS(s);
+ uint32_t retval = 0;
+
+ switch (addr) {
+ case MFI_IDB:
+ retval = 0;
+ trace_megasas_mmio_readl("MFI_IDB", retval);
+ break;
+ case MFI_OMSG0:
+ case MFI_OSP0:
+ retval = (msix_present(pci_dev) ? MFI_FWSTATE_MSIX_SUPPORTED : 0) |
+ (s->fw_state & MFI_FWSTATE_MASK) |
+ ((s->fw_sge & 0xff) << 16) |
+ (s->fw_cmds & 0xFFFF);
+ trace_megasas_mmio_readl(addr == MFI_OMSG0 ? "MFI_OMSG0" : "MFI_OSP0",
+ retval);
+ break;
+ case MFI_OSTS:
+ if (megasas_intr_enabled(s) && s->doorbell) {
+ retval = base_class->osts;
+ }
+ trace_megasas_mmio_readl("MFI_OSTS", retval);
+ break;
+ case MFI_OMSK:
+ retval = s->intr_mask;
+ trace_megasas_mmio_readl("MFI_OMSK", retval);
+ break;
+ case MFI_ODCR0:
+ retval = s->doorbell ? 1 : 0;
+ trace_megasas_mmio_readl("MFI_ODCR0", retval);
+ break;
+ case MFI_DIAG:
+ retval = s->diag;
+ trace_megasas_mmio_readl("MFI_DIAG", retval);
+ break;
+ case MFI_OSP1:
+ retval = 15;
+ trace_megasas_mmio_readl("MFI_OSP1", retval);
+ break;
+ default:
+ trace_megasas_mmio_invalid_readl(addr);
+ break;
+ }
+ return retval;
+}
+
+static int adp_reset_seq[] = {0x00, 0x04, 0x0b, 0x02, 0x07, 0x0d};
+
+static void megasas_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MegasasState *s = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ uint64_t frame_addr;
+ uint32_t frame_count;
+ int i;
+
+ switch (addr) {
+ case MFI_IDB:
+ trace_megasas_mmio_writel("MFI_IDB", val);
+ if (val & MFI_FWINIT_ABORT) {
+ /* Abort all pending cmds */
+ for (i = 0; i < s->fw_cmds; i++) {
+ megasas_abort_command(&s->frames[i]);
+ }
+ }
+ if (val & MFI_FWINIT_READY) {
+ /* move to FW READY */
+ megasas_soft_reset(s);
+ }
+ if (val & MFI_FWINIT_MFIMODE) {
+ /* discard MFIs */
+ }
+ if (val & MFI_FWINIT_STOP_ADP) {
+ /* Terminal error, stop processing */
+ s->fw_state = MFI_FWSTATE_FAULT;
+ }
+ break;
+ case MFI_OMSK:
+ trace_megasas_mmio_writel("MFI_OMSK", val);
+ s->intr_mask = val;
+ if (!megasas_intr_enabled(s) &&
+ !msi_enabled(pci_dev) &&
+ !msix_enabled(pci_dev)) {
+ trace_megasas_irq_lower();
+ pci_irq_deassert(pci_dev);
+ }
+ if (megasas_intr_enabled(s)) {
+ if (msix_enabled(pci_dev)) {
+ trace_megasas_msix_enabled(0);
+ } else if (msi_enabled(pci_dev)) {
+ trace_megasas_msi_enabled(0);
+ } else {
+ trace_megasas_intr_enabled();
+ }
+ } else {
+ trace_megasas_intr_disabled();
+ megasas_soft_reset(s);
+ }
+ break;
+ case MFI_ODCR0:
+ trace_megasas_mmio_writel("MFI_ODCR0", val);
+ s->doorbell = 0;
+ if (megasas_intr_enabled(s)) {
+ if (!msix_enabled(pci_dev) && !msi_enabled(pci_dev)) {
+ trace_megasas_irq_lower();
+ pci_irq_deassert(pci_dev);
+ }
+ }
+ break;
+ case MFI_IQPH:
+ trace_megasas_mmio_writel("MFI_IQPH", val);
+ /* Received high 32 bits of a 64 bit MFI frame address */
+ s->frame_hi = val;
+ break;
+ case MFI_IQPL:
+ trace_megasas_mmio_writel("MFI_IQPL", val);
+ /* Received low 32 bits of a 64 bit MFI frame address */
+ /* Fallthrough */
+ case MFI_IQP:
+ if (addr == MFI_IQP) {
+ trace_megasas_mmio_writel("MFI_IQP", val);
+ /* Received 64 bit MFI frame address */
+ s->frame_hi = 0;
+ }
+ frame_addr = (val & ~0x1F);
+ /* Add possible 64 bit offset */
+ frame_addr |= ((uint64_t)s->frame_hi << 32);
+ s->frame_hi = 0;
+ frame_count = (val >> 1) & 0xF;
+ megasas_handle_frame(s, frame_addr, frame_count);
+ break;
+ case MFI_SEQ:
+ trace_megasas_mmio_writel("MFI_SEQ", val);
+ /* Magic sequence to start ADP reset */
+ if (adp_reset_seq[s->adp_reset] == val) {
+ s->adp_reset++;
+ } else {
+ s->adp_reset = 0;
+ s->diag = 0;
+ }
+ if (s->adp_reset == 6) {
+ s->diag = MFI_DIAG_WRITE_ENABLE;
+ }
+ break;
+ case MFI_DIAG:
+ trace_megasas_mmio_writel("MFI_DIAG", val);
+ /* ADP reset */
+ if ((s->diag & MFI_DIAG_WRITE_ENABLE) &&
+ (val & MFI_DIAG_RESET_ADP)) {
+ s->diag |= MFI_DIAG_RESET_ADP;
+ megasas_soft_reset(s);
+ s->adp_reset = 0;
+ s->diag = 0;
+ }
+ break;
+ default:
+ trace_megasas_mmio_invalid_writel(addr, val);
+ break;
+ }
+}
+
+static const MemoryRegionOps megasas_mmio_ops = {
+ .read = megasas_mmio_read,
+ .write = megasas_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ }
+};
+
+static uint64_t megasas_port_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return megasas_mmio_read(opaque, addr & 0xff, size);
+}
+
+static void megasas_port_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ megasas_mmio_write(opaque, addr & 0xff, val, size);
+}
+
+static const MemoryRegionOps megasas_port_ops = {
+ .read = megasas_port_read,
+ .write = megasas_port_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+static uint64_t megasas_queue_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void megasas_queue_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ return;
+}
+
+static const MemoryRegionOps megasas_queue_ops = {
+ .read = megasas_queue_read,
+ .write = megasas_queue_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ }
+};
+
+static void megasas_soft_reset(MegasasState *s)
+{
+ int i;
+ MegasasCmd *cmd;
+
+ trace_megasas_reset(s->fw_state);
+ for (i = 0; i < s->fw_cmds; i++) {
+ cmd = &s->frames[i];
+ megasas_abort_command(cmd);
+ }
+ if (s->fw_state == MFI_FWSTATE_READY) {
+ BusChild *kid;
+
+ /*
+ * The EFI firmware doesn't handle UA,
+ * so we need to clear the Power On/Reset UA
+ * after the initial reset.
+ */
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = DO_UPCAST(SCSIDevice, qdev, kid->child);
+
+ sdev->unit_attention = SENSE_CODE(NO_SENSE);
+ scsi_device_unit_attention_reported(sdev);
+ }
+ }
+ megasas_reset_frames(s);
+ s->reply_queue_len = s->fw_cmds;
+ s->reply_queue_pa = 0;
+ s->consumer_pa = 0;
+ s->producer_pa = 0;
+ s->fw_state = MFI_FWSTATE_READY;
+ s->doorbell = 0;
+ s->intr_mask = MEGASAS_INTR_DISABLED_MASK;
+ s->frame_hi = 0;
+ s->flags &= ~MEGASAS_MASK_USE_QUEUE64;
+ s->event_count++;
+ s->boot_event = s->event_count;
+}
+
+static void megasas_scsi_reset(DeviceState *dev)
+{
+ MegasasState *s = MEGASAS(dev);
+
+ megasas_soft_reset(s);
+}
+
+static const VMStateDescription vmstate_megasas_gen1 = {
+ .name = "megasas",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, MegasasState),
+ VMSTATE_MSIX(parent_obj, MegasasState),
+
+ VMSTATE_INT32(fw_state, MegasasState),
+ VMSTATE_INT32(intr_mask, MegasasState),
+ VMSTATE_INT32(doorbell, MegasasState),
+ VMSTATE_UINT64(reply_queue_pa, MegasasState),
+ VMSTATE_UINT64(consumer_pa, MegasasState),
+ VMSTATE_UINT64(producer_pa, MegasasState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_megasas_gen2 = {
+ .name = "megasas-gen2",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .minimum_version_id_old = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCIE_DEVICE(parent_obj, MegasasState),
+ VMSTATE_MSIX(parent_obj, MegasasState),
+
+ VMSTATE_INT32(fw_state, MegasasState),
+ VMSTATE_INT32(intr_mask, MegasasState),
+ VMSTATE_INT32(doorbell, MegasasState),
+ VMSTATE_UINT64(reply_queue_pa, MegasasState),
+ VMSTATE_UINT64(consumer_pa, MegasasState),
+ VMSTATE_UINT64(producer_pa, MegasasState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void megasas_scsi_uninit(PCIDevice *d)
+{
+ MegasasState *s = MEGASAS(d);
+
+ if (megasas_use_msix(s)) {
+ msix_uninit(d, &s->mmio_io, &s->mmio_io);
+ }
+ if (megasas_use_msi(s)) {
+ msi_uninit(d);
+ }
+}
+
+static const struct SCSIBusInfo megasas_scsi_info = {
+ .tcq = true,
+ .max_target = MFI_MAX_LD,
+ .max_lun = 255,
+
+ .transfer_data = megasas_xfer_complete,
+ .get_sg_list = megasas_get_sg_list,
+ .complete = megasas_command_complete,
+ .cancel = megasas_command_cancel,
+};
+
+static void megasas_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ DeviceState *d = DEVICE(dev);
+ MegasasState *s = MEGASAS(dev);
+ MegasasBaseClass *b = MEGASAS_DEVICE_GET_CLASS(s);
+ uint8_t *pci_conf;
+ int i, bar_type;
+
+ pci_conf = dev->config;
+
+ /* PCI latency timer = 0 */
+ pci_conf[PCI_LATENCY_TIMER] = 0;
+ /* Interrupt pin 1 */
+ pci_conf[PCI_INTERRUPT_PIN] = 0x01;
+
+ memory_region_init_io(&s->mmio_io, OBJECT(s), &megasas_mmio_ops, s,
+ "megasas-mmio", 0x4000);
+ memory_region_init_io(&s->port_io, OBJECT(s), &megasas_port_ops, s,
+ "megasas-io", 256);
+ memory_region_init_io(&s->queue_io, OBJECT(s), &megasas_queue_ops, s,
+ "megasas-queue", 0x40000);
+
+ if (megasas_use_msi(s) &&
+ msi_init(dev, 0x50, 1, true, false)) {
+ s->flags &= ~MEGASAS_MASK_USE_MSI;
+ }
+ if (megasas_use_msix(s) &&
+ msix_init(dev, 15, &s->mmio_io, b->mmio_bar, 0x2000,
+ &s->mmio_io, b->mmio_bar, 0x3800, 0x68)) {
+ s->flags &= ~MEGASAS_MASK_USE_MSIX;
+ }
+ if (pci_is_express(dev)) {
+ pcie_endpoint_cap_init(dev, 0xa0);
+ }
+
+ bar_type = PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64;
+ pci_register_bar(dev, b->ioport_bar,
+ PCI_BASE_ADDRESS_SPACE_IO, &s->port_io);
+ pci_register_bar(dev, b->mmio_bar, bar_type, &s->mmio_io);
+ pci_register_bar(dev, 3, bar_type, &s->queue_io);
+
+ if (megasas_use_msix(s)) {
+ msix_vector_use(dev, 0);
+ }
+
+ s->fw_state = MFI_FWSTATE_READY;
+ if (!s->sas_addr) {
+ s->sas_addr = ((NAA_LOCALLY_ASSIGNED_ID << 24) |
+ IEEE_COMPANY_LOCALLY_ASSIGNED) << 36;
+ s->sas_addr |= (pci_bus_num(dev->bus) << 16);
+ s->sas_addr |= (PCI_SLOT(dev->devfn) << 8);
+ s->sas_addr |= PCI_FUNC(dev->devfn);
+ }
+ if (!s->hba_serial) {
+ s->hba_serial = g_strdup(MEGASAS_HBA_SERIAL);
+ }
+ if (s->fw_sge >= MEGASAS_MAX_SGE - MFI_PASS_FRAME_SIZE) {
+ s->fw_sge = MEGASAS_MAX_SGE - MFI_PASS_FRAME_SIZE;
+ } else if (s->fw_sge >= 128 - MFI_PASS_FRAME_SIZE) {
+ s->fw_sge = 128 - MFI_PASS_FRAME_SIZE;
+ } else {
+ s->fw_sge = 64 - MFI_PASS_FRAME_SIZE;
+ }
+ if (s->fw_cmds > MEGASAS_MAX_FRAMES) {
+ s->fw_cmds = MEGASAS_MAX_FRAMES;
+ }
+ trace_megasas_init(s->fw_sge, s->fw_cmds,
+ megasas_is_jbod(s) ? "jbod" : "raid");
+
+ if (megasas_is_jbod(s)) {
+ s->fw_luns = MFI_MAX_SYS_PDS;
+ } else {
+ s->fw_luns = MFI_MAX_LD;
+ }
+ s->producer_pa = 0;
+ s->consumer_pa = 0;
+ for (i = 0; i < s->fw_cmds; i++) {
+ s->frames[i].index = i;
+ s->frames[i].context = -1;
+ s->frames[i].pa = 0;
+ s->frames[i].state = s;
+ }
+
+ scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev),
+ &megasas_scsi_info, NULL);
+ if (!d->hotplugged) {
+ scsi_bus_legacy_handle_cmdline(&s->bus, errp);
+ }
+}
+
+static Property megasas_properties_gen1[] = {
+ DEFINE_PROP_UINT32("max_sge", MegasasState, fw_sge,
+ MEGASAS_DEFAULT_SGE),
+ DEFINE_PROP_UINT32("max_cmds", MegasasState, fw_cmds,
+ MEGASAS_DEFAULT_FRAMES),
+ DEFINE_PROP_STRING("hba_serial", MegasasState, hba_serial),
+ DEFINE_PROP_UINT64("sas_address", MegasasState, sas_addr, 0),
+ DEFINE_PROP_BIT("use_msi", MegasasState, flags,
+ MEGASAS_FLAG_USE_MSI, false),
+ DEFINE_PROP_BIT("use_msix", MegasasState, flags,
+ MEGASAS_FLAG_USE_MSIX, false),
+ DEFINE_PROP_BIT("use_jbod", MegasasState, flags,
+ MEGASAS_FLAG_USE_JBOD, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property megasas_properties_gen2[] = {
+ DEFINE_PROP_UINT32("max_sge", MegasasState, fw_sge,
+ MEGASAS_DEFAULT_SGE),
+ DEFINE_PROP_UINT32("max_cmds", MegasasState, fw_cmds,
+ MEGASAS_GEN2_DEFAULT_FRAMES),
+ DEFINE_PROP_STRING("hba_serial", MegasasState, hba_serial),
+ DEFINE_PROP_UINT64("sas_address", MegasasState, sas_addr, 0),
+ DEFINE_PROP_BIT("use_msi", MegasasState, flags,
+ MEGASAS_FLAG_USE_MSI, true),
+ DEFINE_PROP_BIT("use_msix", MegasasState, flags,
+ MEGASAS_FLAG_USE_MSIX, true),
+ DEFINE_PROP_BIT("use_jbod", MegasasState, flags,
+ MEGASAS_FLAG_USE_JBOD, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+typedef struct MegasasInfo {
+ const char *name;
+ const char *desc;
+ const char *product_name;
+ const char *product_version;
+ uint16_t device_id;
+ uint16_t subsystem_id;
+ int ioport_bar;
+ int mmio_bar;
+ bool is_express;
+ int osts;
+ const VMStateDescription *vmsd;
+ Property *props;
+} MegasasInfo;
+
+static struct MegasasInfo megasas_devices[] = {
+ {
+ .name = TYPE_MEGASAS_GEN1,
+ .desc = "LSI MegaRAID SAS 1078",
+ .product_name = "LSI MegaRAID SAS 8708EM2",
+ .product_version = MEGASAS_VERSION_GEN1,
+ .device_id = PCI_DEVICE_ID_LSI_SAS1078,
+ .subsystem_id = 0x1013,
+ .ioport_bar = 2,
+ .mmio_bar = 0,
+ .osts = MFI_1078_RM | 1,
+ .is_express = false,
+ .vmsd = &vmstate_megasas_gen1,
+ .props = megasas_properties_gen1,
+ },{
+ .name = TYPE_MEGASAS_GEN2,
+ .desc = "LSI MegaRAID SAS 2108",
+ .product_name = "LSI MegaRAID SAS 9260-8i",
+ .product_version = MEGASAS_VERSION_GEN2,
+ .device_id = PCI_DEVICE_ID_LSI_SAS0079,
+ .subsystem_id = 0x9261,
+ .ioport_bar = 0,
+ .mmio_bar = 1,
+ .osts = MFI_GEN2_RM,
+ .is_express = true,
+ .vmsd = &vmstate_megasas_gen2,
+ .props = megasas_properties_gen2,
+ }
+};
+
+static void megasas_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc);
+ MegasasBaseClass *e = MEGASAS_DEVICE_CLASS(oc);
+ const MegasasInfo *info = data;
+
+ pc->realize = megasas_scsi_realize;
+ pc->exit = megasas_scsi_uninit;
+ pc->vendor_id = PCI_VENDOR_ID_LSI_LOGIC;
+ pc->device_id = info->device_id;
+ pc->subsystem_vendor_id = PCI_VENDOR_ID_LSI_LOGIC;
+ pc->subsystem_id = info->subsystem_id;
+ pc->class_id = PCI_CLASS_STORAGE_RAID;
+ pc->is_express = info->is_express;
+ e->mmio_bar = info->mmio_bar;
+ e->ioport_bar = info->ioport_bar;
+ e->osts = info->osts;
+ e->product_name = info->product_name;
+ e->product_version = info->product_version;
+ dc->props = info->props;
+ dc->reset = megasas_scsi_reset;
+ dc->vmsd = info->vmsd;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->desc = info->desc;
+}
+
+static const TypeInfo megasas_info = {
+ .name = TYPE_MEGASAS_BASE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(MegasasState),
+ .class_size = sizeof(MegasasBaseClass),
+ .abstract = true,
+};
+
+static void megasas_register_types(void)
+{
+ int i;
+
+ type_register_static(&megasas_info);
+ for (i = 0; i < ARRAY_SIZE(megasas_devices); i++) {
+ const MegasasInfo *info = &megasas_devices[i];
+ TypeInfo type_info = {};
+
+ type_info.name = info->name;
+ type_info.parent = TYPE_MEGASAS_BASE;
+ type_info.class_data = (void *)info;
+ type_info.class_init = megasas_class_init;
+
+ type_register(&type_info);
+ }
+}
+
+type_init(megasas_register_types)
diff --git a/hw/scsi/mfi.h b/hw/scsi/mfi.h
new file mode 100644
index 00000000..29d41775
--- /dev/null
+++ b/hw/scsi/mfi.h
@@ -0,0 +1,1272 @@
+/*
+ * NetBSD header file, copied from
+ * http://gitorious.org/freebsd/freebsd/blobs/HEAD/sys/dev/mfi/mfireg.h
+ */
+/*-
+ * Copyright (c) 2006 IronPort Systems
+ * Copyright (c) 2007 LSI Corp.
+ * Copyright (c) 2007 Rajesh Prabhakaran.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MFI_REG_H
+#define MFI_REG_H
+
+/*
+ * MegaRAID SAS MFI firmware definitions
+ */
+
+/*
+ * Start with the register set. All registers are 32 bits wide.
+ * The usual Intel IOP style setup.
+ */
+#define MFI_IMSG0 0x10 /* Inbound message 0 */
+#define MFI_IMSG1 0x14 /* Inbound message 1 */
+#define MFI_OMSG0 0x18 /* Outbound message 0 */
+#define MFI_OMSG1 0x1c /* Outbound message 1 */
+#define MFI_IDB 0x20 /* Inbound doorbell */
+#define MFI_ISTS 0x24 /* Inbound interrupt status */
+#define MFI_IMSK 0x28 /* Inbound interrupt mask */
+#define MFI_ODB 0x2c /* Outbound doorbell */
+#define MFI_OSTS 0x30 /* Outbound interrupt status */
+#define MFI_OMSK 0x34 /* Outbound interrupt mask */
+#define MFI_IQP 0x40 /* Inbound queue port */
+#define MFI_OQP 0x44 /* Outbound queue port */
+
+/*
+ * 1078 specific related register
+ */
+#define MFI_ODR0 0x9c /* outbound doorbell register0 */
+#define MFI_ODCR0 0xa0 /* outbound doorbell clear register0 */
+#define MFI_OSP0 0xb0 /* outbound scratch pad0 */
+#define MFI_OSP1 0xb4 /* outbound scratch pad1 */
+#define MFI_IQPL 0xc0 /* Inbound queue port (low bytes) */
+#define MFI_IQPH 0xc4 /* Inbound queue port (high bytes) */
+#define MFI_DIAG 0xf8 /* Host diag */
+#define MFI_SEQ 0xfc /* Sequencer offset */
+#define MFI_1078_EIM 0x80000004 /* 1078 enable intrrupt mask */
+#define MFI_RMI 0x2 /* reply message interrupt */
+#define MFI_1078_RM 0x80000000 /* reply 1078 message interrupt */
+#define MFI_ODC 0x4 /* outbound doorbell change interrupt */
+
+/*
+ * gen2 specific changes
+ */
+#define MFI_GEN2_EIM 0x00000005 /* gen2 enable interrupt mask */
+#define MFI_GEN2_RM 0x00000001 /* reply gen2 message interrupt */
+
+/*
+ * skinny specific changes
+ */
+#define MFI_SKINNY_IDB 0x00 /* Inbound doorbell is at 0x00 for skinny */
+#define MFI_SKINNY_RM 0x00000001 /* reply skinny message interrupt */
+
+/* Bits for MFI_OSTS */
+#define MFI_OSTS_INTR_VALID 0x00000002
+
+/*
+ * Firmware state values. Found in OMSG0 during initialization.
+ */
+#define MFI_FWSTATE_MASK 0xf0000000
+#define MFI_FWSTATE_UNDEFINED 0x00000000
+#define MFI_FWSTATE_BB_INIT 0x10000000
+#define MFI_FWSTATE_FW_INIT 0x40000000
+#define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000
+#define MFI_FWSTATE_FW_INIT_2 0x70000000
+#define MFI_FWSTATE_DEVICE_SCAN 0x80000000
+#define MFI_FWSTATE_BOOT_MSG_PENDING 0x90000000
+#define MFI_FWSTATE_FLUSH_CACHE 0xa0000000
+#define MFI_FWSTATE_READY 0xb0000000
+#define MFI_FWSTATE_OPERATIONAL 0xc0000000
+#define MFI_FWSTATE_FAULT 0xf0000000
+#define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000
+#define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff
+#define MFI_FWSTATE_MSIX_SUPPORTED 0x04000000
+#define MFI_FWSTATE_HOSTMEMREQD_MASK 0x08000000
+
+/*
+ * Control bits to drive the card to ready state. These go into the IDB
+ * register.
+ */
+#define MFI_FWINIT_ABORT 0x00000001 /* Abort all pending commands */
+#define MFI_FWINIT_READY 0x00000002 /* Move from operational to ready */
+#define MFI_FWINIT_MFIMODE 0x00000004 /* unknown */
+#define MFI_FWINIT_CLEAR_HANDSHAKE 0x00000008 /* Respond to WAIT_HANDSHAKE */
+#define MFI_FWINIT_HOTPLUG 0x00000010
+#define MFI_FWINIT_STOP_ADP 0x00000020 /* Move to operational, stop */
+#define MFI_FWINIT_ADP_RESET 0x00000040 /* Reset ADP */
+
+/*
+ * Control bits for the DIAG register
+ */
+#define MFI_DIAG_WRITE_ENABLE 0x00000080
+#define MFI_DIAG_RESET_ADP 0x00000004
+
+/* MFI Commands */
+typedef enum {
+ MFI_CMD_INIT = 0x00,
+ MFI_CMD_LD_READ,
+ MFI_CMD_LD_WRITE,
+ MFI_CMD_LD_SCSI_IO,
+ MFI_CMD_PD_SCSI_IO,
+ MFI_CMD_DCMD,
+ MFI_CMD_ABORT,
+ MFI_CMD_SMP,
+ MFI_CMD_STP
+} mfi_cmd_t;
+
+/* Direct commands */
+typedef enum {
+ MFI_DCMD_CTRL_MFI_HOST_MEM_ALLOC = 0x0100e100,
+ MFI_DCMD_CTRL_GET_INFO = 0x01010000,
+ MFI_DCMD_CTRL_GET_PROPERTIES = 0x01020100,
+ MFI_DCMD_CTRL_SET_PROPERTIES = 0x01020200,
+ MFI_DCMD_CTRL_ALARM = 0x01030000,
+ MFI_DCMD_CTRL_ALARM_GET = 0x01030100,
+ MFI_DCMD_CTRL_ALARM_ENABLE = 0x01030200,
+ MFI_DCMD_CTRL_ALARM_DISABLE = 0x01030300,
+ MFI_DCMD_CTRL_ALARM_SILENCE = 0x01030400,
+ MFI_DCMD_CTRL_ALARM_TEST = 0x01030500,
+ MFI_DCMD_CTRL_EVENT_GETINFO = 0x01040100,
+ MFI_DCMD_CTRL_EVENT_CLEAR = 0x01040200,
+ MFI_DCMD_CTRL_EVENT_GET = 0x01040300,
+ MFI_DCMD_CTRL_EVENT_COUNT = 0x01040400,
+ MFI_DCMD_CTRL_EVENT_WAIT = 0x01040500,
+ MFI_DCMD_CTRL_SHUTDOWN = 0x01050000,
+ MFI_DCMD_HIBERNATE_STANDBY = 0x01060000,
+ MFI_DCMD_CTRL_GET_TIME = 0x01080101,
+ MFI_DCMD_CTRL_SET_TIME = 0x01080102,
+ MFI_DCMD_CTRL_BIOS_DATA_GET = 0x010c0100,
+ MFI_DCMD_CTRL_BIOS_DATA_SET = 0x010c0200,
+ MFI_DCMD_CTRL_FACTORY_DEFAULTS = 0x010d0000,
+ MFI_DCMD_CTRL_MFC_DEFAULTS_GET = 0x010e0201,
+ MFI_DCMD_CTRL_MFC_DEFAULTS_SET = 0x010e0202,
+ MFI_DCMD_CTRL_CACHE_FLUSH = 0x01101000,
+ MFI_DCMD_PD_GET_LIST = 0x02010000,
+ MFI_DCMD_PD_LIST_QUERY = 0x02010100,
+ MFI_DCMD_PD_GET_INFO = 0x02020000,
+ MFI_DCMD_PD_STATE_SET = 0x02030100,
+ MFI_DCMD_PD_REBUILD = 0x02040100,
+ MFI_DCMD_PD_BLINK = 0x02070100,
+ MFI_DCMD_PD_UNBLINK = 0x02070200,
+ MFI_DCMD_LD_GET_LIST = 0x03010000,
+ MFI_DCMD_LD_LIST_QUERY = 0x03010100,
+ MFI_DCMD_LD_GET_INFO = 0x03020000,
+ MFI_DCMD_LD_GET_PROP = 0x03030000,
+ MFI_DCMD_LD_SET_PROP = 0x03040000,
+ MFI_DCMD_LD_DELETE = 0x03090000,
+ MFI_DCMD_CFG_READ = 0x04010000,
+ MFI_DCMD_CFG_ADD = 0x04020000,
+ MFI_DCMD_CFG_CLEAR = 0x04030000,
+ MFI_DCMD_CFG_FOREIGN_READ = 0x04060100,
+ MFI_DCMD_CFG_FOREIGN_IMPORT = 0x04060400,
+ MFI_DCMD_BBU_STATUS = 0x05010000,
+ MFI_DCMD_BBU_CAPACITY_INFO = 0x05020000,
+ MFI_DCMD_BBU_DESIGN_INFO = 0x05030000,
+ MFI_DCMD_BBU_PROP_GET = 0x05050100,
+ MFI_DCMD_CLUSTER = 0x08000000,
+ MFI_DCMD_CLUSTER_RESET_ALL = 0x08010100,
+ MFI_DCMD_CLUSTER_RESET_LD = 0x08010200
+} mfi_dcmd_t;
+
+/* Modifiers for MFI_DCMD_CTRL_FLUSHCACHE */
+#define MFI_FLUSHCACHE_CTRL 0x01
+#define MFI_FLUSHCACHE_DISK 0x02
+
+/* Modifiers for MFI_DCMD_CTRL_SHUTDOWN */
+#define MFI_SHUTDOWN_SPINDOWN 0x01
+
+/*
+ * MFI Frame flags
+ */
+typedef enum {
+ MFI_FRAME_DONT_POST_IN_REPLY_QUEUE = 0x0001,
+ MFI_FRAME_SGL64 = 0x0002,
+ MFI_FRAME_SENSE64 = 0x0004,
+ MFI_FRAME_DIR_WRITE = 0x0008,
+ MFI_FRAME_DIR_READ = 0x0010,
+ MFI_FRAME_IEEE_SGL = 0x0020,
+} mfi_frame_flags;
+
+/* MFI Status codes */
+typedef enum {
+ MFI_STAT_OK = 0x00,
+ MFI_STAT_INVALID_CMD,
+ MFI_STAT_INVALID_DCMD,
+ MFI_STAT_INVALID_PARAMETER,
+ MFI_STAT_INVALID_SEQUENCE_NUMBER,
+ MFI_STAT_ABORT_NOT_POSSIBLE,
+ MFI_STAT_APP_HOST_CODE_NOT_FOUND,
+ MFI_STAT_APP_IN_USE,
+ MFI_STAT_APP_NOT_INITIALIZED,
+ MFI_STAT_ARRAY_INDEX_INVALID,
+ MFI_STAT_ARRAY_ROW_NOT_EMPTY,
+ MFI_STAT_CONFIG_RESOURCE_CONFLICT,
+ MFI_STAT_DEVICE_NOT_FOUND,
+ MFI_STAT_DRIVE_TOO_SMALL,
+ MFI_STAT_FLASH_ALLOC_FAIL,
+ MFI_STAT_FLASH_BUSY,
+ MFI_STAT_FLASH_ERROR = 0x10,
+ MFI_STAT_FLASH_IMAGE_BAD,
+ MFI_STAT_FLASH_IMAGE_INCOMPLETE,
+ MFI_STAT_FLASH_NOT_OPEN,
+ MFI_STAT_FLASH_NOT_STARTED,
+ MFI_STAT_FLUSH_FAILED,
+ MFI_STAT_HOST_CODE_NOT_FOUNT,
+ MFI_STAT_LD_CC_IN_PROGRESS,
+ MFI_STAT_LD_INIT_IN_PROGRESS,
+ MFI_STAT_LD_LBA_OUT_OF_RANGE,
+ MFI_STAT_LD_MAX_CONFIGURED,
+ MFI_STAT_LD_NOT_OPTIMAL,
+ MFI_STAT_LD_RBLD_IN_PROGRESS,
+ MFI_STAT_LD_RECON_IN_PROGRESS,
+ MFI_STAT_LD_WRONG_RAID_LEVEL,
+ MFI_STAT_MAX_SPARES_EXCEEDED,
+ MFI_STAT_MEMORY_NOT_AVAILABLE = 0x20,
+ MFI_STAT_MFC_HW_ERROR,
+ MFI_STAT_NO_HW_PRESENT,
+ MFI_STAT_NOT_FOUND,
+ MFI_STAT_NOT_IN_ENCL,
+ MFI_STAT_PD_CLEAR_IN_PROGRESS,
+ MFI_STAT_PD_TYPE_WRONG,
+ MFI_STAT_PR_DISABLED,
+ MFI_STAT_ROW_INDEX_INVALID,
+ MFI_STAT_SAS_CONFIG_INVALID_ACTION,
+ MFI_STAT_SAS_CONFIG_INVALID_DATA,
+ MFI_STAT_SAS_CONFIG_INVALID_PAGE,
+ MFI_STAT_SAS_CONFIG_INVALID_TYPE,
+ MFI_STAT_SCSI_DONE_WITH_ERROR,
+ MFI_STAT_SCSI_IO_FAILED,
+ MFI_STAT_SCSI_RESERVATION_CONFLICT,
+ MFI_STAT_SHUTDOWN_FAILED = 0x30,
+ MFI_STAT_TIME_NOT_SET,
+ MFI_STAT_WRONG_STATE,
+ MFI_STAT_LD_OFFLINE,
+ MFI_STAT_PEER_NOTIFICATION_REJECTED,
+ MFI_STAT_PEER_NOTIFICATION_FAILED,
+ MFI_STAT_RESERVATION_IN_PROGRESS,
+ MFI_STAT_I2C_ERRORS_DETECTED,
+ MFI_STAT_PCI_ERRORS_DETECTED,
+ MFI_STAT_DIAG_FAILED,
+ MFI_STAT_BOOT_MSG_PENDING,
+ MFI_STAT_FOREIGN_CONFIG_INCOMPLETE,
+ MFI_STAT_INVALID_SGL,
+ MFI_STAT_UNSUPPORTED_HW,
+ MFI_STAT_CC_SCHEDULE_DISABLED,
+ MFI_STAT_PD_COPYBACK_IN_PROGRESS,
+ MFI_STAT_MULTIPLE_PDS_IN_ARRAY = 0x40,
+ MFI_STAT_FW_DOWNLOAD_ERROR,
+ MFI_STAT_FEATURE_SECURITY_NOT_ENABLED,
+ MFI_STAT_LOCK_KEY_ALREADY_EXISTS,
+ MFI_STAT_LOCK_KEY_BACKUP_NOT_ALLOWED,
+ MFI_STAT_LOCK_KEY_VERIFY_NOT_ALLOWED,
+ MFI_STAT_LOCK_KEY_VERIFY_FAILED,
+ MFI_STAT_LOCK_KEY_REKEY_NOT_ALLOWED,
+ MFI_STAT_LOCK_KEY_INVALID,
+ MFI_STAT_LOCK_KEY_ESCROW_INVALID,
+ MFI_STAT_LOCK_KEY_BACKUP_REQUIRED,
+ MFI_STAT_SECURE_LD_EXISTS,
+ MFI_STAT_LD_SECURE_NOT_ALLOWED,
+ MFI_STAT_REPROVISION_NOT_ALLOWED,
+ MFI_STAT_PD_SECURITY_TYPE_WRONG,
+ MFI_STAT_LD_ENCRYPTION_TYPE_INVALID,
+ MFI_STAT_CONFIG_FDE_NON_FDE_MIX_NOT_ALLOWED = 0x50,
+ MFI_STAT_CONFIG_LD_ENCRYPTION_TYPE_MIX_NOT_ALLOWED,
+ MFI_STAT_SECRET_KEY_NOT_ALLOWED,
+ MFI_STAT_PD_HW_ERRORS_DETECTED,
+ MFI_STAT_LD_CACHE_PINNED,
+ MFI_STAT_POWER_STATE_SET_IN_PROGRESS,
+ MFI_STAT_POWER_STATE_SET_BUSY,
+ MFI_STAT_POWER_STATE_WRONG,
+ MFI_STAT_PR_NO_AVAILABLE_PD_FOUND,
+ MFI_STAT_CTRL_RESET_REQUIRED,
+ MFI_STAT_LOCK_KEY_EKM_NO_BOOT_AGENT,
+ MFI_STAT_SNAP_NO_SPACE,
+ MFI_STAT_SNAP_PARTIAL_FAILURE,
+ MFI_STAT_UPGRADE_KEY_INCOMPATIBLE,
+ MFI_STAT_PFK_INCOMPATIBLE,
+ MFI_STAT_PD_MAX_UNCONFIGURED,
+ MFI_STAT_IO_METRICS_DISABLED = 0x60,
+ MFI_STAT_AEC_NOT_STOPPED,
+ MFI_STAT_PI_TYPE_WRONG,
+ MFI_STAT_LD_PD_PI_INCOMPATIBLE,
+ MFI_STAT_PI_NOT_ENABLED,
+ MFI_STAT_LD_BLOCK_SIZE_MISMATCH,
+ MFI_STAT_INVALID_STATUS = 0xFF
+} mfi_status_t;
+
+/* Event classes */
+typedef enum {
+ MFI_EVT_CLASS_DEBUG = -2,
+ MFI_EVT_CLASS_PROGRESS = -1,
+ MFI_EVT_CLASS_INFO = 0,
+ MFI_EVT_CLASS_WARNING = 1,
+ MFI_EVT_CLASS_CRITICAL = 2,
+ MFI_EVT_CLASS_FATAL = 3,
+ MFI_EVT_CLASS_DEAD = 4
+} mfi_evt_class_t;
+
+/* Event locales */
+typedef enum {
+ MFI_EVT_LOCALE_LD = 0x0001,
+ MFI_EVT_LOCALE_PD = 0x0002,
+ MFI_EVT_LOCALE_ENCL = 0x0004,
+ MFI_EVT_LOCALE_BBU = 0x0008,
+ MFI_EVT_LOCALE_SAS = 0x0010,
+ MFI_EVT_LOCALE_CTRL = 0x0020,
+ MFI_EVT_LOCALE_CONFIG = 0x0040,
+ MFI_EVT_LOCALE_CLUSTER = 0x0080,
+ MFI_EVT_LOCALE_ALL = 0xffff
+} mfi_evt_locale_t;
+
+/* Event args */
+typedef enum {
+ MR_EVT_ARGS_NONE = 0x00,
+ MR_EVT_ARGS_CDB_SENSE,
+ MR_EVT_ARGS_LD,
+ MR_EVT_ARGS_LD_COUNT,
+ MR_EVT_ARGS_LD_LBA,
+ MR_EVT_ARGS_LD_OWNER,
+ MR_EVT_ARGS_LD_LBA_PD_LBA,
+ MR_EVT_ARGS_LD_PROG,
+ MR_EVT_ARGS_LD_STATE,
+ MR_EVT_ARGS_LD_STRIP,
+ MR_EVT_ARGS_PD,
+ MR_EVT_ARGS_PD_ERR,
+ MR_EVT_ARGS_PD_LBA,
+ MR_EVT_ARGS_PD_LBA_LD,
+ MR_EVT_ARGS_PD_PROG,
+ MR_EVT_ARGS_PD_STATE,
+ MR_EVT_ARGS_PCI,
+ MR_EVT_ARGS_RATE,
+ MR_EVT_ARGS_STR,
+ MR_EVT_ARGS_TIME,
+ MR_EVT_ARGS_ECC,
+ MR_EVT_ARGS_LD_PROP,
+ MR_EVT_ARGS_PD_SPARE,
+ MR_EVT_ARGS_PD_INDEX,
+ MR_EVT_ARGS_DIAG_PASS,
+ MR_EVT_ARGS_DIAG_FAIL,
+ MR_EVT_ARGS_PD_LBA_LBA,
+ MR_EVT_ARGS_PORT_PHY,
+ MR_EVT_ARGS_PD_MISSING,
+ MR_EVT_ARGS_PD_ADDRESS,
+ MR_EVT_ARGS_BITMAP,
+ MR_EVT_ARGS_CONNECTOR,
+ MR_EVT_ARGS_PD_PD,
+ MR_EVT_ARGS_PD_FRU,
+ MR_EVT_ARGS_PD_PATHINFO,
+ MR_EVT_ARGS_PD_POWER_STATE,
+ MR_EVT_ARGS_GENERIC,
+} mfi_evt_args;
+
+/* Event codes */
+#define MR_EVT_CFG_CLEARED 0x0004
+#define MR_EVT_CTRL_SHUTDOWN 0x002a
+#define MR_EVT_LD_STATE_CHANGE 0x0051
+#define MR_EVT_PD_INSERTED 0x005b
+#define MR_EVT_PD_REMOVED 0x0070
+#define MR_EVT_PD_STATE_CHANGED 0x0072
+#define MR_EVT_LD_CREATED 0x008a
+#define MR_EVT_LD_DELETED 0x008b
+#define MR_EVT_FOREIGN_CFG_IMPORTED 0x00db
+#define MR_EVT_LD_OFFLINE 0x00fc
+#define MR_EVT_CTRL_HOST_BUS_SCAN_REQUESTED 0x0152
+
+typedef enum {
+ MR_LD_CACHE_WRITE_BACK = 0x01,
+ MR_LD_CACHE_WRITE_ADAPTIVE = 0x02,
+ MR_LD_CACHE_READ_AHEAD = 0x04,
+ MR_LD_CACHE_READ_ADAPTIVE = 0x08,
+ MR_LD_CACHE_WRITE_CACHE_BAD_BBU = 0x10,
+ MR_LD_CACHE_ALLOW_WRITE_CACHE = 0x20,
+ MR_LD_CACHE_ALLOW_READ_CACHE = 0x40
+} mfi_ld_cache;
+
+typedef enum {
+ MR_PD_CACHE_UNCHANGED = 0,
+ MR_PD_CACHE_ENABLE = 1,
+ MR_PD_CACHE_DISABLE = 2
+} mfi_pd_cache;
+
+typedef enum {
+ MR_PD_QUERY_TYPE_ALL = 0,
+ MR_PD_QUERY_TYPE_STATE = 1,
+ MR_PD_QUERY_TYPE_POWER_STATE = 2,
+ MR_PD_QUERY_TYPE_MEDIA_TYPE = 3,
+ MR_PD_QUERY_TYPE_SPEED = 4,
+ MR_PD_QUERY_TYPE_EXPOSED_TO_HOST = 5, /*query for system drives */
+} mfi_pd_query_type;
+
+typedef enum {
+ MR_LD_QUERY_TYPE_ALL = 0,
+ MR_LD_QUERY_TYPE_EXPOSED_TO_HOST = 1,
+ MR_LD_QUERY_TYPE_USED_TGT_IDS = 2,
+ MR_LD_QUERY_TYPE_CLUSTER_ACCESS = 3,
+ MR_LD_QUERY_TYPE_CLUSTER_LOCALE = 4,
+} mfi_ld_query_type;
+
+/*
+ * Other propertities and definitions
+ */
+#define MFI_MAX_PD_CHANNELS 2
+#define MFI_MAX_LD_CHANNELS 2
+#define MFI_MAX_CHANNELS (MFI_MAX_PD_CHANNELS + MFI_MAX_LD_CHANNELS)
+#define MFI_MAX_CHANNEL_DEVS 128
+#define MFI_DEFAULT_ID -1
+#define MFI_MAX_LUN 8
+#define MFI_MAX_LD 64
+
+#define MFI_FRAME_SIZE 64
+#define MFI_MBOX_SIZE 12
+
+/* Firmware flashing can take 40s */
+#define MFI_POLL_TIMEOUT_SECS 50
+
+/* Allow for speedier math calculations */
+#define MFI_SECTOR_LEN 512
+
+/* Scatter Gather elements */
+struct mfi_sg32 {
+ uint32_t addr;
+ uint32_t len;
+} QEMU_PACKED;
+
+struct mfi_sg64 {
+ uint64_t addr;
+ uint32_t len;
+} QEMU_PACKED;
+
+struct mfi_sg_skinny {
+ uint64_t addr;
+ uint32_t len;
+ uint32_t flag;
+} QEMU_PACKED;
+
+union mfi_sgl {
+ struct mfi_sg32 sg32[1];
+ struct mfi_sg64 sg64[1];
+ struct mfi_sg_skinny sg_skinny[1];
+} QEMU_PACKED;
+
+/* Message frames. All messages have a common header */
+struct mfi_frame_header {
+ uint8_t frame_cmd;
+ uint8_t sense_len;
+ uint8_t cmd_status;
+ uint8_t scsi_status;
+ uint8_t target_id;
+ uint8_t lun_id;
+ uint8_t cdb_len;
+ uint8_t sge_count;
+ uint64_t context;
+ uint16_t flags;
+ uint16_t timeout;
+ uint32_t data_len;
+} QEMU_PACKED;
+
+struct mfi_init_frame {
+ struct mfi_frame_header header;
+ uint32_t qinfo_new_addr_lo;
+ uint32_t qinfo_new_addr_hi;
+ uint32_t qinfo_old_addr_lo;
+ uint32_t qinfo_old_addr_hi;
+ uint32_t reserved[6];
+};
+
+#define MFI_IO_FRAME_SIZE 40
+struct mfi_io_frame {
+ struct mfi_frame_header header;
+ uint32_t sense_addr_lo;
+ uint32_t sense_addr_hi;
+ uint32_t lba_lo;
+ uint32_t lba_hi;
+ union mfi_sgl sgl;
+} QEMU_PACKED;
+
+#define MFI_PASS_FRAME_SIZE 48
+struct mfi_pass_frame {
+ struct mfi_frame_header header;
+ uint32_t sense_addr_lo;
+ uint32_t sense_addr_hi;
+ uint8_t cdb[16];
+ union mfi_sgl sgl;
+} QEMU_PACKED;
+
+#define MFI_DCMD_FRAME_SIZE 40
+struct mfi_dcmd_frame {
+ struct mfi_frame_header header;
+ uint32_t opcode;
+ uint8_t mbox[MFI_MBOX_SIZE];
+ union mfi_sgl sgl;
+} QEMU_PACKED;
+
+struct mfi_abort_frame {
+ struct mfi_frame_header header;
+ uint64_t abort_context;
+ uint32_t abort_mfi_addr_lo;
+ uint32_t abort_mfi_addr_hi;
+ uint32_t reserved1[6];
+} QEMU_PACKED;
+
+struct mfi_smp_frame {
+ struct mfi_frame_header header;
+ uint64_t sas_addr;
+ union {
+ struct mfi_sg32 sg32[2];
+ struct mfi_sg64 sg64[2];
+ } sgl;
+} QEMU_PACKED;
+
+struct mfi_stp_frame {
+ struct mfi_frame_header header;
+ uint16_t fis[10];
+ uint32_t stp_flags;
+ union {
+ struct mfi_sg32 sg32[2];
+ struct mfi_sg64 sg64[2];
+ } sgl;
+} QEMU_PACKED;
+
+union mfi_frame {
+ struct mfi_frame_header header;
+ struct mfi_init_frame init;
+ struct mfi_io_frame io;
+ struct mfi_pass_frame pass;
+ struct mfi_dcmd_frame dcmd;
+ struct mfi_abort_frame abort;
+ struct mfi_smp_frame smp;
+ struct mfi_stp_frame stp;
+ uint64_t raw[8];
+ uint8_t bytes[MFI_FRAME_SIZE];
+};
+
+#define MFI_SENSE_LEN 128
+struct mfi_sense {
+ uint8_t data[MFI_SENSE_LEN];
+};
+
+#define MFI_QUEUE_FLAG_CONTEXT64 0x00000002
+
+/* The queue init structure that is passed with the init message */
+struct mfi_init_qinfo {
+ uint32_t flags;
+ uint32_t rq_entries;
+ uint32_t rq_addr_lo;
+ uint32_t rq_addr_hi;
+ uint32_t pi_addr_lo;
+ uint32_t pi_addr_hi;
+ uint32_t ci_addr_lo;
+ uint32_t ci_addr_hi;
+} QEMU_PACKED;
+
+/* Controller properties */
+struct mfi_ctrl_props {
+ uint16_t seq_num;
+ uint16_t pred_fail_poll_interval;
+ uint16_t intr_throttle_cnt;
+ uint16_t intr_throttle_timeout;
+ uint8_t rebuild_rate;
+ uint8_t patrol_read_rate;
+ uint8_t bgi_rate;
+ uint8_t cc_rate;
+ uint8_t recon_rate;
+ uint8_t cache_flush_interval;
+ uint8_t spinup_drv_cnt;
+ uint8_t spinup_delay;
+ uint8_t cluster_enable;
+ uint8_t coercion_mode;
+ uint8_t alarm_enable;
+ uint8_t disable_auto_rebuild;
+ uint8_t disable_battery_warn;
+ uint8_t ecc_bucket_size;
+ uint16_t ecc_bucket_leak_rate;
+ uint8_t restore_hotspare_on_insertion;
+ uint8_t expose_encl_devices;
+ uint8_t maintainPdFailHistory;
+ uint8_t disallowHostRequestReordering;
+ uint8_t abortCCOnError;
+ uint8_t loadBalanceMode;
+ uint8_t disableAutoDetectBackplane;
+ uint8_t snapVDSpace;
+ uint32_t OnOffProperties;
+/* set TRUE to disable copyBack (0=copyback enabled) */
+#define MFI_CTRL_PROP_CopyBackDisabled (1 << 0)
+#define MFI_CTRL_PROP_SMARTerEnabled (1 << 1)
+#define MFI_CTRL_PROP_PRCorrectUnconfiguredAreas (1 << 2)
+#define MFI_CTRL_PROP_UseFdeOnly (1 << 3)
+#define MFI_CTRL_PROP_DisableNCQ (1 << 4)
+#define MFI_CTRL_PROP_SSDSMARTerEnabled (1 << 5)
+#define MFI_CTRL_PROP_SSDPatrolReadEnabled (1 << 6)
+#define MFI_CTRL_PROP_EnableSpinDownUnconfigured (1 << 7)
+#define MFI_CTRL_PROP_AutoEnhancedImport (1 << 8)
+#define MFI_CTRL_PROP_EnableSecretKeyControl (1 << 9)
+#define MFI_CTRL_PROP_DisableOnlineCtrlReset (1 << 10)
+#define MFI_CTRL_PROP_AllowBootWithPinnedCache (1 << 11)
+#define MFI_CTRL_PROP_DisableSpinDownHS (1 << 12)
+#define MFI_CTRL_PROP_EnableJBOD (1 << 13)
+
+ uint8_t autoSnapVDSpace; /* % of source LD to be
+ * reserved for auto snapshot
+ * in snapshot repository, for
+ * metadata and user data
+ * 1=5%, 2=10%, 3=15% and so on
+ */
+ uint8_t viewSpace; /* snapshot writeable VIEWs
+ * capacity as a % of source LD
+ * capacity. 0=READ only
+ * 1=5%, 2=10%, 3=15% and so on
+ */
+ uint16_t spinDownTime; /* # of idle minutes before device
+ * is spun down (0=use FW defaults)
+ */
+ uint8_t reserved[24];
+} QEMU_PACKED;
+
+/* PCI information about the card. */
+struct mfi_info_pci {
+ uint16_t vendor;
+ uint16_t device;
+ uint16_t subvendor;
+ uint16_t subdevice;
+ uint8_t reserved[24];
+} QEMU_PACKED;
+
+/* Host (front end) interface information */
+struct mfi_info_host {
+ uint8_t type;
+#define MFI_INFO_HOST_PCIX 0x01
+#define MFI_INFO_HOST_PCIE 0x02
+#define MFI_INFO_HOST_ISCSI 0x04
+#define MFI_INFO_HOST_SAS3G 0x08
+ uint8_t reserved[6];
+ uint8_t port_count;
+ uint64_t port_addr[8];
+} QEMU_PACKED;
+
+/* Device (back end) interface information */
+struct mfi_info_device {
+ uint8_t type;
+#define MFI_INFO_DEV_SPI 0x01
+#define MFI_INFO_DEV_SAS3G 0x02
+#define MFI_INFO_DEV_SATA1 0x04
+#define MFI_INFO_DEV_SATA3G 0x08
+#define MFI_INFO_DEV_PCIE 0x10
+ uint8_t reserved[6];
+ uint8_t port_count;
+ uint64_t port_addr[8];
+} QEMU_PACKED;
+
+/* Firmware component information */
+struct mfi_info_component {
+ char name[8];
+ char version[32];
+ char build_date[16];
+ char build_time[16];
+} QEMU_PACKED;
+
+/* Controller default settings */
+struct mfi_defaults {
+ uint64_t sas_addr;
+ uint8_t phy_polarity;
+ uint8_t background_rate;
+ uint8_t stripe_size;
+ uint8_t flush_time;
+ uint8_t write_back;
+ uint8_t read_ahead;
+ uint8_t cache_when_bbu_bad;
+ uint8_t cached_io;
+ uint8_t smart_mode;
+ uint8_t alarm_disable;
+ uint8_t coercion;
+ uint8_t zrc_config;
+ uint8_t dirty_led_shows_drive_activity;
+ uint8_t bios_continue_on_error;
+ uint8_t spindown_mode;
+ uint8_t allowed_device_types;
+ uint8_t allow_mix_in_enclosure;
+ uint8_t allow_mix_in_ld;
+ uint8_t allow_sata_in_cluster;
+ uint8_t max_chained_enclosures;
+ uint8_t disable_ctrl_r;
+ uint8_t enable_web_bios;
+ uint8_t phy_polarity_split;
+ uint8_t direct_pd_mapping;
+ uint8_t bios_enumerate_lds;
+ uint8_t restored_hot_spare_on_insertion;
+ uint8_t expose_enclosure_devices;
+ uint8_t maintain_pd_fail_history;
+ uint8_t disable_puncture;
+ uint8_t zero_based_enumeration;
+ uint8_t disable_preboot_cli;
+ uint8_t show_drive_led_on_activity;
+ uint8_t cluster_disable;
+ uint8_t sas_disable;
+ uint8_t auto_detect_backplane;
+ uint8_t fde_only;
+ uint8_t delay_during_post;
+ uint8_t resv[19];
+} QEMU_PACKED;
+
+/* Controller default settings */
+struct mfi_bios_data {
+ uint16_t boot_target_id;
+ uint8_t do_not_int_13;
+ uint8_t continue_on_error;
+ uint8_t verbose;
+ uint8_t geometry;
+ uint8_t expose_all_drives;
+ uint8_t reserved[56];
+ uint8_t check_sum;
+} QEMU_PACKED;
+
+/* SAS (?) controller info, returned from MFI_DCMD_CTRL_GETINFO. */
+struct mfi_ctrl_info {
+ struct mfi_info_pci pci;
+ struct mfi_info_host host;
+ struct mfi_info_device device;
+
+ /* Firmware components that are present and active. */
+ uint32_t image_check_word;
+ uint32_t image_component_count;
+ struct mfi_info_component image_component[8];
+
+ /* Firmware components that have been flashed but are inactive */
+ uint32_t pending_image_component_count;
+ struct mfi_info_component pending_image_component[8];
+
+ uint8_t max_arms;
+ uint8_t max_spans;
+ uint8_t max_arrays;
+ uint8_t max_lds;
+ char product_name[80];
+ char serial_number[32];
+ uint32_t hw_present;
+#define MFI_INFO_HW_BBU 0x01
+#define MFI_INFO_HW_ALARM 0x02
+#define MFI_INFO_HW_NVRAM 0x04
+#define MFI_INFO_HW_UART 0x08
+#define MFI_INFO_HW_MEM 0x10
+#define MFI_INFO_HW_FLASH 0x20
+ uint32_t current_fw_time;
+ uint16_t max_cmds;
+ uint16_t max_sg_elements;
+ uint32_t max_request_size;
+ uint16_t lds_present;
+ uint16_t lds_degraded;
+ uint16_t lds_offline;
+ uint16_t pd_present;
+ uint16_t pd_disks_present;
+ uint16_t pd_disks_pred_failure;
+ uint16_t pd_disks_failed;
+ uint16_t nvram_size;
+ uint16_t memory_size;
+ uint16_t flash_size;
+ uint16_t ram_correctable_errors;
+ uint16_t ram_uncorrectable_errors;
+ uint8_t cluster_allowed;
+ uint8_t cluster_active;
+ uint16_t max_strips_per_io;
+
+ uint32_t raid_levels;
+#define MFI_INFO_RAID_0 0x01
+#define MFI_INFO_RAID_1 0x02
+#define MFI_INFO_RAID_5 0x04
+#define MFI_INFO_RAID_1E 0x08
+#define MFI_INFO_RAID_6 0x10
+
+ uint32_t adapter_ops;
+#define MFI_INFO_AOPS_RBLD_RATE 0x0001
+#define MFI_INFO_AOPS_CC_RATE 0x0002
+#define MFI_INFO_AOPS_BGI_RATE 0x0004
+#define MFI_INFO_AOPS_RECON_RATE 0x0008
+#define MFI_INFO_AOPS_PATROL_RATE 0x0010
+#define MFI_INFO_AOPS_ALARM_CONTROL 0x0020
+#define MFI_INFO_AOPS_CLUSTER_SUPPORTED 0x0040
+#define MFI_INFO_AOPS_BBU 0x0080
+#define MFI_INFO_AOPS_SPANNING_ALLOWED 0x0100
+#define MFI_INFO_AOPS_DEDICATED_SPARES 0x0200
+#define MFI_INFO_AOPS_REVERTIBLE_SPARES 0x0400
+#define MFI_INFO_AOPS_FOREIGN_IMPORT 0x0800
+#define MFI_INFO_AOPS_SELF_DIAGNOSTIC 0x1000
+#define MFI_INFO_AOPS_MIXED_ARRAY 0x2000
+#define MFI_INFO_AOPS_GLOBAL_SPARES 0x4000
+
+ uint32_t ld_ops;
+#define MFI_INFO_LDOPS_READ_POLICY 0x01
+#define MFI_INFO_LDOPS_WRITE_POLICY 0x02
+#define MFI_INFO_LDOPS_IO_POLICY 0x04
+#define MFI_INFO_LDOPS_ACCESS_POLICY 0x08
+#define MFI_INFO_LDOPS_DISK_CACHE_POLICY 0x10
+
+ struct {
+ uint8_t min;
+ uint8_t max;
+ uint8_t reserved[2];
+ } QEMU_PACKED stripe_sz_ops;
+
+ uint32_t pd_ops;
+#define MFI_INFO_PDOPS_FORCE_ONLINE 0x01
+#define MFI_INFO_PDOPS_FORCE_OFFLINE 0x02
+#define MFI_INFO_PDOPS_FORCE_REBUILD 0x04
+
+ uint32_t pd_mix_support;
+#define MFI_INFO_PDMIX_SAS 0x01
+#define MFI_INFO_PDMIX_SATA 0x02
+#define MFI_INFO_PDMIX_ENCL 0x04
+#define MFI_INFO_PDMIX_LD 0x08
+#define MFI_INFO_PDMIX_SATA_CLUSTER 0x10
+
+ uint8_t ecc_bucket_count;
+ uint8_t reserved2[11];
+ struct mfi_ctrl_props properties;
+ char package_version[0x60];
+ uint8_t pad[0x800 - 0x6a0];
+} QEMU_PACKED;
+
+/* keep track of an event. */
+union mfi_evt {
+ struct {
+ uint16_t locale;
+ uint8_t reserved;
+ int8_t class;
+ } members;
+ uint32_t word;
+} QEMU_PACKED;
+
+/* event log state. */
+struct mfi_evt_log_state {
+ uint32_t newest_seq_num;
+ uint32_t oldest_seq_num;
+ uint32_t clear_seq_num;
+ uint32_t shutdown_seq_num;
+ uint32_t boot_seq_num;
+} QEMU_PACKED;
+
+struct mfi_progress {
+ uint16_t progress;
+ uint16_t elapsed_seconds;
+} QEMU_PACKED;
+
+struct mfi_evt_ld {
+ uint16_t target_id;
+ uint8_t ld_index;
+ uint8_t reserved;
+} QEMU_PACKED;
+
+struct mfi_evt_pd {
+ uint16_t device_id;
+ uint8_t enclosure_index;
+ uint8_t slot_number;
+} QEMU_PACKED;
+
+/* event detail, returned from MFI_DCMD_CTRL_EVENT_WAIT. */
+struct mfi_evt_detail {
+ uint32_t seq;
+ uint32_t time;
+ uint32_t code;
+ union mfi_evt class;
+ uint8_t arg_type;
+ uint8_t reserved1[15];
+
+ union {
+ struct {
+ struct mfi_evt_pd pd;
+ uint8_t cdb_len;
+ uint8_t sense_len;
+ uint8_t reserved[2];
+ uint8_t cdb[16];
+ uint8_t sense[64];
+ } cdb_sense;
+
+ struct mfi_evt_ld ld;
+
+ struct {
+ struct mfi_evt_ld ld;
+ uint64_t count;
+ } ld_count;
+
+ struct {
+ uint64_t lba;
+ struct mfi_evt_ld ld;
+ } ld_lba;
+
+ struct {
+ struct mfi_evt_ld ld;
+ uint32_t pre_owner;
+ uint32_t new_owner;
+ } ld_owner;
+
+ struct {
+ uint64_t ld_lba;
+ uint64_t pd_lba;
+ struct mfi_evt_ld ld;
+ struct mfi_evt_pd pd;
+ } ld_lba_pd_lba;
+
+ struct {
+ struct mfi_evt_ld ld;
+ struct mfi_progress prog;
+ } ld_prog;
+
+ struct {
+ struct mfi_evt_ld ld;
+ uint32_t prev_state;
+ uint32_t new_state;
+ } ld_state;
+
+ struct {
+ uint64_t strip;
+ struct mfi_evt_ld ld;
+ } ld_strip;
+
+ struct mfi_evt_pd pd;
+
+ struct {
+ struct mfi_evt_pd pd;
+ uint32_t err;
+ } pd_err;
+
+ struct {
+ uint64_t lba;
+ struct mfi_evt_pd pd;
+ } pd_lba;
+
+ struct {
+ uint64_t lba;
+ struct mfi_evt_pd pd;
+ struct mfi_evt_ld ld;
+ } pd_lba_ld;
+
+ struct {
+ struct mfi_evt_pd pd;
+ struct mfi_progress prog;
+ } pd_prog;
+
+ struct {
+ struct mfi_evt_pd ld;
+ uint32_t prev_state;
+ uint32_t new_state;
+ } pd_state;
+
+ struct {
+ uint16_t venderId;
+ uint16_t deviceId;
+ uint16_t subVenderId;
+ uint16_t subDeviceId;
+ } pci;
+
+ uint32_t rate;
+
+ char str[96];
+
+ struct {
+ uint32_t rtc;
+ uint16_t elapsedSeconds;
+ } time;
+
+ struct {
+ uint32_t ecar;
+ uint32_t elog;
+ char str[64];
+ } ecc;
+
+ uint8_t b[96];
+ uint16_t s[48];
+ uint32_t w[24];
+ uint64_t d[12];
+ } args;
+
+ char description[128];
+} QEMU_PACKED;
+
+struct mfi_evt_list {
+ uint32_t count;
+ uint32_t reserved;
+ struct mfi_evt_detail event[1];
+} QEMU_PACKED;
+
+union mfi_pd_ref {
+ struct {
+ uint16_t device_id;
+ uint16_t seq_num;
+ } v;
+ uint32_t ref;
+} QEMU_PACKED;
+
+union mfi_pd_ddf_type {
+ struct {
+ uint16_t pd_type;
+#define MFI_PD_DDF_TYPE_FORCED_PD_GUID (1 << 0)
+#define MFI_PD_DDF_TYPE_IN_VD (1 << 1)
+#define MFI_PD_DDF_TYPE_IS_GLOBAL_SPARE (1 << 2)
+#define MFI_PD_DDF_TYPE_IS_SPARE (1 << 3)
+#define MFI_PD_DDF_TYPE_IS_FOREIGN (1 << 4)
+#define MFI_PD_DDF_TYPE_INTF_SPI (1 << 12)
+#define MFI_PD_DDF_TYPE_INTF_SAS (1 << 13)
+#define MFI_PD_DDF_TYPE_INTF_SATA1 (1 << 14)
+#define MFI_PD_DDF_TYPE_INTF_SATA3G (1 << 15)
+ uint16_t reserved;
+ } ddf;
+ struct {
+ uint32_t reserved;
+ } non_disk;
+ uint32_t type;
+} QEMU_PACKED;
+
+struct mfi_pd_progress {
+ uint32_t active;
+#define PD_PROGRESS_ACTIVE_REBUILD (1 << 0)
+#define PD_PROGRESS_ACTIVE_PATROL (1 << 1)
+#define PD_PROGRESS_ACTIVE_CLEAR (1 << 2)
+ struct mfi_progress rbld;
+ struct mfi_progress patrol;
+ struct mfi_progress clear;
+ struct mfi_progress reserved[4];
+} QEMU_PACKED;
+
+struct mfi_pd_info {
+ union mfi_pd_ref ref;
+ uint8_t inquiry_data[96];
+ uint8_t vpd_page83[64];
+ uint8_t not_supported;
+ uint8_t scsi_dev_type;
+ uint8_t connected_port_bitmap;
+ uint8_t device_speed;
+ uint32_t media_err_count;
+ uint32_t other_err_count;
+ uint32_t pred_fail_count;
+ uint32_t last_pred_fail_event_seq_num;
+ uint16_t fw_state;
+ uint8_t disable_for_removal;
+ uint8_t link_speed;
+ union mfi_pd_ddf_type state;
+ struct {
+ uint8_t count;
+ uint8_t is_path_broken;
+ uint8_t reserved[6];
+ uint64_t sas_addr[4];
+ } path_info;
+ uint64_t raw_size;
+ uint64_t non_coerced_size;
+ uint64_t coerced_size;
+ uint16_t encl_device_id;
+ uint8_t encl_index;
+ uint8_t slot_number;
+ struct mfi_pd_progress prog_info;
+ uint8_t bad_block_table_full;
+ uint8_t unusable_in_current_config;
+ uint8_t vpd_page83_ext[64];
+ uint8_t reserved[512-358];
+} QEMU_PACKED;
+
+struct mfi_pd_address {
+ uint16_t device_id;
+ uint16_t encl_device_id;
+ uint8_t encl_index;
+ uint8_t slot_number;
+ uint8_t scsi_dev_type;
+ uint8_t connect_port_bitmap;
+ uint64_t sas_addr[2];
+} QEMU_PACKED;
+
+#define MFI_MAX_SYS_PDS 240
+struct mfi_pd_list {
+ uint32_t size;
+ uint32_t count;
+ struct mfi_pd_address addr[MFI_MAX_SYS_PDS];
+} QEMU_PACKED;
+
+union mfi_ld_ref {
+ struct {
+ uint8_t target_id;
+ uint8_t reserved;
+ uint16_t seq;
+ } v;
+ uint32_t ref;
+} QEMU_PACKED;
+
+struct mfi_ld_list {
+ uint32_t ld_count;
+ uint32_t reserved1;
+ struct {
+ union mfi_ld_ref ld;
+ uint8_t state;
+ uint8_t reserved2[3];
+ uint64_t size;
+ } ld_list[MFI_MAX_LD];
+} QEMU_PACKED;
+
+struct mfi_ld_targetid_list {
+ uint32_t size;
+ uint32_t ld_count;
+ uint8_t pad[3];
+ uint8_t targetid[MFI_MAX_LD];
+} QEMU_PACKED;
+
+enum mfi_ld_access {
+ MFI_LD_ACCESS_RW = 0,
+ MFI_LD_ACCSSS_RO = 2,
+ MFI_LD_ACCESS_BLOCKED = 3,
+};
+#define MFI_LD_ACCESS_MASK 3
+
+enum mfi_ld_state {
+ MFI_LD_STATE_OFFLINE = 0,
+ MFI_LD_STATE_PARTIALLY_DEGRADED = 1,
+ MFI_LD_STATE_DEGRADED = 2,
+ MFI_LD_STATE_OPTIMAL = 3
+};
+
+enum mfi_syspd_state {
+ MFI_PD_STATE_UNCONFIGURED_GOOD = 0x00,
+ MFI_PD_STATE_UNCONFIGURED_BAD = 0x01,
+ MFI_PD_STATE_HOT_SPARE = 0x02,
+ MFI_PD_STATE_OFFLINE = 0x10,
+ MFI_PD_STATE_FAILED = 0x11,
+ MFI_PD_STATE_REBUILD = 0x14,
+ MFI_PD_STATE_ONLINE = 0x18,
+ MFI_PD_STATE_COPYBACK = 0x20,
+ MFI_PD_STATE_SYSTEM = 0x40
+};
+
+struct mfi_ld_props {
+ union mfi_ld_ref ld;
+ char name[16];
+ uint8_t default_cache_policy;
+ uint8_t access_policy;
+ uint8_t disk_cache_policy;
+ uint8_t current_cache_policy;
+ uint8_t no_bgi;
+ uint8_t reserved[7];
+} QEMU_PACKED;
+
+struct mfi_ld_params {
+ uint8_t primary_raid_level;
+ uint8_t raid_level_qualifier;
+ uint8_t secondary_raid_level;
+ uint8_t stripe_size;
+ uint8_t num_drives;
+ uint8_t span_depth;
+ uint8_t state;
+ uint8_t init_state;
+ uint8_t is_consistent;
+ uint8_t reserved[23];
+} QEMU_PACKED;
+
+struct mfi_ld_progress {
+ uint32_t active;
+#define MFI_LD_PROGRESS_CC (1<<0)
+#define MFI_LD_PROGRESS_BGI (1<<1)
+#define MFI_LD_PROGRESS_FGI (1<<2)
+#define MFI_LD_PORGRESS_RECON (1<<3)
+ struct mfi_progress cc;
+ struct mfi_progress bgi;
+ struct mfi_progress fgi;
+ struct mfi_progress recon;
+ struct mfi_progress reserved[4];
+} QEMU_PACKED;
+
+struct mfi_span {
+ uint64_t start_block;
+ uint64_t num_blocks;
+ uint16_t array_ref;
+ uint8_t reserved[6];
+} QEMU_PACKED;
+
+#define MFI_MAX_SPAN_DEPTH 8
+struct mfi_ld_config {
+ struct mfi_ld_props properties;
+ struct mfi_ld_params params;
+ struct mfi_span span[MFI_MAX_SPAN_DEPTH];
+} QEMU_PACKED;
+
+struct mfi_ld_info {
+ struct mfi_ld_config ld_config;
+ uint64_t size;
+ struct mfi_ld_progress progress;
+ uint16_t cluster_owner;
+ uint8_t reconstruct_active;
+ uint8_t reserved1[1];
+ uint8_t vpd_page83[64];
+ uint8_t reserved2[16];
+} QEMU_PACKED;
+
+union mfi_spare_type {
+ uint8_t flags;
+#define MFI_SPARE_IS_DEDICATED (1 << 0)
+#define MFI_SPARE_IS_REVERTABLE (1 << 1)
+#define MFI_SPARE_IS_ENCL_AFFINITY (1 << 2)
+ uint8_t type;
+} QEMU_PACKED;
+
+#define MFI_MAX_ARRAYS 16
+struct mfi_spare {
+ union mfi_pd_ref ref;
+ union mfi_spare_type spare_type;
+ uint8_t reserved[2];
+ uint8_t array_count;
+ uint16_t array_refd[MFI_MAX_ARRAYS];
+} QEMU_PACKED;
+
+#define MFI_MAX_ROW_SIZE 32
+struct mfi_array {
+ uint64_t size;
+ uint8_t num_drives;
+ uint8_t reserved;
+ uint16_t array_ref;
+ uint8_t pad[20];
+ struct {
+ union mfi_pd_ref ref;
+ uint16_t fw_state; /* enum mfi_syspd_state */
+ struct {
+ uint8_t pd;
+ uint8_t slot;
+ } encl;
+ } pd[MFI_MAX_ROW_SIZE];
+} QEMU_PACKED;
+
+struct mfi_config_data {
+ uint32_t size;
+ uint16_t array_count;
+ uint16_t array_size;
+ uint16_t log_drv_count;
+ uint16_t log_drv_size;
+ uint16_t spares_count;
+ uint16_t spares_size;
+ uint8_t reserved[16];
+ /*
+ struct mfi_array array[];
+ struct mfi_ld_config ld[];
+ struct mfi_spare spare[];
+ */
+} QEMU_PACKED;
+
+#define MFI_SCSI_MAX_TARGETS 128
+#define MFI_SCSI_MAX_LUNS 8
+#define MFI_SCSI_INITIATOR_ID 255
+#define MFI_SCSI_MAX_CMDS 8
+#define MFI_SCSI_MAX_CDB_LEN 16
+
+#endif /* MFI_REG_H */
diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c
new file mode 100644
index 00000000..f0ae4625
--- /dev/null
+++ b/hw/scsi/scsi-bus.c
@@ -0,0 +1,2049 @@
+#include "hw/hw.h"
+#include "qemu/error-report.h"
+#include "hw/scsi/scsi.h"
+#include "block/scsi.h"
+#include "hw/qdev.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "trace.h"
+#include "sysemu/dma.h"
+
+static char *scsibus_get_dev_path(DeviceState *dev);
+static char *scsibus_get_fw_dev_path(DeviceState *dev);
+static void scsi_req_dequeue(SCSIRequest *req);
+static uint8_t *scsi_target_alloc_buf(SCSIRequest *req, size_t len);
+static void scsi_target_free_buf(SCSIRequest *req);
+
+static Property scsi_props[] = {
+ DEFINE_PROP_UINT32("channel", SCSIDevice, channel, 0),
+ DEFINE_PROP_UINT32("scsi-id", SCSIDevice, id, -1),
+ DEFINE_PROP_UINT32("lun", SCSIDevice, lun, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void scsi_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ k->get_dev_path = scsibus_get_dev_path;
+ k->get_fw_dev_path = scsibus_get_fw_dev_path;
+ hc->unplug = qdev_simple_device_unplug_cb;
+}
+
+static const TypeInfo scsi_bus_info = {
+ .name = TYPE_SCSI_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(SCSIBus),
+ .class_init = scsi_bus_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+static int next_scsi_bus;
+
+static void scsi_device_realize(SCSIDevice *s, Error **errp)
+{
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
+ if (sc->realize) {
+ sc->realize(s, errp);
+ }
+}
+
+int scsi_bus_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf,
+ void *hba_private)
+{
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus);
+ int rc;
+
+ assert(cmd->len == 0);
+ rc = scsi_req_parse_cdb(dev, cmd, buf);
+ if (bus->info->parse_cdb) {
+ rc = bus->info->parse_cdb(dev, cmd, buf, hba_private);
+ }
+ return rc;
+}
+
+static SCSIRequest *scsi_device_alloc_req(SCSIDevice *s, uint32_t tag, uint32_t lun,
+ uint8_t *buf, void *hba_private)
+{
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
+ if (sc->alloc_req) {
+ return sc->alloc_req(s, tag, lun, buf, hba_private);
+ }
+
+ return NULL;
+}
+
+void scsi_device_unit_attention_reported(SCSIDevice *s)
+{
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
+ if (sc->unit_attention_reported) {
+ sc->unit_attention_reported(s);
+ }
+}
+
+/* Create a scsi bus, and attach devices to it. */
+void scsi_bus_new(SCSIBus *bus, size_t bus_size, DeviceState *host,
+ const SCSIBusInfo *info, const char *bus_name)
+{
+ qbus_create_inplace(bus, bus_size, TYPE_SCSI_BUS, host, bus_name);
+ bus->busnr = next_scsi_bus++;
+ bus->info = info;
+ qbus_set_bus_hotplug_handler(BUS(bus), &error_abort);
+}
+
+static void scsi_dma_restart_bh(void *opaque)
+{
+ SCSIDevice *s = opaque;
+ SCSIRequest *req, *next;
+
+ qemu_bh_delete(s->bh);
+ s->bh = NULL;
+
+ QTAILQ_FOREACH_SAFE(req, &s->requests, next, next) {
+ scsi_req_ref(req);
+ if (req->retry) {
+ req->retry = false;
+ switch (req->cmd.mode) {
+ case SCSI_XFER_FROM_DEV:
+ case SCSI_XFER_TO_DEV:
+ scsi_req_continue(req);
+ break;
+ case SCSI_XFER_NONE:
+ scsi_req_dequeue(req);
+ scsi_req_enqueue(req);
+ break;
+ }
+ }
+ scsi_req_unref(req);
+ }
+}
+
+void scsi_req_retry(SCSIRequest *req)
+{
+ /* No need to save a reference, because scsi_dma_restart_bh just
+ * looks at the request list. */
+ req->retry = true;
+}
+
+static void scsi_dma_restart_cb(void *opaque, int running, RunState state)
+{
+ SCSIDevice *s = opaque;
+
+ if (!running) {
+ return;
+ }
+ if (!s->bh) {
+ s->bh = qemu_bh_new(scsi_dma_restart_bh, s);
+ qemu_bh_schedule(s->bh);
+ }
+}
+
+static void scsi_qdev_realize(DeviceState *qdev, Error **errp)
+{
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus);
+ SCSIDevice *d;
+ Error *local_err = NULL;
+
+ if (dev->channel > bus->info->max_channel) {
+ error_setg(errp, "bad scsi channel id: %d", dev->channel);
+ return;
+ }
+ if (dev->id != -1 && dev->id > bus->info->max_target) {
+ error_setg(errp, "bad scsi device id: %d", dev->id);
+ return;
+ }
+ if (dev->lun != -1 && dev->lun > bus->info->max_lun) {
+ error_setg(errp, "bad scsi device lun: %d", dev->lun);
+ return;
+ }
+
+ if (dev->id == -1) {
+ int id = -1;
+ if (dev->lun == -1) {
+ dev->lun = 0;
+ }
+ do {
+ d = scsi_device_find(bus, dev->channel, ++id, dev->lun);
+ } while (d && d->lun == dev->lun && id < bus->info->max_target);
+ if (d && d->lun == dev->lun) {
+ error_setg(errp, "no free target");
+ return;
+ }
+ dev->id = id;
+ } else if (dev->lun == -1) {
+ int lun = -1;
+ do {
+ d = scsi_device_find(bus, dev->channel, dev->id, ++lun);
+ } while (d && d->lun == lun && lun < bus->info->max_lun);
+ if (d && d->lun == lun) {
+ error_setg(errp, "no free lun");
+ return;
+ }
+ dev->lun = lun;
+ } else {
+ d = scsi_device_find(bus, dev->channel, dev->id, dev->lun);
+ assert(d);
+ if (d->lun == dev->lun && dev != d) {
+ error_setg(errp, "lun already used by '%s'", d->qdev.id);
+ return;
+ }
+ }
+
+ QTAILQ_INIT(&dev->requests);
+ scsi_device_realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ dev->vmsentry = qemu_add_vm_change_state_handler(scsi_dma_restart_cb,
+ dev);
+}
+
+static void scsi_qdev_unrealize(DeviceState *qdev, Error **errp)
+{
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->vmsentry) {
+ qemu_del_vm_change_state_handler(dev->vmsentry);
+ }
+
+ scsi_device_purge_requests(dev, SENSE_CODE(NO_SENSE));
+ blockdev_mark_auto_del(dev->conf.blk);
+}
+
+/* handle legacy '-drive if=scsi,...' cmd line args */
+SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockBackend *blk,
+ int unit, bool removable, int bootindex,
+ const char *serial, Error **errp)
+{
+ const char *driver;
+ char *name;
+ DeviceState *dev;
+ Error *err = NULL;
+
+ driver = blk_is_sg(blk) ? "scsi-generic" : "scsi-disk";
+ dev = qdev_create(&bus->qbus, driver);
+ name = g_strdup_printf("legacy[%d]", unit);
+ object_property_add_child(OBJECT(bus), name, OBJECT(dev), NULL);
+ g_free(name);
+
+ qdev_prop_set_uint32(dev, "scsi-id", unit);
+ if (bootindex >= 0) {
+ object_property_set_int(OBJECT(dev), bootindex, "bootindex",
+ &error_abort);
+ }
+ if (object_property_find(OBJECT(dev), "removable", NULL)) {
+ qdev_prop_set_bit(dev, "removable", removable);
+ }
+ if (serial && object_property_find(OBJECT(dev), "serial", NULL)) {
+ qdev_prop_set_string(dev, "serial", serial);
+ }
+ qdev_prop_set_drive(dev, "drive", blk, &err);
+ if (err) {
+ error_propagate(errp, err);
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+ object_property_set_bool(OBJECT(dev), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+ return SCSI_DEVICE(dev);
+}
+
+void scsi_bus_legacy_handle_cmdline(SCSIBus *bus, Error **errp)
+{
+ Location loc;
+ DriveInfo *dinfo;
+ int unit;
+ Error *err = NULL;
+
+ loc_push_none(&loc);
+ for (unit = 0; unit <= bus->info->max_target; unit++) {
+ dinfo = drive_get(IF_SCSI, bus->busnr, unit);
+ if (dinfo == NULL) {
+ continue;
+ }
+ qemu_opts_loc_restore(dinfo->opts);
+ scsi_bus_legacy_add_drive(bus, blk_by_legacy_dinfo(dinfo),
+ unit, false, -1, NULL, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ break;
+ }
+ }
+ loc_pop(&loc);
+}
+
+static int32_t scsi_invalid_field(SCSIRequest *req, uint8_t *buf)
+{
+ scsi_req_build_sense(req, SENSE_CODE(INVALID_FIELD));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+}
+
+static const struct SCSIReqOps reqops_invalid_field = {
+ .size = sizeof(SCSIRequest),
+ .send_command = scsi_invalid_field
+};
+
+/* SCSIReqOps implementation for invalid commands. */
+
+static int32_t scsi_invalid_command(SCSIRequest *req, uint8_t *buf)
+{
+ scsi_req_build_sense(req, SENSE_CODE(INVALID_OPCODE));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+}
+
+static const struct SCSIReqOps reqops_invalid_opcode = {
+ .size = sizeof(SCSIRequest),
+ .send_command = scsi_invalid_command
+};
+
+/* SCSIReqOps implementation for unit attention conditions. */
+
+static int32_t scsi_unit_attention(SCSIRequest *req, uint8_t *buf)
+{
+ if (req->dev->unit_attention.key == UNIT_ATTENTION) {
+ scsi_req_build_sense(req, req->dev->unit_attention);
+ } else if (req->bus->unit_attention.key == UNIT_ATTENTION) {
+ scsi_req_build_sense(req, req->bus->unit_attention);
+ }
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+}
+
+static const struct SCSIReqOps reqops_unit_attention = {
+ .size = sizeof(SCSIRequest),
+ .send_command = scsi_unit_attention
+};
+
+/* SCSIReqOps implementation for REPORT LUNS and for commands sent to
+ an invalid LUN. */
+
+typedef struct SCSITargetReq SCSITargetReq;
+
+struct SCSITargetReq {
+ SCSIRequest req;
+ int len;
+ uint8_t *buf;
+ int buf_len;
+};
+
+static void store_lun(uint8_t *outbuf, int lun)
+{
+ if (lun < 256) {
+ outbuf[1] = lun;
+ return;
+ }
+ outbuf[1] = (lun & 255);
+ outbuf[0] = (lun >> 8) | 0x40;
+}
+
+static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
+{
+ BusChild *kid;
+ int i, len, n;
+ int channel, id;
+ bool found_lun0;
+
+ if (r->req.cmd.xfer < 16) {
+ return false;
+ }
+ if (r->req.cmd.buf[2] > 2) {
+ return false;
+ }
+ channel = r->req.dev->channel;
+ id = r->req.dev->id;
+ found_lun0 = false;
+ n = 0;
+ QTAILQ_FOREACH(kid, &r->req.bus->qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->channel == channel && dev->id == id) {
+ if (dev->lun == 0) {
+ found_lun0 = true;
+ }
+ n += 8;
+ }
+ }
+ if (!found_lun0) {
+ n += 8;
+ }
+
+ scsi_target_alloc_buf(&r->req, n + 8);
+
+ len = MIN(n + 8, r->req.cmd.xfer & ~7);
+ memset(r->buf, 0, len);
+ stl_be_p(&r->buf[0], n);
+ i = found_lun0 ? 8 : 16;
+ QTAILQ_FOREACH(kid, &r->req.bus->qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->channel == channel && dev->id == id) {
+ store_lun(&r->buf[i], dev->lun);
+ i += 8;
+ }
+ }
+ assert(i == n + 8);
+ r->len = len;
+ return true;
+}
+
+static bool scsi_target_emulate_inquiry(SCSITargetReq *r)
+{
+ assert(r->req.dev->lun != r->req.lun);
+
+ scsi_target_alloc_buf(&r->req, SCSI_INQUIRY_LEN);
+
+ if (r->req.cmd.buf[1] & 0x2) {
+ /* Command support data - optional, not implemented */
+ return false;
+ }
+
+ if (r->req.cmd.buf[1] & 0x1) {
+ /* Vital product data */
+ uint8_t page_code = r->req.cmd.buf[2];
+ r->buf[r->len++] = page_code ; /* this page */
+ r->buf[r->len++] = 0x00;
+
+ switch (page_code) {
+ case 0x00: /* Supported page codes, mandatory */
+ {
+ int pages;
+ pages = r->len++;
+ r->buf[r->len++] = 0x00; /* list of supported pages (this page) */
+ r->buf[pages] = r->len - pages - 1; /* number of pages */
+ break;
+ }
+ default:
+ return false;
+ }
+ /* done with EVPD */
+ assert(r->len < r->buf_len);
+ r->len = MIN(r->req.cmd.xfer, r->len);
+ return true;
+ }
+
+ /* Standard INQUIRY data */
+ if (r->req.cmd.buf[2] != 0) {
+ return false;
+ }
+
+ /* PAGE CODE == 0 */
+ r->len = MIN(r->req.cmd.xfer, SCSI_INQUIRY_LEN);
+ memset(r->buf, 0, r->len);
+ if (r->req.lun != 0) {
+ r->buf[0] = TYPE_NO_LUN;
+ } else {
+ r->buf[0] = TYPE_NOT_PRESENT | TYPE_INACTIVE;
+ r->buf[2] = 5; /* Version */
+ r->buf[3] = 2 | 0x10; /* HiSup, response data format */
+ r->buf[4] = r->len - 5; /* Additional Length = (Len - 1) - 4 */
+ r->buf[7] = 0x10 | (r->req.bus->info->tcq ? 0x02 : 0); /* Sync, TCQ. */
+ memcpy(&r->buf[8], "QEMU ", 8);
+ memcpy(&r->buf[16], "QEMU TARGET ", 16);
+ pstrcpy((char *) &r->buf[32], 4, qemu_get_version());
+ }
+ return true;
+}
+
+static int32_t scsi_target_send_command(SCSIRequest *req, uint8_t *buf)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+ switch (buf[0]) {
+ case REPORT_LUNS:
+ if (!scsi_target_emulate_report_luns(r)) {
+ goto illegal_request;
+ }
+ break;
+ case INQUIRY:
+ if (!scsi_target_emulate_inquiry(r)) {
+ goto illegal_request;
+ }
+ break;
+ case REQUEST_SENSE:
+ scsi_target_alloc_buf(&r->req, SCSI_SENSE_LEN);
+ r->len = scsi_device_get_sense(r->req.dev, r->buf,
+ MIN(req->cmd.xfer, r->buf_len),
+ (req->cmd.buf[1] & 1) == 0);
+ if (r->req.dev->sense_is_ua) {
+ scsi_device_unit_attention_reported(req->dev);
+ r->req.dev->sense_len = 0;
+ r->req.dev->sense_is_ua = false;
+ }
+ break;
+ case TEST_UNIT_READY:
+ break;
+ default:
+ scsi_req_build_sense(req, SENSE_CODE(LUN_NOT_SUPPORTED));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+ illegal_request:
+ scsi_req_build_sense(req, SENSE_CODE(INVALID_FIELD));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+ }
+
+ if (!r->len) {
+ scsi_req_complete(req, GOOD);
+ }
+ return r->len;
+}
+
+static void scsi_target_read_data(SCSIRequest *req)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+ uint32_t n;
+
+ n = r->len;
+ if (n > 0) {
+ r->len = 0;
+ scsi_req_data(&r->req, n);
+ } else {
+ scsi_req_complete(&r->req, GOOD);
+ }
+}
+
+static uint8_t *scsi_target_get_buf(SCSIRequest *req)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+ return r->buf;
+}
+
+static uint8_t *scsi_target_alloc_buf(SCSIRequest *req, size_t len)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+ r->buf = g_malloc(len);
+ r->buf_len = len;
+
+ return r->buf;
+}
+
+static void scsi_target_free_buf(SCSIRequest *req)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+ g_free(r->buf);
+}
+
+static const struct SCSIReqOps reqops_target_command = {
+ .size = sizeof(SCSITargetReq),
+ .send_command = scsi_target_send_command,
+ .read_data = scsi_target_read_data,
+ .get_buf = scsi_target_get_buf,
+ .free_req = scsi_target_free_buf,
+};
+
+
+SCSIRequest *scsi_req_alloc(const SCSIReqOps *reqops, SCSIDevice *d,
+ uint32_t tag, uint32_t lun, void *hba_private)
+{
+ SCSIRequest *req;
+ SCSIBus *bus = scsi_bus_from_device(d);
+ BusState *qbus = BUS(bus);
+ const int memset_off = offsetof(SCSIRequest, sense)
+ + sizeof(req->sense);
+
+ req = g_slice_alloc(reqops->size);
+ memset((uint8_t *)req + memset_off, 0, reqops->size - memset_off);
+ req->refcount = 1;
+ req->bus = bus;
+ req->dev = d;
+ req->tag = tag;
+ req->lun = lun;
+ req->hba_private = hba_private;
+ req->status = -1;
+ req->ops = reqops;
+ object_ref(OBJECT(d));
+ object_ref(OBJECT(qbus->parent));
+ notifier_list_init(&req->cancel_notifiers);
+ trace_scsi_req_alloc(req->dev->id, req->lun, req->tag);
+ return req;
+}
+
+SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun,
+ uint8_t *buf, void *hba_private)
+{
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, d->qdev.parent_bus);
+ const SCSIReqOps *ops;
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(d);
+ SCSIRequest *req;
+ SCSICommand cmd = { .len = 0 };
+ int ret;
+
+ if ((d->unit_attention.key == UNIT_ATTENTION ||
+ bus->unit_attention.key == UNIT_ATTENTION) &&
+ (buf[0] != INQUIRY &&
+ buf[0] != REPORT_LUNS &&
+ buf[0] != GET_CONFIGURATION &&
+ buf[0] != GET_EVENT_STATUS_NOTIFICATION &&
+
+ /*
+ * If we already have a pending unit attention condition,
+ * report this one before triggering another one.
+ */
+ !(buf[0] == REQUEST_SENSE && d->sense_is_ua))) {
+ ops = &reqops_unit_attention;
+ } else if (lun != d->lun ||
+ buf[0] == REPORT_LUNS ||
+ (buf[0] == REQUEST_SENSE && d->sense_len)) {
+ ops = &reqops_target_command;
+ } else {
+ ops = NULL;
+ }
+
+ if (ops != NULL || !sc->parse_cdb) {
+ ret = scsi_req_parse_cdb(d, &cmd, buf);
+ } else {
+ ret = sc->parse_cdb(d, &cmd, buf, hba_private);
+ }
+
+ if (ret != 0) {
+ trace_scsi_req_parse_bad(d->id, lun, tag, buf[0]);
+ req = scsi_req_alloc(&reqops_invalid_opcode, d, tag, lun, hba_private);
+ } else {
+ assert(cmd.len != 0);
+ trace_scsi_req_parsed(d->id, lun, tag, buf[0],
+ cmd.mode, cmd.xfer);
+ if (cmd.lba != -1) {
+ trace_scsi_req_parsed_lba(d->id, lun, tag, buf[0],
+ cmd.lba);
+ }
+
+ if (cmd.xfer > INT32_MAX) {
+ req = scsi_req_alloc(&reqops_invalid_field, d, tag, lun, hba_private);
+ } else if (ops) {
+ req = scsi_req_alloc(ops, d, tag, lun, hba_private);
+ } else {
+ req = scsi_device_alloc_req(d, tag, lun, buf, hba_private);
+ }
+ }
+
+ req->cmd = cmd;
+ req->resid = req->cmd.xfer;
+
+ switch (buf[0]) {
+ case INQUIRY:
+ trace_scsi_inquiry(d->id, lun, tag, cmd.buf[1], cmd.buf[2]);
+ break;
+ case TEST_UNIT_READY:
+ trace_scsi_test_unit_ready(d->id, lun, tag);
+ break;
+ case REPORT_LUNS:
+ trace_scsi_report_luns(d->id, lun, tag);
+ break;
+ case REQUEST_SENSE:
+ trace_scsi_request_sense(d->id, lun, tag);
+ break;
+ default:
+ break;
+ }
+
+ return req;
+}
+
+uint8_t *scsi_req_get_buf(SCSIRequest *req)
+{
+ return req->ops->get_buf(req);
+}
+
+static void scsi_clear_unit_attention(SCSIRequest *req)
+{
+ SCSISense *ua;
+ if (req->dev->unit_attention.key != UNIT_ATTENTION &&
+ req->bus->unit_attention.key != UNIT_ATTENTION) {
+ return;
+ }
+
+ /*
+ * If an INQUIRY command enters the enabled command state,
+ * the device server shall [not] clear any unit attention condition;
+ * See also MMC-6, paragraphs 6.5 and 6.6.2.
+ */
+ if (req->cmd.buf[0] == INQUIRY ||
+ req->cmd.buf[0] == GET_CONFIGURATION ||
+ req->cmd.buf[0] == GET_EVENT_STATUS_NOTIFICATION) {
+ return;
+ }
+
+ if (req->dev->unit_attention.key == UNIT_ATTENTION) {
+ ua = &req->dev->unit_attention;
+ } else {
+ ua = &req->bus->unit_attention;
+ }
+
+ /*
+ * If a REPORT LUNS command enters the enabled command state, [...]
+ * the device server shall clear any pending unit attention condition
+ * with an additional sense code of REPORTED LUNS DATA HAS CHANGED.
+ */
+ if (req->cmd.buf[0] == REPORT_LUNS &&
+ !(ua->asc == SENSE_CODE(REPORTED_LUNS_CHANGED).asc &&
+ ua->ascq == SENSE_CODE(REPORTED_LUNS_CHANGED).ascq)) {
+ return;
+ }
+
+ *ua = SENSE_CODE(NO_SENSE);
+}
+
+int scsi_req_get_sense(SCSIRequest *req, uint8_t *buf, int len)
+{
+ int ret;
+
+ assert(len >= 14);
+ if (!req->sense_len) {
+ return 0;
+ }
+
+ ret = scsi_build_sense(req->sense, req->sense_len, buf, len, true);
+
+ /*
+ * FIXME: clearing unit attention conditions upon autosense should be done
+ * only if the UA_INTLCK_CTRL field in the Control mode page is set to 00b
+ * (SAM-5, 5.14).
+ *
+ * We assume UA_INTLCK_CTRL to be 00b for HBAs that support autosense, and
+ * 10b for HBAs that do not support it (do not call scsi_req_get_sense).
+ * Here we handle unit attention clearing for UA_INTLCK_CTRL == 00b.
+ */
+ if (req->dev->sense_is_ua) {
+ scsi_device_unit_attention_reported(req->dev);
+ req->dev->sense_len = 0;
+ req->dev->sense_is_ua = false;
+ }
+ return ret;
+}
+
+int scsi_device_get_sense(SCSIDevice *dev, uint8_t *buf, int len, bool fixed)
+{
+ return scsi_build_sense(dev->sense, dev->sense_len, buf, len, fixed);
+}
+
+void scsi_req_build_sense(SCSIRequest *req, SCSISense sense)
+{
+ trace_scsi_req_build_sense(req->dev->id, req->lun, req->tag,
+ sense.key, sense.asc, sense.ascq);
+ memset(req->sense, 0, 18);
+ req->sense[0] = 0x70;
+ req->sense[2] = sense.key;
+ req->sense[7] = 10;
+ req->sense[12] = sense.asc;
+ req->sense[13] = sense.ascq;
+ req->sense_len = 18;
+}
+
+static void scsi_req_enqueue_internal(SCSIRequest *req)
+{
+ assert(!req->enqueued);
+ scsi_req_ref(req);
+ if (req->bus->info->get_sg_list) {
+ req->sg = req->bus->info->get_sg_list(req);
+ } else {
+ req->sg = NULL;
+ }
+ req->enqueued = true;
+ QTAILQ_INSERT_TAIL(&req->dev->requests, req, next);
+}
+
+int32_t scsi_req_enqueue(SCSIRequest *req)
+{
+ int32_t rc;
+
+ assert(!req->retry);
+ scsi_req_enqueue_internal(req);
+ scsi_req_ref(req);
+ rc = req->ops->send_command(req, req->cmd.buf);
+ scsi_req_unref(req);
+ return rc;
+}
+
+static void scsi_req_dequeue(SCSIRequest *req)
+{
+ trace_scsi_req_dequeue(req->dev->id, req->lun, req->tag);
+ req->retry = false;
+ if (req->enqueued) {
+ QTAILQ_REMOVE(&req->dev->requests, req, next);
+ req->enqueued = false;
+ scsi_req_unref(req);
+ }
+}
+
+static int scsi_get_performance_length(int num_desc, int type, int data_type)
+{
+ /* MMC-6, paragraph 6.7. */
+ switch (type) {
+ case 0:
+ if ((data_type & 3) == 0) {
+ /* Each descriptor is as in Table 295 - Nominal performance. */
+ return 16 * num_desc + 8;
+ } else {
+ /* Each descriptor is as in Table 296 - Exceptions. */
+ return 6 * num_desc + 8;
+ }
+ case 1:
+ case 4:
+ case 5:
+ return 8 * num_desc + 8;
+ case 2:
+ return 2048 * num_desc + 8;
+ case 3:
+ return 16 * num_desc + 8;
+ default:
+ return 8;
+ }
+}
+
+static int ata_passthrough_xfer_unit(SCSIDevice *dev, uint8_t *buf)
+{
+ int byte_block = (buf[2] >> 2) & 0x1;
+ int type = (buf[2] >> 4) & 0x1;
+ int xfer_unit;
+
+ if (byte_block) {
+ if (type) {
+ xfer_unit = dev->blocksize;
+ } else {
+ xfer_unit = 512;
+ }
+ } else {
+ xfer_unit = 1;
+ }
+
+ return xfer_unit;
+}
+
+static int ata_passthrough_12_xfer(SCSIDevice *dev, uint8_t *buf)
+{
+ int length = buf[2] & 0x3;
+ int xfer;
+ int unit = ata_passthrough_xfer_unit(dev, buf);
+
+ switch (length) {
+ case 0:
+ case 3: /* USB-specific. */
+ default:
+ xfer = 0;
+ break;
+ case 1:
+ xfer = buf[3];
+ break;
+ case 2:
+ xfer = buf[4];
+ break;
+ }
+
+ return xfer * unit;
+}
+
+static int ata_passthrough_16_xfer(SCSIDevice *dev, uint8_t *buf)
+{
+ int extend = buf[1] & 0x1;
+ int length = buf[2] & 0x3;
+ int xfer;
+ int unit = ata_passthrough_xfer_unit(dev, buf);
+
+ switch (length) {
+ case 0:
+ case 3: /* USB-specific. */
+ default:
+ xfer = 0;
+ break;
+ case 1:
+ xfer = buf[4];
+ xfer |= (extend ? buf[3] << 8 : 0);
+ break;
+ case 2:
+ xfer = buf[6];
+ xfer |= (extend ? buf[5] << 8 : 0);
+ break;
+ }
+
+ return xfer * unit;
+}
+
+uint32_t scsi_data_cdb_xfer(uint8_t *buf)
+{
+ if ((buf[0] >> 5) == 0 && buf[4] == 0) {
+ return 256;
+ } else {
+ return scsi_cdb_xfer(buf);
+ }
+}
+
+uint32_t scsi_cdb_xfer(uint8_t *buf)
+{
+ switch (buf[0] >> 5) {
+ case 0:
+ return buf[4];
+ break;
+ case 1:
+ case 2:
+ return lduw_be_p(&buf[7]);
+ break;
+ case 4:
+ return ldl_be_p(&buf[10]) & 0xffffffffULL;
+ break;
+ case 5:
+ return ldl_be_p(&buf[6]) & 0xffffffffULL;
+ break;
+ default:
+ return -1;
+ }
+}
+
+static int scsi_req_xfer(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf)
+{
+ cmd->xfer = scsi_cdb_xfer(buf);
+ switch (buf[0]) {
+ case TEST_UNIT_READY:
+ case REWIND:
+ case START_STOP:
+ case SET_CAPACITY:
+ case WRITE_FILEMARKS:
+ case WRITE_FILEMARKS_16:
+ case SPACE:
+ case RESERVE:
+ case RELEASE:
+ case ERASE:
+ case ALLOW_MEDIUM_REMOVAL:
+ case SEEK_10:
+ case SYNCHRONIZE_CACHE:
+ case SYNCHRONIZE_CACHE_16:
+ case LOCATE_16:
+ case LOCK_UNLOCK_CACHE:
+ case SET_CD_SPEED:
+ case SET_LIMITS:
+ case WRITE_LONG_10:
+ case UPDATE_BLOCK:
+ case RESERVE_TRACK:
+ case SET_READ_AHEAD:
+ case PRE_FETCH:
+ case PRE_FETCH_16:
+ case ALLOW_OVERWRITE:
+ cmd->xfer = 0;
+ break;
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ if ((buf[1] & 2) == 0) {
+ cmd->xfer = 0;
+ } else if ((buf[1] & 4) != 0) {
+ cmd->xfer = 1;
+ }
+ cmd->xfer *= dev->blocksize;
+ break;
+ case MODE_SENSE:
+ break;
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ cmd->xfer = dev->blocksize;
+ break;
+ case READ_CAPACITY_10:
+ cmd->xfer = 8;
+ break;
+ case READ_BLOCK_LIMITS:
+ cmd->xfer = 6;
+ break;
+ case SEND_VOLUME_TAG:
+ /* GPCMD_SET_STREAMING from multimedia commands. */
+ if (dev->type == TYPE_ROM) {
+ cmd->xfer = buf[10] | (buf[9] << 8);
+ } else {
+ cmd->xfer = buf[9] | (buf[8] << 8);
+ }
+ break;
+ case WRITE_6:
+ /* length 0 means 256 blocks */
+ if (cmd->xfer == 0) {
+ cmd->xfer = 256;
+ }
+ /* fall through */
+ case WRITE_10:
+ case WRITE_VERIFY_10:
+ case WRITE_12:
+ case WRITE_VERIFY_12:
+ case WRITE_16:
+ case WRITE_VERIFY_16:
+ cmd->xfer *= dev->blocksize;
+ break;
+ case READ_6:
+ case READ_REVERSE:
+ /* length 0 means 256 blocks */
+ if (cmd->xfer == 0) {
+ cmd->xfer = 256;
+ }
+ /* fall through */
+ case READ_10:
+ case RECOVER_BUFFERED_DATA:
+ case READ_12:
+ case READ_16:
+ cmd->xfer *= dev->blocksize;
+ break;
+ case FORMAT_UNIT:
+ /* MMC mandates the parameter list to be 12-bytes long. Parameters
+ * for block devices are restricted to the header right now. */
+ if (dev->type == TYPE_ROM && (buf[1] & 16)) {
+ cmd->xfer = 12;
+ } else {
+ cmd->xfer = (buf[1] & 16) == 0 ? 0 : (buf[1] & 32 ? 8 : 4);
+ }
+ break;
+ case INQUIRY:
+ case RECEIVE_DIAGNOSTIC:
+ case SEND_DIAGNOSTIC:
+ cmd->xfer = buf[4] | (buf[3] << 8);
+ break;
+ case READ_CD:
+ case READ_BUFFER:
+ case WRITE_BUFFER:
+ case SEND_CUE_SHEET:
+ cmd->xfer = buf[8] | (buf[7] << 8) | (buf[6] << 16);
+ break;
+ case PERSISTENT_RESERVE_OUT:
+ cmd->xfer = ldl_be_p(&buf[5]) & 0xffffffffULL;
+ break;
+ case ERASE_12:
+ if (dev->type == TYPE_ROM) {
+ /* MMC command GET PERFORMANCE. */
+ cmd->xfer = scsi_get_performance_length(buf[9] | (buf[8] << 8),
+ buf[10], buf[1] & 0x1f);
+ }
+ break;
+ case MECHANISM_STATUS:
+ case READ_DVD_STRUCTURE:
+ case SEND_DVD_STRUCTURE:
+ case MAINTENANCE_OUT:
+ case MAINTENANCE_IN:
+ if (dev->type == TYPE_ROM) {
+ /* GPCMD_REPORT_KEY and GPCMD_SEND_KEY from multi media commands */
+ cmd->xfer = buf[9] | (buf[8] << 8);
+ }
+ break;
+ case ATA_PASSTHROUGH_12:
+ if (dev->type == TYPE_ROM) {
+ /* BLANK command of MMC */
+ cmd->xfer = 0;
+ } else {
+ cmd->xfer = ata_passthrough_12_xfer(dev, buf);
+ }
+ break;
+ case ATA_PASSTHROUGH_16:
+ cmd->xfer = ata_passthrough_16_xfer(dev, buf);
+ break;
+ }
+ return 0;
+}
+
+static int scsi_req_stream_xfer(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf)
+{
+ switch (buf[0]) {
+ /* stream commands */
+ case ERASE_12:
+ case ERASE_16:
+ cmd->xfer = 0;
+ break;
+ case READ_6:
+ case READ_REVERSE:
+ case RECOVER_BUFFERED_DATA:
+ case WRITE_6:
+ cmd->xfer = buf[4] | (buf[3] << 8) | (buf[2] << 16);
+ if (buf[1] & 0x01) { /* fixed */
+ cmd->xfer *= dev->blocksize;
+ }
+ break;
+ case READ_16:
+ case READ_REVERSE_16:
+ case VERIFY_16:
+ case WRITE_16:
+ cmd->xfer = buf[14] | (buf[13] << 8) | (buf[12] << 16);
+ if (buf[1] & 0x01) { /* fixed */
+ cmd->xfer *= dev->blocksize;
+ }
+ break;
+ case REWIND:
+ case LOAD_UNLOAD:
+ cmd->xfer = 0;
+ break;
+ case SPACE_16:
+ cmd->xfer = buf[13] | (buf[12] << 8);
+ break;
+ case READ_POSITION:
+ switch (buf[1] & 0x1f) /* operation code */ {
+ case SHORT_FORM_BLOCK_ID:
+ case SHORT_FORM_VENDOR_SPECIFIC:
+ cmd->xfer = 20;
+ break;
+ case LONG_FORM:
+ cmd->xfer = 32;
+ break;
+ case EXTENDED_FORM:
+ cmd->xfer = buf[8] | (buf[7] << 8);
+ break;
+ default:
+ return -1;
+ }
+
+ break;
+ case FORMAT_UNIT:
+ cmd->xfer = buf[4] | (buf[3] << 8);
+ break;
+ /* generic commands */
+ default:
+ return scsi_req_xfer(cmd, dev, buf);
+ }
+ return 0;
+}
+
+static int scsi_req_medium_changer_xfer(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf)
+{
+ switch (buf[0]) {
+ /* medium changer commands */
+ case EXCHANGE_MEDIUM:
+ case INITIALIZE_ELEMENT_STATUS:
+ case INITIALIZE_ELEMENT_STATUS_WITH_RANGE:
+ case MOVE_MEDIUM:
+ case POSITION_TO_ELEMENT:
+ cmd->xfer = 0;
+ break;
+ case READ_ELEMENT_STATUS:
+ cmd->xfer = buf[9] | (buf[8] << 8) | (buf[7] << 16);
+ break;
+
+ /* generic commands */
+ default:
+ return scsi_req_xfer(cmd, dev, buf);
+ }
+ return 0;
+}
+
+
+static void scsi_cmd_xfer_mode(SCSICommand *cmd)
+{
+ if (!cmd->xfer) {
+ cmd->mode = SCSI_XFER_NONE;
+ return;
+ }
+ switch (cmd->buf[0]) {
+ case WRITE_6:
+ case WRITE_10:
+ case WRITE_VERIFY_10:
+ case WRITE_12:
+ case WRITE_VERIFY_12:
+ case WRITE_16:
+ case WRITE_VERIFY_16:
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ case COPY:
+ case COPY_VERIFY:
+ case COMPARE:
+ case CHANGE_DEFINITION:
+ case LOG_SELECT:
+ case MODE_SELECT:
+ case MODE_SELECT_10:
+ case SEND_DIAGNOSTIC:
+ case WRITE_BUFFER:
+ case FORMAT_UNIT:
+ case REASSIGN_BLOCKS:
+ case SEARCH_EQUAL:
+ case SEARCH_HIGH:
+ case SEARCH_LOW:
+ case UPDATE_BLOCK:
+ case WRITE_LONG_10:
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ case UNMAP:
+ case SEARCH_HIGH_12:
+ case SEARCH_EQUAL_12:
+ case SEARCH_LOW_12:
+ case MEDIUM_SCAN:
+ case SEND_VOLUME_TAG:
+ case SEND_CUE_SHEET:
+ case SEND_DVD_STRUCTURE:
+ case PERSISTENT_RESERVE_OUT:
+ case MAINTENANCE_OUT:
+ cmd->mode = SCSI_XFER_TO_DEV;
+ break;
+ case ATA_PASSTHROUGH_12:
+ case ATA_PASSTHROUGH_16:
+ /* T_DIR */
+ cmd->mode = (cmd->buf[2] & 0x8) ?
+ SCSI_XFER_FROM_DEV : SCSI_XFER_TO_DEV;
+ break;
+ default:
+ cmd->mode = SCSI_XFER_FROM_DEV;
+ break;
+ }
+}
+
+static uint64_t scsi_cmd_lba(SCSICommand *cmd)
+{
+ uint8_t *buf = cmd->buf;
+ uint64_t lba;
+
+ switch (buf[0] >> 5) {
+ case 0:
+ lba = ldl_be_p(&buf[0]) & 0x1fffff;
+ break;
+ case 1:
+ case 2:
+ case 5:
+ lba = ldl_be_p(&buf[2]) & 0xffffffffULL;
+ break;
+ case 4:
+ lba = ldq_be_p(&buf[2]);
+ break;
+ default:
+ lba = -1;
+
+ }
+ return lba;
+}
+
+int scsi_cdb_length(uint8_t *buf) {
+ int cdb_len;
+
+ switch (buf[0] >> 5) {
+ case 0:
+ cdb_len = 6;
+ break;
+ case 1:
+ case 2:
+ cdb_len = 10;
+ break;
+ case 4:
+ cdb_len = 16;
+ break;
+ case 5:
+ cdb_len = 12;
+ break;
+ default:
+ cdb_len = -1;
+ }
+ return cdb_len;
+}
+
+int scsi_req_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf)
+{
+ int rc;
+ int len;
+
+ cmd->lba = -1;
+ len = scsi_cdb_length(buf);
+ if (len < 0) {
+ return -1;
+ }
+
+ cmd->len = len;
+ switch (dev->type) {
+ case TYPE_TAPE:
+ rc = scsi_req_stream_xfer(cmd, dev, buf);
+ break;
+ case TYPE_MEDIUM_CHANGER:
+ rc = scsi_req_medium_changer_xfer(cmd, dev, buf);
+ break;
+ default:
+ rc = scsi_req_xfer(cmd, dev, buf);
+ break;
+ }
+
+ if (rc != 0)
+ return rc;
+
+ memcpy(cmd->buf, buf, cmd->len);
+ scsi_cmd_xfer_mode(cmd);
+ cmd->lba = scsi_cmd_lba(cmd);
+ return 0;
+}
+
+void scsi_device_report_change(SCSIDevice *dev, SCSISense sense)
+{
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus);
+
+ scsi_device_set_ua(dev, sense);
+ if (bus->info->change) {
+ bus->info->change(bus, dev, sense);
+ }
+}
+
+/*
+ * Predefined sense codes
+ */
+
+/* No sense data available */
+const struct SCSISense sense_code_NO_SENSE = {
+ .key = NO_SENSE , .asc = 0x00 , .ascq = 0x00
+};
+
+/* LUN not ready, Manual intervention required */
+const struct SCSISense sense_code_LUN_NOT_READY = {
+ .key = NOT_READY, .asc = 0x04, .ascq = 0x03
+};
+
+/* LUN not ready, Medium not present */
+const struct SCSISense sense_code_NO_MEDIUM = {
+ .key = NOT_READY, .asc = 0x3a, .ascq = 0x00
+};
+
+/* LUN not ready, medium removal prevented */
+const struct SCSISense sense_code_NOT_READY_REMOVAL_PREVENTED = {
+ .key = NOT_READY, .asc = 0x53, .ascq = 0x02
+};
+
+/* Hardware error, internal target failure */
+const struct SCSISense sense_code_TARGET_FAILURE = {
+ .key = HARDWARE_ERROR, .asc = 0x44, .ascq = 0x00
+};
+
+/* Illegal request, invalid command operation code */
+const struct SCSISense sense_code_INVALID_OPCODE = {
+ .key = ILLEGAL_REQUEST, .asc = 0x20, .ascq = 0x00
+};
+
+/* Illegal request, LBA out of range */
+const struct SCSISense sense_code_LBA_OUT_OF_RANGE = {
+ .key = ILLEGAL_REQUEST, .asc = 0x21, .ascq = 0x00
+};
+
+/* Illegal request, Invalid field in CDB */
+const struct SCSISense sense_code_INVALID_FIELD = {
+ .key = ILLEGAL_REQUEST, .asc = 0x24, .ascq = 0x00
+};
+
+/* Illegal request, Invalid field in parameter list */
+const struct SCSISense sense_code_INVALID_PARAM = {
+ .key = ILLEGAL_REQUEST, .asc = 0x26, .ascq = 0x00
+};
+
+/* Illegal request, Parameter list length error */
+const struct SCSISense sense_code_INVALID_PARAM_LEN = {
+ .key = ILLEGAL_REQUEST, .asc = 0x1a, .ascq = 0x00
+};
+
+/* Illegal request, LUN not supported */
+const struct SCSISense sense_code_LUN_NOT_SUPPORTED = {
+ .key = ILLEGAL_REQUEST, .asc = 0x25, .ascq = 0x00
+};
+
+/* Illegal request, Saving parameters not supported */
+const struct SCSISense sense_code_SAVING_PARAMS_NOT_SUPPORTED = {
+ .key = ILLEGAL_REQUEST, .asc = 0x39, .ascq = 0x00
+};
+
+/* Illegal request, Incompatible medium installed */
+const struct SCSISense sense_code_INCOMPATIBLE_FORMAT = {
+ .key = ILLEGAL_REQUEST, .asc = 0x30, .ascq = 0x00
+};
+
+/* Illegal request, medium removal prevented */
+const struct SCSISense sense_code_ILLEGAL_REQ_REMOVAL_PREVENTED = {
+ .key = ILLEGAL_REQUEST, .asc = 0x53, .ascq = 0x02
+};
+
+/* Illegal request, Invalid Transfer Tag */
+const struct SCSISense sense_code_INVALID_TAG = {
+ .key = ILLEGAL_REQUEST, .asc = 0x4b, .ascq = 0x01
+};
+
+/* Command aborted, I/O process terminated */
+const struct SCSISense sense_code_IO_ERROR = {
+ .key = ABORTED_COMMAND, .asc = 0x00, .ascq = 0x06
+};
+
+/* Command aborted, I_T Nexus loss occurred */
+const struct SCSISense sense_code_I_T_NEXUS_LOSS = {
+ .key = ABORTED_COMMAND, .asc = 0x29, .ascq = 0x07
+};
+
+/* Command aborted, Logical Unit failure */
+const struct SCSISense sense_code_LUN_FAILURE = {
+ .key = ABORTED_COMMAND, .asc = 0x3e, .ascq = 0x01
+};
+
+/* Command aborted, Overlapped Commands Attempted */
+const struct SCSISense sense_code_OVERLAPPED_COMMANDS = {
+ .key = ABORTED_COMMAND, .asc = 0x4e, .ascq = 0x00
+};
+
+/* Unit attention, Capacity data has changed */
+const struct SCSISense sense_code_CAPACITY_CHANGED = {
+ .key = UNIT_ATTENTION, .asc = 0x2a, .ascq = 0x09
+};
+
+/* Unit attention, Power on, reset or bus device reset occurred */
+const struct SCSISense sense_code_RESET = {
+ .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x00
+};
+
+/* Unit attention, No medium */
+const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM = {
+ .key = UNIT_ATTENTION, .asc = 0x3a, .ascq = 0x00
+};
+
+/* Unit attention, Medium may have changed */
+const struct SCSISense sense_code_MEDIUM_CHANGED = {
+ .key = UNIT_ATTENTION, .asc = 0x28, .ascq = 0x00
+};
+
+/* Unit attention, Reported LUNs data has changed */
+const struct SCSISense sense_code_REPORTED_LUNS_CHANGED = {
+ .key = UNIT_ATTENTION, .asc = 0x3f, .ascq = 0x0e
+};
+
+/* Unit attention, Device internal reset */
+const struct SCSISense sense_code_DEVICE_INTERNAL_RESET = {
+ .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x04
+};
+
+/* Data Protection, Write Protected */
+const struct SCSISense sense_code_WRITE_PROTECTED = {
+ .key = DATA_PROTECT, .asc = 0x27, .ascq = 0x00
+};
+
+/* Data Protection, Space Allocation Failed Write Protect */
+const struct SCSISense sense_code_SPACE_ALLOC_FAILED = {
+ .key = DATA_PROTECT, .asc = 0x27, .ascq = 0x07
+};
+
+/*
+ * scsi_build_sense
+ *
+ * Convert between fixed and descriptor sense buffers
+ */
+int scsi_build_sense(uint8_t *in_buf, int in_len,
+ uint8_t *buf, int len, bool fixed)
+{
+ bool fixed_in;
+ SCSISense sense;
+ if (!fixed && len < 8) {
+ return 0;
+ }
+
+ if (in_len == 0) {
+ sense.key = NO_SENSE;
+ sense.asc = 0;
+ sense.ascq = 0;
+ } else {
+ fixed_in = (in_buf[0] & 2) == 0;
+
+ if (fixed == fixed_in) {
+ memcpy(buf, in_buf, MIN(len, in_len));
+ return MIN(len, in_len);
+ }
+
+ if (fixed_in) {
+ sense.key = in_buf[2];
+ sense.asc = in_buf[12];
+ sense.ascq = in_buf[13];
+ } else {
+ sense.key = in_buf[1];
+ sense.asc = in_buf[2];
+ sense.ascq = in_buf[3];
+ }
+ }
+
+ memset(buf, 0, len);
+ if (fixed) {
+ /* Return fixed format sense buffer */
+ buf[0] = 0x70;
+ buf[2] = sense.key;
+ buf[7] = 10;
+ buf[12] = sense.asc;
+ buf[13] = sense.ascq;
+ return MIN(len, SCSI_SENSE_LEN);
+ } else {
+ /* Return descriptor format sense buffer */
+ buf[0] = 0x72;
+ buf[1] = sense.key;
+ buf[2] = sense.asc;
+ buf[3] = sense.ascq;
+ return 8;
+ }
+}
+
+const char *scsi_command_name(uint8_t cmd)
+{
+ static const char *names[] = {
+ [ TEST_UNIT_READY ] = "TEST_UNIT_READY",
+ [ REWIND ] = "REWIND",
+ [ REQUEST_SENSE ] = "REQUEST_SENSE",
+ [ FORMAT_UNIT ] = "FORMAT_UNIT",
+ [ READ_BLOCK_LIMITS ] = "READ_BLOCK_LIMITS",
+ [ REASSIGN_BLOCKS ] = "REASSIGN_BLOCKS/INITIALIZE ELEMENT STATUS",
+ /* LOAD_UNLOAD and INITIALIZE_ELEMENT_STATUS use the same operation code */
+ [ READ_6 ] = "READ_6",
+ [ WRITE_6 ] = "WRITE_6",
+ [ SET_CAPACITY ] = "SET_CAPACITY",
+ [ READ_REVERSE ] = "READ_REVERSE",
+ [ WRITE_FILEMARKS ] = "WRITE_FILEMARKS",
+ [ SPACE ] = "SPACE",
+ [ INQUIRY ] = "INQUIRY",
+ [ RECOVER_BUFFERED_DATA ] = "RECOVER_BUFFERED_DATA",
+ [ MAINTENANCE_IN ] = "MAINTENANCE_IN",
+ [ MAINTENANCE_OUT ] = "MAINTENANCE_OUT",
+ [ MODE_SELECT ] = "MODE_SELECT",
+ [ RESERVE ] = "RESERVE",
+ [ RELEASE ] = "RELEASE",
+ [ COPY ] = "COPY",
+ [ ERASE ] = "ERASE",
+ [ MODE_SENSE ] = "MODE_SENSE",
+ [ START_STOP ] = "START_STOP/LOAD_UNLOAD",
+ /* LOAD_UNLOAD and START_STOP use the same operation code */
+ [ RECEIVE_DIAGNOSTIC ] = "RECEIVE_DIAGNOSTIC",
+ [ SEND_DIAGNOSTIC ] = "SEND_DIAGNOSTIC",
+ [ ALLOW_MEDIUM_REMOVAL ] = "ALLOW_MEDIUM_REMOVAL",
+ [ READ_CAPACITY_10 ] = "READ_CAPACITY_10",
+ [ READ_10 ] = "READ_10",
+ [ WRITE_10 ] = "WRITE_10",
+ [ SEEK_10 ] = "SEEK_10/POSITION_TO_ELEMENT",
+ /* SEEK_10 and POSITION_TO_ELEMENT use the same operation code */
+ [ WRITE_VERIFY_10 ] = "WRITE_VERIFY_10",
+ [ VERIFY_10 ] = "VERIFY_10",
+ [ SEARCH_HIGH ] = "SEARCH_HIGH",
+ [ SEARCH_EQUAL ] = "SEARCH_EQUAL",
+ [ SEARCH_LOW ] = "SEARCH_LOW",
+ [ SET_LIMITS ] = "SET_LIMITS",
+ [ PRE_FETCH ] = "PRE_FETCH/READ_POSITION",
+ /* READ_POSITION and PRE_FETCH use the same operation code */
+ [ SYNCHRONIZE_CACHE ] = "SYNCHRONIZE_CACHE",
+ [ LOCK_UNLOCK_CACHE ] = "LOCK_UNLOCK_CACHE",
+ [ READ_DEFECT_DATA ] = "READ_DEFECT_DATA/INITIALIZE_ELEMENT_STATUS_WITH_RANGE",
+ /* READ_DEFECT_DATA and INITIALIZE_ELEMENT_STATUS_WITH_RANGE use the same operation code */
+ [ MEDIUM_SCAN ] = "MEDIUM_SCAN",
+ [ COMPARE ] = "COMPARE",
+ [ COPY_VERIFY ] = "COPY_VERIFY",
+ [ WRITE_BUFFER ] = "WRITE_BUFFER",
+ [ READ_BUFFER ] = "READ_BUFFER",
+ [ UPDATE_BLOCK ] = "UPDATE_BLOCK",
+ [ READ_LONG_10 ] = "READ_LONG_10",
+ [ WRITE_LONG_10 ] = "WRITE_LONG_10",
+ [ CHANGE_DEFINITION ] = "CHANGE_DEFINITION",
+ [ WRITE_SAME_10 ] = "WRITE_SAME_10",
+ [ UNMAP ] = "UNMAP",
+ [ READ_TOC ] = "READ_TOC",
+ [ REPORT_DENSITY_SUPPORT ] = "REPORT_DENSITY_SUPPORT",
+ [ SANITIZE ] = "SANITIZE",
+ [ GET_CONFIGURATION ] = "GET_CONFIGURATION",
+ [ LOG_SELECT ] = "LOG_SELECT",
+ [ LOG_SENSE ] = "LOG_SENSE",
+ [ MODE_SELECT_10 ] = "MODE_SELECT_10",
+ [ RESERVE_10 ] = "RESERVE_10",
+ [ RELEASE_10 ] = "RELEASE_10",
+ [ MODE_SENSE_10 ] = "MODE_SENSE_10",
+ [ PERSISTENT_RESERVE_IN ] = "PERSISTENT_RESERVE_IN",
+ [ PERSISTENT_RESERVE_OUT ] = "PERSISTENT_RESERVE_OUT",
+ [ WRITE_FILEMARKS_16 ] = "WRITE_FILEMARKS_16",
+ [ EXTENDED_COPY ] = "EXTENDED_COPY",
+ [ ATA_PASSTHROUGH_16 ] = "ATA_PASSTHROUGH_16",
+ [ ACCESS_CONTROL_IN ] = "ACCESS_CONTROL_IN",
+ [ ACCESS_CONTROL_OUT ] = "ACCESS_CONTROL_OUT",
+ [ READ_16 ] = "READ_16",
+ [ COMPARE_AND_WRITE ] = "COMPARE_AND_WRITE",
+ [ WRITE_16 ] = "WRITE_16",
+ [ WRITE_VERIFY_16 ] = "WRITE_VERIFY_16",
+ [ VERIFY_16 ] = "VERIFY_16",
+ [ PRE_FETCH_16 ] = "PRE_FETCH_16",
+ [ SYNCHRONIZE_CACHE_16 ] = "SPACE_16/SYNCHRONIZE_CACHE_16",
+ /* SPACE_16 and SYNCHRONIZE_CACHE_16 use the same operation code */
+ [ LOCATE_16 ] = "LOCATE_16",
+ [ WRITE_SAME_16 ] = "ERASE_16/WRITE_SAME_16",
+ /* ERASE_16 and WRITE_SAME_16 use the same operation code */
+ [ SERVICE_ACTION_IN_16 ] = "SERVICE_ACTION_IN_16",
+ [ WRITE_LONG_16 ] = "WRITE_LONG_16",
+ [ REPORT_LUNS ] = "REPORT_LUNS",
+ [ ATA_PASSTHROUGH_12 ] = "BLANK/ATA_PASSTHROUGH_12",
+ [ MOVE_MEDIUM ] = "MOVE_MEDIUM",
+ [ EXCHANGE_MEDIUM ] = "EXCHANGE MEDIUM",
+ [ READ_12 ] = "READ_12",
+ [ WRITE_12 ] = "WRITE_12",
+ [ ERASE_12 ] = "ERASE_12/GET_PERFORMANCE",
+ /* ERASE_12 and GET_PERFORMANCE use the same operation code */
+ [ SERVICE_ACTION_IN_12 ] = "SERVICE_ACTION_IN_12",
+ [ WRITE_VERIFY_12 ] = "WRITE_VERIFY_12",
+ [ VERIFY_12 ] = "VERIFY_12",
+ [ SEARCH_HIGH_12 ] = "SEARCH_HIGH_12",
+ [ SEARCH_EQUAL_12 ] = "SEARCH_EQUAL_12",
+ [ SEARCH_LOW_12 ] = "SEARCH_LOW_12",
+ [ READ_ELEMENT_STATUS ] = "READ_ELEMENT_STATUS",
+ [ SEND_VOLUME_TAG ] = "SEND_VOLUME_TAG/SET_STREAMING",
+ /* SEND_VOLUME_TAG and SET_STREAMING use the same operation code */
+ [ READ_CD ] = "READ_CD",
+ [ READ_DEFECT_DATA_12 ] = "READ_DEFECT_DATA_12",
+ [ READ_DVD_STRUCTURE ] = "READ_DVD_STRUCTURE",
+ [ RESERVE_TRACK ] = "RESERVE_TRACK",
+ [ SEND_CUE_SHEET ] = "SEND_CUE_SHEET",
+ [ SEND_DVD_STRUCTURE ] = "SEND_DVD_STRUCTURE",
+ [ SET_CD_SPEED ] = "SET_CD_SPEED",
+ [ SET_READ_AHEAD ] = "SET_READ_AHEAD",
+ [ ALLOW_OVERWRITE ] = "ALLOW_OVERWRITE",
+ [ MECHANISM_STATUS ] = "MECHANISM_STATUS",
+ [ GET_EVENT_STATUS_NOTIFICATION ] = "GET_EVENT_STATUS_NOTIFICATION",
+ [ READ_DISC_INFORMATION ] = "READ_DISC_INFORMATION",
+ };
+
+ if (cmd >= ARRAY_SIZE(names) || names[cmd] == NULL)
+ return "*UNKNOWN*";
+ return names[cmd];
+}
+
+SCSIRequest *scsi_req_ref(SCSIRequest *req)
+{
+ assert(req->refcount > 0);
+ req->refcount++;
+ return req;
+}
+
+void scsi_req_unref(SCSIRequest *req)
+{
+ assert(req->refcount > 0);
+ if (--req->refcount == 0) {
+ BusState *qbus = req->dev->qdev.parent_bus;
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, qbus);
+
+ if (bus->info->free_request && req->hba_private) {
+ bus->info->free_request(bus, req->hba_private);
+ }
+ if (req->ops->free_req) {
+ req->ops->free_req(req);
+ }
+ object_unref(OBJECT(req->dev));
+ object_unref(OBJECT(qbus->parent));
+ g_slice_free1(req->ops->size, req);
+ }
+}
+
+/* Tell the device that we finished processing this chunk of I/O. It
+ will start the next chunk or complete the command. */
+void scsi_req_continue(SCSIRequest *req)
+{
+ if (req->io_canceled) {
+ trace_scsi_req_continue_canceled(req->dev->id, req->lun, req->tag);
+ return;
+ }
+ trace_scsi_req_continue(req->dev->id, req->lun, req->tag);
+ if (req->cmd.mode == SCSI_XFER_TO_DEV) {
+ req->ops->write_data(req);
+ } else {
+ req->ops->read_data(req);
+ }
+}
+
+/* Called by the devices when data is ready for the HBA. The HBA should
+ start a DMA operation to read or fill the device's data buffer.
+ Once it completes, calling scsi_req_continue will restart I/O. */
+void scsi_req_data(SCSIRequest *req, int len)
+{
+ uint8_t *buf;
+ if (req->io_canceled) {
+ trace_scsi_req_data_canceled(req->dev->id, req->lun, req->tag, len);
+ return;
+ }
+ trace_scsi_req_data(req->dev->id, req->lun, req->tag, len);
+ assert(req->cmd.mode != SCSI_XFER_NONE);
+ if (!req->sg) {
+ req->resid -= len;
+ req->bus->info->transfer_data(req, len);
+ return;
+ }
+
+ /* If the device calls scsi_req_data and the HBA specified a
+ * scatter/gather list, the transfer has to happen in a single
+ * step. */
+ assert(!req->dma_started);
+ req->dma_started = true;
+
+ buf = scsi_req_get_buf(req);
+ if (req->cmd.mode == SCSI_XFER_FROM_DEV) {
+ req->resid = dma_buf_read(buf, len, req->sg);
+ } else {
+ req->resid = dma_buf_write(buf, len, req->sg);
+ }
+ scsi_req_continue(req);
+}
+
+void scsi_req_print(SCSIRequest *req)
+{
+ FILE *fp = stderr;
+ int i;
+
+ fprintf(fp, "[%s id=%d] %s",
+ req->dev->qdev.parent_bus->name,
+ req->dev->id,
+ scsi_command_name(req->cmd.buf[0]));
+ for (i = 1; i < req->cmd.len; i++) {
+ fprintf(fp, " 0x%02x", req->cmd.buf[i]);
+ }
+ switch (req->cmd.mode) {
+ case SCSI_XFER_NONE:
+ fprintf(fp, " - none\n");
+ break;
+ case SCSI_XFER_FROM_DEV:
+ fprintf(fp, " - from-dev len=%zd\n", req->cmd.xfer);
+ break;
+ case SCSI_XFER_TO_DEV:
+ fprintf(fp, " - to-dev len=%zd\n", req->cmd.xfer);
+ break;
+ default:
+ fprintf(fp, " - Oops\n");
+ break;
+ }
+}
+
+void scsi_req_complete(SCSIRequest *req, int status)
+{
+ assert(req->status == -1);
+ req->status = status;
+
+ assert(req->sense_len <= sizeof(req->sense));
+ if (status == GOOD) {
+ req->sense_len = 0;
+ }
+
+ if (req->sense_len) {
+ memcpy(req->dev->sense, req->sense, req->sense_len);
+ req->dev->sense_len = req->sense_len;
+ req->dev->sense_is_ua = (req->ops == &reqops_unit_attention);
+ } else {
+ req->dev->sense_len = 0;
+ req->dev->sense_is_ua = false;
+ }
+
+ /*
+ * Unit attention state is now stored in the device's sense buffer
+ * if the HBA didn't do autosense. Clear the pending unit attention
+ * flags.
+ */
+ scsi_clear_unit_attention(req);
+
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ req->bus->info->complete(req, req->status, req->resid);
+
+ /* Cancelled requests might end up being completed instead of cancelled */
+ notifier_list_notify(&req->cancel_notifiers, req);
+ scsi_req_unref(req);
+}
+
+/* Called by the devices when the request is canceled. */
+void scsi_req_cancel_complete(SCSIRequest *req)
+{
+ assert(req->io_canceled);
+ if (req->bus->info->cancel) {
+ req->bus->info->cancel(req);
+ }
+ notifier_list_notify(&req->cancel_notifiers, req);
+ scsi_req_unref(req);
+}
+
+/* Cancel @req asynchronously. @notifier is added to @req's cancellation
+ * notifier list, the bus will be notified the requests cancellation is
+ * completed.
+ * */
+void scsi_req_cancel_async(SCSIRequest *req, Notifier *notifier)
+{
+ trace_scsi_req_cancel(req->dev->id, req->lun, req->tag);
+ if (notifier) {
+ notifier_list_add(&req->cancel_notifiers, notifier);
+ }
+ if (req->io_canceled) {
+ return;
+ }
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ req->io_canceled = true;
+ if (req->aiocb) {
+ blk_aio_cancel_async(req->aiocb);
+ } else {
+ scsi_req_cancel_complete(req);
+ }
+}
+
+void scsi_req_cancel(SCSIRequest *req)
+{
+ trace_scsi_req_cancel(req->dev->id, req->lun, req->tag);
+ if (!req->enqueued) {
+ return;
+ }
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ req->io_canceled = true;
+ if (req->aiocb) {
+ blk_aio_cancel(req->aiocb);
+ } else {
+ scsi_req_cancel_complete(req);
+ }
+}
+
+static int scsi_ua_precedence(SCSISense sense)
+{
+ if (sense.key != UNIT_ATTENTION) {
+ return INT_MAX;
+ }
+ if (sense.asc == 0x29 && sense.ascq == 0x04) {
+ /* DEVICE INTERNAL RESET goes with POWER ON OCCURRED */
+ return 1;
+ } else if (sense.asc == 0x3F && sense.ascq == 0x01) {
+ /* MICROCODE HAS BEEN CHANGED goes with SCSI BUS RESET OCCURRED */
+ return 2;
+ } else if (sense.asc == 0x29 && (sense.ascq == 0x05 || sense.ascq == 0x06)) {
+ /* These two go with "all others". */
+ ;
+ } else if (sense.asc == 0x29 && sense.ascq <= 0x07) {
+ /* POWER ON, RESET OR BUS DEVICE RESET OCCURRED = 0
+ * POWER ON OCCURRED = 1
+ * SCSI BUS RESET OCCURRED = 2
+ * BUS DEVICE RESET FUNCTION OCCURRED = 3
+ * I_T NEXUS LOSS OCCURRED = 7
+ */
+ return sense.ascq;
+ } else if (sense.asc == 0x2F && sense.ascq == 0x01) {
+ /* COMMANDS CLEARED BY POWER LOSS NOTIFICATION */
+ return 8;
+ }
+ return (sense.asc << 8) | sense.ascq;
+}
+
+void scsi_device_set_ua(SCSIDevice *sdev, SCSISense sense)
+{
+ int prec1, prec2;
+ if (sense.key != UNIT_ATTENTION) {
+ return;
+ }
+ trace_scsi_device_set_ua(sdev->id, sdev->lun, sense.key,
+ sense.asc, sense.ascq);
+
+ /*
+ * Override a pre-existing unit attention condition, except for a more
+ * important reset condition.
+ */
+ prec1 = scsi_ua_precedence(sdev->unit_attention);
+ prec2 = scsi_ua_precedence(sense);
+ if (prec2 < prec1) {
+ sdev->unit_attention = sense;
+ }
+}
+
+void scsi_device_purge_requests(SCSIDevice *sdev, SCSISense sense)
+{
+ SCSIRequest *req;
+
+ while (!QTAILQ_EMPTY(&sdev->requests)) {
+ req = QTAILQ_FIRST(&sdev->requests);
+ scsi_req_cancel(req);
+ }
+
+ scsi_device_set_ua(sdev, sense);
+}
+
+static char *scsibus_get_dev_path(DeviceState *dev)
+{
+ SCSIDevice *d = DO_UPCAST(SCSIDevice, qdev, dev);
+ DeviceState *hba = dev->parent_bus->parent;
+ char *id;
+ char *path;
+
+ id = qdev_get_dev_path(hba);
+ if (id) {
+ path = g_strdup_printf("%s/%d:%d:%d", id, d->channel, d->id, d->lun);
+ } else {
+ path = g_strdup_printf("%d:%d:%d", d->channel, d->id, d->lun);
+ }
+ g_free(id);
+ return path;
+}
+
+static char *scsibus_get_fw_dev_path(DeviceState *dev)
+{
+ SCSIDevice *d = SCSI_DEVICE(dev);
+ return g_strdup_printf("channel@%x/%s@%x,%x", d->channel,
+ qdev_fw_name(dev), d->id, d->lun);
+}
+
+SCSIDevice *scsi_device_find(SCSIBus *bus, int channel, int id, int lun)
+{
+ BusChild *kid;
+ SCSIDevice *target_dev = NULL;
+
+ QTAILQ_FOREACH_REVERSE(kid, &bus->qbus.children, ChildrenHead, sibling) {
+ DeviceState *qdev = kid->child;
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->channel == channel && dev->id == id) {
+ if (dev->lun == lun) {
+ return dev;
+ }
+ target_dev = dev;
+ }
+ }
+ return target_dev;
+}
+
+/* SCSI request list. For simplicity, pv points to the whole device */
+
+static void put_scsi_requests(QEMUFile *f, void *pv, size_t size)
+{
+ SCSIDevice *s = pv;
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, s->qdev.parent_bus);
+ SCSIRequest *req;
+
+ QTAILQ_FOREACH(req, &s->requests, next) {
+ assert(!req->io_canceled);
+ assert(req->status == -1);
+ assert(req->enqueued);
+
+ qemu_put_sbyte(f, req->retry ? 1 : 2);
+ qemu_put_buffer(f, req->cmd.buf, sizeof(req->cmd.buf));
+ qemu_put_be32s(f, &req->tag);
+ qemu_put_be32s(f, &req->lun);
+ if (bus->info->save_request) {
+ bus->info->save_request(f, req);
+ }
+ if (req->ops->save_request) {
+ req->ops->save_request(f, req);
+ }
+ }
+ qemu_put_sbyte(f, 0);
+}
+
+static int get_scsi_requests(QEMUFile *f, void *pv, size_t size)
+{
+ SCSIDevice *s = pv;
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, s->qdev.parent_bus);
+ int8_t sbyte;
+
+ while ((sbyte = qemu_get_sbyte(f)) > 0) {
+ uint8_t buf[SCSI_CMD_BUF_SIZE];
+ uint32_t tag;
+ uint32_t lun;
+ SCSIRequest *req;
+
+ qemu_get_buffer(f, buf, sizeof(buf));
+ qemu_get_be32s(f, &tag);
+ qemu_get_be32s(f, &lun);
+ req = scsi_req_new(s, tag, lun, buf, NULL);
+ req->retry = (sbyte == 1);
+ if (bus->info->load_request) {
+ req->hba_private = bus->info->load_request(f, req);
+ }
+ if (req->ops->load_request) {
+ req->ops->load_request(f, req);
+ }
+
+ /* Just restart it later. */
+ scsi_req_enqueue_internal(req);
+
+ /* At this point, the request will be kept alive by the reference
+ * added by scsi_req_enqueue_internal, so we can release our reference.
+ * The HBA of course will add its own reference in the load_request
+ * callback if it needs to hold on the SCSIRequest.
+ */
+ scsi_req_unref(req);
+ }
+
+ return 0;
+}
+
+static const VMStateInfo vmstate_info_scsi_requests = {
+ .name = "scsi-requests",
+ .get = get_scsi_requests,
+ .put = put_scsi_requests,
+};
+
+static bool scsi_sense_state_needed(void *opaque)
+{
+ SCSIDevice *s = opaque;
+
+ return s->sense_len > SCSI_SENSE_BUF_SIZE_OLD;
+}
+
+static const VMStateDescription vmstate_scsi_sense_state = {
+ .name = "SCSIDevice/sense",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = scsi_sense_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_SUB_ARRAY(sense, SCSIDevice,
+ SCSI_SENSE_BUF_SIZE_OLD,
+ SCSI_SENSE_BUF_SIZE - SCSI_SENSE_BUF_SIZE_OLD),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_scsi_device = {
+ .name = "SCSIDevice",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(unit_attention.key, SCSIDevice),
+ VMSTATE_UINT8(unit_attention.asc, SCSIDevice),
+ VMSTATE_UINT8(unit_attention.ascq, SCSIDevice),
+ VMSTATE_BOOL(sense_is_ua, SCSIDevice),
+ VMSTATE_UINT8_SUB_ARRAY(sense, SCSIDevice, 0, SCSI_SENSE_BUF_SIZE_OLD),
+ VMSTATE_UINT32(sense_len, SCSIDevice),
+ {
+ .name = "requests",
+ .version_id = 0,
+ .field_exists = NULL,
+ .size = 0, /* ouch */
+ .info = &vmstate_info_scsi_requests,
+ .flags = VMS_SINGLE,
+ .offset = 0,
+ },
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_scsi_sense_state,
+ NULL
+ }
+};
+
+static void scsi_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ set_bit(DEVICE_CATEGORY_STORAGE, k->categories);
+ k->bus_type = TYPE_SCSI_BUS;
+ k->realize = scsi_qdev_realize;
+ k->unrealize = scsi_qdev_unrealize;
+ k->props = scsi_props;
+}
+
+static void scsi_dev_instance_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SCSIDevice *s = DO_UPCAST(SCSIDevice, qdev, dev);
+
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", NULL,
+ &s->qdev, NULL);
+}
+
+static const TypeInfo scsi_device_type_info = {
+ .name = TYPE_SCSI_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(SCSIDevice),
+ .abstract = true,
+ .class_size = sizeof(SCSIDeviceClass),
+ .class_init = scsi_device_class_init,
+ .instance_init = scsi_dev_instance_init,
+};
+
+static void scsi_register_types(void)
+{
+ type_register_static(&scsi_bus_info);
+ type_register_static(&scsi_device_type_info);
+}
+
+type_init(scsi_register_types)
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
new file mode 100644
index 00000000..0e0bc644
--- /dev/null
+++ b/hw/scsi/scsi-disk.c
@@ -0,0 +1,2780 @@
+/*
+ * SCSI Device emulation
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Based on code by Fabrice Bellard
+ *
+ * Written by Paul Brook
+ * Modifications:
+ * 2009-Dec-12 Artyom Tarasenko : implemented stamdard inquiry for the case
+ * when the allocation length of CDB is smaller
+ * than 36.
+ * 2009-Oct-13 Artyom Tarasenko : implemented the block descriptor in the
+ * MODE SENSE response.
+ *
+ * This code is licensed under the LGPL.
+ *
+ * Note that this file only handles the SCSI architecture model and device
+ * commands. Emulation of interface/link layer protocols is handled by
+ * the host adapter emulator.
+ */
+
+//#define DEBUG_SCSI
+
+#ifdef DEBUG_SCSI
+#define DPRINTF(fmt, ...) \
+do { printf("scsi-disk: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "hw/scsi/scsi.h"
+#include "block/scsi.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/block/block.h"
+#include "sysemu/dma.h"
+
+#ifdef __linux
+#include <scsi/sg.h>
+#endif
+
+#define SCSI_WRITE_SAME_MAX 524288
+#define SCSI_DMA_BUF_SIZE 131072
+#define SCSI_MAX_INQUIRY_LEN 256
+#define SCSI_MAX_MODE_LEN 256
+
+#define DEFAULT_DISCARD_GRANULARITY 4096
+#define DEFAULT_MAX_UNMAP_SIZE (1 << 30) /* 1 GB */
+#define DEFAULT_MAX_IO_SIZE INT_MAX /* 2 GB - 1 block */
+
+typedef struct SCSIDiskState SCSIDiskState;
+
+typedef struct SCSIDiskReq {
+ SCSIRequest req;
+ /* Both sector and sector_count are in terms of qemu 512 byte blocks. */
+ uint64_t sector;
+ uint32_t sector_count;
+ uint32_t buflen;
+ bool started;
+ struct iovec iov;
+ QEMUIOVector qiov;
+ BlockAcctCookie acct;
+} SCSIDiskReq;
+
+#define SCSI_DISK_F_REMOVABLE 0
+#define SCSI_DISK_F_DPOFUA 1
+#define SCSI_DISK_F_NO_REMOVABLE_DEVOPS 2
+
+struct SCSIDiskState
+{
+ SCSIDevice qdev;
+ uint32_t features;
+ bool media_changed;
+ bool media_event;
+ bool eject_request;
+ uint64_t wwn;
+ uint64_t port_wwn;
+ uint16_t port_index;
+ uint64_t max_unmap_size;
+ uint64_t max_io_size;
+ QEMUBH *bh;
+ char *version;
+ char *serial;
+ char *vendor;
+ char *product;
+ bool tray_open;
+ bool tray_locked;
+};
+
+static int scsi_handle_rw_error(SCSIDiskReq *r, int error);
+
+static void scsi_free_request(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ qemu_vfree(r->iov.iov_base);
+}
+
+/* Helper function for command completion with sense. */
+static void scsi_check_condition(SCSIDiskReq *r, SCSISense sense)
+{
+ DPRINTF("Command complete tag=0x%x sense=%d/%d/%d\n",
+ r->req.tag, sense.key, sense.asc, sense.ascq);
+ scsi_req_build_sense(&r->req, sense);
+ scsi_req_complete(&r->req, CHECK_CONDITION);
+}
+
+static uint32_t scsi_init_iovec(SCSIDiskReq *r, size_t size)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ if (!r->iov.iov_base) {
+ r->buflen = size;
+ r->iov.iov_base = blk_blockalign(s->qdev.conf.blk, r->buflen);
+ }
+ r->iov.iov_len = MIN(r->sector_count * 512, r->buflen);
+ qemu_iovec_init_external(&r->qiov, &r->iov, 1);
+ return r->qiov.size / 512;
+}
+
+static void scsi_disk_save_request(QEMUFile *f, SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ qemu_put_be64s(f, &r->sector);
+ qemu_put_be32s(f, &r->sector_count);
+ qemu_put_be32s(f, &r->buflen);
+ if (r->buflen) {
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ qemu_put_buffer(f, r->iov.iov_base, r->iov.iov_len);
+ } else if (!req->retry) {
+ uint32_t len = r->iov.iov_len;
+ qemu_put_be32s(f, &len);
+ qemu_put_buffer(f, r->iov.iov_base, r->iov.iov_len);
+ }
+ }
+}
+
+static void scsi_disk_load_request(QEMUFile *f, SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ qemu_get_be64s(f, &r->sector);
+ qemu_get_be32s(f, &r->sector_count);
+ qemu_get_be32s(f, &r->buflen);
+ if (r->buflen) {
+ scsi_init_iovec(r, r->buflen);
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ qemu_get_buffer(f, r->iov.iov_base, r->iov.iov_len);
+ } else if (!r->req.retry) {
+ uint32_t len;
+ qemu_get_be32s(f, &len);
+ r->iov.iov_len = len;
+ assert(r->iov.iov_len <= r->buflen);
+ qemu_get_buffer(f, r->iov.iov_base, r->iov.iov_len);
+ }
+ }
+
+ qemu_iovec_init_external(&r->qiov, &r->iov, 1);
+}
+
+static void scsi_aio_complete(void *opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+
+ if (ret < 0) {
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
+ }
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static bool scsi_is_cmd_fua(SCSICommand *cmd)
+{
+ switch (cmd->buf[0]) {
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ return (cmd->buf[1] & 8) != 0;
+
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ case WRITE_VERIFY_10:
+ case WRITE_VERIFY_12:
+ case WRITE_VERIFY_16:
+ return true;
+
+ case READ_6:
+ case WRITE_6:
+ default:
+ return false;
+ }
+}
+
+static void scsi_write_do_fua(SCSIDiskReq *r)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+
+ if (scsi_is_cmd_fua(&r->req.cmd)) {
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ r->req.aiocb = blk_aio_flush(s->qdev.conf.blk, scsi_aio_complete, r);
+ return;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_dma_complete_noio(void *opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ if (r->req.aiocb != NULL) {
+ r->req.aiocb = NULL;
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ }
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+
+ if (ret < 0) {
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
+ }
+ }
+
+ r->sector += r->sector_count;
+ r->sector_count = 0;
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ scsi_write_do_fua(r);
+ return;
+ } else {
+ scsi_req_complete(&r->req, GOOD);
+ }
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_dma_complete(void *opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+
+ assert(r->req.aiocb != NULL);
+ scsi_dma_complete_noio(opaque, ret);
+}
+
+static void scsi_read_complete(void * opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ int n;
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+
+ if (ret < 0) {
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
+ }
+ }
+
+ DPRINTF("Data ready tag=0x%x len=%zd\n", r->req.tag, r->qiov.size);
+
+ n = r->qiov.size / 512;
+ r->sector += n;
+ r->sector_count -= n;
+ scsi_req_data(&r->req, r->qiov.size);
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+/* Actually issue a read to the block device. */
+static void scsi_do_read(void *opaque, int ret)
+{
+ SCSIDiskReq *r = opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint32_t n;
+
+ if (r->req.aiocb != NULL) {
+ r->req.aiocb = NULL;
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ }
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+
+ if (ret < 0) {
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
+ }
+ }
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+
+ if (r->req.sg) {
+ dma_acct_start(s->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_READ);
+ r->req.resid -= r->req.sg->size;
+ r->req.aiocb = dma_blk_read(s->qdev.conf.blk, r->req.sg, r->sector,
+ scsi_dma_complete, r);
+ } else {
+ n = scsi_init_iovec(r, SCSI_DMA_BUF_SIZE);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ n * BDRV_SECTOR_SIZE, BLOCK_ACCT_READ);
+ r->req.aiocb = blk_aio_readv(s->qdev.conf.blk, r->sector, &r->qiov, n,
+ scsi_read_complete, r);
+ }
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+/* Read more data from scsi device into buffer. */
+static void scsi_read_data(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ bool first;
+
+ DPRINTF("Read sector_count=%d\n", r->sector_count);
+ if (r->sector_count == 0) {
+ /* This also clears the sense buffer for REQUEST SENSE. */
+ scsi_req_complete(&r->req, GOOD);
+ return;
+ }
+
+ /* No data transfer may already be in progress */
+ assert(r->req.aiocb == NULL);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ DPRINTF("Data transfer direction invalid\n");
+ scsi_read_complete(r, -EINVAL);
+ return;
+ }
+
+ if (s->tray_open) {
+ scsi_read_complete(r, -ENOMEDIUM);
+ return;
+ }
+
+ first = !r->started;
+ r->started = true;
+ if (first && scsi_is_cmd_fua(&r->req.cmd)) {
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ r->req.aiocb = blk_aio_flush(s->qdev.conf.blk, scsi_do_read, r);
+ } else {
+ scsi_do_read(r, 0);
+ }
+}
+
+/*
+ * scsi_handle_rw_error has two return values. 0 means that the error
+ * must be ignored, 1 means that the error has been processed and the
+ * caller should not do anything else for this request. Note that
+ * scsi_handle_rw_error always manages its reference counts, independent
+ * of the return value.
+ */
+static int scsi_handle_rw_error(SCSIDiskReq *r, int error)
+{
+ bool is_read = (r->req.cmd.xfer == SCSI_XFER_FROM_DEV);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ BlockErrorAction action = blk_get_error_action(s->qdev.conf.blk,
+ is_read, error);
+
+ if (action == BLOCK_ERROR_ACTION_REPORT) {
+ switch (error) {
+ case ENOMEDIUM:
+ scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
+ break;
+ case ENOMEM:
+ scsi_check_condition(r, SENSE_CODE(TARGET_FAILURE));
+ break;
+ case EINVAL:
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ break;
+ case ENOSPC:
+ scsi_check_condition(r, SENSE_CODE(SPACE_ALLOC_FAILED));
+ break;
+ default:
+ scsi_check_condition(r, SENSE_CODE(IO_ERROR));
+ break;
+ }
+ }
+ blk_error_action(s->qdev.conf.blk, action, is_read, error);
+ if (action == BLOCK_ERROR_ACTION_STOP) {
+ scsi_req_retry(&r->req);
+ }
+ return action != BLOCK_ERROR_ACTION_IGNORE;
+}
+
+static void scsi_write_complete(void * opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint32_t n;
+
+ if (r->req.aiocb != NULL) {
+ r->req.aiocb = NULL;
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ }
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+
+ if (ret < 0) {
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
+ }
+ }
+
+ n = r->qiov.size / 512;
+ r->sector += n;
+ r->sector_count -= n;
+ if (r->sector_count == 0) {
+ scsi_write_do_fua(r);
+ return;
+ } else {
+ scsi_init_iovec(r, SCSI_DMA_BUF_SIZE);
+ DPRINTF("Write complete tag=0x%x more=%zd\n", r->req.tag, r->qiov.size);
+ scsi_req_data(&r->req, r->qiov.size);
+ }
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_write_data(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint32_t n;
+
+ /* No data transfer may already be in progress */
+ assert(r->req.aiocb == NULL);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ if (r->req.cmd.mode != SCSI_XFER_TO_DEV) {
+ DPRINTF("Data transfer direction invalid\n");
+ scsi_write_complete(r, -EINVAL);
+ return;
+ }
+
+ if (!r->req.sg && !r->qiov.size) {
+ /* Called for the first time. Ask the driver to send us more data. */
+ r->started = true;
+ scsi_write_complete(r, 0);
+ return;
+ }
+ if (s->tray_open) {
+ scsi_write_complete(r, -ENOMEDIUM);
+ return;
+ }
+
+ if (r->req.cmd.buf[0] == VERIFY_10 || r->req.cmd.buf[0] == VERIFY_12 ||
+ r->req.cmd.buf[0] == VERIFY_16) {
+ if (r->req.sg) {
+ scsi_dma_complete_noio(r, 0);
+ } else {
+ scsi_write_complete(r, 0);
+ }
+ return;
+ }
+
+ if (r->req.sg) {
+ dma_acct_start(s->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_WRITE);
+ r->req.resid -= r->req.sg->size;
+ r->req.aiocb = dma_blk_write(s->qdev.conf.blk, r->req.sg, r->sector,
+ scsi_dma_complete, r);
+ } else {
+ n = r->qiov.size / 512;
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ n * BDRV_SECTOR_SIZE, BLOCK_ACCT_WRITE);
+ r->req.aiocb = blk_aio_writev(s->qdev.conf.blk, r->sector, &r->qiov, n,
+ scsi_write_complete, r);
+ }
+}
+
+/* Return a pointer to the data buffer. */
+static uint8_t *scsi_get_buf(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ return (uint8_t *)r->iov.iov_base;
+}
+
+static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ int buflen = 0;
+ int start;
+
+ if (req->cmd.buf[1] & 0x1) {
+ /* Vital product data */
+ uint8_t page_code = req->cmd.buf[2];
+
+ outbuf[buflen++] = s->qdev.type & 0x1f;
+ outbuf[buflen++] = page_code ; // this page
+ outbuf[buflen++] = 0x00;
+ outbuf[buflen++] = 0x00;
+ start = buflen;
+
+ switch (page_code) {
+ case 0x00: /* Supported page codes, mandatory */
+ {
+ DPRINTF("Inquiry EVPD[Supported pages] "
+ "buffer size %zd\n", req->cmd.xfer);
+ outbuf[buflen++] = 0x00; // list of supported pages (this page)
+ if (s->serial) {
+ outbuf[buflen++] = 0x80; // unit serial number
+ }
+ outbuf[buflen++] = 0x83; // device identification
+ if (s->qdev.type == TYPE_DISK) {
+ outbuf[buflen++] = 0xb0; // block limits
+ outbuf[buflen++] = 0xb2; // thin provisioning
+ }
+ break;
+ }
+ case 0x80: /* Device serial number, optional */
+ {
+ int l;
+
+ if (!s->serial) {
+ DPRINTF("Inquiry (EVPD[Serial number] not supported\n");
+ return -1;
+ }
+
+ l = strlen(s->serial);
+ if (l > 20) {
+ l = 20;
+ }
+
+ DPRINTF("Inquiry EVPD[Serial number] "
+ "buffer size %zd\n", req->cmd.xfer);
+ memcpy(outbuf+buflen, s->serial, l);
+ buflen += l;
+ break;
+ }
+
+ case 0x83: /* Device identification page, mandatory */
+ {
+ const char *str = s->serial ?: blk_name(s->qdev.conf.blk);
+ int max_len = s->serial ? 20 : 255 - 8;
+ int id_len = strlen(str);
+
+ if (id_len > max_len) {
+ id_len = max_len;
+ }
+ DPRINTF("Inquiry EVPD[Device identification] "
+ "buffer size %zd\n", req->cmd.xfer);
+
+ outbuf[buflen++] = 0x2; // ASCII
+ outbuf[buflen++] = 0; // not officially assigned
+ outbuf[buflen++] = 0; // reserved
+ outbuf[buflen++] = id_len; // length of data following
+ memcpy(outbuf+buflen, str, id_len);
+ buflen += id_len;
+
+ if (s->wwn) {
+ outbuf[buflen++] = 0x1; // Binary
+ outbuf[buflen++] = 0x3; // NAA
+ outbuf[buflen++] = 0; // reserved
+ outbuf[buflen++] = 8;
+ stq_be_p(&outbuf[buflen], s->wwn);
+ buflen += 8;
+ }
+
+ if (s->port_wwn) {
+ outbuf[buflen++] = 0x61; // SAS / Binary
+ outbuf[buflen++] = 0x93; // PIV / Target port / NAA
+ outbuf[buflen++] = 0; // reserved
+ outbuf[buflen++] = 8;
+ stq_be_p(&outbuf[buflen], s->port_wwn);
+ buflen += 8;
+ }
+
+ if (s->port_index) {
+ outbuf[buflen++] = 0x61; // SAS / Binary
+ outbuf[buflen++] = 0x94; // PIV / Target port / relative target port
+ outbuf[buflen++] = 0; // reserved
+ outbuf[buflen++] = 4;
+ stw_be_p(&outbuf[buflen + 2], s->port_index);
+ buflen += 4;
+ }
+ break;
+ }
+ case 0xb0: /* block limits */
+ {
+ unsigned int unmap_sectors =
+ s->qdev.conf.discard_granularity / s->qdev.blocksize;
+ unsigned int min_io_size =
+ s->qdev.conf.min_io_size / s->qdev.blocksize;
+ unsigned int opt_io_size =
+ s->qdev.conf.opt_io_size / s->qdev.blocksize;
+ unsigned int max_unmap_sectors =
+ s->max_unmap_size / s->qdev.blocksize;
+ unsigned int max_io_sectors =
+ s->max_io_size / s->qdev.blocksize;
+
+ if (s->qdev.type == TYPE_ROM) {
+ DPRINTF("Inquiry (EVPD[%02X] not supported for CDROM\n",
+ page_code);
+ return -1;
+ }
+ /* required VPD size with unmap support */
+ buflen = 0x40;
+ memset(outbuf + 4, 0, buflen - 4);
+
+ outbuf[4] = 0x1; /* wsnz */
+
+ /* optimal transfer length granularity */
+ outbuf[6] = (min_io_size >> 8) & 0xff;
+ outbuf[7] = min_io_size & 0xff;
+
+ /* maximum transfer length */
+ outbuf[8] = (max_io_sectors >> 24) & 0xff;
+ outbuf[9] = (max_io_sectors >> 16) & 0xff;
+ outbuf[10] = (max_io_sectors >> 8) & 0xff;
+ outbuf[11] = max_io_sectors & 0xff;
+
+ /* optimal transfer length */
+ outbuf[12] = (opt_io_size >> 24) & 0xff;
+ outbuf[13] = (opt_io_size >> 16) & 0xff;
+ outbuf[14] = (opt_io_size >> 8) & 0xff;
+ outbuf[15] = opt_io_size & 0xff;
+
+ /* max unmap LBA count, default is 1GB */
+ outbuf[20] = (max_unmap_sectors >> 24) & 0xff;
+ outbuf[21] = (max_unmap_sectors >> 16) & 0xff;
+ outbuf[22] = (max_unmap_sectors >> 8) & 0xff;
+ outbuf[23] = max_unmap_sectors & 0xff;
+
+ /* max unmap descriptors, 255 fit in 4 kb with an 8-byte header. */
+ outbuf[24] = 0;
+ outbuf[25] = 0;
+ outbuf[26] = 0;
+ outbuf[27] = 255;
+
+ /* optimal unmap granularity */
+ outbuf[28] = (unmap_sectors >> 24) & 0xff;
+ outbuf[29] = (unmap_sectors >> 16) & 0xff;
+ outbuf[30] = (unmap_sectors >> 8) & 0xff;
+ outbuf[31] = unmap_sectors & 0xff;
+
+ /* max write same size */
+ outbuf[36] = 0;
+ outbuf[37] = 0;
+ outbuf[38] = 0;
+ outbuf[39] = 0;
+
+ outbuf[40] = (max_io_sectors >> 24) & 0xff;
+ outbuf[41] = (max_io_sectors >> 16) & 0xff;
+ outbuf[42] = (max_io_sectors >> 8) & 0xff;
+ outbuf[43] = max_io_sectors & 0xff;
+ break;
+ }
+ case 0xb2: /* thin provisioning */
+ {
+ buflen = 8;
+ outbuf[4] = 0;
+ outbuf[5] = 0xe0; /* unmap & write_same 10/16 all supported */
+ outbuf[6] = s->qdev.conf.discard_granularity ? 2 : 1;
+ outbuf[7] = 0;
+ break;
+ }
+ default:
+ return -1;
+ }
+ /* done with EVPD */
+ assert(buflen - start <= 255);
+ outbuf[start - 1] = buflen - start;
+ return buflen;
+ }
+
+ /* Standard INQUIRY data */
+ if (req->cmd.buf[2] != 0) {
+ return -1;
+ }
+
+ /* PAGE CODE == 0 */
+ buflen = req->cmd.xfer;
+ if (buflen > SCSI_MAX_INQUIRY_LEN) {
+ buflen = SCSI_MAX_INQUIRY_LEN;
+ }
+
+ outbuf[0] = s->qdev.type & 0x1f;
+ outbuf[1] = (s->features & (1 << SCSI_DISK_F_REMOVABLE)) ? 0x80 : 0;
+
+ strpadcpy((char *) &outbuf[16], 16, s->product, ' ');
+ strpadcpy((char *) &outbuf[8], 8, s->vendor, ' ');
+
+ memset(&outbuf[32], 0, 4);
+ memcpy(&outbuf[32], s->version, MIN(4, strlen(s->version)));
+ /*
+ * We claim conformance to SPC-3, which is required for guests
+ * to ask for modern features like READ CAPACITY(16) or the
+ * block characteristics VPD page by default. Not all of SPC-3
+ * is actually implemented, but we're good enough.
+ */
+ outbuf[2] = 5;
+ outbuf[3] = 2 | 0x10; /* Format 2, HiSup */
+
+ if (buflen > 36) {
+ outbuf[4] = buflen - 5; /* Additional Length = (Len - 1) - 4 */
+ } else {
+ /* If the allocation length of CDB is too small,
+ the additional length is not adjusted */
+ outbuf[4] = 36 - 5;
+ }
+
+ /* Sync data transfer and TCQ. */
+ outbuf[7] = 0x10 | (req->bus->info->tcq ? 0x02 : 0);
+ return buflen;
+}
+
+static inline bool media_is_dvd(SCSIDiskState *s)
+{
+ uint64_t nb_sectors;
+ if (s->qdev.type != TYPE_ROM) {
+ return false;
+ }
+ if (!blk_is_inserted(s->qdev.conf.blk)) {
+ return false;
+ }
+ if (s->tray_open) {
+ return false;
+ }
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ return nb_sectors > CD_MAX_SECTORS;
+}
+
+static inline bool media_is_cd(SCSIDiskState *s)
+{
+ uint64_t nb_sectors;
+ if (s->qdev.type != TYPE_ROM) {
+ return false;
+ }
+ if (!blk_is_inserted(s->qdev.conf.blk)) {
+ return false;
+ }
+ if (s->tray_open) {
+ return false;
+ }
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ return nb_sectors <= CD_MAX_SECTORS;
+}
+
+static int scsi_read_disc_information(SCSIDiskState *s, SCSIDiskReq *r,
+ uint8_t *outbuf)
+{
+ uint8_t type = r->req.cmd.buf[1] & 7;
+
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+
+ /* Types 1/2 are only defined for Blu-Ray. */
+ if (type != 0) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return -1;
+ }
+
+ memset(outbuf, 0, 34);
+ outbuf[1] = 32;
+ outbuf[2] = 0xe; /* last session complete, disc finalized */
+ outbuf[3] = 1; /* first track on disc */
+ outbuf[4] = 1; /* # of sessions */
+ outbuf[5] = 1; /* first track of last session */
+ outbuf[6] = 1; /* last track of last session */
+ outbuf[7] = 0x20; /* unrestricted use */
+ outbuf[8] = 0x00; /* CD-ROM or DVD-ROM */
+ /* 9-10-11: most significant byte corresponding bytes 4-5-6 */
+ /* 12-23: not meaningful for CD-ROM or DVD-ROM */
+ /* 24-31: disc bar code */
+ /* 32: disc application code */
+ /* 33: number of OPC tables */
+
+ return 34;
+}
+
+static int scsi_read_dvd_structure(SCSIDiskState *s, SCSIDiskReq *r,
+ uint8_t *outbuf)
+{
+ static const int rds_caps_size[5] = {
+ [0] = 2048 + 4,
+ [1] = 4 + 4,
+ [3] = 188 + 4,
+ [4] = 2048 + 4,
+ };
+
+ uint8_t media = r->req.cmd.buf[1];
+ uint8_t layer = r->req.cmd.buf[6];
+ uint8_t format = r->req.cmd.buf[7];
+ int size = -1;
+
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ if (media != 0) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return -1;
+ }
+
+ if (format != 0xff) {
+ if (s->tray_open || !blk_is_inserted(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
+ return -1;
+ }
+ if (media_is_cd(s)) {
+ scsi_check_condition(r, SENSE_CODE(INCOMPATIBLE_FORMAT));
+ return -1;
+ }
+ if (format >= ARRAY_SIZE(rds_caps_size)) {
+ return -1;
+ }
+ size = rds_caps_size[format];
+ memset(outbuf, 0, size);
+ }
+
+ switch (format) {
+ case 0x00: {
+ /* Physical format information */
+ uint64_t nb_sectors;
+ if (layer != 0) {
+ goto fail;
+ }
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+
+ outbuf[4] = 1; /* DVD-ROM, part version 1 */
+ outbuf[5] = 0xf; /* 120mm disc, minimum rate unspecified */
+ outbuf[6] = 1; /* one layer, read-only (per MMC-2 spec) */
+ outbuf[7] = 0; /* default densities */
+
+ stl_be_p(&outbuf[12], (nb_sectors >> 2) - 1); /* end sector */
+ stl_be_p(&outbuf[16], (nb_sectors >> 2) - 1); /* l0 end sector */
+ break;
+ }
+
+ case 0x01: /* DVD copyright information, all zeros */
+ break;
+
+ case 0x03: /* BCA information - invalid field for no BCA info */
+ return -1;
+
+ case 0x04: /* DVD disc manufacturing information, all zeros */
+ break;
+
+ case 0xff: { /* List capabilities */
+ int i;
+ size = 4;
+ for (i = 0; i < ARRAY_SIZE(rds_caps_size); i++) {
+ if (!rds_caps_size[i]) {
+ continue;
+ }
+ outbuf[size] = i;
+ outbuf[size + 1] = 0x40; /* Not writable, readable */
+ stw_be_p(&outbuf[size + 2], rds_caps_size[i]);
+ size += 4;
+ }
+ break;
+ }
+
+ default:
+ return -1;
+ }
+
+ /* Size of buffer, not including 2 byte size field */
+ stw_be_p(outbuf, size - 2);
+ return size;
+
+fail:
+ return -1;
+}
+
+static int scsi_event_status_media(SCSIDiskState *s, uint8_t *outbuf)
+{
+ uint8_t event_code, media_status;
+
+ media_status = 0;
+ if (s->tray_open) {
+ media_status = MS_TRAY_OPEN;
+ } else if (blk_is_inserted(s->qdev.conf.blk)) {
+ media_status = MS_MEDIA_PRESENT;
+ }
+
+ /* Event notification descriptor */
+ event_code = MEC_NO_CHANGE;
+ if (media_status != MS_TRAY_OPEN) {
+ if (s->media_event) {
+ event_code = MEC_NEW_MEDIA;
+ s->media_event = false;
+ } else if (s->eject_request) {
+ event_code = MEC_EJECT_REQUESTED;
+ s->eject_request = false;
+ }
+ }
+
+ outbuf[0] = event_code;
+ outbuf[1] = media_status;
+
+ /* These fields are reserved, just clear them. */
+ outbuf[2] = 0;
+ outbuf[3] = 0;
+ return 4;
+}
+
+static int scsi_get_event_status_notification(SCSIDiskState *s, SCSIDiskReq *r,
+ uint8_t *outbuf)
+{
+ int size;
+ uint8_t *buf = r->req.cmd.buf;
+ uint8_t notification_class_request = buf[4];
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ if ((buf[1] & 1) == 0) {
+ /* asynchronous */
+ return -1;
+ }
+
+ size = 4;
+ outbuf[0] = outbuf[1] = 0;
+ outbuf[3] = 1 << GESN_MEDIA; /* supported events */
+ if (notification_class_request & (1 << GESN_MEDIA)) {
+ outbuf[2] = GESN_MEDIA;
+ size += scsi_event_status_media(s, &outbuf[size]);
+ } else {
+ outbuf[2] = 0x80;
+ }
+ stw_be_p(outbuf, size - 4);
+ return size;
+}
+
+static int scsi_get_configuration(SCSIDiskState *s, uint8_t *outbuf)
+{
+ int current;
+
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+
+ if (media_is_dvd(s)) {
+ current = MMC_PROFILE_DVD_ROM;
+ } else if (media_is_cd(s)) {
+ current = MMC_PROFILE_CD_ROM;
+ } else {
+ current = MMC_PROFILE_NONE;
+ }
+
+ memset(outbuf, 0, 40);
+ stl_be_p(&outbuf[0], 36); /* Bytes after the data length field */
+ stw_be_p(&outbuf[6], current);
+ /* outbuf[8] - outbuf[19]: Feature 0 - Profile list */
+ outbuf[10] = 0x03; /* persistent, current */
+ outbuf[11] = 8; /* two profiles */
+ stw_be_p(&outbuf[12], MMC_PROFILE_DVD_ROM);
+ outbuf[14] = (current == MMC_PROFILE_DVD_ROM);
+ stw_be_p(&outbuf[16], MMC_PROFILE_CD_ROM);
+ outbuf[18] = (current == MMC_PROFILE_CD_ROM);
+ /* outbuf[20] - outbuf[31]: Feature 1 - Core feature */
+ stw_be_p(&outbuf[20], 1);
+ outbuf[22] = 0x08 | 0x03; /* version 2, persistent, current */
+ outbuf[23] = 8;
+ stl_be_p(&outbuf[24], 1); /* SCSI */
+ outbuf[28] = 1; /* DBE = 1, mandatory */
+ /* outbuf[32] - outbuf[39]: Feature 3 - Removable media feature */
+ stw_be_p(&outbuf[32], 3);
+ outbuf[34] = 0x08 | 0x03; /* version 2, persistent, current */
+ outbuf[35] = 4;
+ outbuf[36] = 0x39; /* tray, load=1, eject=1, unlocked at powerup, lock=1 */
+ /* TODO: Random readable, CD read, DVD read, drive serial number,
+ power management */
+ return 40;
+}
+
+static int scsi_emulate_mechanism_status(SCSIDiskState *s, uint8_t *outbuf)
+{
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ memset(outbuf, 0, 8);
+ outbuf[5] = 1; /* CD-ROM */
+ return 8;
+}
+
+static int mode_sense_page(SCSIDiskState *s, int page, uint8_t **p_outbuf,
+ int page_control)
+{
+ static const int mode_sense_valid[0x3f] = {
+ [MODE_PAGE_HD_GEOMETRY] = (1 << TYPE_DISK),
+ [MODE_PAGE_FLEXIBLE_DISK_GEOMETRY] = (1 << TYPE_DISK),
+ [MODE_PAGE_CACHING] = (1 << TYPE_DISK) | (1 << TYPE_ROM),
+ [MODE_PAGE_R_W_ERROR] = (1 << TYPE_DISK) | (1 << TYPE_ROM),
+ [MODE_PAGE_AUDIO_CTL] = (1 << TYPE_ROM),
+ [MODE_PAGE_CAPABILITIES] = (1 << TYPE_ROM),
+ };
+
+ uint8_t *p = *p_outbuf + 2;
+ int length;
+
+ if ((mode_sense_valid[page] & (1 << s->qdev.type)) == 0) {
+ return -1;
+ }
+
+ /*
+ * If Changeable Values are requested, a mask denoting those mode parameters
+ * that are changeable shall be returned. As we currently don't support
+ * parameter changes via MODE_SELECT all bits are returned set to zero.
+ * The buffer was already menset to zero by the caller of this function.
+ *
+ * The offsets here are off by two compared to the descriptions in the
+ * SCSI specs, because those include a 2-byte header. This is unfortunate,
+ * but it is done so that offsets are consistent within our implementation
+ * of MODE SENSE and MODE SELECT. MODE SELECT has to deal with both
+ * 2-byte and 4-byte headers.
+ */
+ switch (page) {
+ case MODE_PAGE_HD_GEOMETRY:
+ length = 0x16;
+ if (page_control == 1) { /* Changeable Values */
+ break;
+ }
+ /* if a geometry hint is available, use it */
+ p[0] = (s->qdev.conf.cyls >> 16) & 0xff;
+ p[1] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[2] = s->qdev.conf.cyls & 0xff;
+ p[3] = s->qdev.conf.heads & 0xff;
+ /* Write precomp start cylinder, disabled */
+ p[4] = (s->qdev.conf.cyls >> 16) & 0xff;
+ p[5] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[6] = s->qdev.conf.cyls & 0xff;
+ /* Reduced current start cylinder, disabled */
+ p[7] = (s->qdev.conf.cyls >> 16) & 0xff;
+ p[8] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[9] = s->qdev.conf.cyls & 0xff;
+ /* Device step rate [ns], 200ns */
+ p[10] = 0;
+ p[11] = 200;
+ /* Landing zone cylinder */
+ p[12] = 0xff;
+ p[13] = 0xff;
+ p[14] = 0xff;
+ /* Medium rotation rate [rpm], 5400 rpm */
+ p[18] = (5400 >> 8) & 0xff;
+ p[19] = 5400 & 0xff;
+ break;
+
+ case MODE_PAGE_FLEXIBLE_DISK_GEOMETRY:
+ length = 0x1e;
+ if (page_control == 1) { /* Changeable Values */
+ break;
+ }
+ /* Transfer rate [kbit/s], 5Mbit/s */
+ p[0] = 5000 >> 8;
+ p[1] = 5000 & 0xff;
+ /* if a geometry hint is available, use it */
+ p[2] = s->qdev.conf.heads & 0xff;
+ p[3] = s->qdev.conf.secs & 0xff;
+ p[4] = s->qdev.blocksize >> 8;
+ p[6] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[7] = s->qdev.conf.cyls & 0xff;
+ /* Write precomp start cylinder, disabled */
+ p[8] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[9] = s->qdev.conf.cyls & 0xff;
+ /* Reduced current start cylinder, disabled */
+ p[10] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[11] = s->qdev.conf.cyls & 0xff;
+ /* Device step rate [100us], 100us */
+ p[12] = 0;
+ p[13] = 1;
+ /* Device step pulse width [us], 1us */
+ p[14] = 1;
+ /* Device head settle delay [100us], 100us */
+ p[15] = 0;
+ p[16] = 1;
+ /* Motor on delay [0.1s], 0.1s */
+ p[17] = 1;
+ /* Motor off delay [0.1s], 0.1s */
+ p[18] = 1;
+ /* Medium rotation rate [rpm], 5400 rpm */
+ p[26] = (5400 >> 8) & 0xff;
+ p[27] = 5400 & 0xff;
+ break;
+
+ case MODE_PAGE_CACHING:
+ length = 0x12;
+ if (page_control == 1 || /* Changeable Values */
+ blk_enable_write_cache(s->qdev.conf.blk)) {
+ p[0] = 4; /* WCE */
+ }
+ break;
+
+ case MODE_PAGE_R_W_ERROR:
+ length = 10;
+ if (page_control == 1) { /* Changeable Values */
+ break;
+ }
+ p[0] = 0x80; /* Automatic Write Reallocation Enabled */
+ if (s->qdev.type == TYPE_ROM) {
+ p[1] = 0x20; /* Read Retry Count */
+ }
+ break;
+
+ case MODE_PAGE_AUDIO_CTL:
+ length = 14;
+ break;
+
+ case MODE_PAGE_CAPABILITIES:
+ length = 0x14;
+ if (page_control == 1) { /* Changeable Values */
+ break;
+ }
+
+ p[0] = 0x3b; /* CD-R & CD-RW read */
+ p[1] = 0; /* Writing not supported */
+ p[2] = 0x7f; /* Audio, composite, digital out,
+ mode 2 form 1&2, multi session */
+ p[3] = 0xff; /* CD DA, DA accurate, RW supported,
+ RW corrected, C2 errors, ISRC,
+ UPC, Bar code */
+ p[4] = 0x2d | (s->tray_locked ? 2 : 0);
+ /* Locking supported, jumper present, eject, tray */
+ p[5] = 0; /* no volume & mute control, no
+ changer */
+ p[6] = (50 * 176) >> 8; /* 50x read speed */
+ p[7] = (50 * 176) & 0xff;
+ p[8] = 2 >> 8; /* Two volume levels */
+ p[9] = 2 & 0xff;
+ p[10] = 2048 >> 8; /* 2M buffer */
+ p[11] = 2048 & 0xff;
+ p[12] = (16 * 176) >> 8; /* 16x read speed current */
+ p[13] = (16 * 176) & 0xff;
+ p[16] = (16 * 176) >> 8; /* 16x write speed */
+ p[17] = (16 * 176) & 0xff;
+ p[18] = (16 * 176) >> 8; /* 16x write speed current */
+ p[19] = (16 * 176) & 0xff;
+ break;
+
+ default:
+ return -1;
+ }
+
+ assert(length < 256);
+ (*p_outbuf)[0] = page;
+ (*p_outbuf)[1] = length;
+ *p_outbuf += length + 2;
+ return length + 2;
+}
+
+static int scsi_disk_emulate_mode_sense(SCSIDiskReq *r, uint8_t *outbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint64_t nb_sectors;
+ bool dbd;
+ int page, buflen, ret, page_control;
+ uint8_t *p;
+ uint8_t dev_specific_param;
+
+ dbd = (r->req.cmd.buf[1] & 0x8) != 0;
+ page = r->req.cmd.buf[2] & 0x3f;
+ page_control = (r->req.cmd.buf[2] & 0xc0) >> 6;
+ DPRINTF("Mode Sense(%d) (page %d, xfer %zd, page_control %d)\n",
+ (r->req.cmd.buf[0] == MODE_SENSE) ? 6 : 10, page, r->req.cmd.xfer, page_control);
+ memset(outbuf, 0, r->req.cmd.xfer);
+ p = outbuf;
+
+ if (s->qdev.type == TYPE_DISK) {
+ dev_specific_param = s->features & (1 << SCSI_DISK_F_DPOFUA) ? 0x10 : 0;
+ if (blk_is_read_only(s->qdev.conf.blk)) {
+ dev_specific_param |= 0x80; /* Readonly. */
+ }
+ } else {
+ /* MMC prescribes that CD/DVD drives have no block descriptors,
+ * and defines no device-specific parameter. */
+ dev_specific_param = 0x00;
+ dbd = true;
+ }
+
+ if (r->req.cmd.buf[0] == MODE_SENSE) {
+ p[1] = 0; /* Default media type. */
+ p[2] = dev_specific_param;
+ p[3] = 0; /* Block descriptor length. */
+ p += 4;
+ } else { /* MODE_SENSE_10 */
+ p[2] = 0; /* Default media type. */
+ p[3] = dev_specific_param;
+ p[6] = p[7] = 0; /* Block descriptor length. */
+ p += 8;
+ }
+
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ if (!dbd && nb_sectors) {
+ if (r->req.cmd.buf[0] == MODE_SENSE) {
+ outbuf[3] = 8; /* Block descriptor length */
+ } else { /* MODE_SENSE_10 */
+ outbuf[7] = 8; /* Block descriptor length */
+ }
+ nb_sectors /= (s->qdev.blocksize / 512);
+ if (nb_sectors > 0xffffff) {
+ nb_sectors = 0;
+ }
+ p[0] = 0; /* media density code */
+ p[1] = (nb_sectors >> 16) & 0xff;
+ p[2] = (nb_sectors >> 8) & 0xff;
+ p[3] = nb_sectors & 0xff;
+ p[4] = 0; /* reserved */
+ p[5] = 0; /* bytes 5-7 are the sector size in bytes */
+ p[6] = s->qdev.blocksize >> 8;
+ p[7] = 0;
+ p += 8;
+ }
+
+ if (page_control == 3) {
+ /* Saved Values */
+ scsi_check_condition(r, SENSE_CODE(SAVING_PARAMS_NOT_SUPPORTED));
+ return -1;
+ }
+
+ if (page == 0x3f) {
+ for (page = 0; page <= 0x3e; page++) {
+ mode_sense_page(s, page, &p, page_control);
+ }
+ } else {
+ ret = mode_sense_page(s, page, &p, page_control);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ buflen = p - outbuf;
+ /*
+ * The mode data length field specifies the length in bytes of the
+ * following data that is available to be transferred. The mode data
+ * length does not include itself.
+ */
+ if (r->req.cmd.buf[0] == MODE_SENSE) {
+ outbuf[0] = buflen - 1;
+ } else { /* MODE_SENSE_10 */
+ outbuf[0] = ((buflen - 2) >> 8) & 0xff;
+ outbuf[1] = (buflen - 2) & 0xff;
+ }
+ return buflen;
+}
+
+static int scsi_disk_emulate_read_toc(SCSIRequest *req, uint8_t *outbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ int start_track, format, msf, toclen;
+ uint64_t nb_sectors;
+
+ msf = req->cmd.buf[1] & 2;
+ format = req->cmd.buf[2] & 0xf;
+ start_track = req->cmd.buf[6];
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ DPRINTF("Read TOC (track %d format %d msf %d)\n", start_track, format, msf >> 1);
+ nb_sectors /= s->qdev.blocksize / 512;
+ switch (format) {
+ case 0:
+ toclen = cdrom_read_toc(nb_sectors, outbuf, msf, start_track);
+ break;
+ case 1:
+ /* multi session : only a single session defined */
+ toclen = 12;
+ memset(outbuf, 0, 12);
+ outbuf[1] = 0x0a;
+ outbuf[2] = 0x01;
+ outbuf[3] = 0x01;
+ break;
+ case 2:
+ toclen = cdrom_read_toc_raw(nb_sectors, outbuf, msf, start_track);
+ break;
+ default:
+ return -1;
+ }
+ return toclen;
+}
+
+static int scsi_disk_emulate_start_stop(SCSIDiskReq *r)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ bool start = req->cmd.buf[4] & 1;
+ bool loej = req->cmd.buf[4] & 2; /* load on start, eject on !start */
+ int pwrcnd = req->cmd.buf[4] & 0xf0;
+
+ if (pwrcnd) {
+ /* eject/load only happens for power condition == 0 */
+ return 0;
+ }
+
+ if ((s->features & (1 << SCSI_DISK_F_REMOVABLE)) && loej) {
+ if (!start && !s->tray_open && s->tray_locked) {
+ scsi_check_condition(r,
+ blk_is_inserted(s->qdev.conf.blk)
+ ? SENSE_CODE(ILLEGAL_REQ_REMOVAL_PREVENTED)
+ : SENSE_CODE(NOT_READY_REMOVAL_PREVENTED));
+ return -1;
+ }
+
+ if (s->tray_open != !start) {
+ blk_eject(s->qdev.conf.blk, !start);
+ s->tray_open = !start;
+ }
+ }
+ return 0;
+}
+
+static void scsi_disk_emulate_read_data(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ int buflen = r->iov.iov_len;
+
+ if (buflen) {
+ DPRINTF("Read buf_len=%d\n", buflen);
+ r->iov.iov_len = 0;
+ r->started = true;
+ scsi_req_data(&r->req, buflen);
+ return;
+ }
+
+ /* This also clears the sense buffer for REQUEST SENSE. */
+ scsi_req_complete(&r->req, GOOD);
+}
+
+static int scsi_disk_check_mode_select(SCSIDiskState *s, int page,
+ uint8_t *inbuf, int inlen)
+{
+ uint8_t mode_current[SCSI_MAX_MODE_LEN];
+ uint8_t mode_changeable[SCSI_MAX_MODE_LEN];
+ uint8_t *p;
+ int len, expected_len, changeable_len, i;
+
+ /* The input buffer does not include the page header, so it is
+ * off by 2 bytes.
+ */
+ expected_len = inlen + 2;
+ if (expected_len > SCSI_MAX_MODE_LEN) {
+ return -1;
+ }
+
+ p = mode_current;
+ memset(mode_current, 0, inlen + 2);
+ len = mode_sense_page(s, page, &p, 0);
+ if (len < 0 || len != expected_len) {
+ return -1;
+ }
+
+ p = mode_changeable;
+ memset(mode_changeable, 0, inlen + 2);
+ changeable_len = mode_sense_page(s, page, &p, 1);
+ assert(changeable_len == len);
+
+ /* Check that unchangeable bits are the same as what MODE SENSE
+ * would return.
+ */
+ for (i = 2; i < len; i++) {
+ if (((mode_current[i] ^ inbuf[i - 2]) & ~mode_changeable[i]) != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void scsi_disk_apply_mode_select(SCSIDiskState *s, int page, uint8_t *p)
+{
+ switch (page) {
+ case MODE_PAGE_CACHING:
+ blk_set_enable_write_cache(s->qdev.conf.blk, (p[0] & 4) != 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int mode_select_pages(SCSIDiskReq *r, uint8_t *p, int len, bool change)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ while (len > 0) {
+ int page, subpage, page_len;
+
+ /* Parse both possible formats for the mode page headers. */
+ page = p[0] & 0x3f;
+ if (p[0] & 0x40) {
+ if (len < 4) {
+ goto invalid_param_len;
+ }
+ subpage = p[1];
+ page_len = lduw_be_p(&p[2]);
+ p += 4;
+ len -= 4;
+ } else {
+ if (len < 2) {
+ goto invalid_param_len;
+ }
+ subpage = 0;
+ page_len = p[1];
+ p += 2;
+ len -= 2;
+ }
+
+ if (subpage) {
+ goto invalid_param;
+ }
+ if (page_len > len) {
+ goto invalid_param_len;
+ }
+
+ if (!change) {
+ if (scsi_disk_check_mode_select(s, page, p, page_len) < 0) {
+ goto invalid_param;
+ }
+ } else {
+ scsi_disk_apply_mode_select(s, page, p);
+ }
+
+ p += page_len;
+ len -= page_len;
+ }
+ return 0;
+
+invalid_param:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM));
+ return -1;
+
+invalid_param_len:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return -1;
+}
+
+static void scsi_disk_emulate_mode_select(SCSIDiskReq *r, uint8_t *inbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint8_t *p = inbuf;
+ int cmd = r->req.cmd.buf[0];
+ int len = r->req.cmd.xfer;
+ int hdr_len = (cmd == MODE_SELECT ? 4 : 8);
+ int bd_len;
+ int pass;
+
+ /* We only support PF=1, SP=0. */
+ if ((r->req.cmd.buf[1] & 0x11) != 0x10) {
+ goto invalid_field;
+ }
+
+ if (len < hdr_len) {
+ goto invalid_param_len;
+ }
+
+ bd_len = (cmd == MODE_SELECT ? p[3] : lduw_be_p(&p[6]));
+ len -= hdr_len;
+ p += hdr_len;
+ if (len < bd_len) {
+ goto invalid_param_len;
+ }
+ if (bd_len != 0 && bd_len != 8) {
+ goto invalid_param;
+ }
+
+ len -= bd_len;
+ p += bd_len;
+
+ /* Ensure no change is made if there is an error! */
+ for (pass = 0; pass < 2; pass++) {
+ if (mode_select_pages(r, p, len, pass == 1) < 0) {
+ assert(pass == 0);
+ return;
+ }
+ }
+ if (!blk_enable_write_cache(s->qdev.conf.blk)) {
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ r->req.aiocb = blk_aio_flush(s->qdev.conf.blk, scsi_aio_complete, r);
+ return;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+ return;
+
+invalid_param:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM));
+ return;
+
+invalid_param_len:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return;
+
+invalid_field:
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+}
+
+static inline bool check_lba_range(SCSIDiskState *s,
+ uint64_t sector_num, uint32_t nb_sectors)
+{
+ /*
+ * The first line tests that no overflow happens when computing the last
+ * sector. The second line tests that the last accessed sector is in
+ * range.
+ *
+ * Careful, the computations should not underflow for nb_sectors == 0,
+ * and a 0-block read to the first LBA beyond the end of device is
+ * valid.
+ */
+ return (sector_num <= sector_num + nb_sectors &&
+ sector_num + nb_sectors <= s->qdev.max_lba + 1);
+}
+
+typedef struct UnmapCBData {
+ SCSIDiskReq *r;
+ uint8_t *inbuf;
+ int count;
+} UnmapCBData;
+
+static void scsi_unmap_complete(void *opaque, int ret)
+{
+ UnmapCBData *data = opaque;
+ SCSIDiskReq *r = data->r;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint64_t sector_num;
+ uint32_t nb_sectors;
+
+ r->req.aiocb = NULL;
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+
+ if (ret < 0) {
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
+ }
+ }
+
+ if (data->count > 0) {
+ sector_num = ldq_be_p(&data->inbuf[0]);
+ nb_sectors = ldl_be_p(&data->inbuf[8]) & 0xffffffffULL;
+ if (!check_lba_range(s, sector_num, nb_sectors)) {
+ scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE));
+ goto done;
+ }
+
+ r->req.aiocb = blk_aio_discard(s->qdev.conf.blk,
+ sector_num * (s->qdev.blocksize / 512),
+ nb_sectors * (s->qdev.blocksize / 512),
+ scsi_unmap_complete, data);
+ data->count--;
+ data->inbuf += 16;
+ return;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+
+done:
+ scsi_req_unref(&r->req);
+ g_free(data);
+}
+
+static void scsi_disk_emulate_unmap(SCSIDiskReq *r, uint8_t *inbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint8_t *p = inbuf;
+ int len = r->req.cmd.xfer;
+ UnmapCBData *data;
+
+ /* Reject ANCHOR=1. */
+ if (r->req.cmd.buf[1] & 0x1) {
+ goto invalid_field;
+ }
+
+ if (len < 8) {
+ goto invalid_param_len;
+ }
+ if (len < lduw_be_p(&p[0]) + 2) {
+ goto invalid_param_len;
+ }
+ if (len < lduw_be_p(&p[2]) + 8) {
+ goto invalid_param_len;
+ }
+ if (lduw_be_p(&p[2]) & 15) {
+ goto invalid_param_len;
+ }
+
+ if (blk_is_read_only(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED));
+ return;
+ }
+
+ data = g_new0(UnmapCBData, 1);
+ data->r = r;
+ data->inbuf = &p[8];
+ data->count = lduw_be_p(&p[2]) >> 4;
+
+ /* The matching unref is in scsi_unmap_complete, before data is freed. */
+ scsi_req_ref(&r->req);
+ scsi_unmap_complete(data, 0);
+ return;
+
+invalid_param_len:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return;
+
+invalid_field:
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+}
+
+typedef struct WriteSameCBData {
+ SCSIDiskReq *r;
+ int64_t sector;
+ int nb_sectors;
+ QEMUIOVector qiov;
+ struct iovec iov;
+} WriteSameCBData;
+
+static void scsi_write_same_complete(void *opaque, int ret)
+{
+ WriteSameCBData *data = opaque;
+ SCSIDiskReq *r = data->r;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+
+ if (ret < 0) {
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
+ }
+ }
+
+ data->nb_sectors -= data->iov.iov_len / 512;
+ data->sector += data->iov.iov_len / 512;
+ data->iov.iov_len = MIN(data->nb_sectors * 512, data->iov.iov_len);
+ if (data->iov.iov_len) {
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ data->iov.iov_len, BLOCK_ACCT_WRITE);
+ /* blk_aio_write doesn't like the qiov size being different from
+ * nb_sectors, make sure they match.
+ */
+ qemu_iovec_init_external(&data->qiov, &data->iov, 1);
+ r->req.aiocb = blk_aio_writev(s->qdev.conf.blk, data->sector,
+ &data->qiov, data->iov.iov_len / 512,
+ scsi_write_same_complete, data);
+ return;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+
+done:
+ scsi_req_unref(&r->req);
+ qemu_vfree(data->iov.iov_base);
+ g_free(data);
+}
+
+static void scsi_disk_emulate_write_same(SCSIDiskReq *r, uint8_t *inbuf)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ uint32_t nb_sectors = scsi_data_cdb_xfer(r->req.cmd.buf);
+ WriteSameCBData *data;
+ uint8_t *buf;
+ int i;
+
+ /* Fail if PBDATA=1 or LBDATA=1 or ANCHOR=1. */
+ if (nb_sectors == 0 || (req->cmd.buf[1] & 0x16)) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return;
+ }
+
+ if (blk_is_read_only(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED));
+ return;
+ }
+ if (!check_lba_range(s, r->req.cmd.lba, nb_sectors)) {
+ scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE));
+ return;
+ }
+
+ if (buffer_is_zero(inbuf, s->qdev.blocksize)) {
+ int flags = (req->cmd.buf[1] & 0x8) ? BDRV_REQ_MAY_UNMAP : 0;
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ nb_sectors * s->qdev.blocksize,
+ BLOCK_ACCT_WRITE);
+ r->req.aiocb = blk_aio_write_zeroes(s->qdev.conf.blk,
+ r->req.cmd.lba * (s->qdev.blocksize / 512),
+ nb_sectors * (s->qdev.blocksize / 512),
+ flags, scsi_aio_complete, r);
+ return;
+ }
+
+ data = g_new0(WriteSameCBData, 1);
+ data->r = r;
+ data->sector = r->req.cmd.lba * (s->qdev.blocksize / 512);
+ data->nb_sectors = nb_sectors * (s->qdev.blocksize / 512);
+ data->iov.iov_len = MIN(data->nb_sectors * 512, SCSI_WRITE_SAME_MAX);
+ data->iov.iov_base = buf = blk_blockalign(s->qdev.conf.blk,
+ data->iov.iov_len);
+ qemu_iovec_init_external(&data->qiov, &data->iov, 1);
+
+ for (i = 0; i < data->iov.iov_len; i += s->qdev.blocksize) {
+ memcpy(&buf[i], inbuf, s->qdev.blocksize);
+ }
+
+ scsi_req_ref(&r->req);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ data->iov.iov_len, BLOCK_ACCT_WRITE);
+ r->req.aiocb = blk_aio_writev(s->qdev.conf.blk, data->sector,
+ &data->qiov, data->iov.iov_len / 512,
+ scsi_write_same_complete, data);
+}
+
+static void scsi_disk_emulate_write_data(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ if (r->iov.iov_len) {
+ int buflen = r->iov.iov_len;
+ DPRINTF("Write buf_len=%d\n", buflen);
+ r->iov.iov_len = 0;
+ scsi_req_data(&r->req, buflen);
+ return;
+ }
+
+ switch (req->cmd.buf[0]) {
+ case MODE_SELECT:
+ case MODE_SELECT_10:
+ /* This also clears the sense buffer for REQUEST SENSE. */
+ scsi_disk_emulate_mode_select(r, r->iov.iov_base);
+ break;
+
+ case UNMAP:
+ scsi_disk_emulate_unmap(r, r->iov.iov_base);
+ break;
+
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ if (r->req.status == -1) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ }
+ break;
+
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ scsi_disk_emulate_write_same(r, r->iov.iov_base);
+ break;
+
+ default:
+ abort();
+ }
+}
+
+static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ uint64_t nb_sectors;
+ uint8_t *outbuf;
+ int buflen;
+
+ switch (req->cmd.buf[0]) {
+ case INQUIRY:
+ case MODE_SENSE:
+ case MODE_SENSE_10:
+ case RESERVE:
+ case RESERVE_10:
+ case RELEASE:
+ case RELEASE_10:
+ case START_STOP:
+ case ALLOW_MEDIUM_REMOVAL:
+ case GET_CONFIGURATION:
+ case GET_EVENT_STATUS_NOTIFICATION:
+ case MECHANISM_STATUS:
+ case REQUEST_SENSE:
+ break;
+
+ default:
+ if (s->tray_open || !blk_is_inserted(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
+ return 0;
+ }
+ break;
+ }
+
+ /*
+ * FIXME: we shouldn't return anything bigger than 4k, but the code
+ * requires the buffer to be as big as req->cmd.xfer in several
+ * places. So, do not allow CDBs with a very large ALLOCATION
+ * LENGTH. The real fix would be to modify scsi_read_data and
+ * dma_buf_read, so that they return data beyond the buflen
+ * as all zeros.
+ */
+ if (req->cmd.xfer > 65536) {
+ goto illegal_request;
+ }
+ r->buflen = MAX(4096, req->cmd.xfer);
+
+ if (!r->iov.iov_base) {
+ r->iov.iov_base = blk_blockalign(s->qdev.conf.blk, r->buflen);
+ }
+
+ buflen = req->cmd.xfer;
+ outbuf = r->iov.iov_base;
+ memset(outbuf, 0, r->buflen);
+ switch (req->cmd.buf[0]) {
+ case TEST_UNIT_READY:
+ assert(!s->tray_open && blk_is_inserted(s->qdev.conf.blk));
+ break;
+ case INQUIRY:
+ buflen = scsi_disk_emulate_inquiry(req, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case MODE_SENSE:
+ case MODE_SENSE_10:
+ buflen = scsi_disk_emulate_mode_sense(r, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case READ_TOC:
+ buflen = scsi_disk_emulate_read_toc(req, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case RESERVE:
+ if (req->cmd.buf[1] & 1) {
+ goto illegal_request;
+ }
+ break;
+ case RESERVE_10:
+ if (req->cmd.buf[1] & 3) {
+ goto illegal_request;
+ }
+ break;
+ case RELEASE:
+ if (req->cmd.buf[1] & 1) {
+ goto illegal_request;
+ }
+ break;
+ case RELEASE_10:
+ if (req->cmd.buf[1] & 3) {
+ goto illegal_request;
+ }
+ break;
+ case START_STOP:
+ if (scsi_disk_emulate_start_stop(r) < 0) {
+ return 0;
+ }
+ break;
+ case ALLOW_MEDIUM_REMOVAL:
+ s->tray_locked = req->cmd.buf[4] & 1;
+ blk_lock_medium(s->qdev.conf.blk, req->cmd.buf[4] & 1);
+ break;
+ case READ_CAPACITY_10:
+ /* The normal LEN field for this command is zero. */
+ memset(outbuf, 0, 8);
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ if (!nb_sectors) {
+ scsi_check_condition(r, SENSE_CODE(LUN_NOT_READY));
+ return 0;
+ }
+ if ((req->cmd.buf[8] & 1) == 0 && req->cmd.lba) {
+ goto illegal_request;
+ }
+ nb_sectors /= s->qdev.blocksize / 512;
+ /* Returned value is the address of the last sector. */
+ nb_sectors--;
+ /* Remember the new size for read/write sanity checking. */
+ s->qdev.max_lba = nb_sectors;
+ /* Clip to 2TB, instead of returning capacity modulo 2TB. */
+ if (nb_sectors > UINT32_MAX) {
+ nb_sectors = UINT32_MAX;
+ }
+ outbuf[0] = (nb_sectors >> 24) & 0xff;
+ outbuf[1] = (nb_sectors >> 16) & 0xff;
+ outbuf[2] = (nb_sectors >> 8) & 0xff;
+ outbuf[3] = nb_sectors & 0xff;
+ outbuf[4] = 0;
+ outbuf[5] = 0;
+ outbuf[6] = s->qdev.blocksize >> 8;
+ outbuf[7] = 0;
+ break;
+ case REQUEST_SENSE:
+ /* Just return "NO SENSE". */
+ buflen = scsi_build_sense(NULL, 0, outbuf, r->buflen,
+ (req->cmd.buf[1] & 1) == 0);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case MECHANISM_STATUS:
+ buflen = scsi_emulate_mechanism_status(s, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case GET_CONFIGURATION:
+ buflen = scsi_get_configuration(s, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case GET_EVENT_STATUS_NOTIFICATION:
+ buflen = scsi_get_event_status_notification(s, r, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case READ_DISC_INFORMATION:
+ buflen = scsi_read_disc_information(s, r, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case READ_DVD_STRUCTURE:
+ buflen = scsi_read_dvd_structure(s, r, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case SERVICE_ACTION_IN_16:
+ /* Service Action In subcommands. */
+ if ((req->cmd.buf[1] & 31) == SAI_READ_CAPACITY_16) {
+ DPRINTF("SAI READ CAPACITY(16)\n");
+ memset(outbuf, 0, req->cmd.xfer);
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ if (!nb_sectors) {
+ scsi_check_condition(r, SENSE_CODE(LUN_NOT_READY));
+ return 0;
+ }
+ if ((req->cmd.buf[14] & 1) == 0 && req->cmd.lba) {
+ goto illegal_request;
+ }
+ nb_sectors /= s->qdev.blocksize / 512;
+ /* Returned value is the address of the last sector. */
+ nb_sectors--;
+ /* Remember the new size for read/write sanity checking. */
+ s->qdev.max_lba = nb_sectors;
+ outbuf[0] = (nb_sectors >> 56) & 0xff;
+ outbuf[1] = (nb_sectors >> 48) & 0xff;
+ outbuf[2] = (nb_sectors >> 40) & 0xff;
+ outbuf[3] = (nb_sectors >> 32) & 0xff;
+ outbuf[4] = (nb_sectors >> 24) & 0xff;
+ outbuf[5] = (nb_sectors >> 16) & 0xff;
+ outbuf[6] = (nb_sectors >> 8) & 0xff;
+ outbuf[7] = nb_sectors & 0xff;
+ outbuf[8] = 0;
+ outbuf[9] = 0;
+ outbuf[10] = s->qdev.blocksize >> 8;
+ outbuf[11] = 0;
+ outbuf[12] = 0;
+ outbuf[13] = get_physical_block_exp(&s->qdev.conf);
+
+ /* set TPE bit if the format supports discard */
+ if (s->qdev.conf.discard_granularity) {
+ outbuf[14] = 0x80;
+ }
+
+ /* Protection, exponent and lowest lba field left blank. */
+ break;
+ }
+ DPRINTF("Unsupported Service Action In\n");
+ goto illegal_request;
+ case SYNCHRONIZE_CACHE:
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ r->req.aiocb = blk_aio_flush(s->qdev.conf.blk, scsi_aio_complete, r);
+ return 0;
+ case SEEK_10:
+ DPRINTF("Seek(10) (sector %" PRId64 ")\n", r->req.cmd.lba);
+ if (r->req.cmd.lba > s->qdev.max_lba) {
+ goto illegal_lba;
+ }
+ break;
+ case MODE_SELECT:
+ DPRINTF("Mode Select(6) (len %lu)\n", (long)r->req.cmd.xfer);
+ break;
+ case MODE_SELECT_10:
+ DPRINTF("Mode Select(10) (len %lu)\n", (long)r->req.cmd.xfer);
+ break;
+ case UNMAP:
+ DPRINTF("Unmap (len %lu)\n", (long)r->req.cmd.xfer);
+ break;
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ DPRINTF("Verify (bytchk %d)\n", (req->cmd.buf[1] >> 1) & 3);
+ if (req->cmd.buf[1] & 6) {
+ goto illegal_request;
+ }
+ break;
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ DPRINTF("WRITE SAME %d (len %lu)\n",
+ req->cmd.buf[0] == WRITE_SAME_10 ? 10 : 16,
+ (long)r->req.cmd.xfer);
+ break;
+ default:
+ DPRINTF("Unknown SCSI command (%2.2x=%s)\n", buf[0],
+ scsi_command_name(buf[0]));
+ scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE));
+ return 0;
+ }
+ assert(!r->req.aiocb);
+ r->iov.iov_len = MIN(r->buflen, req->cmd.xfer);
+ if (r->iov.iov_len == 0) {
+ scsi_req_complete(&r->req, GOOD);
+ }
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ assert(r->iov.iov_len == req->cmd.xfer);
+ return -r->iov.iov_len;
+ } else {
+ return r->iov.iov_len;
+ }
+
+illegal_request:
+ if (r->req.status == -1) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ }
+ return 0;
+
+illegal_lba:
+ scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE));
+ return 0;
+}
+
+/* Execute a scsi command. Returns the length of the data expected by the
+ command. This will be Positive for data transfers from the device
+ (eg. disk reads), negative for transfers to the device (eg. disk writes),
+ and zero if the command does not transfer any data. */
+
+static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ uint32_t len;
+ uint8_t command;
+
+ command = buf[0];
+
+ if (s->tray_open || !blk_is_inserted(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
+ return 0;
+ }
+
+ len = scsi_data_cdb_xfer(r->req.cmd.buf);
+ switch (command) {
+ case READ_6:
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ DPRINTF("Read (sector %" PRId64 ", count %u)\n", r->req.cmd.lba, len);
+ if (r->req.cmd.buf[1] & 0xe0) {
+ goto illegal_request;
+ }
+ if (!check_lba_range(s, r->req.cmd.lba, len)) {
+ goto illegal_lba;
+ }
+ r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512);
+ r->sector_count = len * (s->qdev.blocksize / 512);
+ break;
+ case WRITE_6:
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ case WRITE_VERIFY_10:
+ case WRITE_VERIFY_12:
+ case WRITE_VERIFY_16:
+ if (blk_is_read_only(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED));
+ return 0;
+ }
+ DPRINTF("Write %s(sector %" PRId64 ", count %u)\n",
+ (command & 0xe) == 0xe ? "And Verify " : "",
+ r->req.cmd.lba, len);
+ if (r->req.cmd.buf[1] & 0xe0) {
+ goto illegal_request;
+ }
+ if (!check_lba_range(s, r->req.cmd.lba, len)) {
+ goto illegal_lba;
+ }
+ r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512);
+ r->sector_count = len * (s->qdev.blocksize / 512);
+ break;
+ default:
+ abort();
+ illegal_request:
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return 0;
+ illegal_lba:
+ scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE));
+ return 0;
+ }
+ if (r->sector_count == 0) {
+ scsi_req_complete(&r->req, GOOD);
+ }
+ assert(r->iov.iov_len == 0);
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ return -r->sector_count * 512;
+ } else {
+ return r->sector_count * 512;
+ }
+}
+
+static void scsi_disk_reset(DeviceState *dev)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev.qdev, dev);
+ uint64_t nb_sectors;
+
+ scsi_device_purge_requests(&s->qdev, SENSE_CODE(RESET));
+
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ nb_sectors /= s->qdev.blocksize / 512;
+ if (nb_sectors) {
+ nb_sectors--;
+ }
+ s->qdev.max_lba = nb_sectors;
+ /* reset tray statuses */
+ s->tray_locked = 0;
+ s->tray_open = 0;
+}
+
+static void scsi_disk_resize_cb(void *opaque)
+{
+ SCSIDiskState *s = opaque;
+
+ /* SPC lists this sense code as available only for
+ * direct-access devices.
+ */
+ if (s->qdev.type == TYPE_DISK) {
+ scsi_device_report_change(&s->qdev, SENSE_CODE(CAPACITY_CHANGED));
+ }
+}
+
+static void scsi_cd_change_media_cb(void *opaque, bool load)
+{
+ SCSIDiskState *s = opaque;
+
+ /*
+ * When a CD gets changed, we have to report an ejected state and
+ * then a loaded state to guests so that they detect tray
+ * open/close and media change events. Guests that do not use
+ * GET_EVENT_STATUS_NOTIFICATION to detect such tray open/close
+ * states rely on this behavior.
+ *
+ * media_changed governs the state machine used for unit attention
+ * report. media_event is used by GET EVENT STATUS NOTIFICATION.
+ */
+ s->media_changed = load;
+ s->tray_open = !load;
+ scsi_device_set_ua(&s->qdev, SENSE_CODE(UNIT_ATTENTION_NO_MEDIUM));
+ s->media_event = true;
+ s->eject_request = false;
+}
+
+static void scsi_cd_eject_request_cb(void *opaque, bool force)
+{
+ SCSIDiskState *s = opaque;
+
+ s->eject_request = true;
+ if (force) {
+ s->tray_locked = false;
+ }
+}
+
+static bool scsi_cd_is_tray_open(void *opaque)
+{
+ return ((SCSIDiskState *)opaque)->tray_open;
+}
+
+static bool scsi_cd_is_medium_locked(void *opaque)
+{
+ return ((SCSIDiskState *)opaque)->tray_locked;
+}
+
+static const BlockDevOps scsi_disk_removable_block_ops = {
+ .change_media_cb = scsi_cd_change_media_cb,
+ .eject_request_cb = scsi_cd_eject_request_cb,
+ .is_tray_open = scsi_cd_is_tray_open,
+ .is_medium_locked = scsi_cd_is_medium_locked,
+
+ .resize_cb = scsi_disk_resize_cb,
+};
+
+static const BlockDevOps scsi_disk_block_ops = {
+ .resize_cb = scsi_disk_resize_cb,
+};
+
+static void scsi_disk_unit_attention_reported(SCSIDevice *dev)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ if (s->media_changed) {
+ s->media_changed = false;
+ scsi_device_set_ua(&s->qdev, SENSE_CODE(MEDIUM_CHANGED));
+ }
+}
+
+static void scsi_realize(SCSIDevice *dev, Error **errp)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ Error *err = NULL;
+
+ if (!s->qdev.conf.blk) {
+ error_setg(errp, "drive property not set");
+ return;
+ }
+
+ if (!(s->features & (1 << SCSI_DISK_F_REMOVABLE)) &&
+ !blk_is_inserted(s->qdev.conf.blk)) {
+ error_setg(errp, "Device needs media, but drive is empty");
+ return;
+ }
+
+ blkconf_serial(&s->qdev.conf, &s->serial);
+ blkconf_blocksizes(&s->qdev.conf);
+ if (dev->type == TYPE_DISK) {
+ blkconf_geometry(&dev->conf, NULL, 65535, 255, 255, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ }
+
+ if (s->qdev.conf.discard_granularity == -1) {
+ s->qdev.conf.discard_granularity =
+ MAX(s->qdev.conf.logical_block_size, DEFAULT_DISCARD_GRANULARITY);
+ }
+
+ if (!s->version) {
+ s->version = g_strdup(qemu_get_version());
+ }
+ if (!s->vendor) {
+ s->vendor = g_strdup("QEMU");
+ }
+
+ if (blk_is_sg(s->qdev.conf.blk)) {
+ error_setg(errp, "unwanted /dev/sg*");
+ return;
+ }
+
+ if ((s->features & (1 << SCSI_DISK_F_REMOVABLE)) &&
+ !(s->features & (1 << SCSI_DISK_F_NO_REMOVABLE_DEVOPS))) {
+ blk_set_dev_ops(s->qdev.conf.blk, &scsi_disk_removable_block_ops, s);
+ } else {
+ blk_set_dev_ops(s->qdev.conf.blk, &scsi_disk_block_ops, s);
+ }
+ blk_set_guest_block_size(s->qdev.conf.blk, s->qdev.blocksize);
+
+ blk_iostatus_enable(s->qdev.conf.blk);
+}
+
+static void scsi_hd_realize(SCSIDevice *dev, Error **errp)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ /* can happen for devices without drive. The error message for missing
+ * backend will be issued in scsi_realize
+ */
+ if (s->qdev.conf.blk) {
+ blkconf_blocksizes(&s->qdev.conf);
+ }
+ s->qdev.blocksize = s->qdev.conf.logical_block_size;
+ s->qdev.type = TYPE_DISK;
+ if (!s->product) {
+ s->product = g_strdup("QEMU HARDDISK");
+ }
+ scsi_realize(&s->qdev, errp);
+}
+
+static void scsi_cd_realize(SCSIDevice *dev, Error **errp)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ s->qdev.blocksize = 2048;
+ s->qdev.type = TYPE_ROM;
+ s->features |= 1 << SCSI_DISK_F_REMOVABLE;
+ if (!s->product) {
+ s->product = g_strdup("QEMU CD-ROM");
+ }
+ scsi_realize(&s->qdev, errp);
+}
+
+static void scsi_disk_realize(SCSIDevice *dev, Error **errp)
+{
+ DriveInfo *dinfo;
+ Error *local_err = NULL;
+
+ if (!dev->conf.blk) {
+ scsi_realize(dev, &local_err);
+ assert(local_err);
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ dinfo = blk_legacy_dinfo(dev->conf.blk);
+ if (dinfo && dinfo->media_cd) {
+ scsi_cd_realize(dev, errp);
+ } else {
+ scsi_hd_realize(dev, errp);
+ }
+}
+
+static const SCSIReqOps scsi_disk_emulate_reqops = {
+ .size = sizeof(SCSIDiskReq),
+ .free_req = scsi_free_request,
+ .send_command = scsi_disk_emulate_command,
+ .read_data = scsi_disk_emulate_read_data,
+ .write_data = scsi_disk_emulate_write_data,
+ .get_buf = scsi_get_buf,
+};
+
+static const SCSIReqOps scsi_disk_dma_reqops = {
+ .size = sizeof(SCSIDiskReq),
+ .free_req = scsi_free_request,
+ .send_command = scsi_disk_dma_command,
+ .read_data = scsi_read_data,
+ .write_data = scsi_write_data,
+ .get_buf = scsi_get_buf,
+ .load_request = scsi_disk_load_request,
+ .save_request = scsi_disk_save_request,
+};
+
+static const SCSIReqOps *const scsi_disk_reqops_dispatch[256] = {
+ [TEST_UNIT_READY] = &scsi_disk_emulate_reqops,
+ [INQUIRY] = &scsi_disk_emulate_reqops,
+ [MODE_SENSE] = &scsi_disk_emulate_reqops,
+ [MODE_SENSE_10] = &scsi_disk_emulate_reqops,
+ [START_STOP] = &scsi_disk_emulate_reqops,
+ [ALLOW_MEDIUM_REMOVAL] = &scsi_disk_emulate_reqops,
+ [READ_CAPACITY_10] = &scsi_disk_emulate_reqops,
+ [READ_TOC] = &scsi_disk_emulate_reqops,
+ [READ_DVD_STRUCTURE] = &scsi_disk_emulate_reqops,
+ [READ_DISC_INFORMATION] = &scsi_disk_emulate_reqops,
+ [GET_CONFIGURATION] = &scsi_disk_emulate_reqops,
+ [GET_EVENT_STATUS_NOTIFICATION] = &scsi_disk_emulate_reqops,
+ [MECHANISM_STATUS] = &scsi_disk_emulate_reqops,
+ [SERVICE_ACTION_IN_16] = &scsi_disk_emulate_reqops,
+ [REQUEST_SENSE] = &scsi_disk_emulate_reqops,
+ [SYNCHRONIZE_CACHE] = &scsi_disk_emulate_reqops,
+ [SEEK_10] = &scsi_disk_emulate_reqops,
+ [MODE_SELECT] = &scsi_disk_emulate_reqops,
+ [MODE_SELECT_10] = &scsi_disk_emulate_reqops,
+ [UNMAP] = &scsi_disk_emulate_reqops,
+ [WRITE_SAME_10] = &scsi_disk_emulate_reqops,
+ [WRITE_SAME_16] = &scsi_disk_emulate_reqops,
+ [VERIFY_10] = &scsi_disk_emulate_reqops,
+ [VERIFY_12] = &scsi_disk_emulate_reqops,
+ [VERIFY_16] = &scsi_disk_emulate_reqops,
+
+ [READ_6] = &scsi_disk_dma_reqops,
+ [READ_10] = &scsi_disk_dma_reqops,
+ [READ_12] = &scsi_disk_dma_reqops,
+ [READ_16] = &scsi_disk_dma_reqops,
+ [WRITE_6] = &scsi_disk_dma_reqops,
+ [WRITE_10] = &scsi_disk_dma_reqops,
+ [WRITE_12] = &scsi_disk_dma_reqops,
+ [WRITE_16] = &scsi_disk_dma_reqops,
+ [WRITE_VERIFY_10] = &scsi_disk_dma_reqops,
+ [WRITE_VERIFY_12] = &scsi_disk_dma_reqops,
+ [WRITE_VERIFY_16] = &scsi_disk_dma_reqops,
+};
+
+static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun,
+ uint8_t *buf, void *hba_private)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
+ SCSIRequest *req;
+ const SCSIReqOps *ops;
+ uint8_t command;
+
+ command = buf[0];
+ ops = scsi_disk_reqops_dispatch[command];
+ if (!ops) {
+ ops = &scsi_disk_emulate_reqops;
+ }
+ req = scsi_req_alloc(ops, &s->qdev, tag, lun, hba_private);
+
+#ifdef DEBUG_SCSI
+ DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, tag, buf[0]);
+ {
+ int i;
+ for (i = 1; i < scsi_cdb_length(buf); i++) {
+ printf(" 0x%02x", buf[i]);
+ }
+ printf("\n");
+ }
+#endif
+
+ return req;
+}
+
+#ifdef __linux__
+static int get_device_type(SCSIDiskState *s)
+{
+ uint8_t cmd[16];
+ uint8_t buf[36];
+ uint8_t sensebuf[8];
+ sg_io_hdr_t io_header;
+ int ret;
+
+ memset(cmd, 0, sizeof(cmd));
+ memset(buf, 0, sizeof(buf));
+ cmd[0] = INQUIRY;
+ cmd[4] = sizeof(buf);
+
+ memset(&io_header, 0, sizeof(io_header));
+ io_header.interface_id = 'S';
+ io_header.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_header.dxfer_len = sizeof(buf);
+ io_header.dxferp = buf;
+ io_header.cmdp = cmd;
+ io_header.cmd_len = sizeof(cmd);
+ io_header.mx_sb_len = sizeof(sensebuf);
+ io_header.sbp = sensebuf;
+ io_header.timeout = 6000; /* XXX */
+
+ ret = blk_ioctl(s->qdev.conf.blk, SG_IO, &io_header);
+ if (ret < 0 || io_header.driver_status || io_header.host_status) {
+ return -1;
+ }
+ s->qdev.type = buf[0];
+ if (buf[1] & 0x80) {
+ s->features |= 1 << SCSI_DISK_F_REMOVABLE;
+ }
+ return 0;
+}
+
+static void scsi_block_realize(SCSIDevice *dev, Error **errp)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ int sg_version;
+ int rc;
+
+ if (!s->qdev.conf.blk) {
+ error_setg(errp, "drive property not set");
+ return;
+ }
+
+ /* check we are using a driver managing SG_IO (version 3 and after) */
+ rc = blk_ioctl(s->qdev.conf.blk, SG_GET_VERSION_NUM, &sg_version);
+ if (rc < 0) {
+ error_setg(errp, "cannot get SG_IO version number: %s. "
+ "Is this a SCSI device?",
+ strerror(-rc));
+ return;
+ }
+ if (sg_version < 30000) {
+ error_setg(errp, "scsi generic interface too old");
+ return;
+ }
+
+ /* get device type from INQUIRY data */
+ rc = get_device_type(s);
+ if (rc < 0) {
+ error_setg(errp, "INQUIRY failed");
+ return;
+ }
+
+ /* Make a guess for the block size, we'll fix it when the guest sends.
+ * READ CAPACITY. If they don't, they likely would assume these sizes
+ * anyway. (TODO: check in /sys).
+ */
+ if (s->qdev.type == TYPE_ROM || s->qdev.type == TYPE_WORM) {
+ s->qdev.blocksize = 2048;
+ } else {
+ s->qdev.blocksize = 512;
+ }
+
+ /* Makes the scsi-block device not removable by using HMP and QMP eject
+ * command.
+ */
+ s->features |= (1 << SCSI_DISK_F_NO_REMOVABLE_DEVOPS);
+
+ scsi_realize(&s->qdev, errp);
+}
+
+static bool scsi_block_is_passthrough(SCSIDiskState *s, uint8_t *buf)
+{
+ switch (buf[0]) {
+ case READ_6:
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ case WRITE_6:
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ case WRITE_VERIFY_10:
+ case WRITE_VERIFY_12:
+ case WRITE_VERIFY_16:
+ /* If we are not using O_DIRECT, we might read stale data from the
+ * host cache if writes were made using other commands than these
+ * ones (such as WRITE SAME or EXTENDED COPY, etc.). So, without
+ * O_DIRECT everything must go through SG_IO.
+ */
+ if (!(blk_get_flags(s->qdev.conf.blk) & BDRV_O_NOCACHE)) {
+ break;
+ }
+
+ /* MMC writing cannot be done via pread/pwrite, because it sometimes
+ * involves writing beyond the maximum LBA or to negative LBA (lead-in).
+ * And once you do these writes, reading from the block device is
+ * unreliable, too. It is even possible that reads deliver random data
+ * from the host page cache (this is probably a Linux bug).
+ *
+ * We might use scsi_disk_dma_reqops as long as no writing commands are
+ * seen, but performance usually isn't paramount on optical media. So,
+ * just make scsi-block operate the same as scsi-generic for them.
+ */
+ if (s->qdev.type != TYPE_ROM) {
+ return false;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+static SCSIRequest *scsi_block_new_request(SCSIDevice *d, uint32_t tag,
+ uint32_t lun, uint8_t *buf,
+ void *hba_private)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
+
+ if (scsi_block_is_passthrough(s, buf)) {
+ return scsi_req_alloc(&scsi_generic_req_ops, &s->qdev, tag, lun,
+ hba_private);
+ } else {
+ return scsi_req_alloc(&scsi_disk_dma_reqops, &s->qdev, tag, lun,
+ hba_private);
+ }
+}
+
+static int scsi_block_parse_cdb(SCSIDevice *d, SCSICommand *cmd,
+ uint8_t *buf, void *hba_private)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
+
+ if (scsi_block_is_passthrough(s, buf)) {
+ return scsi_bus_parse_cdb(&s->qdev, cmd, buf, hba_private);
+ } else {
+ return scsi_req_parse_cdb(&s->qdev, cmd, buf);
+ }
+}
+
+#endif
+
+#define DEFINE_SCSI_DISK_PROPERTIES() \
+ DEFINE_BLOCK_PROPERTIES(SCSIDiskState, qdev.conf), \
+ DEFINE_PROP_STRING("ver", SCSIDiskState, version), \
+ DEFINE_PROP_STRING("serial", SCSIDiskState, serial), \
+ DEFINE_PROP_STRING("vendor", SCSIDiskState, vendor), \
+ DEFINE_PROP_STRING("product", SCSIDiskState, product)
+
+static Property scsi_hd_properties[] = {
+ DEFINE_SCSI_DISK_PROPERTIES(),
+ DEFINE_PROP_BIT("removable", SCSIDiskState, features,
+ SCSI_DISK_F_REMOVABLE, false),
+ DEFINE_PROP_BIT("dpofua", SCSIDiskState, features,
+ SCSI_DISK_F_DPOFUA, false),
+ DEFINE_PROP_UINT64("wwn", SCSIDiskState, wwn, 0),
+ DEFINE_PROP_UINT64("port_wwn", SCSIDiskState, port_wwn, 0),
+ DEFINE_PROP_UINT16("port_index", SCSIDiskState, port_index, 0),
+ DEFINE_PROP_UINT64("max_unmap_size", SCSIDiskState, max_unmap_size,
+ DEFAULT_MAX_UNMAP_SIZE),
+ DEFINE_PROP_UINT64("max_io_size", SCSIDiskState, max_io_size,
+ DEFAULT_MAX_IO_SIZE),
+ DEFINE_BLOCK_CHS_PROPERTIES(SCSIDiskState, qdev.conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_scsi_disk_state = {
+ .name = "scsi-disk",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_SCSI_DEVICE(qdev, SCSIDiskState),
+ VMSTATE_BOOL(media_changed, SCSIDiskState),
+ VMSTATE_BOOL(media_event, SCSIDiskState),
+ VMSTATE_BOOL(eject_request, SCSIDiskState),
+ VMSTATE_BOOL(tray_open, SCSIDiskState),
+ VMSTATE_BOOL(tray_locked, SCSIDiskState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void scsi_hd_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+ sc->realize = scsi_hd_realize;
+ sc->alloc_req = scsi_new_request;
+ sc->unit_attention_reported = scsi_disk_unit_attention_reported;
+ dc->fw_name = "disk";
+ dc->desc = "virtual SCSI disk";
+ dc->reset = scsi_disk_reset;
+ dc->props = scsi_hd_properties;
+ dc->vmsd = &vmstate_scsi_disk_state;
+}
+
+static const TypeInfo scsi_hd_info = {
+ .name = "scsi-hd",
+ .parent = TYPE_SCSI_DEVICE,
+ .instance_size = sizeof(SCSIDiskState),
+ .class_init = scsi_hd_class_initfn,
+};
+
+static Property scsi_cd_properties[] = {
+ DEFINE_SCSI_DISK_PROPERTIES(),
+ DEFINE_PROP_UINT64("wwn", SCSIDiskState, wwn, 0),
+ DEFINE_PROP_UINT64("port_wwn", SCSIDiskState, port_wwn, 0),
+ DEFINE_PROP_UINT16("port_index", SCSIDiskState, port_index, 0),
+ DEFINE_PROP_UINT64("max_io_size", SCSIDiskState, max_io_size,
+ DEFAULT_MAX_IO_SIZE),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void scsi_cd_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+ sc->realize = scsi_cd_realize;
+ sc->alloc_req = scsi_new_request;
+ sc->unit_attention_reported = scsi_disk_unit_attention_reported;
+ dc->fw_name = "disk";
+ dc->desc = "virtual SCSI CD-ROM";
+ dc->reset = scsi_disk_reset;
+ dc->props = scsi_cd_properties;
+ dc->vmsd = &vmstate_scsi_disk_state;
+}
+
+static const TypeInfo scsi_cd_info = {
+ .name = "scsi-cd",
+ .parent = TYPE_SCSI_DEVICE,
+ .instance_size = sizeof(SCSIDiskState),
+ .class_init = scsi_cd_class_initfn,
+};
+
+#ifdef __linux__
+static Property scsi_block_properties[] = {
+ DEFINE_PROP_DRIVE("drive", SCSIDiskState, qdev.conf.blk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void scsi_block_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+ sc->realize = scsi_block_realize;
+ sc->alloc_req = scsi_block_new_request;
+ sc->parse_cdb = scsi_block_parse_cdb;
+ dc->fw_name = "disk";
+ dc->desc = "SCSI block device passthrough";
+ dc->reset = scsi_disk_reset;
+ dc->props = scsi_block_properties;
+ dc->vmsd = &vmstate_scsi_disk_state;
+}
+
+static const TypeInfo scsi_block_info = {
+ .name = "scsi-block",
+ .parent = TYPE_SCSI_DEVICE,
+ .instance_size = sizeof(SCSIDiskState),
+ .class_init = scsi_block_class_initfn,
+};
+#endif
+
+static Property scsi_disk_properties[] = {
+ DEFINE_SCSI_DISK_PROPERTIES(),
+ DEFINE_PROP_BIT("removable", SCSIDiskState, features,
+ SCSI_DISK_F_REMOVABLE, false),
+ DEFINE_PROP_BIT("dpofua", SCSIDiskState, features,
+ SCSI_DISK_F_DPOFUA, false),
+ DEFINE_PROP_UINT64("wwn", SCSIDiskState, wwn, 0),
+ DEFINE_PROP_UINT64("port_wwn", SCSIDiskState, port_wwn, 0),
+ DEFINE_PROP_UINT16("port_index", SCSIDiskState, port_index, 0),
+ DEFINE_PROP_UINT64("max_unmap_size", SCSIDiskState, max_unmap_size,
+ DEFAULT_MAX_UNMAP_SIZE),
+ DEFINE_PROP_UINT64("max_io_size", SCSIDiskState, max_io_size,
+ DEFAULT_MAX_IO_SIZE),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void scsi_disk_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+ sc->realize = scsi_disk_realize;
+ sc->alloc_req = scsi_new_request;
+ sc->unit_attention_reported = scsi_disk_unit_attention_reported;
+ dc->fw_name = "disk";
+ dc->desc = "virtual SCSI disk or CD-ROM (legacy)";
+ dc->reset = scsi_disk_reset;
+ dc->props = scsi_disk_properties;
+ dc->vmsd = &vmstate_scsi_disk_state;
+}
+
+static const TypeInfo scsi_disk_info = {
+ .name = "scsi-disk",
+ .parent = TYPE_SCSI_DEVICE,
+ .instance_size = sizeof(SCSIDiskState),
+ .class_init = scsi_disk_class_initfn,
+};
+
+static void scsi_disk_register_types(void)
+{
+ type_register_static(&scsi_hd_info);
+ type_register_static(&scsi_cd_info);
+#ifdef __linux__
+ type_register_static(&scsi_block_info);
+#endif
+ type_register_static(&scsi_disk_info);
+}
+
+type_init(scsi_disk_register_types)
diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c
new file mode 100644
index 00000000..e53470f8
--- /dev/null
+++ b/hw/scsi/scsi-generic.c
@@ -0,0 +1,496 @@
+/*
+ * Generic SCSI Device support
+ *
+ * Copyright (c) 2007 Bull S.A.S.
+ * Based on code by Paul Brook
+ * Based on code by Fabrice Bellard
+ *
+ * Written by Laurent Vivier <Laurent.Vivier@bull.net>
+ *
+ * This code is licensed under the LGPL.
+ *
+ */
+
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "hw/scsi/scsi.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+
+#ifdef __linux__
+
+//#define DEBUG_SCSI
+
+#ifdef DEBUG_SCSI
+#define DPRINTF(fmt, ...) \
+do { printf("scsi-generic: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "scsi-generic: " fmt , ## __VA_ARGS__); } while (0)
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <scsi/sg.h>
+#include "block/scsi.h"
+
+#define SG_ERR_DRIVER_TIMEOUT 0x06
+#define SG_ERR_DRIVER_SENSE 0x08
+
+#define SG_ERR_DID_OK 0x00
+#define SG_ERR_DID_NO_CONNECT 0x01
+#define SG_ERR_DID_BUS_BUSY 0x02
+#define SG_ERR_DID_TIME_OUT 0x03
+
+#ifndef MAX_UINT
+#define MAX_UINT ((unsigned int)-1)
+#endif
+
+typedef struct SCSIGenericReq {
+ SCSIRequest req;
+ uint8_t *buf;
+ int buflen;
+ int len;
+ sg_io_hdr_t io_header;
+} SCSIGenericReq;
+
+static void scsi_generic_save_request(QEMUFile *f, SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+ qemu_put_sbe32s(f, &r->buflen);
+ if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ assert(!r->req.sg);
+ qemu_put_buffer(f, r->buf, r->req.cmd.xfer);
+ }
+}
+
+static void scsi_generic_load_request(QEMUFile *f, SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+ qemu_get_sbe32s(f, &r->buflen);
+ if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ assert(!r->req.sg);
+ qemu_get_buffer(f, r->buf, r->req.cmd.xfer);
+ }
+}
+
+static void scsi_free_request(SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+ g_free(r->buf);
+}
+
+/* Helper function for command completion. */
+static void scsi_command_complete(void *opaque, int ret)
+{
+ int status;
+ SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+
+ r->req.aiocb = NULL;
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+ if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) {
+ r->req.sense_len = r->io_header.sb_len_wr;
+ }
+
+ if (ret != 0) {
+ switch (ret) {
+ case -EDOM:
+ status = TASK_SET_FULL;
+ break;
+ case -ENOMEM:
+ status = CHECK_CONDITION;
+ scsi_req_build_sense(&r->req, SENSE_CODE(TARGET_FAILURE));
+ break;
+ default:
+ status = CHECK_CONDITION;
+ scsi_req_build_sense(&r->req, SENSE_CODE(IO_ERROR));
+ break;
+ }
+ } else {
+ if (r->io_header.host_status == SG_ERR_DID_NO_CONNECT ||
+ r->io_header.host_status == SG_ERR_DID_BUS_BUSY ||
+ r->io_header.host_status == SG_ERR_DID_TIME_OUT ||
+ (r->io_header.driver_status & SG_ERR_DRIVER_TIMEOUT)) {
+ status = BUSY;
+ BADF("Driver Timeout\n");
+ } else if (r->io_header.host_status) {
+ status = CHECK_CONDITION;
+ scsi_req_build_sense(&r->req, SENSE_CODE(I_T_NEXUS_LOSS));
+ } else if (r->io_header.status) {
+ status = r->io_header.status;
+ } else if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) {
+ status = CHECK_CONDITION;
+ } else {
+ status = GOOD;
+ }
+ }
+ DPRINTF("Command complete 0x%p tag=0x%x status=%d\n",
+ r, r->req.tag, status);
+
+ scsi_req_complete(&r->req, status);
+done:
+ scsi_req_unref(&r->req);
+}
+
+static int execute_command(BlockBackend *blk,
+ SCSIGenericReq *r, int direction,
+ BlockCompletionFunc *complete)
+{
+ r->io_header.interface_id = 'S';
+ r->io_header.dxfer_direction = direction;
+ r->io_header.dxferp = r->buf;
+ r->io_header.dxfer_len = r->buflen;
+ r->io_header.cmdp = r->req.cmd.buf;
+ r->io_header.cmd_len = r->req.cmd.len;
+ r->io_header.mx_sb_len = sizeof(r->req.sense);
+ r->io_header.sbp = r->req.sense;
+ r->io_header.timeout = MAX_UINT;
+ r->io_header.usr_ptr = r;
+ r->io_header.flags |= SG_FLAG_DIRECT_IO;
+
+ r->req.aiocb = blk_aio_ioctl(blk, SG_IO, &r->io_header, complete, r);
+ if (r->req.aiocb == NULL) {
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void scsi_read_complete(void * opaque, int ret)
+{
+ SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+ SCSIDevice *s = r->req.dev;
+ int len;
+
+ r->req.aiocb = NULL;
+ if (ret || r->req.io_canceled) {
+ scsi_command_complete(r, ret);
+ return;
+ }
+ len = r->io_header.dxfer_len - r->io_header.resid;
+ DPRINTF("Data ready tag=0x%x len=%d\n", r->req.tag, len);
+
+ r->len = -1;
+ if (len == 0) {
+ scsi_command_complete(r, 0);
+ } else {
+ /* Snoop READ CAPACITY output to set the blocksize. */
+ if (r->req.cmd.buf[0] == READ_CAPACITY_10 &&
+ (ldl_be_p(&r->buf[0]) != 0xffffffffU || s->max_lba == 0)) {
+ s->blocksize = ldl_be_p(&r->buf[4]);
+ s->max_lba = ldl_be_p(&r->buf[0]) & 0xffffffffULL;
+ } else if (r->req.cmd.buf[0] == SERVICE_ACTION_IN_16 &&
+ (r->req.cmd.buf[1] & 31) == SAI_READ_CAPACITY_16) {
+ s->blocksize = ldl_be_p(&r->buf[8]);
+ s->max_lba = ldq_be_p(&r->buf[0]);
+ }
+ blk_set_guest_block_size(s->conf.blk, s->blocksize);
+
+ scsi_req_data(&r->req, len);
+ scsi_req_unref(&r->req);
+ }
+}
+
+/* Read more data from scsi device into buffer. */
+static void scsi_read_data(SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+ SCSIDevice *s = r->req.dev;
+ int ret;
+
+ DPRINTF("scsi_read_data 0x%x\n", req->tag);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ if (r->len == -1) {
+ scsi_command_complete(r, 0);
+ return;
+ }
+
+ ret = execute_command(s->conf.blk, r, SG_DXFER_FROM_DEV,
+ scsi_read_complete);
+ if (ret < 0) {
+ scsi_command_complete(r, ret);
+ }
+}
+
+static void scsi_write_complete(void * opaque, int ret)
+{
+ SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+ SCSIDevice *s = r->req.dev;
+
+ DPRINTF("scsi_write_complete() ret = %d\n", ret);
+ r->req.aiocb = NULL;
+ if (ret || r->req.io_canceled) {
+ scsi_command_complete(r, ret);
+ return;
+ }
+
+ if (r->req.cmd.buf[0] == MODE_SELECT && r->req.cmd.buf[4] == 12 &&
+ s->type == TYPE_TAPE) {
+ s->blocksize = (r->buf[9] << 16) | (r->buf[10] << 8) | r->buf[11];
+ DPRINTF("block size %d\n", s->blocksize);
+ }
+
+ scsi_command_complete(r, ret);
+}
+
+/* Write data to a scsi device. Returns nonzero on failure.
+ The transfer may complete asynchronously. */
+static void scsi_write_data(SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+ SCSIDevice *s = r->req.dev;
+ int ret;
+
+ DPRINTF("scsi_write_data 0x%x\n", req->tag);
+ if (r->len == 0) {
+ r->len = r->buflen;
+ scsi_req_data(&r->req, r->len);
+ return;
+ }
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ ret = execute_command(s->conf.blk, r, SG_DXFER_TO_DEV, scsi_write_complete);
+ if (ret < 0) {
+ scsi_command_complete(r, ret);
+ }
+}
+
+/* Return a pointer to the data buffer. */
+static uint8_t *scsi_get_buf(SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+ return r->buf;
+}
+
+/* Execute a scsi command. Returns the length of the data expected by the
+ command. This will be Positive for data transfers from the device
+ (eg. disk reads), negative for transfers to the device (eg. disk writes),
+ and zero if the command does not transfer any data. */
+
+static int32_t scsi_send_command(SCSIRequest *req, uint8_t *cmd)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+ SCSIDevice *s = r->req.dev;
+ int ret;
+
+#ifdef DEBUG_SCSI
+ {
+ int i;
+ for (i = 1; i < r->req.cmd.len; i++) {
+ printf(" 0x%02x", cmd[i]);
+ }
+ printf("\n");
+ }
+#endif
+
+ if (r->req.cmd.xfer == 0) {
+ g_free(r->buf);
+ r->buflen = 0;
+ r->buf = NULL;
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ ret = execute_command(s->conf.blk, r, SG_DXFER_NONE,
+ scsi_command_complete);
+ if (ret < 0) {
+ scsi_command_complete(r, ret);
+ return 0;
+ }
+ return 0;
+ }
+
+ if (r->buflen != r->req.cmd.xfer) {
+ g_free(r->buf);
+ r->buf = g_malloc(r->req.cmd.xfer);
+ r->buflen = r->req.cmd.xfer;
+ }
+
+ memset(r->buf, 0, r->buflen);
+ r->len = r->req.cmd.xfer;
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ r->len = 0;
+ return -r->req.cmd.xfer;
+ } else {
+ return r->req.cmd.xfer;
+ }
+}
+
+static int get_stream_blocksize(BlockBackend *blk)
+{
+ uint8_t cmd[6];
+ uint8_t buf[12];
+ uint8_t sensebuf[8];
+ sg_io_hdr_t io_header;
+ int ret;
+
+ memset(cmd, 0, sizeof(cmd));
+ memset(buf, 0, sizeof(buf));
+ cmd[0] = MODE_SENSE;
+ cmd[4] = sizeof(buf);
+
+ memset(&io_header, 0, sizeof(io_header));
+ io_header.interface_id = 'S';
+ io_header.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_header.dxfer_len = sizeof(buf);
+ io_header.dxferp = buf;
+ io_header.cmdp = cmd;
+ io_header.cmd_len = sizeof(cmd);
+ io_header.mx_sb_len = sizeof(sensebuf);
+ io_header.sbp = sensebuf;
+ io_header.timeout = 6000; /* XXX */
+
+ ret = blk_ioctl(blk, SG_IO, &io_header);
+ if (ret < 0 || io_header.driver_status || io_header.host_status) {
+ return -1;
+ }
+ return (buf[9] << 16) | (buf[10] << 8) | buf[11];
+}
+
+static void scsi_generic_reset(DeviceState *dev)
+{
+ SCSIDevice *s = SCSI_DEVICE(dev);
+
+ scsi_device_purge_requests(s, SENSE_CODE(RESET));
+}
+
+static void scsi_generic_realize(SCSIDevice *s, Error **errp)
+{
+ int rc;
+ int sg_version;
+ struct sg_scsi_id scsiid;
+
+ if (!s->conf.blk) {
+ error_setg(errp, "drive property not set");
+ return;
+ }
+
+ if (blk_get_on_error(s->conf.blk, 0) != BLOCKDEV_ON_ERROR_ENOSPC) {
+ error_setg(errp, "Device doesn't support drive option werror");
+ return;
+ }
+ if (blk_get_on_error(s->conf.blk, 1) != BLOCKDEV_ON_ERROR_REPORT) {
+ error_setg(errp, "Device doesn't support drive option rerror");
+ return;
+ }
+
+ /* check we are using a driver managing SG_IO (version 3 and after */
+ rc = blk_ioctl(s->conf.blk, SG_GET_VERSION_NUM, &sg_version);
+ if (rc < 0) {
+ error_setg(errp, "cannot get SG_IO version number: %s. "
+ "Is this a SCSI device?",
+ strerror(-rc));
+ return;
+ }
+ if (sg_version < 30000) {
+ error_setg(errp, "scsi generic interface too old");
+ return;
+ }
+
+ /* get LUN of the /dev/sg? */
+ if (blk_ioctl(s->conf.blk, SG_GET_SCSI_ID, &scsiid)) {
+ error_setg(errp, "SG_GET_SCSI_ID ioctl failed");
+ return;
+ }
+
+ /* define device state */
+ s->type = scsiid.scsi_type;
+ DPRINTF("device type %d\n", s->type);
+
+ switch (s->type) {
+ case TYPE_TAPE:
+ s->blocksize = get_stream_blocksize(s->conf.blk);
+ if (s->blocksize == -1) {
+ s->blocksize = 0;
+ }
+ break;
+
+ /* Make a guess for block devices, we'll fix it when the guest sends.
+ * READ CAPACITY. If they don't, they likely would assume these sizes
+ * anyway. (TODO: they could also send MODE SENSE).
+ */
+ case TYPE_ROM:
+ case TYPE_WORM:
+ s->blocksize = 2048;
+ break;
+ default:
+ s->blocksize = 512;
+ break;
+ }
+
+ DPRINTF("block size %d\n", s->blocksize);
+}
+
+const SCSIReqOps scsi_generic_req_ops = {
+ .size = sizeof(SCSIGenericReq),
+ .free_req = scsi_free_request,
+ .send_command = scsi_send_command,
+ .read_data = scsi_read_data,
+ .write_data = scsi_write_data,
+ .get_buf = scsi_get_buf,
+ .load_request = scsi_generic_load_request,
+ .save_request = scsi_generic_save_request,
+};
+
+static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun,
+ uint8_t *buf, void *hba_private)
+{
+ SCSIRequest *req;
+
+ req = scsi_req_alloc(&scsi_generic_req_ops, d, tag, lun, hba_private);
+ return req;
+}
+
+static Property scsi_generic_properties[] = {
+ DEFINE_PROP_DRIVE("drive", SCSIDevice, conf.blk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static int scsi_generic_parse_cdb(SCSIDevice *dev, SCSICommand *cmd,
+ uint8_t *buf, void *hba_private)
+{
+ return scsi_bus_parse_cdb(dev, cmd, buf, hba_private);
+}
+
+static void scsi_generic_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+ sc->realize = scsi_generic_realize;
+ sc->alloc_req = scsi_new_request;
+ sc->parse_cdb = scsi_generic_parse_cdb;
+ dc->fw_name = "disk";
+ dc->desc = "pass through generic scsi device (/dev/sg*)";
+ dc->reset = scsi_generic_reset;
+ dc->props = scsi_generic_properties;
+ dc->vmsd = &vmstate_scsi_device;
+}
+
+static const TypeInfo scsi_generic_info = {
+ .name = "scsi-generic",
+ .parent = TYPE_SCSI_DEVICE,
+ .instance_size = sizeof(SCSIDevice),
+ .class_init = scsi_generic_class_initfn,
+};
+
+static void scsi_generic_register_types(void)
+{
+ type_register_static(&scsi_generic_info);
+}
+
+type_init(scsi_generic_register_types)
+
+#endif /* __linux__ */
diff --git a/hw/scsi/spapr_vscsi.c b/hw/scsi/spapr_vscsi.c
new file mode 100644
index 00000000..891424fa
--- /dev/null
+++ b/hw/scsi/spapr_vscsi.c
@@ -0,0 +1,1302 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * PAPR Virtual SCSI, aka ibmvscsi
+ *
+ * Copyright (c) 2010,2011 Benjamin Herrenschmidt, IBM Corporation.
+ *
+ * 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.
+ *
+ * TODO:
+ *
+ * - Cleanups :-)
+ * - Sort out better how to assign devices to VSCSI instances
+ * - Fix residual counts
+ * - Add indirect descriptors support
+ * - Maybe do autosense (PAPR seems to mandate it, linux doesn't care)
+ */
+#include "hw/hw.h"
+#include "hw/scsi/scsi.h"
+#include "block/scsi.h"
+#include "srp.h"
+#include "hw/qdev.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "viosrp.h"
+
+#include <libfdt.h>
+
+/*#define DEBUG_VSCSI*/
+
+#ifdef DEBUG_VSCSI
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+ do { } while (0)
+#endif
+
+/*
+ * Virtual SCSI device
+ */
+
+/* Random numbers */
+#define VSCSI_MAX_SECTORS 4096
+#define VSCSI_REQ_LIMIT 24
+
+#define SRP_RSP_SENSE_DATA_LEN 18
+
+#define SRP_REPORT_LUNS_WLUN 0xc10100000000000ULL
+
+typedef union vscsi_crq {
+ struct viosrp_crq s;
+ uint8_t raw[16];
+} vscsi_crq;
+
+typedef struct vscsi_req {
+ vscsi_crq crq;
+ union viosrp_iu iu;
+
+ /* SCSI request tracking */
+ SCSIRequest *sreq;
+ uint32_t qtag; /* qemu tag != srp tag */
+ bool active;
+ bool writing;
+ bool dma_error;
+ uint32_t data_len;
+ uint32_t senselen;
+ uint8_t sense[SCSI_SENSE_BUF_SIZE];
+
+ /* RDMA related bits */
+ uint8_t dma_fmt;
+ uint16_t local_desc;
+ uint16_t total_desc;
+ uint16_t cdb_offset;
+ uint16_t cur_desc_num;
+ uint16_t cur_desc_offset;
+} vscsi_req;
+
+#define TYPE_VIO_SPAPR_VSCSI_DEVICE "spapr-vscsi"
+#define VIO_SPAPR_VSCSI_DEVICE(obj) \
+ OBJECT_CHECK(VSCSIState, (obj), TYPE_VIO_SPAPR_VSCSI_DEVICE)
+
+typedef struct {
+ VIOsPAPRDevice vdev;
+ SCSIBus bus;
+ vscsi_req reqs[VSCSI_REQ_LIMIT];
+} VSCSIState;
+
+static struct vscsi_req *vscsi_get_req(VSCSIState *s)
+{
+ vscsi_req *req;
+ int i;
+
+ for (i = 0; i < VSCSI_REQ_LIMIT; i++) {
+ req = &s->reqs[i];
+ if (!req->active) {
+ memset(req, 0, sizeof(*req));
+ req->qtag = i;
+ req->active = 1;
+ return req;
+ }
+ }
+ return NULL;
+}
+
+static struct vscsi_req *vscsi_find_req(VSCSIState *s, uint64_t srp_tag)
+{
+ vscsi_req *req;
+ int i;
+
+ for (i = 0; i < VSCSI_REQ_LIMIT; i++) {
+ req = &s->reqs[i];
+ if (req->iu.srp.cmd.tag == srp_tag) {
+ return req;
+ }
+ }
+ return NULL;
+}
+
+static void vscsi_put_req(vscsi_req *req)
+{
+ if (req->sreq != NULL) {
+ scsi_req_unref(req->sreq);
+ }
+ req->sreq = NULL;
+ req->active = 0;
+}
+
+static SCSIDevice *vscsi_device_find(SCSIBus *bus, uint64_t srp_lun, int *lun)
+{
+ int channel = 0, id = 0;
+
+retry:
+ switch (srp_lun >> 62) {
+ case 0:
+ if ((srp_lun >> 56) != 0) {
+ channel = (srp_lun >> 56) & 0x3f;
+ id = (srp_lun >> 48) & 0xff;
+ srp_lun <<= 16;
+ goto retry;
+ }
+ *lun = (srp_lun >> 48) & 0xff;
+ break;
+
+ case 1:
+ *lun = (srp_lun >> 48) & 0x3fff;
+ break;
+ case 2:
+ channel = (srp_lun >> 53) & 0x7;
+ id = (srp_lun >> 56) & 0x3f;
+ *lun = (srp_lun >> 48) & 0x1f;
+ break;
+ case 3:
+ *lun = -1;
+ return NULL;
+ default:
+ abort();
+ }
+
+ return scsi_device_find(bus, channel, id, *lun);
+}
+
+static int vscsi_send_iu(VSCSIState *s, vscsi_req *req,
+ uint64_t length, uint8_t format)
+{
+ long rc, rc1;
+
+ /* First copy the SRP */
+ rc = spapr_vio_dma_write(&s->vdev, req->crq.s.IU_data_ptr,
+ &req->iu, length);
+ if (rc) {
+ fprintf(stderr, "vscsi_send_iu: DMA write failure !\n");
+ }
+
+ req->crq.s.valid = 0x80;
+ req->crq.s.format = format;
+ req->crq.s.reserved = 0x00;
+ req->crq.s.timeout = cpu_to_be16(0x0000);
+ req->crq.s.IU_length = cpu_to_be16(length);
+ req->crq.s.IU_data_ptr = req->iu.srp.rsp.tag; /* right byte order */
+
+ if (rc == 0) {
+ req->crq.s.status = VIOSRP_OK;
+ } else {
+ req->crq.s.status = VIOSRP_ADAPTER_FAIL;
+ }
+
+ rc1 = spapr_vio_send_crq(&s->vdev, req->crq.raw);
+ if (rc1) {
+ fprintf(stderr, "vscsi_send_iu: Error sending response\n");
+ return rc1;
+ }
+
+ return rc;
+}
+
+static void vscsi_makeup_sense(VSCSIState *s, vscsi_req *req,
+ uint8_t key, uint8_t asc, uint8_t ascq)
+{
+ req->senselen = SRP_RSP_SENSE_DATA_LEN;
+
+ /* Valid bit and 'current errors' */
+ req->sense[0] = (0x1 << 7 | 0x70);
+ /* Sense key */
+ req->sense[2] = key;
+ /* Additional sense length */
+ req->sense[7] = 0xa; /* 10 bytes */
+ /* Additional sense code */
+ req->sense[12] = asc;
+ req->sense[13] = ascq;
+}
+
+static int vscsi_send_rsp(VSCSIState *s, vscsi_req *req,
+ uint8_t status, int32_t res_in, int32_t res_out)
+{
+ union viosrp_iu *iu = &req->iu;
+ uint64_t tag = iu->srp.rsp.tag;
+ int total_len = sizeof(iu->srp.rsp);
+ uint8_t sol_not = iu->srp.cmd.sol_not;
+
+ DPRINTF("VSCSI: Sending resp status: 0x%x, "
+ "res_in: %d, res_out: %d\n", status, res_in, res_out);
+
+ memset(iu, 0, sizeof(struct srp_rsp));
+ iu->srp.rsp.opcode = SRP_RSP;
+ iu->srp.rsp.req_lim_delta = cpu_to_be32(1);
+ iu->srp.rsp.tag = tag;
+
+ /* Handle residuals */
+ if (res_in < 0) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DIUNDER;
+ res_in = -res_in;
+ } else if (res_in) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DIOVER;
+ }
+ if (res_out < 0) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DOUNDER;
+ res_out = -res_out;
+ } else if (res_out) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DOOVER;
+ }
+ iu->srp.rsp.data_in_res_cnt = cpu_to_be32(res_in);
+ iu->srp.rsp.data_out_res_cnt = cpu_to_be32(res_out);
+
+ /* We don't do response data */
+ /* iu->srp.rsp.flags &= ~SRP_RSP_FLAG_RSPVALID; */
+ iu->srp.rsp.resp_data_len = cpu_to_be32(0);
+
+ /* Handle success vs. failure */
+ iu->srp.rsp.status = status;
+ if (status) {
+ iu->srp.rsp.sol_not = (sol_not & 0x04) >> 2;
+ if (req->senselen) {
+ req->iu.srp.rsp.flags |= SRP_RSP_FLAG_SNSVALID;
+ req->iu.srp.rsp.sense_data_len = cpu_to_be32(req->senselen);
+ memcpy(req->iu.srp.rsp.data, req->sense, req->senselen);
+ total_len += req->senselen;
+ }
+ } else {
+ iu->srp.rsp.sol_not = (sol_not & 0x02) >> 1;
+ }
+
+ vscsi_send_iu(s, req, total_len, VIOSRP_SRP_FORMAT);
+ return 0;
+}
+
+static inline struct srp_direct_buf vscsi_swap_desc(struct srp_direct_buf desc)
+{
+ desc.va = be64_to_cpu(desc.va);
+ desc.len = be32_to_cpu(desc.len);
+ return desc;
+}
+
+static int vscsi_fetch_desc(VSCSIState *s, struct vscsi_req *req,
+ unsigned n, unsigned buf_offset,
+ struct srp_direct_buf *ret)
+{
+ struct srp_cmd *cmd = &req->iu.srp.cmd;
+
+ switch (req->dma_fmt) {
+ case SRP_NO_DATA_DESC: {
+ DPRINTF("VSCSI: no data descriptor\n");
+ return 0;
+ }
+ case SRP_DATA_DESC_DIRECT: {
+ memcpy(ret, cmd->add_data + req->cdb_offset, sizeof(*ret));
+ assert(req->cur_desc_num == 0);
+ DPRINTF("VSCSI: direct segment\n");
+ break;
+ }
+ case SRP_DATA_DESC_INDIRECT: {
+ struct srp_indirect_buf *tmp = (struct srp_indirect_buf *)
+ (cmd->add_data + req->cdb_offset);
+ if (n < req->local_desc) {
+ *ret = tmp->desc_list[n];
+ DPRINTF("VSCSI: indirect segment local tag=0x%x desc#%d/%d\n",
+ req->qtag, n, req->local_desc);
+
+ } else if (n < req->total_desc) {
+ int rc;
+ struct srp_direct_buf tbl_desc = vscsi_swap_desc(tmp->table_desc);
+ unsigned desc_offset = n * sizeof(struct srp_direct_buf);
+
+ if (desc_offset >= tbl_desc.len) {
+ DPRINTF("VSCSI: #%d is ouf of range (%d bytes)\n",
+ n, desc_offset);
+ return -1;
+ }
+ rc = spapr_vio_dma_read(&s->vdev, tbl_desc.va + desc_offset,
+ ret, sizeof(struct srp_direct_buf));
+ if (rc) {
+ DPRINTF("VSCSI: spapr_vio_dma_read -> %d reading ext_desc\n",
+ rc);
+ return -1;
+ }
+ DPRINTF("VSCSI: indirect segment ext. tag=0x%x desc#%d/%d { va=%"PRIx64" len=%x }\n",
+ req->qtag, n, req->total_desc, tbl_desc.va, tbl_desc.len);
+ } else {
+ DPRINTF("VSCSI: Out of descriptors !\n");
+ return 0;
+ }
+ break;
+ }
+ default:
+ fprintf(stderr, "VSCSI: Unknown format %x\n", req->dma_fmt);
+ return -1;
+ }
+
+ *ret = vscsi_swap_desc(*ret);
+ if (buf_offset > ret->len) {
+ DPRINTF(" offset=%x is out of a descriptor #%d boundary=%x\n",
+ buf_offset, req->cur_desc_num, ret->len);
+ return -1;
+ }
+ ret->va += buf_offset;
+ ret->len -= buf_offset;
+
+ DPRINTF(" cur=%d offs=%x ret { va=%"PRIx64" len=%x }\n",
+ req->cur_desc_num, req->cur_desc_offset, ret->va, ret->len);
+
+ return ret->len ? 1 : 0;
+}
+
+static int vscsi_srp_direct_data(VSCSIState *s, vscsi_req *req,
+ uint8_t *buf, uint32_t len)
+{
+ struct srp_direct_buf md;
+ uint32_t llen;
+ int rc = 0;
+
+ rc = vscsi_fetch_desc(s, req, req->cur_desc_num, req->cur_desc_offset, &md);
+ if (rc < 0) {
+ return -1;
+ } else if (rc == 0) {
+ return 0;
+ }
+
+ llen = MIN(len, md.len);
+ if (llen) {
+ if (req->writing) { /* writing = to device = reading from memory */
+ rc = spapr_vio_dma_read(&s->vdev, md.va, buf, llen);
+ } else {
+ rc = spapr_vio_dma_write(&s->vdev, md.va, buf, llen);
+ }
+ }
+
+ if (rc) {
+ return -1;
+ }
+ req->cur_desc_offset += llen;
+
+ return llen;
+}
+
+static int vscsi_srp_indirect_data(VSCSIState *s, vscsi_req *req,
+ uint8_t *buf, uint32_t len)
+{
+ struct srp_direct_buf md;
+ int rc = 0;
+ uint32_t llen, total = 0;
+
+ DPRINTF("VSCSI: indirect segment 0x%x bytes\n", len);
+
+ /* While we have data ... */
+ while (len) {
+ rc = vscsi_fetch_desc(s, req, req->cur_desc_num, req->cur_desc_offset, &md);
+ if (rc < 0) {
+ return -1;
+ } else if (rc == 0) {
+ break;
+ }
+
+ /* Perform transfer */
+ llen = MIN(len, md.len);
+ if (req->writing) { /* writing = to device = reading from memory */
+ rc = spapr_vio_dma_read(&s->vdev, md.va, buf, llen);
+ } else {
+ rc = spapr_vio_dma_write(&s->vdev, md.va, buf, llen);
+ }
+ if (rc) {
+ DPRINTF("VSCSI: spapr_vio_dma_r/w(%d) -> %d\n", req->writing, rc);
+ break;
+ }
+ DPRINTF("VSCSI: data: %02x %02x %02x %02x...\n",
+ buf[0], buf[1], buf[2], buf[3]);
+
+ len -= llen;
+ buf += llen;
+
+ total += llen;
+
+ /* Update current position in the current descriptor */
+ req->cur_desc_offset += llen;
+ if (md.len == llen) {
+ /* Go to the next descriptor if the current one finished */
+ ++req->cur_desc_num;
+ req->cur_desc_offset = 0;
+ }
+ }
+
+ return rc ? -1 : total;
+}
+
+static int vscsi_srp_transfer_data(VSCSIState *s, vscsi_req *req,
+ int writing, uint8_t *buf, uint32_t len)
+{
+ int err = 0;
+
+ switch (req->dma_fmt) {
+ case SRP_NO_DATA_DESC:
+ DPRINTF("VSCSI: no data desc transfer, skipping 0x%x bytes\n", len);
+ break;
+ case SRP_DATA_DESC_DIRECT:
+ err = vscsi_srp_direct_data(s, req, buf, len);
+ break;
+ case SRP_DATA_DESC_INDIRECT:
+ err = vscsi_srp_indirect_data(s, req, buf, len);
+ break;
+ }
+ return err;
+}
+
+/* Bits from linux srp */
+static int data_out_desc_size(struct srp_cmd *cmd)
+{
+ int size = 0;
+ uint8_t fmt = cmd->buf_fmt >> 4;
+
+ switch (fmt) {
+ case SRP_NO_DATA_DESC:
+ break;
+ case SRP_DATA_DESC_DIRECT:
+ size = sizeof(struct srp_direct_buf);
+ break;
+ case SRP_DATA_DESC_INDIRECT:
+ size = sizeof(struct srp_indirect_buf) +
+ sizeof(struct srp_direct_buf)*cmd->data_out_desc_cnt;
+ break;
+ default:
+ break;
+ }
+ return size;
+}
+
+static int vscsi_preprocess_desc(vscsi_req *req)
+{
+ struct srp_cmd *cmd = &req->iu.srp.cmd;
+
+ req->cdb_offset = cmd->add_cdb_len & ~3;
+
+ if (req->writing) {
+ req->dma_fmt = cmd->buf_fmt >> 4;
+ } else {
+ req->cdb_offset += data_out_desc_size(cmd);
+ req->dma_fmt = cmd->buf_fmt & ((1U << 4) - 1);
+ }
+
+ switch (req->dma_fmt) {
+ case SRP_NO_DATA_DESC:
+ break;
+ case SRP_DATA_DESC_DIRECT:
+ req->total_desc = req->local_desc = 1;
+ break;
+ case SRP_DATA_DESC_INDIRECT: {
+ struct srp_indirect_buf *ind_tmp = (struct srp_indirect_buf *)
+ (cmd->add_data + req->cdb_offset);
+
+ req->total_desc = be32_to_cpu(ind_tmp->table_desc.len) /
+ sizeof(struct srp_direct_buf);
+ req->local_desc = req->writing ? cmd->data_out_desc_cnt :
+ cmd->data_in_desc_cnt;
+ break;
+ }
+ default:
+ fprintf(stderr,
+ "vscsi_preprocess_desc: Unknown format %x\n", req->dma_fmt);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Callback to indicate that the SCSI layer has completed a transfer. */
+static void vscsi_transfer_data(SCSIRequest *sreq, uint32_t len)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(sreq->bus->qbus.parent);
+ vscsi_req *req = sreq->hba_private;
+ uint8_t *buf;
+ int rc = 0;
+
+ DPRINTF("VSCSI: SCSI xfer complete tag=0x%x len=0x%x, req=%p\n",
+ sreq->tag, len, req);
+ if (req == NULL) {
+ fprintf(stderr, "VSCSI: Can't find request for tag 0x%x\n", sreq->tag);
+ return;
+ }
+
+ if (len) {
+ buf = scsi_req_get_buf(sreq);
+ rc = vscsi_srp_transfer_data(s, req, req->writing, buf, len);
+ }
+ if (rc < 0) {
+ fprintf(stderr, "VSCSI: RDMA error rc=%d!\n", rc);
+ req->dma_error = true;
+ scsi_req_cancel(req->sreq);
+ return;
+ }
+
+ /* Start next chunk */
+ req->data_len -= rc;
+ scsi_req_continue(sreq);
+}
+
+/* Callback to indicate that the SCSI layer has completed a transfer. */
+static void vscsi_command_complete(SCSIRequest *sreq, uint32_t status, size_t resid)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(sreq->bus->qbus.parent);
+ vscsi_req *req = sreq->hba_private;
+ int32_t res_in = 0, res_out = 0;
+
+ DPRINTF("VSCSI: SCSI cmd complete, tag=0x%x status=0x%x, req=%p\n",
+ sreq->tag, status, req);
+ if (req == NULL) {
+ fprintf(stderr, "VSCSI: Can't find request for tag 0x%x\n", sreq->tag);
+ return;
+ }
+
+ if (status == CHECK_CONDITION) {
+ req->senselen = scsi_req_get_sense(req->sreq, req->sense,
+ sizeof(req->sense));
+ DPRINTF("VSCSI: Sense data, %d bytes:\n", req->senselen);
+ DPRINTF(" %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ req->sense[0], req->sense[1], req->sense[2], req->sense[3],
+ req->sense[4], req->sense[5], req->sense[6], req->sense[7]);
+ DPRINTF(" %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ req->sense[8], req->sense[9], req->sense[10], req->sense[11],
+ req->sense[12], req->sense[13], req->sense[14], req->sense[15]);
+ }
+
+ DPRINTF("VSCSI: Command complete err=%d\n", status);
+ if (status == 0) {
+ /* We handle overflows, not underflows for normal commands,
+ * but hopefully nobody cares
+ */
+ if (req->writing) {
+ res_out = req->data_len;
+ } else {
+ res_in = req->data_len;
+ }
+ }
+ vscsi_send_rsp(s, req, status, res_in, res_out);
+ vscsi_put_req(req);
+}
+
+static void vscsi_request_cancelled(SCSIRequest *sreq)
+{
+ vscsi_req *req = sreq->hba_private;
+
+ if (req->dma_error) {
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(sreq->bus->qbus.parent);
+
+ vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ }
+ vscsi_put_req(req);
+}
+
+static const VMStateDescription vmstate_spapr_vscsi_req = {
+ .name = "spapr_vscsi_req",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(crq.raw, vscsi_req),
+ VMSTATE_BUFFER(iu.srp.reserved, vscsi_req),
+ VMSTATE_UINT32(qtag, vscsi_req),
+ VMSTATE_BOOL(active, vscsi_req),
+ VMSTATE_UINT32(data_len, vscsi_req),
+ VMSTATE_BOOL(writing, vscsi_req),
+ VMSTATE_UINT32(senselen, vscsi_req),
+ VMSTATE_BUFFER(sense, vscsi_req),
+ VMSTATE_UINT8(dma_fmt, vscsi_req),
+ VMSTATE_UINT16(local_desc, vscsi_req),
+ VMSTATE_UINT16(total_desc, vscsi_req),
+ VMSTATE_UINT16(cdb_offset, vscsi_req),
+ /*Restart SCSI request from the beginning for now */
+ /*VMSTATE_UINT16(cur_desc_num, vscsi_req),
+ VMSTATE_UINT16(cur_desc_offset, vscsi_req),*/
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void vscsi_save_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ vscsi_req *req = sreq->hba_private;
+ assert(req->active);
+
+ vmstate_save_state(f, &vmstate_spapr_vscsi_req, req, NULL);
+
+ DPRINTF("VSCSI: saving tag=%u, current desc#%d, offset=%x\n",
+ req->qtag, req->cur_desc_num, req->cur_desc_offset);
+}
+
+static void *vscsi_load_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ SCSIBus *bus = sreq->bus;
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(bus->qbus.parent);
+ vscsi_req *req;
+ int rc;
+
+ assert(sreq->tag < VSCSI_REQ_LIMIT);
+ req = &s->reqs[sreq->tag];
+ assert(!req->active);
+
+ memset(req, 0, sizeof(*req));
+ rc = vmstate_load_state(f, &vmstate_spapr_vscsi_req, req, 1);
+ if (rc) {
+ fprintf(stderr, "VSCSI: failed loading request tag#%u\n", sreq->tag);
+ return NULL;
+ }
+ assert(req->active);
+
+ req->sreq = scsi_req_ref(sreq);
+
+ DPRINTF("VSCSI: restoring tag=%u, current desc#%d, offset=%x\n",
+ req->qtag, req->cur_desc_num, req->cur_desc_offset);
+
+ return req;
+}
+
+static void vscsi_process_login(VSCSIState *s, vscsi_req *req)
+{
+ union viosrp_iu *iu = &req->iu;
+ struct srp_login_rsp *rsp = &iu->srp.login_rsp;
+ uint64_t tag = iu->srp.rsp.tag;
+
+ DPRINTF("VSCSI: Got login, sendin response !\n");
+
+ /* TODO handle case that requested size is wrong and
+ * buffer format is wrong
+ */
+ memset(iu, 0, sizeof(struct srp_login_rsp));
+ rsp->opcode = SRP_LOGIN_RSP;
+ /* Don't advertise quite as many request as we support to
+ * keep room for management stuff etc...
+ */
+ rsp->req_lim_delta = cpu_to_be32(VSCSI_REQ_LIMIT-2);
+ rsp->tag = tag;
+ rsp->max_it_iu_len = cpu_to_be32(sizeof(union srp_iu));
+ rsp->max_ti_iu_len = cpu_to_be32(sizeof(union srp_iu));
+ /* direct and indirect */
+ rsp->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT | SRP_BUF_FORMAT_INDIRECT);
+
+ vscsi_send_iu(s, req, sizeof(*rsp), VIOSRP_SRP_FORMAT);
+}
+
+static void vscsi_inquiry_no_target(VSCSIState *s, vscsi_req *req)
+{
+ uint8_t *cdb = req->iu.srp.cmd.cdb;
+ uint8_t resp_data[36];
+ int rc, len, alen;
+
+ /* We dont do EVPD. Also check that page_code is 0 */
+ if ((cdb[1] & 0x01) || cdb[2] != 0) {
+ /* Send INVALID FIELD IN CDB */
+ vscsi_makeup_sense(s, req, ILLEGAL_REQUEST, 0x24, 0);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ return;
+ }
+ alen = cdb[3];
+ alen = (alen << 8) | cdb[4];
+ len = MIN(alen, 36);
+
+ /* Fake up inquiry using PQ=3 */
+ memset(resp_data, 0, 36);
+ resp_data[0] = 0x7f; /* Not capable of supporting a device here */
+ resp_data[2] = 0x06; /* SPS-4 */
+ resp_data[3] = 0x02; /* Resp data format */
+ resp_data[4] = 36 - 5; /* Additional length */
+ resp_data[7] = 0x10; /* Sync transfers */
+ memcpy(&resp_data[16], "QEMU EMPTY ", 16);
+ memcpy(&resp_data[8], "QEMU ", 8);
+
+ req->writing = 0;
+ vscsi_preprocess_desc(req);
+ rc = vscsi_srp_transfer_data(s, req, 0, resp_data, len);
+ if (rc < 0) {
+ vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ } else {
+ vscsi_send_rsp(s, req, 0, 36 - rc, 0);
+ }
+}
+
+static void vscsi_report_luns(VSCSIState *s, vscsi_req *req)
+{
+ BusChild *kid;
+ int i, len, n, rc;
+ uint8_t *resp_data;
+ bool found_lun0;
+
+ n = 0;
+ found_lun0 = false;
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *dev = SCSI_DEVICE(kid->child);
+
+ n += 8;
+ if (dev->channel == 0 && dev->id == 0 && dev->lun == 0) {
+ found_lun0 = true;
+ }
+ }
+ if (!found_lun0) {
+ n += 8;
+ }
+ len = n+8;
+
+ resp_data = g_malloc0(len);
+ memset(resp_data, 0, len);
+ stl_be_p(resp_data, n);
+ i = found_lun0 ? 8 : 16;
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->id == 0 && dev->channel == 0) {
+ resp_data[i] = 0; /* Use simple LUN for 0 (SAM5 4.7.7.1) */
+ } else {
+ resp_data[i] = (2 << 6); /* Otherwise LUN addressing (4.7.7.4) */
+ }
+ resp_data[i] |= dev->id;
+ resp_data[i+1] = (dev->channel << 5);
+ resp_data[i+1] |= dev->lun;
+ i += 8;
+ }
+
+ vscsi_preprocess_desc(req);
+ rc = vscsi_srp_transfer_data(s, req, 0, resp_data, len);
+ g_free(resp_data);
+ if (rc < 0) {
+ vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ } else {
+ vscsi_send_rsp(s, req, 0, len - rc, 0);
+ }
+}
+
+static int vscsi_queue_cmd(VSCSIState *s, vscsi_req *req)
+{
+ union srp_iu *srp = &req->iu.srp;
+ SCSIDevice *sdev;
+ int n, lun;
+
+ if ((srp->cmd.lun == 0 || be64_to_cpu(srp->cmd.lun) == SRP_REPORT_LUNS_WLUN)
+ && srp->cmd.cdb[0] == REPORT_LUNS) {
+ vscsi_report_luns(s, req);
+ return 0;
+ }
+
+ sdev = vscsi_device_find(&s->bus, be64_to_cpu(srp->cmd.lun), &lun);
+ if (!sdev) {
+ DPRINTF("VSCSI: Command for lun %08" PRIx64 " with no drive\n",
+ be64_to_cpu(srp->cmd.lun));
+ if (srp->cmd.cdb[0] == INQUIRY) {
+ vscsi_inquiry_no_target(s, req);
+ } else {
+ vscsi_makeup_sense(s, req, ILLEGAL_REQUEST, 0x24, 0x00);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ } return 1;
+ }
+
+ req->sreq = scsi_req_new(sdev, req->qtag, lun, srp->cmd.cdb, req);
+ n = scsi_req_enqueue(req->sreq);
+
+ DPRINTF("VSCSI: Queued command tag 0x%x CMD 0x%x=%s LUN %d ret: %d\n",
+ req->qtag, srp->cmd.cdb[0], scsi_command_name(srp->cmd.cdb[0]),
+ lun, n);
+
+ if (n) {
+ /* Transfer direction must be set before preprocessing the
+ * descriptors
+ */
+ req->writing = (n < 1);
+
+ /* Preprocess RDMA descriptors */
+ vscsi_preprocess_desc(req);
+
+ /* Get transfer direction and initiate transfer */
+ if (n > 0) {
+ req->data_len = n;
+ } else if (n < 0) {
+ req->data_len = -n;
+ }
+ scsi_req_continue(req->sreq);
+ }
+ /* Don't touch req here, it may have been recycled already */
+
+ return 0;
+}
+
+static int vscsi_process_tsk_mgmt(VSCSIState *s, vscsi_req *req)
+{
+ union viosrp_iu *iu = &req->iu;
+ vscsi_req *tmpreq;
+ int i, lun = 0, resp = SRP_TSK_MGMT_COMPLETE;
+ SCSIDevice *d;
+ uint64_t tag = iu->srp.rsp.tag;
+ uint8_t sol_not = iu->srp.cmd.sol_not;
+
+ fprintf(stderr, "vscsi_process_tsk_mgmt %02x\n",
+ iu->srp.tsk_mgmt.tsk_mgmt_func);
+
+ d = vscsi_device_find(&s->bus, be64_to_cpu(req->iu.srp.tsk_mgmt.lun), &lun);
+ if (!d) {
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ } else {
+ switch (iu->srp.tsk_mgmt.tsk_mgmt_func) {
+ case SRP_TSK_ABORT_TASK:
+ if (d->lun != lun) {
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ break;
+ }
+
+ tmpreq = vscsi_find_req(s, req->iu.srp.tsk_mgmt.task_tag);
+ if (tmpreq && tmpreq->sreq) {
+ assert(tmpreq->sreq->hba_private);
+ scsi_req_cancel(tmpreq->sreq);
+ }
+ break;
+
+ case SRP_TSK_LUN_RESET:
+ if (d->lun != lun) {
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ break;
+ }
+
+ qdev_reset_all(&d->qdev);
+ break;
+
+ case SRP_TSK_ABORT_TASK_SET:
+ case SRP_TSK_CLEAR_TASK_SET:
+ if (d->lun != lun) {
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ break;
+ }
+
+ for (i = 0; i < VSCSI_REQ_LIMIT; i++) {
+ tmpreq = &s->reqs[i];
+ if (tmpreq->iu.srp.cmd.lun != req->iu.srp.tsk_mgmt.lun) {
+ continue;
+ }
+ if (!tmpreq->active || !tmpreq->sreq) {
+ continue;
+ }
+ assert(tmpreq->sreq->hba_private);
+ scsi_req_cancel(tmpreq->sreq);
+ }
+ break;
+
+ case SRP_TSK_CLEAR_ACA:
+ resp = SRP_TSK_MGMT_NOT_SUPPORTED;
+ break;
+
+ default:
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ break;
+ }
+ }
+
+ /* Compose the response here as */
+ memset(iu, 0, sizeof(struct srp_rsp) + 4);
+ iu->srp.rsp.opcode = SRP_RSP;
+ iu->srp.rsp.req_lim_delta = cpu_to_be32(1);
+ iu->srp.rsp.tag = tag;
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_RSPVALID;
+ iu->srp.rsp.resp_data_len = cpu_to_be32(4);
+ if (resp) {
+ iu->srp.rsp.sol_not = (sol_not & 0x04) >> 2;
+ } else {
+ iu->srp.rsp.sol_not = (sol_not & 0x02) >> 1;
+ }
+
+ iu->srp.rsp.status = GOOD;
+ iu->srp.rsp.data[3] = resp;
+
+ vscsi_send_iu(s, req, sizeof(iu->srp.rsp) + 4, VIOSRP_SRP_FORMAT);
+
+ return 1;
+}
+
+static int vscsi_handle_srp_req(VSCSIState *s, vscsi_req *req)
+{
+ union srp_iu *srp = &req->iu.srp;
+ int done = 1;
+ uint8_t opcode = srp->rsp.opcode;
+
+ switch (opcode) {
+ case SRP_LOGIN_REQ:
+ vscsi_process_login(s, req);
+ break;
+ case SRP_TSK_MGMT:
+ done = vscsi_process_tsk_mgmt(s, req);
+ break;
+ case SRP_CMD:
+ done = vscsi_queue_cmd(s, req);
+ break;
+ case SRP_LOGIN_RSP:
+ case SRP_I_LOGOUT:
+ case SRP_T_LOGOUT:
+ case SRP_RSP:
+ case SRP_CRED_REQ:
+ case SRP_CRED_RSP:
+ case SRP_AER_REQ:
+ case SRP_AER_RSP:
+ fprintf(stderr, "VSCSI: Unsupported opcode %02x\n", opcode);
+ break;
+ default:
+ fprintf(stderr, "VSCSI: Unknown type %02x\n", opcode);
+ }
+
+ return done;
+}
+
+static int vscsi_send_adapter_info(VSCSIState *s, vscsi_req *req)
+{
+ struct viosrp_adapter_info *sinfo;
+ struct mad_adapter_info_data info;
+ int rc;
+
+ sinfo = &req->iu.mad.adapter_info;
+
+#if 0 /* What for ? */
+ rc = spapr_vio_dma_read(&s->vdev, be64_to_cpu(sinfo->buffer),
+ &info, be16_to_cpu(sinfo->common.length));
+ if (rc) {
+ fprintf(stderr, "vscsi_send_adapter_info: DMA read failure !\n");
+ }
+#endif
+ memset(&info, 0, sizeof(info));
+ strcpy(info.srp_version, SRP_VERSION);
+ memcpy(info.partition_name, "qemu", sizeof("qemu"));
+ info.partition_number = cpu_to_be32(0);
+ info.mad_version = cpu_to_be32(1);
+ info.os_type = cpu_to_be32(2);
+ info.port_max_txu[0] = cpu_to_be32(VSCSI_MAX_SECTORS << 9);
+
+ rc = spapr_vio_dma_write(&s->vdev, be64_to_cpu(sinfo->buffer),
+ &info, be16_to_cpu(sinfo->common.length));
+ if (rc) {
+ fprintf(stderr, "vscsi_send_adapter_info: DMA write failure !\n");
+ }
+
+ sinfo->common.status = rc ? cpu_to_be32(1) : 0;
+
+ return vscsi_send_iu(s, req, sizeof(*sinfo), VIOSRP_MAD_FORMAT);
+}
+
+static int vscsi_send_capabilities(VSCSIState *s, vscsi_req *req)
+{
+ struct viosrp_capabilities *vcap;
+ struct capabilities cap = { };
+ uint16_t len, req_len;
+ uint64_t buffer;
+ int rc;
+
+ vcap = &req->iu.mad.capabilities;
+ req_len = len = be16_to_cpu(vcap->common.length);
+ buffer = be64_to_cpu(vcap->buffer);
+ if (len > sizeof(cap)) {
+ fprintf(stderr, "vscsi_send_capabilities: capabilities size mismatch !\n");
+
+ /*
+ * Just read and populate the structure that is known.
+ * Zero rest of the structure.
+ */
+ len = sizeof(cap);
+ }
+ rc = spapr_vio_dma_read(&s->vdev, buffer, &cap, len);
+ if (rc) {
+ fprintf(stderr, "vscsi_send_capabilities: DMA read failure !\n");
+ }
+
+ /*
+ * Current implementation does not suppport any migration or
+ * reservation capabilities. Construct the response telling the
+ * guest not to use them.
+ */
+ cap.flags = 0;
+ cap.migration.ecl = 0;
+ cap.reserve.type = 0;
+ cap.migration.common.server_support = 0;
+ cap.reserve.common.server_support = 0;
+
+ rc = spapr_vio_dma_write(&s->vdev, buffer, &cap, len);
+ if (rc) {
+ fprintf(stderr, "vscsi_send_capabilities: DMA write failure !\n");
+ }
+ if (req_len > len) {
+ /*
+ * Being paranoid and lets not worry about the error code
+ * here. Actual write of the cap is done above.
+ */
+ spapr_vio_dma_set(&s->vdev, (buffer + len), 0, (req_len - len));
+ }
+ vcap->common.status = rc ? cpu_to_be32(1) : 0;
+ return vscsi_send_iu(s, req, sizeof(*vcap), VIOSRP_MAD_FORMAT);
+}
+
+static int vscsi_handle_mad_req(VSCSIState *s, vscsi_req *req)
+{
+ union mad_iu *mad = &req->iu.mad;
+ bool request_handled = false;
+ uint64_t retlen = 0;
+
+ switch (be32_to_cpu(mad->empty_iu.common.type)) {
+ case VIOSRP_EMPTY_IU_TYPE:
+ fprintf(stderr, "Unsupported EMPTY MAD IU\n");
+ retlen = sizeof(mad->empty_iu);
+ break;
+ case VIOSRP_ERROR_LOG_TYPE:
+ fprintf(stderr, "Unsupported ERROR LOG MAD IU\n");
+ retlen = sizeof(mad->error_log);
+ break;
+ case VIOSRP_ADAPTER_INFO_TYPE:
+ vscsi_send_adapter_info(s, req);
+ request_handled = true;
+ break;
+ case VIOSRP_HOST_CONFIG_TYPE:
+ retlen = sizeof(mad->host_config);
+ break;
+ case VIOSRP_CAPABILITIES_TYPE:
+ vscsi_send_capabilities(s, req);
+ request_handled = true;
+ break;
+ default:
+ fprintf(stderr, "VSCSI: Unknown MAD type %02x\n",
+ be32_to_cpu(mad->empty_iu.common.type));
+ /*
+ * PAPR+ says that "The length field is set to the length
+ * of the data structure(s) used in the command".
+ * As we did not recognize the request type, put zero there.
+ */
+ retlen = 0;
+ }
+
+ if (!request_handled) {
+ mad->empty_iu.common.status = cpu_to_be16(VIOSRP_MAD_NOT_SUPPORTED);
+ vscsi_send_iu(s, req, retlen, VIOSRP_MAD_FORMAT);
+ }
+
+ return 1;
+}
+
+static void vscsi_got_payload(VSCSIState *s, vscsi_crq *crq)
+{
+ vscsi_req *req;
+ int done;
+
+ req = vscsi_get_req(s);
+ if (req == NULL) {
+ fprintf(stderr, "VSCSI: Failed to get a request !\n");
+ return;
+ }
+
+ /* We only support a limited number of descriptors, we know
+ * the ibmvscsi driver uses up to 10 max, so it should fit
+ * in our 256 bytes IUs. If not we'll have to increase the size
+ * of the structure.
+ */
+ if (crq->s.IU_length > sizeof(union viosrp_iu)) {
+ fprintf(stderr, "VSCSI: SRP IU too long (%d bytes) !\n",
+ crq->s.IU_length);
+ vscsi_put_req(req);
+ return;
+ }
+
+ /* XXX Handle failure differently ? */
+ if (spapr_vio_dma_read(&s->vdev, crq->s.IU_data_ptr, &req->iu,
+ crq->s.IU_length)) {
+ fprintf(stderr, "vscsi_got_payload: DMA read failure !\n");
+ vscsi_put_req(req);
+ return;
+ }
+ memcpy(&req->crq, crq, sizeof(vscsi_crq));
+
+ if (crq->s.format == VIOSRP_MAD_FORMAT) {
+ done = vscsi_handle_mad_req(s, req);
+ } else {
+ done = vscsi_handle_srp_req(s, req);
+ }
+
+ if (done) {
+ vscsi_put_req(req);
+ }
+}
+
+
+static int vscsi_do_crq(struct VIOsPAPRDevice *dev, uint8_t *crq_data)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(dev);
+ vscsi_crq crq;
+
+ memcpy(crq.raw, crq_data, 16);
+ crq.s.timeout = be16_to_cpu(crq.s.timeout);
+ crq.s.IU_length = be16_to_cpu(crq.s.IU_length);
+ crq.s.IU_data_ptr = be64_to_cpu(crq.s.IU_data_ptr);
+
+ DPRINTF("VSCSI: do_crq %02x %02x ...\n", crq.raw[0], crq.raw[1]);
+
+ switch (crq.s.valid) {
+ case 0xc0: /* Init command/response */
+
+ /* Respond to initialization request */
+ if (crq.s.format == 0x01) {
+ memset(crq.raw, 0, 16);
+ crq.s.valid = 0xc0;
+ crq.s.format = 0x02;
+ spapr_vio_send_crq(dev, crq.raw);
+ }
+
+ /* Note that in hotplug cases, we might get a 0x02
+ * as a result of us emitting the init request
+ */
+
+ break;
+ case 0xff: /* Link event */
+
+ /* Not handled for now */
+
+ break;
+ case 0x80: /* Payloads */
+ switch (crq.s.format) {
+ case VIOSRP_SRP_FORMAT: /* AKA VSCSI request */
+ case VIOSRP_MAD_FORMAT: /* AKA VSCSI response */
+ vscsi_got_payload(s, &crq);
+ break;
+ case VIOSRP_OS400_FORMAT:
+ case VIOSRP_AIX_FORMAT:
+ case VIOSRP_LINUX_FORMAT:
+ case VIOSRP_INLINE_FORMAT:
+ fprintf(stderr, "vscsi_do_srq: Unsupported payload format %02x\n",
+ crq.s.format);
+ break;
+ default:
+ fprintf(stderr, "vscsi_do_srq: Unknown payload format %02x\n",
+ crq.s.format);
+ }
+ break;
+ default:
+ fprintf(stderr, "vscsi_do_crq: unknown CRQ %02x %02x ...\n",
+ crq.raw[0], crq.raw[1]);
+ };
+
+ return 0;
+}
+
+static const struct SCSIBusInfo vscsi_scsi_info = {
+ .tcq = true,
+ .max_channel = 7, /* logical unit addressing format */
+ .max_target = 63,
+ .max_lun = 31,
+
+ .transfer_data = vscsi_transfer_data,
+ .complete = vscsi_command_complete,
+ .cancel = vscsi_request_cancelled,
+ .save_request = vscsi_save_request,
+ .load_request = vscsi_load_request,
+};
+
+static void spapr_vscsi_reset(VIOsPAPRDevice *dev)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(dev);
+ int i;
+
+ memset(s->reqs, 0, sizeof(s->reqs));
+ for (i = 0; i < VSCSI_REQ_LIMIT; i++) {
+ s->reqs[i].qtag = i;
+ }
+}
+
+static void spapr_vscsi_realize(VIOsPAPRDevice *dev, Error **errp)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(dev);
+
+ dev->crq.SendFunc = vscsi_do_crq;
+
+ scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev),
+ &vscsi_scsi_info, NULL);
+ if (!dev->qdev.hotplugged) {
+ scsi_bus_legacy_handle_cmdline(&s->bus, errp);
+ }
+}
+
+void spapr_vscsi_create(VIOsPAPRBus *bus)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->bus, "spapr-vscsi");
+
+ qdev_init_nofail(dev);
+}
+
+static int spapr_vscsi_devnode(VIOsPAPRDevice *dev, void *fdt, int node_off)
+{
+ int ret;
+
+ ret = fdt_setprop_cell(fdt, node_off, "#address-cells", 2);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = fdt_setprop_cell(fdt, node_off, "#size-cells", 0);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static Property spapr_vscsi_properties[] = {
+ DEFINE_SPAPR_PROPERTIES(VSCSIState, vdev),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_spapr_vscsi = {
+ .name = "spapr_vscsi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_SPAPR_VIO(vdev, VSCSIState),
+ /* VSCSI state */
+ /* ???? */
+
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void spapr_vscsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VIOsPAPRDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
+
+ k->realize = spapr_vscsi_realize;
+ k->reset = spapr_vscsi_reset;
+ k->devnode = spapr_vscsi_devnode;
+ k->dt_name = "v-scsi";
+ k->dt_type = "vscsi";
+ k->dt_compatible = "IBM,v-scsi";
+ k->signal_mask = 0x00000001;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->props = spapr_vscsi_properties;
+ k->rtce_window_size = 0x10000000;
+ dc->vmsd = &vmstate_spapr_vscsi;
+}
+
+static const TypeInfo spapr_vscsi_info = {
+ .name = TYPE_VIO_SPAPR_VSCSI_DEVICE,
+ .parent = TYPE_VIO_SPAPR_DEVICE,
+ .instance_size = sizeof(VSCSIState),
+ .class_init = spapr_vscsi_class_init,
+};
+
+static void spapr_vscsi_register_types(void)
+{
+ type_register_static(&spapr_vscsi_info);
+}
+
+type_init(spapr_vscsi_register_types)
diff --git a/hw/scsi/srp.h b/hw/scsi/srp.h
new file mode 100644
index 00000000..d27f31d2
--- /dev/null
+++ b/hw/scsi/srp.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2005 Cisco Systems. All rights reserved.
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available from the file
+ * COPYING in the main directory of this source tree, or the
+ * OpenIB.org BSD license below:
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SCSI_SRP_H
+#define SCSI_SRP_H
+
+/*
+ * Structures and constants for the SCSI RDMA Protocol (SRP) as
+ * defined by the INCITS T10 committee. This file was written using
+ * draft Revision 16a of the SRP standard.
+ */
+
+enum {
+
+ SRP_LOGIN_REQ = 0x00,
+ SRP_TSK_MGMT = 0x01,
+ SRP_CMD = 0x02,
+ SRP_I_LOGOUT = 0x03,
+ SRP_LOGIN_RSP = 0xc0,
+ SRP_RSP = 0xc1,
+ SRP_LOGIN_REJ = 0xc2,
+ SRP_T_LOGOUT = 0x80,
+ SRP_CRED_REQ = 0x81,
+ SRP_AER_REQ = 0x82,
+ SRP_CRED_RSP = 0x41,
+ SRP_AER_RSP = 0x42
+};
+
+enum {
+ SRP_BUF_FORMAT_DIRECT = 1 << 1,
+ SRP_BUF_FORMAT_INDIRECT = 1 << 2
+};
+
+enum {
+ SRP_NO_DATA_DESC = 0,
+ SRP_DATA_DESC_DIRECT = 1,
+ SRP_DATA_DESC_INDIRECT = 2
+};
+
+enum {
+ SRP_TSK_ABORT_TASK = 0x01,
+ SRP_TSK_ABORT_TASK_SET = 0x02,
+ SRP_TSK_CLEAR_TASK_SET = 0x04,
+ SRP_TSK_LUN_RESET = 0x08,
+ SRP_TSK_CLEAR_ACA = 0x40
+};
+
+enum srp_login_rej_reason {
+ SRP_LOGIN_REJ_UNABLE_ESTABLISH_CHANNEL = 0x00010000,
+ SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES = 0x00010001,
+ SRP_LOGIN_REJ_REQ_IT_IU_LENGTH_TOO_LARGE = 0x00010002,
+ SRP_LOGIN_REJ_UNABLE_ASSOCIATE_CHANNEL = 0x00010003,
+ SRP_LOGIN_REJ_UNSUPPORTED_DESCRIPTOR_FMT = 0x00010004,
+ SRP_LOGIN_REJ_MULTI_CHANNEL_UNSUPPORTED = 0x00010005,
+ SRP_LOGIN_REJ_CHANNEL_LIMIT_REACHED = 0x00010006
+};
+
+enum {
+ SRP_REV10_IB_IO_CLASS = 0xff00,
+ SRP_REV16A_IB_IO_CLASS = 0x0100
+};
+
+enum {
+ SRP_TSK_MGMT_COMPLETE = 0x00,
+ SRP_TSK_MGMT_FIELDS_INVALID = 0x02,
+ SRP_TSK_MGMT_NOT_SUPPORTED = 0x04,
+ SRP_TSK_MGMT_FAILED = 0x05
+};
+
+struct srp_direct_buf {
+ uint64_t va;
+ uint32_t key;
+ uint32_t len;
+};
+
+/*
+ * We need the packed attribute because the SRP spec puts the list of
+ * descriptors at an offset of 20, which is not aligned to the size of
+ * struct srp_direct_buf. The whole structure must be packed to avoid
+ * having the 20-byte structure padded to 24 bytes on 64-bit architectures.
+ */
+struct srp_indirect_buf {
+ struct srp_direct_buf table_desc;
+ uint32_t len;
+ struct srp_direct_buf desc_list[0];
+} QEMU_PACKED;
+
+enum {
+ SRP_MULTICHAN_SINGLE = 0,
+ SRP_MULTICHAN_MULTI = 1
+};
+
+struct srp_login_req {
+ uint8_t opcode;
+ uint8_t reserved1[7];
+ uint64_t tag;
+ uint32_t req_it_iu_len;
+ uint8_t reserved2[4];
+ uint16_t req_buf_fmt;
+ uint8_t req_flags;
+ uint8_t reserved3[5];
+ uint8_t initiator_port_id[16];
+ uint8_t target_port_id[16];
+};
+
+/*
+ * The SRP spec defines the size of the LOGIN_RSP structure to be 52
+ * bytes, so it needs to be packed to avoid having it padded to 56
+ * bytes on 64-bit architectures.
+ */
+struct srp_login_rsp {
+ uint8_t opcode;
+ uint8_t reserved1[3];
+ uint32_t req_lim_delta;
+ uint64_t tag;
+ uint32_t max_it_iu_len;
+ uint32_t max_ti_iu_len;
+ uint16_t buf_fmt;
+ uint8_t rsp_flags;
+ uint8_t reserved2[25];
+} QEMU_PACKED;
+
+struct srp_login_rej {
+ uint8_t opcode;
+ uint8_t reserved1[3];
+ uint32_t reason;
+ uint64_t tag;
+ uint8_t reserved2[8];
+ uint16_t buf_fmt;
+ uint8_t reserved3[6];
+};
+
+struct srp_i_logout {
+ uint8_t opcode;
+ uint8_t reserved[7];
+ uint64_t tag;
+};
+
+struct srp_t_logout {
+ uint8_t opcode;
+ uint8_t sol_not;
+ uint8_t reserved[2];
+ uint32_t reason;
+ uint64_t tag;
+};
+
+/*
+ * We need the packed attribute because the SRP spec only aligns the
+ * 8-byte LUN field to 4 bytes.
+ */
+struct srp_tsk_mgmt {
+ uint8_t opcode;
+ uint8_t sol_not;
+ uint8_t reserved1[6];
+ uint64_t tag;
+ uint8_t reserved2[4];
+ uint64_t lun;
+ uint8_t reserved3[2];
+ uint8_t tsk_mgmt_func;
+ uint8_t reserved4;
+ uint64_t task_tag;
+ uint8_t reserved5[8];
+} QEMU_PACKED;
+
+/*
+ * We need the packed attribute because the SRP spec only aligns the
+ * 8-byte LUN field to 4 bytes.
+ */
+struct srp_cmd {
+ uint8_t opcode;
+ uint8_t sol_not;
+ uint8_t reserved1[3];
+ uint8_t buf_fmt;
+ uint8_t data_out_desc_cnt;
+ uint8_t data_in_desc_cnt;
+ uint64_t tag;
+ uint8_t reserved2[4];
+ uint64_t lun;
+ uint8_t reserved3;
+ uint8_t task_attr;
+ uint8_t reserved4;
+ uint8_t add_cdb_len;
+ uint8_t cdb[16];
+ uint8_t add_data[0];
+} QEMU_PACKED;
+
+enum {
+ SRP_RSP_FLAG_RSPVALID = 1 << 0,
+ SRP_RSP_FLAG_SNSVALID = 1 << 1,
+ SRP_RSP_FLAG_DOOVER = 1 << 2,
+ SRP_RSP_FLAG_DOUNDER = 1 << 3,
+ SRP_RSP_FLAG_DIOVER = 1 << 4,
+ SRP_RSP_FLAG_DIUNDER = 1 << 5
+};
+
+/*
+ * The SRP spec defines the size of the RSP structure to be 36 bytes,
+ * so it needs to be packed to avoid having it padded to 40 bytes on
+ * 64-bit architectures.
+ */
+struct srp_rsp {
+ uint8_t opcode;
+ uint8_t sol_not;
+ uint8_t reserved1[2];
+ uint32_t req_lim_delta;
+ uint64_t tag;
+ uint8_t reserved2[2];
+ uint8_t flags;
+ uint8_t status;
+ uint32_t data_out_res_cnt;
+ uint32_t data_in_res_cnt;
+ uint32_t sense_data_len;
+ uint32_t resp_data_len;
+ uint8_t data[0];
+} QEMU_PACKED;
+
+#endif /* SCSI_SRP_H */
diff --git a/hw/scsi/vhost-scsi.c b/hw/scsi/vhost-scsi.c
new file mode 100644
index 00000000..62d91003
--- /dev/null
+++ b/hw/scsi/vhost-scsi.c
@@ -0,0 +1,351 @@
+/*
+ * vhost_scsi host device
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
+ *
+ * Changes for QEMU mainline + tcm_vhost kernel upstream:
+ * Nicholas Bellinger <nab@risingtidesystems.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include <sys/ioctl.h>
+#include "config.h"
+#include "qemu/error-report.h"
+#include "qemu/queue.h"
+#include "monitor/monitor.h"
+#include "migration/migration.h"
+#include "hw/virtio/vhost-scsi.h"
+#include "hw/virtio/vhost.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+#include "hw/fw-path-provider.h"
+
+/* Features supported by host kernel. */
+static const int kernel_feature_bits[] = {
+ VIRTIO_F_NOTIFY_ON_EMPTY,
+ VIRTIO_RING_F_INDIRECT_DESC,
+ VIRTIO_RING_F_EVENT_IDX,
+ VIRTIO_SCSI_F_HOTPLUG,
+ VHOST_INVALID_FEATURE_BIT
+};
+
+static int vhost_scsi_set_endpoint(VHostSCSI *s)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ const VhostOps *vhost_ops = s->dev.vhost_ops;
+ struct vhost_scsi_target backend;
+ int ret;
+
+ memset(&backend, 0, sizeof(backend));
+ pstrcpy(backend.vhost_wwpn, sizeof(backend.vhost_wwpn), vs->conf.wwpn);
+ ret = vhost_ops->vhost_call(&s->dev, VHOST_SCSI_SET_ENDPOINT, &backend);
+ if (ret < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+static void vhost_scsi_clear_endpoint(VHostSCSI *s)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ struct vhost_scsi_target backend;
+ const VhostOps *vhost_ops = s->dev.vhost_ops;
+
+ memset(&backend, 0, sizeof(backend));
+ pstrcpy(backend.vhost_wwpn, sizeof(backend.vhost_wwpn), vs->conf.wwpn);
+ vhost_ops->vhost_call(&s->dev, VHOST_SCSI_CLEAR_ENDPOINT, &backend);
+}
+
+static int vhost_scsi_start(VHostSCSI *s)
+{
+ int ret, abi_version, i;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ const VhostOps *vhost_ops = s->dev.vhost_ops;
+
+ if (!k->set_guest_notifiers) {
+ error_report("binding does not support guest notifiers");
+ return -ENOSYS;
+ }
+
+ ret = vhost_ops->vhost_call(&s->dev,
+ VHOST_SCSI_GET_ABI_VERSION, &abi_version);
+ if (ret < 0) {
+ return -errno;
+ }
+ if (abi_version > VHOST_SCSI_ABI_VERSION) {
+ error_report("vhost-scsi: The running tcm_vhost kernel abi_version:"
+ " %d is greater than vhost_scsi userspace supports: %d, please"
+ " upgrade your version of QEMU", abi_version,
+ VHOST_SCSI_ABI_VERSION);
+ return -ENOSYS;
+ }
+
+ ret = vhost_dev_enable_notifiers(&s->dev, vdev);
+ if (ret < 0) {
+ return ret;
+ }
+
+ s->dev.acked_features = vdev->guest_features;
+ ret = vhost_dev_start(&s->dev, vdev);
+ if (ret < 0) {
+ error_report("Error start vhost dev");
+ goto err_notifiers;
+ }
+
+ ret = vhost_scsi_set_endpoint(s);
+ if (ret < 0) {
+ error_report("Error set vhost-scsi endpoint");
+ goto err_vhost_stop;
+ }
+
+ ret = k->set_guest_notifiers(qbus->parent, s->dev.nvqs, true);
+ if (ret < 0) {
+ error_report("Error binding guest notifier");
+ goto err_endpoint;
+ }
+
+ /* guest_notifier_mask/pending not used yet, so just unmask
+ * everything here. virtio-pci will do the right thing by
+ * enabling/disabling irqfd.
+ */
+ for (i = 0; i < s->dev.nvqs; i++) {
+ vhost_virtqueue_mask(&s->dev, vdev, i, false);
+ }
+
+ return ret;
+
+err_endpoint:
+ vhost_scsi_clear_endpoint(s);
+err_vhost_stop:
+ vhost_dev_stop(&s->dev, vdev);
+err_notifiers:
+ vhost_dev_disable_notifiers(&s->dev, vdev);
+ return ret;
+}
+
+static void vhost_scsi_stop(VHostSCSI *s)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ int ret = 0;
+
+ if (k->set_guest_notifiers) {
+ ret = k->set_guest_notifiers(qbus->parent, s->dev.nvqs, false);
+ if (ret < 0) {
+ error_report("vhost guest notifier cleanup failed: %d", ret);
+ }
+ }
+ assert(ret >= 0);
+
+ vhost_scsi_clear_endpoint(s);
+ vhost_dev_stop(&s->dev, vdev);
+ vhost_dev_disable_notifiers(&s->dev, vdev);
+}
+
+static uint64_t vhost_scsi_get_features(VirtIODevice *vdev,
+ uint64_t features,
+ Error **errp)
+{
+ VHostSCSI *s = VHOST_SCSI(vdev);
+
+ return vhost_get_features(&s->dev, kernel_feature_bits, features);
+}
+
+static void vhost_scsi_set_config(VirtIODevice *vdev,
+ const uint8_t *config)
+{
+ VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config;
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+
+ if ((uint32_t) virtio_ldl_p(vdev, &scsiconf->sense_size) != vs->sense_size ||
+ (uint32_t) virtio_ldl_p(vdev, &scsiconf->cdb_size) != vs->cdb_size) {
+ error_report("vhost-scsi does not support changing the sense data and CDB sizes");
+ exit(1);
+ }
+}
+
+static void vhost_scsi_set_status(VirtIODevice *vdev, uint8_t val)
+{
+ VHostSCSI *s = (VHostSCSI *)vdev;
+ bool start = (val & VIRTIO_CONFIG_S_DRIVER_OK);
+
+ if (s->dev.started == start) {
+ return;
+ }
+
+ if (start) {
+ int ret;
+
+ ret = vhost_scsi_start(s);
+ if (ret < 0) {
+ error_report("virtio-scsi: unable to start vhost: %s",
+ strerror(-ret));
+
+ /* There is no userspace virtio-scsi fallback so exit */
+ exit(1);
+ }
+ } else {
+ vhost_scsi_stop(s);
+ }
+}
+
+static void vhost_dummy_handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+}
+
+static void vhost_scsi_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
+ VHostSCSI *s = VHOST_SCSI(dev);
+ Error *err = NULL;
+ int vhostfd = -1;
+ int ret;
+
+ if (!vs->conf.wwpn) {
+ error_setg(errp, "vhost-scsi: missing wwpn");
+ return;
+ }
+
+ if (vs->conf.vhostfd) {
+ vhostfd = monitor_fd_param(cur_mon, vs->conf.vhostfd, &err);
+ if (vhostfd == -1) {
+ error_setg(errp, "vhost-scsi: unable to parse vhostfd: %s",
+ error_get_pretty(err));
+ error_free(err);
+ return;
+ }
+ } else {
+ vhostfd = open("/dev/vhost-scsi", O_RDWR);
+ if (vhostfd < 0) {
+ error_setg(errp, "vhost-scsi: open vhost char device failed: %s",
+ strerror(errno));
+ return;
+ }
+ }
+
+ virtio_scsi_common_realize(dev, &err, vhost_dummy_handle_output,
+ vhost_dummy_handle_output,
+ vhost_dummy_handle_output);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ close(vhostfd);
+ return;
+ }
+
+ s->dev.nvqs = VHOST_SCSI_VQ_NUM_FIXED + vs->conf.num_queues;
+ s->dev.vqs = g_new(struct vhost_virtqueue, s->dev.nvqs);
+ s->dev.vq_index = 0;
+ s->dev.backend_features = 0;
+
+ ret = vhost_dev_init(&s->dev, (void *)(uintptr_t)vhostfd,
+ VHOST_BACKEND_TYPE_KERNEL);
+ if (ret < 0) {
+ error_setg(errp, "vhost-scsi: vhost initialization failed: %s",
+ strerror(-ret));
+ return;
+ }
+
+ /* At present, channel and lun both are 0 for bootable vhost-scsi disk */
+ s->channel = 0;
+ s->lun = 0;
+ /* Note: we can also get the minimum tpgt from kernel */
+ s->target = vs->conf.boot_tpgt;
+
+ error_setg(&s->migration_blocker,
+ "vhost-scsi does not support migration");
+ migrate_add_blocker(s->migration_blocker);
+}
+
+static void vhost_scsi_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VHostSCSI *s = VHOST_SCSI(dev);
+
+ migrate_del_blocker(s->migration_blocker);
+ error_free(s->migration_blocker);
+
+ /* This will stop vhost backend. */
+ vhost_scsi_set_status(vdev, 0);
+
+ g_free(s->dev.vqs);
+
+ virtio_scsi_common_unrealize(dev, errp);
+}
+
+/*
+ * Implementation of an interface to adjust firmware path
+ * for the bootindex property handling.
+ */
+static char *vhost_scsi_get_fw_dev_path(FWPathProvider *p, BusState *bus,
+ DeviceState *dev)
+{
+ VHostSCSI *s = VHOST_SCSI(dev);
+ /* format: channel@channel/vhost-scsi@target,lun */
+ return g_strdup_printf("/channel@%x/%s@%x,%x", s->channel,
+ qdev_fw_name(dev), s->target, s->lun);
+}
+
+static Property vhost_scsi_properties[] = {
+ DEFINE_PROP_STRING("vhostfd", VHostSCSI, parent_obj.conf.vhostfd),
+ DEFINE_PROP_STRING("wwpn", VHostSCSI, parent_obj.conf.wwpn),
+ DEFINE_PROP_UINT32("boot_tpgt", VHostSCSI, parent_obj.conf.boot_tpgt, 0),
+ DEFINE_PROP_UINT32("num_queues", VHostSCSI, parent_obj.conf.num_queues, 1),
+ DEFINE_PROP_UINT32("max_sectors", VHostSCSI, parent_obj.conf.max_sectors,
+ 0xFFFF),
+ DEFINE_PROP_UINT32("cmd_per_lun", VHostSCSI, parent_obj.conf.cmd_per_lun,
+ 128),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vhost_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ FWPathProviderClass *fwc = FW_PATH_PROVIDER_CLASS(klass);
+
+ dc->props = vhost_scsi_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ vdc->realize = vhost_scsi_realize;
+ vdc->unrealize = vhost_scsi_unrealize;
+ vdc->get_features = vhost_scsi_get_features;
+ vdc->set_config = vhost_scsi_set_config;
+ vdc->set_status = vhost_scsi_set_status;
+ fwc->get_dev_path = vhost_scsi_get_fw_dev_path;
+}
+
+static void vhost_scsi_instance_init(Object *obj)
+{
+ VHostSCSI *dev = VHOST_SCSI(obj);
+
+ device_add_bootindex_property(obj, &dev->bootindex, "bootindex", NULL,
+ DEVICE(dev), NULL);
+}
+
+static const TypeInfo vhost_scsi_info = {
+ .name = TYPE_VHOST_SCSI,
+ .parent = TYPE_VIRTIO_SCSI_COMMON,
+ .instance_size = sizeof(VHostSCSI),
+ .class_init = vhost_scsi_class_init,
+ .instance_init = vhost_scsi_instance_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_FW_PATH_PROVIDER },
+ { }
+ },
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&vhost_scsi_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/scsi/viosrp.h b/hw/scsi/viosrp.h
new file mode 100644
index 00000000..d8e365db
--- /dev/null
+++ b/hw/scsi/viosrp.h
@@ -0,0 +1,216 @@
+/*****************************************************************************/
+/* srp.h -- SCSI RDMA Protocol definitions */
+/* */
+/* Written By: Colin Devilbis, IBM Corporation */
+/* */
+/* Copyright (C) 2003 IBM Corporation */
+/* */
+/* This program is free software; you can redistribute it and/or modify */
+/* it under the terms of the GNU General Public License as published by */
+/* the Free Software Foundation; either version 2 of the License, or */
+/* (at your option) any later version. */
+/* */
+/* This program is distributed in the hope that it will be useful, */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
+/* GNU General Public License for more details. */
+/* */
+/* You should have received a copy of the GNU General Public License */
+/* along with this program; if not, write to the Free Software */
+/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+/* */
+/* */
+/* This file contains structures and definitions for IBM RPA (RS/6000 */
+/* platform architecture) implementation of the SRP (SCSI RDMA Protocol) */
+/* standard. SRP is used on IBM iSeries and pSeries platforms to send SCSI */
+/* commands between logical partitions. */
+/* */
+/* SRP Information Units (IUs) are sent on a "Command/Response Queue" (CRQ) */
+/* between partitions. The definitions in this file are architected, */
+/* and cannot be changed without breaking compatibility with other versions */
+/* of Linux and other operating systems (AIX, OS/400) that talk this protocol*/
+/* between logical partitions */
+/*****************************************************************************/
+#ifndef PPC_VIOSRP_H
+#define PPC_VIOSRP_H
+
+#define SRP_VERSION "16.a"
+#define SRP_MAX_IU_LEN 256
+#define SRP_MAX_LOC_LEN 32
+
+union srp_iu {
+ struct srp_login_req login_req;
+ struct srp_login_rsp login_rsp;
+ struct srp_login_rej login_rej;
+ struct srp_i_logout i_logout;
+ struct srp_t_logout t_logout;
+ struct srp_tsk_mgmt tsk_mgmt;
+ struct srp_cmd cmd;
+ struct srp_rsp rsp;
+ uint8_t reserved[SRP_MAX_IU_LEN];
+};
+
+enum viosrp_crq_formats {
+ VIOSRP_SRP_FORMAT = 0x01,
+ VIOSRP_MAD_FORMAT = 0x02,
+ VIOSRP_OS400_FORMAT = 0x03,
+ VIOSRP_AIX_FORMAT = 0x04,
+ VIOSRP_LINUX_FORMAT = 0x06,
+ VIOSRP_INLINE_FORMAT = 0x07
+};
+
+enum viosrp_crq_status {
+ VIOSRP_OK = 0x0,
+ VIOSRP_NONRECOVERABLE_ERR = 0x1,
+ VIOSRP_VIOLATES_MAX_XFER = 0x2,
+ VIOSRP_PARTNER_PANIC = 0x3,
+ VIOSRP_DEVICE_BUSY = 0x8,
+ VIOSRP_ADAPTER_FAIL = 0x10,
+ VIOSRP_OK2 = 0x99,
+};
+
+struct viosrp_crq {
+ uint8_t valid; /* used by RPA */
+ uint8_t format; /* SCSI vs out-of-band */
+ uint8_t reserved;
+ uint8_t status; /* non-scsi failure? (e.g. DMA failure) */
+ uint16_t timeout; /* in seconds */
+ uint16_t IU_length; /* in bytes */
+ uint64_t IU_data_ptr; /* the TCE for transferring data */
+};
+
+/* MADs are Management requests above and beyond the IUs defined in the SRP
+ * standard.
+ */
+enum viosrp_mad_types {
+ VIOSRP_EMPTY_IU_TYPE = 0x01,
+ VIOSRP_ERROR_LOG_TYPE = 0x02,
+ VIOSRP_ADAPTER_INFO_TYPE = 0x03,
+ VIOSRP_HOST_CONFIG_TYPE = 0x04,
+ VIOSRP_CAPABILITIES_TYPE = 0x05,
+ VIOSRP_ENABLE_FAST_FAIL = 0x08,
+};
+
+enum viosrp_mad_status {
+ VIOSRP_MAD_SUCCESS = 0x00,
+ VIOSRP_MAD_NOT_SUPPORTED = 0xF1,
+ VIOSRP_MAD_FAILED = 0xF7,
+};
+
+enum viosrp_capability_type {
+ MIGRATION_CAPABILITIES = 0x01,
+ RESERVATION_CAPABILITIES = 0x02,
+};
+
+enum viosrp_capability_support {
+ SERVER_DOES_NOT_SUPPORTS_CAP = 0x0,
+ SERVER_SUPPORTS_CAP = 0x01,
+ SERVER_CAP_DATA = 0x02,
+};
+
+enum viosrp_reserve_type {
+ CLIENT_RESERVE_SCSI_2 = 0x01,
+};
+
+enum viosrp_capability_flag {
+ CLIENT_MIGRATED = 0x01,
+ CLIENT_RECONNECT = 0x02,
+ CAP_LIST_SUPPORTED = 0x04,
+ CAP_LIST_DATA = 0x08,
+};
+
+/*
+ * Common MAD header
+ */
+struct mad_common {
+ uint32_t type;
+ uint16_t status;
+ uint16_t length;
+ uint64_t tag;
+};
+
+/*
+ * All SRP (and MAD) requests normally flow from the
+ * client to the server. There is no way for the server to send
+ * an asynchronous message back to the client. The Empty IU is used
+ * to hang out a meaningless request to the server so that it can respond
+ * asynchrouously with something like a SCSI AER
+ */
+struct viosrp_empty_iu {
+ struct mad_common common;
+ uint64_t buffer;
+ uint32_t port;
+};
+
+struct viosrp_error_log {
+ struct mad_common common;
+ uint64_t buffer;
+};
+
+struct viosrp_adapter_info {
+ struct mad_common common;
+ uint64_t buffer;
+};
+
+struct viosrp_host_config {
+ struct mad_common common;
+ uint64_t buffer;
+};
+
+struct viosrp_fast_fail {
+ struct mad_common common;
+};
+
+struct viosrp_capabilities {
+ struct mad_common common;
+ uint64_t buffer;
+};
+
+struct mad_capability_common {
+ uint32_t cap_type;
+ uint16_t length;
+ uint16_t server_support;
+};
+
+struct mad_reserve_cap {
+ struct mad_capability_common common;
+ uint32_t type;
+};
+
+struct mad_migration_cap {
+ struct mad_capability_common common;
+ uint32_t ecl;
+};
+
+struct capabilities {
+ uint32_t flags;
+ char name[SRP_MAX_LOC_LEN];
+ char loc[SRP_MAX_LOC_LEN];
+ struct mad_migration_cap migration;
+ struct mad_reserve_cap reserve;
+};
+
+union mad_iu {
+ struct viosrp_empty_iu empty_iu;
+ struct viosrp_error_log error_log;
+ struct viosrp_adapter_info adapter_info;
+ struct viosrp_host_config host_config;
+ struct viosrp_fast_fail fast_fail;
+ struct viosrp_capabilities capabilities;
+};
+
+union viosrp_iu {
+ union srp_iu srp;
+ union mad_iu mad;
+};
+
+struct mad_adapter_info_data {
+ char srp_version[8];
+ char partition_name[96];
+ uint32_t partition_number;
+ uint32_t mad_version;
+ uint32_t os_type;
+ uint32_t port_max_txu[8]; /* per-port maximum transfer */
+};
+
+#endif
diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c
new file mode 100644
index 00000000..5575648a
--- /dev/null
+++ b/hw/scsi/virtio-scsi-dataplane.c
@@ -0,0 +1,316 @@
+/*
+ * Virtio SCSI dataplane
+ *
+ * Copyright Red Hat, Inc. 2014
+ *
+ * Authors:
+ * Fam Zheng <famz@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/virtio-scsi.h"
+#include "qemu/error-report.h"
+#include "sysemu/block-backend.h"
+#include <hw/scsi/scsi.h>
+#include <block/scsi.h>
+#include <hw/virtio/virtio-bus.h>
+#include "hw/virtio/virtio-access.h"
+#include "stdio.h"
+
+/* Context: QEMU global mutex held */
+void virtio_scsi_set_iothread(VirtIOSCSI *s, IOThread *iothread)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+
+ assert(!s->ctx);
+ s->ctx = iothread_get_aio_context(vs->conf.iothread);
+
+ /* Don't try if transport does not support notifiers. */
+ if (!k->set_guest_notifiers || !k->set_host_notifier) {
+ fprintf(stderr, "virtio-scsi: Failed to set iothread "
+ "(transport does not support notifiers)");
+ exit(1);
+ }
+}
+
+static VirtIOSCSIVring *virtio_scsi_vring_init(VirtIOSCSI *s,
+ VirtQueue *vq,
+ EventNotifierHandler *handler,
+ int n)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtIOSCSIVring *r;
+ int rc;
+
+ /* Set up virtqueue notify */
+ rc = k->set_host_notifier(qbus->parent, n, true);
+ if (rc != 0) {
+ fprintf(stderr, "virtio-scsi: Failed to set host notifier (%d)\n",
+ rc);
+ s->dataplane_fenced = true;
+ return NULL;
+ }
+
+ r = g_slice_new(VirtIOSCSIVring);
+ r->host_notifier = *virtio_queue_get_host_notifier(vq);
+ r->guest_notifier = *virtio_queue_get_guest_notifier(vq);
+ aio_set_event_notifier(s->ctx, &r->host_notifier, handler);
+
+ r->parent = s;
+
+ if (!vring_setup(&r->vring, VIRTIO_DEVICE(s), n)) {
+ fprintf(stderr, "virtio-scsi: VRing setup failed\n");
+ goto fail_vring;
+ }
+ return r;
+
+fail_vring:
+ aio_set_event_notifier(s->ctx, &r->host_notifier, NULL);
+ k->set_host_notifier(qbus->parent, n, false);
+ g_slice_free(VirtIOSCSIVring, r);
+ return NULL;
+}
+
+VirtIOSCSIReq *virtio_scsi_pop_req_vring(VirtIOSCSI *s,
+ VirtIOSCSIVring *vring)
+{
+ VirtIOSCSIReq *req = virtio_scsi_init_req(s, NULL);
+ int r;
+
+ req->vring = vring;
+ r = vring_pop((VirtIODevice *)s, &vring->vring, &req->elem);
+ if (r < 0) {
+ virtio_scsi_free_req(req);
+ req = NULL;
+ }
+ return req;
+}
+
+void virtio_scsi_vring_push_notify(VirtIOSCSIReq *req)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(req->vring->parent);
+
+ vring_push(vdev, &req->vring->vring, &req->elem,
+ req->qsgl.size + req->resp_iov.size);
+
+ if (vring_should_notify(vdev, &req->vring->vring)) {
+ event_notifier_set(&req->vring->guest_notifier);
+ }
+}
+
+static void virtio_scsi_iothread_handle_ctrl(EventNotifier *notifier)
+{
+ VirtIOSCSIVring *vring = container_of(notifier,
+ VirtIOSCSIVring, host_notifier);
+ VirtIOSCSI *s = VIRTIO_SCSI(vring->parent);
+ VirtIOSCSIReq *req;
+
+ event_notifier_test_and_clear(notifier);
+ while ((req = virtio_scsi_pop_req_vring(s, vring))) {
+ virtio_scsi_handle_ctrl_req(s, req);
+ }
+}
+
+static void virtio_scsi_iothread_handle_event(EventNotifier *notifier)
+{
+ VirtIOSCSIVring *vring = container_of(notifier,
+ VirtIOSCSIVring, host_notifier);
+ VirtIOSCSI *s = vring->parent;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ event_notifier_test_and_clear(notifier);
+
+ if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ return;
+ }
+
+ if (s->events_dropped) {
+ virtio_scsi_push_event(s, NULL, VIRTIO_SCSI_T_NO_EVENT, 0);
+ }
+}
+
+static void virtio_scsi_iothread_handle_cmd(EventNotifier *notifier)
+{
+ VirtIOSCSIVring *vring = container_of(notifier,
+ VirtIOSCSIVring, host_notifier);
+ VirtIOSCSI *s = (VirtIOSCSI *)vring->parent;
+ VirtIOSCSIReq *req, *next;
+ QTAILQ_HEAD(, VirtIOSCSIReq) reqs = QTAILQ_HEAD_INITIALIZER(reqs);
+
+ event_notifier_test_and_clear(notifier);
+ while ((req = virtio_scsi_pop_req_vring(s, vring))) {
+ if (virtio_scsi_handle_cmd_req_prepare(s, req)) {
+ QTAILQ_INSERT_TAIL(&reqs, req, next);
+ }
+ }
+
+ QTAILQ_FOREACH_SAFE(req, &reqs, next, next) {
+ virtio_scsi_handle_cmd_req_submit(s, req);
+ }
+}
+
+/* assumes s->ctx held */
+static void virtio_scsi_clear_aio(VirtIOSCSI *s)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ int i;
+
+ if (s->ctrl_vring) {
+ aio_set_event_notifier(s->ctx, &s->ctrl_vring->host_notifier, NULL);
+ }
+ if (s->event_vring) {
+ aio_set_event_notifier(s->ctx, &s->event_vring->host_notifier, NULL);
+ }
+ if (s->cmd_vrings) {
+ for (i = 0; i < vs->conf.num_queues && s->cmd_vrings[i]; i++) {
+ aio_set_event_notifier(s->ctx, &s->cmd_vrings[i]->host_notifier, NULL);
+ }
+ }
+}
+
+static void virtio_scsi_vring_teardown(VirtIOSCSI *s)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ int i;
+
+ if (s->ctrl_vring) {
+ vring_teardown(&s->ctrl_vring->vring, vdev, 0);
+ g_slice_free(VirtIOSCSIVring, s->ctrl_vring);
+ s->ctrl_vring = NULL;
+ }
+ if (s->event_vring) {
+ vring_teardown(&s->event_vring->vring, vdev, 1);
+ g_slice_free(VirtIOSCSIVring, s->event_vring);
+ s->event_vring = NULL;
+ }
+ if (s->cmd_vrings) {
+ for (i = 0; i < vs->conf.num_queues && s->cmd_vrings[i]; i++) {
+ vring_teardown(&s->cmd_vrings[i]->vring, vdev, 2 + i);
+ g_slice_free(VirtIOSCSIVring, s->cmd_vrings[i]);
+ s->cmd_vrings[i] = NULL;
+ }
+ free(s->cmd_vrings);
+ s->cmd_vrings = NULL;
+ }
+}
+
+/* Context: QEMU global mutex held */
+void virtio_scsi_dataplane_start(VirtIOSCSI *s)
+{
+ int i;
+ int rc;
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+
+ if (s->dataplane_started ||
+ s->dataplane_starting ||
+ s->dataplane_fenced ||
+ s->ctx != iothread_get_aio_context(vs->conf.iothread)) {
+ return;
+ }
+
+ s->dataplane_starting = true;
+
+ /* Set up guest notifier (irq) */
+ rc = k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, true);
+ if (rc != 0) {
+ fprintf(stderr, "virtio-scsi: Failed to set guest notifiers (%d), "
+ "ensure -enable-kvm is set\n", rc);
+ s->dataplane_fenced = true;
+ goto fail_guest_notifiers;
+ }
+
+ aio_context_acquire(s->ctx);
+ s->ctrl_vring = virtio_scsi_vring_init(s, vs->ctrl_vq,
+ virtio_scsi_iothread_handle_ctrl,
+ 0);
+ if (!s->ctrl_vring) {
+ goto fail_vrings;
+ }
+ s->event_vring = virtio_scsi_vring_init(s, vs->event_vq,
+ virtio_scsi_iothread_handle_event,
+ 1);
+ if (!s->event_vring) {
+ goto fail_vrings;
+ }
+ s->cmd_vrings = g_new(VirtIOSCSIVring *, vs->conf.num_queues);
+ for (i = 0; i < vs->conf.num_queues; i++) {
+ s->cmd_vrings[i] =
+ virtio_scsi_vring_init(s, vs->cmd_vqs[i],
+ virtio_scsi_iothread_handle_cmd,
+ i + 2);
+ if (!s->cmd_vrings[i]) {
+ goto fail_vrings;
+ }
+ }
+
+ s->dataplane_starting = false;
+ s->dataplane_started = true;
+ aio_context_release(s->ctx);
+ return;
+
+fail_vrings:
+ virtio_scsi_clear_aio(s);
+ aio_context_release(s->ctx);
+ virtio_scsi_vring_teardown(s);
+ for (i = 0; i < vs->conf.num_queues + 2; i++) {
+ k->set_host_notifier(qbus->parent, i, false);
+ }
+ k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
+fail_guest_notifiers:
+ s->dataplane_starting = false;
+}
+
+/* Context: QEMU global mutex held */
+void virtio_scsi_dataplane_stop(VirtIOSCSI *s)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ int i;
+
+ /* Better luck next time. */
+ if (s->dataplane_fenced) {
+ s->dataplane_fenced = false;
+ return;
+ }
+ if (!s->dataplane_started || s->dataplane_stopping) {
+ return;
+ }
+ s->dataplane_stopping = true;
+ assert(s->ctx == iothread_get_aio_context(vs->conf.iothread));
+
+ aio_context_acquire(s->ctx);
+
+ aio_set_event_notifier(s->ctx, &s->ctrl_vring->host_notifier, NULL);
+ aio_set_event_notifier(s->ctx, &s->event_vring->host_notifier, NULL);
+ for (i = 0; i < vs->conf.num_queues; i++) {
+ aio_set_event_notifier(s->ctx, &s->cmd_vrings[i]->host_notifier, NULL);
+ }
+
+ blk_drain_all(); /* ensure there are no in-flight requests */
+
+ aio_context_release(s->ctx);
+
+ /* Sync vring state back to virtqueue so that non-dataplane request
+ * processing can continue when we disable the host notifier below.
+ */
+ virtio_scsi_vring_teardown(s);
+
+ for (i = 0; i < vs->conf.num_queues + 2; i++) {
+ k->set_host_notifier(qbus->parent, i, false);
+ }
+
+ /* Clean up guest notifier (irq) */
+ k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
+ s->dataplane_stopping = false;
+ s->dataplane_started = false;
+}
diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c
new file mode 100644
index 00000000..93c33e11
--- /dev/null
+++ b/hw/scsi/virtio-scsi.c
@@ -0,0 +1,1021 @@
+/*
+ * Virtio SCSI HBA
+ *
+ * Copyright IBM, Corp. 2010
+ * Copyright Red Hat, Inc. 2011
+ *
+ * Authors:
+ * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
+ * Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "standard-headers/linux/virtio_ids.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "qemu/error-report.h"
+#include "qemu/iov.h"
+#include "sysemu/block-backend.h"
+#include <hw/scsi/scsi.h>
+#include <block/scsi.h>
+#include <hw/virtio/virtio-bus.h>
+#include "hw/virtio/virtio-access.h"
+#include "migration/migration.h"
+
+static inline int virtio_scsi_get_lun(uint8_t *lun)
+{
+ return ((lun[2] << 8) | lun[3]) & 0x3FFF;
+}
+
+static inline SCSIDevice *virtio_scsi_device_find(VirtIOSCSI *s, uint8_t *lun)
+{
+ if (lun[0] != 1) {
+ return NULL;
+ }
+ if (lun[2] != 0 && !(lun[2] >= 0x40 && lun[2] < 0x80)) {
+ return NULL;
+ }
+ return scsi_device_find(&s->bus, 0, lun[1], virtio_scsi_get_lun(lun));
+}
+
+VirtIOSCSIReq *virtio_scsi_init_req(VirtIOSCSI *s, VirtQueue *vq)
+{
+ VirtIOSCSIReq *req;
+ VirtIOSCSICommon *vs = (VirtIOSCSICommon *)s;
+ const size_t zero_skip = offsetof(VirtIOSCSIReq, elem)
+ + sizeof(VirtQueueElement);
+
+ req = g_slice_alloc(sizeof(*req) + vs->cdb_size);
+ req->vq = vq;
+ req->dev = s;
+ qemu_sglist_init(&req->qsgl, DEVICE(s), 8, &address_space_memory);
+ qemu_iovec_init(&req->resp_iov, 1);
+ memset((uint8_t *)req + zero_skip, 0, sizeof(*req) - zero_skip);
+ return req;
+}
+
+void virtio_scsi_free_req(VirtIOSCSIReq *req)
+{
+ VirtIOSCSICommon *vs = (VirtIOSCSICommon *)req->dev;
+
+ qemu_iovec_destroy(&req->resp_iov);
+ qemu_sglist_destroy(&req->qsgl);
+ g_slice_free1(sizeof(*req) + vs->cdb_size, req);
+}
+
+static void virtio_scsi_complete_req(VirtIOSCSIReq *req)
+{
+ VirtIOSCSI *s = req->dev;
+ VirtQueue *vq = req->vq;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ qemu_iovec_from_buf(&req->resp_iov, 0, &req->resp, req->resp_size);
+ if (req->vring) {
+ assert(req->vq == NULL);
+ virtio_scsi_vring_push_notify(req);
+ } else {
+ virtqueue_push(vq, &req->elem, req->qsgl.size + req->resp_iov.size);
+ virtio_notify(vdev, vq);
+ }
+
+ if (req->sreq) {
+ req->sreq->hba_private = NULL;
+ scsi_req_unref(req->sreq);
+ }
+ virtio_scsi_free_req(req);
+}
+
+static void virtio_scsi_bad_req(void)
+{
+ error_report("wrong size for virtio-scsi headers");
+ exit(1);
+}
+
+static size_t qemu_sgl_concat(VirtIOSCSIReq *req, struct iovec *iov,
+ hwaddr *addr, int num, size_t skip)
+{
+ QEMUSGList *qsgl = &req->qsgl;
+ size_t copied = 0;
+
+ while (num) {
+ if (skip >= iov->iov_len) {
+ skip -= iov->iov_len;
+ } else {
+ qemu_sglist_add(qsgl, *addr + skip, iov->iov_len - skip);
+ copied += iov->iov_len - skip;
+ skip = 0;
+ }
+ iov++;
+ addr++;
+ num--;
+ }
+
+ assert(skip == 0);
+ return copied;
+}
+
+static int virtio_scsi_parse_req(VirtIOSCSIReq *req,
+ unsigned req_size, unsigned resp_size)
+{
+ VirtIODevice *vdev = (VirtIODevice *) req->dev;
+ size_t in_size, out_size;
+
+ if (iov_to_buf(req->elem.out_sg, req->elem.out_num, 0,
+ &req->req, req_size) < req_size) {
+ return -EINVAL;
+ }
+
+ if (qemu_iovec_concat_iov(&req->resp_iov,
+ req->elem.in_sg, req->elem.in_num, 0,
+ resp_size) < resp_size) {
+ return -EINVAL;
+ }
+
+ req->resp_size = resp_size;
+
+ /* Old BIOSes left some padding by mistake after the req_size/resp_size.
+ * As a workaround, always consider the first buffer as the virtio-scsi
+ * request/response, making the payload start at the second element
+ * of the iovec.
+ *
+ * The actual length of the response header, stored in req->resp_size,
+ * does not change.
+ *
+ * TODO: always disable this workaround for virtio 1.0 devices.
+ */
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_F_ANY_LAYOUT)) {
+ if (req->elem.out_num) {
+ req_size = req->elem.out_sg[0].iov_len;
+ }
+ if (req->elem.in_num) {
+ resp_size = req->elem.in_sg[0].iov_len;
+ }
+ }
+
+ out_size = qemu_sgl_concat(req, req->elem.out_sg,
+ &req->elem.out_addr[0], req->elem.out_num,
+ req_size);
+ in_size = qemu_sgl_concat(req, req->elem.in_sg,
+ &req->elem.in_addr[0], req->elem.in_num,
+ resp_size);
+
+ if (out_size && in_size) {
+ return -ENOTSUP;
+ }
+
+ if (out_size) {
+ req->mode = SCSI_XFER_TO_DEV;
+ } else if (in_size) {
+ req->mode = SCSI_XFER_FROM_DEV;
+ }
+
+ return 0;
+}
+
+static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq)
+{
+ VirtIOSCSIReq *req = virtio_scsi_init_req(s, vq);
+ if (!virtqueue_pop(vq, &req->elem)) {
+ virtio_scsi_free_req(req);
+ return NULL;
+ }
+ return req;
+}
+
+static void virtio_scsi_save_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ VirtIOSCSIReq *req = sreq->hba_private;
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(req->dev);
+ uint32_t n = virtio_queue_get_id(req->vq) - 2;
+
+ assert(n < vs->conf.num_queues);
+ qemu_put_be32s(f, &n);
+ qemu_put_buffer(f, (unsigned char *)&req->elem, sizeof(req->elem));
+}
+
+static void *virtio_scsi_load_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ SCSIBus *bus = sreq->bus;
+ VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ VirtIOSCSIReq *req;
+ uint32_t n;
+
+ qemu_get_be32s(f, &n);
+ assert(n < vs->conf.num_queues);
+ req = virtio_scsi_init_req(s, vs->cmd_vqs[n]);
+ qemu_get_buffer(f, (unsigned char *)&req->elem, sizeof(req->elem));
+ /* TODO: add a way for SCSIBusInfo's load_request to fail,
+ * and fail migration instead of asserting here.
+ * When we do, we might be able to re-enable NDEBUG below.
+ */
+#ifdef NDEBUG
+#error building with NDEBUG is not supported
+#endif
+ assert(req->elem.in_num <= ARRAY_SIZE(req->elem.in_sg));
+ assert(req->elem.out_num <= ARRAY_SIZE(req->elem.out_sg));
+
+ if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + vs->cdb_size,
+ sizeof(VirtIOSCSICmdResp) + vs->sense_size) < 0) {
+ error_report("invalid SCSI request migration data");
+ exit(1);
+ }
+
+ scsi_req_ref(sreq);
+ req->sreq = sreq;
+ if (req->sreq->cmd.mode != SCSI_XFER_NONE) {
+ assert(req->sreq->cmd.mode == req->mode);
+ }
+ return req;
+}
+
+typedef struct {
+ Notifier notifier;
+ VirtIOSCSIReq *tmf_req;
+} VirtIOSCSICancelNotifier;
+
+static void virtio_scsi_cancel_notify(Notifier *notifier, void *data)
+{
+ VirtIOSCSICancelNotifier *n = container_of(notifier,
+ VirtIOSCSICancelNotifier,
+ notifier);
+
+ if (--n->tmf_req->remaining == 0) {
+ virtio_scsi_complete_req(n->tmf_req);
+ }
+ g_slice_free(VirtIOSCSICancelNotifier, n);
+}
+
+/* Return 0 if the request is ready to be completed and return to guest;
+ * -EINPROGRESS if the request is submitted and will be completed later, in the
+ * case of async cancellation. */
+static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req)
+{
+ SCSIDevice *d = virtio_scsi_device_find(s, req->req.tmf.lun);
+ SCSIRequest *r, *next;
+ BusChild *kid;
+ int target;
+ int ret = 0;
+
+ if (s->dataplane_started) {
+ assert(blk_get_aio_context(d->conf.blk) == s->ctx);
+ }
+ /* Here VIRTIO_SCSI_S_OK means "FUNCTION COMPLETE". */
+ req->resp.tmf.response = VIRTIO_SCSI_S_OK;
+
+ virtio_tswap32s(VIRTIO_DEVICE(s), &req->req.tmf.subtype);
+ switch (req->req.tmf.subtype) {
+ case VIRTIO_SCSI_T_TMF_ABORT_TASK:
+ case VIRTIO_SCSI_T_TMF_QUERY_TASK:
+ if (!d) {
+ goto fail;
+ }
+ if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
+ goto incorrect_lun;
+ }
+ QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) {
+ VirtIOSCSIReq *cmd_req = r->hba_private;
+ if (cmd_req && cmd_req->req.cmd.tag == req->req.tmf.tag) {
+ break;
+ }
+ }
+ if (r) {
+ /*
+ * Assert that the request has not been completed yet, we
+ * check for it in the loop above.
+ */
+ assert(r->hba_private);
+ if (req->req.tmf.subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK) {
+ /* "If the specified command is present in the task set, then
+ * return a service response set to FUNCTION SUCCEEDED".
+ */
+ req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
+ } else {
+ VirtIOSCSICancelNotifier *notifier;
+
+ req->remaining = 1;
+ notifier = g_slice_new(VirtIOSCSICancelNotifier);
+ notifier->tmf_req = req;
+ notifier->notifier.notify = virtio_scsi_cancel_notify;
+ scsi_req_cancel_async(r, &notifier->notifier);
+ ret = -EINPROGRESS;
+ }
+ }
+ break;
+
+ case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET:
+ if (!d) {
+ goto fail;
+ }
+ if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
+ goto incorrect_lun;
+ }
+ s->resetting++;
+ qdev_reset_all(&d->qdev);
+ s->resetting--;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET:
+ case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET:
+ case VIRTIO_SCSI_T_TMF_QUERY_TASK_SET:
+ if (!d) {
+ goto fail;
+ }
+ if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
+ goto incorrect_lun;
+ }
+
+ /* Add 1 to "remaining" until virtio_scsi_do_tmf returns.
+ * This way, if the bus starts calling back to the notifiers
+ * even before we finish the loop, virtio_scsi_cancel_notify
+ * will not complete the TMF too early.
+ */
+ req->remaining = 1;
+ QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) {
+ if (r->hba_private) {
+ if (req->req.tmf.subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK_SET) {
+ /* "If there is any command present in the task set, then
+ * return a service response set to FUNCTION SUCCEEDED".
+ */
+ req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
+ break;
+ } else {
+ VirtIOSCSICancelNotifier *notifier;
+
+ req->remaining++;
+ notifier = g_slice_new(VirtIOSCSICancelNotifier);
+ notifier->notifier.notify = virtio_scsi_cancel_notify;
+ notifier->tmf_req = req;
+ scsi_req_cancel_async(r, &notifier->notifier);
+ }
+ }
+ }
+ if (--req->remaining > 0) {
+ ret = -EINPROGRESS;
+ }
+ break;
+
+ case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET:
+ target = req->req.tmf.lun[1];
+ s->resetting++;
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ d = DO_UPCAST(SCSIDevice, qdev, kid->child);
+ if (d->channel == 0 && d->id == target) {
+ qdev_reset_all(&d->qdev);
+ }
+ }
+ s->resetting--;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_CLEAR_ACA:
+ default:
+ req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_REJECTED;
+ break;
+ }
+
+ return ret;
+
+incorrect_lun:
+ req->resp.tmf.response = VIRTIO_SCSI_S_INCORRECT_LUN;
+ return ret;
+
+fail:
+ req->resp.tmf.response = VIRTIO_SCSI_S_BAD_TARGET;
+ return ret;
+}
+
+void virtio_scsi_handle_ctrl_req(VirtIOSCSI *s, VirtIOSCSIReq *req)
+{
+ VirtIODevice *vdev = (VirtIODevice *)s;
+ uint32_t type;
+ int r = 0;
+
+ if (iov_to_buf(req->elem.out_sg, req->elem.out_num, 0,
+ &type, sizeof(type)) < sizeof(type)) {
+ virtio_scsi_bad_req();
+ return;
+ }
+
+ virtio_tswap32s(vdev, &type);
+ if (type == VIRTIO_SCSI_T_TMF) {
+ if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICtrlTMFReq),
+ sizeof(VirtIOSCSICtrlTMFResp)) < 0) {
+ virtio_scsi_bad_req();
+ } else {
+ r = virtio_scsi_do_tmf(s, req);
+ }
+
+ } else if (type == VIRTIO_SCSI_T_AN_QUERY ||
+ type == VIRTIO_SCSI_T_AN_SUBSCRIBE) {
+ if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICtrlANReq),
+ sizeof(VirtIOSCSICtrlANResp)) < 0) {
+ virtio_scsi_bad_req();
+ } else {
+ req->resp.an.event_actual = 0;
+ req->resp.an.response = VIRTIO_SCSI_S_OK;
+ }
+ }
+ if (r == 0) {
+ virtio_scsi_complete_req(req);
+ } else {
+ assert(r == -EINPROGRESS);
+ }
+}
+
+static void virtio_scsi_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSCSI *s = (VirtIOSCSI *)vdev;
+ VirtIOSCSIReq *req;
+
+ if (s->ctx && !s->dataplane_disabled) {
+ virtio_scsi_dataplane_start(s);
+ return;
+ }
+ while ((req = virtio_scsi_pop_req(s, vq))) {
+ virtio_scsi_handle_ctrl_req(s, req);
+ }
+}
+
+static void virtio_scsi_complete_cmd_req(VirtIOSCSIReq *req)
+{
+ /* Sense data is not in req->resp and is copied separately
+ * in virtio_scsi_command_complete.
+ */
+ req->resp_size = sizeof(VirtIOSCSICmdResp);
+ virtio_scsi_complete_req(req);
+}
+
+static void virtio_scsi_command_complete(SCSIRequest *r, uint32_t status,
+ size_t resid)
+{
+ VirtIOSCSIReq *req = r->hba_private;
+ uint8_t sense[SCSI_SENSE_BUF_SIZE];
+ uint32_t sense_len;
+ VirtIODevice *vdev = VIRTIO_DEVICE(req->dev);
+
+ if (r->io_canceled) {
+ return;
+ }
+
+ req->resp.cmd.response = VIRTIO_SCSI_S_OK;
+ req->resp.cmd.status = status;
+ if (req->resp.cmd.status == GOOD) {
+ req->resp.cmd.resid = virtio_tswap32(vdev, resid);
+ } else {
+ req->resp.cmd.resid = 0;
+ sense_len = scsi_req_get_sense(r, sense, sizeof(sense));
+ sense_len = MIN(sense_len, req->resp_iov.size - sizeof(req->resp.cmd));
+ qemu_iovec_from_buf(&req->resp_iov, sizeof(req->resp.cmd),
+ sense, sense_len);
+ req->resp.cmd.sense_len = virtio_tswap32(vdev, sense_len);
+ }
+ virtio_scsi_complete_cmd_req(req);
+}
+
+static int virtio_scsi_parse_cdb(SCSIDevice *dev, SCSICommand *cmd,
+ uint8_t *buf, void *hba_private)
+{
+ VirtIOSCSIReq *req = hba_private;
+
+ if (cmd->len == 0) {
+ cmd->len = MIN(VIRTIO_SCSI_CDB_DEFAULT_SIZE, SCSI_CMD_BUF_SIZE);
+ memcpy(cmd->buf, buf, cmd->len);
+ }
+
+ /* Extract the direction and mode directly from the request, for
+ * host device passthrough.
+ */
+ cmd->xfer = req->qsgl.size;
+ cmd->mode = req->mode;
+ return 0;
+}
+
+static QEMUSGList *virtio_scsi_get_sg_list(SCSIRequest *r)
+{
+ VirtIOSCSIReq *req = r->hba_private;
+
+ return &req->qsgl;
+}
+
+static void virtio_scsi_request_cancelled(SCSIRequest *r)
+{
+ VirtIOSCSIReq *req = r->hba_private;
+
+ if (!req) {
+ return;
+ }
+ if (req->dev->resetting) {
+ req->resp.cmd.response = VIRTIO_SCSI_S_RESET;
+ } else {
+ req->resp.cmd.response = VIRTIO_SCSI_S_ABORTED;
+ }
+ virtio_scsi_complete_cmd_req(req);
+}
+
+static void virtio_scsi_fail_cmd_req(VirtIOSCSIReq *req)
+{
+ req->resp.cmd.response = VIRTIO_SCSI_S_FAILURE;
+ virtio_scsi_complete_cmd_req(req);
+}
+
+bool virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req)
+{
+ VirtIOSCSICommon *vs = &s->parent_obj;
+ SCSIDevice *d;
+ int rc;
+
+ rc = virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + vs->cdb_size,
+ sizeof(VirtIOSCSICmdResp) + vs->sense_size);
+ if (rc < 0) {
+ if (rc == -ENOTSUP) {
+ virtio_scsi_fail_cmd_req(req);
+ } else {
+ virtio_scsi_bad_req();
+ }
+ return false;
+ }
+
+ d = virtio_scsi_device_find(s, req->req.cmd.lun);
+ if (!d) {
+ req->resp.cmd.response = VIRTIO_SCSI_S_BAD_TARGET;
+ virtio_scsi_complete_cmd_req(req);
+ return false;
+ }
+ if (s->dataplane_started) {
+ assert(blk_get_aio_context(d->conf.blk) == s->ctx);
+ }
+ req->sreq = scsi_req_new(d, req->req.cmd.tag,
+ virtio_scsi_get_lun(req->req.cmd.lun),
+ req->req.cmd.cdb, req);
+
+ if (req->sreq->cmd.mode != SCSI_XFER_NONE
+ && (req->sreq->cmd.mode != req->mode ||
+ req->sreq->cmd.xfer > req->qsgl.size)) {
+ req->resp.cmd.response = VIRTIO_SCSI_S_OVERRUN;
+ virtio_scsi_complete_cmd_req(req);
+ return false;
+ }
+ scsi_req_ref(req->sreq);
+ blk_io_plug(d->conf.blk);
+ return true;
+}
+
+void virtio_scsi_handle_cmd_req_submit(VirtIOSCSI *s, VirtIOSCSIReq *req)
+{
+ SCSIRequest *sreq = req->sreq;
+ if (scsi_req_enqueue(sreq)) {
+ scsi_req_continue(sreq);
+ }
+ blk_io_unplug(sreq->dev->conf.blk);
+ scsi_req_unref(sreq);
+}
+
+static void virtio_scsi_handle_cmd(VirtIODevice *vdev, VirtQueue *vq)
+{
+ /* use non-QOM casts in the data path */
+ VirtIOSCSI *s = (VirtIOSCSI *)vdev;
+ VirtIOSCSIReq *req, *next;
+ QTAILQ_HEAD(, VirtIOSCSIReq) reqs = QTAILQ_HEAD_INITIALIZER(reqs);
+
+ if (s->ctx && !s->dataplane_disabled) {
+ virtio_scsi_dataplane_start(s);
+ return;
+ }
+ while ((req = virtio_scsi_pop_req(s, vq))) {
+ if (virtio_scsi_handle_cmd_req_prepare(s, req)) {
+ QTAILQ_INSERT_TAIL(&reqs, req, next);
+ }
+ }
+
+ QTAILQ_FOREACH_SAFE(req, &reqs, next, next) {
+ virtio_scsi_handle_cmd_req_submit(s, req);
+ }
+}
+
+static void virtio_scsi_get_config(VirtIODevice *vdev,
+ uint8_t *config)
+{
+ VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config;
+ VirtIOSCSICommon *s = VIRTIO_SCSI_COMMON(vdev);
+
+ virtio_stl_p(vdev, &scsiconf->num_queues, s->conf.num_queues);
+ virtio_stl_p(vdev, &scsiconf->seg_max, 128 - 2);
+ virtio_stl_p(vdev, &scsiconf->max_sectors, s->conf.max_sectors);
+ virtio_stl_p(vdev, &scsiconf->cmd_per_lun, s->conf.cmd_per_lun);
+ virtio_stl_p(vdev, &scsiconf->event_info_size, sizeof(VirtIOSCSIEvent));
+ virtio_stl_p(vdev, &scsiconf->sense_size, s->sense_size);
+ virtio_stl_p(vdev, &scsiconf->cdb_size, s->cdb_size);
+ virtio_stw_p(vdev, &scsiconf->max_channel, VIRTIO_SCSI_MAX_CHANNEL);
+ virtio_stw_p(vdev, &scsiconf->max_target, VIRTIO_SCSI_MAX_TARGET);
+ virtio_stl_p(vdev, &scsiconf->max_lun, VIRTIO_SCSI_MAX_LUN);
+}
+
+static void virtio_scsi_set_config(VirtIODevice *vdev,
+ const uint8_t *config)
+{
+ VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config;
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+
+ if ((uint32_t) virtio_ldl_p(vdev, &scsiconf->sense_size) >= 65536 ||
+ (uint32_t) virtio_ldl_p(vdev, &scsiconf->cdb_size) >= 256) {
+ error_report("bad data written to virtio-scsi configuration space");
+ exit(1);
+ }
+
+ vs->sense_size = virtio_ldl_p(vdev, &scsiconf->sense_size);
+ vs->cdb_size = virtio_ldl_p(vdev, &scsiconf->cdb_size);
+}
+
+static uint64_t virtio_scsi_get_features(VirtIODevice *vdev,
+ uint64_t requested_features,
+ Error **errp)
+{
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+
+ /* Firstly sync all virtio-scsi possible supported features */
+ requested_features |= s->host_features;
+ return requested_features;
+}
+
+static void virtio_scsi_reset(VirtIODevice *vdev)
+{
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+
+ if (s->ctx) {
+ virtio_scsi_dataplane_stop(s);
+ }
+ s->resetting++;
+ qbus_reset_all(&s->bus.qbus);
+ s->resetting--;
+
+ vs->sense_size = VIRTIO_SCSI_SENSE_DEFAULT_SIZE;
+ vs->cdb_size = VIRTIO_SCSI_CDB_DEFAULT_SIZE;
+ s->events_dropped = false;
+}
+
+/* The device does not have anything to save beyond the virtio data.
+ * Request data is saved with callbacks from SCSI devices.
+ */
+static void virtio_scsi_save(QEMUFile *f, void *opaque)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+
+ if (s->dataplane_started) {
+ virtio_scsi_dataplane_stop(s);
+ }
+ virtio_save(vdev, f);
+}
+
+static int virtio_scsi_load(QEMUFile *f, void *opaque, int version_id)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
+ int ret;
+
+ ret = virtio_load(vdev, f, version_id);
+ if (ret) {
+ return ret;
+ }
+ return 0;
+}
+
+void virtio_scsi_push_event(VirtIOSCSI *s, SCSIDevice *dev,
+ uint32_t event, uint32_t reason)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ VirtIOSCSIReq *req;
+ VirtIOSCSIEvent *evt;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ return;
+ }
+
+ if (s->dataplane_started) {
+ assert(s->ctx);
+ aio_context_acquire(s->ctx);
+ }
+
+ if (s->dataplane_started) {
+ req = virtio_scsi_pop_req_vring(s, s->event_vring);
+ } else {
+ req = virtio_scsi_pop_req(s, vs->event_vq);
+ }
+ if (!req) {
+ s->events_dropped = true;
+ goto out;
+ }
+
+ if (s->events_dropped) {
+ event |= VIRTIO_SCSI_T_EVENTS_MISSED;
+ s->events_dropped = false;
+ }
+
+ if (virtio_scsi_parse_req(req, 0, sizeof(VirtIOSCSIEvent))) {
+ virtio_scsi_bad_req();
+ }
+
+ evt = &req->resp.event;
+ memset(evt, 0, sizeof(VirtIOSCSIEvent));
+ evt->event = virtio_tswap32(vdev, event);
+ evt->reason = virtio_tswap32(vdev, reason);
+ if (!dev) {
+ assert(event == VIRTIO_SCSI_T_EVENTS_MISSED);
+ } else {
+ evt->lun[0] = 1;
+ evt->lun[1] = dev->id;
+
+ /* Linux wants us to keep the same encoding we use for REPORT LUNS. */
+ if (dev->lun >= 256) {
+ evt->lun[2] = (dev->lun >> 8) | 0x40;
+ }
+ evt->lun[3] = dev->lun & 0xFF;
+ }
+ virtio_scsi_complete_req(req);
+out:
+ if (s->dataplane_started) {
+ aio_context_release(s->ctx);
+ }
+}
+
+static void virtio_scsi_handle_event(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+
+ if (s->ctx && !s->dataplane_disabled) {
+ virtio_scsi_dataplane_start(s);
+ return;
+ }
+ if (s->events_dropped) {
+ virtio_scsi_push_event(s, NULL, VIRTIO_SCSI_T_NO_EVENT, 0);
+ }
+}
+
+static void virtio_scsi_change(SCSIBus *bus, SCSIDevice *dev, SCSISense sense)
+{
+ VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus);
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_SCSI_F_CHANGE) &&
+ dev->type != TYPE_ROM) {
+ virtio_scsi_push_event(s, dev, VIRTIO_SCSI_T_PARAM_CHANGE,
+ sense.asc | (sense.ascq << 8));
+ }
+}
+
+static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(hotplug_dev);
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+ SCSIDevice *sd = SCSI_DEVICE(dev);
+
+ if (s->ctx && !s->dataplane_disabled) {
+ if (blk_op_is_blocked(sd->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) {
+ return;
+ }
+ blk_op_block_all(sd->conf.blk, s->blocker);
+ aio_context_acquire(s->ctx);
+ blk_set_aio_context(sd->conf.blk, s->ctx);
+ aio_context_release(s->ctx);
+ }
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_SCSI_F_HOTPLUG)) {
+ virtio_scsi_push_event(s, sd,
+ VIRTIO_SCSI_T_TRANSPORT_RESET,
+ VIRTIO_SCSI_EVT_RESET_RESCAN);
+ }
+}
+
+static void virtio_scsi_hotunplug(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(hotplug_dev);
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+ SCSIDevice *sd = SCSI_DEVICE(dev);
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_SCSI_F_HOTPLUG)) {
+ virtio_scsi_push_event(s, sd,
+ VIRTIO_SCSI_T_TRANSPORT_RESET,
+ VIRTIO_SCSI_EVT_RESET_REMOVED);
+ }
+
+ if (s->ctx) {
+ blk_op_unblock_all(sd->conf.blk, s->blocker);
+ }
+ qdev_simple_device_unplug_cb(hotplug_dev, dev, errp);
+}
+
+static struct SCSIBusInfo virtio_scsi_scsi_info = {
+ .tcq = true,
+ .max_channel = VIRTIO_SCSI_MAX_CHANNEL,
+ .max_target = VIRTIO_SCSI_MAX_TARGET,
+ .max_lun = VIRTIO_SCSI_MAX_LUN,
+
+ .complete = virtio_scsi_command_complete,
+ .cancel = virtio_scsi_request_cancelled,
+ .change = virtio_scsi_change,
+ .parse_cdb = virtio_scsi_parse_cdb,
+ .get_sg_list = virtio_scsi_get_sg_list,
+ .save_request = virtio_scsi_save_request,
+ .load_request = virtio_scsi_load_request,
+};
+
+void virtio_scsi_common_realize(DeviceState *dev, Error **errp,
+ HandleOutput ctrl, HandleOutput evt,
+ HandleOutput cmd)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSCSICommon *s = VIRTIO_SCSI_COMMON(dev);
+ int i;
+
+ virtio_init(vdev, "virtio-scsi", VIRTIO_ID_SCSI,
+ sizeof(VirtIOSCSIConfig));
+
+ if (s->conf.num_queues == 0 ||
+ s->conf.num_queues > VIRTIO_QUEUE_MAX - 2) {
+ error_setg(errp, "Invalid number of queues (= %" PRIu32 "), "
+ "must be a positive integer less than %d.",
+ s->conf.num_queues, VIRTIO_QUEUE_MAX - 2);
+ virtio_cleanup(vdev);
+ return;
+ }
+ s->cmd_vqs = g_new0(VirtQueue *, s->conf.num_queues);
+ s->sense_size = VIRTIO_SCSI_SENSE_DEFAULT_SIZE;
+ s->cdb_size = VIRTIO_SCSI_CDB_DEFAULT_SIZE;
+
+ s->ctrl_vq = virtio_add_queue(vdev, VIRTIO_SCSI_VQ_SIZE,
+ ctrl);
+ s->event_vq = virtio_add_queue(vdev, VIRTIO_SCSI_VQ_SIZE,
+ evt);
+ for (i = 0; i < s->conf.num_queues; i++) {
+ s->cmd_vqs[i] = virtio_add_queue(vdev, VIRTIO_SCSI_VQ_SIZE,
+ cmd);
+ }
+
+ if (s->conf.iothread) {
+ virtio_scsi_set_iothread(VIRTIO_SCSI(s), s->conf.iothread);
+ }
+}
+
+/* Disable dataplane thread during live migration since it does not
+ * update the dirty memory bitmap yet.
+ */
+static void virtio_scsi_migration_state_changed(Notifier *notifier, void *data)
+{
+ VirtIOSCSI *s = container_of(notifier, VirtIOSCSI,
+ migration_state_notifier);
+ MigrationState *mig = data;
+
+ if (migration_in_setup(mig)) {
+ if (!s->dataplane_started) {
+ return;
+ }
+ virtio_scsi_dataplane_stop(s);
+ s->dataplane_disabled = true;
+ } else if (migration_has_finished(mig) ||
+ migration_has_failed(mig)) {
+ if (s->dataplane_started) {
+ return;
+ }
+ blk_drain_all(); /* complete in-flight non-dataplane requests */
+ s->dataplane_disabled = false;
+ }
+}
+
+static void virtio_scsi_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSCSI *s = VIRTIO_SCSI(dev);
+ static int virtio_scsi_id;
+ Error *err = NULL;
+
+ virtio_scsi_common_realize(dev, &err, virtio_scsi_handle_ctrl,
+ virtio_scsi_handle_event,
+ virtio_scsi_handle_cmd);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ scsi_bus_new(&s->bus, sizeof(s->bus), dev,
+ &virtio_scsi_scsi_info, vdev->bus_name);
+ /* override default SCSI bus hotplug-handler, with virtio-scsi's one */
+ qbus_set_hotplug_handler(BUS(&s->bus), dev, &error_abort);
+
+ if (!dev->hotplugged) {
+ scsi_bus_legacy_handle_cmdline(&s->bus, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ }
+
+ register_savevm(dev, "virtio-scsi", virtio_scsi_id++, 1,
+ virtio_scsi_save, virtio_scsi_load, s);
+ s->migration_state_notifier.notify = virtio_scsi_migration_state_changed;
+ add_migration_state_change_notifier(&s->migration_state_notifier);
+
+ error_setg(&s->blocker, "block device is in use by data plane");
+}
+
+static void virtio_scsi_instance_init(Object *obj)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(obj);
+
+ object_property_add_link(obj, "iothread", TYPE_IOTHREAD,
+ (Object **)&vs->conf.iothread,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE, &error_abort);
+}
+
+void virtio_scsi_common_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
+
+ g_free(vs->cmd_vqs);
+ virtio_cleanup(vdev);
+}
+
+static void virtio_scsi_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIOSCSI *s = VIRTIO_SCSI(dev);
+
+ error_free(s->blocker);
+
+ unregister_savevm(dev, "virtio-scsi", s);
+ remove_migration_state_change_notifier(&s->migration_state_notifier);
+
+ virtio_scsi_common_unrealize(dev, errp);
+}
+
+static Property virtio_scsi_properties[] = {
+ DEFINE_PROP_UINT32("num_queues", VirtIOSCSI, parent_obj.conf.num_queues, 1),
+ DEFINE_PROP_UINT32("max_sectors", VirtIOSCSI, parent_obj.conf.max_sectors,
+ 0xFFFF),
+ DEFINE_PROP_UINT32("cmd_per_lun", VirtIOSCSI, parent_obj.conf.cmd_per_lun,
+ 128),
+ DEFINE_PROP_BIT("hotplug", VirtIOSCSI, host_features,
+ VIRTIO_SCSI_F_HOTPLUG, true),
+ DEFINE_PROP_BIT("param_change", VirtIOSCSI, host_features,
+ VIRTIO_SCSI_F_CHANGE, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_scsi_common_class_init(ObjectClass *klass, void *data)
+{
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ vdc->get_config = virtio_scsi_get_config;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static void virtio_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ dc->props = virtio_scsi_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ vdc->realize = virtio_scsi_device_realize;
+ vdc->unrealize = virtio_scsi_device_unrealize;
+ vdc->set_config = virtio_scsi_set_config;
+ vdc->get_features = virtio_scsi_get_features;
+ vdc->reset = virtio_scsi_reset;
+ hc->plug = virtio_scsi_hotplug;
+ hc->unplug = virtio_scsi_hotunplug;
+}
+
+static const TypeInfo virtio_scsi_common_info = {
+ .name = TYPE_VIRTIO_SCSI_COMMON,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOSCSICommon),
+ .abstract = true,
+ .class_init = virtio_scsi_common_class_init,
+};
+
+static const TypeInfo virtio_scsi_info = {
+ .name = TYPE_VIRTIO_SCSI,
+ .parent = TYPE_VIRTIO_SCSI_COMMON,
+ .instance_size = sizeof(VirtIOSCSI),
+ .instance_init = virtio_scsi_instance_init,
+ .class_init = virtio_scsi_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_scsi_common_info);
+ type_register_static(&virtio_scsi_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/scsi/vmw_pvscsi.c b/hw/scsi/vmw_pvscsi.c
new file mode 100644
index 00000000..9c71f31f
--- /dev/null
+++ b/hw/scsi/vmw_pvscsi.c
@@ -0,0 +1,1219 @@
+/*
+ * QEMU VMWARE PVSCSI paravirtual SCSI bus
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Based on implementation by Paolo Bonzini
+ * http://lists.gnu.org/archive/html/qemu-devel/2011-08/msg00729.html
+ *
+ * Authors:
+ * Paolo Bonzini <pbonzini@redhat.com>
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ * See the COPYING file in the top-level directory.
+ *
+ * NOTE about MSI-X:
+ * MSI-X support has been removed for the moment because it leads Windows OS
+ * to crash on startup. The crash happens because Windows driver requires
+ * MSI-X shared memory to be part of the same BAR used for rings state
+ * registers, etc. This is not supported by QEMU infrastructure so separate
+ * BAR created from MSI-X purposes. Windows driver fails to deal with 2 BARs.
+ *
+ */
+
+#include "hw/scsi/scsi.h"
+#include <block/scsi.h>
+#include "hw/pci/msi.h"
+#include "vmw_pvscsi.h"
+#include "trace.h"
+
+
+#define PVSCSI_MSI_OFFSET (0x50)
+#define PVSCSI_USE_64BIT (true)
+#define PVSCSI_PER_VECTOR_MASK (false)
+
+#define PVSCSI_MAX_DEVS (64)
+#define PVSCSI_MSIX_NUM_VECTORS (1)
+
+#define PVSCSI_MAX_CMD_DATA_WORDS \
+ (sizeof(PVSCSICmdDescSetupRings)/sizeof(uint32_t))
+
+#define RS_GET_FIELD(m, field) \
+ (ldl_le_pci_dma(&container_of(m, PVSCSIState, rings)->parent_obj, \
+ (m)->rs_pa + offsetof(struct PVSCSIRingsState, field)))
+#define RS_SET_FIELD(m, field, val) \
+ (stl_le_pci_dma(&container_of(m, PVSCSIState, rings)->parent_obj, \
+ (m)->rs_pa + offsetof(struct PVSCSIRingsState, field), val))
+
+#define TYPE_PVSCSI "pvscsi"
+#define PVSCSI(obj) OBJECT_CHECK(PVSCSIState, (obj), TYPE_PVSCSI)
+
+typedef struct PVSCSIRingInfo {
+ uint64_t rs_pa;
+ uint32_t txr_len_mask;
+ uint32_t rxr_len_mask;
+ uint32_t msg_len_mask;
+ uint64_t req_ring_pages_pa[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ uint64_t cmp_ring_pages_pa[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ uint64_t msg_ring_pages_pa[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES];
+ uint64_t consumed_ptr;
+ uint64_t filled_cmp_ptr;
+ uint64_t filled_msg_ptr;
+} PVSCSIRingInfo;
+
+typedef struct PVSCSISGState {
+ hwaddr elemAddr;
+ hwaddr dataAddr;
+ uint32_t resid;
+} PVSCSISGState;
+
+typedef QTAILQ_HEAD(, PVSCSIRequest) PVSCSIRequestList;
+
+typedef struct {
+ PCIDevice parent_obj;
+ MemoryRegion io_space;
+ SCSIBus bus;
+ QEMUBH *completion_worker;
+ PVSCSIRequestList pending_queue;
+ PVSCSIRequestList completion_queue;
+
+ uint64_t reg_interrupt_status; /* Interrupt status register value */
+ uint64_t reg_interrupt_enabled; /* Interrupt mask register value */
+ uint64_t reg_command_status; /* Command status register value */
+
+ /* Command data adoption mechanism */
+ uint64_t curr_cmd; /* Last command arrived */
+ uint32_t curr_cmd_data_cntr; /* Amount of data for last command */
+
+ /* Collector for current command data */
+ uint32_t curr_cmd_data[PVSCSI_MAX_CMD_DATA_WORDS];
+
+ uint8_t rings_info_valid; /* Whether data rings initialized */
+ uint8_t msg_ring_info_valid; /* Whether message ring initialized */
+ uint8_t use_msg; /* Whether to use message ring */
+
+ uint8_t msi_used; /* Whether MSI support was installed successfully */
+
+ PVSCSIRingInfo rings; /* Data transfer rings manager */
+ uint32_t resetting; /* Reset in progress */
+} PVSCSIState;
+
+typedef struct PVSCSIRequest {
+ SCSIRequest *sreq;
+ PVSCSIState *dev;
+ uint8_t sense_key;
+ uint8_t completed;
+ int lun;
+ QEMUSGList sgl;
+ PVSCSISGState sg;
+ struct PVSCSIRingReqDesc req;
+ struct PVSCSIRingCmpDesc cmp;
+ QTAILQ_ENTRY(PVSCSIRequest) next;
+} PVSCSIRequest;
+
+/* Integer binary logarithm */
+static int
+pvscsi_log2(uint32_t input)
+{
+ int log = 0;
+ assert(input > 0);
+ while (input >> ++log) {
+ }
+ return log;
+}
+
+static void
+pvscsi_ring_init_data(PVSCSIRingInfo *m, PVSCSICmdDescSetupRings *ri)
+{
+ int i;
+ uint32_t txr_len_log2, rxr_len_log2;
+ uint32_t req_ring_size, cmp_ring_size;
+ m->rs_pa = ri->ringsStatePPN << VMW_PAGE_SHIFT;
+
+ req_ring_size = ri->reqRingNumPages * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ cmp_ring_size = ri->cmpRingNumPages * PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE;
+ txr_len_log2 = pvscsi_log2(req_ring_size - 1);
+ rxr_len_log2 = pvscsi_log2(cmp_ring_size - 1);
+
+ m->txr_len_mask = MASK(txr_len_log2);
+ m->rxr_len_mask = MASK(rxr_len_log2);
+
+ m->consumed_ptr = 0;
+ m->filled_cmp_ptr = 0;
+
+ for (i = 0; i < ri->reqRingNumPages; i++) {
+ m->req_ring_pages_pa[i] = ri->reqRingPPNs[i] << VMW_PAGE_SHIFT;
+ }
+
+ for (i = 0; i < ri->cmpRingNumPages; i++) {
+ m->cmp_ring_pages_pa[i] = ri->cmpRingPPNs[i] << VMW_PAGE_SHIFT;
+ }
+
+ RS_SET_FIELD(m, reqProdIdx, 0);
+ RS_SET_FIELD(m, reqConsIdx, 0);
+ RS_SET_FIELD(m, reqNumEntriesLog2, txr_len_log2);
+
+ RS_SET_FIELD(m, cmpProdIdx, 0);
+ RS_SET_FIELD(m, cmpConsIdx, 0);
+ RS_SET_FIELD(m, cmpNumEntriesLog2, rxr_len_log2);
+
+ trace_pvscsi_ring_init_data(txr_len_log2, rxr_len_log2);
+
+ /* Flush ring state page changes */
+ smp_wmb();
+}
+
+static void
+pvscsi_ring_init_msg(PVSCSIRingInfo *m, PVSCSICmdDescSetupMsgRing *ri)
+{
+ int i;
+ uint32_t len_log2;
+ uint32_t ring_size;
+
+ ring_size = ri->numPages * PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE;
+ len_log2 = pvscsi_log2(ring_size - 1);
+
+ m->msg_len_mask = MASK(len_log2);
+
+ m->filled_msg_ptr = 0;
+
+ for (i = 0; i < ri->numPages; i++) {
+ m->msg_ring_pages_pa[i] = ri->ringPPNs[i] << VMW_PAGE_SHIFT;
+ }
+
+ RS_SET_FIELD(m, msgProdIdx, 0);
+ RS_SET_FIELD(m, msgConsIdx, 0);
+ RS_SET_FIELD(m, msgNumEntriesLog2, len_log2);
+
+ trace_pvscsi_ring_init_msg(len_log2);
+
+ /* Flush ring state page changes */
+ smp_wmb();
+}
+
+static void
+pvscsi_ring_cleanup(PVSCSIRingInfo *mgr)
+{
+ mgr->rs_pa = 0;
+ mgr->txr_len_mask = 0;
+ mgr->rxr_len_mask = 0;
+ mgr->msg_len_mask = 0;
+ mgr->consumed_ptr = 0;
+ mgr->filled_cmp_ptr = 0;
+ mgr->filled_msg_ptr = 0;
+ memset(mgr->req_ring_pages_pa, 0, sizeof(mgr->req_ring_pages_pa));
+ memset(mgr->cmp_ring_pages_pa, 0, sizeof(mgr->cmp_ring_pages_pa));
+ memset(mgr->msg_ring_pages_pa, 0, sizeof(mgr->msg_ring_pages_pa));
+}
+
+static hwaddr
+pvscsi_ring_pop_req_descr(PVSCSIRingInfo *mgr)
+{
+ uint32_t ready_ptr = RS_GET_FIELD(mgr, reqProdIdx);
+
+ if (ready_ptr != mgr->consumed_ptr) {
+ uint32_t next_ready_ptr =
+ mgr->consumed_ptr++ & mgr->txr_len_mask;
+ uint32_t next_ready_page =
+ next_ready_ptr / PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ uint32_t inpage_idx =
+ next_ready_ptr % PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+
+ return mgr->req_ring_pages_pa[next_ready_page] +
+ inpage_idx * sizeof(PVSCSIRingReqDesc);
+ } else {
+ return 0;
+ }
+}
+
+static void
+pvscsi_ring_flush_req(PVSCSIRingInfo *mgr)
+{
+ RS_SET_FIELD(mgr, reqConsIdx, mgr->consumed_ptr);
+}
+
+static hwaddr
+pvscsi_ring_pop_cmp_descr(PVSCSIRingInfo *mgr)
+{
+ /*
+ * According to Linux driver code it explicitly verifies that number
+ * of requests being processed by device is less then the size of
+ * completion queue, so device may omit completion queue overflow
+ * conditions check. We assume that this is true for other (Windows)
+ * drivers as well.
+ */
+
+ uint32_t free_cmp_ptr =
+ mgr->filled_cmp_ptr++ & mgr->rxr_len_mask;
+ uint32_t free_cmp_page =
+ free_cmp_ptr / PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE;
+ uint32_t inpage_idx =
+ free_cmp_ptr % PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE;
+ return mgr->cmp_ring_pages_pa[free_cmp_page] +
+ inpage_idx * sizeof(PVSCSIRingCmpDesc);
+}
+
+static hwaddr
+pvscsi_ring_pop_msg_descr(PVSCSIRingInfo *mgr)
+{
+ uint32_t free_msg_ptr =
+ mgr->filled_msg_ptr++ & mgr->msg_len_mask;
+ uint32_t free_msg_page =
+ free_msg_ptr / PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE;
+ uint32_t inpage_idx =
+ free_msg_ptr % PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE;
+ return mgr->msg_ring_pages_pa[free_msg_page] +
+ inpage_idx * sizeof(PVSCSIRingMsgDesc);
+}
+
+static void
+pvscsi_ring_flush_cmp(PVSCSIRingInfo *mgr)
+{
+ /* Flush descriptor changes */
+ smp_wmb();
+
+ trace_pvscsi_ring_flush_cmp(mgr->filled_cmp_ptr);
+
+ RS_SET_FIELD(mgr, cmpProdIdx, mgr->filled_cmp_ptr);
+}
+
+static bool
+pvscsi_ring_msg_has_room(PVSCSIRingInfo *mgr)
+{
+ uint32_t prodIdx = RS_GET_FIELD(mgr, msgProdIdx);
+ uint32_t consIdx = RS_GET_FIELD(mgr, msgConsIdx);
+
+ return (prodIdx - consIdx) < (mgr->msg_len_mask + 1);
+}
+
+static void
+pvscsi_ring_flush_msg(PVSCSIRingInfo *mgr)
+{
+ /* Flush descriptor changes */
+ smp_wmb();
+
+ trace_pvscsi_ring_flush_msg(mgr->filled_msg_ptr);
+
+ RS_SET_FIELD(mgr, msgProdIdx, mgr->filled_msg_ptr);
+}
+
+static void
+pvscsi_reset_state(PVSCSIState *s)
+{
+ s->curr_cmd = PVSCSI_CMD_FIRST;
+ s->curr_cmd_data_cntr = 0;
+ s->reg_command_status = PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+ s->reg_interrupt_status = 0;
+ pvscsi_ring_cleanup(&s->rings);
+ s->rings_info_valid = FALSE;
+ s->msg_ring_info_valid = FALSE;
+ QTAILQ_INIT(&s->pending_queue);
+ QTAILQ_INIT(&s->completion_queue);
+}
+
+static void
+pvscsi_update_irq_status(PVSCSIState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ bool should_raise = s->reg_interrupt_enabled & s->reg_interrupt_status;
+
+ trace_pvscsi_update_irq_level(should_raise, s->reg_interrupt_enabled,
+ s->reg_interrupt_status);
+
+ if (s->msi_used && msi_enabled(d)) {
+ if (should_raise) {
+ trace_pvscsi_update_irq_msi();
+ msi_notify(d, PVSCSI_VECTOR_COMPLETION);
+ }
+ return;
+ }
+
+ pci_set_irq(d, !!should_raise);
+}
+
+static void
+pvscsi_raise_completion_interrupt(PVSCSIState *s)
+{
+ s->reg_interrupt_status |= PVSCSI_INTR_CMPL_0;
+
+ /* Memory barrier to flush interrupt status register changes*/
+ smp_wmb();
+
+ pvscsi_update_irq_status(s);
+}
+
+static void
+pvscsi_raise_message_interrupt(PVSCSIState *s)
+{
+ s->reg_interrupt_status |= PVSCSI_INTR_MSG_0;
+
+ /* Memory barrier to flush interrupt status register changes*/
+ smp_wmb();
+
+ pvscsi_update_irq_status(s);
+}
+
+static void
+pvscsi_cmp_ring_put(PVSCSIState *s, struct PVSCSIRingCmpDesc *cmp_desc)
+{
+ hwaddr cmp_descr_pa;
+
+ cmp_descr_pa = pvscsi_ring_pop_cmp_descr(&s->rings);
+ trace_pvscsi_cmp_ring_put(cmp_descr_pa);
+ cpu_physical_memory_write(cmp_descr_pa, (void *)cmp_desc,
+ sizeof(*cmp_desc));
+}
+
+static void
+pvscsi_msg_ring_put(PVSCSIState *s, struct PVSCSIRingMsgDesc *msg_desc)
+{
+ hwaddr msg_descr_pa;
+
+ msg_descr_pa = pvscsi_ring_pop_msg_descr(&s->rings);
+ trace_pvscsi_msg_ring_put(msg_descr_pa);
+ cpu_physical_memory_write(msg_descr_pa, (void *)msg_desc,
+ sizeof(*msg_desc));
+}
+
+static void
+pvscsi_process_completion_queue(void *opaque)
+{
+ PVSCSIState *s = opaque;
+ PVSCSIRequest *pvscsi_req;
+ bool has_completed = false;
+
+ while (!QTAILQ_EMPTY(&s->completion_queue)) {
+ pvscsi_req = QTAILQ_FIRST(&s->completion_queue);
+ QTAILQ_REMOVE(&s->completion_queue, pvscsi_req, next);
+ pvscsi_cmp_ring_put(s, &pvscsi_req->cmp);
+ g_free(pvscsi_req);
+ has_completed = true;
+ }
+
+ if (has_completed) {
+ pvscsi_ring_flush_cmp(&s->rings);
+ pvscsi_raise_completion_interrupt(s);
+ }
+}
+
+static void
+pvscsi_reset_adapter(PVSCSIState *s)
+{
+ s->resetting++;
+ qbus_reset_all_fn(&s->bus);
+ s->resetting--;
+ pvscsi_process_completion_queue(s);
+ assert(QTAILQ_EMPTY(&s->pending_queue));
+ pvscsi_reset_state(s);
+}
+
+static void
+pvscsi_schedule_completion_processing(PVSCSIState *s)
+{
+ /* Try putting more complete requests on the ring. */
+ if (!QTAILQ_EMPTY(&s->completion_queue)) {
+ qemu_bh_schedule(s->completion_worker);
+ }
+}
+
+static void
+pvscsi_complete_request(PVSCSIState *s, PVSCSIRequest *r)
+{
+ assert(!r->completed);
+
+ trace_pvscsi_complete_request(r->cmp.context, r->cmp.dataLen,
+ r->sense_key);
+ if (r->sreq != NULL) {
+ scsi_req_unref(r->sreq);
+ r->sreq = NULL;
+ }
+ r->completed = 1;
+ QTAILQ_REMOVE(&s->pending_queue, r, next);
+ QTAILQ_INSERT_TAIL(&s->completion_queue, r, next);
+ pvscsi_schedule_completion_processing(s);
+}
+
+static QEMUSGList *pvscsi_get_sg_list(SCSIRequest *r)
+{
+ PVSCSIRequest *req = r->hba_private;
+
+ trace_pvscsi_get_sg_list(req->sgl.nsg, req->sgl.size);
+
+ return &req->sgl;
+}
+
+static void
+pvscsi_get_next_sg_elem(PVSCSISGState *sg)
+{
+ struct PVSCSISGElement elem;
+
+ cpu_physical_memory_read(sg->elemAddr, (void *)&elem, sizeof(elem));
+ if ((elem.flags & ~PVSCSI_KNOWN_FLAGS) != 0) {
+ /*
+ * There is PVSCSI_SGE_FLAG_CHAIN_ELEMENT flag described in
+ * header file but its value is unknown. This flag requires
+ * additional processing, so we put warning here to catch it
+ * some day and make proper implementation
+ */
+ trace_pvscsi_get_next_sg_elem(elem.flags);
+ }
+
+ sg->elemAddr += sizeof(elem);
+ sg->dataAddr = elem.addr;
+ sg->resid = elem.length;
+}
+
+static void
+pvscsi_write_sense(PVSCSIRequest *r, uint8_t *sense, int len)
+{
+ r->cmp.senseLen = MIN(r->req.senseLen, len);
+ r->sense_key = sense[(sense[0] & 2) ? 1 : 2];
+ cpu_physical_memory_write(r->req.senseAddr, sense, r->cmp.senseLen);
+}
+
+static void
+pvscsi_command_complete(SCSIRequest *req, uint32_t status, size_t resid)
+{
+ PVSCSIRequest *pvscsi_req = req->hba_private;
+ PVSCSIState *s;
+
+ if (!pvscsi_req) {
+ trace_pvscsi_command_complete_not_found(req->tag);
+ return;
+ }
+ s = pvscsi_req->dev;
+
+ if (resid) {
+ /* Short transfer. */
+ trace_pvscsi_command_complete_data_run();
+ pvscsi_req->cmp.hostStatus = BTSTAT_DATARUN;
+ }
+
+ pvscsi_req->cmp.scsiStatus = status;
+ if (pvscsi_req->cmp.scsiStatus == CHECK_CONDITION) {
+ uint8_t sense[SCSI_SENSE_BUF_SIZE];
+ int sense_len =
+ scsi_req_get_sense(pvscsi_req->sreq, sense, sizeof(sense));
+
+ trace_pvscsi_command_complete_sense_len(sense_len);
+ pvscsi_write_sense(pvscsi_req, sense, sense_len);
+ }
+ qemu_sglist_destroy(&pvscsi_req->sgl);
+ pvscsi_complete_request(s, pvscsi_req);
+}
+
+static void
+pvscsi_send_msg(PVSCSIState *s, SCSIDevice *dev, uint32_t msg_type)
+{
+ if (s->msg_ring_info_valid && pvscsi_ring_msg_has_room(&s->rings)) {
+ PVSCSIMsgDescDevStatusChanged msg = {0};
+
+ msg.type = msg_type;
+ msg.bus = dev->channel;
+ msg.target = dev->id;
+ msg.lun[1] = dev->lun;
+
+ pvscsi_msg_ring_put(s, (PVSCSIRingMsgDesc *)&msg);
+ pvscsi_ring_flush_msg(&s->rings);
+ pvscsi_raise_message_interrupt(s);
+ }
+}
+
+static void
+pvscsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp)
+{
+ PVSCSIState *s = PVSCSI(hotplug_dev);
+
+ pvscsi_send_msg(s, SCSI_DEVICE(dev), PVSCSI_MSG_DEV_ADDED);
+}
+
+static void
+pvscsi_hot_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp)
+{
+ PVSCSIState *s = PVSCSI(hotplug_dev);
+
+ pvscsi_send_msg(s, SCSI_DEVICE(dev), PVSCSI_MSG_DEV_REMOVED);
+ qdev_simple_device_unplug_cb(hotplug_dev, dev, errp);
+}
+
+static void
+pvscsi_request_cancelled(SCSIRequest *req)
+{
+ PVSCSIRequest *pvscsi_req = req->hba_private;
+ PVSCSIState *s = pvscsi_req->dev;
+
+ if (pvscsi_req->completed) {
+ return;
+ }
+
+ if (pvscsi_req->dev->resetting) {
+ pvscsi_req->cmp.hostStatus = BTSTAT_BUSRESET;
+ } else {
+ pvscsi_req->cmp.hostStatus = BTSTAT_ABORTQUEUE;
+ }
+
+ pvscsi_complete_request(s, pvscsi_req);
+}
+
+static SCSIDevice*
+pvscsi_device_find(PVSCSIState *s, int channel, int target,
+ uint8_t *requested_lun, uint8_t *target_lun)
+{
+ if (requested_lun[0] || requested_lun[2] || requested_lun[3] ||
+ requested_lun[4] || requested_lun[5] || requested_lun[6] ||
+ requested_lun[7] || (target > PVSCSI_MAX_DEVS)) {
+ return NULL;
+ } else {
+ *target_lun = requested_lun[1];
+ return scsi_device_find(&s->bus, channel, target, *target_lun);
+ }
+}
+
+static PVSCSIRequest *
+pvscsi_queue_pending_descriptor(PVSCSIState *s, SCSIDevice **d,
+ struct PVSCSIRingReqDesc *descr)
+{
+ PVSCSIRequest *pvscsi_req;
+ uint8_t lun;
+
+ pvscsi_req = g_malloc0(sizeof(*pvscsi_req));
+ pvscsi_req->dev = s;
+ pvscsi_req->req = *descr;
+ pvscsi_req->cmp.context = pvscsi_req->req.context;
+ QTAILQ_INSERT_TAIL(&s->pending_queue, pvscsi_req, next);
+
+ *d = pvscsi_device_find(s, descr->bus, descr->target, descr->lun, &lun);
+ if (*d) {
+ pvscsi_req->lun = lun;
+ }
+
+ return pvscsi_req;
+}
+
+static void
+pvscsi_convert_sglist(PVSCSIRequest *r)
+{
+ int chunk_size;
+ uint64_t data_length = r->req.dataLen;
+ PVSCSISGState sg = r->sg;
+ while (data_length) {
+ while (!sg.resid) {
+ pvscsi_get_next_sg_elem(&sg);
+ trace_pvscsi_convert_sglist(r->req.context, r->sg.dataAddr,
+ r->sg.resid);
+ }
+ assert(data_length > 0);
+ chunk_size = MIN((unsigned) data_length, sg.resid);
+ if (chunk_size) {
+ qemu_sglist_add(&r->sgl, sg.dataAddr, chunk_size);
+ }
+
+ sg.dataAddr += chunk_size;
+ data_length -= chunk_size;
+ sg.resid -= chunk_size;
+ }
+}
+
+static void
+pvscsi_build_sglist(PVSCSIState *s, PVSCSIRequest *r)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ pci_dma_sglist_init(&r->sgl, d, 1);
+ if (r->req.flags & PVSCSI_FLAG_CMD_WITH_SG_LIST) {
+ pvscsi_convert_sglist(r);
+ } else {
+ qemu_sglist_add(&r->sgl, r->req.dataAddr, r->req.dataLen);
+ }
+}
+
+static void
+pvscsi_process_request_descriptor(PVSCSIState *s,
+ struct PVSCSIRingReqDesc *descr)
+{
+ SCSIDevice *d;
+ PVSCSIRequest *r = pvscsi_queue_pending_descriptor(s, &d, descr);
+ int64_t n;
+
+ trace_pvscsi_process_req_descr(descr->cdb[0], descr->context);
+
+ if (!d) {
+ r->cmp.hostStatus = BTSTAT_SELTIMEO;
+ trace_pvscsi_process_req_descr_unknown_device();
+ pvscsi_complete_request(s, r);
+ return;
+ }
+
+ if (descr->flags & PVSCSI_FLAG_CMD_WITH_SG_LIST) {
+ r->sg.elemAddr = descr->dataAddr;
+ }
+
+ r->sreq = scsi_req_new(d, descr->context, r->lun, descr->cdb, r);
+ if (r->sreq->cmd.mode == SCSI_XFER_FROM_DEV &&
+ (descr->flags & PVSCSI_FLAG_CMD_DIR_TODEVICE)) {
+ r->cmp.hostStatus = BTSTAT_BADMSG;
+ trace_pvscsi_process_req_descr_invalid_dir();
+ scsi_req_cancel(r->sreq);
+ return;
+ }
+ if (r->sreq->cmd.mode == SCSI_XFER_TO_DEV &&
+ (descr->flags & PVSCSI_FLAG_CMD_DIR_TOHOST)) {
+ r->cmp.hostStatus = BTSTAT_BADMSG;
+ trace_pvscsi_process_req_descr_invalid_dir();
+ scsi_req_cancel(r->sreq);
+ return;
+ }
+
+ pvscsi_build_sglist(s, r);
+ n = scsi_req_enqueue(r->sreq);
+
+ if (n) {
+ scsi_req_continue(r->sreq);
+ }
+}
+
+static void
+pvscsi_process_io(PVSCSIState *s)
+{
+ PVSCSIRingReqDesc descr;
+ hwaddr next_descr_pa;
+
+ assert(s->rings_info_valid);
+ while ((next_descr_pa = pvscsi_ring_pop_req_descr(&s->rings)) != 0) {
+
+ /* Only read after production index verification */
+ smp_rmb();
+
+ trace_pvscsi_process_io(next_descr_pa);
+ cpu_physical_memory_read(next_descr_pa, &descr, sizeof(descr));
+ pvscsi_process_request_descriptor(s, &descr);
+ }
+
+ pvscsi_ring_flush_req(&s->rings);
+}
+
+static void
+pvscsi_dbg_dump_tx_rings_config(PVSCSICmdDescSetupRings *rc)
+{
+ int i;
+ trace_pvscsi_tx_rings_ppn("Rings State", rc->ringsStatePPN);
+
+ trace_pvscsi_tx_rings_num_pages("Request Ring", rc->reqRingNumPages);
+ for (i = 0; i < rc->reqRingNumPages; i++) {
+ trace_pvscsi_tx_rings_ppn("Request Ring", rc->reqRingPPNs[i]);
+ }
+
+ trace_pvscsi_tx_rings_num_pages("Confirm Ring", rc->cmpRingNumPages);
+ for (i = 0; i < rc->cmpRingNumPages; i++) {
+ trace_pvscsi_tx_rings_ppn("Confirm Ring", rc->reqRingPPNs[i]);
+ }
+}
+
+static uint64_t
+pvscsi_on_cmd_config(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_noimpl("PVSCSI_CMD_CONFIG");
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_cmd_unplug(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_noimpl("PVSCSI_CMD_DEVICE_UNPLUG");
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_issue_scsi(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_noimpl("PVSCSI_CMD_ISSUE_SCSI");
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_cmd_setup_rings(PVSCSIState *s)
+{
+ PVSCSICmdDescSetupRings *rc =
+ (PVSCSICmdDescSetupRings *) s->curr_cmd_data;
+
+ trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_SETUP_RINGS");
+
+ pvscsi_dbg_dump_tx_rings_config(rc);
+ pvscsi_ring_init_data(&s->rings, rc);
+ s->rings_info_valid = TRUE;
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+}
+
+static uint64_t
+pvscsi_on_cmd_abort(PVSCSIState *s)
+{
+ PVSCSICmdDescAbortCmd *cmd = (PVSCSICmdDescAbortCmd *) s->curr_cmd_data;
+ PVSCSIRequest *r, *next;
+
+ trace_pvscsi_on_cmd_abort(cmd->context, cmd->target);
+
+ QTAILQ_FOREACH_SAFE(r, &s->pending_queue, next, next) {
+ if (r->req.context == cmd->context) {
+ break;
+ }
+ }
+ if (r) {
+ assert(!r->completed);
+ r->cmp.hostStatus = BTSTAT_ABORTQUEUE;
+ scsi_req_cancel(r->sreq);
+ }
+
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+}
+
+static uint64_t
+pvscsi_on_cmd_unknown(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_unknown_data(s->curr_cmd_data[0]);
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_cmd_reset_device(PVSCSIState *s)
+{
+ uint8_t target_lun = 0;
+ struct PVSCSICmdDescResetDevice *cmd =
+ (struct PVSCSICmdDescResetDevice *) s->curr_cmd_data;
+ SCSIDevice *sdev;
+
+ sdev = pvscsi_device_find(s, 0, cmd->target, cmd->lun, &target_lun);
+
+ trace_pvscsi_on_cmd_reset_dev(cmd->target, (int) target_lun, sdev);
+
+ if (sdev != NULL) {
+ s->resetting++;
+ device_reset(&sdev->qdev);
+ s->resetting--;
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+ }
+
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_cmd_reset_bus(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_RESET_BUS");
+
+ s->resetting++;
+ qbus_reset_all_fn(&s->bus);
+ s->resetting--;
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+}
+
+static uint64_t
+pvscsi_on_cmd_setup_msg_ring(PVSCSIState *s)
+{
+ PVSCSICmdDescSetupMsgRing *rc =
+ (PVSCSICmdDescSetupMsgRing *) s->curr_cmd_data;
+
+ trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_SETUP_MSG_RING");
+
+ if (!s->use_msg) {
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+ }
+
+ if (s->rings_info_valid) {
+ pvscsi_ring_init_msg(&s->rings, rc);
+ s->msg_ring_info_valid = TRUE;
+ }
+ return sizeof(PVSCSICmdDescSetupMsgRing) / sizeof(uint32_t);
+}
+
+static uint64_t
+pvscsi_on_cmd_adapter_reset(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_ADAPTER_RESET");
+
+ pvscsi_reset_adapter(s);
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+}
+
+static const struct {
+ int data_size;
+ uint64_t (*handler_fn)(PVSCSIState *s);
+} pvscsi_commands[] = {
+ [PVSCSI_CMD_FIRST] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_cmd_unknown,
+ },
+
+ /* Not implemented, data size defined based on what arrives on windows */
+ [PVSCSI_CMD_CONFIG] = {
+ .data_size = 6 * sizeof(uint32_t),
+ .handler_fn = pvscsi_on_cmd_config,
+ },
+
+ /* Command not implemented, data size is unknown */
+ [PVSCSI_CMD_ISSUE_SCSI] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_issue_scsi,
+ },
+
+ /* Command not implemented, data size is unknown */
+ [PVSCSI_CMD_DEVICE_UNPLUG] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_cmd_unplug,
+ },
+
+ [PVSCSI_CMD_SETUP_RINGS] = {
+ .data_size = sizeof(PVSCSICmdDescSetupRings),
+ .handler_fn = pvscsi_on_cmd_setup_rings,
+ },
+
+ [PVSCSI_CMD_RESET_DEVICE] = {
+ .data_size = sizeof(struct PVSCSICmdDescResetDevice),
+ .handler_fn = pvscsi_on_cmd_reset_device,
+ },
+
+ [PVSCSI_CMD_RESET_BUS] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_cmd_reset_bus,
+ },
+
+ [PVSCSI_CMD_SETUP_MSG_RING] = {
+ .data_size = sizeof(PVSCSICmdDescSetupMsgRing),
+ .handler_fn = pvscsi_on_cmd_setup_msg_ring,
+ },
+
+ [PVSCSI_CMD_ADAPTER_RESET] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_cmd_adapter_reset,
+ },
+
+ [PVSCSI_CMD_ABORT_CMD] = {
+ .data_size = sizeof(struct PVSCSICmdDescAbortCmd),
+ .handler_fn = pvscsi_on_cmd_abort,
+ },
+};
+
+static void
+pvscsi_do_command_processing(PVSCSIState *s)
+{
+ size_t bytes_arrived = s->curr_cmd_data_cntr * sizeof(uint32_t);
+
+ assert(s->curr_cmd < PVSCSI_CMD_LAST);
+ if (bytes_arrived >= pvscsi_commands[s->curr_cmd].data_size) {
+ s->reg_command_status = pvscsi_commands[s->curr_cmd].handler_fn(s);
+ s->curr_cmd = PVSCSI_CMD_FIRST;
+ s->curr_cmd_data_cntr = 0;
+ }
+}
+
+static void
+pvscsi_on_command_data(PVSCSIState *s, uint32_t value)
+{
+ size_t bytes_arrived = s->curr_cmd_data_cntr * sizeof(uint32_t);
+
+ assert(bytes_arrived < sizeof(s->curr_cmd_data));
+ s->curr_cmd_data[s->curr_cmd_data_cntr++] = value;
+
+ pvscsi_do_command_processing(s);
+}
+
+static void
+pvscsi_on_command(PVSCSIState *s, uint64_t cmd_id)
+{
+ if ((cmd_id > PVSCSI_CMD_FIRST) && (cmd_id < PVSCSI_CMD_LAST)) {
+ s->curr_cmd = cmd_id;
+ } else {
+ s->curr_cmd = PVSCSI_CMD_FIRST;
+ trace_pvscsi_on_cmd_unknown(cmd_id);
+ }
+
+ s->curr_cmd_data_cntr = 0;
+ s->reg_command_status = PVSCSI_COMMAND_NOT_ENOUGH_DATA;
+
+ pvscsi_do_command_processing(s);
+}
+
+static void
+pvscsi_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PVSCSIState *s = opaque;
+
+ switch (addr) {
+ case PVSCSI_REG_OFFSET_COMMAND:
+ pvscsi_on_command(s, val);
+ break;
+
+ case PVSCSI_REG_OFFSET_COMMAND_DATA:
+ pvscsi_on_command_data(s, (uint32_t) val);
+ break;
+
+ case PVSCSI_REG_OFFSET_INTR_STATUS:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_INTR_STATUS", val);
+ s->reg_interrupt_status &= ~val;
+ pvscsi_update_irq_status(s);
+ pvscsi_schedule_completion_processing(s);
+ break;
+
+ case PVSCSI_REG_OFFSET_INTR_MASK:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_INTR_MASK", val);
+ s->reg_interrupt_enabled = val;
+ pvscsi_update_irq_status(s);
+ break;
+
+ case PVSCSI_REG_OFFSET_KICK_NON_RW_IO:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_KICK_NON_RW_IO", val);
+ pvscsi_process_io(s);
+ break;
+
+ case PVSCSI_REG_OFFSET_KICK_RW_IO:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_KICK_RW_IO", val);
+ pvscsi_process_io(s);
+ break;
+
+ case PVSCSI_REG_OFFSET_DEBUG:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_DEBUG", val);
+ break;
+
+ default:
+ trace_pvscsi_io_write_unknown(addr, size, val);
+ break;
+ }
+
+}
+
+static uint64_t
+pvscsi_io_read(void *opaque, hwaddr addr, unsigned size)
+{
+ PVSCSIState *s = opaque;
+
+ switch (addr) {
+ case PVSCSI_REG_OFFSET_INTR_STATUS:
+ trace_pvscsi_io_read("PVSCSI_REG_OFFSET_INTR_STATUS",
+ s->reg_interrupt_status);
+ return s->reg_interrupt_status;
+
+ case PVSCSI_REG_OFFSET_INTR_MASK:
+ trace_pvscsi_io_read("PVSCSI_REG_OFFSET_INTR_MASK",
+ s->reg_interrupt_status);
+ return s->reg_interrupt_enabled;
+
+ case PVSCSI_REG_OFFSET_COMMAND_STATUS:
+ trace_pvscsi_io_read("PVSCSI_REG_OFFSET_COMMAND_STATUS",
+ s->reg_interrupt_status);
+ return s->reg_command_status;
+
+ default:
+ trace_pvscsi_io_read_unknown(addr, size);
+ return 0;
+ }
+}
+
+
+static bool
+pvscsi_init_msi(PVSCSIState *s)
+{
+ int res;
+ PCIDevice *d = PCI_DEVICE(s);
+
+ res = msi_init(d, PVSCSI_MSI_OFFSET, PVSCSI_MSIX_NUM_VECTORS,
+ PVSCSI_USE_64BIT, PVSCSI_PER_VECTOR_MASK);
+ if (res < 0) {
+ trace_pvscsi_init_msi_fail(res);
+ s->msi_used = false;
+ } else {
+ s->msi_used = true;
+ }
+
+ return s->msi_used;
+}
+
+static void
+pvscsi_cleanup_msi(PVSCSIState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ if (s->msi_used) {
+ msi_uninit(d);
+ }
+}
+
+static const MemoryRegionOps pvscsi_ops = {
+ .read = pvscsi_io_read,
+ .write = pvscsi_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const struct SCSIBusInfo pvscsi_scsi_info = {
+ .tcq = true,
+ .max_target = PVSCSI_MAX_DEVS,
+ .max_channel = 0,
+ .max_lun = 0,
+
+ .get_sg_list = pvscsi_get_sg_list,
+ .complete = pvscsi_command_complete,
+ .cancel = pvscsi_request_cancelled,
+};
+
+static int
+pvscsi_init(PCIDevice *pci_dev)
+{
+ PVSCSIState *s = PVSCSI(pci_dev);
+
+ trace_pvscsi_state("init");
+
+ /* PCI subsystem ID */
+ pci_dev->config[PCI_SUBSYSTEM_ID] = 0x00;
+ pci_dev->config[PCI_SUBSYSTEM_ID + 1] = 0x10;
+
+ /* PCI latency timer = 255 */
+ pci_dev->config[PCI_LATENCY_TIMER] = 0xff;
+
+ /* Interrupt pin A */
+ pci_config_set_interrupt_pin(pci_dev->config, 1);
+
+ memory_region_init_io(&s->io_space, OBJECT(s), &pvscsi_ops, s,
+ "pvscsi-io", PVSCSI_MEM_SPACE_SIZE);
+ pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->io_space);
+
+ pvscsi_init_msi(s);
+
+ s->completion_worker = qemu_bh_new(pvscsi_process_completion_queue, s);
+ if (!s->completion_worker) {
+ pvscsi_cleanup_msi(s);
+ return -ENOMEM;
+ }
+
+ scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(pci_dev),
+ &pvscsi_scsi_info, NULL);
+ /* override default SCSI bus hotplug-handler, with pvscsi's one */
+ qbus_set_hotplug_handler(BUS(&s->bus), DEVICE(s), &error_abort);
+ pvscsi_reset_state(s);
+
+ return 0;
+}
+
+static void
+pvscsi_uninit(PCIDevice *pci_dev)
+{
+ PVSCSIState *s = PVSCSI(pci_dev);
+
+ trace_pvscsi_state("uninit");
+ qemu_bh_delete(s->completion_worker);
+
+ pvscsi_cleanup_msi(s);
+}
+
+static void
+pvscsi_reset(DeviceState *dev)
+{
+ PCIDevice *d = PCI_DEVICE(dev);
+ PVSCSIState *s = PVSCSI(d);
+
+ trace_pvscsi_state("reset");
+ pvscsi_reset_adapter(s);
+}
+
+static void
+pvscsi_pre_save(void *opaque)
+{
+ PVSCSIState *s = (PVSCSIState *) opaque;
+
+ trace_pvscsi_state("presave");
+
+ assert(QTAILQ_EMPTY(&s->pending_queue));
+ assert(QTAILQ_EMPTY(&s->completion_queue));
+}
+
+static int
+pvscsi_post_load(void *opaque, int version_id)
+{
+ trace_pvscsi_state("postload");
+ return 0;
+}
+
+static const VMStateDescription vmstate_pvscsi = {
+ .name = "pvscsi",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_save = pvscsi_pre_save,
+ .post_load = pvscsi_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PVSCSIState),
+ VMSTATE_UINT8(msi_used, PVSCSIState),
+ VMSTATE_UINT32(resetting, PVSCSIState),
+ VMSTATE_UINT64(reg_interrupt_status, PVSCSIState),
+ VMSTATE_UINT64(reg_interrupt_enabled, PVSCSIState),
+ VMSTATE_UINT64(reg_command_status, PVSCSIState),
+ VMSTATE_UINT64(curr_cmd, PVSCSIState),
+ VMSTATE_UINT32(curr_cmd_data_cntr, PVSCSIState),
+ VMSTATE_UINT32_ARRAY(curr_cmd_data, PVSCSIState,
+ ARRAY_SIZE(((PVSCSIState *)NULL)->curr_cmd_data)),
+ VMSTATE_UINT8(rings_info_valid, PVSCSIState),
+ VMSTATE_UINT8(msg_ring_info_valid, PVSCSIState),
+ VMSTATE_UINT8(use_msg, PVSCSIState),
+
+ VMSTATE_UINT64(rings.rs_pa, PVSCSIState),
+ VMSTATE_UINT32(rings.txr_len_mask, PVSCSIState),
+ VMSTATE_UINT32(rings.rxr_len_mask, PVSCSIState),
+ VMSTATE_UINT64_ARRAY(rings.req_ring_pages_pa, PVSCSIState,
+ PVSCSI_SETUP_RINGS_MAX_NUM_PAGES),
+ VMSTATE_UINT64_ARRAY(rings.cmp_ring_pages_pa, PVSCSIState,
+ PVSCSI_SETUP_RINGS_MAX_NUM_PAGES),
+ VMSTATE_UINT64(rings.consumed_ptr, PVSCSIState),
+ VMSTATE_UINT64(rings.filled_cmp_ptr, PVSCSIState),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property pvscsi_properties[] = {
+ DEFINE_PROP_UINT8("use_msg", PVSCSIState, use_msg, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pvscsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ k->init = pvscsi_init;
+ k->exit = pvscsi_uninit;
+ k->vendor_id = PCI_VENDOR_ID_VMWARE;
+ k->device_id = PCI_DEVICE_ID_VMWARE_PVSCSI;
+ k->class_id = PCI_CLASS_STORAGE_SCSI;
+ k->subsystem_id = 0x1000;
+ dc->reset = pvscsi_reset;
+ dc->vmsd = &vmstate_pvscsi;
+ dc->props = pvscsi_properties;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ hc->unplug = pvscsi_hot_unplug;
+ hc->plug = pvscsi_hotplug;
+}
+
+static const TypeInfo pvscsi_info = {
+ .name = TYPE_PVSCSI,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PVSCSIState),
+ .class_init = pvscsi_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static void
+pvscsi_register_types(void)
+{
+ type_register_static(&pvscsi_info);
+}
+
+type_init(pvscsi_register_types);
diff --git a/hw/scsi/vmw_pvscsi.h b/hw/scsi/vmw_pvscsi.h
new file mode 100644
index 00000000..17fcf662
--- /dev/null
+++ b/hw/scsi/vmw_pvscsi.h
@@ -0,0 +1,434 @@
+/*
+ * VMware PVSCSI header file
+ *
+ * Copyright (C) 2008-2009, VMware, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; version 2 of the License and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Maintained by: Arvind Kumar <arvindkumar@vmware.com>
+ *
+ */
+
+#ifndef VMW_PVSCSI_H
+#define VMW_PVSCSI_H
+
+#define VMW_PAGE_SIZE (4096)
+#define VMW_PAGE_SHIFT (12)
+
+#define MASK(n) ((1 << (n)) - 1) /* make an n-bit mask */
+
+/*
+ * host adapter status/error codes
+ */
+enum HostBusAdapterStatus {
+ BTSTAT_SUCCESS = 0x00, /* CCB complete normally with no errors */
+ BTSTAT_LINKED_COMMAND_COMPLETED = 0x0a,
+ BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG = 0x0b,
+ BTSTAT_DATA_UNDERRUN = 0x0c,
+ BTSTAT_SELTIMEO = 0x11, /* SCSI selection timeout */
+ BTSTAT_DATARUN = 0x12, /* data overrun/underrun */
+ BTSTAT_BUSFREE = 0x13, /* unexpected bus free */
+ BTSTAT_INVPHASE = 0x14, /* invalid bus phase or sequence */
+ /* requested by target */
+ BTSTAT_LUNMISMATCH = 0x17, /* linked CCB has different LUN */
+ /* from first CCB */
+ BTSTAT_SENSFAILED = 0x1b, /* auto request sense failed */
+ BTSTAT_TAGREJECT = 0x1c, /* SCSI II tagged queueing message */
+ /* rejected by target */
+ BTSTAT_BADMSG = 0x1d, /* unsupported message received by */
+ /* the host adapter */
+ BTSTAT_HAHARDWARE = 0x20, /* host adapter hardware failed */
+ BTSTAT_NORESPONSE = 0x21, /* target did not respond to SCSI ATN, */
+ /* sent a SCSI RST */
+ BTSTAT_SENTRST = 0x22, /* host adapter asserted a SCSI RST */
+ BTSTAT_RECVRST = 0x23, /* other SCSI devices asserted a SCSI RST */
+ BTSTAT_DISCONNECT = 0x24, /* target device reconnected improperly */
+ /* (w/o tag) */
+ BTSTAT_BUSRESET = 0x25, /* host adapter issued BUS device reset */
+ BTSTAT_ABORTQUEUE = 0x26, /* abort queue generated */
+ BTSTAT_HASOFTWARE = 0x27, /* host adapter software error */
+ BTSTAT_HATIMEOUT = 0x30, /* host adapter hardware timeout error */
+ BTSTAT_SCSIPARITY = 0x34, /* SCSI parity error detected */
+};
+
+/*
+ * Register offsets.
+ *
+ * These registers are accessible both via i/o space and mm i/o.
+ */
+
+enum PVSCSIRegOffset {
+ PVSCSI_REG_OFFSET_COMMAND = 0x0,
+ PVSCSI_REG_OFFSET_COMMAND_DATA = 0x4,
+ PVSCSI_REG_OFFSET_COMMAND_STATUS = 0x8,
+ PVSCSI_REG_OFFSET_LAST_STS_0 = 0x100,
+ PVSCSI_REG_OFFSET_LAST_STS_1 = 0x104,
+ PVSCSI_REG_OFFSET_LAST_STS_2 = 0x108,
+ PVSCSI_REG_OFFSET_LAST_STS_3 = 0x10c,
+ PVSCSI_REG_OFFSET_INTR_STATUS = 0x100c,
+ PVSCSI_REG_OFFSET_INTR_MASK = 0x2010,
+ PVSCSI_REG_OFFSET_KICK_NON_RW_IO = 0x3014,
+ PVSCSI_REG_OFFSET_DEBUG = 0x3018,
+ PVSCSI_REG_OFFSET_KICK_RW_IO = 0x4018,
+};
+
+/*
+ * Virtual h/w commands.
+ */
+
+enum PVSCSICommands {
+ PVSCSI_CMD_FIRST = 0, /* has to be first */
+
+ PVSCSI_CMD_ADAPTER_RESET = 1,
+ PVSCSI_CMD_ISSUE_SCSI = 2,
+ PVSCSI_CMD_SETUP_RINGS = 3,
+ PVSCSI_CMD_RESET_BUS = 4,
+ PVSCSI_CMD_RESET_DEVICE = 5,
+ PVSCSI_CMD_ABORT_CMD = 6,
+ PVSCSI_CMD_CONFIG = 7,
+ PVSCSI_CMD_SETUP_MSG_RING = 8,
+ PVSCSI_CMD_DEVICE_UNPLUG = 9,
+
+ PVSCSI_CMD_LAST = 10 /* has to be last */
+};
+
+#define PVSCSI_COMMAND_PROCESSING_SUCCEEDED (0)
+#define PVSCSI_COMMAND_PROCESSING_FAILED (-1)
+#define PVSCSI_COMMAND_NOT_ENOUGH_DATA (-2)
+
+/*
+ * Command descriptor for PVSCSI_CMD_RESET_DEVICE --
+ */
+
+struct PVSCSICmdDescResetDevice {
+ uint32_t target;
+ uint8_t lun[8];
+} QEMU_PACKED;
+
+typedef struct PVSCSICmdDescResetDevice PVSCSICmdDescResetDevice;
+
+/*
+ * Command descriptor for PVSCSI_CMD_ABORT_CMD --
+ *
+ * - currently does not support specifying the LUN.
+ * - pad should be 0.
+ */
+
+struct PVSCSICmdDescAbortCmd {
+ uint64_t context;
+ uint32_t target;
+ uint32_t pad;
+} QEMU_PACKED;
+
+typedef struct PVSCSICmdDescAbortCmd PVSCSICmdDescAbortCmd;
+
+/*
+ * Command descriptor for PVSCSI_CMD_SETUP_RINGS --
+ *
+ * Notes:
+ * - reqRingNumPages and cmpRingNumPages need to be power of two.
+ * - reqRingNumPages and cmpRingNumPages need to be different from 0,
+ * - reqRingNumPages and cmpRingNumPages need to be inferior to
+ * PVSCSI_SETUP_RINGS_MAX_NUM_PAGES.
+ */
+
+#define PVSCSI_SETUP_RINGS_MAX_NUM_PAGES 32
+struct PVSCSICmdDescSetupRings {
+ uint32_t reqRingNumPages;
+ uint32_t cmpRingNumPages;
+ uint64_t ringsStatePPN;
+ uint64_t reqRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ uint64_t cmpRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+} QEMU_PACKED;
+
+typedef struct PVSCSICmdDescSetupRings PVSCSICmdDescSetupRings;
+
+/*
+ * Command descriptor for PVSCSI_CMD_SETUP_MSG_RING --
+ *
+ * Notes:
+ * - this command was not supported in the initial revision of the h/w
+ * interface. Before using it, you need to check that it is supported by
+ * writing PVSCSI_CMD_SETUP_MSG_RING to the 'command' register, then
+ * immediately after read the 'command status' register:
+ * * a value of -1 means that the cmd is NOT supported,
+ * * a value != -1 means that the cmd IS supported.
+ * If it's supported the 'command status' register should return:
+ * sizeof(PVSCSICmdDescSetupMsgRing) / sizeof(uint32_t).
+ * - this command should be issued _after_ the usual SETUP_RINGS so that the
+ * RingsState page is already setup. If not, the command is a nop.
+ * - numPages needs to be a power of two,
+ * - numPages needs to be different from 0,
+ * - pad should be zero.
+ */
+
+#define PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES 16
+
+struct PVSCSICmdDescSetupMsgRing {
+ uint32_t numPages;
+ uint32_t pad;
+ uint64_t ringPPNs[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES];
+} QEMU_PACKED;
+
+typedef struct PVSCSICmdDescSetupMsgRing PVSCSICmdDescSetupMsgRing;
+
+enum PVSCSIMsgType {
+ PVSCSI_MSG_DEV_ADDED = 0,
+ PVSCSI_MSG_DEV_REMOVED = 1,
+ PVSCSI_MSG_LAST = 2,
+};
+
+/*
+ * Msg descriptor.
+ *
+ * sizeof(struct PVSCSIRingMsgDesc) == 128.
+ *
+ * - type is of type enum PVSCSIMsgType.
+ * - the content of args depend on the type of event being delivered.
+ */
+
+struct PVSCSIRingMsgDesc {
+ uint32_t type;
+ uint32_t args[31];
+} QEMU_PACKED;
+
+typedef struct PVSCSIRingMsgDesc PVSCSIRingMsgDesc;
+
+struct PVSCSIMsgDescDevStatusChanged {
+ uint32_t type; /* PVSCSI_MSG_DEV _ADDED / _REMOVED */
+ uint32_t bus;
+ uint32_t target;
+ uint8_t lun[8];
+ uint32_t pad[27];
+} QEMU_PACKED;
+
+typedef struct PVSCSIMsgDescDevStatusChanged PVSCSIMsgDescDevStatusChanged;
+
+/*
+ * Rings state.
+ *
+ * - the fields:
+ * . msgProdIdx,
+ * . msgConsIdx,
+ * . msgNumEntriesLog2,
+ * .. are only used once the SETUP_MSG_RING cmd has been issued.
+ * - 'pad' helps to ensure that the msg related fields are on their own
+ * cache-line.
+ */
+
+struct PVSCSIRingsState {
+ uint32_t reqProdIdx;
+ uint32_t reqConsIdx;
+ uint32_t reqNumEntriesLog2;
+
+ uint32_t cmpProdIdx;
+ uint32_t cmpConsIdx;
+ uint32_t cmpNumEntriesLog2;
+
+ uint8_t pad[104];
+
+ uint32_t msgProdIdx;
+ uint32_t msgConsIdx;
+ uint32_t msgNumEntriesLog2;
+} QEMU_PACKED;
+
+typedef struct PVSCSIRingsState PVSCSIRingsState;
+
+/*
+ * Request descriptor.
+ *
+ * sizeof(RingReqDesc) = 128
+ *
+ * - context: is a unique identifier of a command. It could normally be any
+ * 64bit value, however we currently store it in the serialNumber variable
+ * of struct SCSI_Command, so we have the following restrictions due to the
+ * way this field is handled in the vmkernel storage stack:
+ * * this value can't be 0,
+ * * the upper 32bit need to be 0 since serialNumber is as a uint32_t.
+ * Currently tracked as PR 292060.
+ * - dataLen: contains the total number of bytes that need to be transferred.
+ * - dataAddr:
+ * * if PVSCSI_FLAG_CMD_WITH_SG_LIST is set: dataAddr is the PA of the first
+ * s/g table segment, each s/g segment is entirely contained on a single
+ * page of physical memory,
+ * * if PVSCSI_FLAG_CMD_WITH_SG_LIST is NOT set, then dataAddr is the PA of
+ * the buffer used for the DMA transfer,
+ * - flags:
+ * * PVSCSI_FLAG_CMD_WITH_SG_LIST: see dataAddr above,
+ * * PVSCSI_FLAG_CMD_DIR_NONE: no DMA involved,
+ * * PVSCSI_FLAG_CMD_DIR_TOHOST: transfer from device to main memory,
+ * * PVSCSI_FLAG_CMD_DIR_TODEVICE: transfer from main memory to device,
+ * * PVSCSI_FLAG_CMD_OUT_OF_BAND_CDB: reserved to handle CDBs larger than
+ * 16bytes. To be specified.
+ * - vcpuHint: vcpuId of the processor that will be most likely waiting for the
+ * completion of the i/o. For guest OSes that use lowest priority message
+ * delivery mode (such as windows), we use this "hint" to deliver the
+ * completion action to the proper vcpu. For now, we can use the vcpuId of
+ * the processor that initiated the i/o as a likely candidate for the vcpu
+ * that will be waiting for the completion..
+ * - bus should be 0: we currently only support bus 0 for now.
+ * - unused should be zero'd.
+ */
+
+#define PVSCSI_FLAG_CMD_WITH_SG_LIST (1 << 0)
+#define PVSCSI_FLAG_CMD_OUT_OF_BAND_CDB (1 << 1)
+#define PVSCSI_FLAG_CMD_DIR_NONE (1 << 2)
+#define PVSCSI_FLAG_CMD_DIR_TOHOST (1 << 3)
+#define PVSCSI_FLAG_CMD_DIR_TODEVICE (1 << 4)
+
+#define PVSCSI_KNOWN_FLAGS \
+ (PVSCSI_FLAG_CMD_WITH_SG_LIST | \
+ PVSCSI_FLAG_CMD_OUT_OF_BAND_CDB | \
+ PVSCSI_FLAG_CMD_DIR_NONE | \
+ PVSCSI_FLAG_CMD_DIR_TOHOST | \
+ PVSCSI_FLAG_CMD_DIR_TODEVICE)
+
+struct PVSCSIRingReqDesc {
+ uint64_t context;
+ uint64_t dataAddr;
+ uint64_t dataLen;
+ uint64_t senseAddr;
+ uint32_t senseLen;
+ uint32_t flags;
+ uint8_t cdb[16];
+ uint8_t cdbLen;
+ uint8_t lun[8];
+ uint8_t tag;
+ uint8_t bus;
+ uint8_t target;
+ uint8_t vcpuHint;
+ uint8_t unused[59];
+} QEMU_PACKED;
+
+typedef struct PVSCSIRingReqDesc PVSCSIRingReqDesc;
+
+/*
+ * Scatter-gather list management.
+ *
+ * As described above, when PVSCSI_FLAG_CMD_WITH_SG_LIST is set in the
+ * RingReqDesc.flags, then RingReqDesc.dataAddr is the PA of the first s/g
+ * table segment.
+ *
+ * - each segment of the s/g table contain a succession of struct
+ * PVSCSISGElement.
+ * - each segment is entirely contained on a single physical page of memory.
+ * - a "chain" s/g element has the flag PVSCSI_SGE_FLAG_CHAIN_ELEMENT set in
+ * PVSCSISGElement.flags and in this case:
+ * * addr is the PA of the next s/g segment,
+ * * length is undefined, assumed to be 0.
+ */
+
+struct PVSCSISGElement {
+ uint64_t addr;
+ uint32_t length;
+ uint32_t flags;
+} QEMU_PACKED;
+
+typedef struct PVSCSISGElement PVSCSISGElement;
+
+/*
+ * Completion descriptor.
+ *
+ * sizeof(RingCmpDesc) = 32
+ *
+ * - context: identifier of the command. The same thing that was specified
+ * under "context" as part of struct RingReqDesc at initiation time,
+ * - dataLen: number of bytes transferred for the actual i/o operation,
+ * - senseLen: number of bytes written into the sense buffer,
+ * - hostStatus: adapter status,
+ * - scsiStatus: device status,
+ * - pad should be zero.
+ */
+
+struct PVSCSIRingCmpDesc {
+ uint64_t context;
+ uint64_t dataLen;
+ uint32_t senseLen;
+ uint16_t hostStatus;
+ uint16_t scsiStatus;
+ uint32_t pad[2];
+} QEMU_PACKED;
+
+typedef struct PVSCSIRingCmpDesc PVSCSIRingCmpDesc;
+
+/*
+ * Interrupt status / IRQ bits.
+ */
+
+#define PVSCSI_INTR_CMPL_0 (1 << 0)
+#define PVSCSI_INTR_CMPL_1 (1 << 1)
+#define PVSCSI_INTR_CMPL_MASK MASK(2)
+
+#define PVSCSI_INTR_MSG_0 (1 << 2)
+#define PVSCSI_INTR_MSG_1 (1 << 3)
+#define PVSCSI_INTR_MSG_MASK (MASK(2) << 2)
+
+#define PVSCSI_INTR_ALL_SUPPORTED MASK(4)
+
+/*
+ * Number of MSI-X vectors supported.
+ */
+#define PVSCSI_MAX_INTRS 24
+
+/*
+ * Enumeration of supported MSI-X vectors
+ */
+#define PVSCSI_VECTOR_COMPLETION 0
+
+/*
+ * Misc constants for the rings.
+ */
+
+#define PVSCSI_MAX_NUM_PAGES_REQ_RING PVSCSI_SETUP_RINGS_MAX_NUM_PAGES
+#define PVSCSI_MAX_NUM_PAGES_CMP_RING PVSCSI_SETUP_RINGS_MAX_NUM_PAGES
+#define PVSCSI_MAX_NUM_PAGES_MSG_RING PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES
+
+#define PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE \
+ (VMW_PAGE_SIZE / sizeof(struct PVSCSIRingReqDesc))
+
+#define PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE \
+ (VMW_PAGE_SIZE / sizeof(PVSCSIRingCmpDesc))
+
+#define PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE \
+ (VMW_PAGE_SIZE / sizeof(PVSCSIRingMsgDesc))
+
+#define PVSCSI_MAX_REQ_QUEUE_DEPTH \
+ (PVSCSI_MAX_NUM_PAGES_REQ_RING * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE)
+
+#define PVSCSI_MEM_SPACE_COMMAND_NUM_PAGES 1
+#define PVSCSI_MEM_SPACE_INTR_STATUS_NUM_PAGES 1
+#define PVSCSI_MEM_SPACE_MISC_NUM_PAGES 2
+#define PVSCSI_MEM_SPACE_KICK_IO_NUM_PAGES 2
+#define PVSCSI_MEM_SPACE_MSIX_NUM_PAGES 2
+
+enum PVSCSIMemSpace {
+ PVSCSI_MEM_SPACE_COMMAND_PAGE = 0,
+ PVSCSI_MEM_SPACE_INTR_STATUS_PAGE = 1,
+ PVSCSI_MEM_SPACE_MISC_PAGE = 2,
+ PVSCSI_MEM_SPACE_KICK_IO_PAGE = 4,
+ PVSCSI_MEM_SPACE_MSIX_TABLE_PAGE = 6,
+ PVSCSI_MEM_SPACE_MSIX_PBA_PAGE = 7,
+};
+
+#define PVSCSI_MEM_SPACE_NUM_PAGES \
+ (PVSCSI_MEM_SPACE_COMMAND_NUM_PAGES + \
+ PVSCSI_MEM_SPACE_INTR_STATUS_NUM_PAGES + \
+ PVSCSI_MEM_SPACE_MISC_NUM_PAGES + \
+ PVSCSI_MEM_SPACE_KICK_IO_NUM_PAGES + \
+ PVSCSI_MEM_SPACE_MSIX_NUM_PAGES)
+
+#define PVSCSI_MEM_SPACE_SIZE (PVSCSI_MEM_SPACE_NUM_PAGES * VMW_PAGE_SIZE)
+
+#endif /* VMW_PVSCSI_H */
diff --git a/hw/sd/Makefile.objs b/hw/sd/Makefile.objs
new file mode 100644
index 00000000..f1aed83d
--- /dev/null
+++ b/hw/sd/Makefile.objs
@@ -0,0 +1,8 @@
+common-obj-$(CONFIG_PL181) += pl181.o
+common-obj-$(CONFIG_SSI_SD) += ssi-sd.o
+common-obj-$(CONFIG_SD) += sd.o
+common-obj-$(CONFIG_SDHCI) += sdhci.o
+
+obj-$(CONFIG_MILKYMIST) += milkymist-memcard.o
+obj-$(CONFIG_OMAP) += omap_mmc.o
+obj-$(CONFIG_PXA2XX) += pxa2xx_mmci.o
diff --git a/hw/sd/milkymist-memcard.c b/hw/sd/milkymist-memcard.c
new file mode 100644
index 00000000..2209ef1d
--- /dev/null
+++ b/hw/sd/milkymist-memcard.c
@@ -0,0 +1,316 @@
+/*
+ * QEMU model of the Milkymist SD Card Controller.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/memcard.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+#include "qemu/error-report.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/sd.h"
+
+enum {
+ ENABLE_CMD_TX = (1<<0),
+ ENABLE_CMD_RX = (1<<1),
+ ENABLE_DAT_TX = (1<<2),
+ ENABLE_DAT_RX = (1<<3),
+};
+
+enum {
+ PENDING_CMD_TX = (1<<0),
+ PENDING_CMD_RX = (1<<1),
+ PENDING_DAT_TX = (1<<2),
+ PENDING_DAT_RX = (1<<3),
+};
+
+enum {
+ START_CMD_TX = (1<<0),
+ START_DAT_RX = (1<<1),
+};
+
+enum {
+ R_CLK2XDIV = 0,
+ R_ENABLE,
+ R_PENDING,
+ R_START,
+ R_CMD,
+ R_DAT,
+ R_MAX
+};
+
+#define TYPE_MILKYMIST_MEMCARD "milkymist-memcard"
+#define MILKYMIST_MEMCARD(obj) \
+ OBJECT_CHECK(MilkymistMemcardState, (obj), TYPE_MILKYMIST_MEMCARD)
+
+struct MilkymistMemcardState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+ SDState *card;
+
+ int command_write_ptr;
+ int response_read_ptr;
+ int response_len;
+ int ignore_next_cmd;
+ int enabled;
+ uint8_t command[6];
+ uint8_t response[17];
+ uint32_t regs[R_MAX];
+};
+typedef struct MilkymistMemcardState MilkymistMemcardState;
+
+static void update_pending_bits(MilkymistMemcardState *s)
+{
+ /* transmits are instantaneous, thus tx pending bits are never set */
+ s->regs[R_PENDING] = 0;
+ /* if rx is enabled the corresponding pending bits are always set */
+ if (s->regs[R_ENABLE] & ENABLE_CMD_RX) {
+ s->regs[R_PENDING] |= PENDING_CMD_RX;
+ }
+ if (s->regs[R_ENABLE] & ENABLE_DAT_RX) {
+ s->regs[R_PENDING] |= PENDING_DAT_RX;
+ }
+}
+
+static void memcard_sd_command(MilkymistMemcardState *s)
+{
+ SDRequest req;
+
+ req.cmd = s->command[0] & 0x3f;
+ req.arg = (s->command[1] << 24) | (s->command[2] << 16)
+ | (s->command[3] << 8) | s->command[4];
+ req.crc = s->command[5];
+
+ s->response[0] = req.cmd;
+ s->response_len = sd_do_command(s->card, &req, s->response+1);
+ s->response_read_ptr = 0;
+
+ if (s->response_len == 16) {
+ /* R2 response */
+ s->response[0] = 0x3f;
+ s->response_len += 1;
+ } else if (s->response_len == 4) {
+ /* no crc calculation, insert dummy byte */
+ s->response[5] = 0;
+ s->response_len += 2;
+ }
+
+ if (req.cmd == 0) {
+ /* next write is a dummy byte to clock the initialization of the sd
+ * card */
+ s->ignore_next_cmd = 1;
+ }
+}
+
+static uint64_t memcard_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistMemcardState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CMD:
+ if (!s->enabled) {
+ r = 0xff;
+ } else {
+ r = s->response[s->response_read_ptr++];
+ if (s->response_read_ptr > s->response_len) {
+ error_report("milkymist_memcard: "
+ "read more cmd bytes than available. Clipping.");
+ s->response_read_ptr = 0;
+ }
+ }
+ break;
+ case R_DAT:
+ if (!s->enabled) {
+ r = 0xffffffff;
+ } else {
+ r = 0;
+ r |= sd_read_data(s->card) << 24;
+ r |= sd_read_data(s->card) << 16;
+ r |= sd_read_data(s->card) << 8;
+ r |= sd_read_data(s->card);
+ }
+ break;
+ case R_CLK2XDIV:
+ case R_ENABLE:
+ case R_PENDING:
+ case R_START:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_memcard: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_memcard_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void memcard_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistMemcardState *s = opaque;
+
+ trace_milkymist_memcard_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_PENDING:
+ /* clear rx pending bits */
+ s->regs[R_PENDING] &= ~(value & (PENDING_CMD_RX | PENDING_DAT_RX));
+ update_pending_bits(s);
+ break;
+ case R_CMD:
+ if (!s->enabled) {
+ break;
+ }
+ if (s->ignore_next_cmd) {
+ s->ignore_next_cmd = 0;
+ break;
+ }
+ s->command[s->command_write_ptr] = value & 0xff;
+ s->command_write_ptr = (s->command_write_ptr + 1) % 6;
+ if (s->command_write_ptr == 0) {
+ memcard_sd_command(s);
+ }
+ break;
+ case R_DAT:
+ if (!s->enabled) {
+ break;
+ }
+ sd_write_data(s->card, (value >> 24) & 0xff);
+ sd_write_data(s->card, (value >> 16) & 0xff);
+ sd_write_data(s->card, (value >> 8) & 0xff);
+ sd_write_data(s->card, value & 0xff);
+ break;
+ case R_ENABLE:
+ s->regs[addr] = value;
+ update_pending_bits(s);
+ break;
+ case R_CLK2XDIV:
+ case R_START:
+ s->regs[addr] = value;
+ break;
+
+ default:
+ error_report("milkymist_memcard: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps memcard_mmio_ops = {
+ .read = memcard_read,
+ .write = memcard_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void milkymist_memcard_reset(DeviceState *d)
+{
+ MilkymistMemcardState *s = MILKYMIST_MEMCARD(d);
+ int i;
+
+ s->command_write_ptr = 0;
+ s->response_read_ptr = 0;
+ s->response_len = 0;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+}
+
+static int milkymist_memcard_init(SysBusDevice *dev)
+{
+ MilkymistMemcardState *s = MILKYMIST_MEMCARD(dev);
+ DriveInfo *dinfo;
+ BlockBackend *blk;
+
+ /* FIXME use a qdev drive property instead of drive_get_next() */
+ dinfo = drive_get_next(IF_SD);
+ blk = dinfo ? blk_by_legacy_dinfo(dinfo) : NULL;
+ s->card = sd_init(blk, false);
+ if (s->card == NULL) {
+ return -1;
+ }
+
+ s->enabled = blk && blk_is_inserted(blk);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &memcard_mmio_ops, s,
+ "milkymist-memcard", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_memcard = {
+ .name = "milkymist-memcard",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(command_write_ptr, MilkymistMemcardState),
+ VMSTATE_INT32(response_read_ptr, MilkymistMemcardState),
+ VMSTATE_INT32(response_len, MilkymistMemcardState),
+ VMSTATE_INT32(ignore_next_cmd, MilkymistMemcardState),
+ VMSTATE_INT32(enabled, MilkymistMemcardState),
+ VMSTATE_UINT8_ARRAY(command, MilkymistMemcardState, 6),
+ VMSTATE_UINT8_ARRAY(response, MilkymistMemcardState, 17),
+ VMSTATE_UINT32_ARRAY(regs, MilkymistMemcardState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void milkymist_memcard_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_memcard_init;
+ dc->reset = milkymist_memcard_reset;
+ dc->vmsd = &vmstate_milkymist_memcard;
+ /* Reason: init() method uses drive_get_next() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo milkymist_memcard_info = {
+ .name = TYPE_MILKYMIST_MEMCARD,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistMemcardState),
+ .class_init = milkymist_memcard_class_init,
+};
+
+static void milkymist_memcard_register_types(void)
+{
+ type_register_static(&milkymist_memcard_info);
+}
+
+type_init(milkymist_memcard_register_types)
diff --git a/hw/sd/omap_mmc.c b/hw/sd/omap_mmc.c
new file mode 100644
index 00000000..d072deca
--- /dev/null
+++ b/hw/sd/omap_mmc.c
@@ -0,0 +1,648 @@
+/*
+ * OMAP on-chip MMC/SD host emulation.
+ *
+ * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+#include "hw/sd.h"
+
+struct omap_mmc_s {
+ qemu_irq irq;
+ qemu_irq *dma;
+ qemu_irq coverswitch;
+ MemoryRegion iomem;
+ omap_clk clk;
+ SDState *card;
+ uint16_t last_cmd;
+ uint16_t sdio;
+ uint16_t rsp[8];
+ uint32_t arg;
+ int lines;
+ int dw;
+ int mode;
+ int enable;
+ int be;
+ int rev;
+ uint16_t status;
+ uint16_t mask;
+ uint8_t cto;
+ uint16_t dto;
+ int clkdiv;
+ uint16_t fifo[32];
+ int fifo_start;
+ int fifo_len;
+ uint16_t blen;
+ uint16_t blen_counter;
+ uint16_t nblk;
+ uint16_t nblk_counter;
+ int tx_dma;
+ int rx_dma;
+ int af_level;
+ int ae_level;
+
+ int ddir;
+ int transfer;
+
+ int cdet_wakeup;
+ int cdet_enable;
+ int cdet_state;
+ qemu_irq cdet;
+};
+
+static void omap_mmc_interrupts_update(struct omap_mmc_s *s)
+{
+ qemu_set_irq(s->irq, !!(s->status & s->mask));
+}
+
+static void omap_mmc_fifolevel_update(struct omap_mmc_s *host)
+{
+ if (!host->transfer && !host->fifo_len) {
+ host->status &= 0xf3ff;
+ return;
+ }
+
+ if (host->fifo_len > host->af_level && host->ddir) {
+ if (host->rx_dma) {
+ host->status &= 0xfbff;
+ qemu_irq_raise(host->dma[1]);
+ } else
+ host->status |= 0x0400;
+ } else {
+ host->status &= 0xfbff;
+ qemu_irq_lower(host->dma[1]);
+ }
+
+ if (host->fifo_len < host->ae_level && !host->ddir) {
+ if (host->tx_dma) {
+ host->status &= 0xf7ff;
+ qemu_irq_raise(host->dma[0]);
+ } else
+ host->status |= 0x0800;
+ } else {
+ qemu_irq_lower(host->dma[0]);
+ host->status &= 0xf7ff;
+ }
+}
+
+typedef enum {
+ sd_nore = 0, /* no response */
+ sd_r1, /* normal response command */
+ sd_r2, /* CID, CSD registers */
+ sd_r3, /* OCR register */
+ sd_r6 = 6, /* Published RCA response */
+ sd_r1b = -1,
+} sd_rsp_type_t;
+
+static void omap_mmc_command(struct omap_mmc_s *host, int cmd, int dir,
+ sd_cmd_type_t type, int busy, sd_rsp_type_t resptype, int init)
+{
+ uint32_t rspstatus, mask;
+ int rsplen, timeout;
+ SDRequest request;
+ uint8_t response[16];
+
+ if (init && cmd == 0) {
+ host->status |= 0x0001;
+ return;
+ }
+
+ if (resptype == sd_r1 && busy)
+ resptype = sd_r1b;
+
+ if (type == sd_adtc) {
+ host->fifo_start = 0;
+ host->fifo_len = 0;
+ host->transfer = 1;
+ host->ddir = dir;
+ } else
+ host->transfer = 0;
+ timeout = 0;
+ mask = 0;
+ rspstatus = 0;
+
+ request.cmd = cmd;
+ request.arg = host->arg;
+ request.crc = 0; /* FIXME */
+
+ rsplen = sd_do_command(host->card, &request, response);
+
+ /* TODO: validate CRCs */
+ switch (resptype) {
+ case sd_nore:
+ rsplen = 0;
+ break;
+
+ case sd_r1:
+ case sd_r1b:
+ if (rsplen < 4) {
+ timeout = 1;
+ break;
+ }
+ rsplen = 4;
+
+ mask = OUT_OF_RANGE | ADDRESS_ERROR | BLOCK_LEN_ERROR |
+ ERASE_SEQ_ERROR | ERASE_PARAM | WP_VIOLATION |
+ LOCK_UNLOCK_FAILED | COM_CRC_ERROR | ILLEGAL_COMMAND |
+ CARD_ECC_FAILED | CC_ERROR | SD_ERROR |
+ CID_CSD_OVERWRITE;
+ if (host->sdio & (1 << 13))
+ mask |= AKE_SEQ_ERROR;
+ rspstatus = (response[0] << 24) | (response[1] << 16) |
+ (response[2] << 8) | (response[3] << 0);
+ break;
+
+ case sd_r2:
+ if (rsplen < 16) {
+ timeout = 1;
+ break;
+ }
+ rsplen = 16;
+ break;
+
+ case sd_r3:
+ if (rsplen < 4) {
+ timeout = 1;
+ break;
+ }
+ rsplen = 4;
+
+ rspstatus = (response[0] << 24) | (response[1] << 16) |
+ (response[2] << 8) | (response[3] << 0);
+ if (rspstatus & 0x80000000)
+ host->status &= 0xe000;
+ else
+ host->status |= 0x1000;
+ break;
+
+ case sd_r6:
+ if (rsplen < 4) {
+ timeout = 1;
+ break;
+ }
+ rsplen = 4;
+
+ mask = 0xe000 | AKE_SEQ_ERROR;
+ rspstatus = (response[2] << 8) | (response[3] << 0);
+ }
+
+ if (rspstatus & mask)
+ host->status |= 0x4000;
+ else
+ host->status &= 0xb000;
+
+ if (rsplen)
+ for (rsplen = 0; rsplen < 8; rsplen ++)
+ host->rsp[~rsplen & 7] = response[(rsplen << 1) | 1] |
+ (response[(rsplen << 1) | 0] << 8);
+
+ if (timeout)
+ host->status |= 0x0080;
+ else if (cmd == 12)
+ host->status |= 0x0005; /* Makes it more real */
+ else
+ host->status |= 0x0001;
+}
+
+static void omap_mmc_transfer(struct omap_mmc_s *host)
+{
+ uint8_t value;
+
+ if (!host->transfer)
+ return;
+
+ while (1) {
+ if (host->ddir) {
+ if (host->fifo_len > host->af_level)
+ break;
+
+ value = sd_read_data(host->card);
+ host->fifo[(host->fifo_start + host->fifo_len) & 31] = value;
+ if (-- host->blen_counter) {
+ value = sd_read_data(host->card);
+ host->fifo[(host->fifo_start + host->fifo_len) & 31] |=
+ value << 8;
+ host->blen_counter --;
+ }
+
+ host->fifo_len ++;
+ } else {
+ if (!host->fifo_len)
+ break;
+
+ value = host->fifo[host->fifo_start] & 0xff;
+ sd_write_data(host->card, value);
+ if (-- host->blen_counter) {
+ value = host->fifo[host->fifo_start] >> 8;
+ sd_write_data(host->card, value);
+ host->blen_counter --;
+ }
+
+ host->fifo_start ++;
+ host->fifo_len --;
+ host->fifo_start &= 31;
+ }
+
+ if (host->blen_counter == 0) {
+ host->nblk_counter --;
+ host->blen_counter = host->blen;
+
+ if (host->nblk_counter == 0) {
+ host->nblk_counter = host->nblk;
+ host->transfer = 0;
+ host->status |= 0x0008;
+ break;
+ }
+ }
+ }
+}
+
+static void omap_mmc_update(void *opaque)
+{
+ struct omap_mmc_s *s = opaque;
+ omap_mmc_transfer(s);
+ omap_mmc_fifolevel_update(s);
+ omap_mmc_interrupts_update(s);
+}
+
+void omap_mmc_reset(struct omap_mmc_s *host)
+{
+ host->last_cmd = 0;
+ memset(host->rsp, 0, sizeof(host->rsp));
+ host->arg = 0;
+ host->dw = 0;
+ host->mode = 0;
+ host->enable = 0;
+ host->status = 0;
+ host->mask = 0;
+ host->cto = 0;
+ host->dto = 0;
+ host->fifo_len = 0;
+ host->blen = 0;
+ host->blen_counter = 0;
+ host->nblk = 0;
+ host->nblk_counter = 0;
+ host->tx_dma = 0;
+ host->rx_dma = 0;
+ host->ae_level = 0x00;
+ host->af_level = 0x1f;
+ host->transfer = 0;
+ host->cdet_wakeup = 0;
+ host->cdet_enable = 0;
+ qemu_set_irq(host->coverswitch, host->cdet_state);
+ host->clkdiv = 0;
+}
+
+static uint64_t omap_mmc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint16_t i;
+ struct omap_mmc_s *s = (struct omap_mmc_s *) opaque;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, offset);
+ }
+
+ switch (offset) {
+ case 0x00: /* MMC_CMD */
+ return s->last_cmd;
+
+ case 0x04: /* MMC_ARGL */
+ return s->arg & 0x0000ffff;
+
+ case 0x08: /* MMC_ARGH */
+ return s->arg >> 16;
+
+ case 0x0c: /* MMC_CON */
+ return (s->dw << 15) | (s->mode << 12) | (s->enable << 11) |
+ (s->be << 10) | s->clkdiv;
+
+ case 0x10: /* MMC_STAT */
+ return s->status;
+
+ case 0x14: /* MMC_IE */
+ return s->mask;
+
+ case 0x18: /* MMC_CTO */
+ return s->cto;
+
+ case 0x1c: /* MMC_DTO */
+ return s->dto;
+
+ case 0x20: /* MMC_DATA */
+ /* TODO: support 8-bit access */
+ i = s->fifo[s->fifo_start];
+ if (s->fifo_len == 0) {
+ printf("MMC: FIFO underrun\n");
+ return i;
+ }
+ s->fifo_start ++;
+ s->fifo_len --;
+ s->fifo_start &= 31;
+ omap_mmc_transfer(s);
+ omap_mmc_fifolevel_update(s);
+ omap_mmc_interrupts_update(s);
+ return i;
+
+ case 0x24: /* MMC_BLEN */
+ return s->blen_counter;
+
+ case 0x28: /* MMC_NBLK */
+ return s->nblk_counter;
+
+ case 0x2c: /* MMC_BUF */
+ return (s->rx_dma << 15) | (s->af_level << 8) |
+ (s->tx_dma << 7) | s->ae_level;
+
+ case 0x30: /* MMC_SPI */
+ return 0x0000;
+ case 0x34: /* MMC_SDIO */
+ return (s->cdet_wakeup << 2) | (s->cdet_enable) | s->sdio;
+ case 0x38: /* MMC_SYST */
+ return 0x0000;
+
+ case 0x3c: /* MMC_REV */
+ return s->rev;
+
+ case 0x40: /* MMC_RSP0 */
+ case 0x44: /* MMC_RSP1 */
+ case 0x48: /* MMC_RSP2 */
+ case 0x4c: /* MMC_RSP3 */
+ case 0x50: /* MMC_RSP4 */
+ case 0x54: /* MMC_RSP5 */
+ case 0x58: /* MMC_RSP6 */
+ case 0x5c: /* MMC_RSP7 */
+ return s->rsp[(offset - 0x40) >> 2];
+
+ /* OMAP2-specific */
+ case 0x60: /* MMC_IOSR */
+ case 0x64: /* MMC_SYSC */
+ return 0;
+ case 0x68: /* MMC_SYSS */
+ return 1; /* RSTD */
+ }
+
+ OMAP_BAD_REG(offset);
+ return 0;
+}
+
+static void omap_mmc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ int i;
+ struct omap_mmc_s *s = (struct omap_mmc_s *) opaque;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, offset, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* MMC_CMD */
+ if (!s->enable)
+ break;
+
+ s->last_cmd = value;
+ for (i = 0; i < 8; i ++)
+ s->rsp[i] = 0x0000;
+ omap_mmc_command(s, value & 63, (value >> 15) & 1,
+ (sd_cmd_type_t) ((value >> 12) & 3),
+ (value >> 11) & 1,
+ (sd_rsp_type_t) ((value >> 8) & 7),
+ (value >> 7) & 1);
+ omap_mmc_update(s);
+ break;
+
+ case 0x04: /* MMC_ARGL */
+ s->arg &= 0xffff0000;
+ s->arg |= 0x0000ffff & value;
+ break;
+
+ case 0x08: /* MMC_ARGH */
+ s->arg &= 0x0000ffff;
+ s->arg |= value << 16;
+ break;
+
+ case 0x0c: /* MMC_CON */
+ s->dw = (value >> 15) & 1;
+ s->mode = (value >> 12) & 3;
+ s->enable = (value >> 11) & 1;
+ s->be = (value >> 10) & 1;
+ s->clkdiv = (value >> 0) & (s->rev >= 2 ? 0x3ff : 0xff);
+ if (s->mode != 0)
+ printf("SD mode %i unimplemented!\n", s->mode);
+ if (s->be != 0)
+ printf("SD FIFO byte sex unimplemented!\n");
+ if (s->dw != 0 && s->lines < 4)
+ printf("4-bit SD bus enabled\n");
+ if (!s->enable)
+ omap_mmc_reset(s);
+ break;
+
+ case 0x10: /* MMC_STAT */
+ s->status &= ~value;
+ omap_mmc_interrupts_update(s);
+ break;
+
+ case 0x14: /* MMC_IE */
+ s->mask = value & 0x7fff;
+ omap_mmc_interrupts_update(s);
+ break;
+
+ case 0x18: /* MMC_CTO */
+ s->cto = value & 0xff;
+ if (s->cto > 0xfd && s->rev <= 1)
+ printf("MMC: CTO of 0xff and 0xfe cannot be used!\n");
+ break;
+
+ case 0x1c: /* MMC_DTO */
+ s->dto = value & 0xffff;
+ break;
+
+ case 0x20: /* MMC_DATA */
+ /* TODO: support 8-bit access */
+ if (s->fifo_len == 32)
+ break;
+ s->fifo[(s->fifo_start + s->fifo_len) & 31] = value;
+ s->fifo_len ++;
+ omap_mmc_transfer(s);
+ omap_mmc_fifolevel_update(s);
+ omap_mmc_interrupts_update(s);
+ break;
+
+ case 0x24: /* MMC_BLEN */
+ s->blen = (value & 0x07ff) + 1;
+ s->blen_counter = s->blen;
+ break;
+
+ case 0x28: /* MMC_NBLK */
+ s->nblk = (value & 0x07ff) + 1;
+ s->nblk_counter = s->nblk;
+ s->blen_counter = s->blen;
+ break;
+
+ case 0x2c: /* MMC_BUF */
+ s->rx_dma = (value >> 15) & 1;
+ s->af_level = (value >> 8) & 0x1f;
+ s->tx_dma = (value >> 7) & 1;
+ s->ae_level = value & 0x1f;
+
+ if (s->rx_dma)
+ s->status &= 0xfbff;
+ if (s->tx_dma)
+ s->status &= 0xf7ff;
+ omap_mmc_fifolevel_update(s);
+ omap_mmc_interrupts_update(s);
+ break;
+
+ /* SPI, SDIO and TEST modes unimplemented */
+ case 0x30: /* MMC_SPI (OMAP1 only) */
+ break;
+ case 0x34: /* MMC_SDIO */
+ s->sdio = value & (s->rev >= 2 ? 0xfbf3 : 0x2020);
+ s->cdet_wakeup = (value >> 9) & 1;
+ s->cdet_enable = (value >> 2) & 1;
+ break;
+ case 0x38: /* MMC_SYST */
+ break;
+
+ case 0x3c: /* MMC_REV */
+ case 0x40: /* MMC_RSP0 */
+ case 0x44: /* MMC_RSP1 */
+ case 0x48: /* MMC_RSP2 */
+ case 0x4c: /* MMC_RSP3 */
+ case 0x50: /* MMC_RSP4 */
+ case 0x54: /* MMC_RSP5 */
+ case 0x58: /* MMC_RSP6 */
+ case 0x5c: /* MMC_RSP7 */
+ OMAP_RO_REG(offset);
+ break;
+
+ /* OMAP2-specific */
+ case 0x60: /* MMC_IOSR */
+ if (value & 0xf)
+ printf("MMC: SDIO bits used!\n");
+ break;
+ case 0x64: /* MMC_SYSC */
+ if (value & (1 << 2)) /* SRTS */
+ omap_mmc_reset(s);
+ break;
+ case 0x68: /* MMC_SYSS */
+ OMAP_RO_REG(offset);
+ break;
+
+ default:
+ OMAP_BAD_REG(offset);
+ }
+}
+
+static const MemoryRegionOps omap_mmc_ops = {
+ .read = omap_mmc_read,
+ .write = omap_mmc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_mmc_cover_cb(void *opaque, int line, int level)
+{
+ struct omap_mmc_s *host = (struct omap_mmc_s *) opaque;
+
+ if (!host->cdet_state && level) {
+ host->status |= 0x0002;
+ omap_mmc_interrupts_update(host);
+ if (host->cdet_wakeup) {
+ /* TODO: Assert wake-up */
+ }
+ }
+
+ if (host->cdet_state != level) {
+ qemu_set_irq(host->coverswitch, level);
+ host->cdet_state = level;
+ }
+}
+
+struct omap_mmc_s *omap_mmc_init(hwaddr base,
+ MemoryRegion *sysmem,
+ BlockBackend *blk,
+ qemu_irq irq, qemu_irq dma[], omap_clk clk)
+{
+ struct omap_mmc_s *s = (struct omap_mmc_s *)
+ g_malloc0(sizeof(struct omap_mmc_s));
+
+ s->irq = irq;
+ s->dma = dma;
+ s->clk = clk;
+ s->lines = 1; /* TODO: needs to be settable per-board */
+ s->rev = 1;
+
+ omap_mmc_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_mmc_ops, s, "omap.mmc", 0x800);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ /* Instantiate the storage */
+ s->card = sd_init(blk, false);
+ if (s->card == NULL) {
+ exit(1);
+ }
+
+ return s;
+}
+
+struct omap_mmc_s *omap2_mmc_init(struct omap_target_agent_s *ta,
+ BlockBackend *blk, qemu_irq irq, qemu_irq dma[],
+ omap_clk fclk, omap_clk iclk)
+{
+ struct omap_mmc_s *s = (struct omap_mmc_s *)
+ g_malloc0(sizeof(struct omap_mmc_s));
+
+ s->irq = irq;
+ s->dma = dma;
+ s->clk = fclk;
+ s->lines = 4;
+ s->rev = 2;
+
+ omap_mmc_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_mmc_ops, s, "omap.mmc",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ /* Instantiate the storage */
+ s->card = sd_init(blk, false);
+ if (s->card == NULL) {
+ exit(1);
+ }
+
+ s->cdet = qemu_allocate_irq(omap_mmc_cover_cb, s, 0);
+ sd_set_cb(s->card, NULL, s->cdet);
+
+ return s;
+}
+
+void omap_mmc_handlers(struct omap_mmc_s *s, qemu_irq ro, qemu_irq cover)
+{
+ if (s->cdet) {
+ sd_set_cb(s->card, ro, s->cdet);
+ s->coverswitch = cover;
+ qemu_set_irq(cover, s->cdet_state);
+ } else
+ sd_set_cb(s->card, ro, cover);
+}
+
+void omap_mmc_enable(struct omap_mmc_s *s, int enable)
+{
+ sd_enable(s->card, enable);
+}
diff --git a/hw/sd/pl181.c b/hw/sd/pl181.c
new file mode 100644
index 00000000..11fcd479
--- /dev/null
+++ b/hw/sd/pl181.c
@@ -0,0 +1,527 @@
+/*
+ * Arm PrimeCell PL181 MultiMedia Card Interface
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/sysbus.h"
+#include "hw/sd.h"
+
+//#define DEBUG_PL181 1
+
+#ifdef DEBUG_PL181
+#define DPRINTF(fmt, ...) \
+do { printf("pl181: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define PL181_FIFO_LEN 16
+
+#define TYPE_PL181 "pl181"
+#define PL181(obj) OBJECT_CHECK(PL181State, (obj), TYPE_PL181)
+
+typedef struct PL181State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ SDState *card;
+ uint32_t clock;
+ uint32_t power;
+ uint32_t cmdarg;
+ uint32_t cmd;
+ uint32_t datatimer;
+ uint32_t datalength;
+ uint32_t respcmd;
+ uint32_t response[4];
+ uint32_t datactrl;
+ uint32_t datacnt;
+ uint32_t status;
+ uint32_t mask[2];
+ int32_t fifo_pos;
+ int32_t fifo_len;
+ /* The linux 2.6.21 driver is buggy, and misbehaves if new data arrives
+ while it is reading the FIFO. We hack around this be defering
+ subsequent transfers until after the driver polls the status word.
+ http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=4446/1
+ */
+ int32_t linux_hack;
+ uint32_t fifo[PL181_FIFO_LEN];
+ qemu_irq irq[2];
+ /* GPIO outputs for 'card is readonly' and 'card inserted' */
+ qemu_irq cardstatus[2];
+} PL181State;
+
+static const VMStateDescription vmstate_pl181 = {
+ .name = "pl181",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(clock, PL181State),
+ VMSTATE_UINT32(power, PL181State),
+ VMSTATE_UINT32(cmdarg, PL181State),
+ VMSTATE_UINT32(cmd, PL181State),
+ VMSTATE_UINT32(datatimer, PL181State),
+ VMSTATE_UINT32(datalength, PL181State),
+ VMSTATE_UINT32(respcmd, PL181State),
+ VMSTATE_UINT32_ARRAY(response, PL181State, 4),
+ VMSTATE_UINT32(datactrl, PL181State),
+ VMSTATE_UINT32(datacnt, PL181State),
+ VMSTATE_UINT32(status, PL181State),
+ VMSTATE_UINT32_ARRAY(mask, PL181State, 2),
+ VMSTATE_INT32(fifo_pos, PL181State),
+ VMSTATE_INT32(fifo_len, PL181State),
+ VMSTATE_INT32(linux_hack, PL181State),
+ VMSTATE_UINT32_ARRAY(fifo, PL181State, PL181_FIFO_LEN),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define PL181_CMD_INDEX 0x3f
+#define PL181_CMD_RESPONSE (1 << 6)
+#define PL181_CMD_LONGRESP (1 << 7)
+#define PL181_CMD_INTERRUPT (1 << 8)
+#define PL181_CMD_PENDING (1 << 9)
+#define PL181_CMD_ENABLE (1 << 10)
+
+#define PL181_DATA_ENABLE (1 << 0)
+#define PL181_DATA_DIRECTION (1 << 1)
+#define PL181_DATA_MODE (1 << 2)
+#define PL181_DATA_DMAENABLE (1 << 3)
+
+#define PL181_STATUS_CMDCRCFAIL (1 << 0)
+#define PL181_STATUS_DATACRCFAIL (1 << 1)
+#define PL181_STATUS_CMDTIMEOUT (1 << 2)
+#define PL181_STATUS_DATATIMEOUT (1 << 3)
+#define PL181_STATUS_TXUNDERRUN (1 << 4)
+#define PL181_STATUS_RXOVERRUN (1 << 5)
+#define PL181_STATUS_CMDRESPEND (1 << 6)
+#define PL181_STATUS_CMDSENT (1 << 7)
+#define PL181_STATUS_DATAEND (1 << 8)
+#define PL181_STATUS_DATABLOCKEND (1 << 10)
+#define PL181_STATUS_CMDACTIVE (1 << 11)
+#define PL181_STATUS_TXACTIVE (1 << 12)
+#define PL181_STATUS_RXACTIVE (1 << 13)
+#define PL181_STATUS_TXFIFOHALFEMPTY (1 << 14)
+#define PL181_STATUS_RXFIFOHALFFULL (1 << 15)
+#define PL181_STATUS_TXFIFOFULL (1 << 16)
+#define PL181_STATUS_RXFIFOFULL (1 << 17)
+#define PL181_STATUS_TXFIFOEMPTY (1 << 18)
+#define PL181_STATUS_RXFIFOEMPTY (1 << 19)
+#define PL181_STATUS_TXDATAAVLBL (1 << 20)
+#define PL181_STATUS_RXDATAAVLBL (1 << 21)
+
+#define PL181_STATUS_TX_FIFO (PL181_STATUS_TXACTIVE \
+ |PL181_STATUS_TXFIFOHALFEMPTY \
+ |PL181_STATUS_TXFIFOFULL \
+ |PL181_STATUS_TXFIFOEMPTY \
+ |PL181_STATUS_TXDATAAVLBL)
+#define PL181_STATUS_RX_FIFO (PL181_STATUS_RXACTIVE \
+ |PL181_STATUS_RXFIFOHALFFULL \
+ |PL181_STATUS_RXFIFOFULL \
+ |PL181_STATUS_RXFIFOEMPTY \
+ |PL181_STATUS_RXDATAAVLBL)
+
+static const unsigned char pl181_id[] =
+{ 0x81, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static void pl181_update(PL181State *s)
+{
+ int i;
+ for (i = 0; i < 2; i++) {
+ qemu_set_irq(s->irq[i], (s->status & s->mask[i]) != 0);
+ }
+}
+
+static void pl181_fifo_push(PL181State *s, uint32_t value)
+{
+ int n;
+
+ if (s->fifo_len == PL181_FIFO_LEN) {
+ fprintf(stderr, "pl181: FIFO overflow\n");
+ return;
+ }
+ n = (s->fifo_pos + s->fifo_len) & (PL181_FIFO_LEN - 1);
+ s->fifo_len++;
+ s->fifo[n] = value;
+ DPRINTF("FIFO push %08x\n", (int)value);
+}
+
+static uint32_t pl181_fifo_pop(PL181State *s)
+{
+ uint32_t value;
+
+ if (s->fifo_len == 0) {
+ fprintf(stderr, "pl181: FIFO underflow\n");
+ return 0;
+ }
+ value = s->fifo[s->fifo_pos];
+ s->fifo_len--;
+ s->fifo_pos = (s->fifo_pos + 1) & (PL181_FIFO_LEN - 1);
+ DPRINTF("FIFO pop %08x\n", (int)value);
+ return value;
+}
+
+static void pl181_send_command(PL181State *s)
+{
+ SDRequest request;
+ uint8_t response[16];
+ int rlen;
+
+ request.cmd = s->cmd & PL181_CMD_INDEX;
+ request.arg = s->cmdarg;
+ DPRINTF("Command %d %08x\n", request.cmd, request.arg);
+ rlen = sd_do_command(s->card, &request, response);
+ if (rlen < 0)
+ goto error;
+ if (s->cmd & PL181_CMD_RESPONSE) {
+#define RWORD(n) (((uint32_t)response[n] << 24) | (response[n + 1] << 16) \
+ | (response[n + 2] << 8) | response[n + 3])
+ if (rlen == 0 || (rlen == 4 && (s->cmd & PL181_CMD_LONGRESP)))
+ goto error;
+ if (rlen != 4 && rlen != 16)
+ goto error;
+ s->response[0] = RWORD(0);
+ if (rlen == 4) {
+ s->response[1] = s->response[2] = s->response[3] = 0;
+ } else {
+ s->response[1] = RWORD(4);
+ s->response[2] = RWORD(8);
+ s->response[3] = RWORD(12) & ~1;
+ }
+ DPRINTF("Response received\n");
+ s->status |= PL181_STATUS_CMDRESPEND;
+#undef RWORD
+ } else {
+ DPRINTF("Command sent\n");
+ s->status |= PL181_STATUS_CMDSENT;
+ }
+ return;
+
+error:
+ DPRINTF("Timeout\n");
+ s->status |= PL181_STATUS_CMDTIMEOUT;
+}
+
+/* Transfer data between the card and the FIFO. This is complicated by
+ the FIFO holding 32-bit words and the card taking data in single byte
+ chunks. FIFO bytes are transferred in little-endian order. */
+
+static void pl181_fifo_run(PL181State *s)
+{
+ uint32_t bits;
+ uint32_t value = 0;
+ int n;
+ int is_read;
+
+ is_read = (s->datactrl & PL181_DATA_DIRECTION) != 0;
+ if (s->datacnt != 0 && (!is_read || sd_data_ready(s->card))
+ && !s->linux_hack) {
+ if (is_read) {
+ n = 0;
+ while (s->datacnt && s->fifo_len < PL181_FIFO_LEN) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ s->datacnt--;
+ n++;
+ if (n == 4) {
+ pl181_fifo_push(s, value);
+ n = 0;
+ value = 0;
+ }
+ }
+ if (n != 0) {
+ pl181_fifo_push(s, value);
+ }
+ } else { /* write */
+ n = 0;
+ while (s->datacnt > 0 && (s->fifo_len > 0 || n > 0)) {
+ if (n == 0) {
+ value = pl181_fifo_pop(s);
+ n = 4;
+ }
+ n--;
+ s->datacnt--;
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ }
+ }
+ }
+ s->status &= ~(PL181_STATUS_RX_FIFO | PL181_STATUS_TX_FIFO);
+ if (s->datacnt == 0) {
+ s->status |= PL181_STATUS_DATAEND;
+ /* HACK: */
+ s->status |= PL181_STATUS_DATABLOCKEND;
+ DPRINTF("Transfer Complete\n");
+ }
+ if (s->datacnt == 0 && s->fifo_len == 0) {
+ s->datactrl &= ~PL181_DATA_ENABLE;
+ DPRINTF("Data engine idle\n");
+ } else {
+ /* Update FIFO bits. */
+ bits = PL181_STATUS_TXACTIVE | PL181_STATUS_RXACTIVE;
+ if (s->fifo_len == 0) {
+ bits |= PL181_STATUS_TXFIFOEMPTY;
+ bits |= PL181_STATUS_RXFIFOEMPTY;
+ } else {
+ bits |= PL181_STATUS_TXDATAAVLBL;
+ bits |= PL181_STATUS_RXDATAAVLBL;
+ }
+ if (s->fifo_len == 16) {
+ bits |= PL181_STATUS_TXFIFOFULL;
+ bits |= PL181_STATUS_RXFIFOFULL;
+ }
+ if (s->fifo_len <= 8) {
+ bits |= PL181_STATUS_TXFIFOHALFEMPTY;
+ }
+ if (s->fifo_len >= 8) {
+ bits |= PL181_STATUS_RXFIFOHALFFULL;
+ }
+ if (s->datactrl & PL181_DATA_DIRECTION) {
+ bits &= PL181_STATUS_RX_FIFO;
+ } else {
+ bits &= PL181_STATUS_TX_FIFO;
+ }
+ s->status |= bits;
+ }
+}
+
+static uint64_t pl181_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL181State *s = (PL181State *)opaque;
+ uint32_t tmp;
+
+ if (offset >= 0xfe0 && offset < 0x1000) {
+ return pl181_id[(offset - 0xfe0) >> 2];
+ }
+ switch (offset) {
+ case 0x00: /* Power */
+ return s->power;
+ case 0x04: /* Clock */
+ return s->clock;
+ case 0x08: /* Argument */
+ return s->cmdarg;
+ case 0x0c: /* Command */
+ return s->cmd;
+ case 0x10: /* RespCmd */
+ return s->respcmd;
+ case 0x14: /* Response0 */
+ return s->response[0];
+ case 0x18: /* Response1 */
+ return s->response[1];
+ case 0x1c: /* Response2 */
+ return s->response[2];
+ case 0x20: /* Response3 */
+ return s->response[3];
+ case 0x24: /* DataTimer */
+ return s->datatimer;
+ case 0x28: /* DataLength */
+ return s->datalength;
+ case 0x2c: /* DataCtrl */
+ return s->datactrl;
+ case 0x30: /* DataCnt */
+ return s->datacnt;
+ case 0x34: /* Status */
+ tmp = s->status;
+ if (s->linux_hack) {
+ s->linux_hack = 0;
+ pl181_fifo_run(s);
+ pl181_update(s);
+ }
+ return tmp;
+ case 0x3c: /* Mask0 */
+ return s->mask[0];
+ case 0x40: /* Mask1 */
+ return s->mask[1];
+ case 0x48: /* FifoCnt */
+ /* The documentation is somewhat vague about exactly what FifoCnt
+ does. On real hardware it appears to be when decrememnted
+ when a word is transferred between the FIFO and the serial
+ data engine. DataCnt is decremented after each byte is
+ transferred between the serial engine and the card.
+ We don't emulate this level of detail, so both can be the same. */
+ tmp = (s->datacnt + 3) >> 2;
+ if (s->linux_hack) {
+ s->linux_hack = 0;
+ pl181_fifo_run(s);
+ pl181_update(s);
+ }
+ return tmp;
+ case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */
+ case 0x90: case 0x94: case 0x98: case 0x9c:
+ case 0xa0: case 0xa4: case 0xa8: case 0xac:
+ case 0xb0: case 0xb4: case 0xb8: case 0xbc:
+ if (s->fifo_len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "pl181: Unexpected FIFO read\n");
+ return 0;
+ } else {
+ uint32_t value;
+ value = pl181_fifo_pop(s);
+ s->linux_hack = 1;
+ pl181_fifo_run(s);
+ pl181_update(s);
+ return value;
+ }
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl181_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl181_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL181State *s = (PL181State *)opaque;
+
+ switch (offset) {
+ case 0x00: /* Power */
+ s->power = value & 0xff;
+ break;
+ case 0x04: /* Clock */
+ s->clock = value & 0xff;
+ break;
+ case 0x08: /* Argument */
+ s->cmdarg = value;
+ break;
+ case 0x0c: /* Command */
+ s->cmd = value;
+ if (s->cmd & PL181_CMD_ENABLE) {
+ if (s->cmd & PL181_CMD_INTERRUPT) {
+ qemu_log_mask(LOG_UNIMP,
+ "pl181: Interrupt mode not implemented\n");
+ } if (s->cmd & PL181_CMD_PENDING) {
+ qemu_log_mask(LOG_UNIMP,
+ "pl181: Pending commands not implemented\n");
+ } else {
+ pl181_send_command(s);
+ pl181_fifo_run(s);
+ }
+ /* The command has completed one way or the other. */
+ s->cmd &= ~PL181_CMD_ENABLE;
+ }
+ break;
+ case 0x24: /* DataTimer */
+ s->datatimer = value;
+ break;
+ case 0x28: /* DataLength */
+ s->datalength = value & 0xffff;
+ break;
+ case 0x2c: /* DataCtrl */
+ s->datactrl = value & 0xff;
+ if (value & PL181_DATA_ENABLE) {
+ s->datacnt = s->datalength;
+ pl181_fifo_run(s);
+ }
+ break;
+ case 0x38: /* Clear */
+ s->status &= ~(value & 0x7ff);
+ break;
+ case 0x3c: /* Mask0 */
+ s->mask[0] = value;
+ break;
+ case 0x40: /* Mask1 */
+ s->mask[1] = value;
+ break;
+ case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */
+ case 0x90: case 0x94: case 0x98: case 0x9c:
+ case 0xa0: case 0xa4: case 0xa8: case 0xac:
+ case 0xb0: case 0xb4: case 0xb8: case 0xbc:
+ if (s->datacnt == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "pl181: Unexpected FIFO write\n");
+ } else {
+ pl181_fifo_push(s, value);
+ pl181_fifo_run(s);
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl181_write: Bad offset %x\n", (int)offset);
+ }
+ pl181_update(s);
+}
+
+static const MemoryRegionOps pl181_ops = {
+ .read = pl181_read,
+ .write = pl181_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pl181_reset(DeviceState *d)
+{
+ PL181State *s = PL181(d);
+
+ s->power = 0;
+ s->cmdarg = 0;
+ s->cmd = 0;
+ s->datatimer = 0;
+ s->datalength = 0;
+ s->respcmd = 0;
+ s->response[0] = 0;
+ s->response[1] = 0;
+ s->response[2] = 0;
+ s->response[3] = 0;
+ s->datatimer = 0;
+ s->datalength = 0;
+ s->datactrl = 0;
+ s->datacnt = 0;
+ s->status = 0;
+ s->linux_hack = 0;
+ s->mask[0] = 0;
+ s->mask[1] = 0;
+
+ /* We can assume our GPIO outputs have been wired up now */
+ sd_set_cb(s->card, s->cardstatus[0], s->cardstatus[1]);
+}
+
+static int pl181_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PL181State *s = PL181(dev);
+ DriveInfo *dinfo;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl181_ops, s, "pl181", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq[0]);
+ sysbus_init_irq(sbd, &s->irq[1]);
+ qdev_init_gpio_out(dev, s->cardstatus, 2);
+ /* FIXME use a qdev drive property instead of drive_get_next() */
+ dinfo = drive_get_next(IF_SD);
+ s->card = sd_init(dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, false);
+ if (s->card == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void pl181_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *k = DEVICE_CLASS(klass);
+
+ sdc->init = pl181_init;
+ k->vmsd = &vmstate_pl181;
+ k->reset = pl181_reset;
+ /* Reason: init() method uses drive_get_next() */
+ k->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo pl181_info = {
+ .name = TYPE_PL181,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL181State),
+ .class_init = pl181_class_init,
+};
+
+static void pl181_register_types(void)
+{
+ type_register_static(&pl181_info);
+}
+
+type_init(pl181_register_types)
diff --git a/hw/sd/pxa2xx_mmci.c b/hw/sd/pxa2xx_mmci.c
new file mode 100644
index 00000000..d1fe6d58
--- /dev/null
+++ b/hw/sd/pxa2xx_mmci.c
@@ -0,0 +1,504 @@
+/*
+ * Intel XScale PXA255/270 MultiMediaCard/SD/SDIO Controller emulation.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "hw/sd.h"
+#include "hw/qdev.h"
+
+struct PXA2xxMMCIState {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq rx_dma;
+ qemu_irq tx_dma;
+
+ SDState *card;
+
+ uint32_t status;
+ uint32_t clkrt;
+ uint32_t spi;
+ uint32_t cmdat;
+ uint32_t resp_tout;
+ uint32_t read_tout;
+ int blklen;
+ int numblk;
+ uint32_t intmask;
+ uint32_t intreq;
+ int cmd;
+ uint32_t arg;
+
+ int active;
+ int bytesleft;
+ uint8_t tx_fifo[64];
+ int tx_start;
+ int tx_len;
+ uint8_t rx_fifo[32];
+ int rx_start;
+ int rx_len;
+ uint16_t resp_fifo[9];
+ int resp_len;
+
+ int cmdreq;
+};
+
+#define MMC_STRPCL 0x00 /* MMC Clock Start/Stop register */
+#define MMC_STAT 0x04 /* MMC Status register */
+#define MMC_CLKRT 0x08 /* MMC Clock Rate register */
+#define MMC_SPI 0x0c /* MMC SPI Mode register */
+#define MMC_CMDAT 0x10 /* MMC Command/Data register */
+#define MMC_RESTO 0x14 /* MMC Response Time-Out register */
+#define MMC_RDTO 0x18 /* MMC Read Time-Out register */
+#define MMC_BLKLEN 0x1c /* MMC Block Length register */
+#define MMC_NUMBLK 0x20 /* MMC Number of Blocks register */
+#define MMC_PRTBUF 0x24 /* MMC Buffer Partly Full register */
+#define MMC_I_MASK 0x28 /* MMC Interrupt Mask register */
+#define MMC_I_REG 0x2c /* MMC Interrupt Request register */
+#define MMC_CMD 0x30 /* MMC Command register */
+#define MMC_ARGH 0x34 /* MMC Argument High register */
+#define MMC_ARGL 0x38 /* MMC Argument Low register */
+#define MMC_RES 0x3c /* MMC Response FIFO */
+#define MMC_RXFIFO 0x40 /* MMC Receive FIFO */
+#define MMC_TXFIFO 0x44 /* MMC Transmit FIFO */
+#define MMC_RDWAIT 0x48 /* MMC RD_WAIT register */
+#define MMC_BLKS_REM 0x4c /* MMC Blocks Remaining register */
+
+/* Bitfield masks */
+#define STRPCL_STOP_CLK (1 << 0)
+#define STRPCL_STRT_CLK (1 << 1)
+#define STAT_TOUT_RES (1 << 1)
+#define STAT_CLK_EN (1 << 8)
+#define STAT_DATA_DONE (1 << 11)
+#define STAT_PRG_DONE (1 << 12)
+#define STAT_END_CMDRES (1 << 13)
+#define SPI_SPI_MODE (1 << 0)
+#define CMDAT_RES_TYPE (3 << 0)
+#define CMDAT_DATA_EN (1 << 2)
+#define CMDAT_WR_RD (1 << 3)
+#define CMDAT_DMA_EN (1 << 7)
+#define CMDAT_STOP_TRAN (1 << 10)
+#define INT_DATA_DONE (1 << 0)
+#define INT_PRG_DONE (1 << 1)
+#define INT_END_CMD (1 << 2)
+#define INT_STOP_CMD (1 << 3)
+#define INT_CLK_OFF (1 << 4)
+#define INT_RXFIFO_REQ (1 << 5)
+#define INT_TXFIFO_REQ (1 << 6)
+#define INT_TINT (1 << 7)
+#define INT_DAT_ERR (1 << 8)
+#define INT_RES_ERR (1 << 9)
+#define INT_RD_STALLED (1 << 10)
+#define INT_SDIO_INT (1 << 11)
+#define INT_SDIO_SACK (1 << 12)
+#define PRTBUF_PRT_BUF (1 << 0)
+
+/* Route internal interrupt lines to the global IC and DMA */
+static void pxa2xx_mmci_int_update(PXA2xxMMCIState *s)
+{
+ uint32_t mask = s->intmask;
+ if (s->cmdat & CMDAT_DMA_EN) {
+ mask |= INT_RXFIFO_REQ | INT_TXFIFO_REQ;
+
+ qemu_set_irq(s->rx_dma, !!(s->intreq & INT_RXFIFO_REQ));
+ qemu_set_irq(s->tx_dma, !!(s->intreq & INT_TXFIFO_REQ));
+ }
+
+ qemu_set_irq(s->irq, !!(s->intreq & ~mask));
+}
+
+static void pxa2xx_mmci_fifo_update(PXA2xxMMCIState *s)
+{
+ if (!s->active)
+ return;
+
+ if (s->cmdat & CMDAT_WR_RD) {
+ while (s->bytesleft && s->tx_len) {
+ sd_write_data(s->card, s->tx_fifo[s->tx_start ++]);
+ s->tx_start &= 0x1f;
+ s->tx_len --;
+ s->bytesleft --;
+ }
+ if (s->bytesleft)
+ s->intreq |= INT_TXFIFO_REQ;
+ } else
+ while (s->bytesleft && s->rx_len < 32) {
+ s->rx_fifo[(s->rx_start + (s->rx_len ++)) & 0x1f] =
+ sd_read_data(s->card);
+ s->bytesleft --;
+ s->intreq |= INT_RXFIFO_REQ;
+ }
+
+ if (!s->bytesleft) {
+ s->active = 0;
+ s->intreq |= INT_DATA_DONE;
+ s->status |= STAT_DATA_DONE;
+
+ if (s->cmdat & CMDAT_WR_RD) {
+ s->intreq |= INT_PRG_DONE;
+ s->status |= STAT_PRG_DONE;
+ }
+ }
+
+ pxa2xx_mmci_int_update(s);
+}
+
+static void pxa2xx_mmci_wakequeues(PXA2xxMMCIState *s)
+{
+ int rsplen, i;
+ SDRequest request;
+ uint8_t response[16];
+
+ s->active = 1;
+ s->rx_len = 0;
+ s->tx_len = 0;
+ s->cmdreq = 0;
+
+ request.cmd = s->cmd;
+ request.arg = s->arg;
+ request.crc = 0; /* FIXME */
+
+ rsplen = sd_do_command(s->card, &request, response);
+ s->intreq |= INT_END_CMD;
+
+ memset(s->resp_fifo, 0, sizeof(s->resp_fifo));
+ switch (s->cmdat & CMDAT_RES_TYPE) {
+#define PXAMMCI_RESP(wd, value0, value1) \
+ s->resp_fifo[(wd) + 0] |= (value0); \
+ s->resp_fifo[(wd) + 1] |= (value1) << 8;
+ case 0: /* No response */
+ goto complete;
+
+ case 1: /* R1, R4, R5 or R6 */
+ if (rsplen < 4)
+ goto timeout;
+ goto complete;
+
+ case 2: /* R2 */
+ if (rsplen < 16)
+ goto timeout;
+ goto complete;
+
+ case 3: /* R3 */
+ if (rsplen < 4)
+ goto timeout;
+ goto complete;
+
+ complete:
+ for (i = 0; rsplen > 0; i ++, rsplen -= 2) {
+ PXAMMCI_RESP(i, response[i * 2], response[i * 2 + 1]);
+ }
+ s->status |= STAT_END_CMDRES;
+
+ if (!(s->cmdat & CMDAT_DATA_EN))
+ s->active = 0;
+ else
+ s->bytesleft = s->numblk * s->blklen;
+
+ s->resp_len = 0;
+ break;
+
+ timeout:
+ s->active = 0;
+ s->status |= STAT_TOUT_RES;
+ break;
+ }
+
+ pxa2xx_mmci_fifo_update(s);
+}
+
+static uint64_t pxa2xx_mmci_read(void *opaque, hwaddr offset, unsigned size)
+{
+ PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque;
+ uint32_t ret;
+
+ switch (offset) {
+ case MMC_STRPCL:
+ return 0;
+ case MMC_STAT:
+ return s->status;
+ case MMC_CLKRT:
+ return s->clkrt;
+ case MMC_SPI:
+ return s->spi;
+ case MMC_CMDAT:
+ return s->cmdat;
+ case MMC_RESTO:
+ return s->resp_tout;
+ case MMC_RDTO:
+ return s->read_tout;
+ case MMC_BLKLEN:
+ return s->blklen;
+ case MMC_NUMBLK:
+ return s->numblk;
+ case MMC_PRTBUF:
+ return 0;
+ case MMC_I_MASK:
+ return s->intmask;
+ case MMC_I_REG:
+ return s->intreq;
+ case MMC_CMD:
+ return s->cmd | 0x40;
+ case MMC_ARGH:
+ return s->arg >> 16;
+ case MMC_ARGL:
+ return s->arg & 0xffff;
+ case MMC_RES:
+ if (s->resp_len < 9)
+ return s->resp_fifo[s->resp_len ++];
+ return 0;
+ case MMC_RXFIFO:
+ ret = 0;
+ while (size-- && s->rx_len) {
+ ret |= s->rx_fifo[s->rx_start++] << (size << 3);
+ s->rx_start &= 0x1f;
+ s->rx_len --;
+ }
+ s->intreq &= ~INT_RXFIFO_REQ;
+ pxa2xx_mmci_fifo_update(s);
+ return ret;
+ case MMC_RDWAIT:
+ return 0;
+ case MMC_BLKS_REM:
+ return s->numblk;
+ default:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_mmci_write(void *opaque,
+ hwaddr offset, uint64_t value, unsigned size)
+{
+ PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque;
+
+ switch (offset) {
+ case MMC_STRPCL:
+ if (value & STRPCL_STRT_CLK) {
+ s->status |= STAT_CLK_EN;
+ s->intreq &= ~INT_CLK_OFF;
+
+ if (s->cmdreq && !(s->cmdat & CMDAT_STOP_TRAN)) {
+ s->status &= STAT_CLK_EN;
+ pxa2xx_mmci_wakequeues(s);
+ }
+ }
+
+ if (value & STRPCL_STOP_CLK) {
+ s->status &= ~STAT_CLK_EN;
+ s->intreq |= INT_CLK_OFF;
+ s->active = 0;
+ }
+
+ pxa2xx_mmci_int_update(s);
+ break;
+
+ case MMC_CLKRT:
+ s->clkrt = value & 7;
+ break;
+
+ case MMC_SPI:
+ s->spi = value & 0xf;
+ if (value & SPI_SPI_MODE)
+ printf("%s: attempted to use card in SPI mode\n", __FUNCTION__);
+ break;
+
+ case MMC_CMDAT:
+ s->cmdat = value & 0x3dff;
+ s->active = 0;
+ s->cmdreq = 1;
+ if (!(value & CMDAT_STOP_TRAN)) {
+ s->status &= STAT_CLK_EN;
+
+ if (s->status & STAT_CLK_EN)
+ pxa2xx_mmci_wakequeues(s);
+ }
+
+ pxa2xx_mmci_int_update(s);
+ break;
+
+ case MMC_RESTO:
+ s->resp_tout = value & 0x7f;
+ break;
+
+ case MMC_RDTO:
+ s->read_tout = value & 0xffff;
+ break;
+
+ case MMC_BLKLEN:
+ s->blklen = value & 0xfff;
+ break;
+
+ case MMC_NUMBLK:
+ s->numblk = value & 0xffff;
+ break;
+
+ case MMC_PRTBUF:
+ if (value & PRTBUF_PRT_BUF) {
+ s->tx_start ^= 32;
+ s->tx_len = 0;
+ }
+ pxa2xx_mmci_fifo_update(s);
+ break;
+
+ case MMC_I_MASK:
+ s->intmask = value & 0x1fff;
+ pxa2xx_mmci_int_update(s);
+ break;
+
+ case MMC_CMD:
+ s->cmd = value & 0x3f;
+ break;
+
+ case MMC_ARGH:
+ s->arg &= 0x0000ffff;
+ s->arg |= value << 16;
+ break;
+
+ case MMC_ARGL:
+ s->arg &= 0xffff0000;
+ s->arg |= value & 0x0000ffff;
+ break;
+
+ case MMC_TXFIFO:
+ while (size-- && s->tx_len < 0x20)
+ s->tx_fifo[(s->tx_start + (s->tx_len ++)) & 0x1f] =
+ (value >> (size << 3)) & 0xff;
+ s->intreq &= ~INT_TXFIFO_REQ;
+ pxa2xx_mmci_fifo_update(s);
+ break;
+
+ case MMC_RDWAIT:
+ case MMC_BLKS_REM:
+ break;
+
+ default:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_mmci_ops = {
+ .read = pxa2xx_mmci_read,
+ .write = pxa2xx_mmci_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pxa2xx_mmci_save(QEMUFile *f, void *opaque)
+{
+ PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque;
+ int i;
+
+ qemu_put_be32s(f, &s->status);
+ qemu_put_be32s(f, &s->clkrt);
+ qemu_put_be32s(f, &s->spi);
+ qemu_put_be32s(f, &s->cmdat);
+ qemu_put_be32s(f, &s->resp_tout);
+ qemu_put_be32s(f, &s->read_tout);
+ qemu_put_be32(f, s->blklen);
+ qemu_put_be32(f, s->numblk);
+ qemu_put_be32s(f, &s->intmask);
+ qemu_put_be32s(f, &s->intreq);
+ qemu_put_be32(f, s->cmd);
+ qemu_put_be32s(f, &s->arg);
+ qemu_put_be32(f, s->cmdreq);
+ qemu_put_be32(f, s->active);
+ qemu_put_be32(f, s->bytesleft);
+
+ qemu_put_byte(f, s->tx_len);
+ for (i = 0; i < s->tx_len; i ++)
+ qemu_put_byte(f, s->tx_fifo[(s->tx_start + i) & 63]);
+
+ qemu_put_byte(f, s->rx_len);
+ for (i = 0; i < s->rx_len; i ++)
+ qemu_put_byte(f, s->rx_fifo[(s->rx_start + i) & 31]);
+
+ qemu_put_byte(f, s->resp_len);
+ for (i = s->resp_len; i < 9; i ++)
+ qemu_put_be16s(f, &s->resp_fifo[i]);
+}
+
+static int pxa2xx_mmci_load(QEMUFile *f, void *opaque, int version_id)
+{
+ PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque;
+ int i;
+
+ qemu_get_be32s(f, &s->status);
+ qemu_get_be32s(f, &s->clkrt);
+ qemu_get_be32s(f, &s->spi);
+ qemu_get_be32s(f, &s->cmdat);
+ qemu_get_be32s(f, &s->resp_tout);
+ qemu_get_be32s(f, &s->read_tout);
+ s->blklen = qemu_get_be32(f);
+ s->numblk = qemu_get_be32(f);
+ qemu_get_be32s(f, &s->intmask);
+ qemu_get_be32s(f, &s->intreq);
+ s->cmd = qemu_get_be32(f);
+ qemu_get_be32s(f, &s->arg);
+ s->cmdreq = qemu_get_be32(f);
+ s->active = qemu_get_be32(f);
+ s->bytesleft = qemu_get_be32(f);
+
+ s->tx_len = qemu_get_byte(f);
+ s->tx_start = 0;
+ if (s->tx_len >= sizeof(s->tx_fifo) || s->tx_len < 0)
+ return -EINVAL;
+ for (i = 0; i < s->tx_len; i ++)
+ s->tx_fifo[i] = qemu_get_byte(f);
+
+ s->rx_len = qemu_get_byte(f);
+ s->rx_start = 0;
+ if (s->rx_len >= sizeof(s->rx_fifo) || s->rx_len < 0)
+ return -EINVAL;
+ for (i = 0; i < s->rx_len; i ++)
+ s->rx_fifo[i] = qemu_get_byte(f);
+
+ s->resp_len = qemu_get_byte(f);
+ if (s->resp_len > 9 || s->resp_len < 0)
+ return -EINVAL;
+ for (i = s->resp_len; i < 9; i ++)
+ qemu_get_be16s(f, &s->resp_fifo[i]);
+
+ return 0;
+}
+
+PXA2xxMMCIState *pxa2xx_mmci_init(MemoryRegion *sysmem,
+ hwaddr base,
+ BlockBackend *blk, qemu_irq irq,
+ qemu_irq rx_dma, qemu_irq tx_dma)
+{
+ PXA2xxMMCIState *s;
+
+ s = (PXA2xxMMCIState *) g_malloc0(sizeof(PXA2xxMMCIState));
+ s->irq = irq;
+ s->rx_dma = rx_dma;
+ s->tx_dma = tx_dma;
+
+ memory_region_init_io(&s->iomem, NULL, &pxa2xx_mmci_ops, s,
+ "pxa2xx-mmci", 0x00100000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ /* Instantiate the actual storage */
+ s->card = sd_init(blk, false);
+ if (s->card == NULL) {
+ exit(1);
+ }
+
+ register_savevm(NULL, "pxa2xx_mmci", 0, 0,
+ pxa2xx_mmci_save, pxa2xx_mmci_load, s);
+
+ return s;
+}
+
+void pxa2xx_mmci_handlers(PXA2xxMMCIState *s, qemu_irq readonly,
+ qemu_irq coverswitch)
+{
+ sd_set_cb(s->card, readonly, coverswitch);
+}
diff --git a/hw/sd/sd.c b/hw/sd/sd.c
new file mode 100644
index 00000000..a1ff465a
--- /dev/null
+++ b/hw/sd/sd.c
@@ -0,0 +1,1765 @@
+/*
+ * SD Memory Card emulation as defined in the "SD Memory Card Physical
+ * layer specification, Version 1.10."
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (c) 2007 CodeSourcery
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/block-backend.h"
+#include "hw/sd.h"
+#include "qemu/bitmap.h"
+
+//#define DEBUG_SD 1
+
+#ifdef DEBUG_SD
+#define DPRINTF(fmt, ...) \
+do { fprintf(stderr, "SD: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define ACMD41_ENQUIRY_MASK 0x00ffffff
+
+typedef enum {
+ sd_r0 = 0, /* no response */
+ sd_r1, /* normal response command */
+ sd_r2_i, /* CID register */
+ sd_r2_s, /* CSD register */
+ sd_r3, /* OCR register */
+ sd_r6 = 6, /* Published RCA response */
+ sd_r7, /* Operating voltage */
+ sd_r1b = -1,
+ sd_illegal = -2,
+} sd_rsp_type_t;
+
+enum SDCardModes {
+ sd_inactive,
+ sd_card_identification_mode,
+ sd_data_transfer_mode,
+};
+
+enum SDCardStates {
+ sd_inactive_state = -1,
+ sd_idle_state = 0,
+ sd_ready_state,
+ sd_identification_state,
+ sd_standby_state,
+ sd_transfer_state,
+ sd_sendingdata_state,
+ sd_receivingdata_state,
+ sd_programming_state,
+ sd_disconnect_state,
+};
+
+struct SDState {
+ uint32_t mode; /* current card mode, one of SDCardModes */
+ int32_t state; /* current card state, one of SDCardStates */
+ uint32_t ocr;
+ uint8_t scr[8];
+ uint8_t cid[16];
+ uint8_t csd[16];
+ uint16_t rca;
+ uint32_t card_status;
+ uint8_t sd_status[64];
+ uint32_t vhs;
+ bool wp_switch;
+ unsigned long *wp_groups;
+ int32_t wpgrps_size;
+ uint64_t size;
+ uint32_t blk_len;
+ uint32_t erase_start;
+ uint32_t erase_end;
+ uint8_t pwd[16];
+ uint32_t pwd_len;
+ uint8_t function_group[6];
+
+ bool spi;
+ uint8_t current_cmd;
+ /* True if we will handle the next command as an ACMD. Note that this does
+ * *not* track the APP_CMD status bit!
+ */
+ bool expecting_acmd;
+ uint32_t blk_written;
+ uint64_t data_start;
+ uint32_t data_offset;
+ uint8_t data[512];
+ qemu_irq readonly_cb;
+ qemu_irq inserted_cb;
+ BlockBackend *blk;
+ uint8_t *buf;
+
+ bool enable;
+};
+
+static void sd_set_mode(SDState *sd)
+{
+ switch (sd->state) {
+ case sd_inactive_state:
+ sd->mode = sd_inactive;
+ break;
+
+ case sd_idle_state:
+ case sd_ready_state:
+ case sd_identification_state:
+ sd->mode = sd_card_identification_mode;
+ break;
+
+ case sd_standby_state:
+ case sd_transfer_state:
+ case sd_sendingdata_state:
+ case sd_receivingdata_state:
+ case sd_programming_state:
+ case sd_disconnect_state:
+ sd->mode = sd_data_transfer_mode;
+ break;
+ }
+}
+
+static const sd_cmd_type_t sd_cmd_type[64] = {
+ sd_bc, sd_none, sd_bcr, sd_bcr, sd_none, sd_none, sd_none, sd_ac,
+ sd_bcr, sd_ac, sd_ac, sd_adtc, sd_ac, sd_ac, sd_none, sd_ac,
+ sd_ac, sd_adtc, sd_adtc, sd_none, sd_none, sd_none, sd_none, sd_none,
+ sd_adtc, sd_adtc, sd_adtc, sd_adtc, sd_ac, sd_ac, sd_adtc, sd_none,
+ sd_ac, sd_ac, sd_none, sd_none, sd_none, sd_none, sd_ac, sd_none,
+ sd_none, sd_none, sd_bc, sd_none, sd_none, sd_none, sd_none, sd_none,
+ sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, sd_ac,
+ sd_adtc, sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, sd_none,
+};
+
+static const int sd_cmd_class[64] = {
+ 0, 0, 0, 0, 0, 9, 10, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 6, 6, 6, 6,
+ 5, 5, 10, 10, 10, 10, 5, 9, 9, 9, 7, 7, 7, 7, 7, 7,
+ 7, 7, 10, 7, 9, 9, 9, 8, 8, 10, 8, 8, 8, 8, 8, 8,
+};
+
+static uint8_t sd_crc7(void *message, size_t width)
+{
+ int i, bit;
+ uint8_t shift_reg = 0x00;
+ uint8_t *msg = (uint8_t *) message;
+
+ for (i = 0; i < width; i ++, msg ++)
+ for (bit = 7; bit >= 0; bit --) {
+ shift_reg <<= 1;
+ if ((shift_reg >> 7) ^ ((*msg >> bit) & 1))
+ shift_reg ^= 0x89;
+ }
+
+ return shift_reg;
+}
+
+static uint16_t sd_crc16(void *message, size_t width)
+{
+ int i, bit;
+ uint16_t shift_reg = 0x0000;
+ uint16_t *msg = (uint16_t *) message;
+ width <<= 1;
+
+ for (i = 0; i < width; i ++, msg ++)
+ for (bit = 15; bit >= 0; bit --) {
+ shift_reg <<= 1;
+ if ((shift_reg >> 15) ^ ((*msg >> bit) & 1))
+ shift_reg ^= 0x1011;
+ }
+
+ return shift_reg;
+}
+
+static void sd_set_ocr(SDState *sd)
+{
+ /* All voltages OK, card power-up OK, Standard Capacity SD Memory Card */
+ sd->ocr = 0x80ffff00;
+}
+
+static void sd_set_scr(SDState *sd)
+{
+ sd->scr[0] = 0x00; /* SCR Structure */
+ sd->scr[1] = 0x2f; /* SD Security Support */
+ sd->scr[2] = 0x00;
+ sd->scr[3] = 0x00;
+ sd->scr[4] = 0x00;
+ sd->scr[5] = 0x00;
+ sd->scr[6] = 0x00;
+ sd->scr[7] = 0x00;
+}
+
+#define MID 0xaa
+#define OID "XY"
+#define PNM "QEMU!"
+#define PRV 0x01
+#define MDT_YR 2006
+#define MDT_MON 2
+
+static void sd_set_cid(SDState *sd)
+{
+ sd->cid[0] = MID; /* Fake card manufacturer ID (MID) */
+ sd->cid[1] = OID[0]; /* OEM/Application ID (OID) */
+ sd->cid[2] = OID[1];
+ sd->cid[3] = PNM[0]; /* Fake product name (PNM) */
+ sd->cid[4] = PNM[1];
+ sd->cid[5] = PNM[2];
+ sd->cid[6] = PNM[3];
+ sd->cid[7] = PNM[4];
+ sd->cid[8] = PRV; /* Fake product revision (PRV) */
+ sd->cid[9] = 0xde; /* Fake serial number (PSN) */
+ sd->cid[10] = 0xad;
+ sd->cid[11] = 0xbe;
+ sd->cid[12] = 0xef;
+ sd->cid[13] = 0x00 | /* Manufacture date (MDT) */
+ ((MDT_YR - 2000) / 10);
+ sd->cid[14] = ((MDT_YR % 10) << 4) | MDT_MON;
+ sd->cid[15] = (sd_crc7(sd->cid, 15) << 1) | 1;
+}
+
+#define HWBLOCK_SHIFT 9 /* 512 bytes */
+#define SECTOR_SHIFT 5 /* 16 kilobytes */
+#define WPGROUP_SHIFT 7 /* 2 megs */
+#define CMULT_SHIFT 9 /* 512 times HWBLOCK_SIZE */
+#define WPGROUP_SIZE (1 << (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT))
+
+static const uint8_t sd_csd_rw_mask[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfe,
+};
+
+static void sd_set_csd(SDState *sd, uint64_t size)
+{
+ uint32_t csize = (size >> (CMULT_SHIFT + HWBLOCK_SHIFT)) - 1;
+ uint32_t sectsize = (1 << (SECTOR_SHIFT + 1)) - 1;
+ uint32_t wpsize = (1 << (WPGROUP_SHIFT + 1)) - 1;
+
+ if (size <= 0x40000000) { /* Standard Capacity SD */
+ sd->csd[0] = 0x00; /* CSD structure */
+ sd->csd[1] = 0x26; /* Data read access-time-1 */
+ sd->csd[2] = 0x00; /* Data read access-time-2 */
+ sd->csd[3] = 0x5a; /* Max. data transfer rate */
+ sd->csd[4] = 0x5f; /* Card Command Classes */
+ sd->csd[5] = 0x50 | /* Max. read data block length */
+ HWBLOCK_SHIFT;
+ sd->csd[6] = 0xe0 | /* Partial block for read allowed */
+ ((csize >> 10) & 0x03);
+ sd->csd[7] = 0x00 | /* Device size */
+ ((csize >> 2) & 0xff);
+ sd->csd[8] = 0x3f | /* Max. read current */
+ ((csize << 6) & 0xc0);
+ sd->csd[9] = 0xfc | /* Max. write current */
+ ((CMULT_SHIFT - 2) >> 1);
+ sd->csd[10] = 0x40 | /* Erase sector size */
+ (((CMULT_SHIFT - 2) << 7) & 0x80) | (sectsize >> 1);
+ sd->csd[11] = 0x00 | /* Write protect group size */
+ ((sectsize << 7) & 0x80) | wpsize;
+ sd->csd[12] = 0x90 | /* Write speed factor */
+ (HWBLOCK_SHIFT >> 2);
+ sd->csd[13] = 0x20 | /* Max. write data block length */
+ ((HWBLOCK_SHIFT << 6) & 0xc0);
+ sd->csd[14] = 0x00; /* File format group */
+ sd->csd[15] = (sd_crc7(sd->csd, 15) << 1) | 1;
+ } else { /* SDHC */
+ size /= 512 * 1024;
+ size -= 1;
+ sd->csd[0] = 0x40;
+ sd->csd[1] = 0x0e;
+ sd->csd[2] = 0x00;
+ sd->csd[3] = 0x32;
+ sd->csd[4] = 0x5b;
+ sd->csd[5] = 0x59;
+ sd->csd[6] = 0x00;
+ sd->csd[7] = (size >> 16) & 0xff;
+ sd->csd[8] = (size >> 8) & 0xff;
+ sd->csd[9] = (size & 0xff);
+ sd->csd[10] = 0x7f;
+ sd->csd[11] = 0x80;
+ sd->csd[12] = 0x0a;
+ sd->csd[13] = 0x40;
+ sd->csd[14] = 0x00;
+ sd->csd[15] = 0x00;
+ sd->ocr |= 1 << 30; /* High Capacity SD Memory Card */
+ }
+}
+
+static void sd_set_rca(SDState *sd)
+{
+ sd->rca += 0x4567;
+}
+
+/* Card status bits, split by clear condition:
+ * A : According to the card current state
+ * B : Always related to the previous command
+ * C : Cleared by read
+ */
+#define CARD_STATUS_A 0x02004100
+#define CARD_STATUS_B 0x00c01e00
+#define CARD_STATUS_C 0xfd39a028
+
+static void sd_set_cardstatus(SDState *sd)
+{
+ sd->card_status = 0x00000100;
+}
+
+static void sd_set_sdstatus(SDState *sd)
+{
+ memset(sd->sd_status, 0, 64);
+}
+
+static int sd_req_crc_validate(SDRequest *req)
+{
+ uint8_t buffer[5];
+ buffer[0] = 0x40 | req->cmd;
+ buffer[1] = (req->arg >> 24) & 0xff;
+ buffer[2] = (req->arg >> 16) & 0xff;
+ buffer[3] = (req->arg >> 8) & 0xff;
+ buffer[4] = (req->arg >> 0) & 0xff;
+ return 0;
+ return sd_crc7(buffer, 5) != req->crc; /* TODO */
+}
+
+static void sd_response_r1_make(SDState *sd, uint8_t *response)
+{
+ uint32_t status = sd->card_status;
+ /* Clear the "clear on read" status bits */
+ sd->card_status &= ~CARD_STATUS_C;
+
+ response[0] = (status >> 24) & 0xff;
+ response[1] = (status >> 16) & 0xff;
+ response[2] = (status >> 8) & 0xff;
+ response[3] = (status >> 0) & 0xff;
+}
+
+static void sd_response_r3_make(SDState *sd, uint8_t *response)
+{
+ response[0] = (sd->ocr >> 24) & 0xff;
+ response[1] = (sd->ocr >> 16) & 0xff;
+ response[2] = (sd->ocr >> 8) & 0xff;
+ response[3] = (sd->ocr >> 0) & 0xff;
+}
+
+static void sd_response_r6_make(SDState *sd, uint8_t *response)
+{
+ uint16_t arg;
+ uint16_t status;
+
+ arg = sd->rca;
+ status = ((sd->card_status >> 8) & 0xc000) |
+ ((sd->card_status >> 6) & 0x2000) |
+ (sd->card_status & 0x1fff);
+ sd->card_status &= ~(CARD_STATUS_C & 0xc81fff);
+
+ response[0] = (arg >> 8) & 0xff;
+ response[1] = arg & 0xff;
+ response[2] = (status >> 8) & 0xff;
+ response[3] = status & 0xff;
+}
+
+static void sd_response_r7_make(SDState *sd, uint8_t *response)
+{
+ response[0] = (sd->vhs >> 24) & 0xff;
+ response[1] = (sd->vhs >> 16) & 0xff;
+ response[2] = (sd->vhs >> 8) & 0xff;
+ response[3] = (sd->vhs >> 0) & 0xff;
+}
+
+static inline uint64_t sd_addr_to_wpnum(uint64_t addr)
+{
+ return addr >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT);
+}
+
+static void sd_reset(SDState *sd)
+{
+ uint64_t size;
+ uint64_t sect;
+
+ if (sd->blk) {
+ blk_get_geometry(sd->blk, &sect);
+ } else {
+ sect = 0;
+ }
+ size = sect << 9;
+
+ sect = sd_addr_to_wpnum(size) + 1;
+
+ sd->state = sd_idle_state;
+ sd->rca = 0x0000;
+ sd_set_ocr(sd);
+ sd_set_scr(sd);
+ sd_set_cid(sd);
+ sd_set_csd(sd, size);
+ sd_set_cardstatus(sd);
+ sd_set_sdstatus(sd);
+
+ if (sd->wp_groups)
+ g_free(sd->wp_groups);
+ sd->wp_switch = sd->blk ? blk_is_read_only(sd->blk) : false;
+ sd->wpgrps_size = sect;
+ sd->wp_groups = bitmap_new(sd->wpgrps_size);
+ memset(sd->function_group, 0, sizeof(sd->function_group));
+ sd->erase_start = 0;
+ sd->erase_end = 0;
+ sd->size = size;
+ sd->blk_len = 0x200;
+ sd->pwd_len = 0;
+ sd->expecting_acmd = false;
+}
+
+static void sd_cardchange(void *opaque, bool load)
+{
+ SDState *sd = opaque;
+
+ qemu_set_irq(sd->inserted_cb, blk_is_inserted(sd->blk));
+ if (blk_is_inserted(sd->blk)) {
+ sd_reset(sd);
+ qemu_set_irq(sd->readonly_cb, sd->wp_switch);
+ }
+}
+
+static const BlockDevOps sd_block_ops = {
+ .change_media_cb = sd_cardchange,
+};
+
+static const VMStateDescription sd_vmstate = {
+ .name = "sd-card",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(mode, SDState),
+ VMSTATE_INT32(state, SDState),
+ VMSTATE_UINT8_ARRAY(cid, SDState, 16),
+ VMSTATE_UINT8_ARRAY(csd, SDState, 16),
+ VMSTATE_UINT16(rca, SDState),
+ VMSTATE_UINT32(card_status, SDState),
+ VMSTATE_PARTIAL_BUFFER(sd_status, SDState, 1),
+ VMSTATE_UINT32(vhs, SDState),
+ VMSTATE_BITMAP(wp_groups, SDState, 0, wpgrps_size),
+ VMSTATE_UINT32(blk_len, SDState),
+ VMSTATE_UINT32(erase_start, SDState),
+ VMSTATE_UINT32(erase_end, SDState),
+ VMSTATE_UINT8_ARRAY(pwd, SDState, 16),
+ VMSTATE_UINT32(pwd_len, SDState),
+ VMSTATE_UINT8_ARRAY(function_group, SDState, 6),
+ VMSTATE_UINT8(current_cmd, SDState),
+ VMSTATE_BOOL(expecting_acmd, SDState),
+ VMSTATE_UINT32(blk_written, SDState),
+ VMSTATE_UINT64(data_start, SDState),
+ VMSTATE_UINT32(data_offset, SDState),
+ VMSTATE_UINT8_ARRAY(data, SDState, 512),
+ VMSTATE_BUFFER_POINTER_UNSAFE(buf, SDState, 1, 512),
+ VMSTATE_BOOL(enable, SDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* We do not model the chip select pin, so allow the board to select
+ whether card should be in SSI or MMC/SD mode. It is also up to the
+ board to ensure that ssi transfers only occur when the chip select
+ is asserted. */
+SDState *sd_init(BlockBackend *blk, bool is_spi)
+{
+ SDState *sd;
+
+ if (blk && blk_is_read_only(blk)) {
+ fprintf(stderr, "sd_init: Cannot use read-only drive\n");
+ return NULL;
+ }
+
+ sd = (SDState *) g_malloc0(sizeof(SDState));
+ sd->buf = blk_blockalign(blk, 512);
+ sd->spi = is_spi;
+ sd->enable = true;
+ sd->blk = blk;
+ sd_reset(sd);
+ if (sd->blk) {
+ blk_attach_dev_nofail(sd->blk, sd);
+ blk_set_dev_ops(sd->blk, &sd_block_ops, sd);
+ }
+ vmstate_register(NULL, -1, &sd_vmstate, sd);
+ return sd;
+}
+
+void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert)
+{
+ sd->readonly_cb = readonly;
+ sd->inserted_cb = insert;
+ qemu_set_irq(readonly, sd->blk ? blk_is_read_only(sd->blk) : 0);
+ qemu_set_irq(insert, sd->blk ? blk_is_inserted(sd->blk) : 0);
+}
+
+static void sd_erase(SDState *sd)
+{
+ int i;
+ uint64_t erase_start = sd->erase_start;
+ uint64_t erase_end = sd->erase_end;
+
+ if (!sd->erase_start || !sd->erase_end) {
+ sd->card_status |= ERASE_SEQ_ERROR;
+ return;
+ }
+
+ if (extract32(sd->ocr, OCR_CCS_BITN, 1)) {
+ /* High capacity memory card: erase units are 512 byte blocks */
+ erase_start *= 512;
+ erase_end *= 512;
+ }
+
+ erase_start = sd_addr_to_wpnum(erase_start);
+ erase_end = sd_addr_to_wpnum(erase_end);
+ sd->erase_start = 0;
+ sd->erase_end = 0;
+ sd->csd[14] |= 0x40;
+
+ for (i = erase_start; i <= erase_end; i++) {
+ if (test_bit(i, sd->wp_groups)) {
+ sd->card_status |= WP_ERASE_SKIP;
+ }
+ }
+}
+
+static uint32_t sd_wpbits(SDState *sd, uint64_t addr)
+{
+ uint32_t i, wpnum;
+ uint32_t ret = 0;
+
+ wpnum = sd_addr_to_wpnum(addr);
+
+ for (i = 0; i < 32; i++, wpnum++, addr += WPGROUP_SIZE) {
+ if (addr < sd->size && test_bit(wpnum, sd->wp_groups)) {
+ ret |= (1 << i);
+ }
+ }
+
+ return ret;
+}
+
+static void sd_function_switch(SDState *sd, uint32_t arg)
+{
+ int i, mode, new_func, crc;
+ mode = !!(arg & 0x80000000);
+
+ sd->data[0] = 0x00; /* Maximum current consumption */
+ sd->data[1] = 0x01;
+ sd->data[2] = 0x80; /* Supported group 6 functions */
+ sd->data[3] = 0x01;
+ sd->data[4] = 0x80; /* Supported group 5 functions */
+ sd->data[5] = 0x01;
+ sd->data[6] = 0x80; /* Supported group 4 functions */
+ sd->data[7] = 0x01;
+ sd->data[8] = 0x80; /* Supported group 3 functions */
+ sd->data[9] = 0x01;
+ sd->data[10] = 0x80; /* Supported group 2 functions */
+ sd->data[11] = 0x43;
+ sd->data[12] = 0x80; /* Supported group 1 functions */
+ sd->data[13] = 0x03;
+ for (i = 0; i < 6; i ++) {
+ new_func = (arg >> (i * 4)) & 0x0f;
+ if (mode && new_func != 0x0f)
+ sd->function_group[i] = new_func;
+ sd->data[14 + (i >> 1)] = new_func << ((i * 4) & 4);
+ }
+ memset(&sd->data[17], 0, 47);
+ crc = sd_crc16(sd->data, 64);
+ sd->data[65] = crc >> 8;
+ sd->data[66] = crc & 0xff;
+}
+
+static inline bool sd_wp_addr(SDState *sd, uint64_t addr)
+{
+ return test_bit(sd_addr_to_wpnum(addr), sd->wp_groups);
+}
+
+static void sd_lock_command(SDState *sd)
+{
+ int erase, lock, clr_pwd, set_pwd, pwd_len;
+ erase = !!(sd->data[0] & 0x08);
+ lock = sd->data[0] & 0x04;
+ clr_pwd = sd->data[0] & 0x02;
+ set_pwd = sd->data[0] & 0x01;
+
+ if (sd->blk_len > 1)
+ pwd_len = sd->data[1];
+ else
+ pwd_len = 0;
+
+ if (erase) {
+ if (!(sd->card_status & CARD_IS_LOCKED) || sd->blk_len > 1 ||
+ set_pwd || clr_pwd || lock || sd->wp_switch ||
+ (sd->csd[14] & 0x20)) {
+ sd->card_status |= LOCK_UNLOCK_FAILED;
+ return;
+ }
+ bitmap_zero(sd->wp_groups, sd->wpgrps_size);
+ sd->csd[14] &= ~0x10;
+ sd->card_status &= ~CARD_IS_LOCKED;
+ sd->pwd_len = 0;
+ /* Erasing the entire card here! */
+ fprintf(stderr, "SD: Card force-erased by CMD42\n");
+ return;
+ }
+
+ if (sd->blk_len < 2 + pwd_len ||
+ pwd_len <= sd->pwd_len ||
+ pwd_len > sd->pwd_len + 16) {
+ sd->card_status |= LOCK_UNLOCK_FAILED;
+ return;
+ }
+
+ if (sd->pwd_len && memcmp(sd->pwd, sd->data + 2, sd->pwd_len)) {
+ sd->card_status |= LOCK_UNLOCK_FAILED;
+ return;
+ }
+
+ pwd_len -= sd->pwd_len;
+ if ((pwd_len && !set_pwd) ||
+ (clr_pwd && (set_pwd || lock)) ||
+ (lock && !sd->pwd_len && !set_pwd) ||
+ (!set_pwd && !clr_pwd &&
+ (((sd->card_status & CARD_IS_LOCKED) && lock) ||
+ (!(sd->card_status & CARD_IS_LOCKED) && !lock)))) {
+ sd->card_status |= LOCK_UNLOCK_FAILED;
+ return;
+ }
+
+ if (set_pwd) {
+ memcpy(sd->pwd, sd->data + 2 + sd->pwd_len, pwd_len);
+ sd->pwd_len = pwd_len;
+ }
+
+ if (clr_pwd) {
+ sd->pwd_len = 0;
+ }
+
+ if (lock)
+ sd->card_status |= CARD_IS_LOCKED;
+ else
+ sd->card_status &= ~CARD_IS_LOCKED;
+}
+
+static sd_rsp_type_t sd_normal_command(SDState *sd,
+ SDRequest req)
+{
+ uint32_t rca = 0x0000;
+ uint64_t addr = (sd->ocr & (1 << 30)) ? (uint64_t) req.arg << 9 : req.arg;
+
+ /* Not interpreting this as an app command */
+ sd->card_status &= ~APP_CMD;
+
+ if (sd_cmd_type[req.cmd] == sd_ac || sd_cmd_type[req.cmd] == sd_adtc)
+ rca = req.arg >> 16;
+
+ DPRINTF("CMD%d 0x%08x state %d\n", req.cmd, req.arg, sd->state);
+ switch (req.cmd) {
+ /* Basic commands (Class 0 and Class 1) */
+ case 0: /* CMD0: GO_IDLE_STATE */
+ switch (sd->state) {
+ case sd_inactive_state:
+ return sd->spi ? sd_r1 : sd_r0;
+
+ default:
+ sd->state = sd_idle_state;
+ sd_reset(sd);
+ return sd->spi ? sd_r1 : sd_r0;
+ }
+ break;
+
+ case 1: /* CMD1: SEND_OP_CMD */
+ if (!sd->spi)
+ goto bad_cmd;
+
+ sd->state = sd_transfer_state;
+ return sd_r1;
+
+ case 2: /* CMD2: ALL_SEND_CID */
+ if (sd->spi)
+ goto bad_cmd;
+ switch (sd->state) {
+ case sd_ready_state:
+ sd->state = sd_identification_state;
+ return sd_r2_i;
+
+ default:
+ break;
+ }
+ break;
+
+ case 3: /* CMD3: SEND_RELATIVE_ADDR */
+ if (sd->spi)
+ goto bad_cmd;
+ switch (sd->state) {
+ case sd_identification_state:
+ case sd_standby_state:
+ sd->state = sd_standby_state;
+ sd_set_rca(sd);
+ return sd_r6;
+
+ default:
+ break;
+ }
+ break;
+
+ case 4: /* CMD4: SEND_DSR */
+ if (sd->spi)
+ goto bad_cmd;
+ switch (sd->state) {
+ case sd_standby_state:
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case 5: /* CMD5: reserved for SDIO cards */
+ return sd_illegal;
+
+ case 6: /* CMD6: SWITCH_FUNCTION */
+ if (sd->spi)
+ goto bad_cmd;
+ switch (sd->mode) {
+ case sd_data_transfer_mode:
+ sd_function_switch(sd, req.arg);
+ sd->state = sd_sendingdata_state;
+ sd->data_start = 0;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 7: /* CMD7: SELECT/DESELECT_CARD */
+ if (sd->spi)
+ goto bad_cmd;
+ switch (sd->state) {
+ case sd_standby_state:
+ if (sd->rca != rca)
+ return sd_r0;
+
+ sd->state = sd_transfer_state;
+ return sd_r1b;
+
+ case sd_transfer_state:
+ case sd_sendingdata_state:
+ if (sd->rca == rca)
+ break;
+
+ sd->state = sd_standby_state;
+ return sd_r1b;
+
+ case sd_disconnect_state:
+ if (sd->rca != rca)
+ return sd_r0;
+
+ sd->state = sd_programming_state;
+ return sd_r1b;
+
+ case sd_programming_state:
+ if (sd->rca == rca)
+ break;
+
+ sd->state = sd_disconnect_state;
+ return sd_r1b;
+
+ default:
+ break;
+ }
+ break;
+
+ case 8: /* CMD8: SEND_IF_COND */
+ /* Physical Layer Specification Version 2.00 command */
+ switch (sd->state) {
+ case sd_idle_state:
+ sd->vhs = 0;
+
+ /* No response if not exactly one VHS bit is set. */
+ if (!(req.arg >> 8) || (req.arg >> (ctz32(req.arg & ~0xff) + 1))) {
+ return sd->spi ? sd_r7 : sd_r0;
+ }
+
+ /* Accept. */
+ sd->vhs = req.arg;
+ return sd_r7;
+
+ default:
+ break;
+ }
+ break;
+
+ case 9: /* CMD9: SEND_CSD */
+ switch (sd->state) {
+ case sd_standby_state:
+ if (sd->rca != rca)
+ return sd_r0;
+
+ return sd_r2_s;
+
+ case sd_transfer_state:
+ if (!sd->spi)
+ break;
+ sd->state = sd_sendingdata_state;
+ memcpy(sd->data, sd->csd, 16);
+ sd->data_start = addr;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 10: /* CMD10: SEND_CID */
+ switch (sd->state) {
+ case sd_standby_state:
+ if (sd->rca != rca)
+ return sd_r0;
+
+ return sd_r2_i;
+
+ case sd_transfer_state:
+ if (!sd->spi)
+ break;
+ sd->state = sd_sendingdata_state;
+ memcpy(sd->data, sd->cid, 16);
+ sd->data_start = addr;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 11: /* CMD11: READ_DAT_UNTIL_STOP */
+ if (sd->spi)
+ goto bad_cmd;
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_sendingdata_state;
+ sd->data_start = req.arg;
+ sd->data_offset = 0;
+
+ if (sd->data_start + sd->blk_len > sd->size)
+ sd->card_status |= ADDRESS_ERROR;
+ return sd_r0;
+
+ default:
+ break;
+ }
+ break;
+
+ case 12: /* CMD12: STOP_TRANSMISSION */
+ switch (sd->state) {
+ case sd_sendingdata_state:
+ sd->state = sd_transfer_state;
+ return sd_r1b;
+
+ case sd_receivingdata_state:
+ sd->state = sd_programming_state;
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_transfer_state;
+ return sd_r1b;
+
+ default:
+ break;
+ }
+ break;
+
+ case 13: /* CMD13: SEND_STATUS */
+ switch (sd->mode) {
+ case sd_data_transfer_mode:
+ if (sd->rca != rca)
+ return sd_r0;
+
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 15: /* CMD15: GO_INACTIVE_STATE */
+ if (sd->spi)
+ goto bad_cmd;
+ switch (sd->mode) {
+ case sd_data_transfer_mode:
+ if (sd->rca != rca)
+ return sd_r0;
+
+ sd->state = sd_inactive_state;
+ return sd_r0;
+
+ default:
+ break;
+ }
+ break;
+
+ /* Block read commands (Classs 2) */
+ case 16: /* CMD16: SET_BLOCKLEN */
+ switch (sd->state) {
+ case sd_transfer_state:
+ if (req.arg > (1 << HWBLOCK_SHIFT))
+ sd->card_status |= BLOCK_LEN_ERROR;
+ else
+ sd->blk_len = req.arg;
+
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 17: /* CMD17: READ_SINGLE_BLOCK */
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_sendingdata_state;
+ sd->data_start = addr;
+ sd->data_offset = 0;
+
+ if (sd->data_start + sd->blk_len > sd->size)
+ sd->card_status |= ADDRESS_ERROR;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 18: /* CMD18: READ_MULTIPLE_BLOCK */
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_sendingdata_state;
+ sd->data_start = addr;
+ sd->data_offset = 0;
+
+ if (sd->data_start + sd->blk_len > sd->size)
+ sd->card_status |= ADDRESS_ERROR;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ /* Block write commands (Class 4) */
+ case 24: /* CMD24: WRITE_SINGLE_BLOCK */
+ if (sd->spi)
+ goto unimplemented_cmd;
+ switch (sd->state) {
+ case sd_transfer_state:
+ /* Writing in SPI mode not implemented. */
+ if (sd->spi)
+ break;
+ sd->state = sd_receivingdata_state;
+ sd->data_start = addr;
+ sd->data_offset = 0;
+ sd->blk_written = 0;
+
+ if (sd->data_start + sd->blk_len > sd->size)
+ sd->card_status |= ADDRESS_ERROR;
+ if (sd_wp_addr(sd, sd->data_start))
+ sd->card_status |= WP_VIOLATION;
+ if (sd->csd[14] & 0x30)
+ sd->card_status |= WP_VIOLATION;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 25: /* CMD25: WRITE_MULTIPLE_BLOCK */
+ if (sd->spi)
+ goto unimplemented_cmd;
+ switch (sd->state) {
+ case sd_transfer_state:
+ /* Writing in SPI mode not implemented. */
+ if (sd->spi)
+ break;
+ sd->state = sd_receivingdata_state;
+ sd->data_start = addr;
+ sd->data_offset = 0;
+ sd->blk_written = 0;
+
+ if (sd->data_start + sd->blk_len > sd->size)
+ sd->card_status |= ADDRESS_ERROR;
+ if (sd_wp_addr(sd, sd->data_start))
+ sd->card_status |= WP_VIOLATION;
+ if (sd->csd[14] & 0x30)
+ sd->card_status |= WP_VIOLATION;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 26: /* CMD26: PROGRAM_CID */
+ if (sd->spi)
+ goto bad_cmd;
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_receivingdata_state;
+ sd->data_start = 0;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 27: /* CMD27: PROGRAM_CSD */
+ if (sd->spi)
+ goto unimplemented_cmd;
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_receivingdata_state;
+ sd->data_start = 0;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ /* Write protection (Class 6) */
+ case 28: /* CMD28: SET_WRITE_PROT */
+ switch (sd->state) {
+ case sd_transfer_state:
+ if (addr >= sd->size) {
+ sd->card_status |= ADDRESS_ERROR;
+ return sd_r1b;
+ }
+
+ sd->state = sd_programming_state;
+ set_bit(sd_addr_to_wpnum(addr), sd->wp_groups);
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_transfer_state;
+ return sd_r1b;
+
+ default:
+ break;
+ }
+ break;
+
+ case 29: /* CMD29: CLR_WRITE_PROT */
+ switch (sd->state) {
+ case sd_transfer_state:
+ if (addr >= sd->size) {
+ sd->card_status |= ADDRESS_ERROR;
+ return sd_r1b;
+ }
+
+ sd->state = sd_programming_state;
+ clear_bit(sd_addr_to_wpnum(addr), sd->wp_groups);
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_transfer_state;
+ return sd_r1b;
+
+ default:
+ break;
+ }
+ break;
+
+ case 30: /* CMD30: SEND_WRITE_PROT */
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_sendingdata_state;
+ *(uint32_t *) sd->data = sd_wpbits(sd, req.arg);
+ sd->data_start = addr;
+ sd->data_offset = 0;
+ return sd_r1b;
+
+ default:
+ break;
+ }
+ break;
+
+ /* Erase commands (Class 5) */
+ case 32: /* CMD32: ERASE_WR_BLK_START */
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->erase_start = req.arg;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 33: /* CMD33: ERASE_WR_BLK_END */
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->erase_end = req.arg;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 38: /* CMD38: ERASE */
+ switch (sd->state) {
+ case sd_transfer_state:
+ if (sd->csd[14] & 0x30) {
+ sd->card_status |= WP_VIOLATION;
+ return sd_r1b;
+ }
+
+ sd->state = sd_programming_state;
+ sd_erase(sd);
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_transfer_state;
+ return sd_r1b;
+
+ default:
+ break;
+ }
+ break;
+
+ /* Lock card commands (Class 7) */
+ case 42: /* CMD42: LOCK_UNLOCK */
+ if (sd->spi)
+ goto unimplemented_cmd;
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_receivingdata_state;
+ sd->data_start = 0;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 52:
+ case 53:
+ /* CMD52, CMD53: reserved for SDIO cards
+ * (see the SDIO Simplified Specification V2.0)
+ * Handle as illegal command but do not complain
+ * on stderr, as some OSes may use these in their
+ * probing for presence of an SDIO card.
+ */
+ return sd_illegal;
+
+ /* Application specific commands (Class 8) */
+ case 55: /* CMD55: APP_CMD */
+ if (sd->rca != rca)
+ return sd_r0;
+
+ sd->expecting_acmd = true;
+ sd->card_status |= APP_CMD;
+ return sd_r1;
+
+ case 56: /* CMD56: GEN_CMD */
+ fprintf(stderr, "SD: GEN_CMD 0x%08x\n", req.arg);
+
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->data_offset = 0;
+ if (req.arg & 1)
+ sd->state = sd_sendingdata_state;
+ else
+ sd->state = sd_receivingdata_state;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ bad_cmd:
+ fprintf(stderr, "SD: Unknown CMD%i\n", req.cmd);
+ return sd_illegal;
+
+ unimplemented_cmd:
+ /* Commands that are recognised but not yet implemented in SPI mode. */
+ fprintf(stderr, "SD: CMD%i not implemented in SPI mode\n", req.cmd);
+ return sd_illegal;
+ }
+
+ fprintf(stderr, "SD: CMD%i in a wrong state\n", req.cmd);
+ return sd_illegal;
+}
+
+static sd_rsp_type_t sd_app_command(SDState *sd,
+ SDRequest req)
+{
+ DPRINTF("ACMD%d 0x%08x\n", req.cmd, req.arg);
+ sd->card_status |= APP_CMD;
+ switch (req.cmd) {
+ case 6: /* ACMD6: SET_BUS_WIDTH */
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->sd_status[0] &= 0x3f;
+ sd->sd_status[0] |= (req.arg & 0x03) << 6;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 13: /* ACMD13: SD_STATUS */
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_sendingdata_state;
+ sd->data_start = 0;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 22: /* ACMD22: SEND_NUM_WR_BLOCKS */
+ switch (sd->state) {
+ case sd_transfer_state:
+ *(uint32_t *) sd->data = sd->blk_written;
+
+ sd->state = sd_sendingdata_state;
+ sd->data_start = 0;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 23: /* ACMD23: SET_WR_BLK_ERASE_COUNT */
+ switch (sd->state) {
+ case sd_transfer_state:
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 41: /* ACMD41: SD_APP_OP_COND */
+ if (sd->spi) {
+ /* SEND_OP_CMD */
+ sd->state = sd_transfer_state;
+ return sd_r1;
+ }
+ switch (sd->state) {
+ case sd_idle_state:
+ /* We accept any voltage. 10000 V is nothing.
+ *
+ * We don't model init delay so just advance straight to ready state
+ * unless it's an enquiry ACMD41 (bits 23:0 == 0).
+ */
+ if (req.arg & ACMD41_ENQUIRY_MASK) {
+ sd->state = sd_ready_state;
+ }
+
+ return sd_r3;
+
+ default:
+ break;
+ }
+ break;
+
+ case 42: /* ACMD42: SET_CLR_CARD_DETECT */
+ switch (sd->state) {
+ case sd_transfer_state:
+ /* Bringing in the 50KOhm pull-up resistor... Done. */
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ case 51: /* ACMD51: SEND_SCR */
+ switch (sd->state) {
+ case sd_transfer_state:
+ sd->state = sd_sendingdata_state;
+ sd->data_start = 0;
+ sd->data_offset = 0;
+ return sd_r1;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ /* Fall back to standard commands. */
+ return sd_normal_command(sd, req);
+ }
+
+ fprintf(stderr, "SD: ACMD%i in a wrong state\n", req.cmd);
+ return sd_illegal;
+}
+
+static int cmd_valid_while_locked(SDState *sd, SDRequest *req)
+{
+ /* Valid commands in locked state:
+ * basic class (0)
+ * lock card class (7)
+ * CMD16
+ * implicitly, the ACMD prefix CMD55
+ * ACMD41 and ACMD42
+ * Anything else provokes an "illegal command" response.
+ */
+ if (sd->expecting_acmd) {
+ return req->cmd == 41 || req->cmd == 42;
+ }
+ if (req->cmd == 16 || req->cmd == 55) {
+ return 1;
+ }
+ return sd_cmd_class[req->cmd] == 0 || sd_cmd_class[req->cmd] == 7;
+}
+
+int sd_do_command(SDState *sd, SDRequest *req,
+ uint8_t *response) {
+ int last_state;
+ sd_rsp_type_t rtype;
+ int rsplen;
+
+ if (!sd->blk || !blk_is_inserted(sd->blk) || !sd->enable) {
+ return 0;
+ }
+
+ if (sd_req_crc_validate(req)) {
+ sd->card_status |= COM_CRC_ERROR;
+ rtype = sd_illegal;
+ goto send_response;
+ }
+
+ if (sd->card_status & CARD_IS_LOCKED) {
+ if (!cmd_valid_while_locked(sd, req)) {
+ sd->card_status |= ILLEGAL_COMMAND;
+ sd->expecting_acmd = false;
+ fprintf(stderr, "SD: Card is locked\n");
+ rtype = sd_illegal;
+ goto send_response;
+ }
+ }
+
+ last_state = sd->state;
+ sd_set_mode(sd);
+
+ if (sd->expecting_acmd) {
+ sd->expecting_acmd = false;
+ rtype = sd_app_command(sd, *req);
+ } else {
+ rtype = sd_normal_command(sd, *req);
+ }
+
+ if (rtype == sd_illegal) {
+ sd->card_status |= ILLEGAL_COMMAND;
+ } else {
+ /* Valid command, we can update the 'state before command' bits.
+ * (Do this now so they appear in r1 responses.)
+ */
+ sd->current_cmd = req->cmd;
+ sd->card_status &= ~CURRENT_STATE;
+ sd->card_status |= (last_state << 9);
+ }
+
+send_response:
+ switch (rtype) {
+ case sd_r1:
+ case sd_r1b:
+ sd_response_r1_make(sd, response);
+ rsplen = 4;
+ break;
+
+ case sd_r2_i:
+ memcpy(response, sd->cid, sizeof(sd->cid));
+ rsplen = 16;
+ break;
+
+ case sd_r2_s:
+ memcpy(response, sd->csd, sizeof(sd->csd));
+ rsplen = 16;
+ break;
+
+ case sd_r3:
+ sd_response_r3_make(sd, response);
+ rsplen = 4;
+ break;
+
+ case sd_r6:
+ sd_response_r6_make(sd, response);
+ rsplen = 4;
+ break;
+
+ case sd_r7:
+ sd_response_r7_make(sd, response);
+ rsplen = 4;
+ break;
+
+ case sd_r0:
+ case sd_illegal:
+ default:
+ rsplen = 0;
+ break;
+ }
+
+ if (rtype != sd_illegal) {
+ /* Clear the "clear on valid command" status bits now we've
+ * sent any response
+ */
+ sd->card_status &= ~CARD_STATUS_B;
+ }
+
+#ifdef DEBUG_SD
+ if (rsplen) {
+ int i;
+ DPRINTF("Response:");
+ for (i = 0; i < rsplen; i++)
+ fprintf(stderr, " %02x", response[i]);
+ fprintf(stderr, " state %d\n", sd->state);
+ } else {
+ DPRINTF("No response %d\n", sd->state);
+ }
+#endif
+
+ return rsplen;
+}
+
+static void sd_blk_read(SDState *sd, uint64_t addr, uint32_t len)
+{
+ uint64_t end = addr + len;
+
+ DPRINTF("sd_blk_read: addr = 0x%08llx, len = %d\n",
+ (unsigned long long) addr, len);
+ if (!sd->blk || blk_read(sd->blk, addr >> 9, sd->buf, 1) < 0) {
+ fprintf(stderr, "sd_blk_read: read error on host side\n");
+ return;
+ }
+
+ if (end > (addr & ~511) + 512) {
+ memcpy(sd->data, sd->buf + (addr & 511), 512 - (addr & 511));
+
+ if (blk_read(sd->blk, end >> 9, sd->buf, 1) < 0) {
+ fprintf(stderr, "sd_blk_read: read error on host side\n");
+ return;
+ }
+ memcpy(sd->data + 512 - (addr & 511), sd->buf, end & 511);
+ } else
+ memcpy(sd->data, sd->buf + (addr & 511), len);
+}
+
+static void sd_blk_write(SDState *sd, uint64_t addr, uint32_t len)
+{
+ uint64_t end = addr + len;
+
+ if ((addr & 511) || len < 512)
+ if (!sd->blk || blk_read(sd->blk, addr >> 9, sd->buf, 1) < 0) {
+ fprintf(stderr, "sd_blk_write: read error on host side\n");
+ return;
+ }
+
+ if (end > (addr & ~511) + 512) {
+ memcpy(sd->buf + (addr & 511), sd->data, 512 - (addr & 511));
+ if (blk_write(sd->blk, addr >> 9, sd->buf, 1) < 0) {
+ fprintf(stderr, "sd_blk_write: write error on host side\n");
+ return;
+ }
+
+ if (blk_read(sd->blk, end >> 9, sd->buf, 1) < 0) {
+ fprintf(stderr, "sd_blk_write: read error on host side\n");
+ return;
+ }
+ memcpy(sd->buf, sd->data + 512 - (addr & 511), end & 511);
+ if (blk_write(sd->blk, end >> 9, sd->buf, 1) < 0) {
+ fprintf(stderr, "sd_blk_write: write error on host side\n");
+ }
+ } else {
+ memcpy(sd->buf + (addr & 511), sd->data, len);
+ if (!sd->blk || blk_write(sd->blk, addr >> 9, sd->buf, 1) < 0) {
+ fprintf(stderr, "sd_blk_write: write error on host side\n");
+ }
+ }
+}
+
+#define BLK_READ_BLOCK(a, len) sd_blk_read(sd, a, len)
+#define BLK_WRITE_BLOCK(a, len) sd_blk_write(sd, a, len)
+#define APP_READ_BLOCK(a, len) memset(sd->data, 0xec, len)
+#define APP_WRITE_BLOCK(a, len)
+
+void sd_write_data(SDState *sd, uint8_t value)
+{
+ int i;
+
+ if (!sd->blk || !blk_is_inserted(sd->blk) || !sd->enable)
+ return;
+
+ if (sd->state != sd_receivingdata_state) {
+ fprintf(stderr, "sd_write_data: not in Receiving-Data state\n");
+ return;
+ }
+
+ if (sd->card_status & (ADDRESS_ERROR | WP_VIOLATION))
+ return;
+
+ switch (sd->current_cmd) {
+ case 24: /* CMD24: WRITE_SINGLE_BLOCK */
+ sd->data[sd->data_offset ++] = value;
+ if (sd->data_offset >= sd->blk_len) {
+ /* TODO: Check CRC before committing */
+ sd->state = sd_programming_state;
+ BLK_WRITE_BLOCK(sd->data_start, sd->data_offset);
+ sd->blk_written ++;
+ sd->csd[14] |= 0x40;
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_transfer_state;
+ }
+ break;
+
+ case 25: /* CMD25: WRITE_MULTIPLE_BLOCK */
+ if (sd->data_offset == 0) {
+ /* Start of the block - let's check the address is valid */
+ if (sd->data_start + sd->blk_len > sd->size) {
+ sd->card_status |= ADDRESS_ERROR;
+ break;
+ }
+ if (sd_wp_addr(sd, sd->data_start)) {
+ sd->card_status |= WP_VIOLATION;
+ break;
+ }
+ }
+ sd->data[sd->data_offset++] = value;
+ if (sd->data_offset >= sd->blk_len) {
+ /* TODO: Check CRC before committing */
+ sd->state = sd_programming_state;
+ BLK_WRITE_BLOCK(sd->data_start, sd->data_offset);
+ sd->blk_written++;
+ sd->data_start += sd->blk_len;
+ sd->data_offset = 0;
+ sd->csd[14] |= 0x40;
+
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_receivingdata_state;
+ }
+ break;
+
+ case 26: /* CMD26: PROGRAM_CID */
+ sd->data[sd->data_offset ++] = value;
+ if (sd->data_offset >= sizeof(sd->cid)) {
+ /* TODO: Check CRC before committing */
+ sd->state = sd_programming_state;
+ for (i = 0; i < sizeof(sd->cid); i ++)
+ if ((sd->cid[i] | 0x00) != sd->data[i])
+ sd->card_status |= CID_CSD_OVERWRITE;
+
+ if (!(sd->card_status & CID_CSD_OVERWRITE))
+ for (i = 0; i < sizeof(sd->cid); i ++) {
+ sd->cid[i] |= 0x00;
+ sd->cid[i] &= sd->data[i];
+ }
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_transfer_state;
+ }
+ break;
+
+ case 27: /* CMD27: PROGRAM_CSD */
+ sd->data[sd->data_offset ++] = value;
+ if (sd->data_offset >= sizeof(sd->csd)) {
+ /* TODO: Check CRC before committing */
+ sd->state = sd_programming_state;
+ for (i = 0; i < sizeof(sd->csd); i ++)
+ if ((sd->csd[i] | sd_csd_rw_mask[i]) !=
+ (sd->data[i] | sd_csd_rw_mask[i]))
+ sd->card_status |= CID_CSD_OVERWRITE;
+
+ /* Copy flag (OTP) & Permanent write protect */
+ if (sd->csd[14] & ~sd->data[14] & 0x60)
+ sd->card_status |= CID_CSD_OVERWRITE;
+
+ if (!(sd->card_status & CID_CSD_OVERWRITE))
+ for (i = 0; i < sizeof(sd->csd); i ++) {
+ sd->csd[i] |= sd_csd_rw_mask[i];
+ sd->csd[i] &= sd->data[i];
+ }
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_transfer_state;
+ }
+ break;
+
+ case 42: /* CMD42: LOCK_UNLOCK */
+ sd->data[sd->data_offset ++] = value;
+ if (sd->data_offset >= sd->blk_len) {
+ /* TODO: Check CRC before committing */
+ sd->state = sd_programming_state;
+ sd_lock_command(sd);
+ /* Bzzzzzzztt .... Operation complete. */
+ sd->state = sd_transfer_state;
+ }
+ break;
+
+ case 56: /* CMD56: GEN_CMD */
+ sd->data[sd->data_offset ++] = value;
+ if (sd->data_offset >= sd->blk_len) {
+ APP_WRITE_BLOCK(sd->data_start, sd->data_offset);
+ sd->state = sd_transfer_state;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "sd_write_data: unknown command\n");
+ break;
+ }
+}
+
+uint8_t sd_read_data(SDState *sd)
+{
+ /* TODO: Append CRCs */
+ uint8_t ret;
+ int io_len;
+
+ if (!sd->blk || !blk_is_inserted(sd->blk) || !sd->enable)
+ return 0x00;
+
+ if (sd->state != sd_sendingdata_state) {
+ fprintf(stderr, "sd_read_data: not in Sending-Data state\n");
+ return 0x00;
+ }
+
+ if (sd->card_status & (ADDRESS_ERROR | WP_VIOLATION))
+ return 0x00;
+
+ io_len = (sd->ocr & (1 << 30)) ? 512 : sd->blk_len;
+
+ switch (sd->current_cmd) {
+ case 6: /* CMD6: SWITCH_FUNCTION */
+ ret = sd->data[sd->data_offset ++];
+
+ if (sd->data_offset >= 64)
+ sd->state = sd_transfer_state;
+ break;
+
+ case 9: /* CMD9: SEND_CSD */
+ case 10: /* CMD10: SEND_CID */
+ ret = sd->data[sd->data_offset ++];
+
+ if (sd->data_offset >= 16)
+ sd->state = sd_transfer_state;
+ break;
+
+ case 11: /* CMD11: READ_DAT_UNTIL_STOP */
+ if (sd->data_offset == 0)
+ BLK_READ_BLOCK(sd->data_start, io_len);
+ ret = sd->data[sd->data_offset ++];
+
+ if (sd->data_offset >= io_len) {
+ sd->data_start += io_len;
+ sd->data_offset = 0;
+ if (sd->data_start + io_len > sd->size) {
+ sd->card_status |= ADDRESS_ERROR;
+ break;
+ }
+ }
+ break;
+
+ case 13: /* ACMD13: SD_STATUS */
+ ret = sd->sd_status[sd->data_offset ++];
+
+ if (sd->data_offset >= sizeof(sd->sd_status))
+ sd->state = sd_transfer_state;
+ break;
+
+ case 17: /* CMD17: READ_SINGLE_BLOCK */
+ if (sd->data_offset == 0)
+ BLK_READ_BLOCK(sd->data_start, io_len);
+ ret = sd->data[sd->data_offset ++];
+
+ if (sd->data_offset >= io_len)
+ sd->state = sd_transfer_state;
+ break;
+
+ case 18: /* CMD18: READ_MULTIPLE_BLOCK */
+ if (sd->data_offset == 0)
+ BLK_READ_BLOCK(sd->data_start, io_len);
+ ret = sd->data[sd->data_offset ++];
+
+ if (sd->data_offset >= io_len) {
+ sd->data_start += io_len;
+ sd->data_offset = 0;
+ if (sd->data_start + io_len > sd->size) {
+ sd->card_status |= ADDRESS_ERROR;
+ break;
+ }
+ }
+ break;
+
+ case 22: /* ACMD22: SEND_NUM_WR_BLOCKS */
+ ret = sd->data[sd->data_offset ++];
+
+ if (sd->data_offset >= 4)
+ sd->state = sd_transfer_state;
+ break;
+
+ case 30: /* CMD30: SEND_WRITE_PROT */
+ ret = sd->data[sd->data_offset ++];
+
+ if (sd->data_offset >= 4)
+ sd->state = sd_transfer_state;
+ break;
+
+ case 51: /* ACMD51: SEND_SCR */
+ ret = sd->scr[sd->data_offset ++];
+
+ if (sd->data_offset >= sizeof(sd->scr))
+ sd->state = sd_transfer_state;
+ break;
+
+ case 56: /* CMD56: GEN_CMD */
+ if (sd->data_offset == 0)
+ APP_READ_BLOCK(sd->data_start, sd->blk_len);
+ ret = sd->data[sd->data_offset ++];
+
+ if (sd->data_offset >= sd->blk_len)
+ sd->state = sd_transfer_state;
+ break;
+
+ default:
+ fprintf(stderr, "sd_read_data: unknown command\n");
+ return 0x00;
+ }
+
+ return ret;
+}
+
+bool sd_data_ready(SDState *sd)
+{
+ return sd->state == sd_sendingdata_state;
+}
+
+void sd_enable(SDState *sd, bool enable)
+{
+ sd->enable = enable;
+}
diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
new file mode 100644
index 00000000..e63367ba
--- /dev/null
+++ b/hw/sd/sdhci.c
@@ -0,0 +1,1319 @@
+/*
+ * SD Association Host Standard Specification v2.0 controller emulation
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Mitsyanko Igor <i.mitsyanko@samsung.com>
+ * Peter A.G. Crosthwaite <peter.crosthwaite@petalogix.com>
+ *
+ * Based on MMC controller for Samsung S5PC1xx-based board emulation
+ * by Alexey Merkulov and Vladimir Monakhov.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/dma.h"
+#include "qemu/timer.h"
+#include "qemu/bitops.h"
+
+#include "sdhci.h"
+
+/* host controller debug messages */
+#ifndef SDHC_DEBUG
+#define SDHC_DEBUG 0
+#endif
+
+#if SDHC_DEBUG == 0
+ #define DPRINT_L1(fmt, args...) do { } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+ #define ERRPRINT(fmt, args...) do { } while (0)
+#elif SDHC_DEBUG == 1
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+ #define ERRPRINT(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#else
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+ #define ERRPRINT(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#endif
+
+/* Default SD/MMC host controller features information, which will be
+ * presented in CAPABILITIES register of generic SD host controller at reset.
+ * If not stated otherwise:
+ * 0 - not supported, 1 - supported, other - prohibited.
+ */
+#define SDHC_CAPAB_64BITBUS 0ul /* 64-bit System Bus Support */
+#define SDHC_CAPAB_18V 1ul /* Voltage support 1.8v */
+#define SDHC_CAPAB_30V 0ul /* Voltage support 3.0v */
+#define SDHC_CAPAB_33V 1ul /* Voltage support 3.3v */
+#define SDHC_CAPAB_SUSPRESUME 0ul /* Suspend/resume support */
+#define SDHC_CAPAB_SDMA 1ul /* SDMA support */
+#define SDHC_CAPAB_HIGHSPEED 1ul /* High speed support */
+#define SDHC_CAPAB_ADMA1 1ul /* ADMA1 support */
+#define SDHC_CAPAB_ADMA2 1ul /* ADMA2 support */
+/* Maximum host controller R/W buffers size
+ * Possible values: 512, 1024, 2048 bytes */
+#define SDHC_CAPAB_MAXBLOCKLENGTH 512ul
+/* Maximum clock frequency for SDclock in MHz
+ * value in range 10-63 MHz, 0 - not defined */
+#define SDHC_CAPAB_BASECLKFREQ 52ul
+#define SDHC_CAPAB_TOUNIT 1ul /* Timeout clock unit 0 - kHz, 1 - MHz */
+/* Timeout clock frequency 1-63, 0 - not defined */
+#define SDHC_CAPAB_TOCLKFREQ 52ul
+
+/* Now check all parameters and calculate CAPABILITIES REGISTER value */
+#if SDHC_CAPAB_64BITBUS > 1 || SDHC_CAPAB_18V > 1 || SDHC_CAPAB_30V > 1 || \
+ SDHC_CAPAB_33V > 1 || SDHC_CAPAB_SUSPRESUME > 1 || SDHC_CAPAB_SDMA > 1 || \
+ SDHC_CAPAB_HIGHSPEED > 1 || SDHC_CAPAB_ADMA2 > 1 || SDHC_CAPAB_ADMA1 > 1 ||\
+ SDHC_CAPAB_TOUNIT > 1
+#error Capabilities features can have value 0 or 1 only!
+#endif
+
+#if SDHC_CAPAB_MAXBLOCKLENGTH == 512
+#define MAX_BLOCK_LENGTH 0ul
+#elif SDHC_CAPAB_MAXBLOCKLENGTH == 1024
+#define MAX_BLOCK_LENGTH 1ul
+#elif SDHC_CAPAB_MAXBLOCKLENGTH == 2048
+#define MAX_BLOCK_LENGTH 2ul
+#else
+#error Max host controller block size can have value 512, 1024 or 2048 only!
+#endif
+
+#if (SDHC_CAPAB_BASECLKFREQ > 0 && SDHC_CAPAB_BASECLKFREQ < 10) || \
+ SDHC_CAPAB_BASECLKFREQ > 63
+#error SDclock frequency can have value in range 0, 10-63 only!
+#endif
+
+#if SDHC_CAPAB_TOCLKFREQ > 63
+#error Timeout clock frequency can have value in range 0-63 only!
+#endif
+
+#define SDHC_CAPAB_REG_DEFAULT \
+ ((SDHC_CAPAB_64BITBUS << 28) | (SDHC_CAPAB_18V << 26) | \
+ (SDHC_CAPAB_30V << 25) | (SDHC_CAPAB_33V << 24) | \
+ (SDHC_CAPAB_SUSPRESUME << 23) | (SDHC_CAPAB_SDMA << 22) | \
+ (SDHC_CAPAB_HIGHSPEED << 21) | (SDHC_CAPAB_ADMA1 << 20) | \
+ (SDHC_CAPAB_ADMA2 << 19) | (MAX_BLOCK_LENGTH << 16) | \
+ (SDHC_CAPAB_BASECLKFREQ << 8) | (SDHC_CAPAB_TOUNIT << 7) | \
+ (SDHC_CAPAB_TOCLKFREQ))
+
+#define MASKED_WRITE(reg, mask, val) (reg = (reg & (mask)) | (val))
+
+static uint8_t sdhci_slotint(SDHCIState *s)
+{
+ return (s->norintsts & s->norintsigen) || (s->errintsts & s->errintsigen) ||
+ ((s->norintsts & SDHC_NIS_INSERT) && (s->wakcon & SDHC_WKUP_ON_INS)) ||
+ ((s->norintsts & SDHC_NIS_REMOVE) && (s->wakcon & SDHC_WKUP_ON_RMV));
+}
+
+static inline void sdhci_update_irq(SDHCIState *s)
+{
+ qemu_set_irq(s->irq, sdhci_slotint(s));
+}
+
+static void sdhci_raise_insertion_irq(void *opaque)
+{
+ SDHCIState *s = (SDHCIState *)opaque;
+
+ if (s->norintsts & SDHC_NIS_REMOVE) {
+ timer_mod(s->insert_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_INSERTION_DELAY);
+ } else {
+ s->prnsts = 0x1ff0000;
+ if (s->norintstsen & SDHC_NISEN_INSERT) {
+ s->norintsts |= SDHC_NIS_INSERT;
+ }
+ sdhci_update_irq(s);
+ }
+}
+
+static void sdhci_insert_eject_cb(void *opaque, int irq, int level)
+{
+ SDHCIState *s = (SDHCIState *)opaque;
+ DPRINT_L1("Card state changed: %s!\n", level ? "insert" : "eject");
+
+ if ((s->norintsts & SDHC_NIS_REMOVE) && level) {
+ /* Give target some time to notice card ejection */
+ timer_mod(s->insert_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_INSERTION_DELAY);
+ } else {
+ if (level) {
+ s->prnsts = 0x1ff0000;
+ if (s->norintstsen & SDHC_NISEN_INSERT) {
+ s->norintsts |= SDHC_NIS_INSERT;
+ }
+ } else {
+ s->prnsts = 0x1fa0000;
+ s->pwrcon &= ~SDHC_POWER_ON;
+ s->clkcon &= ~SDHC_CLOCK_SDCLK_EN;
+ if (s->norintstsen & SDHC_NISEN_REMOVE) {
+ s->norintsts |= SDHC_NIS_REMOVE;
+ }
+ }
+ sdhci_update_irq(s);
+ }
+}
+
+static void sdhci_card_readonly_cb(void *opaque, int irq, int level)
+{
+ SDHCIState *s = (SDHCIState *)opaque;
+
+ if (level) {
+ s->prnsts &= ~SDHC_WRITE_PROTECT;
+ } else {
+ /* Write enabled */
+ s->prnsts |= SDHC_WRITE_PROTECT;
+ }
+}
+
+static void sdhci_reset(SDHCIState *s)
+{
+ timer_del(s->insert_timer);
+ timer_del(s->transfer_timer);
+ /* Set all registers to 0. Capabilities registers are not cleared
+ * and assumed to always preserve their value, given to them during
+ * initialization */
+ memset(&s->sdmasysad, 0, (uintptr_t)&s->capareg - (uintptr_t)&s->sdmasysad);
+
+ sd_set_cb(s->card, s->ro_cb, s->eject_cb);
+ s->data_count = 0;
+ s->stopped_state = sdhc_not_stopped;
+}
+
+static void sdhci_data_transfer(void *opaque);
+
+static void sdhci_send_command(SDHCIState *s)
+{
+ SDRequest request;
+ uint8_t response[16];
+ int rlen;
+
+ s->errintsts = 0;
+ s->acmd12errsts = 0;
+ request.cmd = s->cmdreg >> 8;
+ request.arg = s->argument;
+ DPRINT_L1("sending CMD%u ARG[0x%08x]\n", request.cmd, request.arg);
+ rlen = sd_do_command(s->card, &request, response);
+
+ if (s->cmdreg & SDHC_CMD_RESPONSE) {
+ if (rlen == 4) {
+ s->rspreg[0] = (response[0] << 24) | (response[1] << 16) |
+ (response[2] << 8) | response[3];
+ s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0;
+ DPRINT_L1("Response: RSPREG[31..0]=0x%08x\n", s->rspreg[0]);
+ } else if (rlen == 16) {
+ s->rspreg[0] = (response[11] << 24) | (response[12] << 16) |
+ (response[13] << 8) | response[14];
+ s->rspreg[1] = (response[7] << 24) | (response[8] << 16) |
+ (response[9] << 8) | response[10];
+ s->rspreg[2] = (response[3] << 24) | (response[4] << 16) |
+ (response[5] << 8) | response[6];
+ s->rspreg[3] = (response[0] << 16) | (response[1] << 8) |
+ response[2];
+ DPRINT_L1("Response received:\n RSPREG[127..96]=0x%08x, RSPREG[95.."
+ "64]=0x%08x,\n RSPREG[63..32]=0x%08x, RSPREG[31..0]=0x%08x\n",
+ s->rspreg[3], s->rspreg[2], s->rspreg[1], s->rspreg[0]);
+ } else {
+ ERRPRINT("Timeout waiting for command response\n");
+ if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
+ s->errintsts |= SDHC_EIS_CMDTIMEOUT;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+ }
+
+ if ((s->norintstsen & SDHC_NISEN_TRSCMP) &&
+ (s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY) {
+ s->norintsts |= SDHC_NIS_TRSCMP;
+ }
+ } else if (rlen != 0 && (s->errintstsen & SDHC_EISEN_CMDIDX)) {
+ s->errintsts |= SDHC_EIS_CMDIDX;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+
+ if (s->norintstsen & SDHC_NISEN_CMDCMP) {
+ s->norintsts |= SDHC_NIS_CMDCMP;
+ }
+
+ sdhci_update_irq(s);
+
+ if (s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
+ s->data_count = 0;
+ sdhci_data_transfer(s);
+ }
+}
+
+static void sdhci_end_transfer(SDHCIState *s)
+{
+ /* Automatically send CMD12 to stop transfer if AutoCMD12 enabled */
+ if ((s->trnmod & SDHC_TRNS_ACMD12) != 0) {
+ SDRequest request;
+ uint8_t response[16];
+
+ request.cmd = 0x0C;
+ request.arg = 0;
+ DPRINT_L1("Automatically issue CMD%d %08x\n", request.cmd, request.arg);
+ sd_do_command(s->card, &request, response);
+ /* Auto CMD12 response goes to the upper Response register */
+ s->rspreg[3] = (response[0] << 24) | (response[1] << 16) |
+ (response[2] << 8) | response[3];
+ }
+
+ s->prnsts &= ~(SDHC_DOING_READ | SDHC_DOING_WRITE |
+ SDHC_DAT_LINE_ACTIVE | SDHC_DATA_INHIBIT |
+ SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE);
+
+ if (s->norintstsen & SDHC_NISEN_TRSCMP) {
+ s->norintsts |= SDHC_NIS_TRSCMP;
+ }
+
+ sdhci_update_irq(s);
+}
+
+/*
+ * Programmed i/o data transfer
+ */
+
+/* Fill host controller's read buffer with BLKSIZE bytes of data from card */
+static void sdhci_read_block_from_card(SDHCIState *s)
+{
+ int index = 0;
+
+ if ((s->trnmod & SDHC_TRNS_MULTI) &&
+ (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) {
+ return;
+ }
+
+ for (index = 0; index < (s->blksize & 0x0fff); index++) {
+ s->fifo_buffer[index] = sd_read_data(s->card);
+ }
+
+ /* New data now available for READ through Buffer Port Register */
+ s->prnsts |= SDHC_DATA_AVAILABLE;
+ if (s->norintstsen & SDHC_NISEN_RBUFRDY) {
+ s->norintsts |= SDHC_NIS_RBUFRDY;
+ }
+
+ /* Clear DAT line active status if that was the last block */
+ if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+ ((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) {
+ s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
+ }
+
+ /* If stop at block gap request was set and it's not the last block of
+ * data - generate Block Event interrupt */
+ if (s->stopped_state == sdhc_gap_read && (s->trnmod & SDHC_TRNS_MULTI) &&
+ s->blkcnt != 1) {
+ s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
+ if (s->norintstsen & SDHC_EISEN_BLKGAP) {
+ s->norintsts |= SDHC_EIS_BLKGAP;
+ }
+ }
+
+ sdhci_update_irq(s);
+}
+
+/* Read @size byte of data from host controller @s BUFFER DATA PORT register */
+static uint32_t sdhci_read_dataport(SDHCIState *s, unsigned size)
+{
+ uint32_t value = 0;
+ int i;
+
+ /* first check that a valid data exists in host controller input buffer */
+ if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0) {
+ ERRPRINT("Trying to read from empty buffer\n");
+ return 0;
+ }
+
+ for (i = 0; i < size; i++) {
+ value |= s->fifo_buffer[s->data_count] << i * 8;
+ s->data_count++;
+ /* check if we've read all valid data (blksize bytes) from buffer */
+ if ((s->data_count) >= (s->blksize & 0x0fff)) {
+ DPRINT_L2("All %u bytes of data have been read from input buffer\n",
+ s->data_count);
+ s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */
+ s->data_count = 0; /* next buff read must start at position [0] */
+
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ s->blkcnt--;
+ }
+
+ /* if that was the last block of data */
+ if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+ ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) ||
+ /* stop at gap request */
+ (s->stopped_state == sdhc_gap_read &&
+ !(s->prnsts & SDHC_DAT_LINE_ACTIVE))) {
+ sdhci_end_transfer(s);
+ } else { /* if there are more data, read next block from card */
+ sdhci_read_block_from_card(s);
+ }
+ break;
+ }
+ }
+
+ return value;
+}
+
+/* Write data from host controller FIFO to card */
+static void sdhci_write_block_to_card(SDHCIState *s)
+{
+ int index = 0;
+
+ if (s->prnsts & SDHC_SPACE_AVAILABLE) {
+ if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+ s->norintsts |= SDHC_NIS_WBUFRDY;
+ }
+ sdhci_update_irq(s);
+ return;
+ }
+
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ if (s->blkcnt == 0) {
+ return;
+ } else {
+ s->blkcnt--;
+ }
+ }
+
+ for (index = 0; index < (s->blksize & 0x0fff); index++) {
+ sd_write_data(s->card, s->fifo_buffer[index]);
+ }
+
+ /* Next data can be written through BUFFER DATORT register */
+ s->prnsts |= SDHC_SPACE_AVAILABLE;
+
+ /* Finish transfer if that was the last block of data */
+ if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+ ((s->trnmod & SDHC_TRNS_MULTI) &&
+ (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) {
+ sdhci_end_transfer(s);
+ } else if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+ s->norintsts |= SDHC_NIS_WBUFRDY;
+ }
+
+ /* Generate Block Gap Event if requested and if not the last block */
+ if (s->stopped_state == sdhc_gap_write && (s->trnmod & SDHC_TRNS_MULTI) &&
+ s->blkcnt > 0) {
+ s->prnsts &= ~SDHC_DOING_WRITE;
+ if (s->norintstsen & SDHC_EISEN_BLKGAP) {
+ s->norintsts |= SDHC_EIS_BLKGAP;
+ }
+ sdhci_end_transfer(s);
+ }
+
+ sdhci_update_irq(s);
+}
+
+/* Write @size bytes of @value data to host controller @s Buffer Data Port
+ * register */
+static void sdhci_write_dataport(SDHCIState *s, uint32_t value, unsigned size)
+{
+ unsigned i;
+
+ /* Check that there is free space left in a buffer */
+ if (!(s->prnsts & SDHC_SPACE_AVAILABLE)) {
+ ERRPRINT("Can't write to data buffer: buffer full\n");
+ return;
+ }
+
+ for (i = 0; i < size; i++) {
+ s->fifo_buffer[s->data_count] = value & 0xFF;
+ s->data_count++;
+ value >>= 8;
+ if (s->data_count >= (s->blksize & 0x0fff)) {
+ DPRINT_L2("write buffer filled with %u bytes of data\n",
+ s->data_count);
+ s->data_count = 0;
+ s->prnsts &= ~SDHC_SPACE_AVAILABLE;
+ if (s->prnsts & SDHC_DOING_WRITE) {
+ sdhci_write_block_to_card(s);
+ }
+ }
+ }
+}
+
+/*
+ * Single DMA data transfer
+ */
+
+/* Multi block SDMA transfer */
+static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s)
+{
+ bool page_aligned = false;
+ unsigned int n, begin;
+ const uint16_t block_size = s->blksize & 0x0fff;
+ uint32_t boundary_chk = 1 << (((s->blksize & 0xf000) >> 12) + 12);
+ uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk);
+
+ /* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for
+ * possible stop at page boundary if initial address is not page aligned,
+ * allow them to work properly */
+ if ((s->sdmasysad % boundary_chk) == 0) {
+ page_aligned = true;
+ }
+
+ if (s->trnmod & SDHC_TRNS_READ) {
+ s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+ SDHC_DAT_LINE_ACTIVE;
+ while (s->blkcnt) {
+ if (s->data_count == 0) {
+ for (n = 0; n < block_size; n++) {
+ s->fifo_buffer[n] = sd_read_data(s->card);
+ }
+ }
+ begin = s->data_count;
+ if (((boundary_count + begin) < block_size) && page_aligned) {
+ s->data_count = boundary_count + begin;
+ boundary_count = 0;
+ } else {
+ s->data_count = block_size;
+ boundary_count -= block_size - begin;
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ s->blkcnt--;
+ }
+ }
+ dma_memory_write(&address_space_memory, s->sdmasysad,
+ &s->fifo_buffer[begin], s->data_count - begin);
+ s->sdmasysad += s->data_count - begin;
+ if (s->data_count == block_size) {
+ s->data_count = 0;
+ }
+ if (page_aligned && boundary_count == 0) {
+ break;
+ }
+ }
+ } else {
+ s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT |
+ SDHC_DAT_LINE_ACTIVE;
+ while (s->blkcnt) {
+ begin = s->data_count;
+ if (((boundary_count + begin) < block_size) && page_aligned) {
+ s->data_count = boundary_count + begin;
+ boundary_count = 0;
+ } else {
+ s->data_count = block_size;
+ boundary_count -= block_size - begin;
+ }
+ dma_memory_read(&address_space_memory, s->sdmasysad,
+ &s->fifo_buffer[begin], s->data_count);
+ s->sdmasysad += s->data_count - begin;
+ if (s->data_count == block_size) {
+ for (n = 0; n < block_size; n++) {
+ sd_write_data(s->card, s->fifo_buffer[n]);
+ }
+ s->data_count = 0;
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ s->blkcnt--;
+ }
+ }
+ if (page_aligned && boundary_count == 0) {
+ break;
+ }
+ }
+ }
+
+ if (s->blkcnt == 0) {
+ sdhci_end_transfer(s);
+ } else {
+ if (s->norintstsen & SDHC_NISEN_DMA) {
+ s->norintsts |= SDHC_NIS_DMA;
+ }
+ sdhci_update_irq(s);
+ }
+}
+
+/* single block SDMA transfer */
+
+static void sdhci_sdma_transfer_single_block(SDHCIState *s)
+{
+ int n;
+ uint32_t datacnt = s->blksize & 0x0fff;
+
+ if (s->trnmod & SDHC_TRNS_READ) {
+ for (n = 0; n < datacnt; n++) {
+ s->fifo_buffer[n] = sd_read_data(s->card);
+ }
+ dma_memory_write(&address_space_memory, s->sdmasysad, s->fifo_buffer,
+ datacnt);
+ } else {
+ dma_memory_read(&address_space_memory, s->sdmasysad, s->fifo_buffer,
+ datacnt);
+ for (n = 0; n < datacnt; n++) {
+ sd_write_data(s->card, s->fifo_buffer[n]);
+ }
+ }
+
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ s->blkcnt--;
+ }
+
+ sdhci_end_transfer(s);
+}
+
+typedef struct ADMADescr {
+ hwaddr addr;
+ uint16_t length;
+ uint8_t attr;
+ uint8_t incr;
+} ADMADescr;
+
+static void get_adma_description(SDHCIState *s, ADMADescr *dscr)
+{
+ uint32_t adma1 = 0;
+ uint64_t adma2 = 0;
+ hwaddr entry_addr = (hwaddr)s->admasysaddr;
+ switch (SDHC_DMA_TYPE(s->hostctl)) {
+ case SDHC_CTRL_ADMA2_32:
+ dma_memory_read(&address_space_memory, entry_addr, (uint8_t *)&adma2,
+ sizeof(adma2));
+ adma2 = le64_to_cpu(adma2);
+ /* The spec does not specify endianness of descriptor table.
+ * We currently assume that it is LE.
+ */
+ dscr->addr = (hwaddr)extract64(adma2, 32, 32) & ~0x3ull;
+ dscr->length = (uint16_t)extract64(adma2, 16, 16);
+ dscr->attr = (uint8_t)extract64(adma2, 0, 7);
+ dscr->incr = 8;
+ break;
+ case SDHC_CTRL_ADMA1_32:
+ dma_memory_read(&address_space_memory, entry_addr, (uint8_t *)&adma1,
+ sizeof(adma1));
+ adma1 = le32_to_cpu(adma1);
+ dscr->addr = (hwaddr)(adma1 & 0xFFFFF000);
+ dscr->attr = (uint8_t)extract32(adma1, 0, 7);
+ dscr->incr = 4;
+ if ((dscr->attr & SDHC_ADMA_ATTR_ACT_MASK) == SDHC_ADMA_ATTR_SET_LEN) {
+ dscr->length = (uint16_t)extract32(adma1, 12, 16);
+ } else {
+ dscr->length = 4096;
+ }
+ break;
+ case SDHC_CTRL_ADMA2_64:
+ dma_memory_read(&address_space_memory, entry_addr,
+ (uint8_t *)(&dscr->attr), 1);
+ dma_memory_read(&address_space_memory, entry_addr + 2,
+ (uint8_t *)(&dscr->length), 2);
+ dscr->length = le16_to_cpu(dscr->length);
+ dma_memory_read(&address_space_memory, entry_addr + 4,
+ (uint8_t *)(&dscr->addr), 8);
+ dscr->attr = le64_to_cpu(dscr->attr);
+ dscr->attr &= 0xfffffff8;
+ dscr->incr = 12;
+ break;
+ }
+}
+
+/* Advanced DMA data transfer */
+
+static void sdhci_do_adma(SDHCIState *s)
+{
+ unsigned int n, begin, length;
+ const uint16_t block_size = s->blksize & 0x0fff;
+ ADMADescr dscr;
+ int i;
+
+ for (i = 0; i < SDHC_ADMA_DESCS_PER_DELAY; ++i) {
+ s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH;
+
+ get_adma_description(s, &dscr);
+ DPRINT_L2("ADMA loop: addr=" TARGET_FMT_plx ", len=%d, attr=%x\n",
+ dscr.addr, dscr.length, dscr.attr);
+
+ if ((dscr.attr & SDHC_ADMA_ATTR_VALID) == 0) {
+ /* Indicate that error occurred in ST_FDS state */
+ s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
+ s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS;
+
+ /* Generate ADMA error interrupt */
+ if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+ s->errintsts |= SDHC_EIS_ADMAERR;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+
+ sdhci_update_irq(s);
+ return;
+ }
+
+ length = dscr.length ? dscr.length : 65536;
+
+ switch (dscr.attr & SDHC_ADMA_ATTR_ACT_MASK) {
+ case SDHC_ADMA_ATTR_ACT_TRAN: /* data transfer */
+
+ if (s->trnmod & SDHC_TRNS_READ) {
+ while (length) {
+ if (s->data_count == 0) {
+ for (n = 0; n < block_size; n++) {
+ s->fifo_buffer[n] = sd_read_data(s->card);
+ }
+ }
+ begin = s->data_count;
+ if ((length + begin) < block_size) {
+ s->data_count = length + begin;
+ length = 0;
+ } else {
+ s->data_count = block_size;
+ length -= block_size - begin;
+ }
+ dma_memory_write(&address_space_memory, dscr.addr,
+ &s->fifo_buffer[begin],
+ s->data_count - begin);
+ dscr.addr += s->data_count - begin;
+ if (s->data_count == block_size) {
+ s->data_count = 0;
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ s->blkcnt--;
+ if (s->blkcnt == 0) {
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ while (length) {
+ begin = s->data_count;
+ if ((length + begin) < block_size) {
+ s->data_count = length + begin;
+ length = 0;
+ } else {
+ s->data_count = block_size;
+ length -= block_size - begin;
+ }
+ dma_memory_read(&address_space_memory, dscr.addr,
+ &s->fifo_buffer[begin],
+ s->data_count - begin);
+ dscr.addr += s->data_count - begin;
+ if (s->data_count == block_size) {
+ for (n = 0; n < block_size; n++) {
+ sd_write_data(s->card, s->fifo_buffer[n]);
+ }
+ s->data_count = 0;
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ s->blkcnt--;
+ if (s->blkcnt == 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ s->admasysaddr += dscr.incr;
+ break;
+ case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */
+ s->admasysaddr = dscr.addr;
+ DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", s->admasysaddr);
+ break;
+ default:
+ s->admasysaddr += dscr.incr;
+ break;
+ }
+
+ if (dscr.attr & SDHC_ADMA_ATTR_INT) {
+ DPRINT_L1("ADMA interrupt: admasysaddr=0x%lx\n", s->admasysaddr);
+ if (s->norintstsen & SDHC_NISEN_DMA) {
+ s->norintsts |= SDHC_NIS_DMA;
+ }
+
+ sdhci_update_irq(s);
+ }
+
+ /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
+ if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+ (s->blkcnt == 0)) || (dscr.attr & SDHC_ADMA_ATTR_END)) {
+ DPRINT_L2("ADMA transfer completed\n");
+ if (length || ((dscr.attr & SDHC_ADMA_ATTR_END) &&
+ (s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+ s->blkcnt != 0)) {
+ ERRPRINT("SD/MMC host ADMA length mismatch\n");
+ s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+ SDHC_ADMAERR_STATE_ST_TFR;
+ if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+ ERRPRINT("Set ADMA error flag\n");
+ s->errintsts |= SDHC_EIS_ADMAERR;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+
+ sdhci_update_irq(s);
+ }
+ sdhci_end_transfer(s);
+ return;
+ }
+
+ }
+
+ /* we have unfinished business - reschedule to continue ADMA */
+ timer_mod(s->transfer_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY);
+}
+
+/* Perform data transfer according to controller configuration */
+
+static void sdhci_data_transfer(void *opaque)
+{
+ SDHCIState *s = (SDHCIState *)opaque;
+
+ if (s->trnmod & SDHC_TRNS_DMA) {
+ switch (SDHC_DMA_TYPE(s->hostctl)) {
+ case SDHC_CTRL_SDMA:
+ if ((s->trnmod & SDHC_TRNS_MULTI) &&
+ (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || s->blkcnt == 0)) {
+ break;
+ }
+
+ if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) {
+ sdhci_sdma_transfer_single_block(s);
+ } else {
+ sdhci_sdma_transfer_multi_blocks(s);
+ }
+
+ break;
+ case SDHC_CTRL_ADMA1_32:
+ if (!(s->capareg & SDHC_CAN_DO_ADMA1)) {
+ ERRPRINT("ADMA1 not supported\n");
+ break;
+ }
+
+ sdhci_do_adma(s);
+ break;
+ case SDHC_CTRL_ADMA2_32:
+ if (!(s->capareg & SDHC_CAN_DO_ADMA2)) {
+ ERRPRINT("ADMA2 not supported\n");
+ break;
+ }
+
+ sdhci_do_adma(s);
+ break;
+ case SDHC_CTRL_ADMA2_64:
+ if (!(s->capareg & SDHC_CAN_DO_ADMA2) ||
+ !(s->capareg & SDHC_64_BIT_BUS_SUPPORT)) {
+ ERRPRINT("64 bit ADMA not supported\n");
+ break;
+ }
+
+ sdhci_do_adma(s);
+ break;
+ default:
+ ERRPRINT("Unsupported DMA type\n");
+ break;
+ }
+ } else {
+ if ((s->trnmod & SDHC_TRNS_READ) && sd_data_ready(s->card)) {
+ s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+ SDHC_DAT_LINE_ACTIVE;
+ sdhci_read_block_from_card(s);
+ } else {
+ s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE |
+ SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT;
+ sdhci_write_block_to_card(s);
+ }
+ }
+}
+
+static bool sdhci_can_issue_command(SDHCIState *s)
+{
+ if (!SDHC_CLOCK_IS_ON(s->clkcon) || !(s->pwrcon & SDHC_POWER_ON) ||
+ (((s->prnsts & SDHC_DATA_INHIBIT) || s->stopped_state) &&
+ ((s->cmdreg & SDHC_CMD_DATA_PRESENT) ||
+ ((s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY &&
+ !(SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT))))) {
+ return false;
+ }
+
+ return true;
+}
+
+/* The Buffer Data Port register must be accessed in sequential and
+ * continuous manner */
+static inline bool
+sdhci_buff_access_is_sequential(SDHCIState *s, unsigned byte_num)
+{
+ if ((s->data_count & 0x3) != byte_num) {
+ ERRPRINT("Non-sequential access to Buffer Data Port register"
+ "is prohibited\n");
+ return false;
+ }
+ return true;
+}
+
+static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size)
+{
+ SDHCIState *s = (SDHCIState *)opaque;
+ uint32_t ret = 0;
+
+ switch (offset & ~0x3) {
+ case SDHC_SYSAD:
+ ret = s->sdmasysad;
+ break;
+ case SDHC_BLKSIZE:
+ ret = s->blksize | (s->blkcnt << 16);
+ break;
+ case SDHC_ARGUMENT:
+ ret = s->argument;
+ break;
+ case SDHC_TRNMOD:
+ ret = s->trnmod | (s->cmdreg << 16);
+ break;
+ case SDHC_RSPREG0 ... SDHC_RSPREG3:
+ ret = s->rspreg[((offset & ~0x3) - SDHC_RSPREG0) >> 2];
+ break;
+ case SDHC_BDATA:
+ if (sdhci_buff_access_is_sequential(s, offset - SDHC_BDATA)) {
+ ret = sdhci_read_dataport(s, size);
+ DPRINT_L2("read %ub: addr[0x%04x] -> %u(0x%x)\n", size, (int)offset,
+ ret, ret);
+ return ret;
+ }
+ break;
+ case SDHC_PRNSTS:
+ ret = s->prnsts;
+ break;
+ case SDHC_HOSTCTL:
+ ret = s->hostctl | (s->pwrcon << 8) | (s->blkgap << 16) |
+ (s->wakcon << 24);
+ break;
+ case SDHC_CLKCON:
+ ret = s->clkcon | (s->timeoutcon << 16);
+ break;
+ case SDHC_NORINTSTS:
+ ret = s->norintsts | (s->errintsts << 16);
+ break;
+ case SDHC_NORINTSTSEN:
+ ret = s->norintstsen | (s->errintstsen << 16);
+ break;
+ case SDHC_NORINTSIGEN:
+ ret = s->norintsigen | (s->errintsigen << 16);
+ break;
+ case SDHC_ACMD12ERRSTS:
+ ret = s->acmd12errsts;
+ break;
+ case SDHC_CAPAREG:
+ ret = s->capareg;
+ break;
+ case SDHC_MAXCURR:
+ ret = s->maxcurr;
+ break;
+ case SDHC_ADMAERR:
+ ret = s->admaerr;
+ break;
+ case SDHC_ADMASYSADDR:
+ ret = (uint32_t)s->admasysaddr;
+ break;
+ case SDHC_ADMASYSADDR + 4:
+ ret = (uint32_t)(s->admasysaddr >> 32);
+ break;
+ case SDHC_SLOT_INT_STATUS:
+ ret = (SD_HOST_SPECv2_VERS << 16) | sdhci_slotint(s);
+ break;
+ default:
+ ERRPRINT("bad %ub read: addr[0x%04x]\n", size, (int)offset);
+ break;
+ }
+
+ ret >>= (offset & 0x3) * 8;
+ ret &= (1ULL << (size * 8)) - 1;
+ DPRINT_L2("read %ub: addr[0x%04x] -> %u(0x%x)\n", size, (int)offset, ret, ret);
+ return ret;
+}
+
+static inline void sdhci_blkgap_write(SDHCIState *s, uint8_t value)
+{
+ if ((value & SDHC_STOP_AT_GAP_REQ) && (s->blkgap & SDHC_STOP_AT_GAP_REQ)) {
+ return;
+ }
+ s->blkgap = value & SDHC_STOP_AT_GAP_REQ;
+
+ if ((value & SDHC_CONTINUE_REQ) && s->stopped_state &&
+ (s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) {
+ if (s->stopped_state == sdhc_gap_read) {
+ s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ;
+ sdhci_read_block_from_card(s);
+ } else {
+ s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE;
+ sdhci_write_block_to_card(s);
+ }
+ s->stopped_state = sdhc_not_stopped;
+ } else if (!s->stopped_state && (value & SDHC_STOP_AT_GAP_REQ)) {
+ if (s->prnsts & SDHC_DOING_READ) {
+ s->stopped_state = sdhc_gap_read;
+ } else if (s->prnsts & SDHC_DOING_WRITE) {
+ s->stopped_state = sdhc_gap_write;
+ }
+ }
+}
+
+static inline void sdhci_reset_write(SDHCIState *s, uint8_t value)
+{
+ switch (value) {
+ case SDHC_RESET_ALL:
+ sdhci_reset(s);
+ break;
+ case SDHC_RESET_CMD:
+ s->prnsts &= ~SDHC_CMD_INHIBIT;
+ s->norintsts &= ~SDHC_NIS_CMDCMP;
+ break;
+ case SDHC_RESET_DATA:
+ s->data_count = 0;
+ s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE |
+ SDHC_DOING_READ | SDHC_DOING_WRITE |
+ SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE);
+ s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ);
+ s->stopped_state = sdhc_not_stopped;
+ s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
+ SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
+ break;
+ }
+}
+
+static void
+sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
+{
+ SDHCIState *s = (SDHCIState *)opaque;
+ unsigned shift = 8 * (offset & 0x3);
+ uint32_t mask = ~(((1ULL << (size * 8)) - 1) << shift);
+ uint32_t value = val;
+ value <<= shift;
+
+ switch (offset & ~0x3) {
+ case SDHC_SYSAD:
+ s->sdmasysad = (s->sdmasysad & mask) | value;
+ MASKED_WRITE(s->sdmasysad, mask, value);
+ /* Writing to last byte of sdmasysad might trigger transfer */
+ if (!(mask & 0xFF000000) && TRANSFERRING_DATA(s->prnsts) && s->blkcnt &&
+ s->blksize && SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) {
+ sdhci_sdma_transfer_multi_blocks(s);
+ }
+ break;
+ case SDHC_BLKSIZE:
+ if (!TRANSFERRING_DATA(s->prnsts)) {
+ MASKED_WRITE(s->blksize, mask, value);
+ MASKED_WRITE(s->blkcnt, mask >> 16, value >> 16);
+ }
+ break;
+ case SDHC_ARGUMENT:
+ MASKED_WRITE(s->argument, mask, value);
+ break;
+ case SDHC_TRNMOD:
+ /* DMA can be enabled only if it is supported as indicated by
+ * capabilities register */
+ if (!(s->capareg & SDHC_CAN_DO_DMA)) {
+ value &= ~SDHC_TRNS_DMA;
+ }
+ MASKED_WRITE(s->trnmod, mask, value);
+ MASKED_WRITE(s->cmdreg, mask >> 16, value >> 16);
+
+ /* Writing to the upper byte of CMDREG triggers SD command generation */
+ if ((mask & 0xFF000000) || !sdhci_can_issue_command(s)) {
+ break;
+ }
+
+ sdhci_send_command(s);
+ break;
+ case SDHC_BDATA:
+ if (sdhci_buff_access_is_sequential(s, offset - SDHC_BDATA)) {
+ sdhci_write_dataport(s, value >> shift, size);
+ }
+ break;
+ case SDHC_HOSTCTL:
+ if (!(mask & 0xFF0000)) {
+ sdhci_blkgap_write(s, value >> 16);
+ }
+ MASKED_WRITE(s->hostctl, mask, value);
+ MASKED_WRITE(s->pwrcon, mask >> 8, value >> 8);
+ MASKED_WRITE(s->wakcon, mask >> 24, value >> 24);
+ if (!(s->prnsts & SDHC_CARD_PRESENT) || ((s->pwrcon >> 1) & 0x7) < 5 ||
+ !(s->capareg & (1 << (31 - ((s->pwrcon >> 1) & 0x7))))) {
+ s->pwrcon &= ~SDHC_POWER_ON;
+ }
+ break;
+ case SDHC_CLKCON:
+ if (!(mask & 0xFF000000)) {
+ sdhci_reset_write(s, value >> 24);
+ }
+ MASKED_WRITE(s->clkcon, mask, value);
+ MASKED_WRITE(s->timeoutcon, mask >> 16, value >> 16);
+ if (s->clkcon & SDHC_CLOCK_INT_EN) {
+ s->clkcon |= SDHC_CLOCK_INT_STABLE;
+ } else {
+ s->clkcon &= ~SDHC_CLOCK_INT_STABLE;
+ }
+ break;
+ case SDHC_NORINTSTS:
+ if (s->norintstsen & SDHC_NISEN_CARDINT) {
+ value &= ~SDHC_NIS_CARDINT;
+ }
+ s->norintsts &= mask | ~value;
+ s->errintsts &= (mask >> 16) | ~(value >> 16);
+ if (s->errintsts) {
+ s->norintsts |= SDHC_NIS_ERR;
+ } else {
+ s->norintsts &= ~SDHC_NIS_ERR;
+ }
+ sdhci_update_irq(s);
+ break;
+ case SDHC_NORINTSTSEN:
+ MASKED_WRITE(s->norintstsen, mask, value);
+ MASKED_WRITE(s->errintstsen, mask >> 16, value >> 16);
+ s->norintsts &= s->norintstsen;
+ s->errintsts &= s->errintstsen;
+ if (s->errintsts) {
+ s->norintsts |= SDHC_NIS_ERR;
+ } else {
+ s->norintsts &= ~SDHC_NIS_ERR;
+ }
+ sdhci_update_irq(s);
+ break;
+ case SDHC_NORINTSIGEN:
+ MASKED_WRITE(s->norintsigen, mask, value);
+ MASKED_WRITE(s->errintsigen, mask >> 16, value >> 16);
+ sdhci_update_irq(s);
+ break;
+ case SDHC_ADMAERR:
+ MASKED_WRITE(s->admaerr, mask, value);
+ break;
+ case SDHC_ADMASYSADDR:
+ s->admasysaddr = (s->admasysaddr & (0xFFFFFFFF00000000ULL |
+ (uint64_t)mask)) | (uint64_t)value;
+ break;
+ case SDHC_ADMASYSADDR + 4:
+ s->admasysaddr = (s->admasysaddr & (0x00000000FFFFFFFFULL |
+ ((uint64_t)mask << 32))) | ((uint64_t)value << 32);
+ break;
+ case SDHC_FEAER:
+ s->acmd12errsts |= value;
+ s->errintsts |= (value >> 16) & s->errintstsen;
+ if (s->acmd12errsts) {
+ s->errintsts |= SDHC_EIS_CMD12ERR;
+ }
+ if (s->errintsts) {
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+ sdhci_update_irq(s);
+ break;
+ default:
+ ERRPRINT("bad %ub write offset: addr[0x%04x] <- %u(0x%x)\n",
+ size, (int)offset, value >> shift, value >> shift);
+ break;
+ }
+ DPRINT_L2("write %ub: addr[0x%04x] <- %u(0x%x)\n",
+ size, (int)offset, value >> shift, value >> shift);
+}
+
+static const MemoryRegionOps sdhci_mmio_ops = {
+ .read = sdhci_read,
+ .write = sdhci_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static inline unsigned int sdhci_get_fifolen(SDHCIState *s)
+{
+ switch (SDHC_CAPAB_BLOCKSIZE(s->capareg)) {
+ case 0:
+ return 512;
+ case 1:
+ return 1024;
+ case 2:
+ return 2048;
+ default:
+ hw_error("SDHC: unsupported value for maximum block size\n");
+ return 0;
+ }
+}
+
+static void sdhci_initfn(SDHCIState *s)
+{
+ DriveInfo *di;
+
+ /* FIXME use a qdev drive property instead of drive_get_next() */
+ di = drive_get_next(IF_SD);
+ s->card = sd_init(di ? blk_by_legacy_dinfo(di) : NULL, false);
+ if (s->card == NULL) {
+ exit(1);
+ }
+ s->eject_cb = qemu_allocate_irq(sdhci_insert_eject_cb, s, 0);
+ s->ro_cb = qemu_allocate_irq(sdhci_card_readonly_cb, s, 0);
+ sd_set_cb(s->card, s->ro_cb, s->eject_cb);
+
+ s->insert_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_raise_insertion_irq, s);
+ s->transfer_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_data_transfer, s);
+}
+
+static void sdhci_uninitfn(SDHCIState *s)
+{
+ timer_del(s->insert_timer);
+ timer_free(s->insert_timer);
+ timer_del(s->transfer_timer);
+ timer_free(s->transfer_timer);
+ qemu_free_irq(s->eject_cb);
+ qemu_free_irq(s->ro_cb);
+
+ if (s->fifo_buffer) {
+ g_free(s->fifo_buffer);
+ s->fifo_buffer = NULL;
+ }
+}
+
+const VMStateDescription sdhci_vmstate = {
+ .name = "sdhci",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(sdmasysad, SDHCIState),
+ VMSTATE_UINT16(blksize, SDHCIState),
+ VMSTATE_UINT16(blkcnt, SDHCIState),
+ VMSTATE_UINT32(argument, SDHCIState),
+ VMSTATE_UINT16(trnmod, SDHCIState),
+ VMSTATE_UINT16(cmdreg, SDHCIState),
+ VMSTATE_UINT32_ARRAY(rspreg, SDHCIState, 4),
+ VMSTATE_UINT32(prnsts, SDHCIState),
+ VMSTATE_UINT8(hostctl, SDHCIState),
+ VMSTATE_UINT8(pwrcon, SDHCIState),
+ VMSTATE_UINT8(blkgap, SDHCIState),
+ VMSTATE_UINT8(wakcon, SDHCIState),
+ VMSTATE_UINT16(clkcon, SDHCIState),
+ VMSTATE_UINT8(timeoutcon, SDHCIState),
+ VMSTATE_UINT8(admaerr, SDHCIState),
+ VMSTATE_UINT16(norintsts, SDHCIState),
+ VMSTATE_UINT16(errintsts, SDHCIState),
+ VMSTATE_UINT16(norintstsen, SDHCIState),
+ VMSTATE_UINT16(errintstsen, SDHCIState),
+ VMSTATE_UINT16(norintsigen, SDHCIState),
+ VMSTATE_UINT16(errintsigen, SDHCIState),
+ VMSTATE_UINT16(acmd12errsts, SDHCIState),
+ VMSTATE_UINT16(data_count, SDHCIState),
+ VMSTATE_UINT64(admasysaddr, SDHCIState),
+ VMSTATE_UINT8(stopped_state, SDHCIState),
+ VMSTATE_VBUFFER_UINT32(fifo_buffer, SDHCIState, 1, NULL, 0, buf_maxsz),
+ VMSTATE_TIMER_PTR(insert_timer, SDHCIState),
+ VMSTATE_TIMER_PTR(transfer_timer, SDHCIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* Capabilities registers provide information on supported features of this
+ * specific host controller implementation */
+static Property sdhci_properties[] = {
+ DEFINE_PROP_UINT32("capareg", SDHCIState, capareg,
+ SDHC_CAPAB_REG_DEFAULT),
+ DEFINE_PROP_UINT32("maxcurr", SDHCIState, maxcurr, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sdhci_pci_realize(PCIDevice *dev, Error **errp)
+{
+ SDHCIState *s = PCI_SDHCI(dev);
+ dev->config[PCI_CLASS_PROG] = 0x01; /* Standard Host supported DMA */
+ dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */
+ sdhci_initfn(s);
+ s->buf_maxsz = sdhci_get_fifolen(s);
+ s->fifo_buffer = g_malloc0(s->buf_maxsz);
+ s->irq = pci_allocate_irq(dev);
+ memory_region_init_io(&s->iomem, OBJECT(s), &sdhci_mmio_ops, s, "sdhci",
+ SDHC_REGISTERS_MAP_SIZE);
+ pci_register_bar(dev, 0, 0, &s->iomem);
+}
+
+static void sdhci_pci_exit(PCIDevice *dev)
+{
+ SDHCIState *s = PCI_SDHCI(dev);
+ sdhci_uninitfn(s);
+}
+
+static void sdhci_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = sdhci_pci_realize;
+ k->exit = sdhci_pci_exit;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT;
+ k->device_id = PCI_DEVICE_ID_REDHAT_SDHCI;
+ k->class_id = PCI_CLASS_SYSTEM_SDHCI;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->vmsd = &sdhci_vmstate;
+ dc->props = sdhci_properties;
+ /* Reason: realize() method uses drive_get_next() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo sdhci_pci_info = {
+ .name = TYPE_PCI_SDHCI,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(SDHCIState),
+ .class_init = sdhci_pci_class_init,
+};
+
+static void sdhci_sysbus_init(Object *obj)
+{
+ SDHCIState *s = SYSBUS_SDHCI(obj);
+ sdhci_initfn(s);
+}
+
+static void sdhci_sysbus_finalize(Object *obj)
+{
+ SDHCIState *s = SYSBUS_SDHCI(obj);
+ sdhci_uninitfn(s);
+}
+
+static void sdhci_sysbus_realize(DeviceState *dev, Error ** errp)
+{
+ SDHCIState *s = SYSBUS_SDHCI(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ s->buf_maxsz = sdhci_get_fifolen(s);
+ s->fifo_buffer = g_malloc0(s->buf_maxsz);
+ sysbus_init_irq(sbd, &s->irq);
+ memory_region_init_io(&s->iomem, OBJECT(s), &sdhci_mmio_ops, s, "sdhci",
+ SDHC_REGISTERS_MAP_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void sdhci_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &sdhci_vmstate;
+ dc->props = sdhci_properties;
+ dc->realize = sdhci_sysbus_realize;
+ /* Reason: instance_init() method uses drive_get_next() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo sdhci_sysbus_info = {
+ .name = TYPE_SYSBUS_SDHCI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SDHCIState),
+ .instance_init = sdhci_sysbus_init,
+ .instance_finalize = sdhci_sysbus_finalize,
+ .class_init = sdhci_sysbus_class_init,
+};
+
+static void sdhci_register_types(void)
+{
+ type_register_static(&sdhci_pci_info);
+ type_register_static(&sdhci_sysbus_info);
+}
+
+type_init(sdhci_register_types)
diff --git a/hw/sd/sdhci.h b/hw/sd/sdhci.h
new file mode 100644
index 00000000..3352d23d
--- /dev/null
+++ b/hw/sd/sdhci.h
@@ -0,0 +1,295 @@
+/*
+ * SD Association Host Standard Specification v2.0 controller emulation
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Mitsyanko Igor <i.mitsyanko@samsung.com>
+ * Peter A.G. Crosthwaite <peter.crosthwaite@petalogix.com>
+ *
+ * Based on MMC controller for Samsung S5PC1xx-based board emulation
+ * by Alexey Merkulov and Vladimir Monakhov.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SDHCI_H
+#define SDHCI_H
+
+#include "qemu-common.h"
+#include "hw/pci/pci.h"
+#include "hw/sysbus.h"
+#include "hw/sd.h"
+
+/* R/W SDMA System Address register 0x0 */
+#define SDHC_SYSAD 0x00
+
+/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */
+#define SDHC_BLKSIZE 0x04
+
+/* R/W Blocks count for current transfer 0x0 */
+#define SDHC_BLKCNT 0x06
+
+/* R/W Command Argument Register 0x0 */
+#define SDHC_ARGUMENT 0x08
+
+/* R/W Transfer Mode Setting Register 0x0 */
+#define SDHC_TRNMOD 0x0C
+#define SDHC_TRNS_DMA 0x0001
+#define SDHC_TRNS_BLK_CNT_EN 0x0002
+#define SDHC_TRNS_ACMD12 0x0004
+#define SDHC_TRNS_READ 0x0010
+#define SDHC_TRNS_MULTI 0x0020
+
+/* R/W Command Register 0x0 */
+#define SDHC_CMDREG 0x0E
+#define SDHC_CMD_RSP_WITH_BUSY (3 << 0)
+#define SDHC_CMD_DATA_PRESENT (1 << 5)
+#define SDHC_CMD_SUSPEND (1 << 6)
+#define SDHC_CMD_RESUME (1 << 7)
+#define SDHC_CMD_ABORT ((1 << 6)|(1 << 7))
+#define SDHC_CMD_TYPE_MASK ((1 << 6)|(1 << 7))
+#define SDHC_COMMAND_TYPE(x) ((x) & SDHC_CMD_TYPE_MASK)
+
+/* ROC Response Register 0 0x0 */
+#define SDHC_RSPREG0 0x10
+/* ROC Response Register 1 0x0 */
+#define SDHC_RSPREG1 0x14
+/* ROC Response Register 2 0x0 */
+#define SDHC_RSPREG2 0x18
+/* ROC Response Register 3 0x0 */
+#define SDHC_RSPREG3 0x1C
+
+/* R/W Buffer Data Register 0x0 */
+#define SDHC_BDATA 0x20
+
+/* R/ROC Present State Register 0x000A0000 */
+#define SDHC_PRNSTS 0x24
+#define SDHC_CMD_INHIBIT 0x00000001
+#define SDHC_DATA_INHIBIT 0x00000002
+#define SDHC_DAT_LINE_ACTIVE 0x00000004
+#define SDHC_DOING_WRITE 0x00000100
+#define SDHC_DOING_READ 0x00000200
+#define SDHC_SPACE_AVAILABLE 0x00000400
+#define SDHC_DATA_AVAILABLE 0x00000800
+#define SDHC_CARD_PRESENT 0x00010000
+#define SDHC_CARD_DETECT 0x00040000
+#define SDHC_WRITE_PROTECT 0x00080000
+#define TRANSFERRING_DATA(x) \
+ ((x) & (SDHC_DOING_READ | SDHC_DOING_WRITE))
+
+/* R/W Host control Register 0x0 */
+#define SDHC_HOSTCTL 0x28
+#define SDHC_CTRL_DMA_CHECK_MASK 0x18
+#define SDHC_CTRL_SDMA 0x00
+#define SDHC_CTRL_ADMA1_32 0x08
+#define SDHC_CTRL_ADMA2_32 0x10
+#define SDHC_CTRL_ADMA2_64 0x18
+#define SDHC_DMA_TYPE(x) ((x) & SDHC_CTRL_DMA_CHECK_MASK)
+
+/* R/W Power Control Register 0x0 */
+#define SDHC_PWRCON 0x29
+#define SDHC_POWER_ON (1 << 0)
+
+/* R/W Block Gap Control Register 0x0 */
+#define SDHC_BLKGAP 0x2A
+#define SDHC_STOP_AT_GAP_REQ 0x01
+#define SDHC_CONTINUE_REQ 0x02
+
+/* R/W WakeUp Control Register 0x0 */
+#define SDHC_WAKCON 0x2B
+#define SDHC_WKUP_ON_INS (1 << 1)
+#define SDHC_WKUP_ON_RMV (1 << 2)
+
+/* CLKCON */
+#define SDHC_CLKCON 0x2C
+#define SDHC_CLOCK_INT_STABLE 0x0002
+#define SDHC_CLOCK_INT_EN 0x0001
+#define SDHC_CLOCK_SDCLK_EN (1 << 2)
+#define SDHC_CLOCK_CHK_MASK 0x0007
+#define SDHC_CLOCK_IS_ON(x) \
+ (((x) & SDHC_CLOCK_CHK_MASK) == SDHC_CLOCK_CHK_MASK)
+
+/* R/W Timeout Control Register 0x0 */
+#define SDHC_TIMEOUTCON 0x2E
+
+/* R/W Software Reset Register 0x0 */
+#define SDHC_SWRST 0x2F
+#define SDHC_RESET_ALL 0x01
+#define SDHC_RESET_CMD 0x02
+#define SDHC_RESET_DATA 0x04
+
+/* ROC/RW1C Normal Interrupt Status Register 0x0 */
+#define SDHC_NORINTSTS 0x30
+#define SDHC_NIS_ERR 0x8000
+#define SDHC_NIS_CMDCMP 0x0001
+#define SDHC_NIS_TRSCMP 0x0002
+#define SDHC_NIS_BLKGAP 0x0004
+#define SDHC_NIS_DMA 0x0008
+#define SDHC_NIS_WBUFRDY 0x0010
+#define SDHC_NIS_RBUFRDY 0x0020
+#define SDHC_NIS_INSERT 0x0040
+#define SDHC_NIS_REMOVE 0x0080
+#define SDHC_NIS_CARDINT 0x0100
+
+/* ROC/RW1C Error Interrupt Status Register 0x0 */
+#define SDHC_ERRINTSTS 0x32
+#define SDHC_EIS_CMDTIMEOUT 0x0001
+#define SDHC_EIS_BLKGAP 0x0004
+#define SDHC_EIS_CMDIDX 0x0008
+#define SDHC_EIS_CMD12ERR 0x0100
+#define SDHC_EIS_ADMAERR 0x0200
+
+/* R/W Normal Interrupt Status Enable Register 0x0 */
+#define SDHC_NORINTSTSEN 0x34
+#define SDHC_NISEN_CMDCMP 0x0001
+#define SDHC_NISEN_TRSCMP 0x0002
+#define SDHC_NISEN_DMA 0x0008
+#define SDHC_NISEN_WBUFRDY 0x0010
+#define SDHC_NISEN_RBUFRDY 0x0020
+#define SDHC_NISEN_INSERT 0x0040
+#define SDHC_NISEN_REMOVE 0x0080
+#define SDHC_NISEN_CARDINT 0x0100
+
+/* R/W Error Interrupt Status Enable Register 0x0 */
+#define SDHC_ERRINTSTSEN 0x36
+#define SDHC_EISEN_CMDTIMEOUT 0x0001
+#define SDHC_EISEN_BLKGAP 0x0004
+#define SDHC_EISEN_CMDIDX 0x0008
+#define SDHC_EISEN_ADMAERR 0x0200
+
+/* R/W Normal Interrupt Signal Enable Register 0x0 */
+#define SDHC_NORINTSIGEN 0x38
+#define SDHC_NORINTSIG_INSERT (1 << 6)
+#define SDHC_NORINTSIG_REMOVE (1 << 7)
+
+/* R/W Error Interrupt Signal Enable Register 0x0 */
+#define SDHC_ERRINTSIGEN 0x3A
+
+/* ROC Auto CMD12 error status register 0x0 */
+#define SDHC_ACMD12ERRSTS 0x3C
+
+/* HWInit Capabilities Register 0x05E80080 */
+#define SDHC_CAPAREG 0x40
+#define SDHC_CAN_DO_DMA 0x00400000
+#define SDHC_CAN_DO_ADMA2 0x00080000
+#define SDHC_CAN_DO_ADMA1 0x00100000
+#define SDHC_64_BIT_BUS_SUPPORT (1 << 28)
+#define SDHC_CAPAB_BLOCKSIZE(x) (((x) >> 16) & 0x3)
+
+/* HWInit Maximum Current Capabilities Register 0x0 */
+#define SDHC_MAXCURR 0x48
+
+/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */
+#define SDHC_FEAER 0x50
+/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */
+#define SDHC_FEERR 0x52
+
+/* R/W ADMA Error Status Register 0x00 */
+#define SDHC_ADMAERR 0x54
+#define SDHC_ADMAERR_LENGTH_MISMATCH (1 << 2)
+#define SDHC_ADMAERR_STATE_ST_STOP (0 << 0)
+#define SDHC_ADMAERR_STATE_ST_FDS (1 << 0)
+#define SDHC_ADMAERR_STATE_ST_TFR (3 << 0)
+#define SDHC_ADMAERR_STATE_MASK (3 << 0)
+
+/* R/W ADMA System Address Register 0x00 */
+#define SDHC_ADMASYSADDR 0x58
+#define SDHC_ADMA_ATTR_SET_LEN (1 << 4)
+#define SDHC_ADMA_ATTR_ACT_TRAN (1 << 5)
+#define SDHC_ADMA_ATTR_ACT_LINK (3 << 4)
+#define SDHC_ADMA_ATTR_INT (1 << 2)
+#define SDHC_ADMA_ATTR_END (1 << 1)
+#define SDHC_ADMA_ATTR_VALID (1 << 0)
+#define SDHC_ADMA_ATTR_ACT_MASK ((1 << 4)|(1 << 5))
+
+/* Slot interrupt status */
+#define SDHC_SLOT_INT_STATUS 0xFC
+
+/* HWInit Host Controller Version Register 0x0401 */
+#define SDHC_HCVER 0xFE
+#define SD_HOST_SPECv2_VERS 0x2401
+
+#define SDHC_REGISTERS_MAP_SIZE 0x100
+#define SDHC_INSERTION_DELAY (get_ticks_per_sec())
+#define SDHC_TRANSFER_DELAY 100
+#define SDHC_ADMA_DESCS_PER_DELAY 5
+#define SDHC_CMD_RESPONSE (3 << 0)
+
+enum {
+ sdhc_not_stopped = 0, /* normal SDHC state */
+ sdhc_gap_read = 1, /* SDHC stopped at block gap during read operation */
+ sdhc_gap_write = 2 /* SDHC stopped at block gap during write operation */
+};
+
+/* SD/MMC host controller state */
+typedef struct SDHCIState {
+ union {
+ PCIDevice pcidev;
+ SysBusDevice busdev;
+ };
+ SDState *card;
+ MemoryRegion iomem;
+
+ QEMUTimer *insert_timer; /* timer for 'changing' sd card. */
+ QEMUTimer *transfer_timer;
+ qemu_irq eject_cb;
+ qemu_irq ro_cb;
+ qemu_irq irq;
+
+ uint32_t sdmasysad; /* SDMA System Address register */
+ uint16_t blksize; /* Host DMA Buff Boundary and Transfer BlkSize Reg */
+ uint16_t blkcnt; /* Blocks count for current transfer */
+ uint32_t argument; /* Command Argument Register */
+ uint16_t trnmod; /* Transfer Mode Setting Register */
+ uint16_t cmdreg; /* Command Register */
+ uint32_t rspreg[4]; /* Response Registers 0-3 */
+ uint32_t prnsts; /* Present State Register */
+ uint8_t hostctl; /* Host Control Register */
+ uint8_t pwrcon; /* Power control Register */
+ uint8_t blkgap; /* Block Gap Control Register */
+ uint8_t wakcon; /* WakeUp Control Register */
+ uint16_t clkcon; /* Clock control Register */
+ uint8_t timeoutcon; /* Timeout Control Register */
+ uint8_t admaerr; /* ADMA Error Status Register */
+ uint16_t norintsts; /* Normal Interrupt Status Register */
+ uint16_t errintsts; /* Error Interrupt Status Register */
+ uint16_t norintstsen; /* Normal Interrupt Status Enable Register */
+ uint16_t errintstsen; /* Error Interrupt Status Enable Register */
+ uint16_t norintsigen; /* Normal Interrupt Signal Enable Register */
+ uint16_t errintsigen; /* Error Interrupt Signal Enable Register */
+ uint16_t acmd12errsts; /* Auto CMD12 error status register */
+ uint64_t admasysaddr; /* ADMA System Address Register */
+
+ uint32_t capareg; /* Capabilities Register */
+ uint32_t maxcurr; /* Maximum Current Capabilities Register */
+ uint8_t *fifo_buffer; /* SD host i/o FIFO buffer */
+ uint32_t buf_maxsz;
+ uint16_t data_count; /* current element in FIFO buffer */
+ uint8_t stopped_state;/* Current SDHC state */
+ /* Buffer Data Port Register - virtual access point to R and W buffers */
+ /* Software Reset Register - always reads as 0 */
+ /* Force Event Auto CMD12 Error Interrupt Reg - write only */
+ /* Force Event Error Interrupt Register- write only */
+ /* RO Host Controller Version Register always reads as 0x2401 */
+} SDHCIState;
+
+extern const VMStateDescription sdhci_vmstate;
+
+#define TYPE_PCI_SDHCI "sdhci-pci"
+#define PCI_SDHCI(obj) OBJECT_CHECK(SDHCIState, (obj), TYPE_PCI_SDHCI)
+
+#define TYPE_SYSBUS_SDHCI "generic-sdhci"
+#define SYSBUS_SDHCI(obj) \
+ OBJECT_CHECK(SDHCIState, (obj), TYPE_SYSBUS_SDHCI)
+
+#endif /* SDHCI_H */
diff --git a/hw/sd/ssi-sd.c b/hw/sd/ssi-sd.c
new file mode 100644
index 00000000..e4b2d4f8
--- /dev/null
+++ b/hw/sd/ssi-sd.c
@@ -0,0 +1,289 @@
+/*
+ * SSI to SD card adapter.
+ *
+ * Copyright (c) 2007-2009 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/ssi.h"
+#include "hw/sd.h"
+
+//#define DEBUG_SSI_SD 1
+
+#ifdef DEBUG_SSI_SD
+#define DPRINTF(fmt, ...) \
+do { printf("ssi_sd: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "ssi_sd: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "ssi_sd: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+typedef enum {
+ SSI_SD_CMD,
+ SSI_SD_CMDARG,
+ SSI_SD_RESPONSE,
+ SSI_SD_DATA_START,
+ SSI_SD_DATA_READ,
+} ssi_sd_mode;
+
+typedef struct {
+ SSISlave ssidev;
+ ssi_sd_mode mode;
+ int cmd;
+ uint8_t cmdarg[4];
+ uint8_t response[5];
+ int arglen;
+ int response_pos;
+ int stopping;
+ SDState *sd;
+} ssi_sd_state;
+
+/* State word bits. */
+#define SSI_SDR_LOCKED 0x0001
+#define SSI_SDR_WP_ERASE 0x0002
+#define SSI_SDR_ERROR 0x0004
+#define SSI_SDR_CC_ERROR 0x0008
+#define SSI_SDR_ECC_FAILED 0x0010
+#define SSI_SDR_WP_VIOLATION 0x0020
+#define SSI_SDR_ERASE_PARAM 0x0040
+#define SSI_SDR_OUT_OF_RANGE 0x0080
+#define SSI_SDR_IDLE 0x0100
+#define SSI_SDR_ERASE_RESET 0x0200
+#define SSI_SDR_ILLEGAL_COMMAND 0x0400
+#define SSI_SDR_COM_CRC_ERROR 0x0800
+#define SSI_SDR_ERASE_SEQ_ERROR 0x1000
+#define SSI_SDR_ADDRESS_ERROR 0x2000
+#define SSI_SDR_PARAMETER_ERROR 0x4000
+
+static uint32_t ssi_sd_transfer(SSISlave *dev, uint32_t val)
+{
+ ssi_sd_state *s = FROM_SSI_SLAVE(ssi_sd_state, dev);
+
+ /* Special case: allow CMD12 (STOP TRANSMISSION) while reading data. */
+ if (s->mode == SSI_SD_DATA_READ && val == 0x4d) {
+ s->mode = SSI_SD_CMD;
+ /* There must be at least one byte delay before the card responds. */
+ s->stopping = 1;
+ }
+
+ switch (s->mode) {
+ case SSI_SD_CMD:
+ if (val == 0xff) {
+ DPRINTF("NULL command\n");
+ return 0xff;
+ }
+ s->cmd = val & 0x3f;
+ s->mode = SSI_SD_CMDARG;
+ s->arglen = 0;
+ return 0xff;
+ case SSI_SD_CMDARG:
+ if (s->arglen == 4) {
+ SDRequest request;
+ uint8_t longresp[16];
+ /* FIXME: Check CRC. */
+ request.cmd = s->cmd;
+ request.arg = (s->cmdarg[0] << 24) | (s->cmdarg[1] << 16)
+ | (s->cmdarg[2] << 8) | s->cmdarg[3];
+ DPRINTF("CMD%d arg 0x%08x\n", s->cmd, request.arg);
+ s->arglen = sd_do_command(s->sd, &request, longresp);
+ if (s->arglen <= 0) {
+ s->arglen = 1;
+ s->response[0] = 4;
+ DPRINTF("SD command failed\n");
+ } else if (s->cmd == 58) {
+ /* CMD58 returns R3 response (OCR) */
+ DPRINTF("Returned OCR\n");
+ s->arglen = 5;
+ s->response[0] = 1;
+ memcpy(&s->response[1], longresp, 4);
+ } else if (s->arglen != 4) {
+ BADF("Unexpected response to cmd %d\n", s->cmd);
+ /* Illegal command is about as near as we can get. */
+ s->arglen = 1;
+ s->response[0] = 4;
+ } else {
+ /* All other commands return status. */
+ uint32_t cardstatus;
+ uint16_t status;
+ /* CMD13 returns a 2-byte statuse work. Other commands
+ only return the first byte. */
+ s->arglen = (s->cmd == 13) ? 2 : 1;
+ cardstatus = (longresp[0] << 24) | (longresp[1] << 16)
+ | (longresp[2] << 8) | longresp[3];
+ status = 0;
+ if (((cardstatus >> 9) & 0xf) < 4)
+ status |= SSI_SDR_IDLE;
+ if (cardstatus & ERASE_RESET)
+ status |= SSI_SDR_ERASE_RESET;
+ if (cardstatus & ILLEGAL_COMMAND)
+ status |= SSI_SDR_ILLEGAL_COMMAND;
+ if (cardstatus & COM_CRC_ERROR)
+ status |= SSI_SDR_COM_CRC_ERROR;
+ if (cardstatus & ERASE_SEQ_ERROR)
+ status |= SSI_SDR_ERASE_SEQ_ERROR;
+ if (cardstatus & ADDRESS_ERROR)
+ status |= SSI_SDR_ADDRESS_ERROR;
+ if (cardstatus & CARD_IS_LOCKED)
+ status |= SSI_SDR_LOCKED;
+ if (cardstatus & (LOCK_UNLOCK_FAILED | WP_ERASE_SKIP))
+ status |= SSI_SDR_WP_ERASE;
+ if (cardstatus & SD_ERROR)
+ status |= SSI_SDR_ERROR;
+ if (cardstatus & CC_ERROR)
+ status |= SSI_SDR_CC_ERROR;
+ if (cardstatus & CARD_ECC_FAILED)
+ status |= SSI_SDR_ECC_FAILED;
+ if (cardstatus & WP_VIOLATION)
+ status |= SSI_SDR_WP_VIOLATION;
+ if (cardstatus & ERASE_PARAM)
+ status |= SSI_SDR_ERASE_PARAM;
+ if (cardstatus & (OUT_OF_RANGE | CID_CSD_OVERWRITE))
+ status |= SSI_SDR_OUT_OF_RANGE;
+ /* ??? Don't know what Parameter Error really means, so
+ assume it's set if the second byte is nonzero. */
+ if (status & 0xff)
+ status |= SSI_SDR_PARAMETER_ERROR;
+ s->response[0] = status >> 8;
+ s->response[1] = status;
+ DPRINTF("Card status 0x%02x\n", status);
+ }
+ s->mode = SSI_SD_RESPONSE;
+ s->response_pos = 0;
+ } else {
+ s->cmdarg[s->arglen++] = val;
+ }
+ return 0xff;
+ case SSI_SD_RESPONSE:
+ if (s->stopping) {
+ s->stopping = 0;
+ return 0xff;
+ }
+ if (s->response_pos < s->arglen) {
+ DPRINTF("Response 0x%02x\n", s->response[s->response_pos]);
+ return s->response[s->response_pos++];
+ }
+ if (sd_data_ready(s->sd)) {
+ DPRINTF("Data read\n");
+ s->mode = SSI_SD_DATA_START;
+ } else {
+ DPRINTF("End of command\n");
+ s->mode = SSI_SD_CMD;
+ }
+ return 0xff;
+ case SSI_SD_DATA_START:
+ DPRINTF("Start read block\n");
+ s->mode = SSI_SD_DATA_READ;
+ return 0xfe;
+ case SSI_SD_DATA_READ:
+ val = sd_read_data(s->sd);
+ if (!sd_data_ready(s->sd)) {
+ DPRINTF("Data read end\n");
+ s->mode = SSI_SD_CMD;
+ }
+ return val;
+ }
+ /* Should never happen. */
+ return 0xff;
+}
+
+static void ssi_sd_save(QEMUFile *f, void *opaque)
+{
+ SSISlave *ss = SSI_SLAVE(opaque);
+ ssi_sd_state *s = (ssi_sd_state *)opaque;
+ int i;
+
+ qemu_put_be32(f, s->mode);
+ qemu_put_be32(f, s->cmd);
+ for (i = 0; i < 4; i++)
+ qemu_put_be32(f, s->cmdarg[i]);
+ for (i = 0; i < 5; i++)
+ qemu_put_be32(f, s->response[i]);
+ qemu_put_be32(f, s->arglen);
+ qemu_put_be32(f, s->response_pos);
+ qemu_put_be32(f, s->stopping);
+
+ qemu_put_be32(f, ss->cs);
+}
+
+static int ssi_sd_load(QEMUFile *f, void *opaque, int version_id)
+{
+ SSISlave *ss = SSI_SLAVE(opaque);
+ ssi_sd_state *s = (ssi_sd_state *)opaque;
+ int i;
+
+ if (version_id != 1)
+ return -EINVAL;
+
+ s->mode = qemu_get_be32(f);
+ s->cmd = qemu_get_be32(f);
+ for (i = 0; i < 4; i++)
+ s->cmdarg[i] = qemu_get_be32(f);
+ for (i = 0; i < 5; i++)
+ s->response[i] = qemu_get_be32(f);
+ s->arglen = qemu_get_be32(f);
+ if (s->mode == SSI_SD_CMDARG &&
+ (s->arglen < 0 || s->arglen >= ARRAY_SIZE(s->cmdarg))) {
+ return -EINVAL;
+ }
+ s->response_pos = qemu_get_be32(f);
+ s->stopping = qemu_get_be32(f);
+ if (s->mode == SSI_SD_RESPONSE &&
+ (s->response_pos < 0 || s->response_pos >= ARRAY_SIZE(s->response) ||
+ (!s->stopping && s->arglen > ARRAY_SIZE(s->response)))) {
+ return -EINVAL;
+ }
+
+ ss->cs = qemu_get_be32(f);
+
+ return 0;
+}
+
+static int ssi_sd_init(SSISlave *d)
+{
+ DeviceState *dev = DEVICE(d);
+ ssi_sd_state *s = FROM_SSI_SLAVE(ssi_sd_state, d);
+ DriveInfo *dinfo;
+
+ s->mode = SSI_SD_CMD;
+ /* FIXME use a qdev drive property instead of drive_get_next() */
+ dinfo = drive_get_next(IF_SD);
+ s->sd = sd_init(dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, true);
+ if (s->sd == NULL) {
+ return -1;
+ }
+ register_savevm(dev, "ssi_sd", -1, 1, ssi_sd_save, ssi_sd_load, s);
+ return 0;
+}
+
+static void ssi_sd_class_init(ObjectClass *klass, void *data)
+{
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = ssi_sd_init;
+ k->transfer = ssi_sd_transfer;
+ k->cs_polarity = SSI_CS_LOW;
+}
+
+static const TypeInfo ssi_sd_info = {
+ .name = "ssi-sd",
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(ssi_sd_state),
+ .class_init = ssi_sd_class_init,
+};
+
+static void ssi_sd_register_types(void)
+{
+ type_register_static(&ssi_sd_info);
+}
+
+type_init(ssi_sd_register_types)
diff --git a/hw/sh4/Makefile.objs b/hw/sh4/Makefile.objs
new file mode 100644
index 00000000..2393702c
--- /dev/null
+++ b/hw/sh4/Makefile.objs
@@ -0,0 +1,4 @@
+obj-y += shix.o r2d.o
+
+obj-y += sh7750.o sh7750_regnames.o
+obj-y += sh_pci.o
diff --git a/hw/sh4/r2d.c b/hw/sh4/r2d.c
new file mode 100644
index 00000000..5e22ed79
--- /dev/null
+++ b/hw/sh4/r2d.c
@@ -0,0 +1,368 @@
+/*
+ * Renesas SH7751R R2D-PLUS emulation
+ *
+ * Copyright (c) 2007 Magnus Damm
+ * Copyright (c) 2008 Paul Mundt
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "hw/devices.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/pci/pci.h"
+#include "net/net.h"
+#include "sh7750_regs.h"
+#include "hw/ide.h"
+#include "hw/loader.h"
+#include "hw/usb.h"
+#include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+
+#define FLASH_BASE 0x00000000
+#define FLASH_SIZE 0x02000000
+
+#define SDRAM_BASE 0x0c000000 /* Physical location of SDRAM: Area 3 */
+#define SDRAM_SIZE 0x04000000
+
+#define SM501_VRAM_SIZE 0x800000
+
+#define BOOT_PARAMS_OFFSET 0x0010000
+/* CONFIG_BOOT_LINK_OFFSET of Linux kernel */
+#define LINUX_LOAD_OFFSET 0x0800000
+#define INITRD_LOAD_OFFSET 0x1800000
+
+#define PA_IRLMSK 0x00
+#define PA_POWOFF 0x30
+#define PA_VERREG 0x32
+#define PA_OUTPORT 0x36
+
+typedef struct {
+ uint16_t bcr;
+ uint16_t irlmsk;
+ uint16_t irlmon;
+ uint16_t cfctl;
+ uint16_t cfpow;
+ uint16_t dispctl;
+ uint16_t sdmpow;
+ uint16_t rtcce;
+ uint16_t pcicd;
+ uint16_t voyagerrts;
+ uint16_t cfrst;
+ uint16_t admrts;
+ uint16_t extrst;
+ uint16_t cfcdintclr;
+ uint16_t keyctlclr;
+ uint16_t pad0;
+ uint16_t pad1;
+ uint16_t verreg;
+ uint16_t inport;
+ uint16_t outport;
+ uint16_t bverreg;
+
+/* output pin */
+ qemu_irq irl;
+ MemoryRegion iomem;
+} r2d_fpga_t;
+
+enum r2d_fpga_irq {
+ PCI_INTD, CF_IDE, CF_CD, PCI_INTC, SM501, KEY, RTC_A, RTC_T,
+ SDCARD, PCI_INTA, PCI_INTB, EXT, TP,
+ NR_IRQS
+};
+
+static const struct { short irl; uint16_t msk; } irqtab[NR_IRQS] = {
+ [CF_IDE] = { 1, 1<<9 },
+ [CF_CD] = { 2, 1<<8 },
+ [PCI_INTA] = { 9, 1<<14 },
+ [PCI_INTB] = { 10, 1<<13 },
+ [PCI_INTC] = { 3, 1<<12 },
+ [PCI_INTD] = { 0, 1<<11 },
+ [SM501] = { 4, 1<<10 },
+ [KEY] = { 5, 1<<6 },
+ [RTC_A] = { 6, 1<<5 },
+ [RTC_T] = { 7, 1<<4 },
+ [SDCARD] = { 8, 1<<7 },
+ [EXT] = { 11, 1<<0 },
+ [TP] = { 12, 1<<15 },
+};
+
+static void update_irl(r2d_fpga_t *fpga)
+{
+ int i, irl = 15;
+ for (i = 0; i < NR_IRQS; i++)
+ if (fpga->irlmon & fpga->irlmsk & irqtab[i].msk)
+ if (irqtab[i].irl < irl)
+ irl = irqtab[i].irl;
+ qemu_set_irq(fpga->irl, irl ^ 15);
+}
+
+static void r2d_fpga_irq_set(void *opaque, int n, int level)
+{
+ r2d_fpga_t *fpga = opaque;
+ if (level)
+ fpga->irlmon |= irqtab[n].msk;
+ else
+ fpga->irlmon &= ~irqtab[n].msk;
+ update_irl(fpga);
+}
+
+static uint64_t r2d_fpga_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ r2d_fpga_t *s = opaque;
+
+ switch (addr) {
+ case PA_IRLMSK:
+ return s->irlmsk;
+ case PA_OUTPORT:
+ return s->outport;
+ case PA_POWOFF:
+ return 0x00;
+ case PA_VERREG:
+ return 0x10;
+ }
+
+ return 0;
+}
+
+static void
+r2d_fpga_write(void *opaque, hwaddr addr, uint64_t value, unsigned int size)
+{
+ r2d_fpga_t *s = opaque;
+
+ switch (addr) {
+ case PA_IRLMSK:
+ s->irlmsk = value;
+ update_irl(s);
+ break;
+ case PA_OUTPORT:
+ s->outport = value;
+ break;
+ case PA_POWOFF:
+ if (value & 1) {
+ qemu_system_shutdown_request();
+ }
+ break;
+ case PA_VERREG:
+ /* Discard writes */
+ break;
+ }
+}
+
+static const MemoryRegionOps r2d_fpga_ops = {
+ .read = r2d_fpga_read,
+ .write = r2d_fpga_write,
+ .impl.min_access_size = 2,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static qemu_irq *r2d_fpga_init(MemoryRegion *sysmem,
+ hwaddr base, qemu_irq irl)
+{
+ r2d_fpga_t *s;
+
+ s = g_malloc0(sizeof(r2d_fpga_t));
+
+ s->irl = irl;
+
+ memory_region_init_io(&s->iomem, NULL, &r2d_fpga_ops, s, "r2d-fpga", 0x40);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+ return qemu_allocate_irqs(r2d_fpga_irq_set, s, NR_IRQS);
+}
+
+typedef struct ResetData {
+ SuperHCPU *cpu;
+ uint32_t vector;
+} ResetData;
+
+static void main_cpu_reset(void *opaque)
+{
+ ResetData *s = (ResetData *)opaque;
+ CPUSH4State *env = &s->cpu->env;
+
+ cpu_reset(CPU(s->cpu));
+ env->pc = s->vector;
+}
+
+static struct QEMU_PACKED
+{
+ int mount_root_rdonly;
+ int ramdisk_flags;
+ int orig_root_dev;
+ int loader_type;
+ int initrd_start;
+ int initrd_size;
+
+ char pad[232];
+
+ char kernel_cmdline[256];
+} boot_params;
+
+static void r2d_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ SuperHCPU *cpu;
+ CPUSH4State *env;
+ ResetData *reset_info;
+ struct SH7750State *s;
+ MemoryRegion *sdram = g_new(MemoryRegion, 1);
+ qemu_irq *irq;
+ DriveInfo *dinfo;
+ int i;
+ DeviceState *dev;
+ SysBusDevice *busdev;
+ MemoryRegion *address_space_mem = get_system_memory();
+ PCIBus *pci_bus;
+
+ if (cpu_model == NULL) {
+ cpu_model = "SH7751R";
+ }
+
+ cpu = cpu_sh4_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ reset_info = g_malloc0(sizeof(ResetData));
+ reset_info->cpu = cpu;
+ reset_info->vector = env->pc;
+ qemu_register_reset(main_cpu_reset, reset_info);
+
+ /* Allocate memory space */
+ memory_region_init_ram(sdram, NULL, "r2d.sdram", SDRAM_SIZE, &error_abort);
+ vmstate_register_ram_global(sdram);
+ memory_region_add_subregion(address_space_mem, SDRAM_BASE, sdram);
+ /* Register peripherals */
+ s = sh7750_init(cpu, address_space_mem);
+ irq = r2d_fpga_init(address_space_mem, 0x04000000, sh7750_irl(s));
+
+ dev = qdev_create(NULL, "sh_pci");
+ busdev = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ pci_bus = PCI_BUS(qdev_get_child_bus(dev, "pci"));
+ sysbus_mmio_map(busdev, 0, P4ADDR(0x1e200000));
+ sysbus_mmio_map(busdev, 1, A7ADDR(0x1e200000));
+ sysbus_connect_irq(busdev, 0, irq[PCI_INTA]);
+ sysbus_connect_irq(busdev, 1, irq[PCI_INTB]);
+ sysbus_connect_irq(busdev, 2, irq[PCI_INTC]);
+ sysbus_connect_irq(busdev, 3, irq[PCI_INTD]);
+
+ sm501_init(address_space_mem, 0x10000000, SM501_VRAM_SIZE,
+ irq[SM501], serial_hds[2]);
+
+ /* onboard CF (True IDE mode, Master only). */
+ dinfo = drive_get(IF_IDE, 0, 0);
+ dev = qdev_create(NULL, "mmio-ide");
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(busdev, 0, irq[CF_IDE]);
+ qdev_prop_set_uint32(dev, "shift", 1);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(busdev, 0, 0x14001000);
+ sysbus_mmio_map(busdev, 1, 0x1400080c);
+ mmio_ide_init_drives(dev, dinfo, NULL);
+
+ /* onboard flash memory */
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ pflash_cfi02_register(0x0, NULL, "r2d.flash", FLASH_SIZE,
+ dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
+ (16 * 1024), FLASH_SIZE >> 16,
+ 1, 4, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x555, 0x2aa, 0);
+
+ /* NIC: rtl8139 on-board, and 2 slots. */
+ for (i = 0; i < nb_nics; i++)
+ pci_nic_init_nofail(&nd_table[i], pci_bus,
+ "rtl8139", i==0 ? "2" : NULL);
+
+ /* USB keyboard */
+ usb_create_simple(usb_bus_find(-1), "usb-kbd");
+
+ /* Todo: register on board registers */
+ memset(&boot_params, 0, sizeof(boot_params));
+
+ if (kernel_filename) {
+ int kernel_size;
+
+ kernel_size = load_image_targphys(kernel_filename,
+ SDRAM_BASE + LINUX_LOAD_OFFSET,
+ INITRD_LOAD_OFFSET - LINUX_LOAD_OFFSET);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n", kernel_filename);
+ exit(1);
+ }
+
+ /* initialization which should be done by firmware */
+ address_space_stl(&address_space_memory, SH7750_BCR1, 1 << 3,
+ MEMTXATTRS_UNSPECIFIED, NULL); /* cs3 SDRAM */
+ address_space_stw(&address_space_memory, SH7750_BCR2, 3 << (3 * 2),
+ MEMTXATTRS_UNSPECIFIED, NULL); /* cs3 32bit */
+ reset_info->vector = (SDRAM_BASE + LINUX_LOAD_OFFSET) | 0xa0000000; /* Start from P2 area */
+ }
+
+ if (initrd_filename) {
+ int initrd_size;
+
+ initrd_size = load_image_targphys(initrd_filename,
+ SDRAM_BASE + INITRD_LOAD_OFFSET,
+ SDRAM_SIZE - INITRD_LOAD_OFFSET);
+
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initrd '%s'\n", initrd_filename);
+ exit(1);
+ }
+
+ /* initialization which should be done by firmware */
+ boot_params.loader_type = 1;
+ boot_params.initrd_start = INITRD_LOAD_OFFSET;
+ boot_params.initrd_size = initrd_size;
+ }
+
+ if (kernel_cmdline) {
+ /* I see no evidence that this .kernel_cmdline buffer requires
+ NUL-termination, so using strncpy should be ok. */
+ strncpy(boot_params.kernel_cmdline, kernel_cmdline,
+ sizeof(boot_params.kernel_cmdline));
+ }
+
+ rom_add_blob_fixed("boot_params", &boot_params, sizeof(boot_params),
+ SDRAM_BASE + BOOT_PARAMS_OFFSET);
+}
+
+static QEMUMachine r2d_machine = {
+ .name = "r2d",
+ .desc = "r2d-plus board",
+ .init = r2d_init,
+};
+
+static void r2d_machine_init(void)
+{
+ qemu_register_machine(&r2d_machine);
+}
+
+machine_init(r2d_machine_init);
diff --git a/hw/sh4/sh7750.c b/hw/sh4/sh7750.c
new file mode 100644
index 00000000..5dda5de3
--- /dev/null
+++ b/hw/sh4/sh7750.c
@@ -0,0 +1,842 @@
+/*
+ * SH7750 device
+ *
+ * Copyright (c) 2007 Magnus Damm
+ * Copyright (c) 2005 Samuel Tardieu
+ *
+ * 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.
+ */
+#include <stdio.h>
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "sysemu/sysemu.h"
+#include "sh7750_regs.h"
+#include "sh7750_regnames.h"
+#include "hw/sh4/sh_intc.h"
+#include "cpu.h"
+#include "exec/address-spaces.h"
+
+#define NB_DEVICES 4
+
+typedef struct SH7750State {
+ MemoryRegion iomem;
+ MemoryRegion iomem_1f0;
+ MemoryRegion iomem_ff0;
+ MemoryRegion iomem_1f8;
+ MemoryRegion iomem_ff8;
+ MemoryRegion iomem_1fc;
+ MemoryRegion iomem_ffc;
+ MemoryRegion mmct_iomem;
+ /* CPU */
+ SuperHCPU *cpu;
+ /* Peripheral frequency in Hz */
+ uint32_t periph_freq;
+ /* SDRAM controller */
+ uint32_t bcr1;
+ uint16_t bcr2;
+ uint16_t bcr3;
+ uint32_t bcr4;
+ uint16_t rfcr;
+ /* PCMCIA controller */
+ uint16_t pcr;
+ /* IO ports */
+ uint16_t gpioic;
+ uint32_t pctra;
+ uint32_t pctrb;
+ uint16_t portdira; /* Cached */
+ uint16_t portpullupa; /* Cached */
+ uint16_t portdirb; /* Cached */
+ uint16_t portpullupb; /* Cached */
+ uint16_t pdtra;
+ uint16_t pdtrb;
+ uint16_t periph_pdtra; /* Imposed by the peripherals */
+ uint16_t periph_portdira; /* Direction seen from the peripherals */
+ uint16_t periph_pdtrb; /* Imposed by the peripherals */
+ uint16_t periph_portdirb; /* Direction seen from the peripherals */
+ sh7750_io_device *devices[NB_DEVICES]; /* External peripherals */
+
+ /* Cache */
+ uint32_t ccr;
+
+ struct intc_desc intc;
+} SH7750State;
+
+static inline int has_bcr3_and_bcr4(SH7750State * s)
+{
+ return s->cpu->env.features & SH_FEATURE_BCR3_AND_BCR4;
+}
+/**********************************************************************
+ I/O ports
+**********************************************************************/
+
+int sh7750_register_io_device(SH7750State * s, sh7750_io_device * device)
+{
+ int i;
+
+ for (i = 0; i < NB_DEVICES; i++) {
+ if (s->devices[i] == NULL) {
+ s->devices[i] = device;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static uint16_t portdir(uint32_t v)
+{
+#define EVENPORTMASK(n) ((v & (1<<((n)<<1))) >> (n))
+ return
+ EVENPORTMASK(15) | EVENPORTMASK(14) | EVENPORTMASK(13) |
+ EVENPORTMASK(12) | EVENPORTMASK(11) | EVENPORTMASK(10) |
+ EVENPORTMASK(9) | EVENPORTMASK(8) | EVENPORTMASK(7) |
+ EVENPORTMASK(6) | EVENPORTMASK(5) | EVENPORTMASK(4) |
+ EVENPORTMASK(3) | EVENPORTMASK(2) | EVENPORTMASK(1) |
+ EVENPORTMASK(0);
+}
+
+static uint16_t portpullup(uint32_t v)
+{
+#define ODDPORTMASK(n) ((v & (1<<(((n)<<1)+1))) >> (n))
+ return
+ ODDPORTMASK(15) | ODDPORTMASK(14) | ODDPORTMASK(13) |
+ ODDPORTMASK(12) | ODDPORTMASK(11) | ODDPORTMASK(10) |
+ ODDPORTMASK(9) | ODDPORTMASK(8) | ODDPORTMASK(7) | ODDPORTMASK(6) |
+ ODDPORTMASK(5) | ODDPORTMASK(4) | ODDPORTMASK(3) | ODDPORTMASK(2) |
+ ODDPORTMASK(1) | ODDPORTMASK(0);
+}
+
+static uint16_t porta_lines(SH7750State * s)
+{
+ return (s->portdira & s->pdtra) | /* CPU */
+ (s->periph_portdira & s->periph_pdtra) | /* Peripherals */
+ (~(s->portdira | s->periph_portdira) & s->portpullupa); /* Pullups */
+}
+
+static uint16_t portb_lines(SH7750State * s)
+{
+ return (s->portdirb & s->pdtrb) | /* CPU */
+ (s->periph_portdirb & s->periph_pdtrb) | /* Peripherals */
+ (~(s->portdirb | s->periph_portdirb) & s->portpullupb); /* Pullups */
+}
+
+static void gen_port_interrupts(SH7750State * s)
+{
+ /* XXXXX interrupts not generated */
+}
+
+static void porta_changed(SH7750State * s, uint16_t prev)
+{
+ uint16_t currenta, changes;
+ int i, r = 0;
+
+#if 0
+ fprintf(stderr, "porta changed from 0x%04x to 0x%04x\n",
+ prev, porta_lines(s));
+ fprintf(stderr, "pdtra=0x%04x, pctra=0x%08x\n", s->pdtra, s->pctra);
+#endif
+ currenta = porta_lines(s);
+ if (currenta == prev)
+ return;
+ changes = currenta ^ prev;
+
+ for (i = 0; i < NB_DEVICES; i++) {
+ if (s->devices[i] && (s->devices[i]->portamask_trigger & changes)) {
+ r |= s->devices[i]->port_change_cb(currenta, portb_lines(s),
+ &s->periph_pdtra,
+ &s->periph_portdira,
+ &s->periph_pdtrb,
+ &s->periph_portdirb);
+ }
+ }
+
+ if (r)
+ gen_port_interrupts(s);
+}
+
+static void portb_changed(SH7750State * s, uint16_t prev)
+{
+ uint16_t currentb, changes;
+ int i, r = 0;
+
+ currentb = portb_lines(s);
+ if (currentb == prev)
+ return;
+ changes = currentb ^ prev;
+
+ for (i = 0; i < NB_DEVICES; i++) {
+ if (s->devices[i] && (s->devices[i]->portbmask_trigger & changes)) {
+ r |= s->devices[i]->port_change_cb(portb_lines(s), currentb,
+ &s->periph_pdtra,
+ &s->periph_portdira,
+ &s->periph_pdtrb,
+ &s->periph_portdirb);
+ }
+ }
+
+ if (r)
+ gen_port_interrupts(s);
+}
+
+/**********************************************************************
+ Memory
+**********************************************************************/
+
+static void error_access(const char *kind, hwaddr addr)
+{
+ fprintf(stderr, "%s to %s (0x" TARGET_FMT_plx ") not supported\n",
+ kind, regname(addr), addr);
+}
+
+static void ignore_access(const char *kind, hwaddr addr)
+{
+ fprintf(stderr, "%s to %s (0x" TARGET_FMT_plx ") ignored\n",
+ kind, regname(addr), addr);
+}
+
+static uint32_t sh7750_mem_readb(void *opaque, hwaddr addr)
+{
+ switch (addr) {
+ default:
+ error_access("byte read", addr);
+ abort();
+ }
+}
+
+static uint32_t sh7750_mem_readw(void *opaque, hwaddr addr)
+{
+ SH7750State *s = opaque;
+
+ switch (addr) {
+ case SH7750_BCR2_A7:
+ return s->bcr2;
+ case SH7750_BCR3_A7:
+ if(!has_bcr3_and_bcr4(s))
+ error_access("word read", addr);
+ return s->bcr3;
+ case SH7750_FRQCR_A7:
+ return 0;
+ case SH7750_PCR_A7:
+ return s->pcr;
+ case SH7750_RFCR_A7:
+ fprintf(stderr,
+ "Read access to refresh count register, incrementing\n");
+ return s->rfcr++;
+ case SH7750_PDTRA_A7:
+ return porta_lines(s);
+ case SH7750_PDTRB_A7:
+ return portb_lines(s);
+ case SH7750_RTCOR_A7:
+ case SH7750_RTCNT_A7:
+ case SH7750_RTCSR_A7:
+ ignore_access("word read", addr);
+ return 0;
+ default:
+ error_access("word read", addr);
+ abort();
+ }
+}
+
+static uint32_t sh7750_mem_readl(void *opaque, hwaddr addr)
+{
+ SH7750State *s = opaque;
+ SuperHCPUClass *scc;
+
+ switch (addr) {
+ case SH7750_BCR1_A7:
+ return s->bcr1;
+ case SH7750_BCR4_A7:
+ if(!has_bcr3_and_bcr4(s))
+ error_access("long read", addr);
+ return s->bcr4;
+ case SH7750_WCR1_A7:
+ case SH7750_WCR2_A7:
+ case SH7750_WCR3_A7:
+ case SH7750_MCR_A7:
+ ignore_access("long read", addr);
+ return 0;
+ case SH7750_MMUCR_A7:
+ return s->cpu->env.mmucr;
+ case SH7750_PTEH_A7:
+ return s->cpu->env.pteh;
+ case SH7750_PTEL_A7:
+ return s->cpu->env.ptel;
+ case SH7750_TTB_A7:
+ return s->cpu->env.ttb;
+ case SH7750_TEA_A7:
+ return s->cpu->env.tea;
+ case SH7750_TRA_A7:
+ return s->cpu->env.tra;
+ case SH7750_EXPEVT_A7:
+ return s->cpu->env.expevt;
+ case SH7750_INTEVT_A7:
+ return s->cpu->env.intevt;
+ case SH7750_CCR_A7:
+ return s->ccr;
+ case 0x1f000030: /* Processor version */
+ scc = SUPERH_CPU_GET_CLASS(s->cpu);
+ return scc->pvr;
+ case 0x1f000040: /* Cache version */
+ scc = SUPERH_CPU_GET_CLASS(s->cpu);
+ return scc->cvr;
+ case 0x1f000044: /* Processor revision */
+ scc = SUPERH_CPU_GET_CLASS(s->cpu);
+ return scc->prr;
+ default:
+ error_access("long read", addr);
+ abort();
+ }
+}
+
+#define is_in_sdrmx(a, x) (a >= SH7750_SDMR ## x ## _A7 \
+ && a <= (SH7750_SDMR ## x ## _A7 + SH7750_SDMR ## x ## _REGNB))
+static void sh7750_mem_writeb(void *opaque, hwaddr addr,
+ uint32_t mem_value)
+{
+
+ if (is_in_sdrmx(addr, 2) || is_in_sdrmx(addr, 3)) {
+ ignore_access("byte write", addr);
+ return;
+ }
+
+ error_access("byte write", addr);
+ abort();
+}
+
+static void sh7750_mem_writew(void *opaque, hwaddr addr,
+ uint32_t mem_value)
+{
+ SH7750State *s = opaque;
+ uint16_t temp;
+
+ switch (addr) {
+ /* SDRAM controller */
+ case SH7750_BCR2_A7:
+ s->bcr2 = mem_value;
+ return;
+ case SH7750_BCR3_A7:
+ if(!has_bcr3_and_bcr4(s))
+ error_access("word write", addr);
+ s->bcr3 = mem_value;
+ return;
+ case SH7750_PCR_A7:
+ s->pcr = mem_value;
+ return;
+ case SH7750_RTCNT_A7:
+ case SH7750_RTCOR_A7:
+ case SH7750_RTCSR_A7:
+ ignore_access("word write", addr);
+ return;
+ /* IO ports */
+ case SH7750_PDTRA_A7:
+ temp = porta_lines(s);
+ s->pdtra = mem_value;
+ porta_changed(s, temp);
+ return;
+ case SH7750_PDTRB_A7:
+ temp = portb_lines(s);
+ s->pdtrb = mem_value;
+ portb_changed(s, temp);
+ return;
+ case SH7750_RFCR_A7:
+ fprintf(stderr, "Write access to refresh count register\n");
+ s->rfcr = mem_value;
+ return;
+ case SH7750_GPIOIC_A7:
+ s->gpioic = mem_value;
+ if (mem_value != 0) {
+ fprintf(stderr, "I/O interrupts not implemented\n");
+ abort();
+ }
+ return;
+ default:
+ error_access("word write", addr);
+ abort();
+ }
+}
+
+static void sh7750_mem_writel(void *opaque, hwaddr addr,
+ uint32_t mem_value)
+{
+ SH7750State *s = opaque;
+ uint16_t temp;
+
+ switch (addr) {
+ /* SDRAM controller */
+ case SH7750_BCR1_A7:
+ s->bcr1 = mem_value;
+ return;
+ case SH7750_BCR4_A7:
+ if(!has_bcr3_and_bcr4(s))
+ error_access("long write", addr);
+ s->bcr4 = mem_value;
+ return;
+ case SH7750_WCR1_A7:
+ case SH7750_WCR2_A7:
+ case SH7750_WCR3_A7:
+ case SH7750_MCR_A7:
+ ignore_access("long write", addr);
+ return;
+ /* IO ports */
+ case SH7750_PCTRA_A7:
+ temp = porta_lines(s);
+ s->pctra = mem_value;
+ s->portdira = portdir(mem_value);
+ s->portpullupa = portpullup(mem_value);
+ porta_changed(s, temp);
+ return;
+ case SH7750_PCTRB_A7:
+ temp = portb_lines(s);
+ s->pctrb = mem_value;
+ s->portdirb = portdir(mem_value);
+ s->portpullupb = portpullup(mem_value);
+ portb_changed(s, temp);
+ return;
+ case SH7750_MMUCR_A7:
+ if (mem_value & MMUCR_TI) {
+ cpu_sh4_invalidate_tlb(&s->cpu->env);
+ }
+ s->cpu->env.mmucr = mem_value & ~MMUCR_TI;
+ return;
+ case SH7750_PTEH_A7:
+ /* If asid changes, clear all registered tlb entries. */
+ if ((s->cpu->env.pteh & 0xff) != (mem_value & 0xff)) {
+ tlb_flush(CPU(s->cpu), 1);
+ }
+ s->cpu->env.pteh = mem_value;
+ return;
+ case SH7750_PTEL_A7:
+ s->cpu->env.ptel = mem_value;
+ return;
+ case SH7750_PTEA_A7:
+ s->cpu->env.ptea = mem_value & 0x0000000f;
+ return;
+ case SH7750_TTB_A7:
+ s->cpu->env.ttb = mem_value;
+ return;
+ case SH7750_TEA_A7:
+ s->cpu->env.tea = mem_value;
+ return;
+ case SH7750_TRA_A7:
+ s->cpu->env.tra = mem_value & 0x000007ff;
+ return;
+ case SH7750_EXPEVT_A7:
+ s->cpu->env.expevt = mem_value & 0x000007ff;
+ return;
+ case SH7750_INTEVT_A7:
+ s->cpu->env.intevt = mem_value & 0x000007ff;
+ return;
+ case SH7750_CCR_A7:
+ s->ccr = mem_value;
+ return;
+ default:
+ error_access("long write", addr);
+ abort();
+ }
+}
+
+static const MemoryRegionOps sh7750_mem_ops = {
+ .old_mmio = {
+ .read = {sh7750_mem_readb,
+ sh7750_mem_readw,
+ sh7750_mem_readl },
+ .write = {sh7750_mem_writeb,
+ sh7750_mem_writew,
+ sh7750_mem_writel },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* sh775x interrupt controller tables for sh_intc.c
+ * stolen from linux/arch/sh/kernel/cpu/sh4/setup-sh7750.c
+ */
+
+enum {
+ UNUSED = 0,
+
+ /* interrupt sources */
+ IRL_0, IRL_1, IRL_2, IRL_3, IRL_4, IRL_5, IRL_6, IRL_7,
+ IRL_8, IRL_9, IRL_A, IRL_B, IRL_C, IRL_D, IRL_E,
+ IRL0, IRL1, IRL2, IRL3,
+ HUDI, GPIOI,
+ DMAC_DMTE0, DMAC_DMTE1, DMAC_DMTE2, DMAC_DMTE3,
+ DMAC_DMTE4, DMAC_DMTE5, DMAC_DMTE6, DMAC_DMTE7,
+ DMAC_DMAE,
+ PCIC0_PCISERR, PCIC1_PCIERR, PCIC1_PCIPWDWN, PCIC1_PCIPWON,
+ PCIC1_PCIDMA0, PCIC1_PCIDMA1, PCIC1_PCIDMA2, PCIC1_PCIDMA3,
+ TMU3, TMU4, TMU0, TMU1, TMU2_TUNI, TMU2_TICPI,
+ RTC_ATI, RTC_PRI, RTC_CUI,
+ SCI1_ERI, SCI1_RXI, SCI1_TXI, SCI1_TEI,
+ SCIF_ERI, SCIF_RXI, SCIF_BRI, SCIF_TXI,
+ WDT,
+ REF_RCMI, REF_ROVI,
+
+ /* interrupt groups */
+ DMAC, PCIC1, TMU2, RTC, SCI1, SCIF, REF,
+ /* irl bundle */
+ IRL,
+
+ NR_SOURCES,
+};
+
+static struct intc_vect vectors[] = {
+ INTC_VECT(HUDI, 0x600), INTC_VECT(GPIOI, 0x620),
+ INTC_VECT(TMU0, 0x400), INTC_VECT(TMU1, 0x420),
+ INTC_VECT(TMU2_TUNI, 0x440), INTC_VECT(TMU2_TICPI, 0x460),
+ INTC_VECT(RTC_ATI, 0x480), INTC_VECT(RTC_PRI, 0x4a0),
+ INTC_VECT(RTC_CUI, 0x4c0),
+ INTC_VECT(SCI1_ERI, 0x4e0), INTC_VECT(SCI1_RXI, 0x500),
+ INTC_VECT(SCI1_TXI, 0x520), INTC_VECT(SCI1_TEI, 0x540),
+ INTC_VECT(SCIF_ERI, 0x700), INTC_VECT(SCIF_RXI, 0x720),
+ INTC_VECT(SCIF_BRI, 0x740), INTC_VECT(SCIF_TXI, 0x760),
+ INTC_VECT(WDT, 0x560),
+ INTC_VECT(REF_RCMI, 0x580), INTC_VECT(REF_ROVI, 0x5a0),
+};
+
+static struct intc_group groups[] = {
+ INTC_GROUP(TMU2, TMU2_TUNI, TMU2_TICPI),
+ INTC_GROUP(RTC, RTC_ATI, RTC_PRI, RTC_CUI),
+ INTC_GROUP(SCI1, SCI1_ERI, SCI1_RXI, SCI1_TXI, SCI1_TEI),
+ INTC_GROUP(SCIF, SCIF_ERI, SCIF_RXI, SCIF_BRI, SCIF_TXI),
+ INTC_GROUP(REF, REF_RCMI, REF_ROVI),
+};
+
+static struct intc_prio_reg prio_registers[] = {
+ { 0xffd00004, 0, 16, 4, /* IPRA */ { TMU0, TMU1, TMU2, RTC } },
+ { 0xffd00008, 0, 16, 4, /* IPRB */ { WDT, REF, SCI1, 0 } },
+ { 0xffd0000c, 0, 16, 4, /* IPRC */ { GPIOI, DMAC, SCIF, HUDI } },
+ { 0xffd00010, 0, 16, 4, /* IPRD */ { IRL0, IRL1, IRL2, IRL3 } },
+ { 0xfe080000, 0, 32, 4, /* INTPRI00 */ { 0, 0, 0, 0,
+ TMU4, TMU3,
+ PCIC1, PCIC0_PCISERR } },
+};
+
+/* SH7750, SH7750S, SH7751 and SH7091 all have 4-channel DMA controllers */
+
+static struct intc_vect vectors_dma4[] = {
+ INTC_VECT(DMAC_DMTE0, 0x640), INTC_VECT(DMAC_DMTE1, 0x660),
+ INTC_VECT(DMAC_DMTE2, 0x680), INTC_VECT(DMAC_DMTE3, 0x6a0),
+ INTC_VECT(DMAC_DMAE, 0x6c0),
+};
+
+static struct intc_group groups_dma4[] = {
+ INTC_GROUP(DMAC, DMAC_DMTE0, DMAC_DMTE1, DMAC_DMTE2,
+ DMAC_DMTE3, DMAC_DMAE),
+};
+
+/* SH7750R and SH7751R both have 8-channel DMA controllers */
+
+static struct intc_vect vectors_dma8[] = {
+ INTC_VECT(DMAC_DMTE0, 0x640), INTC_VECT(DMAC_DMTE1, 0x660),
+ INTC_VECT(DMAC_DMTE2, 0x680), INTC_VECT(DMAC_DMTE3, 0x6a0),
+ INTC_VECT(DMAC_DMTE4, 0x780), INTC_VECT(DMAC_DMTE5, 0x7a0),
+ INTC_VECT(DMAC_DMTE6, 0x7c0), INTC_VECT(DMAC_DMTE7, 0x7e0),
+ INTC_VECT(DMAC_DMAE, 0x6c0),
+};
+
+static struct intc_group groups_dma8[] = {
+ INTC_GROUP(DMAC, DMAC_DMTE0, DMAC_DMTE1, DMAC_DMTE2,
+ DMAC_DMTE3, DMAC_DMTE4, DMAC_DMTE5,
+ DMAC_DMTE6, DMAC_DMTE7, DMAC_DMAE),
+};
+
+/* SH7750R, SH7751 and SH7751R all have two extra timer channels */
+
+static struct intc_vect vectors_tmu34[] = {
+ INTC_VECT(TMU3, 0xb00), INTC_VECT(TMU4, 0xb80),
+};
+
+static struct intc_mask_reg mask_registers[] = {
+ { 0xfe080040, 0xfe080060, 32, /* INTMSK00 / INTMSKCLR00 */
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, TMU4, TMU3,
+ PCIC1_PCIERR, PCIC1_PCIPWDWN, PCIC1_PCIPWON,
+ PCIC1_PCIDMA0, PCIC1_PCIDMA1, PCIC1_PCIDMA2,
+ PCIC1_PCIDMA3, PCIC0_PCISERR } },
+};
+
+/* SH7750S, SH7750R, SH7751 and SH7751R all have IRLM priority registers */
+
+static struct intc_vect vectors_irlm[] = {
+ INTC_VECT(IRL0, 0x240), INTC_VECT(IRL1, 0x2a0),
+ INTC_VECT(IRL2, 0x300), INTC_VECT(IRL3, 0x360),
+};
+
+/* SH7751 and SH7751R both have PCI */
+
+static struct intc_vect vectors_pci[] = {
+ INTC_VECT(PCIC0_PCISERR, 0xa00), INTC_VECT(PCIC1_PCIERR, 0xae0),
+ INTC_VECT(PCIC1_PCIPWDWN, 0xac0), INTC_VECT(PCIC1_PCIPWON, 0xaa0),
+ INTC_VECT(PCIC1_PCIDMA0, 0xa80), INTC_VECT(PCIC1_PCIDMA1, 0xa60),
+ INTC_VECT(PCIC1_PCIDMA2, 0xa40), INTC_VECT(PCIC1_PCIDMA3, 0xa20),
+};
+
+static struct intc_group groups_pci[] = {
+ INTC_GROUP(PCIC1, PCIC1_PCIERR, PCIC1_PCIPWDWN, PCIC1_PCIPWON,
+ PCIC1_PCIDMA0, PCIC1_PCIDMA1, PCIC1_PCIDMA2, PCIC1_PCIDMA3),
+};
+
+static struct intc_vect vectors_irl[] = {
+ INTC_VECT(IRL_0, 0x200),
+ INTC_VECT(IRL_1, 0x220),
+ INTC_VECT(IRL_2, 0x240),
+ INTC_VECT(IRL_3, 0x260),
+ INTC_VECT(IRL_4, 0x280),
+ INTC_VECT(IRL_5, 0x2a0),
+ INTC_VECT(IRL_6, 0x2c0),
+ INTC_VECT(IRL_7, 0x2e0),
+ INTC_VECT(IRL_8, 0x300),
+ INTC_VECT(IRL_9, 0x320),
+ INTC_VECT(IRL_A, 0x340),
+ INTC_VECT(IRL_B, 0x360),
+ INTC_VECT(IRL_C, 0x380),
+ INTC_VECT(IRL_D, 0x3a0),
+ INTC_VECT(IRL_E, 0x3c0),
+};
+
+static struct intc_group groups_irl[] = {
+ INTC_GROUP(IRL, IRL_0, IRL_1, IRL_2, IRL_3, IRL_4, IRL_5, IRL_6,
+ IRL_7, IRL_8, IRL_9, IRL_A, IRL_B, IRL_C, IRL_D, IRL_E),
+};
+
+/**********************************************************************
+ Memory mapped cache and TLB
+**********************************************************************/
+
+#define MM_REGION_MASK 0x07000000
+#define MM_ICACHE_ADDR (0)
+#define MM_ICACHE_DATA (1)
+#define MM_ITLB_ADDR (2)
+#define MM_ITLB_DATA (3)
+#define MM_OCACHE_ADDR (4)
+#define MM_OCACHE_DATA (5)
+#define MM_UTLB_ADDR (6)
+#define MM_UTLB_DATA (7)
+#define MM_REGION_TYPE(addr) ((addr & MM_REGION_MASK) >> 24)
+
+static uint64_t invalid_read(void *opaque, hwaddr addr)
+{
+ abort();
+
+ return 0;
+}
+
+static uint64_t sh7750_mmct_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SH7750State *s = opaque;
+ uint32_t ret = 0;
+
+ if (size != 4) {
+ return invalid_read(opaque, addr);
+ }
+
+ switch (MM_REGION_TYPE(addr)) {
+ case MM_ICACHE_ADDR:
+ case MM_ICACHE_DATA:
+ /* do nothing */
+ break;
+ case MM_ITLB_ADDR:
+ ret = cpu_sh4_read_mmaped_itlb_addr(&s->cpu->env, addr);
+ break;
+ case MM_ITLB_DATA:
+ ret = cpu_sh4_read_mmaped_itlb_data(&s->cpu->env, addr);
+ break;
+ case MM_OCACHE_ADDR:
+ case MM_OCACHE_DATA:
+ /* do nothing */
+ break;
+ case MM_UTLB_ADDR:
+ ret = cpu_sh4_read_mmaped_utlb_addr(&s->cpu->env, addr);
+ break;
+ case MM_UTLB_DATA:
+ ret = cpu_sh4_read_mmaped_utlb_data(&s->cpu->env, addr);
+ break;
+ default:
+ abort();
+ }
+
+ return ret;
+}
+
+static void invalid_write(void *opaque, hwaddr addr,
+ uint64_t mem_value)
+{
+ abort();
+}
+
+static void sh7750_mmct_write(void *opaque, hwaddr addr,
+ uint64_t mem_value, unsigned size)
+{
+ SH7750State *s = opaque;
+
+ if (size != 4) {
+ invalid_write(opaque, addr, mem_value);
+ }
+
+ switch (MM_REGION_TYPE(addr)) {
+ case MM_ICACHE_ADDR:
+ case MM_ICACHE_DATA:
+ /* do nothing */
+ break;
+ case MM_ITLB_ADDR:
+ cpu_sh4_write_mmaped_itlb_addr(&s->cpu->env, addr, mem_value);
+ break;
+ case MM_ITLB_DATA:
+ cpu_sh4_write_mmaped_itlb_data(&s->cpu->env, addr, mem_value);
+ abort();
+ break;
+ case MM_OCACHE_ADDR:
+ case MM_OCACHE_DATA:
+ /* do nothing */
+ break;
+ case MM_UTLB_ADDR:
+ cpu_sh4_write_mmaped_utlb_addr(&s->cpu->env, addr, mem_value);
+ break;
+ case MM_UTLB_DATA:
+ cpu_sh4_write_mmaped_utlb_data(&s->cpu->env, addr, mem_value);
+ break;
+ default:
+ abort();
+ break;
+ }
+}
+
+static const MemoryRegionOps sh7750_mmct_ops = {
+ .read = sh7750_mmct_read,
+ .write = sh7750_mmct_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+SH7750State *sh7750_init(SuperHCPU *cpu, MemoryRegion *sysmem)
+{
+ SH7750State *s;
+
+ s = g_malloc0(sizeof(SH7750State));
+ s->cpu = cpu;
+ s->periph_freq = 60000000; /* 60MHz */
+ memory_region_init_io(&s->iomem, NULL, &sh7750_mem_ops, s,
+ "memory", 0x1fc01000);
+
+ memory_region_init_alias(&s->iomem_1f0, NULL, "memory-1f0",
+ &s->iomem, 0x1f000000, 0x1000);
+ memory_region_add_subregion(sysmem, 0x1f000000, &s->iomem_1f0);
+
+ memory_region_init_alias(&s->iomem_ff0, NULL, "memory-ff0",
+ &s->iomem, 0x1f000000, 0x1000);
+ memory_region_add_subregion(sysmem, 0xff000000, &s->iomem_ff0);
+
+ memory_region_init_alias(&s->iomem_1f8, NULL, "memory-1f8",
+ &s->iomem, 0x1f800000, 0x1000);
+ memory_region_add_subregion(sysmem, 0x1f800000, &s->iomem_1f8);
+
+ memory_region_init_alias(&s->iomem_ff8, NULL, "memory-ff8",
+ &s->iomem, 0x1f800000, 0x1000);
+ memory_region_add_subregion(sysmem, 0xff800000, &s->iomem_ff8);
+
+ memory_region_init_alias(&s->iomem_1fc, NULL, "memory-1fc",
+ &s->iomem, 0x1fc00000, 0x1000);
+ memory_region_add_subregion(sysmem, 0x1fc00000, &s->iomem_1fc);
+
+ memory_region_init_alias(&s->iomem_ffc, NULL, "memory-ffc",
+ &s->iomem, 0x1fc00000, 0x1000);
+ memory_region_add_subregion(sysmem, 0xffc00000, &s->iomem_ffc);
+
+ memory_region_init_io(&s->mmct_iomem, NULL, &sh7750_mmct_ops, s,
+ "cache-and-tlb", 0x08000000);
+ memory_region_add_subregion(sysmem, 0xf0000000, &s->mmct_iomem);
+
+ sh_intc_init(sysmem, &s->intc, NR_SOURCES,
+ _INTC_ARRAY(mask_registers),
+ _INTC_ARRAY(prio_registers));
+
+ sh_intc_register_sources(&s->intc,
+ _INTC_ARRAY(vectors),
+ _INTC_ARRAY(groups));
+
+ cpu->env.intc_handle = &s->intc;
+
+ sh_serial_init(sysmem, 0x1fe00000,
+ 0, s->periph_freq, serial_hds[0],
+ s->intc.irqs[SCI1_ERI],
+ s->intc.irqs[SCI1_RXI],
+ s->intc.irqs[SCI1_TXI],
+ s->intc.irqs[SCI1_TEI],
+ NULL);
+ sh_serial_init(sysmem, 0x1fe80000,
+ SH_SERIAL_FEAT_SCIF,
+ s->periph_freq, serial_hds[1],
+ s->intc.irqs[SCIF_ERI],
+ s->intc.irqs[SCIF_RXI],
+ s->intc.irqs[SCIF_TXI],
+ NULL,
+ s->intc.irqs[SCIF_BRI]);
+
+ tmu012_init(sysmem, 0x1fd80000,
+ TMU012_FEAT_TOCR | TMU012_FEAT_3CHAN | TMU012_FEAT_EXTCLK,
+ s->periph_freq,
+ s->intc.irqs[TMU0],
+ s->intc.irqs[TMU1],
+ s->intc.irqs[TMU2_TUNI],
+ s->intc.irqs[TMU2_TICPI]);
+
+ if (cpu->env.id & (SH_CPU_SH7750 | SH_CPU_SH7750S | SH_CPU_SH7751)) {
+ sh_intc_register_sources(&s->intc,
+ _INTC_ARRAY(vectors_dma4),
+ _INTC_ARRAY(groups_dma4));
+ }
+
+ if (cpu->env.id & (SH_CPU_SH7750R | SH_CPU_SH7751R)) {
+ sh_intc_register_sources(&s->intc,
+ _INTC_ARRAY(vectors_dma8),
+ _INTC_ARRAY(groups_dma8));
+ }
+
+ if (cpu->env.id & (SH_CPU_SH7750R | SH_CPU_SH7751 | SH_CPU_SH7751R)) {
+ sh_intc_register_sources(&s->intc,
+ _INTC_ARRAY(vectors_tmu34),
+ NULL, 0);
+ tmu012_init(sysmem, 0x1e100000, 0, s->periph_freq,
+ s->intc.irqs[TMU3],
+ s->intc.irqs[TMU4],
+ NULL, NULL);
+ }
+
+ if (cpu->env.id & (SH_CPU_SH7751_ALL)) {
+ sh_intc_register_sources(&s->intc,
+ _INTC_ARRAY(vectors_pci),
+ _INTC_ARRAY(groups_pci));
+ }
+
+ if (cpu->env.id & (SH_CPU_SH7750S | SH_CPU_SH7750R | SH_CPU_SH7751_ALL)) {
+ sh_intc_register_sources(&s->intc,
+ _INTC_ARRAY(vectors_irlm),
+ NULL, 0);
+ }
+
+ sh_intc_register_sources(&s->intc,
+ _INTC_ARRAY(vectors_irl),
+ _INTC_ARRAY(groups_irl));
+ return s;
+}
+
+qemu_irq sh7750_irl(SH7750State *s)
+{
+ sh_intc_toggle_source(sh_intc_source(&s->intc, IRL), 1, 0); /* enable */
+ return qemu_allocate_irq(sh_intc_set_irl, sh_intc_source(&s->intc, IRL), 0);
+}
diff --git a/hw/sh4/sh7750_regnames.c b/hw/sh4/sh7750_regnames.c
new file mode 100644
index 00000000..52ac1cc7
--- /dev/null
+++ b/hw/sh4/sh7750_regnames.c
@@ -0,0 +1,97 @@
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "sh7750_regs.h"
+#include "sh7750_regnames.h"
+
+#define REGNAME(r) {r, #r},
+
+typedef struct {
+ uint32_t regaddr;
+ const char *regname;
+} regname_t;
+
+static regname_t regnames[] = {
+ REGNAME(SH7750_PTEH_A7)
+ REGNAME(SH7750_PTEL_A7)
+ REGNAME(SH7750_PTEA_A7)
+ REGNAME(SH7750_TTB_A7)
+ REGNAME(SH7750_TEA_A7)
+ REGNAME(SH7750_MMUCR_A7)
+ REGNAME(SH7750_CCR_A7)
+ REGNAME(SH7750_QACR0_A7)
+ REGNAME(SH7750_QACR1_A7)
+ REGNAME(SH7750_TRA_A7)
+ REGNAME(SH7750_EXPEVT_A7)
+ REGNAME(SH7750_INTEVT_A7)
+ REGNAME(SH7750_STBCR_A7)
+ REGNAME(SH7750_STBCR2_A7)
+ REGNAME(SH7750_FRQCR_A7)
+ REGNAME(SH7750_WTCNT_A7)
+ REGNAME(SH7750_WTCSR_A7)
+ REGNAME(SH7750_R64CNT_A7)
+ REGNAME(SH7750_RSECCNT_A7)
+ REGNAME(SH7750_RMINCNT_A7)
+ REGNAME(SH7750_RHRCNT_A7)
+ REGNAME(SH7750_RWKCNT_A7)
+ REGNAME(SH7750_RDAYCNT_A7)
+ REGNAME(SH7750_RMONCNT_A7)
+ REGNAME(SH7750_RYRCNT_A7)
+ REGNAME(SH7750_RSECAR_A7)
+ REGNAME(SH7750_RMINAR_A7)
+ REGNAME(SH7750_RHRAR_A7)
+ REGNAME(SH7750_RWKAR_A7)
+ REGNAME(SH7750_RDAYAR_A7)
+ REGNAME(SH7750_RMONAR_A7)
+ REGNAME(SH7750_RCR1_A7)
+ REGNAME(SH7750_RCR2_A7)
+ REGNAME(SH7750_BCR1_A7)
+ REGNAME(SH7750_BCR2_A7)
+ REGNAME(SH7750_WCR1_A7)
+ REGNAME(SH7750_WCR2_A7)
+ REGNAME(SH7750_WCR3_A7)
+ REGNAME(SH7750_MCR_A7)
+ REGNAME(SH7750_PCR_A7)
+ REGNAME(SH7750_RTCSR_A7)
+ REGNAME(SH7750_RTCNT_A7)
+ REGNAME(SH7750_RTCOR_A7)
+ REGNAME(SH7750_RFCR_A7)
+ REGNAME(SH7750_SAR0_A7)
+ REGNAME(SH7750_SAR1_A7)
+ REGNAME(SH7750_SAR2_A7)
+ REGNAME(SH7750_SAR3_A7)
+ REGNAME(SH7750_DAR0_A7)
+ REGNAME(SH7750_DAR1_A7)
+ REGNAME(SH7750_DAR2_A7)
+ REGNAME(SH7750_DAR3_A7)
+ REGNAME(SH7750_DMATCR0_A7)
+ REGNAME(SH7750_DMATCR1_A7)
+ REGNAME(SH7750_DMATCR2_A7)
+ REGNAME(SH7750_DMATCR3_A7)
+ REGNAME(SH7750_CHCR0_A7)
+ REGNAME(SH7750_CHCR1_A7)
+ REGNAME(SH7750_CHCR2_A7)
+ REGNAME(SH7750_CHCR3_A7)
+ REGNAME(SH7750_DMAOR_A7)
+ REGNAME(SH7750_PCTRA_A7)
+ REGNAME(SH7750_PDTRA_A7)
+ REGNAME(SH7750_PCTRB_A7)
+ REGNAME(SH7750_PDTRB_A7)
+ REGNAME(SH7750_GPIOIC_A7)
+ REGNAME(SH7750_ICR_A7)
+ REGNAME(SH7750_BCR3_A7)
+ REGNAME(SH7750_BCR4_A7)
+ REGNAME(SH7750_SDMR2_A7)
+ REGNAME(SH7750_SDMR3_A7) {(uint32_t) - 1, NULL}
+};
+
+const char *regname(uint32_t addr)
+{
+ unsigned int i;
+
+ for (i = 0; regnames[i].regaddr != (uint32_t) - 1; i++) {
+ if (regnames[i].regaddr == addr)
+ return regnames[i].regname;
+ }
+
+ return "<unknown reg>";
+}
diff --git a/hw/sh4/sh7750_regnames.h b/hw/sh4/sh7750_regnames.h
new file mode 100644
index 00000000..7463709b
--- /dev/null
+++ b/hw/sh4/sh7750_regnames.h
@@ -0,0 +1,6 @@
+#ifndef _SH7750_REGNAMES_H
+#define _SH7750_REGNAMES_H
+
+const char *regname(uint32_t addr);
+
+#endif /* _SH7750_REGNAMES_H */
diff --git a/hw/sh4/sh7750_regs.h b/hw/sh4/sh7750_regs.h
new file mode 100644
index 00000000..534aa484
--- /dev/null
+++ b/hw/sh4/sh7750_regs.h
@@ -0,0 +1,1277 @@
+/*
+ * SH-7750 memory-mapped registers
+ * This file based on information provided in the following document:
+ * "Hitachi SuperH (tm) RISC engine. SH7750 Series (SH7750, SH7750S)
+ * Hardware Manual"
+ * Document Number ADE-602-124C, Rev. 4.0, 4/21/00, Hitachi Ltd.
+ *
+ * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia
+ * Author: Alexandra Kossovsky <sasha@oktet.ru>
+ * Victor V. Vengerov <vvv@oktet.ru>
+ *
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution or at
+ * http://www.rtems.com/license/LICENSE.
+ *
+ * @(#) sh7750_regs.h,v 1.2.4.1 2003/09/04 18:46:00 joel Exp
+ */
+
+#ifndef __SH7750_REGS_H__
+#define __SH7750_REGS_H__
+
+/*
+ * All register has 2 addresses: in 0xff000000 - 0xffffffff (P4 address) and
+ * in 0x1f000000 - 0x1fffffff (area 7 address)
+ */
+#define SH7750_P4_BASE 0xff000000 /* Accessible only in
+ privileged mode */
+#define SH7750_A7_BASE 0x1f000000 /* Accessible only using TLB */
+
+#define SH7750_P4_REG32(ofs) (SH7750_P4_BASE + (ofs))
+#define SH7750_A7_REG32(ofs) (SH7750_A7_BASE + (ofs))
+
+/*
+ * MMU Registers
+ */
+
+/* Page Table Entry High register - PTEH */
+#define SH7750_PTEH_REGOFS 0x000000 /* offset */
+#define SH7750_PTEH SH7750_P4_REG32(SH7750_PTEH_REGOFS)
+#define SH7750_PTEH_A7 SH7750_A7_REG32(SH7750_PTEH_REGOFS)
+#define SH7750_PTEH_VPN 0xfffffd00 /* Virtual page number */
+#define SH7750_PTEH_VPN_S 10
+#define SH7750_PTEH_ASID 0x000000ff /* Address space identifier */
+#define SH7750_PTEH_ASID_S 0
+
+/* Page Table Entry Low register - PTEL */
+#define SH7750_PTEL_REGOFS 0x000004 /* offset */
+#define SH7750_PTEL SH7750_P4_REG32(SH7750_PTEL_REGOFS)
+#define SH7750_PTEL_A7 SH7750_A7_REG32(SH7750_PTEL_REGOFS)
+#define SH7750_PTEL_PPN 0x1ffffc00 /* Physical page number */
+#define SH7750_PTEL_PPN_S 10
+#define SH7750_PTEL_V 0x00000100 /* Validity (0-entry is invalid) */
+#define SH7750_PTEL_SZ1 0x00000080 /* Page size bit 1 */
+#define SH7750_PTEL_SZ0 0x00000010 /* Page size bit 0 */
+#define SH7750_PTEL_SZ_1KB 0x00000000 /* 1-kbyte page */
+#define SH7750_PTEL_SZ_4KB 0x00000010 /* 4-kbyte page */
+#define SH7750_PTEL_SZ_64KB 0x00000080 /* 64-kbyte page */
+#define SH7750_PTEL_SZ_1MB 0x00000090 /* 1-Mbyte page */
+#define SH7750_PTEL_PR 0x00000060 /* Protection Key Data */
+#define SH7750_PTEL_PR_ROPO 0x00000000 /* read-only in priv mode */
+#define SH7750_PTEL_PR_RWPO 0x00000020 /* read-write in priv mode */
+#define SH7750_PTEL_PR_ROPU 0x00000040 /* read-only in priv or user mode */
+#define SH7750_PTEL_PR_RWPU 0x00000060 /* read-write in priv or user mode */
+#define SH7750_PTEL_C 0x00000008 /* Cacheability
+ (0 - page not cacheable) */
+#define SH7750_PTEL_D 0x00000004 /* Dirty bit (1 - write has been
+ performed to a page) */
+#define SH7750_PTEL_SH 0x00000002 /* Share Status bit (1 - page are
+ shared by processes) */
+#define SH7750_PTEL_WT 0x00000001 /* Write-through bit, specifies the
+ cache write mode:
+ 0 - Copy-back mode
+ 1 - Write-through mode */
+
+/* Page Table Entry Assistance register - PTEA */
+#define SH7750_PTEA_REGOFS 0x000034 /* offset */
+#define SH7750_PTEA SH7750_P4_REG32(SH7750_PTEA_REGOFS)
+#define SH7750_PTEA_A7 SH7750_A7_REG32(SH7750_PTEA_REGOFS)
+#define SH7750_PTEA_TC 0x00000008 /* Timing Control bit
+ 0 - use area 5 wait states
+ 1 - use area 6 wait states */
+#define SH7750_PTEA_SA 0x00000007 /* Space Attribute bits: */
+#define SH7750_PTEA_SA_UNDEF 0x00000000 /* 0 - undefined */
+#define SH7750_PTEA_SA_IOVAR 0x00000001 /* 1 - variable-size I/O space */
+#define SH7750_PTEA_SA_IO8 0x00000002 /* 2 - 8-bit I/O space */
+#define SH7750_PTEA_SA_IO16 0x00000003 /* 3 - 16-bit I/O space */
+#define SH7750_PTEA_SA_CMEM8 0x00000004 /* 4 - 8-bit common memory space */
+#define SH7750_PTEA_SA_CMEM16 0x00000005 /* 5 - 16-bit common memory space */
+#define SH7750_PTEA_SA_AMEM8 0x00000006 /* 6 - 8-bit attr memory space */
+#define SH7750_PTEA_SA_AMEM16 0x00000007 /* 7 - 16-bit attr memory space */
+
+
+/* Translation table base register */
+#define SH7750_TTB_REGOFS 0x000008 /* offset */
+#define SH7750_TTB SH7750_P4_REG32(SH7750_TTB_REGOFS)
+#define SH7750_TTB_A7 SH7750_A7_REG32(SH7750_TTB_REGOFS)
+
+/* TLB exeption address register - TEA */
+#define SH7750_TEA_REGOFS 0x00000c /* offset */
+#define SH7750_TEA SH7750_P4_REG32(SH7750_TEA_REGOFS)
+#define SH7750_TEA_A7 SH7750_A7_REG32(SH7750_TEA_REGOFS)
+
+/* MMU control register - MMUCR */
+#define SH7750_MMUCR_REGOFS 0x000010 /* offset */
+#define SH7750_MMUCR SH7750_P4_REG32(SH7750_MMUCR_REGOFS)
+#define SH7750_MMUCR_A7 SH7750_A7_REG32(SH7750_MMUCR_REGOFS)
+#define SH7750_MMUCR_AT 0x00000001 /* Address translation bit */
+#define SH7750_MMUCR_TI 0x00000004 /* TLB invalidate */
+#define SH7750_MMUCR_SV 0x00000100 /* Single Virtual Mode bit */
+#define SH7750_MMUCR_SQMD 0x00000200 /* Store Queue Mode bit */
+#define SH7750_MMUCR_URC 0x0000FC00 /* UTLB Replace Counter */
+#define SH7750_MMUCR_URC_S 10
+#define SH7750_MMUCR_URB 0x00FC0000 /* UTLB Replace Boundary */
+#define SH7750_MMUCR_URB_S 18
+#define SH7750_MMUCR_LRUI 0xFC000000 /* Least Recently Used ITLB */
+#define SH7750_MMUCR_LRUI_S 26
+
+
+
+
+/*
+ * Cache registers
+ * IC -- instructions cache
+ * OC -- operand cache
+ */
+
+/* Cache Control Register - CCR */
+#define SH7750_CCR_REGOFS 0x00001c /* offset */
+#define SH7750_CCR SH7750_P4_REG32(SH7750_CCR_REGOFS)
+#define SH7750_CCR_A7 SH7750_A7_REG32(SH7750_CCR_REGOFS)
+
+#define SH7750_CCR_IIX 0x00008000 /* IC index enable bit */
+#define SH7750_CCR_ICI 0x00000800 /* IC invalidation bit:
+ set it to clear IC */
+#define SH7750_CCR_ICE 0x00000100 /* IC enable bit */
+#define SH7750_CCR_OIX 0x00000080 /* OC index enable bit */
+#define SH7750_CCR_ORA 0x00000020 /* OC RAM enable bit
+ if you set OCE = 0,
+ you should set ORA = 0 */
+#define SH7750_CCR_OCI 0x00000008 /* OC invalidation bit */
+#define SH7750_CCR_CB 0x00000004 /* Copy-back bit for P1 area */
+#define SH7750_CCR_WT 0x00000002 /* Write-through bit for P0,U0,P3 area */
+#define SH7750_CCR_OCE 0x00000001 /* OC enable bit */
+
+/* Queue address control register 0 - QACR0 */
+#define SH7750_QACR0_REGOFS 0x000038 /* offset */
+#define SH7750_QACR0 SH7750_P4_REG32(SH7750_QACR0_REGOFS)
+#define SH7750_QACR0_A7 SH7750_A7_REG32(SH7750_QACR0_REGOFS)
+
+/* Queue address control register 1 - QACR1 */
+#define SH7750_QACR1_REGOFS 0x00003c /* offset */
+#define SH7750_QACR1 SH7750_P4_REG32(SH7750_QACR1_REGOFS)
+#define SH7750_QACR1_A7 SH7750_A7_REG32(SH7750_QACR1_REGOFS)
+
+
+/*
+ * Exeption-related registers
+ */
+
+/* Immediate data for TRAPA instruction - TRA */
+#define SH7750_TRA_REGOFS 0x000020 /* offset */
+#define SH7750_TRA SH7750_P4_REG32(SH7750_TRA_REGOFS)
+#define SH7750_TRA_A7 SH7750_A7_REG32(SH7750_TRA_REGOFS)
+
+#define SH7750_TRA_IMM 0x000003fd /* Immediate data operand */
+#define SH7750_TRA_IMM_S 2
+
+/* Exeption event register - EXPEVT */
+#define SH7750_EXPEVT_REGOFS 0x000024
+#define SH7750_EXPEVT SH7750_P4_REG32(SH7750_EXPEVT_REGOFS)
+#define SH7750_EXPEVT_A7 SH7750_A7_REG32(SH7750_EXPEVT_REGOFS)
+
+#define SH7750_EXPEVT_EX 0x00000fff /* Exeption code */
+#define SH7750_EXPEVT_EX_S 0
+
+/* Interrupt event register */
+#define SH7750_INTEVT_REGOFS 0x000028
+#define SH7750_INTEVT SH7750_P4_REG32(SH7750_INTEVT_REGOFS)
+#define SH7750_INTEVT_A7 SH7750_A7_REG32(SH7750_INTEVT_REGOFS)
+#define SH7750_INTEVT_EX 0x00000fff /* Exeption code */
+#define SH7750_INTEVT_EX_S 0
+
+/*
+ * Exception/interrupt codes
+ */
+#define SH7750_EVT_TO_NUM(evt) ((evt) >> 5)
+
+/* Reset exception category */
+#define SH7750_EVT_POWER_ON_RST 0x000 /* Power-on reset */
+#define SH7750_EVT_MANUAL_RST 0x020 /* Manual reset */
+#define SH7750_EVT_TLB_MULT_HIT 0x140 /* TLB multiple-hit exception */
+
+/* General exception category */
+#define SH7750_EVT_USER_BREAK 0x1E0 /* User break */
+#define SH7750_EVT_IADDR_ERR 0x0E0 /* Instruction address error */
+#define SH7750_EVT_TLB_READ_MISS 0x040 /* ITLB miss exception /
+ DTLB miss exception (read) */
+#define SH7750_EVT_TLB_READ_PROTV 0x0A0 /* ITLB protection violation /
+ DTLB protection violation (read) */
+#define SH7750_EVT_ILLEGAL_INSTR 0x180 /* General Illegal Instruction
+ exception */
+#define SH7750_EVT_SLOT_ILLEGAL_INSTR 0x1A0 /* Slot Illegal Instruction
+ exception */
+#define SH7750_EVT_FPU_DISABLE 0x800 /* General FPU disable exception */
+#define SH7750_EVT_SLOT_FPU_DISABLE 0x820 /* Slot FPU disable exception */
+#define SH7750_EVT_DATA_READ_ERR 0x0E0 /* Data address error (read) */
+#define SH7750_EVT_DATA_WRITE_ERR 0x100 /* Data address error (write) */
+#define SH7750_EVT_DTLB_WRITE_MISS 0x060 /* DTLB miss exception (write) */
+#define SH7750_EVT_DTLB_WRITE_PROTV 0x0C0 /* DTLB protection violation
+ exception (write) */
+#define SH7750_EVT_FPU_EXCEPTION 0x120 /* FPU exception */
+#define SH7750_EVT_INITIAL_PGWRITE 0x080 /* Initial Page Write exception */
+#define SH7750_EVT_TRAPA 0x160 /* Unconditional trap (TRAPA) */
+
+/* Interrupt exception category */
+#define SH7750_EVT_NMI 0x1C0 /* Non-maskable interrupt */
+#define SH7750_EVT_IRQ0 0x200 /* External Interrupt 0 */
+#define SH7750_EVT_IRQ1 0x220 /* External Interrupt 1 */
+#define SH7750_EVT_IRQ2 0x240 /* External Interrupt 2 */
+#define SH7750_EVT_IRQ3 0x260 /* External Interrupt 3 */
+#define SH7750_EVT_IRQ4 0x280 /* External Interrupt 4 */
+#define SH7750_EVT_IRQ5 0x2A0 /* External Interrupt 5 */
+#define SH7750_EVT_IRQ6 0x2C0 /* External Interrupt 6 */
+#define SH7750_EVT_IRQ7 0x2E0 /* External Interrupt 7 */
+#define SH7750_EVT_IRQ8 0x300 /* External Interrupt 8 */
+#define SH7750_EVT_IRQ9 0x320 /* External Interrupt 9 */
+#define SH7750_EVT_IRQA 0x340 /* External Interrupt A */
+#define SH7750_EVT_IRQB 0x360 /* External Interrupt B */
+#define SH7750_EVT_IRQC 0x380 /* External Interrupt C */
+#define SH7750_EVT_IRQD 0x3A0 /* External Interrupt D */
+#define SH7750_EVT_IRQE 0x3C0 /* External Interrupt E */
+
+/* Peripheral Module Interrupts - Timer Unit (TMU) */
+#define SH7750_EVT_TUNI0 0x400 /* TMU Underflow Interrupt 0 */
+#define SH7750_EVT_TUNI1 0x420 /* TMU Underflow Interrupt 1 */
+#define SH7750_EVT_TUNI2 0x440 /* TMU Underflow Interrupt 2 */
+#define SH7750_EVT_TICPI2 0x460 /* TMU Input Capture Interrupt 2 */
+
+/* Peripheral Module Interrupts - Real-Time Clock (RTC) */
+#define SH7750_EVT_RTC_ATI 0x480 /* Alarm Interrupt Request */
+#define SH7750_EVT_RTC_PRI 0x4A0 /* Periodic Interrupt Request */
+#define SH7750_EVT_RTC_CUI 0x4C0 /* Carry Interrupt Request */
+
+/* Peripheral Module Interrupts - Serial Communication Interface (SCI) */
+#define SH7750_EVT_SCI_ERI 0x4E0 /* Receive Error */
+#define SH7750_EVT_SCI_RXI 0x500 /* Receive Data Register Full */
+#define SH7750_EVT_SCI_TXI 0x520 /* Transmit Data Register Empty */
+#define SH7750_EVT_SCI_TEI 0x540 /* Transmit End */
+
+/* Peripheral Module Interrupts - Watchdog Timer (WDT) */
+#define SH7750_EVT_WDT_ITI 0x560 /* Interval Timer Interrupt
+ (used when WDT operates in
+ interval timer mode) */
+
+/* Peripheral Module Interrupts - Memory Refresh Unit (REF) */
+#define SH7750_EVT_REF_RCMI 0x580 /* Compare-match Interrupt */
+#define SH7750_EVT_REF_ROVI 0x5A0 /* Refresh Counter Overflow
+ interrupt */
+
+/* Peripheral Module Interrupts - Hitachi User Debug Interface (H-UDI) */
+#define SH7750_EVT_HUDI 0x600 /* UDI interrupt */
+
+/* Peripheral Module Interrupts - General-Purpose I/O (GPIO) */
+#define SH7750_EVT_GPIO 0x620 /* GPIO Interrupt */
+
+/* Peripheral Module Interrupts - DMA Controller (DMAC) */
+#define SH7750_EVT_DMAC_DMTE0 0x640 /* DMAC 0 Transfer End Interrupt */
+#define SH7750_EVT_DMAC_DMTE1 0x660 /* DMAC 1 Transfer End Interrupt */
+#define SH7750_EVT_DMAC_DMTE2 0x680 /* DMAC 2 Transfer End Interrupt */
+#define SH7750_EVT_DMAC_DMTE3 0x6A0 /* DMAC 3 Transfer End Interrupt */
+#define SH7750_EVT_DMAC_DMAE 0x6C0 /* DMAC Address Error Interrupt */
+
+/* Peripheral Module Interrupts - Serial Communication Interface with FIFO */
+/* (SCIF) */
+#define SH7750_EVT_SCIF_ERI 0x700 /* Receive Error */
+#define SH7750_EVT_SCIF_RXI 0x720 /* Receive FIFO Data Full or
+ Receive Data ready interrupt */
+#define SH7750_EVT_SCIF_BRI 0x740 /* Break or overrun error */
+#define SH7750_EVT_SCIF_TXI 0x760 /* Transmit FIFO Data Empty */
+
+/*
+ * Power Management
+ */
+#define SH7750_STBCR_REGOFS 0xC00004 /* offset */
+#define SH7750_STBCR SH7750_P4_REG32(SH7750_STBCR_REGOFS)
+#define SH7750_STBCR_A7 SH7750_A7_REG32(SH7750_STBCR_REGOFS)
+
+#define SH7750_STBCR_STBY 0x80 /* Specifies a transition to standby mode:
+ 0 - Transition to SLEEP mode on SLEEP
+ 1 - Transition to STANDBY mode on SLEEP */
+#define SH7750_STBCR_PHZ 0x40 /* State of peripheral module pins in
+ standby mode:
+ 0 - normal state
+ 1 - high-impendance state */
+
+#define SH7750_STBCR_PPU 0x20 /* Peripheral module pins pull-up controls */
+#define SH7750_STBCR_MSTP4 0x10 /* Stopping the clock supply to DMAC */
+#define SH7750_STBCR_DMAC_STP SH7750_STBCR_MSTP4
+#define SH7750_STBCR_MSTP3 0x08 /* Stopping the clock supply to SCIF */
+#define SH7750_STBCR_SCIF_STP SH7750_STBCR_MSTP3
+#define SH7750_STBCR_MSTP2 0x04 /* Stopping the clock supply to TMU */
+#define SH7750_STBCR_TMU_STP SH7750_STBCR_MSTP2
+#define SH7750_STBCR_MSTP1 0x02 /* Stopping the clock supply to RTC */
+#define SH7750_STBCR_RTC_STP SH7750_STBCR_MSTP1
+#define SH7750_STBCR_MSPT0 0x01 /* Stopping the clock supply to SCI */
+#define SH7750_STBCR_SCI_STP SH7750_STBCR_MSTP0
+
+#define SH7750_STBCR_STBY 0x80
+
+
+#define SH7750_STBCR2_REGOFS 0xC00010 /* offset */
+#define SH7750_STBCR2 SH7750_P4_REG32(SH7750_STBCR2_REGOFS)
+#define SH7750_STBCR2_A7 SH7750_A7_REG32(SH7750_STBCR2_REGOFS)
+
+#define SH7750_STBCR2_DSLP 0x80 /* Specifies transition to deep sleep mode:
+ 0 - transition to sleep or standby mode
+ as it is specified in STBY bit
+ 1 - transition to deep sleep mode on
+ execution of SLEEP instruction */
+#define SH7750_STBCR2_MSTP6 0x02 /* Stopping the clock supply to Store Queue
+ in the cache controller */
+#define SH7750_STBCR2_SQ_STP SH7750_STBCR2_MSTP6
+#define SH7750_STBCR2_MSTP5 0x01 /* Stopping the clock supply to the User
+ Break Controller (UBC) */
+#define SH7750_STBCR2_UBC_STP SH7750_STBCR2_MSTP5
+
+/*
+ * Clock Pulse Generator (CPG)
+ */
+#define SH7750_FRQCR_REGOFS 0xC00000 /* offset */
+#define SH7750_FRQCR SH7750_P4_REG32(SH7750_FRQCR_REGOFS)
+#define SH7750_FRQCR_A7 SH7750_A7_REG32(SH7750_FRQCR_REGOFS)
+
+#define SH7750_FRQCR_CKOEN 0x0800 /* Clock Output Enable
+ 0 - CKIO pin goes to HiZ/pullup
+ 1 - Clock is output from CKIO */
+#define SH7750_FRQCR_PLL1EN 0x0400 /* PLL circuit 1 enable */
+#define SH7750_FRQCR_PLL2EN 0x0200 /* PLL circuit 2 enable */
+
+#define SH7750_FRQCR_IFC 0x01C0 /* CPU clock frequency division ratio: */
+#define SH7750_FRQCR_IFCDIV1 0x0000 /* 0 - * 1 */
+#define SH7750_FRQCR_IFCDIV2 0x0040 /* 1 - * 1/2 */
+#define SH7750_FRQCR_IFCDIV3 0x0080 /* 2 - * 1/3 */
+#define SH7750_FRQCR_IFCDIV4 0x00C0 /* 3 - * 1/4 */
+#define SH7750_FRQCR_IFCDIV6 0x0100 /* 4 - * 1/6 */
+#define SH7750_FRQCR_IFCDIV8 0x0140 /* 5 - * 1/8 */
+
+#define SH7750_FRQCR_BFC 0x0038 /* Bus clock frequency division ratio: */
+#define SH7750_FRQCR_BFCDIV1 0x0000 /* 0 - * 1 */
+#define SH7750_FRQCR_BFCDIV2 0x0008 /* 1 - * 1/2 */
+#define SH7750_FRQCR_BFCDIV3 0x0010 /* 2 - * 1/3 */
+#define SH7750_FRQCR_BFCDIV4 0x0018 /* 3 - * 1/4 */
+#define SH7750_FRQCR_BFCDIV6 0x0020 /* 4 - * 1/6 */
+#define SH7750_FRQCR_BFCDIV8 0x0028 /* 5 - * 1/8 */
+
+#define SH7750_FRQCR_PFC 0x0007 /* Peripheral module clock frequency
+ division ratio: */
+#define SH7750_FRQCR_PFCDIV2 0x0000 /* 0 - * 1/2 */
+#define SH7750_FRQCR_PFCDIV3 0x0001 /* 1 - * 1/3 */
+#define SH7750_FRQCR_PFCDIV4 0x0002 /* 2 - * 1/4 */
+#define SH7750_FRQCR_PFCDIV6 0x0003 /* 3 - * 1/6 */
+#define SH7750_FRQCR_PFCDIV8 0x0004 /* 4 - * 1/8 */
+
+/*
+ * Watchdog Timer (WDT)
+ */
+
+/* Watchdog Timer Counter register - WTCNT */
+#define SH7750_WTCNT_REGOFS 0xC00008 /* offset */
+#define SH7750_WTCNT SH7750_P4_REG32(SH7750_WTCNT_REGOFS)
+#define SH7750_WTCNT_A7 SH7750_A7_REG32(SH7750_WTCNT_REGOFS)
+#define SH7750_WTCNT_KEY 0x5A00 /* When WTCNT byte register written,
+ you have to set the upper byte to
+ 0x5A */
+
+/* Watchdog Timer Control/Status register - WTCSR */
+#define SH7750_WTCSR_REGOFS 0xC0000C /* offset */
+#define SH7750_WTCSR SH7750_P4_REG32(SH7750_WTCSR_REGOFS)
+#define SH7750_WTCSR_A7 SH7750_A7_REG32(SH7750_WTCSR_REGOFS)
+#define SH7750_WTCSR_KEY 0xA500 /* When WTCSR byte register written,
+ you have to set the upper byte to
+ 0xA5 */
+#define SH7750_WTCSR_TME 0x80 /* Timer enable (1-upcount start) */
+#define SH7750_WTCSR_MODE 0x40 /* Timer Mode Select: */
+#define SH7750_WTCSR_MODE_WT 0x40 /* Watchdog Timer Mode */
+#define SH7750_WTCSR_MODE_IT 0x00 /* Interval Timer Mode */
+#define SH7750_WTCSR_RSTS 0x20 /* Reset Select: */
+#define SH7750_WTCSR_RST_MAN 0x20 /* Manual Reset */
+#define SH7750_WTCSR_RST_PWR 0x00 /* Power-on Reset */
+#define SH7750_WTCSR_WOVF 0x10 /* Watchdog Timer Overflow Flag */
+#define SH7750_WTCSR_IOVF 0x08 /* Interval Timer Overflow Flag */
+#define SH7750_WTCSR_CKS 0x07 /* Clock Select: */
+#define SH7750_WTCSR_CKS_DIV32 0x00 /* 1/32 of frequency divider 2 input */
+#define SH7750_WTCSR_CKS_DIV64 0x01 /* 1/64 */
+#define SH7750_WTCSR_CKS_DIV128 0x02 /* 1/128 */
+#define SH7750_WTCSR_CKS_DIV256 0x03 /* 1/256 */
+#define SH7750_WTCSR_CKS_DIV512 0x04 /* 1/512 */
+#define SH7750_WTCSR_CKS_DIV1024 0x05 /* 1/1024 */
+#define SH7750_WTCSR_CKS_DIV2048 0x06 /* 1/2048 */
+#define SH7750_WTCSR_CKS_DIV4096 0x07 /* 1/4096 */
+
+/*
+ * Real-Time Clock (RTC)
+ */
+/* 64-Hz Counter Register (byte, read-only) - R64CNT */
+#define SH7750_R64CNT_REGOFS 0xC80000 /* offset */
+#define SH7750_R64CNT SH7750_P4_REG32(SH7750_R64CNT_REGOFS)
+#define SH7750_R64CNT_A7 SH7750_A7_REG32(SH7750_R64CNT_REGOFS)
+
+/* Second Counter Register (byte, BCD-coded) - RSECCNT */
+#define SH7750_RSECCNT_REGOFS 0xC80004 /* offset */
+#define SH7750_RSECCNT SH7750_P4_REG32(SH7750_RSECCNT_REGOFS)
+#define SH7750_RSECCNT_A7 SH7750_A7_REG32(SH7750_RSECCNT_REGOFS)
+
+/* Minute Counter Register (byte, BCD-coded) - RMINCNT */
+#define SH7750_RMINCNT_REGOFS 0xC80008 /* offset */
+#define SH7750_RMINCNT SH7750_P4_REG32(SH7750_RMINCNT_REGOFS)
+#define SH7750_RMINCNT_A7 SH7750_A7_REG32(SH7750_RMINCNT_REGOFS)
+
+/* Hour Counter Register (byte, BCD-coded) - RHRCNT */
+#define SH7750_RHRCNT_REGOFS 0xC8000C /* offset */
+#define SH7750_RHRCNT SH7750_P4_REG32(SH7750_RHRCNT_REGOFS)
+#define SH7750_RHRCNT_A7 SH7750_A7_REG32(SH7750_RHRCNT_REGOFS)
+
+/* Day-of-Week Counter Register (byte) - RWKCNT */
+#define SH7750_RWKCNT_REGOFS 0xC80010 /* offset */
+#define SH7750_RWKCNT SH7750_P4_REG32(SH7750_RWKCNT_REGOFS)
+#define SH7750_RWKCNT_A7 SH7750_A7_REG32(SH7750_RWKCNT_REGOFS)
+
+#define SH7750_RWKCNT_SUN 0 /* Sunday */
+#define SH7750_RWKCNT_MON 1 /* Monday */
+#define SH7750_RWKCNT_TUE 2 /* Tuesday */
+#define SH7750_RWKCNT_WED 3 /* Wednesday */
+#define SH7750_RWKCNT_THU 4 /* Thursday */
+#define SH7750_RWKCNT_FRI 5 /* Friday */
+#define SH7750_RWKCNT_SAT 6 /* Saturday */
+
+/* Day Counter Register (byte, BCD-coded) - RDAYCNT */
+#define SH7750_RDAYCNT_REGOFS 0xC80014 /* offset */
+#define SH7750_RDAYCNT SH7750_P4_REG32(SH7750_RDAYCNT_REGOFS)
+#define SH7750_RDAYCNT_A7 SH7750_A7_REG32(SH7750_RDAYCNT_REGOFS)
+
+/* Month Counter Register (byte, BCD-coded) - RMONCNT */
+#define SH7750_RMONCNT_REGOFS 0xC80018 /* offset */
+#define SH7750_RMONCNT SH7750_P4_REG32(SH7750_RMONCNT_REGOFS)
+#define SH7750_RMONCNT_A7 SH7750_A7_REG32(SH7750_RMONCNT_REGOFS)
+
+/* Year Counter Register (half, BCD-coded) - RYRCNT */
+#define SH7750_RYRCNT_REGOFS 0xC8001C /* offset */
+#define SH7750_RYRCNT SH7750_P4_REG32(SH7750_RYRCNT_REGOFS)
+#define SH7750_RYRCNT_A7 SH7750_A7_REG32(SH7750_RYRCNT_REGOFS)
+
+/* Second Alarm Register (byte, BCD-coded) - RSECAR */
+#define SH7750_RSECAR_REGOFS 0xC80020 /* offset */
+#define SH7750_RSECAR SH7750_P4_REG32(SH7750_RSECAR_REGOFS)
+#define SH7750_RSECAR_A7 SH7750_A7_REG32(SH7750_RSECAR_REGOFS)
+#define SH7750_RSECAR_ENB 0x80 /* Second Alarm Enable */
+
+/* Minute Alarm Register (byte, BCD-coded) - RMINAR */
+#define SH7750_RMINAR_REGOFS 0xC80024 /* offset */
+#define SH7750_RMINAR SH7750_P4_REG32(SH7750_RMINAR_REGOFS)
+#define SH7750_RMINAR_A7 SH7750_A7_REG32(SH7750_RMINAR_REGOFS)
+#define SH7750_RMINAR_ENB 0x80 /* Minute Alarm Enable */
+
+/* Hour Alarm Register (byte, BCD-coded) - RHRAR */
+#define SH7750_RHRAR_REGOFS 0xC80028 /* offset */
+#define SH7750_RHRAR SH7750_P4_REG32(SH7750_RHRAR_REGOFS)
+#define SH7750_RHRAR_A7 SH7750_A7_REG32(SH7750_RHRAR_REGOFS)
+#define SH7750_RHRAR_ENB 0x80 /* Hour Alarm Enable */
+
+/* Day-of-Week Alarm Register (byte) - RWKAR */
+#define SH7750_RWKAR_REGOFS 0xC8002C /* offset */
+#define SH7750_RWKAR SH7750_P4_REG32(SH7750_RWKAR_REGOFS)
+#define SH7750_RWKAR_A7 SH7750_A7_REG32(SH7750_RWKAR_REGOFS)
+#define SH7750_RWKAR_ENB 0x80 /* Day-of-week Alarm Enable */
+
+#define SH7750_RWKAR_SUN 0 /* Sunday */
+#define SH7750_RWKAR_MON 1 /* Monday */
+#define SH7750_RWKAR_TUE 2 /* Tuesday */
+#define SH7750_RWKAR_WED 3 /* Wednesday */
+#define SH7750_RWKAR_THU 4 /* Thursday */
+#define SH7750_RWKAR_FRI 5 /* Friday */
+#define SH7750_RWKAR_SAT 6 /* Saturday */
+
+/* Day Alarm Register (byte, BCD-coded) - RDAYAR */
+#define SH7750_RDAYAR_REGOFS 0xC80030 /* offset */
+#define SH7750_RDAYAR SH7750_P4_REG32(SH7750_RDAYAR_REGOFS)
+#define SH7750_RDAYAR_A7 SH7750_A7_REG32(SH7750_RDAYAR_REGOFS)
+#define SH7750_RDAYAR_ENB 0x80 /* Day Alarm Enable */
+
+/* Month Counter Register (byte, BCD-coded) - RMONAR */
+#define SH7750_RMONAR_REGOFS 0xC80034 /* offset */
+#define SH7750_RMONAR SH7750_P4_REG32(SH7750_RMONAR_REGOFS)
+#define SH7750_RMONAR_A7 SH7750_A7_REG32(SH7750_RMONAR_REGOFS)
+#define SH7750_RMONAR_ENB 0x80 /* Month Alarm Enable */
+
+/* RTC Control Register 1 (byte) - RCR1 */
+#define SH7750_RCR1_REGOFS 0xC80038 /* offset */
+#define SH7750_RCR1 SH7750_P4_REG32(SH7750_RCR1_REGOFS)
+#define SH7750_RCR1_A7 SH7750_A7_REG32(SH7750_RCR1_REGOFS)
+#define SH7750_RCR1_CF 0x80 /* Carry Flag */
+#define SH7750_RCR1_CIE 0x10 /* Carry Interrupt Enable */
+#define SH7750_RCR1_AIE 0x08 /* Alarm Interrupt Enable */
+#define SH7750_RCR1_AF 0x01 /* Alarm Flag */
+
+/* RTC Control Register 2 (byte) - RCR2 */
+#define SH7750_RCR2_REGOFS 0xC8003C /* offset */
+#define SH7750_RCR2 SH7750_P4_REG32(SH7750_RCR2_REGOFS)
+#define SH7750_RCR2_A7 SH7750_A7_REG32(SH7750_RCR2_REGOFS)
+#define SH7750_RCR2_PEF 0x80 /* Periodic Interrupt Flag */
+#define SH7750_RCR2_PES 0x70 /* Periodic Interrupt Enable: */
+#define SH7750_RCR2_PES_DIS 0x00 /* Periodic Interrupt Disabled */
+#define SH7750_RCR2_PES_DIV256 0x10 /* Generated at 1/256 sec interval */
+#define SH7750_RCR2_PES_DIV64 0x20 /* Generated at 1/64 sec interval */
+#define SH7750_RCR2_PES_DIV16 0x30 /* Generated at 1/16 sec interval */
+#define SH7750_RCR2_PES_DIV4 0x40 /* Generated at 1/4 sec interval */
+#define SH7750_RCR2_PES_DIV2 0x50 /* Generated at 1/2 sec interval */
+#define SH7750_RCR2_PES_x1 0x60 /* Generated at 1 sec interval */
+#define SH7750_RCR2_PES_x2 0x70 /* Generated at 2 sec interval */
+#define SH7750_RCR2_RTCEN 0x08 /* RTC Crystal Oscillator is Operated */
+#define SH7750_RCR2_ADJ 0x04 /* 30-Second Adjastment */
+#define SH7750_RCR2_RESET 0x02 /* Frequency divider circuits are reset */
+#define SH7750_RCR2_START 0x01 /* 0 - sec, min, hr, day-of-week, month,
+ year counters are stopped
+ 1 - sec, min, hr, day-of-week, month,
+ year counters operate normally */
+/*
+ * Bus State Controller - BSC
+ */
+/* Bus Control Register 1 - BCR1 */
+#define SH7750_BCR1_REGOFS 0x800000 /* offset */
+#define SH7750_BCR1 SH7750_P4_REG32(SH7750_BCR1_REGOFS)
+#define SH7750_BCR1_A7 SH7750_A7_REG32(SH7750_BCR1_REGOFS)
+#define SH7750_BCR1_ENDIAN 0x80000000 /* Endianness (1 - little endian) */
+#define SH7750_BCR1_MASTER 0x40000000 /* Master/Slave mode (1-master) */
+#define SH7750_BCR1_A0MPX 0x20000000 /* Area 0 Memory Type (0-SRAM,1-MPX) */
+#define SH7750_BCR1_IPUP 0x02000000 /* Input Pin Pull-up Control:
+ 0 - pull-up resistor is on for
+ control input pins
+ 1 - pull-up resistor is off */
+#define SH7750_BCR1_OPUP 0x01000000 /* Output Pin Pull-up Control:
+ 0 - pull-up resistor is on for
+ control output pins
+ 1 - pull-up resistor is off */
+#define SH7750_BCR1_A1MBC 0x00200000 /* Area 1 SRAM Byte Control Mode:
+ 0 - Area 1 SRAM is set to
+ normal mode
+ 1 - Area 1 SRAM is set to byte
+ control mode */
+#define SH7750_BCR1_A4MBC 0x00100000 /* Area 4 SRAM Byte Control Mode:
+ 0 - Area 4 SRAM is set to
+ normal mode
+ 1 - Area 4 SRAM is set to byte
+ control mode */
+#define SH7750_BCR1_BREQEN 0x00080000 /* BREQ Enable:
+ 0 - External requests are not
+ accepted
+ 1 - External requests are
+ accepted */
+#define SH7750_BCR1_PSHR 0x00040000 /* Partial Sharing Bit:
+ 0 - Master Mode
+ 1 - Partial-sharing Mode */
+#define SH7750_BCR1_MEMMPX 0x00020000 /* Area 1 to 6 MPX Interface:
+ 0 - SRAM/burst ROM interface
+ 1 - MPX interface */
+#define SH7750_BCR1_HIZMEM 0x00008000 /* High Impendance Control. Specifies
+ the state of A[25:0], BS\, CSn\,
+ RD/WR\, CE2A\, CE2B\ in standby
+ mode and when bus is released:
+ 0 - signals go to High-Z mode
+ 1 - signals driven */
+#define SH7750_BCR1_HIZCNT 0x00004000 /* High Impendance Control. Specifies
+ the state of the RAS\, RAS2\, WEn\,
+ CASn\, DQMn, RD\, CASS\, FRAME\,
+ RD2\ signals in standby mode and
+ when bus is released:
+ 0 - signals go to High-Z mode
+ 1 - signals driven */
+#define SH7750_BCR1_A0BST 0x00003800 /* Area 0 Burst ROM Control */
+#define SH7750_BCR1_A0BST_SRAM 0x0000 /* Area 0 accessed as SRAM i/f */
+#define SH7750_BCR1_A0BST_ROM4 0x0800 /* Area 0 accessed as burst ROM
+ interface, 4 cosequtive access */
+#define SH7750_BCR1_A0BST_ROM8 0x1000 /* Area 0 accessed as burst ROM
+ interface, 8 cosequtive access */
+#define SH7750_BCR1_A0BST_ROM16 0x1800 /* Area 0 accessed as burst ROM
+ interface, 16 cosequtive access */
+#define SH7750_BCR1_A0BST_ROM32 0x2000 /* Area 0 accessed as burst ROM
+ interface, 32 cosequtive access */
+
+#define SH7750_BCR1_A5BST 0x00000700 /* Area 5 Burst ROM Control */
+#define SH7750_BCR1_A5BST_SRAM 0x0000 /* Area 5 accessed as SRAM i/f */
+#define SH7750_BCR1_A5BST_ROM4 0x0100 /* Area 5 accessed as burst ROM
+ interface, 4 cosequtive access */
+#define SH7750_BCR1_A5BST_ROM8 0x0200 /* Area 5 accessed as burst ROM
+ interface, 8 cosequtive access */
+#define SH7750_BCR1_A5BST_ROM16 0x0300 /* Area 5 accessed as burst ROM
+ interface, 16 cosequtive access */
+#define SH7750_BCR1_A5BST_ROM32 0x0400 /* Area 5 accessed as burst ROM
+ interface, 32 cosequtive access */
+
+#define SH7750_BCR1_A6BST 0x000000E0 /* Area 6 Burst ROM Control */
+#define SH7750_BCR1_A6BST_SRAM 0x0000 /* Area 6 accessed as SRAM i/f */
+#define SH7750_BCR1_A6BST_ROM4 0x0020 /* Area 6 accessed as burst ROM
+ interface, 4 cosequtive access */
+#define SH7750_BCR1_A6BST_ROM8 0x0040 /* Area 6 accessed as burst ROM
+ interface, 8 cosequtive access */
+#define SH7750_BCR1_A6BST_ROM16 0x0060 /* Area 6 accessed as burst ROM
+ interface, 16 cosequtive access */
+#define SH7750_BCR1_A6BST_ROM32 0x0080 /* Area 6 accessed as burst ROM
+ interface, 32 cosequtive access */
+
+#define SH7750_BCR1_DRAMTP 0x001C /* Area 2 and 3 Memory Type */
+#define SH7750_BCR1_DRAMTP_2SRAM_3SRAM 0x0000 /* Area 2 and 3 are SRAM or MPX
+ interface. */
+#define SH7750_BCR1_DRAMTP_2SRAM_3SDRAM 0x0008 /* Area 2 - SRAM/MPX, Area 3 -
+ synchronous DRAM */
+#define SH7750_BCR1_DRAMTP_2SDRAM_3SDRAM 0x000C /* Area 2 and 3 are synchronous
+ DRAM interface */
+#define SH7750_BCR1_DRAMTP_2SRAM_3DRAM 0x0010 /* Area 2 - SRAM/MPX, Area 3 -
+ DRAM interface */
+#define SH7750_BCR1_DRAMTP_2DRAM_3DRAM 0x0014 /* Area 2 and 3 are DRAM
+ interface */
+
+#define SH7750_BCR1_A56PCM 0x00000001 /* Area 5 and 6 Bus Type:
+ 0 - SRAM interface
+ 1 - PCMCIA interface */
+
+/* Bus Control Register 2 (half) - BCR2 */
+#define SH7750_BCR2_REGOFS 0x800004 /* offset */
+#define SH7750_BCR2 SH7750_P4_REG32(SH7750_BCR2_REGOFS)
+#define SH7750_BCR2_A7 SH7750_A7_REG32(SH7750_BCR2_REGOFS)
+
+#define SH7750_BCR2_A0SZ 0xC000 /* Area 0 Bus Width */
+#define SH7750_BCR2_A0SZ_S 14
+#define SH7750_BCR2_A6SZ 0x3000 /* Area 6 Bus Width */
+#define SH7750_BCR2_A6SZ_S 12
+#define SH7750_BCR2_A5SZ 0x0C00 /* Area 5 Bus Width */
+#define SH7750_BCR2_A5SZ_S 10
+#define SH7750_BCR2_A4SZ 0x0300 /* Area 4 Bus Width */
+#define SH7750_BCR2_A4SZ_S 8
+#define SH7750_BCR2_A3SZ 0x00C0 /* Area 3 Bus Width */
+#define SH7750_BCR2_A3SZ_S 6
+#define SH7750_BCR2_A2SZ 0x0030 /* Area 2 Bus Width */
+#define SH7750_BCR2_A2SZ_S 4
+#define SH7750_BCR2_A1SZ 0x000C /* Area 1 Bus Width */
+#define SH7750_BCR2_A1SZ_S 2
+#define SH7750_BCR2_SZ_64 0 /* 64 bits */
+#define SH7750_BCR2_SZ_8 1 /* 8 bits */
+#define SH7750_BCR2_SZ_16 2 /* 16 bits */
+#define SH7750_BCR2_SZ_32 3 /* 32 bits */
+#define SH7750_BCR2_PORTEN 0x0001 /* Port Function Enable :
+ 0 - D51-D32 are not used as a port
+ 1 - D51-D32 are used as a port */
+
+/* Wait Control Register 1 - WCR1 */
+#define SH7750_WCR1_REGOFS 0x800008 /* offset */
+#define SH7750_WCR1 SH7750_P4_REG32(SH7750_WCR1_REGOFS)
+#define SH7750_WCR1_A7 SH7750_A7_REG32(SH7750_WCR1_REGOFS)
+#define SH7750_WCR1_DMAIW 0x70000000 /* DACK Device Inter-Cycle Idle
+ specification */
+#define SH7750_WCR1_DMAIW_S 28
+#define SH7750_WCR1_A6IW 0x07000000 /* Area 6 Inter-Cycle Idle spec. */
+#define SH7750_WCR1_A6IW_S 24
+#define SH7750_WCR1_A5IW 0x00700000 /* Area 5 Inter-Cycle Idle spec. */
+#define SH7750_WCR1_A5IW_S 20
+#define SH7750_WCR1_A4IW 0x00070000 /* Area 4 Inter-Cycle Idle spec. */
+#define SH7750_WCR1_A4IW_S 16
+#define SH7750_WCR1_A3IW 0x00007000 /* Area 3 Inter-Cycle Idle spec. */
+#define SH7750_WCR1_A3IW_S 12
+#define SH7750_WCR1_A2IW 0x00000700 /* Area 2 Inter-Cycle Idle spec. */
+#define SH7750_WCR1_A2IW_S 8
+#define SH7750_WCR1_A1IW 0x00000070 /* Area 1 Inter-Cycle Idle spec. */
+#define SH7750_WCR1_A1IW_S 4
+#define SH7750_WCR1_A0IW 0x00000007 /* Area 0 Inter-Cycle Idle spec. */
+#define SH7750_WCR1_A0IW_S 0
+
+/* Wait Control Register 2 - WCR2 */
+#define SH7750_WCR2_REGOFS 0x80000C /* offset */
+#define SH7750_WCR2 SH7750_P4_REG32(SH7750_WCR2_REGOFS)
+#define SH7750_WCR2_A7 SH7750_A7_REG32(SH7750_WCR2_REGOFS)
+
+#define SH7750_WCR2_A6W 0xE0000000 /* Area 6 Wait Control */
+#define SH7750_WCR2_A6W_S 29
+#define SH7750_WCR2_A6B 0x1C000000 /* Area 6 Burst Pitch */
+#define SH7750_WCR2_A6B_S 26
+#define SH7750_WCR2_A5W 0x03800000 /* Area 5 Wait Control */
+#define SH7750_WCR2_A5W_S 23
+#define SH7750_WCR2_A5B 0x00700000 /* Area 5 Burst Pitch */
+#define SH7750_WCR2_A5B_S 20
+#define SH7750_WCR2_A4W 0x000E0000 /* Area 4 Wait Control */
+#define SH7750_WCR2_A4W_S 17
+#define SH7750_WCR2_A3W 0x0000E000 /* Area 3 Wait Control */
+#define SH7750_WCR2_A3W_S 13
+#define SH7750_WCR2_A2W 0x00000E00 /* Area 2 Wait Control */
+#define SH7750_WCR2_A2W_S 9
+#define SH7750_WCR2_A1W 0x000001C0 /* Area 1 Wait Control */
+#define SH7750_WCR2_A1W_S 6
+#define SH7750_WCR2_A0W 0x00000038 /* Area 0 Wait Control */
+#define SH7750_WCR2_A0W_S 3
+#define SH7750_WCR2_A0B 0x00000007 /* Area 0 Burst Pitch */
+#define SH7750_WCR2_A0B_S 0
+
+#define SH7750_WCR2_WS0 0 /* 0 wait states inserted */
+#define SH7750_WCR2_WS1 1 /* 1 wait states inserted */
+#define SH7750_WCR2_WS2 2 /* 2 wait states inserted */
+#define SH7750_WCR2_WS3 3 /* 3 wait states inserted */
+#define SH7750_WCR2_WS6 4 /* 6 wait states inserted */
+#define SH7750_WCR2_WS9 5 /* 9 wait states inserted */
+#define SH7750_WCR2_WS12 6 /* 12 wait states inserted */
+#define SH7750_WCR2_WS15 7 /* 15 wait states inserted */
+
+#define SH7750_WCR2_BPWS0 0 /* 0 wait states inserted from 2nd access */
+#define SH7750_WCR2_BPWS1 1 /* 1 wait states inserted from 2nd access */
+#define SH7750_WCR2_BPWS2 2 /* 2 wait states inserted from 2nd access */
+#define SH7750_WCR2_BPWS3 3 /* 3 wait states inserted from 2nd access */
+#define SH7750_WCR2_BPWS4 4 /* 4 wait states inserted from 2nd access */
+#define SH7750_WCR2_BPWS5 5 /* 5 wait states inserted from 2nd access */
+#define SH7750_WCR2_BPWS6 6 /* 6 wait states inserted from 2nd access */
+#define SH7750_WCR2_BPWS7 7 /* 7 wait states inserted from 2nd access */
+
+/* DRAM CAS\ Assertion Delay (area 3,2) */
+#define SH7750_WCR2_DRAM_CAS_ASW1 0 /* 1 cycle */
+#define SH7750_WCR2_DRAM_CAS_ASW2 1 /* 2 cycles */
+#define SH7750_WCR2_DRAM_CAS_ASW3 2 /* 3 cycles */
+#define SH7750_WCR2_DRAM_CAS_ASW4 3 /* 4 cycles */
+#define SH7750_WCR2_DRAM_CAS_ASW7 4 /* 7 cycles */
+#define SH7750_WCR2_DRAM_CAS_ASW10 5 /* 10 cycles */
+#define SH7750_WCR2_DRAM_CAS_ASW13 6 /* 13 cycles */
+#define SH7750_WCR2_DRAM_CAS_ASW16 7 /* 16 cycles */
+
+/* SDRAM CAS\ Latency Cycles */
+#define SH7750_WCR2_SDRAM_CAS_LAT1 1 /* 1 cycle */
+#define SH7750_WCR2_SDRAM_CAS_LAT2 2 /* 2 cycles */
+#define SH7750_WCR2_SDRAM_CAS_LAT3 3 /* 3 cycles */
+#define SH7750_WCR2_SDRAM_CAS_LAT4 4 /* 4 cycles */
+#define SH7750_WCR2_SDRAM_CAS_LAT5 5 /* 5 cycles */
+
+/* Wait Control Register 3 - WCR3 */
+#define SH7750_WCR3_REGOFS 0x800010 /* offset */
+#define SH7750_WCR3 SH7750_P4_REG32(SH7750_WCR3_REGOFS)
+#define SH7750_WCR3_A7 SH7750_A7_REG32(SH7750_WCR3_REGOFS)
+
+#define SH7750_WCR3_A6S 0x04000000 /* Area 6 Write Strobe Setup time */
+#define SH7750_WCR3_A6H 0x03000000 /* Area 6 Data Hold Time */
+#define SH7750_WCR3_A6H_S 24
+#define SH7750_WCR3_A5S 0x00400000 /* Area 5 Write Strobe Setup time */
+#define SH7750_WCR3_A5H 0x00300000 /* Area 5 Data Hold Time */
+#define SH7750_WCR3_A5H_S 20
+#define SH7750_WCR3_A4S 0x00040000 /* Area 4 Write Strobe Setup time */
+#define SH7750_WCR3_A4H 0x00030000 /* Area 4 Data Hold Time */
+#define SH7750_WCR3_A4H_S 16
+#define SH7750_WCR3_A3S 0x00004000 /* Area 3 Write Strobe Setup time */
+#define SH7750_WCR3_A3H 0x00003000 /* Area 3 Data Hold Time */
+#define SH7750_WCR3_A3H_S 12
+#define SH7750_WCR3_A2S 0x00000400 /* Area 2 Write Strobe Setup time */
+#define SH7750_WCR3_A2H 0x00000300 /* Area 2 Data Hold Time */
+#define SH7750_WCR3_A2H_S 8
+#define SH7750_WCR3_A1S 0x00000040 /* Area 1 Write Strobe Setup time */
+#define SH7750_WCR3_A1H 0x00000030 /* Area 1 Data Hold Time */
+#define SH7750_WCR3_A1H_S 4
+#define SH7750_WCR3_A0S 0x00000004 /* Area 0 Write Strobe Setup time */
+#define SH7750_WCR3_A0H 0x00000003 /* Area 0 Data Hold Time */
+#define SH7750_WCR3_A0H_S 0
+
+#define SH7750_WCR3_DHWS_0 0 /* 0 wait states data hold time */
+#define SH7750_WCR3_DHWS_1 1 /* 1 wait states data hold time */
+#define SH7750_WCR3_DHWS_2 2 /* 2 wait states data hold time */
+#define SH7750_WCR3_DHWS_3 3 /* 3 wait states data hold time */
+
+#define SH7750_MCR_REGOFS 0x800014 /* offset */
+#define SH7750_MCR SH7750_P4_REG32(SH7750_MCR_REGOFS)
+#define SH7750_MCR_A7 SH7750_A7_REG32(SH7750_MCR_REGOFS)
+
+#define SH7750_MCR_RASD 0x80000000 /* RAS Down mode */
+#define SH7750_MCR_MRSET 0x40000000 /* SDRAM Mode Register Set */
+#define SH7750_MCR_PALL 0x00000000 /* SDRAM Precharge All cmd. Mode */
+#define SH7750_MCR_TRC 0x38000000 /* RAS Precharge Time at End of
+ Refresh: */
+#define SH7750_MCR_TRC_0 0x00000000 /* 0 */
+#define SH7750_MCR_TRC_3 0x08000000 /* 3 */
+#define SH7750_MCR_TRC_6 0x10000000 /* 6 */
+#define SH7750_MCR_TRC_9 0x18000000 /* 9 */
+#define SH7750_MCR_TRC_12 0x20000000 /* 12 */
+#define SH7750_MCR_TRC_15 0x28000000 /* 15 */
+#define SH7750_MCR_TRC_18 0x30000000 /* 18 */
+#define SH7750_MCR_TRC_21 0x38000000 /* 21 */
+
+#define SH7750_MCR_TCAS 0x00800000 /* CAS Negation Period */
+#define SH7750_MCR_TCAS_1 0x00000000 /* 1 */
+#define SH7750_MCR_TCAS_2 0x00800000 /* 2 */
+
+#define SH7750_MCR_TPC 0x00380000 /* DRAM: RAS Precharge Period
+ SDRAM: minimum number of cycles
+ until the next bank active cmd
+ is output after precharging */
+#define SH7750_MCR_TPC_S 19
+#define SH7750_MCR_TPC_SDRAM_1 0x00000000 /* 1 cycle */
+#define SH7750_MCR_TPC_SDRAM_2 0x00080000 /* 2 cycles */
+#define SH7750_MCR_TPC_SDRAM_3 0x00100000 /* 3 cycles */
+#define SH7750_MCR_TPC_SDRAM_4 0x00180000 /* 4 cycles */
+#define SH7750_MCR_TPC_SDRAM_5 0x00200000 /* 5 cycles */
+#define SH7750_MCR_TPC_SDRAM_6 0x00280000 /* 6 cycles */
+#define SH7750_MCR_TPC_SDRAM_7 0x00300000 /* 7 cycles */
+#define SH7750_MCR_TPC_SDRAM_8 0x00380000 /* 8 cycles */
+
+#define SH7750_MCR_RCD 0x00030000 /* DRAM: RAS-CAS Assertion Delay time
+ SDRAM: bank active-read/write cmd
+ delay time */
+#define SH7750_MCR_RCD_DRAM_2 0x00000000 /* DRAM delay 2 clocks */
+#define SH7750_MCR_RCD_DRAM_3 0x00010000 /* DRAM delay 3 clocks */
+#define SH7750_MCR_RCD_DRAM_4 0x00020000 /* DRAM delay 4 clocks */
+#define SH7750_MCR_RCD_DRAM_5 0x00030000 /* DRAM delay 5 clocks */
+#define SH7750_MCR_RCD_SDRAM_2 0x00010000 /* DRAM delay 2 clocks */
+#define SH7750_MCR_RCD_SDRAM_3 0x00020000 /* DRAM delay 3 clocks */
+#define SH7750_MCR_RCD_SDRAM_4 0x00030000 /* DRAM delay 4 clocks */
+
+#define SH7750_MCR_TRWL 0x0000E000 /* SDRAM Write Precharge Delay */
+#define SH7750_MCR_TRWL_1 0x00000000 /* 1 */
+#define SH7750_MCR_TRWL_2 0x00002000 /* 2 */
+#define SH7750_MCR_TRWL_3 0x00004000 /* 3 */
+#define SH7750_MCR_TRWL_4 0x00006000 /* 4 */
+#define SH7750_MCR_TRWL_5 0x00008000 /* 5 */
+
+#define SH7750_MCR_TRAS 0x00001C00 /* DRAM: CAS-Before-RAS Refresh RAS
+ asserting period
+ SDRAM: Command interval after
+ synchronous DRAM refresh */
+#define SH7750_MCR_TRAS_DRAM_2 0x00000000 /* 2 */
+#define SH7750_MCR_TRAS_DRAM_3 0x00000400 /* 3 */
+#define SH7750_MCR_TRAS_DRAM_4 0x00000800 /* 4 */
+#define SH7750_MCR_TRAS_DRAM_5 0x00000C00 /* 5 */
+#define SH7750_MCR_TRAS_DRAM_6 0x00001000 /* 6 */
+#define SH7750_MCR_TRAS_DRAM_7 0x00001400 /* 7 */
+#define SH7750_MCR_TRAS_DRAM_8 0x00001800 /* 8 */
+#define SH7750_MCR_TRAS_DRAM_9 0x00001C00 /* 9 */
+
+#define SH7750_MCR_TRAS_SDRAM_TRC_4 0x00000000 /* 4 + TRC */
+#define SH7750_MCR_TRAS_SDRAM_TRC_5 0x00000400 /* 5 + TRC */
+#define SH7750_MCR_TRAS_SDRAM_TRC_6 0x00000800 /* 6 + TRC */
+#define SH7750_MCR_TRAS_SDRAM_TRC_7 0x00000C00 /* 7 + TRC */
+#define SH7750_MCR_TRAS_SDRAM_TRC_8 0x00001000 /* 8 + TRC */
+#define SH7750_MCR_TRAS_SDRAM_TRC_9 0x00001400 /* 9 + TRC */
+#define SH7750_MCR_TRAS_SDRAM_TRC_10 0x00001800 /* 10 + TRC */
+#define SH7750_MCR_TRAS_SDRAM_TRC_11 0x00001C00 /* 11 + TRC */
+
+#define SH7750_MCR_BE 0x00000200 /* Burst Enable */
+#define SH7750_MCR_SZ 0x00000180 /* Memory Data Size */
+#define SH7750_MCR_SZ_64 0x00000000 /* 64 bits */
+#define SH7750_MCR_SZ_16 0x00000100 /* 16 bits */
+#define SH7750_MCR_SZ_32 0x00000180 /* 32 bits */
+
+#define SH7750_MCR_AMX 0x00000078 /* Address Multiplexing */
+#define SH7750_MCR_AMX_S 3
+#define SH7750_MCR_AMX_DRAM_8BIT_COL 0x00000000 /* 8-bit column addr */
+#define SH7750_MCR_AMX_DRAM_9BIT_COL 0x00000008 /* 9-bit column addr */
+#define SH7750_MCR_AMX_DRAM_10BIT_COL 0x00000010 /* 10-bit column addr */
+#define SH7750_MCR_AMX_DRAM_11BIT_COL 0x00000018 /* 11-bit column addr */
+#define SH7750_MCR_AMX_DRAM_12BIT_COL 0x00000020 /* 12-bit column addr */
+/* See SH7750 Hardware Manual for SDRAM address multiplexor selection */
+
+#define SH7750_MCR_RFSH 0x00000004 /* Refresh Control */
+#define SH7750_MCR_RMODE 0x00000002 /* Refresh Mode: */
+#define SH7750_MCR_RMODE_NORMAL 0x00000000 /* Normal Refresh Mode */
+#define SH7750_MCR_RMODE_SELF 0x00000002 /* Self-Refresh Mode */
+#define SH7750_MCR_RMODE_EDO 0x00000001 /* EDO Mode */
+
+/* SDRAM Mode Set address */
+#define SH7750_SDRAM_MODE_A2_BASE 0xFF900000
+#define SH7750_SDRAM_MODE_A3_BASE 0xFF940000
+#define SH7750_SDRAM_MODE_A2_32BIT(x) (SH7750_SDRAM_MODE_A2_BASE + ((x) << 2))
+#define SH7750_SDRAM_MODE_A3_32BIT(x) (SH7750_SDRAM_MODE_A3_BASE + ((x) << 2))
+#define SH7750_SDRAM_MODE_A2_64BIT(x) (SH7750_SDRAM_MODE_A2_BASE + ((x) << 3))
+#define SH7750_SDRAM_MODE_A3_64BIT(x) (SH7750_SDRAM_MODE_A3_BASE + ((x) << 3))
+
+
+/* PCMCIA Control Register (half) - PCR */
+#define SH7750_PCR_REGOFS 0x800018 /* offset */
+#define SH7750_PCR SH7750_P4_REG32(SH7750_PCR_REGOFS)
+#define SH7750_PCR_A7 SH7750_A7_REG32(SH7750_PCR_REGOFS)
+
+#define SH7750_PCR_A5PCW 0xC000 /* Area 5 PCMCIA Wait - Number of wait
+ states to be added to the number of
+ waits specified by WCR2 in a low-speed
+ PCMCIA wait cycle */
+#define SH7750_PCR_A5PCW_0 0x0000 /* 0 waits inserted */
+#define SH7750_PCR_A5PCW_15 0x4000 /* 15 waits inserted */
+#define SH7750_PCR_A5PCW_30 0x8000 /* 30 waits inserted */
+#define SH7750_PCR_A5PCW_50 0xC000 /* 50 waits inserted */
+
+#define SH7750_PCR_A6PCW 0x3000 /* Area 6 PCMCIA Wait - Number of wait
+ states to be added to the number of
+ waits specified by WCR2 in a low-speed
+ PCMCIA wait cycle */
+#define SH7750_PCR_A6PCW_0 0x0000 /* 0 waits inserted */
+#define SH7750_PCR_A6PCW_15 0x1000 /* 15 waits inserted */
+#define SH7750_PCR_A6PCW_30 0x2000 /* 30 waits inserted */
+#define SH7750_PCR_A6PCW_50 0x3000 /* 50 waits inserted */
+
+#define SH7750_PCR_A5TED 0x0E00 /* Area 5 Address-OE\/WE\ Assertion Delay,
+ delay time from address output to
+ OE\/WE\ assertion on the connected
+ PCMCIA interface */
+#define SH7750_PCR_A5TED_S 9
+#define SH7750_PCR_A6TED 0x01C0 /* Area 6 Address-OE\/WE\ Assertion Delay */
+#define SH7750_PCR_A6TED_S 6
+
+#define SH7750_PCR_TED_0WS 0 /* 0 Waits inserted */
+#define SH7750_PCR_TED_1WS 1 /* 1 Waits inserted */
+#define SH7750_PCR_TED_2WS 2 /* 2 Waits inserted */
+#define SH7750_PCR_TED_3WS 3 /* 3 Waits inserted */
+#define SH7750_PCR_TED_6WS 4 /* 6 Waits inserted */
+#define SH7750_PCR_TED_9WS 5 /* 9 Waits inserted */
+#define SH7750_PCR_TED_12WS 6 /* 12 Waits inserted */
+#define SH7750_PCR_TED_15WS 7 /* 15 Waits inserted */
+
+#define SH7750_PCR_A5TEH 0x0038 /* Area 5 OE\/WE\ Negation Address delay,
+ address hold delay time from OE\/WE\
+ negation in a write on the connected
+ PCMCIA interface */
+#define SH7750_PCR_A5TEH_S 3
+
+#define SH7750_PCR_A6TEH 0x0007 /* Area 6 OE\/WE\ Negation Address delay */
+#define SH7750_PCR_A6TEH_S 0
+
+#define SH7750_PCR_TEH_0WS 0 /* 0 Waits inserted */
+#define SH7750_PCR_TEH_1WS 1 /* 1 Waits inserted */
+#define SH7750_PCR_TEH_2WS 2 /* 2 Waits inserted */
+#define SH7750_PCR_TEH_3WS 3 /* 3 Waits inserted */
+#define SH7750_PCR_TEH_6WS 4 /* 6 Waits inserted */
+#define SH7750_PCR_TEH_9WS 5 /* 9 Waits inserted */
+#define SH7750_PCR_TEH_12WS 6 /* 12 Waits inserted */
+#define SH7750_PCR_TEH_15WS 7 /* 15 Waits inserted */
+
+/* Refresh Timer Control/Status Register (half) - RTSCR */
+#define SH7750_RTCSR_REGOFS 0x80001C /* offset */
+#define SH7750_RTCSR SH7750_P4_REG32(SH7750_RTCSR_REGOFS)
+#define SH7750_RTCSR_A7 SH7750_A7_REG32(SH7750_RTCSR_REGOFS)
+
+#define SH7750_RTCSR_KEY 0xA500 /* RTCSR write key */
+#define SH7750_RTCSR_CMF 0x0080 /* Compare-Match Flag (indicates a
+ match between the refresh timer
+ counter and refresh time constant) */
+#define SH7750_RTCSR_CMIE 0x0040 /* Compare-Match Interrupt Enable */
+#define SH7750_RTCSR_CKS 0x0038 /* Refresh Counter Clock Selects */
+#define SH7750_RTCSR_CKS_DIS 0x0000 /* Clock Input Disabled */
+#define SH7750_RTCSR_CKS_CKIO_DIV4 0x0008 /* Bus Clock / 4 */
+#define SH7750_RTCSR_CKS_CKIO_DIV16 0x0010 /* Bus Clock / 16 */
+#define SH7750_RTCSR_CKS_CKIO_DIV64 0x0018 /* Bus Clock / 64 */
+#define SH7750_RTCSR_CKS_CKIO_DIV256 0x0020 /* Bus Clock / 256 */
+#define SH7750_RTCSR_CKS_CKIO_DIV1024 0x0028 /* Bus Clock / 1024 */
+#define SH7750_RTCSR_CKS_CKIO_DIV2048 0x0030 /* Bus Clock / 2048 */
+#define SH7750_RTCSR_CKS_CKIO_DIV4096 0x0038 /* Bus Clock / 4096 */
+
+#define SH7750_RTCSR_OVF 0x0004 /* Refresh Count Overflow Flag */
+#define SH7750_RTCSR_OVIE 0x0002 /* Refresh Count Overflow Interrupt
+ Enable */
+#define SH7750_RTCSR_LMTS 0x0001 /* Refresh Count Overflow Limit Select */
+#define SH7750_RTCSR_LMTS_1024 0x0000 /* Count Limit is 1024 */
+#define SH7750_RTCSR_LMTS_512 0x0001 /* Count Limit is 512 */
+
+/* Refresh Timer Counter (half) - RTCNT */
+#define SH7750_RTCNT_REGOFS 0x800020 /* offset */
+#define SH7750_RTCNT SH7750_P4_REG32(SH7750_RTCNT_REGOFS)
+#define SH7750_RTCNT_A7 SH7750_A7_REG32(SH7750_RTCNT_REGOFS)
+
+#define SH7750_RTCNT_KEY 0xA500 /* RTCNT write key */
+
+/* Refresh Time Constant Register (half) - RTCOR */
+#define SH7750_RTCOR_REGOFS 0x800024 /* offset */
+#define SH7750_RTCOR SH7750_P4_REG32(SH7750_RTCOR_REGOFS)
+#define SH7750_RTCOR_A7 SH7750_A7_REG32(SH7750_RTCOR_REGOFS)
+
+#define SH7750_RTCOR_KEY 0xA500 /* RTCOR write key */
+
+/* Refresh Count Register (half) - RFCR */
+#define SH7750_RFCR_REGOFS 0x800028 /* offset */
+#define SH7750_RFCR SH7750_P4_REG32(SH7750_RFCR_REGOFS)
+#define SH7750_RFCR_A7 SH7750_A7_REG32(SH7750_RFCR_REGOFS)
+
+#define SH7750_RFCR_KEY 0xA400 /* RFCR write key */
+
+/* Synchronous DRAM mode registers - SDMR */
+#define SH7750_SDMR2_REGOFS 0x900000 /* base offset */
+#define SH7750_SDMR2_REGNB 0x0FFC /* nb of register */
+#define SH7750_SDMR2 SH7750_P4_REG32(SH7750_SDMR2_REGOFS)
+#define SH7750_SDMR2_A7 SH7750_A7_REG32(SH7750_SDMR2_REGOFS)
+
+#define SH7750_SDMR3_REGOFS 0x940000 /* offset */
+#define SH7750_SDMR3_REGNB 0x0FFC /* nb of register */
+#define SH7750_SDMR3 SH7750_P4_REG32(SH7750_SDMR3_REGOFS)
+#define SH7750_SDMR3_A7 SH7750_A7_REG32(SH7750_SDMR3_REGOFS)
+
+/*
+ * Direct Memory Access Controller (DMAC)
+ */
+
+/* DMA Source Address Register - SAR0, SAR1, SAR2, SAR3 */
+#define SH7750_SAR_REGOFS(n) (0xA00000 + ((n)*16)) /* offset */
+#define SH7750_SAR(n) SH7750_P4_REG32(SH7750_SAR_REGOFS(n))
+#define SH7750_SAR_A7(n) SH7750_A7_REG32(SH7750_SAR_REGOFS(n))
+#define SH7750_SAR0 SH7750_SAR(0)
+#define SH7750_SAR1 SH7750_SAR(1)
+#define SH7750_SAR2 SH7750_SAR(2)
+#define SH7750_SAR3 SH7750_SAR(3)
+#define SH7750_SAR0_A7 SH7750_SAR_A7(0)
+#define SH7750_SAR1_A7 SH7750_SAR_A7(1)
+#define SH7750_SAR2_A7 SH7750_SAR_A7(2)
+#define SH7750_SAR3_A7 SH7750_SAR_A7(3)
+
+/* DMA Destination Address Register - DAR0, DAR1, DAR2, DAR3 */
+#define SH7750_DAR_REGOFS(n) (0xA00004 + ((n)*16)) /* offset */
+#define SH7750_DAR(n) SH7750_P4_REG32(SH7750_DAR_REGOFS(n))
+#define SH7750_DAR_A7(n) SH7750_A7_REG32(SH7750_DAR_REGOFS(n))
+#define SH7750_DAR0 SH7750_DAR(0)
+#define SH7750_DAR1 SH7750_DAR(1)
+#define SH7750_DAR2 SH7750_DAR(2)
+#define SH7750_DAR3 SH7750_DAR(3)
+#define SH7750_DAR0_A7 SH7750_DAR_A7(0)
+#define SH7750_DAR1_A7 SH7750_DAR_A7(1)
+#define SH7750_DAR2_A7 SH7750_DAR_A7(2)
+#define SH7750_DAR3_A7 SH7750_DAR_A7(3)
+
+/* DMA Transfer Count Register - DMATCR0, DMATCR1, DMATCR2, DMATCR3 */
+#define SH7750_DMATCR_REGOFS(n) (0xA00008 + ((n)*16)) /* offset */
+#define SH7750_DMATCR(n) SH7750_P4_REG32(SH7750_DMATCR_REGOFS(n))
+#define SH7750_DMATCR_A7(n) SH7750_A7_REG32(SH7750_DMATCR_REGOFS(n))
+#define SH7750_DMATCR0_P4 SH7750_DMATCR(0)
+#define SH7750_DMATCR1_P4 SH7750_DMATCR(1)
+#define SH7750_DMATCR2_P4 SH7750_DMATCR(2)
+#define SH7750_DMATCR3_P4 SH7750_DMATCR(3)
+#define SH7750_DMATCR0_A7 SH7750_DMATCR_A7(0)
+#define SH7750_DMATCR1_A7 SH7750_DMATCR_A7(1)
+#define SH7750_DMATCR2_A7 SH7750_DMATCR_A7(2)
+#define SH7750_DMATCR3_A7 SH7750_DMATCR_A7(3)
+
+/* DMA Channel Control Register - CHCR0, CHCR1, CHCR2, CHCR3 */
+#define SH7750_CHCR_REGOFS(n) (0xA0000C + ((n)*16)) /* offset */
+#define SH7750_CHCR(n) SH7750_P4_REG32(SH7750_CHCR_REGOFS(n))
+#define SH7750_CHCR_A7(n) SH7750_A7_REG32(SH7750_CHCR_REGOFS(n))
+#define SH7750_CHCR0 SH7750_CHCR(0)
+#define SH7750_CHCR1 SH7750_CHCR(1)
+#define SH7750_CHCR2 SH7750_CHCR(2)
+#define SH7750_CHCR3 SH7750_CHCR(3)
+#define SH7750_CHCR0_A7 SH7750_CHCR_A7(0)
+#define SH7750_CHCR1_A7 SH7750_CHCR_A7(1)
+#define SH7750_CHCR2_A7 SH7750_CHCR_A7(2)
+#define SH7750_CHCR3_A7 SH7750_CHCR_A7(3)
+
+#define SH7750_CHCR_SSA 0xE0000000 /* Source Address Space Attribute */
+#define SH7750_CHCR_SSA_PCMCIA 0x00000000 /* Reserved in PCMCIA access */
+#define SH7750_CHCR_SSA_DYNBSZ 0x20000000 /* Dynamic Bus Sizing I/O space */
+#define SH7750_CHCR_SSA_IO8 0x40000000 /* 8-bit I/O space */
+#define SH7750_CHCR_SSA_IO16 0x60000000 /* 16-bit I/O space */
+#define SH7750_CHCR_SSA_CMEM8 0x80000000 /* 8-bit common memory space */
+#define SH7750_CHCR_SSA_CMEM16 0xA0000000 /* 16-bit common memory space */
+#define SH7750_CHCR_SSA_AMEM8 0xC0000000 /* 8-bit attribute memory space */
+#define SH7750_CHCR_SSA_AMEM16 0xE0000000 /* 16-bit attribute memory space */
+
+#define SH7750_CHCR_STC 0x10000000 /* Source Address Wait Control Select,
+ specifies CS5 or CS6 space wait
+ control for PCMCIA access */
+
+#define SH7750_CHCR_DSA 0x0E000000 /* Source Address Space Attribute */
+#define SH7750_CHCR_DSA_PCMCIA 0x00000000 /* Reserved in PCMCIA access */
+#define SH7750_CHCR_DSA_DYNBSZ 0x02000000 /* Dynamic Bus Sizing I/O space */
+#define SH7750_CHCR_DSA_IO8 0x04000000 /* 8-bit I/O space */
+#define SH7750_CHCR_DSA_IO16 0x06000000 /* 16-bit I/O space */
+#define SH7750_CHCR_DSA_CMEM8 0x08000000 /* 8-bit common memory space */
+#define SH7750_CHCR_DSA_CMEM16 0x0A000000 /* 16-bit common memory space */
+#define SH7750_CHCR_DSA_AMEM8 0x0C000000 /* 8-bit attribute memory space */
+#define SH7750_CHCR_DSA_AMEM16 0x0E000000 /* 16-bit attribute memory space */
+
+#define SH7750_CHCR_DTC 0x01000000 /* Destination Address Wait Control
+ Select, specifies CS5 or CS6
+ space wait control for PCMCIA
+ access */
+
+#define SH7750_CHCR_DS 0x00080000 /* DREQ\ Select : */
+#define SH7750_CHCR_DS_LOWLVL 0x00000000 /* Low Level Detection */
+#define SH7750_CHCR_DS_FALL 0x00080000 /* Falling Edge Detection */
+
+#define SH7750_CHCR_RL 0x00040000 /* Request Check Level: */
+#define SH7750_CHCR_RL_ACTH 0x00000000 /* DRAK is an active high out */
+#define SH7750_CHCR_RL_ACTL 0x00040000 /* DRAK is an active low out */
+
+#define SH7750_CHCR_AM 0x00020000 /* Acknowledge Mode: */
+#define SH7750_CHCR_AM_RD 0x00000000 /* DACK is output in read cycle */
+#define SH7750_CHCR_AM_WR 0x00020000 /* DACK is output in write cycle */
+
+#define SH7750_CHCR_AL 0x00010000 /* Acknowledge Level: */
+#define SH7750_CHCR_AL_ACTH 0x00000000 /* DACK is an active high out */
+#define SH7750_CHCR_AL_ACTL 0x00010000 /* DACK is an active low out */
+
+#define SH7750_CHCR_DM 0x0000C000 /* Destination Address Mode: */
+#define SH7750_CHCR_DM_FIX 0x00000000 /* Destination Addr Fixed */
+#define SH7750_CHCR_DM_INC 0x00004000 /* Destination Addr Incremented */
+#define SH7750_CHCR_DM_DEC 0x00008000 /* Destination Addr Decremented */
+
+#define SH7750_CHCR_SM 0x00003000 /* Source Address Mode: */
+#define SH7750_CHCR_SM_FIX 0x00000000 /* Source Addr Fixed */
+#define SH7750_CHCR_SM_INC 0x00001000 /* Source Addr Incremented */
+#define SH7750_CHCR_SM_DEC 0x00002000 /* Source Addr Decremented */
+
+#define SH7750_CHCR_RS 0x00000F00 /* Request Source Select: */
+#define SH7750_CHCR_RS_ER_DA_EA_TO_EA 0x000 /* External Request, Dual Address
+ Mode (External Addr Space->
+ External Addr Space) */
+#define SH7750_CHCR_RS_ER_SA_EA_TO_ED 0x200 /* External Request, Single
+ Address Mode (External Addr
+ Space -> External Device) */
+#define SH7750_CHCR_RS_ER_SA_ED_TO_EA 0x300 /* External Request, Single
+ Address Mode, (External
+ Device -> External Addr
+ Space) */
+#define SH7750_CHCR_RS_AR_EA_TO_EA 0x400 /* Auto-Request (External Addr
+ Space -> External Addr Space) */
+
+#define SH7750_CHCR_RS_AR_EA_TO_OCP 0x500 /* Auto-Request (External Addr
+ Space -> On-chip Peripheral
+ Module) */
+#define SH7750_CHCR_RS_AR_OCP_TO_EA 0x600 /* Auto-Request (On-chip
+ Peripheral Module ->
+ External Addr Space */
+#define SH7750_CHCR_RS_SCITX_EA_TO_SC 0x800 /* SCI Transmit-Data-Empty intr
+ transfer request (external
+ address space -> SCTDR1) */
+#define SH7750_CHCR_RS_SCIRX_SC_TO_EA 0x900 /* SCI Receive-Data-Full intr
+ transfer request (SCRDR1 ->
+ External Addr Space) */
+#define SH7750_CHCR_RS_SCIFTX_EA_TO_SC 0xA00 /* SCIF Transmit-Data-Empty intr
+ transfer request (external
+ address space -> SCFTDR1) */
+#define SH7750_CHCR_RS_SCIFRX_SC_TO_EA 0xB00 /* SCIF Receive-Data-Full intr
+ transfer request (SCFRDR2 ->
+ External Addr Space) */
+#define SH7750_CHCR_RS_TMU2_EA_TO_EA 0xC00 /* TMU Channel 2 (input capture
+ interrupt), (external address
+ space -> external address
+ space) */
+#define SH7750_CHCR_RS_TMU2_EA_TO_OCP 0xD00 /* TMU Channel 2 (input capture
+ interrupt), (external address
+ space -> on-chip peripheral
+ module) */
+#define SH7750_CHCR_RS_TMU2_OCP_TO_EA 0xE00 /* TMU Channel 2 (input capture
+ interrupt), (on-chip
+ peripheral module -> external
+ address space) */
+
+#define SH7750_CHCR_TM 0x00000080 /* Transmit mode: */
+#define SH7750_CHCR_TM_CSTEAL 0x00000000 /* Cycle Steal Mode */
+#define SH7750_CHCR_TM_BURST 0x00000080 /* Burst Mode */
+
+#define SH7750_CHCR_TS 0x00000070 /* Transmit Size: */
+#define SH7750_CHCR_TS_QUAD 0x00000000 /* Quadword Size (64 bits) */
+#define SH7750_CHCR_TS_BYTE 0x00000010 /* Byte Size (8 bit) */
+#define SH7750_CHCR_TS_WORD 0x00000020 /* Word Size (16 bit) */
+#define SH7750_CHCR_TS_LONG 0x00000030 /* Longword Size (32 bit) */
+#define SH7750_CHCR_TS_BLOCK 0x00000040 /* 32-byte block transfer */
+
+#define SH7750_CHCR_IE 0x00000004 /* Interrupt Enable */
+#define SH7750_CHCR_TE 0x00000002 /* Transfer End */
+#define SH7750_CHCR_DE 0x00000001 /* DMAC Enable */
+
+/* DMA Operation Register - DMAOR */
+#define SH7750_DMAOR_REGOFS 0xA00040 /* offset */
+#define SH7750_DMAOR SH7750_P4_REG32(SH7750_DMAOR_REGOFS)
+#define SH7750_DMAOR_A7 SH7750_A7_REG32(SH7750_DMAOR_REGOFS)
+
+#define SH7750_DMAOR_DDT 0x00008000 /* On-Demand Data Transfer Mode */
+
+#define SH7750_DMAOR_PR 0x00000300 /* Priority Mode: */
+#define SH7750_DMAOR_PR_0123 0x00000000 /* CH0 > CH1 > CH2 > CH3 */
+#define SH7750_DMAOR_PR_0231 0x00000100 /* CH0 > CH2 > CH3 > CH1 */
+#define SH7750_DMAOR_PR_2013 0x00000200 /* CH2 > CH0 > CH1 > CH3 */
+#define SH7750_DMAOR_PR_RR 0x00000300 /* Round-robin mode */
+
+#define SH7750_DMAOR_COD 0x00000010 /* Check Overrun for DREQ\ */
+#define SH7750_DMAOR_AE 0x00000004 /* Address Error flag */
+#define SH7750_DMAOR_NMIF 0x00000002 /* NMI Flag */
+#define SH7750_DMAOR_DME 0x00000001 /* DMAC Master Enable */
+
+/*
+ * I/O Ports
+ */
+/* Port Control Register A - PCTRA */
+#define SH7750_PCTRA_REGOFS 0x80002C /* offset */
+#define SH7750_PCTRA SH7750_P4_REG32(SH7750_PCTRA_REGOFS)
+#define SH7750_PCTRA_A7 SH7750_A7_REG32(SH7750_PCTRA_REGOFS)
+
+#define SH7750_PCTRA_PBPUP(n) 0 /* Bit n is pulled up */
+#define SH7750_PCTRA_PBNPUP(n) (1 << ((n)*2+1)) /* Bit n is not pulled up */
+#define SH7750_PCTRA_PBINP(n) 0 /* Bit n is an input */
+#define SH7750_PCTRA_PBOUT(n) (1 << ((n)*2)) /* Bit n is an output */
+
+/* Port Data Register A - PDTRA(half) */
+#define SH7750_PDTRA_REGOFS 0x800030 /* offset */
+#define SH7750_PDTRA SH7750_P4_REG32(SH7750_PDTRA_REGOFS)
+#define SH7750_PDTRA_A7 SH7750_A7_REG32(SH7750_PDTRA_REGOFS)
+
+#define SH7750_PDTRA_BIT(n) (1 << (n))
+
+/* Port Control Register B - PCTRB */
+#define SH7750_PCTRB_REGOFS 0x800040 /* offset */
+#define SH7750_PCTRB SH7750_P4_REG32(SH7750_PCTRB_REGOFS)
+#define SH7750_PCTRB_A7 SH7750_A7_REG32(SH7750_PCTRB_REGOFS)
+
+#define SH7750_PCTRB_PBPUP(n) 0 /* Bit n is pulled up */
+#define SH7750_PCTRB_PBNPUP(n) (1 << ((n-16)*2+1)) /* Bit n is not pulled up */
+#define SH7750_PCTRB_PBINP(n) 0 /* Bit n is an input */
+#define SH7750_PCTRB_PBOUT(n) (1 << ((n-16)*2)) /* Bit n is an output */
+
+/* Port Data Register B - PDTRB(half) */
+#define SH7750_PDTRB_REGOFS 0x800044 /* offset */
+#define SH7750_PDTRB SH7750_P4_REG32(SH7750_PDTRB_REGOFS)
+#define SH7750_PDTRB_A7 SH7750_A7_REG32(SH7750_PDTRB_REGOFS)
+
+#define SH7750_PDTRB_BIT(n) (1 << ((n)-16))
+
+/* GPIO Interrupt Control Register - GPIOIC(half) */
+#define SH7750_GPIOIC_REGOFS 0x800048 /* offset */
+#define SH7750_GPIOIC SH7750_P4_REG32(SH7750_GPIOIC_REGOFS)
+#define SH7750_GPIOIC_A7 SH7750_A7_REG32(SH7750_GPIOIC_REGOFS)
+
+#define SH7750_GPIOIC_PTIREN(n) (1 << (n)) /* Port n is used as a GPIO int */
+
+/*
+ * Interrupt Controller - INTC
+ */
+/* Interrupt Control Register - ICR (half) */
+#define SH7750_ICR_REGOFS 0xD00000 /* offset */
+#define SH7750_ICR SH7750_P4_REG32(SH7750_ICR_REGOFS)
+#define SH7750_ICR_A7 SH7750_A7_REG32(SH7750_ICR_REGOFS)
+
+#define SH7750_ICR_NMIL 0x8000 /* NMI Input Level */
+#define SH7750_ICR_MAI 0x4000 /* NMI Interrupt Mask */
+
+#define SH7750_ICR_NMIB 0x0200 /* NMI Block Mode: */
+#define SH7750_ICR_NMIB_BLK 0x0000 /* NMI requests held pending while
+ SR.BL bit is set to 1 */
+#define SH7750_ICR_NMIB_NBLK 0x0200 /* NMI requests detected when SR.BL bit
+ set to 1 */
+
+#define SH7750_ICR_NMIE 0x0100 /* NMI Edge Select: */
+#define SH7750_ICR_NMIE_FALL 0x0000 /* Interrupt request detected on falling
+ edge of NMI input */
+#define SH7750_ICR_NMIE_RISE 0x0100 /* Interrupt request detected on rising
+ edge of NMI input */
+
+#define SH7750_ICR_IRLM 0x0080 /* IRL Pin Mode: */
+#define SH7750_ICR_IRLM_ENC 0x0000 /* IRL\ pins used as a level-encoded
+ interrupt requests */
+#define SH7750_ICR_IRLM_RAW 0x0080 /* IRL\ pins used as a four independent
+ interrupt requests */
+
+/*
+ * User Break Controller registers
+ */
+#define SH7750_BARA 0x200000 /* Break address regiser A */
+#define SH7750_BAMRA 0x200004 /* Break address mask regiser A */
+#define SH7750_BBRA 0x200008 /* Break bus cycle regiser A */
+#define SH7750_BARB 0x20000c /* Break address regiser B */
+#define SH7750_BAMRB 0x200010 /* Break address mask regiser B */
+#define SH7750_BBRB 0x200014 /* Break bus cycle regiser B */
+#define SH7750_BASRB 0x000018 /* Break ASID regiser B */
+#define SH7750_BDRB 0x200018 /* Break data regiser B */
+#define SH7750_BDMRB 0x20001c /* Break data mask regiser B */
+#define SH7750_BRCR 0x200020 /* Break control register */
+
+#define SH7750_BRCR_UDBE 0x0001 /* User break debug enable bit */
+
+/*
+ * Missing in RTEMS, added for QEMU
+ */
+#define SH7750_BCR3_A7 0x1f800050
+#define SH7750_BCR4_A7 0x1e0a00f0
+
+#endif
diff --git a/hw/sh4/sh_pci.c b/hw/sh4/sh_pci.c
new file mode 100644
index 00000000..a2f6d9e0
--- /dev/null
+++ b/hw/sh4/sh_pci.c
@@ -0,0 +1,204 @@
+/*
+ * SuperH on-chip PCIC emulation.
+ *
+ * Copyright (c) 2008 Takashi YOSHII
+ *
+ * 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.
+ */
+#include "hw/sysbus.h"
+#include "hw/sh4/sh.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "qemu/bswap.h"
+#include "exec/address-spaces.h"
+
+#define TYPE_SH_PCI_HOST_BRIDGE "sh_pci"
+
+#define SH_PCI_HOST_BRIDGE(obj) \
+ OBJECT_CHECK(SHPCIState, (obj), TYPE_SH_PCI_HOST_BRIDGE)
+
+typedef struct SHPCIState {
+ PCIHostState parent_obj;
+
+ PCIDevice *dev;
+ qemu_irq irq[4];
+ MemoryRegion memconfig_p4;
+ MemoryRegion memconfig_a7;
+ MemoryRegion isa;
+ uint32_t par;
+ uint32_t mbr;
+ uint32_t iobr;
+} SHPCIState;
+
+static void sh_pci_reg_write (void *p, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ SHPCIState *pcic = p;
+ PCIHostState *phb = PCI_HOST_BRIDGE(pcic);
+
+ switch(addr) {
+ case 0 ... 0xfc:
+ cpu_to_le32w((uint32_t*)(pcic->dev->config + addr), val);
+ break;
+ case 0x1c0:
+ pcic->par = val;
+ break;
+ case 0x1c4:
+ pcic->mbr = val & 0xff000001;
+ break;
+ case 0x1c8:
+ if ((val & 0xfffc0000) != (pcic->iobr & 0xfffc0000)) {
+ memory_region_del_subregion(get_system_memory(), &pcic->isa);
+ pcic->iobr = val & 0xfffc0001;
+ memory_region_add_subregion(get_system_memory(),
+ pcic->iobr & 0xfffc0000, &pcic->isa);
+ }
+ break;
+ case 0x220:
+ pci_data_write(phb->bus, pcic->par, val, 4);
+ break;
+ }
+}
+
+static uint64_t sh_pci_reg_read (void *p, hwaddr addr,
+ unsigned size)
+{
+ SHPCIState *pcic = p;
+ PCIHostState *phb = PCI_HOST_BRIDGE(pcic);
+
+ switch(addr) {
+ case 0 ... 0xfc:
+ return le32_to_cpup((uint32_t*)(pcic->dev->config + addr));
+ case 0x1c0:
+ return pcic->par;
+ case 0x1c4:
+ return pcic->mbr;
+ case 0x1c8:
+ return pcic->iobr;
+ case 0x220:
+ return pci_data_read(phb->bus, pcic->par, 4);
+ }
+ return 0;
+}
+
+static const MemoryRegionOps sh_pci_reg_ops = {
+ .read = sh_pci_reg_read,
+ .write = sh_pci_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static int sh_pci_map_irq(PCIDevice *d, int irq_num)
+{
+ return (d->devfn >> 3);
+}
+
+static void sh_pci_set_irq(void *opaque, int irq_num, int level)
+{
+ qemu_irq *pic = opaque;
+
+ qemu_set_irq(pic[irq_num], level);
+}
+
+static int sh_pci_device_init(SysBusDevice *dev)
+{
+ PCIHostState *phb;
+ SHPCIState *s;
+ int i;
+
+ s = SH_PCI_HOST_BRIDGE(dev);
+ phb = PCI_HOST_BRIDGE(s);
+ for (i = 0; i < 4; i++) {
+ sysbus_init_irq(dev, &s->irq[i]);
+ }
+ phb->bus = pci_register_bus(DEVICE(dev), "pci",
+ sh_pci_set_irq, sh_pci_map_irq,
+ s->irq,
+ get_system_memory(),
+ get_system_io(),
+ PCI_DEVFN(0, 0), 4, TYPE_PCI_BUS);
+ memory_region_init_io(&s->memconfig_p4, OBJECT(s), &sh_pci_reg_ops, s,
+ "sh_pci", 0x224);
+ memory_region_init_alias(&s->memconfig_a7, OBJECT(s), "sh_pci.2",
+ &s->memconfig_p4, 0, 0x224);
+ memory_region_init_alias(&s->isa, OBJECT(s), "sh_pci.isa",
+ get_system_io(), 0, 0x40000);
+ sysbus_init_mmio(dev, &s->memconfig_p4);
+ sysbus_init_mmio(dev, &s->memconfig_a7);
+ s->iobr = 0xfe240000;
+ memory_region_add_subregion(get_system_memory(), s->iobr, &s->isa);
+
+ s->dev = pci_create_simple(phb->bus, PCI_DEVFN(0, 0), "sh_pci_host");
+ return 0;
+}
+
+static int sh_pci_host_init(PCIDevice *d)
+{
+ pci_set_word(d->config + PCI_COMMAND, PCI_COMMAND_WAIT);
+ pci_set_word(d->config + PCI_STATUS, PCI_STATUS_CAP_LIST |
+ PCI_STATUS_FAST_BACK | PCI_STATUS_DEVSEL_MEDIUM);
+ return 0;
+}
+
+static void sh_pci_host_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->init = sh_pci_host_init;
+ k->vendor_id = PCI_VENDOR_ID_HITACHI;
+ k->device_id = PCI_DEVICE_ID_HITACHI_SH7751R;
+ /*
+ * PCI-facing part of the host bridge, not usable without the
+ * host-facing part, which can't be device_add'ed, yet.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo sh_pci_host_info = {
+ .name = "sh_pci_host",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIDevice),
+ .class_init = sh_pci_host_class_init,
+};
+
+static void sh_pci_device_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = sh_pci_device_init;
+}
+
+static const TypeInfo sh_pci_device_info = {
+ .name = TYPE_SH_PCI_HOST_BRIDGE,
+ .parent = TYPE_PCI_HOST_BRIDGE,
+ .instance_size = sizeof(SHPCIState),
+ .class_init = sh_pci_device_class_init,
+};
+
+static void sh_pci_register_types(void)
+{
+ type_register_static(&sh_pci_device_info);
+ type_register_static(&sh_pci_host_info);
+}
+
+type_init(sh_pci_register_types)
diff --git a/hw/sh4/shix.c b/hw/sh4/shix.c
new file mode 100644
index 00000000..f93f98e5
--- /dev/null
+++ b/hw/sh4/shix.c
@@ -0,0 +1,102 @@
+/*
+ * SHIX 2.0 board description
+ *
+ * Copyright (c) 2005 Samuel Tardieu
+ *
+ * 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.
+ */
+/*
+ Shix 2.0 board by Alexis Polti, described at
+ http://perso.enst.fr/~polti/realisations/shix20/
+
+ More information in target-sh4/README.sh4
+*/
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+
+#define BIOS_FILENAME "shix_bios.bin"
+#define BIOS_ADDRESS 0xA0000000
+
+static void shix_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ int ret;
+ SuperHCPU *cpu;
+ struct SH7750State *s;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *rom = g_new(MemoryRegion, 1);
+ MemoryRegion *sdram = g_new(MemoryRegion, 2);
+
+ if (!cpu_model)
+ cpu_model = "any";
+
+ cpu = cpu_sh4_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+
+ /* Allocate memory space */
+ memory_region_init_ram(rom, NULL, "shix.rom", 0x4000, &error_abort);
+ vmstate_register_ram_global(rom);
+ memory_region_set_readonly(rom, true);
+ memory_region_add_subregion(sysmem, 0x00000000, rom);
+ memory_region_init_ram(&sdram[0], NULL, "shix.sdram1", 0x01000000,
+ &error_abort);
+ vmstate_register_ram_global(&sdram[0]);
+ memory_region_add_subregion(sysmem, 0x08000000, &sdram[0]);
+ memory_region_init_ram(&sdram[1], NULL, "shix.sdram2", 0x01000000,
+ &error_abort);
+ vmstate_register_ram_global(&sdram[1]);
+ memory_region_add_subregion(sysmem, 0x0c000000, &sdram[1]);
+
+ /* Load BIOS in 0 (and access it through P2, 0xA0000000) */
+ if (bios_name == NULL)
+ bios_name = BIOS_FILENAME;
+ ret = load_image_targphys(bios_name, 0, 0x4000);
+ if (ret < 0 && !qtest_enabled()) {
+ error_report("Could not load SHIX bios '%s'", bios_name);
+ exit(1);
+ }
+
+ /* Register peripherals */
+ s = sh7750_init(cpu, sysmem);
+ /* XXXXX Check success */
+ tc58128_init(s, "shix_linux_nand.bin", NULL);
+}
+
+static QEMUMachine shix_machine = {
+ .name = "shix",
+ .desc = "shix card",
+ .init = shix_init,
+ .is_default = 1,
+};
+
+static void shix_machine_init(void)
+{
+ qemu_register_machine(&shix_machine);
+}
+
+machine_init(shix_machine_init);
diff --git a/hw/sparc/Makefile.objs b/hw/sparc/Makefile.objs
new file mode 100644
index 00000000..c987b5b5
--- /dev/null
+++ b/hw/sparc/Makefile.objs
@@ -0,0 +1 @@
+obj-y += sun4m.o leon3.o
diff --git a/hw/sparc/leon3.c b/hw/sparc/leon3.c
new file mode 100644
index 00000000..7f5dcd6d
--- /dev/null
+++ b/hw/sparc/leon3.c
@@ -0,0 +1,230 @@
+/*
+ * QEMU Leon3 System Emulator
+ *
+ * Copyright (c) 2010-2011 AdaCore
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "sysemu/char.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "trace.h"
+#include "exec/address-spaces.h"
+
+#include "hw/sparc/grlib.h"
+
+/* Default system clock. */
+#define CPU_CLK (40 * 1000 * 1000)
+
+#define PROM_FILENAME "u-boot.bin"
+
+#define MAX_PILS 16
+
+typedef struct ResetData {
+ SPARCCPU *cpu;
+ uint32_t entry; /* save kernel entry in case of reset */
+ target_ulong sp; /* initial stack pointer */
+} ResetData;
+
+static void main_cpu_reset(void *opaque)
+{
+ ResetData *s = (ResetData *)opaque;
+ CPUState *cpu = CPU(s->cpu);
+ CPUSPARCState *env = &s->cpu->env;
+
+ cpu_reset(cpu);
+
+ cpu->halted = 0;
+ env->pc = s->entry;
+ env->npc = s->entry + 4;
+ env->regbase[6] = s->sp;
+}
+
+void leon3_irq_ack(void *irq_manager, int intno)
+{
+ grlib_irqmp_ack((DeviceState *)irq_manager, intno);
+}
+
+static void leon3_set_pil_in(void *opaque, uint32_t pil_in)
+{
+ CPUSPARCState *env = (CPUSPARCState *)opaque;
+ CPUState *cs;
+
+ assert(env != NULL);
+
+ env->pil_in = pil_in;
+
+ if (env->pil_in && (env->interrupt_index == 0 ||
+ (env->interrupt_index & ~15) == TT_EXTINT)) {
+ unsigned int i;
+
+ for (i = 15; i > 0; i--) {
+ if (env->pil_in & (1 << i)) {
+ int old_interrupt = env->interrupt_index;
+
+ env->interrupt_index = TT_EXTINT | i;
+ if (old_interrupt != env->interrupt_index) {
+ cs = CPU(sparc_env_get_cpu(env));
+ trace_leon3_set_irq(i);
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ break;
+ }
+ }
+ } else if (!env->pil_in && (env->interrupt_index & ~15) == TT_EXTINT) {
+ cs = CPU(sparc_env_get_cpu(env));
+ trace_leon3_reset_irq(env->interrupt_index & 15);
+ env->interrupt_index = 0;
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+}
+
+static void leon3_generic_hw_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ SPARCCPU *cpu;
+ CPUSPARCState *env;
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ MemoryRegion *prom = g_new(MemoryRegion, 1);
+ int ret;
+ char *filename;
+ qemu_irq *cpu_irqs = NULL;
+ int bios_size;
+ int prom_size;
+ ResetData *reset_info;
+
+ /* Init CPU */
+ if (!cpu_model) {
+ cpu_model = "LEON3";
+ }
+
+ cpu = cpu_sparc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "qemu: Unable to find Sparc CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ cpu_sparc_set_id(env, 0);
+
+ /* Reset data */
+ reset_info = g_malloc0(sizeof(ResetData));
+ reset_info->cpu = cpu;
+ reset_info->sp = 0x40000000 + ram_size;
+ qemu_register_reset(main_cpu_reset, reset_info);
+
+ /* Allocate IRQ manager */
+ grlib_irqmp_create(0x80000200, env, &cpu_irqs, MAX_PILS, &leon3_set_pil_in);
+
+ env->qemu_irq_ack = leon3_irq_manager;
+
+ /* Allocate RAM */
+ if ((uint64_t)ram_size > (1UL << 30)) {
+ fprintf(stderr,
+ "qemu: Too much memory for this machine: %d, maximum 1G\n",
+ (unsigned int)(ram_size / (1024 * 1024)));
+ exit(1);
+ }
+
+ memory_region_allocate_system_memory(ram, NULL, "leon3.ram", ram_size);
+ memory_region_add_subregion(address_space_mem, 0x40000000, ram);
+
+ /* Allocate BIOS */
+ prom_size = 8 * 1024 * 1024; /* 8Mb */
+ memory_region_init_ram(prom, NULL, "Leon3.bios", prom_size, &error_abort);
+ vmstate_register_ram_global(prom);
+ memory_region_set_readonly(prom, true);
+ memory_region_add_subregion(address_space_mem, 0x00000000, prom);
+
+ /* Load boot prom */
+ if (bios_name == NULL) {
+ bios_name = PROM_FILENAME;
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+
+ bios_size = get_image_size(filename);
+
+ if (bios_size > prom_size) {
+ fprintf(stderr, "qemu: could not load prom '%s': file too big\n",
+ filename);
+ exit(1);
+ }
+
+ if (bios_size > 0) {
+ ret = load_image_targphys(filename, 0x00000000, bios_size);
+ if (ret < 0 || ret > prom_size) {
+ fprintf(stderr, "qemu: could not load prom '%s'\n", filename);
+ exit(1);
+ }
+ } else if (kernel_filename == NULL && !qtest_enabled()) {
+ fprintf(stderr, "Can't read bios image %s\n", filename);
+ exit(1);
+ }
+ g_free(filename);
+
+ /* Can directly load an application. */
+ if (kernel_filename != NULL) {
+ long kernel_size;
+ uint64_t entry;
+
+ kernel_size = load_elf(kernel_filename, NULL, NULL, &entry, NULL, NULL,
+ 1 /* big endian */, ELF_MACHINE, 0);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ if (bios_size <= 0) {
+ /* If there is no bios/monitor, start the application. */
+ env->pc = entry;
+ env->npc = entry + 4;
+ reset_info->entry = entry;
+ }
+ }
+
+ /* Allocate timers */
+ grlib_gptimer_create(0x80000300, 2, CPU_CLK, cpu_irqs, 6);
+
+ /* Allocate uart */
+ if (serial_hds[0]) {
+ grlib_apbuart_create(0x80000100, serial_hds[0], cpu_irqs[3]);
+ }
+}
+
+static QEMUMachine leon3_generic_machine = {
+ .name = "leon3_generic",
+ .desc = "Leon-3 generic",
+ .init = leon3_generic_hw_init,
+};
+
+static void leon3_machine_init(void)
+{
+ qemu_register_machine(&leon3_generic_machine);
+}
+
+machine_init(leon3_machine_init);
diff --git a/hw/sparc/sun4m.c b/hw/sparc/sun4m.c
new file mode 100644
index 00000000..68ac4d8b
--- /dev/null
+++ b/hw/sparc/sun4m.c
@@ -0,0 +1,1521 @@
+/*
+ * QEMU Sun4m & Sun4d & Sun4c System Emulator
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/sysbus.h"
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "hw/sparc/sun4m.h"
+#include "hw/timer/m48t59.h"
+#include "hw/sparc/sparc32_dma.h"
+#include "hw/block/fdc.h"
+#include "sysemu/sysemu.h"
+#include "net/net.h"
+#include "hw/boards.h"
+#include "hw/nvram/openbios_firmware_abi.h"
+#include "hw/scsi/esp.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/isa.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/char/escc.h"
+#include "hw/empty_slot.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "sysemu/block-backend.h"
+#include "trace.h"
+
+/*
+ * Sun4m architecture was used in the following machines:
+ *
+ * SPARCserver 6xxMP/xx
+ * SPARCclassic (SPARCclassic Server)(SPARCstation LC) (4/15),
+ * SPARCclassic X (4/10)
+ * SPARCstation LX/ZX (4/30)
+ * SPARCstation Voyager
+ * SPARCstation 10/xx, SPARCserver 10/xx
+ * SPARCstation 5, SPARCserver 5
+ * SPARCstation 20/xx, SPARCserver 20
+ * SPARCstation 4
+ *
+ * See for example: http://www.sunhelp.org/faq/sunref1.html
+ */
+
+#define KERNEL_LOAD_ADDR 0x00004000
+#define CMDLINE_ADDR 0x007ff000
+#define INITRD_LOAD_ADDR 0x00800000
+#define PROM_SIZE_MAX (1024 * 1024)
+#define PROM_VADDR 0xffd00000
+#define PROM_FILENAME "openbios-sparc32"
+#define CFG_ADDR 0xd00000510ULL
+#define FW_CFG_SUN4M_DEPTH (FW_CFG_ARCH_LOCAL + 0x00)
+#define FW_CFG_SUN4M_WIDTH (FW_CFG_ARCH_LOCAL + 0x01)
+#define FW_CFG_SUN4M_HEIGHT (FW_CFG_ARCH_LOCAL + 0x02)
+
+#define MAX_CPUS 16
+#define MAX_PILS 16
+#define MAX_VSIMMS 4
+
+#define ESCC_CLOCK 4915200
+
+struct sun4m_hwdef {
+ hwaddr iommu_base, iommu_pad_base, iommu_pad_len, slavio_base;
+ hwaddr intctl_base, counter_base, nvram_base, ms_kb_base;
+ hwaddr serial_base, fd_base;
+ hwaddr afx_base, idreg_base, dma_base, esp_base, le_base;
+ hwaddr tcx_base, cs_base, apc_base, aux1_base, aux2_base;
+ hwaddr bpp_base, dbri_base, sx_base;
+ struct {
+ hwaddr reg_base, vram_base;
+ } vsimm[MAX_VSIMMS];
+ hwaddr ecc_base;
+ uint64_t max_mem;
+ const char * const default_cpu_model;
+ uint32_t ecc_version;
+ uint32_t iommu_version;
+ uint16_t machine_id;
+ uint8_t nvram_machine_id;
+};
+
+int DMA_get_channel_mode (int nchan)
+{
+ return 0;
+}
+int DMA_read_memory (int nchan, void *buf, int pos, int size)
+{
+ return 0;
+}
+int DMA_write_memory (int nchan, void *buf, int pos, int size)
+{
+ return 0;
+}
+void DMA_hold_DREQ (int nchan) {}
+void DMA_release_DREQ (int nchan) {}
+void DMA_schedule(int nchan) {}
+
+void DMA_init(int high_page_enable, qemu_irq *cpu_request_exit)
+{
+}
+
+void DMA_register_channel (int nchan,
+ DMA_transfer_handler transfer_handler,
+ void *opaque)
+{
+}
+
+static void fw_cfg_boot_set(void *opaque, const char *boot_device,
+ Error **errp)
+{
+ fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]);
+}
+
+static void nvram_init(Nvram *nvram, uint8_t *macaddr,
+ const char *cmdline, const char *boot_devices,
+ ram_addr_t RAM_size, uint32_t kernel_size,
+ int width, int height, int depth,
+ int nvram_machine_id, const char *arch)
+{
+ unsigned int i;
+ uint32_t start, end;
+ uint8_t image[0x1ff0];
+ struct OpenBIOS_nvpart_v1 *part_header;
+ NvramClass *k = NVRAM_GET_CLASS(nvram);
+
+ memset(image, '\0', sizeof(image));
+
+ start = 0;
+
+ // OpenBIOS nvram variables
+ // Variable partition
+ part_header = (struct OpenBIOS_nvpart_v1 *)&image[start];
+ part_header->signature = OPENBIOS_PART_SYSTEM;
+ pstrcpy(part_header->name, sizeof(part_header->name), "system");
+
+ end = start + sizeof(struct OpenBIOS_nvpart_v1);
+ for (i = 0; i < nb_prom_envs; i++)
+ end = OpenBIOS_set_var(image, end, prom_envs[i]);
+
+ // End marker
+ image[end++] = '\0';
+
+ end = start + ((end - start + 15) & ~15);
+ OpenBIOS_finish_partition(part_header, end - start);
+
+ // free partition
+ start = end;
+ part_header = (struct OpenBIOS_nvpart_v1 *)&image[start];
+ part_header->signature = OPENBIOS_PART_FREE;
+ pstrcpy(part_header->name, sizeof(part_header->name), "free");
+
+ end = 0x1fd0;
+ OpenBIOS_finish_partition(part_header, end - start);
+
+ Sun_init_header((struct Sun_nvram *)&image[0x1fd8], macaddr,
+ nvram_machine_id);
+
+ for (i = 0; i < sizeof(image); i++) {
+ (k->write)(nvram, i, image[i]);
+ }
+}
+
+static DeviceState *slavio_intctl;
+
+void sun4m_hmp_info_pic(Monitor *mon, const QDict *qdict)
+{
+ if (slavio_intctl)
+ slavio_pic_info(mon, slavio_intctl);
+}
+
+void sun4m_hmp_info_irq(Monitor *mon, const QDict *qdict)
+{
+ if (slavio_intctl)
+ slavio_irq_info(mon, slavio_intctl);
+}
+
+void cpu_check_irqs(CPUSPARCState *env)
+{
+ CPUState *cs;
+
+ if (env->pil_in && (env->interrupt_index == 0 ||
+ (env->interrupt_index & ~15) == TT_EXTINT)) {
+ unsigned int i;
+
+ for (i = 15; i > 0; i--) {
+ if (env->pil_in & (1 << i)) {
+ int old_interrupt = env->interrupt_index;
+
+ env->interrupt_index = TT_EXTINT | i;
+ if (old_interrupt != env->interrupt_index) {
+ cs = CPU(sparc_env_get_cpu(env));
+ trace_sun4m_cpu_interrupt(i);
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ break;
+ }
+ }
+ } else if (!env->pil_in && (env->interrupt_index & ~15) == TT_EXTINT) {
+ cs = CPU(sparc_env_get_cpu(env));
+ trace_sun4m_cpu_reset_interrupt(env->interrupt_index & 15);
+ env->interrupt_index = 0;
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+}
+
+static void cpu_kick_irq(SPARCCPU *cpu)
+{
+ CPUSPARCState *env = &cpu->env;
+ CPUState *cs = CPU(cpu);
+
+ cs->halted = 0;
+ cpu_check_irqs(env);
+ qemu_cpu_kick(cs);
+}
+
+static void cpu_set_irq(void *opaque, int irq, int level)
+{
+ SPARCCPU *cpu = opaque;
+ CPUSPARCState *env = &cpu->env;
+
+ if (level) {
+ trace_sun4m_cpu_set_irq_raise(irq);
+ env->pil_in |= 1 << irq;
+ cpu_kick_irq(cpu);
+ } else {
+ trace_sun4m_cpu_set_irq_lower(irq);
+ env->pil_in &= ~(1 << irq);
+ cpu_check_irqs(env);
+ }
+}
+
+static void dummy_cpu_set_irq(void *opaque, int irq, int level)
+{
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ SPARCCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+
+ cpu_reset(cs);
+ cs->halted = 0;
+}
+
+static void secondary_cpu_reset(void *opaque)
+{
+ SPARCCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+
+ cpu_reset(cs);
+ cs->halted = 1;
+}
+
+static void cpu_halt_signal(void *opaque, int irq, int level)
+{
+ if (level && current_cpu) {
+ cpu_interrupt(current_cpu, CPU_INTERRUPT_HALT);
+ }
+}
+
+static uint64_t translate_kernel_address(void *opaque, uint64_t addr)
+{
+ return addr - 0xf0000000ULL;
+}
+
+static unsigned long sun4m_load_kernel(const char *kernel_filename,
+ const char *initrd_filename,
+ ram_addr_t RAM_size)
+{
+ int linux_boot;
+ unsigned int i;
+ long initrd_size, kernel_size;
+ uint8_t *ptr;
+
+ linux_boot = (kernel_filename != NULL);
+
+ kernel_size = 0;
+ if (linux_boot) {
+ int bswap_needed;
+
+#ifdef BSWAP_NEEDED
+ bswap_needed = 1;
+#else
+ bswap_needed = 0;
+#endif
+ kernel_size = load_elf(kernel_filename, translate_kernel_address, NULL,
+ NULL, NULL, NULL, 1, ELF_MACHINE, 0);
+ if (kernel_size < 0)
+ kernel_size = load_aout(kernel_filename, KERNEL_LOAD_ADDR,
+ RAM_size - KERNEL_LOAD_ADDR, bswap_needed,
+ TARGET_PAGE_SIZE);
+ if (kernel_size < 0)
+ kernel_size = load_image_targphys(kernel_filename,
+ KERNEL_LOAD_ADDR,
+ RAM_size - KERNEL_LOAD_ADDR);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+
+ /* load initrd */
+ initrd_size = 0;
+ if (initrd_filename) {
+ initrd_size = load_image_targphys(initrd_filename,
+ INITRD_LOAD_ADDR,
+ RAM_size - INITRD_LOAD_ADDR);
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ }
+ if (initrd_size > 0) {
+ for (i = 0; i < 64 * TARGET_PAGE_SIZE; i += TARGET_PAGE_SIZE) {
+ ptr = rom_ptr(KERNEL_LOAD_ADDR + i);
+ if (ldl_p(ptr) == 0x48647253) { // HdrS
+ stl_p(ptr + 16, INITRD_LOAD_ADDR);
+ stl_p(ptr + 20, initrd_size);
+ break;
+ }
+ }
+ }
+ }
+ return kernel_size;
+}
+
+static void *iommu_init(hwaddr addr, uint32_t version, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "iommu");
+ qdev_prop_set_uint32(dev, "version", version);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irq);
+ sysbus_mmio_map(s, 0, addr);
+
+ return s;
+}
+
+static void *sparc32_dma_init(hwaddr daddr, qemu_irq parent_irq,
+ void *iommu, qemu_irq *dev_irq, int is_ledma)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "sparc32_dma");
+ qdev_prop_set_ptr(dev, "iommu_opaque", iommu);
+ qdev_prop_set_uint32(dev, "is_ledma", is_ledma);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, parent_irq);
+ *dev_irq = qdev_get_gpio_in(dev, 0);
+ sysbus_mmio_map(s, 0, daddr);
+
+ return s;
+}
+
+static void lance_init(NICInfo *nd, hwaddr leaddr,
+ void *dma_opaque, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ qemu_irq reset;
+
+ qemu_check_nic_model(&nd_table[0], "lance");
+
+ dev = qdev_create(NULL, "lance");
+ qdev_set_nic_properties(dev, nd);
+ qdev_prop_set_ptr(dev, "dma", dma_opaque);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, leaddr);
+ sysbus_connect_irq(s, 0, irq);
+ reset = qdev_get_gpio_in(dev, 0);
+ qdev_connect_gpio_out(dma_opaque, 0, reset);
+}
+
+static DeviceState *slavio_intctl_init(hwaddr addr,
+ hwaddr addrg,
+ qemu_irq **parent_irq)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ unsigned int i, j;
+
+ dev = qdev_create(NULL, "slavio_intctl");
+ qdev_init_nofail(dev);
+
+ s = SYS_BUS_DEVICE(dev);
+
+ for (i = 0; i < MAX_CPUS; i++) {
+ for (j = 0; j < MAX_PILS; j++) {
+ sysbus_connect_irq(s, i * MAX_PILS + j, parent_irq[i][j]);
+ }
+ }
+ sysbus_mmio_map(s, 0, addrg);
+ for (i = 0; i < MAX_CPUS; i++) {
+ sysbus_mmio_map(s, i + 1, addr + i * TARGET_PAGE_SIZE);
+ }
+
+ return dev;
+}
+
+#define SYS_TIMER_OFFSET 0x10000ULL
+#define CPU_TIMER_OFFSET(cpu) (0x1000ULL * cpu)
+
+static void slavio_timer_init_all(hwaddr addr, qemu_irq master_irq,
+ qemu_irq *cpu_irqs, unsigned int num_cpus)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ unsigned int i;
+
+ dev = qdev_create(NULL, "slavio_timer");
+ qdev_prop_set_uint32(dev, "num_cpus", num_cpus);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, master_irq);
+ sysbus_mmio_map(s, 0, addr + SYS_TIMER_OFFSET);
+
+ for (i = 0; i < MAX_CPUS; i++) {
+ sysbus_mmio_map(s, i + 1, addr + (hwaddr)CPU_TIMER_OFFSET(i));
+ sysbus_connect_irq(s, i + 1, cpu_irqs[i]);
+ }
+}
+
+static qemu_irq slavio_system_powerdown;
+
+static void slavio_powerdown_req(Notifier *n, void *opaque)
+{
+ qemu_irq_raise(slavio_system_powerdown);
+}
+
+static Notifier slavio_system_powerdown_notifier = {
+ .notify = slavio_powerdown_req
+};
+
+#define MISC_LEDS 0x01600000
+#define MISC_CFG 0x01800000
+#define MISC_DIAG 0x01a00000
+#define MISC_MDM 0x01b00000
+#define MISC_SYS 0x01f00000
+
+static void slavio_misc_init(hwaddr base,
+ hwaddr aux1_base,
+ hwaddr aux2_base, qemu_irq irq,
+ qemu_irq fdc_tc)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "slavio_misc");
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ if (base) {
+ /* 8 bit registers */
+ /* Slavio control */
+ sysbus_mmio_map(s, 0, base + MISC_CFG);
+ /* Diagnostics */
+ sysbus_mmio_map(s, 1, base + MISC_DIAG);
+ /* Modem control */
+ sysbus_mmio_map(s, 2, base + MISC_MDM);
+ /* 16 bit registers */
+ /* ss600mp diag LEDs */
+ sysbus_mmio_map(s, 3, base + MISC_LEDS);
+ /* 32 bit registers */
+ /* System control */
+ sysbus_mmio_map(s, 4, base + MISC_SYS);
+ }
+ if (aux1_base) {
+ /* AUX 1 (Misc System Functions) */
+ sysbus_mmio_map(s, 5, aux1_base);
+ }
+ if (aux2_base) {
+ /* AUX 2 (Software Powerdown Control) */
+ sysbus_mmio_map(s, 6, aux2_base);
+ }
+ sysbus_connect_irq(s, 0, irq);
+ sysbus_connect_irq(s, 1, fdc_tc);
+ slavio_system_powerdown = qdev_get_gpio_in(dev, 0);
+ qemu_register_powerdown_notifier(&slavio_system_powerdown_notifier);
+}
+
+static void ecc_init(hwaddr base, qemu_irq irq, uint32_t version)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "eccmemctl");
+ qdev_prop_set_uint32(dev, "version", version);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irq);
+ sysbus_mmio_map(s, 0, base);
+ if (version == 0) { // SS-600MP only
+ sysbus_mmio_map(s, 1, base + 0x1000);
+ }
+}
+
+static void apc_init(hwaddr power_base, qemu_irq cpu_halt)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "apc");
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ /* Power management (APC) XXX: not a Slavio device */
+ sysbus_mmio_map(s, 0, power_base);
+ sysbus_connect_irq(s, 0, cpu_halt);
+}
+
+static void tcx_init(hwaddr addr, qemu_irq irq, int vram_size, int width,
+ int height, int depth)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "SUNW,tcx");
+ qdev_prop_set_uint32(dev, "vram_size", vram_size);
+ qdev_prop_set_uint16(dev, "width", width);
+ qdev_prop_set_uint16(dev, "height", height);
+ qdev_prop_set_uint16(dev, "depth", depth);
+ qdev_prop_set_uint64(dev, "prom_addr", addr);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ /* 10/ROM : FCode ROM */
+ sysbus_mmio_map(s, 0, addr);
+ /* 2/STIP : Stipple */
+ sysbus_mmio_map(s, 1, addr + 0x04000000ULL);
+ /* 3/BLIT : Blitter */
+ sysbus_mmio_map(s, 2, addr + 0x06000000ULL);
+ /* 5/RSTIP : Raw Stipple */
+ sysbus_mmio_map(s, 3, addr + 0x0c000000ULL);
+ /* 6/RBLIT : Raw Blitter */
+ sysbus_mmio_map(s, 4, addr + 0x0e000000ULL);
+ /* 7/TEC : Transform Engine */
+ sysbus_mmio_map(s, 5, addr + 0x00700000ULL);
+ /* 8/CMAP : DAC */
+ sysbus_mmio_map(s, 6, addr + 0x00200000ULL);
+ /* 9/THC : */
+ if (depth == 8) {
+ sysbus_mmio_map(s, 7, addr + 0x00300000ULL);
+ } else {
+ sysbus_mmio_map(s, 7, addr + 0x00301000ULL);
+ }
+ /* 11/DHC : */
+ sysbus_mmio_map(s, 8, addr + 0x00240000ULL);
+ /* 12/ALT : */
+ sysbus_mmio_map(s, 9, addr + 0x00280000ULL);
+ /* 0/DFB8 : 8-bit plane */
+ sysbus_mmio_map(s, 10, addr + 0x00800000ULL);
+ /* 1/DFB24 : 24bit plane */
+ sysbus_mmio_map(s, 11, addr + 0x02000000ULL);
+ /* 4/RDFB32: Raw framebuffer. Control plane */
+ sysbus_mmio_map(s, 12, addr + 0x0a000000ULL);
+ /* 9/THC24bits : NetBSD writes here even with 8-bit display: dummy */
+ if (depth == 8) {
+ sysbus_mmio_map(s, 13, addr + 0x00301000ULL);
+ }
+
+ sysbus_connect_irq(s, 0, irq);
+}
+
+static void cg3_init(hwaddr addr, qemu_irq irq, int vram_size, int width,
+ int height, int depth)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, "cgthree");
+ qdev_prop_set_uint32(dev, "vram-size", vram_size);
+ qdev_prop_set_uint16(dev, "width", width);
+ qdev_prop_set_uint16(dev, "height", height);
+ qdev_prop_set_uint16(dev, "depth", depth);
+ qdev_prop_set_uint64(dev, "prom-addr", addr);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ /* FCode ROM */
+ sysbus_mmio_map(s, 0, addr);
+ /* DAC */
+ sysbus_mmio_map(s, 1, addr + 0x400000ULL);
+ /* 8-bit plane */
+ sysbus_mmio_map(s, 2, addr + 0x800000ULL);
+
+ sysbus_connect_irq(s, 0, irq);
+}
+
+/* NCR89C100/MACIO Internal ID register */
+
+#define TYPE_MACIO_ID_REGISTER "macio_idreg"
+
+static const uint8_t idreg_data[] = { 0xfe, 0x81, 0x01, 0x03 };
+
+static void idreg_init(hwaddr addr)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, TYPE_MACIO_ID_REGISTER);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ sysbus_mmio_map(s, 0, addr);
+ cpu_physical_memory_write_rom(&address_space_memory,
+ addr, idreg_data, sizeof(idreg_data));
+}
+
+#define MACIO_ID_REGISTER(obj) \
+ OBJECT_CHECK(IDRegState, (obj), TYPE_MACIO_ID_REGISTER)
+
+typedef struct IDRegState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mem;
+} IDRegState;
+
+static int idreg_init1(SysBusDevice *dev)
+{
+ IDRegState *s = MACIO_ID_REGISTER(dev);
+
+ memory_region_init_ram(&s->mem, OBJECT(s),
+ "sun4m.idreg", sizeof(idreg_data), &error_abort);
+ vmstate_register_ram_global(&s->mem);
+ memory_region_set_readonly(&s->mem, true);
+ sysbus_init_mmio(dev, &s->mem);
+ return 0;
+}
+
+static void idreg_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = idreg_init1;
+}
+
+static const TypeInfo idreg_info = {
+ .name = TYPE_MACIO_ID_REGISTER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IDRegState),
+ .class_init = idreg_class_init,
+};
+
+#define TYPE_TCX_AFX "tcx_afx"
+#define TCX_AFX(obj) OBJECT_CHECK(AFXState, (obj), TYPE_TCX_AFX)
+
+typedef struct AFXState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mem;
+} AFXState;
+
+/* SS-5 TCX AFX register */
+static void afx_init(hwaddr addr)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, TYPE_TCX_AFX);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ sysbus_mmio_map(s, 0, addr);
+}
+
+static int afx_init1(SysBusDevice *dev)
+{
+ AFXState *s = TCX_AFX(dev);
+
+ memory_region_init_ram(&s->mem, OBJECT(s), "sun4m.afx", 4, &error_abort);
+ vmstate_register_ram_global(&s->mem);
+ sysbus_init_mmio(dev, &s->mem);
+ return 0;
+}
+
+static void afx_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = afx_init1;
+}
+
+static const TypeInfo afx_info = {
+ .name = TYPE_TCX_AFX,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AFXState),
+ .class_init = afx_class_init,
+};
+
+#define TYPE_OPENPROM "openprom"
+#define OPENPROM(obj) OBJECT_CHECK(PROMState, (obj), TYPE_OPENPROM)
+
+typedef struct PROMState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion prom;
+} PROMState;
+
+/* Boot PROM (OpenBIOS) */
+static uint64_t translate_prom_address(void *opaque, uint64_t addr)
+{
+ hwaddr *base_addr = (hwaddr *)opaque;
+ return addr + *base_addr - PROM_VADDR;
+}
+
+static void prom_init(hwaddr addr, const char *bios_name)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ char *filename;
+ int ret;
+
+ dev = qdev_create(NULL, TYPE_OPENPROM);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ sysbus_mmio_map(s, 0, addr);
+
+ /* load boot prom */
+ if (bios_name == NULL) {
+ bios_name = PROM_FILENAME;
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ ret = load_elf(filename, translate_prom_address, &addr, NULL,
+ NULL, NULL, 1, ELF_MACHINE, 0);
+ if (ret < 0 || ret > PROM_SIZE_MAX) {
+ ret = load_image_targphys(filename, addr, PROM_SIZE_MAX);
+ }
+ g_free(filename);
+ } else {
+ ret = -1;
+ }
+ if (ret < 0 || ret > PROM_SIZE_MAX) {
+ fprintf(stderr, "qemu: could not load prom '%s'\n", bios_name);
+ exit(1);
+ }
+}
+
+static int prom_init1(SysBusDevice *dev)
+{
+ PROMState *s = OPENPROM(dev);
+
+ memory_region_init_ram(&s->prom, OBJECT(s), "sun4m.prom", PROM_SIZE_MAX,
+ &error_abort);
+ vmstate_register_ram_global(&s->prom);
+ memory_region_set_readonly(&s->prom, true);
+ sysbus_init_mmio(dev, &s->prom);
+ return 0;
+}
+
+static Property prom_properties[] = {
+ {/* end of property list */},
+};
+
+static void prom_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = prom_init1;
+ dc->props = prom_properties;
+}
+
+static const TypeInfo prom_info = {
+ .name = TYPE_OPENPROM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PROMState),
+ .class_init = prom_class_init,
+};
+
+#define TYPE_SUN4M_MEMORY "memory"
+#define SUN4M_RAM(obj) OBJECT_CHECK(RamDevice, (obj), TYPE_SUN4M_MEMORY)
+
+typedef struct RamDevice {
+ SysBusDevice parent_obj;
+
+ MemoryRegion ram;
+ uint64_t size;
+} RamDevice;
+
+/* System RAM */
+static int ram_init1(SysBusDevice *dev)
+{
+ RamDevice *d = SUN4M_RAM(dev);
+
+ memory_region_allocate_system_memory(&d->ram, OBJECT(d), "sun4m.ram",
+ d->size);
+ sysbus_init_mmio(dev, &d->ram);
+ return 0;
+}
+
+static void ram_init(hwaddr addr, ram_addr_t RAM_size,
+ uint64_t max_mem)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ RamDevice *d;
+
+ /* allocate RAM */
+ if ((uint64_t)RAM_size > max_mem) {
+ fprintf(stderr,
+ "qemu: Too much memory for this machine: %d, maximum %d\n",
+ (unsigned int)(RAM_size / (1024 * 1024)),
+ (unsigned int)(max_mem / (1024 * 1024)));
+ exit(1);
+ }
+ dev = qdev_create(NULL, "memory");
+ s = SYS_BUS_DEVICE(dev);
+
+ d = SUN4M_RAM(dev);
+ d->size = RAM_size;
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(s, 0, addr);
+}
+
+static Property ram_properties[] = {
+ DEFINE_PROP_UINT64("size", RamDevice, size, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ram_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = ram_init1;
+ dc->props = ram_properties;
+}
+
+static const TypeInfo ram_info = {
+ .name = TYPE_SUN4M_MEMORY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RamDevice),
+ .class_init = ram_class_init,
+};
+
+static void cpu_devinit(const char *cpu_model, unsigned int id,
+ uint64_t prom_addr, qemu_irq **cpu_irqs)
+{
+ CPUState *cs;
+ SPARCCPU *cpu;
+ CPUSPARCState *env;
+
+ cpu = cpu_sparc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "qemu: Unable to find Sparc CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ cpu_sparc_set_id(env, id);
+ if (id == 0) {
+ qemu_register_reset(main_cpu_reset, cpu);
+ } else {
+ qemu_register_reset(secondary_cpu_reset, cpu);
+ cs = CPU(cpu);
+ cs->halted = 1;
+ }
+ *cpu_irqs = qemu_allocate_irqs(cpu_set_irq, cpu, MAX_PILS);
+ env->prom_addr = prom_addr;
+}
+
+static void dummy_fdc_tc(void *opaque, int irq, int level)
+{
+}
+
+static void sun4m_hw_init(const struct sun4m_hwdef *hwdef,
+ MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_model;
+ unsigned int i;
+ void *iommu, *espdma, *ledma, *nvram;
+ qemu_irq *cpu_irqs[MAX_CPUS], slavio_irq[32], slavio_cpu_irq[MAX_CPUS],
+ espdma_irq, ledma_irq;
+ qemu_irq esp_reset, dma_enable;
+ qemu_irq fdc_tc;
+ unsigned long kernel_size;
+ DriveInfo *fd[MAX_FD];
+ FWCfgState *fw_cfg;
+ unsigned int num_vsimms;
+
+ /* init CPUs */
+ if (!cpu_model)
+ cpu_model = hwdef->default_cpu_model;
+
+ for(i = 0; i < smp_cpus; i++) {
+ cpu_devinit(cpu_model, i, hwdef->slavio_base, &cpu_irqs[i]);
+ }
+
+ for (i = smp_cpus; i < MAX_CPUS; i++)
+ cpu_irqs[i] = qemu_allocate_irqs(dummy_cpu_set_irq, NULL, MAX_PILS);
+
+
+ /* set up devices */
+ ram_init(0, machine->ram_size, hwdef->max_mem);
+ /* models without ECC don't trap when missing ram is accessed */
+ if (!hwdef->ecc_base) {
+ empty_slot_init(machine->ram_size, hwdef->max_mem - machine->ram_size);
+ }
+
+ prom_init(hwdef->slavio_base, bios_name);
+
+ slavio_intctl = slavio_intctl_init(hwdef->intctl_base,
+ hwdef->intctl_base + 0x10000ULL,
+ cpu_irqs);
+
+ for (i = 0; i < 32; i++) {
+ slavio_irq[i] = qdev_get_gpio_in(slavio_intctl, i);
+ }
+ for (i = 0; i < MAX_CPUS; i++) {
+ slavio_cpu_irq[i] = qdev_get_gpio_in(slavio_intctl, 32 + i);
+ }
+
+ if (hwdef->idreg_base) {
+ idreg_init(hwdef->idreg_base);
+ }
+
+ if (hwdef->afx_base) {
+ afx_init(hwdef->afx_base);
+ }
+
+ iommu = iommu_init(hwdef->iommu_base, hwdef->iommu_version,
+ slavio_irq[30]);
+
+ if (hwdef->iommu_pad_base) {
+ /* On the real hardware (SS-5, LX) the MMU is not padded, but aliased.
+ Software shouldn't use aliased addresses, neither should it crash
+ when does. Using empty_slot instead of aliasing can help with
+ debugging such accesses */
+ empty_slot_init(hwdef->iommu_pad_base,hwdef->iommu_pad_len);
+ }
+
+ espdma = sparc32_dma_init(hwdef->dma_base, slavio_irq[18],
+ iommu, &espdma_irq, 0);
+
+ ledma = sparc32_dma_init(hwdef->dma_base + 16ULL,
+ slavio_irq[16], iommu, &ledma_irq, 1);
+
+ if (graphic_depth != 8 && graphic_depth != 24) {
+ error_report("Unsupported depth: %d", graphic_depth);
+ exit (1);
+ }
+ num_vsimms = 0;
+ if (num_vsimms == 0) {
+ if (vga_interface_type == VGA_CG3) {
+ if (graphic_depth != 8) {
+ error_report("Unsupported depth: %d", graphic_depth);
+ exit(1);
+ }
+
+ if (!(graphic_width == 1024 && graphic_height == 768) &&
+ !(graphic_width == 1152 && graphic_height == 900)) {
+ error_report("Unsupported resolution: %d x %d", graphic_width,
+ graphic_height);
+ exit(1);
+ }
+
+ /* sbus irq 5 */
+ cg3_init(hwdef->tcx_base, slavio_irq[11], 0x00100000,
+ graphic_width, graphic_height, graphic_depth);
+ } else {
+ /* If no display specified, default to TCX */
+ if (graphic_depth != 8 && graphic_depth != 24) {
+ error_report("Unsupported depth: %d", graphic_depth);
+ exit(1);
+ }
+
+ if (!(graphic_width == 1024 && graphic_height == 768)) {
+ error_report("Unsupported resolution: %d x %d",
+ graphic_width, graphic_height);
+ exit(1);
+ }
+
+ tcx_init(hwdef->tcx_base, slavio_irq[11], 0x00100000,
+ graphic_width, graphic_height, graphic_depth);
+ }
+ }
+
+ for (i = num_vsimms; i < MAX_VSIMMS; i++) {
+ /* vsimm registers probed by OBP */
+ if (hwdef->vsimm[i].reg_base) {
+ empty_slot_init(hwdef->vsimm[i].reg_base, 0x2000);
+ }
+ }
+
+ if (hwdef->sx_base) {
+ empty_slot_init(hwdef->sx_base, 0x2000);
+ }
+
+ lance_init(&nd_table[0], hwdef->le_base, ledma, ledma_irq);
+
+ nvram = m48t59_init(slavio_irq[0], hwdef->nvram_base, 0, 0x2000, 1968, 8);
+
+ slavio_timer_init_all(hwdef->counter_base, slavio_irq[19], slavio_cpu_irq, smp_cpus);
+
+ slavio_serial_ms_kbd_init(hwdef->ms_kb_base, slavio_irq[14],
+ display_type == DT_NOGRAPHIC, ESCC_CLOCK, 1);
+ /* Slavio TTYA (base+4, Linux ttyS0) is the first QEMU serial device
+ Slavio TTYB (base+0, Linux ttyS1) is the second QEMU serial device */
+ escc_init(hwdef->serial_base, slavio_irq[15], slavio_irq[15],
+ serial_hds[0], serial_hds[1], ESCC_CLOCK, 1);
+
+ if (hwdef->apc_base) {
+ apc_init(hwdef->apc_base, qemu_allocate_irq(cpu_halt_signal, NULL, 0));
+ }
+
+ if (hwdef->fd_base) {
+ /* there is zero or one floppy drive */
+ memset(fd, 0, sizeof(fd));
+ fd[0] = drive_get(IF_FLOPPY, 0, 0);
+ sun4m_fdctrl_init(slavio_irq[22], hwdef->fd_base, fd,
+ &fdc_tc);
+ } else {
+ fdc_tc = qemu_allocate_irq(dummy_fdc_tc, NULL, 0);
+ }
+
+ slavio_misc_init(hwdef->slavio_base, hwdef->aux1_base, hwdef->aux2_base,
+ slavio_irq[30], fdc_tc);
+
+ if (drive_get_max_bus(IF_SCSI) > 0) {
+ fprintf(stderr, "qemu: too many SCSI bus\n");
+ exit(1);
+ }
+
+ esp_init(hwdef->esp_base, 2,
+ espdma_memory_read, espdma_memory_write,
+ espdma, espdma_irq, &esp_reset, &dma_enable);
+
+ qdev_connect_gpio_out(espdma, 0, esp_reset);
+ qdev_connect_gpio_out(espdma, 1, dma_enable);
+
+ if (hwdef->cs_base) {
+ sysbus_create_simple("SUNW,CS4231", hwdef->cs_base,
+ slavio_irq[5]);
+ }
+
+ if (hwdef->dbri_base) {
+ /* ISDN chip with attached CS4215 audio codec */
+ /* prom space */
+ empty_slot_init(hwdef->dbri_base+0x1000, 0x30);
+ /* reg space */
+ empty_slot_init(hwdef->dbri_base+0x10000, 0x100);
+ }
+
+ if (hwdef->bpp_base) {
+ /* parallel port */
+ empty_slot_init(hwdef->bpp_base, 0x20);
+ }
+
+ kernel_size = sun4m_load_kernel(machine->kernel_filename,
+ machine->initrd_filename,
+ machine->ram_size);
+
+ nvram_init(nvram, (uint8_t *)&nd_table[0].macaddr, machine->kernel_cmdline,
+ machine->boot_order, machine->ram_size, kernel_size,
+ graphic_width, graphic_height, graphic_depth,
+ hwdef->nvram_machine_id, "Sun4m");
+
+ if (hwdef->ecc_base)
+ ecc_init(hwdef->ecc_base, slavio_irq[28],
+ hwdef->ecc_version);
+
+ fw_cfg = fw_cfg_init_mem(CFG_ADDR, CFG_ADDR + 2);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, (uint16_t)max_cpus);
+ fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MACHINE_ID, hwdef->machine_id);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_SUN4M_DEPTH, graphic_depth);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_SUN4M_WIDTH, graphic_width);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_SUN4M_HEIGHT, graphic_height);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, KERNEL_LOAD_ADDR);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_SIZE, kernel_size);
+ if (machine->kernel_cmdline) {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, CMDLINE_ADDR);
+ pstrcpy_targphys("cmdline", CMDLINE_ADDR, TARGET_PAGE_SIZE,
+ machine->kernel_cmdline);
+ fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, machine->kernel_cmdline);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE,
+ strlen(machine->kernel_cmdline) + 1);
+ } else {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, 0);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, 0);
+ }
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, INITRD_LOAD_ADDR);
+ fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, 0); // not used
+ fw_cfg_add_i16(fw_cfg, FW_CFG_BOOT_DEVICE, machine->boot_order[0]);
+ qemu_register_boot_set(fw_cfg_boot_set, fw_cfg);
+}
+
+enum {
+ ss5_id = 32,
+ vger_id,
+ lx_id,
+ ss4_id,
+ scls_id,
+ sbook_id,
+ ss10_id = 64,
+ ss20_id,
+ ss600mp_id,
+};
+
+static const struct sun4m_hwdef sun4m_hwdefs[] = {
+ /* SS-5 */
+ {
+ .iommu_base = 0x10000000,
+ .iommu_pad_base = 0x10004000,
+ .iommu_pad_len = 0x0fffb000,
+ .tcx_base = 0x50000000,
+ .cs_base = 0x6c000000,
+ .slavio_base = 0x70000000,
+ .ms_kb_base = 0x71000000,
+ .serial_base = 0x71100000,
+ .nvram_base = 0x71200000,
+ .fd_base = 0x71400000,
+ .counter_base = 0x71d00000,
+ .intctl_base = 0x71e00000,
+ .idreg_base = 0x78000000,
+ .dma_base = 0x78400000,
+ .esp_base = 0x78800000,
+ .le_base = 0x78c00000,
+ .apc_base = 0x6a000000,
+ .afx_base = 0x6e000000,
+ .aux1_base = 0x71900000,
+ .aux2_base = 0x71910000,
+ .nvram_machine_id = 0x80,
+ .machine_id = ss5_id,
+ .iommu_version = 0x05000000,
+ .max_mem = 0x10000000,
+ .default_cpu_model = "Fujitsu MB86904",
+ },
+ /* SS-10 */
+ {
+ .iommu_base = 0xfe0000000ULL,
+ .tcx_base = 0xe20000000ULL,
+ .slavio_base = 0xff0000000ULL,
+ .ms_kb_base = 0xff1000000ULL,
+ .serial_base = 0xff1100000ULL,
+ .nvram_base = 0xff1200000ULL,
+ .fd_base = 0xff1700000ULL,
+ .counter_base = 0xff1300000ULL,
+ .intctl_base = 0xff1400000ULL,
+ .idreg_base = 0xef0000000ULL,
+ .dma_base = 0xef0400000ULL,
+ .esp_base = 0xef0800000ULL,
+ .le_base = 0xef0c00000ULL,
+ .apc_base = 0xefa000000ULL, // XXX should not exist
+ .aux1_base = 0xff1800000ULL,
+ .aux2_base = 0xff1a01000ULL,
+ .ecc_base = 0xf00000000ULL,
+ .ecc_version = 0x10000000, // version 0, implementation 1
+ .nvram_machine_id = 0x72,
+ .machine_id = ss10_id,
+ .iommu_version = 0x03000000,
+ .max_mem = 0xf00000000ULL,
+ .default_cpu_model = "TI SuperSparc II",
+ },
+ /* SS-600MP */
+ {
+ .iommu_base = 0xfe0000000ULL,
+ .tcx_base = 0xe20000000ULL,
+ .slavio_base = 0xff0000000ULL,
+ .ms_kb_base = 0xff1000000ULL,
+ .serial_base = 0xff1100000ULL,
+ .nvram_base = 0xff1200000ULL,
+ .counter_base = 0xff1300000ULL,
+ .intctl_base = 0xff1400000ULL,
+ .dma_base = 0xef0081000ULL,
+ .esp_base = 0xef0080000ULL,
+ .le_base = 0xef0060000ULL,
+ .apc_base = 0xefa000000ULL, // XXX should not exist
+ .aux1_base = 0xff1800000ULL,
+ .aux2_base = 0xff1a01000ULL, // XXX should not exist
+ .ecc_base = 0xf00000000ULL,
+ .ecc_version = 0x00000000, // version 0, implementation 0
+ .nvram_machine_id = 0x71,
+ .machine_id = ss600mp_id,
+ .iommu_version = 0x01000000,
+ .max_mem = 0xf00000000ULL,
+ .default_cpu_model = "TI SuperSparc II",
+ },
+ /* SS-20 */
+ {
+ .iommu_base = 0xfe0000000ULL,
+ .tcx_base = 0xe20000000ULL,
+ .slavio_base = 0xff0000000ULL,
+ .ms_kb_base = 0xff1000000ULL,
+ .serial_base = 0xff1100000ULL,
+ .nvram_base = 0xff1200000ULL,
+ .fd_base = 0xff1700000ULL,
+ .counter_base = 0xff1300000ULL,
+ .intctl_base = 0xff1400000ULL,
+ .idreg_base = 0xef0000000ULL,
+ .dma_base = 0xef0400000ULL,
+ .esp_base = 0xef0800000ULL,
+ .le_base = 0xef0c00000ULL,
+ .bpp_base = 0xef4800000ULL,
+ .apc_base = 0xefa000000ULL, // XXX should not exist
+ .aux1_base = 0xff1800000ULL,
+ .aux2_base = 0xff1a01000ULL,
+ .dbri_base = 0xee0000000ULL,
+ .sx_base = 0xf80000000ULL,
+ .vsimm = {
+ {
+ .reg_base = 0x9c000000ULL,
+ .vram_base = 0xfc000000ULL
+ }, {
+ .reg_base = 0x90000000ULL,
+ .vram_base = 0xf0000000ULL
+ }, {
+ .reg_base = 0x94000000ULL
+ }, {
+ .reg_base = 0x98000000ULL
+ }
+ },
+ .ecc_base = 0xf00000000ULL,
+ .ecc_version = 0x20000000, // version 0, implementation 2
+ .nvram_machine_id = 0x72,
+ .machine_id = ss20_id,
+ .iommu_version = 0x13000000,
+ .max_mem = 0xf00000000ULL,
+ .default_cpu_model = "TI SuperSparc II",
+ },
+ /* Voyager */
+ {
+ .iommu_base = 0x10000000,
+ .tcx_base = 0x50000000,
+ .slavio_base = 0x70000000,
+ .ms_kb_base = 0x71000000,
+ .serial_base = 0x71100000,
+ .nvram_base = 0x71200000,
+ .fd_base = 0x71400000,
+ .counter_base = 0x71d00000,
+ .intctl_base = 0x71e00000,
+ .idreg_base = 0x78000000,
+ .dma_base = 0x78400000,
+ .esp_base = 0x78800000,
+ .le_base = 0x78c00000,
+ .apc_base = 0x71300000, // pmc
+ .aux1_base = 0x71900000,
+ .aux2_base = 0x71910000,
+ .nvram_machine_id = 0x80,
+ .machine_id = vger_id,
+ .iommu_version = 0x05000000,
+ .max_mem = 0x10000000,
+ .default_cpu_model = "Fujitsu MB86904",
+ },
+ /* LX */
+ {
+ .iommu_base = 0x10000000,
+ .iommu_pad_base = 0x10004000,
+ .iommu_pad_len = 0x0fffb000,
+ .tcx_base = 0x50000000,
+ .slavio_base = 0x70000000,
+ .ms_kb_base = 0x71000000,
+ .serial_base = 0x71100000,
+ .nvram_base = 0x71200000,
+ .fd_base = 0x71400000,
+ .counter_base = 0x71d00000,
+ .intctl_base = 0x71e00000,
+ .idreg_base = 0x78000000,
+ .dma_base = 0x78400000,
+ .esp_base = 0x78800000,
+ .le_base = 0x78c00000,
+ .aux1_base = 0x71900000,
+ .aux2_base = 0x71910000,
+ .nvram_machine_id = 0x80,
+ .machine_id = lx_id,
+ .iommu_version = 0x04000000,
+ .max_mem = 0x10000000,
+ .default_cpu_model = "TI MicroSparc I",
+ },
+ /* SS-4 */
+ {
+ .iommu_base = 0x10000000,
+ .tcx_base = 0x50000000,
+ .cs_base = 0x6c000000,
+ .slavio_base = 0x70000000,
+ .ms_kb_base = 0x71000000,
+ .serial_base = 0x71100000,
+ .nvram_base = 0x71200000,
+ .fd_base = 0x71400000,
+ .counter_base = 0x71d00000,
+ .intctl_base = 0x71e00000,
+ .idreg_base = 0x78000000,
+ .dma_base = 0x78400000,
+ .esp_base = 0x78800000,
+ .le_base = 0x78c00000,
+ .apc_base = 0x6a000000,
+ .aux1_base = 0x71900000,
+ .aux2_base = 0x71910000,
+ .nvram_machine_id = 0x80,
+ .machine_id = ss4_id,
+ .iommu_version = 0x05000000,
+ .max_mem = 0x10000000,
+ .default_cpu_model = "Fujitsu MB86904",
+ },
+ /* SPARCClassic */
+ {
+ .iommu_base = 0x10000000,
+ .tcx_base = 0x50000000,
+ .slavio_base = 0x70000000,
+ .ms_kb_base = 0x71000000,
+ .serial_base = 0x71100000,
+ .nvram_base = 0x71200000,
+ .fd_base = 0x71400000,
+ .counter_base = 0x71d00000,
+ .intctl_base = 0x71e00000,
+ .idreg_base = 0x78000000,
+ .dma_base = 0x78400000,
+ .esp_base = 0x78800000,
+ .le_base = 0x78c00000,
+ .apc_base = 0x6a000000,
+ .aux1_base = 0x71900000,
+ .aux2_base = 0x71910000,
+ .nvram_machine_id = 0x80,
+ .machine_id = scls_id,
+ .iommu_version = 0x05000000,
+ .max_mem = 0x10000000,
+ .default_cpu_model = "TI MicroSparc I",
+ },
+ /* SPARCbook */
+ {
+ .iommu_base = 0x10000000,
+ .tcx_base = 0x50000000, // XXX
+ .slavio_base = 0x70000000,
+ .ms_kb_base = 0x71000000,
+ .serial_base = 0x71100000,
+ .nvram_base = 0x71200000,
+ .fd_base = 0x71400000,
+ .counter_base = 0x71d00000,
+ .intctl_base = 0x71e00000,
+ .idreg_base = 0x78000000,
+ .dma_base = 0x78400000,
+ .esp_base = 0x78800000,
+ .le_base = 0x78c00000,
+ .apc_base = 0x6a000000,
+ .aux1_base = 0x71900000,
+ .aux2_base = 0x71910000,
+ .nvram_machine_id = 0x80,
+ .machine_id = sbook_id,
+ .iommu_version = 0x05000000,
+ .max_mem = 0x10000000,
+ .default_cpu_model = "TI MicroSparc I",
+ },
+};
+
+/* SPARCstation 5 hardware initialisation */
+static void ss5_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[0], machine);
+}
+
+/* SPARCstation 10 hardware initialisation */
+static void ss10_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[1], machine);
+}
+
+/* SPARCserver 600MP hardware initialisation */
+static void ss600mp_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[2], machine);
+}
+
+/* SPARCstation 20 hardware initialisation */
+static void ss20_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[3], machine);
+}
+
+/* SPARCstation Voyager hardware initialisation */
+static void vger_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[4], machine);
+}
+
+/* SPARCstation LX hardware initialisation */
+static void ss_lx_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[5], machine);
+}
+
+/* SPARCstation 4 hardware initialisation */
+static void ss4_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[6], machine);
+}
+
+/* SPARCClassic hardware initialisation */
+static void scls_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[7], machine);
+}
+
+/* SPARCbook hardware initialisation */
+static void sbook_init(MachineState *machine)
+{
+ sun4m_hw_init(&sun4m_hwdefs[8], machine);
+}
+
+static QEMUMachine ss5_machine = {
+ .name = "SS-5",
+ .desc = "Sun4m platform, SPARCstation 5",
+ .init = ss5_init,
+ .block_default_type = IF_SCSI,
+ .is_default = 1,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine ss10_machine = {
+ .name = "SS-10",
+ .desc = "Sun4m platform, SPARCstation 10",
+ .init = ss10_init,
+ .block_default_type = IF_SCSI,
+ .max_cpus = 4,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine ss600mp_machine = {
+ .name = "SS-600MP",
+ .desc = "Sun4m platform, SPARCserver 600MP",
+ .init = ss600mp_init,
+ .block_default_type = IF_SCSI,
+ .max_cpus = 4,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine ss20_machine = {
+ .name = "SS-20",
+ .desc = "Sun4m platform, SPARCstation 20",
+ .init = ss20_init,
+ .block_default_type = IF_SCSI,
+ .max_cpus = 4,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine voyager_machine = {
+ .name = "Voyager",
+ .desc = "Sun4m platform, SPARCstation Voyager",
+ .init = vger_init,
+ .block_default_type = IF_SCSI,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine ss_lx_machine = {
+ .name = "LX",
+ .desc = "Sun4m platform, SPARCstation LX",
+ .init = ss_lx_init,
+ .block_default_type = IF_SCSI,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine ss4_machine = {
+ .name = "SS-4",
+ .desc = "Sun4m platform, SPARCstation 4",
+ .init = ss4_init,
+ .block_default_type = IF_SCSI,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine scls_machine = {
+ .name = "SPARCClassic",
+ .desc = "Sun4m platform, SPARCClassic",
+ .init = scls_init,
+ .block_default_type = IF_SCSI,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine sbook_machine = {
+ .name = "SPARCbook",
+ .desc = "Sun4m platform, SPARCbook",
+ .init = sbook_init,
+ .block_default_type = IF_SCSI,
+ .default_boot_order = "c",
+};
+
+static void sun4m_register_types(void)
+{
+ type_register_static(&idreg_info);
+ type_register_static(&afx_info);
+ type_register_static(&prom_info);
+ type_register_static(&ram_info);
+}
+
+static void sun4m_machine_init(void)
+{
+ qemu_register_machine(&ss5_machine);
+ qemu_register_machine(&ss10_machine);
+ qemu_register_machine(&ss600mp_machine);
+ qemu_register_machine(&ss20_machine);
+ qemu_register_machine(&voyager_machine);
+ qemu_register_machine(&ss_lx_machine);
+ qemu_register_machine(&ss4_machine);
+ qemu_register_machine(&scls_machine);
+ qemu_register_machine(&sbook_machine);
+}
+
+type_init(sun4m_register_types)
+machine_init(sun4m_machine_init);
diff --git a/hw/sparc64/Makefile.objs b/hw/sparc64/Makefile.objs
new file mode 100644
index 00000000..a84cfe3e
--- /dev/null
+++ b/hw/sparc64/Makefile.objs
@@ -0,0 +1 @@
+obj-y += sun4u.o
diff --git a/hw/sparc64/sun4u.c b/hw/sparc64/sun4u.c
new file mode 100644
index 00000000..30cfa0e0
--- /dev/null
+++ b/hw/sparc64/sun4u.c
@@ -0,0 +1,1008 @@
+/*
+ * QEMU Sun4u/Sun4v System Emulator
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/pci-host/apb.h"
+#include "hw/i386/pc.h"
+#include "hw/char/serial.h"
+#include "hw/timer/m48t59.h"
+#include "hw/block/fdc.h"
+#include "net/net.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/nvram/openbios_firmware_abi.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/sysbus.h"
+#include "hw/ide.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_IRQ
+//#define DEBUG_EBUS
+//#define DEBUG_TIMER
+
+#ifdef DEBUG_IRQ
+#define CPUIRQ_DPRINTF(fmt, ...) \
+ do { printf("CPUIRQ: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define CPUIRQ_DPRINTF(fmt, ...)
+#endif
+
+#ifdef DEBUG_EBUS
+#define EBUS_DPRINTF(fmt, ...) \
+ do { printf("EBUS: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define EBUS_DPRINTF(fmt, ...)
+#endif
+
+#ifdef DEBUG_TIMER
+#define TIMER_DPRINTF(fmt, ...) \
+ do { printf("TIMER: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define TIMER_DPRINTF(fmt, ...)
+#endif
+
+#define KERNEL_LOAD_ADDR 0x00404000
+#define CMDLINE_ADDR 0x003ff000
+#define PROM_SIZE_MAX (4 * 1024 * 1024)
+#define PROM_VADDR 0x000ffd00000ULL
+#define APB_SPECIAL_BASE 0x1fe00000000ULL
+#define APB_MEM_BASE 0x1ff00000000ULL
+#define APB_PCI_IO_BASE (APB_SPECIAL_BASE + 0x02000000ULL)
+#define PROM_FILENAME "openbios-sparc64"
+#define NVRAM_SIZE 0x2000
+#define MAX_IDE_BUS 2
+#define BIOS_CFG_IOPORT 0x510
+#define FW_CFG_SPARC64_WIDTH (FW_CFG_ARCH_LOCAL + 0x00)
+#define FW_CFG_SPARC64_HEIGHT (FW_CFG_ARCH_LOCAL + 0x01)
+#define FW_CFG_SPARC64_DEPTH (FW_CFG_ARCH_LOCAL + 0x02)
+
+#define IVEC_MAX 0x40
+
+#define TICK_MAX 0x7fffffffffffffffULL
+
+struct hwdef {
+ const char * const default_cpu_model;
+ uint16_t machine_id;
+ uint64_t prom_addr;
+ uint64_t console_serial_base;
+};
+
+typedef struct EbusState {
+ PCIDevice pci_dev;
+ MemoryRegion bar0;
+ MemoryRegion bar1;
+} EbusState;
+
+int DMA_get_channel_mode (int nchan)
+{
+ return 0;
+}
+int DMA_read_memory (int nchan, void *buf, int pos, int size)
+{
+ return 0;
+}
+int DMA_write_memory (int nchan, void *buf, int pos, int size)
+{
+ return 0;
+}
+void DMA_hold_DREQ (int nchan) {}
+void DMA_release_DREQ (int nchan) {}
+void DMA_schedule(int nchan) {}
+
+void DMA_init(int high_page_enable, qemu_irq *cpu_request_exit)
+{
+}
+
+void DMA_register_channel (int nchan,
+ DMA_transfer_handler transfer_handler,
+ void *opaque)
+{
+}
+
+static void fw_cfg_boot_set(void *opaque, const char *boot_device,
+ Error **errp)
+{
+ fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]);
+}
+
+static int sun4u_NVRAM_set_params(Nvram *nvram, uint16_t NVRAM_size,
+ const char *arch, ram_addr_t RAM_size,
+ const char *boot_devices,
+ uint32_t kernel_image, uint32_t kernel_size,
+ const char *cmdline,
+ uint32_t initrd_image, uint32_t initrd_size,
+ uint32_t NVRAM_image,
+ int width, int height, int depth,
+ const uint8_t *macaddr)
+{
+ unsigned int i;
+ uint32_t start, end;
+ uint8_t image[0x1ff0];
+ struct OpenBIOS_nvpart_v1 *part_header;
+ NvramClass *k = NVRAM_GET_CLASS(nvram);
+
+ memset(image, '\0', sizeof(image));
+
+ start = 0;
+
+ // OpenBIOS nvram variables
+ // Variable partition
+ part_header = (struct OpenBIOS_nvpart_v1 *)&image[start];
+ part_header->signature = OPENBIOS_PART_SYSTEM;
+ pstrcpy(part_header->name, sizeof(part_header->name), "system");
+
+ end = start + sizeof(struct OpenBIOS_nvpart_v1);
+ for (i = 0; i < nb_prom_envs; i++)
+ end = OpenBIOS_set_var(image, end, prom_envs[i]);
+
+ // End marker
+ image[end++] = '\0';
+
+ end = start + ((end - start + 15) & ~15);
+ OpenBIOS_finish_partition(part_header, end - start);
+
+ // free partition
+ start = end;
+ part_header = (struct OpenBIOS_nvpart_v1 *)&image[start];
+ part_header->signature = OPENBIOS_PART_FREE;
+ pstrcpy(part_header->name, sizeof(part_header->name), "free");
+
+ end = 0x1fd0;
+ OpenBIOS_finish_partition(part_header, end - start);
+
+ Sun_init_header((struct Sun_nvram *)&image[0x1fd8], macaddr, 0x80);
+
+ for (i = 0; i < sizeof(image); i++) {
+ (k->write)(nvram, i, image[i]);
+ }
+
+ return 0;
+}
+
+static uint64_t sun4u_load_kernel(const char *kernel_filename,
+ const char *initrd_filename,
+ ram_addr_t RAM_size, uint64_t *initrd_size,
+ uint64_t *initrd_addr, uint64_t *kernel_addr,
+ uint64_t *kernel_entry)
+{
+ int linux_boot;
+ unsigned int i;
+ long kernel_size;
+ uint8_t *ptr;
+ uint64_t kernel_top;
+
+ linux_boot = (kernel_filename != NULL);
+
+ kernel_size = 0;
+ if (linux_boot) {
+ int bswap_needed;
+
+#ifdef BSWAP_NEEDED
+ bswap_needed = 1;
+#else
+ bswap_needed = 0;
+#endif
+ kernel_size = load_elf(kernel_filename, NULL, NULL, kernel_entry,
+ kernel_addr, &kernel_top, 1, ELF_MACHINE, 0);
+ if (kernel_size < 0) {
+ *kernel_addr = KERNEL_LOAD_ADDR;
+ *kernel_entry = KERNEL_LOAD_ADDR;
+ kernel_size = load_aout(kernel_filename, KERNEL_LOAD_ADDR,
+ RAM_size - KERNEL_LOAD_ADDR, bswap_needed,
+ TARGET_PAGE_SIZE);
+ }
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename,
+ KERNEL_LOAD_ADDR,
+ RAM_size - KERNEL_LOAD_ADDR);
+ }
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ /* load initrd above kernel */
+ *initrd_size = 0;
+ if (initrd_filename) {
+ *initrd_addr = TARGET_PAGE_ALIGN(kernel_top);
+
+ *initrd_size = load_image_targphys(initrd_filename,
+ *initrd_addr,
+ RAM_size - *initrd_addr);
+ if ((int)*initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ }
+ if (*initrd_size > 0) {
+ for (i = 0; i < 64 * TARGET_PAGE_SIZE; i += TARGET_PAGE_SIZE) {
+ ptr = rom_ptr(*kernel_addr + i);
+ if (ldl_p(ptr + 8) == 0x48647253) { /* HdrS */
+ stl_p(ptr + 24, *initrd_addr + *kernel_addr);
+ stl_p(ptr + 28, *initrd_size);
+ break;
+ }
+ }
+ }
+ }
+ return kernel_size;
+}
+
+void cpu_check_irqs(CPUSPARCState *env)
+{
+ CPUState *cs;
+ uint32_t pil = env->pil_in |
+ (env->softint & ~(SOFTINT_TIMER | SOFTINT_STIMER));
+
+ /* TT_IVEC has a higher priority (16) than TT_EXTINT (31..17) */
+ if (env->ivec_status & 0x20) {
+ return;
+ }
+ cs = CPU(sparc_env_get_cpu(env));
+ /* check if TM or SM in SOFTINT are set
+ setting these also causes interrupt 14 */
+ if (env->softint & (SOFTINT_TIMER | SOFTINT_STIMER)) {
+ pil |= 1 << 14;
+ }
+
+ /* The bit corresponding to psrpil is (1<< psrpil), the next bit
+ is (2 << psrpil). */
+ if (pil < (2 << env->psrpil)){
+ if (cs->interrupt_request & CPU_INTERRUPT_HARD) {
+ CPUIRQ_DPRINTF("Reset CPU IRQ (current interrupt %x)\n",
+ env->interrupt_index);
+ env->interrupt_index = 0;
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ return;
+ }
+
+ if (cpu_interrupts_enabled(env)) {
+
+ unsigned int i;
+
+ for (i = 15; i > env->psrpil; i--) {
+ if (pil & (1 << i)) {
+ int old_interrupt = env->interrupt_index;
+ int new_interrupt = TT_EXTINT | i;
+
+ if (unlikely(env->tl > 0 && cpu_tsptr(env)->tt > new_interrupt
+ && ((cpu_tsptr(env)->tt & 0x1f0) == TT_EXTINT))) {
+ CPUIRQ_DPRINTF("Not setting CPU IRQ: TL=%d "
+ "current %x >= pending %x\n",
+ env->tl, cpu_tsptr(env)->tt, new_interrupt);
+ } else if (old_interrupt != new_interrupt) {
+ env->interrupt_index = new_interrupt;
+ CPUIRQ_DPRINTF("Set CPU IRQ %d old=%x new=%x\n", i,
+ old_interrupt, new_interrupt);
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ break;
+ }
+ }
+ } else if (cs->interrupt_request & CPU_INTERRUPT_HARD) {
+ CPUIRQ_DPRINTF("Interrupts disabled, pil=%08x pil_in=%08x softint=%08x "
+ "current interrupt %x\n",
+ pil, env->pil_in, env->softint, env->interrupt_index);
+ env->interrupt_index = 0;
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+}
+
+static void cpu_kick_irq(SPARCCPU *cpu)
+{
+ CPUState *cs = CPU(cpu);
+ CPUSPARCState *env = &cpu->env;
+
+ cs->halted = 0;
+ cpu_check_irqs(env);
+ qemu_cpu_kick(cs);
+}
+
+static void cpu_set_ivec_irq(void *opaque, int irq, int level)
+{
+ SPARCCPU *cpu = opaque;
+ CPUSPARCState *env = &cpu->env;
+ CPUState *cs;
+
+ if (level) {
+ if (!(env->ivec_status & 0x20)) {
+ CPUIRQ_DPRINTF("Raise IVEC IRQ %d\n", irq);
+ cs = CPU(cpu);
+ cs->halted = 0;
+ env->interrupt_index = TT_IVEC;
+ env->ivec_status |= 0x20;
+ env->ivec_data[0] = (0x1f << 6) | irq;
+ env->ivec_data[1] = 0;
+ env->ivec_data[2] = 0;
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ } else {
+ if (env->ivec_status & 0x20) {
+ CPUIRQ_DPRINTF("Lower IVEC IRQ %d\n", irq);
+ cs = CPU(cpu);
+ env->ivec_status &= ~0x20;
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+ }
+}
+
+typedef struct ResetData {
+ SPARCCPU *cpu;
+ uint64_t prom_addr;
+} ResetData;
+
+void cpu_put_timer(QEMUFile *f, CPUTimer *s)
+{
+ qemu_put_be32s(f, &s->frequency);
+ qemu_put_be32s(f, &s->disabled);
+ qemu_put_be64s(f, &s->disabled_mask);
+ qemu_put_sbe64s(f, &s->clock_offset);
+
+ timer_put(f, s->qtimer);
+}
+
+void cpu_get_timer(QEMUFile *f, CPUTimer *s)
+{
+ qemu_get_be32s(f, &s->frequency);
+ qemu_get_be32s(f, &s->disabled);
+ qemu_get_be64s(f, &s->disabled_mask);
+ qemu_get_sbe64s(f, &s->clock_offset);
+
+ timer_get(f, s->qtimer);
+}
+
+static CPUTimer *cpu_timer_create(const char *name, SPARCCPU *cpu,
+ QEMUBHFunc *cb, uint32_t frequency,
+ uint64_t disabled_mask)
+{
+ CPUTimer *timer = g_malloc0(sizeof (CPUTimer));
+
+ timer->name = name;
+ timer->frequency = frequency;
+ timer->disabled_mask = disabled_mask;
+
+ timer->disabled = 1;
+ timer->clock_offset = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ timer->qtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, cb, cpu);
+
+ return timer;
+}
+
+static void cpu_timer_reset(CPUTimer *timer)
+{
+ timer->disabled = 1;
+ timer->clock_offset = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ timer_del(timer->qtimer);
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ ResetData *s = (ResetData *)opaque;
+ CPUSPARCState *env = &s->cpu->env;
+ static unsigned int nr_resets;
+
+ cpu_reset(CPU(s->cpu));
+
+ cpu_timer_reset(env->tick);
+ cpu_timer_reset(env->stick);
+ cpu_timer_reset(env->hstick);
+
+ env->gregs[1] = 0; // Memory start
+ env->gregs[2] = ram_size; // Memory size
+ env->gregs[3] = 0; // Machine description XXX
+ if (nr_resets++ == 0) {
+ /* Power on reset */
+ env->pc = s->prom_addr + 0x20ULL;
+ } else {
+ env->pc = s->prom_addr + 0x40ULL;
+ }
+ env->npc = env->pc + 4;
+}
+
+static void tick_irq(void *opaque)
+{
+ SPARCCPU *cpu = opaque;
+ CPUSPARCState *env = &cpu->env;
+
+ CPUTimer* timer = env->tick;
+
+ if (timer->disabled) {
+ CPUIRQ_DPRINTF("tick_irq: softint disabled\n");
+ return;
+ } else {
+ CPUIRQ_DPRINTF("tick: fire\n");
+ }
+
+ env->softint |= SOFTINT_TIMER;
+ cpu_kick_irq(cpu);
+}
+
+static void stick_irq(void *opaque)
+{
+ SPARCCPU *cpu = opaque;
+ CPUSPARCState *env = &cpu->env;
+
+ CPUTimer* timer = env->stick;
+
+ if (timer->disabled) {
+ CPUIRQ_DPRINTF("stick_irq: softint disabled\n");
+ return;
+ } else {
+ CPUIRQ_DPRINTF("stick: fire\n");
+ }
+
+ env->softint |= SOFTINT_STIMER;
+ cpu_kick_irq(cpu);
+}
+
+static void hstick_irq(void *opaque)
+{
+ SPARCCPU *cpu = opaque;
+ CPUSPARCState *env = &cpu->env;
+
+ CPUTimer* timer = env->hstick;
+
+ if (timer->disabled) {
+ CPUIRQ_DPRINTF("hstick_irq: softint disabled\n");
+ return;
+ } else {
+ CPUIRQ_DPRINTF("hstick: fire\n");
+ }
+
+ env->softint |= SOFTINT_STIMER;
+ cpu_kick_irq(cpu);
+}
+
+static int64_t cpu_to_timer_ticks(int64_t cpu_ticks, uint32_t frequency)
+{
+ return muldiv64(cpu_ticks, get_ticks_per_sec(), frequency);
+}
+
+static uint64_t timer_to_cpu_ticks(int64_t timer_ticks, uint32_t frequency)
+{
+ return muldiv64(timer_ticks, frequency, get_ticks_per_sec());
+}
+
+void cpu_tick_set_count(CPUTimer *timer, uint64_t count)
+{
+ uint64_t real_count = count & ~timer->disabled_mask;
+ uint64_t disabled_bit = count & timer->disabled_mask;
+
+ int64_t vm_clock_offset = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
+ cpu_to_timer_ticks(real_count, timer->frequency);
+
+ TIMER_DPRINTF("%s set_count count=0x%016lx (%s) p=%p\n",
+ timer->name, real_count,
+ timer->disabled?"disabled":"enabled", timer);
+
+ timer->disabled = disabled_bit ? 1 : 0;
+ timer->clock_offset = vm_clock_offset;
+}
+
+uint64_t cpu_tick_get_count(CPUTimer *timer)
+{
+ uint64_t real_count = timer_to_cpu_ticks(
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - timer->clock_offset,
+ timer->frequency);
+
+ TIMER_DPRINTF("%s get_count count=0x%016lx (%s) p=%p\n",
+ timer->name, real_count,
+ timer->disabled?"disabled":"enabled", timer);
+
+ if (timer->disabled)
+ real_count |= timer->disabled_mask;
+
+ return real_count;
+}
+
+void cpu_tick_set_limit(CPUTimer *timer, uint64_t limit)
+{
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ uint64_t real_limit = limit & ~timer->disabled_mask;
+ timer->disabled = (limit & timer->disabled_mask) ? 1 : 0;
+
+ int64_t expires = cpu_to_timer_ticks(real_limit, timer->frequency) +
+ timer->clock_offset;
+
+ if (expires < now) {
+ expires = now + 1;
+ }
+
+ TIMER_DPRINTF("%s set_limit limit=0x%016lx (%s) p=%p "
+ "called with limit=0x%016lx at 0x%016lx (delta=0x%016lx)\n",
+ timer->name, real_limit,
+ timer->disabled?"disabled":"enabled",
+ timer, limit,
+ timer_to_cpu_ticks(now - timer->clock_offset,
+ timer->frequency),
+ timer_to_cpu_ticks(expires - now, timer->frequency));
+
+ if (!real_limit) {
+ TIMER_DPRINTF("%s set_limit limit=ZERO - not starting timer\n",
+ timer->name);
+ timer_del(timer->qtimer);
+ } else if (timer->disabled) {
+ timer_del(timer->qtimer);
+ } else {
+ timer_mod(timer->qtimer, expires);
+ }
+}
+
+static void isa_irq_handler(void *opaque, int n, int level)
+{
+ static const int isa_irq_to_ivec[16] = {
+ [1] = 0x29, /* keyboard */
+ [4] = 0x2b, /* serial */
+ [6] = 0x27, /* floppy */
+ [7] = 0x22, /* parallel */
+ [12] = 0x2a, /* mouse */
+ };
+ qemu_irq *irqs = opaque;
+ int ivec;
+
+ assert(n < 16);
+ ivec = isa_irq_to_ivec[n];
+ EBUS_DPRINTF("Set ISA IRQ %d level %d -> ivec 0x%x\n", n, level, ivec);
+ if (ivec) {
+ qemu_set_irq(irqs[ivec], level);
+ }
+}
+
+/* EBUS (Eight bit bus) bridge */
+static ISABus *
+pci_ebus_init(PCIBus *bus, int devfn, qemu_irq *irqs)
+{
+ qemu_irq *isa_irq;
+ PCIDevice *pci_dev;
+ ISABus *isa_bus;
+
+ pci_dev = pci_create_simple(bus, devfn, "ebus");
+ isa_bus = ISA_BUS(qdev_get_child_bus(DEVICE(pci_dev), "isa.0"));
+ isa_irq = qemu_allocate_irqs(isa_irq_handler, irqs, 16);
+ isa_bus_irqs(isa_bus, isa_irq);
+ return isa_bus;
+}
+
+static int
+pci_ebus_init1(PCIDevice *pci_dev)
+{
+ EbusState *s = DO_UPCAST(EbusState, pci_dev, pci_dev);
+
+ isa_bus_new(DEVICE(pci_dev), get_system_memory(),
+ pci_address_space_io(pci_dev));
+
+ pci_dev->config[0x04] = 0x06; // command = bus master, pci mem
+ pci_dev->config[0x05] = 0x00;
+ pci_dev->config[0x06] = 0xa0; // status = fast back-to-back, 66MHz, no error
+ pci_dev->config[0x07] = 0x03; // status = medium devsel
+ pci_dev->config[0x09] = 0x00; // programming i/f
+ pci_dev->config[0x0D] = 0x0a; // latency_timer
+
+ memory_region_init_alias(&s->bar0, OBJECT(s), "bar0", get_system_io(),
+ 0, 0x1000000);
+ pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
+ memory_region_init_alias(&s->bar1, OBJECT(s), "bar1", get_system_io(),
+ 0, 0x4000);
+ pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->bar1);
+ return 0;
+}
+
+static void ebus_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = pci_ebus_init1;
+ k->vendor_id = PCI_VENDOR_ID_SUN;
+ k->device_id = PCI_DEVICE_ID_SUN_EBUS;
+ k->revision = 0x01;
+ k->class_id = PCI_CLASS_BRIDGE_OTHER;
+}
+
+static const TypeInfo ebus_info = {
+ .name = "ebus",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(EbusState),
+ .class_init = ebus_class_init,
+};
+
+#define TYPE_OPENPROM "openprom"
+#define OPENPROM(obj) OBJECT_CHECK(PROMState, (obj), TYPE_OPENPROM)
+
+typedef struct PROMState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion prom;
+} PROMState;
+
+static uint64_t translate_prom_address(void *opaque, uint64_t addr)
+{
+ hwaddr *base_addr = (hwaddr *)opaque;
+ return addr + *base_addr - PROM_VADDR;
+}
+
+/* Boot PROM (OpenBIOS) */
+static void prom_init(hwaddr addr, const char *bios_name)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ char *filename;
+ int ret;
+
+ dev = qdev_create(NULL, TYPE_OPENPROM);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+
+ sysbus_mmio_map(s, 0, addr);
+
+ /* load boot prom */
+ if (bios_name == NULL) {
+ bios_name = PROM_FILENAME;
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ ret = load_elf(filename, translate_prom_address, &addr,
+ NULL, NULL, NULL, 1, ELF_MACHINE, 0);
+ if (ret < 0 || ret > PROM_SIZE_MAX) {
+ ret = load_image_targphys(filename, addr, PROM_SIZE_MAX);
+ }
+ g_free(filename);
+ } else {
+ ret = -1;
+ }
+ if (ret < 0 || ret > PROM_SIZE_MAX) {
+ fprintf(stderr, "qemu: could not load prom '%s'\n", bios_name);
+ exit(1);
+ }
+}
+
+static int prom_init1(SysBusDevice *dev)
+{
+ PROMState *s = OPENPROM(dev);
+
+ memory_region_init_ram(&s->prom, OBJECT(s), "sun4u.prom", PROM_SIZE_MAX,
+ &error_abort);
+ vmstate_register_ram_global(&s->prom);
+ memory_region_set_readonly(&s->prom, true);
+ sysbus_init_mmio(dev, &s->prom);
+ return 0;
+}
+
+static Property prom_properties[] = {
+ {/* end of property list */},
+};
+
+static void prom_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = prom_init1;
+ dc->props = prom_properties;
+}
+
+static const TypeInfo prom_info = {
+ .name = TYPE_OPENPROM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PROMState),
+ .class_init = prom_class_init,
+};
+
+
+#define TYPE_SUN4U_MEMORY "memory"
+#define SUN4U_RAM(obj) OBJECT_CHECK(RamDevice, (obj), TYPE_SUN4U_MEMORY)
+
+typedef struct RamDevice {
+ SysBusDevice parent_obj;
+
+ MemoryRegion ram;
+ uint64_t size;
+} RamDevice;
+
+/* System RAM */
+static int ram_init1(SysBusDevice *dev)
+{
+ RamDevice *d = SUN4U_RAM(dev);
+
+ memory_region_init_ram(&d->ram, OBJECT(d), "sun4u.ram", d->size,
+ &error_abort);
+ vmstate_register_ram_global(&d->ram);
+ sysbus_init_mmio(dev, &d->ram);
+ return 0;
+}
+
+static void ram_init(hwaddr addr, ram_addr_t RAM_size)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ RamDevice *d;
+
+ /* allocate RAM */
+ dev = qdev_create(NULL, TYPE_SUN4U_MEMORY);
+ s = SYS_BUS_DEVICE(dev);
+
+ d = SUN4U_RAM(dev);
+ d->size = RAM_size;
+ qdev_init_nofail(dev);
+
+ sysbus_mmio_map(s, 0, addr);
+}
+
+static Property ram_properties[] = {
+ DEFINE_PROP_UINT64("size", RamDevice, size, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ram_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = ram_init1;
+ dc->props = ram_properties;
+}
+
+static const TypeInfo ram_info = {
+ .name = TYPE_SUN4U_MEMORY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RamDevice),
+ .class_init = ram_class_init,
+};
+
+static SPARCCPU *cpu_devinit(const char *cpu_model, const struct hwdef *hwdef)
+{
+ SPARCCPU *cpu;
+ CPUSPARCState *env;
+ ResetData *reset_info;
+
+ uint32_t tick_frequency = 100*1000000;
+ uint32_t stick_frequency = 100*1000000;
+ uint32_t hstick_frequency = 100*1000000;
+
+ if (cpu_model == NULL) {
+ cpu_model = hwdef->default_cpu_model;
+ }
+ cpu = cpu_sparc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find Sparc CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ env->tick = cpu_timer_create("tick", cpu, tick_irq,
+ tick_frequency, TICK_NPT_MASK);
+
+ env->stick = cpu_timer_create("stick", cpu, stick_irq,
+ stick_frequency, TICK_INT_DIS);
+
+ env->hstick = cpu_timer_create("hstick", cpu, hstick_irq,
+ hstick_frequency, TICK_INT_DIS);
+
+ reset_info = g_malloc0(sizeof(ResetData));
+ reset_info->cpu = cpu;
+ reset_info->prom_addr = hwdef->prom_addr;
+ qemu_register_reset(main_cpu_reset, reset_info);
+
+ return cpu;
+}
+
+static void sun4uv_init(MemoryRegion *address_space_mem,
+ MachineState *machine,
+ const struct hwdef *hwdef)
+{
+ SPARCCPU *cpu;
+ Nvram *nvram;
+ unsigned int i;
+ uint64_t initrd_addr, initrd_size, kernel_addr, kernel_size, kernel_entry;
+ PCIBus *pci_bus, *pci_bus2, *pci_bus3;
+ ISABus *isa_bus;
+ SysBusDevice *s;
+ qemu_irq *ivec_irqs, *pbm_irqs;
+ DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+ DriveInfo *fd[MAX_FD];
+ FWCfgState *fw_cfg;
+
+ /* init CPUs */
+ cpu = cpu_devinit(machine->cpu_model, hwdef);
+
+ /* set up devices */
+ ram_init(0, machine->ram_size);
+
+ prom_init(hwdef->prom_addr, bios_name);
+
+ ivec_irqs = qemu_allocate_irqs(cpu_set_ivec_irq, cpu, IVEC_MAX);
+ pci_bus = pci_apb_init(APB_SPECIAL_BASE, APB_MEM_BASE, ivec_irqs, &pci_bus2,
+ &pci_bus3, &pbm_irqs);
+ pci_vga_init(pci_bus);
+
+ // XXX Should be pci_bus3
+ isa_bus = pci_ebus_init(pci_bus, -1, pbm_irqs);
+
+ i = 0;
+ if (hwdef->console_serial_base) {
+ serial_mm_init(address_space_mem, hwdef->console_serial_base, 0,
+ NULL, 115200, serial_hds[i], DEVICE_BIG_ENDIAN);
+ i++;
+ }
+
+ serial_hds_isa_init(isa_bus, MAX_SERIAL_PORTS);
+ parallel_hds_isa_init(isa_bus, MAX_PARALLEL_PORTS);
+
+ for(i = 0; i < nb_nics; i++)
+ pci_nic_init_nofail(&nd_table[i], pci_bus, "ne2k_pci", NULL);
+
+ ide_drive_get(hd, ARRAY_SIZE(hd));
+
+ pci_cmd646_ide_init(pci_bus, hd, 1);
+
+ isa_create_simple(isa_bus, "i8042");
+ for(i = 0; i < MAX_FD; i++) {
+ fd[i] = drive_get(IF_FLOPPY, 0, i);
+ }
+ fdctrl_init_isa(isa_bus, fd);
+
+ /* Map NVRAM into I/O (ebus) space */
+ nvram = m48t59_init(NULL, 0, 0, NVRAM_SIZE, 1968, 59);
+ s = SYS_BUS_DEVICE(nvram);
+ memory_region_add_subregion(get_system_io(), 0x2000,
+ sysbus_mmio_get_region(s, 0));
+
+ initrd_size = 0;
+ initrd_addr = 0;
+ kernel_size = sun4u_load_kernel(machine->kernel_filename,
+ machine->initrd_filename,
+ ram_size, &initrd_size, &initrd_addr,
+ &kernel_addr, &kernel_entry);
+
+ sun4u_NVRAM_set_params(nvram, NVRAM_SIZE, "Sun4u", machine->ram_size,
+ machine->boot_order,
+ kernel_addr, kernel_size,
+ machine->kernel_cmdline,
+ initrd_addr, initrd_size,
+ /* XXX: need an option to load a NVRAM image */
+ 0,
+ graphic_width, graphic_height, graphic_depth,
+ (uint8_t *)&nd_table[0].macaddr);
+
+ fw_cfg = fw_cfg_init_io(BIOS_CFG_IOPORT);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, (uint16_t)max_cpus);
+ fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MACHINE_ID, hwdef->machine_id);
+ fw_cfg_add_i64(fw_cfg, FW_CFG_KERNEL_ADDR, kernel_entry);
+ fw_cfg_add_i64(fw_cfg, FW_CFG_KERNEL_SIZE, kernel_size);
+ if (machine->kernel_cmdline) {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE,
+ strlen(machine->kernel_cmdline) + 1);
+ fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, machine->kernel_cmdline);
+ } else {
+ fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, 0);
+ }
+ fw_cfg_add_i64(fw_cfg, FW_CFG_INITRD_ADDR, initrd_addr);
+ fw_cfg_add_i64(fw_cfg, FW_CFG_INITRD_SIZE, initrd_size);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_BOOT_DEVICE, machine->boot_order[0]);
+
+ fw_cfg_add_i16(fw_cfg, FW_CFG_SPARC64_WIDTH, graphic_width);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_SPARC64_HEIGHT, graphic_height);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_SPARC64_DEPTH, graphic_depth);
+
+ qemu_register_boot_set(fw_cfg_boot_set, fw_cfg);
+}
+
+enum {
+ sun4u_id = 0,
+ sun4v_id = 64,
+ niagara_id,
+};
+
+static const struct hwdef hwdefs[] = {
+ /* Sun4u generic PC-like machine */
+ {
+ .default_cpu_model = "TI UltraSparc IIi",
+ .machine_id = sun4u_id,
+ .prom_addr = 0x1fff0000000ULL,
+ .console_serial_base = 0,
+ },
+ /* Sun4v generic PC-like machine */
+ {
+ .default_cpu_model = "Sun UltraSparc T1",
+ .machine_id = sun4v_id,
+ .prom_addr = 0x1fff0000000ULL,
+ .console_serial_base = 0,
+ },
+ /* Sun4v generic Niagara machine */
+ {
+ .default_cpu_model = "Sun UltraSparc T1",
+ .machine_id = niagara_id,
+ .prom_addr = 0xfff0000000ULL,
+ .console_serial_base = 0xfff0c2c000ULL,
+ },
+};
+
+/* Sun4u hardware initialisation */
+static void sun4u_init(MachineState *machine)
+{
+ sun4uv_init(get_system_memory(), machine, &hwdefs[0]);
+}
+
+/* Sun4v hardware initialisation */
+static void sun4v_init(MachineState *machine)
+{
+ sun4uv_init(get_system_memory(), machine, &hwdefs[1]);
+}
+
+/* Niagara hardware initialisation */
+static void niagara_init(MachineState *machine)
+{
+ sun4uv_init(get_system_memory(), machine, &hwdefs[2]);
+}
+
+static QEMUMachine sun4u_machine = {
+ .name = "sun4u",
+ .desc = "Sun4u platform",
+ .init = sun4u_init,
+ .max_cpus = 1, // XXX for now
+ .is_default = 1,
+ .default_boot_order = "c",
+};
+
+static QEMUMachine sun4v_machine = {
+ .name = "sun4v",
+ .desc = "Sun4v platform",
+ .init = sun4v_init,
+ .max_cpus = 1, // XXX for now
+ .default_boot_order = "c",
+};
+
+static QEMUMachine niagara_machine = {
+ .name = "Niagara",
+ .desc = "Sun4v platform, Niagara",
+ .init = niagara_init,
+ .max_cpus = 1, // XXX for now
+ .default_boot_order = "c",
+};
+
+static void sun4u_register_types(void)
+{
+ type_register_static(&ebus_info);
+ type_register_static(&prom_info);
+ type_register_static(&ram_info);
+}
+
+static void sun4u_machine_init(void)
+{
+ qemu_register_machine(&sun4u_machine);
+ qemu_register_machine(&sun4v_machine);
+ qemu_register_machine(&niagara_machine);
+}
+
+type_init(sun4u_register_types)
+machine_init(sun4u_machine_init);
diff --git a/hw/ssi/Makefile.objs b/hw/ssi/Makefile.objs
new file mode 100644
index 00000000..9555825a
--- /dev/null
+++ b/hw/ssi/Makefile.objs
@@ -0,0 +1,6 @@
+common-obj-$(CONFIG_PL022) += pl022.o
+common-obj-$(CONFIG_SSI) += ssi.o
+common-obj-$(CONFIG_XILINX_SPI) += xilinx_spi.o
+common-obj-$(CONFIG_XILINX_SPIPS) += xilinx_spips.o
+
+obj-$(CONFIG_OMAP) += omap_spi.o
diff --git a/hw/ssi/omap_spi.c b/hw/ssi/omap_spi.c
new file mode 100644
index 00000000..119e325a
--- /dev/null
+++ b/hw/ssi/omap_spi.c
@@ -0,0 +1,374 @@
+/*
+ * TI OMAP processor's Multichannel SPI emulation.
+ *
+ * Copyright (C) 2007-2009 Nokia Corporation
+ *
+ * Original code for OMAP2 by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+
+/* Multichannel SPI */
+struct omap_mcspi_s {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ int chnum;
+
+ uint32_t sysconfig;
+ uint32_t systest;
+ uint32_t irqst;
+ uint32_t irqen;
+ uint32_t wken;
+ uint32_t control;
+
+ struct omap_mcspi_ch_s {
+ qemu_irq txdrq;
+ qemu_irq rxdrq;
+ uint32_t (*txrx)(void *opaque, uint32_t, int);
+ void *opaque;
+
+ uint32_t tx;
+ uint32_t rx;
+
+ uint32_t config;
+ uint32_t status;
+ uint32_t control;
+ } ch[4];
+};
+
+static inline void omap_mcspi_interrupt_update(struct omap_mcspi_s *s)
+{
+ qemu_set_irq(s->irq, s->irqst & s->irqen);
+}
+
+static inline void omap_mcspi_dmarequest_update(struct omap_mcspi_ch_s *ch)
+{
+ qemu_set_irq(ch->txdrq,
+ (ch->control & 1) && /* EN */
+ (ch->config & (1 << 14)) && /* DMAW */
+ (ch->status & (1 << 1)) && /* TXS */
+ ((ch->config >> 12) & 3) != 1); /* TRM */
+ qemu_set_irq(ch->rxdrq,
+ (ch->control & 1) && /* EN */
+ (ch->config & (1 << 15)) && /* DMAW */
+ (ch->status & (1 << 0)) && /* RXS */
+ ((ch->config >> 12) & 3) != 2); /* TRM */
+}
+
+static void omap_mcspi_transfer_run(struct omap_mcspi_s *s, int chnum)
+{
+ struct omap_mcspi_ch_s *ch = s->ch + chnum;
+
+ if (!(ch->control & 1)) /* EN */
+ return;
+ if ((ch->status & (1 << 0)) && /* RXS */
+ ((ch->config >> 12) & 3) != 2 && /* TRM */
+ !(ch->config & (1 << 19))) /* TURBO */
+ goto intr_update;
+ if ((ch->status & (1 << 1)) && /* TXS */
+ ((ch->config >> 12) & 3) != 1) /* TRM */
+ goto intr_update;
+
+ if (!(s->control & 1) || /* SINGLE */
+ (ch->config & (1 << 20))) { /* FORCE */
+ if (ch->txrx)
+ ch->rx = ch->txrx(ch->opaque, ch->tx, /* WL */
+ 1 + (0x1f & (ch->config >> 7)));
+ }
+
+ ch->tx = 0;
+ ch->status |= 1 << 2; /* EOT */
+ ch->status |= 1 << 1; /* TXS */
+ if (((ch->config >> 12) & 3) != 2) /* TRM */
+ ch->status |= 1 << 0; /* RXS */
+
+intr_update:
+ if ((ch->status & (1 << 0)) && /* RXS */
+ ((ch->config >> 12) & 3) != 2 && /* TRM */
+ !(ch->config & (1 << 19))) /* TURBO */
+ s->irqst |= 1 << (2 + 4 * chnum); /* RX_FULL */
+ if ((ch->status & (1 << 1)) && /* TXS */
+ ((ch->config >> 12) & 3) != 1) /* TRM */
+ s->irqst |= 1 << (0 + 4 * chnum); /* TX_EMPTY */
+ omap_mcspi_interrupt_update(s);
+ omap_mcspi_dmarequest_update(ch);
+}
+
+void omap_mcspi_reset(struct omap_mcspi_s *s)
+{
+ int ch;
+
+ s->sysconfig = 0;
+ s->systest = 0;
+ s->irqst = 0;
+ s->irqen = 0;
+ s->wken = 0;
+ s->control = 4;
+
+ for (ch = 0; ch < 4; ch ++) {
+ s->ch[ch].config = 0x060000;
+ s->ch[ch].status = 2; /* TXS */
+ s->ch[ch].control = 0;
+
+ omap_mcspi_dmarequest_update(s->ch + ch);
+ }
+
+ omap_mcspi_interrupt_update(s);
+}
+
+static uint64_t omap_mcspi_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
+ int ch = 0;
+ uint32_t ret;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* MCSPI_REVISION */
+ return 0x91;
+
+ case 0x10: /* MCSPI_SYSCONFIG */
+ return s->sysconfig;
+
+ case 0x14: /* MCSPI_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x18: /* MCSPI_IRQSTATUS */
+ return s->irqst;
+
+ case 0x1c: /* MCSPI_IRQENABLE */
+ return s->irqen;
+
+ case 0x20: /* MCSPI_WAKEUPENABLE */
+ return s->wken;
+
+ case 0x24: /* MCSPI_SYST */
+ return s->systest;
+
+ case 0x28: /* MCSPI_MODULCTRL */
+ return s->control;
+
+ case 0x68: ch ++;
+ /* fall through */
+ case 0x54: ch ++;
+ /* fall through */
+ case 0x40: ch ++;
+ /* fall through */
+ case 0x2c: /* MCSPI_CHCONF */
+ return s->ch[ch].config;
+
+ case 0x6c: ch ++;
+ /* fall through */
+ case 0x58: ch ++;
+ /* fall through */
+ case 0x44: ch ++;
+ /* fall through */
+ case 0x30: /* MCSPI_CHSTAT */
+ return s->ch[ch].status;
+
+ case 0x70: ch ++;
+ /* fall through */
+ case 0x5c: ch ++;
+ /* fall through */
+ case 0x48: ch ++;
+ /* fall through */
+ case 0x34: /* MCSPI_CHCTRL */
+ return s->ch[ch].control;
+
+ case 0x74: ch ++;
+ /* fall through */
+ case 0x60: ch ++;
+ /* fall through */
+ case 0x4c: ch ++;
+ /* fall through */
+ case 0x38: /* MCSPI_TX */
+ return s->ch[ch].tx;
+
+ case 0x78: ch ++;
+ /* fall through */
+ case 0x64: ch ++;
+ /* fall through */
+ case 0x50: ch ++;
+ /* fall through */
+ case 0x3c: /* MCSPI_RX */
+ s->ch[ch].status &= ~(1 << 0); /* RXS */
+ ret = s->ch[ch].rx;
+ omap_mcspi_transfer_run(s, ch);
+ return ret;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_mcspi_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
+ int ch = 0;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* MCSPI_REVISION */
+ case 0x14: /* MCSPI_SYSSTATUS */
+ case 0x30: /* MCSPI_CHSTAT0 */
+ case 0x3c: /* MCSPI_RX0 */
+ case 0x44: /* MCSPI_CHSTAT1 */
+ case 0x50: /* MCSPI_RX1 */
+ case 0x58: /* MCSPI_CHSTAT2 */
+ case 0x64: /* MCSPI_RX2 */
+ case 0x6c: /* MCSPI_CHSTAT3 */
+ case 0x78: /* MCSPI_RX3 */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x10: /* MCSPI_SYSCONFIG */
+ if (value & (1 << 1)) /* SOFTRESET */
+ omap_mcspi_reset(s);
+ s->sysconfig = value & 0x31d;
+ break;
+
+ case 0x18: /* MCSPI_IRQSTATUS */
+ if (!((s->control & (1 << 3)) && (s->systest & (1 << 11)))) {
+ s->irqst &= ~value;
+ omap_mcspi_interrupt_update(s);
+ }
+ break;
+
+ case 0x1c: /* MCSPI_IRQENABLE */
+ s->irqen = value & 0x1777f;
+ omap_mcspi_interrupt_update(s);
+ break;
+
+ case 0x20: /* MCSPI_WAKEUPENABLE */
+ s->wken = value & 1;
+ break;
+
+ case 0x24: /* MCSPI_SYST */
+ if (s->control & (1 << 3)) /* SYSTEM_TEST */
+ if (value & (1 << 11)) { /* SSB */
+ s->irqst |= 0x1777f;
+ omap_mcspi_interrupt_update(s);
+ }
+ s->systest = value & 0xfff;
+ break;
+
+ case 0x28: /* MCSPI_MODULCTRL */
+ if (value & (1 << 3)) /* SYSTEM_TEST */
+ if (s->systest & (1 << 11)) { /* SSB */
+ s->irqst |= 0x1777f;
+ omap_mcspi_interrupt_update(s);
+ }
+ s->control = value & 0xf;
+ break;
+
+ case 0x68: ch ++;
+ /* fall through */
+ case 0x54: ch ++;
+ /* fall through */
+ case 0x40: ch ++;
+ /* fall through */
+ case 0x2c: /* MCSPI_CHCONF */
+ if ((value ^ s->ch[ch].config) & (3 << 14)) /* DMAR | DMAW */
+ omap_mcspi_dmarequest_update(s->ch + ch);
+ if (((value >> 12) & 3) == 3) /* TRM */
+ fprintf(stderr, "%s: invalid TRM value (3)\n", __FUNCTION__);
+ if (((value >> 7) & 0x1f) < 3) /* WL */
+ fprintf(stderr, "%s: invalid WL value (%" PRIx64 ")\n",
+ __FUNCTION__, (value >> 7) & 0x1f);
+ s->ch[ch].config = value & 0x7fffff;
+ break;
+
+ case 0x70: ch ++;
+ /* fall through */
+ case 0x5c: ch ++;
+ /* fall through */
+ case 0x48: ch ++;
+ /* fall through */
+ case 0x34: /* MCSPI_CHCTRL */
+ if (value & ~s->ch[ch].control & 1) { /* EN */
+ s->ch[ch].control |= 1;
+ omap_mcspi_transfer_run(s, ch);
+ } else
+ s->ch[ch].control = value & 1;
+ break;
+
+ case 0x74: ch ++;
+ /* fall through */
+ case 0x60: ch ++;
+ /* fall through */
+ case 0x4c: ch ++;
+ /* fall through */
+ case 0x38: /* MCSPI_TX */
+ s->ch[ch].tx = value;
+ s->ch[ch].status &= ~(1 << 1); /* TXS */
+ omap_mcspi_transfer_run(s, ch);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_mcspi_ops = {
+ .read = omap_mcspi_read,
+ .write = omap_mcspi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_mcspi_s *omap_mcspi_init(struct omap_target_agent_s *ta, int chnum,
+ qemu_irq irq, qemu_irq *drq, omap_clk fclk, omap_clk iclk)
+{
+ struct omap_mcspi_s *s = (struct omap_mcspi_s *)
+ g_malloc0(sizeof(struct omap_mcspi_s));
+ struct omap_mcspi_ch_s *ch = s->ch;
+
+ s->irq = irq;
+ s->chnum = chnum;
+ while (chnum --) {
+ ch->txdrq = *drq ++;
+ ch->rxdrq = *drq ++;
+ ch ++;
+ }
+ omap_mcspi_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_mcspi_ops, s, "omap.mcspi",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ return s;
+}
+
+void omap_mcspi_attach(struct omap_mcspi_s *s,
+ uint32_t (*txrx)(void *opaque, uint32_t, int), void *opaque,
+ int chipselect)
+{
+ if (chipselect < 0 || chipselect >= s->chnum)
+ hw_error("%s: Bad chipselect %i\n", __FUNCTION__, chipselect);
+
+ s->ch[chipselect].txrx = txrx;
+ s->ch[chipselect].opaque = opaque;
+}
diff --git a/hw/ssi/pl022.c b/hw/ssi/pl022.c
new file mode 100644
index 00000000..61d568f3
--- /dev/null
+++ b/hw/ssi/pl022.c
@@ -0,0 +1,326 @@
+/*
+ * Arm PrimeCell PL022 Synchronous Serial Port
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/ssi.h"
+
+//#define DEBUG_PL022 1
+
+#ifdef DEBUG_PL022
+#define DPRINTF(fmt, ...) \
+do { printf("pl022: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "pl022: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "pl022: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+#define PL022_CR1_LBM 0x01
+#define PL022_CR1_SSE 0x02
+#define PL022_CR1_MS 0x04
+#define PL022_CR1_SDO 0x08
+
+#define PL022_SR_TFE 0x01
+#define PL022_SR_TNF 0x02
+#define PL022_SR_RNE 0x04
+#define PL022_SR_RFF 0x08
+#define PL022_SR_BSY 0x10
+
+#define PL022_INT_ROR 0x01
+#define PL022_INT_RT 0x04
+#define PL022_INT_RX 0x04
+#define PL022_INT_TX 0x08
+
+#define TYPE_PL022 "pl022"
+#define PL022(obj) OBJECT_CHECK(PL022State, (obj), TYPE_PL022)
+
+typedef struct PL022State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t cr0;
+ uint32_t cr1;
+ uint32_t bitmask;
+ uint32_t sr;
+ uint32_t cpsr;
+ uint32_t is;
+ uint32_t im;
+ /* The FIFO head points to the next empty entry. */
+ int tx_fifo_head;
+ int rx_fifo_head;
+ int tx_fifo_len;
+ int rx_fifo_len;
+ uint16_t tx_fifo[8];
+ uint16_t rx_fifo[8];
+ qemu_irq irq;
+ SSIBus *ssi;
+} PL022State;
+
+static const unsigned char pl022_id[8] =
+ { 0x22, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static void pl022_update(PL022State *s)
+{
+ s->sr = 0;
+ if (s->tx_fifo_len == 0)
+ s->sr |= PL022_SR_TFE;
+ if (s->tx_fifo_len != 8)
+ s->sr |= PL022_SR_TNF;
+ if (s->rx_fifo_len != 0)
+ s->sr |= PL022_SR_RNE;
+ if (s->rx_fifo_len == 8)
+ s->sr |= PL022_SR_RFF;
+ if (s->tx_fifo_len)
+ s->sr |= PL022_SR_BSY;
+ s->is = 0;
+ if (s->rx_fifo_len >= 4)
+ s->is |= PL022_INT_RX;
+ if (s->tx_fifo_len <= 4)
+ s->is |= PL022_INT_TX;
+
+ qemu_set_irq(s->irq, (s->is & s->im) != 0);
+}
+
+static void pl022_xfer(PL022State *s)
+{
+ int i;
+ int o;
+ int val;
+
+ if ((s->cr1 & PL022_CR1_SSE) == 0) {
+ pl022_update(s);
+ DPRINTF("Disabled\n");
+ return;
+ }
+
+ DPRINTF("Maybe xfer %d/%d\n", s->tx_fifo_len, s->rx_fifo_len);
+ i = (s->tx_fifo_head - s->tx_fifo_len) & 7;
+ o = s->rx_fifo_head;
+ /* ??? We do not emulate the line speed.
+ This may break some applications. The are two problematic cases:
+ (a) A driver feeds data into the TX FIFO until it is full,
+ and only then drains the RX FIFO. On real hardware the CPU can
+ feed data fast enough that the RX fifo never gets chance to overflow.
+ (b) A driver transmits data, deliberately allowing the RX FIFO to
+ overflow because it ignores the RX data anyway.
+
+ We choose to support (a) by stalling the transmit engine if it would
+ cause the RX FIFO to overflow. In practice much transmit-only code
+ falls into (a) because it flushes the RX FIFO to determine when
+ the transfer has completed. */
+ while (s->tx_fifo_len && s->rx_fifo_len < 8) {
+ DPRINTF("xfer\n");
+ val = s->tx_fifo[i];
+ if (s->cr1 & PL022_CR1_LBM) {
+ /* Loopback mode. */
+ } else {
+ val = ssi_transfer(s->ssi, val);
+ }
+ s->rx_fifo[o] = val & s->bitmask;
+ i = (i + 1) & 7;
+ o = (o + 1) & 7;
+ s->tx_fifo_len--;
+ s->rx_fifo_len++;
+ }
+ s->rx_fifo_head = o;
+ pl022_update(s);
+}
+
+static uint64_t pl022_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL022State *s = (PL022State *)opaque;
+ int val;
+
+ if (offset >= 0xfe0 && offset < 0x1000) {
+ return pl022_id[(offset - 0xfe0) >> 2];
+ }
+ switch (offset) {
+ case 0x00: /* CR0 */
+ return s->cr0;
+ case 0x04: /* CR1 */
+ return s->cr1;
+ case 0x08: /* DR */
+ if (s->rx_fifo_len) {
+ val = s->rx_fifo[(s->rx_fifo_head - s->rx_fifo_len) & 7];
+ DPRINTF("RX %02x\n", val);
+ s->rx_fifo_len--;
+ pl022_xfer(s);
+ } else {
+ val = 0;
+ }
+ return val;
+ case 0x0c: /* SR */
+ return s->sr;
+ case 0x10: /* CPSR */
+ return s->cpsr;
+ case 0x14: /* IMSC */
+ return s->im;
+ case 0x18: /* RIS */
+ return s->is;
+ case 0x1c: /* MIS */
+ return s->im & s->is;
+ case 0x20: /* DMACR */
+ /* Not implemented. */
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl022_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl022_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL022State *s = (PL022State *)opaque;
+
+ switch (offset) {
+ case 0x00: /* CR0 */
+ s->cr0 = value;
+ /* Clock rate and format are ignored. */
+ s->bitmask = (1 << ((value & 15) + 1)) - 1;
+ break;
+ case 0x04: /* CR1 */
+ s->cr1 = value;
+ if ((s->cr1 & (PL022_CR1_MS | PL022_CR1_SSE))
+ == (PL022_CR1_MS | PL022_CR1_SSE)) {
+ BADF("SPI slave mode not implemented\n");
+ }
+ pl022_xfer(s);
+ break;
+ case 0x08: /* DR */
+ if (s->tx_fifo_len < 8) {
+ DPRINTF("TX %02x\n", (unsigned)value);
+ s->tx_fifo[s->tx_fifo_head] = value & s->bitmask;
+ s->tx_fifo_head = (s->tx_fifo_head + 1) & 7;
+ s->tx_fifo_len++;
+ pl022_xfer(s);
+ }
+ break;
+ case 0x10: /* CPSR */
+ /* Prescaler. Ignored. */
+ s->cpsr = value & 0xff;
+ break;
+ case 0x14: /* IMSC */
+ s->im = value;
+ pl022_update(s);
+ break;
+ case 0x20: /* DMACR */
+ if (value) {
+ qemu_log_mask(LOG_UNIMP, "pl022: DMA not implemented\n");
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl022_write: Bad offset %x\n", (int)offset);
+ }
+}
+
+static void pl022_reset(PL022State *s)
+{
+ s->rx_fifo_len = 0;
+ s->tx_fifo_len = 0;
+ s->im = 0;
+ s->is = PL022_INT_TX;
+ s->sr = PL022_SR_TFE | PL022_SR_TNF;
+}
+
+static const MemoryRegionOps pl022_ops = {
+ .read = pl022_read,
+ .write = pl022_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pl022_post_load(void *opaque, int version_id)
+{
+ PL022State *s = opaque;
+
+ if (s->tx_fifo_head < 0 ||
+ s->tx_fifo_head >= ARRAY_SIZE(s->tx_fifo) ||
+ s->rx_fifo_head < 0 ||
+ s->rx_fifo_head >= ARRAY_SIZE(s->rx_fifo)) {
+ return -1;
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_pl022 = {
+ .name = "pl022_ssp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pl022_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cr0, PL022State),
+ VMSTATE_UINT32(cr1, PL022State),
+ VMSTATE_UINT32(bitmask, PL022State),
+ VMSTATE_UINT32(sr, PL022State),
+ VMSTATE_UINT32(cpsr, PL022State),
+ VMSTATE_UINT32(is, PL022State),
+ VMSTATE_UINT32(im, PL022State),
+ VMSTATE_INT32(tx_fifo_head, PL022State),
+ VMSTATE_INT32(rx_fifo_head, PL022State),
+ VMSTATE_INT32(tx_fifo_len, PL022State),
+ VMSTATE_INT32(rx_fifo_len, PL022State),
+ VMSTATE_UINT16(tx_fifo[0], PL022State),
+ VMSTATE_UINT16(rx_fifo[0], PL022State),
+ VMSTATE_UINT16(tx_fifo[1], PL022State),
+ VMSTATE_UINT16(rx_fifo[1], PL022State),
+ VMSTATE_UINT16(tx_fifo[2], PL022State),
+ VMSTATE_UINT16(rx_fifo[2], PL022State),
+ VMSTATE_UINT16(tx_fifo[3], PL022State),
+ VMSTATE_UINT16(rx_fifo[3], PL022State),
+ VMSTATE_UINT16(tx_fifo[4], PL022State),
+ VMSTATE_UINT16(rx_fifo[4], PL022State),
+ VMSTATE_UINT16(tx_fifo[5], PL022State),
+ VMSTATE_UINT16(rx_fifo[5], PL022State),
+ VMSTATE_UINT16(tx_fifo[6], PL022State),
+ VMSTATE_UINT16(rx_fifo[6], PL022State),
+ VMSTATE_UINT16(tx_fifo[7], PL022State),
+ VMSTATE_UINT16(rx_fifo[7], PL022State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int pl022_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ PL022State *s = PL022(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl022_ops, s, "pl022", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ s->ssi = ssi_create_bus(dev, "ssi");
+ pl022_reset(s);
+ vmstate_register(dev, -1, &vmstate_pl022, s);
+ return 0;
+}
+
+static void pl022_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = pl022_init;
+}
+
+static const TypeInfo pl022_info = {
+ .name = TYPE_PL022,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL022State),
+ .class_init = pl022_class_init,
+};
+
+static void pl022_register_types(void)
+{
+ type_register_static(&pl022_info);
+}
+
+type_init(pl022_register_types)
diff --git a/hw/ssi/ssi.c b/hw/ssi/ssi.c
new file mode 100644
index 00000000..2aab79ba
--- /dev/null
+++ b/hw/ssi/ssi.c
@@ -0,0 +1,174 @@
+/*
+ * QEMU Synchronous Serial Interface support
+ *
+ * Copyright (c) 2009 CodeSourcery.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/ssi.h"
+
+struct SSIBus {
+ BusState parent_obj;
+};
+
+#define TYPE_SSI_BUS "SSI"
+#define SSI_BUS(obj) OBJECT_CHECK(SSIBus, (obj), TYPE_SSI_BUS)
+
+static const TypeInfo ssi_bus_info = {
+ .name = TYPE_SSI_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(SSIBus),
+};
+
+static void ssi_cs_default(void *opaque, int n, int level)
+{
+ SSISlave *s = SSI_SLAVE(opaque);
+ bool cs = !!level;
+ assert(n == 0);
+ if (s->cs != cs) {
+ SSISlaveClass *ssc = SSI_SLAVE_GET_CLASS(s);
+ if (ssc->set_cs) {
+ ssc->set_cs(s, cs);
+ }
+ }
+ s->cs = cs;
+}
+
+static uint32_t ssi_transfer_raw_default(SSISlave *dev, uint32_t val)
+{
+ SSISlaveClass *ssc = SSI_SLAVE_GET_CLASS(dev);
+
+ if ((dev->cs && ssc->cs_polarity == SSI_CS_HIGH) ||
+ (!dev->cs && ssc->cs_polarity == SSI_CS_LOW) ||
+ ssc->cs_polarity == SSI_CS_NONE) {
+ return ssc->transfer(dev, val);
+ }
+ return 0;
+}
+
+static int ssi_slave_init(DeviceState *dev)
+{
+ SSISlave *s = SSI_SLAVE(dev);
+ SSISlaveClass *ssc = SSI_SLAVE_GET_CLASS(s);
+
+ if (ssc->transfer_raw == ssi_transfer_raw_default &&
+ ssc->cs_polarity != SSI_CS_NONE) {
+ qdev_init_gpio_in_named(dev, ssi_cs_default, SSI_GPIO_CS, 1);
+ }
+
+ return ssc->init(s);
+}
+
+static void ssi_slave_class_init(ObjectClass *klass, void *data)
+{
+ SSISlaveClass *ssc = SSI_SLAVE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->init = ssi_slave_init;
+ dc->bus_type = TYPE_SSI_BUS;
+ if (!ssc->transfer_raw) {
+ ssc->transfer_raw = ssi_transfer_raw_default;
+ }
+}
+
+static const TypeInfo ssi_slave_info = {
+ .name = TYPE_SSI_SLAVE,
+ .parent = TYPE_DEVICE,
+ .class_init = ssi_slave_class_init,
+ .class_size = sizeof(SSISlaveClass),
+ .abstract = true,
+};
+
+DeviceState *ssi_create_slave_no_init(SSIBus *bus, const char *name)
+{
+ return qdev_create(BUS(bus), name);
+}
+
+DeviceState *ssi_create_slave(SSIBus *bus, const char *name)
+{
+ DeviceState *dev = ssi_create_slave_no_init(bus, name);
+
+ qdev_init_nofail(dev);
+ return dev;
+}
+
+SSIBus *ssi_create_bus(DeviceState *parent, const char *name)
+{
+ BusState *bus;
+ bus = qbus_create(TYPE_SSI_BUS, parent, name);
+ return SSI_BUS(bus);
+}
+
+uint32_t ssi_transfer(SSIBus *bus, uint32_t val)
+{
+ BusState *b = BUS(bus);
+ BusChild *kid;
+ SSISlaveClass *ssc;
+ uint32_t r = 0;
+
+ QTAILQ_FOREACH(kid, &b->children, sibling) {
+ SSISlave *slave = SSI_SLAVE(kid->child);
+ ssc = SSI_SLAVE_GET_CLASS(slave);
+ r |= ssc->transfer_raw(slave, val);
+ }
+
+ return r;
+}
+
+const VMStateDescription vmstate_ssi_slave = {
+ .name = "SSISlave",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(cs, SSISlave),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ssi_slave_register_types(void)
+{
+ type_register_static(&ssi_bus_info);
+ type_register_static(&ssi_slave_info);
+}
+
+type_init(ssi_slave_register_types)
+
+typedef struct SSIAutoConnectArg {
+ qemu_irq **cs_linep;
+ SSIBus *bus;
+} SSIAutoConnectArg;
+
+static int ssi_auto_connect_slave(Object *child, void *opaque)
+{
+ SSIAutoConnectArg *arg = opaque;
+ SSISlave *dev = (SSISlave *)object_dynamic_cast(child, TYPE_SSI_SLAVE);
+ qemu_irq cs_line;
+
+ if (!dev) {
+ return 0;
+ }
+
+ cs_line = qdev_get_gpio_in_named(DEVICE(dev), SSI_GPIO_CS, 0);
+ qdev_set_parent_bus(DEVICE(dev), BUS(arg->bus));
+ **arg->cs_linep = cs_line;
+ (*arg->cs_linep)++;
+ return 0;
+}
+
+void ssi_auto_connect_slaves(DeviceState *parent, qemu_irq *cs_line,
+ SSIBus *bus)
+{
+ SSIAutoConnectArg arg = {
+ .cs_linep = &cs_line,
+ .bus = bus
+ };
+
+ object_child_foreach(OBJECT(parent), ssi_auto_connect_slave, &arg);
+}
diff --git a/hw/ssi/xilinx_spi.c b/hw/ssi/xilinx_spi.c
new file mode 100644
index 00000000..620573ca
--- /dev/null
+++ b/hw/ssi/xilinx_spi.c
@@ -0,0 +1,390 @@
+/*
+ * QEMU model of the Xilinx SPI Controller
+ *
+ * Copyright (C) 2010 Edgar E. Iglesias.
+ * Copyright (C) 2012 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
+ * Copyright (C) 2012 PetaLogix
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "qemu/log.h"
+#include "qemu/fifo8.h"
+
+#include "hw/ssi.h"
+
+#ifdef XILINX_SPI_ERR_DEBUG
+#define DB_PRINT(...) do { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } while (0);
+#else
+ #define DB_PRINT(...)
+#endif
+
+#define R_DGIER (0x1c / 4)
+#define R_DGIER_IE (1 << 31)
+
+#define R_IPISR (0x20 / 4)
+#define IRQ_DRR_NOT_EMPTY (1 << (31 - 23))
+#define IRQ_DRR_OVERRUN (1 << (31 - 26))
+#define IRQ_DRR_FULL (1 << (31 - 27))
+#define IRQ_TX_FF_HALF_EMPTY (1 << 6)
+#define IRQ_DTR_UNDERRUN (1 << 3)
+#define IRQ_DTR_EMPTY (1 << (31 - 29))
+
+#define R_IPIER (0x28 / 4)
+#define R_SRR (0x40 / 4)
+#define R_SPICR (0x60 / 4)
+#define R_SPICR_TXFF_RST (1 << 5)
+#define R_SPICR_RXFF_RST (1 << 6)
+#define R_SPICR_MTI (1 << 8)
+
+#define R_SPISR (0x64 / 4)
+#define SR_TX_FULL (1 << 3)
+#define SR_TX_EMPTY (1 << 2)
+#define SR_RX_FULL (1 << 1)
+#define SR_RX_EMPTY (1 << 0)
+
+#define R_SPIDTR (0x68 / 4)
+#define R_SPIDRR (0x6C / 4)
+#define R_SPISSR (0x70 / 4)
+#define R_TX_FF_OCY (0x74 / 4)
+#define R_RX_FF_OCY (0x78 / 4)
+#define R_MAX (0x7C / 4)
+
+#define FIFO_CAPACITY 256
+
+#define TYPE_XILINX_SPI "xlnx.xps-spi"
+#define XILINX_SPI(obj) OBJECT_CHECK(XilinxSPI, (obj), TYPE_XILINX_SPI)
+
+typedef struct XilinxSPI {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+
+ qemu_irq irq;
+ int irqline;
+
+ uint8_t num_cs;
+ qemu_irq *cs_lines;
+
+ SSIBus *spi;
+
+ Fifo8 rx_fifo;
+ Fifo8 tx_fifo;
+
+ uint32_t regs[R_MAX];
+} XilinxSPI;
+
+static void txfifo_reset(XilinxSPI *s)
+{
+ fifo8_reset(&s->tx_fifo);
+
+ s->regs[R_SPISR] &= ~SR_TX_FULL;
+ s->regs[R_SPISR] |= SR_TX_EMPTY;
+}
+
+static void rxfifo_reset(XilinxSPI *s)
+{
+ fifo8_reset(&s->rx_fifo);
+
+ s->regs[R_SPISR] |= SR_RX_EMPTY;
+ s->regs[R_SPISR] &= ~SR_RX_FULL;
+}
+
+static void xlx_spi_update_cs(XilinxSPI *s)
+{
+ int i;
+
+ for (i = 0; i < s->num_cs; ++i) {
+ qemu_set_irq(s->cs_lines[i], !(~s->regs[R_SPISSR] & 1 << i));
+ }
+}
+
+static void xlx_spi_update_irq(XilinxSPI *s)
+{
+ uint32_t pending;
+
+ s->regs[R_IPISR] |=
+ (!fifo8_is_empty(&s->rx_fifo) ? IRQ_DRR_NOT_EMPTY : 0) |
+ (fifo8_is_full(&s->rx_fifo) ? IRQ_DRR_FULL : 0);
+
+ pending = s->regs[R_IPISR] & s->regs[R_IPIER];
+
+ pending = pending && (s->regs[R_DGIER] & R_DGIER_IE);
+ pending = !!pending;
+
+ /* This call lies right in the data paths so don't call the
+ irq chain unless things really changed. */
+ if (pending != s->irqline) {
+ s->irqline = pending;
+ DB_PRINT("irq_change of state %d ISR:%x IER:%X\n",
+ pending, s->regs[R_IPISR], s->regs[R_IPIER]);
+ qemu_set_irq(s->irq, pending);
+ }
+
+}
+
+static void xlx_spi_do_reset(XilinxSPI *s)
+{
+ memset(s->regs, 0, sizeof s->regs);
+
+ rxfifo_reset(s);
+ txfifo_reset(s);
+
+ s->regs[R_SPISSR] = ~0;
+ xlx_spi_update_irq(s);
+ xlx_spi_update_cs(s);
+}
+
+static void xlx_spi_reset(DeviceState *d)
+{
+ xlx_spi_do_reset(XILINX_SPI(d));
+}
+
+static inline int spi_master_enabled(XilinxSPI *s)
+{
+ return !(s->regs[R_SPICR] & R_SPICR_MTI);
+}
+
+static void spi_flush_txfifo(XilinxSPI *s)
+{
+ uint32_t tx;
+ uint32_t rx;
+
+ while (!fifo8_is_empty(&s->tx_fifo)) {
+ tx = (uint32_t)fifo8_pop(&s->tx_fifo);
+ DB_PRINT("data tx:%x\n", tx);
+ rx = ssi_transfer(s->spi, tx);
+ DB_PRINT("data rx:%x\n", rx);
+ if (fifo8_is_full(&s->rx_fifo)) {
+ s->regs[R_IPISR] |= IRQ_DRR_OVERRUN;
+ } else {
+ fifo8_push(&s->rx_fifo, (uint8_t)rx);
+ if (fifo8_is_full(&s->rx_fifo)) {
+ s->regs[R_SPISR] |= SR_RX_FULL;
+ s->regs[R_IPISR] |= IRQ_DRR_FULL;
+ }
+ }
+
+ s->regs[R_SPISR] &= ~SR_RX_EMPTY;
+ s->regs[R_SPISR] &= ~SR_TX_FULL;
+ s->regs[R_SPISR] |= SR_TX_EMPTY;
+
+ s->regs[R_IPISR] |= IRQ_DTR_EMPTY;
+ s->regs[R_IPISR] |= IRQ_DRR_NOT_EMPTY;
+ }
+
+}
+
+static uint64_t
+spi_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ XilinxSPI *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SPIDRR:
+ if (fifo8_is_empty(&s->rx_fifo)) {
+ DB_PRINT("Read from empty FIFO!\n");
+ return 0xdeadbeef;
+ }
+
+ s->regs[R_SPISR] &= ~SR_RX_FULL;
+ r = fifo8_pop(&s->rx_fifo);
+ if (fifo8_is_empty(&s->rx_fifo)) {
+ s->regs[R_SPISR] |= SR_RX_EMPTY;
+ }
+ break;
+
+ case R_SPISR:
+ r = s->regs[addr];
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(s->regs)) {
+ r = s->regs[addr];
+ }
+ break;
+
+ }
+ DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr * 4, r);
+ xlx_spi_update_irq(s);
+ return r;
+}
+
+static void
+spi_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ XilinxSPI *s = opaque;
+ uint32_t value = val64;
+
+ DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr, value);
+ addr >>= 2;
+ switch (addr) {
+ case R_SRR:
+ if (value != 0xa) {
+ DB_PRINT("Invalid write to SRR %x\n", value);
+ } else {
+ xlx_spi_do_reset(s);
+ }
+ break;
+
+ case R_SPIDTR:
+ s->regs[R_SPISR] &= ~SR_TX_EMPTY;
+ fifo8_push(&s->tx_fifo, (uint8_t)value);
+ if (fifo8_is_full(&s->tx_fifo)) {
+ s->regs[R_SPISR] |= SR_TX_FULL;
+ }
+ if (!spi_master_enabled(s)) {
+ goto done;
+ } else {
+ DB_PRINT("DTR and master enabled\n");
+ }
+ spi_flush_txfifo(s);
+ break;
+
+ case R_SPISR:
+ DB_PRINT("Invalid write to SPISR %x\n", value);
+ break;
+
+ case R_IPISR:
+ /* Toggle the bits. */
+ s->regs[addr] ^= value;
+ break;
+
+ /* Slave Select Register. */
+ case R_SPISSR:
+ s->regs[addr] = value;
+ xlx_spi_update_cs(s);
+ break;
+
+ case R_SPICR:
+ /* FIXME: reset irq and sr state to empty queues. */
+ if (value & R_SPICR_RXFF_RST) {
+ rxfifo_reset(s);
+ }
+
+ if (value & R_SPICR_TXFF_RST) {
+ txfifo_reset(s);
+ }
+ value &= ~(R_SPICR_RXFF_RST | R_SPICR_TXFF_RST);
+ s->regs[addr] = value;
+
+ if (!(value & R_SPICR_MTI)) {
+ spi_flush_txfifo(s);
+ }
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(s->regs)) {
+ s->regs[addr] = value;
+ }
+ break;
+ }
+
+done:
+ xlx_spi_update_irq(s);
+}
+
+static const MemoryRegionOps spi_ops = {
+ .read = spi_read,
+ .write = spi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static int xilinx_spi_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ XilinxSPI *s = XILINX_SPI(dev);
+ int i;
+
+ DB_PRINT("\n");
+
+ s->spi = ssi_create_bus(dev, "spi");
+
+ sysbus_init_irq(sbd, &s->irq);
+ s->cs_lines = g_new0(qemu_irq, s->num_cs);
+ ssi_auto_connect_slaves(dev, s->cs_lines, s->spi);
+ for (i = 0; i < s->num_cs; ++i) {
+ sysbus_init_irq(sbd, &s->cs_lines[i]);
+ }
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &spi_ops, s,
+ "xilinx-spi", R_MAX * 4);
+ sysbus_init_mmio(sbd, &s->mmio);
+
+ s->irqline = -1;
+
+ fifo8_create(&s->tx_fifo, FIFO_CAPACITY);
+ fifo8_create(&s->rx_fifo, FIFO_CAPACITY);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_xilinx_spi = {
+ .name = "xilinx_spi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_FIFO8(tx_fifo, XilinxSPI),
+ VMSTATE_FIFO8(rx_fifo, XilinxSPI),
+ VMSTATE_UINT32_ARRAY(regs, XilinxSPI, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property xilinx_spi_properties[] = {
+ DEFINE_PROP_UINT8("num-ss-bits", XilinxSPI, num_cs, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_spi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = xilinx_spi_init;
+ dc->reset = xlx_spi_reset;
+ dc->props = xilinx_spi_properties;
+ dc->vmsd = &vmstate_xilinx_spi;
+}
+
+static const TypeInfo xilinx_spi_info = {
+ .name = TYPE_XILINX_SPI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XilinxSPI),
+ .class_init = xilinx_spi_class_init,
+};
+
+static void xilinx_spi_register_types(void)
+{
+ type_register_static(&xilinx_spi_info);
+}
+
+type_init(xilinx_spi_register_types)
diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c
new file mode 100644
index 00000000..0910f547
--- /dev/null
+++ b/hw/ssi/xilinx_spips.c
@@ -0,0 +1,771 @@
+/*
+ * QEMU model of the Xilinx Zynq SPI controller
+ *
+ * Copyright (c) 2012 Peter A. G. Crosthwaite
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "hw/ptimer.h"
+#include "qemu/log.h"
+#include "qemu/fifo8.h"
+#include "hw/ssi.h"
+#include "qemu/bitops.h"
+
+#ifndef XILINX_SPIPS_ERR_DEBUG
+#define XILINX_SPIPS_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(level, ...) do { \
+ if (XILINX_SPIPS_ERR_DEBUG > (level)) { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } \
+} while (0);
+
+/* config register */
+#define R_CONFIG (0x00 / 4)
+#define IFMODE (1U << 31)
+#define ENDIAN (1 << 26)
+#define MODEFAIL_GEN_EN (1 << 17)
+#define MAN_START_COM (1 << 16)
+#define MAN_START_EN (1 << 15)
+#define MANUAL_CS (1 << 14)
+#define CS (0xF << 10)
+#define CS_SHIFT (10)
+#define PERI_SEL (1 << 9)
+#define REF_CLK (1 << 8)
+#define FIFO_WIDTH (3 << 6)
+#define BAUD_RATE_DIV (7 << 3)
+#define CLK_PH (1 << 2)
+#define CLK_POL (1 << 1)
+#define MODE_SEL (1 << 0)
+#define R_CONFIG_RSVD (0x7bf40000)
+
+/* interrupt mechanism */
+#define R_INTR_STATUS (0x04 / 4)
+#define R_INTR_EN (0x08 / 4)
+#define R_INTR_DIS (0x0C / 4)
+#define R_INTR_MASK (0x10 / 4)
+#define IXR_TX_FIFO_UNDERFLOW (1 << 6)
+#define IXR_RX_FIFO_FULL (1 << 5)
+#define IXR_RX_FIFO_NOT_EMPTY (1 << 4)
+#define IXR_TX_FIFO_FULL (1 << 3)
+#define IXR_TX_FIFO_NOT_FULL (1 << 2)
+#define IXR_TX_FIFO_MODE_FAIL (1 << 1)
+#define IXR_RX_FIFO_OVERFLOW (1 << 0)
+#define IXR_ALL ((IXR_TX_FIFO_UNDERFLOW<<1)-1)
+
+#define R_EN (0x14 / 4)
+#define R_DELAY (0x18 / 4)
+#define R_TX_DATA (0x1C / 4)
+#define R_RX_DATA (0x20 / 4)
+#define R_SLAVE_IDLE_COUNT (0x24 / 4)
+#define R_TX_THRES (0x28 / 4)
+#define R_RX_THRES (0x2C / 4)
+#define R_TXD1 (0x80 / 4)
+#define R_TXD2 (0x84 / 4)
+#define R_TXD3 (0x88 / 4)
+
+#define R_LQSPI_CFG (0xa0 / 4)
+#define R_LQSPI_CFG_RESET 0x03A002EB
+#define LQSPI_CFG_LQ_MODE (1U << 31)
+#define LQSPI_CFG_TWO_MEM (1 << 30)
+#define LQSPI_CFG_SEP_BUS (1 << 30)
+#define LQSPI_CFG_U_PAGE (1 << 28)
+#define LQSPI_CFG_MODE_EN (1 << 25)
+#define LQSPI_CFG_MODE_WIDTH 8
+#define LQSPI_CFG_MODE_SHIFT 16
+#define LQSPI_CFG_DUMMY_WIDTH 3
+#define LQSPI_CFG_DUMMY_SHIFT 8
+#define LQSPI_CFG_INST_CODE 0xFF
+
+#define R_LQSPI_STS (0xA4 / 4)
+#define LQSPI_STS_WR_RECVD (1 << 1)
+
+#define R_MOD_ID (0xFC / 4)
+
+#define R_MAX (R_MOD_ID+1)
+
+/* size of TXRX FIFOs */
+#define RXFF_A 32
+#define TXFF_A 32
+
+#define RXFF_A_Q (64 * 4)
+#define TXFF_A_Q (64 * 4)
+
+/* 16MB per linear region */
+#define LQSPI_ADDRESS_BITS 24
+/* Bite off 4k chunks at a time */
+#define LQSPI_CACHE_SIZE 1024
+
+#define SNOOP_CHECKING 0xFF
+#define SNOOP_NONE 0xFE
+#define SNOOP_STRIPING 0
+
+typedef enum {
+ READ = 0x3,
+ FAST_READ = 0xb,
+ DOR = 0x3b,
+ QOR = 0x6b,
+ DIOR = 0xbb,
+ QIOR = 0xeb,
+
+ PP = 0x2,
+ DPP = 0xa2,
+ QPP = 0x32,
+} FlashCMD;
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ MemoryRegion mmlqspi;
+
+ qemu_irq irq;
+ int irqline;
+
+ uint8_t num_cs;
+ uint8_t num_busses;
+
+ uint8_t snoop_state;
+ qemu_irq *cs_lines;
+ SSIBus **spi;
+
+ Fifo8 rx_fifo;
+ Fifo8 tx_fifo;
+
+ uint8_t num_txrx_bytes;
+
+ uint32_t regs[R_MAX];
+} XilinxSPIPS;
+
+typedef struct {
+ XilinxSPIPS parent_obj;
+
+ uint8_t lqspi_buf[LQSPI_CACHE_SIZE];
+ hwaddr lqspi_cached_addr;
+} XilinxQSPIPS;
+
+typedef struct XilinxSPIPSClass {
+ SysBusDeviceClass parent_class;
+
+ const MemoryRegionOps *reg_ops;
+
+ uint32_t rx_fifo_size;
+ uint32_t tx_fifo_size;
+} XilinxSPIPSClass;
+
+#define TYPE_XILINX_SPIPS "xlnx.ps7-spi"
+#define TYPE_XILINX_QSPIPS "xlnx.ps7-qspi"
+
+#define XILINX_SPIPS(obj) \
+ OBJECT_CHECK(XilinxSPIPS, (obj), TYPE_XILINX_SPIPS)
+#define XILINX_SPIPS_CLASS(klass) \
+ OBJECT_CLASS_CHECK(XilinxSPIPSClass, (klass), TYPE_XILINX_SPIPS)
+#define XILINX_SPIPS_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(XilinxSPIPSClass, (obj), TYPE_XILINX_SPIPS)
+
+#define XILINX_QSPIPS(obj) \
+ OBJECT_CHECK(XilinxQSPIPS, (obj), TYPE_XILINX_QSPIPS)
+
+static inline int num_effective_busses(XilinxSPIPS *s)
+{
+ return (s->regs[R_LQSPI_CFG] & LQSPI_CFG_SEP_BUS &&
+ s->regs[R_LQSPI_CFG] & LQSPI_CFG_TWO_MEM) ? s->num_busses : 1;
+}
+
+static inline bool xilinx_spips_cs_is_set(XilinxSPIPS *s, int i, int field)
+{
+ return ~field & (1 << i) && (s->regs[R_CONFIG] & MANUAL_CS
+ || !fifo8_is_empty(&s->tx_fifo));
+}
+
+static void xilinx_spips_update_cs_lines(XilinxSPIPS *s)
+{
+ int i, j;
+ bool found = false;
+ int field = s->regs[R_CONFIG] >> CS_SHIFT;
+
+ for (i = 0; i < s->num_cs; i++) {
+ for (j = 0; j < num_effective_busses(s); j++) {
+ int upage = !!(s->regs[R_LQSPI_STS] & LQSPI_CFG_U_PAGE);
+ int cs_to_set = (j * s->num_cs + i + upage) %
+ (s->num_cs * s->num_busses);
+
+ if (xilinx_spips_cs_is_set(s, i, field) && !found) {
+ DB_PRINT_L(0, "selecting slave %d\n", i);
+ qemu_set_irq(s->cs_lines[cs_to_set], 0);
+ } else {
+ DB_PRINT_L(0, "deselecting slave %d\n", i);
+ qemu_set_irq(s->cs_lines[cs_to_set], 1);
+ }
+ }
+ if (xilinx_spips_cs_is_set(s, i, field)) {
+ found = true;
+ }
+ }
+ if (!found) {
+ s->snoop_state = SNOOP_CHECKING;
+ DB_PRINT_L(1, "moving to snoop check state\n");
+ }
+}
+
+static void xilinx_spips_update_ixr(XilinxSPIPS *s)
+{
+ if (s->regs[R_LQSPI_CFG] & LQSPI_CFG_LQ_MODE) {
+ return;
+ }
+ /* These are set/cleared as they occur */
+ s->regs[R_INTR_STATUS] &= (IXR_TX_FIFO_UNDERFLOW | IXR_RX_FIFO_OVERFLOW |
+ IXR_TX_FIFO_MODE_FAIL);
+ /* these are pure functions of fifo state, set them here */
+ s->regs[R_INTR_STATUS] |=
+ (fifo8_is_full(&s->rx_fifo) ? IXR_RX_FIFO_FULL : 0) |
+ (s->rx_fifo.num >= s->regs[R_RX_THRES] ? IXR_RX_FIFO_NOT_EMPTY : 0) |
+ (fifo8_is_full(&s->tx_fifo) ? IXR_TX_FIFO_FULL : 0) |
+ (s->tx_fifo.num < s->regs[R_TX_THRES] ? IXR_TX_FIFO_NOT_FULL : 0);
+ /* drive external interrupt pin */
+ int new_irqline = !!(s->regs[R_INTR_MASK] & s->regs[R_INTR_STATUS] &
+ IXR_ALL);
+ if (new_irqline != s->irqline) {
+ s->irqline = new_irqline;
+ qemu_set_irq(s->irq, s->irqline);
+ }
+}
+
+static void xilinx_spips_reset(DeviceState *d)
+{
+ XilinxSPIPS *s = XILINX_SPIPS(d);
+
+ int i;
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ fifo8_reset(&s->rx_fifo);
+ fifo8_reset(&s->rx_fifo);
+ /* non zero resets */
+ s->regs[R_CONFIG] |= MODEFAIL_GEN_EN;
+ s->regs[R_SLAVE_IDLE_COUNT] = 0xFF;
+ s->regs[R_TX_THRES] = 1;
+ s->regs[R_RX_THRES] = 1;
+ /* FIXME: move magic number definition somewhere sensible */
+ s->regs[R_MOD_ID] = 0x01090106;
+ s->regs[R_LQSPI_CFG] = R_LQSPI_CFG_RESET;
+ s->snoop_state = SNOOP_CHECKING;
+ xilinx_spips_update_ixr(s);
+ xilinx_spips_update_cs_lines(s);
+}
+
+/* N way (num) in place bit striper. Lay out row wise bits (LSB to MSB)
+ * column wise (from element 0 to N-1). num is the length of x, and dir
+ * reverses the direction of the transform. Best illustrated by example:
+ * Each digit in the below array is a single bit (num == 3):
+ *
+ * {{ 76543210, } ----- stripe (dir == false) -----> {{ FCheb630, }
+ * { hgfedcba, } { GDAfc741, }
+ * { HGFEDCBA, }} <---- upstripe (dir == true) ----- { HEBgda52, }}
+ */
+
+static inline void stripe8(uint8_t *x, int num, bool dir)
+{
+ uint8_t r[num];
+ memset(r, 0, sizeof(uint8_t) * num);
+ int idx[2] = {0, 0};
+ int bit[2] = {0, 0};
+ int d = dir;
+
+ for (idx[0] = 0; idx[0] < num; ++idx[0]) {
+ for (bit[0] = 0; bit[0] < 8; ++bit[0]) {
+ r[idx[d]] |= x[idx[!d]] & 1 << bit[!d] ? 1 << bit[d] : 0;
+ idx[1] = (idx[1] + 1) % num;
+ if (!idx[1]) {
+ bit[1]++;
+ }
+ }
+ }
+ memcpy(x, r, sizeof(uint8_t) * num);
+}
+
+static void xilinx_spips_flush_txfifo(XilinxSPIPS *s)
+{
+ int debug_level = 0;
+
+ for (;;) {
+ int i;
+ uint8_t tx = 0;
+ uint8_t tx_rx[num_effective_busses(s)];
+
+ if (fifo8_is_empty(&s->tx_fifo)) {
+ if (!(s->regs[R_LQSPI_CFG] & LQSPI_CFG_LQ_MODE)) {
+ s->regs[R_INTR_STATUS] |= IXR_TX_FIFO_UNDERFLOW;
+ }
+ xilinx_spips_update_ixr(s);
+ return;
+ } else if (s->snoop_state == SNOOP_STRIPING) {
+ for (i = 0; i < num_effective_busses(s); ++i) {
+ tx_rx[i] = fifo8_pop(&s->tx_fifo);
+ }
+ stripe8(tx_rx, num_effective_busses(s), false);
+ } else {
+ tx = fifo8_pop(&s->tx_fifo);
+ for (i = 0; i < num_effective_busses(s); ++i) {
+ tx_rx[i] = tx;
+ }
+ }
+
+ for (i = 0; i < num_effective_busses(s); ++i) {
+ DB_PRINT_L(debug_level, "tx = %02x\n", tx_rx[i]);
+ tx_rx[i] = ssi_transfer(s->spi[i], (uint32_t)tx_rx[i]);
+ DB_PRINT_L(debug_level, "rx = %02x\n", tx_rx[i]);
+ }
+
+ if (fifo8_is_full(&s->rx_fifo)) {
+ s->regs[R_INTR_STATUS] |= IXR_RX_FIFO_OVERFLOW;
+ DB_PRINT_L(0, "rx FIFO overflow");
+ } else if (s->snoop_state == SNOOP_STRIPING) {
+ stripe8(tx_rx, num_effective_busses(s), true);
+ for (i = 0; i < num_effective_busses(s); ++i) {
+ fifo8_push(&s->rx_fifo, (uint8_t)tx_rx[i]);
+ }
+ } else {
+ fifo8_push(&s->rx_fifo, (uint8_t)tx_rx[0]);
+ }
+
+ DB_PRINT_L(debug_level, "initial snoop state: %x\n",
+ (unsigned)s->snoop_state);
+ switch (s->snoop_state) {
+ case (SNOOP_CHECKING):
+ switch (tx) { /* new instruction code */
+ case READ: /* 3 address bytes, no dummy bytes/cycles */
+ case PP:
+ case DPP:
+ case QPP:
+ s->snoop_state = 3;
+ break;
+ case FAST_READ: /* 3 address bytes, 1 dummy byte */
+ case DOR:
+ case QOR:
+ case DIOR: /* FIXME: these vary between vendor - set to spansion */
+ s->snoop_state = 4;
+ break;
+ case QIOR: /* 3 address bytes, 2 dummy bytes */
+ s->snoop_state = 6;
+ break;
+ default:
+ s->snoop_state = SNOOP_NONE;
+ }
+ break;
+ case (SNOOP_STRIPING):
+ case (SNOOP_NONE):
+ /* Once we hit the boring stuff - squelch debug noise */
+ if (!debug_level) {
+ DB_PRINT_L(0, "squelching debug info ....\n");
+ debug_level = 1;
+ }
+ break;
+ default:
+ s->snoop_state--;
+ }
+ DB_PRINT_L(debug_level, "final snoop state: %x\n",
+ (unsigned)s->snoop_state);
+ }
+}
+
+static inline void rx_data_bytes(XilinxSPIPS *s, uint8_t *value, int max)
+{
+ int i;
+
+ for (i = 0; i < max && !fifo8_is_empty(&s->rx_fifo); ++i) {
+ value[i] = fifo8_pop(&s->rx_fifo);
+ }
+}
+
+static uint64_t xilinx_spips_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ XilinxSPIPS *s = opaque;
+ uint32_t mask = ~0;
+ uint32_t ret;
+ uint8_t rx_buf[4];
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CONFIG:
+ mask = ~(R_CONFIG_RSVD | MAN_START_COM);
+ break;
+ case R_INTR_STATUS:
+ ret = s->regs[addr] & IXR_ALL;
+ s->regs[addr] = 0;
+ DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret);
+ return ret;
+ case R_INTR_MASK:
+ mask = IXR_ALL;
+ break;
+ case R_EN:
+ mask = 0x1;
+ break;
+ case R_SLAVE_IDLE_COUNT:
+ mask = 0xFF;
+ break;
+ case R_MOD_ID:
+ mask = 0x01FFFFFF;
+ break;
+ case R_INTR_EN:
+ case R_INTR_DIS:
+ case R_TX_DATA:
+ mask = 0;
+ break;
+ case R_RX_DATA:
+ memset(rx_buf, 0, sizeof(rx_buf));
+ rx_data_bytes(s, rx_buf, s->num_txrx_bytes);
+ ret = s->regs[R_CONFIG] & ENDIAN ? cpu_to_be32(*(uint32_t *)rx_buf)
+ : cpu_to_le32(*(uint32_t *)rx_buf);
+ DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret);
+ xilinx_spips_update_ixr(s);
+ return ret;
+ }
+ DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4,
+ s->regs[addr] & mask);
+ return s->regs[addr] & mask;
+
+}
+
+static inline void tx_data_bytes(XilinxSPIPS *s, uint32_t value, int num)
+{
+ int i;
+ for (i = 0; i < num && !fifo8_is_full(&s->tx_fifo); ++i) {
+ if (s->regs[R_CONFIG] & ENDIAN) {
+ fifo8_push(&s->tx_fifo, (uint8_t)(value >> 24));
+ value <<= 8;
+ } else {
+ fifo8_push(&s->tx_fifo, (uint8_t)value);
+ value >>= 8;
+ }
+ }
+}
+
+static void xilinx_spips_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ int mask = ~0;
+ int man_start_com = 0;
+ XilinxSPIPS *s = opaque;
+
+ DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr, (unsigned)value);
+ addr >>= 2;
+ switch (addr) {
+ case R_CONFIG:
+ mask = ~(R_CONFIG_RSVD | MAN_START_COM);
+ if (value & MAN_START_COM) {
+ man_start_com = 1;
+ }
+ break;
+ case R_INTR_STATUS:
+ mask = IXR_ALL;
+ s->regs[R_INTR_STATUS] &= ~(mask & value);
+ goto no_reg_update;
+ case R_INTR_DIS:
+ mask = IXR_ALL;
+ s->regs[R_INTR_MASK] &= ~(mask & value);
+ goto no_reg_update;
+ case R_INTR_EN:
+ mask = IXR_ALL;
+ s->regs[R_INTR_MASK] |= mask & value;
+ goto no_reg_update;
+ case R_EN:
+ mask = 0x1;
+ break;
+ case R_SLAVE_IDLE_COUNT:
+ mask = 0xFF;
+ break;
+ case R_RX_DATA:
+ case R_INTR_MASK:
+ case R_MOD_ID:
+ mask = 0;
+ break;
+ case R_TX_DATA:
+ tx_data_bytes(s, (uint32_t)value, s->num_txrx_bytes);
+ goto no_reg_update;
+ case R_TXD1:
+ tx_data_bytes(s, (uint32_t)value, 1);
+ goto no_reg_update;
+ case R_TXD2:
+ tx_data_bytes(s, (uint32_t)value, 2);
+ goto no_reg_update;
+ case R_TXD3:
+ tx_data_bytes(s, (uint32_t)value, 3);
+ goto no_reg_update;
+ }
+ s->regs[addr] = (s->regs[addr] & ~mask) | (value & mask);
+no_reg_update:
+ xilinx_spips_update_cs_lines(s);
+ if ((man_start_com && s->regs[R_CONFIG] & MAN_START_EN) ||
+ (fifo8_is_empty(&s->tx_fifo) && s->regs[R_CONFIG] & MAN_START_EN)) {
+ xilinx_spips_flush_txfifo(s);
+ }
+ xilinx_spips_update_cs_lines(s);
+ xilinx_spips_update_ixr(s);
+}
+
+static const MemoryRegionOps spips_ops = {
+ .read = xilinx_spips_read,
+ .write = xilinx_spips_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void xilinx_qspips_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ XilinxQSPIPS *q = XILINX_QSPIPS(opaque);
+
+ xilinx_spips_write(opaque, addr, value, size);
+ addr >>= 2;
+
+ if (addr == R_LQSPI_CFG) {
+ q->lqspi_cached_addr = ~0ULL;
+ }
+}
+
+static const MemoryRegionOps qspips_ops = {
+ .read = xilinx_spips_read,
+ .write = xilinx_qspips_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+#define LQSPI_CACHE_SIZE 1024
+
+static uint64_t
+lqspi_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ int i;
+ XilinxQSPIPS *q = opaque;
+ XilinxSPIPS *s = opaque;
+ uint32_t ret;
+
+ if (addr >= q->lqspi_cached_addr &&
+ addr <= q->lqspi_cached_addr + LQSPI_CACHE_SIZE - 4) {
+ uint8_t *retp = &q->lqspi_buf[addr - q->lqspi_cached_addr];
+ ret = cpu_to_le32(*(uint32_t *)retp);
+ DB_PRINT_L(1, "addr: %08x, data: %08x\n", (unsigned)addr,
+ (unsigned)ret);
+ return ret;
+ } else {
+ int flash_addr = (addr / num_effective_busses(s));
+ int slave = flash_addr >> LQSPI_ADDRESS_BITS;
+ int cache_entry = 0;
+ uint32_t u_page_save = s->regs[R_LQSPI_STS] & ~LQSPI_CFG_U_PAGE;
+
+ s->regs[R_LQSPI_STS] &= ~LQSPI_CFG_U_PAGE;
+ s->regs[R_LQSPI_STS] |= slave ? LQSPI_CFG_U_PAGE : 0;
+
+ DB_PRINT_L(0, "config reg status: %08x\n", s->regs[R_LQSPI_CFG]);
+
+ fifo8_reset(&s->tx_fifo);
+ fifo8_reset(&s->rx_fifo);
+
+ /* instruction */
+ DB_PRINT_L(0, "pushing read instruction: %02x\n",
+ (unsigned)(uint8_t)(s->regs[R_LQSPI_CFG] &
+ LQSPI_CFG_INST_CODE));
+ fifo8_push(&s->tx_fifo, s->regs[R_LQSPI_CFG] & LQSPI_CFG_INST_CODE);
+ /* read address */
+ DB_PRINT_L(0, "pushing read address %06x\n", flash_addr);
+ fifo8_push(&s->tx_fifo, (uint8_t)(flash_addr >> 16));
+ fifo8_push(&s->tx_fifo, (uint8_t)(flash_addr >> 8));
+ fifo8_push(&s->tx_fifo, (uint8_t)flash_addr);
+ /* mode bits */
+ if (s->regs[R_LQSPI_CFG] & LQSPI_CFG_MODE_EN) {
+ fifo8_push(&s->tx_fifo, extract32(s->regs[R_LQSPI_CFG],
+ LQSPI_CFG_MODE_SHIFT,
+ LQSPI_CFG_MODE_WIDTH));
+ }
+ /* dummy bytes */
+ for (i = 0; i < (extract32(s->regs[R_LQSPI_CFG], LQSPI_CFG_DUMMY_SHIFT,
+ LQSPI_CFG_DUMMY_WIDTH)); ++i) {
+ DB_PRINT_L(0, "pushing dummy byte\n");
+ fifo8_push(&s->tx_fifo, 0);
+ }
+ xilinx_spips_update_cs_lines(s);
+ xilinx_spips_flush_txfifo(s);
+ fifo8_reset(&s->rx_fifo);
+
+ DB_PRINT_L(0, "starting QSPI data read\n");
+
+ while (cache_entry < LQSPI_CACHE_SIZE) {
+ for (i = 0; i < 64; ++i) {
+ tx_data_bytes(s, 0, 1);
+ }
+ xilinx_spips_flush_txfifo(s);
+ for (i = 0; i < 64; ++i) {
+ rx_data_bytes(s, &q->lqspi_buf[cache_entry++], 1);
+ }
+ }
+
+ s->regs[R_LQSPI_STS] &= ~LQSPI_CFG_U_PAGE;
+ s->regs[R_LQSPI_STS] |= u_page_save;
+ xilinx_spips_update_cs_lines(s);
+
+ q->lqspi_cached_addr = flash_addr * num_effective_busses(s);
+ return lqspi_read(opaque, addr, size);
+ }
+}
+
+static const MemoryRegionOps lqspi_ops = {
+ .read = lqspi_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4
+ }
+};
+
+static void xilinx_spips_realize(DeviceState *dev, Error **errp)
+{
+ XilinxSPIPS *s = XILINX_SPIPS(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ XilinxSPIPSClass *xsc = XILINX_SPIPS_GET_CLASS(s);
+ int i;
+
+ DB_PRINT_L(0, "realized spips\n");
+
+ s->spi = g_new(SSIBus *, s->num_busses);
+ for (i = 0; i < s->num_busses; ++i) {
+ char bus_name[16];
+ snprintf(bus_name, 16, "spi%d", i);
+ s->spi[i] = ssi_create_bus(dev, bus_name);
+ }
+
+ s->cs_lines = g_new0(qemu_irq, s->num_cs * s->num_busses);
+ ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi[0]);
+ ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi[1]);
+ sysbus_init_irq(sbd, &s->irq);
+ for (i = 0; i < s->num_cs * s->num_busses; ++i) {
+ sysbus_init_irq(sbd, &s->cs_lines[i]);
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), xsc->reg_ops, s,
+ "spi", R_MAX*4);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->irqline = -1;
+
+ fifo8_create(&s->rx_fifo, xsc->rx_fifo_size);
+ fifo8_create(&s->tx_fifo, xsc->tx_fifo_size);
+}
+
+static void xilinx_qspips_realize(DeviceState *dev, Error **errp)
+{
+ XilinxSPIPS *s = XILINX_SPIPS(dev);
+ XilinxQSPIPS *q = XILINX_QSPIPS(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ DB_PRINT_L(0, "realized qspips\n");
+
+ s->num_busses = 2;
+ s->num_cs = 2;
+ s->num_txrx_bytes = 4;
+
+ xilinx_spips_realize(dev, errp);
+ memory_region_init_io(&s->mmlqspi, OBJECT(s), &lqspi_ops, s, "lqspi",
+ (1 << LQSPI_ADDRESS_BITS) * 2);
+ sysbus_init_mmio(sbd, &s->mmlqspi);
+
+ q->lqspi_cached_addr = ~0ULL;
+}
+
+static int xilinx_spips_post_load(void *opaque, int version_id)
+{
+ xilinx_spips_update_ixr((XilinxSPIPS *)opaque);
+ xilinx_spips_update_cs_lines((XilinxSPIPS *)opaque);
+ return 0;
+}
+
+static const VMStateDescription vmstate_xilinx_spips = {
+ .name = "xilinx_spips",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = xilinx_spips_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_FIFO8(tx_fifo, XilinxSPIPS),
+ VMSTATE_FIFO8(rx_fifo, XilinxSPIPS),
+ VMSTATE_UINT32_ARRAY(regs, XilinxSPIPS, R_MAX),
+ VMSTATE_UINT8(snoop_state, XilinxSPIPS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property xilinx_spips_properties[] = {
+ DEFINE_PROP_UINT8("num-busses", XilinxSPIPS, num_busses, 1),
+ DEFINE_PROP_UINT8("num-ss-bits", XilinxSPIPS, num_cs, 4),
+ DEFINE_PROP_UINT8("num-txrx-bytes", XilinxSPIPS, num_txrx_bytes, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_qspips_class_init(ObjectClass *klass, void * data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ XilinxSPIPSClass *xsc = XILINX_SPIPS_CLASS(klass);
+
+ dc->realize = xilinx_qspips_realize;
+ xsc->reg_ops = &qspips_ops;
+ xsc->rx_fifo_size = RXFF_A_Q;
+ xsc->tx_fifo_size = TXFF_A_Q;
+}
+
+static void xilinx_spips_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ XilinxSPIPSClass *xsc = XILINX_SPIPS_CLASS(klass);
+
+ dc->realize = xilinx_spips_realize;
+ dc->reset = xilinx_spips_reset;
+ dc->props = xilinx_spips_properties;
+ dc->vmsd = &vmstate_xilinx_spips;
+
+ xsc->reg_ops = &spips_ops;
+ xsc->rx_fifo_size = RXFF_A;
+ xsc->tx_fifo_size = TXFF_A;
+}
+
+static const TypeInfo xilinx_spips_info = {
+ .name = TYPE_XILINX_SPIPS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XilinxSPIPS),
+ .class_init = xilinx_spips_class_init,
+ .class_size = sizeof(XilinxSPIPSClass),
+};
+
+static const TypeInfo xilinx_qspips_info = {
+ .name = TYPE_XILINX_QSPIPS,
+ .parent = TYPE_XILINX_SPIPS,
+ .instance_size = sizeof(XilinxQSPIPS),
+ .class_init = xilinx_qspips_class_init,
+};
+
+static void xilinx_spips_register_types(void)
+{
+ type_register_static(&xilinx_spips_info);
+ type_register_static(&xilinx_qspips_info);
+}
+
+type_init(xilinx_spips_register_types)
diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs
new file mode 100644
index 00000000..133bd0d4
--- /dev/null
+++ b/hw/timer/Makefile.objs
@@ -0,0 +1,35 @@
+common-obj-$(CONFIG_ARM_TIMER) += arm_timer.o
+common-obj-$(CONFIG_ARM_MPTIMER) += arm_mptimer.o
+common-obj-$(CONFIG_A9_GTIMER) += a9gtimer.o
+common-obj-$(CONFIG_CADENCE) += cadence_ttc.o
+common-obj-$(CONFIG_DS1338) += ds1338.o
+common-obj-$(CONFIG_HPET) += hpet.o
+common-obj-$(CONFIG_I8254) += i8254_common.o i8254.o
+common-obj-$(CONFIG_M48T59) += m48t59.o
+common-obj-$(CONFIG_PL031) += pl031.o
+common-obj-$(CONFIG_PUV3) += puv3_ost.o
+common-obj-$(CONFIG_TWL92230) += twl92230.o
+common-obj-$(CONFIG_XILINX) += xilinx_timer.o
+common-obj-$(CONFIG_SLAVIO) += slavio_timer.o
+common-obj-$(CONFIG_ETRAXFS) += etraxfs_timer.o
+common-obj-$(CONFIG_GRLIB) += grlib_gptimer.o
+common-obj-$(CONFIG_IMX) += imx_epit.o
+common-obj-$(CONFIG_IMX) += imx_gpt.o
+common-obj-$(CONFIG_LM32) += lm32_timer.o
+common-obj-$(CONFIG_MILKYMIST) += milkymist-sysctl.o
+
+obj-$(CONFIG_EXYNOS4) += exynos4210_mct.o
+obj-$(CONFIG_EXYNOS4) += exynos4210_pwm.o
+obj-$(CONFIG_EXYNOS4) += exynos4210_rtc.o
+obj-$(CONFIG_OMAP) += omap_gptimer.o
+obj-$(CONFIG_OMAP) += omap_synctimer.o
+obj-$(CONFIG_PXA2XX) += pxa2xx_timer.o
+obj-$(CONFIG_SH4) += sh_timer.o
+obj-$(CONFIG_TUSB6010) += tusb6010.o
+obj-$(CONFIG_DIGIC) += digic-timer.o
+
+obj-$(CONFIG_MC146818RTC) += mc146818rtc.o
+
+obj-$(CONFIG_ALLWINNER_A10_PIT) += allwinner-a10-pit.o
+
+common-obj-$(CONFIG_STM32F2XX_TIMER) += stm32f2xx_timer.o
diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c
new file mode 100644
index 00000000..dd4aae8b
--- /dev/null
+++ b/hw/timer/a9gtimer.c
@@ -0,0 +1,369 @@
+/*
+ * Global peripheral timer block for ARM A9MP
+ *
+ * (C) 2013 Xilinx Inc.
+ *
+ * Written by François LEGAL
+ * Written by Peter Crosthwaite <peter.crosthwaite@xilinx.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/timer/a9gtimer.h"
+#include "qemu/timer.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+
+#ifndef A9_GTIMER_ERR_DEBUG
+#define A9_GTIMER_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(level, ...) do { \
+ if (A9_GTIMER_ERR_DEBUG > (level)) { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } \
+} while (0);
+
+#define DB_PRINT(...) DB_PRINT_L(0, ## __VA_ARGS__)
+
+static inline int a9_gtimer_get_current_cpu(A9GTimerState *s)
+{
+ if (current_cpu->cpu_index >= s->num_cpu) {
+ hw_error("a9gtimer: num-cpu %d but this cpu is %d!\n",
+ s->num_cpu, current_cpu->cpu_index);
+ }
+ return current_cpu->cpu_index;
+}
+
+static inline uint64_t a9_gtimer_get_conv(A9GTimerState *s)
+{
+ uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT,
+ R_CONTROL_PRESCALER_LEN);
+
+ return (prescale + 1) * 10;
+}
+
+static A9GTimerUpdate a9_gtimer_get_update(A9GTimerState *s)
+{
+ A9GTimerUpdate ret;
+
+ ret.now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ret.new = s->ref_counter +
+ (ret.now - s->cpu_ref_time) / a9_gtimer_get_conv(s);
+ return ret;
+}
+
+static void a9_gtimer_update(A9GTimerState *s, bool sync)
+{
+
+ A9GTimerUpdate update = a9_gtimer_get_update(s);
+ int i;
+ int64_t next_cdiff = 0;
+
+ for (i = 0; i < s->num_cpu; ++i) {
+ A9GTimerPerCPU *gtb = &s->per_cpu[i];
+ int64_t cdiff = 0;
+
+ if ((s->control & R_CONTROL_TIMER_ENABLE) &&
+ (gtb->control & R_CONTROL_COMP_ENABLE)) {
+ /* R2p0+, where the compare function is >= */
+ while (gtb->compare < update.new) {
+ DB_PRINT("Compare event happened for CPU %d\n", i);
+ gtb->status = 1;
+ if (gtb->control & R_CONTROL_AUTO_INCREMENT) {
+ DB_PRINT("Auto incrementing timer compare by %" PRId32 "\n",
+ gtb->inc);
+ gtb->compare += gtb->inc;
+ } else {
+ break;
+ }
+ }
+ cdiff = (int64_t)gtb->compare - (int64_t)update.new + 1;
+ if (cdiff > 0 && (cdiff < next_cdiff || !next_cdiff)) {
+ next_cdiff = cdiff;
+ }
+ }
+
+ qemu_set_irq(gtb->irq,
+ gtb->status && (gtb->control & R_CONTROL_IRQ_ENABLE));
+ }
+
+ timer_del(s->timer);
+ if (next_cdiff) {
+ DB_PRINT("scheduling qemu_timer to fire again in %"
+ PRIx64 " cycles\n", next_cdiff);
+ timer_mod(s->timer, update.now + next_cdiff * a9_gtimer_get_conv(s));
+ }
+
+ if (s->control & R_CONTROL_TIMER_ENABLE) {
+ s->counter = update.new;
+ }
+
+ if (sync) {
+ s->cpu_ref_time = update.now;
+ s->ref_counter = s->counter;
+ }
+}
+
+static void a9_gtimer_update_no_sync(void *opaque)
+{
+ A9GTimerState *s = A9_GTIMER(opaque);
+
+ a9_gtimer_update(s, false);
+}
+
+static uint64_t a9_gtimer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ A9GTimerPerCPU *gtb = (A9GTimerPerCPU *)opaque;
+ A9GTimerState *s = gtb->parent;
+ A9GTimerUpdate update;
+ uint64_t ret = 0;
+ int shift = 0;
+
+ switch (addr) {
+ case R_COUNTER_HI:
+ shift = 32;
+ /* fallthrough */
+ case R_COUNTER_LO:
+ update = a9_gtimer_get_update(s);
+ ret = extract64(update.new, shift, 32);
+ break;
+ case R_CONTROL:
+ ret = s->control | gtb->control;
+ break;
+ case R_INTERRUPT_STATUS:
+ ret = gtb->status;
+ break;
+ case R_COMPARATOR_HI:
+ shift = 32;
+ /* fallthrough */
+ case R_COMPARATOR_LO:
+ ret = extract64(gtb->compare, shift, 32);
+ break;
+ case R_AUTO_INCREMENT:
+ ret = gtb->inc;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "bad a9gtimer register: %x\n",
+ (unsigned)addr);
+ return 0;
+ }
+
+ DB_PRINT("addr:%#x data:%#08" PRIx64 "\n", (unsigned)addr, ret);
+ return ret;
+}
+
+static void a9_gtimer_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ A9GTimerPerCPU *gtb = (A9GTimerPerCPU *)opaque;
+ A9GTimerState *s = gtb->parent;
+ int shift = 0;
+
+ DB_PRINT("addr:%#x data:%#08" PRIx64 "\n", (unsigned)addr, value);
+
+ switch (addr) {
+ case R_COUNTER_HI:
+ shift = 32;
+ /* fallthrough */
+ case R_COUNTER_LO:
+ /*
+ * Keep it simple - ARM docco explicitly says to disable timer before
+ * modding it, so dont bother trying to do all the difficult on the fly
+ * timer modifications - (if they even work in real hardware??).
+ */
+ if (s->control & R_CONTROL_TIMER_ENABLE) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Cannot mod running ARM gtimer\n");
+ return;
+ }
+ s->counter = deposit64(s->counter, shift, 32, value);
+ return;
+ case R_CONTROL:
+ a9_gtimer_update(s, (value ^ s->control) & R_CONTROL_NEEDS_SYNC);
+ gtb->control = value & R_CONTROL_BANKED;
+ s->control = value & ~R_CONTROL_BANKED;
+ break;
+ case R_INTERRUPT_STATUS:
+ a9_gtimer_update(s, false);
+ gtb->status &= ~value;
+ break;
+ case R_COMPARATOR_HI:
+ shift = 32;
+ /* fallthrough */
+ case R_COMPARATOR_LO:
+ a9_gtimer_update(s, false);
+ gtb->compare = deposit64(gtb->compare, shift, 32, value);
+ break;
+ case R_AUTO_INCREMENT:
+ gtb->inc = value;
+ return;
+ default:
+ return;
+ }
+
+ a9_gtimer_update(s, false);
+}
+
+/* Wrapper functions to implement the "read global timer for
+ * the current CPU" memory regions.
+ */
+static uint64_t a9_gtimer_this_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ A9GTimerState *s = A9_GTIMER(opaque);
+ int id = a9_gtimer_get_current_cpu(s);
+
+ /* no \n so concatenates with message from read fn */
+ DB_PRINT("CPU:%d:", id);
+
+ return a9_gtimer_read(&s->per_cpu[id], addr, size);
+}
+
+static void a9_gtimer_this_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ A9GTimerState *s = A9_GTIMER(opaque);
+ int id = a9_gtimer_get_current_cpu(s);
+
+ /* no \n so concatenates with message from write fn */
+ DB_PRINT("CPU:%d:", id);
+
+ a9_gtimer_write(&s->per_cpu[id], addr, value, size);
+}
+
+static const MemoryRegionOps a9_gtimer_this_ops = {
+ .read = a9_gtimer_this_read,
+ .write = a9_gtimer_this_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps a9_gtimer_ops = {
+ .read = a9_gtimer_read,
+ .write = a9_gtimer_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void a9_gtimer_reset(DeviceState *dev)
+{
+ A9GTimerState *s = A9_GTIMER(dev);
+ int i;
+
+ s->counter = 0;
+ s->control = 0;
+
+ for (i = 0; i < s->num_cpu; i++) {
+ A9GTimerPerCPU *gtb = &s->per_cpu[i];
+
+ gtb->control = 0;
+ gtb->status = 0;
+ gtb->compare = 0;
+ gtb->inc = 0;
+ }
+ a9_gtimer_update(s, false);
+}
+
+static void a9_gtimer_realize(DeviceState *dev, Error **errp)
+{
+ A9GTimerState *s = A9_GTIMER(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ int i;
+
+ if (s->num_cpu < 1 || s->num_cpu > A9_GTIMER_MAX_CPUS) {
+ error_setg(errp, "%s: num-cpu must be between 1 and %d",
+ __func__, A9_GTIMER_MAX_CPUS);
+ return;
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &a9_gtimer_this_ops, s,
+ "a9gtimer shared", 0x20);
+ sysbus_init_mmio(sbd, &s->iomem);
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, a9_gtimer_update_no_sync, s);
+
+ for (i = 0; i < s->num_cpu; i++) {
+ A9GTimerPerCPU *gtb = &s->per_cpu[i];
+
+ gtb->parent = s;
+ sysbus_init_irq(sbd, &gtb->irq);
+ memory_region_init_io(&gtb->iomem, OBJECT(dev), &a9_gtimer_ops, gtb,
+ "a9gtimer per cpu", 0x20);
+ sysbus_init_mmio(sbd, &gtb->iomem);
+ }
+}
+
+static const VMStateDescription vmstate_a9_gtimer_per_cpu = {
+ .name = "arm.cortex-a9-global-timer.percpu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(control, A9GTimerPerCPU),
+ VMSTATE_UINT64(compare, A9GTimerPerCPU),
+ VMSTATE_UINT32(status, A9GTimerPerCPU),
+ VMSTATE_UINT32(inc, A9GTimerPerCPU),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_a9_gtimer = {
+ .name = "arm.cortex-a9-global-timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(timer, A9GTimerState),
+ VMSTATE_UINT64(counter, A9GTimerState),
+ VMSTATE_UINT64(ref_counter, A9GTimerState),
+ VMSTATE_UINT64(cpu_ref_time, A9GTimerState),
+ VMSTATE_STRUCT_VARRAY_UINT32(per_cpu, A9GTimerState, num_cpu,
+ 1, vmstate_a9_gtimer_per_cpu,
+ A9GTimerPerCPU),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property a9_gtimer_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void a9_gtimer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = a9_gtimer_realize;
+ dc->vmsd = &vmstate_a9_gtimer;
+ dc->reset = a9_gtimer_reset;
+ dc->props = a9_gtimer_properties;
+}
+
+static const TypeInfo a9_gtimer_info = {
+ .name = TYPE_A9_GTIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(A9GTimerState),
+ .class_init = a9_gtimer_class_init,
+};
+
+static void a9_gtimer_register_types(void)
+{
+ type_register_static(&a9_gtimer_info);
+}
+
+type_init(a9_gtimer_register_types)
diff --git a/hw/timer/allwinner-a10-pit.c b/hw/timer/allwinner-a10-pit.c
new file mode 100644
index 00000000..34124fe3
--- /dev/null
+++ b/hw/timer/allwinner-a10-pit.c
@@ -0,0 +1,295 @@
+/*
+ * Allwinner A10 timer device emulation
+ *
+ * Copyright (C) 2013 Li Guang
+ * Written by Li Guang <lig.fnst@cn.fujitsu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "hw/timer/allwinner-a10-pit.h"
+
+static void a10_pit_update_irq(AwA10PITState *s)
+{
+ int i;
+
+ for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
+ qemu_set_irq(s->irq[i], !!(s->irq_status & s->irq_enable & (1 << i)));
+ }
+}
+
+static uint64_t a10_pit_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AwA10PITState *s = AW_A10_PIT(opaque);
+ uint8_t index;
+
+ switch (offset) {
+ case AW_A10_PIT_TIMER_IRQ_EN:
+ return s->irq_enable;
+ case AW_A10_PIT_TIMER_IRQ_ST:
+ return s->irq_status;
+ case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END:
+ index = offset & 0xf0;
+ index >>= 4;
+ index -= 1;
+ switch (offset & 0x0f) {
+ case AW_A10_PIT_TIMER_CONTROL:
+ return s->control[index];
+ case AW_A10_PIT_TIMER_INTERVAL:
+ return s->interval[index];
+ case AW_A10_PIT_TIMER_COUNT:
+ s->count[index] = ptimer_get_count(s->timer[index]);
+ return s->count[index];
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ break;
+ }
+ case AW_A10_PIT_WDOG_CONTROL:
+ break;
+ case AW_A10_PIT_WDOG_MODE:
+ break;
+ case AW_A10_PIT_COUNT_LO:
+ return s->count_lo;
+ case AW_A10_PIT_COUNT_HI:
+ return s->count_hi;
+ case AW_A10_PIT_COUNT_CTL:
+ return s->count_ctl;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ break;
+ }
+
+ return 0;
+}
+
+static void a10_pit_set_freq(AwA10PITState *s, int index)
+{
+ uint32_t prescaler, source, source_freq;
+
+ prescaler = 1 << extract32(s->control[index], 4, 3);
+ source = extract32(s->control[index], 2, 2);
+ source_freq = s->clk_freq[source];
+
+ if (source_freq) {
+ ptimer_set_freq(s->timer[index], source_freq / prescaler);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid clock source %u\n",
+ __func__, source);
+ }
+}
+
+static void a10_pit_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ AwA10PITState *s = AW_A10_PIT(opaque);
+ uint8_t index;
+
+ switch (offset) {
+ case AW_A10_PIT_TIMER_IRQ_EN:
+ s->irq_enable = value;
+ a10_pit_update_irq(s);
+ break;
+ case AW_A10_PIT_TIMER_IRQ_ST:
+ s->irq_status &= ~value;
+ a10_pit_update_irq(s);
+ break;
+ case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END:
+ index = offset & 0xf0;
+ index >>= 4;
+ index -= 1;
+ switch (offset & 0x0f) {
+ case AW_A10_PIT_TIMER_CONTROL:
+ s->control[index] = value;
+ a10_pit_set_freq(s, index);
+ if (s->control[index] & AW_A10_PIT_TIMER_RELOAD) {
+ ptimer_set_count(s->timer[index], s->interval[index]);
+ }
+ if (s->control[index] & AW_A10_PIT_TIMER_EN) {
+ int oneshot = 0;
+ if (s->control[index] & AW_A10_PIT_TIMER_MODE) {
+ oneshot = 1;
+ }
+ ptimer_run(s->timer[index], oneshot);
+ } else {
+ ptimer_stop(s->timer[index]);
+ }
+ break;
+ case AW_A10_PIT_TIMER_INTERVAL:
+ s->interval[index] = value;
+ ptimer_set_limit(s->timer[index], s->interval[index], 1);
+ break;
+ case AW_A10_PIT_TIMER_COUNT:
+ s->count[index] = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ }
+ break;
+ case AW_A10_PIT_WDOG_CONTROL:
+ s->watch_dog_control = value;
+ break;
+ case AW_A10_PIT_WDOG_MODE:
+ s->watch_dog_mode = value;
+ break;
+ case AW_A10_PIT_COUNT_LO:
+ s->count_lo = value;
+ break;
+ case AW_A10_PIT_COUNT_HI:
+ s->count_hi = value;
+ break;
+ case AW_A10_PIT_COUNT_CTL:
+ s->count_ctl = value;
+ if (s->count_ctl & AW_A10_PIT_COUNT_RL_EN) {
+ uint64_t tmp_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ s->count_lo = tmp_count;
+ s->count_hi = tmp_count >> 32;
+ s->count_ctl &= ~AW_A10_PIT_COUNT_RL_EN;
+ }
+ if (s->count_ctl & AW_A10_PIT_COUNT_CLR_EN) {
+ s->count_lo = 0;
+ s->count_hi = 0;
+ s->count_ctl &= ~AW_A10_PIT_COUNT_CLR_EN;
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps a10_pit_ops = {
+ .read = a10_pit_read,
+ .write = a10_pit_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static Property a10_pit_properties[] = {
+ DEFINE_PROP_UINT32("clk0-freq", AwA10PITState, clk_freq[0], 0),
+ DEFINE_PROP_UINT32("clk1-freq", AwA10PITState, clk_freq[1], 0),
+ DEFINE_PROP_UINT32("clk2-freq", AwA10PITState, clk_freq[2], 0),
+ DEFINE_PROP_UINT32("clk3-freq", AwA10PITState, clk_freq[3], 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_a10_pit = {
+ .name = "a10.pit",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(irq_enable, AwA10PITState),
+ VMSTATE_UINT32(irq_status, AwA10PITState),
+ VMSTATE_UINT32_ARRAY(control, AwA10PITState, AW_A10_PIT_TIMER_NR),
+ VMSTATE_UINT32_ARRAY(interval, AwA10PITState, AW_A10_PIT_TIMER_NR),
+ VMSTATE_UINT32_ARRAY(count, AwA10PITState, AW_A10_PIT_TIMER_NR),
+ VMSTATE_UINT32(watch_dog_mode, AwA10PITState),
+ VMSTATE_UINT32(watch_dog_control, AwA10PITState),
+ VMSTATE_UINT32(count_lo, AwA10PITState),
+ VMSTATE_UINT32(count_hi, AwA10PITState),
+ VMSTATE_UINT32(count_ctl, AwA10PITState),
+ VMSTATE_PTIMER_ARRAY(timer, AwA10PITState, AW_A10_PIT_TIMER_NR),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void a10_pit_reset(DeviceState *dev)
+{
+ AwA10PITState *s = AW_A10_PIT(dev);
+ uint8_t i;
+
+ s->irq_enable = 0;
+ s->irq_status = 0;
+ a10_pit_update_irq(s);
+
+ for (i = 0; i < 6; i++) {
+ s->control[i] = AW_A10_PIT_DEFAULT_CLOCK;
+ s->interval[i] = 0;
+ s->count[i] = 0;
+ ptimer_stop(s->timer[i]);
+ a10_pit_set_freq(s, i);
+ }
+ s->watch_dog_mode = 0;
+ s->watch_dog_control = 0;
+ s->count_lo = 0;
+ s->count_hi = 0;
+ s->count_ctl = 0;
+}
+
+static void a10_pit_timer_cb(void *opaque)
+{
+ AwA10TimerContext *tc = opaque;
+ AwA10PITState *s = tc->container;
+ uint8_t i = tc->index;
+
+ if (s->control[i] & AW_A10_PIT_TIMER_EN) {
+ s->irq_status |= 1 << i;
+ if (s->control[i] & AW_A10_PIT_TIMER_MODE) {
+ ptimer_stop(s->timer[i]);
+ s->control[i] &= ~AW_A10_PIT_TIMER_EN;
+ }
+ a10_pit_update_irq(s);
+ }
+}
+
+static void a10_pit_init(Object *obj)
+{
+ AwA10PITState *s = AW_A10_PIT(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ QEMUBH * bh[AW_A10_PIT_TIMER_NR];
+ uint8_t i;
+
+ for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
+ sysbus_init_irq(sbd, &s->irq[i]);
+ }
+ memory_region_init_io(&s->iomem, OBJECT(s), &a10_pit_ops, s,
+ TYPE_AW_A10_PIT, 0x400);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
+ AwA10TimerContext *tc = &s->timer_context[i];
+
+ tc->container = s;
+ tc->index = i;
+ bh[i] = qemu_bh_new(a10_pit_timer_cb, tc);
+ s->timer[i] = ptimer_init(bh[i]);
+ }
+}
+
+static void a10_pit_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = a10_pit_reset;
+ dc->props = a10_pit_properties;
+ dc->desc = "allwinner a10 timer";
+ dc->vmsd = &vmstate_a10_pit;
+}
+
+static const TypeInfo a10_pit_info = {
+ .name = TYPE_AW_A10_PIT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AwA10PITState),
+ .instance_init = a10_pit_init,
+ .class_init = a10_pit_class_init,
+};
+
+static void a10_register_types(void)
+{
+ type_register_static(&a10_pit_info);
+}
+
+type_init(a10_register_types);
diff --git a/hw/timer/arm_mptimer.c b/hw/timer/arm_mptimer.c
new file mode 100644
index 00000000..3e59c2a2
--- /dev/null
+++ b/hw/timer/arm_mptimer.c
@@ -0,0 +1,300 @@
+/*
+ * Private peripheral timer/watchdog blocks for ARM 11MPCore and A9MP
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Copyright (c) 2011 Linaro Limited
+ * Written by Paul Brook, Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/timer/arm_mptimer.h"
+#include "qemu/timer.h"
+#include "qom/cpu.h"
+
+/* This device implements the per-cpu private timer and watchdog block
+ * which is used in both the ARM11MPCore and Cortex-A9MP.
+ */
+
+static inline int get_current_cpu(ARMMPTimerState *s)
+{
+ if (current_cpu->cpu_index >= s->num_cpu) {
+ hw_error("arm_mptimer: num-cpu %d but this cpu is %d!\n",
+ s->num_cpu, current_cpu->cpu_index);
+ }
+ return current_cpu->cpu_index;
+}
+
+static inline void timerblock_update_irq(TimerBlock *tb)
+{
+ qemu_set_irq(tb->irq, tb->status && (tb->control & 4));
+}
+
+/* Return conversion factor from mpcore timer ticks to qemu timer ticks. */
+static inline uint32_t timerblock_scale(TimerBlock *tb)
+{
+ return (((tb->control >> 8) & 0xff) + 1) * 10;
+}
+
+static void timerblock_reload(TimerBlock *tb, int restart)
+{
+ if (tb->count == 0) {
+ return;
+ }
+ if (restart) {
+ tb->tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+ tb->tick += (int64_t)tb->count * timerblock_scale(tb);
+ timer_mod(tb->timer, tb->tick);
+}
+
+static void timerblock_tick(void *opaque)
+{
+ TimerBlock *tb = (TimerBlock *)opaque;
+ tb->status = 1;
+ if (tb->control & 2) {
+ tb->count = tb->load;
+ timerblock_reload(tb, 0);
+ } else {
+ tb->count = 0;
+ }
+ timerblock_update_irq(tb);
+}
+
+static uint64_t timerblock_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TimerBlock *tb = (TimerBlock *)opaque;
+ int64_t val;
+ switch (addr) {
+ case 0: /* Load */
+ return tb->load;
+ case 4: /* Counter. */
+ if (((tb->control & 1) == 0) || (tb->count == 0)) {
+ return 0;
+ }
+ /* Slow and ugly, but hopefully won't happen too often. */
+ val = tb->tick - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ val /= timerblock_scale(tb);
+ if (val < 0) {
+ val = 0;
+ }
+ return val;
+ case 8: /* Control. */
+ return tb->control;
+ case 12: /* Interrupt status. */
+ return tb->status;
+ default:
+ return 0;
+ }
+}
+
+static void timerblock_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ TimerBlock *tb = (TimerBlock *)opaque;
+ int64_t old;
+ switch (addr) {
+ case 0: /* Load */
+ tb->load = value;
+ /* Fall through. */
+ case 4: /* Counter. */
+ if ((tb->control & 1) && tb->count) {
+ /* Cancel the previous timer. */
+ timer_del(tb->timer);
+ }
+ tb->count = value;
+ if (tb->control & 1) {
+ timerblock_reload(tb, 1);
+ }
+ break;
+ case 8: /* Control. */
+ old = tb->control;
+ tb->control = value;
+ if (value & 1) {
+ if ((old & 1) && (tb->count != 0)) {
+ /* Do nothing if timer is ticking right now. */
+ break;
+ }
+ if (tb->control & 2) {
+ tb->count = tb->load;
+ }
+ timerblock_reload(tb, 1);
+ } else if (old & 1) {
+ /* Shutdown the timer. */
+ timer_del(tb->timer);
+ }
+ break;
+ case 12: /* Interrupt status. */
+ tb->status &= ~value;
+ timerblock_update_irq(tb);
+ break;
+ }
+}
+
+/* Wrapper functions to implement the "read timer/watchdog for
+ * the current CPU" memory regions.
+ */
+static uint64_t arm_thistimer_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ARMMPTimerState *s = (ARMMPTimerState *)opaque;
+ int id = get_current_cpu(s);
+ return timerblock_read(&s->timerblock[id], addr, size);
+}
+
+static void arm_thistimer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ ARMMPTimerState *s = (ARMMPTimerState *)opaque;
+ int id = get_current_cpu(s);
+ timerblock_write(&s->timerblock[id], addr, value, size);
+}
+
+static const MemoryRegionOps arm_thistimer_ops = {
+ .read = arm_thistimer_read,
+ .write = arm_thistimer_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps timerblock_ops = {
+ .read = timerblock_read,
+ .write = timerblock_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void timerblock_reset(TimerBlock *tb)
+{
+ tb->count = 0;
+ tb->load = 0;
+ tb->control = 0;
+ tb->status = 0;
+ tb->tick = 0;
+ if (tb->timer) {
+ timer_del(tb->timer);
+ }
+}
+
+static void arm_mptimer_reset(DeviceState *dev)
+{
+ ARMMPTimerState *s = ARM_MPTIMER(dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(s->timerblock); i++) {
+ timerblock_reset(&s->timerblock[i]);
+ }
+}
+
+static void arm_mptimer_init(Object *obj)
+{
+ ARMMPTimerState *s = ARM_MPTIMER(obj);
+
+ memory_region_init_io(&s->iomem, obj, &arm_thistimer_ops, s,
+ "arm_mptimer_timer", 0x20);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void arm_mptimer_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ ARMMPTimerState *s = ARM_MPTIMER(dev);
+ int i;
+
+ if (s->num_cpu < 1 || s->num_cpu > ARM_MPTIMER_MAX_CPUS) {
+ hw_error("%s: num-cpu must be between 1 and %d\n",
+ __func__, ARM_MPTIMER_MAX_CPUS);
+ }
+ /* We implement one timer block per CPU, and expose multiple MMIO regions:
+ * * region 0 is "timer for this core"
+ * * region 1 is "timer for core 0"
+ * * region 2 is "timer for core 1"
+ * and so on.
+ * The outgoing interrupt lines are
+ * * timer for core 0
+ * * timer for core 1
+ * and so on.
+ */
+ for (i = 0; i < s->num_cpu; i++) {
+ TimerBlock *tb = &s->timerblock[i];
+ tb->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, timerblock_tick, tb);
+ sysbus_init_irq(sbd, &tb->irq);
+ memory_region_init_io(&tb->iomem, OBJECT(s), &timerblock_ops, tb,
+ "arm_mptimer_timerblock", 0x20);
+ sysbus_init_mmio(sbd, &tb->iomem);
+ }
+}
+
+static const VMStateDescription vmstate_timerblock = {
+ .name = "arm_mptimer_timerblock",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(count, TimerBlock),
+ VMSTATE_UINT32(load, TimerBlock),
+ VMSTATE_UINT32(control, TimerBlock),
+ VMSTATE_UINT32(status, TimerBlock),
+ VMSTATE_INT64(tick, TimerBlock),
+ VMSTATE_TIMER_PTR(timer, TimerBlock),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_arm_mptimer = {
+ .name = "arm_mptimer",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_VARRAY_UINT32(timerblock, ARMMPTimerState, num_cpu,
+ 2, vmstate_timerblock, TimerBlock),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property arm_mptimer_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", ARMMPTimerState, num_cpu, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void arm_mptimer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = arm_mptimer_realize;
+ dc->vmsd = &vmstate_arm_mptimer;
+ dc->reset = arm_mptimer_reset;
+ dc->props = arm_mptimer_properties;
+}
+
+static const TypeInfo arm_mptimer_info = {
+ .name = TYPE_ARM_MPTIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARMMPTimerState),
+ .instance_init = arm_mptimer_init,
+ .class_init = arm_mptimer_class_init,
+};
+
+static void arm_mptimer_register_types(void)
+{
+ type_register_static(&arm_mptimer_info);
+}
+
+type_init(arm_mptimer_register_types)
diff --git a/hw/timer/arm_timer.c b/hw/timer/arm_timer.c
new file mode 100644
index 00000000..d53f39ad
--- /dev/null
+++ b/hw/timer/arm_timer.c
@@ -0,0 +1,410 @@
+/*
+ * ARM PrimeCell Timer modules.
+ *
+ * Copyright (c) 2005-2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "qemu-common.h"
+#include "hw/qdev.h"
+#include "hw/ptimer.h"
+#include "qemu/main-loop.h"
+
+/* Common timer implementation. */
+
+#define TIMER_CTRL_ONESHOT (1 << 0)
+#define TIMER_CTRL_32BIT (1 << 1)
+#define TIMER_CTRL_DIV1 (0 << 2)
+#define TIMER_CTRL_DIV16 (1 << 2)
+#define TIMER_CTRL_DIV256 (2 << 2)
+#define TIMER_CTRL_IE (1 << 5)
+#define TIMER_CTRL_PERIODIC (1 << 6)
+#define TIMER_CTRL_ENABLE (1 << 7)
+
+typedef struct {
+ ptimer_state *timer;
+ uint32_t control;
+ uint32_t limit;
+ int freq;
+ int int_level;
+ qemu_irq irq;
+} arm_timer_state;
+
+/* Check all active timers, and schedule the next timer interrupt. */
+
+static void arm_timer_update(arm_timer_state *s)
+{
+ /* Update interrupts. */
+ if (s->int_level && (s->control & TIMER_CTRL_IE)) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static uint32_t arm_timer_read(void *opaque, hwaddr offset)
+{
+ arm_timer_state *s = (arm_timer_state *)opaque;
+
+ switch (offset >> 2) {
+ case 0: /* TimerLoad */
+ case 6: /* TimerBGLoad */
+ return s->limit;
+ case 1: /* TimerValue */
+ return ptimer_get_count(s->timer);
+ case 2: /* TimerControl */
+ return s->control;
+ case 4: /* TimerRIS */
+ return s->int_level;
+ case 5: /* TimerMIS */
+ if ((s->control & TIMER_CTRL_IE) == 0)
+ return 0;
+ return s->int_level;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset %x\n", __func__, (int)offset);
+ return 0;
+ }
+}
+
+/* Reset the timer limit after settings have changed. */
+static void arm_timer_recalibrate(arm_timer_state *s, int reload)
+{
+ uint32_t limit;
+
+ if ((s->control & (TIMER_CTRL_PERIODIC | TIMER_CTRL_ONESHOT)) == 0) {
+ /* Free running. */
+ if (s->control & TIMER_CTRL_32BIT)
+ limit = 0xffffffff;
+ else
+ limit = 0xffff;
+ } else {
+ /* Periodic. */
+ limit = s->limit;
+ }
+ ptimer_set_limit(s->timer, limit, reload);
+}
+
+static void arm_timer_write(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ arm_timer_state *s = (arm_timer_state *)opaque;
+ int freq;
+
+ switch (offset >> 2) {
+ case 0: /* TimerLoad */
+ s->limit = value;
+ arm_timer_recalibrate(s, 1);
+ break;
+ case 1: /* TimerValue */
+ /* ??? Linux seems to want to write to this readonly register.
+ Ignore it. */
+ break;
+ case 2: /* TimerControl */
+ if (s->control & TIMER_CTRL_ENABLE) {
+ /* Pause the timer if it is running. This may cause some
+ inaccuracy dure to rounding, but avoids a whole lot of other
+ messyness. */
+ ptimer_stop(s->timer);
+ }
+ s->control = value;
+ freq = s->freq;
+ /* ??? Need to recalculate expiry time after changing divisor. */
+ switch ((value >> 2) & 3) {
+ case 1: freq >>= 4; break;
+ case 2: freq >>= 8; break;
+ }
+ arm_timer_recalibrate(s, s->control & TIMER_CTRL_ENABLE);
+ ptimer_set_freq(s->timer, freq);
+ if (s->control & TIMER_CTRL_ENABLE) {
+ /* Restart the timer if still enabled. */
+ ptimer_run(s->timer, (s->control & TIMER_CTRL_ONESHOT) != 0);
+ }
+ break;
+ case 3: /* TimerIntClr */
+ s->int_level = 0;
+ break;
+ case 6: /* TimerBGLoad */
+ s->limit = value;
+ arm_timer_recalibrate(s, 0);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset %x\n", __func__, (int)offset);
+ }
+ arm_timer_update(s);
+}
+
+static void arm_timer_tick(void *opaque)
+{
+ arm_timer_state *s = (arm_timer_state *)opaque;
+ s->int_level = 1;
+ arm_timer_update(s);
+}
+
+static const VMStateDescription vmstate_arm_timer = {
+ .name = "arm_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(control, arm_timer_state),
+ VMSTATE_UINT32(limit, arm_timer_state),
+ VMSTATE_INT32(int_level, arm_timer_state),
+ VMSTATE_PTIMER(timer, arm_timer_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static arm_timer_state *arm_timer_init(uint32_t freq)
+{
+ arm_timer_state *s;
+ QEMUBH *bh;
+
+ s = (arm_timer_state *)g_malloc0(sizeof(arm_timer_state));
+ s->freq = freq;
+ s->control = TIMER_CTRL_IE;
+
+ bh = qemu_bh_new(arm_timer_tick, s);
+ s->timer = ptimer_init(bh);
+ vmstate_register(NULL, -1, &vmstate_arm_timer, s);
+ return s;
+}
+
+/* ARM PrimeCell SP804 dual timer module.
+ * Docs at
+ * http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0271d/index.html
+*/
+
+#define TYPE_SP804 "sp804"
+#define SP804(obj) OBJECT_CHECK(SP804State, (obj), TYPE_SP804)
+
+typedef struct SP804State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ arm_timer_state *timer[2];
+ uint32_t freq0, freq1;
+ int level[2];
+ qemu_irq irq;
+} SP804State;
+
+static const uint8_t sp804_ids[] = {
+ /* Timer ID */
+ 0x04, 0x18, 0x14, 0,
+ /* PrimeCell ID */
+ 0xd, 0xf0, 0x05, 0xb1
+};
+
+/* Merge the IRQs from the two component devices. */
+static void sp804_set_irq(void *opaque, int irq, int level)
+{
+ SP804State *s = (SP804State *)opaque;
+
+ s->level[irq] = level;
+ qemu_set_irq(s->irq, s->level[0] || s->level[1]);
+}
+
+static uint64_t sp804_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ SP804State *s = (SP804State *)opaque;
+
+ if (offset < 0x20) {
+ return arm_timer_read(s->timer[0], offset);
+ }
+ if (offset < 0x40) {
+ return arm_timer_read(s->timer[1], offset - 0x20);
+ }
+
+ /* TimerPeriphID */
+ if (offset >= 0xfe0 && offset <= 0xffc) {
+ return sp804_ids[(offset - 0xfe0) >> 2];
+ }
+
+ switch (offset) {
+ /* Integration Test control registers, which we won't support */
+ case 0xf00: /* TimerITCR */
+ case 0xf04: /* TimerITOP (strictly write only but..) */
+ qemu_log_mask(LOG_UNIMP,
+ "%s: integration test registers unimplemented\n",
+ __func__);
+ return 0;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset %x\n", __func__, (int)offset);
+ return 0;
+}
+
+static void sp804_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ SP804State *s = (SP804State *)opaque;
+
+ if (offset < 0x20) {
+ arm_timer_write(s->timer[0], offset, value);
+ return;
+ }
+
+ if (offset < 0x40) {
+ arm_timer_write(s->timer[1], offset - 0x20, value);
+ return;
+ }
+
+ /* Technically we could be writing to the Test Registers, but not likely */
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %x\n",
+ __func__, (int)offset);
+}
+
+static const MemoryRegionOps sp804_ops = {
+ .read = sp804_read,
+ .write = sp804_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_sp804 = {
+ .name = "sp804",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_ARRAY(level, SP804State, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int sp804_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ SP804State *s = SP804(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+ s->timer[0] = arm_timer_init(s->freq0);
+ s->timer[1] = arm_timer_init(s->freq1);
+ s->timer[0]->irq = qemu_allocate_irq(sp804_set_irq, s, 0);
+ s->timer[1]->irq = qemu_allocate_irq(sp804_set_irq, s, 1);
+ memory_region_init_io(&s->iomem, OBJECT(s), &sp804_ops, s,
+ "sp804", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ vmstate_register(dev, -1, &vmstate_sp804, s);
+ return 0;
+}
+
+/* Integrator/CP timer module. */
+
+#define TYPE_INTEGRATOR_PIT "integrator_pit"
+#define INTEGRATOR_PIT(obj) \
+ OBJECT_CHECK(icp_pit_state, (obj), TYPE_INTEGRATOR_PIT)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ arm_timer_state *timer[3];
+} icp_pit_state;
+
+static uint64_t icp_pit_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ icp_pit_state *s = (icp_pit_state *)opaque;
+ int n;
+
+ /* ??? Don't know the PrimeCell ID for this device. */
+ n = offset >> 8;
+ if (n > 2) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad timer %d\n", __func__, n);
+ return 0;
+ }
+
+ return arm_timer_read(s->timer[n], offset & 0xff);
+}
+
+static void icp_pit_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ icp_pit_state *s = (icp_pit_state *)opaque;
+ int n;
+
+ n = offset >> 8;
+ if (n > 2) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad timer %d\n", __func__, n);
+ return;
+ }
+
+ arm_timer_write(s->timer[n], offset & 0xff, value);
+}
+
+static const MemoryRegionOps icp_pit_ops = {
+ .read = icp_pit_read,
+ .write = icp_pit_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int icp_pit_init(SysBusDevice *dev)
+{
+ icp_pit_state *s = INTEGRATOR_PIT(dev);
+
+ /* Timer 0 runs at the system clock speed (40MHz). */
+ s->timer[0] = arm_timer_init(40000000);
+ /* The other two timers run at 1MHz. */
+ s->timer[1] = arm_timer_init(1000000);
+ s->timer[2] = arm_timer_init(1000000);
+
+ sysbus_init_irq(dev, &s->timer[0]->irq);
+ sysbus_init_irq(dev, &s->timer[1]->irq);
+ sysbus_init_irq(dev, &s->timer[2]->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &icp_pit_ops, s,
+ "icp_pit", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ /* This device has no state to save/restore. The component timers will
+ save themselves. */
+ return 0;
+}
+
+static void icp_pit_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = icp_pit_init;
+}
+
+static const TypeInfo icp_pit_info = {
+ .name = TYPE_INTEGRATOR_PIT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(icp_pit_state),
+ .class_init = icp_pit_class_init,
+};
+
+static Property sp804_properties[] = {
+ DEFINE_PROP_UINT32("freq0", SP804State, freq0, 1000000),
+ DEFINE_PROP_UINT32("freq1", SP804State, freq1, 1000000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sp804_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+ DeviceClass *k = DEVICE_CLASS(klass);
+
+ sdc->init = sp804_init;
+ k->props = sp804_properties;
+}
+
+static const TypeInfo sp804_info = {
+ .name = TYPE_SP804,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SP804State),
+ .class_init = sp804_class_init,
+};
+
+static void arm_timer_register_types(void)
+{
+ type_register_static(&icp_pit_info);
+ type_register_static(&sp804_info);
+}
+
+type_init(arm_timer_register_types)
diff --git a/hw/timer/cadence_ttc.c b/hw/timer/cadence_ttc.c
new file mode 100644
index 00000000..35bc8803
--- /dev/null
+++ b/hw/timer/cadence_ttc.c
@@ -0,0 +1,491 @@
+/*
+ * Xilinx Zynq cadence TTC model
+ *
+ * Copyright (c) 2011 Xilinx Inc.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ * Written By Haibing Ma
+ * M. Habib
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+
+#ifdef CADENCE_TTC_ERR_DEBUG
+#define DB_PRINT(...) do { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } while (0);
+#else
+ #define DB_PRINT(...)
+#endif
+
+#define COUNTER_INTR_IV 0x00000001
+#define COUNTER_INTR_M1 0x00000002
+#define COUNTER_INTR_M2 0x00000004
+#define COUNTER_INTR_M3 0x00000008
+#define COUNTER_INTR_OV 0x00000010
+#define COUNTER_INTR_EV 0x00000020
+
+#define COUNTER_CTRL_DIS 0x00000001
+#define COUNTER_CTRL_INT 0x00000002
+#define COUNTER_CTRL_DEC 0x00000004
+#define COUNTER_CTRL_MATCH 0x00000008
+#define COUNTER_CTRL_RST 0x00000010
+
+#define CLOCK_CTRL_PS_EN 0x00000001
+#define CLOCK_CTRL_PS_V 0x0000001e
+
+typedef struct {
+ QEMUTimer *timer;
+ int freq;
+
+ uint32_t reg_clock;
+ uint32_t reg_count;
+ uint32_t reg_value;
+ uint16_t reg_interval;
+ uint16_t reg_match[3];
+ uint32_t reg_intr;
+ uint32_t reg_intr_en;
+ uint32_t reg_event_ctrl;
+ uint32_t reg_event;
+
+ uint64_t cpu_time;
+ unsigned int cpu_time_valid;
+
+ qemu_irq irq;
+} CadenceTimerState;
+
+#define TYPE_CADENCE_TTC "cadence_ttc"
+#define CADENCE_TTC(obj) \
+ OBJECT_CHECK(CadenceTTCState, (obj), TYPE_CADENCE_TTC)
+
+typedef struct CadenceTTCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ CadenceTimerState timer[3];
+} CadenceTTCState;
+
+static void cadence_timer_update(CadenceTimerState *s)
+{
+ qemu_set_irq(s->irq, !!(s->reg_intr & s->reg_intr_en));
+}
+
+static CadenceTimerState *cadence_timer_from_addr(void *opaque,
+ hwaddr offset)
+{
+ unsigned int index;
+ CadenceTTCState *s = (CadenceTTCState *)opaque;
+
+ index = (offset >> 2) % 3;
+
+ return &s->timer[index];
+}
+
+static uint64_t cadence_timer_get_ns(CadenceTimerState *s, uint64_t timer_steps)
+{
+ /* timer_steps has max value of 0x100000000. double check it
+ * (or overflow can happen below) */
+ assert(timer_steps <= 1ULL << 32);
+
+ uint64_t r = timer_steps * 1000000000ULL;
+ if (s->reg_clock & CLOCK_CTRL_PS_EN) {
+ r >>= 16 - (((s->reg_clock & CLOCK_CTRL_PS_V) >> 1) + 1);
+ } else {
+ r >>= 16;
+ }
+ r /= (uint64_t)s->freq;
+ return r;
+}
+
+static uint64_t cadence_timer_get_steps(CadenceTimerState *s, uint64_t ns)
+{
+ uint64_t to_divide = 1000000000ULL;
+
+ uint64_t r = ns;
+ /* for very large intervals (> 8s) do some division first to stop
+ * overflow (costs some prescision) */
+ while (r >= 8ULL << 30 && to_divide > 1) {
+ r /= 1000;
+ to_divide /= 1000;
+ }
+ r <<= 16;
+ /* keep early-dividing as needed */
+ while (r >= 8ULL << 30 && to_divide > 1) {
+ r /= 1000;
+ to_divide /= 1000;
+ }
+ r *= (uint64_t)s->freq;
+ if (s->reg_clock & CLOCK_CTRL_PS_EN) {
+ r /= 1 << (((s->reg_clock & CLOCK_CTRL_PS_V) >> 1) + 1);
+ }
+
+ r /= to_divide;
+ return r;
+}
+
+/* determine if x is in between a and b, exclusive of a, inclusive of b */
+
+static inline int64_t is_between(int64_t x, int64_t a, int64_t b)
+{
+ if (a < b) {
+ return x > a && x <= b;
+ }
+ return x < a && x >= b;
+}
+
+static void cadence_timer_run(CadenceTimerState *s)
+{
+ int i;
+ int64_t event_interval, next_value;
+
+ assert(s->cpu_time_valid); /* cadence_timer_sync must be called first */
+
+ if (s->reg_count & COUNTER_CTRL_DIS) {
+ s->cpu_time_valid = 0;
+ return;
+ }
+
+ { /* figure out what's going to happen next (rollover or match) */
+ int64_t interval = (uint64_t)((s->reg_count & COUNTER_CTRL_INT) ?
+ (int64_t)s->reg_interval + 1 : 0x10000ULL) << 16;
+ next_value = (s->reg_count & COUNTER_CTRL_DEC) ? -1ULL : interval;
+ for (i = 0; i < 3; ++i) {
+ int64_t cand = (uint64_t)s->reg_match[i] << 16;
+ if (is_between(cand, (uint64_t)s->reg_value, next_value)) {
+ next_value = cand;
+ }
+ }
+ }
+ DB_PRINT("next timer event value: %09llx\n",
+ (unsigned long long)next_value);
+
+ event_interval = next_value - (int64_t)s->reg_value;
+ event_interval = (event_interval < 0) ? -event_interval : event_interval;
+
+ timer_mod(s->timer, s->cpu_time +
+ cadence_timer_get_ns(s, event_interval));
+}
+
+static void cadence_timer_sync(CadenceTimerState *s)
+{
+ int i;
+ int64_t r, x;
+ int64_t interval = ((s->reg_count & COUNTER_CTRL_INT) ?
+ (int64_t)s->reg_interval + 1 : 0x10000ULL) << 16;
+ uint64_t old_time = s->cpu_time;
+
+ s->cpu_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ DB_PRINT("cpu time: %lld ns\n", (long long)old_time);
+
+ if (!s->cpu_time_valid || old_time == s->cpu_time) {
+ s->cpu_time_valid = 1;
+ return;
+ }
+
+ r = (int64_t)cadence_timer_get_steps(s, s->cpu_time - old_time);
+ x = (int64_t)s->reg_value + ((s->reg_count & COUNTER_CTRL_DEC) ? -r : r);
+
+ for (i = 0; i < 3; ++i) {
+ int64_t m = (int64_t)s->reg_match[i] << 16;
+ if (m > interval) {
+ continue;
+ }
+ /* check to see if match event has occurred. check m +/- interval
+ * to account for match events in wrap around cases */
+ if (is_between(m, s->reg_value, x) ||
+ is_between(m + interval, s->reg_value, x) ||
+ is_between(m - interval, s->reg_value, x)) {
+ s->reg_intr |= (2 << i);
+ }
+ }
+ if ((x < 0) || (x >= interval)) {
+ s->reg_intr |= (s->reg_count & COUNTER_CTRL_INT) ?
+ COUNTER_INTR_IV : COUNTER_INTR_OV;
+ }
+ while (x < 0) {
+ x += interval;
+ }
+ s->reg_value = (uint32_t)(x % interval);
+ cadence_timer_update(s);
+}
+
+static void cadence_timer_tick(void *opaque)
+{
+ CadenceTimerState *s = opaque;
+
+ DB_PRINT("\n");
+ cadence_timer_sync(s);
+ cadence_timer_run(s);
+}
+
+static uint32_t cadence_ttc_read_imp(void *opaque, hwaddr offset)
+{
+ CadenceTimerState *s = cadence_timer_from_addr(opaque, offset);
+ uint32_t value;
+
+ cadence_timer_sync(s);
+ cadence_timer_run(s);
+
+ switch (offset) {
+ case 0x00: /* clock control */
+ case 0x04:
+ case 0x08:
+ return s->reg_clock;
+
+ case 0x0c: /* counter control */
+ case 0x10:
+ case 0x14:
+ return s->reg_count;
+
+ case 0x18: /* counter value */
+ case 0x1c:
+ case 0x20:
+ return (uint16_t)(s->reg_value >> 16);
+
+ case 0x24: /* reg_interval counter */
+ case 0x28:
+ case 0x2c:
+ return s->reg_interval;
+
+ case 0x30: /* match 1 counter */
+ case 0x34:
+ case 0x38:
+ return s->reg_match[0];
+
+ case 0x3c: /* match 2 counter */
+ case 0x40:
+ case 0x44:
+ return s->reg_match[1];
+
+ case 0x48: /* match 3 counter */
+ case 0x4c:
+ case 0x50:
+ return s->reg_match[2];
+
+ case 0x54: /* interrupt register */
+ case 0x58:
+ case 0x5c:
+ /* cleared after read */
+ value = s->reg_intr;
+ s->reg_intr = 0;
+ cadence_timer_update(s);
+ return value;
+
+ case 0x60: /* interrupt enable */
+ case 0x64:
+ case 0x68:
+ return s->reg_intr_en;
+
+ case 0x6c:
+ case 0x70:
+ case 0x74:
+ return s->reg_event_ctrl;
+
+ case 0x78:
+ case 0x7c:
+ case 0x80:
+ return s->reg_event;
+
+ default:
+ return 0;
+ }
+}
+
+static uint64_t cadence_ttc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t ret = cadence_ttc_read_imp(opaque, offset);
+
+ DB_PRINT("addr: %08x data: %08x\n", (unsigned)offset, (unsigned)ret);
+ return ret;
+}
+
+static void cadence_ttc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ CadenceTimerState *s = cadence_timer_from_addr(opaque, offset);
+
+ DB_PRINT("addr: %08x data %08x\n", (unsigned)offset, (unsigned)value);
+
+ cadence_timer_sync(s);
+
+ switch (offset) {
+ case 0x00: /* clock control */
+ case 0x04:
+ case 0x08:
+ s->reg_clock = value & 0x3F;
+ break;
+
+ case 0x0c: /* counter control */
+ case 0x10:
+ case 0x14:
+ if (value & COUNTER_CTRL_RST) {
+ s->reg_value = 0;
+ }
+ s->reg_count = value & 0x3f & ~COUNTER_CTRL_RST;
+ break;
+
+ case 0x24: /* interval register */
+ case 0x28:
+ case 0x2c:
+ s->reg_interval = value & 0xffff;
+ break;
+
+ case 0x30: /* match register */
+ case 0x34:
+ case 0x38:
+ s->reg_match[0] = value & 0xffff;
+ break;
+
+ case 0x3c: /* match register */
+ case 0x40:
+ case 0x44:
+ s->reg_match[1] = value & 0xffff;
+ break;
+
+ case 0x48: /* match register */
+ case 0x4c:
+ case 0x50:
+ s->reg_match[2] = value & 0xffff;
+ break;
+
+ case 0x54: /* interrupt register */
+ case 0x58:
+ case 0x5c:
+ break;
+
+ case 0x60: /* interrupt enable */
+ case 0x64:
+ case 0x68:
+ s->reg_intr_en = value & 0x3f;
+ break;
+
+ case 0x6c: /* event control */
+ case 0x70:
+ case 0x74:
+ s->reg_event_ctrl = value & 0x07;
+ break;
+
+ default:
+ return;
+ }
+
+ cadence_timer_run(s);
+ cadence_timer_update(s);
+}
+
+static const MemoryRegionOps cadence_ttc_ops = {
+ .read = cadence_ttc_read,
+ .write = cadence_ttc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void cadence_timer_reset(CadenceTimerState *s)
+{
+ s->reg_count = 0x21;
+}
+
+static void cadence_timer_init(uint32_t freq, CadenceTimerState *s)
+{
+ memset(s, 0, sizeof(CadenceTimerState));
+ s->freq = freq;
+
+ cadence_timer_reset(s);
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, cadence_timer_tick, s);
+}
+
+static void cadence_ttc_init(Object *obj)
+{
+ CadenceTTCState *s = CADENCE_TTC(obj);
+ int i;
+
+ for (i = 0; i < 3; ++i) {
+ cadence_timer_init(133000000, &s->timer[i]);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->timer[i].irq);
+ }
+
+ memory_region_init_io(&s->iomem, obj, &cadence_ttc_ops, s,
+ "timer", 0x1000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void cadence_timer_pre_save(void *opaque)
+{
+ cadence_timer_sync((CadenceTimerState *)opaque);
+}
+
+static int cadence_timer_post_load(void *opaque, int version_id)
+{
+ CadenceTimerState *s = opaque;
+
+ s->cpu_time_valid = 0;
+ cadence_timer_sync(s);
+ cadence_timer_run(s);
+ cadence_timer_update(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_cadence_timer = {
+ .name = "cadence_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = cadence_timer_pre_save,
+ .post_load = cadence_timer_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg_clock, CadenceTimerState),
+ VMSTATE_UINT32(reg_count, CadenceTimerState),
+ VMSTATE_UINT32(reg_value, CadenceTimerState),
+ VMSTATE_UINT16(reg_interval, CadenceTimerState),
+ VMSTATE_UINT16_ARRAY(reg_match, CadenceTimerState, 3),
+ VMSTATE_UINT32(reg_intr, CadenceTimerState),
+ VMSTATE_UINT32(reg_intr_en, CadenceTimerState),
+ VMSTATE_UINT32(reg_event_ctrl, CadenceTimerState),
+ VMSTATE_UINT32(reg_event, CadenceTimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_cadence_ttc = {
+ .name = "cadence_TTC",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(timer, CadenceTTCState, 3, 0,
+ vmstate_cadence_timer,
+ CadenceTimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cadence_ttc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_cadence_ttc;
+}
+
+static const TypeInfo cadence_ttc_info = {
+ .name = TYPE_CADENCE_TTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CadenceTTCState),
+ .instance_init = cadence_ttc_init,
+ .class_init = cadence_ttc_class_init,
+};
+
+static void cadence_ttc_register_types(void)
+{
+ type_register_static(&cadence_ttc_info);
+}
+
+type_init(cadence_ttc_register_types)
diff --git a/hw/timer/digic-timer.c b/hw/timer/digic-timer.c
new file mode 100644
index 00000000..7e28e7e5
--- /dev/null
+++ b/hw/timer/digic-timer.c
@@ -0,0 +1,162 @@
+/*
+ * QEMU model of the Canon DIGIC timer block.
+ *
+ * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com>
+ *
+ * This model is based on reverse engineering efforts
+ * made by CHDK (http://chdk.wikia.com) and
+ * Magic Lantern (http://www.magiclantern.fm) projects
+ * contributors.
+ *
+ * See "Timer/Clock Module" docs here:
+ * http://magiclantern.wikia.com/wiki/Register_Map
+ *
+ * The QEMU model of the OSTimer in PKUnity SoC by Guan Xuetao
+ * is used as a template.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "hw/sysbus.h"
+#include "hw/ptimer.h"
+#include "qemu/main-loop.h"
+
+#include "hw/timer/digic-timer.h"
+
+static const VMStateDescription vmstate_digic_timer = {
+ .name = "digic.timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PTIMER(ptimer, DigicTimerState),
+ VMSTATE_UINT32(control, DigicTimerState),
+ VMSTATE_UINT32(relvalue, DigicTimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void digic_timer_reset(DeviceState *dev)
+{
+ DigicTimerState *s = DIGIC_TIMER(dev);
+
+ ptimer_stop(s->ptimer);
+ s->control = 0;
+ s->relvalue = 0;
+}
+
+static uint64_t digic_timer_read(void *opaque, hwaddr offset, unsigned size)
+{
+ DigicTimerState *s = opaque;
+ uint64_t ret = 0;
+
+ switch (offset) {
+ case DIGIC_TIMER_CONTROL:
+ ret = s->control;
+ break;
+ case DIGIC_TIMER_RELVALUE:
+ ret = s->relvalue;
+ break;
+ case DIGIC_TIMER_VALUE:
+ ret = ptimer_get_count(s->ptimer) & 0xffff;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "digic-timer: read access to unknown register 0x"
+ TARGET_FMT_plx, offset);
+ }
+
+ return ret;
+}
+
+static void digic_timer_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ DigicTimerState *s = opaque;
+
+ switch (offset) {
+ case DIGIC_TIMER_CONTROL:
+ if (value & DIGIC_TIMER_CONTROL_RST) {
+ digic_timer_reset((DeviceState *)s);
+ break;
+ }
+
+ if (value & DIGIC_TIMER_CONTROL_EN) {
+ ptimer_run(s->ptimer, 0);
+ }
+
+ s->control = (uint32_t)value;
+ break;
+
+ case DIGIC_TIMER_RELVALUE:
+ s->relvalue = extract32(value, 0, 16);
+ ptimer_set_limit(s->ptimer, s->relvalue, 1);
+ break;
+
+ case DIGIC_TIMER_VALUE:
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "digic-timer: read access to unknown register 0x"
+ TARGET_FMT_plx, offset);
+ }
+}
+
+static const MemoryRegionOps digic_timer_ops = {
+ .read = digic_timer_read,
+ .write = digic_timer_write,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void digic_timer_init(Object *obj)
+{
+ DigicTimerState *s = DIGIC_TIMER(obj);
+
+ s->ptimer = ptimer_init(NULL);
+
+ /*
+ * FIXME: there is no documentation on Digic timer
+ * frequency setup so let it always run at 1 MHz
+ */
+ ptimer_set_freq(s->ptimer, 1 * 1000 * 1000);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &digic_timer_ops, s,
+ TYPE_DIGIC_TIMER, 0x100);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void digic_timer_class_init(ObjectClass *klass, void *class_data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = digic_timer_reset;
+ dc->vmsd = &vmstate_digic_timer;
+}
+
+static const TypeInfo digic_timer_info = {
+ .name = TYPE_DIGIC_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(DigicTimerState),
+ .instance_init = digic_timer_init,
+ .class_init = digic_timer_class_init,
+};
+
+static void digic_timer_register_type(void)
+{
+ type_register_static(&digic_timer_info);
+}
+
+type_init(digic_timer_register_type)
diff --git a/hw/timer/ds1338.c b/hw/timer/ds1338.c
new file mode 100644
index 00000000..ec6dbeea
--- /dev/null
+++ b/hw/timer/ds1338.c
@@ -0,0 +1,240 @@
+/*
+ * MAXIM DS1338 I2C RTC+NVRAM
+ *
+ * Copyright (c) 2009 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/i2c/i2c.h"
+
+/* Size of NVRAM including both the user-accessible area and the
+ * secondary register area.
+ */
+#define NVRAM_SIZE 64
+
+/* Flags definitions */
+#define SECONDS_CH 0x80
+#define HOURS_12 0x40
+#define HOURS_PM 0x20
+#define CTRL_OSF 0x20
+
+#define TYPE_DS1338 "ds1338"
+#define DS1338(obj) OBJECT_CHECK(DS1338State, (obj), TYPE_DS1338)
+
+typedef struct DS1338State {
+ I2CSlave parent_obj;
+
+ int64_t offset;
+ uint8_t wday_offset;
+ uint8_t nvram[NVRAM_SIZE];
+ int32_t ptr;
+ bool addr_byte;
+} DS1338State;
+
+static const VMStateDescription vmstate_ds1338 = {
+ .name = "ds1338",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
+ VMSTATE_INT64(offset, DS1338State),
+ VMSTATE_UINT8_V(wday_offset, DS1338State, 2),
+ VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
+ VMSTATE_INT32(ptr, DS1338State),
+ VMSTATE_BOOL(addr_byte, DS1338State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void capture_current_time(DS1338State *s)
+{
+ /* Capture the current time into the secondary registers
+ * which will be actually read by the data transfer operation.
+ */
+ struct tm now;
+ qemu_get_timedate(&now, s->offset);
+ s->nvram[0] = to_bcd(now.tm_sec);
+ s->nvram[1] = to_bcd(now.tm_min);
+ if (s->nvram[2] & HOURS_12) {
+ int tmp = now.tm_hour;
+ if (tmp % 12 == 0) {
+ tmp += 12;
+ }
+ if (tmp <= 12) {
+ s->nvram[2] = HOURS_12 | to_bcd(tmp);
+ } else {
+ s->nvram[2] = HOURS_12 | HOURS_PM | to_bcd(tmp - 12);
+ }
+ } else {
+ s->nvram[2] = to_bcd(now.tm_hour);
+ }
+ s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1;
+ s->nvram[4] = to_bcd(now.tm_mday);
+ s->nvram[5] = to_bcd(now.tm_mon + 1);
+ s->nvram[6] = to_bcd(now.tm_year - 100);
+}
+
+static void inc_regptr(DS1338State *s)
+{
+ /* The register pointer wraps around after 0x3F; wraparound
+ * causes the current time/date to be retransferred into
+ * the secondary registers.
+ */
+ s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
+ if (!s->ptr) {
+ capture_current_time(s);
+ }
+}
+
+static void ds1338_event(I2CSlave *i2c, enum i2c_event event)
+{
+ DS1338State *s = DS1338(i2c);
+
+ switch (event) {
+ case I2C_START_RECV:
+ /* In h/w, capture happens on any START condition, not just a
+ * START_RECV, but there is no need to actually capture on
+ * START_SEND, because the guest can't get at that data
+ * without going through a START_RECV which would overwrite it.
+ */
+ capture_current_time(s);
+ break;
+ case I2C_START_SEND:
+ s->addr_byte = true;
+ break;
+ default:
+ break;
+ }
+}
+
+static int ds1338_recv(I2CSlave *i2c)
+{
+ DS1338State *s = DS1338(i2c);
+ uint8_t res;
+
+ res = s->nvram[s->ptr];
+ inc_regptr(s);
+ return res;
+}
+
+static int ds1338_send(I2CSlave *i2c, uint8_t data)
+{
+ DS1338State *s = DS1338(i2c);
+
+ if (s->addr_byte) {
+ s->ptr = data & (NVRAM_SIZE - 1);
+ s->addr_byte = false;
+ return 0;
+ }
+ if (s->ptr < 7) {
+ /* Time register. */
+ struct tm now;
+ qemu_get_timedate(&now, s->offset);
+ switch(s->ptr) {
+ case 0:
+ /* TODO: Implement CH (stop) bit. */
+ now.tm_sec = from_bcd(data & 0x7f);
+ break;
+ case 1:
+ now.tm_min = from_bcd(data & 0x7f);
+ break;
+ case 2:
+ if (data & HOURS_12) {
+ int tmp = from_bcd(data & (HOURS_PM - 1));
+ if (data & HOURS_PM) {
+ tmp += 12;
+ }
+ if (tmp % 12 == 0) {
+ tmp -= 12;
+ }
+ now.tm_hour = tmp;
+ } else {
+ now.tm_hour = from_bcd(data & (HOURS_12 - 1));
+ }
+ break;
+ case 3:
+ {
+ /* The day field is supposed to contain a value in
+ the range 1-7. Otherwise behavior is undefined.
+ */
+ int user_wday = (data & 7) - 1;
+ s->wday_offset = (user_wday - now.tm_wday + 7) % 7;
+ }
+ break;
+ case 4:
+ now.tm_mday = from_bcd(data & 0x3f);
+ break;
+ case 5:
+ now.tm_mon = from_bcd(data & 0x1f) - 1;
+ break;
+ case 6:
+ now.tm_year = from_bcd(data) + 100;
+ break;
+ }
+ s->offset = qemu_timedate_diff(&now);
+ } else if (s->ptr == 7) {
+ /* Control register. */
+
+ /* Ensure bits 2, 3 and 6 will read back as zero. */
+ data &= 0xB3;
+
+ /* Attempting to write the OSF flag to logic 1 leaves the
+ value unchanged. */
+ data = (data & ~CTRL_OSF) | (data & s->nvram[s->ptr] & CTRL_OSF);
+
+ s->nvram[s->ptr] = data;
+ } else {
+ s->nvram[s->ptr] = data;
+ }
+ inc_regptr(s);
+ return 0;
+}
+
+static int ds1338_init(I2CSlave *i2c)
+{
+ return 0;
+}
+
+static void ds1338_reset(DeviceState *dev)
+{
+ DS1338State *s = DS1338(dev);
+
+ /* The clock is running and synchronized with the host */
+ s->offset = 0;
+ s->wday_offset = 0;
+ memset(s->nvram, 0, NVRAM_SIZE);
+ s->ptr = 0;
+ s->addr_byte = false;
+}
+
+static void ds1338_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = ds1338_init;
+ k->event = ds1338_event;
+ k->recv = ds1338_recv;
+ k->send = ds1338_send;
+ dc->reset = ds1338_reset;
+ dc->vmsd = &vmstate_ds1338;
+}
+
+static const TypeInfo ds1338_info = {
+ .name = TYPE_DS1338,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(DS1338State),
+ .class_init = ds1338_class_init,
+};
+
+static void ds1338_register_types(void)
+{
+ type_register_static(&ds1338_info);
+}
+
+type_init(ds1338_register_types)
diff --git a/hw/timer/etraxfs_timer.c b/hw/timer/etraxfs_timer.c
new file mode 100644
index 00000000..aee4990e
--- /dev/null
+++ b/hw/timer/etraxfs_timer.c
@@ -0,0 +1,357 @@
+/*
+ * QEMU ETRAX Timers
+ *
+ * Copyright (c) 2007 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * 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.
+ */
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+
+#define D(x)
+
+#define RW_TMR0_DIV 0x00
+#define R_TMR0_DATA 0x04
+#define RW_TMR0_CTRL 0x08
+#define RW_TMR1_DIV 0x10
+#define R_TMR1_DATA 0x14
+#define RW_TMR1_CTRL 0x18
+#define R_TIME 0x38
+#define RW_WD_CTRL 0x40
+#define R_WD_STAT 0x44
+#define RW_INTR_MASK 0x48
+#define RW_ACK_INTR 0x4c
+#define R_INTR 0x50
+#define R_MASKED_INTR 0x54
+
+#define TYPE_ETRAX_FS_TIMER "etraxfs,timer"
+#define ETRAX_TIMER(obj) \
+ OBJECT_CHECK(ETRAXTimerState, (obj), TYPE_ETRAX_FS_TIMER)
+
+typedef struct ETRAXTimerState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ qemu_irq irq;
+ qemu_irq nmi;
+
+ QEMUBH *bh_t0;
+ QEMUBH *bh_t1;
+ QEMUBH *bh_wd;
+ ptimer_state *ptimer_t0;
+ ptimer_state *ptimer_t1;
+ ptimer_state *ptimer_wd;
+
+ int wd_hits;
+
+ /* Control registers. */
+ uint32_t rw_tmr0_div;
+ uint32_t r_tmr0_data;
+ uint32_t rw_tmr0_ctrl;
+
+ uint32_t rw_tmr1_div;
+ uint32_t r_tmr1_data;
+ uint32_t rw_tmr1_ctrl;
+
+ uint32_t rw_wd_ctrl;
+
+ uint32_t rw_intr_mask;
+ uint32_t rw_ack_intr;
+ uint32_t r_intr;
+ uint32_t r_masked_intr;
+} ETRAXTimerState;
+
+static uint64_t
+timer_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ ETRAXTimerState *t = opaque;
+ uint32_t r = 0;
+
+ switch (addr) {
+ case R_TMR0_DATA:
+ r = ptimer_get_count(t->ptimer_t0);
+ break;
+ case R_TMR1_DATA:
+ r = ptimer_get_count(t->ptimer_t1);
+ break;
+ case R_TIME:
+ r = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 10;
+ break;
+ case RW_INTR_MASK:
+ r = t->rw_intr_mask;
+ break;
+ case R_MASKED_INTR:
+ r = t->r_intr & t->rw_intr_mask;
+ break;
+ default:
+ D(printf ("%s %x\n", __func__, addr));
+ break;
+ }
+ return r;
+}
+
+static void update_ctrl(ETRAXTimerState *t, int tnum)
+{
+ unsigned int op;
+ unsigned int freq;
+ unsigned int freq_hz;
+ unsigned int div;
+ uint32_t ctrl;
+
+ ptimer_state *timer;
+
+ if (tnum == 0) {
+ ctrl = t->rw_tmr0_ctrl;
+ div = t->rw_tmr0_div;
+ timer = t->ptimer_t0;
+ } else {
+ ctrl = t->rw_tmr1_ctrl;
+ div = t->rw_tmr1_div;
+ timer = t->ptimer_t1;
+ }
+
+
+ op = ctrl & 3;
+ freq = ctrl >> 2;
+ freq_hz = 32000000;
+
+ switch (freq)
+ {
+ case 0:
+ case 1:
+ D(printf ("extern or disabled timer clock?\n"));
+ break;
+ case 4: freq_hz = 29493000; break;
+ case 5: freq_hz = 32000000; break;
+ case 6: freq_hz = 32768000; break;
+ case 7: freq_hz = 100000000; break;
+ default:
+ abort();
+ break;
+ }
+
+ D(printf ("freq_hz=%d div=%d\n", freq_hz, div));
+ ptimer_set_freq(timer, freq_hz);
+ ptimer_set_limit(timer, div, 0);
+
+ switch (op)
+ {
+ case 0:
+ /* Load. */
+ ptimer_set_limit(timer, div, 1);
+ break;
+ case 1:
+ /* Hold. */
+ ptimer_stop(timer);
+ break;
+ case 2:
+ /* Run. */
+ ptimer_run(timer, 0);
+ break;
+ default:
+ abort();
+ break;
+ }
+}
+
+static void timer_update_irq(ETRAXTimerState *t)
+{
+ t->r_intr &= ~(t->rw_ack_intr);
+ t->r_masked_intr = t->r_intr & t->rw_intr_mask;
+
+ D(printf("%s: masked_intr=%x\n", __func__, t->r_masked_intr));
+ qemu_set_irq(t->irq, !!t->r_masked_intr);
+}
+
+static void timer0_hit(void *opaque)
+{
+ ETRAXTimerState *t = opaque;
+ t->r_intr |= 1;
+ timer_update_irq(t);
+}
+
+static void timer1_hit(void *opaque)
+{
+ ETRAXTimerState *t = opaque;
+ t->r_intr |= 2;
+ timer_update_irq(t);
+}
+
+static void watchdog_hit(void *opaque)
+{
+ ETRAXTimerState *t = opaque;
+ if (t->wd_hits == 0) {
+ /* real hw gives a single tick before reseting but we are
+ a bit friendlier to compensate for our slower execution. */
+ ptimer_set_count(t->ptimer_wd, 10);
+ ptimer_run(t->ptimer_wd, 1);
+ qemu_irq_raise(t->nmi);
+ }
+ else
+ qemu_system_reset_request();
+
+ t->wd_hits++;
+}
+
+static inline void timer_watchdog_update(ETRAXTimerState *t, uint32_t value)
+{
+ unsigned int wd_en = t->rw_wd_ctrl & (1 << 8);
+ unsigned int wd_key = t->rw_wd_ctrl >> 9;
+ unsigned int wd_cnt = t->rw_wd_ctrl & 511;
+ unsigned int new_key = value >> 9 & ((1 << 7) - 1);
+ unsigned int new_cmd = (value >> 8) & 1;
+
+ /* If the watchdog is enabled, they written key must match the
+ complement of the previous. */
+ wd_key = ~wd_key & ((1 << 7) - 1);
+
+ if (wd_en && wd_key != new_key)
+ return;
+
+ D(printf("en=%d new_key=%x oldkey=%x cmd=%d cnt=%d\n",
+ wd_en, new_key, wd_key, new_cmd, wd_cnt));
+
+ if (t->wd_hits)
+ qemu_irq_lower(t->nmi);
+
+ t->wd_hits = 0;
+
+ ptimer_set_freq(t->ptimer_wd, 760);
+ if (wd_cnt == 0)
+ wd_cnt = 256;
+ ptimer_set_count(t->ptimer_wd, wd_cnt);
+ if (new_cmd)
+ ptimer_run(t->ptimer_wd, 1);
+ else
+ ptimer_stop(t->ptimer_wd);
+
+ t->rw_wd_ctrl = value;
+}
+
+static void
+timer_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ ETRAXTimerState *t = opaque;
+ uint32_t value = val64;
+
+ switch (addr)
+ {
+ case RW_TMR0_DIV:
+ t->rw_tmr0_div = value;
+ break;
+ case RW_TMR0_CTRL:
+ D(printf ("RW_TMR0_CTRL=%x\n", value));
+ t->rw_tmr0_ctrl = value;
+ update_ctrl(t, 0);
+ break;
+ case RW_TMR1_DIV:
+ t->rw_tmr1_div = value;
+ break;
+ case RW_TMR1_CTRL:
+ D(printf ("RW_TMR1_CTRL=%x\n", value));
+ t->rw_tmr1_ctrl = value;
+ update_ctrl(t, 1);
+ break;
+ case RW_INTR_MASK:
+ D(printf ("RW_INTR_MASK=%x\n", value));
+ t->rw_intr_mask = value;
+ timer_update_irq(t);
+ break;
+ case RW_WD_CTRL:
+ timer_watchdog_update(t, value);
+ break;
+ case RW_ACK_INTR:
+ t->rw_ack_intr = value;
+ timer_update_irq(t);
+ t->rw_ack_intr = 0;
+ break;
+ default:
+ printf ("%s " TARGET_FMT_plx " %x\n",
+ __func__, addr, value);
+ break;
+ }
+}
+
+static const MemoryRegionOps timer_ops = {
+ .read = timer_read,
+ .write = timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void etraxfs_timer_reset(void *opaque)
+{
+ ETRAXTimerState *t = opaque;
+
+ ptimer_stop(t->ptimer_t0);
+ ptimer_stop(t->ptimer_t1);
+ ptimer_stop(t->ptimer_wd);
+ t->rw_wd_ctrl = 0;
+ t->r_intr = 0;
+ t->rw_intr_mask = 0;
+ qemu_irq_lower(t->irq);
+}
+
+static int etraxfs_timer_init(SysBusDevice *dev)
+{
+ ETRAXTimerState *t = ETRAX_TIMER(dev);
+
+ t->bh_t0 = qemu_bh_new(timer0_hit, t);
+ t->bh_t1 = qemu_bh_new(timer1_hit, t);
+ t->bh_wd = qemu_bh_new(watchdog_hit, t);
+ t->ptimer_t0 = ptimer_init(t->bh_t0);
+ t->ptimer_t1 = ptimer_init(t->bh_t1);
+ t->ptimer_wd = ptimer_init(t->bh_wd);
+
+ sysbus_init_irq(dev, &t->irq);
+ sysbus_init_irq(dev, &t->nmi);
+
+ memory_region_init_io(&t->mmio, OBJECT(t), &timer_ops, t,
+ "etraxfs-timer", 0x5c);
+ sysbus_init_mmio(dev, &t->mmio);
+ qemu_register_reset(etraxfs_timer_reset, t);
+ return 0;
+}
+
+static void etraxfs_timer_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = etraxfs_timer_init;
+}
+
+static const TypeInfo etraxfs_timer_info = {
+ .name = TYPE_ETRAX_FS_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ETRAXTimerState),
+ .class_init = etraxfs_timer_class_init,
+};
+
+static void etraxfs_timer_register_types(void)
+{
+ type_register_static(&etraxfs_timer_info);
+}
+
+type_init(etraxfs_timer_register_types)
diff --git a/hw/timer/exynos4210_mct.c b/hw/timer/exynos4210_mct.c
new file mode 100644
index 00000000..015bbaf1
--- /dev/null
+++ b/hw/timer/exynos4210_mct.c
@@ -0,0 +1,1481 @@
+/*
+ * Samsung exynos4210 Multi Core timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Global Timer:
+ *
+ * Consists of two timers. First represents Free Running Counter and second
+ * is used to measure interval from FRC to nearest comparator.
+ *
+ * 0 UINT64_MAX
+ * | timer0 |
+ * | <-------------------------------------------------------------- |
+ * | --------------------------------------------frc---------------> |
+ * |______________________________________________|__________________|
+ * CMP0 CMP1 CMP2 | CMP3
+ * __| |_
+ * | timer1 |
+ * | -------------> |
+ * frc CMPx
+ *
+ * Problem: when implementing global timer as is, overflow arises.
+ * next_time = cur_time + period * count;
+ * period and count are 64 bits width.
+ * Lets arm timer for MCT_GT_COUNTER_STEP count and update internal G_CNT
+ * register during each event.
+ *
+ * Problem: both timers need to be implemented using MCT_XT_COUNTER_STEP because
+ * local timer contains two counters: TCNT and ICNT. TCNT == 0 -> ICNT--.
+ * IRQ is generated when ICNT riches zero. Implementation where TCNT == 0
+ * generates IRQs suffers from too frequently events. Better to have one
+ * uint64_t counter equal to TCNT*ICNT and arm ptimer.c for a minimum(TCNT*ICNT,
+ * MCT_GT_COUNTER_STEP); (yes, if target tunes ICNT * TCNT to be too low values,
+ * there is no way to avoid frequently events).
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "qemu/main-loop.h"
+#include "qemu-common.h"
+#include "hw/ptimer.h"
+
+#include "hw/arm/exynos4210.h"
+
+//#define DEBUG_MCT
+
+#ifdef DEBUG_MCT
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "MCT: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define MCT_CFG 0x000
+#define G_CNT_L 0x100
+#define G_CNT_U 0x104
+#define G_CNT_WSTAT 0x110
+#define G_COMP0_L 0x200
+#define G_COMP0_U 0x204
+#define G_COMP0_ADD_INCR 0x208
+#define G_COMP1_L 0x210
+#define G_COMP1_U 0x214
+#define G_COMP1_ADD_INCR 0x218
+#define G_COMP2_L 0x220
+#define G_COMP2_U 0x224
+#define G_COMP2_ADD_INCR 0x228
+#define G_COMP3_L 0x230
+#define G_COMP3_U 0x234
+#define G_COMP3_ADD_INCR 0x238
+#define G_TCON 0x240
+#define G_INT_CSTAT 0x244
+#define G_INT_ENB 0x248
+#define G_WSTAT 0x24C
+#define L0_TCNTB 0x300
+#define L0_TCNTO 0x304
+#define L0_ICNTB 0x308
+#define L0_ICNTO 0x30C
+#define L0_FRCNTB 0x310
+#define L0_FRCNTO 0x314
+#define L0_TCON 0x320
+#define L0_INT_CSTAT 0x330
+#define L0_INT_ENB 0x334
+#define L0_WSTAT 0x340
+#define L1_TCNTB 0x400
+#define L1_TCNTO 0x404
+#define L1_ICNTB 0x408
+#define L1_ICNTO 0x40C
+#define L1_FRCNTB 0x410
+#define L1_FRCNTO 0x414
+#define L1_TCON 0x420
+#define L1_INT_CSTAT 0x430
+#define L1_INT_ENB 0x434
+#define L1_WSTAT 0x440
+
+#define MCT_CFG_GET_PRESCALER(x) ((x) & 0xFF)
+#define MCT_CFG_GET_DIVIDER(x) (1 << ((x) >> 8 & 7))
+
+#define GET_G_COMP_IDX(offset) (((offset) - G_COMP0_L) / 0x10)
+#define GET_G_COMP_ADD_INCR_IDX(offset) (((offset) - G_COMP0_ADD_INCR) / 0x10)
+
+#define G_COMP_L(x) (G_COMP0_L + (x) * 0x10)
+#define G_COMP_U(x) (G_COMP0_U + (x) * 0x10)
+
+#define G_COMP_ADD_INCR(x) (G_COMP0_ADD_INCR + (x) * 0x10)
+
+/* MCT bits */
+#define G_TCON_COMP_ENABLE(x) (1 << 2 * (x))
+#define G_TCON_AUTO_ICREMENT(x) (1 << (2 * (x) + 1))
+#define G_TCON_TIMER_ENABLE (1 << 8)
+
+#define G_INT_ENABLE(x) (1 << (x))
+#define G_INT_CSTAT_COMP(x) (1 << (x))
+
+#define G_CNT_WSTAT_L 1
+#define G_CNT_WSTAT_U 2
+
+#define G_WSTAT_COMP_L(x) (1 << 4 * (x))
+#define G_WSTAT_COMP_U(x) (1 << ((4 * (x)) + 1))
+#define G_WSTAT_COMP_ADDINCR(x) (1 << ((4 * (x)) + 2))
+#define G_WSTAT_TCON_WRITE (1 << 16)
+
+#define GET_L_TIMER_IDX(offset) ((((offset) & 0xF00) - L0_TCNTB) / 0x100)
+#define GET_L_TIMER_CNT_REG_IDX(offset, lt_i) \
+ (((offset) - (L0_TCNTB + 0x100 * (lt_i))) >> 2)
+
+#define L_ICNTB_MANUAL_UPDATE (1 << 31)
+
+#define L_TCON_TICK_START (1)
+#define L_TCON_INT_START (1 << 1)
+#define L_TCON_INTERVAL_MODE (1 << 2)
+#define L_TCON_FRC_START (1 << 3)
+
+#define L_INT_CSTAT_INTCNT (1 << 0)
+#define L_INT_CSTAT_FRCCNT (1 << 1)
+
+#define L_INT_INTENB_ICNTEIE (1 << 0)
+#define L_INT_INTENB_FRCEIE (1 << 1)
+
+#define L_WSTAT_TCNTB_WRITE (1 << 0)
+#define L_WSTAT_ICNTB_WRITE (1 << 1)
+#define L_WSTAT_FRCCNTB_WRITE (1 << 2)
+#define L_WSTAT_TCON_WRITE (1 << 3)
+
+enum LocalTimerRegCntIndexes {
+ L_REG_CNT_TCNTB,
+ L_REG_CNT_TCNTO,
+ L_REG_CNT_ICNTB,
+ L_REG_CNT_ICNTO,
+ L_REG_CNT_FRCCNTB,
+ L_REG_CNT_FRCCNTO,
+
+ L_REG_CNT_AMOUNT
+};
+
+#define MCT_NIRQ 6
+#define MCT_SFR_SIZE 0x444
+
+#define MCT_GT_CMP_NUM 4
+
+#define MCT_GT_MAX_VAL UINT64_MAX
+
+#define MCT_GT_COUNTER_STEP 0x100000000ULL
+#define MCT_LT_COUNTER_STEP 0x100000000ULL
+#define MCT_LT_CNT_LOW_LIMIT 0x100
+
+/* global timer */
+typedef struct {
+ qemu_irq irq[MCT_GT_CMP_NUM];
+
+ struct gregs {
+ uint64_t cnt;
+ uint32_t cnt_wstat;
+ uint32_t tcon;
+ uint32_t int_cstat;
+ uint32_t int_enb;
+ uint32_t wstat;
+ uint64_t comp[MCT_GT_CMP_NUM];
+ uint32_t comp_add_incr[MCT_GT_CMP_NUM];
+ } reg;
+
+ uint64_t count; /* Value FRC was armed with */
+ int32_t curr_comp; /* Current comparator FRC is running to */
+
+ ptimer_state *ptimer_frc; /* FRC timer */
+
+} Exynos4210MCTGT;
+
+/* local timer */
+typedef struct {
+ int id; /* timer id */
+ qemu_irq irq; /* local timer irq */
+
+ struct tick_timer {
+ uint32_t cnt_run; /* cnt timer is running */
+ uint32_t int_run; /* int timer is running */
+
+ uint32_t last_icnto;
+ uint32_t last_tcnto;
+ uint32_t tcntb; /* initial value for TCNTB */
+ uint32_t icntb; /* initial value for ICNTB */
+
+ /* for step mode */
+ uint64_t distance; /* distance to count to the next event */
+ uint64_t progress; /* progress when counting by steps */
+ uint64_t count; /* count to arm timer with */
+
+ ptimer_state *ptimer_tick; /* timer for tick counter */
+ } tick_timer;
+
+ /* use ptimer.c to represent count down timer */
+
+ ptimer_state *ptimer_frc; /* timer for free running counter */
+
+ /* registers */
+ struct lregs {
+ uint32_t cnt[L_REG_CNT_AMOUNT];
+ uint32_t tcon;
+ uint32_t int_cstat;
+ uint32_t int_enb;
+ uint32_t wstat;
+ } reg;
+
+} Exynos4210MCTLT;
+
+#define TYPE_EXYNOS4210_MCT "exynos4210.mct"
+#define EXYNOS4210_MCT(obj) \
+ OBJECT_CHECK(Exynos4210MCTState, (obj), TYPE_EXYNOS4210_MCT)
+
+typedef struct Exynos4210MCTState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ /* Registers */
+ uint32_t reg_mct_cfg;
+
+ Exynos4210MCTLT l_timer[2];
+ Exynos4210MCTGT g_timer;
+
+ uint32_t freq; /* all timers tick frequency, TCLK */
+} Exynos4210MCTState;
+
+/*** VMState ***/
+static const VMStateDescription vmstate_tick_timer = {
+ .name = "exynos4210.mct.tick_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cnt_run, struct tick_timer),
+ VMSTATE_UINT32(int_run, struct tick_timer),
+ VMSTATE_UINT32(last_icnto, struct tick_timer),
+ VMSTATE_UINT32(last_tcnto, struct tick_timer),
+ VMSTATE_UINT32(tcntb, struct tick_timer),
+ VMSTATE_UINT32(icntb, struct tick_timer),
+ VMSTATE_UINT64(distance, struct tick_timer),
+ VMSTATE_UINT64(progress, struct tick_timer),
+ VMSTATE_UINT64(count, struct tick_timer),
+ VMSTATE_PTIMER(ptimer_tick, struct tick_timer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_lregs = {
+ .name = "exynos4210.mct.lregs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(cnt, struct lregs, L_REG_CNT_AMOUNT),
+ VMSTATE_UINT32(tcon, struct lregs),
+ VMSTATE_UINT32(int_cstat, struct lregs),
+ VMSTATE_UINT32(int_enb, struct lregs),
+ VMSTATE_UINT32(wstat, struct lregs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_mct_lt = {
+ .name = "exynos4210.mct.lt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(id, Exynos4210MCTLT),
+ VMSTATE_STRUCT(tick_timer, Exynos4210MCTLT, 0,
+ vmstate_tick_timer,
+ struct tick_timer),
+ VMSTATE_PTIMER(ptimer_frc, Exynos4210MCTLT),
+ VMSTATE_STRUCT(reg, Exynos4210MCTLT, 0,
+ vmstate_lregs,
+ struct lregs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_gregs = {
+ .name = "exynos4210.mct.lregs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(cnt, struct gregs),
+ VMSTATE_UINT32(cnt_wstat, struct gregs),
+ VMSTATE_UINT32(tcon, struct gregs),
+ VMSTATE_UINT32(int_cstat, struct gregs),
+ VMSTATE_UINT32(int_enb, struct gregs),
+ VMSTATE_UINT32(wstat, struct gregs),
+ VMSTATE_UINT64_ARRAY(comp, struct gregs, MCT_GT_CMP_NUM),
+ VMSTATE_UINT32_ARRAY(comp_add_incr, struct gregs,
+ MCT_GT_CMP_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_mct_gt = {
+ .name = "exynos4210.mct.lt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(reg, Exynos4210MCTGT, 0, vmstate_gregs,
+ struct gregs),
+ VMSTATE_UINT64(count, Exynos4210MCTGT),
+ VMSTATE_INT32(curr_comp, Exynos4210MCTGT),
+ VMSTATE_PTIMER(ptimer_frc, Exynos4210MCTGT),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_mct_state = {
+ .name = "exynos4210.mct",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg_mct_cfg, Exynos4210MCTState),
+ VMSTATE_STRUCT_ARRAY(l_timer, Exynos4210MCTState, 2, 0,
+ vmstate_exynos4210_mct_lt, Exynos4210MCTLT),
+ VMSTATE_STRUCT(g_timer, Exynos4210MCTState, 0,
+ vmstate_exynos4210_mct_gt, Exynos4210MCTGT),
+ VMSTATE_UINT32(freq, Exynos4210MCTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_mct_update_freq(Exynos4210MCTState *s);
+
+/*
+ * Set counter of FRC global timer.
+ */
+static void exynos4210_gfrc_set_count(Exynos4210MCTGT *s, uint64_t count)
+{
+ s->count = count;
+ DPRINTF("global timer frc set count 0x%llx\n", count);
+ ptimer_set_count(s->ptimer_frc, count);
+}
+
+/*
+ * Get counter of FRC global timer.
+ */
+static uint64_t exynos4210_gfrc_get_count(Exynos4210MCTGT *s)
+{
+ uint64_t count = 0;
+ count = ptimer_get_count(s->ptimer_frc);
+ count = s->count - count;
+ return s->reg.cnt + count;
+}
+
+/*
+ * Stop global FRC timer
+ */
+static void exynos4210_gfrc_stop(Exynos4210MCTGT *s)
+{
+ DPRINTF("global timer frc stop\n");
+
+ ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Start global FRC timer
+ */
+static void exynos4210_gfrc_start(Exynos4210MCTGT *s)
+{
+ DPRINTF("global timer frc start\n");
+
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Find next nearest Comparator. If current Comparator value equals to other
+ * Comparator value, skip them both
+ */
+static int32_t exynos4210_gcomp_find(Exynos4210MCTState *s)
+{
+ int res;
+ int i;
+ int enabled;
+ uint64_t min;
+ int min_comp_i;
+ uint64_t gfrc;
+ uint64_t distance;
+ uint64_t distance_min;
+ int comp_i;
+
+ /* get gfrc count */
+ gfrc = exynos4210_gfrc_get_count(&s->g_timer);
+
+ min = UINT64_MAX;
+ distance_min = UINT64_MAX;
+ comp_i = MCT_GT_CMP_NUM;
+ min_comp_i = MCT_GT_CMP_NUM;
+ enabled = 0;
+
+ /* lookup for nearest comparator */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+
+ if (s->g_timer.reg.tcon & G_TCON_COMP_ENABLE(i)) {
+
+ enabled = 1;
+
+ if (s->g_timer.reg.comp[i] > gfrc) {
+ /* Comparator is upper then FRC */
+ distance = s->g_timer.reg.comp[i] - gfrc;
+
+ if (distance <= distance_min) {
+ distance_min = distance;
+ comp_i = i;
+ }
+ } else {
+ /* Comparator is below FRC, find the smallest */
+
+ if (s->g_timer.reg.comp[i] <= min) {
+ min = s->g_timer.reg.comp[i];
+ min_comp_i = i;
+ }
+ }
+ }
+ }
+
+ if (!enabled) {
+ /* All Comparators disabled */
+ res = -1;
+ } else if (comp_i < MCT_GT_CMP_NUM) {
+ /* Found upper Comparator */
+ res = comp_i;
+ } else {
+ /* All Comparators are below or equal to FRC */
+ res = min_comp_i;
+ }
+
+ DPRINTF("found comparator %d: comp 0x%llx distance 0x%llx, gfrc 0x%llx\n",
+ res,
+ s->g_timer.reg.comp[res],
+ distance_min,
+ gfrc);
+
+ return res;
+}
+
+/*
+ * Get distance to nearest Comparator
+ */
+static uint64_t exynos4210_gcomp_get_distance(Exynos4210MCTState *s, int32_t id)
+{
+ if (id == -1) {
+ /* no enabled Comparators, choose max distance */
+ return MCT_GT_COUNTER_STEP;
+ }
+ if (s->g_timer.reg.comp[id] - s->g_timer.reg.cnt < MCT_GT_COUNTER_STEP) {
+ return s->g_timer.reg.comp[id] - s->g_timer.reg.cnt;
+ } else {
+ return MCT_GT_COUNTER_STEP;
+ }
+}
+
+/*
+ * Restart global FRC timer
+ */
+static void exynos4210_gfrc_restart(Exynos4210MCTState *s)
+{
+ uint64_t distance;
+
+ exynos4210_gfrc_stop(&s->g_timer);
+
+ s->g_timer.curr_comp = exynos4210_gcomp_find(s);
+
+ distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp);
+
+ if (distance > MCT_GT_COUNTER_STEP || !distance) {
+ distance = MCT_GT_COUNTER_STEP;
+ }
+
+ exynos4210_gfrc_set_count(&s->g_timer, distance);
+ exynos4210_gfrc_start(&s->g_timer);
+}
+
+/*
+ * Raise global timer CMP IRQ
+ */
+static void exynos4210_gcomp_raise_irq(void *opaque, uint32_t id)
+{
+ Exynos4210MCTGT *s = opaque;
+
+ /* If CSTAT is pending and IRQ is enabled */
+ if ((s->reg.int_cstat & G_INT_CSTAT_COMP(id)) &&
+ (s->reg.int_enb & G_INT_ENABLE(id))) {
+ DPRINTF("gcmp timer[%d] IRQ\n", id);
+ qemu_irq_raise(s->irq[id]);
+ }
+}
+
+/*
+ * Lower global timer CMP IRQ
+ */
+static void exynos4210_gcomp_lower_irq(void *opaque, uint32_t id)
+{
+ Exynos4210MCTGT *s = opaque;
+ qemu_irq_lower(s->irq[id]);
+}
+
+/*
+ * Global timer FRC event handler.
+ * Each event occurs when internal counter reaches counter + MCT_GT_COUNTER_STEP
+ * Every time we arm global FRC timer to count for MCT_GT_COUNTER_STEP value
+ */
+static void exynos4210_gfrc_event(void *opaque)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int i;
+ uint64_t distance;
+
+ DPRINTF("\n");
+
+ s->g_timer.reg.cnt += s->g_timer.count;
+
+ /* Process all comparators */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+
+ if (s->g_timer.reg.cnt == s->g_timer.reg.comp[i]) {
+ /* reached nearest comparator */
+
+ s->g_timer.reg.int_cstat |= G_INT_CSTAT_COMP(i);
+
+ /* Auto increment */
+ if (s->g_timer.reg.tcon & G_TCON_AUTO_ICREMENT(i)) {
+ s->g_timer.reg.comp[i] += s->g_timer.reg.comp_add_incr[i];
+ }
+
+ /* IRQ */
+ exynos4210_gcomp_raise_irq(&s->g_timer, i);
+ }
+ }
+
+ /* Reload FRC to reach nearest comparator */
+ s->g_timer.curr_comp = exynos4210_gcomp_find(s);
+ distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp);
+ if (distance > MCT_GT_COUNTER_STEP || !distance) {
+ distance = MCT_GT_COUNTER_STEP;
+ }
+ exynos4210_gfrc_set_count(&s->g_timer, distance);
+
+ exynos4210_gfrc_start(&s->g_timer);
+}
+
+/*
+ * Get counter of FRC local timer.
+ */
+static uint64_t exynos4210_lfrc_get_count(Exynos4210MCTLT *s)
+{
+ return ptimer_get_count(s->ptimer_frc);
+}
+
+/*
+ * Set counter of FRC local timer.
+ */
+static void exynos4210_lfrc_update_count(Exynos4210MCTLT *s)
+{
+ if (!s->reg.cnt[L_REG_CNT_FRCCNTB]) {
+ ptimer_set_count(s->ptimer_frc, MCT_LT_COUNTER_STEP);
+ } else {
+ ptimer_set_count(s->ptimer_frc, s->reg.cnt[L_REG_CNT_FRCCNTB]);
+ }
+}
+
+/*
+ * Start local FRC timer
+ */
+static void exynos4210_lfrc_start(Exynos4210MCTLT *s)
+{
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Stop local FRC timer
+ */
+static void exynos4210_lfrc_stop(Exynos4210MCTLT *s)
+{
+ ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Local timer free running counter tick handler
+ */
+static void exynos4210_lfrc_event(void *opaque)
+{
+ Exynos4210MCTLT * s = (Exynos4210MCTLT *)opaque;
+
+ /* local frc expired */
+
+ DPRINTF("\n");
+
+ s->reg.int_cstat |= L_INT_CSTAT_FRCCNT;
+
+ /* update frc counter */
+ exynos4210_lfrc_update_count(s);
+
+ /* raise irq */
+ if (s->reg.int_enb & L_INT_INTENB_FRCEIE) {
+ qemu_irq_raise(s->irq);
+ }
+
+ /* we reached here, this means that timer is enabled */
+ exynos4210_lfrc_start(s);
+}
+
+static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s);
+static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s);
+static void exynos4210_ltick_recalc_count(struct tick_timer *s);
+
+/*
+ * Action on enabling local tick int timer
+ */
+static void exynos4210_ltick_int_start(struct tick_timer *s)
+{
+ if (!s->int_run) {
+ s->int_run = 1;
+ }
+}
+
+/*
+ * Action on disabling local tick int timer
+ */
+static void exynos4210_ltick_int_stop(struct tick_timer *s)
+{
+ if (s->int_run) {
+ s->last_icnto = exynos4210_ltick_int_get_cnto(s);
+ s->int_run = 0;
+ }
+}
+
+/*
+ * Get count for INT timer
+ */
+static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s)
+{
+ uint32_t icnto;
+ uint64_t remain;
+ uint64_t count;
+ uint64_t counted;
+ uint64_t cur_progress;
+
+ count = ptimer_get_count(s->ptimer_tick);
+ if (count) {
+ /* timer is still counting, called not from event */
+ counted = s->count - ptimer_get_count(s->ptimer_tick);
+ cur_progress = s->progress + counted;
+ } else {
+ /* timer expired earlier */
+ cur_progress = s->progress;
+ }
+
+ remain = s->distance - cur_progress;
+
+ if (!s->int_run) {
+ /* INT is stopped. */
+ icnto = s->last_icnto;
+ } else {
+ /* Both are counting */
+ icnto = remain / s->tcntb;
+ }
+
+ return icnto;
+}
+
+/*
+ * Start local tick cnt timer.
+ */
+static void exynos4210_ltick_cnt_start(struct tick_timer *s)
+{
+ if (!s->cnt_run) {
+
+ exynos4210_ltick_recalc_count(s);
+ ptimer_set_count(s->ptimer_tick, s->count);
+ ptimer_run(s->ptimer_tick, 1);
+
+ s->cnt_run = 1;
+ }
+}
+
+/*
+ * Stop local tick cnt timer.
+ */
+static void exynos4210_ltick_cnt_stop(struct tick_timer *s)
+{
+ if (s->cnt_run) {
+
+ s->last_tcnto = exynos4210_ltick_cnt_get_cnto(s);
+
+ if (s->int_run) {
+ exynos4210_ltick_int_stop(s);
+ }
+
+ ptimer_stop(s->ptimer_tick);
+
+ s->cnt_run = 0;
+ }
+}
+
+/*
+ * Get counter for CNT timer
+ */
+static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s)
+{
+ uint32_t tcnto;
+ uint32_t icnto;
+ uint64_t remain;
+ uint64_t counted;
+ uint64_t count;
+ uint64_t cur_progress;
+
+ count = ptimer_get_count(s->ptimer_tick);
+ if (count) {
+ /* timer is still counting, called not from event */
+ counted = s->count - ptimer_get_count(s->ptimer_tick);
+ cur_progress = s->progress + counted;
+ } else {
+ /* timer expired earlier */
+ cur_progress = s->progress;
+ }
+
+ remain = s->distance - cur_progress;
+
+ if (!s->cnt_run) {
+ /* Both are stopped. */
+ tcnto = s->last_tcnto;
+ } else if (!s->int_run) {
+ /* INT counter is stopped, progress is by CNT timer */
+ tcnto = remain % s->tcntb;
+ } else {
+ /* Both are counting */
+ icnto = remain / s->tcntb;
+ if (icnto) {
+ tcnto = remain % (icnto * s->tcntb);
+ } else {
+ tcnto = remain % s->tcntb;
+ }
+ }
+
+ return tcnto;
+}
+
+/*
+ * Set new values of counters for CNT and INT timers
+ */
+static void exynos4210_ltick_set_cntb(struct tick_timer *s, uint32_t new_cnt,
+ uint32_t new_int)
+{
+ uint32_t cnt_stopped = 0;
+ uint32_t int_stopped = 0;
+
+ if (s->cnt_run) {
+ exynos4210_ltick_cnt_stop(s);
+ cnt_stopped = 1;
+ }
+
+ if (s->int_run) {
+ exynos4210_ltick_int_stop(s);
+ int_stopped = 1;
+ }
+
+ s->tcntb = new_cnt + 1;
+ s->icntb = new_int + 1;
+
+ if (cnt_stopped) {
+ exynos4210_ltick_cnt_start(s);
+ }
+ if (int_stopped) {
+ exynos4210_ltick_int_start(s);
+ }
+
+}
+
+/*
+ * Calculate new counter value for tick timer
+ */
+static void exynos4210_ltick_recalc_count(struct tick_timer *s)
+{
+ uint64_t to_count;
+
+ if ((s->cnt_run && s->last_tcnto) || (s->int_run && s->last_icnto)) {
+ /*
+ * one or both timers run and not counted to the end;
+ * distance is not passed, recalculate with last_tcnto * last_icnto
+ */
+
+ if (s->last_tcnto) {
+ to_count = (uint64_t)s->last_tcnto * s->last_icnto;
+ } else {
+ to_count = s->last_icnto;
+ }
+ } else {
+ /* distance is passed, recalculate with tcnto * icnto */
+ if (s->icntb) {
+ s->distance = (uint64_t)s->tcntb * s->icntb;
+ } else {
+ s->distance = s->tcntb;
+ }
+
+ to_count = s->distance;
+ s->progress = 0;
+ }
+
+ if (to_count > MCT_LT_COUNTER_STEP) {
+ /* count by step */
+ s->count = MCT_LT_COUNTER_STEP;
+ } else {
+ s->count = to_count;
+ }
+}
+
+/*
+ * Initialize tick_timer
+ */
+static void exynos4210_ltick_timer_init(struct tick_timer *s)
+{
+ exynos4210_ltick_int_stop(s);
+ exynos4210_ltick_cnt_stop(s);
+
+ s->count = 0;
+ s->distance = 0;
+ s->progress = 0;
+ s->icntb = 0;
+ s->tcntb = 0;
+}
+
+/*
+ * tick_timer event.
+ * Raises when abstract tick_timer expires.
+ */
+static void exynos4210_ltick_timer_event(struct tick_timer *s)
+{
+ s->progress += s->count;
+}
+
+/*
+ * Local timer tick counter handler.
+ * Don't use reloaded timers. If timer counter = zero
+ * then handler called but after handler finished no
+ * timer reload occurs.
+ */
+static void exynos4210_ltick_event(void *opaque)
+{
+ Exynos4210MCTLT * s = (Exynos4210MCTLT *)opaque;
+ uint32_t tcnto;
+ uint32_t icnto;
+#ifdef DEBUG_MCT
+ static uint64_t time1[2] = {0};
+ static uint64_t time2[2] = {0};
+#endif
+
+ /* Call tick_timer event handler, it will update its tcntb and icntb. */
+ exynos4210_ltick_timer_event(&s->tick_timer);
+
+ /* get tick_timer cnt */
+ tcnto = exynos4210_ltick_cnt_get_cnto(&s->tick_timer);
+
+ /* get tick_timer int */
+ icnto = exynos4210_ltick_int_get_cnto(&s->tick_timer);
+
+ /* raise IRQ if needed */
+ if (!icnto && s->reg.tcon & L_TCON_INT_START) {
+ /* INT counter enabled and expired */
+
+ s->reg.int_cstat |= L_INT_CSTAT_INTCNT;
+
+ /* raise interrupt if enabled */
+ if (s->reg.int_enb & L_INT_INTENB_ICNTEIE) {
+#ifdef DEBUG_MCT
+ time2[s->id] = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ DPRINTF("local timer[%d] IRQ: %llx\n", s->id,
+ time2[s->id] - time1[s->id]);
+ time1[s->id] = time2[s->id];
+#endif
+ qemu_irq_raise(s->irq);
+ }
+
+ /* reload ICNTB */
+ if (s->reg.tcon & L_TCON_INTERVAL_MODE) {
+ exynos4210_ltick_set_cntb(&s->tick_timer,
+ s->reg.cnt[L_REG_CNT_TCNTB],
+ s->reg.cnt[L_REG_CNT_ICNTB]);
+ }
+ } else {
+ /* reload TCNTB */
+ if (!tcnto) {
+ exynos4210_ltick_set_cntb(&s->tick_timer,
+ s->reg.cnt[L_REG_CNT_TCNTB],
+ icnto);
+ }
+ }
+
+ /* start tick_timer cnt */
+ exynos4210_ltick_cnt_start(&s->tick_timer);
+
+ /* start tick_timer int */
+ exynos4210_ltick_int_start(&s->tick_timer);
+}
+
+/* update timer frequency */
+static void exynos4210_mct_update_freq(Exynos4210MCTState *s)
+{
+ uint32_t freq = s->freq;
+ s->freq = 24000000 /
+ ((MCT_CFG_GET_PRESCALER(s->reg_mct_cfg)+1) *
+ MCT_CFG_GET_DIVIDER(s->reg_mct_cfg));
+
+ if (freq != s->freq) {
+ DPRINTF("freq=%dHz\n", s->freq);
+
+ /* global timer */
+ ptimer_set_freq(s->g_timer.ptimer_frc, s->freq);
+
+ /* local timer */
+ ptimer_set_freq(s->l_timer[0].tick_timer.ptimer_tick, s->freq);
+ ptimer_set_freq(s->l_timer[0].ptimer_frc, s->freq);
+ ptimer_set_freq(s->l_timer[1].tick_timer.ptimer_tick, s->freq);
+ ptimer_set_freq(s->l_timer[1].ptimer_frc, s->freq);
+ }
+}
+
+/* set defaul_timer values for all fields */
+static void exynos4210_mct_reset(DeviceState *d)
+{
+ Exynos4210MCTState *s = EXYNOS4210_MCT(d);
+ uint32_t i;
+
+ s->reg_mct_cfg = 0;
+
+ /* global timer */
+ memset(&s->g_timer.reg, 0, sizeof(s->g_timer.reg));
+ exynos4210_gfrc_stop(&s->g_timer);
+
+ /* local timer */
+ memset(s->l_timer[0].reg.cnt, 0, sizeof(s->l_timer[0].reg.cnt));
+ memset(s->l_timer[1].reg.cnt, 0, sizeof(s->l_timer[1].reg.cnt));
+ for (i = 0; i < 2; i++) {
+ s->l_timer[i].reg.int_cstat = 0;
+ s->l_timer[i].reg.int_enb = 0;
+ s->l_timer[i].reg.tcon = 0;
+ s->l_timer[i].reg.wstat = 0;
+ s->l_timer[i].tick_timer.count = 0;
+ s->l_timer[i].tick_timer.distance = 0;
+ s->l_timer[i].tick_timer.progress = 0;
+ ptimer_stop(s->l_timer[i].ptimer_frc);
+
+ exynos4210_ltick_timer_init(&s->l_timer[i].tick_timer);
+ }
+
+ exynos4210_mct_update_freq(s);
+
+}
+
+/* Multi Core Timer read */
+static uint64_t exynos4210_mct_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int index;
+ int shift;
+ uint64_t count;
+ uint32_t value;
+ int lt_i;
+
+ switch (offset) {
+
+ case MCT_CFG:
+ value = s->reg_mct_cfg;
+ break;
+
+ case G_CNT_L: case G_CNT_U:
+ shift = 8 * (offset & 0x4);
+ count = exynos4210_gfrc_get_count(&s->g_timer);
+ value = UINT32_MAX & (count >> shift);
+ DPRINTF("read FRC=0x%llx\n", count);
+ break;
+
+ case G_CNT_WSTAT:
+ value = s->g_timer.reg.cnt_wstat;
+ break;
+
+ case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3):
+ case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3):
+ index = GET_G_COMP_IDX(offset);
+ shift = 8 * (offset & 0x4);
+ value = UINT32_MAX & (s->g_timer.reg.comp[index] >> shift);
+ break;
+
+ case G_TCON:
+ value = s->g_timer.reg.tcon;
+ break;
+
+ case G_INT_CSTAT:
+ value = s->g_timer.reg.int_cstat;
+ break;
+
+ case G_INT_ENB:
+ value = s->g_timer.reg.int_enb;
+ break;
+ case G_WSTAT:
+ value = s->g_timer.reg.wstat;
+ break;
+
+ case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR:
+ case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR:
+ value = s->g_timer.reg.comp_add_incr[GET_G_COMP_ADD_INCR_IDX(offset)];
+ break;
+
+ /* Local timers */
+ case L0_TCNTB: case L0_ICNTB: case L0_FRCNTB:
+ case L1_TCNTB: case L1_ICNTB: case L1_FRCNTB:
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+ value = s->l_timer[lt_i].reg.cnt[index];
+ break;
+
+ case L0_TCNTO: case L1_TCNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_ltick_cnt_get_cnto(&s->l_timer[lt_i].tick_timer);
+ DPRINTF("local timer[%d] read TCNTO %x\n", lt_i, value);
+ break;
+
+ case L0_ICNTO: case L1_ICNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_ltick_int_get_cnto(&s->l_timer[lt_i].tick_timer);
+ DPRINTF("local timer[%d] read ICNTO %x\n", lt_i, value);
+ break;
+
+ case L0_FRCNTO: case L1_FRCNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_lfrc_get_count(&s->l_timer[lt_i]);
+
+ break;
+
+ case L0_TCON: case L1_TCON:
+ lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100;
+ value = s->l_timer[lt_i].reg.tcon;
+ break;
+
+ case L0_INT_CSTAT: case L1_INT_CSTAT:
+ lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100;
+ value = s->l_timer[lt_i].reg.int_cstat;
+ break;
+
+ case L0_INT_ENB: case L1_INT_ENB:
+ lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100;
+ value = s->l_timer[lt_i].reg.int_enb;
+ break;
+
+ case L0_WSTAT: case L1_WSTAT:
+ lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100;
+ value = s->l_timer[lt_i].reg.wstat;
+ break;
+
+ default:
+ hw_error("exynos4210.mct: bad read offset "
+ TARGET_FMT_plx "\n", offset);
+ break;
+ }
+ return value;
+}
+
+/* MCT write */
+static void exynos4210_mct_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int index; /* index in buffer which represents register set */
+ int shift;
+ int lt_i;
+ uint64_t new_frc;
+ uint32_t i;
+ uint32_t old_val;
+#ifdef DEBUG_MCT
+ static uint32_t icntb_max[2] = {0};
+ static uint32_t icntb_min[2] = {UINT32_MAX, UINT32_MAX};
+ static uint32_t tcntb_max[2] = {0};
+ static uint32_t tcntb_min[2] = {UINT32_MAX, UINT32_MAX};
+#endif
+
+ new_frc = s->g_timer.reg.cnt;
+
+ switch (offset) {
+
+ case MCT_CFG:
+ s->reg_mct_cfg = value;
+ exynos4210_mct_update_freq(s);
+ break;
+
+ case G_CNT_L:
+ case G_CNT_U:
+ if (offset == G_CNT_L) {
+
+ DPRINTF("global timer write to reg.cntl %llx\n", value);
+
+ new_frc = (s->g_timer.reg.cnt & (uint64_t)UINT32_MAX << 32) + value;
+ s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_L;
+ }
+ if (offset == G_CNT_U) {
+
+ DPRINTF("global timer write to reg.cntu %llx\n", value);
+
+ new_frc = (s->g_timer.reg.cnt & UINT32_MAX) +
+ ((uint64_t)value << 32);
+ s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_U;
+ }
+
+ s->g_timer.reg.cnt = new_frc;
+ exynos4210_gfrc_restart(s);
+ break;
+
+ case G_CNT_WSTAT:
+ s->g_timer.reg.cnt_wstat &= ~(value);
+ break;
+
+ case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3):
+ case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3):
+ index = GET_G_COMP_IDX(offset);
+ shift = 8 * (offset & 0x4);
+ s->g_timer.reg.comp[index] =
+ (s->g_timer.reg.comp[index] &
+ (((uint64_t)UINT32_MAX << 32) >> shift)) +
+ (value << shift);
+
+ DPRINTF("comparator %d write 0x%llx val << %d\n", index, value, shift);
+
+ if (offset&0x4) {
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_U(index);
+ } else {
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_L(index);
+ }
+
+ exynos4210_gfrc_restart(s);
+ break;
+
+ case G_TCON:
+ old_val = s->g_timer.reg.tcon;
+ s->g_timer.reg.tcon = value;
+ s->g_timer.reg.wstat |= G_WSTAT_TCON_WRITE;
+
+ DPRINTF("global timer write to reg.g_tcon %llx\n", value);
+
+ /* Start FRC if transition from disabled to enabled */
+ if ((value & G_TCON_TIMER_ENABLE) > (old_val &
+ G_TCON_TIMER_ENABLE)) {
+ exynos4210_gfrc_start(&s->g_timer);
+ }
+ if ((value & G_TCON_TIMER_ENABLE) < (old_val &
+ G_TCON_TIMER_ENABLE)) {
+ exynos4210_gfrc_stop(&s->g_timer);
+ }
+
+ /* Start CMP if transition from disabled to enabled */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if ((value & G_TCON_COMP_ENABLE(i)) != (old_val &
+ G_TCON_COMP_ENABLE(i))) {
+ exynos4210_gfrc_restart(s);
+ }
+ }
+ break;
+
+ case G_INT_CSTAT:
+ s->g_timer.reg.int_cstat &= ~(value);
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if (value & G_INT_CSTAT_COMP(i)) {
+ exynos4210_gcomp_lower_irq(&s->g_timer, i);
+ }
+ }
+ break;
+
+ case G_INT_ENB:
+
+ /* Raise IRQ if transition from disabled to enabled and CSTAT pending */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if ((value & G_INT_ENABLE(i)) > (s->g_timer.reg.tcon &
+ G_INT_ENABLE(i))) {
+ if (s->g_timer.reg.int_cstat & G_INT_CSTAT_COMP(i)) {
+ exynos4210_gcomp_raise_irq(&s->g_timer, i);
+ }
+ }
+
+ if ((value & G_INT_ENABLE(i)) < (s->g_timer.reg.tcon &
+ G_INT_ENABLE(i))) {
+ exynos4210_gcomp_lower_irq(&s->g_timer, i);
+ }
+ }
+
+ DPRINTF("global timer INT enable %llx\n", value);
+ s->g_timer.reg.int_enb = value;
+ break;
+
+ case G_WSTAT:
+ s->g_timer.reg.wstat &= ~(value);
+ break;
+
+ case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR:
+ case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR:
+ index = GET_G_COMP_ADD_INCR_IDX(offset);
+ s->g_timer.reg.comp_add_incr[index] = value;
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_ADDINCR(index);
+ break;
+
+ /* Local timers */
+ case L0_TCON: case L1_TCON:
+ lt_i = GET_L_TIMER_IDX(offset);
+ old_val = s->l_timer[lt_i].reg.tcon;
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCON_WRITE;
+ s->l_timer[lt_i].reg.tcon = value;
+
+ /* Stop local CNT */
+ if ((value & L_TCON_TICK_START) <
+ (old_val & L_TCON_TICK_START)) {
+ DPRINTF("local timer[%d] stop cnt\n", lt_i);
+ exynos4210_ltick_cnt_stop(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Stop local INT */
+ if ((value & L_TCON_INT_START) <
+ (old_val & L_TCON_INT_START)) {
+ DPRINTF("local timer[%d] stop int\n", lt_i);
+ exynos4210_ltick_int_stop(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Start local CNT */
+ if ((value & L_TCON_TICK_START) >
+ (old_val & L_TCON_TICK_START)) {
+ DPRINTF("local timer[%d] start cnt\n", lt_i);
+ exynos4210_ltick_cnt_start(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Start local INT */
+ if ((value & L_TCON_INT_START) >
+ (old_val & L_TCON_INT_START)) {
+ DPRINTF("local timer[%d] start int\n", lt_i);
+ exynos4210_ltick_int_start(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Start or Stop local FRC if TCON changed */
+ if ((value & L_TCON_FRC_START) >
+ (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) {
+ DPRINTF("local timer[%d] start frc\n", lt_i);
+ exynos4210_lfrc_start(&s->l_timer[lt_i]);
+ }
+ if ((value & L_TCON_FRC_START) <
+ (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) {
+ DPRINTF("local timer[%d] stop frc\n", lt_i);
+ exynos4210_lfrc_stop(&s->l_timer[lt_i]);
+ }
+ break;
+
+ case L0_TCNTB: case L1_TCNTB:
+
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ /*
+ * TCNTB is updated to internal register only after CNT expired.
+ * Due to this we should reload timer to nearest moment when CNT is
+ * expired and then in event handler update tcntb to new TCNTB value.
+ */
+ exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer, value,
+ s->l_timer[lt_i].tick_timer.icntb);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] = value;
+
+#ifdef DEBUG_MCT
+ if (tcntb_min[lt_i] > value) {
+ tcntb_min[lt_i] = value;
+ }
+ if (tcntb_max[lt_i] < value) {
+ tcntb_max[lt_i] = value;
+ }
+ DPRINTF("local timer[%d] TCNTB write %llx; max=%x, min=%x\n",
+ lt_i, value, tcntb_max[lt_i], tcntb_min[lt_i]);
+#endif
+ break;
+
+ case L0_ICNTB: case L1_ICNTB:
+
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_ICNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] = value &
+ ~L_ICNTB_MANUAL_UPDATE;
+
+ /*
+ * We need to avoid too small values for TCNTB*ICNTB. If not, IRQ event
+ * could raise too fast disallowing QEMU to execute target code.
+ */
+ if (s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] *
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] < MCT_LT_CNT_LOW_LIMIT) {
+ if (!s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB]) {
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] =
+ MCT_LT_CNT_LOW_LIMIT;
+ } else {
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] =
+ MCT_LT_CNT_LOW_LIMIT /
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB];
+ }
+ }
+
+ if (value & L_ICNTB_MANUAL_UPDATE) {
+ exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer,
+ s->l_timer[lt_i].tick_timer.tcntb,
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB]);
+ }
+
+#ifdef DEBUG_MCT
+ if (icntb_min[lt_i] > value) {
+ icntb_min[lt_i] = value;
+ }
+ if (icntb_max[lt_i] < value) {
+ icntb_max[lt_i] = value;
+ }
+DPRINTF("local timer[%d] ICNTB write %llx; max=%x, min=%x\n\n",
+ lt_i, value, icntb_max[lt_i], icntb_min[lt_i]);
+#endif
+break;
+
+ case L0_FRCNTB: case L1_FRCNTB:
+
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ DPRINTF("local timer[%d] FRCNTB write %llx\n", lt_i, value);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_FRCCNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_FRCCNTB] = value;
+
+ break;
+
+ case L0_TCNTO: case L1_TCNTO:
+ case L0_ICNTO: case L1_ICNTO:
+ case L0_FRCNTO: case L1_FRCNTO:
+ fprintf(stderr, "\n[exynos4210.mct: write to RO register "
+ TARGET_FMT_plx "]\n\n", offset);
+ break;
+
+ case L0_INT_CSTAT: case L1_INT_CSTAT:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ DPRINTF("local timer[%d] CSTAT write %llx\n", lt_i, value);
+
+ s->l_timer[lt_i].reg.int_cstat &= ~value;
+ if (!s->l_timer[lt_i].reg.int_cstat) {
+ qemu_irq_lower(s->l_timer[lt_i].irq);
+ }
+ break;
+
+ case L0_INT_ENB: case L1_INT_ENB:
+ lt_i = GET_L_TIMER_IDX(offset);
+ old_val = s->l_timer[lt_i].reg.int_enb;
+
+ /* Raise Local timer IRQ if cstat is pending */
+ if ((value & L_INT_INTENB_ICNTEIE) > (old_val & L_INT_INTENB_ICNTEIE)) {
+ if (s->l_timer[lt_i].reg.int_cstat & L_INT_CSTAT_INTCNT) {
+ qemu_irq_raise(s->l_timer[lt_i].irq);
+ }
+ }
+
+ s->l_timer[lt_i].reg.int_enb = value;
+
+ break;
+
+ case L0_WSTAT: case L1_WSTAT:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ s->l_timer[lt_i].reg.wstat &= ~value;
+ break;
+
+ default:
+ hw_error("exynos4210.mct: bad write offset "
+ TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps exynos4210_mct_ops = {
+ .read = exynos4210_mct_read,
+ .write = exynos4210_mct_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* MCT init */
+static int exynos4210_mct_init(SysBusDevice *dev)
+{
+ int i;
+ Exynos4210MCTState *s = EXYNOS4210_MCT(dev);
+ QEMUBH *bh[2];
+
+ /* Global timer */
+ bh[0] = qemu_bh_new(exynos4210_gfrc_event, s);
+ s->g_timer.ptimer_frc = ptimer_init(bh[0]);
+ memset(&s->g_timer.reg, 0, sizeof(struct gregs));
+
+ /* Local timers */
+ for (i = 0; i < 2; i++) {
+ bh[0] = qemu_bh_new(exynos4210_ltick_event, &s->l_timer[i]);
+ bh[1] = qemu_bh_new(exynos4210_lfrc_event, &s->l_timer[i]);
+ s->l_timer[i].tick_timer.ptimer_tick = ptimer_init(bh[0]);
+ s->l_timer[i].ptimer_frc = ptimer_init(bh[1]);
+ s->l_timer[i].id = i;
+ }
+
+ /* IRQs */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ sysbus_init_irq(dev, &s->g_timer.irq[i]);
+ }
+ for (i = 0; i < 2; i++) {
+ sysbus_init_irq(dev, &s->l_timer[i].irq);
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_mct_ops, s,
+ "exynos4210-mct", MCT_SFR_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void exynos4210_mct_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_mct_init;
+ dc->reset = exynos4210_mct_reset;
+ dc->vmsd = &vmstate_exynos4210_mct_state;
+}
+
+static const TypeInfo exynos4210_mct_info = {
+ .name = TYPE_EXYNOS4210_MCT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210MCTState),
+ .class_init = exynos4210_mct_class_init,
+};
+
+static void exynos4210_mct_register_types(void)
+{
+ type_register_static(&exynos4210_mct_info);
+}
+
+type_init(exynos4210_mct_register_types)
diff --git a/hw/timer/exynos4210_pwm.c b/hw/timer/exynos4210_pwm.c
new file mode 100644
index 00000000..1c1a2b8f
--- /dev/null
+++ b/hw/timer/exynos4210_pwm.c
@@ -0,0 +1,425 @@
+/*
+ * Samsung exynos4210 Pulse Width Modulation Timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "qemu-common.h"
+#include "qemu/main-loop.h"
+#include "hw/ptimer.h"
+
+#include "hw/arm/exynos4210.h"
+
+//#define DEBUG_PWM
+
+#ifdef DEBUG_PWM
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define EXYNOS4210_PWM_TIMERS_NUM 5
+#define EXYNOS4210_PWM_REG_MEM_SIZE 0x50
+
+#define TCFG0 0x0000
+#define TCFG1 0x0004
+#define TCON 0x0008
+#define TCNTB0 0x000C
+#define TCMPB0 0x0010
+#define TCNTO0 0x0014
+#define TCNTB1 0x0018
+#define TCMPB1 0x001C
+#define TCNTO1 0x0020
+#define TCNTB2 0x0024
+#define TCMPB2 0x0028
+#define TCNTO2 0x002C
+#define TCNTB3 0x0030
+#define TCMPB3 0x0034
+#define TCNTO3 0x0038
+#define TCNTB4 0x003C
+#define TCNTO4 0x0040
+#define TINT_CSTAT 0x0044
+
+#define TCNTB(x) (0xC * (x))
+#define TCMPB(x) (0xC * (x) + 1)
+#define TCNTO(x) (0xC * (x) + 2)
+
+#define GET_PRESCALER(reg, x) (((reg) & (0xFF << (8 * (x)))) >> 8 * (x))
+#define GET_DIVIDER(reg, x) (1 << (((reg) & (0xF << (4 * (x)))) >> (4 * (x))))
+
+/*
+ * Attention! Timer4 doesn't have OUTPUT_INVERTER,
+ * so Auto Reload bit is not accessible by macros!
+ */
+#define TCON_TIMER_BASE(x) (((x) ? 1 : 0) * 4 + 4 * (x))
+#define TCON_TIMER_START(x) (1 << (TCON_TIMER_BASE(x) + 0))
+#define TCON_TIMER_MANUAL_UPD(x) (1 << (TCON_TIMER_BASE(x) + 1))
+#define TCON_TIMER_OUTPUT_INV(x) (1 << (TCON_TIMER_BASE(x) + 2))
+#define TCON_TIMER_AUTO_RELOAD(x) (1 << (TCON_TIMER_BASE(x) + 3))
+#define TCON_TIMER4_AUTO_RELOAD (1 << 22)
+
+#define TINT_CSTAT_STATUS(x) (1 << (5 + (x)))
+#define TINT_CSTAT_ENABLE(x) (1 << (x))
+
+/* timer struct */
+typedef struct {
+ uint32_t id; /* timer id */
+ qemu_irq irq; /* local timer irq */
+ uint32_t freq; /* timer frequency */
+
+ /* use ptimer.c to represent count down timer */
+ ptimer_state *ptimer; /* timer */
+
+ /* registers */
+ uint32_t reg_tcntb; /* counter register buffer */
+ uint32_t reg_tcmpb; /* compare register buffer */
+
+ struct Exynos4210PWMState *parent;
+
+} Exynos4210PWM;
+
+#define TYPE_EXYNOS4210_PWM "exynos4210.pwm"
+#define EXYNOS4210_PWM(obj) \
+ OBJECT_CHECK(Exynos4210PWMState, (obj), TYPE_EXYNOS4210_PWM)
+
+typedef struct Exynos4210PWMState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t reg_tcfg[2];
+ uint32_t reg_tcon;
+ uint32_t reg_tint_cstat;
+
+ Exynos4210PWM timer[EXYNOS4210_PWM_TIMERS_NUM];
+
+} Exynos4210PWMState;
+
+/*** VMState ***/
+static const VMStateDescription vmstate_exynos4210_pwm = {
+ .name = "exynos4210.pwm.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(id, Exynos4210PWM),
+ VMSTATE_UINT32(freq, Exynos4210PWM),
+ VMSTATE_PTIMER(ptimer, Exynos4210PWM),
+ VMSTATE_UINT32(reg_tcntb, Exynos4210PWM),
+ VMSTATE_UINT32(reg_tcmpb, Exynos4210PWM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_pwm_state = {
+ .name = "exynos4210.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2),
+ VMSTATE_UINT32(reg_tcon, Exynos4210PWMState),
+ VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState),
+ VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState,
+ EXYNOS4210_PWM_TIMERS_NUM, 0,
+ vmstate_exynos4210_pwm, Exynos4210PWM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/*
+ * PWM update frequency
+ */
+static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id)
+{
+ uint32_t freq;
+ freq = s->timer[id].freq;
+ if (id > 1) {
+ s->timer[id].freq = 24000000 /
+ ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) *
+ (GET_DIVIDER(s->reg_tcfg[1], id)));
+ } else {
+ s->timer[id].freq = 24000000 /
+ ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) *
+ (GET_DIVIDER(s->reg_tcfg[1], id)));
+ }
+
+ if (freq != s->timer[id].freq) {
+ ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq);
+ DPRINTF("freq=%dHz\n", s->timer[id].freq);
+ }
+}
+
+/*
+ * Counter tick handler
+ */
+static void exynos4210_pwm_tick(void *opaque)
+{
+ Exynos4210PWM *s = (Exynos4210PWM *)opaque;
+ Exynos4210PWMState *p = (Exynos4210PWMState *)s->parent;
+ uint32_t id = s->id;
+ bool cmp;
+
+ DPRINTF("timer %d tick\n", id);
+
+ /* set irq status */
+ p->reg_tint_cstat |= TINT_CSTAT_STATUS(id);
+
+ /* raise IRQ */
+ if (p->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) {
+ DPRINTF("timer %d IRQ\n", id);
+ qemu_irq_raise(p->timer[id].irq);
+ }
+
+ /* reload timer */
+ if (id != 4) {
+ cmp = p->reg_tcon & TCON_TIMER_AUTO_RELOAD(id);
+ } else {
+ cmp = p->reg_tcon & TCON_TIMER4_AUTO_RELOAD;
+ }
+
+ if (cmp) {
+ DPRINTF("auto reload timer %d count to %x\n", id,
+ p->timer[id].reg_tcntb);
+ ptimer_set_count(p->timer[id].ptimer, p->timer[id].reg_tcntb);
+ ptimer_run(p->timer[id].ptimer, 1);
+ } else {
+ /* stop timer, set status to STOP, see Basic Timer Operation */
+ p->reg_tcon &= ~TCON_TIMER_START(id);
+ ptimer_stop(p->timer[id].ptimer);
+ }
+}
+
+/*
+ * PWM Read
+ */
+static uint64_t exynos4210_pwm_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+ uint32_t value = 0;
+ int index;
+
+ switch (offset) {
+ case TCFG0: case TCFG1:
+ index = (offset - TCFG0) >> 2;
+ value = s->reg_tcfg[index];
+ break;
+
+ case TCON:
+ value = s->reg_tcon;
+ break;
+
+ case TCNTB0: case TCNTB1:
+ case TCNTB2: case TCNTB3: case TCNTB4:
+ index = (offset - TCNTB0) / 0xC;
+ value = s->timer[index].reg_tcntb;
+ break;
+
+ case TCMPB0: case TCMPB1:
+ case TCMPB2: case TCMPB3:
+ index = (offset - TCMPB0) / 0xC;
+ value = s->timer[index].reg_tcmpb;
+ break;
+
+ case TCNTO0: case TCNTO1:
+ case TCNTO2: case TCNTO3: case TCNTO4:
+ index = (offset == TCNTO4) ? 4 : (offset - TCNTO0) / 0xC;
+ value = ptimer_get_count(s->timer[index].ptimer);
+ break;
+
+ case TINT_CSTAT:
+ value = s->reg_tint_cstat;
+ break;
+
+ default:
+ fprintf(stderr,
+ "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+ }
+ return value;
+}
+
+/*
+ * PWM Write
+ */
+static void exynos4210_pwm_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+ int index;
+ uint32_t new_val;
+ int i;
+
+ switch (offset) {
+ case TCFG0: case TCFG1:
+ index = (offset - TCFG0) >> 2;
+ s->reg_tcfg[index] = value;
+
+ /* update timers frequencies */
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ exynos4210_pwm_update_freq(s, s->timer[i].id);
+ }
+ break;
+
+ case TCON:
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ if ((value & TCON_TIMER_MANUAL_UPD(i)) >
+ (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) {
+ /*
+ * TCNTB and TCMPB are loaded into TCNT and TCMP.
+ * Update timers.
+ */
+
+ /* this will start timer to run, this ok, because
+ * during processing start bit timer will be stopped
+ * if needed */
+ ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb);
+ DPRINTF("set timer %d count to %x\n", i,
+ s->timer[i].reg_tcntb);
+ }
+
+ if ((value & TCON_TIMER_START(i)) >
+ (s->reg_tcon & TCON_TIMER_START(i))) {
+ /* changed to start */
+ ptimer_run(s->timer[i].ptimer, 1);
+ DPRINTF("run timer %d\n", i);
+ }
+
+ if ((value & TCON_TIMER_START(i)) <
+ (s->reg_tcon & TCON_TIMER_START(i))) {
+ /* changed to stop */
+ ptimer_stop(s->timer[i].ptimer);
+ DPRINTF("stop timer %d\n", i);
+ }
+ }
+ s->reg_tcon = value;
+ break;
+
+ case TCNTB0: case TCNTB1:
+ case TCNTB2: case TCNTB3: case TCNTB4:
+ index = (offset - TCNTB0) / 0xC;
+ s->timer[index].reg_tcntb = value;
+ break;
+
+ case TCMPB0: case TCMPB1:
+ case TCMPB2: case TCMPB3:
+ index = (offset - TCMPB0) / 0xC;
+ s->timer[index].reg_tcmpb = value;
+ break;
+
+ case TINT_CSTAT:
+ new_val = (s->reg_tint_cstat & 0x3E0) + (0x1F & value);
+ new_val &= ~(0x3E0 & value);
+
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ if ((new_val & TINT_CSTAT_STATUS(i)) <
+ (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) {
+ qemu_irq_lower(s->timer[i].irq);
+ }
+ }
+
+ s->reg_tint_cstat = new_val;
+ break;
+
+ default:
+ fprintf(stderr,
+ "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+
+ }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void exynos4210_pwm_reset(DeviceState *d)
+{
+ Exynos4210PWMState *s = EXYNOS4210_PWM(d);
+ int i;
+ s->reg_tcfg[0] = 0x0101;
+ s->reg_tcfg[1] = 0x0;
+ s->reg_tcon = 0;
+ s->reg_tint_cstat = 0;
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ s->timer[i].reg_tcmpb = 0;
+ s->timer[i].reg_tcntb = 0;
+
+ exynos4210_pwm_update_freq(s, s->timer[i].id);
+ ptimer_stop(s->timer[i].ptimer);
+ }
+}
+
+static const MemoryRegionOps exynos4210_pwm_ops = {
+ .read = exynos4210_pwm_read,
+ .write = exynos4210_pwm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * PWM timer initialization
+ */
+static int exynos4210_pwm_init(SysBusDevice *dev)
+{
+ Exynos4210PWMState *s = EXYNOS4210_PWM(dev);
+ int i;
+ QEMUBH *bh;
+
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ bh = qemu_bh_new(exynos4210_pwm_tick, &s->timer[i]);
+ sysbus_init_irq(dev, &s->timer[i].irq);
+ s->timer[i].ptimer = ptimer_init(bh);
+ s->timer[i].id = i;
+ s->timer[i].parent = s;
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_pwm_ops, s,
+ "exynos4210-pwm", EXYNOS4210_PWM_REG_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void exynos4210_pwm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_pwm_init;
+ dc->reset = exynos4210_pwm_reset;
+ dc->vmsd = &vmstate_exynos4210_pwm_state;
+}
+
+static const TypeInfo exynos4210_pwm_info = {
+ .name = TYPE_EXYNOS4210_PWM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210PWMState),
+ .class_init = exynos4210_pwm_class_init,
+};
+
+static void exynos4210_pwm_register_types(void)
+{
+ type_register_static(&exynos4210_pwm_info);
+}
+
+type_init(exynos4210_pwm_register_types)
diff --git a/hw/timer/exynos4210_rtc.c b/hw/timer/exynos4210_rtc.c
new file mode 100644
index 00000000..bf2ee9f8
--- /dev/null
+++ b/hw/timer/exynos4210_rtc.c
@@ -0,0 +1,595 @@
+/*
+ * Samsung exynos4210 Real Time Clock
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * Ogurtsov Oleg <o.ogurtsov@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* Description:
+ * Register RTCCON:
+ * CLKSEL Bit[1] not used
+ * CLKOUTEN Bit[9] not used
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "qemu-common.h"
+#include "hw/ptimer.h"
+
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+
+#include "hw/arm/exynos4210.h"
+
+#define DEBUG_RTC 0
+
+#if DEBUG_RTC
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "RTC: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define EXYNOS4210_RTC_REG_MEM_SIZE 0x0100
+
+#define INTP 0x0030
+#define RTCCON 0x0040
+#define TICCNT 0x0044
+#define RTCALM 0x0050
+#define ALMSEC 0x0054
+#define ALMMIN 0x0058
+#define ALMHOUR 0x005C
+#define ALMDAY 0x0060
+#define ALMMON 0x0064
+#define ALMYEAR 0x0068
+#define BCDSEC 0x0070
+#define BCDMIN 0x0074
+#define BCDHOUR 0x0078
+#define BCDDAY 0x007C
+#define BCDDAYWEEK 0x0080
+#define BCDMON 0x0084
+#define BCDYEAR 0x0088
+#define CURTICNT 0x0090
+
+#define TICK_TIMER_ENABLE 0x0100
+#define TICNT_THRESHOLD 2
+
+
+#define RTC_ENABLE 0x0001
+
+#define INTP_TICK_ENABLE 0x0001
+#define INTP_ALM_ENABLE 0x0002
+
+#define ALARM_INT_ENABLE 0x0040
+
+#define RTC_BASE_FREQ 32768
+
+#define TYPE_EXYNOS4210_RTC "exynos4210.rtc"
+#define EXYNOS4210_RTC(obj) \
+ OBJECT_CHECK(Exynos4210RTCState, (obj), TYPE_EXYNOS4210_RTC)
+
+typedef struct Exynos4210RTCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ /* registers */
+ uint32_t reg_intp;
+ uint32_t reg_rtccon;
+ uint32_t reg_ticcnt;
+ uint32_t reg_rtcalm;
+ uint32_t reg_almsec;
+ uint32_t reg_almmin;
+ uint32_t reg_almhour;
+ uint32_t reg_almday;
+ uint32_t reg_almmon;
+ uint32_t reg_almyear;
+ uint32_t reg_curticcnt;
+
+ ptimer_state *ptimer; /* tick timer */
+ ptimer_state *ptimer_1Hz; /* clock timer */
+ uint32_t freq;
+
+ qemu_irq tick_irq; /* Time Tick Generator irq */
+ qemu_irq alm_irq; /* alarm irq */
+
+ struct tm current_tm; /* current time */
+} Exynos4210RTCState;
+
+#define TICCKSEL(value) ((value & (0x0F << 4)) >> 4)
+
+/*** VMState ***/
+static const VMStateDescription vmstate_exynos4210_rtc_state = {
+ .name = "exynos4210.rtc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg_intp, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_rtccon, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_ticcnt, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_rtcalm, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almsec, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almmin, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almhour, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almday, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almmon, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almyear, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_curticcnt, Exynos4210RTCState),
+ VMSTATE_PTIMER(ptimer, Exynos4210RTCState),
+ VMSTATE_PTIMER(ptimer_1Hz, Exynos4210RTCState),
+ VMSTATE_UINT32(freq, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_sec, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_min, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_hour, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_wday, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_mday, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_mon, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_year, Exynos4210RTCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define BCD3DIGITS(x) \
+ ((uint32_t)to_bcd((uint8_t)(x % 100)) + \
+ ((uint32_t)to_bcd((uint8_t)((x % 1000) / 100)) << 8))
+
+static void check_alarm_raise(Exynos4210RTCState *s)
+{
+ unsigned int alarm_raise = 0;
+ struct tm stm = s->current_tm;
+
+ if ((s->reg_rtcalm & 0x01) &&
+ (to_bcd((uint8_t)stm.tm_sec) == (uint8_t)s->reg_almsec)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x02) &&
+ (to_bcd((uint8_t)stm.tm_min) == (uint8_t)s->reg_almmin)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x04) &&
+ (to_bcd((uint8_t)stm.tm_hour) == (uint8_t)s->reg_almhour)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x08) &&
+ (to_bcd((uint8_t)stm.tm_mday) == (uint8_t)s->reg_almday)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x10) &&
+ (to_bcd((uint8_t)stm.tm_mon) == (uint8_t)s->reg_almmon)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x20) &&
+ (BCD3DIGITS(stm.tm_year) == s->reg_almyear)) {
+ alarm_raise = 1;
+ }
+
+ if (alarm_raise) {
+ DPRINTF("ALARM IRQ\n");
+ /* set irq status */
+ s->reg_intp |= INTP_ALM_ENABLE;
+ qemu_irq_raise(s->alm_irq);
+ }
+}
+
+/*
+ * RTC update frequency
+ * Parameters:
+ * reg_value - current RTCCON register or his new value
+ */
+static void exynos4210_rtc_update_freq(Exynos4210RTCState *s,
+ uint32_t reg_value)
+{
+ uint32_t freq;
+
+ freq = s->freq;
+ /* set frequncy for time generator */
+ s->freq = RTC_BASE_FREQ / (1 << TICCKSEL(reg_value));
+
+ if (freq != s->freq) {
+ ptimer_set_freq(s->ptimer, s->freq);
+ DPRINTF("freq=%dHz\n", s->freq);
+ }
+}
+
+/* month is between 0 and 11. */
+static int get_days_in_month(int month, int year)
+{
+ static const int days_tab[12] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ int d;
+ if ((unsigned)month >= 12) {
+ return 31;
+ }
+ d = days_tab[month];
+ if (month == 1) {
+ if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
+ d++;
+ }
+ }
+ return d;
+}
+
+/* update 'tm' to the next second */
+static void rtc_next_second(struct tm *tm)
+{
+ int days_in_month;
+
+ tm->tm_sec++;
+ if ((unsigned)tm->tm_sec >= 60) {
+ tm->tm_sec = 0;
+ tm->tm_min++;
+ if ((unsigned)tm->tm_min >= 60) {
+ tm->tm_min = 0;
+ tm->tm_hour++;
+ if ((unsigned)tm->tm_hour >= 24) {
+ tm->tm_hour = 0;
+ /* next day */
+ tm->tm_wday++;
+ if ((unsigned)tm->tm_wday >= 7) {
+ tm->tm_wday = 0;
+ }
+ days_in_month = get_days_in_month(tm->tm_mon,
+ tm->tm_year + 1900);
+ tm->tm_mday++;
+ if (tm->tm_mday < 1) {
+ tm->tm_mday = 1;
+ } else if (tm->tm_mday > days_in_month) {
+ tm->tm_mday = 1;
+ tm->tm_mon++;
+ if (tm->tm_mon >= 12) {
+ tm->tm_mon = 0;
+ tm->tm_year++;
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * tick handler
+ */
+static void exynos4210_rtc_tick(void *opaque)
+{
+ Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+ DPRINTF("TICK IRQ\n");
+ /* set irq status */
+ s->reg_intp |= INTP_TICK_ENABLE;
+ /* raise IRQ */
+ qemu_irq_raise(s->tick_irq);
+
+ /* restart timer */
+ ptimer_set_count(s->ptimer, s->reg_ticcnt);
+ ptimer_run(s->ptimer, 1);
+}
+
+/*
+ * 1Hz clock handler
+ */
+static void exynos4210_rtc_1Hz_tick(void *opaque)
+{
+ Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+ rtc_next_second(&s->current_tm);
+ /* DPRINTF("1Hz tick\n"); */
+
+ /* raise IRQ */
+ if (s->reg_rtcalm & ALARM_INT_ENABLE) {
+ check_alarm_raise(s);
+ }
+
+ ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ);
+ ptimer_run(s->ptimer_1Hz, 1);
+}
+
+/*
+ * RTC Read
+ */
+static uint64_t exynos4210_rtc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t value = 0;
+ Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+ switch (offset) {
+ case INTP:
+ value = s->reg_intp;
+ break;
+ case RTCCON:
+ value = s->reg_rtccon;
+ break;
+ case TICCNT:
+ value = s->reg_ticcnt;
+ break;
+ case RTCALM:
+ value = s->reg_rtcalm;
+ break;
+ case ALMSEC:
+ value = s->reg_almsec;
+ break;
+ case ALMMIN:
+ value = s->reg_almmin;
+ break;
+ case ALMHOUR:
+ value = s->reg_almhour;
+ break;
+ case ALMDAY:
+ value = s->reg_almday;
+ break;
+ case ALMMON:
+ value = s->reg_almmon;
+ break;
+ case ALMYEAR:
+ value = s->reg_almyear;
+ break;
+
+ case BCDSEC:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_sec);
+ break;
+ case BCDMIN:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_min);
+ break;
+ case BCDHOUR:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_hour);
+ break;
+ case BCDDAYWEEK:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_wday);
+ break;
+ case BCDDAY:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_mday);
+ break;
+ case BCDMON:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_mon + 1);
+ break;
+ case BCDYEAR:
+ value = BCD3DIGITS(s->current_tm.tm_year);
+ break;
+
+ case CURTICNT:
+ s->reg_curticcnt = ptimer_get_count(s->ptimer);
+ value = s->reg_curticcnt;
+ break;
+
+ default:
+ fprintf(stderr,
+ "[exynos4210.rtc: bad read offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+ }
+ return value;
+}
+
+/*
+ * RTC Write
+ */
+static void exynos4210_rtc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+ switch (offset) {
+ case INTP:
+ if (value & INTP_ALM_ENABLE) {
+ qemu_irq_lower(s->alm_irq);
+ s->reg_intp &= (~INTP_ALM_ENABLE);
+ }
+ if (value & INTP_TICK_ENABLE) {
+ qemu_irq_lower(s->tick_irq);
+ s->reg_intp &= (~INTP_TICK_ENABLE);
+ }
+ break;
+ case RTCCON:
+ if (value & RTC_ENABLE) {
+ exynos4210_rtc_update_freq(s, value);
+ }
+ if ((value & RTC_ENABLE) > (s->reg_rtccon & RTC_ENABLE)) {
+ /* clock timer */
+ ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ);
+ ptimer_run(s->ptimer_1Hz, 1);
+ DPRINTF("run clock timer\n");
+ }
+ if ((value & RTC_ENABLE) < (s->reg_rtccon & RTC_ENABLE)) {
+ /* tick timer */
+ ptimer_stop(s->ptimer);
+ /* clock timer */
+ ptimer_stop(s->ptimer_1Hz);
+ DPRINTF("stop all timers\n");
+ }
+ if (value & RTC_ENABLE) {
+ if ((value & TICK_TIMER_ENABLE) >
+ (s->reg_rtccon & TICK_TIMER_ENABLE) &&
+ (s->reg_ticcnt)) {
+ ptimer_set_count(s->ptimer, s->reg_ticcnt);
+ ptimer_run(s->ptimer, 1);
+ DPRINTF("run tick timer\n");
+ }
+ if ((value & TICK_TIMER_ENABLE) <
+ (s->reg_rtccon & TICK_TIMER_ENABLE)) {
+ ptimer_stop(s->ptimer);
+ }
+ }
+ s->reg_rtccon = value;
+ break;
+ case TICCNT:
+ if (value > TICNT_THRESHOLD) {
+ s->reg_ticcnt = value;
+ } else {
+ fprintf(stderr,
+ "[exynos4210.rtc: bad TICNT value %u ]\n",
+ (uint32_t)value);
+ }
+ break;
+
+ case RTCALM:
+ s->reg_rtcalm = value;
+ break;
+ case ALMSEC:
+ s->reg_almsec = (value & 0x7f);
+ break;
+ case ALMMIN:
+ s->reg_almmin = (value & 0x7f);
+ break;
+ case ALMHOUR:
+ s->reg_almhour = (value & 0x3f);
+ break;
+ case ALMDAY:
+ s->reg_almday = (value & 0x3f);
+ break;
+ case ALMMON:
+ s->reg_almmon = (value & 0x1f);
+ break;
+ case ALMYEAR:
+ s->reg_almyear = (value & 0x0fff);
+ break;
+
+ case BCDSEC:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_sec = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDMIN:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_min = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDHOUR:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_hour = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDDAYWEEK:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_wday = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDDAY:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_mday = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDMON:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_mon = (int)from_bcd((uint8_t)value) - 1;
+ }
+ break;
+ case BCDYEAR:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ /* 3 digits */
+ s->current_tm.tm_year = (int)from_bcd((uint8_t)value) +
+ (int)from_bcd((uint8_t)((value >> 8) & 0x0f)) * 100;
+ }
+ break;
+
+ default:
+ fprintf(stderr,
+ "[exynos4210.rtc: bad write offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+
+ }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void exynos4210_rtc_reset(DeviceState *d)
+{
+ Exynos4210RTCState *s = EXYNOS4210_RTC(d);
+
+ qemu_get_timedate(&s->current_tm, 0);
+
+ DPRINTF("Get time from host: %d-%d-%d %2d:%02d:%02d\n",
+ s->current_tm.tm_year, s->current_tm.tm_mon, s->current_tm.tm_mday,
+ s->current_tm.tm_hour, s->current_tm.tm_min, s->current_tm.tm_sec);
+
+ s->reg_intp = 0;
+ s->reg_rtccon = 0;
+ s->reg_ticcnt = 0;
+ s->reg_rtcalm = 0;
+ s->reg_almsec = 0;
+ s->reg_almmin = 0;
+ s->reg_almhour = 0;
+ s->reg_almday = 0;
+ s->reg_almmon = 0;
+ s->reg_almyear = 0;
+
+ s->reg_curticcnt = 0;
+
+ exynos4210_rtc_update_freq(s, s->reg_rtccon);
+ ptimer_stop(s->ptimer);
+ ptimer_stop(s->ptimer_1Hz);
+}
+
+static const MemoryRegionOps exynos4210_rtc_ops = {
+ .read = exynos4210_rtc_read,
+ .write = exynos4210_rtc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * RTC timer initialization
+ */
+static int exynos4210_rtc_init(SysBusDevice *dev)
+{
+ Exynos4210RTCState *s = EXYNOS4210_RTC(dev);
+ QEMUBH *bh;
+
+ bh = qemu_bh_new(exynos4210_rtc_tick, s);
+ s->ptimer = ptimer_init(bh);
+ ptimer_set_freq(s->ptimer, RTC_BASE_FREQ);
+ exynos4210_rtc_update_freq(s, 0);
+
+ bh = qemu_bh_new(exynos4210_rtc_1Hz_tick, s);
+ s->ptimer_1Hz = ptimer_init(bh);
+ ptimer_set_freq(s->ptimer_1Hz, RTC_BASE_FREQ);
+
+ sysbus_init_irq(dev, &s->alm_irq);
+ sysbus_init_irq(dev, &s->tick_irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_rtc_ops, s,
+ "exynos4210-rtc", EXYNOS4210_RTC_REG_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void exynos4210_rtc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_rtc_init;
+ dc->reset = exynos4210_rtc_reset;
+ dc->vmsd = &vmstate_exynos4210_rtc_state;
+}
+
+static const TypeInfo exynos4210_rtc_info = {
+ .name = TYPE_EXYNOS4210_RTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210RTCState),
+ .class_init = exynos4210_rtc_class_init,
+};
+
+static void exynos4210_rtc_register_types(void)
+{
+ type_register_static(&exynos4210_rtc_info);
+}
+
+type_init(exynos4210_rtc_register_types)
diff --git a/hw/timer/grlib_gptimer.c b/hw/timer/grlib_gptimer.c
new file mode 100644
index 00000000..d655bb2a
--- /dev/null
+++ b/hw/timer/grlib_gptimer.c
@@ -0,0 +1,411 @@
+/*
+ * QEMU GRLIB GPTimer Emulator
+ *
+ * Copyright (c) 2010-2011 AdaCore
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "qemu/timer.h"
+#include "qemu/main-loop.h"
+
+#include "trace.h"
+
+#define UNIT_REG_SIZE 16 /* Size of memory mapped regs for the unit */
+#define GPTIMER_REG_SIZE 16 /* Size of memory mapped regs for a GPTimer */
+
+#define GPTIMER_MAX_TIMERS 8
+
+/* GPTimer Config register fields */
+#define GPTIMER_ENABLE (1 << 0)
+#define GPTIMER_RESTART (1 << 1)
+#define GPTIMER_LOAD (1 << 2)
+#define GPTIMER_INT_ENABLE (1 << 3)
+#define GPTIMER_INT_PENDING (1 << 4)
+#define GPTIMER_CHAIN (1 << 5) /* Not supported */
+#define GPTIMER_DEBUG_HALT (1 << 6) /* Not supported */
+
+/* Memory mapped register offsets */
+#define SCALER_OFFSET 0x00
+#define SCALER_RELOAD_OFFSET 0x04
+#define CONFIG_OFFSET 0x08
+#define COUNTER_OFFSET 0x00
+#define COUNTER_RELOAD_OFFSET 0x04
+#define TIMER_BASE 0x10
+
+#define TYPE_GRLIB_GPTIMER "grlib,gptimer"
+#define GRLIB_GPTIMER(obj) \
+ OBJECT_CHECK(GPTimerUnit, (obj), TYPE_GRLIB_GPTIMER)
+
+typedef struct GPTimer GPTimer;
+typedef struct GPTimerUnit GPTimerUnit;
+
+struct GPTimer {
+ QEMUBH *bh;
+ struct ptimer_state *ptimer;
+
+ qemu_irq irq;
+ int id;
+ GPTimerUnit *unit;
+
+ /* registers */
+ uint32_t counter;
+ uint32_t reload;
+ uint32_t config;
+};
+
+struct GPTimerUnit {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t nr_timers; /* Number of timers available */
+ uint32_t freq_hz; /* System frequency */
+ uint32_t irq_line; /* Base irq line */
+
+ GPTimer *timers;
+
+ /* registers */
+ uint32_t scaler;
+ uint32_t reload;
+ uint32_t config;
+};
+
+static void grlib_gptimer_enable(GPTimer *timer)
+{
+ assert(timer != NULL);
+
+
+ ptimer_stop(timer->ptimer);
+
+ if (!(timer->config & GPTIMER_ENABLE)) {
+ /* Timer disabled */
+ trace_grlib_gptimer_disabled(timer->id, timer->config);
+ return;
+ }
+
+ /* ptimer is triggered when the counter reach 0 but GPTimer is triggered at
+ underflow. Set count + 1 to simulate the GPTimer behavior. */
+
+ trace_grlib_gptimer_enable(timer->id, timer->counter);
+
+ ptimer_set_count(timer->ptimer, (uint64_t)timer->counter + 1);
+ ptimer_run(timer->ptimer, 1);
+}
+
+static void grlib_gptimer_restart(GPTimer *timer)
+{
+ assert(timer != NULL);
+
+ trace_grlib_gptimer_restart(timer->id, timer->reload);
+
+ timer->counter = timer->reload;
+ grlib_gptimer_enable(timer);
+}
+
+static void grlib_gptimer_set_scaler(GPTimerUnit *unit, uint32_t scaler)
+{
+ int i = 0;
+ uint32_t value = 0;
+
+ assert(unit != NULL);
+
+ if (scaler > 0) {
+ value = unit->freq_hz / (scaler + 1);
+ } else {
+ value = unit->freq_hz;
+ }
+
+ trace_grlib_gptimer_set_scaler(scaler, value);
+
+ for (i = 0; i < unit->nr_timers; i++) {
+ ptimer_set_freq(unit->timers[i].ptimer, value);
+ }
+}
+
+static void grlib_gptimer_hit(void *opaque)
+{
+ GPTimer *timer = opaque;
+ assert(timer != NULL);
+
+ trace_grlib_gptimer_hit(timer->id);
+
+ /* Timer expired */
+
+ if (timer->config & GPTIMER_INT_ENABLE) {
+ /* Set the pending bit (only unset by write in the config register) */
+ timer->config |= GPTIMER_INT_PENDING;
+ qemu_irq_pulse(timer->irq);
+ }
+
+ if (timer->config & GPTIMER_RESTART) {
+ grlib_gptimer_restart(timer);
+ }
+}
+
+static uint64_t grlib_gptimer_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ GPTimerUnit *unit = opaque;
+ hwaddr timer_addr;
+ int id;
+ uint32_t value = 0;
+
+ addr &= 0xff;
+
+ /* Unit registers */
+ switch (addr) {
+ case SCALER_OFFSET:
+ trace_grlib_gptimer_readl(-1, addr, unit->scaler);
+ return unit->scaler;
+
+ case SCALER_RELOAD_OFFSET:
+ trace_grlib_gptimer_readl(-1, addr, unit->reload);
+ return unit->reload;
+
+ case CONFIG_OFFSET:
+ trace_grlib_gptimer_readl(-1, addr, unit->config);
+ return unit->config;
+
+ default:
+ break;
+ }
+
+ timer_addr = (addr % TIMER_BASE);
+ id = (addr - TIMER_BASE) / TIMER_BASE;
+
+ if (id >= 0 && id < unit->nr_timers) {
+
+ /* GPTimer registers */
+ switch (timer_addr) {
+ case COUNTER_OFFSET:
+ value = ptimer_get_count(unit->timers[id].ptimer);
+ trace_grlib_gptimer_readl(id, addr, value);
+ return value;
+
+ case COUNTER_RELOAD_OFFSET:
+ value = unit->timers[id].reload;
+ trace_grlib_gptimer_readl(id, addr, value);
+ return value;
+
+ case CONFIG_OFFSET:
+ trace_grlib_gptimer_readl(id, addr, unit->timers[id].config);
+ return unit->timers[id].config;
+
+ default:
+ break;
+ }
+
+ }
+
+ trace_grlib_gptimer_readl(-1, addr, 0);
+ return 0;
+}
+
+static void grlib_gptimer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ GPTimerUnit *unit = opaque;
+ hwaddr timer_addr;
+ int id;
+
+ addr &= 0xff;
+
+ /* Unit registers */
+ switch (addr) {
+ case SCALER_OFFSET:
+ value &= 0xFFFF; /* clean up the value */
+ unit->scaler = value;
+ trace_grlib_gptimer_writel(-1, addr, unit->scaler);
+ return;
+
+ case SCALER_RELOAD_OFFSET:
+ value &= 0xFFFF; /* clean up the value */
+ unit->reload = value;
+ trace_grlib_gptimer_writel(-1, addr, unit->reload);
+ grlib_gptimer_set_scaler(unit, value);
+ return;
+
+ case CONFIG_OFFSET:
+ /* Read Only (disable timer freeze not supported) */
+ trace_grlib_gptimer_writel(-1, addr, 0);
+ return;
+
+ default:
+ break;
+ }
+
+ timer_addr = (addr % TIMER_BASE);
+ id = (addr - TIMER_BASE) / TIMER_BASE;
+
+ if (id >= 0 && id < unit->nr_timers) {
+
+ /* GPTimer registers */
+ switch (timer_addr) {
+ case COUNTER_OFFSET:
+ trace_grlib_gptimer_writel(id, addr, value);
+ unit->timers[id].counter = value;
+ grlib_gptimer_enable(&unit->timers[id]);
+ return;
+
+ case COUNTER_RELOAD_OFFSET:
+ trace_grlib_gptimer_writel(id, addr, value);
+ unit->timers[id].reload = value;
+ return;
+
+ case CONFIG_OFFSET:
+ trace_grlib_gptimer_writel(id, addr, value);
+
+ if (value & GPTIMER_INT_PENDING) {
+ /* clear pending bit */
+ value &= ~GPTIMER_INT_PENDING;
+ } else {
+ /* keep pending bit */
+ value |= unit->timers[id].config & GPTIMER_INT_PENDING;
+ }
+
+ unit->timers[id].config = value;
+
+ /* gptimer_restart calls gptimer_enable, so if "enable" and "load"
+ bits are present, we just have to call restart. */
+
+ if (value & GPTIMER_LOAD) {
+ grlib_gptimer_restart(&unit->timers[id]);
+ } else if (value & GPTIMER_ENABLE) {
+ grlib_gptimer_enable(&unit->timers[id]);
+ }
+
+ /* These fields must always be read as 0 */
+ value &= ~(GPTIMER_LOAD & GPTIMER_DEBUG_HALT);
+
+ unit->timers[id].config = value;
+ return;
+
+ default:
+ break;
+ }
+
+ }
+
+ trace_grlib_gptimer_writel(-1, addr, value);
+}
+
+static const MemoryRegionOps grlib_gptimer_ops = {
+ .read = grlib_gptimer_read,
+ .write = grlib_gptimer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void grlib_gptimer_reset(DeviceState *d)
+{
+ GPTimerUnit *unit = GRLIB_GPTIMER(d);
+ int i = 0;
+
+ assert(unit != NULL);
+
+ unit->scaler = 0;
+ unit->reload = 0;
+
+ unit->config = unit->nr_timers;
+ unit->config |= unit->irq_line << 3;
+ unit->config |= 1 << 8; /* separate interrupt */
+ unit->config |= 1 << 9; /* Disable timer freeze */
+
+
+ for (i = 0; i < unit->nr_timers; i++) {
+ GPTimer *timer = &unit->timers[i];
+
+ timer->counter = 0;
+ timer->reload = 0;
+ timer->config = 0;
+ ptimer_stop(timer->ptimer);
+ ptimer_set_count(timer->ptimer, 0);
+ ptimer_set_freq(timer->ptimer, unit->freq_hz);
+ }
+}
+
+static int grlib_gptimer_init(SysBusDevice *dev)
+{
+ GPTimerUnit *unit = GRLIB_GPTIMER(dev);
+ unsigned int i;
+
+ assert(unit->nr_timers > 0);
+ assert(unit->nr_timers <= GPTIMER_MAX_TIMERS);
+
+ unit->timers = g_malloc0(sizeof unit->timers[0] * unit->nr_timers);
+
+ for (i = 0; i < unit->nr_timers; i++) {
+ GPTimer *timer = &unit->timers[i];
+
+ timer->unit = unit;
+ timer->bh = qemu_bh_new(grlib_gptimer_hit, timer);
+ timer->ptimer = ptimer_init(timer->bh);
+ timer->id = i;
+
+ /* One IRQ line for each timer */
+ sysbus_init_irq(dev, &timer->irq);
+
+ ptimer_set_freq(timer->ptimer, unit->freq_hz);
+ }
+
+ memory_region_init_io(&unit->iomem, OBJECT(unit), &grlib_gptimer_ops,
+ unit, "gptimer",
+ UNIT_REG_SIZE + GPTIMER_REG_SIZE * unit->nr_timers);
+
+ sysbus_init_mmio(dev, &unit->iomem);
+ return 0;
+}
+
+static Property grlib_gptimer_properties[] = {
+ DEFINE_PROP_UINT32("frequency", GPTimerUnit, freq_hz, 40000000),
+ DEFINE_PROP_UINT32("irq-line", GPTimerUnit, irq_line, 8),
+ DEFINE_PROP_UINT32("nr-timers", GPTimerUnit, nr_timers, 2),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void grlib_gptimer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = grlib_gptimer_init;
+ dc->reset = grlib_gptimer_reset;
+ dc->props = grlib_gptimer_properties;
+}
+
+static const TypeInfo grlib_gptimer_info = {
+ .name = TYPE_GRLIB_GPTIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(GPTimerUnit),
+ .class_init = grlib_gptimer_class_init,
+};
+
+static void grlib_gptimer_register_types(void)
+{
+ type_register_static(&grlib_gptimer_info);
+}
+
+type_init(grlib_gptimer_register_types)
diff --git a/hw/timer/hpet.c b/hw/timer/hpet.c
new file mode 100644
index 00000000..2bb62211
--- /dev/null
+++ b/hw/timer/hpet.c
@@ -0,0 +1,797 @@
+/*
+ * High Precision Event Timer emulation
+ *
+ * Copyright (c) 2007 Alexander Graf
+ * Copyright (c) 2008 IBM Corporation
+ *
+ * Authors: Beth Kon <bkon@us.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * *****************************************************************
+ *
+ * This driver attempts to emulate an HPET device in software.
+ */
+
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "ui/console.h"
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "hw/timer/hpet.h"
+#include "hw/sysbus.h"
+#include "hw/timer/mc146818rtc.h"
+#include "hw/timer/i8254.h"
+
+//#define HPET_DEBUG
+#ifdef HPET_DEBUG
+#define DPRINTF printf
+#else
+#define DPRINTF(...)
+#endif
+
+#define HPET_MSI_SUPPORT 0
+
+#define HPET(obj) OBJECT_CHECK(HPETState, (obj), TYPE_HPET)
+
+struct HPETState;
+typedef struct HPETTimer { /* timers */
+ uint8_t tn; /*timer number*/
+ QEMUTimer *qemu_timer;
+ struct HPETState *state;
+ /* Memory-mapped, software visible timer registers */
+ uint64_t config; /* configuration/cap */
+ uint64_t cmp; /* comparator */
+ uint64_t fsb; /* FSB route */
+ /* Hidden register state */
+ uint64_t period; /* Last value written to comparator */
+ uint8_t wrap_flag; /* timer pop will indicate wrap for one-shot 32-bit
+ * mode. Next pop will be actual timer expiration.
+ */
+} HPETTimer;
+
+typedef struct HPETState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint64_t hpet_offset;
+ qemu_irq irqs[HPET_NUM_IRQ_ROUTES];
+ uint32_t flags;
+ uint8_t rtc_irq_level;
+ qemu_irq pit_enabled;
+ uint8_t num_timers;
+ uint32_t intcap;
+ HPETTimer timer[HPET_MAX_TIMERS];
+
+ /* Memory-mapped, software visible registers */
+ uint64_t capability; /* capabilities */
+ uint64_t config; /* configuration */
+ uint64_t isr; /* interrupt status reg */
+ uint64_t hpet_counter; /* main counter */
+ uint8_t hpet_id; /* instance id */
+} HPETState;
+
+static uint32_t hpet_in_legacy_mode(HPETState *s)
+{
+ return s->config & HPET_CFG_LEGACY;
+}
+
+static uint32_t timer_int_route(struct HPETTimer *timer)
+{
+ return (timer->config & HPET_TN_INT_ROUTE_MASK) >> HPET_TN_INT_ROUTE_SHIFT;
+}
+
+static uint32_t timer_fsb_route(HPETTimer *t)
+{
+ return t->config & HPET_TN_FSB_ENABLE;
+}
+
+static uint32_t hpet_enabled(HPETState *s)
+{
+ return s->config & HPET_CFG_ENABLE;
+}
+
+static uint32_t timer_is_periodic(HPETTimer *t)
+{
+ return t->config & HPET_TN_PERIODIC;
+}
+
+static uint32_t timer_enabled(HPETTimer *t)
+{
+ return t->config & HPET_TN_ENABLE;
+}
+
+static uint32_t hpet_time_after(uint64_t a, uint64_t b)
+{
+ return ((int32_t)(b) - (int32_t)(a) < 0);
+}
+
+static uint32_t hpet_time_after64(uint64_t a, uint64_t b)
+{
+ return ((int64_t)(b) - (int64_t)(a) < 0);
+}
+
+static uint64_t ticks_to_ns(uint64_t value)
+{
+ return (muldiv64(value, HPET_CLK_PERIOD, FS_PER_NS));
+}
+
+static uint64_t ns_to_ticks(uint64_t value)
+{
+ return (muldiv64(value, FS_PER_NS, HPET_CLK_PERIOD));
+}
+
+static uint64_t hpet_fixup_reg(uint64_t new, uint64_t old, uint64_t mask)
+{
+ new &= mask;
+ new |= old & ~mask;
+ return new;
+}
+
+static int activating_bit(uint64_t old, uint64_t new, uint64_t mask)
+{
+ return (!(old & mask) && (new & mask));
+}
+
+static int deactivating_bit(uint64_t old, uint64_t new, uint64_t mask)
+{
+ return ((old & mask) && !(new & mask));
+}
+
+static uint64_t hpet_get_ticks(HPETState *s)
+{
+ return ns_to_ticks(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->hpet_offset);
+}
+
+/*
+ * calculate diff between comparator value and current ticks
+ */
+static inline uint64_t hpet_calculate_diff(HPETTimer *t, uint64_t current)
+{
+
+ if (t->config & HPET_TN_32BIT) {
+ uint32_t diff, cmp;
+
+ cmp = (uint32_t)t->cmp;
+ diff = cmp - (uint32_t)current;
+ diff = (int32_t)diff > 0 ? diff : (uint32_t)1;
+ return (uint64_t)diff;
+ } else {
+ uint64_t diff, cmp;
+
+ cmp = t->cmp;
+ diff = cmp - current;
+ diff = (int64_t)diff > 0 ? diff : (uint64_t)1;
+ return diff;
+ }
+}
+
+static void update_irq(struct HPETTimer *timer, int set)
+{
+ uint64_t mask;
+ HPETState *s;
+ int route;
+
+ if (timer->tn <= 1 && hpet_in_legacy_mode(timer->state)) {
+ /* if LegacyReplacementRoute bit is set, HPET specification requires
+ * timer0 be routed to IRQ0 in NON-APIC or IRQ2 in the I/O APIC,
+ * timer1 be routed to IRQ8 in NON-APIC or IRQ8 in the I/O APIC.
+ */
+ route = (timer->tn == 0) ? 0 : RTC_ISA_IRQ;
+ } else {
+ route = timer_int_route(timer);
+ }
+ s = timer->state;
+ mask = 1 << timer->tn;
+ if (!set || !timer_enabled(timer) || !hpet_enabled(timer->state)) {
+ s->isr &= ~mask;
+ if (!timer_fsb_route(timer)) {
+ /* fold the ICH PIRQ# pin's internal inversion logic into hpet */
+ if (route >= ISA_NUM_IRQS) {
+ qemu_irq_raise(s->irqs[route]);
+ } else {
+ qemu_irq_lower(s->irqs[route]);
+ }
+ }
+ } else if (timer_fsb_route(timer)) {
+ address_space_stl_le(&address_space_memory, timer->fsb >> 32,
+ timer->fsb & 0xffffffff, MEMTXATTRS_UNSPECIFIED,
+ NULL);
+ } else if (timer->config & HPET_TN_TYPE_LEVEL) {
+ s->isr |= mask;
+ /* fold the ICH PIRQ# pin's internal inversion logic into hpet */
+ if (route >= ISA_NUM_IRQS) {
+ qemu_irq_lower(s->irqs[route]);
+ } else {
+ qemu_irq_raise(s->irqs[route]);
+ }
+ } else {
+ s->isr &= ~mask;
+ qemu_irq_pulse(s->irqs[route]);
+ }
+}
+
+static void hpet_pre_save(void *opaque)
+{
+ HPETState *s = opaque;
+
+ /* save current counter value */
+ s->hpet_counter = hpet_get_ticks(s);
+}
+
+static int hpet_pre_load(void *opaque)
+{
+ HPETState *s = opaque;
+
+ /* version 1 only supports 3, later versions will load the actual value */
+ s->num_timers = HPET_MIN_TIMERS;
+ return 0;
+}
+
+static bool hpet_validate_num_timers(void *opaque, int version_id)
+{
+ HPETState *s = opaque;
+
+ if (s->num_timers < HPET_MIN_TIMERS) {
+ return false;
+ } else if (s->num_timers > HPET_MAX_TIMERS) {
+ return false;
+ }
+ return true;
+}
+
+static int hpet_post_load(void *opaque, int version_id)
+{
+ HPETState *s = opaque;
+
+ /* Recalculate the offset between the main counter and guest time */
+ s->hpet_offset = ticks_to_ns(s->hpet_counter) - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ /* Push number of timers into capability returned via HPET_ID */
+ s->capability &= ~HPET_ID_NUM_TIM_MASK;
+ s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT;
+ hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
+
+ /* Derive HPET_MSI_SUPPORT from the capability of the first timer. */
+ s->flags &= ~(1 << HPET_MSI_SUPPORT);
+ if (s->timer[0].config & HPET_TN_FSB_CAP) {
+ s->flags |= 1 << HPET_MSI_SUPPORT;
+ }
+ return 0;
+}
+
+static bool hpet_rtc_irq_level_needed(void *opaque)
+{
+ HPETState *s = opaque;
+
+ return s->rtc_irq_level != 0;
+}
+
+static const VMStateDescription vmstate_hpet_rtc_irq_level = {
+ .name = "hpet/rtc_irq_level",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = hpet_rtc_irq_level_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(rtc_irq_level, HPETState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_hpet_timer = {
+ .name = "hpet_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(tn, HPETTimer),
+ VMSTATE_UINT64(config, HPETTimer),
+ VMSTATE_UINT64(cmp, HPETTimer),
+ VMSTATE_UINT64(fsb, HPETTimer),
+ VMSTATE_UINT64(period, HPETTimer),
+ VMSTATE_UINT8(wrap_flag, HPETTimer),
+ VMSTATE_TIMER_PTR(qemu_timer, HPETTimer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_hpet = {
+ .name = "hpet",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .pre_save = hpet_pre_save,
+ .pre_load = hpet_pre_load,
+ .post_load = hpet_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(config, HPETState),
+ VMSTATE_UINT64(isr, HPETState),
+ VMSTATE_UINT64(hpet_counter, HPETState),
+ VMSTATE_UINT8_V(num_timers, HPETState, 2),
+ VMSTATE_VALIDATE("num_timers in range", hpet_validate_num_timers),
+ VMSTATE_STRUCT_VARRAY_UINT8(timer, HPETState, num_timers, 0,
+ vmstate_hpet_timer, HPETTimer),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_hpet_rtc_irq_level,
+ NULL
+ }
+};
+
+/*
+ * timer expiration callback
+ */
+static void hpet_timer(void *opaque)
+{
+ HPETTimer *t = opaque;
+ uint64_t diff;
+
+ uint64_t period = t->period;
+ uint64_t cur_tick = hpet_get_ticks(t->state);
+
+ if (timer_is_periodic(t) && period != 0) {
+ if (t->config & HPET_TN_32BIT) {
+ while (hpet_time_after(cur_tick, t->cmp)) {
+ t->cmp = (uint32_t)(t->cmp + t->period);
+ }
+ } else {
+ while (hpet_time_after64(cur_tick, t->cmp)) {
+ t->cmp += period;
+ }
+ }
+ diff = hpet_calculate_diff(t, cur_tick);
+ timer_mod(t->qemu_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (int64_t)ticks_to_ns(diff));
+ } else if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) {
+ if (t->wrap_flag) {
+ diff = hpet_calculate_diff(t, cur_tick);
+ timer_mod(t->qemu_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (int64_t)ticks_to_ns(diff));
+ t->wrap_flag = 0;
+ }
+ }
+ update_irq(t, 1);
+}
+
+static void hpet_set_timer(HPETTimer *t)
+{
+ uint64_t diff;
+ uint32_t wrap_diff; /* how many ticks until we wrap? */
+ uint64_t cur_tick = hpet_get_ticks(t->state);
+
+ /* whenever new timer is being set up, make sure wrap_flag is 0 */
+ t->wrap_flag = 0;
+ diff = hpet_calculate_diff(t, cur_tick);
+
+ /* hpet spec says in one-shot 32-bit mode, generate an interrupt when
+ * counter wraps in addition to an interrupt with comparator match.
+ */
+ if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) {
+ wrap_diff = 0xffffffff - (uint32_t)cur_tick;
+ if (wrap_diff < (uint32_t)diff) {
+ diff = wrap_diff;
+ t->wrap_flag = 1;
+ }
+ }
+ timer_mod(t->qemu_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (int64_t)ticks_to_ns(diff));
+}
+
+static void hpet_del_timer(HPETTimer *t)
+{
+ timer_del(t->qemu_timer);
+ update_irq(t, 0);
+}
+
+#ifdef HPET_DEBUG
+static uint32_t hpet_ram_readb(void *opaque, hwaddr addr)
+{
+ printf("qemu: hpet_read b at %" PRIx64 "\n", addr);
+ return 0;
+}
+
+static uint32_t hpet_ram_readw(void *opaque, hwaddr addr)
+{
+ printf("qemu: hpet_read w at %" PRIx64 "\n", addr);
+ return 0;
+}
+#endif
+
+static uint64_t hpet_ram_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ HPETState *s = opaque;
+ uint64_t cur_tick, index;
+
+ DPRINTF("qemu: Enter hpet_ram_readl at %" PRIx64 "\n", addr);
+ index = addr;
+ /*address range of all TN regs*/
+ if (index >= 0x100 && index <= 0x3ff) {
+ uint8_t timer_id = (addr - 0x100) / 0x20;
+ HPETTimer *timer = &s->timer[timer_id];
+
+ if (timer_id > s->num_timers) {
+ DPRINTF("qemu: timer id out of range\n");
+ return 0;
+ }
+
+ switch ((addr - 0x100) % 0x20) {
+ case HPET_TN_CFG:
+ return timer->config;
+ case HPET_TN_CFG + 4: // Interrupt capabilities
+ return timer->config >> 32;
+ case HPET_TN_CMP: // comparator register
+ return timer->cmp;
+ case HPET_TN_CMP + 4:
+ return timer->cmp >> 32;
+ case HPET_TN_ROUTE:
+ return timer->fsb;
+ case HPET_TN_ROUTE + 4:
+ return timer->fsb >> 32;
+ default:
+ DPRINTF("qemu: invalid hpet_ram_readl\n");
+ break;
+ }
+ } else {
+ switch (index) {
+ case HPET_ID:
+ return s->capability;
+ case HPET_PERIOD:
+ return s->capability >> 32;
+ case HPET_CFG:
+ return s->config;
+ case HPET_CFG + 4:
+ DPRINTF("qemu: invalid HPET_CFG + 4 hpet_ram_readl\n");
+ return 0;
+ case HPET_COUNTER:
+ if (hpet_enabled(s)) {
+ cur_tick = hpet_get_ticks(s);
+ } else {
+ cur_tick = s->hpet_counter;
+ }
+ DPRINTF("qemu: reading counter = %" PRIx64 "\n", cur_tick);
+ return cur_tick;
+ case HPET_COUNTER + 4:
+ if (hpet_enabled(s)) {
+ cur_tick = hpet_get_ticks(s);
+ } else {
+ cur_tick = s->hpet_counter;
+ }
+ DPRINTF("qemu: reading counter + 4 = %" PRIx64 "\n", cur_tick);
+ return cur_tick >> 32;
+ case HPET_STATUS:
+ return s->isr;
+ default:
+ DPRINTF("qemu: invalid hpet_ram_readl\n");
+ break;
+ }
+ }
+ return 0;
+}
+
+static void hpet_ram_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ int i;
+ HPETState *s = opaque;
+ uint64_t old_val, new_val, val, index;
+
+ DPRINTF("qemu: Enter hpet_ram_writel at %" PRIx64 " = %#x\n", addr, value);
+ index = addr;
+ old_val = hpet_ram_read(opaque, addr, 4);
+ new_val = value;
+
+ /*address range of all TN regs*/
+ if (index >= 0x100 && index <= 0x3ff) {
+ uint8_t timer_id = (addr - 0x100) / 0x20;
+ HPETTimer *timer = &s->timer[timer_id];
+
+ DPRINTF("qemu: hpet_ram_writel timer_id = %#x\n", timer_id);
+ if (timer_id > s->num_timers) {
+ DPRINTF("qemu: timer id out of range\n");
+ return;
+ }
+ switch ((addr - 0x100) % 0x20) {
+ case HPET_TN_CFG:
+ DPRINTF("qemu: hpet_ram_writel HPET_TN_CFG\n");
+ if (activating_bit(old_val, new_val, HPET_TN_FSB_ENABLE)) {
+ update_irq(timer, 0);
+ }
+ val = hpet_fixup_reg(new_val, old_val, HPET_TN_CFG_WRITE_MASK);
+ timer->config = (timer->config & 0xffffffff00000000ULL) | val;
+ if (new_val & HPET_TN_32BIT) {
+ timer->cmp = (uint32_t)timer->cmp;
+ timer->period = (uint32_t)timer->period;
+ }
+ if (activating_bit(old_val, new_val, HPET_TN_ENABLE) &&
+ hpet_enabled(s)) {
+ hpet_set_timer(timer);
+ } else if (deactivating_bit(old_val, new_val, HPET_TN_ENABLE)) {
+ hpet_del_timer(timer);
+ }
+ break;
+ case HPET_TN_CFG + 4: // Interrupt capabilities
+ DPRINTF("qemu: invalid HPET_TN_CFG+4 write\n");
+ break;
+ case HPET_TN_CMP: // comparator register
+ DPRINTF("qemu: hpet_ram_writel HPET_TN_CMP\n");
+ if (timer->config & HPET_TN_32BIT) {
+ new_val = (uint32_t)new_val;
+ }
+ if (!timer_is_periodic(timer)
+ || (timer->config & HPET_TN_SETVAL)) {
+ timer->cmp = (timer->cmp & 0xffffffff00000000ULL) | new_val;
+ }
+ if (timer_is_periodic(timer)) {
+ /*
+ * FIXME: Clamp period to reasonable min value?
+ * Clamp period to reasonable max value
+ */
+ new_val &= (timer->config & HPET_TN_32BIT ? ~0u : ~0ull) >> 1;
+ timer->period =
+ (timer->period & 0xffffffff00000000ULL) | new_val;
+ }
+ timer->config &= ~HPET_TN_SETVAL;
+ if (hpet_enabled(s)) {
+ hpet_set_timer(timer);
+ }
+ break;
+ case HPET_TN_CMP + 4: // comparator register high order
+ DPRINTF("qemu: hpet_ram_writel HPET_TN_CMP + 4\n");
+ if (!timer_is_periodic(timer)
+ || (timer->config & HPET_TN_SETVAL)) {
+ timer->cmp = (timer->cmp & 0xffffffffULL) | new_val << 32;
+ } else {
+ /*
+ * FIXME: Clamp period to reasonable min value?
+ * Clamp period to reasonable max value
+ */
+ new_val &= (timer->config & HPET_TN_32BIT ? ~0u : ~0ull) >> 1;
+ timer->period =
+ (timer->period & 0xffffffffULL) | new_val << 32;
+ }
+ timer->config &= ~HPET_TN_SETVAL;
+ if (hpet_enabled(s)) {
+ hpet_set_timer(timer);
+ }
+ break;
+ case HPET_TN_ROUTE:
+ timer->fsb = (timer->fsb & 0xffffffff00000000ULL) | new_val;
+ break;
+ case HPET_TN_ROUTE + 4:
+ timer->fsb = (new_val << 32) | (timer->fsb & 0xffffffff);
+ break;
+ default:
+ DPRINTF("qemu: invalid hpet_ram_writel\n");
+ break;
+ }
+ return;
+ } else {
+ switch (index) {
+ case HPET_ID:
+ return;
+ case HPET_CFG:
+ val = hpet_fixup_reg(new_val, old_val, HPET_CFG_WRITE_MASK);
+ s->config = (s->config & 0xffffffff00000000ULL) | val;
+ if (activating_bit(old_val, new_val, HPET_CFG_ENABLE)) {
+ /* Enable main counter and interrupt generation. */
+ s->hpet_offset =
+ ticks_to_ns(s->hpet_counter) - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ for (i = 0; i < s->num_timers; i++) {
+ if ((&s->timer[i])->cmp != ~0ULL) {
+ hpet_set_timer(&s->timer[i]);
+ }
+ }
+ } else if (deactivating_bit(old_val, new_val, HPET_CFG_ENABLE)) {
+ /* Halt main counter and disable interrupt generation. */
+ s->hpet_counter = hpet_get_ticks(s);
+ for (i = 0; i < s->num_timers; i++) {
+ hpet_del_timer(&s->timer[i]);
+ }
+ }
+ /* i8254 and RTC output pins are disabled
+ * when HPET is in legacy mode */
+ if (activating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
+ qemu_set_irq(s->pit_enabled, 0);
+ qemu_irq_lower(s->irqs[0]);
+ qemu_irq_lower(s->irqs[RTC_ISA_IRQ]);
+ } else if (deactivating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
+ qemu_irq_lower(s->irqs[0]);
+ qemu_set_irq(s->pit_enabled, 1);
+ qemu_set_irq(s->irqs[RTC_ISA_IRQ], s->rtc_irq_level);
+ }
+ break;
+ case HPET_CFG + 4:
+ DPRINTF("qemu: invalid HPET_CFG+4 write\n");
+ break;
+ case HPET_STATUS:
+ val = new_val & s->isr;
+ for (i = 0; i < s->num_timers; i++) {
+ if (val & (1 << i)) {
+ update_irq(&s->timer[i], 0);
+ }
+ }
+ break;
+ case HPET_COUNTER:
+ if (hpet_enabled(s)) {
+ DPRINTF("qemu: Writing counter while HPET enabled!\n");
+ }
+ s->hpet_counter =
+ (s->hpet_counter & 0xffffffff00000000ULL) | value;
+ DPRINTF("qemu: HPET counter written. ctr = %#x -> %" PRIx64 "\n",
+ value, s->hpet_counter);
+ break;
+ case HPET_COUNTER + 4:
+ if (hpet_enabled(s)) {
+ DPRINTF("qemu: Writing counter while HPET enabled!\n");
+ }
+ s->hpet_counter =
+ (s->hpet_counter & 0xffffffffULL) | (((uint64_t)value) << 32);
+ DPRINTF("qemu: HPET counter + 4 written. ctr = %#x -> %" PRIx64 "\n",
+ value, s->hpet_counter);
+ break;
+ default:
+ DPRINTF("qemu: invalid hpet_ram_writel\n");
+ break;
+ }
+ }
+}
+
+static const MemoryRegionOps hpet_ram_ops = {
+ .read = hpet_ram_read,
+ .write = hpet_ram_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void hpet_reset(DeviceState *d)
+{
+ HPETState *s = HPET(d);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(d);
+ int i;
+
+ for (i = 0; i < s->num_timers; i++) {
+ HPETTimer *timer = &s->timer[i];
+
+ hpet_del_timer(timer);
+ timer->cmp = ~0ULL;
+ timer->config = HPET_TN_PERIODIC_CAP | HPET_TN_SIZE_CAP;
+ if (s->flags & (1 << HPET_MSI_SUPPORT)) {
+ timer->config |= HPET_TN_FSB_CAP;
+ }
+ /* advertise availability of ioapic int */
+ timer->config |= (uint64_t)s->intcap << 32;
+ timer->period = 0ULL;
+ timer->wrap_flag = 0;
+ }
+
+ qemu_set_irq(s->pit_enabled, 1);
+ s->hpet_counter = 0ULL;
+ s->hpet_offset = 0ULL;
+ s->config = 0ULL;
+ hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
+ hpet_cfg.hpet[s->hpet_id].address = sbd->mmio[0].addr;
+
+ /* to document that the RTC lowers its output on reset as well */
+ s->rtc_irq_level = 0;
+}
+
+static void hpet_handle_legacy_irq(void *opaque, int n, int level)
+{
+ HPETState *s = HPET(opaque);
+
+ if (n == HPET_LEGACY_PIT_INT) {
+ if (!hpet_in_legacy_mode(s)) {
+ qemu_set_irq(s->irqs[0], level);
+ }
+ } else {
+ s->rtc_irq_level = level;
+ if (!hpet_in_legacy_mode(s)) {
+ qemu_set_irq(s->irqs[RTC_ISA_IRQ], level);
+ }
+ }
+}
+
+static void hpet_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ HPETState *s = HPET(obj);
+
+ /* HPET Area */
+ memory_region_init_io(&s->iomem, obj, &hpet_ram_ops, s, "hpet", 0x400);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void hpet_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ HPETState *s = HPET(dev);
+ int i;
+ HPETTimer *timer;
+
+ if (!s->intcap) {
+ error_printf("Hpet's intcap not initialized.\n");
+ }
+ if (hpet_cfg.count == UINT8_MAX) {
+ /* first instance */
+ hpet_cfg.count = 0;
+ }
+
+ if (hpet_cfg.count == 8) {
+ error_setg(errp, "Only 8 instances of HPET is allowed");
+ return;
+ }
+
+ s->hpet_id = hpet_cfg.count++;
+
+ for (i = 0; i < HPET_NUM_IRQ_ROUTES; i++) {
+ sysbus_init_irq(sbd, &s->irqs[i]);
+ }
+
+ if (s->num_timers < HPET_MIN_TIMERS) {
+ s->num_timers = HPET_MIN_TIMERS;
+ } else if (s->num_timers > HPET_MAX_TIMERS) {
+ s->num_timers = HPET_MAX_TIMERS;
+ }
+ for (i = 0; i < HPET_MAX_TIMERS; i++) {
+ timer = &s->timer[i];
+ timer->qemu_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hpet_timer, timer);
+ timer->tn = i;
+ timer->state = s;
+ }
+
+ /* 64-bit main counter; LegacyReplacementRoute. */
+ s->capability = 0x8086a001ULL;
+ s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT;
+ s->capability |= ((HPET_CLK_PERIOD) << 32);
+
+ qdev_init_gpio_in(dev, hpet_handle_legacy_irq, 2);
+ qdev_init_gpio_out(dev, &s->pit_enabled, 1);
+}
+
+static Property hpet_device_properties[] = {
+ DEFINE_PROP_UINT8("timers", HPETState, num_timers, HPET_MIN_TIMERS),
+ DEFINE_PROP_BIT("msi", HPETState, flags, HPET_MSI_SUPPORT, false),
+ DEFINE_PROP_UINT32(HPET_INTCAP, HPETState, intcap, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void hpet_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = hpet_realize;
+ dc->reset = hpet_reset;
+ dc->vmsd = &vmstate_hpet;
+ dc->props = hpet_device_properties;
+}
+
+static const TypeInfo hpet_device_info = {
+ .name = TYPE_HPET,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(HPETState),
+ .instance_init = hpet_init,
+ .class_init = hpet_device_class_init,
+};
+
+static void hpet_register_types(void)
+{
+ type_register_static(&hpet_device_info);
+}
+
+type_init(hpet_register_types)
diff --git a/hw/timer/i8254.c b/hw/timer/i8254.c
new file mode 100644
index 00000000..9b65a336
--- /dev/null
+++ b/hw/timer/i8254.c
@@ -0,0 +1,383 @@
+/*
+ * QEMU 8253/8254 interval timer emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/isa.h"
+#include "qemu/timer.h"
+#include "hw/timer/i8254.h"
+#include "hw/timer/i8254_internal.h"
+
+//#define DEBUG_PIT
+
+#define RW_STATE_LSB 1
+#define RW_STATE_MSB 2
+#define RW_STATE_WORD0 3
+#define RW_STATE_WORD1 4
+
+#define PIT_CLASS(class) OBJECT_CLASS_CHECK(PITClass, (class), TYPE_I8254)
+#define PIT_GET_CLASS(obj) OBJECT_GET_CLASS(PITClass, (obj), TYPE_I8254)
+
+typedef struct PITClass {
+ PITCommonClass parent_class;
+
+ DeviceRealize parent_realize;
+} PITClass;
+
+static void pit_irq_timer_update(PITChannelState *s, int64_t current_time);
+
+static int pit_get_count(PITChannelState *s)
+{
+ uint64_t d;
+ int counter;
+
+ d = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->count_load_time, PIT_FREQ,
+ get_ticks_per_sec());
+ switch(s->mode) {
+ case 0:
+ case 1:
+ case 4:
+ case 5:
+ counter = (s->count - d) & 0xffff;
+ break;
+ case 3:
+ /* XXX: may be incorrect for odd counts */
+ counter = s->count - ((2 * d) % s->count);
+ break;
+ default:
+ counter = s->count - (d % s->count);
+ break;
+ }
+ return counter;
+}
+
+/* val must be 0 or 1 */
+static void pit_set_channel_gate(PITCommonState *s, PITChannelState *sc,
+ int val)
+{
+ switch (sc->mode) {
+ default:
+ case 0:
+ case 4:
+ /* XXX: just disable/enable counting */
+ break;
+ case 1:
+ case 5:
+ if (sc->gate < val) {
+ /* restart counting on rising edge */
+ sc->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ pit_irq_timer_update(sc, sc->count_load_time);
+ }
+ break;
+ case 2:
+ case 3:
+ if (sc->gate < val) {
+ /* restart counting on rising edge */
+ sc->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ pit_irq_timer_update(sc, sc->count_load_time);
+ }
+ /* XXX: disable/enable counting */
+ break;
+ }
+ sc->gate = val;
+}
+
+static inline void pit_load_count(PITChannelState *s, int val)
+{
+ if (val == 0)
+ val = 0x10000;
+ s->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->count = val;
+ pit_irq_timer_update(s, s->count_load_time);
+}
+
+/* if already latched, do not latch again */
+static void pit_latch_count(PITChannelState *s)
+{
+ if (!s->count_latched) {
+ s->latched_count = pit_get_count(s);
+ s->count_latched = s->rw_mode;
+ }
+}
+
+static void pit_ioport_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PITCommonState *pit = opaque;
+ int channel, access;
+ PITChannelState *s;
+
+ addr &= 3;
+ if (addr == 3) {
+ channel = val >> 6;
+ if (channel == 3) {
+ /* read back command */
+ for(channel = 0; channel < 3; channel++) {
+ s = &pit->channels[channel];
+ if (val & (2 << channel)) {
+ if (!(val & 0x20)) {
+ pit_latch_count(s);
+ }
+ if (!(val & 0x10) && !s->status_latched) {
+ /* status latch */
+ /* XXX: add BCD and null count */
+ s->status =
+ (pit_get_out(s,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) << 7) |
+ (s->rw_mode << 4) |
+ (s->mode << 1) |
+ s->bcd;
+ s->status_latched = 1;
+ }
+ }
+ }
+ } else {
+ s = &pit->channels[channel];
+ access = (val >> 4) & 3;
+ if (access == 0) {
+ pit_latch_count(s);
+ } else {
+ s->rw_mode = access;
+ s->read_state = access;
+ s->write_state = access;
+
+ s->mode = (val >> 1) & 7;
+ s->bcd = val & 1;
+ /* XXX: update irq timer ? */
+ }
+ }
+ } else {
+ s = &pit->channels[addr];
+ switch(s->write_state) {
+ default:
+ case RW_STATE_LSB:
+ pit_load_count(s, val);
+ break;
+ case RW_STATE_MSB:
+ pit_load_count(s, val << 8);
+ break;
+ case RW_STATE_WORD0:
+ s->write_latch = val;
+ s->write_state = RW_STATE_WORD1;
+ break;
+ case RW_STATE_WORD1:
+ pit_load_count(s, s->write_latch | (val << 8));
+ s->write_state = RW_STATE_WORD0;
+ break;
+ }
+ }
+}
+
+static uint64_t pit_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PITCommonState *pit = opaque;
+ int ret, count;
+ PITChannelState *s;
+
+ addr &= 3;
+
+ if (addr == 3) {
+ /* Mode/Command register is write only, read is ignored */
+ return 0;
+ }
+
+ s = &pit->channels[addr];
+ if (s->status_latched) {
+ s->status_latched = 0;
+ ret = s->status;
+ } else if (s->count_latched) {
+ switch(s->count_latched) {
+ default:
+ case RW_STATE_LSB:
+ ret = s->latched_count & 0xff;
+ s->count_latched = 0;
+ break;
+ case RW_STATE_MSB:
+ ret = s->latched_count >> 8;
+ s->count_latched = 0;
+ break;
+ case RW_STATE_WORD0:
+ ret = s->latched_count & 0xff;
+ s->count_latched = RW_STATE_MSB;
+ break;
+ }
+ } else {
+ switch(s->read_state) {
+ default:
+ case RW_STATE_LSB:
+ count = pit_get_count(s);
+ ret = count & 0xff;
+ break;
+ case RW_STATE_MSB:
+ count = pit_get_count(s);
+ ret = (count >> 8) & 0xff;
+ break;
+ case RW_STATE_WORD0:
+ count = pit_get_count(s);
+ ret = count & 0xff;
+ s->read_state = RW_STATE_WORD1;
+ break;
+ case RW_STATE_WORD1:
+ count = pit_get_count(s);
+ ret = (count >> 8) & 0xff;
+ s->read_state = RW_STATE_WORD0;
+ break;
+ }
+ }
+ return ret;
+}
+
+static void pit_irq_timer_update(PITChannelState *s, int64_t current_time)
+{
+ int64_t expire_time;
+ int irq_level;
+
+ if (!s->irq_timer || s->irq_disabled) {
+ return;
+ }
+ expire_time = pit_get_next_transition_time(s, current_time);
+ irq_level = pit_get_out(s, current_time);
+ qemu_set_irq(s->irq, irq_level);
+#ifdef DEBUG_PIT
+ printf("irq_level=%d next_delay=%f\n",
+ irq_level,
+ (double)(expire_time - current_time) / get_ticks_per_sec());
+#endif
+ s->next_transition_time = expire_time;
+ if (expire_time != -1)
+ timer_mod(s->irq_timer, expire_time);
+ else
+ timer_del(s->irq_timer);
+}
+
+static void pit_irq_timer(void *opaque)
+{
+ PITChannelState *s = opaque;
+
+ pit_irq_timer_update(s, s->next_transition_time);
+}
+
+static void pit_reset(DeviceState *dev)
+{
+ PITCommonState *pit = PIT_COMMON(dev);
+ PITChannelState *s;
+
+ pit_reset_common(pit);
+
+ s = &pit->channels[0];
+ if (!s->irq_disabled) {
+ timer_mod(s->irq_timer, s->next_transition_time);
+ }
+}
+
+/* When HPET is operating in legacy mode, suppress the ignored timer IRQ,
+ * reenable it when legacy mode is left again. */
+static void pit_irq_control(void *opaque, int n, int enable)
+{
+ PITCommonState *pit = opaque;
+ PITChannelState *s = &pit->channels[0];
+
+ if (enable) {
+ s->irq_disabled = 0;
+ pit_irq_timer_update(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ } else {
+ s->irq_disabled = 1;
+ timer_del(s->irq_timer);
+ }
+}
+
+static const MemoryRegionOps pit_ioport_ops = {
+ .read = pit_ioport_read,
+ .write = pit_ioport_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void pit_post_load(PITCommonState *s)
+{
+ PITChannelState *sc = &s->channels[0];
+
+ if (sc->next_transition_time != -1) {
+ timer_mod(sc->irq_timer, sc->next_transition_time);
+ } else {
+ timer_del(sc->irq_timer);
+ }
+}
+
+static void pit_realizefn(DeviceState *dev, Error **errp)
+{
+ PITCommonState *pit = PIT_COMMON(dev);
+ PITClass *pc = PIT_GET_CLASS(dev);
+ PITChannelState *s;
+
+ s = &pit->channels[0];
+ /* the timer 0 is connected to an IRQ */
+ s->irq_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, pit_irq_timer, s);
+ qdev_init_gpio_out(dev, &s->irq, 1);
+
+ memory_region_init_io(&pit->ioports, OBJECT(pit), &pit_ioport_ops,
+ pit, "pit", 4);
+
+ qdev_init_gpio_in(dev, pit_irq_control, 1);
+
+ pc->parent_realize(dev, errp);
+}
+
+static Property pit_properties[] = {
+ DEFINE_PROP_UINT32("iobase", PITCommonState, iobase, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pit_class_initfn(ObjectClass *klass, void *data)
+{
+ PITClass *pc = PIT_CLASS(klass);
+ PITCommonClass *k = PIT_COMMON_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ pc->parent_realize = dc->realize;
+ dc->realize = pit_realizefn;
+ k->set_channel_gate = pit_set_channel_gate;
+ k->get_channel_info = pit_get_channel_info_common;
+ k->post_load = pit_post_load;
+ dc->reset = pit_reset;
+ dc->props = pit_properties;
+}
+
+static const TypeInfo pit_info = {
+ .name = TYPE_I8254,
+ .parent = TYPE_PIT_COMMON,
+ .instance_size = sizeof(PITCommonState),
+ .class_init = pit_class_initfn,
+ .class_size = sizeof(PITClass),
+};
+
+static void pit_register_types(void)
+{
+ type_register_static(&pit_info);
+}
+
+type_init(pit_register_types)
diff --git a/hw/timer/i8254_common.c b/hw/timer/i8254_common.c
new file mode 100644
index 00000000..07345f66
--- /dev/null
+++ b/hw/timer/i8254_common.c
@@ -0,0 +1,306 @@
+/*
+ * QEMU 8253/8254 - common bits of emulated and KVM kernel model
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2012 Jan Kiszka, Siemens AG
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/isa/isa.h"
+#include "qemu/timer.h"
+#include "hw/timer/i8254.h"
+#include "hw/timer/i8254_internal.h"
+
+/* val must be 0 or 1 */
+void pit_set_gate(ISADevice *dev, int channel, int val)
+{
+ PITCommonState *pit = PIT_COMMON(dev);
+ PITChannelState *s = &pit->channels[channel];
+ PITCommonClass *c = PIT_COMMON_GET_CLASS(pit);
+
+ c->set_channel_gate(pit, s, val);
+}
+
+/* get pit output bit */
+int pit_get_out(PITChannelState *s, int64_t current_time)
+{
+ uint64_t d;
+ int out;
+
+ d = muldiv64(current_time - s->count_load_time, PIT_FREQ,
+ get_ticks_per_sec());
+ switch (s->mode) {
+ default:
+ case 0:
+ out = (d >= s->count);
+ break;
+ case 1:
+ out = (d < s->count);
+ break;
+ case 2:
+ if ((d % s->count) == 0 && d != 0) {
+ out = 1;
+ } else {
+ out = 0;
+ }
+ break;
+ case 3:
+ out = (d % s->count) < ((s->count + 1) >> 1);
+ break;
+ case 4:
+ case 5:
+ out = (d == s->count);
+ break;
+ }
+ return out;
+}
+
+/* return -1 if no transition will occur. */
+int64_t pit_get_next_transition_time(PITChannelState *s, int64_t current_time)
+{
+ uint64_t d, next_time, base;
+ int period2;
+
+ d = muldiv64(current_time - s->count_load_time, PIT_FREQ,
+ get_ticks_per_sec());
+ switch (s->mode) {
+ default:
+ case 0:
+ case 1:
+ if (d < s->count) {
+ next_time = s->count;
+ } else {
+ return -1;
+ }
+ break;
+ case 2:
+ base = (d / s->count) * s->count;
+ if ((d - base) == 0 && d != 0) {
+ next_time = base + s->count;
+ } else {
+ next_time = base + s->count + 1;
+ }
+ break;
+ case 3:
+ base = (d / s->count) * s->count;
+ period2 = ((s->count + 1) >> 1);
+ if ((d - base) < period2) {
+ next_time = base + period2;
+ } else {
+ next_time = base + s->count;
+ }
+ break;
+ case 4:
+ case 5:
+ if (d < s->count) {
+ next_time = s->count;
+ } else if (d == s->count) {
+ next_time = s->count + 1;
+ } else {
+ return -1;
+ }
+ break;
+ }
+ /* convert to timer units */
+ next_time = s->count_load_time + muldiv64(next_time, get_ticks_per_sec(),
+ PIT_FREQ);
+ /* fix potential rounding problems */
+ /* XXX: better solution: use a clock at PIT_FREQ Hz */
+ if (next_time <= current_time) {
+ next_time = current_time + 1;
+ }
+ return next_time;
+}
+
+void pit_get_channel_info_common(PITCommonState *s, PITChannelState *sc,
+ PITChannelInfo *info)
+{
+ info->gate = sc->gate;
+ info->mode = sc->mode;
+ info->initial_count = sc->count;
+ info->out = pit_get_out(sc, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+}
+
+void pit_get_channel_info(ISADevice *dev, int channel, PITChannelInfo *info)
+{
+ PITCommonState *pit = PIT_COMMON(dev);
+ PITChannelState *s = &pit->channels[channel];
+ PITCommonClass *c = PIT_COMMON_GET_CLASS(pit);
+
+ c->get_channel_info(pit, s, info);
+}
+
+void pit_reset_common(PITCommonState *pit)
+{
+ PITChannelState *s;
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ s = &pit->channels[i];
+ s->mode = 3;
+ s->gate = (i != 2);
+ s->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->count = 0x10000;
+ if (i == 0 && !s->irq_disabled) {
+ s->next_transition_time =
+ pit_get_next_transition_time(s, s->count_load_time);
+ }
+ }
+}
+
+static void pit_common_realize(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ PITCommonState *pit = PIT_COMMON(dev);
+
+ isa_register_ioport(isadev, &pit->ioports, pit->iobase);
+
+ qdev_set_legacy_instance_id(dev, pit->iobase, 2);
+}
+
+static const VMStateDescription vmstate_pit_channel = {
+ .name = "pit channel",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(count, PITChannelState),
+ VMSTATE_UINT16(latched_count, PITChannelState),
+ VMSTATE_UINT8(count_latched, PITChannelState),
+ VMSTATE_UINT8(status_latched, PITChannelState),
+ VMSTATE_UINT8(status, PITChannelState),
+ VMSTATE_UINT8(read_state, PITChannelState),
+ VMSTATE_UINT8(write_state, PITChannelState),
+ VMSTATE_UINT8(write_latch, PITChannelState),
+ VMSTATE_UINT8(rw_mode, PITChannelState),
+ VMSTATE_UINT8(mode, PITChannelState),
+ VMSTATE_UINT8(bcd, PITChannelState),
+ VMSTATE_UINT8(gate, PITChannelState),
+ VMSTATE_INT64(count_load_time, PITChannelState),
+ VMSTATE_INT64(next_transition_time, PITChannelState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int pit_load_old(QEMUFile *f, void *opaque, int version_id)
+{
+ PITCommonState *pit = opaque;
+ PITCommonClass *c = PIT_COMMON_GET_CLASS(pit);
+ PITChannelState *s;
+ int i;
+
+ if (version_id != 1) {
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 3; i++) {
+ s = &pit->channels[i];
+ s->count = qemu_get_be32(f);
+ qemu_get_be16s(f, &s->latched_count);
+ qemu_get_8s(f, &s->count_latched);
+ qemu_get_8s(f, &s->status_latched);
+ qemu_get_8s(f, &s->status);
+ qemu_get_8s(f, &s->read_state);
+ qemu_get_8s(f, &s->write_state);
+ qemu_get_8s(f, &s->write_latch);
+ qemu_get_8s(f, &s->rw_mode);
+ qemu_get_8s(f, &s->mode);
+ qemu_get_8s(f, &s->bcd);
+ qemu_get_8s(f, &s->gate);
+ s->count_load_time = qemu_get_be64(f);
+ s->irq_disabled = 0;
+ if (i == 0) {
+ s->next_transition_time = qemu_get_be64(f);
+ }
+ }
+ if (c->post_load) {
+ c->post_load(pit);
+ }
+ return 0;
+}
+
+static void pit_dispatch_pre_save(void *opaque)
+{
+ PITCommonState *s = opaque;
+ PITCommonClass *c = PIT_COMMON_GET_CLASS(s);
+
+ if (c->pre_save) {
+ c->pre_save(s);
+ }
+}
+
+static int pit_dispatch_post_load(void *opaque, int version_id)
+{
+ PITCommonState *s = opaque;
+ PITCommonClass *c = PIT_COMMON_GET_CLASS(s);
+
+ if (c->post_load) {
+ c->post_load(s);
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_pit_common = {
+ .name = "i8254",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .minimum_version_id_old = 1,
+ .load_state_old = pit_load_old,
+ .pre_save = pit_dispatch_pre_save,
+ .post_load = pit_dispatch_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_V(channels[0].irq_disabled, PITCommonState, 3),
+ VMSTATE_STRUCT_ARRAY(channels, PITCommonState, 3, 2,
+ vmstate_pit_channel, PITChannelState),
+ VMSTATE_INT64(channels[0].next_transition_time,
+ PITCommonState), /* formerly irq_timer */
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pit_common_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pit_common_realize;
+ dc->vmsd = &vmstate_pit_common;
+ /*
+ * Reason: unlike ordinary ISA devices, the PIT may need to be
+ * wired to the HPET, and because of that, some wiring is always
+ * done by board code.
+ */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo pit_common_type = {
+ .name = TYPE_PIT_COMMON,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(PITCommonState),
+ .class_size = sizeof(PITCommonClass),
+ .class_init = pit_common_class_init,
+ .abstract = true,
+};
+
+static void register_devices(void)
+{
+ type_register_static(&pit_common_type);
+}
+
+type_init(register_devices);
diff --git a/hw/timer/imx_epit.c b/hw/timer/imx_epit.c
new file mode 100644
index 00000000..ffefc22f
--- /dev/null
+++ b/hw/timer/imx_epit.c
@@ -0,0 +1,410 @@
+/*
+ * IMX EPIT Timer
+ *
+ * Copyright (c) 2008 OK Labs
+ * Copyright (c) 2011 NICTA Pty Ltd
+ * Originally written by Hans Jiang
+ * Updated by Peter Chubb
+ * Updated by Jean-Christophe Dubois
+ *
+ * This code is licensed under GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/hw.h"
+#include "qemu/bitops.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "hw/sysbus.h"
+#include "hw/arm/imx.h"
+#include "qemu/main-loop.h"
+
+#define TYPE_IMX_EPIT "imx.epit"
+
+#define DEBUG_TIMER 0
+#if DEBUG_TIMER
+
+static char const *imx_epit_reg_name(uint32_t reg)
+{
+ switch (reg) {
+ case 0:
+ return "CR";
+ case 1:
+ return "SR";
+ case 2:
+ return "LR";
+ case 3:
+ return "CMP";
+ case 4:
+ return "CNT";
+ default:
+ return "[?]";
+ }
+}
+
+# define DPRINTF(fmt, args...) \
+ do { fprintf(stderr, "%s: " fmt , __func__, ##args); } while (0)
+#else
+# define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Define to 1 for messages about attempts to
+ * access unimplemented registers or similar.
+ */
+#define DEBUG_IMPLEMENTATION 1
+#if DEBUG_IMPLEMENTATION
+# define IPRINTF(fmt, args...) \
+ do { fprintf(stderr, "%s: " fmt, __func__, ##args); } while (0)
+#else
+# define IPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define IMX_EPIT(obj) \
+ OBJECT_CHECK(IMXEPITState, (obj), TYPE_IMX_EPIT)
+
+/*
+ * EPIT: Enhanced periodic interrupt timer
+ */
+
+#define CR_EN (1 << 0)
+#define CR_ENMOD (1 << 1)
+#define CR_OCIEN (1 << 2)
+#define CR_RLD (1 << 3)
+#define CR_PRESCALE_SHIFT (4)
+#define CR_PRESCALE_MASK (0xfff)
+#define CR_SWR (1 << 16)
+#define CR_IOVW (1 << 17)
+#define CR_DBGEN (1 << 18)
+#define CR_WAITEN (1 << 19)
+#define CR_DOZEN (1 << 20)
+#define CR_STOPEN (1 << 21)
+#define CR_CLKSRC_SHIFT (24)
+#define CR_CLKSRC_MASK (0x3 << CR_CLKSRC_SHIFT)
+
+#define EPIT_TIMER_MAX 0XFFFFFFFFUL
+
+/*
+ * Exact clock frequencies vary from board to board.
+ * These are typical.
+ */
+static const IMXClk imx_epit_clocks[] = {
+ 0, /* 00 disabled */
+ IPG, /* 01 ipg_clk, ~532MHz */
+ IPG, /* 10 ipg_clk_highfreq */
+ CLK_32k, /* 11 ipg_clk_32k -- ~32kHz */
+};
+
+typedef struct {
+ SysBusDevice busdev;
+ ptimer_state *timer_reload;
+ ptimer_state *timer_cmp;
+ MemoryRegion iomem;
+ DeviceState *ccm;
+
+ uint32_t cr;
+ uint32_t sr;
+ uint32_t lr;
+ uint32_t cmp;
+ uint32_t cnt;
+
+ uint32_t freq;
+ qemu_irq irq;
+} IMXEPITState;
+
+/*
+ * Update interrupt status
+ */
+static void imx_epit_update_int(IMXEPITState *s)
+{
+ if (s->sr && (s->cr & CR_OCIEN) && (s->cr & CR_EN)) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void imx_epit_set_freq(IMXEPITState *s)
+{
+ uint32_t clksrc;
+ uint32_t prescaler;
+ uint32_t freq;
+
+ clksrc = extract32(s->cr, CR_CLKSRC_SHIFT, 2);
+ prescaler = 1 + extract32(s->cr, CR_PRESCALE_SHIFT, 12);
+
+ freq = imx_clock_frequency(s->ccm, imx_epit_clocks[clksrc]) / prescaler;
+
+ s->freq = freq;
+
+ DPRINTF("Setting ptimer frequency to %u\n", freq);
+
+ if (freq) {
+ ptimer_set_freq(s->timer_reload, freq);
+ ptimer_set_freq(s->timer_cmp, freq);
+ }
+}
+
+static void imx_epit_reset(DeviceState *dev)
+{
+ IMXEPITState *s = IMX_EPIT(dev);
+
+ /*
+ * Soft reset doesn't touch some bits; hard reset clears them
+ */
+ s->cr &= (CR_EN|CR_ENMOD|CR_STOPEN|CR_DOZEN|CR_WAITEN|CR_DBGEN);
+ s->sr = 0;
+ s->lr = EPIT_TIMER_MAX;
+ s->cmp = 0;
+ s->cnt = 0;
+ /* stop both timers */
+ ptimer_stop(s->timer_cmp);
+ ptimer_stop(s->timer_reload);
+ /* compute new frequency */
+ imx_epit_set_freq(s);
+ /* init both timers to EPIT_TIMER_MAX */
+ ptimer_set_limit(s->timer_cmp, EPIT_TIMER_MAX, 1);
+ ptimer_set_limit(s->timer_reload, EPIT_TIMER_MAX, 1);
+ if (s->freq && (s->cr & CR_EN)) {
+ /* if the timer is still enabled, restart it */
+ ptimer_run(s->timer_reload, 0);
+ }
+}
+
+static uint32_t imx_epit_update_count(IMXEPITState *s)
+{
+ s->cnt = ptimer_get_count(s->timer_reload);
+
+ return s->cnt;
+}
+
+static uint64_t imx_epit_read(void *opaque, hwaddr offset, unsigned size)
+{
+ IMXEPITState *s = IMX_EPIT(opaque);
+ uint32_t reg_value = 0;
+ uint32_t reg = offset >> 2;
+
+ switch (reg) {
+ case 0: /* Control Register */
+ reg_value = s->cr;
+ break;
+
+ case 1: /* Status Register */
+ reg_value = s->sr;
+ break;
+
+ case 2: /* LR - ticks*/
+ reg_value = s->lr;
+ break;
+
+ case 3: /* CMP */
+ reg_value = s->cmp;
+ break;
+
+ case 4: /* CNT */
+ imx_epit_update_count(s);
+ reg_value = s->cnt;
+ break;
+
+ default:
+ IPRINTF("Bad offset %x\n", reg);
+ break;
+ }
+
+ DPRINTF("(%s) = 0x%08x\n", imx_epit_reg_name(reg), reg_value);
+
+ return reg_value;
+}
+
+static void imx_epit_reload_compare_timer(IMXEPITState *s)
+{
+ if ((s->cr & (CR_EN | CR_OCIEN)) == (CR_EN | CR_OCIEN)) {
+ /* if the compare feature is on and timers are running */
+ uint32_t tmp = imx_epit_update_count(s);
+ uint64_t next;
+ if (tmp > s->cmp) {
+ /* It'll fire in this round of the timer */
+ next = tmp - s->cmp;
+ } else { /* catch it next time around */
+ next = tmp - s->cmp + ((s->cr & CR_RLD) ? EPIT_TIMER_MAX : s->lr);
+ }
+ ptimer_set_count(s->timer_cmp, next);
+ }
+}
+
+static void imx_epit_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ IMXEPITState *s = IMX_EPIT(opaque);
+ uint32_t reg = offset >> 2;
+ uint64_t oldcr;
+
+ DPRINTF("(%s, value = 0x%08x)\n", imx_epit_reg_name(reg), (uint32_t)value);
+
+ switch (reg) {
+ case 0: /* CR */
+
+ oldcr = s->cr;
+ s->cr = value & 0x03ffffff;
+ if (s->cr & CR_SWR) {
+ /* handle the reset */
+ imx_epit_reset(DEVICE(s));
+ } else {
+ imx_epit_set_freq(s);
+ }
+
+ if (s->freq && (s->cr & CR_EN) && !(oldcr & CR_EN)) {
+ if (s->cr & CR_ENMOD) {
+ if (s->cr & CR_RLD) {
+ ptimer_set_limit(s->timer_reload, s->lr, 1);
+ ptimer_set_limit(s->timer_cmp, s->lr, 1);
+ } else {
+ ptimer_set_limit(s->timer_reload, EPIT_TIMER_MAX, 1);
+ ptimer_set_limit(s->timer_cmp, EPIT_TIMER_MAX, 1);
+ }
+ }
+
+ imx_epit_reload_compare_timer(s);
+ ptimer_run(s->timer_reload, 0);
+ if (s->cr & CR_OCIEN) {
+ ptimer_run(s->timer_cmp, 0);
+ } else {
+ ptimer_stop(s->timer_cmp);
+ }
+ } else if (!(s->cr & CR_EN)) {
+ /* stop both timers */
+ ptimer_stop(s->timer_reload);
+ ptimer_stop(s->timer_cmp);
+ } else if (s->cr & CR_OCIEN) {
+ if (!(oldcr & CR_OCIEN)) {
+ imx_epit_reload_compare_timer(s);
+ ptimer_run(s->timer_cmp, 0);
+ }
+ } else {
+ ptimer_stop(s->timer_cmp);
+ }
+ break;
+
+ case 1: /* SR - ACK*/
+ /* writing 1 to OCIF clear the OCIF bit */
+ if (value & 0x01) {
+ s->sr = 0;
+ imx_epit_update_int(s);
+ }
+ break;
+
+ case 2: /* LR - set ticks */
+ s->lr = value;
+
+ if (s->cr & CR_RLD) {
+ /* Also set the limit if the LRD bit is set */
+ /* If IOVW bit is set then set the timer value */
+ ptimer_set_limit(s->timer_reload, s->lr, s->cr & CR_IOVW);
+ ptimer_set_limit(s->timer_cmp, s->lr, 0);
+ } else if (s->cr & CR_IOVW) {
+ /* If IOVW bit is set then set the timer value */
+ ptimer_set_count(s->timer_reload, s->lr);
+ }
+
+ imx_epit_reload_compare_timer(s);
+ break;
+
+ case 3: /* CMP */
+ s->cmp = value;
+
+ imx_epit_reload_compare_timer(s);
+
+ break;
+
+ default:
+ IPRINTF("Bad offset %x\n", reg);
+
+ break;
+ }
+}
+static void imx_epit_cmp(void *opaque)
+{
+ IMXEPITState *s = IMX_EPIT(opaque);
+
+ DPRINTF("sr was %d\n", s->sr);
+
+ s->sr = 1;
+ imx_epit_update_int(s);
+}
+
+void imx_timerp_create(const hwaddr addr, qemu_irq irq, DeviceState *ccm)
+{
+ IMXEPITState *pp;
+ DeviceState *dev;
+
+ dev = sysbus_create_simple(TYPE_IMX_EPIT, addr, irq);
+ pp = IMX_EPIT(dev);
+ pp->ccm = ccm;
+}
+
+static const MemoryRegionOps imx_epit_ops = {
+ .read = imx_epit_read,
+ .write = imx_epit_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_imx_timer_epit = {
+ .name = "imx.epit",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cr, IMXEPITState),
+ VMSTATE_UINT32(sr, IMXEPITState),
+ VMSTATE_UINT32(lr, IMXEPITState),
+ VMSTATE_UINT32(cmp, IMXEPITState),
+ VMSTATE_UINT32(cnt, IMXEPITState),
+ VMSTATE_UINT32(freq, IMXEPITState),
+ VMSTATE_PTIMER(timer_reload, IMXEPITState),
+ VMSTATE_PTIMER(timer_cmp, IMXEPITState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void imx_epit_realize(DeviceState *dev, Error **errp)
+{
+ IMXEPITState *s = IMX_EPIT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ QEMUBH *bh;
+
+ DPRINTF("\n");
+
+ sysbus_init_irq(sbd, &s->irq);
+ memory_region_init_io(&s->iomem, OBJECT(s), &imx_epit_ops, s, TYPE_IMX_EPIT,
+ 0x00001000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->timer_reload = ptimer_init(NULL);
+
+ bh = qemu_bh_new(imx_epit_cmp, s);
+ s->timer_cmp = ptimer_init(bh);
+}
+
+static void imx_epit_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = imx_epit_realize;
+ dc->reset = imx_epit_reset;
+ dc->vmsd = &vmstate_imx_timer_epit;
+ dc->desc = "i.MX periodic timer";
+}
+
+static const TypeInfo imx_epit_info = {
+ .name = TYPE_IMX_EPIT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXEPITState),
+ .class_init = imx_epit_class_init,
+};
+
+static void imx_epit_register_types(void)
+{
+ type_register_static(&imx_epit_info);
+}
+
+type_init(imx_epit_register_types)
diff --git a/hw/timer/imx_gpt.c b/hw/timer/imx_gpt.c
new file mode 100644
index 00000000..3b310108
--- /dev/null
+++ b/hw/timer/imx_gpt.c
@@ -0,0 +1,557 @@
+/*
+ * IMX GPT Timer
+ *
+ * Copyright (c) 2008 OK Labs
+ * Copyright (c) 2011 NICTA Pty Ltd
+ * Originally written by Hans Jiang
+ * Updated by Peter Chubb
+ * Updated by Jean-Christophe Dubois
+ *
+ * This code is licensed under GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/hw.h"
+#include "qemu/bitops.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "hw/sysbus.h"
+#include "hw/arm/imx.h"
+#include "qemu/main-loop.h"
+
+#define TYPE_IMX_GPT "imx.gpt"
+
+/*
+ * Define to 1 for debug messages
+ */
+#define DEBUG_TIMER 0
+#if DEBUG_TIMER
+
+static char const *imx_gpt_reg_name(uint32_t reg)
+{
+ switch (reg) {
+ case 0:
+ return "CR";
+ case 1:
+ return "PR";
+ case 2:
+ return "SR";
+ case 3:
+ return "IR";
+ case 4:
+ return "OCR1";
+ case 5:
+ return "OCR2";
+ case 6:
+ return "OCR3";
+ case 7:
+ return "ICR1";
+ case 8:
+ return "ICR2";
+ case 9:
+ return "CNT";
+ default:
+ return "[?]";
+ }
+}
+
+# define DPRINTF(fmt, args...) \
+ do { printf("%s: " fmt , __func__, ##args); } while (0)
+#else
+# define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Define to 1 for messages about attempts to
+ * access unimplemented registers or similar.
+ */
+#define DEBUG_IMPLEMENTATION 1
+#if DEBUG_IMPLEMENTATION
+# define IPRINTF(fmt, args...) \
+ do { fprintf(stderr, "%s: " fmt, __func__, ##args); } while (0)
+#else
+# define IPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define IMX_GPT(obj) \
+ OBJECT_CHECK(IMXGPTState, (obj), TYPE_IMX_GPT)
+/*
+ * GPT : General purpose timer
+ *
+ * This timer counts up continuously while it is enabled, resetting itself
+ * to 0 when it reaches GPT_TIMER_MAX (in freerun mode) or when it
+ * reaches the value of one of the ocrX (in periodic mode).
+ */
+
+#define GPT_TIMER_MAX 0XFFFFFFFFUL
+
+/* Control register. Not all of these bits have any effect (yet) */
+#define GPT_CR_EN (1 << 0) /* GPT Enable */
+#define GPT_CR_ENMOD (1 << 1) /* GPT Enable Mode */
+#define GPT_CR_DBGEN (1 << 2) /* GPT Debug mode enable */
+#define GPT_CR_WAITEN (1 << 3) /* GPT Wait Mode Enable */
+#define GPT_CR_DOZEN (1 << 4) /* GPT Doze mode enable */
+#define GPT_CR_STOPEN (1 << 5) /* GPT Stop Mode Enable */
+#define GPT_CR_CLKSRC_SHIFT (6)
+#define GPT_CR_CLKSRC_MASK (0x7)
+
+#define GPT_CR_FRR (1 << 9) /* Freerun or Restart */
+#define GPT_CR_SWR (1 << 15) /* Software Reset */
+#define GPT_CR_IM1 (3 << 16) /* Input capture channel 1 mode (2 bits) */
+#define GPT_CR_IM2 (3 << 18) /* Input capture channel 2 mode (2 bits) */
+#define GPT_CR_OM1 (7 << 20) /* Output Compare Channel 1 Mode (3 bits) */
+#define GPT_CR_OM2 (7 << 23) /* Output Compare Channel 2 Mode (3 bits) */
+#define GPT_CR_OM3 (7 << 26) /* Output Compare Channel 3 Mode (3 bits) */
+#define GPT_CR_FO1 (1 << 29) /* Force Output Compare Channel 1 */
+#define GPT_CR_FO2 (1 << 30) /* Force Output Compare Channel 2 */
+#define GPT_CR_FO3 (1 << 31) /* Force Output Compare Channel 3 */
+
+#define GPT_SR_OF1 (1 << 0)
+#define GPT_SR_OF2 (1 << 1)
+#define GPT_SR_OF3 (1 << 2)
+#define GPT_SR_ROV (1 << 5)
+
+#define GPT_IR_OF1IE (1 << 0)
+#define GPT_IR_OF2IE (1 << 1)
+#define GPT_IR_OF3IE (1 << 2)
+#define GPT_IR_ROVIE (1 << 5)
+
+typedef struct {
+ SysBusDevice busdev;
+ ptimer_state *timer;
+ MemoryRegion iomem;
+ DeviceState *ccm;
+
+ uint32_t cr;
+ uint32_t pr;
+ uint32_t sr;
+ uint32_t ir;
+ uint32_t ocr1;
+ uint32_t ocr2;
+ uint32_t ocr3;
+ uint32_t icr1;
+ uint32_t icr2;
+ uint32_t cnt;
+
+ uint32_t next_timeout;
+ uint32_t next_int;
+
+ uint32_t freq;
+
+ qemu_irq irq;
+} IMXGPTState;
+
+static const VMStateDescription vmstate_imx_timer_gpt = {
+ .name = "imx.gpt",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cr, IMXGPTState),
+ VMSTATE_UINT32(pr, IMXGPTState),
+ VMSTATE_UINT32(sr, IMXGPTState),
+ VMSTATE_UINT32(ir, IMXGPTState),
+ VMSTATE_UINT32(ocr1, IMXGPTState),
+ VMSTATE_UINT32(ocr2, IMXGPTState),
+ VMSTATE_UINT32(ocr3, IMXGPTState),
+ VMSTATE_UINT32(icr1, IMXGPTState),
+ VMSTATE_UINT32(icr2, IMXGPTState),
+ VMSTATE_UINT32(cnt, IMXGPTState),
+ VMSTATE_UINT32(next_timeout, IMXGPTState),
+ VMSTATE_UINT32(next_int, IMXGPTState),
+ VMSTATE_UINT32(freq, IMXGPTState),
+ VMSTATE_PTIMER(timer, IMXGPTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const IMXClk imx_gpt_clocks[] = {
+ NOCLK, /* 000 No clock source */
+ IPG, /* 001 ipg_clk, 532MHz*/
+ IPG, /* 010 ipg_clk_highfreq */
+ NOCLK, /* 011 not defined */
+ CLK_32k, /* 100 ipg_clk_32k */
+ NOCLK, /* 101 not defined */
+ NOCLK, /* 110 not defined */
+ NOCLK, /* 111 not defined */
+};
+
+static void imx_gpt_set_freq(IMXGPTState *s)
+{
+ uint32_t clksrc = extract32(s->cr, GPT_CR_CLKSRC_SHIFT, 3);
+ uint32_t freq = imx_clock_frequency(s->ccm, imx_gpt_clocks[clksrc])
+ / (1 + s->pr);
+ s->freq = freq;
+
+ DPRINTF("Setting clksrc %d to frequency %d\n", clksrc, freq);
+
+ if (freq) {
+ ptimer_set_freq(s->timer, freq);
+ }
+}
+
+static void imx_gpt_update_int(IMXGPTState *s)
+{
+ if ((s->sr & s->ir) && (s->cr & GPT_CR_EN)) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static uint32_t imx_gpt_update_count(IMXGPTState *s)
+{
+ s->cnt = s->next_timeout - (uint32_t)ptimer_get_count(s->timer);
+
+ return s->cnt;
+}
+
+static inline uint32_t imx_gpt_find_limit(uint32_t count, uint32_t reg,
+ uint32_t timeout)
+{
+ if ((count < reg) && (timeout > reg)) {
+ timeout = reg;
+ }
+
+ return timeout;
+}
+
+static void imx_gpt_compute_next_timeout(IMXGPTState *s, bool event)
+{
+ uint32_t timeout = GPT_TIMER_MAX;
+ uint32_t count = 0;
+ long long limit;
+
+ if (!(s->cr & GPT_CR_EN)) {
+ /* if not enabled just return */
+ return;
+ }
+
+ if (event) {
+ /* This is a timer event */
+
+ if ((s->cr & GPT_CR_FRR) && (s->next_timeout != GPT_TIMER_MAX)) {
+ /*
+ * if we are in free running mode and we have not reached
+ * the GPT_TIMER_MAX limit, then update the count
+ */
+ count = imx_gpt_update_count(s);
+ }
+ } else {
+ /* not a timer event, then just update the count */
+
+ count = imx_gpt_update_count(s);
+ }
+
+ /* now, find the next timeout related to count */
+
+ if (s->ir & GPT_IR_OF1IE) {
+ timeout = imx_gpt_find_limit(count, s->ocr1, timeout);
+ }
+ if (s->ir & GPT_IR_OF2IE) {
+ timeout = imx_gpt_find_limit(count, s->ocr2, timeout);
+ }
+ if (s->ir & GPT_IR_OF3IE) {
+ timeout = imx_gpt_find_limit(count, s->ocr3, timeout);
+ }
+
+ /* find the next set of interrupts to raise for next timer event */
+
+ s->next_int = 0;
+ if ((s->ir & GPT_IR_OF1IE) && (timeout == s->ocr1)) {
+ s->next_int |= GPT_SR_OF1;
+ }
+ if ((s->ir & GPT_IR_OF2IE) && (timeout == s->ocr2)) {
+ s->next_int |= GPT_SR_OF2;
+ }
+ if ((s->ir & GPT_IR_OF3IE) && (timeout == s->ocr3)) {
+ s->next_int |= GPT_SR_OF3;
+ }
+ if ((s->ir & GPT_IR_ROVIE) && (timeout == GPT_TIMER_MAX)) {
+ s->next_int |= GPT_SR_ROV;
+ }
+
+ /* the new range to count down from */
+ limit = timeout - imx_gpt_update_count(s);
+
+ if (limit < 0) {
+ /*
+ * if we reach here, then QEMU is running too slow and we pass the
+ * timeout limit while computing it. Let's deliver the interrupt
+ * and compute a new limit.
+ */
+ s->sr |= s->next_int;
+
+ imx_gpt_compute_next_timeout(s, event);
+
+ imx_gpt_update_int(s);
+ } else {
+ /* New timeout value */
+ s->next_timeout = timeout;
+
+ /* reset the limit to the computed range */
+ ptimer_set_limit(s->timer, limit, 1);
+ }
+}
+
+static uint64_t imx_gpt_read(void *opaque, hwaddr offset, unsigned size)
+{
+ IMXGPTState *s = IMX_GPT(opaque);
+ uint32_t reg_value = 0;
+ uint32_t reg = offset >> 2;
+
+ switch (reg) {
+ case 0: /* Control Register */
+ reg_value = s->cr;
+ break;
+
+ case 1: /* prescaler */
+ reg_value = s->pr;
+ break;
+
+ case 2: /* Status Register */
+ reg_value = s->sr;
+ break;
+
+ case 3: /* Interrupt Register */
+ reg_value = s->ir;
+ break;
+
+ case 4: /* Output Compare Register 1 */
+ reg_value = s->ocr1;
+ break;
+
+ case 5: /* Output Compare Register 2 */
+ reg_value = s->ocr2;
+ break;
+
+ case 6: /* Output Compare Register 3 */
+ reg_value = s->ocr3;
+ break;
+
+ case 7: /* input Capture Register 1 */
+ qemu_log_mask(LOG_UNIMP, "icr1 feature is not implemented\n");
+ reg_value = s->icr1;
+ break;
+
+ case 8: /* input Capture Register 2 */
+ qemu_log_mask(LOG_UNIMP, "icr2 feature is not implemented\n");
+ reg_value = s->icr2;
+ break;
+
+ case 9: /* cnt */
+ imx_gpt_update_count(s);
+ reg_value = s->cnt;
+ break;
+
+ default:
+ IPRINTF("Bad offset %x\n", reg);
+ break;
+ }
+
+ DPRINTF("(%s) = 0x%08x\n", imx_gpt_reg_name(reg), reg_value);
+
+ return reg_value;
+}
+
+static void imx_gpt_reset(DeviceState *dev)
+{
+ IMXGPTState *s = IMX_GPT(dev);
+
+ /* stop timer */
+ ptimer_stop(s->timer);
+
+ /*
+ * Soft reset doesn't touch some bits; hard reset clears them
+ */
+ s->cr &= ~(GPT_CR_EN|GPT_CR_ENMOD|GPT_CR_STOPEN|GPT_CR_DOZEN|
+ GPT_CR_WAITEN|GPT_CR_DBGEN);
+ s->sr = 0;
+ s->pr = 0;
+ s->ir = 0;
+ s->cnt = 0;
+ s->ocr1 = GPT_TIMER_MAX;
+ s->ocr2 = GPT_TIMER_MAX;
+ s->ocr3 = GPT_TIMER_MAX;
+ s->icr1 = 0;
+ s->icr2 = 0;
+
+ s->next_timeout = GPT_TIMER_MAX;
+ s->next_int = 0;
+
+ /* compute new freq */
+ imx_gpt_set_freq(s);
+
+ /* reset the limit to GPT_TIMER_MAX */
+ ptimer_set_limit(s->timer, GPT_TIMER_MAX, 1);
+
+ /* if the timer is still enabled, restart it */
+ if (s->freq && (s->cr & GPT_CR_EN)) {
+ ptimer_run(s->timer, 1);
+ }
+}
+
+static void imx_gpt_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ IMXGPTState *s = IMX_GPT(opaque);
+ uint32_t oldreg;
+ uint32_t reg = offset >> 2;
+
+ DPRINTF("(%s, value = 0x%08x)\n", imx_gpt_reg_name(reg),
+ (uint32_t)value);
+
+ switch (reg) {
+ case 0:
+ oldreg = s->cr;
+ s->cr = value & ~0x7c14;
+ if (s->cr & GPT_CR_SWR) { /* force reset */
+ /* handle the reset */
+ imx_gpt_reset(DEVICE(s));
+ } else {
+ /* set our freq, as the source might have changed */
+ imx_gpt_set_freq(s);
+
+ if ((oldreg ^ s->cr) & GPT_CR_EN) {
+ if (s->cr & GPT_CR_EN) {
+ if (s->cr & GPT_CR_ENMOD) {
+ s->next_timeout = GPT_TIMER_MAX;
+ ptimer_set_count(s->timer, GPT_TIMER_MAX);
+ imx_gpt_compute_next_timeout(s, false);
+ }
+ ptimer_run(s->timer, 1);
+ } else {
+ /* stop timer */
+ ptimer_stop(s->timer);
+ }
+ }
+ }
+ break;
+
+ case 1: /* Prescaler */
+ s->pr = value & 0xfff;
+ imx_gpt_set_freq(s);
+ break;
+
+ case 2: /* SR */
+ s->sr &= ~(value & 0x3f);
+ imx_gpt_update_int(s);
+ break;
+
+ case 3: /* IR -- interrupt register */
+ s->ir = value & 0x3f;
+ imx_gpt_update_int(s);
+
+ imx_gpt_compute_next_timeout(s, false);
+
+ break;
+
+ case 4: /* OCR1 -- output compare register */
+ s->ocr1 = value;
+
+ /* In non-freerun mode, reset count when this register is written */
+ if (!(s->cr & GPT_CR_FRR)) {
+ s->next_timeout = GPT_TIMER_MAX;
+ ptimer_set_limit(s->timer, GPT_TIMER_MAX, 1);
+ }
+
+ /* compute the new timeout */
+ imx_gpt_compute_next_timeout(s, false);
+
+ break;
+
+ case 5: /* OCR2 -- output compare register */
+ s->ocr2 = value;
+
+ /* compute the new timeout */
+ imx_gpt_compute_next_timeout(s, false);
+
+ break;
+
+ case 6: /* OCR3 -- output compare register */
+ s->ocr3 = value;
+
+ /* compute the new timeout */
+ imx_gpt_compute_next_timeout(s, false);
+
+ break;
+
+ default:
+ IPRINTF("Bad offset %x\n", reg);
+ break;
+ }
+}
+
+static void imx_gpt_timeout(void *opaque)
+{
+ IMXGPTState *s = IMX_GPT(opaque);
+
+ DPRINTF("\n");
+
+ s->sr |= s->next_int;
+ s->next_int = 0;
+
+ imx_gpt_compute_next_timeout(s, true);
+
+ imx_gpt_update_int(s);
+
+ if (s->freq && (s->cr & GPT_CR_EN)) {
+ ptimer_run(s->timer, 1);
+ }
+}
+
+static const MemoryRegionOps imx_gpt_ops = {
+ .read = imx_gpt_read,
+ .write = imx_gpt_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+
+static void imx_gpt_realize(DeviceState *dev, Error **errp)
+{
+ IMXGPTState *s = IMX_GPT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ QEMUBH *bh;
+
+ sysbus_init_irq(sbd, &s->irq);
+ memory_region_init_io(&s->iomem, OBJECT(s), &imx_gpt_ops, s, TYPE_IMX_GPT,
+ 0x00001000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ bh = qemu_bh_new(imx_gpt_timeout, s);
+ s->timer = ptimer_init(bh);
+}
+
+void imx_timerg_create(const hwaddr addr, qemu_irq irq, DeviceState *ccm)
+{
+ IMXGPTState *pp;
+ DeviceState *dev;
+
+ dev = sysbus_create_simple(TYPE_IMX_GPT, addr, irq);
+ pp = IMX_GPT(dev);
+ pp->ccm = ccm;
+}
+
+static void imx_gpt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = imx_gpt_realize;
+ dc->reset = imx_gpt_reset;
+ dc->vmsd = &vmstate_imx_timer_gpt;
+ dc->desc = "i.MX general timer";
+}
+
+static const TypeInfo imx_gpt_info = {
+ .name = TYPE_IMX_GPT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXGPTState),
+ .class_init = imx_gpt_class_init,
+};
+
+static void imx_gpt_register_types(void)
+{
+ type_register_static(&imx_gpt_info);
+}
+
+type_init(imx_gpt_register_types)
diff --git a/hw/timer/lm32_timer.c b/hw/timer/lm32_timer.c
new file mode 100644
index 00000000..d2ab1e74
--- /dev/null
+++ b/hw/timer/lm32_timer.c
@@ -0,0 +1,235 @@
+/*
+ * QEMU model of the LatticeMico32 timer block.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.latticesemi.com/documents/mico32timer.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+
+#define DEFAULT_FREQUENCY (50*1000000)
+
+enum {
+ R_SR = 0,
+ R_CR,
+ R_PERIOD,
+ R_SNAPSHOT,
+ R_MAX
+};
+
+enum {
+ SR_TO = (1 << 0),
+ SR_RUN = (1 << 1),
+};
+
+enum {
+ CR_ITO = (1 << 0),
+ CR_CONT = (1 << 1),
+ CR_START = (1 << 2),
+ CR_STOP = (1 << 3),
+};
+
+#define TYPE_LM32_TIMER "lm32-timer"
+#define LM32_TIMER(obj) OBJECT_CHECK(LM32TimerState, (obj), TYPE_LM32_TIMER)
+
+struct LM32TimerState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ QEMUBH *bh;
+ ptimer_state *ptimer;
+
+ qemu_irq irq;
+ uint32_t freq_hz;
+
+ uint32_t regs[R_MAX];
+};
+typedef struct LM32TimerState LM32TimerState;
+
+static void timer_update_irq(LM32TimerState *s)
+{
+ int state = (s->regs[R_SR] & SR_TO) && (s->regs[R_CR] & CR_ITO);
+
+ trace_lm32_timer_irq_state(state);
+ qemu_set_irq(s->irq, state);
+}
+
+static uint64_t timer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ LM32TimerState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SR:
+ case R_CR:
+ case R_PERIOD:
+ r = s->regs[addr];
+ break;
+ case R_SNAPSHOT:
+ r = (uint32_t)ptimer_get_count(s->ptimer);
+ break;
+ default:
+ error_report("lm32_timer: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_lm32_timer_memory_read(addr << 2, r);
+ return r;
+}
+
+static void timer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ LM32TimerState *s = opaque;
+
+ trace_lm32_timer_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SR:
+ s->regs[R_SR] &= ~SR_TO;
+ break;
+ case R_CR:
+ s->regs[R_CR] = value;
+ if (s->regs[R_CR] & CR_START) {
+ ptimer_run(s->ptimer, 1);
+ }
+ if (s->regs[R_CR] & CR_STOP) {
+ ptimer_stop(s->ptimer);
+ }
+ break;
+ case R_PERIOD:
+ s->regs[R_PERIOD] = value;
+ ptimer_set_count(s->ptimer, value);
+ break;
+ case R_SNAPSHOT:
+ error_report("lm32_timer: write access to read only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ default:
+ error_report("lm32_timer: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+ timer_update_irq(s);
+}
+
+static const MemoryRegionOps timer_ops = {
+ .read = timer_read,
+ .write = timer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void timer_hit(void *opaque)
+{
+ LM32TimerState *s = opaque;
+
+ trace_lm32_timer_hit();
+
+ s->regs[R_SR] |= SR_TO;
+
+ if (s->regs[R_CR] & CR_CONT) {
+ ptimer_set_count(s->ptimer, s->regs[R_PERIOD]);
+ ptimer_run(s->ptimer, 1);
+ }
+ timer_update_irq(s);
+}
+
+static void timer_reset(DeviceState *d)
+{
+ LM32TimerState *s = LM32_TIMER(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+ ptimer_stop(s->ptimer);
+}
+
+static int lm32_timer_init(SysBusDevice *dev)
+{
+ LM32TimerState *s = LM32_TIMER(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ s->bh = qemu_bh_new(timer_hit, s);
+ s->ptimer = ptimer_init(s->bh);
+ ptimer_set_freq(s->ptimer, s->freq_hz);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &timer_ops, s,
+ "timer", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm32_timer = {
+ .name = "lm32-timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PTIMER(ptimer, LM32TimerState),
+ VMSTATE_UINT32(freq_hz, LM32TimerState),
+ VMSTATE_UINT32_ARRAY(regs, LM32TimerState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property lm32_timer_properties[] = {
+ DEFINE_PROP_UINT32("frequency", LM32TimerState, freq_hz, DEFAULT_FREQUENCY),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void lm32_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lm32_timer_init;
+ dc->reset = timer_reset;
+ dc->vmsd = &vmstate_lm32_timer;
+ dc->props = lm32_timer_properties;
+}
+
+static const TypeInfo lm32_timer_info = {
+ .name = TYPE_LM32_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LM32TimerState),
+ .class_init = lm32_timer_class_init,
+};
+
+static void lm32_timer_register_types(void)
+{
+ type_register_static(&lm32_timer_info);
+}
+
+type_init(lm32_timer_register_types)
diff --git a/hw/timer/m48t59.c b/hw/timer/m48t59.c
new file mode 100644
index 00000000..8ab683dd
--- /dev/null
+++ b/hw/timer/m48t59.c
@@ -0,0 +1,946 @@
+/*
+ * QEMU M48T59 and M48T08 NVRAM emulation for PPC PREP and Sparc platforms
+ *
+ * Copyright (c) 2003-2005, 2007 Jocelyn Mayer
+ * Copyright (c) 2013 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.
+ */
+#include "hw/hw.h"
+#include "hw/timer/m48t59.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+#include "hw/isa/isa.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_NVRAM
+
+#if defined(DEBUG_NVRAM)
+#define NVRAM_PRINTF(fmt, ...) do { printf(fmt , ## __VA_ARGS__); } while (0)
+#else
+#define NVRAM_PRINTF(fmt, ...) do { } while (0)
+#endif
+
+#define TYPE_M48TXX_SYS_BUS "sysbus-m48txx"
+#define M48TXX_SYS_BUS_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(M48txxSysBusDeviceClass, (obj), TYPE_M48TXX_SYS_BUS)
+#define M48TXX_SYS_BUS_CLASS(klass) \
+ OBJECT_CLASS_CHECK(M48txxSysBusDeviceClass, (klass), TYPE_M48TXX_SYS_BUS)
+#define M48TXX_SYS_BUS(obj) \
+ OBJECT_CHECK(M48txxSysBusState, (obj), TYPE_M48TXX_SYS_BUS)
+
+#define TYPE_M48TXX_ISA "isa-m48txx"
+#define M48TXX_ISA_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(M48txxISADeviceClass, (obj), TYPE_M48TXX_ISA)
+#define M48TXX_ISA_CLASS(klass) \
+ OBJECT_CLASS_CHECK(M48txxISADeviceClass, (klass), TYPE_M48TXX_ISA)
+#define M48TXX_ISA(obj) \
+ OBJECT_CHECK(M48txxISAState, (obj), TYPE_M48TXX_ISA)
+
+/*
+ * The M48T02, M48T08 and M48T59 chips are very similar. The newer '59 has
+ * alarm and a watchdog timer and related control registers. In the
+ * PPC platform there is also a nvram lock function.
+ */
+
+typedef struct M48txxInfo {
+ const char *isa_name;
+ const char *sysbus_name;
+ uint32_t model; /* 2 = m48t02, 8 = m48t08, 59 = m48t59 */
+ uint32_t size;
+} M48txxInfo;
+
+/*
+ * Chipset docs:
+ * http://www.st.com/stonline/products/literature/ds/2410/m48t02.pdf
+ * http://www.st.com/stonline/products/literature/ds/2411/m48t08.pdf
+ * http://www.st.com/stonline/products/literature/od/7001/m48t59y.pdf
+ */
+
+typedef struct M48t59State {
+ /* Hardware parameters */
+ qemu_irq IRQ;
+ MemoryRegion iomem;
+ uint32_t size;
+ int32_t base_year;
+ /* RTC management */
+ time_t time_offset;
+ time_t stop_time;
+ /* Alarm & watchdog */
+ struct tm alarm;
+ QEMUTimer *alrm_timer;
+ QEMUTimer *wd_timer;
+ /* NVRAM storage */
+ uint8_t *buffer;
+ /* Model parameters */
+ uint32_t model; /* 2 = m48t02, 8 = m48t08, 59 = m48t59 */
+ /* NVRAM storage */
+ uint16_t addr;
+ uint8_t lock;
+} M48t59State;
+
+typedef struct M48txxISAState {
+ ISADevice parent_obj;
+ M48t59State state;
+ uint32_t io_base;
+ MemoryRegion io;
+} M48txxISAState;
+
+typedef struct M48txxISADeviceClass {
+ ISADeviceClass parent_class;
+ M48txxInfo info;
+} M48txxISADeviceClass;
+
+typedef struct M48txxSysBusState {
+ SysBusDevice parent_obj;
+ M48t59State state;
+ MemoryRegion io;
+} M48txxSysBusState;
+
+typedef struct M48txxSysBusDeviceClass {
+ SysBusDeviceClass parent_class;
+ M48txxInfo info;
+} M48txxSysBusDeviceClass;
+
+static M48txxInfo m48txx_info[] = {
+ {
+ .sysbus_name = "sysbus-m48t02",
+ .model = 2,
+ .size = 0x800,
+ },{
+ .sysbus_name = "sysbus-m48t08",
+ .model = 8,
+ .size = 0x2000,
+ },{
+ .sysbus_name = "sysbus-m48t59",
+ .model = 59,
+ .size = 0x2000,
+ },{
+ .isa_name = "isa-m48t59",
+ .model = 59,
+ .size = 0x2000,
+ }
+};
+
+
+/* Fake timer functions */
+
+/* Alarm management */
+static void alarm_cb (void *opaque)
+{
+ struct tm tm;
+ uint64_t next_time;
+ M48t59State *NVRAM = opaque;
+
+ qemu_set_irq(NVRAM->IRQ, 1);
+ if ((NVRAM->buffer[0x1FF5] & 0x80) == 0 &&
+ (NVRAM->buffer[0x1FF4] & 0x80) == 0 &&
+ (NVRAM->buffer[0x1FF3] & 0x80) == 0 &&
+ (NVRAM->buffer[0x1FF2] & 0x80) == 0) {
+ /* Repeat once a month */
+ qemu_get_timedate(&tm, NVRAM->time_offset);
+ tm.tm_mon++;
+ if (tm.tm_mon == 13) {
+ tm.tm_mon = 1;
+ tm.tm_year++;
+ }
+ next_time = qemu_timedate_diff(&tm) - NVRAM->time_offset;
+ } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 &&
+ (NVRAM->buffer[0x1FF4] & 0x80) == 0 &&
+ (NVRAM->buffer[0x1FF3] & 0x80) == 0 &&
+ (NVRAM->buffer[0x1FF2] & 0x80) == 0) {
+ /* Repeat once a day */
+ next_time = 24 * 60 * 60;
+ } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 &&
+ (NVRAM->buffer[0x1FF4] & 0x80) != 0 &&
+ (NVRAM->buffer[0x1FF3] & 0x80) == 0 &&
+ (NVRAM->buffer[0x1FF2] & 0x80) == 0) {
+ /* Repeat once an hour */
+ next_time = 60 * 60;
+ } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 &&
+ (NVRAM->buffer[0x1FF4] & 0x80) != 0 &&
+ (NVRAM->buffer[0x1FF3] & 0x80) != 0 &&
+ (NVRAM->buffer[0x1FF2] & 0x80) == 0) {
+ /* Repeat once a minute */
+ next_time = 60;
+ } else {
+ /* Repeat once a second */
+ next_time = 1;
+ }
+ timer_mod(NVRAM->alrm_timer, qemu_clock_get_ns(rtc_clock) +
+ next_time * 1000);
+ qemu_set_irq(NVRAM->IRQ, 0);
+}
+
+static void set_alarm(M48t59State *NVRAM)
+{
+ int diff;
+ if (NVRAM->alrm_timer != NULL) {
+ timer_del(NVRAM->alrm_timer);
+ diff = qemu_timedate_diff(&NVRAM->alarm) - NVRAM->time_offset;
+ if (diff > 0)
+ timer_mod(NVRAM->alrm_timer, diff * 1000);
+ }
+}
+
+/* RTC management helpers */
+static inline void get_time(M48t59State *NVRAM, struct tm *tm)
+{
+ qemu_get_timedate(tm, NVRAM->time_offset);
+}
+
+static void set_time(M48t59State *NVRAM, struct tm *tm)
+{
+ NVRAM->time_offset = qemu_timedate_diff(tm);
+ set_alarm(NVRAM);
+}
+
+/* Watchdog management */
+static void watchdog_cb (void *opaque)
+{
+ M48t59State *NVRAM = opaque;
+
+ NVRAM->buffer[0x1FF0] |= 0x80;
+ if (NVRAM->buffer[0x1FF7] & 0x80) {
+ NVRAM->buffer[0x1FF7] = 0x00;
+ NVRAM->buffer[0x1FFC] &= ~0x40;
+ /* May it be a hw CPU Reset instead ? */
+ qemu_system_reset_request();
+ } else {
+ qemu_set_irq(NVRAM->IRQ, 1);
+ qemu_set_irq(NVRAM->IRQ, 0);
+ }
+}
+
+static void set_up_watchdog(M48t59State *NVRAM, uint8_t value)
+{
+ uint64_t interval; /* in 1/16 seconds */
+
+ NVRAM->buffer[0x1FF0] &= ~0x80;
+ if (NVRAM->wd_timer != NULL) {
+ timer_del(NVRAM->wd_timer);
+ if (value != 0) {
+ interval = (1 << (2 * (value & 0x03))) * ((value >> 2) & 0x1F);
+ timer_mod(NVRAM->wd_timer, ((uint64_t)time(NULL) * 1000) +
+ ((interval * 1000) >> 4));
+ }
+ }
+}
+
+/* Direct access to NVRAM */
+static void m48t59_write(M48t59State *NVRAM, uint32_t addr, uint32_t val)
+{
+ struct tm tm;
+ int tmp;
+
+ if (addr > 0x1FF8 && addr < 0x2000)
+ NVRAM_PRINTF("%s: 0x%08x => 0x%08x\n", __func__, addr, val);
+
+ /* check for NVRAM access */
+ if ((NVRAM->model == 2 && addr < 0x7f8) ||
+ (NVRAM->model == 8 && addr < 0x1ff8) ||
+ (NVRAM->model == 59 && addr < 0x1ff0)) {
+ goto do_write;
+ }
+
+ /* TOD access */
+ switch (addr) {
+ case 0x1FF0:
+ /* flags register : read-only */
+ break;
+ case 0x1FF1:
+ /* unused */
+ break;
+ case 0x1FF2:
+ /* alarm seconds */
+ tmp = from_bcd(val & 0x7F);
+ if (tmp >= 0 && tmp <= 59) {
+ NVRAM->alarm.tm_sec = tmp;
+ NVRAM->buffer[0x1FF2] = val;
+ set_alarm(NVRAM);
+ }
+ break;
+ case 0x1FF3:
+ /* alarm minutes */
+ tmp = from_bcd(val & 0x7F);
+ if (tmp >= 0 && tmp <= 59) {
+ NVRAM->alarm.tm_min = tmp;
+ NVRAM->buffer[0x1FF3] = val;
+ set_alarm(NVRAM);
+ }
+ break;
+ case 0x1FF4:
+ /* alarm hours */
+ tmp = from_bcd(val & 0x3F);
+ if (tmp >= 0 && tmp <= 23) {
+ NVRAM->alarm.tm_hour = tmp;
+ NVRAM->buffer[0x1FF4] = val;
+ set_alarm(NVRAM);
+ }
+ break;
+ case 0x1FF5:
+ /* alarm date */
+ tmp = from_bcd(val & 0x3F);
+ if (tmp != 0) {
+ NVRAM->alarm.tm_mday = tmp;
+ NVRAM->buffer[0x1FF5] = val;
+ set_alarm(NVRAM);
+ }
+ break;
+ case 0x1FF6:
+ /* interrupts */
+ NVRAM->buffer[0x1FF6] = val;
+ break;
+ case 0x1FF7:
+ /* watchdog */
+ NVRAM->buffer[0x1FF7] = val;
+ set_up_watchdog(NVRAM, val);
+ break;
+ case 0x1FF8:
+ case 0x07F8:
+ /* control */
+ NVRAM->buffer[addr] = (val & ~0xA0) | 0x90;
+ break;
+ case 0x1FF9:
+ case 0x07F9:
+ /* seconds (BCD) */
+ tmp = from_bcd(val & 0x7F);
+ if (tmp >= 0 && tmp <= 59) {
+ get_time(NVRAM, &tm);
+ tm.tm_sec = tmp;
+ set_time(NVRAM, &tm);
+ }
+ if ((val & 0x80) ^ (NVRAM->buffer[addr] & 0x80)) {
+ if (val & 0x80) {
+ NVRAM->stop_time = time(NULL);
+ } else {
+ NVRAM->time_offset += NVRAM->stop_time - time(NULL);
+ NVRAM->stop_time = 0;
+ }
+ }
+ NVRAM->buffer[addr] = val & 0x80;
+ break;
+ case 0x1FFA:
+ case 0x07FA:
+ /* minutes (BCD) */
+ tmp = from_bcd(val & 0x7F);
+ if (tmp >= 0 && tmp <= 59) {
+ get_time(NVRAM, &tm);
+ tm.tm_min = tmp;
+ set_time(NVRAM, &tm);
+ }
+ break;
+ case 0x1FFB:
+ case 0x07FB:
+ /* hours (BCD) */
+ tmp = from_bcd(val & 0x3F);
+ if (tmp >= 0 && tmp <= 23) {
+ get_time(NVRAM, &tm);
+ tm.tm_hour = tmp;
+ set_time(NVRAM, &tm);
+ }
+ break;
+ case 0x1FFC:
+ case 0x07FC:
+ /* day of the week / century */
+ tmp = from_bcd(val & 0x07);
+ get_time(NVRAM, &tm);
+ tm.tm_wday = tmp;
+ set_time(NVRAM, &tm);
+ NVRAM->buffer[addr] = val & 0x40;
+ break;
+ case 0x1FFD:
+ case 0x07FD:
+ /* date (BCD) */
+ tmp = from_bcd(val & 0x3F);
+ if (tmp != 0) {
+ get_time(NVRAM, &tm);
+ tm.tm_mday = tmp;
+ set_time(NVRAM, &tm);
+ }
+ break;
+ case 0x1FFE:
+ case 0x07FE:
+ /* month */
+ tmp = from_bcd(val & 0x1F);
+ if (tmp >= 1 && tmp <= 12) {
+ get_time(NVRAM, &tm);
+ tm.tm_mon = tmp - 1;
+ set_time(NVRAM, &tm);
+ }
+ break;
+ case 0x1FFF:
+ case 0x07FF:
+ /* year */
+ tmp = from_bcd(val);
+ if (tmp >= 0 && tmp <= 99) {
+ get_time(NVRAM, &tm);
+ tm.tm_year = from_bcd(val) + NVRAM->base_year - 1900;
+ set_time(NVRAM, &tm);
+ }
+ break;
+ default:
+ /* Check lock registers state */
+ if (addr >= 0x20 && addr <= 0x2F && (NVRAM->lock & 1))
+ break;
+ if (addr >= 0x30 && addr <= 0x3F && (NVRAM->lock & 2))
+ break;
+ do_write:
+ if (addr < NVRAM->size) {
+ NVRAM->buffer[addr] = val & 0xFF;
+ }
+ break;
+ }
+}
+
+static uint32_t m48t59_read(M48t59State *NVRAM, uint32_t addr)
+{
+ struct tm tm;
+ uint32_t retval = 0xFF;
+
+ /* check for NVRAM access */
+ if ((NVRAM->model == 2 && addr < 0x078f) ||
+ (NVRAM->model == 8 && addr < 0x1ff8) ||
+ (NVRAM->model == 59 && addr < 0x1ff0)) {
+ goto do_read;
+ }
+
+ /* TOD access */
+ switch (addr) {
+ case 0x1FF0:
+ /* flags register */
+ goto do_read;
+ case 0x1FF1:
+ /* unused */
+ retval = 0;
+ break;
+ case 0x1FF2:
+ /* alarm seconds */
+ goto do_read;
+ case 0x1FF3:
+ /* alarm minutes */
+ goto do_read;
+ case 0x1FF4:
+ /* alarm hours */
+ goto do_read;
+ case 0x1FF5:
+ /* alarm date */
+ goto do_read;
+ case 0x1FF6:
+ /* interrupts */
+ goto do_read;
+ case 0x1FF7:
+ /* A read resets the watchdog */
+ set_up_watchdog(NVRAM, NVRAM->buffer[0x1FF7]);
+ goto do_read;
+ case 0x1FF8:
+ case 0x07F8:
+ /* control */
+ goto do_read;
+ case 0x1FF9:
+ case 0x07F9:
+ /* seconds (BCD) */
+ get_time(NVRAM, &tm);
+ retval = (NVRAM->buffer[addr] & 0x80) | to_bcd(tm.tm_sec);
+ break;
+ case 0x1FFA:
+ case 0x07FA:
+ /* minutes (BCD) */
+ get_time(NVRAM, &tm);
+ retval = to_bcd(tm.tm_min);
+ break;
+ case 0x1FFB:
+ case 0x07FB:
+ /* hours (BCD) */
+ get_time(NVRAM, &tm);
+ retval = to_bcd(tm.tm_hour);
+ break;
+ case 0x1FFC:
+ case 0x07FC:
+ /* day of the week / century */
+ get_time(NVRAM, &tm);
+ retval = NVRAM->buffer[addr] | tm.tm_wday;
+ break;
+ case 0x1FFD:
+ case 0x07FD:
+ /* date */
+ get_time(NVRAM, &tm);
+ retval = to_bcd(tm.tm_mday);
+ break;
+ case 0x1FFE:
+ case 0x07FE:
+ /* month */
+ get_time(NVRAM, &tm);
+ retval = to_bcd(tm.tm_mon + 1);
+ break;
+ case 0x1FFF:
+ case 0x07FF:
+ /* year */
+ get_time(NVRAM, &tm);
+ retval = to_bcd((tm.tm_year + 1900 - NVRAM->base_year) % 100);
+ break;
+ default:
+ /* Check lock registers state */
+ if (addr >= 0x20 && addr <= 0x2F && (NVRAM->lock & 1))
+ break;
+ if (addr >= 0x30 && addr <= 0x3F && (NVRAM->lock & 2))
+ break;
+ do_read:
+ if (addr < NVRAM->size) {
+ retval = NVRAM->buffer[addr];
+ }
+ break;
+ }
+ if (addr > 0x1FF9 && addr < 0x2000)
+ NVRAM_PRINTF("%s: 0x%08x <= 0x%08x\n", __func__, addr, retval);
+
+ return retval;
+}
+
+static void m48t59_toggle_lock(M48t59State *NVRAM, int lock)
+{
+ NVRAM->lock ^= 1 << lock;
+}
+
+/* IO access to NVRAM */
+static void NVRAM_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ M48t59State *NVRAM = opaque;
+
+ NVRAM_PRINTF("%s: 0x%08x => 0x%08x\n", __func__, addr, val);
+ switch (addr) {
+ case 0:
+ NVRAM->addr &= ~0x00FF;
+ NVRAM->addr |= val;
+ break;
+ case 1:
+ NVRAM->addr &= ~0xFF00;
+ NVRAM->addr |= val << 8;
+ break;
+ case 3:
+ m48t59_write(NVRAM, NVRAM->addr, val);
+ NVRAM->addr = 0x0000;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t NVRAM_readb(void *opaque, hwaddr addr, unsigned size)
+{
+ M48t59State *NVRAM = opaque;
+ uint32_t retval;
+
+ switch (addr) {
+ case 3:
+ retval = m48t59_read(NVRAM, NVRAM->addr);
+ break;
+ default:
+ retval = -1;
+ break;
+ }
+ NVRAM_PRINTF("%s: 0x%08x <= 0x%08x\n", __func__, addr, retval);
+
+ return retval;
+}
+
+static void nvram_writeb (void *opaque, hwaddr addr, uint32_t value)
+{
+ M48t59State *NVRAM = opaque;
+
+ m48t59_write(NVRAM, addr, value & 0xff);
+}
+
+static void nvram_writew (void *opaque, hwaddr addr, uint32_t value)
+{
+ M48t59State *NVRAM = opaque;
+
+ m48t59_write(NVRAM, addr, (value >> 8) & 0xff);
+ m48t59_write(NVRAM, addr + 1, value & 0xff);
+}
+
+static void nvram_writel (void *opaque, hwaddr addr, uint32_t value)
+{
+ M48t59State *NVRAM = opaque;
+
+ m48t59_write(NVRAM, addr, (value >> 24) & 0xff);
+ m48t59_write(NVRAM, addr + 1, (value >> 16) & 0xff);
+ m48t59_write(NVRAM, addr + 2, (value >> 8) & 0xff);
+ m48t59_write(NVRAM, addr + 3, value & 0xff);
+}
+
+static uint32_t nvram_readb (void *opaque, hwaddr addr)
+{
+ M48t59State *NVRAM = opaque;
+ uint32_t retval;
+
+ retval = m48t59_read(NVRAM, addr);
+ return retval;
+}
+
+static uint32_t nvram_readw (void *opaque, hwaddr addr)
+{
+ M48t59State *NVRAM = opaque;
+ uint32_t retval;
+
+ retval = m48t59_read(NVRAM, addr) << 8;
+ retval |= m48t59_read(NVRAM, addr + 1);
+ return retval;
+}
+
+static uint32_t nvram_readl (void *opaque, hwaddr addr)
+{
+ M48t59State *NVRAM = opaque;
+ uint32_t retval;
+
+ retval = m48t59_read(NVRAM, addr) << 24;
+ retval |= m48t59_read(NVRAM, addr + 1) << 16;
+ retval |= m48t59_read(NVRAM, addr + 2) << 8;
+ retval |= m48t59_read(NVRAM, addr + 3);
+ return retval;
+}
+
+static const MemoryRegionOps nvram_ops = {
+ .old_mmio = {
+ .read = { nvram_readb, nvram_readw, nvram_readl, },
+ .write = { nvram_writeb, nvram_writew, nvram_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_m48t59 = {
+ .name = "m48t59",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(lock, M48t59State),
+ VMSTATE_UINT16(addr, M48t59State),
+ VMSTATE_VBUFFER_UINT32(buffer, M48t59State, 0, NULL, 0, size),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void m48t59_reset_common(M48t59State *NVRAM)
+{
+ NVRAM->addr = 0;
+ NVRAM->lock = 0;
+ if (NVRAM->alrm_timer != NULL)
+ timer_del(NVRAM->alrm_timer);
+
+ if (NVRAM->wd_timer != NULL)
+ timer_del(NVRAM->wd_timer);
+}
+
+static void m48t59_reset_isa(DeviceState *d)
+{
+ M48txxISAState *isa = M48TXX_ISA(d);
+ M48t59State *NVRAM = &isa->state;
+
+ m48t59_reset_common(NVRAM);
+}
+
+static void m48t59_reset_sysbus(DeviceState *d)
+{
+ M48txxSysBusState *sys = M48TXX_SYS_BUS(d);
+ M48t59State *NVRAM = &sys->state;
+
+ m48t59_reset_common(NVRAM);
+}
+
+static const MemoryRegionOps m48t59_io_ops = {
+ .read = NVRAM_readb,
+ .write = NVRAM_writeb,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/* Initialisation routine */
+Nvram *m48t59_init(qemu_irq IRQ, hwaddr mem_base,
+ uint32_t io_base, uint16_t size, int base_year,
+ int model)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(m48txx_info); i++) {
+ if (!m48txx_info[i].sysbus_name ||
+ m48txx_info[i].size != size ||
+ m48txx_info[i].model != model) {
+ continue;
+ }
+
+ dev = qdev_create(NULL, m48txx_info[i].sysbus_name);
+ qdev_prop_set_int32(dev, "base-year", base_year);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, IRQ);
+ if (io_base != 0) {
+ memory_region_add_subregion(get_system_io(), io_base,
+ sysbus_mmio_get_region(s, 1));
+ }
+ if (mem_base != 0) {
+ sysbus_mmio_map(s, 0, mem_base);
+ }
+
+ return NVRAM(s);
+ }
+
+ assert(false);
+ return NULL;
+}
+
+Nvram *m48t59_init_isa(ISABus *bus, uint32_t io_base, uint16_t size,
+ int base_year, int model)
+{
+ DeviceState *dev;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(m48txx_info); i++) {
+ if (!m48txx_info[i].isa_name ||
+ m48txx_info[i].size != size ||
+ m48txx_info[i].model != model) {
+ continue;
+ }
+
+ dev = DEVICE(isa_create(bus, m48txx_info[i].isa_name));
+ qdev_prop_set_uint32(dev, "iobase", io_base);
+ qdev_prop_set_int32(dev, "base-year", base_year);
+ qdev_init_nofail(dev);
+ return NVRAM(dev);
+ }
+
+ assert(false);
+ return NULL;
+}
+
+static void m48t59_realize_common(M48t59State *s, Error **errp)
+{
+ s->buffer = g_malloc0(s->size);
+ if (s->model == 59) {
+ s->alrm_timer = timer_new_ns(rtc_clock, &alarm_cb, s);
+ s->wd_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &watchdog_cb, s);
+ }
+ qemu_get_timedate(&s->alarm, 0);
+
+ vmstate_register(NULL, -1, &vmstate_m48t59, s);
+}
+
+static void m48t59_isa_realize(DeviceState *dev, Error **errp)
+{
+ M48txxISADeviceClass *u = M48TXX_ISA_GET_CLASS(dev);
+ ISADevice *isadev = ISA_DEVICE(dev);
+ M48txxISAState *d = M48TXX_ISA(dev);
+ M48t59State *s = &d->state;
+
+ s->model = u->info.model;
+ s->size = u->info.size;
+ isa_init_irq(isadev, &s->IRQ, 8);
+ m48t59_realize_common(s, errp);
+ memory_region_init_io(&d->io, OBJECT(dev), &m48t59_io_ops, s, "m48t59", 4);
+ if (d->io_base != 0) {
+ isa_register_ioport(isadev, &d->io, d->io_base);
+ }
+}
+
+static int m48t59_init1(SysBusDevice *dev)
+{
+ M48txxSysBusDeviceClass *u = M48TXX_SYS_BUS_GET_CLASS(dev);
+ M48txxSysBusState *d = M48TXX_SYS_BUS(dev);
+ Object *o = OBJECT(dev);
+ M48t59State *s = &d->state;
+ Error *err = NULL;
+
+ s->model = u->info.model;
+ s->size = u->info.size;
+ sysbus_init_irq(dev, &s->IRQ);
+
+ memory_region_init_io(&s->iomem, o, &nvram_ops, s, "m48t59.nvram",
+ s->size);
+ memory_region_init_io(&d->io, o, &m48t59_io_ops, s, "m48t59", 4);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_mmio(dev, &d->io);
+ m48t59_realize_common(s, &err);
+ if (err != NULL) {
+ error_free(err);
+ return -1;
+ }
+
+ return 0;
+}
+
+static uint32_t m48txx_isa_read(Nvram *obj, uint32_t addr)
+{
+ M48txxISAState *d = M48TXX_ISA(obj);
+ return m48t59_read(&d->state, addr);
+}
+
+static void m48txx_isa_write(Nvram *obj, uint32_t addr, uint32_t val)
+{
+ M48txxISAState *d = M48TXX_ISA(obj);
+ m48t59_write(&d->state, addr, val);
+}
+
+static void m48txx_isa_toggle_lock(Nvram *obj, int lock)
+{
+ M48txxISAState *d = M48TXX_ISA(obj);
+ m48t59_toggle_lock(&d->state, lock);
+}
+
+static Property m48t59_isa_properties[] = {
+ DEFINE_PROP_INT32("base-year", M48txxISAState, state.base_year, 0),
+ DEFINE_PROP_UINT32("iobase", M48txxISAState, io_base, 0x74),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void m48txx_isa_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ NvramClass *nc = NVRAM_CLASS(klass);
+
+ dc->realize = m48t59_isa_realize;
+ dc->reset = m48t59_reset_isa;
+ dc->props = m48t59_isa_properties;
+ nc->read = m48txx_isa_read;
+ nc->write = m48txx_isa_write;
+ nc->toggle_lock = m48txx_isa_toggle_lock;
+}
+
+static void m48txx_isa_concrete_class_init(ObjectClass *klass, void *data)
+{
+ M48txxISADeviceClass *u = M48TXX_ISA_CLASS(klass);
+ M48txxInfo *info = data;
+
+ u->info = *info;
+}
+
+static uint32_t m48txx_sysbus_read(Nvram *obj, uint32_t addr)
+{
+ M48txxSysBusState *d = M48TXX_SYS_BUS(obj);
+ return m48t59_read(&d->state, addr);
+}
+
+static void m48txx_sysbus_write(Nvram *obj, uint32_t addr, uint32_t val)
+{
+ M48txxSysBusState *d = M48TXX_SYS_BUS(obj);
+ m48t59_write(&d->state, addr, val);
+}
+
+static void m48txx_sysbus_toggle_lock(Nvram *obj, int lock)
+{
+ M48txxSysBusState *d = M48TXX_SYS_BUS(obj);
+ m48t59_toggle_lock(&d->state, lock);
+}
+
+static Property m48t59_sysbus_properties[] = {
+ DEFINE_PROP_INT32("base-year", M48txxSysBusState, state.base_year, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void m48txx_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ NvramClass *nc = NVRAM_CLASS(klass);
+
+ k->init = m48t59_init1;
+ dc->reset = m48t59_reset_sysbus;
+ dc->props = m48t59_sysbus_properties;
+ nc->read = m48txx_sysbus_read;
+ nc->write = m48txx_sysbus_write;
+ nc->toggle_lock = m48txx_sysbus_toggle_lock;
+}
+
+static void m48txx_sysbus_concrete_class_init(ObjectClass *klass, void *data)
+{
+ M48txxSysBusDeviceClass *u = M48TXX_SYS_BUS_CLASS(klass);
+ M48txxInfo *info = data;
+
+ u->info = *info;
+}
+
+static const TypeInfo nvram_info = {
+ .name = TYPE_NVRAM,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(NvramClass),
+};
+
+static const TypeInfo m48txx_sysbus_type_info = {
+ .name = TYPE_M48TXX_SYS_BUS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(M48txxSysBusState),
+ .abstract = true,
+ .class_init = m48txx_sysbus_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_NVRAM },
+ { }
+ }
+};
+
+static const TypeInfo m48txx_isa_type_info = {
+ .name = TYPE_M48TXX_ISA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(M48txxISAState),
+ .abstract = true,
+ .class_init = m48txx_isa_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_NVRAM },
+ { }
+ }
+};
+
+static void m48t59_register_types(void)
+{
+ TypeInfo sysbus_type_info = {
+ .parent = TYPE_M48TXX_SYS_BUS,
+ .class_size = sizeof(M48txxSysBusDeviceClass),
+ .class_init = m48txx_sysbus_concrete_class_init,
+ };
+ TypeInfo isa_type_info = {
+ .parent = TYPE_M48TXX_ISA,
+ .class_size = sizeof(M48txxISADeviceClass),
+ .class_init = m48txx_isa_concrete_class_init,
+ };
+ int i;
+
+ type_register_static(&nvram_info);
+ type_register_static(&m48txx_sysbus_type_info);
+ type_register_static(&m48txx_isa_type_info);
+
+ for (i = 0; i < ARRAY_SIZE(m48txx_info); i++) {
+ if (m48txx_info[i].sysbus_name) {
+ sysbus_type_info.name = m48txx_info[i].sysbus_name;
+ sysbus_type_info.class_data = &m48txx_info[i];
+ type_register(&sysbus_type_info);
+ }
+
+ if (m48txx_info[i].isa_name) {
+ isa_type_info.name = m48txx_info[i].isa_name;
+ isa_type_info.class_data = &m48txx_info[i];
+ type_register(&isa_type_info);
+ }
+ }
+}
+
+type_init(m48t59_register_types)
diff --git a/hw/timer/mc146818rtc.c b/hw/timer/mc146818rtc.c
new file mode 100644
index 00000000..a9f0efd5
--- /dev/null
+++ b/hw/timer/mc146818rtc.c
@@ -0,0 +1,965 @@
+/*
+ * QEMU MC146818 RTC emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/timer/mc146818rtc.h"
+#include "qapi/visitor.h"
+#include "qapi-event.h"
+#include "qmp-commands.h"
+
+#ifdef TARGET_I386
+#include "hw/i386/apic.h"
+#endif
+
+//#define DEBUG_CMOS
+//#define DEBUG_COALESCED
+
+#ifdef DEBUG_CMOS
+# define CMOS_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define CMOS_DPRINTF(format, ...) do { } while (0)
+#endif
+
+#ifdef DEBUG_COALESCED
+# define DPRINTF_C(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define DPRINTF_C(format, ...) do { } while (0)
+#endif
+
+#define SEC_PER_MIN 60
+#define MIN_PER_HOUR 60
+#define SEC_PER_HOUR 3600
+#define HOUR_PER_DAY 24
+#define SEC_PER_DAY 86400
+
+#define RTC_REINJECT_ON_ACK_COUNT 20
+#define RTC_CLOCK_RATE 32768
+#define UIP_HOLD_LENGTH (8 * NANOSECONDS_PER_SECOND / 32768)
+
+#define MC146818_RTC(obj) OBJECT_CHECK(RTCState, (obj), TYPE_MC146818_RTC)
+
+typedef struct RTCState {
+ ISADevice parent_obj;
+
+ MemoryRegion io;
+ uint8_t cmos_data[128];
+ uint8_t cmos_index;
+ int32_t base_year;
+ uint64_t base_rtc;
+ uint64_t last_update;
+ int64_t offset;
+ qemu_irq irq;
+ int it_shift;
+ /* periodic timer */
+ QEMUTimer *periodic_timer;
+ int64_t next_periodic_time;
+ /* update-ended timer */
+ QEMUTimer *update_timer;
+ uint64_t next_alarm_time;
+ uint16_t irq_reinject_on_ack_count;
+ uint32_t irq_coalesced;
+ uint32_t period;
+ QEMUTimer *coalesced_timer;
+ Notifier clock_reset_notifier;
+ LostTickPolicy lost_tick_policy;
+ Notifier suspend_notifier;
+ QLIST_ENTRY(RTCState) link;
+} RTCState;
+
+static void rtc_set_time(RTCState *s);
+static void rtc_update_time(RTCState *s);
+static void rtc_set_cmos(RTCState *s, const struct tm *tm);
+static inline int rtc_from_bcd(RTCState *s, int a);
+static uint64_t get_next_alarm(RTCState *s);
+
+static inline bool rtc_running(RTCState *s)
+{
+ return (!(s->cmos_data[RTC_REG_B] & REG_B_SET) &&
+ (s->cmos_data[RTC_REG_A] & 0x70) <= 0x20);
+}
+
+static uint64_t get_guest_rtc_ns(RTCState *s)
+{
+ uint64_t guest_rtc;
+ uint64_t guest_clock = qemu_clock_get_ns(rtc_clock);
+
+ guest_rtc = s->base_rtc * NANOSECONDS_PER_SECOND
+ + guest_clock - s->last_update + s->offset;
+ return guest_rtc;
+}
+
+#ifdef TARGET_I386
+static void rtc_coalesced_timer_update(RTCState *s)
+{
+ if (s->irq_coalesced == 0) {
+ timer_del(s->coalesced_timer);
+ } else {
+ /* divide each RTC interval to 2 - 8 smaller intervals */
+ int c = MIN(s->irq_coalesced, 7) + 1;
+ int64_t next_clock = qemu_clock_get_ns(rtc_clock) +
+ muldiv64(s->period / c, get_ticks_per_sec(), RTC_CLOCK_RATE);
+ timer_mod(s->coalesced_timer, next_clock);
+ }
+}
+
+static void rtc_coalesced_timer(void *opaque)
+{
+ RTCState *s = opaque;
+
+ if (s->irq_coalesced != 0) {
+ apic_reset_irq_delivered();
+ s->cmos_data[RTC_REG_C] |= 0xc0;
+ DPRINTF_C("cmos: injecting from timer\n");
+ qemu_irq_raise(s->irq);
+ if (apic_get_irq_delivered()) {
+ s->irq_coalesced--;
+ DPRINTF_C("cmos: coalesced irqs decreased to %d\n",
+ s->irq_coalesced);
+ }
+ }
+
+ rtc_coalesced_timer_update(s);
+}
+#endif
+
+/* handle periodic timer */
+static void periodic_timer_update(RTCState *s, int64_t current_time)
+{
+ int period_code, period;
+ int64_t cur_clock, next_irq_clock;
+
+ period_code = s->cmos_data[RTC_REG_A] & 0x0f;
+ if (period_code != 0
+ && (s->cmos_data[RTC_REG_B] & REG_B_PIE)) {
+ if (period_code <= 2)
+ period_code += 7;
+ /* period in 32 Khz cycles */
+ period = 1 << (period_code - 1);
+#ifdef TARGET_I386
+ if (period != s->period) {
+ s->irq_coalesced = (s->irq_coalesced * s->period) / period;
+ DPRINTF_C("cmos: coalesced irqs scaled to %d\n", s->irq_coalesced);
+ }
+ s->period = period;
+#endif
+ /* compute 32 khz clock */
+ cur_clock = muldiv64(current_time, RTC_CLOCK_RATE, get_ticks_per_sec());
+ next_irq_clock = (cur_clock & ~(period - 1)) + period;
+ s->next_periodic_time =
+ muldiv64(next_irq_clock, get_ticks_per_sec(), RTC_CLOCK_RATE) + 1;
+ timer_mod(s->periodic_timer, s->next_periodic_time);
+ } else {
+#ifdef TARGET_I386
+ s->irq_coalesced = 0;
+#endif
+ timer_del(s->periodic_timer);
+ }
+}
+
+static void rtc_periodic_timer(void *opaque)
+{
+ RTCState *s = opaque;
+
+ periodic_timer_update(s, s->next_periodic_time);
+ s->cmos_data[RTC_REG_C] |= REG_C_PF;
+ if (s->cmos_data[RTC_REG_B] & REG_B_PIE) {
+ s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
+#ifdef TARGET_I386
+ if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
+ if (s->irq_reinject_on_ack_count >= RTC_REINJECT_ON_ACK_COUNT)
+ s->irq_reinject_on_ack_count = 0;
+ apic_reset_irq_delivered();
+ qemu_irq_raise(s->irq);
+ if (!apic_get_irq_delivered()) {
+ s->irq_coalesced++;
+ rtc_coalesced_timer_update(s);
+ DPRINTF_C("cmos: coalesced irqs increased to %d\n",
+ s->irq_coalesced);
+ }
+ } else
+#endif
+ qemu_irq_raise(s->irq);
+ }
+}
+
+/* handle update-ended timer */
+static void check_update_timer(RTCState *s)
+{
+ uint64_t next_update_time;
+ uint64_t guest_nsec;
+ int next_alarm_sec;
+
+ /* From the data sheet: "Holding the dividers in reset prevents
+ * interrupts from operating, while setting the SET bit allows"
+ * them to occur. However, it will prevent an alarm interrupt
+ * from occurring, because the time of day is not updated.
+ */
+ if ((s->cmos_data[RTC_REG_A] & 0x60) == 0x60) {
+ timer_del(s->update_timer);
+ return;
+ }
+ if ((s->cmos_data[RTC_REG_C] & REG_C_UF) &&
+ (s->cmos_data[RTC_REG_B] & REG_B_SET)) {
+ timer_del(s->update_timer);
+ return;
+ }
+ if ((s->cmos_data[RTC_REG_C] & REG_C_UF) &&
+ (s->cmos_data[RTC_REG_C] & REG_C_AF)) {
+ timer_del(s->update_timer);
+ return;
+ }
+
+ guest_nsec = get_guest_rtc_ns(s) % NANOSECONDS_PER_SECOND;
+ /* if UF is clear, reprogram to next second */
+ next_update_time = qemu_clock_get_ns(rtc_clock)
+ + NANOSECONDS_PER_SECOND - guest_nsec;
+
+ /* Compute time of next alarm. One second is already accounted
+ * for in next_update_time.
+ */
+ next_alarm_sec = get_next_alarm(s);
+ s->next_alarm_time = next_update_time +
+ (next_alarm_sec - 1) * NANOSECONDS_PER_SECOND;
+
+ if (s->cmos_data[RTC_REG_C] & REG_C_UF) {
+ /* UF is set, but AF is clear. Program the timer to target
+ * the alarm time. */
+ next_update_time = s->next_alarm_time;
+ }
+ if (next_update_time != timer_expire_time_ns(s->update_timer)) {
+ timer_mod(s->update_timer, next_update_time);
+ }
+}
+
+static inline uint8_t convert_hour(RTCState *s, uint8_t hour)
+{
+ if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) {
+ hour %= 12;
+ if (s->cmos_data[RTC_HOURS] & 0x80) {
+ hour += 12;
+ }
+ }
+ return hour;
+}
+
+static uint64_t get_next_alarm(RTCState *s)
+{
+ int32_t alarm_sec, alarm_min, alarm_hour, cur_hour, cur_min, cur_sec;
+ int32_t hour, min, sec;
+
+ rtc_update_time(s);
+
+ alarm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]);
+ alarm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]);
+ alarm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]);
+ alarm_hour = alarm_hour == -1 ? -1 : convert_hour(s, alarm_hour);
+
+ cur_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]);
+ cur_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]);
+ cur_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS]);
+ cur_hour = convert_hour(s, cur_hour);
+
+ if (alarm_hour == -1) {
+ alarm_hour = cur_hour;
+ if (alarm_min == -1) {
+ alarm_min = cur_min;
+ if (alarm_sec == -1) {
+ alarm_sec = cur_sec + 1;
+ } else if (cur_sec > alarm_sec) {
+ alarm_min++;
+ }
+ } else if (cur_min == alarm_min) {
+ if (alarm_sec == -1) {
+ alarm_sec = cur_sec + 1;
+ } else {
+ if (cur_sec > alarm_sec) {
+ alarm_hour++;
+ }
+ }
+ if (alarm_sec == SEC_PER_MIN) {
+ /* wrap to next hour, minutes is not in don't care mode */
+ alarm_sec = 0;
+ alarm_hour++;
+ }
+ } else if (cur_min > alarm_min) {
+ alarm_hour++;
+ }
+ } else if (cur_hour == alarm_hour) {
+ if (alarm_min == -1) {
+ alarm_min = cur_min;
+ if (alarm_sec == -1) {
+ alarm_sec = cur_sec + 1;
+ } else if (cur_sec > alarm_sec) {
+ alarm_min++;
+ }
+
+ if (alarm_sec == SEC_PER_MIN) {
+ alarm_sec = 0;
+ alarm_min++;
+ }
+ /* wrap to next day, hour is not in don't care mode */
+ alarm_min %= MIN_PER_HOUR;
+ } else if (cur_min == alarm_min) {
+ if (alarm_sec == -1) {
+ alarm_sec = cur_sec + 1;
+ }
+ /* wrap to next day, hours+minutes not in don't care mode */
+ alarm_sec %= SEC_PER_MIN;
+ }
+ }
+
+ /* values that are still don't care fire at the next min/sec */
+ if (alarm_min == -1) {
+ alarm_min = 0;
+ }
+ if (alarm_sec == -1) {
+ alarm_sec = 0;
+ }
+
+ /* keep values in range */
+ if (alarm_sec == SEC_PER_MIN) {
+ alarm_sec = 0;
+ alarm_min++;
+ }
+ if (alarm_min == MIN_PER_HOUR) {
+ alarm_min = 0;
+ alarm_hour++;
+ }
+ alarm_hour %= HOUR_PER_DAY;
+
+ hour = alarm_hour - cur_hour;
+ min = hour * MIN_PER_HOUR + alarm_min - cur_min;
+ sec = min * SEC_PER_MIN + alarm_sec - cur_sec;
+ return sec <= 0 ? sec + SEC_PER_DAY : sec;
+}
+
+static void rtc_update_timer(void *opaque)
+{
+ RTCState *s = opaque;
+ int32_t irqs = REG_C_UF;
+ int32_t new_irqs;
+
+ assert((s->cmos_data[RTC_REG_A] & 0x60) != 0x60);
+
+ /* UIP might have been latched, update time and clear it. */
+ rtc_update_time(s);
+ s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
+
+ if (qemu_clock_get_ns(rtc_clock) >= s->next_alarm_time) {
+ irqs |= REG_C_AF;
+ if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_RTC);
+ }
+ }
+
+ new_irqs = irqs & ~s->cmos_data[RTC_REG_C];
+ s->cmos_data[RTC_REG_C] |= irqs;
+ if ((new_irqs & s->cmos_data[RTC_REG_B]) != 0) {
+ s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
+ qemu_irq_raise(s->irq);
+ }
+ check_update_timer(s);
+}
+
+static void cmos_ioport_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ RTCState *s = opaque;
+
+ if ((addr & 1) == 0) {
+ s->cmos_index = data & 0x7f;
+ } else {
+ CMOS_DPRINTF("cmos: write index=0x%02x val=0x%02" PRIx64 "\n",
+ s->cmos_index, data);
+ switch(s->cmos_index) {
+ case RTC_SECONDS_ALARM:
+ case RTC_MINUTES_ALARM:
+ case RTC_HOURS_ALARM:
+ s->cmos_data[s->cmos_index] = data;
+ check_update_timer(s);
+ break;
+ case RTC_IBM_PS2_CENTURY_BYTE:
+ s->cmos_index = RTC_CENTURY;
+ /* fall through */
+ case RTC_CENTURY:
+ case RTC_SECONDS:
+ case RTC_MINUTES:
+ case RTC_HOURS:
+ case RTC_DAY_OF_WEEK:
+ case RTC_DAY_OF_MONTH:
+ case RTC_MONTH:
+ case RTC_YEAR:
+ s->cmos_data[s->cmos_index] = data;
+ /* if in set mode, do not update the time */
+ if (rtc_running(s)) {
+ rtc_set_time(s);
+ check_update_timer(s);
+ }
+ break;
+ case RTC_REG_A:
+ if ((data & 0x60) == 0x60) {
+ if (rtc_running(s)) {
+ rtc_update_time(s);
+ }
+ /* What happens to UIP when divider reset is enabled is
+ * unclear from the datasheet. Shouldn't matter much
+ * though.
+ */
+ s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
+ } else if (((s->cmos_data[RTC_REG_A] & 0x60) == 0x60) &&
+ (data & 0x70) <= 0x20) {
+ /* when the divider reset is removed, the first update cycle
+ * begins one-half second later*/
+ if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
+ s->offset = 500000000;
+ rtc_set_time(s);
+ }
+ s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
+ }
+ /* UIP bit is read only */
+ s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) |
+ (s->cmos_data[RTC_REG_A] & REG_A_UIP);
+ periodic_timer_update(s, qemu_clock_get_ns(rtc_clock));
+ check_update_timer(s);
+ break;
+ case RTC_REG_B:
+ if (data & REG_B_SET) {
+ /* update cmos to when the rtc was stopping */
+ if (rtc_running(s)) {
+ rtc_update_time(s);
+ }
+ /* set mode: reset UIP mode */
+ s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
+ data &= ~REG_B_UIE;
+ } else {
+ /* if disabling set mode, update the time */
+ if ((s->cmos_data[RTC_REG_B] & REG_B_SET) &&
+ (s->cmos_data[RTC_REG_A] & 0x70) <= 0x20) {
+ s->offset = get_guest_rtc_ns(s) % NANOSECONDS_PER_SECOND;
+ rtc_set_time(s);
+ }
+ }
+ /* if an interrupt flag is already set when the interrupt
+ * becomes enabled, raise an interrupt immediately. */
+ if (data & s->cmos_data[RTC_REG_C] & REG_C_MASK) {
+ s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
+ qemu_irq_raise(s->irq);
+ } else {
+ s->cmos_data[RTC_REG_C] &= ~REG_C_IRQF;
+ qemu_irq_lower(s->irq);
+ }
+ s->cmos_data[RTC_REG_B] = data;
+ periodic_timer_update(s, qemu_clock_get_ns(rtc_clock));
+ check_update_timer(s);
+ break;
+ case RTC_REG_C:
+ case RTC_REG_D:
+ /* cannot write to them */
+ break;
+ default:
+ s->cmos_data[s->cmos_index] = data;
+ break;
+ }
+ }
+}
+
+static inline int rtc_to_bcd(RTCState *s, int a)
+{
+ if (s->cmos_data[RTC_REG_B] & REG_B_DM) {
+ return a;
+ } else {
+ return ((a / 10) << 4) | (a % 10);
+ }
+}
+
+static inline int rtc_from_bcd(RTCState *s, int a)
+{
+ if ((a & 0xc0) == 0xc0) {
+ return -1;
+ }
+ if (s->cmos_data[RTC_REG_B] & REG_B_DM) {
+ return a;
+ } else {
+ return ((a >> 4) * 10) + (a & 0x0f);
+ }
+}
+
+static void rtc_get_time(RTCState *s, struct tm *tm)
+{
+ tm->tm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]);
+ tm->tm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]);
+ tm->tm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS] & 0x7f);
+ if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) {
+ tm->tm_hour %= 12;
+ if (s->cmos_data[RTC_HOURS] & 0x80) {
+ tm->tm_hour += 12;
+ }
+ }
+ tm->tm_wday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]) - 1;
+ tm->tm_mday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]);
+ tm->tm_mon = rtc_from_bcd(s, s->cmos_data[RTC_MONTH]) - 1;
+ tm->tm_year =
+ rtc_from_bcd(s, s->cmos_data[RTC_YEAR]) + s->base_year +
+ rtc_from_bcd(s, s->cmos_data[RTC_CENTURY]) * 100 - 1900;
+}
+
+static QLIST_HEAD(, RTCState) rtc_devices =
+ QLIST_HEAD_INITIALIZER(rtc_devices);
+
+#ifdef TARGET_I386
+void qmp_rtc_reset_reinjection(Error **errp)
+{
+ RTCState *s;
+
+ QLIST_FOREACH(s, &rtc_devices, link) {
+ s->irq_coalesced = 0;
+ }
+}
+#endif
+
+static void rtc_set_time(RTCState *s)
+{
+ struct tm tm;
+
+ rtc_get_time(s, &tm);
+ s->base_rtc = mktimegm(&tm);
+ s->last_update = qemu_clock_get_ns(rtc_clock);
+
+ qapi_event_send_rtc_change(qemu_timedate_diff(&tm), &error_abort);
+}
+
+static void rtc_set_cmos(RTCState *s, const struct tm *tm)
+{
+ int year;
+
+ s->cmos_data[RTC_SECONDS] = rtc_to_bcd(s, tm->tm_sec);
+ s->cmos_data[RTC_MINUTES] = rtc_to_bcd(s, tm->tm_min);
+ if (s->cmos_data[RTC_REG_B] & REG_B_24H) {
+ /* 24 hour format */
+ s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm->tm_hour);
+ } else {
+ /* 12 hour format */
+ int h = (tm->tm_hour % 12) ? tm->tm_hour % 12 : 12;
+ s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, h);
+ if (tm->tm_hour >= 12)
+ s->cmos_data[RTC_HOURS] |= 0x80;
+ }
+ s->cmos_data[RTC_DAY_OF_WEEK] = rtc_to_bcd(s, tm->tm_wday + 1);
+ s->cmos_data[RTC_DAY_OF_MONTH] = rtc_to_bcd(s, tm->tm_mday);
+ s->cmos_data[RTC_MONTH] = rtc_to_bcd(s, tm->tm_mon + 1);
+ year = tm->tm_year + 1900 - s->base_year;
+ s->cmos_data[RTC_YEAR] = rtc_to_bcd(s, year % 100);
+ s->cmos_data[RTC_CENTURY] = rtc_to_bcd(s, year / 100);
+}
+
+static void rtc_update_time(RTCState *s)
+{
+ struct tm ret;
+ time_t guest_sec;
+ int64_t guest_nsec;
+
+ guest_nsec = get_guest_rtc_ns(s);
+ guest_sec = guest_nsec / NANOSECONDS_PER_SECOND;
+ gmtime_r(&guest_sec, &ret);
+
+ /* Is SET flag of Register B disabled? */
+ if ((s->cmos_data[RTC_REG_B] & REG_B_SET) == 0) {
+ rtc_set_cmos(s, &ret);
+ }
+}
+
+static int update_in_progress(RTCState *s)
+{
+ int64_t guest_nsec;
+
+ if (!rtc_running(s)) {
+ return 0;
+ }
+ if (timer_pending(s->update_timer)) {
+ int64_t next_update_time = timer_expire_time_ns(s->update_timer);
+ /* Latch UIP until the timer expires. */
+ if (qemu_clock_get_ns(rtc_clock) >=
+ (next_update_time - UIP_HOLD_LENGTH)) {
+ s->cmos_data[RTC_REG_A] |= REG_A_UIP;
+ return 1;
+ }
+ }
+
+ guest_nsec = get_guest_rtc_ns(s);
+ /* UIP bit will be set at last 244us of every second. */
+ if ((guest_nsec % NANOSECONDS_PER_SECOND) >=
+ (NANOSECONDS_PER_SECOND - UIP_HOLD_LENGTH)) {
+ return 1;
+ }
+ return 0;
+}
+
+static uint64_t cmos_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ RTCState *s = opaque;
+ int ret;
+ if ((addr & 1) == 0) {
+ return 0xff;
+ } else {
+ switch(s->cmos_index) {
+ case RTC_IBM_PS2_CENTURY_BYTE:
+ s->cmos_index = RTC_CENTURY;
+ /* fall through */
+ case RTC_CENTURY:
+ case RTC_SECONDS:
+ case RTC_MINUTES:
+ case RTC_HOURS:
+ case RTC_DAY_OF_WEEK:
+ case RTC_DAY_OF_MONTH:
+ case RTC_MONTH:
+ case RTC_YEAR:
+ /* if not in set mode, calibrate cmos before
+ * reading*/
+ if (rtc_running(s)) {
+ rtc_update_time(s);
+ }
+ ret = s->cmos_data[s->cmos_index];
+ break;
+ case RTC_REG_A:
+ if (update_in_progress(s)) {
+ s->cmos_data[s->cmos_index] |= REG_A_UIP;
+ } else {
+ s->cmos_data[s->cmos_index] &= ~REG_A_UIP;
+ }
+ ret = s->cmos_data[s->cmos_index];
+ break;
+ case RTC_REG_C:
+ ret = s->cmos_data[s->cmos_index];
+ qemu_irq_lower(s->irq);
+ s->cmos_data[RTC_REG_C] = 0x00;
+ if (ret & (REG_C_UF | REG_C_AF)) {
+ check_update_timer(s);
+ }
+#ifdef TARGET_I386
+ if(s->irq_coalesced &&
+ (s->cmos_data[RTC_REG_B] & REG_B_PIE) &&
+ s->irq_reinject_on_ack_count < RTC_REINJECT_ON_ACK_COUNT) {
+ s->irq_reinject_on_ack_count++;
+ s->cmos_data[RTC_REG_C] |= REG_C_IRQF | REG_C_PF;
+ apic_reset_irq_delivered();
+ DPRINTF_C("cmos: injecting on ack\n");
+ qemu_irq_raise(s->irq);
+ if (apic_get_irq_delivered()) {
+ s->irq_coalesced--;
+ DPRINTF_C("cmos: coalesced irqs decreased to %d\n",
+ s->irq_coalesced);
+ }
+ }
+#endif
+ break;
+ default:
+ ret = s->cmos_data[s->cmos_index];
+ break;
+ }
+ CMOS_DPRINTF("cmos: read index=0x%02x val=0x%02x\n",
+ s->cmos_index, ret);
+ return ret;
+ }
+}
+
+void rtc_set_memory(ISADevice *dev, int addr, int val)
+{
+ RTCState *s = MC146818_RTC(dev);
+ if (addr >= 0 && addr <= 127)
+ s->cmos_data[addr] = val;
+}
+
+int rtc_get_memory(ISADevice *dev, int addr)
+{
+ RTCState *s = MC146818_RTC(dev);
+ assert(addr >= 0 && addr <= 127);
+ return s->cmos_data[addr];
+}
+
+static void rtc_set_date_from_host(ISADevice *dev)
+{
+ RTCState *s = MC146818_RTC(dev);
+ struct tm tm;
+
+ qemu_get_timedate(&tm, 0);
+
+ s->base_rtc = mktimegm(&tm);
+ s->last_update = qemu_clock_get_ns(rtc_clock);
+ s->offset = 0;
+
+ /* set the CMOS date */
+ rtc_set_cmos(s, &tm);
+}
+
+static int rtc_post_load(void *opaque, int version_id)
+{
+ RTCState *s = opaque;
+
+ if (version_id <= 2) {
+ rtc_set_time(s);
+ s->offset = 0;
+ check_update_timer(s);
+ }
+
+ uint64_t now = qemu_clock_get_ns(rtc_clock);
+ if (now < s->next_periodic_time ||
+ now > (s->next_periodic_time + get_max_clock_jump())) {
+ periodic_timer_update(s, qemu_clock_get_ns(rtc_clock));
+ }
+
+#ifdef TARGET_I386
+ if (version_id >= 2) {
+ if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
+ rtc_coalesced_timer_update(s);
+ }
+ }
+#endif
+ return 0;
+}
+
+static bool rtc_irq_reinject_on_ack_count_needed(void *opaque)
+{
+ RTCState *s = (RTCState *)opaque;
+ return s->irq_reinject_on_ack_count != 0;
+}
+
+static const VMStateDescription vmstate_rtc_irq_reinject_on_ack_count = {
+ .name = "mc146818rtc/irq_reinject_on_ack_count",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = rtc_irq_reinject_on_ack_count_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(irq_reinject_on_ack_count, RTCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_rtc = {
+ .name = "mc146818rtc",
+ .version_id = 3,
+ .minimum_version_id = 1,
+ .post_load = rtc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(cmos_data, RTCState),
+ VMSTATE_UINT8(cmos_index, RTCState),
+ VMSTATE_UNUSED(7*4),
+ VMSTATE_TIMER_PTR(periodic_timer, RTCState),
+ VMSTATE_INT64(next_periodic_time, RTCState),
+ VMSTATE_UNUSED(3*8),
+ VMSTATE_UINT32_V(irq_coalesced, RTCState, 2),
+ VMSTATE_UINT32_V(period, RTCState, 2),
+ VMSTATE_UINT64_V(base_rtc, RTCState, 3),
+ VMSTATE_UINT64_V(last_update, RTCState, 3),
+ VMSTATE_INT64_V(offset, RTCState, 3),
+ VMSTATE_TIMER_PTR_V(update_timer, RTCState, 3),
+ VMSTATE_UINT64_V(next_alarm_time, RTCState, 3),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_rtc_irq_reinject_on_ack_count,
+ NULL
+ }
+};
+
+static void rtc_notify_clock_reset(Notifier *notifier, void *data)
+{
+ RTCState *s = container_of(notifier, RTCState, clock_reset_notifier);
+ int64_t now = *(int64_t *)data;
+
+ rtc_set_date_from_host(ISA_DEVICE(s));
+ periodic_timer_update(s, now);
+ check_update_timer(s);
+#ifdef TARGET_I386
+ if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
+ rtc_coalesced_timer_update(s);
+ }
+#endif
+}
+
+/* set CMOS shutdown status register (index 0xF) as S3_resume(0xFE)
+ BIOS will read it and start S3 resume at POST Entry */
+static void rtc_notify_suspend(Notifier *notifier, void *data)
+{
+ RTCState *s = container_of(notifier, RTCState, suspend_notifier);
+ rtc_set_memory(ISA_DEVICE(s), 0xF, 0xFE);
+}
+
+static void rtc_reset(void *opaque)
+{
+ RTCState *s = opaque;
+
+ s->cmos_data[RTC_REG_B] &= ~(REG_B_PIE | REG_B_AIE | REG_B_SQWE);
+ s->cmos_data[RTC_REG_C] &= ~(REG_C_UF | REG_C_IRQF | REG_C_PF | REG_C_AF);
+ check_update_timer(s);
+
+ qemu_irq_lower(s->irq);
+
+#ifdef TARGET_I386
+ if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
+ s->irq_coalesced = 0;
+ s->irq_reinject_on_ack_count = 0;
+ }
+#endif
+}
+
+static const MemoryRegionOps cmos_ops = {
+ .read = cmos_ioport_read,
+ .write = cmos_ioport_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void rtc_get_date(Object *obj, struct tm *current_tm, Error **errp)
+{
+ RTCState *s = MC146818_RTC(obj);
+
+ rtc_update_time(s);
+ rtc_get_time(s, current_tm);
+}
+
+static void rtc_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ RTCState *s = MC146818_RTC(dev);
+ int base = 0x70;
+
+ s->cmos_data[RTC_REG_A] = 0x26;
+ s->cmos_data[RTC_REG_B] = 0x02;
+ s->cmos_data[RTC_REG_C] = 0x00;
+ s->cmos_data[RTC_REG_D] = 0x80;
+
+ /* This is for historical reasons. The default base year qdev property
+ * was set to 2000 for most machine types before the century byte was
+ * implemented.
+ *
+ * This if statement means that the century byte will be always 0
+ * (at least until 2079...) for base_year = 1980, but will be set
+ * correctly for base_year = 2000.
+ */
+ if (s->base_year == 2000) {
+ s->base_year = 0;
+ }
+
+ rtc_set_date_from_host(isadev);
+
+#ifdef TARGET_I386
+ switch (s->lost_tick_policy) {
+ case LOST_TICK_POLICY_SLEW:
+ s->coalesced_timer =
+ timer_new_ns(rtc_clock, rtc_coalesced_timer, s);
+ break;
+ case LOST_TICK_POLICY_DISCARD:
+ break;
+ default:
+ error_setg(errp, "Invalid lost tick policy.");
+ return;
+ }
+#endif
+
+ s->periodic_timer = timer_new_ns(rtc_clock, rtc_periodic_timer, s);
+ s->update_timer = timer_new_ns(rtc_clock, rtc_update_timer, s);
+ check_update_timer(s);
+
+ s->clock_reset_notifier.notify = rtc_notify_clock_reset;
+ qemu_clock_register_reset_notifier(rtc_clock,
+ &s->clock_reset_notifier);
+
+ s->suspend_notifier.notify = rtc_notify_suspend;
+ qemu_register_suspend_notifier(&s->suspend_notifier);
+
+ memory_region_init_io(&s->io, OBJECT(s), &cmos_ops, s, "rtc", 2);
+ isa_register_ioport(isadev, &s->io, base);
+
+ qdev_set_legacy_instance_id(dev, base, 3);
+ qemu_register_reset(rtc_reset, s);
+
+ object_property_add_tm(OBJECT(s), "date", rtc_get_date, NULL);
+
+ object_property_add_alias(qdev_get_machine(), "rtc-time",
+ OBJECT(s), "date", NULL);
+}
+
+ISADevice *rtc_init(ISABus *bus, int base_year, qemu_irq intercept_irq)
+{
+ DeviceState *dev;
+ ISADevice *isadev;
+ RTCState *s;
+
+ isadev = isa_create(bus, TYPE_MC146818_RTC);
+ dev = DEVICE(isadev);
+ s = MC146818_RTC(isadev);
+ qdev_prop_set_int32(dev, "base_year", base_year);
+ qdev_init_nofail(dev);
+ if (intercept_irq) {
+ s->irq = intercept_irq;
+ } else {
+ isa_init_irq(isadev, &s->irq, RTC_ISA_IRQ);
+ }
+ QLIST_INSERT_HEAD(&rtc_devices, s, link);
+
+ return isadev;
+}
+
+static Property mc146818rtc_properties[] = {
+ DEFINE_PROP_INT32("base_year", RTCState, base_year, 1980),
+ DEFINE_PROP_LOSTTICKPOLICY("lost_tick_policy", RTCState,
+ lost_tick_policy, LOST_TICK_POLICY_DISCARD),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void rtc_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = rtc_realizefn;
+ dc->vmsd = &vmstate_rtc;
+ dc->props = mc146818rtc_properties;
+ /* Reason: needs to be wired up by rtc_init() */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static void rtc_finalize(Object *obj)
+{
+ object_property_del(qdev_get_machine(), "rtc", NULL);
+}
+
+static const TypeInfo mc146818rtc_info = {
+ .name = TYPE_MC146818_RTC,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(RTCState),
+ .class_init = rtc_class_initfn,
+ .instance_finalize = rtc_finalize,
+};
+
+static void mc146818rtc_register_types(void)
+{
+ type_register_static(&mc146818rtc_info);
+}
+
+type_init(mc146818rtc_register_types)
diff --git a/hw/timer/milkymist-sysctl.c b/hw/timer/milkymist-sysctl.c
new file mode 100644
index 00000000..30535a4e
--- /dev/null
+++ b/hw/timer/milkymist-sysctl.c
@@ -0,0 +1,341 @@
+/*
+ * QEMU model of the Milkymist System Controller.
+ *
+ * Copyright (c) 2010-2012 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/sysctl.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "qemu/error-report.h"
+
+enum {
+ CTRL_ENABLE = (1<<0),
+ CTRL_AUTORESTART = (1<<1),
+};
+
+enum {
+ ICAP_READY = (1<<0),
+};
+
+enum {
+ R_GPIO_IN = 0,
+ R_GPIO_OUT,
+ R_GPIO_INTEN,
+ R_TIMER0_CONTROL = 4,
+ R_TIMER0_COMPARE,
+ R_TIMER0_COUNTER,
+ R_TIMER1_CONTROL = 8,
+ R_TIMER1_COMPARE,
+ R_TIMER1_COUNTER,
+ R_ICAP = 16,
+ R_DBG_SCRATCHPAD = 20,
+ R_DBG_WRITE_LOCK,
+ R_CLK_FREQUENCY = 29,
+ R_CAPABILITIES,
+ R_SYSTEM_ID,
+ R_MAX
+};
+
+#define TYPE_MILKYMIST_SYSCTL "milkymist-sysctl"
+#define MILKYMIST_SYSCTL(obj) \
+ OBJECT_CHECK(MilkymistSysctlState, (obj), TYPE_MILKYMIST_SYSCTL)
+
+struct MilkymistSysctlState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+
+ QEMUBH *bh0;
+ QEMUBH *bh1;
+ ptimer_state *ptimer0;
+ ptimer_state *ptimer1;
+
+ uint32_t freq_hz;
+ uint32_t capabilities;
+ uint32_t systemid;
+ uint32_t strappings;
+
+ uint32_t regs[R_MAX];
+
+ qemu_irq gpio_irq;
+ qemu_irq timer0_irq;
+ qemu_irq timer1_irq;
+};
+typedef struct MilkymistSysctlState MilkymistSysctlState;
+
+static void sysctl_icap_write(MilkymistSysctlState *s, uint32_t value)
+{
+ trace_milkymist_sysctl_icap_write(value);
+ switch (value & 0xffff) {
+ case 0x000e:
+ qemu_system_shutdown_request();
+ break;
+ }
+}
+
+static uint64_t sysctl_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistSysctlState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_TIMER0_COUNTER:
+ r = (uint32_t)ptimer_get_count(s->ptimer0);
+ /* milkymist timer counts up */
+ r = s->regs[R_TIMER0_COMPARE] - r;
+ break;
+ case R_TIMER1_COUNTER:
+ r = (uint32_t)ptimer_get_count(s->ptimer1);
+ /* milkymist timer counts up */
+ r = s->regs[R_TIMER1_COMPARE] - r;
+ break;
+ case R_GPIO_IN:
+ case R_GPIO_OUT:
+ case R_GPIO_INTEN:
+ case R_TIMER0_CONTROL:
+ case R_TIMER0_COMPARE:
+ case R_TIMER1_CONTROL:
+ case R_TIMER1_COMPARE:
+ case R_ICAP:
+ case R_DBG_SCRATCHPAD:
+ case R_DBG_WRITE_LOCK:
+ case R_CLK_FREQUENCY:
+ case R_CAPABILITIES:
+ case R_SYSTEM_ID:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_sysctl: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_sysctl_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void sysctl_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistSysctlState *s = opaque;
+
+ trace_milkymist_sysctl_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_GPIO_OUT:
+ case R_GPIO_INTEN:
+ case R_TIMER0_COUNTER:
+ case R_TIMER1_COUNTER:
+ case R_DBG_SCRATCHPAD:
+ s->regs[addr] = value;
+ break;
+ case R_TIMER0_COMPARE:
+ ptimer_set_limit(s->ptimer0, value, 0);
+ s->regs[addr] = value;
+ break;
+ case R_TIMER1_COMPARE:
+ ptimer_set_limit(s->ptimer1, value, 0);
+ s->regs[addr] = value;
+ break;
+ case R_TIMER0_CONTROL:
+ s->regs[addr] = value;
+ if (s->regs[R_TIMER0_CONTROL] & CTRL_ENABLE) {
+ trace_milkymist_sysctl_start_timer0();
+ ptimer_set_count(s->ptimer0,
+ s->regs[R_TIMER0_COMPARE] - s->regs[R_TIMER0_COUNTER]);
+ ptimer_run(s->ptimer0, 0);
+ } else {
+ trace_milkymist_sysctl_stop_timer0();
+ ptimer_stop(s->ptimer0);
+ }
+ break;
+ case R_TIMER1_CONTROL:
+ s->regs[addr] = value;
+ if (s->regs[R_TIMER1_CONTROL] & CTRL_ENABLE) {
+ trace_milkymist_sysctl_start_timer1();
+ ptimer_set_count(s->ptimer1,
+ s->regs[R_TIMER1_COMPARE] - s->regs[R_TIMER1_COUNTER]);
+ ptimer_run(s->ptimer1, 0);
+ } else {
+ trace_milkymist_sysctl_stop_timer1();
+ ptimer_stop(s->ptimer1);
+ }
+ break;
+ case R_ICAP:
+ sysctl_icap_write(s, value);
+ break;
+ case R_DBG_WRITE_LOCK:
+ s->regs[addr] = 1;
+ break;
+ case R_SYSTEM_ID:
+ qemu_system_reset_request();
+ break;
+
+ case R_GPIO_IN:
+ case R_CLK_FREQUENCY:
+ case R_CAPABILITIES:
+ error_report("milkymist_sysctl: write to read-only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+
+ default:
+ error_report("milkymist_sysctl: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps sysctl_mmio_ops = {
+ .read = sysctl_read,
+ .write = sysctl_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void timer0_hit(void *opaque)
+{
+ MilkymistSysctlState *s = opaque;
+
+ if (!(s->regs[R_TIMER0_CONTROL] & CTRL_AUTORESTART)) {
+ s->regs[R_TIMER0_CONTROL] &= ~CTRL_ENABLE;
+ trace_milkymist_sysctl_stop_timer0();
+ ptimer_stop(s->ptimer0);
+ }
+
+ trace_milkymist_sysctl_pulse_irq_timer0();
+ qemu_irq_pulse(s->timer0_irq);
+}
+
+static void timer1_hit(void *opaque)
+{
+ MilkymistSysctlState *s = opaque;
+
+ if (!(s->regs[R_TIMER1_CONTROL] & CTRL_AUTORESTART)) {
+ s->regs[R_TIMER1_CONTROL] &= ~CTRL_ENABLE;
+ trace_milkymist_sysctl_stop_timer1();
+ ptimer_stop(s->ptimer1);
+ }
+
+ trace_milkymist_sysctl_pulse_irq_timer1();
+ qemu_irq_pulse(s->timer1_irq);
+}
+
+static void milkymist_sysctl_reset(DeviceState *d)
+{
+ MilkymistSysctlState *s = MILKYMIST_SYSCTL(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ ptimer_stop(s->ptimer0);
+ ptimer_stop(s->ptimer1);
+
+ /* defaults */
+ s->regs[R_ICAP] = ICAP_READY;
+ s->regs[R_SYSTEM_ID] = s->systemid;
+ s->regs[R_CLK_FREQUENCY] = s->freq_hz;
+ s->regs[R_CAPABILITIES] = s->capabilities;
+ s->regs[R_GPIO_IN] = s->strappings;
+}
+
+static int milkymist_sysctl_init(SysBusDevice *dev)
+{
+ MilkymistSysctlState *s = MILKYMIST_SYSCTL(dev);
+
+ sysbus_init_irq(dev, &s->gpio_irq);
+ sysbus_init_irq(dev, &s->timer0_irq);
+ sysbus_init_irq(dev, &s->timer1_irq);
+
+ s->bh0 = qemu_bh_new(timer0_hit, s);
+ s->bh1 = qemu_bh_new(timer1_hit, s);
+ s->ptimer0 = ptimer_init(s->bh0);
+ s->ptimer1 = ptimer_init(s->bh1);
+ ptimer_set_freq(s->ptimer0, s->freq_hz);
+ ptimer_set_freq(s->ptimer1, s->freq_hz);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &sysctl_mmio_ops, s,
+ "milkymist-sysctl", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_sysctl = {
+ .name = "milkymist-sysctl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistSysctlState, R_MAX),
+ VMSTATE_PTIMER(ptimer0, MilkymistSysctlState),
+ VMSTATE_PTIMER(ptimer1, MilkymistSysctlState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property milkymist_sysctl_properties[] = {
+ DEFINE_PROP_UINT32("frequency", MilkymistSysctlState,
+ freq_hz, 80000000),
+ DEFINE_PROP_UINT32("capabilities", MilkymistSysctlState,
+ capabilities, 0x00000000),
+ DEFINE_PROP_UINT32("systemid", MilkymistSysctlState,
+ systemid, 0x10014d31),
+ DEFINE_PROP_UINT32("gpio_strappings", MilkymistSysctlState,
+ strappings, 0x00000001),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void milkymist_sysctl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_sysctl_init;
+ dc->reset = milkymist_sysctl_reset;
+ dc->vmsd = &vmstate_milkymist_sysctl;
+ dc->props = milkymist_sysctl_properties;
+}
+
+static const TypeInfo milkymist_sysctl_info = {
+ .name = TYPE_MILKYMIST_SYSCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistSysctlState),
+ .class_init = milkymist_sysctl_class_init,
+};
+
+static void milkymist_sysctl_register_types(void)
+{
+ type_register_static(&milkymist_sysctl_info);
+}
+
+type_init(milkymist_sysctl_register_types)
diff --git a/hw/timer/omap_gptimer.c b/hw/timer/omap_gptimer.c
new file mode 100644
index 00000000..b8c8c013
--- /dev/null
+++ b/hw/timer/omap_gptimer.c
@@ -0,0 +1,488 @@
+/*
+ * TI OMAP2 general purpose timers emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/arm/omap.h"
+
+/* GP timers */
+struct omap_gp_timer_s {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq wkup;
+ qemu_irq in;
+ qemu_irq out;
+ omap_clk clk;
+ QEMUTimer *timer;
+ QEMUTimer *match;
+ struct omap_target_agent_s *ta;
+
+ int in_val;
+ int out_val;
+ int64_t time;
+ int64_t rate;
+ int64_t ticks_per_sec;
+
+ int16_t config;
+ int status;
+ int it_ena;
+ int wu_ena;
+ int enable;
+ int inout;
+ int capt2;
+ int pt;
+ enum {
+ gpt_trigger_none, gpt_trigger_overflow, gpt_trigger_both
+ } trigger;
+ enum {
+ gpt_capture_none, gpt_capture_rising,
+ gpt_capture_falling, gpt_capture_both
+ } capture;
+ int scpwm;
+ int ce;
+ int pre;
+ int ptv;
+ int ar;
+ int st;
+ int posted;
+ uint32_t val;
+ uint32_t load_val;
+ uint32_t capture_val[2];
+ uint32_t match_val;
+ int capt_num;
+
+ uint16_t writeh; /* LSB */
+ uint16_t readh; /* MSB */
+};
+
+#define GPT_TCAR_IT (1 << 2)
+#define GPT_OVF_IT (1 << 1)
+#define GPT_MAT_IT (1 << 0)
+
+static inline void omap_gp_timer_intr(struct omap_gp_timer_s *timer, int it)
+{
+ if (timer->it_ena & it) {
+ if (!timer->status)
+ qemu_irq_raise(timer->irq);
+
+ timer->status |= it;
+ /* Or are the status bits set even when masked?
+ * i.e. is masking applied before or after the status register? */
+ }
+
+ if (timer->wu_ena & it)
+ qemu_irq_pulse(timer->wkup);
+}
+
+static inline void omap_gp_timer_out(struct omap_gp_timer_s *timer, int level)
+{
+ if (!timer->inout && timer->out_val != level) {
+ timer->out_val = level;
+ qemu_set_irq(timer->out, level);
+ }
+}
+
+static inline uint32_t omap_gp_timer_read(struct omap_gp_timer_s *timer)
+{
+ uint64_t distance;
+
+ if (timer->st && timer->rate) {
+ distance = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - timer->time;
+ distance = muldiv64(distance, timer->rate, timer->ticks_per_sec);
+
+ if (distance >= 0xffffffff - timer->val)
+ return 0xffffffff;
+ else
+ return timer->val + distance;
+ } else
+ return timer->val;
+}
+
+static inline void omap_gp_timer_sync(struct omap_gp_timer_s *timer)
+{
+ if (timer->st) {
+ timer->val = omap_gp_timer_read(timer);
+ timer->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+}
+
+static inline void omap_gp_timer_update(struct omap_gp_timer_s *timer)
+{
+ int64_t expires, matches;
+
+ if (timer->st && timer->rate) {
+ expires = muldiv64(0x100000000ll - timer->val,
+ timer->ticks_per_sec, timer->rate);
+ timer_mod(timer->timer, timer->time + expires);
+
+ if (timer->ce && timer->match_val >= timer->val) {
+ matches = muldiv64(timer->match_val - timer->val,
+ timer->ticks_per_sec, timer->rate);
+ timer_mod(timer->match, timer->time + matches);
+ } else
+ timer_del(timer->match);
+ } else {
+ timer_del(timer->timer);
+ timer_del(timer->match);
+ omap_gp_timer_out(timer, timer->scpwm);
+ }
+}
+
+static inline void omap_gp_timer_trigger(struct omap_gp_timer_s *timer)
+{
+ if (timer->pt)
+ /* TODO in overflow-and-match mode if the first event to
+ * occur is the match, don't toggle. */
+ omap_gp_timer_out(timer, !timer->out_val);
+ else
+ /* TODO inverted pulse on timer->out_val == 1? */
+ qemu_irq_pulse(timer->out);
+}
+
+static void omap_gp_timer_tick(void *opaque)
+{
+ struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+ if (!timer->ar) {
+ timer->st = 0;
+ timer->val = 0;
+ } else {
+ timer->val = timer->load_val;
+ timer->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+
+ if (timer->trigger == gpt_trigger_overflow ||
+ timer->trigger == gpt_trigger_both)
+ omap_gp_timer_trigger(timer);
+
+ omap_gp_timer_intr(timer, GPT_OVF_IT);
+ omap_gp_timer_update(timer);
+}
+
+static void omap_gp_timer_match(void *opaque)
+{
+ struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+ if (timer->trigger == gpt_trigger_both)
+ omap_gp_timer_trigger(timer);
+
+ omap_gp_timer_intr(timer, GPT_MAT_IT);
+}
+
+static void omap_gp_timer_input(void *opaque, int line, int on)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+ int trigger;
+
+ switch (s->capture) {
+ default:
+ case gpt_capture_none:
+ trigger = 0;
+ break;
+ case gpt_capture_rising:
+ trigger = !s->in_val && on;
+ break;
+ case gpt_capture_falling:
+ trigger = s->in_val && !on;
+ break;
+ case gpt_capture_both:
+ trigger = (s->in_val == !on);
+ break;
+ }
+ s->in_val = on;
+
+ if (s->inout && trigger && s->capt_num < 2) {
+ s->capture_val[s->capt_num] = omap_gp_timer_read(s);
+
+ if (s->capt2 == s->capt_num ++)
+ omap_gp_timer_intr(s, GPT_TCAR_IT);
+ }
+}
+
+static void omap_gp_timer_clk_update(void *opaque, int line, int on)
+{
+ struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+ omap_gp_timer_sync(timer);
+ timer->rate = on ? omap_clk_getrate(timer->clk) : 0;
+ omap_gp_timer_update(timer);
+}
+
+static void omap_gp_timer_clk_setup(struct omap_gp_timer_s *timer)
+{
+ omap_clk_adduser(timer->clk,
+ qemu_allocate_irq(omap_gp_timer_clk_update, timer, 0));
+ timer->rate = omap_clk_getrate(timer->clk);
+}
+
+void omap_gp_timer_reset(struct omap_gp_timer_s *s)
+{
+ s->config = 0x000;
+ s->status = 0;
+ s->it_ena = 0;
+ s->wu_ena = 0;
+ s->inout = 0;
+ s->capt2 = 0;
+ s->capt_num = 0;
+ s->pt = 0;
+ s->trigger = gpt_trigger_none;
+ s->capture = gpt_capture_none;
+ s->scpwm = 0;
+ s->ce = 0;
+ s->pre = 0;
+ s->ptv = 0;
+ s->ar = 0;
+ s->st = 0;
+ s->posted = 1;
+ s->val = 0x00000000;
+ s->load_val = 0x00000000;
+ s->capture_val[0] = 0x00000000;
+ s->capture_val[1] = 0x00000000;
+ s->match_val = 0x00000000;
+ omap_gp_timer_update(s);
+}
+
+static uint32_t omap_gp_timer_readw(void *opaque, hwaddr addr)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* TIDR */
+ return 0x21;
+
+ case 0x10: /* TIOCP_CFG */
+ return s->config;
+
+ case 0x14: /* TISTAT */
+ /* ??? When's this bit reset? */
+ return 1; /* RESETDONE */
+
+ case 0x18: /* TISR */
+ return s->status;
+
+ case 0x1c: /* TIER */
+ return s->it_ena;
+
+ case 0x20: /* TWER */
+ return s->wu_ena;
+
+ case 0x24: /* TCLR */
+ return (s->inout << 14) |
+ (s->capt2 << 13) |
+ (s->pt << 12) |
+ (s->trigger << 10) |
+ (s->capture << 8) |
+ (s->scpwm << 7) |
+ (s->ce << 6) |
+ (s->pre << 5) |
+ (s->ptv << 2) |
+ (s->ar << 1) |
+ (s->st << 0);
+
+ case 0x28: /* TCRR */
+ return omap_gp_timer_read(s);
+
+ case 0x2c: /* TLDR */
+ return s->load_val;
+
+ case 0x30: /* TTGR */
+ return 0xffffffff;
+
+ case 0x34: /* TWPS */
+ return 0x00000000; /* No posted writes pending. */
+
+ case 0x38: /* TMAR */
+ return s->match_val;
+
+ case 0x3c: /* TCAR1 */
+ return s->capture_val[0];
+
+ case 0x40: /* TSICR */
+ return s->posted << 2;
+
+ case 0x44: /* TCAR2 */
+ return s->capture_val[1];
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static uint32_t omap_gp_timer_readh(void *opaque, hwaddr addr)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+ uint32_t ret;
+
+ if (addr & 2)
+ return s->readh;
+ else {
+ ret = omap_gp_timer_readw(opaque, addr);
+ s->readh = ret >> 16;
+ return ret & 0xffff;
+ }
+}
+
+static void omap_gp_timer_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* TIDR */
+ case 0x14: /* TISTAT */
+ case 0x34: /* TWPS */
+ case 0x3c: /* TCAR1 */
+ case 0x44: /* TCAR2 */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* TIOCP_CFG */
+ s->config = value & 0x33d;
+ if (((value >> 3) & 3) == 3) /* IDLEMODE */
+ fprintf(stderr, "%s: illegal IDLEMODE value in TIOCP_CFG\n",
+ __FUNCTION__);
+ if (value & 2) /* SOFTRESET */
+ omap_gp_timer_reset(s);
+ break;
+
+ case 0x18: /* TISR */
+ if (value & GPT_TCAR_IT)
+ s->capt_num = 0;
+ if (s->status && !(s->status &= ~value))
+ qemu_irq_lower(s->irq);
+ break;
+
+ case 0x1c: /* TIER */
+ s->it_ena = value & 7;
+ break;
+
+ case 0x20: /* TWER */
+ s->wu_ena = value & 7;
+ break;
+
+ case 0x24: /* TCLR */
+ omap_gp_timer_sync(s);
+ s->inout = (value >> 14) & 1;
+ s->capt2 = (value >> 13) & 1;
+ s->pt = (value >> 12) & 1;
+ s->trigger = (value >> 10) & 3;
+ if (s->capture == gpt_capture_none &&
+ ((value >> 8) & 3) != gpt_capture_none)
+ s->capt_num = 0;
+ s->capture = (value >> 8) & 3;
+ s->scpwm = (value >> 7) & 1;
+ s->ce = (value >> 6) & 1;
+ s->pre = (value >> 5) & 1;
+ s->ptv = (value >> 2) & 7;
+ s->ar = (value >> 1) & 1;
+ s->st = (value >> 0) & 1;
+ if (s->inout && s->trigger != gpt_trigger_none)
+ fprintf(stderr, "%s: GP timer pin must be an output "
+ "for this trigger mode\n", __FUNCTION__);
+ if (!s->inout && s->capture != gpt_capture_none)
+ fprintf(stderr, "%s: GP timer pin must be an input "
+ "for this capture mode\n", __FUNCTION__);
+ if (s->trigger == gpt_trigger_none)
+ omap_gp_timer_out(s, s->scpwm);
+ /* TODO: make sure this doesn't overflow 32-bits */
+ s->ticks_per_sec = get_ticks_per_sec() << (s->pre ? s->ptv + 1 : 0);
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x28: /* TCRR */
+ s->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->val = value;
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x2c: /* TLDR */
+ s->load_val = value;
+ break;
+
+ case 0x30: /* TTGR */
+ s->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->val = s->load_val;
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x38: /* TMAR */
+ omap_gp_timer_sync(s);
+ s->match_val = value;
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x40: /* TSICR */
+ s->posted = (value >> 2) & 1;
+ if (value & 2) /* How much exactly are we supposed to reset? */
+ omap_gp_timer_reset(s);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static void omap_gp_timer_writeh(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+
+ if (addr & 2)
+ omap_gp_timer_write(opaque, addr, (value << 16) | s->writeh);
+ else
+ s->writeh = (uint16_t) value;
+}
+
+static const MemoryRegionOps omap_gp_timer_ops = {
+ .old_mmio = {
+ .read = {
+ omap_badwidth_read32,
+ omap_gp_timer_readh,
+ omap_gp_timer_readw,
+ },
+ .write = {
+ omap_badwidth_write32,
+ omap_gp_timer_writeh,
+ omap_gp_timer_write,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_gp_timer_s *omap_gp_timer_init(struct omap_target_agent_s *ta,
+ qemu_irq irq, omap_clk fclk, omap_clk iclk)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *)
+ g_malloc0(sizeof(struct omap_gp_timer_s));
+
+ s->ta = ta;
+ s->irq = irq;
+ s->clk = fclk;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_gp_timer_tick, s);
+ s->match = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_gp_timer_match, s);
+ s->in = qemu_allocate_irq(omap_gp_timer_input, s, 0);
+ omap_gp_timer_reset(s);
+ omap_gp_timer_clk_setup(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_gp_timer_ops, s, "omap.gptimer",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ return s;
+}
diff --git a/hw/timer/omap_synctimer.c b/hw/timer/omap_synctimer.c
new file mode 100644
index 00000000..8e50488d
--- /dev/null
+++ b/hw/timer/omap_synctimer.c
@@ -0,0 +1,102 @@
+/*
+ * TI OMAP2 32kHz sync timer emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/arm/omap.h"
+struct omap_synctimer_s {
+ MemoryRegion iomem;
+ uint32_t val;
+ uint16_t readh;
+};
+
+/* 32-kHz Sync Timer of the OMAP2 */
+static uint32_t omap_synctimer_read(struct omap_synctimer_s *s) {
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 0x8000, get_ticks_per_sec());
+}
+
+void omap_synctimer_reset(struct omap_synctimer_s *s)
+{
+ s->val = omap_synctimer_read(s);
+}
+
+static uint32_t omap_synctimer_readw(void *opaque, hwaddr addr)
+{
+ struct omap_synctimer_s *s = (struct omap_synctimer_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* 32KSYNCNT_REV */
+ return 0x21;
+
+ case 0x10: /* CR */
+ return omap_synctimer_read(s) - s->val;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static uint32_t omap_synctimer_readh(void *opaque, hwaddr addr)
+{
+ struct omap_synctimer_s *s = (struct omap_synctimer_s *) opaque;
+ uint32_t ret;
+
+ if (addr & 2)
+ return s->readh;
+ else {
+ ret = omap_synctimer_readw(opaque, addr);
+ s->readh = ret >> 16;
+ return ret & 0xffff;
+ }
+}
+
+static void omap_synctimer_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap_synctimer_ops = {
+ .old_mmio = {
+ .read = {
+ omap_badwidth_read32,
+ omap_synctimer_readh,
+ omap_synctimer_readw,
+ },
+ .write = {
+ omap_badwidth_write32,
+ omap_synctimer_write,
+ omap_synctimer_write,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_synctimer_s *omap_synctimer_init(struct omap_target_agent_s *ta,
+ struct omap_mpu_state_s *mpu, omap_clk fclk, omap_clk iclk)
+{
+ struct omap_synctimer_s *s = g_malloc0(sizeof(*s));
+
+ omap_synctimer_reset(s);
+ memory_region_init_io(&s->iomem, NULL, &omap_synctimer_ops, s, "omap.synctimer",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ return s;
+}
diff --git a/hw/timer/pl031.c b/hw/timer/pl031.c
new file mode 100644
index 00000000..34d9b44e
--- /dev/null
+++ b/hw/timer/pl031.c
@@ -0,0 +1,269 @@
+/*
+ * ARM AMBA PrimeCell PL031 RTC
+ *
+ * Copyright (c) 2007 CodeSourcery
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+
+//#define DEBUG_PL031
+
+#ifdef DEBUG_PL031
+#define DPRINTF(fmt, ...) \
+do { printf("pl031: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define RTC_DR 0x00 /* Data read register */
+#define RTC_MR 0x04 /* Match register */
+#define RTC_LR 0x08 /* Data load register */
+#define RTC_CR 0x0c /* Control register */
+#define RTC_IMSC 0x10 /* Interrupt mask and set register */
+#define RTC_RIS 0x14 /* Raw interrupt status register */
+#define RTC_MIS 0x18 /* Masked interrupt status register */
+#define RTC_ICR 0x1c /* Interrupt clear register */
+
+#define TYPE_PL031 "pl031"
+#define PL031(obj) OBJECT_CHECK(PL031State, (obj), TYPE_PL031)
+
+typedef struct PL031State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ QEMUTimer *timer;
+ qemu_irq irq;
+
+ /* Needed to preserve the tick_count across migration, even if the
+ * absolute value of the rtc_clock is different on the source and
+ * destination.
+ */
+ uint32_t tick_offset_vmstate;
+ uint32_t tick_offset;
+
+ uint32_t mr;
+ uint32_t lr;
+ uint32_t cr;
+ uint32_t im;
+ uint32_t is;
+} PL031State;
+
+static const unsigned char pl031_id[] = {
+ 0x31, 0x10, 0x14, 0x00, /* Device ID */
+ 0x0d, 0xf0, 0x05, 0xb1 /* Cell ID */
+};
+
+static void pl031_update(PL031State *s)
+{
+ qemu_set_irq(s->irq, s->is & s->im);
+}
+
+static void pl031_interrupt(void * opaque)
+{
+ PL031State *s = (PL031State *)opaque;
+
+ s->is = 1;
+ DPRINTF("Alarm raised\n");
+ pl031_update(s);
+}
+
+static uint32_t pl031_get_count(PL031State *s)
+{
+ int64_t now = qemu_clock_get_ns(rtc_clock);
+ return s->tick_offset + now / get_ticks_per_sec();
+}
+
+static void pl031_set_alarm(PL031State *s)
+{
+ uint32_t ticks;
+
+ /* The timer wraps around. This subtraction also wraps in the same way,
+ and gives correct results when alarm < now_ticks. */
+ ticks = s->mr - pl031_get_count(s);
+ DPRINTF("Alarm set in %ud ticks\n", ticks);
+ if (ticks == 0) {
+ timer_del(s->timer);
+ pl031_interrupt(s);
+ } else {
+ int64_t now = qemu_clock_get_ns(rtc_clock);
+ timer_mod(s->timer, now + (int64_t)ticks * get_ticks_per_sec());
+ }
+}
+
+static uint64_t pl031_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL031State *s = (PL031State *)opaque;
+
+ if (offset >= 0xfe0 && offset < 0x1000)
+ return pl031_id[(offset - 0xfe0) >> 2];
+
+ switch (offset) {
+ case RTC_DR:
+ return pl031_get_count(s);
+ case RTC_MR:
+ return s->mr;
+ case RTC_IMSC:
+ return s->im;
+ case RTC_RIS:
+ return s->is;
+ case RTC_LR:
+ return s->lr;
+ case RTC_CR:
+ /* RTC is permanently enabled. */
+ return 1;
+ case RTC_MIS:
+ return s->is & s->im;
+ case RTC_ICR:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl031: read of write-only register at offset 0x%x\n",
+ (int)offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl031_read: Bad offset 0x%x\n", (int)offset);
+ break;
+ }
+
+ return 0;
+}
+
+static void pl031_write(void * opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL031State *s = (PL031State *)opaque;
+
+
+ switch (offset) {
+ case RTC_LR:
+ s->tick_offset += value - pl031_get_count(s);
+ pl031_set_alarm(s);
+ break;
+ case RTC_MR:
+ s->mr = value;
+ pl031_set_alarm(s);
+ break;
+ case RTC_IMSC:
+ s->im = value & 1;
+ DPRINTF("Interrupt mask %d\n", s->im);
+ pl031_update(s);
+ break;
+ case RTC_ICR:
+ /* The PL031 documentation (DDI0224B) states that the interrupt is
+ cleared when bit 0 of the written value is set. However the
+ arm926e documentation (DDI0287B) states that the interrupt is
+ cleared when any value is written. */
+ DPRINTF("Interrupt cleared");
+ s->is = 0;
+ pl031_update(s);
+ break;
+ case RTC_CR:
+ /* Written value is ignored. */
+ break;
+
+ case RTC_DR:
+ case RTC_MIS:
+ case RTC_RIS:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl031: write to read-only register at offset 0x%x\n",
+ (int)offset);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl031_write: Bad offset 0x%x\n", (int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps pl031_ops = {
+ .read = pl031_read,
+ .write = pl031_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pl031_init(SysBusDevice *dev)
+{
+ PL031State *s = PL031(dev);
+ struct tm tm;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl031_ops, s, "pl031", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ sysbus_init_irq(dev, &s->irq);
+ qemu_get_timedate(&tm, 0);
+ s->tick_offset = mktimegm(&tm) -
+ qemu_clock_get_ns(rtc_clock) / get_ticks_per_sec();
+
+ s->timer = timer_new_ns(rtc_clock, pl031_interrupt, s);
+ return 0;
+}
+
+static void pl031_pre_save(void *opaque)
+{
+ PL031State *s = opaque;
+
+ /* tick_offset is base_time - rtc_clock base time. Instead, we want to
+ * store the base time relative to the QEMU_CLOCK_VIRTUAL for backwards-compatibility. */
+ int64_t delta = qemu_clock_get_ns(rtc_clock) - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->tick_offset_vmstate = s->tick_offset + delta / get_ticks_per_sec();
+}
+
+static int pl031_post_load(void *opaque, int version_id)
+{
+ PL031State *s = opaque;
+
+ int64_t delta = qemu_clock_get_ns(rtc_clock) - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->tick_offset = s->tick_offset_vmstate - delta / get_ticks_per_sec();
+ pl031_set_alarm(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_pl031 = {
+ .name = "pl031",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = pl031_pre_save,
+ .post_load = pl031_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(tick_offset_vmstate, PL031State),
+ VMSTATE_UINT32(mr, PL031State),
+ VMSTATE_UINT32(lr, PL031State),
+ VMSTATE_UINT32(cr, PL031State),
+ VMSTATE_UINT32(im, PL031State),
+ VMSTATE_UINT32(is, PL031State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pl031_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pl031_init;
+ dc->vmsd = &vmstate_pl031;
+}
+
+static const TypeInfo pl031_info = {
+ .name = TYPE_PL031,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL031State),
+ .class_init = pl031_class_init,
+};
+
+static void pl031_register_types(void)
+{
+ type_register_static(&pl031_info);
+}
+
+type_init(pl031_register_types)
diff --git a/hw/timer/puv3_ost.c b/hw/timer/puv3_ost.c
new file mode 100644
index 00000000..fa9eefd9
--- /dev/null
+++ b/hw/timer/puv3_ost.c
@@ -0,0 +1,156 @@
+/*
+ * OSTimer device simulation in PKUnity SoC
+ *
+ * Copyright (C) 2010-2012 Guan Xuetao
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation, or any later version.
+ * See the COPYING file in the top-level directory.
+ */
+#include "hw/sysbus.h"
+#include "hw/ptimer.h"
+#include "qemu/main-loop.h"
+
+#undef DEBUG_PUV3
+#include "hw/unicore32/puv3.h"
+
+#define TYPE_PUV3_OST "puv3_ost"
+#define PUV3_OST(obj) OBJECT_CHECK(PUV3OSTState, (obj), TYPE_PUV3_OST)
+
+/* puv3 ostimer implementation. */
+typedef struct PUV3OSTState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ QEMUBH *bh;
+ qemu_irq irq;
+ ptimer_state *ptimer;
+
+ uint32_t reg_OSMR0;
+ uint32_t reg_OSCR;
+ uint32_t reg_OSSR;
+ uint32_t reg_OIER;
+} PUV3OSTState;
+
+static uint64_t puv3_ost_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PUV3OSTState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (offset) {
+ case 0x10: /* Counter Register */
+ ret = s->reg_OSMR0 - (uint32_t)ptimer_get_count(s->ptimer);
+ break;
+ case 0x14: /* Status Register */
+ ret = s->reg_OSSR;
+ break;
+ case 0x1c: /* Interrupt Enable Register */
+ ret = s->reg_OIER;
+ break;
+ default:
+ DPRINTF("Bad offset %x\n", (int)offset);
+ }
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, ret);
+ return ret;
+}
+
+static void puv3_ost_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PUV3OSTState *s = opaque;
+
+ DPRINTF("offset 0x%x, value 0x%x\n", offset, value);
+ switch (offset) {
+ case 0x00: /* Match Register 0 */
+ s->reg_OSMR0 = value;
+ if (s->reg_OSMR0 > s->reg_OSCR) {
+ ptimer_set_count(s->ptimer, s->reg_OSMR0 - s->reg_OSCR);
+ } else {
+ ptimer_set_count(s->ptimer, s->reg_OSMR0 +
+ (0xffffffff - s->reg_OSCR));
+ }
+ ptimer_run(s->ptimer, 2);
+ break;
+ case 0x14: /* Status Register */
+ assert(value == 0);
+ if (s->reg_OSSR) {
+ s->reg_OSSR = value;
+ qemu_irq_lower(s->irq);
+ }
+ break;
+ case 0x1c: /* Interrupt Enable Register */
+ s->reg_OIER = value;
+ break;
+ default:
+ DPRINTF("Bad offset %x\n", (int)offset);
+ }
+}
+
+static const MemoryRegionOps puv3_ost_ops = {
+ .read = puv3_ost_read,
+ .write = puv3_ost_write,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void puv3_ost_tick(void *opaque)
+{
+ PUV3OSTState *s = opaque;
+
+ DPRINTF("ost hit when ptimer counter from 0x%x to 0x%x!\n",
+ s->reg_OSCR, s->reg_OSMR0);
+
+ s->reg_OSCR = s->reg_OSMR0;
+ if (s->reg_OIER) {
+ s->reg_OSSR = 1;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static int puv3_ost_init(SysBusDevice *dev)
+{
+ PUV3OSTState *s = PUV3_OST(dev);
+
+ s->reg_OIER = 0;
+ s->reg_OSSR = 0;
+ s->reg_OSMR0 = 0;
+ s->reg_OSCR = 0;
+
+ sysbus_init_irq(dev, &s->irq);
+
+ s->bh = qemu_bh_new(puv3_ost_tick, s);
+ s->ptimer = ptimer_init(s->bh);
+ ptimer_set_freq(s->ptimer, 50 * 1000 * 1000);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &puv3_ost_ops, s, "puv3_ost",
+ PUV3_REGS_OFFSET);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void puv3_ost_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = puv3_ost_init;
+}
+
+static const TypeInfo puv3_ost_info = {
+ .name = TYPE_PUV3_OST,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PUV3OSTState),
+ .class_init = puv3_ost_class_init,
+};
+
+static void puv3_ost_register_type(void)
+{
+ type_register_static(&puv3_ost_info);
+}
+
+type_init(puv3_ost_register_type)
diff --git a/hw/timer/pxa2xx_timer.c b/hw/timer/pxa2xx_timer.c
new file mode 100644
index 00000000..130e9dc3
--- /dev/null
+++ b/hw/timer/pxa2xx_timer.c
@@ -0,0 +1,596 @@
+/*
+ * Intel XScale PXA255/270 OS Timers.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Copyright (c) 2006 Thorsten Zitterell
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/arm/pxa.h"
+#include "hw/sysbus.h"
+
+#define OSMR0 0x00
+#define OSMR1 0x04
+#define OSMR2 0x08
+#define OSMR3 0x0c
+#define OSMR4 0x80
+#define OSMR5 0x84
+#define OSMR6 0x88
+#define OSMR7 0x8c
+#define OSMR8 0x90
+#define OSMR9 0x94
+#define OSMR10 0x98
+#define OSMR11 0x9c
+#define OSCR 0x10 /* OS Timer Count */
+#define OSCR4 0x40
+#define OSCR5 0x44
+#define OSCR6 0x48
+#define OSCR7 0x4c
+#define OSCR8 0x50
+#define OSCR9 0x54
+#define OSCR10 0x58
+#define OSCR11 0x5c
+#define OSSR 0x14 /* Timer status register */
+#define OWER 0x18
+#define OIER 0x1c /* Interrupt enable register 3-0 to E3-E0 */
+#define OMCR4 0xc0 /* OS Match Control registers */
+#define OMCR5 0xc4
+#define OMCR6 0xc8
+#define OMCR7 0xcc
+#define OMCR8 0xd0
+#define OMCR9 0xd4
+#define OMCR10 0xd8
+#define OMCR11 0xdc
+#define OSNR 0x20
+
+#define PXA25X_FREQ 3686400 /* 3.6864 MHz */
+#define PXA27X_FREQ 3250000 /* 3.25 MHz */
+
+static int pxa2xx_timer4_freq[8] = {
+ [0] = 0,
+ [1] = 32768,
+ [2] = 1000,
+ [3] = 1,
+ [4] = 1000000,
+ /* [5] is the "Externally supplied clock". Assign if necessary. */
+ [5 ... 7] = 0,
+};
+
+#define TYPE_PXA2XX_TIMER "pxa2xx-timer"
+#define PXA2XX_TIMER(obj) \
+ OBJECT_CHECK(PXA2xxTimerInfo, (obj), TYPE_PXA2XX_TIMER)
+
+typedef struct PXA2xxTimerInfo PXA2xxTimerInfo;
+
+typedef struct {
+ uint32_t value;
+ qemu_irq irq;
+ QEMUTimer *qtimer;
+ int num;
+ PXA2xxTimerInfo *info;
+} PXA2xxTimer0;
+
+typedef struct {
+ PXA2xxTimer0 tm;
+ int32_t oldclock;
+ int32_t clock;
+ uint64_t lastload;
+ uint32_t freq;
+ uint32_t control;
+} PXA2xxTimer4;
+
+struct PXA2xxTimerInfo {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t flags;
+
+ int32_t clock;
+ int32_t oldclock;
+ uint64_t lastload;
+ uint32_t freq;
+ PXA2xxTimer0 timer[4];
+ uint32_t events;
+ uint32_t irq_enabled;
+ uint32_t reset3;
+ uint32_t snapshot;
+
+ qemu_irq irq4;
+ PXA2xxTimer4 tm4[8];
+};
+
+#define PXA2XX_TIMER_HAVE_TM4 0
+
+static inline int pxa2xx_timer_has_tm4(PXA2xxTimerInfo *s)
+{
+ return s->flags & (1 << PXA2XX_TIMER_HAVE_TM4);
+}
+
+static void pxa2xx_timer_update(void *opaque, uint64_t now_qemu)
+{
+ PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
+ int i;
+ uint32_t now_vm;
+ uint64_t new_qemu;
+
+ now_vm = s->clock +
+ muldiv64(now_qemu - s->lastload, s->freq, get_ticks_per_sec());
+
+ for (i = 0; i < 4; i ++) {
+ new_qemu = now_qemu + muldiv64((uint32_t) (s->timer[i].value - now_vm),
+ get_ticks_per_sec(), s->freq);
+ timer_mod(s->timer[i].qtimer, new_qemu);
+ }
+}
+
+static void pxa2xx_timer_update4(void *opaque, uint64_t now_qemu, int n)
+{
+ PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
+ uint32_t now_vm;
+ uint64_t new_qemu;
+ static const int counters[8] = { 0, 0, 0, 0, 4, 4, 6, 6 };
+ int counter;
+
+ if (s->tm4[n].control & (1 << 7))
+ counter = n;
+ else
+ counter = counters[n];
+
+ if (!s->tm4[counter].freq) {
+ timer_del(s->tm4[n].tm.qtimer);
+ return;
+ }
+
+ now_vm = s->tm4[counter].clock + muldiv64(now_qemu -
+ s->tm4[counter].lastload,
+ s->tm4[counter].freq, get_ticks_per_sec());
+
+ new_qemu = now_qemu + muldiv64((uint32_t) (s->tm4[n].tm.value - now_vm),
+ get_ticks_per_sec(), s->tm4[counter].freq);
+ timer_mod(s->tm4[n].tm.qtimer, new_qemu);
+}
+
+static uint64_t pxa2xx_timer_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
+ int tm = 0;
+
+ switch (offset) {
+ case OSMR3: tm ++;
+ /* fall through */
+ case OSMR2: tm ++;
+ /* fall through */
+ case OSMR1: tm ++;
+ /* fall through */
+ case OSMR0:
+ return s->timer[tm].value;
+ case OSMR11: tm ++;
+ /* fall through */
+ case OSMR10: tm ++;
+ /* fall through */
+ case OSMR9: tm ++;
+ /* fall through */
+ case OSMR8: tm ++;
+ /* fall through */
+ case OSMR7: tm ++;
+ /* fall through */
+ case OSMR6: tm ++;
+ /* fall through */
+ case OSMR5: tm ++;
+ /* fall through */
+ case OSMR4:
+ if (!pxa2xx_timer_has_tm4(s))
+ goto badreg;
+ return s->tm4[tm].tm.value;
+ case OSCR:
+ return s->clock + muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
+ s->lastload, s->freq, get_ticks_per_sec());
+ case OSCR11: tm ++;
+ /* fall through */
+ case OSCR10: tm ++;
+ /* fall through */
+ case OSCR9: tm ++;
+ /* fall through */
+ case OSCR8: tm ++;
+ /* fall through */
+ case OSCR7: tm ++;
+ /* fall through */
+ case OSCR6: tm ++;
+ /* fall through */
+ case OSCR5: tm ++;
+ /* fall through */
+ case OSCR4:
+ if (!pxa2xx_timer_has_tm4(s))
+ goto badreg;
+
+ if ((tm == 9 - 4 || tm == 11 - 4) && (s->tm4[tm].control & (1 << 9))) {
+ if (s->tm4[tm - 1].freq)
+ s->snapshot = s->tm4[tm - 1].clock + muldiv64(
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
+ s->tm4[tm - 1].lastload,
+ s->tm4[tm - 1].freq, get_ticks_per_sec());
+ else
+ s->snapshot = s->tm4[tm - 1].clock;
+ }
+
+ if (!s->tm4[tm].freq)
+ return s->tm4[tm].clock;
+ return s->tm4[tm].clock + muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
+ s->tm4[tm].lastload, s->tm4[tm].freq, get_ticks_per_sec());
+ case OIER:
+ return s->irq_enabled;
+ case OSSR: /* Status register */
+ return s->events;
+ case OWER:
+ return s->reset3;
+ case OMCR11: tm ++;
+ /* fall through */
+ case OMCR10: tm ++;
+ /* fall through */
+ case OMCR9: tm ++;
+ /* fall through */
+ case OMCR8: tm ++;
+ /* fall through */
+ case OMCR7: tm ++;
+ /* fall through */
+ case OMCR6: tm ++;
+ /* fall through */
+ case OMCR5: tm ++;
+ /* fall through */
+ case OMCR4:
+ if (!pxa2xx_timer_has_tm4(s))
+ goto badreg;
+ return s->tm4[tm].control;
+ case OSNR:
+ return s->snapshot;
+ default:
+ badreg:
+ hw_error("pxa2xx_timer_read: Bad offset " REG_FMT "\n", offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_timer_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ int i, tm = 0;
+ PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
+
+ switch (offset) {
+ case OSMR3: tm ++;
+ /* fall through */
+ case OSMR2: tm ++;
+ /* fall through */
+ case OSMR1: tm ++;
+ /* fall through */
+ case OSMR0:
+ s->timer[tm].value = value;
+ pxa2xx_timer_update(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ case OSMR11: tm ++;
+ /* fall through */
+ case OSMR10: tm ++;
+ /* fall through */
+ case OSMR9: tm ++;
+ /* fall through */
+ case OSMR8: tm ++;
+ /* fall through */
+ case OSMR7: tm ++;
+ /* fall through */
+ case OSMR6: tm ++;
+ /* fall through */
+ case OSMR5: tm ++;
+ /* fall through */
+ case OSMR4:
+ if (!pxa2xx_timer_has_tm4(s))
+ goto badreg;
+ s->tm4[tm].tm.value = value;
+ pxa2xx_timer_update4(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tm);
+ break;
+ case OSCR:
+ s->oldclock = s->clock;
+ s->lastload = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->clock = value;
+ pxa2xx_timer_update(s, s->lastload);
+ break;
+ case OSCR11: tm ++;
+ /* fall through */
+ case OSCR10: tm ++;
+ /* fall through */
+ case OSCR9: tm ++;
+ /* fall through */
+ case OSCR8: tm ++;
+ /* fall through */
+ case OSCR7: tm ++;
+ /* fall through */
+ case OSCR6: tm ++;
+ /* fall through */
+ case OSCR5: tm ++;
+ /* fall through */
+ case OSCR4:
+ if (!pxa2xx_timer_has_tm4(s))
+ goto badreg;
+ s->tm4[tm].oldclock = s->tm4[tm].clock;
+ s->tm4[tm].lastload = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->tm4[tm].clock = value;
+ pxa2xx_timer_update4(s, s->tm4[tm].lastload, tm);
+ break;
+ case OIER:
+ s->irq_enabled = value & 0xfff;
+ break;
+ case OSSR: /* Status register */
+ value &= s->events;
+ s->events &= ~value;
+ for (i = 0; i < 4; i ++, value >>= 1)
+ if (value & 1)
+ qemu_irq_lower(s->timer[i].irq);
+ if (pxa2xx_timer_has_tm4(s) && !(s->events & 0xff0) && value)
+ qemu_irq_lower(s->irq4);
+ break;
+ case OWER: /* XXX: Reset on OSMR3 match? */
+ s->reset3 = value;
+ break;
+ case OMCR7: tm ++;
+ /* fall through */
+ case OMCR6: tm ++;
+ /* fall through */
+ case OMCR5: tm ++;
+ /* fall through */
+ case OMCR4:
+ if (!pxa2xx_timer_has_tm4(s))
+ goto badreg;
+ s->tm4[tm].control = value & 0x0ff;
+ /* XXX Stop if running (shouldn't happen) */
+ if ((value & (1 << 7)) || tm == 0)
+ s->tm4[tm].freq = pxa2xx_timer4_freq[value & 7];
+ else {
+ s->tm4[tm].freq = 0;
+ pxa2xx_timer_update4(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tm);
+ }
+ break;
+ case OMCR11: tm ++;
+ /* fall through */
+ case OMCR10: tm ++;
+ /* fall through */
+ case OMCR9: tm ++;
+ /* fall through */
+ case OMCR8: tm += 4;
+ if (!pxa2xx_timer_has_tm4(s))
+ goto badreg;
+ s->tm4[tm].control = value & 0x3ff;
+ /* XXX Stop if running (shouldn't happen) */
+ if ((value & (1 << 7)) || !(tm & 1))
+ s->tm4[tm].freq =
+ pxa2xx_timer4_freq[(value & (1 << 8)) ? 0 : (value & 7)];
+ else {
+ s->tm4[tm].freq = 0;
+ pxa2xx_timer_update4(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tm);
+ }
+ break;
+ default:
+ badreg:
+ hw_error("pxa2xx_timer_write: Bad offset " REG_FMT "\n", offset);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_timer_ops = {
+ .read = pxa2xx_timer_read,
+ .write = pxa2xx_timer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pxa2xx_timer_tick(void *opaque)
+{
+ PXA2xxTimer0 *t = (PXA2xxTimer0 *) opaque;
+ PXA2xxTimerInfo *i = t->info;
+
+ if (i->irq_enabled & (1 << t->num)) {
+ i->events |= 1 << t->num;
+ qemu_irq_raise(t->irq);
+ }
+
+ if (t->num == 3)
+ if (i->reset3 & 1) {
+ i->reset3 = 0;
+ qemu_system_reset_request();
+ }
+}
+
+static void pxa2xx_timer_tick4(void *opaque)
+{
+ PXA2xxTimer4 *t = (PXA2xxTimer4 *) opaque;
+ PXA2xxTimerInfo *i = (PXA2xxTimerInfo *) t->tm.info;
+
+ pxa2xx_timer_tick(&t->tm);
+ if (t->control & (1 << 3))
+ t->clock = 0;
+ if (t->control & (1 << 6))
+ pxa2xx_timer_update4(i, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), t->tm.num - 4);
+ if (i->events & 0xff0)
+ qemu_irq_raise(i->irq4);
+}
+
+static int pxa25x_timer_post_load(void *opaque, int version_id)
+{
+ PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
+ int64_t now;
+ int i;
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ pxa2xx_timer_update(s, now);
+
+ if (pxa2xx_timer_has_tm4(s))
+ for (i = 0; i < 8; i ++)
+ pxa2xx_timer_update4(s, now, i);
+
+ return 0;
+}
+
+static int pxa2xx_timer_init(SysBusDevice *dev)
+{
+ PXA2xxTimerInfo *s = PXA2XX_TIMER(dev);
+ int i;
+
+ s->irq_enabled = 0;
+ s->oldclock = 0;
+ s->clock = 0;
+ s->lastload = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->reset3 = 0;
+
+ for (i = 0; i < 4; i ++) {
+ s->timer[i].value = 0;
+ sysbus_init_irq(dev, &s->timer[i].irq);
+ s->timer[i].info = s;
+ s->timer[i].num = i;
+ s->timer[i].qtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ pxa2xx_timer_tick, &s->timer[i]);
+ }
+ if (s->flags & (1 << PXA2XX_TIMER_HAVE_TM4)) {
+ sysbus_init_irq(dev, &s->irq4);
+
+ for (i = 0; i < 8; i ++) {
+ s->tm4[i].tm.value = 0;
+ s->tm4[i].tm.info = s;
+ s->tm4[i].tm.num = i + 4;
+ s->tm4[i].freq = 0;
+ s->tm4[i].control = 0x0;
+ s->tm4[i].tm.qtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ pxa2xx_timer_tick4, &s->tm4[i]);
+ }
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_timer_ops, s,
+ "pxa2xx-timer", 0x00001000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_pxa2xx_timer0_regs = {
+ .name = "pxa2xx_timer0",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(value, PXA2xxTimer0),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static const VMStateDescription vmstate_pxa2xx_timer4_regs = {
+ .name = "pxa2xx_timer4",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(tm, PXA2xxTimer4, 1,
+ vmstate_pxa2xx_timer0_regs, PXA2xxTimer0),
+ VMSTATE_INT32(oldclock, PXA2xxTimer4),
+ VMSTATE_INT32(clock, PXA2xxTimer4),
+ VMSTATE_UINT64(lastload, PXA2xxTimer4),
+ VMSTATE_UINT32(freq, PXA2xxTimer4),
+ VMSTATE_UINT32(control, PXA2xxTimer4),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static bool pxa2xx_timer_has_tm4_test(void *opaque, int version_id)
+{
+ return pxa2xx_timer_has_tm4(opaque);
+}
+
+static const VMStateDescription vmstate_pxa2xx_timer_regs = {
+ .name = "pxa2xx_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pxa25x_timer_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(clock, PXA2xxTimerInfo),
+ VMSTATE_INT32(oldclock, PXA2xxTimerInfo),
+ VMSTATE_UINT64(lastload, PXA2xxTimerInfo),
+ VMSTATE_STRUCT_ARRAY(timer, PXA2xxTimerInfo, 4, 1,
+ vmstate_pxa2xx_timer0_regs, PXA2xxTimer0),
+ VMSTATE_UINT32(events, PXA2xxTimerInfo),
+ VMSTATE_UINT32(irq_enabled, PXA2xxTimerInfo),
+ VMSTATE_UINT32(reset3, PXA2xxTimerInfo),
+ VMSTATE_UINT32(snapshot, PXA2xxTimerInfo),
+ VMSTATE_STRUCT_ARRAY_TEST(tm4, PXA2xxTimerInfo, 8,
+ pxa2xx_timer_has_tm4_test, 0,
+ vmstate_pxa2xx_timer4_regs, PXA2xxTimer4),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property pxa25x_timer_dev_properties[] = {
+ DEFINE_PROP_UINT32("freq", PXA2xxTimerInfo, freq, PXA25X_FREQ),
+ DEFINE_PROP_BIT("tm4", PXA2xxTimerInfo, flags,
+ PXA2XX_TIMER_HAVE_TM4, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxa25x_timer_dev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "PXA25x timer";
+ dc->props = pxa25x_timer_dev_properties;
+}
+
+static const TypeInfo pxa25x_timer_dev_info = {
+ .name = "pxa25x-timer",
+ .parent = TYPE_PXA2XX_TIMER,
+ .instance_size = sizeof(PXA2xxTimerInfo),
+ .class_init = pxa25x_timer_dev_class_init,
+};
+
+static Property pxa27x_timer_dev_properties[] = {
+ DEFINE_PROP_UINT32("freq", PXA2xxTimerInfo, freq, PXA27X_FREQ),
+ DEFINE_PROP_BIT("tm4", PXA2xxTimerInfo, flags,
+ PXA2XX_TIMER_HAVE_TM4, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxa27x_timer_dev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "PXA27x timer";
+ dc->props = pxa27x_timer_dev_properties;
+}
+
+static const TypeInfo pxa27x_timer_dev_info = {
+ .name = "pxa27x-timer",
+ .parent = TYPE_PXA2XX_TIMER,
+ .instance_size = sizeof(PXA2xxTimerInfo),
+ .class_init = pxa27x_timer_dev_class_init,
+};
+
+static void pxa2xx_timer_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(oc);
+
+ sdc->init = pxa2xx_timer_init;
+ dc->vmsd = &vmstate_pxa2xx_timer_regs;
+}
+
+static const TypeInfo pxa2xx_timer_type_info = {
+ .name = TYPE_PXA2XX_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxTimerInfo),
+ .abstract = true,
+ .class_init = pxa2xx_timer_class_init,
+};
+
+static void pxa2xx_timer_register_types(void)
+{
+ type_register_static(&pxa2xx_timer_type_info);
+ type_register_static(&pxa25x_timer_dev_info);
+ type_register_static(&pxa27x_timer_dev_info);
+}
+
+type_init(pxa2xx_timer_register_types)
diff --git a/hw/timer/sh_timer.c b/hw/timer/sh_timer.c
new file mode 100644
index 00000000..07f0670b
--- /dev/null
+++ b/hw/timer/sh_timer.c
@@ -0,0 +1,334 @@
+/*
+ * SuperH Timer modules.
+ *
+ * Copyright (c) 2007 Magnus Damm
+ * Based on arm_timer.c by Paul Brook
+ * Copyright (c) 2005-2006 CodeSourcery.
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "qemu/timer.h"
+#include "qemu/main-loop.h"
+#include "exec/address-spaces.h"
+#include "hw/ptimer.h"
+
+//#define DEBUG_TIMER
+
+#define TIMER_TCR_TPSC (7 << 0)
+#define TIMER_TCR_CKEG (3 << 3)
+#define TIMER_TCR_UNIE (1 << 5)
+#define TIMER_TCR_ICPE (3 << 6)
+#define TIMER_TCR_UNF (1 << 8)
+#define TIMER_TCR_ICPF (1 << 9)
+#define TIMER_TCR_RESERVED (0x3f << 10)
+
+#define TIMER_FEAT_CAPT (1 << 0)
+#define TIMER_FEAT_EXTCLK (1 << 1)
+
+#define OFFSET_TCOR 0
+#define OFFSET_TCNT 1
+#define OFFSET_TCR 2
+#define OFFSET_TCPR 3
+
+typedef struct {
+ ptimer_state *timer;
+ uint32_t tcnt;
+ uint32_t tcor;
+ uint32_t tcr;
+ uint32_t tcpr;
+ int freq;
+ int int_level;
+ int old_level;
+ int feat;
+ int enabled;
+ qemu_irq irq;
+} sh_timer_state;
+
+/* Check all active timers, and schedule the next timer interrupt. */
+
+static void sh_timer_update(sh_timer_state *s)
+{
+ int new_level = s->int_level && (s->tcr & TIMER_TCR_UNIE);
+
+ if (new_level != s->old_level)
+ qemu_set_irq (s->irq, new_level);
+
+ s->old_level = s->int_level;
+ s->int_level = new_level;
+}
+
+static uint32_t sh_timer_read(void *opaque, hwaddr offset)
+{
+ sh_timer_state *s = (sh_timer_state *)opaque;
+
+ switch (offset >> 2) {
+ case OFFSET_TCOR:
+ return s->tcor;
+ case OFFSET_TCNT:
+ return ptimer_get_count(s->timer);
+ case OFFSET_TCR:
+ return s->tcr | (s->int_level ? TIMER_TCR_UNF : 0);
+ case OFFSET_TCPR:
+ if (s->feat & TIMER_FEAT_CAPT)
+ return s->tcpr;
+ default:
+ hw_error("sh_timer_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void sh_timer_write(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ sh_timer_state *s = (sh_timer_state *)opaque;
+ int freq;
+
+ switch (offset >> 2) {
+ case OFFSET_TCOR:
+ s->tcor = value;
+ ptimer_set_limit(s->timer, s->tcor, 0);
+ break;
+ case OFFSET_TCNT:
+ s->tcnt = value;
+ ptimer_set_count(s->timer, s->tcnt);
+ break;
+ case OFFSET_TCR:
+ if (s->enabled) {
+ /* Pause the timer if it is running. This may cause some
+ inaccuracy dure to rounding, but avoids a whole lot of other
+ messyness. */
+ ptimer_stop(s->timer);
+ }
+ freq = s->freq;
+ /* ??? Need to recalculate expiry time after changing divisor. */
+ switch (value & TIMER_TCR_TPSC) {
+ case 0: freq >>= 2; break;
+ case 1: freq >>= 4; break;
+ case 2: freq >>= 6; break;
+ case 3: freq >>= 8; break;
+ case 4: freq >>= 10; break;
+ case 6:
+ case 7: if (s->feat & TIMER_FEAT_EXTCLK) break;
+ default: hw_error("sh_timer_write: Reserved TPSC value\n"); break;
+ }
+ switch ((value & TIMER_TCR_CKEG) >> 3) {
+ case 0: break;
+ case 1:
+ case 2:
+ case 3: if (s->feat & TIMER_FEAT_EXTCLK) break;
+ default: hw_error("sh_timer_write: Reserved CKEG value\n"); break;
+ }
+ switch ((value & TIMER_TCR_ICPE) >> 6) {
+ case 0: break;
+ case 2:
+ case 3: if (s->feat & TIMER_FEAT_CAPT) break;
+ default: hw_error("sh_timer_write: Reserved ICPE value\n"); break;
+ }
+ if ((value & TIMER_TCR_UNF) == 0)
+ s->int_level = 0;
+
+ value &= ~TIMER_TCR_UNF;
+
+ if ((value & TIMER_TCR_ICPF) && (!(s->feat & TIMER_FEAT_CAPT)))
+ hw_error("sh_timer_write: Reserved ICPF value\n");
+
+ value &= ~TIMER_TCR_ICPF; /* capture not supported */
+
+ if (value & TIMER_TCR_RESERVED)
+ hw_error("sh_timer_write: Reserved TCR bits set\n");
+ s->tcr = value;
+ ptimer_set_limit(s->timer, s->tcor, 0);
+ ptimer_set_freq(s->timer, freq);
+ if (s->enabled) {
+ /* Restart the timer if still enabled. */
+ ptimer_run(s->timer, 0);
+ }
+ break;
+ case OFFSET_TCPR:
+ if (s->feat & TIMER_FEAT_CAPT) {
+ s->tcpr = value;
+ break;
+ }
+ default:
+ hw_error("sh_timer_write: Bad offset %x\n", (int)offset);
+ }
+ sh_timer_update(s);
+}
+
+static void sh_timer_start_stop(void *opaque, int enable)
+{
+ sh_timer_state *s = (sh_timer_state *)opaque;
+
+#ifdef DEBUG_TIMER
+ printf("sh_timer_start_stop %d (%d)\n", enable, s->enabled);
+#endif
+
+ if (s->enabled && !enable) {
+ ptimer_stop(s->timer);
+ }
+ if (!s->enabled && enable) {
+ ptimer_run(s->timer, 0);
+ }
+ s->enabled = !!enable;
+
+#ifdef DEBUG_TIMER
+ printf("sh_timer_start_stop done %d\n", s->enabled);
+#endif
+}
+
+static void sh_timer_tick(void *opaque)
+{
+ sh_timer_state *s = (sh_timer_state *)opaque;
+ s->int_level = s->enabled;
+ sh_timer_update(s);
+}
+
+static void *sh_timer_init(uint32_t freq, int feat, qemu_irq irq)
+{
+ sh_timer_state *s;
+ QEMUBH *bh;
+
+ s = (sh_timer_state *)g_malloc0(sizeof(sh_timer_state));
+ s->freq = freq;
+ s->feat = feat;
+ s->tcor = 0xffffffff;
+ s->tcnt = 0xffffffff;
+ s->tcpr = 0xdeadbeef;
+ s->tcr = 0;
+ s->enabled = 0;
+ s->irq = irq;
+
+ bh = qemu_bh_new(sh_timer_tick, s);
+ s->timer = ptimer_init(bh);
+
+ sh_timer_write(s, OFFSET_TCOR >> 2, s->tcor);
+ sh_timer_write(s, OFFSET_TCNT >> 2, s->tcnt);
+ sh_timer_write(s, OFFSET_TCPR >> 2, s->tcpr);
+ sh_timer_write(s, OFFSET_TCR >> 2, s->tcpr);
+ /* ??? Save/restore. */
+ return s;
+}
+
+typedef struct {
+ MemoryRegion iomem;
+ MemoryRegion iomem_p4;
+ MemoryRegion iomem_a7;
+ void *timer[3];
+ int level[3];
+ uint32_t tocr;
+ uint32_t tstr;
+ int feat;
+} tmu012_state;
+
+static uint64_t tmu012_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ tmu012_state *s = (tmu012_state *)opaque;
+
+#ifdef DEBUG_TIMER
+ printf("tmu012_read 0x%lx\n", (unsigned long) offset);
+#endif
+
+ if (offset >= 0x20) {
+ if (!(s->feat & TMU012_FEAT_3CHAN))
+ hw_error("tmu012_write: Bad channel offset %x\n", (int)offset);
+ return sh_timer_read(s->timer[2], offset - 0x20);
+ }
+
+ if (offset >= 0x14)
+ return sh_timer_read(s->timer[1], offset - 0x14);
+
+ if (offset >= 0x08)
+ return sh_timer_read(s->timer[0], offset - 0x08);
+
+ if (offset == 4)
+ return s->tstr;
+
+ if ((s->feat & TMU012_FEAT_TOCR) && offset == 0)
+ return s->tocr;
+
+ hw_error("tmu012_write: Bad offset %x\n", (int)offset);
+ return 0;
+}
+
+static void tmu012_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ tmu012_state *s = (tmu012_state *)opaque;
+
+#ifdef DEBUG_TIMER
+ printf("tmu012_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
+#endif
+
+ if (offset >= 0x20) {
+ if (!(s->feat & TMU012_FEAT_3CHAN))
+ hw_error("tmu012_write: Bad channel offset %x\n", (int)offset);
+ sh_timer_write(s->timer[2], offset - 0x20, value);
+ return;
+ }
+
+ if (offset >= 0x14) {
+ sh_timer_write(s->timer[1], offset - 0x14, value);
+ return;
+ }
+
+ if (offset >= 0x08) {
+ sh_timer_write(s->timer[0], offset - 0x08, value);
+ return;
+ }
+
+ if (offset == 4) {
+ sh_timer_start_stop(s->timer[0], value & (1 << 0));
+ sh_timer_start_stop(s->timer[1], value & (1 << 1));
+ if (s->feat & TMU012_FEAT_3CHAN)
+ sh_timer_start_stop(s->timer[2], value & (1 << 2));
+ else
+ if (value & (1 << 2))
+ hw_error("tmu012_write: Bad channel\n");
+
+ s->tstr = value;
+ return;
+ }
+
+ if ((s->feat & TMU012_FEAT_TOCR) && offset == 0) {
+ s->tocr = value & (1 << 0);
+ }
+}
+
+static const MemoryRegionOps tmu012_ops = {
+ .read = tmu012_read,
+ .write = tmu012_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void tmu012_init(MemoryRegion *sysmem, hwaddr base,
+ int feat, uint32_t freq,
+ qemu_irq ch0_irq, qemu_irq ch1_irq,
+ qemu_irq ch2_irq0, qemu_irq ch2_irq1)
+{
+ tmu012_state *s;
+ int timer_feat = (feat & TMU012_FEAT_EXTCLK) ? TIMER_FEAT_EXTCLK : 0;
+
+ s = (tmu012_state *)g_malloc0(sizeof(tmu012_state));
+ s->feat = feat;
+ s->timer[0] = sh_timer_init(freq, timer_feat, ch0_irq);
+ s->timer[1] = sh_timer_init(freq, timer_feat, ch1_irq);
+ if (feat & TMU012_FEAT_3CHAN)
+ s->timer[2] = sh_timer_init(freq, timer_feat | TIMER_FEAT_CAPT,
+ ch2_irq0); /* ch2_irq1 not supported */
+
+ memory_region_init_io(&s->iomem, NULL, &tmu012_ops, s,
+ "timer", 0x100000000ULL);
+
+ memory_region_init_alias(&s->iomem_p4, NULL, "timer-p4",
+ &s->iomem, 0, 0x1000);
+ memory_region_add_subregion(sysmem, P4ADDR(base), &s->iomem_p4);
+
+ memory_region_init_alias(&s->iomem_a7, NULL, "timer-a7",
+ &s->iomem, 0, 0x1000);
+ memory_region_add_subregion(sysmem, A7ADDR(base), &s->iomem_a7);
+ /* ??? Save/restore. */
+}
diff --git a/hw/timer/slavio_timer.c b/hw/timer/slavio_timer.c
new file mode 100644
index 00000000..45d97e66
--- /dev/null
+++ b/hw/timer/slavio_timer.c
@@ -0,0 +1,434 @@
+/*
+ * QEMU Sparc SLAVIO timer controller emulation
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ *
+ * 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.
+ */
+
+#include "hw/sparc/sun4m.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "qemu/main-loop.h"
+
+/*
+ * Registers of hardware timer in sun4m.
+ *
+ * This is the timer/counter part of chip STP2001 (Slave I/O), also
+ * produced as NCR89C105. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
+ *
+ * The 31-bit counter is incremented every 500ns by bit 9. Bits 8..0
+ * are zero. Bit 31 is 1 when count has been reached.
+ *
+ * Per-CPU timers interrupt local CPU, system timer uses normal
+ * interrupt routing.
+ *
+ */
+
+#define MAX_CPUS 16
+
+typedef struct CPUTimerState {
+ qemu_irq irq;
+ ptimer_state *timer;
+ uint32_t count, counthigh, reached;
+ /* processor only */
+ uint32_t run;
+ uint64_t limit;
+} CPUTimerState;
+
+#define TYPE_SLAVIO_TIMER "slavio_timer"
+#define SLAVIO_TIMER(obj) \
+ OBJECT_CHECK(SLAVIO_TIMERState, (obj), TYPE_SLAVIO_TIMER)
+
+typedef struct SLAVIO_TIMERState {
+ SysBusDevice parent_obj;
+
+ uint32_t num_cpus;
+ uint32_t cputimer_mode;
+ CPUTimerState cputimer[MAX_CPUS + 1];
+} SLAVIO_TIMERState;
+
+typedef struct TimerContext {
+ MemoryRegion iomem;
+ SLAVIO_TIMERState *s;
+ unsigned int timer_index; /* 0 for system, 1 ... MAX_CPUS for CPU timers */
+} TimerContext;
+
+#define SYS_TIMER_SIZE 0x14
+#define CPU_TIMER_SIZE 0x10
+
+#define TIMER_LIMIT 0
+#define TIMER_COUNTER 1
+#define TIMER_COUNTER_NORST 2
+#define TIMER_STATUS 3
+#define TIMER_MODE 4
+
+#define TIMER_COUNT_MASK32 0xfffffe00
+#define TIMER_LIMIT_MASK32 0x7fffffff
+#define TIMER_MAX_COUNT64 0x7ffffffffffffe00ULL
+#define TIMER_MAX_COUNT32 0x7ffffe00ULL
+#define TIMER_REACHED 0x80000000
+#define TIMER_PERIOD 500ULL // 500ns
+#define LIMIT_TO_PERIODS(l) (((l) >> 9) - 1)
+#define PERIODS_TO_LIMIT(l) (((l) + 1) << 9)
+
+static int slavio_timer_is_user(TimerContext *tc)
+{
+ SLAVIO_TIMERState *s = tc->s;
+ unsigned int timer_index = tc->timer_index;
+
+ return timer_index != 0 && (s->cputimer_mode & (1 << (timer_index - 1)));
+}
+
+// Update count, set irq, update expire_time
+// Convert from ptimer countdown units
+static void slavio_timer_get_out(CPUTimerState *t)
+{
+ uint64_t count, limit;
+
+ if (t->limit == 0) { /* free-run system or processor counter */
+ limit = TIMER_MAX_COUNT32;
+ } else {
+ limit = t->limit;
+ }
+ count = limit - PERIODS_TO_LIMIT(ptimer_get_count(t->timer));
+
+ trace_slavio_timer_get_out(t->limit, t->counthigh, t->count);
+ t->count = count & TIMER_COUNT_MASK32;
+ t->counthigh = count >> 32;
+}
+
+// timer callback
+static void slavio_timer_irq(void *opaque)
+{
+ TimerContext *tc = opaque;
+ SLAVIO_TIMERState *s = tc->s;
+ CPUTimerState *t = &s->cputimer[tc->timer_index];
+
+ slavio_timer_get_out(t);
+ trace_slavio_timer_irq(t->counthigh, t->count);
+ /* if limit is 0 (free-run), there will be no match */
+ if (t->limit != 0) {
+ t->reached = TIMER_REACHED;
+ }
+ /* there is no interrupt if user timer or free-run */
+ if (!slavio_timer_is_user(tc) && t->limit != 0) {
+ qemu_irq_raise(t->irq);
+ }
+}
+
+static uint64_t slavio_timer_mem_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TimerContext *tc = opaque;
+ SLAVIO_TIMERState *s = tc->s;
+ uint32_t saddr, ret;
+ unsigned int timer_index = tc->timer_index;
+ CPUTimerState *t = &s->cputimer[timer_index];
+
+ saddr = addr >> 2;
+ switch (saddr) {
+ case TIMER_LIMIT:
+ // read limit (system counter mode) or read most signifying
+ // part of counter (user mode)
+ if (slavio_timer_is_user(tc)) {
+ // read user timer MSW
+ slavio_timer_get_out(t);
+ ret = t->counthigh | t->reached;
+ } else {
+ // read limit
+ // clear irq
+ qemu_irq_lower(t->irq);
+ t->reached = 0;
+ ret = t->limit & TIMER_LIMIT_MASK32;
+ }
+ break;
+ case TIMER_COUNTER:
+ // read counter and reached bit (system mode) or read lsbits
+ // of counter (user mode)
+ slavio_timer_get_out(t);
+ if (slavio_timer_is_user(tc)) { // read user timer LSW
+ ret = t->count & TIMER_MAX_COUNT64;
+ } else { // read limit
+ ret = (t->count & TIMER_MAX_COUNT32) |
+ t->reached;
+ }
+ break;
+ case TIMER_STATUS:
+ // only available in processor counter/timer
+ // read start/stop status
+ if (timer_index > 0) {
+ ret = t->run;
+ } else {
+ ret = 0;
+ }
+ break;
+ case TIMER_MODE:
+ // only available in system counter
+ // read user/system mode
+ ret = s->cputimer_mode;
+ break;
+ default:
+ trace_slavio_timer_mem_readl_invalid(addr);
+ ret = 0;
+ break;
+ }
+ trace_slavio_timer_mem_readl(addr, ret);
+ return ret;
+}
+
+static void slavio_timer_mem_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TimerContext *tc = opaque;
+ SLAVIO_TIMERState *s = tc->s;
+ uint32_t saddr;
+ unsigned int timer_index = tc->timer_index;
+ CPUTimerState *t = &s->cputimer[timer_index];
+
+ trace_slavio_timer_mem_writel(addr, val);
+ saddr = addr >> 2;
+ switch (saddr) {
+ case TIMER_LIMIT:
+ if (slavio_timer_is_user(tc)) {
+ uint64_t count;
+
+ // set user counter MSW, reset counter
+ t->limit = TIMER_MAX_COUNT64;
+ t->counthigh = val & (TIMER_MAX_COUNT64 >> 32);
+ t->reached = 0;
+ count = ((uint64_t)t->counthigh << 32) | t->count;
+ trace_slavio_timer_mem_writel_limit(timer_index, count);
+ ptimer_set_count(t->timer, LIMIT_TO_PERIODS(t->limit - count));
+ } else {
+ // set limit, reset counter
+ qemu_irq_lower(t->irq);
+ t->limit = val & TIMER_MAX_COUNT32;
+ if (t->timer) {
+ if (t->limit == 0) { /* free-run */
+ ptimer_set_limit(t->timer,
+ LIMIT_TO_PERIODS(TIMER_MAX_COUNT32), 1);
+ } else {
+ ptimer_set_limit(t->timer, LIMIT_TO_PERIODS(t->limit), 1);
+ }
+ }
+ }
+ break;
+ case TIMER_COUNTER:
+ if (slavio_timer_is_user(tc)) {
+ uint64_t count;
+
+ // set user counter LSW, reset counter
+ t->limit = TIMER_MAX_COUNT64;
+ t->count = val & TIMER_MAX_COUNT64;
+ t->reached = 0;
+ count = ((uint64_t)t->counthigh) << 32 | t->count;
+ trace_slavio_timer_mem_writel_limit(timer_index, count);
+ ptimer_set_count(t->timer, LIMIT_TO_PERIODS(t->limit - count));
+ } else {
+ trace_slavio_timer_mem_writel_counter_invalid();
+ }
+ break;
+ case TIMER_COUNTER_NORST:
+ // set limit without resetting counter
+ t->limit = val & TIMER_MAX_COUNT32;
+ if (t->limit == 0) { /* free-run */
+ ptimer_set_limit(t->timer, LIMIT_TO_PERIODS(TIMER_MAX_COUNT32), 0);
+ } else {
+ ptimer_set_limit(t->timer, LIMIT_TO_PERIODS(t->limit), 0);
+ }
+ break;
+ case TIMER_STATUS:
+ if (slavio_timer_is_user(tc)) {
+ // start/stop user counter
+ if (val & 1) {
+ trace_slavio_timer_mem_writel_status_start(timer_index);
+ ptimer_run(t->timer, 0);
+ } else {
+ trace_slavio_timer_mem_writel_status_stop(timer_index);
+ ptimer_stop(t->timer);
+ }
+ }
+ t->run = val & 1;
+ break;
+ case TIMER_MODE:
+ if (timer_index == 0) {
+ unsigned int i;
+
+ for (i = 0; i < s->num_cpus; i++) {
+ unsigned int processor = 1 << i;
+ CPUTimerState *curr_timer = &s->cputimer[i + 1];
+
+ // check for a change in timer mode for this processor
+ if ((val & processor) != (s->cputimer_mode & processor)) {
+ if (val & processor) { // counter -> user timer
+ qemu_irq_lower(curr_timer->irq);
+ // counters are always running
+ if (!curr_timer->run) {
+ ptimer_stop(curr_timer->timer);
+ }
+ // user timer limit is always the same
+ curr_timer->limit = TIMER_MAX_COUNT64;
+ ptimer_set_limit(curr_timer->timer,
+ LIMIT_TO_PERIODS(curr_timer->limit),
+ 1);
+ // set this processors user timer bit in config
+ // register
+ s->cputimer_mode |= processor;
+ trace_slavio_timer_mem_writel_mode_user(timer_index);
+ } else { // user timer -> counter
+ // start the counter
+ ptimer_run(curr_timer->timer, 0);
+ // clear this processors user timer bit in config
+ // register
+ s->cputimer_mode &= ~processor;
+ trace_slavio_timer_mem_writel_mode_counter(timer_index);
+ }
+ }
+ }
+ } else {
+ trace_slavio_timer_mem_writel_mode_invalid();
+ }
+ break;
+ default:
+ trace_slavio_timer_mem_writel_invalid(addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps slavio_timer_mem_ops = {
+ .read = slavio_timer_mem_readl,
+ .write = slavio_timer_mem_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const VMStateDescription vmstate_timer = {
+ .name ="timer",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(limit, CPUTimerState),
+ VMSTATE_UINT32(count, CPUTimerState),
+ VMSTATE_UINT32(counthigh, CPUTimerState),
+ VMSTATE_UINT32(reached, CPUTimerState),
+ VMSTATE_UINT32(run , CPUTimerState),
+ VMSTATE_PTIMER(timer, CPUTimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_slavio_timer = {
+ .name ="slavio_timer",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(cputimer, SLAVIO_TIMERState, MAX_CPUS + 1, 3,
+ vmstate_timer, CPUTimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void slavio_timer_reset(DeviceState *d)
+{
+ SLAVIO_TIMERState *s = SLAVIO_TIMER(d);
+ unsigned int i;
+ CPUTimerState *curr_timer;
+
+ for (i = 0; i <= MAX_CPUS; i++) {
+ curr_timer = &s->cputimer[i];
+ curr_timer->limit = 0;
+ curr_timer->count = 0;
+ curr_timer->reached = 0;
+ if (i <= s->num_cpus) {
+ ptimer_set_limit(curr_timer->timer,
+ LIMIT_TO_PERIODS(TIMER_MAX_COUNT32), 1);
+ ptimer_run(curr_timer->timer, 0);
+ curr_timer->run = 1;
+ }
+ }
+ s->cputimer_mode = 0;
+}
+
+static int slavio_timer_init1(SysBusDevice *dev)
+{
+ SLAVIO_TIMERState *s = SLAVIO_TIMER(dev);
+ QEMUBH *bh;
+ unsigned int i;
+ TimerContext *tc;
+
+ for (i = 0; i <= MAX_CPUS; i++) {
+ uint64_t size;
+ char timer_name[20];
+
+ tc = g_malloc0(sizeof(TimerContext));
+ tc->s = s;
+ tc->timer_index = i;
+
+ bh = qemu_bh_new(slavio_timer_irq, tc);
+ s->cputimer[i].timer = ptimer_init(bh);
+ ptimer_set_period(s->cputimer[i].timer, TIMER_PERIOD);
+
+ size = i == 0 ? SYS_TIMER_SIZE : CPU_TIMER_SIZE;
+ snprintf(timer_name, sizeof(timer_name), "timer-%i", i);
+ memory_region_init_io(&tc->iomem, OBJECT(s), &slavio_timer_mem_ops, tc,
+ timer_name, size);
+ sysbus_init_mmio(dev, &tc->iomem);
+
+ sysbus_init_irq(dev, &s->cputimer[i].irq);
+ }
+
+ return 0;
+}
+
+static Property slavio_timer_properties[] = {
+ DEFINE_PROP_UINT32("num_cpus", SLAVIO_TIMERState, num_cpus, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void slavio_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = slavio_timer_init1;
+ dc->reset = slavio_timer_reset;
+ dc->vmsd = &vmstate_slavio_timer;
+ dc->props = slavio_timer_properties;
+}
+
+static const TypeInfo slavio_timer_info = {
+ .name = TYPE_SLAVIO_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SLAVIO_TIMERState),
+ .class_init = slavio_timer_class_init,
+};
+
+static void slavio_timer_register_types(void)
+{
+ type_register_static(&slavio_timer_info);
+}
+
+type_init(slavio_timer_register_types)
diff --git a/hw/timer/stm32f2xx_timer.c b/hw/timer/stm32f2xx_timer.c
new file mode 100644
index 00000000..ecadf9df
--- /dev/null
+++ b/hw/timer/stm32f2xx_timer.c
@@ -0,0 +1,328 @@
+/*
+ * STM32F2XX Timer
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * 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.
+ */
+
+#include "hw/timer/stm32f2xx_timer.h"
+
+#ifndef STM_TIMER_ERR_DEBUG
+#define STM_TIMER_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(lvl, fmt, args...) do { \
+ if (STM_TIMER_ERR_DEBUG >= lvl) { \
+ qemu_log("%s: " fmt, __func__, ## args); \
+ } \
+} while (0);
+
+#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
+
+static void stm32f2xx_timer_set_alarm(STM32F2XXTimerState *s, int64_t now);
+
+static void stm32f2xx_timer_interrupt(void *opaque)
+{
+ STM32F2XXTimerState *s = opaque;
+
+ DB_PRINT("Interrupt\n");
+
+ if (s->tim_dier & TIM_DIER_UIE && s->tim_cr1 & TIM_CR1_CEN) {
+ s->tim_sr |= 1;
+ qemu_irq_pulse(s->irq);
+ stm32f2xx_timer_set_alarm(s, s->hit_time);
+ }
+}
+
+static inline int64_t stm32f2xx_ns_to_ticks(STM32F2XXTimerState *s, int64_t t)
+{
+ return muldiv64(t, s->freq_hz, 1000000000ULL) / (s->tim_psc + 1);
+}
+
+static void stm32f2xx_timer_set_alarm(STM32F2XXTimerState *s, int64_t now)
+{
+ uint64_t ticks;
+ int64_t now_ticks;
+
+ if (s->tim_arr == 0) {
+ return;
+ }
+
+ DB_PRINT("Alarm set at: 0x%x\n", s->tim_cr1);
+
+ now_ticks = stm32f2xx_ns_to_ticks(s, now);
+ ticks = s->tim_arr - (now_ticks - s->tick_offset);
+
+ DB_PRINT("Alarm set in %d ticks\n", (int) ticks);
+
+ s->hit_time = muldiv64((ticks + (uint64_t) now_ticks) * (s->tim_psc + 1),
+ 1000000000ULL, s->freq_hz);
+
+ timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->hit_time);
+ DB_PRINT("Wait Time: %" PRId64 " ticks\n", s->hit_time);
+}
+
+static void stm32f2xx_timer_reset(DeviceState *dev)
+{
+ STM32F2XXTimerState *s = STM32F2XXTIMER(dev);
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ s->tim_cr1 = 0;
+ s->tim_cr2 = 0;
+ s->tim_smcr = 0;
+ s->tim_dier = 0;
+ s->tim_sr = 0;
+ s->tim_egr = 0;
+ s->tim_ccmr1 = 0;
+ s->tim_ccmr2 = 0;
+ s->tim_ccer = 0;
+ s->tim_psc = 0;
+ s->tim_arr = 0;
+ s->tim_ccr1 = 0;
+ s->tim_ccr2 = 0;
+ s->tim_ccr3 = 0;
+ s->tim_ccr4 = 0;
+ s->tim_dcr = 0;
+ s->tim_dmar = 0;
+ s->tim_or = 0;
+
+ s->tick_offset = stm32f2xx_ns_to_ticks(s, now);
+}
+
+static uint64_t stm32f2xx_timer_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ STM32F2XXTimerState *s = opaque;
+
+ DB_PRINT("Read 0x%"HWADDR_PRIx"\n", offset);
+
+ switch (offset) {
+ case TIM_CR1:
+ return s->tim_cr1;
+ case TIM_CR2:
+ return s->tim_cr2;
+ case TIM_SMCR:
+ return s->tim_smcr;
+ case TIM_DIER:
+ return s->tim_dier;
+ case TIM_SR:
+ return s->tim_sr;
+ case TIM_EGR:
+ return s->tim_egr;
+ case TIM_CCMR1:
+ return s->tim_ccmr1;
+ case TIM_CCMR2:
+ return s->tim_ccmr2;
+ case TIM_CCER:
+ return s->tim_ccer;
+ case TIM_CNT:
+ return stm32f2xx_ns_to_ticks(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) -
+ s->tick_offset;
+ case TIM_PSC:
+ return s->tim_psc;
+ case TIM_ARR:
+ return s->tim_arr;
+ case TIM_CCR1:
+ return s->tim_ccr1;
+ case TIM_CCR2:
+ return s->tim_ccr2;
+ case TIM_CCR3:
+ return s->tim_ccr3;
+ case TIM_CCR4:
+ return s->tim_ccr4;
+ case TIM_DCR:
+ return s->tim_dcr;
+ case TIM_DMAR:
+ return s->tim_dmar;
+ case TIM_OR:
+ return s->tim_or;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, offset);
+ }
+
+ return 0;
+}
+
+static void stm32f2xx_timer_write(void *opaque, hwaddr offset,
+ uint64_t val64, unsigned size)
+{
+ STM32F2XXTimerState *s = opaque;
+ uint32_t value = val64;
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ uint32_t timer_val = 0;
+
+ DB_PRINT("Write 0x%x, 0x%"HWADDR_PRIx"\n", value, offset);
+
+ switch (offset) {
+ case TIM_CR1:
+ s->tim_cr1 = value;
+ return;
+ case TIM_CR2:
+ s->tim_cr2 = value;
+ return;
+ case TIM_SMCR:
+ s->tim_smcr = value;
+ return;
+ case TIM_DIER:
+ s->tim_dier = value;
+ return;
+ case TIM_SR:
+ /* This is set by hardware and cleared by software */
+ s->tim_sr &= value;
+ return;
+ case TIM_EGR:
+ s->tim_egr = value;
+ if (s->tim_egr & TIM_EGR_UG) {
+ timer_val = 0;
+ break;
+ }
+ return;
+ case TIM_CCMR1:
+ s->tim_ccmr1 = value;
+ return;
+ case TIM_CCMR2:
+ s->tim_ccmr2 = value;
+ return;
+ case TIM_CCER:
+ s->tim_ccer = value;
+ return;
+ case TIM_PSC:
+ timer_val = stm32f2xx_ns_to_ticks(s, now) - s->tick_offset;
+ s->tim_psc = value;
+ value = timer_val;
+ break;
+ case TIM_CNT:
+ timer_val = value;
+ break;
+ case TIM_ARR:
+ s->tim_arr = value;
+ stm32f2xx_timer_set_alarm(s, now);
+ return;
+ case TIM_CCR1:
+ s->tim_ccr1 = value;
+ return;
+ case TIM_CCR2:
+ s->tim_ccr2 = value;
+ return;
+ case TIM_CCR3:
+ s->tim_ccr3 = value;
+ return;
+ case TIM_CCR4:
+ s->tim_ccr4 = value;
+ return;
+ case TIM_DCR:
+ s->tim_dcr = value;
+ return;
+ case TIM_DMAR:
+ s->tim_dmar = value;
+ return;
+ case TIM_OR:
+ s->tim_or = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, offset);
+ return;
+ }
+
+ /* This means that a register write has affected the timer in a way that
+ * requires a refresh of both tick_offset and the alarm.
+ */
+ s->tick_offset = stm32f2xx_ns_to_ticks(s, now) - timer_val;
+ stm32f2xx_timer_set_alarm(s, now);
+}
+
+static const MemoryRegionOps stm32f2xx_timer_ops = {
+ .read = stm32f2xx_timer_read,
+ .write = stm32f2xx_timer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_stm32f2xx_timer = {
+ .name = TYPE_STM32F2XX_TIMER,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT64(tick_offset, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_cr1, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_cr2, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_smcr, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_dier, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_sr, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_egr, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_ccmr1, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_ccmr2, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_ccer, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_psc, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_arr, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_ccr1, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_ccr2, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_ccr3, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_ccr4, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_dcr, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_dmar, STM32F2XXTimerState),
+ VMSTATE_UINT32(tim_or, STM32F2XXTimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property stm32f2xx_timer_properties[] = {
+ DEFINE_PROP_UINT64("clock-frequency", struct STM32F2XXTimerState,
+ freq_hz, 1000000000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void stm32f2xx_timer_init(Object *obj)
+{
+ STM32F2XXTimerState *s = STM32F2XXTIMER(obj);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+ memory_region_init_io(&s->iomem, obj, &stm32f2xx_timer_ops, s,
+ "stm32f2xx_timer", 0x4000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, stm32f2xx_timer_interrupt, s);
+}
+
+static void stm32f2xx_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = stm32f2xx_timer_reset;
+ dc->props = stm32f2xx_timer_properties;
+ dc->vmsd = &vmstate_stm32f2xx_timer;
+}
+
+static const TypeInfo stm32f2xx_timer_info = {
+ .name = TYPE_STM32F2XX_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F2XXTimerState),
+ .instance_init = stm32f2xx_timer_init,
+ .class_init = stm32f2xx_timer_class_init,
+};
+
+static void stm32f2xx_timer_register_types(void)
+{
+ type_register_static(&stm32f2xx_timer_info);
+}
+
+type_init(stm32f2xx_timer_register_types)
diff --git a/hw/timer/tusb6010.c b/hw/timer/tusb6010.c
new file mode 100644
index 00000000..459c748e
--- /dev/null
+++ b/hw/timer/tusb6010.c
@@ -0,0 +1,816 @@
+/*
+ * Texas Instruments TUSB6010 emulation.
+ * Based on reverse-engineering of a linux driver.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "hw/usb.h"
+#include "hw/arm/omap.h"
+#include "hw/irq.h"
+#include "hw/devices.h"
+#include "hw/sysbus.h"
+
+#define TYPE_TUSB6010 "tusb6010"
+#define TUSB(obj) OBJECT_CHECK(TUSBState, (obj), TYPE_TUSB6010)
+
+typedef struct TUSBState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem[2];
+ qemu_irq irq;
+ MUSBState *musb;
+ QEMUTimer *otg_timer;
+ QEMUTimer *pwr_timer;
+
+ int power;
+ uint32_t scratch;
+ uint16_t test_reset;
+ uint32_t prcm_config;
+ uint32_t prcm_mngmt;
+ uint16_t otg_status;
+ uint32_t dev_config;
+ int host_mode;
+ uint32_t intr;
+ uint32_t intr_ok;
+ uint32_t mask;
+ uint32_t usbip_intr;
+ uint32_t usbip_mask;
+ uint32_t gpio_intr;
+ uint32_t gpio_mask;
+ uint32_t gpio_config;
+ uint32_t dma_intr;
+ uint32_t dma_mask;
+ uint32_t dma_map;
+ uint32_t dma_config;
+ uint32_t ep0_config;
+ uint32_t rx_config[15];
+ uint32_t tx_config[15];
+ uint32_t wkup_mask;
+ uint32_t pullup[2];
+ uint32_t control_config;
+ uint32_t otg_timer_val;
+} TUSBState;
+
+#define TUSB_DEVCLOCK 60000000 /* 60 MHz */
+
+#define TUSB_VLYNQ_CTRL 0x004
+
+/* Mentor Graphics OTG core registers. */
+#define TUSB_BASE_OFFSET 0x400
+
+/* FIFO registers, 32-bit. */
+#define TUSB_FIFO_BASE 0x600
+
+/* Device System & Control registers, 32-bit. */
+#define TUSB_SYS_REG_BASE 0x800
+
+#define TUSB_DEV_CONF (TUSB_SYS_REG_BASE + 0x000)
+#define TUSB_DEV_CONF_USB_HOST_MODE (1 << 16)
+#define TUSB_DEV_CONF_PROD_TEST_MODE (1 << 15)
+#define TUSB_DEV_CONF_SOFT_ID (1 << 1)
+#define TUSB_DEV_CONF_ID_SEL (1 << 0)
+
+#define TUSB_PHY_OTG_CTRL_ENABLE (TUSB_SYS_REG_BASE + 0x004)
+#define TUSB_PHY_OTG_CTRL (TUSB_SYS_REG_BASE + 0x008)
+#define TUSB_PHY_OTG_CTRL_WRPROTECT (0xa5 << 24)
+#define TUSB_PHY_OTG_CTRL_O_ID_PULLUP (1 << 23)
+#define TUSB_PHY_OTG_CTRL_O_VBUS_DET_EN (1 << 19)
+#define TUSB_PHY_OTG_CTRL_O_SESS_END_EN (1 << 18)
+#define TUSB_PHY_OTG_CTRL_TESTM2 (1 << 17)
+#define TUSB_PHY_OTG_CTRL_TESTM1 (1 << 16)
+#define TUSB_PHY_OTG_CTRL_TESTM0 (1 << 15)
+#define TUSB_PHY_OTG_CTRL_TX_DATA2 (1 << 14)
+#define TUSB_PHY_OTG_CTRL_TX_GZ2 (1 << 13)
+#define TUSB_PHY_OTG_CTRL_TX_ENABLE2 (1 << 12)
+#define TUSB_PHY_OTG_CTRL_DM_PULLDOWN (1 << 11)
+#define TUSB_PHY_OTG_CTRL_DP_PULLDOWN (1 << 10)
+#define TUSB_PHY_OTG_CTRL_OSC_EN (1 << 9)
+#define TUSB_PHY_OTG_CTRL_PHYREF_CLK(v) (((v) & 3) << 7)
+#define TUSB_PHY_OTG_CTRL_PD (1 << 6)
+#define TUSB_PHY_OTG_CTRL_PLL_ON (1 << 5)
+#define TUSB_PHY_OTG_CTRL_EXT_RPU (1 << 4)
+#define TUSB_PHY_OTG_CTRL_PWR_GOOD (1 << 3)
+#define TUSB_PHY_OTG_CTRL_RESET (1 << 2)
+#define TUSB_PHY_OTG_CTRL_SUSPENDM (1 << 1)
+#define TUSB_PHY_OTG_CTRL_CLK_MODE (1 << 0)
+
+/* OTG status register */
+#define TUSB_DEV_OTG_STAT (TUSB_SYS_REG_BASE + 0x00c)
+#define TUSB_DEV_OTG_STAT_PWR_CLK_GOOD (1 << 8)
+#define TUSB_DEV_OTG_STAT_SESS_END (1 << 7)
+#define TUSB_DEV_OTG_STAT_SESS_VALID (1 << 6)
+#define TUSB_DEV_OTG_STAT_VBUS_VALID (1 << 5)
+#define TUSB_DEV_OTG_STAT_VBUS_SENSE (1 << 4)
+#define TUSB_DEV_OTG_STAT_ID_STATUS (1 << 3)
+#define TUSB_DEV_OTG_STAT_HOST_DISCON (1 << 2)
+#define TUSB_DEV_OTG_STAT_LINE_STATE (3 << 0)
+#define TUSB_DEV_OTG_STAT_DP_ENABLE (1 << 1)
+#define TUSB_DEV_OTG_STAT_DM_ENABLE (1 << 0)
+
+#define TUSB_DEV_OTG_TIMER (TUSB_SYS_REG_BASE + 0x010)
+#define TUSB_DEV_OTG_TIMER_ENABLE (1 << 31)
+#define TUSB_DEV_OTG_TIMER_VAL(v) ((v) & 0x07ffffff)
+#define TUSB_PRCM_REV (TUSB_SYS_REG_BASE + 0x014)
+
+/* PRCM configuration register */
+#define TUSB_PRCM_CONF (TUSB_SYS_REG_BASE + 0x018)
+#define TUSB_PRCM_CONF_SFW_CPEN (1 << 24)
+#define TUSB_PRCM_CONF_SYS_CLKSEL(v) (((v) & 3) << 16)
+
+/* PRCM management register */
+#define TUSB_PRCM_MNGMT (TUSB_SYS_REG_BASE + 0x01c)
+#define TUSB_PRCM_MNGMT_SRP_FIX_TMR(v) (((v) & 0xf) << 25)
+#define TUSB_PRCM_MNGMT_SRP_FIX_EN (1 << 24)
+#define TUSB_PRCM_MNGMT_VBUS_VAL_TMR(v) (((v) & 0xf) << 20)
+#define TUSB_PRCM_MNGMT_VBUS_VAL_FLT_EN (1 << 19)
+#define TUSB_PRCM_MNGMT_DFT_CLK_DIS (1 << 18)
+#define TUSB_PRCM_MNGMT_VLYNQ_CLK_DIS (1 << 17)
+#define TUSB_PRCM_MNGMT_OTG_SESS_END_EN (1 << 10)
+#define TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN (1 << 9)
+#define TUSB_PRCM_MNGMT_OTG_ID_PULLUP (1 << 8)
+#define TUSB_PRCM_MNGMT_15_SW_EN (1 << 4)
+#define TUSB_PRCM_MNGMT_33_SW_EN (1 << 3)
+#define TUSB_PRCM_MNGMT_5V_CPEN (1 << 2)
+#define TUSB_PRCM_MNGMT_PM_IDLE (1 << 1)
+#define TUSB_PRCM_MNGMT_DEV_IDLE (1 << 0)
+
+/* Wake-up source clear and mask registers */
+#define TUSB_PRCM_WAKEUP_SOURCE (TUSB_SYS_REG_BASE + 0x020)
+#define TUSB_PRCM_WAKEUP_CLEAR (TUSB_SYS_REG_BASE + 0x028)
+#define TUSB_PRCM_WAKEUP_MASK (TUSB_SYS_REG_BASE + 0x02c)
+#define TUSB_PRCM_WAKEUP_RESERVED_BITS (0xffffe << 13)
+#define TUSB_PRCM_WGPIO_7 (1 << 12)
+#define TUSB_PRCM_WGPIO_6 (1 << 11)
+#define TUSB_PRCM_WGPIO_5 (1 << 10)
+#define TUSB_PRCM_WGPIO_4 (1 << 9)
+#define TUSB_PRCM_WGPIO_3 (1 << 8)
+#define TUSB_PRCM_WGPIO_2 (1 << 7)
+#define TUSB_PRCM_WGPIO_1 (1 << 6)
+#define TUSB_PRCM_WGPIO_0 (1 << 5)
+#define TUSB_PRCM_WHOSTDISCON (1 << 4) /* Host disconnect */
+#define TUSB_PRCM_WBUS (1 << 3) /* USB bus resume */
+#define TUSB_PRCM_WNORCS (1 << 2) /* NOR chip select */
+#define TUSB_PRCM_WVBUS (1 << 1) /* OTG PHY VBUS */
+#define TUSB_PRCM_WID (1 << 0) /* OTG PHY ID detect */
+
+#define TUSB_PULLUP_1_CTRL (TUSB_SYS_REG_BASE + 0x030)
+#define TUSB_PULLUP_2_CTRL (TUSB_SYS_REG_BASE + 0x034)
+#define TUSB_INT_CTRL_REV (TUSB_SYS_REG_BASE + 0x038)
+#define TUSB_INT_CTRL_CONF (TUSB_SYS_REG_BASE + 0x03c)
+#define TUSB_USBIP_INT_SRC (TUSB_SYS_REG_BASE + 0x040)
+#define TUSB_USBIP_INT_SET (TUSB_SYS_REG_BASE + 0x044)
+#define TUSB_USBIP_INT_CLEAR (TUSB_SYS_REG_BASE + 0x048)
+#define TUSB_USBIP_INT_MASK (TUSB_SYS_REG_BASE + 0x04c)
+#define TUSB_DMA_INT_SRC (TUSB_SYS_REG_BASE + 0x050)
+#define TUSB_DMA_INT_SET (TUSB_SYS_REG_BASE + 0x054)
+#define TUSB_DMA_INT_CLEAR (TUSB_SYS_REG_BASE + 0x058)
+#define TUSB_DMA_INT_MASK (TUSB_SYS_REG_BASE + 0x05c)
+#define TUSB_GPIO_INT_SRC (TUSB_SYS_REG_BASE + 0x060)
+#define TUSB_GPIO_INT_SET (TUSB_SYS_REG_BASE + 0x064)
+#define TUSB_GPIO_INT_CLEAR (TUSB_SYS_REG_BASE + 0x068)
+#define TUSB_GPIO_INT_MASK (TUSB_SYS_REG_BASE + 0x06c)
+
+/* NOR flash interrupt source registers */
+#define TUSB_INT_SRC (TUSB_SYS_REG_BASE + 0x070)
+#define TUSB_INT_SRC_SET (TUSB_SYS_REG_BASE + 0x074)
+#define TUSB_INT_SRC_CLEAR (TUSB_SYS_REG_BASE + 0x078)
+#define TUSB_INT_MASK (TUSB_SYS_REG_BASE + 0x07c)
+#define TUSB_INT_SRC_TXRX_DMA_DONE (1 << 24)
+#define TUSB_INT_SRC_USB_IP_CORE (1 << 17)
+#define TUSB_INT_SRC_OTG_TIMEOUT (1 << 16)
+#define TUSB_INT_SRC_VBUS_SENSE_CHNG (1 << 15)
+#define TUSB_INT_SRC_ID_STATUS_CHNG (1 << 14)
+#define TUSB_INT_SRC_DEV_WAKEUP (1 << 13)
+#define TUSB_INT_SRC_DEV_READY (1 << 12)
+#define TUSB_INT_SRC_USB_IP_TX (1 << 9)
+#define TUSB_INT_SRC_USB_IP_RX (1 << 8)
+#define TUSB_INT_SRC_USB_IP_VBUS_ERR (1 << 7)
+#define TUSB_INT_SRC_USB_IP_VBUS_REQ (1 << 6)
+#define TUSB_INT_SRC_USB_IP_DISCON (1 << 5)
+#define TUSB_INT_SRC_USB_IP_CONN (1 << 4)
+#define TUSB_INT_SRC_USB_IP_SOF (1 << 3)
+#define TUSB_INT_SRC_USB_IP_RST_BABBLE (1 << 2)
+#define TUSB_INT_SRC_USB_IP_RESUME (1 << 1)
+#define TUSB_INT_SRC_USB_IP_SUSPEND (1 << 0)
+
+#define TUSB_GPIO_REV (TUSB_SYS_REG_BASE + 0x080)
+#define TUSB_GPIO_CONF (TUSB_SYS_REG_BASE + 0x084)
+#define TUSB_DMA_CTRL_REV (TUSB_SYS_REG_BASE + 0x100)
+#define TUSB_DMA_REQ_CONF (TUSB_SYS_REG_BASE + 0x104)
+#define TUSB_EP0_CONF (TUSB_SYS_REG_BASE + 0x108)
+#define TUSB_EP_IN_SIZE (TUSB_SYS_REG_BASE + 0x10c)
+#define TUSB_DMA_EP_MAP (TUSB_SYS_REG_BASE + 0x148)
+#define TUSB_EP_OUT_SIZE (TUSB_SYS_REG_BASE + 0x14c)
+#define TUSB_EP_MAX_PACKET_SIZE_OFFSET (TUSB_SYS_REG_BASE + 0x188)
+#define TUSB_SCRATCH_PAD (TUSB_SYS_REG_BASE + 0x1c4)
+#define TUSB_WAIT_COUNT (TUSB_SYS_REG_BASE + 0x1c8)
+#define TUSB_PROD_TEST_RESET (TUSB_SYS_REG_BASE + 0x1d8)
+
+#define TUSB_DIDR1_LO (TUSB_SYS_REG_BASE + 0x1f8)
+#define TUSB_DIDR1_HI (TUSB_SYS_REG_BASE + 0x1fc)
+
+/* Device System & Control register bitfields */
+#define TUSB_INT_CTRL_CONF_INT_RLCYC(v) (((v) & 0x7) << 18)
+#define TUSB_INT_CTRL_CONF_INT_POLARITY (1 << 17)
+#define TUSB_INT_CTRL_CONF_INT_MODE (1 << 16)
+#define TUSB_GPIO_CONF_DMAREQ(v) (((v) & 0x3f) << 24)
+#define TUSB_DMA_REQ_CONF_BURST_SIZE(v) (((v) & 3) << 26)
+#define TUSB_DMA_REQ_CONF_DMA_RQ_EN(v) (((v) & 0x3f) << 20)
+#define TUSB_DMA_REQ_CONF_DMA_RQ_ASR(v) (((v) & 0xf) << 16)
+#define TUSB_EP0_CONFIG_SW_EN (1 << 8)
+#define TUSB_EP0_CONFIG_DIR_TX (1 << 7)
+#define TUSB_EP0_CONFIG_XFR_SIZE(v) ((v) & 0x7f)
+#define TUSB_EP_CONFIG_SW_EN (1 << 31)
+#define TUSB_EP_CONFIG_XFR_SIZE(v) ((v) & 0x7fffffff)
+#define TUSB_PROD_TEST_RESET_VAL 0xa596
+
+static void tusb_intr_update(TUSBState *s)
+{
+ if (s->control_config & TUSB_INT_CTRL_CONF_INT_POLARITY)
+ qemu_set_irq(s->irq, s->intr & ~s->mask & s->intr_ok);
+ else
+ qemu_set_irq(s->irq, (!(s->intr & ~s->mask)) & s->intr_ok);
+}
+
+static void tusb_usbip_intr_update(TUSBState *s)
+{
+ /* TX interrupt in the MUSB */
+ if (s->usbip_intr & 0x0000ffff & ~s->usbip_mask)
+ s->intr |= TUSB_INT_SRC_USB_IP_TX;
+ else
+ s->intr &= ~TUSB_INT_SRC_USB_IP_TX;
+
+ /* RX interrupt in the MUSB */
+ if (s->usbip_intr & 0xffff0000 & ~s->usbip_mask)
+ s->intr |= TUSB_INT_SRC_USB_IP_RX;
+ else
+ s->intr &= ~TUSB_INT_SRC_USB_IP_RX;
+
+ /* XXX: What about TUSB_INT_SRC_USB_IP_CORE? */
+
+ tusb_intr_update(s);
+}
+
+static void tusb_dma_intr_update(TUSBState *s)
+{
+ if (s->dma_intr & ~s->dma_mask)
+ s->intr |= TUSB_INT_SRC_TXRX_DMA_DONE;
+ else
+ s->intr &= ~TUSB_INT_SRC_TXRX_DMA_DONE;
+
+ tusb_intr_update(s);
+}
+
+static void tusb_gpio_intr_update(TUSBState *s)
+{
+ /* TODO: How is this signalled? */
+}
+
+static uint32_t tusb_async_readb(void *opaque, hwaddr addr)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ switch (addr & 0xfff) {
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ return musb_read[0](s->musb, addr & 0x1ff);
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ return musb_read[0](s->musb, 0x20 + ((addr >> 3) & 0x3c));
+ }
+
+ printf("%s: unknown register at %03x\n",
+ __FUNCTION__, (int) (addr & 0xfff));
+ return 0;
+}
+
+static uint32_t tusb_async_readh(void *opaque, hwaddr addr)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ switch (addr & 0xfff) {
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ return musb_read[1](s->musb, addr & 0x1ff);
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ return musb_read[1](s->musb, 0x20 + ((addr >> 3) & 0x3c));
+ }
+
+ printf("%s: unknown register at %03x\n",
+ __FUNCTION__, (int) (addr & 0xfff));
+ return 0;
+}
+
+static uint32_t tusb_async_readw(void *opaque, hwaddr addr)
+{
+ TUSBState *s = (TUSBState *) opaque;
+ int offset = addr & 0xfff;
+ int epnum;
+ uint32_t ret;
+
+ switch (offset) {
+ case TUSB_DEV_CONF:
+ return s->dev_config;
+
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ return musb_read[2](s->musb, offset & 0x1ff);
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ return musb_read[2](s->musb, 0x20 + ((addr >> 3) & 0x3c));
+
+ case TUSB_PHY_OTG_CTRL_ENABLE:
+ case TUSB_PHY_OTG_CTRL:
+ return 0x00; /* TODO */
+
+ case TUSB_DEV_OTG_STAT:
+ ret = s->otg_status;
+#if 0
+ if (!(s->prcm_mngmt & TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN))
+ ret &= ~TUSB_DEV_OTG_STAT_VBUS_VALID;
+#endif
+ return ret;
+ case TUSB_DEV_OTG_TIMER:
+ return s->otg_timer_val;
+
+ case TUSB_PRCM_REV:
+ return 0x20;
+ case TUSB_PRCM_CONF:
+ return s->prcm_config;
+ case TUSB_PRCM_MNGMT:
+ return s->prcm_mngmt;
+ case TUSB_PRCM_WAKEUP_SOURCE:
+ case TUSB_PRCM_WAKEUP_CLEAR: /* TODO: What does this one return? */
+ return 0x00000000;
+ case TUSB_PRCM_WAKEUP_MASK:
+ return s->wkup_mask;
+
+ case TUSB_PULLUP_1_CTRL:
+ return s->pullup[0];
+ case TUSB_PULLUP_2_CTRL:
+ return s->pullup[1];
+
+ case TUSB_INT_CTRL_REV:
+ return 0x20;
+ case TUSB_INT_CTRL_CONF:
+ return s->control_config;
+
+ case TUSB_USBIP_INT_SRC:
+ case TUSB_USBIP_INT_SET: /* TODO: What do these two return? */
+ case TUSB_USBIP_INT_CLEAR:
+ return s->usbip_intr;
+ case TUSB_USBIP_INT_MASK:
+ return s->usbip_mask;
+
+ case TUSB_DMA_INT_SRC:
+ case TUSB_DMA_INT_SET: /* TODO: What do these two return? */
+ case TUSB_DMA_INT_CLEAR:
+ return s->dma_intr;
+ case TUSB_DMA_INT_MASK:
+ return s->dma_mask;
+
+ case TUSB_GPIO_INT_SRC: /* TODO: What do these two return? */
+ case TUSB_GPIO_INT_SET:
+ case TUSB_GPIO_INT_CLEAR:
+ return s->gpio_intr;
+ case TUSB_GPIO_INT_MASK:
+ return s->gpio_mask;
+
+ case TUSB_INT_SRC:
+ case TUSB_INT_SRC_SET: /* TODO: What do these two return? */
+ case TUSB_INT_SRC_CLEAR:
+ return s->intr;
+ case TUSB_INT_MASK:
+ return s->mask;
+
+ case TUSB_GPIO_REV:
+ return 0x30;
+ case TUSB_GPIO_CONF:
+ return s->gpio_config;
+
+ case TUSB_DMA_CTRL_REV:
+ return 0x30;
+ case TUSB_DMA_REQ_CONF:
+ return s->dma_config;
+ case TUSB_EP0_CONF:
+ return s->ep0_config;
+ case TUSB_EP_IN_SIZE ... (TUSB_EP_IN_SIZE + 0x3b):
+ epnum = (offset - TUSB_EP_IN_SIZE) >> 2;
+ return s->tx_config[epnum];
+ case TUSB_DMA_EP_MAP:
+ return s->dma_map;
+ case TUSB_EP_OUT_SIZE ... (TUSB_EP_OUT_SIZE + 0x3b):
+ epnum = (offset - TUSB_EP_OUT_SIZE) >> 2;
+ return s->rx_config[epnum];
+ case TUSB_EP_MAX_PACKET_SIZE_OFFSET ...
+ (TUSB_EP_MAX_PACKET_SIZE_OFFSET + 0x3b):
+ return 0x00000000; /* TODO */
+ case TUSB_WAIT_COUNT:
+ return 0x00; /* TODO */
+
+ case TUSB_SCRATCH_PAD:
+ return s->scratch;
+
+ case TUSB_PROD_TEST_RESET:
+ return s->test_reset;
+
+ /* DIE IDs */
+ case TUSB_DIDR1_LO:
+ return 0xa9453c59;
+ case TUSB_DIDR1_HI:
+ return 0x54059adf;
+ }
+
+ printf("%s: unknown register at %03x\n", __FUNCTION__, offset);
+ return 0;
+}
+
+static void tusb_async_writeb(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ switch (addr & 0xfff) {
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ musb_write[0](s->musb, addr & 0x1ff, value);
+ break;
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ musb_write[0](s->musb, 0x20 + ((addr >> 3) & 0x3c), value);
+ break;
+
+ default:
+ printf("%s: unknown register at %03x\n",
+ __FUNCTION__, (int) (addr & 0xfff));
+ return;
+ }
+}
+
+static void tusb_async_writeh(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ switch (addr & 0xfff) {
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ musb_write[1](s->musb, addr & 0x1ff, value);
+ break;
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ musb_write[1](s->musb, 0x20 + ((addr >> 3) & 0x3c), value);
+ break;
+
+ default:
+ printf("%s: unknown register at %03x\n",
+ __FUNCTION__, (int) (addr & 0xfff));
+ return;
+ }
+}
+
+static void tusb_async_writew(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ TUSBState *s = (TUSBState *) opaque;
+ int offset = addr & 0xfff;
+ int epnum;
+
+ switch (offset) {
+ case TUSB_VLYNQ_CTRL:
+ break;
+
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ musb_write[2](s->musb, offset & 0x1ff, value);
+ break;
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ musb_write[2](s->musb, 0x20 + ((addr >> 3) & 0x3c), value);
+ break;
+
+ case TUSB_DEV_CONF:
+ s->dev_config = value;
+ s->host_mode = (value & TUSB_DEV_CONF_USB_HOST_MODE);
+ if (value & TUSB_DEV_CONF_PROD_TEST_MODE)
+ hw_error("%s: Product Test mode not allowed\n", __FUNCTION__);
+ break;
+
+ case TUSB_PHY_OTG_CTRL_ENABLE:
+ case TUSB_PHY_OTG_CTRL:
+ return; /* TODO */
+ case TUSB_DEV_OTG_TIMER:
+ s->otg_timer_val = value;
+ if (value & TUSB_DEV_OTG_TIMER_ENABLE)
+ timer_mod(s->otg_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ muldiv64(TUSB_DEV_OTG_TIMER_VAL(value),
+ get_ticks_per_sec(), TUSB_DEVCLOCK));
+ else
+ timer_del(s->otg_timer);
+ break;
+
+ case TUSB_PRCM_CONF:
+ s->prcm_config = value;
+ break;
+ case TUSB_PRCM_MNGMT:
+ s->prcm_mngmt = value;
+ break;
+ case TUSB_PRCM_WAKEUP_CLEAR:
+ break;
+ case TUSB_PRCM_WAKEUP_MASK:
+ s->wkup_mask = value;
+ break;
+
+ case TUSB_PULLUP_1_CTRL:
+ s->pullup[0] = value;
+ break;
+ case TUSB_PULLUP_2_CTRL:
+ s->pullup[1] = value;
+ break;
+ case TUSB_INT_CTRL_CONF:
+ s->control_config = value;
+ tusb_intr_update(s);
+ break;
+
+ case TUSB_USBIP_INT_SET:
+ s->usbip_intr |= value;
+ tusb_usbip_intr_update(s);
+ break;
+ case TUSB_USBIP_INT_CLEAR:
+ s->usbip_intr &= ~value;
+ tusb_usbip_intr_update(s);
+ musb_core_intr_clear(s->musb, ~value);
+ break;
+ case TUSB_USBIP_INT_MASK:
+ s->usbip_mask = value;
+ tusb_usbip_intr_update(s);
+ break;
+
+ case TUSB_DMA_INT_SET:
+ s->dma_intr |= value;
+ tusb_dma_intr_update(s);
+ break;
+ case TUSB_DMA_INT_CLEAR:
+ s->dma_intr &= ~value;
+ tusb_dma_intr_update(s);
+ break;
+ case TUSB_DMA_INT_MASK:
+ s->dma_mask = value;
+ tusb_dma_intr_update(s);
+ break;
+
+ case TUSB_GPIO_INT_SET:
+ s->gpio_intr |= value;
+ tusb_gpio_intr_update(s);
+ break;
+ case TUSB_GPIO_INT_CLEAR:
+ s->gpio_intr &= ~value;
+ tusb_gpio_intr_update(s);
+ break;
+ case TUSB_GPIO_INT_MASK:
+ s->gpio_mask = value;
+ tusb_gpio_intr_update(s);
+ break;
+
+ case TUSB_INT_SRC_SET:
+ s->intr |= value;
+ tusb_intr_update(s);
+ break;
+ case TUSB_INT_SRC_CLEAR:
+ s->intr &= ~value;
+ tusb_intr_update(s);
+ break;
+ case TUSB_INT_MASK:
+ s->mask = value;
+ tusb_intr_update(s);
+ break;
+
+ case TUSB_GPIO_CONF:
+ s->gpio_config = value;
+ break;
+ case TUSB_DMA_REQ_CONF:
+ s->dma_config = value;
+ break;
+ case TUSB_EP0_CONF:
+ s->ep0_config = value & 0x1ff;
+ musb_set_size(s->musb, 0, TUSB_EP0_CONFIG_XFR_SIZE(value),
+ value & TUSB_EP0_CONFIG_DIR_TX);
+ break;
+ case TUSB_EP_IN_SIZE ... (TUSB_EP_IN_SIZE + 0x3b):
+ epnum = (offset - TUSB_EP_IN_SIZE) >> 2;
+ s->tx_config[epnum] = value;
+ musb_set_size(s->musb, epnum + 1, TUSB_EP_CONFIG_XFR_SIZE(value), 1);
+ break;
+ case TUSB_DMA_EP_MAP:
+ s->dma_map = value;
+ break;
+ case TUSB_EP_OUT_SIZE ... (TUSB_EP_OUT_SIZE + 0x3b):
+ epnum = (offset - TUSB_EP_OUT_SIZE) >> 2;
+ s->rx_config[epnum] = value;
+ musb_set_size(s->musb, epnum + 1, TUSB_EP_CONFIG_XFR_SIZE(value), 0);
+ break;
+ case TUSB_EP_MAX_PACKET_SIZE_OFFSET ...
+ (TUSB_EP_MAX_PACKET_SIZE_OFFSET + 0x3b):
+ return; /* TODO */
+ case TUSB_WAIT_COUNT:
+ return; /* TODO */
+
+ case TUSB_SCRATCH_PAD:
+ s->scratch = value;
+ break;
+
+ case TUSB_PROD_TEST_RESET:
+ s->test_reset = value;
+ break;
+
+ default:
+ printf("%s: unknown register at %03x\n", __FUNCTION__, offset);
+ return;
+ }
+}
+
+static const MemoryRegionOps tusb_async_ops = {
+ .old_mmio = {
+ .read = { tusb_async_readb, tusb_async_readh, tusb_async_readw, },
+ .write = { tusb_async_writeb, tusb_async_writeh, tusb_async_writew, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void tusb_otg_tick(void *opaque)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ s->otg_timer_val = 0;
+ s->intr |= TUSB_INT_SRC_OTG_TIMEOUT;
+ tusb_intr_update(s);
+}
+
+static void tusb_power_tick(void *opaque)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ if (s->power) {
+ s->intr_ok = ~0;
+ tusb_intr_update(s);
+ }
+}
+
+static void tusb_musb_core_intr(void *opaque, int source, int level)
+{
+ TUSBState *s = (TUSBState *) opaque;
+ uint16_t otg_status = s->otg_status;
+
+ switch (source) {
+ case musb_set_vbus:
+ if (level)
+ otg_status |= TUSB_DEV_OTG_STAT_VBUS_VALID;
+ else
+ otg_status &= ~TUSB_DEV_OTG_STAT_VBUS_VALID;
+
+ /* XXX: only if TUSB_PHY_OTG_CTRL_OTG_VBUS_DET_EN set? */
+ /* XXX: only if TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN set? */
+ if (s->otg_status != otg_status) {
+ s->otg_status = otg_status;
+ s->intr |= TUSB_INT_SRC_VBUS_SENSE_CHNG;
+ tusb_intr_update(s);
+ }
+ break;
+
+ case musb_set_session:
+ /* XXX: only if TUSB_PHY_OTG_CTRL_OTG_SESS_END_EN set? */
+ /* XXX: only if TUSB_PRCM_MNGMT_OTG_SESS_END_EN set? */
+ if (level) {
+ s->otg_status |= TUSB_DEV_OTG_STAT_SESS_VALID;
+ s->otg_status &= ~TUSB_DEV_OTG_STAT_SESS_END;
+ } else {
+ s->otg_status &= ~TUSB_DEV_OTG_STAT_SESS_VALID;
+ s->otg_status |= TUSB_DEV_OTG_STAT_SESS_END;
+ }
+
+ /* XXX: some IRQ or anything? */
+ break;
+
+ case musb_irq_tx:
+ case musb_irq_rx:
+ s->usbip_intr = musb_core_intr_get(s->musb);
+ /* Fall through. */
+ default:
+ if (level)
+ s->intr |= 1 << source;
+ else
+ s->intr &= ~(1 << source);
+ tusb_intr_update(s);
+ break;
+ }
+}
+
+static void tusb6010_power(TUSBState *s, int on)
+{
+ if (!on) {
+ s->power = 0;
+ } else if (!s->power && on) {
+ s->power = 1;
+ /* Pull the interrupt down after TUSB6010 comes up. */
+ s->intr_ok = 0;
+ tusb_intr_update(s);
+ timer_mod(s->pwr_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + get_ticks_per_sec() / 2);
+ }
+}
+
+static void tusb6010_irq(void *opaque, int source, int level)
+{
+ if (source) {
+ tusb_musb_core_intr(opaque, source - 1, level);
+ } else {
+ tusb6010_power(opaque, level);
+ }
+}
+
+static void tusb6010_reset(DeviceState *dev)
+{
+ TUSBState *s = TUSB(dev);
+ int i;
+
+ s->test_reset = TUSB_PROD_TEST_RESET_VAL;
+ s->host_mode = 0;
+ s->dev_config = 0;
+ s->otg_status = 0; /* !TUSB_DEV_OTG_STAT_ID_STATUS means host mode */
+ s->power = 0;
+ s->mask = 0xffffffff;
+ s->intr = 0x00000000;
+ s->otg_timer_val = 0;
+ s->scratch = 0;
+ s->prcm_config = 0;
+ s->prcm_mngmt = 0;
+ s->intr_ok = 0;
+ s->usbip_intr = 0;
+ s->usbip_mask = 0;
+ s->gpio_intr = 0;
+ s->gpio_mask = 0;
+ s->gpio_config = 0;
+ s->dma_intr = 0;
+ s->dma_mask = 0;
+ s->dma_map = 0;
+ s->dma_config = 0;
+ s->ep0_config = 0;
+ s->wkup_mask = 0;
+ s->pullup[0] = s->pullup[1] = 0;
+ s->control_config = 0;
+ for (i = 0; i < 15; i++) {
+ s->rx_config[i] = s->tx_config[i] = 0;
+ }
+ musb_reset(s->musb);
+}
+
+static int tusb6010_init(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ TUSBState *s = TUSB(dev);
+
+ s->otg_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tusb_otg_tick, s);
+ s->pwr_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tusb_power_tick, s);
+ memory_region_init_io(&s->iomem[1], OBJECT(s), &tusb_async_ops, s,
+ "tusb-async", UINT32_MAX);
+ sysbus_init_mmio(sbd, &s->iomem[0]);
+ sysbus_init_mmio(sbd, &s->iomem[1]);
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_in(dev, tusb6010_irq, musb_irq_max + 1);
+ s->musb = musb_init(dev, 1);
+ return 0;
+}
+
+static void tusb6010_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = tusb6010_init;
+ dc->reset = tusb6010_reset;
+}
+
+static const TypeInfo tusb6010_info = {
+ .name = TYPE_TUSB6010,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TUSBState),
+ .class_init = tusb6010_class_init,
+};
+
+static void tusb6010_register_types(void)
+{
+ type_register_static(&tusb6010_info);
+}
+
+type_init(tusb6010_register_types)
diff --git a/hw/timer/twl92230.c b/hw/timer/twl92230.c
new file mode 100644
index 00000000..7ded4ba2
--- /dev/null
+++ b/hw/timer/twl92230.c
@@ -0,0 +1,887 @@
+/*
+ * TI TWL92230C energy-management companion device for the OMAP24xx.
+ * Aka. Menelaus (N4200 MENELAUS1_V2.2)
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/i2c/i2c.h"
+#include "sysemu/sysemu.h"
+#include "ui/console.h"
+
+#define VERBOSE 1
+
+#define TYPE_TWL92230 "twl92230"
+#define TWL92230(obj) OBJECT_CHECK(MenelausState, (obj), TYPE_TWL92230)
+
+typedef struct MenelausState {
+ I2CSlave parent_obj;
+
+ int firstbyte;
+ uint8_t reg;
+
+ uint8_t vcore[5];
+ uint8_t dcdc[3];
+ uint8_t ldo[8];
+ uint8_t sleep[2];
+ uint8_t osc;
+ uint8_t detect;
+ uint16_t mask;
+ uint16_t status;
+ uint8_t dir;
+ uint8_t inputs;
+ uint8_t outputs;
+ uint8_t bbsms;
+ uint8_t pull[4];
+ uint8_t mmc_ctrl[3];
+ uint8_t mmc_debounce;
+ struct {
+ uint8_t ctrl;
+ uint16_t comp;
+ QEMUTimer *hz_tm;
+ int64_t next;
+ struct tm tm;
+ struct tm new;
+ struct tm alm;
+ int sec_offset;
+ int alm_sec;
+ int next_comp;
+ } rtc;
+ uint16_t rtc_next_vmstate;
+ qemu_irq out[4];
+ uint8_t pwrbtn_state;
+} MenelausState;
+
+static inline void menelaus_update(MenelausState *s)
+{
+ qemu_set_irq(s->out[3], s->status & ~s->mask);
+}
+
+static inline void menelaus_rtc_start(MenelausState *s)
+{
+ s->rtc.next += qemu_clock_get_ms(rtc_clock);
+ timer_mod(s->rtc.hz_tm, s->rtc.next);
+}
+
+static inline void menelaus_rtc_stop(MenelausState *s)
+{
+ timer_del(s->rtc.hz_tm);
+ s->rtc.next -= qemu_clock_get_ms(rtc_clock);
+ if (s->rtc.next < 1)
+ s->rtc.next = 1;
+}
+
+static void menelaus_rtc_update(MenelausState *s)
+{
+ qemu_get_timedate(&s->rtc.tm, s->rtc.sec_offset);
+}
+
+static void menelaus_alm_update(MenelausState *s)
+{
+ if ((s->rtc.ctrl & 3) == 3)
+ s->rtc.alm_sec = qemu_timedate_diff(&s->rtc.alm) - s->rtc.sec_offset;
+}
+
+static void menelaus_rtc_hz(void *opaque)
+{
+ MenelausState *s = (MenelausState *) opaque;
+
+ s->rtc.next_comp --;
+ s->rtc.alm_sec --;
+ s->rtc.next += 1000;
+ timer_mod(s->rtc.hz_tm, s->rtc.next);
+ if ((s->rtc.ctrl >> 3) & 3) { /* EVERY */
+ menelaus_rtc_update(s);
+ if (((s->rtc.ctrl >> 3) & 3) == 1 && !s->rtc.tm.tm_sec)
+ s->status |= 1 << 8; /* RTCTMR */
+ else if (((s->rtc.ctrl >> 3) & 3) == 2 && !s->rtc.tm.tm_min)
+ s->status |= 1 << 8; /* RTCTMR */
+ else if (!s->rtc.tm.tm_hour)
+ s->status |= 1 << 8; /* RTCTMR */
+ } else
+ s->status |= 1 << 8; /* RTCTMR */
+ if ((s->rtc.ctrl >> 1) & 1) { /* RTC_AL_EN */
+ if (s->rtc.alm_sec == 0)
+ s->status |= 1 << 9; /* RTCALM */
+ /* TODO: wake-up */
+ }
+ if (s->rtc.next_comp <= 0) {
+ s->rtc.next -= muldiv64((int16_t) s->rtc.comp, 1000, 0x8000);
+ s->rtc.next_comp = 3600;
+ }
+ menelaus_update(s);
+}
+
+static void menelaus_reset(I2CSlave *i2c)
+{
+ MenelausState *s = TWL92230(i2c);
+
+ s->reg = 0x00;
+
+ s->vcore[0] = 0x0c; /* XXX: X-loader needs 0x8c? check! */
+ s->vcore[1] = 0x05;
+ s->vcore[2] = 0x02;
+ s->vcore[3] = 0x0c;
+ s->vcore[4] = 0x03;
+ s->dcdc[0] = 0x33; /* Depends on wiring */
+ s->dcdc[1] = 0x03;
+ s->dcdc[2] = 0x00;
+ s->ldo[0] = 0x95;
+ s->ldo[1] = 0x7e;
+ s->ldo[2] = 0x00;
+ s->ldo[3] = 0x00; /* Depends on wiring */
+ s->ldo[4] = 0x03; /* Depends on wiring */
+ s->ldo[5] = 0x00;
+ s->ldo[6] = 0x00;
+ s->ldo[7] = 0x00;
+ s->sleep[0] = 0x00;
+ s->sleep[1] = 0x00;
+ s->osc = 0x01;
+ s->detect = 0x09;
+ s->mask = 0x0fff;
+ s->status = 0;
+ s->dir = 0x07;
+ s->outputs = 0x00;
+ s->bbsms = 0x00;
+ s->pull[0] = 0x00;
+ s->pull[1] = 0x00;
+ s->pull[2] = 0x00;
+ s->pull[3] = 0x00;
+ s->mmc_ctrl[0] = 0x03;
+ s->mmc_ctrl[1] = 0xc0;
+ s->mmc_ctrl[2] = 0x00;
+ s->mmc_debounce = 0x05;
+
+ if (s->rtc.ctrl & 1)
+ menelaus_rtc_stop(s);
+ s->rtc.ctrl = 0x00;
+ s->rtc.comp = 0x0000;
+ s->rtc.next = 1000;
+ s->rtc.sec_offset = 0;
+ s->rtc.next_comp = 1800;
+ s->rtc.alm_sec = 1800;
+ s->rtc.alm.tm_sec = 0x00;
+ s->rtc.alm.tm_min = 0x00;
+ s->rtc.alm.tm_hour = 0x00;
+ s->rtc.alm.tm_mday = 0x01;
+ s->rtc.alm.tm_mon = 0x00;
+ s->rtc.alm.tm_year = 2004;
+ menelaus_update(s);
+}
+
+static void menelaus_gpio_set(void *opaque, int line, int level)
+{
+ MenelausState *s = (MenelausState *) opaque;
+
+ if (line < 3) {
+ /* No interrupt generated */
+ s->inputs &= ~(1 << line);
+ s->inputs |= level << line;
+ return;
+ }
+
+ if (!s->pwrbtn_state && level) {
+ s->status |= 1 << 11; /* PSHBTN */
+ menelaus_update(s);
+ }
+ s->pwrbtn_state = level;
+}
+
+#define MENELAUS_REV 0x01
+#define MENELAUS_VCORE_CTRL1 0x02
+#define MENELAUS_VCORE_CTRL2 0x03
+#define MENELAUS_VCORE_CTRL3 0x04
+#define MENELAUS_VCORE_CTRL4 0x05
+#define MENELAUS_VCORE_CTRL5 0x06
+#define MENELAUS_DCDC_CTRL1 0x07
+#define MENELAUS_DCDC_CTRL2 0x08
+#define MENELAUS_DCDC_CTRL3 0x09
+#define MENELAUS_LDO_CTRL1 0x0a
+#define MENELAUS_LDO_CTRL2 0x0b
+#define MENELAUS_LDO_CTRL3 0x0c
+#define MENELAUS_LDO_CTRL4 0x0d
+#define MENELAUS_LDO_CTRL5 0x0e
+#define MENELAUS_LDO_CTRL6 0x0f
+#define MENELAUS_LDO_CTRL7 0x10
+#define MENELAUS_LDO_CTRL8 0x11
+#define MENELAUS_SLEEP_CTRL1 0x12
+#define MENELAUS_SLEEP_CTRL2 0x13
+#define MENELAUS_DEVICE_OFF 0x14
+#define MENELAUS_OSC_CTRL 0x15
+#define MENELAUS_DETECT_CTRL 0x16
+#define MENELAUS_INT_MASK1 0x17
+#define MENELAUS_INT_MASK2 0x18
+#define MENELAUS_INT_STATUS1 0x19
+#define MENELAUS_INT_STATUS2 0x1a
+#define MENELAUS_INT_ACK1 0x1b
+#define MENELAUS_INT_ACK2 0x1c
+#define MENELAUS_GPIO_CTRL 0x1d
+#define MENELAUS_GPIO_IN 0x1e
+#define MENELAUS_GPIO_OUT 0x1f
+#define MENELAUS_BBSMS 0x20
+#define MENELAUS_RTC_CTRL 0x21
+#define MENELAUS_RTC_UPDATE 0x22
+#define MENELAUS_RTC_SEC 0x23
+#define MENELAUS_RTC_MIN 0x24
+#define MENELAUS_RTC_HR 0x25
+#define MENELAUS_RTC_DAY 0x26
+#define MENELAUS_RTC_MON 0x27
+#define MENELAUS_RTC_YR 0x28
+#define MENELAUS_RTC_WKDAY 0x29
+#define MENELAUS_RTC_AL_SEC 0x2a
+#define MENELAUS_RTC_AL_MIN 0x2b
+#define MENELAUS_RTC_AL_HR 0x2c
+#define MENELAUS_RTC_AL_DAY 0x2d
+#define MENELAUS_RTC_AL_MON 0x2e
+#define MENELAUS_RTC_AL_YR 0x2f
+#define MENELAUS_RTC_COMP_MSB 0x30
+#define MENELAUS_RTC_COMP_LSB 0x31
+#define MENELAUS_S1_PULL_EN 0x32
+#define MENELAUS_S1_PULL_DIR 0x33
+#define MENELAUS_S2_PULL_EN 0x34
+#define MENELAUS_S2_PULL_DIR 0x35
+#define MENELAUS_MCT_CTRL1 0x36
+#define MENELAUS_MCT_CTRL2 0x37
+#define MENELAUS_MCT_CTRL3 0x38
+#define MENELAUS_MCT_PIN_ST 0x39
+#define MENELAUS_DEBOUNCE1 0x3a
+
+static uint8_t menelaus_read(void *opaque, uint8_t addr)
+{
+ MenelausState *s = (MenelausState *) opaque;
+ int reg = 0;
+
+ switch (addr) {
+ case MENELAUS_REV:
+ return 0x22;
+
+ case MENELAUS_VCORE_CTRL5: reg ++;
+ case MENELAUS_VCORE_CTRL4: reg ++;
+ case MENELAUS_VCORE_CTRL3: reg ++;
+ case MENELAUS_VCORE_CTRL2: reg ++;
+ case MENELAUS_VCORE_CTRL1:
+ return s->vcore[reg];
+
+ case MENELAUS_DCDC_CTRL3: reg ++;
+ case MENELAUS_DCDC_CTRL2: reg ++;
+ case MENELAUS_DCDC_CTRL1:
+ return s->dcdc[reg];
+
+ case MENELAUS_LDO_CTRL8: reg ++;
+ case MENELAUS_LDO_CTRL7: reg ++;
+ case MENELAUS_LDO_CTRL6: reg ++;
+ case MENELAUS_LDO_CTRL5: reg ++;
+ case MENELAUS_LDO_CTRL4: reg ++;
+ case MENELAUS_LDO_CTRL3: reg ++;
+ case MENELAUS_LDO_CTRL2: reg ++;
+ case MENELAUS_LDO_CTRL1:
+ return s->ldo[reg];
+
+ case MENELAUS_SLEEP_CTRL2: reg ++;
+ case MENELAUS_SLEEP_CTRL1:
+ return s->sleep[reg];
+
+ case MENELAUS_DEVICE_OFF:
+ return 0;
+
+ case MENELAUS_OSC_CTRL:
+ return s->osc | (1 << 7); /* CLK32K_GOOD */
+
+ case MENELAUS_DETECT_CTRL:
+ return s->detect;
+
+ case MENELAUS_INT_MASK1:
+ return (s->mask >> 0) & 0xff;
+ case MENELAUS_INT_MASK2:
+ return (s->mask >> 8) & 0xff;
+
+ case MENELAUS_INT_STATUS1:
+ return (s->status >> 0) & 0xff;
+ case MENELAUS_INT_STATUS2:
+ return (s->status >> 8) & 0xff;
+
+ case MENELAUS_INT_ACK1:
+ case MENELAUS_INT_ACK2:
+ return 0;
+
+ case MENELAUS_GPIO_CTRL:
+ return s->dir;
+ case MENELAUS_GPIO_IN:
+ return s->inputs | (~s->dir & s->outputs);
+ case MENELAUS_GPIO_OUT:
+ return s->outputs;
+
+ case MENELAUS_BBSMS:
+ return s->bbsms;
+
+ case MENELAUS_RTC_CTRL:
+ return s->rtc.ctrl;
+ case MENELAUS_RTC_UPDATE:
+ return 0x00;
+ case MENELAUS_RTC_SEC:
+ menelaus_rtc_update(s);
+ return to_bcd(s->rtc.tm.tm_sec);
+ case MENELAUS_RTC_MIN:
+ menelaus_rtc_update(s);
+ return to_bcd(s->rtc.tm.tm_min);
+ case MENELAUS_RTC_HR:
+ menelaus_rtc_update(s);
+ if ((s->rtc.ctrl >> 2) & 1) /* MODE12_n24 */
+ return to_bcd((s->rtc.tm.tm_hour % 12) + 1) |
+ (!!(s->rtc.tm.tm_hour >= 12) << 7); /* PM_nAM */
+ else
+ return to_bcd(s->rtc.tm.tm_hour);
+ case MENELAUS_RTC_DAY:
+ menelaus_rtc_update(s);
+ return to_bcd(s->rtc.tm.tm_mday);
+ case MENELAUS_RTC_MON:
+ menelaus_rtc_update(s);
+ return to_bcd(s->rtc.tm.tm_mon + 1);
+ case MENELAUS_RTC_YR:
+ menelaus_rtc_update(s);
+ return to_bcd(s->rtc.tm.tm_year - 2000);
+ case MENELAUS_RTC_WKDAY:
+ menelaus_rtc_update(s);
+ return to_bcd(s->rtc.tm.tm_wday);
+ case MENELAUS_RTC_AL_SEC:
+ return to_bcd(s->rtc.alm.tm_sec);
+ case MENELAUS_RTC_AL_MIN:
+ return to_bcd(s->rtc.alm.tm_min);
+ case MENELAUS_RTC_AL_HR:
+ if ((s->rtc.ctrl >> 2) & 1) /* MODE12_n24 */
+ return to_bcd((s->rtc.alm.tm_hour % 12) + 1) |
+ (!!(s->rtc.alm.tm_hour >= 12) << 7);/* AL_PM_nAM */
+ else
+ return to_bcd(s->rtc.alm.tm_hour);
+ case MENELAUS_RTC_AL_DAY:
+ return to_bcd(s->rtc.alm.tm_mday);
+ case MENELAUS_RTC_AL_MON:
+ return to_bcd(s->rtc.alm.tm_mon + 1);
+ case MENELAUS_RTC_AL_YR:
+ return to_bcd(s->rtc.alm.tm_year - 2000);
+ case MENELAUS_RTC_COMP_MSB:
+ return (s->rtc.comp >> 8) & 0xff;
+ case MENELAUS_RTC_COMP_LSB:
+ return (s->rtc.comp >> 0) & 0xff;
+
+ case MENELAUS_S1_PULL_EN:
+ return s->pull[0];
+ case MENELAUS_S1_PULL_DIR:
+ return s->pull[1];
+ case MENELAUS_S2_PULL_EN:
+ return s->pull[2];
+ case MENELAUS_S2_PULL_DIR:
+ return s->pull[3];
+
+ case MENELAUS_MCT_CTRL3: reg ++;
+ case MENELAUS_MCT_CTRL2: reg ++;
+ case MENELAUS_MCT_CTRL1:
+ return s->mmc_ctrl[reg];
+ case MENELAUS_MCT_PIN_ST:
+ /* TODO: return the real Card Detect */
+ return 0;
+ case MENELAUS_DEBOUNCE1:
+ return s->mmc_debounce;
+
+ default:
+#ifdef VERBOSE
+ printf("%s: unknown register %02x\n", __FUNCTION__, addr);
+#endif
+ break;
+ }
+ return 0;
+}
+
+static void menelaus_write(void *opaque, uint8_t addr, uint8_t value)
+{
+ MenelausState *s = (MenelausState *) opaque;
+ int line;
+ int reg = 0;
+ struct tm tm;
+
+ switch (addr) {
+ case MENELAUS_VCORE_CTRL1:
+ s->vcore[0] = (value & 0xe) | MIN(value & 0x1f, 0x12);
+ break;
+ case MENELAUS_VCORE_CTRL2:
+ s->vcore[1] = value;
+ break;
+ case MENELAUS_VCORE_CTRL3:
+ s->vcore[2] = MIN(value & 0x1f, 0x12);
+ break;
+ case MENELAUS_VCORE_CTRL4:
+ s->vcore[3] = MIN(value & 0x1f, 0x12);
+ break;
+ case MENELAUS_VCORE_CTRL5:
+ s->vcore[4] = value & 3;
+ /* XXX
+ * auto set to 3 on M_Active, nRESWARM
+ * auto set to 0 on M_WaitOn, M_Backup
+ */
+ break;
+
+ case MENELAUS_DCDC_CTRL1:
+ s->dcdc[0] = value & 0x3f;
+ break;
+ case MENELAUS_DCDC_CTRL2:
+ s->dcdc[1] = value & 0x07;
+ /* XXX
+ * auto set to 3 on M_Active, nRESWARM
+ * auto set to 0 on M_WaitOn, M_Backup
+ */
+ break;
+ case MENELAUS_DCDC_CTRL3:
+ s->dcdc[2] = value & 0x07;
+ break;
+
+ case MENELAUS_LDO_CTRL1:
+ s->ldo[0] = value;
+ break;
+ case MENELAUS_LDO_CTRL2:
+ s->ldo[1] = value & 0x7f;
+ /* XXX
+ * auto set to 0x7e on M_WaitOn, M_Backup
+ */
+ break;
+ case MENELAUS_LDO_CTRL3:
+ s->ldo[2] = value & 3;
+ /* XXX
+ * auto set to 3 on M_Active, nRESWARM
+ * auto set to 0 on M_WaitOn, M_Backup
+ */
+ break;
+ case MENELAUS_LDO_CTRL4:
+ s->ldo[3] = value & 3;
+ /* XXX
+ * auto set to 3 on M_Active, nRESWARM
+ * auto set to 0 on M_WaitOn, M_Backup
+ */
+ break;
+ case MENELAUS_LDO_CTRL5:
+ s->ldo[4] = value & 3;
+ /* XXX
+ * auto set to 3 on M_Active, nRESWARM
+ * auto set to 0 on M_WaitOn, M_Backup
+ */
+ break;
+ case MENELAUS_LDO_CTRL6:
+ s->ldo[5] = value & 3;
+ break;
+ case MENELAUS_LDO_CTRL7:
+ s->ldo[6] = value & 3;
+ break;
+ case MENELAUS_LDO_CTRL8:
+ s->ldo[7] = value & 3;
+ break;
+
+ case MENELAUS_SLEEP_CTRL2: reg ++;
+ case MENELAUS_SLEEP_CTRL1:
+ s->sleep[reg] = value;
+ break;
+
+ case MENELAUS_DEVICE_OFF:
+ if (value & 1) {
+ menelaus_reset(I2C_SLAVE(s));
+ }
+ break;
+
+ case MENELAUS_OSC_CTRL:
+ s->osc = value & 7;
+ break;
+
+ case MENELAUS_DETECT_CTRL:
+ s->detect = value & 0x7f;
+ break;
+
+ case MENELAUS_INT_MASK1:
+ s->mask &= 0xf00;
+ s->mask |= value << 0;
+ menelaus_update(s);
+ break;
+ case MENELAUS_INT_MASK2:
+ s->mask &= 0x0ff;
+ s->mask |= value << 8;
+ menelaus_update(s);
+ break;
+
+ case MENELAUS_INT_ACK1:
+ s->status &= ~(((uint16_t) value) << 0);
+ menelaus_update(s);
+ break;
+ case MENELAUS_INT_ACK2:
+ s->status &= ~(((uint16_t) value) << 8);
+ menelaus_update(s);
+ break;
+
+ case MENELAUS_GPIO_CTRL:
+ for (line = 0; line < 3; line ++) {
+ if (((s->dir ^ value) >> line) & 1) {
+ qemu_set_irq(s->out[line],
+ ((s->outputs & ~s->dir) >> line) & 1);
+ }
+ }
+ s->dir = value & 0x67;
+ break;
+ case MENELAUS_GPIO_OUT:
+ for (line = 0; line < 3; line ++) {
+ if ((((s->outputs ^ value) & ~s->dir) >> line) & 1) {
+ qemu_set_irq(s->out[line], (s->outputs >> line) & 1);
+ }
+ }
+ s->outputs = value & 0x07;
+ break;
+
+ case MENELAUS_BBSMS:
+ s->bbsms = 0x0d;
+ break;
+
+ case MENELAUS_RTC_CTRL:
+ if ((s->rtc.ctrl ^ value) & 1) { /* RTC_EN */
+ if (value & 1)
+ menelaus_rtc_start(s);
+ else
+ menelaus_rtc_stop(s);
+ }
+ s->rtc.ctrl = value & 0x1f;
+ menelaus_alm_update(s);
+ break;
+ case MENELAUS_RTC_UPDATE:
+ menelaus_rtc_update(s);
+ memcpy(&tm, &s->rtc.tm, sizeof(tm));
+ switch (value & 0xf) {
+ case 0:
+ break;
+ case 1:
+ tm.tm_sec = s->rtc.new.tm_sec;
+ break;
+ case 2:
+ tm.tm_min = s->rtc.new.tm_min;
+ break;
+ case 3:
+ if (s->rtc.new.tm_hour > 23)
+ goto rtc_badness;
+ tm.tm_hour = s->rtc.new.tm_hour;
+ break;
+ case 4:
+ if (s->rtc.new.tm_mday < 1)
+ goto rtc_badness;
+ /* TODO check range */
+ tm.tm_mday = s->rtc.new.tm_mday;
+ break;
+ case 5:
+ if (s->rtc.new.tm_mon < 0 || s->rtc.new.tm_mon > 11)
+ goto rtc_badness;
+ tm.tm_mon = s->rtc.new.tm_mon;
+ break;
+ case 6:
+ tm.tm_year = s->rtc.new.tm_year;
+ break;
+ case 7:
+ /* TODO set .tm_mday instead */
+ tm.tm_wday = s->rtc.new.tm_wday;
+ break;
+ case 8:
+ if (s->rtc.new.tm_hour > 23)
+ goto rtc_badness;
+ if (s->rtc.new.tm_mday < 1)
+ goto rtc_badness;
+ if (s->rtc.new.tm_mon < 0 || s->rtc.new.tm_mon > 11)
+ goto rtc_badness;
+ tm.tm_sec = s->rtc.new.tm_sec;
+ tm.tm_min = s->rtc.new.tm_min;
+ tm.tm_hour = s->rtc.new.tm_hour;
+ tm.tm_mday = s->rtc.new.tm_mday;
+ tm.tm_mon = s->rtc.new.tm_mon;
+ tm.tm_year = s->rtc.new.tm_year;
+ break;
+ rtc_badness:
+ default:
+ fprintf(stderr, "%s: bad RTC_UPDATE value %02x\n",
+ __FUNCTION__, value);
+ s->status |= 1 << 10; /* RTCERR */
+ menelaus_update(s);
+ }
+ s->rtc.sec_offset = qemu_timedate_diff(&tm);
+ break;
+ case MENELAUS_RTC_SEC:
+ s->rtc.tm.tm_sec = from_bcd(value & 0x7f);
+ break;
+ case MENELAUS_RTC_MIN:
+ s->rtc.tm.tm_min = from_bcd(value & 0x7f);
+ break;
+ case MENELAUS_RTC_HR:
+ s->rtc.tm.tm_hour = (s->rtc.ctrl & (1 << 2)) ? /* MODE12_n24 */
+ MIN(from_bcd(value & 0x3f), 12) + ((value >> 7) ? 11 : -1) :
+ from_bcd(value & 0x3f);
+ break;
+ case MENELAUS_RTC_DAY:
+ s->rtc.tm.tm_mday = from_bcd(value);
+ break;
+ case MENELAUS_RTC_MON:
+ s->rtc.tm.tm_mon = MAX(1, from_bcd(value)) - 1;
+ break;
+ case MENELAUS_RTC_YR:
+ s->rtc.tm.tm_year = 2000 + from_bcd(value);
+ break;
+ case MENELAUS_RTC_WKDAY:
+ s->rtc.tm.tm_mday = from_bcd(value);
+ break;
+ case MENELAUS_RTC_AL_SEC:
+ s->rtc.alm.tm_sec = from_bcd(value & 0x7f);
+ menelaus_alm_update(s);
+ break;
+ case MENELAUS_RTC_AL_MIN:
+ s->rtc.alm.tm_min = from_bcd(value & 0x7f);
+ menelaus_alm_update(s);
+ break;
+ case MENELAUS_RTC_AL_HR:
+ s->rtc.alm.tm_hour = (s->rtc.ctrl & (1 << 2)) ? /* MODE12_n24 */
+ MIN(from_bcd(value & 0x3f), 12) + ((value >> 7) ? 11 : -1) :
+ from_bcd(value & 0x3f);
+ menelaus_alm_update(s);
+ break;
+ case MENELAUS_RTC_AL_DAY:
+ s->rtc.alm.tm_mday = from_bcd(value);
+ menelaus_alm_update(s);
+ break;
+ case MENELAUS_RTC_AL_MON:
+ s->rtc.alm.tm_mon = MAX(1, from_bcd(value)) - 1;
+ menelaus_alm_update(s);
+ break;
+ case MENELAUS_RTC_AL_YR:
+ s->rtc.alm.tm_year = 2000 + from_bcd(value);
+ menelaus_alm_update(s);
+ break;
+ case MENELAUS_RTC_COMP_MSB:
+ s->rtc.comp &= 0xff;
+ s->rtc.comp |= value << 8;
+ break;
+ case MENELAUS_RTC_COMP_LSB:
+ s->rtc.comp &= 0xff << 8;
+ s->rtc.comp |= value;
+ break;
+
+ case MENELAUS_S1_PULL_EN:
+ s->pull[0] = value;
+ break;
+ case MENELAUS_S1_PULL_DIR:
+ s->pull[1] = value & 0x1f;
+ break;
+ case MENELAUS_S2_PULL_EN:
+ s->pull[2] = value;
+ break;
+ case MENELAUS_S2_PULL_DIR:
+ s->pull[3] = value & 0x1f;
+ break;
+
+ case MENELAUS_MCT_CTRL1:
+ s->mmc_ctrl[0] = value & 0x7f;
+ break;
+ case MENELAUS_MCT_CTRL2:
+ s->mmc_ctrl[1] = value;
+ /* TODO update Card Detect interrupts */
+ break;
+ case MENELAUS_MCT_CTRL3:
+ s->mmc_ctrl[2] = value & 0xf;
+ break;
+ case MENELAUS_DEBOUNCE1:
+ s->mmc_debounce = value & 0x3f;
+ break;
+
+ default:
+#ifdef VERBOSE
+ printf("%s: unknown register %02x\n", __FUNCTION__, addr);
+#endif
+ }
+}
+
+static void menelaus_event(I2CSlave *i2c, enum i2c_event event)
+{
+ MenelausState *s = TWL92230(i2c);
+
+ if (event == I2C_START_SEND)
+ s->firstbyte = 1;
+}
+
+static int menelaus_tx(I2CSlave *i2c, uint8_t data)
+{
+ MenelausState *s = TWL92230(i2c);
+
+ /* Interpret register address byte */
+ if (s->firstbyte) {
+ s->reg = data;
+ s->firstbyte = 0;
+ } else
+ menelaus_write(s, s->reg ++, data);
+
+ return 0;
+}
+
+static int menelaus_rx(I2CSlave *i2c)
+{
+ MenelausState *s = TWL92230(i2c);
+
+ return menelaus_read(s, s->reg ++);
+}
+
+/* Save restore 32 bit int as uint16_t
+ This is a Big hack, but it is how the old state did it.
+ Or we broke compatibility in the state, or we can't use struct tm
+ */
+
+static int get_int32_as_uint16(QEMUFile *f, void *pv, size_t size)
+{
+ int *v = pv;
+ *v = qemu_get_be16(f);
+ return 0;
+}
+
+static void put_int32_as_uint16(QEMUFile *f, void *pv, size_t size)
+{
+ int *v = pv;
+ qemu_put_be16(f, *v);
+}
+
+static const VMStateInfo vmstate_hack_int32_as_uint16 = {
+ .name = "int32_as_uint16",
+ .get = get_int32_as_uint16,
+ .put = put_int32_as_uint16,
+};
+
+#define VMSTATE_UINT16_HACK(_f, _s) \
+ VMSTATE_SINGLE(_f, _s, 0, vmstate_hack_int32_as_uint16, int32_t)
+
+
+static const VMStateDescription vmstate_menelaus_tm = {
+ .name = "menelaus_tm",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16_HACK(tm_sec, struct tm),
+ VMSTATE_UINT16_HACK(tm_min, struct tm),
+ VMSTATE_UINT16_HACK(tm_hour, struct tm),
+ VMSTATE_UINT16_HACK(tm_mday, struct tm),
+ VMSTATE_UINT16_HACK(tm_min, struct tm),
+ VMSTATE_UINT16_HACK(tm_year, struct tm),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void menelaus_pre_save(void *opaque)
+{
+ MenelausState *s = opaque;
+ /* Should be <= 1000 */
+ s->rtc_next_vmstate = s->rtc.next - qemu_clock_get_ms(rtc_clock);
+}
+
+static int menelaus_post_load(void *opaque, int version_id)
+{
+ MenelausState *s = opaque;
+
+ if (s->rtc.ctrl & 1) /* RTC_EN */
+ menelaus_rtc_stop(s);
+
+ s->rtc.next = s->rtc_next_vmstate;
+
+ menelaus_alm_update(s);
+ menelaus_update(s);
+ if (s->rtc.ctrl & 1) /* RTC_EN */
+ menelaus_rtc_start(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_menelaus = {
+ .name = "menelaus",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_save = menelaus_pre_save,
+ .post_load = menelaus_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(firstbyte, MenelausState),
+ VMSTATE_UINT8(reg, MenelausState),
+ VMSTATE_UINT8_ARRAY(vcore, MenelausState, 5),
+ VMSTATE_UINT8_ARRAY(dcdc, MenelausState, 3),
+ VMSTATE_UINT8_ARRAY(ldo, MenelausState, 8),
+ VMSTATE_UINT8_ARRAY(sleep, MenelausState, 2),
+ VMSTATE_UINT8(osc, MenelausState),
+ VMSTATE_UINT8(detect, MenelausState),
+ VMSTATE_UINT16(mask, MenelausState),
+ VMSTATE_UINT16(status, MenelausState),
+ VMSTATE_UINT8(dir, MenelausState),
+ VMSTATE_UINT8(inputs, MenelausState),
+ VMSTATE_UINT8(outputs, MenelausState),
+ VMSTATE_UINT8(bbsms, MenelausState),
+ VMSTATE_UINT8_ARRAY(pull, MenelausState, 4),
+ VMSTATE_UINT8_ARRAY(mmc_ctrl, MenelausState, 3),
+ VMSTATE_UINT8(mmc_debounce, MenelausState),
+ VMSTATE_UINT8(rtc.ctrl, MenelausState),
+ VMSTATE_UINT16(rtc.comp, MenelausState),
+ VMSTATE_UINT16(rtc_next_vmstate, MenelausState),
+ VMSTATE_STRUCT(rtc.new, MenelausState, 0, vmstate_menelaus_tm,
+ struct tm),
+ VMSTATE_STRUCT(rtc.alm, MenelausState, 0, vmstate_menelaus_tm,
+ struct tm),
+ VMSTATE_UINT8(pwrbtn_state, MenelausState),
+ VMSTATE_I2C_SLAVE(parent_obj, MenelausState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int twl92230_init(I2CSlave *i2c)
+{
+ DeviceState *dev = DEVICE(i2c);
+ MenelausState *s = TWL92230(i2c);
+
+ s->rtc.hz_tm = timer_new_ms(rtc_clock, menelaus_rtc_hz, s);
+ /* Three output pins plus one interrupt pin. */
+ qdev_init_gpio_out(dev, s->out, 4);
+
+ /* Three input pins plus one power-button pin. */
+ qdev_init_gpio_in(dev, menelaus_gpio_set, 4);
+
+ menelaus_reset(i2c);
+
+ return 0;
+}
+
+static void twl92230_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
+
+ sc->init = twl92230_init;
+ sc->event = menelaus_event;
+ sc->recv = menelaus_rx;
+ sc->send = menelaus_tx;
+ dc->vmsd = &vmstate_menelaus;
+}
+
+static const TypeInfo twl92230_info = {
+ .name = TYPE_TWL92230,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(MenelausState),
+ .class_init = twl92230_class_init,
+};
+
+static void twl92230_register_types(void)
+{
+ type_register_static(&twl92230_info);
+}
+
+type_init(twl92230_register_types)
diff --git a/hw/timer/xilinx_timer.c b/hw/timer/xilinx_timer.c
new file mode 100644
index 00000000..cdb33551
--- /dev/null
+++ b/hw/timer/xilinx_timer.c
@@ -0,0 +1,265 @@
+/*
+ * QEMU model of the Xilinx timer block.
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * 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.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/ptimer.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+
+#define D(x)
+
+#define R_TCSR 0
+#define R_TLR 1
+#define R_TCR 2
+#define R_MAX 4
+
+#define TCSR_MDT (1<<0)
+#define TCSR_UDT (1<<1)
+#define TCSR_GENT (1<<2)
+#define TCSR_CAPT (1<<3)
+#define TCSR_ARHT (1<<4)
+#define TCSR_LOAD (1<<5)
+#define TCSR_ENIT (1<<6)
+#define TCSR_ENT (1<<7)
+#define TCSR_TINT (1<<8)
+#define TCSR_PWMA (1<<9)
+#define TCSR_ENALL (1<<10)
+
+struct xlx_timer
+{
+ QEMUBH *bh;
+ ptimer_state *ptimer;
+ void *parent;
+ int nr; /* for debug. */
+
+ unsigned long timer_div;
+
+ uint32_t regs[R_MAX];
+};
+
+#define TYPE_XILINX_TIMER "xlnx.xps-timer"
+#define XILINX_TIMER(obj) \
+ OBJECT_CHECK(struct timerblock, (obj), TYPE_XILINX_TIMER)
+
+struct timerblock
+{
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ qemu_irq irq;
+ uint8_t one_timer_only;
+ uint32_t freq_hz;
+ struct xlx_timer *timers;
+};
+
+static inline unsigned int num_timers(struct timerblock *t)
+{
+ return 2 - t->one_timer_only;
+}
+
+static inline unsigned int timer_from_addr(hwaddr addr)
+{
+ /* Timers get a 4x32bit control reg area each. */
+ return addr >> 2;
+}
+
+static void timer_update_irq(struct timerblock *t)
+{
+ unsigned int i, irq = 0;
+ uint32_t csr;
+
+ for (i = 0; i < num_timers(t); i++) {
+ csr = t->timers[i].regs[R_TCSR];
+ irq |= (csr & TCSR_TINT) && (csr & TCSR_ENIT);
+ }
+
+ /* All timers within the same slave share a single IRQ line. */
+ qemu_set_irq(t->irq, !!irq);
+}
+
+static uint64_t
+timer_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ struct timerblock *t = opaque;
+ struct xlx_timer *xt;
+ uint32_t r = 0;
+ unsigned int timer;
+
+ addr >>= 2;
+ timer = timer_from_addr(addr);
+ xt = &t->timers[timer];
+ /* Further decoding to address a specific timers reg. */
+ addr &= 0x3;
+ switch (addr)
+ {
+ case R_TCR:
+ r = ptimer_get_count(xt->ptimer);
+ if (!(xt->regs[R_TCSR] & TCSR_UDT))
+ r = ~r;
+ D(qemu_log("xlx_timer t=%d read counter=%x udt=%d\n",
+ timer, r, xt->regs[R_TCSR] & TCSR_UDT));
+ break;
+ default:
+ if (addr < ARRAY_SIZE(xt->regs))
+ r = xt->regs[addr];
+ break;
+
+ }
+ D(fprintf(stderr, "%s timer=%d %x=%x\n", __func__, timer, addr * 4, r));
+ return r;
+}
+
+static void timer_enable(struct xlx_timer *xt)
+{
+ uint64_t count;
+
+ D(fprintf(stderr, "%s timer=%d down=%d\n", __func__,
+ xt->nr, xt->regs[R_TCSR] & TCSR_UDT));
+
+ ptimer_stop(xt->ptimer);
+
+ if (xt->regs[R_TCSR] & TCSR_UDT)
+ count = xt->regs[R_TLR];
+ else
+ count = ~0 - xt->regs[R_TLR];
+ ptimer_set_limit(xt->ptimer, count, 1);
+ ptimer_run(xt->ptimer, 1);
+}
+
+static void
+timer_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ struct timerblock *t = opaque;
+ struct xlx_timer *xt;
+ unsigned int timer;
+ uint32_t value = val64;
+
+ addr >>= 2;
+ timer = timer_from_addr(addr);
+ xt = &t->timers[timer];
+ D(fprintf(stderr, "%s addr=%x val=%x (timer=%d off=%d)\n",
+ __func__, addr * 4, value, timer, addr & 3));
+ /* Further decoding to address a specific timers reg. */
+ addr &= 3;
+ switch (addr)
+ {
+ case R_TCSR:
+ if (value & TCSR_TINT)
+ value &= ~TCSR_TINT;
+
+ xt->regs[addr] = value & 0x7ff;
+ if (value & TCSR_ENT)
+ timer_enable(xt);
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(xt->regs))
+ xt->regs[addr] = value;
+ break;
+ }
+ timer_update_irq(t);
+}
+
+static const MemoryRegionOps timer_ops = {
+ .read = timer_read,
+ .write = timer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void timer_hit(void *opaque)
+{
+ struct xlx_timer *xt = opaque;
+ struct timerblock *t = xt->parent;
+ D(fprintf(stderr, "%s %d\n", __func__, xt->nr));
+ xt->regs[R_TCSR] |= TCSR_TINT;
+
+ if (xt->regs[R_TCSR] & TCSR_ARHT)
+ timer_enable(xt);
+ timer_update_irq(t);
+}
+
+static void xilinx_timer_realize(DeviceState *dev, Error **errp)
+{
+ struct timerblock *t = XILINX_TIMER(dev);
+ unsigned int i;
+
+ /* Init all the ptimers. */
+ t->timers = g_malloc0(sizeof t->timers[0] * num_timers(t));
+ for (i = 0; i < num_timers(t); i++) {
+ struct xlx_timer *xt = &t->timers[i];
+
+ xt->parent = t;
+ xt->nr = i;
+ xt->bh = qemu_bh_new(timer_hit, xt);
+ xt->ptimer = ptimer_init(xt->bh);
+ ptimer_set_freq(xt->ptimer, t->freq_hz);
+ }
+
+ memory_region_init_io(&t->mmio, OBJECT(t), &timer_ops, t, "xlnx.xps-timer",
+ R_MAX * 4 * num_timers(t));
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &t->mmio);
+}
+
+static void xilinx_timer_init(Object *obj)
+{
+ struct timerblock *t = XILINX_TIMER(obj);
+
+ /* All timers share a single irq line. */
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &t->irq);
+}
+
+static Property xilinx_timer_properties[] = {
+ DEFINE_PROP_UINT32("clock-frequency", struct timerblock, freq_hz,
+ 62 * 1000000),
+ DEFINE_PROP_UINT8("one-timer-only", struct timerblock, one_timer_only, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = xilinx_timer_realize;
+ dc->props = xilinx_timer_properties;
+}
+
+static const TypeInfo xilinx_timer_info = {
+ .name = TYPE_XILINX_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct timerblock),
+ .instance_init = xilinx_timer_init,
+ .class_init = xilinx_timer_class_init,
+};
+
+static void xilinx_timer_register_types(void)
+{
+ type_register_static(&xilinx_timer_info);
+}
+
+type_init(xilinx_timer_register_types)
diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs
new file mode 100644
index 00000000..64cecc3b
--- /dev/null
+++ b/hw/tpm/Makefile.objs
@@ -0,0 +1,2 @@
+common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o
+common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o tpm_util.o
diff --git a/hw/tpm/tpm_int.h b/hw/tpm/tpm_int.h
new file mode 100644
index 00000000..f2f285b3
--- /dev/null
+++ b/hw/tpm/tpm_int.h
@@ -0,0 +1,75 @@
+/*
+ * TPM configuration
+ *
+ * Copyright (C) 2011-2013 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef TPM_TPM_INT_H
+#define TPM_TPM_INT_H
+
+#include "exec/memory.h"
+#include "tpm_tis.h"
+
+/* overall state of the TPM interface */
+struct TPMState {
+ ISADevice busdev;
+ MemoryRegion mmio;
+
+ union {
+ TPMTISEmuState tis;
+ } s;
+
+ uint8_t locty_number;
+ TPMLocality *locty_data;
+
+ char *backend;
+ TPMBackend *be_driver;
+ TPMVersion be_tpm_version;
+};
+
+#define TPM(obj) OBJECT_CHECK(TPMState, (obj), TYPE_TPM_TIS)
+
+#define TPM_STANDARD_CMDLINE_OPTS \
+ { \
+ .name = "type", \
+ .type = QEMU_OPT_STRING, \
+ .help = "Type of TPM backend", \
+ }
+
+struct tpm_req_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t ordinal;
+} QEMU_PACKED;
+
+struct tpm_resp_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t errcode;
+} QEMU_PACKED;
+
+#define TPM_TAG_RQU_COMMAND 0xc1
+#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
+#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
+
+#define TPM_TAG_RSP_COMMAND 0xc4
+#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
+#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
+
+#define TPM_FAIL 9
+
+#define TPM_ORD_ContinueSelfTest 0x53
+#define TPM_ORD_GetTicks 0xf1
+
+
+/* TPM2 defines */
+#define TPM2_ST_NO_SESSIONS 0x8001
+
+#define TPM2_CC_ReadClock 0x00000181
+
+#endif /* TPM_TPM_INT_H */
diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c
new file mode 100644
index 00000000..79a8f98a
--- /dev/null
+++ b/hw/tpm/tpm_passthrough.c
@@ -0,0 +1,543 @@
+/*
+ * passthrough TPM driver
+ *
+ * Copyright (c) 2010 - 2013 IBM Corporation
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * Copyright (C) 2011 IAIK, Graz University of Technology
+ * Author: Andreas Niederl
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <dirent.h>
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/sockets.h"
+#include "sysemu/tpm_backend.h"
+#include "tpm_int.h"
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "sysemu/tpm_backend_int.h"
+#include "tpm_tis.h"
+#include "tpm_util.h"
+
+#define DEBUG_TPM 0
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_TPM) { \
+ fprintf(stderr, fmt, ## __VA_ARGS__); \
+ } \
+} while (0);
+
+#define TYPE_TPM_PASSTHROUGH "tpm-passthrough"
+#define TPM_PASSTHROUGH(obj) \
+ OBJECT_CHECK(TPMPassthruState, (obj), TYPE_TPM_PASSTHROUGH)
+
+static const TPMDriverOps tpm_passthrough_driver;
+
+/* data structures */
+typedef struct TPMPassthruThreadParams {
+ TPMState *tpm_state;
+
+ TPMRecvDataCB *recv_data_callback;
+ TPMBackend *tb;
+} TPMPassthruThreadParams;
+
+struct TPMPassthruState {
+ TPMBackend parent;
+
+ TPMBackendThread tbt;
+
+ TPMPassthruThreadParams tpm_thread_params;
+
+ char *tpm_dev;
+ int tpm_fd;
+ bool tpm_executing;
+ bool tpm_op_canceled;
+ int cancel_fd;
+ bool had_startup_error;
+
+ TPMVersion tpm_version;
+};
+
+typedef struct TPMPassthruState TPMPassthruState;
+
+#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
+
+/* functions */
+
+static void tpm_passthrough_cancel_cmd(TPMBackend *tb);
+
+static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
+{
+ return send_all(fd, buf, len);
+}
+
+static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
+{
+ return recv_all(fd, buf, len, true);
+}
+
+static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
+{
+ struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)buf;
+
+ return be32_to_cpu(resp->len);
+}
+
+/*
+ * Write an error message in the given output buffer.
+ */
+static void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
+{
+ if (out_len >= sizeof(struct tpm_resp_hdr)) {
+ struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
+
+ resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
+ resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
+ resp->errcode = cpu_to_be32(TPM_FAIL);
+ }
+}
+
+static bool tpm_passthrough_is_selftest(const uint8_t *in, uint32_t in_len)
+{
+ struct tpm_req_hdr *hdr = (struct tpm_req_hdr *)in;
+
+ if (in_len >= sizeof(*hdr)) {
+ return (be32_to_cpu(hdr->ordinal) == TPM_ORD_ContinueSelfTest);
+ }
+
+ return false;
+}
+
+static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
+ const uint8_t *in, uint32_t in_len,
+ uint8_t *out, uint32_t out_len,
+ bool *selftest_done)
+{
+ int ret;
+ bool is_selftest;
+ const struct tpm_resp_hdr *hdr;
+
+ tpm_pt->tpm_op_canceled = false;
+ tpm_pt->tpm_executing = true;
+ *selftest_done = false;
+
+ is_selftest = tpm_passthrough_is_selftest(in, in_len);
+
+ ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
+ if (ret != in_len) {
+ if (!tpm_pt->tpm_op_canceled ||
+ (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
+ error_report("tpm_passthrough: error while transmitting data "
+ "to TPM: %s (%i)",
+ strerror(errno), errno);
+ }
+ goto err_exit;
+ }
+
+ tpm_pt->tpm_executing = false;
+
+ ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
+ if (ret < 0) {
+ if (!tpm_pt->tpm_op_canceled ||
+ (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
+ error_report("tpm_passthrough: error while reading data from "
+ "TPM: %s (%i)",
+ strerror(errno), errno);
+ }
+ } else if (ret < sizeof(struct tpm_resp_hdr) ||
+ tpm_passthrough_get_size_from_buffer(out) != ret) {
+ ret = -1;
+ error_report("tpm_passthrough: received invalid response "
+ "packet from TPM");
+ }
+
+ if (is_selftest && (ret >= sizeof(struct tpm_resp_hdr))) {
+ hdr = (struct tpm_resp_hdr *)out;
+ *selftest_done = (be32_to_cpu(hdr->errcode) == 0);
+ }
+
+err_exit:
+ if (ret < 0) {
+ tpm_write_fatal_error_response(out, out_len);
+ }
+
+ tpm_pt->tpm_executing = false;
+
+ return ret;
+}
+
+static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt,
+ const TPMLocality *locty_data,
+ bool *selftest_done)
+{
+ return tpm_passthrough_unix_tx_bufs(tpm_pt,
+ locty_data->w_buffer.buffer,
+ locty_data->w_offset,
+ locty_data->r_buffer.buffer,
+ locty_data->r_buffer.size,
+ selftest_done);
+}
+
+static void tpm_passthrough_worker_thread(gpointer data,
+ gpointer user_data)
+{
+ TPMPassthruThreadParams *thr_parms = user_data;
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(thr_parms->tb);
+ TPMBackendCmd cmd = (TPMBackendCmd)data;
+ bool selftest_done = false;
+
+ DPRINTF("tpm_passthrough: processing command type %d\n", cmd);
+
+ switch (cmd) {
+ case TPM_BACKEND_CMD_PROCESS_CMD:
+ tpm_passthrough_unix_transfer(tpm_pt,
+ thr_parms->tpm_state->locty_data,
+ &selftest_done);
+
+ thr_parms->recv_data_callback(thr_parms->tpm_state,
+ thr_parms->tpm_state->locty_number,
+ selftest_done);
+ break;
+ case TPM_BACKEND_CMD_INIT:
+ case TPM_BACKEND_CMD_END:
+ case TPM_BACKEND_CMD_TPM_RESET:
+ /* nothing to do */
+ break;
+ }
+}
+
+/*
+ * Start the TPM (thread). If it had been started before, then terminate
+ * and start it again.
+ */
+static int tpm_passthrough_startup_tpm(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+
+ /* terminate a running TPM */
+ tpm_backend_thread_end(&tpm_pt->tbt);
+
+ tpm_backend_thread_create(&tpm_pt->tbt,
+ tpm_passthrough_worker_thread,
+ &tpm_pt->tpm_thread_params);
+
+ return 0;
+}
+
+static void tpm_passthrough_reset(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+
+ DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n");
+
+ tpm_passthrough_cancel_cmd(tb);
+
+ tpm_backend_thread_end(&tpm_pt->tbt);
+
+ tpm_pt->had_startup_error = false;
+}
+
+static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
+ TPMRecvDataCB *recv_data_cb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+
+ tpm_pt->tpm_thread_params.tpm_state = s;
+ tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
+ tpm_pt->tpm_thread_params.tb = tb;
+
+ return 0;
+}
+
+static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
+{
+ return false;
+}
+
+static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb,
+ uint8_t locty)
+{
+ /* only a TPM 2.0 will support this */
+ return 0;
+}
+
+static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+
+ return tpm_pt->had_startup_error;
+}
+
+static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
+{
+ size_t wanted_size = 4096; /* Linux tpm.c buffer size */
+
+ if (sb->size != wanted_size) {
+ sb->buffer = g_realloc(sb->buffer, wanted_size);
+ sb->size = wanted_size;
+ }
+ return sb->size;
+}
+
+static void tpm_passthrough_deliver_request(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+
+ tpm_backend_thread_deliver_request(&tpm_pt->tbt);
+}
+
+static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+ int n;
+
+ /*
+ * As of Linux 3.7 the tpm_tis driver does not properly cancel
+ * commands on all TPM manufacturers' TPMs.
+ * Only cancel if we're busy so we don't cancel someone else's
+ * command, e.g., a command executed on the host.
+ */
+ if (tpm_pt->tpm_executing) {
+ if (tpm_pt->cancel_fd >= 0) {
+ n = write(tpm_pt->cancel_fd, "-", 1);
+ if (n != 1) {
+ error_report("Canceling TPM command failed: %s",
+ strerror(errno));
+ } else {
+ tpm_pt->tpm_op_canceled = true;
+ }
+ } else {
+ error_report("Cannot cancel TPM command due to missing "
+ "TPM sysfs cancel entry");
+ }
+ }
+}
+
+static const char *tpm_passthrough_create_desc(void)
+{
+ return "Passthrough TPM backend driver";
+}
+
+static TPMVersion tpm_passthrough_get_tpm_version(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+
+ return tpm_pt->tpm_version;
+}
+
+/*
+ * Unless path or file descriptor set has been provided by user,
+ * determine the sysfs cancel file following kernel documentation
+ * in Documentation/ABI/stable/sysfs-class-tpm.
+ * From /dev/tpm0 create /sys/class/misc/tpm0/device/cancel
+ */
+static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+ int fd = -1;
+ char *dev;
+ char path[PATH_MAX];
+
+ if (tb->cancel_path) {
+ fd = qemu_open(tb->cancel_path, O_WRONLY);
+ if (fd < 0) {
+ error_report("Could not open TPM cancel path : %s",
+ strerror(errno));
+ }
+ return fd;
+ }
+
+ dev = strrchr(tpm_pt->tpm_dev, '/');
+ if (dev) {
+ dev++;
+ if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel",
+ dev) < sizeof(path)) {
+ fd = qemu_open(path, O_WRONLY);
+ if (fd >= 0) {
+ tb->cancel_path = g_strdup(path);
+ } else {
+ error_report("tpm_passthrough: Could not open TPM cancel "
+ "path %s : %s", path, strerror(errno));
+ }
+ }
+ } else {
+ error_report("tpm_passthrough: Bad TPM device path %s",
+ tpm_pt->tpm_dev);
+ }
+
+ return fd;
+}
+
+static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+ const char *value;
+
+ value = qemu_opt_get(opts, "cancel-path");
+ tb->cancel_path = g_strdup(value);
+
+ value = qemu_opt_get(opts, "path");
+ if (!value) {
+ value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
+ }
+
+ tpm_pt->tpm_dev = g_strdup(value);
+
+ tb->path = g_strdup(tpm_pt->tpm_dev);
+
+ tpm_pt->tpm_fd = qemu_open(tpm_pt->tpm_dev, O_RDWR);
+ if (tpm_pt->tpm_fd < 0) {
+ error_report("Cannot access TPM device using '%s': %s",
+ tpm_pt->tpm_dev, strerror(errno));
+ goto err_free_parameters;
+ }
+
+ if (tpm_util_test_tpmdev(tpm_pt->tpm_fd, &tpm_pt->tpm_version)) {
+ error_report("'%s' is not a TPM device.",
+ tpm_pt->tpm_dev);
+ goto err_close_tpmdev;
+ }
+
+ return 0;
+
+ err_close_tpmdev:
+ qemu_close(tpm_pt->tpm_fd);
+ tpm_pt->tpm_fd = -1;
+
+ err_free_parameters:
+ g_free(tb->path);
+ tb->path = NULL;
+
+ g_free(tpm_pt->tpm_dev);
+ tpm_pt->tpm_dev = NULL;
+
+ return 1;
+}
+
+static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
+{
+ Object *obj = object_new(TYPE_TPM_PASSTHROUGH);
+ TPMBackend *tb = TPM_BACKEND(obj);
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+
+ tb->id = g_strdup(id);
+ /* let frontend set the fe_model to proper value */
+ tb->fe_model = -1;
+
+ tb->ops = &tpm_passthrough_driver;
+
+ if (tpm_passthrough_handle_device_opts(opts, tb)) {
+ goto err_exit;
+ }
+
+ tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb);
+ if (tpm_pt->cancel_fd < 0) {
+ goto err_exit;
+ }
+
+ return tb;
+
+err_exit:
+ g_free(tb->id);
+
+ return NULL;
+}
+
+static void tpm_passthrough_destroy(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+
+ tpm_passthrough_cancel_cmd(tb);
+
+ tpm_backend_thread_end(&tpm_pt->tbt);
+
+ qemu_close(tpm_pt->tpm_fd);
+ qemu_close(tpm_pt->cancel_fd);
+
+ g_free(tb->id);
+ g_free(tb->path);
+ g_free(tb->cancel_path);
+ g_free(tpm_pt->tpm_dev);
+}
+
+static const QemuOptDesc tpm_passthrough_cmdline_opts[] = {
+ TPM_STANDARD_CMDLINE_OPTS,
+ {
+ .name = "cancel-path",
+ .type = QEMU_OPT_STRING,
+ .help = "Sysfs file entry for canceling TPM commands",
+ },
+ {
+ .name = "path",
+ .type = QEMU_OPT_STRING,
+ .help = "Path to TPM device on the host",
+ },
+ { /* end of list */ },
+};
+
+static const TPMDriverOps tpm_passthrough_driver = {
+ .type = TPM_TYPE_PASSTHROUGH,
+ .opts = tpm_passthrough_cmdline_opts,
+ .desc = tpm_passthrough_create_desc,
+ .create = tpm_passthrough_create,
+ .destroy = tpm_passthrough_destroy,
+ .init = tpm_passthrough_init,
+ .startup_tpm = tpm_passthrough_startup_tpm,
+ .realloc_buffer = tpm_passthrough_realloc_buffer,
+ .reset = tpm_passthrough_reset,
+ .had_startup_error = tpm_passthrough_get_startup_error,
+ .deliver_request = tpm_passthrough_deliver_request,
+ .cancel_cmd = tpm_passthrough_cancel_cmd,
+ .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
+ .reset_tpm_established_flag = tpm_passthrough_reset_tpm_established_flag,
+ .get_tpm_version = tpm_passthrough_get_tpm_version,
+};
+
+static void tpm_passthrough_inst_init(Object *obj)
+{
+}
+
+static void tpm_passthrough_inst_finalize(Object *obj)
+{
+}
+
+static void tpm_passthrough_class_init(ObjectClass *klass, void *data)
+{
+ TPMBackendClass *tbc = TPM_BACKEND_CLASS(klass);
+
+ tbc->ops = &tpm_passthrough_driver;
+}
+
+static const TypeInfo tpm_passthrough_info = {
+ .name = TYPE_TPM_PASSTHROUGH,
+ .parent = TYPE_TPM_BACKEND,
+ .instance_size = sizeof(TPMPassthruState),
+ .class_init = tpm_passthrough_class_init,
+ .instance_init = tpm_passthrough_inst_init,
+ .instance_finalize = tpm_passthrough_inst_finalize,
+};
+
+static void tpm_passthrough_register(void)
+{
+ type_register_static(&tpm_passthrough_info);
+ tpm_register_driver(&tpm_passthrough_driver);
+}
+
+type_init(tpm_passthrough_register)
diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c
new file mode 100644
index 00000000..0806b5f8
--- /dev/null
+++ b/hw/tpm/tpm_tis.c
@@ -0,0 +1,1099 @@
+/*
+ * tpm_tis.c - QEMU's TPM TIS interface emulator
+ *
+ * Copyright (C) 2006,2010-2013 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ * David Safford <safford@us.ibm.com>
+ *
+ * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Implementation of the TIS interface according to specs found at
+ * http://www.trustedcomputinggroup.org. This implementation currently
+ * supports version 1.3, 21 March 2013
+ * In the developers menu choose the PC Client section then find the TIS
+ * specification.
+ *
+ * TPM TIS for TPM 2 implementation following TCG PC Client Platform
+ * TPM Profile (PTP) Specification, Familiy 2.0, Revision 00.43
+ */
+
+#include "sysemu/tpm_backend.h"
+#include "tpm_int.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/pci/pci_ids.h"
+#include "tpm_tis.h"
+#include "qemu-common.h"
+#include "qemu/main-loop.h"
+#include "sysemu/tpm_backend.h"
+
+#define DEBUG_TIS 0
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_TIS) { \
+ printf(fmt, ## __VA_ARGS__); \
+ } \
+} while (0);
+
+/* whether the STS interrupt is supported */
+#define RAISE_STS_IRQ
+
+/* tis registers */
+#define TPM_TIS_REG_ACCESS 0x00
+#define TPM_TIS_REG_INT_ENABLE 0x08
+#define TPM_TIS_REG_INT_VECTOR 0x0c
+#define TPM_TIS_REG_INT_STATUS 0x10
+#define TPM_TIS_REG_INTF_CAPABILITY 0x14
+#define TPM_TIS_REG_STS 0x18
+#define TPM_TIS_REG_DATA_FIFO 0x24
+#define TPM_TIS_REG_INTERFACE_ID 0x30
+#define TPM_TIS_REG_DATA_XFIFO 0x80
+#define TPM_TIS_REG_DATA_XFIFO_END 0xbc
+#define TPM_TIS_REG_DID_VID 0xf00
+#define TPM_TIS_REG_RID 0xf04
+
+/* vendor-specific registers */
+#define TPM_TIS_REG_DEBUG 0xf90
+
+#define TPM_TIS_STS_TPM_FAMILY_MASK (0x3 << 26)/* TPM 2.0 */
+#define TPM_TIS_STS_TPM_FAMILY1_2 (0 << 26) /* TPM 2.0 */
+#define TPM_TIS_STS_TPM_FAMILY2_0 (1 << 26) /* TPM 2.0 */
+#define TPM_TIS_STS_RESET_ESTABLISHMENT_BIT (1 << 25) /* TPM 2.0 */
+#define TPM_TIS_STS_COMMAND_CANCEL (1 << 24) /* TPM 2.0 */
+
+#define TPM_TIS_STS_VALID (1 << 7)
+#define TPM_TIS_STS_COMMAND_READY (1 << 6)
+#define TPM_TIS_STS_TPM_GO (1 << 5)
+#define TPM_TIS_STS_DATA_AVAILABLE (1 << 4)
+#define TPM_TIS_STS_EXPECT (1 << 3)
+#define TPM_TIS_STS_SELFTEST_DONE (1 << 2)
+#define TPM_TIS_STS_RESPONSE_RETRY (1 << 1)
+
+#define TPM_TIS_BURST_COUNT_SHIFT 8
+#define TPM_TIS_BURST_COUNT(X) \
+ ((X) << TPM_TIS_BURST_COUNT_SHIFT)
+
+#define TPM_TIS_ACCESS_TPM_REG_VALID_STS (1 << 7)
+#define TPM_TIS_ACCESS_ACTIVE_LOCALITY (1 << 5)
+#define TPM_TIS_ACCESS_BEEN_SEIZED (1 << 4)
+#define TPM_TIS_ACCESS_SEIZE (1 << 3)
+#define TPM_TIS_ACCESS_PENDING_REQUEST (1 << 2)
+#define TPM_TIS_ACCESS_REQUEST_USE (1 << 1)
+#define TPM_TIS_ACCESS_TPM_ESTABLISHMENT (1 << 0)
+
+#define TPM_TIS_INT_ENABLED (1 << 31)
+#define TPM_TIS_INT_DATA_AVAILABLE (1 << 0)
+#define TPM_TIS_INT_STS_VALID (1 << 1)
+#define TPM_TIS_INT_LOCALITY_CHANGED (1 << 2)
+#define TPM_TIS_INT_COMMAND_READY (1 << 7)
+
+#define TPM_TIS_INT_POLARITY_MASK (3 << 3)
+#define TPM_TIS_INT_POLARITY_LOW_LEVEL (1 << 3)
+
+#ifndef RAISE_STS_IRQ
+
+#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \
+ TPM_TIS_INT_DATA_AVAILABLE | \
+ TPM_TIS_INT_COMMAND_READY)
+
+#else
+
+#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \
+ TPM_TIS_INT_DATA_AVAILABLE | \
+ TPM_TIS_INT_STS_VALID | \
+ TPM_TIS_INT_COMMAND_READY)
+
+#endif
+
+#define TPM_TIS_CAP_INTERFACE_VERSION1_3 (2 << 28)
+#define TPM_TIS_CAP_INTERFACE_VERSION1_3_FOR_TPM2_0 (3 << 28)
+#define TPM_TIS_CAP_DATA_TRANSFER_64B (3 << 9)
+#define TPM_TIS_CAP_DATA_TRANSFER_LEGACY (0 << 9)
+#define TPM_TIS_CAP_BURST_COUNT_DYNAMIC (0 << 8)
+#define TPM_TIS_CAP_INTERRUPT_LOW_LEVEL (1 << 4) /* support is mandatory */
+#define TPM_TIS_CAPABILITIES_SUPPORTED1_3 \
+ (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \
+ TPM_TIS_CAP_BURST_COUNT_DYNAMIC | \
+ TPM_TIS_CAP_DATA_TRANSFER_64B | \
+ TPM_TIS_CAP_INTERFACE_VERSION1_3 | \
+ TPM_TIS_INTERRUPTS_SUPPORTED)
+
+#define TPM_TIS_CAPABILITIES_SUPPORTED2_0 \
+ (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \
+ TPM_TIS_CAP_BURST_COUNT_DYNAMIC | \
+ TPM_TIS_CAP_DATA_TRANSFER_64B | \
+ TPM_TIS_CAP_INTERFACE_VERSION1_3_FOR_TPM2_0 | \
+ TPM_TIS_INTERRUPTS_SUPPORTED)
+
+#define TPM_TIS_IFACE_ID_INTERFACE_TIS1_3 (0xf) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_INTERFACE_FIFO (0x0) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_INTERFACE_VER_FIFO (0 << 4) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_CAP_5_LOCALITIES (1 << 8) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_CAP_TIS_SUPPORTED (1 << 13) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_INT_SEL_LOCK (1 << 19) /* TPM 2.0 */
+
+#define TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3 \
+ (TPM_TIS_IFACE_ID_INTERFACE_TIS1_3 | \
+ (~0 << 4)/* all of it is don't care */)
+
+/* if backend was a TPM 2.0: */
+#define TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0 \
+ (TPM_TIS_IFACE_ID_INTERFACE_FIFO | \
+ TPM_TIS_IFACE_ID_INTERFACE_VER_FIFO | \
+ TPM_TIS_IFACE_ID_CAP_5_LOCALITIES | \
+ TPM_TIS_IFACE_ID_CAP_TIS_SUPPORTED)
+
+#define TPM_TIS_TPM_DID 0x0001
+#define TPM_TIS_TPM_VID PCI_VENDOR_ID_IBM
+#define TPM_TIS_TPM_RID 0x0001
+
+#define TPM_TIS_NO_DATA_BYTE 0xff
+
+/* local prototypes */
+
+static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
+ unsigned size);
+
+/* utility functions */
+
+static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
+{
+ return (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7);
+}
+
+static uint32_t tpm_tis_get_size_from_buffer(const TPMSizedBuffer *sb)
+{
+ return be32_to_cpu(*(uint32_t *)&sb->buffer[2]);
+}
+
+static void tpm_tis_show_buffer(const TPMSizedBuffer *sb, const char *string)
+{
+#ifdef DEBUG_TIS
+ uint32_t len, i;
+
+ len = tpm_tis_get_size_from_buffer(sb);
+ DPRINTF("tpm_tis: %s length = %d\n", string, len);
+ for (i = 0; i < len; i++) {
+ if (i && !(i % 16)) {
+ DPRINTF("\n");
+ }
+ DPRINTF("%.2X ", sb->buffer[i]);
+ }
+ DPRINTF("\n");
+#endif
+}
+
+/*
+ * Set the given flags in the STS register by clearing the register but
+ * preserving the SELFTEST_DONE and TPM_FAMILY_MASK flags and then setting
+ * the new flags.
+ *
+ * The SELFTEST_DONE flag is acquired from the backend that determines it by
+ * peeking into TPM commands.
+ *
+ * A VM suspend/resume will preserve the flag by storing it into the VM
+ * device state, but the backend will not remember it when QEMU is started
+ * again. Therefore, we cache the flag here. Once set, it will not be unset
+ * except by a reset.
+ */
+static void tpm_tis_sts_set(TPMLocality *l, uint32_t flags)
+{
+ l->sts &= TPM_TIS_STS_SELFTEST_DONE | TPM_TIS_STS_TPM_FAMILY_MASK;
+ l->sts |= flags;
+}
+
+/*
+ * Send a request to the TPM.
+ */
+static void tpm_tis_tpm_send(TPMState *s, uint8_t locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+
+ tpm_tis_show_buffer(&tis->loc[locty].w_buffer, "tpm_tis: To TPM");
+
+ s->locty_number = locty;
+ s->locty_data = &tis->loc[locty];
+
+ /*
+ * w_offset serves as length indicator for length of data;
+ * it's reset when the response comes back
+ */
+ tis->loc[locty].state = TPM_TIS_STATE_EXECUTION;
+
+ tpm_backend_deliver_request(s->be_driver);
+}
+
+/* raise an interrupt if allowed */
+static void tpm_tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+
+ if (!TPM_TIS_IS_VALID_LOCTY(locty)) {
+ return;
+ }
+
+ if ((tis->loc[locty].inte & TPM_TIS_INT_ENABLED) &&
+ (tis->loc[locty].inte & irqmask)) {
+ DPRINTF("tpm_tis: Raising IRQ for flag %08x\n", irqmask);
+ qemu_irq_raise(s->s.tis.irq);
+ tis->loc[locty].ints |= irqmask;
+ }
+}
+
+static uint32_t tpm_tis_check_request_use_except(TPMState *s, uint8_t locty)
+{
+ uint8_t l;
+
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ if (l == locty) {
+ continue;
+ }
+ if ((s->s.tis.loc[l].access & TPM_TIS_ACCESS_REQUEST_USE)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void tpm_tis_new_active_locality(TPMState *s, uint8_t new_active_locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+ bool change = (s->s.tis.active_locty != new_active_locty);
+ bool is_seize;
+ uint8_t mask;
+
+ if (change && TPM_TIS_IS_VALID_LOCTY(s->s.tis.active_locty)) {
+ is_seize = TPM_TIS_IS_VALID_LOCTY(new_active_locty) &&
+ tis->loc[new_active_locty].access & TPM_TIS_ACCESS_SEIZE;
+
+ if (is_seize) {
+ mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ } else {
+ mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY|
+ TPM_TIS_ACCESS_REQUEST_USE);
+ }
+ /* reset flags on the old active locality */
+ tis->loc[s->s.tis.active_locty].access &= mask;
+
+ if (is_seize) {
+ tis->loc[tis->active_locty].access |= TPM_TIS_ACCESS_BEEN_SEIZED;
+ }
+ }
+
+ tis->active_locty = new_active_locty;
+
+ DPRINTF("tpm_tis: Active locality is now %d\n", s->s.tis.active_locty);
+
+ if (TPM_TIS_IS_VALID_LOCTY(new_active_locty)) {
+ /* set flags on the new active locality */
+ tis->loc[new_active_locty].access |= TPM_TIS_ACCESS_ACTIVE_LOCALITY;
+ tis->loc[new_active_locty].access &= ~(TPM_TIS_ACCESS_REQUEST_USE |
+ TPM_TIS_ACCESS_SEIZE);
+ }
+
+ if (change) {
+ tpm_tis_raise_irq(s, tis->active_locty, TPM_TIS_INT_LOCALITY_CHANGED);
+ }
+}
+
+/* abort -- this function switches the locality */
+static void tpm_tis_abort(TPMState *s, uint8_t locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+
+ tis->loc[locty].r_offset = 0;
+ tis->loc[locty].w_offset = 0;
+
+ DPRINTF("tpm_tis: tis_abort: new active locality is %d\n", tis->next_locty);
+
+ /*
+ * Need to react differently depending on who's aborting now and
+ * which locality will become active afterwards.
+ */
+ if (tis->aborting_locty == tis->next_locty) {
+ tis->loc[tis->aborting_locty].state = TPM_TIS_STATE_READY;
+ tpm_tis_sts_set(&tis->loc[tis->aborting_locty],
+ TPM_TIS_STS_COMMAND_READY);
+ tpm_tis_raise_irq(s, tis->aborting_locty, TPM_TIS_INT_COMMAND_READY);
+ }
+
+ /* locality after abort is another one than the current one */
+ tpm_tis_new_active_locality(s, tis->next_locty);
+
+ tis->next_locty = TPM_TIS_NO_LOCALITY;
+ /* nobody's aborting a command anymore */
+ tis->aborting_locty = TPM_TIS_NO_LOCALITY;
+}
+
+/* prepare aborting current command */
+static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+ uint8_t busy_locty;
+
+ tis->aborting_locty = locty;
+ tis->next_locty = newlocty; /* locality after successful abort */
+
+ /*
+ * only abort a command using an interrupt if currently executing
+ * a command AND if there's a valid connection to the vTPM.
+ */
+ for (busy_locty = 0; busy_locty < TPM_TIS_NUM_LOCALITIES; busy_locty++) {
+ if (tis->loc[busy_locty].state == TPM_TIS_STATE_EXECUTION) {
+ /*
+ * request the backend to cancel. Some backends may not
+ * support it
+ */
+ tpm_backend_cancel_cmd(s->be_driver);
+ return;
+ }
+ }
+
+ tpm_tis_abort(s, locty);
+}
+
+static void tpm_tis_receive_bh(void *opaque)
+{
+ TPMState *s = opaque;
+ TPMTISEmuState *tis = &s->s.tis;
+ uint8_t locty = s->locty_number;
+
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE);
+ tis->loc[locty].state = TPM_TIS_STATE_COMPLETION;
+ tis->loc[locty].r_offset = 0;
+ tis->loc[locty].w_offset = 0;
+
+ if (TPM_TIS_IS_VALID_LOCTY(tis->next_locty)) {
+ tpm_tis_abort(s, locty);
+ }
+
+#ifndef RAISE_STS_IRQ
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_DATA_AVAILABLE);
+#else
+ tpm_tis_raise_irq(s, locty,
+ TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID);
+#endif
+}
+
+/*
+ * Callback from the TPM to indicate that the response was received.
+ */
+static void tpm_tis_receive_cb(TPMState *s, uint8_t locty,
+ bool is_selftest_done)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+ uint8_t l;
+
+ assert(s->locty_number == locty);
+
+ if (is_selftest_done) {
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ tis->loc[locty].sts |= TPM_TIS_STS_SELFTEST_DONE;
+ }
+ }
+
+ qemu_bh_schedule(tis->bh);
+}
+
+/*
+ * Read a byte of response data
+ */
+static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+ uint32_t ret = TPM_TIS_NO_DATA_BYTE;
+ uint16_t len;
+
+ if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
+ len = tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer);
+
+ ret = tis->loc[locty].r_buffer.buffer[tis->loc[locty].r_offset++];
+ if (tis->loc[locty].r_offset >= len) {
+ /* got last byte */
+ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_VALID);
+#ifdef RAISE_STS_IRQ
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
+#endif
+ }
+ DPRINTF("tpm_tis: tpm_tis_data_read byte 0x%02x [%d]\n",
+ ret, tis->loc[locty].r_offset-1);
+ }
+
+ return ret;
+}
+
+#ifdef DEBUG_TIS
+static void tpm_tis_dump_state(void *opaque, hwaddr addr)
+{
+ static const unsigned regs[] = {
+ TPM_TIS_REG_ACCESS,
+ TPM_TIS_REG_INT_ENABLE,
+ TPM_TIS_REG_INT_VECTOR,
+ TPM_TIS_REG_INT_STATUS,
+ TPM_TIS_REG_INTF_CAPABILITY,
+ TPM_TIS_REG_STS,
+ TPM_TIS_REG_DID_VID,
+ TPM_TIS_REG_RID,
+ 0xfff};
+ int idx;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ hwaddr base = addr & ~0xfff;
+ TPMState *s = opaque;
+ TPMTISEmuState *tis = &s->s.tis;
+
+ DPRINTF("tpm_tis: active locality : %d\n"
+ "tpm_tis: state of locality %d : %d\n"
+ "tpm_tis: register dump:\n",
+ tis->active_locty,
+ locty, tis->loc[locty].state);
+
+ for (idx = 0; regs[idx] != 0xfff; idx++) {
+ DPRINTF("tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
+ (int)tpm_tis_mmio_read(opaque, base + regs[idx], 4));
+ }
+
+ DPRINTF("tpm_tis: read offset : %d\n"
+ "tpm_tis: result buffer : ",
+ tis->loc[locty].r_offset);
+ for (idx = 0;
+ idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer);
+ idx++) {
+ DPRINTF("%c%02x%s",
+ tis->loc[locty].r_offset == idx ? '>' : ' ',
+ tis->loc[locty].r_buffer.buffer[idx],
+ ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
+ }
+ DPRINTF("\n"
+ "tpm_tis: write offset : %d\n"
+ "tpm_tis: request buffer: ",
+ tis->loc[locty].w_offset);
+ for (idx = 0;
+ idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
+ idx++) {
+ DPRINTF("%c%02x%s",
+ tis->loc[locty].w_offset == idx ? '>' : ' ',
+ tis->loc[locty].w_buffer.buffer[idx],
+ ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
+ }
+ DPRINTF("\n");
+}
+#endif
+
+/*
+ * Read a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TPMState *s = opaque;
+ TPMTISEmuState *tis = &s->s.tis;
+ uint16_t offset = addr & 0xffc;
+ uint8_t shift = (addr & 0x3) * 8;
+ uint32_t val = 0xffffffff;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ uint32_t avail;
+ uint8_t v;
+
+ if (tpm_backend_had_startup_error(s->be_driver)) {
+ return val;
+ }
+
+ switch (offset) {
+ case TPM_TIS_REG_ACCESS:
+ /* never show the SEIZE flag even though we use it internally */
+ val = tis->loc[locty].access & ~TPM_TIS_ACCESS_SEIZE;
+ /* the pending flag is always calculated */
+ if (tpm_tis_check_request_use_except(s, locty)) {
+ val |= TPM_TIS_ACCESS_PENDING_REQUEST;
+ }
+ val |= !tpm_backend_get_tpm_established_flag(s->be_driver);
+ break;
+ case TPM_TIS_REG_INT_ENABLE:
+ val = tis->loc[locty].inte;
+ break;
+ case TPM_TIS_REG_INT_VECTOR:
+ val = tis->irq_num;
+ break;
+ case TPM_TIS_REG_INT_STATUS:
+ val = tis->loc[locty].ints;
+ break;
+ case TPM_TIS_REG_INTF_CAPABILITY:
+ switch (s->be_tpm_version) {
+ case TPM_VERSION_UNSPEC:
+ val = 0;
+ break;
+ case TPM_VERSION_1_2:
+ val = TPM_TIS_CAPABILITIES_SUPPORTED1_3;
+ break;
+ case TPM_VERSION_2_0:
+ val = TPM_TIS_CAPABILITIES_SUPPORTED2_0;
+ break;
+ }
+ break;
+ case TPM_TIS_REG_STS:
+ if (tis->active_locty == locty) {
+ if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
+ val = TPM_TIS_BURST_COUNT(
+ tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer)
+ - tis->loc[locty].r_offset) | tis->loc[locty].sts;
+ } else {
+ avail = tis->loc[locty].w_buffer.size
+ - tis->loc[locty].w_offset;
+ /*
+ * byte-sized reads should not return 0x00 for 0x100
+ * available bytes.
+ */
+ if (size == 1 && avail > 0xff) {
+ avail = 0xff;
+ }
+ val = TPM_TIS_BURST_COUNT(avail) | tis->loc[locty].sts;
+ }
+ }
+ break;
+ case TPM_TIS_REG_DATA_FIFO:
+ case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END:
+ if (tis->active_locty == locty) {
+ if (size > 4 - (addr & 0x3)) {
+ /* prevent access beyond FIFO */
+ size = 4 - (addr & 0x3);
+ }
+ val = 0;
+ shift = 0;
+ while (size > 0) {
+ switch (tis->loc[locty].state) {
+ case TPM_TIS_STATE_COMPLETION:
+ v = tpm_tis_data_read(s, locty);
+ break;
+ default:
+ v = TPM_TIS_NO_DATA_BYTE;
+ break;
+ }
+ val |= (v << shift);
+ shift += 8;
+ size--;
+ }
+ shift = 0; /* no more adjustments */
+ }
+ break;
+ case TPM_TIS_REG_INTERFACE_ID:
+ val = tis->loc[locty].iface_id;
+ break;
+ case TPM_TIS_REG_DID_VID:
+ val = (TPM_TIS_TPM_DID << 16) | TPM_TIS_TPM_VID;
+ break;
+ case TPM_TIS_REG_RID:
+ val = TPM_TIS_TPM_RID;
+ break;
+#ifdef DEBUG_TIS
+ case TPM_TIS_REG_DEBUG:
+ tpm_tis_dump_state(opaque, addr);
+ break;
+#endif
+ }
+
+ if (shift) {
+ val >>= shift;
+ }
+
+ DPRINTF("tpm_tis: read.%u(%08x) = %08x\n", size, (int)addr, (int)val);
+
+ return val;
+}
+
+/*
+ * Write a value to a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static void tpm_tis_mmio_write_intern(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size,
+ bool hw_access)
+{
+ TPMState *s = opaque;
+ TPMTISEmuState *tis = &s->s.tis;
+ uint16_t off = addr & 0xffc;
+ uint8_t shift = (addr & 0x3) * 8;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ uint8_t active_locty, l;
+ int c, set_new_locty = 1;
+ uint16_t len;
+ uint32_t mask = (size == 1) ? 0xff : ((size == 2) ? 0xffff : ~0);
+
+ DPRINTF("tpm_tis: write.%u(%08x) = %08x\n", size, (int)addr, (int)val);
+
+ if (locty == 4 && !hw_access) {
+ DPRINTF("tpm_tis: Access to locality 4 only allowed from hardware\n");
+ return;
+ }
+
+ if (tpm_backend_had_startup_error(s->be_driver)) {
+ return;
+ }
+
+ val &= mask;
+
+ if (shift) {
+ val <<= shift;
+ mask <<= shift;
+ }
+
+ mask ^= 0xffffffff;
+
+ switch (off) {
+ case TPM_TIS_REG_ACCESS:
+
+ if ((val & TPM_TIS_ACCESS_SEIZE)) {
+ val &= ~(TPM_TIS_ACCESS_REQUEST_USE |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ }
+
+ active_locty = tis->active_locty;
+
+ if ((val & TPM_TIS_ACCESS_ACTIVE_LOCALITY)) {
+ /* give up locality if currently owned */
+ if (tis->active_locty == locty) {
+ DPRINTF("tpm_tis: Releasing locality %d\n", locty);
+
+ uint8_t newlocty = TPM_TIS_NO_LOCALITY;
+ /* anybody wants the locality ? */
+ for (c = TPM_TIS_NUM_LOCALITIES - 1; c >= 0; c--) {
+ if ((tis->loc[c].access & TPM_TIS_ACCESS_REQUEST_USE)) {
+ DPRINTF("tpm_tis: Locality %d requests use.\n", c);
+ newlocty = c;
+ break;
+ }
+ }
+ DPRINTF("tpm_tis: TPM_TIS_ACCESS_ACTIVE_LOCALITY: "
+ "Next active locality: %d\n",
+ newlocty);
+
+ if (TPM_TIS_IS_VALID_LOCTY(newlocty)) {
+ set_new_locty = 0;
+ tpm_tis_prep_abort(s, locty, newlocty);
+ } else {
+ active_locty = TPM_TIS_NO_LOCALITY;
+ }
+ } else {
+ /* not currently the owner; clear a pending request */
+ tis->loc[locty].access &= ~TPM_TIS_ACCESS_REQUEST_USE;
+ }
+ }
+
+ if ((val & TPM_TIS_ACCESS_BEEN_SEIZED)) {
+ tis->loc[locty].access &= ~TPM_TIS_ACCESS_BEEN_SEIZED;
+ }
+
+ if ((val & TPM_TIS_ACCESS_SEIZE)) {
+ /*
+ * allow seize if a locality is active and the requesting
+ * locality is higher than the one that's active
+ * OR
+ * allow seize for requesting locality if no locality is
+ * active
+ */
+ while ((TPM_TIS_IS_VALID_LOCTY(tis->active_locty) &&
+ locty > tis->active_locty) ||
+ !TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) {
+ bool higher_seize = FALSE;
+
+ /* already a pending SEIZE ? */
+ if ((tis->loc[locty].access & TPM_TIS_ACCESS_SEIZE)) {
+ break;
+ }
+
+ /* check for ongoing seize by a higher locality */
+ for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ if ((tis->loc[l].access & TPM_TIS_ACCESS_SEIZE)) {
+ higher_seize = TRUE;
+ break;
+ }
+ }
+
+ if (higher_seize) {
+ break;
+ }
+
+ /* cancel any seize by a lower locality */
+ for (l = 0; l < locty - 1; l++) {
+ tis->loc[l].access &= ~TPM_TIS_ACCESS_SEIZE;
+ }
+
+ tis->loc[locty].access |= TPM_TIS_ACCESS_SEIZE;
+ DPRINTF("tpm_tis: TPM_TIS_ACCESS_SEIZE: "
+ "Locality %d seized from locality %d\n",
+ locty, tis->active_locty);
+ DPRINTF("tpm_tis: TPM_TIS_ACCESS_SEIZE: Initiating abort.\n");
+ set_new_locty = 0;
+ tpm_tis_prep_abort(s, tis->active_locty, locty);
+ break;
+ }
+ }
+
+ if ((val & TPM_TIS_ACCESS_REQUEST_USE)) {
+ if (tis->active_locty != locty) {
+ if (TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) {
+ tis->loc[locty].access |= TPM_TIS_ACCESS_REQUEST_USE;
+ } else {
+ /* no locality active -> make this one active now */
+ active_locty = locty;
+ }
+ }
+ }
+
+ if (set_new_locty) {
+ tpm_tis_new_active_locality(s, active_locty);
+ }
+
+ break;
+ case TPM_TIS_REG_INT_ENABLE:
+ if (tis->active_locty != locty) {
+ break;
+ }
+
+ tis->loc[locty].inte &= mask;
+ tis->loc[locty].inte |= (val & (TPM_TIS_INT_ENABLED |
+ TPM_TIS_INT_POLARITY_MASK |
+ TPM_TIS_INTERRUPTS_SUPPORTED));
+ break;
+ case TPM_TIS_REG_INT_VECTOR:
+ /* hard wired -- ignore */
+ break;
+ case TPM_TIS_REG_INT_STATUS:
+ if (tis->active_locty != locty) {
+ break;
+ }
+
+ /* clearing of interrupt flags */
+ if (((val & TPM_TIS_INTERRUPTS_SUPPORTED)) &&
+ (tis->loc[locty].ints & TPM_TIS_INTERRUPTS_SUPPORTED)) {
+ tis->loc[locty].ints &= ~val;
+ if (tis->loc[locty].ints == 0) {
+ qemu_irq_lower(tis->irq);
+ DPRINTF("tpm_tis: Lowering IRQ\n");
+ }
+ }
+ tis->loc[locty].ints &= ~(val & TPM_TIS_INTERRUPTS_SUPPORTED);
+ break;
+ case TPM_TIS_REG_STS:
+ if (tis->active_locty != locty) {
+ break;
+ }
+
+ if (s->be_tpm_version == TPM_VERSION_2_0) {
+ /* some flags that are only supported for TPM 2 */
+ if (val & TPM_TIS_STS_COMMAND_CANCEL) {
+ if (tis->loc[locty].state == TPM_TIS_STATE_EXECUTION) {
+ /*
+ * request the backend to cancel. Some backends may not
+ * support it
+ */
+ tpm_backend_cancel_cmd(s->be_driver);
+ }
+ }
+
+ if (val & TPM_TIS_STS_RESET_ESTABLISHMENT_BIT) {
+ if (locty == 3 || locty == 4) {
+ tpm_backend_reset_tpm_established_flag(s->be_driver, locty);
+ }
+ }
+ }
+
+ val &= (TPM_TIS_STS_COMMAND_READY | TPM_TIS_STS_TPM_GO |
+ TPM_TIS_STS_RESPONSE_RETRY);
+
+ if (val == TPM_TIS_STS_COMMAND_READY) {
+ switch (tis->loc[locty].state) {
+
+ case TPM_TIS_STATE_READY:
+ tis->loc[locty].w_offset = 0;
+ tis->loc[locty].r_offset = 0;
+ break;
+
+ case TPM_TIS_STATE_IDLE:
+ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_COMMAND_READY);
+ tis->loc[locty].state = TPM_TIS_STATE_READY;
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
+ break;
+
+ case TPM_TIS_STATE_EXECUTION:
+ case TPM_TIS_STATE_RECEPTION:
+ /* abort currently running command */
+ DPRINTF("tpm_tis: %s: Initiating abort.\n",
+ __func__);
+ tpm_tis_prep_abort(s, locty, locty);
+ break;
+
+ case TPM_TIS_STATE_COMPLETION:
+ tis->loc[locty].w_offset = 0;
+ tis->loc[locty].r_offset = 0;
+ /* shortcut to ready state with C/R set */
+ tis->loc[locty].state = TPM_TIS_STATE_READY;
+ if (!(tis->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) {
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_COMMAND_READY);
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
+ }
+ tis->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE);
+ break;
+
+ }
+ } else if (val == TPM_TIS_STS_TPM_GO) {
+ switch (tis->loc[locty].state) {
+ case TPM_TIS_STATE_RECEPTION:
+ if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT) == 0) {
+ tpm_tis_tpm_send(s, locty);
+ }
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ } else if (val == TPM_TIS_STS_RESPONSE_RETRY) {
+ switch (tis->loc[locty].state) {
+ case TPM_TIS_STATE_COMPLETION:
+ tis->loc[locty].r_offset = 0;
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_VALID|
+ TPM_TIS_STS_DATA_AVAILABLE);
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+ break;
+ case TPM_TIS_REG_DATA_FIFO:
+ case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END:
+ /* data fifo */
+ if (tis->active_locty != locty) {
+ break;
+ }
+
+ if (tis->loc[locty].state == TPM_TIS_STATE_IDLE ||
+ tis->loc[locty].state == TPM_TIS_STATE_EXECUTION ||
+ tis->loc[locty].state == TPM_TIS_STATE_COMPLETION) {
+ /* drop the byte */
+ } else {
+ DPRINTF("tpm_tis: Data to send to TPM: %08x (size=%d)\n",
+ (int)val, size);
+ if (tis->loc[locty].state == TPM_TIS_STATE_READY) {
+ tis->loc[locty].state = TPM_TIS_STATE_RECEPTION;
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID);
+ }
+
+ val >>= shift;
+ if (size > 4 - (addr & 0x3)) {
+ /* prevent access beyond FIFO */
+ size = 4 - (addr & 0x3);
+ }
+
+ while ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT) && size > 0) {
+ if (tis->loc[locty].w_offset < tis->loc[locty].w_buffer.size) {
+ tis->loc[locty].w_buffer.
+ buffer[tis->loc[locty].w_offset++] = (uint8_t)val;
+ val >>= 8;
+ size--;
+ } else {
+ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_VALID);
+ }
+ }
+
+ /* check for complete packet */
+ if (tis->loc[locty].w_offset > 5 &&
+ (tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
+ /* we have a packet length - see if we have all of it */
+#ifdef RAISE_STS_IRQ
+ bool need_irq = !(tis->loc[locty].sts & TPM_TIS_STS_VALID);
+#endif
+ len = tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
+ if (len > tis->loc[locty].w_offset) {
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID);
+ } else {
+ /* packet complete */
+ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_VALID);
+ }
+#ifdef RAISE_STS_IRQ
+ if (need_irq) {
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
+ }
+#endif
+ }
+ }
+ break;
+ case TPM_TIS_REG_INTERFACE_ID:
+ if (val & TPM_TIS_IFACE_ID_INT_SEL_LOCK) {
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ tis->loc[l].iface_id |= TPM_TIS_IFACE_ID_INT_SEL_LOCK;
+ }
+ }
+ break;
+ }
+}
+
+static void tpm_tis_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ tpm_tis_mmio_write_intern(opaque, addr, val, size, false);
+}
+
+static const MemoryRegionOps tpm_tis_memory_ops = {
+ .read = tpm_tis_mmio_read,
+ .write = tpm_tis_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static int tpm_tis_do_startup_tpm(TPMState *s)
+{
+ return tpm_backend_startup_tpm(s->be_driver);
+}
+
+/*
+ * Get the TPMVersion of the backend device being used
+ */
+TPMVersion tpm_tis_get_tpm_version(Object *obj)
+{
+ TPMState *s = TPM(obj);
+
+ return tpm_backend_get_tpm_version(s->be_driver);
+}
+
+/*
+ * This function is called when the machine starts, resets or due to
+ * S3 resume.
+ */
+static void tpm_tis_reset(DeviceState *dev)
+{
+ TPMState *s = TPM(dev);
+ TPMTISEmuState *tis = &s->s.tis;
+ int c;
+
+ s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver);
+
+ tpm_backend_reset(s->be_driver);
+
+ tis->active_locty = TPM_TIS_NO_LOCALITY;
+ tis->next_locty = TPM_TIS_NO_LOCALITY;
+ tis->aborting_locty = TPM_TIS_NO_LOCALITY;
+
+ for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) {
+ tis->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS;
+ switch (s->be_tpm_version) {
+ case TPM_VERSION_UNSPEC:
+ break;
+ case TPM_VERSION_1_2:
+ tis->loc[c].sts = TPM_TIS_STS_TPM_FAMILY1_2;
+ tis->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3;
+ break;
+ case TPM_VERSION_2_0:
+ tis->loc[c].sts = TPM_TIS_STS_TPM_FAMILY2_0;
+ tis->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0;
+ break;
+ }
+ tis->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL;
+ tis->loc[c].ints = 0;
+ tis->loc[c].state = TPM_TIS_STATE_IDLE;
+
+ tis->loc[c].w_offset = 0;
+ tpm_backend_realloc_buffer(s->be_driver, &tis->loc[c].w_buffer);
+ tis->loc[c].r_offset = 0;
+ tpm_backend_realloc_buffer(s->be_driver, &tis->loc[c].r_buffer);
+ }
+
+ tpm_tis_do_startup_tpm(s);
+}
+
+static const VMStateDescription vmstate_tpm_tis = {
+ .name = "tpm",
+ .unmigratable = 1,
+};
+
+static Property tpm_tis_properties[] = {
+ DEFINE_PROP_UINT32("irq", TPMState,
+ s.tis.irq_num, TPM_TIS_IRQ),
+ DEFINE_PROP_STRING("tpmdev", TPMState, backend),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_tis_realizefn(DeviceState *dev, Error **errp)
+{
+ TPMState *s = TPM(dev);
+ TPMTISEmuState *tis = &s->s.tis;
+
+ s->be_driver = qemu_find_tpm(s->backend);
+ if (!s->be_driver) {
+ error_setg(errp, "tpm_tis: backend driver with id %s could not be "
+ "found", s->backend);
+ return;
+ }
+
+ s->be_driver->fe_model = TPM_MODEL_TPM_TIS;
+
+ if (tpm_backend_init(s->be_driver, s, tpm_tis_receive_cb)) {
+ error_setg(errp, "tpm_tis: backend driver with id %s could not be "
+ "initialized", s->backend);
+ return;
+ }
+
+ if (tis->irq_num > 15) {
+ error_setg(errp, "tpm_tis: IRQ %d for TPM TIS is outside valid range "
+ "of 0 to 15.\n", tis->irq_num);
+ return;
+ }
+
+ tis->bh = qemu_bh_new(tpm_tis_receive_bh, s);
+
+ isa_init_irq(&s->busdev, &tis->irq, tis->irq_num);
+
+ memory_region_add_subregion(isa_address_space(ISA_DEVICE(dev)),
+ TPM_TIS_ADDR_BASE, &s->mmio);
+}
+
+static void tpm_tis_initfn(Object *obj)
+{
+ TPMState *s = TPM(obj);
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &tpm_tis_memory_ops,
+ s, "tpm-tis-mmio",
+ TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
+}
+
+static void tpm_tis_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = tpm_tis_realizefn;
+ dc->props = tpm_tis_properties;
+ dc->reset = tpm_tis_reset;
+ dc->vmsd = &vmstate_tpm_tis;
+}
+
+static const TypeInfo tpm_tis_info = {
+ .name = TYPE_TPM_TIS,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(TPMState),
+ .instance_init = tpm_tis_initfn,
+ .class_init = tpm_tis_class_init,
+};
+
+static void tpm_tis_register(void)
+{
+ type_register_static(&tpm_tis_info);
+ tpm_register_model(TPM_MODEL_TPM_TIS);
+}
+
+type_init(tpm_tis_register)
diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
new file mode 100644
index 00000000..a1df41fa
--- /dev/null
+++ b/hw/tpm/tpm_tis.h
@@ -0,0 +1,70 @@
+/*
+ * tpm_tis.h - QEMU's TPM TIS interface emulator
+ *
+ * Copyright (C) 2006, 2010-2013 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ * David Safford <safford@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Implementation of the TIS interface according to specs found at
+ * http://www.trustedcomputinggroup.org
+ *
+ */
+#ifndef TPM_TPM_TIS_H
+#define TPM_TPM_TIS_H
+
+#include "hw/isa/isa.h"
+#include "hw/acpi/tpm.h"
+#include "qemu-common.h"
+
+#define TPM_TIS_NUM_LOCALITIES 5 /* per spec */
+#define TPM_TIS_LOCALITY_SHIFT 12
+#define TPM_TIS_NO_LOCALITY 0xff
+
+#define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
+
+#define TPM_TIS_BUFFER_MAX 4096
+
+typedef enum {
+ TPM_TIS_STATE_IDLE = 0,
+ TPM_TIS_STATE_READY,
+ TPM_TIS_STATE_COMPLETION,
+ TPM_TIS_STATE_EXECUTION,
+ TPM_TIS_STATE_RECEPTION,
+} TPMTISState;
+
+/* locality data -- all fields are persisted */
+typedef struct TPMLocality {
+ TPMTISState state;
+ uint8_t access;
+ uint32_t sts;
+ uint32_t iface_id;
+ uint32_t inte;
+ uint32_t ints;
+
+ uint16_t w_offset;
+ uint16_t r_offset;
+ TPMSizedBuffer w_buffer;
+ TPMSizedBuffer r_buffer;
+} TPMLocality;
+
+typedef struct TPMTISEmuState {
+ QEMUBH *bh;
+ uint32_t offset;
+ uint8_t buf[TPM_TIS_BUFFER_MAX];
+
+ uint8_t active_locty;
+ uint8_t aborting_locty;
+ uint8_t next_locty;
+
+ TPMLocality loc[TPM_TIS_NUM_LOCALITIES];
+
+ qemu_irq irq;
+ uint32_t irq_num;
+} TPMTISEmuState;
+
+#endif /* TPM_TPM_TIS_H */
diff --git a/hw/tpm/tpm_util.c b/hw/tpm/tpm_util.c
new file mode 100644
index 00000000..4ace5852
--- /dev/null
+++ b/hw/tpm/tpm_util.c
@@ -0,0 +1,126 @@
+/*
+ * TPM utility functions
+ *
+ * Copyright (c) 2010 - 2015 IBM Corporation
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "tpm_util.h"
+#include "tpm_int.h"
+
+/*
+ * A basic test of a TPM device. We expect a well formatted response header
+ * (error response is fine) within one second.
+ */
+static int tpm_util_test(int fd,
+ unsigned char *request,
+ size_t requestlen,
+ uint16_t *return_tag)
+{
+ struct tpm_resp_hdr *resp;
+ fd_set readfds;
+ int n;
+ struct timeval tv = {
+ .tv_sec = 1,
+ .tv_usec = 0,
+ };
+ unsigned char buf[1024];
+
+ n = write(fd, request, requestlen);
+ if (n < 0) {
+ return errno;
+ }
+ if (n != requestlen) {
+ return EFAULT;
+ }
+
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+
+ /* wait for a second */
+ n = select(fd + 1, &readfds, NULL, NULL, &tv);
+ if (n != 1) {
+ return errno;
+ }
+
+ n = read(fd, &buf, sizeof(buf));
+ if (n < sizeof(struct tpm_resp_hdr)) {
+ return EFAULT;
+ }
+
+ resp = (struct tpm_resp_hdr *)buf;
+ /* check the header */
+ if (be32_to_cpu(resp->len) != n) {
+ return EBADMSG;
+ }
+
+ *return_tag = be16_to_cpu(resp->tag);
+
+ return 0;
+}
+
+/*
+ * Probe for the TPM device in the back
+ * Returns 0 on success with the version of the probed TPM set, 1 on failure.
+ */
+int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version)
+{
+ /*
+ * Sending a TPM1.2 command to a TPM2 should return a TPM1.2
+ * header (tag = 0xc4) and error code (TPM_BADTAG = 0x1e)
+ *
+ * Sending a TPM2 command to a TPM 2 will give a TPM 2 tag in the
+ * header.
+ * Sending a TPM2 command to a TPM 1.2 will give a TPM 1.2 tag
+ * in the header and an error code.
+ */
+ const struct tpm_req_hdr test_req = {
+ .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
+ .len = cpu_to_be32(sizeof(test_req)),
+ .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
+ };
+
+ const struct tpm_req_hdr test_req_tpm2 = {
+ .tag = cpu_to_be16(TPM2_ST_NO_SESSIONS),
+ .len = cpu_to_be32(sizeof(test_req_tpm2)),
+ .ordinal = cpu_to_be32(TPM2_CC_ReadClock),
+ };
+ uint16_t return_tag;
+ int ret;
+
+ /* Send TPM 2 command */
+ ret = tpm_util_test(tpm_fd, (unsigned char *)&test_req_tpm2,
+ sizeof(test_req_tpm2), &return_tag);
+ /* TPM 2 would respond with a tag of TPM2_ST_NO_SESSIONS */
+ if (!ret && return_tag == TPM2_ST_NO_SESSIONS) {
+ *tpm_version = TPM_VERSION_2_0;
+ return 0;
+ }
+
+ /* Send TPM 1.2 command */
+ ret = tpm_util_test(tpm_fd, (unsigned char *)&test_req,
+ sizeof(test_req), &return_tag);
+ if (!ret && return_tag == TPM_TAG_RSP_COMMAND) {
+ *tpm_version = TPM_VERSION_1_2;
+ /* this is a TPM 1.2 */
+ return 0;
+ }
+
+ *tpm_version = TPM_VERSION_UNSPEC;
+
+ return 1;
+}
diff --git a/hw/tpm/tpm_util.h b/hw/tpm/tpm_util.h
new file mode 100644
index 00000000..e7f354a5
--- /dev/null
+++ b/hw/tpm/tpm_util.h
@@ -0,0 +1,28 @@
+/*
+ * TPM utility functions
+ *
+ * Copyright (c) 2010 - 2015 IBM Corporation
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef TPM_TPM_UTILS_H
+#define TPM_TPM_UTILS_H
+
+#include "sysemu/tpm_backend.h"
+
+int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version);
+
+#endif /* TPM_TPM_UTILS_H */
diff --git a/hw/tricore/Makefile.objs b/hw/tricore/Makefile.objs
new file mode 100644
index 00000000..435e095c
--- /dev/null
+++ b/hw/tricore/Makefile.objs
@@ -0,0 +1 @@
+obj-y += tricore_testboard.o
diff --git a/hw/tricore/tricore_testboard.c b/hw/tricore/tricore_testboard.c
new file mode 100644
index 00000000..a059a20a
--- /dev/null
+++ b/hw/tricore/tricore_testboard.c
@@ -0,0 +1,124 @@
+/*
+ * TriCore Baseboard System emulation.
+ *
+ * Copyright (c) 2013-2014 Bastian Koppelmann C-Lab/University Paderborn
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "hw/hw.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "sysemu/block-backend.h"
+#include "exec/address-spaces.h"
+#include "hw/block/flash.h"
+#include "elf.h"
+#include "hw/tricore/tricore.h"
+#include "qemu/error-report.h"
+
+
+/* Board init. */
+
+static struct tricore_boot_info tricoretb_binfo;
+
+static void tricore_load_kernel(CPUTriCoreState *env)
+{
+ uint64_t entry;
+ long kernel_size;
+
+ kernel_size = load_elf(tricoretb_binfo.kernel_filename, NULL,
+ NULL, (uint64_t *)&entry, NULL,
+ NULL, 0,
+ ELF_MACHINE, 1);
+ if (kernel_size <= 0) {
+ error_report("qemu: no kernel file '%s'",
+ tricoretb_binfo.kernel_filename);
+ exit(1);
+ }
+ env->PC = entry;
+
+}
+
+static void tricore_testboard_init(MachineState *machine, int board_id)
+{
+ TriCoreCPU *cpu;
+ CPUTriCoreState *env;
+
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ext_cram = g_new(MemoryRegion, 1);
+ MemoryRegion *ext_dram = g_new(MemoryRegion, 1);
+ MemoryRegion *int_cram = g_new(MemoryRegion, 1);
+ MemoryRegion *int_dram = g_new(MemoryRegion, 1);
+ MemoryRegion *pcp_data = g_new(MemoryRegion, 1);
+ MemoryRegion *pcp_text = g_new(MemoryRegion, 1);
+
+ if (!machine->cpu_model) {
+ machine->cpu_model = "tc1796";
+ }
+ cpu = cpu_tricore_init(machine->cpu_model);
+ if (!cpu) {
+ error_report("Unable to find CPU definition");
+ exit(1);
+ }
+ env = &cpu->env;
+ memory_region_init_ram(ext_cram, NULL, "powerlink_ext_c.ram", 2*1024*1024, &error_abort);
+ vmstate_register_ram_global(ext_cram);
+ memory_region_init_ram(ext_dram, NULL, "powerlink_ext_d.ram", 4*1024*1024, &error_abort);
+ vmstate_register_ram_global(ext_dram);
+ memory_region_init_ram(int_cram, NULL, "powerlink_int_c.ram", 48*1024, &error_abort);
+ vmstate_register_ram_global(int_cram);
+ memory_region_init_ram(int_dram, NULL, "powerlink_int_d.ram", 48*1024, &error_abort);
+ vmstate_register_ram_global(int_dram);
+ memory_region_init_ram(pcp_data, NULL, "powerlink_pcp_data.ram", 16*1024, &error_abort);
+ vmstate_register_ram_global(pcp_data);
+ memory_region_init_ram(pcp_text, NULL, "powerlink_pcp_text.ram", 32*1024, &error_abort);
+ vmstate_register_ram_global(pcp_text);
+
+ memory_region_add_subregion(sysmem, 0x80000000, ext_cram);
+ memory_region_add_subregion(sysmem, 0xa1000000, ext_dram);
+ memory_region_add_subregion(sysmem, 0xd4000000, int_cram);
+ memory_region_add_subregion(sysmem, 0xd0000000, int_dram);
+ memory_region_add_subregion(sysmem, 0xf0050000, pcp_data);
+ memory_region_add_subregion(sysmem, 0xf0060000, pcp_text);
+
+ tricoretb_binfo.ram_size = machine->ram_size;
+ tricoretb_binfo.kernel_filename = machine->kernel_filename;
+
+ if (machine->kernel_filename) {
+ tricore_load_kernel(env);
+ }
+}
+
+static void tricoreboard_init(MachineState *machine)
+{
+ tricore_testboard_init(machine, 0x183);
+}
+
+static QEMUMachine ttb_machine = {
+ .name = "tricore_testboard",
+ .desc = "a minimal TriCore board",
+ .init = tricoreboard_init,
+ .is_default = 0,
+};
+
+static void tricore_testboard_machine_init(void)
+{
+ qemu_register_machine(&ttb_machine);
+}
+
+machine_init(tricore_testboard_machine_init);
diff --git a/hw/unicore32/Makefile.objs b/hw/unicore32/Makefile.objs
new file mode 100644
index 00000000..e0fd6285
--- /dev/null
+++ b/hw/unicore32/Makefile.objs
@@ -0,0 +1,4 @@
+# For UniCore32 machines and boards
+
+# PKUnity-v3 SoC and board information
+obj-${CONFIG_PUV3} += puv3.o
diff --git a/hw/unicore32/puv3.c b/hw/unicore32/puv3.c
new file mode 100644
index 00000000..703e29d6
--- /dev/null
+++ b/hw/unicore32/puv3.c
@@ -0,0 +1,145 @@
+/*
+ * Generic PKUnity SoC machine and board descriptor
+ *
+ * Copyright (C) 2010-2012 Guan Xuetao
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation, or any later version.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu-common.h"
+#include "ui/console.h"
+#include "elf.h"
+#include "exec/address-spaces.h"
+#include "hw/sysbus.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "hw/i386/pc.h"
+#include "sysemu/qtest.h"
+
+#undef DEBUG_PUV3
+#include "hw/unicore32/puv3.h"
+
+#define KERNEL_LOAD_ADDR 0x03000000
+#define KERNEL_MAX_SIZE 0x00800000 /* Just a guess */
+
+static void puv3_intc_cpu_handler(void *opaque, int irq, int level)
+{
+ UniCore32CPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+
+ assert(irq == 0);
+ if (level) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+}
+
+static void puv3_soc_init(CPUUniCore32State *env)
+{
+ qemu_irq cpu_intc, irqs[PUV3_IRQS_NR];
+ DeviceState *dev;
+ MemoryRegion *i8042 = g_new(MemoryRegion, 1);
+ int i;
+
+ /* Initialize interrupt controller */
+ cpu_intc = qemu_allocate_irq(puv3_intc_cpu_handler,
+ uc32_env_get_cpu(env), 0);
+ dev = sysbus_create_simple("puv3_intc", PUV3_INTC_BASE, cpu_intc);
+ for (i = 0; i < PUV3_IRQS_NR; i++) {
+ irqs[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ /* Initialize minimal necessary devices for kernel booting */
+ sysbus_create_simple("puv3_pm", PUV3_PM_BASE, NULL);
+ sysbus_create_simple("puv3_dma", PUV3_DMA_BASE, NULL);
+ sysbus_create_simple("puv3_ost", PUV3_OST_BASE, irqs[PUV3_IRQS_OST0]);
+ sysbus_create_varargs("puv3_gpio", PUV3_GPIO_BASE,
+ irqs[PUV3_IRQS_GPIOLOW0], irqs[PUV3_IRQS_GPIOLOW1],
+ irqs[PUV3_IRQS_GPIOLOW2], irqs[PUV3_IRQS_GPIOLOW3],
+ irqs[PUV3_IRQS_GPIOLOW4], irqs[PUV3_IRQS_GPIOLOW5],
+ irqs[PUV3_IRQS_GPIOLOW6], irqs[PUV3_IRQS_GPIOLOW7],
+ irqs[PUV3_IRQS_GPIOHIGH], NULL);
+
+ /* Keyboard (i8042), mouse disabled for nographic */
+ i8042_mm_init(irqs[PUV3_IRQS_PS2_KBD], NULL, i8042, PUV3_REGS_OFFSET, 4);
+ memory_region_add_subregion(get_system_memory(), PUV3_PS2_BASE, i8042);
+}
+
+static void puv3_board_init(CPUUniCore32State *env, ram_addr_t ram_size)
+{
+ MemoryRegion *ram_memory = g_new(MemoryRegion, 1);
+
+ /* SDRAM at address zero. */
+ memory_region_init_ram(ram_memory, NULL, "puv3.ram", ram_size,
+ &error_abort);
+ vmstate_register_ram_global(ram_memory);
+ memory_region_add_subregion(get_system_memory(), 0, ram_memory);
+}
+
+static const GraphicHwOps no_ops;
+
+static void puv3_load_kernel(const char *kernel_filename)
+{
+ int size;
+
+ if (kernel_filename == NULL && qtest_enabled()) {
+ return;
+ }
+ assert(kernel_filename != NULL);
+
+ /* only zImage format supported */
+ size = load_image_targphys(kernel_filename, KERNEL_LOAD_ADDR,
+ KERNEL_MAX_SIZE);
+ if (size < 0) {
+ hw_error("Load kernel error: '%s'\n", kernel_filename);
+ }
+
+ /* cheat curses that we have a graphic console, only under ocd console */
+ graphic_console_init(NULL, 0, &no_ops, NULL);
+}
+
+static void puv3_init(MachineState *machine)
+{
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *initrd_filename = machine->initrd_filename;
+ CPUUniCore32State *env;
+ UniCore32CPU *cpu;
+
+ if (initrd_filename) {
+ hw_error("Please use kernel built-in initramdisk.\n");
+ }
+
+ if (!cpu_model) {
+ cpu_model = "UniCore-II";
+ }
+
+ cpu = uc32_cpu_init(cpu_model);
+ if (!cpu) {
+ hw_error("Unable to find CPU definition\n");
+ }
+ env = &cpu->env;
+
+ puv3_soc_init(env);
+ puv3_board_init(env, ram_size);
+ puv3_load_kernel(kernel_filename);
+}
+
+static QEMUMachine puv3_machine = {
+ .name = "puv3",
+ .desc = "PKUnity Version-3 based on UniCore32",
+ .init = puv3_init,
+ .is_default = 1,
+};
+
+static void puv3_machine_init(void)
+{
+ qemu_register_machine(&puv3_machine);
+}
+
+machine_init(puv3_machine_init)
diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs
new file mode 100644
index 00000000..7443e386
--- /dev/null
+++ b/hw/usb/Makefile.objs
@@ -0,0 +1,39 @@
+# usb subsystem core
+common-obj-y += core.o combined-packet.o bus.o libhw.o
+common-obj-$(CONFIG_USB) += desc.o desc-msos.o
+
+# usb host adapters
+common-obj-$(CONFIG_USB_UHCI) += hcd-uhci.o
+common-obj-$(CONFIG_USB_OHCI) += hcd-ohci.o
+common-obj-$(CONFIG_USB_EHCI) += hcd-ehci.o hcd-ehci-pci.o
+common-obj-$(CONFIG_USB_EHCI_SYSBUS) += hcd-ehci-sysbus.o
+common-obj-$(CONFIG_USB_XHCI) += hcd-xhci.o
+common-obj-$(CONFIG_USB_MUSB) += hcd-musb.o
+
+# emulated usb devices
+common-obj-$(CONFIG_USB) += dev-hub.o
+common-obj-$(CONFIG_USB) += dev-hid.o
+common-obj-$(CONFIG_USB_TABLET_WACOM) += dev-wacom.o
+common-obj-$(CONFIG_USB_STORAGE_BOT) += dev-storage.o
+common-obj-$(CONFIG_USB_STORAGE_UAS) += dev-uas.o
+common-obj-$(CONFIG_USB_AUDIO) += dev-audio.o
+common-obj-$(CONFIG_USB_SERIAL) += dev-serial.o
+common-obj-$(CONFIG_USB_NETWORK) += dev-network.o
+common-obj-$(CONFIG_USB_BLUETOOTH) += dev-bluetooth.o
+
+ifeq ($(CONFIG_USB_SMARTCARD),y)
+common-obj-y += dev-smartcard-reader.o
+common-obj-y += ccid-card-passthru.o
+common-obj-$(CONFIG_SMARTCARD_NSS) += ccid-card-emulated.o
+ccid-card-emulated.o-cflags := -I$(SRC_PATH)/libcacard
+endif
+
+ifeq ($(CONFIG_POSIX),y)
+common-obj-$(CONFIG_USB_STORAGE_MTP) += dev-mtp.o
+endif
+
+# usb redirection
+common-obj-$(CONFIG_USB_REDIR) += redirect.o quirks.o
+
+# usb pass-through
+common-obj-y += $(patsubst %,host-%.o,$(HOST_USB))
diff --git a/hw/usb/bus.c b/hw/usb/bus.c
new file mode 100644
index 00000000..5f39e1e3
--- /dev/null
+++ b/hw/usb/bus.c
@@ -0,0 +1,758 @@
+#include "hw/hw.h"
+#include "hw/usb.h"
+#include "hw/qdev.h"
+#include "qemu/error-report.h"
+#include "sysemu/sysemu.h"
+#include "monitor/monitor.h"
+#include "trace.h"
+
+static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent);
+
+static char *usb_get_dev_path(DeviceState *dev);
+static char *usb_get_fw_dev_path(DeviceState *qdev);
+static void usb_qdev_unrealize(DeviceState *qdev, Error **errp);
+
+static Property usb_props[] = {
+ DEFINE_PROP_STRING("port", USBDevice, port_path),
+ DEFINE_PROP_STRING("serial", USBDevice, serial),
+ DEFINE_PROP_BIT("full-path", USBDevice, flags,
+ USB_DEV_FLAG_FULL_PATH, true),
+ DEFINE_PROP_BIT("msos-desc", USBDevice, flags,
+ USB_DEV_FLAG_MSOS_DESC_ENABLE, true),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void usb_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ k->print_dev = usb_bus_dev_print;
+ k->get_dev_path = usb_get_dev_path;
+ k->get_fw_dev_path = usb_get_fw_dev_path;
+ hc->unplug = qdev_simple_device_unplug_cb;
+}
+
+static const TypeInfo usb_bus_info = {
+ .name = TYPE_USB_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(USBBus),
+ .class_init = usb_bus_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static int next_usb_bus = 0;
+static QTAILQ_HEAD(, USBBus) busses = QTAILQ_HEAD_INITIALIZER(busses);
+
+static int usb_device_post_load(void *opaque, int version_id)
+{
+ USBDevice *dev = opaque;
+
+ if (dev->state == USB_STATE_NOTATTACHED) {
+ dev->attached = 0;
+ } else {
+ dev->attached = 1;
+ }
+ if (dev->setup_index < 0 ||
+ dev->setup_len < 0 ||
+ dev->setup_index > dev->setup_len ||
+ dev->setup_len > sizeof(dev->data_buf)) {
+ return -EINVAL;
+ }
+ return 0;
+}
+
+const VMStateDescription vmstate_usb_device = {
+ .name = "USBDevice",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = usb_device_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(addr, USBDevice),
+ VMSTATE_INT32(state, USBDevice),
+ VMSTATE_INT32(remote_wakeup, USBDevice),
+ VMSTATE_INT32(setup_state, USBDevice),
+ VMSTATE_INT32(setup_len, USBDevice),
+ VMSTATE_INT32(setup_index, USBDevice),
+ VMSTATE_UINT8_ARRAY(setup_buf, USBDevice, 8),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+void usb_bus_new(USBBus *bus, size_t bus_size,
+ USBBusOps *ops, DeviceState *host)
+{
+ qbus_create_inplace(bus, bus_size, TYPE_USB_BUS, host, NULL);
+ qbus_set_bus_hotplug_handler(BUS(bus), &error_abort);
+ bus->ops = ops;
+ bus->busnr = next_usb_bus++;
+ QTAILQ_INIT(&bus->free);
+ QTAILQ_INIT(&bus->used);
+ QTAILQ_INSERT_TAIL(&busses, bus, next);
+}
+
+void usb_bus_release(USBBus *bus)
+{
+ assert(next_usb_bus > 0);
+
+ QTAILQ_REMOVE(&busses, bus, next);
+}
+
+USBBus *usb_bus_find(int busnr)
+{
+ USBBus *bus;
+
+ if (-1 == busnr)
+ return QTAILQ_FIRST(&busses);
+ QTAILQ_FOREACH(bus, &busses, next) {
+ if (bus->busnr == busnr)
+ return bus;
+ }
+ return NULL;
+}
+
+static void usb_device_realize(USBDevice *dev, Error **errp)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+
+ if (klass->realize) {
+ klass->realize(dev, errp);
+ }
+}
+
+USBDevice *usb_device_find_device(USBDevice *dev, uint8_t addr)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->find_device) {
+ return klass->find_device(dev, addr);
+ }
+ return NULL;
+}
+
+static void usb_device_handle_destroy(USBDevice *dev)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->handle_destroy) {
+ klass->handle_destroy(dev);
+ }
+}
+
+void usb_device_cancel_packet(USBDevice *dev, USBPacket *p)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->cancel_packet) {
+ klass->cancel_packet(dev, p);
+ }
+}
+
+void usb_device_handle_attach(USBDevice *dev)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->handle_attach) {
+ klass->handle_attach(dev);
+ }
+}
+
+void usb_device_handle_reset(USBDevice *dev)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->handle_reset) {
+ klass->handle_reset(dev);
+ }
+}
+
+void usb_device_handle_control(USBDevice *dev, USBPacket *p, int request,
+ int value, int index, int length, uint8_t *data)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->handle_control) {
+ klass->handle_control(dev, p, request, value, index, length, data);
+ }
+}
+
+void usb_device_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->handle_data) {
+ klass->handle_data(dev, p);
+ }
+}
+
+const char *usb_device_get_product_desc(USBDevice *dev)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ return klass->product_desc;
+}
+
+const USBDesc *usb_device_get_usb_desc(USBDevice *dev)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (dev->usb_desc) {
+ return dev->usb_desc;
+ }
+ return klass->usb_desc;
+}
+
+void usb_device_set_interface(USBDevice *dev, int interface,
+ int alt_old, int alt_new)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->set_interface) {
+ klass->set_interface(dev, interface, alt_old, alt_new);
+ }
+}
+
+void usb_device_flush_ep_queue(USBDevice *dev, USBEndpoint *ep)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->flush_ep_queue) {
+ klass->flush_ep_queue(dev, ep);
+ }
+}
+
+void usb_device_ep_stopped(USBDevice *dev, USBEndpoint *ep)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->ep_stopped) {
+ klass->ep_stopped(dev, ep);
+ }
+}
+
+int usb_device_alloc_streams(USBDevice *dev, USBEndpoint **eps, int nr_eps,
+ int streams)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->alloc_streams) {
+ return klass->alloc_streams(dev, eps, nr_eps, streams);
+ }
+ return 0;
+}
+
+void usb_device_free_streams(USBDevice *dev, USBEndpoint **eps, int nr_eps)
+{
+ USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev);
+ if (klass->free_streams) {
+ klass->free_streams(dev, eps, nr_eps);
+ }
+}
+
+static void usb_qdev_realize(DeviceState *qdev, Error **errp)
+{
+ USBDevice *dev = USB_DEVICE(qdev);
+ Error *local_err = NULL;
+
+ pstrcpy(dev->product_desc, sizeof(dev->product_desc),
+ usb_device_get_product_desc(dev));
+ dev->auto_attach = 1;
+ QLIST_INIT(&dev->strings);
+ usb_ep_init(dev);
+
+ usb_claim_port(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ usb_device_realize(dev, &local_err);
+ if (local_err) {
+ usb_release_port(dev);
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (dev->auto_attach) {
+ usb_device_attach(dev, &local_err);
+ if (local_err) {
+ usb_qdev_unrealize(qdev, NULL);
+ error_propagate(errp, local_err);
+ return;
+ }
+ }
+}
+
+static void usb_qdev_unrealize(DeviceState *qdev, Error **errp)
+{
+ USBDevice *dev = USB_DEVICE(qdev);
+
+ if (dev->attached) {
+ usb_device_detach(dev);
+ }
+ usb_device_handle_destroy(dev);
+ if (dev->port) {
+ usb_release_port(dev);
+ }
+}
+
+typedef struct LegacyUSBFactory
+{
+ const char *name;
+ const char *usbdevice_name;
+ USBDevice *(*usbdevice_init)(USBBus *bus, const char *params);
+} LegacyUSBFactory;
+
+static GSList *legacy_usb_factory;
+
+void usb_legacy_register(const char *typename, const char *usbdevice_name,
+ USBDevice *(*usbdevice_init)(USBBus *bus,
+ const char *params))
+{
+ if (usbdevice_name) {
+ LegacyUSBFactory *f = g_malloc0(sizeof(*f));
+ f->name = typename;
+ f->usbdevice_name = usbdevice_name;
+ f->usbdevice_init = usbdevice_init;
+ legacy_usb_factory = g_slist_append(legacy_usb_factory, f);
+ }
+}
+
+USBDevice *usb_create(USBBus *bus, const char *name)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->qbus, name);
+ return USB_DEVICE(dev);
+}
+
+static USBDevice *usb_try_create_simple(USBBus *bus, const char *name,
+ Error **errp)
+{
+ Error *err = NULL;
+ USBDevice *dev;
+
+ dev = USB_DEVICE(qdev_try_create(&bus->qbus, name));
+ if (!dev) {
+ error_setg(errp, "Failed to create USB device '%s'", name);
+ return NULL;
+ }
+ object_property_set_bool(OBJECT(dev), true, "realized", &err);
+ if (err) {
+ error_setg(errp, "Failed to initialize USB device '%s': %s",
+ name, error_get_pretty(err));
+ error_free(err);
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+ return dev;
+}
+
+USBDevice *usb_create_simple(USBBus *bus, const char *name)
+{
+ return usb_try_create_simple(bus, name, &error_abort);
+}
+
+static void usb_fill_port(USBPort *port, void *opaque, int index,
+ USBPortOps *ops, int speedmask)
+{
+ port->opaque = opaque;
+ port->index = index;
+ port->ops = ops;
+ port->speedmask = speedmask;
+ usb_port_location(port, NULL, index + 1);
+}
+
+void usb_register_port(USBBus *bus, USBPort *port, void *opaque, int index,
+ USBPortOps *ops, int speedmask)
+{
+ usb_fill_port(port, opaque, index, ops, speedmask);
+ QTAILQ_INSERT_TAIL(&bus->free, port, next);
+ bus->nfree++;
+}
+
+void usb_register_companion(const char *masterbus, USBPort *ports[],
+ uint32_t portcount, uint32_t firstport,
+ void *opaque, USBPortOps *ops, int speedmask,
+ Error **errp)
+{
+ USBBus *bus;
+ int i;
+
+ QTAILQ_FOREACH(bus, &busses, next) {
+ if (strcmp(bus->qbus.name, masterbus) == 0) {
+ break;
+ }
+ }
+
+ if (!bus) {
+ error_setg(errp, "USB bus '%s' not found", masterbus);
+ return;
+ }
+ if (!bus->ops->register_companion) {
+ error_setg(errp, "Can't use USB bus '%s' as masterbus,"
+ " it doesn't support companion controllers",
+ masterbus);
+ return;
+ }
+
+ for (i = 0; i < portcount; i++) {
+ usb_fill_port(ports[i], opaque, i, ops, speedmask);
+ }
+
+ bus->ops->register_companion(bus, ports, portcount, firstport, errp);
+}
+
+void usb_port_location(USBPort *downstream, USBPort *upstream, int portnr)
+{
+ if (upstream) {
+ snprintf(downstream->path, sizeof(downstream->path), "%s.%d",
+ upstream->path, portnr);
+ downstream->hubcount = upstream->hubcount + 1;
+ } else {
+ snprintf(downstream->path, sizeof(downstream->path), "%d", portnr);
+ downstream->hubcount = 0;
+ }
+}
+
+void usb_unregister_port(USBBus *bus, USBPort *port)
+{
+ if (port->dev) {
+ object_unparent(OBJECT(port->dev));
+ }
+ QTAILQ_REMOVE(&bus->free, port, next);
+ bus->nfree--;
+}
+
+void usb_claim_port(USBDevice *dev, Error **errp)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ USBPort *port;
+
+ assert(dev->port == NULL);
+
+ if (dev->port_path) {
+ QTAILQ_FOREACH(port, &bus->free, next) {
+ if (strcmp(port->path, dev->port_path) == 0) {
+ break;
+ }
+ }
+ if (port == NULL) {
+ error_setg(errp, "usb port %s (bus %s) not found (in use?)",
+ dev->port_path, bus->qbus.name);
+ return;
+ }
+ } else {
+ if (bus->nfree == 1 && strcmp(object_get_typename(OBJECT(dev)), "usb-hub") != 0) {
+ /* Create a new hub and chain it on */
+ usb_try_create_simple(bus, "usb-hub", NULL);
+ }
+ if (bus->nfree == 0) {
+ error_setg(errp, "tried to attach usb device %s to a bus "
+ "with no free ports", dev->product_desc);
+ return;
+ }
+ port = QTAILQ_FIRST(&bus->free);
+ }
+ trace_usb_port_claim(bus->busnr, port->path);
+
+ QTAILQ_REMOVE(&bus->free, port, next);
+ bus->nfree--;
+
+ dev->port = port;
+ port->dev = dev;
+
+ QTAILQ_INSERT_TAIL(&bus->used, port, next);
+ bus->nused++;
+}
+
+void usb_release_port(USBDevice *dev)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ USBPort *port = dev->port;
+
+ assert(port != NULL);
+ trace_usb_port_release(bus->busnr, port->path);
+
+ QTAILQ_REMOVE(&bus->used, port, next);
+ bus->nused--;
+
+ dev->port = NULL;
+ port->dev = NULL;
+
+ QTAILQ_INSERT_TAIL(&bus->free, port, next);
+ bus->nfree++;
+}
+
+static void usb_mask_to_str(char *dest, size_t size,
+ unsigned int speedmask)
+{
+ static const struct {
+ unsigned int mask;
+ const char *name;
+ } speeds[] = {
+ { .mask = USB_SPEED_MASK_FULL, .name = "full" },
+ { .mask = USB_SPEED_MASK_HIGH, .name = "high" },
+ { .mask = USB_SPEED_MASK_SUPER, .name = "super" },
+ };
+ int i, pos = 0;
+
+ for (i = 0; i < ARRAY_SIZE(speeds); i++) {
+ if (speeds[i].mask & speedmask) {
+ pos += snprintf(dest + pos, size - pos, "%s%s",
+ pos ? "+" : "",
+ speeds[i].name);
+ }
+ }
+}
+
+void usb_check_attach(USBDevice *dev, Error **errp)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ USBPort *port = dev->port;
+ char devspeed[32], portspeed[32];
+
+ assert(port != NULL);
+ assert(!dev->attached);
+ usb_mask_to_str(devspeed, sizeof(devspeed), dev->speedmask);
+ usb_mask_to_str(portspeed, sizeof(portspeed), port->speedmask);
+ trace_usb_port_attach(bus->busnr, port->path,
+ devspeed, portspeed);
+
+ if (!(port->speedmask & dev->speedmask)) {
+ error_setg(errp, "Warning: speed mismatch trying to attach"
+ " usb device \"%s\" (%s speed)"
+ " to bus \"%s\", port \"%s\" (%s speed)",
+ dev->product_desc, devspeed,
+ bus->qbus.name, port->path, portspeed);
+ return;
+ }
+}
+
+void usb_device_attach(USBDevice *dev, Error **errp)
+{
+ USBPort *port = dev->port;
+ Error *local_err = NULL;
+
+ usb_check_attach(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ dev->attached++;
+ usb_attach(port);
+}
+
+int usb_device_detach(USBDevice *dev)
+{
+ USBBus *bus = usb_bus_from_device(dev);
+ USBPort *port = dev->port;
+
+ assert(port != NULL);
+ assert(dev->attached);
+ trace_usb_port_detach(bus->busnr, port->path);
+
+ usb_detach(port);
+ dev->attached--;
+ return 0;
+}
+
+int usb_device_delete_addr(int busnr, int addr)
+{
+ USBBus *bus;
+ USBPort *port;
+ USBDevice *dev;
+
+ bus = usb_bus_find(busnr);
+ if (!bus)
+ return -1;
+
+ QTAILQ_FOREACH(port, &bus->used, next) {
+ if (port->dev->addr == addr)
+ break;
+ }
+ if (!port)
+ return -1;
+ dev = port->dev;
+
+ object_unparent(OBJECT(dev));
+ return 0;
+}
+
+static const char *usb_speed(unsigned int speed)
+{
+ static const char *txt[] = {
+ [ USB_SPEED_LOW ] = "1.5",
+ [ USB_SPEED_FULL ] = "12",
+ [ USB_SPEED_HIGH ] = "480",
+ [ USB_SPEED_SUPER ] = "5000",
+ };
+ if (speed >= ARRAY_SIZE(txt))
+ return "?";
+ return txt[speed];
+}
+
+static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent)
+{
+ USBDevice *dev = USB_DEVICE(qdev);
+ USBBus *bus = usb_bus_from_device(dev);
+
+ monitor_printf(mon, "%*saddr %d.%d, port %s, speed %s, name %s%s\n",
+ indent, "", bus->busnr, dev->addr,
+ dev->port ? dev->port->path : "-",
+ usb_speed(dev->speed), dev->product_desc,
+ dev->attached ? ", attached" : "");
+}
+
+static char *usb_get_dev_path(DeviceState *qdev)
+{
+ USBDevice *dev = USB_DEVICE(qdev);
+ DeviceState *hcd = qdev->parent_bus->parent;
+ char *id = NULL;
+
+ if (dev->flags & (1 << USB_DEV_FLAG_FULL_PATH)) {
+ id = qdev_get_dev_path(hcd);
+ }
+ if (id) {
+ char *ret = g_strdup_printf("%s/%s", id, dev->port->path);
+ g_free(id);
+ return ret;
+ } else {
+ return g_strdup(dev->port->path);
+ }
+}
+
+static char *usb_get_fw_dev_path(DeviceState *qdev)
+{
+ USBDevice *dev = USB_DEVICE(qdev);
+ char *fw_path, *in;
+ ssize_t pos = 0, fw_len;
+ long nr;
+
+ fw_len = 32 + strlen(dev->port->path) * 6;
+ fw_path = g_malloc(fw_len);
+ in = dev->port->path;
+ while (fw_len - pos > 0) {
+ nr = strtol(in, &in, 10);
+ if (in[0] == '.') {
+ /* some hub between root port and device */
+ pos += snprintf(fw_path + pos, fw_len - pos, "hub@%lx/", nr);
+ in++;
+ } else {
+ /* the device itself */
+ pos += snprintf(fw_path + pos, fw_len - pos, "%s@%lx",
+ qdev_fw_name(qdev), nr);
+ break;
+ }
+ }
+ return fw_path;
+}
+
+void hmp_info_usb(Monitor *mon, const QDict *qdict)
+{
+ USBBus *bus;
+ USBDevice *dev;
+ USBPort *port;
+
+ if (QTAILQ_EMPTY(&busses)) {
+ monitor_printf(mon, "USB support not enabled\n");
+ return;
+ }
+
+ QTAILQ_FOREACH(bus, &busses, next) {
+ QTAILQ_FOREACH(port, &bus->used, next) {
+ dev = port->dev;
+ if (!dev)
+ continue;
+ monitor_printf(mon, " Device %d.%d, Port %s, Speed %s Mb/s, Product %s\n",
+ bus->busnr, dev->addr, port->path, usb_speed(dev->speed),
+ dev->product_desc);
+ }
+ }
+}
+
+/* handle legacy -usbdevice cmd line option */
+USBDevice *usbdevice_create(const char *cmdline)
+{
+ USBBus *bus = usb_bus_find(-1 /* any */);
+ LegacyUSBFactory *f = NULL;
+ Error *err = NULL;
+ GSList *i;
+ char driver[32];
+ const char *params;
+ int len;
+ USBDevice *dev;
+
+ params = strchr(cmdline,':');
+ if (params) {
+ params++;
+ len = params - cmdline;
+ if (len > sizeof(driver))
+ len = sizeof(driver);
+ pstrcpy(driver, len, cmdline);
+ } else {
+ params = "";
+ pstrcpy(driver, sizeof(driver), cmdline);
+ }
+
+ for (i = legacy_usb_factory; i; i = i->next) {
+ f = i->data;
+ if (strcmp(f->usbdevice_name, driver) == 0) {
+ break;
+ }
+ }
+ if (i == NULL) {
+#if 0
+ /* no error because some drivers are not converted (yet) */
+ error_report("usbdevice %s not found", driver);
+#endif
+ return NULL;
+ }
+
+ if (!bus) {
+ error_report("Error: no usb bus to attach usbdevice %s, "
+ "please try -machine usb=on and check that "
+ "the machine model supports USB", driver);
+ return NULL;
+ }
+
+ if (f->usbdevice_init) {
+ dev = f->usbdevice_init(bus, params);
+ } else {
+ if (*params) {
+ error_report("usbdevice %s accepts no params", driver);
+ return NULL;
+ }
+ dev = usb_create(bus, f->name);
+ }
+ if (!dev) {
+ error_report("Failed to create USB device '%s'", f->name);
+ return NULL;
+ }
+ object_property_set_bool(OBJECT(dev), true, "realized", &err);
+ if (err) {
+ error_report("Failed to initialize USB device '%s': %s",
+ f->name, error_get_pretty(err));
+ error_free(err);
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+ return dev;
+}
+
+static void usb_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->bus_type = TYPE_USB_BUS;
+ k->realize = usb_qdev_realize;
+ k->unrealize = usb_qdev_unrealize;
+ k->props = usb_props;
+}
+
+static const TypeInfo usb_device_type_info = {
+ .name = TYPE_USB_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(USBDevice),
+ .abstract = true,
+ .class_size = sizeof(USBDeviceClass),
+ .class_init = usb_device_class_init,
+};
+
+static void usb_register_types(void)
+{
+ type_register_static(&usb_bus_info);
+ type_register_static(&usb_device_type_info);
+}
+
+type_init(usb_register_types)
diff --git a/hw/usb/ccid-card-emulated.c b/hw/usb/ccid-card-emulated.c
new file mode 100644
index 00000000..72329ed7
--- /dev/null
+++ b/hw/usb/ccid-card-emulated.c
@@ -0,0 +1,602 @@
+/*
+ * CCID Card Device. Emulated card.
+ *
+ * Copyright (c) 2011 Red Hat.
+ * Written by Alon Levy.
+ *
+ * This code is licensed under the GNU LGPL, version 2 or later.
+ */
+
+/*
+ * It can be used to provide access to the local hardware in a non exclusive
+ * way, or it can use certificates. It requires the usb-ccid bus.
+ *
+ * Usage 1: standard, mirror hardware reader+card:
+ * qemu .. -usb -device usb-ccid -device ccid-card-emulated
+ *
+ * Usage 2: use certificates, no hardware required
+ * one time: create the certificates:
+ * for i in 1 2 3; do
+ * certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i
+ * done
+ * qemu .. -usb -device usb-ccid \
+ * -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3
+ *
+ * If you use a non default db for the certificates you can specify it using
+ * the db parameter.
+ */
+
+#include <eventt.h>
+#include <vevent.h>
+#include <vreader.h>
+#include <vcard_emul.h>
+
+#include "qemu/thread.h"
+#include "sysemu/char.h"
+#include "ccid.h"
+
+#define DPRINTF(card, lvl, fmt, ...) \
+do {\
+ if (lvl <= card->debug) {\
+ printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__);\
+ } \
+} while (0)
+
+#define EMULATED_DEV_NAME "ccid-card-emulated"
+
+#define BACKEND_NSS_EMULATED_NAME "nss-emulated"
+#define BACKEND_CERTIFICATES_NAME "certificates"
+
+enum {
+ BACKEND_NSS_EMULATED = 1,
+ BACKEND_CERTIFICATES
+};
+
+#define DEFAULT_BACKEND BACKEND_NSS_EMULATED
+
+typedef struct EmulatedState EmulatedState;
+
+enum {
+ EMUL_READER_INSERT = 0,
+ EMUL_READER_REMOVE,
+ EMUL_CARD_INSERT,
+ EMUL_CARD_REMOVE,
+ EMUL_GUEST_APDU,
+ EMUL_RESPONSE_APDU,
+ EMUL_ERROR,
+};
+
+static const char *emul_event_to_string(uint32_t emul_event)
+{
+ switch (emul_event) {
+ case EMUL_READER_INSERT:
+ return "EMUL_READER_INSERT";
+ case EMUL_READER_REMOVE:
+ return "EMUL_READER_REMOVE";
+ case EMUL_CARD_INSERT:
+ return "EMUL_CARD_INSERT";
+ case EMUL_CARD_REMOVE:
+ return "EMUL_CARD_REMOVE";
+ case EMUL_GUEST_APDU:
+ return "EMUL_GUEST_APDU";
+ case EMUL_RESPONSE_APDU:
+ return "EMUL_RESPONSE_APDU";
+ case EMUL_ERROR:
+ return "EMUL_ERROR";
+ }
+ return "UNKNOWN";
+}
+
+typedef struct EmulEvent {
+ QSIMPLEQ_ENTRY(EmulEvent) entry;
+ union {
+ struct {
+ uint32_t type;
+ } gen;
+ struct {
+ uint32_t type;
+ uint64_t code;
+ } error;
+ struct {
+ uint32_t type;
+ uint32_t len;
+ uint8_t data[];
+ } data;
+ } p;
+} EmulEvent;
+
+#define MAX_ATR_SIZE 40
+struct EmulatedState {
+ CCIDCardState base;
+ uint8_t debug;
+ char *backend_str;
+ uint32_t backend;
+ char *cert1;
+ char *cert2;
+ char *cert3;
+ char *db;
+ uint8_t atr[MAX_ATR_SIZE];
+ uint8_t atr_length;
+ QSIMPLEQ_HEAD(event_list, EmulEvent) event_list;
+ QemuMutex event_list_mutex;
+ QemuThread event_thread_id;
+ VReader *reader;
+ QSIMPLEQ_HEAD(guest_apdu_list, EmulEvent) guest_apdu_list;
+ QemuMutex vreader_mutex; /* and guest_apdu_list mutex */
+ QemuMutex handle_apdu_mutex;
+ QemuCond handle_apdu_cond;
+ EventNotifier notifier;
+ int quit_apdu_thread;
+ QemuThread apdu_thread_id;
+};
+
+static void emulated_apdu_from_guest(CCIDCardState *base,
+ const uint8_t *apdu, uint32_t len)
+{
+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base);
+ EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len);
+
+ assert(event);
+ event->p.data.type = EMUL_GUEST_APDU;
+ event->p.data.len = len;
+ memcpy(event->p.data.data, apdu, len);
+ qemu_mutex_lock(&card->vreader_mutex);
+ QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry);
+ qemu_mutex_unlock(&card->vreader_mutex);
+ qemu_mutex_lock(&card->handle_apdu_mutex);
+ qemu_cond_signal(&card->handle_apdu_cond);
+ qemu_mutex_unlock(&card->handle_apdu_mutex);
+}
+
+static const uint8_t *emulated_get_atr(CCIDCardState *base, uint32_t *len)
+{
+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base);
+
+ *len = card->atr_length;
+ return card->atr;
+}
+
+static void emulated_push_event(EmulatedState *card, EmulEvent *event)
+{
+ qemu_mutex_lock(&card->event_list_mutex);
+ QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry);
+ qemu_mutex_unlock(&card->event_list_mutex);
+ event_notifier_set(&card->notifier);
+}
+
+static void emulated_push_type(EmulatedState *card, uint32_t type)
+{
+ EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent));
+
+ assert(event);
+ event->p.gen.type = type;
+ emulated_push_event(card, event);
+}
+
+static void emulated_push_error(EmulatedState *card, uint64_t code)
+{
+ EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent));
+
+ assert(event);
+ event->p.error.type = EMUL_ERROR;
+ event->p.error.code = code;
+ emulated_push_event(card, event);
+}
+
+static void emulated_push_data_type(EmulatedState *card, uint32_t type,
+ const uint8_t *data, uint32_t len)
+{
+ EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len);
+
+ assert(event);
+ event->p.data.type = type;
+ event->p.data.len = len;
+ memcpy(event->p.data.data, data, len);
+ emulated_push_event(card, event);
+}
+
+static void emulated_push_reader_insert(EmulatedState *card)
+{
+ emulated_push_type(card, EMUL_READER_INSERT);
+}
+
+static void emulated_push_reader_remove(EmulatedState *card)
+{
+ emulated_push_type(card, EMUL_READER_REMOVE);
+}
+
+static void emulated_push_card_insert(EmulatedState *card,
+ const uint8_t *atr, uint32_t len)
+{
+ emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len);
+}
+
+static void emulated_push_card_remove(EmulatedState *card)
+{
+ emulated_push_type(card, EMUL_CARD_REMOVE);
+}
+
+static void emulated_push_response_apdu(EmulatedState *card,
+ const uint8_t *apdu, uint32_t len)
+{
+ emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len);
+}
+
+#define APDU_BUF_SIZE 270
+static void *handle_apdu_thread(void* arg)
+{
+ EmulatedState *card = arg;
+ uint8_t recv_data[APDU_BUF_SIZE];
+ int recv_len;
+ VReaderStatus reader_status;
+ EmulEvent *event;
+
+ while (1) {
+ qemu_mutex_lock(&card->handle_apdu_mutex);
+ qemu_cond_wait(&card->handle_apdu_cond, &card->handle_apdu_mutex);
+ qemu_mutex_unlock(&card->handle_apdu_mutex);
+ if (card->quit_apdu_thread) {
+ card->quit_apdu_thread = 0; /* debugging */
+ break;
+ }
+ qemu_mutex_lock(&card->vreader_mutex);
+ while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) {
+ event = QSIMPLEQ_FIRST(&card->guest_apdu_list);
+ assert((unsigned long)event > 1000);
+ QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry);
+ if (event->p.data.type != EMUL_GUEST_APDU) {
+ DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n");
+ g_free(event);
+ continue;
+ }
+ if (card->reader == NULL) {
+ DPRINTF(card, 1, "reader is NULL\n");
+ g_free(event);
+ continue;
+ }
+ recv_len = sizeof(recv_data);
+ reader_status = vreader_xfr_bytes(card->reader,
+ event->p.data.data, event->p.data.len,
+ recv_data, &recv_len);
+ DPRINTF(card, 2, "got back apdu of length %d\n", recv_len);
+ if (reader_status == VREADER_OK) {
+ emulated_push_response_apdu(card, recv_data, recv_len);
+ } else {
+ emulated_push_error(card, reader_status);
+ }
+ g_free(event);
+ }
+ qemu_mutex_unlock(&card->vreader_mutex);
+ }
+ return NULL;
+}
+
+static void *event_thread(void *arg)
+{
+ int atr_len = MAX_ATR_SIZE;
+ uint8_t atr[MAX_ATR_SIZE];
+ VEvent *event = NULL;
+ EmulatedState *card = arg;
+
+ while (1) {
+ const char *reader_name;
+
+ event = vevent_wait_next_vevent();
+ if (event == NULL || event->type == VEVENT_LAST) {
+ break;
+ }
+ if (event->type != VEVENT_READER_INSERT) {
+ if (card->reader == NULL && event->reader != NULL) {
+ /* Happens after device_add followed by card remove or insert.
+ * XXX: create synthetic add_reader events if vcard_emul_init
+ * already called, which happens if device_del and device_add
+ * are called */
+ card->reader = vreader_reference(event->reader);
+ } else {
+ if (event->reader != card->reader) {
+ fprintf(stderr,
+ "ERROR: wrong reader: quiting event_thread\n");
+ break;
+ }
+ }
+ }
+ switch (event->type) {
+ case VEVENT_READER_INSERT:
+ /* TODO: take a specific reader. i.e. track which reader
+ * we are seeing here, check it is the one we want (the first,
+ * or by a particular name), and ignore if we don't want it.
+ */
+ reader_name = vreader_get_name(event->reader);
+ if (card->reader != NULL) {
+ DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n",
+ vreader_get_name(card->reader), reader_name);
+ qemu_mutex_lock(&card->vreader_mutex);
+ vreader_free(card->reader);
+ qemu_mutex_unlock(&card->vreader_mutex);
+ emulated_push_reader_remove(card);
+ }
+ qemu_mutex_lock(&card->vreader_mutex);
+ DPRINTF(card, 2, "READER INSERT %s\n", reader_name);
+ card->reader = vreader_reference(event->reader);
+ qemu_mutex_unlock(&card->vreader_mutex);
+ emulated_push_reader_insert(card);
+ break;
+ case VEVENT_READER_REMOVE:
+ DPRINTF(card, 2, " READER REMOVE: %s\n",
+ vreader_get_name(event->reader));
+ qemu_mutex_lock(&card->vreader_mutex);
+ vreader_free(card->reader);
+ card->reader = NULL;
+ qemu_mutex_unlock(&card->vreader_mutex);
+ emulated_push_reader_remove(card);
+ break;
+ case VEVENT_CARD_INSERT:
+ /* get the ATR (intended as a response to a power on from the
+ * reader */
+ atr_len = MAX_ATR_SIZE;
+ vreader_power_on(event->reader, atr, &atr_len);
+ card->atr_length = (uint8_t)atr_len;
+ DPRINTF(card, 2, " CARD INSERT\n");
+ emulated_push_card_insert(card, atr, atr_len);
+ break;
+ case VEVENT_CARD_REMOVE:
+ DPRINTF(card, 2, " CARD REMOVE\n");
+ emulated_push_card_remove(card);
+ break;
+ case VEVENT_LAST: /* quit */
+ vevent_delete(event);
+ return NULL;
+ break;
+ default:
+ break;
+ }
+ vevent_delete(event);
+ }
+ return NULL;
+}
+
+static void card_event_handler(EventNotifier *notifier)
+{
+ EmulatedState *card = container_of(notifier, EmulatedState, notifier);
+ EmulEvent *event, *next;
+
+ event_notifier_test_and_clear(&card->notifier);
+ qemu_mutex_lock(&card->event_list_mutex);
+ QSIMPLEQ_FOREACH_SAFE(event, &card->event_list, entry, next) {
+ DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type));
+ switch (event->p.gen.type) {
+ case EMUL_RESPONSE_APDU:
+ ccid_card_send_apdu_to_guest(&card->base, event->p.data.data,
+ event->p.data.len);
+ break;
+ case EMUL_READER_INSERT:
+ ccid_card_ccid_attach(&card->base);
+ break;
+ case EMUL_READER_REMOVE:
+ ccid_card_ccid_detach(&card->base);
+ break;
+ case EMUL_CARD_INSERT:
+ assert(event->p.data.len <= MAX_ATR_SIZE);
+ card->atr_length = event->p.data.len;
+ memcpy(card->atr, event->p.data.data, card->atr_length);
+ ccid_card_card_inserted(&card->base);
+ break;
+ case EMUL_CARD_REMOVE:
+ ccid_card_card_removed(&card->base);
+ break;
+ case EMUL_ERROR:
+ ccid_card_card_error(&card->base, event->p.error.code);
+ break;
+ default:
+ DPRINTF(card, 2, "unexpected event\n");
+ break;
+ }
+ g_free(event);
+ }
+ QSIMPLEQ_INIT(&card->event_list);
+ qemu_mutex_unlock(&card->event_list_mutex);
+}
+
+static int init_event_notifier(EmulatedState *card)
+{
+ if (event_notifier_init(&card->notifier, false) < 0) {
+ DPRINTF(card, 2, "event notifier creation failed\n");
+ return -1;
+ }
+ event_notifier_set_handler(&card->notifier, card_event_handler);
+ return 0;
+}
+
+#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb"
+#define CERTIFICATES_ARGS_TEMPLATE\
+ "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)"
+
+static int wrap_vcard_emul_init(VCardEmulOptions *options)
+{
+ static int called;
+ static int options_was_null;
+
+ if (called) {
+ if ((options == NULL) != options_was_null) {
+ printf("%s: warning: running emulated with certificates"
+ " and emulated side by side is not supported\n",
+ __func__);
+ return VCARD_EMUL_FAIL;
+ }
+ vcard_emul_replay_insertion_events();
+ return VCARD_EMUL_OK;
+ }
+ options_was_null = (options == NULL);
+ called = 1;
+ return vcard_emul_init(options);
+}
+
+static int emulated_initialize_vcard_from_certificates(EmulatedState *card)
+{
+ char emul_args[200];
+ VCardEmulOptions *options = NULL;
+
+ snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE,
+ card->db ? card->db : CERTIFICATES_DEFAULT_DB,
+ card->cert1, card->cert2, card->cert3);
+ options = vcard_emul_options(emul_args);
+ if (options == NULL) {
+ printf("%s: warning: not using certificates due to"
+ " initialization error\n", __func__);
+ }
+ return wrap_vcard_emul_init(options);
+}
+
+typedef struct EnumTable {
+ const char *name;
+ uint32_t value;
+} EnumTable;
+
+static const EnumTable backend_enum_table[] = {
+ {BACKEND_NSS_EMULATED_NAME, BACKEND_NSS_EMULATED},
+ {BACKEND_CERTIFICATES_NAME, BACKEND_CERTIFICATES},
+ {NULL, 0},
+};
+
+static uint32_t parse_enumeration(char *str,
+ const EnumTable *table, uint32_t not_found_value)
+{
+ uint32_t ret = not_found_value;
+
+ if (str == NULL)
+ return 0;
+
+ while (table->name != NULL) {
+ if (strcmp(table->name, str) == 0) {
+ ret = table->value;
+ break;
+ }
+ table++;
+ }
+ return ret;
+}
+
+static int emulated_initfn(CCIDCardState *base)
+{
+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base);
+ VCardEmulError ret;
+ const EnumTable *ptable;
+
+ QSIMPLEQ_INIT(&card->event_list);
+ QSIMPLEQ_INIT(&card->guest_apdu_list);
+ qemu_mutex_init(&card->event_list_mutex);
+ qemu_mutex_init(&card->vreader_mutex);
+ qemu_mutex_init(&card->handle_apdu_mutex);
+ qemu_cond_init(&card->handle_apdu_cond);
+ card->reader = NULL;
+ card->quit_apdu_thread = 0;
+ if (init_event_notifier(card) < 0) {
+ return -1;
+ }
+
+ card->backend = 0;
+ if (card->backend_str) {
+ card->backend = parse_enumeration(card->backend_str,
+ backend_enum_table, 0);
+ }
+
+ if (card->backend == 0) {
+ printf("backend must be one of:\n");
+ for (ptable = backend_enum_table; ptable->name != NULL; ++ptable) {
+ printf("%s\n", ptable->name);
+ }
+ return -1;
+ }
+
+ /* TODO: a passthru backened that works on local machine. third card type?*/
+ if (card->backend == BACKEND_CERTIFICATES) {
+ if (card->cert1 != NULL && card->cert2 != NULL && card->cert3 != NULL) {
+ ret = emulated_initialize_vcard_from_certificates(card);
+ } else {
+ printf("%s: you must provide all three certs for"
+ " certificates backend\n", EMULATED_DEV_NAME);
+ return -1;
+ }
+ } else {
+ if (card->backend != BACKEND_NSS_EMULATED) {
+ printf("%s: bad backend specified. The options are:\n%s (default),"
+ " %s.\n", EMULATED_DEV_NAME, BACKEND_NSS_EMULATED_NAME,
+ BACKEND_CERTIFICATES_NAME);
+ return -1;
+ }
+ if (card->cert1 != NULL || card->cert2 != NULL || card->cert3 != NULL) {
+ printf("%s: unexpected cert parameters to nss emulated backend\n",
+ EMULATED_DEV_NAME);
+ return -1;
+ }
+ /* default to mirroring the local hardware readers */
+ ret = wrap_vcard_emul_init(NULL);
+ }
+ if (ret != VCARD_EMUL_OK) {
+ printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME);
+ return -1;
+ }
+ qemu_thread_create(&card->event_thread_id, "ccid/event", event_thread,
+ card, QEMU_THREAD_JOINABLE);
+ qemu_thread_create(&card->apdu_thread_id, "ccid/apdu", handle_apdu_thread,
+ card, QEMU_THREAD_JOINABLE);
+ return 0;
+}
+
+static int emulated_exitfn(CCIDCardState *base)
+{
+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base);
+ VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL);
+
+ vevent_queue_vevent(vevent); /* stop vevent thread */
+ qemu_thread_join(&card->event_thread_id);
+
+ card->quit_apdu_thread = 1; /* stop handle_apdu thread */
+ qemu_cond_signal(&card->handle_apdu_cond);
+ qemu_thread_join(&card->apdu_thread_id);
+
+ /* threads exited, can destroy all condvars/mutexes */
+ qemu_cond_destroy(&card->handle_apdu_cond);
+ qemu_mutex_destroy(&card->handle_apdu_mutex);
+ qemu_mutex_destroy(&card->vreader_mutex);
+ qemu_mutex_destroy(&card->event_list_mutex);
+ return 0;
+}
+
+static Property emulated_card_properties[] = {
+ DEFINE_PROP_STRING("backend", EmulatedState, backend_str),
+ DEFINE_PROP_STRING("cert1", EmulatedState, cert1),
+ DEFINE_PROP_STRING("cert2", EmulatedState, cert2),
+ DEFINE_PROP_STRING("cert3", EmulatedState, cert3),
+ DEFINE_PROP_STRING("db", EmulatedState, db),
+ DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void emulated_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ CCIDCardClass *cc = CCID_CARD_CLASS(klass);
+
+ cc->initfn = emulated_initfn;
+ cc->exitfn = emulated_exitfn;
+ cc->get_atr = emulated_get_atr;
+ cc->apdu_from_guest = emulated_apdu_from_guest;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "emulated smartcard";
+ dc->props = emulated_card_properties;
+}
+
+static const TypeInfo emulated_card_info = {
+ .name = EMULATED_DEV_NAME,
+ .parent = TYPE_CCID_CARD,
+ .instance_size = sizeof(EmulatedState),
+ .class_init = emulated_class_initfn,
+};
+
+static void ccid_card_emulated_register_types(void)
+{
+ type_register_static(&emulated_card_info);
+}
+
+type_init(ccid_card_emulated_register_types)
diff --git a/hw/usb/ccid-card-passthru.c b/hw/usb/ccid-card-passthru.c
new file mode 100644
index 00000000..85a4fc3e
--- /dev/null
+++ b/hw/usb/ccid-card-passthru.c
@@ -0,0 +1,413 @@
+/*
+ * CCID Passthru Card Device emulation
+ *
+ * Copyright (c) 2011 Red Hat.
+ * Written by Alon Levy.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.1 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "sysemu/char.h"
+#include "qemu/error-report.h"
+#include "qemu/sockets.h"
+#include "ccid.h"
+#include "libcacard/vscard_common.h"
+
+#define DPRINTF(card, lvl, fmt, ...) \
+do { \
+ if (lvl <= card->debug) { \
+ printf("ccid-card-passthru: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0)
+
+#define D_WARN 1
+#define D_INFO 2
+#define D_MORE_INFO 3
+#define D_VERBOSE 4
+
+/* TODO: do we still need this? */
+static const uint8_t DEFAULT_ATR[] = {
+/*
+ * From some example somewhere
+ * 0x3B, 0xB0, 0x18, 0x00, 0xD1, 0x81, 0x05, 0xB1, 0x40, 0x38, 0x1F, 0x03, 0x28
+ */
+
+/* From an Athena smart card */
+ 0x3B, 0xD5, 0x18, 0xFF, 0x80, 0x91, 0xFE, 0x1F, 0xC3, 0x80, 0x73, 0xC8, 0x21,
+ 0x13, 0x08
+};
+
+
+#define PASSTHRU_DEV_NAME "ccid-card-passthru"
+#define VSCARD_IN_SIZE 65536
+
+/* maximum size of ATR - from 7816-3 */
+#define MAX_ATR_SIZE 40
+
+typedef struct PassthruState PassthruState;
+
+struct PassthruState {
+ CCIDCardState base;
+ CharDriverState *cs;
+ uint8_t vscard_in_data[VSCARD_IN_SIZE];
+ uint32_t vscard_in_pos;
+ uint32_t vscard_in_hdr;
+ uint8_t atr[MAX_ATR_SIZE];
+ uint8_t atr_length;
+ uint8_t debug;
+};
+
+/*
+ * VSCard protocol over chardev
+ * This code should not depend on the card type.
+ */
+
+static void ccid_card_vscard_send_msg(PassthruState *s,
+ VSCMsgType type, uint32_t reader_id,
+ const uint8_t *payload, uint32_t length)
+{
+ VSCMsgHeader scr_msg_header;
+
+ scr_msg_header.type = htonl(type);
+ scr_msg_header.reader_id = htonl(reader_id);
+ scr_msg_header.length = htonl(length);
+ qemu_chr_fe_write(s->cs, (uint8_t *)&scr_msg_header, sizeof(VSCMsgHeader));
+ qemu_chr_fe_write(s->cs, payload, length);
+}
+
+static void ccid_card_vscard_send_apdu(PassthruState *s,
+ const uint8_t *apdu, uint32_t length)
+{
+ ccid_card_vscard_send_msg(
+ s, VSC_APDU, VSCARD_MINIMAL_READER_ID, apdu, length);
+}
+
+static void ccid_card_vscard_send_error(PassthruState *s,
+ uint32_t reader_id, VSCErrorCode code)
+{
+ VSCMsgError msg = {.code = htonl(code)};
+
+ ccid_card_vscard_send_msg(
+ s, VSC_Error, reader_id, (uint8_t *)&msg, sizeof(msg));
+}
+
+static void ccid_card_vscard_send_init(PassthruState *s)
+{
+ VSCMsgInit msg = {
+ .version = htonl(VSCARD_VERSION),
+ .magic = VSCARD_MAGIC,
+ .capabilities = {0}
+ };
+
+ ccid_card_vscard_send_msg(s, VSC_Init, VSCARD_UNDEFINED_READER_ID,
+ (uint8_t *)&msg, sizeof(msg));
+}
+
+static int ccid_card_vscard_can_read(void *opaque)
+{
+ PassthruState *card = opaque;
+
+ return VSCARD_IN_SIZE >= card->vscard_in_pos ?
+ VSCARD_IN_SIZE - card->vscard_in_pos : 0;
+}
+
+static void ccid_card_vscard_handle_init(
+ PassthruState *card, VSCMsgHeader *hdr, VSCMsgInit *init)
+{
+ uint32_t *capabilities;
+ int num_capabilities;
+ int i;
+
+ capabilities = init->capabilities;
+ num_capabilities =
+ 1 + ((hdr->length - sizeof(VSCMsgInit)) / sizeof(uint32_t));
+ init->version = ntohl(init->version);
+ for (i = 0 ; i < num_capabilities; ++i) {
+ capabilities[i] = ntohl(capabilities[i]);
+ }
+ if (init->magic != VSCARD_MAGIC) {
+ error_report("wrong magic");
+ /* we can't disconnect the chardev */
+ }
+ if (init->version != VSCARD_VERSION) {
+ DPRINTF(card, D_WARN,
+ "got version %d, have %d", init->version, VSCARD_VERSION);
+ }
+ /* future handling of capabilities, none exist atm */
+ ccid_card_vscard_send_init(card);
+}
+
+static int check_atr(PassthruState *card, uint8_t *data, int len)
+{
+ int historical_length, opt_bytes;
+ int td_count = 0;
+ int td;
+
+ if (len < 2) {
+ return 0;
+ }
+ historical_length = data[1] & 0xf;
+ opt_bytes = 0;
+ if (data[0] != 0x3b && data[0] != 0x3f) {
+ DPRINTF(card, D_WARN, "atr's T0 is 0x%X, not in {0x3b, 0x3f}\n",
+ data[0]);
+ return 0;
+ }
+ td_count = 0;
+ td = data[1] >> 4;
+ while (td && td_count < 2 && opt_bytes + historical_length + 2 < len) {
+ td_count++;
+ if (td & 0x1) {
+ opt_bytes++;
+ }
+ if (td & 0x2) {
+ opt_bytes++;
+ }
+ if (td & 0x4) {
+ opt_bytes++;
+ }
+ if (td & 0x8) {
+ opt_bytes++;
+ td = data[opt_bytes + 2] >> 4;
+ }
+ }
+ if (len < 2 + historical_length + opt_bytes) {
+ DPRINTF(card, D_WARN,
+ "atr too short: len %d, but historical_len %d, T1 0x%X\n",
+ len, historical_length, data[1]);
+ return 0;
+ }
+ if (len > 2 + historical_length + opt_bytes) {
+ DPRINTF(card, D_WARN,
+ "atr too long: len %d, but hist/opt %d/%d, T1 0x%X\n",
+ len, historical_length, opt_bytes, data[1]);
+ /* let it through */
+ }
+ DPRINTF(card, D_VERBOSE,
+ "atr passes check: %d total length, %d historical, %d optional\n",
+ len, historical_length, opt_bytes);
+
+ return 1;
+}
+
+static void ccid_card_vscard_handle_message(PassthruState *card,
+ VSCMsgHeader *scr_msg_header)
+{
+ uint8_t *data = (uint8_t *)&scr_msg_header[1];
+
+ switch (scr_msg_header->type) {
+ case VSC_ATR:
+ DPRINTF(card, D_INFO, "VSC_ATR %d\n", scr_msg_header->length);
+ if (scr_msg_header->length > MAX_ATR_SIZE) {
+ error_report("ATR size exceeds spec, ignoring");
+ ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
+ VSC_GENERAL_ERROR);
+ break;
+ }
+ if (!check_atr(card, data, scr_msg_header->length)) {
+ error_report("ATR is inconsistent, ignoring");
+ ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
+ VSC_GENERAL_ERROR);
+ break;
+ }
+ memcpy(card->atr, data, scr_msg_header->length);
+ card->atr_length = scr_msg_header->length;
+ ccid_card_card_inserted(&card->base);
+ ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
+ VSC_SUCCESS);
+ break;
+ case VSC_APDU:
+ ccid_card_send_apdu_to_guest(
+ &card->base, data, scr_msg_header->length);
+ break;
+ case VSC_CardRemove:
+ DPRINTF(card, D_INFO, "VSC_CardRemove\n");
+ ccid_card_card_removed(&card->base);
+ ccid_card_vscard_send_error(card,
+ scr_msg_header->reader_id, VSC_SUCCESS);
+ break;
+ case VSC_Init:
+ ccid_card_vscard_handle_init(
+ card, scr_msg_header, (VSCMsgInit *)data);
+ break;
+ case VSC_Error:
+ ccid_card_card_error(&card->base, *(uint32_t *)data);
+ break;
+ case VSC_ReaderAdd:
+ if (ccid_card_ccid_attach(&card->base) < 0) {
+ ccid_card_vscard_send_error(card, VSCARD_UNDEFINED_READER_ID,
+ VSC_CANNOT_ADD_MORE_READERS);
+ } else {
+ ccid_card_vscard_send_error(card, VSCARD_MINIMAL_READER_ID,
+ VSC_SUCCESS);
+ }
+ break;
+ case VSC_ReaderRemove:
+ ccid_card_ccid_detach(&card->base);
+ ccid_card_vscard_send_error(card,
+ scr_msg_header->reader_id, VSC_SUCCESS);
+ break;
+ default:
+ printf("usb-ccid: chardev: unexpected message of type %X\n",
+ scr_msg_header->type);
+ ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
+ VSC_GENERAL_ERROR);
+ }
+}
+
+static void ccid_card_vscard_drop_connection(PassthruState *card)
+{
+ qemu_chr_delete(card->cs);
+ card->vscard_in_pos = card->vscard_in_hdr = 0;
+}
+
+static void ccid_card_vscard_read(void *opaque, const uint8_t *buf, int size)
+{
+ PassthruState *card = opaque;
+ VSCMsgHeader *hdr;
+
+ if (card->vscard_in_pos + size > VSCARD_IN_SIZE) {
+ error_report(
+ "no room for data: pos %d + size %d > %d. dropping connection.",
+ card->vscard_in_pos, size, VSCARD_IN_SIZE);
+ ccid_card_vscard_drop_connection(card);
+ return;
+ }
+ assert(card->vscard_in_pos < VSCARD_IN_SIZE);
+ assert(card->vscard_in_hdr < VSCARD_IN_SIZE);
+ memcpy(card->vscard_in_data + card->vscard_in_pos, buf, size);
+ card->vscard_in_pos += size;
+ hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr);
+
+ while ((card->vscard_in_pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader))
+ &&(card->vscard_in_pos - card->vscard_in_hdr >=
+ sizeof(VSCMsgHeader) + ntohl(hdr->length))) {
+ hdr->reader_id = ntohl(hdr->reader_id);
+ hdr->length = ntohl(hdr->length);
+ hdr->type = ntohl(hdr->type);
+ ccid_card_vscard_handle_message(card, hdr);
+ card->vscard_in_hdr += hdr->length + sizeof(VSCMsgHeader);
+ hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr);
+ }
+ if (card->vscard_in_hdr == card->vscard_in_pos) {
+ card->vscard_in_pos = card->vscard_in_hdr = 0;
+ }
+}
+
+static void ccid_card_vscard_event(void *opaque, int event)
+{
+ PassthruState *card = opaque;
+
+ switch (event) {
+ case CHR_EVENT_BREAK:
+ card->vscard_in_pos = card->vscard_in_hdr = 0;
+ break;
+ case CHR_EVENT_FOCUS:
+ break;
+ case CHR_EVENT_OPENED:
+ DPRINTF(card, D_INFO, "%s: CHR_EVENT_OPENED\n", __func__);
+ break;
+ }
+}
+
+/* End VSCard handling */
+
+static void passthru_apdu_from_guest(
+ CCIDCardState *base, const uint8_t *apdu, uint32_t len)
+{
+ PassthruState *card = DO_UPCAST(PassthruState, base, base);
+
+ if (!card->cs) {
+ printf("ccid-passthru: no chardev, discarding apdu length %d\n", len);
+ return;
+ }
+ ccid_card_vscard_send_apdu(card, apdu, len);
+}
+
+static const uint8_t *passthru_get_atr(CCIDCardState *base, uint32_t *len)
+{
+ PassthruState *card = DO_UPCAST(PassthruState, base, base);
+
+ *len = card->atr_length;
+ return card->atr;
+}
+
+static int passthru_initfn(CCIDCardState *base)
+{
+ PassthruState *card = DO_UPCAST(PassthruState, base, base);
+
+ card->vscard_in_pos = 0;
+ card->vscard_in_hdr = 0;
+ if (card->cs) {
+ DPRINTF(card, D_INFO, "initing chardev\n");
+ qemu_chr_add_handlers(card->cs,
+ ccid_card_vscard_can_read,
+ ccid_card_vscard_read,
+ ccid_card_vscard_event, card);
+ ccid_card_vscard_send_init(card);
+ } else {
+ error_report("missing chardev");
+ return -1;
+ }
+ card->debug = parse_debug_env("QEMU_CCID_PASSTHRU_DEBUG", D_VERBOSE,
+ card->debug);
+ assert(sizeof(DEFAULT_ATR) <= MAX_ATR_SIZE);
+ memcpy(card->atr, DEFAULT_ATR, sizeof(DEFAULT_ATR));
+ card->atr_length = sizeof(DEFAULT_ATR);
+ return 0;
+}
+
+static int passthru_exitfn(CCIDCardState *base)
+{
+ return 0;
+}
+
+static VMStateDescription passthru_vmstate = {
+ .name = "ccid-card-passthru",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(vscard_in_data, PassthruState),
+ VMSTATE_UINT32(vscard_in_pos, PassthruState),
+ VMSTATE_UINT32(vscard_in_hdr, PassthruState),
+ VMSTATE_BUFFER(atr, PassthruState),
+ VMSTATE_UINT8(atr_length, PassthruState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property passthru_card_properties[] = {
+ DEFINE_PROP_CHR("chardev", PassthruState, cs),
+ DEFINE_PROP_UINT8("debug", PassthruState, debug, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void passthru_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ CCIDCardClass *cc = CCID_CARD_CLASS(klass);
+
+ cc->initfn = passthru_initfn;
+ cc->exitfn = passthru_exitfn;
+ cc->get_atr = passthru_get_atr;
+ cc->apdu_from_guest = passthru_apdu_from_guest;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "passthrough smartcard";
+ dc->vmsd = &passthru_vmstate;
+ dc->props = passthru_card_properties;
+}
+
+static const TypeInfo passthru_card_info = {
+ .name = PASSTHRU_DEV_NAME,
+ .parent = TYPE_CCID_CARD,
+ .instance_size = sizeof(PassthruState),
+ .class_init = passthru_class_initfn,
+};
+
+static void ccid_card_passthru_register_types(void)
+{
+ type_register_static(&passthru_card_info);
+}
+
+type_init(ccid_card_passthru_register_types)
diff --git a/hw/usb/ccid.h b/hw/usb/ccid.h
new file mode 100644
index 00000000..9334da8a
--- /dev/null
+++ b/hw/usb/ccid.h
@@ -0,0 +1,65 @@
+/*
+ * CCID Passthru Card Device emulation
+ *
+ * Copyright (c) 2011 Red Hat.
+ * Written by Alon Levy.
+ *
+ * This code is licensed under the GNU LGPL, version 2 or later.
+ */
+
+#ifndef CCID_H
+#define CCID_H
+
+#include "hw/qdev.h"
+
+typedef struct CCIDCardState CCIDCardState;
+typedef struct CCIDCardInfo CCIDCardInfo;
+
+#define TYPE_CCID_CARD "ccid-card"
+#define CCID_CARD(obj) \
+ OBJECT_CHECK(CCIDCardState, (obj), TYPE_CCID_CARD)
+#define CCID_CARD_CLASS(klass) \
+ OBJECT_CLASS_CHECK(CCIDCardClass, (klass), TYPE_CCID_CARD)
+#define CCID_CARD_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(CCIDCardClass, (obj), TYPE_CCID_CARD)
+
+/*
+ * callbacks to be used by the CCID device (hw/usb-ccid.c) to call
+ * into the smartcard device (hw/ccid-card-*.c)
+ */
+typedef struct CCIDCardClass {
+ DeviceClass parent_class;
+ const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len);
+ void (*apdu_from_guest)(CCIDCardState *card,
+ const uint8_t *apdu,
+ uint32_t len);
+ int (*exitfn)(CCIDCardState *card);
+ int (*initfn)(CCIDCardState *card);
+} CCIDCardClass;
+
+/*
+ * state of the CCID Card device (i.e. hw/ccid-card-*.c)
+ */
+struct CCIDCardState {
+ DeviceState qdev;
+ uint32_t slot; /* For future use with multiple slot reader. */
+};
+
+/*
+ * API for smartcard calling the CCID device (used by hw/ccid-card-*.c)
+ */
+void ccid_card_send_apdu_to_guest(CCIDCardState *card,
+ uint8_t *apdu,
+ uint32_t len);
+void ccid_card_card_removed(CCIDCardState *card);
+void ccid_card_card_inserted(CCIDCardState *card);
+void ccid_card_card_error(CCIDCardState *card, uint64_t error);
+
+/*
+ * support guest visible insertion/removal of ccid devices based on actual
+ * devices connected/removed. Called by card implementation (passthru, local)
+ */
+int ccid_card_ccid_attach(CCIDCardState *card);
+void ccid_card_ccid_detach(CCIDCardState *card);
+
+#endif /* CCID_H */
diff --git a/hw/usb/combined-packet.c b/hw/usb/combined-packet.c
new file mode 100644
index 00000000..ad77705f
--- /dev/null
+++ b/hw/usb/combined-packet.c
@@ -0,0 +1,187 @@
+/*
+ * QEMU USB packet combining code (for input pipelining)
+ *
+ * Copyright(c) 2012 Red Hat, Inc.
+ *
+ * Red Hat Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or(at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu-common.h"
+#include "hw/usb.h"
+#include "qemu/iov.h"
+#include "trace.h"
+
+static void usb_combined_packet_add(USBCombinedPacket *combined, USBPacket *p)
+{
+ qemu_iovec_concat(&combined->iov, &p->iov, 0, p->iov.size);
+ QTAILQ_INSERT_TAIL(&combined->packets, p, combined_entry);
+ p->combined = combined;
+}
+
+/* Note will free combined when the last packet gets removed */
+static void usb_combined_packet_remove(USBCombinedPacket *combined,
+ USBPacket *p)
+{
+ assert(p->combined == combined);
+ p->combined = NULL;
+ QTAILQ_REMOVE(&combined->packets, p, combined_entry);
+ if (QTAILQ_EMPTY(&combined->packets)) {
+ qemu_iovec_destroy(&combined->iov);
+ g_free(combined);
+ }
+}
+
+/* Also handles completion of non combined packets for pipelined input eps */
+void usb_combined_input_packet_complete(USBDevice *dev, USBPacket *p)
+{
+ USBCombinedPacket *combined = p->combined;
+ USBEndpoint *ep = p->ep;
+ USBPacket *next;
+ int status, actual_length;
+ bool short_not_ok, done = false;
+
+ if (combined == NULL) {
+ usb_packet_complete_one(dev, p);
+ goto leave;
+ }
+
+ assert(combined->first == p && p == QTAILQ_FIRST(&combined->packets));
+
+ status = combined->first->status;
+ actual_length = combined->first->actual_length;
+ short_not_ok = QTAILQ_LAST(&combined->packets, packets_head)->short_not_ok;
+
+ QTAILQ_FOREACH_SAFE(p, &combined->packets, combined_entry, next) {
+ if (!done) {
+ /* Distribute data over uncombined packets */
+ if (actual_length >= p->iov.size) {
+ p->actual_length = p->iov.size;
+ } else {
+ /* Send short or error packet to complete the transfer */
+ p->actual_length = actual_length;
+ done = true;
+ }
+ /* Report status on the last packet */
+ if (done || next == NULL) {
+ p->status = status;
+ } else {
+ p->status = USB_RET_SUCCESS;
+ }
+ p->short_not_ok = short_not_ok;
+ /* Note will free combined when the last packet gets removed! */
+ usb_combined_packet_remove(combined, p);
+ usb_packet_complete_one(dev, p);
+ actual_length -= p->actual_length;
+ } else {
+ /* Remove any leftover packets from the queue */
+ p->status = USB_RET_REMOVE_FROM_QUEUE;
+ /* Note will free combined on the last packet! */
+ dev->port->ops->complete(dev->port, p);
+ }
+ }
+ /* Do not use combined here, it has been freed! */
+leave:
+ /* Check if there are packets in the queue waiting for our completion */
+ usb_ep_combine_input_packets(ep);
+}
+
+/* May only be called for combined packets! */
+void usb_combined_packet_cancel(USBDevice *dev, USBPacket *p)
+{
+ USBCombinedPacket *combined = p->combined;
+ assert(combined != NULL);
+ USBPacket *first = p->combined->first;
+
+ /* Note will free combined on the last packet! */
+ usb_combined_packet_remove(combined, p);
+ if (p == first) {
+ usb_device_cancel_packet(dev, p);
+ }
+}
+
+/*
+ * Large input transfers can get split into multiple input packets, this
+ * function recombines them, removing the short_not_ok checks which all but
+ * the last packet of such splits transfers have, thereby allowing input
+ * transfer pipelining (which we cannot do on short_not_ok transfers)
+ */
+void usb_ep_combine_input_packets(USBEndpoint *ep)
+{
+ USBPacket *p, *u, *next, *prev = NULL, *first = NULL;
+ USBPort *port = ep->dev->port;
+ int totalsize;
+
+ assert(ep->pipeline);
+ assert(ep->pid == USB_TOKEN_IN);
+
+ QTAILQ_FOREACH_SAFE(p, &ep->queue, queue, next) {
+ /* Empty the queue on a halt */
+ if (ep->halted) {
+ p->status = USB_RET_REMOVE_FROM_QUEUE;
+ port->ops->complete(port, p);
+ continue;
+ }
+
+ /* Skip packets already submitted to the device */
+ if (p->state == USB_PACKET_ASYNC) {
+ prev = p;
+ continue;
+ }
+ usb_packet_check_state(p, USB_PACKET_QUEUED);
+
+ /*
+ * If the previous (combined) packet has the short_not_ok flag set
+ * stop, as we must not submit packets to the device after a transfer
+ * ending with short_not_ok packet.
+ */
+ if (prev && prev->short_not_ok) {
+ break;
+ }
+
+ if (first) {
+ if (first->combined == NULL) {
+ USBCombinedPacket *combined = g_new0(USBCombinedPacket, 1);
+
+ combined->first = first;
+ QTAILQ_INIT(&combined->packets);
+ qemu_iovec_init(&combined->iov, 2);
+ usb_combined_packet_add(combined, first);
+ }
+ usb_combined_packet_add(first->combined, p);
+ } else {
+ first = p;
+ }
+
+ /* Is this packet the last one of a (combined) transfer? */
+ totalsize = (p->combined) ? p->combined->iov.size : p->iov.size;
+ if ((p->iov.size % ep->max_packet_size) != 0 || !p->short_not_ok ||
+ next == NULL ||
+ /* Work around for Linux usbfs bulk splitting + migration */
+ (totalsize == 16348 && p->int_req)) {
+ usb_device_handle_data(ep->dev, first);
+ assert(first->status == USB_RET_ASYNC);
+ if (first->combined) {
+ QTAILQ_FOREACH(u, &first->combined->packets, combined_entry) {
+ usb_packet_set_state(u, USB_PACKET_ASYNC);
+ }
+ } else {
+ usb_packet_set_state(first, USB_PACKET_ASYNC);
+ }
+ first = NULL;
+ prev = p;
+ }
+ }
+}
diff --git a/hw/usb/core.c b/hw/usb/core.c
new file mode 100644
index 00000000..d0025db6
--- /dev/null
+++ b/hw/usb/core.c
@@ -0,0 +1,794 @@
+/*
+ * QEMU USB emulation
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * 2008 Generic packet handler rewrite by Max Krasnyansky
+ *
+ * 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.
+ */
+#include "qemu-common.h"
+#include "hw/usb.h"
+#include "qemu/iov.h"
+#include "trace.h"
+
+void usb_pick_speed(USBPort *port)
+{
+ static const int speeds[] = {
+ USB_SPEED_SUPER,
+ USB_SPEED_HIGH,
+ USB_SPEED_FULL,
+ USB_SPEED_LOW,
+ };
+ USBDevice *udev = port->dev;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(speeds); i++) {
+ if ((udev->speedmask & (1 << speeds[i])) &&
+ (port->speedmask & (1 << speeds[i]))) {
+ udev->speed = speeds[i];
+ return;
+ }
+ }
+}
+
+void usb_attach(USBPort *port)
+{
+ USBDevice *dev = port->dev;
+
+ assert(dev != NULL);
+ assert(dev->attached);
+ assert(dev->state == USB_STATE_NOTATTACHED);
+ usb_pick_speed(port);
+ port->ops->attach(port);
+ dev->state = USB_STATE_ATTACHED;
+ usb_device_handle_attach(dev);
+}
+
+void usb_detach(USBPort *port)
+{
+ USBDevice *dev = port->dev;
+
+ assert(dev != NULL);
+ assert(dev->state != USB_STATE_NOTATTACHED);
+ port->ops->detach(port);
+ dev->state = USB_STATE_NOTATTACHED;
+}
+
+void usb_port_reset(USBPort *port)
+{
+ USBDevice *dev = port->dev;
+
+ assert(dev != NULL);
+ usb_detach(port);
+ usb_attach(port);
+ usb_device_reset(dev);
+}
+
+void usb_device_reset(USBDevice *dev)
+{
+ if (dev == NULL || !dev->attached) {
+ return;
+ }
+ dev->remote_wakeup = 0;
+ dev->addr = 0;
+ dev->state = USB_STATE_DEFAULT;
+ usb_device_handle_reset(dev);
+}
+
+void usb_wakeup(USBEndpoint *ep, unsigned int stream)
+{
+ USBDevice *dev = ep->dev;
+ USBBus *bus = usb_bus_from_device(dev);
+
+ if (dev->remote_wakeup && dev->port && dev->port->ops->wakeup) {
+ dev->port->ops->wakeup(dev->port);
+ }
+ if (bus->ops->wakeup_endpoint) {
+ bus->ops->wakeup_endpoint(bus, ep, stream);
+ }
+}
+
+/**********************/
+
+/* generic USB device helpers (you are not forced to use them when
+ writing your USB device driver, but they help handling the
+ protocol)
+*/
+
+#define SETUP_STATE_IDLE 0
+#define SETUP_STATE_SETUP 1
+#define SETUP_STATE_DATA 2
+#define SETUP_STATE_ACK 3
+#define SETUP_STATE_PARAM 4
+
+static void do_token_setup(USBDevice *s, USBPacket *p)
+{
+ int request, value, index;
+
+ if (p->iov.size != 8) {
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ usb_packet_copy(p, s->setup_buf, p->iov.size);
+ p->actual_length = 0;
+ s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
+ s->setup_index = 0;
+
+ request = (s->setup_buf[0] << 8) | s->setup_buf[1];
+ value = (s->setup_buf[3] << 8) | s->setup_buf[2];
+ index = (s->setup_buf[5] << 8) | s->setup_buf[4];
+
+ if (s->setup_buf[0] & USB_DIR_IN) {
+ usb_device_handle_control(s, p, request, value, index,
+ s->setup_len, s->data_buf);
+ if (p->status == USB_RET_ASYNC) {
+ s->setup_state = SETUP_STATE_SETUP;
+ }
+ if (p->status != USB_RET_SUCCESS) {
+ return;
+ }
+
+ if (p->actual_length < s->setup_len) {
+ s->setup_len = p->actual_length;
+ }
+ s->setup_state = SETUP_STATE_DATA;
+ } else {
+ if (s->setup_len > sizeof(s->data_buf)) {
+ fprintf(stderr,
+ "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
+ s->setup_len, sizeof(s->data_buf));
+ p->status = USB_RET_STALL;
+ return;
+ }
+ if (s->setup_len == 0)
+ s->setup_state = SETUP_STATE_ACK;
+ else
+ s->setup_state = SETUP_STATE_DATA;
+ }
+
+ p->actual_length = 8;
+}
+
+static void do_token_in(USBDevice *s, USBPacket *p)
+{
+ int request, value, index;
+
+ assert(p->ep->nr == 0);
+
+ request = (s->setup_buf[0] << 8) | s->setup_buf[1];
+ value = (s->setup_buf[3] << 8) | s->setup_buf[2];
+ index = (s->setup_buf[5] << 8) | s->setup_buf[4];
+
+ switch(s->setup_state) {
+ case SETUP_STATE_ACK:
+ if (!(s->setup_buf[0] & USB_DIR_IN)) {
+ usb_device_handle_control(s, p, request, value, index,
+ s->setup_len, s->data_buf);
+ if (p->status == USB_RET_ASYNC) {
+ return;
+ }
+ s->setup_state = SETUP_STATE_IDLE;
+ p->actual_length = 0;
+ }
+ break;
+
+ case SETUP_STATE_DATA:
+ if (s->setup_buf[0] & USB_DIR_IN) {
+ int len = s->setup_len - s->setup_index;
+ if (len > p->iov.size) {
+ len = p->iov.size;
+ }
+ usb_packet_copy(p, s->data_buf + s->setup_index, len);
+ s->setup_index += len;
+ if (s->setup_index >= s->setup_len) {
+ s->setup_state = SETUP_STATE_ACK;
+ }
+ return;
+ }
+ s->setup_state = SETUP_STATE_IDLE;
+ p->status = USB_RET_STALL;
+ break;
+
+ default:
+ p->status = USB_RET_STALL;
+ }
+}
+
+static void do_token_out(USBDevice *s, USBPacket *p)
+{
+ assert(p->ep->nr == 0);
+
+ switch(s->setup_state) {
+ case SETUP_STATE_ACK:
+ if (s->setup_buf[0] & USB_DIR_IN) {
+ s->setup_state = SETUP_STATE_IDLE;
+ /* transfer OK */
+ } else {
+ /* ignore additional output */
+ }
+ break;
+
+ case SETUP_STATE_DATA:
+ if (!(s->setup_buf[0] & USB_DIR_IN)) {
+ int len = s->setup_len - s->setup_index;
+ if (len > p->iov.size) {
+ len = p->iov.size;
+ }
+ usb_packet_copy(p, s->data_buf + s->setup_index, len);
+ s->setup_index += len;
+ if (s->setup_index >= s->setup_len) {
+ s->setup_state = SETUP_STATE_ACK;
+ }
+ return;
+ }
+ s->setup_state = SETUP_STATE_IDLE;
+ p->status = USB_RET_STALL;
+ break;
+
+ default:
+ p->status = USB_RET_STALL;
+ }
+}
+
+static void do_parameter(USBDevice *s, USBPacket *p)
+{
+ int i, request, value, index;
+
+ for (i = 0; i < 8; i++) {
+ s->setup_buf[i] = p->parameter >> (i*8);
+ }
+
+ s->setup_state = SETUP_STATE_PARAM;
+ s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
+ s->setup_index = 0;
+
+ request = (s->setup_buf[0] << 8) | s->setup_buf[1];
+ value = (s->setup_buf[3] << 8) | s->setup_buf[2];
+ index = (s->setup_buf[5] << 8) | s->setup_buf[4];
+
+ if (s->setup_len > sizeof(s->data_buf)) {
+ fprintf(stderr,
+ "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
+ s->setup_len, sizeof(s->data_buf));
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ if (p->pid == USB_TOKEN_OUT) {
+ usb_packet_copy(p, s->data_buf, s->setup_len);
+ }
+
+ usb_device_handle_control(s, p, request, value, index,
+ s->setup_len, s->data_buf);
+ if (p->status == USB_RET_ASYNC) {
+ return;
+ }
+
+ if (p->actual_length < s->setup_len) {
+ s->setup_len = p->actual_length;
+ }
+ if (p->pid == USB_TOKEN_IN) {
+ p->actual_length = 0;
+ usb_packet_copy(p, s->data_buf, s->setup_len);
+ }
+}
+
+/* ctrl complete function for devices which use usb_generic_handle_packet and
+ may return USB_RET_ASYNC from their handle_control callback. Device code
+ which does this *must* call this function instead of the normal
+ usb_packet_complete to complete their async control packets. */
+void usb_generic_async_ctrl_complete(USBDevice *s, USBPacket *p)
+{
+ if (p->status < 0) {
+ s->setup_state = SETUP_STATE_IDLE;
+ }
+
+ switch (s->setup_state) {
+ case SETUP_STATE_SETUP:
+ if (p->actual_length < s->setup_len) {
+ s->setup_len = p->actual_length;
+ }
+ s->setup_state = SETUP_STATE_DATA;
+ p->actual_length = 8;
+ break;
+
+ case SETUP_STATE_ACK:
+ s->setup_state = SETUP_STATE_IDLE;
+ p->actual_length = 0;
+ break;
+
+ case SETUP_STATE_PARAM:
+ if (p->actual_length < s->setup_len) {
+ s->setup_len = p->actual_length;
+ }
+ if (p->pid == USB_TOKEN_IN) {
+ p->actual_length = 0;
+ usb_packet_copy(p, s->data_buf, s->setup_len);
+ }
+ break;
+
+ default:
+ break;
+ }
+ usb_packet_complete(s, p);
+}
+
+USBDevice *usb_find_device(USBPort *port, uint8_t addr)
+{
+ USBDevice *dev = port->dev;
+
+ if (dev == NULL || !dev->attached || dev->state != USB_STATE_DEFAULT) {
+ return NULL;
+ }
+ if (dev->addr == addr) {
+ return dev;
+ }
+ return usb_device_find_device(dev, addr);
+}
+
+static void usb_process_one(USBPacket *p)
+{
+ USBDevice *dev = p->ep->dev;
+
+ /*
+ * Handlers expect status to be initialized to USB_RET_SUCCESS, but it
+ * can be USB_RET_NAK here from a previous usb_process_one() call,
+ * or USB_RET_ASYNC from going through usb_queue_one().
+ */
+ p->status = USB_RET_SUCCESS;
+
+ if (p->ep->nr == 0) {
+ /* control pipe */
+ if (p->parameter) {
+ do_parameter(dev, p);
+ return;
+ }
+ switch (p->pid) {
+ case USB_TOKEN_SETUP:
+ do_token_setup(dev, p);
+ break;
+ case USB_TOKEN_IN:
+ do_token_in(dev, p);
+ break;
+ case USB_TOKEN_OUT:
+ do_token_out(dev, p);
+ break;
+ default:
+ p->status = USB_RET_STALL;
+ }
+ } else {
+ /* data pipe */
+ usb_device_handle_data(dev, p);
+ }
+}
+
+static void usb_queue_one(USBPacket *p)
+{
+ usb_packet_set_state(p, USB_PACKET_QUEUED);
+ QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue);
+ p->status = USB_RET_ASYNC;
+}
+
+/* Hand over a packet to a device for processing. p->status ==
+ USB_RET_ASYNC indicates the processing isn't finished yet, the
+ driver will call usb_packet_complete() when done processing it. */
+void usb_handle_packet(USBDevice *dev, USBPacket *p)
+{
+ if (dev == NULL) {
+ p->status = USB_RET_NODEV;
+ return;
+ }
+ assert(dev == p->ep->dev);
+ assert(dev->state == USB_STATE_DEFAULT);
+ usb_packet_check_state(p, USB_PACKET_SETUP);
+ assert(p->ep != NULL);
+
+ /* Submitting a new packet clears halt */
+ if (p->ep->halted) {
+ assert(QTAILQ_EMPTY(&p->ep->queue));
+ p->ep->halted = false;
+ }
+
+ if (QTAILQ_EMPTY(&p->ep->queue) || p->ep->pipeline || p->stream) {
+ usb_process_one(p);
+ if (p->status == USB_RET_ASYNC) {
+ /* hcd drivers cannot handle async for isoc */
+ assert(p->ep->type != USB_ENDPOINT_XFER_ISOC);
+ /* using async for interrupt packets breaks migration */
+ assert(p->ep->type != USB_ENDPOINT_XFER_INT ||
+ (dev->flags & (1 << USB_DEV_FLAG_IS_HOST)));
+ usb_packet_set_state(p, USB_PACKET_ASYNC);
+ QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue);
+ } else if (p->status == USB_RET_ADD_TO_QUEUE) {
+ usb_queue_one(p);
+ } else {
+ /*
+ * When pipelining is enabled usb-devices must always return async,
+ * otherwise packets can complete out of order!
+ */
+ assert(p->stream || !p->ep->pipeline ||
+ QTAILQ_EMPTY(&p->ep->queue));
+ if (p->status != USB_RET_NAK) {
+ usb_packet_set_state(p, USB_PACKET_COMPLETE);
+ }
+ }
+ } else {
+ usb_queue_one(p);
+ }
+}
+
+void usb_packet_complete_one(USBDevice *dev, USBPacket *p)
+{
+ USBEndpoint *ep = p->ep;
+
+ assert(p->stream || QTAILQ_FIRST(&ep->queue) == p);
+ assert(p->status != USB_RET_ASYNC && p->status != USB_RET_NAK);
+
+ if (p->status != USB_RET_SUCCESS ||
+ (p->short_not_ok && (p->actual_length < p->iov.size))) {
+ ep->halted = true;
+ }
+ usb_packet_set_state(p, USB_PACKET_COMPLETE);
+ QTAILQ_REMOVE(&ep->queue, p, queue);
+ dev->port->ops->complete(dev->port, p);
+}
+
+/* Notify the controller that an async packet is complete. This should only
+ be called for packets previously deferred by returning USB_RET_ASYNC from
+ handle_packet. */
+void usb_packet_complete(USBDevice *dev, USBPacket *p)
+{
+ USBEndpoint *ep = p->ep;
+
+ usb_packet_check_state(p, USB_PACKET_ASYNC);
+ usb_packet_complete_one(dev, p);
+
+ while (!QTAILQ_EMPTY(&ep->queue)) {
+ p = QTAILQ_FIRST(&ep->queue);
+ if (ep->halted) {
+ /* Empty the queue on a halt */
+ p->status = USB_RET_REMOVE_FROM_QUEUE;
+ dev->port->ops->complete(dev->port, p);
+ continue;
+ }
+ if (p->state == USB_PACKET_ASYNC) {
+ break;
+ }
+ usb_packet_check_state(p, USB_PACKET_QUEUED);
+ usb_process_one(p);
+ if (p->status == USB_RET_ASYNC) {
+ usb_packet_set_state(p, USB_PACKET_ASYNC);
+ break;
+ }
+ usb_packet_complete_one(ep->dev, p);
+ }
+}
+
+/* Cancel an active packet. The packed must have been deferred by
+ returning USB_RET_ASYNC from handle_packet, and not yet
+ completed. */
+void usb_cancel_packet(USBPacket * p)
+{
+ bool callback = (p->state == USB_PACKET_ASYNC);
+ assert(usb_packet_is_inflight(p));
+ usb_packet_set_state(p, USB_PACKET_CANCELED);
+ QTAILQ_REMOVE(&p->ep->queue, p, queue);
+ if (callback) {
+ usb_device_cancel_packet(p->ep->dev, p);
+ }
+}
+
+
+void usb_packet_init(USBPacket *p)
+{
+ qemu_iovec_init(&p->iov, 1);
+}
+
+static const char *usb_packet_state_name(USBPacketState state)
+{
+ static const char *name[] = {
+ [USB_PACKET_UNDEFINED] = "undef",
+ [USB_PACKET_SETUP] = "setup",
+ [USB_PACKET_QUEUED] = "queued",
+ [USB_PACKET_ASYNC] = "async",
+ [USB_PACKET_COMPLETE] = "complete",
+ [USB_PACKET_CANCELED] = "canceled",
+ };
+ if (state < ARRAY_SIZE(name)) {
+ return name[state];
+ }
+ return "INVALID";
+}
+
+void usb_packet_check_state(USBPacket *p, USBPacketState expected)
+{
+ USBDevice *dev;
+ USBBus *bus;
+
+ if (p->state == expected) {
+ return;
+ }
+ dev = p->ep->dev;
+ bus = usb_bus_from_device(dev);
+ trace_usb_packet_state_fault(bus->busnr, dev->port->path, p->ep->nr, p,
+ usb_packet_state_name(p->state),
+ usb_packet_state_name(expected));
+ assert(!"usb packet state check failed");
+}
+
+void usb_packet_set_state(USBPacket *p, USBPacketState state)
+{
+ if (p->ep) {
+ USBDevice *dev = p->ep->dev;
+ USBBus *bus = usb_bus_from_device(dev);
+ trace_usb_packet_state_change(bus->busnr, dev->port->path, p->ep->nr, p,
+ usb_packet_state_name(p->state),
+ usb_packet_state_name(state));
+ } else {
+ trace_usb_packet_state_change(-1, "", -1, p,
+ usb_packet_state_name(p->state),
+ usb_packet_state_name(state));
+ }
+ p->state = state;
+}
+
+void usb_packet_setup(USBPacket *p, int pid,
+ USBEndpoint *ep, unsigned int stream,
+ uint64_t id, bool short_not_ok, bool int_req)
+{
+ assert(!usb_packet_is_inflight(p));
+ assert(p->iov.iov != NULL);
+ p->id = id;
+ p->pid = pid;
+ p->ep = ep;
+ p->stream = stream;
+ p->status = USB_RET_SUCCESS;
+ p->actual_length = 0;
+ p->parameter = 0;
+ p->short_not_ok = short_not_ok;
+ p->int_req = int_req;
+ p->combined = NULL;
+ qemu_iovec_reset(&p->iov);
+ usb_packet_set_state(p, USB_PACKET_SETUP);
+}
+
+void usb_packet_addbuf(USBPacket *p, void *ptr, size_t len)
+{
+ qemu_iovec_add(&p->iov, ptr, len);
+}
+
+void usb_packet_copy(USBPacket *p, void *ptr, size_t bytes)
+{
+ QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;
+
+ assert(p->actual_length >= 0);
+ assert(p->actual_length + bytes <= iov->size);
+ switch (p->pid) {
+ case USB_TOKEN_SETUP:
+ case USB_TOKEN_OUT:
+ iov_to_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes);
+ break;
+ case USB_TOKEN_IN:
+ iov_from_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes);
+ break;
+ default:
+ fprintf(stderr, "%s: invalid pid: %x\n", __func__, p->pid);
+ abort();
+ }
+ p->actual_length += bytes;
+}
+
+void usb_packet_skip(USBPacket *p, size_t bytes)
+{
+ QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;
+
+ assert(p->actual_length >= 0);
+ assert(p->actual_length + bytes <= iov->size);
+ if (p->pid == USB_TOKEN_IN) {
+ iov_memset(iov->iov, iov->niov, p->actual_length, 0, bytes);
+ }
+ p->actual_length += bytes;
+}
+
+size_t usb_packet_size(USBPacket *p)
+{
+ return p->combined ? p->combined->iov.size : p->iov.size;
+}
+
+void usb_packet_cleanup(USBPacket *p)
+{
+ assert(!usb_packet_is_inflight(p));
+ qemu_iovec_destroy(&p->iov);
+}
+
+void usb_ep_reset(USBDevice *dev)
+{
+ int ep;
+
+ dev->ep_ctl.nr = 0;
+ dev->ep_ctl.type = USB_ENDPOINT_XFER_CONTROL;
+ dev->ep_ctl.ifnum = 0;
+ dev->ep_ctl.max_packet_size = 64;
+ dev->ep_ctl.max_streams = 0;
+ dev->ep_ctl.dev = dev;
+ dev->ep_ctl.pipeline = false;
+ for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
+ dev->ep_in[ep].nr = ep + 1;
+ dev->ep_out[ep].nr = ep + 1;
+ dev->ep_in[ep].pid = USB_TOKEN_IN;
+ dev->ep_out[ep].pid = USB_TOKEN_OUT;
+ dev->ep_in[ep].type = USB_ENDPOINT_XFER_INVALID;
+ dev->ep_out[ep].type = USB_ENDPOINT_XFER_INVALID;
+ dev->ep_in[ep].ifnum = USB_INTERFACE_INVALID;
+ dev->ep_out[ep].ifnum = USB_INTERFACE_INVALID;
+ dev->ep_in[ep].max_packet_size = 0;
+ dev->ep_out[ep].max_packet_size = 0;
+ dev->ep_in[ep].max_streams = 0;
+ dev->ep_out[ep].max_streams = 0;
+ dev->ep_in[ep].dev = dev;
+ dev->ep_out[ep].dev = dev;
+ dev->ep_in[ep].pipeline = false;
+ dev->ep_out[ep].pipeline = false;
+ }
+}
+
+void usb_ep_init(USBDevice *dev)
+{
+ int ep;
+
+ usb_ep_reset(dev);
+ QTAILQ_INIT(&dev->ep_ctl.queue);
+ for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
+ QTAILQ_INIT(&dev->ep_in[ep].queue);
+ QTAILQ_INIT(&dev->ep_out[ep].queue);
+ }
+}
+
+void usb_ep_dump(USBDevice *dev)
+{
+ static const char *tname[] = {
+ [USB_ENDPOINT_XFER_CONTROL] = "control",
+ [USB_ENDPOINT_XFER_ISOC] = "isoc",
+ [USB_ENDPOINT_XFER_BULK] = "bulk",
+ [USB_ENDPOINT_XFER_INT] = "int",
+ };
+ int ifnum, ep, first;
+
+ fprintf(stderr, "Device \"%s\", config %d\n",
+ dev->product_desc, dev->configuration);
+ for (ifnum = 0; ifnum < 16; ifnum++) {
+ first = 1;
+ for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
+ if (dev->ep_in[ep].type != USB_ENDPOINT_XFER_INVALID &&
+ dev->ep_in[ep].ifnum == ifnum) {
+ if (first) {
+ first = 0;
+ fprintf(stderr, " Interface %d, alternative %d\n",
+ ifnum, dev->altsetting[ifnum]);
+ }
+ fprintf(stderr, " Endpoint %d, IN, %s, %d max\n", ep,
+ tname[dev->ep_in[ep].type],
+ dev->ep_in[ep].max_packet_size);
+ }
+ if (dev->ep_out[ep].type != USB_ENDPOINT_XFER_INVALID &&
+ dev->ep_out[ep].ifnum == ifnum) {
+ if (first) {
+ first = 0;
+ fprintf(stderr, " Interface %d, alternative %d\n",
+ ifnum, dev->altsetting[ifnum]);
+ }
+ fprintf(stderr, " Endpoint %d, OUT, %s, %d max\n", ep,
+ tname[dev->ep_out[ep].type],
+ dev->ep_out[ep].max_packet_size);
+ }
+ }
+ }
+ fprintf(stderr, "--\n");
+}
+
+struct USBEndpoint *usb_ep_get(USBDevice *dev, int pid, int ep)
+{
+ struct USBEndpoint *eps;
+
+ if (dev == NULL) {
+ return NULL;
+ }
+ eps = (pid == USB_TOKEN_IN) ? dev->ep_in : dev->ep_out;
+ if (ep == 0) {
+ return &dev->ep_ctl;
+ }
+ assert(pid == USB_TOKEN_IN || pid == USB_TOKEN_OUT);
+ assert(ep > 0 && ep <= USB_MAX_ENDPOINTS);
+ return eps + ep - 1;
+}
+
+uint8_t usb_ep_get_type(USBDevice *dev, int pid, int ep)
+{
+ struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
+ return uep->type;
+}
+
+void usb_ep_set_type(USBDevice *dev, int pid, int ep, uint8_t type)
+{
+ struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
+ uep->type = type;
+}
+
+void usb_ep_set_ifnum(USBDevice *dev, int pid, int ep, uint8_t ifnum)
+{
+ struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
+ uep->ifnum = ifnum;
+}
+
+void usb_ep_set_max_packet_size(USBDevice *dev, int pid, int ep,
+ uint16_t raw)
+{
+ struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
+ int size, microframes;
+
+ size = raw & 0x7ff;
+ switch ((raw >> 11) & 3) {
+ case 1:
+ microframes = 2;
+ break;
+ case 2:
+ microframes = 3;
+ break;
+ default:
+ microframes = 1;
+ break;
+ }
+ uep->max_packet_size = size * microframes;
+}
+
+void usb_ep_set_max_streams(USBDevice *dev, int pid, int ep, uint8_t raw)
+{
+ struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
+ int MaxStreams;
+
+ MaxStreams = raw & 0x1f;
+ if (MaxStreams) {
+ uep->max_streams = 1 << MaxStreams;
+ } else {
+ uep->max_streams = 0;
+ }
+}
+
+void usb_ep_set_halted(USBDevice *dev, int pid, int ep, bool halted)
+{
+ struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
+ uep->halted = halted;
+}
+
+USBPacket *usb_ep_find_packet_by_id(USBDevice *dev, int pid, int ep,
+ uint64_t id)
+{
+ struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
+ USBPacket *p;
+
+ QTAILQ_FOREACH(p, &uep->queue, queue) {
+ if (p->id == id) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
diff --git a/hw/usb/desc-msos.c b/hw/usb/desc-msos.c
new file mode 100644
index 00000000..32c3600d
--- /dev/null
+++ b/hw/usb/desc-msos.c
@@ -0,0 +1,238 @@
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+
+/*
+ * Microsoft OS Descriptors
+ *
+ * Windows tries to fetch some special descriptors with informations
+ * specifically for windows. Presence is indicated using a special
+ * string @ index 0xee. There are two kinds of descriptors:
+ *
+ * compatid descriptor
+ * Used to bind drivers, if usb class isn't specific enougth.
+ * Used for PTP/MTP for example (both share the same usb class).
+ *
+ * properties descriptor
+ * Does carry registry entries. They show up in
+ * HLM\SYSTEM\CurrentControlSet\Enum\USB\<devid>\<serial>\Device Parameters
+ *
+ * Note that Windows caches the stuff it got in the registry, so when
+ * playing with this you have to delete registry subtrees to make
+ * windows query the device again:
+ * HLM\SYSTEM\CurrentControlSet\Control\usbflags
+ * HLM\SYSTEM\CurrentControlSet\Enum\USB
+ * Windows will complain it can't delete entries on the second one.
+ * It has deleted everything it had permissions too, which is enouth
+ * as this includes "Device Parameters".
+ *
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff537430.aspx
+ *
+ */
+
+/* ------------------------------------------------------------------ */
+
+typedef struct msos_compat_hdr {
+ uint32_t dwLength;
+ uint8_t bcdVersion_lo;
+ uint8_t bcdVersion_hi;
+ uint8_t wIndex_lo;
+ uint8_t wIndex_hi;
+ uint8_t bCount;
+ uint8_t reserved[7];
+} QEMU_PACKED msos_compat_hdr;
+
+typedef struct msos_compat_func {
+ uint8_t bFirstInterfaceNumber;
+ uint8_t reserved_1;
+ char compatibleId[8];
+ uint8_t subCompatibleId[8];
+ uint8_t reserved_2[6];
+} QEMU_PACKED msos_compat_func;
+
+static int usb_desc_msos_compat(const USBDesc *desc, uint8_t *dest)
+{
+ msos_compat_hdr *hdr = (void *)dest;
+ msos_compat_func *func;
+ int length = sizeof(*hdr);
+ int count = 0;
+
+ func = (void *)(dest + length);
+ func->bFirstInterfaceNumber = 0;
+ func->reserved_1 = 0x01;
+ if (desc->msos->CompatibleID) {
+ snprintf(func->compatibleId, sizeof(func->compatibleId),
+ "%s", desc->msos->CompatibleID);
+ }
+ length += sizeof(*func);
+ count++;
+
+ hdr->dwLength = cpu_to_le32(length);
+ hdr->bcdVersion_lo = 0x00;
+ hdr->bcdVersion_hi = 0x01;
+ hdr->wIndex_lo = 0x04;
+ hdr->wIndex_hi = 0x00;
+ hdr->bCount = count;
+ return length;
+}
+
+/* ------------------------------------------------------------------ */
+
+typedef struct msos_prop_hdr {
+ uint32_t dwLength;
+ uint8_t bcdVersion_lo;
+ uint8_t bcdVersion_hi;
+ uint8_t wIndex_lo;
+ uint8_t wIndex_hi;
+ uint8_t wCount_lo;
+ uint8_t wCount_hi;
+} QEMU_PACKED msos_prop_hdr;
+
+typedef struct msos_prop {
+ uint32_t dwLength;
+ uint32_t dwPropertyDataType;
+ uint8_t dwPropertyNameLength_lo;
+ uint8_t dwPropertyNameLength_hi;
+ uint8_t bPropertyName[];
+} QEMU_PACKED msos_prop;
+
+typedef struct msos_prop_data {
+ uint32_t dwPropertyDataLength;
+ uint8_t bPropertyData[];
+} QEMU_PACKED msos_prop_data;
+
+typedef enum msos_prop_type {
+ MSOS_REG_SZ = 1,
+ MSOS_REG_EXPAND_SZ = 2,
+ MSOS_REG_BINARY = 3,
+ MSOS_REG_DWORD_LE = 4,
+ MSOS_REG_DWORD_BE = 5,
+ MSOS_REG_LINK = 6,
+ MSOS_REG_MULTI_SZ = 7,
+} msos_prop_type;
+
+static int usb_desc_msos_prop_name(struct msos_prop *prop,
+ const wchar_t *name)
+{
+ int length = wcslen(name) + 1;
+ int i;
+
+ prop->dwPropertyNameLength_lo = usb_lo(length*2);
+ prop->dwPropertyNameLength_hi = usb_hi(length*2);
+ for (i = 0; i < length; i++) {
+ prop->bPropertyName[i*2] = usb_lo(name[i]);
+ prop->bPropertyName[i*2+1] = usb_hi(name[i]);
+ }
+ return length*2;
+}
+
+static int usb_desc_msos_prop_str(uint8_t *dest, msos_prop_type type,
+ const wchar_t *name, const wchar_t *value)
+{
+ struct msos_prop *prop = (void *)dest;
+ struct msos_prop_data *data;
+ int length = sizeof(*prop);
+ int i, vlen = wcslen(value) + 1;
+
+ prop->dwPropertyDataType = cpu_to_le32(type);
+ length += usb_desc_msos_prop_name(prop, name);
+ data = (void *)(dest + length);
+
+ data->dwPropertyDataLength = cpu_to_le32(vlen*2);
+ length += sizeof(*prop);
+
+ for (i = 0; i < vlen; i++) {
+ data->bPropertyData[i*2] = usb_lo(value[i]);
+ data->bPropertyData[i*2+1] = usb_hi(value[i]);
+ }
+ length += vlen*2;
+
+ prop->dwLength = cpu_to_le32(length);
+ return length;
+}
+
+static int usb_desc_msos_prop_dword(uint8_t *dest, const wchar_t *name,
+ uint32_t value)
+{
+ struct msos_prop *prop = (void *)dest;
+ struct msos_prop_data *data;
+ int length = sizeof(*prop);
+
+ prop->dwPropertyDataType = cpu_to_le32(MSOS_REG_DWORD_LE);
+ length += usb_desc_msos_prop_name(prop, name);
+ data = (void *)(dest + length);
+
+ data->dwPropertyDataLength = cpu_to_le32(4);
+ data->bPropertyData[0] = (value) & 0xff;
+ data->bPropertyData[1] = (value >> 8) & 0xff;
+ data->bPropertyData[2] = (value >> 16) & 0xff;
+ data->bPropertyData[3] = (value >> 24) & 0xff;
+ length += sizeof(*prop) + 4;
+
+ prop->dwLength = cpu_to_le32(length);
+ return length;
+}
+
+static int usb_desc_msos_prop(const USBDesc *desc, uint8_t *dest)
+{
+ msos_prop_hdr *hdr = (void *)dest;
+ int length = sizeof(*hdr);
+ int count = 0;
+
+ if (desc->msos->Label) {
+ /*
+ * Given as example in the specs. Havn't figured yet where
+ * this label shows up in the windows gui.
+ */
+ length += usb_desc_msos_prop_str(dest+length, MSOS_REG_SZ,
+ L"Label", desc->msos->Label);
+ count++;
+ }
+
+ if (desc->msos->SelectiveSuspendEnabled) {
+ /*
+ * Signaling remote wakeup capability in the standard usb
+ * descriptors isn't enouth to make windows actually use it.
+ * This is the "Yes, we really mean it" registy entry to flip
+ * the switch in the windows drivers.
+ */
+ length += usb_desc_msos_prop_dword(dest+length,
+ L"SelectiveSuspendEnabled", 1);
+ count++;
+ }
+
+ hdr->dwLength = cpu_to_le32(length);
+ hdr->bcdVersion_lo = 0x00;
+ hdr->bcdVersion_hi = 0x01;
+ hdr->wIndex_lo = 0x05;
+ hdr->wIndex_hi = 0x00;
+ hdr->wCount_lo = usb_lo(count);
+ hdr->wCount_hi = usb_hi(count);
+ return length;
+}
+
+/* ------------------------------------------------------------------ */
+
+int usb_desc_msos(const USBDesc *desc, USBPacket *p,
+ int index, uint8_t *dest, size_t len)
+{
+ void *buf = g_malloc0(4096);
+ int length = 0;
+
+ switch (index) {
+ case 0x0004:
+ length = usb_desc_msos_compat(desc, buf);
+ break;
+ case 0x0005:
+ length = usb_desc_msos_prop(desc, buf);
+ break;
+ }
+
+ if (length > len) {
+ length = len;
+ }
+ memcpy(dest, buf, length);
+ g_free(buf);
+
+ p->actual_length = length;
+ return 0;
+}
diff --git a/hw/usb/desc.c b/hw/usb/desc.c
new file mode 100644
index 00000000..b82c397e
--- /dev/null
+++ b/hw/usb/desc.c
@@ -0,0 +1,804 @@
+#include <ctype.h>
+
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "trace.h"
+
+/* ------------------------------------------------------------------ */
+
+int usb_desc_device(const USBDescID *id, const USBDescDevice *dev,
+ bool msos, uint8_t *dest, size_t len)
+{
+ uint8_t bLength = 0x12;
+ USBDescriptor *d = (void *)dest;
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ d->bLength = bLength;
+ d->bDescriptorType = USB_DT_DEVICE;
+
+ if (msos && dev->bcdUSB < 0x0200) {
+ /*
+ * Version 2.0+ required for microsoft os descriptors to work.
+ * Done this way so msos-desc compat property will handle both
+ * the version and the new descriptors being present.
+ */
+ d->u.device.bcdUSB_lo = usb_lo(0x0200);
+ d->u.device.bcdUSB_hi = usb_hi(0x0200);
+ } else {
+ d->u.device.bcdUSB_lo = usb_lo(dev->bcdUSB);
+ d->u.device.bcdUSB_hi = usb_hi(dev->bcdUSB);
+ }
+ d->u.device.bDeviceClass = dev->bDeviceClass;
+ d->u.device.bDeviceSubClass = dev->bDeviceSubClass;
+ d->u.device.bDeviceProtocol = dev->bDeviceProtocol;
+ d->u.device.bMaxPacketSize0 = dev->bMaxPacketSize0;
+
+ d->u.device.idVendor_lo = usb_lo(id->idVendor);
+ d->u.device.idVendor_hi = usb_hi(id->idVendor);
+ d->u.device.idProduct_lo = usb_lo(id->idProduct);
+ d->u.device.idProduct_hi = usb_hi(id->idProduct);
+ d->u.device.bcdDevice_lo = usb_lo(id->bcdDevice);
+ d->u.device.bcdDevice_hi = usb_hi(id->bcdDevice);
+ d->u.device.iManufacturer = id->iManufacturer;
+ d->u.device.iProduct = id->iProduct;
+ d->u.device.iSerialNumber = id->iSerialNumber;
+
+ d->u.device.bNumConfigurations = dev->bNumConfigurations;
+
+ return bLength;
+}
+
+int usb_desc_device_qualifier(const USBDescDevice *dev,
+ uint8_t *dest, size_t len)
+{
+ uint8_t bLength = 0x0a;
+ USBDescriptor *d = (void *)dest;
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ d->bLength = bLength;
+ d->bDescriptorType = USB_DT_DEVICE_QUALIFIER;
+
+ d->u.device_qualifier.bcdUSB_lo = usb_lo(dev->bcdUSB);
+ d->u.device_qualifier.bcdUSB_hi = usb_hi(dev->bcdUSB);
+ d->u.device_qualifier.bDeviceClass = dev->bDeviceClass;
+ d->u.device_qualifier.bDeviceSubClass = dev->bDeviceSubClass;
+ d->u.device_qualifier.bDeviceProtocol = dev->bDeviceProtocol;
+ d->u.device_qualifier.bMaxPacketSize0 = dev->bMaxPacketSize0;
+ d->u.device_qualifier.bNumConfigurations = dev->bNumConfigurations;
+ d->u.device_qualifier.bReserved = 0;
+
+ return bLength;
+}
+
+int usb_desc_config(const USBDescConfig *conf, int flags,
+ uint8_t *dest, size_t len)
+{
+ uint8_t bLength = 0x09;
+ uint16_t wTotalLength = 0;
+ USBDescriptor *d = (void *)dest;
+ int i, rc;
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ d->bLength = bLength;
+ d->bDescriptorType = USB_DT_CONFIG;
+
+ d->u.config.bNumInterfaces = conf->bNumInterfaces;
+ d->u.config.bConfigurationValue = conf->bConfigurationValue;
+ d->u.config.iConfiguration = conf->iConfiguration;
+ d->u.config.bmAttributes = conf->bmAttributes;
+ d->u.config.bMaxPower = conf->bMaxPower;
+ wTotalLength += bLength;
+
+ /* handle grouped interfaces if any */
+ for (i = 0; i < conf->nif_groups; i++) {
+ rc = usb_desc_iface_group(&(conf->if_groups[i]), flags,
+ dest + wTotalLength,
+ len - wTotalLength);
+ if (rc < 0) {
+ return rc;
+ }
+ wTotalLength += rc;
+ }
+
+ /* handle normal (ungrouped / no IAD) interfaces if any */
+ for (i = 0; i < conf->nif; i++) {
+ rc = usb_desc_iface(conf->ifs + i, flags,
+ dest + wTotalLength, len - wTotalLength);
+ if (rc < 0) {
+ return rc;
+ }
+ wTotalLength += rc;
+ }
+
+ d->u.config.wTotalLength_lo = usb_lo(wTotalLength);
+ d->u.config.wTotalLength_hi = usb_hi(wTotalLength);
+ return wTotalLength;
+}
+
+int usb_desc_iface_group(const USBDescIfaceAssoc *iad, int flags,
+ uint8_t *dest, size_t len)
+{
+ int pos = 0;
+ int i = 0;
+
+ /* handle interface association descriptor */
+ uint8_t bLength = 0x08;
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ dest[0x00] = bLength;
+ dest[0x01] = USB_DT_INTERFACE_ASSOC;
+ dest[0x02] = iad->bFirstInterface;
+ dest[0x03] = iad->bInterfaceCount;
+ dest[0x04] = iad->bFunctionClass;
+ dest[0x05] = iad->bFunctionSubClass;
+ dest[0x06] = iad->bFunctionProtocol;
+ dest[0x07] = iad->iFunction;
+ pos += bLength;
+
+ /* handle associated interfaces in this group */
+ for (i = 0; i < iad->nif; i++) {
+ int rc = usb_desc_iface(&(iad->ifs[i]), flags, dest + pos, len - pos);
+ if (rc < 0) {
+ return rc;
+ }
+ pos += rc;
+ }
+
+ return pos;
+}
+
+int usb_desc_iface(const USBDescIface *iface, int flags,
+ uint8_t *dest, size_t len)
+{
+ uint8_t bLength = 0x09;
+ int i, rc, pos = 0;
+ USBDescriptor *d = (void *)dest;
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ d->bLength = bLength;
+ d->bDescriptorType = USB_DT_INTERFACE;
+
+ d->u.interface.bInterfaceNumber = iface->bInterfaceNumber;
+ d->u.interface.bAlternateSetting = iface->bAlternateSetting;
+ d->u.interface.bNumEndpoints = iface->bNumEndpoints;
+ d->u.interface.bInterfaceClass = iface->bInterfaceClass;
+ d->u.interface.bInterfaceSubClass = iface->bInterfaceSubClass;
+ d->u.interface.bInterfaceProtocol = iface->bInterfaceProtocol;
+ d->u.interface.iInterface = iface->iInterface;
+ pos += bLength;
+
+ for (i = 0; i < iface->ndesc; i++) {
+ rc = usb_desc_other(iface->descs + i, dest + pos, len - pos);
+ if (rc < 0) {
+ return rc;
+ }
+ pos += rc;
+ }
+
+ for (i = 0; i < iface->bNumEndpoints; i++) {
+ rc = usb_desc_endpoint(iface->eps + i, flags, dest + pos, len - pos);
+ if (rc < 0) {
+ return rc;
+ }
+ pos += rc;
+ }
+
+ return pos;
+}
+
+int usb_desc_endpoint(const USBDescEndpoint *ep, int flags,
+ uint8_t *dest, size_t len)
+{
+ uint8_t bLength = ep->is_audio ? 0x09 : 0x07;
+ uint8_t extralen = ep->extra ? ep->extra[0] : 0;
+ uint8_t superlen = (flags & USB_DESC_FLAG_SUPER) ? 0x06 : 0;
+ USBDescriptor *d = (void *)dest;
+
+ if (len < bLength + extralen + superlen) {
+ return -1;
+ }
+
+ d->bLength = bLength;
+ d->bDescriptorType = USB_DT_ENDPOINT;
+
+ d->u.endpoint.bEndpointAddress = ep->bEndpointAddress;
+ d->u.endpoint.bmAttributes = ep->bmAttributes;
+ d->u.endpoint.wMaxPacketSize_lo = usb_lo(ep->wMaxPacketSize);
+ d->u.endpoint.wMaxPacketSize_hi = usb_hi(ep->wMaxPacketSize);
+ d->u.endpoint.bInterval = ep->bInterval;
+ if (ep->is_audio) {
+ d->u.endpoint.bRefresh = ep->bRefresh;
+ d->u.endpoint.bSynchAddress = ep->bSynchAddress;
+ }
+
+ if (superlen) {
+ USBDescriptor *d = (void *)(dest + bLength);
+
+ d->bLength = 0x06;
+ d->bDescriptorType = USB_DT_ENDPOINT_COMPANION;
+
+ d->u.super_endpoint.bMaxBurst = ep->bMaxBurst;
+ d->u.super_endpoint.bmAttributes = ep->bmAttributes_super;
+ d->u.super_endpoint.wBytesPerInterval_lo =
+ usb_lo(ep->wBytesPerInterval);
+ d->u.super_endpoint.wBytesPerInterval_hi =
+ usb_hi(ep->wBytesPerInterval);
+ }
+
+ if (ep->extra) {
+ memcpy(dest + bLength + superlen, ep->extra, extralen);
+ }
+
+ return bLength + extralen + superlen;
+}
+
+int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len)
+{
+ int bLength = desc->length ? desc->length : desc->data[0];
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ memcpy(dest, desc->data, bLength);
+ return bLength;
+}
+
+static int usb_desc_cap_usb2_ext(const USBDesc *desc, uint8_t *dest, size_t len)
+{
+ uint8_t bLength = 0x07;
+ USBDescriptor *d = (void *)dest;
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ d->bLength = bLength;
+ d->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ d->u.cap.bDevCapabilityType = USB_DEV_CAP_USB2_EXT;
+
+ d->u.cap.u.usb2_ext.bmAttributes_1 = (1 << 1); /* LPM */
+ d->u.cap.u.usb2_ext.bmAttributes_2 = 0;
+ d->u.cap.u.usb2_ext.bmAttributes_3 = 0;
+ d->u.cap.u.usb2_ext.bmAttributes_4 = 0;
+
+ return bLength;
+}
+
+static int usb_desc_cap_super(const USBDesc *desc, uint8_t *dest, size_t len)
+{
+ uint8_t bLength = 0x0a;
+ USBDescriptor *d = (void *)dest;
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ d->bLength = bLength;
+ d->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ d->u.cap.bDevCapabilityType = USB_DEV_CAP_SUPERSPEED;
+
+ d->u.cap.u.super.bmAttributes = 0;
+ d->u.cap.u.super.wSpeedsSupported_lo = 0;
+ d->u.cap.u.super.wSpeedsSupported_hi = 0;
+ d->u.cap.u.super.bFunctionalitySupport = 0;
+ d->u.cap.u.super.bU1DevExitLat = 0x0a;
+ d->u.cap.u.super.wU2DevExitLat_lo = 0x20;
+ d->u.cap.u.super.wU2DevExitLat_hi = 0;
+
+ if (desc->full) {
+ d->u.cap.u.super.wSpeedsSupported_lo |= (1 << 1);
+ d->u.cap.u.super.bFunctionalitySupport = 1;
+ }
+ if (desc->high) {
+ d->u.cap.u.super.wSpeedsSupported_lo |= (1 << 2);
+ if (!d->u.cap.u.super.bFunctionalitySupport) {
+ d->u.cap.u.super.bFunctionalitySupport = 2;
+ }
+ }
+ if (desc->super) {
+ d->u.cap.u.super.wSpeedsSupported_lo |= (1 << 3);
+ if (!d->u.cap.u.super.bFunctionalitySupport) {
+ d->u.cap.u.super.bFunctionalitySupport = 3;
+ }
+ }
+
+ return bLength;
+}
+
+static int usb_desc_bos(const USBDesc *desc, uint8_t *dest, size_t len)
+{
+ uint8_t bLength = 0x05;
+ uint16_t wTotalLength = 0;
+ uint8_t bNumDeviceCaps = 0;
+ USBDescriptor *d = (void *)dest;
+ int rc;
+
+ if (len < bLength) {
+ return -1;
+ }
+
+ d->bLength = bLength;
+ d->bDescriptorType = USB_DT_BOS;
+
+ wTotalLength += bLength;
+
+ if (desc->high != NULL) {
+ rc = usb_desc_cap_usb2_ext(desc, dest + wTotalLength,
+ len - wTotalLength);
+ if (rc < 0) {
+ return rc;
+ }
+ wTotalLength += rc;
+ bNumDeviceCaps++;
+ }
+
+ if (desc->super != NULL) {
+ rc = usb_desc_cap_super(desc, dest + wTotalLength,
+ len - wTotalLength);
+ if (rc < 0) {
+ return rc;
+ }
+ wTotalLength += rc;
+ bNumDeviceCaps++;
+ }
+
+ d->u.bos.wTotalLength_lo = usb_lo(wTotalLength);
+ d->u.bos.wTotalLength_hi = usb_hi(wTotalLength);
+ d->u.bos.bNumDeviceCaps = bNumDeviceCaps;
+ return wTotalLength;
+}
+
+/* ------------------------------------------------------------------ */
+
+static void usb_desc_ep_init(USBDevice *dev)
+{
+ const USBDescIface *iface;
+ int i, e, pid, ep;
+
+ usb_ep_init(dev);
+ for (i = 0; i < dev->ninterfaces; i++) {
+ iface = dev->ifaces[i];
+ if (iface == NULL) {
+ continue;
+ }
+ for (e = 0; e < iface->bNumEndpoints; e++) {
+ pid = (iface->eps[e].bEndpointAddress & USB_DIR_IN) ?
+ USB_TOKEN_IN : USB_TOKEN_OUT;
+ ep = iface->eps[e].bEndpointAddress & 0x0f;
+ usb_ep_set_type(dev, pid, ep, iface->eps[e].bmAttributes & 0x03);
+ usb_ep_set_ifnum(dev, pid, ep, iface->bInterfaceNumber);
+ usb_ep_set_max_packet_size(dev, pid, ep,
+ iface->eps[e].wMaxPacketSize);
+ usb_ep_set_max_streams(dev, pid, ep,
+ iface->eps[e].bmAttributes_super);
+ }
+ }
+}
+
+static const USBDescIface *usb_desc_find_interface(USBDevice *dev,
+ int nif, int alt)
+{
+ const USBDescIface *iface;
+ int g, i;
+
+ if (!dev->config) {
+ return NULL;
+ }
+ for (g = 0; g < dev->config->nif_groups; g++) {
+ for (i = 0; i < dev->config->if_groups[g].nif; i++) {
+ iface = &dev->config->if_groups[g].ifs[i];
+ if (iface->bInterfaceNumber == nif &&
+ iface->bAlternateSetting == alt) {
+ return iface;
+ }
+ }
+ }
+ for (i = 0; i < dev->config->nif; i++) {
+ iface = &dev->config->ifs[i];
+ if (iface->bInterfaceNumber == nif &&
+ iface->bAlternateSetting == alt) {
+ return iface;
+ }
+ }
+ return NULL;
+}
+
+static int usb_desc_set_interface(USBDevice *dev, int index, int value)
+{
+ const USBDescIface *iface;
+ int old;
+
+ iface = usb_desc_find_interface(dev, index, value);
+ if (iface == NULL) {
+ return -1;
+ }
+
+ old = dev->altsetting[index];
+ dev->altsetting[index] = value;
+ dev->ifaces[index] = iface;
+ usb_desc_ep_init(dev);
+
+ if (old != value) {
+ usb_device_set_interface(dev, index, old, value);
+ }
+ return 0;
+}
+
+static int usb_desc_set_config(USBDevice *dev, int value)
+{
+ int i;
+
+ if (value == 0) {
+ dev->configuration = 0;
+ dev->ninterfaces = 0;
+ dev->config = NULL;
+ } else {
+ for (i = 0; i < dev->device->bNumConfigurations; i++) {
+ if (dev->device->confs[i].bConfigurationValue == value) {
+ dev->configuration = value;
+ dev->ninterfaces = dev->device->confs[i].bNumInterfaces;
+ dev->config = dev->device->confs + i;
+ assert(dev->ninterfaces <= USB_MAX_INTERFACES);
+ }
+ }
+ if (i < dev->device->bNumConfigurations) {
+ return -1;
+ }
+ }
+
+ for (i = 0; i < dev->ninterfaces; i++) {
+ usb_desc_set_interface(dev, i, 0);
+ }
+ for (; i < USB_MAX_INTERFACES; i++) {
+ dev->altsetting[i] = 0;
+ dev->ifaces[i] = NULL;
+ }
+
+ return 0;
+}
+
+static void usb_desc_setdefaults(USBDevice *dev)
+{
+ const USBDesc *desc = usb_device_get_usb_desc(dev);
+
+ assert(desc != NULL);
+ switch (dev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ dev->device = desc->full;
+ break;
+ case USB_SPEED_HIGH:
+ dev->device = desc->high;
+ break;
+ case USB_SPEED_SUPER:
+ dev->device = desc->super;
+ break;
+ }
+ usb_desc_set_config(dev, 0);
+}
+
+void usb_desc_init(USBDevice *dev)
+{
+ const USBDesc *desc = usb_device_get_usb_desc(dev);
+
+ assert(desc != NULL);
+ dev->speed = USB_SPEED_FULL;
+ dev->speedmask = 0;
+ if (desc->full) {
+ dev->speedmask |= USB_SPEED_MASK_FULL;
+ }
+ if (desc->high) {
+ dev->speedmask |= USB_SPEED_MASK_HIGH;
+ }
+ if (desc->super) {
+ dev->speedmask |= USB_SPEED_MASK_SUPER;
+ }
+ if (desc->msos && (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_ENABLE))) {
+ dev->flags |= (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE);
+ usb_desc_set_string(dev, 0xee, "MSFT100Q");
+ }
+ usb_desc_setdefaults(dev);
+}
+
+void usb_desc_attach(USBDevice *dev)
+{
+ usb_desc_setdefaults(dev);
+}
+
+void usb_desc_set_string(USBDevice *dev, uint8_t index, const char *str)
+{
+ USBDescString *s;
+
+ QLIST_FOREACH(s, &dev->strings, next) {
+ if (s->index == index) {
+ break;
+ }
+ }
+ if (s == NULL) {
+ s = g_malloc0(sizeof(*s));
+ s->index = index;
+ QLIST_INSERT_HEAD(&dev->strings, s, next);
+ }
+ g_free(s->str);
+ s->str = g_strdup(str);
+}
+
+/*
+ * This function creates a serial number for a usb device.
+ * The serial number should:
+ * (a) Be unique within the virtual machine.
+ * (b) Be constant, so you don't get a new one each
+ * time the guest is started.
+ * So we are using the physical location to generate a serial number
+ * from it. It has three pieces: First a fixed, device-specific
+ * prefix. Second the device path of the host controller (which is
+ * the pci address in most cases). Third the physical port path.
+ * Results in serial numbers like this: "314159-0000:00:1d.7-3".
+ */
+void usb_desc_create_serial(USBDevice *dev)
+{
+ DeviceState *hcd = dev->qdev.parent_bus->parent;
+ const USBDesc *desc = usb_device_get_usb_desc(dev);
+ int index = desc->id.iSerialNumber;
+ char serial[64];
+ char *path;
+ int dst;
+
+ if (dev->serial) {
+ /* 'serial' usb bus property has priority if present */
+ usb_desc_set_string(dev, index, dev->serial);
+ return;
+ }
+
+ assert(index != 0 && desc->str[index] != NULL);
+ dst = snprintf(serial, sizeof(serial), "%s", desc->str[index]);
+ path = qdev_get_dev_path(hcd);
+ if (path) {
+ dst += snprintf(serial+dst, sizeof(serial)-dst, "-%s", path);
+ }
+ dst += snprintf(serial+dst, sizeof(serial)-dst, "-%s", dev->port->path);
+ usb_desc_set_string(dev, index, serial);
+}
+
+const char *usb_desc_get_string(USBDevice *dev, uint8_t index)
+{
+ USBDescString *s;
+
+ QLIST_FOREACH(s, &dev->strings, next) {
+ if (s->index == index) {
+ return s->str;
+ }
+ }
+ return NULL;
+}
+
+int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len)
+{
+ uint8_t bLength, pos, i;
+ const char *str;
+
+ if (len < 4) {
+ return -1;
+ }
+
+ if (index == 0) {
+ /* language ids */
+ dest[0] = 4;
+ dest[1] = USB_DT_STRING;
+ dest[2] = 0x09;
+ dest[3] = 0x04;
+ return 4;
+ }
+
+ str = usb_desc_get_string(dev, index);
+ if (str == NULL) {
+ str = usb_device_get_usb_desc(dev)->str[index];
+ if (str == NULL) {
+ return 0;
+ }
+ }
+
+ bLength = strlen(str) * 2 + 2;
+ dest[0] = bLength;
+ dest[1] = USB_DT_STRING;
+ i = 0; pos = 2;
+ while (pos+1 < bLength && pos+1 < len) {
+ dest[pos++] = str[i++];
+ dest[pos++] = 0;
+ }
+ return pos;
+}
+
+int usb_desc_get_descriptor(USBDevice *dev, USBPacket *p,
+ int value, uint8_t *dest, size_t len)
+{
+ bool msos = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE));
+ const USBDesc *desc = usb_device_get_usb_desc(dev);
+ const USBDescDevice *other_dev;
+ uint8_t buf[256];
+ uint8_t type = value >> 8;
+ uint8_t index = value & 0xff;
+ int flags, ret = -1;
+
+ if (dev->speed == USB_SPEED_HIGH) {
+ other_dev = usb_device_get_usb_desc(dev)->full;
+ } else {
+ other_dev = usb_device_get_usb_desc(dev)->high;
+ }
+
+ flags = 0;
+ if (dev->device->bcdUSB >= 0x0300) {
+ flags |= USB_DESC_FLAG_SUPER;
+ }
+
+ switch(type) {
+ case USB_DT_DEVICE:
+ ret = usb_desc_device(&desc->id, dev->device, msos, buf, sizeof(buf));
+ trace_usb_desc_device(dev->addr, len, ret);
+ break;
+ case USB_DT_CONFIG:
+ if (index < dev->device->bNumConfigurations) {
+ ret = usb_desc_config(dev->device->confs + index, flags,
+ buf, sizeof(buf));
+ }
+ trace_usb_desc_config(dev->addr, index, len, ret);
+ break;
+ case USB_DT_STRING:
+ ret = usb_desc_string(dev, index, buf, sizeof(buf));
+ trace_usb_desc_string(dev->addr, index, len, ret);
+ break;
+ case USB_DT_DEVICE_QUALIFIER:
+ if (other_dev != NULL) {
+ ret = usb_desc_device_qualifier(other_dev, buf, sizeof(buf));
+ }
+ trace_usb_desc_device_qualifier(dev->addr, len, ret);
+ break;
+ case USB_DT_OTHER_SPEED_CONFIG:
+ if (other_dev != NULL && index < other_dev->bNumConfigurations) {
+ ret = usb_desc_config(other_dev->confs + index, flags,
+ buf, sizeof(buf));
+ buf[0x01] = USB_DT_OTHER_SPEED_CONFIG;
+ }
+ trace_usb_desc_other_speed_config(dev->addr, index, len, ret);
+ break;
+ case USB_DT_BOS:
+ ret = usb_desc_bos(desc, buf, sizeof(buf));
+ trace_usb_desc_bos(dev->addr, len, ret);
+ break;
+
+ case USB_DT_DEBUG:
+ /* ignore silently */
+ break;
+
+ default:
+ fprintf(stderr, "%s: %d unknown type %d (len %zd)\n", __FUNCTION__,
+ dev->addr, type, len);
+ break;
+ }
+
+ if (ret > 0) {
+ if (ret > len) {
+ ret = len;
+ }
+ memcpy(dest, buf, ret);
+ p->actual_length = ret;
+ ret = 0;
+ }
+ return ret;
+}
+
+int usb_desc_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ bool msos = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE));
+ const USBDesc *desc = usb_device_get_usb_desc(dev);
+ int ret = -1;
+
+ assert(desc != NULL);
+ switch(request) {
+ case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+ dev->addr = value;
+ trace_usb_set_addr(dev->addr);
+ ret = 0;
+ break;
+
+ case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+ ret = usb_desc_get_descriptor(dev, p, value, data, length);
+ break;
+
+ case DeviceRequest | USB_REQ_GET_CONFIGURATION:
+ /*
+ * 9.4.2: 0 should be returned if the device is unconfigured, otherwise
+ * the non zero value of bConfigurationValue.
+ */
+ data[0] = dev->config ? dev->config->bConfigurationValue : 0;
+ p->actual_length = 1;
+ ret = 0;
+ break;
+ case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+ ret = usb_desc_set_config(dev, value);
+ trace_usb_set_config(dev->addr, value, ret);
+ break;
+
+ case DeviceRequest | USB_REQ_GET_STATUS: {
+ const USBDescConfig *config = dev->config ?
+ dev->config : &dev->device->confs[0];
+
+ data[0] = 0;
+ /*
+ * Default state: Device behavior when this request is received while
+ * the device is in the Default state is not specified.
+ * We return the same value that a configured device would return if
+ * it used the first configuration.
+ */
+ if (config->bmAttributes & USB_CFG_ATT_SELFPOWER) {
+ data[0] |= 1 << USB_DEVICE_SELF_POWERED;
+ }
+ if (dev->remote_wakeup) {
+ data[0] |= 1 << USB_DEVICE_REMOTE_WAKEUP;
+ }
+ data[1] = 0x00;
+ p->actual_length = 2;
+ ret = 0;
+ break;
+ }
+ case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
+ if (value == USB_DEVICE_REMOTE_WAKEUP) {
+ dev->remote_wakeup = 0;
+ ret = 0;
+ }
+ trace_usb_clear_device_feature(dev->addr, value, ret);
+ break;
+ case DeviceOutRequest | USB_REQ_SET_FEATURE:
+ if (value == USB_DEVICE_REMOTE_WAKEUP) {
+ dev->remote_wakeup = 1;
+ ret = 0;
+ }
+ trace_usb_set_device_feature(dev->addr, value, ret);
+ break;
+
+ case InterfaceRequest | USB_REQ_GET_INTERFACE:
+ if (index < 0 || index >= dev->ninterfaces) {
+ break;
+ }
+ data[0] = dev->altsetting[index];
+ p->actual_length = 1;
+ ret = 0;
+ break;
+ case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+ ret = usb_desc_set_interface(dev, index, value);
+ trace_usb_set_interface(dev->addr, index, value, ret);
+ break;
+
+ case VendorDeviceRequest | 'Q':
+ if (msos) {
+ ret = usb_desc_msos(desc, p, index, data, length);
+ trace_usb_desc_msos(dev->addr, index, length, ret);
+ }
+ break;
+ case VendorInterfaceRequest | 'Q':
+ if (msos) {
+ ret = usb_desc_msos(desc, p, index, data, length);
+ trace_usb_desc_msos(dev->addr, index, length, ret);
+ }
+ break;
+
+ }
+ return ret;
+}
diff --git a/hw/usb/desc.h b/hw/usb/desc.h
new file mode 100644
index 00000000..8e8db03a
--- /dev/null
+++ b/hw/usb/desc.h
@@ -0,0 +1,245 @@
+#ifndef QEMU_HW_USB_DESC_H
+#define QEMU_HW_USB_DESC_H
+
+#include <inttypes.h>
+#include <wchar.h>
+
+/* binary representation */
+typedef struct USBDescriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ union {
+ struct {
+ uint8_t bcdUSB_lo;
+ uint8_t bcdUSB_hi;
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+ uint8_t bMaxPacketSize0;
+ uint8_t idVendor_lo;
+ uint8_t idVendor_hi;
+ uint8_t idProduct_lo;
+ uint8_t idProduct_hi;
+ uint8_t bcdDevice_lo;
+ uint8_t bcdDevice_hi;
+ uint8_t iManufacturer;
+ uint8_t iProduct;
+ uint8_t iSerialNumber;
+ uint8_t bNumConfigurations;
+ } device;
+ struct {
+ uint8_t bcdUSB_lo;
+ uint8_t bcdUSB_hi;
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+ uint8_t bMaxPacketSize0;
+ uint8_t bNumConfigurations;
+ uint8_t bReserved;
+ } device_qualifier;
+ struct {
+ uint8_t wTotalLength_lo;
+ uint8_t wTotalLength_hi;
+ uint8_t bNumInterfaces;
+ uint8_t bConfigurationValue;
+ uint8_t iConfiguration;
+ uint8_t bmAttributes;
+ uint8_t bMaxPower;
+ } config;
+ struct {
+ uint8_t bInterfaceNumber;
+ uint8_t bAlternateSetting;
+ uint8_t bNumEndpoints;
+ uint8_t bInterfaceClass;
+ uint8_t bInterfaceSubClass;
+ uint8_t bInterfaceProtocol;
+ uint8_t iInterface;
+ } interface;
+ struct {
+ uint8_t bEndpointAddress;
+ uint8_t bmAttributes;
+ uint8_t wMaxPacketSize_lo;
+ uint8_t wMaxPacketSize_hi;
+ uint8_t bInterval;
+ uint8_t bRefresh; /* only audio ep */
+ uint8_t bSynchAddress; /* only audio ep */
+ } endpoint;
+ struct {
+ uint8_t bMaxBurst;
+ uint8_t bmAttributes;
+ uint8_t wBytesPerInterval_lo;
+ uint8_t wBytesPerInterval_hi;
+ } super_endpoint;
+ struct {
+ uint8_t wTotalLength_lo;
+ uint8_t wTotalLength_hi;
+ uint8_t bNumDeviceCaps;
+ } bos;
+ struct {
+ uint8_t bDevCapabilityType;
+ union {
+ struct {
+ uint8_t bmAttributes_1;
+ uint8_t bmAttributes_2;
+ uint8_t bmAttributes_3;
+ uint8_t bmAttributes_4;
+ } usb2_ext;
+ struct {
+ uint8_t bmAttributes;
+ uint8_t wSpeedsSupported_lo;
+ uint8_t wSpeedsSupported_hi;
+ uint8_t bFunctionalitySupport;
+ uint8_t bU1DevExitLat;
+ uint8_t wU2DevExitLat_lo;
+ uint8_t wU2DevExitLat_hi;
+ } super;
+ } u;
+ } cap;
+ } u;
+} QEMU_PACKED USBDescriptor;
+
+struct USBDescID {
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice;
+ uint8_t iManufacturer;
+ uint8_t iProduct;
+ uint8_t iSerialNumber;
+};
+
+struct USBDescDevice {
+ uint16_t bcdUSB;
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+ uint8_t bMaxPacketSize0;
+ uint8_t bNumConfigurations;
+
+ const USBDescConfig *confs;
+};
+
+struct USBDescConfig {
+ uint8_t bNumInterfaces;
+ uint8_t bConfigurationValue;
+ uint8_t iConfiguration;
+ uint8_t bmAttributes;
+ uint8_t bMaxPower;
+
+ /* grouped interfaces */
+ uint8_t nif_groups;
+ const USBDescIfaceAssoc *if_groups;
+
+ /* "normal" interfaces */
+ uint8_t nif;
+ const USBDescIface *ifs;
+};
+
+/* conceptually an Interface Association Descriptor, and releated interfaces */
+struct USBDescIfaceAssoc {
+ uint8_t bFirstInterface;
+ uint8_t bInterfaceCount;
+ uint8_t bFunctionClass;
+ uint8_t bFunctionSubClass;
+ uint8_t bFunctionProtocol;
+ uint8_t iFunction;
+
+ uint8_t nif;
+ const USBDescIface *ifs;
+};
+
+struct USBDescIface {
+ uint8_t bInterfaceNumber;
+ uint8_t bAlternateSetting;
+ uint8_t bNumEndpoints;
+ uint8_t bInterfaceClass;
+ uint8_t bInterfaceSubClass;
+ uint8_t bInterfaceProtocol;
+ uint8_t iInterface;
+
+ uint8_t ndesc;
+ USBDescOther *descs;
+ USBDescEndpoint *eps;
+};
+
+struct USBDescEndpoint {
+ uint8_t bEndpointAddress;
+ uint8_t bmAttributes;
+ uint16_t wMaxPacketSize;
+ uint8_t bInterval;
+ uint8_t bRefresh;
+ uint8_t bSynchAddress;
+
+ uint8_t is_audio; /* has bRefresh + bSynchAddress */
+ uint8_t *extra;
+
+ /* superspeed endpoint companion */
+ uint8_t bMaxBurst;
+ uint8_t bmAttributes_super;
+ uint16_t wBytesPerInterval;
+};
+
+struct USBDescOther {
+ uint8_t length;
+ const uint8_t *data;
+};
+
+struct USBDescMSOS {
+ const char *CompatibleID;
+ const wchar_t *Label;
+ bool SelectiveSuspendEnabled;
+};
+
+typedef const char *USBDescStrings[256];
+
+struct USBDesc {
+ USBDescID id;
+ const USBDescDevice *full;
+ const USBDescDevice *high;
+ const USBDescDevice *super;
+ const char* const *str;
+ const USBDescMSOS *msos;
+};
+
+#define USB_DESC_FLAG_SUPER (1 << 1)
+
+/* little helpers */
+static inline uint8_t usb_lo(uint16_t val)
+{
+ return val & 0xff;
+}
+
+static inline uint8_t usb_hi(uint16_t val)
+{
+ return (val >> 8) & 0xff;
+}
+
+/* generate usb packages from structs */
+int usb_desc_device(const USBDescID *id, const USBDescDevice *dev,
+ bool msos, uint8_t *dest, size_t len);
+int usb_desc_device_qualifier(const USBDescDevice *dev,
+ uint8_t *dest, size_t len);
+int usb_desc_config(const USBDescConfig *conf, int flags,
+ uint8_t *dest, size_t len);
+int usb_desc_iface_group(const USBDescIfaceAssoc *iad, int flags,
+ uint8_t *dest, size_t len);
+int usb_desc_iface(const USBDescIface *iface, int flags,
+ uint8_t *dest, size_t len);
+int usb_desc_endpoint(const USBDescEndpoint *ep, int flags,
+ uint8_t *dest, size_t len);
+int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len);
+int usb_desc_msos(const USBDesc *desc, USBPacket *p,
+ int index, uint8_t *dest, size_t len);
+
+/* control message emulation helpers */
+void usb_desc_init(USBDevice *dev);
+void usb_desc_attach(USBDevice *dev);
+void usb_desc_set_string(USBDevice *dev, uint8_t index, const char *str);
+void usb_desc_create_serial(USBDevice *dev);
+const char *usb_desc_get_string(USBDevice *dev, uint8_t index);
+int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len);
+int usb_desc_get_descriptor(USBDevice *dev, USBPacket *p,
+ int value, uint8_t *dest, size_t len);
+int usb_desc_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data);
+
+#endif /* QEMU_HW_USB_DESC_H */
diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c
new file mode 100644
index 00000000..f092bb84
--- /dev/null
+++ b/hw/usb/dev-audio.c
@@ -0,0 +1,702 @@
+/*
+ * QEMU USB audio device
+ *
+ * written by:
+ * H. Peter Anvin <hpa@linux.intel.com>
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * lousely based on usb net device code which is:
+ *
+ * Copyright (c) 2006 Thomas Sailer
+ * Copyright (c) 2008 Andrzej Zaborowski
+ *
+ * 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.
+ */
+
+#include "qemu-common.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "hw/hw.h"
+#include "audio/audio.h"
+
+#define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */
+#define USBAUDIO_PRODUCT_NUM 0x0002
+
+#define DEV_CONFIG_VALUE 1 /* The one and only */
+
+/* Descriptor subtypes for AC interfaces */
+#define DST_AC_HEADER 1
+#define DST_AC_INPUT_TERMINAL 2
+#define DST_AC_OUTPUT_TERMINAL 3
+#define DST_AC_FEATURE_UNIT 6
+/* Descriptor subtypes for AS interfaces */
+#define DST_AS_GENERAL 1
+#define DST_AS_FORMAT_TYPE 2
+/* Descriptor subtypes for endpoints */
+#define DST_EP_GENERAL 1
+
+enum usb_audio_strings {
+ STRING_NULL,
+ STRING_MANUFACTURER,
+ STRING_PRODUCT,
+ STRING_SERIALNUMBER,
+ STRING_CONFIG,
+ STRING_USBAUDIO_CONTROL,
+ STRING_INPUT_TERMINAL,
+ STRING_FEATURE_UNIT,
+ STRING_OUTPUT_TERMINAL,
+ STRING_NULL_STREAM,
+ STRING_REAL_STREAM,
+};
+
+static const USBDescStrings usb_audio_stringtable = {
+ [STRING_MANUFACTURER] = "QEMU",
+ [STRING_PRODUCT] = "QEMU USB Audio",
+ [STRING_SERIALNUMBER] = "1",
+ [STRING_CONFIG] = "Audio Configuration",
+ [STRING_USBAUDIO_CONTROL] = "Audio Device",
+ [STRING_INPUT_TERMINAL] = "Audio Output Pipe",
+ [STRING_FEATURE_UNIT] = "Audio Output Volume Control",
+ [STRING_OUTPUT_TERMINAL] = "Audio Output Terminal",
+ [STRING_NULL_STREAM] = "Audio Output - Disabled",
+ [STRING_REAL_STREAM] = "Audio Output - 48 kHz Stereo",
+};
+
+#define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff)
+#define U24(x) U16(x), (((x) >> 16) & 0xff)
+#define U32(x) U24(x), (((x) >> 24) & 0xff)
+
+/*
+ * A Basic Audio Device uses these specific values
+ */
+#define USBAUDIO_PACKET_SIZE 192
+#define USBAUDIO_SAMPLE_RATE 48000
+#define USBAUDIO_PACKET_INTERVAL 1
+
+static const USBDescIface desc_iface[] = {
+ {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL,
+ .bInterfaceProtocol = 0x04,
+ .iInterface = STRING_USBAUDIO_CONTROL,
+ .ndesc = 4,
+ .descs = (USBDescOther[]) {
+ {
+ /* Headphone Class-Specific AC Interface Header Descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ DST_AC_HEADER, /* u8 bDescriptorSubtype */
+ U16(0x0100), /* u16 bcdADC */
+ U16(0x2b), /* u16 wTotalLength */
+ 0x01, /* u8 bInCollection */
+ 0x01, /* u8 baInterfaceNr */
+ }
+ },{
+ /* Generic Stereo Input Terminal ID1 Descriptor */
+ .data = (uint8_t[]) {
+ 0x0c, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ DST_AC_INPUT_TERMINAL, /* u8 bDescriptorSubtype */
+ 0x01, /* u8 bTerminalID */
+ U16(0x0101), /* u16 wTerminalType */
+ 0x00, /* u8 bAssocTerminal */
+ 0x02, /* u16 bNrChannels */
+ U16(0x0003), /* u16 wChannelConfig */
+ 0x00, /* u8 iChannelNames */
+ STRING_INPUT_TERMINAL, /* u8 iTerminal */
+ }
+ },{
+ /* Generic Stereo Feature Unit ID2 Descriptor */
+ .data = (uint8_t[]) {
+ 0x0d, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ DST_AC_FEATURE_UNIT, /* u8 bDescriptorSubtype */
+ 0x02, /* u8 bUnitID */
+ 0x01, /* u8 bSourceID */
+ 0x02, /* u8 bControlSize */
+ U16(0x0001), /* u16 bmaControls(0) */
+ U16(0x0002), /* u16 bmaControls(1) */
+ U16(0x0002), /* u16 bmaControls(2) */
+ STRING_FEATURE_UNIT, /* u8 iFeature */
+ }
+ },{
+ /* Headphone Ouptut Terminal ID3 Descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ DST_AC_OUTPUT_TERMINAL, /* u8 bDescriptorSubtype */
+ 0x03, /* u8 bUnitID */
+ U16(0x0301), /* u16 wTerminalType (SPK) */
+ 0x00, /* u8 bAssocTerminal */
+ 0x02, /* u8 bSourceID */
+ STRING_OUTPUT_TERMINAL, /* u8 iTerminal */
+ }
+ }
+ },
+ },{
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
+ .iInterface = STRING_NULL_STREAM,
+ },{
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 1,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
+ .iInterface = STRING_REAL_STREAM,
+ .ndesc = 2,
+ .descs = (USBDescOther[]) {
+ {
+ /* Headphone Class-specific AS General Interface Descriptor */
+ .data = (uint8_t[]) {
+ 0x07, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ DST_AS_GENERAL, /* u8 bDescriptorSubtype */
+ 0x01, /* u8 bTerminalLink */
+ 0x00, /* u8 bDelay */
+ 0x01, 0x00, /* u16 wFormatTag */
+ }
+ },{
+ /* Headphone Type I Format Type Descriptor */
+ .data = (uint8_t[]) {
+ 0x0b, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ DST_AS_FORMAT_TYPE, /* u8 bDescriptorSubtype */
+ 0x01, /* u8 bFormatType */
+ 0x02, /* u8 bNrChannels */
+ 0x02, /* u8 bSubFrameSize */
+ 0x10, /* u8 bBitResolution */
+ 0x01, /* u8 bSamFreqType */
+ U24(USBAUDIO_SAMPLE_RATE), /* u24 tSamFreq */
+ }
+ }
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | 0x01,
+ .bmAttributes = 0x0d,
+ .wMaxPacketSize = USBAUDIO_PACKET_SIZE,
+ .bInterval = 1,
+ .is_audio = 1,
+ /* Stereo Headphone Class-specific
+ AS Audio Data Endpoint Descriptor */
+ .extra = (uint8_t[]) {
+ 0x07, /* u8 bLength */
+ USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */
+ DST_EP_GENERAL, /* u8 bDescriptorSubtype */
+ 0x00, /* u8 bmAttributes */
+ 0x00, /* u8 bLockDelayUnits */
+ U16(0x0000), /* u16 wLockDelay */
+ },
+ },
+ }
+ }
+};
+
+static const USBDescDevice desc_device = {
+ .bcdUSB = 0x0100,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 2,
+ .bConfigurationValue = DEV_CONFIG_VALUE,
+ .iConfiguration = STRING_CONFIG,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .bMaxPower = 0x32,
+ .nif = ARRAY_SIZE(desc_iface),
+ .ifs = desc_iface,
+ },
+ },
+};
+
+static const USBDesc desc_audio = {
+ .id = {
+ .idVendor = USBAUDIO_VENDOR_NUM,
+ .idProduct = USBAUDIO_PRODUCT_NUM,
+ .bcdDevice = 0,
+ .iManufacturer = STRING_MANUFACTURER,
+ .iProduct = STRING_PRODUCT,
+ .iSerialNumber = STRING_SERIALNUMBER,
+ },
+ .full = &desc_device,
+ .str = usb_audio_stringtable,
+};
+
+/*
+ * A USB audio device supports an arbitrary number of alternate
+ * interface settings for each interface. Each corresponds to a block
+ * diagram of parameterized blocks. This can thus refer to things like
+ * number of channels, data rates, or in fact completely different
+ * block diagrams. Alternative setting 0 is always the null block diagram,
+ * which is used by a disabled device.
+ */
+enum usb_audio_altset {
+ ALTSET_OFF = 0x00, /* No endpoint */
+ ALTSET_ON = 0x01, /* Single endpoint */
+};
+
+/*
+ * Class-specific control requests
+ */
+#define CR_SET_CUR 0x01
+#define CR_GET_CUR 0x81
+#define CR_SET_MIN 0x02
+#define CR_GET_MIN 0x82
+#define CR_SET_MAX 0x03
+#define CR_GET_MAX 0x83
+#define CR_SET_RES 0x04
+#define CR_GET_RES 0x84
+#define CR_SET_MEM 0x05
+#define CR_GET_MEM 0x85
+#define CR_GET_STAT 0xff
+
+/*
+ * Feature Unit Control Selectors
+ */
+#define MUTE_CONTROL 0x01
+#define VOLUME_CONTROL 0x02
+#define BASS_CONTROL 0x03
+#define MID_CONTROL 0x04
+#define TREBLE_CONTROL 0x05
+#define GRAPHIC_EQUALIZER_CONTROL 0x06
+#define AUTOMATIC_GAIN_CONTROL 0x07
+#define DELAY_CONTROL 0x08
+#define BASS_BOOST_CONTROL 0x09
+#define LOUDNESS_CONTROL 0x0a
+
+/*
+ * buffering
+ */
+
+struct streambuf {
+ uint8_t *data;
+ uint32_t size;
+ uint32_t prod;
+ uint32_t cons;
+};
+
+static void streambuf_init(struct streambuf *buf, uint32_t size)
+{
+ g_free(buf->data);
+ buf->size = size - (size % USBAUDIO_PACKET_SIZE);
+ buf->data = g_malloc(buf->size);
+ buf->prod = 0;
+ buf->cons = 0;
+}
+
+static void streambuf_fini(struct streambuf *buf)
+{
+ g_free(buf->data);
+ buf->data = NULL;
+}
+
+static int streambuf_put(struct streambuf *buf, USBPacket *p)
+{
+ uint32_t free = buf->size - (buf->prod - buf->cons);
+
+ if (!free) {
+ return 0;
+ }
+ assert(free >= USBAUDIO_PACKET_SIZE);
+ usb_packet_copy(p, buf->data + (buf->prod % buf->size),
+ USBAUDIO_PACKET_SIZE);
+ buf->prod += USBAUDIO_PACKET_SIZE;
+ return USBAUDIO_PACKET_SIZE;
+}
+
+static uint8_t *streambuf_get(struct streambuf *buf)
+{
+ uint32_t used = buf->prod - buf->cons;
+ uint8_t *data;
+
+ if (!used) {
+ return NULL;
+ }
+ assert(used >= USBAUDIO_PACKET_SIZE);
+ data = buf->data + (buf->cons % buf->size);
+ buf->cons += USBAUDIO_PACKET_SIZE;
+ return data;
+}
+
+typedef struct USBAudioState {
+ /* qemu interfaces */
+ USBDevice dev;
+ QEMUSoundCard card;
+
+ /* state */
+ struct {
+ enum usb_audio_altset altset;
+ struct audsettings as;
+ SWVoiceOut *voice;
+ bool mute;
+ uint8_t vol[2];
+ struct streambuf buf;
+ } out;
+
+ /* properties */
+ uint32_t debug;
+ uint32_t buffer;
+} USBAudioState;
+
+#define TYPE_USB_AUDIO "usb-audio"
+#define USB_AUDIO(obj) OBJECT_CHECK(USBAudioState, (obj), TYPE_USB_AUDIO)
+
+static void output_callback(void *opaque, int avail)
+{
+ USBAudioState *s = opaque;
+ uint8_t *data;
+
+ for (;;) {
+ if (avail < USBAUDIO_PACKET_SIZE) {
+ return;
+ }
+ data = streambuf_get(&s->out.buf);
+ if (!data) {
+ return;
+ }
+ AUD_write(s->out.voice, data, USBAUDIO_PACKET_SIZE);
+ avail -= USBAUDIO_PACKET_SIZE;
+ }
+}
+
+static int usb_audio_set_output_altset(USBAudioState *s, int altset)
+{
+ switch (altset) {
+ case ALTSET_OFF:
+ streambuf_init(&s->out.buf, s->buffer);
+ AUD_set_active_out(s->out.voice, false);
+ break;
+ case ALTSET_ON:
+ AUD_set_active_out(s->out.voice, true);
+ break;
+ default:
+ return -1;
+ }
+
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: set interface %d\n", altset);
+ }
+ s->out.altset = altset;
+ return 0;
+}
+
+/*
+ * Note: we arbitrarily map the volume control range onto -inf..+8 dB
+ */
+#define ATTRIB_ID(cs, attrib, idif) \
+ (((cs) << 24) | ((attrib) << 16) | (idif))
+
+static int usb_audio_get_control(USBAudioState *s, uint8_t attrib,
+ uint16_t cscn, uint16_t idif,
+ int length, uint8_t *data)
+{
+ uint8_t cs = cscn >> 8;
+ uint8_t cn = cscn - 1; /* -1 for the non-present master control */
+ uint32_t aid = ATTRIB_ID(cs, attrib, idif);
+ int ret = USB_RET_STALL;
+
+ switch (aid) {
+ case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200):
+ data[0] = s->out.mute;
+ ret = 1;
+ break;
+ case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200):
+ if (cn < 2) {
+ uint16_t vol = (s->out.vol[cn] * 0x8800 + 127) / 255 + 0x8000;
+ data[0] = vol;
+ data[1] = vol >> 8;
+ ret = 2;
+ }
+ break;
+ case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200):
+ if (cn < 2) {
+ data[0] = 0x01;
+ data[1] = 0x80;
+ ret = 2;
+ }
+ break;
+ case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200):
+ if (cn < 2) {
+ data[0] = 0x00;
+ data[1] = 0x08;
+ ret = 2;
+ }
+ break;
+ case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200):
+ if (cn < 2) {
+ data[0] = 0x88;
+ data[1] = 0x00;
+ ret = 2;
+ }
+ break;
+ }
+
+ return ret;
+}
+static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
+ uint16_t cscn, uint16_t idif,
+ int length, uint8_t *data)
+{
+ uint8_t cs = cscn >> 8;
+ uint8_t cn = cscn - 1; /* -1 for the non-present master control */
+ uint32_t aid = ATTRIB_ID(cs, attrib, idif);
+ int ret = USB_RET_STALL;
+ bool set_vol = false;
+
+ switch (aid) {
+ case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200):
+ s->out.mute = data[0] & 1;
+ set_vol = true;
+ ret = 0;
+ break;
+ case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200):
+ if (cn < 2) {
+ uint16_t vol = data[0] + (data[1] << 8);
+
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol);
+ }
+
+ vol -= 0x8000;
+ vol = (vol * 255 + 0x4400) / 0x8800;
+ if (vol > 255) {
+ vol = 255;
+ }
+
+ s->out.vol[cn] = vol;
+ set_vol = true;
+ ret = 0;
+ }
+ break;
+ }
+
+ if (set_vol) {
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n",
+ s->out.mute, s->out.vol[0], s->out.vol[1]);
+ }
+ AUD_set_volume_out(s->out.voice, s->out.mute,
+ s->out.vol[0], s->out.vol[1]);
+ }
+
+ return ret;
+}
+
+static void usb_audio_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index,
+ int length, uint8_t *data)
+{
+ USBAudioState *s = USB_AUDIO(dev);
+ int ret = 0;
+
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: control transaction: "
+ "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n",
+ request, value, index, length);
+ }
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch (request) {
+ case ClassInterfaceRequest | CR_GET_CUR:
+ case ClassInterfaceRequest | CR_GET_MIN:
+ case ClassInterfaceRequest | CR_GET_MAX:
+ case ClassInterfaceRequest | CR_GET_RES:
+ ret = usb_audio_get_control(s, request & 0xff, value, index,
+ length, data);
+ if (ret < 0) {
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: fail: get control\n");
+ }
+ goto fail;
+ }
+ p->actual_length = ret;
+ break;
+
+ case ClassInterfaceOutRequest | CR_SET_CUR:
+ case ClassInterfaceOutRequest | CR_SET_MIN:
+ case ClassInterfaceOutRequest | CR_SET_MAX:
+ case ClassInterfaceOutRequest | CR_SET_RES:
+ ret = usb_audio_set_control(s, request & 0xff, value, index,
+ length, data);
+ if (ret < 0) {
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: fail: set control\n");
+ }
+ goto fail;
+ }
+ break;
+
+ default:
+fail:
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: failed control transaction: "
+ "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n",
+ request, value, index, length);
+ }
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_audio_set_interface(USBDevice *dev, int iface,
+ int old, int value)
+{
+ USBAudioState *s = USB_AUDIO(dev);
+
+ if (iface == 1) {
+ usb_audio_set_output_altset(s, value);
+ }
+}
+
+static void usb_audio_handle_reset(USBDevice *dev)
+{
+ USBAudioState *s = USB_AUDIO(dev);
+
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: reset\n");
+ }
+ usb_audio_set_output_altset(s, ALTSET_OFF);
+}
+
+static void usb_audio_handle_dataout(USBAudioState *s, USBPacket *p)
+{
+ if (s->out.altset == ALTSET_OFF) {
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ streambuf_put(&s->out.buf, p);
+ if (p->actual_length < p->iov.size && s->debug > 1) {
+ fprintf(stderr, "usb-audio: output overrun (%zd bytes)\n",
+ p->iov.size - p->actual_length);
+ }
+}
+
+static void usb_audio_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBAudioState *s = (USBAudioState *) dev;
+
+ if (p->pid == USB_TOKEN_OUT && p->ep->nr == 1) {
+ usb_audio_handle_dataout(s, p);
+ return;
+ }
+
+ p->status = USB_RET_STALL;
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: failed data transaction: "
+ "pid 0x%x ep 0x%x len 0x%zx\n",
+ p->pid, p->ep->nr, p->iov.size);
+ }
+}
+
+static void usb_audio_handle_destroy(USBDevice *dev)
+{
+ USBAudioState *s = USB_AUDIO(dev);
+
+ if (s->debug) {
+ fprintf(stderr, "usb-audio: destroy\n");
+ }
+
+ usb_audio_set_output_altset(s, ALTSET_OFF);
+ AUD_close_out(&s->card, s->out.voice);
+ AUD_remove_card(&s->card);
+
+ streambuf_fini(&s->out.buf);
+}
+
+static void usb_audio_realize(USBDevice *dev, Error **errp)
+{
+ USBAudioState *s = USB_AUDIO(dev);
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ s->dev.opaque = s;
+ AUD_register_card(TYPE_USB_AUDIO, &s->card);
+
+ s->out.altset = ALTSET_OFF;
+ s->out.mute = false;
+ s->out.vol[0] = 240; /* 0 dB */
+ s->out.vol[1] = 240; /* 0 dB */
+ s->out.as.freq = USBAUDIO_SAMPLE_RATE;
+ s->out.as.nchannels = 2;
+ s->out.as.fmt = AUD_FMT_S16;
+ s->out.as.endianness = 0;
+ streambuf_init(&s->out.buf, s->buffer);
+
+ s->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_USB_AUDIO,
+ s, output_callback, &s->out.as);
+ AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], s->out.vol[1]);
+ AUD_set_active_out(s->out.voice, 0);
+}
+
+static const VMStateDescription vmstate_usb_audio = {
+ .name = TYPE_USB_AUDIO,
+ .unmigratable = 1,
+};
+
+static Property usb_audio_properties[] = {
+ DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0),
+ DEFINE_PROP_UINT32("buffer", USBAudioState, buffer,
+ 8 * USBAUDIO_PACKET_SIZE),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_audio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *k = USB_DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_usb_audio;
+ dc->props = usb_audio_properties;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ k->product_desc = "QEMU USB Audio Interface";
+ k->usb_desc = &desc_audio;
+ k->realize = usb_audio_realize;
+ k->handle_reset = usb_audio_handle_reset;
+ k->handle_control = usb_audio_handle_control;
+ k->handle_data = usb_audio_handle_data;
+ k->handle_destroy = usb_audio_handle_destroy;
+ k->set_interface = usb_audio_set_interface;
+}
+
+static const TypeInfo usb_audio_info = {
+ .name = TYPE_USB_AUDIO,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBAudioState),
+ .class_init = usb_audio_class_init,
+};
+
+static void usb_audio_register_types(void)
+{
+ type_register_static(&usb_audio_info);
+ usb_legacy_register(TYPE_USB_AUDIO, "audio", NULL);
+}
+
+type_init(usb_audio_register_types)
diff --git a/hw/usb/dev-bluetooth.c b/hw/usb/dev-bluetooth.c
new file mode 100644
index 00000000..b19ec76b
--- /dev/null
+++ b/hw/usb/dev-bluetooth.c
@@ -0,0 +1,579 @@
+/*
+ * QEMU Bluetooth HCI USB Transport Layer v1.0
+ *
+ * Copyright (C) 2007 OpenMoko, Inc.
+ * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "sysemu/bt.h"
+#include "hw/bt.h"
+
+struct USBBtState {
+ USBDevice dev;
+ struct HCIInfo *hci;
+ USBEndpoint *intr;
+
+ int config;
+
+#define CFIFO_LEN_MASK 255
+#define DFIFO_LEN_MASK 4095
+ struct usb_hci_in_fifo_s {
+ uint8_t data[(DFIFO_LEN_MASK + 1) * 2];
+ struct {
+ uint8_t *data;
+ int len;
+ } fifo[CFIFO_LEN_MASK + 1];
+ int dstart, dlen, dsize, start, len;
+ } evt, acl, sco;
+
+ struct usb_hci_out_fifo_s {
+ uint8_t data[4096];
+ int len;
+ } outcmd, outacl, outsco;
+};
+
+#define TYPE_USB_BT "usb-bt-dongle"
+#define USB_BT(obj) OBJECT_CHECK(struct USBBtState, (obj), TYPE_USB_BT)
+
+#define USB_EVT_EP 1
+#define USB_ACL_EP 2
+#define USB_SCO_EP 3
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_SERIALNUMBER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_SERIALNUMBER] = "1",
+};
+
+static const USBDescIface desc_iface_bluetooth[] = {
+ {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = 0xe0, /* Wireless */
+ .bInterfaceSubClass = 0x01, /* Radio Frequency */
+ .bInterfaceProtocol = 0x01, /* Bluetooth */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | USB_EVT_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 0x10,
+ .bInterval = 0x02,
+ },
+ {
+ .bEndpointAddress = USB_DIR_OUT | USB_ACL_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 0x40,
+ .bInterval = 0x0a,
+ },
+ {
+ .bEndpointAddress = USB_DIR_IN | USB_ACL_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 0x40,
+ .bInterval = 0x0a,
+ },
+ },
+ },{
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xe0, /* Wireless */
+ .bInterfaceSubClass = 0x01, /* Radio Frequency */
+ .bInterfaceProtocol = 0x01, /* Bluetooth */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0,
+ .bInterval = 0x01,
+ },
+ {
+ .bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0,
+ .bInterval = 0x01,
+ },
+ },
+ },{
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 1,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xe0, /* Wireless */
+ .bInterfaceSubClass = 0x01, /* Radio Frequency */
+ .bInterfaceProtocol = 0x01, /* Bluetooth */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x09,
+ .bInterval = 0x01,
+ },
+ {
+ .bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x09,
+ .bInterval = 0x01,
+ },
+ },
+ },{
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 2,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xe0, /* Wireless */
+ .bInterfaceSubClass = 0x01, /* Radio Frequency */
+ .bInterfaceProtocol = 0x01, /* Bluetooth */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x11,
+ .bInterval = 0x01,
+ },
+ {
+ .bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x11,
+ .bInterval = 0x01,
+ },
+ },
+ },{
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 3,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xe0, /* Wireless */
+ .bInterfaceSubClass = 0x01, /* Radio Frequency */
+ .bInterfaceProtocol = 0x01, /* Bluetooth */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x19,
+ .bInterval = 0x01,
+ },
+ {
+ .bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x19,
+ .bInterval = 0x01,
+ },
+ },
+ },{
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 4,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xe0, /* Wireless */
+ .bInterfaceSubClass = 0x01, /* Radio Frequency */
+ .bInterfaceProtocol = 0x01, /* Bluetooth */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x21,
+ .bInterval = 0x01,
+ },
+ {
+ .bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x21,
+ .bInterval = 0x01,
+ },
+ },
+ },{
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 5,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xe0, /* Wireless */
+ .bInterfaceSubClass = 0x01, /* Radio Frequency */
+ .bInterfaceProtocol = 0x01, /* Bluetooth */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x31,
+ .bInterval = 0x01,
+ },
+ {
+ .bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = 0x31,
+ .bInterval = 0x01,
+ },
+ },
+ }
+};
+
+static const USBDescDevice desc_device_bluetooth = {
+ .bcdUSB = 0x0110,
+ .bDeviceClass = 0xe0, /* Wireless */
+ .bDeviceSubClass = 0x01, /* Radio Frequency */
+ .bDeviceProtocol = 0x01, /* Bluetooth */
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 2,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .bMaxPower = 0,
+ .nif = ARRAY_SIZE(desc_iface_bluetooth),
+ .ifs = desc_iface_bluetooth,
+ },
+ },
+};
+
+static const USBDesc desc_bluetooth = {
+ .id = {
+ .idVendor = 0x0a12,
+ .idProduct = 0x0001,
+ .bcdDevice = 0x1958,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = 0,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_bluetooth,
+ .str = desc_strings,
+};
+
+static void usb_bt_fifo_reset(struct usb_hci_in_fifo_s *fifo)
+{
+ fifo->dstart = 0;
+ fifo->dlen = 0;
+ fifo->dsize = DFIFO_LEN_MASK + 1;
+ fifo->start = 0;
+ fifo->len = 0;
+}
+
+static void usb_bt_fifo_enqueue(struct usb_hci_in_fifo_s *fifo,
+ const uint8_t *data, int len)
+{
+ int off = fifo->dstart + fifo->dlen;
+ uint8_t *buf;
+
+ fifo->dlen += len;
+ if (off <= DFIFO_LEN_MASK) {
+ if (off + len > DFIFO_LEN_MASK + 1 &&
+ (fifo->dsize = off + len) > (DFIFO_LEN_MASK + 1) * 2) {
+ fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len);
+ exit(-1);
+ }
+ buf = fifo->data + off;
+ } else {
+ if (fifo->dlen > fifo->dsize) {
+ fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len);
+ exit(-1);
+ }
+ buf = fifo->data + off - fifo->dsize;
+ }
+
+ off = (fifo->start + fifo->len ++) & CFIFO_LEN_MASK;
+ fifo->fifo[off].data = memcpy(buf, data, len);
+ fifo->fifo[off].len = len;
+}
+
+static inline void usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo,
+ USBPacket *p)
+{
+ int len;
+
+ assert(fifo->len != 0);
+
+ len = MIN(p->iov.size, fifo->fifo[fifo->start].len);
+ usb_packet_copy(p, fifo->fifo[fifo->start].data, len);
+ if (len == p->iov.size) {
+ fifo->fifo[fifo->start].len -= len;
+ fifo->fifo[fifo->start].data += len;
+ } else {
+ fifo->start ++;
+ fifo->start &= CFIFO_LEN_MASK;
+ fifo->len --;
+ }
+
+ fifo->dstart += len;
+ fifo->dlen -= len;
+ if (fifo->dstart >= fifo->dsize) {
+ fifo->dstart = 0;
+ fifo->dsize = DFIFO_LEN_MASK + 1;
+ }
+}
+
+static inline void usb_bt_fifo_out_enqueue(struct USBBtState *s,
+ struct usb_hci_out_fifo_s *fifo,
+ void (*send)(struct HCIInfo *, const uint8_t *, int),
+ int (*complete)(const uint8_t *, int),
+ USBPacket *p)
+{
+ usb_packet_copy(p, fifo->data + fifo->len, p->iov.size);
+ fifo->len += p->iov.size;
+ if (complete(fifo->data, fifo->len)) {
+ send(s->hci, fifo->data, fifo->len);
+ fifo->len = 0;
+ }
+
+ /* TODO: do we need to loop? */
+}
+
+static int usb_bt_hci_cmd_complete(const uint8_t *data, int len)
+{
+ len -= HCI_COMMAND_HDR_SIZE;
+ return len >= 0 &&
+ len >= ((struct hci_command_hdr *) data)->plen;
+}
+
+static int usb_bt_hci_acl_complete(const uint8_t *data, int len)
+{
+ len -= HCI_ACL_HDR_SIZE;
+ return len >= 0 &&
+ len >= le16_to_cpu(((struct hci_acl_hdr *) data)->dlen);
+}
+
+static int usb_bt_hci_sco_complete(const uint8_t *data, int len)
+{
+ len -= HCI_SCO_HDR_SIZE;
+ return len >= 0 &&
+ len >= ((struct hci_sco_hdr *) data)->dlen;
+}
+
+static void usb_bt_handle_reset(USBDevice *dev)
+{
+ struct USBBtState *s = (struct USBBtState *) dev->opaque;
+
+ usb_bt_fifo_reset(&s->evt);
+ usb_bt_fifo_reset(&s->acl);
+ usb_bt_fifo_reset(&s->sco);
+ s->outcmd.len = 0;
+ s->outacl.len = 0;
+ s->outsco.len = 0;
+}
+
+static void usb_bt_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ struct USBBtState *s = (struct USBBtState *) dev->opaque;
+ int ret;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ switch (request) {
+ case DeviceRequest | USB_REQ_GET_CONFIGURATION:
+ s->config = 0;
+ break;
+ case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+ s->config = 1;
+ usb_bt_fifo_reset(&s->evt);
+ usb_bt_fifo_reset(&s->acl);
+ usb_bt_fifo_reset(&s->sco);
+ break;
+ }
+ return;
+ }
+
+ switch (request) {
+ case InterfaceRequest | USB_REQ_GET_STATUS:
+ case EndpointRequest | USB_REQ_GET_STATUS:
+ data[0] = 0x00;
+ data[1] = 0x00;
+ p->actual_length = 2;
+ break;
+ case InterfaceOutRequest | USB_REQ_CLEAR_FEATURE:
+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+ goto fail;
+ case InterfaceOutRequest | USB_REQ_SET_FEATURE:
+ case EndpointOutRequest | USB_REQ_SET_FEATURE:
+ goto fail;
+ break;
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE) << 8):
+ if (s->config)
+ usb_bt_fifo_out_enqueue(s, &s->outcmd, s->hci->cmd_send,
+ usb_bt_hci_cmd_complete, p);
+ break;
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_bt_handle_data(USBDevice *dev, USBPacket *p)
+{
+ struct USBBtState *s = (struct USBBtState *) dev->opaque;
+
+ if (!s->config)
+ goto fail;
+
+ switch (p->pid) {
+ case USB_TOKEN_IN:
+ switch (p->ep->nr) {
+ case USB_EVT_EP:
+ if (s->evt.len == 0) {
+ p->status = USB_RET_NAK;
+ break;
+ }
+ usb_bt_fifo_dequeue(&s->evt, p);
+ break;
+
+ case USB_ACL_EP:
+ if (s->evt.len == 0) {
+ p->status = USB_RET_STALL;
+ break;
+ }
+ usb_bt_fifo_dequeue(&s->acl, p);
+ break;
+
+ case USB_SCO_EP:
+ if (s->evt.len == 0) {
+ p->status = USB_RET_STALL;
+ break;
+ }
+ usb_bt_fifo_dequeue(&s->sco, p);
+ break;
+
+ default:
+ goto fail;
+ }
+ break;
+
+ case USB_TOKEN_OUT:
+ switch (p->ep->nr) {
+ case USB_ACL_EP:
+ usb_bt_fifo_out_enqueue(s, &s->outacl, s->hci->acl_send,
+ usb_bt_hci_acl_complete, p);
+ break;
+
+ case USB_SCO_EP:
+ usb_bt_fifo_out_enqueue(s, &s->outsco, s->hci->sco_send,
+ usb_bt_hci_sco_complete, p);
+ break;
+
+ default:
+ goto fail;
+ }
+ break;
+
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_bt_out_hci_packet_event(void *opaque,
+ const uint8_t *data, int len)
+{
+ struct USBBtState *s = (struct USBBtState *) opaque;
+
+ if (s->evt.len == 0) {
+ usb_wakeup(s->intr, 0);
+ }
+ usb_bt_fifo_enqueue(&s->evt, data, len);
+}
+
+static void usb_bt_out_hci_packet_acl(void *opaque,
+ const uint8_t *data, int len)
+{
+ struct USBBtState *s = (struct USBBtState *) opaque;
+
+ usb_bt_fifo_enqueue(&s->acl, data, len);
+}
+
+static void usb_bt_handle_destroy(USBDevice *dev)
+{
+ struct USBBtState *s = (struct USBBtState *) dev->opaque;
+
+ s->hci->opaque = NULL;
+ s->hci->evt_recv = NULL;
+ s->hci->acl_recv = NULL;
+}
+
+static void usb_bt_realize(USBDevice *dev, Error **errp)
+{
+ struct USBBtState *s = USB_BT(dev);
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ s->dev.opaque = s;
+ if (!s->hci) {
+ s->hci = bt_new_hci(qemu_find_bt_vlan(0));
+ }
+ s->hci->opaque = s;
+ s->hci->evt_recv = usb_bt_out_hci_packet_event;
+ s->hci->acl_recv = usb_bt_out_hci_packet_acl;
+ usb_bt_handle_reset(&s->dev);
+ s->intr = usb_ep_get(dev, USB_TOKEN_IN, USB_EVT_EP);
+}
+
+static USBDevice *usb_bt_init(USBBus *bus, const char *cmdline)
+{
+ USBDevice *dev;
+ struct USBBtState *s;
+ HCIInfo *hci;
+ const char *name = TYPE_USB_BT;
+
+ if (*cmdline) {
+ hci = hci_init(cmdline);
+ } else {
+ hci = bt_new_hci(qemu_find_bt_vlan(0));
+ }
+ if (!hci)
+ return NULL;
+
+ dev = usb_create(bus, name);
+ s = USB_BT(dev);
+ s->hci = hci;
+ return dev;
+}
+
+static const VMStateDescription vmstate_usb_bt = {
+ .name = "usb-bt",
+ .unmigratable = 1,
+};
+
+static void usb_bt_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_bt_realize;
+ uc->product_desc = "QEMU BT dongle";
+ uc->usb_desc = &desc_bluetooth;
+ uc->handle_reset = usb_bt_handle_reset;
+ uc->handle_control = usb_bt_handle_control;
+ uc->handle_data = usb_bt_handle_data;
+ uc->handle_destroy = usb_bt_handle_destroy;
+ dc->vmsd = &vmstate_usb_bt;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo bt_info = {
+ .name = TYPE_USB_BT,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(struct USBBtState),
+ .class_init = usb_bt_class_initfn,
+};
+
+static void usb_bt_register_types(void)
+{
+ type_register_static(&bt_info);
+ usb_legacy_register(TYPE_USB_BT, "bt", usb_bt_init);
+}
+
+type_init(usb_bt_register_types)
diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c
new file mode 100644
index 00000000..2e7dcd96
--- /dev/null
+++ b/hw/usb/dev-hid.c
@@ -0,0 +1,881 @@
+/*
+ * QEMU USB HID devices
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ * Copyright (c) 2007 OpenMoko, Inc. (andrew@openedhand.com)
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "qemu/timer.h"
+#include "hw/input/hid.h"
+
+/* HID interface requests */
+#define GET_REPORT 0xa101
+#define GET_IDLE 0xa102
+#define GET_PROTOCOL 0xa103
+#define SET_REPORT 0x2109
+#define SET_IDLE 0x210a
+#define SET_PROTOCOL 0x210b
+
+/* HID descriptor types */
+#define USB_DT_HID 0x21
+#define USB_DT_REPORT 0x22
+#define USB_DT_PHY 0x23
+
+typedef struct USBHIDState {
+ USBDevice dev;
+ USBEndpoint *intr;
+ HIDState hid;
+ uint32_t usb_version;
+ char *display;
+ uint32_t head;
+} USBHIDState;
+
+#define TYPE_USB_HID "usb-hid"
+#define USB_HID(obj) OBJECT_CHECK(USBHIDState, (obj), TYPE_USB_HID)
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT_MOUSE,
+ STR_PRODUCT_TABLET,
+ STR_PRODUCT_KEYBOARD,
+ STR_SERIALNUMBER,
+ STR_CONFIG_MOUSE,
+ STR_CONFIG_TABLET,
+ STR_CONFIG_KEYBOARD,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT_MOUSE] = "QEMU USB Mouse",
+ [STR_PRODUCT_TABLET] = "QEMU USB Tablet",
+ [STR_PRODUCT_KEYBOARD] = "QEMU USB Keyboard",
+ [STR_SERIALNUMBER] = "42", /* == remote wakeup works */
+ [STR_CONFIG_MOUSE] = "HID Mouse",
+ [STR_CONFIG_TABLET] = "HID Tablet",
+ [STR_CONFIG_KEYBOARD] = "HID Keyboard",
+};
+
+static const USBDescIface desc_iface_mouse = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0x01, /* boot */
+ .bInterfaceProtocol = 0x02,
+ .ndesc = 1,
+ .descs = (USBDescOther[]) {
+ {
+ /* HID descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ USB_DT_HID, /* u8 bDescriptorType */
+ 0x01, 0x00, /* u16 HID_class */
+ 0x00, /* u8 country_code */
+ 0x01, /* u8 num_descriptors */
+ USB_DT_REPORT, /* u8 type: Report */
+ 52, 0, /* u16 len */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 4,
+ .bInterval = 0x0a,
+ },
+ },
+};
+
+static const USBDescIface desc_iface_mouse2 = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0x01, /* boot */
+ .bInterfaceProtocol = 0x02,
+ .ndesc = 1,
+ .descs = (USBDescOther[]) {
+ {
+ /* HID descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ USB_DT_HID, /* u8 bDescriptorType */
+ 0x01, 0x00, /* u16 HID_class */
+ 0x00, /* u8 country_code */
+ 0x01, /* u8 num_descriptors */
+ USB_DT_REPORT, /* u8 type: Report */
+ 52, 0, /* u16 len */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 4,
+ .bInterval = 7, /* 2 ^ (8-1) * 125 usecs = 8 ms */
+ },
+ },
+};
+
+static const USBDescIface desc_iface_tablet = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceProtocol = 0x02,
+ .ndesc = 1,
+ .descs = (USBDescOther[]) {
+ {
+ /* HID descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ USB_DT_HID, /* u8 bDescriptorType */
+ 0x01, 0x00, /* u16 HID_class */
+ 0x00, /* u8 country_code */
+ 0x01, /* u8 num_descriptors */
+ USB_DT_REPORT, /* u8 type: Report */
+ 74, 0, /* u16 len */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 8,
+ .bInterval = 0x0a,
+ },
+ },
+};
+
+static const USBDescIface desc_iface_tablet2 = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceProtocol = 0x02,
+ .ndesc = 1,
+ .descs = (USBDescOther[]) {
+ {
+ /* HID descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ USB_DT_HID, /* u8 bDescriptorType */
+ 0x01, 0x00, /* u16 HID_class */
+ 0x00, /* u8 country_code */
+ 0x01, /* u8 num_descriptors */
+ USB_DT_REPORT, /* u8 type: Report */
+ 74, 0, /* u16 len */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 8,
+ .bInterval = 4, /* 2 ^ (4-1) * 125 usecs = 1 ms */
+ },
+ },
+};
+
+static const USBDescIface desc_iface_keyboard = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0x01, /* boot */
+ .bInterfaceProtocol = 0x01, /* keyboard */
+ .ndesc = 1,
+ .descs = (USBDescOther[]) {
+ {
+ /* HID descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ USB_DT_HID, /* u8 bDescriptorType */
+ 0x11, 0x01, /* u16 HID_class */
+ 0x00, /* u8 country_code */
+ 0x01, /* u8 num_descriptors */
+ USB_DT_REPORT, /* u8 type: Report */
+ 0x3f, 0, /* u16 len */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 8,
+ .bInterval = 0x0a,
+ },
+ },
+};
+
+static const USBDescIface desc_iface_keyboard2 = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0x01, /* boot */
+ .bInterfaceProtocol = 0x01, /* keyboard */
+ .ndesc = 1,
+ .descs = (USBDescOther[]) {
+ {
+ /* HID descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ USB_DT_HID, /* u8 bDescriptorType */
+ 0x11, 0x01, /* u16 HID_class */
+ 0x00, /* u8 country_code */
+ 0x01, /* u8 num_descriptors */
+ USB_DT_REPORT, /* u8 type: Report */
+ 0x3f, 0, /* u16 len */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 8,
+ .bInterval = 7, /* 2 ^ (8-1) * 125 usecs = 8 ms */
+ },
+ },
+};
+
+static const USBDescDevice desc_device_mouse = {
+ .bcdUSB = 0x0100,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_MOUSE,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface_mouse,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_mouse2 = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_MOUSE,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface_mouse2,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_tablet = {
+ .bcdUSB = 0x0100,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_TABLET,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface_tablet,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_tablet2 = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_TABLET,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface_tablet2,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_keyboard = {
+ .bcdUSB = 0x0100,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_KEYBOARD,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface_keyboard,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_keyboard2 = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_KEYBOARD,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface_keyboard2,
+ },
+ },
+};
+
+static const USBDescMSOS desc_msos_suspend = {
+ .SelectiveSuspendEnabled = true,
+};
+
+static const USBDesc desc_mouse = {
+ .id = {
+ .idVendor = 0x0627,
+ .idProduct = 0x0001,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT_MOUSE,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_mouse,
+ .str = desc_strings,
+ .msos = &desc_msos_suspend,
+};
+
+static const USBDesc desc_mouse2 = {
+ .id = {
+ .idVendor = 0x0627,
+ .idProduct = 0x0001,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT_MOUSE,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_mouse,
+ .high = &desc_device_mouse2,
+ .str = desc_strings,
+ .msos = &desc_msos_suspend,
+};
+
+static const USBDesc desc_tablet = {
+ .id = {
+ .idVendor = 0x0627,
+ .idProduct = 0x0001,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT_TABLET,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_tablet,
+ .str = desc_strings,
+ .msos = &desc_msos_suspend,
+};
+
+static const USBDesc desc_tablet2 = {
+ .id = {
+ .idVendor = 0x0627,
+ .idProduct = 0x0001,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT_TABLET,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_tablet,
+ .high = &desc_device_tablet2,
+ .str = desc_strings,
+ .msos = &desc_msos_suspend,
+};
+
+static const USBDesc desc_keyboard = {
+ .id = {
+ .idVendor = 0x0627,
+ .idProduct = 0x0001,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT_KEYBOARD,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_keyboard,
+ .str = desc_strings,
+ .msos = &desc_msos_suspend,
+};
+
+static const USBDesc desc_keyboard2 = {
+ .id = {
+ .idVendor = 0x0627,
+ .idProduct = 0x0001,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT_KEYBOARD,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_keyboard,
+ .high = &desc_device_keyboard2,
+ .str = desc_strings,
+ .msos = &desc_msos_suspend,
+};
+
+static const uint8_t qemu_mouse_hid_report_descriptor[] = {
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x02, /* Usage (Mouse) */
+ 0xa1, 0x01, /* Collection (Application) */
+ 0x09, 0x01, /* Usage (Pointer) */
+ 0xa1, 0x00, /* Collection (Physical) */
+ 0x05, 0x09, /* Usage Page (Button) */
+ 0x19, 0x01, /* Usage Minimum (1) */
+ 0x29, 0x03, /* Usage Maximum (3) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x95, 0x03, /* Report Count (3) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x81, 0x02, /* Input (Data, Variable, Absolute) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x05, /* Report Size (5) */
+ 0x81, 0x01, /* Input (Constant) */
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x30, /* Usage (X) */
+ 0x09, 0x31, /* Usage (Y) */
+ 0x09, 0x38, /* Usage (Wheel) */
+ 0x15, 0x81, /* Logical Minimum (-0x7f) */
+ 0x25, 0x7f, /* Logical Maximum (0x7f) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x95, 0x03, /* Report Count (3) */
+ 0x81, 0x06, /* Input (Data, Variable, Relative) */
+ 0xc0, /* End Collection */
+ 0xc0, /* End Collection */
+};
+
+static const uint8_t qemu_tablet_hid_report_descriptor[] = {
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x01, /* Usage (Pointer) */
+ 0xa1, 0x01, /* Collection (Application) */
+ 0x09, 0x01, /* Usage (Pointer) */
+ 0xa1, 0x00, /* Collection (Physical) */
+ 0x05, 0x09, /* Usage Page (Button) */
+ 0x19, 0x01, /* Usage Minimum (1) */
+ 0x29, 0x03, /* Usage Maximum (3) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x95, 0x03, /* Report Count (3) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x81, 0x02, /* Input (Data, Variable, Absolute) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x05, /* Report Size (5) */
+ 0x81, 0x01, /* Input (Constant) */
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x30, /* Usage (X) */
+ 0x09, 0x31, /* Usage (Y) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x26, 0xff, 0x7f, /* Logical Maximum (0x7fff) */
+ 0x35, 0x00, /* Physical Minimum (0) */
+ 0x46, 0xff, 0x7f, /* Physical Maximum (0x7fff) */
+ 0x75, 0x10, /* Report Size (16) */
+ 0x95, 0x02, /* Report Count (2) */
+ 0x81, 0x02, /* Input (Data, Variable, Absolute) */
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x38, /* Usage (Wheel) */
+ 0x15, 0x81, /* Logical Minimum (-0x7f) */
+ 0x25, 0x7f, /* Logical Maximum (0x7f) */
+ 0x35, 0x00, /* Physical Minimum (same as logical) */
+ 0x45, 0x00, /* Physical Maximum (same as logical) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x81, 0x06, /* Input (Data, Variable, Relative) */
+ 0xc0, /* End Collection */
+ 0xc0, /* End Collection */
+};
+
+static const uint8_t qemu_keyboard_hid_report_descriptor[] = {
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x06, /* Usage (Keyboard) */
+ 0xa1, 0x01, /* Collection (Application) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x95, 0x08, /* Report Count (8) */
+ 0x05, 0x07, /* Usage Page (Key Codes) */
+ 0x19, 0xe0, /* Usage Minimum (224) */
+ 0x29, 0xe7, /* Usage Maximum (231) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x81, 0x02, /* Input (Data, Variable, Absolute) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x81, 0x01, /* Input (Constant) */
+ 0x95, 0x05, /* Report Count (5) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x05, 0x08, /* Usage Page (LEDs) */
+ 0x19, 0x01, /* Usage Minimum (1) */
+ 0x29, 0x05, /* Usage Maximum (5) */
+ 0x91, 0x02, /* Output (Data, Variable, Absolute) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x03, /* Report Size (3) */
+ 0x91, 0x01, /* Output (Constant) */
+ 0x95, 0x06, /* Report Count (6) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0xff, /* Logical Maximum (255) */
+ 0x05, 0x07, /* Usage Page (Key Codes) */
+ 0x19, 0x00, /* Usage Minimum (0) */
+ 0x29, 0xff, /* Usage Maximum (255) */
+ 0x81, 0x00, /* Input (Data, Array) */
+ 0xc0, /* End Collection */
+};
+
+static void usb_hid_changed(HIDState *hs)
+{
+ USBHIDState *us = container_of(hs, USBHIDState, hid);
+
+ usb_wakeup(us->intr, 0);
+}
+
+static void usb_hid_handle_reset(USBDevice *dev)
+{
+ USBHIDState *us = USB_HID(dev);
+
+ hid_reset(&us->hid);
+}
+
+static void usb_hid_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ USBHIDState *us = USB_HID(dev);
+ HIDState *hs = &us->hid;
+ int ret;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch (request) {
+ /* hid specific requests */
+ case InterfaceRequest | USB_REQ_GET_DESCRIPTOR:
+ switch (value >> 8) {
+ case 0x22:
+ if (hs->kind == HID_MOUSE) {
+ memcpy(data, qemu_mouse_hid_report_descriptor,
+ sizeof(qemu_mouse_hid_report_descriptor));
+ p->actual_length = sizeof(qemu_mouse_hid_report_descriptor);
+ } else if (hs->kind == HID_TABLET) {
+ memcpy(data, qemu_tablet_hid_report_descriptor,
+ sizeof(qemu_tablet_hid_report_descriptor));
+ p->actual_length = sizeof(qemu_tablet_hid_report_descriptor);
+ } else if (hs->kind == HID_KEYBOARD) {
+ memcpy(data, qemu_keyboard_hid_report_descriptor,
+ sizeof(qemu_keyboard_hid_report_descriptor));
+ p->actual_length = sizeof(qemu_keyboard_hid_report_descriptor);
+ }
+ break;
+ default:
+ goto fail;
+ }
+ break;
+ case GET_REPORT:
+ if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) {
+ p->actual_length = hid_pointer_poll(hs, data, length);
+ } else if (hs->kind == HID_KEYBOARD) {
+ p->actual_length = hid_keyboard_poll(hs, data, length);
+ }
+ break;
+ case SET_REPORT:
+ if (hs->kind == HID_KEYBOARD) {
+ p->actual_length = hid_keyboard_write(hs, data, length);
+ } else {
+ goto fail;
+ }
+ break;
+ case GET_PROTOCOL:
+ if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) {
+ goto fail;
+ }
+ data[0] = hs->protocol;
+ p->actual_length = 1;
+ break;
+ case SET_PROTOCOL:
+ if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) {
+ goto fail;
+ }
+ hs->protocol = value;
+ break;
+ case GET_IDLE:
+ data[0] = hs->idle;
+ p->actual_length = 1;
+ break;
+ case SET_IDLE:
+ hs->idle = (uint8_t) (value >> 8);
+ hid_set_next_idle(hs);
+ if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) {
+ hid_pointer_activate(hs);
+ }
+ break;
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_hid_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBHIDState *us = USB_HID(dev);
+ HIDState *hs = &us->hid;
+ uint8_t buf[p->iov.size];
+ int len = 0;
+
+ switch (p->pid) {
+ case USB_TOKEN_IN:
+ if (p->ep->nr == 1) {
+ if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) {
+ hid_pointer_activate(hs);
+ }
+ if (!hid_has_events(hs)) {
+ p->status = USB_RET_NAK;
+ return;
+ }
+ hid_set_next_idle(hs);
+ if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) {
+ len = hid_pointer_poll(hs, buf, p->iov.size);
+ } else if (hs->kind == HID_KEYBOARD) {
+ len = hid_keyboard_poll(hs, buf, p->iov.size);
+ }
+ usb_packet_copy(p, buf, len);
+ } else {
+ goto fail;
+ }
+ break;
+ case USB_TOKEN_OUT:
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_hid_handle_destroy(USBDevice *dev)
+{
+ USBHIDState *us = USB_HID(dev);
+
+ hid_free(&us->hid);
+}
+
+static void usb_hid_initfn(USBDevice *dev, int kind,
+ const USBDesc *usb1, const USBDesc *usb2,
+ Error **errp)
+{
+ USBHIDState *us = USB_HID(dev);
+ switch (us->usb_version) {
+ case 1:
+ dev->usb_desc = usb1;
+ break;
+ case 2:
+ dev->usb_desc = usb2;
+ break;
+ default:
+ dev->usb_desc = NULL;
+ }
+ if (!dev->usb_desc) {
+ error_setg(errp, "Invalid usb version %d for usb hid device",
+ us->usb_version);
+ return;
+ }
+
+ if (dev->serial) {
+ usb_desc_set_string(dev, STR_SERIALNUMBER, dev->serial);
+ }
+ usb_desc_init(dev);
+ us->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
+ hid_init(&us->hid, kind, usb_hid_changed);
+ if (us->display && us->hid.s) {
+ qemu_input_handler_bind(us->hid.s, us->display, us->head, NULL);
+ }
+}
+
+static void usb_tablet_realize(USBDevice *dev, Error **errp)
+{
+
+ usb_hid_initfn(dev, HID_TABLET, &desc_tablet, &desc_tablet2, errp);
+}
+
+static void usb_mouse_realize(USBDevice *dev, Error **errp)
+{
+ usb_hid_initfn(dev, HID_MOUSE, &desc_mouse, &desc_mouse2, errp);
+}
+
+static void usb_keyboard_realize(USBDevice *dev, Error **errp)
+{
+ usb_hid_initfn(dev, HID_KEYBOARD, &desc_keyboard, &desc_keyboard2, errp);
+}
+
+static int usb_ptr_post_load(void *opaque, int version_id)
+{
+ USBHIDState *s = opaque;
+
+ if (s->dev.remote_wakeup) {
+ hid_pointer_activate(&s->hid);
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_usb_ptr = {
+ .name = "usb-ptr",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = usb_ptr_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, USBHIDState),
+ VMSTATE_HID_POINTER_DEVICE(hid, USBHIDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_usb_kbd = {
+ .name = "usb-kbd",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, USBHIDState),
+ VMSTATE_HID_KEYBOARD_DEVICE(hid, USBHIDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void usb_hid_class_initfn(ObjectClass *klass, void *data)
+{
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->handle_reset = usb_hid_handle_reset;
+ uc->handle_control = usb_hid_handle_control;
+ uc->handle_data = usb_hid_handle_data;
+ uc->handle_destroy = usb_hid_handle_destroy;
+ uc->handle_attach = usb_desc_attach;
+}
+
+static const TypeInfo usb_hid_type_info = {
+ .name = TYPE_USB_HID,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBHIDState),
+ .abstract = true,
+ .class_init = usb_hid_class_initfn,
+};
+
+static Property usb_tablet_properties[] = {
+ DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2),
+ DEFINE_PROP_STRING("display", USBHIDState, display),
+ DEFINE_PROP_UINT32("head", USBHIDState, head, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_tablet_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_tablet_realize;
+ uc->product_desc = "QEMU USB Tablet";
+ dc->vmsd = &vmstate_usb_ptr;
+ dc->props = usb_tablet_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo usb_tablet_info = {
+ .name = "usb-tablet",
+ .parent = TYPE_USB_HID,
+ .class_init = usb_tablet_class_initfn,
+};
+
+static Property usb_mouse_properties[] = {
+ DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_mouse_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_mouse_realize;
+ uc->product_desc = "QEMU USB Mouse";
+ dc->vmsd = &vmstate_usb_ptr;
+ dc->props = usb_mouse_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo usb_mouse_info = {
+ .name = "usb-mouse",
+ .parent = TYPE_USB_HID,
+ .class_init = usb_mouse_class_initfn,
+};
+
+static Property usb_keyboard_properties[] = {
+ DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2),
+ DEFINE_PROP_STRING("display", USBHIDState, display),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_keyboard_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_keyboard_realize;
+ uc->product_desc = "QEMU USB Keyboard";
+ dc->vmsd = &vmstate_usb_kbd;
+ dc->props = usb_keyboard_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo usb_keyboard_info = {
+ .name = "usb-kbd",
+ .parent = TYPE_USB_HID,
+ .class_init = usb_keyboard_class_initfn,
+};
+
+static void usb_hid_register_types(void)
+{
+ type_register_static(&usb_hid_type_info);
+ type_register_static(&usb_tablet_info);
+ usb_legacy_register("usb-tablet", "tablet", NULL);
+ type_register_static(&usb_mouse_info);
+ usb_legacy_register("usb-mouse", "mouse", NULL);
+ type_register_static(&usb_keyboard_info);
+ usb_legacy_register("usb-kbd", "keyboard", NULL);
+}
+
+type_init(usb_hid_register_types)
diff --git a/hw/usb/dev-hub.c b/hw/usb/dev-hub.c
new file mode 100644
index 00000000..c8c68555
--- /dev/null
+++ b/hw/usb/dev-hub.c
@@ -0,0 +1,594 @@
+/*
+ * QEMU USB HUB emulation
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "qemu-common.h"
+#include "trace.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "qemu/error-report.h"
+
+#define NUM_PORTS 8
+
+typedef struct USBHubPort {
+ USBPort port;
+ uint16_t wPortStatus;
+ uint16_t wPortChange;
+} USBHubPort;
+
+typedef struct USBHubState {
+ USBDevice dev;
+ USBEndpoint *intr;
+ USBHubPort ports[NUM_PORTS];
+} USBHubState;
+
+#define TYPE_USB_HUB "usb-hub"
+#define USB_HUB(obj) OBJECT_CHECK(USBHubState, (obj), TYPE_USB_HUB)
+
+#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE)
+#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE)
+#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR)
+#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS)
+#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS)
+#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE)
+#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE)
+
+#define PORT_STAT_CONNECTION 0x0001
+#define PORT_STAT_ENABLE 0x0002
+#define PORT_STAT_SUSPEND 0x0004
+#define PORT_STAT_OVERCURRENT 0x0008
+#define PORT_STAT_RESET 0x0010
+#define PORT_STAT_POWER 0x0100
+#define PORT_STAT_LOW_SPEED 0x0200
+#define PORT_STAT_HIGH_SPEED 0x0400
+#define PORT_STAT_TEST 0x0800
+#define PORT_STAT_INDICATOR 0x1000
+
+#define PORT_STAT_C_CONNECTION 0x0001
+#define PORT_STAT_C_ENABLE 0x0002
+#define PORT_STAT_C_SUSPEND 0x0004
+#define PORT_STAT_C_OVERCURRENT 0x0008
+#define PORT_STAT_C_RESET 0x0010
+
+#define PORT_CONNECTION 0
+#define PORT_ENABLE 1
+#define PORT_SUSPEND 2
+#define PORT_OVERCURRENT 3
+#define PORT_RESET 4
+#define PORT_POWER 8
+#define PORT_LOWSPEED 9
+#define PORT_HIGHSPEED 10
+#define PORT_C_CONNECTION 16
+#define PORT_C_ENABLE 17
+#define PORT_C_SUSPEND 18
+#define PORT_C_OVERCURRENT 19
+#define PORT_C_RESET 20
+#define PORT_TEST 21
+#define PORT_INDICATOR 22
+
+/* same as Linux kernel root hubs */
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT] = "QEMU USB Hub",
+ [STR_SERIALNUMBER] = "314159",
+};
+
+static const USBDescIface desc_iface_hub = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HUB,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 1 + (NUM_PORTS + 7) / 8,
+ .bInterval = 0xff,
+ },
+ }
+};
+
+static const USBDescDevice desc_device_hub = {
+ .bcdUSB = 0x0110,
+ .bDeviceClass = USB_CLASS_HUB,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER |
+ USB_CFG_ATT_WAKEUP,
+ .nif = 1,
+ .ifs = &desc_iface_hub,
+ },
+ },
+};
+
+static const USBDesc desc_hub = {
+ .id = {
+ .idVendor = 0x0409,
+ .idProduct = 0x55aa,
+ .bcdDevice = 0x0101,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_hub,
+ .str = desc_strings,
+};
+
+static const uint8_t qemu_hub_hub_descriptor[] =
+{
+ 0x00, /* u8 bLength; patched in later */
+ 0x29, /* u8 bDescriptorType; Hub-descriptor */
+ 0x00, /* u8 bNbrPorts; (patched later) */
+ 0x0a, /* u16 wHubCharacteristics; */
+ 0x00, /* (per-port OC, no power switching) */
+ 0x01, /* u8 bPwrOn2pwrGood; 2ms */
+ 0x00 /* u8 bHubContrCurrent; 0 mA */
+
+ /* DeviceRemovable and PortPwrCtrlMask patched in later */
+};
+
+static void usb_hub_attach(USBPort *port1)
+{
+ USBHubState *s = port1->opaque;
+ USBHubPort *port = &s->ports[port1->index];
+
+ trace_usb_hub_attach(s->dev.addr, port1->index + 1);
+ port->wPortStatus |= PORT_STAT_CONNECTION;
+ port->wPortChange |= PORT_STAT_C_CONNECTION;
+ if (port->port.dev->speed == USB_SPEED_LOW) {
+ port->wPortStatus |= PORT_STAT_LOW_SPEED;
+ } else {
+ port->wPortStatus &= ~PORT_STAT_LOW_SPEED;
+ }
+ usb_wakeup(s->intr, 0);
+}
+
+static void usb_hub_detach(USBPort *port1)
+{
+ USBHubState *s = port1->opaque;
+ USBHubPort *port = &s->ports[port1->index];
+
+ trace_usb_hub_detach(s->dev.addr, port1->index + 1);
+ usb_wakeup(s->intr, 0);
+
+ /* Let upstream know the device on this port is gone */
+ s->dev.port->ops->child_detach(s->dev.port, port1->dev);
+
+ port->wPortStatus &= ~PORT_STAT_CONNECTION;
+ port->wPortChange |= PORT_STAT_C_CONNECTION;
+ if (port->wPortStatus & PORT_STAT_ENABLE) {
+ port->wPortStatus &= ~PORT_STAT_ENABLE;
+ port->wPortChange |= PORT_STAT_C_ENABLE;
+ }
+ usb_wakeup(s->intr, 0);
+}
+
+static void usb_hub_child_detach(USBPort *port1, USBDevice *child)
+{
+ USBHubState *s = port1->opaque;
+
+ /* Pass along upstream */
+ s->dev.port->ops->child_detach(s->dev.port, child);
+}
+
+static void usb_hub_wakeup(USBPort *port1)
+{
+ USBHubState *s = port1->opaque;
+ USBHubPort *port = &s->ports[port1->index];
+
+ if (port->wPortStatus & PORT_STAT_SUSPEND) {
+ port->wPortChange |= PORT_STAT_C_SUSPEND;
+ usb_wakeup(s->intr, 0);
+ }
+}
+
+static void usb_hub_complete(USBPort *port, USBPacket *packet)
+{
+ USBHubState *s = port->opaque;
+
+ /*
+ * Just pass it along upstream for now.
+ *
+ * If we ever implement usb 2.0 split transactions this will
+ * become a little more complicated ...
+ *
+ * Can't use usb_packet_complete() here because packet->owner is
+ * cleared already, go call the ->complete() callback directly
+ * instead.
+ */
+ s->dev.port->ops->complete(s->dev.port, packet);
+}
+
+static USBDevice *usb_hub_find_device(USBDevice *dev, uint8_t addr)
+{
+ USBHubState *s = USB_HUB(dev);
+ USBHubPort *port;
+ USBDevice *downstream;
+ int i;
+
+ for (i = 0; i < NUM_PORTS; i++) {
+ port = &s->ports[i];
+ if (!(port->wPortStatus & PORT_STAT_ENABLE)) {
+ continue;
+ }
+ downstream = usb_find_device(&port->port, addr);
+ if (downstream != NULL) {
+ return downstream;
+ }
+ }
+ return NULL;
+}
+
+static void usb_hub_handle_reset(USBDevice *dev)
+{
+ USBHubState *s = USB_HUB(dev);
+ USBHubPort *port;
+ int i;
+
+ trace_usb_hub_reset(s->dev.addr);
+ for (i = 0; i < NUM_PORTS; i++) {
+ port = s->ports + i;
+ port->wPortStatus = PORT_STAT_POWER;
+ port->wPortChange = 0;
+ if (port->port.dev && port->port.dev->attached) {
+ port->wPortStatus |= PORT_STAT_CONNECTION;
+ port->wPortChange |= PORT_STAT_C_CONNECTION;
+ if (port->port.dev->speed == USB_SPEED_LOW) {
+ port->wPortStatus |= PORT_STAT_LOW_SPEED;
+ }
+ }
+ }
+}
+
+static const char *feature_name(int feature)
+{
+ static const char *name[] = {
+ [PORT_CONNECTION] = "connection",
+ [PORT_ENABLE] = "enable",
+ [PORT_SUSPEND] = "suspend",
+ [PORT_OVERCURRENT] = "overcurrent",
+ [PORT_RESET] = "reset",
+ [PORT_POWER] = "power",
+ [PORT_LOWSPEED] = "lowspeed",
+ [PORT_HIGHSPEED] = "highspeed",
+ [PORT_C_CONNECTION] = "change connection",
+ [PORT_C_ENABLE] = "change enable",
+ [PORT_C_SUSPEND] = "change suspend",
+ [PORT_C_OVERCURRENT] = "change overcurrent",
+ [PORT_C_RESET] = "change reset",
+ [PORT_TEST] = "test",
+ [PORT_INDICATOR] = "indicator",
+ };
+ if (feature < 0 || feature >= ARRAY_SIZE(name)) {
+ return "?";
+ }
+ return name[feature] ?: "?";
+}
+
+static void usb_hub_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ USBHubState *s = (USBHubState *)dev;
+ int ret;
+
+ trace_usb_hub_control(s->dev.addr, request, value, index, length);
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch(request) {
+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+ if (value == 0 && index != 0x81) { /* clear ep halt */
+ goto fail;
+ }
+ break;
+ /* usb specific requests */
+ case GetHubStatus:
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0;
+ p->actual_length = 4;
+ break;
+ case GetPortStatus:
+ {
+ unsigned int n = index - 1;
+ USBHubPort *port;
+ if (n >= NUM_PORTS) {
+ goto fail;
+ }
+ port = &s->ports[n];
+ trace_usb_hub_get_port_status(s->dev.addr, index,
+ port->wPortStatus,
+ port->wPortChange);
+ data[0] = port->wPortStatus;
+ data[1] = port->wPortStatus >> 8;
+ data[2] = port->wPortChange;
+ data[3] = port->wPortChange >> 8;
+ p->actual_length = 4;
+ }
+ break;
+ case SetHubFeature:
+ case ClearHubFeature:
+ if (value != 0 && value != 1) {
+ goto fail;
+ }
+ break;
+ case SetPortFeature:
+ {
+ unsigned int n = index - 1;
+ USBHubPort *port;
+ USBDevice *dev;
+
+ trace_usb_hub_set_port_feature(s->dev.addr, index,
+ feature_name(value));
+
+ if (n >= NUM_PORTS) {
+ goto fail;
+ }
+ port = &s->ports[n];
+ dev = port->port.dev;
+ switch(value) {
+ case PORT_SUSPEND:
+ port->wPortStatus |= PORT_STAT_SUSPEND;
+ break;
+ case PORT_RESET:
+ if (dev && dev->attached) {
+ usb_device_reset(dev);
+ port->wPortChange |= PORT_STAT_C_RESET;
+ /* set enable bit */
+ port->wPortStatus |= PORT_STAT_ENABLE;
+ usb_wakeup(s->intr, 0);
+ }
+ break;
+ case PORT_POWER:
+ break;
+ default:
+ goto fail;
+ }
+ }
+ break;
+ case ClearPortFeature:
+ {
+ unsigned int n = index - 1;
+ USBHubPort *port;
+
+ trace_usb_hub_clear_port_feature(s->dev.addr, index,
+ feature_name(value));
+
+ if (n >= NUM_PORTS) {
+ goto fail;
+ }
+ port = &s->ports[n];
+ switch(value) {
+ case PORT_ENABLE:
+ port->wPortStatus &= ~PORT_STAT_ENABLE;
+ break;
+ case PORT_C_ENABLE:
+ port->wPortChange &= ~PORT_STAT_C_ENABLE;
+ break;
+ case PORT_SUSPEND:
+ port->wPortStatus &= ~PORT_STAT_SUSPEND;
+ break;
+ case PORT_C_SUSPEND:
+ port->wPortChange &= ~PORT_STAT_C_SUSPEND;
+ break;
+ case PORT_C_CONNECTION:
+ port->wPortChange &= ~PORT_STAT_C_CONNECTION;
+ break;
+ case PORT_C_OVERCURRENT:
+ port->wPortChange &= ~PORT_STAT_C_OVERCURRENT;
+ break;
+ case PORT_C_RESET:
+ port->wPortChange &= ~PORT_STAT_C_RESET;
+ break;
+ default:
+ goto fail;
+ }
+ }
+ break;
+ case GetHubDescriptor:
+ {
+ unsigned int n, limit, var_hub_size = 0;
+ memcpy(data, qemu_hub_hub_descriptor,
+ sizeof(qemu_hub_hub_descriptor));
+ data[2] = NUM_PORTS;
+
+ /* fill DeviceRemovable bits */
+ limit = ((NUM_PORTS + 1 + 7) / 8) + 7;
+ for (n = 7; n < limit; n++) {
+ data[n] = 0x00;
+ var_hub_size++;
+ }
+
+ /* fill PortPwrCtrlMask bits */
+ limit = limit + ((NUM_PORTS + 7) / 8);
+ for (;n < limit; n++) {
+ data[n] = 0xff;
+ var_hub_size++;
+ }
+
+ p->actual_length = sizeof(qemu_hub_hub_descriptor) + var_hub_size;
+ data[0] = p->actual_length;
+ break;
+ }
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_hub_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBHubState *s = (USBHubState *)dev;
+
+ switch(p->pid) {
+ case USB_TOKEN_IN:
+ if (p->ep->nr == 1) {
+ USBHubPort *port;
+ unsigned int status;
+ uint8_t buf[4];
+ int i, n;
+ n = (NUM_PORTS + 1 + 7) / 8;
+ if (p->iov.size == 1) { /* FreeBSD workaround */
+ n = 1;
+ } else if (n > p->iov.size) {
+ p->status = USB_RET_BABBLE;
+ return;
+ }
+ status = 0;
+ for(i = 0; i < NUM_PORTS; i++) {
+ port = &s->ports[i];
+ if (port->wPortChange)
+ status |= (1 << (i + 1));
+ }
+ if (status != 0) {
+ trace_usb_hub_status_report(s->dev.addr, status);
+ for(i = 0; i < n; i++) {
+ buf[i] = status >> (8 * i);
+ }
+ usb_packet_copy(p, buf, n);
+ } else {
+ p->status = USB_RET_NAK; /* usb11 11.13.1 */
+ }
+ } else {
+ goto fail;
+ }
+ break;
+ case USB_TOKEN_OUT:
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_hub_handle_destroy(USBDevice *dev)
+{
+ USBHubState *s = (USBHubState *)dev;
+ int i;
+
+ for (i = 0; i < NUM_PORTS; i++) {
+ usb_unregister_port(usb_bus_from_device(dev),
+ &s->ports[i].port);
+ }
+}
+
+static USBPortOps usb_hub_port_ops = {
+ .attach = usb_hub_attach,
+ .detach = usb_hub_detach,
+ .child_detach = usb_hub_child_detach,
+ .wakeup = usb_hub_wakeup,
+ .complete = usb_hub_complete,
+};
+
+static void usb_hub_realize(USBDevice *dev, Error **errp)
+{
+ USBHubState *s = USB_HUB(dev);
+ USBHubPort *port;
+ int i;
+
+ if (dev->port->hubcount == 5) {
+ error_setg(errp, "usb hub chain too deep");
+ return;
+ }
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
+ for (i = 0; i < NUM_PORTS; i++) {
+ port = &s->ports[i];
+ usb_register_port(usb_bus_from_device(dev),
+ &port->port, s, i, &usb_hub_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
+ usb_port_location(&port->port, dev->port, i+1);
+ }
+ usb_hub_handle_reset(dev);
+}
+
+static const VMStateDescription vmstate_usb_hub_port = {
+ .name = "usb-hub-port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(wPortStatus, USBHubPort),
+ VMSTATE_UINT16(wPortChange, USBHubPort),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_usb_hub = {
+ .name = "usb-hub",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, USBHubState),
+ VMSTATE_STRUCT_ARRAY(ports, USBHubState, NUM_PORTS, 0,
+ vmstate_usb_hub_port, USBHubPort),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void usb_hub_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_hub_realize;
+ uc->product_desc = "QEMU USB Hub";
+ uc->usb_desc = &desc_hub;
+ uc->find_device = usb_hub_find_device;
+ uc->handle_reset = usb_hub_handle_reset;
+ uc->handle_control = usb_hub_handle_control;
+ uc->handle_data = usb_hub_handle_data;
+ uc->handle_destroy = usb_hub_handle_destroy;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->fw_name = "hub";
+ dc->vmsd = &vmstate_usb_hub;
+}
+
+static const TypeInfo hub_info = {
+ .name = TYPE_USB_HUB,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBHubState),
+ .class_init = usb_hub_class_initfn,
+};
+
+static void usb_hub_register_types(void)
+{
+ type_register_static(&hub_info);
+}
+
+type_init(usb_hub_register_types)
diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
new file mode 100644
index 00000000..809b1cb1
--- /dev/null
+++ b/hw/usb/dev-mtp.c
@@ -0,0 +1,1134 @@
+/*
+ * Media Transfer Protocol implementation, backed by host filesystem.
+ *
+ * Copyright Red Hat, Inc 2014
+ *
+ * Author:
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This code is licensed under the GPL v2 or later.
+ */
+
+#include <wchar.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+
+#include "qemu-common.h"
+#include "qemu/iov.h"
+#include "trace.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+
+/* ----------------------------------------------------------------------- */
+
+enum mtp_container_type {
+ TYPE_COMMAND = 1,
+ TYPE_DATA = 2,
+ TYPE_RESPONSE = 3,
+ TYPE_EVENT = 4,
+};
+
+enum mtp_code {
+ /* command codes */
+ CMD_GET_DEVICE_INFO = 0x1001,
+ CMD_OPEN_SESSION = 0x1002,
+ CMD_CLOSE_SESSION = 0x1003,
+ CMD_GET_STORAGE_IDS = 0x1004,
+ CMD_GET_STORAGE_INFO = 0x1005,
+ CMD_GET_NUM_OBJECTS = 0x1006,
+ CMD_GET_OBJECT_HANDLES = 0x1007,
+ CMD_GET_OBJECT_INFO = 0x1008,
+ CMD_GET_OBJECT = 0x1009,
+ CMD_GET_PARTIAL_OBJECT = 0x101b,
+
+ /* response codes */
+ RES_OK = 0x2001,
+ RES_GENERAL_ERROR = 0x2002,
+ RES_SESSION_NOT_OPEN = 0x2003,
+ RES_INVALID_TRANSACTION_ID = 0x2004,
+ RES_OPERATION_NOT_SUPPORTED = 0x2005,
+ RES_PARAMETER_NOT_SUPPORTED = 0x2006,
+ RES_INCOMPLETE_TRANSFER = 0x2007,
+ RES_INVALID_STORAGE_ID = 0x2008,
+ RES_INVALID_OBJECT_HANDLE = 0x2009,
+ RES_SPEC_BY_FORMAT_UNSUPPORTED = 0x2014,
+ RES_INVALID_PARENT_OBJECT = 0x201a,
+ RES_INVALID_PARAMETER = 0x201d,
+ RES_SESSION_ALREADY_OPEN = 0x201e,
+
+ /* format codes */
+ FMT_UNDEFINED_OBJECT = 0x3000,
+ FMT_ASSOCIATION = 0x3001,
+};
+
+typedef struct {
+ uint32_t length;
+ uint16_t type;
+ uint16_t code;
+ uint32_t trans;
+} QEMU_PACKED mtp_container;
+
+/* ----------------------------------------------------------------------- */
+
+typedef struct MTPState MTPState;
+typedef struct MTPControl MTPControl;
+typedef struct MTPData MTPData;
+typedef struct MTPObject MTPObject;
+
+enum {
+ EP_DATA_IN = 1,
+ EP_DATA_OUT,
+ EP_EVENT,
+};
+
+struct MTPControl {
+ uint16_t code;
+ uint32_t trans;
+ int argc;
+ uint32_t argv[5];
+};
+
+struct MTPData {
+ uint16_t code;
+ uint32_t trans;
+ uint32_t offset;
+ uint32_t length;
+ uint32_t alloc;
+ uint8_t *data;
+ bool first;
+ int fd;
+};
+
+struct MTPObject {
+ uint32_t handle;
+ uint16_t format;
+ char *name;
+ char *path;
+ struct stat stat;
+ MTPObject *parent;
+ MTPObject **children;
+ uint32_t nchildren;
+ bool have_children;
+ QTAILQ_ENTRY(MTPObject) next;
+};
+
+struct MTPState {
+ USBDevice dev;
+ char *root;
+ char *desc;
+ uint32_t flags;
+
+ MTPData *data_in;
+ MTPData *data_out;
+ MTPControl *result;
+ uint32_t session;
+ uint32_t next_handle;
+
+ QTAILQ_HEAD(, MTPObject) objects;
+};
+
+#define TYPE_USB_MTP "usb-mtp"
+#define USB_MTP(obj) OBJECT_CHECK(MTPState, (obj), TYPE_USB_MTP)
+
+#define QEMU_STORAGE_ID 0x00010001
+
+#define MTP_FLAG_WRITABLE 0
+
+#define FLAG_SET(_mtp, _flag) ((_mtp)->flags & (1 << (_flag)))
+
+/* ----------------------------------------------------------------------- */
+
+#define MTP_MANUFACTURER "QEMU"
+#define MTP_PRODUCT "QEMU filesharing"
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+ STR_MTP,
+ STR_CONFIG_FULL,
+ STR_CONFIG_HIGH,
+ STR_CONFIG_SUPER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = MTP_MANUFACTURER,
+ [STR_PRODUCT] = MTP_PRODUCT,
+ [STR_SERIALNUMBER] = "34617",
+ [STR_MTP] = "MTP",
+ [STR_CONFIG_FULL] = "Full speed config (usb 1.1)",
+ [STR_CONFIG_HIGH] = "High speed config (usb 2.0)",
+ [STR_CONFIG_SUPER] = "Super speed config (usb 3.0)",
+};
+
+static const USBDescIface desc_iface_full = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_STILL_IMAGE,
+ .bInterfaceSubClass = 0x01,
+ .bInterfaceProtocol = 0x01,
+ .iInterface = STR_MTP,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | EP_DATA_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | EP_DATA_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },{
+ .bEndpointAddress = USB_DIR_IN | EP_EVENT,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 8,
+ .bInterval = 0x0a,
+ },
+ }
+};
+
+static const USBDescDevice desc_device_full = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_FULL,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 2,
+ .nif = 1,
+ .ifs = &desc_iface_full,
+ },
+ },
+};
+
+static const USBDescIface desc_iface_high = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_STILL_IMAGE,
+ .bInterfaceSubClass = 0x01,
+ .bInterfaceProtocol = 0x01,
+ .iInterface = STR_MTP,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | EP_DATA_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | EP_DATA_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ },{
+ .bEndpointAddress = USB_DIR_IN | EP_EVENT,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 8,
+ .bInterval = 0x0a,
+ },
+ }
+};
+
+static const USBDescDevice desc_device_high = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_HIGH,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 2,
+ .nif = 1,
+ .ifs = &desc_iface_high,
+ },
+ },
+};
+
+static const USBDescMSOS desc_msos = {
+ .CompatibleID = "MTP",
+ .SelectiveSuspendEnabled = true,
+};
+
+static const USBDesc desc = {
+ .id = {
+ .idVendor = 0x46f4, /* CRC16() of "QEMU" */
+ .idProduct = 0x0004,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_full,
+ .high = &desc_device_high,
+ .str = desc_strings,
+ .msos = &desc_msos,
+};
+
+/* ----------------------------------------------------------------------- */
+
+static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
+ MTPObject *parent, char *name)
+{
+ MTPObject *o = g_new0(MTPObject, 1);
+
+ if (name[0] == '.') {
+ goto ignore;
+ }
+
+ o->handle = handle;
+ o->parent = parent;
+ o->name = g_strdup(name);
+ if (parent == NULL) {
+ o->path = g_strdup(name);
+ } else {
+ o->path = g_strdup_printf("%s/%s", parent->path, name);
+ }
+
+ if (lstat(o->path, &o->stat) != 0) {
+ goto ignore;
+ }
+ if (S_ISREG(o->stat.st_mode)) {
+ o->format = FMT_UNDEFINED_OBJECT;
+ } else if (S_ISDIR(o->stat.st_mode)) {
+ o->format = FMT_ASSOCIATION;
+ } else {
+ goto ignore;
+ }
+
+ if (access(o->path, R_OK) != 0) {
+ goto ignore;
+ }
+
+ trace_usb_mtp_object_alloc(s->dev.addr, o->handle, o->path);
+
+ QTAILQ_INSERT_TAIL(&s->objects, o, next);
+ return o;
+
+ignore:
+ g_free(o->name);
+ g_free(o->path);
+ g_free(o);
+ return NULL;
+}
+
+static void usb_mtp_object_free(MTPState *s, MTPObject *o)
+{
+ int i;
+
+ trace_usb_mtp_object_free(s->dev.addr, o->handle, o->path);
+
+ QTAILQ_REMOVE(&s->objects, o, next);
+ for (i = 0; i < o->nchildren; i++) {
+ usb_mtp_object_free(s, o->children[i]);
+ }
+ g_free(o->children);
+ g_free(o->name);
+ g_free(o->path);
+ g_free(o);
+}
+
+static MTPObject *usb_mtp_object_lookup(MTPState *s, uint32_t handle)
+{
+ MTPObject *o;
+
+ QTAILQ_FOREACH(o, &s->objects, next) {
+ if (o->handle == handle) {
+ return o;
+ }
+ }
+ return NULL;
+}
+
+static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
+{
+ struct dirent *entry;
+ DIR *dir;
+
+ if (o->have_children) {
+ return;
+ }
+ o->have_children = true;
+
+ dir = opendir(o->path);
+ if (!dir) {
+ return;
+ }
+ while ((entry = readdir(dir)) != NULL) {
+ if ((o->nchildren % 32) == 0) {
+ o->children = g_realloc(o->children,
+ (o->nchildren + 32) * sizeof(MTPObject *));
+ }
+ o->children[o->nchildren] =
+ usb_mtp_object_alloc(s, s->next_handle++, o, entry->d_name);
+ if (o->children[o->nchildren] != NULL) {
+ o->nchildren++;
+ }
+ }
+ closedir(dir);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static MTPData *usb_mtp_data_alloc(MTPControl *c)
+{
+ MTPData *data = g_new0(MTPData, 1);
+
+ data->code = c->code;
+ data->trans = c->trans;
+ data->fd = -1;
+ data->first = true;
+ return data;
+}
+
+static void usb_mtp_data_free(MTPData *data)
+{
+ if (data == NULL) {
+ return;
+ }
+ if (data->fd != -1) {
+ close(data->fd);
+ }
+ g_free(data->data);
+ g_free(data);
+}
+
+static void usb_mtp_realloc(MTPData *data, uint32_t bytes)
+{
+ if (data->length + bytes <= data->alloc) {
+ return;
+ }
+ data->alloc = (data->length + bytes + 0xff) & ~0xff;
+ data->data = g_realloc(data->data, data->alloc);
+}
+
+static void usb_mtp_add_u8(MTPData *data, uint8_t val)
+{
+ usb_mtp_realloc(data, 1);
+ data->data[data->length++] = val;
+}
+
+static void usb_mtp_add_u16(MTPData *data, uint16_t val)
+{
+ usb_mtp_realloc(data, 2);
+ data->data[data->length++] = (val >> 0) & 0xff;
+ data->data[data->length++] = (val >> 8) & 0xff;
+}
+
+static void usb_mtp_add_u32(MTPData *data, uint32_t val)
+{
+ usb_mtp_realloc(data, 4);
+ data->data[data->length++] = (val >> 0) & 0xff;
+ data->data[data->length++] = (val >> 8) & 0xff;
+ data->data[data->length++] = (val >> 16) & 0xff;
+ data->data[data->length++] = (val >> 24) & 0xff;
+}
+
+static void usb_mtp_add_u64(MTPData *data, uint64_t val)
+{
+ usb_mtp_realloc(data, 8);
+ data->data[data->length++] = (val >> 0) & 0xff;
+ data->data[data->length++] = (val >> 8) & 0xff;
+ data->data[data->length++] = (val >> 16) & 0xff;
+ data->data[data->length++] = (val >> 24) & 0xff;
+ data->data[data->length++] = (val >> 32) & 0xff;
+ data->data[data->length++] = (val >> 40) & 0xff;
+ data->data[data->length++] = (val >> 48) & 0xff;
+ data->data[data->length++] = (val >> 56) & 0xff;
+}
+
+static void usb_mtp_add_u16_array(MTPData *data, uint32_t len,
+ const uint16_t *vals)
+{
+ int i;
+
+ usb_mtp_add_u32(data, len);
+ for (i = 0; i < len; i++) {
+ usb_mtp_add_u16(data, vals[i]);
+ }
+}
+
+static void usb_mtp_add_u32_array(MTPData *data, uint32_t len,
+ const uint32_t *vals)
+{
+ int i;
+
+ usb_mtp_add_u32(data, len);
+ for (i = 0; i < len; i++) {
+ usb_mtp_add_u32(data, vals[i]);
+ }
+}
+
+static void usb_mtp_add_wstr(MTPData *data, const wchar_t *str)
+{
+ uint32_t len = wcslen(str);
+ int i;
+
+ if (len > 0) {
+ len++; /* include terminating L'\0' */
+ }
+
+ usb_mtp_add_u8(data, len);
+ for (i = 0; i < len; i++) {
+ usb_mtp_add_u16(data, str[i]);
+ }
+}
+
+static void usb_mtp_add_str(MTPData *data, const char *str)
+{
+ uint32_t len = strlen(str)+1;
+ wchar_t wstr[len];
+ size_t ret;
+
+ ret = mbstowcs(wstr, str, len);
+ if (ret == -1) {
+ usb_mtp_add_wstr(data, L"Oops");
+ } else {
+ usb_mtp_add_wstr(data, wstr);
+ }
+}
+
+static void usb_mtp_add_time(MTPData *data, time_t time)
+{
+ char buf[16];
+ struct tm tm;
+
+ gmtime_r(&time, &tm);
+ strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", &tm);
+ usb_mtp_add_str(data, buf);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void usb_mtp_queue_result(MTPState *s, uint16_t code, uint32_t trans,
+ int argc, uint32_t arg0, uint32_t arg1)
+{
+ MTPControl *c = g_new0(MTPControl, 1);
+
+ c->code = code;
+ c->trans = trans;
+ c->argc = argc;
+ if (argc > 0) {
+ c->argv[0] = arg0;
+ }
+ if (argc > 1) {
+ c->argv[1] = arg1;
+ }
+
+ assert(s->result == NULL);
+ s->result = c;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static MTPData *usb_mtp_get_device_info(MTPState *s, MTPControl *c)
+{
+ static const uint16_t ops[] = {
+ CMD_GET_DEVICE_INFO,
+ CMD_OPEN_SESSION,
+ CMD_CLOSE_SESSION,
+ CMD_GET_STORAGE_IDS,
+ CMD_GET_STORAGE_INFO,
+ CMD_GET_NUM_OBJECTS,
+ CMD_GET_OBJECT_HANDLES,
+ CMD_GET_OBJECT_INFO,
+ CMD_GET_OBJECT,
+ CMD_GET_PARTIAL_OBJECT,
+ };
+ static const uint16_t fmt[] = {
+ FMT_UNDEFINED_OBJECT,
+ FMT_ASSOCIATION,
+ };
+ MTPData *d = usb_mtp_data_alloc(c);
+
+ trace_usb_mtp_op_get_device_info(s->dev.addr);
+
+ usb_mtp_add_u16(d, 100);
+ usb_mtp_add_u32(d, 0xffffffff);
+ usb_mtp_add_u16(d, 0x0101);
+ usb_mtp_add_wstr(d, L"");
+ usb_mtp_add_u16(d, 0x0000);
+
+ usb_mtp_add_u16_array(d, ARRAY_SIZE(ops), ops);
+ usb_mtp_add_u16_array(d, 0, NULL);
+ usb_mtp_add_u16_array(d, 0, NULL);
+ usb_mtp_add_u16_array(d, 0, NULL);
+ usb_mtp_add_u16_array(d, ARRAY_SIZE(fmt), fmt);
+
+ usb_mtp_add_wstr(d, L"" MTP_MANUFACTURER);
+ usb_mtp_add_wstr(d, L"" MTP_PRODUCT);
+ usb_mtp_add_wstr(d, L"0.1");
+ usb_mtp_add_wstr(d, L"0123456789abcdef0123456789abcdef");
+
+ return d;
+}
+
+static MTPData *usb_mtp_get_storage_ids(MTPState *s, MTPControl *c)
+{
+ static const uint32_t ids[] = {
+ QEMU_STORAGE_ID,
+ };
+ MTPData *d = usb_mtp_data_alloc(c);
+
+ trace_usb_mtp_op_get_storage_ids(s->dev.addr);
+
+ usb_mtp_add_u32_array(d, ARRAY_SIZE(ids), ids);
+
+ return d;
+}
+
+static MTPData *usb_mtp_get_storage_info(MTPState *s, MTPControl *c)
+{
+ MTPData *d = usb_mtp_data_alloc(c);
+ struct statvfs buf;
+ int rc;
+
+ trace_usb_mtp_op_get_storage_info(s->dev.addr);
+
+ if (FLAG_SET(s, MTP_FLAG_WRITABLE)) {
+ usb_mtp_add_u16(d, 0x0003);
+ usb_mtp_add_u16(d, 0x0002);
+ usb_mtp_add_u16(d, 0x0000);
+ } else {
+ usb_mtp_add_u16(d, 0x0001);
+ usb_mtp_add_u16(d, 0x0002);
+ usb_mtp_add_u16(d, 0x0001);
+ }
+
+ rc = statvfs(s->root, &buf);
+ if (rc == 0) {
+ usb_mtp_add_u64(d, (uint64_t)buf.f_frsize * buf.f_blocks);
+ usb_mtp_add_u64(d, (uint64_t)buf.f_bavail * buf.f_blocks);
+ usb_mtp_add_u32(d, buf.f_ffree);
+ } else {
+ usb_mtp_add_u64(d, 0xffffffff);
+ usb_mtp_add_u64(d, 0xffffffff);
+ usb_mtp_add_u32(d, 0xffffffff);
+ }
+
+ usb_mtp_add_str(d, s->desc);
+ usb_mtp_add_wstr(d, L"123456789abcdef");
+ return d;
+}
+
+static MTPData *usb_mtp_get_object_handles(MTPState *s, MTPControl *c,
+ MTPObject *o)
+{
+ MTPData *d = usb_mtp_data_alloc(c);
+ uint32_t i, handles[o->nchildren];
+
+ trace_usb_mtp_op_get_object_handles(s->dev.addr, o->handle, o->path);
+
+ for (i = 0; i < o->nchildren; i++) {
+ handles[i] = o->children[i]->handle;
+ }
+ usb_mtp_add_u32_array(d, o->nchildren, handles);
+
+ return d;
+}
+
+static MTPData *usb_mtp_get_object_info(MTPState *s, MTPControl *c,
+ MTPObject *o)
+{
+ MTPData *d = usb_mtp_data_alloc(c);
+
+ trace_usb_mtp_op_get_object_info(s->dev.addr, o->handle, o->path);
+
+ usb_mtp_add_u32(d, QEMU_STORAGE_ID);
+ usb_mtp_add_u16(d, o->format);
+ usb_mtp_add_u16(d, 0);
+ usb_mtp_add_u32(d, o->stat.st_size);
+
+ usb_mtp_add_u16(d, 0);
+ usb_mtp_add_u32(d, 0);
+ usb_mtp_add_u32(d, 0);
+ usb_mtp_add_u32(d, 0);
+ usb_mtp_add_u32(d, 0);
+ usb_mtp_add_u32(d, 0);
+ usb_mtp_add_u32(d, 0);
+
+ if (o->parent) {
+ usb_mtp_add_u32(d, o->parent->handle);
+ } else {
+ usb_mtp_add_u32(d, 0);
+ }
+ if (o->format == FMT_ASSOCIATION) {
+ usb_mtp_add_u16(d, 0x0001);
+ usb_mtp_add_u32(d, 0x00000001);
+ usb_mtp_add_u32(d, 0);
+ } else {
+ usb_mtp_add_u16(d, 0);
+ usb_mtp_add_u32(d, 0);
+ usb_mtp_add_u32(d, 0);
+ }
+
+ usb_mtp_add_str(d, o->name);
+ usb_mtp_add_time(d, o->stat.st_ctime);
+ usb_mtp_add_time(d, o->stat.st_mtime);
+ usb_mtp_add_wstr(d, L"");
+
+ return d;
+}
+
+static MTPData *usb_mtp_get_object(MTPState *s, MTPControl *c,
+ MTPObject *o)
+{
+ MTPData *d = usb_mtp_data_alloc(c);
+
+ trace_usb_mtp_op_get_object(s->dev.addr, o->handle, o->path);
+
+ d->fd = open(o->path, O_RDONLY);
+ if (d->fd == -1) {
+ usb_mtp_data_free(d);
+ return NULL;
+ }
+ d->length = o->stat.st_size;
+ d->alloc = 512;
+ d->data = g_malloc(d->alloc);
+ return d;
+}
+
+static MTPData *usb_mtp_get_partial_object(MTPState *s, MTPControl *c,
+ MTPObject *o)
+{
+ MTPData *d = usb_mtp_data_alloc(c);
+ off_t offset;
+
+ trace_usb_mtp_op_get_partial_object(s->dev.addr, o->handle, o->path,
+ c->argv[1], c->argv[2]);
+
+ d->fd = open(o->path, O_RDONLY);
+ if (d->fd == -1) {
+ usb_mtp_data_free(d);
+ return NULL;
+ }
+
+ offset = c->argv[1];
+ if (offset > o->stat.st_size) {
+ offset = o->stat.st_size;
+ }
+ if (lseek(d->fd, offset, SEEK_SET) < 0) {
+ usb_mtp_data_free(d);
+ return NULL;
+ }
+
+ d->length = c->argv[2];
+ if (d->length > o->stat.st_size - offset) {
+ d->length = o->stat.st_size - offset;
+ }
+
+ return d;
+}
+
+static void usb_mtp_command(MTPState *s, MTPControl *c)
+{
+ MTPData *data_in = NULL;
+ MTPObject *o;
+ uint32_t nres = 0, res0 = 0;
+
+ /* sanity checks */
+ if (c->code >= CMD_CLOSE_SESSION && s->session == 0) {
+ usb_mtp_queue_result(s, RES_SESSION_NOT_OPEN,
+ c->trans, 0, 0, 0);
+ return;
+ }
+
+ /* process commands */
+ switch (c->code) {
+ case CMD_GET_DEVICE_INFO:
+ data_in = usb_mtp_get_device_info(s, c);
+ break;
+ case CMD_OPEN_SESSION:
+ if (s->session) {
+ usb_mtp_queue_result(s, RES_SESSION_ALREADY_OPEN,
+ c->trans, 1, s->session, 0);
+ return;
+ }
+ if (c->argv[0] == 0) {
+ usb_mtp_queue_result(s, RES_INVALID_PARAMETER,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ trace_usb_mtp_op_open_session(s->dev.addr);
+ s->session = c->argv[0];
+ usb_mtp_object_alloc(s, s->next_handle++, NULL, s->root);
+ break;
+ case CMD_CLOSE_SESSION:
+ trace_usb_mtp_op_close_session(s->dev.addr);
+ s->session = 0;
+ s->next_handle = 0;
+ usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
+ assert(QTAILQ_EMPTY(&s->objects));
+ break;
+ case CMD_GET_STORAGE_IDS:
+ data_in = usb_mtp_get_storage_ids(s, c);
+ break;
+ case CMD_GET_STORAGE_INFO:
+ if (c->argv[0] != QEMU_STORAGE_ID &&
+ c->argv[0] != 0xffffffff) {
+ usb_mtp_queue_result(s, RES_INVALID_STORAGE_ID,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ data_in = usb_mtp_get_storage_info(s, c);
+ break;
+ case CMD_GET_NUM_OBJECTS:
+ case CMD_GET_OBJECT_HANDLES:
+ if (c->argv[0] != QEMU_STORAGE_ID &&
+ c->argv[0] != 0xffffffff) {
+ usb_mtp_queue_result(s, RES_INVALID_STORAGE_ID,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ if (c->argv[1] != 0x00000000) {
+ usb_mtp_queue_result(s, RES_SPEC_BY_FORMAT_UNSUPPORTED,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ if (c->argv[2] == 0x00000000 ||
+ c->argv[2] == 0xffffffff) {
+ o = QTAILQ_FIRST(&s->objects);
+ } else {
+ o = usb_mtp_object_lookup(s, c->argv[2]);
+ }
+ if (o == NULL) {
+ usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ if (o->format != FMT_ASSOCIATION) {
+ usb_mtp_queue_result(s, RES_INVALID_PARENT_OBJECT,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ usb_mtp_object_readdir(s, o);
+ if (c->code == CMD_GET_NUM_OBJECTS) {
+ trace_usb_mtp_op_get_num_objects(s->dev.addr, o->handle, o->path);
+ nres = 1;
+ res0 = o->nchildren;
+ } else {
+ data_in = usb_mtp_get_object_handles(s, c, o);
+ }
+ break;
+ case CMD_GET_OBJECT_INFO:
+ o = usb_mtp_object_lookup(s, c->argv[0]);
+ if (o == NULL) {
+ usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ data_in = usb_mtp_get_object_info(s, c, o);
+ break;
+ case CMD_GET_OBJECT:
+ o = usb_mtp_object_lookup(s, c->argv[0]);
+ if (o == NULL) {
+ usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ if (o->format == FMT_ASSOCIATION) {
+ usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ data_in = usb_mtp_get_object(s, c, o);
+ if (data_in == NULL) {
+ usb_mtp_queue_result(s, RES_GENERAL_ERROR,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ break;
+ case CMD_GET_PARTIAL_OBJECT:
+ o = usb_mtp_object_lookup(s, c->argv[0]);
+ if (o == NULL) {
+ usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ if (o->format == FMT_ASSOCIATION) {
+ usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ data_in = usb_mtp_get_partial_object(s, c, o);
+ if (data_in == NULL) {
+ usb_mtp_queue_result(s, RES_GENERAL_ERROR,
+ c->trans, 0, 0, 0);
+ return;
+ }
+ nres = 1;
+ res0 = data_in->length;
+ break;
+ default:
+ trace_usb_mtp_op_unknown(s->dev.addr, c->code);
+ usb_mtp_queue_result(s, RES_OPERATION_NOT_SUPPORTED,
+ c->trans, 0, 0, 0);
+ return;
+ }
+
+ /* return results on success */
+ if (data_in) {
+ assert(s->data_in == NULL);
+ s->data_in = data_in;
+ }
+ usb_mtp_queue_result(s, RES_OK, c->trans, nres, res0, 0);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void usb_mtp_handle_reset(USBDevice *dev)
+{
+ MTPState *s = USB_MTP(dev);
+
+ trace_usb_mtp_reset(s->dev.addr);
+
+ s->session = 0;
+ usb_mtp_data_free(s->data_in);
+ s->data_in = NULL;
+ usb_mtp_data_free(s->data_out);
+ s->data_out = NULL;
+ g_free(s->result);
+ s->result = NULL;
+}
+
+static void usb_mtp_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index,
+ int length, uint8_t *data)
+{
+ int ret;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ trace_usb_mtp_stall(dev->addr, "unknown control request");
+ p->status = USB_RET_STALL;
+}
+
+static void usb_mtp_cancel_packet(USBDevice *dev, USBPacket *p)
+{
+ /* we don't use async packets, so this should never be called */
+ fprintf(stderr, "%s\n", __func__);
+}
+
+static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
+{
+ MTPState *s = USB_MTP(dev);
+ MTPControl cmd;
+ mtp_container container;
+ uint32_t params[5];
+ int i, rc;
+
+ switch (p->ep->nr) {
+ case EP_DATA_IN:
+ if (s->data_out != NULL) {
+ /* guest bug */
+ trace_usb_mtp_stall(s->dev.addr, "awaiting data-out");
+ p->status = USB_RET_STALL;
+ return;
+ }
+ if (p->iov.size < sizeof(container)) {
+ trace_usb_mtp_stall(s->dev.addr, "packet too small");
+ p->status = USB_RET_STALL;
+ return;
+ }
+ if (s->data_in != NULL) {
+ MTPData *d = s->data_in;
+ int dlen = d->length - d->offset;
+ if (d->first) {
+ trace_usb_mtp_data_in(s->dev.addr, d->trans, d->length);
+ container.length = cpu_to_le32(d->length + sizeof(container));
+ container.type = cpu_to_le16(TYPE_DATA);
+ container.code = cpu_to_le16(d->code);
+ container.trans = cpu_to_le32(d->trans);
+ usb_packet_copy(p, &container, sizeof(container));
+ d->first = false;
+ if (dlen > p->iov.size - sizeof(container)) {
+ dlen = p->iov.size - sizeof(container);
+ }
+ } else {
+ if (dlen > p->iov.size) {
+ dlen = p->iov.size;
+ }
+ }
+ if (d->fd == -1) {
+ usb_packet_copy(p, d->data + d->offset, dlen);
+ } else {
+ if (d->alloc < p->iov.size) {
+ d->alloc = p->iov.size;
+ d->data = g_realloc(d->data, d->alloc);
+ }
+ rc = read(d->fd, d->data, dlen);
+ if (rc != dlen) {
+ memset(d->data, 0, dlen);
+ s->result->code = RES_INCOMPLETE_TRANSFER;
+ }
+ usb_packet_copy(p, d->data, dlen);
+ }
+ d->offset += dlen;
+ if (d->offset == d->length) {
+ usb_mtp_data_free(s->data_in);
+ s->data_in = NULL;
+ }
+ } else if (s->result != NULL) {
+ MTPControl *r = s->result;
+ int length = sizeof(container) + r->argc * sizeof(uint32_t);
+ if (r->code == RES_OK) {
+ trace_usb_mtp_success(s->dev.addr, r->trans,
+ (r->argc > 0) ? r->argv[0] : 0,
+ (r->argc > 1) ? r->argv[1] : 0);
+ } else {
+ trace_usb_mtp_error(s->dev.addr, r->code, r->trans,
+ (r->argc > 0) ? r->argv[0] : 0,
+ (r->argc > 1) ? r->argv[1] : 0);
+ }
+ container.length = cpu_to_le32(length);
+ container.type = cpu_to_le16(TYPE_RESPONSE);
+ container.code = cpu_to_le16(r->code);
+ container.trans = cpu_to_le32(r->trans);
+ for (i = 0; i < r->argc; i++) {
+ params[i] = cpu_to_le32(r->argv[i]);
+ }
+ usb_packet_copy(p, &container, sizeof(container));
+ usb_packet_copy(p, &params, length - sizeof(container));
+ g_free(s->result);
+ s->result = NULL;
+ }
+ break;
+ case EP_DATA_OUT:
+ if (p->iov.size < sizeof(container)) {
+ trace_usb_mtp_stall(s->dev.addr, "packet too small");
+ p->status = USB_RET_STALL;
+ return;
+ }
+ usb_packet_copy(p, &container, sizeof(container));
+ switch (le16_to_cpu(container.type)) {
+ case TYPE_COMMAND:
+ if (s->data_in || s->data_out || s->result) {
+ trace_usb_mtp_stall(s->dev.addr, "transaction inflight");
+ p->status = USB_RET_STALL;
+ return;
+ }
+ cmd.code = le16_to_cpu(container.code);
+ cmd.argc = (le32_to_cpu(container.length) - sizeof(container))
+ / sizeof(uint32_t);
+ cmd.trans = le32_to_cpu(container.trans);
+ if (cmd.argc > ARRAY_SIZE(cmd.argv)) {
+ cmd.argc = ARRAY_SIZE(cmd.argv);
+ }
+ if (p->iov.size < sizeof(container) + cmd.argc * sizeof(uint32_t)) {
+ trace_usb_mtp_stall(s->dev.addr, "packet too small");
+ p->status = USB_RET_STALL;
+ return;
+ }
+ usb_packet_copy(p, &params, cmd.argc * sizeof(uint32_t));
+ for (i = 0; i < cmd.argc; i++) {
+ cmd.argv[i] = le32_to_cpu(params[i]);
+ }
+ trace_usb_mtp_command(s->dev.addr, cmd.code, cmd.trans,
+ (cmd.argc > 0) ? cmd.argv[0] : 0,
+ (cmd.argc > 1) ? cmd.argv[1] : 0,
+ (cmd.argc > 2) ? cmd.argv[2] : 0,
+ (cmd.argc > 3) ? cmd.argv[3] : 0,
+ (cmd.argc > 4) ? cmd.argv[4] : 0);
+ usb_mtp_command(s, &cmd);
+ break;
+ default:
+ /* not needed as long as the mtp device is read-only */
+ p->status = USB_RET_STALL;
+ return;
+ }
+ break;
+ case EP_EVENT:
+ p->status = USB_RET_NAK;
+ return;
+ default:
+ trace_usb_mtp_stall(s->dev.addr, "invalid endpoint");
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ if (p->actual_length == 0) {
+ trace_usb_mtp_nak(s->dev.addr, p->ep->nr);
+ p->status = USB_RET_NAK;
+ return;
+ } else {
+ trace_usb_mtp_xfer(s->dev.addr, p->ep->nr, p->actual_length,
+ p->iov.size);
+ return;
+ }
+}
+
+static void usb_mtp_realize(USBDevice *dev, Error **errp)
+{
+ MTPState *s = USB_MTP(dev);
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ QTAILQ_INIT(&s->objects);
+ if (s->desc == NULL) {
+ if (s->root == NULL) {
+ error_setg(errp, "usb-mtp: x-root property must be configured");
+ return;
+ }
+ s->desc = strrchr(s->root, '/');
+ if (s->desc && s->desc[0]) {
+ s->desc = g_strdup(s->desc + 1);
+ } else {
+ s->desc = g_strdup("none");
+ }
+ }
+}
+
+static const VMStateDescription vmstate_usb_mtp = {
+ .name = "usb-mtp",
+ .unmigratable = 1,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, MTPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property mtp_properties[] = {
+ DEFINE_PROP_STRING("x-root", MTPState, root),
+ DEFINE_PROP_STRING("desc", MTPState, desc),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_mtp_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_mtp_realize;
+ uc->product_desc = "QEMU USB MTP";
+ uc->usb_desc = &desc;
+ uc->cancel_packet = usb_mtp_cancel_packet;
+ uc->handle_attach = usb_desc_attach;
+ uc->handle_reset = usb_mtp_handle_reset;
+ uc->handle_control = usb_mtp_handle_control;
+ uc->handle_data = usb_mtp_handle_data;
+ dc->fw_name = "mtp";
+ dc->vmsd = &vmstate_usb_mtp;
+ dc->props = mtp_properties;
+}
+
+static TypeInfo mtp_info = {
+ .name = TYPE_USB_MTP,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(MTPState),
+ .class_init = usb_mtp_class_initfn,
+};
+
+static void usb_mtp_register_types(void)
+{
+ type_register_static(&mtp_info);
+}
+
+type_init(usb_mtp_register_types)
diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c
new file mode 100644
index 00000000..7800ceea
--- /dev/null
+++ b/hw/usb/dev-network.c
@@ -0,0 +1,1448 @@
+/*
+ * QEMU USB Net devices
+ *
+ * Copyright (c) 2006 Thomas Sailer
+ * Copyright (c) 2008 Andrzej Zaborowski
+ *
+ * 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.
+ */
+
+#include "qemu-common.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "net/net.h"
+#include "qemu/error-report.h"
+#include "qemu/queue.h"
+#include "qemu/config-file.h"
+#include "sysemu/sysemu.h"
+#include "qemu/iov.h"
+
+/*#define TRAFFIC_DEBUG*/
+/* Thanks to NetChip Technologies for donating this product ID.
+ * It's for devices with only CDC Ethernet configurations.
+ */
+#define CDC_VENDOR_NUM 0x0525 /* NetChip */
+#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */
+/* For hardware that can talk RNDIS and either of the above protocols,
+ * use this ID ... the windows INF files will know it.
+ */
+#define RNDIS_VENDOR_NUM 0x0525 /* NetChip */
+#define RNDIS_PRODUCT_NUM 0xa4a2 /* Ethernet/RNDIS Gadget */
+
+enum usbstring_idx {
+ STRING_MANUFACTURER = 1,
+ STRING_PRODUCT,
+ STRING_ETHADDR,
+ STRING_DATA,
+ STRING_CONTROL,
+ STRING_RNDIS_CONTROL,
+ STRING_CDC,
+ STRING_SUBSET,
+ STRING_RNDIS,
+ STRING_SERIALNUMBER,
+};
+
+#define DEV_CONFIG_VALUE 1 /* CDC or a subset */
+#define DEV_RNDIS_CONFIG_VALUE 2 /* RNDIS; optional */
+
+#define USB_CDC_SUBCLASS_ACM 0x02
+#define USB_CDC_SUBCLASS_ETHERNET 0x06
+
+#define USB_CDC_PROTO_NONE 0
+#define USB_CDC_ACM_PROTO_VENDOR 0xff
+
+#define USB_CDC_HEADER_TYPE 0x00 /* header_desc */
+#define USB_CDC_CALL_MANAGEMENT_TYPE 0x01 /* call_mgmt_descriptor */
+#define USB_CDC_ACM_TYPE 0x02 /* acm_descriptor */
+#define USB_CDC_UNION_TYPE 0x06 /* union_desc */
+#define USB_CDC_ETHERNET_TYPE 0x0f /* ether_desc */
+
+#define USB_CDC_SEND_ENCAPSULATED_COMMAND 0x00
+#define USB_CDC_GET_ENCAPSULATED_RESPONSE 0x01
+#define USB_CDC_REQ_SET_LINE_CODING 0x20
+#define USB_CDC_REQ_GET_LINE_CODING 0x21
+#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22
+#define USB_CDC_REQ_SEND_BREAK 0x23
+#define USB_CDC_SET_ETHERNET_MULTICAST_FILTERS 0x40
+#define USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER 0x41
+#define USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER 0x42
+#define USB_CDC_SET_ETHERNET_PACKET_FILTER 0x43
+#define USB_CDC_GET_ETHERNET_STATISTIC 0x44
+
+#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */
+#define STATUS_BYTECOUNT 16 /* 8 byte header + data */
+
+#define ETH_FRAME_LEN 1514 /* Max. octets in frame sans FCS */
+
+static const USBDescStrings usb_net_stringtable = {
+ [STRING_MANUFACTURER] = "QEMU",
+ [STRING_PRODUCT] = "RNDIS/QEMU USB Network Device",
+ [STRING_ETHADDR] = "400102030405",
+ [STRING_DATA] = "QEMU USB Net Data Interface",
+ [STRING_CONTROL] = "QEMU USB Net Control Interface",
+ [STRING_RNDIS_CONTROL] = "QEMU USB Net RNDIS Control Interface",
+ [STRING_CDC] = "QEMU USB Net CDC",
+ [STRING_SUBSET] = "QEMU USB Net Subset",
+ [STRING_RNDIS] = "QEMU USB Net RNDIS",
+ [STRING_SERIALNUMBER] = "1",
+};
+
+static const USBDescIface desc_iface_rndis[] = {
+ {
+ /* RNDIS Control Interface */
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_COMM,
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
+ .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR,
+ .iInterface = STRING_RNDIS_CONTROL,
+ .ndesc = 4,
+ .descs = (USBDescOther[]) {
+ {
+ /* Header Descriptor */
+ .data = (uint8_t[]) {
+ 0x05, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ USB_CDC_HEADER_TYPE, /* u8 bDescriptorSubType */
+ 0x10, 0x01, /* le16 bcdCDC */
+ },
+ },{
+ /* Call Management Descriptor */
+ .data = (uint8_t[]) {
+ 0x05, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ USB_CDC_CALL_MANAGEMENT_TYPE, /* u8 bDescriptorSubType */
+ 0x00, /* u8 bmCapabilities */
+ 0x01, /* u8 bDataInterface */
+ },
+ },{
+ /* ACM Descriptor */
+ .data = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ USB_CDC_ACM_TYPE, /* u8 bDescriptorSubType */
+ 0x00, /* u8 bmCapabilities */
+ },
+ },{
+ /* Union Descriptor */
+ .data = (uint8_t[]) {
+ 0x05, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ USB_CDC_UNION_TYPE, /* u8 bDescriptorSubType */
+ 0x00, /* u8 bMasterInterface0 */
+ 0x01, /* u8 bSlaveInterface0 */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = STATUS_BYTECOUNT,
+ .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC,
+ },
+ }
+ },{
+ /* RNDIS Data Interface */
+ .bInterfaceNumber = 1,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_CDC_DATA,
+ .iInterface = STRING_DATA,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 0x40,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 0x40,
+ }
+ }
+ }
+};
+
+static const USBDescIface desc_iface_cdc[] = {
+ {
+ /* CDC Control Interface */
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_COMM,
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET,
+ .bInterfaceProtocol = USB_CDC_PROTO_NONE,
+ .iInterface = STRING_CONTROL,
+ .ndesc = 3,
+ .descs = (USBDescOther[]) {
+ {
+ /* Header Descriptor */
+ .data = (uint8_t[]) {
+ 0x05, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ USB_CDC_HEADER_TYPE, /* u8 bDescriptorSubType */
+ 0x10, 0x01, /* le16 bcdCDC */
+ },
+ },{
+ /* Union Descriptor */
+ .data = (uint8_t[]) {
+ 0x05, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ USB_CDC_UNION_TYPE, /* u8 bDescriptorSubType */
+ 0x00, /* u8 bMasterInterface0 */
+ 0x01, /* u8 bSlaveInterface0 */
+ },
+ },{
+ /* Ethernet Descriptor */
+ .data = (uint8_t[]) {
+ 0x0d, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ USB_CDC_ETHERNET_TYPE, /* u8 bDescriptorSubType */
+ STRING_ETHADDR, /* u8 iMACAddress */
+ 0x00, 0x00, 0x00, 0x00, /* le32 bmEthernetStatistics */
+ ETH_FRAME_LEN & 0xff,
+ ETH_FRAME_LEN >> 8, /* le16 wMaxSegmentSize */
+ 0x00, 0x00, /* le16 wNumberMCFilters */
+ 0x00, /* u8 bNumberPowerFilters */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = STATUS_BYTECOUNT,
+ .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC,
+ },
+ }
+ },{
+ /* CDC Data Interface (off) */
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_CDC_DATA,
+ },{
+ /* CDC Data Interface */
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 1,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_CDC_DATA,
+ .iInterface = STRING_DATA,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 0x40,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 0x40,
+ }
+ }
+ }
+};
+
+static const USBDescDevice desc_device_net = {
+ .bcdUSB = 0x0200,
+ .bDeviceClass = USB_CLASS_COMM,
+ .bMaxPacketSize0 = 0x40,
+ .bNumConfigurations = 2,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 2,
+ .bConfigurationValue = DEV_RNDIS_CONFIG_VALUE,
+ .iConfiguration = STRING_RNDIS,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .bMaxPower = 0x32,
+ .nif = ARRAY_SIZE(desc_iface_rndis),
+ .ifs = desc_iface_rndis,
+ },{
+ .bNumInterfaces = 2,
+ .bConfigurationValue = DEV_CONFIG_VALUE,
+ .iConfiguration = STRING_CDC,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .bMaxPower = 0x32,
+ .nif = ARRAY_SIZE(desc_iface_cdc),
+ .ifs = desc_iface_cdc,
+ }
+ },
+};
+
+static const USBDesc desc_net = {
+ .id = {
+ .idVendor = RNDIS_VENDOR_NUM,
+ .idProduct = RNDIS_PRODUCT_NUM,
+ .bcdDevice = 0,
+ .iManufacturer = STRING_MANUFACTURER,
+ .iProduct = STRING_PRODUCT,
+ .iSerialNumber = STRING_SERIALNUMBER,
+ },
+ .full = &desc_device_net,
+ .str = usb_net_stringtable,
+};
+
+/*
+ * RNDIS Definitions - in theory not specific to USB.
+ */
+#define RNDIS_MAXIMUM_FRAME_SIZE 1518
+#define RNDIS_MAX_TOTAL_SIZE 1558
+
+/* Remote NDIS Versions */
+#define RNDIS_MAJOR_VERSION 1
+#define RNDIS_MINOR_VERSION 0
+
+/* Status Values */
+#define RNDIS_STATUS_SUCCESS 0x00000000U /* Success */
+#define RNDIS_STATUS_FAILURE 0xc0000001U /* Unspecified error */
+#define RNDIS_STATUS_INVALID_DATA 0xc0010015U /* Invalid data */
+#define RNDIS_STATUS_NOT_SUPPORTED 0xc00000bbU /* Unsupported request */
+#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000bU /* Device connected */
+#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000cU /* Device disconnected */
+
+/* Message Set for Connectionless (802.3) Devices */
+enum {
+ RNDIS_PACKET_MSG = 1,
+ RNDIS_INITIALIZE_MSG = 2, /* Initialize device */
+ RNDIS_HALT_MSG = 3,
+ RNDIS_QUERY_MSG = 4,
+ RNDIS_SET_MSG = 5,
+ RNDIS_RESET_MSG = 6,
+ RNDIS_INDICATE_STATUS_MSG = 7,
+ RNDIS_KEEPALIVE_MSG = 8,
+};
+
+/* Message completion */
+enum {
+ RNDIS_INITIALIZE_CMPLT = 0x80000002U,
+ RNDIS_QUERY_CMPLT = 0x80000004U,
+ RNDIS_SET_CMPLT = 0x80000005U,
+ RNDIS_RESET_CMPLT = 0x80000006U,
+ RNDIS_KEEPALIVE_CMPLT = 0x80000008U,
+};
+
+/* Device Flags */
+enum {
+ RNDIS_DF_CONNECTIONLESS = 1,
+ RNDIS_DF_CONNECTIONORIENTED = 2,
+};
+
+#define RNDIS_MEDIUM_802_3 0x00000000U
+
+/* from drivers/net/sk98lin/h/skgepnmi.h */
+#define OID_PNP_CAPABILITIES 0xfd010100
+#define OID_PNP_SET_POWER 0xfd010101
+#define OID_PNP_QUERY_POWER 0xfd010102
+#define OID_PNP_ADD_WAKE_UP_PATTERN 0xfd010103
+#define OID_PNP_REMOVE_WAKE_UP_PATTERN 0xfd010104
+#define OID_PNP_ENABLE_WAKE_UP 0xfd010106
+
+typedef uint32_t le32;
+
+typedef struct rndis_init_msg_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+ le32 MajorVersion;
+ le32 MinorVersion;
+ le32 MaxTransferSize;
+} rndis_init_msg_type;
+
+typedef struct rndis_init_cmplt_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+ le32 Status;
+ le32 MajorVersion;
+ le32 MinorVersion;
+ le32 DeviceFlags;
+ le32 Medium;
+ le32 MaxPacketsPerTransfer;
+ le32 MaxTransferSize;
+ le32 PacketAlignmentFactor;
+ le32 AFListOffset;
+ le32 AFListSize;
+} rndis_init_cmplt_type;
+
+typedef struct rndis_halt_msg_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+} rndis_halt_msg_type;
+
+typedef struct rndis_query_msg_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+ le32 OID;
+ le32 InformationBufferLength;
+ le32 InformationBufferOffset;
+ le32 DeviceVcHandle;
+} rndis_query_msg_type;
+
+typedef struct rndis_query_cmplt_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+ le32 Status;
+ le32 InformationBufferLength;
+ le32 InformationBufferOffset;
+} rndis_query_cmplt_type;
+
+typedef struct rndis_set_msg_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+ le32 OID;
+ le32 InformationBufferLength;
+ le32 InformationBufferOffset;
+ le32 DeviceVcHandle;
+} rndis_set_msg_type;
+
+typedef struct rndis_set_cmplt_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+ le32 Status;
+} rndis_set_cmplt_type;
+
+typedef struct rndis_reset_msg_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 Reserved;
+} rndis_reset_msg_type;
+
+typedef struct rndis_reset_cmplt_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 Status;
+ le32 AddressingReset;
+} rndis_reset_cmplt_type;
+
+typedef struct rndis_indicate_status_msg_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 Status;
+ le32 StatusBufferLength;
+ le32 StatusBufferOffset;
+} rndis_indicate_status_msg_type;
+
+typedef struct rndis_keepalive_msg_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+} rndis_keepalive_msg_type;
+
+typedef struct rndis_keepalive_cmplt_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 RequestID;
+ le32 Status;
+} rndis_keepalive_cmplt_type;
+
+struct rndis_packet_msg_type {
+ le32 MessageType;
+ le32 MessageLength;
+ le32 DataOffset;
+ le32 DataLength;
+ le32 OOBDataOffset;
+ le32 OOBDataLength;
+ le32 NumOOBDataElements;
+ le32 PerPacketInfoOffset;
+ le32 PerPacketInfoLength;
+ le32 VcHandle;
+ le32 Reserved;
+};
+
+struct rndis_config_parameter {
+ le32 ParameterNameOffset;
+ le32 ParameterNameLength;
+ le32 ParameterType;
+ le32 ParameterValueOffset;
+ le32 ParameterValueLength;
+};
+
+/* implementation specific */
+enum rndis_state
+{
+ RNDIS_UNINITIALIZED,
+ RNDIS_INITIALIZED,
+ RNDIS_DATA_INITIALIZED,
+};
+
+/* from ndis.h */
+enum ndis_oid {
+ /* Required Object IDs (OIDs) */
+ OID_GEN_SUPPORTED_LIST = 0x00010101,
+ OID_GEN_HARDWARE_STATUS = 0x00010102,
+ OID_GEN_MEDIA_SUPPORTED = 0x00010103,
+ OID_GEN_MEDIA_IN_USE = 0x00010104,
+ OID_GEN_MAXIMUM_LOOKAHEAD = 0x00010105,
+ OID_GEN_MAXIMUM_FRAME_SIZE = 0x00010106,
+ OID_GEN_LINK_SPEED = 0x00010107,
+ OID_GEN_TRANSMIT_BUFFER_SPACE = 0x00010108,
+ OID_GEN_RECEIVE_BUFFER_SPACE = 0x00010109,
+ OID_GEN_TRANSMIT_BLOCK_SIZE = 0x0001010a,
+ OID_GEN_RECEIVE_BLOCK_SIZE = 0x0001010b,
+ OID_GEN_VENDOR_ID = 0x0001010c,
+ OID_GEN_VENDOR_DESCRIPTION = 0x0001010d,
+ OID_GEN_CURRENT_PACKET_FILTER = 0x0001010e,
+ OID_GEN_CURRENT_LOOKAHEAD = 0x0001010f,
+ OID_GEN_DRIVER_VERSION = 0x00010110,
+ OID_GEN_MAXIMUM_TOTAL_SIZE = 0x00010111,
+ OID_GEN_PROTOCOL_OPTIONS = 0x00010112,
+ OID_GEN_MAC_OPTIONS = 0x00010113,
+ OID_GEN_MEDIA_CONNECT_STATUS = 0x00010114,
+ OID_GEN_MAXIMUM_SEND_PACKETS = 0x00010115,
+ OID_GEN_VENDOR_DRIVER_VERSION = 0x00010116,
+ OID_GEN_SUPPORTED_GUIDS = 0x00010117,
+ OID_GEN_NETWORK_LAYER_ADDRESSES = 0x00010118,
+ OID_GEN_TRANSPORT_HEADER_OFFSET = 0x00010119,
+ OID_GEN_MACHINE_NAME = 0x0001021a,
+ OID_GEN_RNDIS_CONFIG_PARAMETER = 0x0001021b,
+ OID_GEN_VLAN_ID = 0x0001021c,
+
+ /* Optional OIDs */
+ OID_GEN_MEDIA_CAPABILITIES = 0x00010201,
+ OID_GEN_PHYSICAL_MEDIUM = 0x00010202,
+
+ /* Required statistics OIDs */
+ OID_GEN_XMIT_OK = 0x00020101,
+ OID_GEN_RCV_OK = 0x00020102,
+ OID_GEN_XMIT_ERROR = 0x00020103,
+ OID_GEN_RCV_ERROR = 0x00020104,
+ OID_GEN_RCV_NO_BUFFER = 0x00020105,
+
+ /* Optional statistics OIDs */
+ OID_GEN_DIRECTED_BYTES_XMIT = 0x00020201,
+ OID_GEN_DIRECTED_FRAMES_XMIT = 0x00020202,
+ OID_GEN_MULTICAST_BYTES_XMIT = 0x00020203,
+ OID_GEN_MULTICAST_FRAMES_XMIT = 0x00020204,
+ OID_GEN_BROADCAST_BYTES_XMIT = 0x00020205,
+ OID_GEN_BROADCAST_FRAMES_XMIT = 0x00020206,
+ OID_GEN_DIRECTED_BYTES_RCV = 0x00020207,
+ OID_GEN_DIRECTED_FRAMES_RCV = 0x00020208,
+ OID_GEN_MULTICAST_BYTES_RCV = 0x00020209,
+ OID_GEN_MULTICAST_FRAMES_RCV = 0x0002020a,
+ OID_GEN_BROADCAST_BYTES_RCV = 0x0002020b,
+ OID_GEN_BROADCAST_FRAMES_RCV = 0x0002020c,
+ OID_GEN_RCV_CRC_ERROR = 0x0002020d,
+ OID_GEN_TRANSMIT_QUEUE_LENGTH = 0x0002020e,
+ OID_GEN_GET_TIME_CAPS = 0x0002020f,
+ OID_GEN_GET_NETCARD_TIME = 0x00020210,
+ OID_GEN_NETCARD_LOAD = 0x00020211,
+ OID_GEN_DEVICE_PROFILE = 0x00020212,
+ OID_GEN_INIT_TIME_MS = 0x00020213,
+ OID_GEN_RESET_COUNTS = 0x00020214,
+ OID_GEN_MEDIA_SENSE_COUNTS = 0x00020215,
+ OID_GEN_FRIENDLY_NAME = 0x00020216,
+ OID_GEN_MINIPORT_INFO = 0x00020217,
+ OID_GEN_RESET_VERIFY_PARAMETERS = 0x00020218,
+
+ /* IEEE 802.3 (Ethernet) OIDs */
+ OID_802_3_PERMANENT_ADDRESS = 0x01010101,
+ OID_802_3_CURRENT_ADDRESS = 0x01010102,
+ OID_802_3_MULTICAST_LIST = 0x01010103,
+ OID_802_3_MAXIMUM_LIST_SIZE = 0x01010104,
+ OID_802_3_MAC_OPTIONS = 0x01010105,
+ OID_802_3_RCV_ERROR_ALIGNMENT = 0x01020101,
+ OID_802_3_XMIT_ONE_COLLISION = 0x01020102,
+ OID_802_3_XMIT_MORE_COLLISIONS = 0x01020103,
+ OID_802_3_XMIT_DEFERRED = 0x01020201,
+ OID_802_3_XMIT_MAX_COLLISIONS = 0x01020202,
+ OID_802_3_RCV_OVERRUN = 0x01020203,
+ OID_802_3_XMIT_UNDERRUN = 0x01020204,
+ OID_802_3_XMIT_HEARTBEAT_FAILURE = 0x01020205,
+ OID_802_3_XMIT_TIMES_CRS_LOST = 0x01020206,
+ OID_802_3_XMIT_LATE_COLLISIONS = 0x01020207,
+};
+
+static const uint32_t oid_supported_list[] =
+{
+ /* the general stuff */
+ OID_GEN_SUPPORTED_LIST,
+ OID_GEN_HARDWARE_STATUS,
+ OID_GEN_MEDIA_SUPPORTED,
+ OID_GEN_MEDIA_IN_USE,
+ OID_GEN_MAXIMUM_FRAME_SIZE,
+ OID_GEN_LINK_SPEED,
+ OID_GEN_TRANSMIT_BLOCK_SIZE,
+ OID_GEN_RECEIVE_BLOCK_SIZE,
+ OID_GEN_VENDOR_ID,
+ OID_GEN_VENDOR_DESCRIPTION,
+ OID_GEN_VENDOR_DRIVER_VERSION,
+ OID_GEN_CURRENT_PACKET_FILTER,
+ OID_GEN_MAXIMUM_TOTAL_SIZE,
+ OID_GEN_MEDIA_CONNECT_STATUS,
+ OID_GEN_PHYSICAL_MEDIUM,
+
+ /* the statistical stuff */
+ OID_GEN_XMIT_OK,
+ OID_GEN_RCV_OK,
+ OID_GEN_XMIT_ERROR,
+ OID_GEN_RCV_ERROR,
+ OID_GEN_RCV_NO_BUFFER,
+
+ /* IEEE 802.3 */
+ /* the general stuff */
+ OID_802_3_PERMANENT_ADDRESS,
+ OID_802_3_CURRENT_ADDRESS,
+ OID_802_3_MULTICAST_LIST,
+ OID_802_3_MAC_OPTIONS,
+ OID_802_3_MAXIMUM_LIST_SIZE,
+
+ /* the statistical stuff */
+ OID_802_3_RCV_ERROR_ALIGNMENT,
+ OID_802_3_XMIT_ONE_COLLISION,
+ OID_802_3_XMIT_MORE_COLLISIONS,
+};
+
+#define NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA (1 << 0)
+#define NDIS_MAC_OPTION_RECEIVE_SERIALIZED (1 << 1)
+#define NDIS_MAC_OPTION_TRANSFERS_NOT_PEND (1 << 2)
+#define NDIS_MAC_OPTION_NO_LOOPBACK (1 << 3)
+#define NDIS_MAC_OPTION_FULL_DUPLEX (1 << 4)
+#define NDIS_MAC_OPTION_EOTX_INDICATION (1 << 5)
+#define NDIS_MAC_OPTION_8021P_PRIORITY (1 << 6)
+
+struct rndis_response {
+ QTAILQ_ENTRY(rndis_response) entries;
+ uint32_t length;
+ uint8_t buf[0];
+};
+
+typedef struct USBNetState {
+ USBDevice dev;
+
+ enum rndis_state rndis_state;
+ uint32_t medium;
+ uint32_t speed;
+ uint32_t media_state;
+ uint16_t filter;
+ uint32_t vendorid;
+
+ unsigned int out_ptr;
+ uint8_t out_buf[2048];
+
+ unsigned int in_ptr, in_len;
+ uint8_t in_buf[2048];
+
+ USBEndpoint *intr;
+
+ char usbstring_mac[13];
+ NICState *nic;
+ NICConf conf;
+ QTAILQ_HEAD(rndis_resp_head, rndis_response) rndis_resp;
+} USBNetState;
+
+#define TYPE_USB_NET "usb-net"
+#define USB_NET(obj) OBJECT_CHECK(USBNetState, (obj), TYPE_USB_NET)
+
+static int is_rndis(USBNetState *s)
+{
+ return s->dev.config->bConfigurationValue == DEV_RNDIS_CONFIG_VALUE;
+}
+
+static int ndis_query(USBNetState *s, uint32_t oid,
+ uint8_t *inbuf, unsigned int inlen, uint8_t *outbuf,
+ size_t outlen)
+{
+ unsigned int i;
+
+ switch (oid) {
+ /* general oids (table 4-1) */
+ /* mandatory */
+ case OID_GEN_SUPPORTED_LIST:
+ for (i = 0; i < ARRAY_SIZE(oid_supported_list); i++)
+ ((le32 *) outbuf)[i] = cpu_to_le32(oid_supported_list[i]);
+ return sizeof(oid_supported_list);
+
+ /* mandatory */
+ case OID_GEN_HARDWARE_STATUS:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_MEDIA_SUPPORTED:
+ *((le32 *) outbuf) = cpu_to_le32(s->medium);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_MEDIA_IN_USE:
+ *((le32 *) outbuf) = cpu_to_le32(s->medium);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_MAXIMUM_FRAME_SIZE:
+ *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_LINK_SPEED:
+ *((le32 *) outbuf) = cpu_to_le32(s->speed);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_TRANSMIT_BLOCK_SIZE:
+ *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_RECEIVE_BLOCK_SIZE:
+ *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_VENDOR_ID:
+ *((le32 *) outbuf) = cpu_to_le32(s->vendorid);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_VENDOR_DESCRIPTION:
+ pstrcpy((char *)outbuf, outlen, "QEMU USB RNDIS Net");
+ return strlen((char *)outbuf) + 1;
+
+ case OID_GEN_VENDOR_DRIVER_VERSION:
+ *((le32 *) outbuf) = cpu_to_le32(1);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_CURRENT_PACKET_FILTER:
+ *((le32 *) outbuf) = cpu_to_le32(s->filter);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_MAXIMUM_TOTAL_SIZE:
+ *((le32 *) outbuf) = cpu_to_le32(RNDIS_MAX_TOTAL_SIZE);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_MEDIA_CONNECT_STATUS:
+ *((le32 *) outbuf) = cpu_to_le32(s->media_state);
+ return sizeof(le32);
+
+ case OID_GEN_PHYSICAL_MEDIUM:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ case OID_GEN_MAC_OPTIONS:
+ *((le32 *) outbuf) = cpu_to_le32(
+ NDIS_MAC_OPTION_RECEIVE_SERIALIZED |
+ NDIS_MAC_OPTION_FULL_DUPLEX);
+ return sizeof(le32);
+
+ /* statistics OIDs (table 4-2) */
+ /* mandatory */
+ case OID_GEN_XMIT_OK:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_RCV_OK:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_XMIT_ERROR:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_RCV_ERROR:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_GEN_RCV_NO_BUFFER:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ /* ieee802.3 OIDs (table 4-3) */
+ /* mandatory */
+ case OID_802_3_PERMANENT_ADDRESS:
+ memcpy(outbuf, s->conf.macaddr.a, 6);
+ return 6;
+
+ /* mandatory */
+ case OID_802_3_CURRENT_ADDRESS:
+ memcpy(outbuf, s->conf.macaddr.a, 6);
+ return 6;
+
+ /* mandatory */
+ case OID_802_3_MULTICAST_LIST:
+ *((le32 *) outbuf) = cpu_to_le32(0xe0000000);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_802_3_MAXIMUM_LIST_SIZE:
+ *((le32 *) outbuf) = cpu_to_le32(1);
+ return sizeof(le32);
+
+ case OID_802_3_MAC_OPTIONS:
+ return 0;
+
+ /* ieee802.3 statistics OIDs (table 4-4) */
+ /* mandatory */
+ case OID_802_3_RCV_ERROR_ALIGNMENT:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_802_3_XMIT_ONE_COLLISION:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ /* mandatory */
+ case OID_802_3_XMIT_MORE_COLLISIONS:
+ *((le32 *) outbuf) = cpu_to_le32(0);
+ return sizeof(le32);
+
+ default:
+ fprintf(stderr, "usbnet: unknown OID 0x%08x\n", oid);
+ return 0;
+ }
+ return -1;
+}
+
+static int ndis_set(USBNetState *s, uint32_t oid,
+ uint8_t *inbuf, unsigned int inlen)
+{
+ switch (oid) {
+ case OID_GEN_CURRENT_PACKET_FILTER:
+ s->filter = le32_to_cpup((le32 *) inbuf);
+ if (s->filter) {
+ s->rndis_state = RNDIS_DATA_INITIALIZED;
+ } else {
+ s->rndis_state = RNDIS_INITIALIZED;
+ }
+ return 0;
+
+ case OID_802_3_MULTICAST_LIST:
+ return 0;
+ }
+ return -1;
+}
+
+static int rndis_get_response(USBNetState *s, uint8_t *buf)
+{
+ int ret = 0;
+ struct rndis_response *r = s->rndis_resp.tqh_first;
+
+ if (!r)
+ return ret;
+
+ QTAILQ_REMOVE(&s->rndis_resp, r, entries);
+ ret = r->length;
+ memcpy(buf, r->buf, r->length);
+ g_free(r);
+
+ return ret;
+}
+
+static void *rndis_queue_response(USBNetState *s, unsigned int length)
+{
+ struct rndis_response *r =
+ g_malloc0(sizeof(struct rndis_response) + length);
+
+ if (QTAILQ_EMPTY(&s->rndis_resp)) {
+ usb_wakeup(s->intr, 0);
+ }
+
+ QTAILQ_INSERT_TAIL(&s->rndis_resp, r, entries);
+ r->length = length;
+
+ return &r->buf[0];
+}
+
+static void rndis_clear_responsequeue(USBNetState *s)
+{
+ struct rndis_response *r;
+
+ while ((r = s->rndis_resp.tqh_first)) {
+ QTAILQ_REMOVE(&s->rndis_resp, r, entries);
+ g_free(r);
+ }
+}
+
+static int rndis_init_response(USBNetState *s, rndis_init_msg_type *buf)
+{
+ rndis_init_cmplt_type *resp =
+ rndis_queue_response(s, sizeof(rndis_init_cmplt_type));
+
+ if (!resp)
+ return USB_RET_STALL;
+
+ resp->MessageType = cpu_to_le32(RNDIS_INITIALIZE_CMPLT);
+ resp->MessageLength = cpu_to_le32(sizeof(rndis_init_cmplt_type));
+ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+ resp->MajorVersion = cpu_to_le32(RNDIS_MAJOR_VERSION);
+ resp->MinorVersion = cpu_to_le32(RNDIS_MINOR_VERSION);
+ resp->DeviceFlags = cpu_to_le32(RNDIS_DF_CONNECTIONLESS);
+ resp->Medium = cpu_to_le32(RNDIS_MEDIUM_802_3);
+ resp->MaxPacketsPerTransfer = cpu_to_le32(1);
+ resp->MaxTransferSize = cpu_to_le32(ETH_FRAME_LEN +
+ sizeof(struct rndis_packet_msg_type) + 22);
+ resp->PacketAlignmentFactor = cpu_to_le32(0);
+ resp->AFListOffset = cpu_to_le32(0);
+ resp->AFListSize = cpu_to_le32(0);
+ return 0;
+}
+
+static int rndis_query_response(USBNetState *s,
+ rndis_query_msg_type *buf, unsigned int length)
+{
+ rndis_query_cmplt_type *resp;
+ /* oid_supported_list is the largest data reply */
+ uint8_t infobuf[sizeof(oid_supported_list)];
+ uint32_t bufoffs, buflen;
+ int infobuflen;
+ unsigned int resplen;
+
+ bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8;
+ buflen = le32_to_cpu(buf->InformationBufferLength);
+ if (bufoffs + buflen > length)
+ return USB_RET_STALL;
+
+ infobuflen = ndis_query(s, le32_to_cpu(buf->OID),
+ bufoffs + (uint8_t *) buf, buflen, infobuf,
+ sizeof(infobuf));
+ resplen = sizeof(rndis_query_cmplt_type) +
+ ((infobuflen < 0) ? 0 : infobuflen);
+ resp = rndis_queue_response(s, resplen);
+ if (!resp)
+ return USB_RET_STALL;
+
+ resp->MessageType = cpu_to_le32(RNDIS_QUERY_CMPLT);
+ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+ resp->MessageLength = cpu_to_le32(resplen);
+
+ if (infobuflen < 0) {
+ /* OID not supported */
+ resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
+ resp->InformationBufferLength = cpu_to_le32(0);
+ resp->InformationBufferOffset = cpu_to_le32(0);
+ return 0;
+ }
+
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+ resp->InformationBufferOffset =
+ cpu_to_le32(infobuflen ? sizeof(rndis_query_cmplt_type) - 8 : 0);
+ resp->InformationBufferLength = cpu_to_le32(infobuflen);
+ memcpy(resp + 1, infobuf, infobuflen);
+
+ return 0;
+}
+
+static int rndis_set_response(USBNetState *s,
+ rndis_set_msg_type *buf, unsigned int length)
+{
+ rndis_set_cmplt_type *resp =
+ rndis_queue_response(s, sizeof(rndis_set_cmplt_type));
+ uint32_t bufoffs, buflen;
+ int ret;
+
+ if (!resp)
+ return USB_RET_STALL;
+
+ bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8;
+ buflen = le32_to_cpu(buf->InformationBufferLength);
+ if (bufoffs + buflen > length)
+ return USB_RET_STALL;
+
+ ret = ndis_set(s, le32_to_cpu(buf->OID),
+ bufoffs + (uint8_t *) buf, buflen);
+ resp->MessageType = cpu_to_le32(RNDIS_SET_CMPLT);
+ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+ resp->MessageLength = cpu_to_le32(sizeof(rndis_set_cmplt_type));
+ if (ret < 0) {
+ /* OID not supported */
+ resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
+ return 0;
+ }
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+
+ return 0;
+}
+
+static int rndis_reset_response(USBNetState *s, rndis_reset_msg_type *buf)
+{
+ rndis_reset_cmplt_type *resp =
+ rndis_queue_response(s, sizeof(rndis_reset_cmplt_type));
+
+ if (!resp)
+ return USB_RET_STALL;
+
+ resp->MessageType = cpu_to_le32(RNDIS_RESET_CMPLT);
+ resp->MessageLength = cpu_to_le32(sizeof(rndis_reset_cmplt_type));
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+ resp->AddressingReset = cpu_to_le32(1); /* reset information */
+
+ return 0;
+}
+
+static int rndis_keepalive_response(USBNetState *s,
+ rndis_keepalive_msg_type *buf)
+{
+ rndis_keepalive_cmplt_type *resp =
+ rndis_queue_response(s, sizeof(rndis_keepalive_cmplt_type));
+
+ if (!resp)
+ return USB_RET_STALL;
+
+ resp->MessageType = cpu_to_le32(RNDIS_KEEPALIVE_CMPLT);
+ resp->MessageLength = cpu_to_le32(sizeof(rndis_keepalive_cmplt_type));
+ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+
+ return 0;
+}
+
+/* Prepare to receive the next packet */
+static void usb_net_reset_in_buf(USBNetState *s)
+{
+ s->in_ptr = s->in_len = 0;
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+}
+
+static int rndis_parse(USBNetState *s, uint8_t *data, int length)
+{
+ uint32_t msg_type;
+ le32 *tmp = (le32 *) data;
+
+ msg_type = le32_to_cpup(tmp);
+
+ switch (msg_type) {
+ case RNDIS_INITIALIZE_MSG:
+ s->rndis_state = RNDIS_INITIALIZED;
+ return rndis_init_response(s, (rndis_init_msg_type *) data);
+
+ case RNDIS_HALT_MSG:
+ s->rndis_state = RNDIS_UNINITIALIZED;
+ return 0;
+
+ case RNDIS_QUERY_MSG:
+ return rndis_query_response(s, (rndis_query_msg_type *) data, length);
+
+ case RNDIS_SET_MSG:
+ return rndis_set_response(s, (rndis_set_msg_type *) data, length);
+
+ case RNDIS_RESET_MSG:
+ rndis_clear_responsequeue(s);
+ s->out_ptr = 0;
+ usb_net_reset_in_buf(s);
+ return rndis_reset_response(s, (rndis_reset_msg_type *) data);
+
+ case RNDIS_KEEPALIVE_MSG:
+ /* For USB: host does this every 5 seconds */
+ return rndis_keepalive_response(s, (rndis_keepalive_msg_type *) data);
+ }
+
+ return USB_RET_STALL;
+}
+
+static void usb_net_handle_reset(USBDevice *dev)
+{
+}
+
+static void usb_net_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ USBNetState *s = (USBNetState *) dev;
+ int ret;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch(request) {
+ case ClassInterfaceOutRequest | USB_CDC_SEND_ENCAPSULATED_COMMAND:
+ if (!is_rndis(s) || value || index != 0) {
+ goto fail;
+ }
+#ifdef TRAFFIC_DEBUG
+ {
+ unsigned int i;
+ fprintf(stderr, "SEND_ENCAPSULATED_COMMAND:");
+ for (i = 0; i < length; i++) {
+ if (!(i & 15))
+ fprintf(stderr, "\n%04x:", i);
+ fprintf(stderr, " %02x", data[i]);
+ }
+ fprintf(stderr, "\n\n");
+ }
+#endif
+ ret = rndis_parse(s, data, length);
+ if (ret < 0) {
+ p->status = ret;
+ }
+ break;
+
+ case ClassInterfaceRequest | USB_CDC_GET_ENCAPSULATED_RESPONSE:
+ if (!is_rndis(s) || value || index != 0) {
+ goto fail;
+ }
+ p->actual_length = rndis_get_response(s, data);
+ if (p->actual_length == 0) {
+ data[0] = 0;
+ p->actual_length = 1;
+ }
+#ifdef TRAFFIC_DEBUG
+ {
+ unsigned int i;
+ fprintf(stderr, "GET_ENCAPSULATED_RESPONSE:");
+ for (i = 0; i < p->actual_length; i++) {
+ if (!(i & 15))
+ fprintf(stderr, "\n%04x:", i);
+ fprintf(stderr, " %02x", data[i]);
+ }
+ fprintf(stderr, "\n\n");
+ }
+#endif
+ break;
+
+ default:
+ fail:
+ fprintf(stderr, "usbnet: failed control transaction: "
+ "request 0x%x value 0x%x index 0x%x length 0x%x\n",
+ request, value, index, length);
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_net_handle_statusin(USBNetState *s, USBPacket *p)
+{
+ le32 buf[2];
+
+ if (p->iov.size < 8) {
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ buf[0] = cpu_to_le32(1);
+ buf[1] = cpu_to_le32(0);
+ usb_packet_copy(p, buf, 8);
+ if (!s->rndis_resp.tqh_first) {
+ p->status = USB_RET_NAK;
+ }
+
+#ifdef TRAFFIC_DEBUG
+ fprintf(stderr, "usbnet: interrupt poll len %zu return %d",
+ p->iov.size, p->status);
+ iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", p->status);
+#endif
+}
+
+static void usb_net_handle_datain(USBNetState *s, USBPacket *p)
+{
+ int len;
+
+ if (s->in_ptr > s->in_len) {
+ usb_net_reset_in_buf(s);
+ p->status = USB_RET_NAK;
+ return;
+ }
+ if (!s->in_len) {
+ p->status = USB_RET_NAK;
+ return;
+ }
+ len = s->in_len - s->in_ptr;
+ if (len > p->iov.size) {
+ len = p->iov.size;
+ }
+ usb_packet_copy(p, &s->in_buf[s->in_ptr], len);
+ s->in_ptr += len;
+ if (s->in_ptr >= s->in_len &&
+ (is_rndis(s) || (s->in_len & (64 - 1)) || !len)) {
+ /* no short packet necessary */
+ usb_net_reset_in_buf(s);
+ }
+
+#ifdef TRAFFIC_DEBUG
+ fprintf(stderr, "usbnet: data in len %zu return %d", p->iov.size, len);
+ iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", len);
+#endif
+}
+
+static void usb_net_handle_dataout(USBNetState *s, USBPacket *p)
+{
+ int sz = sizeof(s->out_buf) - s->out_ptr;
+ struct rndis_packet_msg_type *msg =
+ (struct rndis_packet_msg_type *) s->out_buf;
+ uint32_t len;
+
+#ifdef TRAFFIC_DEBUG
+ fprintf(stderr, "usbnet: data out len %zu\n", p->iov.size);
+ iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", p->iov.size);
+#endif
+
+ if (sz > p->iov.size) {
+ sz = p->iov.size;
+ }
+ usb_packet_copy(p, &s->out_buf[s->out_ptr], sz);
+ s->out_ptr += sz;
+
+ if (!is_rndis(s)) {
+ if (p->iov.size < 64) {
+ qemu_send_packet(qemu_get_queue(s->nic), s->out_buf, s->out_ptr);
+ s->out_ptr = 0;
+ }
+ return;
+ }
+ len = le32_to_cpu(msg->MessageLength);
+ if (s->out_ptr < 8 || s->out_ptr < len) {
+ return;
+ }
+ if (le32_to_cpu(msg->MessageType) == RNDIS_PACKET_MSG) {
+ uint32_t offs = 8 + le32_to_cpu(msg->DataOffset);
+ uint32_t size = le32_to_cpu(msg->DataLength);
+ if (offs + size <= len)
+ qemu_send_packet(qemu_get_queue(s->nic), s->out_buf + offs, size);
+ }
+ s->out_ptr -= len;
+ memmove(s->out_buf, &s->out_buf[len], s->out_ptr);
+}
+
+static void usb_net_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBNetState *s = (USBNetState *) dev;
+
+ switch(p->pid) {
+ case USB_TOKEN_IN:
+ switch (p->ep->nr) {
+ case 1:
+ usb_net_handle_statusin(s, p);
+ break;
+
+ case 2:
+ usb_net_handle_datain(s, p);
+ break;
+
+ default:
+ goto fail;
+ }
+ break;
+
+ case USB_TOKEN_OUT:
+ switch (p->ep->nr) {
+ case 2:
+ usb_net_handle_dataout(s, p);
+ break;
+
+ default:
+ goto fail;
+ }
+ break;
+
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+
+ if (p->status == USB_RET_STALL) {
+ fprintf(stderr, "usbnet: failed data transaction: "
+ "pid 0x%x ep 0x%x len 0x%zx\n",
+ p->pid, p->ep->nr, p->iov.size);
+ }
+}
+
+static ssize_t usbnet_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+ USBNetState *s = qemu_get_nic_opaque(nc);
+ uint8_t *in_buf = s->in_buf;
+ size_t total_size = size;
+
+ if (!s->dev.config) {
+ return -1;
+ }
+
+ if (is_rndis(s)) {
+ if (s->rndis_state != RNDIS_DATA_INITIALIZED) {
+ return -1;
+ }
+ total_size += sizeof(struct rndis_packet_msg_type);
+ }
+ if (total_size > sizeof(s->in_buf)) {
+ return -1;
+ }
+
+ /* Only accept packet if input buffer is empty */
+ if (s->in_len > 0) {
+ return 0;
+ }
+
+ if (is_rndis(s)) {
+ struct rndis_packet_msg_type *msg;
+
+ msg = (struct rndis_packet_msg_type *)in_buf;
+ memset(msg, 0, sizeof(struct rndis_packet_msg_type));
+ msg->MessageType = cpu_to_le32(RNDIS_PACKET_MSG);
+ msg->MessageLength = cpu_to_le32(size + sizeof(*msg));
+ msg->DataOffset = cpu_to_le32(sizeof(*msg) - 8);
+ msg->DataLength = cpu_to_le32(size);
+ /* msg->OOBDataOffset;
+ * msg->OOBDataLength;
+ * msg->NumOOBDataElements;
+ * msg->PerPacketInfoOffset;
+ * msg->PerPacketInfoLength;
+ * msg->VcHandle;
+ * msg->Reserved;
+ */
+ in_buf += sizeof(*msg);
+ }
+
+ memcpy(in_buf, buf, size);
+ s->in_len = total_size;
+ s->in_ptr = 0;
+ return size;
+}
+
+static void usbnet_cleanup(NetClientState *nc)
+{
+ USBNetState *s = qemu_get_nic_opaque(nc);
+
+ s->nic = NULL;
+}
+
+static void usb_net_handle_destroy(USBDevice *dev)
+{
+ USBNetState *s = (USBNetState *) dev;
+
+ /* TODO: remove the nd_table[] entry */
+ rndis_clear_responsequeue(s);
+ qemu_del_nic(s->nic);
+}
+
+static NetClientInfo net_usbnet_info = {
+ .type = NET_CLIENT_OPTIONS_KIND_NIC,
+ .size = sizeof(NICState),
+ .receive = usbnet_receive,
+ .cleanup = usbnet_cleanup,
+};
+
+static void usb_net_realize(USBDevice *dev, Error **errrp)
+{
+ USBNetState *s = USB_NET(dev);
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+
+ s->rndis_state = RNDIS_UNINITIALIZED;
+ QTAILQ_INIT(&s->rndis_resp);
+
+ s->medium = 0; /* NDIS_MEDIUM_802_3 */
+ s->speed = 1000000; /* 100MBps, in 100Bps units */
+ s->media_state = 0; /* NDIS_MEDIA_STATE_CONNECTED */;
+ s->filter = 0;
+ s->vendorid = 0x1234;
+ s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ s->nic = qemu_new_nic(&net_usbnet_info, &s->conf,
+ object_get_typename(OBJECT(s)), s->dev.qdev.id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+ snprintf(s->usbstring_mac, sizeof(s->usbstring_mac),
+ "%02x%02x%02x%02x%02x%02x",
+ 0x40,
+ s->conf.macaddr.a[1],
+ s->conf.macaddr.a[2],
+ s->conf.macaddr.a[3],
+ s->conf.macaddr.a[4],
+ s->conf.macaddr.a[5]);
+ usb_desc_set_string(dev, STRING_ETHADDR, s->usbstring_mac);
+}
+
+static void usb_net_instance_init(Object *obj)
+{
+ USBDevice *dev = USB_DEVICE(obj);
+ USBNetState *s = USB_NET(dev);
+
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ &dev->qdev, NULL);
+}
+
+static USBDevice *usb_net_init(USBBus *bus, const char *cmdline)
+{
+ Error *local_err = NULL;
+ USBDevice *dev;
+ QemuOpts *opts;
+ int idx;
+
+ opts = qemu_opts_parse_noisily(qemu_find_opts("net"), cmdline, false);
+ if (!opts) {
+ return NULL;
+ }
+ qemu_opt_set(opts, "type", "nic", &error_abort);
+ qemu_opt_set(opts, "model", "usb", &error_abort);
+
+ idx = net_client_init(opts, 0, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ return NULL;
+ }
+
+ dev = usb_create(bus, "usb-net");
+ qdev_set_nic_properties(&dev->qdev, &nd_table[idx]);
+ return dev;
+}
+
+static const VMStateDescription vmstate_usb_net = {
+ .name = "usb-net",
+ .unmigratable = 1,
+};
+
+static Property net_properties[] = {
+ DEFINE_NIC_PROPERTIES(USBNetState, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_net_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_net_realize;
+ uc->product_desc = "QEMU USB Network Interface";
+ uc->usb_desc = &desc_net;
+ uc->handle_reset = usb_net_handle_reset;
+ uc->handle_control = usb_net_handle_control;
+ uc->handle_data = usb_net_handle_data;
+ uc->handle_destroy = usb_net_handle_destroy;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->fw_name = "network";
+ dc->vmsd = &vmstate_usb_net;
+ dc->props = net_properties;
+}
+
+static const TypeInfo net_info = {
+ .name = TYPE_USB_NET,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBNetState),
+ .class_init = usb_net_class_initfn,
+ .instance_init = usb_net_instance_init,
+};
+
+static void usb_net_register_types(void)
+{
+ type_register_static(&net_info);
+ usb_legacy_register(TYPE_USB_NET, "net", usb_net_init);
+}
+
+type_init(usb_net_register_types)
diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c
new file mode 100644
index 00000000..a6a66008
--- /dev/null
+++ b/hw/usb/dev-serial.c
@@ -0,0 +1,649 @@
+/*
+ * FTDI FT232BM Device emulation
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org>
+ * Written by Paul Brook, reused for FTDI by Samuel Thibault
+ *
+ * This code is licensed under the LGPL.
+ */
+
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "sysemu/char.h"
+
+//#define DEBUG_Serial
+
+#ifdef DEBUG_Serial
+#define DPRINTF(fmt, ...) \
+do { printf("usb-serial: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define RECV_BUF 384
+
+/* Commands */
+#define FTDI_RESET 0
+#define FTDI_SET_MDM_CTRL 1
+#define FTDI_SET_FLOW_CTRL 2
+#define FTDI_SET_BAUD 3
+#define FTDI_SET_DATA 4
+#define FTDI_GET_MDM_ST 5
+#define FTDI_SET_EVENT_CHR 6
+#define FTDI_SET_ERROR_CHR 7
+#define FTDI_SET_LATENCY 9
+#define FTDI_GET_LATENCY 10
+
+#define DeviceOutVendor ((USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8)
+#define DeviceInVendor ((USB_DIR_IN |USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8)
+
+/* RESET */
+
+#define FTDI_RESET_SIO 0
+#define FTDI_RESET_RX 1
+#define FTDI_RESET_TX 2
+
+/* SET_MDM_CTRL */
+
+#define FTDI_DTR 1
+#define FTDI_SET_DTR (FTDI_DTR << 8)
+#define FTDI_RTS 2
+#define FTDI_SET_RTS (FTDI_RTS << 8)
+
+/* SET_FLOW_CTRL */
+
+#define FTDI_RTS_CTS_HS 1
+#define FTDI_DTR_DSR_HS 2
+#define FTDI_XON_XOFF_HS 4
+
+/* SET_DATA */
+
+#define FTDI_PARITY (0x7 << 8)
+#define FTDI_ODD (0x1 << 8)
+#define FTDI_EVEN (0x2 << 8)
+#define FTDI_MARK (0x3 << 8)
+#define FTDI_SPACE (0x4 << 8)
+
+#define FTDI_STOP (0x3 << 11)
+#define FTDI_STOP1 (0x0 << 11)
+#define FTDI_STOP15 (0x1 << 11)
+#define FTDI_STOP2 (0x2 << 11)
+
+/* GET_MDM_ST */
+/* TODO: should be sent every 40ms */
+#define FTDI_CTS (1<<4) // CTS line status
+#define FTDI_DSR (1<<5) // DSR line status
+#define FTDI_RI (1<<6) // RI line status
+#define FTDI_RLSD (1<<7) // Receive Line Signal Detect
+
+/* Status */
+
+#define FTDI_DR (1<<0) // Data Ready
+#define FTDI_OE (1<<1) // Overrun Err
+#define FTDI_PE (1<<2) // Parity Err
+#define FTDI_FE (1<<3) // Framing Err
+#define FTDI_BI (1<<4) // Break Interrupt
+#define FTDI_THRE (1<<5) // Transmitter Holding Register
+#define FTDI_TEMT (1<<6) // Transmitter Empty
+#define FTDI_FIFO (1<<7) // Error in FIFO
+
+typedef struct {
+ USBDevice dev;
+ uint8_t recv_buf[RECV_BUF];
+ uint16_t recv_ptr;
+ uint16_t recv_used;
+ uint8_t event_chr;
+ uint8_t error_chr;
+ uint8_t event_trigger;
+ QEMUSerialSetParams params;
+ int latency; /* ms */
+ CharDriverState *cs;
+} USBSerialState;
+
+#define TYPE_USB_SERIAL "usb-serial-dev"
+#define USB_SERIAL_DEV(obj) OBJECT_CHECK(USBSerialState, (obj), TYPE_USB_SERIAL)
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT_SERIAL,
+ STR_PRODUCT_BRAILLE,
+ STR_SERIALNUMBER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT_SERIAL] = "QEMU USB SERIAL",
+ [STR_PRODUCT_BRAILLE] = "QEMU USB BAUM BRAILLE",
+ [STR_SERIALNUMBER] = "1",
+};
+
+static const USBDescIface desc_iface0 = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xff,
+ .bInterfaceSubClass = 0xff,
+ .bInterfaceProtocol = 0xff,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },
+ }
+};
+
+static const USBDescDevice desc_device = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CFG_ATT_ONE,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface0,
+ },
+ },
+};
+
+static const USBDesc desc_serial = {
+ .id = {
+ .idVendor = 0x0403,
+ .idProduct = 0x6001,
+ .bcdDevice = 0x0400,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT_SERIAL,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device,
+ .str = desc_strings,
+};
+
+static const USBDesc desc_braille = {
+ .id = {
+ .idVendor = 0x0403,
+ .idProduct = 0xfe72,
+ .bcdDevice = 0x0400,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT_BRAILLE,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device,
+ .str = desc_strings,
+};
+
+static void usb_serial_reset(USBSerialState *s)
+{
+ /* TODO: Set flow control to none */
+ s->event_chr = 0x0d;
+ s->event_trigger = 0;
+ s->recv_ptr = 0;
+ s->recv_used = 0;
+ /* TODO: purge in char driver */
+}
+
+static void usb_serial_handle_reset(USBDevice *dev)
+{
+ USBSerialState *s = (USBSerialState *)dev;
+
+ DPRINTF("Reset\n");
+
+ usb_serial_reset(s);
+ /* TODO: Reset char device, send BREAK? */
+}
+
+static uint8_t usb_get_modem_lines(USBSerialState *s)
+{
+ int flags;
+ uint8_t ret;
+
+ if (qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP)
+ return FTDI_CTS|FTDI_DSR|FTDI_RLSD;
+
+ ret = 0;
+ if (flags & CHR_TIOCM_CTS)
+ ret |= FTDI_CTS;
+ if (flags & CHR_TIOCM_DSR)
+ ret |= FTDI_DSR;
+ if (flags & CHR_TIOCM_RI)
+ ret |= FTDI_RI;
+ if (flags & CHR_TIOCM_CAR)
+ ret |= FTDI_RLSD;
+
+ return ret;
+}
+
+static void usb_serial_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ USBSerialState *s = (USBSerialState *)dev;
+ int ret;
+
+ DPRINTF("got control %x, value %x\n",request, value);
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch (request) {
+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+ break;
+
+ /* Class specific requests. */
+ case DeviceOutVendor | FTDI_RESET:
+ switch (value) {
+ case FTDI_RESET_SIO:
+ usb_serial_reset(s);
+ break;
+ case FTDI_RESET_RX:
+ s->recv_ptr = 0;
+ s->recv_used = 0;
+ /* TODO: purge from char device */
+ break;
+ case FTDI_RESET_TX:
+ /* TODO: purge from char device */
+ break;
+ }
+ break;
+ case DeviceOutVendor | FTDI_SET_MDM_CTRL:
+ {
+ static int flags;
+ qemu_chr_fe_ioctl(s->cs,CHR_IOCTL_SERIAL_GET_TIOCM, &flags);
+ if (value & FTDI_SET_RTS) {
+ if (value & FTDI_RTS)
+ flags |= CHR_TIOCM_RTS;
+ else
+ flags &= ~CHR_TIOCM_RTS;
+ }
+ if (value & FTDI_SET_DTR) {
+ if (value & FTDI_DTR)
+ flags |= CHR_TIOCM_DTR;
+ else
+ flags &= ~CHR_TIOCM_DTR;
+ }
+ qemu_chr_fe_ioctl(s->cs,CHR_IOCTL_SERIAL_SET_TIOCM, &flags);
+ break;
+ }
+ case DeviceOutVendor | FTDI_SET_FLOW_CTRL:
+ /* TODO: ioctl */
+ break;
+ case DeviceOutVendor | FTDI_SET_BAUD: {
+ static const int subdivisors8[8] = { 0, 4, 2, 1, 3, 5, 6, 7 };
+ int subdivisor8 = subdivisors8[((value & 0xc000) >> 14)
+ | ((index & 1) << 2)];
+ int divisor = value & 0x3fff;
+
+ /* chip special cases */
+ if (divisor == 1 && subdivisor8 == 0)
+ subdivisor8 = 4;
+ if (divisor == 0 && subdivisor8 == 0)
+ divisor = 1;
+
+ s->params.speed = (48000000 / 2) / (8 * divisor + subdivisor8);
+ qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params);
+ break;
+ }
+ case DeviceOutVendor | FTDI_SET_DATA:
+ switch (value & FTDI_PARITY) {
+ case 0:
+ s->params.parity = 'N';
+ break;
+ case FTDI_ODD:
+ s->params.parity = 'O';
+ break;
+ case FTDI_EVEN:
+ s->params.parity = 'E';
+ break;
+ default:
+ DPRINTF("unsupported parity %d\n", value & FTDI_PARITY);
+ goto fail;
+ }
+ switch (value & FTDI_STOP) {
+ case FTDI_STOP1:
+ s->params.stop_bits = 1;
+ break;
+ case FTDI_STOP2:
+ s->params.stop_bits = 2;
+ break;
+ default:
+ DPRINTF("unsupported stop bits %d\n", value & FTDI_STOP);
+ goto fail;
+ }
+ qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params);
+ /* TODO: TX ON/OFF */
+ break;
+ case DeviceInVendor | FTDI_GET_MDM_ST:
+ data[0] = usb_get_modem_lines(s) | 1;
+ data[1] = 0;
+ p->actual_length = 2;
+ break;
+ case DeviceOutVendor | FTDI_SET_EVENT_CHR:
+ /* TODO: handle it */
+ s->event_chr = value;
+ break;
+ case DeviceOutVendor | FTDI_SET_ERROR_CHR:
+ /* TODO: handle it */
+ s->error_chr = value;
+ break;
+ case DeviceOutVendor | FTDI_SET_LATENCY:
+ s->latency = value;
+ break;
+ case DeviceInVendor | FTDI_GET_LATENCY:
+ data[0] = s->latency;
+ p->actual_length = 1;
+ break;
+ default:
+ fail:
+ DPRINTF("got unsupported/bogus control %x, value %x\n", request, value);
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_serial_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBSerialState *s = (USBSerialState *)dev;
+ uint8_t devep = p->ep->nr;
+ struct iovec *iov;
+ uint8_t header[2];
+ int i, first_len, len;
+
+ switch (p->pid) {
+ case USB_TOKEN_OUT:
+ if (devep != 2)
+ goto fail;
+ for (i = 0; i < p->iov.niov; i++) {
+ iov = p->iov.iov + i;
+ qemu_chr_fe_write(s->cs, iov->iov_base, iov->iov_len);
+ }
+ p->actual_length = p->iov.size;
+ break;
+
+ case USB_TOKEN_IN:
+ if (devep != 1)
+ goto fail;
+ first_len = RECV_BUF - s->recv_ptr;
+ len = p->iov.size;
+ if (len <= 2) {
+ p->status = USB_RET_NAK;
+ break;
+ }
+ header[0] = usb_get_modem_lines(s) | 1;
+ /* We do not have the uart details */
+ /* handle serial break */
+ if (s->event_trigger && s->event_trigger & FTDI_BI) {
+ s->event_trigger &= ~FTDI_BI;
+ header[1] = FTDI_BI;
+ usb_packet_copy(p, header, 2);
+ break;
+ } else {
+ header[1] = 0;
+ }
+ len -= 2;
+ if (len > s->recv_used)
+ len = s->recv_used;
+ if (!len) {
+ p->status = USB_RET_NAK;
+ break;
+ }
+ if (first_len > len)
+ first_len = len;
+ usb_packet_copy(p, header, 2);
+ usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len);
+ if (len > first_len)
+ usb_packet_copy(p, s->recv_buf, len - first_len);
+ s->recv_used -= len;
+ s->recv_ptr = (s->recv_ptr + len) % RECV_BUF;
+ break;
+
+ default:
+ DPRINTF("Bad token\n");
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static int usb_serial_can_read(void *opaque)
+{
+ USBSerialState *s = opaque;
+
+ if (!s->dev.attached) {
+ return 0;
+ }
+ return RECV_BUF - s->recv_used;
+}
+
+static void usb_serial_read(void *opaque, const uint8_t *buf, int size)
+{
+ USBSerialState *s = opaque;
+ int first_size, start;
+
+ /* room in the buffer? */
+ if (size > (RECV_BUF - s->recv_used))
+ size = RECV_BUF - s->recv_used;
+
+ start = s->recv_ptr + s->recv_used;
+ if (start < RECV_BUF) {
+ /* copy data to end of buffer */
+ first_size = RECV_BUF - start;
+ if (first_size > size)
+ first_size = size;
+
+ memcpy(s->recv_buf + start, buf, first_size);
+
+ /* wrap around to front if needed */
+ if (size > first_size)
+ memcpy(s->recv_buf, buf + first_size, size - first_size);
+ } else {
+ start -= RECV_BUF;
+ memcpy(s->recv_buf + start, buf, size);
+ }
+ s->recv_used += size;
+}
+
+static void usb_serial_event(void *opaque, int event)
+{
+ USBSerialState *s = opaque;
+
+ switch (event) {
+ case CHR_EVENT_BREAK:
+ s->event_trigger |= FTDI_BI;
+ break;
+ case CHR_EVENT_FOCUS:
+ break;
+ case CHR_EVENT_OPENED:
+ if (!s->dev.attached) {
+ usb_device_attach(&s->dev, &error_abort);
+ }
+ break;
+ case CHR_EVENT_CLOSED:
+ if (s->dev.attached) {
+ usb_device_detach(&s->dev);
+ }
+ break;
+ }
+}
+
+static void usb_serial_realize(USBDevice *dev, Error **errp)
+{
+ USBSerialState *s = USB_SERIAL_DEV(dev);
+ Error *local_err = NULL;
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ dev->auto_attach = 0;
+
+ if (!s->cs) {
+ error_setg(errp, "Property chardev is required");
+ return;
+ }
+
+ usb_check_attach(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ qemu_chr_add_handlers(s->cs, usb_serial_can_read, usb_serial_read,
+ usb_serial_event, s);
+ usb_serial_handle_reset(dev);
+
+ if (s->cs->be_open && !dev->attached) {
+ usb_device_attach(dev, &error_abort);
+ }
+}
+
+static USBDevice *usb_serial_init(USBBus *bus, const char *filename)
+{
+ USBDevice *dev;
+ CharDriverState *cdrv;
+ uint32_t vendorid = 0, productid = 0;
+ char label[32];
+ static int index;
+
+ while (*filename && *filename != ':') {
+ const char *p;
+ char *e;
+ if (strstart(filename, "vendorid=", &p)) {
+ vendorid = strtol(p, &e, 16);
+ if (e == p || (*e && *e != ',' && *e != ':')) {
+ error_report("bogus vendor ID %s", p);
+ return NULL;
+ }
+ filename = e;
+ } else if (strstart(filename, "productid=", &p)) {
+ productid = strtol(p, &e, 16);
+ if (e == p || (*e && *e != ',' && *e != ':')) {
+ error_report("bogus product ID %s", p);
+ return NULL;
+ }
+ filename = e;
+ } else {
+ error_report("unrecognized serial USB option %s", filename);
+ return NULL;
+ }
+ while(*filename == ',')
+ filename++;
+ }
+ if (!*filename) {
+ error_report("character device specification needed");
+ return NULL;
+ }
+ filename++;
+
+ snprintf(label, sizeof(label), "usbserial%d", index++);
+ cdrv = qemu_chr_new(label, filename, NULL);
+ if (!cdrv)
+ return NULL;
+
+ dev = usb_create(bus, "usb-serial");
+ qdev_prop_set_chr(&dev->qdev, "chardev", cdrv);
+ if (vendorid)
+ qdev_prop_set_uint16(&dev->qdev, "vendorid", vendorid);
+ if (productid)
+ qdev_prop_set_uint16(&dev->qdev, "productid", productid);
+ return dev;
+}
+
+static USBDevice *usb_braille_init(USBBus *bus, const char *unused)
+{
+ USBDevice *dev;
+ CharDriverState *cdrv;
+
+ cdrv = qemu_chr_new("braille", "braille", NULL);
+ if (!cdrv)
+ return NULL;
+
+ dev = usb_create(bus, "usb-braille");
+ qdev_prop_set_chr(&dev->qdev, "chardev", cdrv);
+ return dev;
+}
+
+static const VMStateDescription vmstate_usb_serial = {
+ .name = "usb-serial",
+ .unmigratable = 1,
+};
+
+static Property serial_properties[] = {
+ DEFINE_PROP_CHR("chardev", USBSerialState, cs),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_serial_dev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_serial_realize;
+ uc->handle_reset = usb_serial_handle_reset;
+ uc->handle_control = usb_serial_handle_control;
+ uc->handle_data = usb_serial_handle_data;
+ dc->vmsd = &vmstate_usb_serial;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo usb_serial_dev_type_info = {
+ .name = TYPE_USB_SERIAL,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBSerialState),
+ .abstract = true,
+ .class_init = usb_serial_dev_class_init,
+};
+
+static void usb_serial_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->product_desc = "QEMU USB Serial";
+ uc->usb_desc = &desc_serial;
+ dc->props = serial_properties;
+}
+
+static const TypeInfo serial_info = {
+ .name = "usb-serial",
+ .parent = TYPE_USB_SERIAL,
+ .class_init = usb_serial_class_initfn,
+};
+
+static Property braille_properties[] = {
+ DEFINE_PROP_CHR("chardev", USBSerialState, cs),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_braille_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->product_desc = "QEMU USB Braille";
+ uc->usb_desc = &desc_braille;
+ dc->props = braille_properties;
+}
+
+static const TypeInfo braille_info = {
+ .name = "usb-braille",
+ .parent = TYPE_USB_SERIAL,
+ .class_init = usb_braille_class_initfn,
+};
+
+static void usb_serial_register_types(void)
+{
+ type_register_static(&usb_serial_dev_type_info);
+ type_register_static(&serial_info);
+ usb_legacy_register("usb-serial", "serial", usb_serial_init);
+ type_register_static(&braille_info);
+ usb_legacy_register("usb-braille", "braille", usb_braille_init);
+}
+
+type_init(usb_serial_register_types)
diff --git a/hw/usb/dev-smartcard-reader.c b/hw/usb/dev-smartcard-reader.c
new file mode 100644
index 00000000..8952efff
--- /dev/null
+++ b/hw/usb/dev-smartcard-reader.c
@@ -0,0 +1,1505 @@
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * CCID Device emulation
+ *
+ * Written by Alon Levy, with contributions from Robert Relyea.
+ *
+ * Based on usb-serial.c, see its copyright and attributions below.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.1 or later.
+ * See the COPYING file in the top-level directory.
+ * ------- (original copyright & attribution for usb-serial.c below) --------
+ * Copyright (c) 2006 CodeSourcery.
+ * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org>
+ * Written by Paul Brook, reused for FTDI by Samuel Thibault,
+ */
+
+/*
+ * References:
+ *
+ * CCID Specification Revision 1.1 April 22nd 2005
+ * "Universal Serial Bus, Device Class: Smart Card"
+ * Specification for Integrated Circuit(s) Cards Interface Devices
+ *
+ * Endianness note: from the spec (1.3)
+ * "Fields that are larger than a byte are stored in little endian"
+ *
+ * KNOWN BUGS
+ * 1. remove/insert can sometimes result in removed state instead of inserted.
+ * This is a result of the following:
+ * symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This can happen
+ * when a short packet is sent, as seen in uhci-usb.c, resulting from a urb
+ * from the guest requesting SPD and us returning a smaller packet.
+ * Not sure which messages trigger this.
+ */
+
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+
+#include "ccid.h"
+
+#define DPRINTF(s, lvl, fmt, ...) \
+do { \
+ if (lvl <= s->debug) { \
+ printf("usb-ccid: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0)
+
+#define D_WARN 1
+#define D_INFO 2
+#define D_MORE_INFO 3
+#define D_VERBOSE 4
+
+#define CCID_DEV_NAME "usb-ccid"
+#define USB_CCID_DEV(obj) OBJECT_CHECK(USBCCIDState, (obj), CCID_DEV_NAME)
+/*
+ * The two options for variable sized buffers:
+ * make them constant size, for large enough constant,
+ * or handle the migration complexity - VMState doesn't handle this case.
+ * sizes are expected never to be exceeded, unless guest misbehaves.
+ */
+#define BULK_OUT_DATA_SIZE 65536
+#define PENDING_ANSWERS_NUM 128
+
+#define BULK_IN_BUF_SIZE 384
+#define BULK_IN_PENDING_NUM 8
+
+#define CCID_MAX_PACKET_SIZE 64
+
+#define CCID_CONTROL_ABORT 0x1
+#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x2
+#define CCID_CONTROL_GET_DATA_RATES 0x3
+
+#define CCID_PRODUCT_DESCRIPTION "QEMU USB CCID"
+#define CCID_VENDOR_DESCRIPTION "QEMU"
+#define CCID_INTERFACE_NAME "CCID Interface"
+#define CCID_SERIAL_NUMBER_STRING "1"
+/*
+ * Using Gemplus Vendor and Product id
+ * Effect on various drivers:
+ * usbccid.sys (winxp, others untested) is a class driver so it doesn't care.
+ * linux has a number of class drivers, but openct filters based on
+ * vendor/product (/etc/openct.conf under fedora), hence Gemplus.
+ */
+#define CCID_VENDOR_ID 0x08e6
+#define CCID_PRODUCT_ID 0x4433
+#define CCID_DEVICE_VERSION 0x0000
+
+/*
+ * BULK_OUT messages from PC to Reader
+ * Defined in CCID Rev 1.1 6.1 (page 26)
+ */
+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn 0x62
+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff 0x63
+#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus 0x65
+#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock 0x6f
+#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters 0x6c
+#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters 0x6d
+#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters 0x61
+#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape 0x6b
+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock 0x6e
+#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU 0x6a
+#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure 0x69
+#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical 0x71
+#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort 0x72
+#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73
+
+/*
+ * BULK_IN messages from Reader to PC
+ * Defined in CCID Rev 1.1 6.2 (page 48)
+ */
+#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock 0x80
+#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus 0x81
+#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters 0x82
+#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape 0x83
+#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84
+
+/*
+ * INTERRUPT_IN messages from Reader to PC
+ * Defined in CCID Rev 1.1 6.3 (page 56)
+ */
+#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange 0x50
+#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError 0x51
+
+/*
+ * Endpoints for CCID - addresses are up to us to decide.
+ * To support slot insertion and removal we must have an interrupt in ep
+ * in addition we need a bulk in and bulk out ep
+ * 5.2, page 20
+ */
+#define CCID_INT_IN_EP 1
+#define CCID_BULK_IN_EP 2
+#define CCID_BULK_OUT_EP 3
+
+/* bmSlotICCState masks */
+#define SLOT_0_STATE_MASK 1
+#define SLOT_0_CHANGED_MASK 2
+
+/* Status codes that go in bStatus (see 6.2.6) */
+enum {
+ ICC_STATUS_PRESENT_ACTIVE = 0,
+ ICC_STATUS_PRESENT_INACTIVE,
+ ICC_STATUS_NOT_PRESENT
+};
+
+enum {
+ COMMAND_STATUS_NO_ERROR = 0,
+ COMMAND_STATUS_FAILED,
+ COMMAND_STATUS_TIME_EXTENSION_REQUIRED
+};
+
+/* Error codes that go in bError (see 6.2.6) */
+enum {
+ ERROR_CMD_NOT_SUPPORTED = 0,
+ ERROR_CMD_ABORTED = -1,
+ ERROR_ICC_MUTE = -2,
+ ERROR_XFR_PARITY_ERROR = -3,
+ ERROR_XFR_OVERRUN = -4,
+ ERROR_HW_ERROR = -5,
+};
+
+/* 6.2.6 RDR_to_PC_SlotStatus definitions */
+enum {
+ CLOCK_STATUS_RUNNING = 0,
+ /*
+ * 0 - Clock Running, 1 - Clock stopped in State L, 2 - H,
+ * 3 - unknown state. rest are RFU
+ */
+};
+
+typedef struct QEMU_PACKED CCID_Header {
+ uint8_t bMessageType;
+ uint32_t dwLength;
+ uint8_t bSlot;
+ uint8_t bSeq;
+} CCID_Header;
+
+typedef struct QEMU_PACKED CCID_BULK_IN {
+ CCID_Header hdr;
+ uint8_t bStatus; /* Only used in BULK_IN */
+ uint8_t bError; /* Only used in BULK_IN */
+} CCID_BULK_IN;
+
+typedef struct QEMU_PACKED CCID_SlotStatus {
+ CCID_BULK_IN b;
+ uint8_t bClockStatus;
+} CCID_SlotStatus;
+
+typedef struct QEMU_PACKED CCID_T0ProtocolDataStructure {
+ uint8_t bmFindexDindex;
+ uint8_t bmTCCKST0;
+ uint8_t bGuardTimeT0;
+ uint8_t bWaitingIntegerT0;
+ uint8_t bClockStop;
+} CCID_T0ProtocolDataStructure;
+
+typedef struct QEMU_PACKED CCID_T1ProtocolDataStructure {
+ uint8_t bmFindexDindex;
+ uint8_t bmTCCKST1;
+ uint8_t bGuardTimeT1;
+ uint8_t bWaitingIntegerT1;
+ uint8_t bClockStop;
+ uint8_t bIFSC;
+ uint8_t bNadValue;
+} CCID_T1ProtocolDataStructure;
+
+typedef union CCID_ProtocolDataStructure {
+ CCID_T0ProtocolDataStructure t0;
+ CCID_T1ProtocolDataStructure t1;
+ uint8_t data[7]; /* must be = max(sizeof(t0), sizeof(t1)) */
+} CCID_ProtocolDataStructure;
+
+typedef struct QEMU_PACKED CCID_Parameter {
+ CCID_BULK_IN b;
+ uint8_t bProtocolNum;
+ CCID_ProtocolDataStructure abProtocolDataStructure;
+} CCID_Parameter;
+
+typedef struct QEMU_PACKED CCID_DataBlock {
+ CCID_BULK_IN b;
+ uint8_t bChainParameter;
+ uint8_t abData[0];
+} CCID_DataBlock;
+
+/* 6.1.4 PC_to_RDR_XfrBlock */
+typedef struct QEMU_PACKED CCID_XferBlock {
+ CCID_Header hdr;
+ uint8_t bBWI; /* Block Waiting Timeout */
+ uint16_t wLevelParameter; /* XXX currently unused */
+ uint8_t abData[0];
+} CCID_XferBlock;
+
+typedef struct QEMU_PACKED CCID_IccPowerOn {
+ CCID_Header hdr;
+ uint8_t bPowerSelect;
+ uint16_t abRFU;
+} CCID_IccPowerOn;
+
+typedef struct QEMU_PACKED CCID_IccPowerOff {
+ CCID_Header hdr;
+ uint16_t abRFU;
+} CCID_IccPowerOff;
+
+typedef struct QEMU_PACKED CCID_SetParameters {
+ CCID_Header hdr;
+ uint8_t bProtocolNum;
+ uint16_t abRFU;
+ CCID_ProtocolDataStructure abProtocolDataStructure;
+} CCID_SetParameters;
+
+typedef struct CCID_Notify_Slot_Change {
+ uint8_t bMessageType; /* CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange */
+ uint8_t bmSlotICCState;
+} CCID_Notify_Slot_Change;
+
+/* used for DataBlock response to XferBlock */
+typedef struct Answer {
+ uint8_t slot;
+ uint8_t seq;
+} Answer;
+
+/* pending BULK_IN messages */
+typedef struct BulkIn {
+ uint8_t data[BULK_IN_BUF_SIZE];
+ uint32_t len;
+ uint32_t pos;
+} BulkIn;
+
+enum {
+ MIGRATION_NONE,
+ MIGRATION_MIGRATED,
+};
+
+typedef struct CCIDBus {
+ BusState qbus;
+} CCIDBus;
+
+/*
+ * powered - defaults to true, changed by PowerOn/PowerOff messages
+ */
+typedef struct USBCCIDState {
+ USBDevice dev;
+ USBEndpoint *intr;
+ USBEndpoint *bulk;
+ CCIDBus bus;
+ CCIDCardState *card;
+ BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */
+ uint32_t bulk_in_pending_start;
+ uint32_t bulk_in_pending_end; /* first free */
+ uint32_t bulk_in_pending_num;
+ BulkIn *current_bulk_in;
+ uint8_t bulk_out_data[BULK_OUT_DATA_SIZE];
+ uint32_t bulk_out_pos;
+ uint64_t last_answer_error;
+ Answer pending_answers[PENDING_ANSWERS_NUM];
+ uint32_t pending_answers_start;
+ uint32_t pending_answers_end;
+ uint32_t pending_answers_num;
+ uint8_t bError;
+ uint8_t bmCommandStatus;
+ uint8_t bProtocolNum;
+ CCID_ProtocolDataStructure abProtocolDataStructure;
+ uint32_t ulProtocolDataStructureSize;
+ uint32_t state_vmstate;
+ uint32_t migration_target_ip;
+ uint16_t migration_target_port;
+ uint8_t migration_state;
+ uint8_t bmSlotICCState;
+ uint8_t powered;
+ uint8_t notify_slot_change;
+ uint8_t debug;
+} USBCCIDState;
+
+/*
+ * CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9,
+ * "USB Device Framework", section 9.6.1, in the Universal Serial Bus
+ * Specification.
+ *
+ * This device implemented based on the spec and with an Athena Smart Card
+ * Reader as reference:
+ * 0dc3:1004 Athena Smartcard Solutions, Inc.
+ */
+
+static const uint8_t qemu_ccid_descriptor[] = {
+ /* Smart Card Device Class Descriptor */
+ 0x36, /* u8 bLength; */
+ 0x21, /* u8 bDescriptorType; Functional */
+ 0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */
+ 0x00, /*
+ * u8 bMaxSlotIndex; The index of the highest available
+ * slot on this device. All slots are consecutive starting
+ * at 00h.
+ */
+ 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */
+
+ 0x00, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/
+ 0x01, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */
+ /* u32 dwDefaultClock; in kHZ (0x0fa0 is 4 MHz) */
+ 0xa0, 0x0f, 0x00, 0x00,
+ /* u32 dwMaximumClock; */
+ 0x00, 0x00, 0x01, 0x00,
+ 0x00, /* u8 bNumClockSupported; *
+ * 0 means just the default and max. */
+ /* u32 dwDataRate ;bps. 9600 == 00002580h */
+ 0x80, 0x25, 0x00, 0x00,
+ /* u32 dwMaxDataRate ; 11520 bps == 0001C200h */
+ 0x00, 0xC2, 0x01, 0x00,
+ 0x00, /* u8 bNumDataRatesSupported; 00 means all rates between
+ * default and max */
+ /* u32 dwMaxIFSD; *
+ * maximum IFSD supported by CCID for protocol *
+ * T=1 (Maximum seen from various cards) */
+ 0xfe, 0x00, 0x00, 0x00,
+ /* u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */
+ 0x00, 0x00, 0x00, 0x00,
+ /* u32 dwMechanical; 0 - no special characteristics. */
+ 0x00, 0x00, 0x00, 0x00,
+ /*
+ * u32 dwFeatures;
+ * 0 - No special characteristics
+ * + 2 Automatic parameter configuration based on ATR data
+ * + 4 Automatic activation of ICC on inserting
+ * + 8 Automatic ICC voltage selection
+ * + 10 Automatic ICC clock frequency change
+ * + 20 Automatic baud rate change
+ * + 40 Automatic parameters negotiation made by the CCID
+ * + 80 automatic PPS made by the CCID
+ * 100 CCID can set ICC in clock stop mode
+ * 200 NAD value other then 00 accepted (T=1 protocol)
+ * + 400 Automatic IFSD exchange as first exchange (T=1)
+ * One of the following only:
+ * + 10000 TPDU level exchanges with CCID
+ * 20000 Short APDU level exchange with CCID
+ * 40000 Short and Extended APDU level exchange with CCID
+ *
+ * 100000 USB Wake up signaling supported on card
+ * insertion and removal. Must set bit 5 in bmAttributes
+ * in Configuration descriptor if 100000 is set.
+ */
+ 0xfe, 0x04, 0x01, 0x00,
+ /*
+ * u32 dwMaxCCIDMessageLength; For extended APDU in
+ * [261 + 10 , 65544 + 10]. Otherwise the minimum is
+ * wMaxPacketSize of the Bulk-OUT endpoint
+ */
+ 0x12, 0x00, 0x01, 0x00,
+ 0xFF, /*
+ * u8 bClassGetResponse; Significant only for CCID that
+ * offers an APDU level for exchanges. Indicates the
+ * default class value used by the CCID when it sends a
+ * Get Response command to perform the transportation of
+ * an APDU by T=0 protocol
+ * FFh indicates that the CCID echos the class of the APDU.
+ */
+ 0xFF, /*
+ * u8 bClassEnvelope; EAPDU only. Envelope command for
+ * T=0
+ */
+ 0x00, 0x00, /*
+ * u16 wLcdLayout; XXYY Number of lines (XX) and chars per
+ * line for LCD display used for PIN entry. 0000 - no LCD
+ */
+ 0x01, /*
+ * u8 bPINSupport; 01h PIN Verification,
+ * 02h PIN Modification
+ */
+ 0x01, /* u8 bMaxCCIDBusySlots; */
+};
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+ STR_INTERFACE,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT] = "QEMU USB CCID",
+ [STR_SERIALNUMBER] = "1",
+ [STR_INTERFACE] = "CCID Interface",
+};
+
+static const USBDescIface desc_iface0 = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_CSCID,
+ .bInterfaceSubClass = USB_SUBCLASS_UNDEFINED,
+ .bInterfaceProtocol = 0x00,
+ .iInterface = STR_INTERFACE,
+ .ndesc = 1,
+ .descs = (USBDescOther[]) {
+ {
+ /* smartcard descriptor */
+ .data = qemu_ccid_descriptor,
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | CCID_INT_IN_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .bInterval = 255,
+ .wMaxPacketSize = 64,
+ },{
+ .bEndpointAddress = USB_DIR_IN | CCID_BULK_IN_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | CCID_BULK_OUT_EP,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },
+ }
+};
+
+static const USBDescDevice desc_device = {
+ .bcdUSB = 0x0110,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER |
+ USB_CFG_ATT_WAKEUP,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface0,
+ },
+ },
+};
+
+static const USBDesc desc_ccid = {
+ .id = {
+ .idVendor = CCID_VENDOR_ID,
+ .idProduct = CCID_PRODUCT_ID,
+ .bcdDevice = CCID_DEVICE_VERSION,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device,
+ .str = desc_strings,
+};
+
+static const uint8_t *ccid_card_get_atr(CCIDCardState *card, uint32_t *len)
+{
+ CCIDCardClass *cc = CCID_CARD_GET_CLASS(card);
+
+ if (cc->get_atr) {
+ return cc->get_atr(card, len);
+ }
+ return NULL;
+}
+
+static void ccid_card_apdu_from_guest(CCIDCardState *card,
+ const uint8_t *apdu,
+ uint32_t len)
+{
+ CCIDCardClass *cc = CCID_CARD_GET_CLASS(card);
+
+ if (cc->apdu_from_guest) {
+ cc->apdu_from_guest(card, apdu, len);
+ }
+}
+
+static int ccid_card_exitfn(CCIDCardState *card)
+{
+ CCIDCardClass *cc = CCID_CARD_GET_CLASS(card);
+
+ if (cc->exitfn) {
+ return cc->exitfn(card);
+ }
+ return 0;
+}
+
+static int ccid_card_initfn(CCIDCardState *card)
+{
+ CCIDCardClass *cc = CCID_CARD_GET_CLASS(card);
+
+ if (cc->initfn) {
+ return cc->initfn(card);
+ }
+ return 0;
+}
+
+static bool ccid_has_pending_answers(USBCCIDState *s)
+{
+ return s->pending_answers_num > 0;
+}
+
+static void ccid_clear_pending_answers(USBCCIDState *s)
+{
+ s->pending_answers_num = 0;
+ s->pending_answers_start = 0;
+ s->pending_answers_end = 0;
+}
+
+static void ccid_print_pending_answers(USBCCIDState *s)
+{
+ Answer *answer;
+ int i, count;
+
+ DPRINTF(s, D_VERBOSE, "usb-ccid: pending answers:");
+ if (!ccid_has_pending_answers(s)) {
+ DPRINTF(s, D_VERBOSE, " empty\n");
+ return;
+ }
+ for (i = s->pending_answers_start, count = s->pending_answers_num ;
+ count > 0; count--, i++) {
+ answer = &s->pending_answers[i % PENDING_ANSWERS_NUM];
+ if (count == 1) {
+ DPRINTF(s, D_VERBOSE, "%d:%d\n", answer->slot, answer->seq);
+ } else {
+ DPRINTF(s, D_VERBOSE, "%d:%d,", answer->slot, answer->seq);
+ }
+ }
+}
+
+static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr)
+{
+ Answer *answer;
+
+ assert(s->pending_answers_num < PENDING_ANSWERS_NUM);
+ s->pending_answers_num++;
+ answer =
+ &s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM];
+ answer->slot = hdr->bSlot;
+ answer->seq = hdr->bSeq;
+ ccid_print_pending_answers(s);
+}
+
+static void ccid_remove_pending_answer(USBCCIDState *s,
+ uint8_t *slot, uint8_t *seq)
+{
+ Answer *answer;
+
+ assert(s->pending_answers_num > 0);
+ s->pending_answers_num--;
+ answer =
+ &s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM];
+ *slot = answer->slot;
+ *seq = answer->seq;
+ ccid_print_pending_answers(s);
+}
+
+static void ccid_bulk_in_clear(USBCCIDState *s)
+{
+ s->bulk_in_pending_start = 0;
+ s->bulk_in_pending_end = 0;
+ s->bulk_in_pending_num = 0;
+}
+
+static void ccid_bulk_in_release(USBCCIDState *s)
+{
+ assert(s->current_bulk_in != NULL);
+ s->current_bulk_in->pos = 0;
+ s->current_bulk_in = NULL;
+}
+
+static void ccid_bulk_in_get(USBCCIDState *s)
+{
+ if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) {
+ return;
+ }
+ assert(s->bulk_in_pending_num > 0);
+ s->bulk_in_pending_num--;
+ s->current_bulk_in =
+ &s->bulk_in_pending[(s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM];
+}
+
+static void *ccid_reserve_recv_buf(USBCCIDState *s, uint16_t len)
+{
+ BulkIn *bulk_in;
+
+ DPRINTF(s, D_VERBOSE, "%s: QUEUE: reserve %d bytes\n", __func__, len);
+
+ /* look for an existing element */
+ if (len > BULK_IN_BUF_SIZE) {
+ DPRINTF(s, D_WARN, "usb-ccid.c: %s: len larger then max (%d>%d). "
+ "discarding message.\n",
+ __func__, len, BULK_IN_BUF_SIZE);
+ return NULL;
+ }
+ if (s->bulk_in_pending_num >= BULK_IN_PENDING_NUM) {
+ DPRINTF(s, D_WARN, "usb-ccid.c: %s: No free bulk_in buffers. "
+ "discarding message.\n", __func__);
+ return NULL;
+ }
+ bulk_in =
+ &s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM];
+ s->bulk_in_pending_num++;
+ bulk_in->len = len;
+ return bulk_in->data;
+}
+
+static void ccid_reset(USBCCIDState *s)
+{
+ ccid_bulk_in_clear(s);
+ ccid_clear_pending_answers(s);
+}
+
+static void ccid_detach(USBCCIDState *s)
+{
+ ccid_reset(s);
+}
+
+static void ccid_handle_reset(USBDevice *dev)
+{
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ DPRINTF(s, 1, "Reset\n");
+
+ ccid_reset(s);
+}
+
+static const char *ccid_control_to_str(USBCCIDState *s, int request)
+{
+ switch (request) {
+ /* generic - should be factored out if there are other debugees */
+ case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+ return "(generic) set address";
+ case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+ return "(generic) get descriptor";
+ case DeviceRequest | USB_REQ_GET_CONFIGURATION:
+ return "(generic) get configuration";
+ case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+ return "(generic) set configuration";
+ case DeviceRequest | USB_REQ_GET_STATUS:
+ return "(generic) get status";
+ case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
+ return "(generic) clear feature";
+ case DeviceOutRequest | USB_REQ_SET_FEATURE:
+ return "(generic) set_feature";
+ case InterfaceRequest | USB_REQ_GET_INTERFACE:
+ return "(generic) get interface";
+ case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+ return "(generic) set interface";
+ /* class requests */
+ case ClassInterfaceOutRequest | CCID_CONTROL_ABORT:
+ return "ABORT";
+ case ClassInterfaceRequest | CCID_CONTROL_GET_CLOCK_FREQUENCIES:
+ return "GET_CLOCK_FREQUENCIES";
+ case ClassInterfaceRequest | CCID_CONTROL_GET_DATA_RATES:
+ return "GET_DATA_RATES";
+ }
+ return "unknown";
+}
+
+static void ccid_handle_control(USBDevice *dev, USBPacket *p, int request,
+ int value, int index, int length, uint8_t *data)
+{
+ USBCCIDState *s = USB_CCID_DEV(dev);
+ int ret;
+
+ DPRINTF(s, 1, "%s: got control %s (%x), value %x\n", __func__,
+ ccid_control_to_str(s, request), request, value);
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch (request) {
+ /* Class specific requests. */
+ case ClassInterfaceOutRequest | CCID_CONTROL_ABORT:
+ DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n");
+ p->status = USB_RET_STALL;
+ break;
+ case ClassInterfaceRequest | CCID_CONTROL_GET_CLOCK_FREQUENCIES:
+ DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n");
+ p->status = USB_RET_STALL;
+ break;
+ case ClassInterfaceRequest | CCID_CONTROL_GET_DATA_RATES:
+ DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n");
+ p->status = USB_RET_STALL;
+ break;
+ default:
+ DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n",
+ request, value);
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static bool ccid_card_inserted(USBCCIDState *s)
+{
+ return s->bmSlotICCState & SLOT_0_STATE_MASK;
+}
+
+static uint8_t ccid_card_status(USBCCIDState *s)
+{
+ return ccid_card_inserted(s)
+ ? (s->powered ?
+ ICC_STATUS_PRESENT_ACTIVE
+ : ICC_STATUS_PRESENT_INACTIVE
+ )
+ : ICC_STATUS_NOT_PRESENT;
+}
+
+static uint8_t ccid_calc_status(USBCCIDState *s)
+{
+ /*
+ * page 55, 6.2.6, calculation of bStatus from bmICCStatus and
+ * bmCommandStatus
+ */
+ uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus << 6);
+ DPRINTF(s, D_VERBOSE, "%s: status = %d\n", __func__, ret);
+ return ret;
+}
+
+static void ccid_reset_error_status(USBCCIDState *s)
+{
+ s->bError = ERROR_CMD_NOT_SUPPORTED;
+ s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+}
+
+static void ccid_write_slot_status(USBCCIDState *s, CCID_Header *recv)
+{
+ CCID_SlotStatus *h = ccid_reserve_recv_buf(s, sizeof(CCID_SlotStatus));
+ if (h == NULL) {
+ return;
+ }
+ h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus;
+ h->b.hdr.dwLength = 0;
+ h->b.hdr.bSlot = recv->bSlot;
+ h->b.hdr.bSeq = recv->bSeq;
+ h->b.bStatus = ccid_calc_status(s);
+ h->b.bError = s->bError;
+ h->bClockStatus = CLOCK_STATUS_RUNNING;
+ ccid_reset_error_status(s);
+ usb_wakeup(s->bulk, 0);
+}
+
+static void ccid_write_parameters(USBCCIDState *s, CCID_Header *recv)
+{
+ CCID_Parameter *h;
+ uint32_t len = s->ulProtocolDataStructureSize;
+
+ h = ccid_reserve_recv_buf(s, sizeof(CCID_Parameter) + len);
+ if (h == NULL) {
+ return;
+ }
+ h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters;
+ h->b.hdr.dwLength = 0;
+ h->b.hdr.bSlot = recv->bSlot;
+ h->b.hdr.bSeq = recv->bSeq;
+ h->b.bStatus = ccid_calc_status(s);
+ h->b.bError = s->bError;
+ h->bProtocolNum = s->bProtocolNum;
+ h->abProtocolDataStructure = s->abProtocolDataStructure;
+ ccid_reset_error_status(s);
+ usb_wakeup(s->bulk, 0);
+}
+
+static void ccid_write_data_block(USBCCIDState *s, uint8_t slot, uint8_t seq,
+ const uint8_t *data, uint32_t len)
+{
+ CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len);
+
+ if (p == NULL) {
+ return;
+ }
+ p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock;
+ p->b.hdr.dwLength = cpu_to_le32(len);
+ p->b.hdr.bSlot = slot;
+ p->b.hdr.bSeq = seq;
+ p->b.bStatus = ccid_calc_status(s);
+ p->b.bError = s->bError;
+ if (p->b.bError) {
+ DPRINTF(s, D_VERBOSE, "error %d\n", p->b.bError);
+ }
+ memcpy(p->abData, data, len);
+ ccid_reset_error_status(s);
+ usb_wakeup(s->bulk, 0);
+}
+
+static void ccid_report_error_failed(USBCCIDState *s, uint8_t error)
+{
+ s->bmCommandStatus = COMMAND_STATUS_FAILED;
+ s->bError = error;
+}
+
+static void ccid_write_data_block_answer(USBCCIDState *s,
+ const uint8_t *data, uint32_t len)
+{
+ uint8_t seq;
+ uint8_t slot;
+
+ if (!ccid_has_pending_answers(s)) {
+ DPRINTF(s, D_WARN, "error: no pending answer to return to guest\n");
+ ccid_report_error_failed(s, ERROR_ICC_MUTE);
+ return;
+ }
+ ccid_remove_pending_answer(s, &slot, &seq);
+ ccid_write_data_block(s, slot, seq, data, len);
+}
+
+static uint8_t atr_get_protocol_num(const uint8_t *atr, uint32_t len)
+{
+ int i;
+
+ if (len < 2 || !(atr[1] & 0x80)) {
+ /* too short or TD1 not included */
+ return 0; /* T=0, default */
+ }
+ i = 1 + !!(atr[1] & 0x10) + !!(atr[1] & 0x20) + !!(atr[1] & 0x40);
+ i += !!(atr[1] & 0x80);
+ return atr[i] & 0x0f;
+}
+
+static void ccid_write_data_block_atr(USBCCIDState *s, CCID_Header *recv)
+{
+ const uint8_t *atr = NULL;
+ uint32_t len = 0;
+ uint8_t atr_protocol_num;
+ CCID_T0ProtocolDataStructure *t0 = &s->abProtocolDataStructure.t0;
+ CCID_T1ProtocolDataStructure *t1 = &s->abProtocolDataStructure.t1;
+
+ if (s->card) {
+ atr = ccid_card_get_atr(s->card, &len);
+ }
+ atr_protocol_num = atr_get_protocol_num(atr, len);
+ DPRINTF(s, D_VERBOSE, "%s: atr contains protocol=%d\n", __func__,
+ atr_protocol_num);
+ /* set parameters from ATR - see spec page 109 */
+ s->bProtocolNum = (atr_protocol_num <= 1 ? atr_protocol_num
+ : s->bProtocolNum);
+ switch (atr_protocol_num) {
+ case 0:
+ /* TODO: unimplemented ATR T0 parameters */
+ t0->bmFindexDindex = 0;
+ t0->bmTCCKST0 = 0;
+ t0->bGuardTimeT0 = 0;
+ t0->bWaitingIntegerT0 = 0;
+ t0->bClockStop = 0;
+ break;
+ case 1:
+ /* TODO: unimplemented ATR T1 parameters */
+ t1->bmFindexDindex = 0;
+ t1->bmTCCKST1 = 0;
+ t1->bGuardTimeT1 = 0;
+ t1->bWaitingIntegerT1 = 0;
+ t1->bClockStop = 0;
+ t1->bIFSC = 0;
+ t1->bNadValue = 0;
+ break;
+ default:
+ DPRINTF(s, D_WARN, "%s: error: unsupported ATR protocol %d\n",
+ __func__, atr_protocol_num);
+ }
+ ccid_write_data_block(s, recv->bSlot, recv->bSeq, atr, len);
+}
+
+static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv)
+{
+ CCID_SetParameters *ph = (CCID_SetParameters *) recv;
+ uint32_t protocol_num = ph->bProtocolNum & 3;
+
+ if (protocol_num != 0 && protocol_num != 1) {
+ ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED);
+ return;
+ }
+ s->bProtocolNum = protocol_num;
+ s->abProtocolDataStructure = ph->abProtocolDataStructure;
+}
+
+/*
+ * must be 5 bytes for T=0, 7 bytes for T=1
+ * See page 52
+ */
+static const CCID_ProtocolDataStructure defaultProtocolDataStructure = {
+ .t1 = {
+ .bmFindexDindex = 0x77,
+ .bmTCCKST1 = 0x00,
+ .bGuardTimeT1 = 0x00,
+ .bWaitingIntegerT1 = 0x00,
+ .bClockStop = 0x00,
+ .bIFSC = 0xfe,
+ .bNadValue = 0x00,
+ }
+};
+
+static void ccid_reset_parameters(USBCCIDState *s)
+{
+ s->bProtocolNum = 0; /* T=0 */
+ s->abProtocolDataStructure = defaultProtocolDataStructure;
+}
+
+/* NOTE: only a single slot is supported (SLOT_0) */
+static void ccid_on_slot_change(USBCCIDState *s, bool full)
+{
+ /* RDR_to_PC_NotifySlotChange, 6.3.1 page 56 */
+ uint8_t current = s->bmSlotICCState;
+ if (full) {
+ s->bmSlotICCState |= SLOT_0_STATE_MASK;
+ } else {
+ s->bmSlotICCState &= ~SLOT_0_STATE_MASK;
+ }
+ if (current != s->bmSlotICCState) {
+ s->bmSlotICCState |= SLOT_0_CHANGED_MASK;
+ }
+ s->notify_slot_change = true;
+ usb_wakeup(s->intr, 0);
+}
+
+static void ccid_write_data_block_error(
+ USBCCIDState *s, uint8_t slot, uint8_t seq)
+{
+ ccid_write_data_block(s, slot, seq, NULL, 0);
+}
+
+static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv)
+{
+ uint32_t len;
+
+ if (ccid_card_status(s) != ICC_STATUS_PRESENT_ACTIVE) {
+ DPRINTF(s, 1,
+ "usb-ccid: not sending apdu to client, no card connected\n");
+ ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq);
+ return;
+ }
+ len = le32_to_cpu(recv->hdr.dwLength);
+ DPRINTF(s, 1, "%s: seq %d, len %d\n", __func__,
+ recv->hdr.bSeq, len);
+ ccid_add_pending_answer(s, (CCID_Header *)recv);
+ if (s->card) {
+ ccid_card_apdu_from_guest(s->card, recv->abData, len);
+ } else {
+ DPRINTF(s, D_WARN, "warning: discarded apdu\n");
+ }
+}
+
+static const char *ccid_message_type_to_str(uint8_t type)
+{
+ switch (type) {
+ case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: return "IccPowerOn";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: return "IccPowerOff";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: return "GetSlotStatus";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: return "XfrBlock";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: return "GetParameters";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: return "ResetParameters";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: return "SetParameters";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_Escape: return "Escape";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_IccClock: return "IccClock";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU: return "T0APDU";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_Secure: return "Secure";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical: return "Mechanical";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_Abort: return "Abort";
+ case CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency:
+ return "SetDataRateAndClockFrequency";
+ }
+ return "unknown";
+}
+
+static void ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p)
+{
+ CCID_Header *ccid_header;
+
+ if (p->iov.size + s->bulk_out_pos > BULK_OUT_DATA_SIZE) {
+ p->status = USB_RET_STALL;
+ return;
+ }
+ ccid_header = (CCID_Header *)s->bulk_out_data;
+ usb_packet_copy(p, s->bulk_out_data + s->bulk_out_pos, p->iov.size);
+ s->bulk_out_pos += p->iov.size;
+ if (p->iov.size == CCID_MAX_PACKET_SIZE) {
+ DPRINTF(s, D_VERBOSE,
+ "usb-ccid: bulk_in: expecting more packets (%zd/%d)\n",
+ p->iov.size, ccid_header->dwLength);
+ return;
+ }
+ if (s->bulk_out_pos < 10) {
+ DPRINTF(s, 1,
+ "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n",
+ __func__);
+ } else {
+ DPRINTF(s, D_MORE_INFO, "%s %x %s\n", __func__,
+ ccid_header->bMessageType,
+ ccid_message_type_to_str(ccid_header->bMessageType));
+ switch (ccid_header->bMessageType) {
+ case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus:
+ ccid_write_slot_status(s, ccid_header);
+ break;
+ case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn:
+ DPRINTF(s, 1, "%s: PowerOn: %d\n", __func__,
+ ((CCID_IccPowerOn *)(ccid_header))->bPowerSelect);
+ s->powered = true;
+ if (!ccid_card_inserted(s)) {
+ ccid_report_error_failed(s, ERROR_ICC_MUTE);
+ }
+ /* atr is written regardless of error. */
+ ccid_write_data_block_atr(s, ccid_header);
+ break;
+ case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff:
+ ccid_reset_error_status(s);
+ s->powered = false;
+ ccid_write_slot_status(s, ccid_header);
+ break;
+ case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock:
+ ccid_on_apdu_from_guest(s, (CCID_XferBlock *)s->bulk_out_data);
+ break;
+ case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters:
+ ccid_reset_error_status(s);
+ ccid_set_parameters(s, ccid_header);
+ ccid_write_parameters(s, ccid_header);
+ break;
+ case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters:
+ ccid_reset_error_status(s);
+ ccid_reset_parameters(s);
+ ccid_write_parameters(s, ccid_header);
+ break;
+ case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters:
+ ccid_reset_error_status(s);
+ ccid_write_parameters(s, ccid_header);
+ break;
+ case CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical:
+ ccid_report_error_failed(s, 0);
+ ccid_write_slot_status(s, ccid_header);
+ break;
+ default:
+ DPRINTF(s, 1,
+ "handle_data: ERROR: unhandled message type %Xh\n",
+ ccid_header->bMessageType);
+ /*
+ * The caller is expecting the device to respond, tell it we
+ * don't support the operation.
+ */
+ ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED);
+ ccid_write_slot_status(s, ccid_header);
+ break;
+ }
+ }
+ s->bulk_out_pos = 0;
+}
+
+static void ccid_bulk_in_copy_to_guest(USBCCIDState *s, USBPacket *p)
+{
+ int len = 0;
+
+ ccid_bulk_in_get(s);
+ if (s->current_bulk_in != NULL) {
+ len = MIN(s->current_bulk_in->len - s->current_bulk_in->pos,
+ p->iov.size);
+ usb_packet_copy(p, s->current_bulk_in->data +
+ s->current_bulk_in->pos, len);
+ s->current_bulk_in->pos += len;
+ if (s->current_bulk_in->pos == s->current_bulk_in->len) {
+ ccid_bulk_in_release(s);
+ }
+ } else {
+ /* return when device has no data - usb 2.0 spec Table 8-4 */
+ p->status = USB_RET_NAK;
+ }
+ if (len) {
+ DPRINTF(s, D_MORE_INFO,
+ "%s: %zd/%d req/act to guest (BULK_IN)\n",
+ __func__, p->iov.size, len);
+ }
+ if (len < p->iov.size) {
+ DPRINTF(s, 1,
+ "%s: returning short (EREMOTEIO) %d < %zd\n",
+ __func__, len, p->iov.size);
+ }
+}
+
+static void ccid_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBCCIDState *s = USB_CCID_DEV(dev);
+ uint8_t buf[2];
+
+ switch (p->pid) {
+ case USB_TOKEN_OUT:
+ ccid_handle_bulk_out(s, p);
+ break;
+
+ case USB_TOKEN_IN:
+ switch (p->ep->nr) {
+ case CCID_BULK_IN_EP:
+ ccid_bulk_in_copy_to_guest(s, p);
+ break;
+ case CCID_INT_IN_EP:
+ if (s->notify_slot_change) {
+ /* page 56, RDR_to_PC_NotifySlotChange */
+ buf[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange;
+ buf[1] = s->bmSlotICCState;
+ usb_packet_copy(p, buf, 2);
+ s->notify_slot_change = false;
+ s->bmSlotICCState &= ~SLOT_0_CHANGED_MASK;
+ DPRINTF(s, D_INFO,
+ "handle_data: int_in: notify_slot_change %X, "
+ "requested len %zd\n",
+ s->bmSlotICCState, p->iov.size);
+ } else {
+ p->status = USB_RET_NAK;
+ }
+ break;
+ default:
+ DPRINTF(s, 1, "Bad endpoint\n");
+ p->status = USB_RET_STALL;
+ break;
+ }
+ break;
+ default:
+ DPRINTF(s, 1, "Bad token\n");
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void ccid_handle_destroy(USBDevice *dev)
+{
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ ccid_bulk_in_clear(s);
+}
+
+static void ccid_flush_pending_answers(USBCCIDState *s)
+{
+ while (ccid_has_pending_answers(s)) {
+ ccid_write_data_block_answer(s, NULL, 0);
+ }
+}
+
+static Answer *ccid_peek_next_answer(USBCCIDState *s)
+{
+ return s->pending_answers_num == 0
+ ? NULL
+ : &s->pending_answers[s->pending_answers_start % PENDING_ANSWERS_NUM];
+}
+
+static Property ccid_props[] = {
+ DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+#define TYPE_CCID_BUS "ccid-bus"
+#define CCID_BUS(obj) OBJECT_CHECK(CCIDBus, (obj), TYPE_CCID_BUS)
+
+static const TypeInfo ccid_bus_info = {
+ .name = TYPE_CCID_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(CCIDBus),
+};
+
+void ccid_card_send_apdu_to_guest(CCIDCardState *card,
+ uint8_t *apdu, uint32_t len)
+{
+ DeviceState *qdev = DEVICE(card);
+ USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent);
+ USBCCIDState *s = USB_CCID_DEV(dev);
+ Answer *answer;
+
+ if (!ccid_has_pending_answers(s)) {
+ DPRINTF(s, 1, "CCID ERROR: got an APDU without pending answers\n");
+ return;
+ }
+ s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+ answer = ccid_peek_next_answer(s);
+ if (answer == NULL) {
+ DPRINTF(s, D_WARN, "%s: error: unexpected lack of answer\n", __func__);
+ ccid_report_error_failed(s, ERROR_HW_ERROR);
+ return;
+ }
+ DPRINTF(s, 1, "APDU returned to guest %d (answer seq %d, slot %d)\n",
+ len, answer->seq, answer->slot);
+ ccid_write_data_block_answer(s, apdu, len);
+}
+
+void ccid_card_card_removed(CCIDCardState *card)
+{
+ DeviceState *qdev = DEVICE(card);
+ USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent);
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ ccid_on_slot_change(s, false);
+ ccid_flush_pending_answers(s);
+ ccid_reset(s);
+}
+
+int ccid_card_ccid_attach(CCIDCardState *card)
+{
+ DeviceState *qdev = DEVICE(card);
+ USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent);
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ DPRINTF(s, 1, "CCID Attach\n");
+ if (s->migration_state == MIGRATION_MIGRATED) {
+ s->migration_state = MIGRATION_NONE;
+ }
+ return 0;
+}
+
+void ccid_card_ccid_detach(CCIDCardState *card)
+{
+ DeviceState *qdev = DEVICE(card);
+ USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent);
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ DPRINTF(s, 1, "CCID Detach\n");
+ if (ccid_card_inserted(s)) {
+ ccid_on_slot_change(s, false);
+ }
+ ccid_detach(s);
+}
+
+void ccid_card_card_error(CCIDCardState *card, uint64_t error)
+{
+ DeviceState *qdev = DEVICE(card);
+ USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent);
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ s->bmCommandStatus = COMMAND_STATUS_FAILED;
+ s->last_answer_error = error;
+ DPRINTF(s, 1, "VSC_Error: %" PRIX64 "\n", s->last_answer_error);
+ /* TODO: these errors should be more verbose and propagated to the guest.*/
+ /*
+ * We flush all pending answers on CardRemove message in ccid-card-passthru,
+ * so check that first to not trigger abort
+ */
+ if (ccid_has_pending_answers(s)) {
+ ccid_write_data_block_answer(s, NULL, 0);
+ }
+}
+
+void ccid_card_card_inserted(CCIDCardState *card)
+{
+ DeviceState *qdev = DEVICE(card);
+ USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent);
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+ ccid_flush_pending_answers(s);
+ ccid_on_slot_change(s, true);
+}
+
+static int ccid_card_exit(DeviceState *qdev)
+{
+ int ret = 0;
+ CCIDCardState *card = CCID_CARD(qdev);
+ USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent);
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ if (ccid_card_inserted(s)) {
+ ccid_card_card_removed(card);
+ }
+ ret = ccid_card_exitfn(card);
+ s->card = NULL;
+ return ret;
+}
+
+static int ccid_card_init(DeviceState *qdev)
+{
+ CCIDCardState *card = CCID_CARD(qdev);
+ USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent);
+ USBCCIDState *s = USB_CCID_DEV(dev);
+ int ret = 0;
+
+ if (card->slot != 0) {
+ error_report("Warning: usb-ccid supports one slot, can't add %d",
+ card->slot);
+ return -1;
+ }
+ if (s->card != NULL) {
+ error_report("Warning: usb-ccid card already full, not adding");
+ return -1;
+ }
+ ret = ccid_card_initfn(card);
+ if (ret == 0) {
+ s->card = card;
+ }
+ return ret;
+}
+
+static void ccid_realize(USBDevice *dev, Error **errp)
+{
+ USBCCIDState *s = USB_CCID_DEV(dev);
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ qbus_create_inplace(&s->bus, sizeof(s->bus), TYPE_CCID_BUS, DEVICE(dev),
+ NULL);
+ qbus_set_hotplug_handler(BUS(&s->bus), DEVICE(dev), &error_abort);
+ s->intr = usb_ep_get(dev, USB_TOKEN_IN, CCID_INT_IN_EP);
+ s->bulk = usb_ep_get(dev, USB_TOKEN_IN, CCID_BULK_IN_EP);
+ s->card = NULL;
+ s->migration_state = MIGRATION_NONE;
+ s->migration_target_ip = 0;
+ s->migration_target_port = 0;
+ s->dev.speed = USB_SPEED_FULL;
+ s->dev.speedmask = USB_SPEED_MASK_FULL;
+ s->notify_slot_change = false;
+ s->powered = true;
+ s->pending_answers_num = 0;
+ s->last_answer_error = 0;
+ s->bulk_in_pending_start = 0;
+ s->bulk_in_pending_end = 0;
+ s->current_bulk_in = NULL;
+ ccid_reset_error_status(s);
+ s->bulk_out_pos = 0;
+ ccid_reset_parameters(s);
+ ccid_reset(s);
+ s->debug = parse_debug_env("QEMU_CCID_DEBUG", D_VERBOSE, s->debug);
+}
+
+static int ccid_post_load(void *opaque, int version_id)
+{
+ USBCCIDState *s = opaque;
+
+ /*
+ * This must be done after usb_device_attach, which sets state to ATTACHED,
+ * while it must be DEFAULT in order to accept packets (like it is after
+ * reset, but reset will reset our addr and call our reset handler which
+ * may change state, and we don't want to do that when migrating).
+ */
+ s->dev.state = s->state_vmstate;
+ return 0;
+}
+
+static void ccid_pre_save(void *opaque)
+{
+ USBCCIDState *s = opaque;
+
+ s->state_vmstate = s->dev.state;
+ if (s->dev.attached) {
+ /*
+ * Migrating an open device, ignore reconnection CHR_EVENT to avoid an
+ * erroneous detach.
+ */
+ s->migration_state = MIGRATION_MIGRATED;
+ }
+}
+
+static VMStateDescription bulk_in_vmstate = {
+ .name = "CCID BulkIn state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(data, BulkIn),
+ VMSTATE_UINT32(len, BulkIn),
+ VMSTATE_UINT32(pos, BulkIn),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static VMStateDescription answer_vmstate = {
+ .name = "CCID Answer state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(slot, Answer),
+ VMSTATE_UINT8(seq, Answer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static VMStateDescription usb_device_vmstate = {
+ .name = "usb_device",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(addr, USBDevice),
+ VMSTATE_BUFFER(setup_buf, USBDevice),
+ VMSTATE_BUFFER(data_buf, USBDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static VMStateDescription ccid_vmstate = {
+ .name = "usb-ccid",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ccid_post_load,
+ .pre_save = ccid_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(dev, USBCCIDState, 1, usb_device_vmstate, USBDevice),
+ VMSTATE_UINT8(debug, USBCCIDState),
+ VMSTATE_BUFFER(bulk_out_data, USBCCIDState),
+ VMSTATE_UINT32(bulk_out_pos, USBCCIDState),
+ VMSTATE_UINT8(bmSlotICCState, USBCCIDState),
+ VMSTATE_UINT8(powered, USBCCIDState),
+ VMSTATE_UINT8(notify_slot_change, USBCCIDState),
+ VMSTATE_UINT64(last_answer_error, USBCCIDState),
+ VMSTATE_UINT8(bError, USBCCIDState),
+ VMSTATE_UINT8(bmCommandStatus, USBCCIDState),
+ VMSTATE_UINT8(bProtocolNum, USBCCIDState),
+ VMSTATE_BUFFER(abProtocolDataStructure.data, USBCCIDState),
+ VMSTATE_UINT32(ulProtocolDataStructureSize, USBCCIDState),
+ VMSTATE_STRUCT_ARRAY(bulk_in_pending, USBCCIDState,
+ BULK_IN_PENDING_NUM, 1, bulk_in_vmstate, BulkIn),
+ VMSTATE_UINT32(bulk_in_pending_start, USBCCIDState),
+ VMSTATE_UINT32(bulk_in_pending_end, USBCCIDState),
+ VMSTATE_STRUCT_ARRAY(pending_answers, USBCCIDState,
+ PENDING_ANSWERS_NUM, 1, answer_vmstate, Answer),
+ VMSTATE_UINT32(pending_answers_num, USBCCIDState),
+ VMSTATE_UINT8(migration_state, USBCCIDState),
+ VMSTATE_UINT32(state_vmstate, USBCCIDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property ccid_properties[] = {
+ DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ccid_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ uc->realize = ccid_realize;
+ uc->product_desc = "QEMU USB CCID";
+ uc->usb_desc = &desc_ccid;
+ uc->handle_reset = ccid_handle_reset;
+ uc->handle_control = ccid_handle_control;
+ uc->handle_data = ccid_handle_data;
+ uc->handle_destroy = ccid_handle_destroy;
+ dc->desc = "CCID Rev 1.1 smartcard reader";
+ dc->vmsd = &ccid_vmstate;
+ dc->props = ccid_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ hc->unplug = qdev_simple_device_unplug_cb;
+}
+
+static const TypeInfo ccid_info = {
+ .name = CCID_DEV_NAME,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBCCIDState),
+ .class_init = ccid_class_initfn,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static void ccid_card_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->bus_type = TYPE_CCID_BUS;
+ k->init = ccid_card_init;
+ k->exit = ccid_card_exit;
+ k->props = ccid_props;
+}
+
+static const TypeInfo ccid_card_type_info = {
+ .name = TYPE_CCID_CARD,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(CCIDCardState),
+ .abstract = true,
+ .class_size = sizeof(CCIDCardClass),
+ .class_init = ccid_card_class_init,
+};
+
+static void ccid_register_types(void)
+{
+ type_register_static(&ccid_bus_info);
+ type_register_static(&ccid_card_type_info);
+ type_register_static(&ccid_info);
+ usb_legacy_register(CCID_DEV_NAME, "ccid", NULL);
+}
+
+type_init(ccid_register_types)
diff --git a/hw/usb/dev-storage.c b/hw/usb/dev-storage.c
new file mode 100644
index 00000000..9a4e7dc0
--- /dev/null
+++ b/hw/usb/dev-storage.c
@@ -0,0 +1,867 @@
+/*
+ * USB Mass Storage Device emulation
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "hw/scsi/scsi.h"
+#include "ui/console.h"
+#include "monitor/monitor.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "qapi/visitor.h"
+
+//#define DEBUG_MSD
+
+#ifdef DEBUG_MSD
+#define DPRINTF(fmt, ...) \
+do { printf("usb-msd: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+/* USB requests. */
+#define MassStorageReset 0xff
+#define GetMaxLun 0xfe
+
+enum USBMSDMode {
+ USB_MSDM_CBW, /* Command Block. */
+ USB_MSDM_DATAOUT, /* Transfer data to device. */
+ USB_MSDM_DATAIN, /* Transfer data from device. */
+ USB_MSDM_CSW /* Command Status. */
+};
+
+struct usb_msd_csw {
+ uint32_t sig;
+ uint32_t tag;
+ uint32_t residue;
+ uint8_t status;
+};
+
+typedef struct {
+ USBDevice dev;
+ enum USBMSDMode mode;
+ uint32_t scsi_off;
+ uint32_t scsi_len;
+ uint32_t data_len;
+ struct usb_msd_csw csw;
+ SCSIRequest *req;
+ SCSIBus bus;
+ /* For async completion. */
+ USBPacket *packet;
+ /* usb-storage only */
+ BlockConf conf;
+ uint32_t removable;
+ SCSIDevice *scsi_dev;
+} MSDState;
+
+#define TYPE_USB_STORAGE "usb-storage-dev"
+#define USB_STORAGE_DEV(obj) OBJECT_CHECK(MSDState, (obj), TYPE_USB_STORAGE)
+
+struct usb_msd_cbw {
+ uint32_t sig;
+ uint32_t tag;
+ uint32_t data_len;
+ uint8_t flags;
+ uint8_t lun;
+ uint8_t cmd_len;
+ uint8_t cmd[16];
+};
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+ STR_CONFIG_FULL,
+ STR_CONFIG_HIGH,
+ STR_CONFIG_SUPER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT] = "QEMU USB HARDDRIVE",
+ [STR_SERIALNUMBER] = "1",
+ [STR_CONFIG_FULL] = "Full speed config (usb 1.1)",
+ [STR_CONFIG_HIGH] = "High speed config (usb 2.0)",
+ [STR_CONFIG_SUPER] = "Super speed config (usb 3.0)",
+};
+
+static const USBDescIface desc_iface_full = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_MASS_STORAGE,
+ .bInterfaceSubClass = 0x06, /* SCSI */
+ .bInterfaceProtocol = 0x50, /* Bulk */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 64,
+ },
+ }
+};
+
+static const USBDescDevice desc_device_full = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_FULL,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .nif = 1,
+ .ifs = &desc_iface_full,
+ },
+ },
+};
+
+static const USBDescIface desc_iface_high = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_MASS_STORAGE,
+ .bInterfaceSubClass = 0x06, /* SCSI */
+ .bInterfaceProtocol = 0x50, /* Bulk */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ },
+ }
+};
+
+static const USBDescDevice desc_device_high = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_HIGH,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .nif = 1,
+ .ifs = &desc_iface_high,
+ },
+ },
+};
+
+static const USBDescIface desc_iface_super = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_MASS_STORAGE,
+ .bInterfaceSubClass = 0x06, /* SCSI */
+ .bInterfaceProtocol = 0x50, /* Bulk */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 1024,
+ .bMaxBurst = 15,
+ },{
+ .bEndpointAddress = USB_DIR_OUT | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 1024,
+ .bMaxBurst = 15,
+ },
+ }
+};
+
+static const USBDescDevice desc_device_super = {
+ .bcdUSB = 0x0300,
+ .bMaxPacketSize0 = 9,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_SUPER,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .nif = 1,
+ .ifs = &desc_iface_super,
+ },
+ },
+};
+
+static const USBDesc desc = {
+ .id = {
+ .idVendor = 0x46f4, /* CRC16() of "QEMU" */
+ .idProduct = 0x0001,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_full,
+ .high = &desc_device_high,
+ .super = &desc_device_super,
+ .str = desc_strings,
+};
+
+static void usb_msd_copy_data(MSDState *s, USBPacket *p)
+{
+ uint32_t len;
+ len = p->iov.size - p->actual_length;
+ if (len > s->scsi_len)
+ len = s->scsi_len;
+ usb_packet_copy(p, scsi_req_get_buf(s->req) + s->scsi_off, len);
+ s->scsi_len -= len;
+ s->scsi_off += len;
+ s->data_len -= len;
+ if (s->scsi_len == 0 || s->data_len == 0) {
+ scsi_req_continue(s->req);
+ }
+}
+
+static void usb_msd_send_status(MSDState *s, USBPacket *p)
+{
+ int len;
+
+ DPRINTF("Command status %d tag 0x%x, len %zd\n",
+ s->csw.status, le32_to_cpu(s->csw.tag), p->iov.size);
+
+ assert(s->csw.sig == cpu_to_le32(0x53425355));
+ len = MIN(sizeof(s->csw), p->iov.size);
+ usb_packet_copy(p, &s->csw, len);
+ memset(&s->csw, 0, sizeof(s->csw));
+}
+
+static void usb_msd_packet_complete(MSDState *s)
+{
+ USBPacket *p = s->packet;
+
+ /* Set s->packet to NULL before calling usb_packet_complete
+ because another request may be issued before
+ usb_packet_complete returns. */
+ DPRINTF("Packet complete %p\n", p);
+ s->packet = NULL;
+ usb_packet_complete(&s->dev, p);
+}
+
+static void usb_msd_transfer_data(SCSIRequest *req, uint32_t len)
+{
+ MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent);
+ USBPacket *p = s->packet;
+
+ assert((s->mode == USB_MSDM_DATAOUT) == (req->cmd.mode == SCSI_XFER_TO_DEV));
+ s->scsi_len = len;
+ s->scsi_off = 0;
+ if (p) {
+ usb_msd_copy_data(s, p);
+ p = s->packet;
+ if (p && p->actual_length == p->iov.size) {
+ p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */
+ usb_msd_packet_complete(s);
+ }
+ }
+}
+
+static void usb_msd_command_complete(SCSIRequest *req, uint32_t status, size_t resid)
+{
+ MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent);
+ USBPacket *p = s->packet;
+
+ DPRINTF("Command complete %d tag 0x%x\n", status, req->tag);
+
+ s->csw.sig = cpu_to_le32(0x53425355);
+ s->csw.tag = cpu_to_le32(req->tag);
+ s->csw.residue = cpu_to_le32(s->data_len);
+ s->csw.status = status != 0;
+
+ if (s->packet) {
+ if (s->data_len == 0 && s->mode == USB_MSDM_DATAOUT) {
+ /* A deferred packet with no write data remaining must be
+ the status read packet. */
+ usb_msd_send_status(s, p);
+ s->mode = USB_MSDM_CBW;
+ } else if (s->mode == USB_MSDM_CSW) {
+ usb_msd_send_status(s, p);
+ s->mode = USB_MSDM_CBW;
+ } else {
+ if (s->data_len) {
+ int len = (p->iov.size - p->actual_length);
+ usb_packet_skip(p, len);
+ s->data_len -= len;
+ }
+ if (s->data_len == 0) {
+ s->mode = USB_MSDM_CSW;
+ }
+ }
+ p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */
+ usb_msd_packet_complete(s);
+ } else if (s->data_len == 0) {
+ s->mode = USB_MSDM_CSW;
+ }
+ scsi_req_unref(req);
+ s->req = NULL;
+}
+
+static void usb_msd_request_cancelled(SCSIRequest *req)
+{
+ MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent);
+
+ if (req == s->req) {
+ scsi_req_unref(s->req);
+ s->req = NULL;
+ s->scsi_len = 0;
+ }
+}
+
+static void usb_msd_handle_reset(USBDevice *dev)
+{
+ MSDState *s = (MSDState *)dev;
+
+ DPRINTF("Reset\n");
+ if (s->req) {
+ scsi_req_cancel(s->req);
+ }
+ assert(s->req == NULL);
+
+ if (s->packet) {
+ s->packet->status = USB_RET_STALL;
+ usb_msd_packet_complete(s);
+ }
+
+ s->mode = USB_MSDM_CBW;
+}
+
+static void usb_msd_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ MSDState *s = (MSDState *)dev;
+ SCSIDevice *scsi_dev;
+ int ret, maxlun;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch (request) {
+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+ break;
+ /* Class specific requests. */
+ case ClassInterfaceOutRequest | MassStorageReset:
+ /* Reset state ready for the next CBW. */
+ s->mode = USB_MSDM_CBW;
+ break;
+ case ClassInterfaceRequest | GetMaxLun:
+ maxlun = 0;
+ for (;;) {
+ scsi_dev = scsi_device_find(&s->bus, 0, 0, maxlun+1);
+ if (scsi_dev == NULL) {
+ break;
+ }
+ if (scsi_dev->lun != maxlun+1) {
+ break;
+ }
+ maxlun++;
+ }
+ DPRINTF("MaxLun %d\n", maxlun);
+ data[0] = maxlun;
+ p->actual_length = 1;
+ break;
+ default:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_msd_cancel_io(USBDevice *dev, USBPacket *p)
+{
+ MSDState *s = USB_STORAGE_DEV(dev);
+
+ assert(s->packet == p);
+ s->packet = NULL;
+
+ if (s->req) {
+ scsi_req_cancel(s->req);
+ }
+}
+
+static void usb_msd_handle_data(USBDevice *dev, USBPacket *p)
+{
+ MSDState *s = (MSDState *)dev;
+ uint32_t tag;
+ struct usb_msd_cbw cbw;
+ uint8_t devep = p->ep->nr;
+ SCSIDevice *scsi_dev;
+ uint32_t len;
+
+ switch (p->pid) {
+ case USB_TOKEN_OUT:
+ if (devep != 2)
+ goto fail;
+
+ switch (s->mode) {
+ case USB_MSDM_CBW:
+ if (p->iov.size != 31) {
+ error_report("usb-msd: Bad CBW size");
+ goto fail;
+ }
+ usb_packet_copy(p, &cbw, 31);
+ if (le32_to_cpu(cbw.sig) != 0x43425355) {
+ error_report("usb-msd: Bad signature %08x",
+ le32_to_cpu(cbw.sig));
+ goto fail;
+ }
+ DPRINTF("Command on LUN %d\n", cbw.lun);
+ scsi_dev = scsi_device_find(&s->bus, 0, 0, cbw.lun);
+ if (scsi_dev == NULL) {
+ error_report("usb-msd: Bad LUN %d", cbw.lun);
+ goto fail;
+ }
+ tag = le32_to_cpu(cbw.tag);
+ s->data_len = le32_to_cpu(cbw.data_len);
+ if (s->data_len == 0) {
+ s->mode = USB_MSDM_CSW;
+ } else if (cbw.flags & 0x80) {
+ s->mode = USB_MSDM_DATAIN;
+ } else {
+ s->mode = USB_MSDM_DATAOUT;
+ }
+ DPRINTF("Command tag 0x%x flags %08x len %d data %d\n",
+ tag, cbw.flags, cbw.cmd_len, s->data_len);
+ assert(le32_to_cpu(s->csw.residue) == 0);
+ s->scsi_len = 0;
+ s->req = scsi_req_new(scsi_dev, tag, cbw.lun, cbw.cmd, NULL);
+#ifdef DEBUG_MSD
+ scsi_req_print(s->req);
+#endif
+ len = scsi_req_enqueue(s->req);
+ if (len) {
+ scsi_req_continue(s->req);
+ }
+ break;
+
+ case USB_MSDM_DATAOUT:
+ DPRINTF("Data out %zd/%d\n", p->iov.size, s->data_len);
+ if (p->iov.size > s->data_len) {
+ goto fail;
+ }
+
+ if (s->scsi_len) {
+ usb_msd_copy_data(s, p);
+ }
+ if (le32_to_cpu(s->csw.residue)) {
+ int len = p->iov.size - p->actual_length;
+ if (len) {
+ usb_packet_skip(p, len);
+ s->data_len -= len;
+ if (s->data_len == 0) {
+ s->mode = USB_MSDM_CSW;
+ }
+ }
+ }
+ if (p->actual_length < p->iov.size) {
+ DPRINTF("Deferring packet %p [wait data-out]\n", p);
+ s->packet = p;
+ p->status = USB_RET_ASYNC;
+ }
+ break;
+
+ default:
+ DPRINTF("Unexpected write (len %zd)\n", p->iov.size);
+ goto fail;
+ }
+ break;
+
+ case USB_TOKEN_IN:
+ if (devep != 1)
+ goto fail;
+
+ switch (s->mode) {
+ case USB_MSDM_DATAOUT:
+ if (s->data_len != 0 || p->iov.size < 13) {
+ goto fail;
+ }
+ /* Waiting for SCSI write to complete. */
+ s->packet = p;
+ p->status = USB_RET_ASYNC;
+ break;
+
+ case USB_MSDM_CSW:
+ if (p->iov.size < 13) {
+ goto fail;
+ }
+
+ if (s->req) {
+ /* still in flight */
+ DPRINTF("Deferring packet %p [wait status]\n", p);
+ s->packet = p;
+ p->status = USB_RET_ASYNC;
+ } else {
+ usb_msd_send_status(s, p);
+ s->mode = USB_MSDM_CBW;
+ }
+ break;
+
+ case USB_MSDM_DATAIN:
+ DPRINTF("Data in %zd/%d, scsi_len %d\n",
+ p->iov.size, s->data_len, s->scsi_len);
+ if (s->scsi_len) {
+ usb_msd_copy_data(s, p);
+ }
+ if (le32_to_cpu(s->csw.residue)) {
+ int len = p->iov.size - p->actual_length;
+ if (len) {
+ usb_packet_skip(p, len);
+ s->data_len -= len;
+ if (s->data_len == 0) {
+ s->mode = USB_MSDM_CSW;
+ }
+ }
+ }
+ if (p->actual_length < p->iov.size) {
+ DPRINTF("Deferring packet %p [wait data-in]\n", p);
+ s->packet = p;
+ p->status = USB_RET_ASYNC;
+ }
+ break;
+
+ default:
+ DPRINTF("Unexpected read (len %zd)\n", p->iov.size);
+ goto fail;
+ }
+ break;
+
+ default:
+ DPRINTF("Bad token\n");
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_msd_password_cb(void *opaque, int err)
+{
+ MSDState *s = opaque;
+ Error *local_err = NULL;
+
+ if (!err) {
+ usb_device_attach(&s->dev, &local_err);
+ }
+
+ if (local_err) {
+ error_report_err(local_err);
+ qdev_unplug(&s->dev.qdev, NULL);
+ }
+}
+
+static void *usb_msd_load_request(QEMUFile *f, SCSIRequest *req)
+{
+ MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent);
+
+ /* nothing to load, just store req in our state struct */
+ assert(s->req == NULL);
+ scsi_req_ref(req);
+ s->req = req;
+ return NULL;
+}
+
+static const struct SCSIBusInfo usb_msd_scsi_info_storage = {
+ .tcq = false,
+ .max_target = 0,
+ .max_lun = 0,
+
+ .transfer_data = usb_msd_transfer_data,
+ .complete = usb_msd_command_complete,
+ .cancel = usb_msd_request_cancelled,
+ .load_request = usb_msd_load_request,
+};
+
+static const struct SCSIBusInfo usb_msd_scsi_info_bot = {
+ .tcq = false,
+ .max_target = 0,
+ .max_lun = 15,
+
+ .transfer_data = usb_msd_transfer_data,
+ .complete = usb_msd_command_complete,
+ .cancel = usb_msd_request_cancelled,
+ .load_request = usb_msd_load_request,
+};
+
+static void usb_msd_realize_storage(USBDevice *dev, Error **errp)
+{
+ MSDState *s = USB_STORAGE_DEV(dev);
+ BlockBackend *blk = s->conf.blk;
+ SCSIDevice *scsi_dev;
+ Error *err = NULL;
+
+ if (!blk) {
+ error_setg(errp, "drive property not set");
+ return;
+ }
+
+ bdrv_add_key(blk_bs(blk), NULL, &err);
+ if (err) {
+ if (monitor_cur_is_qmp()) {
+ error_propagate(errp, err);
+ return;
+ }
+ error_free(err);
+ err = NULL;
+ if (cur_mon) {
+ monitor_read_bdrv_key_start(cur_mon, blk_bs(blk),
+ usb_msd_password_cb, s);
+ s->dev.auto_attach = 0;
+ } else {
+ autostart = 0;
+ }
+ }
+
+ blkconf_serial(&s->conf, &dev->serial);
+ blkconf_blocksizes(&s->conf);
+
+ /*
+ * Hack alert: this pretends to be a block device, but it's really
+ * a SCSI bus that can serve only a single device, which it
+ * creates automatically. But first it needs to detach from its
+ * blockdev, or else scsi_bus_legacy_add_drive() dies when it
+ * attaches again.
+ *
+ * The hack is probably a bad idea.
+ */
+ blk_detach_dev(blk, &s->dev.qdev);
+ s->conf.blk = NULL;
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev),
+ &usb_msd_scsi_info_storage, NULL);
+ scsi_dev = scsi_bus_legacy_add_drive(&s->bus, blk, 0, !!s->removable,
+ s->conf.bootindex, dev->serial,
+ &err);
+ if (!scsi_dev) {
+ error_propagate(errp, err);
+ return;
+ }
+ usb_msd_handle_reset(dev);
+ s->scsi_dev = scsi_dev;
+}
+
+static void usb_msd_realize_bot(USBDevice *dev, Error **errp)
+{
+ MSDState *s = USB_STORAGE_DEV(dev);
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev),
+ &usb_msd_scsi_info_bot, NULL);
+ usb_msd_handle_reset(dev);
+}
+
+static USBDevice *usb_msd_init(USBBus *bus, const char *filename)
+{
+ static int nr=0;
+ Error *err = NULL;
+ char id[8];
+ QemuOpts *opts;
+ DriveInfo *dinfo;
+ USBDevice *dev;
+ const char *p1;
+ char fmt[32];
+
+ /* parse -usbdevice disk: syntax into drive opts */
+ do {
+ snprintf(id, sizeof(id), "usb%d", nr++);
+ opts = qemu_opts_create(qemu_find_opts("drive"), id, 1, NULL);
+ } while (!opts);
+
+ p1 = strchr(filename, ':');
+ if (p1++) {
+ const char *p2;
+
+ if (strstart(filename, "format=", &p2)) {
+ int len = MIN(p1 - p2, sizeof(fmt));
+ pstrcpy(fmt, len, p2);
+ qemu_opt_set(opts, "format", fmt, &error_abort);
+ } else if (*filename != ':') {
+ error_report("unrecognized USB mass-storage option %s", filename);
+ return NULL;
+ }
+ filename = p1;
+ }
+ if (!*filename) {
+ error_report("block device specification needed");
+ return NULL;
+ }
+ qemu_opt_set(opts, "file", filename, &error_abort);
+ qemu_opt_set(opts, "if", "none", &error_abort);
+
+ /* create host drive */
+ dinfo = drive_new(opts, 0);
+ if (!dinfo) {
+ qemu_opts_del(opts);
+ return NULL;
+ }
+
+ /* create guest device */
+ dev = usb_create(bus, "usb-storage");
+ qdev_prop_set_drive(&dev->qdev, "drive", blk_by_legacy_dinfo(dinfo),
+ &err);
+ if (err) {
+ error_report_err(err);
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+ return dev;
+}
+
+static const VMStateDescription vmstate_usb_msd = {
+ .name = "usb-storage",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, MSDState),
+ VMSTATE_UINT32(mode, MSDState),
+ VMSTATE_UINT32(scsi_len, MSDState),
+ VMSTATE_UINT32(scsi_off, MSDState),
+ VMSTATE_UINT32(data_len, MSDState),
+ VMSTATE_UINT32(csw.sig, MSDState),
+ VMSTATE_UINT32(csw.tag, MSDState),
+ VMSTATE_UINT32(csw.residue, MSDState),
+ VMSTATE_UINT8(csw.status, MSDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property msd_properties[] = {
+ DEFINE_BLOCK_PROPERTIES(MSDState, conf),
+ DEFINE_PROP_BIT("removable", MSDState, removable, 0, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_msd_class_initfn_common(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->product_desc = "QEMU USB MSD";
+ uc->usb_desc = &desc;
+ uc->cancel_packet = usb_msd_cancel_io;
+ uc->handle_attach = usb_desc_attach;
+ uc->handle_reset = usb_msd_handle_reset;
+ uc->handle_control = usb_msd_handle_control;
+ uc->handle_data = usb_msd_handle_data;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->fw_name = "storage";
+ dc->vmsd = &vmstate_usb_msd;
+}
+
+static void usb_msd_class_initfn_storage(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_msd_realize_storage;
+ dc->props = msd_properties;
+}
+
+static void usb_msd_get_bootindex(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ USBDevice *dev = USB_DEVICE(obj);
+ MSDState *s = USB_STORAGE_DEV(dev);
+
+ visit_type_int32(v, &s->conf.bootindex, name, errp);
+}
+
+static void usb_msd_set_bootindex(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ USBDevice *dev = USB_DEVICE(obj);
+ MSDState *s = USB_STORAGE_DEV(dev);
+ int32_t boot_index;
+ Error *local_err = NULL;
+
+ visit_type_int32(v, &boot_index, name, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ /* check whether bootindex is present in fw_boot_order list */
+ check_boot_index(boot_index, &local_err);
+ if (local_err) {
+ goto out;
+ }
+ /* change bootindex to a new one */
+ s->conf.bootindex = boot_index;
+
+ if (s->scsi_dev) {
+ object_property_set_int(OBJECT(s->scsi_dev), boot_index, "bootindex",
+ &error_abort);
+ }
+
+out:
+ if (local_err) {
+ error_propagate(errp, local_err);
+ }
+}
+
+static const TypeInfo usb_storage_dev_type_info = {
+ .name = TYPE_USB_STORAGE,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(MSDState),
+ .abstract = true,
+ .class_init = usb_msd_class_initfn_common,
+};
+
+static void usb_msd_instance_init(Object *obj)
+{
+ object_property_add(obj, "bootindex", "int32",
+ usb_msd_get_bootindex,
+ usb_msd_set_bootindex, NULL, NULL, NULL);
+ object_property_set_int(obj, -1, "bootindex", NULL);
+}
+
+static void usb_msd_class_initfn_bot(ObjectClass *klass, void *data)
+{
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ uc->realize = usb_msd_realize_bot;
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo msd_info = {
+ .name = "usb-storage",
+ .parent = TYPE_USB_STORAGE,
+ .class_init = usb_msd_class_initfn_storage,
+ .instance_init = usb_msd_instance_init,
+};
+
+static const TypeInfo bot_info = {
+ .name = "usb-bot",
+ .parent = TYPE_USB_STORAGE,
+ .class_init = usb_msd_class_initfn_bot,
+};
+
+static void usb_msd_register_types(void)
+{
+ type_register_static(&usb_storage_dev_type_info);
+ type_register_static(&msd_info);
+ type_register_static(&bot_info);
+ usb_legacy_register("usb-storage", "disk", usb_msd_init);
+}
+
+type_init(usb_msd_register_types)
diff --git a/hw/usb/dev-uas.c b/hw/usb/dev-uas.c
new file mode 100644
index 00000000..38b26c58
--- /dev/null
+++ b/hw/usb/dev-uas.c
@@ -0,0 +1,960 @@
+/*
+ * UAS (USB Attached SCSI) emulation
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Author: Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu-common.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "trace.h"
+#include "qemu/error-report.h"
+
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "hw/scsi/scsi.h"
+#include "block/scsi.h"
+
+/* --------------------------------------------------------------------- */
+
+#define UAS_UI_COMMAND 0x01
+#define UAS_UI_SENSE 0x03
+#define UAS_UI_RESPONSE 0x04
+#define UAS_UI_TASK_MGMT 0x05
+#define UAS_UI_READ_READY 0x06
+#define UAS_UI_WRITE_READY 0x07
+
+#define UAS_RC_TMF_COMPLETE 0x00
+#define UAS_RC_INVALID_INFO_UNIT 0x02
+#define UAS_RC_TMF_NOT_SUPPORTED 0x04
+#define UAS_RC_TMF_FAILED 0x05
+#define UAS_RC_TMF_SUCCEEDED 0x08
+#define UAS_RC_INCORRECT_LUN 0x09
+#define UAS_RC_OVERLAPPED_TAG 0x0a
+
+#define UAS_TMF_ABORT_TASK 0x01
+#define UAS_TMF_ABORT_TASK_SET 0x02
+#define UAS_TMF_CLEAR_TASK_SET 0x04
+#define UAS_TMF_LOGICAL_UNIT_RESET 0x08
+#define UAS_TMF_I_T_NEXUS_RESET 0x10
+#define UAS_TMF_CLEAR_ACA 0x40
+#define UAS_TMF_QUERY_TASK 0x80
+#define UAS_TMF_QUERY_TASK_SET 0x81
+#define UAS_TMF_QUERY_ASYNC_EVENT 0x82
+
+#define UAS_PIPE_ID_COMMAND 0x01
+#define UAS_PIPE_ID_STATUS 0x02
+#define UAS_PIPE_ID_DATA_IN 0x03
+#define UAS_PIPE_ID_DATA_OUT 0x04
+
+typedef struct {
+ uint8_t id;
+ uint8_t reserved;
+ uint16_t tag;
+} QEMU_PACKED uas_iu_header;
+
+typedef struct {
+ uint8_t prio_taskattr; /* 6:3 priority, 2:0 task attribute */
+ uint8_t reserved_1;
+ uint8_t add_cdb_length; /* 7:2 additional adb length (dwords) */
+ uint8_t reserved_2;
+ uint64_t lun;
+ uint8_t cdb[16];
+ uint8_t add_cdb[];
+} QEMU_PACKED uas_iu_command;
+
+typedef struct {
+ uint16_t status_qualifier;
+ uint8_t status;
+ uint8_t reserved[7];
+ uint16_t sense_length;
+ uint8_t sense_data[18];
+} QEMU_PACKED uas_iu_sense;
+
+typedef struct {
+ uint8_t add_response_info[3];
+ uint8_t response_code;
+} QEMU_PACKED uas_iu_response;
+
+typedef struct {
+ uint8_t function;
+ uint8_t reserved;
+ uint16_t task_tag;
+ uint64_t lun;
+} QEMU_PACKED uas_iu_task_mgmt;
+
+typedef struct {
+ uas_iu_header hdr;
+ union {
+ uas_iu_command command;
+ uas_iu_sense sense;
+ uas_iu_task_mgmt task;
+ uas_iu_response response;
+ };
+} QEMU_PACKED uas_iu;
+
+/* --------------------------------------------------------------------- */
+
+#define UAS_STREAM_BM_ATTR 4
+#define UAS_MAX_STREAMS (1 << UAS_STREAM_BM_ATTR)
+
+typedef struct UASDevice UASDevice;
+typedef struct UASRequest UASRequest;
+typedef struct UASStatus UASStatus;
+
+struct UASDevice {
+ USBDevice dev;
+ SCSIBus bus;
+ QEMUBH *status_bh;
+ QTAILQ_HEAD(, UASStatus) results;
+ QTAILQ_HEAD(, UASRequest) requests;
+
+ /* properties */
+ uint32_t requestlog;
+
+ /* usb 2.0 only */
+ USBPacket *status2;
+ UASRequest *datain2;
+ UASRequest *dataout2;
+
+ /* usb 3.0 only */
+ USBPacket *data3[UAS_MAX_STREAMS + 1];
+ USBPacket *status3[UAS_MAX_STREAMS + 1];
+};
+
+#define TYPE_USB_UAS "usb-uas"
+#define USB_UAS(obj) OBJECT_CHECK(UASDevice, (obj), TYPE_USB_UAS)
+
+struct UASRequest {
+ uint16_t tag;
+ uint64_t lun;
+ UASDevice *uas;
+ SCSIDevice *dev;
+ SCSIRequest *req;
+ USBPacket *data;
+ bool data_async;
+ bool active;
+ bool complete;
+ uint32_t buf_off;
+ uint32_t buf_size;
+ uint32_t data_off;
+ uint32_t data_size;
+ QTAILQ_ENTRY(UASRequest) next;
+};
+
+struct UASStatus {
+ uint32_t stream;
+ uas_iu status;
+ uint32_t length;
+ QTAILQ_ENTRY(UASStatus) next;
+};
+
+/* --------------------------------------------------------------------- */
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+ STR_CONFIG_HIGH,
+ STR_CONFIG_SUPER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT] = "USB Attached SCSI HBA",
+ [STR_SERIALNUMBER] = "27842",
+ [STR_CONFIG_HIGH] = "High speed config (usb 2.0)",
+ [STR_CONFIG_SUPER] = "Super speed config (usb 3.0)",
+};
+
+static const USBDescIface desc_iface_high = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 4,
+ .bInterfaceClass = USB_CLASS_MASS_STORAGE,
+ .bInterfaceSubClass = 0x06, /* SCSI */
+ .bInterfaceProtocol = 0x62, /* UAS */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_COMMAND,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ .extra = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ 0x24, /* u8 bDescriptorType */
+ UAS_PIPE_ID_COMMAND,
+ 0x00, /* u8 bReserved */
+ },
+ },{
+ .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_STATUS,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ .extra = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ 0x24, /* u8 bDescriptorType */
+ UAS_PIPE_ID_STATUS,
+ 0x00, /* u8 bReserved */
+ },
+ },{
+ .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_DATA_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ .extra = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ 0x24, /* u8 bDescriptorType */
+ UAS_PIPE_ID_DATA_IN,
+ 0x00, /* u8 bReserved */
+ },
+ },{
+ .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_DATA_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 512,
+ .extra = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ 0x24, /* u8 bDescriptorType */
+ UAS_PIPE_ID_DATA_OUT,
+ 0x00, /* u8 bReserved */
+ },
+ },
+ }
+};
+
+static const USBDescIface desc_iface_super = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 4,
+ .bInterfaceClass = USB_CLASS_MASS_STORAGE,
+ .bInterfaceSubClass = 0x06, /* SCSI */
+ .bInterfaceProtocol = 0x62, /* UAS */
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_COMMAND,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 1024,
+ .bMaxBurst = 15,
+ .extra = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ 0x24, /* u8 bDescriptorType */
+ UAS_PIPE_ID_COMMAND,
+ 0x00, /* u8 bReserved */
+ },
+ },{
+ .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_STATUS,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 1024,
+ .bMaxBurst = 15,
+ .bmAttributes_super = UAS_STREAM_BM_ATTR,
+ .extra = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ 0x24, /* u8 bDescriptorType */
+ UAS_PIPE_ID_STATUS,
+ 0x00, /* u8 bReserved */
+ },
+ },{
+ .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_DATA_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 1024,
+ .bMaxBurst = 15,
+ .bmAttributes_super = UAS_STREAM_BM_ATTR,
+ .extra = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ 0x24, /* u8 bDescriptorType */
+ UAS_PIPE_ID_DATA_IN,
+ 0x00, /* u8 bReserved */
+ },
+ },{
+ .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_DATA_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = 1024,
+ .bMaxBurst = 15,
+ .bmAttributes_super = UAS_STREAM_BM_ATTR,
+ .extra = (uint8_t[]) {
+ 0x04, /* u8 bLength */
+ 0x24, /* u8 bDescriptorType */
+ UAS_PIPE_ID_DATA_OUT,
+ 0x00, /* u8 bReserved */
+ },
+ },
+ }
+};
+
+static const USBDescDevice desc_device_high = {
+ .bcdUSB = 0x0200,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_HIGH,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .nif = 1,
+ .ifs = &desc_iface_high,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_super = {
+ .bcdUSB = 0x0300,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_SUPER,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .nif = 1,
+ .ifs = &desc_iface_super,
+ },
+ },
+};
+
+static const USBDesc desc = {
+ .id = {
+ .idVendor = 0x46f4, /* CRC16() of "QEMU" */
+ .idProduct = 0x0003,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .high = &desc_device_high,
+ .super = &desc_device_super,
+ .str = desc_strings,
+};
+
+/* --------------------------------------------------------------------- */
+
+static bool uas_using_streams(UASDevice *uas)
+{
+ return uas->dev.speed == USB_SPEED_SUPER;
+}
+
+/* --------------------------------------------------------------------- */
+
+static UASStatus *usb_uas_alloc_status(UASDevice *uas, uint8_t id, uint16_t tag)
+{
+ UASStatus *st = g_new0(UASStatus, 1);
+
+ st->status.hdr.id = id;
+ st->status.hdr.tag = cpu_to_be16(tag);
+ st->length = sizeof(uas_iu_header);
+ if (uas_using_streams(uas)) {
+ st->stream = tag;
+ }
+ return st;
+}
+
+static void usb_uas_send_status_bh(void *opaque)
+{
+ UASDevice *uas = opaque;
+ UASStatus *st;
+ USBPacket *p;
+
+ while ((st = QTAILQ_FIRST(&uas->results)) != NULL) {
+ if (uas_using_streams(uas)) {
+ p = uas->status3[st->stream];
+ uas->status3[st->stream] = NULL;
+ } else {
+ p = uas->status2;
+ uas->status2 = NULL;
+ }
+ if (p == NULL) {
+ break;
+ }
+
+ usb_packet_copy(p, &st->status, st->length);
+ QTAILQ_REMOVE(&uas->results, st, next);
+ g_free(st);
+
+ p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */
+ usb_packet_complete(&uas->dev, p);
+ }
+}
+
+static void usb_uas_queue_status(UASDevice *uas, UASStatus *st, int length)
+{
+ USBPacket *p = uas_using_streams(uas) ?
+ uas->status3[st->stream] : uas->status2;
+
+ st->length += length;
+ QTAILQ_INSERT_TAIL(&uas->results, st, next);
+ if (p) {
+ /*
+ * Just schedule bh make sure any in-flight data transaction
+ * is finished before completing (sending) the status packet.
+ */
+ qemu_bh_schedule(uas->status_bh);
+ } else {
+ USBEndpoint *ep = usb_ep_get(&uas->dev, USB_TOKEN_IN,
+ UAS_PIPE_ID_STATUS);
+ usb_wakeup(ep, st->stream);
+ }
+}
+
+static void usb_uas_queue_response(UASDevice *uas, uint16_t tag, uint8_t code)
+{
+ UASStatus *st = usb_uas_alloc_status(uas, UAS_UI_RESPONSE, tag);
+
+ trace_usb_uas_response(uas->dev.addr, tag, code);
+ st->status.response.response_code = code;
+ usb_uas_queue_status(uas, st, sizeof(uas_iu_response));
+}
+
+static void usb_uas_queue_sense(UASRequest *req, uint8_t status)
+{
+ UASStatus *st = usb_uas_alloc_status(req->uas, UAS_UI_SENSE, req->tag);
+ int len, slen = 0;
+
+ trace_usb_uas_sense(req->uas->dev.addr, req->tag, status);
+ st->status.sense.status = status;
+ st->status.sense.status_qualifier = cpu_to_be16(0);
+ if (status != GOOD) {
+ slen = scsi_req_get_sense(req->req, st->status.sense.sense_data,
+ sizeof(st->status.sense.sense_data));
+ st->status.sense.sense_length = cpu_to_be16(slen);
+ }
+ len = sizeof(uas_iu_sense) - sizeof(st->status.sense.sense_data) + slen;
+ usb_uas_queue_status(req->uas, st, len);
+}
+
+static void usb_uas_queue_fake_sense(UASDevice *uas, uint16_t tag,
+ struct SCSISense sense)
+{
+ UASStatus *st = usb_uas_alloc_status(uas, UAS_UI_SENSE, tag);
+ int len, slen = 0;
+
+ st->status.sense.status = CHECK_CONDITION;
+ st->status.sense.status_qualifier = cpu_to_be16(0);
+ st->status.sense.sense_data[0] = 0x70;
+ st->status.sense.sense_data[2] = sense.key;
+ st->status.sense.sense_data[7] = 10;
+ st->status.sense.sense_data[12] = sense.asc;
+ st->status.sense.sense_data[13] = sense.ascq;
+ slen = 18;
+ len = sizeof(uas_iu_sense) - sizeof(st->status.sense.sense_data) + slen;
+ usb_uas_queue_status(uas, st, len);
+}
+
+static void usb_uas_queue_read_ready(UASRequest *req)
+{
+ UASStatus *st = usb_uas_alloc_status(req->uas, UAS_UI_READ_READY,
+ req->tag);
+
+ trace_usb_uas_read_ready(req->uas->dev.addr, req->tag);
+ usb_uas_queue_status(req->uas, st, 0);
+}
+
+static void usb_uas_queue_write_ready(UASRequest *req)
+{
+ UASStatus *st = usb_uas_alloc_status(req->uas, UAS_UI_WRITE_READY,
+ req->tag);
+
+ trace_usb_uas_write_ready(req->uas->dev.addr, req->tag);
+ usb_uas_queue_status(req->uas, st, 0);
+}
+
+/* --------------------------------------------------------------------- */
+
+static int usb_uas_get_lun(uint64_t lun64)
+{
+ return (lun64 >> 48) & 0xff;
+}
+
+static SCSIDevice *usb_uas_get_dev(UASDevice *uas, uint64_t lun64)
+{
+ if ((lun64 >> 56) != 0x00) {
+ return NULL;
+ }
+ return scsi_device_find(&uas->bus, 0, 0, usb_uas_get_lun(lun64));
+}
+
+static void usb_uas_complete_data_packet(UASRequest *req)
+{
+ USBPacket *p;
+
+ if (!req->data_async) {
+ return;
+ }
+ p = req->data;
+ req->data = NULL;
+ req->data_async = false;
+ p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */
+ usb_packet_complete(&req->uas->dev, p);
+}
+
+static void usb_uas_copy_data(UASRequest *req)
+{
+ uint32_t length;
+
+ length = MIN(req->buf_size - req->buf_off,
+ req->data->iov.size - req->data->actual_length);
+ trace_usb_uas_xfer_data(req->uas->dev.addr, req->tag, length,
+ req->data->actual_length, req->data->iov.size,
+ req->buf_off, req->buf_size);
+ usb_packet_copy(req->data, scsi_req_get_buf(req->req) + req->buf_off,
+ length);
+ req->buf_off += length;
+ req->data_off += length;
+
+ if (req->data->actual_length == req->data->iov.size) {
+ usb_uas_complete_data_packet(req);
+ }
+ if (req->buf_size && req->buf_off == req->buf_size) {
+ req->buf_off = 0;
+ req->buf_size = 0;
+ scsi_req_continue(req->req);
+ }
+}
+
+static void usb_uas_start_next_transfer(UASDevice *uas)
+{
+ UASRequest *req;
+
+ if (uas_using_streams(uas)) {
+ return;
+ }
+
+ QTAILQ_FOREACH(req, &uas->requests, next) {
+ if (req->active || req->complete) {
+ continue;
+ }
+ if (req->req->cmd.mode == SCSI_XFER_FROM_DEV && uas->datain2 == NULL) {
+ uas->datain2 = req;
+ usb_uas_queue_read_ready(req);
+ req->active = true;
+ return;
+ }
+ if (req->req->cmd.mode == SCSI_XFER_TO_DEV && uas->dataout2 == NULL) {
+ uas->dataout2 = req;
+ usb_uas_queue_write_ready(req);
+ req->active = true;
+ return;
+ }
+ }
+}
+
+static UASRequest *usb_uas_alloc_request(UASDevice *uas, uas_iu *iu)
+{
+ UASRequest *req;
+
+ req = g_new0(UASRequest, 1);
+ req->uas = uas;
+ req->tag = be16_to_cpu(iu->hdr.tag);
+ req->lun = be64_to_cpu(iu->command.lun);
+ req->dev = usb_uas_get_dev(req->uas, req->lun);
+ return req;
+}
+
+static void usb_uas_scsi_free_request(SCSIBus *bus, void *priv)
+{
+ UASRequest *req = priv;
+ UASDevice *uas = req->uas;
+
+ if (req == uas->datain2) {
+ uas->datain2 = NULL;
+ }
+ if (req == uas->dataout2) {
+ uas->dataout2 = NULL;
+ }
+ QTAILQ_REMOVE(&uas->requests, req, next);
+ g_free(req);
+ usb_uas_start_next_transfer(uas);
+}
+
+static UASRequest *usb_uas_find_request(UASDevice *uas, uint16_t tag)
+{
+ UASRequest *req;
+
+ QTAILQ_FOREACH(req, &uas->requests, next) {
+ if (req->tag == tag) {
+ return req;
+ }
+ }
+ return NULL;
+}
+
+static void usb_uas_scsi_transfer_data(SCSIRequest *r, uint32_t len)
+{
+ UASRequest *req = r->hba_private;
+
+ trace_usb_uas_scsi_data(req->uas->dev.addr, req->tag, len);
+ req->buf_off = 0;
+ req->buf_size = len;
+ if (req->data) {
+ usb_uas_copy_data(req);
+ } else {
+ usb_uas_start_next_transfer(req->uas);
+ }
+}
+
+static void usb_uas_scsi_command_complete(SCSIRequest *r,
+ uint32_t status, size_t resid)
+{
+ UASRequest *req = r->hba_private;
+
+ trace_usb_uas_scsi_complete(req->uas->dev.addr, req->tag, status, resid);
+ req->complete = true;
+ if (req->data) {
+ usb_uas_complete_data_packet(req);
+ }
+ usb_uas_queue_sense(req, status);
+ scsi_req_unref(req->req);
+}
+
+static void usb_uas_scsi_request_cancelled(SCSIRequest *r)
+{
+ UASRequest *req = r->hba_private;
+
+ /* FIXME: queue notification to status pipe? */
+ scsi_req_unref(req->req);
+}
+
+static const struct SCSIBusInfo usb_uas_scsi_info = {
+ .tcq = true,
+ .max_target = 0,
+ .max_lun = 255,
+
+ .transfer_data = usb_uas_scsi_transfer_data,
+ .complete = usb_uas_scsi_command_complete,
+ .cancel = usb_uas_scsi_request_cancelled,
+ .free_request = usb_uas_scsi_free_request,
+};
+
+/* --------------------------------------------------------------------- */
+
+static void usb_uas_handle_reset(USBDevice *dev)
+{
+ UASDevice *uas = USB_UAS(dev);
+ UASRequest *req, *nreq;
+ UASStatus *st, *nst;
+
+ trace_usb_uas_reset(dev->addr);
+ QTAILQ_FOREACH_SAFE(req, &uas->requests, next, nreq) {
+ scsi_req_cancel(req->req);
+ }
+ QTAILQ_FOREACH_SAFE(st, &uas->results, next, nst) {
+ QTAILQ_REMOVE(&uas->results, st, next);
+ g_free(st);
+ }
+}
+
+static void usb_uas_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ int ret;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+ error_report("%s: unhandled control request", __func__);
+ p->status = USB_RET_STALL;
+}
+
+static void usb_uas_cancel_io(USBDevice *dev, USBPacket *p)
+{
+ UASDevice *uas = USB_UAS(dev);
+ UASRequest *req, *nreq;
+ int i;
+
+ if (uas->status2 == p) {
+ uas->status2 = NULL;
+ qemu_bh_cancel(uas->status_bh);
+ return;
+ }
+ if (uas_using_streams(uas)) {
+ for (i = 0; i <= UAS_MAX_STREAMS; i++) {
+ if (uas->status3[i] == p) {
+ uas->status3[i] = NULL;
+ return;
+ }
+ if (uas->data3[i] == p) {
+ uas->data3[i] = NULL;
+ return;
+ }
+ }
+ }
+ QTAILQ_FOREACH_SAFE(req, &uas->requests, next, nreq) {
+ if (req->data == p) {
+ req->data = NULL;
+ return;
+ }
+ }
+ assert(!"canceled usb packet not found");
+}
+
+static void usb_uas_command(UASDevice *uas, uas_iu *iu)
+{
+ UASRequest *req;
+ uint32_t len;
+ uint16_t tag = be16_to_cpu(iu->hdr.tag);
+
+ if (uas_using_streams(uas) && tag > UAS_MAX_STREAMS) {
+ goto invalid_tag;
+ }
+ req = usb_uas_find_request(uas, tag);
+ if (req) {
+ goto overlapped_tag;
+ }
+ req = usb_uas_alloc_request(uas, iu);
+ if (req->dev == NULL) {
+ goto bad_target;
+ }
+
+ trace_usb_uas_command(uas->dev.addr, req->tag,
+ usb_uas_get_lun(req->lun),
+ req->lun >> 32, req->lun & 0xffffffff);
+ QTAILQ_INSERT_TAIL(&uas->requests, req, next);
+ if (uas_using_streams(uas) && uas->data3[req->tag] != NULL) {
+ req->data = uas->data3[req->tag];
+ req->data_async = true;
+ uas->data3[req->tag] = NULL;
+ }
+
+ req->req = scsi_req_new(req->dev, req->tag,
+ usb_uas_get_lun(req->lun),
+ iu->command.cdb, req);
+ if (uas->requestlog) {
+ scsi_req_print(req->req);
+ }
+ len = scsi_req_enqueue(req->req);
+ if (len) {
+ req->data_size = len;
+ scsi_req_continue(req->req);
+ }
+ return;
+
+invalid_tag:
+ usb_uas_queue_fake_sense(uas, tag, sense_code_INVALID_TAG);
+ return;
+
+overlapped_tag:
+ usb_uas_queue_fake_sense(uas, tag, sense_code_OVERLAPPED_COMMANDS);
+ return;
+
+bad_target:
+ usb_uas_queue_fake_sense(uas, tag, sense_code_LUN_NOT_SUPPORTED);
+ g_free(req);
+}
+
+static void usb_uas_task(UASDevice *uas, uas_iu *iu)
+{
+ uint16_t tag = be16_to_cpu(iu->hdr.tag);
+ uint64_t lun64 = be64_to_cpu(iu->task.lun);
+ SCSIDevice *dev = usb_uas_get_dev(uas, lun64);
+ int lun = usb_uas_get_lun(lun64);
+ UASRequest *req;
+ uint16_t task_tag;
+
+ if (uas_using_streams(uas) && tag > UAS_MAX_STREAMS) {
+ goto invalid_tag;
+ }
+ req = usb_uas_find_request(uas, be16_to_cpu(iu->hdr.tag));
+ if (req) {
+ goto overlapped_tag;
+ }
+ if (dev == NULL) {
+ goto incorrect_lun;
+ }
+
+ switch (iu->task.function) {
+ case UAS_TMF_ABORT_TASK:
+ task_tag = be16_to_cpu(iu->task.task_tag);
+ trace_usb_uas_tmf_abort_task(uas->dev.addr, tag, task_tag);
+ req = usb_uas_find_request(uas, task_tag);
+ if (req && req->dev == dev) {
+ scsi_req_cancel(req->req);
+ }
+ usb_uas_queue_response(uas, tag, UAS_RC_TMF_COMPLETE);
+ break;
+
+ case UAS_TMF_LOGICAL_UNIT_RESET:
+ trace_usb_uas_tmf_logical_unit_reset(uas->dev.addr, tag, lun);
+ qdev_reset_all(&dev->qdev);
+ usb_uas_queue_response(uas, tag, UAS_RC_TMF_COMPLETE);
+ break;
+
+ default:
+ trace_usb_uas_tmf_unsupported(uas->dev.addr, tag, iu->task.function);
+ usb_uas_queue_response(uas, tag, UAS_RC_TMF_NOT_SUPPORTED);
+ break;
+ }
+ return;
+
+invalid_tag:
+ usb_uas_queue_response(uas, tag, UAS_RC_INVALID_INFO_UNIT);
+ return;
+
+overlapped_tag:
+ usb_uas_queue_response(uas, req->tag, UAS_RC_OVERLAPPED_TAG);
+ return;
+
+incorrect_lun:
+ usb_uas_queue_response(uas, tag, UAS_RC_INCORRECT_LUN);
+}
+
+static void usb_uas_handle_data(USBDevice *dev, USBPacket *p)
+{
+ UASDevice *uas = USB_UAS(dev);
+ uas_iu iu;
+ UASStatus *st;
+ UASRequest *req;
+ int length;
+
+ switch (p->ep->nr) {
+ case UAS_PIPE_ID_COMMAND:
+ length = MIN(sizeof(iu), p->iov.size);
+ usb_packet_copy(p, &iu, length);
+ switch (iu.hdr.id) {
+ case UAS_UI_COMMAND:
+ usb_uas_command(uas, &iu);
+ break;
+ case UAS_UI_TASK_MGMT:
+ usb_uas_task(uas, &iu);
+ break;
+ default:
+ error_report("%s: unknown command iu: id 0x%x",
+ __func__, iu.hdr.id);
+ p->status = USB_RET_STALL;
+ break;
+ }
+ break;
+ case UAS_PIPE_ID_STATUS:
+ if (p->stream) {
+ QTAILQ_FOREACH(st, &uas->results, next) {
+ if (st->stream == p->stream) {
+ break;
+ }
+ }
+ if (st == NULL) {
+ assert(uas->status3[p->stream] == NULL);
+ uas->status3[p->stream] = p;
+ p->status = USB_RET_ASYNC;
+ break;
+ }
+ } else {
+ st = QTAILQ_FIRST(&uas->results);
+ if (st == NULL) {
+ assert(uas->status2 == NULL);
+ uas->status2 = p;
+ p->status = USB_RET_ASYNC;
+ break;
+ }
+ }
+ usb_packet_copy(p, &st->status, st->length);
+ QTAILQ_REMOVE(&uas->results, st, next);
+ g_free(st);
+ break;
+ case UAS_PIPE_ID_DATA_IN:
+ case UAS_PIPE_ID_DATA_OUT:
+ if (p->stream) {
+ req = usb_uas_find_request(uas, p->stream);
+ } else {
+ req = (p->ep->nr == UAS_PIPE_ID_DATA_IN)
+ ? uas->datain2 : uas->dataout2;
+ }
+ if (req == NULL) {
+ if (p->stream) {
+ assert(uas->data3[p->stream] == NULL);
+ uas->data3[p->stream] = p;
+ p->status = USB_RET_ASYNC;
+ break;
+ } else {
+ error_report("%s: no inflight request", __func__);
+ p->status = USB_RET_STALL;
+ break;
+ }
+ }
+ scsi_req_ref(req->req);
+ req->data = p;
+ usb_uas_copy_data(req);
+ if (p->actual_length == p->iov.size || req->complete) {
+ req->data = NULL;
+ } else {
+ req->data_async = true;
+ p->status = USB_RET_ASYNC;
+ }
+ scsi_req_unref(req->req);
+ usb_uas_start_next_transfer(uas);
+ break;
+ default:
+ error_report("%s: invalid endpoint %d", __func__, p->ep->nr);
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_uas_handle_destroy(USBDevice *dev)
+{
+ UASDevice *uas = USB_UAS(dev);
+
+ qemu_bh_delete(uas->status_bh);
+}
+
+static void usb_uas_realize(USBDevice *dev, Error **errp)
+{
+ UASDevice *uas = USB_UAS(dev);
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+
+ QTAILQ_INIT(&uas->results);
+ QTAILQ_INIT(&uas->requests);
+ uas->status_bh = qemu_bh_new(usb_uas_send_status_bh, uas);
+
+ scsi_bus_new(&uas->bus, sizeof(uas->bus), DEVICE(dev),
+ &usb_uas_scsi_info, NULL);
+}
+
+static const VMStateDescription vmstate_usb_uas = {
+ .name = "usb-uas",
+ .unmigratable = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, UASDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property uas_properties[] = {
+ DEFINE_PROP_UINT32("log-scsi-req", UASDevice, requestlog, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_uas_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_uas_realize;
+ uc->product_desc = desc_strings[STR_PRODUCT];
+ uc->usb_desc = &desc;
+ uc->cancel_packet = usb_uas_cancel_io;
+ uc->handle_attach = usb_desc_attach;
+ uc->handle_reset = usb_uas_handle_reset;
+ uc->handle_control = usb_uas_handle_control;
+ uc->handle_data = usb_uas_handle_data;
+ uc->handle_destroy = usb_uas_handle_destroy;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->fw_name = "storage";
+ dc->vmsd = &vmstate_usb_uas;
+ dc->props = uas_properties;
+}
+
+static const TypeInfo uas_info = {
+ .name = TYPE_USB_UAS,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(UASDevice),
+ .class_init = usb_uas_class_initfn,
+};
+
+static void usb_uas_register_types(void)
+{
+ type_register_static(&uas_info);
+}
+
+type_init(usb_uas_register_types)
diff --git a/hw/usb/dev-wacom.c b/hw/usb/dev-wacom.c
new file mode 100644
index 00000000..c2450e72
--- /dev/null
+++ b/hw/usb/dev-wacom.c
@@ -0,0 +1,385 @@
+/*
+ * Wacom PenPartner USB tablet emulation.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Author: Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * Based on hw/usb-hid.c:
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+
+/* Interface requests */
+#define WACOM_GET_REPORT 0x2101
+#define WACOM_SET_REPORT 0x2109
+
+/* HID interface requests */
+#define HID_GET_REPORT 0xa101
+#define HID_GET_IDLE 0xa102
+#define HID_GET_PROTOCOL 0xa103
+#define HID_SET_IDLE 0x210a
+#define HID_SET_PROTOCOL 0x210b
+
+typedef struct USBWacomState {
+ USBDevice dev;
+ USBEndpoint *intr;
+ QEMUPutMouseEntry *eh_entry;
+ int dx, dy, dz, buttons_state;
+ int x, y;
+ int mouse_grabbed;
+ enum {
+ WACOM_MODE_HID = 1,
+ WACOM_MODE_WACOM = 2,
+ } mode;
+ uint8_t idle;
+ int changed;
+} USBWacomState;
+
+#define TYPE_USB_WACOM "usb-wacom-tablet"
+#define USB_WACOM(obj) OBJECT_CHECK(USBWacomState, (obj), TYPE_USB_WACOM)
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT] = "Wacom PenPartner",
+ [STR_SERIALNUMBER] = "1",
+};
+
+static const USBDescIface desc_iface_wacom = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0x01, /* boot */
+ .bInterfaceProtocol = 0x02,
+ .ndesc = 1,
+ .descs = (USBDescOther[]) {
+ {
+ /* HID descriptor */
+ .data = (uint8_t[]) {
+ 0x09, /* u8 bLength */
+ 0x21, /* u8 bDescriptorType */
+ 0x01, 0x10, /* u16 HID_class */
+ 0x00, /* u8 country_code */
+ 0x01, /* u8 num_descriptors */
+ 0x22, /* u8 type: Report */
+ 0x6e, 0, /* u16 len */
+ },
+ },
+ },
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 8,
+ .bInterval = 0x0a,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_wacom = {
+ .bcdUSB = 0x0110,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CFG_ATT_ONE,
+ .bMaxPower = 40,
+ .nif = 1,
+ .ifs = &desc_iface_wacom,
+ },
+ },
+};
+
+static const USBDesc desc_wacom = {
+ .id = {
+ .idVendor = 0x056a,
+ .idProduct = 0x0000,
+ .bcdDevice = 0x4210,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_wacom,
+ .str = desc_strings,
+};
+
+static void usb_mouse_event(void *opaque,
+ int dx1, int dy1, int dz1, int buttons_state)
+{
+ USBWacomState *s = opaque;
+
+ s->dx += dx1;
+ s->dy += dy1;
+ s->dz += dz1;
+ s->buttons_state = buttons_state;
+ s->changed = 1;
+ usb_wakeup(s->intr, 0);
+}
+
+static void usb_wacom_event(void *opaque,
+ int x, int y, int dz, int buttons_state)
+{
+ USBWacomState *s = opaque;
+
+ /* scale to Penpartner resolution */
+ s->x = (x * 5040 / 0x7FFF);
+ s->y = (y * 3780 / 0x7FFF);
+ s->dz += dz;
+ s->buttons_state = buttons_state;
+ s->changed = 1;
+ usb_wakeup(s->intr, 0);
+}
+
+static inline int int_clamp(int val, int vmin, int vmax)
+{
+ if (val < vmin)
+ return vmin;
+ else if (val > vmax)
+ return vmax;
+ else
+ return val;
+}
+
+static int usb_mouse_poll(USBWacomState *s, uint8_t *buf, int len)
+{
+ int dx, dy, dz, b, l;
+
+ if (!s->mouse_grabbed) {
+ s->eh_entry = qemu_add_mouse_event_handler(usb_mouse_event, s, 0,
+ "QEMU PenPartner tablet");
+ qemu_activate_mouse_event_handler(s->eh_entry);
+ s->mouse_grabbed = 1;
+ }
+
+ dx = int_clamp(s->dx, -128, 127);
+ dy = int_clamp(s->dy, -128, 127);
+ dz = int_clamp(s->dz, -128, 127);
+
+ s->dx -= dx;
+ s->dy -= dy;
+ s->dz -= dz;
+
+ b = 0;
+ if (s->buttons_state & MOUSE_EVENT_LBUTTON)
+ b |= 0x01;
+ if (s->buttons_state & MOUSE_EVENT_RBUTTON)
+ b |= 0x02;
+ if (s->buttons_state & MOUSE_EVENT_MBUTTON)
+ b |= 0x04;
+
+ buf[0] = b;
+ buf[1] = dx;
+ buf[2] = dy;
+ l = 3;
+ if (len >= 4) {
+ buf[3] = dz;
+ l = 4;
+ }
+ return l;
+}
+
+static int usb_wacom_poll(USBWacomState *s, uint8_t *buf, int len)
+{
+ int b;
+
+ if (!s->mouse_grabbed) {
+ s->eh_entry = qemu_add_mouse_event_handler(usb_wacom_event, s, 1,
+ "QEMU PenPartner tablet");
+ qemu_activate_mouse_event_handler(s->eh_entry);
+ s->mouse_grabbed = 1;
+ }
+
+ b = 0;
+ if (s->buttons_state & MOUSE_EVENT_LBUTTON)
+ b |= 0x01;
+ if (s->buttons_state & MOUSE_EVENT_RBUTTON)
+ b |= 0x40;
+ if (s->buttons_state & MOUSE_EVENT_MBUTTON)
+ b |= 0x20; /* eraser */
+
+ if (len < 7)
+ return 0;
+
+ buf[0] = s->mode;
+ buf[5] = 0x00 | (b & 0xf0);
+ buf[1] = s->x & 0xff;
+ buf[2] = s->x >> 8;
+ buf[3] = s->y & 0xff;
+ buf[4] = s->y >> 8;
+ if (b & 0x3f) {
+ buf[6] = 0;
+ } else {
+ buf[6] = (unsigned char) -127;
+ }
+
+ return 7;
+}
+
+static void usb_wacom_handle_reset(USBDevice *dev)
+{
+ USBWacomState *s = (USBWacomState *) dev;
+
+ s->dx = 0;
+ s->dy = 0;
+ s->dz = 0;
+ s->x = 0;
+ s->y = 0;
+ s->buttons_state = 0;
+ s->mode = WACOM_MODE_HID;
+}
+
+static void usb_wacom_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ USBWacomState *s = (USBWacomState *) dev;
+ int ret;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch (request) {
+ case WACOM_SET_REPORT:
+ if (s->mouse_grabbed) {
+ qemu_remove_mouse_event_handler(s->eh_entry);
+ s->mouse_grabbed = 0;
+ }
+ s->mode = data[0];
+ break;
+ case WACOM_GET_REPORT:
+ data[0] = 0;
+ data[1] = s->mode;
+ p->actual_length = 2;
+ break;
+ /* USB HID requests */
+ case HID_GET_REPORT:
+ if (s->mode == WACOM_MODE_HID)
+ p->actual_length = usb_mouse_poll(s, data, length);
+ else if (s->mode == WACOM_MODE_WACOM)
+ p->actual_length = usb_wacom_poll(s, data, length);
+ break;
+ case HID_GET_IDLE:
+ data[0] = s->idle;
+ p->actual_length = 1;
+ break;
+ case HID_SET_IDLE:
+ s->idle = (uint8_t) (value >> 8);
+ break;
+ default:
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void usb_wacom_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBWacomState *s = (USBWacomState *) dev;
+ uint8_t buf[p->iov.size];
+ int len = 0;
+
+ switch (p->pid) {
+ case USB_TOKEN_IN:
+ if (p->ep->nr == 1) {
+ if (!(s->changed || s->idle)) {
+ p->status = USB_RET_NAK;
+ return;
+ }
+ s->changed = 0;
+ if (s->mode == WACOM_MODE_HID)
+ len = usb_mouse_poll(s, buf, p->iov.size);
+ else if (s->mode == WACOM_MODE_WACOM)
+ len = usb_wacom_poll(s, buf, p->iov.size);
+ usb_packet_copy(p, buf, len);
+ break;
+ }
+ /* Fall through. */
+ case USB_TOKEN_OUT:
+ default:
+ p->status = USB_RET_STALL;
+ }
+}
+
+static void usb_wacom_handle_destroy(USBDevice *dev)
+{
+ USBWacomState *s = (USBWacomState *) dev;
+
+ if (s->mouse_grabbed) {
+ qemu_remove_mouse_event_handler(s->eh_entry);
+ s->mouse_grabbed = 0;
+ }
+}
+
+static void usb_wacom_realize(USBDevice *dev, Error **errp)
+{
+ USBWacomState *s = USB_WACOM(dev);
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
+ s->changed = 1;
+}
+
+static const VMStateDescription vmstate_usb_wacom = {
+ .name = "usb-wacom",
+ .unmigratable = 1,
+};
+
+static void usb_wacom_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->product_desc = "QEMU PenPartner Tablet";
+ uc->usb_desc = &desc_wacom;
+ uc->realize = usb_wacom_realize;
+ uc->handle_reset = usb_wacom_handle_reset;
+ uc->handle_control = usb_wacom_handle_control;
+ uc->handle_data = usb_wacom_handle_data;
+ uc->handle_destroy = usb_wacom_handle_destroy;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "QEMU PenPartner Tablet";
+ dc->vmsd = &vmstate_usb_wacom;
+}
+
+static const TypeInfo wacom_info = {
+ .name = TYPE_USB_WACOM,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBWacomState),
+ .class_init = usb_wacom_class_init,
+};
+
+static void usb_wacom_register_types(void)
+{
+ type_register_static(&wacom_info);
+ usb_legacy_register(TYPE_USB_WACOM, "wacom-tablet", NULL);
+}
+
+type_init(usb_wacom_register_types)
diff --git a/hw/usb/hcd-ehci-pci.c b/hw/usb/hcd-ehci-pci.c
new file mode 100644
index 00000000..7afa5f9d
--- /dev/null
+++ b/hw/usb/hcd-ehci-pci.c
@@ -0,0 +1,273 @@
+/*
+ * QEMU USB EHCI Emulation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or(at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/usb/hcd-ehci.h"
+#include "qemu/range.h"
+
+typedef struct EHCIPCIInfo {
+ const char *name;
+ uint16_t vendor_id;
+ uint16_t device_id;
+ uint8_t revision;
+ bool companion;
+} EHCIPCIInfo;
+
+static void usb_ehci_pci_realize(PCIDevice *dev, Error **errp)
+{
+ EHCIPCIState *i = PCI_EHCI(dev);
+ EHCIState *s = &i->ehci;
+ uint8_t *pci_conf = dev->config;
+
+ pci_set_byte(&pci_conf[PCI_CLASS_PROG], 0x20);
+
+ /* capabilities pointer */
+ pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x00);
+ /* pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x50); */
+
+ pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); /* interrupt pin D */
+ pci_set_byte(&pci_conf[PCI_MIN_GNT], 0);
+ pci_set_byte(&pci_conf[PCI_MAX_LAT], 0);
+
+ /* pci_conf[0x50] = 0x01; *//* power management caps */
+
+ pci_set_byte(&pci_conf[USB_SBRN], USB_RELEASE_2); /* release # (2.1.4) */
+ pci_set_byte(&pci_conf[0x61], 0x20); /* frame length adjustment (2.1.5) */
+ pci_set_word(&pci_conf[0x62], 0x00); /* port wake up capability (2.1.6) */
+
+ pci_conf[0x64] = 0x00;
+ pci_conf[0x65] = 0x00;
+ pci_conf[0x66] = 0x00;
+ pci_conf[0x67] = 0x00;
+ pci_conf[0x68] = 0x01;
+ pci_conf[0x69] = 0x00;
+ pci_conf[0x6a] = 0x00;
+ pci_conf[0x6b] = 0x00; /* USBLEGSUP */
+ pci_conf[0x6c] = 0x00;
+ pci_conf[0x6d] = 0x00;
+ pci_conf[0x6e] = 0x00;
+ pci_conf[0x6f] = 0xc0; /* USBLEFCTLSTS */
+
+ s->irq = pci_allocate_irq(dev);
+ s->as = pci_get_address_space(dev);
+
+ usb_ehci_realize(s, DEVICE(dev), NULL);
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem);
+}
+
+static void usb_ehci_pci_init(Object *obj)
+{
+ DeviceClass *dc = OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE);
+ EHCIPCIState *i = PCI_EHCI(obj);
+ EHCIState *s = &i->ehci;
+
+ s->caps[0x09] = 0x68; /* EECP */
+
+ s->capsbase = 0x00;
+ s->opregbase = 0x20;
+ s->portscbase = 0x44;
+ s->portnr = NB_PORTS;
+
+ if (!dc->hotpluggable) {
+ s->companion_enable = true;
+ }
+
+ usb_ehci_init(s, DEVICE(obj));
+}
+
+static void usb_ehci_pci_exit(PCIDevice *dev)
+{
+ EHCIPCIState *i = PCI_EHCI(dev);
+ EHCIState *s = &i->ehci;
+
+ usb_ehci_unrealize(s, DEVICE(dev), NULL);
+
+ if (s->irq) {
+ g_free(s->irq);
+ s->irq = NULL;
+ }
+}
+
+static void usb_ehci_pci_reset(DeviceState *dev)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(dev);
+ EHCIPCIState *i = PCI_EHCI(pci_dev);
+ EHCIState *s = &i->ehci;
+
+ ehci_reset(s);
+}
+
+static void usb_ehci_pci_write_config(PCIDevice *dev, uint32_t addr,
+ uint32_t val, int l)
+{
+ EHCIPCIState *i = PCI_EHCI(dev);
+ bool busmaster;
+
+ pci_default_write_config(dev, addr, val, l);
+
+ if (!range_covers_byte(addr, l, PCI_COMMAND)) {
+ return;
+ }
+ busmaster = pci_get_word(dev->config + PCI_COMMAND) & PCI_COMMAND_MASTER;
+ i->ehci.as = busmaster ? pci_get_address_space(dev) : &address_space_memory;
+}
+
+static Property ehci_pci_properties[] = {
+ DEFINE_PROP_UINT32("maxframes", EHCIPCIState, ehci.maxframes, 128),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_ehci_pci = {
+ .name = "ehci",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(pcidev, EHCIPCIState),
+ VMSTATE_STRUCT(ehci, EHCIPCIState, 2, vmstate_ehci, EHCIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ehci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = usb_ehci_pci_realize;
+ k->exit = usb_ehci_pci_exit;
+ k->class_id = PCI_CLASS_SERIAL_USB;
+ k->config_write = usb_ehci_pci_write_config;
+ dc->vmsd = &vmstate_ehci_pci;
+ dc->props = ehci_pci_properties;
+ dc->reset = usb_ehci_pci_reset;
+}
+
+static const TypeInfo ehci_pci_type_info = {
+ .name = TYPE_PCI_EHCI,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(EHCIPCIState),
+ .instance_init = usb_ehci_pci_init,
+ .abstract = true,
+ .class_init = ehci_class_init,
+};
+
+static void ehci_data_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ EHCIPCIInfo *i = data;
+
+ k->vendor_id = i->vendor_id;
+ k->device_id = i->device_id;
+ k->revision = i->revision;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+ if (i->companion) {
+ dc->hotpluggable = false;
+ }
+}
+
+static struct EHCIPCIInfo ehci_pci_info[] = {
+ {
+ .name = "usb-ehci",
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801D, /* ich4 */
+ .revision = 0x10,
+ },{
+ .name = "ich9-usb-ehci1", /* 00:1d.7 */
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI1,
+ .revision = 0x03,
+ .companion = true,
+ },{
+ .name = "ich9-usb-ehci2", /* 00:1a.7 */
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI2,
+ .revision = 0x03,
+ .companion = true,
+ }
+};
+
+static void ehci_pci_register_types(void)
+{
+ TypeInfo ehci_type_info = {
+ .parent = TYPE_PCI_EHCI,
+ .class_init = ehci_data_class_init,
+ };
+ int i;
+
+ type_register_static(&ehci_pci_type_info);
+
+ for (i = 0; i < ARRAY_SIZE(ehci_pci_info); i++) {
+ ehci_type_info.name = ehci_pci_info[i].name;
+ ehci_type_info.class_data = ehci_pci_info + i;
+ type_register(&ehci_type_info);
+ }
+}
+
+type_init(ehci_pci_register_types)
+
+struct ehci_companions {
+ const char *name;
+ int func;
+ int port;
+};
+
+static const struct ehci_companions ich9_1d[] = {
+ { .name = "ich9-usb-uhci1", .func = 0, .port = 0 },
+ { .name = "ich9-usb-uhci2", .func = 1, .port = 2 },
+ { .name = "ich9-usb-uhci3", .func = 2, .port = 4 },
+};
+
+static const struct ehci_companions ich9_1a[] = {
+ { .name = "ich9-usb-uhci4", .func = 0, .port = 0 },
+ { .name = "ich9-usb-uhci5", .func = 1, .port = 2 },
+ { .name = "ich9-usb-uhci6", .func = 2, .port = 4 },
+};
+
+int ehci_create_ich9_with_companions(PCIBus *bus, int slot)
+{
+ const struct ehci_companions *comp;
+ PCIDevice *ehci, *uhci;
+ BusState *usbbus;
+ const char *name;
+ int i;
+
+ switch (slot) {
+ case 0x1d:
+ name = "ich9-usb-ehci1";
+ comp = ich9_1d;
+ break;
+ case 0x1a:
+ name = "ich9-usb-ehci2";
+ comp = ich9_1a;
+ break;
+ default:
+ return -1;
+ }
+
+ ehci = pci_create_multifunction(bus, PCI_DEVFN(slot, 7), true, name);
+ qdev_init_nofail(&ehci->qdev);
+ usbbus = QLIST_FIRST(&ehci->qdev.child_bus);
+
+ for (i = 0; i < 3; i++) {
+ uhci = pci_create_multifunction(bus, PCI_DEVFN(slot, comp[i].func),
+ true, comp[i].name);
+ qdev_prop_set_string(&uhci->qdev, "masterbus", usbbus->name);
+ qdev_prop_set_uint32(&uhci->qdev, "firstport", comp[i].port);
+ qdev_init_nofail(&uhci->qdev);
+ }
+ return 0;
+}
diff --git a/hw/usb/hcd-ehci-sysbus.c b/hw/usb/hcd-ehci-sysbus.c
new file mode 100644
index 00000000..cd1cc142
--- /dev/null
+++ b/hw/usb/hcd-ehci-sysbus.c
@@ -0,0 +1,229 @@
+/*
+ * QEMU USB EHCI Emulation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or(at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/usb/hcd-ehci.h"
+
+static const VMStateDescription vmstate_ehci_sysbus = {
+ .name = "ehci-sysbus",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(ehci, EHCISysBusState, 2, vmstate_ehci, EHCIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property ehci_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("maxframes", EHCISysBusState, ehci.maxframes, 128),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_ehci_sysbus_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(dev);
+ EHCISysBusState *i = SYS_BUS_EHCI(dev);
+ EHCIState *s = &i->ehci;
+
+ usb_ehci_realize(s, dev, errp);
+ sysbus_init_irq(d, &s->irq);
+}
+
+static void usb_ehci_sysbus_reset(DeviceState *dev)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(dev);
+ EHCISysBusState *i = SYS_BUS_EHCI(d);
+ EHCIState *s = &i->ehci;
+
+ ehci_reset(s);
+}
+
+static void ehci_sysbus_init(Object *obj)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(obj);
+ EHCISysBusState *i = SYS_BUS_EHCI(obj);
+ SysBusEHCIClass *sec = SYS_BUS_EHCI_GET_CLASS(obj);
+ EHCIState *s = &i->ehci;
+
+ s->capsbase = sec->capsbase;
+ s->opregbase = sec->opregbase;
+ s->portscbase = sec->portscbase;
+ s->portnr = sec->portnr;
+ s->as = &address_space_memory;
+
+ usb_ehci_init(s, DEVICE(obj));
+ sysbus_init_mmio(d, &s->mem);
+}
+
+static void ehci_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(klass);
+
+ sec->portscbase = 0x44;
+ sec->portnr = NB_PORTS;
+
+ dc->realize = usb_ehci_sysbus_realize;
+ dc->vmsd = &vmstate_ehci_sysbus;
+ dc->props = ehci_sysbus_properties;
+ dc->reset = usb_ehci_sysbus_reset;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+}
+
+static const TypeInfo ehci_type_info = {
+ .name = TYPE_SYS_BUS_EHCI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(EHCISysBusState),
+ .instance_init = ehci_sysbus_init,
+ .abstract = true,
+ .class_init = ehci_sysbus_class_init,
+ .class_size = sizeof(SysBusEHCIClass),
+};
+
+static void ehci_xlnx_class_init(ObjectClass *oc, void *data)
+{
+ SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+ sec->capsbase = 0x100;
+ sec->opregbase = 0x140;
+}
+
+static const TypeInfo ehci_xlnx_type_info = {
+ .name = "xlnx,ps7-usb",
+ .parent = TYPE_SYS_BUS_EHCI,
+ .class_init = ehci_xlnx_class_init,
+};
+
+static void ehci_exynos4210_class_init(ObjectClass *oc, void *data)
+{
+ SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ sec->capsbase = 0x0;
+ sec->opregbase = 0x10;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+}
+
+static const TypeInfo ehci_exynos4210_type_info = {
+ .name = TYPE_EXYNOS4210_EHCI,
+ .parent = TYPE_SYS_BUS_EHCI,
+ .class_init = ehci_exynos4210_class_init,
+};
+
+static void ehci_tegra2_class_init(ObjectClass *oc, void *data)
+{
+ SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ sec->capsbase = 0x100;
+ sec->opregbase = 0x140;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+}
+
+static const TypeInfo ehci_tegra2_type_info = {
+ .name = TYPE_TEGRA2_EHCI,
+ .parent = TYPE_SYS_BUS_EHCI,
+ .class_init = ehci_tegra2_class_init,
+};
+
+/*
+ * Faraday FUSBH200 USB 2.0 EHCI
+ */
+
+/**
+ * FUSBH200EHCIRegs:
+ * @FUSBH200_REG_EOF_ASTR: EOF/Async. Sleep Timer Register
+ * @FUSBH200_REG_BMCSR: Bus Monitor Control/Status Register
+ */
+enum FUSBH200EHCIRegs {
+ FUSBH200_REG_EOF_ASTR = 0x34,
+ FUSBH200_REG_BMCSR = 0x40,
+};
+
+static uint64_t fusbh200_ehci_read(void *opaque, hwaddr addr, unsigned size)
+{
+ EHCIState *s = opaque;
+ hwaddr off = s->opregbase + s->portscbase + 4 * s->portnr + addr;
+
+ switch (off) {
+ case FUSBH200_REG_EOF_ASTR:
+ return 0x00000041;
+ case FUSBH200_REG_BMCSR:
+ /* High-Speed, VBUS valid, interrupt level-high active */
+ return (2 << 9) | (1 << 8) | (1 << 3);
+ }
+
+ return 0;
+}
+
+static void fusbh200_ehci_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+}
+
+static const MemoryRegionOps fusbh200_ehci_mmio_ops = {
+ .read = fusbh200_ehci_read,
+ .write = fusbh200_ehci_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void fusbh200_ehci_init(Object *obj)
+{
+ EHCISysBusState *i = SYS_BUS_EHCI(obj);
+ FUSBH200EHCIState *f = FUSBH200_EHCI(obj);
+ EHCIState *s = &i->ehci;
+
+ memory_region_init_io(&f->mem_vendor, OBJECT(f), &fusbh200_ehci_mmio_ops, s,
+ "fusbh200", 0x4c);
+ memory_region_add_subregion(&s->mem,
+ s->opregbase + s->portscbase + 4 * s->portnr,
+ &f->mem_vendor);
+}
+
+static void fusbh200_ehci_class_init(ObjectClass *oc, void *data)
+{
+ SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ sec->capsbase = 0x0;
+ sec->opregbase = 0x10;
+ sec->portscbase = 0x20;
+ sec->portnr = 1;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+}
+
+static const TypeInfo ehci_fusbh200_type_info = {
+ .name = TYPE_FUSBH200_EHCI,
+ .parent = TYPE_SYS_BUS_EHCI,
+ .instance_size = sizeof(FUSBH200EHCIState),
+ .instance_init = fusbh200_ehci_init,
+ .class_init = fusbh200_ehci_class_init,
+};
+
+static void ehci_sysbus_register_types(void)
+{
+ type_register_static(&ehci_type_info);
+ type_register_static(&ehci_xlnx_type_info);
+ type_register_static(&ehci_exynos4210_type_info);
+ type_register_static(&ehci_tegra2_type_info);
+ type_register_static(&ehci_fusbh200_type_info);
+}
+
+type_init(ehci_sysbus_register_types)
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
new file mode 100644
index 00000000..25c225ae
--- /dev/null
+++ b/hw/usb/hcd-ehci.c
@@ -0,0 +1,2534 @@
+/*
+ * QEMU USB EHCI Emulation
+ *
+ * Copyright(c) 2008 Emutex Ltd. (address@hidden)
+ * Copyright(c) 2011-2012 Red Hat, Inc.
+ *
+ * Red Hat Authors:
+ * Gerd Hoffmann <kraxel@redhat.com>
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * EHCI project was started by Mark Burkley, with contributions by
+ * Niels de Vos. David S. Ahern continued working on it. Kevin Wolf,
+ * Jan Kiszka and Vincent Palatin contributed bugfixes.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or(at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/usb/ehci-regs.h"
+#include "hw/usb/hcd-ehci.h"
+#include "trace.h"
+
+#define FRAME_TIMER_FREQ 1000
+#define FRAME_TIMER_NS (NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ)
+#define UFRAME_TIMER_NS (FRAME_TIMER_NS / 8)
+
+#define NB_MAXINTRATE 8 // Max rate at which controller issues ints
+#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction
+#define MAX_QH 100 // Max allowable queue heads in a chain
+#define MIN_UFR_PER_TICK 24 /* Min frames to process when catching up */
+#define PERIODIC_ACTIVE 512 /* Micro-frames */
+
+/* Internal periodic / asynchronous schedule state machine states
+ */
+typedef enum {
+ EST_INACTIVE = 1000,
+ EST_ACTIVE,
+ EST_EXECUTING,
+ EST_SLEEPING,
+ /* The following states are internal to the state machine function
+ */
+ EST_WAITLISTHEAD,
+ EST_FETCHENTRY,
+ EST_FETCHQH,
+ EST_FETCHITD,
+ EST_FETCHSITD,
+ EST_ADVANCEQUEUE,
+ EST_FETCHQTD,
+ EST_EXECUTE,
+ EST_WRITEBACK,
+ EST_HORIZONTALQH
+} EHCI_STATES;
+
+/* macros for accessing fields within next link pointer entry */
+#define NLPTR_GET(x) ((x) & 0xffffffe0)
+#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
+#define NLPTR_TBIT(x) ((x) & 1) // 1=invalid, 0=valid
+
+/* link pointer types */
+#define NLPTR_TYPE_ITD 0 // isoc xfer descriptor
+#define NLPTR_TYPE_QH 1 // queue head
+#define NLPTR_TYPE_STITD 2 // split xaction, isoc xfer descriptor
+#define NLPTR_TYPE_FSTN 3 // frame span traversal node
+
+#define SET_LAST_RUN_CLOCK(s) \
+ (s)->last_run_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+/* nifty macros from Arnon's EHCI version */
+#define get_field(data, field) \
+ (((data) & field##_MASK) >> field##_SH)
+
+#define set_field(data, newval, field) do { \
+ uint32_t val = *data; \
+ val &= ~ field##_MASK; \
+ val |= ((newval) << field##_SH) & field##_MASK; \
+ *data = val; \
+ } while(0)
+
+static const char *ehci_state_names[] = {
+ [EST_INACTIVE] = "INACTIVE",
+ [EST_ACTIVE] = "ACTIVE",
+ [EST_EXECUTING] = "EXECUTING",
+ [EST_SLEEPING] = "SLEEPING",
+ [EST_WAITLISTHEAD] = "WAITLISTHEAD",
+ [EST_FETCHENTRY] = "FETCH ENTRY",
+ [EST_FETCHQH] = "FETCH QH",
+ [EST_FETCHITD] = "FETCH ITD",
+ [EST_ADVANCEQUEUE] = "ADVANCEQUEUE",
+ [EST_FETCHQTD] = "FETCH QTD",
+ [EST_EXECUTE] = "EXECUTE",
+ [EST_WRITEBACK] = "WRITEBACK",
+ [EST_HORIZONTALQH] = "HORIZONTALQH",
+};
+
+static const char *ehci_mmio_names[] = {
+ [USBCMD] = "USBCMD",
+ [USBSTS] = "USBSTS",
+ [USBINTR] = "USBINTR",
+ [FRINDEX] = "FRINDEX",
+ [PERIODICLISTBASE] = "P-LIST BASE",
+ [ASYNCLISTADDR] = "A-LIST ADDR",
+ [CONFIGFLAG] = "CONFIGFLAG",
+};
+
+static int ehci_state_executing(EHCIQueue *q);
+static int ehci_state_writeback(EHCIQueue *q);
+static int ehci_state_advqueue(EHCIQueue *q);
+static int ehci_fill_queue(EHCIPacket *p);
+static void ehci_free_packet(EHCIPacket *p);
+
+static const char *nr2str(const char **n, size_t len, uint32_t nr)
+{
+ if (nr < len && n[nr] != NULL) {
+ return n[nr];
+ } else {
+ return "unknown";
+ }
+}
+
+static const char *state2str(uint32_t state)
+{
+ return nr2str(ehci_state_names, ARRAY_SIZE(ehci_state_names), state);
+}
+
+static const char *addr2str(hwaddr addr)
+{
+ return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr);
+}
+
+static void ehci_trace_usbsts(uint32_t mask, int state)
+{
+ /* interrupts */
+ if (mask & USBSTS_INT) {
+ trace_usb_ehci_usbsts("INT", state);
+ }
+ if (mask & USBSTS_ERRINT) {
+ trace_usb_ehci_usbsts("ERRINT", state);
+ }
+ if (mask & USBSTS_PCD) {
+ trace_usb_ehci_usbsts("PCD", state);
+ }
+ if (mask & USBSTS_FLR) {
+ trace_usb_ehci_usbsts("FLR", state);
+ }
+ if (mask & USBSTS_HSE) {
+ trace_usb_ehci_usbsts("HSE", state);
+ }
+ if (mask & USBSTS_IAA) {
+ trace_usb_ehci_usbsts("IAA", state);
+ }
+
+ /* status */
+ if (mask & USBSTS_HALT) {
+ trace_usb_ehci_usbsts("HALT", state);
+ }
+ if (mask & USBSTS_REC) {
+ trace_usb_ehci_usbsts("REC", state);
+ }
+ if (mask & USBSTS_PSS) {
+ trace_usb_ehci_usbsts("PSS", state);
+ }
+ if (mask & USBSTS_ASS) {
+ trace_usb_ehci_usbsts("ASS", state);
+ }
+}
+
+static inline void ehci_set_usbsts(EHCIState *s, int mask)
+{
+ if ((s->usbsts & mask) == mask) {
+ return;
+ }
+ ehci_trace_usbsts(mask, 1);
+ s->usbsts |= mask;
+}
+
+static inline void ehci_clear_usbsts(EHCIState *s, int mask)
+{
+ if ((s->usbsts & mask) == 0) {
+ return;
+ }
+ ehci_trace_usbsts(mask, 0);
+ s->usbsts &= ~mask;
+}
+
+/* update irq line */
+static inline void ehci_update_irq(EHCIState *s)
+{
+ int level = 0;
+
+ if ((s->usbsts & USBINTR_MASK) & s->usbintr) {
+ level = 1;
+ }
+
+ trace_usb_ehci_irq(level, s->frindex, s->usbsts, s->usbintr);
+ qemu_set_irq(s->irq, level);
+}
+
+/* flag interrupt condition */
+static inline void ehci_raise_irq(EHCIState *s, int intr)
+{
+ if (intr & (USBSTS_PCD | USBSTS_FLR | USBSTS_HSE)) {
+ s->usbsts |= intr;
+ ehci_update_irq(s);
+ } else {
+ s->usbsts_pending |= intr;
+ }
+}
+
+/*
+ * Commit pending interrupts (added via ehci_raise_irq),
+ * at the rate allowed by "Interrupt Threshold Control".
+ */
+static inline void ehci_commit_irq(EHCIState *s)
+{
+ uint32_t itc;
+
+ if (!s->usbsts_pending) {
+ return;
+ }
+ if (s->usbsts_frindex > s->frindex) {
+ return;
+ }
+
+ itc = (s->usbcmd >> 16) & 0xff;
+ s->usbsts |= s->usbsts_pending;
+ s->usbsts_pending = 0;
+ s->usbsts_frindex = s->frindex + itc;
+ ehci_update_irq(s);
+}
+
+static void ehci_update_halt(EHCIState *s)
+{
+ if (s->usbcmd & USBCMD_RUNSTOP) {
+ ehci_clear_usbsts(s, USBSTS_HALT);
+ } else {
+ if (s->astate == EST_INACTIVE && s->pstate == EST_INACTIVE) {
+ ehci_set_usbsts(s, USBSTS_HALT);
+ }
+ }
+}
+
+static void ehci_set_state(EHCIState *s, int async, int state)
+{
+ if (async) {
+ trace_usb_ehci_state("async", state2str(state));
+ s->astate = state;
+ if (s->astate == EST_INACTIVE) {
+ ehci_clear_usbsts(s, USBSTS_ASS);
+ ehci_update_halt(s);
+ } else {
+ ehci_set_usbsts(s, USBSTS_ASS);
+ }
+ } else {
+ trace_usb_ehci_state("periodic", state2str(state));
+ s->pstate = state;
+ if (s->pstate == EST_INACTIVE) {
+ ehci_clear_usbsts(s, USBSTS_PSS);
+ ehci_update_halt(s);
+ } else {
+ ehci_set_usbsts(s, USBSTS_PSS);
+ }
+ }
+}
+
+static int ehci_get_state(EHCIState *s, int async)
+{
+ return async ? s->astate : s->pstate;
+}
+
+static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr)
+{
+ if (async) {
+ s->a_fetch_addr = addr;
+ } else {
+ s->p_fetch_addr = addr;
+ }
+}
+
+static int ehci_get_fetch_addr(EHCIState *s, int async)
+{
+ return async ? s->a_fetch_addr : s->p_fetch_addr;
+}
+
+static void ehci_trace_qh(EHCIQueue *q, hwaddr addr, EHCIqh *qh)
+{
+ /* need three here due to argument count limits */
+ trace_usb_ehci_qh_ptrs(q, addr, qh->next,
+ qh->current_qtd, qh->next_qtd, qh->altnext_qtd);
+ trace_usb_ehci_qh_fields(addr,
+ get_field(qh->epchar, QH_EPCHAR_RL),
+ get_field(qh->epchar, QH_EPCHAR_MPLEN),
+ get_field(qh->epchar, QH_EPCHAR_EPS),
+ get_field(qh->epchar, QH_EPCHAR_EP),
+ get_field(qh->epchar, QH_EPCHAR_DEVADDR));
+ trace_usb_ehci_qh_bits(addr,
+ (bool)(qh->epchar & QH_EPCHAR_C),
+ (bool)(qh->epchar & QH_EPCHAR_H),
+ (bool)(qh->epchar & QH_EPCHAR_DTC),
+ (bool)(qh->epchar & QH_EPCHAR_I));
+}
+
+static void ehci_trace_qtd(EHCIQueue *q, hwaddr addr, EHCIqtd *qtd)
+{
+ /* need three here due to argument count limits */
+ trace_usb_ehci_qtd_ptrs(q, addr, qtd->next, qtd->altnext);
+ trace_usb_ehci_qtd_fields(addr,
+ get_field(qtd->token, QTD_TOKEN_TBYTES),
+ get_field(qtd->token, QTD_TOKEN_CPAGE),
+ get_field(qtd->token, QTD_TOKEN_CERR),
+ get_field(qtd->token, QTD_TOKEN_PID));
+ trace_usb_ehci_qtd_bits(addr,
+ (bool)(qtd->token & QTD_TOKEN_IOC),
+ (bool)(qtd->token & QTD_TOKEN_ACTIVE),
+ (bool)(qtd->token & QTD_TOKEN_HALT),
+ (bool)(qtd->token & QTD_TOKEN_BABBLE),
+ (bool)(qtd->token & QTD_TOKEN_XACTERR));
+}
+
+static void ehci_trace_itd(EHCIState *s, hwaddr addr, EHCIitd *itd)
+{
+ trace_usb_ehci_itd(addr, itd->next,
+ get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT),
+ get_field(itd->bufptr[2], ITD_BUFPTR_MULT),
+ get_field(itd->bufptr[0], ITD_BUFPTR_EP),
+ get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR));
+}
+
+static void ehci_trace_sitd(EHCIState *s, hwaddr addr,
+ EHCIsitd *sitd)
+{
+ trace_usb_ehci_sitd(addr, sitd->next,
+ (bool)(sitd->results & SITD_RESULTS_ACTIVE));
+}
+
+static void ehci_trace_guest_bug(EHCIState *s, const char *message)
+{
+ trace_usb_ehci_guest_bug(message);
+ fprintf(stderr, "ehci warning: %s\n", message);
+}
+
+static inline bool ehci_enabled(EHCIState *s)
+{
+ return s->usbcmd & USBCMD_RUNSTOP;
+}
+
+static inline bool ehci_async_enabled(EHCIState *s)
+{
+ return ehci_enabled(s) && (s->usbcmd & USBCMD_ASE);
+}
+
+static inline bool ehci_periodic_enabled(EHCIState *s)
+{
+ return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE);
+}
+
+/* Get an array of dwords from main memory */
+static inline int get_dwords(EHCIState *ehci, uint32_t addr,
+ uint32_t *buf, int num)
+{
+ int i;
+
+ if (!ehci->as) {
+ ehci_raise_irq(ehci, USBSTS_HSE);
+ ehci->usbcmd &= ~USBCMD_RUNSTOP;
+ trace_usb_ehci_dma_error();
+ return -1;
+ }
+
+ for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ dma_memory_read(ehci->as, addr, buf, sizeof(*buf));
+ *buf = le32_to_cpu(*buf);
+ }
+
+ return num;
+}
+
+/* Put an array of dwords in to main memory */
+static inline int put_dwords(EHCIState *ehci, uint32_t addr,
+ uint32_t *buf, int num)
+{
+ int i;
+
+ if (!ehci->as) {
+ ehci_raise_irq(ehci, USBSTS_HSE);
+ ehci->usbcmd &= ~USBCMD_RUNSTOP;
+ trace_usb_ehci_dma_error();
+ return -1;
+ }
+
+ for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ uint32_t tmp = cpu_to_le32(*buf);
+ dma_memory_write(ehci->as, addr, &tmp, sizeof(tmp));
+ }
+
+ return num;
+}
+
+static int ehci_get_pid(EHCIqtd *qtd)
+{
+ switch (get_field(qtd->token, QTD_TOKEN_PID)) {
+ case 0:
+ return USB_TOKEN_OUT;
+ case 1:
+ return USB_TOKEN_IN;
+ case 2:
+ return USB_TOKEN_SETUP;
+ default:
+ fprintf(stderr, "bad token\n");
+ return 0;
+ }
+}
+
+static bool ehci_verify_qh(EHCIQueue *q, EHCIqh *qh)
+{
+ uint32_t devaddr = get_field(qh->epchar, QH_EPCHAR_DEVADDR);
+ uint32_t endp = get_field(qh->epchar, QH_EPCHAR_EP);
+ if ((devaddr != get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)) ||
+ (endp != get_field(q->qh.epchar, QH_EPCHAR_EP)) ||
+ (qh->current_qtd != q->qh.current_qtd) ||
+ (q->async && qh->next_qtd != q->qh.next_qtd) ||
+ (memcmp(&qh->altnext_qtd, &q->qh.altnext_qtd,
+ 7 * sizeof(uint32_t)) != 0) ||
+ (q->dev != NULL && q->dev->addr != devaddr)) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+static bool ehci_verify_qtd(EHCIPacket *p, EHCIqtd *qtd)
+{
+ if (p->qtdaddr != p->queue->qtdaddr ||
+ (p->queue->async && !NLPTR_TBIT(p->qtd.next) &&
+ (p->qtd.next != qtd->next)) ||
+ (!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd->altnext)) ||
+ p->qtd.token != qtd->token ||
+ p->qtd.bufptr[0] != qtd->bufptr[0]) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+static bool ehci_verify_pid(EHCIQueue *q, EHCIqtd *qtd)
+{
+ int ep = get_field(q->qh.epchar, QH_EPCHAR_EP);
+ int pid = ehci_get_pid(qtd);
+
+ /* Note the pid changing is normal for ep 0 (the control ep) */
+ if (q->last_pid && ep != 0 && pid != q->last_pid) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+/* Finish executing and writeback a packet outside of the regular
+ fetchqh -> fetchqtd -> execute -> writeback cycle */
+static void ehci_writeback_async_complete_packet(EHCIPacket *p)
+{
+ EHCIQueue *q = p->queue;
+ EHCIqtd qtd;
+ EHCIqh qh;
+ int state;
+
+ /* Verify the qh + qtd, like we do when going through fetchqh & fetchqtd */
+ get_dwords(q->ehci, NLPTR_GET(q->qhaddr),
+ (uint32_t *) &qh, sizeof(EHCIqh) >> 2);
+ get_dwords(q->ehci, NLPTR_GET(q->qtdaddr),
+ (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2);
+ if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) {
+ p->async = EHCI_ASYNC_INITIALIZED;
+ ehci_free_packet(p);
+ return;
+ }
+
+ state = ehci_get_state(q->ehci, q->async);
+ ehci_state_executing(q);
+ ehci_state_writeback(q); /* Frees the packet! */
+ if (!(q->qh.token & QTD_TOKEN_HALT)) {
+ ehci_state_advqueue(q);
+ }
+ ehci_set_state(q->ehci, q->async, state);
+}
+
+/* packet management */
+
+static EHCIPacket *ehci_alloc_packet(EHCIQueue *q)
+{
+ EHCIPacket *p;
+
+ p = g_new0(EHCIPacket, 1);
+ p->queue = q;
+ usb_packet_init(&p->packet);
+ QTAILQ_INSERT_TAIL(&q->packets, p, next);
+ trace_usb_ehci_packet_action(p->queue, p, "alloc");
+ return p;
+}
+
+static void ehci_free_packet(EHCIPacket *p)
+{
+ if (p->async == EHCI_ASYNC_FINISHED &&
+ !(p->queue->qh.token & QTD_TOKEN_HALT)) {
+ ehci_writeback_async_complete_packet(p);
+ return;
+ }
+ trace_usb_ehci_packet_action(p->queue, p, "free");
+ if (p->async == EHCI_ASYNC_INFLIGHT) {
+ usb_cancel_packet(&p->packet);
+ }
+ if (p->async == EHCI_ASYNC_FINISHED &&
+ p->packet.status == USB_RET_SUCCESS) {
+ fprintf(stderr,
+ "EHCI: Dropping completed packet from halted %s ep %02X\n",
+ (p->pid == USB_TOKEN_IN) ? "in" : "out",
+ get_field(p->queue->qh.epchar, QH_EPCHAR_EP));
+ }
+ if (p->async != EHCI_ASYNC_NONE) {
+ usb_packet_unmap(&p->packet, &p->sgl);
+ qemu_sglist_destroy(&p->sgl);
+ }
+ QTAILQ_REMOVE(&p->queue->packets, p, next);
+ usb_packet_cleanup(&p->packet);
+ g_free(p);
+}
+
+/* queue management */
+
+static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async)
+{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ EHCIQueue *q;
+
+ q = g_malloc0(sizeof(*q));
+ q->ehci = ehci;
+ q->qhaddr = addr;
+ q->async = async;
+ QTAILQ_INIT(&q->packets);
+ QTAILQ_INSERT_HEAD(head, q, next);
+ trace_usb_ehci_queue_action(q, "alloc");
+ return q;
+}
+
+static void ehci_queue_stopped(EHCIQueue *q)
+{
+ int endp = get_field(q->qh.epchar, QH_EPCHAR_EP);
+
+ if (!q->last_pid || !q->dev) {
+ return;
+ }
+
+ usb_device_ep_stopped(q->dev, usb_ep_get(q->dev, q->last_pid, endp));
+}
+
+static int ehci_cancel_queue(EHCIQueue *q)
+{
+ EHCIPacket *p;
+ int packets = 0;
+
+ p = QTAILQ_FIRST(&q->packets);
+ if (p == NULL) {
+ goto leave;
+ }
+
+ trace_usb_ehci_queue_action(q, "cancel");
+ do {
+ ehci_free_packet(p);
+ packets++;
+ } while ((p = QTAILQ_FIRST(&q->packets)) != NULL);
+
+leave:
+ ehci_queue_stopped(q);
+ return packets;
+}
+
+static int ehci_reset_queue(EHCIQueue *q)
+{
+ int packets;
+
+ trace_usb_ehci_queue_action(q, "reset");
+ packets = ehci_cancel_queue(q);
+ q->dev = NULL;
+ q->qtdaddr = 0;
+ q->last_pid = 0;
+ return packets;
+}
+
+static void ehci_free_queue(EHCIQueue *q, const char *warn)
+{
+ EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues;
+ int cancelled;
+
+ trace_usb_ehci_queue_action(q, "free");
+ cancelled = ehci_cancel_queue(q);
+ if (warn && cancelled > 0) {
+ ehci_trace_guest_bug(q->ehci, warn);
+ }
+ QTAILQ_REMOVE(head, q, next);
+ g_free(q);
+}
+
+static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr,
+ int async)
+{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ EHCIQueue *q;
+
+ QTAILQ_FOREACH(q, head, next) {
+ if (addr == q->qhaddr) {
+ return q;
+ }
+ }
+ return NULL;
+}
+
+static void ehci_queues_rip_unused(EHCIState *ehci, int async)
+{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ const char *warn = async ? "guest unlinked busy QH" : NULL;
+ uint64_t maxage = FRAME_TIMER_NS * ehci->maxframes * 4;
+ EHCIQueue *q, *tmp;
+
+ QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
+ if (q->seen) {
+ q->seen = 0;
+ q->ts = ehci->last_run_ns;
+ continue;
+ }
+ if (ehci->last_run_ns < q->ts + maxage) {
+ continue;
+ }
+ ehci_free_queue(q, warn);
+ }
+}
+
+static void ehci_queues_rip_unseen(EHCIState *ehci, int async)
+{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ EHCIQueue *q, *tmp;
+
+ QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
+ if (!q->seen) {
+ ehci_free_queue(q, NULL);
+ }
+ }
+}
+
+static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev, int async)
+{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ EHCIQueue *q, *tmp;
+
+ QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
+ if (q->dev != dev) {
+ continue;
+ }
+ ehci_free_queue(q, NULL);
+ }
+}
+
+static void ehci_queues_rip_all(EHCIState *ehci, int async)
+{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ const char *warn = async ? "guest stopped busy async schedule" : NULL;
+ EHCIQueue *q, *tmp;
+
+ QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
+ ehci_free_queue(q, warn);
+ }
+}
+
+/* Attach or detach a device on root hub */
+
+static void ehci_attach(USBPort *port)
+{
+ EHCIState *s = port->opaque;
+ uint32_t *portsc = &s->portsc[port->index];
+ const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci";
+
+ trace_usb_ehci_port_attach(port->index, owner, port->dev->product_desc);
+
+ if (*portsc & PORTSC_POWNER) {
+ USBPort *companion = s->companion_ports[port->index];
+ companion->dev = port->dev;
+ companion->ops->attach(companion);
+ return;
+ }
+
+ *portsc |= PORTSC_CONNECT;
+ *portsc |= PORTSC_CSC;
+
+ ehci_raise_irq(s, USBSTS_PCD);
+}
+
+static void ehci_detach(USBPort *port)
+{
+ EHCIState *s = port->opaque;
+ uint32_t *portsc = &s->portsc[port->index];
+ const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci";
+
+ trace_usb_ehci_port_detach(port->index, owner);
+
+ if (*portsc & PORTSC_POWNER) {
+ USBPort *companion = s->companion_ports[port->index];
+ companion->ops->detach(companion);
+ companion->dev = NULL;
+ /*
+ * EHCI spec 4.2.2: "When a disconnect occurs... On the event,
+ * the port ownership is returned immediately to the EHCI controller."
+ */
+ *portsc &= ~PORTSC_POWNER;
+ return;
+ }
+
+ ehci_queues_rip_device(s, port->dev, 0);
+ ehci_queues_rip_device(s, port->dev, 1);
+
+ *portsc &= ~(PORTSC_CONNECT|PORTSC_PED);
+ *portsc |= PORTSC_CSC;
+
+ ehci_raise_irq(s, USBSTS_PCD);
+}
+
+static void ehci_child_detach(USBPort *port, USBDevice *child)
+{
+ EHCIState *s = port->opaque;
+ uint32_t portsc = s->portsc[port->index];
+
+ if (portsc & PORTSC_POWNER) {
+ USBPort *companion = s->companion_ports[port->index];
+ companion->ops->child_detach(companion, child);
+ return;
+ }
+
+ ehci_queues_rip_device(s, child, 0);
+ ehci_queues_rip_device(s, child, 1);
+}
+
+static void ehci_wakeup(USBPort *port)
+{
+ EHCIState *s = port->opaque;
+ uint32_t *portsc = &s->portsc[port->index];
+
+ if (*portsc & PORTSC_POWNER) {
+ USBPort *companion = s->companion_ports[port->index];
+ if (companion->ops->wakeup) {
+ companion->ops->wakeup(companion);
+ }
+ return;
+ }
+
+ if (*portsc & PORTSC_SUSPEND) {
+ trace_usb_ehci_port_wakeup(port->index);
+ *portsc |= PORTSC_FPRES;
+ ehci_raise_irq(s, USBSTS_PCD);
+ }
+
+ qemu_bh_schedule(s->async_bh);
+}
+
+static void ehci_register_companion(USBBus *bus, USBPort *ports[],
+ uint32_t portcount, uint32_t firstport,
+ Error **errp)
+{
+ EHCIState *s = container_of(bus, EHCIState, bus);
+ uint32_t i;
+
+ if (firstport + portcount > NB_PORTS) {
+ error_setg(errp, "firstport must be between 0 and %u",
+ NB_PORTS - portcount);
+ return;
+ }
+
+ for (i = 0; i < portcount; i++) {
+ if (s->companion_ports[firstport + i]) {
+ error_setg(errp, "firstport %u asks for ports %u-%u,"
+ " but port %u has a companion assigned already",
+ firstport, firstport, firstport + portcount - 1,
+ firstport + i);
+ return;
+ }
+ }
+
+ for (i = 0; i < portcount; i++) {
+ s->companion_ports[firstport + i] = ports[i];
+ s->ports[firstport + i].speedmask |=
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL;
+ /* Ensure devs attached before the initial reset go to the companion */
+ s->portsc[firstport + i] = PORTSC_POWNER;
+ }
+
+ s->companion_count++;
+ s->caps[0x05] = (s->companion_count << 4) | portcount;
+}
+
+static void ehci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep,
+ unsigned int stream)
+{
+ EHCIState *s = container_of(bus, EHCIState, bus);
+ uint32_t portsc = s->portsc[ep->dev->port->index];
+
+ if (portsc & PORTSC_POWNER) {
+ return;
+ }
+
+ s->periodic_sched_active = PERIODIC_ACTIVE;
+ qemu_bh_schedule(s->async_bh);
+}
+
+static USBDevice *ehci_find_device(EHCIState *ehci, uint8_t addr)
+{
+ USBDevice *dev;
+ USBPort *port;
+ int i;
+
+ for (i = 0; i < NB_PORTS; i++) {
+ port = &ehci->ports[i];
+ if (!(ehci->portsc[i] & PORTSC_PED)) {
+ DPRINTF("Port %d not enabled\n", i);
+ continue;
+ }
+ dev = usb_find_device(port, addr);
+ if (dev != NULL) {
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+/* 4.1 host controller initialization */
+void ehci_reset(void *opaque)
+{
+ EHCIState *s = opaque;
+ int i;
+ USBDevice *devs[NB_PORTS];
+
+ trace_usb_ehci_reset();
+
+ /*
+ * Do the detach before touching portsc, so that it correctly gets send to
+ * us or to our companion based on PORTSC_POWNER before the reset.
+ */
+ for(i = 0; i < NB_PORTS; i++) {
+ devs[i] = s->ports[i].dev;
+ if (devs[i] && devs[i]->attached) {
+ usb_detach(&s->ports[i]);
+ }
+ }
+
+ memset(&s->opreg, 0x00, sizeof(s->opreg));
+ memset(&s->portsc, 0x00, sizeof(s->portsc));
+
+ s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
+ s->usbsts = USBSTS_HALT;
+ s->usbsts_pending = 0;
+ s->usbsts_frindex = 0;
+
+ s->astate = EST_INACTIVE;
+ s->pstate = EST_INACTIVE;
+
+ for(i = 0; i < NB_PORTS; i++) {
+ if (s->companion_ports[i]) {
+ s->portsc[i] = PORTSC_POWNER | PORTSC_PPOWER;
+ } else {
+ s->portsc[i] = PORTSC_PPOWER;
+ }
+ if (devs[i] && devs[i]->attached) {
+ usb_attach(&s->ports[i]);
+ usb_device_reset(devs[i]);
+ }
+ }
+ ehci_queues_rip_all(s, 0);
+ ehci_queues_rip_all(s, 1);
+ timer_del(s->frame_timer);
+ qemu_bh_cancel(s->async_bh);
+}
+
+static uint64_t ehci_caps_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ EHCIState *s = ptr;
+ return s->caps[addr];
+}
+
+static uint64_t ehci_opreg_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ EHCIState *s = ptr;
+ uint32_t val;
+
+ switch (addr) {
+ case FRINDEX:
+ /* Round down to mult of 8, else it can go backwards on migration */
+ val = s->frindex & ~7;
+ break;
+ default:
+ val = s->opreg[addr >> 2];
+ }
+
+ trace_usb_ehci_opreg_read(addr + s->opregbase, addr2str(addr), val);
+ return val;
+}
+
+static uint64_t ehci_port_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ EHCIState *s = ptr;
+ uint32_t val;
+
+ val = s->portsc[addr >> 2];
+ trace_usb_ehci_portsc_read(addr + s->portscbase, addr >> 2, val);
+ return val;
+}
+
+static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner)
+{
+ USBDevice *dev = s->ports[port].dev;
+ uint32_t *portsc = &s->portsc[port];
+ uint32_t orig;
+
+ if (s->companion_ports[port] == NULL)
+ return;
+
+ owner = owner & PORTSC_POWNER;
+ orig = *portsc & PORTSC_POWNER;
+
+ if (!(owner ^ orig)) {
+ return;
+ }
+
+ if (dev && dev->attached) {
+ usb_detach(&s->ports[port]);
+ }
+
+ *portsc &= ~PORTSC_POWNER;
+ *portsc |= owner;
+
+ if (dev && dev->attached) {
+ usb_attach(&s->ports[port]);
+ }
+}
+
+static void ehci_port_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ EHCIState *s = ptr;
+ int port = addr >> 2;
+ uint32_t *portsc = &s->portsc[port];
+ uint32_t old = *portsc;
+ USBDevice *dev = s->ports[port].dev;
+
+ trace_usb_ehci_portsc_write(addr + s->portscbase, addr >> 2, val);
+
+ /* Clear rwc bits */
+ *portsc &= ~(val & PORTSC_RWC_MASK);
+ /* The guest may clear, but not set the PED bit */
+ *portsc &= val | ~PORTSC_PED;
+ /* POWNER is masked out by RO_MASK as it is RO when we've no companion */
+ handle_port_owner_write(s, port, val);
+ /* And finally apply RO_MASK */
+ val &= PORTSC_RO_MASK;
+
+ if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
+ trace_usb_ehci_port_reset(port, 1);
+ }
+
+ if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
+ trace_usb_ehci_port_reset(port, 0);
+ if (dev && dev->attached) {
+ usb_port_reset(&s->ports[port]);
+ *portsc &= ~PORTSC_CSC;
+ }
+
+ /*
+ * Table 2.16 Set the enable bit(and enable bit change) to indicate
+ * to SW that this port has a high speed device attached
+ */
+ if (dev && dev->attached && (dev->speedmask & USB_SPEED_MASK_HIGH)) {
+ val |= PORTSC_PED;
+ }
+ }
+
+ if ((val & PORTSC_SUSPEND) && !(*portsc & PORTSC_SUSPEND)) {
+ trace_usb_ehci_port_suspend(port);
+ }
+ if (!(val & PORTSC_FPRES) && (*portsc & PORTSC_FPRES)) {
+ trace_usb_ehci_port_resume(port);
+ val &= ~PORTSC_SUSPEND;
+ }
+
+ *portsc &= ~PORTSC_RO_MASK;
+ *portsc |= val;
+ trace_usb_ehci_portsc_change(addr + s->portscbase, addr >> 2, *portsc, old);
+}
+
+static void ehci_opreg_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ EHCIState *s = ptr;
+ uint32_t *mmio = s->opreg + (addr >> 2);
+ uint32_t old = *mmio;
+ int i;
+
+ trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val);
+
+ switch (addr) {
+ case USBCMD:
+ if (val & USBCMD_HCRESET) {
+ ehci_reset(s);
+ val = s->usbcmd;
+ break;
+ }
+
+ /* not supporting dynamic frame list size at the moment */
+ if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) {
+ fprintf(stderr, "attempt to set frame list size -- value %d\n",
+ (int)val & USBCMD_FLS);
+ val &= ~USBCMD_FLS;
+ }
+
+ if (val & USBCMD_IAAD) {
+ /*
+ * Process IAAD immediately, otherwise the Linux IAAD watchdog may
+ * trigger and re-use a qh without us seeing the unlink.
+ */
+ s->async_stepdown = 0;
+ qemu_bh_schedule(s->async_bh);
+ trace_usb_ehci_doorbell_ring();
+ }
+
+ if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) !=
+ ((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & s->usbcmd)) {
+ if (s->pstate == EST_INACTIVE) {
+ SET_LAST_RUN_CLOCK(s);
+ }
+ s->usbcmd = val; /* Set usbcmd for ehci_update_halt() */
+ ehci_update_halt(s);
+ s->async_stepdown = 0;
+ qemu_bh_schedule(s->async_bh);
+ }
+ break;
+
+ case USBSTS:
+ val &= USBSTS_RO_MASK; // bits 6 through 31 are RO
+ ehci_clear_usbsts(s, val); // bits 0 through 5 are R/WC
+ val = s->usbsts;
+ ehci_update_irq(s);
+ break;
+
+ case USBINTR:
+ val &= USBINTR_MASK;
+ if (ehci_enabled(s) && (USBSTS_FLR & val)) {
+ qemu_bh_schedule(s->async_bh);
+ }
+ break;
+
+ case FRINDEX:
+ val &= 0x00003fff; /* frindex is 14bits */
+ s->usbsts_frindex = val;
+ break;
+
+ case CONFIGFLAG:
+ val &= 0x1;
+ if (val) {
+ for(i = 0; i < NB_PORTS; i++)
+ handle_port_owner_write(s, i, 0);
+ }
+ break;
+
+ case PERIODICLISTBASE:
+ if (ehci_periodic_enabled(s)) {
+ fprintf(stderr,
+ "ehci: PERIODIC list base register set while periodic schedule\n"
+ " is enabled and HC is enabled\n");
+ }
+ break;
+
+ case ASYNCLISTADDR:
+ if (ehci_async_enabled(s)) {
+ fprintf(stderr,
+ "ehci: ASYNC list address register set while async schedule\n"
+ " is enabled and HC is enabled\n");
+ }
+ break;
+ }
+
+ *mmio = val;
+ trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr),
+ *mmio, old);
+}
+
+/*
+ * Write the qh back to guest physical memory. This step isn't
+ * in the EHCI spec but we need to do it since we don't share
+ * physical memory with our guest VM.
+ *
+ * The first three dwords are read-only for the EHCI, so skip them
+ * when writing back the qh.
+ */
+static void ehci_flush_qh(EHCIQueue *q)
+{
+ uint32_t *qh = (uint32_t *) &q->qh;
+ uint32_t dwords = sizeof(EHCIqh) >> 2;
+ uint32_t addr = NLPTR_GET(q->qhaddr);
+
+ put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
+}
+
+// 4.10.2
+
+static int ehci_qh_do_overlay(EHCIQueue *q)
+{
+ EHCIPacket *p = QTAILQ_FIRST(&q->packets);
+ int i;
+ int dtoggle;
+ int ping;
+ int eps;
+ int reload;
+
+ assert(p != NULL);
+ assert(p->qtdaddr == q->qtdaddr);
+
+ // remember values in fields to preserve in qh after overlay
+
+ dtoggle = q->qh.token & QTD_TOKEN_DTOGGLE;
+ ping = q->qh.token & QTD_TOKEN_PING;
+
+ q->qh.current_qtd = p->qtdaddr;
+ q->qh.next_qtd = p->qtd.next;
+ q->qh.altnext_qtd = p->qtd.altnext;
+ q->qh.token = p->qtd.token;
+
+
+ eps = get_field(q->qh.epchar, QH_EPCHAR_EPS);
+ if (eps == EHCI_QH_EPS_HIGH) {
+ q->qh.token &= ~QTD_TOKEN_PING;
+ q->qh.token |= ping;
+ }
+
+ reload = get_field(q->qh.epchar, QH_EPCHAR_RL);
+ set_field(&q->qh.altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
+
+ for (i = 0; i < 5; i++) {
+ q->qh.bufptr[i] = p->qtd.bufptr[i];
+ }
+
+ if (!(q->qh.epchar & QH_EPCHAR_DTC)) {
+ // preserve QH DT bit
+ q->qh.token &= ~QTD_TOKEN_DTOGGLE;
+ q->qh.token |= dtoggle;
+ }
+
+ q->qh.bufptr[1] &= ~BUFPTR_CPROGMASK_MASK;
+ q->qh.bufptr[2] &= ~BUFPTR_FRAMETAG_MASK;
+
+ ehci_flush_qh(q);
+
+ return 0;
+}
+
+static int ehci_init_transfer(EHCIPacket *p)
+{
+ uint32_t cpage, offset, bytes, plen;
+ dma_addr_t page;
+
+ cpage = get_field(p->qtd.token, QTD_TOKEN_CPAGE);
+ bytes = get_field(p->qtd.token, QTD_TOKEN_TBYTES);
+ offset = p->qtd.bufptr[0] & ~QTD_BUFPTR_MASK;
+ qemu_sglist_init(&p->sgl, p->queue->ehci->device, 5, p->queue->ehci->as);
+
+ while (bytes > 0) {
+ if (cpage > 4) {
+ fprintf(stderr, "cpage out of range (%d)\n", cpage);
+ return -1;
+ }
+
+ page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK;
+ page += offset;
+ plen = bytes;
+ if (plen > 4096 - offset) {
+ plen = 4096 - offset;
+ offset = 0;
+ cpage++;
+ }
+
+ qemu_sglist_add(&p->sgl, page, plen);
+ bytes -= plen;
+ }
+ return 0;
+}
+
+static void ehci_finish_transfer(EHCIQueue *q, int len)
+{
+ uint32_t cpage, offset;
+
+ if (len > 0) {
+ /* update cpage & offset */
+ cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
+ offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
+
+ offset += len;
+ cpage += offset >> QTD_BUFPTR_SH;
+ offset &= ~QTD_BUFPTR_MASK;
+
+ set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE);
+ q->qh.bufptr[0] &= QTD_BUFPTR_MASK;
+ q->qh.bufptr[0] |= offset;
+ }
+}
+
+static void ehci_async_complete_packet(USBPort *port, USBPacket *packet)
+{
+ EHCIPacket *p;
+ EHCIState *s = port->opaque;
+ uint32_t portsc = s->portsc[port->index];
+
+ if (portsc & PORTSC_POWNER) {
+ USBPort *companion = s->companion_ports[port->index];
+ companion->ops->complete(companion, packet);
+ return;
+ }
+
+ p = container_of(packet, EHCIPacket, packet);
+ assert(p->async == EHCI_ASYNC_INFLIGHT);
+
+ if (packet->status == USB_RET_REMOVE_FROM_QUEUE) {
+ trace_usb_ehci_packet_action(p->queue, p, "remove");
+ ehci_free_packet(p);
+ return;
+ }
+
+ trace_usb_ehci_packet_action(p->queue, p, "wakeup");
+ p->async = EHCI_ASYNC_FINISHED;
+
+ if (!p->queue->async) {
+ s->periodic_sched_active = PERIODIC_ACTIVE;
+ }
+ qemu_bh_schedule(s->async_bh);
+}
+
+static void ehci_execute_complete(EHCIQueue *q)
+{
+ EHCIPacket *p = QTAILQ_FIRST(&q->packets);
+ uint32_t tbytes;
+
+ assert(p != NULL);
+ assert(p->qtdaddr == q->qtdaddr);
+ assert(p->async == EHCI_ASYNC_INITIALIZED ||
+ p->async == EHCI_ASYNC_FINISHED);
+
+ DPRINTF("execute_complete: qhaddr 0x%x, next 0x%x, qtdaddr 0x%x, "
+ "status %d, actual_length %d\n",
+ q->qhaddr, q->qh.next, q->qtdaddr,
+ p->packet.status, p->packet.actual_length);
+
+ switch (p->packet.status) {
+ case USB_RET_SUCCESS:
+ break;
+ case USB_RET_IOERROR:
+ case USB_RET_NODEV:
+ q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR);
+ set_field(&q->qh.token, 0, QTD_TOKEN_CERR);
+ ehci_raise_irq(q->ehci, USBSTS_ERRINT);
+ break;
+ case USB_RET_STALL:
+ q->qh.token |= QTD_TOKEN_HALT;
+ ehci_raise_irq(q->ehci, USBSTS_ERRINT);
+ break;
+ case USB_RET_NAK:
+ set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT);
+ return; /* We're not done yet with this transaction */
+ case USB_RET_BABBLE:
+ q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE);
+ ehci_raise_irq(q->ehci, USBSTS_ERRINT);
+ break;
+ default:
+ /* should not be triggerable */
+ fprintf(stderr, "USB invalid response %d\n", p->packet.status);
+ g_assert_not_reached();
+ break;
+ }
+
+ /* TODO check 4.12 for splits */
+ tbytes = get_field(q->qh.token, QTD_TOKEN_TBYTES);
+ if (tbytes && p->pid == USB_TOKEN_IN) {
+ tbytes -= p->packet.actual_length;
+ if (tbytes) {
+ /* 4.15.1.2 must raise int on a short input packet */
+ ehci_raise_irq(q->ehci, USBSTS_INT);
+ if (q->async) {
+ q->ehci->int_req_by_async = true;
+ }
+ }
+ } else {
+ tbytes = 0;
+ }
+ DPRINTF("updating tbytes to %d\n", tbytes);
+ set_field(&q->qh.token, tbytes, QTD_TOKEN_TBYTES);
+
+ ehci_finish_transfer(q, p->packet.actual_length);
+ usb_packet_unmap(&p->packet, &p->sgl);
+ qemu_sglist_destroy(&p->sgl);
+ p->async = EHCI_ASYNC_NONE;
+
+ q->qh.token ^= QTD_TOKEN_DTOGGLE;
+ q->qh.token &= ~QTD_TOKEN_ACTIVE;
+
+ if (q->qh.token & QTD_TOKEN_IOC) {
+ ehci_raise_irq(q->ehci, USBSTS_INT);
+ if (q->async) {
+ q->ehci->int_req_by_async = true;
+ }
+ }
+}
+
+/* 4.10.3 returns "again" */
+static int ehci_execute(EHCIPacket *p, const char *action)
+{
+ USBEndpoint *ep;
+ int endp;
+ bool spd;
+
+ assert(p->async == EHCI_ASYNC_NONE ||
+ p->async == EHCI_ASYNC_INITIALIZED);
+
+ if (!(p->qtd.token & QTD_TOKEN_ACTIVE)) {
+ fprintf(stderr, "Attempting to execute inactive qtd\n");
+ return -1;
+ }
+
+ if (get_field(p->qtd.token, QTD_TOKEN_TBYTES) > BUFF_SIZE) {
+ ehci_trace_guest_bug(p->queue->ehci,
+ "guest requested more bytes than allowed");
+ return -1;
+ }
+
+ if (!ehci_verify_pid(p->queue, &p->qtd)) {
+ ehci_queue_stopped(p->queue); /* Mark the ep in the prev dir stopped */
+ }
+ p->pid = ehci_get_pid(&p->qtd);
+ p->queue->last_pid = p->pid;
+ endp = get_field(p->queue->qh.epchar, QH_EPCHAR_EP);
+ ep = usb_ep_get(p->queue->dev, p->pid, endp);
+
+ if (p->async == EHCI_ASYNC_NONE) {
+ if (ehci_init_transfer(p) != 0) {
+ return -1;
+ }
+
+ spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0);
+ usb_packet_setup(&p->packet, p->pid, ep, 0, p->qtdaddr, spd,
+ (p->qtd.token & QTD_TOKEN_IOC) != 0);
+ usb_packet_map(&p->packet, &p->sgl);
+ p->async = EHCI_ASYNC_INITIALIZED;
+ }
+
+ trace_usb_ehci_packet_action(p->queue, p, action);
+ usb_handle_packet(p->queue->dev, &p->packet);
+ DPRINTF("submit: qh 0x%x next 0x%x qtd 0x%x pid 0x%x len %zd endp 0x%x "
+ "status %d actual_length %d\n", p->queue->qhaddr, p->qtd.next,
+ p->qtdaddr, p->pid, p->packet.iov.size, endp, p->packet.status,
+ p->packet.actual_length);
+
+ if (p->packet.actual_length > BUFF_SIZE) {
+ fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n");
+ return -1;
+ }
+
+ return 1;
+}
+
+/* 4.7.2
+ */
+
+static int ehci_process_itd(EHCIState *ehci,
+ EHCIitd *itd,
+ uint32_t addr)
+{
+ USBDevice *dev;
+ USBEndpoint *ep;
+ uint32_t i, len, pid, dir, devaddr, endp, xfers = 0;
+ uint32_t pg, off, ptr1, ptr2, max, mult;
+
+ ehci->periodic_sched_active = PERIODIC_ACTIVE;
+
+ dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
+ devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR);
+ endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP);
+ max = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT);
+ mult = get_field(itd->bufptr[2], ITD_BUFPTR_MULT);
+
+ for(i = 0; i < 8; i++) {
+ if (itd->transact[i] & ITD_XACT_ACTIVE) {
+ pg = get_field(itd->transact[i], ITD_XACT_PGSEL);
+ off = itd->transact[i] & ITD_XACT_OFFSET_MASK;
+ ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK);
+ ptr2 = (itd->bufptr[pg+1] & ITD_BUFPTR_MASK);
+ len = get_field(itd->transact[i], ITD_XACT_LENGTH);
+
+ if (len > max * mult) {
+ len = max * mult;
+ }
+
+ if (len > BUFF_SIZE) {
+ return -1;
+ }
+
+ qemu_sglist_init(&ehci->isgl, ehci->device, 2, ehci->as);
+ if (off + len > 4096) {
+ /* transfer crosses page border */
+ uint32_t len2 = off + len - 4096;
+ uint32_t len1 = len - len2;
+ qemu_sglist_add(&ehci->isgl, ptr1 + off, len1);
+ qemu_sglist_add(&ehci->isgl, ptr2, len2);
+ } else {
+ qemu_sglist_add(&ehci->isgl, ptr1 + off, len);
+ }
+
+ pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT;
+
+ dev = ehci_find_device(ehci, devaddr);
+ ep = usb_ep_get(dev, pid, endp);
+ if (ep && ep->type == USB_ENDPOINT_XFER_ISOC) {
+ usb_packet_setup(&ehci->ipacket, pid, ep, 0, addr, false,
+ (itd->transact[i] & ITD_XACT_IOC) != 0);
+ usb_packet_map(&ehci->ipacket, &ehci->isgl);
+ usb_handle_packet(dev, &ehci->ipacket);
+ usb_packet_unmap(&ehci->ipacket, &ehci->isgl);
+ } else {
+ DPRINTF("ISOCH: attempt to addess non-iso endpoint\n");
+ ehci->ipacket.status = USB_RET_NAK;
+ ehci->ipacket.actual_length = 0;
+ }
+ qemu_sglist_destroy(&ehci->isgl);
+
+ switch (ehci->ipacket.status) {
+ case USB_RET_SUCCESS:
+ break;
+ default:
+ fprintf(stderr, "Unexpected iso usb result: %d\n",
+ ehci->ipacket.status);
+ /* Fall through */
+ case USB_RET_IOERROR:
+ case USB_RET_NODEV:
+ /* 3.3.2: XACTERR is only allowed on IN transactions */
+ if (dir) {
+ itd->transact[i] |= ITD_XACT_XACTERR;
+ ehci_raise_irq(ehci, USBSTS_ERRINT);
+ }
+ break;
+ case USB_RET_BABBLE:
+ itd->transact[i] |= ITD_XACT_BABBLE;
+ ehci_raise_irq(ehci, USBSTS_ERRINT);
+ break;
+ case USB_RET_NAK:
+ /* no data for us, so do a zero-length transfer */
+ ehci->ipacket.actual_length = 0;
+ break;
+ }
+ if (!dir) {
+ set_field(&itd->transact[i], len - ehci->ipacket.actual_length,
+ ITD_XACT_LENGTH); /* OUT */
+ } else {
+ set_field(&itd->transact[i], ehci->ipacket.actual_length,
+ ITD_XACT_LENGTH); /* IN */
+ }
+ if (itd->transact[i] & ITD_XACT_IOC) {
+ ehci_raise_irq(ehci, USBSTS_INT);
+ }
+ itd->transact[i] &= ~ITD_XACT_ACTIVE;
+ xfers++;
+ }
+ }
+ return xfers ? 0 : -1;
+}
+
+
+/* This state is the entry point for asynchronous schedule
+ * processing. Entry here consitutes a EHCI start event state (4.8.5)
+ */
+static int ehci_state_waitlisthead(EHCIState *ehci, int async)
+{
+ EHCIqh qh;
+ int i = 0;
+ int again = 0;
+ uint32_t entry = ehci->asynclistaddr;
+
+ /* set reclamation flag at start event (4.8.6) */
+ if (async) {
+ ehci_set_usbsts(ehci, USBSTS_REC);
+ }
+
+ ehci_queues_rip_unused(ehci, async);
+
+ /* Find the head of the list (4.9.1.1) */
+ for(i = 0; i < MAX_QH; i++) {
+ if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
+ sizeof(EHCIqh) >> 2) < 0) {
+ return 0;
+ }
+ ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
+
+ if (qh.epchar & QH_EPCHAR_H) {
+ if (async) {
+ entry |= (NLPTR_TYPE_QH << 1);
+ }
+
+ ehci_set_fetch_addr(ehci, async, entry);
+ ehci_set_state(ehci, async, EST_FETCHENTRY);
+ again = 1;
+ goto out;
+ }
+
+ entry = qh.next;
+ if (entry == ehci->asynclistaddr) {
+ break;
+ }
+ }
+
+ /* no head found for list. */
+
+ ehci_set_state(ehci, async, EST_ACTIVE);
+
+out:
+ return again;
+}
+
+
+/* This state is the entry point for periodic schedule processing as
+ * well as being a continuation state for async processing.
+ */
+static int ehci_state_fetchentry(EHCIState *ehci, int async)
+{
+ int again = 0;
+ uint32_t entry = ehci_get_fetch_addr(ehci, async);
+
+ if (NLPTR_TBIT(entry)) {
+ ehci_set_state(ehci, async, EST_ACTIVE);
+ goto out;
+ }
+
+ /* section 4.8, only QH in async schedule */
+ if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) {
+ fprintf(stderr, "non queue head request in async schedule\n");
+ return -1;
+ }
+
+ switch (NLPTR_TYPE_GET(entry)) {
+ case NLPTR_TYPE_QH:
+ ehci_set_state(ehci, async, EST_FETCHQH);
+ again = 1;
+ break;
+
+ case NLPTR_TYPE_ITD:
+ ehci_set_state(ehci, async, EST_FETCHITD);
+ again = 1;
+ break;
+
+ case NLPTR_TYPE_STITD:
+ ehci_set_state(ehci, async, EST_FETCHSITD);
+ again = 1;
+ break;
+
+ default:
+ /* TODO: handle FSTN type */
+ fprintf(stderr, "FETCHENTRY: entry at %X is of type %d "
+ "which is not supported yet\n", entry, NLPTR_TYPE_GET(entry));
+ return -1;
+ }
+
+out:
+ return again;
+}
+
+static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
+{
+ uint32_t entry;
+ EHCIQueue *q;
+ EHCIqh qh;
+
+ entry = ehci_get_fetch_addr(ehci, async);
+ q = ehci_find_queue_by_qh(ehci, entry, async);
+ if (q == NULL) {
+ q = ehci_alloc_queue(ehci, entry, async);
+ }
+
+ q->seen++;
+ if (q->seen > 1) {
+ /* we are going in circles -- stop processing */
+ ehci_set_state(ehci, async, EST_ACTIVE);
+ q = NULL;
+ goto out;
+ }
+
+ if (get_dwords(ehci, NLPTR_GET(q->qhaddr),
+ (uint32_t *) &qh, sizeof(EHCIqh) >> 2) < 0) {
+ q = NULL;
+ goto out;
+ }
+ ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh);
+
+ /*
+ * The overlay area of the qh should never be changed by the guest,
+ * except when idle, in which case the reset is a nop.
+ */
+ if (!ehci_verify_qh(q, &qh)) {
+ if (ehci_reset_queue(q) > 0) {
+ ehci_trace_guest_bug(ehci, "guest updated active QH");
+ }
+ }
+ q->qh = qh;
+
+ q->transact_ctr = get_field(q->qh.epcap, QH_EPCAP_MULT);
+ if (q->transact_ctr == 0) { /* Guest bug in some versions of windows */
+ q->transact_ctr = 4;
+ }
+
+ if (q->dev == NULL) {
+ q->dev = ehci_find_device(q->ehci,
+ get_field(q->qh.epchar, QH_EPCHAR_DEVADDR));
+ }
+
+ if (async && (q->qh.epchar & QH_EPCHAR_H)) {
+
+ /* EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */
+ if (ehci->usbsts & USBSTS_REC) {
+ ehci_clear_usbsts(ehci, USBSTS_REC);
+ } else {
+ DPRINTF("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset"
+ " - done processing\n", q->qhaddr);
+ ehci_set_state(ehci, async, EST_ACTIVE);
+ q = NULL;
+ goto out;
+ }
+ }
+
+#if EHCI_DEBUG
+ if (q->qhaddr != q->qh.next) {
+ DPRINTF("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x\n",
+ q->qhaddr,
+ q->qh.epchar & QH_EPCHAR_H,
+ q->qh.token & QTD_TOKEN_HALT,
+ q->qh.token & QTD_TOKEN_ACTIVE,
+ q->qh.next);
+ }
+#endif
+
+ if (q->qh.token & QTD_TOKEN_HALT) {
+ ehci_set_state(ehci, async, EST_HORIZONTALQH);
+
+ } else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
+ (NLPTR_TBIT(q->qh.current_qtd) == 0)) {
+ q->qtdaddr = q->qh.current_qtd;
+ ehci_set_state(ehci, async, EST_FETCHQTD);
+
+ } else {
+ /* EHCI spec version 1.0 Section 4.10.2 */
+ ehci_set_state(ehci, async, EST_ADVANCEQUEUE);
+ }
+
+out:
+ return q;
+}
+
+static int ehci_state_fetchitd(EHCIState *ehci, int async)
+{
+ uint32_t entry;
+ EHCIitd itd;
+
+ assert(!async);
+ entry = ehci_get_fetch_addr(ehci, async);
+
+ if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
+ sizeof(EHCIitd) >> 2) < 0) {
+ return -1;
+ }
+ ehci_trace_itd(ehci, entry, &itd);
+
+ if (ehci_process_itd(ehci, &itd, entry) != 0) {
+ return -1;
+ }
+
+ put_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
+ sizeof(EHCIitd) >> 2);
+ ehci_set_fetch_addr(ehci, async, itd.next);
+ ehci_set_state(ehci, async, EST_FETCHENTRY);
+
+ return 1;
+}
+
+static int ehci_state_fetchsitd(EHCIState *ehci, int async)
+{
+ uint32_t entry;
+ EHCIsitd sitd;
+
+ assert(!async);
+ entry = ehci_get_fetch_addr(ehci, async);
+
+ if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd,
+ sizeof(EHCIsitd) >> 2) < 0) {
+ return 0;
+ }
+ ehci_trace_sitd(ehci, entry, &sitd);
+
+ if (!(sitd.results & SITD_RESULTS_ACTIVE)) {
+ /* siTD is not active, nothing to do */;
+ } else {
+ /* TODO: split transfers are not implemented */
+ fprintf(stderr, "WARNING: Skipping active siTD\n");
+ }
+
+ ehci_set_fetch_addr(ehci, async, sitd.next);
+ ehci_set_state(ehci, async, EST_FETCHENTRY);
+ return 1;
+}
+
+/* Section 4.10.2 - paragraph 3 */
+static int ehci_state_advqueue(EHCIQueue *q)
+{
+#if 0
+ /* TO-DO: 4.10.2 - paragraph 2
+ * if I-bit is set to 1 and QH is not active
+ * go to horizontal QH
+ */
+ if (I-bit set) {
+ ehci_set_state(ehci, async, EST_HORIZONTALQH);
+ goto out;
+ }
+#endif
+
+ /*
+ * want data and alt-next qTD is valid
+ */
+ if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
+ (NLPTR_TBIT(q->qh.altnext_qtd) == 0)) {
+ q->qtdaddr = q->qh.altnext_qtd;
+ ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
+
+ /*
+ * next qTD is valid
+ */
+ } else if (NLPTR_TBIT(q->qh.next_qtd) == 0) {
+ q->qtdaddr = q->qh.next_qtd;
+ ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
+
+ /*
+ * no valid qTD, try next QH
+ */
+ } else {
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ }
+
+ return 1;
+}
+
+/* Section 4.10.2 - paragraph 4 */
+static int ehci_state_fetchqtd(EHCIQueue *q)
+{
+ EHCIqtd qtd;
+ EHCIPacket *p;
+ int again = 1;
+
+ if (get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &qtd,
+ sizeof(EHCIqtd) >> 2) < 0) {
+ return 0;
+ }
+ ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);
+
+ p = QTAILQ_FIRST(&q->packets);
+ if (p != NULL) {
+ if (!ehci_verify_qtd(p, &qtd)) {
+ ehci_cancel_queue(q);
+ if (qtd.token & QTD_TOKEN_ACTIVE) {
+ ehci_trace_guest_bug(q->ehci, "guest updated active qTD");
+ }
+ p = NULL;
+ } else {
+ p->qtd = qtd;
+ ehci_qh_do_overlay(q);
+ }
+ }
+
+ if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ } else if (p != NULL) {
+ switch (p->async) {
+ case EHCI_ASYNC_NONE:
+ case EHCI_ASYNC_INITIALIZED:
+ /* Not yet executed (MULT), or previously nacked (int) packet */
+ ehci_set_state(q->ehci, q->async, EST_EXECUTE);
+ break;
+ case EHCI_ASYNC_INFLIGHT:
+ /* Check if the guest has added new tds to the queue */
+ again = ehci_fill_queue(QTAILQ_LAST(&q->packets, pkts_head));
+ /* Unfinished async handled packet, go horizontal */
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ break;
+ case EHCI_ASYNC_FINISHED:
+ /* Complete executing of the packet */
+ ehci_set_state(q->ehci, q->async, EST_EXECUTING);
+ break;
+ }
+ } else {
+ p = ehci_alloc_packet(q);
+ p->qtdaddr = q->qtdaddr;
+ p->qtd = qtd;
+ ehci_set_state(q->ehci, q->async, EST_EXECUTE);
+ }
+
+ return again;
+}
+
+static int ehci_state_horizqh(EHCIQueue *q)
+{
+ int again = 0;
+
+ if (ehci_get_fetch_addr(q->ehci, q->async) != q->qh.next) {
+ ehci_set_fetch_addr(q->ehci, q->async, q->qh.next);
+ ehci_set_state(q->ehci, q->async, EST_FETCHENTRY);
+ again = 1;
+ } else {
+ ehci_set_state(q->ehci, q->async, EST_ACTIVE);
+ }
+
+ return again;
+}
+
+/* Returns "again" */
+static int ehci_fill_queue(EHCIPacket *p)
+{
+ USBEndpoint *ep = p->packet.ep;
+ EHCIQueue *q = p->queue;
+ EHCIqtd qtd = p->qtd;
+ uint32_t qtdaddr;
+
+ for (;;) {
+ if (NLPTR_TBIT(qtd.next) != 0) {
+ break;
+ }
+ qtdaddr = qtd.next;
+ /*
+ * Detect circular td lists, Windows creates these, counting on the
+ * active bit going low after execution to make the queue stop.
+ */
+ QTAILQ_FOREACH(p, &q->packets, next) {
+ if (p->qtdaddr == qtdaddr) {
+ goto leave;
+ }
+ }
+ if (get_dwords(q->ehci, NLPTR_GET(qtdaddr),
+ (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2) < 0) {
+ return -1;
+ }
+ ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd);
+ if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
+ break;
+ }
+ if (!ehci_verify_pid(q, &qtd)) {
+ ehci_trace_guest_bug(q->ehci, "guest queued token with wrong pid");
+ break;
+ }
+ p = ehci_alloc_packet(q);
+ p->qtdaddr = qtdaddr;
+ p->qtd = qtd;
+ if (ehci_execute(p, "queue") == -1) {
+ return -1;
+ }
+ assert(p->packet.status == USB_RET_ASYNC);
+ p->async = EHCI_ASYNC_INFLIGHT;
+ }
+leave:
+ usb_device_flush_ep_queue(ep->dev, ep);
+ return 1;
+}
+
+static int ehci_state_execute(EHCIQueue *q)
+{
+ EHCIPacket *p = QTAILQ_FIRST(&q->packets);
+ int again = 0;
+
+ assert(p != NULL);
+ assert(p->qtdaddr == q->qtdaddr);
+
+ if (ehci_qh_do_overlay(q) != 0) {
+ return -1;
+ }
+
+ // TODO verify enough time remains in the uframe as in 4.4.1.1
+ // TODO write back ptr to async list when done or out of time
+
+ /* 4.10.3, bottom of page 82, go horizontal on transaction counter == 0 */
+ if (!q->async && q->transact_ctr == 0) {
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ again = 1;
+ goto out;
+ }
+
+ if (q->async) {
+ ehci_set_usbsts(q->ehci, USBSTS_REC);
+ }
+
+ again = ehci_execute(p, "process");
+ if (again == -1) {
+ goto out;
+ }
+ if (p->packet.status == USB_RET_ASYNC) {
+ ehci_flush_qh(q);
+ trace_usb_ehci_packet_action(p->queue, p, "async");
+ p->async = EHCI_ASYNC_INFLIGHT;
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ if (q->async) {
+ again = ehci_fill_queue(p);
+ } else {
+ again = 1;
+ }
+ goto out;
+ }
+
+ ehci_set_state(q->ehci, q->async, EST_EXECUTING);
+ again = 1;
+
+out:
+ return again;
+}
+
+static int ehci_state_executing(EHCIQueue *q)
+{
+ EHCIPacket *p = QTAILQ_FIRST(&q->packets);
+
+ assert(p != NULL);
+ assert(p->qtdaddr == q->qtdaddr);
+
+ ehci_execute_complete(q);
+
+ /* 4.10.3 */
+ if (!q->async && q->transact_ctr > 0) {
+ q->transact_ctr--;
+ }
+
+ /* 4.10.5 */
+ if (p->packet.status == USB_RET_NAK) {
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ } else {
+ ehci_set_state(q->ehci, q->async, EST_WRITEBACK);
+ }
+
+ ehci_flush_qh(q);
+ return 1;
+}
+
+
+static int ehci_state_writeback(EHCIQueue *q)
+{
+ EHCIPacket *p = QTAILQ_FIRST(&q->packets);
+ uint32_t *qtd, addr;
+ int again = 0;
+
+ /* Write back the QTD from the QH area */
+ assert(p != NULL);
+ assert(p->qtdaddr == q->qtdaddr);
+
+ ehci_trace_qtd(q, NLPTR_GET(p->qtdaddr), (EHCIqtd *) &q->qh.next_qtd);
+ qtd = (uint32_t *) &q->qh.next_qtd;
+ addr = NLPTR_GET(p->qtdaddr);
+ put_dwords(q->ehci, addr + 2 * sizeof(uint32_t), qtd + 2, 2);
+ ehci_free_packet(p);
+
+ /*
+ * EHCI specs say go horizontal here.
+ *
+ * We can also advance the queue here for performance reasons. We
+ * need to take care to only take that shortcut in case we've
+ * processed the qtd just written back without errors, i.e. halt
+ * bit is clear.
+ */
+ if (q->qh.token & QTD_TOKEN_HALT) {
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ again = 1;
+ } else {
+ ehci_set_state(q->ehci, q->async, EST_ADVANCEQUEUE);
+ again = 1;
+ }
+ return again;
+}
+
+/*
+ * This is the state machine that is common to both async and periodic
+ */
+
+static void ehci_advance_state(EHCIState *ehci, int async)
+{
+ EHCIQueue *q = NULL;
+ int again;
+
+ do {
+ switch(ehci_get_state(ehci, async)) {
+ case EST_WAITLISTHEAD:
+ again = ehci_state_waitlisthead(ehci, async);
+ break;
+
+ case EST_FETCHENTRY:
+ again = ehci_state_fetchentry(ehci, async);
+ break;
+
+ case EST_FETCHQH:
+ q = ehci_state_fetchqh(ehci, async);
+ if (q != NULL) {
+ assert(q->async == async);
+ again = 1;
+ } else {
+ again = 0;
+ }
+ break;
+
+ case EST_FETCHITD:
+ again = ehci_state_fetchitd(ehci, async);
+ break;
+
+ case EST_FETCHSITD:
+ again = ehci_state_fetchsitd(ehci, async);
+ break;
+
+ case EST_ADVANCEQUEUE:
+ assert(q != NULL);
+ again = ehci_state_advqueue(q);
+ break;
+
+ case EST_FETCHQTD:
+ assert(q != NULL);
+ again = ehci_state_fetchqtd(q);
+ break;
+
+ case EST_HORIZONTALQH:
+ assert(q != NULL);
+ again = ehci_state_horizqh(q);
+ break;
+
+ case EST_EXECUTE:
+ assert(q != NULL);
+ again = ehci_state_execute(q);
+ if (async) {
+ ehci->async_stepdown = 0;
+ }
+ break;
+
+ case EST_EXECUTING:
+ assert(q != NULL);
+ if (async) {
+ ehci->async_stepdown = 0;
+ }
+ again = ehci_state_executing(q);
+ break;
+
+ case EST_WRITEBACK:
+ assert(q != NULL);
+ again = ehci_state_writeback(q);
+ if (!async) {
+ ehci->periodic_sched_active = PERIODIC_ACTIVE;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "Bad state!\n");
+ again = -1;
+ g_assert_not_reached();
+ break;
+ }
+
+ if (again < 0) {
+ fprintf(stderr, "processing error - resetting ehci HC\n");
+ ehci_reset(ehci);
+ again = 0;
+ }
+ }
+ while (again);
+}
+
+static void ehci_advance_async_state(EHCIState *ehci)
+{
+ const int async = 1;
+
+ switch(ehci_get_state(ehci, async)) {
+ case EST_INACTIVE:
+ if (!ehci_async_enabled(ehci)) {
+ break;
+ }
+ ehci_set_state(ehci, async, EST_ACTIVE);
+ // No break, fall through to ACTIVE
+
+ case EST_ACTIVE:
+ if (!ehci_async_enabled(ehci)) {
+ ehci_queues_rip_all(ehci, async);
+ ehci_set_state(ehci, async, EST_INACTIVE);
+ break;
+ }
+
+ /* make sure guest has acknowledged the doorbell interrupt */
+ /* TO-DO: is this really needed? */
+ if (ehci->usbsts & USBSTS_IAA) {
+ DPRINTF("IAA status bit still set.\n");
+ break;
+ }
+
+ /* check that address register has been set */
+ if (ehci->asynclistaddr == 0) {
+ break;
+ }
+
+ ehci_set_state(ehci, async, EST_WAITLISTHEAD);
+ ehci_advance_state(ehci, async);
+
+ /* If the doorbell is set, the guest wants to make a change to the
+ * schedule. The host controller needs to release cached data.
+ * (section 4.8.2)
+ */
+ if (ehci->usbcmd & USBCMD_IAAD) {
+ /* Remove all unseen qhs from the async qhs queue */
+ ehci_queues_rip_unseen(ehci, async);
+ trace_usb_ehci_doorbell_ack();
+ ehci->usbcmd &= ~USBCMD_IAAD;
+ ehci_raise_irq(ehci, USBSTS_IAA);
+ }
+ break;
+
+ default:
+ /* this should only be due to a developer mistake */
+ fprintf(stderr, "ehci: Bad asynchronous state %d. "
+ "Resetting to active\n", ehci->astate);
+ g_assert_not_reached();
+ }
+}
+
+static void ehci_advance_periodic_state(EHCIState *ehci)
+{
+ uint32_t entry;
+ uint32_t list;
+ const int async = 0;
+
+ // 4.6
+
+ switch(ehci_get_state(ehci, async)) {
+ case EST_INACTIVE:
+ if (!(ehci->frindex & 7) && ehci_periodic_enabled(ehci)) {
+ ehci_set_state(ehci, async, EST_ACTIVE);
+ // No break, fall through to ACTIVE
+ } else
+ break;
+
+ case EST_ACTIVE:
+ if (!(ehci->frindex & 7) && !ehci_periodic_enabled(ehci)) {
+ ehci_queues_rip_all(ehci, async);
+ ehci_set_state(ehci, async, EST_INACTIVE);
+ break;
+ }
+
+ list = ehci->periodiclistbase & 0xfffff000;
+ /* check that register has been set */
+ if (list == 0) {
+ break;
+ }
+ list |= ((ehci->frindex & 0x1ff8) >> 1);
+
+ if (get_dwords(ehci, list, &entry, 1) < 0) {
+ break;
+ }
+
+ DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n",
+ ehci->frindex / 8, list, entry);
+ ehci_set_fetch_addr(ehci, async,entry);
+ ehci_set_state(ehci, async, EST_FETCHENTRY);
+ ehci_advance_state(ehci, async);
+ ehci_queues_rip_unused(ehci, async);
+ break;
+
+ default:
+ /* this should only be due to a developer mistake */
+ fprintf(stderr, "ehci: Bad periodic state %d. "
+ "Resetting to active\n", ehci->pstate);
+ g_assert_not_reached();
+ }
+}
+
+static void ehci_update_frindex(EHCIState *ehci, int uframes)
+{
+ int i;
+
+ if (!ehci_enabled(ehci) && ehci->pstate == EST_INACTIVE) {
+ return;
+ }
+
+ for (i = 0; i < uframes; i++) {
+ ehci->frindex++;
+
+ if (ehci->frindex == 0x00002000) {
+ ehci_raise_irq(ehci, USBSTS_FLR);
+ }
+
+ if (ehci->frindex == 0x00004000) {
+ ehci_raise_irq(ehci, USBSTS_FLR);
+ ehci->frindex = 0;
+ if (ehci->usbsts_frindex >= 0x00004000) {
+ ehci->usbsts_frindex -= 0x00004000;
+ } else {
+ ehci->usbsts_frindex = 0;
+ }
+ }
+ }
+}
+
+static void ehci_frame_timer(void *opaque)
+{
+ EHCIState *ehci = opaque;
+ int need_timer = 0;
+ int64_t expire_time, t_now;
+ uint64_t ns_elapsed;
+ int uframes, skipped_uframes;
+ int i;
+
+ t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ns_elapsed = t_now - ehci->last_run_ns;
+ uframes = ns_elapsed / UFRAME_TIMER_NS;
+
+ if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) {
+ need_timer++;
+
+ if (uframes > (ehci->maxframes * 8)) {
+ skipped_uframes = uframes - (ehci->maxframes * 8);
+ ehci_update_frindex(ehci, skipped_uframes);
+ ehci->last_run_ns += UFRAME_TIMER_NS * skipped_uframes;
+ uframes -= skipped_uframes;
+ DPRINTF("WARNING - EHCI skipped %d uframes\n", skipped_uframes);
+ }
+
+ for (i = 0; i < uframes; i++) {
+ /*
+ * If we're running behind schedule, we should not catch up
+ * too fast, as that will make some guests unhappy:
+ * 1) We must process a minimum of MIN_UFR_PER_TICK frames,
+ * otherwise we will never catch up
+ * 2) Process frames until the guest has requested an irq (IOC)
+ */
+ if (i >= MIN_UFR_PER_TICK) {
+ ehci_commit_irq(ehci);
+ if ((ehci->usbsts & USBINTR_MASK) & ehci->usbintr) {
+ break;
+ }
+ }
+ if (ehci->periodic_sched_active) {
+ ehci->periodic_sched_active--;
+ }
+ ehci_update_frindex(ehci, 1);
+ if ((ehci->frindex & 7) == 0) {
+ ehci_advance_periodic_state(ehci);
+ }
+ ehci->last_run_ns += UFRAME_TIMER_NS;
+ }
+ } else {
+ ehci->periodic_sched_active = 0;
+ ehci_update_frindex(ehci, uframes);
+ ehci->last_run_ns += UFRAME_TIMER_NS * uframes;
+ }
+
+ if (ehci->periodic_sched_active) {
+ ehci->async_stepdown = 0;
+ } else if (ehci->async_stepdown < ehci->maxframes / 2) {
+ ehci->async_stepdown++;
+ }
+
+ /* Async is not inside loop since it executes everything it can once
+ * called
+ */
+ if (ehci_async_enabled(ehci) || ehci->astate != EST_INACTIVE) {
+ need_timer++;
+ ehci_advance_async_state(ehci);
+ }
+
+ ehci_commit_irq(ehci);
+ if (ehci->usbsts_pending) {
+ need_timer++;
+ ehci->async_stepdown = 0;
+ }
+
+ if (ehci_enabled(ehci) && (ehci->usbintr & USBSTS_FLR)) {
+ need_timer++;
+ }
+
+ if (need_timer) {
+ /* If we've raised int, we speed up the timer, so that we quickly
+ * notice any new packets queued up in response */
+ if (ehci->int_req_by_async && (ehci->usbsts & USBSTS_INT)) {
+ expire_time = t_now + get_ticks_per_sec() / (FRAME_TIMER_FREQ * 4);
+ ehci->int_req_by_async = false;
+ } else {
+ expire_time = t_now + (get_ticks_per_sec()
+ * (ehci->async_stepdown+1) / FRAME_TIMER_FREQ);
+ }
+ timer_mod(ehci->frame_timer, expire_time);
+ }
+}
+
+static const MemoryRegionOps ehci_mmio_caps_ops = {
+ .read = ehci_caps_read,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps ehci_mmio_opreg_ops = {
+ .read = ehci_opreg_read,
+ .write = ehci_opreg_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps ehci_mmio_port_ops = {
+ .read = ehci_port_read,
+ .write = ehci_port_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static USBPortOps ehci_port_ops = {
+ .attach = ehci_attach,
+ .detach = ehci_detach,
+ .child_detach = ehci_child_detach,
+ .wakeup = ehci_wakeup,
+ .complete = ehci_async_complete_packet,
+};
+
+static USBBusOps ehci_bus_ops_companion = {
+ .register_companion = ehci_register_companion,
+ .wakeup_endpoint = ehci_wakeup_endpoint,
+};
+static USBBusOps ehci_bus_ops_standalone = {
+ .wakeup_endpoint = ehci_wakeup_endpoint,
+};
+
+static void usb_ehci_pre_save(void *opaque)
+{
+ EHCIState *ehci = opaque;
+ uint32_t new_frindex;
+
+ /* Round down frindex to a multiple of 8 for migration compatibility */
+ new_frindex = ehci->frindex & ~7;
+ ehci->last_run_ns -= (ehci->frindex - new_frindex) * UFRAME_TIMER_NS;
+ ehci->frindex = new_frindex;
+}
+
+static int usb_ehci_post_load(void *opaque, int version_id)
+{
+ EHCIState *s = opaque;
+ int i;
+
+ for (i = 0; i < NB_PORTS; i++) {
+ USBPort *companion = s->companion_ports[i];
+ if (companion == NULL) {
+ continue;
+ }
+ if (s->portsc[i] & PORTSC_POWNER) {
+ companion->dev = s->ports[i].dev;
+ } else {
+ companion->dev = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static void usb_ehci_vm_state_change(void *opaque, int running, RunState state)
+{
+ EHCIState *ehci = opaque;
+
+ /*
+ * We don't migrate the EHCIQueue-s, instead we rebuild them for the
+ * schedule in guest memory. We must do the rebuilt ASAP, so that
+ * USB-devices which have async handled packages have a packet in the
+ * ep queue to match the completion with.
+ */
+ if (state == RUN_STATE_RUNNING) {
+ ehci_advance_async_state(ehci);
+ }
+
+ /*
+ * The schedule rebuilt from guest memory could cause the migration dest
+ * to miss a QH unlink, and fail to cancel packets, since the unlinked QH
+ * will never have existed on the destination. Therefor we must flush the
+ * async schedule on savevm to catch any not yet noticed unlinks.
+ */
+ if (state == RUN_STATE_SAVE_VM) {
+ ehci_advance_async_state(ehci);
+ ehci_queues_rip_unseen(ehci, 1);
+ }
+}
+
+const VMStateDescription vmstate_ehci = {
+ .name = "ehci-core",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .pre_save = usb_ehci_pre_save,
+ .post_load = usb_ehci_post_load,
+ .fields = (VMStateField[]) {
+ /* mmio registers */
+ VMSTATE_UINT32(usbcmd, EHCIState),
+ VMSTATE_UINT32(usbsts, EHCIState),
+ VMSTATE_UINT32_V(usbsts_pending, EHCIState, 2),
+ VMSTATE_UINT32_V(usbsts_frindex, EHCIState, 2),
+ VMSTATE_UINT32(usbintr, EHCIState),
+ VMSTATE_UINT32(frindex, EHCIState),
+ VMSTATE_UINT32(ctrldssegment, EHCIState),
+ VMSTATE_UINT32(periodiclistbase, EHCIState),
+ VMSTATE_UINT32(asynclistaddr, EHCIState),
+ VMSTATE_UINT32(configflag, EHCIState),
+ VMSTATE_UINT32(portsc[0], EHCIState),
+ VMSTATE_UINT32(portsc[1], EHCIState),
+ VMSTATE_UINT32(portsc[2], EHCIState),
+ VMSTATE_UINT32(portsc[3], EHCIState),
+ VMSTATE_UINT32(portsc[4], EHCIState),
+ VMSTATE_UINT32(portsc[5], EHCIState),
+ /* frame timer */
+ VMSTATE_TIMER_PTR(frame_timer, EHCIState),
+ VMSTATE_UINT64(last_run_ns, EHCIState),
+ VMSTATE_UINT32(async_stepdown, EHCIState),
+ /* schedule state */
+ VMSTATE_UINT32(astate, EHCIState),
+ VMSTATE_UINT32(pstate, EHCIState),
+ VMSTATE_UINT32(a_fetch_addr, EHCIState),
+ VMSTATE_UINT32(p_fetch_addr, EHCIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+void usb_ehci_realize(EHCIState *s, DeviceState *dev, Error **errp)
+{
+ int i;
+
+ if (s->portnr > NB_PORTS) {
+ error_setg(errp, "Too many ports! Max. port number is %d.",
+ NB_PORTS);
+ return;
+ }
+
+ usb_bus_new(&s->bus, sizeof(s->bus), s->companion_enable ?
+ &ehci_bus_ops_companion : &ehci_bus_ops_standalone, dev);
+ for (i = 0; i < s->portnr; i++) {
+ usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops,
+ USB_SPEED_MASK_HIGH);
+ s->ports[i].dev = 0;
+ }
+
+ s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ehci_frame_timer, s);
+ s->async_bh = qemu_bh_new(ehci_frame_timer, s);
+ s->device = dev;
+
+ s->vmstate = qemu_add_vm_change_state_handler(usb_ehci_vm_state_change, s);
+}
+
+void usb_ehci_unrealize(EHCIState *s, DeviceState *dev, Error **errp)
+{
+ trace_usb_ehci_unrealize();
+
+ if (s->frame_timer) {
+ timer_del(s->frame_timer);
+ timer_free(s->frame_timer);
+ s->frame_timer = NULL;
+ }
+ if (s->async_bh) {
+ qemu_bh_delete(s->async_bh);
+ }
+
+ ehci_queues_rip_all(s, 0);
+ ehci_queues_rip_all(s, 1);
+
+ memory_region_del_subregion(&s->mem, &s->mem_caps);
+ memory_region_del_subregion(&s->mem, &s->mem_opreg);
+ memory_region_del_subregion(&s->mem, &s->mem_ports);
+
+ usb_bus_release(&s->bus);
+
+ if (s->vmstate) {
+ qemu_del_vm_change_state_handler(s->vmstate);
+ }
+}
+
+void usb_ehci_init(EHCIState *s, DeviceState *dev)
+{
+ /* 2.2 host controller interface version */
+ s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase);
+ s->caps[0x01] = 0x00;
+ s->caps[0x02] = 0x00;
+ s->caps[0x03] = 0x01; /* HC version */
+ s->caps[0x04] = s->portnr; /* Number of downstream ports */
+ s->caps[0x05] = 0x00; /* No companion ports at present */
+ s->caps[0x06] = 0x00;
+ s->caps[0x07] = 0x00;
+ s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */
+ s->caps[0x0a] = 0x00;
+ s->caps[0x0b] = 0x00;
+
+ QTAILQ_INIT(&s->aqueues);
+ QTAILQ_INIT(&s->pqueues);
+ usb_packet_init(&s->ipacket);
+
+ memory_region_init(&s->mem, OBJECT(dev), "ehci", MMIO_SIZE);
+ memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s,
+ "capabilities", CAPA_SIZE);
+ memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s,
+ "operational", s->portscbase);
+ memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s,
+ "ports", 4 * s->portnr);
+
+ memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps);
+ memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg);
+ memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase,
+ &s->mem_ports);
+}
+
+/*
+ * vim: expandtab ts=4
+ */
diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
new file mode 100644
index 00000000..30218423
--- /dev/null
+++ b/hw/usb/hcd-ehci.h
@@ -0,0 +1,383 @@
+/*
+ * QEMU USB EHCI Emulation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or(at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef HW_USB_EHCI_H
+#define HW_USB_EHCI_H 1
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/usb.h"
+#include "sysemu/dma.h"
+#include "sysemu/sysemu.h"
+#include "hw/pci/pci.h"
+#include "hw/sysbus.h"
+
+#ifndef EHCI_DEBUG
+#define EHCI_DEBUG 0
+#endif
+
+#if EHCI_DEBUG
+#define DPRINTF printf
+#else
+#define DPRINTF(...)
+#endif
+
+#define MMIO_SIZE 0x1000
+#define CAPA_SIZE 0x10
+
+#define NB_PORTS 6 /* Max. Number of downstream ports */
+
+typedef struct EHCIPacket EHCIPacket;
+typedef struct EHCIQueue EHCIQueue;
+typedef struct EHCIState EHCIState;
+
+/* EHCI spec version 1.0 Section 3.3
+ */
+typedef struct EHCIitd {
+ uint32_t next;
+
+ uint32_t transact[8];
+#define ITD_XACT_ACTIVE (1 << 31)
+#define ITD_XACT_DBERROR (1 << 30)
+#define ITD_XACT_BABBLE (1 << 29)
+#define ITD_XACT_XACTERR (1 << 28)
+#define ITD_XACT_LENGTH_MASK 0x0fff0000
+#define ITD_XACT_LENGTH_SH 16
+#define ITD_XACT_IOC (1 << 15)
+#define ITD_XACT_PGSEL_MASK 0x00007000
+#define ITD_XACT_PGSEL_SH 12
+#define ITD_XACT_OFFSET_MASK 0x00000fff
+
+ uint32_t bufptr[7];
+#define ITD_BUFPTR_MASK 0xfffff000
+#define ITD_BUFPTR_SH 12
+#define ITD_BUFPTR_EP_MASK 0x00000f00
+#define ITD_BUFPTR_EP_SH 8
+#define ITD_BUFPTR_DEVADDR_MASK 0x0000007f
+#define ITD_BUFPTR_DEVADDR_SH 0
+#define ITD_BUFPTR_DIRECTION (1 << 11)
+#define ITD_BUFPTR_MAXPKT_MASK 0x000007ff
+#define ITD_BUFPTR_MAXPKT_SH 0
+#define ITD_BUFPTR_MULT_MASK 0x00000003
+#define ITD_BUFPTR_MULT_SH 0
+} EHCIitd;
+
+/* EHCI spec version 1.0 Section 3.4
+ */
+typedef struct EHCIsitd {
+ uint32_t next; /* Standard next link pointer */
+ uint32_t epchar;
+#define SITD_EPCHAR_IO (1 << 31)
+#define SITD_EPCHAR_PORTNUM_MASK 0x7f000000
+#define SITD_EPCHAR_PORTNUM_SH 24
+#define SITD_EPCHAR_HUBADD_MASK 0x007f0000
+#define SITD_EPCHAR_HUBADDR_SH 16
+#define SITD_EPCHAR_EPNUM_MASK 0x00000f00
+#define SITD_EPCHAR_EPNUM_SH 8
+#define SITD_EPCHAR_DEVADDR_MASK 0x0000007f
+
+ uint32_t uframe;
+#define SITD_UFRAME_CMASK_MASK 0x0000ff00
+#define SITD_UFRAME_CMASK_SH 8
+#define SITD_UFRAME_SMASK_MASK 0x000000ff
+
+ uint32_t results;
+#define SITD_RESULTS_IOC (1 << 31)
+#define SITD_RESULTS_PGSEL (1 << 30)
+#define SITD_RESULTS_TBYTES_MASK 0x03ff0000
+#define SITD_RESULTS_TYBYTES_SH 16
+#define SITD_RESULTS_CPROGMASK_MASK 0x0000ff00
+#define SITD_RESULTS_CPROGMASK_SH 8
+#define SITD_RESULTS_ACTIVE (1 << 7)
+#define SITD_RESULTS_ERR (1 << 6)
+#define SITD_RESULTS_DBERR (1 << 5)
+#define SITD_RESULTS_BABBLE (1 << 4)
+#define SITD_RESULTS_XACTERR (1 << 3)
+#define SITD_RESULTS_MISSEDUF (1 << 2)
+#define SITD_RESULTS_SPLITXSTATE (1 << 1)
+
+ uint32_t bufptr[2];
+#define SITD_BUFPTR_MASK 0xfffff000
+#define SITD_BUFPTR_CURROFF_MASK 0x00000fff
+#define SITD_BUFPTR_TPOS_MASK 0x00000018
+#define SITD_BUFPTR_TPOS_SH 3
+#define SITD_BUFPTR_TCNT_MASK 0x00000007
+
+ uint32_t backptr; /* Standard next link pointer */
+} EHCIsitd;
+
+/* EHCI spec version 1.0 Section 3.5
+ */
+typedef struct EHCIqtd {
+ uint32_t next; /* Standard next link pointer */
+ uint32_t altnext; /* Standard next link pointer */
+ uint32_t token;
+#define QTD_TOKEN_DTOGGLE (1 << 31)
+#define QTD_TOKEN_TBYTES_MASK 0x7fff0000
+#define QTD_TOKEN_TBYTES_SH 16
+#define QTD_TOKEN_IOC (1 << 15)
+#define QTD_TOKEN_CPAGE_MASK 0x00007000
+#define QTD_TOKEN_CPAGE_SH 12
+#define QTD_TOKEN_CERR_MASK 0x00000c00
+#define QTD_TOKEN_CERR_SH 10
+#define QTD_TOKEN_PID_MASK 0x00000300
+#define QTD_TOKEN_PID_SH 8
+#define QTD_TOKEN_ACTIVE (1 << 7)
+#define QTD_TOKEN_HALT (1 << 6)
+#define QTD_TOKEN_DBERR (1 << 5)
+#define QTD_TOKEN_BABBLE (1 << 4)
+#define QTD_TOKEN_XACTERR (1 << 3)
+#define QTD_TOKEN_MISSEDUF (1 << 2)
+#define QTD_TOKEN_SPLITXSTATE (1 << 1)
+#define QTD_TOKEN_PING (1 << 0)
+
+ uint32_t bufptr[5]; /* Standard buffer pointer */
+#define QTD_BUFPTR_MASK 0xfffff000
+#define QTD_BUFPTR_SH 12
+} EHCIqtd;
+
+/* EHCI spec version 1.0 Section 3.6
+ */
+typedef struct EHCIqh {
+ uint32_t next; /* Standard next link pointer */
+
+ /* endpoint characteristics */
+ uint32_t epchar;
+#define QH_EPCHAR_RL_MASK 0xf0000000
+#define QH_EPCHAR_RL_SH 28
+#define QH_EPCHAR_C (1 << 27)
+#define QH_EPCHAR_MPLEN_MASK 0x07FF0000
+#define QH_EPCHAR_MPLEN_SH 16
+#define QH_EPCHAR_H (1 << 15)
+#define QH_EPCHAR_DTC (1 << 14)
+#define QH_EPCHAR_EPS_MASK 0x00003000
+#define QH_EPCHAR_EPS_SH 12
+#define EHCI_QH_EPS_FULL 0
+#define EHCI_QH_EPS_LOW 1
+#define EHCI_QH_EPS_HIGH 2
+#define EHCI_QH_EPS_RESERVED 3
+
+#define QH_EPCHAR_EP_MASK 0x00000f00
+#define QH_EPCHAR_EP_SH 8
+#define QH_EPCHAR_I (1 << 7)
+#define QH_EPCHAR_DEVADDR_MASK 0x0000007f
+#define QH_EPCHAR_DEVADDR_SH 0
+
+ /* endpoint capabilities */
+ uint32_t epcap;
+#define QH_EPCAP_MULT_MASK 0xc0000000
+#define QH_EPCAP_MULT_SH 30
+#define QH_EPCAP_PORTNUM_MASK 0x3f800000
+#define QH_EPCAP_PORTNUM_SH 23
+#define QH_EPCAP_HUBADDR_MASK 0x007f0000
+#define QH_EPCAP_HUBADDR_SH 16
+#define QH_EPCAP_CMASK_MASK 0x0000ff00
+#define QH_EPCAP_CMASK_SH 8
+#define QH_EPCAP_SMASK_MASK 0x000000ff
+#define QH_EPCAP_SMASK_SH 0
+
+ uint32_t current_qtd; /* Standard next link pointer */
+ uint32_t next_qtd; /* Standard next link pointer */
+ uint32_t altnext_qtd;
+#define QH_ALTNEXT_NAKCNT_MASK 0x0000001e
+#define QH_ALTNEXT_NAKCNT_SH 1
+
+ uint32_t token; /* Same as QTD token */
+ uint32_t bufptr[5]; /* Standard buffer pointer */
+#define BUFPTR_CPROGMASK_MASK 0x000000ff
+#define BUFPTR_FRAMETAG_MASK 0x0000001f
+#define BUFPTR_SBYTES_MASK 0x00000fe0
+#define BUFPTR_SBYTES_SH 5
+} EHCIqh;
+
+/* EHCI spec version 1.0 Section 3.7
+ */
+typedef struct EHCIfstn {
+ uint32_t next; /* Standard next link pointer */
+ uint32_t backptr; /* Standard next link pointer */
+} EHCIfstn;
+
+enum async_state {
+ EHCI_ASYNC_NONE = 0,
+ EHCI_ASYNC_INITIALIZED,
+ EHCI_ASYNC_INFLIGHT,
+ EHCI_ASYNC_FINISHED,
+};
+
+struct EHCIPacket {
+ EHCIQueue *queue;
+ QTAILQ_ENTRY(EHCIPacket) next;
+
+ EHCIqtd qtd; /* copy of current QTD (being worked on) */
+ uint32_t qtdaddr; /* address QTD read from */
+
+ USBPacket packet;
+ QEMUSGList sgl;
+ int pid;
+ enum async_state async;
+};
+
+struct EHCIQueue {
+ EHCIState *ehci;
+ QTAILQ_ENTRY(EHCIQueue) next;
+ uint32_t seen;
+ uint64_t ts;
+ int async;
+ int transact_ctr;
+
+ /* cached data from guest - needs to be flushed
+ * when guest removes an entry (doorbell, handshake sequence)
+ */
+ EHCIqh qh; /* copy of current QH (being worked on) */
+ uint32_t qhaddr; /* address QH read from */
+ uint32_t qtdaddr; /* address QTD read from */
+ int last_pid; /* pid of last packet executed */
+ USBDevice *dev;
+ QTAILQ_HEAD(pkts_head, EHCIPacket) packets;
+};
+
+typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead;
+
+struct EHCIState {
+ USBBus bus;
+ DeviceState *device;
+ qemu_irq irq;
+ MemoryRegion mem;
+ AddressSpace *as;
+ MemoryRegion mem_caps;
+ MemoryRegion mem_opreg;
+ MemoryRegion mem_ports;
+ int companion_count;
+ bool companion_enable;
+ uint16_t capsbase;
+ uint16_t opregbase;
+ uint16_t portscbase;
+ uint16_t portnr;
+
+ /* properties */
+ uint32_t maxframes;
+
+ /*
+ * EHCI spec version 1.0 Section 2.3
+ * Host Controller Operational Registers
+ */
+ uint8_t caps[CAPA_SIZE];
+ union {
+ uint32_t opreg[0x44/sizeof(uint32_t)];
+ struct {
+ uint32_t usbcmd;
+ uint32_t usbsts;
+ uint32_t usbintr;
+ uint32_t frindex;
+ uint32_t ctrldssegment;
+ uint32_t periodiclistbase;
+ uint32_t asynclistaddr;
+ uint32_t notused[9];
+ uint32_t configflag;
+ };
+ };
+ uint32_t portsc[NB_PORTS];
+
+ /*
+ * Internal states, shadow registers, etc
+ */
+ QEMUTimer *frame_timer;
+ QEMUBH *async_bh;
+ uint32_t astate; /* Current state in asynchronous schedule */
+ uint32_t pstate; /* Current state in periodic schedule */
+ USBPort ports[NB_PORTS];
+ USBPort *companion_ports[NB_PORTS];
+ uint32_t usbsts_pending;
+ uint32_t usbsts_frindex;
+ EHCIQueueHead aqueues;
+ EHCIQueueHead pqueues;
+
+ /* which address to look at next */
+ uint32_t a_fetch_addr;
+ uint32_t p_fetch_addr;
+
+ USBPacket ipacket;
+ QEMUSGList isgl;
+
+ uint64_t last_run_ns;
+ uint32_t async_stepdown;
+ uint32_t periodic_sched_active;
+ bool int_req_by_async;
+ VMChangeStateEntry *vmstate;
+};
+
+extern const VMStateDescription vmstate_ehci;
+
+void usb_ehci_init(EHCIState *s, DeviceState *dev);
+void usb_ehci_realize(EHCIState *s, DeviceState *dev, Error **errp);
+void usb_ehci_unrealize(EHCIState *s, DeviceState *dev, Error **errp);
+void ehci_reset(void *opaque);
+
+#define TYPE_PCI_EHCI "pci-ehci-usb"
+#define PCI_EHCI(obj) OBJECT_CHECK(EHCIPCIState, (obj), TYPE_PCI_EHCI)
+
+typedef struct EHCIPCIState {
+ /*< private >*/
+ PCIDevice pcidev;
+ /*< public >*/
+
+ EHCIState ehci;
+} EHCIPCIState;
+
+
+#define TYPE_SYS_BUS_EHCI "sysbus-ehci-usb"
+#define TYPE_EXYNOS4210_EHCI "exynos4210-ehci-usb"
+#define TYPE_TEGRA2_EHCI "tegra2-ehci-usb"
+#define TYPE_FUSBH200_EHCI "fusbh200-ehci-usb"
+
+#define SYS_BUS_EHCI(obj) \
+ OBJECT_CHECK(EHCISysBusState, (obj), TYPE_SYS_BUS_EHCI)
+#define SYS_BUS_EHCI_CLASS(class) \
+ OBJECT_CLASS_CHECK(SysBusEHCIClass, (class), TYPE_SYS_BUS_EHCI)
+#define SYS_BUS_EHCI_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(SysBusEHCIClass, (obj), TYPE_SYS_BUS_EHCI)
+
+typedef struct EHCISysBusState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ EHCIState ehci;
+} EHCISysBusState;
+
+typedef struct SysBusEHCIClass {
+ /*< private >*/
+ SysBusDeviceClass parent_class;
+ /*< public >*/
+
+ uint16_t capsbase;
+ uint16_t opregbase;
+ uint16_t portscbase;
+ uint16_t portnr;
+} SysBusEHCIClass;
+
+#define FUSBH200_EHCI(obj) \
+ OBJECT_CHECK(FUSBH200EHCIState, (obj), TYPE_FUSBH200_EHCI)
+
+typedef struct FUSBH200EHCIState {
+ /*< private >*/
+ EHCISysBusState parent_obj;
+ /*< public >*/
+
+ MemoryRegion mem_vendor;
+} FUSBH200EHCIState;
+
+#endif
diff --git a/hw/usb/hcd-musb.c b/hw/usb/hcd-musb.c
new file mode 100644
index 00000000..61cc8789
--- /dev/null
+++ b/hw/usb/hcd-musb.c
@@ -0,0 +1,1552 @@
+/*
+ * "Inventra" High-speed Dual-Role Controller (MUSB-HDRC), Mentor Graphics,
+ * USB2.0 OTG compliant core used in various chips.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Only host-mode and non-DMA accesses are currently supported.
+ */
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "hw/usb.h"
+#include "hw/irq.h"
+#include "hw/hw.h"
+
+/* Common USB registers */
+#define MUSB_HDRC_FADDR 0x00 /* 8-bit */
+#define MUSB_HDRC_POWER 0x01 /* 8-bit */
+
+#define MUSB_HDRC_INTRTX 0x02 /* 16-bit */
+#define MUSB_HDRC_INTRRX 0x04
+#define MUSB_HDRC_INTRTXE 0x06
+#define MUSB_HDRC_INTRRXE 0x08
+#define MUSB_HDRC_INTRUSB 0x0a /* 8 bit */
+#define MUSB_HDRC_INTRUSBE 0x0b /* 8 bit */
+#define MUSB_HDRC_FRAME 0x0c /* 16-bit */
+#define MUSB_HDRC_INDEX 0x0e /* 8 bit */
+#define MUSB_HDRC_TESTMODE 0x0f /* 8 bit */
+
+/* Per-EP registers in indexed mode */
+#define MUSB_HDRC_EP_IDX 0x10 /* 8-bit */
+
+/* EP FIFOs */
+#define MUSB_HDRC_FIFO 0x20
+
+/* Additional Control Registers */
+#define MUSB_HDRC_DEVCTL 0x60 /* 8 bit */
+
+/* These are indexed */
+#define MUSB_HDRC_TXFIFOSZ 0x62 /* 8 bit (see masks) */
+#define MUSB_HDRC_RXFIFOSZ 0x63 /* 8 bit (see masks) */
+#define MUSB_HDRC_TXFIFOADDR 0x64 /* 16 bit offset shifted right 3 */
+#define MUSB_HDRC_RXFIFOADDR 0x66 /* 16 bit offset shifted right 3 */
+
+/* Some more registers */
+#define MUSB_HDRC_VCTRL 0x68 /* 8 bit */
+#define MUSB_HDRC_HWVERS 0x6c /* 8 bit */
+
+/* Added in HDRC 1.9(?) & MHDRC 1.4 */
+/* ULPI pass-through */
+#define MUSB_HDRC_ULPI_VBUSCTL 0x70
+#define MUSB_HDRC_ULPI_REGDATA 0x74
+#define MUSB_HDRC_ULPI_REGADDR 0x75
+#define MUSB_HDRC_ULPI_REGCTL 0x76
+
+/* Extended config & PHY control */
+#define MUSB_HDRC_ENDCOUNT 0x78 /* 8 bit */
+#define MUSB_HDRC_DMARAMCFG 0x79 /* 8 bit */
+#define MUSB_HDRC_PHYWAIT 0x7a /* 8 bit */
+#define MUSB_HDRC_PHYVPLEN 0x7b /* 8 bit */
+#define MUSB_HDRC_HS_EOF1 0x7c /* 8 bit, units of 546.1 us */
+#define MUSB_HDRC_FS_EOF1 0x7d /* 8 bit, units of 533.3 ns */
+#define MUSB_HDRC_LS_EOF1 0x7e /* 8 bit, units of 1.067 us */
+
+/* Per-EP BUSCTL registers */
+#define MUSB_HDRC_BUSCTL 0x80
+
+/* Per-EP registers in flat mode */
+#define MUSB_HDRC_EP 0x100
+
+/* offsets to registers in flat model */
+#define MUSB_HDRC_TXMAXP 0x00 /* 16 bit apparently */
+#define MUSB_HDRC_TXCSR 0x02 /* 16 bit apparently */
+#define MUSB_HDRC_CSR0 MUSB_HDRC_TXCSR /* re-used for EP0 */
+#define MUSB_HDRC_RXMAXP 0x04 /* 16 bit apparently */
+#define MUSB_HDRC_RXCSR 0x06 /* 16 bit apparently */
+#define MUSB_HDRC_RXCOUNT 0x08 /* 16 bit apparently */
+#define MUSB_HDRC_COUNT0 MUSB_HDRC_RXCOUNT /* re-used for EP0 */
+#define MUSB_HDRC_TXTYPE 0x0a /* 8 bit apparently */
+#define MUSB_HDRC_TYPE0 MUSB_HDRC_TXTYPE /* re-used for EP0 */
+#define MUSB_HDRC_TXINTERVAL 0x0b /* 8 bit apparently */
+#define MUSB_HDRC_NAKLIMIT0 MUSB_HDRC_TXINTERVAL /* re-used for EP0 */
+#define MUSB_HDRC_RXTYPE 0x0c /* 8 bit apparently */
+#define MUSB_HDRC_RXINTERVAL 0x0d /* 8 bit apparently */
+#define MUSB_HDRC_FIFOSIZE 0x0f /* 8 bit apparently */
+#define MUSB_HDRC_CONFIGDATA MGC_O_HDRC_FIFOSIZE /* re-used for EP0 */
+
+/* "Bus control" registers */
+#define MUSB_HDRC_TXFUNCADDR 0x00
+#define MUSB_HDRC_TXHUBADDR 0x02
+#define MUSB_HDRC_TXHUBPORT 0x03
+
+#define MUSB_HDRC_RXFUNCADDR 0x04
+#define MUSB_HDRC_RXHUBADDR 0x06
+#define MUSB_HDRC_RXHUBPORT 0x07
+
+/*
+ * MUSBHDRC Register bit masks
+ */
+
+/* POWER */
+#define MGC_M_POWER_ISOUPDATE 0x80
+#define MGC_M_POWER_SOFTCONN 0x40
+#define MGC_M_POWER_HSENAB 0x20
+#define MGC_M_POWER_HSMODE 0x10
+#define MGC_M_POWER_RESET 0x08
+#define MGC_M_POWER_RESUME 0x04
+#define MGC_M_POWER_SUSPENDM 0x02
+#define MGC_M_POWER_ENSUSPEND 0x01
+
+/* INTRUSB */
+#define MGC_M_INTR_SUSPEND 0x01
+#define MGC_M_INTR_RESUME 0x02
+#define MGC_M_INTR_RESET 0x04
+#define MGC_M_INTR_BABBLE 0x04
+#define MGC_M_INTR_SOF 0x08
+#define MGC_M_INTR_CONNECT 0x10
+#define MGC_M_INTR_DISCONNECT 0x20
+#define MGC_M_INTR_SESSREQ 0x40
+#define MGC_M_INTR_VBUSERROR 0x80 /* FOR SESSION END */
+#define MGC_M_INTR_EP0 0x01 /* FOR EP0 INTERRUPT */
+
+/* DEVCTL */
+#define MGC_M_DEVCTL_BDEVICE 0x80
+#define MGC_M_DEVCTL_FSDEV 0x40
+#define MGC_M_DEVCTL_LSDEV 0x20
+#define MGC_M_DEVCTL_VBUS 0x18
+#define MGC_S_DEVCTL_VBUS 3
+#define MGC_M_DEVCTL_HM 0x04
+#define MGC_M_DEVCTL_HR 0x02
+#define MGC_M_DEVCTL_SESSION 0x01
+
+/* TESTMODE */
+#define MGC_M_TEST_FORCE_HOST 0x80
+#define MGC_M_TEST_FIFO_ACCESS 0x40
+#define MGC_M_TEST_FORCE_FS 0x20
+#define MGC_M_TEST_FORCE_HS 0x10
+#define MGC_M_TEST_PACKET 0x08
+#define MGC_M_TEST_K 0x04
+#define MGC_M_TEST_J 0x02
+#define MGC_M_TEST_SE0_NAK 0x01
+
+/* CSR0 */
+#define MGC_M_CSR0_FLUSHFIFO 0x0100
+#define MGC_M_CSR0_TXPKTRDY 0x0002
+#define MGC_M_CSR0_RXPKTRDY 0x0001
+
+/* CSR0 in Peripheral mode */
+#define MGC_M_CSR0_P_SVDSETUPEND 0x0080
+#define MGC_M_CSR0_P_SVDRXPKTRDY 0x0040
+#define MGC_M_CSR0_P_SENDSTALL 0x0020
+#define MGC_M_CSR0_P_SETUPEND 0x0010
+#define MGC_M_CSR0_P_DATAEND 0x0008
+#define MGC_M_CSR0_P_SENTSTALL 0x0004
+
+/* CSR0 in Host mode */
+#define MGC_M_CSR0_H_NO_PING 0x0800
+#define MGC_M_CSR0_H_WR_DATATOGGLE 0x0400 /* set to allow setting: */
+#define MGC_M_CSR0_H_DATATOGGLE 0x0200 /* data toggle control */
+#define MGC_M_CSR0_H_NAKTIMEOUT 0x0080
+#define MGC_M_CSR0_H_STATUSPKT 0x0040
+#define MGC_M_CSR0_H_REQPKT 0x0020
+#define MGC_M_CSR0_H_ERROR 0x0010
+#define MGC_M_CSR0_H_SETUPPKT 0x0008
+#define MGC_M_CSR0_H_RXSTALL 0x0004
+
+/* CONFIGDATA */
+#define MGC_M_CONFIGDATA_MPRXE 0x80 /* auto bulk pkt combining */
+#define MGC_M_CONFIGDATA_MPTXE 0x40 /* auto bulk pkt splitting */
+#define MGC_M_CONFIGDATA_BIGENDIAN 0x20
+#define MGC_M_CONFIGDATA_HBRXE 0x10 /* HB-ISO for RX */
+#define MGC_M_CONFIGDATA_HBTXE 0x08 /* HB-ISO for TX */
+#define MGC_M_CONFIGDATA_DYNFIFO 0x04 /* dynamic FIFO sizing */
+#define MGC_M_CONFIGDATA_SOFTCONE 0x02 /* SoftConnect */
+#define MGC_M_CONFIGDATA_UTMIDW 0x01 /* Width, 0 => 8b, 1 => 16b */
+
+/* TXCSR in Peripheral and Host mode */
+#define MGC_M_TXCSR_AUTOSET 0x8000
+#define MGC_M_TXCSR_ISO 0x4000
+#define MGC_M_TXCSR_MODE 0x2000
+#define MGC_M_TXCSR_DMAENAB 0x1000
+#define MGC_M_TXCSR_FRCDATATOG 0x0800
+#define MGC_M_TXCSR_DMAMODE 0x0400
+#define MGC_M_TXCSR_CLRDATATOG 0x0040
+#define MGC_M_TXCSR_FLUSHFIFO 0x0008
+#define MGC_M_TXCSR_FIFONOTEMPTY 0x0002
+#define MGC_M_TXCSR_TXPKTRDY 0x0001
+
+/* TXCSR in Peripheral mode */
+#define MGC_M_TXCSR_P_INCOMPTX 0x0080
+#define MGC_M_TXCSR_P_SENTSTALL 0x0020
+#define MGC_M_TXCSR_P_SENDSTALL 0x0010
+#define MGC_M_TXCSR_P_UNDERRUN 0x0004
+
+/* TXCSR in Host mode */
+#define MGC_M_TXCSR_H_WR_DATATOGGLE 0x0200
+#define MGC_M_TXCSR_H_DATATOGGLE 0x0100
+#define MGC_M_TXCSR_H_NAKTIMEOUT 0x0080
+#define MGC_M_TXCSR_H_RXSTALL 0x0020
+#define MGC_M_TXCSR_H_ERROR 0x0004
+
+/* RXCSR in Peripheral and Host mode */
+#define MGC_M_RXCSR_AUTOCLEAR 0x8000
+#define MGC_M_RXCSR_DMAENAB 0x2000
+#define MGC_M_RXCSR_DISNYET 0x1000
+#define MGC_M_RXCSR_DMAMODE 0x0800
+#define MGC_M_RXCSR_INCOMPRX 0x0100
+#define MGC_M_RXCSR_CLRDATATOG 0x0080
+#define MGC_M_RXCSR_FLUSHFIFO 0x0010
+#define MGC_M_RXCSR_DATAERROR 0x0008
+#define MGC_M_RXCSR_FIFOFULL 0x0002
+#define MGC_M_RXCSR_RXPKTRDY 0x0001
+
+/* RXCSR in Peripheral mode */
+#define MGC_M_RXCSR_P_ISO 0x4000
+#define MGC_M_RXCSR_P_SENTSTALL 0x0040
+#define MGC_M_RXCSR_P_SENDSTALL 0x0020
+#define MGC_M_RXCSR_P_OVERRUN 0x0004
+
+/* RXCSR in Host mode */
+#define MGC_M_RXCSR_H_AUTOREQ 0x4000
+#define MGC_M_RXCSR_H_WR_DATATOGGLE 0x0400
+#define MGC_M_RXCSR_H_DATATOGGLE 0x0200
+#define MGC_M_RXCSR_H_RXSTALL 0x0040
+#define MGC_M_RXCSR_H_REQPKT 0x0020
+#define MGC_M_RXCSR_H_ERROR 0x0004
+
+/* HUBADDR */
+#define MGC_M_HUBADDR_MULTI_TT 0x80
+
+/* ULPI: Added in HDRC 1.9(?) & MHDRC 1.4 */
+#define MGC_M_ULPI_VBCTL_USEEXTVBUSIND 0x02
+#define MGC_M_ULPI_VBCTL_USEEXTVBUS 0x01
+#define MGC_M_ULPI_REGCTL_INT_ENABLE 0x08
+#define MGC_M_ULPI_REGCTL_READNOTWRITE 0x04
+#define MGC_M_ULPI_REGCTL_COMPLETE 0x02
+#define MGC_M_ULPI_REGCTL_REG 0x01
+
+/* #define MUSB_DEBUG */
+
+#ifdef MUSB_DEBUG
+#define TRACE(fmt,...) fprintf(stderr, "%s@%d: " fmt "\n", __FUNCTION__, \
+ __LINE__, ##__VA_ARGS__)
+#else
+#define TRACE(...)
+#endif
+
+
+static void musb_attach(USBPort *port);
+static void musb_detach(USBPort *port);
+static void musb_child_detach(USBPort *port, USBDevice *child);
+static void musb_schedule_cb(USBPort *port, USBPacket *p);
+static void musb_async_cancel_device(MUSBState *s, USBDevice *dev);
+
+static USBPortOps musb_port_ops = {
+ .attach = musb_attach,
+ .detach = musb_detach,
+ .child_detach = musb_child_detach,
+ .complete = musb_schedule_cb,
+};
+
+static USBBusOps musb_bus_ops = {
+};
+
+typedef struct MUSBPacket MUSBPacket;
+typedef struct MUSBEndPoint MUSBEndPoint;
+
+struct MUSBPacket {
+ USBPacket p;
+ MUSBEndPoint *ep;
+ int dir;
+};
+
+struct MUSBEndPoint {
+ uint16_t faddr[2];
+ uint8_t haddr[2];
+ uint8_t hport[2];
+ uint16_t csr[2];
+ uint16_t maxp[2];
+ uint16_t rxcount;
+ uint8_t type[2];
+ uint8_t interval[2];
+ uint8_t config;
+ uint8_t fifosize;
+ int timeout[2]; /* Always in microframes */
+
+ uint8_t *buf[2];
+ int fifolen[2];
+ int fifostart[2];
+ int fifoaddr[2];
+ MUSBPacket packey[2];
+ int status[2];
+ int ext_size[2];
+
+ /* For callbacks' use */
+ int epnum;
+ int interrupt[2];
+ MUSBState *musb;
+ USBCallback *delayed_cb[2];
+ QEMUTimer *intv_timer[2];
+};
+
+struct MUSBState {
+ qemu_irq irqs[musb_irq_max];
+ USBBus bus;
+ USBPort port;
+
+ int idx;
+ uint8_t devctl;
+ uint8_t power;
+ uint8_t faddr;
+
+ uint8_t intr;
+ uint8_t mask;
+ uint16_t tx_intr;
+ uint16_t tx_mask;
+ uint16_t rx_intr;
+ uint16_t rx_mask;
+
+ int setup_len;
+ int session;
+
+ uint8_t buf[0x8000];
+
+ /* Duplicating the world since 2008!... probably we should have 32
+ * logical, single endpoints instead. */
+ MUSBEndPoint ep[16];
+};
+
+void musb_reset(MUSBState *s)
+{
+ int i;
+
+ s->faddr = 0x00;
+ s->devctl = 0;
+ s->power = MGC_M_POWER_HSENAB;
+ s->tx_intr = 0x0000;
+ s->rx_intr = 0x0000;
+ s->tx_mask = 0xffff;
+ s->rx_mask = 0xffff;
+ s->intr = 0x00;
+ s->mask = 0x06;
+ s->idx = 0;
+
+ s->setup_len = 0;
+ s->session = 0;
+ memset(s->buf, 0, sizeof(s->buf));
+
+ /* TODO: _DW */
+ s->ep[0].config = MGC_M_CONFIGDATA_SOFTCONE | MGC_M_CONFIGDATA_DYNFIFO;
+ for (i = 0; i < 16; i ++) {
+ s->ep[i].fifosize = 64;
+ s->ep[i].maxp[0] = 0x40;
+ s->ep[i].maxp[1] = 0x40;
+ s->ep[i].musb = s;
+ s->ep[i].epnum = i;
+ usb_packet_init(&s->ep[i].packey[0].p);
+ usb_packet_init(&s->ep[i].packey[1].p);
+ }
+}
+
+struct MUSBState *musb_init(DeviceState *parent_device, int gpio_base)
+{
+ MUSBState *s = g_malloc0(sizeof(*s));
+ int i;
+
+ for (i = 0; i < musb_irq_max; i++) {
+ s->irqs[i] = qdev_get_gpio_in(parent_device, gpio_base + i);
+ }
+
+ musb_reset(s);
+
+ usb_bus_new(&s->bus, sizeof(s->bus), &musb_bus_ops, parent_device);
+ usb_register_port(&s->bus, &s->port, s, 0, &musb_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
+
+ return s;
+}
+
+static void musb_vbus_set(MUSBState *s, int level)
+{
+ if (level)
+ s->devctl |= 3 << MGC_S_DEVCTL_VBUS;
+ else
+ s->devctl &= ~MGC_M_DEVCTL_VBUS;
+
+ qemu_set_irq(s->irqs[musb_set_vbus], level);
+}
+
+static void musb_intr_set(MUSBState *s, int line, int level)
+{
+ if (!level) {
+ s->intr &= ~(1 << line);
+ qemu_irq_lower(s->irqs[line]);
+ } else if (s->mask & (1 << line)) {
+ s->intr |= 1 << line;
+ qemu_irq_raise(s->irqs[line]);
+ }
+}
+
+static void musb_tx_intr_set(MUSBState *s, int line, int level)
+{
+ if (!level) {
+ s->tx_intr &= ~(1 << line);
+ if (!s->tx_intr)
+ qemu_irq_lower(s->irqs[musb_irq_tx]);
+ } else if (s->tx_mask & (1 << line)) {
+ s->tx_intr |= 1 << line;
+ qemu_irq_raise(s->irqs[musb_irq_tx]);
+ }
+}
+
+static void musb_rx_intr_set(MUSBState *s, int line, int level)
+{
+ if (line) {
+ if (!level) {
+ s->rx_intr &= ~(1 << line);
+ if (!s->rx_intr)
+ qemu_irq_lower(s->irqs[musb_irq_rx]);
+ } else if (s->rx_mask & (1 << line)) {
+ s->rx_intr |= 1 << line;
+ qemu_irq_raise(s->irqs[musb_irq_rx]);
+ }
+ } else
+ musb_tx_intr_set(s, line, level);
+}
+
+uint32_t musb_core_intr_get(MUSBState *s)
+{
+ return (s->rx_intr << 15) | s->tx_intr;
+}
+
+void musb_core_intr_clear(MUSBState *s, uint32_t mask)
+{
+ if (s->rx_intr) {
+ s->rx_intr &= mask >> 15;
+ if (!s->rx_intr)
+ qemu_irq_lower(s->irqs[musb_irq_rx]);
+ }
+
+ if (s->tx_intr) {
+ s->tx_intr &= mask & 0xffff;
+ if (!s->tx_intr)
+ qemu_irq_lower(s->irqs[musb_irq_tx]);
+ }
+}
+
+void musb_set_size(MUSBState *s, int epnum, int size, int is_tx)
+{
+ s->ep[epnum].ext_size[!is_tx] = size;
+ s->ep[epnum].fifostart[0] = 0;
+ s->ep[epnum].fifostart[1] = 0;
+ s->ep[epnum].fifolen[0] = 0;
+ s->ep[epnum].fifolen[1] = 0;
+}
+
+static void musb_session_update(MUSBState *s, int prev_dev, int prev_sess)
+{
+ int detect_prev = prev_dev && prev_sess;
+ int detect = !!s->port.dev && s->session;
+
+ if (detect && !detect_prev) {
+ /* Let's skip the ID pin sense and VBUS sense formalities and
+ * and signal a successful SRP directly. This should work at least
+ * for the Linux driver stack. */
+ musb_intr_set(s, musb_irq_connect, 1);
+
+ if (s->port.dev->speed == USB_SPEED_LOW) {
+ s->devctl &= ~MGC_M_DEVCTL_FSDEV;
+ s->devctl |= MGC_M_DEVCTL_LSDEV;
+ } else {
+ s->devctl |= MGC_M_DEVCTL_FSDEV;
+ s->devctl &= ~MGC_M_DEVCTL_LSDEV;
+ }
+
+ /* A-mode? */
+ s->devctl &= ~MGC_M_DEVCTL_BDEVICE;
+
+ /* Host-mode bit? */
+ s->devctl |= MGC_M_DEVCTL_HM;
+#if 1
+ musb_vbus_set(s, 1);
+#endif
+ } else if (!detect && detect_prev) {
+#if 1
+ musb_vbus_set(s, 0);
+#endif
+ }
+}
+
+/* Attach or detach a device on our only port. */
+static void musb_attach(USBPort *port)
+{
+ MUSBState *s = (MUSBState *) port->opaque;
+
+ musb_intr_set(s, musb_irq_vbus_request, 1);
+ musb_session_update(s, 0, s->session);
+}
+
+static void musb_detach(USBPort *port)
+{
+ MUSBState *s = (MUSBState *) port->opaque;
+
+ musb_async_cancel_device(s, port->dev);
+
+ musb_intr_set(s, musb_irq_disconnect, 1);
+ musb_session_update(s, 1, s->session);
+}
+
+static void musb_child_detach(USBPort *port, USBDevice *child)
+{
+ MUSBState *s = (MUSBState *) port->opaque;
+
+ musb_async_cancel_device(s, child);
+}
+
+static void musb_cb_tick0(void *opaque)
+{
+ MUSBEndPoint *ep = (MUSBEndPoint *) opaque;
+
+ ep->delayed_cb[0](&ep->packey[0].p, opaque);
+}
+
+static void musb_cb_tick1(void *opaque)
+{
+ MUSBEndPoint *ep = (MUSBEndPoint *) opaque;
+
+ ep->delayed_cb[1](&ep->packey[1].p, opaque);
+}
+
+#define musb_cb_tick (dir ? musb_cb_tick1 : musb_cb_tick0)
+
+static void musb_schedule_cb(USBPort *port, USBPacket *packey)
+{
+ MUSBPacket *p = container_of(packey, MUSBPacket, p);
+ MUSBEndPoint *ep = p->ep;
+ int dir = p->dir;
+ int timeout = 0;
+
+ if (ep->status[dir] == USB_RET_NAK)
+ timeout = ep->timeout[dir];
+ else if (ep->interrupt[dir])
+ timeout = 8;
+ else {
+ musb_cb_tick(ep);
+ return;
+ }
+
+ if (!ep->intv_timer[dir])
+ ep->intv_timer[dir] = timer_new_ns(QEMU_CLOCK_VIRTUAL, musb_cb_tick, ep);
+
+ timer_mod(ep->intv_timer[dir], qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ muldiv64(timeout, get_ticks_per_sec(), 8000));
+}
+
+static int musb_timeout(int ttype, int speed, int val)
+{
+#if 1
+ return val << 3;
+#endif
+
+ switch (ttype) {
+ case USB_ENDPOINT_XFER_CONTROL:
+ if (val < 2)
+ return 0;
+ else if (speed == USB_SPEED_HIGH)
+ return 1 << (val - 1);
+ else
+ return 8 << (val - 1);
+
+ case USB_ENDPOINT_XFER_INT:
+ if (speed == USB_SPEED_HIGH)
+ if (val < 2)
+ return 0;
+ else
+ return 1 << (val - 1);
+ else
+ return val << 3;
+
+ case USB_ENDPOINT_XFER_BULK:
+ case USB_ENDPOINT_XFER_ISOC:
+ if (val < 2)
+ return 0;
+ else if (speed == USB_SPEED_HIGH)
+ return 1 << (val - 1);
+ else
+ return 8 << (val - 1);
+ /* TODO: what with low-speed Bulk and Isochronous? */
+ }
+
+ hw_error("bad interval\n");
+}
+
+static void musb_packet(MUSBState *s, MUSBEndPoint *ep,
+ int epnum, int pid, int len, USBCallback cb, int dir)
+{
+ USBDevice *dev;
+ USBEndpoint *uep;
+ int idx = epnum && dir;
+ int id;
+ int ttype;
+
+ /* ep->type[0,1] contains:
+ * in bits 7:6 the speed (0 - invalid, 1 - high, 2 - full, 3 - slow)
+ * in bits 5:4 the transfer type (BULK / INT)
+ * in bits 3:0 the EP num
+ */
+ ttype = epnum ? (ep->type[idx] >> 4) & 3 : 0;
+
+ ep->timeout[dir] = musb_timeout(ttype,
+ ep->type[idx] >> 6, ep->interval[idx]);
+ ep->interrupt[dir] = ttype == USB_ENDPOINT_XFER_INT;
+ ep->delayed_cb[dir] = cb;
+
+ /* A wild guess on the FADDR semantics... */
+ dev = usb_find_device(&s->port, ep->faddr[idx]);
+ uep = usb_ep_get(dev, pid, ep->type[idx] & 0xf);
+ id = pid;
+ if (uep) {
+ id |= (dev->addr << 16) | (uep->nr << 8);
+ }
+ usb_packet_setup(&ep->packey[dir].p, pid, uep, 0, id, false, true);
+ usb_packet_addbuf(&ep->packey[dir].p, ep->buf[idx], len);
+ ep->packey[dir].ep = ep;
+ ep->packey[dir].dir = dir;
+
+ usb_handle_packet(dev, &ep->packey[dir].p);
+
+ if (ep->packey[dir].p.status == USB_RET_ASYNC) {
+ usb_device_flush_ep_queue(dev, uep);
+ ep->status[dir] = len;
+ return;
+ }
+
+ if (ep->packey[dir].p.status == USB_RET_SUCCESS) {
+ ep->status[dir] = ep->packey[dir].p.actual_length;
+ } else {
+ ep->status[dir] = ep->packey[dir].p.status;
+ }
+ musb_schedule_cb(&s->port, &ep->packey[dir].p);
+}
+
+static void musb_tx_packet_complete(USBPacket *packey, void *opaque)
+{
+ /* Unfortunately we can't use packey->devep because that's the remote
+ * endpoint number and may be different than our local. */
+ MUSBEndPoint *ep = (MUSBEndPoint *) opaque;
+ int epnum = ep->epnum;
+ MUSBState *s = ep->musb;
+
+ ep->fifostart[0] = 0;
+ ep->fifolen[0] = 0;
+#ifdef CLEAR_NAK
+ if (ep->status[0] != USB_RET_NAK) {
+#endif
+ if (epnum)
+ ep->csr[0] &= ~(MGC_M_TXCSR_FIFONOTEMPTY | MGC_M_TXCSR_TXPKTRDY);
+ else
+ ep->csr[0] &= ~MGC_M_CSR0_TXPKTRDY;
+#ifdef CLEAR_NAK
+ }
+#endif
+
+ /* Clear all of the error bits first */
+ if (epnum)
+ ep->csr[0] &= ~(MGC_M_TXCSR_H_ERROR | MGC_M_TXCSR_H_RXSTALL |
+ MGC_M_TXCSR_H_NAKTIMEOUT);
+ else
+ ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL |
+ MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING);
+
+ if (ep->status[0] == USB_RET_STALL) {
+ /* Command not supported by target! */
+ ep->status[0] = 0;
+
+ if (epnum)
+ ep->csr[0] |= MGC_M_TXCSR_H_RXSTALL;
+ else
+ ep->csr[0] |= MGC_M_CSR0_H_RXSTALL;
+ }
+
+ if (ep->status[0] == USB_RET_NAK) {
+ ep->status[0] = 0;
+
+ /* NAK timeouts are only generated in Bulk transfers and
+ * Data-errors in Isochronous. */
+ if (ep->interrupt[0]) {
+ return;
+ }
+
+ if (epnum)
+ ep->csr[0] |= MGC_M_TXCSR_H_NAKTIMEOUT;
+ else
+ ep->csr[0] |= MGC_M_CSR0_H_NAKTIMEOUT;
+ }
+
+ if (ep->status[0] < 0) {
+ if (ep->status[0] == USB_RET_BABBLE)
+ musb_intr_set(s, musb_irq_rst_babble, 1);
+
+ /* Pretend we've tried three times already and failed (in
+ * case of USB_TOKEN_SETUP). */
+ if (epnum)
+ ep->csr[0] |= MGC_M_TXCSR_H_ERROR;
+ else
+ ep->csr[0] |= MGC_M_CSR0_H_ERROR;
+
+ musb_tx_intr_set(s, epnum, 1);
+ return;
+ }
+ /* TODO: check len for over/underruns of an OUT packet? */
+
+#ifdef SETUPLEN_HACK
+ if (!epnum && ep->packey[0].pid == USB_TOKEN_SETUP)
+ s->setup_len = ep->packey[0].data[6];
+#endif
+
+ /* In DMA mode: if no error, assert DMA request for this EP,
+ * and skip the interrupt. */
+ musb_tx_intr_set(s, epnum, 1);
+}
+
+static void musb_rx_packet_complete(USBPacket *packey, void *opaque)
+{
+ /* Unfortunately we can't use packey->devep because that's the remote
+ * endpoint number and may be different than our local. */
+ MUSBEndPoint *ep = (MUSBEndPoint *) opaque;
+ int epnum = ep->epnum;
+ MUSBState *s = ep->musb;
+
+ ep->fifostart[1] = 0;
+ ep->fifolen[1] = 0;
+
+#ifdef CLEAR_NAK
+ if (ep->status[1] != USB_RET_NAK) {
+#endif
+ ep->csr[1] &= ~MGC_M_RXCSR_H_REQPKT;
+ if (!epnum)
+ ep->csr[0] &= ~MGC_M_CSR0_H_REQPKT;
+#ifdef CLEAR_NAK
+ }
+#endif
+
+ /* Clear all of the imaginable error bits first */
+ ep->csr[1] &= ~(MGC_M_RXCSR_H_ERROR | MGC_M_RXCSR_H_RXSTALL |
+ MGC_M_RXCSR_DATAERROR);
+ if (!epnum)
+ ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL |
+ MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING);
+
+ if (ep->status[1] == USB_RET_STALL) {
+ ep->status[1] = 0;
+
+ ep->csr[1] |= MGC_M_RXCSR_H_RXSTALL;
+ if (!epnum)
+ ep->csr[0] |= MGC_M_CSR0_H_RXSTALL;
+ }
+
+ if (ep->status[1] == USB_RET_NAK) {
+ ep->status[1] = 0;
+
+ /* NAK timeouts are only generated in Bulk transfers and
+ * Data-errors in Isochronous. */
+ if (ep->interrupt[1]) {
+ musb_packet(s, ep, epnum, USB_TOKEN_IN,
+ packey->iov.size, musb_rx_packet_complete, 1);
+ return;
+ }
+
+ ep->csr[1] |= MGC_M_RXCSR_DATAERROR;
+ if (!epnum)
+ ep->csr[0] |= MGC_M_CSR0_H_NAKTIMEOUT;
+ }
+
+ if (ep->status[1] < 0) {
+ if (ep->status[1] == USB_RET_BABBLE) {
+ musb_intr_set(s, musb_irq_rst_babble, 1);
+ return;
+ }
+
+ /* Pretend we've tried three times already and failed (in
+ * case of a control transfer). */
+ ep->csr[1] |= MGC_M_RXCSR_H_ERROR;
+ if (!epnum)
+ ep->csr[0] |= MGC_M_CSR0_H_ERROR;
+
+ musb_rx_intr_set(s, epnum, 1);
+ return;
+ }
+ /* TODO: check len for over/underruns of an OUT packet? */
+ /* TODO: perhaps make use of e->ext_size[1] here. */
+
+ if (!(ep->csr[1] & (MGC_M_RXCSR_H_RXSTALL | MGC_M_RXCSR_DATAERROR))) {
+ ep->csr[1] |= MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY;
+ if (!epnum)
+ ep->csr[0] |= MGC_M_CSR0_RXPKTRDY;
+
+ ep->rxcount = ep->status[1]; /* XXX: MIN(packey->len, ep->maxp[1]); */
+ /* In DMA mode: assert DMA request for this EP */
+ }
+
+ /* Only if DMA has not been asserted */
+ musb_rx_intr_set(s, epnum, 1);
+}
+
+static void musb_async_cancel_device(MUSBState *s, USBDevice *dev)
+{
+ int ep, dir;
+
+ for (ep = 0; ep < 16; ep++) {
+ for (dir = 0; dir < 2; dir++) {
+ if (!usb_packet_is_inflight(&s->ep[ep].packey[dir].p) ||
+ s->ep[ep].packey[dir].p.ep->dev != dev) {
+ continue;
+ }
+ usb_cancel_packet(&s->ep[ep].packey[dir].p);
+ /* status updates needed here? */
+ }
+ }
+}
+
+static void musb_tx_rdy(MUSBState *s, int epnum)
+{
+ MUSBEndPoint *ep = s->ep + epnum;
+ int pid;
+ int total, valid = 0;
+ TRACE("start %d, len %d", ep->fifostart[0], ep->fifolen[0] );
+ ep->fifostart[0] += ep->fifolen[0];
+ ep->fifolen[0] = 0;
+
+ /* XXX: how's the total size of the packet retrieved exactly in
+ * the generic case? */
+ total = ep->maxp[0] & 0x3ff;
+
+ if (ep->ext_size[0]) {
+ total = ep->ext_size[0];
+ ep->ext_size[0] = 0;
+ valid = 1;
+ }
+
+ /* If the packet is not fully ready yet, wait for a next segment. */
+ if (epnum && (ep->fifostart[0]) < total)
+ return;
+
+ if (!valid)
+ total = ep->fifostart[0];
+
+ pid = USB_TOKEN_OUT;
+ if (!epnum && (ep->csr[0] & MGC_M_CSR0_H_SETUPPKT)) {
+ pid = USB_TOKEN_SETUP;
+ if (total != 8) {
+ TRACE("illegal SETUPPKT length of %i bytes", total);
+ }
+ /* Controller should retry SETUP packets three times on errors
+ * but it doesn't make sense for us to do that. */
+ }
+
+ musb_packet(s, ep, epnum, pid, total, musb_tx_packet_complete, 0);
+}
+
+static void musb_rx_req(MUSBState *s, int epnum)
+{
+ MUSBEndPoint *ep = s->ep + epnum;
+ int total;
+
+ /* If we already have a packet, which didn't fit into the
+ * 64 bytes of the FIFO, only move the FIFO start and return. (Obsolete) */
+ if (ep->packey[1].p.pid == USB_TOKEN_IN && ep->status[1] >= 0 &&
+ (ep->fifostart[1]) + ep->rxcount <
+ ep->packey[1].p.iov.size) {
+ TRACE("0x%08x, %d", ep->fifostart[1], ep->rxcount );
+ ep->fifostart[1] += ep->rxcount;
+ ep->fifolen[1] = 0;
+
+ ep->rxcount = MIN(ep->packey[0].p.iov.size - (ep->fifostart[1]),
+ ep->maxp[1]);
+
+ ep->csr[1] &= ~MGC_M_RXCSR_H_REQPKT;
+ if (!epnum)
+ ep->csr[0] &= ~MGC_M_CSR0_H_REQPKT;
+
+ /* Clear all of the error bits first */
+ ep->csr[1] &= ~(MGC_M_RXCSR_H_ERROR | MGC_M_RXCSR_H_RXSTALL |
+ MGC_M_RXCSR_DATAERROR);
+ if (!epnum)
+ ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL |
+ MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING);
+
+ ep->csr[1] |= MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY;
+ if (!epnum)
+ ep->csr[0] |= MGC_M_CSR0_RXPKTRDY;
+ musb_rx_intr_set(s, epnum, 1);
+ return;
+ }
+
+ /* The driver sets maxp[1] to 64 or less because it knows the hardware
+ * FIFO is this deep. Bigger packets get split in
+ * usb_generic_handle_packet but we can also do the splitting locally
+ * for performance. It turns out we can also have a bigger FIFO and
+ * ignore the limit set in ep->maxp[1]. The Linux MUSB driver deals
+ * OK with single packets of even 32KB and we avoid splitting, however
+ * usb_msd.c sometimes sends a packet bigger than what Linux expects
+ * (e.g. 8192 bytes instead of 4096) and we get an OVERRUN. Splitting
+ * hides this overrun from Linux. Up to 4096 everything is fine
+ * though. Currently this is disabled.
+ *
+ * XXX: mind ep->fifosize. */
+ total = MIN(ep->maxp[1] & 0x3ff, sizeof(s->buf));
+
+#ifdef SETUPLEN_HACK
+ /* Why should *we* do that instead of Linux? */
+ if (!epnum) {
+ if (ep->packey[0].p.devaddr == 2) {
+ total = MIN(s->setup_len, 8);
+ } else {
+ total = MIN(s->setup_len, 64);
+ }
+ s->setup_len -= total;
+ }
+#endif
+
+ musb_packet(s, ep, epnum, USB_TOKEN_IN, total, musb_rx_packet_complete, 1);
+}
+
+static uint8_t musb_read_fifo(MUSBEndPoint *ep)
+{
+ uint8_t value;
+ if (ep->fifolen[1] >= 64) {
+ /* We have a FIFO underrun */
+ TRACE("EP%d FIFO is now empty, stop reading", ep->epnum);
+ return 0x00000000;
+ }
+ /* In DMA mode clear RXPKTRDY and set REQPKT automatically
+ * (if AUTOREQ is set) */
+
+ ep->csr[1] &= ~MGC_M_RXCSR_FIFOFULL;
+ value=ep->buf[1][ep->fifostart[1] + ep->fifolen[1] ++];
+ TRACE("EP%d 0x%02x, %d", ep->epnum, value, ep->fifolen[1] );
+ return value;
+}
+
+static void musb_write_fifo(MUSBEndPoint *ep, uint8_t value)
+{
+ TRACE("EP%d = %02x", ep->epnum, value);
+ if (ep->fifolen[0] >= 64) {
+ /* We have a FIFO overrun */
+ TRACE("EP%d FIFO exceeded 64 bytes, stop feeding data", ep->epnum);
+ return;
+ }
+
+ ep->buf[0][ep->fifostart[0] + ep->fifolen[0] ++] = value;
+ ep->csr[0] |= MGC_M_TXCSR_FIFONOTEMPTY;
+}
+
+static void musb_ep_frame_cancel(MUSBEndPoint *ep, int dir)
+{
+ if (ep->intv_timer[dir])
+ timer_del(ep->intv_timer[dir]);
+}
+
+/* Bus control */
+static uint8_t musb_busctl_readb(void *opaque, int ep, int addr)
+{
+ MUSBState *s = (MUSBState *) opaque;
+
+ switch (addr) {
+ /* For USB2.0 HS hubs only */
+ case MUSB_HDRC_TXHUBADDR:
+ return s->ep[ep].haddr[0];
+ case MUSB_HDRC_TXHUBPORT:
+ return s->ep[ep].hport[0];
+ case MUSB_HDRC_RXHUBADDR:
+ return s->ep[ep].haddr[1];
+ case MUSB_HDRC_RXHUBPORT:
+ return s->ep[ep].hport[1];
+
+ default:
+ TRACE("unknown register 0x%02x", addr);
+ return 0x00;
+ };
+}
+
+static void musb_busctl_writeb(void *opaque, int ep, int addr, uint8_t value)
+{
+ MUSBState *s = (MUSBState *) opaque;
+
+ switch (addr) {
+ case MUSB_HDRC_TXFUNCADDR:
+ s->ep[ep].faddr[0] = value;
+ break;
+ case MUSB_HDRC_RXFUNCADDR:
+ s->ep[ep].faddr[1] = value;
+ break;
+ case MUSB_HDRC_TXHUBADDR:
+ s->ep[ep].haddr[0] = value;
+ break;
+ case MUSB_HDRC_TXHUBPORT:
+ s->ep[ep].hport[0] = value;
+ break;
+ case MUSB_HDRC_RXHUBADDR:
+ s->ep[ep].haddr[1] = value;
+ break;
+ case MUSB_HDRC_RXHUBPORT:
+ s->ep[ep].hport[1] = value;
+ break;
+
+ default:
+ TRACE("unknown register 0x%02x", addr);
+ break;
+ };
+}
+
+static uint16_t musb_busctl_readh(void *opaque, int ep, int addr)
+{
+ MUSBState *s = (MUSBState *) opaque;
+
+ switch (addr) {
+ case MUSB_HDRC_TXFUNCADDR:
+ return s->ep[ep].faddr[0];
+ case MUSB_HDRC_RXFUNCADDR:
+ return s->ep[ep].faddr[1];
+
+ default:
+ return musb_busctl_readb(s, ep, addr) |
+ (musb_busctl_readb(s, ep, addr | 1) << 8);
+ };
+}
+
+static void musb_busctl_writeh(void *opaque, int ep, int addr, uint16_t value)
+{
+ MUSBState *s = (MUSBState *) opaque;
+
+ switch (addr) {
+ case MUSB_HDRC_TXFUNCADDR:
+ s->ep[ep].faddr[0] = value;
+ break;
+ case MUSB_HDRC_RXFUNCADDR:
+ s->ep[ep].faddr[1] = value;
+ break;
+
+ default:
+ musb_busctl_writeb(s, ep, addr, value & 0xff);
+ musb_busctl_writeb(s, ep, addr | 1, value >> 8);
+ };
+}
+
+/* Endpoint control */
+static uint8_t musb_ep_readb(void *opaque, int ep, int addr)
+{
+ MUSBState *s = (MUSBState *) opaque;
+
+ switch (addr) {
+ case MUSB_HDRC_TXTYPE:
+ return s->ep[ep].type[0];
+ case MUSB_HDRC_TXINTERVAL:
+ return s->ep[ep].interval[0];
+ case MUSB_HDRC_RXTYPE:
+ return s->ep[ep].type[1];
+ case MUSB_HDRC_RXINTERVAL:
+ return s->ep[ep].interval[1];
+ case (MUSB_HDRC_FIFOSIZE & ~1):
+ return 0x00;
+ case MUSB_HDRC_FIFOSIZE:
+ return ep ? s->ep[ep].fifosize : s->ep[ep].config;
+ case MUSB_HDRC_RXCOUNT:
+ return s->ep[ep].rxcount;
+
+ default:
+ TRACE("unknown register 0x%02x", addr);
+ return 0x00;
+ };
+}
+
+static void musb_ep_writeb(void *opaque, int ep, int addr, uint8_t value)
+{
+ MUSBState *s = (MUSBState *) opaque;
+
+ switch (addr) {
+ case MUSB_HDRC_TXTYPE:
+ s->ep[ep].type[0] = value;
+ break;
+ case MUSB_HDRC_TXINTERVAL:
+ s->ep[ep].interval[0] = value;
+ musb_ep_frame_cancel(&s->ep[ep], 0);
+ break;
+ case MUSB_HDRC_RXTYPE:
+ s->ep[ep].type[1] = value;
+ break;
+ case MUSB_HDRC_RXINTERVAL:
+ s->ep[ep].interval[1] = value;
+ musb_ep_frame_cancel(&s->ep[ep], 1);
+ break;
+ case (MUSB_HDRC_FIFOSIZE & ~1):
+ break;
+ case MUSB_HDRC_FIFOSIZE:
+ TRACE("somebody messes with fifosize (now %i bytes)", value);
+ s->ep[ep].fifosize = value;
+ break;
+ default:
+ TRACE("unknown register 0x%02x", addr);
+ break;
+ };
+}
+
+static uint16_t musb_ep_readh(void *opaque, int ep, int addr)
+{
+ MUSBState *s = (MUSBState *) opaque;
+ uint16_t ret;
+
+ switch (addr) {
+ case MUSB_HDRC_TXMAXP:
+ return s->ep[ep].maxp[0];
+ case MUSB_HDRC_TXCSR:
+ return s->ep[ep].csr[0];
+ case MUSB_HDRC_RXMAXP:
+ return s->ep[ep].maxp[1];
+ case MUSB_HDRC_RXCSR:
+ ret = s->ep[ep].csr[1];
+
+ /* TODO: This and other bits probably depend on
+ * ep->csr[1] & MGC_M_RXCSR_AUTOCLEAR. */
+ if (s->ep[ep].csr[1] & MGC_M_RXCSR_AUTOCLEAR)
+ s->ep[ep].csr[1] &= ~MGC_M_RXCSR_RXPKTRDY;
+
+ return ret;
+ case MUSB_HDRC_RXCOUNT:
+ return s->ep[ep].rxcount;
+
+ default:
+ return musb_ep_readb(s, ep, addr) |
+ (musb_ep_readb(s, ep, addr | 1) << 8);
+ };
+}
+
+static void musb_ep_writeh(void *opaque, int ep, int addr, uint16_t value)
+{
+ MUSBState *s = (MUSBState *) opaque;
+
+ switch (addr) {
+ case MUSB_HDRC_TXMAXP:
+ s->ep[ep].maxp[0] = value;
+ break;
+ case MUSB_HDRC_TXCSR:
+ if (ep) {
+ s->ep[ep].csr[0] &= value & 0xa6;
+ s->ep[ep].csr[0] |= value & 0xff59;
+ } else {
+ s->ep[ep].csr[0] &= value & 0x85;
+ s->ep[ep].csr[0] |= value & 0xf7a;
+ }
+
+ musb_ep_frame_cancel(&s->ep[ep], 0);
+
+ if ((ep && (value & MGC_M_TXCSR_FLUSHFIFO)) ||
+ (!ep && (value & MGC_M_CSR0_FLUSHFIFO))) {
+ s->ep[ep].fifolen[0] = 0;
+ s->ep[ep].fifostart[0] = 0;
+ if (ep)
+ s->ep[ep].csr[0] &=
+ ~(MGC_M_TXCSR_FIFONOTEMPTY | MGC_M_TXCSR_TXPKTRDY);
+ else
+ s->ep[ep].csr[0] &=
+ ~(MGC_M_CSR0_TXPKTRDY | MGC_M_CSR0_RXPKTRDY);
+ }
+ if (
+ (ep &&
+#ifdef CLEAR_NAK
+ (value & MGC_M_TXCSR_TXPKTRDY) &&
+ !(value & MGC_M_TXCSR_H_NAKTIMEOUT)) ||
+#else
+ (value & MGC_M_TXCSR_TXPKTRDY)) ||
+#endif
+ (!ep &&
+#ifdef CLEAR_NAK
+ (value & MGC_M_CSR0_TXPKTRDY) &&
+ !(value & MGC_M_CSR0_H_NAKTIMEOUT)))
+#else
+ (value & MGC_M_CSR0_TXPKTRDY)))
+#endif
+ musb_tx_rdy(s, ep);
+ if (!ep &&
+ (value & MGC_M_CSR0_H_REQPKT) &&
+#ifdef CLEAR_NAK
+ !(value & (MGC_M_CSR0_H_NAKTIMEOUT |
+ MGC_M_CSR0_RXPKTRDY)))
+#else
+ !(value & MGC_M_CSR0_RXPKTRDY))
+#endif
+ musb_rx_req(s, ep);
+ break;
+
+ case MUSB_HDRC_RXMAXP:
+ s->ep[ep].maxp[1] = value;
+ break;
+ case MUSB_HDRC_RXCSR:
+ /* (DMA mode only) */
+ if (
+ (value & MGC_M_RXCSR_H_AUTOREQ) &&
+ !(value & MGC_M_RXCSR_RXPKTRDY) &&
+ (s->ep[ep].csr[1] & MGC_M_RXCSR_RXPKTRDY))
+ value |= MGC_M_RXCSR_H_REQPKT;
+
+ s->ep[ep].csr[1] &= 0x102 | (value & 0x4d);
+ s->ep[ep].csr[1] |= value & 0xfeb0;
+
+ musb_ep_frame_cancel(&s->ep[ep], 1);
+
+ if (value & MGC_M_RXCSR_FLUSHFIFO) {
+ s->ep[ep].fifolen[1] = 0;
+ s->ep[ep].fifostart[1] = 0;
+ s->ep[ep].csr[1] &= ~(MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY);
+ /* If double buffering and we have two packets ready, flush
+ * only the first one and set up the fifo at the second packet. */
+ }
+#ifdef CLEAR_NAK
+ if ((value & MGC_M_RXCSR_H_REQPKT) && !(value & MGC_M_RXCSR_DATAERROR))
+#else
+ if (value & MGC_M_RXCSR_H_REQPKT)
+#endif
+ musb_rx_req(s, ep);
+ break;
+ case MUSB_HDRC_RXCOUNT:
+ s->ep[ep].rxcount = value;
+ break;
+
+ default:
+ musb_ep_writeb(s, ep, addr, value & 0xff);
+ musb_ep_writeb(s, ep, addr | 1, value >> 8);
+ };
+}
+
+/* Generic control */
+static uint32_t musb_readb(void *opaque, hwaddr addr)
+{
+ MUSBState *s = (MUSBState *) opaque;
+ int ep, i;
+ uint8_t ret;
+
+ switch (addr) {
+ case MUSB_HDRC_FADDR:
+ return s->faddr;
+ case MUSB_HDRC_POWER:
+ return s->power;
+ case MUSB_HDRC_INTRUSB:
+ ret = s->intr;
+ for (i = 0; i < sizeof(ret) * 8; i ++)
+ if (ret & (1 << i))
+ musb_intr_set(s, i, 0);
+ return ret;
+ case MUSB_HDRC_INTRUSBE:
+ return s->mask;
+ case MUSB_HDRC_INDEX:
+ return s->idx;
+ case MUSB_HDRC_TESTMODE:
+ return 0x00;
+
+ case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf):
+ return musb_ep_readb(s, s->idx, addr & 0xf);
+
+ case MUSB_HDRC_DEVCTL:
+ return s->devctl;
+
+ case MUSB_HDRC_TXFIFOSZ:
+ case MUSB_HDRC_RXFIFOSZ:
+ case MUSB_HDRC_VCTRL:
+ /* TODO */
+ return 0x00;
+
+ case MUSB_HDRC_HWVERS:
+ return (1 << 10) | 400;
+
+ case (MUSB_HDRC_VCTRL | 1):
+ case (MUSB_HDRC_HWVERS | 1):
+ case (MUSB_HDRC_DEVCTL | 1):
+ return 0x00;
+
+ case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f):
+ ep = (addr >> 3) & 0xf;
+ return musb_busctl_readb(s, ep, addr & 0x7);
+
+ case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff):
+ ep = (addr >> 4) & 0xf;
+ return musb_ep_readb(s, ep, addr & 0xf);
+
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ return musb_read_fifo(s->ep + ep);
+
+ default:
+ TRACE("unknown register 0x%02x", (int) addr);
+ return 0x00;
+ };
+}
+
+static void musb_writeb(void *opaque, hwaddr addr, uint32_t value)
+{
+ MUSBState *s = (MUSBState *) opaque;
+ int ep;
+
+ switch (addr) {
+ case MUSB_HDRC_FADDR:
+ s->faddr = value & 0x7f;
+ break;
+ case MUSB_HDRC_POWER:
+ s->power = (value & 0xef) | (s->power & 0x10);
+ /* MGC_M_POWER_RESET is also read-only in Peripheral Mode */
+ if ((value & MGC_M_POWER_RESET) && s->port.dev) {
+ usb_device_reset(s->port.dev);
+ /* Negotiate high-speed operation if MGC_M_POWER_HSENAB is set. */
+ if ((value & MGC_M_POWER_HSENAB) &&
+ s->port.dev->speed == USB_SPEED_HIGH)
+ s->power |= MGC_M_POWER_HSMODE; /* Success */
+ /* Restart frame counting. */
+ }
+ if (value & MGC_M_POWER_SUSPENDM) {
+ /* When all transfers finish, suspend and if MGC_M_POWER_ENSUSPEND
+ * is set, also go into low power mode. Frame counting stops. */
+ /* XXX: Cleared when the interrupt register is read */
+ }
+ if (value & MGC_M_POWER_RESUME) {
+ /* Wait 20ms and signal resuming on the bus. Frame counting
+ * restarts. */
+ }
+ break;
+ case MUSB_HDRC_INTRUSB:
+ break;
+ case MUSB_HDRC_INTRUSBE:
+ s->mask = value & 0xff;
+ break;
+ case MUSB_HDRC_INDEX:
+ s->idx = value & 0xf;
+ break;
+ case MUSB_HDRC_TESTMODE:
+ break;
+
+ case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf):
+ musb_ep_writeb(s, s->idx, addr & 0xf, value);
+ break;
+
+ case MUSB_HDRC_DEVCTL:
+ s->session = !!(value & MGC_M_DEVCTL_SESSION);
+ musb_session_update(s,
+ !!s->port.dev,
+ !!(s->devctl & MGC_M_DEVCTL_SESSION));
+
+ /* It seems this is the only R/W bit in this register? */
+ s->devctl &= ~MGC_M_DEVCTL_SESSION;
+ s->devctl |= value & MGC_M_DEVCTL_SESSION;
+ break;
+
+ case MUSB_HDRC_TXFIFOSZ:
+ case MUSB_HDRC_RXFIFOSZ:
+ case MUSB_HDRC_VCTRL:
+ /* TODO */
+ break;
+
+ case (MUSB_HDRC_VCTRL | 1):
+ case (MUSB_HDRC_DEVCTL | 1):
+ break;
+
+ case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f):
+ ep = (addr >> 3) & 0xf;
+ musb_busctl_writeb(s, ep, addr & 0x7, value);
+ break;
+
+ case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff):
+ ep = (addr >> 4) & 0xf;
+ musb_ep_writeb(s, ep, addr & 0xf, value);
+ break;
+
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ musb_write_fifo(s->ep + ep, value & 0xff);
+ break;
+
+ default:
+ TRACE("unknown register 0x%02x", (int) addr);
+ break;
+ };
+}
+
+static uint32_t musb_readh(void *opaque, hwaddr addr)
+{
+ MUSBState *s = (MUSBState *) opaque;
+ int ep, i;
+ uint16_t ret;
+
+ switch (addr) {
+ case MUSB_HDRC_INTRTX:
+ ret = s->tx_intr;
+ /* Auto clear */
+ for (i = 0; i < sizeof(ret) * 8; i ++)
+ if (ret & (1 << i))
+ musb_tx_intr_set(s, i, 0);
+ return ret;
+ case MUSB_HDRC_INTRRX:
+ ret = s->rx_intr;
+ /* Auto clear */
+ for (i = 0; i < sizeof(ret) * 8; i ++)
+ if (ret & (1 << i))
+ musb_rx_intr_set(s, i, 0);
+ return ret;
+ case MUSB_HDRC_INTRTXE:
+ return s->tx_mask;
+ case MUSB_HDRC_INTRRXE:
+ return s->rx_mask;
+
+ case MUSB_HDRC_FRAME:
+ /* TODO */
+ return 0x0000;
+ case MUSB_HDRC_TXFIFOADDR:
+ return s->ep[s->idx].fifoaddr[0];
+ case MUSB_HDRC_RXFIFOADDR:
+ return s->ep[s->idx].fifoaddr[1];
+
+ case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf):
+ return musb_ep_readh(s, s->idx, addr & 0xf);
+
+ case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f):
+ ep = (addr >> 3) & 0xf;
+ return musb_busctl_readh(s, ep, addr & 0x7);
+
+ case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff):
+ ep = (addr >> 4) & 0xf;
+ return musb_ep_readh(s, ep, addr & 0xf);
+
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ return (musb_read_fifo(s->ep + ep) | musb_read_fifo(s->ep + ep) << 8);
+
+ default:
+ return musb_readb(s, addr) | (musb_readb(s, addr | 1) << 8);
+ };
+}
+
+static void musb_writeh(void *opaque, hwaddr addr, uint32_t value)
+{
+ MUSBState *s = (MUSBState *) opaque;
+ int ep;
+
+ switch (addr) {
+ case MUSB_HDRC_INTRTXE:
+ s->tx_mask = value;
+ /* XXX: the masks seem to apply on the raising edge like with
+ * edge-triggered interrupts, thus no need to update. I may be
+ * wrong though. */
+ break;
+ case MUSB_HDRC_INTRRXE:
+ s->rx_mask = value;
+ break;
+
+ case MUSB_HDRC_FRAME:
+ /* TODO */
+ break;
+ case MUSB_HDRC_TXFIFOADDR:
+ s->ep[s->idx].fifoaddr[0] = value;
+ s->ep[s->idx].buf[0] =
+ s->buf + ((value << 3) & 0x7ff );
+ break;
+ case MUSB_HDRC_RXFIFOADDR:
+ s->ep[s->idx].fifoaddr[1] = value;
+ s->ep[s->idx].buf[1] =
+ s->buf + ((value << 3) & 0x7ff);
+ break;
+
+ case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf):
+ musb_ep_writeh(s, s->idx, addr & 0xf, value);
+ break;
+
+ case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f):
+ ep = (addr >> 3) & 0xf;
+ musb_busctl_writeh(s, ep, addr & 0x7, value);
+ break;
+
+ case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff):
+ ep = (addr >> 4) & 0xf;
+ musb_ep_writeh(s, ep, addr & 0xf, value);
+ break;
+
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ musb_write_fifo(s->ep + ep, value & 0xff);
+ musb_write_fifo(s->ep + ep, (value >> 8) & 0xff);
+ break;
+
+ default:
+ musb_writeb(s, addr, value & 0xff);
+ musb_writeb(s, addr | 1, value >> 8);
+ };
+}
+
+static uint32_t musb_readw(void *opaque, hwaddr addr)
+{
+ MUSBState *s = (MUSBState *) opaque;
+ int ep;
+
+ switch (addr) {
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ return ( musb_read_fifo(s->ep + ep) |
+ musb_read_fifo(s->ep + ep) << 8 |
+ musb_read_fifo(s->ep + ep) << 16 |
+ musb_read_fifo(s->ep + ep) << 24 );
+ default:
+ TRACE("unknown register 0x%02x", (int) addr);
+ return 0x00000000;
+ };
+}
+
+static void musb_writew(void *opaque, hwaddr addr, uint32_t value)
+{
+ MUSBState *s = (MUSBState *) opaque;
+ int ep;
+
+ switch (addr) {
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ musb_write_fifo(s->ep + ep, value & 0xff);
+ musb_write_fifo(s->ep + ep, (value >> 8 ) & 0xff);
+ musb_write_fifo(s->ep + ep, (value >> 16) & 0xff);
+ musb_write_fifo(s->ep + ep, (value >> 24) & 0xff);
+ break;
+ default:
+ TRACE("unknown register 0x%02x", (int) addr);
+ break;
+ };
+}
+
+CPUReadMemoryFunc * const musb_read[] = {
+ musb_readb,
+ musb_readh,
+ musb_readw,
+};
+
+CPUWriteMemoryFunc * const musb_write[] = {
+ musb_writeb,
+ musb_writeh,
+ musb_writew,
+};
diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c
new file mode 100644
index 00000000..7d658180
--- /dev/null
+++ b/hw/usb/hcd-ohci.c
@@ -0,0 +1,2157 @@
+/*
+ * QEMU USB OHCI Emulation
+ * Copyright (c) 2004 Gianni Tedesco
+ * Copyright (c) 2006 CodeSourcery
+ * Copyright (c) 2006 Openedhand Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * TODO:
+ * o Isochronous transfers
+ * o Allocate bandwidth in frames properly
+ * o Disable timers when nothing needs to be done, or remove timer usage
+ * all together.
+ * o BIOS work to boot from USB storage
+*/
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/usb.h"
+#include "hw/pci/pci.h"
+#include "hw/sysbus.h"
+#include "hw/qdev-dma.h"
+#include "trace.h"
+
+/* This causes frames to occur 1000x slower */
+//#define OHCI_TIME_WARP 1
+
+/* Number of Downstream Ports on the root hub. */
+
+#define OHCI_MAX_PORTS 15
+
+static int64_t usb_frame_time;
+static int64_t usb_bit_time;
+
+typedef struct OHCIPort {
+ USBPort port;
+ uint32_t ctrl;
+} OHCIPort;
+
+typedef struct {
+ USBBus bus;
+ qemu_irq irq;
+ MemoryRegion mem;
+ AddressSpace *as;
+ int num_ports;
+ const char *name;
+
+ QEMUTimer *eof_timer;
+ int64_t sof_time;
+
+ /* OHCI state */
+ /* Control partition */
+ uint32_t ctl, status;
+ uint32_t intr_status;
+ uint32_t intr;
+
+ /* memory pointer partition */
+ uint32_t hcca;
+ uint32_t ctrl_head, ctrl_cur;
+ uint32_t bulk_head, bulk_cur;
+ uint32_t per_cur;
+ uint32_t done;
+ int32_t done_count;
+
+ /* Frame counter partition */
+ uint16_t fsmps;
+ uint8_t fit;
+ uint16_t fi;
+ uint8_t frt;
+ uint16_t frame_number;
+ uint16_t padding;
+ uint32_t pstart;
+ uint32_t lst;
+
+ /* Root Hub partition */
+ uint32_t rhdesc_a, rhdesc_b;
+ uint32_t rhstatus;
+ OHCIPort rhport[OHCI_MAX_PORTS];
+
+ /* PXA27x Non-OHCI events */
+ uint32_t hstatus;
+ uint32_t hmask;
+ uint32_t hreset;
+ uint32_t htest;
+
+ /* SM501 local memory offset */
+ dma_addr_t localmem_base;
+
+ /* Active packets. */
+ uint32_t old_ctl;
+ USBPacket usb_packet;
+ uint8_t usb_buf[8192];
+ uint32_t async_td;
+ bool async_complete;
+
+} OHCIState;
+
+/* Host Controller Communications Area */
+struct ohci_hcca {
+ uint32_t intr[32];
+ uint16_t frame, pad;
+ uint32_t done;
+};
+#define HCCA_WRITEBACK_OFFSET offsetof(struct ohci_hcca, frame)
+#define HCCA_WRITEBACK_SIZE 8 /* frame, pad, done */
+
+#define ED_WBACK_OFFSET offsetof(struct ohci_ed, head)
+#define ED_WBACK_SIZE 4
+
+static void ohci_bus_stop(OHCIState *ohci);
+static void ohci_async_cancel_device(OHCIState *ohci, USBDevice *dev);
+
+/* Bitfields for the first word of an Endpoint Desciptor. */
+#define OHCI_ED_FA_SHIFT 0
+#define OHCI_ED_FA_MASK (0x7f<<OHCI_ED_FA_SHIFT)
+#define OHCI_ED_EN_SHIFT 7
+#define OHCI_ED_EN_MASK (0xf<<OHCI_ED_EN_SHIFT)
+#define OHCI_ED_D_SHIFT 11
+#define OHCI_ED_D_MASK (3<<OHCI_ED_D_SHIFT)
+#define OHCI_ED_S (1<<13)
+#define OHCI_ED_K (1<<14)
+#define OHCI_ED_F (1<<15)
+#define OHCI_ED_MPS_SHIFT 16
+#define OHCI_ED_MPS_MASK (0x7ff<<OHCI_ED_MPS_SHIFT)
+
+/* Flags in the head field of an Endpoint Desciptor. */
+#define OHCI_ED_H 1
+#define OHCI_ED_C 2
+
+/* Bitfields for the first word of a Transfer Desciptor. */
+#define OHCI_TD_R (1<<18)
+#define OHCI_TD_DP_SHIFT 19
+#define OHCI_TD_DP_MASK (3<<OHCI_TD_DP_SHIFT)
+#define OHCI_TD_DI_SHIFT 21
+#define OHCI_TD_DI_MASK (7<<OHCI_TD_DI_SHIFT)
+#define OHCI_TD_T0 (1<<24)
+#define OHCI_TD_T1 (1<<25)
+#define OHCI_TD_EC_SHIFT 26
+#define OHCI_TD_EC_MASK (3<<OHCI_TD_EC_SHIFT)
+#define OHCI_TD_CC_SHIFT 28
+#define OHCI_TD_CC_MASK (0xf<<OHCI_TD_CC_SHIFT)
+
+/* Bitfields for the first word of an Isochronous Transfer Desciptor. */
+/* CC & DI - same as in the General Transfer Desciptor */
+#define OHCI_TD_SF_SHIFT 0
+#define OHCI_TD_SF_MASK (0xffff<<OHCI_TD_SF_SHIFT)
+#define OHCI_TD_FC_SHIFT 24
+#define OHCI_TD_FC_MASK (7<<OHCI_TD_FC_SHIFT)
+
+/* Isochronous Transfer Desciptor - Offset / PacketStatusWord */
+#define OHCI_TD_PSW_CC_SHIFT 12
+#define OHCI_TD_PSW_CC_MASK (0xf<<OHCI_TD_PSW_CC_SHIFT)
+#define OHCI_TD_PSW_SIZE_SHIFT 0
+#define OHCI_TD_PSW_SIZE_MASK (0xfff<<OHCI_TD_PSW_SIZE_SHIFT)
+
+#define OHCI_PAGE_MASK 0xfffff000
+#define OHCI_OFFSET_MASK 0xfff
+
+#define OHCI_DPTR_MASK 0xfffffff0
+
+#define OHCI_BM(val, field) \
+ (((val) & OHCI_##field##_MASK) >> OHCI_##field##_SHIFT)
+
+#define OHCI_SET_BM(val, field, newval) do { \
+ val &= ~OHCI_##field##_MASK; \
+ val |= ((newval) << OHCI_##field##_SHIFT) & OHCI_##field##_MASK; \
+ } while(0)
+
+/* endpoint descriptor */
+struct ohci_ed {
+ uint32_t flags;
+ uint32_t tail;
+ uint32_t head;
+ uint32_t next;
+};
+
+/* General transfer descriptor */
+struct ohci_td {
+ uint32_t flags;
+ uint32_t cbp;
+ uint32_t next;
+ uint32_t be;
+};
+
+/* Isochronous transfer descriptor */
+struct ohci_iso_td {
+ uint32_t flags;
+ uint32_t bp;
+ uint32_t next;
+ uint32_t be;
+ uint16_t offset[8];
+};
+
+#define USB_HZ 12000000
+
+/* OHCI Local stuff */
+#define OHCI_CTL_CBSR ((1<<0)|(1<<1))
+#define OHCI_CTL_PLE (1<<2)
+#define OHCI_CTL_IE (1<<3)
+#define OHCI_CTL_CLE (1<<4)
+#define OHCI_CTL_BLE (1<<5)
+#define OHCI_CTL_HCFS ((1<<6)|(1<<7))
+#define OHCI_USB_RESET 0x00
+#define OHCI_USB_RESUME 0x40
+#define OHCI_USB_OPERATIONAL 0x80
+#define OHCI_USB_SUSPEND 0xc0
+#define OHCI_CTL_IR (1<<8)
+#define OHCI_CTL_RWC (1<<9)
+#define OHCI_CTL_RWE (1<<10)
+
+#define OHCI_STATUS_HCR (1<<0)
+#define OHCI_STATUS_CLF (1<<1)
+#define OHCI_STATUS_BLF (1<<2)
+#define OHCI_STATUS_OCR (1<<3)
+#define OHCI_STATUS_SOC ((1<<6)|(1<<7))
+
+#define OHCI_INTR_SO (1U<<0) /* Scheduling overrun */
+#define OHCI_INTR_WD (1U<<1) /* HcDoneHead writeback */
+#define OHCI_INTR_SF (1U<<2) /* Start of frame */
+#define OHCI_INTR_RD (1U<<3) /* Resume detect */
+#define OHCI_INTR_UE (1U<<4) /* Unrecoverable error */
+#define OHCI_INTR_FNO (1U<<5) /* Frame number overflow */
+#define OHCI_INTR_RHSC (1U<<6) /* Root hub status change */
+#define OHCI_INTR_OC (1U<<30) /* Ownership change */
+#define OHCI_INTR_MIE (1U<<31) /* Master Interrupt Enable */
+
+#define OHCI_HCCA_SIZE 0x100
+#define OHCI_HCCA_MASK 0xffffff00
+
+#define OHCI_EDPTR_MASK 0xfffffff0
+
+#define OHCI_FMI_FI 0x00003fff
+#define OHCI_FMI_FSMPS 0xffff0000
+#define OHCI_FMI_FIT 0x80000000
+
+#define OHCI_FR_RT (1U<<31)
+
+#define OHCI_LS_THRESH 0x628
+
+#define OHCI_RHA_RW_MASK 0x00000000 /* Mask of supported features. */
+#define OHCI_RHA_PSM (1<<8)
+#define OHCI_RHA_NPS (1<<9)
+#define OHCI_RHA_DT (1<<10)
+#define OHCI_RHA_OCPM (1<<11)
+#define OHCI_RHA_NOCP (1<<12)
+#define OHCI_RHA_POTPGT_MASK 0xff000000
+
+#define OHCI_RHS_LPS (1U<<0)
+#define OHCI_RHS_OCI (1U<<1)
+#define OHCI_RHS_DRWE (1U<<15)
+#define OHCI_RHS_LPSC (1U<<16)
+#define OHCI_RHS_OCIC (1U<<17)
+#define OHCI_RHS_CRWE (1U<<31)
+
+#define OHCI_PORT_CCS (1<<0)
+#define OHCI_PORT_PES (1<<1)
+#define OHCI_PORT_PSS (1<<2)
+#define OHCI_PORT_POCI (1<<3)
+#define OHCI_PORT_PRS (1<<4)
+#define OHCI_PORT_PPS (1<<8)
+#define OHCI_PORT_LSDA (1<<9)
+#define OHCI_PORT_CSC (1<<16)
+#define OHCI_PORT_PESC (1<<17)
+#define OHCI_PORT_PSSC (1<<18)
+#define OHCI_PORT_OCIC (1<<19)
+#define OHCI_PORT_PRSC (1<<20)
+#define OHCI_PORT_WTC (OHCI_PORT_CSC|OHCI_PORT_PESC|OHCI_PORT_PSSC \
+ |OHCI_PORT_OCIC|OHCI_PORT_PRSC)
+
+#define OHCI_TD_DIR_SETUP 0x0
+#define OHCI_TD_DIR_OUT 0x1
+#define OHCI_TD_DIR_IN 0x2
+#define OHCI_TD_DIR_RESERVED 0x3
+
+#define OHCI_CC_NOERROR 0x0
+#define OHCI_CC_CRC 0x1
+#define OHCI_CC_BITSTUFFING 0x2
+#define OHCI_CC_DATATOGGLEMISMATCH 0x3
+#define OHCI_CC_STALL 0x4
+#define OHCI_CC_DEVICENOTRESPONDING 0x5
+#define OHCI_CC_PIDCHECKFAILURE 0x6
+#define OHCI_CC_UNDEXPETEDPID 0x7
+#define OHCI_CC_DATAOVERRUN 0x8
+#define OHCI_CC_DATAUNDERRUN 0x9
+#define OHCI_CC_BUFFEROVERRUN 0xc
+#define OHCI_CC_BUFFERUNDERRUN 0xd
+
+#define OHCI_HRESET_FSBIR (1 << 0)
+
+static void ohci_die(OHCIState *ohci);
+
+/* Update IRQ levels */
+static inline void ohci_intr_update(OHCIState *ohci)
+{
+ int level = 0;
+
+ if ((ohci->intr & OHCI_INTR_MIE) &&
+ (ohci->intr_status & ohci->intr))
+ level = 1;
+
+ qemu_set_irq(ohci->irq, level);
+}
+
+/* Set an interrupt */
+static inline void ohci_set_interrupt(OHCIState *ohci, uint32_t intr)
+{
+ ohci->intr_status |= intr;
+ ohci_intr_update(ohci);
+}
+
+/* Attach or detach a device on a root hub port. */
+static void ohci_attach(USBPort *port1)
+{
+ OHCIState *s = port1->opaque;
+ OHCIPort *port = &s->rhport[port1->index];
+ uint32_t old_state = port->ctrl;
+
+ /* set connect status */
+ port->ctrl |= OHCI_PORT_CCS | OHCI_PORT_CSC;
+
+ /* update speed */
+ if (port->port.dev->speed == USB_SPEED_LOW) {
+ port->ctrl |= OHCI_PORT_LSDA;
+ } else {
+ port->ctrl &= ~OHCI_PORT_LSDA;
+ }
+
+ /* notify of remote-wakeup */
+ if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) {
+ ohci_set_interrupt(s, OHCI_INTR_RD);
+ }
+
+ trace_usb_ohci_port_attach(port1->index);
+
+ if (old_state != port->ctrl) {
+ ohci_set_interrupt(s, OHCI_INTR_RHSC);
+ }
+}
+
+static void ohci_detach(USBPort *port1)
+{
+ OHCIState *s = port1->opaque;
+ OHCIPort *port = &s->rhport[port1->index];
+ uint32_t old_state = port->ctrl;
+
+ ohci_async_cancel_device(s, port1->dev);
+
+ /* set connect status */
+ if (port->ctrl & OHCI_PORT_CCS) {
+ port->ctrl &= ~OHCI_PORT_CCS;
+ port->ctrl |= OHCI_PORT_CSC;
+ }
+ /* disable port */
+ if (port->ctrl & OHCI_PORT_PES) {
+ port->ctrl &= ~OHCI_PORT_PES;
+ port->ctrl |= OHCI_PORT_PESC;
+ }
+ trace_usb_ohci_port_detach(port1->index);
+
+ if (old_state != port->ctrl) {
+ ohci_set_interrupt(s, OHCI_INTR_RHSC);
+ }
+}
+
+static void ohci_wakeup(USBPort *port1)
+{
+ OHCIState *s = port1->opaque;
+ OHCIPort *port = &s->rhport[port1->index];
+ uint32_t intr = 0;
+ if (port->ctrl & OHCI_PORT_PSS) {
+ trace_usb_ohci_port_wakeup(port1->index);
+ port->ctrl |= OHCI_PORT_PSSC;
+ port->ctrl &= ~OHCI_PORT_PSS;
+ intr = OHCI_INTR_RHSC;
+ }
+ /* Note that the controller can be suspended even if this port is not */
+ if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) {
+ trace_usb_ohci_remote_wakeup(s->name);
+ /* This is the one state transition the controller can do by itself */
+ s->ctl &= ~OHCI_CTL_HCFS;
+ s->ctl |= OHCI_USB_RESUME;
+ /* In suspend mode only ResumeDetected is possible, not RHSC:
+ * see the OHCI spec 5.1.2.3.
+ */
+ intr = OHCI_INTR_RD;
+ }
+ ohci_set_interrupt(s, intr);
+}
+
+static void ohci_child_detach(USBPort *port1, USBDevice *child)
+{
+ OHCIState *s = port1->opaque;
+
+ ohci_async_cancel_device(s, child);
+}
+
+static USBDevice *ohci_find_device(OHCIState *ohci, uint8_t addr)
+{
+ USBDevice *dev;
+ int i;
+
+ for (i = 0; i < ohci->num_ports; i++) {
+ if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0) {
+ continue;
+ }
+ dev = usb_find_device(&ohci->rhport[i].port, addr);
+ if (dev != NULL) {
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+static void ohci_stop_endpoints(OHCIState *ohci)
+{
+ USBDevice *dev;
+ int i, j;
+
+ for (i = 0; i < ohci->num_ports; i++) {
+ dev = ohci->rhport[i].port.dev;
+ if (dev && dev->attached) {
+ usb_device_ep_stopped(dev, &dev->ep_ctl);
+ for (j = 0; j < USB_MAX_ENDPOINTS; j++) {
+ usb_device_ep_stopped(dev, &dev->ep_in[j]);
+ usb_device_ep_stopped(dev, &dev->ep_out[j]);
+ }
+ }
+ }
+}
+
+/* Reset the controller */
+static void ohci_reset(void *opaque)
+{
+ OHCIState *ohci = opaque;
+ OHCIPort *port;
+ int i;
+
+ ohci_bus_stop(ohci);
+ ohci->ctl = 0;
+ ohci->old_ctl = 0;
+ ohci->status = 0;
+ ohci->intr_status = 0;
+ ohci->intr = OHCI_INTR_MIE;
+
+ ohci->hcca = 0;
+ ohci->ctrl_head = ohci->ctrl_cur = 0;
+ ohci->bulk_head = ohci->bulk_cur = 0;
+ ohci->per_cur = 0;
+ ohci->done = 0;
+ ohci->done_count = 7;
+
+ /* FSMPS is marked TBD in OCHI 1.0, what gives ffs?
+ * I took the value linux sets ...
+ */
+ ohci->fsmps = 0x2778;
+ ohci->fi = 0x2edf;
+ ohci->fit = 0;
+ ohci->frt = 0;
+ ohci->frame_number = 0;
+ ohci->pstart = 0;
+ ohci->lst = OHCI_LS_THRESH;
+
+ ohci->rhdesc_a = OHCI_RHA_NPS | ohci->num_ports;
+ ohci->rhdesc_b = 0x0; /* Impl. specific */
+ ohci->rhstatus = 0;
+
+ for (i = 0; i < ohci->num_ports; i++)
+ {
+ port = &ohci->rhport[i];
+ port->ctrl = 0;
+ if (port->port.dev && port->port.dev->attached) {
+ usb_port_reset(&port->port);
+ }
+ }
+ if (ohci->async_td) {
+ usb_cancel_packet(&ohci->usb_packet);
+ ohci->async_td = 0;
+ }
+ ohci_stop_endpoints(ohci);
+ trace_usb_ohci_reset(ohci->name);
+}
+
+/* Get an array of dwords from main memory */
+static inline int get_dwords(OHCIState *ohci,
+ dma_addr_t addr, uint32_t *buf, int num)
+{
+ int i;
+
+ addr += ohci->localmem_base;
+
+ for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ if (dma_memory_read(ohci->as, addr, buf, sizeof(*buf))) {
+ return -1;
+ }
+ *buf = le32_to_cpu(*buf);
+ }
+
+ return 0;
+}
+
+/* Put an array of dwords in to main memory */
+static inline int put_dwords(OHCIState *ohci,
+ dma_addr_t addr, uint32_t *buf, int num)
+{
+ int i;
+
+ addr += ohci->localmem_base;
+
+ for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ uint32_t tmp = cpu_to_le32(*buf);
+ if (dma_memory_write(ohci->as, addr, &tmp, sizeof(tmp))) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Get an array of words from main memory */
+static inline int get_words(OHCIState *ohci,
+ dma_addr_t addr, uint16_t *buf, int num)
+{
+ int i;
+
+ addr += ohci->localmem_base;
+
+ for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ if (dma_memory_read(ohci->as, addr, buf, sizeof(*buf))) {
+ return -1;
+ }
+ *buf = le16_to_cpu(*buf);
+ }
+
+ return 0;
+}
+
+/* Put an array of words in to main memory */
+static inline int put_words(OHCIState *ohci,
+ dma_addr_t addr, uint16_t *buf, int num)
+{
+ int i;
+
+ addr += ohci->localmem_base;
+
+ for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ uint16_t tmp = cpu_to_le16(*buf);
+ if (dma_memory_write(ohci->as, addr, &tmp, sizeof(tmp))) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static inline int ohci_read_ed(OHCIState *ohci,
+ dma_addr_t addr, struct ohci_ed *ed)
+{
+ return get_dwords(ohci, addr, (uint32_t *)ed, sizeof(*ed) >> 2);
+}
+
+static inline int ohci_read_td(OHCIState *ohci,
+ dma_addr_t addr, struct ohci_td *td)
+{
+ return get_dwords(ohci, addr, (uint32_t *)td, sizeof(*td) >> 2);
+}
+
+static inline int ohci_read_iso_td(OHCIState *ohci,
+ dma_addr_t addr, struct ohci_iso_td *td)
+{
+ return get_dwords(ohci, addr, (uint32_t *)td, 4) ||
+ get_words(ohci, addr + 16, td->offset, 8);
+}
+
+static inline int ohci_read_hcca(OHCIState *ohci,
+ dma_addr_t addr, struct ohci_hcca *hcca)
+{
+ return dma_memory_read(ohci->as, addr + ohci->localmem_base,
+ hcca, sizeof(*hcca));
+}
+
+static inline int ohci_put_ed(OHCIState *ohci,
+ dma_addr_t addr, struct ohci_ed *ed)
+{
+ /* ed->tail is under control of the HCD.
+ * Since just ed->head is changed by HC, just write back this
+ */
+
+ return put_dwords(ohci, addr + ED_WBACK_OFFSET,
+ (uint32_t *)((char *)ed + ED_WBACK_OFFSET),
+ ED_WBACK_SIZE >> 2);
+}
+
+static inline int ohci_put_td(OHCIState *ohci,
+ dma_addr_t addr, struct ohci_td *td)
+{
+ return put_dwords(ohci, addr, (uint32_t *)td, sizeof(*td) >> 2);
+}
+
+static inline int ohci_put_iso_td(OHCIState *ohci,
+ dma_addr_t addr, struct ohci_iso_td *td)
+{
+ return put_dwords(ohci, addr, (uint32_t *)td, 4) ||
+ put_words(ohci, addr + 16, td->offset, 8);
+}
+
+static inline int ohci_put_hcca(OHCIState *ohci,
+ dma_addr_t addr, struct ohci_hcca *hcca)
+{
+ return dma_memory_write(ohci->as,
+ addr + ohci->localmem_base + HCCA_WRITEBACK_OFFSET,
+ (char *)hcca + HCCA_WRITEBACK_OFFSET,
+ HCCA_WRITEBACK_SIZE);
+}
+
+/* Read/Write the contents of a TD from/to main memory. */
+static int ohci_copy_td(OHCIState *ohci, struct ohci_td *td,
+ uint8_t *buf, int len, DMADirection dir)
+{
+ dma_addr_t ptr, n;
+
+ ptr = td->cbp;
+ n = 0x1000 - (ptr & 0xfff);
+ if (n > len)
+ n = len;
+
+ if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf, n, dir)) {
+ return -1;
+ }
+ if (n == len) {
+ return 0;
+ }
+ ptr = td->be & ~0xfffu;
+ buf += n;
+ if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf,
+ len - n, dir)) {
+ return -1;
+ }
+ return 0;
+}
+
+/* Read/Write the contents of an ISO TD from/to main memory. */
+static int ohci_copy_iso_td(OHCIState *ohci,
+ uint32_t start_addr, uint32_t end_addr,
+ uint8_t *buf, int len, DMADirection dir)
+{
+ dma_addr_t ptr, n;
+
+ ptr = start_addr;
+ n = 0x1000 - (ptr & 0xfff);
+ if (n > len)
+ n = len;
+
+ if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf, n, dir)) {
+ return -1;
+ }
+ if (n == len) {
+ return 0;
+ }
+ ptr = end_addr & ~0xfffu;
+ buf += n;
+ if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf,
+ len - n, dir)) {
+ return -1;
+ }
+ return 0;
+}
+
+static void ohci_process_lists(OHCIState *ohci, int completion);
+
+static void ohci_async_complete_packet(USBPort *port, USBPacket *packet)
+{
+ OHCIState *ohci = container_of(packet, OHCIState, usb_packet);
+
+ trace_usb_ohci_async_complete();
+ ohci->async_complete = true;
+ ohci_process_lists(ohci, 1);
+}
+
+#define USUB(a, b) ((int16_t)((uint16_t)(a) - (uint16_t)(b)))
+
+static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed,
+ int completion)
+{
+ int dir;
+ size_t len = 0;
+ const char *str = NULL;
+ int pid;
+ int ret;
+ int i;
+ USBDevice *dev;
+ USBEndpoint *ep;
+ struct ohci_iso_td iso_td;
+ uint32_t addr;
+ uint16_t starting_frame;
+ int16_t relative_frame_number;
+ int frame_count;
+ uint32_t start_offset, next_offset, end_offset = 0;
+ uint32_t start_addr, end_addr;
+
+ addr = ed->head & OHCI_DPTR_MASK;
+
+ if (ohci_read_iso_td(ohci, addr, &iso_td)) {
+ trace_usb_ohci_iso_td_read_failed(addr);
+ ohci_die(ohci);
+ return 0;
+ }
+
+ starting_frame = OHCI_BM(iso_td.flags, TD_SF);
+ frame_count = OHCI_BM(iso_td.flags, TD_FC);
+ relative_frame_number = USUB(ohci->frame_number, starting_frame);
+
+ trace_usb_ohci_iso_td_head(
+ ed->head & OHCI_DPTR_MASK, ed->tail & OHCI_DPTR_MASK,
+ iso_td.flags, iso_td.bp, iso_td.next, iso_td.be,
+ ohci->frame_number, starting_frame,
+ frame_count, relative_frame_number);
+ trace_usb_ohci_iso_td_head_offset(
+ iso_td.offset[0], iso_td.offset[1],
+ iso_td.offset[2], iso_td.offset[3],
+ iso_td.offset[4], iso_td.offset[5],
+ iso_td.offset[6], iso_td.offset[7]);
+
+ if (relative_frame_number < 0) {
+ trace_usb_ohci_iso_td_relative_frame_number_neg(relative_frame_number);
+ return 1;
+ } else if (relative_frame_number > frame_count) {
+ /* ISO TD expired - retire the TD to the Done Queue and continue with
+ the next ISO TD of the same ED */
+ trace_usb_ohci_iso_td_relative_frame_number_big(relative_frame_number,
+ frame_count);
+ OHCI_SET_BM(iso_td.flags, TD_CC, OHCI_CC_DATAOVERRUN);
+ ed->head &= ~OHCI_DPTR_MASK;
+ ed->head |= (iso_td.next & OHCI_DPTR_MASK);
+ iso_td.next = ohci->done;
+ ohci->done = addr;
+ i = OHCI_BM(iso_td.flags, TD_DI);
+ if (i < ohci->done_count)
+ ohci->done_count = i;
+ if (ohci_put_iso_td(ohci, addr, &iso_td)) {
+ ohci_die(ohci);
+ return 1;
+ }
+ return 0;
+ }
+
+ dir = OHCI_BM(ed->flags, ED_D);
+ switch (dir) {
+ case OHCI_TD_DIR_IN:
+ str = "in";
+ pid = USB_TOKEN_IN;
+ break;
+ case OHCI_TD_DIR_OUT:
+ str = "out";
+ pid = USB_TOKEN_OUT;
+ break;
+ case OHCI_TD_DIR_SETUP:
+ str = "setup";
+ pid = USB_TOKEN_SETUP;
+ break;
+ default:
+ trace_usb_ohci_iso_td_bad_direction(dir);
+ return 1;
+ }
+
+ if (!iso_td.bp || !iso_td.be) {
+ trace_usb_ohci_iso_td_bad_bp_be(iso_td.bp, iso_td.be);
+ return 1;
+ }
+
+ start_offset = iso_td.offset[relative_frame_number];
+ next_offset = iso_td.offset[relative_frame_number + 1];
+
+ if (!(OHCI_BM(start_offset, TD_PSW_CC) & 0xe) ||
+ ((relative_frame_number < frame_count) &&
+ !(OHCI_BM(next_offset, TD_PSW_CC) & 0xe))) {
+ trace_usb_ohci_iso_td_bad_cc_not_accessed(start_offset, next_offset);
+ return 1;
+ }
+
+ if ((relative_frame_number < frame_count) && (start_offset > next_offset)) {
+ trace_usb_ohci_iso_td_bad_cc_overrun(start_offset, next_offset);
+ return 1;
+ }
+
+ if ((start_offset & 0x1000) == 0) {
+ start_addr = (iso_td.bp & OHCI_PAGE_MASK) |
+ (start_offset & OHCI_OFFSET_MASK);
+ } else {
+ start_addr = (iso_td.be & OHCI_PAGE_MASK) |
+ (start_offset & OHCI_OFFSET_MASK);
+ }
+
+ if (relative_frame_number < frame_count) {
+ end_offset = next_offset - 1;
+ if ((end_offset & 0x1000) == 0) {
+ end_addr = (iso_td.bp & OHCI_PAGE_MASK) |
+ (end_offset & OHCI_OFFSET_MASK);
+ } else {
+ end_addr = (iso_td.be & OHCI_PAGE_MASK) |
+ (end_offset & OHCI_OFFSET_MASK);
+ }
+ } else {
+ /* Last packet in the ISO TD */
+ end_addr = iso_td.be;
+ }
+
+ if ((start_addr & OHCI_PAGE_MASK) != (end_addr & OHCI_PAGE_MASK)) {
+ len = (end_addr & OHCI_OFFSET_MASK) + 0x1001
+ - (start_addr & OHCI_OFFSET_MASK);
+ } else {
+ len = end_addr - start_addr + 1;
+ }
+
+ if (len && dir != OHCI_TD_DIR_IN) {
+ if (ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, len,
+ DMA_DIRECTION_TO_DEVICE)) {
+ ohci_die(ohci);
+ return 1;
+ }
+ }
+
+ if (!completion) {
+ bool int_req = relative_frame_number == frame_count &&
+ OHCI_BM(iso_td.flags, TD_DI) == 0;
+ dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA));
+ ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN));
+ usb_packet_setup(&ohci->usb_packet, pid, ep, 0, addr, false, int_req);
+ usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, len);
+ usb_handle_packet(dev, &ohci->usb_packet);
+ if (ohci->usb_packet.status == USB_RET_ASYNC) {
+ usb_device_flush_ep_queue(dev, ep);
+ return 1;
+ }
+ }
+ if (ohci->usb_packet.status == USB_RET_SUCCESS) {
+ ret = ohci->usb_packet.actual_length;
+ } else {
+ ret = ohci->usb_packet.status;
+ }
+
+ trace_usb_ohci_iso_td_so(start_offset, end_offset, start_addr, end_addr,
+ str, len, ret);
+
+ /* Writeback */
+ if (dir == OHCI_TD_DIR_IN && ret >= 0 && ret <= len) {
+ /* IN transfer succeeded */
+ if (ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, ret,
+ DMA_DIRECTION_FROM_DEVICE)) {
+ ohci_die(ohci);
+ return 1;
+ }
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
+ OHCI_CC_NOERROR);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, ret);
+ } else if (dir == OHCI_TD_DIR_OUT && ret == len) {
+ /* OUT transfer succeeded */
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
+ OHCI_CC_NOERROR);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, 0);
+ } else {
+ if (ret > (ssize_t) len) {
+ trace_usb_ohci_iso_td_data_overrun(ret, len);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
+ OHCI_CC_DATAOVERRUN);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE,
+ len);
+ } else if (ret >= 0) {
+ trace_usb_ohci_iso_td_data_underrun(ret);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
+ OHCI_CC_DATAUNDERRUN);
+ } else {
+ switch (ret) {
+ case USB_RET_IOERROR:
+ case USB_RET_NODEV:
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
+ OHCI_CC_DEVICENOTRESPONDING);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE,
+ 0);
+ break;
+ case USB_RET_NAK:
+ case USB_RET_STALL:
+ trace_usb_ohci_iso_td_nak(ret);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
+ OHCI_CC_STALL);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE,
+ 0);
+ break;
+ default:
+ trace_usb_ohci_iso_td_bad_response(ret);
+ OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
+ OHCI_CC_UNDEXPETEDPID);
+ break;
+ }
+ }
+ }
+
+ if (relative_frame_number == frame_count) {
+ /* Last data packet of ISO TD - retire the TD to the Done Queue */
+ OHCI_SET_BM(iso_td.flags, TD_CC, OHCI_CC_NOERROR);
+ ed->head &= ~OHCI_DPTR_MASK;
+ ed->head |= (iso_td.next & OHCI_DPTR_MASK);
+ iso_td.next = ohci->done;
+ ohci->done = addr;
+ i = OHCI_BM(iso_td.flags, TD_DI);
+ if (i < ohci->done_count)
+ ohci->done_count = i;
+ }
+ if (ohci_put_iso_td(ohci, addr, &iso_td)) {
+ ohci_die(ohci);
+ }
+ return 1;
+}
+
+#ifdef trace_event_get_state
+static void ohci_td_pkt(const char *msg, const uint8_t *buf, size_t len)
+{
+ bool print16 = !!trace_event_get_state(TRACE_USB_OHCI_TD_PKT_SHORT);
+ bool printall = !!trace_event_get_state(TRACE_USB_OHCI_TD_PKT_FULL);
+ const int width = 16;
+ int i;
+ char tmp[3 * width + 1];
+ char *p = tmp;
+
+ if (!printall && !print16) {
+ return;
+ }
+
+ for (i = 0; ; i++) {
+ if (i && (!(i % width) || (i == len))) {
+ if (!printall) {
+ trace_usb_ohci_td_pkt_short(msg, tmp);
+ break;
+ }
+ trace_usb_ohci_td_pkt_full(msg, tmp);
+ p = tmp;
+ *p = 0;
+ }
+ if (i == len) {
+ break;
+ }
+
+ p += sprintf(p, " %.2x", buf[i]);
+ }
+}
+#else
+static void ohci_td_pkt(const char *msg, const uint8_t *buf, size_t len)
+{
+}
+#endif
+
+/* Service a transport descriptor.
+ Returns nonzero to terminate processing of this endpoint. */
+
+static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)
+{
+ int dir;
+ size_t len = 0, pktlen = 0;
+ const char *str = NULL;
+ int pid;
+ int ret;
+ int i;
+ USBDevice *dev;
+ USBEndpoint *ep;
+ struct ohci_td td;
+ uint32_t addr;
+ int flag_r;
+ int completion;
+
+ addr = ed->head & OHCI_DPTR_MASK;
+ /* See if this TD has already been submitted to the device. */
+ completion = (addr == ohci->async_td);
+ if (completion && !ohci->async_complete) {
+ trace_usb_ohci_td_skip_async();
+ return 1;
+ }
+ if (ohci_read_td(ohci, addr, &td)) {
+ trace_usb_ohci_td_read_error(addr);
+ ohci_die(ohci);
+ return 0;
+ }
+
+ dir = OHCI_BM(ed->flags, ED_D);
+ switch (dir) {
+ case OHCI_TD_DIR_OUT:
+ case OHCI_TD_DIR_IN:
+ /* Same value. */
+ break;
+ default:
+ dir = OHCI_BM(td.flags, TD_DP);
+ break;
+ }
+
+ switch (dir) {
+ case OHCI_TD_DIR_IN:
+ str = "in";
+ pid = USB_TOKEN_IN;
+ break;
+ case OHCI_TD_DIR_OUT:
+ str = "out";
+ pid = USB_TOKEN_OUT;
+ break;
+ case OHCI_TD_DIR_SETUP:
+ str = "setup";
+ pid = USB_TOKEN_SETUP;
+ break;
+ default:
+ trace_usb_ohci_td_bad_direction(dir);
+ return 1;
+ }
+ if (td.cbp && td.be) {
+ if ((td.cbp & 0xfffff000) != (td.be & 0xfffff000)) {
+ len = (td.be & 0xfff) + 0x1001 - (td.cbp & 0xfff);
+ } else {
+ len = (td.be - td.cbp) + 1;
+ }
+
+ pktlen = len;
+ if (len && dir != OHCI_TD_DIR_IN) {
+ /* The endpoint may not allow us to transfer it all now */
+ pktlen = (ed->flags & OHCI_ED_MPS_MASK) >> OHCI_ED_MPS_SHIFT;
+ if (pktlen > len) {
+ pktlen = len;
+ }
+ if (!completion) {
+ if (ohci_copy_td(ohci, &td, ohci->usb_buf, pktlen,
+ DMA_DIRECTION_TO_DEVICE)) {
+ ohci_die(ohci);
+ }
+ }
+ }
+ }
+
+ flag_r = (td.flags & OHCI_TD_R) != 0;
+ trace_usb_ohci_td_pkt_hdr(addr, (int64_t)pktlen, (int64_t)len, str,
+ flag_r, td.cbp, td.be);
+ ohci_td_pkt("OUT", ohci->usb_buf, pktlen);
+
+ if (completion) {
+ ohci->async_td = 0;
+ ohci->async_complete = false;
+ } else {
+ if (ohci->async_td) {
+ /* ??? The hardware should allow one active packet per
+ endpoint. We only allow one active packet per controller.
+ This should be sufficient as long as devices respond in a
+ timely manner.
+ */
+ trace_usb_ohci_td_too_many_pending();
+ return 1;
+ }
+ dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA));
+ ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN));
+ usb_packet_setup(&ohci->usb_packet, pid, ep, 0, addr, !flag_r,
+ OHCI_BM(td.flags, TD_DI) == 0);
+ usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, pktlen);
+ usb_handle_packet(dev, &ohci->usb_packet);
+ trace_usb_ohci_td_packet_status(ohci->usb_packet.status);
+
+ if (ohci->usb_packet.status == USB_RET_ASYNC) {
+ usb_device_flush_ep_queue(dev, ep);
+ ohci->async_td = addr;
+ return 1;
+ }
+ }
+ if (ohci->usb_packet.status == USB_RET_SUCCESS) {
+ ret = ohci->usb_packet.actual_length;
+ } else {
+ ret = ohci->usb_packet.status;
+ }
+
+ if (ret >= 0) {
+ if (dir == OHCI_TD_DIR_IN) {
+ if (ohci_copy_td(ohci, &td, ohci->usb_buf, ret,
+ DMA_DIRECTION_FROM_DEVICE)) {
+ ohci_die(ohci);
+ }
+ ohci_td_pkt("IN", ohci->usb_buf, pktlen);
+ } else {
+ ret = pktlen;
+ }
+ }
+
+ /* Writeback */
+ if (ret == pktlen || (dir == OHCI_TD_DIR_IN && ret >= 0 && flag_r)) {
+ /* Transmission succeeded. */
+ if (ret == len) {
+ td.cbp = 0;
+ } else {
+ if ((td.cbp & 0xfff) + ret > 0xfff) {
+ td.cbp = (td.be & ~0xfff) + ((td.cbp + ret) & 0xfff);
+ } else {
+ td.cbp += ret;
+ }
+ }
+ td.flags |= OHCI_TD_T1;
+ td.flags ^= OHCI_TD_T0;
+ OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_NOERROR);
+ OHCI_SET_BM(td.flags, TD_EC, 0);
+
+ if ((dir != OHCI_TD_DIR_IN) && (ret != len)) {
+ /* Partial packet transfer: TD not ready to retire yet */
+ goto exit_no_retire;
+ }
+
+ /* Setting ED_C is part of the TD retirement process */
+ ed->head &= ~OHCI_ED_C;
+ if (td.flags & OHCI_TD_T0)
+ ed->head |= OHCI_ED_C;
+ } else {
+ if (ret >= 0) {
+ trace_usb_ohci_td_underrun();
+ OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAUNDERRUN);
+ } else {
+ switch (ret) {
+ case USB_RET_IOERROR:
+ case USB_RET_NODEV:
+ trace_usb_ohci_td_dev_error();
+ OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DEVICENOTRESPONDING);
+ break;
+ case USB_RET_NAK:
+ trace_usb_ohci_td_nak();
+ return 1;
+ case USB_RET_STALL:
+ trace_usb_ohci_td_stall();
+ OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_STALL);
+ break;
+ case USB_RET_BABBLE:
+ trace_usb_ohci_td_babble();
+ OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAOVERRUN);
+ break;
+ default:
+ trace_usb_ohci_td_bad_device_response(ret);
+ OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_UNDEXPETEDPID);
+ OHCI_SET_BM(td.flags, TD_EC, 3);
+ break;
+ }
+ }
+ ed->head |= OHCI_ED_H;
+ }
+
+ /* Retire this TD */
+ ed->head &= ~OHCI_DPTR_MASK;
+ ed->head |= td.next & OHCI_DPTR_MASK;
+ td.next = ohci->done;
+ ohci->done = addr;
+ i = OHCI_BM(td.flags, TD_DI);
+ if (i < ohci->done_count)
+ ohci->done_count = i;
+exit_no_retire:
+ if (ohci_put_td(ohci, addr, &td)) {
+ ohci_die(ohci);
+ return 1;
+ }
+ return OHCI_BM(td.flags, TD_CC) != OHCI_CC_NOERROR;
+}
+
+/* Service an endpoint list. Returns nonzero if active TD were found. */
+static int ohci_service_ed_list(OHCIState *ohci, uint32_t head, int completion)
+{
+ struct ohci_ed ed;
+ uint32_t next_ed;
+ uint32_t cur;
+ int active;
+
+ active = 0;
+
+ if (head == 0)
+ return 0;
+
+ for (cur = head; cur; cur = next_ed) {
+ if (ohci_read_ed(ohci, cur, &ed)) {
+ trace_usb_ohci_ed_read_error(cur);
+ ohci_die(ohci);
+ return 0;
+ }
+
+ next_ed = ed.next & OHCI_DPTR_MASK;
+
+ if ((ed.head & OHCI_ED_H) || (ed.flags & OHCI_ED_K)) {
+ uint32_t addr;
+ /* Cancel pending packets for ED that have been paused. */
+ addr = ed.head & OHCI_DPTR_MASK;
+ if (ohci->async_td && addr == ohci->async_td) {
+ usb_cancel_packet(&ohci->usb_packet);
+ ohci->async_td = 0;
+ usb_device_ep_stopped(ohci->usb_packet.ep->dev,
+ ohci->usb_packet.ep);
+ }
+ continue;
+ }
+
+ while ((ed.head & OHCI_DPTR_MASK) != ed.tail) {
+ trace_usb_ohci_ed_pkt(cur, (ed.head & OHCI_ED_H) != 0,
+ (ed.head & OHCI_ED_C) != 0, ed.head & OHCI_DPTR_MASK,
+ ed.tail & OHCI_DPTR_MASK, ed.next & OHCI_DPTR_MASK);
+ trace_usb_ohci_ed_pkt_flags(
+ OHCI_BM(ed.flags, ED_FA), OHCI_BM(ed.flags, ED_EN),
+ OHCI_BM(ed.flags, ED_D), (ed.flags & OHCI_ED_S)!= 0,
+ (ed.flags & OHCI_ED_K) != 0, (ed.flags & OHCI_ED_F) != 0,
+ OHCI_BM(ed.flags, ED_MPS));
+
+ active = 1;
+
+ if ((ed.flags & OHCI_ED_F) == 0) {
+ if (ohci_service_td(ohci, &ed))
+ break;
+ } else {
+ /* Handle isochronous endpoints */
+ if (ohci_service_iso_td(ohci, &ed, completion))
+ break;
+ }
+ }
+
+ if (ohci_put_ed(ohci, cur, &ed)) {
+ ohci_die(ohci);
+ return 0;
+ }
+ }
+
+ return active;
+}
+
+/* Generate a SOF event, and set a timer for EOF */
+static void ohci_sof(OHCIState *ohci)
+{
+ ohci->sof_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ timer_mod(ohci->eof_timer, ohci->sof_time + usb_frame_time);
+ ohci_set_interrupt(ohci, OHCI_INTR_SF);
+}
+
+/* Process Control and Bulk lists. */
+static void ohci_process_lists(OHCIState *ohci, int completion)
+{
+ if ((ohci->ctl & OHCI_CTL_CLE) && (ohci->status & OHCI_STATUS_CLF)) {
+ if (ohci->ctrl_cur && ohci->ctrl_cur != ohci->ctrl_head) {
+ trace_usb_ohci_process_lists(ohci->ctrl_head, ohci->ctrl_cur);
+ }
+ if (!ohci_service_ed_list(ohci, ohci->ctrl_head, completion)) {
+ ohci->ctrl_cur = 0;
+ ohci->status &= ~OHCI_STATUS_CLF;
+ }
+ }
+
+ if ((ohci->ctl & OHCI_CTL_BLE) && (ohci->status & OHCI_STATUS_BLF)) {
+ if (!ohci_service_ed_list(ohci, ohci->bulk_head, completion)) {
+ ohci->bulk_cur = 0;
+ ohci->status &= ~OHCI_STATUS_BLF;
+ }
+ }
+}
+
+/* Do frame processing on frame boundary */
+static void ohci_frame_boundary(void *opaque)
+{
+ OHCIState *ohci = opaque;
+ struct ohci_hcca hcca;
+
+ if (ohci_read_hcca(ohci, ohci->hcca, &hcca)) {
+ trace_usb_ohci_hcca_read_error(ohci->hcca);
+ ohci_die(ohci);
+ return;
+ }
+
+ /* Process all the lists at the end of the frame */
+ if (ohci->ctl & OHCI_CTL_PLE) {
+ int n;
+
+ n = ohci->frame_number & 0x1f;
+ ohci_service_ed_list(ohci, le32_to_cpu(hcca.intr[n]), 0);
+ }
+
+ /* Cancel all pending packets if either of the lists has been disabled. */
+ if (ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) {
+ if (ohci->async_td) {
+ usb_cancel_packet(&ohci->usb_packet);
+ ohci->async_td = 0;
+ }
+ ohci_stop_endpoints(ohci);
+ }
+ ohci->old_ctl = ohci->ctl;
+ ohci_process_lists(ohci, 0);
+
+ /* Stop if UnrecoverableError happened or ohci_sof will crash */
+ if (ohci->intr_status & OHCI_INTR_UE) {
+ return;
+ }
+
+ /* Frame boundary, so do EOF stuf here */
+ ohci->frt = ohci->fit;
+
+ /* Increment frame number and take care of endianness. */
+ ohci->frame_number = (ohci->frame_number + 1) & 0xffff;
+ hcca.frame = cpu_to_le16(ohci->frame_number);
+
+ if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) {
+ if (!ohci->done)
+ abort();
+ if (ohci->intr & ohci->intr_status)
+ ohci->done |= 1;
+ hcca.done = cpu_to_le32(ohci->done);
+ ohci->done = 0;
+ ohci->done_count = 7;
+ ohci_set_interrupt(ohci, OHCI_INTR_WD);
+ }
+
+ if (ohci->done_count != 7 && ohci->done_count != 0)
+ ohci->done_count--;
+
+ /* Do SOF stuff here */
+ ohci_sof(ohci);
+
+ /* Writeback HCCA */
+ if (ohci_put_hcca(ohci, ohci->hcca, &hcca)) {
+ ohci_die(ohci);
+ }
+}
+
+/* Start sending SOF tokens across the USB bus, lists are processed in
+ * next frame
+ */
+static int ohci_bus_start(OHCIState *ohci)
+{
+ ohci->eof_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ ohci_frame_boundary,
+ ohci);
+
+ if (ohci->eof_timer == NULL) {
+ trace_usb_ohci_bus_eof_timer_failed(ohci->name);
+ ohci_die(ohci);
+ return 0;
+ }
+
+ trace_usb_ohci_start(ohci->name);
+
+ ohci_sof(ohci);
+
+ return 1;
+}
+
+/* Stop sending SOF tokens on the bus */
+static void ohci_bus_stop(OHCIState *ohci)
+{
+ trace_usb_ohci_stop(ohci->name);
+ if (ohci->eof_timer) {
+ timer_del(ohci->eof_timer);
+ timer_free(ohci->eof_timer);
+ }
+ ohci->eof_timer = NULL;
+}
+
+/* Sets a flag in a port status register but only set it if the port is
+ * connected, if not set ConnectStatusChange flag. If flag is enabled
+ * return 1.
+ */
+static int ohci_port_set_if_connected(OHCIState *ohci, int i, uint32_t val)
+{
+ int ret = 1;
+
+ /* writing a 0 has no effect */
+ if (val == 0)
+ return 0;
+
+ /* If CurrentConnectStatus is cleared we set
+ * ConnectStatusChange
+ */
+ if (!(ohci->rhport[i].ctrl & OHCI_PORT_CCS)) {
+ ohci->rhport[i].ctrl |= OHCI_PORT_CSC;
+ if (ohci->rhstatus & OHCI_RHS_DRWE) {
+ /* TODO: CSC is a wakeup event */
+ }
+ return 0;
+ }
+
+ if (ohci->rhport[i].ctrl & val)
+ ret = 0;
+
+ /* set the bit */
+ ohci->rhport[i].ctrl |= val;
+
+ return ret;
+}
+
+/* Set the frame interval - frame interval toggle is manipulated by the hcd only */
+static void ohci_set_frame_interval(OHCIState *ohci, uint16_t val)
+{
+ val &= OHCI_FMI_FI;
+
+ if (val != ohci->fi) {
+ trace_usb_ohci_set_frame_interval(ohci->name, ohci->fi, ohci->fi);
+ }
+
+ ohci->fi = val;
+}
+
+static void ohci_port_power(OHCIState *ohci, int i, int p)
+{
+ if (p) {
+ ohci->rhport[i].ctrl |= OHCI_PORT_PPS;
+ } else {
+ ohci->rhport[i].ctrl &= ~(OHCI_PORT_PPS|
+ OHCI_PORT_CCS|
+ OHCI_PORT_PSS|
+ OHCI_PORT_PRS);
+ }
+}
+
+/* Set HcControlRegister */
+static void ohci_set_ctl(OHCIState *ohci, uint32_t val)
+{
+ uint32_t old_state;
+ uint32_t new_state;
+
+ old_state = ohci->ctl & OHCI_CTL_HCFS;
+ ohci->ctl = val;
+ new_state = ohci->ctl & OHCI_CTL_HCFS;
+
+ /* no state change */
+ if (old_state == new_state)
+ return;
+
+ trace_usb_ohci_set_ctl(ohci->name, new_state);
+ switch (new_state) {
+ case OHCI_USB_OPERATIONAL:
+ ohci_bus_start(ohci);
+ break;
+ case OHCI_USB_SUSPEND:
+ ohci_bus_stop(ohci);
+ break;
+ case OHCI_USB_RESUME:
+ trace_usb_ohci_resume(ohci->name);
+ break;
+ case OHCI_USB_RESET:
+ ohci_reset(ohci);
+ break;
+ }
+}
+
+static uint32_t ohci_get_frame_remaining(OHCIState *ohci)
+{
+ uint16_t fr;
+ int64_t tks;
+
+ if ((ohci->ctl & OHCI_CTL_HCFS) != OHCI_USB_OPERATIONAL)
+ return (ohci->frt << 31);
+
+ /* Being in USB operational state guarnatees sof_time was
+ * set already.
+ */
+ tks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - ohci->sof_time;
+
+ /* avoid muldiv if possible */
+ if (tks >= usb_frame_time)
+ return (ohci->frt << 31);
+
+ tks = muldiv64(1, tks, usb_bit_time);
+ fr = (uint16_t)(ohci->fi - tks);
+
+ return (ohci->frt << 31) | fr;
+}
+
+
+/* Set root hub status */
+static void ohci_set_hub_status(OHCIState *ohci, uint32_t val)
+{
+ uint32_t old_state;
+
+ old_state = ohci->rhstatus;
+
+ /* write 1 to clear OCIC */
+ if (val & OHCI_RHS_OCIC)
+ ohci->rhstatus &= ~OHCI_RHS_OCIC;
+
+ if (val & OHCI_RHS_LPS) {
+ int i;
+
+ for (i = 0; i < ohci->num_ports; i++)
+ ohci_port_power(ohci, i, 0);
+ trace_usb_ohci_hub_power_down();
+ }
+
+ if (val & OHCI_RHS_LPSC) {
+ int i;
+
+ for (i = 0; i < ohci->num_ports; i++)
+ ohci_port_power(ohci, i, 1);
+ trace_usb_ohci_hub_power_up();
+ }
+
+ if (val & OHCI_RHS_DRWE)
+ ohci->rhstatus |= OHCI_RHS_DRWE;
+
+ if (val & OHCI_RHS_CRWE)
+ ohci->rhstatus &= ~OHCI_RHS_DRWE;
+
+ if (old_state != ohci->rhstatus)
+ ohci_set_interrupt(ohci, OHCI_INTR_RHSC);
+}
+
+/* Set root hub port status */
+static void ohci_port_set_status(OHCIState *ohci, int portnum, uint32_t val)
+{
+ uint32_t old_state;
+ OHCIPort *port;
+
+ port = &ohci->rhport[portnum];
+ old_state = port->ctrl;
+
+ /* Write to clear CSC, PESC, PSSC, OCIC, PRSC */
+ if (val & OHCI_PORT_WTC)
+ port->ctrl &= ~(val & OHCI_PORT_WTC);
+
+ if (val & OHCI_PORT_CCS)
+ port->ctrl &= ~OHCI_PORT_PES;
+
+ ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PES);
+
+ if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PSS)) {
+ trace_usb_ohci_port_suspend(portnum);
+ }
+
+ if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PRS)) {
+ trace_usb_ohci_port_reset(portnum);
+ usb_device_reset(port->port.dev);
+ port->ctrl &= ~OHCI_PORT_PRS;
+ /* ??? Should this also set OHCI_PORT_PESC. */
+ port->ctrl |= OHCI_PORT_PES | OHCI_PORT_PRSC;
+ }
+
+ /* Invert order here to ensure in ambiguous case, device is
+ * powered up...
+ */
+ if (val & OHCI_PORT_LSDA)
+ ohci_port_power(ohci, portnum, 0);
+ if (val & OHCI_PORT_PPS)
+ ohci_port_power(ohci, portnum, 1);
+
+ if (old_state != port->ctrl)
+ ohci_set_interrupt(ohci, OHCI_INTR_RHSC);
+}
+
+static uint64_t ohci_mem_read(void *opaque,
+ hwaddr addr,
+ unsigned size)
+{
+ OHCIState *ohci = opaque;
+ uint32_t retval;
+
+ /* Only aligned reads are allowed on OHCI */
+ if (addr & 3) {
+ trace_usb_ohci_mem_read_unaligned(addr);
+ return 0xffffffff;
+ } else if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) {
+ /* HcRhPortStatus */
+ retval = ohci->rhport[(addr - 0x54) >> 2].ctrl | OHCI_PORT_PPS;
+ } else {
+ switch (addr >> 2) {
+ case 0: /* HcRevision */
+ retval = 0x10;
+ break;
+
+ case 1: /* HcControl */
+ retval = ohci->ctl;
+ break;
+
+ case 2: /* HcCommandStatus */
+ retval = ohci->status;
+ break;
+
+ case 3: /* HcInterruptStatus */
+ retval = ohci->intr_status;
+ break;
+
+ case 4: /* HcInterruptEnable */
+ case 5: /* HcInterruptDisable */
+ retval = ohci->intr;
+ break;
+
+ case 6: /* HcHCCA */
+ retval = ohci->hcca;
+ break;
+
+ case 7: /* HcPeriodCurrentED */
+ retval = ohci->per_cur;
+ break;
+
+ case 8: /* HcControlHeadED */
+ retval = ohci->ctrl_head;
+ break;
+
+ case 9: /* HcControlCurrentED */
+ retval = ohci->ctrl_cur;
+ break;
+
+ case 10: /* HcBulkHeadED */
+ retval = ohci->bulk_head;
+ break;
+
+ case 11: /* HcBulkCurrentED */
+ retval = ohci->bulk_cur;
+ break;
+
+ case 12: /* HcDoneHead */
+ retval = ohci->done;
+ break;
+
+ case 13: /* HcFmInterretval */
+ retval = (ohci->fit << 31) | (ohci->fsmps << 16) | (ohci->fi);
+ break;
+
+ case 14: /* HcFmRemaining */
+ retval = ohci_get_frame_remaining(ohci);
+ break;
+
+ case 15: /* HcFmNumber */
+ retval = ohci->frame_number;
+ break;
+
+ case 16: /* HcPeriodicStart */
+ retval = ohci->pstart;
+ break;
+
+ case 17: /* HcLSThreshold */
+ retval = ohci->lst;
+ break;
+
+ case 18: /* HcRhDescriptorA */
+ retval = ohci->rhdesc_a;
+ break;
+
+ case 19: /* HcRhDescriptorB */
+ retval = ohci->rhdesc_b;
+ break;
+
+ case 20: /* HcRhStatus */
+ retval = ohci->rhstatus;
+ break;
+
+ /* PXA27x specific registers */
+ case 24: /* HcStatus */
+ retval = ohci->hstatus & ohci->hmask;
+ break;
+
+ case 25: /* HcHReset */
+ retval = ohci->hreset;
+ break;
+
+ case 26: /* HcHInterruptEnable */
+ retval = ohci->hmask;
+ break;
+
+ case 27: /* HcHInterruptTest */
+ retval = ohci->htest;
+ break;
+
+ default:
+ trace_usb_ohci_mem_read_bad_offset(addr);
+ retval = 0xffffffff;
+ }
+ }
+
+ return retval;
+}
+
+static void ohci_mem_write(void *opaque,
+ hwaddr addr,
+ uint64_t val,
+ unsigned size)
+{
+ OHCIState *ohci = opaque;
+
+ /* Only aligned reads are allowed on OHCI */
+ if (addr & 3) {
+ trace_usb_ohci_mem_write_unaligned(addr);
+ return;
+ }
+
+ if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) {
+ /* HcRhPortStatus */
+ ohci_port_set_status(ohci, (addr - 0x54) >> 2, val);
+ return;
+ }
+
+ switch (addr >> 2) {
+ case 1: /* HcControl */
+ ohci_set_ctl(ohci, val);
+ break;
+
+ case 2: /* HcCommandStatus */
+ /* SOC is read-only */
+ val = (val & ~OHCI_STATUS_SOC);
+
+ /* Bits written as '0' remain unchanged in the register */
+ ohci->status |= val;
+
+ if (ohci->status & OHCI_STATUS_HCR)
+ ohci_reset(ohci);
+ break;
+
+ case 3: /* HcInterruptStatus */
+ ohci->intr_status &= ~val;
+ ohci_intr_update(ohci);
+ break;
+
+ case 4: /* HcInterruptEnable */
+ ohci->intr |= val;
+ ohci_intr_update(ohci);
+ break;
+
+ case 5: /* HcInterruptDisable */
+ ohci->intr &= ~val;
+ ohci_intr_update(ohci);
+ break;
+
+ case 6: /* HcHCCA */
+ ohci->hcca = val & OHCI_HCCA_MASK;
+ break;
+
+ case 7: /* HcPeriodCurrentED */
+ /* Ignore writes to this read-only register, Linux does them */
+ break;
+
+ case 8: /* HcControlHeadED */
+ ohci->ctrl_head = val & OHCI_EDPTR_MASK;
+ break;
+
+ case 9: /* HcControlCurrentED */
+ ohci->ctrl_cur = val & OHCI_EDPTR_MASK;
+ break;
+
+ case 10: /* HcBulkHeadED */
+ ohci->bulk_head = val & OHCI_EDPTR_MASK;
+ break;
+
+ case 11: /* HcBulkCurrentED */
+ ohci->bulk_cur = val & OHCI_EDPTR_MASK;
+ break;
+
+ case 13: /* HcFmInterval */
+ ohci->fsmps = (val & OHCI_FMI_FSMPS) >> 16;
+ ohci->fit = (val & OHCI_FMI_FIT) >> 31;
+ ohci_set_frame_interval(ohci, val);
+ break;
+
+ case 15: /* HcFmNumber */
+ break;
+
+ case 16: /* HcPeriodicStart */
+ ohci->pstart = val & 0xffff;
+ break;
+
+ case 17: /* HcLSThreshold */
+ ohci->lst = val & 0xffff;
+ break;
+
+ case 18: /* HcRhDescriptorA */
+ ohci->rhdesc_a &= ~OHCI_RHA_RW_MASK;
+ ohci->rhdesc_a |= val & OHCI_RHA_RW_MASK;
+ break;
+
+ case 19: /* HcRhDescriptorB */
+ break;
+
+ case 20: /* HcRhStatus */
+ ohci_set_hub_status(ohci, val);
+ break;
+
+ /* PXA27x specific registers */
+ case 24: /* HcStatus */
+ ohci->hstatus &= ~(val & ohci->hmask);
+ break;
+
+ case 25: /* HcHReset */
+ ohci->hreset = val & ~OHCI_HRESET_FSBIR;
+ if (val & OHCI_HRESET_FSBIR)
+ ohci_reset(ohci);
+ break;
+
+ case 26: /* HcHInterruptEnable */
+ ohci->hmask = val;
+ break;
+
+ case 27: /* HcHInterruptTest */
+ ohci->htest = val;
+ break;
+
+ default:
+ trace_usb_ohci_mem_write_bad_offset(addr);
+ break;
+ }
+}
+
+static void ohci_async_cancel_device(OHCIState *ohci, USBDevice *dev)
+{
+ if (ohci->async_td &&
+ usb_packet_is_inflight(&ohci->usb_packet) &&
+ ohci->usb_packet.ep->dev == dev) {
+ usb_cancel_packet(&ohci->usb_packet);
+ ohci->async_td = 0;
+ }
+}
+
+static const MemoryRegionOps ohci_mem_ops = {
+ .read = ohci_mem_read,
+ .write = ohci_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static USBPortOps ohci_port_ops = {
+ .attach = ohci_attach,
+ .detach = ohci_detach,
+ .child_detach = ohci_child_detach,
+ .wakeup = ohci_wakeup,
+ .complete = ohci_async_complete_packet,
+};
+
+static USBBusOps ohci_bus_ops = {
+};
+
+static void usb_ohci_init(OHCIState *ohci, DeviceState *dev,
+ int num_ports, dma_addr_t localmem_base,
+ char *masterbus, uint32_t firstport,
+ AddressSpace *as, Error **errp)
+{
+ Error *err = NULL;
+ int i;
+
+ ohci->as = as;
+
+ if (usb_frame_time == 0) {
+#ifdef OHCI_TIME_WARP
+ usb_frame_time = get_ticks_per_sec();
+ usb_bit_time = muldiv64(1, get_ticks_per_sec(), USB_HZ/1000);
+#else
+ usb_frame_time = muldiv64(1, get_ticks_per_sec(), 1000);
+ if (get_ticks_per_sec() >= USB_HZ) {
+ usb_bit_time = muldiv64(1, get_ticks_per_sec(), USB_HZ);
+ } else {
+ usb_bit_time = 1;
+ }
+#endif
+ trace_usb_ohci_init_time(usb_frame_time, usb_bit_time);
+ }
+
+ ohci->num_ports = num_ports;
+ if (masterbus) {
+ USBPort *ports[OHCI_MAX_PORTS];
+ for(i = 0; i < num_ports; i++) {
+ ports[i] = &ohci->rhport[i].port;
+ }
+ usb_register_companion(masterbus, ports, num_ports,
+ firstport, ohci, &ohci_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL,
+ &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ } else {
+ usb_bus_new(&ohci->bus, sizeof(ohci->bus), &ohci_bus_ops, dev);
+ for (i = 0; i < num_ports; i++) {
+ usb_register_port(&ohci->bus, &ohci->rhport[i].port,
+ ohci, i, &ohci_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
+ }
+ }
+
+ memory_region_init_io(&ohci->mem, OBJECT(dev), &ohci_mem_ops,
+ ohci, "ohci", 256);
+ ohci->localmem_base = localmem_base;
+
+ ohci->name = object_get_typename(OBJECT(dev));
+ usb_packet_init(&ohci->usb_packet);
+
+ ohci->async_td = 0;
+}
+
+#define TYPE_PCI_OHCI "pci-ohci"
+#define PCI_OHCI(obj) OBJECT_CHECK(OHCIPCIState, (obj), TYPE_PCI_OHCI)
+
+typedef struct {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ OHCIState state;
+ char *masterbus;
+ uint32_t num_ports;
+ uint32_t firstport;
+} OHCIPCIState;
+
+/** A typical O/EHCI will stop operating, set itself into error state
+ * (which can be queried by MMIO) and will set PERR in its config
+ * space to signal that it got an error
+ */
+static void ohci_die(OHCIState *ohci)
+{
+ OHCIPCIState *dev = container_of(ohci, OHCIPCIState, state);
+
+ trace_usb_ohci_die();
+
+ ohci_set_interrupt(ohci, OHCI_INTR_UE);
+ ohci_bus_stop(ohci);
+ pci_set_word(dev->parent_obj.config + PCI_STATUS,
+ PCI_STATUS_DETECTED_PARITY);
+}
+
+static void usb_ohci_realize_pci(PCIDevice *dev, Error **errp)
+{
+ Error *err = NULL;
+ OHCIPCIState *ohci = PCI_OHCI(dev);
+
+ dev->config[PCI_CLASS_PROG] = 0x10; /* OHCI */
+ dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */
+
+ usb_ohci_init(&ohci->state, DEVICE(dev), ohci->num_ports, 0,
+ ohci->masterbus, ohci->firstport,
+ pci_get_address_space(dev), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ ohci->state.irq = pci_allocate_irq(dev);
+ pci_register_bar(dev, 0, 0, &ohci->state.mem);
+}
+
+static void usb_ohci_exit(PCIDevice *dev)
+{
+ OHCIPCIState *ohci = PCI_OHCI(dev);
+ OHCIState *s = &ohci->state;
+
+ trace_usb_ohci_exit(s->name);
+ ohci_bus_stop(s);
+
+ if (s->async_td) {
+ usb_cancel_packet(&s->usb_packet);
+ s->async_td = 0;
+ }
+ ohci_stop_endpoints(s);
+
+ if (!ohci->masterbus) {
+ usb_bus_release(&s->bus);
+ }
+}
+
+static void usb_ohci_reset_pci(DeviceState *d)
+{
+ PCIDevice *dev = PCI_DEVICE(d);
+ OHCIPCIState *ohci = PCI_OHCI(dev);
+ OHCIState *s = &ohci->state;
+
+ ohci_reset(s);
+}
+
+#define TYPE_SYSBUS_OHCI "sysbus-ohci"
+#define SYSBUS_OHCI(obj) OBJECT_CHECK(OHCISysBusState, (obj), TYPE_SYSBUS_OHCI)
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ OHCIState ohci;
+ uint32_t num_ports;
+ dma_addr_t dma_offset;
+} OHCISysBusState;
+
+static void ohci_realize_pxa(DeviceState *dev, Error **errp)
+{
+ OHCISysBusState *s = SYSBUS_OHCI(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ /* Cannot fail as we pass NULL for masterbus */
+ usb_ohci_init(&s->ohci, dev, s->num_ports, s->dma_offset, NULL, 0,
+ &address_space_memory, &error_abort);
+ sysbus_init_irq(sbd, &s->ohci.irq);
+ sysbus_init_mmio(sbd, &s->ohci.mem);
+}
+
+static void usb_ohci_reset_sysbus(DeviceState *dev)
+{
+ OHCISysBusState *s = SYSBUS_OHCI(dev);
+ OHCIState *ohci = &s->ohci;
+
+ ohci_reset(ohci);
+}
+
+static Property ohci_pci_properties[] = {
+ DEFINE_PROP_STRING("masterbus", OHCIPCIState, masterbus),
+ DEFINE_PROP_UINT32("num-ports", OHCIPCIState, num_ports, 3),
+ DEFINE_PROP_UINT32("firstport", OHCIPCIState, firstport, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_ohci_state_port = {
+ .name = "ohci-core/port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ctrl, OHCIPort),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static bool ohci_eof_timer_needed(void *opaque)
+{
+ OHCIState *ohci = opaque;
+
+ return ohci->eof_timer != NULL;
+}
+
+static int ohci_eof_timer_pre_load(void *opaque)
+{
+ OHCIState *ohci = opaque;
+
+ ohci_bus_start(ohci);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_ohci_eof_timer = {
+ .name = "ohci-core/eof-timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_load = ohci_eof_timer_pre_load,
+ .needed = ohci_eof_timer_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(eof_timer, OHCIState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const VMStateDescription vmstate_ohci_state = {
+ .name = "ohci-core",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT64(sof_time, OHCIState),
+ VMSTATE_UINT32(ctl, OHCIState),
+ VMSTATE_UINT32(status, OHCIState),
+ VMSTATE_UINT32(intr_status, OHCIState),
+ VMSTATE_UINT32(intr, OHCIState),
+ VMSTATE_UINT32(hcca, OHCIState),
+ VMSTATE_UINT32(ctrl_head, OHCIState),
+ VMSTATE_UINT32(ctrl_cur, OHCIState),
+ VMSTATE_UINT32(bulk_head, OHCIState),
+ VMSTATE_UINT32(bulk_cur, OHCIState),
+ VMSTATE_UINT32(per_cur, OHCIState),
+ VMSTATE_UINT32(done, OHCIState),
+ VMSTATE_INT32(done_count, OHCIState),
+ VMSTATE_UINT16(fsmps, OHCIState),
+ VMSTATE_UINT8(fit, OHCIState),
+ VMSTATE_UINT16(fi, OHCIState),
+ VMSTATE_UINT8(frt, OHCIState),
+ VMSTATE_UINT16(frame_number, OHCIState),
+ VMSTATE_UINT16(padding, OHCIState),
+ VMSTATE_UINT32(pstart, OHCIState),
+ VMSTATE_UINT32(lst, OHCIState),
+ VMSTATE_UINT32(rhdesc_a, OHCIState),
+ VMSTATE_UINT32(rhdesc_b, OHCIState),
+ VMSTATE_UINT32(rhstatus, OHCIState),
+ VMSTATE_STRUCT_ARRAY(rhport, OHCIState, OHCI_MAX_PORTS, 0,
+ vmstate_ohci_state_port, OHCIPort),
+ VMSTATE_UINT32(hstatus, OHCIState),
+ VMSTATE_UINT32(hmask, OHCIState),
+ VMSTATE_UINT32(hreset, OHCIState),
+ VMSTATE_UINT32(htest, OHCIState),
+ VMSTATE_UINT32(old_ctl, OHCIState),
+ VMSTATE_UINT8_ARRAY(usb_buf, OHCIState, 8192),
+ VMSTATE_UINT32(async_td, OHCIState),
+ VMSTATE_BOOL(async_complete, OHCIState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_ohci_eof_timer,
+ NULL
+ }
+};
+
+static const VMStateDescription vmstate_ohci = {
+ .name = "ohci",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, OHCIPCIState),
+ VMSTATE_STRUCT(state, OHCIPCIState, 1, vmstate_ohci_state, OHCIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ohci_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = usb_ohci_realize_pci;
+ k->exit = usb_ohci_exit;
+ k->vendor_id = PCI_VENDOR_ID_APPLE;
+ k->device_id = PCI_DEVICE_ID_APPLE_IPID_USB;
+ k->class_id = PCI_CLASS_SERIAL_USB;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+ dc->desc = "Apple USB Controller";
+ dc->props = ohci_pci_properties;
+ dc->hotpluggable = false;
+ dc->vmsd = &vmstate_ohci;
+ dc->reset = usb_ohci_reset_pci;
+}
+
+static const TypeInfo ohci_pci_info = {
+ .name = TYPE_PCI_OHCI,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(OHCIPCIState),
+ .class_init = ohci_pci_class_init,
+};
+
+static Property ohci_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("num-ports", OHCISysBusState, num_ports, 3),
+ DEFINE_PROP_DMAADDR("dma-offset", OHCISysBusState, dma_offset, 3),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ohci_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = ohci_realize_pxa;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+ dc->desc = "OHCI USB Controller";
+ dc->props = ohci_sysbus_properties;
+ dc->reset = usb_ohci_reset_sysbus;
+}
+
+static const TypeInfo ohci_sysbus_info = {
+ .name = TYPE_SYSBUS_OHCI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OHCISysBusState),
+ .class_init = ohci_sysbus_class_init,
+};
+
+static void ohci_register_types(void)
+{
+ type_register_static(&ohci_pci_info);
+ type_register_static(&ohci_sysbus_info);
+}
+
+type_init(ohci_register_types)
diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c
new file mode 100644
index 00000000..3f0ed626
--- /dev/null
+++ b/hw/usb/hcd-uhci.c
@@ -0,0 +1,1423 @@
+/*
+ * USB UHCI controller emulation
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * Copyright (c) 2008 Max Krasnyansky
+ * Magor rewrite of the UHCI data structures parser and frame processor
+ * Support for fully async operation and multiple outstanding transactions
+ *
+ * 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.
+ */
+#include "hw/hw.h"
+#include "hw/usb.h"
+#include "hw/usb/uhci-regs.h"
+#include "hw/pci/pci.h"
+#include "qemu/timer.h"
+#include "qemu/iov.h"
+#include "sysemu/dma.h"
+#include "trace.h"
+#include "qemu/main-loop.h"
+
+#define FRAME_TIMER_FREQ 1000
+
+#define FRAME_MAX_LOOPS 256
+
+/* Must be large enough to handle 10 frame delay for initial isoc requests */
+#define QH_VALID 32
+
+#define MAX_FRAMES_PER_TICK (QH_VALID / 2)
+
+#define NB_PORTS 2
+
+enum {
+ TD_RESULT_STOP_FRAME = 10,
+ TD_RESULT_COMPLETE,
+ TD_RESULT_NEXT_QH,
+ TD_RESULT_ASYNC_START,
+ TD_RESULT_ASYNC_CONT,
+};
+
+typedef struct UHCIState UHCIState;
+typedef struct UHCIAsync UHCIAsync;
+typedef struct UHCIQueue UHCIQueue;
+typedef struct UHCIInfo UHCIInfo;
+typedef struct UHCIPCIDeviceClass UHCIPCIDeviceClass;
+
+struct UHCIInfo {
+ const char *name;
+ uint16_t vendor_id;
+ uint16_t device_id;
+ uint8_t revision;
+ uint8_t irq_pin;
+ void (*realize)(PCIDevice *dev, Error **errp);
+ bool unplug;
+};
+
+struct UHCIPCIDeviceClass {
+ PCIDeviceClass parent_class;
+ UHCIInfo info;
+};
+
+/*
+ * Pending async transaction.
+ * 'packet' must be the first field because completion
+ * handler does "(UHCIAsync *) pkt" cast.
+ */
+
+struct UHCIAsync {
+ USBPacket packet;
+ uint8_t static_buf[64]; /* 64 bytes is enough, except for isoc packets */
+ uint8_t *buf;
+ UHCIQueue *queue;
+ QTAILQ_ENTRY(UHCIAsync) next;
+ uint32_t td_addr;
+ uint8_t done;
+};
+
+struct UHCIQueue {
+ uint32_t qh_addr;
+ uint32_t token;
+ UHCIState *uhci;
+ USBEndpoint *ep;
+ QTAILQ_ENTRY(UHCIQueue) next;
+ QTAILQ_HEAD(asyncs_head, UHCIAsync) asyncs;
+ int8_t valid;
+};
+
+typedef struct UHCIPort {
+ USBPort port;
+ uint16_t ctrl;
+} UHCIPort;
+
+struct UHCIState {
+ PCIDevice dev;
+ MemoryRegion io_bar;
+ USBBus bus; /* Note unused when we're a companion controller */
+ uint16_t cmd; /* cmd register */
+ uint16_t status;
+ uint16_t intr; /* interrupt enable register */
+ uint16_t frnum; /* frame number */
+ uint32_t fl_base_addr; /* frame list base address */
+ uint8_t sof_timing;
+ uint8_t status2; /* bit 0 and 1 are used to generate UHCI_STS_USBINT */
+ int64_t expire_time;
+ QEMUTimer *frame_timer;
+ QEMUBH *bh;
+ uint32_t frame_bytes;
+ uint32_t frame_bandwidth;
+ bool completions_only;
+ UHCIPort ports[NB_PORTS];
+
+ /* Interrupts that should be raised at the end of the current frame. */
+ uint32_t pending_int_mask;
+
+ /* Active packets */
+ QTAILQ_HEAD(, UHCIQueue) queues;
+ uint8_t num_ports_vmstate;
+
+ /* Properties */
+ char *masterbus;
+ uint32_t firstport;
+ uint32_t maxframes;
+};
+
+typedef struct UHCI_TD {
+ uint32_t link;
+ uint32_t ctrl; /* see TD_CTRL_xxx */
+ uint32_t token;
+ uint32_t buffer;
+} UHCI_TD;
+
+typedef struct UHCI_QH {
+ uint32_t link;
+ uint32_t el_link;
+} UHCI_QH;
+
+static void uhci_async_cancel(UHCIAsync *async);
+static void uhci_queue_fill(UHCIQueue *q, UHCI_TD *td);
+static void uhci_resume(void *opaque);
+
+#define TYPE_UHCI "pci-uhci-usb"
+#define UHCI(obj) OBJECT_CHECK(UHCIState, (obj), TYPE_UHCI)
+
+static inline int32_t uhci_queue_token(UHCI_TD *td)
+{
+ if ((td->token & (0xf << 15)) == 0) {
+ /* ctrl ep, cover ep and dev, not pid! */
+ return td->token & 0x7ff00;
+ } else {
+ /* covers ep, dev, pid -> identifies the endpoint */
+ return td->token & 0x7ffff;
+ }
+}
+
+static UHCIQueue *uhci_queue_new(UHCIState *s, uint32_t qh_addr, UHCI_TD *td,
+ USBEndpoint *ep)
+{
+ UHCIQueue *queue;
+
+ queue = g_new0(UHCIQueue, 1);
+ queue->uhci = s;
+ queue->qh_addr = qh_addr;
+ queue->token = uhci_queue_token(td);
+ queue->ep = ep;
+ QTAILQ_INIT(&queue->asyncs);
+ QTAILQ_INSERT_HEAD(&s->queues, queue, next);
+ queue->valid = QH_VALID;
+ trace_usb_uhci_queue_add(queue->token);
+ return queue;
+}
+
+static void uhci_queue_free(UHCIQueue *queue, const char *reason)
+{
+ UHCIState *s = queue->uhci;
+ UHCIAsync *async;
+
+ while (!QTAILQ_EMPTY(&queue->asyncs)) {
+ async = QTAILQ_FIRST(&queue->asyncs);
+ uhci_async_cancel(async);
+ }
+ usb_device_ep_stopped(queue->ep->dev, queue->ep);
+
+ trace_usb_uhci_queue_del(queue->token, reason);
+ QTAILQ_REMOVE(&s->queues, queue, next);
+ g_free(queue);
+}
+
+static UHCIQueue *uhci_queue_find(UHCIState *s, UHCI_TD *td)
+{
+ uint32_t token = uhci_queue_token(td);
+ UHCIQueue *queue;
+
+ QTAILQ_FOREACH(queue, &s->queues, next) {
+ if (queue->token == token) {
+ return queue;
+ }
+ }
+ return NULL;
+}
+
+static bool uhci_queue_verify(UHCIQueue *queue, uint32_t qh_addr, UHCI_TD *td,
+ uint32_t td_addr, bool queuing)
+{
+ UHCIAsync *first = QTAILQ_FIRST(&queue->asyncs);
+ uint32_t queue_token_addr = (queue->token >> 8) & 0x7f;
+
+ return queue->qh_addr == qh_addr &&
+ queue->token == uhci_queue_token(td) &&
+ queue_token_addr == queue->ep->dev->addr &&
+ (queuing || !(td->ctrl & TD_CTRL_ACTIVE) || first == NULL ||
+ first->td_addr == td_addr);
+}
+
+static UHCIAsync *uhci_async_alloc(UHCIQueue *queue, uint32_t td_addr)
+{
+ UHCIAsync *async = g_new0(UHCIAsync, 1);
+
+ async->queue = queue;
+ async->td_addr = td_addr;
+ usb_packet_init(&async->packet);
+ trace_usb_uhci_packet_add(async->queue->token, async->td_addr);
+
+ return async;
+}
+
+static void uhci_async_free(UHCIAsync *async)
+{
+ trace_usb_uhci_packet_del(async->queue->token, async->td_addr);
+ usb_packet_cleanup(&async->packet);
+ if (async->buf != async->static_buf) {
+ g_free(async->buf);
+ }
+ g_free(async);
+}
+
+static void uhci_async_link(UHCIAsync *async)
+{
+ UHCIQueue *queue = async->queue;
+ QTAILQ_INSERT_TAIL(&queue->asyncs, async, next);
+ trace_usb_uhci_packet_link_async(async->queue->token, async->td_addr);
+}
+
+static void uhci_async_unlink(UHCIAsync *async)
+{
+ UHCIQueue *queue = async->queue;
+ QTAILQ_REMOVE(&queue->asyncs, async, next);
+ trace_usb_uhci_packet_unlink_async(async->queue->token, async->td_addr);
+}
+
+static void uhci_async_cancel(UHCIAsync *async)
+{
+ uhci_async_unlink(async);
+ trace_usb_uhci_packet_cancel(async->queue->token, async->td_addr,
+ async->done);
+ if (!async->done)
+ usb_cancel_packet(&async->packet);
+ uhci_async_free(async);
+}
+
+/*
+ * Mark all outstanding async packets as invalid.
+ * This is used for canceling them when TDs are removed by the HCD.
+ */
+static void uhci_async_validate_begin(UHCIState *s)
+{
+ UHCIQueue *queue;
+
+ QTAILQ_FOREACH(queue, &s->queues, next) {
+ queue->valid--;
+ }
+}
+
+/*
+ * Cancel async packets that are no longer valid
+ */
+static void uhci_async_validate_end(UHCIState *s)
+{
+ UHCIQueue *queue, *n;
+
+ QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) {
+ if (!queue->valid) {
+ uhci_queue_free(queue, "validate-end");
+ }
+ }
+}
+
+static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev)
+{
+ UHCIQueue *queue, *n;
+
+ QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) {
+ if (queue->ep->dev == dev) {
+ uhci_queue_free(queue, "cancel-device");
+ }
+ }
+}
+
+static void uhci_async_cancel_all(UHCIState *s)
+{
+ UHCIQueue *queue, *nq;
+
+ QTAILQ_FOREACH_SAFE(queue, &s->queues, next, nq) {
+ uhci_queue_free(queue, "cancel-all");
+ }
+}
+
+static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t td_addr)
+{
+ UHCIQueue *queue;
+ UHCIAsync *async;
+
+ QTAILQ_FOREACH(queue, &s->queues, next) {
+ QTAILQ_FOREACH(async, &queue->asyncs, next) {
+ if (async->td_addr == td_addr) {
+ return async;
+ }
+ }
+ }
+ return NULL;
+}
+
+static void uhci_update_irq(UHCIState *s)
+{
+ int level;
+ if (((s->status2 & 1) && (s->intr & (1 << 2))) ||
+ ((s->status2 & 2) && (s->intr & (1 << 3))) ||
+ ((s->status & UHCI_STS_USBERR) && (s->intr & (1 << 0))) ||
+ ((s->status & UHCI_STS_RD) && (s->intr & (1 << 1))) ||
+ (s->status & UHCI_STS_HSERR) ||
+ (s->status & UHCI_STS_HCPERR)) {
+ level = 1;
+ } else {
+ level = 0;
+ }
+ pci_set_irq(&s->dev, level);
+}
+
+static void uhci_reset(DeviceState *dev)
+{
+ PCIDevice *d = PCI_DEVICE(dev);
+ UHCIState *s = UHCI(d);
+ uint8_t *pci_conf;
+ int i;
+ UHCIPort *port;
+
+ trace_usb_uhci_reset();
+
+ pci_conf = s->dev.config;
+
+ pci_conf[0x6a] = 0x01; /* usb clock */
+ pci_conf[0x6b] = 0x00;
+ s->cmd = 0;
+ s->status = UHCI_STS_HCHALTED;
+ s->status2 = 0;
+ s->intr = 0;
+ s->fl_base_addr = 0;
+ s->sof_timing = 64;
+
+ for(i = 0; i < NB_PORTS; i++) {
+ port = &s->ports[i];
+ port->ctrl = 0x0080;
+ if (port->port.dev && port->port.dev->attached) {
+ usb_port_reset(&port->port);
+ }
+ }
+
+ uhci_async_cancel_all(s);
+ qemu_bh_cancel(s->bh);
+ uhci_update_irq(s);
+}
+
+static const VMStateDescription vmstate_uhci_port = {
+ .name = "uhci port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(ctrl, UHCIPort),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int uhci_post_load(void *opaque, int version_id)
+{
+ UHCIState *s = opaque;
+
+ if (version_id < 2) {
+ s->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (get_ticks_per_sec() / FRAME_TIMER_FREQ);
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_uhci = {
+ .name = "uhci",
+ .version_id = 3,
+ .minimum_version_id = 1,
+ .post_load = uhci_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, UHCIState),
+ VMSTATE_UINT8_EQUAL(num_ports_vmstate, UHCIState),
+ VMSTATE_STRUCT_ARRAY(ports, UHCIState, NB_PORTS, 1,
+ vmstate_uhci_port, UHCIPort),
+ VMSTATE_UINT16(cmd, UHCIState),
+ VMSTATE_UINT16(status, UHCIState),
+ VMSTATE_UINT16(intr, UHCIState),
+ VMSTATE_UINT16(frnum, UHCIState),
+ VMSTATE_UINT32(fl_base_addr, UHCIState),
+ VMSTATE_UINT8(sof_timing, UHCIState),
+ VMSTATE_UINT8(status2, UHCIState),
+ VMSTATE_TIMER_PTR(frame_timer, UHCIState),
+ VMSTATE_INT64_V(expire_time, UHCIState, 2),
+ VMSTATE_UINT32_V(pending_int_mask, UHCIState, 3),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void uhci_port_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ UHCIState *s = opaque;
+
+ trace_usb_uhci_mmio_writew(addr, val);
+
+ switch(addr) {
+ case 0x00:
+ if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) {
+ /* start frame processing */
+ trace_usb_uhci_schedule_start();
+ s->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (get_ticks_per_sec() / FRAME_TIMER_FREQ);
+ timer_mod(s->frame_timer, s->expire_time);
+ s->status &= ~UHCI_STS_HCHALTED;
+ } else if (!(val & UHCI_CMD_RS)) {
+ s->status |= UHCI_STS_HCHALTED;
+ }
+ if (val & UHCI_CMD_GRESET) {
+ UHCIPort *port;
+ int i;
+
+ /* send reset on the USB bus */
+ for(i = 0; i < NB_PORTS; i++) {
+ port = &s->ports[i];
+ usb_device_reset(port->port.dev);
+ }
+ uhci_reset(DEVICE(s));
+ return;
+ }
+ if (val & UHCI_CMD_HCRESET) {
+ uhci_reset(DEVICE(s));
+ return;
+ }
+ s->cmd = val;
+ if (val & UHCI_CMD_EGSM) {
+ if ((s->ports[0].ctrl & UHCI_PORT_RD) ||
+ (s->ports[1].ctrl & UHCI_PORT_RD)) {
+ uhci_resume(s);
+ }
+ }
+ break;
+ case 0x02:
+ s->status &= ~val;
+ /* XXX: the chip spec is not coherent, so we add a hidden
+ register to distinguish between IOC and SPD */
+ if (val & UHCI_STS_USBINT)
+ s->status2 = 0;
+ uhci_update_irq(s);
+ break;
+ case 0x04:
+ s->intr = val;
+ uhci_update_irq(s);
+ break;
+ case 0x06:
+ if (s->status & UHCI_STS_HCHALTED)
+ s->frnum = val & 0x7ff;
+ break;
+ case 0x08:
+ s->fl_base_addr &= 0xffff0000;
+ s->fl_base_addr |= val & ~0xfff;
+ break;
+ case 0x0a:
+ s->fl_base_addr &= 0x0000ffff;
+ s->fl_base_addr |= (val << 16);
+ break;
+ case 0x0c:
+ s->sof_timing = val & 0xff;
+ break;
+ case 0x10 ... 0x1f:
+ {
+ UHCIPort *port;
+ USBDevice *dev;
+ int n;
+
+ n = (addr >> 1) & 7;
+ if (n >= NB_PORTS)
+ return;
+ port = &s->ports[n];
+ dev = port->port.dev;
+ if (dev && dev->attached) {
+ /* port reset */
+ if ( (val & UHCI_PORT_RESET) &&
+ !(port->ctrl & UHCI_PORT_RESET) ) {
+ usb_device_reset(dev);
+ }
+ }
+ port->ctrl &= UHCI_PORT_READ_ONLY;
+ /* enabled may only be set if a device is connected */
+ if (!(port->ctrl & UHCI_PORT_CCS)) {
+ val &= ~UHCI_PORT_EN;
+ }
+ port->ctrl |= (val & ~UHCI_PORT_READ_ONLY);
+ /* some bits are reset when a '1' is written to them */
+ port->ctrl &= ~(val & UHCI_PORT_WRITE_CLEAR);
+ }
+ break;
+ }
+}
+
+static uint64_t uhci_port_read(void *opaque, hwaddr addr, unsigned size)
+{
+ UHCIState *s = opaque;
+ uint32_t val;
+
+ switch(addr) {
+ case 0x00:
+ val = s->cmd;
+ break;
+ case 0x02:
+ val = s->status;
+ break;
+ case 0x04:
+ val = s->intr;
+ break;
+ case 0x06:
+ val = s->frnum;
+ break;
+ case 0x08:
+ val = s->fl_base_addr & 0xffff;
+ break;
+ case 0x0a:
+ val = (s->fl_base_addr >> 16) & 0xffff;
+ break;
+ case 0x0c:
+ val = s->sof_timing;
+ break;
+ case 0x10 ... 0x1f:
+ {
+ UHCIPort *port;
+ int n;
+ n = (addr >> 1) & 7;
+ if (n >= NB_PORTS)
+ goto read_default;
+ port = &s->ports[n];
+ val = port->ctrl;
+ }
+ break;
+ default:
+ read_default:
+ val = 0xff7f; /* disabled port */
+ break;
+ }
+
+ trace_usb_uhci_mmio_readw(addr, val);
+
+ return val;
+}
+
+/* signal resume if controller suspended */
+static void uhci_resume (void *opaque)
+{
+ UHCIState *s = (UHCIState *)opaque;
+
+ if (!s)
+ return;
+
+ if (s->cmd & UHCI_CMD_EGSM) {
+ s->cmd |= UHCI_CMD_FGR;
+ s->status |= UHCI_STS_RD;
+ uhci_update_irq(s);
+ }
+}
+
+static void uhci_attach(USBPort *port1)
+{
+ UHCIState *s = port1->opaque;
+ UHCIPort *port = &s->ports[port1->index];
+
+ /* set connect status */
+ port->ctrl |= UHCI_PORT_CCS | UHCI_PORT_CSC;
+
+ /* update speed */
+ if (port->port.dev->speed == USB_SPEED_LOW) {
+ port->ctrl |= UHCI_PORT_LSDA;
+ } else {
+ port->ctrl &= ~UHCI_PORT_LSDA;
+ }
+
+ uhci_resume(s);
+}
+
+static void uhci_detach(USBPort *port1)
+{
+ UHCIState *s = port1->opaque;
+ UHCIPort *port = &s->ports[port1->index];
+
+ uhci_async_cancel_device(s, port1->dev);
+
+ /* set connect status */
+ if (port->ctrl & UHCI_PORT_CCS) {
+ port->ctrl &= ~UHCI_PORT_CCS;
+ port->ctrl |= UHCI_PORT_CSC;
+ }
+ /* disable port */
+ if (port->ctrl & UHCI_PORT_EN) {
+ port->ctrl &= ~UHCI_PORT_EN;
+ port->ctrl |= UHCI_PORT_ENC;
+ }
+
+ uhci_resume(s);
+}
+
+static void uhci_child_detach(USBPort *port1, USBDevice *child)
+{
+ UHCIState *s = port1->opaque;
+
+ uhci_async_cancel_device(s, child);
+}
+
+static void uhci_wakeup(USBPort *port1)
+{
+ UHCIState *s = port1->opaque;
+ UHCIPort *port = &s->ports[port1->index];
+
+ if (port->ctrl & UHCI_PORT_SUSPEND && !(port->ctrl & UHCI_PORT_RD)) {
+ port->ctrl |= UHCI_PORT_RD;
+ uhci_resume(s);
+ }
+}
+
+static USBDevice *uhci_find_device(UHCIState *s, uint8_t addr)
+{
+ USBDevice *dev;
+ int i;
+
+ for (i = 0; i < NB_PORTS; i++) {
+ UHCIPort *port = &s->ports[i];
+ if (!(port->ctrl & UHCI_PORT_EN)) {
+ continue;
+ }
+ dev = usb_find_device(&port->port, addr);
+ if (dev != NULL) {
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+static void uhci_read_td(UHCIState *s, UHCI_TD *td, uint32_t link)
+{
+ pci_dma_read(&s->dev, link & ~0xf, td, sizeof(*td));
+ le32_to_cpus(&td->link);
+ le32_to_cpus(&td->ctrl);
+ le32_to_cpus(&td->token);
+ le32_to_cpus(&td->buffer);
+}
+
+static int uhci_handle_td_error(UHCIState *s, UHCI_TD *td, uint32_t td_addr,
+ int status, uint32_t *int_mask)
+{
+ uint32_t queue_token = uhci_queue_token(td);
+ int ret;
+
+ switch (status) {
+ case USB_RET_NAK:
+ td->ctrl |= TD_CTRL_NAK;
+ return TD_RESULT_NEXT_QH;
+
+ case USB_RET_STALL:
+ td->ctrl |= TD_CTRL_STALL;
+ trace_usb_uhci_packet_complete_stall(queue_token, td_addr);
+ ret = TD_RESULT_NEXT_QH;
+ break;
+
+ case USB_RET_BABBLE:
+ td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL;
+ /* frame interrupted */
+ trace_usb_uhci_packet_complete_babble(queue_token, td_addr);
+ ret = TD_RESULT_STOP_FRAME;
+ break;
+
+ case USB_RET_IOERROR:
+ case USB_RET_NODEV:
+ default:
+ td->ctrl |= TD_CTRL_TIMEOUT;
+ td->ctrl &= ~(3 << TD_CTRL_ERROR_SHIFT);
+ trace_usb_uhci_packet_complete_error(queue_token, td_addr);
+ ret = TD_RESULT_NEXT_QH;
+ break;
+ }
+
+ td->ctrl &= ~TD_CTRL_ACTIVE;
+ s->status |= UHCI_STS_USBERR;
+ if (td->ctrl & TD_CTRL_IOC) {
+ *int_mask |= 0x01;
+ }
+ uhci_update_irq(s);
+ return ret;
+}
+
+static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_t *int_mask)
+{
+ int len = 0, max_len;
+ uint8_t pid;
+
+ max_len = ((td->token >> 21) + 1) & 0x7ff;
+ pid = td->token & 0xff;
+
+ if (td->ctrl & TD_CTRL_IOS)
+ td->ctrl &= ~TD_CTRL_ACTIVE;
+
+ if (async->packet.status != USB_RET_SUCCESS) {
+ return uhci_handle_td_error(s, td, async->td_addr,
+ async->packet.status, int_mask);
+ }
+
+ len = async->packet.actual_length;
+ td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff);
+
+ /* The NAK bit may have been set by a previous frame, so clear it
+ here. The docs are somewhat unclear, but win2k relies on this
+ behavior. */
+ td->ctrl &= ~(TD_CTRL_ACTIVE | TD_CTRL_NAK);
+ if (td->ctrl & TD_CTRL_IOC)
+ *int_mask |= 0x01;
+
+ if (pid == USB_TOKEN_IN) {
+ pci_dma_write(&s->dev, td->buffer, async->buf, len);
+ if ((td->ctrl & TD_CTRL_SPD) && len < max_len) {
+ *int_mask |= 0x02;
+ /* short packet: do not update QH */
+ trace_usb_uhci_packet_complete_shortxfer(async->queue->token,
+ async->td_addr);
+ return TD_RESULT_NEXT_QH;
+ }
+ }
+
+ /* success */
+ trace_usb_uhci_packet_complete_success(async->queue->token,
+ async->td_addr);
+ return TD_RESULT_COMPLETE;
+}
+
+static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr,
+ UHCI_TD *td, uint32_t td_addr, uint32_t *int_mask)
+{
+ int ret, max_len;
+ bool spd;
+ bool queuing = (q != NULL);
+ uint8_t pid = td->token & 0xff;
+ UHCIAsync *async = uhci_async_find_td(s, td_addr);
+
+ if (async) {
+ if (uhci_queue_verify(async->queue, qh_addr, td, td_addr, queuing)) {
+ assert(q == NULL || q == async->queue);
+ q = async->queue;
+ } else {
+ uhci_queue_free(async->queue, "guest re-used pending td");
+ async = NULL;
+ }
+ }
+
+ if (q == NULL) {
+ q = uhci_queue_find(s, td);
+ if (q && !uhci_queue_verify(q, qh_addr, td, td_addr, queuing)) {
+ uhci_queue_free(q, "guest re-used qh");
+ q = NULL;
+ }
+ }
+
+ if (q) {
+ q->valid = QH_VALID;
+ }
+
+ /* Is active ? */
+ if (!(td->ctrl & TD_CTRL_ACTIVE)) {
+ if (async) {
+ /* Guest marked a pending td non-active, cancel the queue */
+ uhci_queue_free(async->queue, "pending td non-active");
+ }
+ /*
+ * ehci11d spec page 22: "Even if the Active bit in the TD is already
+ * cleared when the TD is fetched ... an IOC interrupt is generated"
+ */
+ if (td->ctrl & TD_CTRL_IOC) {
+ *int_mask |= 0x01;
+ }
+ return TD_RESULT_NEXT_QH;
+ }
+
+ if (async) {
+ if (queuing) {
+ /* we are busy filling the queue, we are not prepared
+ to consume completed packages then, just leave them
+ in async state */
+ return TD_RESULT_ASYNC_CONT;
+ }
+ if (!async->done) {
+ UHCI_TD last_td;
+ UHCIAsync *last = QTAILQ_LAST(&async->queue->asyncs, asyncs_head);
+ /*
+ * While we are waiting for the current td to complete, the guest
+ * may have added more tds to the queue. Note we re-read the td
+ * rather then caching it, as we want to see guest made changes!
+ */
+ uhci_read_td(s, &last_td, last->td_addr);
+ uhci_queue_fill(async->queue, &last_td);
+
+ return TD_RESULT_ASYNC_CONT;
+ }
+ uhci_async_unlink(async);
+ goto done;
+ }
+
+ if (s->completions_only) {
+ return TD_RESULT_ASYNC_CONT;
+ }
+
+ /* Allocate new packet */
+ if (q == NULL) {
+ USBDevice *dev = uhci_find_device(s, (td->token >> 8) & 0x7f);
+ USBEndpoint *ep = usb_ep_get(dev, pid, (td->token >> 15) & 0xf);
+
+ if (ep == NULL) {
+ return uhci_handle_td_error(s, td, td_addr, USB_RET_NODEV,
+ int_mask);
+ }
+ q = uhci_queue_new(s, qh_addr, td, ep);
+ }
+ async = uhci_async_alloc(q, td_addr);
+
+ max_len = ((td->token >> 21) + 1) & 0x7ff;
+ spd = (pid == USB_TOKEN_IN && (td->ctrl & TD_CTRL_SPD) != 0);
+ usb_packet_setup(&async->packet, pid, q->ep, 0, td_addr, spd,
+ (td->ctrl & TD_CTRL_IOC) != 0);
+ if (max_len <= sizeof(async->static_buf)) {
+ async->buf = async->static_buf;
+ } else {
+ async->buf = g_malloc(max_len);
+ }
+ usb_packet_addbuf(&async->packet, async->buf, max_len);
+
+ switch(pid) {
+ case USB_TOKEN_OUT:
+ case USB_TOKEN_SETUP:
+ pci_dma_read(&s->dev, td->buffer, async->buf, max_len);
+ usb_handle_packet(q->ep->dev, &async->packet);
+ if (async->packet.status == USB_RET_SUCCESS) {
+ async->packet.actual_length = max_len;
+ }
+ break;
+
+ case USB_TOKEN_IN:
+ usb_handle_packet(q->ep->dev, &async->packet);
+ break;
+
+ default:
+ /* invalid pid : frame interrupted */
+ uhci_async_free(async);
+ s->status |= UHCI_STS_HCPERR;
+ uhci_update_irq(s);
+ return TD_RESULT_STOP_FRAME;
+ }
+
+ if (async->packet.status == USB_RET_ASYNC) {
+ uhci_async_link(async);
+ if (!queuing) {
+ uhci_queue_fill(q, td);
+ }
+ return TD_RESULT_ASYNC_START;
+ }
+
+done:
+ ret = uhci_complete_td(s, td, async, int_mask);
+ uhci_async_free(async);
+ return ret;
+}
+
+static void uhci_async_complete(USBPort *port, USBPacket *packet)
+{
+ UHCIAsync *async = container_of(packet, UHCIAsync, packet);
+ UHCIState *s = async->queue->uhci;
+
+ if (packet->status == USB_RET_REMOVE_FROM_QUEUE) {
+ uhci_async_cancel(async);
+ return;
+ }
+
+ async->done = 1;
+ /* Force processing of this packet *now*, needed for migration */
+ s->completions_only = true;
+ qemu_bh_schedule(s->bh);
+}
+
+static int is_valid(uint32_t link)
+{
+ return (link & 1) == 0;
+}
+
+static int is_qh(uint32_t link)
+{
+ return (link & 2) != 0;
+}
+
+static int depth_first(uint32_t link)
+{
+ return (link & 4) != 0;
+}
+
+/* QH DB used for detecting QH loops */
+#define UHCI_MAX_QUEUES 128
+typedef struct {
+ uint32_t addr[UHCI_MAX_QUEUES];
+ int count;
+} QhDb;
+
+static void qhdb_reset(QhDb *db)
+{
+ db->count = 0;
+}
+
+/* Add QH to DB. Returns 1 if already present or DB is full. */
+static int qhdb_insert(QhDb *db, uint32_t addr)
+{
+ int i;
+ for (i = 0; i < db->count; i++)
+ if (db->addr[i] == addr)
+ return 1;
+
+ if (db->count >= UHCI_MAX_QUEUES)
+ return 1;
+
+ db->addr[db->count++] = addr;
+ return 0;
+}
+
+static void uhci_queue_fill(UHCIQueue *q, UHCI_TD *td)
+{
+ uint32_t int_mask = 0;
+ uint32_t plink = td->link;
+ UHCI_TD ptd;
+ int ret;
+
+ while (is_valid(plink)) {
+ uhci_read_td(q->uhci, &ptd, plink);
+ if (!(ptd.ctrl & TD_CTRL_ACTIVE)) {
+ break;
+ }
+ if (uhci_queue_token(&ptd) != q->token) {
+ break;
+ }
+ trace_usb_uhci_td_queue(plink & ~0xf, ptd.ctrl, ptd.token);
+ ret = uhci_handle_td(q->uhci, q, q->qh_addr, &ptd, plink, &int_mask);
+ if (ret == TD_RESULT_ASYNC_CONT) {
+ break;
+ }
+ assert(ret == TD_RESULT_ASYNC_START);
+ assert(int_mask == 0);
+ plink = ptd.link;
+ }
+ usb_device_flush_ep_queue(q->ep->dev, q->ep);
+}
+
+static void uhci_process_frame(UHCIState *s)
+{
+ uint32_t frame_addr, link, old_td_ctrl, val, int_mask;
+ uint32_t curr_qh, td_count = 0;
+ int cnt, ret;
+ UHCI_TD td;
+ UHCI_QH qh;
+ QhDb qhdb;
+
+ frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2);
+
+ pci_dma_read(&s->dev, frame_addr, &link, 4);
+ le32_to_cpus(&link);
+
+ int_mask = 0;
+ curr_qh = 0;
+
+ qhdb_reset(&qhdb);
+
+ for (cnt = FRAME_MAX_LOOPS; is_valid(link) && cnt; cnt--) {
+ if (!s->completions_only && s->frame_bytes >= s->frame_bandwidth) {
+ /* We've reached the usb 1.1 bandwidth, which is
+ 1280 bytes/frame, stop processing */
+ trace_usb_uhci_frame_stop_bandwidth();
+ break;
+ }
+ if (is_qh(link)) {
+ /* QH */
+ trace_usb_uhci_qh_load(link & ~0xf);
+
+ if (qhdb_insert(&qhdb, link)) {
+ /*
+ * We're going in circles. Which is not a bug because
+ * HCD is allowed to do that as part of the BW management.
+ *
+ * Stop processing here if no transaction has been done
+ * since we've been here last time.
+ */
+ if (td_count == 0) {
+ trace_usb_uhci_frame_loop_stop_idle();
+ break;
+ } else {
+ trace_usb_uhci_frame_loop_continue();
+ td_count = 0;
+ qhdb_reset(&qhdb);
+ qhdb_insert(&qhdb, link);
+ }
+ }
+
+ pci_dma_read(&s->dev, link & ~0xf, &qh, sizeof(qh));
+ le32_to_cpus(&qh.link);
+ le32_to_cpus(&qh.el_link);
+
+ if (!is_valid(qh.el_link)) {
+ /* QH w/o elements */
+ curr_qh = 0;
+ link = qh.link;
+ } else {
+ /* QH with elements */
+ curr_qh = link;
+ link = qh.el_link;
+ }
+ continue;
+ }
+
+ /* TD */
+ uhci_read_td(s, &td, link);
+ trace_usb_uhci_td_load(curr_qh & ~0xf, link & ~0xf, td.ctrl, td.token);
+
+ old_td_ctrl = td.ctrl;
+ ret = uhci_handle_td(s, NULL, curr_qh, &td, link, &int_mask);
+ if (old_td_ctrl != td.ctrl) {
+ /* update the status bits of the TD */
+ val = cpu_to_le32(td.ctrl);
+ pci_dma_write(&s->dev, (link & ~0xf) + 4, &val, sizeof(val));
+ }
+
+ switch (ret) {
+ case TD_RESULT_STOP_FRAME: /* interrupted frame */
+ goto out;
+
+ case TD_RESULT_NEXT_QH:
+ case TD_RESULT_ASYNC_CONT:
+ trace_usb_uhci_td_nextqh(curr_qh & ~0xf, link & ~0xf);
+ link = curr_qh ? qh.link : td.link;
+ continue;
+
+ case TD_RESULT_ASYNC_START:
+ trace_usb_uhci_td_async(curr_qh & ~0xf, link & ~0xf);
+ link = curr_qh ? qh.link : td.link;
+ continue;
+
+ case TD_RESULT_COMPLETE:
+ trace_usb_uhci_td_complete(curr_qh & ~0xf, link & ~0xf);
+ link = td.link;
+ td_count++;
+ s->frame_bytes += (td.ctrl & 0x7ff) + 1;
+
+ if (curr_qh) {
+ /* update QH element link */
+ qh.el_link = link;
+ val = cpu_to_le32(qh.el_link);
+ pci_dma_write(&s->dev, (curr_qh & ~0xf) + 4, &val, sizeof(val));
+
+ if (!depth_first(link)) {
+ /* done with this QH */
+ curr_qh = 0;
+ link = qh.link;
+ }
+ }
+ break;
+
+ default:
+ assert(!"unknown return code");
+ }
+
+ /* go to the next entry */
+ }
+
+out:
+ s->pending_int_mask |= int_mask;
+}
+
+static void uhci_bh(void *opaque)
+{
+ UHCIState *s = opaque;
+ uhci_process_frame(s);
+}
+
+static void uhci_frame_timer(void *opaque)
+{
+ UHCIState *s = opaque;
+ uint64_t t_now, t_last_run;
+ int i, frames;
+ const uint64_t frame_t = get_ticks_per_sec() / FRAME_TIMER_FREQ;
+
+ s->completions_only = false;
+ qemu_bh_cancel(s->bh);
+
+ if (!(s->cmd & UHCI_CMD_RS)) {
+ /* Full stop */
+ trace_usb_uhci_schedule_stop();
+ timer_del(s->frame_timer);
+ uhci_async_cancel_all(s);
+ /* set hchalted bit in status - UHCI11D 2.1.2 */
+ s->status |= UHCI_STS_HCHALTED;
+ return;
+ }
+
+ /* We still store expire_time in our state, for migration */
+ t_last_run = s->expire_time - frame_t;
+ t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ /* Process up to MAX_FRAMES_PER_TICK frames */
+ frames = (t_now - t_last_run) / frame_t;
+ if (frames > s->maxframes) {
+ int skipped = frames - s->maxframes;
+ s->expire_time += skipped * frame_t;
+ s->frnum = (s->frnum + skipped) & 0x7ff;
+ frames -= skipped;
+ }
+ if (frames > MAX_FRAMES_PER_TICK) {
+ frames = MAX_FRAMES_PER_TICK;
+ }
+
+ for (i = 0; i < frames; i++) {
+ s->frame_bytes = 0;
+ trace_usb_uhci_frame_start(s->frnum);
+ uhci_async_validate_begin(s);
+ uhci_process_frame(s);
+ uhci_async_validate_end(s);
+ /* The spec says frnum is the frame currently being processed, and
+ * the guest must look at frnum - 1 on interrupt, so inc frnum now */
+ s->frnum = (s->frnum + 1) & 0x7ff;
+ s->expire_time += frame_t;
+ }
+
+ /* Complete the previous frame(s) */
+ if (s->pending_int_mask) {
+ s->status2 |= s->pending_int_mask;
+ s->status |= UHCI_STS_USBINT;
+ uhci_update_irq(s);
+ }
+ s->pending_int_mask = 0;
+
+ timer_mod(s->frame_timer, t_now + frame_t);
+}
+
+static const MemoryRegionOps uhci_ioport_ops = {
+ .read = uhci_port_read,
+ .write = uhci_port_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 2,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static USBPortOps uhci_port_ops = {
+ .attach = uhci_attach,
+ .detach = uhci_detach,
+ .child_detach = uhci_child_detach,
+ .wakeup = uhci_wakeup,
+ .complete = uhci_async_complete,
+};
+
+static USBBusOps uhci_bus_ops = {
+};
+
+static void usb_uhci_common_realize(PCIDevice *dev, Error **errp)
+{
+ Error *err = NULL;
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+ UHCIPCIDeviceClass *u = container_of(pc, UHCIPCIDeviceClass, parent_class);
+ UHCIState *s = UHCI(dev);
+ uint8_t *pci_conf = s->dev.config;
+ int i;
+
+ pci_conf[PCI_CLASS_PROG] = 0x00;
+ /* TODO: reset value should be 0. */
+ pci_conf[USB_SBRN] = USB_RELEASE_1; // release number
+
+ pci_config_set_interrupt_pin(pci_conf, u->info.irq_pin + 1);
+
+ if (s->masterbus) {
+ USBPort *ports[NB_PORTS];
+ for(i = 0; i < NB_PORTS; i++) {
+ ports[i] = &s->ports[i].port;
+ }
+ usb_register_companion(s->masterbus, ports, NB_PORTS,
+ s->firstport, s, &uhci_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL,
+ &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ } else {
+ usb_bus_new(&s->bus, sizeof(s->bus), &uhci_bus_ops, DEVICE(dev));
+ for (i = 0; i < NB_PORTS; i++) {
+ usb_register_port(&s->bus, &s->ports[i].port, s, i, &uhci_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
+ }
+ }
+ s->bh = qemu_bh_new(uhci_bh, s);
+ s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, uhci_frame_timer, s);
+ s->num_ports_vmstate = NB_PORTS;
+ QTAILQ_INIT(&s->queues);
+
+ memory_region_init_io(&s->io_bar, OBJECT(s), &uhci_ioport_ops, s,
+ "uhci", 0x20);
+
+ /* Use region 4 for consistency with real hardware. BSD guests seem
+ to rely on this. */
+ pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar);
+}
+
+static void usb_uhci_vt82c686b_realize(PCIDevice *dev, Error **errp)
+{
+ UHCIState *s = UHCI(dev);
+ uint8_t *pci_conf = s->dev.config;
+
+ /* USB misc control 1/2 */
+ pci_set_long(pci_conf + 0x40,0x00001000);
+ /* PM capability */
+ pci_set_long(pci_conf + 0x80,0x00020001);
+ /* USB legacy support */
+ pci_set_long(pci_conf + 0xc0,0x00002000);
+
+ usb_uhci_common_realize(dev, errp);
+}
+
+static void usb_uhci_exit(PCIDevice *dev)
+{
+ UHCIState *s = UHCI(dev);
+
+ trace_usb_uhci_exit();
+
+ if (s->frame_timer) {
+ timer_del(s->frame_timer);
+ timer_free(s->frame_timer);
+ s->frame_timer = NULL;
+ }
+
+ if (s->bh) {
+ qemu_bh_delete(s->bh);
+ }
+
+ uhci_async_cancel_all(s);
+
+ if (!s->masterbus) {
+ usb_bus_release(&s->bus);
+ }
+}
+
+static Property uhci_properties_companion[] = {
+ DEFINE_PROP_STRING("masterbus", UHCIState, masterbus),
+ DEFINE_PROP_UINT32("firstport", UHCIState, firstport, 0),
+ DEFINE_PROP_UINT32("bandwidth", UHCIState, frame_bandwidth, 1280),
+ DEFINE_PROP_UINT32("maxframes", UHCIState, maxframes, 128),
+ DEFINE_PROP_END_OF_LIST(),
+};
+static Property uhci_properties_standalone[] = {
+ DEFINE_PROP_UINT32("bandwidth", UHCIState, frame_bandwidth, 1280),
+ DEFINE_PROP_UINT32("maxframes", UHCIState, maxframes, 128),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void uhci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->class_id = PCI_CLASS_SERIAL_USB;
+ dc->vmsd = &vmstate_uhci;
+ dc->reset = uhci_reset;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+}
+
+static const TypeInfo uhci_pci_type_info = {
+ .name = TYPE_UHCI,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(UHCIState),
+ .class_size = sizeof(UHCIPCIDeviceClass),
+ .abstract = true,
+ .class_init = uhci_class_init,
+};
+
+static void uhci_data_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ UHCIPCIDeviceClass *u = container_of(k, UHCIPCIDeviceClass, parent_class);
+ UHCIInfo *info = data;
+
+ k->realize = info->realize ? info->realize : usb_uhci_common_realize;
+ k->exit = info->unplug ? usb_uhci_exit : NULL;
+ k->vendor_id = info->vendor_id;
+ k->device_id = info->device_id;
+ k->revision = info->revision;
+ if (!info->unplug) {
+ /* uhci controllers in companion setups can't be hotplugged */
+ dc->hotpluggable = false;
+ dc->props = uhci_properties_companion;
+ } else {
+ dc->props = uhci_properties_standalone;
+ }
+ u->info = *info;
+}
+
+static UHCIInfo uhci_info[] = {
+ {
+ .name = "piix3-usb-uhci",
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82371SB_2,
+ .revision = 0x01,
+ .irq_pin = 3,
+ .unplug = true,
+ },{
+ .name = "piix4-usb-uhci",
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82371AB_2,
+ .revision = 0x01,
+ .irq_pin = 3,
+ .unplug = true,
+ },{
+ .name = "vt82c686b-usb-uhci",
+ .vendor_id = PCI_VENDOR_ID_VIA,
+ .device_id = PCI_DEVICE_ID_VIA_UHCI,
+ .revision = 0x01,
+ .irq_pin = 3,
+ .realize = usb_uhci_vt82c686b_realize,
+ .unplug = true,
+ },{
+ .name = "ich9-usb-uhci1", /* 00:1d.0 */
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI1,
+ .revision = 0x03,
+ .irq_pin = 0,
+ .unplug = false,
+ },{
+ .name = "ich9-usb-uhci2", /* 00:1d.1 */
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI2,
+ .revision = 0x03,
+ .irq_pin = 1,
+ .unplug = false,
+ },{
+ .name = "ich9-usb-uhci3", /* 00:1d.2 */
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI3,
+ .revision = 0x03,
+ .irq_pin = 2,
+ .unplug = false,
+ },{
+ .name = "ich9-usb-uhci4", /* 00:1a.0 */
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI4,
+ .revision = 0x03,
+ .irq_pin = 0,
+ .unplug = false,
+ },{
+ .name = "ich9-usb-uhci5", /* 00:1a.1 */
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI5,
+ .revision = 0x03,
+ .irq_pin = 1,
+ .unplug = false,
+ },{
+ .name = "ich9-usb-uhci6", /* 00:1a.2 */
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI6,
+ .revision = 0x03,
+ .irq_pin = 2,
+ .unplug = false,
+ }
+};
+
+static void uhci_register_types(void)
+{
+ TypeInfo uhci_type_info = {
+ .parent = TYPE_UHCI,
+ .class_init = uhci_data_class_init,
+ };
+ int i;
+
+ type_register_static(&uhci_pci_type_info);
+
+ for (i = 0; i < ARRAY_SIZE(uhci_info); i++) {
+ uhci_type_info.name = uhci_info[i].name;
+ uhci_type_info.class_data = uhci_info + i;
+ type_register(&uhci_type_info);
+ }
+}
+
+type_init(uhci_register_types)
diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
new file mode 100644
index 00000000..c673bed4
--- /dev/null
+++ b/hw/usb/hcd-xhci.c
@@ -0,0 +1,3916 @@
+/*
+ * USB xHCI controller emulation
+ *
+ * Copyright (c) 2011 Securiforest
+ * Date: 2011-05-11 ; Author: Hector Martin <hector@marcansoft.com>
+ * Based on usb-ohci.c, emulates Renesas NEC USB 3.0
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/usb.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "trace.h"
+
+//#define DEBUG_XHCI
+//#define DEBUG_DATA
+
+#ifdef DEBUG_XHCI
+#define DPRINTF(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define DPRINTF(...) do {} while (0)
+#endif
+#define FIXME(_msg) do { fprintf(stderr, "FIXME %s:%d %s\n", \
+ __func__, __LINE__, _msg); abort(); } while (0)
+
+#define MAXPORTS_2 15
+#define MAXPORTS_3 15
+
+#define MAXPORTS (MAXPORTS_2+MAXPORTS_3)
+#define MAXSLOTS 64
+#define MAXINTRS 16
+
+#define TD_QUEUE 24
+
+/* Very pessimistic, let's hope it's enough for all cases */
+#define EV_QUEUE (((3*TD_QUEUE)+16)*MAXSLOTS)
+/* Do not deliver ER Full events. NEC's driver does some things not bound
+ * to the specs when it gets them */
+#define ER_FULL_HACK
+
+#define LEN_CAP 0x40
+#define LEN_OPER (0x400 + 0x10 * MAXPORTS)
+#define LEN_RUNTIME ((MAXINTRS + 1) * 0x20)
+#define LEN_DOORBELL ((MAXSLOTS + 1) * 0x20)
+
+#define OFF_OPER LEN_CAP
+#define OFF_RUNTIME 0x1000
+#define OFF_DOORBELL 0x2000
+#define OFF_MSIX_TABLE 0x3000
+#define OFF_MSIX_PBA 0x3800
+/* must be power of 2 */
+#define LEN_REGS 0x4000
+
+#if (OFF_OPER + LEN_OPER) > OFF_RUNTIME
+#error Increase OFF_RUNTIME
+#endif
+#if (OFF_RUNTIME + LEN_RUNTIME) > OFF_DOORBELL
+#error Increase OFF_DOORBELL
+#endif
+#if (OFF_DOORBELL + LEN_DOORBELL) > LEN_REGS
+# error Increase LEN_REGS
+#endif
+
+/* bit definitions */
+#define USBCMD_RS (1<<0)
+#define USBCMD_HCRST (1<<1)
+#define USBCMD_INTE (1<<2)
+#define USBCMD_HSEE (1<<3)
+#define USBCMD_LHCRST (1<<7)
+#define USBCMD_CSS (1<<8)
+#define USBCMD_CRS (1<<9)
+#define USBCMD_EWE (1<<10)
+#define USBCMD_EU3S (1<<11)
+
+#define USBSTS_HCH (1<<0)
+#define USBSTS_HSE (1<<2)
+#define USBSTS_EINT (1<<3)
+#define USBSTS_PCD (1<<4)
+#define USBSTS_SSS (1<<8)
+#define USBSTS_RSS (1<<9)
+#define USBSTS_SRE (1<<10)
+#define USBSTS_CNR (1<<11)
+#define USBSTS_HCE (1<<12)
+
+
+#define PORTSC_CCS (1<<0)
+#define PORTSC_PED (1<<1)
+#define PORTSC_OCA (1<<3)
+#define PORTSC_PR (1<<4)
+#define PORTSC_PLS_SHIFT 5
+#define PORTSC_PLS_MASK 0xf
+#define PORTSC_PP (1<<9)
+#define PORTSC_SPEED_SHIFT 10
+#define PORTSC_SPEED_MASK 0xf
+#define PORTSC_SPEED_FULL (1<<10)
+#define PORTSC_SPEED_LOW (2<<10)
+#define PORTSC_SPEED_HIGH (3<<10)
+#define PORTSC_SPEED_SUPER (4<<10)
+#define PORTSC_PIC_SHIFT 14
+#define PORTSC_PIC_MASK 0x3
+#define PORTSC_LWS (1<<16)
+#define PORTSC_CSC (1<<17)
+#define PORTSC_PEC (1<<18)
+#define PORTSC_WRC (1<<19)
+#define PORTSC_OCC (1<<20)
+#define PORTSC_PRC (1<<21)
+#define PORTSC_PLC (1<<22)
+#define PORTSC_CEC (1<<23)
+#define PORTSC_CAS (1<<24)
+#define PORTSC_WCE (1<<25)
+#define PORTSC_WDE (1<<26)
+#define PORTSC_WOE (1<<27)
+#define PORTSC_DR (1<<30)
+#define PORTSC_WPR (1<<31)
+
+#define CRCR_RCS (1<<0)
+#define CRCR_CS (1<<1)
+#define CRCR_CA (1<<2)
+#define CRCR_CRR (1<<3)
+
+#define IMAN_IP (1<<0)
+#define IMAN_IE (1<<1)
+
+#define ERDP_EHB (1<<3)
+
+#define TRB_SIZE 16
+typedef struct XHCITRB {
+ uint64_t parameter;
+ uint32_t status;
+ uint32_t control;
+ dma_addr_t addr;
+ bool ccs;
+} XHCITRB;
+
+enum {
+ PLS_U0 = 0,
+ PLS_U1 = 1,
+ PLS_U2 = 2,
+ PLS_U3 = 3,
+ PLS_DISABLED = 4,
+ PLS_RX_DETECT = 5,
+ PLS_INACTIVE = 6,
+ PLS_POLLING = 7,
+ PLS_RECOVERY = 8,
+ PLS_HOT_RESET = 9,
+ PLS_COMPILANCE_MODE = 10,
+ PLS_TEST_MODE = 11,
+ PLS_RESUME = 15,
+};
+
+typedef enum TRBType {
+ TRB_RESERVED = 0,
+ TR_NORMAL,
+ TR_SETUP,
+ TR_DATA,
+ TR_STATUS,
+ TR_ISOCH,
+ TR_LINK,
+ TR_EVDATA,
+ TR_NOOP,
+ CR_ENABLE_SLOT,
+ CR_DISABLE_SLOT,
+ CR_ADDRESS_DEVICE,
+ CR_CONFIGURE_ENDPOINT,
+ CR_EVALUATE_CONTEXT,
+ CR_RESET_ENDPOINT,
+ CR_STOP_ENDPOINT,
+ CR_SET_TR_DEQUEUE,
+ CR_RESET_DEVICE,
+ CR_FORCE_EVENT,
+ CR_NEGOTIATE_BW,
+ CR_SET_LATENCY_TOLERANCE,
+ CR_GET_PORT_BANDWIDTH,
+ CR_FORCE_HEADER,
+ CR_NOOP,
+ ER_TRANSFER = 32,
+ ER_COMMAND_COMPLETE,
+ ER_PORT_STATUS_CHANGE,
+ ER_BANDWIDTH_REQUEST,
+ ER_DOORBELL,
+ ER_HOST_CONTROLLER,
+ ER_DEVICE_NOTIFICATION,
+ ER_MFINDEX_WRAP,
+ /* vendor specific bits */
+ CR_VENDOR_VIA_CHALLENGE_RESPONSE = 48,
+ CR_VENDOR_NEC_FIRMWARE_REVISION = 49,
+ CR_VENDOR_NEC_CHALLENGE_RESPONSE = 50,
+} TRBType;
+
+#define CR_LINK TR_LINK
+
+typedef enum TRBCCode {
+ CC_INVALID = 0,
+ CC_SUCCESS,
+ CC_DATA_BUFFER_ERROR,
+ CC_BABBLE_DETECTED,
+ CC_USB_TRANSACTION_ERROR,
+ CC_TRB_ERROR,
+ CC_STALL_ERROR,
+ CC_RESOURCE_ERROR,
+ CC_BANDWIDTH_ERROR,
+ CC_NO_SLOTS_ERROR,
+ CC_INVALID_STREAM_TYPE_ERROR,
+ CC_SLOT_NOT_ENABLED_ERROR,
+ CC_EP_NOT_ENABLED_ERROR,
+ CC_SHORT_PACKET,
+ CC_RING_UNDERRUN,
+ CC_RING_OVERRUN,
+ CC_VF_ER_FULL,
+ CC_PARAMETER_ERROR,
+ CC_BANDWIDTH_OVERRUN,
+ CC_CONTEXT_STATE_ERROR,
+ CC_NO_PING_RESPONSE_ERROR,
+ CC_EVENT_RING_FULL_ERROR,
+ CC_INCOMPATIBLE_DEVICE_ERROR,
+ CC_MISSED_SERVICE_ERROR,
+ CC_COMMAND_RING_STOPPED,
+ CC_COMMAND_ABORTED,
+ CC_STOPPED,
+ CC_STOPPED_LENGTH_INVALID,
+ CC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR = 29,
+ CC_ISOCH_BUFFER_OVERRUN = 31,
+ CC_EVENT_LOST_ERROR,
+ CC_UNDEFINED_ERROR,
+ CC_INVALID_STREAM_ID_ERROR,
+ CC_SECONDARY_BANDWIDTH_ERROR,
+ CC_SPLIT_TRANSACTION_ERROR
+} TRBCCode;
+
+#define TRB_C (1<<0)
+#define TRB_TYPE_SHIFT 10
+#define TRB_TYPE_MASK 0x3f
+#define TRB_TYPE(t) (((t).control >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK)
+
+#define TRB_EV_ED (1<<2)
+
+#define TRB_TR_ENT (1<<1)
+#define TRB_TR_ISP (1<<2)
+#define TRB_TR_NS (1<<3)
+#define TRB_TR_CH (1<<4)
+#define TRB_TR_IOC (1<<5)
+#define TRB_TR_IDT (1<<6)
+#define TRB_TR_TBC_SHIFT 7
+#define TRB_TR_TBC_MASK 0x3
+#define TRB_TR_BEI (1<<9)
+#define TRB_TR_TLBPC_SHIFT 16
+#define TRB_TR_TLBPC_MASK 0xf
+#define TRB_TR_FRAMEID_SHIFT 20
+#define TRB_TR_FRAMEID_MASK 0x7ff
+#define TRB_TR_SIA (1<<31)
+
+#define TRB_TR_DIR (1<<16)
+
+#define TRB_CR_SLOTID_SHIFT 24
+#define TRB_CR_SLOTID_MASK 0xff
+#define TRB_CR_EPID_SHIFT 16
+#define TRB_CR_EPID_MASK 0x1f
+
+#define TRB_CR_BSR (1<<9)
+#define TRB_CR_DC (1<<9)
+
+#define TRB_LK_TC (1<<1)
+
+#define TRB_INTR_SHIFT 22
+#define TRB_INTR_MASK 0x3ff
+#define TRB_INTR(t) (((t).status >> TRB_INTR_SHIFT) & TRB_INTR_MASK)
+
+#define EP_TYPE_MASK 0x7
+#define EP_TYPE_SHIFT 3
+
+#define EP_STATE_MASK 0x7
+#define EP_DISABLED (0<<0)
+#define EP_RUNNING (1<<0)
+#define EP_HALTED (2<<0)
+#define EP_STOPPED (3<<0)
+#define EP_ERROR (4<<0)
+
+#define SLOT_STATE_MASK 0x1f
+#define SLOT_STATE_SHIFT 27
+#define SLOT_STATE(s) (((s)>>SLOT_STATE_SHIFT)&SLOT_STATE_MASK)
+#define SLOT_ENABLED 0
+#define SLOT_DEFAULT 1
+#define SLOT_ADDRESSED 2
+#define SLOT_CONFIGURED 3
+
+#define SLOT_CONTEXT_ENTRIES_MASK 0x1f
+#define SLOT_CONTEXT_ENTRIES_SHIFT 27
+
+typedef struct XHCIState XHCIState;
+typedef struct XHCIStreamContext XHCIStreamContext;
+typedef struct XHCIEPContext XHCIEPContext;
+
+#define get_field(data, field) \
+ (((data) >> field##_SHIFT) & field##_MASK)
+
+#define set_field(data, newval, field) do { \
+ uint32_t val = *data; \
+ val &= ~(field##_MASK << field##_SHIFT); \
+ val |= ((newval) & field##_MASK) << field##_SHIFT; \
+ *data = val; \
+ } while (0)
+
+typedef enum EPType {
+ ET_INVALID = 0,
+ ET_ISO_OUT,
+ ET_BULK_OUT,
+ ET_INTR_OUT,
+ ET_CONTROL,
+ ET_ISO_IN,
+ ET_BULK_IN,
+ ET_INTR_IN,
+} EPType;
+
+typedef struct XHCIRing {
+ dma_addr_t dequeue;
+ bool ccs;
+} XHCIRing;
+
+typedef struct XHCIPort {
+ XHCIState *xhci;
+ uint32_t portsc;
+ uint32_t portnr;
+ USBPort *uport;
+ uint32_t speedmask;
+ char name[16];
+ MemoryRegion mem;
+} XHCIPort;
+
+typedef struct XHCITransfer {
+ XHCIState *xhci;
+ USBPacket packet;
+ QEMUSGList sgl;
+ bool running_async;
+ bool running_retry;
+ bool complete;
+ bool int_req;
+ unsigned int iso_pkts;
+ unsigned int slotid;
+ unsigned int epid;
+ unsigned int streamid;
+ bool in_xfer;
+ bool iso_xfer;
+ bool timed_xfer;
+
+ unsigned int trb_count;
+ unsigned int trb_alloced;
+ XHCITRB *trbs;
+
+ TRBCCode status;
+
+ unsigned int pkts;
+ unsigned int pktsize;
+ unsigned int cur_pkt;
+
+ uint64_t mfindex_kick;
+} XHCITransfer;
+
+struct XHCIStreamContext {
+ dma_addr_t pctx;
+ unsigned int sct;
+ XHCIRing ring;
+};
+
+struct XHCIEPContext {
+ XHCIState *xhci;
+ unsigned int slotid;
+ unsigned int epid;
+
+ XHCIRing ring;
+ unsigned int next_xfer;
+ unsigned int comp_xfer;
+ XHCITransfer transfers[TD_QUEUE];
+ XHCITransfer *retry;
+ EPType type;
+ dma_addr_t pctx;
+ unsigned int max_psize;
+ uint32_t state;
+
+ /* streams */
+ unsigned int max_pstreams;
+ bool lsa;
+ unsigned int nr_pstreams;
+ XHCIStreamContext *pstreams;
+
+ /* iso xfer scheduling */
+ unsigned int interval;
+ int64_t mfindex_last;
+ QEMUTimer *kick_timer;
+};
+
+typedef struct XHCISlot {
+ bool enabled;
+ bool addressed;
+ dma_addr_t ctx;
+ USBPort *uport;
+ XHCIEPContext * eps[31];
+} XHCISlot;
+
+typedef struct XHCIEvent {
+ TRBType type;
+ TRBCCode ccode;
+ uint64_t ptr;
+ uint32_t length;
+ uint32_t flags;
+ uint8_t slotid;
+ uint8_t epid;
+} XHCIEvent;
+
+typedef struct XHCIInterrupter {
+ uint32_t iman;
+ uint32_t imod;
+ uint32_t erstsz;
+ uint32_t erstba_low;
+ uint32_t erstba_high;
+ uint32_t erdp_low;
+ uint32_t erdp_high;
+
+ bool msix_used, er_pcs, er_full;
+
+ dma_addr_t er_start;
+ uint32_t er_size;
+ unsigned int er_ep_idx;
+
+ XHCIEvent ev_buffer[EV_QUEUE];
+ unsigned int ev_buffer_put;
+ unsigned int ev_buffer_get;
+
+} XHCIInterrupter;
+
+struct XHCIState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ USBBus bus;
+ MemoryRegion mem;
+ MemoryRegion mem_cap;
+ MemoryRegion mem_oper;
+ MemoryRegion mem_runtime;
+ MemoryRegion mem_doorbell;
+
+ /* properties */
+ uint32_t numports_2;
+ uint32_t numports_3;
+ uint32_t numintrs;
+ uint32_t numslots;
+ uint32_t flags;
+ uint32_t max_pstreams_mask;
+
+ /* Operational Registers */
+ uint32_t usbcmd;
+ uint32_t usbsts;
+ uint32_t dnctrl;
+ uint32_t crcr_low;
+ uint32_t crcr_high;
+ uint32_t dcbaap_low;
+ uint32_t dcbaap_high;
+ uint32_t config;
+
+ USBPort uports[MAX(MAXPORTS_2, MAXPORTS_3)];
+ XHCIPort ports[MAXPORTS];
+ XHCISlot slots[MAXSLOTS];
+ uint32_t numports;
+
+ /* Runtime Registers */
+ int64_t mfindex_start;
+ QEMUTimer *mfwrap_timer;
+ XHCIInterrupter intr[MAXINTRS];
+
+ XHCIRing cmd_ring;
+};
+
+#define TYPE_XHCI "nec-usb-xhci"
+
+#define XHCI(obj) \
+ OBJECT_CHECK(XHCIState, (obj), TYPE_XHCI)
+
+typedef struct XHCIEvRingSeg {
+ uint32_t addr_low;
+ uint32_t addr_high;
+ uint32_t size;
+ uint32_t rsvd;
+} XHCIEvRingSeg;
+
+enum xhci_flags {
+ XHCI_FLAG_USE_MSI = 1,
+ XHCI_FLAG_USE_MSI_X,
+ XHCI_FLAG_SS_FIRST,
+ XHCI_FLAG_FORCE_PCIE_ENDCAP,
+ XHCI_FLAG_ENABLE_STREAMS,
+};
+
+static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid, unsigned int streamid);
+static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid);
+static void xhci_xfer_report(XHCITransfer *xfer);
+static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v);
+static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v);
+static USBEndpoint *xhci_epid_to_usbep(XHCIState *xhci,
+ unsigned int slotid, unsigned int epid);
+
+static const char *TRBType_names[] = {
+ [TRB_RESERVED] = "TRB_RESERVED",
+ [TR_NORMAL] = "TR_NORMAL",
+ [TR_SETUP] = "TR_SETUP",
+ [TR_DATA] = "TR_DATA",
+ [TR_STATUS] = "TR_STATUS",
+ [TR_ISOCH] = "TR_ISOCH",
+ [TR_LINK] = "TR_LINK",
+ [TR_EVDATA] = "TR_EVDATA",
+ [TR_NOOP] = "TR_NOOP",
+ [CR_ENABLE_SLOT] = "CR_ENABLE_SLOT",
+ [CR_DISABLE_SLOT] = "CR_DISABLE_SLOT",
+ [CR_ADDRESS_DEVICE] = "CR_ADDRESS_DEVICE",
+ [CR_CONFIGURE_ENDPOINT] = "CR_CONFIGURE_ENDPOINT",
+ [CR_EVALUATE_CONTEXT] = "CR_EVALUATE_CONTEXT",
+ [CR_RESET_ENDPOINT] = "CR_RESET_ENDPOINT",
+ [CR_STOP_ENDPOINT] = "CR_STOP_ENDPOINT",
+ [CR_SET_TR_DEQUEUE] = "CR_SET_TR_DEQUEUE",
+ [CR_RESET_DEVICE] = "CR_RESET_DEVICE",
+ [CR_FORCE_EVENT] = "CR_FORCE_EVENT",
+ [CR_NEGOTIATE_BW] = "CR_NEGOTIATE_BW",
+ [CR_SET_LATENCY_TOLERANCE] = "CR_SET_LATENCY_TOLERANCE",
+ [CR_GET_PORT_BANDWIDTH] = "CR_GET_PORT_BANDWIDTH",
+ [CR_FORCE_HEADER] = "CR_FORCE_HEADER",
+ [CR_NOOP] = "CR_NOOP",
+ [ER_TRANSFER] = "ER_TRANSFER",
+ [ER_COMMAND_COMPLETE] = "ER_COMMAND_COMPLETE",
+ [ER_PORT_STATUS_CHANGE] = "ER_PORT_STATUS_CHANGE",
+ [ER_BANDWIDTH_REQUEST] = "ER_BANDWIDTH_REQUEST",
+ [ER_DOORBELL] = "ER_DOORBELL",
+ [ER_HOST_CONTROLLER] = "ER_HOST_CONTROLLER",
+ [ER_DEVICE_NOTIFICATION] = "ER_DEVICE_NOTIFICATION",
+ [ER_MFINDEX_WRAP] = "ER_MFINDEX_WRAP",
+ [CR_VENDOR_VIA_CHALLENGE_RESPONSE] = "CR_VENDOR_VIA_CHALLENGE_RESPONSE",
+ [CR_VENDOR_NEC_FIRMWARE_REVISION] = "CR_VENDOR_NEC_FIRMWARE_REVISION",
+ [CR_VENDOR_NEC_CHALLENGE_RESPONSE] = "CR_VENDOR_NEC_CHALLENGE_RESPONSE",
+};
+
+static const char *TRBCCode_names[] = {
+ [CC_INVALID] = "CC_INVALID",
+ [CC_SUCCESS] = "CC_SUCCESS",
+ [CC_DATA_BUFFER_ERROR] = "CC_DATA_BUFFER_ERROR",
+ [CC_BABBLE_DETECTED] = "CC_BABBLE_DETECTED",
+ [CC_USB_TRANSACTION_ERROR] = "CC_USB_TRANSACTION_ERROR",
+ [CC_TRB_ERROR] = "CC_TRB_ERROR",
+ [CC_STALL_ERROR] = "CC_STALL_ERROR",
+ [CC_RESOURCE_ERROR] = "CC_RESOURCE_ERROR",
+ [CC_BANDWIDTH_ERROR] = "CC_BANDWIDTH_ERROR",
+ [CC_NO_SLOTS_ERROR] = "CC_NO_SLOTS_ERROR",
+ [CC_INVALID_STREAM_TYPE_ERROR] = "CC_INVALID_STREAM_TYPE_ERROR",
+ [CC_SLOT_NOT_ENABLED_ERROR] = "CC_SLOT_NOT_ENABLED_ERROR",
+ [CC_EP_NOT_ENABLED_ERROR] = "CC_EP_NOT_ENABLED_ERROR",
+ [CC_SHORT_PACKET] = "CC_SHORT_PACKET",
+ [CC_RING_UNDERRUN] = "CC_RING_UNDERRUN",
+ [CC_RING_OVERRUN] = "CC_RING_OVERRUN",
+ [CC_VF_ER_FULL] = "CC_VF_ER_FULL",
+ [CC_PARAMETER_ERROR] = "CC_PARAMETER_ERROR",
+ [CC_BANDWIDTH_OVERRUN] = "CC_BANDWIDTH_OVERRUN",
+ [CC_CONTEXT_STATE_ERROR] = "CC_CONTEXT_STATE_ERROR",
+ [CC_NO_PING_RESPONSE_ERROR] = "CC_NO_PING_RESPONSE_ERROR",
+ [CC_EVENT_RING_FULL_ERROR] = "CC_EVENT_RING_FULL_ERROR",
+ [CC_INCOMPATIBLE_DEVICE_ERROR] = "CC_INCOMPATIBLE_DEVICE_ERROR",
+ [CC_MISSED_SERVICE_ERROR] = "CC_MISSED_SERVICE_ERROR",
+ [CC_COMMAND_RING_STOPPED] = "CC_COMMAND_RING_STOPPED",
+ [CC_COMMAND_ABORTED] = "CC_COMMAND_ABORTED",
+ [CC_STOPPED] = "CC_STOPPED",
+ [CC_STOPPED_LENGTH_INVALID] = "CC_STOPPED_LENGTH_INVALID",
+ [CC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR]
+ = "CC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR",
+ [CC_ISOCH_BUFFER_OVERRUN] = "CC_ISOCH_BUFFER_OVERRUN",
+ [CC_EVENT_LOST_ERROR] = "CC_EVENT_LOST_ERROR",
+ [CC_UNDEFINED_ERROR] = "CC_UNDEFINED_ERROR",
+ [CC_INVALID_STREAM_ID_ERROR] = "CC_INVALID_STREAM_ID_ERROR",
+ [CC_SECONDARY_BANDWIDTH_ERROR] = "CC_SECONDARY_BANDWIDTH_ERROR",
+ [CC_SPLIT_TRANSACTION_ERROR] = "CC_SPLIT_TRANSACTION_ERROR",
+};
+
+static const char *ep_state_names[] = {
+ [EP_DISABLED] = "disabled",
+ [EP_RUNNING] = "running",
+ [EP_HALTED] = "halted",
+ [EP_STOPPED] = "stopped",
+ [EP_ERROR] = "error",
+};
+
+static const char *lookup_name(uint32_t index, const char **list, uint32_t llen)
+{
+ if (index >= llen || list[index] == NULL) {
+ return "???";
+ }
+ return list[index];
+}
+
+static const char *trb_name(XHCITRB *trb)
+{
+ return lookup_name(TRB_TYPE(*trb), TRBType_names,
+ ARRAY_SIZE(TRBType_names));
+}
+
+static const char *event_name(XHCIEvent *event)
+{
+ return lookup_name(event->ccode, TRBCCode_names,
+ ARRAY_SIZE(TRBCCode_names));
+}
+
+static const char *ep_state_name(uint32_t state)
+{
+ return lookup_name(state, ep_state_names,
+ ARRAY_SIZE(ep_state_names));
+}
+
+static bool xhci_get_flag(XHCIState *xhci, enum xhci_flags bit)
+{
+ return xhci->flags & (1 << bit);
+}
+
+static uint64_t xhci_mfindex_get(XHCIState *xhci)
+{
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ return (now - xhci->mfindex_start) / 125000;
+}
+
+static void xhci_mfwrap_update(XHCIState *xhci)
+{
+ const uint32_t bits = USBCMD_RS | USBCMD_EWE;
+ uint32_t mfindex, left;
+ int64_t now;
+
+ if ((xhci->usbcmd & bits) == bits) {
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ mfindex = ((now - xhci->mfindex_start) / 125000) & 0x3fff;
+ left = 0x4000 - mfindex;
+ timer_mod(xhci->mfwrap_timer, now + left * 125000);
+ } else {
+ timer_del(xhci->mfwrap_timer);
+ }
+}
+
+static void xhci_mfwrap_timer(void *opaque)
+{
+ XHCIState *xhci = opaque;
+ XHCIEvent wrap = { ER_MFINDEX_WRAP, CC_SUCCESS };
+
+ xhci_event(xhci, &wrap, 0);
+ xhci_mfwrap_update(xhci);
+}
+
+static inline dma_addr_t xhci_addr64(uint32_t low, uint32_t high)
+{
+ if (sizeof(dma_addr_t) == 4) {
+ return low;
+ } else {
+ return low | (((dma_addr_t)high << 16) << 16);
+ }
+}
+
+static inline dma_addr_t xhci_mask64(uint64_t addr)
+{
+ if (sizeof(dma_addr_t) == 4) {
+ return addr & 0xffffffff;
+ } else {
+ return addr;
+ }
+}
+
+static inline void xhci_dma_read_u32s(XHCIState *xhci, dma_addr_t addr,
+ uint32_t *buf, size_t len)
+{
+ int i;
+
+ assert((len % sizeof(uint32_t)) == 0);
+
+ pci_dma_read(PCI_DEVICE(xhci), addr, buf, len);
+
+ for (i = 0; i < (len / sizeof(uint32_t)); i++) {
+ buf[i] = le32_to_cpu(buf[i]);
+ }
+}
+
+static inline void xhci_dma_write_u32s(XHCIState *xhci, dma_addr_t addr,
+ uint32_t *buf, size_t len)
+{
+ int i;
+ uint32_t tmp[len / sizeof(uint32_t)];
+
+ assert((len % sizeof(uint32_t)) == 0);
+
+ for (i = 0; i < (len / sizeof(uint32_t)); i++) {
+ tmp[i] = cpu_to_le32(buf[i]);
+ }
+ pci_dma_write(PCI_DEVICE(xhci), addr, tmp, len);
+}
+
+static XHCIPort *xhci_lookup_port(XHCIState *xhci, struct USBPort *uport)
+{
+ int index;
+
+ if (!uport->dev) {
+ return NULL;
+ }
+ switch (uport->dev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) {
+ index = uport->index + xhci->numports_3;
+ } else {
+ index = uport->index;
+ }
+ break;
+ case USB_SPEED_SUPER:
+ if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) {
+ index = uport->index;
+ } else {
+ index = uport->index + xhci->numports_2;
+ }
+ break;
+ default:
+ return NULL;
+ }
+ return &xhci->ports[index];
+}
+
+static void xhci_intx_update(XHCIState *xhci)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(xhci);
+ int level = 0;
+
+ if (msix_enabled(pci_dev) ||
+ msi_enabled(pci_dev)) {
+ return;
+ }
+
+ if (xhci->intr[0].iman & IMAN_IP &&
+ xhci->intr[0].iman & IMAN_IE &&
+ xhci->usbcmd & USBCMD_INTE) {
+ level = 1;
+ }
+
+ trace_usb_xhci_irq_intx(level);
+ pci_set_irq(pci_dev, level);
+}
+
+static void xhci_msix_update(XHCIState *xhci, int v)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(xhci);
+ bool enabled;
+
+ if (!msix_enabled(pci_dev)) {
+ return;
+ }
+
+ enabled = xhci->intr[v].iman & IMAN_IE;
+ if (enabled == xhci->intr[v].msix_used) {
+ return;
+ }
+
+ if (enabled) {
+ trace_usb_xhci_irq_msix_use(v);
+ msix_vector_use(pci_dev, v);
+ xhci->intr[v].msix_used = true;
+ } else {
+ trace_usb_xhci_irq_msix_unuse(v);
+ msix_vector_unuse(pci_dev, v);
+ xhci->intr[v].msix_used = false;
+ }
+}
+
+static void xhci_intr_raise(XHCIState *xhci, int v)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(xhci);
+
+ xhci->intr[v].erdp_low |= ERDP_EHB;
+ xhci->intr[v].iman |= IMAN_IP;
+ xhci->usbsts |= USBSTS_EINT;
+
+ if (!(xhci->intr[v].iman & IMAN_IE)) {
+ return;
+ }
+
+ if (!(xhci->usbcmd & USBCMD_INTE)) {
+ return;
+ }
+
+ if (msix_enabled(pci_dev)) {
+ trace_usb_xhci_irq_msix(v);
+ msix_notify(pci_dev, v);
+ return;
+ }
+
+ if (msi_enabled(pci_dev)) {
+ trace_usb_xhci_irq_msi(v);
+ msi_notify(pci_dev, v);
+ return;
+ }
+
+ if (v == 0) {
+ trace_usb_xhci_irq_intx(1);
+ pci_irq_assert(pci_dev);
+ }
+}
+
+static inline int xhci_running(XHCIState *xhci)
+{
+ return !(xhci->usbsts & USBSTS_HCH) && !xhci->intr[0].er_full;
+}
+
+static void xhci_die(XHCIState *xhci)
+{
+ xhci->usbsts |= USBSTS_HCE;
+ DPRINTF("xhci: asserted controller error\n");
+}
+
+static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(xhci);
+ XHCIInterrupter *intr = &xhci->intr[v];
+ XHCITRB ev_trb;
+ dma_addr_t addr;
+
+ ev_trb.parameter = cpu_to_le64(event->ptr);
+ ev_trb.status = cpu_to_le32(event->length | (event->ccode << 24));
+ ev_trb.control = (event->slotid << 24) | (event->epid << 16) |
+ event->flags | (event->type << TRB_TYPE_SHIFT);
+ if (intr->er_pcs) {
+ ev_trb.control |= TRB_C;
+ }
+ ev_trb.control = cpu_to_le32(ev_trb.control);
+
+ trace_usb_xhci_queue_event(v, intr->er_ep_idx, trb_name(&ev_trb),
+ event_name(event), ev_trb.parameter,
+ ev_trb.status, ev_trb.control);
+
+ addr = intr->er_start + TRB_SIZE*intr->er_ep_idx;
+ pci_dma_write(pci_dev, addr, &ev_trb, TRB_SIZE);
+
+ intr->er_ep_idx++;
+ if (intr->er_ep_idx >= intr->er_size) {
+ intr->er_ep_idx = 0;
+ intr->er_pcs = !intr->er_pcs;
+ }
+}
+
+static void xhci_events_update(XHCIState *xhci, int v)
+{
+ XHCIInterrupter *intr = &xhci->intr[v];
+ dma_addr_t erdp;
+ unsigned int dp_idx;
+ bool do_irq = 0;
+
+ if (xhci->usbsts & USBSTS_HCH) {
+ return;
+ }
+
+ erdp = xhci_addr64(intr->erdp_low, intr->erdp_high);
+ if (erdp < intr->er_start ||
+ erdp >= (intr->er_start + TRB_SIZE*intr->er_size)) {
+ DPRINTF("xhci: ERDP out of bounds: "DMA_ADDR_FMT"\n", erdp);
+ DPRINTF("xhci: ER[%d] at "DMA_ADDR_FMT" len %d\n",
+ v, intr->er_start, intr->er_size);
+ xhci_die(xhci);
+ return;
+ }
+ dp_idx = (erdp - intr->er_start) / TRB_SIZE;
+ assert(dp_idx < intr->er_size);
+
+ /* NEC didn't read section 4.9.4 of the spec (v1.0 p139 top Note) and thus
+ * deadlocks when the ER is full. Hack it by holding off events until
+ * the driver decides to free at least half of the ring */
+ if (intr->er_full) {
+ int er_free = dp_idx - intr->er_ep_idx;
+ if (er_free <= 0) {
+ er_free += intr->er_size;
+ }
+ if (er_free < (intr->er_size/2)) {
+ DPRINTF("xhci_events_update(): event ring still "
+ "more than half full (hack)\n");
+ return;
+ }
+ }
+
+ while (intr->ev_buffer_put != intr->ev_buffer_get) {
+ assert(intr->er_full);
+ if (((intr->er_ep_idx+1) % intr->er_size) == dp_idx) {
+ DPRINTF("xhci_events_update(): event ring full again\n");
+#ifndef ER_FULL_HACK
+ XHCIEvent full = {ER_HOST_CONTROLLER, CC_EVENT_RING_FULL_ERROR};
+ xhci_write_event(xhci, &full, v);
+#endif
+ do_irq = 1;
+ break;
+ }
+ XHCIEvent *event = &intr->ev_buffer[intr->ev_buffer_get];
+ xhci_write_event(xhci, event, v);
+ intr->ev_buffer_get++;
+ do_irq = 1;
+ if (intr->ev_buffer_get == EV_QUEUE) {
+ intr->ev_buffer_get = 0;
+ }
+ }
+
+ if (do_irq) {
+ xhci_intr_raise(xhci, v);
+ }
+
+ if (intr->er_full && intr->ev_buffer_put == intr->ev_buffer_get) {
+ DPRINTF("xhci_events_update(): event ring no longer full\n");
+ intr->er_full = 0;
+ }
+}
+
+static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v)
+{
+ XHCIInterrupter *intr;
+ dma_addr_t erdp;
+ unsigned int dp_idx;
+
+ if (v >= xhci->numintrs) {
+ DPRINTF("intr nr out of range (%d >= %d)\n", v, xhci->numintrs);
+ return;
+ }
+ intr = &xhci->intr[v];
+
+ if (intr->er_full) {
+ DPRINTF("xhci_event(): ER full, queueing\n");
+ if (((intr->ev_buffer_put+1) % EV_QUEUE) == intr->ev_buffer_get) {
+ DPRINTF("xhci: event queue full, dropping event!\n");
+ return;
+ }
+ intr->ev_buffer[intr->ev_buffer_put++] = *event;
+ if (intr->ev_buffer_put == EV_QUEUE) {
+ intr->ev_buffer_put = 0;
+ }
+ return;
+ }
+
+ erdp = xhci_addr64(intr->erdp_low, intr->erdp_high);
+ if (erdp < intr->er_start ||
+ erdp >= (intr->er_start + TRB_SIZE*intr->er_size)) {
+ DPRINTF("xhci: ERDP out of bounds: "DMA_ADDR_FMT"\n", erdp);
+ DPRINTF("xhci: ER[%d] at "DMA_ADDR_FMT" len %d\n",
+ v, intr->er_start, intr->er_size);
+ xhci_die(xhci);
+ return;
+ }
+
+ dp_idx = (erdp - intr->er_start) / TRB_SIZE;
+ assert(dp_idx < intr->er_size);
+
+ if ((intr->er_ep_idx+1) % intr->er_size == dp_idx) {
+ DPRINTF("xhci_event(): ER full, queueing\n");
+#ifndef ER_FULL_HACK
+ XHCIEvent full = {ER_HOST_CONTROLLER, CC_EVENT_RING_FULL_ERROR};
+ xhci_write_event(xhci, &full);
+#endif
+ intr->er_full = 1;
+ if (((intr->ev_buffer_put+1) % EV_QUEUE) == intr->ev_buffer_get) {
+ DPRINTF("xhci: event queue full, dropping event!\n");
+ return;
+ }
+ intr->ev_buffer[intr->ev_buffer_put++] = *event;
+ if (intr->ev_buffer_put == EV_QUEUE) {
+ intr->ev_buffer_put = 0;
+ }
+ } else {
+ xhci_write_event(xhci, event, v);
+ }
+
+ xhci_intr_raise(xhci, v);
+}
+
+static void xhci_ring_init(XHCIState *xhci, XHCIRing *ring,
+ dma_addr_t base)
+{
+ ring->dequeue = base;
+ ring->ccs = 1;
+}
+
+static TRBType xhci_ring_fetch(XHCIState *xhci, XHCIRing *ring, XHCITRB *trb,
+ dma_addr_t *addr)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(xhci);
+
+ while (1) {
+ TRBType type;
+ pci_dma_read(pci_dev, ring->dequeue, trb, TRB_SIZE);
+ trb->addr = ring->dequeue;
+ trb->ccs = ring->ccs;
+ le64_to_cpus(&trb->parameter);
+ le32_to_cpus(&trb->status);
+ le32_to_cpus(&trb->control);
+
+ trace_usb_xhci_fetch_trb(ring->dequeue, trb_name(trb),
+ trb->parameter, trb->status, trb->control);
+
+ if ((trb->control & TRB_C) != ring->ccs) {
+ return 0;
+ }
+
+ type = TRB_TYPE(*trb);
+
+ if (type != TR_LINK) {
+ if (addr) {
+ *addr = ring->dequeue;
+ }
+ ring->dequeue += TRB_SIZE;
+ return type;
+ } else {
+ ring->dequeue = xhci_mask64(trb->parameter);
+ if (trb->control & TRB_LK_TC) {
+ ring->ccs = !ring->ccs;
+ }
+ }
+ }
+}
+
+static int xhci_ring_chain_length(XHCIState *xhci, const XHCIRing *ring)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(xhci);
+ XHCITRB trb;
+ int length = 0;
+ dma_addr_t dequeue = ring->dequeue;
+ bool ccs = ring->ccs;
+ /* hack to bundle together the two/three TDs that make a setup transfer */
+ bool control_td_set = 0;
+
+ while (1) {
+ TRBType type;
+ pci_dma_read(pci_dev, dequeue, &trb, TRB_SIZE);
+ le64_to_cpus(&trb.parameter);
+ le32_to_cpus(&trb.status);
+ le32_to_cpus(&trb.control);
+
+ if ((trb.control & TRB_C) != ccs) {
+ return -length;
+ }
+
+ type = TRB_TYPE(trb);
+
+ if (type == TR_LINK) {
+ dequeue = xhci_mask64(trb.parameter);
+ if (trb.control & TRB_LK_TC) {
+ ccs = !ccs;
+ }
+ continue;
+ }
+
+ length += 1;
+ dequeue += TRB_SIZE;
+
+ if (type == TR_SETUP) {
+ control_td_set = 1;
+ } else if (type == TR_STATUS) {
+ control_td_set = 0;
+ }
+
+ if (!control_td_set && !(trb.control & TRB_TR_CH)) {
+ return length;
+ }
+ }
+}
+
+static void xhci_er_reset(XHCIState *xhci, int v)
+{
+ XHCIInterrupter *intr = &xhci->intr[v];
+ XHCIEvRingSeg seg;
+
+ if (intr->erstsz == 0) {
+ /* disabled */
+ intr->er_start = 0;
+ intr->er_size = 0;
+ return;
+ }
+ /* cache the (sole) event ring segment location */
+ if (intr->erstsz != 1) {
+ DPRINTF("xhci: invalid value for ERSTSZ: %d\n", intr->erstsz);
+ xhci_die(xhci);
+ return;
+ }
+ dma_addr_t erstba = xhci_addr64(intr->erstba_low, intr->erstba_high);
+ pci_dma_read(PCI_DEVICE(xhci), erstba, &seg, sizeof(seg));
+ le32_to_cpus(&seg.addr_low);
+ le32_to_cpus(&seg.addr_high);
+ le32_to_cpus(&seg.size);
+ if (seg.size < 16 || seg.size > 4096) {
+ DPRINTF("xhci: invalid value for segment size: %d\n", seg.size);
+ xhci_die(xhci);
+ return;
+ }
+ intr->er_start = xhci_addr64(seg.addr_low, seg.addr_high);
+ intr->er_size = seg.size;
+
+ intr->er_ep_idx = 0;
+ intr->er_pcs = 1;
+ intr->er_full = 0;
+
+ DPRINTF("xhci: event ring[%d]:" DMA_ADDR_FMT " [%d]\n",
+ v, intr->er_start, intr->er_size);
+}
+
+static void xhci_run(XHCIState *xhci)
+{
+ trace_usb_xhci_run();
+ xhci->usbsts &= ~USBSTS_HCH;
+ xhci->mfindex_start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+static void xhci_stop(XHCIState *xhci)
+{
+ trace_usb_xhci_stop();
+ xhci->usbsts |= USBSTS_HCH;
+ xhci->crcr_low &= ~CRCR_CRR;
+}
+
+static XHCIStreamContext *xhci_alloc_stream_contexts(unsigned count,
+ dma_addr_t base)
+{
+ XHCIStreamContext *stctx;
+ unsigned int i;
+
+ stctx = g_new0(XHCIStreamContext, count);
+ for (i = 0; i < count; i++) {
+ stctx[i].pctx = base + i * 16;
+ stctx[i].sct = -1;
+ }
+ return stctx;
+}
+
+static void xhci_reset_streams(XHCIEPContext *epctx)
+{
+ unsigned int i;
+
+ for (i = 0; i < epctx->nr_pstreams; i++) {
+ epctx->pstreams[i].sct = -1;
+ }
+}
+
+static void xhci_alloc_streams(XHCIEPContext *epctx, dma_addr_t base)
+{
+ assert(epctx->pstreams == NULL);
+ epctx->nr_pstreams = 2 << epctx->max_pstreams;
+ epctx->pstreams = xhci_alloc_stream_contexts(epctx->nr_pstreams, base);
+}
+
+static void xhci_free_streams(XHCIEPContext *epctx)
+{
+ assert(epctx->pstreams != NULL);
+
+ g_free(epctx->pstreams);
+ epctx->pstreams = NULL;
+ epctx->nr_pstreams = 0;
+}
+
+static int xhci_epmask_to_eps_with_streams(XHCIState *xhci,
+ unsigned int slotid,
+ uint32_t epmask,
+ XHCIEPContext **epctxs,
+ USBEndpoint **eps)
+{
+ XHCISlot *slot;
+ XHCIEPContext *epctx;
+ USBEndpoint *ep;
+ int i, j;
+
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ slot = &xhci->slots[slotid - 1];
+
+ for (i = 2, j = 0; i <= 31; i++) {
+ if (!(epmask & (1u << i))) {
+ continue;
+ }
+
+ epctx = slot->eps[i - 1];
+ ep = xhci_epid_to_usbep(xhci, slotid, i);
+ if (!epctx || !epctx->nr_pstreams || !ep) {
+ continue;
+ }
+
+ if (epctxs) {
+ epctxs[j] = epctx;
+ }
+ eps[j++] = ep;
+ }
+ return j;
+}
+
+static void xhci_free_device_streams(XHCIState *xhci, unsigned int slotid,
+ uint32_t epmask)
+{
+ USBEndpoint *eps[30];
+ int nr_eps;
+
+ nr_eps = xhci_epmask_to_eps_with_streams(xhci, slotid, epmask, NULL, eps);
+ if (nr_eps) {
+ usb_device_free_streams(eps[0]->dev, eps, nr_eps);
+ }
+}
+
+static TRBCCode xhci_alloc_device_streams(XHCIState *xhci, unsigned int slotid,
+ uint32_t epmask)
+{
+ XHCIEPContext *epctxs[30];
+ USBEndpoint *eps[30];
+ int i, r, nr_eps, req_nr_streams, dev_max_streams;
+
+ nr_eps = xhci_epmask_to_eps_with_streams(xhci, slotid, epmask, epctxs,
+ eps);
+ if (nr_eps == 0) {
+ return CC_SUCCESS;
+ }
+
+ req_nr_streams = epctxs[0]->nr_pstreams;
+ dev_max_streams = eps[0]->max_streams;
+
+ for (i = 1; i < nr_eps; i++) {
+ /*
+ * HdG: I don't expect these to ever trigger, but if they do we need
+ * to come up with another solution, ie group identical endpoints
+ * together and make an usb_device_alloc_streams call per group.
+ */
+ if (epctxs[i]->nr_pstreams != req_nr_streams) {
+ FIXME("guest streams config not identical for all eps");
+ return CC_RESOURCE_ERROR;
+ }
+ if (eps[i]->max_streams != dev_max_streams) {
+ FIXME("device streams config not identical for all eps");
+ return CC_RESOURCE_ERROR;
+ }
+ }
+
+ /*
+ * max-streams in both the device descriptor and in the controller is a
+ * power of 2. But stream id 0 is reserved, so if a device can do up to 4
+ * streams the guest will ask for 5 rounded up to the next power of 2 which
+ * becomes 8. For emulated devices usb_device_alloc_streams is a nop.
+ *
+ * For redirected devices however this is an issue, as there we must ask
+ * the real xhci controller to alloc streams, and the host driver for the
+ * real xhci controller will likely disallow allocating more streams then
+ * the device can handle.
+ *
+ * So we limit the requested nr_streams to the maximum number the device
+ * can handle.
+ */
+ if (req_nr_streams > dev_max_streams) {
+ req_nr_streams = dev_max_streams;
+ }
+
+ r = usb_device_alloc_streams(eps[0]->dev, eps, nr_eps, req_nr_streams);
+ if (r != 0) {
+ DPRINTF("xhci: alloc streams failed\n");
+ return CC_RESOURCE_ERROR;
+ }
+
+ return CC_SUCCESS;
+}
+
+static XHCIStreamContext *xhci_find_stream(XHCIEPContext *epctx,
+ unsigned int streamid,
+ uint32_t *cc_error)
+{
+ XHCIStreamContext *sctx;
+ dma_addr_t base;
+ uint32_t ctx[2], sct;
+
+ assert(streamid != 0);
+ if (epctx->lsa) {
+ if (streamid >= epctx->nr_pstreams) {
+ *cc_error = CC_INVALID_STREAM_ID_ERROR;
+ return NULL;
+ }
+ sctx = epctx->pstreams + streamid;
+ } else {
+ FIXME("secondary streams not implemented yet");
+ }
+
+ if (sctx->sct == -1) {
+ xhci_dma_read_u32s(epctx->xhci, sctx->pctx, ctx, sizeof(ctx));
+ sct = (ctx[0] >> 1) & 0x07;
+ if (epctx->lsa && sct != 1) {
+ *cc_error = CC_INVALID_STREAM_TYPE_ERROR;
+ return NULL;
+ }
+ sctx->sct = sct;
+ base = xhci_addr64(ctx[0] & ~0xf, ctx[1]);
+ xhci_ring_init(epctx->xhci, &sctx->ring, base);
+ }
+ return sctx;
+}
+
+static void xhci_set_ep_state(XHCIState *xhci, XHCIEPContext *epctx,
+ XHCIStreamContext *sctx, uint32_t state)
+{
+ XHCIRing *ring = NULL;
+ uint32_t ctx[5];
+ uint32_t ctx2[2];
+
+ xhci_dma_read_u32s(xhci, epctx->pctx, ctx, sizeof(ctx));
+ ctx[0] &= ~EP_STATE_MASK;
+ ctx[0] |= state;
+
+ /* update ring dequeue ptr */
+ if (epctx->nr_pstreams) {
+ if (sctx != NULL) {
+ ring = &sctx->ring;
+ xhci_dma_read_u32s(xhci, sctx->pctx, ctx2, sizeof(ctx2));
+ ctx2[0] &= 0xe;
+ ctx2[0] |= sctx->ring.dequeue | sctx->ring.ccs;
+ ctx2[1] = (sctx->ring.dequeue >> 16) >> 16;
+ xhci_dma_write_u32s(xhci, sctx->pctx, ctx2, sizeof(ctx2));
+ }
+ } else {
+ ring = &epctx->ring;
+ }
+ if (ring) {
+ ctx[2] = ring->dequeue | ring->ccs;
+ ctx[3] = (ring->dequeue >> 16) >> 16;
+
+ DPRINTF("xhci: set epctx: " DMA_ADDR_FMT " state=%d dequeue=%08x%08x\n",
+ epctx->pctx, state, ctx[3], ctx[2]);
+ }
+
+ xhci_dma_write_u32s(xhci, epctx->pctx, ctx, sizeof(ctx));
+ if (epctx->state != state) {
+ trace_usb_xhci_ep_state(epctx->slotid, epctx->epid,
+ ep_state_name(epctx->state),
+ ep_state_name(state));
+ }
+ epctx->state = state;
+}
+
+static void xhci_ep_kick_timer(void *opaque)
+{
+ XHCIEPContext *epctx = opaque;
+ xhci_kick_ep(epctx->xhci, epctx->slotid, epctx->epid, 0);
+}
+
+static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci,
+ unsigned int slotid,
+ unsigned int epid)
+{
+ XHCIEPContext *epctx;
+ int i;
+
+ epctx = g_new0(XHCIEPContext, 1);
+ epctx->xhci = xhci;
+ epctx->slotid = slotid;
+ epctx->epid = epid;
+
+ for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) {
+ epctx->transfers[i].xhci = xhci;
+ epctx->transfers[i].slotid = slotid;
+ epctx->transfers[i].epid = epid;
+ usb_packet_init(&epctx->transfers[i].packet);
+ }
+ epctx->kick_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_ep_kick_timer, epctx);
+
+ return epctx;
+}
+
+static void xhci_init_epctx(XHCIEPContext *epctx,
+ dma_addr_t pctx, uint32_t *ctx)
+{
+ dma_addr_t dequeue;
+
+ dequeue = xhci_addr64(ctx[2] & ~0xf, ctx[3]);
+
+ epctx->type = (ctx[1] >> EP_TYPE_SHIFT) & EP_TYPE_MASK;
+ epctx->pctx = pctx;
+ epctx->max_psize = ctx[1]>>16;
+ epctx->max_psize *= 1+((ctx[1]>>8)&0xff);
+ epctx->max_pstreams = (ctx[0] >> 10) & epctx->xhci->max_pstreams_mask;
+ epctx->lsa = (ctx[0] >> 15) & 1;
+ if (epctx->max_pstreams) {
+ xhci_alloc_streams(epctx, dequeue);
+ } else {
+ xhci_ring_init(epctx->xhci, &epctx->ring, dequeue);
+ epctx->ring.ccs = ctx[2] & 1;
+ }
+
+ epctx->interval = 1 << ((ctx[0] >> 16) & 0xff);
+}
+
+static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid, dma_addr_t pctx,
+ uint32_t *ctx)
+{
+ XHCISlot *slot;
+ XHCIEPContext *epctx;
+
+ trace_usb_xhci_ep_enable(slotid, epid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+ assert(epid >= 1 && epid <= 31);
+
+ slot = &xhci->slots[slotid-1];
+ if (slot->eps[epid-1]) {
+ xhci_disable_ep(xhci, slotid, epid);
+ }
+
+ epctx = xhci_alloc_epctx(xhci, slotid, epid);
+ slot->eps[epid-1] = epctx;
+ xhci_init_epctx(epctx, pctx, ctx);
+
+ DPRINTF("xhci: endpoint %d.%d type is %d, max transaction (burst) "
+ "size is %d\n", epid/2, epid%2, epctx->type, epctx->max_psize);
+
+ epctx->mfindex_last = 0;
+
+ epctx->state = EP_RUNNING;
+ ctx[0] &= ~EP_STATE_MASK;
+ ctx[0] |= EP_RUNNING;
+
+ return CC_SUCCESS;
+}
+
+static int xhci_ep_nuke_one_xfer(XHCITransfer *t, TRBCCode report)
+{
+ int killed = 0;
+
+ if (report && (t->running_async || t->running_retry)) {
+ t->status = report;
+ xhci_xfer_report(t);
+ }
+
+ if (t->running_async) {
+ usb_cancel_packet(&t->packet);
+ t->running_async = 0;
+ killed = 1;
+ }
+ if (t->running_retry) {
+ XHCIEPContext *epctx = t->xhci->slots[t->slotid-1].eps[t->epid-1];
+ if (epctx) {
+ epctx->retry = NULL;
+ timer_del(epctx->kick_timer);
+ }
+ t->running_retry = 0;
+ killed = 1;
+ }
+ if (t->trbs) {
+ g_free(t->trbs);
+ }
+
+ t->trbs = NULL;
+ t->trb_count = t->trb_alloced = 0;
+
+ return killed;
+}
+
+static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid, TRBCCode report)
+{
+ XHCISlot *slot;
+ XHCIEPContext *epctx;
+ int i, xferi, killed = 0;
+ USBEndpoint *ep = NULL;
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+ assert(epid >= 1 && epid <= 31);
+
+ DPRINTF("xhci_ep_nuke_xfers(%d, %d)\n", slotid, epid);
+
+ slot = &xhci->slots[slotid-1];
+
+ if (!slot->eps[epid-1]) {
+ return 0;
+ }
+
+ epctx = slot->eps[epid-1];
+
+ xferi = epctx->next_xfer;
+ for (i = 0; i < TD_QUEUE; i++) {
+ killed += xhci_ep_nuke_one_xfer(&epctx->transfers[xferi], report);
+ if (killed) {
+ report = 0; /* Only report once */
+ }
+ epctx->transfers[xferi].packet.ep = NULL;
+ xferi = (xferi + 1) % TD_QUEUE;
+ }
+
+ ep = xhci_epid_to_usbep(xhci, slotid, epid);
+ if (ep) {
+ usb_device_ep_stopped(ep->dev, ep);
+ }
+ return killed;
+}
+
+static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid)
+{
+ XHCISlot *slot;
+ XHCIEPContext *epctx;
+ int i;
+
+ trace_usb_xhci_ep_disable(slotid, epid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+ assert(epid >= 1 && epid <= 31);
+
+ slot = &xhci->slots[slotid-1];
+
+ if (!slot->eps[epid-1]) {
+ DPRINTF("xhci: slot %d ep %d already disabled\n", slotid, epid);
+ return CC_SUCCESS;
+ }
+
+ xhci_ep_nuke_xfers(xhci, slotid, epid, 0);
+
+ epctx = slot->eps[epid-1];
+
+ if (epctx->nr_pstreams) {
+ xhci_free_streams(epctx);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) {
+ usb_packet_cleanup(&epctx->transfers[i].packet);
+ }
+
+ xhci_set_ep_state(xhci, epctx, NULL, EP_DISABLED);
+
+ timer_free(epctx->kick_timer);
+ g_free(epctx);
+ slot->eps[epid-1] = NULL;
+
+ return CC_SUCCESS;
+}
+
+static TRBCCode xhci_stop_ep(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid)
+{
+ XHCISlot *slot;
+ XHCIEPContext *epctx;
+
+ trace_usb_xhci_ep_stop(slotid, epid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ if (epid < 1 || epid > 31) {
+ DPRINTF("xhci: bad ep %d\n", epid);
+ return CC_TRB_ERROR;
+ }
+
+ slot = &xhci->slots[slotid-1];
+
+ if (!slot->eps[epid-1]) {
+ DPRINTF("xhci: slot %d ep %d not enabled\n", slotid, epid);
+ return CC_EP_NOT_ENABLED_ERROR;
+ }
+
+ if (xhci_ep_nuke_xfers(xhci, slotid, epid, CC_STOPPED) > 0) {
+ DPRINTF("xhci: FIXME: endpoint stopped w/ xfers running, "
+ "data might be lost\n");
+ }
+
+ epctx = slot->eps[epid-1];
+
+ xhci_set_ep_state(xhci, epctx, NULL, EP_STOPPED);
+
+ if (epctx->nr_pstreams) {
+ xhci_reset_streams(epctx);
+ }
+
+ return CC_SUCCESS;
+}
+
+static TRBCCode xhci_reset_ep(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid)
+{
+ XHCISlot *slot;
+ XHCIEPContext *epctx;
+
+ trace_usb_xhci_ep_reset(slotid, epid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ if (epid < 1 || epid > 31) {
+ DPRINTF("xhci: bad ep %d\n", epid);
+ return CC_TRB_ERROR;
+ }
+
+ slot = &xhci->slots[slotid-1];
+
+ if (!slot->eps[epid-1]) {
+ DPRINTF("xhci: slot %d ep %d not enabled\n", slotid, epid);
+ return CC_EP_NOT_ENABLED_ERROR;
+ }
+
+ epctx = slot->eps[epid-1];
+
+ if (epctx->state != EP_HALTED) {
+ DPRINTF("xhci: reset EP while EP %d not halted (%d)\n",
+ epid, epctx->state);
+ return CC_CONTEXT_STATE_ERROR;
+ }
+
+ if (xhci_ep_nuke_xfers(xhci, slotid, epid, 0) > 0) {
+ DPRINTF("xhci: FIXME: endpoint reset w/ xfers running, "
+ "data might be lost\n");
+ }
+
+ if (!xhci->slots[slotid-1].uport ||
+ !xhci->slots[slotid-1].uport->dev ||
+ !xhci->slots[slotid-1].uport->dev->attached) {
+ return CC_USB_TRANSACTION_ERROR;
+ }
+
+ xhci_set_ep_state(xhci, epctx, NULL, EP_STOPPED);
+
+ if (epctx->nr_pstreams) {
+ xhci_reset_streams(epctx);
+ }
+
+ return CC_SUCCESS;
+}
+
+static TRBCCode xhci_set_ep_dequeue(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid, unsigned int streamid,
+ uint64_t pdequeue)
+{
+ XHCISlot *slot;
+ XHCIEPContext *epctx;
+ XHCIStreamContext *sctx;
+ dma_addr_t dequeue;
+
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ if (epid < 1 || epid > 31) {
+ DPRINTF("xhci: bad ep %d\n", epid);
+ return CC_TRB_ERROR;
+ }
+
+ trace_usb_xhci_ep_set_dequeue(slotid, epid, streamid, pdequeue);
+ dequeue = xhci_mask64(pdequeue);
+
+ slot = &xhci->slots[slotid-1];
+
+ if (!slot->eps[epid-1]) {
+ DPRINTF("xhci: slot %d ep %d not enabled\n", slotid, epid);
+ return CC_EP_NOT_ENABLED_ERROR;
+ }
+
+ epctx = slot->eps[epid-1];
+
+ if (epctx->state != EP_STOPPED) {
+ DPRINTF("xhci: set EP dequeue pointer while EP %d not stopped\n", epid);
+ return CC_CONTEXT_STATE_ERROR;
+ }
+
+ if (epctx->nr_pstreams) {
+ uint32_t err;
+ sctx = xhci_find_stream(epctx, streamid, &err);
+ if (sctx == NULL) {
+ return err;
+ }
+ xhci_ring_init(xhci, &sctx->ring, dequeue & ~0xf);
+ sctx->ring.ccs = dequeue & 1;
+ } else {
+ sctx = NULL;
+ xhci_ring_init(xhci, &epctx->ring, dequeue & ~0xF);
+ epctx->ring.ccs = dequeue & 1;
+ }
+
+ xhci_set_ep_state(xhci, epctx, sctx, EP_STOPPED);
+
+ return CC_SUCCESS;
+}
+
+static int xhci_xfer_create_sgl(XHCITransfer *xfer, int in_xfer)
+{
+ XHCIState *xhci = xfer->xhci;
+ int i;
+
+ xfer->int_req = false;
+ pci_dma_sglist_init(&xfer->sgl, PCI_DEVICE(xhci), xfer->trb_count);
+ for (i = 0; i < xfer->trb_count; i++) {
+ XHCITRB *trb = &xfer->trbs[i];
+ dma_addr_t addr;
+ unsigned int chunk = 0;
+
+ if (trb->control & TRB_TR_IOC) {
+ xfer->int_req = true;
+ }
+
+ switch (TRB_TYPE(*trb)) {
+ case TR_DATA:
+ if ((!(trb->control & TRB_TR_DIR)) != (!in_xfer)) {
+ DPRINTF("xhci: data direction mismatch for TR_DATA\n");
+ goto err;
+ }
+ /* fallthrough */
+ case TR_NORMAL:
+ case TR_ISOCH:
+ addr = xhci_mask64(trb->parameter);
+ chunk = trb->status & 0x1ffff;
+ if (trb->control & TRB_TR_IDT) {
+ if (chunk > 8 || in_xfer) {
+ DPRINTF("xhci: invalid immediate data TRB\n");
+ goto err;
+ }
+ qemu_sglist_add(&xfer->sgl, trb->addr, chunk);
+ } else {
+ qemu_sglist_add(&xfer->sgl, addr, chunk);
+ }
+ break;
+ }
+ }
+
+ return 0;
+
+err:
+ qemu_sglist_destroy(&xfer->sgl);
+ xhci_die(xhci);
+ return -1;
+}
+
+static void xhci_xfer_unmap(XHCITransfer *xfer)
+{
+ usb_packet_unmap(&xfer->packet, &xfer->sgl);
+ qemu_sglist_destroy(&xfer->sgl);
+}
+
+static void xhci_xfer_report(XHCITransfer *xfer)
+{
+ uint32_t edtla = 0;
+ unsigned int left;
+ bool reported = 0;
+ bool shortpkt = 0;
+ XHCIEvent event = {ER_TRANSFER, CC_SUCCESS};
+ XHCIState *xhci = xfer->xhci;
+ int i;
+
+ left = xfer->packet.actual_length;
+
+ for (i = 0; i < xfer->trb_count; i++) {
+ XHCITRB *trb = &xfer->trbs[i];
+ unsigned int chunk = 0;
+
+ switch (TRB_TYPE(*trb)) {
+ case TR_DATA:
+ case TR_NORMAL:
+ case TR_ISOCH:
+ chunk = trb->status & 0x1ffff;
+ if (chunk > left) {
+ chunk = left;
+ if (xfer->status == CC_SUCCESS) {
+ shortpkt = 1;
+ }
+ }
+ left -= chunk;
+ edtla += chunk;
+ break;
+ case TR_STATUS:
+ reported = 0;
+ shortpkt = 0;
+ break;
+ }
+
+ if (!reported && ((trb->control & TRB_TR_IOC) ||
+ (shortpkt && (trb->control & TRB_TR_ISP)) ||
+ (xfer->status != CC_SUCCESS && left == 0))) {
+ event.slotid = xfer->slotid;
+ event.epid = xfer->epid;
+ event.length = (trb->status & 0x1ffff) - chunk;
+ event.flags = 0;
+ event.ptr = trb->addr;
+ if (xfer->status == CC_SUCCESS) {
+ event.ccode = shortpkt ? CC_SHORT_PACKET : CC_SUCCESS;
+ } else {
+ event.ccode = xfer->status;
+ }
+ if (TRB_TYPE(*trb) == TR_EVDATA) {
+ event.ptr = trb->parameter;
+ event.flags |= TRB_EV_ED;
+ event.length = edtla & 0xffffff;
+ DPRINTF("xhci_xfer_data: EDTLA=%d\n", event.length);
+ edtla = 0;
+ }
+ xhci_event(xhci, &event, TRB_INTR(*trb));
+ reported = 1;
+ if (xfer->status != CC_SUCCESS) {
+ return;
+ }
+ }
+
+ switch (TRB_TYPE(*trb)) {
+ case TR_SETUP:
+ reported = 0;
+ shortpkt = 0;
+ break;
+ }
+
+ }
+}
+
+static void xhci_stall_ep(XHCITransfer *xfer)
+{
+ XHCIState *xhci = xfer->xhci;
+ XHCISlot *slot = &xhci->slots[xfer->slotid-1];
+ XHCIEPContext *epctx = slot->eps[xfer->epid-1];
+ uint32_t err;
+ XHCIStreamContext *sctx;
+
+ if (epctx->nr_pstreams) {
+ sctx = xhci_find_stream(epctx, xfer->streamid, &err);
+ if (sctx == NULL) {
+ return;
+ }
+ sctx->ring.dequeue = xfer->trbs[0].addr;
+ sctx->ring.ccs = xfer->trbs[0].ccs;
+ xhci_set_ep_state(xhci, epctx, sctx, EP_HALTED);
+ } else {
+ epctx->ring.dequeue = xfer->trbs[0].addr;
+ epctx->ring.ccs = xfer->trbs[0].ccs;
+ xhci_set_ep_state(xhci, epctx, NULL, EP_HALTED);
+ }
+}
+
+static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer,
+ XHCIEPContext *epctx);
+
+static int xhci_setup_packet(XHCITransfer *xfer)
+{
+ XHCIState *xhci = xfer->xhci;
+ USBEndpoint *ep;
+ int dir;
+
+ dir = xfer->in_xfer ? USB_TOKEN_IN : USB_TOKEN_OUT;
+
+ if (xfer->packet.ep) {
+ ep = xfer->packet.ep;
+ } else {
+ ep = xhci_epid_to_usbep(xhci, xfer->slotid, xfer->epid);
+ if (!ep) {
+ DPRINTF("xhci: slot %d has no device\n",
+ xfer->slotid);
+ return -1;
+ }
+ }
+
+ xhci_xfer_create_sgl(xfer, dir == USB_TOKEN_IN); /* Also sets int_req */
+ usb_packet_setup(&xfer->packet, dir, ep, xfer->streamid,
+ xfer->trbs[0].addr, false, xfer->int_req);
+ usb_packet_map(&xfer->packet, &xfer->sgl);
+ DPRINTF("xhci: setup packet pid 0x%x addr %d ep %d\n",
+ xfer->packet.pid, ep->dev->addr, ep->nr);
+ return 0;
+}
+
+static int xhci_complete_packet(XHCITransfer *xfer)
+{
+ if (xfer->packet.status == USB_RET_ASYNC) {
+ trace_usb_xhci_xfer_async(xfer);
+ xfer->running_async = 1;
+ xfer->running_retry = 0;
+ xfer->complete = 0;
+ return 0;
+ } else if (xfer->packet.status == USB_RET_NAK) {
+ trace_usb_xhci_xfer_nak(xfer);
+ xfer->running_async = 0;
+ xfer->running_retry = 1;
+ xfer->complete = 0;
+ return 0;
+ } else {
+ xfer->running_async = 0;
+ xfer->running_retry = 0;
+ xfer->complete = 1;
+ xhci_xfer_unmap(xfer);
+ }
+
+ if (xfer->packet.status == USB_RET_SUCCESS) {
+ trace_usb_xhci_xfer_success(xfer, xfer->packet.actual_length);
+ xfer->status = CC_SUCCESS;
+ xhci_xfer_report(xfer);
+ return 0;
+ }
+
+ /* error */
+ trace_usb_xhci_xfer_error(xfer, xfer->packet.status);
+ switch (xfer->packet.status) {
+ case USB_RET_NODEV:
+ case USB_RET_IOERROR:
+ xfer->status = CC_USB_TRANSACTION_ERROR;
+ xhci_xfer_report(xfer);
+ xhci_stall_ep(xfer);
+ break;
+ case USB_RET_STALL:
+ xfer->status = CC_STALL_ERROR;
+ xhci_xfer_report(xfer);
+ xhci_stall_ep(xfer);
+ break;
+ case USB_RET_BABBLE:
+ xfer->status = CC_BABBLE_DETECTED;
+ xhci_xfer_report(xfer);
+ xhci_stall_ep(xfer);
+ break;
+ default:
+ DPRINTF("%s: FIXME: status = %d\n", __func__,
+ xfer->packet.status);
+ FIXME("unhandled USB_RET_*");
+ }
+ return 0;
+}
+
+static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer)
+{
+ XHCITRB *trb_setup, *trb_status;
+ uint8_t bmRequestType;
+
+ trb_setup = &xfer->trbs[0];
+ trb_status = &xfer->trbs[xfer->trb_count-1];
+
+ trace_usb_xhci_xfer_start(xfer, xfer->slotid, xfer->epid, xfer->streamid);
+
+ /* at most one Event Data TRB allowed after STATUS */
+ if (TRB_TYPE(*trb_status) == TR_EVDATA && xfer->trb_count > 2) {
+ trb_status--;
+ }
+
+ /* do some sanity checks */
+ if (TRB_TYPE(*trb_setup) != TR_SETUP) {
+ DPRINTF("xhci: ep0 first TD not SETUP: %d\n",
+ TRB_TYPE(*trb_setup));
+ return -1;
+ }
+ if (TRB_TYPE(*trb_status) != TR_STATUS) {
+ DPRINTF("xhci: ep0 last TD not STATUS: %d\n",
+ TRB_TYPE(*trb_status));
+ return -1;
+ }
+ if (!(trb_setup->control & TRB_TR_IDT)) {
+ DPRINTF("xhci: Setup TRB doesn't have IDT set\n");
+ return -1;
+ }
+ if ((trb_setup->status & 0x1ffff) != 8) {
+ DPRINTF("xhci: Setup TRB has bad length (%d)\n",
+ (trb_setup->status & 0x1ffff));
+ return -1;
+ }
+
+ bmRequestType = trb_setup->parameter;
+
+ xfer->in_xfer = bmRequestType & USB_DIR_IN;
+ xfer->iso_xfer = false;
+ xfer->timed_xfer = false;
+
+ if (xhci_setup_packet(xfer) < 0) {
+ return -1;
+ }
+ xfer->packet.parameter = trb_setup->parameter;
+
+ usb_handle_packet(xfer->packet.ep->dev, &xfer->packet);
+
+ xhci_complete_packet(xfer);
+ if (!xfer->running_async && !xfer->running_retry) {
+ xhci_kick_ep(xhci, xfer->slotid, xfer->epid, 0);
+ }
+ return 0;
+}
+
+static void xhci_calc_intr_kick(XHCIState *xhci, XHCITransfer *xfer,
+ XHCIEPContext *epctx, uint64_t mfindex)
+{
+ uint64_t asap = ((mfindex + epctx->interval - 1) &
+ ~(epctx->interval-1));
+ uint64_t kick = epctx->mfindex_last + epctx->interval;
+
+ assert(epctx->interval != 0);
+ xfer->mfindex_kick = MAX(asap, kick);
+}
+
+static void xhci_calc_iso_kick(XHCIState *xhci, XHCITransfer *xfer,
+ XHCIEPContext *epctx, uint64_t mfindex)
+{
+ if (xfer->trbs[0].control & TRB_TR_SIA) {
+ uint64_t asap = ((mfindex + epctx->interval - 1) &
+ ~(epctx->interval-1));
+ if (asap >= epctx->mfindex_last &&
+ asap <= epctx->mfindex_last + epctx->interval * 4) {
+ xfer->mfindex_kick = epctx->mfindex_last + epctx->interval;
+ } else {
+ xfer->mfindex_kick = asap;
+ }
+ } else {
+ xfer->mfindex_kick = ((xfer->trbs[0].control >> TRB_TR_FRAMEID_SHIFT)
+ & TRB_TR_FRAMEID_MASK) << 3;
+ xfer->mfindex_kick |= mfindex & ~0x3fff;
+ if (xfer->mfindex_kick + 0x100 < mfindex) {
+ xfer->mfindex_kick += 0x4000;
+ }
+ }
+}
+
+static void xhci_check_intr_iso_kick(XHCIState *xhci, XHCITransfer *xfer,
+ XHCIEPContext *epctx, uint64_t mfindex)
+{
+ if (xfer->mfindex_kick > mfindex) {
+ timer_mod(epctx->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (xfer->mfindex_kick - mfindex) * 125000);
+ xfer->running_retry = 1;
+ } else {
+ epctx->mfindex_last = xfer->mfindex_kick;
+ timer_del(epctx->kick_timer);
+ xfer->running_retry = 0;
+ }
+}
+
+
+static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx)
+{
+ uint64_t mfindex;
+
+ DPRINTF("xhci_submit(slotid=%d,epid=%d)\n", xfer->slotid, xfer->epid);
+
+ xfer->in_xfer = epctx->type>>2;
+
+ switch(epctx->type) {
+ case ET_INTR_OUT:
+ case ET_INTR_IN:
+ xfer->pkts = 0;
+ xfer->iso_xfer = false;
+ xfer->timed_xfer = true;
+ mfindex = xhci_mfindex_get(xhci);
+ xhci_calc_intr_kick(xhci, xfer, epctx, mfindex);
+ xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
+ if (xfer->running_retry) {
+ return -1;
+ }
+ break;
+ case ET_BULK_OUT:
+ case ET_BULK_IN:
+ xfer->pkts = 0;
+ xfer->iso_xfer = false;
+ xfer->timed_xfer = false;
+ break;
+ case ET_ISO_OUT:
+ case ET_ISO_IN:
+ xfer->pkts = 1;
+ xfer->iso_xfer = true;
+ xfer->timed_xfer = true;
+ mfindex = xhci_mfindex_get(xhci);
+ xhci_calc_iso_kick(xhci, xfer, epctx, mfindex);
+ xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
+ if (xfer->running_retry) {
+ return -1;
+ }
+ break;
+ default:
+ trace_usb_xhci_unimplemented("endpoint type", epctx->type);
+ return -1;
+ }
+
+ if (xhci_setup_packet(xfer) < 0) {
+ return -1;
+ }
+ usb_handle_packet(xfer->packet.ep->dev, &xfer->packet);
+
+ xhci_complete_packet(xfer);
+ if (!xfer->running_async && !xfer->running_retry) {
+ xhci_kick_ep(xhci, xfer->slotid, xfer->epid, xfer->streamid);
+ }
+ return 0;
+}
+
+static int xhci_fire_transfer(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx)
+{
+ trace_usb_xhci_xfer_start(xfer, xfer->slotid, xfer->epid, xfer->streamid);
+ return xhci_submit(xhci, xfer, epctx);
+}
+
+static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
+ unsigned int epid, unsigned int streamid)
+{
+ XHCIStreamContext *stctx;
+ XHCIEPContext *epctx;
+ XHCIRing *ring;
+ USBEndpoint *ep = NULL;
+ uint64_t mfindex;
+ int length;
+ int i;
+
+ trace_usb_xhci_ep_kick(slotid, epid, streamid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+ assert(epid >= 1 && epid <= 31);
+
+ if (!xhci->slots[slotid-1].enabled) {
+ DPRINTF("xhci: xhci_kick_ep for disabled slot %d\n", slotid);
+ return;
+ }
+ epctx = xhci->slots[slotid-1].eps[epid-1];
+ if (!epctx) {
+ DPRINTF("xhci: xhci_kick_ep for disabled endpoint %d,%d\n",
+ epid, slotid);
+ return;
+ }
+
+ /* If the device has been detached, but the guest has not noticed this
+ yet the 2 above checks will succeed, but we must NOT continue */
+ if (!xhci->slots[slotid - 1].uport ||
+ !xhci->slots[slotid - 1].uport->dev ||
+ !xhci->slots[slotid - 1].uport->dev->attached) {
+ return;
+ }
+
+ if (epctx->retry) {
+ XHCITransfer *xfer = epctx->retry;
+
+ trace_usb_xhci_xfer_retry(xfer);
+ assert(xfer->running_retry);
+ if (xfer->timed_xfer) {
+ /* time to kick the transfer? */
+ mfindex = xhci_mfindex_get(xhci);
+ xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
+ if (xfer->running_retry) {
+ return;
+ }
+ xfer->timed_xfer = 0;
+ xfer->running_retry = 1;
+ }
+ if (xfer->iso_xfer) {
+ /* retry iso transfer */
+ if (xhci_setup_packet(xfer) < 0) {
+ return;
+ }
+ usb_handle_packet(xfer->packet.ep->dev, &xfer->packet);
+ assert(xfer->packet.status != USB_RET_NAK);
+ xhci_complete_packet(xfer);
+ } else {
+ /* retry nak'ed transfer */
+ if (xhci_setup_packet(xfer) < 0) {
+ return;
+ }
+ usb_handle_packet(xfer->packet.ep->dev, &xfer->packet);
+ if (xfer->packet.status == USB_RET_NAK) {
+ return;
+ }
+ xhci_complete_packet(xfer);
+ }
+ assert(!xfer->running_retry);
+ epctx->retry = NULL;
+ }
+
+ if (epctx->state == EP_HALTED) {
+ DPRINTF("xhci: ep halted, not running schedule\n");
+ return;
+ }
+
+
+ if (epctx->nr_pstreams) {
+ uint32_t err;
+ stctx = xhci_find_stream(epctx, streamid, &err);
+ if (stctx == NULL) {
+ return;
+ }
+ ring = &stctx->ring;
+ xhci_set_ep_state(xhci, epctx, stctx, EP_RUNNING);
+ } else {
+ ring = &epctx->ring;
+ streamid = 0;
+ xhci_set_ep_state(xhci, epctx, NULL, EP_RUNNING);
+ }
+ assert(ring->dequeue != 0);
+
+ while (1) {
+ XHCITransfer *xfer = &epctx->transfers[epctx->next_xfer];
+ if (xfer->running_async || xfer->running_retry) {
+ break;
+ }
+ length = xhci_ring_chain_length(xhci, ring);
+ if (length < 0) {
+ break;
+ } else if (length == 0) {
+ break;
+ }
+ if (xfer->trbs && xfer->trb_alloced < length) {
+ xfer->trb_count = 0;
+ xfer->trb_alloced = 0;
+ g_free(xfer->trbs);
+ xfer->trbs = NULL;
+ }
+ if (!xfer->trbs) {
+ xfer->trbs = g_malloc(sizeof(XHCITRB) * length);
+ xfer->trb_alloced = length;
+ }
+ xfer->trb_count = length;
+
+ for (i = 0; i < length; i++) {
+ assert(xhci_ring_fetch(xhci, ring, &xfer->trbs[i], NULL));
+ }
+ xfer->streamid = streamid;
+
+ if (epid == 1) {
+ if (xhci_fire_ctl_transfer(xhci, xfer) >= 0) {
+ epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE;
+ } else {
+ DPRINTF("xhci: error firing CTL transfer\n");
+ }
+ } else {
+ if (xhci_fire_transfer(xhci, xfer, epctx) >= 0) {
+ epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE;
+ } else {
+ if (!xfer->timed_xfer) {
+ DPRINTF("xhci: error firing data transfer\n");
+ }
+ }
+ }
+
+ if (epctx->state == EP_HALTED) {
+ break;
+ }
+ if (xfer->running_retry) {
+ DPRINTF("xhci: xfer nacked, stopping schedule\n");
+ epctx->retry = xfer;
+ break;
+ }
+ }
+
+ ep = xhci_epid_to_usbep(xhci, slotid, epid);
+ if (ep) {
+ usb_device_flush_ep_queue(ep->dev, ep);
+ }
+}
+
+static TRBCCode xhci_enable_slot(XHCIState *xhci, unsigned int slotid)
+{
+ trace_usb_xhci_slot_enable(slotid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+ xhci->slots[slotid-1].enabled = 1;
+ xhci->slots[slotid-1].uport = NULL;
+ memset(xhci->slots[slotid-1].eps, 0, sizeof(XHCIEPContext*)*31);
+
+ return CC_SUCCESS;
+}
+
+static TRBCCode xhci_disable_slot(XHCIState *xhci, unsigned int slotid)
+{
+ int i;
+
+ trace_usb_xhci_slot_disable(slotid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ for (i = 1; i <= 31; i++) {
+ if (xhci->slots[slotid-1].eps[i-1]) {
+ xhci_disable_ep(xhci, slotid, i);
+ }
+ }
+
+ xhci->slots[slotid-1].enabled = 0;
+ xhci->slots[slotid-1].addressed = 0;
+ xhci->slots[slotid-1].uport = NULL;
+ return CC_SUCCESS;
+}
+
+static USBPort *xhci_lookup_uport(XHCIState *xhci, uint32_t *slot_ctx)
+{
+ USBPort *uport;
+ char path[32];
+ int i, pos, port;
+
+ port = (slot_ctx[1]>>16) & 0xFF;
+ if (port < 1 || port > xhci->numports) {
+ return NULL;
+ }
+ port = xhci->ports[port-1].uport->index+1;
+ pos = snprintf(path, sizeof(path), "%d", port);
+ for (i = 0; i < 5; i++) {
+ port = (slot_ctx[0] >> 4*i) & 0x0f;
+ if (!port) {
+ break;
+ }
+ pos += snprintf(path + pos, sizeof(path) - pos, ".%d", port);
+ }
+
+ QTAILQ_FOREACH(uport, &xhci->bus.used, next) {
+ if (strcmp(uport->path, path) == 0) {
+ return uport;
+ }
+ }
+ return NULL;
+}
+
+static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid,
+ uint64_t pictx, bool bsr)
+{
+ XHCISlot *slot;
+ USBPort *uport;
+ USBDevice *dev;
+ dma_addr_t ictx, octx, dcbaap;
+ uint64_t poctx;
+ uint32_t ictl_ctx[2];
+ uint32_t slot_ctx[4];
+ uint32_t ep0_ctx[5];
+ int i;
+ TRBCCode res;
+
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ dcbaap = xhci_addr64(xhci->dcbaap_low, xhci->dcbaap_high);
+ poctx = ldq_le_pci_dma(PCI_DEVICE(xhci), dcbaap + 8 * slotid);
+ ictx = xhci_mask64(pictx);
+ octx = xhci_mask64(poctx);
+
+ DPRINTF("xhci: input context at "DMA_ADDR_FMT"\n", ictx);
+ DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx);
+
+ xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx));
+
+ if (ictl_ctx[0] != 0x0 || ictl_ctx[1] != 0x3) {
+ DPRINTF("xhci: invalid input context control %08x %08x\n",
+ ictl_ctx[0], ictl_ctx[1]);
+ return CC_TRB_ERROR;
+ }
+
+ xhci_dma_read_u32s(xhci, ictx+32, slot_ctx, sizeof(slot_ctx));
+ xhci_dma_read_u32s(xhci, ictx+64, ep0_ctx, sizeof(ep0_ctx));
+
+ DPRINTF("xhci: input slot context: %08x %08x %08x %08x\n",
+ slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]);
+
+ DPRINTF("xhci: input ep0 context: %08x %08x %08x %08x %08x\n",
+ ep0_ctx[0], ep0_ctx[1], ep0_ctx[2], ep0_ctx[3], ep0_ctx[4]);
+
+ uport = xhci_lookup_uport(xhci, slot_ctx);
+ if (uport == NULL) {
+ DPRINTF("xhci: port not found\n");
+ return CC_TRB_ERROR;
+ }
+ trace_usb_xhci_slot_address(slotid, uport->path);
+
+ dev = uport->dev;
+ if (!dev || !dev->attached) {
+ DPRINTF("xhci: port %s not connected\n", uport->path);
+ return CC_USB_TRANSACTION_ERROR;
+ }
+
+ for (i = 0; i < xhci->numslots; i++) {
+ if (i == slotid-1) {
+ continue;
+ }
+ if (xhci->slots[i].uport == uport) {
+ DPRINTF("xhci: port %s already assigned to slot %d\n",
+ uport->path, i+1);
+ return CC_TRB_ERROR;
+ }
+ }
+
+ slot = &xhci->slots[slotid-1];
+ slot->uport = uport;
+ slot->ctx = octx;
+
+ if (bsr) {
+ slot_ctx[3] = SLOT_DEFAULT << SLOT_STATE_SHIFT;
+ } else {
+ USBPacket p;
+ uint8_t buf[1];
+
+ slot_ctx[3] = (SLOT_ADDRESSED << SLOT_STATE_SHIFT) | slotid;
+ usb_device_reset(dev);
+ memset(&p, 0, sizeof(p));
+ usb_packet_addbuf(&p, buf, sizeof(buf));
+ usb_packet_setup(&p, USB_TOKEN_OUT,
+ usb_ep_get(dev, USB_TOKEN_OUT, 0), 0,
+ 0, false, false);
+ usb_device_handle_control(dev, &p,
+ DeviceOutRequest | USB_REQ_SET_ADDRESS,
+ slotid, 0, 0, NULL);
+ assert(p.status != USB_RET_ASYNC);
+ }
+
+ res = xhci_enable_ep(xhci, slotid, 1, octx+32, ep0_ctx);
+
+ DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n",
+ slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]);
+ DPRINTF("xhci: output ep0 context: %08x %08x %08x %08x %08x\n",
+ ep0_ctx[0], ep0_ctx[1], ep0_ctx[2], ep0_ctx[3], ep0_ctx[4]);
+
+ xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+ xhci_dma_write_u32s(xhci, octx+32, ep0_ctx, sizeof(ep0_ctx));
+
+ xhci->slots[slotid-1].addressed = 1;
+ return res;
+}
+
+
+static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid,
+ uint64_t pictx, bool dc)
+{
+ dma_addr_t ictx, octx;
+ uint32_t ictl_ctx[2];
+ uint32_t slot_ctx[4];
+ uint32_t islot_ctx[4];
+ uint32_t ep_ctx[5];
+ int i;
+ TRBCCode res;
+
+ trace_usb_xhci_slot_configure(slotid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ ictx = xhci_mask64(pictx);
+ octx = xhci->slots[slotid-1].ctx;
+
+ DPRINTF("xhci: input context at "DMA_ADDR_FMT"\n", ictx);
+ DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx);
+
+ if (dc) {
+ for (i = 2; i <= 31; i++) {
+ if (xhci->slots[slotid-1].eps[i-1]) {
+ xhci_disable_ep(xhci, slotid, i);
+ }
+ }
+
+ xhci_dma_read_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+ slot_ctx[3] &= ~(SLOT_STATE_MASK << SLOT_STATE_SHIFT);
+ slot_ctx[3] |= SLOT_ADDRESSED << SLOT_STATE_SHIFT;
+ DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n",
+ slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]);
+ xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+
+ return CC_SUCCESS;
+ }
+
+ xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx));
+
+ if ((ictl_ctx[0] & 0x3) != 0x0 || (ictl_ctx[1] & 0x3) != 0x1) {
+ DPRINTF("xhci: invalid input context control %08x %08x\n",
+ ictl_ctx[0], ictl_ctx[1]);
+ return CC_TRB_ERROR;
+ }
+
+ xhci_dma_read_u32s(xhci, ictx+32, islot_ctx, sizeof(islot_ctx));
+ xhci_dma_read_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+
+ if (SLOT_STATE(slot_ctx[3]) < SLOT_ADDRESSED) {
+ DPRINTF("xhci: invalid slot state %08x\n", slot_ctx[3]);
+ return CC_CONTEXT_STATE_ERROR;
+ }
+
+ xhci_free_device_streams(xhci, slotid, ictl_ctx[0] | ictl_ctx[1]);
+
+ for (i = 2; i <= 31; i++) {
+ if (ictl_ctx[0] & (1<<i)) {
+ xhci_disable_ep(xhci, slotid, i);
+ }
+ if (ictl_ctx[1] & (1<<i)) {
+ xhci_dma_read_u32s(xhci, ictx+32+(32*i), ep_ctx, sizeof(ep_ctx));
+ DPRINTF("xhci: input ep%d.%d context: %08x %08x %08x %08x %08x\n",
+ i/2, i%2, ep_ctx[0], ep_ctx[1], ep_ctx[2],
+ ep_ctx[3], ep_ctx[4]);
+ xhci_disable_ep(xhci, slotid, i);
+ res = xhci_enable_ep(xhci, slotid, i, octx+(32*i), ep_ctx);
+ if (res != CC_SUCCESS) {
+ return res;
+ }
+ DPRINTF("xhci: output ep%d.%d context: %08x %08x %08x %08x %08x\n",
+ i/2, i%2, ep_ctx[0], ep_ctx[1], ep_ctx[2],
+ ep_ctx[3], ep_ctx[4]);
+ xhci_dma_write_u32s(xhci, octx+(32*i), ep_ctx, sizeof(ep_ctx));
+ }
+ }
+
+ res = xhci_alloc_device_streams(xhci, slotid, ictl_ctx[1]);
+ if (res != CC_SUCCESS) {
+ for (i = 2; i <= 31; i++) {
+ if (ictl_ctx[1] & (1u << i)) {
+ xhci_disable_ep(xhci, slotid, i);
+ }
+ }
+ return res;
+ }
+
+ slot_ctx[3] &= ~(SLOT_STATE_MASK << SLOT_STATE_SHIFT);
+ slot_ctx[3] |= SLOT_CONFIGURED << SLOT_STATE_SHIFT;
+ slot_ctx[0] &= ~(SLOT_CONTEXT_ENTRIES_MASK << SLOT_CONTEXT_ENTRIES_SHIFT);
+ slot_ctx[0] |= islot_ctx[0] & (SLOT_CONTEXT_ENTRIES_MASK <<
+ SLOT_CONTEXT_ENTRIES_SHIFT);
+ DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n",
+ slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]);
+
+ xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+
+ return CC_SUCCESS;
+}
+
+
+static TRBCCode xhci_evaluate_slot(XHCIState *xhci, unsigned int slotid,
+ uint64_t pictx)
+{
+ dma_addr_t ictx, octx;
+ uint32_t ictl_ctx[2];
+ uint32_t iep0_ctx[5];
+ uint32_t ep0_ctx[5];
+ uint32_t islot_ctx[4];
+ uint32_t slot_ctx[4];
+
+ trace_usb_xhci_slot_evaluate(slotid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ ictx = xhci_mask64(pictx);
+ octx = xhci->slots[slotid-1].ctx;
+
+ DPRINTF("xhci: input context at "DMA_ADDR_FMT"\n", ictx);
+ DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx);
+
+ xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx));
+
+ if (ictl_ctx[0] != 0x0 || ictl_ctx[1] & ~0x3) {
+ DPRINTF("xhci: invalid input context control %08x %08x\n",
+ ictl_ctx[0], ictl_ctx[1]);
+ return CC_TRB_ERROR;
+ }
+
+ if (ictl_ctx[1] & 0x1) {
+ xhci_dma_read_u32s(xhci, ictx+32, islot_ctx, sizeof(islot_ctx));
+
+ DPRINTF("xhci: input slot context: %08x %08x %08x %08x\n",
+ islot_ctx[0], islot_ctx[1], islot_ctx[2], islot_ctx[3]);
+
+ xhci_dma_read_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+
+ slot_ctx[1] &= ~0xFFFF; /* max exit latency */
+ slot_ctx[1] |= islot_ctx[1] & 0xFFFF;
+ slot_ctx[2] &= ~0xFF00000; /* interrupter target */
+ slot_ctx[2] |= islot_ctx[2] & 0xFF000000;
+
+ DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n",
+ slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]);
+
+ xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+ }
+
+ if (ictl_ctx[1] & 0x2) {
+ xhci_dma_read_u32s(xhci, ictx+64, iep0_ctx, sizeof(iep0_ctx));
+
+ DPRINTF("xhci: input ep0 context: %08x %08x %08x %08x %08x\n",
+ iep0_ctx[0], iep0_ctx[1], iep0_ctx[2],
+ iep0_ctx[3], iep0_ctx[4]);
+
+ xhci_dma_read_u32s(xhci, octx+32, ep0_ctx, sizeof(ep0_ctx));
+
+ ep0_ctx[1] &= ~0xFFFF0000; /* max packet size*/
+ ep0_ctx[1] |= iep0_ctx[1] & 0xFFFF0000;
+
+ DPRINTF("xhci: output ep0 context: %08x %08x %08x %08x %08x\n",
+ ep0_ctx[0], ep0_ctx[1], ep0_ctx[2], ep0_ctx[3], ep0_ctx[4]);
+
+ xhci_dma_write_u32s(xhci, octx+32, ep0_ctx, sizeof(ep0_ctx));
+ }
+
+ return CC_SUCCESS;
+}
+
+static TRBCCode xhci_reset_slot(XHCIState *xhci, unsigned int slotid)
+{
+ uint32_t slot_ctx[4];
+ dma_addr_t octx;
+ int i;
+
+ trace_usb_xhci_slot_reset(slotid);
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ octx = xhci->slots[slotid-1].ctx;
+
+ DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx);
+
+ for (i = 2; i <= 31; i++) {
+ if (xhci->slots[slotid-1].eps[i-1]) {
+ xhci_disable_ep(xhci, slotid, i);
+ }
+ }
+
+ xhci_dma_read_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+ slot_ctx[3] &= ~(SLOT_STATE_MASK << SLOT_STATE_SHIFT);
+ slot_ctx[3] |= SLOT_DEFAULT << SLOT_STATE_SHIFT;
+ DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n",
+ slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]);
+ xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx));
+
+ return CC_SUCCESS;
+}
+
+static unsigned int xhci_get_slot(XHCIState *xhci, XHCIEvent *event, XHCITRB *trb)
+{
+ unsigned int slotid;
+ slotid = (trb->control >> TRB_CR_SLOTID_SHIFT) & TRB_CR_SLOTID_MASK;
+ if (slotid < 1 || slotid > xhci->numslots) {
+ DPRINTF("xhci: bad slot id %d\n", slotid);
+ event->ccode = CC_TRB_ERROR;
+ return 0;
+ } else if (!xhci->slots[slotid-1].enabled) {
+ DPRINTF("xhci: slot id %d not enabled\n", slotid);
+ event->ccode = CC_SLOT_NOT_ENABLED_ERROR;
+ return 0;
+ }
+ return slotid;
+}
+
+/* cleanup slot state on usb device detach */
+static void xhci_detach_slot(XHCIState *xhci, USBPort *uport)
+{
+ int slot, ep;
+
+ for (slot = 0; slot < xhci->numslots; slot++) {
+ if (xhci->slots[slot].uport == uport) {
+ break;
+ }
+ }
+ if (slot == xhci->numslots) {
+ return;
+ }
+
+ for (ep = 0; ep < 31; ep++) {
+ if (xhci->slots[slot].eps[ep]) {
+ xhci_ep_nuke_xfers(xhci, slot + 1, ep + 1, 0);
+ }
+ }
+ xhci->slots[slot].uport = NULL;
+}
+
+static TRBCCode xhci_get_port_bandwidth(XHCIState *xhci, uint64_t pctx)
+{
+ dma_addr_t ctx;
+ uint8_t bw_ctx[xhci->numports+1];
+
+ DPRINTF("xhci_get_port_bandwidth()\n");
+
+ ctx = xhci_mask64(pctx);
+
+ DPRINTF("xhci: bandwidth context at "DMA_ADDR_FMT"\n", ctx);
+
+ /* TODO: actually implement real values here */
+ bw_ctx[0] = 0;
+ memset(&bw_ctx[1], 80, xhci->numports); /* 80% */
+ pci_dma_write(PCI_DEVICE(xhci), ctx, bw_ctx, sizeof(bw_ctx));
+
+ return CC_SUCCESS;
+}
+
+static uint32_t rotl(uint32_t v, unsigned count)
+{
+ count &= 31;
+ return (v << count) | (v >> (32 - count));
+}
+
+
+static uint32_t xhci_nec_challenge(uint32_t hi, uint32_t lo)
+{
+ uint32_t val;
+ val = rotl(lo - 0x49434878, 32 - ((hi>>8) & 0x1F));
+ val += rotl(lo + 0x49434878, hi & 0x1F);
+ val -= rotl(hi ^ 0x49434878, (lo >> 16) & 0x1F);
+ return ~val;
+}
+
+static void xhci_via_challenge(XHCIState *xhci, uint64_t addr)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(xhci);
+ uint32_t buf[8];
+ uint32_t obuf[8];
+ dma_addr_t paddr = xhci_mask64(addr);
+
+ pci_dma_read(pci_dev, paddr, &buf, 32);
+
+ memcpy(obuf, buf, sizeof(obuf));
+
+ if ((buf[0] & 0xff) == 2) {
+ obuf[0] = 0x49932000 + 0x54dc200 * buf[2] + 0x7429b578 * buf[3];
+ obuf[0] |= (buf[2] * buf[3]) & 0xff;
+ obuf[1] = 0x0132bb37 + 0xe89 * buf[2] + 0xf09 * buf[3];
+ obuf[2] = 0x0066c2e9 + 0x2091 * buf[2] + 0x19bd * buf[3];
+ obuf[3] = 0xd5281342 + 0x2cc9691 * buf[2] + 0x2367662 * buf[3];
+ obuf[4] = 0x0123c75c + 0x1595 * buf[2] + 0x19ec * buf[3];
+ obuf[5] = 0x00f695de + 0x26fd * buf[2] + 0x3e9 * buf[3];
+ obuf[6] = obuf[2] ^ obuf[3] ^ 0x29472956;
+ obuf[7] = obuf[2] ^ obuf[3] ^ 0x65866593;
+ }
+
+ pci_dma_write(pci_dev, paddr, &obuf, 32);
+}
+
+static void xhci_process_commands(XHCIState *xhci)
+{
+ XHCITRB trb;
+ TRBType type;
+ XHCIEvent event = {ER_COMMAND_COMPLETE, CC_SUCCESS};
+ dma_addr_t addr;
+ unsigned int i, slotid = 0;
+
+ DPRINTF("xhci_process_commands()\n");
+ if (!xhci_running(xhci)) {
+ DPRINTF("xhci_process_commands() called while xHC stopped or paused\n");
+ return;
+ }
+
+ xhci->crcr_low |= CRCR_CRR;
+
+ while ((type = xhci_ring_fetch(xhci, &xhci->cmd_ring, &trb, &addr))) {
+ event.ptr = addr;
+ switch (type) {
+ case CR_ENABLE_SLOT:
+ for (i = 0; i < xhci->numslots; i++) {
+ if (!xhci->slots[i].enabled) {
+ break;
+ }
+ }
+ if (i >= xhci->numslots) {
+ DPRINTF("xhci: no device slots available\n");
+ event.ccode = CC_NO_SLOTS_ERROR;
+ } else {
+ slotid = i+1;
+ event.ccode = xhci_enable_slot(xhci, slotid);
+ }
+ break;
+ case CR_DISABLE_SLOT:
+ slotid = xhci_get_slot(xhci, &event, &trb);
+ if (slotid) {
+ event.ccode = xhci_disable_slot(xhci, slotid);
+ }
+ break;
+ case CR_ADDRESS_DEVICE:
+ slotid = xhci_get_slot(xhci, &event, &trb);
+ if (slotid) {
+ event.ccode = xhci_address_slot(xhci, slotid, trb.parameter,
+ trb.control & TRB_CR_BSR);
+ }
+ break;
+ case CR_CONFIGURE_ENDPOINT:
+ slotid = xhci_get_slot(xhci, &event, &trb);
+ if (slotid) {
+ event.ccode = xhci_configure_slot(xhci, slotid, trb.parameter,
+ trb.control & TRB_CR_DC);
+ }
+ break;
+ case CR_EVALUATE_CONTEXT:
+ slotid = xhci_get_slot(xhci, &event, &trb);
+ if (slotid) {
+ event.ccode = xhci_evaluate_slot(xhci, slotid, trb.parameter);
+ }
+ break;
+ case CR_STOP_ENDPOINT:
+ slotid = xhci_get_slot(xhci, &event, &trb);
+ if (slotid) {
+ unsigned int epid = (trb.control >> TRB_CR_EPID_SHIFT)
+ & TRB_CR_EPID_MASK;
+ event.ccode = xhci_stop_ep(xhci, slotid, epid);
+ }
+ break;
+ case CR_RESET_ENDPOINT:
+ slotid = xhci_get_slot(xhci, &event, &trb);
+ if (slotid) {
+ unsigned int epid = (trb.control >> TRB_CR_EPID_SHIFT)
+ & TRB_CR_EPID_MASK;
+ event.ccode = xhci_reset_ep(xhci, slotid, epid);
+ }
+ break;
+ case CR_SET_TR_DEQUEUE:
+ slotid = xhci_get_slot(xhci, &event, &trb);
+ if (slotid) {
+ unsigned int epid = (trb.control >> TRB_CR_EPID_SHIFT)
+ & TRB_CR_EPID_MASK;
+ unsigned int streamid = (trb.status >> 16) & 0xffff;
+ event.ccode = xhci_set_ep_dequeue(xhci, slotid,
+ epid, streamid,
+ trb.parameter);
+ }
+ break;
+ case CR_RESET_DEVICE:
+ slotid = xhci_get_slot(xhci, &event, &trb);
+ if (slotid) {
+ event.ccode = xhci_reset_slot(xhci, slotid);
+ }
+ break;
+ case CR_GET_PORT_BANDWIDTH:
+ event.ccode = xhci_get_port_bandwidth(xhci, trb.parameter);
+ break;
+ case CR_VENDOR_VIA_CHALLENGE_RESPONSE:
+ xhci_via_challenge(xhci, trb.parameter);
+ break;
+ case CR_VENDOR_NEC_FIRMWARE_REVISION:
+ event.type = 48; /* NEC reply */
+ event.length = 0x3025;
+ break;
+ case CR_VENDOR_NEC_CHALLENGE_RESPONSE:
+ {
+ uint32_t chi = trb.parameter >> 32;
+ uint32_t clo = trb.parameter;
+ uint32_t val = xhci_nec_challenge(chi, clo);
+ event.length = val & 0xFFFF;
+ event.epid = val >> 16;
+ slotid = val >> 24;
+ event.type = 48; /* NEC reply */
+ }
+ break;
+ default:
+ trace_usb_xhci_unimplemented("command", type);
+ event.ccode = CC_TRB_ERROR;
+ break;
+ }
+ event.slotid = slotid;
+ xhci_event(xhci, &event, 0);
+ }
+}
+
+static bool xhci_port_have_device(XHCIPort *port)
+{
+ if (!port->uport->dev || !port->uport->dev->attached) {
+ return false; /* no device present */
+ }
+ if (!((1 << port->uport->dev->speed) & port->speedmask)) {
+ return false; /* speed mismatch */
+ }
+ return true;
+}
+
+static void xhci_port_notify(XHCIPort *port, uint32_t bits)
+{
+ XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS,
+ port->portnr << 24 };
+
+ if ((port->portsc & bits) == bits) {
+ return;
+ }
+ trace_usb_xhci_port_notify(port->portnr, bits);
+ port->portsc |= bits;
+ if (!xhci_running(port->xhci)) {
+ return;
+ }
+ xhci_event(port->xhci, &ev, 0);
+}
+
+static void xhci_port_update(XHCIPort *port, int is_detach)
+{
+ uint32_t pls = PLS_RX_DETECT;
+
+ port->portsc = PORTSC_PP;
+ if (!is_detach && xhci_port_have_device(port)) {
+ port->portsc |= PORTSC_CCS;
+ switch (port->uport->dev->speed) {
+ case USB_SPEED_LOW:
+ port->portsc |= PORTSC_SPEED_LOW;
+ pls = PLS_POLLING;
+ break;
+ case USB_SPEED_FULL:
+ port->portsc |= PORTSC_SPEED_FULL;
+ pls = PLS_POLLING;
+ break;
+ case USB_SPEED_HIGH:
+ port->portsc |= PORTSC_SPEED_HIGH;
+ pls = PLS_POLLING;
+ break;
+ case USB_SPEED_SUPER:
+ port->portsc |= PORTSC_SPEED_SUPER;
+ port->portsc |= PORTSC_PED;
+ pls = PLS_U0;
+ break;
+ }
+ }
+ set_field(&port->portsc, pls, PORTSC_PLS);
+ trace_usb_xhci_port_link(port->portnr, pls);
+ xhci_port_notify(port, PORTSC_CSC);
+}
+
+static void xhci_port_reset(XHCIPort *port, bool warm_reset)
+{
+ trace_usb_xhci_port_reset(port->portnr, warm_reset);
+
+ if (!xhci_port_have_device(port)) {
+ return;
+ }
+
+ usb_device_reset(port->uport->dev);
+
+ switch (port->uport->dev->speed) {
+ case USB_SPEED_SUPER:
+ if (warm_reset) {
+ port->portsc |= PORTSC_WRC;
+ }
+ /* fall through */
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ set_field(&port->portsc, PLS_U0, PORTSC_PLS);
+ trace_usb_xhci_port_link(port->portnr, PLS_U0);
+ port->portsc |= PORTSC_PED;
+ break;
+ }
+
+ port->portsc &= ~PORTSC_PR;
+ xhci_port_notify(port, PORTSC_PRC);
+}
+
+static void xhci_reset(DeviceState *dev)
+{
+ XHCIState *xhci = XHCI(dev);
+ int i;
+
+ trace_usb_xhci_reset();
+ if (!(xhci->usbsts & USBSTS_HCH)) {
+ DPRINTF("xhci: reset while running!\n");
+ }
+
+ xhci->usbcmd = 0;
+ xhci->usbsts = USBSTS_HCH;
+ xhci->dnctrl = 0;
+ xhci->crcr_low = 0;
+ xhci->crcr_high = 0;
+ xhci->dcbaap_low = 0;
+ xhci->dcbaap_high = 0;
+ xhci->config = 0;
+
+ for (i = 0; i < xhci->numslots; i++) {
+ xhci_disable_slot(xhci, i+1);
+ }
+
+ for (i = 0; i < xhci->numports; i++) {
+ xhci_port_update(xhci->ports + i, 0);
+ }
+
+ for (i = 0; i < xhci->numintrs; i++) {
+ xhci->intr[i].iman = 0;
+ xhci->intr[i].imod = 0;
+ xhci->intr[i].erstsz = 0;
+ xhci->intr[i].erstba_low = 0;
+ xhci->intr[i].erstba_high = 0;
+ xhci->intr[i].erdp_low = 0;
+ xhci->intr[i].erdp_high = 0;
+ xhci->intr[i].msix_used = 0;
+
+ xhci->intr[i].er_ep_idx = 0;
+ xhci->intr[i].er_pcs = 1;
+ xhci->intr[i].er_full = 0;
+ xhci->intr[i].ev_buffer_put = 0;
+ xhci->intr[i].ev_buffer_get = 0;
+ }
+
+ xhci->mfindex_start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ xhci_mfwrap_update(xhci);
+}
+
+static uint64_t xhci_cap_read(void *ptr, hwaddr reg, unsigned size)
+{
+ XHCIState *xhci = ptr;
+ uint32_t ret;
+
+ switch (reg) {
+ case 0x00: /* HCIVERSION, CAPLENGTH */
+ ret = 0x01000000 | LEN_CAP;
+ break;
+ case 0x04: /* HCSPARAMS 1 */
+ ret = ((xhci->numports_2+xhci->numports_3)<<24)
+ | (xhci->numintrs<<8) | xhci->numslots;
+ break;
+ case 0x08: /* HCSPARAMS 2 */
+ ret = 0x0000000f;
+ break;
+ case 0x0c: /* HCSPARAMS 3 */
+ ret = 0x00000000;
+ break;
+ case 0x10: /* HCCPARAMS */
+ if (sizeof(dma_addr_t) == 4) {
+ ret = 0x00080000 | (xhci->max_pstreams_mask << 12);
+ } else {
+ ret = 0x00080001 | (xhci->max_pstreams_mask << 12);
+ }
+ break;
+ case 0x14: /* DBOFF */
+ ret = OFF_DOORBELL;
+ break;
+ case 0x18: /* RTSOFF */
+ ret = OFF_RUNTIME;
+ break;
+
+ /* extended capabilities */
+ case 0x20: /* Supported Protocol:00 */
+ ret = 0x02000402; /* USB 2.0 */
+ break;
+ case 0x24: /* Supported Protocol:04 */
+ ret = 0x20425355; /* "USB " */
+ break;
+ case 0x28: /* Supported Protocol:08 */
+ if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) {
+ ret = (xhci->numports_2<<8) | (xhci->numports_3+1);
+ } else {
+ ret = (xhci->numports_2<<8) | 1;
+ }
+ break;
+ case 0x2c: /* Supported Protocol:0c */
+ ret = 0x00000000; /* reserved */
+ break;
+ case 0x30: /* Supported Protocol:00 */
+ ret = 0x03000002; /* USB 3.0 */
+ break;
+ case 0x34: /* Supported Protocol:04 */
+ ret = 0x20425355; /* "USB " */
+ break;
+ case 0x38: /* Supported Protocol:08 */
+ if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) {
+ ret = (xhci->numports_3<<8) | 1;
+ } else {
+ ret = (xhci->numports_3<<8) | (xhci->numports_2+1);
+ }
+ break;
+ case 0x3c: /* Supported Protocol:0c */
+ ret = 0x00000000; /* reserved */
+ break;
+ default:
+ trace_usb_xhci_unimplemented("cap read", reg);
+ ret = 0;
+ }
+
+ trace_usb_xhci_cap_read(reg, ret);
+ return ret;
+}
+
+static uint64_t xhci_port_read(void *ptr, hwaddr reg, unsigned size)
+{
+ XHCIPort *port = ptr;
+ uint32_t ret;
+
+ switch (reg) {
+ case 0x00: /* PORTSC */
+ ret = port->portsc;
+ break;
+ case 0x04: /* PORTPMSC */
+ case 0x08: /* PORTLI */
+ ret = 0;
+ break;
+ case 0x0c: /* reserved */
+ default:
+ trace_usb_xhci_unimplemented("port read", reg);
+ ret = 0;
+ }
+
+ trace_usb_xhci_port_read(port->portnr, reg, ret);
+ return ret;
+}
+
+static void xhci_port_write(void *ptr, hwaddr reg,
+ uint64_t val, unsigned size)
+{
+ XHCIPort *port = ptr;
+ uint32_t portsc, notify;
+
+ trace_usb_xhci_port_write(port->portnr, reg, val);
+
+ switch (reg) {
+ case 0x00: /* PORTSC */
+ /* write-1-to-start bits */
+ if (val & PORTSC_WPR) {
+ xhci_port_reset(port, true);
+ break;
+ }
+ if (val & PORTSC_PR) {
+ xhci_port_reset(port, false);
+ break;
+ }
+
+ portsc = port->portsc;
+ notify = 0;
+ /* write-1-to-clear bits*/
+ portsc &= ~(val & (PORTSC_CSC|PORTSC_PEC|PORTSC_WRC|PORTSC_OCC|
+ PORTSC_PRC|PORTSC_PLC|PORTSC_CEC));
+ if (val & PORTSC_LWS) {
+ /* overwrite PLS only when LWS=1 */
+ uint32_t old_pls = get_field(port->portsc, PORTSC_PLS);
+ uint32_t new_pls = get_field(val, PORTSC_PLS);
+ switch (new_pls) {
+ case PLS_U0:
+ if (old_pls != PLS_U0) {
+ set_field(&portsc, new_pls, PORTSC_PLS);
+ trace_usb_xhci_port_link(port->portnr, new_pls);
+ notify = PORTSC_PLC;
+ }
+ break;
+ case PLS_U3:
+ if (old_pls < PLS_U3) {
+ set_field(&portsc, new_pls, PORTSC_PLS);
+ trace_usb_xhci_port_link(port->portnr, new_pls);
+ }
+ break;
+ case PLS_RESUME:
+ /* windows does this for some reason, don't spam stderr */
+ break;
+ default:
+ DPRINTF("%s: ignore pls write (old %d, new %d)\n",
+ __func__, old_pls, new_pls);
+ break;
+ }
+ }
+ /* read/write bits */
+ portsc &= ~(PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE);
+ portsc |= (val & (PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE));
+ port->portsc = portsc;
+ if (notify) {
+ xhci_port_notify(port, notify);
+ }
+ break;
+ case 0x04: /* PORTPMSC */
+ case 0x08: /* PORTLI */
+ default:
+ trace_usb_xhci_unimplemented("port write", reg);
+ }
+}
+
+static uint64_t xhci_oper_read(void *ptr, hwaddr reg, unsigned size)
+{
+ XHCIState *xhci = ptr;
+ uint32_t ret;
+
+ switch (reg) {
+ case 0x00: /* USBCMD */
+ ret = xhci->usbcmd;
+ break;
+ case 0x04: /* USBSTS */
+ ret = xhci->usbsts;
+ break;
+ case 0x08: /* PAGESIZE */
+ ret = 1; /* 4KiB */
+ break;
+ case 0x14: /* DNCTRL */
+ ret = xhci->dnctrl;
+ break;
+ case 0x18: /* CRCR low */
+ ret = xhci->crcr_low & ~0xe;
+ break;
+ case 0x1c: /* CRCR high */
+ ret = xhci->crcr_high;
+ break;
+ case 0x30: /* DCBAAP low */
+ ret = xhci->dcbaap_low;
+ break;
+ case 0x34: /* DCBAAP high */
+ ret = xhci->dcbaap_high;
+ break;
+ case 0x38: /* CONFIG */
+ ret = xhci->config;
+ break;
+ default:
+ trace_usb_xhci_unimplemented("oper read", reg);
+ ret = 0;
+ }
+
+ trace_usb_xhci_oper_read(reg, ret);
+ return ret;
+}
+
+static void xhci_oper_write(void *ptr, hwaddr reg,
+ uint64_t val, unsigned size)
+{
+ XHCIState *xhci = ptr;
+ DeviceState *d = DEVICE(ptr);
+
+ trace_usb_xhci_oper_write(reg, val);
+
+ switch (reg) {
+ case 0x00: /* USBCMD */
+ if ((val & USBCMD_RS) && !(xhci->usbcmd & USBCMD_RS)) {
+ xhci_run(xhci);
+ } else if (!(val & USBCMD_RS) && (xhci->usbcmd & USBCMD_RS)) {
+ xhci_stop(xhci);
+ }
+ if (val & USBCMD_CSS) {
+ /* save state */
+ xhci->usbsts &= ~USBSTS_SRE;
+ }
+ if (val & USBCMD_CRS) {
+ /* restore state */
+ xhci->usbsts |= USBSTS_SRE;
+ }
+ xhci->usbcmd = val & 0xc0f;
+ xhci_mfwrap_update(xhci);
+ if (val & USBCMD_HCRST) {
+ xhci_reset(d);
+ }
+ xhci_intx_update(xhci);
+ break;
+
+ case 0x04: /* USBSTS */
+ /* these bits are write-1-to-clear */
+ xhci->usbsts &= ~(val & (USBSTS_HSE|USBSTS_EINT|USBSTS_PCD|USBSTS_SRE));
+ xhci_intx_update(xhci);
+ break;
+
+ case 0x14: /* DNCTRL */
+ xhci->dnctrl = val & 0xffff;
+ break;
+ case 0x18: /* CRCR low */
+ xhci->crcr_low = (val & 0xffffffcf) | (xhci->crcr_low & CRCR_CRR);
+ break;
+ case 0x1c: /* CRCR high */
+ xhci->crcr_high = val;
+ if (xhci->crcr_low & (CRCR_CA|CRCR_CS) && (xhci->crcr_low & CRCR_CRR)) {
+ XHCIEvent event = {ER_COMMAND_COMPLETE, CC_COMMAND_RING_STOPPED};
+ xhci->crcr_low &= ~CRCR_CRR;
+ xhci_event(xhci, &event, 0);
+ DPRINTF("xhci: command ring stopped (CRCR=%08x)\n", xhci->crcr_low);
+ } else {
+ dma_addr_t base = xhci_addr64(xhci->crcr_low & ~0x3f, val);
+ xhci_ring_init(xhci, &xhci->cmd_ring, base);
+ }
+ xhci->crcr_low &= ~(CRCR_CA | CRCR_CS);
+ break;
+ case 0x30: /* DCBAAP low */
+ xhci->dcbaap_low = val & 0xffffffc0;
+ break;
+ case 0x34: /* DCBAAP high */
+ xhci->dcbaap_high = val;
+ break;
+ case 0x38: /* CONFIG */
+ xhci->config = val & 0xff;
+ break;
+ default:
+ trace_usb_xhci_unimplemented("oper write", reg);
+ }
+}
+
+static uint64_t xhci_runtime_read(void *ptr, hwaddr reg,
+ unsigned size)
+{
+ XHCIState *xhci = ptr;
+ uint32_t ret = 0;
+
+ if (reg < 0x20) {
+ switch (reg) {
+ case 0x00: /* MFINDEX */
+ ret = xhci_mfindex_get(xhci) & 0x3fff;
+ break;
+ default:
+ trace_usb_xhci_unimplemented("runtime read", reg);
+ break;
+ }
+ } else {
+ int v = (reg - 0x20) / 0x20;
+ XHCIInterrupter *intr = &xhci->intr[v];
+ switch (reg & 0x1f) {
+ case 0x00: /* IMAN */
+ ret = intr->iman;
+ break;
+ case 0x04: /* IMOD */
+ ret = intr->imod;
+ break;
+ case 0x08: /* ERSTSZ */
+ ret = intr->erstsz;
+ break;
+ case 0x10: /* ERSTBA low */
+ ret = intr->erstba_low;
+ break;
+ case 0x14: /* ERSTBA high */
+ ret = intr->erstba_high;
+ break;
+ case 0x18: /* ERDP low */
+ ret = intr->erdp_low;
+ break;
+ case 0x1c: /* ERDP high */
+ ret = intr->erdp_high;
+ break;
+ }
+ }
+
+ trace_usb_xhci_runtime_read(reg, ret);
+ return ret;
+}
+
+static void xhci_runtime_write(void *ptr, hwaddr reg,
+ uint64_t val, unsigned size)
+{
+ XHCIState *xhci = ptr;
+ int v = (reg - 0x20) / 0x20;
+ XHCIInterrupter *intr = &xhci->intr[v];
+ trace_usb_xhci_runtime_write(reg, val);
+
+ if (reg < 0x20) {
+ trace_usb_xhci_unimplemented("runtime write", reg);
+ return;
+ }
+
+ switch (reg & 0x1f) {
+ case 0x00: /* IMAN */
+ if (val & IMAN_IP) {
+ intr->iman &= ~IMAN_IP;
+ }
+ intr->iman &= ~IMAN_IE;
+ intr->iman |= val & IMAN_IE;
+ if (v == 0) {
+ xhci_intx_update(xhci);
+ }
+ xhci_msix_update(xhci, v);
+ break;
+ case 0x04: /* IMOD */
+ intr->imod = val;
+ break;
+ case 0x08: /* ERSTSZ */
+ intr->erstsz = val & 0xffff;
+ break;
+ case 0x10: /* ERSTBA low */
+ /* XXX NEC driver bug: it doesn't align this to 64 bytes
+ intr->erstba_low = val & 0xffffffc0; */
+ intr->erstba_low = val & 0xfffffff0;
+ break;
+ case 0x14: /* ERSTBA high */
+ intr->erstba_high = val;
+ xhci_er_reset(xhci, v);
+ break;
+ case 0x18: /* ERDP low */
+ if (val & ERDP_EHB) {
+ intr->erdp_low &= ~ERDP_EHB;
+ }
+ intr->erdp_low = (val & ~ERDP_EHB) | (intr->erdp_low & ERDP_EHB);
+ break;
+ case 0x1c: /* ERDP high */
+ intr->erdp_high = val;
+ xhci_events_update(xhci, v);
+ break;
+ default:
+ trace_usb_xhci_unimplemented("oper write", reg);
+ }
+}
+
+static uint64_t xhci_doorbell_read(void *ptr, hwaddr reg,
+ unsigned size)
+{
+ /* doorbells always read as 0 */
+ trace_usb_xhci_doorbell_read(reg, 0);
+ return 0;
+}
+
+static void xhci_doorbell_write(void *ptr, hwaddr reg,
+ uint64_t val, unsigned size)
+{
+ XHCIState *xhci = ptr;
+ unsigned int epid, streamid;
+
+ trace_usb_xhci_doorbell_write(reg, val);
+
+ if (!xhci_running(xhci)) {
+ DPRINTF("xhci: wrote doorbell while xHC stopped or paused\n");
+ return;
+ }
+
+ reg >>= 2;
+
+ if (reg == 0) {
+ if (val == 0) {
+ xhci_process_commands(xhci);
+ } else {
+ DPRINTF("xhci: bad doorbell 0 write: 0x%x\n",
+ (uint32_t)val);
+ }
+ } else {
+ epid = val & 0xff;
+ streamid = (val >> 16) & 0xffff;
+ if (reg > xhci->numslots) {
+ DPRINTF("xhci: bad doorbell %d\n", (int)reg);
+ } else if (epid > 31) {
+ DPRINTF("xhci: bad doorbell %d write: 0x%x\n",
+ (int)reg, (uint32_t)val);
+ } else {
+ xhci_kick_ep(xhci, reg, epid, streamid);
+ }
+ }
+}
+
+static void xhci_cap_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ /* nothing */
+}
+
+static const MemoryRegionOps xhci_cap_ops = {
+ .read = xhci_cap_read,
+ .write = xhci_cap_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps xhci_oper_ops = {
+ .read = xhci_oper_read,
+ .write = xhci_oper_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps xhci_port_ops = {
+ .read = xhci_port_read,
+ .write = xhci_port_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps xhci_runtime_ops = {
+ .read = xhci_runtime_read,
+ .write = xhci_runtime_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps xhci_doorbell_ops = {
+ .read = xhci_doorbell_read,
+ .write = xhci_doorbell_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void xhci_attach(USBPort *usbport)
+{
+ XHCIState *xhci = usbport->opaque;
+ XHCIPort *port = xhci_lookup_port(xhci, usbport);
+
+ xhci_port_update(port, 0);
+}
+
+static void xhci_detach(USBPort *usbport)
+{
+ XHCIState *xhci = usbport->opaque;
+ XHCIPort *port = xhci_lookup_port(xhci, usbport);
+
+ xhci_detach_slot(xhci, usbport);
+ xhci_port_update(port, 1);
+}
+
+static void xhci_wakeup(USBPort *usbport)
+{
+ XHCIState *xhci = usbport->opaque;
+ XHCIPort *port = xhci_lookup_port(xhci, usbport);
+
+ if (get_field(port->portsc, PORTSC_PLS) != PLS_U3) {
+ return;
+ }
+ set_field(&port->portsc, PLS_RESUME, PORTSC_PLS);
+ xhci_port_notify(port, PORTSC_PLC);
+}
+
+static void xhci_complete(USBPort *port, USBPacket *packet)
+{
+ XHCITransfer *xfer = container_of(packet, XHCITransfer, packet);
+
+ if (packet->status == USB_RET_REMOVE_FROM_QUEUE) {
+ xhci_ep_nuke_one_xfer(xfer, 0);
+ return;
+ }
+ xhci_complete_packet(xfer);
+ xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid, xfer->streamid);
+}
+
+static void xhci_child_detach(USBPort *uport, USBDevice *child)
+{
+ USBBus *bus = usb_bus_from_device(child);
+ XHCIState *xhci = container_of(bus, XHCIState, bus);
+
+ xhci_detach_slot(xhci, child->port);
+}
+
+static USBPortOps xhci_uport_ops = {
+ .attach = xhci_attach,
+ .detach = xhci_detach,
+ .wakeup = xhci_wakeup,
+ .complete = xhci_complete,
+ .child_detach = xhci_child_detach,
+};
+
+static int xhci_find_epid(USBEndpoint *ep)
+{
+ if (ep->nr == 0) {
+ return 1;
+ }
+ if (ep->pid == USB_TOKEN_IN) {
+ return ep->nr * 2 + 1;
+ } else {
+ return ep->nr * 2;
+ }
+}
+
+static USBEndpoint *xhci_epid_to_usbep(XHCIState *xhci,
+ unsigned int slotid, unsigned int epid)
+{
+ assert(slotid >= 1 && slotid <= xhci->numslots);
+
+ if (!xhci->slots[slotid - 1].uport) {
+ return NULL;
+ }
+
+ return usb_ep_get(xhci->slots[slotid - 1].uport->dev,
+ (epid & 1) ? USB_TOKEN_IN : USB_TOKEN_OUT, epid >> 1);
+}
+
+static void xhci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep,
+ unsigned int stream)
+{
+ XHCIState *xhci = container_of(bus, XHCIState, bus);
+ int slotid;
+
+ DPRINTF("%s\n", __func__);
+ slotid = ep->dev->addr;
+ if (slotid == 0 || !xhci->slots[slotid-1].enabled) {
+ DPRINTF("%s: oops, no slot for dev %d\n", __func__, ep->dev->addr);
+ return;
+ }
+ xhci_kick_ep(xhci, slotid, xhci_find_epid(ep), stream);
+}
+
+static USBBusOps xhci_bus_ops = {
+ .wakeup_endpoint = xhci_wakeup_endpoint,
+};
+
+static void usb_xhci_init(XHCIState *xhci)
+{
+ DeviceState *dev = DEVICE(xhci);
+ XHCIPort *port;
+ int i, usbports, speedmask;
+
+ xhci->usbsts = USBSTS_HCH;
+
+ if (xhci->numports_2 > MAXPORTS_2) {
+ xhci->numports_2 = MAXPORTS_2;
+ }
+ if (xhci->numports_3 > MAXPORTS_3) {
+ xhci->numports_3 = MAXPORTS_3;
+ }
+ usbports = MAX(xhci->numports_2, xhci->numports_3);
+ xhci->numports = xhci->numports_2 + xhci->numports_3;
+
+ usb_bus_new(&xhci->bus, sizeof(xhci->bus), &xhci_bus_ops, dev);
+
+ for (i = 0; i < usbports; i++) {
+ speedmask = 0;
+ if (i < xhci->numports_2) {
+ if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) {
+ port = &xhci->ports[i + xhci->numports_3];
+ port->portnr = i + 1 + xhci->numports_3;
+ } else {
+ port = &xhci->ports[i];
+ port->portnr = i + 1;
+ }
+ port->uport = &xhci->uports[i];
+ port->speedmask =
+ USB_SPEED_MASK_LOW |
+ USB_SPEED_MASK_FULL |
+ USB_SPEED_MASK_HIGH;
+ snprintf(port->name, sizeof(port->name), "usb2 port #%d", i+1);
+ speedmask |= port->speedmask;
+ }
+ if (i < xhci->numports_3) {
+ if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) {
+ port = &xhci->ports[i];
+ port->portnr = i + 1;
+ } else {
+ port = &xhci->ports[i + xhci->numports_2];
+ port->portnr = i + 1 + xhci->numports_2;
+ }
+ port->uport = &xhci->uports[i];
+ port->speedmask = USB_SPEED_MASK_SUPER;
+ snprintf(port->name, sizeof(port->name), "usb3 port #%d", i+1);
+ speedmask |= port->speedmask;
+ }
+ usb_register_port(&xhci->bus, &xhci->uports[i], xhci, i,
+ &xhci_uport_ops, speedmask);
+ }
+}
+
+static void usb_xhci_realize(struct PCIDevice *dev, Error **errp)
+{
+ int i, ret;
+
+ XHCIState *xhci = XHCI(dev);
+
+ dev->config[PCI_CLASS_PROG] = 0x30; /* xHCI */
+ dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin 1 */
+ dev->config[PCI_CACHE_LINE_SIZE] = 0x10;
+ dev->config[0x60] = 0x30; /* release number */
+
+ usb_xhci_init(xhci);
+
+ if (xhci->numintrs > MAXINTRS) {
+ xhci->numintrs = MAXINTRS;
+ }
+ while (xhci->numintrs & (xhci->numintrs - 1)) { /* ! power of 2 */
+ xhci->numintrs++;
+ }
+ if (xhci->numintrs < 1) {
+ xhci->numintrs = 1;
+ }
+ if (xhci->numslots > MAXSLOTS) {
+ xhci->numslots = MAXSLOTS;
+ }
+ if (xhci->numslots < 1) {
+ xhci->numslots = 1;
+ }
+ if (xhci_get_flag(xhci, XHCI_FLAG_ENABLE_STREAMS)) {
+ xhci->max_pstreams_mask = 7; /* == 256 primary streams */
+ } else {
+ xhci->max_pstreams_mask = 0;
+ }
+
+ xhci->mfwrap_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_mfwrap_timer, xhci);
+
+ memory_region_init(&xhci->mem, OBJECT(xhci), "xhci", LEN_REGS);
+ memory_region_init_io(&xhci->mem_cap, OBJECT(xhci), &xhci_cap_ops, xhci,
+ "capabilities", LEN_CAP);
+ memory_region_init_io(&xhci->mem_oper, OBJECT(xhci), &xhci_oper_ops, xhci,
+ "operational", 0x400);
+ memory_region_init_io(&xhci->mem_runtime, OBJECT(xhci), &xhci_runtime_ops, xhci,
+ "runtime", LEN_RUNTIME);
+ memory_region_init_io(&xhci->mem_doorbell, OBJECT(xhci), &xhci_doorbell_ops, xhci,
+ "doorbell", LEN_DOORBELL);
+
+ memory_region_add_subregion(&xhci->mem, 0, &xhci->mem_cap);
+ memory_region_add_subregion(&xhci->mem, OFF_OPER, &xhci->mem_oper);
+ memory_region_add_subregion(&xhci->mem, OFF_RUNTIME, &xhci->mem_runtime);
+ memory_region_add_subregion(&xhci->mem, OFF_DOORBELL, &xhci->mem_doorbell);
+
+ for (i = 0; i < xhci->numports; i++) {
+ XHCIPort *port = &xhci->ports[i];
+ uint32_t offset = OFF_OPER + 0x400 + 0x10 * i;
+ port->xhci = xhci;
+ memory_region_init_io(&port->mem, OBJECT(xhci), &xhci_port_ops, port,
+ port->name, 0x10);
+ memory_region_add_subregion(&xhci->mem, offset, &port->mem);
+ }
+
+ pci_register_bar(dev, 0,
+ PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64,
+ &xhci->mem);
+
+ if (pci_bus_is_express(dev->bus) ||
+ xhci_get_flag(xhci, XHCI_FLAG_FORCE_PCIE_ENDCAP)) {
+ ret = pcie_endpoint_cap_init(dev, 0xa0);
+ assert(ret >= 0);
+ }
+
+ if (xhci_get_flag(xhci, XHCI_FLAG_USE_MSI)) {
+ msi_init(dev, 0x70, xhci->numintrs, true, false);
+ }
+ if (xhci_get_flag(xhci, XHCI_FLAG_USE_MSI_X)) {
+ msix_init(dev, xhci->numintrs,
+ &xhci->mem, 0, OFF_MSIX_TABLE,
+ &xhci->mem, 0, OFF_MSIX_PBA,
+ 0x90);
+ }
+}
+
+static void usb_xhci_exit(PCIDevice *dev)
+{
+ int i;
+ XHCIState *xhci = XHCI(dev);
+
+ trace_usb_xhci_exit();
+
+ for (i = 0; i < xhci->numslots; i++) {
+ xhci_disable_slot(xhci, i + 1);
+ }
+
+ if (xhci->mfwrap_timer) {
+ timer_del(xhci->mfwrap_timer);
+ timer_free(xhci->mfwrap_timer);
+ xhci->mfwrap_timer = NULL;
+ }
+
+ memory_region_del_subregion(&xhci->mem, &xhci->mem_cap);
+ memory_region_del_subregion(&xhci->mem, &xhci->mem_oper);
+ memory_region_del_subregion(&xhci->mem, &xhci->mem_runtime);
+ memory_region_del_subregion(&xhci->mem, &xhci->mem_doorbell);
+
+ for (i = 0; i < xhci->numports; i++) {
+ XHCIPort *port = &xhci->ports[i];
+ memory_region_del_subregion(&xhci->mem, &port->mem);
+ }
+
+ /* destroy msix memory region */
+ if (dev->msix_table && dev->msix_pba
+ && dev->msix_entry_used) {
+ memory_region_del_subregion(&xhci->mem, &dev->msix_table_mmio);
+ memory_region_del_subregion(&xhci->mem, &dev->msix_pba_mmio);
+ }
+
+ usb_bus_release(&xhci->bus);
+}
+
+static int usb_xhci_post_load(void *opaque, int version_id)
+{
+ XHCIState *xhci = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(xhci);
+ XHCISlot *slot;
+ XHCIEPContext *epctx;
+ dma_addr_t dcbaap, pctx;
+ uint32_t slot_ctx[4];
+ uint32_t ep_ctx[5];
+ int slotid, epid, state, intr;
+
+ dcbaap = xhci_addr64(xhci->dcbaap_low, xhci->dcbaap_high);
+
+ for (slotid = 1; slotid <= xhci->numslots; slotid++) {
+ slot = &xhci->slots[slotid-1];
+ if (!slot->addressed) {
+ continue;
+ }
+ slot->ctx =
+ xhci_mask64(ldq_le_pci_dma(pci_dev, dcbaap + 8 * slotid));
+ xhci_dma_read_u32s(xhci, slot->ctx, slot_ctx, sizeof(slot_ctx));
+ slot->uport = xhci_lookup_uport(xhci, slot_ctx);
+ if (!slot->uport) {
+ /* should not happen, but may trigger on guest bugs */
+ slot->enabled = 0;
+ slot->addressed = 0;
+ continue;
+ }
+ assert(slot->uport && slot->uport->dev);
+
+ for (epid = 1; epid <= 31; epid++) {
+ pctx = slot->ctx + 32 * epid;
+ xhci_dma_read_u32s(xhci, pctx, ep_ctx, sizeof(ep_ctx));
+ state = ep_ctx[0] & EP_STATE_MASK;
+ if (state == EP_DISABLED) {
+ continue;
+ }
+ epctx = xhci_alloc_epctx(xhci, slotid, epid);
+ slot->eps[epid-1] = epctx;
+ xhci_init_epctx(epctx, pctx, ep_ctx);
+ epctx->state = state;
+ if (state == EP_RUNNING) {
+ /* kick endpoint after vmload is finished */
+ timer_mod(epctx->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ }
+ }
+ }
+
+ for (intr = 0; intr < xhci->numintrs; intr++) {
+ if (xhci->intr[intr].msix_used) {
+ msix_vector_use(pci_dev, intr);
+ } else {
+ msix_vector_unuse(pci_dev, intr);
+ }
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_xhci_ring = {
+ .name = "xhci-ring",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(dequeue, XHCIRing),
+ VMSTATE_BOOL(ccs, XHCIRing),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_xhci_port = {
+ .name = "xhci-port",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(portsc, XHCIPort),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_xhci_slot = {
+ .name = "xhci-slot",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(enabled, XHCISlot),
+ VMSTATE_BOOL(addressed, XHCISlot),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_xhci_event = {
+ .name = "xhci-event",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(type, XHCIEvent),
+ VMSTATE_UINT32(ccode, XHCIEvent),
+ VMSTATE_UINT64(ptr, XHCIEvent),
+ VMSTATE_UINT32(length, XHCIEvent),
+ VMSTATE_UINT32(flags, XHCIEvent),
+ VMSTATE_UINT8(slotid, XHCIEvent),
+ VMSTATE_UINT8(epid, XHCIEvent),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool xhci_er_full(void *opaque, int version_id)
+{
+ struct XHCIInterrupter *intr = opaque;
+ return intr->er_full;
+}
+
+static const VMStateDescription vmstate_xhci_intr = {
+ .name = "xhci-intr",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ /* registers */
+ VMSTATE_UINT32(iman, XHCIInterrupter),
+ VMSTATE_UINT32(imod, XHCIInterrupter),
+ VMSTATE_UINT32(erstsz, XHCIInterrupter),
+ VMSTATE_UINT32(erstba_low, XHCIInterrupter),
+ VMSTATE_UINT32(erstba_high, XHCIInterrupter),
+ VMSTATE_UINT32(erdp_low, XHCIInterrupter),
+ VMSTATE_UINT32(erdp_high, XHCIInterrupter),
+
+ /* state */
+ VMSTATE_BOOL(msix_used, XHCIInterrupter),
+ VMSTATE_BOOL(er_pcs, XHCIInterrupter),
+ VMSTATE_UINT64(er_start, XHCIInterrupter),
+ VMSTATE_UINT32(er_size, XHCIInterrupter),
+ VMSTATE_UINT32(er_ep_idx, XHCIInterrupter),
+
+ /* event queue (used if ring is full) */
+ VMSTATE_BOOL(er_full, XHCIInterrupter),
+ VMSTATE_UINT32_TEST(ev_buffer_put, XHCIInterrupter, xhci_er_full),
+ VMSTATE_UINT32_TEST(ev_buffer_get, XHCIInterrupter, xhci_er_full),
+ VMSTATE_STRUCT_ARRAY_TEST(ev_buffer, XHCIInterrupter, EV_QUEUE,
+ xhci_er_full, 1,
+ vmstate_xhci_event, XHCIEvent),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_xhci = {
+ .name = "xhci",
+ .version_id = 1,
+ .post_load = usb_xhci_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCIE_DEVICE(parent_obj, XHCIState),
+ VMSTATE_MSIX(parent_obj, XHCIState),
+
+ VMSTATE_STRUCT_VARRAY_UINT32(ports, XHCIState, numports, 1,
+ vmstate_xhci_port, XHCIPort),
+ VMSTATE_STRUCT_VARRAY_UINT32(slots, XHCIState, numslots, 1,
+ vmstate_xhci_slot, XHCISlot),
+ VMSTATE_STRUCT_VARRAY_UINT32(intr, XHCIState, numintrs, 1,
+ vmstate_xhci_intr, XHCIInterrupter),
+
+ /* Operational Registers */
+ VMSTATE_UINT32(usbcmd, XHCIState),
+ VMSTATE_UINT32(usbsts, XHCIState),
+ VMSTATE_UINT32(dnctrl, XHCIState),
+ VMSTATE_UINT32(crcr_low, XHCIState),
+ VMSTATE_UINT32(crcr_high, XHCIState),
+ VMSTATE_UINT32(dcbaap_low, XHCIState),
+ VMSTATE_UINT32(dcbaap_high, XHCIState),
+ VMSTATE_UINT32(config, XHCIState),
+
+ /* Runtime Registers & state */
+ VMSTATE_INT64(mfindex_start, XHCIState),
+ VMSTATE_TIMER_PTR(mfwrap_timer, XHCIState),
+ VMSTATE_STRUCT(cmd_ring, XHCIState, 1, vmstate_xhci_ring, XHCIRing),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property xhci_properties[] = {
+ DEFINE_PROP_BIT("msi", XHCIState, flags, XHCI_FLAG_USE_MSI, true),
+ DEFINE_PROP_BIT("msix", XHCIState, flags, XHCI_FLAG_USE_MSI_X, true),
+ DEFINE_PROP_BIT("superspeed-ports-first",
+ XHCIState, flags, XHCI_FLAG_SS_FIRST, true),
+ DEFINE_PROP_BIT("force-pcie-endcap", XHCIState, flags,
+ XHCI_FLAG_FORCE_PCIE_ENDCAP, false),
+ DEFINE_PROP_BIT("streams", XHCIState, flags,
+ XHCI_FLAG_ENABLE_STREAMS, true),
+ DEFINE_PROP_UINT32("intrs", XHCIState, numintrs, MAXINTRS),
+ DEFINE_PROP_UINT32("slots", XHCIState, numslots, MAXSLOTS),
+ DEFINE_PROP_UINT32("p2", XHCIState, numports_2, 4),
+ DEFINE_PROP_UINT32("p3", XHCIState, numports_3, 4),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xhci_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_xhci;
+ dc->props = xhci_properties;
+ dc->reset = xhci_reset;
+ set_bit(DEVICE_CATEGORY_USB, dc->categories);
+ k->realize = usb_xhci_realize;
+ k->exit = usb_xhci_exit;
+ k->vendor_id = PCI_VENDOR_ID_NEC;
+ k->device_id = PCI_DEVICE_ID_NEC_UPD720200;
+ k->class_id = PCI_CLASS_SERIAL_USB;
+ k->revision = 0x03;
+ k->is_express = 1;
+}
+
+static const TypeInfo xhci_info = {
+ .name = TYPE_XHCI,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(XHCIState),
+ .class_init = xhci_class_init,
+};
+
+static void xhci_register_types(void)
+{
+ type_register_static(&xhci_info);
+}
+
+type_init(xhci_register_types)
diff --git a/hw/usb/host-legacy.c b/hw/usb/host-legacy.c
new file mode 100644
index 00000000..422ed9a6
--- /dev/null
+++ b/hw/usb/host-legacy.c
@@ -0,0 +1,143 @@
+/*
+ * Linux host USB redirector
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * Copyright (c) 2008 Max Krasnyansky
+ * Support for host device auto connect & disconnect
+ * Major rewrite to support fully async operation
+ *
+ * Copyright 2008 TJ <linux@tjworld.net>
+ * Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition
+ * to the legacy /proc/bus/usb USB device discovery and handling
+ *
+ * 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.
+ */
+
+#include "qemu-common.h"
+#include "hw/usb.h"
+#include "hw/usb/host.h"
+
+/*
+ * Autoconnect filter
+ * Format:
+ * auto:bus:dev[:vid:pid]
+ * auto:bus.dev[:vid:pid]
+ *
+ * bus - bus number (dec, * means any)
+ * dev - device number (dec, * means any)
+ * vid - vendor id (hex, * means any)
+ * pid - product id (hex, * means any)
+ *
+ * See 'lsusb' output.
+ */
+static int parse_filter(const char *spec, struct USBAutoFilter *f)
+{
+ enum { BUS, DEV, VID, PID, DONE };
+ const char *p = spec;
+ int i;
+
+ f->bus_num = 0;
+ f->addr = 0;
+ f->vendor_id = 0;
+ f->product_id = 0;
+
+ for (i = BUS; i < DONE; i++) {
+ p = strpbrk(p, ":.");
+ if (!p) {
+ break;
+ }
+ p++;
+
+ if (*p == '*') {
+ continue;
+ }
+ switch (i) {
+ case BUS:
+ f->bus_num = strtol(p, NULL, 10);
+ break;
+ case DEV:
+ f->addr = strtol(p, NULL, 10);
+ break;
+ case VID:
+ f->vendor_id = strtol(p, NULL, 16);
+ break;
+ case PID:
+ f->product_id = strtol(p, NULL, 16);
+ break;
+ }
+ }
+
+ if (i < DEV) {
+ fprintf(stderr, "husb: invalid auto filter spec %s\n", spec);
+ return -1;
+ }
+
+ return 0;
+}
+
+USBDevice *usb_host_device_open(USBBus *bus, const char *devname)
+{
+ struct USBAutoFilter filter;
+ USBDevice *dev;
+ char *p;
+
+ dev = usb_create(bus, "usb-host");
+
+ if (strstr(devname, "auto:")) {
+ if (parse_filter(devname, &filter) < 0) {
+ goto fail;
+ }
+ } else {
+ p = strchr(devname, '.');
+ if (p) {
+ filter.bus_num = strtoul(devname, NULL, 0);
+ filter.addr = strtoul(p + 1, NULL, 0);
+ filter.vendor_id = 0;
+ filter.product_id = 0;
+ } else {
+ p = strchr(devname, ':');
+ if (p) {
+ filter.bus_num = 0;
+ filter.addr = 0;
+ filter.vendor_id = strtoul(devname, NULL, 16);
+ filter.product_id = strtoul(p + 1, NULL, 16);
+ } else {
+ goto fail;
+ }
+ }
+ }
+
+ qdev_prop_set_uint32(&dev->qdev, "hostbus", filter.bus_num);
+ qdev_prop_set_uint32(&dev->qdev, "hostaddr", filter.addr);
+ qdev_prop_set_uint32(&dev->qdev, "vendorid", filter.vendor_id);
+ qdev_prop_set_uint32(&dev->qdev, "productid", filter.product_id);
+ return dev;
+
+fail:
+ object_unparent(OBJECT(dev));
+ return NULL;
+}
+
+static void usb_host_register_types(void)
+{
+ usb_legacy_register("usb-host", "host", usb_host_device_open);
+}
+
+type_init(usb_host_register_types)
diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
new file mode 100644
index 00000000..11429f5e
--- /dev/null
+++ b/hw/usb/host-libusb.c
@@ -0,0 +1,1685 @@
+/*
+ * Linux host USB redirector
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * Copyright (c) 2008 Max Krasnyansky
+ * Support for host device auto connect & disconnect
+ * Major rewrite to support fully async operation
+ *
+ * Copyright 2008 TJ <linux@tjworld.net>
+ * Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition
+ * to the legacy /proc/bus/usb USB device discovery and handling
+ *
+ * (c) 2012 Gerd Hoffmann <kraxel@redhat.com>
+ * Completely rewritten to use libusb instead of usbfs ioctls.
+ *
+ * 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.
+ */
+
+#include <poll.h>
+#include <libusb.h>
+
+#include "qemu-common.h"
+#include "monitor/monitor.h"
+#include "qemu/error-report.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+
+#include "hw/usb.h"
+
+/* ------------------------------------------------------------------------ */
+
+#define TYPE_USB_HOST_DEVICE "usb-host"
+#define USB_HOST_DEVICE(obj) \
+ OBJECT_CHECK(USBHostDevice, (obj), TYPE_USB_HOST_DEVICE)
+
+typedef struct USBHostDevice USBHostDevice;
+typedef struct USBHostRequest USBHostRequest;
+typedef struct USBHostIsoXfer USBHostIsoXfer;
+typedef struct USBHostIsoRing USBHostIsoRing;
+
+struct USBAutoFilter {
+ uint32_t bus_num;
+ uint32_t addr;
+ char *port;
+ uint32_t vendor_id;
+ uint32_t product_id;
+};
+
+enum USBHostDeviceOptions {
+ USB_HOST_OPT_PIPELINE,
+};
+
+struct USBHostDevice {
+ USBDevice parent_obj;
+
+ /* properties */
+ struct USBAutoFilter match;
+ int32_t bootindex;
+ uint32_t iso_urb_count;
+ uint32_t iso_urb_frames;
+ uint32_t options;
+ uint32_t loglevel;
+
+ /* state */
+ QTAILQ_ENTRY(USBHostDevice) next;
+ int seen, errcount;
+ int bus_num;
+ int addr;
+ char port[16];
+
+ libusb_device *dev;
+ libusb_device_handle *dh;
+ struct libusb_device_descriptor ddesc;
+
+ struct {
+ bool detached;
+ bool claimed;
+ } ifs[USB_MAX_INTERFACES];
+
+ /* callbacks & friends */
+ QEMUBH *bh_nodev;
+ QEMUBH *bh_postld;
+ Notifier exit;
+
+ /* request queues */
+ QTAILQ_HEAD(, USBHostRequest) requests;
+ QTAILQ_HEAD(, USBHostIsoRing) isorings;
+};
+
+struct USBHostRequest {
+ USBHostDevice *host;
+ USBPacket *p;
+ bool in;
+ struct libusb_transfer *xfer;
+ unsigned char *buffer;
+ unsigned char *cbuf;
+ unsigned int clen;
+ bool usb3ep0quirk;
+ QTAILQ_ENTRY(USBHostRequest) next;
+};
+
+struct USBHostIsoXfer {
+ USBHostIsoRing *ring;
+ struct libusb_transfer *xfer;
+ bool copy_complete;
+ unsigned int packet;
+ QTAILQ_ENTRY(USBHostIsoXfer) next;
+};
+
+struct USBHostIsoRing {
+ USBHostDevice *host;
+ USBEndpoint *ep;
+ QTAILQ_HEAD(, USBHostIsoXfer) unused;
+ QTAILQ_HEAD(, USBHostIsoXfer) inflight;
+ QTAILQ_HEAD(, USBHostIsoXfer) copy;
+ QTAILQ_ENTRY(USBHostIsoRing) next;
+};
+
+static QTAILQ_HEAD(, USBHostDevice) hostdevs =
+ QTAILQ_HEAD_INITIALIZER(hostdevs);
+
+static void usb_host_auto_check(void *unused);
+static void usb_host_release_interfaces(USBHostDevice *s);
+static void usb_host_nodev(USBHostDevice *s);
+static void usb_host_detach_kernel(USBHostDevice *s);
+static void usb_host_attach_kernel(USBHostDevice *s);
+
+/* ------------------------------------------------------------------------ */
+
+#ifndef LIBUSB_LOG_LEVEL_WARNING /* older libusb didn't define these */
+#define LIBUSB_LOG_LEVEL_WARNING 2
+#endif
+
+/* ------------------------------------------------------------------------ */
+
+#define CONTROL_TIMEOUT 10000 /* 10 sec */
+#define BULK_TIMEOUT 0 /* unlimited */
+#define INTR_TIMEOUT 0 /* unlimited */
+
+#if LIBUSBX_API_VERSION >= 0x01000103
+# define HAVE_STREAMS 1
+#endif
+
+static const char *speed_name[] = {
+ [LIBUSB_SPEED_UNKNOWN] = "?",
+ [LIBUSB_SPEED_LOW] = "1.5",
+ [LIBUSB_SPEED_FULL] = "12",
+ [LIBUSB_SPEED_HIGH] = "480",
+ [LIBUSB_SPEED_SUPER] = "5000",
+};
+
+static const unsigned int speed_map[] = {
+ [LIBUSB_SPEED_LOW] = USB_SPEED_LOW,
+ [LIBUSB_SPEED_FULL] = USB_SPEED_FULL,
+ [LIBUSB_SPEED_HIGH] = USB_SPEED_HIGH,
+ [LIBUSB_SPEED_SUPER] = USB_SPEED_SUPER,
+};
+
+static const unsigned int status_map[] = {
+ [LIBUSB_TRANSFER_COMPLETED] = USB_RET_SUCCESS,
+ [LIBUSB_TRANSFER_ERROR] = USB_RET_IOERROR,
+ [LIBUSB_TRANSFER_TIMED_OUT] = USB_RET_IOERROR,
+ [LIBUSB_TRANSFER_CANCELLED] = USB_RET_IOERROR,
+ [LIBUSB_TRANSFER_STALL] = USB_RET_STALL,
+ [LIBUSB_TRANSFER_NO_DEVICE] = USB_RET_NODEV,
+ [LIBUSB_TRANSFER_OVERFLOW] = USB_RET_BABBLE,
+};
+
+static const char *err_names[] = {
+ [-LIBUSB_ERROR_IO] = "IO",
+ [-LIBUSB_ERROR_INVALID_PARAM] = "INVALID_PARAM",
+ [-LIBUSB_ERROR_ACCESS] = "ACCESS",
+ [-LIBUSB_ERROR_NO_DEVICE] = "NO_DEVICE",
+ [-LIBUSB_ERROR_NOT_FOUND] = "NOT_FOUND",
+ [-LIBUSB_ERROR_BUSY] = "BUSY",
+ [-LIBUSB_ERROR_TIMEOUT] = "TIMEOUT",
+ [-LIBUSB_ERROR_OVERFLOW] = "OVERFLOW",
+ [-LIBUSB_ERROR_PIPE] = "PIPE",
+ [-LIBUSB_ERROR_INTERRUPTED] = "INTERRUPTED",
+ [-LIBUSB_ERROR_NO_MEM] = "NO_MEM",
+ [-LIBUSB_ERROR_NOT_SUPPORTED] = "NOT_SUPPORTED",
+ [-LIBUSB_ERROR_OTHER] = "OTHER",
+};
+
+static libusb_context *ctx;
+static uint32_t loglevel;
+
+static void usb_host_handle_fd(void *opaque)
+{
+ struct timeval tv = { 0, 0 };
+ libusb_handle_events_timeout(ctx, &tv);
+}
+
+static void usb_host_add_fd(int fd, short events, void *user_data)
+{
+ qemu_set_fd_handler(fd,
+ (events & POLLIN) ? usb_host_handle_fd : NULL,
+ (events & POLLOUT) ? usb_host_handle_fd : NULL,
+ ctx);
+}
+
+static void usb_host_del_fd(int fd, void *user_data)
+{
+ qemu_set_fd_handler(fd, NULL, NULL, NULL);
+}
+
+static int usb_host_init(void)
+{
+ const struct libusb_pollfd **poll;
+ int i, rc;
+
+ if (ctx) {
+ return 0;
+ }
+ rc = libusb_init(&ctx);
+ if (rc != 0) {
+ return -1;
+ }
+ libusb_set_debug(ctx, loglevel);
+
+ libusb_set_pollfd_notifiers(ctx, usb_host_add_fd,
+ usb_host_del_fd,
+ ctx);
+ poll = libusb_get_pollfds(ctx);
+ if (poll) {
+ for (i = 0; poll[i] != NULL; i++) {
+ usb_host_add_fd(poll[i]->fd, poll[i]->events, ctx);
+ }
+ }
+ free(poll);
+ return 0;
+}
+
+static int usb_host_get_port(libusb_device *dev, char *port, size_t len)
+{
+ uint8_t path[7];
+ size_t off;
+ int rc, i;
+
+#if LIBUSBX_API_VERSION >= 0x01000102
+ rc = libusb_get_port_numbers(dev, path, 7);
+#else
+ rc = libusb_get_port_path(ctx, dev, path, 7);
+#endif
+ if (rc < 0) {
+ return 0;
+ }
+ off = snprintf(port, len, "%d", path[0]);
+ for (i = 1; i < rc; i++) {
+ off += snprintf(port+off, len-off, ".%d", path[i]);
+ }
+ return off;
+}
+
+static void usb_host_libusb_error(const char *func, int rc)
+{
+ const char *errname;
+
+ if (rc >= 0) {
+ return;
+ }
+
+ if (-rc < ARRAY_SIZE(err_names) && err_names[-rc]) {
+ errname = err_names[-rc];
+ } else {
+ errname = "?";
+ }
+ error_report("%s: %d [%s]", func, rc, errname);
+}
+
+/* ------------------------------------------------------------------------ */
+
+static bool usb_host_use_combining(USBEndpoint *ep)
+{
+ int type;
+
+ if (!ep->pipeline) {
+ return false;
+ }
+ if (ep->pid != USB_TOKEN_IN) {
+ return false;
+ }
+ type = usb_ep_get_type(ep->dev, ep->pid, ep->nr);
+ if (type != USB_ENDPOINT_XFER_BULK) {
+ return false;
+ }
+ return true;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static USBHostRequest *usb_host_req_alloc(USBHostDevice *s, USBPacket *p,
+ bool in, size_t bufsize)
+{
+ USBHostRequest *r = g_new0(USBHostRequest, 1);
+
+ r->host = s;
+ r->p = p;
+ r->in = in;
+ r->xfer = libusb_alloc_transfer(0);
+ if (bufsize) {
+ r->buffer = g_malloc(bufsize);
+ }
+ QTAILQ_INSERT_TAIL(&s->requests, r, next);
+ return r;
+}
+
+static void usb_host_req_free(USBHostRequest *r)
+{
+ if (r->host) {
+ QTAILQ_REMOVE(&r->host->requests, r, next);
+ }
+ libusb_free_transfer(r->xfer);
+ g_free(r->buffer);
+ g_free(r);
+}
+
+static USBHostRequest *usb_host_req_find(USBHostDevice *s, USBPacket *p)
+{
+ USBHostRequest *r;
+
+ QTAILQ_FOREACH(r, &s->requests, next) {
+ if (r->p == p) {
+ return r;
+ }
+ }
+ return NULL;
+}
+
+static void usb_host_req_complete_ctrl(struct libusb_transfer *xfer)
+{
+ USBHostRequest *r = xfer->user_data;
+ USBHostDevice *s = r->host;
+ bool disconnect = (xfer->status == LIBUSB_TRANSFER_NO_DEVICE);
+
+ if (r->p == NULL) {
+ goto out; /* request was canceled */
+ }
+
+ r->p->status = status_map[xfer->status];
+ r->p->actual_length = xfer->actual_length;
+ if (r->in && xfer->actual_length) {
+ memcpy(r->cbuf, r->buffer + 8, xfer->actual_length);
+
+ /* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices
+ * to work redirected to a not superspeed capable hcd */
+ if (r->usb3ep0quirk && xfer->actual_length >= 18 &&
+ r->cbuf[7] == 9) {
+ r->cbuf[7] = 64;
+ }
+ }
+ trace_usb_host_req_complete(s->bus_num, s->addr, r->p,
+ r->p->status, r->p->actual_length);
+ usb_generic_async_ctrl_complete(USB_DEVICE(s), r->p);
+
+out:
+ usb_host_req_free(r);
+ if (disconnect) {
+ usb_host_nodev(s);
+ }
+}
+
+static void usb_host_req_complete_data(struct libusb_transfer *xfer)
+{
+ USBHostRequest *r = xfer->user_data;
+ USBHostDevice *s = r->host;
+ bool disconnect = (xfer->status == LIBUSB_TRANSFER_NO_DEVICE);
+
+ if (r->p == NULL) {
+ goto out; /* request was canceled */
+ }
+
+ r->p->status = status_map[xfer->status];
+ if (r->in && xfer->actual_length) {
+ usb_packet_copy(r->p, r->buffer, xfer->actual_length);
+ }
+ trace_usb_host_req_complete(s->bus_num, s->addr, r->p,
+ r->p->status, r->p->actual_length);
+ if (usb_host_use_combining(r->p->ep)) {
+ usb_combined_input_packet_complete(USB_DEVICE(s), r->p);
+ } else {
+ usb_packet_complete(USB_DEVICE(s), r->p);
+ }
+
+out:
+ usb_host_req_free(r);
+ if (disconnect) {
+ usb_host_nodev(s);
+ }
+}
+
+static void usb_host_req_abort(USBHostRequest *r)
+{
+ USBHostDevice *s = r->host;
+ bool inflight = (r->p && r->p->state == USB_PACKET_ASYNC);
+
+ if (inflight) {
+ r->p->status = USB_RET_NODEV;
+ trace_usb_host_req_complete(s->bus_num, s->addr, r->p,
+ r->p->status, r->p->actual_length);
+ if (r->p->ep->nr == 0) {
+ usb_generic_async_ctrl_complete(USB_DEVICE(s), r->p);
+ } else {
+ usb_packet_complete(USB_DEVICE(s), r->p);
+ }
+ r->p = NULL;
+ }
+
+ QTAILQ_REMOVE(&r->host->requests, r, next);
+ r->host = NULL;
+
+ if (inflight) {
+ libusb_cancel_transfer(r->xfer);
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+
+static void usb_host_req_complete_iso(struct libusb_transfer *transfer)
+{
+ USBHostIsoXfer *xfer = transfer->user_data;
+
+ if (!xfer) {
+ /* USBHostIsoXfer released while inflight */
+ g_free(transfer->buffer);
+ libusb_free_transfer(transfer);
+ return;
+ }
+
+ QTAILQ_REMOVE(&xfer->ring->inflight, xfer, next);
+ if (QTAILQ_EMPTY(&xfer->ring->inflight)) {
+ USBHostDevice *s = xfer->ring->host;
+ trace_usb_host_iso_stop(s->bus_num, s->addr, xfer->ring->ep->nr);
+ }
+ if (xfer->ring->ep->pid == USB_TOKEN_IN) {
+ QTAILQ_INSERT_TAIL(&xfer->ring->copy, xfer, next);
+ } else {
+ QTAILQ_INSERT_TAIL(&xfer->ring->unused, xfer, next);
+ }
+}
+
+static USBHostIsoRing *usb_host_iso_alloc(USBHostDevice *s, USBEndpoint *ep)
+{
+ USBHostIsoRing *ring = g_new0(USBHostIsoRing, 1);
+ USBHostIsoXfer *xfer;
+ /* FIXME: check interval (for now assume one xfer per frame) */
+ int packets = s->iso_urb_frames;
+ int i;
+
+ ring->host = s;
+ ring->ep = ep;
+ QTAILQ_INIT(&ring->unused);
+ QTAILQ_INIT(&ring->inflight);
+ QTAILQ_INIT(&ring->copy);
+ QTAILQ_INSERT_TAIL(&s->isorings, ring, next);
+
+ for (i = 0; i < s->iso_urb_count; i++) {
+ xfer = g_new0(USBHostIsoXfer, 1);
+ xfer->ring = ring;
+ xfer->xfer = libusb_alloc_transfer(packets);
+ xfer->xfer->dev_handle = s->dh;
+ xfer->xfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS;
+
+ xfer->xfer->endpoint = ring->ep->nr;
+ if (ring->ep->pid == USB_TOKEN_IN) {
+ xfer->xfer->endpoint |= USB_DIR_IN;
+ }
+ xfer->xfer->callback = usb_host_req_complete_iso;
+ xfer->xfer->user_data = xfer;
+
+ xfer->xfer->num_iso_packets = packets;
+ xfer->xfer->length = ring->ep->max_packet_size * packets;
+ xfer->xfer->buffer = g_malloc0(xfer->xfer->length);
+
+ QTAILQ_INSERT_TAIL(&ring->unused, xfer, next);
+ }
+
+ return ring;
+}
+
+static USBHostIsoRing *usb_host_iso_find(USBHostDevice *s, USBEndpoint *ep)
+{
+ USBHostIsoRing *ring;
+
+ QTAILQ_FOREACH(ring, &s->isorings, next) {
+ if (ring->ep == ep) {
+ return ring;
+ }
+ }
+ return NULL;
+}
+
+static void usb_host_iso_reset_xfer(USBHostIsoXfer *xfer)
+{
+ libusb_set_iso_packet_lengths(xfer->xfer,
+ xfer->ring->ep->max_packet_size);
+ xfer->packet = 0;
+ xfer->copy_complete = false;
+}
+
+static void usb_host_iso_free_xfer(USBHostIsoXfer *xfer, bool inflight)
+{
+ if (inflight) {
+ xfer->xfer->user_data = NULL;
+ } else {
+ g_free(xfer->xfer->buffer);
+ libusb_free_transfer(xfer->xfer);
+ }
+ g_free(xfer);
+}
+
+static void usb_host_iso_free(USBHostIsoRing *ring)
+{
+ USBHostIsoXfer *xfer;
+
+ while ((xfer = QTAILQ_FIRST(&ring->inflight)) != NULL) {
+ QTAILQ_REMOVE(&ring->inflight, xfer, next);
+ usb_host_iso_free_xfer(xfer, true);
+ }
+ while ((xfer = QTAILQ_FIRST(&ring->unused)) != NULL) {
+ QTAILQ_REMOVE(&ring->unused, xfer, next);
+ usb_host_iso_free_xfer(xfer, false);
+ }
+ while ((xfer = QTAILQ_FIRST(&ring->copy)) != NULL) {
+ QTAILQ_REMOVE(&ring->copy, xfer, next);
+ usb_host_iso_free_xfer(xfer, false);
+ }
+
+ QTAILQ_REMOVE(&ring->host->isorings, ring, next);
+ g_free(ring);
+}
+
+static void usb_host_iso_free_all(USBHostDevice *s)
+{
+ USBHostIsoRing *ring;
+
+ while ((ring = QTAILQ_FIRST(&s->isorings)) != NULL) {
+ usb_host_iso_free(ring);
+ }
+}
+
+static bool usb_host_iso_data_copy(USBHostIsoXfer *xfer, USBPacket *p)
+{
+ unsigned int psize;
+ unsigned char *buf;
+
+ buf = libusb_get_iso_packet_buffer_simple(xfer->xfer, xfer->packet);
+ if (p->pid == USB_TOKEN_OUT) {
+ psize = p->iov.size;
+ if (psize > xfer->ring->ep->max_packet_size) {
+ /* should not happen (guest bug) */
+ psize = xfer->ring->ep->max_packet_size;
+ }
+ xfer->xfer->iso_packet_desc[xfer->packet].length = psize;
+ } else {
+ psize = xfer->xfer->iso_packet_desc[xfer->packet].actual_length;
+ if (psize > p->iov.size) {
+ /* should not happen (guest bug) */
+ psize = p->iov.size;
+ }
+ }
+ usb_packet_copy(p, buf, psize);
+ xfer->packet++;
+ xfer->copy_complete = (xfer->packet == xfer->xfer->num_iso_packets);
+ return xfer->copy_complete;
+}
+
+static void usb_host_iso_data_in(USBHostDevice *s, USBPacket *p)
+{
+ USBHostIsoRing *ring;
+ USBHostIsoXfer *xfer;
+ bool disconnect = false;
+ int rc;
+
+ ring = usb_host_iso_find(s, p->ep);
+ if (ring == NULL) {
+ ring = usb_host_iso_alloc(s, p->ep);
+ }
+
+ /* copy data to guest */
+ xfer = QTAILQ_FIRST(&ring->copy);
+ if (xfer != NULL) {
+ if (usb_host_iso_data_copy(xfer, p)) {
+ QTAILQ_REMOVE(&ring->copy, xfer, next);
+ QTAILQ_INSERT_TAIL(&ring->unused, xfer, next);
+ }
+ }
+
+ /* submit empty bufs to host */
+ while ((xfer = QTAILQ_FIRST(&ring->unused)) != NULL) {
+ QTAILQ_REMOVE(&ring->unused, xfer, next);
+ usb_host_iso_reset_xfer(xfer);
+ rc = libusb_submit_transfer(xfer->xfer);
+ if (rc != 0) {
+ usb_host_libusb_error("libusb_submit_transfer [iso]", rc);
+ QTAILQ_INSERT_TAIL(&ring->unused, xfer, next);
+ if (rc == LIBUSB_ERROR_NO_DEVICE) {
+ disconnect = true;
+ }
+ break;
+ }
+ if (QTAILQ_EMPTY(&ring->inflight)) {
+ trace_usb_host_iso_start(s->bus_num, s->addr, p->ep->nr);
+ }
+ QTAILQ_INSERT_TAIL(&ring->inflight, xfer, next);
+ }
+
+ if (disconnect) {
+ usb_host_nodev(s);
+ }
+}
+
+static void usb_host_iso_data_out(USBHostDevice *s, USBPacket *p)
+{
+ USBHostIsoRing *ring;
+ USBHostIsoXfer *xfer;
+ bool disconnect = false;
+ int rc, filled = 0;
+
+ ring = usb_host_iso_find(s, p->ep);
+ if (ring == NULL) {
+ ring = usb_host_iso_alloc(s, p->ep);
+ }
+
+ /* copy data from guest */
+ xfer = QTAILQ_FIRST(&ring->copy);
+ while (xfer != NULL && xfer->copy_complete) {
+ filled++;
+ xfer = QTAILQ_NEXT(xfer, next);
+ }
+ if (xfer == NULL) {
+ xfer = QTAILQ_FIRST(&ring->unused);
+ if (xfer == NULL) {
+ trace_usb_host_iso_out_of_bufs(s->bus_num, s->addr, p->ep->nr);
+ return;
+ }
+ QTAILQ_REMOVE(&ring->unused, xfer, next);
+ usb_host_iso_reset_xfer(xfer);
+ QTAILQ_INSERT_TAIL(&ring->copy, xfer, next);
+ }
+ usb_host_iso_data_copy(xfer, p);
+
+ if (QTAILQ_EMPTY(&ring->inflight)) {
+ /* wait until half of our buffers are filled
+ before kicking the iso out stream */
+ if (filled*2 < s->iso_urb_count) {
+ return;
+ }
+ }
+
+ /* submit filled bufs to host */
+ while ((xfer = QTAILQ_FIRST(&ring->copy)) != NULL &&
+ xfer->copy_complete) {
+ QTAILQ_REMOVE(&ring->copy, xfer, next);
+ rc = libusb_submit_transfer(xfer->xfer);
+ if (rc != 0) {
+ usb_host_libusb_error("libusb_submit_transfer [iso]", rc);
+ QTAILQ_INSERT_TAIL(&ring->unused, xfer, next);
+ if (rc == LIBUSB_ERROR_NO_DEVICE) {
+ disconnect = true;
+ }
+ break;
+ }
+ if (QTAILQ_EMPTY(&ring->inflight)) {
+ trace_usb_host_iso_start(s->bus_num, s->addr, p->ep->nr);
+ }
+ QTAILQ_INSERT_TAIL(&ring->inflight, xfer, next);
+ }
+
+ if (disconnect) {
+ usb_host_nodev(s);
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+
+static void usb_host_speed_compat(USBHostDevice *s)
+{
+ USBDevice *udev = USB_DEVICE(s);
+ struct libusb_config_descriptor *conf;
+ const struct libusb_interface_descriptor *intf;
+ const struct libusb_endpoint_descriptor *endp;
+#ifdef HAVE_STREAMS
+ struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp;
+#endif
+ bool compat_high = true;
+ bool compat_full = true;
+ uint8_t type;
+ int rc, c, i, a, e;
+
+ for (c = 0;; c++) {
+ rc = libusb_get_config_descriptor(s->dev, c, &conf);
+ if (rc != 0) {
+ break;
+ }
+ for (i = 0; i < conf->bNumInterfaces; i++) {
+ for (a = 0; a < conf->interface[i].num_altsetting; a++) {
+ intf = &conf->interface[i].altsetting[a];
+ for (e = 0; e < intf->bNumEndpoints; e++) {
+ endp = &intf->endpoint[e];
+ type = endp->bmAttributes & 0x3;
+ switch (type) {
+ case 0x01: /* ISO */
+ compat_full = false;
+ compat_high = false;
+ break;
+ case 0x02: /* BULK */
+#ifdef HAVE_STREAMS
+ rc = libusb_get_ss_endpoint_companion_descriptor
+ (ctx, endp, &endp_ss_comp);
+ if (rc == LIBUSB_SUCCESS) {
+ libusb_free_ss_endpoint_companion_descriptor
+ (endp_ss_comp);
+ compat_full = false;
+ compat_high = false;
+ }
+#endif
+ break;
+ case 0x03: /* INTERRUPT */
+ if (endp->wMaxPacketSize > 64) {
+ compat_full = false;
+ }
+ if (endp->wMaxPacketSize > 1024) {
+ compat_high = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ libusb_free_config_descriptor(conf);
+ }
+
+ udev->speedmask = (1 << udev->speed);
+ if (udev->speed == USB_SPEED_SUPER && compat_high) {
+ udev->speedmask |= USB_SPEED_MASK_HIGH;
+ }
+ if (udev->speed == USB_SPEED_SUPER && compat_full) {
+ udev->speedmask |= USB_SPEED_MASK_FULL;
+ }
+ if (udev->speed == USB_SPEED_HIGH && compat_full) {
+ udev->speedmask |= USB_SPEED_MASK_FULL;
+ }
+}
+
+static void usb_host_ep_update(USBHostDevice *s)
+{
+ static const char *tname[] = {
+ [USB_ENDPOINT_XFER_CONTROL] = "control",
+ [USB_ENDPOINT_XFER_ISOC] = "isoc",
+ [USB_ENDPOINT_XFER_BULK] = "bulk",
+ [USB_ENDPOINT_XFER_INT] = "int",
+ };
+ USBDevice *udev = USB_DEVICE(s);
+ struct libusb_config_descriptor *conf;
+ const struct libusb_interface_descriptor *intf;
+ const struct libusb_endpoint_descriptor *endp;
+#ifdef HAVE_STREAMS
+ struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp;
+#endif
+ uint8_t devep, type;
+ int pid, ep;
+ int rc, i, e;
+
+ usb_ep_reset(udev);
+ rc = libusb_get_active_config_descriptor(s->dev, &conf);
+ if (rc != 0) {
+ return;
+ }
+ trace_usb_host_parse_config(s->bus_num, s->addr,
+ conf->bConfigurationValue, true);
+
+ for (i = 0; i < conf->bNumInterfaces; i++) {
+ assert(udev->altsetting[i] < conf->interface[i].num_altsetting);
+ intf = &conf->interface[i].altsetting[udev->altsetting[i]];
+ trace_usb_host_parse_interface(s->bus_num, s->addr,
+ intf->bInterfaceNumber,
+ intf->bAlternateSetting, true);
+ for (e = 0; e < intf->bNumEndpoints; e++) {
+ endp = &intf->endpoint[e];
+
+ devep = endp->bEndpointAddress;
+ pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
+ ep = devep & 0xf;
+ type = endp->bmAttributes & 0x3;
+
+ if (ep == 0) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "invalid endpoint address");
+ return;
+ }
+ if (usb_ep_get_type(udev, pid, ep) != USB_ENDPOINT_XFER_INVALID) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "duplicate endpoint address");
+ return;
+ }
+
+ trace_usb_host_parse_endpoint(s->bus_num, s->addr, ep,
+ (devep & USB_DIR_IN) ? "in" : "out",
+ tname[type], true);
+ usb_ep_set_max_packet_size(udev, pid, ep,
+ endp->wMaxPacketSize);
+ usb_ep_set_type(udev, pid, ep, type);
+ usb_ep_set_ifnum(udev, pid, ep, i);
+ usb_ep_set_halted(udev, pid, ep, 0);
+#ifdef HAVE_STREAMS
+ if (type == LIBUSB_TRANSFER_TYPE_BULK &&
+ libusb_get_ss_endpoint_companion_descriptor(ctx, endp,
+ &endp_ss_comp) == LIBUSB_SUCCESS) {
+ usb_ep_set_max_streams(udev, pid, ep,
+ endp_ss_comp->bmAttributes);
+ libusb_free_ss_endpoint_companion_descriptor(endp_ss_comp);
+ }
+#endif
+ }
+ }
+
+ libusb_free_config_descriptor(conf);
+}
+
+static int usb_host_open(USBHostDevice *s, libusb_device *dev)
+{
+ USBDevice *udev = USB_DEVICE(s);
+ int bus_num = libusb_get_bus_number(dev);
+ int addr = libusb_get_device_address(dev);
+ int rc;
+ Error *local_err = NULL;
+
+ trace_usb_host_open_started(bus_num, addr);
+
+ if (s->dh != NULL) {
+ goto fail;
+ }
+ rc = libusb_open(dev, &s->dh);
+ if (rc != 0) {
+ goto fail;
+ }
+
+ s->dev = dev;
+ s->bus_num = bus_num;
+ s->addr = addr;
+
+ usb_host_detach_kernel(s);
+
+ libusb_get_device_descriptor(dev, &s->ddesc);
+ usb_host_get_port(s->dev, s->port, sizeof(s->port));
+
+ usb_ep_init(udev);
+ usb_host_ep_update(s);
+
+ udev->speed = speed_map[libusb_get_device_speed(dev)];
+ usb_host_speed_compat(s);
+
+ if (s->ddesc.iProduct) {
+ libusb_get_string_descriptor_ascii(s->dh, s->ddesc.iProduct,
+ (unsigned char *)udev->product_desc,
+ sizeof(udev->product_desc));
+ } else {
+ snprintf(udev->product_desc, sizeof(udev->product_desc),
+ "host:%d.%d", bus_num, addr);
+ }
+
+ usb_device_attach(udev, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ goto fail;
+ }
+
+ trace_usb_host_open_success(bus_num, addr);
+ return 0;
+
+fail:
+ trace_usb_host_open_failure(bus_num, addr);
+ if (s->dh != NULL) {
+ usb_host_release_interfaces(s);
+ libusb_reset_device(s->dh);
+ usb_host_attach_kernel(s);
+ libusb_close(s->dh);
+ s->dh = NULL;
+ s->dev = NULL;
+ }
+ return -1;
+}
+
+static void usb_host_abort_xfers(USBHostDevice *s)
+{
+ USBHostRequest *r, *rtmp;
+
+ QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) {
+ usb_host_req_abort(r);
+ }
+}
+
+static int usb_host_close(USBHostDevice *s)
+{
+ USBDevice *udev = USB_DEVICE(s);
+
+ if (s->dh == NULL) {
+ return -1;
+ }
+
+ trace_usb_host_close(s->bus_num, s->addr);
+
+ usb_host_abort_xfers(s);
+ usb_host_iso_free_all(s);
+
+ if (udev->attached) {
+ usb_device_detach(udev);
+ }
+
+ usb_host_release_interfaces(s);
+ libusb_reset_device(s->dh);
+ usb_host_attach_kernel(s);
+ libusb_close(s->dh);
+ s->dh = NULL;
+ s->dev = NULL;
+
+ usb_host_auto_check(NULL);
+ return 0;
+}
+
+static void usb_host_nodev_bh(void *opaque)
+{
+ USBHostDevice *s = opaque;
+ usb_host_close(s);
+}
+
+static void usb_host_nodev(USBHostDevice *s)
+{
+ if (!s->bh_nodev) {
+ s->bh_nodev = qemu_bh_new(usb_host_nodev_bh, s);
+ }
+ qemu_bh_schedule(s->bh_nodev);
+}
+
+static void usb_host_exit_notifier(struct Notifier *n, void *data)
+{
+ USBHostDevice *s = container_of(n, USBHostDevice, exit);
+
+ if (s->dh) {
+ usb_host_release_interfaces(s);
+ usb_host_attach_kernel(s);
+ }
+}
+
+static void usb_host_realize(USBDevice *udev, Error **errp)
+{
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+
+ if (s->match.vendor_id > 0xffff) {
+ error_setg(errp, "vendorid out of range");
+ return;
+ }
+ if (s->match.product_id > 0xffff) {
+ error_setg(errp, "productid out of range");
+ return;
+ }
+ if (s->match.addr > 127) {
+ error_setg(errp, "hostaddr out of range");
+ return;
+ }
+
+ loglevel = s->loglevel;
+ udev->flags |= (1 << USB_DEV_FLAG_IS_HOST);
+ udev->auto_attach = 0;
+ QTAILQ_INIT(&s->requests);
+ QTAILQ_INIT(&s->isorings);
+
+ s->exit.notify = usb_host_exit_notifier;
+ qemu_add_exit_notifier(&s->exit);
+
+ QTAILQ_INSERT_TAIL(&hostdevs, s, next);
+ usb_host_auto_check(NULL);
+}
+
+static void usb_host_instance_init(Object *obj)
+{
+ USBDevice *udev = USB_DEVICE(obj);
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+
+ device_add_bootindex_property(obj, &s->bootindex,
+ "bootindex", NULL,
+ &udev->qdev, NULL);
+}
+
+static void usb_host_handle_destroy(USBDevice *udev)
+{
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+
+ qemu_remove_exit_notifier(&s->exit);
+ QTAILQ_REMOVE(&hostdevs, s, next);
+ usb_host_close(s);
+}
+
+static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p)
+{
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+ USBHostRequest *r;
+
+ if (p->combined) {
+ usb_combined_packet_cancel(udev, p);
+ return;
+ }
+
+ trace_usb_host_req_canceled(s->bus_num, s->addr, p);
+
+ r = usb_host_req_find(s, p);
+ if (r && r->p) {
+ r->p = NULL; /* mark as dead */
+ libusb_cancel_transfer(r->xfer);
+ }
+}
+
+static void usb_host_detach_kernel(USBHostDevice *s)
+{
+ struct libusb_config_descriptor *conf;
+ int rc, i;
+
+ rc = libusb_get_active_config_descriptor(s->dev, &conf);
+ if (rc != 0) {
+ return;
+ }
+ for (i = 0; i < conf->bNumInterfaces; i++) {
+ rc = libusb_kernel_driver_active(s->dh, i);
+ usb_host_libusb_error("libusb_kernel_driver_active", rc);
+ if (rc != 1) {
+ continue;
+ }
+ trace_usb_host_detach_kernel(s->bus_num, s->addr, i);
+ rc = libusb_detach_kernel_driver(s->dh, i);
+ usb_host_libusb_error("libusb_detach_kernel_driver", rc);
+ s->ifs[i].detached = true;
+ }
+ libusb_free_config_descriptor(conf);
+}
+
+static void usb_host_attach_kernel(USBHostDevice *s)
+{
+ struct libusb_config_descriptor *conf;
+ int rc, i;
+
+ rc = libusb_get_active_config_descriptor(s->dev, &conf);
+ if (rc != 0) {
+ return;
+ }
+ for (i = 0; i < conf->bNumInterfaces; i++) {
+ if (!s->ifs[i].detached) {
+ continue;
+ }
+ trace_usb_host_attach_kernel(s->bus_num, s->addr, i);
+ libusb_attach_kernel_driver(s->dh, i);
+ s->ifs[i].detached = false;
+ }
+ libusb_free_config_descriptor(conf);
+}
+
+static int usb_host_claim_interfaces(USBHostDevice *s, int configuration)
+{
+ USBDevice *udev = USB_DEVICE(s);
+ struct libusb_config_descriptor *conf;
+ int rc, i;
+
+ for (i = 0; i < USB_MAX_INTERFACES; i++) {
+ udev->altsetting[i] = 0;
+ }
+ udev->ninterfaces = 0;
+ udev->configuration = 0;
+
+ usb_host_detach_kernel(s);
+
+ rc = libusb_get_active_config_descriptor(s->dev, &conf);
+ if (rc != 0) {
+ if (rc == LIBUSB_ERROR_NOT_FOUND) {
+ /* address state - ignore */
+ return USB_RET_SUCCESS;
+ }
+ return USB_RET_STALL;
+ }
+
+ for (i = 0; i < conf->bNumInterfaces; i++) {
+ trace_usb_host_claim_interface(s->bus_num, s->addr, configuration, i);
+ rc = libusb_claim_interface(s->dh, i);
+ usb_host_libusb_error("libusb_claim_interface", rc);
+ if (rc != 0) {
+ return USB_RET_STALL;
+ }
+ s->ifs[i].claimed = true;
+ }
+
+ udev->ninterfaces = conf->bNumInterfaces;
+ udev->configuration = configuration;
+
+ libusb_free_config_descriptor(conf);
+ return USB_RET_SUCCESS;
+}
+
+static void usb_host_release_interfaces(USBHostDevice *s)
+{
+ USBDevice *udev = USB_DEVICE(s);
+ int i, rc;
+
+ for (i = 0; i < udev->ninterfaces; i++) {
+ if (!s->ifs[i].claimed) {
+ continue;
+ }
+ trace_usb_host_release_interface(s->bus_num, s->addr, i);
+ rc = libusb_release_interface(s->dh, i);
+ usb_host_libusb_error("libusb_release_interface", rc);
+ s->ifs[i].claimed = false;
+ }
+}
+
+static void usb_host_set_address(USBHostDevice *s, int addr)
+{
+ USBDevice *udev = USB_DEVICE(s);
+
+ trace_usb_host_set_address(s->bus_num, s->addr, addr);
+ udev->addr = addr;
+}
+
+static void usb_host_set_config(USBHostDevice *s, int config, USBPacket *p)
+{
+ int rc;
+
+ trace_usb_host_set_config(s->bus_num, s->addr, config);
+
+ usb_host_release_interfaces(s);
+ rc = libusb_set_configuration(s->dh, config);
+ if (rc != 0) {
+ usb_host_libusb_error("libusb_set_configuration", rc);
+ p->status = USB_RET_STALL;
+ if (rc == LIBUSB_ERROR_NO_DEVICE) {
+ usb_host_nodev(s);
+ }
+ return;
+ }
+ p->status = usb_host_claim_interfaces(s, config);
+ if (p->status != USB_RET_SUCCESS) {
+ return;
+ }
+ usb_host_ep_update(s);
+}
+
+static void usb_host_set_interface(USBHostDevice *s, int iface, int alt,
+ USBPacket *p)
+{
+ USBDevice *udev = USB_DEVICE(s);
+ int rc;
+
+ trace_usb_host_set_interface(s->bus_num, s->addr, iface, alt);
+
+ usb_host_iso_free_all(s);
+
+ if (iface >= USB_MAX_INTERFACES) {
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ rc = libusb_set_interface_alt_setting(s->dh, iface, alt);
+ if (rc != 0) {
+ usb_host_libusb_error("libusb_set_interface_alt_setting", rc);
+ p->status = USB_RET_STALL;
+ if (rc == LIBUSB_ERROR_NO_DEVICE) {
+ usb_host_nodev(s);
+ }
+ return;
+ }
+
+ udev->altsetting[iface] = alt;
+ usb_host_ep_update(s);
+}
+
+static void usb_host_handle_control(USBDevice *udev, USBPacket *p,
+ int request, int value, int index,
+ int length, uint8_t *data)
+{
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+ USBHostRequest *r;
+ int rc;
+
+ trace_usb_host_req_control(s->bus_num, s->addr, p, request, value, index);
+
+ if (s->dh == NULL) {
+ p->status = USB_RET_NODEV;
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+ }
+
+ switch (request) {
+ case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+ usb_host_set_address(s, value);
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+
+ case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+ usb_host_set_config(s, value & 0xff, p);
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+
+ case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+ usb_host_set_interface(s, index, value, p);
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+
+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+ if (value == 0) { /* clear halt */
+ int pid = (index & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
+ libusb_clear_halt(s->dh, index);
+ usb_ep_set_halted(udev, pid, index & 0x0f, 0);
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+ }
+ }
+
+ r = usb_host_req_alloc(s, p, (request >> 8) & USB_DIR_IN, length + 8);
+ r->cbuf = data;
+ r->clen = length;
+ memcpy(r->buffer, udev->setup_buf, 8);
+ if (!r->in) {
+ memcpy(r->buffer + 8, r->cbuf, r->clen);
+ }
+
+ /* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices
+ * to work redirected to a not superspeed capable hcd */
+ if (udev->speed == USB_SPEED_SUPER &&
+ !(udev->port->speedmask & USB_SPEED_MASK_SUPER) &&
+ request == 0x8006 && value == 0x100 && index == 0) {
+ r->usb3ep0quirk = true;
+ }
+
+ libusb_fill_control_transfer(r->xfer, s->dh, r->buffer,
+ usb_host_req_complete_ctrl, r,
+ CONTROL_TIMEOUT);
+ rc = libusb_submit_transfer(r->xfer);
+ if (rc != 0) {
+ p->status = USB_RET_NODEV;
+ trace_usb_host_req_complete(s->bus_num, s->addr, p,
+ p->status, p->actual_length);
+ if (rc == LIBUSB_ERROR_NO_DEVICE) {
+ usb_host_nodev(s);
+ }
+ return;
+ }
+
+ p->status = USB_RET_ASYNC;
+}
+
+static void usb_host_handle_data(USBDevice *udev, USBPacket *p)
+{
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+ USBHostRequest *r;
+ size_t size;
+ int ep, rc;
+
+ if (usb_host_use_combining(p->ep) && p->state == USB_PACKET_SETUP) {
+ p->status = USB_RET_ADD_TO_QUEUE;
+ return;
+ }
+
+ trace_usb_host_req_data(s->bus_num, s->addr, p,
+ p->pid == USB_TOKEN_IN,
+ p->ep->nr, p->iov.size);
+
+ if (s->dh == NULL) {
+ p->status = USB_RET_NODEV;
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+ }
+ if (p->ep->halted) {
+ p->status = USB_RET_STALL;
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+ }
+
+ switch (usb_ep_get_type(udev, p->pid, p->ep->nr)) {
+ case USB_ENDPOINT_XFER_BULK:
+ size = usb_packet_size(p);
+ r = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN, size);
+ if (!r->in) {
+ usb_packet_copy(p, r->buffer, size);
+ }
+ ep = p->ep->nr | (r->in ? USB_DIR_IN : 0);
+ if (p->stream) {
+#ifdef HAVE_STREAMS
+ libusb_fill_bulk_stream_transfer(r->xfer, s->dh, ep, p->stream,
+ r->buffer, size,
+ usb_host_req_complete_data, r,
+ BULK_TIMEOUT);
+#else
+ usb_host_req_free(r);
+ p->status = USB_RET_STALL;
+ return;
+#endif
+ } else {
+ libusb_fill_bulk_transfer(r->xfer, s->dh, ep,
+ r->buffer, size,
+ usb_host_req_complete_data, r,
+ BULK_TIMEOUT);
+ }
+ break;
+ case USB_ENDPOINT_XFER_INT:
+ r = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN, p->iov.size);
+ if (!r->in) {
+ usb_packet_copy(p, r->buffer, p->iov.size);
+ }
+ ep = p->ep->nr | (r->in ? USB_DIR_IN : 0);
+ libusb_fill_interrupt_transfer(r->xfer, s->dh, ep,
+ r->buffer, p->iov.size,
+ usb_host_req_complete_data, r,
+ INTR_TIMEOUT);
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ if (p->pid == USB_TOKEN_IN) {
+ usb_host_iso_data_in(s, p);
+ } else {
+ usb_host_iso_data_out(s, p);
+ }
+ trace_usb_host_req_complete(s->bus_num, s->addr, p,
+ p->status, p->actual_length);
+ return;
+ default:
+ p->status = USB_RET_STALL;
+ trace_usb_host_req_complete(s->bus_num, s->addr, p,
+ p->status, p->actual_length);
+ return;
+ }
+
+ rc = libusb_submit_transfer(r->xfer);
+ if (rc != 0) {
+ p->status = USB_RET_NODEV;
+ trace_usb_host_req_complete(s->bus_num, s->addr, p,
+ p->status, p->actual_length);
+ if (rc == LIBUSB_ERROR_NO_DEVICE) {
+ usb_host_nodev(s);
+ }
+ return;
+ }
+
+ p->status = USB_RET_ASYNC;
+}
+
+static void usb_host_flush_ep_queue(USBDevice *dev, USBEndpoint *ep)
+{
+ if (usb_host_use_combining(ep)) {
+ usb_ep_combine_input_packets(ep);
+ }
+}
+
+static void usb_host_handle_reset(USBDevice *udev)
+{
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+ int rc;
+
+ trace_usb_host_reset(s->bus_num, s->addr);
+
+ rc = libusb_reset_device(s->dh);
+ if (rc != 0) {
+ usb_host_nodev(s);
+ }
+}
+
+static int usb_host_alloc_streams(USBDevice *udev, USBEndpoint **eps,
+ int nr_eps, int streams)
+{
+#ifdef HAVE_STREAMS
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+ unsigned char endpoints[30];
+ int i, rc;
+
+ for (i = 0; i < nr_eps; i++) {
+ endpoints[i] = eps[i]->nr;
+ if (eps[i]->pid == USB_TOKEN_IN) {
+ endpoints[i] |= 0x80;
+ }
+ }
+ rc = libusb_alloc_streams(s->dh, streams, endpoints, nr_eps);
+ if (rc < 0) {
+ usb_host_libusb_error("libusb_alloc_streams", rc);
+ } else if (rc != streams) {
+ error_report("libusb_alloc_streams: got less streams "
+ "then requested %d < %d", rc, streams);
+ }
+
+ return (rc == streams) ? 0 : -1;
+#else
+ error_report("libusb_alloc_streams: error not implemented");
+ return -1;
+#endif
+}
+
+static void usb_host_free_streams(USBDevice *udev, USBEndpoint **eps,
+ int nr_eps)
+{
+#ifdef HAVE_STREAMS
+ USBHostDevice *s = USB_HOST_DEVICE(udev);
+ unsigned char endpoints[30];
+ int i;
+
+ for (i = 0; i < nr_eps; i++) {
+ endpoints[i] = eps[i]->nr;
+ if (eps[i]->pid == USB_TOKEN_IN) {
+ endpoints[i] |= 0x80;
+ }
+ }
+ libusb_free_streams(s->dh, endpoints, nr_eps);
+#endif
+}
+
+/*
+ * This is *NOT* about restoring state. We have absolutely no idea
+ * what state the host device is in at the moment and whenever it is
+ * still present in the first place. Attemping to contine where we
+ * left off is impossible.
+ *
+ * What we are going to to to here is emulate a surprise removal of
+ * the usb device passed through, then kick host scan so the device
+ * will get re-attached (and re-initialized by the guest) in case it
+ * is still present.
+ *
+ * As the device removal will change the state of other devices (usb
+ * host controller, most likely interrupt controller too) we have to
+ * wait with it until *all* vmstate is loaded. Thus post_load just
+ * kicks a bottom half which then does the actual work.
+ */
+static void usb_host_post_load_bh(void *opaque)
+{
+ USBHostDevice *dev = opaque;
+ USBDevice *udev = USB_DEVICE(dev);
+
+ if (dev->dh != NULL) {
+ usb_host_close(dev);
+ }
+ if (udev->attached) {
+ usb_device_detach(udev);
+ }
+ usb_host_auto_check(NULL);
+}
+
+static int usb_host_post_load(void *opaque, int version_id)
+{
+ USBHostDevice *dev = opaque;
+
+ if (!dev->bh_postld) {
+ dev->bh_postld = qemu_bh_new(usb_host_post_load_bh, dev);
+ }
+ qemu_bh_schedule(dev->bh_postld);
+ return 0;
+}
+
+static const VMStateDescription vmstate_usb_host = {
+ .name = "usb-host",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = usb_host_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(parent_obj, USBHostDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property usb_host_dev_properties[] = {
+ DEFINE_PROP_UINT32("hostbus", USBHostDevice, match.bus_num, 0),
+ DEFINE_PROP_UINT32("hostaddr", USBHostDevice, match.addr, 0),
+ DEFINE_PROP_STRING("hostport", USBHostDevice, match.port),
+ DEFINE_PROP_UINT32("vendorid", USBHostDevice, match.vendor_id, 0),
+ DEFINE_PROP_UINT32("productid", USBHostDevice, match.product_id, 0),
+ DEFINE_PROP_UINT32("isobufs", USBHostDevice, iso_urb_count, 4),
+ DEFINE_PROP_UINT32("isobsize", USBHostDevice, iso_urb_frames, 32),
+ DEFINE_PROP_UINT32("loglevel", USBHostDevice, loglevel,
+ LIBUSB_LOG_LEVEL_WARNING),
+ DEFINE_PROP_BIT("pipeline", USBHostDevice, options,
+ USB_HOST_OPT_PIPELINE, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_host_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_host_realize;
+ uc->product_desc = "USB Host Device";
+ uc->cancel_packet = usb_host_cancel_packet;
+ uc->handle_data = usb_host_handle_data;
+ uc->handle_control = usb_host_handle_control;
+ uc->handle_reset = usb_host_handle_reset;
+ uc->handle_destroy = usb_host_handle_destroy;
+ uc->flush_ep_queue = usb_host_flush_ep_queue;
+ uc->alloc_streams = usb_host_alloc_streams;
+ uc->free_streams = usb_host_free_streams;
+ dc->vmsd = &vmstate_usb_host;
+ dc->props = usb_host_dev_properties;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static TypeInfo usb_host_dev_info = {
+ .name = TYPE_USB_HOST_DEVICE,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBHostDevice),
+ .class_init = usb_host_class_initfn,
+ .instance_init = usb_host_instance_init,
+};
+
+static void usb_host_register_types(void)
+{
+ type_register_static(&usb_host_dev_info);
+}
+
+type_init(usb_host_register_types)
+
+/* ------------------------------------------------------------------------ */
+
+static QEMUTimer *usb_auto_timer;
+static VMChangeStateEntry *usb_vmstate;
+
+static void usb_host_vm_state(void *unused, int running, RunState state)
+{
+ if (running) {
+ usb_host_auto_check(unused);
+ }
+}
+
+static void usb_host_auto_check(void *unused)
+{
+ struct USBHostDevice *s;
+ struct USBAutoFilter *f;
+ libusb_device **devs = NULL;
+ struct libusb_device_descriptor ddesc;
+ int unconnected = 0;
+ int i, n;
+
+ if (usb_host_init() != 0) {
+ return;
+ }
+
+ if (runstate_is_running()) {
+ n = libusb_get_device_list(ctx, &devs);
+ for (i = 0; i < n; i++) {
+ if (libusb_get_device_descriptor(devs[i], &ddesc) != 0) {
+ continue;
+ }
+ if (ddesc.bDeviceClass == LIBUSB_CLASS_HUB) {
+ continue;
+ }
+ QTAILQ_FOREACH(s, &hostdevs, next) {
+ f = &s->match;
+ if (f->bus_num > 0 &&
+ f->bus_num != libusb_get_bus_number(devs[i])) {
+ continue;
+ }
+ if (f->addr > 0 &&
+ f->addr != libusb_get_device_address(devs[i])) {
+ continue;
+ }
+ if (f->port != NULL) {
+ char port[16] = "-";
+ usb_host_get_port(devs[i], port, sizeof(port));
+ if (strcmp(f->port, port) != 0) {
+ continue;
+ }
+ }
+ if (f->vendor_id > 0 &&
+ f->vendor_id != ddesc.idVendor) {
+ continue;
+ }
+ if (f->product_id > 0 &&
+ f->product_id != ddesc.idProduct) {
+ continue;
+ }
+
+ /* We got a match */
+ s->seen++;
+ if (s->errcount >= 3) {
+ continue;
+ }
+ if (s->dh != NULL) {
+ continue;
+ }
+ if (usb_host_open(s, devs[i]) < 0) {
+ s->errcount++;
+ continue;
+ }
+ break;
+ }
+ }
+ libusb_free_device_list(devs, 1);
+
+ QTAILQ_FOREACH(s, &hostdevs, next) {
+ if (s->dh == NULL) {
+ unconnected++;
+ }
+ if (s->seen == 0) {
+ if (s->dh) {
+ usb_host_close(s);
+ }
+ s->errcount = 0;
+ }
+ s->seen = 0;
+ }
+
+#if 0
+ if (unconnected == 0) {
+ /* nothing to watch */
+ if (usb_auto_timer) {
+ timer_del(usb_auto_timer);
+ trace_usb_host_auto_scan_disabled();
+ }
+ return;
+ }
+#endif
+ }
+
+ if (!usb_vmstate) {
+ usb_vmstate = qemu_add_vm_change_state_handler(usb_host_vm_state, NULL);
+ }
+ if (!usb_auto_timer) {
+ usb_auto_timer = timer_new_ms(QEMU_CLOCK_REALTIME, usb_host_auto_check, NULL);
+ if (!usb_auto_timer) {
+ return;
+ }
+ trace_usb_host_auto_scan_enabled();
+ }
+ timer_mod(usb_auto_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000);
+}
+
+void hmp_info_usbhost(Monitor *mon, const QDict *qdict)
+{
+ libusb_device **devs = NULL;
+ struct libusb_device_descriptor ddesc;
+ char port[16];
+ int i, n;
+
+ if (usb_host_init() != 0) {
+ return;
+ }
+
+ n = libusb_get_device_list(ctx, &devs);
+ for (i = 0; i < n; i++) {
+ if (libusb_get_device_descriptor(devs[i], &ddesc) != 0) {
+ continue;
+ }
+ if (ddesc.bDeviceClass == LIBUSB_CLASS_HUB) {
+ continue;
+ }
+ usb_host_get_port(devs[i], port, sizeof(port));
+ monitor_printf(mon, " Bus %d, Addr %d, Port %s, Speed %s Mb/s\n",
+ libusb_get_bus_number(devs[i]),
+ libusb_get_device_address(devs[i]),
+ port,
+ speed_name[libusb_get_device_speed(devs[i])]);
+ monitor_printf(mon, " Class %02x:", ddesc.bDeviceClass);
+ monitor_printf(mon, " USB device %04x:%04x",
+ ddesc.idVendor, ddesc.idProduct);
+ if (ddesc.iProduct) {
+ libusb_device_handle *handle;
+ if (libusb_open(devs[i], &handle) == 0) {
+ unsigned char name[64] = "";
+ libusb_get_string_descriptor_ascii(handle,
+ ddesc.iProduct,
+ name, sizeof(name));
+ libusb_close(handle);
+ monitor_printf(mon, ", %s", name);
+ }
+ }
+ monitor_printf(mon, "\n");
+ }
+ libusb_free_device_list(devs, 1);
+}
diff --git a/hw/usb/host-stub.c b/hw/usb/host-stub.c
new file mode 100644
index 00000000..2eaaa834
--- /dev/null
+++ b/hw/usb/host-stub.c
@@ -0,0 +1,47 @@
+/*
+ * Stub host USB redirector
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * Copyright (c) 2008 Max Krasnyansky
+ * Support for host device auto connect & disconnect
+ * Major rewrite to support fully async operation
+ *
+ * Copyright 2008 TJ <linux@tjworld.net>
+ * Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition
+ * to the legacy /proc/bus/usb USB device discovery and handling
+ *
+ * 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.
+ */
+
+#include "qemu-common.h"
+#include "ui/console.h"
+#include "hw/usb.h"
+#include "monitor/monitor.h"
+
+void hmp_info_usbhost(Monitor *mon, const QDict *qdict)
+{
+ monitor_printf(mon, "USB host devices not supported\n");
+}
+
+/* XXX: modify configure to compile the right host driver */
+USBDevice *usb_host_device_open(USBBus *bus, const char *devname)
+{
+ return NULL;
+}
diff --git a/hw/usb/host.h b/hw/usb/host.h
new file mode 100644
index 00000000..048ff3b4
--- /dev/null
+++ b/hw/usb/host.h
@@ -0,0 +1,44 @@
+/*
+ * Linux host USB redirector
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * Copyright (c) 2008 Max Krasnyansky
+ * Support for host device auto connect & disconnect
+ * Major rewrite to support fully async operation
+ *
+ * Copyright 2008 TJ <linux@tjworld.net>
+ * Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition
+ * to the legacy /proc/bus/usb USB device discovery and handling
+ *
+ * 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.
+ */
+
+#ifndef QEMU_USB_HOST_H
+#define QEMU_USB_HOST_H
+
+struct USBAutoFilter {
+ uint32_t bus_num;
+ uint32_t addr;
+ char *port;
+ uint32_t vendor_id;
+ uint32_t product_id;
+};
+
+#endif /* QEMU_USB_HOST_H */
diff --git a/hw/usb/libhw.c b/hw/usb/libhw.c
new file mode 100644
index 00000000..8df11c46
--- /dev/null
+++ b/hw/usb/libhw.c
@@ -0,0 +1,70 @@
+/*
+ * QEMU USB emulation, libhw bits.
+ *
+ * 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.
+ */
+#include "qemu-common.h"
+#include "hw/hw.h"
+#include "hw/usb.h"
+#include "sysemu/dma.h"
+
+int usb_packet_map(USBPacket *p, QEMUSGList *sgl)
+{
+ DMADirection dir = (p->pid == USB_TOKEN_IN) ?
+ DMA_DIRECTION_FROM_DEVICE : DMA_DIRECTION_TO_DEVICE;
+ void *mem;
+ int i;
+
+ for (i = 0; i < sgl->nsg; i++) {
+ dma_addr_t base = sgl->sg[i].base;
+ dma_addr_t len = sgl->sg[i].len;
+
+ while (len) {
+ dma_addr_t xlen = len;
+ mem = dma_memory_map(sgl->as, base, &xlen, dir);
+ if (!mem) {
+ goto err;
+ }
+ if (xlen > len) {
+ xlen = len;
+ }
+ qemu_iovec_add(&p->iov, mem, xlen);
+ len -= xlen;
+ base += xlen;
+ }
+ }
+ return 0;
+
+err:
+ usb_packet_unmap(p, sgl);
+ return -1;
+}
+
+void usb_packet_unmap(USBPacket *p, QEMUSGList *sgl)
+{
+ DMADirection dir = (p->pid == USB_TOKEN_IN) ?
+ DMA_DIRECTION_FROM_DEVICE : DMA_DIRECTION_TO_DEVICE;
+ int i;
+
+ for (i = 0; i < p->iov.niov; i++) {
+ dma_memory_unmap(sgl->as, p->iov.iov[i].iov_base,
+ p->iov.iov[i].iov_len, dir,
+ p->iov.iov[i].iov_len);
+ }
+}
diff --git a/hw/usb/quirks-ftdi-ids.h b/hw/usb/quirks-ftdi-ids.h
new file mode 100644
index 00000000..57c12ef6
--- /dev/null
+++ b/hw/usb/quirks-ftdi-ids.h
@@ -0,0 +1,1255 @@
+/*
+ * vendor/product IDs (VID/PID) of devices using FTDI USB serial converters.
+ * Please keep numerically sorted within individual areas, thanks!
+ *
+ * Philipp Gühring - pg@futureware.at - added the Device ID of the USB relais
+ * from Rudolf Gugler
+ *
+ */
+
+
+/**********************************/
+/***** devices using FTDI VID *****/
+/**********************************/
+
+
+#define FTDI_VID 0x0403 /* Vendor Id */
+
+
+/*** "original" FTDI device PIDs ***/
+
+#define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */
+#define FTDI_8U232AM_ALT_PID 0x6006 /* FTDI's alternate PID for above */
+#define FTDI_8U2232C_PID 0x6010 /* Dual channel device */
+#define FTDI_4232H_PID 0x6011 /* Quad channel hi-speed device */
+#define FTDI_232H_PID 0x6014 /* Single channel hi-speed device */
+#define FTDI_FTX_PID 0x6015 /* FT-X series (FT201X, FT230X, FT231X, etc) */
+#define FTDI_SIO_PID 0x8372 /* Product Id SIO application of 8U100AX */
+#define FTDI_232RL_PID 0xFBFA /* Product ID for FT232RL */
+
+
+/*** third-party PIDs (using FTDI_VID) ***/
+
+#define FTDI_LUMEL_PD12_PID 0x6002
+
+/*
+ * Marvell OpenRD Base, Client
+ * http://www.open-rd.org
+ * OpenRD Base, Client use VID 0x0403
+ */
+#define MARVELL_OPENRD_PID 0x9e90
+
+/* www.candapter.com Ewert Energy Systems CANdapter device */
+#define FTDI_CANDAPTER_PID 0x9F80 /* Product Id */
+
+/*
+ * Texas Instruments XDS100v2 JTAG / BeagleBone A3
+ * http://processors.wiki.ti.com/index.php/XDS100
+ * http://beagleboard.org/bone
+ */
+#define TI_XDS100V2_PID 0xa6d0
+
+#define FTDI_NXTCAM_PID 0xABB8 /* NXTCam for Mindstorms NXT */
+
+/* US Interface Navigator (http://www.usinterface.com/) */
+#define FTDI_USINT_CAT_PID 0xb810 /* Navigator CAT and 2nd PTT lines */
+#define FTDI_USINT_WKEY_PID 0xb811 /* Navigator WKEY and FSK lines */
+#define FTDI_USINT_RS232_PID 0xb812 /* Navigator RS232 and CONFIG lines */
+
+/* OOCDlink by Joern Kaipf <joernk@web.de>
+ * (http://www.joernonline.de/) */
+#define FTDI_OOCDLINK_PID 0xbaf8 /* Amontec JTAGkey */
+
+/* Luminary Micro Stellaris Boards, VID = FTDI_VID */
+/* FTDI 2332C Dual channel device, side A=245 FIFO (JTAG), Side B=RS232 UART */
+#define LMI_LM3S_DEVEL_BOARD_PID 0xbcd8
+#define LMI_LM3S_EVAL_BOARD_PID 0xbcd9
+#define LMI_LM3S_ICDI_BOARD_PID 0xbcda
+
+#define FTDI_TURTELIZER_PID 0xBDC8 /* JTAG/RS-232 adapter by egnite GmbH */
+
+/* OpenDCC (www.opendcc.de) product id */
+#define FTDI_OPENDCC_PID 0xBFD8
+#define FTDI_OPENDCC_SNIFFER_PID 0xBFD9
+#define FTDI_OPENDCC_THROTTLE_PID 0xBFDA
+#define FTDI_OPENDCC_GATEWAY_PID 0xBFDB
+#define FTDI_OPENDCC_GBM_PID 0xBFDC
+
+/* NZR SEM 16+ USB (http://www.nzr.de) */
+#define FTDI_NZR_SEM_USB_PID 0xC1E0 /* NZR SEM-LOG16+ */
+
+/*
+ * RR-CirKits LocoBuffer USB (http://www.rr-cirkits.com)
+ */
+#define FTDI_RRCIRKITS_LOCOBUFFER_PID 0xc7d0 /* LocoBuffer USB */
+
+/* DMX4ALL DMX Interfaces */
+#define FTDI_DMX4ALL 0xC850
+
+/*
+ * ASK.fr devices
+ */
+#define FTDI_ASK_RDR400_PID 0xC991 /* ASK RDR 400 series card reader */
+
+/* www.starting-point-systems.com µChameleon device */
+#define FTDI_MICRO_CHAMELEON_PID 0xCAA0 /* Product Id */
+
+/*
+ * Tactrix OpenPort (ECU) devices.
+ * OpenPort 1.3M submitted by Donour Sizemore.
+ * OpenPort 1.3S and 1.3U submitted by Ian Abbott.
+ */
+#define FTDI_TACTRIX_OPENPORT_13M_PID 0xCC48 /* OpenPort 1.3 Mitsubishi */
+#define FTDI_TACTRIX_OPENPORT_13S_PID 0xCC49 /* OpenPort 1.3 Subaru */
+#define FTDI_TACTRIX_OPENPORT_13U_PID 0xCC4A /* OpenPort 1.3 Universal */
+
+#define FTDI_DISTORTEC_JTAG_LOCK_PICK_PID 0xCFF8
+
+/* SCS HF Radio Modems PID's (http://www.scs-ptc.com) */
+/* the VID is the standard ftdi vid (FTDI_VID) */
+#define FTDI_SCS_DEVICE_0_PID 0xD010 /* SCS PTC-IIusb */
+#define FTDI_SCS_DEVICE_1_PID 0xD011 /* SCS Tracker / DSP TNC */
+#define FTDI_SCS_DEVICE_2_PID 0xD012
+#define FTDI_SCS_DEVICE_3_PID 0xD013
+#define FTDI_SCS_DEVICE_4_PID 0xD014
+#define FTDI_SCS_DEVICE_5_PID 0xD015
+#define FTDI_SCS_DEVICE_6_PID 0xD016
+#define FTDI_SCS_DEVICE_7_PID 0xD017
+
+/* iPlus device */
+#define FTDI_IPLUS_PID 0xD070 /* Product Id */
+#define FTDI_IPLUS2_PID 0xD071 /* Product Id */
+
+/*
+ * Gamma Scout (http://gamma-scout.com/). Submitted by rsc@runtux.com.
+ */
+#define FTDI_GAMMA_SCOUT_PID 0xD678 /* Gamma Scout online */
+
+/* Propox devices */
+#define FTDI_PROPOX_JTAGCABLEII_PID 0xD738
+#define FTDI_PROPOX_ISPCABLEIII_PID 0xD739
+
+/* Lenz LI-USB Computer Interface. */
+#define FTDI_LENZ_LIUSB_PID 0xD780
+
+/* Vardaan Enterprises Serial Interface VEUSB422R3 */
+#define FTDI_VARDAAN_PID 0xF070
+
+/*
+ * Xsens Technologies BV products (http://www.xsens.com).
+ */
+#define XSENS_CONVERTER_0_PID 0xD388
+#define XSENS_CONVERTER_1_PID 0xD389
+#define XSENS_CONVERTER_2_PID 0xD38A
+#define XSENS_CONVERTER_3_PID 0xD38B
+#define XSENS_CONVERTER_4_PID 0xD38C
+#define XSENS_CONVERTER_5_PID 0xD38D
+#define XSENS_CONVERTER_6_PID 0xD38E
+#define XSENS_CONVERTER_7_PID 0xD38F
+
+/*
+ * NDI (www.ndigital.com) product ids
+ */
+#define FTDI_NDI_HUC_PID 0xDA70 /* NDI Host USB Converter */
+#define FTDI_NDI_SPECTRA_SCU_PID 0xDA71 /* NDI Spectra SCU */
+#define FTDI_NDI_FUTURE_2_PID 0xDA72 /* NDI future device #2 */
+#define FTDI_NDI_FUTURE_3_PID 0xDA73 /* NDI future device #3 */
+#define FTDI_NDI_AURORA_SCU_PID 0xDA74 /* NDI Aurora SCU */
+
+/*
+ * ChamSys Limited (www.chamsys.co.uk) USB wing/interface product IDs
+ */
+#define FTDI_CHAMSYS_24_MASTER_WING_PID 0xDAF8
+#define FTDI_CHAMSYS_PC_WING_PID 0xDAF9
+#define FTDI_CHAMSYS_USB_DMX_PID 0xDAFA
+#define FTDI_CHAMSYS_MIDI_TIMECODE_PID 0xDAFB
+#define FTDI_CHAMSYS_MINI_WING_PID 0xDAFC
+#define FTDI_CHAMSYS_MAXI_WING_PID 0xDAFD
+#define FTDI_CHAMSYS_MEDIA_WING_PID 0xDAFE
+#define FTDI_CHAMSYS_WING_PID 0xDAFF
+
+/*
+ * Westrex International devices submitted by Cory Lee
+ */
+#define FTDI_WESTREX_MODEL_777_PID 0xDC00 /* Model 777 */
+#define FTDI_WESTREX_MODEL_8900F_PID 0xDC01 /* Model 8900F */
+
+/*
+ * ACG Identification Technologies GmbH products (http://www.acg.de/).
+ * Submitted by anton -at- goto10 -dot- org.
+ */
+#define FTDI_ACG_HFDUAL_PID 0xDD20 /* HF Dual ISO Reader (RFID) */
+
+/*
+ * Definitions for Artemis astronomical USB based cameras
+ * Check it at http://www.artemisccd.co.uk/
+ */
+#define FTDI_ARTEMIS_PID 0xDF28 /* All Artemis Cameras */
+
+/*
+ * Definitions for ATIK Instruments astronomical USB based cameras
+ * Check it at http://www.atik-instruments.com/
+ */
+#define FTDI_ATIK_ATK16_PID 0xDF30 /* ATIK ATK-16 Grayscale Camera */
+#define FTDI_ATIK_ATK16C_PID 0xDF32 /* ATIK ATK-16C Colour Camera */
+#define FTDI_ATIK_ATK16HR_PID 0xDF31 /* ATIK ATK-16HR Grayscale Camera */
+#define FTDI_ATIK_ATK16HRC_PID 0xDF33 /* ATIK ATK-16HRC Colour Camera */
+#define FTDI_ATIK_ATK16IC_PID 0xDF35 /* ATIK ATK-16IC Grayscale Camera */
+
+/*
+ * Yost Engineering, Inc. products (www.yostengineering.com).
+ * PID 0xE050 submitted by Aaron Prose.
+ */
+#define FTDI_YEI_SERVOCENTER31_PID 0xE050 /* YEI ServoCenter3.1 USB */
+
+/*
+ * ELV USB devices submitted by Christian Abt of ELV (www.elv.de).
+ * All of these devices use FTDI's vendor ID (0x0403).
+ * Further IDs taken from ELV Windows .inf file.
+ *
+ * The previously included PID for the UO 100 module was incorrect.
+ * In fact, that PID was for ELV's UR 100 USB-RS232 converter (0xFB58).
+ *
+ * Armin Laeuger originally sent the PID for the UM 100 module.
+ */
+#define FTDI_ELV_USR_PID 0xE000 /* ELV Universal-Sound-Recorder */
+#define FTDI_ELV_MSM1_PID 0xE001 /* ELV Mini-Sound-Modul */
+#define FTDI_ELV_KL100_PID 0xE002 /* ELV Kfz-Leistungsmesser KL 100 */
+#define FTDI_ELV_WS550_PID 0xE004 /* WS 550 */
+#define FTDI_ELV_EC3000_PID 0xE006 /* ENERGY CONTROL 3000 USB */
+#define FTDI_ELV_WS888_PID 0xE008 /* WS 888 */
+#define FTDI_ELV_TWS550_PID 0xE009 /* Technoline WS 550 */
+#define FTDI_ELV_FEM_PID 0xE00A /* Funk Energie Monitor */
+#define FTDI_ELV_FHZ1300PC_PID 0xE0E8 /* FHZ 1300 PC */
+#define FTDI_ELV_WS500_PID 0xE0E9 /* PC-Wetterstation (WS 500) */
+#define FTDI_ELV_HS485_PID 0xE0EA /* USB to RS-485 adapter */
+#define FTDI_ELV_UMS100_PID 0xE0EB /* ELV USB Master-Slave Schaltsteckdose UMS 100 */
+#define FTDI_ELV_TFD128_PID 0xE0EC /* ELV Temperatur-Feuchte-Datenlogger TFD 128 */
+#define FTDI_ELV_FM3RX_PID 0xE0ED /* ELV Messwertuebertragung FM3 RX */
+#define FTDI_ELV_WS777_PID 0xE0EE /* Conrad WS 777 */
+#define FTDI_ELV_EM1010PC_PID 0xE0EF /* Energy monitor EM 1010 PC */
+#define FTDI_ELV_CSI8_PID 0xE0F0 /* Computer-Schalt-Interface (CSI 8) */
+#define FTDI_ELV_EM1000DL_PID 0xE0F1 /* PC-Datenlogger fuer Energiemonitor (EM 1000 DL) */
+#define FTDI_ELV_PCK100_PID 0xE0F2 /* PC-Kabeltester (PCK 100) */
+#define FTDI_ELV_RFP500_PID 0xE0F3 /* HF-Leistungsmesser (RFP 500) */
+#define FTDI_ELV_FS20SIG_PID 0xE0F4 /* Signalgeber (FS 20 SIG) */
+#define FTDI_ELV_UTP8_PID 0xE0F5 /* ELV UTP 8 */
+#define FTDI_ELV_WS300PC_PID 0xE0F6 /* PC-Wetterstation (WS 300 PC) */
+#define FTDI_ELV_WS444PC_PID 0xE0F7 /* Conrad WS 444 PC */
+#define FTDI_PHI_FISCO_PID 0xE40B /* PHI Fisco USB to Serial cable */
+#define FTDI_ELV_UAD8_PID 0xF068 /* USB-AD-Wandler (UAD 8) */
+#define FTDI_ELV_UDA7_PID 0xF069 /* USB-DA-Wandler (UDA 7) */
+#define FTDI_ELV_USI2_PID 0xF06A /* USB-Schrittmotoren-Interface (USI 2) */
+#define FTDI_ELV_T1100_PID 0xF06B /* Thermometer (T 1100) */
+#define FTDI_ELV_PCD200_PID 0xF06C /* PC-Datenlogger (PCD 200) */
+#define FTDI_ELV_ULA200_PID 0xF06D /* USB-LCD-Ansteuerung (ULA 200) */
+#define FTDI_ELV_ALC8500_PID 0xF06E /* ALC 8500 Expert */
+#define FTDI_ELV_FHZ1000PC_PID 0xF06F /* FHZ 1000 PC */
+#define FTDI_ELV_UR100_PID 0xFB58 /* USB-RS232-Umsetzer (UR 100) */
+#define FTDI_ELV_UM100_PID 0xFB5A /* USB-Modul UM 100 */
+#define FTDI_ELV_UO100_PID 0xFB5B /* USB-Modul UO 100 */
+/* Additional ELV PIDs that default to using the FTDI D2XX drivers on
+ * MS Windows, rather than the FTDI Virtual Com Port drivers.
+ * Maybe these will be easier to use with the libftdi/libusb user-space
+ * drivers, or possibly the Comedi drivers in some cases. */
+#define FTDI_ELV_CLI7000_PID 0xFB59 /* Computer-Light-Interface (CLI 7000) */
+#define FTDI_ELV_PPS7330_PID 0xFB5C /* Processor-Power-Supply (PPS 7330) */
+#define FTDI_ELV_TFM100_PID 0xFB5D /* Temperatur-Feuchte-Messgeraet (TFM 100) */
+#define FTDI_ELV_UDF77_PID 0xFB5E /* USB DCF Funkuhr (UDF 77) */
+#define FTDI_ELV_UIO88_PID 0xFB5F /* USB-I/O Interface (UIO 88) */
+
+/*
+ * EVER Eco Pro UPS (http://www.ever.com.pl/)
+ */
+
+#define EVER_ECO_PRO_CDS 0xe520 /* RS-232 converter */
+
+/*
+ * Active Robots product ids.
+ */
+#define FTDI_ACTIVE_ROBOTS_PID 0xE548 /* USB comms board */
+
+/* Pyramid Computer GmbH */
+#define FTDI_PYRAMID_PID 0xE6C8 /* Pyramid Appliance Display */
+
+/* www.elsterelectricity.com Elster Unicom III Optical Probe */
+#define FTDI_ELSTER_UNICOM_PID 0xE700 /* Product Id */
+
+/*
+ * Gude Analog- und Digitalsysteme GmbH
+ */
+#define FTDI_GUDEADS_E808_PID 0xE808
+#define FTDI_GUDEADS_E809_PID 0xE809
+#define FTDI_GUDEADS_E80A_PID 0xE80A
+#define FTDI_GUDEADS_E80B_PID 0xE80B
+#define FTDI_GUDEADS_E80C_PID 0xE80C
+#define FTDI_GUDEADS_E80D_PID 0xE80D
+#define FTDI_GUDEADS_E80E_PID 0xE80E
+#define FTDI_GUDEADS_E80F_PID 0xE80F
+#define FTDI_GUDEADS_E888_PID 0xE888 /* Expert ISDN Control USB */
+#define FTDI_GUDEADS_E889_PID 0xE889 /* USB RS-232 OptoBridge */
+#define FTDI_GUDEADS_E88A_PID 0xE88A
+#define FTDI_GUDEADS_E88B_PID 0xE88B
+#define FTDI_GUDEADS_E88C_PID 0xE88C
+#define FTDI_GUDEADS_E88D_PID 0xE88D
+#define FTDI_GUDEADS_E88E_PID 0xE88E
+#define FTDI_GUDEADS_E88F_PID 0xE88F
+
+/*
+ * Eclo (http://www.eclo.pt/) product IDs.
+ * PID 0xEA90 submitted by Martin Grill.
+ */
+#define FTDI_ECLO_COM_1WIRE_PID 0xEA90 /* COM to 1-Wire USB adaptor */
+
+/* TNC-X USB-to-packet-radio adapter, versions prior to 3.0 (DLP module) */
+#define FTDI_TNC_X_PID 0xEBE0
+
+/*
+ * Teratronik product ids.
+ * Submitted by O. Wölfelschneider.
+ */
+#define FTDI_TERATRONIK_VCP_PID 0xEC88 /* Teratronik device (preferring VCP driver on windows) */
+#define FTDI_TERATRONIK_D2XX_PID 0xEC89 /* Teratronik device (preferring D2XX driver on windows) */
+
+/* Rig Expert Ukraine devices */
+#define FTDI_REU_TINY_PID 0xED22 /* RigExpert Tiny */
+
+/*
+ * Hameg HO820 and HO870 interface (using VID 0x0403)
+ */
+#define HAMEG_HO820_PID 0xed74
+#define HAMEG_HO730_PID 0xed73
+#define HAMEG_HO720_PID 0xed72
+#define HAMEG_HO870_PID 0xed71
+
+/*
+ * MaxStream devices www.maxstream.net
+ */
+#define FTDI_MAXSTREAM_PID 0xEE18 /* Xbee PKG-U Module */
+
+/*
+ * microHAM product IDs (http://www.microham.com).
+ * Submitted by Justin Burket (KL1RL) <zorton@jtan.com>
+ * and Mike Studer (K6EEP) <k6eep@hamsoftware.org>.
+ * Ian Abbott <abbotti@mev.co.uk> added a few more from the driver INF file.
+ */
+#define FTDI_MHAM_KW_PID 0xEEE8 /* USB-KW interface */
+#define FTDI_MHAM_YS_PID 0xEEE9 /* USB-YS interface */
+#define FTDI_MHAM_Y6_PID 0xEEEA /* USB-Y6 interface */
+#define FTDI_MHAM_Y8_PID 0xEEEB /* USB-Y8 interface */
+#define FTDI_MHAM_IC_PID 0xEEEC /* USB-IC interface */
+#define FTDI_MHAM_DB9_PID 0xEEED /* USB-DB9 interface */
+#define FTDI_MHAM_RS232_PID 0xEEEE /* USB-RS232 interface */
+#define FTDI_MHAM_Y9_PID 0xEEEF /* USB-Y9 interface */
+
+/* Domintell products http://www.domintell.com */
+#define FTDI_DOMINTELL_DGQG_PID 0xEF50 /* Master */
+#define FTDI_DOMINTELL_DUSB_PID 0xEF51 /* DUSB01 module */
+
+/*
+ * The following are the values for the Perle Systems
+ * UltraPort USB serial converters
+ */
+#define FTDI_PERLE_ULTRAPORT_PID 0xF0C0 /* Perle UltraPort Product Id */
+
+/* Sprog II (Andrew Crosland's SprogII DCC interface) */
+#define FTDI_SPROG_II 0xF0C8
+
+/* an infrared receiver for user access control with IR tags */
+#define FTDI_PIEGROUP_PID 0xF208 /* Product Id */
+
+/* ACT Solutions HomePro ZWave interface
+ (http://www.act-solutions.com/HomePro-Product-Matrix.html) */
+#define FTDI_ACTZWAVE_PID 0xF2D0
+
+/*
+ * 4N-GALAXY.DE PIDs for CAN-USB, USB-RS232, USB-RS422, USB-RS485,
+ * USB-TTY aktiv, USB-TTY passiv. Some PIDs are used by several devices
+ * and I'm not entirely sure which are used by which.
+ */
+#define FTDI_4N_GALAXY_DE_1_PID 0xF3C0
+#define FTDI_4N_GALAXY_DE_2_PID 0xF3C1
+#define FTDI_4N_GALAXY_DE_3_PID 0xF3C2
+
+/*
+ * Linx Technologies product ids
+ */
+#define LINX_SDMUSBQSS_PID 0xF448 /* Linx SDM-USB-QS-S */
+#define LINX_MASTERDEVEL2_PID 0xF449 /* Linx Master Development 2.0 */
+#define LINX_FUTURE_0_PID 0xF44A /* Linx future device */
+#define LINX_FUTURE_1_PID 0xF44B /* Linx future device */
+#define LINX_FUTURE_2_PID 0xF44C /* Linx future device */
+
+/*
+ * Oceanic product ids
+ */
+#define FTDI_OCEANIC_PID 0xF460 /* Oceanic dive instrument */
+
+/*
+ * SUUNTO product ids
+ */
+#define FTDI_SUUNTO_SPORTS_PID 0xF680 /* Suunto Sports instrument */
+
+/* USB-UIRT - An infrared receiver and transmitter using the 8U232AM chip */
+/* http://www.usbuirt.com/ */
+#define FTDI_USB_UIRT_PID 0xF850 /* Product Id */
+
+/* CCS Inc. ICDU/ICDU40 product ID -
+ * the FT232BM is used in an in-circuit-debugger unit for PIC16's/PIC18's */
+#define FTDI_CCSICDU20_0_PID 0xF9D0
+#define FTDI_CCSICDU40_1_PID 0xF9D1
+#define FTDI_CCSMACHX_2_PID 0xF9D2
+#define FTDI_CCSLOAD_N_GO_3_PID 0xF9D3
+#define FTDI_CCSICDU64_4_PID 0xF9D4
+#define FTDI_CCSPRIME8_5_PID 0xF9D5
+
+/*
+ * The following are the values for the Matrix Orbital LCD displays,
+ * which are the FT232BM ( similar to the 8U232AM )
+ */
+#define FTDI_MTXORB_0_PID 0xFA00 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_1_PID 0xFA01 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_2_PID 0xFA02 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_3_PID 0xFA03 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_4_PID 0xFA04 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_5_PID 0xFA05 /* Matrix Orbital Product Id */
+#define FTDI_MTXORB_6_PID 0xFA06 /* Matrix Orbital Product Id */
+
+/*
+ * Home Electronics (www.home-electro.com) USB gadgets
+ */
+#define FTDI_HE_TIRA1_PID 0xFA78 /* Tira-1 IR transceiver */
+
+/* Inside Accesso contactless reader (http://www.insidecontactless.com/) */
+#define INSIDE_ACCESSO 0xFAD0
+
+/*
+ * ThorLabs USB motor drivers
+ */
+#define FTDI_THORLABS_PID 0xfaf0 /* ThorLabs USB motor drivers */
+
+/*
+ * Protego product ids
+ */
+#define PROTEGO_SPECIAL_1 0xFC70 /* special/unknown device */
+#define PROTEGO_R2X0 0xFC71 /* R200-USB TRNG unit (R210, R220, and R230) */
+#define PROTEGO_SPECIAL_3 0xFC72 /* special/unknown device */
+#define PROTEGO_SPECIAL_4 0xFC73 /* special/unknown device */
+
+/*
+ * Sony Ericsson product ids
+ */
+#define FTDI_DSS20_PID 0xFC82 /* DSS-20 Sync Station for Sony Ericsson P800 */
+#define FTDI_URBAN_0_PID 0xFC8A /* Sony Ericsson Urban, uart #0 */
+#define FTDI_URBAN_1_PID 0xFC8B /* Sony Ericsson Urban, uart #1 */
+
+/* www.irtrans.de device */
+#define FTDI_IRTRANS_PID 0xFC60 /* Product Id */
+
+/*
+ * RM Michaelides CANview USB (http://www.rmcan.com) (FTDI_VID)
+ * CAN fieldbus interface adapter, added by port GmbH www.port.de)
+ * Ian Abbott changed the macro names for consistency.
+ */
+#define FTDI_RM_CANVIEW_PID 0xfd60 /* Product Id */
+/* www.thoughttechnology.com/ TT-USB provide with procomp use ftdi_sio */
+#define FTDI_TTUSB_PID 0xFF20 /* Product Id */
+
+#define FTDI_USBX_707_PID 0xF857 /* ADSTech IR Blaster USBX-707 (FTDI_VID) */
+
+#define FTDI_RELAIS_PID 0xFA10 /* Relais device from Rudolf Gugler */
+
+/*
+ * PCDJ use ftdi based dj-controllers. The following PID is
+ * for their DAC-2 device http://www.pcdjhardware.com/DAC2.asp
+ * (the VID is the standard ftdi vid (FTDI_VID), PID sent by Wouter Paesen)
+ */
+#define FTDI_PCDJ_DAC2_PID 0xFA88
+
+#define FTDI_R2000KU_TRUE_RNG 0xFB80 /* R2000KU TRUE RNG (FTDI_VID) */
+
+/*
+ * DIEBOLD BCS SE923 (FTDI_VID)
+ */
+#define DIEBOLD_BCS_SE923_PID 0xfb99
+
+/* www.crystalfontz.com devices
+ * - thanx for providing free devices for evaluation !
+ * they use the ftdi chipset for the USB interface
+ * and the vendor id is the same
+ */
+#define FTDI_XF_632_PID 0xFC08 /* 632: 16x2 Character Display */
+#define FTDI_XF_634_PID 0xFC09 /* 634: 20x4 Character Display */
+#define FTDI_XF_547_PID 0xFC0A /* 547: Two line Display */
+#define FTDI_XF_633_PID 0xFC0B /* 633: 16x2 Character Display with Keys */
+#define FTDI_XF_631_PID 0xFC0C /* 631: 20x2 Character Display */
+#define FTDI_XF_635_PID 0xFC0D /* 635: 20x4 Character Display */
+#define FTDI_XF_640_PID 0xFC0E /* 640: Two line Display */
+#define FTDI_XF_642_PID 0xFC0F /* 642: Two line Display */
+
+/*
+ * Video Networks Limited / Homechoice in the UK use an ftdi-based device
+ * for their 1Mb broadband internet service. The following PID is exhibited
+ * by the usb device supplied (the VID is the standard ftdi vid (FTDI_VID)
+ */
+#define FTDI_VNHCPCUSB_D_PID 0xfe38 /* Product Id */
+
+/* AlphaMicro Components AMC-232USB01 device (FTDI_VID) */
+#define FTDI_AMC232_PID 0xFF00 /* Product Id */
+
+/*
+ * IBS elektronik product ids (FTDI_VID)
+ * Submitted by Thomas Schleusener
+ */
+#define FTDI_IBS_US485_PID 0xff38 /* IBS US485 (USB<-->RS422/485 interface) */
+#define FTDI_IBS_PICPRO_PID 0xff39 /* IBS PIC-Programmer */
+#define FTDI_IBS_PCMCIA_PID 0xff3a /* IBS Card reader for PCMCIA SRAM-cards */
+#define FTDI_IBS_PK1_PID 0xff3b /* IBS PK1 - Particel counter */
+#define FTDI_IBS_RS232MON_PID 0xff3c /* IBS RS232 - Monitor */
+#define FTDI_IBS_APP70_PID 0xff3d /* APP 70 (dust monitoring system) */
+#define FTDI_IBS_PEDO_PID 0xff3e /* IBS PEDO-Modem (RF modem 868.35 MHz) */
+#define FTDI_IBS_PROD_PID 0xff3f /* future device */
+/* www.canusb.com Lawicel CANUSB device (FTDI_VID) */
+#define FTDI_CANUSB_PID 0xFFA8 /* Product Id */
+
+/*
+ * TavIR AVR product ids (FTDI_VID)
+ */
+#define FTDI_TAVIR_STK500_PID 0xFA33 /* STK500 AVR programmer */
+
+/*
+ * TIAO product ids (FTDI_VID)
+ * http://www.tiaowiki.com/w/Main_Page
+ */
+#define FTDI_TIAO_UMPA_PID 0x8a98 /* TIAO/DIYGADGET USB Multi-Protocol Adapter */
+
+
+/********************************/
+/** third-party VID/PID combos **/
+/********************************/
+
+
+
+/*
+ * Atmel STK541
+ */
+#define ATMEL_VID 0x03eb /* Vendor ID */
+#define STK541_PID 0x2109 /* Zigbee Controller */
+
+/*
+ * Blackfin gnICE JTAG
+ * http://docs.blackfin.uclinux.org/doku.php?id=hw:jtag:gnice
+ */
+#define ADI_VID 0x0456
+#define ADI_GNICE_PID 0xF000
+#define ADI_GNICEPLUS_PID 0xF001
+
+/*
+ * Microchip Technology, Inc.
+ *
+ * MICROCHIP_VID (0x04D8) and MICROCHIP_USB_BOARD_PID (0x000A) are
+ * used by single function CDC ACM class based firmware demo
+ * applications. The VID/PID has also been used in firmware
+ * emulating FTDI serial chips by:
+ * Hornby Elite - Digital Command Control Console
+ * http://www.hornby.com/hornby-dcc/controllers/
+ */
+#define MICROCHIP_VID 0x04D8
+#define MICROCHIP_USB_BOARD_PID 0x000A /* CDC RS-232 Emulation Demo */
+
+/*
+ * RATOC REX-USB60F
+ */
+#define RATOC_VENDOR_ID 0x0584
+#define RATOC_PRODUCT_ID_USB60F 0xb020
+
+/*
+ * Acton Research Corp.
+ */
+#define ACTON_VID 0x0647 /* Vendor ID */
+#define ACTON_SPECTRAPRO_PID 0x0100
+
+/*
+ * Contec products (http://www.contec.com)
+ * Submitted by Daniel Sangorrin
+ */
+#define CONTEC_VID 0x06CE /* Vendor ID */
+#define CONTEC_COM1USBH_PID 0x8311 /* COM-1(USB)H */
+
+/*
+ * Definitions for B&B Electronics products.
+ */
+#define BANDB_VID 0x0856 /* B&B Electronics Vendor ID */
+#define BANDB_USOTL4_PID 0xAC01 /* USOTL4 Isolated RS-485 Converter */
+#define BANDB_USTL4_PID 0xAC02 /* USTL4 RS-485 Converter */
+#define BANDB_USO9ML2_PID 0xAC03 /* USO9ML2 Isolated RS-232 Converter */
+#define BANDB_USOPTL4_PID 0xAC11
+#define BANDB_USPTL4_PID 0xAC12
+#define BANDB_USO9ML2DR_2_PID 0xAC16
+#define BANDB_USO9ML2DR_PID 0xAC17
+#define BANDB_USOPTL4DR2_PID 0xAC18 /* USOPTL4R-2 2-port Isolated RS-232 Converter */
+#define BANDB_USOPTL4DR_PID 0xAC19
+#define BANDB_485USB9F_2W_PID 0xAC25
+#define BANDB_485USB9F_4W_PID 0xAC26
+#define BANDB_232USB9M_PID 0xAC27
+#define BANDB_485USBTB_2W_PID 0xAC33
+#define BANDB_485USBTB_4W_PID 0xAC34
+#define BANDB_TTL5USB9M_PID 0xAC49
+#define BANDB_TTL3USB9M_PID 0xAC50
+#define BANDB_ZZ_PROG1_USB_PID 0xBA02
+
+/*
+ * Intrepid Control Systems (http://www.intrepidcs.com/) ValueCAN and NeoVI
+ */
+#define INTREPID_VID 0x093C
+#define INTREPID_VALUECAN_PID 0x0601
+#define INTREPID_NEOVI_PID 0x0701
+
+/*
+ * Definitions for ID TECH (www.idt-net.com) devices
+ */
+#define IDTECH_VID 0x0ACD /* ID TECH Vendor ID */
+#define IDTECH_IDT1221U_PID 0x0300 /* IDT1221U USB to RS-232 adapter */
+
+/*
+ * Definitions for Omnidirectional Control Technology, Inc. devices
+ */
+#define OCT_VID 0x0B39 /* OCT vendor ID */
+/* Note: OCT US101 is also rebadged as Dick Smith Electronics (NZ) XH6381 */
+/* Also rebadged as Dick Smith Electronics (Aus) XH6451 */
+/* Also rebadged as SIIG Inc. model US2308 hardware version 1 */
+#define OCT_DK201_PID 0x0103 /* OCT DK201 USB docking station */
+#define OCT_US101_PID 0x0421 /* OCT US101 USB to RS-232 */
+
+/*
+ * Definitions for Icom Inc. devices
+ */
+#define ICOM_VID 0x0C26 /* Icom vendor ID */
+/* Note: ID-1 is a communications tranceiver for HAM-radio operators */
+#define ICOM_ID_1_PID 0x0004 /* ID-1 USB to RS-232 */
+/* Note: OPC is an Optional cable to connect an Icom Tranceiver */
+#define ICOM_OPC_U_UC_PID 0x0018 /* OPC-478UC, OPC-1122U cloning cable */
+/* Note: ID-RP* devices are Icom Repeater Devices for HAM-radio */
+#define ICOM_ID_RP2C1_PID 0x0009 /* ID-RP2C Asset 1 to RS-232 */
+#define ICOM_ID_RP2C2_PID 0x000A /* ID-RP2C Asset 2 to RS-232 */
+#define ICOM_ID_RP2D_PID 0x000B /* ID-RP2D configuration port*/
+#define ICOM_ID_RP2VT_PID 0x000C /* ID-RP2V Transmit config port */
+#define ICOM_ID_RP2VR_PID 0x000D /* ID-RP2V Receive config port */
+#define ICOM_ID_RP4KVT_PID 0x0010 /* ID-RP4000V Transmit config port */
+#define ICOM_ID_RP4KVR_PID 0x0011 /* ID-RP4000V Receive config port */
+#define ICOM_ID_RP2KVT_PID 0x0012 /* ID-RP2000V Transmit config port */
+#define ICOM_ID_RP2KVR_PID 0x0013 /* ID-RP2000V Receive config port */
+
+/*
+ * GN Otometrics (http://www.otometrics.com)
+ * Submitted by Ville Sundberg.
+ */
+#define GN_OTOMETRICS_VID 0x0c33 /* Vendor ID */
+#define AURICAL_USB_PID 0x0010 /* Aurical USB Audiometer */
+
+/*
+ * The following are the values for the Sealevel SeaLINK+ adapters.
+ * (Original list sent by Tuan Hoang. Ian Abbott renamed the macros and
+ * removed some PIDs that don't seem to match any existing products.)
+ */
+#define SEALEVEL_VID 0x0c52 /* Sealevel Vendor ID */
+#define SEALEVEL_2101_PID 0x2101 /* SeaLINK+232 (2101/2105) */
+#define SEALEVEL_2102_PID 0x2102 /* SeaLINK+485 (2102) */
+#define SEALEVEL_2103_PID 0x2103 /* SeaLINK+232I (2103) */
+#define SEALEVEL_2104_PID 0x2104 /* SeaLINK+485I (2104) */
+#define SEALEVEL_2106_PID 0x9020 /* SeaLINK+422 (2106) */
+#define SEALEVEL_2201_1_PID 0x2211 /* SeaPORT+2/232 (2201) Port 1 */
+#define SEALEVEL_2201_2_PID 0x2221 /* SeaPORT+2/232 (2201) Port 2 */
+#define SEALEVEL_2202_1_PID 0x2212 /* SeaPORT+2/485 (2202) Port 1 */
+#define SEALEVEL_2202_2_PID 0x2222 /* SeaPORT+2/485 (2202) Port 2 */
+#define SEALEVEL_2203_1_PID 0x2213 /* SeaPORT+2 (2203) Port 1 */
+#define SEALEVEL_2203_2_PID 0x2223 /* SeaPORT+2 (2203) Port 2 */
+#define SEALEVEL_2401_1_PID 0x2411 /* SeaPORT+4/232 (2401) Port 1 */
+#define SEALEVEL_2401_2_PID 0x2421 /* SeaPORT+4/232 (2401) Port 2 */
+#define SEALEVEL_2401_3_PID 0x2431 /* SeaPORT+4/232 (2401) Port 3 */
+#define SEALEVEL_2401_4_PID 0x2441 /* SeaPORT+4/232 (2401) Port 4 */
+#define SEALEVEL_2402_1_PID 0x2412 /* SeaPORT+4/485 (2402) Port 1 */
+#define SEALEVEL_2402_2_PID 0x2422 /* SeaPORT+4/485 (2402) Port 2 */
+#define SEALEVEL_2402_3_PID 0x2432 /* SeaPORT+4/485 (2402) Port 3 */
+#define SEALEVEL_2402_4_PID 0x2442 /* SeaPORT+4/485 (2402) Port 4 */
+#define SEALEVEL_2403_1_PID 0x2413 /* SeaPORT+4 (2403) Port 1 */
+#define SEALEVEL_2403_2_PID 0x2423 /* SeaPORT+4 (2403) Port 2 */
+#define SEALEVEL_2403_3_PID 0x2433 /* SeaPORT+4 (2403) Port 3 */
+#define SEALEVEL_2403_4_PID 0x2443 /* SeaPORT+4 (2403) Port 4 */
+#define SEALEVEL_2801_1_PID 0X2811 /* SeaLINK+8/232 (2801) Port 1 */
+#define SEALEVEL_2801_2_PID 0X2821 /* SeaLINK+8/232 (2801) Port 2 */
+#define SEALEVEL_2801_3_PID 0X2831 /* SeaLINK+8/232 (2801) Port 3 */
+#define SEALEVEL_2801_4_PID 0X2841 /* SeaLINK+8/232 (2801) Port 4 */
+#define SEALEVEL_2801_5_PID 0X2851 /* SeaLINK+8/232 (2801) Port 5 */
+#define SEALEVEL_2801_6_PID 0X2861 /* SeaLINK+8/232 (2801) Port 6 */
+#define SEALEVEL_2801_7_PID 0X2871 /* SeaLINK+8/232 (2801) Port 7 */
+#define SEALEVEL_2801_8_PID 0X2881 /* SeaLINK+8/232 (2801) Port 8 */
+#define SEALEVEL_2802_1_PID 0X2812 /* SeaLINK+8/485 (2802) Port 1 */
+#define SEALEVEL_2802_2_PID 0X2822 /* SeaLINK+8/485 (2802) Port 2 */
+#define SEALEVEL_2802_3_PID 0X2832 /* SeaLINK+8/485 (2802) Port 3 */
+#define SEALEVEL_2802_4_PID 0X2842 /* SeaLINK+8/485 (2802) Port 4 */
+#define SEALEVEL_2802_5_PID 0X2852 /* SeaLINK+8/485 (2802) Port 5 */
+#define SEALEVEL_2802_6_PID 0X2862 /* SeaLINK+8/485 (2802) Port 6 */
+#define SEALEVEL_2802_7_PID 0X2872 /* SeaLINK+8/485 (2802) Port 7 */
+#define SEALEVEL_2802_8_PID 0X2882 /* SeaLINK+8/485 (2802) Port 8 */
+#define SEALEVEL_2803_1_PID 0X2813 /* SeaLINK+8 (2803) Port 1 */
+#define SEALEVEL_2803_2_PID 0X2823 /* SeaLINK+8 (2803) Port 2 */
+#define SEALEVEL_2803_3_PID 0X2833 /* SeaLINK+8 (2803) Port 3 */
+#define SEALEVEL_2803_4_PID 0X2843 /* SeaLINK+8 (2803) Port 4 */
+#define SEALEVEL_2803_5_PID 0X2853 /* SeaLINK+8 (2803) Port 5 */
+#define SEALEVEL_2803_6_PID 0X2863 /* SeaLINK+8 (2803) Port 6 */
+#define SEALEVEL_2803_7_PID 0X2873 /* SeaLINK+8 (2803) Port 7 */
+#define SEALEVEL_2803_8_PID 0X2883 /* SeaLINK+8 (2803) Port 8 */
+#define SEALEVEL_2803R_1_PID 0Xa02a /* SeaLINK+8 (2803-ROHS) Port 1+2 */
+#define SEALEVEL_2803R_2_PID 0Xa02b /* SeaLINK+8 (2803-ROHS) Port 3+4 */
+#define SEALEVEL_2803R_3_PID 0Xa02c /* SeaLINK+8 (2803-ROHS) Port 5+6 */
+#define SEALEVEL_2803R_4_PID 0Xa02d /* SeaLINK+8 (2803-ROHS) Port 7+8 */
+
+/*
+ * JETI SPECTROMETER SPECBOS 1201
+ * http://www.jeti.com/cms/index.php/instruments/other-instruments/specbos-2101
+ */
+#define JETI_VID 0x0c6c
+#define JETI_SPC1201_PID 0x04b2
+
+/*
+ * FTDI USB UART chips used in construction projects from the
+ * Elektor Electronics magazine (http://www.elektor.com/)
+ */
+#define ELEKTOR_VID 0x0C7D
+#define ELEKTOR_FT323R_PID 0x0005 /* RFID-Reader, issue 09-2006 */
+
+/*
+ * Posiflex inc retail equipment (http://www.posiflex.com.tw)
+ */
+#define POSIFLEX_VID 0x0d3a /* Vendor ID */
+#define POSIFLEX_PP7000_PID 0x0300 /* PP-7000II thermal printer */
+
+/*
+ * The following are the values for two KOBIL chipcard terminals.
+ */
+#define KOBIL_VID 0x0d46 /* KOBIL Vendor ID */
+#define KOBIL_CONV_B1_PID 0x2020 /* KOBIL Konverter for B1 */
+#define KOBIL_CONV_KAAN_PID 0x2021 /* KOBIL_Konverter for KAAN */
+
+#define FTDI_NF_RIC_VID 0x0DCD /* Vendor Id */
+#define FTDI_NF_RIC_PID 0x0001 /* Product Id */
+
+/*
+ * Falcom Wireless Communications GmbH
+ */
+#define FALCOM_VID 0x0F94 /* Vendor Id */
+#define FALCOM_TWIST_PID 0x0001 /* Falcom Twist USB GPRS modem */
+#define FALCOM_SAMBA_PID 0x0005 /* Falcom Samba USB GPRS modem */
+
+/* Larsen and Brusgaard AltiTrack/USBtrack */
+#define LARSENBRUSGAARD_VID 0x0FD8
+#define LB_ALTITRACK_PID 0x0001
+
+/*
+ * TTi (Thurlby Thandar Instruments)
+ */
+#define TTI_VID 0x103E /* Vendor Id */
+#define TTI_QL355P_PID 0x03E8 /* TTi QL355P power supply */
+
+/* Interbiometrics USB I/O Board */
+/* Developed for Interbiometrics by Rudolf Gugler */
+#define INTERBIOMETRICS_VID 0x1209
+#define INTERBIOMETRICS_IOBOARD_PID 0x1002
+#define INTERBIOMETRICS_MINI_IOBOARD_PID 0x1006
+
+/*
+ * Testo products (http://www.testo.com/)
+ * Submitted by Colin Leroy
+ */
+#define TESTO_VID 0x128D
+#define TESTO_USB_INTERFACE_PID 0x0001
+
+/*
+ * Mobility Electronics products.
+ */
+#define MOBILITY_VID 0x1342
+#define MOBILITY_USB_SERIAL_PID 0x0202 /* EasiDock USB 200 serial */
+
+/*
+ * FIC / OpenMoko, Inc. http://wiki.openmoko.org/wiki/Neo1973_Debug_Board_v3
+ * Submitted by Harald Welte <laforge@openmoko.org>
+ */
+#define FIC_VID 0x1457
+#define FIC_NEO1973_DEBUG_PID 0x5118
+
+/* Olimex */
+#define OLIMEX_VID 0x15BA
+#define OLIMEX_ARM_USB_OCD_PID 0x0003
+#define OLIMEX_ARM_USB_OCD_H_PID 0x002b
+
+/*
+ * Telldus Technologies
+ */
+#define TELLDUS_VID 0x1781 /* Vendor ID */
+#define TELLDUS_TELLSTICK_PID 0x0C30 /* RF control dongle 433 MHz using FT232RL */
+
+/*
+ * RT Systems programming cables for various ham radios
+ */
+#define RTSYSTEMS_VID 0x2100 /* Vendor ID */
+#define RTSYSTEMS_SERIAL_VX7_PID 0x9e52 /* Serial converter for VX-7 Radios using FT232RL */
+#define RTSYSTEMS_CT29B_PID 0x9e54 /* CT29B Radio Cable */
+#define RTSYSTEMS_RTS01_PID 0x9e57 /* USB-RTS01 Radio Cable */
+
+
+/*
+ * Physik Instrumente
+ * http://www.physikinstrumente.com/en/products/
+ */
+/* These two devices use the VID of FTDI */
+#define PI_C865_PID 0xe0a0 /* PI C-865 Piezomotor Controller */
+#define PI_C857_PID 0xe0a1 /* PI Encoder Trigger Box */
+
+#define PI_VID 0x1a72 /* Vendor ID */
+#define PI_C866_PID 0x1000 /* PI C-866 Piezomotor Controller */
+#define PI_C663_PID 0x1001 /* PI C-663 Mercury-Step */
+#define PI_C725_PID 0x1002 /* PI C-725 Piezomotor Controller */
+#define PI_E517_PID 0x1005 /* PI E-517 Digital Piezo Controller Operation Module */
+#define PI_C863_PID 0x1007 /* PI C-863 */
+#define PI_E861_PID 0x1008 /* PI E-861 Piezomotor Controller */
+#define PI_C867_PID 0x1009 /* PI C-867 Piezomotor Controller */
+#define PI_E609_PID 0x100D /* PI E-609 Digital Piezo Controller */
+#define PI_E709_PID 0x100E /* PI E-709 Digital Piezo Controller */
+#define PI_100F_PID 0x100F /* PI Digital Piezo Controller */
+#define PI_1011_PID 0x1011 /* PI Digital Piezo Controller */
+#define PI_1012_PID 0x1012 /* PI Motion Controller */
+#define PI_1013_PID 0x1013 /* PI Motion Controller */
+#define PI_1014_PID 0x1014 /* PI Device */
+#define PI_1015_PID 0x1015 /* PI Device */
+#define PI_1016_PID 0x1016 /* PI Digital Servo Module */
+
+/*
+ * Kondo Kagaku Co.Ltd.
+ * http://www.kondo-robot.com/EN
+ */
+#define KONDO_VID 0x165c
+#define KONDO_USB_SERIAL_PID 0x0002
+
+/*
+ * Bayer Ascensia Contour blood glucose meter USB-converter cable.
+ * http://winglucofacts.com/cables/
+ */
+#define BAYER_VID 0x1A79
+#define BAYER_CONTOUR_CABLE_PID 0x6001
+
+/*
+ * The following are the values for the Matrix Orbital FTDI Range
+ * Anything in this range will use an FT232RL.
+ */
+#define MTXORB_VID 0x1B3D
+#define MTXORB_FTDI_RANGE_0100_PID 0x0100
+#define MTXORB_FTDI_RANGE_0101_PID 0x0101
+#define MTXORB_FTDI_RANGE_0102_PID 0x0102
+#define MTXORB_FTDI_RANGE_0103_PID 0x0103
+#define MTXORB_FTDI_RANGE_0104_PID 0x0104
+#define MTXORB_FTDI_RANGE_0105_PID 0x0105
+#define MTXORB_FTDI_RANGE_0106_PID 0x0106
+#define MTXORB_FTDI_RANGE_0107_PID 0x0107
+#define MTXORB_FTDI_RANGE_0108_PID 0x0108
+#define MTXORB_FTDI_RANGE_0109_PID 0x0109
+#define MTXORB_FTDI_RANGE_010A_PID 0x010A
+#define MTXORB_FTDI_RANGE_010B_PID 0x010B
+#define MTXORB_FTDI_RANGE_010C_PID 0x010C
+#define MTXORB_FTDI_RANGE_010D_PID 0x010D
+#define MTXORB_FTDI_RANGE_010E_PID 0x010E
+#define MTXORB_FTDI_RANGE_010F_PID 0x010F
+#define MTXORB_FTDI_RANGE_0110_PID 0x0110
+#define MTXORB_FTDI_RANGE_0111_PID 0x0111
+#define MTXORB_FTDI_RANGE_0112_PID 0x0112
+#define MTXORB_FTDI_RANGE_0113_PID 0x0113
+#define MTXORB_FTDI_RANGE_0114_PID 0x0114
+#define MTXORB_FTDI_RANGE_0115_PID 0x0115
+#define MTXORB_FTDI_RANGE_0116_PID 0x0116
+#define MTXORB_FTDI_RANGE_0117_PID 0x0117
+#define MTXORB_FTDI_RANGE_0118_PID 0x0118
+#define MTXORB_FTDI_RANGE_0119_PID 0x0119
+#define MTXORB_FTDI_RANGE_011A_PID 0x011A
+#define MTXORB_FTDI_RANGE_011B_PID 0x011B
+#define MTXORB_FTDI_RANGE_011C_PID 0x011C
+#define MTXORB_FTDI_RANGE_011D_PID 0x011D
+#define MTXORB_FTDI_RANGE_011E_PID 0x011E
+#define MTXORB_FTDI_RANGE_011F_PID 0x011F
+#define MTXORB_FTDI_RANGE_0120_PID 0x0120
+#define MTXORB_FTDI_RANGE_0121_PID 0x0121
+#define MTXORB_FTDI_RANGE_0122_PID 0x0122
+#define MTXORB_FTDI_RANGE_0123_PID 0x0123
+#define MTXORB_FTDI_RANGE_0124_PID 0x0124
+#define MTXORB_FTDI_RANGE_0125_PID 0x0125
+#define MTXORB_FTDI_RANGE_0126_PID 0x0126
+#define MTXORB_FTDI_RANGE_0127_PID 0x0127
+#define MTXORB_FTDI_RANGE_0128_PID 0x0128
+#define MTXORB_FTDI_RANGE_0129_PID 0x0129
+#define MTXORB_FTDI_RANGE_012A_PID 0x012A
+#define MTXORB_FTDI_RANGE_012B_PID 0x012B
+#define MTXORB_FTDI_RANGE_012C_PID 0x012C
+#define MTXORB_FTDI_RANGE_012D_PID 0x012D
+#define MTXORB_FTDI_RANGE_012E_PID 0x012E
+#define MTXORB_FTDI_RANGE_012F_PID 0x012F
+#define MTXORB_FTDI_RANGE_0130_PID 0x0130
+#define MTXORB_FTDI_RANGE_0131_PID 0x0131
+#define MTXORB_FTDI_RANGE_0132_PID 0x0132
+#define MTXORB_FTDI_RANGE_0133_PID 0x0133
+#define MTXORB_FTDI_RANGE_0134_PID 0x0134
+#define MTXORB_FTDI_RANGE_0135_PID 0x0135
+#define MTXORB_FTDI_RANGE_0136_PID 0x0136
+#define MTXORB_FTDI_RANGE_0137_PID 0x0137
+#define MTXORB_FTDI_RANGE_0138_PID 0x0138
+#define MTXORB_FTDI_RANGE_0139_PID 0x0139
+#define MTXORB_FTDI_RANGE_013A_PID 0x013A
+#define MTXORB_FTDI_RANGE_013B_PID 0x013B
+#define MTXORB_FTDI_RANGE_013C_PID 0x013C
+#define MTXORB_FTDI_RANGE_013D_PID 0x013D
+#define MTXORB_FTDI_RANGE_013E_PID 0x013E
+#define MTXORB_FTDI_RANGE_013F_PID 0x013F
+#define MTXORB_FTDI_RANGE_0140_PID 0x0140
+#define MTXORB_FTDI_RANGE_0141_PID 0x0141
+#define MTXORB_FTDI_RANGE_0142_PID 0x0142
+#define MTXORB_FTDI_RANGE_0143_PID 0x0143
+#define MTXORB_FTDI_RANGE_0144_PID 0x0144
+#define MTXORB_FTDI_RANGE_0145_PID 0x0145
+#define MTXORB_FTDI_RANGE_0146_PID 0x0146
+#define MTXORB_FTDI_RANGE_0147_PID 0x0147
+#define MTXORB_FTDI_RANGE_0148_PID 0x0148
+#define MTXORB_FTDI_RANGE_0149_PID 0x0149
+#define MTXORB_FTDI_RANGE_014A_PID 0x014A
+#define MTXORB_FTDI_RANGE_014B_PID 0x014B
+#define MTXORB_FTDI_RANGE_014C_PID 0x014C
+#define MTXORB_FTDI_RANGE_014D_PID 0x014D
+#define MTXORB_FTDI_RANGE_014E_PID 0x014E
+#define MTXORB_FTDI_RANGE_014F_PID 0x014F
+#define MTXORB_FTDI_RANGE_0150_PID 0x0150
+#define MTXORB_FTDI_RANGE_0151_PID 0x0151
+#define MTXORB_FTDI_RANGE_0152_PID 0x0152
+#define MTXORB_FTDI_RANGE_0153_PID 0x0153
+#define MTXORB_FTDI_RANGE_0154_PID 0x0154
+#define MTXORB_FTDI_RANGE_0155_PID 0x0155
+#define MTXORB_FTDI_RANGE_0156_PID 0x0156
+#define MTXORB_FTDI_RANGE_0157_PID 0x0157
+#define MTXORB_FTDI_RANGE_0158_PID 0x0158
+#define MTXORB_FTDI_RANGE_0159_PID 0x0159
+#define MTXORB_FTDI_RANGE_015A_PID 0x015A
+#define MTXORB_FTDI_RANGE_015B_PID 0x015B
+#define MTXORB_FTDI_RANGE_015C_PID 0x015C
+#define MTXORB_FTDI_RANGE_015D_PID 0x015D
+#define MTXORB_FTDI_RANGE_015E_PID 0x015E
+#define MTXORB_FTDI_RANGE_015F_PID 0x015F
+#define MTXORB_FTDI_RANGE_0160_PID 0x0160
+#define MTXORB_FTDI_RANGE_0161_PID 0x0161
+#define MTXORB_FTDI_RANGE_0162_PID 0x0162
+#define MTXORB_FTDI_RANGE_0163_PID 0x0163
+#define MTXORB_FTDI_RANGE_0164_PID 0x0164
+#define MTXORB_FTDI_RANGE_0165_PID 0x0165
+#define MTXORB_FTDI_RANGE_0166_PID 0x0166
+#define MTXORB_FTDI_RANGE_0167_PID 0x0167
+#define MTXORB_FTDI_RANGE_0168_PID 0x0168
+#define MTXORB_FTDI_RANGE_0169_PID 0x0169
+#define MTXORB_FTDI_RANGE_016A_PID 0x016A
+#define MTXORB_FTDI_RANGE_016B_PID 0x016B
+#define MTXORB_FTDI_RANGE_016C_PID 0x016C
+#define MTXORB_FTDI_RANGE_016D_PID 0x016D
+#define MTXORB_FTDI_RANGE_016E_PID 0x016E
+#define MTXORB_FTDI_RANGE_016F_PID 0x016F
+#define MTXORB_FTDI_RANGE_0170_PID 0x0170
+#define MTXORB_FTDI_RANGE_0171_PID 0x0171
+#define MTXORB_FTDI_RANGE_0172_PID 0x0172
+#define MTXORB_FTDI_RANGE_0173_PID 0x0173
+#define MTXORB_FTDI_RANGE_0174_PID 0x0174
+#define MTXORB_FTDI_RANGE_0175_PID 0x0175
+#define MTXORB_FTDI_RANGE_0176_PID 0x0176
+#define MTXORB_FTDI_RANGE_0177_PID 0x0177
+#define MTXORB_FTDI_RANGE_0178_PID 0x0178
+#define MTXORB_FTDI_RANGE_0179_PID 0x0179
+#define MTXORB_FTDI_RANGE_017A_PID 0x017A
+#define MTXORB_FTDI_RANGE_017B_PID 0x017B
+#define MTXORB_FTDI_RANGE_017C_PID 0x017C
+#define MTXORB_FTDI_RANGE_017D_PID 0x017D
+#define MTXORB_FTDI_RANGE_017E_PID 0x017E
+#define MTXORB_FTDI_RANGE_017F_PID 0x017F
+#define MTXORB_FTDI_RANGE_0180_PID 0x0180
+#define MTXORB_FTDI_RANGE_0181_PID 0x0181
+#define MTXORB_FTDI_RANGE_0182_PID 0x0182
+#define MTXORB_FTDI_RANGE_0183_PID 0x0183
+#define MTXORB_FTDI_RANGE_0184_PID 0x0184
+#define MTXORB_FTDI_RANGE_0185_PID 0x0185
+#define MTXORB_FTDI_RANGE_0186_PID 0x0186
+#define MTXORB_FTDI_RANGE_0187_PID 0x0187
+#define MTXORB_FTDI_RANGE_0188_PID 0x0188
+#define MTXORB_FTDI_RANGE_0189_PID 0x0189
+#define MTXORB_FTDI_RANGE_018A_PID 0x018A
+#define MTXORB_FTDI_RANGE_018B_PID 0x018B
+#define MTXORB_FTDI_RANGE_018C_PID 0x018C
+#define MTXORB_FTDI_RANGE_018D_PID 0x018D
+#define MTXORB_FTDI_RANGE_018E_PID 0x018E
+#define MTXORB_FTDI_RANGE_018F_PID 0x018F
+#define MTXORB_FTDI_RANGE_0190_PID 0x0190
+#define MTXORB_FTDI_RANGE_0191_PID 0x0191
+#define MTXORB_FTDI_RANGE_0192_PID 0x0192
+#define MTXORB_FTDI_RANGE_0193_PID 0x0193
+#define MTXORB_FTDI_RANGE_0194_PID 0x0194
+#define MTXORB_FTDI_RANGE_0195_PID 0x0195
+#define MTXORB_FTDI_RANGE_0196_PID 0x0196
+#define MTXORB_FTDI_RANGE_0197_PID 0x0197
+#define MTXORB_FTDI_RANGE_0198_PID 0x0198
+#define MTXORB_FTDI_RANGE_0199_PID 0x0199
+#define MTXORB_FTDI_RANGE_019A_PID 0x019A
+#define MTXORB_FTDI_RANGE_019B_PID 0x019B
+#define MTXORB_FTDI_RANGE_019C_PID 0x019C
+#define MTXORB_FTDI_RANGE_019D_PID 0x019D
+#define MTXORB_FTDI_RANGE_019E_PID 0x019E
+#define MTXORB_FTDI_RANGE_019F_PID 0x019F
+#define MTXORB_FTDI_RANGE_01A0_PID 0x01A0
+#define MTXORB_FTDI_RANGE_01A1_PID 0x01A1
+#define MTXORB_FTDI_RANGE_01A2_PID 0x01A2
+#define MTXORB_FTDI_RANGE_01A3_PID 0x01A3
+#define MTXORB_FTDI_RANGE_01A4_PID 0x01A4
+#define MTXORB_FTDI_RANGE_01A5_PID 0x01A5
+#define MTXORB_FTDI_RANGE_01A6_PID 0x01A6
+#define MTXORB_FTDI_RANGE_01A7_PID 0x01A7
+#define MTXORB_FTDI_RANGE_01A8_PID 0x01A8
+#define MTXORB_FTDI_RANGE_01A9_PID 0x01A9
+#define MTXORB_FTDI_RANGE_01AA_PID 0x01AA
+#define MTXORB_FTDI_RANGE_01AB_PID 0x01AB
+#define MTXORB_FTDI_RANGE_01AC_PID 0x01AC
+#define MTXORB_FTDI_RANGE_01AD_PID 0x01AD
+#define MTXORB_FTDI_RANGE_01AE_PID 0x01AE
+#define MTXORB_FTDI_RANGE_01AF_PID 0x01AF
+#define MTXORB_FTDI_RANGE_01B0_PID 0x01B0
+#define MTXORB_FTDI_RANGE_01B1_PID 0x01B1
+#define MTXORB_FTDI_RANGE_01B2_PID 0x01B2
+#define MTXORB_FTDI_RANGE_01B3_PID 0x01B3
+#define MTXORB_FTDI_RANGE_01B4_PID 0x01B4
+#define MTXORB_FTDI_RANGE_01B5_PID 0x01B5
+#define MTXORB_FTDI_RANGE_01B6_PID 0x01B6
+#define MTXORB_FTDI_RANGE_01B7_PID 0x01B7
+#define MTXORB_FTDI_RANGE_01B8_PID 0x01B8
+#define MTXORB_FTDI_RANGE_01B9_PID 0x01B9
+#define MTXORB_FTDI_RANGE_01BA_PID 0x01BA
+#define MTXORB_FTDI_RANGE_01BB_PID 0x01BB
+#define MTXORB_FTDI_RANGE_01BC_PID 0x01BC
+#define MTXORB_FTDI_RANGE_01BD_PID 0x01BD
+#define MTXORB_FTDI_RANGE_01BE_PID 0x01BE
+#define MTXORB_FTDI_RANGE_01BF_PID 0x01BF
+#define MTXORB_FTDI_RANGE_01C0_PID 0x01C0
+#define MTXORB_FTDI_RANGE_01C1_PID 0x01C1
+#define MTXORB_FTDI_RANGE_01C2_PID 0x01C2
+#define MTXORB_FTDI_RANGE_01C3_PID 0x01C3
+#define MTXORB_FTDI_RANGE_01C4_PID 0x01C4
+#define MTXORB_FTDI_RANGE_01C5_PID 0x01C5
+#define MTXORB_FTDI_RANGE_01C6_PID 0x01C6
+#define MTXORB_FTDI_RANGE_01C7_PID 0x01C7
+#define MTXORB_FTDI_RANGE_01C8_PID 0x01C8
+#define MTXORB_FTDI_RANGE_01C9_PID 0x01C9
+#define MTXORB_FTDI_RANGE_01CA_PID 0x01CA
+#define MTXORB_FTDI_RANGE_01CB_PID 0x01CB
+#define MTXORB_FTDI_RANGE_01CC_PID 0x01CC
+#define MTXORB_FTDI_RANGE_01CD_PID 0x01CD
+#define MTXORB_FTDI_RANGE_01CE_PID 0x01CE
+#define MTXORB_FTDI_RANGE_01CF_PID 0x01CF
+#define MTXORB_FTDI_RANGE_01D0_PID 0x01D0
+#define MTXORB_FTDI_RANGE_01D1_PID 0x01D1
+#define MTXORB_FTDI_RANGE_01D2_PID 0x01D2
+#define MTXORB_FTDI_RANGE_01D3_PID 0x01D3
+#define MTXORB_FTDI_RANGE_01D4_PID 0x01D4
+#define MTXORB_FTDI_RANGE_01D5_PID 0x01D5
+#define MTXORB_FTDI_RANGE_01D6_PID 0x01D6
+#define MTXORB_FTDI_RANGE_01D7_PID 0x01D7
+#define MTXORB_FTDI_RANGE_01D8_PID 0x01D8
+#define MTXORB_FTDI_RANGE_01D9_PID 0x01D9
+#define MTXORB_FTDI_RANGE_01DA_PID 0x01DA
+#define MTXORB_FTDI_RANGE_01DB_PID 0x01DB
+#define MTXORB_FTDI_RANGE_01DC_PID 0x01DC
+#define MTXORB_FTDI_RANGE_01DD_PID 0x01DD
+#define MTXORB_FTDI_RANGE_01DE_PID 0x01DE
+#define MTXORB_FTDI_RANGE_01DF_PID 0x01DF
+#define MTXORB_FTDI_RANGE_01E0_PID 0x01E0
+#define MTXORB_FTDI_RANGE_01E1_PID 0x01E1
+#define MTXORB_FTDI_RANGE_01E2_PID 0x01E2
+#define MTXORB_FTDI_RANGE_01E3_PID 0x01E3
+#define MTXORB_FTDI_RANGE_01E4_PID 0x01E4
+#define MTXORB_FTDI_RANGE_01E5_PID 0x01E5
+#define MTXORB_FTDI_RANGE_01E6_PID 0x01E6
+#define MTXORB_FTDI_RANGE_01E7_PID 0x01E7
+#define MTXORB_FTDI_RANGE_01E8_PID 0x01E8
+#define MTXORB_FTDI_RANGE_01E9_PID 0x01E9
+#define MTXORB_FTDI_RANGE_01EA_PID 0x01EA
+#define MTXORB_FTDI_RANGE_01EB_PID 0x01EB
+#define MTXORB_FTDI_RANGE_01EC_PID 0x01EC
+#define MTXORB_FTDI_RANGE_01ED_PID 0x01ED
+#define MTXORB_FTDI_RANGE_01EE_PID 0x01EE
+#define MTXORB_FTDI_RANGE_01EF_PID 0x01EF
+#define MTXORB_FTDI_RANGE_01F0_PID 0x01F0
+#define MTXORB_FTDI_RANGE_01F1_PID 0x01F1
+#define MTXORB_FTDI_RANGE_01F2_PID 0x01F2
+#define MTXORB_FTDI_RANGE_01F3_PID 0x01F3
+#define MTXORB_FTDI_RANGE_01F4_PID 0x01F4
+#define MTXORB_FTDI_RANGE_01F5_PID 0x01F5
+#define MTXORB_FTDI_RANGE_01F6_PID 0x01F6
+#define MTXORB_FTDI_RANGE_01F7_PID 0x01F7
+#define MTXORB_FTDI_RANGE_01F8_PID 0x01F8
+#define MTXORB_FTDI_RANGE_01F9_PID 0x01F9
+#define MTXORB_FTDI_RANGE_01FA_PID 0x01FA
+#define MTXORB_FTDI_RANGE_01FB_PID 0x01FB
+#define MTXORB_FTDI_RANGE_01FC_PID 0x01FC
+#define MTXORB_FTDI_RANGE_01FD_PID 0x01FD
+#define MTXORB_FTDI_RANGE_01FE_PID 0x01FE
+#define MTXORB_FTDI_RANGE_01FF_PID 0x01FF
+
+
+
+/*
+ * The Mobility Lab (TML)
+ * Submitted by Pierre Castella
+ */
+#define TML_VID 0x1B91 /* Vendor ID */
+#define TML_USB_SERIAL_PID 0x0064 /* USB - Serial Converter */
+
+/* Alti-2 products http://www.alti-2.com */
+#define ALTI2_VID 0x1BC9
+#define ALTI2_N3_PID 0x6001 /* Neptune 3 */
+
+/*
+ * Ionics PlugComputer
+ */
+#define IONICS_VID 0x1c0c
+#define IONICS_PLUGCOMPUTER_PID 0x0102
+
+/*
+ * Dresden Elektronik Sensor Terminal Board
+ */
+#define DE_VID 0x1cf1 /* Vendor ID */
+#define STB_PID 0x0001 /* Sensor Terminal Board */
+#define WHT_PID 0x0004 /* Wireless Handheld Terminal */
+
+/*
+ * STMicroelectonics
+ */
+#define ST_VID 0x0483
+#define ST_STMCLT1030_PID 0x3747 /* ST Micro Connect Lite STMCLT1030 */
+
+/*
+ * Papouch products (http://www.papouch.com/)
+ * Submitted by Folkert van Heusden
+ */
+
+#define PAPOUCH_VID 0x5050 /* Vendor ID */
+#define PAPOUCH_SB485_PID 0x0100 /* Papouch SB485 USB-485/422 Converter */
+#define PAPOUCH_AP485_PID 0x0101 /* AP485 USB-RS485 Converter */
+#define PAPOUCH_SB422_PID 0x0102 /* Papouch SB422 USB-RS422 Converter */
+#define PAPOUCH_SB485_2_PID 0x0103 /* Papouch SB485 USB-485/422 Converter */
+#define PAPOUCH_AP485_2_PID 0x0104 /* AP485 USB-RS485 Converter */
+#define PAPOUCH_SB422_2_PID 0x0105 /* Papouch SB422 USB-RS422 Converter */
+#define PAPOUCH_SB485S_PID 0x0106 /* Papouch SB485S USB-485/422 Converter */
+#define PAPOUCH_SB485C_PID 0x0107 /* Papouch SB485C USB-485/422 Converter */
+#define PAPOUCH_LEC_PID 0x0300 /* LEC USB Converter */
+#define PAPOUCH_SB232_PID 0x0301 /* Papouch SB232 USB-RS232 Converter */
+#define PAPOUCH_TMU_PID 0x0400 /* TMU USB Thermometer */
+#define PAPOUCH_IRAMP_PID 0x0500 /* Papouch IRAmp Duplex */
+#define PAPOUCH_DRAK5_PID 0x0700 /* Papouch DRAK5 */
+#define PAPOUCH_QUIDO8x8_PID 0x0800 /* Papouch Quido 8/8 Module */
+#define PAPOUCH_QUIDO4x4_PID 0x0900 /* Papouch Quido 4/4 Module */
+#define PAPOUCH_QUIDO2x2_PID 0x0a00 /* Papouch Quido 2/2 Module */
+#define PAPOUCH_QUIDO10x1_PID 0x0b00 /* Papouch Quido 10/1 Module */
+#define PAPOUCH_QUIDO30x3_PID 0x0c00 /* Papouch Quido 30/3 Module */
+#define PAPOUCH_QUIDO60x3_PID 0x0d00 /* Papouch Quido 60(100)/3 Module */
+#define PAPOUCH_QUIDO2x16_PID 0x0e00 /* Papouch Quido 2/16 Module */
+#define PAPOUCH_QUIDO3x32_PID 0x0f00 /* Papouch Quido 3/32 Module */
+#define PAPOUCH_DRAK6_PID 0x1000 /* Papouch DRAK6 */
+#define PAPOUCH_UPSUSB_PID 0x8000 /* Papouch UPS-USB adapter */
+#define PAPOUCH_MU_PID 0x8001 /* MU controller */
+#define PAPOUCH_SIMUKEY_PID 0x8002 /* Papouch SimuKey */
+#define PAPOUCH_AD4USB_PID 0x8003 /* AD4USB Measurement Module */
+#define PAPOUCH_GMUX_PID 0x8004 /* Papouch GOLIATH MUX */
+#define PAPOUCH_GMSR_PID 0x8005 /* Papouch GOLIATH MSR */
+
+/*
+ * Marvell SheevaPlug
+ */
+#define MARVELL_VID 0x9e88
+#define MARVELL_SHEEVAPLUG_PID 0x9e8f
+
+/*
+ * Evolution Robotics products (http://www.evolution.com/).
+ * Submitted by Shawn M. Lavelle.
+ */
+#define EVOLUTION_VID 0xDEEE /* Vendor ID */
+#define EVOLUTION_ER1_PID 0x0300 /* ER1 Control Module */
+#define EVO_8U232AM_PID 0x02FF /* Evolution robotics RCM2 (FT232AM)*/
+#define EVO_HYBRID_PID 0x0302 /* Evolution robotics RCM4 PID (FT232BM)*/
+#define EVO_RCM4_PID 0x0303 /* Evolution robotics RCM4 PID */
+
+/*
+ * MJS Gadgets HD Radio / XM Radio / Sirius Radio interfaces (using VID 0x0403)
+ */
+#define MJSG_GENERIC_PID 0x9378
+#define MJSG_SR_RADIO_PID 0x9379
+#define MJSG_XM_RADIO_PID 0x937A
+#define MJSG_HD_RADIO_PID 0x937C
+
+/*
+ * D.O.Tec products (http://www.directout.eu)
+ */
+#define FTDI_DOTEC_PID 0x9868
+
+/*
+ * Xverve Signalyzer tools (http://www.signalyzer.com/)
+ */
+#define XVERVE_SIGNALYZER_ST_PID 0xBCA0
+#define XVERVE_SIGNALYZER_SLITE_PID 0xBCA1
+#define XVERVE_SIGNALYZER_SH2_PID 0xBCA2
+#define XVERVE_SIGNALYZER_SH4_PID 0xBCA4
+
+/*
+ * Segway Robotic Mobility Platform USB interface (using VID 0x0403)
+ * Submitted by John G. Rogers
+ */
+#define SEGWAY_RMP200_PID 0xe729
+
+
+/*
+ * Accesio USB Data Acquisition products (http://www.accesio.com/)
+ */
+#define ACCESIO_COM4SM_PID 0xD578
+
+/* www.sciencescope.co.uk educational dataloggers */
+#define FTDI_SCIENCESCOPE_LOGBOOKML_PID 0xFF18
+#define FTDI_SCIENCESCOPE_LS_LOGBOOK_PID 0xFF1C
+#define FTDI_SCIENCESCOPE_HS_LOGBOOK_PID 0xFF1D
+
+/*
+ * Milkymist One JTAG/Serial
+ */
+#define QIHARDWARE_VID 0x20B7
+#define MILKYMISTONE_JTAGSERIAL_PID 0x0713
+
+/*
+ * CTI GmbH RS485 Converter http://www.cti-lean.com/
+ */
+/* USB-485-Mini*/
+#define FTDI_CTI_MINI_PID 0xF608
+/* USB-Nano-485*/
+#define FTDI_CTI_NANO_PID 0xF60B
+
+/*
+ * ZeitControl cardsystems GmbH rfid-readers http://zeitconrol.de
+ */
+/* TagTracer MIFARE*/
+#define FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID 0xF7C0
+
+/*
+ * Rainforest Automation
+ */
+/* ZigBee controller */
+#define FTDI_RF_R106 0x8A28
+
+/*
+ * Product: HCP HIT GPRS modem
+ * Manufacturer: HCP d.o.o.
+ * ATI command output: Cinterion MC55i
+ */
+#define FTDI_CINTERION_MC55I_PID 0xA951
diff --git a/hw/usb/quirks-pl2303-ids.h b/hw/usb/quirks-pl2303-ids.h
new file mode 100644
index 00000000..8dbdb46f
--- /dev/null
+++ b/hw/usb/quirks-pl2303-ids.h
@@ -0,0 +1,150 @@
+/*
+ * Prolific PL2303 USB to serial adaptor driver header file
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#define BENQ_VENDOR_ID 0x04a5
+#define BENQ_PRODUCT_ID_S81 0x4027
+
+#define PL2303_VENDOR_ID 0x067b
+#define PL2303_PRODUCT_ID 0x2303
+#define PL2303_PRODUCT_ID_RSAQ2 0x04bb
+#define PL2303_PRODUCT_ID_DCU11 0x1234
+#define PL2303_PRODUCT_ID_PHAROS 0xaaa0
+#define PL2303_PRODUCT_ID_RSAQ3 0xaaa2
+#define PL2303_PRODUCT_ID_ALDIGA 0x0611
+#define PL2303_PRODUCT_ID_MMX 0x0612
+#define PL2303_PRODUCT_ID_GPRS 0x0609
+#define PL2303_PRODUCT_ID_HCR331 0x331a
+#define PL2303_PRODUCT_ID_MOTOROLA 0x0307
+
+#define ATEN_VENDOR_ID 0x0557
+#define ATEN_VENDOR_ID2 0x0547
+#define ATEN_PRODUCT_ID 0x2008
+
+#define IODATA_VENDOR_ID 0x04bb
+#define IODATA_PRODUCT_ID 0x0a03
+#define IODATA_PRODUCT_ID_RSAQ5 0x0a0e
+
+#define ELCOM_VENDOR_ID 0x056e
+#define ELCOM_PRODUCT_ID 0x5003
+#define ELCOM_PRODUCT_ID_UCSGT 0x5004
+
+#define ITEGNO_VENDOR_ID 0x0eba
+#define ITEGNO_PRODUCT_ID 0x1080
+#define ITEGNO_PRODUCT_ID_2080 0x2080
+
+#define MA620_VENDOR_ID 0x0df7
+#define MA620_PRODUCT_ID 0x0620
+
+#define RATOC_VENDOR_ID 0x0584
+#define RATOC_PRODUCT_ID 0xb000
+
+#define TRIPP_VENDOR_ID 0x2478
+#define TRIPP_PRODUCT_ID 0x2008
+
+#define RADIOSHACK_VENDOR_ID 0x1453
+#define RADIOSHACK_PRODUCT_ID 0x4026
+
+#define DCU10_VENDOR_ID 0x0731
+#define DCU10_PRODUCT_ID 0x0528
+
+#define SITECOM_VENDOR_ID 0x6189
+#define SITECOM_PRODUCT_ID 0x2068
+
+/* Alcatel OT535/735 USB cable */
+#define ALCATEL_VENDOR_ID 0x11f7
+#define ALCATEL_PRODUCT_ID 0x02df
+
+/* Samsung I330 phone cradle */
+#define SAMSUNG_VENDOR_ID 0x04e8
+#define SAMSUNG_PRODUCT_ID 0x8001
+
+#define SIEMENS_VENDOR_ID 0x11f5
+#define SIEMENS_PRODUCT_ID_SX1 0x0001
+#define SIEMENS_PRODUCT_ID_X65 0x0003
+#define SIEMENS_PRODUCT_ID_X75 0x0004
+#define SIEMENS_PRODUCT_ID_EF81 0x0005
+
+#define SYNTECH_VENDOR_ID 0x0745
+#define SYNTECH_PRODUCT_ID 0x0001
+
+/* Nokia CA-42 Cable */
+#define NOKIA_CA42_VENDOR_ID 0x078b
+#define NOKIA_CA42_PRODUCT_ID 0x1234
+
+/* CA-42 CLONE Cable www.ca-42.com chipset: Prolific Technology Inc */
+#define CA_42_CA42_VENDOR_ID 0x10b5
+#define CA_42_CA42_PRODUCT_ID 0xac70
+
+#define SAGEM_VENDOR_ID 0x079b
+#define SAGEM_PRODUCT_ID 0x0027
+
+/* Leadtek GPS 9531 (ID 0413:2101) */
+#define LEADTEK_VENDOR_ID 0x0413
+#define LEADTEK_9531_PRODUCT_ID 0x2101
+
+/* USB GSM cable from Speed Dragon Multimedia, Ltd */
+#define SPEEDDRAGON_VENDOR_ID 0x0e55
+#define SPEEDDRAGON_PRODUCT_ID 0x110b
+
+/* DATAPILOT Universal-2 Phone Cable */
+#define DATAPILOT_U2_VENDOR_ID 0x0731
+#define DATAPILOT_U2_PRODUCT_ID 0x2003
+
+/* Belkin "F5U257" Serial Adapter */
+#define BELKIN_VENDOR_ID 0x050d
+#define BELKIN_PRODUCT_ID 0x0257
+
+/* Alcor Micro Corp. USB 2.0 TO RS-232 */
+#define ALCOR_VENDOR_ID 0x058F
+#define ALCOR_PRODUCT_ID 0x9720
+
+/* Willcom WS002IN Data Driver (by NetIndex Inc.) */
+#define WS002IN_VENDOR_ID 0x11f6
+#define WS002IN_PRODUCT_ID 0x2001
+
+/* Corega CG-USBRS232R Serial Adapter */
+#define COREGA_VENDOR_ID 0x07aa
+#define COREGA_PRODUCT_ID 0x002a
+
+/* Y.C. Cable U.S.A., Inc - USB to RS-232 */
+#define YCCABLE_VENDOR_ID 0x05ad
+#define YCCABLE_PRODUCT_ID 0x0fba
+
+/* "Superial" USB - Serial */
+#define SUPERIAL_VENDOR_ID 0x5372
+#define SUPERIAL_PRODUCT_ID 0x2303
+
+/* Hewlett-Packard LD220-HP POS Pole Display */
+#define HP_VENDOR_ID 0x03f0
+#define HP_LD220_PRODUCT_ID 0x3524
+
+/* Cressi Edy (diving computer) PC interface */
+#define CRESSI_VENDOR_ID 0x04b8
+#define CRESSI_EDY_PRODUCT_ID 0x0521
+
+/* Zeagle dive computer interface */
+#define ZEAGLE_VENDOR_ID 0x04b8
+#define ZEAGLE_N2ITION3_PRODUCT_ID 0x0522
+
+/* Sony, USB data cable for CMD-Jxx mobile phones */
+#define SONY_VENDOR_ID 0x054c
+#define SONY_QN3USB_PRODUCT_ID 0x0437
+
+/* Sanwa KB-USB2 multimeter cable (ID: 11ad:0001) */
+#define SANWA_VENDOR_ID 0x11ad
+#define SANWA_PRODUCT_ID 0x0001
+
+/* ADLINK ND-6530 RS232,RS485 and RS422 adapter */
+#define ADLINK_VENDOR_ID 0x0b63
+#define ADLINK_ND6530_PRODUCT_ID 0x6530
+
+/* SMART USB Serial Adapter */
+#define SMART_VENDOR_ID 0x0b8c
+#define SMART_PRODUCT_ID 0x2303
diff --git a/hw/usb/quirks.c b/hw/usb/quirks.c
new file mode 100644
index 00000000..a761a960
--- /dev/null
+++ b/hw/usb/quirks.c
@@ -0,0 +1,53 @@
+/*
+ * USB quirk handling
+ *
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * Red Hat Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "quirks.h"
+#include "hw/usb.h"
+
+static bool usb_id_match(const struct usb_device_id *ids,
+ uint16_t vendor_id, uint16_t product_id,
+ uint8_t interface_class, uint8_t interface_subclass,
+ uint8_t interface_protocol) {
+ int i;
+
+ for (i = 0; ids[i].vendor_id != -1; i++) {
+ if (ids[i].vendor_id == vendor_id &&
+ ids[i].product_id == product_id &&
+ (ids[i].interface_class == -1 ||
+ (ids[i].interface_class == interface_class &&
+ ids[i].interface_subclass == interface_subclass &&
+ ids[i].interface_protocol == interface_protocol))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+int usb_get_quirks(uint16_t vendor_id, uint16_t product_id,
+ uint8_t interface_class, uint8_t interface_subclass,
+ uint8_t interface_protocol)
+{
+ int quirks = 0;
+
+ if (usb_id_match(usbredir_raw_serial_ids, vendor_id, product_id,
+ interface_class, interface_subclass, interface_protocol)) {
+ quirks |= USB_QUIRK_BUFFER_BULK_IN;
+ }
+ if (usb_id_match(usbredir_ftdi_serial_ids, vendor_id, product_id,
+ interface_class, interface_subclass, interface_protocol)) {
+ quirks |= USB_QUIRK_BUFFER_BULK_IN | USB_QUIRK_IS_FTDI;
+ }
+
+ return quirks;
+}
diff --git a/hw/usb/quirks.h b/hw/usb/quirks.h
new file mode 100644
index 00000000..8dc60655
--- /dev/null
+++ b/hw/usb/quirks.h
@@ -0,0 +1,910 @@
+/*
+ * USB quirk handling
+ *
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * Red Hat Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/* 1 on 1 copy of linux/drivers/usb/serial/ftdi_sio_ids.h */
+#include "quirks-ftdi-ids.h"
+/* 1 on 1 copy of linux/drivers/usb/serial/pl2303.h */
+#include "quirks-pl2303-ids.h"
+
+struct usb_device_id {
+ int vendor_id;
+ int product_id;
+ int interface_class;
+ int interface_subclass;
+ int interface_protocol;
+};
+
+#define USB_DEVICE(vendor, product) \
+ .vendor_id = vendor, .product_id = product, .interface_class = -1,
+
+#define USB_DEVICE_AND_INTERFACE_INFO(vend, prod, iclass, isubclass, iproto) \
+ .vendor_id = vend, .product_id = prod, .interface_class = iclass, \
+ .interface_subclass = isubclass, .interface_protocol = iproto
+
+static const struct usb_device_id usbredir_raw_serial_ids[] = {
+ /*
+ * Silicon Laboratories CP210x USB to RS232 serial adapter ids
+ * copied from linux/drivers/usb/serial/cp210x.c
+ *
+ * Copyright (C) 2005 Craig Shelley (craig@microtron.org.uk)
+ */
+ { USB_DEVICE(0x045B, 0x0053) }, /* Renesas RX610 RX-Stick */
+ { USB_DEVICE(0x0471, 0x066A) }, /* AKTAKOM ACE-1001 cable */
+ { USB_DEVICE(0x0489, 0xE000) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */
+ { USB_DEVICE(0x0489, 0xE003) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */
+ { USB_DEVICE(0x0745, 0x1000) }, /* CipherLab USB CCD Barcode Scanner 1000 */
+ { USB_DEVICE(0x08e6, 0x5501) }, /* Gemalto Prox-PU/CU contactless smartcard reader */
+ { USB_DEVICE(0x08FD, 0x000A) }, /* Digianswer A/S , ZigBee/802.15.4 MAC Device */
+ { USB_DEVICE(0x0BED, 0x1100) }, /* MEI (TM) Cashflow-SC Bill/Voucher Acceptor */
+ { USB_DEVICE(0x0BED, 0x1101) }, /* MEI series 2000 Combo Acceptor */
+ { USB_DEVICE(0x0FCF, 0x1003) }, /* Dynastream ANT development board */
+ { USB_DEVICE(0x0FCF, 0x1004) }, /* Dynastream ANT2USB */
+ { USB_DEVICE(0x0FCF, 0x1006) }, /* Dynastream ANT development board */
+ { USB_DEVICE(0x10A6, 0xAA26) }, /* Knock-off DCU-11 cable */
+ { USB_DEVICE(0x10AB, 0x10C5) }, /* Siemens MC60 Cable */
+ { USB_DEVICE(0x10B5, 0xAC70) }, /* Nokia CA-42 USB */
+ { USB_DEVICE(0x10C4, 0x0F91) }, /* Vstabi */
+ { USB_DEVICE(0x10C4, 0x1101) }, /* Arkham Technology DS101 Bus Monitor */
+ { USB_DEVICE(0x10C4, 0x1601) }, /* Arkham Technology DS101 Adapter */
+ { USB_DEVICE(0x10C4, 0x800A) }, /* SPORTident BSM7-D-USB main station */
+ { USB_DEVICE(0x10C4, 0x803B) }, /* Pololu USB-serial converter */
+ { USB_DEVICE(0x10C4, 0x8044) }, /* Cygnal Debug Adapter */
+ { USB_DEVICE(0x10C4, 0x804E) }, /* Software Bisque Paramount ME build-in converter */
+ { USB_DEVICE(0x10C4, 0x8053) }, /* Enfora EDG1228 */
+ { USB_DEVICE(0x10C4, 0x8054) }, /* Enfora GSM2228 */
+ { USB_DEVICE(0x10C4, 0x8066) }, /* Argussoft In-System Programmer */
+ { USB_DEVICE(0x10C4, 0x806F) }, /* IMS USB to RS422 Converter Cable */
+ { USB_DEVICE(0x10C4, 0x807A) }, /* Crumb128 board */
+ { USB_DEVICE(0x10C4, 0x80C4) }, /* Cygnal Integrated Products, Inc., Optris infrared thermometer */
+ { USB_DEVICE(0x10C4, 0x80CA) }, /* Degree Controls Inc */
+ { USB_DEVICE(0x10C4, 0x80DD) }, /* Tracient RFID */
+ { USB_DEVICE(0x10C4, 0x80F6) }, /* Suunto sports instrument */
+ { USB_DEVICE(0x10C4, 0x8115) }, /* Arygon NFC/Mifare Reader */
+ { USB_DEVICE(0x10C4, 0x813D) }, /* Burnside Telecom Deskmobile */
+ { USB_DEVICE(0x10C4, 0x813F) }, /* Tams Master Easy Control */
+ { USB_DEVICE(0x10C4, 0x814A) }, /* West Mountain Radio RIGblaster P&P */
+ { USB_DEVICE(0x10C4, 0x814B) }, /* West Mountain Radio RIGtalk */
+ { USB_DEVICE(0x10C4, 0x8156) }, /* B&G H3000 link cable */
+ { USB_DEVICE(0x10C4, 0x815E) }, /* Helicomm IP-Link 1220-DVM */
+ { USB_DEVICE(0x10C4, 0x815F) }, /* Timewave HamLinkUSB */
+ { USB_DEVICE(0x10C4, 0x818B) }, /* AVIT Research USB to TTL */
+ { USB_DEVICE(0x10C4, 0x819F) }, /* MJS USB Toslink Switcher */
+ { USB_DEVICE(0x10C4, 0x81A6) }, /* ThinkOptics WavIt */
+ { USB_DEVICE(0x10C4, 0x81A9) }, /* Multiplex RC Interface */
+ { USB_DEVICE(0x10C4, 0x81AC) }, /* MSD Dash Hawk */
+ { USB_DEVICE(0x10C4, 0x81AD) }, /* INSYS USB Modem */
+ { USB_DEVICE(0x10C4, 0x81C8) }, /* Lipowsky Industrie Elektronik GmbH, Baby-JTAG */
+ { USB_DEVICE(0x10C4, 0x81E2) }, /* Lipowsky Industrie Elektronik GmbH, Baby-LIN */
+ { USB_DEVICE(0x10C4, 0x81E7) }, /* Aerocomm Radio */
+ { USB_DEVICE(0x10C4, 0x81E8) }, /* Zephyr Bioharness */
+ { USB_DEVICE(0x10C4, 0x81F2) }, /* C1007 HF band RFID controller */
+ { USB_DEVICE(0x10C4, 0x8218) }, /* Lipowsky Industrie Elektronik GmbH, HARP-1 */
+ { USB_DEVICE(0x10C4, 0x822B) }, /* Modem EDGE(GSM) Comander 2 */
+ { USB_DEVICE(0x10C4, 0x826B) }, /* Cygnal Integrated Products, Inc., Fasttrax GPS demonstration module */
+ { USB_DEVICE(0x10C4, 0x8293) }, /* Telegesis ETRX2USB */
+ { USB_DEVICE(0x10C4, 0x82F9) }, /* Procyon AVS */
+ { USB_DEVICE(0x10C4, 0x8341) }, /* Siemens MC35PU GPRS Modem */
+ { USB_DEVICE(0x10C4, 0x8382) }, /* Cygnal Integrated Products, Inc. */
+ { USB_DEVICE(0x10C4, 0x83A8) }, /* Amber Wireless AMB2560 */
+ { USB_DEVICE(0x10C4, 0x83D8) }, /* DekTec DTA Plus VHF/UHF Booster/Attenuator */
+ { USB_DEVICE(0x10C4, 0x8411) }, /* Kyocera GPS Module */
+ { USB_DEVICE(0x10C4, 0x8418) }, /* IRZ Automation Teleport SG-10 GSM/GPRS Modem */
+ { USB_DEVICE(0x10C4, 0x846E) }, /* BEI USB Sensor Interface (VCP) */
+ { USB_DEVICE(0x10C4, 0x8477) }, /* Balluff RFID */
+ { USB_DEVICE(0x10C4, 0x85EA) }, /* AC-Services IBUS-IF */
+ { USB_DEVICE(0x10C4, 0x85EB) }, /* AC-Services CIS-IBUS */
+ { USB_DEVICE(0x10C4, 0x8664) }, /* AC-Services CAN-IF */
+ { USB_DEVICE(0x10C4, 0x8665) }, /* AC-Services OBD-IF */
+ { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */
+ { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */
+ { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */
+ { USB_DEVICE(0x10C4, 0xEA80) }, /* Silicon Labs factory default */
+ { USB_DEVICE(0x10C4, 0xEA71) }, /* Infinity GPS-MIC-1 Radio Monophone */
+ { USB_DEVICE(0x10C4, 0xF001) }, /* Elan Digital Systems USBscope50 */
+ { USB_DEVICE(0x10C4, 0xF002) }, /* Elan Digital Systems USBwave12 */
+ { USB_DEVICE(0x10C4, 0xF003) }, /* Elan Digital Systems USBpulse100 */
+ { USB_DEVICE(0x10C4, 0xF004) }, /* Elan Digital Systems USBcount50 */
+ { USB_DEVICE(0x10C5, 0xEA61) }, /* Silicon Labs MobiData GPRS USB Modem */
+ { USB_DEVICE(0x10CE, 0xEA6A) }, /* Silicon Labs MobiData GPRS USB Modem 100EU */
+ { USB_DEVICE(0x13AD, 0x9999) }, /* Baltech card reader */
+ { USB_DEVICE(0x1555, 0x0004) }, /* Owen AC4 USB-RS485 Converter */
+ { USB_DEVICE(0x166A, 0x0201) }, /* Clipsal 5500PACA C-Bus Pascal Automation Controller */
+ { USB_DEVICE(0x166A, 0x0301) }, /* Clipsal 5800PC C-Bus Wireless PC Interface */
+ { USB_DEVICE(0x166A, 0x0303) }, /* Clipsal 5500PCU C-Bus USB interface */
+ { USB_DEVICE(0x166A, 0x0304) }, /* Clipsal 5000CT2 C-Bus Black and White Touchscreen */
+ { USB_DEVICE(0x166A, 0x0305) }, /* Clipsal C-5000CT2 C-Bus Spectrum Colour Touchscreen */
+ { USB_DEVICE(0x166A, 0x0401) }, /* Clipsal L51xx C-Bus Architectural Dimmer */
+ { USB_DEVICE(0x166A, 0x0101) }, /* Clipsal 5560884 C-Bus Multi-room Audio Matrix Switcher */
+ { USB_DEVICE(0x16D6, 0x0001) }, /* Jablotron serial interface */
+ { USB_DEVICE(0x16DC, 0x0010) }, /* W-IE-NE-R Plein & Baus GmbH PL512 Power Supply */
+ { USB_DEVICE(0x16DC, 0x0011) }, /* W-IE-NE-R Plein & Baus GmbH RCM Remote Control for MARATON Power Supply */
+ { USB_DEVICE(0x16DC, 0x0012) }, /* W-IE-NE-R Plein & Baus GmbH MPOD Multi Channel Power Supply */
+ { USB_DEVICE(0x16DC, 0x0015) }, /* W-IE-NE-R Plein & Baus GmbH CML Control, Monitoring and Data Logger */
+ { USB_DEVICE(0x17A8, 0x0001) }, /* Kamstrup Optical Eye/3-wire */
+ { USB_DEVICE(0x17A8, 0x0005) }, /* Kamstrup M-Bus Master MultiPort 250D */
+ { USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */
+ { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */
+ { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */
+ { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */
+ { USB_DEVICE(0x1E29, 0x0102) }, /* Festo CPX-USB */
+ { USB_DEVICE(0x1E29, 0x0501) }, /* Festo CMSP */
+ { USB_DEVICE(0x3195, 0xF190) }, /* Link Instruments MSO-19 */
+ { USB_DEVICE(0x3195, 0xF280) }, /* Link Instruments MSO-28 */
+ { USB_DEVICE(0x3195, 0xF281) }, /* Link Instruments MSO-28 */
+ { USB_DEVICE(0x413C, 0x9500) }, /* DW700 GPS USB interface */
+
+ /*
+ * Prolific pl2303 USB to RS232 serial adapter ids
+ * copied from linux/drivers/usb/serial/pl2303.c
+ *
+ * Copyright (C) 2001-2007 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2003 IBM Corp.
+ */
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_DCU11) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ3) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_PHAROS) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ALDIGA) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MMX) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GPRS) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_HCR331) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MOTOROLA) },
+ { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) },
+ { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) },
+ { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID) },
+ { USB_DEVICE(ATEN_VENDOR_ID2, ATEN_PRODUCT_ID) },
+ { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID) },
+ { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID_UCSGT) },
+ { USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID) },
+ { USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID_2080) },
+ { USB_DEVICE(MA620_VENDOR_ID, MA620_PRODUCT_ID) },
+ { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID) },
+ { USB_DEVICE(TRIPP_VENDOR_ID, TRIPP_PRODUCT_ID) },
+ { USB_DEVICE(RADIOSHACK_VENDOR_ID, RADIOSHACK_PRODUCT_ID) },
+ { USB_DEVICE(DCU10_VENDOR_ID, DCU10_PRODUCT_ID) },
+ { USB_DEVICE(SITECOM_VENDOR_ID, SITECOM_PRODUCT_ID) },
+ { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_ID) },
+ { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_ID) },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_SX1) },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X65) },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X75) },
+ { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81) },
+ { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_ID_S81) }, /* Benq/Siemens S81 */
+ { USB_DEVICE(SYNTECH_VENDOR_ID, SYNTECH_PRODUCT_ID) },
+ { USB_DEVICE(NOKIA_CA42_VENDOR_ID, NOKIA_CA42_PRODUCT_ID) },
+ { USB_DEVICE(CA_42_CA42_VENDOR_ID, CA_42_CA42_PRODUCT_ID) },
+ { USB_DEVICE(SAGEM_VENDOR_ID, SAGEM_PRODUCT_ID) },
+ { USB_DEVICE(LEADTEK_VENDOR_ID, LEADTEK_9531_PRODUCT_ID) },
+ { USB_DEVICE(SPEEDDRAGON_VENDOR_ID, SPEEDDRAGON_PRODUCT_ID) },
+ { USB_DEVICE(DATAPILOT_U2_VENDOR_ID, DATAPILOT_U2_PRODUCT_ID) },
+ { USB_DEVICE(BELKIN_VENDOR_ID, BELKIN_PRODUCT_ID) },
+ { USB_DEVICE(ALCOR_VENDOR_ID, ALCOR_PRODUCT_ID) },
+ { USB_DEVICE(WS002IN_VENDOR_ID, WS002IN_PRODUCT_ID) },
+ { USB_DEVICE(COREGA_VENDOR_ID, COREGA_PRODUCT_ID) },
+ { USB_DEVICE(YCCABLE_VENDOR_ID, YCCABLE_PRODUCT_ID) },
+ { USB_DEVICE(SUPERIAL_VENDOR_ID, SUPERIAL_PRODUCT_ID) },
+ { USB_DEVICE(HP_VENDOR_ID, HP_LD220_PRODUCT_ID) },
+ { USB_DEVICE(CRESSI_VENDOR_ID, CRESSI_EDY_PRODUCT_ID) },
+ { USB_DEVICE(ZEAGLE_VENDOR_ID, ZEAGLE_N2ITION3_PRODUCT_ID) },
+ { USB_DEVICE(SONY_VENDOR_ID, SONY_QN3USB_PRODUCT_ID) },
+ { USB_DEVICE(SANWA_VENDOR_ID, SANWA_PRODUCT_ID) },
+ { USB_DEVICE(ADLINK_VENDOR_ID, ADLINK_ND6530_PRODUCT_ID) },
+ { USB_DEVICE(SMART_VENDOR_ID, SMART_PRODUCT_ID) },
+
+ { USB_DEVICE(-1, -1) } /* Terminating Entry */
+};
+
+static const struct usb_device_id usbredir_ftdi_serial_ids[] = {
+ /*
+ * FTDI USB to RS232 serial adapter ids
+ * copied from linux/drivers/usb/serial/ftdi_sio.c
+ *
+ * Copyright (C) 2009 - 2010
+ * Johan Hovold (jhovold@gmail.com)
+ * Copyright (C) 1999 - 2001
+ * Greg Kroah-Hartman (greg@kroah.com)
+ * Bill Ryder (bryder@sgi.com)
+ * Copyright (C) 2002
+ * Kuba Ober (kuba@mareimbrium.org)
+ */
+ { USB_DEVICE(FTDI_VID, FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CTI_MINI_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CTI_NANO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_AMC232_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CANUSB_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CANDAPTER_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NXTCAM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_0_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_3_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_4_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_5_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_6_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_7_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USINT_CAT_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USINT_WKEY_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USINT_RS232_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ACTZWAVE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IRTRANS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IPLUS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IPLUS2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_DMX4ALL) },
+ { USB_DEVICE(FTDI_VID, FTDI_SIO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_8U232AM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_8U232AM_ALT_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_232RL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_4232H_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_232H_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_FTX_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MICRO_CHAMELEON_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_SNIFFER_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_THROTTLE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GATEWAY_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GBM_PID) },
+ { USB_DEVICE(INTERBIOMETRICS_VID, INTERBIOMETRICS_IOBOARD_PID) },
+ { USB_DEVICE(INTERBIOMETRICS_VID, INTERBIOMETRICS_MINI_IOBOARD_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SPROG_II) },
+ { USB_DEVICE(FTDI_VID, FTDI_LENZ_LIUSB_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_632_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_634_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_547_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_633_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_631_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_635_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_640_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_XF_642_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_DSS20_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_URBAN_0_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_URBAN_1_PID) },
+ { USB_DEVICE(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_VNHCPCUSB_D_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_0_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_3_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_4_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_5_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MTXORB_6_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_R2000KU_TRUE_RNG) },
+ { USB_DEVICE(FTDI_VID, FTDI_VARDAAN_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0100_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0101_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0102_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0103_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0104_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0105_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0106_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0107_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0108_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0109_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0110_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0111_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0112_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0113_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0114_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0115_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0116_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0117_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0118_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0119_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0120_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0121_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0122_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0123_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0124_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0125_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0126_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0127_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0128_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0129_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0130_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0131_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0132_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0133_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0134_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0135_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0136_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0137_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0138_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0139_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0140_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0141_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0142_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0143_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0144_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0145_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0146_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0147_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0148_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0149_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0150_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0151_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0152_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0153_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0154_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0155_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0156_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0157_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0158_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0159_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0160_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0161_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0162_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0163_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0164_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0165_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0166_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0167_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0168_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0169_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0170_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0171_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0172_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0173_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0174_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0175_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0176_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0177_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0178_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0179_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0180_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0181_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0182_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0183_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0184_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0185_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0186_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0187_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0188_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0189_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0190_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0191_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0192_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0193_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0194_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0195_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0196_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0197_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0198_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0199_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019A_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019B_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019C_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019D_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019E_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019F_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01ED_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EF_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F0_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F1_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F2_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F3_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F4_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F5_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F6_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F7_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F8_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F9_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FA_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FB_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FC_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FD_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FE_PID) },
+ { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FF_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PERLE_ULTRAPORT_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PIEGROUP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TNC_X_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USBX_707_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2101_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2102_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2103_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2104_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2106_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_5_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_6_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_7_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_8_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_5_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_6_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_7_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_8_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_4_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_5_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_6_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_7_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_8_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_1_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_2_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_3_PID) },
+ { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_4_PID) },
+ { USB_DEVICE(IDTECH_VID, IDTECH_IDT1221U_PID) },
+ { USB_DEVICE(OCT_VID, OCT_US101_PID) },
+ { USB_DEVICE(OCT_VID, OCT_DK201_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_HE_TIRA1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID) },
+ { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_1) },
+ { USB_DEVICE(FTDI_VID, PROTEGO_R2X0) },
+ { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_3) },
+ { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_4) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E808_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E809_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80A_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80B_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80C_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80D_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80E_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80F_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E888_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E889_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88A_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88B_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88C_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88D_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88E_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88F_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UO100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UM100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UR100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_ALC8500_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PYRAMID_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1000PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_US485_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PICPRO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PCMCIA_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PK1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_RS232MON_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_APP70_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PEDO_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_IBS_PROD_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TAVIR_STK500_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TIAO_UMPA_PID) },
+ /*
+ * ELV devices:
+ */
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_USR_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_MSM1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_KL100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS550_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_EC3000_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS888_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_TWS550_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FEM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_CLI7000_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_PPS7330_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_TFM100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UDF77_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UIO88_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UAD8_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UDA7_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_USI2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_T1100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_PCD200_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_ULA200_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_CSI8_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_EM1000DL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_PCK100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_RFP500_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FS20SIG_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UTP8_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS300PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS444PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1300PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_EM1010PC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS500_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_HS485_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_UMS100_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_TFD128_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_FM3RX_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELV_WS777_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_SDMUSBQSS_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_MASTERDEVEL2_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_FUTURE_0_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_FUTURE_1_PID) },
+ { USB_DEVICE(FTDI_VID, LINX_FUTURE_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSICDU20_0_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSICDU40_1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSMACHX_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSLOAD_N_GO_3_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSICDU64_4_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CCSPRIME8_5_PID) },
+ { USB_DEVICE(FTDI_VID, INSIDE_ACCESSO) },
+ { USB_DEVICE(INTREPID_VID, INTREPID_VALUECAN_PID) },
+ { USB_DEVICE(INTREPID_VID, INTREPID_NEOVI_PID) },
+ { USB_DEVICE(FALCOM_VID, FALCOM_TWIST_PID) },
+ { USB_DEVICE(FALCOM_VID, FALCOM_SAMBA_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SUUNTO_SPORTS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OCEANIC_PID) },
+ { USB_DEVICE(TTI_VID, TTI_QL355P_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_RM_CANVIEW_PID) },
+ { USB_DEVICE(ACTON_VID, ACTON_SPECTRAPRO_PID) },
+ { USB_DEVICE(CONTEC_VID, CONTEC_COM1USBH_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USOTL4_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USTL4_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USO9ML2_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USOPTL4_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USPTL4_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USO9ML2DR_2_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USO9ML2DR_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USOPTL4DR2_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_USOPTL4DR_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_485USB9F_2W_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_485USB9F_4W_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_232USB9M_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_485USBTB_2W_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_485USBTB_4W_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_TTL5USB9M_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_TTL3USB9M_PID) },
+ { USB_DEVICE(BANDB_VID, BANDB_ZZ_PROG1_USB_PID) },
+ { USB_DEVICE(FTDI_VID, EVER_ECO_PRO_CDS) },
+ { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_1_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_3_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_0_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_1_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_2_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_3_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_4_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_5_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_6_PID) },
+ { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_7_PID) },
+ { USB_DEVICE(MOBILITY_VID, MOBILITY_USB_SERIAL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ACTIVE_ROBOTS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_KW_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_YS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y6_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y8_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_IC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_DB9_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_RS232_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y9_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TERATRONIK_VCP_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TERATRONIK_D2XX_PID) },
+ { USB_DEVICE(EVOLUTION_VID, EVOLUTION_ER1_PID) },
+ { USB_DEVICE(EVOLUTION_VID, EVO_HYBRID_PID) },
+ { USB_DEVICE(EVOLUTION_VID, EVO_RCM4_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ARTEMIS_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16C_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16HR_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16HRC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16IC_PID) },
+ { USB_DEVICE(KOBIL_VID, KOBIL_CONV_B1_PID) },
+ { USB_DEVICE(KOBIL_VID, KOBIL_CONV_KAAN_PID) },
+ { USB_DEVICE(POSIFLEX_VID, POSIFLEX_PP7000_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TTUSB_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ECLO_COM_1WIRE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_WESTREX_MODEL_777_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_WESTREX_MODEL_8900F_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PCDJ_DAC2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_RRCIRKITS_LOCOBUFFER_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ASK_RDR400_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NZR_SEM_USB_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_1_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_OPC_U_UC_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C1_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C2_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2D_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VT_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VR_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVT_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVR_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVT_PID) },
+ { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVR_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ACG_HFDUAL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_YEI_SERVOCENTER31_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_THORLABS_PID) },
+ { USB_DEVICE(TESTO_VID, TESTO_USB_INTERFACE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_GAMMA_SCOUT_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13M_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13S_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13U_PID) },
+ { USB_DEVICE(ELEKTOR_VID, ELEKTOR_FT323R_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_HUC_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_SPECTRA_SCU_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_2_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_3_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_NDI_AURORA_SCU_PID) },
+ { USB_DEVICE(TELLDUS_VID, TELLDUS_TELLSTICK_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_SERIAL_VX7_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_CT29B_PID) },
+ { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_RTS01_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_MAXSTREAM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PHI_FISCO_PID) },
+ { USB_DEVICE(TML_VID, TML_USB_SERIAL_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_ELSTER_UNICOM_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PROPOX_JTAGCABLEII_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_PROPOX_ISPCABLEIII_PID) },
+ { USB_DEVICE(OLIMEX_VID, OLIMEX_ARM_USB_OCD_PID) },
+ { USB_DEVICE(OLIMEX_VID, OLIMEX_ARM_USB_OCD_H_PID) },
+ { USB_DEVICE(FIC_VID, FIC_NEO1973_DEBUG_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_OOCDLINK_PID) },
+ { USB_DEVICE(FTDI_VID, LMI_LM3S_DEVEL_BOARD_PID) },
+ { USB_DEVICE(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID) },
+ { USB_DEVICE(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_TURTELIZER_PID) },
+ { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_USB60F) },
+ { USB_DEVICE(FTDI_VID, FTDI_REU_TINY_PID) },
+
+ /* Papouch devices based on FTDI chip */
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AP485_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB422_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485_2_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AP485_2_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB422_2_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485S_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485C_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_LEC_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB232_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_TMU_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_IRAMP_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_DRAK5_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO8x8_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO4x4_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO2x2_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO10x1_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO30x3_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO60x3_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO2x16_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO3x32_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_DRAK6_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_UPSUSB_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_MU_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SIMUKEY_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AD4USB_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_GMUX_PID) },
+ { USB_DEVICE(PAPOUCH_VID, PAPOUCH_GMSR_PID) },
+
+ { USB_DEVICE(FTDI_VID, FTDI_DOMINTELL_DGQG_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_DOMINTELL_DUSB_PID) },
+ { USB_DEVICE(ALTI2_VID, ALTI2_N3_PID) },
+ { USB_DEVICE(FTDI_VID, DIEBOLD_BCS_SE923_PID) },
+ { USB_DEVICE(ATMEL_VID, STK541_PID) },
+ { USB_DEVICE(DE_VID, STB_PID) },
+ { USB_DEVICE(DE_VID, WHT_PID) },
+ { USB_DEVICE(ADI_VID, ADI_GNICE_PID) },
+ { USB_DEVICE(ADI_VID, ADI_GNICEPLUS_PID) },
+ { USB_DEVICE_AND_INTERFACE_INFO(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID,
+ 0xff, 0xff, 0x00) },
+ { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) },
+ { USB_DEVICE(MARVELL_VID, MARVELL_SHEEVAPLUG_PID) },
+ { USB_DEVICE(LARSENBRUSGAARD_VID, LB_ALTITRACK_PID) },
+ { USB_DEVICE(GN_OTOMETRICS_VID, AURICAL_USB_PID) },
+ { USB_DEVICE(FTDI_VID, PI_C865_PID) },
+ { USB_DEVICE(FTDI_VID, PI_C857_PID) },
+ { USB_DEVICE(PI_VID, PI_C866_PID) },
+ { USB_DEVICE(PI_VID, PI_C663_PID) },
+ { USB_DEVICE(PI_VID, PI_C725_PID) },
+ { USB_DEVICE(PI_VID, PI_E517_PID) },
+ { USB_DEVICE(PI_VID, PI_C863_PID) },
+ { USB_DEVICE(PI_VID, PI_E861_PID) },
+ { USB_DEVICE(PI_VID, PI_C867_PID) },
+ { USB_DEVICE(PI_VID, PI_E609_PID) },
+ { USB_DEVICE(PI_VID, PI_E709_PID) },
+ { USB_DEVICE(PI_VID, PI_100F_PID) },
+ { USB_DEVICE(PI_VID, PI_1011_PID) },
+ { USB_DEVICE(PI_VID, PI_1012_PID) },
+ { USB_DEVICE(PI_VID, PI_1013_PID) },
+ { USB_DEVICE(PI_VID, PI_1014_PID) },
+ { USB_DEVICE(PI_VID, PI_1015_PID) },
+ { USB_DEVICE(PI_VID, PI_1016_PID) },
+ { USB_DEVICE(KONDO_VID, KONDO_USB_SERIAL_PID) },
+ { USB_DEVICE(BAYER_VID, BAYER_CONTOUR_CABLE_PID) },
+ { USB_DEVICE(FTDI_VID, MARVELL_OPENRD_PID) },
+ { USB_DEVICE(FTDI_VID, TI_XDS100V2_PID) },
+ { USB_DEVICE(FTDI_VID, HAMEG_HO820_PID) },
+ { USB_DEVICE(FTDI_VID, HAMEG_HO720_PID) },
+ { USB_DEVICE(FTDI_VID, HAMEG_HO730_PID) },
+ { USB_DEVICE(FTDI_VID, HAMEG_HO870_PID) },
+ { USB_DEVICE(FTDI_VID, MJSG_GENERIC_PID) },
+ { USB_DEVICE(FTDI_VID, MJSG_SR_RADIO_PID) },
+ { USB_DEVICE(FTDI_VID, MJSG_HD_RADIO_PID) },
+ { USB_DEVICE(FTDI_VID, MJSG_XM_RADIO_PID) },
+ { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_ST_PID) },
+ { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SLITE_PID) },
+ { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH2_PID) },
+ { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH4_PID) },
+ { USB_DEVICE(FTDI_VID, SEGWAY_RMP200_PID) },
+ { USB_DEVICE(FTDI_VID, ACCESIO_COM4SM_PID) },
+ { USB_DEVICE(IONICS_VID, IONICS_PLUGCOMPUTER_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_24_MASTER_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_PC_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_USB_DMX_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MIDI_TIMECODE_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MINI_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MAXI_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MEDIA_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_WING_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LOGBOOKML_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LS_LOGBOOK_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_HS_LOGBOOK_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_CINTERION_MC55I_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_DOTEC_PID) },
+ { USB_DEVICE(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_PID) },
+ { USB_DEVICE(ST_VID, ST_STMCLT1030_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_RF_R106) },
+ { USB_DEVICE(FTDI_VID, FTDI_DISTORTEC_JTAG_LOCK_PICK_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_LUMEL_PD12_PID) },
+
+ { USB_DEVICE(-1, -1) } /* Terminating Entry */
+};
+
+#undef USB_DEVICE
+#undef USB_DEVICE_AND_INTERFACE_INFO
diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c
new file mode 100644
index 00000000..25df25fd
--- /dev/null
+++ b/hw/usb/redirect.c
@@ -0,0 +1,2519 @@
+/*
+ * USB redirector usb-guest
+ *
+ * Copyright (c) 2011-2012 Red Hat, Inc.
+ *
+ * Red Hat Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * 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.
+ */
+
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "qapi/qmp/qerror.h"
+#include "qemu/error-report.h"
+#include "qemu/iov.h"
+#include "sysemu/char.h"
+
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <usbredirparser.h>
+#include <usbredirfilter.h>
+
+#include "hw/usb.h"
+
+#define MAX_ENDPOINTS 32
+#define NO_INTERFACE_INFO 255 /* Valid interface_count always <= 32 */
+#define EP2I(ep_address) (((ep_address & 0x80) >> 3) | (ep_address & 0x0f))
+#define I2EP(i) (((i & 0x10) << 3) | (i & 0x0f))
+#define USBEP2I(usb_ep) (((usb_ep)->pid == USB_TOKEN_IN) ? \
+ ((usb_ep)->nr | 0x10) : ((usb_ep)->nr))
+#define I2USBEP(d, i) (usb_ep_get(&(d)->dev, \
+ ((i) & 0x10) ? USB_TOKEN_IN : USB_TOKEN_OUT, \
+ (i) & 0x0f))
+
+#ifndef USBREDIR_VERSION /* This is not defined in older usbredir versions */
+#define USBREDIR_VERSION 0
+#endif
+
+typedef struct USBRedirDevice USBRedirDevice;
+
+/* Struct to hold buffered packets */
+struct buf_packet {
+ uint8_t *data;
+ void *free_on_destroy;
+ uint16_t len;
+ uint16_t offset;
+ uint8_t status;
+ QTAILQ_ENTRY(buf_packet)next;
+};
+
+struct endp_data {
+ USBRedirDevice *dev;
+ uint8_t type;
+ uint8_t interval;
+ uint8_t interface; /* bInterfaceNumber this ep belongs to */
+ uint16_t max_packet_size; /* In bytes, not wMaxPacketSize format !! */
+ uint32_t max_streams;
+ uint8_t iso_started;
+ uint8_t iso_error; /* For reporting iso errors to the HC */
+ uint8_t interrupt_started;
+ uint8_t interrupt_error;
+ uint8_t bulk_receiving_enabled;
+ uint8_t bulk_receiving_started;
+ uint8_t bufpq_prefilled;
+ uint8_t bufpq_dropping_packets;
+ QTAILQ_HEAD(, buf_packet) bufpq;
+ int32_t bufpq_size;
+ int32_t bufpq_target_size;
+ USBPacket *pending_async_packet;
+};
+
+struct PacketIdQueueEntry {
+ uint64_t id;
+ QTAILQ_ENTRY(PacketIdQueueEntry)next;
+};
+
+struct PacketIdQueue {
+ USBRedirDevice *dev;
+ const char *name;
+ QTAILQ_HEAD(, PacketIdQueueEntry) head;
+ int size;
+};
+
+struct USBRedirDevice {
+ USBDevice dev;
+ /* Properties */
+ CharDriverState *cs;
+ uint8_t debug;
+ char *filter_str;
+ int32_t bootindex;
+ /* Data passed from chardev the fd_read cb to the usbredirparser read cb */
+ const uint8_t *read_buf;
+ int read_buf_size;
+ /* Active chardev-watch-tag */
+ guint watch;
+ /* For async handling of close / reject */
+ QEMUBH *chardev_close_bh;
+ QEMUBH *device_reject_bh;
+ /* To delay the usb attach in case of quick chardev close + open */
+ QEMUTimer *attach_timer;
+ int64_t next_attach_time;
+ struct usbredirparser *parser;
+ struct endp_data endpoint[MAX_ENDPOINTS];
+ struct PacketIdQueue cancelled;
+ struct PacketIdQueue already_in_flight;
+ void (*buffered_bulk_in_complete)(USBRedirDevice *, USBPacket *, uint8_t);
+ /* Data for device filtering */
+ struct usb_redir_device_connect_header device_info;
+ struct usb_redir_interface_info_header interface_info;
+ struct usbredirfilter_rule *filter_rules;
+ int filter_rules_count;
+ int compatible_speedmask;
+};
+
+#define TYPE_USB_REDIR "usb-redir"
+#define USB_REDIRECT(obj) OBJECT_CHECK(USBRedirDevice, (obj), TYPE_USB_REDIR)
+
+static void usbredir_hello(void *priv, struct usb_redir_hello_header *h);
+static void usbredir_device_connect(void *priv,
+ struct usb_redir_device_connect_header *device_connect);
+static void usbredir_device_disconnect(void *priv);
+static void usbredir_interface_info(void *priv,
+ struct usb_redir_interface_info_header *interface_info);
+static void usbredir_ep_info(void *priv,
+ struct usb_redir_ep_info_header *ep_info);
+static void usbredir_configuration_status(void *priv, uint64_t id,
+ struct usb_redir_configuration_status_header *configuration_status);
+static void usbredir_alt_setting_status(void *priv, uint64_t id,
+ struct usb_redir_alt_setting_status_header *alt_setting_status);
+static void usbredir_iso_stream_status(void *priv, uint64_t id,
+ struct usb_redir_iso_stream_status_header *iso_stream_status);
+static void usbredir_interrupt_receiving_status(void *priv, uint64_t id,
+ struct usb_redir_interrupt_receiving_status_header
+ *interrupt_receiving_status);
+static void usbredir_bulk_streams_status(void *priv, uint64_t id,
+ struct usb_redir_bulk_streams_status_header *bulk_streams_status);
+static void usbredir_bulk_receiving_status(void *priv, uint64_t id,
+ struct usb_redir_bulk_receiving_status_header *bulk_receiving_status);
+static void usbredir_control_packet(void *priv, uint64_t id,
+ struct usb_redir_control_packet_header *control_packet,
+ uint8_t *data, int data_len);
+static void usbredir_bulk_packet(void *priv, uint64_t id,
+ struct usb_redir_bulk_packet_header *bulk_packet,
+ uint8_t *data, int data_len);
+static void usbredir_iso_packet(void *priv, uint64_t id,
+ struct usb_redir_iso_packet_header *iso_packet,
+ uint8_t *data, int data_len);
+static void usbredir_interrupt_packet(void *priv, uint64_t id,
+ struct usb_redir_interrupt_packet_header *interrupt_header,
+ uint8_t *data, int data_len);
+static void usbredir_buffered_bulk_packet(void *priv, uint64_t id,
+ struct usb_redir_buffered_bulk_packet_header *buffered_bulk_packet,
+ uint8_t *data, int data_len);
+
+static void usbredir_handle_status(USBRedirDevice *dev, USBPacket *p,
+ int status);
+
+#define VERSION "qemu usb-redir guest " QEMU_VERSION
+
+/*
+ * Logging stuff
+ */
+
+#define ERROR(...) \
+ do { \
+ if (dev->debug >= usbredirparser_error) { \
+ error_report("usb-redir error: " __VA_ARGS__); \
+ } \
+ } while (0)
+#define WARNING(...) \
+ do { \
+ if (dev->debug >= usbredirparser_warning) { \
+ error_report("usb-redir warning: " __VA_ARGS__); \
+ } \
+ } while (0)
+#define INFO(...) \
+ do { \
+ if (dev->debug >= usbredirparser_info) { \
+ error_report("usb-redir: " __VA_ARGS__); \
+ } \
+ } while (0)
+#define DPRINTF(...) \
+ do { \
+ if (dev->debug >= usbredirparser_debug) { \
+ error_report("usb-redir: " __VA_ARGS__); \
+ } \
+ } while (0)
+#define DPRINTF2(...) \
+ do { \
+ if (dev->debug >= usbredirparser_debug_data) { \
+ error_report("usb-redir: " __VA_ARGS__); \
+ } \
+ } while (0)
+
+static void usbredir_log(void *priv, int level, const char *msg)
+{
+ USBRedirDevice *dev = priv;
+
+ if (dev->debug < level) {
+ return;
+ }
+
+ error_report("%s", msg);
+}
+
+static void usbredir_log_data(USBRedirDevice *dev, const char *desc,
+ const uint8_t *data, int len)
+{
+ int i, j, n;
+
+ if (dev->debug < usbredirparser_debug_data) {
+ return;
+ }
+
+ for (i = 0; i < len; i += j) {
+ char buf[128];
+
+ n = sprintf(buf, "%s", desc);
+ for (j = 0; j < 8 && i + j < len; j++) {
+ n += sprintf(buf + n, " %02X", data[i + j]);
+ }
+ error_report("%s", buf);
+ }
+}
+
+/*
+ * usbredirparser io functions
+ */
+
+static int usbredir_read(void *priv, uint8_t *data, int count)
+{
+ USBRedirDevice *dev = priv;
+
+ if (dev->read_buf_size < count) {
+ count = dev->read_buf_size;
+ }
+
+ memcpy(data, dev->read_buf, count);
+
+ dev->read_buf_size -= count;
+ if (dev->read_buf_size) {
+ dev->read_buf += count;
+ } else {
+ dev->read_buf = NULL;
+ }
+
+ return count;
+}
+
+static gboolean usbredir_write_unblocked(GIOChannel *chan, GIOCondition cond,
+ void *opaque)
+{
+ USBRedirDevice *dev = opaque;
+
+ dev->watch = 0;
+ usbredirparser_do_write(dev->parser);
+
+ return FALSE;
+}
+
+static int usbredir_write(void *priv, uint8_t *data, int count)
+{
+ USBRedirDevice *dev = priv;
+ int r;
+
+ if (!dev->cs->be_open) {
+ return 0;
+ }
+
+ /* Don't send new data to the chardev until our state is fully synced */
+ if (!runstate_check(RUN_STATE_RUNNING)) {
+ return 0;
+ }
+
+ r = qemu_chr_fe_write(dev->cs, data, count);
+ if (r < count) {
+ if (!dev->watch) {
+ dev->watch = qemu_chr_fe_add_watch(dev->cs, G_IO_OUT|G_IO_HUP,
+ usbredir_write_unblocked, dev);
+ }
+ if (r < 0) {
+ r = 0;
+ }
+ }
+ return r;
+}
+
+/*
+ * Cancelled and buffered packets helpers
+ */
+
+static void packet_id_queue_init(struct PacketIdQueue *q,
+ USBRedirDevice *dev, const char *name)
+{
+ q->dev = dev;
+ q->name = name;
+ QTAILQ_INIT(&q->head);
+ q->size = 0;
+}
+
+static void packet_id_queue_add(struct PacketIdQueue *q, uint64_t id)
+{
+ USBRedirDevice *dev = q->dev;
+ struct PacketIdQueueEntry *e;
+
+ DPRINTF("adding packet id %"PRIu64" to %s queue\n", id, q->name);
+
+ e = g_malloc0(sizeof(struct PacketIdQueueEntry));
+ e->id = id;
+ QTAILQ_INSERT_TAIL(&q->head, e, next);
+ q->size++;
+}
+
+static int packet_id_queue_remove(struct PacketIdQueue *q, uint64_t id)
+{
+ USBRedirDevice *dev = q->dev;
+ struct PacketIdQueueEntry *e;
+
+ QTAILQ_FOREACH(e, &q->head, next) {
+ if (e->id == id) {
+ DPRINTF("removing packet id %"PRIu64" from %s queue\n",
+ id, q->name);
+ QTAILQ_REMOVE(&q->head, e, next);
+ q->size--;
+ g_free(e);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void packet_id_queue_empty(struct PacketIdQueue *q)
+{
+ USBRedirDevice *dev = q->dev;
+ struct PacketIdQueueEntry *e, *next_e;
+
+ DPRINTF("removing %d packet-ids from %s queue\n", q->size, q->name);
+
+ QTAILQ_FOREACH_SAFE(e, &q->head, next, next_e) {
+ QTAILQ_REMOVE(&q->head, e, next);
+ g_free(e);
+ }
+ q->size = 0;
+}
+
+static void usbredir_cancel_packet(USBDevice *udev, USBPacket *p)
+{
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+ int i = USBEP2I(p->ep);
+
+ if (p->combined) {
+ usb_combined_packet_cancel(udev, p);
+ return;
+ }
+
+ if (dev->endpoint[i].pending_async_packet) {
+ assert(dev->endpoint[i].pending_async_packet == p);
+ dev->endpoint[i].pending_async_packet = NULL;
+ return;
+ }
+
+ packet_id_queue_add(&dev->cancelled, p->id);
+ usbredirparser_send_cancel_data_packet(dev->parser, p->id);
+ usbredirparser_do_write(dev->parser);
+}
+
+static int usbredir_is_cancelled(USBRedirDevice *dev, uint64_t id)
+{
+ if (!dev->dev.attached) {
+ return 1; /* Treat everything as cancelled after a disconnect */
+ }
+ return packet_id_queue_remove(&dev->cancelled, id);
+}
+
+static void usbredir_fill_already_in_flight_from_ep(USBRedirDevice *dev,
+ struct USBEndpoint *ep)
+{
+ static USBPacket *p;
+
+ /* async handled packets for bulk receiving eps do not count as inflight */
+ if (dev->endpoint[USBEP2I(ep)].bulk_receiving_started) {
+ return;
+ }
+
+ QTAILQ_FOREACH(p, &ep->queue, queue) {
+ /* Skip combined packets, except for the first */
+ if (p->combined && p != p->combined->first) {
+ continue;
+ }
+ if (p->state == USB_PACKET_ASYNC) {
+ packet_id_queue_add(&dev->already_in_flight, p->id);
+ }
+ }
+}
+
+static void usbredir_fill_already_in_flight(USBRedirDevice *dev)
+{
+ int ep;
+ struct USBDevice *udev = &dev->dev;
+
+ usbredir_fill_already_in_flight_from_ep(dev, &udev->ep_ctl);
+
+ for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
+ usbredir_fill_already_in_flight_from_ep(dev, &udev->ep_in[ep]);
+ usbredir_fill_already_in_flight_from_ep(dev, &udev->ep_out[ep]);
+ }
+}
+
+static int usbredir_already_in_flight(USBRedirDevice *dev, uint64_t id)
+{
+ return packet_id_queue_remove(&dev->already_in_flight, id);
+}
+
+static USBPacket *usbredir_find_packet_by_id(USBRedirDevice *dev,
+ uint8_t ep, uint64_t id)
+{
+ USBPacket *p;
+
+ if (usbredir_is_cancelled(dev, id)) {
+ return NULL;
+ }
+
+ p = usb_ep_find_packet_by_id(&dev->dev,
+ (ep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT,
+ ep & 0x0f, id);
+ if (p == NULL) {
+ ERROR("could not find packet with id %"PRIu64"\n", id);
+ }
+ return p;
+}
+
+static void bufp_alloc(USBRedirDevice *dev, uint8_t *data, uint16_t len,
+ uint8_t status, uint8_t ep, void *free_on_destroy)
+{
+ struct buf_packet *bufp;
+
+ if (!dev->endpoint[EP2I(ep)].bufpq_dropping_packets &&
+ dev->endpoint[EP2I(ep)].bufpq_size >
+ 2 * dev->endpoint[EP2I(ep)].bufpq_target_size) {
+ DPRINTF("bufpq overflow, dropping packets ep %02X\n", ep);
+ dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 1;
+ }
+ /* Since we're interupting the stream anyways, drop enough packets to get
+ back to our target buffer size */
+ if (dev->endpoint[EP2I(ep)].bufpq_dropping_packets) {
+ if (dev->endpoint[EP2I(ep)].bufpq_size >
+ dev->endpoint[EP2I(ep)].bufpq_target_size) {
+ free(data);
+ return;
+ }
+ dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0;
+ }
+
+ bufp = g_malloc(sizeof(struct buf_packet));
+ bufp->data = data;
+ bufp->len = len;
+ bufp->offset = 0;
+ bufp->status = status;
+ bufp->free_on_destroy = free_on_destroy;
+ QTAILQ_INSERT_TAIL(&dev->endpoint[EP2I(ep)].bufpq, bufp, next);
+ dev->endpoint[EP2I(ep)].bufpq_size++;
+}
+
+static void bufp_free(USBRedirDevice *dev, struct buf_packet *bufp,
+ uint8_t ep)
+{
+ QTAILQ_REMOVE(&dev->endpoint[EP2I(ep)].bufpq, bufp, next);
+ dev->endpoint[EP2I(ep)].bufpq_size--;
+ free(bufp->free_on_destroy);
+ g_free(bufp);
+}
+
+static void usbredir_free_bufpq(USBRedirDevice *dev, uint8_t ep)
+{
+ struct buf_packet *buf, *buf_next;
+
+ QTAILQ_FOREACH_SAFE(buf, &dev->endpoint[EP2I(ep)].bufpq, next, buf_next) {
+ bufp_free(dev, buf, ep);
+ }
+}
+
+/*
+ * USBDevice callbacks
+ */
+
+static void usbredir_handle_reset(USBDevice *udev)
+{
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+
+ DPRINTF("reset device\n");
+ usbredirparser_send_reset(dev->parser);
+ usbredirparser_do_write(dev->parser);
+}
+
+static void usbredir_handle_iso_data(USBRedirDevice *dev, USBPacket *p,
+ uint8_t ep)
+{
+ int status, len;
+ if (!dev->endpoint[EP2I(ep)].iso_started &&
+ !dev->endpoint[EP2I(ep)].iso_error) {
+ struct usb_redir_start_iso_stream_header start_iso = {
+ .endpoint = ep,
+ };
+ int pkts_per_sec;
+
+ if (dev->dev.speed == USB_SPEED_HIGH) {
+ pkts_per_sec = 8000 / dev->endpoint[EP2I(ep)].interval;
+ } else {
+ pkts_per_sec = 1000 / dev->endpoint[EP2I(ep)].interval;
+ }
+ /* Testing has shown that we need circa 60 ms buffer */
+ dev->endpoint[EP2I(ep)].bufpq_target_size = (pkts_per_sec * 60) / 1000;
+
+ /* Aim for approx 100 interrupts / second on the client to
+ balance latency and interrupt load */
+ start_iso.pkts_per_urb = pkts_per_sec / 100;
+ if (start_iso.pkts_per_urb < 1) {
+ start_iso.pkts_per_urb = 1;
+ } else if (start_iso.pkts_per_urb > 32) {
+ start_iso.pkts_per_urb = 32;
+ }
+
+ start_iso.no_urbs = (dev->endpoint[EP2I(ep)].bufpq_target_size +
+ start_iso.pkts_per_urb - 1) /
+ start_iso.pkts_per_urb;
+ /* Output endpoints pre-fill only 1/2 of the packets, keeping the rest
+ as overflow buffer. Also see the usbredir protocol documentation */
+ if (!(ep & USB_DIR_IN)) {
+ start_iso.no_urbs *= 2;
+ }
+ if (start_iso.no_urbs > 16) {
+ start_iso.no_urbs = 16;
+ }
+
+ /* No id, we look at the ep when receiving a status back */
+ usbredirparser_send_start_iso_stream(dev->parser, 0, &start_iso);
+ usbredirparser_do_write(dev->parser);
+ DPRINTF("iso stream started pkts/sec %d pkts/urb %d urbs %d ep %02X\n",
+ pkts_per_sec, start_iso.pkts_per_urb, start_iso.no_urbs, ep);
+ dev->endpoint[EP2I(ep)].iso_started = 1;
+ dev->endpoint[EP2I(ep)].bufpq_prefilled = 0;
+ dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0;
+ }
+
+ if (ep & USB_DIR_IN) {
+ struct buf_packet *isop;
+
+ if (dev->endpoint[EP2I(ep)].iso_started &&
+ !dev->endpoint[EP2I(ep)].bufpq_prefilled) {
+ if (dev->endpoint[EP2I(ep)].bufpq_size <
+ dev->endpoint[EP2I(ep)].bufpq_target_size) {
+ return;
+ }
+ dev->endpoint[EP2I(ep)].bufpq_prefilled = 1;
+ }
+
+ isop = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq);
+ if (isop == NULL) {
+ DPRINTF("iso-token-in ep %02X, no isop, iso_error: %d\n",
+ ep, dev->endpoint[EP2I(ep)].iso_error);
+ /* Re-fill the buffer */
+ dev->endpoint[EP2I(ep)].bufpq_prefilled = 0;
+ /* Check iso_error for stream errors, otherwise its an underrun */
+ status = dev->endpoint[EP2I(ep)].iso_error;
+ dev->endpoint[EP2I(ep)].iso_error = 0;
+ p->status = status ? USB_RET_IOERROR : USB_RET_SUCCESS;
+ return;
+ }
+ DPRINTF2("iso-token-in ep %02X status %d len %d queue-size: %d\n", ep,
+ isop->status, isop->len, dev->endpoint[EP2I(ep)].bufpq_size);
+
+ status = isop->status;
+ len = isop->len;
+ if (len > p->iov.size) {
+ ERROR("received iso data is larger then packet ep %02X (%d > %d)\n",
+ ep, len, (int)p->iov.size);
+ len = p->iov.size;
+ status = usb_redir_babble;
+ }
+ usb_packet_copy(p, isop->data, len);
+ bufp_free(dev, isop, ep);
+ usbredir_handle_status(dev, p, status);
+ } else {
+ /* If the stream was not started because of a pending error don't
+ send the packet to the usb-host */
+ if (dev->endpoint[EP2I(ep)].iso_started) {
+ struct usb_redir_iso_packet_header iso_packet = {
+ .endpoint = ep,
+ .length = p->iov.size
+ };
+ uint8_t buf[p->iov.size];
+ /* No id, we look at the ep when receiving a status back */
+ usb_packet_copy(p, buf, p->iov.size);
+ usbredirparser_send_iso_packet(dev->parser, 0, &iso_packet,
+ buf, p->iov.size);
+ usbredirparser_do_write(dev->parser);
+ }
+ status = dev->endpoint[EP2I(ep)].iso_error;
+ dev->endpoint[EP2I(ep)].iso_error = 0;
+ DPRINTF2("iso-token-out ep %02X status %d len %zd\n", ep, status,
+ p->iov.size);
+ usbredir_handle_status(dev, p, status);
+ }
+}
+
+static void usbredir_stop_iso_stream(USBRedirDevice *dev, uint8_t ep)
+{
+ struct usb_redir_stop_iso_stream_header stop_iso_stream = {
+ .endpoint = ep
+ };
+ if (dev->endpoint[EP2I(ep)].iso_started) {
+ usbredirparser_send_stop_iso_stream(dev->parser, 0, &stop_iso_stream);
+ DPRINTF("iso stream stopped ep %02X\n", ep);
+ dev->endpoint[EP2I(ep)].iso_started = 0;
+ }
+ dev->endpoint[EP2I(ep)].iso_error = 0;
+ usbredir_free_bufpq(dev, ep);
+}
+
+/*
+ * The usb-host may poll the endpoint faster then our guest, resulting in lots
+ * of smaller bulkp-s. The below buffered_bulk_in_complete* functions combine
+ * data from multiple bulkp-s into a single packet, avoiding bufpq overflows.
+ */
+static void usbredir_buffered_bulk_add_data_to_packet(USBRedirDevice *dev,
+ struct buf_packet *bulkp, int count, USBPacket *p, uint8_t ep)
+{
+ usb_packet_copy(p, bulkp->data + bulkp->offset, count);
+ bulkp->offset += count;
+ if (bulkp->offset == bulkp->len) {
+ /* Store status in the last packet with data from this bulkp */
+ usbredir_handle_status(dev, p, bulkp->status);
+ bufp_free(dev, bulkp, ep);
+ }
+}
+
+static void usbredir_buffered_bulk_in_complete_raw(USBRedirDevice *dev,
+ USBPacket *p, uint8_t ep)
+{
+ struct buf_packet *bulkp;
+ int count;
+
+ while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) &&
+ p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) {
+ count = bulkp->len - bulkp->offset;
+ if (count > (p->iov.size - p->actual_length)) {
+ count = p->iov.size - p->actual_length;
+ }
+ usbredir_buffered_bulk_add_data_to_packet(dev, bulkp, count, p, ep);
+ }
+}
+
+static void usbredir_buffered_bulk_in_complete_ftdi(USBRedirDevice *dev,
+ USBPacket *p, uint8_t ep)
+{
+ const int maxp = dev->endpoint[EP2I(ep)].max_packet_size;
+ uint8_t header[2] = { 0, 0 };
+ struct buf_packet *bulkp;
+ int count;
+
+ while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) &&
+ p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) {
+ if (bulkp->len < 2) {
+ WARNING("malformed ftdi bulk in packet\n");
+ bufp_free(dev, bulkp, ep);
+ continue;
+ }
+
+ if ((p->actual_length % maxp) == 0) {
+ usb_packet_copy(p, bulkp->data, 2);
+ memcpy(header, bulkp->data, 2);
+ } else {
+ if (bulkp->data[0] != header[0] || bulkp->data[1] != header[1]) {
+ break; /* Different header, add to next packet */
+ }
+ }
+
+ if (bulkp->offset == 0) {
+ bulkp->offset = 2; /* Skip header */
+ }
+ count = bulkp->len - bulkp->offset;
+ /* Must repeat the header at maxp interval */
+ if (count > (maxp - (p->actual_length % maxp))) {
+ count = maxp - (p->actual_length % maxp);
+ }
+ usbredir_buffered_bulk_add_data_to_packet(dev, bulkp, count, p, ep);
+ }
+}
+
+static void usbredir_buffered_bulk_in_complete(USBRedirDevice *dev,
+ USBPacket *p, uint8_t ep)
+{
+ p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */
+ dev->buffered_bulk_in_complete(dev, p, ep);
+ DPRINTF("bulk-token-in ep %02X status %d len %d id %"PRIu64"\n",
+ ep, p->status, p->actual_length, p->id);
+}
+
+static void usbredir_handle_buffered_bulk_in_data(USBRedirDevice *dev,
+ USBPacket *p, uint8_t ep)
+{
+ /* Input bulk endpoint, buffered packet input */
+ if (!dev->endpoint[EP2I(ep)].bulk_receiving_started) {
+ int bpt;
+ struct usb_redir_start_bulk_receiving_header start = {
+ .endpoint = ep,
+ .stream_id = 0,
+ .no_transfers = 5,
+ };
+ /* Round bytes_per_transfer up to a multiple of max_packet_size */
+ bpt = 512 + dev->endpoint[EP2I(ep)].max_packet_size - 1;
+ bpt /= dev->endpoint[EP2I(ep)].max_packet_size;
+ bpt *= dev->endpoint[EP2I(ep)].max_packet_size;
+ start.bytes_per_transfer = bpt;
+ /* No id, we look at the ep when receiving a status back */
+ usbredirparser_send_start_bulk_receiving(dev->parser, 0, &start);
+ usbredirparser_do_write(dev->parser);
+ DPRINTF("bulk receiving started bytes/transfer %u count %d ep %02X\n",
+ start.bytes_per_transfer, start.no_transfers, ep);
+ dev->endpoint[EP2I(ep)].bulk_receiving_started = 1;
+ /* We don't really want to drop bulk packets ever, but
+ having some upper limit to how much we buffer is good. */
+ dev->endpoint[EP2I(ep)].bufpq_target_size = 5000;
+ dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0;
+ }
+
+ if (QTAILQ_EMPTY(&dev->endpoint[EP2I(ep)].bufpq)) {
+ DPRINTF("bulk-token-in ep %02X, no bulkp\n", ep);
+ assert(dev->endpoint[EP2I(ep)].pending_async_packet == NULL);
+ dev->endpoint[EP2I(ep)].pending_async_packet = p;
+ p->status = USB_RET_ASYNC;
+ return;
+ }
+ usbredir_buffered_bulk_in_complete(dev, p, ep);
+}
+
+static void usbredir_stop_bulk_receiving(USBRedirDevice *dev, uint8_t ep)
+{
+ struct usb_redir_stop_bulk_receiving_header stop_bulk = {
+ .endpoint = ep,
+ .stream_id = 0,
+ };
+ if (dev->endpoint[EP2I(ep)].bulk_receiving_started) {
+ usbredirparser_send_stop_bulk_receiving(dev->parser, 0, &stop_bulk);
+ DPRINTF("bulk receiving stopped ep %02X\n", ep);
+ dev->endpoint[EP2I(ep)].bulk_receiving_started = 0;
+ }
+ usbredir_free_bufpq(dev, ep);
+}
+
+static void usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p,
+ uint8_t ep)
+{
+ struct usb_redir_bulk_packet_header bulk_packet;
+ size_t size = usb_packet_size(p);
+ const int maxp = dev->endpoint[EP2I(ep)].max_packet_size;
+
+ if (usbredir_already_in_flight(dev, p->id)) {
+ p->status = USB_RET_ASYNC;
+ return;
+ }
+
+ if (dev->endpoint[EP2I(ep)].bulk_receiving_enabled) {
+ if (size != 0 && (size % maxp) == 0) {
+ usbredir_handle_buffered_bulk_in_data(dev, p, ep);
+ return;
+ }
+ WARNING("bulk recv invalid size %zd ep %02x, disabling\n", size, ep);
+ assert(dev->endpoint[EP2I(ep)].pending_async_packet == NULL);
+ usbredir_stop_bulk_receiving(dev, ep);
+ dev->endpoint[EP2I(ep)].bulk_receiving_enabled = 0;
+ }
+
+ DPRINTF("bulk-out ep %02X stream %u len %zd id %"PRIu64"\n",
+ ep, p->stream, size, p->id);
+
+ bulk_packet.endpoint = ep;
+ bulk_packet.length = size;
+ bulk_packet.stream_id = p->stream;
+ bulk_packet.length_high = size >> 16;
+ assert(bulk_packet.length_high == 0 ||
+ usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_32bits_bulk_length));
+
+ if (ep & USB_DIR_IN) {
+ usbredirparser_send_bulk_packet(dev->parser, p->id,
+ &bulk_packet, NULL, 0);
+ } else {
+ uint8_t buf[size];
+ usb_packet_copy(p, buf, size);
+ usbredir_log_data(dev, "bulk data out:", buf, size);
+ usbredirparser_send_bulk_packet(dev->parser, p->id,
+ &bulk_packet, buf, size);
+ }
+ usbredirparser_do_write(dev->parser);
+ p->status = USB_RET_ASYNC;
+}
+
+static void usbredir_handle_interrupt_in_data(USBRedirDevice *dev,
+ USBPacket *p, uint8_t ep)
+{
+ /* Input interrupt endpoint, buffered packet input */
+ struct buf_packet *intp;
+ int status, len;
+
+ if (!dev->endpoint[EP2I(ep)].interrupt_started &&
+ !dev->endpoint[EP2I(ep)].interrupt_error) {
+ struct usb_redir_start_interrupt_receiving_header start_int = {
+ .endpoint = ep,
+ };
+ /* No id, we look at the ep when receiving a status back */
+ usbredirparser_send_start_interrupt_receiving(dev->parser, 0,
+ &start_int);
+ usbredirparser_do_write(dev->parser);
+ DPRINTF("interrupt recv started ep %02X\n", ep);
+ dev->endpoint[EP2I(ep)].interrupt_started = 1;
+ /* We don't really want to drop interrupt packets ever, but
+ having some upper limit to how much we buffer is good. */
+ dev->endpoint[EP2I(ep)].bufpq_target_size = 1000;
+ dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0;
+ }
+
+ intp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq);
+ if (intp == NULL) {
+ DPRINTF2("interrupt-token-in ep %02X, no intp\n", ep);
+ /* Check interrupt_error for stream errors */
+ status = dev->endpoint[EP2I(ep)].interrupt_error;
+ dev->endpoint[EP2I(ep)].interrupt_error = 0;
+ if (status) {
+ usbredir_handle_status(dev, p, status);
+ } else {
+ p->status = USB_RET_NAK;
+ }
+ return;
+ }
+ DPRINTF("interrupt-token-in ep %02X status %d len %d\n", ep,
+ intp->status, intp->len);
+
+ status = intp->status;
+ len = intp->len;
+ if (len > p->iov.size) {
+ ERROR("received int data is larger then packet ep %02X\n", ep);
+ len = p->iov.size;
+ status = usb_redir_babble;
+ }
+ usb_packet_copy(p, intp->data, len);
+ bufp_free(dev, intp, ep);
+ usbredir_handle_status(dev, p, status);
+}
+
+/*
+ * Handle interrupt out data, the usbredir protocol expects us to do this
+ * async, so that it can report back a completion status. But guests will
+ * expect immediate completion for an interrupt endpoint, and handling this
+ * async causes migration issues. So we report success directly, counting
+ * on the fact that output interrupt packets normally always succeed.
+ */
+static void usbredir_handle_interrupt_out_data(USBRedirDevice *dev,
+ USBPacket *p, uint8_t ep)
+{
+ struct usb_redir_interrupt_packet_header interrupt_packet;
+ uint8_t buf[p->iov.size];
+
+ DPRINTF("interrupt-out ep %02X len %zd id %"PRIu64"\n", ep,
+ p->iov.size, p->id);
+
+ interrupt_packet.endpoint = ep;
+ interrupt_packet.length = p->iov.size;
+
+ usb_packet_copy(p, buf, p->iov.size);
+ usbredir_log_data(dev, "interrupt data out:", buf, p->iov.size);
+ usbredirparser_send_interrupt_packet(dev->parser, p->id,
+ &interrupt_packet, buf, p->iov.size);
+ usbredirparser_do_write(dev->parser);
+}
+
+static void usbredir_stop_interrupt_receiving(USBRedirDevice *dev,
+ uint8_t ep)
+{
+ struct usb_redir_stop_interrupt_receiving_header stop_interrupt_recv = {
+ .endpoint = ep
+ };
+ if (dev->endpoint[EP2I(ep)].interrupt_started) {
+ usbredirparser_send_stop_interrupt_receiving(dev->parser, 0,
+ &stop_interrupt_recv);
+ DPRINTF("interrupt recv stopped ep %02X\n", ep);
+ dev->endpoint[EP2I(ep)].interrupt_started = 0;
+ }
+ dev->endpoint[EP2I(ep)].interrupt_error = 0;
+ usbredir_free_bufpq(dev, ep);
+}
+
+static void usbredir_handle_data(USBDevice *udev, USBPacket *p)
+{
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+ uint8_t ep;
+
+ ep = p->ep->nr;
+ if (p->pid == USB_TOKEN_IN) {
+ ep |= USB_DIR_IN;
+ }
+
+ switch (dev->endpoint[EP2I(ep)].type) {
+ case USB_ENDPOINT_XFER_CONTROL:
+ ERROR("handle_data called for control transfer on ep %02X\n", ep);
+ p->status = USB_RET_NAK;
+ break;
+ case USB_ENDPOINT_XFER_BULK:
+ if (p->state == USB_PACKET_SETUP && p->pid == USB_TOKEN_IN &&
+ p->ep->pipeline) {
+ p->status = USB_RET_ADD_TO_QUEUE;
+ break;
+ }
+ usbredir_handle_bulk_data(dev, p, ep);
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ usbredir_handle_iso_data(dev, p, ep);
+ break;
+ case USB_ENDPOINT_XFER_INT:
+ if (ep & USB_DIR_IN) {
+ usbredir_handle_interrupt_in_data(dev, p, ep);
+ } else {
+ usbredir_handle_interrupt_out_data(dev, p, ep);
+ }
+ break;
+ default:
+ ERROR("handle_data ep %02X has unknown type %d\n", ep,
+ dev->endpoint[EP2I(ep)].type);
+ p->status = USB_RET_NAK;
+ }
+}
+
+static void usbredir_flush_ep_queue(USBDevice *dev, USBEndpoint *ep)
+{
+ if (ep->pid == USB_TOKEN_IN && ep->pipeline) {
+ usb_ep_combine_input_packets(ep);
+ }
+}
+
+static void usbredir_stop_ep(USBRedirDevice *dev, int i)
+{
+ uint8_t ep = I2EP(i);
+
+ switch (dev->endpoint[i].type) {
+ case USB_ENDPOINT_XFER_BULK:
+ if (ep & USB_DIR_IN) {
+ usbredir_stop_bulk_receiving(dev, ep);
+ }
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ usbredir_stop_iso_stream(dev, ep);
+ break;
+ case USB_ENDPOINT_XFER_INT:
+ if (ep & USB_DIR_IN) {
+ usbredir_stop_interrupt_receiving(dev, ep);
+ }
+ break;
+ }
+ usbredir_free_bufpq(dev, ep);
+}
+
+static void usbredir_ep_stopped(USBDevice *udev, USBEndpoint *uep)
+{
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+
+ usbredir_stop_ep(dev, USBEP2I(uep));
+ usbredirparser_do_write(dev->parser);
+}
+
+static void usbredir_set_config(USBRedirDevice *dev, USBPacket *p,
+ int config)
+{
+ struct usb_redir_set_configuration_header set_config;
+ int i;
+
+ DPRINTF("set config %d id %"PRIu64"\n", config, p->id);
+
+ for (i = 0; i < MAX_ENDPOINTS; i++) {
+ usbredir_stop_ep(dev, i);
+ }
+
+ set_config.configuration = config;
+ usbredirparser_send_set_configuration(dev->parser, p->id, &set_config);
+ usbredirparser_do_write(dev->parser);
+ p->status = USB_RET_ASYNC;
+}
+
+static void usbredir_get_config(USBRedirDevice *dev, USBPacket *p)
+{
+ DPRINTF("get config id %"PRIu64"\n", p->id);
+
+ usbredirparser_send_get_configuration(dev->parser, p->id);
+ usbredirparser_do_write(dev->parser);
+ p->status = USB_RET_ASYNC;
+}
+
+static void usbredir_set_interface(USBRedirDevice *dev, USBPacket *p,
+ int interface, int alt)
+{
+ struct usb_redir_set_alt_setting_header set_alt;
+ int i;
+
+ DPRINTF("set interface %d alt %d id %"PRIu64"\n", interface, alt, p->id);
+
+ for (i = 0; i < MAX_ENDPOINTS; i++) {
+ if (dev->endpoint[i].interface == interface) {
+ usbredir_stop_ep(dev, i);
+ }
+ }
+
+ set_alt.interface = interface;
+ set_alt.alt = alt;
+ usbredirparser_send_set_alt_setting(dev->parser, p->id, &set_alt);
+ usbredirparser_do_write(dev->parser);
+ p->status = USB_RET_ASYNC;
+}
+
+static void usbredir_get_interface(USBRedirDevice *dev, USBPacket *p,
+ int interface)
+{
+ struct usb_redir_get_alt_setting_header get_alt;
+
+ DPRINTF("get interface %d id %"PRIu64"\n", interface, p->id);
+
+ get_alt.interface = interface;
+ usbredirparser_send_get_alt_setting(dev->parser, p->id, &get_alt);
+ usbredirparser_do_write(dev->parser);
+ p->status = USB_RET_ASYNC;
+}
+
+static void usbredir_handle_control(USBDevice *udev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+ struct usb_redir_control_packet_header control_packet;
+
+ if (usbredir_already_in_flight(dev, p->id)) {
+ p->status = USB_RET_ASYNC;
+ return;
+ }
+
+ /* Special cases for certain standard device requests */
+ switch (request) {
+ case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+ DPRINTF("set address %d\n", value);
+ dev->dev.addr = value;
+ return;
+ case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+ usbredir_set_config(dev, p, value & 0xff);
+ return;
+ case DeviceRequest | USB_REQ_GET_CONFIGURATION:
+ usbredir_get_config(dev, p);
+ return;
+ case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+ usbredir_set_interface(dev, p, index, value);
+ return;
+ case InterfaceRequest | USB_REQ_GET_INTERFACE:
+ usbredir_get_interface(dev, p, index);
+ return;
+ }
+
+ /* Normal ctrl requests, note request is (bRequestType << 8) | bRequest */
+ DPRINTF(
+ "ctrl-out type 0x%x req 0x%x val 0x%x index %d len %d id %"PRIu64"\n",
+ request >> 8, request & 0xff, value, index, length, p->id);
+
+ control_packet.request = request & 0xFF;
+ control_packet.requesttype = request >> 8;
+ control_packet.endpoint = control_packet.requesttype & USB_DIR_IN;
+ control_packet.value = value;
+ control_packet.index = index;
+ control_packet.length = length;
+
+ if (control_packet.requesttype & USB_DIR_IN) {
+ usbredirparser_send_control_packet(dev->parser, p->id,
+ &control_packet, NULL, 0);
+ } else {
+ usbredir_log_data(dev, "ctrl data out:", data, length);
+ usbredirparser_send_control_packet(dev->parser, p->id,
+ &control_packet, data, length);
+ }
+ usbredirparser_do_write(dev->parser);
+ p->status = USB_RET_ASYNC;
+}
+
+static int usbredir_alloc_streams(USBDevice *udev, USBEndpoint **eps,
+ int nr_eps, int streams)
+{
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+#if USBREDIR_VERSION >= 0x000700
+ struct usb_redir_alloc_bulk_streams_header alloc_streams;
+ int i;
+
+ if (!usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_bulk_streams)) {
+ ERROR("peer does not support streams\n");
+ goto reject;
+ }
+
+ if (streams == 0) {
+ ERROR("request to allocate 0 streams\n");
+ return -1;
+ }
+
+ alloc_streams.no_streams = streams;
+ alloc_streams.endpoints = 0;
+ for (i = 0; i < nr_eps; i++) {
+ alloc_streams.endpoints |= 1 << USBEP2I(eps[i]);
+ }
+ usbredirparser_send_alloc_bulk_streams(dev->parser, 0, &alloc_streams);
+ usbredirparser_do_write(dev->parser);
+
+ return 0;
+#else
+ ERROR("usbredir_alloc_streams not implemented\n");
+ goto reject;
+#endif
+reject:
+ ERROR("streams are not available, disconnecting\n");
+ qemu_bh_schedule(dev->device_reject_bh);
+ return -1;
+}
+
+static void usbredir_free_streams(USBDevice *udev, USBEndpoint **eps,
+ int nr_eps)
+{
+#if USBREDIR_VERSION >= 0x000700
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+ struct usb_redir_free_bulk_streams_header free_streams;
+ int i;
+
+ if (!usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_bulk_streams)) {
+ return;
+ }
+
+ free_streams.endpoints = 0;
+ for (i = 0; i < nr_eps; i++) {
+ free_streams.endpoints |= 1 << USBEP2I(eps[i]);
+ }
+ usbredirparser_send_free_bulk_streams(dev->parser, 0, &free_streams);
+ usbredirparser_do_write(dev->parser);
+#endif
+}
+
+/*
+ * Close events can be triggered by usbredirparser_do_write which gets called
+ * from within the USBDevice data / control packet callbacks and doing a
+ * usb_detach from within these callbacks is not a good idea.
+ *
+ * So we use a bh handler to take care of close events.
+ */
+static void usbredir_chardev_close_bh(void *opaque)
+{
+ USBRedirDevice *dev = opaque;
+
+ qemu_bh_cancel(dev->device_reject_bh);
+ usbredir_device_disconnect(dev);
+
+ if (dev->parser) {
+ DPRINTF("destroying usbredirparser\n");
+ usbredirparser_destroy(dev->parser);
+ dev->parser = NULL;
+ }
+ if (dev->watch) {
+ g_source_remove(dev->watch);
+ dev->watch = 0;
+ }
+}
+
+static void usbredir_create_parser(USBRedirDevice *dev)
+{
+ uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, };
+ int flags = 0;
+
+ DPRINTF("creating usbredirparser\n");
+
+ dev->parser = qemu_oom_check(usbredirparser_create());
+ dev->parser->priv = dev;
+ dev->parser->log_func = usbredir_log;
+ dev->parser->read_func = usbredir_read;
+ dev->parser->write_func = usbredir_write;
+ dev->parser->hello_func = usbredir_hello;
+ dev->parser->device_connect_func = usbredir_device_connect;
+ dev->parser->device_disconnect_func = usbredir_device_disconnect;
+ dev->parser->interface_info_func = usbredir_interface_info;
+ dev->parser->ep_info_func = usbredir_ep_info;
+ dev->parser->configuration_status_func = usbredir_configuration_status;
+ dev->parser->alt_setting_status_func = usbredir_alt_setting_status;
+ dev->parser->iso_stream_status_func = usbredir_iso_stream_status;
+ dev->parser->interrupt_receiving_status_func =
+ usbredir_interrupt_receiving_status;
+ dev->parser->bulk_streams_status_func = usbredir_bulk_streams_status;
+ dev->parser->bulk_receiving_status_func = usbredir_bulk_receiving_status;
+ dev->parser->control_packet_func = usbredir_control_packet;
+ dev->parser->bulk_packet_func = usbredir_bulk_packet;
+ dev->parser->iso_packet_func = usbredir_iso_packet;
+ dev->parser->interrupt_packet_func = usbredir_interrupt_packet;
+ dev->parser->buffered_bulk_packet_func = usbredir_buffered_bulk_packet;
+ dev->read_buf = NULL;
+ dev->read_buf_size = 0;
+
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_filter);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_receiving);
+#if USBREDIR_VERSION >= 0x000700
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams);
+#endif
+
+ if (runstate_check(RUN_STATE_INMIGRATE)) {
+ flags |= usbredirparser_fl_no_hello;
+ }
+ usbredirparser_init(dev->parser, VERSION, caps, USB_REDIR_CAPS_SIZE,
+ flags);
+ usbredirparser_do_write(dev->parser);
+}
+
+static void usbredir_reject_device(USBRedirDevice *dev)
+{
+ usbredir_device_disconnect(dev);
+ if (usbredirparser_peer_has_cap(dev->parser, usb_redir_cap_filter)) {
+ usbredirparser_send_filter_reject(dev->parser);
+ usbredirparser_do_write(dev->parser);
+ }
+}
+
+/*
+ * We may need to reject the device when the hcd calls alloc_streams, doing
+ * an usb_detach from within a hcd call is not a good idea, hence this bh.
+ */
+static void usbredir_device_reject_bh(void *opaque)
+{
+ USBRedirDevice *dev = opaque;
+
+ usbredir_reject_device(dev);
+}
+
+static void usbredir_do_attach(void *opaque)
+{
+ USBRedirDevice *dev = opaque;
+ Error *local_err = NULL;
+
+ /* In order to work properly with XHCI controllers we need these caps */
+ if ((dev->dev.port->speedmask & USB_SPEED_MASK_SUPER) && !(
+ usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_ep_info_max_packet_size) &&
+ usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_32bits_bulk_length) &&
+ usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_64bits_ids))) {
+ ERROR("usb-redir-host lacks capabilities needed for use with XHCI\n");
+ usbredir_reject_device(dev);
+ return;
+ }
+
+ usb_device_attach(&dev->dev, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ WARNING("rejecting device due to speed mismatch\n");
+ usbredir_reject_device(dev);
+ }
+}
+
+/*
+ * chardev callbacks
+ */
+
+static int usbredir_chardev_can_read(void *opaque)
+{
+ USBRedirDevice *dev = opaque;
+
+ if (!dev->parser) {
+ WARNING("chardev_can_read called on non open chardev!\n");
+ return 0;
+ }
+
+ /* Don't read new data from the chardev until our state is fully synced */
+ if (!runstate_check(RUN_STATE_RUNNING)) {
+ return 0;
+ }
+
+ /* usbredir_parser_do_read will consume *all* data we give it */
+ return 1024 * 1024;
+}
+
+static void usbredir_chardev_read(void *opaque, const uint8_t *buf, int size)
+{
+ USBRedirDevice *dev = opaque;
+
+ /* No recursion allowed! */
+ assert(dev->read_buf == NULL);
+
+ dev->read_buf = buf;
+ dev->read_buf_size = size;
+
+ usbredirparser_do_read(dev->parser);
+ /* Send any acks, etc. which may be queued now */
+ usbredirparser_do_write(dev->parser);
+}
+
+static void usbredir_chardev_event(void *opaque, int event)
+{
+ USBRedirDevice *dev = opaque;
+
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ DPRINTF("chardev open\n");
+ /* Make sure any pending closes are handled (no-op if none pending) */
+ usbredir_chardev_close_bh(dev);
+ qemu_bh_cancel(dev->chardev_close_bh);
+ usbredir_create_parser(dev);
+ break;
+ case CHR_EVENT_CLOSED:
+ DPRINTF("chardev close\n");
+ qemu_bh_schedule(dev->chardev_close_bh);
+ break;
+ }
+}
+
+/*
+ * init + destroy
+ */
+
+static void usbredir_vm_state_change(void *priv, int running, RunState state)
+{
+ USBRedirDevice *dev = priv;
+
+ if (state == RUN_STATE_RUNNING && dev->parser != NULL) {
+ usbredirparser_do_write(dev->parser); /* Flush any pending writes */
+ }
+}
+
+static void usbredir_init_endpoints(USBRedirDevice *dev)
+{
+ int i;
+
+ usb_ep_init(&dev->dev);
+ memset(dev->endpoint, 0, sizeof(dev->endpoint));
+ for (i = 0; i < MAX_ENDPOINTS; i++) {
+ dev->endpoint[i].dev = dev;
+ QTAILQ_INIT(&dev->endpoint[i].bufpq);
+ }
+}
+
+static void usbredir_realize(USBDevice *udev, Error **errp)
+{
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+ int i;
+
+ if (dev->cs == NULL) {
+ error_setg(errp, QERR_MISSING_PARAMETER, "chardev");
+ return;
+ }
+
+ if (dev->filter_str) {
+ i = usbredirfilter_string_to_rules(dev->filter_str, ":", "|",
+ &dev->filter_rules,
+ &dev->filter_rules_count);
+ if (i) {
+ error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "filter",
+ "a usb device filter string");
+ return;
+ }
+ }
+
+ dev->chardev_close_bh = qemu_bh_new(usbredir_chardev_close_bh, dev);
+ dev->device_reject_bh = qemu_bh_new(usbredir_device_reject_bh, dev);
+ dev->attach_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, usbredir_do_attach, dev);
+
+ packet_id_queue_init(&dev->cancelled, dev, "cancelled");
+ packet_id_queue_init(&dev->already_in_flight, dev, "already-in-flight");
+ usbredir_init_endpoints(dev);
+
+ /* We'll do the attach once we receive the speed from the usb-host */
+ udev->auto_attach = 0;
+
+ /* Will be cleared during setup when we find conflicts */
+ dev->compatible_speedmask = USB_SPEED_MASK_FULL | USB_SPEED_MASK_HIGH;
+
+ /* Let the backend know we are ready */
+ qemu_chr_add_handlers(dev->cs, usbredir_chardev_can_read,
+ usbredir_chardev_read, usbredir_chardev_event, dev);
+
+ qemu_add_vm_change_state_handler(usbredir_vm_state_change, dev);
+}
+
+static void usbredir_cleanup_device_queues(USBRedirDevice *dev)
+{
+ int i;
+
+ packet_id_queue_empty(&dev->cancelled);
+ packet_id_queue_empty(&dev->already_in_flight);
+ for (i = 0; i < MAX_ENDPOINTS; i++) {
+ usbredir_free_bufpq(dev, I2EP(i));
+ }
+}
+
+static void usbredir_handle_destroy(USBDevice *udev)
+{
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+
+ qemu_chr_delete(dev->cs);
+ dev->cs = NULL;
+ /* Note must be done after qemu_chr_close, as that causes a close event */
+ qemu_bh_delete(dev->chardev_close_bh);
+ qemu_bh_delete(dev->device_reject_bh);
+
+ timer_del(dev->attach_timer);
+ timer_free(dev->attach_timer);
+
+ usbredir_cleanup_device_queues(dev);
+
+ if (dev->parser) {
+ usbredirparser_destroy(dev->parser);
+ }
+ if (dev->watch) {
+ g_source_remove(dev->watch);
+ }
+
+ free(dev->filter_rules);
+}
+
+static int usbredir_check_filter(USBRedirDevice *dev)
+{
+ if (dev->interface_info.interface_count == NO_INTERFACE_INFO) {
+ ERROR("No interface info for device\n");
+ goto error;
+ }
+
+ if (dev->filter_rules) {
+ if (!usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_connect_device_version)) {
+ ERROR("Device filter specified and peer does not have the "
+ "connect_device_version capability\n");
+ goto error;
+ }
+
+ if (usbredirfilter_check(
+ dev->filter_rules,
+ dev->filter_rules_count,
+ dev->device_info.device_class,
+ dev->device_info.device_subclass,
+ dev->device_info.device_protocol,
+ dev->interface_info.interface_class,
+ dev->interface_info.interface_subclass,
+ dev->interface_info.interface_protocol,
+ dev->interface_info.interface_count,
+ dev->device_info.vendor_id,
+ dev->device_info.product_id,
+ dev->device_info.device_version_bcd,
+ 0) != 0) {
+ goto error;
+ }
+ }
+
+ return 0;
+
+error:
+ usbredir_reject_device(dev);
+ return -1;
+}
+
+static void usbredir_check_bulk_receiving(USBRedirDevice *dev)
+{
+ int i, j, quirks;
+
+ if (!usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_bulk_receiving)) {
+ return;
+ }
+
+ for (i = EP2I(USB_DIR_IN); i < MAX_ENDPOINTS; i++) {
+ dev->endpoint[i].bulk_receiving_enabled = 0;
+ }
+ for (i = 0; i < dev->interface_info.interface_count; i++) {
+ quirks = usb_get_quirks(dev->device_info.vendor_id,
+ dev->device_info.product_id,
+ dev->interface_info.interface_class[i],
+ dev->interface_info.interface_subclass[i],
+ dev->interface_info.interface_protocol[i]);
+ if (!(quirks & USB_QUIRK_BUFFER_BULK_IN)) {
+ continue;
+ }
+ if (quirks & USB_QUIRK_IS_FTDI) {
+ dev->buffered_bulk_in_complete =
+ usbredir_buffered_bulk_in_complete_ftdi;
+ } else {
+ dev->buffered_bulk_in_complete =
+ usbredir_buffered_bulk_in_complete_raw;
+ }
+
+ for (j = EP2I(USB_DIR_IN); j < MAX_ENDPOINTS; j++) {
+ if (dev->endpoint[j].interface ==
+ dev->interface_info.interface[i] &&
+ dev->endpoint[j].type == USB_ENDPOINT_XFER_BULK &&
+ dev->endpoint[j].max_packet_size != 0) {
+ dev->endpoint[j].bulk_receiving_enabled = 1;
+ /*
+ * With buffering pipelining is not necessary. Also packet
+ * combining and bulk in buffering don't play nice together!
+ */
+ I2USBEP(dev, j)->pipeline = false;
+ break; /* Only buffer for the first ep of each intf */
+ }
+ }
+ }
+}
+
+/*
+ * usbredirparser packet complete callbacks
+ */
+
+static void usbredir_handle_status(USBRedirDevice *dev, USBPacket *p,
+ int status)
+{
+ switch (status) {
+ case usb_redir_success:
+ p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */
+ break;
+ case usb_redir_stall:
+ p->status = USB_RET_STALL;
+ break;
+ case usb_redir_cancelled:
+ /*
+ * When the usbredir-host unredirects a device, it will report a status
+ * of cancelled for all pending packets, followed by a disconnect msg.
+ */
+ p->status = USB_RET_IOERROR;
+ break;
+ case usb_redir_inval:
+ WARNING("got invalid param error from usb-host?\n");
+ p->status = USB_RET_IOERROR;
+ break;
+ case usb_redir_babble:
+ p->status = USB_RET_BABBLE;
+ break;
+ case usb_redir_ioerror:
+ case usb_redir_timeout:
+ default:
+ p->status = USB_RET_IOERROR;
+ }
+}
+
+static void usbredir_hello(void *priv, struct usb_redir_hello_header *h)
+{
+ USBRedirDevice *dev = priv;
+
+ /* Try to send the filter info now that we've the usb-host's caps */
+ if (usbredirparser_peer_has_cap(dev->parser, usb_redir_cap_filter) &&
+ dev->filter_rules) {
+ usbredirparser_send_filter_filter(dev->parser, dev->filter_rules,
+ dev->filter_rules_count);
+ usbredirparser_do_write(dev->parser);
+ }
+}
+
+static void usbredir_device_connect(void *priv,
+ struct usb_redir_device_connect_header *device_connect)
+{
+ USBRedirDevice *dev = priv;
+ const char *speed;
+
+ if (timer_pending(dev->attach_timer) || dev->dev.attached) {
+ ERROR("Received device connect while already connected\n");
+ return;
+ }
+
+ switch (device_connect->speed) {
+ case usb_redir_speed_low:
+ speed = "low speed";
+ dev->dev.speed = USB_SPEED_LOW;
+ dev->compatible_speedmask &= ~USB_SPEED_MASK_FULL;
+ dev->compatible_speedmask &= ~USB_SPEED_MASK_HIGH;
+ break;
+ case usb_redir_speed_full:
+ speed = "full speed";
+ dev->dev.speed = USB_SPEED_FULL;
+ dev->compatible_speedmask &= ~USB_SPEED_MASK_HIGH;
+ break;
+ case usb_redir_speed_high:
+ speed = "high speed";
+ dev->dev.speed = USB_SPEED_HIGH;
+ break;
+ case usb_redir_speed_super:
+ speed = "super speed";
+ dev->dev.speed = USB_SPEED_SUPER;
+ break;
+ default:
+ speed = "unknown speed";
+ dev->dev.speed = USB_SPEED_FULL;
+ }
+
+ if (usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_connect_device_version)) {
+ INFO("attaching %s device %04x:%04x version %d.%d class %02x\n",
+ speed, device_connect->vendor_id, device_connect->product_id,
+ ((device_connect->device_version_bcd & 0xf000) >> 12) * 10 +
+ ((device_connect->device_version_bcd & 0x0f00) >> 8),
+ ((device_connect->device_version_bcd & 0x00f0) >> 4) * 10 +
+ ((device_connect->device_version_bcd & 0x000f) >> 0),
+ device_connect->device_class);
+ } else {
+ INFO("attaching %s device %04x:%04x class %02x\n", speed,
+ device_connect->vendor_id, device_connect->product_id,
+ device_connect->device_class);
+ }
+
+ dev->dev.speedmask = (1 << dev->dev.speed) | dev->compatible_speedmask;
+ dev->device_info = *device_connect;
+
+ if (usbredir_check_filter(dev)) {
+ WARNING("Device %04x:%04x rejected by device filter, not attaching\n",
+ device_connect->vendor_id, device_connect->product_id);
+ return;
+ }
+
+ usbredir_check_bulk_receiving(dev);
+ timer_mod(dev->attach_timer, dev->next_attach_time);
+}
+
+static void usbredir_device_disconnect(void *priv)
+{
+ USBRedirDevice *dev = priv;
+
+ /* Stop any pending attaches */
+ timer_del(dev->attach_timer);
+
+ if (dev->dev.attached) {
+ DPRINTF("detaching device\n");
+ usb_device_detach(&dev->dev);
+ /*
+ * Delay next usb device attach to give the guest a chance to see
+ * see the detach / attach in case of quick close / open succession
+ */
+ dev->next_attach_time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 200;
+ }
+
+ /* Reset state so that the next dev connected starts with a clean slate */
+ usbredir_cleanup_device_queues(dev);
+ usbredir_init_endpoints(dev);
+ dev->interface_info.interface_count = NO_INTERFACE_INFO;
+ dev->dev.addr = 0;
+ dev->dev.speed = 0;
+ dev->compatible_speedmask = USB_SPEED_MASK_FULL | USB_SPEED_MASK_HIGH;
+}
+
+static void usbredir_interface_info(void *priv,
+ struct usb_redir_interface_info_header *interface_info)
+{
+ USBRedirDevice *dev = priv;
+
+ dev->interface_info = *interface_info;
+
+ /*
+ * If we receive interface info after the device has already been
+ * connected (ie on a set_config), re-check interface dependent things.
+ */
+ if (timer_pending(dev->attach_timer) || dev->dev.attached) {
+ usbredir_check_bulk_receiving(dev);
+ if (usbredir_check_filter(dev)) {
+ ERROR("Device no longer matches filter after interface info "
+ "change, disconnecting!\n");
+ }
+ }
+}
+
+static void usbredir_mark_speed_incompatible(USBRedirDevice *dev, int speed)
+{
+ dev->compatible_speedmask &= ~(1 << speed);
+ dev->dev.speedmask = (1 << dev->dev.speed) | dev->compatible_speedmask;
+}
+
+static void usbredir_set_pipeline(USBRedirDevice *dev, struct USBEndpoint *uep)
+{
+ if (uep->type != USB_ENDPOINT_XFER_BULK) {
+ return;
+ }
+ if (uep->pid == USB_TOKEN_OUT) {
+ uep->pipeline = true;
+ }
+ if (uep->pid == USB_TOKEN_IN && uep->max_packet_size != 0 &&
+ usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_32bits_bulk_length)) {
+ uep->pipeline = true;
+ }
+}
+
+static void usbredir_setup_usb_eps(USBRedirDevice *dev)
+{
+ struct USBEndpoint *usb_ep;
+ int i;
+
+ for (i = 0; i < MAX_ENDPOINTS; i++) {
+ usb_ep = I2USBEP(dev, i);
+ usb_ep->type = dev->endpoint[i].type;
+ usb_ep->ifnum = dev->endpoint[i].interface;
+ usb_ep->max_packet_size = dev->endpoint[i].max_packet_size;
+ usb_ep->max_streams = dev->endpoint[i].max_streams;
+ usbredir_set_pipeline(dev, usb_ep);
+ }
+}
+
+static void usbredir_ep_info(void *priv,
+ struct usb_redir_ep_info_header *ep_info)
+{
+ USBRedirDevice *dev = priv;
+ int i;
+
+ for (i = 0; i < MAX_ENDPOINTS; i++) {
+ dev->endpoint[i].type = ep_info->type[i];
+ dev->endpoint[i].interval = ep_info->interval[i];
+ dev->endpoint[i].interface = ep_info->interface[i];
+ if (usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_ep_info_max_packet_size)) {
+ dev->endpoint[i].max_packet_size = ep_info->max_packet_size[i];
+ }
+#if USBREDIR_VERSION >= 0x000700
+ if (usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_bulk_streams)) {
+ dev->endpoint[i].max_streams = ep_info->max_streams[i];
+ }
+#endif
+ switch (dev->endpoint[i].type) {
+ case usb_redir_type_invalid:
+ break;
+ case usb_redir_type_iso:
+ usbredir_mark_speed_incompatible(dev, USB_SPEED_FULL);
+ usbredir_mark_speed_incompatible(dev, USB_SPEED_HIGH);
+ /* Fall through */
+ case usb_redir_type_interrupt:
+ if (!usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_ep_info_max_packet_size) ||
+ ep_info->max_packet_size[i] > 64) {
+ usbredir_mark_speed_incompatible(dev, USB_SPEED_FULL);
+ }
+ if (!usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_ep_info_max_packet_size) ||
+ ep_info->max_packet_size[i] > 1024) {
+ usbredir_mark_speed_incompatible(dev, USB_SPEED_HIGH);
+ }
+ if (dev->endpoint[i].interval == 0) {
+ ERROR("Received 0 interval for isoc or irq endpoint\n");
+ usbredir_reject_device(dev);
+ return;
+ }
+ /* Fall through */
+ case usb_redir_type_control:
+ case usb_redir_type_bulk:
+ DPRINTF("ep: %02X type: %d interface: %d\n", I2EP(i),
+ dev->endpoint[i].type, dev->endpoint[i].interface);
+ break;
+ default:
+ ERROR("Received invalid endpoint type\n");
+ usbredir_reject_device(dev);
+ return;
+ }
+ }
+ /* The new ep info may have caused a speed incompatibility, recheck */
+ if (dev->dev.attached &&
+ !(dev->dev.port->speedmask & dev->dev.speedmask)) {
+ ERROR("Device no longer matches speed after endpoint info change, "
+ "disconnecting!\n");
+ usbredir_reject_device(dev);
+ return;
+ }
+ usbredir_setup_usb_eps(dev);
+ usbredir_check_bulk_receiving(dev);
+}
+
+static void usbredir_configuration_status(void *priv, uint64_t id,
+ struct usb_redir_configuration_status_header *config_status)
+{
+ USBRedirDevice *dev = priv;
+ USBPacket *p;
+
+ DPRINTF("set config status %d config %d id %"PRIu64"\n",
+ config_status->status, config_status->configuration, id);
+
+ p = usbredir_find_packet_by_id(dev, 0, id);
+ if (p) {
+ if (dev->dev.setup_buf[0] & USB_DIR_IN) {
+ dev->dev.data_buf[0] = config_status->configuration;
+ p->actual_length = 1;
+ }
+ usbredir_handle_status(dev, p, config_status->status);
+ usb_generic_async_ctrl_complete(&dev->dev, p);
+ }
+}
+
+static void usbredir_alt_setting_status(void *priv, uint64_t id,
+ struct usb_redir_alt_setting_status_header *alt_setting_status)
+{
+ USBRedirDevice *dev = priv;
+ USBPacket *p;
+
+ DPRINTF("alt status %d intf %d alt %d id: %"PRIu64"\n",
+ alt_setting_status->status, alt_setting_status->interface,
+ alt_setting_status->alt, id);
+
+ p = usbredir_find_packet_by_id(dev, 0, id);
+ if (p) {
+ if (dev->dev.setup_buf[0] & USB_DIR_IN) {
+ dev->dev.data_buf[0] = alt_setting_status->alt;
+ p->actual_length = 1;
+ }
+ usbredir_handle_status(dev, p, alt_setting_status->status);
+ usb_generic_async_ctrl_complete(&dev->dev, p);
+ }
+}
+
+static void usbredir_iso_stream_status(void *priv, uint64_t id,
+ struct usb_redir_iso_stream_status_header *iso_stream_status)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t ep = iso_stream_status->endpoint;
+
+ DPRINTF("iso status %d ep %02X id %"PRIu64"\n", iso_stream_status->status,
+ ep, id);
+
+ if (!dev->dev.attached || !dev->endpoint[EP2I(ep)].iso_started) {
+ return;
+ }
+
+ dev->endpoint[EP2I(ep)].iso_error = iso_stream_status->status;
+ if (iso_stream_status->status == usb_redir_stall) {
+ DPRINTF("iso stream stopped by peer ep %02X\n", ep);
+ dev->endpoint[EP2I(ep)].iso_started = 0;
+ }
+}
+
+static void usbredir_interrupt_receiving_status(void *priv, uint64_t id,
+ struct usb_redir_interrupt_receiving_status_header
+ *interrupt_receiving_status)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t ep = interrupt_receiving_status->endpoint;
+
+ DPRINTF("interrupt recv status %d ep %02X id %"PRIu64"\n",
+ interrupt_receiving_status->status, ep, id);
+
+ if (!dev->dev.attached || !dev->endpoint[EP2I(ep)].interrupt_started) {
+ return;
+ }
+
+ dev->endpoint[EP2I(ep)].interrupt_error =
+ interrupt_receiving_status->status;
+ if (interrupt_receiving_status->status == usb_redir_stall) {
+ DPRINTF("interrupt receiving stopped by peer ep %02X\n", ep);
+ dev->endpoint[EP2I(ep)].interrupt_started = 0;
+ }
+}
+
+static void usbredir_bulk_streams_status(void *priv, uint64_t id,
+ struct usb_redir_bulk_streams_status_header *bulk_streams_status)
+{
+#if USBREDIR_VERSION >= 0x000700
+ USBRedirDevice *dev = priv;
+
+ if (bulk_streams_status->status == usb_redir_success) {
+ DPRINTF("bulk streams status %d eps %08x\n",
+ bulk_streams_status->status, bulk_streams_status->endpoints);
+ } else {
+ ERROR("bulk streams %s failed status %d eps %08x\n",
+ (bulk_streams_status->no_streams == 0) ? "free" : "alloc",
+ bulk_streams_status->status, bulk_streams_status->endpoints);
+ ERROR("usb-redir-host does not provide streams, disconnecting\n");
+ usbredir_reject_device(dev);
+ }
+#endif
+}
+
+static void usbredir_bulk_receiving_status(void *priv, uint64_t id,
+ struct usb_redir_bulk_receiving_status_header *bulk_receiving_status)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t ep = bulk_receiving_status->endpoint;
+
+ DPRINTF("bulk recv status %d ep %02X id %"PRIu64"\n",
+ bulk_receiving_status->status, ep, id);
+
+ if (!dev->dev.attached || !dev->endpoint[EP2I(ep)].bulk_receiving_started) {
+ return;
+ }
+
+ if (bulk_receiving_status->status == usb_redir_stall) {
+ DPRINTF("bulk receiving stopped by peer ep %02X\n", ep);
+ dev->endpoint[EP2I(ep)].bulk_receiving_started = 0;
+ }
+}
+
+static void usbredir_control_packet(void *priv, uint64_t id,
+ struct usb_redir_control_packet_header *control_packet,
+ uint8_t *data, int data_len)
+{
+ USBRedirDevice *dev = priv;
+ USBPacket *p;
+ int len = control_packet->length;
+
+ DPRINTF("ctrl-in status %d len %d id %"PRIu64"\n", control_packet->status,
+ len, id);
+
+ /* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices
+ * to work redirected to a not superspeed capable hcd */
+ if (dev->dev.speed == USB_SPEED_SUPER &&
+ !((dev->dev.port->speedmask & USB_SPEED_MASK_SUPER)) &&
+ control_packet->requesttype == 0x80 &&
+ control_packet->request == 6 &&
+ control_packet->value == 0x100 && control_packet->index == 0 &&
+ data_len >= 18 && data[7] == 9) {
+ data[7] = 64;
+ }
+
+ p = usbredir_find_packet_by_id(dev, 0, id);
+ if (p) {
+ usbredir_handle_status(dev, p, control_packet->status);
+ if (data_len > 0) {
+ usbredir_log_data(dev, "ctrl data in:", data, data_len);
+ if (data_len > sizeof(dev->dev.data_buf)) {
+ ERROR("ctrl buffer too small (%d > %zu)\n",
+ data_len, sizeof(dev->dev.data_buf));
+ p->status = USB_RET_STALL;
+ data_len = len = sizeof(dev->dev.data_buf);
+ }
+ memcpy(dev->dev.data_buf, data, data_len);
+ }
+ p->actual_length = len;
+ usb_generic_async_ctrl_complete(&dev->dev, p);
+ }
+ free(data);
+}
+
+static void usbredir_bulk_packet(void *priv, uint64_t id,
+ struct usb_redir_bulk_packet_header *bulk_packet,
+ uint8_t *data, int data_len)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t ep = bulk_packet->endpoint;
+ int len = (bulk_packet->length_high << 16) | bulk_packet->length;
+ USBPacket *p;
+
+ DPRINTF("bulk-in status %d ep %02X stream %u len %d id %"PRIu64"\n",
+ bulk_packet->status, ep, bulk_packet->stream_id, len, id);
+
+ p = usbredir_find_packet_by_id(dev, ep, id);
+ if (p) {
+ size_t size = usb_packet_size(p);
+ usbredir_handle_status(dev, p, bulk_packet->status);
+ if (data_len > 0) {
+ usbredir_log_data(dev, "bulk data in:", data, data_len);
+ if (data_len > size) {
+ ERROR("bulk got more data then requested (%d > %zd)\n",
+ data_len, p->iov.size);
+ p->status = USB_RET_BABBLE;
+ data_len = len = size;
+ }
+ usb_packet_copy(p, data, data_len);
+ }
+ p->actual_length = len;
+ if (p->pid == USB_TOKEN_IN && p->ep->pipeline) {
+ usb_combined_input_packet_complete(&dev->dev, p);
+ } else {
+ usb_packet_complete(&dev->dev, p);
+ }
+ }
+ free(data);
+}
+
+static void usbredir_iso_packet(void *priv, uint64_t id,
+ struct usb_redir_iso_packet_header *iso_packet,
+ uint8_t *data, int data_len)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t ep = iso_packet->endpoint;
+
+ DPRINTF2("iso-in status %d ep %02X len %d id %"PRIu64"\n",
+ iso_packet->status, ep, data_len, id);
+
+ if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_ISOC) {
+ ERROR("received iso packet for non iso endpoint %02X\n", ep);
+ free(data);
+ return;
+ }
+
+ if (dev->endpoint[EP2I(ep)].iso_started == 0) {
+ DPRINTF("received iso packet for non started stream ep %02X\n", ep);
+ free(data);
+ return;
+ }
+
+ /* bufp_alloc also adds the packet to the ep queue */
+ bufp_alloc(dev, data, data_len, iso_packet->status, ep, data);
+}
+
+static void usbredir_interrupt_packet(void *priv, uint64_t id,
+ struct usb_redir_interrupt_packet_header *interrupt_packet,
+ uint8_t *data, int data_len)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t ep = interrupt_packet->endpoint;
+
+ DPRINTF("interrupt-in status %d ep %02X len %d id %"PRIu64"\n",
+ interrupt_packet->status, ep, data_len, id);
+
+ if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_INT) {
+ ERROR("received int packet for non interrupt endpoint %02X\n", ep);
+ free(data);
+ return;
+ }
+
+ if (ep & USB_DIR_IN) {
+ if (dev->endpoint[EP2I(ep)].interrupt_started == 0) {
+ DPRINTF("received int packet while not started ep %02X\n", ep);
+ free(data);
+ return;
+ }
+
+ if (QTAILQ_EMPTY(&dev->endpoint[EP2I(ep)].bufpq)) {
+ usb_wakeup(usb_ep_get(&dev->dev, USB_TOKEN_IN, ep & 0x0f), 0);
+ }
+
+ /* bufp_alloc also adds the packet to the ep queue */
+ bufp_alloc(dev, data, data_len, interrupt_packet->status, ep, data);
+ } else {
+ /*
+ * We report output interrupt packets as completed directly upon
+ * submission, so all we can do here if one failed is warn.
+ */
+ if (interrupt_packet->status) {
+ WARNING("interrupt output failed status %d ep %02X id %"PRIu64"\n",
+ interrupt_packet->status, ep, id);
+ }
+ }
+}
+
+static void usbredir_buffered_bulk_packet(void *priv, uint64_t id,
+ struct usb_redir_buffered_bulk_packet_header *buffered_bulk_packet,
+ uint8_t *data, int data_len)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t status, ep = buffered_bulk_packet->endpoint;
+ void *free_on_destroy;
+ int i, len;
+
+ DPRINTF("buffered-bulk-in status %d ep %02X len %d id %"PRIu64"\n",
+ buffered_bulk_packet->status, ep, data_len, id);
+
+ if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_BULK) {
+ ERROR("received buffered-bulk packet for non bulk ep %02X\n", ep);
+ free(data);
+ return;
+ }
+
+ if (dev->endpoint[EP2I(ep)].bulk_receiving_started == 0) {
+ DPRINTF("received buffered-bulk packet on not started ep %02X\n", ep);
+ free(data);
+ return;
+ }
+
+ /* Data must be in maxp chunks for buffered_bulk_add_*_data_to_packet */
+ len = dev->endpoint[EP2I(ep)].max_packet_size;
+ status = usb_redir_success;
+ free_on_destroy = NULL;
+ for (i = 0; i < data_len; i += len) {
+ if (len >= (data_len - i)) {
+ len = data_len - i;
+ status = buffered_bulk_packet->status;
+ free_on_destroy = data;
+ }
+ /* bufp_alloc also adds the packet to the ep queue */
+ bufp_alloc(dev, data + i, len, status, ep, free_on_destroy);
+ }
+
+ if (dev->endpoint[EP2I(ep)].pending_async_packet) {
+ USBPacket *p = dev->endpoint[EP2I(ep)].pending_async_packet;
+ dev->endpoint[EP2I(ep)].pending_async_packet = NULL;
+ usbredir_buffered_bulk_in_complete(dev, p, ep);
+ usb_packet_complete(&dev->dev, p);
+ }
+}
+
+/*
+ * Migration code
+ */
+
+static void usbredir_pre_save(void *priv)
+{
+ USBRedirDevice *dev = priv;
+
+ usbredir_fill_already_in_flight(dev);
+}
+
+static int usbredir_post_load(void *priv, int version_id)
+{
+ USBRedirDevice *dev = priv;
+
+ if (dev->parser == NULL) {
+ return 0;
+ }
+
+ switch (dev->device_info.speed) {
+ case usb_redir_speed_low:
+ dev->dev.speed = USB_SPEED_LOW;
+ break;
+ case usb_redir_speed_full:
+ dev->dev.speed = USB_SPEED_FULL;
+ break;
+ case usb_redir_speed_high:
+ dev->dev.speed = USB_SPEED_HIGH;
+ break;
+ case usb_redir_speed_super:
+ dev->dev.speed = USB_SPEED_SUPER;
+ break;
+ default:
+ dev->dev.speed = USB_SPEED_FULL;
+ }
+ dev->dev.speedmask = (1 << dev->dev.speed);
+
+ usbredir_setup_usb_eps(dev);
+ usbredir_check_bulk_receiving(dev);
+
+ return 0;
+}
+
+/* For usbredirparser migration */
+static void usbredir_put_parser(QEMUFile *f, void *priv, size_t unused)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t *data;
+ int len;
+
+ if (dev->parser == NULL) {
+ qemu_put_be32(f, 0);
+ return;
+ }
+
+ usbredirparser_serialize(dev->parser, &data, &len);
+ qemu_oom_check(data);
+
+ qemu_put_be32(f, len);
+ qemu_put_buffer(f, data, len);
+
+ free(data);
+}
+
+static int usbredir_get_parser(QEMUFile *f, void *priv, size_t unused)
+{
+ USBRedirDevice *dev = priv;
+ uint8_t *data;
+ int len, ret;
+
+ len = qemu_get_be32(f);
+ if (len == 0) {
+ return 0;
+ }
+
+ /*
+ * If our chardev is not open already at this point the usbredir connection
+ * has been broken (non seamless migration, or restore from disk).
+ *
+ * In this case create a temporary parser to receive the migration data,
+ * and schedule the close_bh to report the device as disconnected to the
+ * guest and to destroy the parser again.
+ */
+ if (dev->parser == NULL) {
+ WARNING("usb-redir connection broken during migration\n");
+ usbredir_create_parser(dev);
+ qemu_bh_schedule(dev->chardev_close_bh);
+ }
+
+ data = g_malloc(len);
+ qemu_get_buffer(f, data, len);
+
+ ret = usbredirparser_unserialize(dev->parser, data, len);
+
+ g_free(data);
+
+ return ret;
+}
+
+static const VMStateInfo usbredir_parser_vmstate_info = {
+ .name = "usb-redir-parser",
+ .put = usbredir_put_parser,
+ .get = usbredir_get_parser,
+};
+
+
+/* For buffered packets (iso/irq) queue migration */
+static void usbredir_put_bufpq(QEMUFile *f, void *priv, size_t unused)
+{
+ struct endp_data *endp = priv;
+ USBRedirDevice *dev = endp->dev;
+ struct buf_packet *bufp;
+ int len, i = 0;
+
+ qemu_put_be32(f, endp->bufpq_size);
+ QTAILQ_FOREACH(bufp, &endp->bufpq, next) {
+ len = bufp->len - bufp->offset;
+ DPRINTF("put_bufpq %d/%d len %d status %d\n", i + 1, endp->bufpq_size,
+ len, bufp->status);
+ qemu_put_be32(f, len);
+ qemu_put_be32(f, bufp->status);
+ qemu_put_buffer(f, bufp->data + bufp->offset, len);
+ i++;
+ }
+ assert(i == endp->bufpq_size);
+}
+
+static int usbredir_get_bufpq(QEMUFile *f, void *priv, size_t unused)
+{
+ struct endp_data *endp = priv;
+ USBRedirDevice *dev = endp->dev;
+ struct buf_packet *bufp;
+ int i;
+
+ endp->bufpq_size = qemu_get_be32(f);
+ for (i = 0; i < endp->bufpq_size; i++) {
+ bufp = g_malloc(sizeof(struct buf_packet));
+ bufp->len = qemu_get_be32(f);
+ bufp->status = qemu_get_be32(f);
+ bufp->offset = 0;
+ bufp->data = qemu_oom_check(malloc(bufp->len)); /* regular malloc! */
+ bufp->free_on_destroy = bufp->data;
+ qemu_get_buffer(f, bufp->data, bufp->len);
+ QTAILQ_INSERT_TAIL(&endp->bufpq, bufp, next);
+ DPRINTF("get_bufpq %d/%d len %d status %d\n", i + 1, endp->bufpq_size,
+ bufp->len, bufp->status);
+ }
+ return 0;
+}
+
+static const VMStateInfo usbredir_ep_bufpq_vmstate_info = {
+ .name = "usb-redir-bufpq",
+ .put = usbredir_put_bufpq,
+ .get = usbredir_get_bufpq,
+};
+
+
+/* For endp_data migration */
+static bool usbredir_bulk_receiving_needed(void *priv)
+{
+ struct endp_data *endp = priv;
+
+ return endp->bulk_receiving_started;
+}
+
+static const VMStateDescription usbredir_bulk_receiving_vmstate = {
+ .name = "usb-redir-ep/bulk-receiving",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = usbredir_bulk_receiving_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(bulk_receiving_started, struct endp_data),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool usbredir_stream_needed(void *priv)
+{
+ struct endp_data *endp = priv;
+
+ return endp->max_streams;
+}
+
+static const VMStateDescription usbredir_stream_vmstate = {
+ .name = "usb-redir-ep/stream-state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = usbredir_stream_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(max_streams, struct endp_data),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription usbredir_ep_vmstate = {
+ .name = "usb-redir-ep",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(type, struct endp_data),
+ VMSTATE_UINT8(interval, struct endp_data),
+ VMSTATE_UINT8(interface, struct endp_data),
+ VMSTATE_UINT16(max_packet_size, struct endp_data),
+ VMSTATE_UINT8(iso_started, struct endp_data),
+ VMSTATE_UINT8(iso_error, struct endp_data),
+ VMSTATE_UINT8(interrupt_started, struct endp_data),
+ VMSTATE_UINT8(interrupt_error, struct endp_data),
+ VMSTATE_UINT8(bufpq_prefilled, struct endp_data),
+ VMSTATE_UINT8(bufpq_dropping_packets, struct endp_data),
+ {
+ .name = "bufpq",
+ .version_id = 0,
+ .field_exists = NULL,
+ .size = 0,
+ .info = &usbredir_ep_bufpq_vmstate_info,
+ .flags = VMS_SINGLE,
+ .offset = 0,
+ },
+ VMSTATE_INT32(bufpq_target_size, struct endp_data),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &usbredir_bulk_receiving_vmstate,
+ &usbredir_stream_vmstate,
+ NULL
+ }
+};
+
+
+/* For PacketIdQueue migration */
+static void usbredir_put_packet_id_q(QEMUFile *f, void *priv, size_t unused)
+{
+ struct PacketIdQueue *q = priv;
+ USBRedirDevice *dev = q->dev;
+ struct PacketIdQueueEntry *e;
+ int remain = q->size;
+
+ DPRINTF("put_packet_id_q %s size %d\n", q->name, q->size);
+ qemu_put_be32(f, q->size);
+ QTAILQ_FOREACH(e, &q->head, next) {
+ qemu_put_be64(f, e->id);
+ remain--;
+ }
+ assert(remain == 0);
+}
+
+static int usbredir_get_packet_id_q(QEMUFile *f, void *priv, size_t unused)
+{
+ struct PacketIdQueue *q = priv;
+ USBRedirDevice *dev = q->dev;
+ int i, size;
+ uint64_t id;
+
+ size = qemu_get_be32(f);
+ DPRINTF("get_packet_id_q %s size %d\n", q->name, size);
+ for (i = 0; i < size; i++) {
+ id = qemu_get_be64(f);
+ packet_id_queue_add(q, id);
+ }
+ assert(q->size == size);
+ return 0;
+}
+
+static const VMStateInfo usbredir_ep_packet_id_q_vmstate_info = {
+ .name = "usb-redir-packet-id-q",
+ .put = usbredir_put_packet_id_q,
+ .get = usbredir_get_packet_id_q,
+};
+
+static const VMStateDescription usbredir_ep_packet_id_queue_vmstate = {
+ .name = "usb-redir-packet-id-queue",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ {
+ .name = "queue",
+ .version_id = 0,
+ .field_exists = NULL,
+ .size = 0,
+ .info = &usbredir_ep_packet_id_q_vmstate_info,
+ .flags = VMS_SINGLE,
+ .offset = 0,
+ },
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+/* For usb_redir_device_connect_header migration */
+static const VMStateDescription usbredir_device_info_vmstate = {
+ .name = "usb-redir-device-info",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(speed, struct usb_redir_device_connect_header),
+ VMSTATE_UINT8(device_class, struct usb_redir_device_connect_header),
+ VMSTATE_UINT8(device_subclass, struct usb_redir_device_connect_header),
+ VMSTATE_UINT8(device_protocol, struct usb_redir_device_connect_header),
+ VMSTATE_UINT16(vendor_id, struct usb_redir_device_connect_header),
+ VMSTATE_UINT16(product_id, struct usb_redir_device_connect_header),
+ VMSTATE_UINT16(device_version_bcd,
+ struct usb_redir_device_connect_header),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+/* For usb_redir_interface_info_header migration */
+static const VMStateDescription usbredir_interface_info_vmstate = {
+ .name = "usb-redir-interface-info",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(interface_count,
+ struct usb_redir_interface_info_header),
+ VMSTATE_UINT8_ARRAY(interface,
+ struct usb_redir_interface_info_header, 32),
+ VMSTATE_UINT8_ARRAY(interface_class,
+ struct usb_redir_interface_info_header, 32),
+ VMSTATE_UINT8_ARRAY(interface_subclass,
+ struct usb_redir_interface_info_header, 32),
+ VMSTATE_UINT8_ARRAY(interface_protocol,
+ struct usb_redir_interface_info_header, 32),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+/* And finally the USBRedirDevice vmstate itself */
+static const VMStateDescription usbredir_vmstate = {
+ .name = "usb-redir",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = usbredir_pre_save,
+ .post_load = usbredir_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, USBRedirDevice),
+ VMSTATE_TIMER_PTR(attach_timer, USBRedirDevice),
+ {
+ .name = "parser",
+ .version_id = 0,
+ .field_exists = NULL,
+ .size = 0,
+ .info = &usbredir_parser_vmstate_info,
+ .flags = VMS_SINGLE,
+ .offset = 0,
+ },
+ VMSTATE_STRUCT_ARRAY(endpoint, USBRedirDevice, MAX_ENDPOINTS, 1,
+ usbredir_ep_vmstate, struct endp_data),
+ VMSTATE_STRUCT(cancelled, USBRedirDevice, 1,
+ usbredir_ep_packet_id_queue_vmstate,
+ struct PacketIdQueue),
+ VMSTATE_STRUCT(already_in_flight, USBRedirDevice, 1,
+ usbredir_ep_packet_id_queue_vmstate,
+ struct PacketIdQueue),
+ VMSTATE_STRUCT(device_info, USBRedirDevice, 1,
+ usbredir_device_info_vmstate,
+ struct usb_redir_device_connect_header),
+ VMSTATE_STRUCT(interface_info, USBRedirDevice, 1,
+ usbredir_interface_info_vmstate,
+ struct usb_redir_interface_info_header),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property usbredir_properties[] = {
+ DEFINE_PROP_CHR("chardev", USBRedirDevice, cs),
+ DEFINE_PROP_UINT8("debug", USBRedirDevice, debug, usbredirparser_warning),
+ DEFINE_PROP_STRING("filter", USBRedirDevice, filter_str),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usbredir_class_initfn(ObjectClass *klass, void *data)
+{
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ uc->realize = usbredir_realize;
+ uc->product_desc = "USB Redirection Device";
+ uc->handle_destroy = usbredir_handle_destroy;
+ uc->cancel_packet = usbredir_cancel_packet;
+ uc->handle_reset = usbredir_handle_reset;
+ uc->handle_data = usbredir_handle_data;
+ uc->handle_control = usbredir_handle_control;
+ uc->flush_ep_queue = usbredir_flush_ep_queue;
+ uc->ep_stopped = usbredir_ep_stopped;
+ uc->alloc_streams = usbredir_alloc_streams;
+ uc->free_streams = usbredir_free_streams;
+ dc->vmsd = &usbredir_vmstate;
+ dc->props = usbredir_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static void usbredir_instance_init(Object *obj)
+{
+ USBDevice *udev = USB_DEVICE(obj);
+ USBRedirDevice *dev = USB_REDIRECT(udev);
+
+ device_add_bootindex_property(obj, &dev->bootindex,
+ "bootindex", NULL,
+ &udev->qdev, NULL);
+}
+
+static const TypeInfo usbredir_dev_info = {
+ .name = TYPE_USB_REDIR,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBRedirDevice),
+ .class_init = usbredir_class_initfn,
+ .instance_init = usbredir_instance_init,
+};
+
+static void usbredir_register_types(void)
+{
+ type_register_static(&usbredir_dev_info);
+}
+
+type_init(usbredir_register_types)
diff --git a/hw/vfio/Makefile.objs b/hw/vfio/Makefile.objs
new file mode 100644
index 00000000..d540c9d1
--- /dev/null
+++ b/hw/vfio/Makefile.objs
@@ -0,0 +1,6 @@
+ifeq ($(CONFIG_LINUX), y)
+obj-$(CONFIG_SOFTMMU) += common.o
+obj-$(CONFIG_PCI) += pci.o
+obj-$(CONFIG_SOFTMMU) += platform.o
+obj-$(CONFIG_SOFTMMU) += calxeda-xgmac.o
+endif
diff --git a/hw/vfio/calxeda-xgmac.c b/hw/vfio/calxeda-xgmac.c
new file mode 100644
index 00000000..eb914f0d
--- /dev/null
+++ b/hw/vfio/calxeda-xgmac.c
@@ -0,0 +1,55 @@
+/*
+ * calxeda xgmac VFIO device
+ *
+ * Copyright Linaro Limited, 2014
+ *
+ * Authors:
+ * Eric Auger <eric.auger@linaro.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/vfio/vfio-calxeda-xgmac.h"
+
+static void calxeda_xgmac_realize(DeviceState *dev, Error **errp)
+{
+ VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev);
+ VFIOCalxedaXgmacDeviceClass *k = VFIO_CALXEDA_XGMAC_DEVICE_GET_CLASS(dev);
+
+ vdev->compat = g_strdup("calxeda,hb-xgmac");
+
+ k->parent_realize(dev, errp);
+}
+
+static const VMStateDescription vfio_platform_calxeda_xgmac_vmstate = {
+ .name = TYPE_VFIO_CALXEDA_XGMAC,
+ .unmigratable = 1,
+};
+
+static void vfio_calxeda_xgmac_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VFIOCalxedaXgmacDeviceClass *vcxc =
+ VFIO_CALXEDA_XGMAC_DEVICE_CLASS(klass);
+ vcxc->parent_realize = dc->realize;
+ dc->realize = calxeda_xgmac_realize;
+ dc->desc = "VFIO Calxeda XGMAC";
+ dc->vmsd = &vfio_platform_calxeda_xgmac_vmstate;
+}
+
+static const TypeInfo vfio_calxeda_xgmac_dev_info = {
+ .name = TYPE_VFIO_CALXEDA_XGMAC,
+ .parent = TYPE_VFIO_PLATFORM,
+ .instance_size = sizeof(VFIOCalxedaXgmacDevice),
+ .class_init = vfio_calxeda_xgmac_class_init,
+ .class_size = sizeof(VFIOCalxedaXgmacDeviceClass),
+};
+
+static void register_calxeda_xgmac_dev_type(void)
+{
+ type_register_static(&vfio_calxeda_xgmac_dev_info);
+}
+
+type_init(register_calxeda_xgmac_dev_type)
diff --git a/hw/vfio/common.c b/hw/vfio/common.c
new file mode 100644
index 00000000..85ee9b00
--- /dev/null
+++ b/hw/vfio/common.c
@@ -0,0 +1,972 @@
+/*
+ * generic functions used by VFIO devices
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Authors:
+ * Alex Williamson <alex.williamson@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Based on qemu-kvm device-assignment:
+ * Adapted for KVM by Qumranet.
+ * Copyright (c) 2007, Neocleus, Alex Novik (alex@neocleus.com)
+ * Copyright (c) 2007, Neocleus, Guy Zana (guy@neocleus.com)
+ * Copyright (C) 2008, Qumranet, Amit Shah (amit.shah@qumranet.com)
+ * Copyright (C) 2008, Red Hat, Amit Shah (amit.shah@redhat.com)
+ * Copyright (C) 2008, IBM, Muli Ben-Yehuda (muli@il.ibm.com)
+ */
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <linux/vfio.h>
+
+#include "hw/vfio/vfio-common.h"
+#include "hw/vfio/vfio.h"
+#include "exec/address-spaces.h"
+#include "exec/memory.h"
+#include "hw/hw.h"
+#include "qemu/error-report.h"
+#include "sysemu/kvm.h"
+#include "trace.h"
+
+struct vfio_group_head vfio_group_list =
+ QLIST_HEAD_INITIALIZER(vfio_group_list);
+struct vfio_as_head vfio_address_spaces =
+ QLIST_HEAD_INITIALIZER(vfio_address_spaces);
+
+#ifdef CONFIG_KVM
+/*
+ * We have a single VFIO pseudo device per KVM VM. Once created it lives
+ * for the life of the VM. Closing the file descriptor only drops our
+ * reference to it and the device's reference to kvm. Therefore once
+ * initialized, this file descriptor is only released on QEMU exit and
+ * we'll re-use it should another vfio device be attached before then.
+ */
+static int vfio_kvm_device_fd = -1;
+#endif
+
+/*
+ * Common VFIO interrupt disable
+ */
+void vfio_disable_irqindex(VFIODevice *vbasedev, int index)
+{
+ struct vfio_irq_set irq_set = {
+ .argsz = sizeof(irq_set),
+ .flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_TRIGGER,
+ .index = index,
+ .start = 0,
+ .count = 0,
+ };
+
+ ioctl(vbasedev->fd, VFIO_DEVICE_SET_IRQS, &irq_set);
+}
+
+void vfio_unmask_single_irqindex(VFIODevice *vbasedev, int index)
+{
+ struct vfio_irq_set irq_set = {
+ .argsz = sizeof(irq_set),
+ .flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_UNMASK,
+ .index = index,
+ .start = 0,
+ .count = 1,
+ };
+
+ ioctl(vbasedev->fd, VFIO_DEVICE_SET_IRQS, &irq_set);
+}
+
+void vfio_mask_single_irqindex(VFIODevice *vbasedev, int index)
+{
+ struct vfio_irq_set irq_set = {
+ .argsz = sizeof(irq_set),
+ .flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_MASK,
+ .index = index,
+ .start = 0,
+ .count = 1,
+ };
+
+ ioctl(vbasedev->fd, VFIO_DEVICE_SET_IRQS, &irq_set);
+}
+
+/*
+ * IO Port/MMIO - Beware of the endians, VFIO is always little endian
+ */
+void vfio_region_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VFIORegion *region = opaque;
+ VFIODevice *vbasedev = region->vbasedev;
+ union {
+ uint8_t byte;
+ uint16_t word;
+ uint32_t dword;
+ uint64_t qword;
+ } buf;
+
+ switch (size) {
+ case 1:
+ buf.byte = data;
+ break;
+ case 2:
+ buf.word = cpu_to_le16(data);
+ break;
+ case 4:
+ buf.dword = cpu_to_le32(data);
+ break;
+ default:
+ hw_error("vfio: unsupported write size, %d bytes", size);
+ break;
+ }
+
+ if (pwrite(vbasedev->fd, &buf, size, region->fd_offset + addr) != size) {
+ error_report("%s(%s:region%d+0x%"HWADDR_PRIx", 0x%"PRIx64
+ ",%d) failed: %m",
+ __func__, vbasedev->name, region->nr,
+ addr, data, size);
+ }
+
+ trace_vfio_region_write(vbasedev->name, region->nr, addr, data, size);
+
+ /*
+ * A read or write to a BAR always signals an INTx EOI. This will
+ * do nothing if not pending (including not in INTx mode). We assume
+ * that a BAR access is in response to an interrupt and that BAR
+ * accesses will service the interrupt. Unfortunately, we don't know
+ * which access will service the interrupt, so we're potentially
+ * getting quite a few host interrupts per guest interrupt.
+ */
+ vbasedev->ops->vfio_eoi(vbasedev);
+}
+
+uint64_t vfio_region_read(void *opaque,
+ hwaddr addr, unsigned size)
+{
+ VFIORegion *region = opaque;
+ VFIODevice *vbasedev = region->vbasedev;
+ union {
+ uint8_t byte;
+ uint16_t word;
+ uint32_t dword;
+ uint64_t qword;
+ } buf;
+ uint64_t data = 0;
+
+ if (pread(vbasedev->fd, &buf, size, region->fd_offset + addr) != size) {
+ error_report("%s(%s:region%d+0x%"HWADDR_PRIx", %d) failed: %m",
+ __func__, vbasedev->name, region->nr,
+ addr, size);
+ return (uint64_t)-1;
+ }
+ switch (size) {
+ case 1:
+ data = buf.byte;
+ break;
+ case 2:
+ data = le16_to_cpu(buf.word);
+ break;
+ case 4:
+ data = le32_to_cpu(buf.dword);
+ break;
+ default:
+ hw_error("vfio: unsupported read size, %d bytes", size);
+ break;
+ }
+
+ trace_vfio_region_read(vbasedev->name, region->nr, addr, size, data);
+
+ /* Same as write above */
+ vbasedev->ops->vfio_eoi(vbasedev);
+
+ return data;
+}
+
+const MemoryRegionOps vfio_region_ops = {
+ .read = vfio_region_read,
+ .write = vfio_region_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/*
+ * DMA - Mapping and unmapping for the "type1" IOMMU interface used on x86
+ */
+static int vfio_dma_unmap(VFIOContainer *container,
+ hwaddr iova, ram_addr_t size)
+{
+ struct vfio_iommu_type1_dma_unmap unmap = {
+ .argsz = sizeof(unmap),
+ .flags = 0,
+ .iova = iova,
+ .size = size,
+ };
+
+ if (ioctl(container->fd, VFIO_IOMMU_UNMAP_DMA, &unmap)) {
+ error_report("VFIO_UNMAP_DMA: %d", -errno);
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int vfio_dma_map(VFIOContainer *container, hwaddr iova,
+ ram_addr_t size, void *vaddr, bool readonly)
+{
+ struct vfio_iommu_type1_dma_map map = {
+ .argsz = sizeof(map),
+ .flags = VFIO_DMA_MAP_FLAG_READ,
+ .vaddr = (__u64)(uintptr_t)vaddr,
+ .iova = iova,
+ .size = size,
+ };
+
+ if (!readonly) {
+ map.flags |= VFIO_DMA_MAP_FLAG_WRITE;
+ }
+
+ /*
+ * Try the mapping, if it fails with EBUSY, unmap the region and try
+ * again. This shouldn't be necessary, but we sometimes see it in
+ * the the VGA ROM space.
+ */
+ if (ioctl(container->fd, VFIO_IOMMU_MAP_DMA, &map) == 0 ||
+ (errno == EBUSY && vfio_dma_unmap(container, iova, size) == 0 &&
+ ioctl(container->fd, VFIO_IOMMU_MAP_DMA, &map) == 0)) {
+ return 0;
+ }
+
+ error_report("VFIO_MAP_DMA: %d", -errno);
+ return -errno;
+}
+
+static bool vfio_listener_skipped_section(MemoryRegionSection *section)
+{
+ return (!memory_region_is_ram(section->mr) &&
+ !memory_region_is_iommu(section->mr)) ||
+ /*
+ * Sizing an enabled 64-bit BAR can cause spurious mappings to
+ * addresses in the upper part of the 64-bit address space. These
+ * are never accessed by the CPU and beyond the address width of
+ * some IOMMU hardware. TODO: VFIO should tell us the IOMMU width.
+ */
+ section->offset_within_address_space & (1ULL << 63);
+}
+
+static void vfio_iommu_map_notify(Notifier *n, void *data)
+{
+ VFIOGuestIOMMU *giommu = container_of(n, VFIOGuestIOMMU, n);
+ VFIOContainer *container = giommu->container;
+ IOMMUTLBEntry *iotlb = data;
+ MemoryRegion *mr;
+ hwaddr xlat;
+ hwaddr len = iotlb->addr_mask + 1;
+ void *vaddr;
+ int ret;
+
+ trace_vfio_iommu_map_notify(iotlb->iova,
+ iotlb->iova + iotlb->addr_mask);
+
+ /*
+ * The IOMMU TLB entry we have just covers translation through
+ * this IOMMU to its immediate target. We need to translate
+ * it the rest of the way through to memory.
+ */
+ rcu_read_lock();
+ mr = address_space_translate(&address_space_memory,
+ iotlb->translated_addr,
+ &xlat, &len, iotlb->perm & IOMMU_WO);
+ if (!memory_region_is_ram(mr)) {
+ error_report("iommu map to non memory area %"HWADDR_PRIx"",
+ xlat);
+ goto out;
+ }
+ /*
+ * Translation truncates length to the IOMMU page size,
+ * check that it did not truncate too much.
+ */
+ if (len & iotlb->addr_mask) {
+ error_report("iommu has granularity incompatible with target AS");
+ goto out;
+ }
+
+ if ((iotlb->perm & IOMMU_RW) != IOMMU_NONE) {
+ vaddr = memory_region_get_ram_ptr(mr) + xlat;
+ ret = vfio_dma_map(container, iotlb->iova,
+ iotlb->addr_mask + 1, vaddr,
+ !(iotlb->perm & IOMMU_WO) || mr->readonly);
+ if (ret) {
+ error_report("vfio_dma_map(%p, 0x%"HWADDR_PRIx", "
+ "0x%"HWADDR_PRIx", %p) = %d (%m)",
+ container, iotlb->iova,
+ iotlb->addr_mask + 1, vaddr, ret);
+ }
+ } else {
+ ret = vfio_dma_unmap(container, iotlb->iova, iotlb->addr_mask + 1);
+ if (ret) {
+ error_report("vfio_dma_unmap(%p, 0x%"HWADDR_PRIx", "
+ "0x%"HWADDR_PRIx") = %d (%m)",
+ container, iotlb->iova,
+ iotlb->addr_mask + 1, ret);
+ }
+ }
+out:
+ rcu_read_unlock();
+}
+
+static void vfio_listener_region_add(MemoryListener *listener,
+ MemoryRegionSection *section)
+{
+ VFIOContainer *container = container_of(listener, VFIOContainer,
+ iommu_data.type1.listener);
+ hwaddr iova, end;
+ Int128 llend;
+ void *vaddr;
+ int ret;
+
+ if (vfio_listener_skipped_section(section)) {
+ trace_vfio_listener_region_add_skip(
+ section->offset_within_address_space,
+ section->offset_within_address_space +
+ int128_get64(int128_sub(section->size, int128_one())));
+ return;
+ }
+
+ if (unlikely((section->offset_within_address_space & ~TARGET_PAGE_MASK) !=
+ (section->offset_within_region & ~TARGET_PAGE_MASK))) {
+ error_report("%s received unaligned region", __func__);
+ return;
+ }
+
+ iova = TARGET_PAGE_ALIGN(section->offset_within_address_space);
+ llend = int128_make64(section->offset_within_address_space);
+ llend = int128_add(llend, section->size);
+ llend = int128_and(llend, int128_exts64(TARGET_PAGE_MASK));
+
+ if (int128_ge(int128_make64(iova), llend)) {
+ return;
+ }
+
+ memory_region_ref(section->mr);
+
+ if (memory_region_is_iommu(section->mr)) {
+ VFIOGuestIOMMU *giommu;
+
+ trace_vfio_listener_region_add_iommu(iova,
+ int128_get64(int128_sub(llend, int128_one())));
+ /*
+ * FIXME: We should do some checking to see if the
+ * capabilities of the host VFIO IOMMU are adequate to model
+ * the guest IOMMU
+ *
+ * FIXME: For VFIO iommu types which have KVM acceleration to
+ * avoid bouncing all map/unmaps through qemu this way, this
+ * would be the right place to wire that up (tell the KVM
+ * device emulation the VFIO iommu handles to use).
+ */
+ /*
+ * This assumes that the guest IOMMU is empty of
+ * mappings at this point.
+ *
+ * One way of doing this is:
+ * 1. Avoid sharing IOMMUs between emulated devices or different
+ * IOMMU groups.
+ * 2. Implement VFIO_IOMMU_ENABLE in the host kernel to fail if
+ * there are some mappings in IOMMU.
+ *
+ * VFIO on SPAPR does that. Other IOMMU models may do that different,
+ * they must make sure there are no existing mappings or
+ * loop through existing mappings to map them into VFIO.
+ */
+ giommu = g_malloc0(sizeof(*giommu));
+ giommu->iommu = section->mr;
+ giommu->container = container;
+ giommu->n.notify = vfio_iommu_map_notify;
+ QLIST_INSERT_HEAD(&container->giommu_list, giommu, giommu_next);
+ memory_region_register_iommu_notifier(giommu->iommu, &giommu->n);
+
+ return;
+ }
+
+ /* Here we assume that memory_region_is_ram(section->mr)==true */
+
+ end = int128_get64(llend);
+ vaddr = memory_region_get_ram_ptr(section->mr) +
+ section->offset_within_region +
+ (iova - section->offset_within_address_space);
+
+ trace_vfio_listener_region_add_ram(iova, end - 1, vaddr);
+
+ ret = vfio_dma_map(container, iova, end - iova, vaddr, section->readonly);
+ if (ret) {
+ error_report("vfio_dma_map(%p, 0x%"HWADDR_PRIx", "
+ "0x%"HWADDR_PRIx", %p) = %d (%m)",
+ container, iova, end - iova, vaddr, ret);
+
+ /*
+ * On the initfn path, store the first error in the container so we
+ * can gracefully fail. Runtime, there's not much we can do other
+ * than throw a hardware error.
+ */
+ if (!container->iommu_data.type1.initialized) {
+ if (!container->iommu_data.type1.error) {
+ container->iommu_data.type1.error = ret;
+ }
+ } else {
+ hw_error("vfio: DMA mapping failed, unable to continue");
+ }
+ }
+}
+
+static void vfio_listener_region_del(MemoryListener *listener,
+ MemoryRegionSection *section)
+{
+ VFIOContainer *container = container_of(listener, VFIOContainer,
+ iommu_data.type1.listener);
+ hwaddr iova, end;
+ int ret;
+
+ if (vfio_listener_skipped_section(section)) {
+ trace_vfio_listener_region_del_skip(
+ section->offset_within_address_space,
+ section->offset_within_address_space +
+ int128_get64(int128_sub(section->size, int128_one())));
+ return;
+ }
+
+ if (unlikely((section->offset_within_address_space & ~TARGET_PAGE_MASK) !=
+ (section->offset_within_region & ~TARGET_PAGE_MASK))) {
+ error_report("%s received unaligned region", __func__);
+ return;
+ }
+
+ if (memory_region_is_iommu(section->mr)) {
+ VFIOGuestIOMMU *giommu;
+
+ QLIST_FOREACH(giommu, &container->giommu_list, giommu_next) {
+ if (giommu->iommu == section->mr) {
+ memory_region_unregister_iommu_notifier(&giommu->n);
+ QLIST_REMOVE(giommu, giommu_next);
+ g_free(giommu);
+ break;
+ }
+ }
+
+ /*
+ * FIXME: We assume the one big unmap below is adequate to
+ * remove any individual page mappings in the IOMMU which
+ * might have been copied into VFIO. This works for a page table
+ * based IOMMU where a big unmap flattens a large range of IO-PTEs.
+ * That may not be true for all IOMMU types.
+ */
+ }
+
+ iova = TARGET_PAGE_ALIGN(section->offset_within_address_space);
+ end = (section->offset_within_address_space + int128_get64(section->size)) &
+ TARGET_PAGE_MASK;
+
+ if (iova >= end) {
+ return;
+ }
+
+ trace_vfio_listener_region_del(iova, end - 1);
+
+ ret = vfio_dma_unmap(container, iova, end - iova);
+ memory_region_unref(section->mr);
+ if (ret) {
+ error_report("vfio_dma_unmap(%p, 0x%"HWADDR_PRIx", "
+ "0x%"HWADDR_PRIx") = %d (%m)",
+ container, iova, end - iova, ret);
+ }
+}
+
+static const MemoryListener vfio_memory_listener = {
+ .region_add = vfio_listener_region_add,
+ .region_del = vfio_listener_region_del,
+};
+
+static void vfio_listener_release(VFIOContainer *container)
+{
+ memory_listener_unregister(&container->iommu_data.type1.listener);
+}
+
+int vfio_mmap_region(Object *obj, VFIORegion *region,
+ MemoryRegion *mem, MemoryRegion *submem,
+ void **map, size_t size, off_t offset,
+ const char *name)
+{
+ int ret = 0;
+ VFIODevice *vbasedev = region->vbasedev;
+
+ if (vbasedev->allow_mmap && size && region->flags &
+ VFIO_REGION_INFO_FLAG_MMAP) {
+ int prot = 0;
+
+ if (region->flags & VFIO_REGION_INFO_FLAG_READ) {
+ prot |= PROT_READ;
+ }
+
+ if (region->flags & VFIO_REGION_INFO_FLAG_WRITE) {
+ prot |= PROT_WRITE;
+ }
+
+ *map = mmap(NULL, size, prot, MAP_SHARED,
+ vbasedev->fd,
+ region->fd_offset + offset);
+ if (*map == MAP_FAILED) {
+ *map = NULL;
+ ret = -errno;
+ goto empty_region;
+ }
+
+ memory_region_init_ram_ptr(submem, obj, name, size, *map);
+ memory_region_set_skip_dump(submem);
+ } else {
+empty_region:
+ /* Create a zero sized sub-region to make cleanup easy. */
+ memory_region_init(submem, obj, name, 0);
+ }
+
+ memory_region_add_subregion(mem, offset, submem);
+
+ return ret;
+}
+
+void vfio_reset_handler(void *opaque)
+{
+ VFIOGroup *group;
+ VFIODevice *vbasedev;
+
+ QLIST_FOREACH(group, &vfio_group_list, next) {
+ QLIST_FOREACH(vbasedev, &group->device_list, next) {
+ vbasedev->ops->vfio_compute_needs_reset(vbasedev);
+ }
+ }
+
+ QLIST_FOREACH(group, &vfio_group_list, next) {
+ QLIST_FOREACH(vbasedev, &group->device_list, next) {
+ if (vbasedev->needs_reset) {
+ vbasedev->ops->vfio_hot_reset_multi(vbasedev);
+ }
+ }
+ }
+}
+
+static void vfio_kvm_device_add_group(VFIOGroup *group)
+{
+#ifdef CONFIG_KVM
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_VFIO_GROUP,
+ .attr = KVM_DEV_VFIO_GROUP_ADD,
+ .addr = (uint64_t)(unsigned long)&group->fd,
+ };
+
+ if (!kvm_enabled()) {
+ return;
+ }
+
+ if (vfio_kvm_device_fd < 0) {
+ struct kvm_create_device cd = {
+ .type = KVM_DEV_TYPE_VFIO,
+ };
+
+ if (kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &cd)) {
+ error_report("Failed to create KVM VFIO device: %m");
+ return;
+ }
+
+ vfio_kvm_device_fd = cd.fd;
+ }
+
+ if (ioctl(vfio_kvm_device_fd, KVM_SET_DEVICE_ATTR, &attr)) {
+ error_report("Failed to add group %d to KVM VFIO device: %m",
+ group->groupid);
+ }
+#endif
+}
+
+static void vfio_kvm_device_del_group(VFIOGroup *group)
+{
+#ifdef CONFIG_KVM
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_VFIO_GROUP,
+ .attr = KVM_DEV_VFIO_GROUP_DEL,
+ .addr = (uint64_t)(unsigned long)&group->fd,
+ };
+
+ if (vfio_kvm_device_fd < 0) {
+ return;
+ }
+
+ if (ioctl(vfio_kvm_device_fd, KVM_SET_DEVICE_ATTR, &attr)) {
+ error_report("Failed to remove group %d from KVM VFIO device: %m",
+ group->groupid);
+ }
+#endif
+}
+
+static VFIOAddressSpace *vfio_get_address_space(AddressSpace *as)
+{
+ VFIOAddressSpace *space;
+
+ QLIST_FOREACH(space, &vfio_address_spaces, list) {
+ if (space->as == as) {
+ return space;
+ }
+ }
+
+ /* No suitable VFIOAddressSpace, create a new one */
+ space = g_malloc0(sizeof(*space));
+ space->as = as;
+ QLIST_INIT(&space->containers);
+
+ QLIST_INSERT_HEAD(&vfio_address_spaces, space, list);
+
+ return space;
+}
+
+static void vfio_put_address_space(VFIOAddressSpace *space)
+{
+ if (QLIST_EMPTY(&space->containers)) {
+ QLIST_REMOVE(space, list);
+ g_free(space);
+ }
+}
+
+static int vfio_connect_container(VFIOGroup *group, AddressSpace *as)
+{
+ VFIOContainer *container;
+ int ret, fd;
+ VFIOAddressSpace *space;
+
+ space = vfio_get_address_space(as);
+
+ QLIST_FOREACH(container, &space->containers, next) {
+ if (!ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &container->fd)) {
+ group->container = container;
+ QLIST_INSERT_HEAD(&container->group_list, group, container_next);
+ return 0;
+ }
+ }
+
+ fd = qemu_open("/dev/vfio/vfio", O_RDWR);
+ if (fd < 0) {
+ error_report("vfio: failed to open /dev/vfio/vfio: %m");
+ ret = -errno;
+ goto put_space_exit;
+ }
+
+ ret = ioctl(fd, VFIO_GET_API_VERSION);
+ if (ret != VFIO_API_VERSION) {
+ error_report("vfio: supported vfio version: %d, "
+ "reported version: %d", VFIO_API_VERSION, ret);
+ ret = -EINVAL;
+ goto close_fd_exit;
+ }
+
+ container = g_malloc0(sizeof(*container));
+ container->space = space;
+ container->fd = fd;
+ if (ioctl(fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU) ||
+ ioctl(fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU)) {
+ bool v2 = !!ioctl(fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU);
+
+ ret = ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &fd);
+ if (ret) {
+ error_report("vfio: failed to set group container: %m");
+ ret = -errno;
+ goto free_container_exit;
+ }
+
+ ret = ioctl(fd, VFIO_SET_IOMMU,
+ v2 ? VFIO_TYPE1v2_IOMMU : VFIO_TYPE1_IOMMU);
+ if (ret) {
+ error_report("vfio: failed to set iommu for container: %m");
+ ret = -errno;
+ goto free_container_exit;
+ }
+
+ container->iommu_data.type1.listener = vfio_memory_listener;
+ container->iommu_data.release = vfio_listener_release;
+
+ memory_listener_register(&container->iommu_data.type1.listener,
+ container->space->as);
+
+ if (container->iommu_data.type1.error) {
+ ret = container->iommu_data.type1.error;
+ error_report("vfio: memory listener initialization failed for container");
+ goto listener_release_exit;
+ }
+
+ container->iommu_data.type1.initialized = true;
+
+ } else if (ioctl(fd, VFIO_CHECK_EXTENSION, VFIO_SPAPR_TCE_IOMMU)) {
+ ret = ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &fd);
+ if (ret) {
+ error_report("vfio: failed to set group container: %m");
+ ret = -errno;
+ goto free_container_exit;
+ }
+ ret = ioctl(fd, VFIO_SET_IOMMU, VFIO_SPAPR_TCE_IOMMU);
+ if (ret) {
+ error_report("vfio: failed to set iommu for container: %m");
+ ret = -errno;
+ goto free_container_exit;
+ }
+
+ /*
+ * The host kernel code implementing VFIO_IOMMU_DISABLE is called
+ * when container fd is closed so we do not call it explicitly
+ * in this file.
+ */
+ ret = ioctl(fd, VFIO_IOMMU_ENABLE);
+ if (ret) {
+ error_report("vfio: failed to enable container: %m");
+ ret = -errno;
+ goto free_container_exit;
+ }
+
+ container->iommu_data.type1.listener = vfio_memory_listener;
+ container->iommu_data.release = vfio_listener_release;
+
+ memory_listener_register(&container->iommu_data.type1.listener,
+ container->space->as);
+
+ } else {
+ error_report("vfio: No available IOMMU models");
+ ret = -EINVAL;
+ goto free_container_exit;
+ }
+
+ QLIST_INIT(&container->group_list);
+ QLIST_INSERT_HEAD(&space->containers, container, next);
+
+ group->container = container;
+ QLIST_INSERT_HEAD(&container->group_list, group, container_next);
+
+ return 0;
+listener_release_exit:
+ vfio_listener_release(container);
+
+free_container_exit:
+ g_free(container);
+
+close_fd_exit:
+ close(fd);
+
+put_space_exit:
+ vfio_put_address_space(space);
+
+ return ret;
+}
+
+static void vfio_disconnect_container(VFIOGroup *group)
+{
+ VFIOContainer *container = group->container;
+
+ if (ioctl(group->fd, VFIO_GROUP_UNSET_CONTAINER, &container->fd)) {
+ error_report("vfio: error disconnecting group %d from container",
+ group->groupid);
+ }
+
+ QLIST_REMOVE(group, container_next);
+ group->container = NULL;
+
+ if (QLIST_EMPTY(&container->group_list)) {
+ VFIOAddressSpace *space = container->space;
+ VFIOGuestIOMMU *giommu, *tmp;
+
+ if (container->iommu_data.release) {
+ container->iommu_data.release(container);
+ }
+ QLIST_REMOVE(container, next);
+
+ QLIST_FOREACH_SAFE(giommu, &container->giommu_list, giommu_next, tmp) {
+ memory_region_unregister_iommu_notifier(&giommu->n);
+ QLIST_REMOVE(giommu, giommu_next);
+ g_free(giommu);
+ }
+
+ trace_vfio_disconnect_container(container->fd);
+ close(container->fd);
+ g_free(container);
+
+ vfio_put_address_space(space);
+ }
+}
+
+VFIOGroup *vfio_get_group(int groupid, AddressSpace *as)
+{
+ VFIOGroup *group;
+ char path[32];
+ struct vfio_group_status status = { .argsz = sizeof(status) };
+
+ QLIST_FOREACH(group, &vfio_group_list, next) {
+ if (group->groupid == groupid) {
+ /* Found it. Now is it already in the right context? */
+ if (group->container->space->as == as) {
+ return group;
+ } else {
+ error_report("vfio: group %d used in multiple address spaces",
+ group->groupid);
+ return NULL;
+ }
+ }
+ }
+
+ group = g_malloc0(sizeof(*group));
+
+ snprintf(path, sizeof(path), "/dev/vfio/%d", groupid);
+ group->fd = qemu_open(path, O_RDWR);
+ if (group->fd < 0) {
+ error_report("vfio: error opening %s: %m", path);
+ goto free_group_exit;
+ }
+
+ if (ioctl(group->fd, VFIO_GROUP_GET_STATUS, &status)) {
+ error_report("vfio: error getting group status: %m");
+ goto close_fd_exit;
+ }
+
+ if (!(status.flags & VFIO_GROUP_FLAGS_VIABLE)) {
+ error_report("vfio: error, group %d is not viable, please ensure "
+ "all devices within the iommu_group are bound to their "
+ "vfio bus driver.", groupid);
+ goto close_fd_exit;
+ }
+
+ group->groupid = groupid;
+ QLIST_INIT(&group->device_list);
+
+ if (vfio_connect_container(group, as)) {
+ error_report("vfio: failed to setup container for group %d", groupid);
+ goto close_fd_exit;
+ }
+
+ if (QLIST_EMPTY(&vfio_group_list)) {
+ qemu_register_reset(vfio_reset_handler, NULL);
+ }
+
+ QLIST_INSERT_HEAD(&vfio_group_list, group, next);
+
+ vfio_kvm_device_add_group(group);
+
+ return group;
+
+close_fd_exit:
+ close(group->fd);
+
+free_group_exit:
+ g_free(group);
+
+ return NULL;
+}
+
+void vfio_put_group(VFIOGroup *group)
+{
+ if (!group || !QLIST_EMPTY(&group->device_list)) {
+ return;
+ }
+
+ vfio_kvm_device_del_group(group);
+ vfio_disconnect_container(group);
+ QLIST_REMOVE(group, next);
+ trace_vfio_put_group(group->fd);
+ close(group->fd);
+ g_free(group);
+
+ if (QLIST_EMPTY(&vfio_group_list)) {
+ qemu_unregister_reset(vfio_reset_handler, NULL);
+ }
+}
+
+int vfio_get_device(VFIOGroup *group, const char *name,
+ VFIODevice *vbasedev)
+{
+ struct vfio_device_info dev_info = { .argsz = sizeof(dev_info) };
+ int ret, fd;
+
+ fd = ioctl(group->fd, VFIO_GROUP_GET_DEVICE_FD, name);
+ if (fd < 0) {
+ error_report("vfio: error getting device %s from group %d: %m",
+ name, group->groupid);
+ error_printf("Verify all devices in group %d are bound to vfio-<bus> "
+ "or pci-stub and not already in use\n", group->groupid);
+ return fd;
+ }
+
+ ret = ioctl(fd, VFIO_DEVICE_GET_INFO, &dev_info);
+ if (ret) {
+ error_report("vfio: error getting device info: %m");
+ close(fd);
+ return ret;
+ }
+
+ vbasedev->fd = fd;
+ vbasedev->group = group;
+ QLIST_INSERT_HEAD(&group->device_list, vbasedev, next);
+
+ vbasedev->num_irqs = dev_info.num_irqs;
+ vbasedev->num_regions = dev_info.num_regions;
+ vbasedev->flags = dev_info.flags;
+
+ trace_vfio_get_device(name, dev_info.flags, dev_info.num_regions,
+ dev_info.num_irqs);
+
+ vbasedev->reset_works = !!(dev_info.flags & VFIO_DEVICE_FLAGS_RESET);
+ return 0;
+}
+
+void vfio_put_base_device(VFIODevice *vbasedev)
+{
+ if (!vbasedev->group) {
+ return;
+ }
+ QLIST_REMOVE(vbasedev, next);
+ vbasedev->group = NULL;
+ trace_vfio_put_base_device(vbasedev->fd);
+ close(vbasedev->fd);
+}
+
+static int vfio_container_do_ioctl(AddressSpace *as, int32_t groupid,
+ int req, void *param)
+{
+ VFIOGroup *group;
+ VFIOContainer *container;
+ int ret = -1;
+
+ group = vfio_get_group(groupid, as);
+ if (!group) {
+ error_report("vfio: group %d not registered", groupid);
+ return ret;
+ }
+
+ container = group->container;
+ if (group->container) {
+ ret = ioctl(container->fd, req, param);
+ if (ret < 0) {
+ error_report("vfio: failed to ioctl %d to container: ret=%d, %s",
+ _IOC_NR(req) - VFIO_BASE, ret, strerror(errno));
+ }
+ }
+
+ vfio_put_group(group);
+
+ return ret;
+}
+
+int vfio_container_ioctl(AddressSpace *as, int32_t groupid,
+ int req, void *param)
+{
+ /* We allow only certain ioctls to the container */
+ switch (req) {
+ case VFIO_CHECK_EXTENSION:
+ case VFIO_IOMMU_SPAPR_TCE_GET_INFO:
+ case VFIO_EEH_PE_OP:
+ break;
+ default:
+ /* Return an error on unknown requests */
+ error_report("vfio: unsupported ioctl %X", req);
+ return -1;
+ }
+
+ return vfio_container_do_ioctl(as, groupid, req, param);
+}
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
new file mode 100644
index 00000000..4023d8e8
--- /dev/null
+++ b/hw/vfio/pci.c
@@ -0,0 +1,3797 @@
+/*
+ * vfio based device assignment support
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Authors:
+ * Alex Williamson <alex.williamson@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Based on qemu-kvm device-assignment:
+ * Adapted for KVM by Qumranet.
+ * Copyright (c) 2007, Neocleus, Alex Novik (alex@neocleus.com)
+ * Copyright (c) 2007, Neocleus, Guy Zana (guy@neocleus.com)
+ * Copyright (C) 2008, Qumranet, Amit Shah (amit.shah@qumranet.com)
+ * Copyright (C) 2008, Red Hat, Amit Shah (amit.shah@redhat.com)
+ * Copyright (C) 2008, IBM, Muli Ben-Yehuda (muli@il.ibm.com)
+ */
+
+#include <dirent.h>
+#include <linux/vfio.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "exec/address-spaces.h"
+#include "exec/memory.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "hw/pci/pci.h"
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "qemu/event_notifier.h"
+#include "qemu/queue.h"
+#include "qemu/range.h"
+#include "sysemu/kvm.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+#include "hw/vfio/vfio.h"
+#include "hw/vfio/vfio-common.h"
+
+struct VFIOPCIDevice;
+
+typedef struct VFIOQuirk {
+ MemoryRegion mem;
+ struct VFIOPCIDevice *vdev;
+ QLIST_ENTRY(VFIOQuirk) next;
+ struct {
+ uint32_t base_offset:TARGET_PAGE_BITS;
+ uint32_t address_offset:TARGET_PAGE_BITS;
+ uint32_t address_size:3;
+ uint32_t bar:3;
+
+ uint32_t address_match;
+ uint32_t address_mask;
+
+ uint32_t address_val:TARGET_PAGE_BITS;
+ uint32_t data_offset:TARGET_PAGE_BITS;
+ uint32_t data_size:3;
+
+ uint8_t flags;
+ uint8_t read_flags;
+ uint8_t write_flags;
+ } data;
+} VFIOQuirk;
+
+typedef struct VFIOBAR {
+ VFIORegion region;
+ bool ioport;
+ bool mem64;
+ QLIST_HEAD(, VFIOQuirk) quirks;
+} VFIOBAR;
+
+typedef struct VFIOVGARegion {
+ MemoryRegion mem;
+ off_t offset;
+ int nr;
+ QLIST_HEAD(, VFIOQuirk) quirks;
+} VFIOVGARegion;
+
+typedef struct VFIOVGA {
+ off_t fd_offset;
+ int fd;
+ VFIOVGARegion region[QEMU_PCI_VGA_NUM_REGIONS];
+} VFIOVGA;
+
+typedef struct VFIOINTx {
+ bool pending; /* interrupt pending */
+ bool kvm_accel; /* set when QEMU bypass through KVM enabled */
+ uint8_t pin; /* which pin to pull for qemu_set_irq */
+ EventNotifier interrupt; /* eventfd triggered on interrupt */
+ EventNotifier unmask; /* eventfd for unmask on QEMU bypass */
+ PCIINTxRoute route; /* routing info for QEMU bypass */
+ uint32_t mmap_timeout; /* delay to re-enable mmaps after interrupt */
+ QEMUTimer *mmap_timer; /* enable mmaps after periods w/o interrupts */
+} VFIOINTx;
+
+typedef struct VFIOMSIVector {
+ /*
+ * Two interrupt paths are configured per vector. The first, is only used
+ * for interrupts injected via QEMU. This is typically the non-accel path,
+ * but may also be used when we want QEMU to handle masking and pending
+ * bits. The KVM path bypasses QEMU and is therefore higher performance,
+ * but requires masking at the device. virq is used to track the MSI route
+ * through KVM, thus kvm_interrupt is only available when virq is set to a
+ * valid (>= 0) value.
+ */
+ EventNotifier interrupt;
+ EventNotifier kvm_interrupt;
+ struct VFIOPCIDevice *vdev; /* back pointer to device */
+ int virq;
+ bool use;
+} VFIOMSIVector;
+
+enum {
+ VFIO_INT_NONE = 0,
+ VFIO_INT_INTx = 1,
+ VFIO_INT_MSI = 2,
+ VFIO_INT_MSIX = 3,
+};
+
+/* Cache of MSI-X setup plus extra mmap and memory region for split BAR map */
+typedef struct VFIOMSIXInfo {
+ uint8_t table_bar;
+ uint8_t pba_bar;
+ uint16_t entries;
+ uint32_t table_offset;
+ uint32_t pba_offset;
+ MemoryRegion mmap_mem;
+ void *mmap;
+} VFIOMSIXInfo;
+
+typedef struct VFIOPCIDevice {
+ PCIDevice pdev;
+ VFIODevice vbasedev;
+ VFIOINTx intx;
+ unsigned int config_size;
+ uint8_t *emulated_config_bits; /* QEMU emulated bits, little-endian */
+ off_t config_offset; /* Offset of config space region within device fd */
+ unsigned int rom_size;
+ off_t rom_offset; /* Offset of ROM region within device fd */
+ void *rom;
+ int msi_cap_size;
+ VFIOMSIVector *msi_vectors;
+ VFIOMSIXInfo *msix;
+ int nr_vectors; /* Number of MSI/MSIX vectors currently in use */
+ int interrupt; /* Current interrupt type */
+ VFIOBAR bars[PCI_NUM_REGIONS - 1]; /* No ROM */
+ VFIOVGA vga; /* 0xa0000, 0x3b0, 0x3c0 */
+ PCIHostDeviceAddress host;
+ EventNotifier err_notifier;
+ EventNotifier req_notifier;
+ int (*resetfn)(struct VFIOPCIDevice *);
+ uint32_t features;
+#define VFIO_FEATURE_ENABLE_VGA_BIT 0
+#define VFIO_FEATURE_ENABLE_VGA (1 << VFIO_FEATURE_ENABLE_VGA_BIT)
+#define VFIO_FEATURE_ENABLE_REQ_BIT 1
+#define VFIO_FEATURE_ENABLE_REQ (1 << VFIO_FEATURE_ENABLE_REQ_BIT)
+ int32_t bootindex;
+ uint8_t pm_cap;
+ bool has_vga;
+ bool pci_aer;
+ bool req_enabled;
+ bool has_flr;
+ bool has_pm_reset;
+ bool rom_read_failed;
+} VFIOPCIDevice;
+
+typedef struct VFIORomBlacklistEntry {
+ uint16_t vendor_id;
+ uint16_t device_id;
+} VFIORomBlacklistEntry;
+
+/*
+ * List of device ids/vendor ids for which to disable
+ * option rom loading. This avoids the guest hangs during rom
+ * execution as noticed with the BCM 57810 card for lack of a
+ * more better way to handle such issues.
+ * The user can still override by specifying a romfile or
+ * rombar=1.
+ * Please see https://bugs.launchpad.net/qemu/+bug/1284874
+ * for an analysis of the 57810 card hang. When adding
+ * a new vendor id/device id combination below, please also add
+ * your card/environment details and information that could
+ * help in debugging to the bug tracking this issue
+ */
+static const VFIORomBlacklistEntry romblacklist[] = {
+ /* Broadcom BCM 57810 */
+ { 0x14e4, 0x168e }
+};
+
+#define MSIX_CAP_LENGTH 12
+
+static void vfio_disable_interrupts(VFIOPCIDevice *vdev);
+static uint32_t vfio_pci_read_config(PCIDevice *pdev, uint32_t addr, int len);
+static void vfio_pci_write_config(PCIDevice *pdev, uint32_t addr,
+ uint32_t val, int len);
+static void vfio_mmap_set_enabled(VFIOPCIDevice *vdev, bool enabled);
+
+/*
+ * Disabling BAR mmaping can be slow, but toggling it around INTx can
+ * also be a huge overhead. We try to get the best of both worlds by
+ * waiting until an interrupt to disable mmaps (subsequent transitions
+ * to the same state are effectively no overhead). If the interrupt has
+ * been serviced and the time gap is long enough, we re-enable mmaps for
+ * performance. This works well for things like graphics cards, which
+ * may not use their interrupt at all and are penalized to an unusable
+ * level by read/write BAR traps. Other devices, like NICs, have more
+ * regular interrupts and see much better latency by staying in non-mmap
+ * mode. We therefore set the default mmap_timeout such that a ping
+ * is just enough to keep the mmap disabled. Users can experiment with
+ * other options with the x-intx-mmap-timeout-ms parameter (a value of
+ * zero disables the timer).
+ */
+static void vfio_intx_mmap_enable(void *opaque)
+{
+ VFIOPCIDevice *vdev = opaque;
+
+ if (vdev->intx.pending) {
+ timer_mod(vdev->intx.mmap_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + vdev->intx.mmap_timeout);
+ return;
+ }
+
+ vfio_mmap_set_enabled(vdev, true);
+}
+
+static void vfio_intx_interrupt(void *opaque)
+{
+ VFIOPCIDevice *vdev = opaque;
+
+ if (!event_notifier_test_and_clear(&vdev->intx.interrupt)) {
+ return;
+ }
+
+ trace_vfio_intx_interrupt(vdev->vbasedev.name, 'A' + vdev->intx.pin);
+
+ vdev->intx.pending = true;
+ pci_irq_assert(&vdev->pdev);
+ vfio_mmap_set_enabled(vdev, false);
+ if (vdev->intx.mmap_timeout) {
+ timer_mod(vdev->intx.mmap_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + vdev->intx.mmap_timeout);
+ }
+}
+
+static void vfio_eoi(VFIODevice *vbasedev)
+{
+ VFIOPCIDevice *vdev = container_of(vbasedev, VFIOPCIDevice, vbasedev);
+
+ if (!vdev->intx.pending) {
+ return;
+ }
+
+ trace_vfio_eoi(vbasedev->name);
+
+ vdev->intx.pending = false;
+ pci_irq_deassert(&vdev->pdev);
+ vfio_unmask_single_irqindex(vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
+}
+
+static void vfio_enable_intx_kvm(VFIOPCIDevice *vdev)
+{
+#ifdef CONFIG_KVM
+ struct kvm_irqfd irqfd = {
+ .fd = event_notifier_get_fd(&vdev->intx.interrupt),
+ .gsi = vdev->intx.route.irq,
+ .flags = KVM_IRQFD_FLAG_RESAMPLE,
+ };
+ struct vfio_irq_set *irq_set;
+ int ret, argsz;
+ int32_t *pfd;
+
+ if (!VFIO_ALLOW_KVM_INTX || !kvm_irqfds_enabled() ||
+ vdev->intx.route.mode != PCI_INTX_ENABLED ||
+ !kvm_resamplefds_enabled()) {
+ return;
+ }
+
+ /* Get to a known interrupt state */
+ qemu_set_fd_handler(irqfd.fd, NULL, NULL, vdev);
+ vfio_mask_single_irqindex(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
+ vdev->intx.pending = false;
+ pci_irq_deassert(&vdev->pdev);
+
+ /* Get an eventfd for resample/unmask */
+ if (event_notifier_init(&vdev->intx.unmask, 0)) {
+ error_report("vfio: Error: event_notifier_init failed eoi");
+ goto fail;
+ }
+
+ /* KVM triggers it, VFIO listens for it */
+ irqfd.resamplefd = event_notifier_get_fd(&vdev->intx.unmask);
+
+ if (kvm_vm_ioctl(kvm_state, KVM_IRQFD, &irqfd)) {
+ error_report("vfio: Error: Failed to setup resample irqfd: %m");
+ goto fail_irqfd;
+ }
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_UNMASK;
+ irq_set->index = VFIO_PCI_INTX_IRQ_INDEX;
+ irq_set->start = 0;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+
+ *pfd = irqfd.resamplefd;
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
+ g_free(irq_set);
+ if (ret) {
+ error_report("vfio: Error: Failed to setup INTx unmask fd: %m");
+ goto fail_vfio;
+ }
+
+ /* Let'em rip */
+ vfio_unmask_single_irqindex(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
+
+ vdev->intx.kvm_accel = true;
+
+ trace_vfio_enable_intx_kvm(vdev->vbasedev.name);
+
+ return;
+
+fail_vfio:
+ irqfd.flags = KVM_IRQFD_FLAG_DEASSIGN;
+ kvm_vm_ioctl(kvm_state, KVM_IRQFD, &irqfd);
+fail_irqfd:
+ event_notifier_cleanup(&vdev->intx.unmask);
+fail:
+ qemu_set_fd_handler(irqfd.fd, vfio_intx_interrupt, NULL, vdev);
+ vfio_unmask_single_irqindex(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
+#endif
+}
+
+static void vfio_disable_intx_kvm(VFIOPCIDevice *vdev)
+{
+#ifdef CONFIG_KVM
+ struct kvm_irqfd irqfd = {
+ .fd = event_notifier_get_fd(&vdev->intx.interrupt),
+ .gsi = vdev->intx.route.irq,
+ .flags = KVM_IRQFD_FLAG_DEASSIGN,
+ };
+
+ if (!vdev->intx.kvm_accel) {
+ return;
+ }
+
+ /*
+ * Get to a known state, hardware masked, QEMU ready to accept new
+ * interrupts, QEMU IRQ de-asserted.
+ */
+ vfio_mask_single_irqindex(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
+ vdev->intx.pending = false;
+ pci_irq_deassert(&vdev->pdev);
+
+ /* Tell KVM to stop listening for an INTx irqfd */
+ if (kvm_vm_ioctl(kvm_state, KVM_IRQFD, &irqfd)) {
+ error_report("vfio: Error: Failed to disable INTx irqfd: %m");
+ }
+
+ /* We only need to close the eventfd for VFIO to cleanup the kernel side */
+ event_notifier_cleanup(&vdev->intx.unmask);
+
+ /* QEMU starts listening for interrupt events. */
+ qemu_set_fd_handler(irqfd.fd, vfio_intx_interrupt, NULL, vdev);
+
+ vdev->intx.kvm_accel = false;
+
+ /* If we've missed an event, let it re-fire through QEMU */
+ vfio_unmask_single_irqindex(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
+
+ trace_vfio_disable_intx_kvm(vdev->vbasedev.name);
+#endif
+}
+
+static void vfio_update_irq(PCIDevice *pdev)
+{
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pdev);
+ PCIINTxRoute route;
+
+ if (vdev->interrupt != VFIO_INT_INTx) {
+ return;
+ }
+
+ route = pci_device_route_intx_to_irq(&vdev->pdev, vdev->intx.pin);
+
+ if (!pci_intx_route_changed(&vdev->intx.route, &route)) {
+ return; /* Nothing changed */
+ }
+
+ trace_vfio_update_irq(vdev->vbasedev.name,
+ vdev->intx.route.irq, route.irq);
+
+ vfio_disable_intx_kvm(vdev);
+
+ vdev->intx.route = route;
+
+ if (route.mode != PCI_INTX_ENABLED) {
+ return;
+ }
+
+ vfio_enable_intx_kvm(vdev);
+
+ /* Re-enable the interrupt in cased we missed an EOI */
+ vfio_eoi(&vdev->vbasedev);
+}
+
+static int vfio_enable_intx(VFIOPCIDevice *vdev)
+{
+ uint8_t pin = vfio_pci_read_config(&vdev->pdev, PCI_INTERRUPT_PIN, 1);
+ int ret, argsz;
+ struct vfio_irq_set *irq_set;
+ int32_t *pfd;
+
+ if (!pin) {
+ return 0;
+ }
+
+ vfio_disable_interrupts(vdev);
+
+ vdev->intx.pin = pin - 1; /* Pin A (1) -> irq[0] */
+ pci_config_set_interrupt_pin(vdev->pdev.config, pin);
+
+#ifdef CONFIG_KVM
+ /*
+ * Only conditional to avoid generating error messages on platforms
+ * where we won't actually use the result anyway.
+ */
+ if (kvm_irqfds_enabled() && kvm_resamplefds_enabled()) {
+ vdev->intx.route = pci_device_route_intx_to_irq(&vdev->pdev,
+ vdev->intx.pin);
+ }
+#endif
+
+ ret = event_notifier_init(&vdev->intx.interrupt, 0);
+ if (ret) {
+ error_report("vfio: Error: event_notifier_init failed");
+ return ret;
+ }
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = VFIO_PCI_INTX_IRQ_INDEX;
+ irq_set->start = 0;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+
+ *pfd = event_notifier_get_fd(&vdev->intx.interrupt);
+ qemu_set_fd_handler(*pfd, vfio_intx_interrupt, NULL, vdev);
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
+ g_free(irq_set);
+ if (ret) {
+ error_report("vfio: Error: Failed to setup INTx fd: %m");
+ qemu_set_fd_handler(*pfd, NULL, NULL, vdev);
+ event_notifier_cleanup(&vdev->intx.interrupt);
+ return -errno;
+ }
+
+ vfio_enable_intx_kvm(vdev);
+
+ vdev->interrupt = VFIO_INT_INTx;
+
+ trace_vfio_enable_intx(vdev->vbasedev.name);
+
+ return 0;
+}
+
+static void vfio_disable_intx(VFIOPCIDevice *vdev)
+{
+ int fd;
+
+ timer_del(vdev->intx.mmap_timer);
+ vfio_disable_intx_kvm(vdev);
+ vfio_disable_irqindex(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
+ vdev->intx.pending = false;
+ pci_irq_deassert(&vdev->pdev);
+ vfio_mmap_set_enabled(vdev, true);
+
+ fd = event_notifier_get_fd(&vdev->intx.interrupt);
+ qemu_set_fd_handler(fd, NULL, NULL, vdev);
+ event_notifier_cleanup(&vdev->intx.interrupt);
+
+ vdev->interrupt = VFIO_INT_NONE;
+
+ trace_vfio_disable_intx(vdev->vbasedev.name);
+}
+
+/*
+ * MSI/X
+ */
+static void vfio_msi_interrupt(void *opaque)
+{
+ VFIOMSIVector *vector = opaque;
+ VFIOPCIDevice *vdev = vector->vdev;
+ int nr = vector - vdev->msi_vectors;
+
+ if (!event_notifier_test_and_clear(&vector->interrupt)) {
+ return;
+ }
+
+#ifdef DEBUG_VFIO
+ MSIMessage msg;
+
+ if (vdev->interrupt == VFIO_INT_MSIX) {
+ msg = msix_get_message(&vdev->pdev, nr);
+ } else if (vdev->interrupt == VFIO_INT_MSI) {
+ msg = msi_get_message(&vdev->pdev, nr);
+ } else {
+ abort();
+ }
+
+ trace_vfio_msi_interrupt(vdev->vbasedev.name, nr, msg.address, msg.data);
+#endif
+
+ if (vdev->interrupt == VFIO_INT_MSIX) {
+ msix_notify(&vdev->pdev, nr);
+ } else if (vdev->interrupt == VFIO_INT_MSI) {
+ msi_notify(&vdev->pdev, nr);
+ } else {
+ error_report("vfio: MSI interrupt receieved, but not enabled?");
+ }
+}
+
+static int vfio_enable_vectors(VFIOPCIDevice *vdev, bool msix)
+{
+ struct vfio_irq_set *irq_set;
+ int ret = 0, i, argsz;
+ int32_t *fds;
+
+ argsz = sizeof(*irq_set) + (vdev->nr_vectors * sizeof(*fds));
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = msix ? VFIO_PCI_MSIX_IRQ_INDEX : VFIO_PCI_MSI_IRQ_INDEX;
+ irq_set->start = 0;
+ irq_set->count = vdev->nr_vectors;
+ fds = (int32_t *)&irq_set->data;
+
+ for (i = 0; i < vdev->nr_vectors; i++) {
+ int fd = -1;
+
+ /*
+ * MSI vs MSI-X - The guest has direct access to MSI mask and pending
+ * bits, therefore we always use the KVM signaling path when setup.
+ * MSI-X mask and pending bits are emulated, so we want to use the
+ * KVM signaling path only when configured and unmasked.
+ */
+ if (vdev->msi_vectors[i].use) {
+ if (vdev->msi_vectors[i].virq < 0 ||
+ (msix && msix_is_masked(&vdev->pdev, i))) {
+ fd = event_notifier_get_fd(&vdev->msi_vectors[i].interrupt);
+ } else {
+ fd = event_notifier_get_fd(&vdev->msi_vectors[i].kvm_interrupt);
+ }
+ }
+
+ fds[i] = fd;
+ }
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
+
+ g_free(irq_set);
+
+ return ret;
+}
+
+static void vfio_add_kvm_msi_virq(VFIOMSIVector *vector, MSIMessage *msg,
+ bool msix)
+{
+ int virq;
+
+ if ((msix && !VFIO_ALLOW_KVM_MSIX) ||
+ (!msix && !VFIO_ALLOW_KVM_MSI) || !msg) {
+ return;
+ }
+
+ if (event_notifier_init(&vector->kvm_interrupt, 0)) {
+ return;
+ }
+
+ virq = kvm_irqchip_add_msi_route(kvm_state, *msg);
+ if (virq < 0) {
+ event_notifier_cleanup(&vector->kvm_interrupt);
+ return;
+ }
+
+ if (kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, &vector->kvm_interrupt,
+ NULL, virq) < 0) {
+ kvm_irqchip_release_virq(kvm_state, virq);
+ event_notifier_cleanup(&vector->kvm_interrupt);
+ return;
+ }
+
+ vector->virq = virq;
+}
+
+static void vfio_remove_kvm_msi_virq(VFIOMSIVector *vector)
+{
+ kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, &vector->kvm_interrupt,
+ vector->virq);
+ kvm_irqchip_release_virq(kvm_state, vector->virq);
+ vector->virq = -1;
+ event_notifier_cleanup(&vector->kvm_interrupt);
+}
+
+static void vfio_update_kvm_msi_virq(VFIOMSIVector *vector, MSIMessage msg)
+{
+ kvm_irqchip_update_msi_route(kvm_state, vector->virq, msg);
+}
+
+static int vfio_msix_vector_do_use(PCIDevice *pdev, unsigned int nr,
+ MSIMessage *msg, IOHandler *handler)
+{
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pdev);
+ VFIOMSIVector *vector;
+ int ret;
+
+ trace_vfio_msix_vector_do_use(vdev->vbasedev.name, nr);
+
+ vector = &vdev->msi_vectors[nr];
+
+ if (!vector->use) {
+ vector->vdev = vdev;
+ vector->virq = -1;
+ if (event_notifier_init(&vector->interrupt, 0)) {
+ error_report("vfio: Error: event_notifier_init failed");
+ }
+ vector->use = true;
+ msix_vector_use(pdev, nr);
+ }
+
+ qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
+ handler, NULL, vector);
+
+ /*
+ * Attempt to enable route through KVM irqchip,
+ * default to userspace handling if unavailable.
+ */
+ if (vector->virq >= 0) {
+ if (!msg) {
+ vfio_remove_kvm_msi_virq(vector);
+ } else {
+ vfio_update_kvm_msi_virq(vector, *msg);
+ }
+ } else {
+ vfio_add_kvm_msi_virq(vector, msg, true);
+ }
+
+ /*
+ * We don't want to have the host allocate all possible MSI vectors
+ * for a device if they're not in use, so we shutdown and incrementally
+ * increase them as needed.
+ */
+ if (vdev->nr_vectors < nr + 1) {
+ vfio_disable_irqindex(&vdev->vbasedev, VFIO_PCI_MSIX_IRQ_INDEX);
+ vdev->nr_vectors = nr + 1;
+ ret = vfio_enable_vectors(vdev, true);
+ if (ret) {
+ error_report("vfio: failed to enable vectors, %d", ret);
+ }
+ } else {
+ int argsz;
+ struct vfio_irq_set *irq_set;
+ int32_t *pfd;
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
+ VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = VFIO_PCI_MSIX_IRQ_INDEX;
+ irq_set->start = nr;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+
+ if (vector->virq >= 0) {
+ *pfd = event_notifier_get_fd(&vector->kvm_interrupt);
+ } else {
+ *pfd = event_notifier_get_fd(&vector->interrupt);
+ }
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
+ g_free(irq_set);
+ if (ret) {
+ error_report("vfio: failed to modify vector, %d", ret);
+ }
+ }
+
+ return 0;
+}
+
+static int vfio_msix_vector_use(PCIDevice *pdev,
+ unsigned int nr, MSIMessage msg)
+{
+ return vfio_msix_vector_do_use(pdev, nr, &msg, vfio_msi_interrupt);
+}
+
+static void vfio_msix_vector_release(PCIDevice *pdev, unsigned int nr)
+{
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pdev);
+ VFIOMSIVector *vector = &vdev->msi_vectors[nr];
+
+ trace_vfio_msix_vector_release(vdev->vbasedev.name, nr);
+
+ /*
+ * There are still old guests that mask and unmask vectors on every
+ * interrupt. If we're using QEMU bypass with a KVM irqfd, leave all of
+ * the KVM setup in place, simply switch VFIO to use the non-bypass
+ * eventfd. We'll then fire the interrupt through QEMU and the MSI-X
+ * core will mask the interrupt and set pending bits, allowing it to
+ * be re-asserted on unmask. Nothing to do if already using QEMU mode.
+ */
+ if (vector->virq >= 0) {
+ int argsz;
+ struct vfio_irq_set *irq_set;
+ int32_t *pfd;
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
+ VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = VFIO_PCI_MSIX_IRQ_INDEX;
+ irq_set->start = nr;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+
+ *pfd = event_notifier_get_fd(&vector->interrupt);
+
+ ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
+
+ g_free(irq_set);
+ }
+}
+
+static void vfio_enable_msix(VFIOPCIDevice *vdev)
+{
+ vfio_disable_interrupts(vdev);
+
+ vdev->msi_vectors = g_malloc0(vdev->msix->entries * sizeof(VFIOMSIVector));
+
+ vdev->interrupt = VFIO_INT_MSIX;
+
+ /*
+ * Some communication channels between VF & PF or PF & fw rely on the
+ * physical state of the device and expect that enabling MSI-X from the
+ * guest enables the same on the host. When our guest is Linux, the
+ * guest driver call to pci_enable_msix() sets the enabling bit in the
+ * MSI-X capability, but leaves the vector table masked. We therefore
+ * can't rely on a vector_use callback (from request_irq() in the guest)
+ * to switch the physical device into MSI-X mode because that may come a
+ * long time after pci_enable_msix(). This code enables vector 0 with
+ * triggering to userspace, then immediately release the vector, leaving
+ * the physical device with no vectors enabled, but MSI-X enabled, just
+ * like the guest view.
+ */
+ vfio_msix_vector_do_use(&vdev->pdev, 0, NULL, NULL);
+ vfio_msix_vector_release(&vdev->pdev, 0);
+
+ if (msix_set_vector_notifiers(&vdev->pdev, vfio_msix_vector_use,
+ vfio_msix_vector_release, NULL)) {
+ error_report("vfio: msix_set_vector_notifiers failed");
+ }
+
+ trace_vfio_enable_msix(vdev->vbasedev.name);
+}
+
+static void vfio_enable_msi(VFIOPCIDevice *vdev)
+{
+ int ret, i;
+
+ vfio_disable_interrupts(vdev);
+
+ vdev->nr_vectors = msi_nr_vectors_allocated(&vdev->pdev);
+retry:
+ vdev->msi_vectors = g_malloc0(vdev->nr_vectors * sizeof(VFIOMSIVector));
+
+ for (i = 0; i < vdev->nr_vectors; i++) {
+ VFIOMSIVector *vector = &vdev->msi_vectors[i];
+ MSIMessage msg = msi_get_message(&vdev->pdev, i);
+
+ vector->vdev = vdev;
+ vector->virq = -1;
+ vector->use = true;
+
+ if (event_notifier_init(&vector->interrupt, 0)) {
+ error_report("vfio: Error: event_notifier_init failed");
+ }
+
+ qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
+ vfio_msi_interrupt, NULL, vector);
+
+ /*
+ * Attempt to enable route through KVM irqchip,
+ * default to userspace handling if unavailable.
+ */
+ vfio_add_kvm_msi_virq(vector, &msg, false);
+ }
+
+ /* Set interrupt type prior to possible interrupts */
+ vdev->interrupt = VFIO_INT_MSI;
+
+ ret = vfio_enable_vectors(vdev, false);
+ if (ret) {
+ if (ret < 0) {
+ error_report("vfio: Error: Failed to setup MSI fds: %m");
+ } else if (ret != vdev->nr_vectors) {
+ error_report("vfio: Error: Failed to enable %d "
+ "MSI vectors, retry with %d", vdev->nr_vectors, ret);
+ }
+
+ for (i = 0; i < vdev->nr_vectors; i++) {
+ VFIOMSIVector *vector = &vdev->msi_vectors[i];
+ if (vector->virq >= 0) {
+ vfio_remove_kvm_msi_virq(vector);
+ }
+ qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
+ NULL, NULL, NULL);
+ event_notifier_cleanup(&vector->interrupt);
+ }
+
+ g_free(vdev->msi_vectors);
+
+ if (ret > 0 && ret != vdev->nr_vectors) {
+ vdev->nr_vectors = ret;
+ goto retry;
+ }
+ vdev->nr_vectors = 0;
+
+ /*
+ * Failing to setup MSI doesn't really fall within any specification.
+ * Let's try leaving interrupts disabled and hope the guest figures
+ * out to fall back to INTx for this device.
+ */
+ error_report("vfio: Error: Failed to enable MSI");
+ vdev->interrupt = VFIO_INT_NONE;
+
+ return;
+ }
+
+ trace_vfio_enable_msi(vdev->vbasedev.name, vdev->nr_vectors);
+}
+
+static void vfio_disable_msi_common(VFIOPCIDevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < vdev->nr_vectors; i++) {
+ VFIOMSIVector *vector = &vdev->msi_vectors[i];
+ if (vdev->msi_vectors[i].use) {
+ if (vector->virq >= 0) {
+ vfio_remove_kvm_msi_virq(vector);
+ }
+ qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
+ NULL, NULL, NULL);
+ event_notifier_cleanup(&vector->interrupt);
+ }
+ }
+
+ g_free(vdev->msi_vectors);
+ vdev->msi_vectors = NULL;
+ vdev->nr_vectors = 0;
+ vdev->interrupt = VFIO_INT_NONE;
+
+ vfio_enable_intx(vdev);
+}
+
+static void vfio_disable_msix(VFIOPCIDevice *vdev)
+{
+ int i;
+
+ msix_unset_vector_notifiers(&vdev->pdev);
+
+ /*
+ * MSI-X will only release vectors if MSI-X is still enabled on the
+ * device, check through the rest and release it ourselves if necessary.
+ */
+ for (i = 0; i < vdev->nr_vectors; i++) {
+ if (vdev->msi_vectors[i].use) {
+ vfio_msix_vector_release(&vdev->pdev, i);
+ msix_vector_unuse(&vdev->pdev, i);
+ }
+ }
+
+ if (vdev->nr_vectors) {
+ vfio_disable_irqindex(&vdev->vbasedev, VFIO_PCI_MSIX_IRQ_INDEX);
+ }
+
+ vfio_disable_msi_common(vdev);
+
+ trace_vfio_disable_msix(vdev->vbasedev.name);
+}
+
+static void vfio_disable_msi(VFIOPCIDevice *vdev)
+{
+ vfio_disable_irqindex(&vdev->vbasedev, VFIO_PCI_MSI_IRQ_INDEX);
+ vfio_disable_msi_common(vdev);
+
+ trace_vfio_disable_msi(vdev->vbasedev.name);
+}
+
+static void vfio_update_msi(VFIOPCIDevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < vdev->nr_vectors; i++) {
+ VFIOMSIVector *vector = &vdev->msi_vectors[i];
+ MSIMessage msg;
+
+ if (!vector->use || vector->virq < 0) {
+ continue;
+ }
+
+ msg = msi_get_message(&vdev->pdev, i);
+ vfio_update_kvm_msi_virq(vector, msg);
+ }
+}
+
+static void vfio_pci_load_rom(VFIOPCIDevice *vdev)
+{
+ struct vfio_region_info reg_info = {
+ .argsz = sizeof(reg_info),
+ .index = VFIO_PCI_ROM_REGION_INDEX
+ };
+ uint64_t size;
+ off_t off = 0;
+ ssize_t bytes;
+
+ if (ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_REGION_INFO, &reg_info)) {
+ error_report("vfio: Error getting ROM info: %m");
+ return;
+ }
+
+ trace_vfio_pci_load_rom(vdev->vbasedev.name, (unsigned long)reg_info.size,
+ (unsigned long)reg_info.offset,
+ (unsigned long)reg_info.flags);
+
+ vdev->rom_size = size = reg_info.size;
+ vdev->rom_offset = reg_info.offset;
+
+ if (!vdev->rom_size) {
+ vdev->rom_read_failed = true;
+ error_report("vfio-pci: Cannot read device rom at "
+ "%s", vdev->vbasedev.name);
+ error_printf("Device option ROM contents are probably invalid "
+ "(check dmesg).\nSkip option ROM probe with rombar=0, "
+ "or load from file with romfile=\n");
+ return;
+ }
+
+ vdev->rom = g_malloc(size);
+ memset(vdev->rom, 0xff, size);
+
+ while (size) {
+ bytes = pread(vdev->vbasedev.fd, vdev->rom + off,
+ size, vdev->rom_offset + off);
+ if (bytes == 0) {
+ break;
+ } else if (bytes > 0) {
+ off += bytes;
+ size -= bytes;
+ } else {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ error_report("vfio: Error reading device ROM: %m");
+ break;
+ }
+ }
+}
+
+static uint64_t vfio_rom_read(void *opaque, hwaddr addr, unsigned size)
+{
+ VFIOPCIDevice *vdev = opaque;
+ union {
+ uint8_t byte;
+ uint16_t word;
+ uint32_t dword;
+ uint64_t qword;
+ } val;
+ uint64_t data = 0;
+
+ /* Load the ROM lazily when the guest tries to read it */
+ if (unlikely(!vdev->rom && !vdev->rom_read_failed)) {
+ vfio_pci_load_rom(vdev);
+ }
+
+ memcpy(&val, vdev->rom + addr,
+ (addr < vdev->rom_size) ? MIN(size, vdev->rom_size - addr) : 0);
+
+ switch (size) {
+ case 1:
+ data = val.byte;
+ break;
+ case 2:
+ data = le16_to_cpu(val.word);
+ break;
+ case 4:
+ data = le32_to_cpu(val.dword);
+ break;
+ default:
+ hw_error("vfio: unsupported read size, %d bytes\n", size);
+ break;
+ }
+
+ trace_vfio_rom_read(vdev->vbasedev.name, addr, size, data);
+
+ return data;
+}
+
+static void vfio_rom_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+}
+
+static const MemoryRegionOps vfio_rom_ops = {
+ .read = vfio_rom_read,
+ .write = vfio_rom_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static bool vfio_blacklist_opt_rom(VFIOPCIDevice *vdev)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ uint16_t vendor_id, device_id;
+ int count = 0;
+
+ vendor_id = pci_get_word(pdev->config + PCI_VENDOR_ID);
+ device_id = pci_get_word(pdev->config + PCI_DEVICE_ID);
+
+ while (count < ARRAY_SIZE(romblacklist)) {
+ if (romblacklist[count].vendor_id == vendor_id &&
+ romblacklist[count].device_id == device_id) {
+ return true;
+ }
+ count++;
+ }
+
+ return false;
+}
+
+static void vfio_pci_size_rom(VFIOPCIDevice *vdev)
+{
+ uint32_t orig, size = cpu_to_le32((uint32_t)PCI_ROM_ADDRESS_MASK);
+ off_t offset = vdev->config_offset + PCI_ROM_ADDRESS;
+ DeviceState *dev = DEVICE(vdev);
+ char name[32];
+ int fd = vdev->vbasedev.fd;
+
+ if (vdev->pdev.romfile || !vdev->pdev.rom_bar) {
+ /* Since pci handles romfile, just print a message and return */
+ if (vfio_blacklist_opt_rom(vdev) && vdev->pdev.romfile) {
+ error_printf("Warning : Device at %04x:%02x:%02x.%x "
+ "is known to cause system instability issues during "
+ "option rom execution. "
+ "Proceeding anyway since user specified romfile\n",
+ vdev->host.domain, vdev->host.bus, vdev->host.slot,
+ vdev->host.function);
+ }
+ return;
+ }
+
+ /*
+ * Use the same size ROM BAR as the physical device. The contents
+ * will get filled in later when the guest tries to read it.
+ */
+ if (pread(fd, &orig, 4, offset) != 4 ||
+ pwrite(fd, &size, 4, offset) != 4 ||
+ pread(fd, &size, 4, offset) != 4 ||
+ pwrite(fd, &orig, 4, offset) != 4) {
+ error_report("%s(%04x:%02x:%02x.%x) failed: %m",
+ __func__, vdev->host.domain, vdev->host.bus,
+ vdev->host.slot, vdev->host.function);
+ return;
+ }
+
+ size = ~(le32_to_cpu(size) & PCI_ROM_ADDRESS_MASK) + 1;
+
+ if (!size) {
+ return;
+ }
+
+ if (vfio_blacklist_opt_rom(vdev)) {
+ if (dev->opts && qemu_opt_get(dev->opts, "rombar")) {
+ error_printf("Warning : Device at %04x:%02x:%02x.%x "
+ "is known to cause system instability issues during "
+ "option rom execution. "
+ "Proceeding anyway since user specified non zero value for "
+ "rombar\n",
+ vdev->host.domain, vdev->host.bus, vdev->host.slot,
+ vdev->host.function);
+ } else {
+ error_printf("Warning : Rom loading for device at "
+ "%04x:%02x:%02x.%x has been disabled due to "
+ "system instability issues. "
+ "Specify rombar=1 or romfile to force\n",
+ vdev->host.domain, vdev->host.bus, vdev->host.slot,
+ vdev->host.function);
+ return;
+ }
+ }
+
+ trace_vfio_pci_size_rom(vdev->vbasedev.name, size);
+
+ snprintf(name, sizeof(name), "vfio[%04x:%02x:%02x.%x].rom",
+ vdev->host.domain, vdev->host.bus, vdev->host.slot,
+ vdev->host.function);
+
+ memory_region_init_io(&vdev->pdev.rom, OBJECT(vdev),
+ &vfio_rom_ops, vdev, name, size);
+
+ pci_register_bar(&vdev->pdev, PCI_ROM_SLOT,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &vdev->pdev.rom);
+
+ vdev->pdev.has_rom = true;
+ vdev->rom_read_failed = false;
+}
+
+static void vfio_vga_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VFIOVGARegion *region = opaque;
+ VFIOVGA *vga = container_of(region, VFIOVGA, region[region->nr]);
+ union {
+ uint8_t byte;
+ uint16_t word;
+ uint32_t dword;
+ uint64_t qword;
+ } buf;
+ off_t offset = vga->fd_offset + region->offset + addr;
+
+ switch (size) {
+ case 1:
+ buf.byte = data;
+ break;
+ case 2:
+ buf.word = cpu_to_le16(data);
+ break;
+ case 4:
+ buf.dword = cpu_to_le32(data);
+ break;
+ default:
+ hw_error("vfio: unsupported write size, %d bytes", size);
+ break;
+ }
+
+ if (pwrite(vga->fd, &buf, size, offset) != size) {
+ error_report("%s(,0x%"HWADDR_PRIx", 0x%"PRIx64", %d) failed: %m",
+ __func__, region->offset + addr, data, size);
+ }
+
+ trace_vfio_vga_write(region->offset + addr, data, size);
+}
+
+static uint64_t vfio_vga_read(void *opaque, hwaddr addr, unsigned size)
+{
+ VFIOVGARegion *region = opaque;
+ VFIOVGA *vga = container_of(region, VFIOVGA, region[region->nr]);
+ union {
+ uint8_t byte;
+ uint16_t word;
+ uint32_t dword;
+ uint64_t qword;
+ } buf;
+ uint64_t data = 0;
+ off_t offset = vga->fd_offset + region->offset + addr;
+
+ if (pread(vga->fd, &buf, size, offset) != size) {
+ error_report("%s(,0x%"HWADDR_PRIx", %d) failed: %m",
+ __func__, region->offset + addr, size);
+ return (uint64_t)-1;
+ }
+
+ switch (size) {
+ case 1:
+ data = buf.byte;
+ break;
+ case 2:
+ data = le16_to_cpu(buf.word);
+ break;
+ case 4:
+ data = le32_to_cpu(buf.dword);
+ break;
+ default:
+ hw_error("vfio: unsupported read size, %d bytes", size);
+ break;
+ }
+
+ trace_vfio_vga_read(region->offset + addr, size, data);
+
+ return data;
+}
+
+static const MemoryRegionOps vfio_vga_ops = {
+ .read = vfio_vga_read,
+ .write = vfio_vga_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/*
+ * Device specific quirks
+ */
+
+/* Is range1 fully contained within range2? */
+static bool vfio_range_contained(uint64_t first1, uint64_t len1,
+ uint64_t first2, uint64_t len2) {
+ return (first1 >= first2 && first1 + len1 <= first2 + len2);
+}
+
+static bool vfio_flags_enabled(uint8_t flags, uint8_t mask)
+{
+ return (mask && (flags & mask) == mask);
+}
+
+static uint64_t vfio_generic_window_quirk_read(void *opaque,
+ hwaddr addr, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+ uint64_t data;
+
+ if (vfio_flags_enabled(quirk->data.flags, quirk->data.read_flags) &&
+ ranges_overlap(addr, size,
+ quirk->data.data_offset, quirk->data.data_size)) {
+ hwaddr offset = addr - quirk->data.data_offset;
+
+ if (!vfio_range_contained(addr, size, quirk->data.data_offset,
+ quirk->data.data_size)) {
+ hw_error("%s: window data read not fully contained: %s",
+ __func__, memory_region_name(&quirk->mem));
+ }
+
+ data = vfio_pci_read_config(&vdev->pdev,
+ quirk->data.address_val + offset, size);
+
+ trace_vfio_generic_window_quirk_read(memory_region_name(&quirk->mem),
+ vdev->vbasedev.name,
+ quirk->data.bar,
+ addr, size, data);
+ } else {
+ data = vfio_region_read(&vdev->bars[quirk->data.bar].region,
+ addr + quirk->data.base_offset, size);
+ }
+
+ return data;
+}
+
+static void vfio_generic_window_quirk_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+
+ if (ranges_overlap(addr, size,
+ quirk->data.address_offset, quirk->data.address_size)) {
+
+ if (addr != quirk->data.address_offset) {
+ hw_error("%s: offset write into address window: %s",
+ __func__, memory_region_name(&quirk->mem));
+ }
+
+ if ((data & ~quirk->data.address_mask) == quirk->data.address_match) {
+ quirk->data.flags |= quirk->data.write_flags |
+ quirk->data.read_flags;
+ quirk->data.address_val = data & quirk->data.address_mask;
+ } else {
+ quirk->data.flags &= ~(quirk->data.write_flags |
+ quirk->data.read_flags);
+ }
+ }
+
+ if (vfio_flags_enabled(quirk->data.flags, quirk->data.write_flags) &&
+ ranges_overlap(addr, size,
+ quirk->data.data_offset, quirk->data.data_size)) {
+ hwaddr offset = addr - quirk->data.data_offset;
+
+ if (!vfio_range_contained(addr, size, quirk->data.data_offset,
+ quirk->data.data_size)) {
+ hw_error("%s: window data write not fully contained: %s",
+ __func__, memory_region_name(&quirk->mem));
+ }
+
+ vfio_pci_write_config(&vdev->pdev,
+ quirk->data.address_val + offset, data, size);
+ trace_vfio_generic_window_quirk_write(memory_region_name(&quirk->mem),
+ vdev->vbasedev.name,
+ quirk->data.bar,
+ addr, data, size);
+ return;
+ }
+
+ vfio_region_write(&vdev->bars[quirk->data.bar].region,
+ addr + quirk->data.base_offset, data, size);
+}
+
+static const MemoryRegionOps vfio_generic_window_quirk = {
+ .read = vfio_generic_window_quirk_read,
+ .write = vfio_generic_window_quirk_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t vfio_generic_quirk_read(void *opaque,
+ hwaddr addr, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+ hwaddr base = quirk->data.address_match & TARGET_PAGE_MASK;
+ hwaddr offset = quirk->data.address_match & ~TARGET_PAGE_MASK;
+ uint64_t data;
+
+ if (vfio_flags_enabled(quirk->data.flags, quirk->data.read_flags) &&
+ ranges_overlap(addr, size, offset, quirk->data.address_mask + 1)) {
+ if (!vfio_range_contained(addr, size, offset,
+ quirk->data.address_mask + 1)) {
+ hw_error("%s: read not fully contained: %s",
+ __func__, memory_region_name(&quirk->mem));
+ }
+
+ data = vfio_pci_read_config(&vdev->pdev, addr - offset, size);
+
+ trace_vfio_generic_quirk_read(memory_region_name(&quirk->mem),
+ vdev->vbasedev.name, quirk->data.bar,
+ addr + base, size, data);
+ } else {
+ data = vfio_region_read(&vdev->bars[quirk->data.bar].region,
+ addr + base, size);
+ }
+
+ return data;
+}
+
+static void vfio_generic_quirk_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+ hwaddr base = quirk->data.address_match & TARGET_PAGE_MASK;
+ hwaddr offset = quirk->data.address_match & ~TARGET_PAGE_MASK;
+
+ if (vfio_flags_enabled(quirk->data.flags, quirk->data.write_flags) &&
+ ranges_overlap(addr, size, offset, quirk->data.address_mask + 1)) {
+ if (!vfio_range_contained(addr, size, offset,
+ quirk->data.address_mask + 1)) {
+ hw_error("%s: write not fully contained: %s",
+ __func__, memory_region_name(&quirk->mem));
+ }
+
+ vfio_pci_write_config(&vdev->pdev, addr - offset, data, size);
+
+ trace_vfio_generic_quirk_write(memory_region_name(&quirk->mem),
+ vdev->vbasedev.name, quirk->data.bar,
+ addr + base, data, size);
+ } else {
+ vfio_region_write(&vdev->bars[quirk->data.bar].region,
+ addr + base, data, size);
+ }
+}
+
+static const MemoryRegionOps vfio_generic_quirk = {
+ .read = vfio_generic_quirk_read,
+ .write = vfio_generic_quirk_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+#define PCI_VENDOR_ID_ATI 0x1002
+
+/*
+ * Radeon HD cards (HD5450 & HD7850) report the upper byte of the I/O port BAR
+ * through VGA register 0x3c3. On newer cards, the I/O port BAR is always
+ * BAR4 (older cards like the X550 used BAR1, but we don't care to support
+ * those). Note that on bare metal, a read of 0x3c3 doesn't always return the
+ * I/O port BAR address. Originally this was coded to return the virtual BAR
+ * address only if the physical register read returns the actual BAR address,
+ * but users have reported greater success if we return the virtual address
+ * unconditionally.
+ */
+static uint64_t vfio_ati_3c3_quirk_read(void *opaque,
+ hwaddr addr, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+ uint64_t data = vfio_pci_read_config(&vdev->pdev,
+ PCI_BASE_ADDRESS_0 + (4 * 4) + 1,
+ size);
+ trace_vfio_ati_3c3_quirk_read(data);
+
+ return data;
+}
+
+static const MemoryRegionOps vfio_ati_3c3_quirk = {
+ .read = vfio_ati_3c3_quirk_read,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void vfio_vga_probe_ati_3c3_quirk(VFIOPCIDevice *vdev)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ VFIOQuirk *quirk;
+
+ if (pci_get_word(pdev->config + PCI_VENDOR_ID) != PCI_VENDOR_ID_ATI) {
+ return;
+ }
+
+ /*
+ * As long as the BAR is >= 256 bytes it will be aligned such that the
+ * lower byte is always zero. Filter out anything else, if it exists.
+ */
+ if (!vdev->bars[4].ioport || vdev->bars[4].region.size < 256) {
+ return;
+ }
+
+ quirk = g_malloc0(sizeof(*quirk));
+ quirk->vdev = vdev;
+
+ memory_region_init_io(&quirk->mem, OBJECT(vdev), &vfio_ati_3c3_quirk, quirk,
+ "vfio-ati-3c3-quirk", 1);
+ memory_region_add_subregion(&vdev->vga.region[QEMU_PCI_VGA_IO_HI].mem,
+ 3 /* offset 3 bytes from 0x3c0 */, &quirk->mem);
+
+ QLIST_INSERT_HEAD(&vdev->vga.region[QEMU_PCI_VGA_IO_HI].quirks,
+ quirk, next);
+
+ trace_vfio_vga_probe_ati_3c3_quirk(vdev->vbasedev.name);
+}
+
+/*
+ * Newer ATI/AMD devices, including HD5450 and HD7850, have a window to PCI
+ * config space through MMIO BAR2 at offset 0x4000. Nothing seems to access
+ * the MMIO space directly, but a window to this space is provided through
+ * I/O port BAR4. Offset 0x0 is the address register and offset 0x4 is the
+ * data register. When the address is programmed to a range of 0x4000-0x4fff
+ * PCI configuration space is available. Experimentation seems to indicate
+ * that only read-only access is provided, but we drop writes when the window
+ * is enabled to config space nonetheless.
+ */
+static void vfio_probe_ati_bar4_window_quirk(VFIOPCIDevice *vdev, int nr)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ VFIOQuirk *quirk;
+
+ if (!vdev->has_vga || nr != 4 ||
+ pci_get_word(pdev->config + PCI_VENDOR_ID) != PCI_VENDOR_ID_ATI) {
+ return;
+ }
+
+ quirk = g_malloc0(sizeof(*quirk));
+ quirk->vdev = vdev;
+ quirk->data.address_size = 4;
+ quirk->data.data_offset = 4;
+ quirk->data.data_size = 4;
+ quirk->data.address_match = 0x4000;
+ quirk->data.address_mask = PCIE_CONFIG_SPACE_SIZE - 1;
+ quirk->data.bar = nr;
+ quirk->data.read_flags = quirk->data.write_flags = 1;
+
+ memory_region_init_io(&quirk->mem, OBJECT(vdev),
+ &vfio_generic_window_quirk, quirk,
+ "vfio-ati-bar4-window-quirk", 8);
+ memory_region_add_subregion_overlap(&vdev->bars[nr].region.mem,
+ quirk->data.base_offset, &quirk->mem, 1);
+
+ QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next);
+
+ trace_vfio_probe_ati_bar4_window_quirk(vdev->vbasedev.name);
+}
+
+#define PCI_VENDOR_ID_REALTEK 0x10ec
+
+/*
+ * RTL8168 devices have a backdoor that can access the MSI-X table. At BAR2
+ * offset 0x70 there is a dword data register, offset 0x74 is a dword address
+ * register. According to the Linux r8169 driver, the MSI-X table is addressed
+ * when the "type" portion of the address register is set to 0x1. This appears
+ * to be bits 16:30. Bit 31 is both a write indicator and some sort of
+ * "address latched" indicator. Bits 12:15 are a mask field, which we can
+ * ignore because the MSI-X table should always be accessed as a dword (full
+ * mask). Bits 0:11 is offset within the type.
+ *
+ * Example trace:
+ *
+ * Read from MSI-X table offset 0
+ * vfio: vfio_bar_write(0000:05:00.0:BAR2+0x74, 0x1f000, 4) // store read addr
+ * vfio: vfio_bar_read(0000:05:00.0:BAR2+0x74, 4) = 0x8001f000 // latch
+ * vfio: vfio_bar_read(0000:05:00.0:BAR2+0x70, 4) = 0xfee00398 // read data
+ *
+ * Write 0xfee00000 to MSI-X table offset 0
+ * vfio: vfio_bar_write(0000:05:00.0:BAR2+0x70, 0xfee00000, 4) // write data
+ * vfio: vfio_bar_write(0000:05:00.0:BAR2+0x74, 0x8001f000, 4) // do write
+ * vfio: vfio_bar_read(0000:05:00.0:BAR2+0x74, 4) = 0x1f000 // complete
+ */
+
+static uint64_t vfio_rtl8168_window_quirk_read(void *opaque,
+ hwaddr addr, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+
+ switch (addr) {
+ case 4: /* address */
+ if (quirk->data.flags) {
+ trace_vfio_rtl8168_window_quirk_read_fake(
+ memory_region_name(&quirk->mem),
+ vdev->vbasedev.name);
+
+ return quirk->data.address_match ^ 0x80000000U;
+ }
+ break;
+ case 0: /* data */
+ if (quirk->data.flags) {
+ uint64_t val;
+
+ trace_vfio_rtl8168_window_quirk_read_table(
+ memory_region_name(&quirk->mem),
+ vdev->vbasedev.name);
+
+ if (!(vdev->pdev.cap_present & QEMU_PCI_CAP_MSIX)) {
+ return 0;
+ }
+
+ memory_region_dispatch_read(&vdev->pdev.msix_table_mmio,
+ (hwaddr)(quirk->data.address_match
+ & 0xfff),
+ &val,
+ size,
+ MEMTXATTRS_UNSPECIFIED);
+ return val;
+ }
+ }
+
+ trace_vfio_rtl8168_window_quirk_read_direct(memory_region_name(&quirk->mem),
+ vdev->vbasedev.name);
+
+ return vfio_region_read(&vdev->bars[quirk->data.bar].region,
+ addr + 0x70, size);
+}
+
+static void vfio_rtl8168_window_quirk_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+
+ switch (addr) {
+ case 4: /* address */
+ if ((data & 0x7fff0000) == 0x10000) {
+ if (data & 0x80000000U &&
+ vdev->pdev.cap_present & QEMU_PCI_CAP_MSIX) {
+
+ trace_vfio_rtl8168_window_quirk_write_table(
+ memory_region_name(&quirk->mem),
+ vdev->vbasedev.name);
+
+ memory_region_dispatch_write(&vdev->pdev.msix_table_mmio,
+ (hwaddr)(data & 0xfff),
+ (uint64_t)quirk->data.address_mask,
+ size, MEMTXATTRS_UNSPECIFIED);
+ }
+
+ quirk->data.flags = 1;
+ quirk->data.address_match = data;
+
+ return;
+ }
+ quirk->data.flags = 0;
+ break;
+ case 0: /* data */
+ quirk->data.address_mask = data;
+ break;
+ }
+
+ trace_vfio_rtl8168_window_quirk_write_direct(
+ memory_region_name(&quirk->mem),
+ vdev->vbasedev.name);
+
+ vfio_region_write(&vdev->bars[quirk->data.bar].region,
+ addr + 0x70, data, size);
+}
+
+static const MemoryRegionOps vfio_rtl8168_window_quirk = {
+ .read = vfio_rtl8168_window_quirk_read,
+ .write = vfio_rtl8168_window_quirk_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void vfio_probe_rtl8168_bar2_window_quirk(VFIOPCIDevice *vdev, int nr)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ VFIOQuirk *quirk;
+
+ if (pci_get_word(pdev->config + PCI_VENDOR_ID) != PCI_VENDOR_ID_REALTEK ||
+ pci_get_word(pdev->config + PCI_DEVICE_ID) != 0x8168 || nr != 2) {
+ return;
+ }
+
+ quirk = g_malloc0(sizeof(*quirk));
+ quirk->vdev = vdev;
+ quirk->data.bar = nr;
+
+ memory_region_init_io(&quirk->mem, OBJECT(vdev), &vfio_rtl8168_window_quirk,
+ quirk, "vfio-rtl8168-window-quirk", 8);
+ memory_region_add_subregion_overlap(&vdev->bars[nr].region.mem,
+ 0x70, &quirk->mem, 1);
+
+ QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next);
+
+ trace_vfio_probe_rtl8168_bar2_window_quirk(vdev->vbasedev.name);
+}
+/*
+ * Trap the BAR2 MMIO window to config space as well.
+ */
+static void vfio_probe_ati_bar2_4000_quirk(VFIOPCIDevice *vdev, int nr)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ VFIOQuirk *quirk;
+
+ /* Only enable on newer devices where BAR2 is 64bit */
+ if (!vdev->has_vga || nr != 2 || !vdev->bars[2].mem64 ||
+ pci_get_word(pdev->config + PCI_VENDOR_ID) != PCI_VENDOR_ID_ATI) {
+ return;
+ }
+
+ quirk = g_malloc0(sizeof(*quirk));
+ quirk->vdev = vdev;
+ quirk->data.flags = quirk->data.read_flags = quirk->data.write_flags = 1;
+ quirk->data.address_match = 0x4000;
+ quirk->data.address_mask = PCIE_CONFIG_SPACE_SIZE - 1;
+ quirk->data.bar = nr;
+
+ memory_region_init_io(&quirk->mem, OBJECT(vdev), &vfio_generic_quirk, quirk,
+ "vfio-ati-bar2-4000-quirk",
+ TARGET_PAGE_ALIGN(quirk->data.address_mask + 1));
+ memory_region_add_subregion_overlap(&vdev->bars[nr].region.mem,
+ quirk->data.address_match & TARGET_PAGE_MASK,
+ &quirk->mem, 1);
+
+ QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next);
+
+ trace_vfio_probe_ati_bar2_4000_quirk(vdev->vbasedev.name);
+}
+
+/*
+ * Older ATI/AMD cards like the X550 have a similar window to that above.
+ * I/O port BAR1 provides a window to a mirror of PCI config space located
+ * in BAR2 at offset 0xf00. We don't care to support such older cards, but
+ * note it for future reference.
+ */
+
+#define PCI_VENDOR_ID_NVIDIA 0x10de
+
+/*
+ * Nvidia has several different methods to get to config space, the
+ * nouveu project has several of these documented here:
+ * https://github.com/pathscale/envytools/tree/master/hwdocs
+ *
+ * The first quirk is actually not documented in envytools and is found
+ * on 10de:01d1 (NVIDIA Corporation G72 [GeForce 7300 LE]). This is an
+ * NV46 chipset. The backdoor uses the legacy VGA I/O ports to access
+ * the mirror of PCI config space found at BAR0 offset 0x1800. The access
+ * sequence first writes 0x338 to I/O port 0x3d4. The target offset is
+ * then written to 0x3d0. Finally 0x538 is written for a read and 0x738
+ * is written for a write to 0x3d4. The BAR0 offset is then accessible
+ * through 0x3d0. This quirk doesn't seem to be necessary on newer cards
+ * that use the I/O port BAR5 window but it doesn't hurt to leave it.
+ */
+enum {
+ NV_3D0_NONE = 0,
+ NV_3D0_SELECT,
+ NV_3D0_WINDOW,
+ NV_3D0_READ,
+ NV_3D0_WRITE,
+};
+
+static uint64_t vfio_nvidia_3d0_quirk_read(void *opaque,
+ hwaddr addr, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+ PCIDevice *pdev = &vdev->pdev;
+ uint64_t data = vfio_vga_read(&vdev->vga.region[QEMU_PCI_VGA_IO_HI],
+ addr + quirk->data.base_offset, size);
+
+ if (quirk->data.flags == NV_3D0_READ && addr == quirk->data.data_offset) {
+ data = vfio_pci_read_config(pdev, quirk->data.address_val, size);
+ trace_vfio_nvidia_3d0_quirk_read(size, data);
+ }
+
+ quirk->data.flags = NV_3D0_NONE;
+
+ return data;
+}
+
+static void vfio_nvidia_3d0_quirk_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+ PCIDevice *pdev = &vdev->pdev;
+
+ switch (quirk->data.flags) {
+ case NV_3D0_NONE:
+ if (addr == quirk->data.address_offset && data == 0x338) {
+ quirk->data.flags = NV_3D0_SELECT;
+ }
+ break;
+ case NV_3D0_SELECT:
+ quirk->data.flags = NV_3D0_NONE;
+ if (addr == quirk->data.data_offset &&
+ (data & ~quirk->data.address_mask) == quirk->data.address_match) {
+ quirk->data.flags = NV_3D0_WINDOW;
+ quirk->data.address_val = data & quirk->data.address_mask;
+ }
+ break;
+ case NV_3D0_WINDOW:
+ quirk->data.flags = NV_3D0_NONE;
+ if (addr == quirk->data.address_offset) {
+ if (data == 0x538) {
+ quirk->data.flags = NV_3D0_READ;
+ } else if (data == 0x738) {
+ quirk->data.flags = NV_3D0_WRITE;
+ }
+ }
+ break;
+ case NV_3D0_WRITE:
+ quirk->data.flags = NV_3D0_NONE;
+ if (addr == quirk->data.data_offset) {
+ vfio_pci_write_config(pdev, quirk->data.address_val, data, size);
+ trace_vfio_nvidia_3d0_quirk_write(data, size);
+ return;
+ }
+ break;
+ }
+
+ vfio_vga_write(&vdev->vga.region[QEMU_PCI_VGA_IO_HI],
+ addr + quirk->data.base_offset, data, size);
+}
+
+static const MemoryRegionOps vfio_nvidia_3d0_quirk = {
+ .read = vfio_nvidia_3d0_quirk_read,
+ .write = vfio_nvidia_3d0_quirk_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void vfio_vga_probe_nvidia_3d0_quirk(VFIOPCIDevice *vdev)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ VFIOQuirk *quirk;
+
+ if (pci_get_word(pdev->config + PCI_VENDOR_ID) != PCI_VENDOR_ID_NVIDIA ||
+ !vdev->bars[1].region.size) {
+ return;
+ }
+
+ quirk = g_malloc0(sizeof(*quirk));
+ quirk->vdev = vdev;
+ quirk->data.base_offset = 0x10;
+ quirk->data.address_offset = 4;
+ quirk->data.address_size = 2;
+ quirk->data.address_match = 0x1800;
+ quirk->data.address_mask = PCI_CONFIG_SPACE_SIZE - 1;
+ quirk->data.data_offset = 0;
+ quirk->data.data_size = 4;
+
+ memory_region_init_io(&quirk->mem, OBJECT(vdev), &vfio_nvidia_3d0_quirk,
+ quirk, "vfio-nvidia-3d0-quirk", 6);
+ memory_region_add_subregion(&vdev->vga.region[QEMU_PCI_VGA_IO_HI].mem,
+ quirk->data.base_offset, &quirk->mem);
+
+ QLIST_INSERT_HEAD(&vdev->vga.region[QEMU_PCI_VGA_IO_HI].quirks,
+ quirk, next);
+
+ trace_vfio_vga_probe_nvidia_3d0_quirk(vdev->vbasedev.name);
+}
+
+/*
+ * The second quirk is documented in envytools. The I/O port BAR5 is just
+ * a set of address/data ports to the MMIO BARs. The BAR we care about is
+ * again BAR0. This backdoor is apparently a bit newer than the one above
+ * so we need to not only trap 256 bytes @0x1800, but all of PCI config
+ * space, including extended space is available at the 4k @0x88000.
+ */
+enum {
+ NV_BAR5_ADDRESS = 0x1,
+ NV_BAR5_ENABLE = 0x2,
+ NV_BAR5_MASTER = 0x4,
+ NV_BAR5_VALID = 0x7,
+};
+
+static void vfio_nvidia_bar5_window_quirk_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+
+ switch (addr) {
+ case 0x0:
+ if (data & 0x1) {
+ quirk->data.flags |= NV_BAR5_MASTER;
+ } else {
+ quirk->data.flags &= ~NV_BAR5_MASTER;
+ }
+ break;
+ case 0x4:
+ if (data & 0x1) {
+ quirk->data.flags |= NV_BAR5_ENABLE;
+ } else {
+ quirk->data.flags &= ~NV_BAR5_ENABLE;
+ }
+ break;
+ case 0x8:
+ if (quirk->data.flags & NV_BAR5_MASTER) {
+ if ((data & ~0xfff) == 0x88000) {
+ quirk->data.flags |= NV_BAR5_ADDRESS;
+ quirk->data.address_val = data & 0xfff;
+ } else if ((data & ~0xff) == 0x1800) {
+ quirk->data.flags |= NV_BAR5_ADDRESS;
+ quirk->data.address_val = data & 0xff;
+ } else {
+ quirk->data.flags &= ~NV_BAR5_ADDRESS;
+ }
+ }
+ break;
+ }
+
+ vfio_generic_window_quirk_write(opaque, addr, data, size);
+}
+
+static const MemoryRegionOps vfio_nvidia_bar5_window_quirk = {
+ .read = vfio_generic_window_quirk_read,
+ .write = vfio_nvidia_bar5_window_quirk_write,
+ .valid.min_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void vfio_probe_nvidia_bar5_window_quirk(VFIOPCIDevice *vdev, int nr)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ VFIOQuirk *quirk;
+
+ if (!vdev->has_vga || nr != 5 ||
+ pci_get_word(pdev->config + PCI_VENDOR_ID) != PCI_VENDOR_ID_NVIDIA) {
+ return;
+ }
+
+ quirk = g_malloc0(sizeof(*quirk));
+ quirk->vdev = vdev;
+ quirk->data.read_flags = quirk->data.write_flags = NV_BAR5_VALID;
+ quirk->data.address_offset = 0x8;
+ quirk->data.address_size = 0; /* actually 4, but avoids generic code */
+ quirk->data.data_offset = 0xc;
+ quirk->data.data_size = 4;
+ quirk->data.bar = nr;
+
+ memory_region_init_io(&quirk->mem, OBJECT(vdev),
+ &vfio_nvidia_bar5_window_quirk, quirk,
+ "vfio-nvidia-bar5-window-quirk", 16);
+ memory_region_add_subregion_overlap(&vdev->bars[nr].region.mem,
+ 0, &quirk->mem, 1);
+
+ QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next);
+
+ trace_vfio_probe_nvidia_bar5_window_quirk(vdev->vbasedev.name);
+}
+
+static void vfio_nvidia_88000_quirk_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VFIOQuirk *quirk = opaque;
+ VFIOPCIDevice *vdev = quirk->vdev;
+ PCIDevice *pdev = &vdev->pdev;
+ hwaddr base = quirk->data.address_match & TARGET_PAGE_MASK;
+
+ vfio_generic_quirk_write(opaque, addr, data, size);
+
+ /*
+ * Nvidia seems to acknowledge MSI interrupts by writing 0xff to the
+ * MSI capability ID register. Both the ID and next register are
+ * read-only, so we allow writes covering either of those to real hw.
+ * NB - only fixed for the 0x88000 MMIO window.
+ */
+ if ((pdev->cap_present & QEMU_PCI_CAP_MSI) &&
+ vfio_range_contained(addr, size, pdev->msi_cap, PCI_MSI_FLAGS)) {
+ vfio_region_write(&vdev->bars[quirk->data.bar].region,
+ addr + base, data, size);
+ }
+}
+
+static const MemoryRegionOps vfio_nvidia_88000_quirk = {
+ .read = vfio_generic_quirk_read,
+ .write = vfio_nvidia_88000_quirk_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/*
+ * Finally, BAR0 itself. We want to redirect any accesses to either
+ * 0x1800 or 0x88000 through the PCI config space access functions.
+ *
+ * NB - quirk at a page granularity or else they don't seem to work when
+ * BARs are mmap'd
+ *
+ * Here's offset 0x88000...
+ */
+static void vfio_probe_nvidia_bar0_88000_quirk(VFIOPCIDevice *vdev, int nr)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ VFIOQuirk *quirk;
+ uint16_t vendor, class;
+
+ vendor = pci_get_word(pdev->config + PCI_VENDOR_ID);
+ class = pci_get_word(pdev->config + PCI_CLASS_DEVICE);
+
+ if (nr != 0 || vendor != PCI_VENDOR_ID_NVIDIA ||
+ class != PCI_CLASS_DISPLAY_VGA) {
+ return;
+ }
+
+ quirk = g_malloc0(sizeof(*quirk));
+ quirk->vdev = vdev;
+ quirk->data.flags = quirk->data.read_flags = quirk->data.write_flags = 1;
+ quirk->data.address_match = 0x88000;
+ quirk->data.address_mask = PCIE_CONFIG_SPACE_SIZE - 1;
+ quirk->data.bar = nr;
+
+ memory_region_init_io(&quirk->mem, OBJECT(vdev), &vfio_nvidia_88000_quirk,
+ quirk, "vfio-nvidia-bar0-88000-quirk",
+ TARGET_PAGE_ALIGN(quirk->data.address_mask + 1));
+ memory_region_add_subregion_overlap(&vdev->bars[nr].region.mem,
+ quirk->data.address_match & TARGET_PAGE_MASK,
+ &quirk->mem, 1);
+
+ QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next);
+
+ trace_vfio_probe_nvidia_bar0_88000_quirk(vdev->vbasedev.name);
+}
+
+/*
+ * And here's the same for BAR0 offset 0x1800...
+ */
+static void vfio_probe_nvidia_bar0_1800_quirk(VFIOPCIDevice *vdev, int nr)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ VFIOQuirk *quirk;
+
+ if (!vdev->has_vga || nr != 0 ||
+ pci_get_word(pdev->config + PCI_VENDOR_ID) != PCI_VENDOR_ID_NVIDIA) {
+ return;
+ }
+
+ /* Log the chipset ID */
+ trace_vfio_probe_nvidia_bar0_1800_quirk_id(
+ (unsigned int)(vfio_region_read(&vdev->bars[0].region, 0, 4) >> 20)
+ & 0xff);
+
+ quirk = g_malloc0(sizeof(*quirk));
+ quirk->vdev = vdev;
+ quirk->data.flags = quirk->data.read_flags = quirk->data.write_flags = 1;
+ quirk->data.address_match = 0x1800;
+ quirk->data.address_mask = PCI_CONFIG_SPACE_SIZE - 1;
+ quirk->data.bar = nr;
+
+ memory_region_init_io(&quirk->mem, OBJECT(vdev), &vfio_generic_quirk, quirk,
+ "vfio-nvidia-bar0-1800-quirk",
+ TARGET_PAGE_ALIGN(quirk->data.address_mask + 1));
+ memory_region_add_subregion_overlap(&vdev->bars[nr].region.mem,
+ quirk->data.address_match & TARGET_PAGE_MASK,
+ &quirk->mem, 1);
+
+ QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next);
+
+ trace_vfio_probe_nvidia_bar0_1800_quirk(vdev->vbasedev.name);
+}
+
+/*
+ * TODO - Some Nvidia devices provide config access to their companion HDA
+ * device and even to their parent bridge via these config space mirrors.
+ * Add quirks for those regions.
+ */
+
+/*
+ * Common quirk probe entry points.
+ */
+static void vfio_vga_quirk_setup(VFIOPCIDevice *vdev)
+{
+ vfio_vga_probe_ati_3c3_quirk(vdev);
+ vfio_vga_probe_nvidia_3d0_quirk(vdev);
+}
+
+static void vfio_vga_quirk_teardown(VFIOPCIDevice *vdev)
+{
+ VFIOQuirk *quirk;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(vdev->vga.region); i++) {
+ QLIST_FOREACH(quirk, &vdev->vga.region[i].quirks, next) {
+ memory_region_del_subregion(&vdev->vga.region[i].mem, &quirk->mem);
+ }
+ }
+}
+
+static void vfio_vga_quirk_free(VFIOPCIDevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(vdev->vga.region); i++) {
+ while (!QLIST_EMPTY(&vdev->vga.region[i].quirks)) {
+ VFIOQuirk *quirk = QLIST_FIRST(&vdev->vga.region[i].quirks);
+ object_unparent(OBJECT(&quirk->mem));
+ QLIST_REMOVE(quirk, next);
+ g_free(quirk);
+ }
+ }
+}
+
+static void vfio_bar_quirk_setup(VFIOPCIDevice *vdev, int nr)
+{
+ vfio_probe_ati_bar4_window_quirk(vdev, nr);
+ vfio_probe_ati_bar2_4000_quirk(vdev, nr);
+ vfio_probe_nvidia_bar5_window_quirk(vdev, nr);
+ vfio_probe_nvidia_bar0_88000_quirk(vdev, nr);
+ vfio_probe_nvidia_bar0_1800_quirk(vdev, nr);
+ vfio_probe_rtl8168_bar2_window_quirk(vdev, nr);
+}
+
+static void vfio_bar_quirk_teardown(VFIOPCIDevice *vdev, int nr)
+{
+ VFIOBAR *bar = &vdev->bars[nr];
+ VFIOQuirk *quirk;
+
+ QLIST_FOREACH(quirk, &bar->quirks, next) {
+ memory_region_del_subregion(&bar->region.mem, &quirk->mem);
+ }
+}
+
+static void vfio_bar_quirk_free(VFIOPCIDevice *vdev, int nr)
+{
+ VFIOBAR *bar = &vdev->bars[nr];
+
+ while (!QLIST_EMPTY(&bar->quirks)) {
+ VFIOQuirk *quirk = QLIST_FIRST(&bar->quirks);
+ object_unparent(OBJECT(&quirk->mem));
+ QLIST_REMOVE(quirk, next);
+ g_free(quirk);
+ }
+}
+
+/*
+ * PCI config space
+ */
+static uint32_t vfio_pci_read_config(PCIDevice *pdev, uint32_t addr, int len)
+{
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pdev);
+ uint32_t emu_bits = 0, emu_val = 0, phys_val = 0, val;
+
+ memcpy(&emu_bits, vdev->emulated_config_bits + addr, len);
+ emu_bits = le32_to_cpu(emu_bits);
+
+ if (emu_bits) {
+ emu_val = pci_default_read_config(pdev, addr, len);
+ }
+
+ if (~emu_bits & (0xffffffffU >> (32 - len * 8))) {
+ ssize_t ret;
+
+ ret = pread(vdev->vbasedev.fd, &phys_val, len,
+ vdev->config_offset + addr);
+ if (ret != len) {
+ error_report("%s(%04x:%02x:%02x.%x, 0x%x, 0x%x) failed: %m",
+ __func__, vdev->host.domain, vdev->host.bus,
+ vdev->host.slot, vdev->host.function, addr, len);
+ return -errno;
+ }
+ phys_val = le32_to_cpu(phys_val);
+ }
+
+ val = (emu_val & emu_bits) | (phys_val & ~emu_bits);
+
+ trace_vfio_pci_read_config(vdev->vbasedev.name, addr, len, val);
+
+ return val;
+}
+
+static void vfio_pci_write_config(PCIDevice *pdev, uint32_t addr,
+ uint32_t val, int len)
+{
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pdev);
+ uint32_t val_le = cpu_to_le32(val);
+
+ trace_vfio_pci_write_config(vdev->vbasedev.name, addr, val, len);
+
+ /* Write everything to VFIO, let it filter out what we can't write */
+ if (pwrite(vdev->vbasedev.fd, &val_le, len, vdev->config_offset + addr)
+ != len) {
+ error_report("%s(%04x:%02x:%02x.%x, 0x%x, 0x%x, 0x%x) failed: %m",
+ __func__, vdev->host.domain, vdev->host.bus,
+ vdev->host.slot, vdev->host.function, addr, val, len);
+ }
+
+ /* MSI/MSI-X Enabling/Disabling */
+ if (pdev->cap_present & QEMU_PCI_CAP_MSI &&
+ ranges_overlap(addr, len, pdev->msi_cap, vdev->msi_cap_size)) {
+ int is_enabled, was_enabled = msi_enabled(pdev);
+
+ pci_default_write_config(pdev, addr, val, len);
+
+ is_enabled = msi_enabled(pdev);
+
+ if (!was_enabled) {
+ if (is_enabled) {
+ vfio_enable_msi(vdev);
+ }
+ } else {
+ if (!is_enabled) {
+ vfio_disable_msi(vdev);
+ } else {
+ vfio_update_msi(vdev);
+ }
+ }
+ } else if (pdev->cap_present & QEMU_PCI_CAP_MSIX &&
+ ranges_overlap(addr, len, pdev->msix_cap, MSIX_CAP_LENGTH)) {
+ int is_enabled, was_enabled = msix_enabled(pdev);
+
+ pci_default_write_config(pdev, addr, val, len);
+
+ is_enabled = msix_enabled(pdev);
+
+ if (!was_enabled && is_enabled) {
+ vfio_enable_msix(vdev);
+ } else if (was_enabled && !is_enabled) {
+ vfio_disable_msix(vdev);
+ }
+ } else {
+ /* Write everything to QEMU to keep emulated bits correct */
+ pci_default_write_config(pdev, addr, val, len);
+ }
+}
+
+/*
+ * Interrupt setup
+ */
+static void vfio_disable_interrupts(VFIOPCIDevice *vdev)
+{
+ /*
+ * More complicated than it looks. Disabling MSI/X transitions the
+ * device to INTx mode (if supported). Therefore we need to first
+ * disable MSI/X and then cleanup by disabling INTx.
+ */
+ if (vdev->interrupt == VFIO_INT_MSIX) {
+ vfio_disable_msix(vdev);
+ } else if (vdev->interrupt == VFIO_INT_MSI) {
+ vfio_disable_msi(vdev);
+ }
+
+ if (vdev->interrupt == VFIO_INT_INTx) {
+ vfio_disable_intx(vdev);
+ }
+}
+
+static int vfio_setup_msi(VFIOPCIDevice *vdev, int pos)
+{
+ uint16_t ctrl;
+ bool msi_64bit, msi_maskbit;
+ int ret, entries;
+
+ if (pread(vdev->vbasedev.fd, &ctrl, sizeof(ctrl),
+ vdev->config_offset + pos + PCI_CAP_FLAGS) != sizeof(ctrl)) {
+ return -errno;
+ }
+ ctrl = le16_to_cpu(ctrl);
+
+ msi_64bit = !!(ctrl & PCI_MSI_FLAGS_64BIT);
+ msi_maskbit = !!(ctrl & PCI_MSI_FLAGS_MASKBIT);
+ entries = 1 << ((ctrl & PCI_MSI_FLAGS_QMASK) >> 1);
+
+ trace_vfio_setup_msi(vdev->vbasedev.name, pos);
+
+ ret = msi_init(&vdev->pdev, pos, entries, msi_64bit, msi_maskbit);
+ if (ret < 0) {
+ if (ret == -ENOTSUP) {
+ return 0;
+ }
+ error_report("vfio: msi_init failed");
+ return ret;
+ }
+ vdev->msi_cap_size = 0xa + (msi_maskbit ? 0xa : 0) + (msi_64bit ? 0x4 : 0);
+
+ return 0;
+}
+
+/*
+ * We don't have any control over how pci_add_capability() inserts
+ * capabilities into the chain. In order to setup MSI-X we need a
+ * MemoryRegion for the BAR. In order to setup the BAR and not
+ * attempt to mmap the MSI-X table area, which VFIO won't allow, we
+ * need to first look for where the MSI-X table lives. So we
+ * unfortunately split MSI-X setup across two functions.
+ */
+static int vfio_early_setup_msix(VFIOPCIDevice *vdev)
+{
+ uint8_t pos;
+ uint16_t ctrl;
+ uint32_t table, pba;
+ int fd = vdev->vbasedev.fd;
+
+ pos = pci_find_capability(&vdev->pdev, PCI_CAP_ID_MSIX);
+ if (!pos) {
+ return 0;
+ }
+
+ if (pread(fd, &ctrl, sizeof(ctrl),
+ vdev->config_offset + pos + PCI_CAP_FLAGS) != sizeof(ctrl)) {
+ return -errno;
+ }
+
+ if (pread(fd, &table, sizeof(table),
+ vdev->config_offset + pos + PCI_MSIX_TABLE) != sizeof(table)) {
+ return -errno;
+ }
+
+ if (pread(fd, &pba, sizeof(pba),
+ vdev->config_offset + pos + PCI_MSIX_PBA) != sizeof(pba)) {
+ return -errno;
+ }
+
+ ctrl = le16_to_cpu(ctrl);
+ table = le32_to_cpu(table);
+ pba = le32_to_cpu(pba);
+
+ vdev->msix = g_malloc0(sizeof(*(vdev->msix)));
+ vdev->msix->table_bar = table & PCI_MSIX_FLAGS_BIRMASK;
+ vdev->msix->table_offset = table & ~PCI_MSIX_FLAGS_BIRMASK;
+ vdev->msix->pba_bar = pba & PCI_MSIX_FLAGS_BIRMASK;
+ vdev->msix->pba_offset = pba & ~PCI_MSIX_FLAGS_BIRMASK;
+ vdev->msix->entries = (ctrl & PCI_MSIX_FLAGS_QSIZE) + 1;
+
+ /*
+ * Test the size of the pba_offset variable and catch if it extends outside
+ * of the specified BAR. If it is the case, we need to apply a hardware
+ * specific quirk if the device is known or we have a broken configuration.
+ */
+ if (vdev->msix->pba_offset >=
+ vdev->bars[vdev->msix->pba_bar].region.size) {
+
+ PCIDevice *pdev = &vdev->pdev;
+ uint16_t vendor = pci_get_word(pdev->config + PCI_VENDOR_ID);
+ uint16_t device = pci_get_word(pdev->config + PCI_DEVICE_ID);
+
+ /*
+ * Chelsio T5 Virtual Function devices are encoded as 0x58xx for T5
+ * adapters. The T5 hardware returns an incorrect value of 0x8000 for
+ * the VF PBA offset while the BAR itself is only 8k. The correct value
+ * is 0x1000, so we hard code that here.
+ */
+ if (vendor == PCI_VENDOR_ID_CHELSIO && (device & 0xff00) == 0x5800) {
+ vdev->msix->pba_offset = 0x1000;
+ } else {
+ error_report("vfio: Hardware reports invalid configuration, "
+ "MSIX PBA outside of specified BAR");
+ return -EINVAL;
+ }
+ }
+
+ trace_vfio_early_setup_msix(vdev->vbasedev.name, pos,
+ vdev->msix->table_bar,
+ vdev->msix->table_offset,
+ vdev->msix->entries);
+
+ return 0;
+}
+
+static int vfio_setup_msix(VFIOPCIDevice *vdev, int pos)
+{
+ int ret;
+
+ ret = msix_init(&vdev->pdev, vdev->msix->entries,
+ &vdev->bars[vdev->msix->table_bar].region.mem,
+ vdev->msix->table_bar, vdev->msix->table_offset,
+ &vdev->bars[vdev->msix->pba_bar].region.mem,
+ vdev->msix->pba_bar, vdev->msix->pba_offset, pos);
+ if (ret < 0) {
+ if (ret == -ENOTSUP) {
+ return 0;
+ }
+ error_report("vfio: msix_init failed");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void vfio_teardown_msi(VFIOPCIDevice *vdev)
+{
+ msi_uninit(&vdev->pdev);
+
+ if (vdev->msix) {
+ msix_uninit(&vdev->pdev,
+ &vdev->bars[vdev->msix->table_bar].region.mem,
+ &vdev->bars[vdev->msix->pba_bar].region.mem);
+ }
+}
+
+/*
+ * Resource setup
+ */
+static void vfio_mmap_set_enabled(VFIOPCIDevice *vdev, bool enabled)
+{
+ int i;
+
+ for (i = 0; i < PCI_ROM_SLOT; i++) {
+ VFIOBAR *bar = &vdev->bars[i];
+
+ if (!bar->region.size) {
+ continue;
+ }
+
+ memory_region_set_enabled(&bar->region.mmap_mem, enabled);
+ if (vdev->msix && vdev->msix->table_bar == i) {
+ memory_region_set_enabled(&vdev->msix->mmap_mem, enabled);
+ }
+ }
+}
+
+static void vfio_unregister_bar(VFIOPCIDevice *vdev, int nr)
+{
+ VFIOBAR *bar = &vdev->bars[nr];
+
+ if (!bar->region.size) {
+ return;
+ }
+
+ vfio_bar_quirk_teardown(vdev, nr);
+
+ memory_region_del_subregion(&bar->region.mem, &bar->region.mmap_mem);
+
+ if (vdev->msix && vdev->msix->table_bar == nr) {
+ memory_region_del_subregion(&bar->region.mem, &vdev->msix->mmap_mem);
+ }
+}
+
+static void vfio_unmap_bar(VFIOPCIDevice *vdev, int nr)
+{
+ VFIOBAR *bar = &vdev->bars[nr];
+
+ if (!bar->region.size) {
+ return;
+ }
+
+ vfio_bar_quirk_free(vdev, nr);
+
+ munmap(bar->region.mmap, memory_region_size(&bar->region.mmap_mem));
+
+ if (vdev->msix && vdev->msix->table_bar == nr) {
+ munmap(vdev->msix->mmap, memory_region_size(&vdev->msix->mmap_mem));
+ }
+}
+
+static void vfio_map_bar(VFIOPCIDevice *vdev, int nr)
+{
+ VFIOBAR *bar = &vdev->bars[nr];
+ uint64_t size = bar->region.size;
+ char name[64];
+ uint32_t pci_bar;
+ uint8_t type;
+ int ret;
+
+ /* Skip both unimplemented BARs and the upper half of 64bit BARS. */
+ if (!size) {
+ return;
+ }
+
+ snprintf(name, sizeof(name), "VFIO %04x:%02x:%02x.%x BAR %d",
+ vdev->host.domain, vdev->host.bus, vdev->host.slot,
+ vdev->host.function, nr);
+
+ /* Determine what type of BAR this is for registration */
+ ret = pread(vdev->vbasedev.fd, &pci_bar, sizeof(pci_bar),
+ vdev->config_offset + PCI_BASE_ADDRESS_0 + (4 * nr));
+ if (ret != sizeof(pci_bar)) {
+ error_report("vfio: Failed to read BAR %d (%m)", nr);
+ return;
+ }
+
+ pci_bar = le32_to_cpu(pci_bar);
+ bar->ioport = (pci_bar & PCI_BASE_ADDRESS_SPACE_IO);
+ bar->mem64 = bar->ioport ? 0 : (pci_bar & PCI_BASE_ADDRESS_MEM_TYPE_64);
+ type = pci_bar & (bar->ioport ? ~PCI_BASE_ADDRESS_IO_MASK :
+ ~PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* A "slow" read/write mapping underlies all BARs */
+ memory_region_init_io(&bar->region.mem, OBJECT(vdev), &vfio_region_ops,
+ bar, name, size);
+ pci_register_bar(&vdev->pdev, nr, type, &bar->region.mem);
+
+ /*
+ * We can't mmap areas overlapping the MSIX vector table, so we
+ * potentially insert a direct-mapped subregion before and after it.
+ */
+ if (vdev->msix && vdev->msix->table_bar == nr) {
+ size = vdev->msix->table_offset & qemu_real_host_page_mask;
+ }
+
+ strncat(name, " mmap", sizeof(name) - strlen(name) - 1);
+ if (vfio_mmap_region(OBJECT(vdev), &bar->region, &bar->region.mem,
+ &bar->region.mmap_mem, &bar->region.mmap,
+ size, 0, name)) {
+ error_report("%s unsupported. Performance may be slow", name);
+ }
+
+ if (vdev->msix && vdev->msix->table_bar == nr) {
+ uint64_t start;
+
+ start = REAL_HOST_PAGE_ALIGN((uint64_t)vdev->msix->table_offset +
+ (vdev->msix->entries *
+ PCI_MSIX_ENTRY_SIZE));
+
+ size = start < bar->region.size ? bar->region.size - start : 0;
+ strncat(name, " msix-hi", sizeof(name) - strlen(name) - 1);
+ /* VFIOMSIXInfo contains another MemoryRegion for this mapping */
+ if (vfio_mmap_region(OBJECT(vdev), &bar->region, &bar->region.mem,
+ &vdev->msix->mmap_mem,
+ &vdev->msix->mmap, size, start, name)) {
+ error_report("%s unsupported. Performance may be slow", name);
+ }
+ }
+
+ vfio_bar_quirk_setup(vdev, nr);
+}
+
+static void vfio_map_bars(VFIOPCIDevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < PCI_ROM_SLOT; i++) {
+ vfio_map_bar(vdev, i);
+ }
+
+ if (vdev->has_vga) {
+ memory_region_init_io(&vdev->vga.region[QEMU_PCI_VGA_MEM].mem,
+ OBJECT(vdev), &vfio_vga_ops,
+ &vdev->vga.region[QEMU_PCI_VGA_MEM],
+ "vfio-vga-mmio@0xa0000",
+ QEMU_PCI_VGA_MEM_SIZE);
+ memory_region_init_io(&vdev->vga.region[QEMU_PCI_VGA_IO_LO].mem,
+ OBJECT(vdev), &vfio_vga_ops,
+ &vdev->vga.region[QEMU_PCI_VGA_IO_LO],
+ "vfio-vga-io@0x3b0",
+ QEMU_PCI_VGA_IO_LO_SIZE);
+ memory_region_init_io(&vdev->vga.region[QEMU_PCI_VGA_IO_HI].mem,
+ OBJECT(vdev), &vfio_vga_ops,
+ &vdev->vga.region[QEMU_PCI_VGA_IO_HI],
+ "vfio-vga-io@0x3c0",
+ QEMU_PCI_VGA_IO_HI_SIZE);
+
+ pci_register_vga(&vdev->pdev, &vdev->vga.region[QEMU_PCI_VGA_MEM].mem,
+ &vdev->vga.region[QEMU_PCI_VGA_IO_LO].mem,
+ &vdev->vga.region[QEMU_PCI_VGA_IO_HI].mem);
+ vfio_vga_quirk_setup(vdev);
+ }
+}
+
+static void vfio_unregister_bars(VFIOPCIDevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < PCI_ROM_SLOT; i++) {
+ vfio_unregister_bar(vdev, i);
+ }
+
+ if (vdev->has_vga) {
+ vfio_vga_quirk_teardown(vdev);
+ pci_unregister_vga(&vdev->pdev);
+ }
+}
+
+static void vfio_unmap_bars(VFIOPCIDevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < PCI_ROM_SLOT; i++) {
+ vfio_unmap_bar(vdev, i);
+ }
+
+ if (vdev->has_vga) {
+ vfio_vga_quirk_free(vdev);
+ }
+}
+
+/*
+ * General setup
+ */
+static uint8_t vfio_std_cap_max_size(PCIDevice *pdev, uint8_t pos)
+{
+ uint8_t tmp, next = 0xff;
+
+ for (tmp = pdev->config[PCI_CAPABILITY_LIST]; tmp;
+ tmp = pdev->config[tmp + 1]) {
+ if (tmp > pos && tmp < next) {
+ next = tmp;
+ }
+ }
+
+ return next - pos;
+}
+
+static void vfio_set_word_bits(uint8_t *buf, uint16_t val, uint16_t mask)
+{
+ pci_set_word(buf, (pci_get_word(buf) & ~mask) | val);
+}
+
+static void vfio_add_emulated_word(VFIOPCIDevice *vdev, int pos,
+ uint16_t val, uint16_t mask)
+{
+ vfio_set_word_bits(vdev->pdev.config + pos, val, mask);
+ vfio_set_word_bits(vdev->pdev.wmask + pos, ~mask, mask);
+ vfio_set_word_bits(vdev->emulated_config_bits + pos, mask, mask);
+}
+
+static void vfio_set_long_bits(uint8_t *buf, uint32_t val, uint32_t mask)
+{
+ pci_set_long(buf, (pci_get_long(buf) & ~mask) | val);
+}
+
+static void vfio_add_emulated_long(VFIOPCIDevice *vdev, int pos,
+ uint32_t val, uint32_t mask)
+{
+ vfio_set_long_bits(vdev->pdev.config + pos, val, mask);
+ vfio_set_long_bits(vdev->pdev.wmask + pos, ~mask, mask);
+ vfio_set_long_bits(vdev->emulated_config_bits + pos, mask, mask);
+}
+
+static int vfio_setup_pcie_cap(VFIOPCIDevice *vdev, int pos, uint8_t size)
+{
+ uint16_t flags;
+ uint8_t type;
+
+ flags = pci_get_word(vdev->pdev.config + pos + PCI_CAP_FLAGS);
+ type = (flags & PCI_EXP_FLAGS_TYPE) >> 4;
+
+ if (type != PCI_EXP_TYPE_ENDPOINT &&
+ type != PCI_EXP_TYPE_LEG_END &&
+ type != PCI_EXP_TYPE_RC_END) {
+
+ error_report("vfio: Assignment of PCIe type 0x%x "
+ "devices is not currently supported", type);
+ return -EINVAL;
+ }
+
+ if (!pci_bus_is_express(vdev->pdev.bus)) {
+ /*
+ * Use express capability as-is on PCI bus. It doesn't make much
+ * sense to even expose, but some drivers (ex. tg3) depend on it
+ * and guests don't seem to be particular about it. We'll need
+ * to revist this or force express devices to express buses if we
+ * ever expose an IOMMU to the guest.
+ */
+ } else if (pci_bus_is_root(vdev->pdev.bus)) {
+ /*
+ * On a Root Complex bus Endpoints become Root Complex Integrated
+ * Endpoints, which changes the type and clears the LNK & LNK2 fields.
+ */
+ if (type == PCI_EXP_TYPE_ENDPOINT) {
+ vfio_add_emulated_word(vdev, pos + PCI_CAP_FLAGS,
+ PCI_EXP_TYPE_RC_END << 4,
+ PCI_EXP_FLAGS_TYPE);
+
+ /* Link Capabilities, Status, and Control goes away */
+ if (size > PCI_EXP_LNKCTL) {
+ vfio_add_emulated_long(vdev, pos + PCI_EXP_LNKCAP, 0, ~0);
+ vfio_add_emulated_word(vdev, pos + PCI_EXP_LNKCTL, 0, ~0);
+ vfio_add_emulated_word(vdev, pos + PCI_EXP_LNKSTA, 0, ~0);
+
+#ifndef PCI_EXP_LNKCAP2
+#define PCI_EXP_LNKCAP2 44
+#endif
+#ifndef PCI_EXP_LNKSTA2
+#define PCI_EXP_LNKSTA2 50
+#endif
+ /* Link 2 Capabilities, Status, and Control goes away */
+ if (size > PCI_EXP_LNKCAP2) {
+ vfio_add_emulated_long(vdev, pos + PCI_EXP_LNKCAP2, 0, ~0);
+ vfio_add_emulated_word(vdev, pos + PCI_EXP_LNKCTL2, 0, ~0);
+ vfio_add_emulated_word(vdev, pos + PCI_EXP_LNKSTA2, 0, ~0);
+ }
+ }
+
+ } else if (type == PCI_EXP_TYPE_LEG_END) {
+ /*
+ * Legacy endpoints don't belong on the root complex. Windows
+ * seems to be happier with devices if we skip the capability.
+ */
+ return 0;
+ }
+
+ } else {
+ /*
+ * Convert Root Complex Integrated Endpoints to regular endpoints.
+ * These devices don't support LNK/LNK2 capabilities, so make them up.
+ */
+ if (type == PCI_EXP_TYPE_RC_END) {
+ vfio_add_emulated_word(vdev, pos + PCI_CAP_FLAGS,
+ PCI_EXP_TYPE_ENDPOINT << 4,
+ PCI_EXP_FLAGS_TYPE);
+ vfio_add_emulated_long(vdev, pos + PCI_EXP_LNKCAP,
+ PCI_EXP_LNK_MLW_1 | PCI_EXP_LNK_LS_25, ~0);
+ vfio_add_emulated_word(vdev, pos + PCI_EXP_LNKCTL, 0, ~0);
+ }
+
+ /* Mark the Link Status bits as emulated to allow virtual negotiation */
+ vfio_add_emulated_word(vdev, pos + PCI_EXP_LNKSTA,
+ pci_get_word(vdev->pdev.config + pos +
+ PCI_EXP_LNKSTA),
+ PCI_EXP_LNKCAP_MLW | PCI_EXP_LNKCAP_SLS);
+ }
+
+ pos = pci_add_capability(&vdev->pdev, PCI_CAP_ID_EXP, pos, size);
+ if (pos >= 0) {
+ vdev->pdev.exp.exp_cap = pos;
+ }
+
+ return pos;
+}
+
+static void vfio_check_pcie_flr(VFIOPCIDevice *vdev, uint8_t pos)
+{
+ uint32_t cap = pci_get_long(vdev->pdev.config + pos + PCI_EXP_DEVCAP);
+
+ if (cap & PCI_EXP_DEVCAP_FLR) {
+ trace_vfio_check_pcie_flr(vdev->vbasedev.name);
+ vdev->has_flr = true;
+ }
+}
+
+static void vfio_check_pm_reset(VFIOPCIDevice *vdev, uint8_t pos)
+{
+ uint16_t csr = pci_get_word(vdev->pdev.config + pos + PCI_PM_CTRL);
+
+ if (!(csr & PCI_PM_CTRL_NO_SOFT_RESET)) {
+ trace_vfio_check_pm_reset(vdev->vbasedev.name);
+ vdev->has_pm_reset = true;
+ }
+}
+
+static void vfio_check_af_flr(VFIOPCIDevice *vdev, uint8_t pos)
+{
+ uint8_t cap = pci_get_byte(vdev->pdev.config + pos + PCI_AF_CAP);
+
+ if ((cap & PCI_AF_CAP_TP) && (cap & PCI_AF_CAP_FLR)) {
+ trace_vfio_check_af_flr(vdev->vbasedev.name);
+ vdev->has_flr = true;
+ }
+}
+
+static int vfio_add_std_cap(VFIOPCIDevice *vdev, uint8_t pos)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ uint8_t cap_id, next, size;
+ int ret;
+
+ cap_id = pdev->config[pos];
+ next = pdev->config[pos + 1];
+
+ /*
+ * If it becomes important to configure capabilities to their actual
+ * size, use this as the default when it's something we don't recognize.
+ * Since QEMU doesn't actually handle many of the config accesses,
+ * exact size doesn't seem worthwhile.
+ */
+ size = vfio_std_cap_max_size(pdev, pos);
+
+ /*
+ * pci_add_capability always inserts the new capability at the head
+ * of the chain. Therefore to end up with a chain that matches the
+ * physical device, we insert from the end by making this recursive.
+ * This is also why we pre-caclulate size above as cached config space
+ * will be changed as we unwind the stack.
+ */
+ if (next) {
+ ret = vfio_add_std_cap(vdev, next);
+ if (ret) {
+ return ret;
+ }
+ } else {
+ /* Begin the rebuild, use QEMU emulated list bits */
+ pdev->config[PCI_CAPABILITY_LIST] = 0;
+ vdev->emulated_config_bits[PCI_CAPABILITY_LIST] = 0xff;
+ vdev->emulated_config_bits[PCI_STATUS] |= PCI_STATUS_CAP_LIST;
+ }
+
+ /* Use emulated next pointer to allow dropping caps */
+ pci_set_byte(vdev->emulated_config_bits + pos + 1, 0xff);
+
+ switch (cap_id) {
+ case PCI_CAP_ID_MSI:
+ ret = vfio_setup_msi(vdev, pos);
+ break;
+ case PCI_CAP_ID_EXP:
+ vfio_check_pcie_flr(vdev, pos);
+ ret = vfio_setup_pcie_cap(vdev, pos, size);
+ break;
+ case PCI_CAP_ID_MSIX:
+ ret = vfio_setup_msix(vdev, pos);
+ break;
+ case PCI_CAP_ID_PM:
+ vfio_check_pm_reset(vdev, pos);
+ vdev->pm_cap = pos;
+ ret = pci_add_capability(pdev, cap_id, pos, size);
+ break;
+ case PCI_CAP_ID_AF:
+ vfio_check_af_flr(vdev, pos);
+ ret = pci_add_capability(pdev, cap_id, pos, size);
+ break;
+ default:
+ ret = pci_add_capability(pdev, cap_id, pos, size);
+ break;
+ }
+
+ if (ret < 0) {
+ error_report("vfio: %04x:%02x:%02x.%x Error adding PCI capability "
+ "0x%x[0x%x]@0x%x: %d", vdev->host.domain,
+ vdev->host.bus, vdev->host.slot, vdev->host.function,
+ cap_id, size, pos, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vfio_add_capabilities(VFIOPCIDevice *vdev)
+{
+ PCIDevice *pdev = &vdev->pdev;
+
+ if (!(pdev->config[PCI_STATUS] & PCI_STATUS_CAP_LIST) ||
+ !pdev->config[PCI_CAPABILITY_LIST]) {
+ return 0; /* Nothing to add */
+ }
+
+ return vfio_add_std_cap(vdev, pdev->config[PCI_CAPABILITY_LIST]);
+}
+
+static void vfio_pci_pre_reset(VFIOPCIDevice *vdev)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ uint16_t cmd;
+
+ vfio_disable_interrupts(vdev);
+
+ /* Make sure the device is in D0 */
+ if (vdev->pm_cap) {
+ uint16_t pmcsr;
+ uint8_t state;
+
+ pmcsr = vfio_pci_read_config(pdev, vdev->pm_cap + PCI_PM_CTRL, 2);
+ state = pmcsr & PCI_PM_CTRL_STATE_MASK;
+ if (state) {
+ pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
+ vfio_pci_write_config(pdev, vdev->pm_cap + PCI_PM_CTRL, pmcsr, 2);
+ /* vfio handles the necessary delay here */
+ pmcsr = vfio_pci_read_config(pdev, vdev->pm_cap + PCI_PM_CTRL, 2);
+ state = pmcsr & PCI_PM_CTRL_STATE_MASK;
+ if (state) {
+ error_report("vfio: Unable to power on device, stuck in D%d",
+ state);
+ }
+ }
+ }
+
+ /*
+ * Stop any ongoing DMA by disconecting I/O, MMIO, and bus master.
+ * Also put INTx Disable in known state.
+ */
+ cmd = vfio_pci_read_config(pdev, PCI_COMMAND, 2);
+ cmd &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
+ PCI_COMMAND_INTX_DISABLE);
+ vfio_pci_write_config(pdev, PCI_COMMAND, cmd, 2);
+}
+
+static void vfio_pci_post_reset(VFIOPCIDevice *vdev)
+{
+ vfio_enable_intx(vdev);
+}
+
+static bool vfio_pci_host_match(PCIHostDeviceAddress *host1,
+ PCIHostDeviceAddress *host2)
+{
+ return (host1->domain == host2->domain && host1->bus == host2->bus &&
+ host1->slot == host2->slot && host1->function == host2->function);
+}
+
+static int vfio_pci_hot_reset(VFIOPCIDevice *vdev, bool single)
+{
+ VFIOGroup *group;
+ struct vfio_pci_hot_reset_info *info;
+ struct vfio_pci_dependent_device *devices;
+ struct vfio_pci_hot_reset *reset;
+ int32_t *fds;
+ int ret, i, count;
+ bool multi = false;
+
+ trace_vfio_pci_hot_reset(vdev->vbasedev.name, single ? "one" : "multi");
+
+ vfio_pci_pre_reset(vdev);
+ vdev->vbasedev.needs_reset = false;
+
+ info = g_malloc0(sizeof(*info));
+ info->argsz = sizeof(*info);
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_PCI_HOT_RESET_INFO, info);
+ if (ret && errno != ENOSPC) {
+ ret = -errno;
+ if (!vdev->has_pm_reset) {
+ error_report("vfio: Cannot reset device %04x:%02x:%02x.%x, "
+ "no available reset mechanism.", vdev->host.domain,
+ vdev->host.bus, vdev->host.slot, vdev->host.function);
+ }
+ goto out_single;
+ }
+
+ count = info->count;
+ info = g_realloc(info, sizeof(*info) + (count * sizeof(*devices)));
+ info->argsz = sizeof(*info) + (count * sizeof(*devices));
+ devices = &info->devices[0];
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_PCI_HOT_RESET_INFO, info);
+ if (ret) {
+ ret = -errno;
+ error_report("vfio: hot reset info failed: %m");
+ goto out_single;
+ }
+
+ trace_vfio_pci_hot_reset_has_dep_devices(vdev->vbasedev.name);
+
+ /* Verify that we have all the groups required */
+ for (i = 0; i < info->count; i++) {
+ PCIHostDeviceAddress host;
+ VFIOPCIDevice *tmp;
+ VFIODevice *vbasedev_iter;
+
+ host.domain = devices[i].segment;
+ host.bus = devices[i].bus;
+ host.slot = PCI_SLOT(devices[i].devfn);
+ host.function = PCI_FUNC(devices[i].devfn);
+
+ trace_vfio_pci_hot_reset_dep_devices(host.domain,
+ host.bus, host.slot, host.function, devices[i].group_id);
+
+ if (vfio_pci_host_match(&host, &vdev->host)) {
+ continue;
+ }
+
+ QLIST_FOREACH(group, &vfio_group_list, next) {
+ if (group->groupid == devices[i].group_id) {
+ break;
+ }
+ }
+
+ if (!group) {
+ if (!vdev->has_pm_reset) {
+ error_report("vfio: Cannot reset device %s, "
+ "depends on group %d which is not owned.",
+ vdev->vbasedev.name, devices[i].group_id);
+ }
+ ret = -EPERM;
+ goto out;
+ }
+
+ /* Prep dependent devices for reset and clear our marker. */
+ QLIST_FOREACH(vbasedev_iter, &group->device_list, next) {
+ if (vbasedev_iter->type != VFIO_DEVICE_TYPE_PCI) {
+ continue;
+ }
+ tmp = container_of(vbasedev_iter, VFIOPCIDevice, vbasedev);
+ if (vfio_pci_host_match(&host, &tmp->host)) {
+ if (single) {
+ ret = -EINVAL;
+ goto out_single;
+ }
+ vfio_pci_pre_reset(tmp);
+ tmp->vbasedev.needs_reset = false;
+ multi = true;
+ break;
+ }
+ }
+ }
+
+ if (!single && !multi) {
+ ret = -EINVAL;
+ goto out_single;
+ }
+
+ /* Determine how many group fds need to be passed */
+ count = 0;
+ QLIST_FOREACH(group, &vfio_group_list, next) {
+ for (i = 0; i < info->count; i++) {
+ if (group->groupid == devices[i].group_id) {
+ count++;
+ break;
+ }
+ }
+ }
+
+ reset = g_malloc0(sizeof(*reset) + (count * sizeof(*fds)));
+ reset->argsz = sizeof(*reset) + (count * sizeof(*fds));
+ fds = &reset->group_fds[0];
+
+ /* Fill in group fds */
+ QLIST_FOREACH(group, &vfio_group_list, next) {
+ for (i = 0; i < info->count; i++) {
+ if (group->groupid == devices[i].group_id) {
+ fds[reset->count++] = group->fd;
+ break;
+ }
+ }
+ }
+
+ /* Bus reset! */
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_PCI_HOT_RESET, reset);
+ g_free(reset);
+
+ trace_vfio_pci_hot_reset_result(vdev->vbasedev.name,
+ ret ? "%m" : "Success");
+
+out:
+ /* Re-enable INTx on affected devices */
+ for (i = 0; i < info->count; i++) {
+ PCIHostDeviceAddress host;
+ VFIOPCIDevice *tmp;
+ VFIODevice *vbasedev_iter;
+
+ host.domain = devices[i].segment;
+ host.bus = devices[i].bus;
+ host.slot = PCI_SLOT(devices[i].devfn);
+ host.function = PCI_FUNC(devices[i].devfn);
+
+ if (vfio_pci_host_match(&host, &vdev->host)) {
+ continue;
+ }
+
+ QLIST_FOREACH(group, &vfio_group_list, next) {
+ if (group->groupid == devices[i].group_id) {
+ break;
+ }
+ }
+
+ if (!group) {
+ break;
+ }
+
+ QLIST_FOREACH(vbasedev_iter, &group->device_list, next) {
+ if (vbasedev_iter->type != VFIO_DEVICE_TYPE_PCI) {
+ continue;
+ }
+ tmp = container_of(vbasedev_iter, VFIOPCIDevice, vbasedev);
+ if (vfio_pci_host_match(&host, &tmp->host)) {
+ vfio_pci_post_reset(tmp);
+ break;
+ }
+ }
+ }
+out_single:
+ vfio_pci_post_reset(vdev);
+ g_free(info);
+
+ return ret;
+}
+
+/*
+ * We want to differentiate hot reset of mulitple in-use devices vs hot reset
+ * of a single in-use device. VFIO_DEVICE_RESET will already handle the case
+ * of doing hot resets when there is only a single device per bus. The in-use
+ * here refers to how many VFIODevices are affected. A hot reset that affects
+ * multiple devices, but only a single in-use device, means that we can call
+ * it from our bus ->reset() callback since the extent is effectively a single
+ * device. This allows us to make use of it in the hotplug path. When there
+ * are multiple in-use devices, we can only trigger the hot reset during a
+ * system reset and thus from our reset handler. We separate _one vs _multi
+ * here so that we don't overlap and do a double reset on the system reset
+ * path where both our reset handler and ->reset() callback are used. Calling
+ * _one() will only do a hot reset for the one in-use devices case, calling
+ * _multi() will do nothing if a _one() would have been sufficient.
+ */
+static int vfio_pci_hot_reset_one(VFIOPCIDevice *vdev)
+{
+ return vfio_pci_hot_reset(vdev, true);
+}
+
+static int vfio_pci_hot_reset_multi(VFIODevice *vbasedev)
+{
+ VFIOPCIDevice *vdev = container_of(vbasedev, VFIOPCIDevice, vbasedev);
+ return vfio_pci_hot_reset(vdev, false);
+}
+
+static void vfio_pci_compute_needs_reset(VFIODevice *vbasedev)
+{
+ VFIOPCIDevice *vdev = container_of(vbasedev, VFIOPCIDevice, vbasedev);
+ if (!vbasedev->reset_works || (!vdev->has_flr && vdev->has_pm_reset)) {
+ vbasedev->needs_reset = true;
+ }
+}
+
+static VFIODeviceOps vfio_pci_ops = {
+ .vfio_compute_needs_reset = vfio_pci_compute_needs_reset,
+ .vfio_hot_reset_multi = vfio_pci_hot_reset_multi,
+ .vfio_eoi = vfio_eoi,
+};
+
+static int vfio_populate_device(VFIOPCIDevice *vdev)
+{
+ VFIODevice *vbasedev = &vdev->vbasedev;
+ struct vfio_region_info reg_info = { .argsz = sizeof(reg_info) };
+ struct vfio_irq_info irq_info = { .argsz = sizeof(irq_info) };
+ int i, ret = -1;
+
+ /* Sanity check device */
+ if (!(vbasedev->flags & VFIO_DEVICE_FLAGS_PCI)) {
+ error_report("vfio: Um, this isn't a PCI device");
+ goto error;
+ }
+
+ if (vbasedev->num_regions < VFIO_PCI_CONFIG_REGION_INDEX + 1) {
+ error_report("vfio: unexpected number of io regions %u",
+ vbasedev->num_regions);
+ goto error;
+ }
+
+ if (vbasedev->num_irqs < VFIO_PCI_MSIX_IRQ_INDEX + 1) {
+ error_report("vfio: unexpected number of irqs %u", vbasedev->num_irqs);
+ goto error;
+ }
+
+ for (i = VFIO_PCI_BAR0_REGION_INDEX; i < VFIO_PCI_ROM_REGION_INDEX; i++) {
+ reg_info.index = i;
+
+ ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_REGION_INFO, &reg_info);
+ if (ret) {
+ error_report("vfio: Error getting region %d info: %m", i);
+ goto error;
+ }
+
+ trace_vfio_populate_device_region(vbasedev->name, i,
+ (unsigned long)reg_info.size,
+ (unsigned long)reg_info.offset,
+ (unsigned long)reg_info.flags);
+
+ vdev->bars[i].region.vbasedev = vbasedev;
+ vdev->bars[i].region.flags = reg_info.flags;
+ vdev->bars[i].region.size = reg_info.size;
+ vdev->bars[i].region.fd_offset = reg_info.offset;
+ vdev->bars[i].region.nr = i;
+ QLIST_INIT(&vdev->bars[i].quirks);
+ }
+
+ reg_info.index = VFIO_PCI_CONFIG_REGION_INDEX;
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_REGION_INFO, &reg_info);
+ if (ret) {
+ error_report("vfio: Error getting config info: %m");
+ goto error;
+ }
+
+ trace_vfio_populate_device_config(vdev->vbasedev.name,
+ (unsigned long)reg_info.size,
+ (unsigned long)reg_info.offset,
+ (unsigned long)reg_info.flags);
+
+ vdev->config_size = reg_info.size;
+ if (vdev->config_size == PCI_CONFIG_SPACE_SIZE) {
+ vdev->pdev.cap_present &= ~QEMU_PCI_CAP_EXPRESS;
+ }
+ vdev->config_offset = reg_info.offset;
+
+ if ((vdev->features & VFIO_FEATURE_ENABLE_VGA) &&
+ vbasedev->num_regions > VFIO_PCI_VGA_REGION_INDEX) {
+ struct vfio_region_info vga_info = {
+ .argsz = sizeof(vga_info),
+ .index = VFIO_PCI_VGA_REGION_INDEX,
+ };
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_REGION_INFO, &vga_info);
+ if (ret) {
+ error_report(
+ "vfio: Device does not support requested feature x-vga");
+ goto error;
+ }
+
+ if (!(vga_info.flags & VFIO_REGION_INFO_FLAG_READ) ||
+ !(vga_info.flags & VFIO_REGION_INFO_FLAG_WRITE) ||
+ vga_info.size < 0xbffff + 1) {
+ error_report("vfio: Unexpected VGA info, flags 0x%lx, size 0x%lx",
+ (unsigned long)vga_info.flags,
+ (unsigned long)vga_info.size);
+ goto error;
+ }
+
+ vdev->vga.fd_offset = vga_info.offset;
+ vdev->vga.fd = vdev->vbasedev.fd;
+
+ vdev->vga.region[QEMU_PCI_VGA_MEM].offset = QEMU_PCI_VGA_MEM_BASE;
+ vdev->vga.region[QEMU_PCI_VGA_MEM].nr = QEMU_PCI_VGA_MEM;
+ QLIST_INIT(&vdev->vga.region[QEMU_PCI_VGA_MEM].quirks);
+
+ vdev->vga.region[QEMU_PCI_VGA_IO_LO].offset = QEMU_PCI_VGA_IO_LO_BASE;
+ vdev->vga.region[QEMU_PCI_VGA_IO_LO].nr = QEMU_PCI_VGA_IO_LO;
+ QLIST_INIT(&vdev->vga.region[QEMU_PCI_VGA_IO_LO].quirks);
+
+ vdev->vga.region[QEMU_PCI_VGA_IO_HI].offset = QEMU_PCI_VGA_IO_HI_BASE;
+ vdev->vga.region[QEMU_PCI_VGA_IO_HI].nr = QEMU_PCI_VGA_IO_HI;
+ QLIST_INIT(&vdev->vga.region[QEMU_PCI_VGA_IO_HI].quirks);
+
+ vdev->has_vga = true;
+ }
+
+ irq_info.index = VFIO_PCI_ERR_IRQ_INDEX;
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_IRQ_INFO, &irq_info);
+ if (ret) {
+ /* This can fail for an old kernel or legacy PCI dev */
+ trace_vfio_populate_device_get_irq_info_failure();
+ ret = 0;
+ } else if (irq_info.count == 1) {
+ vdev->pci_aer = true;
+ } else {
+ error_report("vfio: %s "
+ "Could not enable error recovery for the device",
+ vbasedev->name);
+ }
+
+error:
+ return ret;
+}
+
+static void vfio_put_device(VFIOPCIDevice *vdev)
+{
+ g_free(vdev->vbasedev.name);
+ if (vdev->msix) {
+ object_unparent(OBJECT(&vdev->msix->mmap_mem));
+ g_free(vdev->msix);
+ vdev->msix = NULL;
+ }
+ vfio_put_base_device(&vdev->vbasedev);
+}
+
+static void vfio_err_notifier_handler(void *opaque)
+{
+ VFIOPCIDevice *vdev = opaque;
+
+ if (!event_notifier_test_and_clear(&vdev->err_notifier)) {
+ return;
+ }
+
+ /*
+ * TBD. Retrieve the error details and decide what action
+ * needs to be taken. One of the actions could be to pass
+ * the error to the guest and have the guest driver recover
+ * from the error. This requires that PCIe capabilities be
+ * exposed to the guest. For now, we just terminate the
+ * guest to contain the error.
+ */
+
+ error_report("%s(%04x:%02x:%02x.%x) Unrecoverable error detected. "
+ "Please collect any data possible and then kill the guest",
+ __func__, vdev->host.domain, vdev->host.bus,
+ vdev->host.slot, vdev->host.function);
+
+ vm_stop(RUN_STATE_INTERNAL_ERROR);
+}
+
+/*
+ * Registers error notifier for devices supporting error recovery.
+ * If we encounter a failure in this function, we report an error
+ * and continue after disabling error recovery support for the
+ * device.
+ */
+static void vfio_register_err_notifier(VFIOPCIDevice *vdev)
+{
+ int ret;
+ int argsz;
+ struct vfio_irq_set *irq_set;
+ int32_t *pfd;
+
+ if (!vdev->pci_aer) {
+ return;
+ }
+
+ if (event_notifier_init(&vdev->err_notifier, 0)) {
+ error_report("vfio: Unable to init event notifier for error detection");
+ vdev->pci_aer = false;
+ return;
+ }
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
+ VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = VFIO_PCI_ERR_IRQ_INDEX;
+ irq_set->start = 0;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+
+ *pfd = event_notifier_get_fd(&vdev->err_notifier);
+ qemu_set_fd_handler(*pfd, vfio_err_notifier_handler, NULL, vdev);
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
+ if (ret) {
+ error_report("vfio: Failed to set up error notification");
+ qemu_set_fd_handler(*pfd, NULL, NULL, vdev);
+ event_notifier_cleanup(&vdev->err_notifier);
+ vdev->pci_aer = false;
+ }
+ g_free(irq_set);
+}
+
+static void vfio_unregister_err_notifier(VFIOPCIDevice *vdev)
+{
+ int argsz;
+ struct vfio_irq_set *irq_set;
+ int32_t *pfd;
+ int ret;
+
+ if (!vdev->pci_aer) {
+ return;
+ }
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
+ VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = VFIO_PCI_ERR_IRQ_INDEX;
+ irq_set->start = 0;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+ *pfd = -1;
+
+ ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
+ if (ret) {
+ error_report("vfio: Failed to de-assign error fd: %m");
+ }
+ g_free(irq_set);
+ qemu_set_fd_handler(event_notifier_get_fd(&vdev->err_notifier),
+ NULL, NULL, vdev);
+ event_notifier_cleanup(&vdev->err_notifier);
+}
+
+static void vfio_req_notifier_handler(void *opaque)
+{
+ VFIOPCIDevice *vdev = opaque;
+
+ if (!event_notifier_test_and_clear(&vdev->req_notifier)) {
+ return;
+ }
+
+ qdev_unplug(&vdev->pdev.qdev, NULL);
+}
+
+static void vfio_register_req_notifier(VFIOPCIDevice *vdev)
+{
+ struct vfio_irq_info irq_info = { .argsz = sizeof(irq_info),
+ .index = VFIO_PCI_REQ_IRQ_INDEX };
+ int argsz;
+ struct vfio_irq_set *irq_set;
+ int32_t *pfd;
+
+ if (!(vdev->features & VFIO_FEATURE_ENABLE_REQ)) {
+ return;
+ }
+
+ if (ioctl(vdev->vbasedev.fd,
+ VFIO_DEVICE_GET_IRQ_INFO, &irq_info) < 0 || irq_info.count < 1) {
+ return;
+ }
+
+ if (event_notifier_init(&vdev->req_notifier, 0)) {
+ error_report("vfio: Unable to init event notifier for device request");
+ return;
+ }
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
+ VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = VFIO_PCI_REQ_IRQ_INDEX;
+ irq_set->start = 0;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+
+ *pfd = event_notifier_get_fd(&vdev->req_notifier);
+ qemu_set_fd_handler(*pfd, vfio_req_notifier_handler, NULL, vdev);
+
+ if (ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set)) {
+ error_report("vfio: Failed to set up device request notification");
+ qemu_set_fd_handler(*pfd, NULL, NULL, vdev);
+ event_notifier_cleanup(&vdev->req_notifier);
+ } else {
+ vdev->req_enabled = true;
+ }
+
+ g_free(irq_set);
+}
+
+static void vfio_unregister_req_notifier(VFIOPCIDevice *vdev)
+{
+ int argsz;
+ struct vfio_irq_set *irq_set;
+ int32_t *pfd;
+
+ if (!vdev->req_enabled) {
+ return;
+ }
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
+ VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = VFIO_PCI_REQ_IRQ_INDEX;
+ irq_set->start = 0;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+ *pfd = -1;
+
+ if (ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set)) {
+ error_report("vfio: Failed to de-assign device request fd: %m");
+ }
+ g_free(irq_set);
+ qemu_set_fd_handler(event_notifier_get_fd(&vdev->req_notifier),
+ NULL, NULL, vdev);
+ event_notifier_cleanup(&vdev->req_notifier);
+
+ vdev->req_enabled = false;
+}
+
+/*
+ * AMD Radeon PCI config reset, based on Linux:
+ * drivers/gpu/drm/radeon/ci_smc.c:ci_is_smc_running()
+ * drivers/gpu/drm/radeon/radeon_device.c:radeon_pci_config_reset
+ * drivers/gpu/drm/radeon/ci_smc.c:ci_reset_smc()
+ * drivers/gpu/drm/radeon/ci_smc.c:ci_stop_smc_clock()
+ * IDs: include/drm/drm_pciids.h
+ * Registers: http://cgit.freedesktop.org/~agd5f/linux/commit/?id=4e2aa447f6f0
+ *
+ * Bonaire and Hawaii GPUs do not respond to a bus reset. This is a bug in the
+ * hardware that should be fixed on future ASICs. The symptom of this is that
+ * once the accerlated driver loads, Windows guests will bsod on subsequent
+ * attmpts to load the driver, such as after VM reset or shutdown/restart. To
+ * work around this, we do an AMD specific PCI config reset, followed by an SMC
+ * reset. The PCI config reset only works if SMC firmware is running, so we
+ * have a dependency on the state of the device as to whether this reset will
+ * be effective. There are still cases where we won't be able to kick the
+ * device into working, but this greatly improves the usability overall. The
+ * config reset magic is relatively common on AMD GPUs, but the setup and SMC
+ * poking is largely ASIC specific.
+ */
+static bool vfio_radeon_smc_is_running(VFIOPCIDevice *vdev)
+{
+ uint32_t clk, pc_c;
+
+ /*
+ * Registers 200h and 204h are index and data registers for acessing
+ * indirect configuration registers within the device.
+ */
+ vfio_region_write(&vdev->bars[5].region, 0x200, 0x80000004, 4);
+ clk = vfio_region_read(&vdev->bars[5].region, 0x204, 4);
+ vfio_region_write(&vdev->bars[5].region, 0x200, 0x80000370, 4);
+ pc_c = vfio_region_read(&vdev->bars[5].region, 0x204, 4);
+
+ return (!(clk & 1) && (0x20100 <= pc_c));
+}
+
+/*
+ * The scope of a config reset is controlled by a mode bit in the misc register
+ * and a fuse, exposed as a bit in another register. The fuse is the default
+ * (0 = GFX, 1 = whole GPU), the misc bit is a toggle, with the forumula
+ * scope = !(misc ^ fuse), where the resulting scope is defined the same as
+ * the fuse. A truth table therefore tells us that if misc == fuse, we need
+ * to flip the value of the bit in the misc register.
+ */
+static void vfio_radeon_set_gfx_only_reset(VFIOPCIDevice *vdev)
+{
+ uint32_t misc, fuse;
+ bool a, b;
+
+ vfio_region_write(&vdev->bars[5].region, 0x200, 0xc00c0000, 4);
+ fuse = vfio_region_read(&vdev->bars[5].region, 0x204, 4);
+ b = fuse & 64;
+
+ vfio_region_write(&vdev->bars[5].region, 0x200, 0xc0000010, 4);
+ misc = vfio_region_read(&vdev->bars[5].region, 0x204, 4);
+ a = misc & 2;
+
+ if (a == b) {
+ vfio_region_write(&vdev->bars[5].region, 0x204, misc ^ 2, 4);
+ vfio_region_read(&vdev->bars[5].region, 0x204, 4); /* flush */
+ }
+}
+
+static int vfio_radeon_reset(VFIOPCIDevice *vdev)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ int i, ret = 0;
+ uint32_t data;
+
+ /* Defer to a kernel implemented reset */
+ if (vdev->vbasedev.reset_works) {
+ return -ENODEV;
+ }
+
+ /* Enable only memory BAR access */
+ vfio_pci_write_config(pdev, PCI_COMMAND, PCI_COMMAND_MEMORY, 2);
+
+ /* Reset only works if SMC firmware is loaded and running */
+ if (!vfio_radeon_smc_is_running(vdev)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Make sure only the GFX function is reset */
+ vfio_radeon_set_gfx_only_reset(vdev);
+
+ /* AMD PCI config reset */
+ vfio_pci_write_config(pdev, 0x7c, 0x39d5e86b, 4);
+ usleep(100);
+
+ /* Read back the memory size to make sure we're out of reset */
+ for (i = 0; i < 100000; i++) {
+ if (vfio_region_read(&vdev->bars[5].region, 0x5428, 4) != 0xffffffff) {
+ break;
+ }
+ usleep(1);
+ }
+
+ /* Reset SMC */
+ vfio_region_write(&vdev->bars[5].region, 0x200, 0x80000000, 4);
+ data = vfio_region_read(&vdev->bars[5].region, 0x204, 4);
+ data |= 1;
+ vfio_region_write(&vdev->bars[5].region, 0x204, data, 4);
+
+ /* Disable SMC clock */
+ vfio_region_write(&vdev->bars[5].region, 0x200, 0x80000004, 4);
+ data = vfio_region_read(&vdev->bars[5].region, 0x204, 4);
+ data |= 1;
+ vfio_region_write(&vdev->bars[5].region, 0x204, data, 4);
+
+out:
+ /* Restore PCI command register */
+ vfio_pci_write_config(pdev, PCI_COMMAND, 0, 2);
+
+ return ret;
+}
+
+static void vfio_setup_resetfn(VFIOPCIDevice *vdev)
+{
+ PCIDevice *pdev = &vdev->pdev;
+ uint16_t vendor, device;
+
+ vendor = pci_get_word(pdev->config + PCI_VENDOR_ID);
+ device = pci_get_word(pdev->config + PCI_DEVICE_ID);
+
+ switch (vendor) {
+ case 0x1002:
+ switch (device) {
+ /* Bonaire */
+ case 0x6649: /* Bonaire [FirePro W5100] */
+ case 0x6650:
+ case 0x6651:
+ case 0x6658: /* Bonaire XTX [Radeon R7 260X] */
+ case 0x665c: /* Bonaire XT [Radeon HD 7790/8770 / R9 260 OEM] */
+ case 0x665d: /* Bonaire [Radeon R7 200 Series] */
+ /* Hawaii */
+ case 0x67A0: /* Hawaii XT GL [FirePro W9100] */
+ case 0x67A1: /* Hawaii PRO GL [FirePro W8100] */
+ case 0x67A2:
+ case 0x67A8:
+ case 0x67A9:
+ case 0x67AA:
+ case 0x67B0: /* Hawaii XT [Radeon R9 290X] */
+ case 0x67B1: /* Hawaii PRO [Radeon R9 290] */
+ case 0x67B8:
+ case 0x67B9:
+ case 0x67BA:
+ case 0x67BE:
+ vdev->resetfn = vfio_radeon_reset;
+ break;
+ }
+ break;
+ }
+}
+
+static int vfio_initfn(PCIDevice *pdev)
+{
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pdev);
+ VFIODevice *vbasedev_iter;
+ VFIOGroup *group;
+ char path[PATH_MAX], iommu_group_path[PATH_MAX], *group_name;
+ ssize_t len;
+ struct stat st;
+ int groupid;
+ int ret;
+
+ /* Check that the host device exists */
+ snprintf(path, sizeof(path),
+ "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/",
+ vdev->host.domain, vdev->host.bus, vdev->host.slot,
+ vdev->host.function);
+ if (stat(path, &st) < 0) {
+ error_report("vfio: error: no such host device: %s", path);
+ return -errno;
+ }
+
+ vdev->vbasedev.ops = &vfio_pci_ops;
+
+ vdev->vbasedev.type = VFIO_DEVICE_TYPE_PCI;
+ vdev->vbasedev.name = g_strdup_printf("%04x:%02x:%02x.%01x",
+ vdev->host.domain, vdev->host.bus,
+ vdev->host.slot, vdev->host.function);
+
+ strncat(path, "iommu_group", sizeof(path) - strlen(path) - 1);
+
+ len = readlink(path, iommu_group_path, sizeof(path));
+ if (len <= 0 || len >= sizeof(path)) {
+ error_report("vfio: error no iommu_group for device");
+ return len < 0 ? -errno : -ENAMETOOLONG;
+ }
+
+ iommu_group_path[len] = 0;
+ group_name = basename(iommu_group_path);
+
+ if (sscanf(group_name, "%d", &groupid) != 1) {
+ error_report("vfio: error reading %s: %m", path);
+ return -errno;
+ }
+
+ trace_vfio_initfn(vdev->vbasedev.name, groupid);
+
+ group = vfio_get_group(groupid, pci_device_iommu_address_space(pdev));
+ if (!group) {
+ error_report("vfio: failed to get group %d", groupid);
+ return -ENOENT;
+ }
+
+ snprintf(path, sizeof(path), "%04x:%02x:%02x.%01x",
+ vdev->host.domain, vdev->host.bus, vdev->host.slot,
+ vdev->host.function);
+
+ QLIST_FOREACH(vbasedev_iter, &group->device_list, next) {
+ if (strcmp(vbasedev_iter->name, vdev->vbasedev.name) == 0) {
+ error_report("vfio: error: device %s is already attached", path);
+ vfio_put_group(group);
+ return -EBUSY;
+ }
+ }
+
+ ret = vfio_get_device(group, path, &vdev->vbasedev);
+ if (ret) {
+ error_report("vfio: failed to get device %s", path);
+ vfio_put_group(group);
+ return ret;
+ }
+
+ ret = vfio_populate_device(vdev);
+ if (ret) {
+ return ret;
+ }
+
+ /* Get a copy of config space */
+ ret = pread(vdev->vbasedev.fd, vdev->pdev.config,
+ MIN(pci_config_size(&vdev->pdev), vdev->config_size),
+ vdev->config_offset);
+ if (ret < (int)MIN(pci_config_size(&vdev->pdev), vdev->config_size)) {
+ ret = ret < 0 ? -errno : -EFAULT;
+ error_report("vfio: Failed to read device config space");
+ return ret;
+ }
+
+ /* vfio emulates a lot for us, but some bits need extra love */
+ vdev->emulated_config_bits = g_malloc0(vdev->config_size);
+
+ /* QEMU can choose to expose the ROM or not */
+ memset(vdev->emulated_config_bits + PCI_ROM_ADDRESS, 0xff, 4);
+
+ /* QEMU can change multi-function devices to single function, or reverse */
+ vdev->emulated_config_bits[PCI_HEADER_TYPE] =
+ PCI_HEADER_TYPE_MULTI_FUNCTION;
+
+ /* Restore or clear multifunction, this is always controlled by QEMU */
+ if (vdev->pdev.cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
+ vdev->pdev.config[PCI_HEADER_TYPE] |= PCI_HEADER_TYPE_MULTI_FUNCTION;
+ } else {
+ vdev->pdev.config[PCI_HEADER_TYPE] &= ~PCI_HEADER_TYPE_MULTI_FUNCTION;
+ }
+
+ /*
+ * Clear host resource mapping info. If we choose not to register a
+ * BAR, such as might be the case with the option ROM, we can get
+ * confusing, unwritable, residual addresses from the host here.
+ */
+ memset(&vdev->pdev.config[PCI_BASE_ADDRESS_0], 0, 24);
+ memset(&vdev->pdev.config[PCI_ROM_ADDRESS], 0, 4);
+
+ vfio_pci_size_rom(vdev);
+
+ ret = vfio_early_setup_msix(vdev);
+ if (ret) {
+ return ret;
+ }
+
+ vfio_map_bars(vdev);
+
+ ret = vfio_add_capabilities(vdev);
+ if (ret) {
+ goto out_teardown;
+ }
+
+ /* QEMU emulates all of MSI & MSIX */
+ if (pdev->cap_present & QEMU_PCI_CAP_MSIX) {
+ memset(vdev->emulated_config_bits + pdev->msix_cap, 0xff,
+ MSIX_CAP_LENGTH);
+ }
+
+ if (pdev->cap_present & QEMU_PCI_CAP_MSI) {
+ memset(vdev->emulated_config_bits + pdev->msi_cap, 0xff,
+ vdev->msi_cap_size);
+ }
+
+ if (vfio_pci_read_config(&vdev->pdev, PCI_INTERRUPT_PIN, 1)) {
+ vdev->intx.mmap_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ vfio_intx_mmap_enable, vdev);
+ pci_device_set_intx_routing_notifier(&vdev->pdev, vfio_update_irq);
+ ret = vfio_enable_intx(vdev);
+ if (ret) {
+ goto out_teardown;
+ }
+ }
+
+ vfio_register_err_notifier(vdev);
+ vfio_register_req_notifier(vdev);
+ vfio_setup_resetfn(vdev);
+
+ return 0;
+
+out_teardown:
+ pci_device_set_intx_routing_notifier(&vdev->pdev, NULL);
+ vfio_teardown_msi(vdev);
+ vfio_unregister_bars(vdev);
+ return ret;
+}
+
+static void vfio_instance_finalize(Object *obj)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(obj);
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pci_dev);
+ VFIOGroup *group = vdev->vbasedev.group;
+
+ vfio_unmap_bars(vdev);
+ g_free(vdev->emulated_config_bits);
+ g_free(vdev->rom);
+ vfio_put_device(vdev);
+ vfio_put_group(group);
+}
+
+static void vfio_exitfn(PCIDevice *pdev)
+{
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pdev);
+
+ vfio_unregister_req_notifier(vdev);
+ vfio_unregister_err_notifier(vdev);
+ pci_device_set_intx_routing_notifier(&vdev->pdev, NULL);
+ vfio_disable_interrupts(vdev);
+ if (vdev->intx.mmap_timer) {
+ timer_free(vdev->intx.mmap_timer);
+ }
+ vfio_teardown_msi(vdev);
+ vfio_unregister_bars(vdev);
+}
+
+static void vfio_pci_reset(DeviceState *dev)
+{
+ PCIDevice *pdev = DO_UPCAST(PCIDevice, qdev, dev);
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, pdev);
+
+ trace_vfio_pci_reset(vdev->vbasedev.name);
+
+ vfio_pci_pre_reset(vdev);
+
+ if (vdev->resetfn && !vdev->resetfn(vdev)) {
+ goto post_reset;
+ }
+
+ if (vdev->vbasedev.reset_works &&
+ (vdev->has_flr || !vdev->has_pm_reset) &&
+ !ioctl(vdev->vbasedev.fd, VFIO_DEVICE_RESET)) {
+ trace_vfio_pci_reset_flr(vdev->vbasedev.name);
+ goto post_reset;
+ }
+
+ /* See if we can do our own bus reset */
+ if (!vfio_pci_hot_reset_one(vdev)) {
+ goto post_reset;
+ }
+
+ /* If nothing else works and the device supports PM reset, use it */
+ if (vdev->vbasedev.reset_works && vdev->has_pm_reset &&
+ !ioctl(vdev->vbasedev.fd, VFIO_DEVICE_RESET)) {
+ trace_vfio_pci_reset_pm(vdev->vbasedev.name);
+ goto post_reset;
+ }
+
+post_reset:
+ vfio_pci_post_reset(vdev);
+}
+
+static void vfio_instance_init(Object *obj)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(obj);
+ VFIOPCIDevice *vdev = DO_UPCAST(VFIOPCIDevice, pdev, PCI_DEVICE(obj));
+
+ device_add_bootindex_property(obj, &vdev->bootindex,
+ "bootindex", NULL,
+ &pci_dev->qdev, NULL);
+}
+
+static Property vfio_pci_dev_properties[] = {
+ DEFINE_PROP_PCI_HOST_DEVADDR("host", VFIOPCIDevice, host),
+ DEFINE_PROP_UINT32("x-intx-mmap-timeout-ms", VFIOPCIDevice,
+ intx.mmap_timeout, 1100),
+ DEFINE_PROP_BIT("x-vga", VFIOPCIDevice, features,
+ VFIO_FEATURE_ENABLE_VGA_BIT, false),
+ DEFINE_PROP_BIT("x-req", VFIOPCIDevice, features,
+ VFIO_FEATURE_ENABLE_REQ_BIT, true),
+ DEFINE_PROP_BOOL("x-mmap", VFIOPCIDevice, vbasedev.allow_mmap, true),
+ /*
+ * TODO - support passed fds... is this necessary?
+ * DEFINE_PROP_STRING("vfiofd", VFIOPCIDevice, vfiofd_name),
+ * DEFINE_PROP_STRING("vfiogroupfd, VFIOPCIDevice, vfiogroupfd_name),
+ */
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vfio_pci_vmstate = {
+ .name = "vfio-pci",
+ .unmigratable = 1,
+};
+
+static void vfio_pci_dev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pdc = PCI_DEVICE_CLASS(klass);
+
+ dc->reset = vfio_pci_reset;
+ dc->props = vfio_pci_dev_properties;
+ dc->vmsd = &vfio_pci_vmstate;
+ dc->desc = "VFIO-based PCI device assignment";
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ pdc->init = vfio_initfn;
+ pdc->exit = vfio_exitfn;
+ pdc->config_read = vfio_pci_read_config;
+ pdc->config_write = vfio_pci_write_config;
+ pdc->is_express = 1; /* We might be */
+}
+
+static const TypeInfo vfio_pci_dev_info = {
+ .name = "vfio-pci",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(VFIOPCIDevice),
+ .class_init = vfio_pci_dev_class_init,
+ .instance_init = vfio_instance_init,
+ .instance_finalize = vfio_instance_finalize,
+};
+
+static void register_vfio_pci_dev_type(void)
+{
+ type_register_static(&vfio_pci_dev_info);
+}
+
+type_init(register_vfio_pci_dev_type)
diff --git a/hw/vfio/platform.c b/hw/vfio/platform.c
new file mode 100644
index 00000000..60365d12
--- /dev/null
+++ b/hw/vfio/platform.c
@@ -0,0 +1,715 @@
+/*
+ * vfio based device assignment support - platform devices
+ *
+ * Copyright Linaro Limited, 2014
+ *
+ * Authors:
+ * Kim Phillips <kim.phillips@linaro.org>
+ * Eric Auger <eric.auger@linaro.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Based on vfio based PCI device assignment support:
+ * Copyright Red Hat, Inc. 2012
+ */
+
+#include <sys/ioctl.h>
+#include <linux/vfio.h>
+
+#include "hw/vfio/vfio-platform.h"
+#include "qemu/error-report.h"
+#include "qemu/range.h"
+#include "sysemu/sysemu.h"
+#include "exec/memory.h"
+#include "qemu/queue.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "hw/platform-bus.h"
+#include "sysemu/kvm.h"
+
+/*
+ * Functions used whatever the injection method
+ */
+
+/**
+ * vfio_init_intp - allocate, initialize the IRQ struct pointer
+ * and add it into the list of IRQs
+ * @vbasedev: the VFIO device handle
+ * @info: irq info struct retrieved from VFIO driver
+ */
+static VFIOINTp *vfio_init_intp(VFIODevice *vbasedev,
+ struct vfio_irq_info info)
+{
+ int ret;
+ VFIOPlatformDevice *vdev =
+ container_of(vbasedev, VFIOPlatformDevice, vbasedev);
+ SysBusDevice *sbdev = SYS_BUS_DEVICE(vdev);
+ VFIOINTp *intp;
+
+ intp = g_malloc0(sizeof(*intp));
+ intp->vdev = vdev;
+ intp->pin = info.index;
+ intp->flags = info.flags;
+ intp->state = VFIO_IRQ_INACTIVE;
+ intp->kvm_accel = false;
+
+ sysbus_init_irq(sbdev, &intp->qemuirq);
+
+ /* Get an eventfd for trigger */
+ ret = event_notifier_init(&intp->interrupt, 0);
+ if (ret) {
+ g_free(intp);
+ error_report("vfio: Error: trigger event_notifier_init failed ");
+ return NULL;
+ }
+ /* Get an eventfd for resample/unmask */
+ ret = event_notifier_init(&intp->unmask, 0);
+ if (ret) {
+ g_free(intp);
+ error_report("vfio: Error: resamplefd event_notifier_init failed");
+ return NULL;
+ }
+
+ QLIST_INSERT_HEAD(&vdev->intp_list, intp, next);
+ return intp;
+}
+
+/**
+ * vfio_set_trigger_eventfd - set VFIO eventfd handling
+ *
+ * @intp: IRQ struct handle
+ * @handler: handler to be called on eventfd signaling
+ *
+ * Setup VFIO signaling and attach an optional user-side handler
+ * to the eventfd
+ */
+static int vfio_set_trigger_eventfd(VFIOINTp *intp,
+ eventfd_user_side_handler_t handler)
+{
+ VFIODevice *vbasedev = &intp->vdev->vbasedev;
+ struct vfio_irq_set *irq_set;
+ int argsz, ret;
+ int32_t *pfd;
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
+ irq_set->index = intp->pin;
+ irq_set->start = 0;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+ *pfd = event_notifier_get_fd(&intp->interrupt);
+ qemu_set_fd_handler(*pfd, (IOHandler *)handler, NULL, intp);
+ ret = ioctl(vbasedev->fd, VFIO_DEVICE_SET_IRQS, irq_set);
+ g_free(irq_set);
+ if (ret < 0) {
+ error_report("vfio: Failed to set trigger eventfd: %m");
+ qemu_set_fd_handler(*pfd, NULL, NULL, NULL);
+ }
+ return ret;
+}
+
+/*
+ * Functions only used when eventfds are handled on user-side
+ * ie. without irqfd
+ */
+
+/**
+ * vfio_mmap_set_enabled - enable/disable the fast path mode
+ * @vdev: the VFIO platform device
+ * @enabled: the target mmap state
+ *
+ * enabled = true ~ fast path = MMIO region is mmaped (no KVM TRAP);
+ * enabled = false ~ slow path = MMIO region is trapped and region callbacks
+ * are called; slow path enables to trap the device IRQ status register reset
+*/
+
+static void vfio_mmap_set_enabled(VFIOPlatformDevice *vdev, bool enabled)
+{
+ int i;
+
+ trace_vfio_platform_mmap_set_enabled(enabled);
+
+ for (i = 0; i < vdev->vbasedev.num_regions; i++) {
+ VFIORegion *region = vdev->regions[i];
+
+ memory_region_set_enabled(&region->mmap_mem, enabled);
+ }
+}
+
+/**
+ * vfio_intp_mmap_enable - timer function, restores the fast path
+ * if there is no more active IRQ
+ * @opaque: actually points to the VFIO platform device
+ *
+ * Called on mmap timer timout, this function checks whether the
+ * IRQ is still active and if not, restores the fast path.
+ * by construction a single eventfd is handled at a time.
+ * if the IRQ is still active, the timer is re-programmed.
+ */
+static void vfio_intp_mmap_enable(void *opaque)
+{
+ VFIOINTp *tmp;
+ VFIOPlatformDevice *vdev = (VFIOPlatformDevice *)opaque;
+
+ qemu_mutex_lock(&vdev->intp_mutex);
+ QLIST_FOREACH(tmp, &vdev->intp_list, next) {
+ if (tmp->state == VFIO_IRQ_ACTIVE) {
+ trace_vfio_platform_intp_mmap_enable(tmp->pin);
+ /* re-program the timer to check active status later */
+ timer_mod(vdev->mmap_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+ vdev->mmap_timeout);
+ qemu_mutex_unlock(&vdev->intp_mutex);
+ return;
+ }
+ }
+ vfio_mmap_set_enabled(vdev, true);
+ qemu_mutex_unlock(&vdev->intp_mutex);
+}
+
+/**
+ * vfio_intp_inject_pending_lockheld - Injects a pending IRQ
+ * @opaque: opaque pointer, in practice the VFIOINTp handle
+ *
+ * The function is called on a previous IRQ completion, from
+ * vfio_platform_eoi, while the intp_mutex is locked.
+ * Also in such situation, the slow path already is set and
+ * the mmap timer was already programmed.
+ */
+static void vfio_intp_inject_pending_lockheld(VFIOINTp *intp)
+{
+ trace_vfio_platform_intp_inject_pending_lockheld(intp->pin,
+ event_notifier_get_fd(&intp->interrupt));
+
+ intp->state = VFIO_IRQ_ACTIVE;
+
+ /* trigger the virtual IRQ */
+ qemu_set_irq(intp->qemuirq, 1);
+}
+
+/**
+ * vfio_intp_interrupt - The user-side eventfd handler
+ * @opaque: opaque pointer which in practice is the VFIOINTp handle
+ *
+ * the function is entered in event handler context:
+ * the vIRQ is injected into the guest if there is no other active
+ * or pending IRQ.
+ */
+static void vfio_intp_interrupt(VFIOINTp *intp)
+{
+ int ret;
+ VFIOINTp *tmp;
+ VFIOPlatformDevice *vdev = intp->vdev;
+ bool delay_handling = false;
+
+ qemu_mutex_lock(&vdev->intp_mutex);
+ if (intp->state == VFIO_IRQ_INACTIVE) {
+ QLIST_FOREACH(tmp, &vdev->intp_list, next) {
+ if (tmp->state == VFIO_IRQ_ACTIVE ||
+ tmp->state == VFIO_IRQ_PENDING) {
+ delay_handling = true;
+ break;
+ }
+ }
+ }
+ if (delay_handling) {
+ /*
+ * the new IRQ gets a pending status and is pushed in
+ * the pending queue
+ */
+ intp->state = VFIO_IRQ_PENDING;
+ trace_vfio_intp_interrupt_set_pending(intp->pin);
+ QSIMPLEQ_INSERT_TAIL(&vdev->pending_intp_queue,
+ intp, pqnext);
+ ret = event_notifier_test_and_clear(&intp->interrupt);
+ qemu_mutex_unlock(&vdev->intp_mutex);
+ return;
+ }
+
+ trace_vfio_platform_intp_interrupt(intp->pin,
+ event_notifier_get_fd(&intp->interrupt));
+
+ ret = event_notifier_test_and_clear(&intp->interrupt);
+ if (!ret) {
+ error_report("Error when clearing fd=%d (ret = %d)\n",
+ event_notifier_get_fd(&intp->interrupt), ret);
+ }
+
+ intp->state = VFIO_IRQ_ACTIVE;
+
+ /* sets slow path */
+ vfio_mmap_set_enabled(vdev, false);
+
+ /* trigger the virtual IRQ */
+ qemu_set_irq(intp->qemuirq, 1);
+
+ /*
+ * Schedule the mmap timer which will restore fastpath when no IRQ
+ * is active anymore
+ */
+ if (vdev->mmap_timeout) {
+ timer_mod(vdev->mmap_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+ vdev->mmap_timeout);
+ }
+ qemu_mutex_unlock(&vdev->intp_mutex);
+}
+
+/**
+ * vfio_platform_eoi - IRQ completion routine
+ * @vbasedev: the VFIO device handle
+ *
+ * De-asserts the active virtual IRQ and unmasks the physical IRQ
+ * (effective for level sensitive IRQ auto-masked by the VFIO driver).
+ * Then it handles next pending IRQ if any.
+ * eoi function is called on the first access to any MMIO region
+ * after an IRQ was triggered, trapped since slow path was set.
+ * It is assumed this access corresponds to the IRQ status
+ * register reset. With such a mechanism, a single IRQ can be
+ * handled at a time since there is no way to know which IRQ
+ * was completed by the guest (we would need additional details
+ * about the IRQ status register mask).
+ */
+static void vfio_platform_eoi(VFIODevice *vbasedev)
+{
+ VFIOINTp *intp;
+ VFIOPlatformDevice *vdev =
+ container_of(vbasedev, VFIOPlatformDevice, vbasedev);
+
+ qemu_mutex_lock(&vdev->intp_mutex);
+ QLIST_FOREACH(intp, &vdev->intp_list, next) {
+ if (intp->state == VFIO_IRQ_ACTIVE) {
+ trace_vfio_platform_eoi(intp->pin,
+ event_notifier_get_fd(&intp->interrupt));
+ intp->state = VFIO_IRQ_INACTIVE;
+
+ /* deassert the virtual IRQ */
+ qemu_set_irq(intp->qemuirq, 0);
+
+ if (intp->flags & VFIO_IRQ_INFO_AUTOMASKED) {
+ /* unmasks the physical level-sensitive IRQ */
+ vfio_unmask_single_irqindex(vbasedev, intp->pin);
+ }
+
+ /* a single IRQ can be active at a time */
+ break;
+ }
+ }
+ /* in case there are pending IRQs, handle the first one */
+ if (!QSIMPLEQ_EMPTY(&vdev->pending_intp_queue)) {
+ intp = QSIMPLEQ_FIRST(&vdev->pending_intp_queue);
+ vfio_intp_inject_pending_lockheld(intp);
+ QSIMPLEQ_REMOVE_HEAD(&vdev->pending_intp_queue, pqnext);
+ }
+ qemu_mutex_unlock(&vdev->intp_mutex);
+}
+
+/**
+ * vfio_start_eventfd_injection - starts the virtual IRQ injection using
+ * user-side handled eventfds
+ * @intp: the IRQ struct pointer
+ */
+
+static int vfio_start_eventfd_injection(VFIOINTp *intp)
+{
+ int ret;
+
+ ret = vfio_set_trigger_eventfd(intp, vfio_intp_interrupt);
+ if (ret) {
+ error_report("vfio: Error: Failed to pass IRQ fd to the driver: %m");
+ }
+ return ret;
+}
+
+/*
+ * Functions used for irqfd
+ */
+
+/**
+ * vfio_set_resample_eventfd - sets the resamplefd for an IRQ
+ * @intp: the IRQ struct handle
+ * programs the VFIO driver to unmask this IRQ when the
+ * intp->unmask eventfd is triggered
+ */
+static int vfio_set_resample_eventfd(VFIOINTp *intp)
+{
+ VFIODevice *vbasedev = &intp->vdev->vbasedev;
+ struct vfio_irq_set *irq_set;
+ int argsz, ret;
+ int32_t *pfd;
+
+ argsz = sizeof(*irq_set) + sizeof(*pfd);
+ irq_set = g_malloc0(argsz);
+ irq_set->argsz = argsz;
+ irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_UNMASK;
+ irq_set->index = intp->pin;
+ irq_set->start = 0;
+ irq_set->count = 1;
+ pfd = (int32_t *)&irq_set->data;
+ *pfd = event_notifier_get_fd(&intp->unmask);
+ qemu_set_fd_handler(*pfd, NULL, NULL, NULL);
+ ret = ioctl(vbasedev->fd, VFIO_DEVICE_SET_IRQS, irq_set);
+ g_free(irq_set);
+ if (ret < 0) {
+ error_report("vfio: Failed to set resample eventfd: %m");
+ }
+ return ret;
+}
+
+static void vfio_start_irqfd_injection(SysBusDevice *sbdev, qemu_irq irq)
+{
+ VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
+ VFIOINTp *intp;
+
+ if (!kvm_irqfds_enabled() || !kvm_resamplefds_enabled() ||
+ !vdev->irqfd_allowed) {
+ return;
+ }
+
+ QLIST_FOREACH(intp, &vdev->intp_list, next) {
+ if (intp->qemuirq == irq) {
+ break;
+ }
+ }
+ assert(intp);
+
+ /* Get to a known interrupt state */
+ qemu_set_fd_handler(event_notifier_get_fd(&intp->interrupt),
+ NULL, NULL, vdev);
+
+ vfio_mask_single_irqindex(&vdev->vbasedev, intp->pin);
+ qemu_set_irq(intp->qemuirq, 0);
+
+ if (kvm_irqchip_add_irqfd_notifier(kvm_state, &intp->interrupt,
+ &intp->unmask, irq) < 0) {
+ goto fail_irqfd;
+ }
+
+ if (vfio_set_trigger_eventfd(intp, NULL) < 0) {
+ goto fail_vfio;
+ }
+ if (vfio_set_resample_eventfd(intp) < 0) {
+ goto fail_vfio;
+ }
+
+ /* Let's resume injection with irqfd setup */
+ vfio_unmask_single_irqindex(&vdev->vbasedev, intp->pin);
+
+ intp->kvm_accel = true;
+
+ trace_vfio_platform_start_irqfd_injection(intp->pin,
+ event_notifier_get_fd(&intp->interrupt),
+ event_notifier_get_fd(&intp->unmask));
+ return;
+fail_vfio:
+ kvm_irqchip_remove_irqfd_notifier(kvm_state, &intp->interrupt, irq);
+fail_irqfd:
+ vfio_start_eventfd_injection(intp);
+ vfio_unmask_single_irqindex(&vdev->vbasedev, intp->pin);
+ return;
+}
+
+/* VFIO skeleton */
+
+static void vfio_platform_compute_needs_reset(VFIODevice *vbasedev)
+{
+ vbasedev->needs_reset = true;
+}
+
+/* not implemented yet */
+static int vfio_platform_hot_reset_multi(VFIODevice *vbasedev)
+{
+ return -1;
+}
+
+/**
+ * vfio_populate_device - Allocate and populate MMIO region
+ * and IRQ structs according to driver returned information
+ * @vbasedev: the VFIO device handle
+ *
+ */
+static int vfio_populate_device(VFIODevice *vbasedev)
+{
+ VFIOINTp *intp, *tmp;
+ int i, ret = -1;
+ VFIOPlatformDevice *vdev =
+ container_of(vbasedev, VFIOPlatformDevice, vbasedev);
+
+ if (!(vbasedev->flags & VFIO_DEVICE_FLAGS_PLATFORM)) {
+ error_report("vfio: Um, this isn't a platform device");
+ return ret;
+ }
+
+ vdev->regions = g_new0(VFIORegion *, vbasedev->num_regions);
+
+ for (i = 0; i < vbasedev->num_regions; i++) {
+ struct vfio_region_info reg_info = { .argsz = sizeof(reg_info) };
+ VFIORegion *ptr;
+
+ vdev->regions[i] = g_malloc0(sizeof(VFIORegion));
+ ptr = vdev->regions[i];
+ reg_info.index = i;
+ ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_REGION_INFO, &reg_info);
+ if (ret) {
+ error_report("vfio: Error getting region %d info: %m", i);
+ goto reg_error;
+ }
+ ptr->flags = reg_info.flags;
+ ptr->size = reg_info.size;
+ ptr->fd_offset = reg_info.offset;
+ ptr->nr = i;
+ ptr->vbasedev = vbasedev;
+
+ trace_vfio_platform_populate_regions(ptr->nr,
+ (unsigned long)ptr->flags,
+ (unsigned long)ptr->size,
+ ptr->vbasedev->fd,
+ (unsigned long)ptr->fd_offset);
+ }
+
+ vdev->mmap_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ vfio_intp_mmap_enable, vdev);
+
+ QSIMPLEQ_INIT(&vdev->pending_intp_queue);
+
+ for (i = 0; i < vbasedev->num_irqs; i++) {
+ struct vfio_irq_info irq = { .argsz = sizeof(irq) };
+
+ irq.index = i;
+ ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_IRQ_INFO, &irq);
+ if (ret) {
+ error_printf("vfio: error getting device %s irq info",
+ vbasedev->name);
+ goto irq_err;
+ } else {
+ trace_vfio_platform_populate_interrupts(irq.index,
+ irq.count,
+ irq.flags);
+ intp = vfio_init_intp(vbasedev, irq);
+ if (!intp) {
+ error_report("vfio: Error installing IRQ %d up", i);
+ goto irq_err;
+ }
+ }
+ }
+ return 0;
+irq_err:
+ timer_del(vdev->mmap_timer);
+ QLIST_FOREACH_SAFE(intp, &vdev->intp_list, next, tmp) {
+ QLIST_REMOVE(intp, next);
+ g_free(intp);
+ }
+reg_error:
+ for (i = 0; i < vbasedev->num_regions; i++) {
+ g_free(vdev->regions[i]);
+ }
+ g_free(vdev->regions);
+ return ret;
+}
+
+/* specialized functions for VFIO Platform devices */
+static VFIODeviceOps vfio_platform_ops = {
+ .vfio_compute_needs_reset = vfio_platform_compute_needs_reset,
+ .vfio_hot_reset_multi = vfio_platform_hot_reset_multi,
+ .vfio_eoi = vfio_platform_eoi,
+};
+
+/**
+ * vfio_base_device_init - perform preliminary VFIO setup
+ * @vbasedev: the VFIO device handle
+ *
+ * Implement the VFIO command sequence that allows to discover
+ * assigned device resources: group extraction, device
+ * fd retrieval, resource query.
+ * Precondition: the device name must be initialized
+ */
+static int vfio_base_device_init(VFIODevice *vbasedev)
+{
+ VFIOGroup *group;
+ VFIODevice *vbasedev_iter;
+ char path[PATH_MAX], iommu_group_path[PATH_MAX], *group_name;
+ ssize_t len;
+ struct stat st;
+ int groupid;
+ int ret;
+
+ /* name must be set prior to the call */
+ if (!vbasedev->name || strchr(vbasedev->name, '/')) {
+ return -EINVAL;
+ }
+
+ /* Check that the host device exists */
+ g_snprintf(path, sizeof(path), "/sys/bus/platform/devices/%s/",
+ vbasedev->name);
+
+ if (stat(path, &st) < 0) {
+ error_report("vfio: error: no such host device: %s", path);
+ return -errno;
+ }
+
+ g_strlcat(path, "iommu_group", sizeof(path));
+ len = readlink(path, iommu_group_path, sizeof(iommu_group_path));
+ if (len < 0 || len >= sizeof(iommu_group_path)) {
+ error_report("vfio: error no iommu_group for device");
+ return len < 0 ? -errno : -ENAMETOOLONG;
+ }
+
+ iommu_group_path[len] = 0;
+ group_name = basename(iommu_group_path);
+
+ if (sscanf(group_name, "%d", &groupid) != 1) {
+ error_report("vfio: error reading %s: %m", path);
+ return -errno;
+ }
+
+ trace_vfio_platform_base_device_init(vbasedev->name, groupid);
+
+ group = vfio_get_group(groupid, &address_space_memory);
+ if (!group) {
+ error_report("vfio: failed to get group %d", groupid);
+ return -ENOENT;
+ }
+
+ g_snprintf(path, sizeof(path), "%s", vbasedev->name);
+
+ QLIST_FOREACH(vbasedev_iter, &group->device_list, next) {
+ if (strcmp(vbasedev_iter->name, vbasedev->name) == 0) {
+ error_report("vfio: error: device %s is already attached", path);
+ vfio_put_group(group);
+ return -EBUSY;
+ }
+ }
+ ret = vfio_get_device(group, path, vbasedev);
+ if (ret) {
+ error_report("vfio: failed to get device %s", path);
+ vfio_put_group(group);
+ return ret;
+ }
+
+ ret = vfio_populate_device(vbasedev);
+ if (ret) {
+ error_report("vfio: failed to populate device %s", path);
+ vfio_put_group(group);
+ }
+
+ return ret;
+}
+
+/**
+ * vfio_map_region - initialize the 2 memory regions for a given
+ * MMIO region index
+ * @vdev: the VFIO platform device handle
+ * @nr: the index of the region
+ *
+ * Init the top memory region and the mmapped memory region beneath
+ * VFIOPlatformDevice is used since VFIODevice is not a QOM Object
+ * and could not be passed to memory region functions
+*/
+static void vfio_map_region(VFIOPlatformDevice *vdev, int nr)
+{
+ VFIORegion *region = vdev->regions[nr];
+ uint64_t size = region->size;
+ char name[64];
+
+ if (!size) {
+ return;
+ }
+
+ g_snprintf(name, sizeof(name), "VFIO %s region %d",
+ vdev->vbasedev.name, nr);
+
+ /* A "slow" read/write mapping underlies all regions */
+ memory_region_init_io(&region->mem, OBJECT(vdev), &vfio_region_ops,
+ region, name, size);
+
+ g_strlcat(name, " mmap", sizeof(name));
+
+ if (vfio_mmap_region(OBJECT(vdev), region, &region->mem,
+ &region->mmap_mem, &region->mmap, size, 0, name)) {
+ error_report("%s unsupported. Performance may be slow", name);
+ }
+}
+
+/**
+ * vfio_platform_realize - the device realize function
+ * @dev: device state pointer
+ * @errp: error
+ *
+ * initialize the device, its memory regions and IRQ structures
+ * IRQ are started separately
+ */
+static void vfio_platform_realize(DeviceState *dev, Error **errp)
+{
+ VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev);
+ SysBusDevice *sbdev = SYS_BUS_DEVICE(dev);
+ VFIODevice *vbasedev = &vdev->vbasedev;
+ VFIOINTp *intp;
+ int i, ret;
+
+ vbasedev->type = VFIO_DEVICE_TYPE_PLATFORM;
+ vbasedev->ops = &vfio_platform_ops;
+
+ trace_vfio_platform_realize(vbasedev->name, vdev->compat);
+
+ ret = vfio_base_device_init(vbasedev);
+ if (ret) {
+ error_setg(errp, "vfio: vfio_base_device_init failed for %s",
+ vbasedev->name);
+ return;
+ }
+
+ for (i = 0; i < vbasedev->num_regions; i++) {
+ vfio_map_region(vdev, i);
+ sysbus_init_mmio(sbdev, &vdev->regions[i]->mem);
+ }
+
+ QLIST_FOREACH(intp, &vdev->intp_list, next) {
+ vfio_start_eventfd_injection(intp);
+ }
+}
+
+static const VMStateDescription vfio_platform_vmstate = {
+ .name = TYPE_VFIO_PLATFORM,
+ .unmigratable = 1,
+};
+
+static Property vfio_platform_dev_properties[] = {
+ DEFINE_PROP_STRING("host", VFIOPlatformDevice, vbasedev.name),
+ DEFINE_PROP_BOOL("x-mmap", VFIOPlatformDevice, vbasedev.allow_mmap, true),
+ DEFINE_PROP_UINT32("mmap-timeout-ms", VFIOPlatformDevice,
+ mmap_timeout, 1100),
+ DEFINE_PROP_BOOL("x-irqfd", VFIOPlatformDevice, irqfd_allowed, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vfio_platform_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+
+ dc->realize = vfio_platform_realize;
+ dc->props = vfio_platform_dev_properties;
+ dc->vmsd = &vfio_platform_vmstate;
+ dc->desc = "VFIO-based platform device assignment";
+ sbc->connect_irq_notifier = vfio_start_irqfd_injection;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo vfio_platform_dev_info = {
+ .name = TYPE_VFIO_PLATFORM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(VFIOPlatformDevice),
+ .class_init = vfio_platform_class_init,
+ .class_size = sizeof(VFIOPlatformDeviceClass),
+ .abstract = true,
+};
+
+static void register_vfio_platform_dev_type(void)
+{
+ type_register_static(&vfio_platform_dev_info);
+}
+
+type_init(register_vfio_platform_dev_type)
diff --git a/hw/virtio/Makefile.objs b/hw/virtio/Makefile.objs
new file mode 100644
index 00000000..19b224a4
--- /dev/null
+++ b/hw/virtio/Makefile.objs
@@ -0,0 +1,8 @@
+common-obj-y += virtio-rng.o
+common-obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o
+common-obj-y += virtio-bus.o
+common-obj-y += virtio-mmio.o
+obj-$(CONFIG_VIRTIO) += dataplane/
+
+obj-y += virtio.o virtio-balloon.o
+obj-$(CONFIG_LINUX) += vhost.o vhost-backend.o vhost-user.o
diff --git a/hw/virtio/dataplane/Makefile.objs b/hw/virtio/dataplane/Makefile.objs
new file mode 100644
index 00000000..753a9cab
--- /dev/null
+++ b/hw/virtio/dataplane/Makefile.objs
@@ -0,0 +1 @@
+obj-y += vring.o
diff --git a/hw/virtio/dataplane/vring.c b/hw/virtio/dataplane/vring.c
new file mode 100644
index 00000000..68f19944
--- /dev/null
+++ b/hw/virtio/dataplane/vring.c
@@ -0,0 +1,494 @@
+/* Copyright 2012 Red Hat, Inc.
+ * Copyright IBM, Corp. 2012
+ *
+ * Based on Linux 2.6.39 vhost code:
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2006 Rusty Russell IBM Corporation
+ *
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ * Stefan Hajnoczi <stefanha@redhat.com>
+ *
+ * Inspiration, some code, and most witty comments come from
+ * Documentation/virtual/lguest/lguest.c, by Rusty Russell
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ */
+
+#include "trace.h"
+#include "hw/hw.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+#include "hw/virtio/virtio-access.h"
+#include "hw/virtio/dataplane/vring.h"
+#include "hw/virtio/dataplane/vring-accessors.h"
+#include "qemu/error-report.h"
+
+/* vring_map can be coupled with vring_unmap or (if you still have the
+ * value returned in *mr) memory_region_unref.
+ */
+static void *vring_map(MemoryRegion **mr, hwaddr phys, hwaddr len,
+ bool is_write)
+{
+ MemoryRegionSection section = memory_region_find(get_system_memory(), phys, len);
+
+ if (!section.mr || int128_get64(section.size) < len) {
+ goto out;
+ }
+ if (is_write && section.readonly) {
+ goto out;
+ }
+ if (!memory_region_is_ram(section.mr)) {
+ goto out;
+ }
+
+ /* Ignore regions with dirty logging, we cannot mark them dirty */
+ if (memory_region_get_dirty_log_mask(section.mr)) {
+ goto out;
+ }
+
+ *mr = section.mr;
+ return memory_region_get_ram_ptr(section.mr) + section.offset_within_region;
+
+out:
+ memory_region_unref(section.mr);
+ *mr = NULL;
+ return NULL;
+}
+
+static void vring_unmap(void *buffer, bool is_write)
+{
+ ram_addr_t addr;
+ MemoryRegion *mr;
+
+ mr = qemu_ram_addr_from_host(buffer, &addr);
+ memory_region_unref(mr);
+}
+
+/* Map the guest's vring to host memory */
+bool vring_setup(Vring *vring, VirtIODevice *vdev, int n)
+{
+ struct vring *vr = &vring->vr;
+ hwaddr addr;
+ hwaddr size;
+ void *ptr;
+
+ vring->broken = false;
+ vr->num = virtio_queue_get_num(vdev, n);
+
+ addr = virtio_queue_get_desc_addr(vdev, n);
+ size = virtio_queue_get_desc_size(vdev, n);
+ /* Map the descriptor area as read only */
+ ptr = vring_map(&vring->mr_desc, addr, size, false);
+ if (!ptr) {
+ error_report("Failed to map 0x%" HWADDR_PRIx " byte for vring desc "
+ "at 0x%" HWADDR_PRIx,
+ size, addr);
+ goto out_err_desc;
+ }
+ vr->desc = ptr;
+
+ addr = virtio_queue_get_avail_addr(vdev, n);
+ size = virtio_queue_get_avail_size(vdev, n);
+ /* Add the size of the used_event_idx */
+ size += sizeof(uint16_t);
+ /* Map the driver area as read only */
+ ptr = vring_map(&vring->mr_avail, addr, size, false);
+ if (!ptr) {
+ error_report("Failed to map 0x%" HWADDR_PRIx " byte for vring avail "
+ "at 0x%" HWADDR_PRIx,
+ size, addr);
+ goto out_err_avail;
+ }
+ vr->avail = ptr;
+
+ addr = virtio_queue_get_used_addr(vdev, n);
+ size = virtio_queue_get_used_size(vdev, n);
+ /* Add the size of the avail_event_idx */
+ size += sizeof(uint16_t);
+ /* Map the device area as read-write */
+ ptr = vring_map(&vring->mr_used, addr, size, true);
+ if (!ptr) {
+ error_report("Failed to map 0x%" HWADDR_PRIx " byte for vring used "
+ "at 0x%" HWADDR_PRIx,
+ size, addr);
+ goto out_err_used;
+ }
+ vr->used = ptr;
+
+ vring->last_avail_idx = virtio_queue_get_last_avail_idx(vdev, n);
+ vring->last_used_idx = vring_get_used_idx(vdev, vring);
+ vring->signalled_used = 0;
+ vring->signalled_used_valid = false;
+
+ trace_vring_setup(virtio_queue_get_ring_addr(vdev, n),
+ vring->vr.desc, vring->vr.avail, vring->vr.used);
+ return true;
+
+out_err_used:
+ memory_region_unref(vring->mr_avail);
+out_err_avail:
+ memory_region_unref(vring->mr_desc);
+out_err_desc:
+ vring->broken = true;
+ return false;
+}
+
+void vring_teardown(Vring *vring, VirtIODevice *vdev, int n)
+{
+ virtio_queue_set_last_avail_idx(vdev, n, vring->last_avail_idx);
+ virtio_queue_invalidate_signalled_used(vdev, n);
+
+ memory_region_unref(vring->mr_desc);
+ memory_region_unref(vring->mr_avail);
+ memory_region_unref(vring->mr_used);
+}
+
+/* Disable guest->host notifies */
+void vring_disable_notification(VirtIODevice *vdev, Vring *vring)
+{
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
+ vring_set_used_flags(vdev, vring, VRING_USED_F_NO_NOTIFY);
+ }
+}
+
+/* Enable guest->host notifies
+ *
+ * Return true if the vring is empty, false if there are more requests.
+ */
+bool vring_enable_notification(VirtIODevice *vdev, Vring *vring)
+{
+ if (virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
+ vring_avail_event(&vring->vr) = vring->vr.avail->idx;
+ } else {
+ vring_clear_used_flags(vdev, vring, VRING_USED_F_NO_NOTIFY);
+ }
+ smp_mb(); /* ensure update is seen before reading avail_idx */
+ return !vring_more_avail(vdev, vring);
+}
+
+/* This is stolen from linux/drivers/vhost/vhost.c:vhost_notify() */
+bool vring_should_notify(VirtIODevice *vdev, Vring *vring)
+{
+ uint16_t old, new;
+ bool v;
+ /* Flush out used index updates. This is paired
+ * with the barrier that the Guest executes when enabling
+ * interrupts. */
+ smp_mb();
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_F_NOTIFY_ON_EMPTY) &&
+ unlikely(!vring_more_avail(vdev, vring))) {
+ return true;
+ }
+
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
+ return !(vring_get_avail_flags(vdev, vring) &
+ VRING_AVAIL_F_NO_INTERRUPT);
+ }
+ old = vring->signalled_used;
+ v = vring->signalled_used_valid;
+ new = vring->signalled_used = vring->last_used_idx;
+ vring->signalled_used_valid = true;
+
+ if (unlikely(!v)) {
+ return true;
+ }
+
+ return vring_need_event(virtio_tswap16(vdev, vring_used_event(&vring->vr)),
+ new, old);
+}
+
+
+static int get_desc(Vring *vring, VirtQueueElement *elem,
+ struct vring_desc *desc)
+{
+ unsigned *num;
+ struct iovec *iov;
+ hwaddr *addr;
+ MemoryRegion *mr;
+
+ if (desc->flags & VRING_DESC_F_WRITE) {
+ num = &elem->in_num;
+ iov = &elem->in_sg[*num];
+ addr = &elem->in_addr[*num];
+ } else {
+ num = &elem->out_num;
+ iov = &elem->out_sg[*num];
+ addr = &elem->out_addr[*num];
+
+ /* If it's an output descriptor, they're all supposed
+ * to come before any input descriptors. */
+ if (unlikely(elem->in_num)) {
+ error_report("Descriptor has out after in");
+ return -EFAULT;
+ }
+ }
+
+ /* Stop for now if there are not enough iovecs available. */
+ if (*num >= VIRTQUEUE_MAX_SIZE) {
+ error_report("Invalid SG num: %u", *num);
+ return -EFAULT;
+ }
+
+ /* TODO handle non-contiguous memory across region boundaries */
+ iov->iov_base = vring_map(&mr, desc->addr, desc->len,
+ desc->flags & VRING_DESC_F_WRITE);
+ if (!iov->iov_base) {
+ error_report("Failed to map descriptor addr %#" PRIx64 " len %u",
+ (uint64_t)desc->addr, desc->len);
+ return -EFAULT;
+ }
+
+ /* The MemoryRegion is looked up again and unref'ed later, leave the
+ * ref in place. */
+ iov->iov_len = desc->len;
+ *addr = desc->addr;
+ *num += 1;
+ return 0;
+}
+
+static void copy_in_vring_desc(VirtIODevice *vdev,
+ const struct vring_desc *guest,
+ struct vring_desc *host)
+{
+ host->addr = virtio_ldq_p(vdev, &guest->addr);
+ host->len = virtio_ldl_p(vdev, &guest->len);
+ host->flags = virtio_lduw_p(vdev, &guest->flags);
+ host->next = virtio_lduw_p(vdev, &guest->next);
+}
+
+/* This is stolen from linux/drivers/vhost/vhost.c. */
+static int get_indirect(VirtIODevice *vdev, Vring *vring,
+ VirtQueueElement *elem, struct vring_desc *indirect)
+{
+ struct vring_desc desc;
+ unsigned int i = 0, count, found = 0;
+ int ret;
+
+ /* Sanity check */
+ if (unlikely(indirect->len % sizeof(desc))) {
+ error_report("Invalid length in indirect descriptor: "
+ "len %#x not multiple of %#zx",
+ indirect->len, sizeof(desc));
+ vring->broken = true;
+ return -EFAULT;
+ }
+
+ count = indirect->len / sizeof(desc);
+ /* Buffers are chained via a 16 bit next field, so
+ * we can have at most 2^16 of these. */
+ if (unlikely(count > USHRT_MAX + 1)) {
+ error_report("Indirect buffer length too big: %d", indirect->len);
+ vring->broken = true;
+ return -EFAULT;
+ }
+
+ do {
+ struct vring_desc *desc_ptr;
+ MemoryRegion *mr;
+
+ /* Translate indirect descriptor */
+ desc_ptr = vring_map(&mr,
+ indirect->addr + found * sizeof(desc),
+ sizeof(desc), false);
+ if (!desc_ptr) {
+ error_report("Failed to map indirect descriptor "
+ "addr %#" PRIx64 " len %zu",
+ (uint64_t)indirect->addr + found * sizeof(desc),
+ sizeof(desc));
+ vring->broken = true;
+ return -EFAULT;
+ }
+ copy_in_vring_desc(vdev, desc_ptr, &desc);
+ memory_region_unref(mr);
+
+ /* Ensure descriptor has been loaded before accessing fields */
+ barrier(); /* read_barrier_depends(); */
+
+ if (unlikely(++found > count)) {
+ error_report("Loop detected: last one at %u "
+ "indirect size %u", i, count);
+ vring->broken = true;
+ return -EFAULT;
+ }
+
+ if (unlikely(desc.flags & VRING_DESC_F_INDIRECT)) {
+ error_report("Nested indirect descriptor");
+ vring->broken = true;
+ return -EFAULT;
+ }
+
+ ret = get_desc(vring, elem, &desc);
+ if (ret < 0) {
+ vring->broken |= (ret == -EFAULT);
+ return ret;
+ }
+ i = desc.next;
+ } while (desc.flags & VRING_DESC_F_NEXT);
+ return 0;
+}
+
+static void vring_unmap_element(VirtQueueElement *elem)
+{
+ int i;
+
+ /* This assumes that the iovecs, if changed, are never moved past
+ * the end of the valid area. This is true if iovec manipulations
+ * are done with iov_discard_front and iov_discard_back.
+ */
+ for (i = 0; i < elem->out_num; i++) {
+ vring_unmap(elem->out_sg[i].iov_base, false);
+ }
+
+ for (i = 0; i < elem->in_num; i++) {
+ vring_unmap(elem->in_sg[i].iov_base, true);
+ }
+}
+
+/* This looks in the virtqueue and for the first available buffer, and converts
+ * it to an iovec for convenient access. Since descriptors consist of some
+ * number of output then some number of input descriptors, it's actually two
+ * iovecs, but we pack them into one and note how many of each there were.
+ *
+ * This function returns the descriptor number found, or vq->num (which is
+ * never a valid descriptor number) if none was found. A negative code is
+ * returned on error.
+ *
+ * Stolen from linux/drivers/vhost/vhost.c.
+ */
+int vring_pop(VirtIODevice *vdev, Vring *vring,
+ VirtQueueElement *elem)
+{
+ struct vring_desc desc;
+ unsigned int i, head, found = 0, num = vring->vr.num;
+ uint16_t avail_idx, last_avail_idx;
+ int ret;
+
+ /* Initialize elem so it can be safely unmapped */
+ elem->in_num = elem->out_num = 0;
+
+ /* If there was a fatal error then refuse operation */
+ if (vring->broken) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ /* Check it isn't doing very strange things with descriptor numbers. */
+ last_avail_idx = vring->last_avail_idx;
+ avail_idx = vring_get_avail_idx(vdev, vring);
+ barrier(); /* load indices now and not again later */
+
+ if (unlikely((uint16_t)(avail_idx - last_avail_idx) > num)) {
+ error_report("Guest moved used index from %u to %u",
+ last_avail_idx, avail_idx);
+ ret = -EFAULT;
+ goto out;
+ }
+
+ /* If there's nothing new since last we looked. */
+ if (avail_idx == last_avail_idx) {
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ /* Only get avail ring entries after they have been exposed by guest. */
+ smp_rmb();
+
+ /* Grab the next descriptor number they're advertising, and increment
+ * the index we've seen. */
+ head = vring_get_avail_ring(vdev, vring, last_avail_idx % num);
+
+ elem->index = head;
+
+ /* If their number is silly, that's an error. */
+ if (unlikely(head >= num)) {
+ error_report("Guest says index %u > %u is available", head, num);
+ ret = -EFAULT;
+ goto out;
+ }
+
+ i = head;
+ do {
+ if (unlikely(i >= num)) {
+ error_report("Desc index is %u > %u, head = %u", i, num, head);
+ ret = -EFAULT;
+ goto out;
+ }
+ if (unlikely(++found > num)) {
+ error_report("Loop detected: last one at %u vq size %u head %u",
+ i, num, head);
+ ret = -EFAULT;
+ goto out;
+ }
+ copy_in_vring_desc(vdev, &vring->vr.desc[i], &desc);
+
+ /* Ensure descriptor is loaded before accessing fields */
+ barrier();
+
+ if (desc.flags & VRING_DESC_F_INDIRECT) {
+ ret = get_indirect(vdev, vring, elem, &desc);
+ if (ret < 0) {
+ goto out;
+ }
+ continue;
+ }
+
+ ret = get_desc(vring, elem, &desc);
+ if (ret < 0) {
+ goto out;
+ }
+
+ i = desc.next;
+ } while (desc.flags & VRING_DESC_F_NEXT);
+
+ /* On success, increment avail index. */
+ vring->last_avail_idx++;
+ if (virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
+ vring_avail_event(&vring->vr) =
+ virtio_tswap16(vdev, vring->last_avail_idx);
+ }
+
+ return head;
+
+out:
+ assert(ret < 0);
+ if (ret == -EFAULT) {
+ vring->broken = true;
+ }
+ vring_unmap_element(elem);
+ return ret;
+}
+
+/* After we've used one of their buffers, we tell them about it.
+ *
+ * Stolen from linux/drivers/vhost/vhost.c.
+ */
+void vring_push(VirtIODevice *vdev, Vring *vring, VirtQueueElement *elem,
+ int len)
+{
+ unsigned int head = elem->index;
+ uint16_t new;
+
+ vring_unmap_element(elem);
+
+ /* Don't touch vring if a fatal error occurred */
+ if (vring->broken) {
+ return;
+ }
+
+ /* The virtqueue contains a ring of used buffers. Get a pointer to the
+ * next entry in that used ring. */
+ vring_set_used_ring_id(vdev, vring, vring->last_used_idx % vring->vr.num,
+ head);
+ vring_set_used_ring_len(vdev, vring, vring->last_used_idx % vring->vr.num,
+ len);
+
+ /* Make sure buffer is written before we update index. */
+ smp_wmb();
+
+ new = ++vring->last_used_idx;
+ vring_set_used_idx(vdev, vring, new);
+ if (unlikely((int16_t)(new - vring->signalled_used) < (uint16_t)1)) {
+ vring->signalled_used_valid = false;
+ }
+}
diff --git a/hw/virtio/vhost-backend.c b/hw/virtio/vhost-backend.c
new file mode 100644
index 00000000..4d68a276
--- /dev/null
+++ b/hw/virtio/vhost-backend.c
@@ -0,0 +1,69 @@
+/*
+ * vhost-backend
+ *
+ * Copyright (c) 2013 Virtual Open Systems Sarl.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/vhost.h"
+#include "hw/virtio/vhost-backend.h"
+#include "qemu/error-report.h"
+
+#include <sys/ioctl.h>
+
+static int vhost_kernel_call(struct vhost_dev *dev, unsigned long int request,
+ void *arg)
+{
+ int fd = (uintptr_t) dev->opaque;
+
+ assert(dev->vhost_ops->backend_type == VHOST_BACKEND_TYPE_KERNEL);
+
+ return ioctl(fd, request, arg);
+}
+
+static int vhost_kernel_init(struct vhost_dev *dev, void *opaque)
+{
+ assert(dev->vhost_ops->backend_type == VHOST_BACKEND_TYPE_KERNEL);
+
+ dev->opaque = opaque;
+
+ return 0;
+}
+
+static int vhost_kernel_cleanup(struct vhost_dev *dev)
+{
+ int fd = (uintptr_t) dev->opaque;
+
+ assert(dev->vhost_ops->backend_type == VHOST_BACKEND_TYPE_KERNEL);
+
+ return close(fd);
+}
+
+static const VhostOps kernel_ops = {
+ .backend_type = VHOST_BACKEND_TYPE_KERNEL,
+ .vhost_call = vhost_kernel_call,
+ .vhost_backend_init = vhost_kernel_init,
+ .vhost_backend_cleanup = vhost_kernel_cleanup
+};
+
+int vhost_set_backend_type(struct vhost_dev *dev, VhostBackendType backend_type)
+{
+ int r = 0;
+
+ switch (backend_type) {
+ case VHOST_BACKEND_TYPE_KERNEL:
+ dev->vhost_ops = &kernel_ops;
+ break;
+ case VHOST_BACKEND_TYPE_USER:
+ dev->vhost_ops = &user_ops;
+ break;
+ default:
+ error_report("Unknown vhost backend type");
+ r = -1;
+ }
+
+ return r;
+}
diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c
new file mode 100644
index 00000000..e7ab8293
--- /dev/null
+++ b/hw/virtio/vhost-user.c
@@ -0,0 +1,351 @@
+/*
+ * vhost-user
+ *
+ * Copyright (c) 2013 Virtual Open Systems Sarl.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/virtio/vhost.h"
+#include "hw/virtio/vhost-backend.h"
+#include "sysemu/char.h"
+#include "sysemu/kvm.h"
+#include "qemu/error-report.h"
+#include "qemu/sockets.h"
+#include "exec/ram_addr.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <linux/vhost.h>
+
+#define VHOST_MEMORY_MAX_NREGIONS 8
+
+typedef enum VhostUserRequest {
+ VHOST_USER_NONE = 0,
+ VHOST_USER_GET_FEATURES = 1,
+ VHOST_USER_SET_FEATURES = 2,
+ VHOST_USER_SET_OWNER = 3,
+ VHOST_USER_RESET_OWNER = 4,
+ VHOST_USER_SET_MEM_TABLE = 5,
+ VHOST_USER_SET_LOG_BASE = 6,
+ VHOST_USER_SET_LOG_FD = 7,
+ VHOST_USER_SET_VRING_NUM = 8,
+ VHOST_USER_SET_VRING_ADDR = 9,
+ VHOST_USER_SET_VRING_BASE = 10,
+ VHOST_USER_GET_VRING_BASE = 11,
+ VHOST_USER_SET_VRING_KICK = 12,
+ VHOST_USER_SET_VRING_CALL = 13,
+ VHOST_USER_SET_VRING_ERR = 14,
+ VHOST_USER_MAX
+} VhostUserRequest;
+
+typedef struct VhostUserMemoryRegion {
+ uint64_t guest_phys_addr;
+ uint64_t memory_size;
+ uint64_t userspace_addr;
+ uint64_t mmap_offset;
+} VhostUserMemoryRegion;
+
+typedef struct VhostUserMemory {
+ uint32_t nregions;
+ uint32_t padding;
+ VhostUserMemoryRegion regions[VHOST_MEMORY_MAX_NREGIONS];
+} VhostUserMemory;
+
+typedef struct VhostUserMsg {
+ VhostUserRequest request;
+
+#define VHOST_USER_VERSION_MASK (0x3)
+#define VHOST_USER_REPLY_MASK (0x1<<2)
+ uint32_t flags;
+ uint32_t size; /* the following payload size */
+ union {
+#define VHOST_USER_VRING_IDX_MASK (0xff)
+#define VHOST_USER_VRING_NOFD_MASK (0x1<<8)
+ uint64_t u64;
+ struct vhost_vring_state state;
+ struct vhost_vring_addr addr;
+ VhostUserMemory memory;
+ };
+} QEMU_PACKED VhostUserMsg;
+
+static VhostUserMsg m __attribute__ ((unused));
+#define VHOST_USER_HDR_SIZE (sizeof(m.request) \
+ + sizeof(m.flags) \
+ + sizeof(m.size))
+
+#define VHOST_USER_PAYLOAD_SIZE (sizeof(m) - VHOST_USER_HDR_SIZE)
+
+/* The version of the protocol we support */
+#define VHOST_USER_VERSION (0x1)
+
+static bool ioeventfd_enabled(void)
+{
+ return kvm_enabled() && kvm_eventfds_enabled();
+}
+
+static unsigned long int ioctl_to_vhost_user_request[VHOST_USER_MAX] = {
+ -1, /* VHOST_USER_NONE */
+ VHOST_GET_FEATURES, /* VHOST_USER_GET_FEATURES */
+ VHOST_SET_FEATURES, /* VHOST_USER_SET_FEATURES */
+ VHOST_SET_OWNER, /* VHOST_USER_SET_OWNER */
+ VHOST_RESET_OWNER, /* VHOST_USER_RESET_OWNER */
+ VHOST_SET_MEM_TABLE, /* VHOST_USER_SET_MEM_TABLE */
+ VHOST_SET_LOG_BASE, /* VHOST_USER_SET_LOG_BASE */
+ VHOST_SET_LOG_FD, /* VHOST_USER_SET_LOG_FD */
+ VHOST_SET_VRING_NUM, /* VHOST_USER_SET_VRING_NUM */
+ VHOST_SET_VRING_ADDR, /* VHOST_USER_SET_VRING_ADDR */
+ VHOST_SET_VRING_BASE, /* VHOST_USER_SET_VRING_BASE */
+ VHOST_GET_VRING_BASE, /* VHOST_USER_GET_VRING_BASE */
+ VHOST_SET_VRING_KICK, /* VHOST_USER_SET_VRING_KICK */
+ VHOST_SET_VRING_CALL, /* VHOST_USER_SET_VRING_CALL */
+ VHOST_SET_VRING_ERR /* VHOST_USER_SET_VRING_ERR */
+};
+
+static VhostUserRequest vhost_user_request_translate(unsigned long int request)
+{
+ VhostUserRequest idx;
+
+ for (idx = 0; idx < VHOST_USER_MAX; idx++) {
+ if (ioctl_to_vhost_user_request[idx] == request) {
+ break;
+ }
+ }
+
+ return (idx == VHOST_USER_MAX) ? VHOST_USER_NONE : idx;
+}
+
+static int vhost_user_read(struct vhost_dev *dev, VhostUserMsg *msg)
+{
+ CharDriverState *chr = dev->opaque;
+ uint8_t *p = (uint8_t *) msg;
+ int r, size = VHOST_USER_HDR_SIZE;
+
+ r = qemu_chr_fe_read_all(chr, p, size);
+ if (r != size) {
+ error_report("Failed to read msg header. Read %d instead of %d.", r,
+ size);
+ goto fail;
+ }
+
+ /* validate received flags */
+ if (msg->flags != (VHOST_USER_REPLY_MASK | VHOST_USER_VERSION)) {
+ error_report("Failed to read msg header."
+ " Flags 0x%x instead of 0x%x.", msg->flags,
+ VHOST_USER_REPLY_MASK | VHOST_USER_VERSION);
+ goto fail;
+ }
+
+ /* validate message size is sane */
+ if (msg->size > VHOST_USER_PAYLOAD_SIZE) {
+ error_report("Failed to read msg header."
+ " Size %d exceeds the maximum %zu.", msg->size,
+ VHOST_USER_PAYLOAD_SIZE);
+ goto fail;
+ }
+
+ if (msg->size) {
+ p += VHOST_USER_HDR_SIZE;
+ size = msg->size;
+ r = qemu_chr_fe_read_all(chr, p, size);
+ if (r != size) {
+ error_report("Failed to read msg payload."
+ " Read %d instead of %d.", r, msg->size);
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ return -1;
+}
+
+static int vhost_user_write(struct vhost_dev *dev, VhostUserMsg *msg,
+ int *fds, int fd_num)
+{
+ CharDriverState *chr = dev->opaque;
+ int size = VHOST_USER_HDR_SIZE + msg->size;
+
+ if (fd_num) {
+ qemu_chr_fe_set_msgfds(chr, fds, fd_num);
+ }
+
+ return qemu_chr_fe_write_all(chr, (const uint8_t *) msg, size) == size ?
+ 0 : -1;
+}
+
+static int vhost_user_call(struct vhost_dev *dev, unsigned long int request,
+ void *arg)
+{
+ VhostUserMsg msg;
+ VhostUserRequest msg_request;
+ struct vhost_vring_file *file = 0;
+ int need_reply = 0;
+ int fds[VHOST_MEMORY_MAX_NREGIONS];
+ int i, fd;
+ size_t fd_num = 0;
+
+ assert(dev->vhost_ops->backend_type == VHOST_BACKEND_TYPE_USER);
+
+ msg_request = vhost_user_request_translate(request);
+ msg.request = msg_request;
+ msg.flags = VHOST_USER_VERSION;
+ msg.size = 0;
+
+ switch (request) {
+ case VHOST_GET_FEATURES:
+ need_reply = 1;
+ break;
+
+ case VHOST_SET_FEATURES:
+ case VHOST_SET_LOG_BASE:
+ msg.u64 = *((__u64 *) arg);
+ msg.size = sizeof(m.u64);
+ break;
+
+ case VHOST_SET_OWNER:
+ case VHOST_RESET_OWNER:
+ break;
+
+ case VHOST_SET_MEM_TABLE:
+ for (i = 0; i < dev->mem->nregions; ++i) {
+ struct vhost_memory_region *reg = dev->mem->regions + i;
+ ram_addr_t ram_addr;
+
+ assert((uintptr_t)reg->userspace_addr == reg->userspace_addr);
+ qemu_ram_addr_from_host((void *)(uintptr_t)reg->userspace_addr, &ram_addr);
+ fd = qemu_get_ram_fd(ram_addr);
+ if (fd > 0) {
+ msg.memory.regions[fd_num].userspace_addr = reg->userspace_addr;
+ msg.memory.regions[fd_num].memory_size = reg->memory_size;
+ msg.memory.regions[fd_num].guest_phys_addr = reg->guest_phys_addr;
+ msg.memory.regions[fd_num].mmap_offset = reg->userspace_addr -
+ (uintptr_t) qemu_get_ram_block_host_ptr(ram_addr);
+ assert(fd_num < VHOST_MEMORY_MAX_NREGIONS);
+ fds[fd_num++] = fd;
+ }
+ }
+
+ msg.memory.nregions = fd_num;
+
+ if (!fd_num) {
+ error_report("Failed initializing vhost-user memory map, "
+ "consider using -object memory-backend-file share=on");
+ return -1;
+ }
+
+ msg.size = sizeof(m.memory.nregions);
+ msg.size += sizeof(m.memory.padding);
+ msg.size += fd_num * sizeof(VhostUserMemoryRegion);
+
+ break;
+
+ case VHOST_SET_LOG_FD:
+ fds[fd_num++] = *((int *) arg);
+ break;
+
+ case VHOST_SET_VRING_NUM:
+ case VHOST_SET_VRING_BASE:
+ memcpy(&msg.state, arg, sizeof(struct vhost_vring_state));
+ msg.size = sizeof(m.state);
+ break;
+
+ case VHOST_GET_VRING_BASE:
+ memcpy(&msg.state, arg, sizeof(struct vhost_vring_state));
+ msg.size = sizeof(m.state);
+ need_reply = 1;
+ break;
+
+ case VHOST_SET_VRING_ADDR:
+ memcpy(&msg.addr, arg, sizeof(struct vhost_vring_addr));
+ msg.size = sizeof(m.addr);
+ break;
+
+ case VHOST_SET_VRING_KICK:
+ case VHOST_SET_VRING_CALL:
+ case VHOST_SET_VRING_ERR:
+ file = arg;
+ msg.u64 = file->index & VHOST_USER_VRING_IDX_MASK;
+ msg.size = sizeof(m.u64);
+ if (ioeventfd_enabled() && file->fd > 0) {
+ fds[fd_num++] = file->fd;
+ } else {
+ msg.u64 |= VHOST_USER_VRING_NOFD_MASK;
+ }
+ break;
+ default:
+ error_report("vhost-user trying to send unhandled ioctl");
+ return -1;
+ break;
+ }
+
+ if (vhost_user_write(dev, &msg, fds, fd_num) < 0) {
+ return 0;
+ }
+
+ if (need_reply) {
+ if (vhost_user_read(dev, &msg) < 0) {
+ return 0;
+ }
+
+ if (msg_request != msg.request) {
+ error_report("Received unexpected msg type."
+ " Expected %d received %d", msg_request, msg.request);
+ return -1;
+ }
+
+ switch (msg_request) {
+ case VHOST_USER_GET_FEATURES:
+ if (msg.size != sizeof(m.u64)) {
+ error_report("Received bad msg size.");
+ return -1;
+ }
+ *((__u64 *) arg) = msg.u64;
+ break;
+ case VHOST_USER_GET_VRING_BASE:
+ if (msg.size != sizeof(m.state)) {
+ error_report("Received bad msg size.");
+ return -1;
+ }
+ memcpy(arg, &msg.state, sizeof(struct vhost_vring_state));
+ break;
+ default:
+ error_report("Received unexpected msg type.");
+ return -1;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int vhost_user_init(struct vhost_dev *dev, void *opaque)
+{
+ assert(dev->vhost_ops->backend_type == VHOST_BACKEND_TYPE_USER);
+
+ dev->opaque = opaque;
+
+ return 0;
+}
+
+static int vhost_user_cleanup(struct vhost_dev *dev)
+{
+ assert(dev->vhost_ops->backend_type == VHOST_BACKEND_TYPE_USER);
+
+ dev->opaque = 0;
+
+ return 0;
+}
+
+const VhostOps user_ops = {
+ .backend_type = VHOST_BACKEND_TYPE_USER,
+ .vhost_call = vhost_user_call,
+ .vhost_backend_init = vhost_user_init,
+ .vhost_backend_cleanup = vhost_user_cleanup
+ };
diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c
new file mode 100644
index 00000000..a08c36bb
--- /dev/null
+++ b/hw/virtio/vhost.c
@@ -0,0 +1,1186 @@
+/*
+ * vhost support
+ *
+ * Copyright Red Hat, Inc. 2010
+ *
+ * Authors:
+ * Michael S. Tsirkin <mst@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/virtio/vhost.h"
+#include "hw/hw.h"
+#include "qemu/atomic.h"
+#include "qemu/range.h"
+#include "qemu/error-report.h"
+#include <linux/vhost.h>
+#include "exec/address-spaces.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+#include "migration/migration.h"
+
+static struct vhost_log *vhost_log;
+
+static void vhost_dev_sync_region(struct vhost_dev *dev,
+ MemoryRegionSection *section,
+ uint64_t mfirst, uint64_t mlast,
+ uint64_t rfirst, uint64_t rlast)
+{
+ vhost_log_chunk_t *log = dev->log->log;
+
+ uint64_t start = MAX(mfirst, rfirst);
+ uint64_t end = MIN(mlast, rlast);
+ vhost_log_chunk_t *from = log + start / VHOST_LOG_CHUNK;
+ vhost_log_chunk_t *to = log + end / VHOST_LOG_CHUNK + 1;
+ uint64_t addr = (start / VHOST_LOG_CHUNK) * VHOST_LOG_CHUNK;
+
+ if (end < start) {
+ return;
+ }
+ assert(end / VHOST_LOG_CHUNK < dev->log_size);
+ assert(start / VHOST_LOG_CHUNK < dev->log_size);
+
+ for (;from < to; ++from) {
+ vhost_log_chunk_t log;
+ /* We first check with non-atomic: much cheaper,
+ * and we expect non-dirty to be the common case. */
+ if (!*from) {
+ addr += VHOST_LOG_CHUNK;
+ continue;
+ }
+ /* Data must be read atomically. We don't really need barrier semantics
+ * but it's easier to use atomic_* than roll our own. */
+ log = atomic_xchg(from, 0);
+ while (log) {
+ int bit = ctzl(log);
+ hwaddr page_addr;
+ hwaddr section_offset;
+ hwaddr mr_offset;
+ page_addr = addr + bit * VHOST_LOG_PAGE;
+ section_offset = page_addr - section->offset_within_address_space;
+ mr_offset = section_offset + section->offset_within_region;
+ memory_region_set_dirty(section->mr, mr_offset, VHOST_LOG_PAGE);
+ log &= ~(0x1ull << bit);
+ }
+ addr += VHOST_LOG_CHUNK;
+ }
+}
+
+static int vhost_sync_dirty_bitmap(struct vhost_dev *dev,
+ MemoryRegionSection *section,
+ hwaddr first,
+ hwaddr last)
+{
+ int i;
+ hwaddr start_addr;
+ hwaddr end_addr;
+
+ if (!dev->log_enabled || !dev->started) {
+ return 0;
+ }
+ start_addr = section->offset_within_address_space;
+ end_addr = range_get_last(start_addr, int128_get64(section->size));
+ start_addr = MAX(first, start_addr);
+ end_addr = MIN(last, end_addr);
+
+ for (i = 0; i < dev->mem->nregions; ++i) {
+ struct vhost_memory_region *reg = dev->mem->regions + i;
+ vhost_dev_sync_region(dev, section, start_addr, end_addr,
+ reg->guest_phys_addr,
+ range_get_last(reg->guest_phys_addr,
+ reg->memory_size));
+ }
+ for (i = 0; i < dev->nvqs; ++i) {
+ struct vhost_virtqueue *vq = dev->vqs + i;
+ vhost_dev_sync_region(dev, section, start_addr, end_addr, vq->used_phys,
+ range_get_last(vq->used_phys, vq->used_size));
+ }
+ return 0;
+}
+
+static void vhost_log_sync(MemoryListener *listener,
+ MemoryRegionSection *section)
+{
+ struct vhost_dev *dev = container_of(listener, struct vhost_dev,
+ memory_listener);
+ vhost_sync_dirty_bitmap(dev, section, 0x0, ~0x0ULL);
+}
+
+static void vhost_log_sync_range(struct vhost_dev *dev,
+ hwaddr first, hwaddr last)
+{
+ int i;
+ /* FIXME: this is N^2 in number of sections */
+ for (i = 0; i < dev->n_mem_sections; ++i) {
+ MemoryRegionSection *section = &dev->mem_sections[i];
+ vhost_sync_dirty_bitmap(dev, section, first, last);
+ }
+}
+
+/* Assign/unassign. Keep an unsorted array of non-overlapping
+ * memory regions in dev->mem. */
+static void vhost_dev_unassign_memory(struct vhost_dev *dev,
+ uint64_t start_addr,
+ uint64_t size)
+{
+ int from, to, n = dev->mem->nregions;
+ /* Track overlapping/split regions for sanity checking. */
+ int overlap_start = 0, overlap_end = 0, overlap_middle = 0, split = 0;
+
+ for (from = 0, to = 0; from < n; ++from, ++to) {
+ struct vhost_memory_region *reg = dev->mem->regions + to;
+ uint64_t reglast;
+ uint64_t memlast;
+ uint64_t change;
+
+ /* clone old region */
+ if (to != from) {
+ memcpy(reg, dev->mem->regions + from, sizeof *reg);
+ }
+
+ /* No overlap is simple */
+ if (!ranges_overlap(reg->guest_phys_addr, reg->memory_size,
+ start_addr, size)) {
+ continue;
+ }
+
+ /* Split only happens if supplied region
+ * is in the middle of an existing one. Thus it can not
+ * overlap with any other existing region. */
+ assert(!split);
+
+ reglast = range_get_last(reg->guest_phys_addr, reg->memory_size);
+ memlast = range_get_last(start_addr, size);
+
+ /* Remove whole region */
+ if (start_addr <= reg->guest_phys_addr && memlast >= reglast) {
+ --dev->mem->nregions;
+ --to;
+ ++overlap_middle;
+ continue;
+ }
+
+ /* Shrink region */
+ if (memlast >= reglast) {
+ reg->memory_size = start_addr - reg->guest_phys_addr;
+ assert(reg->memory_size);
+ assert(!overlap_end);
+ ++overlap_end;
+ continue;
+ }
+
+ /* Shift region */
+ if (start_addr <= reg->guest_phys_addr) {
+ change = memlast + 1 - reg->guest_phys_addr;
+ reg->memory_size -= change;
+ reg->guest_phys_addr += change;
+ reg->userspace_addr += change;
+ assert(reg->memory_size);
+ assert(!overlap_start);
+ ++overlap_start;
+ continue;
+ }
+
+ /* This only happens if supplied region
+ * is in the middle of an existing one. Thus it can not
+ * overlap with any other existing region. */
+ assert(!overlap_start);
+ assert(!overlap_end);
+ assert(!overlap_middle);
+ /* Split region: shrink first part, shift second part. */
+ memcpy(dev->mem->regions + n, reg, sizeof *reg);
+ reg->memory_size = start_addr - reg->guest_phys_addr;
+ assert(reg->memory_size);
+ change = memlast + 1 - reg->guest_phys_addr;
+ reg = dev->mem->regions + n;
+ reg->memory_size -= change;
+ assert(reg->memory_size);
+ reg->guest_phys_addr += change;
+ reg->userspace_addr += change;
+ /* Never add more than 1 region */
+ assert(dev->mem->nregions == n);
+ ++dev->mem->nregions;
+ ++split;
+ }
+}
+
+/* Called after unassign, so no regions overlap the given range. */
+static void vhost_dev_assign_memory(struct vhost_dev *dev,
+ uint64_t start_addr,
+ uint64_t size,
+ uint64_t uaddr)
+{
+ int from, to;
+ struct vhost_memory_region *merged = NULL;
+ for (from = 0, to = 0; from < dev->mem->nregions; ++from, ++to) {
+ struct vhost_memory_region *reg = dev->mem->regions + to;
+ uint64_t prlast, urlast;
+ uint64_t pmlast, umlast;
+ uint64_t s, e, u;
+
+ /* clone old region */
+ if (to != from) {
+ memcpy(reg, dev->mem->regions + from, sizeof *reg);
+ }
+ prlast = range_get_last(reg->guest_phys_addr, reg->memory_size);
+ pmlast = range_get_last(start_addr, size);
+ urlast = range_get_last(reg->userspace_addr, reg->memory_size);
+ umlast = range_get_last(uaddr, size);
+
+ /* check for overlapping regions: should never happen. */
+ assert(prlast < start_addr || pmlast < reg->guest_phys_addr);
+ /* Not an adjacent or overlapping region - do not merge. */
+ if ((prlast + 1 != start_addr || urlast + 1 != uaddr) &&
+ (pmlast + 1 != reg->guest_phys_addr ||
+ umlast + 1 != reg->userspace_addr)) {
+ continue;
+ }
+
+ if (merged) {
+ --to;
+ assert(to >= 0);
+ } else {
+ merged = reg;
+ }
+ u = MIN(uaddr, reg->userspace_addr);
+ s = MIN(start_addr, reg->guest_phys_addr);
+ e = MAX(pmlast, prlast);
+ uaddr = merged->userspace_addr = u;
+ start_addr = merged->guest_phys_addr = s;
+ size = merged->memory_size = e - s + 1;
+ assert(merged->memory_size);
+ }
+
+ if (!merged) {
+ struct vhost_memory_region *reg = dev->mem->regions + to;
+ memset(reg, 0, sizeof *reg);
+ reg->memory_size = size;
+ assert(reg->memory_size);
+ reg->guest_phys_addr = start_addr;
+ reg->userspace_addr = uaddr;
+ ++to;
+ }
+ assert(to <= dev->mem->nregions + 1);
+ dev->mem->nregions = to;
+}
+
+static uint64_t vhost_get_log_size(struct vhost_dev *dev)
+{
+ uint64_t log_size = 0;
+ int i;
+ for (i = 0; i < dev->mem->nregions; ++i) {
+ struct vhost_memory_region *reg = dev->mem->regions + i;
+ uint64_t last = range_get_last(reg->guest_phys_addr,
+ reg->memory_size);
+ log_size = MAX(log_size, last / VHOST_LOG_CHUNK + 1);
+ }
+ for (i = 0; i < dev->nvqs; ++i) {
+ struct vhost_virtqueue *vq = dev->vqs + i;
+ uint64_t last = vq->used_phys + vq->used_size - 1;
+ log_size = MAX(log_size, last / VHOST_LOG_CHUNK + 1);
+ }
+ return log_size;
+}
+static struct vhost_log *vhost_log_alloc(uint64_t size)
+{
+ struct vhost_log *log = g_malloc0(sizeof *log + size * sizeof(*(log->log)));
+
+ log->size = size;
+ log->refcnt = 1;
+
+ return log;
+}
+
+static struct vhost_log *vhost_log_get(uint64_t size)
+{
+ if (!vhost_log || vhost_log->size != size) {
+ vhost_log = vhost_log_alloc(size);
+ } else {
+ ++vhost_log->refcnt;
+ }
+
+ return vhost_log;
+}
+
+static void vhost_log_put(struct vhost_dev *dev, bool sync)
+{
+ struct vhost_log *log = dev->log;
+
+ if (!log) {
+ return;
+ }
+
+ --log->refcnt;
+ if (log->refcnt == 0) {
+ /* Sync only the range covered by the old log */
+ if (dev->log_size && sync) {
+ vhost_log_sync_range(dev, 0, dev->log_size * VHOST_LOG_CHUNK - 1);
+ }
+ if (vhost_log == log) {
+ vhost_log = NULL;
+ }
+ g_free(log);
+ }
+}
+
+static inline void vhost_dev_log_resize(struct vhost_dev* dev, uint64_t size)
+{
+ struct vhost_log *log = vhost_log_get(size);
+ uint64_t log_base = (uintptr_t)log->log;
+ int r;
+
+ r = dev->vhost_ops->vhost_call(dev, VHOST_SET_LOG_BASE, &log_base);
+ assert(r >= 0);
+ vhost_log_put(dev, true);
+ dev->log = log;
+ dev->log_size = size;
+}
+
+static int vhost_verify_ring_mappings(struct vhost_dev *dev,
+ uint64_t start_addr,
+ uint64_t size)
+{
+ int i;
+ int r = 0;
+
+ for (i = 0; !r && i < dev->nvqs; ++i) {
+ struct vhost_virtqueue *vq = dev->vqs + i;
+ hwaddr l;
+ void *p;
+
+ if (!ranges_overlap(start_addr, size, vq->ring_phys, vq->ring_size)) {
+ continue;
+ }
+ l = vq->ring_size;
+ p = cpu_physical_memory_map(vq->ring_phys, &l, 1);
+ if (!p || l != vq->ring_size) {
+ fprintf(stderr, "Unable to map ring buffer for ring %d\n", i);
+ r = -ENOMEM;
+ }
+ if (p != vq->ring) {
+ fprintf(stderr, "Ring buffer relocated for ring %d\n", i);
+ r = -EBUSY;
+ }
+ cpu_physical_memory_unmap(p, l, 0, 0);
+ }
+ return r;
+}
+
+static struct vhost_memory_region *vhost_dev_find_reg(struct vhost_dev *dev,
+ uint64_t start_addr,
+ uint64_t size)
+{
+ int i, n = dev->mem->nregions;
+ for (i = 0; i < n; ++i) {
+ struct vhost_memory_region *reg = dev->mem->regions + i;
+ if (ranges_overlap(reg->guest_phys_addr, reg->memory_size,
+ start_addr, size)) {
+ return reg;
+ }
+ }
+ return NULL;
+}
+
+static bool vhost_dev_cmp_memory(struct vhost_dev *dev,
+ uint64_t start_addr,
+ uint64_t size,
+ uint64_t uaddr)
+{
+ struct vhost_memory_region *reg = vhost_dev_find_reg(dev, start_addr, size);
+ uint64_t reglast;
+ uint64_t memlast;
+
+ if (!reg) {
+ return true;
+ }
+
+ reglast = range_get_last(reg->guest_phys_addr, reg->memory_size);
+ memlast = range_get_last(start_addr, size);
+
+ /* Need to extend region? */
+ if (start_addr < reg->guest_phys_addr || memlast > reglast) {
+ return true;
+ }
+ /* userspace_addr changed? */
+ return uaddr != reg->userspace_addr + start_addr - reg->guest_phys_addr;
+}
+
+static void vhost_set_memory(MemoryListener *listener,
+ MemoryRegionSection *section,
+ bool add)
+{
+ struct vhost_dev *dev = container_of(listener, struct vhost_dev,
+ memory_listener);
+ hwaddr start_addr = section->offset_within_address_space;
+ ram_addr_t size = int128_get64(section->size);
+ bool log_dirty =
+ memory_region_get_dirty_log_mask(section->mr) & ~(1 << DIRTY_MEMORY_MIGRATION);
+ int s = offsetof(struct vhost_memory, regions) +
+ (dev->mem->nregions + 1) * sizeof dev->mem->regions[0];
+ void *ram;
+
+ dev->mem = g_realloc(dev->mem, s);
+
+ if (log_dirty) {
+ add = false;
+ }
+
+ assert(size);
+
+ /* Optimize no-change case. At least cirrus_vga does this a lot at this time. */
+ ram = memory_region_get_ram_ptr(section->mr) + section->offset_within_region;
+ if (add) {
+ if (!vhost_dev_cmp_memory(dev, start_addr, size, (uintptr_t)ram)) {
+ /* Region exists with same address. Nothing to do. */
+ return;
+ }
+ } else {
+ if (!vhost_dev_find_reg(dev, start_addr, size)) {
+ /* Removing region that we don't access. Nothing to do. */
+ return;
+ }
+ }
+
+ vhost_dev_unassign_memory(dev, start_addr, size);
+ if (add) {
+ /* Add given mapping, merging adjacent regions if any */
+ vhost_dev_assign_memory(dev, start_addr, size, (uintptr_t)ram);
+ } else {
+ /* Remove old mapping for this memory, if any. */
+ vhost_dev_unassign_memory(dev, start_addr, size);
+ }
+ dev->mem_changed_start_addr = MIN(dev->mem_changed_start_addr, start_addr);
+ dev->mem_changed_end_addr = MAX(dev->mem_changed_end_addr, start_addr + size - 1);
+ dev->memory_changed = true;
+}
+
+static bool vhost_section(MemoryRegionSection *section)
+{
+ return memory_region_is_ram(section->mr);
+}
+
+static void vhost_begin(MemoryListener *listener)
+{
+ struct vhost_dev *dev = container_of(listener, struct vhost_dev,
+ memory_listener);
+ dev->mem_changed_end_addr = 0;
+ dev->mem_changed_start_addr = -1;
+}
+
+static void vhost_commit(MemoryListener *listener)
+{
+ struct vhost_dev *dev = container_of(listener, struct vhost_dev,
+ memory_listener);
+ hwaddr start_addr = 0;
+ ram_addr_t size = 0;
+ uint64_t log_size;
+ int r;
+
+ if (!dev->memory_changed) {
+ return;
+ }
+ if (!dev->started) {
+ return;
+ }
+ if (dev->mem_changed_start_addr > dev->mem_changed_end_addr) {
+ return;
+ }
+
+ if (dev->started) {
+ start_addr = dev->mem_changed_start_addr;
+ size = dev->mem_changed_end_addr - dev->mem_changed_start_addr + 1;
+
+ r = vhost_verify_ring_mappings(dev, start_addr, size);
+ assert(r >= 0);
+ }
+
+ if (!dev->log_enabled) {
+ r = dev->vhost_ops->vhost_call(dev, VHOST_SET_MEM_TABLE, dev->mem);
+ assert(r >= 0);
+ dev->memory_changed = false;
+ return;
+ }
+ log_size = vhost_get_log_size(dev);
+ /* We allocate an extra 4K bytes to log,
+ * to reduce the * number of reallocations. */
+#define VHOST_LOG_BUFFER (0x1000 / sizeof *dev->log)
+ /* To log more, must increase log size before table update. */
+ if (dev->log_size < log_size) {
+ vhost_dev_log_resize(dev, log_size + VHOST_LOG_BUFFER);
+ }
+ r = dev->vhost_ops->vhost_call(dev, VHOST_SET_MEM_TABLE, dev->mem);
+ assert(r >= 0);
+ /* To log less, can only decrease log size after table update. */
+ if (dev->log_size > log_size + VHOST_LOG_BUFFER) {
+ vhost_dev_log_resize(dev, log_size);
+ }
+ dev->memory_changed = false;
+}
+
+static void vhost_region_add(MemoryListener *listener,
+ MemoryRegionSection *section)
+{
+ struct vhost_dev *dev = container_of(listener, struct vhost_dev,
+ memory_listener);
+
+ if (!vhost_section(section)) {
+ return;
+ }
+
+ ++dev->n_mem_sections;
+ dev->mem_sections = g_renew(MemoryRegionSection, dev->mem_sections,
+ dev->n_mem_sections);
+ dev->mem_sections[dev->n_mem_sections - 1] = *section;
+ memory_region_ref(section->mr);
+ vhost_set_memory(listener, section, true);
+}
+
+static void vhost_region_del(MemoryListener *listener,
+ MemoryRegionSection *section)
+{
+ struct vhost_dev *dev = container_of(listener, struct vhost_dev,
+ memory_listener);
+ int i;
+
+ if (!vhost_section(section)) {
+ return;
+ }
+
+ vhost_set_memory(listener, section, false);
+ memory_region_unref(section->mr);
+ for (i = 0; i < dev->n_mem_sections; ++i) {
+ if (dev->mem_sections[i].offset_within_address_space
+ == section->offset_within_address_space) {
+ --dev->n_mem_sections;
+ memmove(&dev->mem_sections[i], &dev->mem_sections[i+1],
+ (dev->n_mem_sections - i) * sizeof(*dev->mem_sections));
+ break;
+ }
+ }
+}
+
+static void vhost_region_nop(MemoryListener *listener,
+ MemoryRegionSection *section)
+{
+}
+
+static int vhost_virtqueue_set_addr(struct vhost_dev *dev,
+ struct vhost_virtqueue *vq,
+ unsigned idx, bool enable_log)
+{
+ struct vhost_vring_addr addr = {
+ .index = idx,
+ .desc_user_addr = (uint64_t)(unsigned long)vq->desc,
+ .avail_user_addr = (uint64_t)(unsigned long)vq->avail,
+ .used_user_addr = (uint64_t)(unsigned long)vq->used,
+ .log_guest_addr = vq->used_phys,
+ .flags = enable_log ? (1 << VHOST_VRING_F_LOG) : 0,
+ };
+ int r = dev->vhost_ops->vhost_call(dev, VHOST_SET_VRING_ADDR, &addr);
+ if (r < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+static int vhost_dev_set_features(struct vhost_dev *dev, bool enable_log)
+{
+ uint64_t features = dev->acked_features;
+ int r;
+ if (enable_log) {
+ features |= 0x1ULL << VHOST_F_LOG_ALL;
+ }
+ r = dev->vhost_ops->vhost_call(dev, VHOST_SET_FEATURES, &features);
+ return r < 0 ? -errno : 0;
+}
+
+static int vhost_dev_set_log(struct vhost_dev *dev, bool enable_log)
+{
+ int r, t, i;
+ r = vhost_dev_set_features(dev, enable_log);
+ if (r < 0) {
+ goto err_features;
+ }
+ for (i = 0; i < dev->nvqs; ++i) {
+ r = vhost_virtqueue_set_addr(dev, dev->vqs + i, i,
+ enable_log);
+ if (r < 0) {
+ goto err_vq;
+ }
+ }
+ return 0;
+err_vq:
+ for (; i >= 0; --i) {
+ t = vhost_virtqueue_set_addr(dev, dev->vqs + i, i,
+ dev->log_enabled);
+ assert(t >= 0);
+ }
+ t = vhost_dev_set_features(dev, dev->log_enabled);
+ assert(t >= 0);
+err_features:
+ return r;
+}
+
+static int vhost_migration_log(MemoryListener *listener, int enable)
+{
+ struct vhost_dev *dev = container_of(listener, struct vhost_dev,
+ memory_listener);
+ int r;
+ if (!!enable == dev->log_enabled) {
+ return 0;
+ }
+ if (!dev->started) {
+ dev->log_enabled = enable;
+ return 0;
+ }
+ if (!enable) {
+ r = vhost_dev_set_log(dev, false);
+ if (r < 0) {
+ return r;
+ }
+ vhost_log_put(dev, false);
+ dev->log = NULL;
+ dev->log_size = 0;
+ } else {
+ vhost_dev_log_resize(dev, vhost_get_log_size(dev));
+ r = vhost_dev_set_log(dev, true);
+ if (r < 0) {
+ return r;
+ }
+ }
+ dev->log_enabled = enable;
+ return 0;
+}
+
+static void vhost_log_global_start(MemoryListener *listener)
+{
+ int r;
+
+ r = vhost_migration_log(listener, true);
+ if (r < 0) {
+ abort();
+ }
+}
+
+static void vhost_log_global_stop(MemoryListener *listener)
+{
+ int r;
+
+ r = vhost_migration_log(listener, false);
+ if (r < 0) {
+ abort();
+ }
+}
+
+static void vhost_log_start(MemoryListener *listener,
+ MemoryRegionSection *section,
+ int old, int new)
+{
+ /* FIXME: implement */
+}
+
+static void vhost_log_stop(MemoryListener *listener,
+ MemoryRegionSection *section,
+ int old, int new)
+{
+ /* FIXME: implement */
+}
+
+static int vhost_virtqueue_set_vring_endian_legacy(struct vhost_dev *dev,
+ bool is_big_endian,
+ int vhost_vq_index)
+{
+ struct vhost_vring_state s = {
+ .index = vhost_vq_index,
+ .num = is_big_endian
+ };
+
+ if (!dev->vhost_ops->vhost_call(dev, VHOST_SET_VRING_ENDIAN, &s)) {
+ return 0;
+ }
+
+ if (errno == ENOTTY) {
+ error_report("vhost does not support cross-endian");
+ return -ENOSYS;
+ }
+
+ return -errno;
+}
+
+static int vhost_virtqueue_start(struct vhost_dev *dev,
+ struct VirtIODevice *vdev,
+ struct vhost_virtqueue *vq,
+ unsigned idx)
+{
+ hwaddr s, l, a;
+ int r;
+ int vhost_vq_index = idx - dev->vq_index;
+ struct vhost_vring_file file = {
+ .index = vhost_vq_index
+ };
+ struct vhost_vring_state state = {
+ .index = vhost_vq_index
+ };
+ struct VirtQueue *vvq = virtio_get_queue(vdev, idx);
+
+ assert(idx >= dev->vq_index && idx < dev->vq_index + dev->nvqs);
+
+ vq->num = state.num = virtio_queue_get_num(vdev, idx);
+ r = dev->vhost_ops->vhost_call(dev, VHOST_SET_VRING_NUM, &state);
+ if (r) {
+ return -errno;
+ }
+
+ state.num = virtio_queue_get_last_avail_idx(vdev, idx);
+ r = dev->vhost_ops->vhost_call(dev, VHOST_SET_VRING_BASE, &state);
+ if (r) {
+ return -errno;
+ }
+
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1) &&
+ virtio_legacy_is_cross_endian(vdev)) {
+ r = vhost_virtqueue_set_vring_endian_legacy(dev,
+ virtio_is_big_endian(vdev),
+ vhost_vq_index);
+ if (r) {
+ return -errno;
+ }
+ }
+
+ s = l = virtio_queue_get_desc_size(vdev, idx);
+ a = virtio_queue_get_desc_addr(vdev, idx);
+ vq->desc = cpu_physical_memory_map(a, &l, 0);
+ if (!vq->desc || l != s) {
+ r = -ENOMEM;
+ goto fail_alloc_desc;
+ }
+ s = l = virtio_queue_get_avail_size(vdev, idx);
+ a = virtio_queue_get_avail_addr(vdev, idx);
+ vq->avail = cpu_physical_memory_map(a, &l, 0);
+ if (!vq->avail || l != s) {
+ r = -ENOMEM;
+ goto fail_alloc_avail;
+ }
+ vq->used_size = s = l = virtio_queue_get_used_size(vdev, idx);
+ vq->used_phys = a = virtio_queue_get_used_addr(vdev, idx);
+ vq->used = cpu_physical_memory_map(a, &l, 1);
+ if (!vq->used || l != s) {
+ r = -ENOMEM;
+ goto fail_alloc_used;
+ }
+
+ vq->ring_size = s = l = virtio_queue_get_ring_size(vdev, idx);
+ vq->ring_phys = a = virtio_queue_get_ring_addr(vdev, idx);
+ vq->ring = cpu_physical_memory_map(a, &l, 1);
+ if (!vq->ring || l != s) {
+ r = -ENOMEM;
+ goto fail_alloc_ring;
+ }
+
+ r = vhost_virtqueue_set_addr(dev, vq, vhost_vq_index, dev->log_enabled);
+ if (r < 0) {
+ r = -errno;
+ goto fail_alloc;
+ }
+
+ file.fd = event_notifier_get_fd(virtio_queue_get_host_notifier(vvq));
+ r = dev->vhost_ops->vhost_call(dev, VHOST_SET_VRING_KICK, &file);
+ if (r) {
+ r = -errno;
+ goto fail_kick;
+ }
+
+ /* Clear and discard previous events if any. */
+ event_notifier_test_and_clear(&vq->masked_notifier);
+
+ return 0;
+
+fail_kick:
+fail_alloc:
+ cpu_physical_memory_unmap(vq->ring, virtio_queue_get_ring_size(vdev, idx),
+ 0, 0);
+fail_alloc_ring:
+ cpu_physical_memory_unmap(vq->used, virtio_queue_get_used_size(vdev, idx),
+ 0, 0);
+fail_alloc_used:
+ cpu_physical_memory_unmap(vq->avail, virtio_queue_get_avail_size(vdev, idx),
+ 0, 0);
+fail_alloc_avail:
+ cpu_physical_memory_unmap(vq->desc, virtio_queue_get_desc_size(vdev, idx),
+ 0, 0);
+fail_alloc_desc:
+ return r;
+}
+
+static void vhost_virtqueue_stop(struct vhost_dev *dev,
+ struct VirtIODevice *vdev,
+ struct vhost_virtqueue *vq,
+ unsigned idx)
+{
+ int vhost_vq_index = idx - dev->vq_index;
+ struct vhost_vring_state state = {
+ .index = vhost_vq_index,
+ };
+ int r;
+ assert(idx >= dev->vq_index && idx < dev->vq_index + dev->nvqs);
+ r = dev->vhost_ops->vhost_call(dev, VHOST_GET_VRING_BASE, &state);
+ if (r < 0) {
+ fprintf(stderr, "vhost VQ %d ring restore failed: %d\n", idx, r);
+ fflush(stderr);
+ }
+ virtio_queue_set_last_avail_idx(vdev, idx, state.num);
+ virtio_queue_invalidate_signalled_used(vdev, idx);
+
+ /* In the cross-endian case, we need to reset the vring endianness to
+ * native as legacy devices expect so by default.
+ */
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1) &&
+ virtio_legacy_is_cross_endian(vdev)) {
+ r = vhost_virtqueue_set_vring_endian_legacy(dev,
+ !virtio_is_big_endian(vdev),
+ vhost_vq_index);
+ if (r < 0) {
+ error_report("failed to reset vring endianness");
+ }
+ }
+
+ assert (r >= 0);
+ cpu_physical_memory_unmap(vq->ring, virtio_queue_get_ring_size(vdev, idx),
+ 0, virtio_queue_get_ring_size(vdev, idx));
+ cpu_physical_memory_unmap(vq->used, virtio_queue_get_used_size(vdev, idx),
+ 1, virtio_queue_get_used_size(vdev, idx));
+ cpu_physical_memory_unmap(vq->avail, virtio_queue_get_avail_size(vdev, idx),
+ 0, virtio_queue_get_avail_size(vdev, idx));
+ cpu_physical_memory_unmap(vq->desc, virtio_queue_get_desc_size(vdev, idx),
+ 0, virtio_queue_get_desc_size(vdev, idx));
+}
+
+static void vhost_eventfd_add(MemoryListener *listener,
+ MemoryRegionSection *section,
+ bool match_data, uint64_t data, EventNotifier *e)
+{
+}
+
+static void vhost_eventfd_del(MemoryListener *listener,
+ MemoryRegionSection *section,
+ bool match_data, uint64_t data, EventNotifier *e)
+{
+}
+
+static int vhost_virtqueue_init(struct vhost_dev *dev,
+ struct vhost_virtqueue *vq, int n)
+{
+ struct vhost_vring_file file = {
+ .index = n,
+ };
+ int r = event_notifier_init(&vq->masked_notifier, 0);
+ if (r < 0) {
+ return r;
+ }
+
+ file.fd = event_notifier_get_fd(&vq->masked_notifier);
+ r = dev->vhost_ops->vhost_call(dev, VHOST_SET_VRING_CALL, &file);
+ if (r) {
+ r = -errno;
+ goto fail_call;
+ }
+ return 0;
+fail_call:
+ event_notifier_cleanup(&vq->masked_notifier);
+ return r;
+}
+
+static void vhost_virtqueue_cleanup(struct vhost_virtqueue *vq)
+{
+ event_notifier_cleanup(&vq->masked_notifier);
+}
+
+int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
+ VhostBackendType backend_type)
+{
+ uint64_t features;
+ int i, r;
+
+ if (vhost_set_backend_type(hdev, backend_type) < 0) {
+ close((uintptr_t)opaque);
+ return -1;
+ }
+
+ if (hdev->vhost_ops->vhost_backend_init(hdev, opaque) < 0) {
+ close((uintptr_t)opaque);
+ return -errno;
+ }
+
+ r = hdev->vhost_ops->vhost_call(hdev, VHOST_SET_OWNER, NULL);
+ if (r < 0) {
+ goto fail;
+ }
+
+ r = hdev->vhost_ops->vhost_call(hdev, VHOST_GET_FEATURES, &features);
+ if (r < 0) {
+ goto fail;
+ }
+
+ for (i = 0; i < hdev->nvqs; ++i) {
+ r = vhost_virtqueue_init(hdev, hdev->vqs + i, i);
+ if (r < 0) {
+ goto fail_vq;
+ }
+ }
+ hdev->features = features;
+
+ hdev->memory_listener = (MemoryListener) {
+ .begin = vhost_begin,
+ .commit = vhost_commit,
+ .region_add = vhost_region_add,
+ .region_del = vhost_region_del,
+ .region_nop = vhost_region_nop,
+ .log_start = vhost_log_start,
+ .log_stop = vhost_log_stop,
+ .log_sync = vhost_log_sync,
+ .log_global_start = vhost_log_global_start,
+ .log_global_stop = vhost_log_global_stop,
+ .eventfd_add = vhost_eventfd_add,
+ .eventfd_del = vhost_eventfd_del,
+ .priority = 10
+ };
+ hdev->migration_blocker = NULL;
+ if (!(hdev->features & (0x1ULL << VHOST_F_LOG_ALL))) {
+ error_setg(&hdev->migration_blocker,
+ "Migration disabled: vhost lacks VHOST_F_LOG_ALL feature.");
+ migrate_add_blocker(hdev->migration_blocker);
+ }
+ hdev->mem = g_malloc0(offsetof(struct vhost_memory, regions));
+ hdev->n_mem_sections = 0;
+ hdev->mem_sections = NULL;
+ hdev->log = NULL;
+ hdev->log_size = 0;
+ hdev->log_enabled = false;
+ hdev->started = false;
+ hdev->memory_changed = false;
+ memory_listener_register(&hdev->memory_listener, &address_space_memory);
+ return 0;
+fail_vq:
+ while (--i >= 0) {
+ vhost_virtqueue_cleanup(hdev->vqs + i);
+ }
+fail:
+ r = -errno;
+ hdev->vhost_ops->vhost_backend_cleanup(hdev);
+ return r;
+}
+
+void vhost_dev_cleanup(struct vhost_dev *hdev)
+{
+ int i;
+ for (i = 0; i < hdev->nvqs; ++i) {
+ vhost_virtqueue_cleanup(hdev->vqs + i);
+ }
+ memory_listener_unregister(&hdev->memory_listener);
+ if (hdev->migration_blocker) {
+ migrate_del_blocker(hdev->migration_blocker);
+ error_free(hdev->migration_blocker);
+ }
+ g_free(hdev->mem);
+ g_free(hdev->mem_sections);
+ hdev->vhost_ops->vhost_backend_cleanup(hdev);
+}
+
+/* Stop processing guest IO notifications in qemu.
+ * Start processing them in vhost in kernel.
+ */
+int vhost_dev_enable_notifiers(struct vhost_dev *hdev, VirtIODevice *vdev)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
+ VirtioBusState *vbus = VIRTIO_BUS(qbus);
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(vbus);
+ int i, r, e;
+ if (!k->set_host_notifier) {
+ fprintf(stderr, "binding does not support host notifiers\n");
+ r = -ENOSYS;
+ goto fail;
+ }
+
+ for (i = 0; i < hdev->nvqs; ++i) {
+ r = k->set_host_notifier(qbus->parent, hdev->vq_index + i, true);
+ if (r < 0) {
+ fprintf(stderr, "vhost VQ %d notifier binding failed: %d\n", i, -r);
+ goto fail_vq;
+ }
+ }
+
+ return 0;
+fail_vq:
+ while (--i >= 0) {
+ e = k->set_host_notifier(qbus->parent, hdev->vq_index + i, false);
+ if (e < 0) {
+ fprintf(stderr, "vhost VQ %d notifier cleanup error: %d\n", i, -r);
+ fflush(stderr);
+ }
+ assert (e >= 0);
+ }
+fail:
+ return r;
+}
+
+/* Stop processing guest IO notifications in vhost.
+ * Start processing them in qemu.
+ * This might actually run the qemu handlers right away,
+ * so virtio in qemu must be completely setup when this is called.
+ */
+void vhost_dev_disable_notifiers(struct vhost_dev *hdev, VirtIODevice *vdev)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
+ VirtioBusState *vbus = VIRTIO_BUS(qbus);
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(vbus);
+ int i, r;
+
+ for (i = 0; i < hdev->nvqs; ++i) {
+ r = k->set_host_notifier(qbus->parent, hdev->vq_index + i, false);
+ if (r < 0) {
+ fprintf(stderr, "vhost VQ %d notifier cleanup failed: %d\n", i, -r);
+ fflush(stderr);
+ }
+ assert (r >= 0);
+ }
+}
+
+/* Test and clear event pending status.
+ * Should be called after unmask to avoid losing events.
+ */
+bool vhost_virtqueue_pending(struct vhost_dev *hdev, int n)
+{
+ struct vhost_virtqueue *vq = hdev->vqs + n - hdev->vq_index;
+ assert(n >= hdev->vq_index && n < hdev->vq_index + hdev->nvqs);
+ return event_notifier_test_and_clear(&vq->masked_notifier);
+}
+
+/* Mask/unmask events from this vq. */
+void vhost_virtqueue_mask(struct vhost_dev *hdev, VirtIODevice *vdev, int n,
+ bool mask)
+{
+ struct VirtQueue *vvq = virtio_get_queue(vdev, n);
+ int r, index = n - hdev->vq_index;
+
+ assert(n >= hdev->vq_index && n < hdev->vq_index + hdev->nvqs);
+
+ struct vhost_vring_file file = {
+ .index = index
+ };
+ if (mask) {
+ file.fd = event_notifier_get_fd(&hdev->vqs[index].masked_notifier);
+ } else {
+ file.fd = event_notifier_get_fd(virtio_queue_get_guest_notifier(vvq));
+ }
+ r = hdev->vhost_ops->vhost_call(hdev, VHOST_SET_VRING_CALL, &file);
+ assert(r >= 0);
+}
+
+uint64_t vhost_get_features(struct vhost_dev *hdev, const int *feature_bits,
+ uint64_t features)
+{
+ const int *bit = feature_bits;
+ while (*bit != VHOST_INVALID_FEATURE_BIT) {
+ uint64_t bit_mask = (1ULL << *bit);
+ if (!(hdev->features & bit_mask)) {
+ features &= ~bit_mask;
+ }
+ bit++;
+ }
+ return features;
+}
+
+void vhost_ack_features(struct vhost_dev *hdev, const int *feature_bits,
+ uint64_t features)
+{
+ const int *bit = feature_bits;
+ while (*bit != VHOST_INVALID_FEATURE_BIT) {
+ uint64_t bit_mask = (1ULL << *bit);
+ if (features & bit_mask) {
+ hdev->acked_features |= bit_mask;
+ }
+ bit++;
+ }
+}
+
+/* Host notifiers must be enabled at this point. */
+int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev)
+{
+ int i, r;
+
+ hdev->started = true;
+
+ r = vhost_dev_set_features(hdev, hdev->log_enabled);
+ if (r < 0) {
+ goto fail_features;
+ }
+ r = hdev->vhost_ops->vhost_call(hdev, VHOST_SET_MEM_TABLE, hdev->mem);
+ if (r < 0) {
+ r = -errno;
+ goto fail_mem;
+ }
+ for (i = 0; i < hdev->nvqs; ++i) {
+ r = vhost_virtqueue_start(hdev,
+ vdev,
+ hdev->vqs + i,
+ hdev->vq_index + i);
+ if (r < 0) {
+ goto fail_vq;
+ }
+ }
+
+ if (hdev->log_enabled) {
+ uint64_t log_base;
+
+ hdev->log_size = vhost_get_log_size(hdev);
+ hdev->log = vhost_log_get(hdev->log_size);
+ log_base = (uintptr_t)hdev->log->log;
+ r = hdev->vhost_ops->vhost_call(hdev, VHOST_SET_LOG_BASE,
+ hdev->log_size ? &log_base : NULL);
+ if (r < 0) {
+ r = -errno;
+ goto fail_log;
+ }
+ }
+
+ return 0;
+fail_log:
+ vhost_log_put(hdev, false);
+fail_vq:
+ while (--i >= 0) {
+ vhost_virtqueue_stop(hdev,
+ vdev,
+ hdev->vqs + i,
+ hdev->vq_index + i);
+ }
+ i = hdev->nvqs;
+fail_mem:
+fail_features:
+
+ hdev->started = false;
+ return r;
+}
+
+/* Host notifiers must be enabled at this point. */
+void vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < hdev->nvqs; ++i) {
+ vhost_virtqueue_stop(hdev,
+ vdev,
+ hdev->vqs + i,
+ hdev->vq_index + i);
+ }
+
+ vhost_log_put(hdev, true);
+ hdev->started = false;
+ hdev->log = NULL;
+ hdev->log_size = 0;
+}
+
diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
new file mode 100644
index 00000000..c419b171
--- /dev/null
+++ b/hw/virtio/virtio-balloon.c
@@ -0,0 +1,463 @@
+/*
+ * Virtio Balloon Device
+ *
+ * Copyright IBM, Corp. 2008
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2011 Amit Shah <amit.shah@redhat.com>
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/iov.h"
+#include "qemu/timer.h"
+#include "qemu-common.h"
+#include "hw/virtio/virtio.h"
+#include "hw/i386/pc.h"
+#include "cpu.h"
+#include "sysemu/balloon.h"
+#include "hw/virtio/virtio-balloon.h"
+#include "sysemu/kvm.h"
+#include "exec/address-spaces.h"
+#include "qapi/visitor.h"
+#include "qapi-event.h"
+#include "trace.h"
+
+#if defined(__linux__)
+#include <sys/mman.h>
+#endif
+
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+
+static void balloon_page(void *addr, int deflate)
+{
+#if defined(__linux__)
+ if (!kvm_enabled() || kvm_has_sync_mmu())
+ qemu_madvise(addr, TARGET_PAGE_SIZE,
+ deflate ? QEMU_MADV_WILLNEED : QEMU_MADV_DONTNEED);
+#endif
+}
+
+static const char *balloon_stat_names[] = {
+ [VIRTIO_BALLOON_S_SWAP_IN] = "stat-swap-in",
+ [VIRTIO_BALLOON_S_SWAP_OUT] = "stat-swap-out",
+ [VIRTIO_BALLOON_S_MAJFLT] = "stat-major-faults",
+ [VIRTIO_BALLOON_S_MINFLT] = "stat-minor-faults",
+ [VIRTIO_BALLOON_S_MEMFREE] = "stat-free-memory",
+ [VIRTIO_BALLOON_S_MEMTOT] = "stat-total-memory",
+ [VIRTIO_BALLOON_S_NR] = NULL
+};
+
+/*
+ * reset_stats - Mark all items in the stats array as unset
+ *
+ * This function needs to be called at device initialization and before
+ * updating to a set of newly-generated stats. This will ensure that no
+ * stale values stick around in case the guest reports a subset of the supported
+ * statistics.
+ */
+static inline void reset_stats(VirtIOBalloon *dev)
+{
+ int i;
+ for (i = 0; i < VIRTIO_BALLOON_S_NR; dev->stats[i++] = -1);
+}
+
+static bool balloon_stats_supported(const VirtIOBalloon *s)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+ return virtio_vdev_has_feature(vdev, VIRTIO_BALLOON_F_STATS_VQ);
+}
+
+static bool balloon_stats_enabled(const VirtIOBalloon *s)
+{
+ return s->stats_poll_interval > 0;
+}
+
+static void balloon_stats_destroy_timer(VirtIOBalloon *s)
+{
+ if (balloon_stats_enabled(s)) {
+ timer_del(s->stats_timer);
+ timer_free(s->stats_timer);
+ s->stats_timer = NULL;
+ s->stats_poll_interval = 0;
+ }
+}
+
+static void balloon_stats_change_timer(VirtIOBalloon *s, int64_t secs)
+{
+ timer_mod(s->stats_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + secs * 1000);
+}
+
+static void balloon_stats_poll_cb(void *opaque)
+{
+ VirtIOBalloon *s = opaque;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ if (!balloon_stats_supported(s)) {
+ /* re-schedule */
+ balloon_stats_change_timer(s, s->stats_poll_interval);
+ return;
+ }
+
+ virtqueue_push(s->svq, &s->stats_vq_elem, s->stats_vq_offset);
+ virtio_notify(vdev, s->svq);
+}
+
+static void balloon_stats_get_all(Object *obj, struct Visitor *v,
+ void *opaque, const char *name, Error **errp)
+{
+ Error *err = NULL;
+ VirtIOBalloon *s = opaque;
+ int i;
+
+ visit_start_struct(v, NULL, "guest-stats", name, 0, &err);
+ if (err) {
+ goto out;
+ }
+ visit_type_int(v, &s->stats_last_update, "last-update", &err);
+ if (err) {
+ goto out_end;
+ }
+
+ visit_start_struct(v, NULL, NULL, "stats", 0, &err);
+ if (err) {
+ goto out_end;
+ }
+ for (i = 0; !err && i < VIRTIO_BALLOON_S_NR; i++) {
+ visit_type_int64(v, (int64_t *) &s->stats[i], balloon_stat_names[i],
+ &err);
+ }
+ error_propagate(errp, err);
+ err = NULL;
+ visit_end_struct(v, &err);
+
+out_end:
+ error_propagate(errp, err);
+ err = NULL;
+ visit_end_struct(v, &err);
+out:
+ error_propagate(errp, err);
+}
+
+static void balloon_stats_get_poll_interval(Object *obj, struct Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ VirtIOBalloon *s = opaque;
+ visit_type_int(v, &s->stats_poll_interval, name, errp);
+}
+
+static void balloon_stats_set_poll_interval(Object *obj, struct Visitor *v,
+ void *opaque, const char *name,
+ Error **errp)
+{
+ VirtIOBalloon *s = opaque;
+ Error *local_err = NULL;
+ int64_t value;
+
+ visit_type_int(v, &value, name, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (value < 0) {
+ error_setg(errp, "timer value must be greater than zero");
+ return;
+ }
+
+ if (value > UINT32_MAX) {
+ error_setg(errp, "timer value is too big");
+ return;
+ }
+
+ if (value == s->stats_poll_interval) {
+ return;
+ }
+
+ if (value == 0) {
+ /* timer=0 disables the timer */
+ balloon_stats_destroy_timer(s);
+ return;
+ }
+
+ if (balloon_stats_enabled(s)) {
+ /* timer interval change */
+ s->stats_poll_interval = value;
+ balloon_stats_change_timer(s, value);
+ return;
+ }
+
+ /* create a new timer */
+ g_assert(s->stats_timer == NULL);
+ s->stats_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, balloon_stats_poll_cb, s);
+ s->stats_poll_interval = value;
+ balloon_stats_change_timer(s, 0);
+}
+
+static void virtio_balloon_handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOBalloon *s = VIRTIO_BALLOON(vdev);
+ VirtQueueElement elem;
+ MemoryRegionSection section;
+
+ while (virtqueue_pop(vq, &elem)) {
+ size_t offset = 0;
+ uint32_t pfn;
+
+ while (iov_to_buf(elem.out_sg, elem.out_num, offset, &pfn, 4) == 4) {
+ ram_addr_t pa;
+ ram_addr_t addr;
+ int p = virtio_ldl_p(vdev, &pfn);
+
+ pa = (ram_addr_t) p << VIRTIO_BALLOON_PFN_SHIFT;
+ offset += 4;
+
+ /* FIXME: remove get_system_memory(), but how? */
+ section = memory_region_find(get_system_memory(), pa, 1);
+ if (!int128_nz(section.size) || !memory_region_is_ram(section.mr))
+ continue;
+
+ trace_virtio_balloon_handle_output(memory_region_name(section.mr),
+ pa);
+ /* Using memory_region_get_ram_ptr is bending the rules a bit, but
+ should be OK because we only want a single page. */
+ addr = section.offset_within_region;
+ balloon_page(memory_region_get_ram_ptr(section.mr) + addr,
+ !!(vq == s->dvq));
+ memory_region_unref(section.mr);
+ }
+
+ virtqueue_push(vq, &elem, offset);
+ virtio_notify(vdev, vq);
+ }
+}
+
+static void virtio_balloon_receive_stats(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOBalloon *s = VIRTIO_BALLOON(vdev);
+ VirtQueueElement *elem = &s->stats_vq_elem;
+ VirtIOBalloonStat stat;
+ size_t offset = 0;
+ qemu_timeval tv;
+
+ if (!virtqueue_pop(vq, elem)) {
+ goto out;
+ }
+
+ /* Initialize the stats to get rid of any stale values. This is only
+ * needed to handle the case where a guest supports fewer stats than it
+ * used to (ie. it has booted into an old kernel).
+ */
+ reset_stats(s);
+
+ while (iov_to_buf(elem->out_sg, elem->out_num, offset, &stat, sizeof(stat))
+ == sizeof(stat)) {
+ uint16_t tag = virtio_tswap16(vdev, stat.tag);
+ uint64_t val = virtio_tswap64(vdev, stat.val);
+
+ offset += sizeof(stat);
+ if (tag < VIRTIO_BALLOON_S_NR)
+ s->stats[tag] = val;
+ }
+ s->stats_vq_offset = offset;
+
+ if (qemu_gettimeofday(&tv) < 0) {
+ fprintf(stderr, "warning: %s: failed to get time of day\n", __func__);
+ goto out;
+ }
+
+ s->stats_last_update = tv.tv_sec;
+
+out:
+ if (balloon_stats_enabled(s)) {
+ balloon_stats_change_timer(s, s->stats_poll_interval);
+ }
+}
+
+static void virtio_balloon_get_config(VirtIODevice *vdev, uint8_t *config_data)
+{
+ VirtIOBalloon *dev = VIRTIO_BALLOON(vdev);
+ struct virtio_balloon_config config;
+
+ config.num_pages = cpu_to_le32(dev->num_pages);
+ config.actual = cpu_to_le32(dev->actual);
+
+ trace_virtio_balloon_get_config(config.num_pages, config.actual);
+ memcpy(config_data, &config, sizeof(struct virtio_balloon_config));
+}
+
+static void virtio_balloon_set_config(VirtIODevice *vdev,
+ const uint8_t *config_data)
+{
+ VirtIOBalloon *dev = VIRTIO_BALLOON(vdev);
+ struct virtio_balloon_config config;
+ uint32_t oldactual = dev->actual;
+ ram_addr_t vm_ram_size = get_current_ram_size();
+
+ memcpy(&config, config_data, sizeof(struct virtio_balloon_config));
+ dev->actual = le32_to_cpu(config.actual);
+ if (dev->actual != oldactual) {
+ qapi_event_send_balloon_change(vm_ram_size -
+ ((ram_addr_t) dev->actual << VIRTIO_BALLOON_PFN_SHIFT),
+ &error_abort);
+ }
+ trace_virtio_balloon_set_config(dev->actual, oldactual);
+}
+
+static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f,
+ Error **errp)
+{
+ VirtIOBalloon *dev = VIRTIO_BALLOON(vdev);
+ f |= dev->host_features;
+ virtio_add_feature(&f, VIRTIO_BALLOON_F_STATS_VQ);
+ return f;
+}
+
+static void virtio_balloon_stat(void *opaque, BalloonInfo *info)
+{
+ VirtIOBalloon *dev = opaque;
+ info->actual = get_current_ram_size() - ((uint64_t) dev->actual <<
+ VIRTIO_BALLOON_PFN_SHIFT);
+}
+
+static void virtio_balloon_to_target(void *opaque, ram_addr_t target)
+{
+ VirtIOBalloon *dev = VIRTIO_BALLOON(opaque);
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ ram_addr_t vm_ram_size = get_current_ram_size();
+
+ if (target > vm_ram_size) {
+ target = vm_ram_size;
+ }
+ if (target) {
+ dev->num_pages = (vm_ram_size - target) >> VIRTIO_BALLOON_PFN_SHIFT;
+ virtio_notify_config(vdev);
+ }
+ trace_virtio_balloon_to_target(target, dev->num_pages);
+}
+
+static void virtio_balloon_save(QEMUFile *f, void *opaque)
+{
+ virtio_save(VIRTIO_DEVICE(opaque), f);
+}
+
+static void virtio_balloon_save_device(VirtIODevice *vdev, QEMUFile *f)
+{
+ VirtIOBalloon *s = VIRTIO_BALLOON(vdev);
+
+ qemu_put_be32(f, s->num_pages);
+ qemu_put_be32(f, s->actual);
+}
+
+static int virtio_balloon_load(QEMUFile *f, void *opaque, int version_id)
+{
+ if (version_id != 1)
+ return -EINVAL;
+
+ return virtio_load(VIRTIO_DEVICE(opaque), f, version_id);
+}
+
+static int virtio_balloon_load_device(VirtIODevice *vdev, QEMUFile *f,
+ int version_id)
+{
+ VirtIOBalloon *s = VIRTIO_BALLOON(vdev);
+
+ s->num_pages = qemu_get_be32(f);
+ s->actual = qemu_get_be32(f);
+ return 0;
+}
+
+static void virtio_balloon_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOBalloon *s = VIRTIO_BALLOON(dev);
+ int ret;
+
+ virtio_init(vdev, "virtio-balloon", VIRTIO_ID_BALLOON,
+ sizeof(struct virtio_balloon_config));
+
+ ret = qemu_add_balloon_handler(virtio_balloon_to_target,
+ virtio_balloon_stat, s);
+
+ if (ret < 0) {
+ error_setg(errp, "Only one balloon device is supported");
+ virtio_cleanup(vdev);
+ return;
+ }
+
+ s->ivq = virtio_add_queue(vdev, 128, virtio_balloon_handle_output);
+ s->dvq = virtio_add_queue(vdev, 128, virtio_balloon_handle_output);
+ s->svq = virtio_add_queue(vdev, 128, virtio_balloon_receive_stats);
+
+ reset_stats(s);
+
+ register_savevm(dev, "virtio-balloon", -1, 1,
+ virtio_balloon_save, virtio_balloon_load, s);
+}
+
+static void virtio_balloon_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOBalloon *s = VIRTIO_BALLOON(dev);
+
+ balloon_stats_destroy_timer(s);
+ qemu_remove_balloon_handler(s);
+ unregister_savevm(dev, "virtio-balloon", s);
+ virtio_cleanup(vdev);
+}
+
+static void virtio_balloon_instance_init(Object *obj)
+{
+ VirtIOBalloon *s = VIRTIO_BALLOON(obj);
+
+ object_property_add(obj, "guest-stats", "guest statistics",
+ balloon_stats_get_all, NULL, NULL, s, NULL);
+
+ object_property_add(obj, "guest-stats-polling-interval", "int",
+ balloon_stats_get_poll_interval,
+ balloon_stats_set_poll_interval,
+ NULL, s, NULL);
+}
+
+static Property virtio_balloon_properties[] = {
+ DEFINE_PROP_BIT("deflate-on-oom", VirtIOBalloon, host_features,
+ VIRTIO_BALLOON_F_DEFLATE_ON_OOM, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_balloon_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ dc->props = virtio_balloon_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ vdc->realize = virtio_balloon_device_realize;
+ vdc->unrealize = virtio_balloon_device_unrealize;
+ vdc->get_config = virtio_balloon_get_config;
+ vdc->set_config = virtio_balloon_set_config;
+ vdc->get_features = virtio_balloon_get_features;
+ vdc->save = virtio_balloon_save_device;
+ vdc->load = virtio_balloon_load_device;
+}
+
+static const TypeInfo virtio_balloon_info = {
+ .name = TYPE_VIRTIO_BALLOON,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOBalloon),
+ .instance_init = virtio_balloon_instance_init,
+ .class_init = virtio_balloon_class_init,
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_balloon_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/virtio/virtio-bus.c b/hw/virtio/virtio-bus.c
new file mode 100644
index 00000000..febda76b
--- /dev/null
+++ b/hw/virtio/virtio-bus.c
@@ -0,0 +1,178 @@
+/*
+ * VirtioBus
+ *
+ * Copyright (C) 2012 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/hw.h"
+#include "qemu/error-report.h"
+#include "hw/qdev.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio.h"
+
+/* #define DEBUG_VIRTIO_BUS */
+
+#ifdef DEBUG_VIRTIO_BUS
+#define DPRINTF(fmt, ...) \
+do { printf("virtio_bus: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+/* A VirtIODevice is being plugged */
+void virtio_bus_device_plugged(VirtIODevice *vdev, Error **errp)
+{
+ DeviceState *qdev = DEVICE(vdev);
+ BusState *qbus = BUS(qdev_get_parent_bus(qdev));
+ VirtioBusState *bus = VIRTIO_BUS(qbus);
+ VirtioBusClass *klass = VIRTIO_BUS_GET_CLASS(bus);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
+
+ DPRINTF("%s: plug device.\n", qbus->name);
+
+ if (klass->device_plugged != NULL) {
+ klass->device_plugged(qbus->parent, errp);
+ }
+
+ /* Get the features of the plugged device. */
+ assert(vdc->get_features != NULL);
+ vdev->host_features = vdc->get_features(vdev, vdev->host_features,
+ errp);
+}
+
+/* Reset the virtio_bus */
+void virtio_bus_reset(VirtioBusState *bus)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(bus);
+
+ DPRINTF("%s: reset device.\n", BUS(bus)->name);
+ if (vdev != NULL) {
+ virtio_reset(vdev);
+ }
+}
+
+/* A VirtIODevice is being unplugged */
+void virtio_bus_device_unplugged(VirtIODevice *vdev)
+{
+ DeviceState *qdev = DEVICE(vdev);
+ BusState *qbus = BUS(qdev_get_parent_bus(qdev));
+ VirtioBusClass *klass = VIRTIO_BUS_GET_CLASS(qbus);
+
+ DPRINTF("%s: remove device.\n", qbus->name);
+
+ if (vdev != NULL) {
+ if (klass->device_unplugged != NULL) {
+ klass->device_unplugged(qbus->parent);
+ }
+ }
+}
+
+/* Get the device id of the plugged device. */
+uint16_t virtio_bus_get_vdev_id(VirtioBusState *bus)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(bus);
+ assert(vdev != NULL);
+ return vdev->device_id;
+}
+
+/* Get the config_len field of the plugged device. */
+size_t virtio_bus_get_vdev_config_len(VirtioBusState *bus)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(bus);
+ assert(vdev != NULL);
+ return vdev->config_len;
+}
+
+/* Get bad features of the plugged device. */
+uint32_t virtio_bus_get_vdev_bad_features(VirtioBusState *bus)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(bus);
+ VirtioDeviceClass *k;
+
+ assert(vdev != NULL);
+ k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ if (k->bad_features != NULL) {
+ return k->bad_features(vdev);
+ } else {
+ return 0;
+ }
+}
+
+/* Get config of the plugged device. */
+void virtio_bus_get_vdev_config(VirtioBusState *bus, uint8_t *config)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(bus);
+ VirtioDeviceClass *k;
+
+ assert(vdev != NULL);
+ k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ if (k->get_config != NULL) {
+ k->get_config(vdev, config);
+ }
+}
+
+/* Set config of the plugged device. */
+void virtio_bus_set_vdev_config(VirtioBusState *bus, uint8_t *config)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(bus);
+ VirtioDeviceClass *k;
+
+ assert(vdev != NULL);
+ k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ if (k->set_config != NULL) {
+ k->set_config(vdev, config);
+ }
+}
+
+static char *virtio_bus_get_dev_path(DeviceState *dev)
+{
+ BusState *bus = qdev_get_parent_bus(dev);
+ DeviceState *proxy = DEVICE(bus->parent);
+ return qdev_get_dev_path(proxy);
+}
+
+static char *virtio_bus_get_fw_dev_path(DeviceState *dev)
+{
+ return NULL;
+}
+
+static void virtio_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *bus_class = BUS_CLASS(klass);
+ bus_class->get_dev_path = virtio_bus_get_dev_path;
+ bus_class->get_fw_dev_path = virtio_bus_get_fw_dev_path;
+}
+
+static const TypeInfo virtio_bus_info = {
+ .name = TYPE_VIRTIO_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(VirtioBusState),
+ .abstract = true,
+ .class_size = sizeof(VirtioBusClass),
+ .class_init = virtio_bus_class_init
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_bus_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/virtio/virtio-mmio.c b/hw/virtio/virtio-mmio.c
new file mode 100644
index 00000000..18660b07
--- /dev/null
+++ b/hw/virtio/virtio-mmio.c
@@ -0,0 +1,579 @@
+/*
+ * Virtio MMIO bindings
+ *
+ * Copyright (c) 2011 Linaro Limited
+ *
+ * Author:
+ * Peter Maydell <peter.maydell@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/virtio/virtio.h"
+#include "qemu/host-utils.h"
+#include "sysemu/kvm.h"
+#include "hw/virtio/virtio-bus.h"
+#include "qemu/error-report.h"
+
+/* #define DEBUG_VIRTIO_MMIO */
+
+#ifdef DEBUG_VIRTIO_MMIO
+
+#define DPRINTF(fmt, ...) \
+do { printf("virtio_mmio: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+/* QOM macros */
+/* virtio-mmio-bus */
+#define TYPE_VIRTIO_MMIO_BUS "virtio-mmio-bus"
+#define VIRTIO_MMIO_BUS(obj) \
+ OBJECT_CHECK(VirtioBusState, (obj), TYPE_VIRTIO_MMIO_BUS)
+#define VIRTIO_MMIO_BUS_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VirtioBusClass, (obj), TYPE_VIRTIO_MMIO_BUS)
+#define VIRTIO_MMIO_BUS_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtioBusClass, (klass), TYPE_VIRTIO_MMIO_BUS)
+
+/* virtio-mmio */
+#define TYPE_VIRTIO_MMIO "virtio-mmio"
+#define VIRTIO_MMIO(obj) \
+ OBJECT_CHECK(VirtIOMMIOProxy, (obj), TYPE_VIRTIO_MMIO)
+
+/* Memory mapped register offsets */
+#define VIRTIO_MMIO_MAGIC 0x0
+#define VIRTIO_MMIO_VERSION 0x4
+#define VIRTIO_MMIO_DEVICEID 0x8
+#define VIRTIO_MMIO_VENDORID 0xc
+#define VIRTIO_MMIO_HOSTFEATURES 0x10
+#define VIRTIO_MMIO_HOSTFEATURESSEL 0x14
+#define VIRTIO_MMIO_GUESTFEATURES 0x20
+#define VIRTIO_MMIO_GUESTFEATURESSEL 0x24
+#define VIRTIO_MMIO_GUESTPAGESIZE 0x28
+#define VIRTIO_MMIO_QUEUESEL 0x30
+#define VIRTIO_MMIO_QUEUENUMMAX 0x34
+#define VIRTIO_MMIO_QUEUENUM 0x38
+#define VIRTIO_MMIO_QUEUEALIGN 0x3c
+#define VIRTIO_MMIO_QUEUEPFN 0x40
+#define VIRTIO_MMIO_QUEUENOTIFY 0x50
+#define VIRTIO_MMIO_INTERRUPTSTATUS 0x60
+#define VIRTIO_MMIO_INTERRUPTACK 0x64
+#define VIRTIO_MMIO_STATUS 0x70
+/* Device specific config space starts here */
+#define VIRTIO_MMIO_CONFIG 0x100
+
+#define VIRT_MAGIC 0x74726976 /* 'virt' */
+#define VIRT_VERSION 1
+#define VIRT_VENDOR 0x554D4551 /* 'QEMU' */
+
+typedef struct {
+ /* Generic */
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+ qemu_irq irq;
+ /* Guest accessible state needing migration and reset */
+ uint32_t host_features_sel;
+ uint32_t guest_features_sel;
+ uint32_t guest_page_shift;
+ /* virtio-bus */
+ VirtioBusState bus;
+ bool ioeventfd_disabled;
+ bool ioeventfd_started;
+} VirtIOMMIOProxy;
+
+static int virtio_mmio_set_host_notifier_internal(VirtIOMMIOProxy *proxy,
+ int n, bool assign,
+ bool set_handler)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_host_notifier(vq);
+ int r = 0;
+
+ if (assign) {
+ r = event_notifier_init(notifier, 1);
+ if (r < 0) {
+ error_report("%s: unable to init event notifier: %d",
+ __func__, r);
+ return r;
+ }
+ virtio_queue_set_host_notifier_fd_handler(vq, true, set_handler);
+ memory_region_add_eventfd(&proxy->iomem, VIRTIO_MMIO_QUEUENOTIFY, 4,
+ true, n, notifier);
+ } else {
+ memory_region_del_eventfd(&proxy->iomem, VIRTIO_MMIO_QUEUENOTIFY, 4,
+ true, n, notifier);
+ virtio_queue_set_host_notifier_fd_handler(vq, false, false);
+ event_notifier_cleanup(notifier);
+ }
+ return r;
+}
+
+static void virtio_mmio_start_ioeventfd(VirtIOMMIOProxy *proxy)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ int n, r;
+
+ if (!kvm_eventfds_enabled() ||
+ proxy->ioeventfd_disabled ||
+ proxy->ioeventfd_started) {
+ return;
+ }
+
+ for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+
+ r = virtio_mmio_set_host_notifier_internal(proxy, n, true, true);
+ if (r < 0) {
+ goto assign_error;
+ }
+ }
+ proxy->ioeventfd_started = true;
+ return;
+
+assign_error:
+ while (--n >= 0) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+
+ r = virtio_mmio_set_host_notifier_internal(proxy, n, false, false);
+ assert(r >= 0);
+ }
+ proxy->ioeventfd_started = false;
+ error_report("%s: failed. Fallback to a userspace (slower).", __func__);
+}
+
+static void virtio_mmio_stop_ioeventfd(VirtIOMMIOProxy *proxy)
+{
+ int r;
+ int n;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ if (!proxy->ioeventfd_started) {
+ return;
+ }
+
+ for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+
+ r = virtio_mmio_set_host_notifier_internal(proxy, n, false, false);
+ assert(r >= 0);
+ }
+ proxy->ioeventfd_started = false;
+}
+
+static uint64_t virtio_mmio_read(void *opaque, hwaddr offset, unsigned size)
+{
+ VirtIOMMIOProxy *proxy = (VirtIOMMIOProxy *)opaque;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ DPRINTF("virtio_mmio_read offset 0x%x\n", (int)offset);
+
+ if (!vdev) {
+ /* If no backend is present, we treat most registers as
+ * read-as-zero, except for the magic number, version and
+ * vendor ID. This is not strictly sanctioned by the virtio
+ * spec, but it allows us to provide transports with no backend
+ * plugged in which don't confuse Linux's virtio code: the
+ * probe won't complain about the bad magic number, but the
+ * device ID of zero means no backend will claim it.
+ */
+ switch (offset) {
+ case VIRTIO_MMIO_MAGIC:
+ return VIRT_MAGIC;
+ case VIRTIO_MMIO_VERSION:
+ return VIRT_VERSION;
+ case VIRTIO_MMIO_VENDORID:
+ return VIRT_VENDOR;
+ default:
+ return 0;
+ }
+ }
+
+ if (offset >= VIRTIO_MMIO_CONFIG) {
+ offset -= VIRTIO_MMIO_CONFIG;
+ switch (size) {
+ case 1:
+ return virtio_config_readb(vdev, offset);
+ case 2:
+ return virtio_config_readw(vdev, offset);
+ case 4:
+ return virtio_config_readl(vdev, offset);
+ default:
+ abort();
+ }
+ }
+ if (size != 4) {
+ DPRINTF("wrong size access to register!\n");
+ return 0;
+ }
+ switch (offset) {
+ case VIRTIO_MMIO_MAGIC:
+ return VIRT_MAGIC;
+ case VIRTIO_MMIO_VERSION:
+ return VIRT_VERSION;
+ case VIRTIO_MMIO_DEVICEID:
+ return vdev->device_id;
+ case VIRTIO_MMIO_VENDORID:
+ return VIRT_VENDOR;
+ case VIRTIO_MMIO_HOSTFEATURES:
+ if (proxy->host_features_sel) {
+ return 0;
+ }
+ return vdev->host_features;
+ case VIRTIO_MMIO_QUEUENUMMAX:
+ if (!virtio_queue_get_num(vdev, vdev->queue_sel)) {
+ return 0;
+ }
+ return VIRTQUEUE_MAX_SIZE;
+ case VIRTIO_MMIO_QUEUEPFN:
+ return virtio_queue_get_addr(vdev, vdev->queue_sel)
+ >> proxy->guest_page_shift;
+ case VIRTIO_MMIO_INTERRUPTSTATUS:
+ return vdev->isr;
+ case VIRTIO_MMIO_STATUS:
+ return vdev->status;
+ case VIRTIO_MMIO_HOSTFEATURESSEL:
+ case VIRTIO_MMIO_GUESTFEATURES:
+ case VIRTIO_MMIO_GUESTFEATURESSEL:
+ case VIRTIO_MMIO_GUESTPAGESIZE:
+ case VIRTIO_MMIO_QUEUESEL:
+ case VIRTIO_MMIO_QUEUENUM:
+ case VIRTIO_MMIO_QUEUEALIGN:
+ case VIRTIO_MMIO_QUEUENOTIFY:
+ case VIRTIO_MMIO_INTERRUPTACK:
+ DPRINTF("read of write-only register\n");
+ return 0;
+ default:
+ DPRINTF("bad register offset\n");
+ return 0;
+ }
+ return 0;
+}
+
+static void virtio_mmio_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ VirtIOMMIOProxy *proxy = (VirtIOMMIOProxy *)opaque;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ DPRINTF("virtio_mmio_write offset 0x%x value 0x%" PRIx64 "\n",
+ (int)offset, value);
+
+ if (!vdev) {
+ /* If no backend is present, we just make all registers
+ * write-ignored. This allows us to provide transports with
+ * no backend plugged in.
+ */
+ return;
+ }
+
+ if (offset >= VIRTIO_MMIO_CONFIG) {
+ offset -= VIRTIO_MMIO_CONFIG;
+ switch (size) {
+ case 1:
+ virtio_config_writeb(vdev, offset, value);
+ break;
+ case 2:
+ virtio_config_writew(vdev, offset, value);
+ break;
+ case 4:
+ virtio_config_writel(vdev, offset, value);
+ break;
+ default:
+ abort();
+ }
+ return;
+ }
+ if (size != 4) {
+ DPRINTF("wrong size access to register!\n");
+ return;
+ }
+ switch (offset) {
+ case VIRTIO_MMIO_HOSTFEATURESSEL:
+ proxy->host_features_sel = value;
+ break;
+ case VIRTIO_MMIO_GUESTFEATURES:
+ if (!proxy->guest_features_sel) {
+ virtio_set_features(vdev, value);
+ }
+ break;
+ case VIRTIO_MMIO_GUESTFEATURESSEL:
+ proxy->guest_features_sel = value;
+ break;
+ case VIRTIO_MMIO_GUESTPAGESIZE:
+ proxy->guest_page_shift = ctz32(value);
+ if (proxy->guest_page_shift > 31) {
+ proxy->guest_page_shift = 0;
+ }
+ DPRINTF("guest page size %" PRIx64 " shift %d\n", value,
+ proxy->guest_page_shift);
+ break;
+ case VIRTIO_MMIO_QUEUESEL:
+ if (value < VIRTIO_QUEUE_MAX) {
+ vdev->queue_sel = value;
+ }
+ break;
+ case VIRTIO_MMIO_QUEUENUM:
+ DPRINTF("mmio_queue write %d max %d\n", (int)value, VIRTQUEUE_MAX_SIZE);
+ virtio_queue_set_num(vdev, vdev->queue_sel, value);
+ /* Note: only call this function for legacy devices */
+ virtio_queue_update_rings(vdev, vdev->queue_sel);
+ break;
+ case VIRTIO_MMIO_QUEUEALIGN:
+ /* Note: this is only valid for legacy devices */
+ virtio_queue_set_align(vdev, vdev->queue_sel, value);
+ break;
+ case VIRTIO_MMIO_QUEUEPFN:
+ if (value == 0) {
+ virtio_reset(vdev);
+ } else {
+ virtio_queue_set_addr(vdev, vdev->queue_sel,
+ value << proxy->guest_page_shift);
+ }
+ break;
+ case VIRTIO_MMIO_QUEUENOTIFY:
+ if (value < VIRTIO_QUEUE_MAX) {
+ virtio_queue_notify(vdev, value);
+ }
+ break;
+ case VIRTIO_MMIO_INTERRUPTACK:
+ vdev->isr &= ~value;
+ virtio_update_irq(vdev);
+ break;
+ case VIRTIO_MMIO_STATUS:
+ if (!(value & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ virtio_mmio_stop_ioeventfd(proxy);
+ }
+
+ virtio_set_status(vdev, value & 0xff);
+
+ if (value & VIRTIO_CONFIG_S_DRIVER_OK) {
+ virtio_mmio_start_ioeventfd(proxy);
+ }
+
+ if (vdev->status == 0) {
+ virtio_reset(vdev);
+ }
+ break;
+ case VIRTIO_MMIO_MAGIC:
+ case VIRTIO_MMIO_VERSION:
+ case VIRTIO_MMIO_DEVICEID:
+ case VIRTIO_MMIO_VENDORID:
+ case VIRTIO_MMIO_HOSTFEATURES:
+ case VIRTIO_MMIO_QUEUENUMMAX:
+ case VIRTIO_MMIO_INTERRUPTSTATUS:
+ DPRINTF("write to readonly register\n");
+ break;
+
+ default:
+ DPRINTF("bad register offset\n");
+ }
+}
+
+static const MemoryRegionOps virtio_mem_ops = {
+ .read = virtio_mmio_read,
+ .write = virtio_mmio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void virtio_mmio_update_irq(DeviceState *opaque, uint16_t vector)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ int level;
+
+ if (!vdev) {
+ return;
+ }
+ level = (vdev->isr != 0);
+ DPRINTF("virtio_mmio setting IRQ %d\n", level);
+ qemu_set_irq(proxy->irq, level);
+}
+
+static int virtio_mmio_load_config(DeviceState *opaque, QEMUFile *f)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+
+ proxy->host_features_sel = qemu_get_be32(f);
+ proxy->guest_features_sel = qemu_get_be32(f);
+ proxy->guest_page_shift = qemu_get_be32(f);
+ return 0;
+}
+
+static void virtio_mmio_save_config(DeviceState *opaque, QEMUFile *f)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+
+ qemu_put_be32(f, proxy->host_features_sel);
+ qemu_put_be32(f, proxy->guest_features_sel);
+ qemu_put_be32(f, proxy->guest_page_shift);
+}
+
+static void virtio_mmio_reset(DeviceState *d)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(d);
+
+ virtio_mmio_stop_ioeventfd(proxy);
+ virtio_bus_reset(&proxy->bus);
+ proxy->host_features_sel = 0;
+ proxy->guest_features_sel = 0;
+ proxy->guest_page_shift = 0;
+}
+
+static int virtio_mmio_set_guest_notifier(DeviceState *d, int n, bool assign,
+ bool with_irqfd)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_guest_notifier(vq);
+
+ if (assign) {
+ int r = event_notifier_init(notifier, 0);
+ if (r < 0) {
+ return r;
+ }
+ virtio_queue_set_guest_notifier_fd_handler(vq, true, with_irqfd);
+ } else {
+ virtio_queue_set_guest_notifier_fd_handler(vq, false, with_irqfd);
+ event_notifier_cleanup(notifier);
+ }
+
+ if (vdc->guest_notifier_mask) {
+ vdc->guest_notifier_mask(vdev, n, !assign);
+ }
+
+ return 0;
+}
+
+static int virtio_mmio_set_guest_notifiers(DeviceState *d, int nvqs,
+ bool assign)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ /* TODO: need to check if kvm-arm supports irqfd */
+ bool with_irqfd = false;
+ int r, n;
+
+ nvqs = MIN(nvqs, VIRTIO_QUEUE_MAX);
+
+ for (n = 0; n < nvqs; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ break;
+ }
+
+ r = virtio_mmio_set_guest_notifier(d, n, assign, with_irqfd);
+ if (r < 0) {
+ goto assign_error;
+ }
+ }
+
+ return 0;
+
+assign_error:
+ /* We get here on assignment failure. Recover by undoing for VQs 0 .. n. */
+ assert(assign);
+ while (--n >= 0) {
+ virtio_mmio_set_guest_notifier(d, n, !assign, false);
+ }
+ return r;
+}
+
+static int virtio_mmio_set_host_notifier(DeviceState *opaque, int n,
+ bool assign)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
+
+ /* Stop using ioeventfd for virtqueue kick if the device starts using host
+ * notifiers. This makes it easy to avoid stepping on each others' toes.
+ */
+ proxy->ioeventfd_disabled = assign;
+ if (assign) {
+ virtio_mmio_stop_ioeventfd(proxy);
+ }
+ /* We don't need to start here: it's not needed because backend
+ * currently only stops on status change away from ok,
+ * reset, vmstop and such. If we do add code to start here,
+ * need to check vmstate, device state etc. */
+ return virtio_mmio_set_host_notifier_internal(proxy, n, assign, false);
+}
+
+/* virtio-mmio device */
+
+static void virtio_mmio_realizefn(DeviceState *d, Error **errp)
+{
+ VirtIOMMIOProxy *proxy = VIRTIO_MMIO(d);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(d);
+
+ qbus_create_inplace(&proxy->bus, sizeof(proxy->bus), TYPE_VIRTIO_MMIO_BUS,
+ d, NULL);
+ sysbus_init_irq(sbd, &proxy->irq);
+ memory_region_init_io(&proxy->iomem, OBJECT(d), &virtio_mem_ops, proxy,
+ TYPE_VIRTIO_MMIO, 0x200);
+ sysbus_init_mmio(sbd, &proxy->iomem);
+}
+
+static void virtio_mmio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = virtio_mmio_realizefn;
+ dc->reset = virtio_mmio_reset;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo virtio_mmio_info = {
+ .name = TYPE_VIRTIO_MMIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(VirtIOMMIOProxy),
+ .class_init = virtio_mmio_class_init,
+};
+
+/* virtio-mmio-bus. */
+
+static void virtio_mmio_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *bus_class = BUS_CLASS(klass);
+ VirtioBusClass *k = VIRTIO_BUS_CLASS(klass);
+
+ k->notify = virtio_mmio_update_irq;
+ k->save_config = virtio_mmio_save_config;
+ k->load_config = virtio_mmio_load_config;
+ k->set_host_notifier = virtio_mmio_set_host_notifier;
+ k->set_guest_notifiers = virtio_mmio_set_guest_notifiers;
+ k->has_variable_vring_alignment = true;
+ bus_class->max_dev = 1;
+}
+
+static const TypeInfo virtio_mmio_bus_info = {
+ .name = TYPE_VIRTIO_MMIO_BUS,
+ .parent = TYPE_VIRTIO_BUS,
+ .instance_size = sizeof(VirtioBusState),
+ .class_init = virtio_mmio_bus_class_init,
+};
+
+static void virtio_mmio_register_types(void)
+{
+ type_register_static(&virtio_mmio_bus_info);
+ type_register_static(&virtio_mmio_info);
+}
+
+type_init(virtio_mmio_register_types)
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
new file mode 100644
index 00000000..c024161f
--- /dev/null
+++ b/hw/virtio/virtio-pci.c
@@ -0,0 +1,2254 @@
+/*
+ * Virtio PCI Bindings
+ *
+ * Copyright IBM, Corp. 2007
+ * Copyright (c) 2009 CodeSourcery
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ * Paul Brook <paul@codesourcery.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include <inttypes.h>
+
+#include "standard-headers/linux/virtio_pci.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-blk.h"
+#include "hw/virtio/virtio-net.h"
+#include "hw/virtio/virtio-serial.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "hw/virtio/virtio-balloon.h"
+#include "hw/virtio/virtio-input.h"
+#include "hw/pci/pci.h"
+#include "qemu/error-report.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "hw/loader.h"
+#include "sysemu/kvm.h"
+#include "sysemu/block-backend.h"
+#include "virtio-pci.h"
+#include "qemu/range.h"
+#include "hw/virtio/virtio-bus.h"
+#include "qapi/visitor.h"
+
+#define VIRTIO_PCI_REGION_SIZE(dev) VIRTIO_PCI_CONFIG_OFF(msix_present(dev))
+
+#undef VIRTIO_PCI_CONFIG
+
+/* The remaining space is defined by each driver as the per-driver
+ * configuration space */
+#define VIRTIO_PCI_CONFIG_SIZE(dev) VIRTIO_PCI_CONFIG_OFF(msix_enabled(dev))
+
+static void virtio_pci_bus_new(VirtioBusState *bus, size_t bus_size,
+ VirtIOPCIProxy *dev);
+
+/* virtio device */
+/* DeviceState to VirtIOPCIProxy. For use off data-path. TODO: use QOM. */
+static inline VirtIOPCIProxy *to_virtio_pci_proxy(DeviceState *d)
+{
+ return container_of(d, VirtIOPCIProxy, pci_dev.qdev);
+}
+
+/* DeviceState to VirtIOPCIProxy. Note: used on datapath,
+ * be careful and test performance if you change this.
+ */
+static inline VirtIOPCIProxy *to_virtio_pci_proxy_fast(DeviceState *d)
+{
+ return container_of(d, VirtIOPCIProxy, pci_dev.qdev);
+}
+
+static void virtio_pci_notify(DeviceState *d, uint16_t vector)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy_fast(d);
+
+ if (msix_enabled(&proxy->pci_dev))
+ msix_notify(&proxy->pci_dev, vector);
+ else {
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ pci_set_irq(&proxy->pci_dev, vdev->isr & 1);
+ }
+}
+
+static void virtio_pci_save_config(DeviceState *d, QEMUFile *f)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ pci_device_save(&proxy->pci_dev, f);
+ msix_save(&proxy->pci_dev, f);
+ if (msix_present(&proxy->pci_dev))
+ qemu_put_be16(f, vdev->config_vector);
+}
+
+static void virtio_pci_save_queue(DeviceState *d, int n, QEMUFile *f)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ if (msix_present(&proxy->pci_dev))
+ qemu_put_be16(f, virtio_queue_vector(vdev, n));
+}
+
+static int virtio_pci_load_config(DeviceState *d, QEMUFile *f)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ int ret;
+ ret = pci_device_load(&proxy->pci_dev, f);
+ if (ret) {
+ return ret;
+ }
+ msix_unuse_all_vectors(&proxy->pci_dev);
+ msix_load(&proxy->pci_dev, f);
+ if (msix_present(&proxy->pci_dev)) {
+ qemu_get_be16s(f, &vdev->config_vector);
+ } else {
+ vdev->config_vector = VIRTIO_NO_VECTOR;
+ }
+ if (vdev->config_vector != VIRTIO_NO_VECTOR) {
+ return msix_vector_use(&proxy->pci_dev, vdev->config_vector);
+ }
+ return 0;
+}
+
+static int virtio_pci_load_queue(DeviceState *d, int n, QEMUFile *f)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ uint16_t vector;
+ if (msix_present(&proxy->pci_dev)) {
+ qemu_get_be16s(f, &vector);
+ } else {
+ vector = VIRTIO_NO_VECTOR;
+ }
+ virtio_queue_set_vector(vdev, n, vector);
+ if (vector != VIRTIO_NO_VECTOR) {
+ return msix_vector_use(&proxy->pci_dev, vector);
+ }
+ return 0;
+}
+
+#define QEMU_VIRTIO_PCI_QUEUE_MEM_MULT 0x1000
+
+static int virtio_pci_set_host_notifier_internal(VirtIOPCIProxy *proxy,
+ int n, bool assign, bool set_handler)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_host_notifier(vq);
+ bool legacy = !(proxy->flags & VIRTIO_PCI_FLAG_DISABLE_LEGACY);
+ bool modern = !(proxy->flags & VIRTIO_PCI_FLAG_DISABLE_MODERN);
+ MemoryRegion *modern_mr = &proxy->notify.mr;
+ MemoryRegion *legacy_mr = &proxy->bar;
+ hwaddr modern_addr = QEMU_VIRTIO_PCI_QUEUE_MEM_MULT *
+ virtio_get_queue_index(vq);
+ hwaddr legacy_addr = VIRTIO_PCI_QUEUE_NOTIFY;
+ int r = 0;
+
+ if (assign) {
+ r = event_notifier_init(notifier, 1);
+ if (r < 0) {
+ error_report("%s: unable to init event notifier: %d",
+ __func__, r);
+ return r;
+ }
+ virtio_queue_set_host_notifier_fd_handler(vq, true, set_handler);
+ if (modern) {
+ memory_region_add_eventfd(modern_mr, modern_addr, 2,
+ true, n, notifier);
+ }
+ if (legacy) {
+ memory_region_add_eventfd(legacy_mr, legacy_addr, 2,
+ true, n, notifier);
+ }
+ } else {
+ if (modern) {
+ memory_region_del_eventfd(modern_mr, modern_addr, 2,
+ true, n, notifier);
+ }
+ if (legacy) {
+ memory_region_del_eventfd(legacy_mr, legacy_addr, 2,
+ true, n, notifier);
+ }
+ virtio_queue_set_host_notifier_fd_handler(vq, false, false);
+ event_notifier_cleanup(notifier);
+ }
+ return r;
+}
+
+static void virtio_pci_start_ioeventfd(VirtIOPCIProxy *proxy)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ int n, r;
+
+ if (!(proxy->flags & VIRTIO_PCI_FLAG_USE_IOEVENTFD) ||
+ proxy->ioeventfd_disabled ||
+ proxy->ioeventfd_started) {
+ return;
+ }
+
+ for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+
+ r = virtio_pci_set_host_notifier_internal(proxy, n, true, true);
+ if (r < 0) {
+ goto assign_error;
+ }
+ }
+ proxy->ioeventfd_started = true;
+ return;
+
+assign_error:
+ while (--n >= 0) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+
+ r = virtio_pci_set_host_notifier_internal(proxy, n, false, false);
+ assert(r >= 0);
+ }
+ proxy->ioeventfd_started = false;
+ error_report("%s: failed. Fallback to a userspace (slower).", __func__);
+}
+
+static void virtio_pci_stop_ioeventfd(VirtIOPCIProxy *proxy)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ int r;
+ int n;
+
+ if (!proxy->ioeventfd_started) {
+ return;
+ }
+
+ for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+
+ r = virtio_pci_set_host_notifier_internal(proxy, n, false, false);
+ assert(r >= 0);
+ }
+ proxy->ioeventfd_started = false;
+}
+
+static void virtio_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ VirtIOPCIProxy *proxy = opaque;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ hwaddr pa;
+
+ switch (addr) {
+ case VIRTIO_PCI_GUEST_FEATURES:
+ /* Guest does not negotiate properly? We have to assume nothing. */
+ if (val & (1 << VIRTIO_F_BAD_FEATURE)) {
+ val = virtio_bus_get_vdev_bad_features(&proxy->bus);
+ }
+ virtio_set_features(vdev, val);
+ break;
+ case VIRTIO_PCI_QUEUE_PFN:
+ pa = (hwaddr)val << VIRTIO_PCI_QUEUE_ADDR_SHIFT;
+ if (pa == 0) {
+ virtio_pci_stop_ioeventfd(proxy);
+ virtio_reset(vdev);
+ msix_unuse_all_vectors(&proxy->pci_dev);
+ }
+ else
+ virtio_queue_set_addr(vdev, vdev->queue_sel, pa);
+ break;
+ case VIRTIO_PCI_QUEUE_SEL:
+ if (val < VIRTIO_QUEUE_MAX)
+ vdev->queue_sel = val;
+ break;
+ case VIRTIO_PCI_QUEUE_NOTIFY:
+ if (val < VIRTIO_QUEUE_MAX) {
+ virtio_queue_notify(vdev, val);
+ }
+ break;
+ case VIRTIO_PCI_STATUS:
+ if (!(val & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ virtio_pci_stop_ioeventfd(proxy);
+ }
+
+ virtio_set_status(vdev, val & 0xFF);
+
+ if (val & VIRTIO_CONFIG_S_DRIVER_OK) {
+ virtio_pci_start_ioeventfd(proxy);
+ }
+
+ if (vdev->status == 0) {
+ virtio_reset(vdev);
+ msix_unuse_all_vectors(&proxy->pci_dev);
+ }
+
+ /* Linux before 2.6.34 drives the device without enabling
+ the PCI device bus master bit. Enable it automatically
+ for the guest. This is a PCI spec violation but so is
+ initiating DMA with bus master bit clear. */
+ if (val == (VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER)) {
+ pci_default_write_config(&proxy->pci_dev, PCI_COMMAND,
+ proxy->pci_dev.config[PCI_COMMAND] |
+ PCI_COMMAND_MASTER, 1);
+ }
+ break;
+ case VIRTIO_MSI_CONFIG_VECTOR:
+ msix_vector_unuse(&proxy->pci_dev, vdev->config_vector);
+ /* Make it possible for guest to discover an error took place. */
+ if (msix_vector_use(&proxy->pci_dev, val) < 0)
+ val = VIRTIO_NO_VECTOR;
+ vdev->config_vector = val;
+ break;
+ case VIRTIO_MSI_QUEUE_VECTOR:
+ msix_vector_unuse(&proxy->pci_dev,
+ virtio_queue_vector(vdev, vdev->queue_sel));
+ /* Make it possible for guest to discover an error took place. */
+ if (msix_vector_use(&proxy->pci_dev, val) < 0)
+ val = VIRTIO_NO_VECTOR;
+ virtio_queue_set_vector(vdev, vdev->queue_sel, val);
+ break;
+ default:
+ error_report("%s: unexpected address 0x%x value 0x%x",
+ __func__, addr, val);
+ break;
+ }
+}
+
+static uint32_t virtio_ioport_read(VirtIOPCIProxy *proxy, uint32_t addr)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ uint32_t ret = 0xFFFFFFFF;
+
+ switch (addr) {
+ case VIRTIO_PCI_HOST_FEATURES:
+ ret = vdev->host_features;
+ break;
+ case VIRTIO_PCI_GUEST_FEATURES:
+ ret = vdev->guest_features;
+ break;
+ case VIRTIO_PCI_QUEUE_PFN:
+ ret = virtio_queue_get_addr(vdev, vdev->queue_sel)
+ >> VIRTIO_PCI_QUEUE_ADDR_SHIFT;
+ break;
+ case VIRTIO_PCI_QUEUE_NUM:
+ ret = virtio_queue_get_num(vdev, vdev->queue_sel);
+ break;
+ case VIRTIO_PCI_QUEUE_SEL:
+ ret = vdev->queue_sel;
+ break;
+ case VIRTIO_PCI_STATUS:
+ ret = vdev->status;
+ break;
+ case VIRTIO_PCI_ISR:
+ /* reading from the ISR also clears it. */
+ ret = vdev->isr;
+ vdev->isr = 0;
+ pci_irq_deassert(&proxy->pci_dev);
+ break;
+ case VIRTIO_MSI_CONFIG_VECTOR:
+ ret = vdev->config_vector;
+ break;
+ case VIRTIO_MSI_QUEUE_VECTOR:
+ ret = virtio_queue_vector(vdev, vdev->queue_sel);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static uint64_t virtio_pci_config_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ VirtIOPCIProxy *proxy = opaque;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ uint32_t config = VIRTIO_PCI_CONFIG_SIZE(&proxy->pci_dev);
+ uint64_t val = 0;
+ if (addr < config) {
+ return virtio_ioport_read(proxy, addr);
+ }
+ addr -= config;
+
+ switch (size) {
+ case 1:
+ val = virtio_config_readb(vdev, addr);
+ break;
+ case 2:
+ val = virtio_config_readw(vdev, addr);
+ if (virtio_is_big_endian(vdev)) {
+ val = bswap16(val);
+ }
+ break;
+ case 4:
+ val = virtio_config_readl(vdev, addr);
+ if (virtio_is_big_endian(vdev)) {
+ val = bswap32(val);
+ }
+ break;
+ }
+ return val;
+}
+
+static void virtio_pci_config_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VirtIOPCIProxy *proxy = opaque;
+ uint32_t config = VIRTIO_PCI_CONFIG_SIZE(&proxy->pci_dev);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ if (addr < config) {
+ virtio_ioport_write(proxy, addr, val);
+ return;
+ }
+ addr -= config;
+ /*
+ * Virtio-PCI is odd. Ioports are LE but config space is target native
+ * endian.
+ */
+ switch (size) {
+ case 1:
+ virtio_config_writeb(vdev, addr, val);
+ break;
+ case 2:
+ if (virtio_is_big_endian(vdev)) {
+ val = bswap16(val);
+ }
+ virtio_config_writew(vdev, addr, val);
+ break;
+ case 4:
+ if (virtio_is_big_endian(vdev)) {
+ val = bswap32(val);
+ }
+ virtio_config_writel(vdev, addr, val);
+ break;
+ }
+}
+
+static const MemoryRegionOps virtio_pci_config_ops = {
+ .read = virtio_pci_config_read,
+ .write = virtio_pci_config_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/* Below are generic functions to do memcpy from/to an address space,
+ * without byteswaps, with input validation.
+ *
+ * As regular address_space_* APIs all do some kind of byteswap at least for
+ * some host/target combinations, we are forced to explicitly convert to a
+ * known-endianness integer value.
+ * It doesn't really matter which endian format to go through, so the code
+ * below selects the endian that causes the least amount of work on the given
+ * host.
+ *
+ * Note: host pointer must be aligned.
+ */
+static
+void virtio_address_space_write(AddressSpace *as, hwaddr addr,
+ const uint8_t *buf, int len)
+{
+ uint32_t val;
+
+ /* address_space_* APIs assume an aligned address.
+ * As address is under guest control, handle illegal values.
+ */
+ addr &= ~(len - 1);
+
+ /* Make sure caller aligned buf properly */
+ assert(!(((uintptr_t)buf) & (len - 1)));
+
+ switch (len) {
+ case 1:
+ val = pci_get_byte(buf);
+ address_space_stb(as, addr, val, MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ case 2:
+ val = pci_get_word(buf);
+ address_space_stw_le(as, addr, val, MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ case 4:
+ val = pci_get_long(buf);
+ address_space_stl_le(as, addr, val, MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ default:
+ /* As length is under guest control, handle illegal values. */
+ break;
+ }
+}
+
+static void
+virtio_address_space_read(AddressSpace *as, hwaddr addr, uint8_t *buf, int len)
+{
+ uint32_t val;
+
+ /* address_space_* APIs assume an aligned address.
+ * As address is under guest control, handle illegal values.
+ */
+ addr &= ~(len - 1);
+
+ /* Make sure caller aligned buf properly */
+ assert(!(((uintptr_t)buf) & (len - 1)));
+
+ switch (len) {
+ case 1:
+ val = address_space_ldub(as, addr, MEMTXATTRS_UNSPECIFIED, NULL);
+ pci_set_byte(buf, val);
+ break;
+ case 2:
+ val = address_space_lduw_le(as, addr, MEMTXATTRS_UNSPECIFIED, NULL);
+ pci_set_word(buf, val);
+ break;
+ case 4:
+ val = address_space_ldl_le(as, addr, MEMTXATTRS_UNSPECIFIED, NULL);
+ pci_set_long(buf, val);
+ break;
+ default:
+ /* As length is under guest control, handle illegal values. */
+ break;
+ }
+}
+
+static void virtio_write_config(PCIDevice *pci_dev, uint32_t address,
+ uint32_t val, int len)
+{
+ VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ struct virtio_pci_cfg_cap *cfg;
+
+ pci_default_write_config(pci_dev, address, val, len);
+
+ if (range_covers_byte(address, len, PCI_COMMAND) &&
+ !(pci_dev->config[PCI_COMMAND] & PCI_COMMAND_MASTER)) {
+ virtio_pci_stop_ioeventfd(proxy);
+ virtio_set_status(vdev, vdev->status & ~VIRTIO_CONFIG_S_DRIVER_OK);
+ }
+
+ if (proxy->config_cap &&
+ ranges_overlap(address, len, proxy->config_cap + offsetof(struct virtio_pci_cfg_cap,
+ pci_cfg_data),
+ sizeof cfg->pci_cfg_data)) {
+ uint32_t off;
+ uint32_t len;
+
+ cfg = (void *)(proxy->pci_dev.config + proxy->config_cap);
+ off = le32_to_cpu(cfg->cap.offset);
+ len = le32_to_cpu(cfg->cap.length);
+
+ if (len == 1 || len == 2 || len == 4) {
+ assert(len <= sizeof cfg->pci_cfg_data);
+ virtio_address_space_write(&proxy->modern_as, off,
+ cfg->pci_cfg_data, len);
+ }
+ }
+}
+
+static uint32_t virtio_read_config(PCIDevice *pci_dev,
+ uint32_t address, int len)
+{
+ VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev);
+ struct virtio_pci_cfg_cap *cfg;
+
+ if (proxy->config_cap &&
+ ranges_overlap(address, len, proxy->config_cap + offsetof(struct virtio_pci_cfg_cap,
+ pci_cfg_data),
+ sizeof cfg->pci_cfg_data)) {
+ uint32_t off;
+ uint32_t len;
+
+ cfg = (void *)(proxy->pci_dev.config + proxy->config_cap);
+ off = le32_to_cpu(cfg->cap.offset);
+ len = le32_to_cpu(cfg->cap.length);
+
+ if (len == 1 || len == 2 || len == 4) {
+ assert(len <= sizeof cfg->pci_cfg_data);
+ virtio_address_space_read(&proxy->modern_as, off,
+ cfg->pci_cfg_data, len);
+ }
+ }
+
+ return pci_default_read_config(pci_dev, address, len);
+}
+
+static int kvm_virtio_pci_vq_vector_use(VirtIOPCIProxy *proxy,
+ unsigned int queue_no,
+ unsigned int vector,
+ MSIMessage msg)
+{
+ VirtIOIRQFD *irqfd = &proxy->vector_irqfd[vector];
+ int ret;
+
+ if (irqfd->users == 0) {
+ ret = kvm_irqchip_add_msi_route(kvm_state, msg);
+ if (ret < 0) {
+ return ret;
+ }
+ irqfd->virq = ret;
+ }
+ irqfd->users++;
+ return 0;
+}
+
+static void kvm_virtio_pci_vq_vector_release(VirtIOPCIProxy *proxy,
+ unsigned int vector)
+{
+ VirtIOIRQFD *irqfd = &proxy->vector_irqfd[vector];
+ if (--irqfd->users == 0) {
+ kvm_irqchip_release_virq(kvm_state, irqfd->virq);
+ }
+}
+
+static int kvm_virtio_pci_irqfd_use(VirtIOPCIProxy *proxy,
+ unsigned int queue_no,
+ unsigned int vector)
+{
+ VirtIOIRQFD *irqfd = &proxy->vector_irqfd[vector];
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtQueue *vq = virtio_get_queue(vdev, queue_no);
+ EventNotifier *n = virtio_queue_get_guest_notifier(vq);
+ int ret;
+ ret = kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, NULL, irqfd->virq);
+ return ret;
+}
+
+static void kvm_virtio_pci_irqfd_release(VirtIOPCIProxy *proxy,
+ unsigned int queue_no,
+ unsigned int vector)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtQueue *vq = virtio_get_queue(vdev, queue_no);
+ EventNotifier *n = virtio_queue_get_guest_notifier(vq);
+ VirtIOIRQFD *irqfd = &proxy->vector_irqfd[vector];
+ int ret;
+
+ ret = kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, n, irqfd->virq);
+ assert(ret == 0);
+}
+
+static int kvm_virtio_pci_vector_use(VirtIOPCIProxy *proxy, int nvqs)
+{
+ PCIDevice *dev = &proxy->pci_dev;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ unsigned int vector;
+ int ret, queue_no;
+ MSIMessage msg;
+
+ for (queue_no = 0; queue_no < nvqs; queue_no++) {
+ if (!virtio_queue_get_num(vdev, queue_no)) {
+ break;
+ }
+ vector = virtio_queue_vector(vdev, queue_no);
+ if (vector >= msix_nr_vectors_allocated(dev)) {
+ continue;
+ }
+ msg = msix_get_message(dev, vector);
+ ret = kvm_virtio_pci_vq_vector_use(proxy, queue_no, vector, msg);
+ if (ret < 0) {
+ goto undo;
+ }
+ /* If guest supports masking, set up irqfd now.
+ * Otherwise, delay until unmasked in the frontend.
+ */
+ if (k->guest_notifier_mask) {
+ ret = kvm_virtio_pci_irqfd_use(proxy, queue_no, vector);
+ if (ret < 0) {
+ kvm_virtio_pci_vq_vector_release(proxy, vector);
+ goto undo;
+ }
+ }
+ }
+ return 0;
+
+undo:
+ while (--queue_no >= 0) {
+ vector = virtio_queue_vector(vdev, queue_no);
+ if (vector >= msix_nr_vectors_allocated(dev)) {
+ continue;
+ }
+ if (k->guest_notifier_mask) {
+ kvm_virtio_pci_irqfd_release(proxy, queue_no, vector);
+ }
+ kvm_virtio_pci_vq_vector_release(proxy, vector);
+ }
+ return ret;
+}
+
+static void kvm_virtio_pci_vector_release(VirtIOPCIProxy *proxy, int nvqs)
+{
+ PCIDevice *dev = &proxy->pci_dev;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ unsigned int vector;
+ int queue_no;
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+
+ for (queue_no = 0; queue_no < nvqs; queue_no++) {
+ if (!virtio_queue_get_num(vdev, queue_no)) {
+ break;
+ }
+ vector = virtio_queue_vector(vdev, queue_no);
+ if (vector >= msix_nr_vectors_allocated(dev)) {
+ continue;
+ }
+ /* If guest supports masking, clean up irqfd now.
+ * Otherwise, it was cleaned when masked in the frontend.
+ */
+ if (k->guest_notifier_mask) {
+ kvm_virtio_pci_irqfd_release(proxy, queue_no, vector);
+ }
+ kvm_virtio_pci_vq_vector_release(proxy, vector);
+ }
+}
+
+static int virtio_pci_vq_vector_unmask(VirtIOPCIProxy *proxy,
+ unsigned int queue_no,
+ unsigned int vector,
+ MSIMessage msg)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ VirtQueue *vq = virtio_get_queue(vdev, queue_no);
+ EventNotifier *n = virtio_queue_get_guest_notifier(vq);
+ VirtIOIRQFD *irqfd;
+ int ret = 0;
+
+ if (proxy->vector_irqfd) {
+ irqfd = &proxy->vector_irqfd[vector];
+ if (irqfd->msg.data != msg.data || irqfd->msg.address != msg.address) {
+ ret = kvm_irqchip_update_msi_route(kvm_state, irqfd->virq, msg);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+ }
+
+ /* If guest supports masking, irqfd is already setup, unmask it.
+ * Otherwise, set it up now.
+ */
+ if (k->guest_notifier_mask) {
+ k->guest_notifier_mask(vdev, queue_no, false);
+ /* Test after unmasking to avoid losing events. */
+ if (k->guest_notifier_pending &&
+ k->guest_notifier_pending(vdev, queue_no)) {
+ event_notifier_set(n);
+ }
+ } else {
+ ret = kvm_virtio_pci_irqfd_use(proxy, queue_no, vector);
+ }
+ return ret;
+}
+
+static void virtio_pci_vq_vector_mask(VirtIOPCIProxy *proxy,
+ unsigned int queue_no,
+ unsigned int vector)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+
+ /* If guest supports masking, keep irqfd but mask it.
+ * Otherwise, clean it up now.
+ */
+ if (k->guest_notifier_mask) {
+ k->guest_notifier_mask(vdev, queue_no, true);
+ } else {
+ kvm_virtio_pci_irqfd_release(proxy, queue_no, vector);
+ }
+}
+
+static int virtio_pci_vector_unmask(PCIDevice *dev, unsigned vector,
+ MSIMessage msg)
+{
+ VirtIOPCIProxy *proxy = container_of(dev, VirtIOPCIProxy, pci_dev);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtQueue *vq = virtio_vector_first_queue(vdev, vector);
+ int ret, index, unmasked = 0;
+
+ while (vq) {
+ index = virtio_get_queue_index(vq);
+ if (!virtio_queue_get_num(vdev, index)) {
+ break;
+ }
+ if (index < proxy->nvqs_with_notifiers) {
+ ret = virtio_pci_vq_vector_unmask(proxy, index, vector, msg);
+ if (ret < 0) {
+ goto undo;
+ }
+ ++unmasked;
+ }
+ vq = virtio_vector_next_queue(vq);
+ }
+
+ return 0;
+
+undo:
+ vq = virtio_vector_first_queue(vdev, vector);
+ while (vq && unmasked >= 0) {
+ index = virtio_get_queue_index(vq);
+ if (index < proxy->nvqs_with_notifiers) {
+ virtio_pci_vq_vector_mask(proxy, index, vector);
+ --unmasked;
+ }
+ vq = virtio_vector_next_queue(vq);
+ }
+ return ret;
+}
+
+static void virtio_pci_vector_mask(PCIDevice *dev, unsigned vector)
+{
+ VirtIOPCIProxy *proxy = container_of(dev, VirtIOPCIProxy, pci_dev);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtQueue *vq = virtio_vector_first_queue(vdev, vector);
+ int index;
+
+ while (vq) {
+ index = virtio_get_queue_index(vq);
+ if (!virtio_queue_get_num(vdev, index)) {
+ break;
+ }
+ if (index < proxy->nvqs_with_notifiers) {
+ virtio_pci_vq_vector_mask(proxy, index, vector);
+ }
+ vq = virtio_vector_next_queue(vq);
+ }
+}
+
+static void virtio_pci_vector_poll(PCIDevice *dev,
+ unsigned int vector_start,
+ unsigned int vector_end)
+{
+ VirtIOPCIProxy *proxy = container_of(dev, VirtIOPCIProxy, pci_dev);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ int queue_no;
+ unsigned int vector;
+ EventNotifier *notifier;
+ VirtQueue *vq;
+
+ for (queue_no = 0; queue_no < proxy->nvqs_with_notifiers; queue_no++) {
+ if (!virtio_queue_get_num(vdev, queue_no)) {
+ break;
+ }
+ vector = virtio_queue_vector(vdev, queue_no);
+ if (vector < vector_start || vector >= vector_end ||
+ !msix_is_masked(dev, vector)) {
+ continue;
+ }
+ vq = virtio_get_queue(vdev, queue_no);
+ notifier = virtio_queue_get_guest_notifier(vq);
+ if (k->guest_notifier_pending) {
+ if (k->guest_notifier_pending(vdev, queue_no)) {
+ msix_set_pending(dev, vector);
+ }
+ } else if (event_notifier_test_and_clear(notifier)) {
+ msix_set_pending(dev, vector);
+ }
+ }
+}
+
+static int virtio_pci_set_guest_notifier(DeviceState *d, int n, bool assign,
+ bool with_irqfd)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_guest_notifier(vq);
+
+ if (assign) {
+ int r = event_notifier_init(notifier, 0);
+ if (r < 0) {
+ return r;
+ }
+ virtio_queue_set_guest_notifier_fd_handler(vq, true, with_irqfd);
+ } else {
+ virtio_queue_set_guest_notifier_fd_handler(vq, false, with_irqfd);
+ event_notifier_cleanup(notifier);
+ }
+
+ if (!msix_enabled(&proxy->pci_dev) && vdc->guest_notifier_mask) {
+ vdc->guest_notifier_mask(vdev, n, !assign);
+ }
+
+ return 0;
+}
+
+static bool virtio_pci_query_guest_notifiers(DeviceState *d)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+ return msix_enabled(&proxy->pci_dev);
+}
+
+static int virtio_pci_set_guest_notifiers(DeviceState *d, int nvqs, bool assign)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ int r, n;
+ bool with_irqfd = msix_enabled(&proxy->pci_dev) &&
+ kvm_msi_via_irqfd_enabled();
+
+ nvqs = MIN(nvqs, VIRTIO_QUEUE_MAX);
+
+ /* When deassigning, pass a consistent nvqs value
+ * to avoid leaking notifiers.
+ */
+ assert(assign || nvqs == proxy->nvqs_with_notifiers);
+
+ proxy->nvqs_with_notifiers = nvqs;
+
+ /* Must unset vector notifier while guest notifier is still assigned */
+ if ((proxy->vector_irqfd || k->guest_notifier_mask) && !assign) {
+ msix_unset_vector_notifiers(&proxy->pci_dev);
+ if (proxy->vector_irqfd) {
+ kvm_virtio_pci_vector_release(proxy, nvqs);
+ g_free(proxy->vector_irqfd);
+ proxy->vector_irqfd = NULL;
+ }
+ }
+
+ for (n = 0; n < nvqs; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ break;
+ }
+
+ r = virtio_pci_set_guest_notifier(d, n, assign, with_irqfd);
+ if (r < 0) {
+ goto assign_error;
+ }
+ }
+
+ /* Must set vector notifier after guest notifier has been assigned */
+ if ((with_irqfd || k->guest_notifier_mask) && assign) {
+ if (with_irqfd) {
+ proxy->vector_irqfd =
+ g_malloc0(sizeof(*proxy->vector_irqfd) *
+ msix_nr_vectors_allocated(&proxy->pci_dev));
+ r = kvm_virtio_pci_vector_use(proxy, nvqs);
+ if (r < 0) {
+ goto assign_error;
+ }
+ }
+ r = msix_set_vector_notifiers(&proxy->pci_dev,
+ virtio_pci_vector_unmask,
+ virtio_pci_vector_mask,
+ virtio_pci_vector_poll);
+ if (r < 0) {
+ goto notifiers_error;
+ }
+ }
+
+ return 0;
+
+notifiers_error:
+ if (with_irqfd) {
+ assert(assign);
+ kvm_virtio_pci_vector_release(proxy, nvqs);
+ }
+
+assign_error:
+ /* We get here on assignment failure. Recover by undoing for VQs 0 .. n. */
+ assert(assign);
+ while (--n >= 0) {
+ virtio_pci_set_guest_notifier(d, n, !assign, with_irqfd);
+ }
+ return r;
+}
+
+static int virtio_pci_set_host_notifier(DeviceState *d, int n, bool assign)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+
+ /* Stop using ioeventfd for virtqueue kick if the device starts using host
+ * notifiers. This makes it easy to avoid stepping on each others' toes.
+ */
+ proxy->ioeventfd_disabled = assign;
+ if (assign) {
+ virtio_pci_stop_ioeventfd(proxy);
+ }
+ /* We don't need to start here: it's not needed because backend
+ * currently only stops on status change away from ok,
+ * reset, vmstop and such. If we do add code to start here,
+ * need to check vmstate, device state etc. */
+ return virtio_pci_set_host_notifier_internal(proxy, n, assign, false);
+}
+
+static void virtio_pci_vmstate_change(DeviceState *d, bool running)
+{
+ VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ if (running) {
+ /* Old QEMU versions did not set bus master enable on status write.
+ * Detect DRIVER set and enable it.
+ */
+ if ((proxy->flags & VIRTIO_PCI_FLAG_BUS_MASTER_BUG_MIGRATION) &&
+ (vdev->status & VIRTIO_CONFIG_S_DRIVER) &&
+ !(proxy->pci_dev.config[PCI_COMMAND] & PCI_COMMAND_MASTER)) {
+ pci_default_write_config(&proxy->pci_dev, PCI_COMMAND,
+ proxy->pci_dev.config[PCI_COMMAND] |
+ PCI_COMMAND_MASTER, 1);
+ }
+ virtio_pci_start_ioeventfd(proxy);
+ } else {
+ virtio_pci_stop_ioeventfd(proxy);
+ }
+}
+
+#ifdef CONFIG_VIRTFS
+static void virtio_9p_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ V9fsPCIState *dev = VIRTIO_9P_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+}
+
+static Property virtio_9p_pci_properties[] = {
+ DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
+ VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_9p_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+
+ k->realize = virtio_9p_pci_realize;
+ pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_9P;
+ pcidev_k->revision = VIRTIO_PCI_ABI_VERSION;
+ pcidev_k->class_id = 0x2;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->props = virtio_9p_pci_properties;
+}
+
+static void virtio_9p_pci_instance_init(Object *obj)
+{
+ V9fsPCIState *dev = VIRTIO_9P_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_9P);
+}
+
+static const TypeInfo virtio_9p_pci_info = {
+ .name = TYPE_VIRTIO_9P_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(V9fsPCIState),
+ .instance_init = virtio_9p_pci_instance_init,
+ .class_init = virtio_9p_pci_class_init,
+};
+#endif /* CONFIG_VIRTFS */
+
+/*
+ * virtio-pci: This is the PCIDevice which has a virtio-pci-bus.
+ */
+
+static int virtio_pci_query_nvectors(DeviceState *d)
+{
+ VirtIOPCIProxy *proxy = VIRTIO_PCI(d);
+
+ return proxy->nvectors;
+}
+
+static int virtio_pci_add_mem_cap(VirtIOPCIProxy *proxy,
+ struct virtio_pci_cap *cap)
+{
+ PCIDevice *dev = &proxy->pci_dev;
+ int offset;
+
+ offset = pci_add_capability(dev, PCI_CAP_ID_VNDR, 0, cap->cap_len);
+ assert(offset > 0);
+
+ assert(cap->cap_len >= sizeof *cap);
+ memcpy(dev->config + offset + PCI_CAP_FLAGS, &cap->cap_len,
+ cap->cap_len - PCI_CAP_FLAGS);
+
+ return offset;
+}
+
+static uint64_t virtio_pci_common_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ VirtIOPCIProxy *proxy = opaque;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ uint32_t val = 0;
+ int i;
+
+ switch (addr) {
+ case VIRTIO_PCI_COMMON_DFSELECT:
+ val = proxy->dfselect;
+ break;
+ case VIRTIO_PCI_COMMON_DF:
+ if (proxy->dfselect <= 1) {
+ val = (vdev->host_features & ~VIRTIO_LEGACY_FEATURES) >>
+ (32 * proxy->dfselect);
+ }
+ break;
+ case VIRTIO_PCI_COMMON_GFSELECT:
+ val = proxy->gfselect;
+ break;
+ case VIRTIO_PCI_COMMON_GF:
+ if (proxy->gfselect < ARRAY_SIZE(proxy->guest_features)) {
+ val = proxy->guest_features[proxy->gfselect];
+ }
+ break;
+ case VIRTIO_PCI_COMMON_MSIX:
+ val = vdev->config_vector;
+ break;
+ case VIRTIO_PCI_COMMON_NUMQ:
+ for (i = 0; i < VIRTIO_QUEUE_MAX; ++i) {
+ if (virtio_queue_get_num(vdev, i)) {
+ val = i + 1;
+ }
+ }
+ break;
+ case VIRTIO_PCI_COMMON_STATUS:
+ val = vdev->status;
+ break;
+ case VIRTIO_PCI_COMMON_CFGGENERATION:
+ val = vdev->generation;
+ break;
+ case VIRTIO_PCI_COMMON_Q_SELECT:
+ val = vdev->queue_sel;
+ break;
+ case VIRTIO_PCI_COMMON_Q_SIZE:
+ val = virtio_queue_get_num(vdev, vdev->queue_sel);
+ break;
+ case VIRTIO_PCI_COMMON_Q_MSIX:
+ val = virtio_queue_vector(vdev, vdev->queue_sel);
+ break;
+ case VIRTIO_PCI_COMMON_Q_ENABLE:
+ val = proxy->vqs[vdev->queue_sel].enabled;
+ break;
+ case VIRTIO_PCI_COMMON_Q_NOFF:
+ /* Simply map queues in order */
+ val = vdev->queue_sel;
+ break;
+ case VIRTIO_PCI_COMMON_Q_DESCLO:
+ val = proxy->vqs[vdev->queue_sel].desc[0];
+ break;
+ case VIRTIO_PCI_COMMON_Q_DESCHI:
+ val = proxy->vqs[vdev->queue_sel].desc[1];
+ break;
+ case VIRTIO_PCI_COMMON_Q_AVAILLO:
+ val = proxy->vqs[vdev->queue_sel].avail[0];
+ break;
+ case VIRTIO_PCI_COMMON_Q_AVAILHI:
+ val = proxy->vqs[vdev->queue_sel].avail[1];
+ break;
+ case VIRTIO_PCI_COMMON_Q_USEDLO:
+ val = proxy->vqs[vdev->queue_sel].used[0];
+ break;
+ case VIRTIO_PCI_COMMON_Q_USEDHI:
+ val = proxy->vqs[vdev->queue_sel].used[1];
+ break;
+ default:
+ val = 0;
+ }
+
+ return val;
+}
+
+static void virtio_pci_common_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VirtIOPCIProxy *proxy = opaque;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ switch (addr) {
+ case VIRTIO_PCI_COMMON_DFSELECT:
+ proxy->dfselect = val;
+ break;
+ case VIRTIO_PCI_COMMON_GFSELECT:
+ proxy->gfselect = val;
+ break;
+ case VIRTIO_PCI_COMMON_GF:
+ if (proxy->gfselect < ARRAY_SIZE(proxy->guest_features)) {
+ proxy->guest_features[proxy->gfselect] = val;
+ virtio_set_features(vdev,
+ (((uint64_t)proxy->guest_features[1]) << 32) |
+ proxy->guest_features[0]);
+ }
+ break;
+ case VIRTIO_PCI_COMMON_MSIX:
+ msix_vector_unuse(&proxy->pci_dev, vdev->config_vector);
+ /* Make it possible for guest to discover an error took place. */
+ if (msix_vector_use(&proxy->pci_dev, val) < 0) {
+ val = VIRTIO_NO_VECTOR;
+ }
+ vdev->config_vector = val;
+ break;
+ case VIRTIO_PCI_COMMON_STATUS:
+ if (!(val & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ virtio_pci_stop_ioeventfd(proxy);
+ }
+
+ virtio_set_status(vdev, val & 0xFF);
+
+ if (val & VIRTIO_CONFIG_S_DRIVER_OK) {
+ virtio_pci_start_ioeventfd(proxy);
+ }
+
+ if (vdev->status == 0) {
+ virtio_reset(vdev);
+ msix_unuse_all_vectors(&proxy->pci_dev);
+ }
+
+ break;
+ case VIRTIO_PCI_COMMON_Q_SELECT:
+ if (val < VIRTIO_QUEUE_MAX) {
+ vdev->queue_sel = val;
+ }
+ break;
+ case VIRTIO_PCI_COMMON_Q_SIZE:
+ proxy->vqs[vdev->queue_sel].num = val;
+ break;
+ case VIRTIO_PCI_COMMON_Q_MSIX:
+ msix_vector_unuse(&proxy->pci_dev,
+ virtio_queue_vector(vdev, vdev->queue_sel));
+ /* Make it possible for guest to discover an error took place. */
+ if (msix_vector_use(&proxy->pci_dev, val) < 0) {
+ val = VIRTIO_NO_VECTOR;
+ }
+ virtio_queue_set_vector(vdev, vdev->queue_sel, val);
+ break;
+ case VIRTIO_PCI_COMMON_Q_ENABLE:
+ /* TODO: need a way to put num back on reset. */
+ virtio_queue_set_num(vdev, vdev->queue_sel,
+ proxy->vqs[vdev->queue_sel].num);
+ virtio_queue_set_rings(vdev, vdev->queue_sel,
+ ((uint64_t)proxy->vqs[vdev->queue_sel].desc[1]) << 32 |
+ proxy->vqs[vdev->queue_sel].desc[0],
+ ((uint64_t)proxy->vqs[vdev->queue_sel].avail[1]) << 32 |
+ proxy->vqs[vdev->queue_sel].avail[0],
+ ((uint64_t)proxy->vqs[vdev->queue_sel].used[1]) << 32 |
+ proxy->vqs[vdev->queue_sel].used[0]);
+ break;
+ case VIRTIO_PCI_COMMON_Q_DESCLO:
+ proxy->vqs[vdev->queue_sel].desc[0] = val;
+ break;
+ case VIRTIO_PCI_COMMON_Q_DESCHI:
+ proxy->vqs[vdev->queue_sel].desc[1] = val;
+ break;
+ case VIRTIO_PCI_COMMON_Q_AVAILLO:
+ proxy->vqs[vdev->queue_sel].avail[0] = val;
+ break;
+ case VIRTIO_PCI_COMMON_Q_AVAILHI:
+ proxy->vqs[vdev->queue_sel].avail[1] = val;
+ break;
+ case VIRTIO_PCI_COMMON_Q_USEDLO:
+ proxy->vqs[vdev->queue_sel].used[0] = val;
+ break;
+ case VIRTIO_PCI_COMMON_Q_USEDHI:
+ proxy->vqs[vdev->queue_sel].used[1] = val;
+ break;
+ default:
+ break;
+ }
+}
+
+
+static uint64_t virtio_pci_notify_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void virtio_pci_notify_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VirtIODevice *vdev = opaque;
+ unsigned queue = addr / QEMU_VIRTIO_PCI_QUEUE_MEM_MULT;
+
+ if (queue < VIRTIO_QUEUE_MAX) {
+ virtio_queue_notify(vdev, queue);
+ }
+}
+
+static uint64_t virtio_pci_isr_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ VirtIOPCIProxy *proxy = opaque;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ uint64_t val = vdev->isr;
+
+ vdev->isr = 0;
+ pci_irq_deassert(&proxy->pci_dev);
+
+ return val;
+}
+
+static void virtio_pci_isr_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+}
+
+static uint64_t virtio_pci_device_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ VirtIODevice *vdev = opaque;
+ uint64_t val = 0;
+
+ switch (size) {
+ case 1:
+ val = virtio_config_modern_readb(vdev, addr);
+ break;
+ case 2:
+ val = virtio_config_modern_readw(vdev, addr);
+ break;
+ case 4:
+ val = virtio_config_modern_readl(vdev, addr);
+ break;
+ }
+ return val;
+}
+
+static void virtio_pci_device_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VirtIODevice *vdev = opaque;
+ switch (size) {
+ case 1:
+ virtio_config_modern_writeb(vdev, addr, val);
+ break;
+ case 2:
+ virtio_config_modern_writew(vdev, addr, val);
+ break;
+ case 4:
+ virtio_config_modern_writel(vdev, addr, val);
+ break;
+ }
+}
+
+static void virtio_pci_modern_regions_init(VirtIOPCIProxy *proxy)
+{
+ static const MemoryRegionOps common_ops = {
+ .read = virtio_pci_common_read,
+ .write = virtio_pci_common_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ };
+ static const MemoryRegionOps isr_ops = {
+ .read = virtio_pci_isr_read,
+ .write = virtio_pci_isr_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ };
+ static const MemoryRegionOps device_ops = {
+ .read = virtio_pci_device_read,
+ .write = virtio_pci_device_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ };
+ static const MemoryRegionOps notify_ops = {
+ .read = virtio_pci_notify_read,
+ .write = virtio_pci_notify_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ };
+
+ memory_region_init_io(&proxy->common.mr, OBJECT(proxy),
+ &common_ops,
+ proxy,
+ "virtio-pci-common",
+ proxy->common.size);
+
+ memory_region_init_io(&proxy->isr.mr, OBJECT(proxy),
+ &isr_ops,
+ proxy,
+ "virtio-pci-isr",
+ proxy->isr.size);
+
+ memory_region_init_io(&proxy->device.mr, OBJECT(proxy),
+ &device_ops,
+ virtio_bus_get_device(&proxy->bus),
+ "virtio-pci-device",
+ proxy->device.size);
+
+ memory_region_init_io(&proxy->notify.mr, OBJECT(proxy),
+ &notify_ops,
+ virtio_bus_get_device(&proxy->bus),
+ "virtio-pci-notify",
+ proxy->notify.size);
+}
+
+static void virtio_pci_modern_region_map(VirtIOPCIProxy *proxy,
+ VirtIOPCIRegion *region,
+ struct virtio_pci_cap *cap)
+{
+ memory_region_add_subregion(&proxy->modern_bar,
+ region->offset,
+ &region->mr);
+
+ cap->cfg_type = region->type;
+ cap->bar = proxy->modern_mem_bar;
+ cap->offset = cpu_to_le32(region->offset);
+ cap->length = cpu_to_le32(region->size);
+ virtio_pci_add_mem_cap(proxy, cap);
+}
+
+static void virtio_pci_modern_region_unmap(VirtIOPCIProxy *proxy,
+ VirtIOPCIRegion *region)
+{
+ memory_region_del_subregion(&proxy->modern_bar,
+ &region->mr);
+}
+
+/* This is called by virtio-bus just after the device is plugged. */
+static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
+{
+ VirtIOPCIProxy *proxy = VIRTIO_PCI(d);
+ VirtioBusState *bus = &proxy->bus;
+ bool legacy = !(proxy->flags & VIRTIO_PCI_FLAG_DISABLE_LEGACY);
+ bool modern = !(proxy->flags & VIRTIO_PCI_FLAG_DISABLE_MODERN);
+ uint8_t *config;
+ uint32_t size;
+ VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ config = proxy->pci_dev.config;
+ if (proxy->class_code) {
+ pci_config_set_class(config, proxy->class_code);
+ }
+
+ if (legacy) {
+ /* legacy and transitional */
+ pci_set_word(config + PCI_SUBSYSTEM_VENDOR_ID,
+ pci_get_word(config + PCI_VENDOR_ID));
+ pci_set_word(config + PCI_SUBSYSTEM_ID, virtio_bus_get_vdev_id(bus));
+ } else {
+ /* pure virtio-1.0 */
+ pci_set_word(config + PCI_VENDOR_ID,
+ PCI_VENDOR_ID_REDHAT_QUMRANET);
+ pci_set_word(config + PCI_DEVICE_ID,
+ 0x1040 + virtio_bus_get_vdev_id(bus));
+ pci_config_set_revision(config, 1);
+ }
+ config[PCI_INTERRUPT_PIN] = 1;
+
+
+ if (modern) {
+ struct virtio_pci_cap cap = {
+ .cap_len = sizeof cap,
+ };
+ struct virtio_pci_notify_cap notify = {
+ .cap.cap_len = sizeof notify,
+ .notify_off_multiplier =
+ cpu_to_le32(QEMU_VIRTIO_PCI_QUEUE_MEM_MULT),
+ };
+ struct virtio_pci_cfg_cap cfg = {
+ .cap.cap_len = sizeof cfg,
+ .cap.cfg_type = VIRTIO_PCI_CAP_PCI_CFG,
+ };
+ struct virtio_pci_cfg_cap *cfg_mask;
+
+ /* TODO: add io access for speed */
+
+ virtio_add_feature(&vdev->host_features, VIRTIO_F_VERSION_1);
+ virtio_pci_modern_regions_init(proxy);
+ virtio_pci_modern_region_map(proxy, &proxy->common, &cap);
+ virtio_pci_modern_region_map(proxy, &proxy->isr, &cap);
+ virtio_pci_modern_region_map(proxy, &proxy->device, &cap);
+ virtio_pci_modern_region_map(proxy, &proxy->notify, &notify.cap);
+
+ pci_register_bar(&proxy->pci_dev, proxy->modern_mem_bar,
+ PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_PREFETCH |
+ PCI_BASE_ADDRESS_MEM_TYPE_64,
+ &proxy->modern_bar);
+
+ proxy->config_cap = virtio_pci_add_mem_cap(proxy, &cfg.cap);
+ cfg_mask = (void *)(proxy->pci_dev.wmask + proxy->config_cap);
+ pci_set_byte(&cfg_mask->cap.bar, ~0x0);
+ pci_set_long((uint8_t *)&cfg_mask->cap.offset, ~0x0);
+ pci_set_long((uint8_t *)&cfg_mask->cap.length, ~0x0);
+ pci_set_long(cfg_mask->pci_cfg_data, ~0x0);
+ }
+
+ if (proxy->nvectors &&
+ msix_init_exclusive_bar(&proxy->pci_dev, proxy->nvectors,
+ proxy->msix_bar)) {
+ error_report("unable to init msix vectors to %" PRIu32,
+ proxy->nvectors);
+ proxy->nvectors = 0;
+ }
+
+ proxy->pci_dev.config_write = virtio_write_config;
+ proxy->pci_dev.config_read = virtio_read_config;
+
+ if (legacy) {
+ size = VIRTIO_PCI_REGION_SIZE(&proxy->pci_dev)
+ + virtio_bus_get_vdev_config_len(bus);
+ if (size & (size - 1)) {
+ size = 1 << qemu_fls(size);
+ }
+
+ memory_region_init_io(&proxy->bar, OBJECT(proxy),
+ &virtio_pci_config_ops,
+ proxy, "virtio-pci", size);
+
+ pci_register_bar(&proxy->pci_dev, proxy->legacy_io_bar,
+ PCI_BASE_ADDRESS_SPACE_IO, &proxy->bar);
+ }
+
+ if (!kvm_has_many_ioeventfds()) {
+ proxy->flags &= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
+ }
+
+ virtio_add_feature(&vdev->host_features, VIRTIO_F_BAD_FEATURE);
+}
+
+static void virtio_pci_device_unplugged(DeviceState *d)
+{
+ VirtIOPCIProxy *proxy = VIRTIO_PCI(d);
+ bool modern = !(proxy->flags & VIRTIO_PCI_FLAG_DISABLE_MODERN);
+
+ virtio_pci_stop_ioeventfd(proxy);
+
+ if (modern) {
+ virtio_pci_modern_region_unmap(proxy, &proxy->common);
+ virtio_pci_modern_region_unmap(proxy, &proxy->isr);
+ virtio_pci_modern_region_unmap(proxy, &proxy->device);
+ virtio_pci_modern_region_unmap(proxy, &proxy->notify);
+ }
+}
+
+static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp)
+{
+ VirtIOPCIProxy *proxy = VIRTIO_PCI(pci_dev);
+ VirtioPCIClass *k = VIRTIO_PCI_GET_CLASS(pci_dev);
+
+ /*
+ * virtio pci bar layout used by default.
+ * subclasses can re-arrange things if needed.
+ *
+ * region 0 -- virtio legacy io bar
+ * region 1 -- msi-x bar
+ * region 4+5 -- virtio modern memory (64bit) bar
+ *
+ */
+ proxy->legacy_io_bar = 0;
+ proxy->msix_bar = 1;
+ proxy->modern_mem_bar = 4;
+
+ proxy->common.offset = 0x0;
+ proxy->common.size = 0x1000;
+ proxy->common.type = VIRTIO_PCI_CAP_COMMON_CFG;
+
+ proxy->isr.offset = 0x1000;
+ proxy->isr.size = 0x1000;
+ proxy->isr.type = VIRTIO_PCI_CAP_ISR_CFG;
+
+ proxy->device.offset = 0x2000;
+ proxy->device.size = 0x1000;
+ proxy->device.type = VIRTIO_PCI_CAP_DEVICE_CFG;
+
+ proxy->notify.offset = 0x3000;
+ proxy->notify.size =
+ QEMU_VIRTIO_PCI_QUEUE_MEM_MULT * VIRTIO_QUEUE_MAX;
+ proxy->notify.type = VIRTIO_PCI_CAP_NOTIFY_CFG;
+
+ /* subclasses can enforce modern, so do this unconditionally */
+ memory_region_init(&proxy->modern_bar, OBJECT(proxy), "virtio-pci",
+ 2 * QEMU_VIRTIO_PCI_QUEUE_MEM_MULT *
+ VIRTIO_QUEUE_MAX);
+
+ memory_region_init_alias(&proxy->modern_cfg,
+ OBJECT(proxy),
+ "virtio-pci-cfg",
+ &proxy->modern_bar,
+ 0,
+ memory_region_size(&proxy->modern_bar));
+
+ address_space_init(&proxy->modern_as, &proxy->modern_cfg, "virtio-pci-cfg-as");
+
+ virtio_pci_bus_new(&proxy->bus, sizeof(proxy->bus), proxy);
+ if (k->realize) {
+ k->realize(proxy, errp);
+ }
+}
+
+static void virtio_pci_exit(PCIDevice *pci_dev)
+{
+ VirtIOPCIProxy *proxy = VIRTIO_PCI(pci_dev);
+
+ msix_uninit_exclusive_bar(pci_dev);
+ address_space_destroy(&proxy->modern_as);
+}
+
+static void virtio_pci_reset(DeviceState *qdev)
+{
+ VirtIOPCIProxy *proxy = VIRTIO_PCI(qdev);
+ VirtioBusState *bus = VIRTIO_BUS(&proxy->bus);
+ virtio_pci_stop_ioeventfd(proxy);
+ virtio_bus_reset(bus);
+ msix_unuse_all_vectors(&proxy->pci_dev);
+}
+
+static Property virtio_pci_properties[] = {
+ DEFINE_PROP_BIT("virtio-pci-bus-master-bug-migration", VirtIOPCIProxy, flags,
+ VIRTIO_PCI_FLAG_BUS_MASTER_BUG_MIGRATION_BIT, false),
+ DEFINE_PROP_BIT("disable-legacy", VirtIOPCIProxy, flags,
+ VIRTIO_PCI_FLAG_DISABLE_LEGACY_BIT, false),
+ DEFINE_PROP_BIT("disable-modern", VirtIOPCIProxy, flags,
+ VIRTIO_PCI_FLAG_DISABLE_MODERN_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ dc->props = virtio_pci_properties;
+ k->realize = virtio_pci_realize;
+ k->exit = virtio_pci_exit;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ k->revision = VIRTIO_PCI_ABI_VERSION;
+ k->class_id = PCI_CLASS_OTHERS;
+ dc->reset = virtio_pci_reset;
+}
+
+static const TypeInfo virtio_pci_info = {
+ .name = TYPE_VIRTIO_PCI,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(VirtIOPCIProxy),
+ .class_init = virtio_pci_class_init,
+ .class_size = sizeof(VirtioPCIClass),
+ .abstract = true,
+};
+
+/* virtio-blk-pci */
+
+static Property virtio_blk_pci_properties[] = {
+ DEFINE_PROP_UINT32("class", VirtIOPCIProxy, class_code, 0),
+ DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
+ VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_blk_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOBlkPCI *dev = VIRTIO_BLK_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+}
+
+static void virtio_blk_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->props = virtio_blk_pci_properties;
+ k->realize = virtio_blk_pci_realize;
+ pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_BLOCK;
+ pcidev_k->revision = VIRTIO_PCI_ABI_VERSION;
+ pcidev_k->class_id = PCI_CLASS_STORAGE_SCSI;
+}
+
+static void virtio_blk_pci_instance_init(Object *obj)
+{
+ VirtIOBlkPCI *dev = VIRTIO_BLK_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_BLK);
+ object_property_add_alias(obj, "iothread", OBJECT(&dev->vdev),"iothread",
+ &error_abort);
+ object_property_add_alias(obj, "bootindex", OBJECT(&dev->vdev),
+ "bootindex", &error_abort);
+}
+
+static const TypeInfo virtio_blk_pci_info = {
+ .name = TYPE_VIRTIO_BLK_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOBlkPCI),
+ .instance_init = virtio_blk_pci_instance_init,
+ .class_init = virtio_blk_pci_class_init,
+};
+
+/* virtio-scsi-pci */
+
+static Property virtio_scsi_pci_properties[] = {
+ DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
+ VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors,
+ DEV_NVECTORS_UNSPECIFIED),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_scsi_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOSCSIPCI *dev = VIRTIO_SCSI_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+ DeviceState *proxy = DEVICE(vpci_dev);
+ char *bus_name;
+
+ if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) {
+ vpci_dev->nvectors = vs->conf.num_queues + 3;
+ }
+
+ /*
+ * For command line compatibility, this sets the virtio-scsi-device bus
+ * name as before.
+ */
+ if (proxy->id) {
+ bus_name = g_strdup_printf("%s.0", proxy->id);
+ virtio_device_set_child_bus_name(VIRTIO_DEVICE(vdev), bus_name);
+ g_free(bus_name);
+ }
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+}
+
+static void virtio_scsi_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = virtio_scsi_pci_realize;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->props = virtio_scsi_pci_properties;
+ pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_SCSI;
+ pcidev_k->revision = 0x00;
+ pcidev_k->class_id = PCI_CLASS_STORAGE_SCSI;
+}
+
+static void virtio_scsi_pci_instance_init(Object *obj)
+{
+ VirtIOSCSIPCI *dev = VIRTIO_SCSI_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_SCSI);
+ object_property_add_alias(obj, "iothread", OBJECT(&dev->vdev), "iothread",
+ &error_abort);
+}
+
+static const TypeInfo virtio_scsi_pci_info = {
+ .name = TYPE_VIRTIO_SCSI_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOSCSIPCI),
+ .instance_init = virtio_scsi_pci_instance_init,
+ .class_init = virtio_scsi_pci_class_init,
+};
+
+/* vhost-scsi-pci */
+
+#ifdef CONFIG_VHOST_SCSI
+static Property vhost_scsi_pci_properties[] = {
+ DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors,
+ DEV_NVECTORS_UNSPECIFIED),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vhost_scsi_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VHostSCSIPCI *dev = VHOST_SCSI_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+
+ if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) {
+ vpci_dev->nvectors = vs->conf.num_queues + 3;
+ }
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+}
+
+static void vhost_scsi_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+ k->realize = vhost_scsi_pci_realize;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->props = vhost_scsi_pci_properties;
+ pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_SCSI;
+ pcidev_k->revision = 0x00;
+ pcidev_k->class_id = PCI_CLASS_STORAGE_SCSI;
+}
+
+static void vhost_scsi_pci_instance_init(Object *obj)
+{
+ VHostSCSIPCI *dev = VHOST_SCSI_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VHOST_SCSI);
+ object_property_add_alias(obj, "bootindex", OBJECT(&dev->vdev),
+ "bootindex", &error_abort);
+}
+
+static const TypeInfo vhost_scsi_pci_info = {
+ .name = TYPE_VHOST_SCSI_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VHostSCSIPCI),
+ .instance_init = vhost_scsi_pci_instance_init,
+ .class_init = vhost_scsi_pci_class_init,
+};
+#endif
+
+/* virtio-balloon-pci */
+
+static Property virtio_balloon_pci_properties[] = {
+ DEFINE_PROP_UINT32("class", VirtIOPCIProxy, class_code, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_balloon_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOBalloonPCI *dev = VIRTIO_BALLOON_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+
+ if (vpci_dev->class_code != PCI_CLASS_OTHERS &&
+ vpci_dev->class_code != PCI_CLASS_MEMORY_RAM) { /* qemu < 1.1 */
+ vpci_dev->class_code = PCI_CLASS_OTHERS;
+ }
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+}
+
+static void virtio_balloon_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+ k->realize = virtio_balloon_pci_realize;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->props = virtio_balloon_pci_properties;
+ pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_BALLOON;
+ pcidev_k->revision = VIRTIO_PCI_ABI_VERSION;
+ pcidev_k->class_id = PCI_CLASS_OTHERS;
+}
+
+static void virtio_balloon_pci_instance_init(Object *obj)
+{
+ VirtIOBalloonPCI *dev = VIRTIO_BALLOON_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_BALLOON);
+ object_property_add_alias(obj, "guest-stats", OBJECT(&dev->vdev),
+ "guest-stats", &error_abort);
+ object_property_add_alias(obj, "guest-stats-polling-interval",
+ OBJECT(&dev->vdev),
+ "guest-stats-polling-interval", &error_abort);
+}
+
+static const TypeInfo virtio_balloon_pci_info = {
+ .name = TYPE_VIRTIO_BALLOON_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOBalloonPCI),
+ .instance_init = virtio_balloon_pci_instance_init,
+ .class_init = virtio_balloon_pci_class_init,
+};
+
+/* virtio-serial-pci */
+
+static void virtio_serial_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOSerialPCI *dev = VIRTIO_SERIAL_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+ DeviceState *proxy = DEVICE(vpci_dev);
+ char *bus_name;
+
+ if (vpci_dev->class_code != PCI_CLASS_COMMUNICATION_OTHER &&
+ vpci_dev->class_code != PCI_CLASS_DISPLAY_OTHER && /* qemu 0.10 */
+ vpci_dev->class_code != PCI_CLASS_OTHERS) { /* qemu-kvm */
+ vpci_dev->class_code = PCI_CLASS_COMMUNICATION_OTHER;
+ }
+
+ /* backwards-compatibility with machines that were created with
+ DEV_NVECTORS_UNSPECIFIED */
+ if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) {
+ vpci_dev->nvectors = dev->vdev.serial.max_virtserial_ports + 1;
+ }
+
+ /*
+ * For command line compatibility, this sets the virtio-serial-device bus
+ * name as before.
+ */
+ if (proxy->id) {
+ bus_name = g_strdup_printf("%s.0", proxy->id);
+ virtio_device_set_child_bus_name(VIRTIO_DEVICE(vdev), bus_name);
+ g_free(bus_name);
+ }
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+}
+
+static Property virtio_serial_pci_properties[] = {
+ DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
+ VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true),
+ DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2),
+ DEFINE_PROP_UINT32("class", VirtIOPCIProxy, class_code, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_serial_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+ k->realize = virtio_serial_pci_realize;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->props = virtio_serial_pci_properties;
+ pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_CONSOLE;
+ pcidev_k->revision = VIRTIO_PCI_ABI_VERSION;
+ pcidev_k->class_id = PCI_CLASS_COMMUNICATION_OTHER;
+}
+
+static void virtio_serial_pci_instance_init(Object *obj)
+{
+ VirtIOSerialPCI *dev = VIRTIO_SERIAL_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_SERIAL);
+}
+
+static const TypeInfo virtio_serial_pci_info = {
+ .name = TYPE_VIRTIO_SERIAL_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOSerialPCI),
+ .instance_init = virtio_serial_pci_instance_init,
+ .class_init = virtio_serial_pci_class_init,
+};
+
+/* virtio-net-pci */
+
+static Property virtio_net_properties[] = {
+ DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
+ VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, false),
+ DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 3),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_net_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ DeviceState *qdev = DEVICE(vpci_dev);
+ VirtIONetPCI *dev = VIRTIO_NET_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&dev->vdev);
+
+ virtio_net_set_netclient_name(&dev->vdev, qdev->id,
+ object_get_typename(OBJECT(qdev)));
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+}
+
+static void virtio_net_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ VirtioPCIClass *vpciklass = VIRTIO_PCI_CLASS(klass);
+
+ k->romfile = "efi-virtio.rom";
+ k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ k->device_id = PCI_DEVICE_ID_VIRTIO_NET;
+ k->revision = VIRTIO_PCI_ABI_VERSION;
+ k->class_id = PCI_CLASS_NETWORK_ETHERNET;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->props = virtio_net_properties;
+ vpciklass->realize = virtio_net_pci_realize;
+}
+
+static void virtio_net_pci_instance_init(Object *obj)
+{
+ VirtIONetPCI *dev = VIRTIO_NET_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_NET);
+ object_property_add_alias(obj, "bootindex", OBJECT(&dev->vdev),
+ "bootindex", &error_abort);
+}
+
+static const TypeInfo virtio_net_pci_info = {
+ .name = TYPE_VIRTIO_NET_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIONetPCI),
+ .instance_init = virtio_net_pci_instance_init,
+ .class_init = virtio_net_pci_class_init,
+};
+
+/* virtio-rng-pci */
+
+static Property virtio_rng_pci_properties[] = {
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_rng_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIORngPCI *vrng = VIRTIO_RNG_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&vrng->vdev);
+ Error *err = NULL;
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ object_property_set_bool(OBJECT(vdev), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ object_property_set_link(OBJECT(vrng),
+ OBJECT(vrng->vdev.conf.rng), "rng",
+ NULL);
+}
+
+static void virtio_rng_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = virtio_rng_pci_realize;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->props = virtio_rng_pci_properties;
+
+ pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_RNG;
+ pcidev_k->revision = VIRTIO_PCI_ABI_VERSION;
+ pcidev_k->class_id = PCI_CLASS_OTHERS;
+}
+
+static void virtio_rng_initfn(Object *obj)
+{
+ VirtIORngPCI *dev = VIRTIO_RNG_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_RNG);
+ object_property_add_alias(obj, "rng", OBJECT(&dev->vdev), "rng",
+ &error_abort);
+}
+
+static const TypeInfo virtio_rng_pci_info = {
+ .name = TYPE_VIRTIO_RNG_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIORngPCI),
+ .instance_init = virtio_rng_initfn,
+ .class_init = virtio_rng_pci_class_init,
+};
+
+/* virtio-input-pci */
+
+static Property virtio_input_pci_properties[] = {
+ DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_input_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOInputPCI *vinput = VIRTIO_INPUT_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&vinput->vdev);
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ /* force virtio-1.0 */
+ vpci_dev->flags &= ~VIRTIO_PCI_FLAG_DISABLE_MODERN;
+ vpci_dev->flags |= VIRTIO_PCI_FLAG_DISABLE_LEGACY;
+ object_property_set_bool(OBJECT(vdev), true, "realized", errp);
+}
+
+static void virtio_input_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ dc->props = virtio_input_pci_properties;
+ k->realize = virtio_input_pci_realize;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+
+ pcidev_k->class_id = PCI_CLASS_INPUT_OTHER;
+}
+
+static void virtio_input_hid_kbd_pci_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ pcidev_k->class_id = PCI_CLASS_INPUT_KEYBOARD;
+}
+
+static void virtio_input_hid_mouse_pci_class_init(ObjectClass *klass,
+ void *data)
+{
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ pcidev_k->class_id = PCI_CLASS_INPUT_MOUSE;
+}
+
+static void virtio_keyboard_initfn(Object *obj)
+{
+ VirtIOInputHIDPCI *dev = VIRTIO_INPUT_HID_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_KEYBOARD);
+}
+
+static void virtio_mouse_initfn(Object *obj)
+{
+ VirtIOInputHIDPCI *dev = VIRTIO_INPUT_HID_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_MOUSE);
+}
+
+static void virtio_tablet_initfn(Object *obj)
+{
+ VirtIOInputHIDPCI *dev = VIRTIO_INPUT_HID_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_TABLET);
+}
+
+static void virtio_host_initfn(Object *obj)
+{
+ VirtIOInputHostPCI *dev = VIRTIO_INPUT_HOST_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_INPUT_HOST);
+}
+
+static const TypeInfo virtio_input_pci_info = {
+ .name = TYPE_VIRTIO_INPUT_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOInputPCI),
+ .class_init = virtio_input_pci_class_init,
+ .abstract = true,
+};
+
+static const TypeInfo virtio_input_hid_pci_info = {
+ .name = TYPE_VIRTIO_INPUT_HID_PCI,
+ .parent = TYPE_VIRTIO_INPUT_PCI,
+ .instance_size = sizeof(VirtIOInputHIDPCI),
+ .abstract = true,
+};
+
+static const TypeInfo virtio_keyboard_pci_info = {
+ .name = TYPE_VIRTIO_KEYBOARD_PCI,
+ .parent = TYPE_VIRTIO_INPUT_HID_PCI,
+ .class_init = virtio_input_hid_kbd_pci_class_init,
+ .instance_size = sizeof(VirtIOInputHIDPCI),
+ .instance_init = virtio_keyboard_initfn,
+};
+
+static const TypeInfo virtio_mouse_pci_info = {
+ .name = TYPE_VIRTIO_MOUSE_PCI,
+ .parent = TYPE_VIRTIO_INPUT_HID_PCI,
+ .class_init = virtio_input_hid_mouse_pci_class_init,
+ .instance_size = sizeof(VirtIOInputHIDPCI),
+ .instance_init = virtio_mouse_initfn,
+};
+
+static const TypeInfo virtio_tablet_pci_info = {
+ .name = TYPE_VIRTIO_TABLET_PCI,
+ .parent = TYPE_VIRTIO_INPUT_HID_PCI,
+ .instance_size = sizeof(VirtIOInputHIDPCI),
+ .instance_init = virtio_tablet_initfn,
+};
+
+static const TypeInfo virtio_host_pci_info = {
+ .name = TYPE_VIRTIO_INPUT_HOST_PCI,
+ .parent = TYPE_VIRTIO_INPUT_PCI,
+ .instance_size = sizeof(VirtIOInputHostPCI),
+ .instance_init = virtio_host_initfn,
+};
+
+/* virtio-pci-bus */
+
+static void virtio_pci_bus_new(VirtioBusState *bus, size_t bus_size,
+ VirtIOPCIProxy *dev)
+{
+ DeviceState *qdev = DEVICE(dev);
+ char virtio_bus_name[] = "virtio-bus";
+
+ qbus_create_inplace(bus, bus_size, TYPE_VIRTIO_PCI_BUS, qdev,
+ virtio_bus_name);
+}
+
+static void virtio_pci_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *bus_class = BUS_CLASS(klass);
+ VirtioBusClass *k = VIRTIO_BUS_CLASS(klass);
+ bus_class->max_dev = 1;
+ k->notify = virtio_pci_notify;
+ k->save_config = virtio_pci_save_config;
+ k->load_config = virtio_pci_load_config;
+ k->save_queue = virtio_pci_save_queue;
+ k->load_queue = virtio_pci_load_queue;
+ k->query_guest_notifiers = virtio_pci_query_guest_notifiers;
+ k->set_host_notifier = virtio_pci_set_host_notifier;
+ k->set_guest_notifiers = virtio_pci_set_guest_notifiers;
+ k->vmstate_change = virtio_pci_vmstate_change;
+ k->device_plugged = virtio_pci_device_plugged;
+ k->device_unplugged = virtio_pci_device_unplugged;
+ k->query_nvectors = virtio_pci_query_nvectors;
+}
+
+static const TypeInfo virtio_pci_bus_info = {
+ .name = TYPE_VIRTIO_PCI_BUS,
+ .parent = TYPE_VIRTIO_BUS,
+ .instance_size = sizeof(VirtioPCIBusState),
+ .class_init = virtio_pci_bus_class_init,
+};
+
+static void virtio_pci_register_types(void)
+{
+ type_register_static(&virtio_rng_pci_info);
+ type_register_static(&virtio_input_pci_info);
+ type_register_static(&virtio_input_hid_pci_info);
+ type_register_static(&virtio_keyboard_pci_info);
+ type_register_static(&virtio_mouse_pci_info);
+ type_register_static(&virtio_tablet_pci_info);
+ type_register_static(&virtio_host_pci_info);
+ type_register_static(&virtio_pci_bus_info);
+ type_register_static(&virtio_pci_info);
+#ifdef CONFIG_VIRTFS
+ type_register_static(&virtio_9p_pci_info);
+#endif
+ type_register_static(&virtio_blk_pci_info);
+ type_register_static(&virtio_scsi_pci_info);
+ type_register_static(&virtio_balloon_pci_info);
+ type_register_static(&virtio_serial_pci_info);
+ type_register_static(&virtio_net_pci_info);
+#ifdef CONFIG_VHOST_SCSI
+ type_register_static(&vhost_scsi_pci_info);
+#endif
+}
+
+type_init(virtio_pci_register_types)
diff --git a/hw/virtio/virtio-pci.h b/hw/virtio/virtio-pci.h
new file mode 100644
index 00000000..b6c442f5
--- /dev/null
+++ b/hw/virtio/virtio-pci.h
@@ -0,0 +1,294 @@
+/*
+ * Virtio PCI Bindings
+ *
+ * Copyright IBM, Corp. 2007
+ * Copyright (c) 2009 CodeSourcery
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ * Paul Brook <paul@codesourcery.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef QEMU_VIRTIO_PCI_H
+#define QEMU_VIRTIO_PCI_H
+
+#include "hw/pci/msi.h"
+#include "hw/virtio/virtio-blk.h"
+#include "hw/virtio/virtio-net.h"
+#include "hw/virtio/virtio-rng.h"
+#include "hw/virtio/virtio-serial.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "hw/virtio/virtio-balloon.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-9p.h"
+#include "hw/virtio/virtio-input.h"
+#include "hw/virtio/virtio-gpu.h"
+#ifdef CONFIG_VIRTFS
+#include "hw/9pfs/virtio-9p.h"
+#endif
+#ifdef CONFIG_VHOST_SCSI
+#include "hw/virtio/vhost-scsi.h"
+#endif
+
+typedef struct VirtIOPCIProxy VirtIOPCIProxy;
+typedef struct VirtIOBlkPCI VirtIOBlkPCI;
+typedef struct VirtIOSCSIPCI VirtIOSCSIPCI;
+typedef struct VirtIOBalloonPCI VirtIOBalloonPCI;
+typedef struct VirtIOSerialPCI VirtIOSerialPCI;
+typedef struct VirtIONetPCI VirtIONetPCI;
+typedef struct VHostSCSIPCI VHostSCSIPCI;
+typedef struct VirtIORngPCI VirtIORngPCI;
+typedef struct VirtIOInputPCI VirtIOInputPCI;
+typedef struct VirtIOInputHIDPCI VirtIOInputHIDPCI;
+typedef struct VirtIOInputHostPCI VirtIOInputHostPCI;
+typedef struct VirtIOGPUPCI VirtIOGPUPCI;
+
+/* virtio-pci-bus */
+
+typedef struct VirtioBusState VirtioPCIBusState;
+typedef struct VirtioBusClass VirtioPCIBusClass;
+
+#define TYPE_VIRTIO_PCI_BUS "virtio-pci-bus"
+#define VIRTIO_PCI_BUS(obj) \
+ OBJECT_CHECK(VirtioPCIBusState, (obj), TYPE_VIRTIO_PCI_BUS)
+#define VIRTIO_PCI_BUS_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VirtioPCIBusClass, obj, TYPE_VIRTIO_PCI_BUS)
+#define VIRTIO_PCI_BUS_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtioPCIBusClass, klass, TYPE_VIRTIO_PCI_BUS)
+
+/* Need to activate work-arounds for buggy guests at vmstate load. */
+#define VIRTIO_PCI_FLAG_BUS_MASTER_BUG_MIGRATION_BIT 0
+#define VIRTIO_PCI_FLAG_BUS_MASTER_BUG_MIGRATION \
+ (1 << VIRTIO_PCI_FLAG_BUS_MASTER_BUG_MIGRATION_BIT)
+
+/* Performance improves when virtqueue kick processing is decoupled from the
+ * vcpu thread using ioeventfd for some devices. */
+#define VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT 1
+#define VIRTIO_PCI_FLAG_USE_IOEVENTFD (1 << VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT)
+
+/* virtio version flags */
+#define VIRTIO_PCI_FLAG_DISABLE_LEGACY_BIT 2
+#define VIRTIO_PCI_FLAG_DISABLE_MODERN_BIT 3
+#define VIRTIO_PCI_FLAG_DISABLE_LEGACY (1 << VIRTIO_PCI_FLAG_DISABLE_LEGACY_BIT)
+#define VIRTIO_PCI_FLAG_DISABLE_MODERN (1 << VIRTIO_PCI_FLAG_DISABLE_MODERN_BIT)
+
+typedef struct {
+ MSIMessage msg;
+ int virq;
+ unsigned int users;
+} VirtIOIRQFD;
+
+/*
+ * virtio-pci: This is the PCIDevice which has a virtio-pci-bus.
+ */
+#define TYPE_VIRTIO_PCI "virtio-pci"
+#define VIRTIO_PCI_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(VirtioPCIClass, obj, TYPE_VIRTIO_PCI)
+#define VIRTIO_PCI_CLASS(klass) \
+ OBJECT_CLASS_CHECK(VirtioPCIClass, klass, TYPE_VIRTIO_PCI)
+#define VIRTIO_PCI(obj) \
+ OBJECT_CHECK(VirtIOPCIProxy, (obj), TYPE_VIRTIO_PCI)
+
+typedef struct VirtioPCIClass {
+ PCIDeviceClass parent_class;
+ void (*realize)(VirtIOPCIProxy *vpci_dev, Error **errp);
+} VirtioPCIClass;
+
+typedef struct VirtIOPCIRegion {
+ MemoryRegion mr;
+ uint32_t offset;
+ uint32_t size;
+ uint32_t type;
+} VirtIOPCIRegion;
+
+struct VirtIOPCIProxy {
+ PCIDevice pci_dev;
+ MemoryRegion bar;
+ VirtIOPCIRegion common;
+ VirtIOPCIRegion isr;
+ VirtIOPCIRegion device;
+ VirtIOPCIRegion notify;
+ MemoryRegion modern_bar;
+ MemoryRegion modern_cfg;
+ AddressSpace modern_as;
+ uint32_t legacy_io_bar;
+ uint32_t msix_bar;
+ uint32_t modern_mem_bar;
+ int config_cap;
+ uint32_t flags;
+ uint32_t class_code;
+ uint32_t nvectors;
+ uint32_t dfselect;
+ uint32_t gfselect;
+ uint32_t guest_features[2];
+ struct {
+ uint16_t num;
+ bool enabled;
+ uint32_t desc[2];
+ uint32_t avail[2];
+ uint32_t used[2];
+ } vqs[VIRTIO_QUEUE_MAX];
+
+ bool ioeventfd_disabled;
+ bool ioeventfd_started;
+ VirtIOIRQFD *vector_irqfd;
+ int nvqs_with_notifiers;
+ VirtioBusState bus;
+};
+
+
+/*
+ * virtio-scsi-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_SCSI_PCI "virtio-scsi-pci"
+#define VIRTIO_SCSI_PCI(obj) \
+ OBJECT_CHECK(VirtIOSCSIPCI, (obj), TYPE_VIRTIO_SCSI_PCI)
+
+struct VirtIOSCSIPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOSCSI vdev;
+};
+
+#ifdef CONFIG_VHOST_SCSI
+/*
+ * vhost-scsi-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VHOST_SCSI_PCI "vhost-scsi-pci"
+#define VHOST_SCSI_PCI(obj) \
+ OBJECT_CHECK(VHostSCSIPCI, (obj), TYPE_VHOST_SCSI_PCI)
+
+struct VHostSCSIPCI {
+ VirtIOPCIProxy parent_obj;
+ VHostSCSI vdev;
+};
+#endif
+
+/*
+ * virtio-blk-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_BLK_PCI "virtio-blk-pci"
+#define VIRTIO_BLK_PCI(obj) \
+ OBJECT_CHECK(VirtIOBlkPCI, (obj), TYPE_VIRTIO_BLK_PCI)
+
+struct VirtIOBlkPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOBlock vdev;
+};
+
+/*
+ * virtio-balloon-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_BALLOON_PCI "virtio-balloon-pci"
+#define VIRTIO_BALLOON_PCI(obj) \
+ OBJECT_CHECK(VirtIOBalloonPCI, (obj), TYPE_VIRTIO_BALLOON_PCI)
+
+struct VirtIOBalloonPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOBalloon vdev;
+};
+
+/*
+ * virtio-serial-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_SERIAL_PCI "virtio-serial-pci"
+#define VIRTIO_SERIAL_PCI(obj) \
+ OBJECT_CHECK(VirtIOSerialPCI, (obj), TYPE_VIRTIO_SERIAL_PCI)
+
+struct VirtIOSerialPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOSerial vdev;
+};
+
+/*
+ * virtio-net-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_NET_PCI "virtio-net-pci"
+#define VIRTIO_NET_PCI(obj) \
+ OBJECT_CHECK(VirtIONetPCI, (obj), TYPE_VIRTIO_NET_PCI)
+
+struct VirtIONetPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIONet vdev;
+};
+
+/*
+ * virtio-9p-pci: This extends VirtioPCIProxy.
+ */
+
+#ifdef CONFIG_VIRTFS
+
+#define TYPE_VIRTIO_9P_PCI "virtio-9p-pci"
+#define VIRTIO_9P_PCI(obj) \
+ OBJECT_CHECK(V9fsPCIState, (obj), TYPE_VIRTIO_9P_PCI)
+
+typedef struct V9fsPCIState {
+ VirtIOPCIProxy parent_obj;
+ V9fsState vdev;
+} V9fsPCIState;
+
+#endif
+
+/*
+ * virtio-rng-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_RNG_PCI "virtio-rng-pci"
+#define VIRTIO_RNG_PCI(obj) \
+ OBJECT_CHECK(VirtIORngPCI, (obj), TYPE_VIRTIO_RNG_PCI)
+
+struct VirtIORngPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIORNG vdev;
+};
+
+/*
+ * virtio-input-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_INPUT_PCI "virtio-input-pci"
+#define VIRTIO_INPUT_PCI(obj) \
+ OBJECT_CHECK(VirtIOInputPCI, (obj), TYPE_VIRTIO_INPUT_PCI)
+
+struct VirtIOInputPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOInput vdev;
+};
+
+#define TYPE_VIRTIO_INPUT_HID_PCI "virtio-input-hid-pci"
+#define TYPE_VIRTIO_KEYBOARD_PCI "virtio-keyboard-pci"
+#define TYPE_VIRTIO_MOUSE_PCI "virtio-mouse-pci"
+#define TYPE_VIRTIO_TABLET_PCI "virtio-tablet-pci"
+#define VIRTIO_INPUT_HID_PCI(obj) \
+ OBJECT_CHECK(VirtIOInputHIDPCI, (obj), TYPE_VIRTIO_INPUT_HID_PCI)
+
+struct VirtIOInputHIDPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOInputHID vdev;
+};
+
+#define TYPE_VIRTIO_INPUT_HOST_PCI "virtio-input-host-pci"
+#define VIRTIO_INPUT_HOST_PCI(obj) \
+ OBJECT_CHECK(VirtIOInputHostPCI, (obj), TYPE_VIRTIO_INPUT_HOST_PCI)
+
+struct VirtIOInputHostPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOInputHost vdev;
+};
+
+/*
+ * virtio-gpu-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_GPU_PCI "virtio-gpu-pci"
+#define VIRTIO_GPU_PCI(obj) \
+ OBJECT_CHECK(VirtIOGPUPCI, (obj), TYPE_VIRTIO_GPU_PCI)
+
+struct VirtIOGPUPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOGPU vdev;
+};
+
+/* Virtio ABI version, if we increment this, we break the guest driver. */
+#define VIRTIO_PCI_ABI_VERSION 0
+
+#endif
diff --git a/hw/virtio/virtio-rng.c b/hw/virtio/virtio-rng.c
new file mode 100644
index 00000000..97d15419
--- /dev/null
+++ b/hw/virtio/virtio-rng.c
@@ -0,0 +1,267 @@
+/*
+ * A virtio device implementing a hardware random number generator.
+ *
+ * Copyright 2012 Red Hat, Inc.
+ * Copyright 2012 Amit Shah <amit.shah@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/iov.h"
+#include "hw/qdev.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-rng.h"
+#include "sysemu/rng.h"
+#include "qom/object_interfaces.h"
+#include "trace.h"
+
+static bool is_guest_ready(VirtIORNG *vrng)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(vrng);
+ if (virtio_queue_ready(vrng->vq)
+ && (vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ return true;
+ }
+ trace_virtio_rng_guest_not_ready(vrng);
+ return false;
+}
+
+static size_t get_request_size(VirtQueue *vq, unsigned quota)
+{
+ unsigned int in, out;
+
+ virtqueue_get_avail_bytes(vq, &in, &out, quota, 0);
+ return in;
+}
+
+static void virtio_rng_process(VirtIORNG *vrng);
+
+/* Send data from a char device over to the guest */
+static void chr_read(void *opaque, const void *buf, size_t size)
+{
+ VirtIORNG *vrng = opaque;
+ VirtIODevice *vdev = VIRTIO_DEVICE(vrng);
+ VirtQueueElement elem;
+ size_t len;
+ int offset;
+
+ if (!is_guest_ready(vrng)) {
+ return;
+ }
+
+ vrng->quota_remaining -= size;
+
+ offset = 0;
+ while (offset < size) {
+ if (!virtqueue_pop(vrng->vq, &elem)) {
+ break;
+ }
+ len = iov_from_buf(elem.in_sg, elem.in_num,
+ 0, buf + offset, size - offset);
+ offset += len;
+
+ virtqueue_push(vrng->vq, &elem, len);
+ trace_virtio_rng_pushed(vrng, len);
+ }
+ virtio_notify(vdev, vrng->vq);
+}
+
+static void virtio_rng_process(VirtIORNG *vrng)
+{
+ size_t size;
+ unsigned quota;
+
+ if (!is_guest_ready(vrng)) {
+ return;
+ }
+
+ if (vrng->activate_timer) {
+ timer_mod(vrng->rate_limit_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + vrng->conf.period_ms);
+ vrng->activate_timer = false;
+ }
+
+ if (vrng->quota_remaining < 0) {
+ quota = 0;
+ } else {
+ quota = MIN((uint64_t)vrng->quota_remaining, (uint64_t)UINT32_MAX);
+ }
+ size = get_request_size(vrng->vq, quota);
+
+ trace_virtio_rng_request(vrng, size, quota);
+
+ size = MIN(vrng->quota_remaining, size);
+ if (size) {
+ rng_backend_request_entropy(vrng->rng, size, chr_read, vrng);
+ }
+}
+
+static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIORNG *vrng = VIRTIO_RNG(vdev);
+ virtio_rng_process(vrng);
+}
+
+static uint64_t get_features(VirtIODevice *vdev, uint64_t f, Error **errp)
+{
+ return f;
+}
+
+static void virtio_rng_save(QEMUFile *f, void *opaque)
+{
+ VirtIODevice *vdev = opaque;
+
+ virtio_save(vdev, f);
+}
+
+static int virtio_rng_load(QEMUFile *f, void *opaque, int version_id)
+{
+ VirtIORNG *vrng = opaque;
+ int ret;
+
+ if (version_id != 1) {
+ return -EINVAL;
+ }
+ ret = virtio_load(VIRTIO_DEVICE(vrng), f, version_id);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* We may have an element ready but couldn't process it due to a quota
+ * limit. Make sure to try again after live migration when the quota may
+ * have been reset.
+ */
+ virtio_rng_process(vrng);
+
+ return 0;
+}
+
+static void check_rate_limit(void *opaque)
+{
+ VirtIORNG *vrng = opaque;
+
+ vrng->quota_remaining = vrng->conf.max_bytes;
+ virtio_rng_process(vrng);
+ vrng->activate_timer = true;
+}
+
+static void virtio_rng_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIORNG *vrng = VIRTIO_RNG(dev);
+ Error *local_err = NULL;
+
+ if (vrng->conf.period_ms <= 0) {
+ error_setg(errp, "'period' parameter expects a positive integer");
+ return;
+ }
+
+ /* Workaround: Property parsing does not enforce unsigned integers,
+ * So this is a hack to reject such numbers. */
+ if (vrng->conf.max_bytes > INT64_MAX) {
+ error_setg(errp, "'max-bytes' parameter must be non-negative, "
+ "and less than 2^63");
+ return;
+ }
+
+ if (vrng->conf.rng == NULL) {
+ vrng->conf.default_backend = RNG_RANDOM(object_new(TYPE_RNG_RANDOM));
+
+ user_creatable_complete(OBJECT(vrng->conf.default_backend),
+ &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ object_unref(OBJECT(vrng->conf.default_backend));
+ return;
+ }
+
+ object_property_add_child(OBJECT(dev),
+ "default-backend",
+ OBJECT(vrng->conf.default_backend),
+ NULL);
+
+ /* The child property took a reference, we can safely drop ours now */
+ object_unref(OBJECT(vrng->conf.default_backend));
+
+ object_property_set_link(OBJECT(dev),
+ OBJECT(vrng->conf.default_backend),
+ "rng", NULL);
+ }
+
+ vrng->rng = vrng->conf.rng;
+ if (vrng->rng == NULL) {
+ error_setg(errp, "'rng' parameter expects a valid object");
+ return;
+ }
+
+ virtio_init(vdev, "virtio-rng", VIRTIO_ID_RNG, 0);
+
+ vrng->vq = virtio_add_queue(vdev, 8, handle_input);
+ vrng->quota_remaining = vrng->conf.max_bytes;
+ vrng->rate_limit_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ check_rate_limit, vrng);
+ vrng->activate_timer = true;
+ register_savevm(dev, "virtio-rng", -1, 1, virtio_rng_save,
+ virtio_rng_load, vrng);
+}
+
+static void virtio_rng_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIORNG *vrng = VIRTIO_RNG(dev);
+
+ timer_del(vrng->rate_limit_timer);
+ timer_free(vrng->rate_limit_timer);
+ unregister_savevm(dev, "virtio-rng", vrng);
+ virtio_cleanup(vdev);
+}
+
+static Property virtio_rng_properties[] = {
+ /* Set a default rate limit of 2^47 bytes per minute or roughly 2TB/s. If
+ * you have an entropy source capable of generating more entropy than this
+ * and you can pass it through via virtio-rng, then hats off to you. Until
+ * then, this is unlimited for all practical purposes.
+ */
+ DEFINE_PROP_UINT64("max-bytes", VirtIORNG, conf.max_bytes, INT64_MAX),
+ DEFINE_PROP_UINT32("period", VirtIORNG, conf.period_ms, 1 << 16),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_rng_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ dc->props = virtio_rng_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ vdc->realize = virtio_rng_device_realize;
+ vdc->unrealize = virtio_rng_device_unrealize;
+ vdc->get_features = get_features;
+}
+
+static void virtio_rng_initfn(Object *obj)
+{
+ VirtIORNG *vrng = VIRTIO_RNG(obj);
+
+ object_property_add_link(obj, "rng", TYPE_RNG_BACKEND,
+ (Object **)&vrng->conf.rng,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_UNREF_ON_RELEASE, NULL);
+}
+
+static const TypeInfo virtio_rng_info = {
+ .name = TYPE_VIRTIO_RNG,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIORNG),
+ .instance_init = virtio_rng_initfn,
+ .class_init = virtio_rng_class_init,
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_rng_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
new file mode 100644
index 00000000..d24f7755
--- /dev/null
+++ b/hw/virtio/virtio.c
@@ -0,0 +1,1651 @@
+/*
+ * Virtio Support
+ *
+ * Copyright IBM, Corp. 2007
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include <inttypes.h>
+
+#include "trace.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+#include "hw/virtio/virtio.h"
+#include "qemu/atomic.h"
+#include "hw/virtio/virtio-bus.h"
+#include "migration/migration.h"
+#include "hw/virtio/virtio-access.h"
+
+/*
+ * The alignment to use between consumer and producer parts of vring.
+ * x86 pagesize again. This is the default, used by transports like PCI
+ * which don't provide a means for the guest to tell the host the alignment.
+ */
+#define VIRTIO_PCI_VRING_ALIGN 4096
+
+typedef struct VRingDesc
+{
+ uint64_t addr;
+ uint32_t len;
+ uint16_t flags;
+ uint16_t next;
+} VRingDesc;
+
+typedef struct VRingAvail
+{
+ uint16_t flags;
+ uint16_t idx;
+ uint16_t ring[0];
+} VRingAvail;
+
+typedef struct VRingUsedElem
+{
+ uint32_t id;
+ uint32_t len;
+} VRingUsedElem;
+
+typedef struct VRingUsed
+{
+ uint16_t flags;
+ uint16_t idx;
+ VRingUsedElem ring[0];
+} VRingUsed;
+
+typedef struct VRing
+{
+ unsigned int num;
+ unsigned int align;
+ hwaddr desc;
+ hwaddr avail;
+ hwaddr used;
+} VRing;
+
+struct VirtQueue
+{
+ VRing vring;
+ uint16_t last_avail_idx;
+ /* Last used index value we have signalled on */
+ uint16_t signalled_used;
+
+ /* Last used index value we have signalled on */
+ bool signalled_used_valid;
+
+ /* Notification enabled? */
+ bool notification;
+
+ uint16_t queue_index;
+
+ int inuse;
+
+ uint16_t vector;
+ void (*handle_output)(VirtIODevice *vdev, VirtQueue *vq);
+ VirtIODevice *vdev;
+ EventNotifier guest_notifier;
+ EventNotifier host_notifier;
+ QLIST_ENTRY(VirtQueue) node;
+};
+
+/* virt queue functions */
+void virtio_queue_update_rings(VirtIODevice *vdev, int n)
+{
+ VRing *vring = &vdev->vq[n].vring;
+
+ if (!vring->desc) {
+ /* not yet setup -> nothing to do */
+ return;
+ }
+ vring->avail = vring->desc + vring->num * sizeof(VRingDesc);
+ vring->used = vring_align(vring->avail +
+ offsetof(VRingAvail, ring[vring->num]),
+ vring->align);
+}
+
+static inline uint64_t vring_desc_addr(VirtIODevice *vdev, hwaddr desc_pa,
+ int i)
+{
+ hwaddr pa;
+ pa = desc_pa + sizeof(VRingDesc) * i + offsetof(VRingDesc, addr);
+ return virtio_ldq_phys(vdev, pa);
+}
+
+static inline uint32_t vring_desc_len(VirtIODevice *vdev, hwaddr desc_pa, int i)
+{
+ hwaddr pa;
+ pa = desc_pa + sizeof(VRingDesc) * i + offsetof(VRingDesc, len);
+ return virtio_ldl_phys(vdev, pa);
+}
+
+static inline uint16_t vring_desc_flags(VirtIODevice *vdev, hwaddr desc_pa,
+ int i)
+{
+ hwaddr pa;
+ pa = desc_pa + sizeof(VRingDesc) * i + offsetof(VRingDesc, flags);
+ return virtio_lduw_phys(vdev, pa);
+}
+
+static inline uint16_t vring_desc_next(VirtIODevice *vdev, hwaddr desc_pa,
+ int i)
+{
+ hwaddr pa;
+ pa = desc_pa + sizeof(VRingDesc) * i + offsetof(VRingDesc, next);
+ return virtio_lduw_phys(vdev, pa);
+}
+
+static inline uint16_t vring_avail_flags(VirtQueue *vq)
+{
+ hwaddr pa;
+ pa = vq->vring.avail + offsetof(VRingAvail, flags);
+ return virtio_lduw_phys(vq->vdev, pa);
+}
+
+static inline uint16_t vring_avail_idx(VirtQueue *vq)
+{
+ hwaddr pa;
+ pa = vq->vring.avail + offsetof(VRingAvail, idx);
+ return virtio_lduw_phys(vq->vdev, pa);
+}
+
+static inline uint16_t vring_avail_ring(VirtQueue *vq, int i)
+{
+ hwaddr pa;
+ pa = vq->vring.avail + offsetof(VRingAvail, ring[i]);
+ return virtio_lduw_phys(vq->vdev, pa);
+}
+
+static inline uint16_t vring_get_used_event(VirtQueue *vq)
+{
+ return vring_avail_ring(vq, vq->vring.num);
+}
+
+static inline void vring_used_ring_id(VirtQueue *vq, int i, uint32_t val)
+{
+ hwaddr pa;
+ pa = vq->vring.used + offsetof(VRingUsed, ring[i].id);
+ virtio_stl_phys(vq->vdev, pa, val);
+}
+
+static inline void vring_used_ring_len(VirtQueue *vq, int i, uint32_t val)
+{
+ hwaddr pa;
+ pa = vq->vring.used + offsetof(VRingUsed, ring[i].len);
+ virtio_stl_phys(vq->vdev, pa, val);
+}
+
+static uint16_t vring_used_idx(VirtQueue *vq)
+{
+ hwaddr pa;
+ pa = vq->vring.used + offsetof(VRingUsed, idx);
+ return virtio_lduw_phys(vq->vdev, pa);
+}
+
+static inline void vring_used_idx_set(VirtQueue *vq, uint16_t val)
+{
+ hwaddr pa;
+ pa = vq->vring.used + offsetof(VRingUsed, idx);
+ virtio_stw_phys(vq->vdev, pa, val);
+}
+
+static inline void vring_used_flags_set_bit(VirtQueue *vq, int mask)
+{
+ VirtIODevice *vdev = vq->vdev;
+ hwaddr pa;
+ pa = vq->vring.used + offsetof(VRingUsed, flags);
+ virtio_stw_phys(vdev, pa, virtio_lduw_phys(vdev, pa) | mask);
+}
+
+static inline void vring_used_flags_unset_bit(VirtQueue *vq, int mask)
+{
+ VirtIODevice *vdev = vq->vdev;
+ hwaddr pa;
+ pa = vq->vring.used + offsetof(VRingUsed, flags);
+ virtio_stw_phys(vdev, pa, virtio_lduw_phys(vdev, pa) & ~mask);
+}
+
+static inline void vring_set_avail_event(VirtQueue *vq, uint16_t val)
+{
+ hwaddr pa;
+ if (!vq->notification) {
+ return;
+ }
+ pa = vq->vring.used + offsetof(VRingUsed, ring[vq->vring.num]);
+ virtio_stw_phys(vq->vdev, pa, val);
+}
+
+void virtio_queue_set_notification(VirtQueue *vq, int enable)
+{
+ vq->notification = enable;
+ if (virtio_vdev_has_feature(vq->vdev, VIRTIO_RING_F_EVENT_IDX)) {
+ vring_set_avail_event(vq, vring_avail_idx(vq));
+ } else if (enable) {
+ vring_used_flags_unset_bit(vq, VRING_USED_F_NO_NOTIFY);
+ } else {
+ vring_used_flags_set_bit(vq, VRING_USED_F_NO_NOTIFY);
+ }
+ if (enable) {
+ /* Expose avail event/used flags before caller checks the avail idx. */
+ smp_mb();
+ }
+}
+
+int virtio_queue_ready(VirtQueue *vq)
+{
+ return vq->vring.avail != 0;
+}
+
+int virtio_queue_empty(VirtQueue *vq)
+{
+ return vring_avail_idx(vq) == vq->last_avail_idx;
+}
+
+static void virtqueue_unmap_sg(VirtQueue *vq, const VirtQueueElement *elem,
+ unsigned int len)
+{
+ unsigned int offset;
+ int i;
+
+ offset = 0;
+ for (i = 0; i < elem->in_num; i++) {
+ size_t size = MIN(len - offset, elem->in_sg[i].iov_len);
+
+ cpu_physical_memory_unmap(elem->in_sg[i].iov_base,
+ elem->in_sg[i].iov_len,
+ 1, size);
+
+ offset += size;
+ }
+
+ for (i = 0; i < elem->out_num; i++)
+ cpu_physical_memory_unmap(elem->out_sg[i].iov_base,
+ elem->out_sg[i].iov_len,
+ 0, elem->out_sg[i].iov_len);
+}
+
+void virtqueue_discard(VirtQueue *vq, const VirtQueueElement *elem,
+ unsigned int len)
+{
+ vq->last_avail_idx--;
+ virtqueue_unmap_sg(vq, elem, len);
+}
+
+void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem,
+ unsigned int len, unsigned int idx)
+{
+ trace_virtqueue_fill(vq, elem, len, idx);
+
+ virtqueue_unmap_sg(vq, elem, len);
+
+ idx = (idx + vring_used_idx(vq)) % vq->vring.num;
+
+ /* Get a pointer to the next entry in the used ring. */
+ vring_used_ring_id(vq, idx, elem->index);
+ vring_used_ring_len(vq, idx, len);
+}
+
+void virtqueue_flush(VirtQueue *vq, unsigned int count)
+{
+ uint16_t old, new;
+ /* Make sure buffer is written before we update index. */
+ smp_wmb();
+ trace_virtqueue_flush(vq, count);
+ old = vring_used_idx(vq);
+ new = old + count;
+ vring_used_idx_set(vq, new);
+ vq->inuse -= count;
+ if (unlikely((int16_t)(new - vq->signalled_used) < (uint16_t)(new - old)))
+ vq->signalled_used_valid = false;
+}
+
+void virtqueue_push(VirtQueue *vq, const VirtQueueElement *elem,
+ unsigned int len)
+{
+ virtqueue_fill(vq, elem, len, 0);
+ virtqueue_flush(vq, 1);
+}
+
+static int virtqueue_num_heads(VirtQueue *vq, unsigned int idx)
+{
+ uint16_t num_heads = vring_avail_idx(vq) - idx;
+
+ /* Check it isn't doing very strange things with descriptor numbers. */
+ if (num_heads > vq->vring.num) {
+ error_report("Guest moved used index from %u to %u",
+ idx, vring_avail_idx(vq));
+ exit(1);
+ }
+ /* On success, callers read a descriptor at vq->last_avail_idx.
+ * Make sure descriptor read does not bypass avail index read. */
+ if (num_heads) {
+ smp_rmb();
+ }
+
+ return num_heads;
+}
+
+static unsigned int virtqueue_get_head(VirtQueue *vq, unsigned int idx)
+{
+ unsigned int head;
+
+ /* Grab the next descriptor number they're advertising, and increment
+ * the index we've seen. */
+ head = vring_avail_ring(vq, idx % vq->vring.num);
+
+ /* If their number is silly, that's a fatal mistake. */
+ if (head >= vq->vring.num) {
+ error_report("Guest says index %u is available", head);
+ exit(1);
+ }
+
+ return head;
+}
+
+static unsigned virtqueue_next_desc(VirtIODevice *vdev, hwaddr desc_pa,
+ unsigned int i, unsigned int max)
+{
+ unsigned int next;
+
+ /* If this descriptor says it doesn't chain, we're done. */
+ if (!(vring_desc_flags(vdev, desc_pa, i) & VRING_DESC_F_NEXT)) {
+ return max;
+ }
+
+ /* Check they're not leading us off end of descriptors. */
+ next = vring_desc_next(vdev, desc_pa, i);
+ /* Make sure compiler knows to grab that: we don't want it changing! */
+ smp_wmb();
+
+ if (next >= max) {
+ error_report("Desc next is %u", next);
+ exit(1);
+ }
+
+ return next;
+}
+
+void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes,
+ unsigned int *out_bytes,
+ unsigned max_in_bytes, unsigned max_out_bytes)
+{
+ unsigned int idx;
+ unsigned int total_bufs, in_total, out_total;
+
+ idx = vq->last_avail_idx;
+
+ total_bufs = in_total = out_total = 0;
+ while (virtqueue_num_heads(vq, idx)) {
+ VirtIODevice *vdev = vq->vdev;
+ unsigned int max, num_bufs, indirect = 0;
+ hwaddr desc_pa;
+ int i;
+
+ max = vq->vring.num;
+ num_bufs = total_bufs;
+ i = virtqueue_get_head(vq, idx++);
+ desc_pa = vq->vring.desc;
+
+ if (vring_desc_flags(vdev, desc_pa, i) & VRING_DESC_F_INDIRECT) {
+ if (vring_desc_len(vdev, desc_pa, i) % sizeof(VRingDesc)) {
+ error_report("Invalid size for indirect buffer table");
+ exit(1);
+ }
+
+ /* If we've got too many, that implies a descriptor loop. */
+ if (num_bufs >= max) {
+ error_report("Looped descriptor");
+ exit(1);
+ }
+
+ /* loop over the indirect descriptor table */
+ indirect = 1;
+ max = vring_desc_len(vdev, desc_pa, i) / sizeof(VRingDesc);
+ desc_pa = vring_desc_addr(vdev, desc_pa, i);
+ num_bufs = i = 0;
+ }
+
+ do {
+ /* If we've got too many, that implies a descriptor loop. */
+ if (++num_bufs > max) {
+ error_report("Looped descriptor");
+ exit(1);
+ }
+
+ if (vring_desc_flags(vdev, desc_pa, i) & VRING_DESC_F_WRITE) {
+ in_total += vring_desc_len(vdev, desc_pa, i);
+ } else {
+ out_total += vring_desc_len(vdev, desc_pa, i);
+ }
+ if (in_total >= max_in_bytes && out_total >= max_out_bytes) {
+ goto done;
+ }
+ } while ((i = virtqueue_next_desc(vdev, desc_pa, i, max)) != max);
+
+ if (!indirect)
+ total_bufs = num_bufs;
+ else
+ total_bufs++;
+ }
+done:
+ if (in_bytes) {
+ *in_bytes = in_total;
+ }
+ if (out_bytes) {
+ *out_bytes = out_total;
+ }
+}
+
+int virtqueue_avail_bytes(VirtQueue *vq, unsigned int in_bytes,
+ unsigned int out_bytes)
+{
+ unsigned int in_total, out_total;
+
+ virtqueue_get_avail_bytes(vq, &in_total, &out_total, in_bytes, out_bytes);
+ return in_bytes <= in_total && out_bytes <= out_total;
+}
+
+void virtqueue_map_sg(struct iovec *sg, hwaddr *addr,
+ size_t num_sg, int is_write)
+{
+ unsigned int i;
+ hwaddr len;
+
+ if (num_sg > VIRTQUEUE_MAX_SIZE) {
+ error_report("virtio: map attempt out of bounds: %zd > %d",
+ num_sg, VIRTQUEUE_MAX_SIZE);
+ exit(1);
+ }
+
+ for (i = 0; i < num_sg; i++) {
+ len = sg[i].iov_len;
+ sg[i].iov_base = cpu_physical_memory_map(addr[i], &len, is_write);
+ if (sg[i].iov_base == NULL || len != sg[i].iov_len) {
+ error_report("virtio: error trying to map MMIO memory");
+ exit(1);
+ }
+ }
+}
+
+int virtqueue_pop(VirtQueue *vq, VirtQueueElement *elem)
+{
+ unsigned int i, head, max;
+ hwaddr desc_pa = vq->vring.desc;
+ VirtIODevice *vdev = vq->vdev;
+
+ if (!virtqueue_num_heads(vq, vq->last_avail_idx))
+ return 0;
+
+ /* When we start there are none of either input nor output. */
+ elem->out_num = elem->in_num = 0;
+
+ max = vq->vring.num;
+
+ i = head = virtqueue_get_head(vq, vq->last_avail_idx++);
+ if (virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
+ vring_set_avail_event(vq, vq->last_avail_idx);
+ }
+
+ if (vring_desc_flags(vdev, desc_pa, i) & VRING_DESC_F_INDIRECT) {
+ if (vring_desc_len(vdev, desc_pa, i) % sizeof(VRingDesc)) {
+ error_report("Invalid size for indirect buffer table");
+ exit(1);
+ }
+
+ /* loop over the indirect descriptor table */
+ max = vring_desc_len(vdev, desc_pa, i) / sizeof(VRingDesc);
+ desc_pa = vring_desc_addr(vdev, desc_pa, i);
+ i = 0;
+ }
+
+ /* Collect all the descriptors */
+ do {
+ struct iovec *sg;
+
+ if (vring_desc_flags(vdev, desc_pa, i) & VRING_DESC_F_WRITE) {
+ if (elem->in_num >= ARRAY_SIZE(elem->in_sg)) {
+ error_report("Too many write descriptors in indirect table");
+ exit(1);
+ }
+ elem->in_addr[elem->in_num] = vring_desc_addr(vdev, desc_pa, i);
+ sg = &elem->in_sg[elem->in_num++];
+ } else {
+ if (elem->out_num >= ARRAY_SIZE(elem->out_sg)) {
+ error_report("Too many read descriptors in indirect table");
+ exit(1);
+ }
+ elem->out_addr[elem->out_num] = vring_desc_addr(vdev, desc_pa, i);
+ sg = &elem->out_sg[elem->out_num++];
+ }
+
+ sg->iov_len = vring_desc_len(vdev, desc_pa, i);
+
+ /* If we've got too many, that implies a descriptor loop. */
+ if ((elem->in_num + elem->out_num) > max) {
+ error_report("Looped descriptor");
+ exit(1);
+ }
+ } while ((i = virtqueue_next_desc(vdev, desc_pa, i, max)) != max);
+
+ /* Now map what we have collected */
+ virtqueue_map_sg(elem->in_sg, elem->in_addr, elem->in_num, 1);
+ virtqueue_map_sg(elem->out_sg, elem->out_addr, elem->out_num, 0);
+
+ elem->index = head;
+
+ vq->inuse++;
+
+ trace_virtqueue_pop(vq, elem, elem->in_num, elem->out_num);
+ return elem->in_num + elem->out_num;
+}
+
+/* virtio device */
+static void virtio_notify_vector(VirtIODevice *vdev, uint16_t vector)
+{
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+
+ if (k->notify) {
+ k->notify(qbus->parent, vector);
+ }
+}
+
+void virtio_update_irq(VirtIODevice *vdev)
+{
+ virtio_notify_vector(vdev, VIRTIO_NO_VECTOR);
+}
+
+static int virtio_validate_features(VirtIODevice *vdev)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+
+ if (k->validate_features) {
+ return k->validate_features(vdev);
+ } else {
+ return 0;
+ }
+}
+
+int virtio_set_status(VirtIODevice *vdev, uint8_t val)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ trace_virtio_set_status(vdev, val);
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
+ if (!(vdev->status & VIRTIO_CONFIG_S_FEATURES_OK) &&
+ val & VIRTIO_CONFIG_S_FEATURES_OK) {
+ int ret = virtio_validate_features(vdev);
+
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+ if (k->set_status) {
+ k->set_status(vdev, val);
+ }
+ vdev->status = val;
+ return 0;
+}
+
+bool target_words_bigendian(void);
+static enum virtio_device_endian virtio_default_endian(void)
+{
+ if (target_words_bigendian()) {
+ return VIRTIO_DEVICE_ENDIAN_BIG;
+ } else {
+ return VIRTIO_DEVICE_ENDIAN_LITTLE;
+ }
+}
+
+static enum virtio_device_endian virtio_current_cpu_endian(void)
+{
+ CPUClass *cc = CPU_GET_CLASS(current_cpu);
+
+ if (cc->virtio_is_big_endian(current_cpu)) {
+ return VIRTIO_DEVICE_ENDIAN_BIG;
+ } else {
+ return VIRTIO_DEVICE_ENDIAN_LITTLE;
+ }
+}
+
+void virtio_reset(void *opaque)
+{
+ VirtIODevice *vdev = opaque;
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ int i;
+
+ virtio_set_status(vdev, 0);
+ if (current_cpu) {
+ /* Guest initiated reset */
+ vdev->device_endian = virtio_current_cpu_endian();
+ } else {
+ /* System reset */
+ vdev->device_endian = virtio_default_endian();
+ }
+
+ if (k->reset) {
+ k->reset(vdev);
+ }
+
+ vdev->guest_features = 0;
+ vdev->queue_sel = 0;
+ vdev->status = 0;
+ vdev->isr = 0;
+ vdev->config_vector = VIRTIO_NO_VECTOR;
+ virtio_notify_vector(vdev, vdev->config_vector);
+
+ for(i = 0; i < VIRTIO_QUEUE_MAX; i++) {
+ vdev->vq[i].vring.desc = 0;
+ vdev->vq[i].vring.avail = 0;
+ vdev->vq[i].vring.used = 0;
+ vdev->vq[i].last_avail_idx = 0;
+ virtio_queue_set_vector(vdev, i, VIRTIO_NO_VECTOR);
+ vdev->vq[i].signalled_used = 0;
+ vdev->vq[i].signalled_used_valid = false;
+ vdev->vq[i].notification = true;
+ }
+}
+
+uint32_t virtio_config_readb(VirtIODevice *vdev, uint32_t addr)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint8_t val;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return (uint32_t)-1;
+ }
+
+ k->get_config(vdev, vdev->config);
+
+ val = ldub_p(vdev->config + addr);
+ return val;
+}
+
+uint32_t virtio_config_readw(VirtIODevice *vdev, uint32_t addr)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint16_t val;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return (uint32_t)-1;
+ }
+
+ k->get_config(vdev, vdev->config);
+
+ val = lduw_p(vdev->config + addr);
+ return val;
+}
+
+uint32_t virtio_config_readl(VirtIODevice *vdev, uint32_t addr)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint32_t val;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return (uint32_t)-1;
+ }
+
+ k->get_config(vdev, vdev->config);
+
+ val = ldl_p(vdev->config + addr);
+ return val;
+}
+
+void virtio_config_writeb(VirtIODevice *vdev, uint32_t addr, uint32_t data)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint8_t val = data;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return;
+ }
+
+ stb_p(vdev->config + addr, val);
+
+ if (k->set_config) {
+ k->set_config(vdev, vdev->config);
+ }
+}
+
+void virtio_config_writew(VirtIODevice *vdev, uint32_t addr, uint32_t data)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint16_t val = data;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return;
+ }
+
+ stw_p(vdev->config + addr, val);
+
+ if (k->set_config) {
+ k->set_config(vdev, vdev->config);
+ }
+}
+
+void virtio_config_writel(VirtIODevice *vdev, uint32_t addr, uint32_t data)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint32_t val = data;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return;
+ }
+
+ stl_p(vdev->config + addr, val);
+
+ if (k->set_config) {
+ k->set_config(vdev, vdev->config);
+ }
+}
+
+uint32_t virtio_config_modern_readb(VirtIODevice *vdev, uint32_t addr)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint8_t val;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return (uint32_t)-1;
+ }
+
+ k->get_config(vdev, vdev->config);
+
+ val = ldub_p(vdev->config + addr);
+ return val;
+}
+
+uint32_t virtio_config_modern_readw(VirtIODevice *vdev, uint32_t addr)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint16_t val;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return (uint32_t)-1;
+ }
+
+ k->get_config(vdev, vdev->config);
+
+ val = lduw_le_p(vdev->config + addr);
+ return val;
+}
+
+uint32_t virtio_config_modern_readl(VirtIODevice *vdev, uint32_t addr)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint32_t val;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return (uint32_t)-1;
+ }
+
+ k->get_config(vdev, vdev->config);
+
+ val = ldl_le_p(vdev->config + addr);
+ return val;
+}
+
+void virtio_config_modern_writeb(VirtIODevice *vdev,
+ uint32_t addr, uint32_t data)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint8_t val = data;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return;
+ }
+
+ stb_p(vdev->config + addr, val);
+
+ if (k->set_config) {
+ k->set_config(vdev, vdev->config);
+ }
+}
+
+void virtio_config_modern_writew(VirtIODevice *vdev,
+ uint32_t addr, uint32_t data)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint16_t val = data;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return;
+ }
+
+ stw_le_p(vdev->config + addr, val);
+
+ if (k->set_config) {
+ k->set_config(vdev, vdev->config);
+ }
+}
+
+void virtio_config_modern_writel(VirtIODevice *vdev,
+ uint32_t addr, uint32_t data)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint32_t val = data;
+
+ if (addr + sizeof(val) > vdev->config_len) {
+ return;
+ }
+
+ stl_le_p(vdev->config + addr, val);
+
+ if (k->set_config) {
+ k->set_config(vdev, vdev->config);
+ }
+}
+
+void virtio_queue_set_addr(VirtIODevice *vdev, int n, hwaddr addr)
+{
+ vdev->vq[n].vring.desc = addr;
+ virtio_queue_update_rings(vdev, n);
+}
+
+hwaddr virtio_queue_get_addr(VirtIODevice *vdev, int n)
+{
+ return vdev->vq[n].vring.desc;
+}
+
+void virtio_queue_set_rings(VirtIODevice *vdev, int n, hwaddr desc,
+ hwaddr avail, hwaddr used)
+{
+ vdev->vq[n].vring.desc = desc;
+ vdev->vq[n].vring.avail = avail;
+ vdev->vq[n].vring.used = used;
+}
+
+void virtio_queue_set_num(VirtIODevice *vdev, int n, int num)
+{
+ /* Don't allow guest to flip queue between existent and
+ * nonexistent states, or to set it to an invalid size.
+ */
+ if (!!num != !!vdev->vq[n].vring.num ||
+ num > VIRTQUEUE_MAX_SIZE ||
+ num < 0) {
+ return;
+ }
+ vdev->vq[n].vring.num = num;
+}
+
+VirtQueue *virtio_vector_first_queue(VirtIODevice *vdev, uint16_t vector)
+{
+ return QLIST_FIRST(&vdev->vector_queues[vector]);
+}
+
+VirtQueue *virtio_vector_next_queue(VirtQueue *vq)
+{
+ return QLIST_NEXT(vq, node);
+}
+
+int virtio_queue_get_num(VirtIODevice *vdev, int n)
+{
+ return vdev->vq[n].vring.num;
+}
+
+int virtio_get_num_queues(VirtIODevice *vdev)
+{
+ int i;
+
+ for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
+ if (!virtio_queue_get_num(vdev, i)) {
+ break;
+ }
+ }
+
+ return i;
+}
+
+int virtio_queue_get_id(VirtQueue *vq)
+{
+ VirtIODevice *vdev = vq->vdev;
+ assert(vq >= &vdev->vq[0] && vq < &vdev->vq[VIRTIO_QUEUE_MAX]);
+ return vq - &vdev->vq[0];
+}
+
+void virtio_queue_set_align(VirtIODevice *vdev, int n, int align)
+{
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+
+ /* virtio-1 compliant devices cannot change the alignment */
+ if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
+ error_report("tried to modify queue alignment for virtio-1 device");
+ return;
+ }
+ /* Check that the transport told us it was going to do this
+ * (so a buggy transport will immediately assert rather than
+ * silently failing to migrate this state)
+ */
+ assert(k->has_variable_vring_alignment);
+
+ vdev->vq[n].vring.align = align;
+ virtio_queue_update_rings(vdev, n);
+}
+
+void virtio_queue_notify_vq(VirtQueue *vq)
+{
+ if (vq->vring.desc && vq->handle_output) {
+ VirtIODevice *vdev = vq->vdev;
+
+ trace_virtio_queue_notify(vdev, vq - vdev->vq, vq);
+ vq->handle_output(vdev, vq);
+ }
+}
+
+void virtio_queue_notify(VirtIODevice *vdev, int n)
+{
+ virtio_queue_notify_vq(&vdev->vq[n]);
+}
+
+uint16_t virtio_queue_vector(VirtIODevice *vdev, int n)
+{
+ return n < VIRTIO_QUEUE_MAX ? vdev->vq[n].vector :
+ VIRTIO_NO_VECTOR;
+}
+
+void virtio_queue_set_vector(VirtIODevice *vdev, int n, uint16_t vector)
+{
+ VirtQueue *vq = &vdev->vq[n];
+
+ if (n < VIRTIO_QUEUE_MAX) {
+ if (vdev->vector_queues &&
+ vdev->vq[n].vector != VIRTIO_NO_VECTOR) {
+ QLIST_REMOVE(vq, node);
+ }
+ vdev->vq[n].vector = vector;
+ if (vdev->vector_queues &&
+ vector != VIRTIO_NO_VECTOR) {
+ QLIST_INSERT_HEAD(&vdev->vector_queues[vector], vq, node);
+ }
+ }
+}
+
+VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size,
+ void (*handle_output)(VirtIODevice *, VirtQueue *))
+{
+ int i;
+
+ for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
+ if (vdev->vq[i].vring.num == 0)
+ break;
+ }
+
+ if (i == VIRTIO_QUEUE_MAX || queue_size > VIRTQUEUE_MAX_SIZE)
+ abort();
+
+ vdev->vq[i].vring.num = queue_size;
+ vdev->vq[i].vring.align = VIRTIO_PCI_VRING_ALIGN;
+ vdev->vq[i].handle_output = handle_output;
+
+ return &vdev->vq[i];
+}
+
+void virtio_del_queue(VirtIODevice *vdev, int n)
+{
+ if (n < 0 || n >= VIRTIO_QUEUE_MAX) {
+ abort();
+ }
+
+ vdev->vq[n].vring.num = 0;
+}
+
+void virtio_irq(VirtQueue *vq)
+{
+ trace_virtio_irq(vq);
+ vq->vdev->isr |= 0x01;
+ virtio_notify_vector(vq->vdev, vq->vector);
+}
+
+static bool vring_notify(VirtIODevice *vdev, VirtQueue *vq)
+{
+ uint16_t old, new;
+ bool v;
+ /* We need to expose used array entries before checking used event. */
+ smp_mb();
+ /* Always notify when queue is empty (when feature acknowledge) */
+ if (virtio_vdev_has_feature(vdev, VIRTIO_F_NOTIFY_ON_EMPTY) &&
+ !vq->inuse && vring_avail_idx(vq) == vq->last_avail_idx) {
+ return true;
+ }
+
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
+ return !(vring_avail_flags(vq) & VRING_AVAIL_F_NO_INTERRUPT);
+ }
+
+ v = vq->signalled_used_valid;
+ vq->signalled_used_valid = true;
+ old = vq->signalled_used;
+ new = vq->signalled_used = vring_used_idx(vq);
+ return !v || vring_need_event(vring_get_used_event(vq), new, old);
+}
+
+void virtio_notify(VirtIODevice *vdev, VirtQueue *vq)
+{
+ if (!vring_notify(vdev, vq)) {
+ return;
+ }
+
+ trace_virtio_notify(vdev, vq);
+ vdev->isr |= 0x01;
+ virtio_notify_vector(vdev, vq->vector);
+}
+
+void virtio_notify_config(VirtIODevice *vdev)
+{
+ if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK))
+ return;
+
+ vdev->isr |= 0x03;
+ vdev->generation++;
+ virtio_notify_vector(vdev, vdev->config_vector);
+}
+
+static bool virtio_device_endian_needed(void *opaque)
+{
+ VirtIODevice *vdev = opaque;
+
+ assert(vdev->device_endian != VIRTIO_DEVICE_ENDIAN_UNKNOWN);
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
+ return vdev->device_endian != virtio_default_endian();
+ }
+ /* Devices conforming to VIRTIO 1.0 or later are always LE. */
+ return vdev->device_endian != VIRTIO_DEVICE_ENDIAN_LITTLE;
+}
+
+static bool virtio_64bit_features_needed(void *opaque)
+{
+ VirtIODevice *vdev = opaque;
+
+ return (vdev->host_features >> 32) != 0;
+}
+
+static bool virtio_virtqueue_needed(void *opaque)
+{
+ VirtIODevice *vdev = opaque;
+
+ return virtio_host_has_feature(vdev, VIRTIO_F_VERSION_1);
+}
+
+static void put_virtqueue_state(QEMUFile *f, void *pv, size_t size)
+{
+ VirtIODevice *vdev = pv;
+ int i;
+
+ for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
+ qemu_put_be64(f, vdev->vq[i].vring.avail);
+ qemu_put_be64(f, vdev->vq[i].vring.used);
+ }
+}
+
+static int get_virtqueue_state(QEMUFile *f, void *pv, size_t size)
+{
+ VirtIODevice *vdev = pv;
+ int i;
+
+ for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
+ vdev->vq[i].vring.avail = qemu_get_be64(f);
+ vdev->vq[i].vring.used = qemu_get_be64(f);
+ }
+ return 0;
+}
+
+static VMStateInfo vmstate_info_virtqueue = {
+ .name = "virtqueue_state",
+ .get = get_virtqueue_state,
+ .put = put_virtqueue_state,
+};
+
+static const VMStateDescription vmstate_virtio_virtqueues = {
+ .name = "virtio/virtqueues",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = &virtio_virtqueue_needed,
+ .fields = (VMStateField[]) {
+ {
+ .name = "virtqueues",
+ .version_id = 0,
+ .field_exists = NULL,
+ .size = 0,
+ .info = &vmstate_info_virtqueue,
+ .flags = VMS_SINGLE,
+ .offset = 0,
+ },
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_virtio_device_endian = {
+ .name = "virtio/device_endian",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = &virtio_device_endian_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(device_endian, VirtIODevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_virtio_64bit_features = {
+ .name = "virtio/64bit_features",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = &virtio_64bit_features_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(guest_features, VirtIODevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_virtio = {
+ .name = "virtio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_virtio_device_endian,
+ &vmstate_virtio_64bit_features,
+ &vmstate_virtio_virtqueues,
+ NULL
+ }
+};
+
+void virtio_save(VirtIODevice *vdev, QEMUFile *f)
+{
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
+ uint32_t guest_features_lo = (vdev->guest_features & 0xffffffff);
+ int i;
+
+ if (k->save_config) {
+ k->save_config(qbus->parent, f);
+ }
+
+ qemu_put_8s(f, &vdev->status);
+ qemu_put_8s(f, &vdev->isr);
+ qemu_put_be16s(f, &vdev->queue_sel);
+ qemu_put_be32s(f, &guest_features_lo);
+ qemu_put_be32(f, vdev->config_len);
+ qemu_put_buffer(f, vdev->config, vdev->config_len);
+
+ for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
+ if (vdev->vq[i].vring.num == 0)
+ break;
+ }
+
+ qemu_put_be32(f, i);
+
+ for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
+ if (vdev->vq[i].vring.num == 0)
+ break;
+
+ qemu_put_be32(f, vdev->vq[i].vring.num);
+ if (k->has_variable_vring_alignment) {
+ qemu_put_be32(f, vdev->vq[i].vring.align);
+ }
+ /* XXX virtio-1 devices */
+ qemu_put_be64(f, vdev->vq[i].vring.desc);
+ qemu_put_be16s(f, &vdev->vq[i].last_avail_idx);
+ if (k->save_queue) {
+ k->save_queue(qbus->parent, i, f);
+ }
+ }
+
+ if (vdc->save != NULL) {
+ vdc->save(vdev, f);
+ }
+
+ /* Subsections */
+ vmstate_save_state(f, &vmstate_virtio, vdev, NULL);
+}
+
+static int virtio_set_features_nocheck(VirtIODevice *vdev, uint64_t val)
+{
+ VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
+ bool bad = (val & ~(vdev->host_features)) != 0;
+
+ val &= vdev->host_features;
+ if (k->set_features) {
+ k->set_features(vdev, val);
+ }
+ vdev->guest_features = val;
+ return bad ? -1 : 0;
+}
+
+int virtio_set_features(VirtIODevice *vdev, uint64_t val)
+{
+ /*
+ * The driver must not attempt to set features after feature negotiation
+ * has finished.
+ */
+ if (vdev->status & VIRTIO_CONFIG_S_FEATURES_OK) {
+ return -EINVAL;
+ }
+ return virtio_set_features_nocheck(vdev, val);
+}
+
+int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
+{
+ int i, ret;
+ int32_t config_len;
+ uint32_t num;
+ uint32_t features;
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
+
+ /*
+ * We poison the endianness to ensure it does not get used before
+ * subsections have been loaded.
+ */
+ vdev->device_endian = VIRTIO_DEVICE_ENDIAN_UNKNOWN;
+
+ if (k->load_config) {
+ ret = k->load_config(qbus->parent, f);
+ if (ret)
+ return ret;
+ }
+
+ qemu_get_8s(f, &vdev->status);
+ qemu_get_8s(f, &vdev->isr);
+ qemu_get_be16s(f, &vdev->queue_sel);
+ if (vdev->queue_sel >= VIRTIO_QUEUE_MAX) {
+ return -1;
+ }
+ qemu_get_be32s(f, &features);
+
+ config_len = qemu_get_be32(f);
+
+ /*
+ * There are cases where the incoming config can be bigger or smaller
+ * than what we have; so load what we have space for, and skip
+ * any excess that's in the stream.
+ */
+ qemu_get_buffer(f, vdev->config, MIN(config_len, vdev->config_len));
+
+ while (config_len > vdev->config_len) {
+ qemu_get_byte(f);
+ config_len--;
+ }
+
+ num = qemu_get_be32(f);
+
+ if (num > VIRTIO_QUEUE_MAX) {
+ error_report("Invalid number of PCI queues: 0x%x", num);
+ return -1;
+ }
+
+ for (i = 0; i < num; i++) {
+ vdev->vq[i].vring.num = qemu_get_be32(f);
+ if (k->has_variable_vring_alignment) {
+ vdev->vq[i].vring.align = qemu_get_be32(f);
+ }
+ vdev->vq[i].vring.desc = qemu_get_be64(f);
+ qemu_get_be16s(f, &vdev->vq[i].last_avail_idx);
+ vdev->vq[i].signalled_used_valid = false;
+ vdev->vq[i].notification = true;
+
+ if (vdev->vq[i].vring.desc) {
+ /* XXX virtio-1 devices */
+ virtio_queue_update_rings(vdev, i);
+ } else if (vdev->vq[i].last_avail_idx) {
+ error_report("VQ %d address 0x0 "
+ "inconsistent with Host index 0x%x",
+ i, vdev->vq[i].last_avail_idx);
+ return -1;
+ }
+ if (k->load_queue) {
+ ret = k->load_queue(qbus->parent, i, f);
+ if (ret)
+ return ret;
+ }
+ }
+
+ virtio_notify_vector(vdev, VIRTIO_NO_VECTOR);
+
+ if (vdc->load != NULL) {
+ ret = vdc->load(vdev, f, version_id);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ /* Subsections */
+ ret = vmstate_load_state(f, &vmstate_virtio, vdev, 1);
+ if (ret) {
+ return ret;
+ }
+
+ if (vdev->device_endian == VIRTIO_DEVICE_ENDIAN_UNKNOWN) {
+ vdev->device_endian = virtio_default_endian();
+ }
+
+ if (virtio_64bit_features_needed(vdev)) {
+ /*
+ * Subsection load filled vdev->guest_features. Run them
+ * through virtio_set_features to sanity-check them against
+ * host_features.
+ */
+ uint64_t features64 = vdev->guest_features;
+ if (virtio_set_features_nocheck(vdev, features64) < 0) {
+ error_report("Features 0x%" PRIx64 " unsupported. "
+ "Allowed features: 0x%" PRIx64,
+ features64, vdev->host_features);
+ return -1;
+ }
+ } else {
+ if (virtio_set_features_nocheck(vdev, features) < 0) {
+ error_report("Features 0x%x unsupported. "
+ "Allowed features: 0x%" PRIx64,
+ features, vdev->host_features);
+ return -1;
+ }
+ }
+
+ for (i = 0; i < num; i++) {
+ if (vdev->vq[i].vring.desc) {
+ uint16_t nheads;
+ nheads = vring_avail_idx(&vdev->vq[i]) - vdev->vq[i].last_avail_idx;
+ /* Check it isn't doing strange things with descriptor numbers. */
+ if (nheads > vdev->vq[i].vring.num) {
+ error_report("VQ %d size 0x%x Guest index 0x%x "
+ "inconsistent with Host index 0x%x: delta 0x%x",
+ i, vdev->vq[i].vring.num,
+ vring_avail_idx(&vdev->vq[i]),
+ vdev->vq[i].last_avail_idx, nheads);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void virtio_cleanup(VirtIODevice *vdev)
+{
+ qemu_del_vm_change_state_handler(vdev->vmstate);
+ g_free(vdev->config);
+ g_free(vdev->vq);
+ g_free(vdev->vector_queues);
+}
+
+static void virtio_vmstate_change(void *opaque, int running, RunState state)
+{
+ VirtIODevice *vdev = opaque;
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ bool backend_run = running && (vdev->status & VIRTIO_CONFIG_S_DRIVER_OK);
+ vdev->vm_running = running;
+
+ if (backend_run) {
+ virtio_set_status(vdev, vdev->status);
+ }
+
+ if (k->vmstate_change) {
+ k->vmstate_change(qbus->parent, backend_run);
+ }
+
+ if (!backend_run) {
+ virtio_set_status(vdev, vdev->status);
+ }
+}
+
+void virtio_instance_init_common(Object *proxy_obj, void *data,
+ size_t vdev_size, const char *vdev_name)
+{
+ DeviceState *vdev = data;
+
+ object_initialize(vdev, vdev_size, vdev_name);
+ object_property_add_child(proxy_obj, "virtio-backend", OBJECT(vdev), NULL);
+ object_unref(OBJECT(vdev));
+ qdev_alias_all_properties(vdev, proxy_obj);
+}
+
+void virtio_init(VirtIODevice *vdev, const char *name,
+ uint16_t device_id, size_t config_size)
+{
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ int i;
+ int nvectors = k->query_nvectors ? k->query_nvectors(qbus->parent) : 0;
+
+ if (nvectors) {
+ vdev->vector_queues =
+ g_malloc0(sizeof(*vdev->vector_queues) * nvectors);
+ }
+
+ vdev->device_id = device_id;
+ vdev->status = 0;
+ vdev->isr = 0;
+ vdev->queue_sel = 0;
+ vdev->config_vector = VIRTIO_NO_VECTOR;
+ vdev->vq = g_malloc0(sizeof(VirtQueue) * VIRTIO_QUEUE_MAX);
+ vdev->vm_running = runstate_is_running();
+ for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
+ vdev->vq[i].vector = VIRTIO_NO_VECTOR;
+ vdev->vq[i].vdev = vdev;
+ vdev->vq[i].queue_index = i;
+ }
+
+ vdev->name = name;
+ vdev->config_len = config_size;
+ if (vdev->config_len) {
+ vdev->config = g_malloc0(config_size);
+ } else {
+ vdev->config = NULL;
+ }
+ vdev->vmstate = qemu_add_vm_change_state_handler(virtio_vmstate_change,
+ vdev);
+ vdev->device_endian = virtio_default_endian();
+}
+
+hwaddr virtio_queue_get_desc_addr(VirtIODevice *vdev, int n)
+{
+ return vdev->vq[n].vring.desc;
+}
+
+hwaddr virtio_queue_get_avail_addr(VirtIODevice *vdev, int n)
+{
+ return vdev->vq[n].vring.avail;
+}
+
+hwaddr virtio_queue_get_used_addr(VirtIODevice *vdev, int n)
+{
+ return vdev->vq[n].vring.used;
+}
+
+hwaddr virtio_queue_get_ring_addr(VirtIODevice *vdev, int n)
+{
+ return vdev->vq[n].vring.desc;
+}
+
+hwaddr virtio_queue_get_desc_size(VirtIODevice *vdev, int n)
+{
+ return sizeof(VRingDesc) * vdev->vq[n].vring.num;
+}
+
+hwaddr virtio_queue_get_avail_size(VirtIODevice *vdev, int n)
+{
+ return offsetof(VRingAvail, ring) +
+ sizeof(uint64_t) * vdev->vq[n].vring.num;
+}
+
+hwaddr virtio_queue_get_used_size(VirtIODevice *vdev, int n)
+{
+ return offsetof(VRingUsed, ring) +
+ sizeof(VRingUsedElem) * vdev->vq[n].vring.num;
+}
+
+hwaddr virtio_queue_get_ring_size(VirtIODevice *vdev, int n)
+{
+ return vdev->vq[n].vring.used - vdev->vq[n].vring.desc +
+ virtio_queue_get_used_size(vdev, n);
+}
+
+uint16_t virtio_queue_get_last_avail_idx(VirtIODevice *vdev, int n)
+{
+ return vdev->vq[n].last_avail_idx;
+}
+
+void virtio_queue_set_last_avail_idx(VirtIODevice *vdev, int n, uint16_t idx)
+{
+ vdev->vq[n].last_avail_idx = idx;
+}
+
+void virtio_queue_invalidate_signalled_used(VirtIODevice *vdev, int n)
+{
+ vdev->vq[n].signalled_used_valid = false;
+}
+
+VirtQueue *virtio_get_queue(VirtIODevice *vdev, int n)
+{
+ return vdev->vq + n;
+}
+
+uint16_t virtio_get_queue_index(VirtQueue *vq)
+{
+ return vq->queue_index;
+}
+
+static void virtio_queue_guest_notifier_read(EventNotifier *n)
+{
+ VirtQueue *vq = container_of(n, VirtQueue, guest_notifier);
+ if (event_notifier_test_and_clear(n)) {
+ virtio_irq(vq);
+ }
+}
+
+void virtio_queue_set_guest_notifier_fd_handler(VirtQueue *vq, bool assign,
+ bool with_irqfd)
+{
+ if (assign && !with_irqfd) {
+ event_notifier_set_handler(&vq->guest_notifier,
+ virtio_queue_guest_notifier_read);
+ } else {
+ event_notifier_set_handler(&vq->guest_notifier, NULL);
+ }
+ if (!assign) {
+ /* Test and clear notifier before closing it,
+ * in case poll callback didn't have time to run. */
+ virtio_queue_guest_notifier_read(&vq->guest_notifier);
+ }
+}
+
+EventNotifier *virtio_queue_get_guest_notifier(VirtQueue *vq)
+{
+ return &vq->guest_notifier;
+}
+
+static void virtio_queue_host_notifier_read(EventNotifier *n)
+{
+ VirtQueue *vq = container_of(n, VirtQueue, host_notifier);
+ if (event_notifier_test_and_clear(n)) {
+ virtio_queue_notify_vq(vq);
+ }
+}
+
+void virtio_queue_set_host_notifier_fd_handler(VirtQueue *vq, bool assign,
+ bool set_handler)
+{
+ if (assign && set_handler) {
+ event_notifier_set_handler(&vq->host_notifier,
+ virtio_queue_host_notifier_read);
+ } else {
+ event_notifier_set_handler(&vq->host_notifier, NULL);
+ }
+ if (!assign) {
+ /* Test and clear notifier before after disabling event,
+ * in case poll callback didn't have time to run. */
+ virtio_queue_host_notifier_read(&vq->host_notifier);
+ }
+}
+
+EventNotifier *virtio_queue_get_host_notifier(VirtQueue *vq)
+{
+ return &vq->host_notifier;
+}
+
+void virtio_device_set_child_bus_name(VirtIODevice *vdev, char *bus_name)
+{
+ g_free(vdev->bus_name);
+ vdev->bus_name = g_strdup(bus_name);
+}
+
+static void virtio_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(dev);
+ Error *err = NULL;
+
+ if (vdc->realize != NULL) {
+ vdc->realize(dev, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ }
+
+ virtio_bus_device_plugged(vdev, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+}
+
+static void virtio_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(dev);
+ Error *err = NULL;
+
+ virtio_bus_device_unplugged(vdev);
+
+ if (vdc->unrealize != NULL) {
+ vdc->unrealize(dev, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ }
+
+ g_free(vdev->bus_name);
+ vdev->bus_name = NULL;
+}
+
+static Property virtio_properties[] = {
+ DEFINE_VIRTIO_COMMON_FEATURES(VirtIODevice, host_features),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_device_class_init(ObjectClass *klass, void *data)
+{
+ /* Set the default value here. */
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = virtio_device_realize;
+ dc->unrealize = virtio_device_unrealize;
+ dc->bus_type = TYPE_VIRTIO_BUS;
+ dc->props = virtio_properties;
+}
+
+static const TypeInfo virtio_device_info = {
+ .name = TYPE_VIRTIO_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(VirtIODevice),
+ .class_init = virtio_device_class_init,
+ .abstract = true,
+ .class_size = sizeof(VirtioDeviceClass),
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_device_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/watchdog/Makefile.objs b/hw/watchdog/Makefile.objs
new file mode 100644
index 00000000..72e3ffd9
--- /dev/null
+++ b/hw/watchdog/Makefile.objs
@@ -0,0 +1,4 @@
+common-obj-y += watchdog.o
+common-obj-$(CONFIG_WDT_IB6300ESB) += wdt_i6300esb.o
+common-obj-$(CONFIG_WDT_IB700) += wdt_ib700.o
+common-obj-$(CONFIG_WDT_DIAG288) += wdt_diag288.o
diff --git a/hw/watchdog/watchdog.c b/hw/watchdog/watchdog.c
new file mode 100644
index 00000000..8d4b0eee
--- /dev/null
+++ b/hw/watchdog/watchdog.c
@@ -0,0 +1,152 @@
+/*
+ * Virtual hardware watchdog.
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * By Richard W.M. Jones (rjones@redhat.com).
+ */
+
+#include "qemu-common.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "qemu/queue.h"
+#include "qapi/qmp/types.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/watchdog.h"
+#include "qapi-event.h"
+#include "hw/nmi.h"
+
+/* Possible values for action parameter. */
+#define WDT_RESET 1 /* Hard reset. */
+#define WDT_SHUTDOWN 2 /* Shutdown. */
+#define WDT_POWEROFF 3 /* Quit. */
+#define WDT_PAUSE 4 /* Pause. */
+#define WDT_DEBUG 5 /* Prints a message and continues running. */
+#define WDT_NONE 6 /* Do nothing. */
+#define WDT_NMI 7 /* Inject nmi into the guest */
+
+static int watchdog_action = WDT_RESET;
+static QLIST_HEAD(watchdog_list, WatchdogTimerModel) watchdog_list;
+
+void watchdog_add_model(WatchdogTimerModel *model)
+{
+ QLIST_INSERT_HEAD(&watchdog_list, model, entry);
+}
+
+/* Returns:
+ * 0 = continue
+ * 1 = exit program with error
+ * 2 = exit program without error
+ */
+int select_watchdog(const char *p)
+{
+ WatchdogTimerModel *model;
+ QemuOpts *opts;
+
+ /* -watchdog ? lists available devices and exits cleanly. */
+ if (is_help_option(p)) {
+ QLIST_FOREACH(model, &watchdog_list, entry) {
+ fprintf(stderr, "\t%s\t%s\n",
+ model->wdt_name, model->wdt_description);
+ }
+ return 2;
+ }
+
+ QLIST_FOREACH(model, &watchdog_list, entry) {
+ if (strcasecmp(model->wdt_name, p) == 0) {
+ /* add the device */
+ opts = qemu_opts_create(qemu_find_opts("device"), NULL, 0,
+ &error_abort);
+ qemu_opt_set(opts, "driver", p, &error_abort);
+ return 0;
+ }
+ }
+
+ fprintf(stderr, "Unknown -watchdog device. Supported devices are:\n");
+ QLIST_FOREACH(model, &watchdog_list, entry) {
+ fprintf(stderr, "\t%s\t%s\n",
+ model->wdt_name, model->wdt_description);
+ }
+ return 1;
+}
+
+int select_watchdog_action(const char *p)
+{
+ if (strcasecmp(p, "reset") == 0)
+ watchdog_action = WDT_RESET;
+ else if (strcasecmp(p, "shutdown") == 0)
+ watchdog_action = WDT_SHUTDOWN;
+ else if (strcasecmp(p, "poweroff") == 0)
+ watchdog_action = WDT_POWEROFF;
+ else if (strcasecmp(p, "pause") == 0)
+ watchdog_action = WDT_PAUSE;
+ else if (strcasecmp(p, "debug") == 0)
+ watchdog_action = WDT_DEBUG;
+ else if (strcasecmp(p, "none") == 0)
+ watchdog_action = WDT_NONE;
+ else if (strcasecmp(p, "inject-nmi") == 0)
+ watchdog_action = WDT_NMI;
+ else
+ return -1;
+
+ return 0;
+}
+
+/* This actually performs the "action" once a watchdog has expired,
+ * ie. reboot, shutdown, exit, etc.
+ */
+void watchdog_perform_action(void)
+{
+ switch (watchdog_action) {
+ case WDT_RESET: /* same as 'system_reset' in monitor */
+ qapi_event_send_watchdog(WATCHDOG_EXPIRATION_ACTION_RESET, &error_abort);
+ qemu_system_reset_request();
+ break;
+
+ case WDT_SHUTDOWN: /* same as 'system_powerdown' in monitor */
+ qapi_event_send_watchdog(WATCHDOG_EXPIRATION_ACTION_SHUTDOWN, &error_abort);
+ qemu_system_powerdown_request();
+ break;
+
+ case WDT_POWEROFF: /* same as 'quit' command in monitor */
+ qapi_event_send_watchdog(WATCHDOG_EXPIRATION_ACTION_POWEROFF, &error_abort);
+ exit(0);
+
+ case WDT_PAUSE: /* same as 'stop' command in monitor */
+ /* In a timer callback, when vm_stop calls qemu_clock_enable
+ * you would get a deadlock. Bypass the problem.
+ */
+ qemu_system_vmstop_request_prepare();
+ qapi_event_send_watchdog(WATCHDOG_EXPIRATION_ACTION_PAUSE, &error_abort);
+ qemu_system_vmstop_request(RUN_STATE_WATCHDOG);
+ break;
+
+ case WDT_DEBUG:
+ qapi_event_send_watchdog(WATCHDOG_EXPIRATION_ACTION_DEBUG, &error_abort);
+ fprintf(stderr, "watchdog: timer fired\n");
+ break;
+
+ case WDT_NONE:
+ qapi_event_send_watchdog(WATCHDOG_EXPIRATION_ACTION_NONE, &error_abort);
+ break;
+
+ case WDT_NMI:
+ qapi_event_send_watchdog(WATCHDOG_EXPIRATION_ACTION_INJECT_NMI,
+ &error_abort);
+ inject_nmi();
+ break;
+ }
+}
diff --git a/hw/watchdog/wdt_diag288.c b/hw/watchdog/wdt_diag288.c
new file mode 100644
index 00000000..2a885a44
--- /dev/null
+++ b/hw/watchdog/wdt_diag288.c
@@ -0,0 +1,130 @@
+/*
+ * watchdog device diag288 support
+ *
+ * Copyright IBM, Corp. 2015
+ *
+ * Authors:
+ * Xu Wang <gesaint@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "sysemu/watchdog.h"
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/watchdog/wdt_diag288.h"
+
+static WatchdogTimerModel model = {
+ .wdt_name = TYPE_WDT_DIAG288,
+ .wdt_description = "diag288 device for s390x platform",
+};
+
+static const VMStateDescription vmstate_diag288 = {
+ .name = "vmstate_diag288",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(timer, DIAG288State),
+ VMSTATE_BOOL(enabled, DIAG288State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void wdt_diag288_reset(DeviceState *dev)
+{
+ DIAG288State *diag288 = DIAG288(dev);
+
+ diag288->enabled = false;
+ timer_del(diag288->timer);
+}
+
+static void diag288_reset(void *opaque)
+{
+ DeviceState *diag288 = opaque;
+
+ wdt_diag288_reset(diag288);
+}
+
+static void diag288_timer_expired(void *dev)
+{
+ qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n");
+ watchdog_perform_action();
+ wdt_diag288_reset(dev);
+}
+
+static int wdt_diag288_handle_timer(DIAG288State *diag288,
+ uint64_t func, uint64_t timeout)
+{
+ switch (func) {
+ case WDT_DIAG288_INIT:
+ diag288->enabled = true;
+ /* fall through */
+ case WDT_DIAG288_CHANGE:
+ if (!diag288->enabled) {
+ return -1;
+ }
+ timer_mod(diag288->timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ timeout * get_ticks_per_sec());
+ break;
+ case WDT_DIAG288_CANCEL:
+ if (!diag288->enabled) {
+ return -1;
+ }
+ diag288->enabled = false;
+ timer_del(diag288->timer);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static void wdt_diag288_realize(DeviceState *dev, Error **errp)
+{
+ DIAG288State *diag288 = DIAG288(dev);
+
+ qemu_register_reset(diag288_reset, diag288);
+ diag288->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, diag288_timer_expired,
+ dev);
+}
+
+static void wdt_diag288_unrealize(DeviceState *dev, Error **errp)
+{
+ DIAG288State *diag288 = DIAG288(dev);
+
+ timer_del(diag288->timer);
+ timer_free(diag288->timer);
+}
+
+static void wdt_diag288_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ DIAG288Class *diag288 = DIAG288_CLASS(klass);
+
+ dc->realize = wdt_diag288_realize;
+ dc->unrealize = wdt_diag288_unrealize;
+ dc->reset = wdt_diag288_reset;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->vmsd = &vmstate_diag288;
+ diag288->handle_timer = wdt_diag288_handle_timer;
+}
+
+static const TypeInfo wdt_diag288_info = {
+ .class_init = wdt_diag288_class_init,
+ .parent = TYPE_DEVICE,
+ .name = TYPE_WDT_DIAG288,
+ .instance_size = sizeof(DIAG288State),
+ .class_size = sizeof(DIAG288Class),
+};
+
+static void wdt_diag288_register_types(void)
+{
+ watchdog_add_model(&model);
+ type_register_static(&wdt_diag288_info);
+}
+
+type_init(wdt_diag288_register_types)
diff --git a/hw/watchdog/wdt_i6300esb.c b/hw/watchdog/wdt_i6300esb.c
new file mode 100644
index 00000000..cfa2b1be
--- /dev/null
+++ b/hw/watchdog/wdt_i6300esb.c
@@ -0,0 +1,470 @@
+/*
+ * Virtual hardware watchdog.
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * By Richard W.M. Jones (rjones@redhat.com).
+ */
+
+#include <inttypes.h>
+
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "sysemu/watchdog.h"
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+
+/*#define I6300ESB_DEBUG 1*/
+
+#ifdef I6300ESB_DEBUG
+#define i6300esb_debug(fs,...) \
+ fprintf(stderr,"i6300esb: %s: "fs,__func__,##__VA_ARGS__)
+#else
+#define i6300esb_debug(fs,...)
+#endif
+
+/* PCI configuration registers */
+#define ESB_CONFIG_REG 0x60 /* Config register */
+#define ESB_LOCK_REG 0x68 /* WDT lock register */
+
+/* Memory mapped registers (offset from base address) */
+#define ESB_TIMER1_REG 0x00 /* Timer1 value after each reset */
+#define ESB_TIMER2_REG 0x04 /* Timer2 value after each reset */
+#define ESB_GINTSR_REG 0x08 /* General Interrupt Status Register */
+#define ESB_RELOAD_REG 0x0c /* Reload register */
+
+/* Lock register bits */
+#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
+#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */
+#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */
+
+/* Config register bits */
+#define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */
+#define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */
+#define ESB_WDT_INTTYPE (0x11 << 0) /* Interrupt type on timer1 timeout */
+
+/* Reload register bits */
+#define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */
+
+/* Magic constants */
+#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
+#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
+
+/* Device state. */
+struct I6300State {
+ PCIDevice dev;
+ MemoryRegion io_mem;
+
+ int reboot_enabled; /* "Reboot" on timer expiry. The real action
+ * performed depends on the -watchdog-action
+ * param passed on QEMU command line.
+ */
+ int clock_scale; /* Clock scale. */
+#define CLOCK_SCALE_1KHZ 0
+#define CLOCK_SCALE_1MHZ 1
+
+ int int_type; /* Interrupt type generated. */
+#define INT_TYPE_IRQ 0 /* APIC 1, INT 10 */
+#define INT_TYPE_SMI 2
+#define INT_TYPE_DISABLED 3
+
+ int free_run; /* If true, reload timer on expiry. */
+ int locked; /* If true, enabled field cannot be changed. */
+ int enabled; /* If true, watchdog is enabled. */
+
+ QEMUTimer *timer; /* The actual watchdog timer. */
+
+ uint32_t timer1_preload; /* Values preloaded into timer1, timer2. */
+ uint32_t timer2_preload;
+ int stage; /* Stage (1 or 2). */
+
+ int unlock_state; /* Guest writes 0x80, 0x86 to unlock the
+ * registers, and we transition through
+ * states 0 -> 1 -> 2 when this happens.
+ */
+
+ int previous_reboot_flag; /* If the watchdog caused the previous
+ * reboot, this flag will be set.
+ */
+};
+
+typedef struct I6300State I6300State;
+
+#define TYPE_WATCHDOG_I6300ESB_DEVICE "i6300esb"
+#define WATCHDOG_I6300ESB_DEVICE(obj) \
+ OBJECT_CHECK(I6300State, (obj), TYPE_WATCHDOG_I6300ESB_DEVICE)
+
+/* This function is called when the watchdog has either been enabled
+ * (hence it starts counting down) or has been keep-alived.
+ */
+static void i6300esb_restart_timer(I6300State *d, int stage)
+{
+ int64_t timeout;
+
+ if (!d->enabled)
+ return;
+
+ d->stage = stage;
+
+ if (d->stage <= 1)
+ timeout = d->timer1_preload;
+ else
+ timeout = d->timer2_preload;
+
+ if (d->clock_scale == CLOCK_SCALE_1KHZ)
+ timeout <<= 15;
+ else
+ timeout <<= 5;
+
+ /* Get the timeout in units of ticks_per_sec.
+ *
+ * ticks_per_sec is typically 10^9 == 0x3B9ACA00 (30 bits), with
+ * 20 bits of user supplied preload, and 15 bits of scale, the
+ * multiply here can exceed 64-bits, before we divide by 33MHz, so
+ * we use a higher-precision intermediate result.
+ */
+ timeout = muldiv64(get_ticks_per_sec(), timeout, 33000000);
+
+ i6300esb_debug("stage %d, timeout %" PRIi64 "\n", d->stage, timeout);
+
+ timer_mod(d->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout);
+}
+
+/* This is called when the guest disables the watchdog. */
+static void i6300esb_disable_timer(I6300State *d)
+{
+ i6300esb_debug("timer disabled\n");
+
+ timer_del(d->timer);
+}
+
+static void i6300esb_reset(DeviceState *dev)
+{
+ PCIDevice *pdev = PCI_DEVICE(dev);
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(pdev);
+
+ i6300esb_debug("I6300State = %p\n", d);
+
+ i6300esb_disable_timer(d);
+
+ /* NB: Don't change d->previous_reboot_flag in this function. */
+
+ d->reboot_enabled = 1;
+ d->clock_scale = CLOCK_SCALE_1KHZ;
+ d->int_type = INT_TYPE_IRQ;
+ d->free_run = 0;
+ d->locked = 0;
+ d->enabled = 0;
+ d->timer1_preload = 0xfffff;
+ d->timer2_preload = 0xfffff;
+ d->stage = 1;
+ d->unlock_state = 0;
+}
+
+/* This function is called when the watchdog expires. Note that
+ * the hardware has two timers, and so expiry happens in two stages.
+ * If d->stage == 1 then we perform the first stage action (usually,
+ * sending an interrupt) and then restart the timer again for the
+ * second stage. If the second stage expires then the watchdog
+ * really has run out.
+ */
+static void i6300esb_timer_expired(void *vp)
+{
+ I6300State *d = vp;
+
+ i6300esb_debug("stage %d\n", d->stage);
+
+ if (d->stage == 1) {
+ /* What to do at the end of stage 1? */
+ switch (d->int_type) {
+ case INT_TYPE_IRQ:
+ fprintf(stderr, "i6300esb_timer_expired: I would send APIC 1 INT 10 here if I knew how (XXX)\n");
+ break;
+ case INT_TYPE_SMI:
+ fprintf(stderr, "i6300esb_timer_expired: I would send SMI here if I knew how (XXX)\n");
+ break;
+ }
+
+ /* Start the second stage. */
+ i6300esb_restart_timer(d, 2);
+ } else {
+ /* Second stage expired, reboot for real. */
+ if (d->reboot_enabled) {
+ d->previous_reboot_flag = 1;
+ watchdog_perform_action(); /* This reboots, exits, etc */
+ i6300esb_reset(&d->dev.qdev);
+ }
+
+ /* In "free running mode" we start stage 1 again. */
+ if (d->free_run)
+ i6300esb_restart_timer(d, 1);
+ }
+}
+
+static void i6300esb_config_write(PCIDevice *dev, uint32_t addr,
+ uint32_t data, int len)
+{
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
+ int old;
+
+ i6300esb_debug("addr = %x, data = %x, len = %d\n", addr, data, len);
+
+ if (addr == ESB_CONFIG_REG && len == 2) {
+ d->reboot_enabled = (data & ESB_WDT_REBOOT) == 0;
+ d->clock_scale =
+ (data & ESB_WDT_FREQ) != 0 ? CLOCK_SCALE_1MHZ : CLOCK_SCALE_1KHZ;
+ d->int_type = (data & ESB_WDT_INTTYPE);
+ } else if (addr == ESB_LOCK_REG && len == 1) {
+ if (!d->locked) {
+ d->locked = (data & ESB_WDT_LOCK) != 0;
+ d->free_run = (data & ESB_WDT_FUNC) != 0;
+ old = d->enabled;
+ d->enabled = (data & ESB_WDT_ENABLE) != 0;
+ if (!old && d->enabled) /* Enabled transitioned from 0 -> 1 */
+ i6300esb_restart_timer(d, 1);
+ else if (!d->enabled)
+ i6300esb_disable_timer(d);
+ }
+ } else {
+ pci_default_write_config(dev, addr, data, len);
+ }
+}
+
+static uint32_t i6300esb_config_read(PCIDevice *dev, uint32_t addr, int len)
+{
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
+ uint32_t data;
+
+ i6300esb_debug ("addr = %x, len = %d\n", addr, len);
+
+ if (addr == ESB_CONFIG_REG && len == 2) {
+ data =
+ (d->reboot_enabled ? 0 : ESB_WDT_REBOOT) |
+ (d->clock_scale == CLOCK_SCALE_1MHZ ? ESB_WDT_FREQ : 0) |
+ d->int_type;
+ return data;
+ } else if (addr == ESB_LOCK_REG && len == 1) {
+ data =
+ (d->free_run ? ESB_WDT_FUNC : 0) |
+ (d->locked ? ESB_WDT_LOCK : 0) |
+ (d->enabled ? ESB_WDT_ENABLE : 0);
+ return data;
+ } else {
+ return pci_default_read_config(dev, addr, len);
+ }
+}
+
+static uint32_t i6300esb_mem_readb(void *vp, hwaddr addr)
+{
+ i6300esb_debug ("addr = %x\n", (int) addr);
+
+ return 0;
+}
+
+static uint32_t i6300esb_mem_readw(void *vp, hwaddr addr)
+{
+ uint32_t data = 0;
+ I6300State *d = vp;
+
+ i6300esb_debug("addr = %x\n", (int) addr);
+
+ if (addr == 0xc) {
+ /* The previous reboot flag is really bit 9, but there is
+ * a bug in the Linux driver where it thinks it's bit 12.
+ * Set both.
+ */
+ data = d->previous_reboot_flag ? 0x1200 : 0;
+ }
+
+ return data;
+}
+
+static uint32_t i6300esb_mem_readl(void *vp, hwaddr addr)
+{
+ i6300esb_debug("addr = %x\n", (int) addr);
+
+ return 0;
+}
+
+static void i6300esb_mem_writeb(void *vp, hwaddr addr, uint32_t val)
+{
+ I6300State *d = vp;
+
+ i6300esb_debug("addr = %x, val = %x\n", (int) addr, val);
+
+ if (addr == 0xc && val == 0x80)
+ d->unlock_state = 1;
+ else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
+ d->unlock_state = 2;
+}
+
+static void i6300esb_mem_writew(void *vp, hwaddr addr, uint32_t val)
+{
+ I6300State *d = vp;
+
+ i6300esb_debug("addr = %x, val = %x\n", (int) addr, val);
+
+ if (addr == 0xc && val == 0x80)
+ d->unlock_state = 1;
+ else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
+ d->unlock_state = 2;
+ else {
+ if (d->unlock_state == 2) {
+ if (addr == 0xc) {
+ if ((val & 0x100) != 0)
+ /* This is the "ping" from the userspace watchdog in
+ * the guest ...
+ */
+ i6300esb_restart_timer(d, 1);
+
+ /* Setting bit 9 resets the previous reboot flag.
+ * There's a bug in the Linux driver where it sets
+ * bit 12 instead.
+ */
+ if ((val & 0x200) != 0 || (val & 0x1000) != 0) {
+ d->previous_reboot_flag = 0;
+ }
+ }
+
+ d->unlock_state = 0;
+ }
+ }
+}
+
+static void i6300esb_mem_writel(void *vp, hwaddr addr, uint32_t val)
+{
+ I6300State *d = vp;
+
+ i6300esb_debug ("addr = %x, val = %x\n", (int) addr, val);
+
+ if (addr == 0xc && val == 0x80)
+ d->unlock_state = 1;
+ else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
+ d->unlock_state = 2;
+ else {
+ if (d->unlock_state == 2) {
+ if (addr == 0)
+ d->timer1_preload = val & 0xfffff;
+ else if (addr == 4)
+ d->timer2_preload = val & 0xfffff;
+
+ d->unlock_state = 0;
+ }
+ }
+}
+
+static const MemoryRegionOps i6300esb_ops = {
+ .old_mmio = {
+ .read = {
+ i6300esb_mem_readb,
+ i6300esb_mem_readw,
+ i6300esb_mem_readl,
+ },
+ .write = {
+ i6300esb_mem_writeb,
+ i6300esb_mem_writew,
+ i6300esb_mem_writel,
+ },
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_i6300esb = {
+ .name = "i6300esb_wdt",
+ /* With this VMSD's introduction, version_id/minimum_version_id were
+ * erroneously set to sizeof(I6300State), causing a somewhat random
+ * version_id to be set for every build. This eventually broke
+ * migration.
+ *
+ * To correct this without breaking old->new migration for older
+ * versions of QEMU, we've set version_id to a value high enough
+ * to exceed all past values of sizeof(I6300State) across various
+ * build environments, and have reset minimum_version_id to 1,
+ * since this VMSD has never changed and thus can accept all past
+ * versions.
+ *
+ * For future changes we can treat these values as we normally would.
+ */
+ .version_id = 10000,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, I6300State),
+ VMSTATE_INT32(reboot_enabled, I6300State),
+ VMSTATE_INT32(clock_scale, I6300State),
+ VMSTATE_INT32(int_type, I6300State),
+ VMSTATE_INT32(free_run, I6300State),
+ VMSTATE_INT32(locked, I6300State),
+ VMSTATE_INT32(enabled, I6300State),
+ VMSTATE_TIMER_PTR(timer, I6300State),
+ VMSTATE_UINT32(timer1_preload, I6300State),
+ VMSTATE_UINT32(timer2_preload, I6300State),
+ VMSTATE_INT32(stage, I6300State),
+ VMSTATE_INT32(unlock_state, I6300State),
+ VMSTATE_INT32(previous_reboot_flag, I6300State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void i6300esb_realize(PCIDevice *dev, Error **errp)
+{
+ I6300State *d = WATCHDOG_I6300ESB_DEVICE(dev);
+
+ i6300esb_debug("I6300State = %p\n", d);
+
+ d->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, i6300esb_timer_expired, d);
+ d->previous_reboot_flag = 0;
+
+ memory_region_init_io(&d->io_mem, OBJECT(d), &i6300esb_ops, d,
+ "i6300esb", 0x10);
+ pci_register_bar(&d->dev, 0, 0, &d->io_mem);
+ /* qemu_register_coalesced_mmio (addr, 0x10); ? */
+}
+
+static WatchdogTimerModel model = {
+ .wdt_name = "i6300esb",
+ .wdt_description = "Intel 6300ESB",
+};
+
+static void i6300esb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->config_read = i6300esb_config_read;
+ k->config_write = i6300esb_config_write;
+ k->realize = i6300esb_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_ESB_9;
+ k->class_id = PCI_CLASS_SYSTEM_OTHER;
+ dc->reset = i6300esb_reset;
+ dc->vmsd = &vmstate_i6300esb;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo i6300esb_info = {
+ .name = TYPE_WATCHDOG_I6300ESB_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(I6300State),
+ .class_init = i6300esb_class_init,
+};
+
+static void i6300esb_register_types(void)
+{
+ watchdog_add_model(&model);
+ type_register_static(&i6300esb_info);
+}
+
+type_init(i6300esb_register_types)
diff --git a/hw/watchdog/wdt_ib700.c b/hw/watchdog/wdt_ib700.c
new file mode 100644
index 00000000..0917a713
--- /dev/null
+++ b/hw/watchdog/wdt_ib700.c
@@ -0,0 +1,156 @@
+/*
+ * Virtual hardware watchdog.
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * By Richard W.M. Jones (rjones@redhat.com).
+ */
+
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "sysemu/watchdog.h"
+#include "hw/hw.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+
+/*#define IB700_DEBUG 1*/
+
+#ifdef IB700_DEBUG
+#define ib700_debug(fs,...) \
+ fprintf(stderr,"ib700: %s: "fs,__func__,##__VA_ARGS__)
+#else
+#define ib700_debug(fs,...)
+#endif
+
+#define TYPE_IB700 "ib700"
+#define IB700(obj) OBJECT_CHECK(IB700State, (obj), TYPE_IB700)
+
+typedef struct IB700state {
+ ISADevice parent_obj;
+
+ QEMUTimer *timer;
+
+ PortioList port_list;
+} IB700State;
+
+/* This is the timer. We use a global here because the watchdog
+ * code ensures there is only one watchdog (it is located at a fixed,
+ * unchangeable IO port, so there could only ever be one anyway).
+ */
+
+/* A write to this register enables the timer. */
+static void ib700_write_enable_reg(void *vp, uint32_t addr, uint32_t data)
+{
+ IB700State *s = vp;
+ static int time_map[] = {
+ 30, 28, 26, 24, 22, 20, 18, 16,
+ 14, 12, 10, 8, 6, 4, 2, 0
+ };
+ int64_t timeout;
+
+ ib700_debug("addr = %x, data = %x\n", addr, data);
+
+ timeout = (int64_t) time_map[data & 0xF] * get_ticks_per_sec();
+ timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout);
+}
+
+/* A write (of any value) to this register disables the timer. */
+static void ib700_write_disable_reg(void *vp, uint32_t addr, uint32_t data)
+{
+ IB700State *s = vp;
+
+ ib700_debug("addr = %x, data = %x\n", addr, data);
+
+ timer_del(s->timer);
+}
+
+/* This is called when the watchdog expires. */
+static void ib700_timer_expired(void *vp)
+{
+ IB700State *s = vp;
+
+ ib700_debug("watchdog expired\n");
+
+ watchdog_perform_action();
+ timer_del(s->timer);
+}
+
+static const VMStateDescription vmstate_ib700 = {
+ .name = "ib700_wdt",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(timer, IB700State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const MemoryRegionPortio wdt_portio_list[] = {
+ { 0x441, 2, 1, .write = ib700_write_disable_reg, },
+ { 0x443, 2, 1, .write = ib700_write_enable_reg, },
+ PORTIO_END_OF_LIST(),
+};
+
+static void wdt_ib700_realize(DeviceState *dev, Error **errp)
+{
+ IB700State *s = IB700(dev);
+
+ ib700_debug("watchdog init\n");
+
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ib700_timer_expired, s);
+
+ portio_list_init(&s->port_list, OBJECT(s), wdt_portio_list, s, "ib700");
+ portio_list_add(&s->port_list, isa_address_space_io(&s->parent_obj), 0);
+}
+
+static void wdt_ib700_reset(DeviceState *dev)
+{
+ IB700State *s = IB700(dev);
+
+ ib700_debug("watchdog reset\n");
+
+ timer_del(s->timer);
+}
+
+static WatchdogTimerModel model = {
+ .wdt_name = "ib700",
+ .wdt_description = "iBASE 700",
+};
+
+static void wdt_ib700_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = wdt_ib700_realize;
+ dc->reset = wdt_ib700_reset;
+ dc->vmsd = &vmstate_ib700;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo wdt_ib700_info = {
+ .name = TYPE_IB700,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(IB700State),
+ .class_init = wdt_ib700_class_init,
+};
+
+static void wdt_ib700_register_types(void)
+{
+ watchdog_add_model(&model);
+ type_register_static(&wdt_ib700_info);
+}
+
+type_init(wdt_ib700_register_types)
diff --git a/hw/xen/Makefile.objs b/hw/xen/Makefile.objs
new file mode 100644
index 00000000..a0ca0aa3
--- /dev/null
+++ b/hw/xen/Makefile.objs
@@ -0,0 +1,5 @@
+# xen backend driver support
+common-obj-$(CONFIG_XEN_BACKEND) += xen_backend.o xen_devconfig.o
+
+obj-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen-host-pci-device.o
+obj-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pt.o xen_pt_config_init.o xen_pt_msi.o
diff --git a/hw/xen/xen-host-pci-device.c b/hw/xen/xen-host-pci-device.c
new file mode 100644
index 00000000..743b37b9
--- /dev/null
+++ b/hw/xen/xen-host-pci-device.c
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2011 Citrix Ltd.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu-common.h"
+#include "xen-host-pci-device.h"
+
+#define XEN_HOST_PCI_MAX_EXT_CAP \
+ ((PCIE_CONFIG_SPACE_SIZE - PCI_CONFIG_SPACE_SIZE) / (PCI_CAP_SIZEOF + 4))
+
+#ifdef XEN_HOST_PCI_DEVICE_DEBUG
+# define XEN_HOST_PCI_LOG(f, a...) fprintf(stderr, "%s: " f, __func__, ##a)
+#else
+# define XEN_HOST_PCI_LOG(f, a...) (void)0
+#endif
+
+/*
+ * from linux/ioport.h
+ * IO resources have these defined flags.
+ */
+#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
+
+#define IORESOURCE_TYPE_BITS 0x00000f00 /* Resource type */
+#define IORESOURCE_IO 0x00000100
+#define IORESOURCE_MEM 0x00000200
+
+#define IORESOURCE_PREFETCH 0x00001000 /* No side effects */
+#define IORESOURCE_MEM_64 0x00100000
+
+static int xen_host_pci_sysfs_path(const XenHostPCIDevice *d,
+ const char *name, char *buf, ssize_t size)
+{
+ int rc;
+
+ rc = snprintf(buf, size, "/sys/bus/pci/devices/%04x:%02x:%02x.%d/%s",
+ d->domain, d->bus, d->dev, d->func, name);
+
+ if (rc >= size || rc < 0) {
+ /* The ouput is truncated or an other error is encountered */
+ return -ENODEV;
+ }
+ return 0;
+}
+
+
+/* This size should be enough to read the first 7 lines of a resource file */
+#define XEN_HOST_PCI_RESOURCE_BUFFER_SIZE 400
+static int xen_host_pci_get_resource(XenHostPCIDevice *d)
+{
+ int i, rc, fd;
+ char path[PATH_MAX];
+ char buf[XEN_HOST_PCI_RESOURCE_BUFFER_SIZE];
+ unsigned long long start, end, flags, size;
+ char *endptr, *s;
+ uint8_t type;
+
+ rc = xen_host_pci_sysfs_path(d, "resource", path, sizeof (path));
+ if (rc) {
+ return rc;
+ }
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ XEN_HOST_PCI_LOG("Error: Can't open %s: %s\n", path, strerror(errno));
+ return -errno;
+ }
+
+ do {
+ rc = read(fd, &buf, sizeof (buf) - 1);
+ if (rc < 0 && errno != EINTR) {
+ rc = -errno;
+ goto out;
+ }
+ } while (rc < 0);
+ buf[rc] = 0;
+ rc = 0;
+
+ s = buf;
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ type = 0;
+
+ start = strtoll(s, &endptr, 16);
+ if (*endptr != ' ' || s == endptr) {
+ break;
+ }
+ s = endptr + 1;
+ end = strtoll(s, &endptr, 16);
+ if (*endptr != ' ' || s == endptr) {
+ break;
+ }
+ s = endptr + 1;
+ flags = strtoll(s, &endptr, 16);
+ if (*endptr != '\n' || s == endptr) {
+ break;
+ }
+ s = endptr + 1;
+
+ if (start) {
+ size = end - start + 1;
+ } else {
+ size = 0;
+ }
+
+ if (flags & IORESOURCE_IO) {
+ type |= XEN_HOST_PCI_REGION_TYPE_IO;
+ }
+ if (flags & IORESOURCE_MEM) {
+ type |= XEN_HOST_PCI_REGION_TYPE_MEM;
+ }
+ if (flags & IORESOURCE_PREFETCH) {
+ type |= XEN_HOST_PCI_REGION_TYPE_PREFETCH;
+ }
+ if (flags & IORESOURCE_MEM_64) {
+ type |= XEN_HOST_PCI_REGION_TYPE_MEM_64;
+ }
+
+ if (i < PCI_ROM_SLOT) {
+ d->io_regions[i].base_addr = start;
+ d->io_regions[i].size = size;
+ d->io_regions[i].type = type;
+ d->io_regions[i].bus_flags = flags & IORESOURCE_BITS;
+ } else {
+ d->rom.base_addr = start;
+ d->rom.size = size;
+ d->rom.type = type;
+ d->rom.bus_flags = flags & IORESOURCE_BITS;
+ }
+ }
+ if (i != PCI_NUM_REGIONS) {
+ /* Invalid format or input to short */
+ rc = -ENODEV;
+ }
+
+out:
+ close(fd);
+ return rc;
+}
+
+/* This size should be enough to read a long from a file */
+#define XEN_HOST_PCI_GET_VALUE_BUFFER_SIZE 22
+static int xen_host_pci_get_value(XenHostPCIDevice *d, const char *name,
+ unsigned int *pvalue, int base)
+{
+ char path[PATH_MAX];
+ char buf[XEN_HOST_PCI_GET_VALUE_BUFFER_SIZE];
+ int fd, rc;
+ unsigned long value;
+ char *endptr;
+
+ rc = xen_host_pci_sysfs_path(d, name, path, sizeof (path));
+ if (rc) {
+ return rc;
+ }
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ XEN_HOST_PCI_LOG("Error: Can't open %s: %s\n", path, strerror(errno));
+ return -errno;
+ }
+ do {
+ rc = read(fd, &buf, sizeof (buf) - 1);
+ if (rc < 0 && errno != EINTR) {
+ rc = -errno;
+ goto out;
+ }
+ } while (rc < 0);
+ buf[rc] = 0;
+ value = strtol(buf, &endptr, base);
+ if (endptr == buf || *endptr != '\n') {
+ rc = -1;
+ } else if ((value == LONG_MIN || value == LONG_MAX) && errno == ERANGE) {
+ rc = -errno;
+ } else {
+ rc = 0;
+ *pvalue = value;
+ }
+out:
+ close(fd);
+ return rc;
+}
+
+static inline int xen_host_pci_get_hex_value(XenHostPCIDevice *d,
+ const char *name,
+ unsigned int *pvalue)
+{
+ return xen_host_pci_get_value(d, name, pvalue, 16);
+}
+
+static inline int xen_host_pci_get_dec_value(XenHostPCIDevice *d,
+ const char *name,
+ unsigned int *pvalue)
+{
+ return xen_host_pci_get_value(d, name, pvalue, 10);
+}
+
+static bool xen_host_pci_dev_is_virtfn(XenHostPCIDevice *d)
+{
+ char path[PATH_MAX];
+ struct stat buf;
+
+ if (xen_host_pci_sysfs_path(d, "physfn", path, sizeof (path))) {
+ return false;
+ }
+ return !stat(path, &buf);
+}
+
+static int xen_host_pci_config_open(XenHostPCIDevice *d)
+{
+ char path[PATH_MAX];
+ int rc;
+
+ rc = xen_host_pci_sysfs_path(d, "config", path, sizeof (path));
+ if (rc) {
+ return rc;
+ }
+ d->config_fd = open(path, O_RDWR);
+ if (d->config_fd < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+static int xen_host_pci_config_read(XenHostPCIDevice *d,
+ int pos, void *buf, int len)
+{
+ int rc;
+
+ do {
+ rc = pread(d->config_fd, buf, len, pos);
+ } while (rc < 0 && (errno == EINTR || errno == EAGAIN));
+ if (rc != len) {
+ return -errno;
+ }
+ return 0;
+}
+
+static int xen_host_pci_config_write(XenHostPCIDevice *d,
+ int pos, const void *buf, int len)
+{
+ int rc;
+
+ do {
+ rc = pwrite(d->config_fd, buf, len, pos);
+ } while (rc < 0 && (errno == EINTR || errno == EAGAIN));
+ if (rc != len) {
+ return -errno;
+ }
+ return 0;
+}
+
+
+int xen_host_pci_get_byte(XenHostPCIDevice *d, int pos, uint8_t *p)
+{
+ uint8_t buf;
+ int rc = xen_host_pci_config_read(d, pos, &buf, 1);
+ if (!rc) {
+ *p = buf;
+ }
+ return rc;
+}
+
+int xen_host_pci_get_word(XenHostPCIDevice *d, int pos, uint16_t *p)
+{
+ uint16_t buf;
+ int rc = xen_host_pci_config_read(d, pos, &buf, 2);
+ if (!rc) {
+ *p = le16_to_cpu(buf);
+ }
+ return rc;
+}
+
+int xen_host_pci_get_long(XenHostPCIDevice *d, int pos, uint32_t *p)
+{
+ uint32_t buf;
+ int rc = xen_host_pci_config_read(d, pos, &buf, 4);
+ if (!rc) {
+ *p = le32_to_cpu(buf);
+ }
+ return rc;
+}
+
+int xen_host_pci_get_block(XenHostPCIDevice *d, int pos, uint8_t *buf, int len)
+{
+ return xen_host_pci_config_read(d, pos, buf, len);
+}
+
+int xen_host_pci_set_byte(XenHostPCIDevice *d, int pos, uint8_t data)
+{
+ return xen_host_pci_config_write(d, pos, &data, 1);
+}
+
+int xen_host_pci_set_word(XenHostPCIDevice *d, int pos, uint16_t data)
+{
+ data = cpu_to_le16(data);
+ return xen_host_pci_config_write(d, pos, &data, 2);
+}
+
+int xen_host_pci_set_long(XenHostPCIDevice *d, int pos, uint32_t data)
+{
+ data = cpu_to_le32(data);
+ return xen_host_pci_config_write(d, pos, &data, 4);
+}
+
+int xen_host_pci_set_block(XenHostPCIDevice *d, int pos, uint8_t *buf, int len)
+{
+ return xen_host_pci_config_write(d, pos, buf, len);
+}
+
+int xen_host_pci_find_ext_cap_offset(XenHostPCIDevice *d, uint32_t cap)
+{
+ uint32_t header = 0;
+ int max_cap = XEN_HOST_PCI_MAX_EXT_CAP;
+ int pos = PCI_CONFIG_SPACE_SIZE;
+
+ do {
+ if (xen_host_pci_get_long(d, pos, &header)) {
+ break;
+ }
+ /*
+ * If we have no capabilities, this is indicated by cap ID,
+ * cap version and next pointer all being 0.
+ */
+ if (header == 0) {
+ break;
+ }
+
+ if (PCI_EXT_CAP_ID(header) == cap) {
+ return pos;
+ }
+
+ pos = PCI_EXT_CAP_NEXT(header);
+ if (pos < PCI_CONFIG_SPACE_SIZE) {
+ break;
+ }
+
+ max_cap--;
+ } while (max_cap > 0);
+
+ return -1;
+}
+
+int xen_host_pci_device_get(XenHostPCIDevice *d, uint16_t domain,
+ uint8_t bus, uint8_t dev, uint8_t func)
+{
+ unsigned int v;
+ int rc = 0;
+
+ d->config_fd = -1;
+ d->domain = domain;
+ d->bus = bus;
+ d->dev = dev;
+ d->func = func;
+
+ rc = xen_host_pci_config_open(d);
+ if (rc) {
+ goto error;
+ }
+ rc = xen_host_pci_get_resource(d);
+ if (rc) {
+ goto error;
+ }
+ rc = xen_host_pci_get_hex_value(d, "vendor", &v);
+ if (rc) {
+ goto error;
+ }
+ d->vendor_id = v;
+ rc = xen_host_pci_get_hex_value(d, "device", &v);
+ if (rc) {
+ goto error;
+ }
+ d->device_id = v;
+ rc = xen_host_pci_get_dec_value(d, "irq", &v);
+ if (rc) {
+ goto error;
+ }
+ d->irq = v;
+ d->is_virtfn = xen_host_pci_dev_is_virtfn(d);
+
+ return 0;
+error:
+ if (d->config_fd >= 0) {
+ close(d->config_fd);
+ d->config_fd = -1;
+ }
+ return rc;
+}
+
+void xen_host_pci_device_put(XenHostPCIDevice *d)
+{
+ if (d->config_fd >= 0) {
+ close(d->config_fd);
+ d->config_fd = -1;
+ }
+}
diff --git a/hw/xen/xen-host-pci-device.h b/hw/xen/xen-host-pci-device.h
new file mode 100644
index 00000000..c2486f0c
--- /dev/null
+++ b/hw/xen/xen-host-pci-device.h
@@ -0,0 +1,55 @@
+#ifndef XEN_HOST_PCI_DEVICE_H
+#define XEN_HOST_PCI_DEVICE_H
+
+#include "hw/pci/pci.h"
+
+enum {
+ XEN_HOST_PCI_REGION_TYPE_IO = 1 << 1,
+ XEN_HOST_PCI_REGION_TYPE_MEM = 1 << 2,
+ XEN_HOST_PCI_REGION_TYPE_PREFETCH = 1 << 3,
+ XEN_HOST_PCI_REGION_TYPE_MEM_64 = 1 << 4,
+};
+
+typedef struct XenHostPCIIORegion {
+ pcibus_t base_addr;
+ pcibus_t size;
+ uint8_t type;
+ uint8_t bus_flags; /* Bus-specific bits */
+} XenHostPCIIORegion;
+
+typedef struct XenHostPCIDevice {
+ uint16_t domain;
+ uint8_t bus;
+ uint8_t dev;
+ uint8_t func;
+
+ uint16_t vendor_id;
+ uint16_t device_id;
+ int irq;
+
+ XenHostPCIIORegion io_regions[PCI_NUM_REGIONS - 1];
+ XenHostPCIIORegion rom;
+
+ bool is_virtfn;
+
+ int config_fd;
+} XenHostPCIDevice;
+
+int xen_host_pci_device_get(XenHostPCIDevice *d, uint16_t domain,
+ uint8_t bus, uint8_t dev, uint8_t func);
+void xen_host_pci_device_put(XenHostPCIDevice *pci_dev);
+
+int xen_host_pci_get_byte(XenHostPCIDevice *d, int pos, uint8_t *p);
+int xen_host_pci_get_word(XenHostPCIDevice *d, int pos, uint16_t *p);
+int xen_host_pci_get_long(XenHostPCIDevice *d, int pos, uint32_t *p);
+int xen_host_pci_get_block(XenHostPCIDevice *d, int pos, uint8_t *buf,
+ int len);
+int xen_host_pci_set_byte(XenHostPCIDevice *d, int pos, uint8_t data);
+int xen_host_pci_set_word(XenHostPCIDevice *d, int pos, uint16_t data);
+int xen_host_pci_set_long(XenHostPCIDevice *d, int pos, uint32_t data);
+int xen_host_pci_set_block(XenHostPCIDevice *d, int pos, uint8_t *buf,
+ int len);
+
+int xen_host_pci_find_ext_cap_offset(XenHostPCIDevice *s, uint32_t cap);
+
+#endif /* !XEN_HOST_PCI_DEVICE_H_ */
diff --git a/hw/xen/xen_backend.c b/hw/xen/xen_backend.c
new file mode 100644
index 00000000..2510e2e4
--- /dev/null
+++ b/hw/xen/xen_backend.c
@@ -0,0 +1,808 @@
+/*
+ * xen backend driver infrastructure
+ * (c) 2008 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+/*
+ * TODO: add some xenbus / xenstore concepts overview here.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/signal.h>
+
+#include "hw/hw.h"
+#include "sysemu/char.h"
+#include "qemu/log.h"
+#include "hw/xen/xen_backend.h"
+
+#include <xen/grant_table.h>
+
+/* ------------------------------------------------------------- */
+
+/* public */
+XenXC xen_xc = XC_HANDLER_INITIAL_VALUE;
+struct xs_handle *xenstore = NULL;
+const char *xen_protocol;
+
+/* private */
+static QTAILQ_HEAD(XenDeviceHead, XenDevice) xendevs = QTAILQ_HEAD_INITIALIZER(xendevs);
+static int debug = 0;
+
+/* ------------------------------------------------------------- */
+
+int xenstore_write_str(const char *base, const char *node, const char *val)
+{
+ char abspath[XEN_BUFSIZE];
+
+ snprintf(abspath, sizeof(abspath), "%s/%s", base, node);
+ if (!xs_write(xenstore, 0, abspath, val, strlen(val))) {
+ return -1;
+ }
+ return 0;
+}
+
+char *xenstore_read_str(const char *base, const char *node)
+{
+ char abspath[XEN_BUFSIZE];
+ unsigned int len;
+ char *str, *ret = NULL;
+
+ snprintf(abspath, sizeof(abspath), "%s/%s", base, node);
+ str = xs_read(xenstore, 0, abspath, &len);
+ if (str != NULL) {
+ /* move to qemu-allocated memory to make sure
+ * callers can savely g_free() stuff. */
+ ret = g_strdup(str);
+ free(str);
+ }
+ return ret;
+}
+
+int xenstore_write_int(const char *base, const char *node, int ival)
+{
+ char val[12];
+
+ snprintf(val, sizeof(val), "%d", ival);
+ return xenstore_write_str(base, node, val);
+}
+
+int xenstore_write_int64(const char *base, const char *node, int64_t ival)
+{
+ char val[21];
+
+ snprintf(val, sizeof(val), "%"PRId64, ival);
+ return xenstore_write_str(base, node, val);
+}
+
+int xenstore_read_int(const char *base, const char *node, int *ival)
+{
+ char *val;
+ int rc = -1;
+
+ val = xenstore_read_str(base, node);
+ if (val && 1 == sscanf(val, "%d", ival)) {
+ rc = 0;
+ }
+ g_free(val);
+ return rc;
+}
+
+int xenstore_read_uint64(const char *base, const char *node, uint64_t *uval)
+{
+ char *val;
+ int rc = -1;
+
+ val = xenstore_read_str(base, node);
+ if (val && 1 == sscanf(val, "%"SCNu64, uval)) {
+ rc = 0;
+ }
+ g_free(val);
+ return rc;
+}
+
+int xenstore_write_be_str(struct XenDevice *xendev, const char *node, const char *val)
+{
+ return xenstore_write_str(xendev->be, node, val);
+}
+
+int xenstore_write_be_int(struct XenDevice *xendev, const char *node, int ival)
+{
+ return xenstore_write_int(xendev->be, node, ival);
+}
+
+int xenstore_write_be_int64(struct XenDevice *xendev, const char *node, int64_t ival)
+{
+ return xenstore_write_int64(xendev->be, node, ival);
+}
+
+char *xenstore_read_be_str(struct XenDevice *xendev, const char *node)
+{
+ return xenstore_read_str(xendev->be, node);
+}
+
+int xenstore_read_be_int(struct XenDevice *xendev, const char *node, int *ival)
+{
+ return xenstore_read_int(xendev->be, node, ival);
+}
+
+char *xenstore_read_fe_str(struct XenDevice *xendev, const char *node)
+{
+ return xenstore_read_str(xendev->fe, node);
+}
+
+int xenstore_read_fe_int(struct XenDevice *xendev, const char *node, int *ival)
+{
+ return xenstore_read_int(xendev->fe, node, ival);
+}
+
+int xenstore_read_fe_uint64(struct XenDevice *xendev, const char *node, uint64_t *uval)
+{
+ return xenstore_read_uint64(xendev->fe, node, uval);
+}
+
+/* ------------------------------------------------------------- */
+
+const char *xenbus_strstate(enum xenbus_state state)
+{
+ static const char *const name[] = {
+ [ XenbusStateUnknown ] = "Unknown",
+ [ XenbusStateInitialising ] = "Initialising",
+ [ XenbusStateInitWait ] = "InitWait",
+ [ XenbusStateInitialised ] = "Initialised",
+ [ XenbusStateConnected ] = "Connected",
+ [ XenbusStateClosing ] = "Closing",
+ [ XenbusStateClosed ] = "Closed",
+ };
+ return (state < ARRAY_SIZE(name)) ? name[state] : "INVALID";
+}
+
+int xen_be_set_state(struct XenDevice *xendev, enum xenbus_state state)
+{
+ int rc;
+
+ rc = xenstore_write_be_int(xendev, "state", state);
+ if (rc < 0) {
+ return rc;
+ }
+ xen_be_printf(xendev, 1, "backend state: %s -> %s\n",
+ xenbus_strstate(xendev->be_state), xenbus_strstate(state));
+ xendev->be_state = state;
+ return 0;
+}
+
+/* ------------------------------------------------------------- */
+
+struct XenDevice *xen_be_find_xendev(const char *type, int dom, int dev)
+{
+ struct XenDevice *xendev;
+
+ QTAILQ_FOREACH(xendev, &xendevs, next) {
+ if (xendev->dom != dom) {
+ continue;
+ }
+ if (xendev->dev != dev) {
+ continue;
+ }
+ if (strcmp(xendev->type, type) != 0) {
+ continue;
+ }
+ return xendev;
+ }
+ return NULL;
+}
+
+/*
+ * get xen backend device, allocate a new one if it doesn't exist.
+ */
+static struct XenDevice *xen_be_get_xendev(const char *type, int dom, int dev,
+ struct XenDevOps *ops)
+{
+ struct XenDevice *xendev;
+
+ xendev = xen_be_find_xendev(type, dom, dev);
+ if (xendev) {
+ return xendev;
+ }
+
+ /* init new xendev */
+ xendev = g_malloc0(ops->size);
+ xendev->type = type;
+ xendev->dom = dom;
+ xendev->dev = dev;
+ xendev->ops = ops;
+
+ snprintf(xendev->be, sizeof(xendev->be), "backend/%s/%d/%d",
+ xendev->type, xendev->dom, xendev->dev);
+ snprintf(xendev->name, sizeof(xendev->name), "%s-%d",
+ xendev->type, xendev->dev);
+
+ xendev->debug = debug;
+ xendev->local_port = -1;
+
+ xendev->evtchndev = xen_xc_evtchn_open(NULL, 0);
+ if (xendev->evtchndev == XC_HANDLER_INITIAL_VALUE) {
+ xen_be_printf(NULL, 0, "can't open evtchn device\n");
+ g_free(xendev);
+ return NULL;
+ }
+ fcntl(xc_evtchn_fd(xendev->evtchndev), F_SETFD, FD_CLOEXEC);
+
+ if (ops->flags & DEVOPS_FLAG_NEED_GNTDEV) {
+ xendev->gnttabdev = xen_xc_gnttab_open(NULL, 0);
+ if (xendev->gnttabdev == XC_HANDLER_INITIAL_VALUE) {
+ xen_be_printf(NULL, 0, "can't open gnttab device\n");
+ xc_evtchn_close(xendev->evtchndev);
+ g_free(xendev);
+ return NULL;
+ }
+ } else {
+ xendev->gnttabdev = XC_HANDLER_INITIAL_VALUE;
+ }
+
+ QTAILQ_INSERT_TAIL(&xendevs, xendev, next);
+
+ if (xendev->ops->alloc) {
+ xendev->ops->alloc(xendev);
+ }
+
+ return xendev;
+}
+
+/*
+ * release xen backend device.
+ */
+static struct XenDevice *xen_be_del_xendev(int dom, int dev)
+{
+ struct XenDevice *xendev, *xnext;
+
+ /*
+ * This is pretty much like QTAILQ_FOREACH(xendev, &xendevs, next) but
+ * we save the next pointer in xnext because we might free xendev.
+ */
+ xnext = xendevs.tqh_first;
+ while (xnext) {
+ xendev = xnext;
+ xnext = xendev->next.tqe_next;
+
+ if (xendev->dom != dom) {
+ continue;
+ }
+ if (xendev->dev != dev && dev != -1) {
+ continue;
+ }
+
+ if (xendev->ops->free) {
+ xendev->ops->free(xendev);
+ }
+
+ if (xendev->fe) {
+ char token[XEN_BUFSIZE];
+ snprintf(token, sizeof(token), "fe:%p", xendev);
+ xs_unwatch(xenstore, xendev->fe, token);
+ g_free(xendev->fe);
+ }
+
+ if (xendev->evtchndev != XC_HANDLER_INITIAL_VALUE) {
+ xc_evtchn_close(xendev->evtchndev);
+ }
+ if (xendev->gnttabdev != XC_HANDLER_INITIAL_VALUE) {
+ xc_gnttab_close(xendev->gnttabdev);
+ }
+
+ QTAILQ_REMOVE(&xendevs, xendev, next);
+ g_free(xendev);
+ }
+ return NULL;
+}
+
+/*
+ * Sync internal data structures on xenstore updates.
+ * Node specifies the changed field. node = NULL means
+ * update all fields (used for initialization).
+ */
+static void xen_be_backend_changed(struct XenDevice *xendev, const char *node)
+{
+ if (node == NULL || strcmp(node, "online") == 0) {
+ if (xenstore_read_be_int(xendev, "online", &xendev->online) == -1) {
+ xendev->online = 0;
+ }
+ }
+
+ if (node) {
+ xen_be_printf(xendev, 2, "backend update: %s\n", node);
+ if (xendev->ops->backend_changed) {
+ xendev->ops->backend_changed(xendev, node);
+ }
+ }
+}
+
+static void xen_be_frontend_changed(struct XenDevice *xendev, const char *node)
+{
+ int fe_state;
+
+ if (node == NULL || strcmp(node, "state") == 0) {
+ if (xenstore_read_fe_int(xendev, "state", &fe_state) == -1) {
+ fe_state = XenbusStateUnknown;
+ }
+ if (xendev->fe_state != fe_state) {
+ xen_be_printf(xendev, 1, "frontend state: %s -> %s\n",
+ xenbus_strstate(xendev->fe_state),
+ xenbus_strstate(fe_state));
+ }
+ xendev->fe_state = fe_state;
+ }
+ if (node == NULL || strcmp(node, "protocol") == 0) {
+ g_free(xendev->protocol);
+ xendev->protocol = xenstore_read_fe_str(xendev, "protocol");
+ if (xendev->protocol) {
+ xen_be_printf(xendev, 1, "frontend protocol: %s\n", xendev->protocol);
+ }
+ }
+
+ if (node) {
+ xen_be_printf(xendev, 2, "frontend update: %s\n", node);
+ if (xendev->ops->frontend_changed) {
+ xendev->ops->frontend_changed(xendev, node);
+ }
+ }
+}
+
+/* ------------------------------------------------------------- */
+/* Check for possible state transitions and perform them. */
+
+/*
+ * Initial xendev setup. Read frontend path, register watch for it.
+ * Should succeed once xend finished setting up the backend device.
+ *
+ * Also sets initial state (-> Initializing) when done. Which
+ * only affects the xendev->be_state variable as xenbus should
+ * already be put into that state by xend.
+ */
+static int xen_be_try_setup(struct XenDevice *xendev)
+{
+ char token[XEN_BUFSIZE];
+ int be_state;
+
+ if (xenstore_read_be_int(xendev, "state", &be_state) == -1) {
+ xen_be_printf(xendev, 0, "reading backend state failed\n");
+ return -1;
+ }
+
+ if (be_state != XenbusStateInitialising) {
+ xen_be_printf(xendev, 0, "initial backend state is wrong (%s)\n",
+ xenbus_strstate(be_state));
+ return -1;
+ }
+
+ xendev->fe = xenstore_read_be_str(xendev, "frontend");
+ if (xendev->fe == NULL) {
+ xen_be_printf(xendev, 0, "reading frontend path failed\n");
+ return -1;
+ }
+
+ /* setup frontend watch */
+ snprintf(token, sizeof(token), "fe:%p", xendev);
+ if (!xs_watch(xenstore, xendev->fe, token)) {
+ xen_be_printf(xendev, 0, "watching frontend path (%s) failed\n",
+ xendev->fe);
+ return -1;
+ }
+ xen_be_set_state(xendev, XenbusStateInitialising);
+
+ xen_be_backend_changed(xendev, NULL);
+ xen_be_frontend_changed(xendev, NULL);
+ return 0;
+}
+
+/*
+ * Try initialize xendev. Prepare everything the backend can do
+ * without synchronizing with the frontend. Fakes hotplug-status. No
+ * hotplug involved here because this is about userspace drivers, thus
+ * there are kernel backend devices which could invoke hotplug.
+ *
+ * Goes to InitWait on success.
+ */
+static int xen_be_try_init(struct XenDevice *xendev)
+{
+ int rc = 0;
+
+ if (!xendev->online) {
+ xen_be_printf(xendev, 1, "not online\n");
+ return -1;
+ }
+
+ if (xendev->ops->init) {
+ rc = xendev->ops->init(xendev);
+ }
+ if (rc != 0) {
+ xen_be_printf(xendev, 1, "init() failed\n");
+ return rc;
+ }
+
+ xenstore_write_be_str(xendev, "hotplug-status", "connected");
+ xen_be_set_state(xendev, XenbusStateInitWait);
+ return 0;
+}
+
+/*
+ * Try to initialise xendev. Depends on the frontend being ready
+ * for it (shared ring and evtchn info in xenstore, state being
+ * Initialised or Connected).
+ *
+ * Goes to Connected on success.
+ */
+static int xen_be_try_initialise(struct XenDevice *xendev)
+{
+ int rc = 0;
+
+ if (xendev->fe_state != XenbusStateInitialised &&
+ xendev->fe_state != XenbusStateConnected) {
+ if (xendev->ops->flags & DEVOPS_FLAG_IGNORE_STATE) {
+ xen_be_printf(xendev, 2, "frontend not ready, ignoring\n");
+ } else {
+ xen_be_printf(xendev, 2, "frontend not ready (yet)\n");
+ return -1;
+ }
+ }
+
+ if (xendev->ops->initialise) {
+ rc = xendev->ops->initialise(xendev);
+ }
+ if (rc != 0) {
+ xen_be_printf(xendev, 0, "initialise() failed\n");
+ return rc;
+ }
+
+ xen_be_set_state(xendev, XenbusStateConnected);
+ return 0;
+}
+
+/*
+ * Try to let xendev know that it is connected. Depends on the
+ * frontend being Connected. Note that this may be called more
+ * than once since the backend state is not modified.
+ */
+static void xen_be_try_connected(struct XenDevice *xendev)
+{
+ if (!xendev->ops->connected) {
+ return;
+ }
+
+ if (xendev->fe_state != XenbusStateConnected) {
+ if (xendev->ops->flags & DEVOPS_FLAG_IGNORE_STATE) {
+ xen_be_printf(xendev, 2, "frontend not ready, ignoring\n");
+ } else {
+ xen_be_printf(xendev, 2, "frontend not ready (yet)\n");
+ return;
+ }
+ }
+
+ xendev->ops->connected(xendev);
+}
+
+/*
+ * Teardown connection.
+ *
+ * Goes to Closed when done.
+ */
+static void xen_be_disconnect(struct XenDevice *xendev, enum xenbus_state state)
+{
+ if (xendev->be_state != XenbusStateClosing &&
+ xendev->be_state != XenbusStateClosed &&
+ xendev->ops->disconnect) {
+ xendev->ops->disconnect(xendev);
+ }
+ if (xendev->be_state != state) {
+ xen_be_set_state(xendev, state);
+ }
+}
+
+/*
+ * Try to reset xendev, for reconnection by another frontend instance.
+ */
+static int xen_be_try_reset(struct XenDevice *xendev)
+{
+ if (xendev->fe_state != XenbusStateInitialising) {
+ return -1;
+ }
+
+ xen_be_printf(xendev, 1, "device reset (for re-connect)\n");
+ xen_be_set_state(xendev, XenbusStateInitialising);
+ return 0;
+}
+
+/*
+ * state change dispatcher function
+ */
+void xen_be_check_state(struct XenDevice *xendev)
+{
+ int rc = 0;
+
+ /* frontend may request shutdown from almost anywhere */
+ if (xendev->fe_state == XenbusStateClosing ||
+ xendev->fe_state == XenbusStateClosed) {
+ xen_be_disconnect(xendev, xendev->fe_state);
+ return;
+ }
+
+ /* check for possible backend state transitions */
+ for (;;) {
+ switch (xendev->be_state) {
+ case XenbusStateUnknown:
+ rc = xen_be_try_setup(xendev);
+ break;
+ case XenbusStateInitialising:
+ rc = xen_be_try_init(xendev);
+ break;
+ case XenbusStateInitWait:
+ rc = xen_be_try_initialise(xendev);
+ break;
+ case XenbusStateConnected:
+ /* xendev->be_state doesn't change */
+ xen_be_try_connected(xendev);
+ rc = -1;
+ break;
+ case XenbusStateClosed:
+ rc = xen_be_try_reset(xendev);
+ break;
+ default:
+ rc = -1;
+ }
+ if (rc != 0) {
+ break;
+ }
+ }
+}
+
+/* ------------------------------------------------------------- */
+
+static int xenstore_scan(const char *type, int dom, struct XenDevOps *ops)
+{
+ struct XenDevice *xendev;
+ char path[XEN_BUFSIZE], token[XEN_BUFSIZE];
+ char **dev = NULL;
+ unsigned int cdev, j;
+
+ /* setup watch */
+ snprintf(token, sizeof(token), "be:%p:%d:%p", type, dom, ops);
+ snprintf(path, sizeof(path), "backend/%s/%d", type, dom);
+ if (!xs_watch(xenstore, path, token)) {
+ xen_be_printf(NULL, 0, "xen be: watching backend path (%s) failed\n", path);
+ return -1;
+ }
+
+ /* look for backends */
+ dev = xs_directory(xenstore, 0, path, &cdev);
+ if (!dev) {
+ return 0;
+ }
+ for (j = 0; j < cdev; j++) {
+ xendev = xen_be_get_xendev(type, dom, atoi(dev[j]), ops);
+ if (xendev == NULL) {
+ continue;
+ }
+ xen_be_check_state(xendev);
+ }
+ free(dev);
+ return 0;
+}
+
+static void xenstore_update_be(char *watch, char *type, int dom,
+ struct XenDevOps *ops)
+{
+ struct XenDevice *xendev;
+ char path[XEN_BUFSIZE], *bepath;
+ unsigned int len, dev;
+
+ len = snprintf(path, sizeof(path), "backend/%s/%d", type, dom);
+ if (strncmp(path, watch, len) != 0) {
+ return;
+ }
+ if (sscanf(watch+len, "/%u/%255s", &dev, path) != 2) {
+ strcpy(path, "");
+ if (sscanf(watch+len, "/%u", &dev) != 1) {
+ dev = -1;
+ }
+ }
+ if (dev == -1) {
+ return;
+ }
+
+ xendev = xen_be_get_xendev(type, dom, dev, ops);
+ if (xendev != NULL) {
+ bepath = xs_read(xenstore, 0, xendev->be, &len);
+ if (bepath == NULL) {
+ xen_be_del_xendev(dom, dev);
+ } else {
+ free(bepath);
+ xen_be_backend_changed(xendev, path);
+ xen_be_check_state(xendev);
+ }
+ }
+}
+
+static void xenstore_update_fe(char *watch, struct XenDevice *xendev)
+{
+ char *node;
+ unsigned int len;
+
+ len = strlen(xendev->fe);
+ if (strncmp(xendev->fe, watch, len) != 0) {
+ return;
+ }
+ if (watch[len] != '/') {
+ return;
+ }
+ node = watch + len + 1;
+
+ xen_be_frontend_changed(xendev, node);
+ xen_be_check_state(xendev);
+}
+
+static void xenstore_update(void *unused)
+{
+ char **vec = NULL;
+ intptr_t type, ops, ptr;
+ unsigned int dom, count;
+
+ vec = xs_read_watch(xenstore, &count);
+ if (vec == NULL) {
+ goto cleanup;
+ }
+
+ if (sscanf(vec[XS_WATCH_TOKEN], "be:%" PRIxPTR ":%d:%" PRIxPTR,
+ &type, &dom, &ops) == 3) {
+ xenstore_update_be(vec[XS_WATCH_PATH], (void*)type, dom, (void*)ops);
+ }
+ if (sscanf(vec[XS_WATCH_TOKEN], "fe:%" PRIxPTR, &ptr) == 1) {
+ xenstore_update_fe(vec[XS_WATCH_PATH], (void*)ptr);
+ }
+
+cleanup:
+ free(vec);
+}
+
+static void xen_be_evtchn_event(void *opaque)
+{
+ struct XenDevice *xendev = opaque;
+ evtchn_port_t port;
+
+ port = xc_evtchn_pending(xendev->evtchndev);
+ if (port != xendev->local_port) {
+ xen_be_printf(xendev, 0, "xc_evtchn_pending returned %d (expected %d)\n",
+ port, xendev->local_port);
+ return;
+ }
+ xc_evtchn_unmask(xendev->evtchndev, port);
+
+ if (xendev->ops->event) {
+ xendev->ops->event(xendev);
+ }
+}
+
+/* -------------------------------------------------------------------- */
+
+int xen_be_init(void)
+{
+ xenstore = xs_daemon_open();
+ if (!xenstore) {
+ xen_be_printf(NULL, 0, "can't connect to xenstored\n");
+ return -1;
+ }
+
+ qemu_set_fd_handler(xs_fileno(xenstore), xenstore_update, NULL, NULL);
+
+ if (xen_xc == XC_HANDLER_INITIAL_VALUE) {
+ /* Check if xen_init() have been called */
+ goto err;
+ }
+ return 0;
+
+err:
+ qemu_set_fd_handler(xs_fileno(xenstore), NULL, NULL, NULL);
+ xs_daemon_close(xenstore);
+ xenstore = NULL;
+
+ return -1;
+}
+
+int xen_be_register(const char *type, struct XenDevOps *ops)
+{
+ return xenstore_scan(type, xen_domid, ops);
+}
+
+int xen_be_bind_evtchn(struct XenDevice *xendev)
+{
+ if (xendev->local_port != -1) {
+ return 0;
+ }
+ xendev->local_port = xc_evtchn_bind_interdomain
+ (xendev->evtchndev, xendev->dom, xendev->remote_port);
+ if (xendev->local_port == -1) {
+ xen_be_printf(xendev, 0, "xc_evtchn_bind_interdomain failed\n");
+ return -1;
+ }
+ xen_be_printf(xendev, 2, "bind evtchn port %d\n", xendev->local_port);
+ qemu_set_fd_handler(xc_evtchn_fd(xendev->evtchndev),
+ xen_be_evtchn_event, NULL, xendev);
+ return 0;
+}
+
+void xen_be_unbind_evtchn(struct XenDevice *xendev)
+{
+ if (xendev->local_port == -1) {
+ return;
+ }
+ qemu_set_fd_handler(xc_evtchn_fd(xendev->evtchndev), NULL, NULL, NULL);
+ xc_evtchn_unbind(xendev->evtchndev, xendev->local_port);
+ xen_be_printf(xendev, 2, "unbind evtchn port %d\n", xendev->local_port);
+ xendev->local_port = -1;
+}
+
+int xen_be_send_notify(struct XenDevice *xendev)
+{
+ return xc_evtchn_notify(xendev->evtchndev, xendev->local_port);
+}
+
+/*
+ * msg_level:
+ * 0 == errors (stderr + logfile).
+ * 1 == informative debug messages (logfile only).
+ * 2 == noisy debug messages (logfile only).
+ * 3 == will flood your log (logfile only).
+ */
+void xen_be_printf(struct XenDevice *xendev, int msg_level, const char *fmt, ...)
+{
+ va_list args;
+
+ if (xendev) {
+ if (msg_level > xendev->debug) {
+ return;
+ }
+ qemu_log("xen be: %s: ", xendev->name);
+ if (msg_level == 0) {
+ fprintf(stderr, "xen be: %s: ", xendev->name);
+ }
+ } else {
+ if (msg_level > debug) {
+ return;
+ }
+ qemu_log("xen be core: ");
+ if (msg_level == 0) {
+ fprintf(stderr, "xen be core: ");
+ }
+ }
+ va_start(args, fmt);
+ qemu_log_vprintf(fmt, args);
+ va_end(args);
+ if (msg_level == 0) {
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ }
+ qemu_log_flush();
+}
diff --git a/hw/xen/xen_devconfig.c b/hw/xen/xen_devconfig.c
new file mode 100644
index 00000000..e138dbbe
--- /dev/null
+++ b/hw/xen/xen_devconfig.c
@@ -0,0 +1,175 @@
+#include "hw/xen/xen_backend.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+
+/* ------------------------------------------------------------- */
+
+struct xs_dirs {
+ char *xs_dir;
+ QTAILQ_ENTRY(xs_dirs) list;
+};
+static QTAILQ_HEAD(xs_dirs_head, xs_dirs) xs_cleanup = QTAILQ_HEAD_INITIALIZER(xs_cleanup);
+
+static void xen_config_cleanup_dir(char *dir)
+{
+ struct xs_dirs *d;
+
+ d = g_malloc(sizeof(*d));
+ d->xs_dir = dir;
+ QTAILQ_INSERT_TAIL(&xs_cleanup, d, list);
+}
+
+void xen_config_cleanup(void)
+{
+ struct xs_dirs *d;
+
+ QTAILQ_FOREACH(d, &xs_cleanup, list) {
+ xs_rm(xenstore, 0, d->xs_dir);
+ }
+}
+
+/* ------------------------------------------------------------- */
+
+static int xen_config_dev_mkdir(char *dev, int p)
+{
+ struct xs_permissions perms[2] = {{
+ .id = 0, /* set owner: dom0 */
+ },{
+ .id = xen_domid,
+ .perms = p,
+ }};
+
+ if (!xs_mkdir(xenstore, 0, dev)) {
+ xen_be_printf(NULL, 0, "xs_mkdir %s: failed\n", dev);
+ return -1;
+ }
+ xen_config_cleanup_dir(g_strdup(dev));
+
+ if (!xs_set_permissions(xenstore, 0, dev, perms, 2)) {
+ xen_be_printf(NULL, 0, "xs_set_permissions %s: failed\n", dev);
+ return -1;
+ }
+ return 0;
+}
+
+static int xen_config_dev_dirs(const char *ftype, const char *btype, int vdev,
+ char *fe, char *be, int len)
+{
+ char *dom;
+
+ dom = xs_get_domain_path(xenstore, xen_domid);
+ snprintf(fe, len, "%s/device/%s/%d", dom, ftype, vdev);
+ free(dom);
+
+ dom = xs_get_domain_path(xenstore, 0);
+ snprintf(be, len, "%s/backend/%s/%d/%d", dom, btype, xen_domid, vdev);
+ free(dom);
+
+ xen_config_dev_mkdir(fe, XS_PERM_READ | XS_PERM_WRITE);
+ xen_config_dev_mkdir(be, XS_PERM_READ);
+ return 0;
+}
+
+static int xen_config_dev_all(char *fe, char *be)
+{
+ /* frontend */
+ if (xen_protocol)
+ xenstore_write_str(fe, "protocol", xen_protocol);
+
+ xenstore_write_int(fe, "state", XenbusStateInitialising);
+ xenstore_write_int(fe, "backend-id", 0);
+ xenstore_write_str(fe, "backend", be);
+
+ /* backend */
+ xenstore_write_str(be, "domain", qemu_name ? qemu_name : "no-name");
+ xenstore_write_int(be, "online", 1);
+ xenstore_write_int(be, "state", XenbusStateInitialising);
+ xenstore_write_int(be, "frontend-id", xen_domid);
+ xenstore_write_str(be, "frontend", fe);
+
+ return 0;
+}
+
+/* ------------------------------------------------------------- */
+
+int xen_config_dev_blk(DriveInfo *disk)
+{
+ char fe[256], be[256], device_name[32];
+ int vdev = 202 * 256 + 16 * disk->unit;
+ int cdrom = disk->media_cd;
+ const char *devtype = cdrom ? "cdrom" : "disk";
+ const char *mode = cdrom ? "r" : "w";
+ const char *filename = qemu_opt_get(disk->opts, "file");
+
+ snprintf(device_name, sizeof(device_name), "xvd%c", 'a' + disk->unit);
+ xen_be_printf(NULL, 1, "config disk %d [%s]: %s\n",
+ disk->unit, device_name, filename);
+ xen_config_dev_dirs("vbd", "qdisk", vdev, fe, be, sizeof(fe));
+
+ /* frontend */
+ xenstore_write_int(fe, "virtual-device", vdev);
+ xenstore_write_str(fe, "device-type", devtype);
+
+ /* backend */
+ xenstore_write_str(be, "dev", device_name);
+ xenstore_write_str(be, "type", "file");
+ xenstore_write_str(be, "params", filename);
+ xenstore_write_str(be, "mode", mode);
+
+ /* common stuff */
+ return xen_config_dev_all(fe, be);
+}
+
+int xen_config_dev_nic(NICInfo *nic)
+{
+ char fe[256], be[256];
+ char mac[20];
+ int vlan_id = -1;
+
+ net_hub_id_for_client(nic->netdev, &vlan_id);
+ snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x",
+ nic->macaddr.a[0], nic->macaddr.a[1], nic->macaddr.a[2],
+ nic->macaddr.a[3], nic->macaddr.a[4], nic->macaddr.a[5]);
+ xen_be_printf(NULL, 1, "config nic %d: mac=\"%s\"\n", vlan_id, mac);
+ xen_config_dev_dirs("vif", "qnic", vlan_id, fe, be, sizeof(fe));
+
+ /* frontend */
+ xenstore_write_int(fe, "handle", vlan_id);
+ xenstore_write_str(fe, "mac", mac);
+
+ /* backend */
+ xenstore_write_int(be, "handle", vlan_id);
+ xenstore_write_str(be, "mac", mac);
+
+ /* common stuff */
+ return xen_config_dev_all(fe, be);
+}
+
+int xen_config_dev_vfb(int vdev, const char *type)
+{
+ char fe[256], be[256];
+
+ xen_config_dev_dirs("vfb", "vfb", vdev, fe, be, sizeof(fe));
+
+ /* backend */
+ xenstore_write_str(be, "type", type);
+
+ /* common stuff */
+ return xen_config_dev_all(fe, be);
+}
+
+int xen_config_dev_vkbd(int vdev)
+{
+ char fe[256], be[256];
+
+ xen_config_dev_dirs("vkbd", "vkbd", vdev, fe, be, sizeof(fe));
+ return xen_config_dev_all(fe, be);
+}
+
+int xen_config_dev_console(int vdev)
+{
+ char fe[256], be[256];
+
+ xen_config_dev_dirs("console", "console", vdev, fe, be, sizeof(fe));
+ return xen_config_dev_all(fe, be);
+}
diff --git a/hw/xen/xen_pt.c b/hw/xen/xen_pt.c
new file mode 100644
index 00000000..ed5fcaec
--- /dev/null
+++ b/hw/xen/xen_pt.c
@@ -0,0 +1,885 @@
+/*
+ * Copyright (c) 2007, Neocleus Corporation.
+ * Copyright (c) 2007, Intel Corporation.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Alex Novik <alex@neocleus.com>
+ * Allen Kay <allen.m.kay@intel.com>
+ * Guy Zana <guy@neocleus.com>
+ *
+ * This file implements direct PCI assignment to a HVM guest
+ */
+
+/*
+ * Interrupt Disable policy:
+ *
+ * INTx interrupt:
+ * Initialize(register_real_device)
+ * Map INTx(xc_physdev_map_pirq):
+ * <fail>
+ * - Set real Interrupt Disable bit to '1'.
+ * - Set machine_irq and assigned_device->machine_irq to '0'.
+ * * Don't bind INTx.
+ *
+ * Bind INTx(xc_domain_bind_pt_pci_irq):
+ * <fail>
+ * - Set real Interrupt Disable bit to '1'.
+ * - Unmap INTx.
+ * - Decrement xen_pt_mapped_machine_irq[machine_irq]
+ * - Set assigned_device->machine_irq to '0'.
+ *
+ * Write to Interrupt Disable bit by guest software(xen_pt_cmd_reg_write)
+ * Write '0'
+ * - Set real bit to '0' if assigned_device->machine_irq isn't '0'.
+ *
+ * Write '1'
+ * - Set real bit to '1'.
+ *
+ * MSI interrupt:
+ * Initialize MSI register(xen_pt_msi_setup, xen_pt_msi_update)
+ * Bind MSI(xc_domain_update_msi_irq)
+ * <fail>
+ * - Unmap MSI.
+ * - Set dev->msi->pirq to '-1'.
+ *
+ * MSI-X interrupt:
+ * Initialize MSI-X register(xen_pt_msix_update_one)
+ * Bind MSI-X(xc_domain_update_msi_irq)
+ * <fail>
+ * - Unmap MSI-X.
+ * - Set entry->pirq to '-1'.
+ */
+
+#include <sys/ioctl.h>
+
+#include "hw/pci/pci.h"
+#include "hw/xen/xen.h"
+#include "hw/xen/xen_backend.h"
+#include "xen_pt.h"
+#include "qemu/range.h"
+#include "exec/address-spaces.h"
+
+#define XEN_PT_NR_IRQS (256)
+static uint8_t xen_pt_mapped_machine_irq[XEN_PT_NR_IRQS] = {0};
+
+void xen_pt_log(const PCIDevice *d, const char *f, ...)
+{
+ va_list ap;
+
+ va_start(ap, f);
+ if (d) {
+ fprintf(stderr, "[%02x:%02x.%d] ", pci_bus_num(d->bus),
+ PCI_SLOT(d->devfn), PCI_FUNC(d->devfn));
+ }
+ vfprintf(stderr, f, ap);
+ va_end(ap);
+}
+
+/* Config Space */
+
+static int xen_pt_pci_config_access_check(PCIDevice *d, uint32_t addr, int len)
+{
+ /* check offset range */
+ if (addr >= 0xFF) {
+ XEN_PT_ERR(d, "Failed to access register with offset exceeding 0xFF. "
+ "(addr: 0x%02x, len: %d)\n", addr, len);
+ return -1;
+ }
+
+ /* check read size */
+ if ((len != 1) && (len != 2) && (len != 4)) {
+ XEN_PT_ERR(d, "Failed to access register with invalid access length. "
+ "(addr: 0x%02x, len: %d)\n", addr, len);
+ return -1;
+ }
+
+ /* check offset alignment */
+ if (addr & (len - 1)) {
+ XEN_PT_ERR(d, "Failed to access register with invalid access size "
+ "alignment. (addr: 0x%02x, len: %d)\n", addr, len);
+ return -1;
+ }
+
+ return 0;
+}
+
+int xen_pt_bar_offset_to_index(uint32_t offset)
+{
+ int index = 0;
+
+ /* check Exp ROM BAR */
+ if (offset == PCI_ROM_ADDRESS) {
+ return PCI_ROM_SLOT;
+ }
+
+ /* calculate BAR index */
+ index = (offset - PCI_BASE_ADDRESS_0) >> 2;
+ if (index >= PCI_NUM_REGIONS) {
+ return -1;
+ }
+
+ return index;
+}
+
+static uint32_t xen_pt_pci_read_config(PCIDevice *d, uint32_t addr, int len)
+{
+ XenPCIPassthroughState *s = XEN_PT_DEVICE(d);
+ uint32_t val = 0;
+ XenPTRegGroup *reg_grp_entry = NULL;
+ XenPTReg *reg_entry = NULL;
+ int rc = 0;
+ int emul_len = 0;
+ uint32_t find_addr = addr;
+
+ if (xen_pt_pci_config_access_check(d, addr, len)) {
+ goto exit;
+ }
+
+ /* find register group entry */
+ reg_grp_entry = xen_pt_find_reg_grp(s, addr);
+ if (reg_grp_entry) {
+ /* check 0-Hardwired register group */
+ if (reg_grp_entry->reg_grp->grp_type == XEN_PT_GRP_TYPE_HARDWIRED) {
+ /* no need to emulate, just return 0 */
+ val = 0;
+ goto exit;
+ }
+ }
+
+ /* read I/O device register value */
+ rc = xen_host_pci_get_block(&s->real_device, addr, (uint8_t *)&val, len);
+ if (rc < 0) {
+ XEN_PT_ERR(d, "pci_read_block failed. return value: %d.\n", rc);
+ memset(&val, 0xff, len);
+ }
+
+ /* just return the I/O device register value for
+ * passthrough type register group */
+ if (reg_grp_entry == NULL) {
+ goto exit;
+ }
+
+ /* adjust the read value to appropriate CFC-CFF window */
+ val <<= (addr & 3) << 3;
+ emul_len = len;
+
+ /* loop around the guest requested size */
+ while (emul_len > 0) {
+ /* find register entry to be emulated */
+ reg_entry = xen_pt_find_reg(reg_grp_entry, find_addr);
+ if (reg_entry) {
+ XenPTRegInfo *reg = reg_entry->reg;
+ uint32_t real_offset = reg_grp_entry->base_offset + reg->offset;
+ uint32_t valid_mask = 0xFFFFFFFF >> ((4 - emul_len) << 3);
+ uint8_t *ptr_val = NULL;
+
+ valid_mask <<= (find_addr - real_offset) << 3;
+ ptr_val = (uint8_t *)&val + (real_offset & 3);
+
+ /* do emulation based on register size */
+ switch (reg->size) {
+ case 1:
+ if (reg->u.b.read) {
+ rc = reg->u.b.read(s, reg_entry, ptr_val, valid_mask);
+ }
+ break;
+ case 2:
+ if (reg->u.w.read) {
+ rc = reg->u.w.read(s, reg_entry,
+ (uint16_t *)ptr_val, valid_mask);
+ }
+ break;
+ case 4:
+ if (reg->u.dw.read) {
+ rc = reg->u.dw.read(s, reg_entry,
+ (uint32_t *)ptr_val, valid_mask);
+ }
+ break;
+ }
+
+ if (rc < 0) {
+ xen_shutdown_fatal_error("Internal error: Invalid read "
+ "emulation. (%s, rc: %d)\n",
+ __func__, rc);
+ return 0;
+ }
+
+ /* calculate next address to find */
+ emul_len -= reg->size;
+ if (emul_len > 0) {
+ find_addr = real_offset + reg->size;
+ }
+ } else {
+ /* nothing to do with passthrough type register,
+ * continue to find next byte */
+ emul_len--;
+ find_addr++;
+ }
+ }
+
+ /* need to shift back before returning them to pci bus emulator */
+ val >>= ((addr & 3) << 3);
+
+exit:
+ XEN_PT_LOG_CONFIG(d, addr, val, len);
+ return val;
+}
+
+static void xen_pt_pci_write_config(PCIDevice *d, uint32_t addr,
+ uint32_t val, int len)
+{
+ XenPCIPassthroughState *s = XEN_PT_DEVICE(d);
+ int index = 0;
+ XenPTRegGroup *reg_grp_entry = NULL;
+ int rc = 0;
+ uint32_t read_val = 0, wb_mask;
+ int emul_len = 0;
+ XenPTReg *reg_entry = NULL;
+ uint32_t find_addr = addr;
+ XenPTRegInfo *reg = NULL;
+ bool wp_flag = false;
+
+ if (xen_pt_pci_config_access_check(d, addr, len)) {
+ return;
+ }
+
+ XEN_PT_LOG_CONFIG(d, addr, val, len);
+
+ /* check unused BAR register */
+ index = xen_pt_bar_offset_to_index(addr);
+ if ((index >= 0) && (val != 0)) {
+ uint32_t chk = val;
+
+ if (index == PCI_ROM_SLOT)
+ chk |= (uint32_t)~PCI_ROM_ADDRESS_MASK;
+
+ if ((chk != XEN_PT_BAR_ALLF) &&
+ (s->bases[index].bar_flag == XEN_PT_BAR_FLAG_UNUSED)) {
+ XEN_PT_WARN(d, "Guest attempt to set address to unused "
+ "Base Address Register. (addr: 0x%02x, len: %d)\n",
+ addr, len);
+ }
+ }
+
+ /* find register group entry */
+ reg_grp_entry = xen_pt_find_reg_grp(s, addr);
+ if (reg_grp_entry) {
+ /* check 0-Hardwired register group */
+ if (reg_grp_entry->reg_grp->grp_type == XEN_PT_GRP_TYPE_HARDWIRED) {
+ /* ignore silently */
+ XEN_PT_WARN(d, "Access to 0-Hardwired register. "
+ "(addr: 0x%02x, len: %d)\n", addr, len);
+ return;
+ }
+ }
+
+ rc = xen_host_pci_get_block(&s->real_device, addr,
+ (uint8_t *)&read_val, len);
+ if (rc < 0) {
+ XEN_PT_ERR(d, "pci_read_block failed. return value: %d.\n", rc);
+ memset(&read_val, 0xff, len);
+ wb_mask = 0;
+ } else {
+ wb_mask = 0xFFFFFFFF >> ((4 - len) << 3);
+ }
+
+ /* pass directly to the real device for passthrough type register group */
+ if (reg_grp_entry == NULL) {
+ if (!s->permissive) {
+ wb_mask = 0;
+ wp_flag = true;
+ }
+ goto out;
+ }
+
+ memory_region_transaction_begin();
+ pci_default_write_config(d, addr, val, len);
+
+ /* adjust the read and write value to appropriate CFC-CFF window */
+ read_val <<= (addr & 3) << 3;
+ val <<= (addr & 3) << 3;
+ emul_len = len;
+
+ /* loop around the guest requested size */
+ while (emul_len > 0) {
+ /* find register entry to be emulated */
+ reg_entry = xen_pt_find_reg(reg_grp_entry, find_addr);
+ if (reg_entry) {
+ reg = reg_entry->reg;
+ uint32_t real_offset = reg_grp_entry->base_offset + reg->offset;
+ uint32_t valid_mask = 0xFFFFFFFF >> ((4 - emul_len) << 3);
+ uint8_t *ptr_val = NULL;
+ uint32_t wp_mask = reg->emu_mask | reg->ro_mask;
+
+ valid_mask <<= (find_addr - real_offset) << 3;
+ ptr_val = (uint8_t *)&val + (real_offset & 3);
+ if (!s->permissive) {
+ wp_mask |= reg->res_mask;
+ }
+ if (wp_mask == (0xFFFFFFFF >> ((4 - reg->size) << 3))) {
+ wb_mask &= ~((wp_mask >> ((find_addr - real_offset) << 3))
+ << ((len - emul_len) << 3));
+ }
+
+ /* do emulation based on register size */
+ switch (reg->size) {
+ case 1:
+ if (reg->u.b.write) {
+ rc = reg->u.b.write(s, reg_entry, ptr_val,
+ read_val >> ((real_offset & 3) << 3),
+ valid_mask);
+ }
+ break;
+ case 2:
+ if (reg->u.w.write) {
+ rc = reg->u.w.write(s, reg_entry, (uint16_t *)ptr_val,
+ (read_val >> ((real_offset & 3) << 3)),
+ valid_mask);
+ }
+ break;
+ case 4:
+ if (reg->u.dw.write) {
+ rc = reg->u.dw.write(s, reg_entry, (uint32_t *)ptr_val,
+ (read_val >> ((real_offset & 3) << 3)),
+ valid_mask);
+ }
+ break;
+ }
+
+ if (rc < 0) {
+ xen_shutdown_fatal_error("Internal error: Invalid write"
+ " emulation. (%s, rc: %d)\n",
+ __func__, rc);
+ return;
+ }
+
+ /* calculate next address to find */
+ emul_len -= reg->size;
+ if (emul_len > 0) {
+ find_addr = real_offset + reg->size;
+ }
+ } else {
+ /* nothing to do with passthrough type register,
+ * continue to find next byte */
+ if (!s->permissive) {
+ wb_mask &= ~(0xff << ((len - emul_len) << 3));
+ /* Unused BARs will make it here, but we don't want to issue
+ * warnings for writes to them (bogus writes get dealt with
+ * above).
+ */
+ if (index < 0) {
+ wp_flag = true;
+ }
+ }
+ emul_len--;
+ find_addr++;
+ }
+ }
+
+ /* need to shift back before passing them to xen_host_pci_device */
+ val >>= (addr & 3) << 3;
+
+ memory_region_transaction_commit();
+
+out:
+ if (wp_flag && !s->permissive_warned) {
+ s->permissive_warned = true;
+ xen_pt_log(d, "Write-back to unknown field 0x%02x (partially) inhibited (0x%0*x)\n",
+ addr, len * 2, wb_mask);
+ xen_pt_log(d, "If the device doesn't work, try enabling permissive mode\n");
+ xen_pt_log(d, "(unsafe) and if it helps report the problem to xen-devel\n");
+ }
+ for (index = 0; wb_mask; index += len) {
+ /* unknown regs are passed through */
+ while (!(wb_mask & 0xff)) {
+ index++;
+ wb_mask >>= 8;
+ }
+ len = 0;
+ do {
+ len++;
+ wb_mask >>= 8;
+ } while (wb_mask & 0xff);
+ rc = xen_host_pci_set_block(&s->real_device, addr + index,
+ (uint8_t *)&val + index, len);
+
+ if (rc < 0) {
+ XEN_PT_ERR(d, "pci_write_block failed. return value: %d.\n", rc);
+ }
+ }
+}
+
+/* register regions */
+
+static uint64_t xen_pt_bar_read(void *o, hwaddr addr,
+ unsigned size)
+{
+ PCIDevice *d = o;
+ /* if this function is called, that probably means that there is a
+ * misconfiguration of the IOMMU. */
+ XEN_PT_ERR(d, "Should not read BAR through QEMU. @0x"TARGET_FMT_plx"\n",
+ addr);
+ return 0;
+}
+static void xen_pt_bar_write(void *o, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ PCIDevice *d = o;
+ /* Same comment as xen_pt_bar_read function */
+ XEN_PT_ERR(d, "Should not write BAR through QEMU. @0x"TARGET_FMT_plx"\n",
+ addr);
+}
+
+static const MemoryRegionOps ops = {
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .read = xen_pt_bar_read,
+ .write = xen_pt_bar_write,
+};
+
+static int xen_pt_register_regions(XenPCIPassthroughState *s, uint16_t *cmd)
+{
+ int i = 0;
+ XenHostPCIDevice *d = &s->real_device;
+
+ /* Register PIO/MMIO BARs */
+ for (i = 0; i < PCI_ROM_SLOT; i++) {
+ XenHostPCIIORegion *r = &d->io_regions[i];
+ uint8_t type;
+
+ if (r->base_addr == 0 || r->size == 0) {
+ continue;
+ }
+
+ s->bases[i].access.u = r->base_addr;
+
+ if (r->type & XEN_HOST_PCI_REGION_TYPE_IO) {
+ type = PCI_BASE_ADDRESS_SPACE_IO;
+ *cmd |= PCI_COMMAND_IO;
+ } else {
+ type = PCI_BASE_ADDRESS_SPACE_MEMORY;
+ if (r->type & XEN_HOST_PCI_REGION_TYPE_PREFETCH) {
+ type |= PCI_BASE_ADDRESS_MEM_PREFETCH;
+ }
+ if (r->type & XEN_HOST_PCI_REGION_TYPE_MEM_64) {
+ type |= PCI_BASE_ADDRESS_MEM_TYPE_64;
+ }
+ *cmd |= PCI_COMMAND_MEMORY;
+ }
+
+ memory_region_init_io(&s->bar[i], OBJECT(s), &ops, &s->dev,
+ "xen-pci-pt-bar", r->size);
+ pci_register_bar(&s->dev, i, type, &s->bar[i]);
+
+ XEN_PT_LOG(&s->dev, "IO region %i registered (size=0x%08"PRIx64
+ " base_addr=0x%08"PRIx64" type: %#x)\n",
+ i, r->size, r->base_addr, type);
+ }
+
+ /* Register expansion ROM address */
+ if (d->rom.base_addr && d->rom.size) {
+ uint32_t bar_data = 0;
+
+ /* Re-set BAR reported by OS, otherwise ROM can't be read. */
+ if (xen_host_pci_get_long(d, PCI_ROM_ADDRESS, &bar_data)) {
+ return 0;
+ }
+ if ((bar_data & PCI_ROM_ADDRESS_MASK) == 0) {
+ bar_data |= d->rom.base_addr & PCI_ROM_ADDRESS_MASK;
+ xen_host_pci_set_long(d, PCI_ROM_ADDRESS, bar_data);
+ }
+
+ s->bases[PCI_ROM_SLOT].access.maddr = d->rom.base_addr;
+
+ memory_region_init_io(&s->rom, OBJECT(s), &ops, &s->dev,
+ "xen-pci-pt-rom", d->rom.size);
+ pci_register_bar(&s->dev, PCI_ROM_SLOT, PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &s->rom);
+
+ XEN_PT_LOG(&s->dev, "Expansion ROM registered (size=0x%08"PRIx64
+ " base_addr=0x%08"PRIx64")\n",
+ d->rom.size, d->rom.base_addr);
+ }
+
+ return 0;
+}
+
+/* region mapping */
+
+static int xen_pt_bar_from_region(XenPCIPassthroughState *s, MemoryRegion *mr)
+{
+ int i = 0;
+
+ for (i = 0; i < PCI_NUM_REGIONS - 1; i++) {
+ if (mr == &s->bar[i]) {
+ return i;
+ }
+ }
+ if (mr == &s->rom) {
+ return PCI_ROM_SLOT;
+ }
+ return -1;
+}
+
+/*
+ * This function checks if an io_region overlaps an io_region from another
+ * device. The io_region to check is provided with (addr, size and type)
+ * A callback can be provided and will be called for every region that is
+ * overlapped.
+ * The return value indicates if the region is overlappsed */
+struct CheckBarArgs {
+ XenPCIPassthroughState *s;
+ pcibus_t addr;
+ pcibus_t size;
+ uint8_t type;
+ bool rc;
+};
+static void xen_pt_check_bar_overlap(PCIBus *bus, PCIDevice *d, void *opaque)
+{
+ struct CheckBarArgs *arg = opaque;
+ XenPCIPassthroughState *s = arg->s;
+ uint8_t type = arg->type;
+ int i;
+
+ if (d->devfn == s->dev.devfn) {
+ return;
+ }
+
+ /* xxx: This ignores bridges. */
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ const PCIIORegion *r = &d->io_regions[i];
+
+ if (!r->size) {
+ continue;
+ }
+ if ((type & PCI_BASE_ADDRESS_SPACE_IO)
+ != (r->type & PCI_BASE_ADDRESS_SPACE_IO)) {
+ continue;
+ }
+
+ if (ranges_overlap(arg->addr, arg->size, r->addr, r->size)) {
+ XEN_PT_WARN(&s->dev,
+ "Overlapped to device [%02x:%02x.%d] Region: %i"
+ " (addr: %#"FMT_PCIBUS", len: %#"FMT_PCIBUS")\n",
+ pci_bus_num(bus), PCI_SLOT(d->devfn),
+ PCI_FUNC(d->devfn), i, r->addr, r->size);
+ arg->rc = true;
+ }
+ }
+}
+
+static void xen_pt_region_update(XenPCIPassthroughState *s,
+ MemoryRegionSection *sec, bool adding)
+{
+ PCIDevice *d = &s->dev;
+ MemoryRegion *mr = sec->mr;
+ int bar = -1;
+ int rc;
+ int op = adding ? DPCI_ADD_MAPPING : DPCI_REMOVE_MAPPING;
+ struct CheckBarArgs args = {
+ .s = s,
+ .addr = sec->offset_within_address_space,
+ .size = int128_get64(sec->size),
+ .rc = false,
+ };
+
+ bar = xen_pt_bar_from_region(s, mr);
+ if (bar == -1 && (!s->msix || &s->msix->mmio != mr)) {
+ return;
+ }
+
+ if (s->msix && &s->msix->mmio == mr) {
+ if (adding) {
+ s->msix->mmio_base_addr = sec->offset_within_address_space;
+ rc = xen_pt_msix_update_remap(s, s->msix->bar_index);
+ }
+ return;
+ }
+
+ args.type = d->io_regions[bar].type;
+ pci_for_each_device(d->bus, pci_bus_num(d->bus),
+ xen_pt_check_bar_overlap, &args);
+ if (args.rc) {
+ XEN_PT_WARN(d, "Region: %d (addr: %#"FMT_PCIBUS
+ ", len: %#"FMT_PCIBUS") is overlapped.\n",
+ bar, sec->offset_within_address_space,
+ int128_get64(sec->size));
+ }
+
+ if (d->io_regions[bar].type & PCI_BASE_ADDRESS_SPACE_IO) {
+ uint32_t guest_port = sec->offset_within_address_space;
+ uint32_t machine_port = s->bases[bar].access.pio_base;
+ uint32_t size = int128_get64(sec->size);
+ rc = xc_domain_ioport_mapping(xen_xc, xen_domid,
+ guest_port, machine_port, size,
+ op);
+ if (rc) {
+ XEN_PT_ERR(d, "%s ioport mapping failed! (err: %i)\n",
+ adding ? "create new" : "remove old", errno);
+ }
+ } else {
+ pcibus_t guest_addr = sec->offset_within_address_space;
+ pcibus_t machine_addr = s->bases[bar].access.maddr
+ + sec->offset_within_region;
+ pcibus_t size = int128_get64(sec->size);
+ rc = xc_domain_memory_mapping(xen_xc, xen_domid,
+ XEN_PFN(guest_addr + XC_PAGE_SIZE - 1),
+ XEN_PFN(machine_addr + XC_PAGE_SIZE - 1),
+ XEN_PFN(size + XC_PAGE_SIZE - 1),
+ op);
+ if (rc) {
+ XEN_PT_ERR(d, "%s mem mapping failed! (err: %i)\n",
+ adding ? "create new" : "remove old", errno);
+ }
+ }
+}
+
+static void xen_pt_region_add(MemoryListener *l, MemoryRegionSection *sec)
+{
+ XenPCIPassthroughState *s = container_of(l, XenPCIPassthroughState,
+ memory_listener);
+
+ memory_region_ref(sec->mr);
+ xen_pt_region_update(s, sec, true);
+}
+
+static void xen_pt_region_del(MemoryListener *l, MemoryRegionSection *sec)
+{
+ XenPCIPassthroughState *s = container_of(l, XenPCIPassthroughState,
+ memory_listener);
+
+ xen_pt_region_update(s, sec, false);
+ memory_region_unref(sec->mr);
+}
+
+static void xen_pt_io_region_add(MemoryListener *l, MemoryRegionSection *sec)
+{
+ XenPCIPassthroughState *s = container_of(l, XenPCIPassthroughState,
+ io_listener);
+
+ memory_region_ref(sec->mr);
+ xen_pt_region_update(s, sec, true);
+}
+
+static void xen_pt_io_region_del(MemoryListener *l, MemoryRegionSection *sec)
+{
+ XenPCIPassthroughState *s = container_of(l, XenPCIPassthroughState,
+ io_listener);
+
+ xen_pt_region_update(s, sec, false);
+ memory_region_unref(sec->mr);
+}
+
+static const MemoryListener xen_pt_memory_listener = {
+ .region_add = xen_pt_region_add,
+ .region_del = xen_pt_region_del,
+ .priority = 10,
+};
+
+static const MemoryListener xen_pt_io_listener = {
+ .region_add = xen_pt_io_region_add,
+ .region_del = xen_pt_io_region_del,
+ .priority = 10,
+};
+
+/* init */
+
+static int xen_pt_initfn(PCIDevice *d)
+{
+ XenPCIPassthroughState *s = XEN_PT_DEVICE(d);
+ int rc = 0;
+ uint8_t machine_irq = 0;
+ uint16_t cmd = 0;
+ int pirq = XEN_PT_UNASSIGNED_PIRQ;
+
+ /* register real device */
+ XEN_PT_LOG(d, "Assigning real physical device %02x:%02x.%d"
+ " to devfn %#x\n",
+ s->hostaddr.bus, s->hostaddr.slot, s->hostaddr.function,
+ s->dev.devfn);
+
+ rc = xen_host_pci_device_get(&s->real_device,
+ s->hostaddr.domain, s->hostaddr.bus,
+ s->hostaddr.slot, s->hostaddr.function);
+ if (rc) {
+ XEN_PT_ERR(d, "Failed to \"open\" the real pci device. rc: %i\n", rc);
+ return -1;
+ }
+
+ s->is_virtfn = s->real_device.is_virtfn;
+ if (s->is_virtfn) {
+ XEN_PT_LOG(d, "%04x:%02x:%02x.%d is a SR-IOV Virtual Function\n",
+ s->real_device.domain, s->real_device.bus,
+ s->real_device.dev, s->real_device.func);
+ }
+
+ /* Initialize virtualized PCI configuration (Extended 256 Bytes) */
+ if (xen_host_pci_get_block(&s->real_device, 0, d->config,
+ PCI_CONFIG_SPACE_SIZE) == -1) {
+ xen_host_pci_device_put(&s->real_device);
+ return -1;
+ }
+
+ s->memory_listener = xen_pt_memory_listener;
+ s->io_listener = xen_pt_io_listener;
+
+ /* Handle real device's MMIO/PIO BARs */
+ xen_pt_register_regions(s, &cmd);
+
+ /* reinitialize each config register to be emulated */
+ if (xen_pt_config_init(s)) {
+ XEN_PT_ERR(d, "PCI Config space initialisation failed.\n");
+ xen_host_pci_device_put(&s->real_device);
+ return -1;
+ }
+
+ /* Bind interrupt */
+ if (!s->dev.config[PCI_INTERRUPT_PIN]) {
+ XEN_PT_LOG(d, "no pin interrupt\n");
+ goto out;
+ }
+
+ machine_irq = s->real_device.irq;
+ rc = xc_physdev_map_pirq(xen_xc, xen_domid, machine_irq, &pirq);
+
+ if (rc < 0) {
+ XEN_PT_ERR(d, "Mapping machine irq %u to pirq %i failed, (err: %d)\n",
+ machine_irq, pirq, errno);
+
+ /* Disable PCI intx assertion (turn on bit10 of devctl) */
+ cmd |= PCI_COMMAND_INTX_DISABLE;
+ machine_irq = 0;
+ s->machine_irq = 0;
+ } else {
+ machine_irq = pirq;
+ s->machine_irq = pirq;
+ xen_pt_mapped_machine_irq[machine_irq]++;
+ }
+
+ /* bind machine_irq to device */
+ if (machine_irq != 0) {
+ uint8_t e_intx = xen_pt_pci_intx(s);
+
+ rc = xc_domain_bind_pt_pci_irq(xen_xc, xen_domid, machine_irq,
+ pci_bus_num(d->bus),
+ PCI_SLOT(d->devfn),
+ e_intx);
+ if (rc < 0) {
+ XEN_PT_ERR(d, "Binding of interrupt %i failed! (err: %d)\n",
+ e_intx, errno);
+
+ /* Disable PCI intx assertion (turn on bit10 of devctl) */
+ cmd |= PCI_COMMAND_INTX_DISABLE;
+ xen_pt_mapped_machine_irq[machine_irq]--;
+
+ if (xen_pt_mapped_machine_irq[machine_irq] == 0) {
+ if (xc_physdev_unmap_pirq(xen_xc, xen_domid, machine_irq)) {
+ XEN_PT_ERR(d, "Unmapping of machine interrupt %i failed!"
+ " (err: %d)\n", machine_irq, errno);
+ }
+ }
+ s->machine_irq = 0;
+ }
+ }
+
+out:
+ if (cmd) {
+ xen_host_pci_set_word(&s->real_device, PCI_COMMAND,
+ pci_get_word(d->config + PCI_COMMAND) | cmd);
+ }
+
+ memory_listener_register(&s->memory_listener, &s->dev.bus_master_as);
+ memory_listener_register(&s->io_listener, &address_space_io);
+ XEN_PT_LOG(d,
+ "Real physical device %02x:%02x.%d registered successfully!\n",
+ s->hostaddr.bus, s->hostaddr.slot, s->hostaddr.function);
+
+ return 0;
+}
+
+static void xen_pt_unregister_device(PCIDevice *d)
+{
+ XenPCIPassthroughState *s = XEN_PT_DEVICE(d);
+ uint8_t machine_irq = s->machine_irq;
+ uint8_t intx = xen_pt_pci_intx(s);
+ int rc;
+
+ if (machine_irq) {
+ rc = xc_domain_unbind_pt_irq(xen_xc, xen_domid, machine_irq,
+ PT_IRQ_TYPE_PCI,
+ pci_bus_num(d->bus),
+ PCI_SLOT(s->dev.devfn),
+ intx,
+ 0 /* isa_irq */);
+ if (rc < 0) {
+ XEN_PT_ERR(d, "unbinding of interrupt INT%c failed."
+ " (machine irq: %i, err: %d)"
+ " But bravely continuing on..\n",
+ 'a' + intx, machine_irq, errno);
+ }
+ }
+
+ if (s->msi) {
+ xen_pt_msi_disable(s);
+ }
+ if (s->msix) {
+ xen_pt_msix_disable(s);
+ }
+
+ if (machine_irq) {
+ xen_pt_mapped_machine_irq[machine_irq]--;
+
+ if (xen_pt_mapped_machine_irq[machine_irq] == 0) {
+ rc = xc_physdev_unmap_pirq(xen_xc, xen_domid, machine_irq);
+
+ if (rc < 0) {
+ XEN_PT_ERR(d, "unmapping of interrupt %i failed. (err: %d)"
+ " But bravely continuing on..\n",
+ machine_irq, errno);
+ }
+ }
+ }
+
+ /* delete all emulated config registers */
+ xen_pt_config_delete(s);
+
+ memory_listener_unregister(&s->memory_listener);
+ memory_listener_unregister(&s->io_listener);
+
+ xen_host_pci_device_put(&s->real_device);
+}
+
+static Property xen_pci_passthrough_properties[] = {
+ DEFINE_PROP_PCI_HOST_DEVADDR("hostaddr", XenPCIPassthroughState, hostaddr),
+ DEFINE_PROP_BOOL("permissive", XenPCIPassthroughState, permissive, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xen_pci_passthrough_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = xen_pt_initfn;
+ k->exit = xen_pt_unregister_device;
+ k->config_read = xen_pt_pci_read_config;
+ k->config_write = xen_pt_pci_write_config;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->desc = "Assign an host PCI device with Xen";
+ dc->props = xen_pci_passthrough_properties;
+};
+
+static const TypeInfo xen_pci_passthrough_info = {
+ .name = TYPE_XEN_PT_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(XenPCIPassthroughState),
+ .class_init = xen_pci_passthrough_class_init,
+};
+
+static void xen_pci_passthrough_register_types(void)
+{
+ type_register_static(&xen_pci_passthrough_info);
+}
+
+type_init(xen_pci_passthrough_register_types)
diff --git a/hw/xen/xen_pt.h b/hw/xen/xen_pt.h
new file mode 100644
index 00000000..393f36cc
--- /dev/null
+++ b/hw/xen/xen_pt.h
@@ -0,0 +1,309 @@
+#ifndef XEN_PT_H
+#define XEN_PT_H
+
+#include "qemu-common.h"
+#include "hw/xen/xen_common.h"
+#include "hw/pci/pci.h"
+#include "xen-host-pci-device.h"
+
+void xen_pt_log(const PCIDevice *d, const char *f, ...) GCC_FMT_ATTR(2, 3);
+
+#define XEN_PT_ERR(d, _f, _a...) xen_pt_log(d, "%s: Error: "_f, __func__, ##_a)
+
+#ifdef XEN_PT_LOGGING_ENABLED
+# define XEN_PT_LOG(d, _f, _a...) xen_pt_log(d, "%s: " _f, __func__, ##_a)
+# define XEN_PT_WARN(d, _f, _a...) \
+ xen_pt_log(d, "%s: Warning: "_f, __func__, ##_a)
+#else
+# define XEN_PT_LOG(d, _f, _a...)
+# define XEN_PT_WARN(d, _f, _a...)
+#endif
+
+#ifdef XEN_PT_DEBUG_PCI_CONFIG_ACCESS
+# define XEN_PT_LOG_CONFIG(d, addr, val, len) \
+ xen_pt_log(d, "%s: address=0x%04x val=0x%08x len=%d\n", \
+ __func__, addr, val, len)
+#else
+# define XEN_PT_LOG_CONFIG(d, addr, val, len)
+#endif
+
+
+/* Helper */
+#define XEN_PFN(x) ((x) >> XC_PAGE_SHIFT)
+
+typedef const struct XenPTRegInfo XenPTRegInfo;
+typedef struct XenPTReg XenPTReg;
+
+typedef struct XenPCIPassthroughState XenPCIPassthroughState;
+
+#define TYPE_XEN_PT_DEVICE "xen-pci-passthrough"
+#define XEN_PT_DEVICE(obj) \
+ OBJECT_CHECK(XenPCIPassthroughState, (obj), TYPE_XEN_PT_DEVICE)
+
+/* function type for config reg */
+typedef int (*xen_pt_conf_reg_init)
+ (XenPCIPassthroughState *, XenPTRegInfo *, uint32_t real_offset,
+ uint32_t *data);
+typedef int (*xen_pt_conf_dword_write)
+ (XenPCIPassthroughState *, XenPTReg *cfg_entry,
+ uint32_t *val, uint32_t dev_value, uint32_t valid_mask);
+typedef int (*xen_pt_conf_word_write)
+ (XenPCIPassthroughState *, XenPTReg *cfg_entry,
+ uint16_t *val, uint16_t dev_value, uint16_t valid_mask);
+typedef int (*xen_pt_conf_byte_write)
+ (XenPCIPassthroughState *, XenPTReg *cfg_entry,
+ uint8_t *val, uint8_t dev_value, uint8_t valid_mask);
+typedef int (*xen_pt_conf_dword_read)
+ (XenPCIPassthroughState *, XenPTReg *cfg_entry,
+ uint32_t *val, uint32_t valid_mask);
+typedef int (*xen_pt_conf_word_read)
+ (XenPCIPassthroughState *, XenPTReg *cfg_entry,
+ uint16_t *val, uint16_t valid_mask);
+typedef int (*xen_pt_conf_byte_read)
+ (XenPCIPassthroughState *, XenPTReg *cfg_entry,
+ uint8_t *val, uint8_t valid_mask);
+
+#define XEN_PT_BAR_ALLF 0xFFFFFFFF
+#define XEN_PT_BAR_UNMAPPED (-1)
+
+#define PCI_CAP_MAX 48
+
+
+typedef enum {
+ XEN_PT_GRP_TYPE_HARDWIRED = 0, /* 0 Hardwired reg group */
+ XEN_PT_GRP_TYPE_EMU, /* emul reg group */
+} XenPTRegisterGroupType;
+
+typedef enum {
+ XEN_PT_BAR_FLAG_MEM = 0, /* Memory type BAR */
+ XEN_PT_BAR_FLAG_IO, /* I/O type BAR */
+ XEN_PT_BAR_FLAG_UPPER, /* upper 64bit BAR */
+ XEN_PT_BAR_FLAG_UNUSED, /* unused BAR */
+} XenPTBarFlag;
+
+
+typedef struct XenPTRegion {
+ /* BAR flag */
+ XenPTBarFlag bar_flag;
+ /* Translation of the emulated address */
+ union {
+ uint64_t maddr;
+ uint64_t pio_base;
+ uint64_t u;
+ } access;
+} XenPTRegion;
+
+/* XenPTRegInfo declaration
+ * - only for emulated register (either a part or whole bit).
+ * - for passthrough register that need special behavior (like interacting with
+ * other component), set emu_mask to all 0 and specify r/w func properly.
+ * - do NOT use ALL F for init_val, otherwise the tbl will not be registered.
+ */
+
+/* emulated register information */
+struct XenPTRegInfo {
+ uint32_t offset;
+ uint32_t size;
+ uint32_t init_val;
+ /* reg reserved field mask (ON:reserved, OFF:defined) */
+ uint32_t res_mask;
+ /* reg read only field mask (ON:RO/ROS, OFF:other) */
+ uint32_t ro_mask;
+ /* reg emulate field mask (ON:emu, OFF:passthrough) */
+ uint32_t emu_mask;
+ xen_pt_conf_reg_init init;
+ /* read/write function pointer
+ * for double_word/word/byte size */
+ union {
+ struct {
+ xen_pt_conf_dword_write write;
+ xen_pt_conf_dword_read read;
+ } dw;
+ struct {
+ xen_pt_conf_word_write write;
+ xen_pt_conf_word_read read;
+ } w;
+ struct {
+ xen_pt_conf_byte_write write;
+ xen_pt_conf_byte_read read;
+ } b;
+ } u;
+};
+
+/* emulated register management */
+struct XenPTReg {
+ QLIST_ENTRY(XenPTReg) entries;
+ XenPTRegInfo *reg;
+ uint32_t data; /* emulated value */
+};
+
+typedef const struct XenPTRegGroupInfo XenPTRegGroupInfo;
+
+/* emul reg group size initialize method */
+typedef int (*xen_pt_reg_size_init_fn)
+ (XenPCIPassthroughState *, XenPTRegGroupInfo *,
+ uint32_t base_offset, uint8_t *size);
+
+/* emulated register group information */
+struct XenPTRegGroupInfo {
+ uint8_t grp_id;
+ XenPTRegisterGroupType grp_type;
+ uint8_t grp_size;
+ xen_pt_reg_size_init_fn size_init;
+ XenPTRegInfo *emu_regs;
+};
+
+/* emul register group management table */
+typedef struct XenPTRegGroup {
+ QLIST_ENTRY(XenPTRegGroup) entries;
+ XenPTRegGroupInfo *reg_grp;
+ uint32_t base_offset;
+ uint8_t size;
+ QLIST_HEAD(, XenPTReg) reg_tbl_list;
+} XenPTRegGroup;
+
+
+#define XEN_PT_UNASSIGNED_PIRQ (-1)
+typedef struct XenPTMSI {
+ uint16_t flags;
+ uint32_t addr_lo; /* guest message address */
+ uint32_t addr_hi; /* guest message upper address */
+ uint16_t data; /* guest message data */
+ uint32_t ctrl_offset; /* saved control offset */
+ int pirq; /* guest pirq corresponding */
+ bool initialized; /* when guest MSI is initialized */
+ bool mapped; /* when pirq is mapped */
+} XenPTMSI;
+
+typedef struct XenPTMSIXEntry {
+ int pirq;
+ uint64_t addr;
+ uint32_t data;
+ uint32_t vector_ctrl;
+ bool updated; /* indicate whether MSI ADDR or DATA is updated */
+ bool warned; /* avoid issuing (bogus) warning more than once */
+} XenPTMSIXEntry;
+typedef struct XenPTMSIX {
+ uint32_t ctrl_offset;
+ bool enabled;
+ int total_entries;
+ int bar_index;
+ uint64_t table_base;
+ uint32_t table_offset_adjust; /* page align mmap */
+ uint64_t mmio_base_addr;
+ MemoryRegion mmio;
+ void *phys_iomem_base;
+ XenPTMSIXEntry msix_entry[0];
+} XenPTMSIX;
+
+struct XenPCIPassthroughState {
+ PCIDevice dev;
+
+ PCIHostDeviceAddress hostaddr;
+ bool is_virtfn;
+ bool permissive;
+ bool permissive_warned;
+ XenHostPCIDevice real_device;
+ XenPTRegion bases[PCI_NUM_REGIONS]; /* Access regions */
+ QLIST_HEAD(, XenPTRegGroup) reg_grps;
+
+ uint32_t machine_irq;
+
+ XenPTMSI *msi;
+ XenPTMSIX *msix;
+
+ MemoryRegion bar[PCI_NUM_REGIONS - 1];
+ MemoryRegion rom;
+
+ MemoryListener memory_listener;
+ MemoryListener io_listener;
+};
+
+int xen_pt_config_init(XenPCIPassthroughState *s);
+void xen_pt_config_delete(XenPCIPassthroughState *s);
+XenPTRegGroup *xen_pt_find_reg_grp(XenPCIPassthroughState *s, uint32_t address);
+XenPTReg *xen_pt_find_reg(XenPTRegGroup *reg_grp, uint32_t address);
+int xen_pt_bar_offset_to_index(uint32_t offset);
+
+static inline pcibus_t xen_pt_get_emul_size(XenPTBarFlag flag, pcibus_t r_size)
+{
+ /* align resource size (memory type only) */
+ if (flag == XEN_PT_BAR_FLAG_MEM) {
+ return (r_size + XC_PAGE_SIZE - 1) & XC_PAGE_MASK;
+ } else {
+ return r_size;
+ }
+}
+
+/* INTx */
+/* The PCI Local Bus Specification, Rev. 3.0,
+ * Section 6.2.4 Miscellaneous Registers, pp 223
+ * outlines 5 valid values for the interrupt pin (intx).
+ * 0: For devices (or device functions) that don't use an interrupt in
+ * 1: INTA#
+ * 2: INTB#
+ * 3: INTC#
+ * 4: INTD#
+ *
+ * Xen uses the following 4 values for intx
+ * 0: INTA#
+ * 1: INTB#
+ * 2: INTC#
+ * 3: INTD#
+ *
+ * Observing that these list of values are not the same, xen_pt_pci_read_intx()
+ * uses the following mapping from hw to xen values.
+ * This seems to reflect the current usage within Xen.
+ *
+ * PCI hardware | Xen | Notes
+ * ----------------+-----+----------------------------------------------------
+ * 0 | 0 | No interrupt
+ * 1 | 0 | INTA#
+ * 2 | 1 | INTB#
+ * 3 | 2 | INTC#
+ * 4 | 3 | INTD#
+ * any other value | 0 | This should never happen, log error message
+ */
+
+static inline uint8_t xen_pt_pci_read_intx(XenPCIPassthroughState *s)
+{
+ uint8_t v = 0;
+ xen_host_pci_get_byte(&s->real_device, PCI_INTERRUPT_PIN, &v);
+ return v;
+}
+
+static inline uint8_t xen_pt_pci_intx(XenPCIPassthroughState *s)
+{
+ uint8_t r_val = xen_pt_pci_read_intx(s);
+
+ XEN_PT_LOG(&s->dev, "intx=%i\n", r_val);
+ if (r_val < 1 || r_val > 4) {
+ XEN_PT_LOG(&s->dev, "Interrupt pin read from hardware is out of range:"
+ " value=%i, acceptable range is 1 - 4\n", r_val);
+ r_val = 0;
+ } else {
+ r_val -= 1;
+ }
+
+ return r_val;
+}
+
+/* MSI/MSI-X */
+int xen_pt_msi_set_enable(XenPCIPassthroughState *s, bool en);
+int xen_pt_msi_setup(XenPCIPassthroughState *s);
+int xen_pt_msi_update(XenPCIPassthroughState *d);
+void xen_pt_msi_disable(XenPCIPassthroughState *s);
+
+int xen_pt_msix_init(XenPCIPassthroughState *s, uint32_t base);
+void xen_pt_msix_delete(XenPCIPassthroughState *s);
+int xen_pt_msix_update(XenPCIPassthroughState *s);
+int xen_pt_msix_update_remap(XenPCIPassthroughState *s, int bar_index);
+void xen_pt_msix_disable(XenPCIPassthroughState *s);
+
+static inline bool xen_pt_has_msix_mapping(XenPCIPassthroughState *s, int bar)
+{
+ return s->msix && s->msix->bar_index == bar;
+}
+
+
+#endif /* !XEN_PT_H */
diff --git a/hw/xen/xen_pt_config_init.c b/hw/xen/xen_pt_config_init.c
new file mode 100644
index 00000000..dd37be38
--- /dev/null
+++ b/hw/xen/xen_pt_config_init.c
@@ -0,0 +1,1936 @@
+/*
+ * Copyright (c) 2007, Neocleus Corporation.
+ * Copyright (c) 2007, Intel Corporation.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Alex Novik <alex@neocleus.com>
+ * Allen Kay <allen.m.kay@intel.com>
+ * Guy Zana <guy@neocleus.com>
+ *
+ * This file implements direct PCI assignment to a HVM guest
+ */
+
+#include "qemu/timer.h"
+#include "hw/xen/xen_backend.h"
+#include "xen_pt.h"
+
+#define XEN_PT_MERGE_VALUE(value, data, val_mask) \
+ (((value) & (val_mask)) | ((data) & ~(val_mask)))
+
+#define XEN_PT_INVALID_REG 0xFFFFFFFF /* invalid register value */
+
+/* prototype */
+
+static int xen_pt_ptr_reg_init(XenPCIPassthroughState *s, XenPTRegInfo *reg,
+ uint32_t real_offset, uint32_t *data);
+
+
+/* helper */
+
+/* A return value of 1 means the capability should NOT be exposed to guest. */
+static int xen_pt_hide_dev_cap(const XenHostPCIDevice *d, uint8_t grp_id)
+{
+ switch (grp_id) {
+ case PCI_CAP_ID_EXP:
+ /* The PCI Express Capability Structure of the VF of Intel 82599 10GbE
+ * Controller looks trivial, e.g., the PCI Express Capabilities
+ * Register is 0. We should not try to expose it to guest.
+ *
+ * The datasheet is available at
+ * http://download.intel.com/design/network/datashts/82599_datasheet.pdf
+ *
+ * See 'Table 9.7. VF PCIe Configuration Space' of the datasheet, the
+ * PCI Express Capability Structure of the VF of Intel 82599 10GbE
+ * Controller looks trivial, e.g., the PCI Express Capabilities
+ * Register is 0, so the Capability Version is 0 and
+ * xen_pt_pcie_size_init() would fail.
+ */
+ if (d->vendor_id == PCI_VENDOR_ID_INTEL &&
+ d->device_id == PCI_DEVICE_ID_INTEL_82599_SFP_VF) {
+ return 1;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* find emulate register group entry */
+XenPTRegGroup *xen_pt_find_reg_grp(XenPCIPassthroughState *s, uint32_t address)
+{
+ XenPTRegGroup *entry = NULL;
+
+ /* find register group entry */
+ QLIST_FOREACH(entry, &s->reg_grps, entries) {
+ /* check address */
+ if ((entry->base_offset <= address)
+ && ((entry->base_offset + entry->size) > address)) {
+ return entry;
+ }
+ }
+
+ /* group entry not found */
+ return NULL;
+}
+
+/* find emulate register entry */
+XenPTReg *xen_pt_find_reg(XenPTRegGroup *reg_grp, uint32_t address)
+{
+ XenPTReg *reg_entry = NULL;
+ XenPTRegInfo *reg = NULL;
+ uint32_t real_offset = 0;
+
+ /* find register entry */
+ QLIST_FOREACH(reg_entry, &reg_grp->reg_tbl_list, entries) {
+ reg = reg_entry->reg;
+ real_offset = reg_grp->base_offset + reg->offset;
+ /* check address */
+ if ((real_offset <= address)
+ && ((real_offset + reg->size) > address)) {
+ return reg_entry;
+ }
+ }
+
+ return NULL;
+}
+
+static uint32_t get_throughable_mask(const XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t valid_mask)
+{
+ uint32_t throughable_mask = ~(reg->emu_mask | reg->ro_mask);
+
+ if (!s->permissive) {
+ throughable_mask &= ~reg->res_mask;
+ }
+
+ return throughable_mask & valid_mask;
+}
+
+/****************
+ * general register functions
+ */
+
+/* register initialization function */
+
+static int xen_pt_common_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ *data = reg->init_val;
+ return 0;
+}
+
+/* Read register functions */
+
+static int xen_pt_byte_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint8_t *value, uint8_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint8_t valid_emu_mask = 0;
+
+ /* emulate byte register */
+ valid_emu_mask = reg->emu_mask & valid_mask;
+ *value = XEN_PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask);
+
+ return 0;
+}
+static int xen_pt_word_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint16_t *value, uint16_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint16_t valid_emu_mask = 0;
+
+ /* emulate word register */
+ valid_emu_mask = reg->emu_mask & valid_mask;
+ *value = XEN_PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask);
+
+ return 0;
+}
+static int xen_pt_long_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint32_t *value, uint32_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint32_t valid_emu_mask = 0;
+
+ /* emulate long register */
+ valid_emu_mask = reg->emu_mask & valid_mask;
+ *value = XEN_PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask);
+
+ return 0;
+}
+
+/* Write register functions */
+
+static int xen_pt_byte_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint8_t *val, uint8_t dev_value,
+ uint8_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint8_t writable_mask = 0;
+ uint8_t throughable_mask = get_throughable_mask(s, reg, valid_mask);
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, throughable_mask);
+
+ return 0;
+}
+static int xen_pt_word_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint16_t *val, uint16_t dev_value,
+ uint16_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint16_t writable_mask = 0;
+ uint16_t throughable_mask = get_throughable_mask(s, reg, valid_mask);
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, throughable_mask);
+
+ return 0;
+}
+static int xen_pt_long_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint32_t *val, uint32_t dev_value,
+ uint32_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint32_t writable_mask = 0;
+ uint32_t throughable_mask = get_throughable_mask(s, reg, valid_mask);
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, throughable_mask);
+
+ return 0;
+}
+
+
+/* XenPTRegInfo declaration
+ * - only for emulated register (either a part or whole bit).
+ * - for passthrough register that need special behavior (like interacting with
+ * other component), set emu_mask to all 0 and specify r/w func properly.
+ * - do NOT use ALL F for init_val, otherwise the tbl will not be registered.
+ */
+
+/********************
+ * Header Type0
+ */
+
+static int xen_pt_vendor_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ *data = s->real_device.vendor_id;
+ return 0;
+}
+static int xen_pt_device_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ *data = s->real_device.device_id;
+ return 0;
+}
+static int xen_pt_status_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ XenPTRegGroup *reg_grp_entry = NULL;
+ XenPTReg *reg_entry = NULL;
+ uint32_t reg_field = 0;
+
+ /* find Header register group */
+ reg_grp_entry = xen_pt_find_reg_grp(s, PCI_CAPABILITY_LIST);
+ if (reg_grp_entry) {
+ /* find Capabilities Pointer register */
+ reg_entry = xen_pt_find_reg(reg_grp_entry, PCI_CAPABILITY_LIST);
+ if (reg_entry) {
+ /* check Capabilities Pointer register */
+ if (reg_entry->data) {
+ reg_field |= PCI_STATUS_CAP_LIST;
+ } else {
+ reg_field &= ~PCI_STATUS_CAP_LIST;
+ }
+ } else {
+ xen_shutdown_fatal_error("Internal error: Couldn't find XenPTReg*"
+ " for Capabilities Pointer register."
+ " (%s)\n", __func__);
+ return -1;
+ }
+ } else {
+ xen_shutdown_fatal_error("Internal error: Couldn't find XenPTRegGroup"
+ " for Header. (%s)\n", __func__);
+ return -1;
+ }
+
+ *data = reg_field;
+ return 0;
+}
+static int xen_pt_header_type_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ /* read PCI_HEADER_TYPE */
+ *data = reg->init_val | 0x80;
+ return 0;
+}
+
+/* initialize Interrupt Pin register */
+static int xen_pt_irqpin_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ *data = xen_pt_pci_read_intx(s);
+ return 0;
+}
+
+/* Command register */
+static int xen_pt_cmd_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint16_t *val, uint16_t dev_value,
+ uint16_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint16_t writable_mask = 0;
+ uint16_t throughable_mask = get_throughable_mask(s, reg, valid_mask);
+
+ /* modify emulate register */
+ writable_mask = ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+
+ /* create value for writing to I/O device register */
+ if (*val & PCI_COMMAND_INTX_DISABLE) {
+ throughable_mask |= PCI_COMMAND_INTX_DISABLE;
+ } else {
+ if (s->machine_irq) {
+ throughable_mask |= PCI_COMMAND_INTX_DISABLE;
+ }
+ }
+
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, throughable_mask);
+
+ return 0;
+}
+
+/* BAR */
+#define XEN_PT_BAR_MEM_RO_MASK 0x0000000F /* BAR ReadOnly mask(Memory) */
+#define XEN_PT_BAR_MEM_EMU_MASK 0xFFFFFFF0 /* BAR emul mask(Memory) */
+#define XEN_PT_BAR_IO_RO_MASK 0x00000003 /* BAR ReadOnly mask(I/O) */
+#define XEN_PT_BAR_IO_EMU_MASK 0xFFFFFFFC /* BAR emul mask(I/O) */
+
+static bool is_64bit_bar(PCIIORegion *r)
+{
+ return !!(r->type & PCI_BASE_ADDRESS_MEM_TYPE_64);
+}
+
+static uint64_t xen_pt_get_bar_size(PCIIORegion *r)
+{
+ if (is_64bit_bar(r)) {
+ uint64_t size64;
+ size64 = (r + 1)->size;
+ size64 <<= 32;
+ size64 += r->size;
+ return size64;
+ }
+ return r->size;
+}
+
+static XenPTBarFlag xen_pt_bar_reg_parse(XenPCIPassthroughState *s,
+ int index)
+{
+ PCIDevice *d = &s->dev;
+ XenPTRegion *region = NULL;
+ PCIIORegion *r;
+
+ /* check 64bit BAR */
+ if ((0 < index) && (index < PCI_ROM_SLOT)) {
+ int type = s->real_device.io_regions[index - 1].type;
+
+ if ((type & XEN_HOST_PCI_REGION_TYPE_MEM)
+ && (type & XEN_HOST_PCI_REGION_TYPE_MEM_64)) {
+ region = &s->bases[index - 1];
+ if (region->bar_flag != XEN_PT_BAR_FLAG_UPPER) {
+ return XEN_PT_BAR_FLAG_UPPER;
+ }
+ }
+ }
+
+ /* check unused BAR */
+ r = &d->io_regions[index];
+ if (!xen_pt_get_bar_size(r)) {
+ return XEN_PT_BAR_FLAG_UNUSED;
+ }
+
+ /* for ExpROM BAR */
+ if (index == PCI_ROM_SLOT) {
+ return XEN_PT_BAR_FLAG_MEM;
+ }
+
+ /* check BAR I/O indicator */
+ if (s->real_device.io_regions[index].type & XEN_HOST_PCI_REGION_TYPE_IO) {
+ return XEN_PT_BAR_FLAG_IO;
+ } else {
+ return XEN_PT_BAR_FLAG_MEM;
+ }
+}
+
+static inline uint32_t base_address_with_flags(XenHostPCIIORegion *hr)
+{
+ if (hr->type & XEN_HOST_PCI_REGION_TYPE_IO) {
+ return hr->base_addr | (hr->bus_flags & ~PCI_BASE_ADDRESS_IO_MASK);
+ } else {
+ return hr->base_addr | (hr->bus_flags & ~PCI_BASE_ADDRESS_MEM_MASK);
+ }
+}
+
+static int xen_pt_bar_reg_init(XenPCIPassthroughState *s, XenPTRegInfo *reg,
+ uint32_t real_offset, uint32_t *data)
+{
+ uint32_t reg_field = 0;
+ int index;
+
+ index = xen_pt_bar_offset_to_index(reg->offset);
+ if (index < 0 || index >= PCI_NUM_REGIONS) {
+ XEN_PT_ERR(&s->dev, "Internal error: Invalid BAR index [%d].\n", index);
+ return -1;
+ }
+
+ /* set BAR flag */
+ s->bases[index].bar_flag = xen_pt_bar_reg_parse(s, index);
+ if (s->bases[index].bar_flag == XEN_PT_BAR_FLAG_UNUSED) {
+ reg_field = XEN_PT_INVALID_REG;
+ }
+
+ *data = reg_field;
+ return 0;
+}
+static int xen_pt_bar_reg_read(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint32_t *value, uint32_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint32_t valid_emu_mask = 0;
+ uint32_t bar_emu_mask = 0;
+ int index;
+
+ /* get BAR index */
+ index = xen_pt_bar_offset_to_index(reg->offset);
+ if (index < 0 || index >= PCI_NUM_REGIONS - 1) {
+ XEN_PT_ERR(&s->dev, "Internal error: Invalid BAR index [%d].\n", index);
+ return -1;
+ }
+
+ /* use fixed-up value from kernel sysfs */
+ *value = base_address_with_flags(&s->real_device.io_regions[index]);
+
+ /* set emulate mask depend on BAR flag */
+ switch (s->bases[index].bar_flag) {
+ case XEN_PT_BAR_FLAG_MEM:
+ bar_emu_mask = XEN_PT_BAR_MEM_EMU_MASK;
+ break;
+ case XEN_PT_BAR_FLAG_IO:
+ bar_emu_mask = XEN_PT_BAR_IO_EMU_MASK;
+ break;
+ case XEN_PT_BAR_FLAG_UPPER:
+ bar_emu_mask = XEN_PT_BAR_ALLF;
+ break;
+ default:
+ break;
+ }
+
+ /* emulate BAR */
+ valid_emu_mask = bar_emu_mask & valid_mask;
+ *value = XEN_PT_MERGE_VALUE(*value, cfg_entry->data, ~valid_emu_mask);
+
+ return 0;
+}
+static int xen_pt_bar_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry,
+ uint32_t *val, uint32_t dev_value,
+ uint32_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ XenPTRegion *base = NULL;
+ PCIDevice *d = &s->dev;
+ const PCIIORegion *r;
+ uint32_t writable_mask = 0;
+ uint32_t bar_emu_mask = 0;
+ uint32_t bar_ro_mask = 0;
+ uint32_t r_size = 0;
+ int index = 0;
+
+ index = xen_pt_bar_offset_to_index(reg->offset);
+ if (index < 0 || index >= PCI_NUM_REGIONS) {
+ XEN_PT_ERR(d, "Internal error: Invalid BAR index [%d].\n", index);
+ return -1;
+ }
+
+ r = &d->io_regions[index];
+ base = &s->bases[index];
+ r_size = xen_pt_get_emul_size(base->bar_flag, r->size);
+
+ /* set emulate mask and read-only mask values depend on the BAR flag */
+ switch (s->bases[index].bar_flag) {
+ case XEN_PT_BAR_FLAG_MEM:
+ bar_emu_mask = XEN_PT_BAR_MEM_EMU_MASK;
+ if (!r_size) {
+ /* low 32 bits mask for 64 bit bars */
+ bar_ro_mask = XEN_PT_BAR_ALLF;
+ } else {
+ bar_ro_mask = XEN_PT_BAR_MEM_RO_MASK | (r_size - 1);
+ }
+ break;
+ case XEN_PT_BAR_FLAG_IO:
+ bar_emu_mask = XEN_PT_BAR_IO_EMU_MASK;
+ bar_ro_mask = XEN_PT_BAR_IO_RO_MASK | (r_size - 1);
+ break;
+ case XEN_PT_BAR_FLAG_UPPER:
+ bar_emu_mask = XEN_PT_BAR_ALLF;
+ bar_ro_mask = r_size ? r_size - 1 : 0;
+ break;
+ default:
+ break;
+ }
+
+ /* modify emulate register */
+ writable_mask = bar_emu_mask & ~bar_ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+
+ /* check whether we need to update the virtual region address or not */
+ switch (s->bases[index].bar_flag) {
+ case XEN_PT_BAR_FLAG_UPPER:
+ case XEN_PT_BAR_FLAG_MEM:
+ /* nothing to do */
+ break;
+ case XEN_PT_BAR_FLAG_IO:
+ /* nothing to do */
+ break;
+ default:
+ break;
+ }
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, 0);
+
+ return 0;
+}
+
+/* write Exp ROM BAR */
+static int xen_pt_exp_rom_bar_reg_write(XenPCIPassthroughState *s,
+ XenPTReg *cfg_entry, uint32_t *val,
+ uint32_t dev_value, uint32_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ XenPTRegion *base = NULL;
+ PCIDevice *d = (PCIDevice *)&s->dev;
+ uint32_t writable_mask = 0;
+ uint32_t throughable_mask = get_throughable_mask(s, reg, valid_mask);
+ pcibus_t r_size = 0;
+ uint32_t bar_ro_mask = 0;
+
+ r_size = d->io_regions[PCI_ROM_SLOT].size;
+ base = &s->bases[PCI_ROM_SLOT];
+ /* align memory type resource size */
+ r_size = xen_pt_get_emul_size(base->bar_flag, r_size);
+
+ /* set emulate mask and read-only mask */
+ bar_ro_mask = (reg->ro_mask | (r_size - 1)) & ~PCI_ROM_ADDRESS_ENABLE;
+
+ /* modify emulate register */
+ writable_mask = ~bar_ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, throughable_mask);
+
+ return 0;
+}
+
+/* Header Type0 reg static information table */
+static XenPTRegInfo xen_pt_emu_reg_header0[] = {
+ /* Vendor ID reg */
+ {
+ .offset = PCI_VENDOR_ID,
+ .size = 2,
+ .init_val = 0x0000,
+ .ro_mask = 0xFFFF,
+ .emu_mask = 0xFFFF,
+ .init = xen_pt_vendor_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* Device ID reg */
+ {
+ .offset = PCI_DEVICE_ID,
+ .size = 2,
+ .init_val = 0x0000,
+ .ro_mask = 0xFFFF,
+ .emu_mask = 0xFFFF,
+ .init = xen_pt_device_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* Command reg */
+ {
+ .offset = PCI_COMMAND,
+ .size = 2,
+ .init_val = 0x0000,
+ .res_mask = 0xF880,
+ .emu_mask = 0x0743,
+ .init = xen_pt_common_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_cmd_reg_write,
+ },
+ /* Capabilities Pointer reg */
+ {
+ .offset = PCI_CAPABILITY_LIST,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0xFF,
+ .init = xen_pt_ptr_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Status reg */
+ /* use emulated Cap Ptr value to initialize,
+ * so need to be declared after Cap Ptr reg
+ */
+ {
+ .offset = PCI_STATUS,
+ .size = 2,
+ .init_val = 0x0000,
+ .res_mask = 0x0007,
+ .ro_mask = 0x06F8,
+ .emu_mask = 0x0010,
+ .init = xen_pt_status_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* Cache Line Size reg */
+ {
+ .offset = PCI_CACHE_LINE_SIZE,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0x00,
+ .emu_mask = 0xFF,
+ .init = xen_pt_common_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Latency Timer reg */
+ {
+ .offset = PCI_LATENCY_TIMER,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0x00,
+ .emu_mask = 0xFF,
+ .init = xen_pt_common_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Header Type reg */
+ {
+ .offset = PCI_HEADER_TYPE,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0x00,
+ .init = xen_pt_header_type_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Interrupt Line reg */
+ {
+ .offset = PCI_INTERRUPT_LINE,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0x00,
+ .emu_mask = 0xFF,
+ .init = xen_pt_common_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Interrupt Pin reg */
+ {
+ .offset = PCI_INTERRUPT_PIN,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0xFF,
+ .init = xen_pt_irqpin_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* BAR 0 reg */
+ /* mask of BAR need to be decided later, depends on IO/MEM type */
+ {
+ .offset = PCI_BASE_ADDRESS_0,
+ .size = 4,
+ .init_val = 0x00000000,
+ .init = xen_pt_bar_reg_init,
+ .u.dw.read = xen_pt_bar_reg_read,
+ .u.dw.write = xen_pt_bar_reg_write,
+ },
+ /* BAR 1 reg */
+ {
+ .offset = PCI_BASE_ADDRESS_1,
+ .size = 4,
+ .init_val = 0x00000000,
+ .init = xen_pt_bar_reg_init,
+ .u.dw.read = xen_pt_bar_reg_read,
+ .u.dw.write = xen_pt_bar_reg_write,
+ },
+ /* BAR 2 reg */
+ {
+ .offset = PCI_BASE_ADDRESS_2,
+ .size = 4,
+ .init_val = 0x00000000,
+ .init = xen_pt_bar_reg_init,
+ .u.dw.read = xen_pt_bar_reg_read,
+ .u.dw.write = xen_pt_bar_reg_write,
+ },
+ /* BAR 3 reg */
+ {
+ .offset = PCI_BASE_ADDRESS_3,
+ .size = 4,
+ .init_val = 0x00000000,
+ .init = xen_pt_bar_reg_init,
+ .u.dw.read = xen_pt_bar_reg_read,
+ .u.dw.write = xen_pt_bar_reg_write,
+ },
+ /* BAR 4 reg */
+ {
+ .offset = PCI_BASE_ADDRESS_4,
+ .size = 4,
+ .init_val = 0x00000000,
+ .init = xen_pt_bar_reg_init,
+ .u.dw.read = xen_pt_bar_reg_read,
+ .u.dw.write = xen_pt_bar_reg_write,
+ },
+ /* BAR 5 reg */
+ {
+ .offset = PCI_BASE_ADDRESS_5,
+ .size = 4,
+ .init_val = 0x00000000,
+ .init = xen_pt_bar_reg_init,
+ .u.dw.read = xen_pt_bar_reg_read,
+ .u.dw.write = xen_pt_bar_reg_write,
+ },
+ /* Expansion ROM BAR reg */
+ {
+ .offset = PCI_ROM_ADDRESS,
+ .size = 4,
+ .init_val = 0x00000000,
+ .ro_mask = ~PCI_ROM_ADDRESS_MASK & ~PCI_ROM_ADDRESS_ENABLE,
+ .emu_mask = (uint32_t)PCI_ROM_ADDRESS_MASK,
+ .init = xen_pt_bar_reg_init,
+ .u.dw.read = xen_pt_long_reg_read,
+ .u.dw.write = xen_pt_exp_rom_bar_reg_write,
+ },
+ {
+ .size = 0,
+ },
+};
+
+
+/*********************************
+ * Vital Product Data Capability
+ */
+
+/* Vital Product Data Capability Structure reg static information table */
+static XenPTRegInfo xen_pt_emu_reg_vpd[] = {
+ {
+ .offset = PCI_CAP_LIST_NEXT,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0xFF,
+ .init = xen_pt_ptr_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ {
+ .offset = PCI_VPD_ADDR,
+ .size = 2,
+ .ro_mask = 0x0003,
+ .emu_mask = 0x0003,
+ .init = xen_pt_common_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ {
+ .size = 0,
+ },
+};
+
+
+/**************************************
+ * Vendor Specific Capability
+ */
+
+/* Vendor Specific Capability Structure reg static information table */
+static XenPTRegInfo xen_pt_emu_reg_vendor[] = {
+ {
+ .offset = PCI_CAP_LIST_NEXT,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0xFF,
+ .init = xen_pt_ptr_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ {
+ .size = 0,
+ },
+};
+
+
+/*****************************
+ * PCI Express Capability
+ */
+
+static inline uint8_t get_capability_version(XenPCIPassthroughState *s,
+ uint32_t offset)
+{
+ uint8_t flags = pci_get_byte(s->dev.config + offset + PCI_EXP_FLAGS);
+ return flags & PCI_EXP_FLAGS_VERS;
+}
+
+static inline uint8_t get_device_type(XenPCIPassthroughState *s,
+ uint32_t offset)
+{
+ uint8_t flags = pci_get_byte(s->dev.config + offset + PCI_EXP_FLAGS);
+ return (flags & PCI_EXP_FLAGS_TYPE) >> 4;
+}
+
+/* initialize Link Control register */
+static int xen_pt_linkctrl_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ uint8_t cap_ver = get_capability_version(s, real_offset - reg->offset);
+ uint8_t dev_type = get_device_type(s, real_offset - reg->offset);
+
+ /* no need to initialize in case of Root Complex Integrated Endpoint
+ * with cap_ver 1.x
+ */
+ if ((dev_type == PCI_EXP_TYPE_RC_END) && (cap_ver == 1)) {
+ *data = XEN_PT_INVALID_REG;
+ }
+
+ *data = reg->init_val;
+ return 0;
+}
+/* initialize Device Control 2 register */
+static int xen_pt_devctrl2_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ uint8_t cap_ver = get_capability_version(s, real_offset - reg->offset);
+
+ /* no need to initialize in case of cap_ver 1.x */
+ if (cap_ver == 1) {
+ *data = XEN_PT_INVALID_REG;
+ }
+
+ *data = reg->init_val;
+ return 0;
+}
+/* initialize Link Control 2 register */
+static int xen_pt_linkctrl2_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ uint8_t cap_ver = get_capability_version(s, real_offset - reg->offset);
+ uint32_t reg_field = 0;
+
+ /* no need to initialize in case of cap_ver 1.x */
+ if (cap_ver == 1) {
+ reg_field = XEN_PT_INVALID_REG;
+ } else {
+ /* set Supported Link Speed */
+ uint8_t lnkcap = pci_get_byte(s->dev.config + real_offset - reg->offset
+ + PCI_EXP_LNKCAP);
+ reg_field |= PCI_EXP_LNKCAP_SLS & lnkcap;
+ }
+
+ *data = reg_field;
+ return 0;
+}
+
+/* PCI Express Capability Structure reg static information table */
+static XenPTRegInfo xen_pt_emu_reg_pcie[] = {
+ /* Next Pointer reg */
+ {
+ .offset = PCI_CAP_LIST_NEXT,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0xFF,
+ .init = xen_pt_ptr_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Device Capabilities reg */
+ {
+ .offset = PCI_EXP_DEVCAP,
+ .size = 4,
+ .init_val = 0x00000000,
+ .ro_mask = 0xFFFFFFFF,
+ .emu_mask = 0x10000000,
+ .init = xen_pt_common_reg_init,
+ .u.dw.read = xen_pt_long_reg_read,
+ .u.dw.write = xen_pt_long_reg_write,
+ },
+ /* Device Control reg */
+ {
+ .offset = PCI_EXP_DEVCTL,
+ .size = 2,
+ .init_val = 0x2810,
+ .ro_mask = 0x8400,
+ .emu_mask = 0xFFFF,
+ .init = xen_pt_common_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* Device Status reg */
+ {
+ .offset = PCI_EXP_DEVSTA,
+ .size = 2,
+ .res_mask = 0xFFC0,
+ .ro_mask = 0x0030,
+ .init = xen_pt_common_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* Link Control reg */
+ {
+ .offset = PCI_EXP_LNKCTL,
+ .size = 2,
+ .init_val = 0x0000,
+ .ro_mask = 0xFC34,
+ .emu_mask = 0xFFFF,
+ .init = xen_pt_linkctrl_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* Link Status reg */
+ {
+ .offset = PCI_EXP_LNKSTA,
+ .size = 2,
+ .ro_mask = 0x3FFF,
+ .init = xen_pt_common_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* Device Control 2 reg */
+ {
+ .offset = 0x28,
+ .size = 2,
+ .init_val = 0x0000,
+ .ro_mask = 0xFFE0,
+ .emu_mask = 0xFFFF,
+ .init = xen_pt_devctrl2_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* Link Control 2 reg */
+ {
+ .offset = 0x30,
+ .size = 2,
+ .init_val = 0x0000,
+ .ro_mask = 0xE040,
+ .emu_mask = 0xFFFF,
+ .init = xen_pt_linkctrl2_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ {
+ .size = 0,
+ },
+};
+
+
+/*********************************
+ * Power Management Capability
+ */
+
+/* write Power Management Control/Status register */
+static int xen_pt_pmcsr_reg_write(XenPCIPassthroughState *s,
+ XenPTReg *cfg_entry, uint16_t *val,
+ uint16_t dev_value, uint16_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint16_t writable_mask = 0;
+ uint16_t throughable_mask = get_throughable_mask(s, reg, valid_mask);
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value & ~PCI_PM_CTRL_PME_STATUS,
+ throughable_mask);
+
+ return 0;
+}
+
+/* Power Management Capability reg static information table */
+static XenPTRegInfo xen_pt_emu_reg_pm[] = {
+ /* Next Pointer reg */
+ {
+ .offset = PCI_CAP_LIST_NEXT,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0xFF,
+ .init = xen_pt_ptr_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Power Management Capabilities reg */
+ {
+ .offset = PCI_CAP_FLAGS,
+ .size = 2,
+ .init_val = 0x0000,
+ .ro_mask = 0xFFFF,
+ .emu_mask = 0xF9C8,
+ .init = xen_pt_common_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_word_reg_write,
+ },
+ /* PCI Power Management Control/Status reg */
+ {
+ .offset = PCI_PM_CTRL,
+ .size = 2,
+ .init_val = 0x0008,
+ .res_mask = 0x00F0,
+ .ro_mask = 0xE10C,
+ .emu_mask = 0x810B,
+ .init = xen_pt_common_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_pmcsr_reg_write,
+ },
+ {
+ .size = 0,
+ },
+};
+
+
+/********************************
+ * MSI Capability
+ */
+
+/* Helper */
+#define xen_pt_msi_check_type(offset, flags, what) \
+ ((offset) == ((flags) & PCI_MSI_FLAGS_64BIT ? \
+ PCI_MSI_##what##_64 : PCI_MSI_##what##_32))
+
+/* Message Control register */
+static int xen_pt_msgctrl_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ PCIDevice *d = &s->dev;
+ XenPTMSI *msi = s->msi;
+ uint16_t reg_field = 0;
+
+ /* use I/O device register's value as initial value */
+ reg_field = pci_get_word(d->config + real_offset);
+
+ if (reg_field & PCI_MSI_FLAGS_ENABLE) {
+ XEN_PT_LOG(&s->dev, "MSI already enabled, disabling it first\n");
+ xen_host_pci_set_word(&s->real_device, real_offset,
+ reg_field & ~PCI_MSI_FLAGS_ENABLE);
+ }
+ msi->flags |= reg_field;
+ msi->ctrl_offset = real_offset;
+ msi->initialized = false;
+ msi->mapped = false;
+
+ *data = reg->init_val;
+ return 0;
+}
+static int xen_pt_msgctrl_reg_write(XenPCIPassthroughState *s,
+ XenPTReg *cfg_entry, uint16_t *val,
+ uint16_t dev_value, uint16_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ XenPTMSI *msi = s->msi;
+ uint16_t writable_mask = 0;
+ uint16_t throughable_mask = get_throughable_mask(s, reg, valid_mask);
+
+ /* Currently no support for multi-vector */
+ if (*val & PCI_MSI_FLAGS_QSIZE) {
+ XEN_PT_WARN(&s->dev, "Tries to set more than 1 vector ctrl %x\n", *val);
+ }
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+ msi->flags |= cfg_entry->data & ~PCI_MSI_FLAGS_ENABLE;
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, throughable_mask);
+
+ /* update MSI */
+ if (*val & PCI_MSI_FLAGS_ENABLE) {
+ /* setup MSI pirq for the first time */
+ if (!msi->initialized) {
+ /* Init physical one */
+ XEN_PT_LOG(&s->dev, "setup MSI\n");
+ if (xen_pt_msi_setup(s)) {
+ /* We do not broadcast the error to the framework code, so
+ * that MSI errors are contained in MSI emulation code and
+ * QEMU can go on running.
+ * Guest MSI would be actually not working.
+ */
+ *val &= ~PCI_MSI_FLAGS_ENABLE;
+ XEN_PT_WARN(&s->dev, "Can not map MSI.\n");
+ return 0;
+ }
+ if (xen_pt_msi_update(s)) {
+ *val &= ~PCI_MSI_FLAGS_ENABLE;
+ XEN_PT_WARN(&s->dev, "Can not bind MSI\n");
+ return 0;
+ }
+ msi->initialized = true;
+ msi->mapped = true;
+ }
+ msi->flags |= PCI_MSI_FLAGS_ENABLE;
+ } else if (msi->mapped) {
+ xen_pt_msi_disable(s);
+ }
+
+ return 0;
+}
+
+/* initialize Message Upper Address register */
+static int xen_pt_msgaddr64_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ /* no need to initialize in case of 32 bit type */
+ if (!(s->msi->flags & PCI_MSI_FLAGS_64BIT)) {
+ *data = XEN_PT_INVALID_REG;
+ } else {
+ *data = reg->init_val;
+ }
+
+ return 0;
+}
+/* this function will be called twice (for 32 bit and 64 bit type) */
+/* initialize Message Data register */
+static int xen_pt_msgdata_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ uint32_t flags = s->msi->flags;
+ uint32_t offset = reg->offset;
+
+ /* check the offset whether matches the type or not */
+ if (xen_pt_msi_check_type(offset, flags, DATA)) {
+ *data = reg->init_val;
+ } else {
+ *data = XEN_PT_INVALID_REG;
+ }
+ return 0;
+}
+
+/* this function will be called twice (for 32 bit and 64 bit type) */
+/* initialize Mask register */
+static int xen_pt_mask_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ uint32_t flags = s->msi->flags;
+
+ /* check the offset whether matches the type or not */
+ if (!(flags & PCI_MSI_FLAGS_MASKBIT)) {
+ *data = XEN_PT_INVALID_REG;
+ } else if (xen_pt_msi_check_type(reg->offset, flags, MASK)) {
+ *data = reg->init_val;
+ } else {
+ *data = XEN_PT_INVALID_REG;
+ }
+ return 0;
+}
+
+/* this function will be called twice (for 32 bit and 64 bit type) */
+/* initialize Pending register */
+static int xen_pt_pending_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ uint32_t flags = s->msi->flags;
+
+ /* check the offset whether matches the type or not */
+ if (!(flags & PCI_MSI_FLAGS_MASKBIT)) {
+ *data = XEN_PT_INVALID_REG;
+ } else if (xen_pt_msi_check_type(reg->offset, flags, PENDING)) {
+ *data = reg->init_val;
+ } else {
+ *data = XEN_PT_INVALID_REG;
+ }
+ return 0;
+}
+
+/* write Message Address register */
+static int xen_pt_msgaddr32_reg_write(XenPCIPassthroughState *s,
+ XenPTReg *cfg_entry, uint32_t *val,
+ uint32_t dev_value, uint32_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint32_t writable_mask = 0;
+ uint32_t old_addr = cfg_entry->data;
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+ s->msi->addr_lo = cfg_entry->data;
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, 0);
+
+ /* update MSI */
+ if (cfg_entry->data != old_addr) {
+ if (s->msi->mapped) {
+ xen_pt_msi_update(s);
+ }
+ }
+
+ return 0;
+}
+/* write Message Upper Address register */
+static int xen_pt_msgaddr64_reg_write(XenPCIPassthroughState *s,
+ XenPTReg *cfg_entry, uint32_t *val,
+ uint32_t dev_value, uint32_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint32_t writable_mask = 0;
+ uint32_t old_addr = cfg_entry->data;
+
+ /* check whether the type is 64 bit or not */
+ if (!(s->msi->flags & PCI_MSI_FLAGS_64BIT)) {
+ XEN_PT_ERR(&s->dev,
+ "Can't write to the upper address without 64 bit support\n");
+ return -1;
+ }
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+ /* update the msi_info too */
+ s->msi->addr_hi = cfg_entry->data;
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, 0);
+
+ /* update MSI */
+ if (cfg_entry->data != old_addr) {
+ if (s->msi->mapped) {
+ xen_pt_msi_update(s);
+ }
+ }
+
+ return 0;
+}
+
+
+/* this function will be called twice (for 32 bit and 64 bit type) */
+/* write Message Data register */
+static int xen_pt_msgdata_reg_write(XenPCIPassthroughState *s,
+ XenPTReg *cfg_entry, uint16_t *val,
+ uint16_t dev_value, uint16_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ XenPTMSI *msi = s->msi;
+ uint16_t writable_mask = 0;
+ uint16_t old_data = cfg_entry->data;
+ uint32_t offset = reg->offset;
+
+ /* check the offset whether matches the type or not */
+ if (!xen_pt_msi_check_type(offset, msi->flags, DATA)) {
+ /* exit I/O emulator */
+ XEN_PT_ERR(&s->dev, "the offset does not match the 32/64 bit type!\n");
+ return -1;
+ }
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+ /* update the msi_info too */
+ msi->data = cfg_entry->data;
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, 0);
+
+ /* update MSI */
+ if (cfg_entry->data != old_data) {
+ if (msi->mapped) {
+ xen_pt_msi_update(s);
+ }
+ }
+
+ return 0;
+}
+
+/* MSI Capability Structure reg static information table */
+static XenPTRegInfo xen_pt_emu_reg_msi[] = {
+ /* Next Pointer reg */
+ {
+ .offset = PCI_CAP_LIST_NEXT,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0xFF,
+ .init = xen_pt_ptr_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Message Control reg */
+ {
+ .offset = PCI_MSI_FLAGS,
+ .size = 2,
+ .init_val = 0x0000,
+ .res_mask = 0xFE00,
+ .ro_mask = 0x018E,
+ .emu_mask = 0x017E,
+ .init = xen_pt_msgctrl_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_msgctrl_reg_write,
+ },
+ /* Message Address reg */
+ {
+ .offset = PCI_MSI_ADDRESS_LO,
+ .size = 4,
+ .init_val = 0x00000000,
+ .ro_mask = 0x00000003,
+ .emu_mask = 0xFFFFFFFF,
+ .init = xen_pt_common_reg_init,
+ .u.dw.read = xen_pt_long_reg_read,
+ .u.dw.write = xen_pt_msgaddr32_reg_write,
+ },
+ /* Message Upper Address reg (if PCI_MSI_FLAGS_64BIT set) */
+ {
+ .offset = PCI_MSI_ADDRESS_HI,
+ .size = 4,
+ .init_val = 0x00000000,
+ .ro_mask = 0x00000000,
+ .emu_mask = 0xFFFFFFFF,
+ .init = xen_pt_msgaddr64_reg_init,
+ .u.dw.read = xen_pt_long_reg_read,
+ .u.dw.write = xen_pt_msgaddr64_reg_write,
+ },
+ /* Message Data reg (16 bits of data for 32-bit devices) */
+ {
+ .offset = PCI_MSI_DATA_32,
+ .size = 2,
+ .init_val = 0x0000,
+ .ro_mask = 0x0000,
+ .emu_mask = 0xFFFF,
+ .init = xen_pt_msgdata_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_msgdata_reg_write,
+ },
+ /* Message Data reg (16 bits of data for 64-bit devices) */
+ {
+ .offset = PCI_MSI_DATA_64,
+ .size = 2,
+ .init_val = 0x0000,
+ .ro_mask = 0x0000,
+ .emu_mask = 0xFFFF,
+ .init = xen_pt_msgdata_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_msgdata_reg_write,
+ },
+ /* Mask reg (if PCI_MSI_FLAGS_MASKBIT set, for 32-bit devices) */
+ {
+ .offset = PCI_MSI_MASK_32,
+ .size = 4,
+ .init_val = 0x00000000,
+ .ro_mask = 0xFFFFFFFF,
+ .emu_mask = 0xFFFFFFFF,
+ .init = xen_pt_mask_reg_init,
+ .u.dw.read = xen_pt_long_reg_read,
+ .u.dw.write = xen_pt_long_reg_write,
+ },
+ /* Mask reg (if PCI_MSI_FLAGS_MASKBIT set, for 64-bit devices) */
+ {
+ .offset = PCI_MSI_MASK_64,
+ .size = 4,
+ .init_val = 0x00000000,
+ .ro_mask = 0xFFFFFFFF,
+ .emu_mask = 0xFFFFFFFF,
+ .init = xen_pt_mask_reg_init,
+ .u.dw.read = xen_pt_long_reg_read,
+ .u.dw.write = xen_pt_long_reg_write,
+ },
+ /* Pending reg (if PCI_MSI_FLAGS_MASKBIT set, for 32-bit devices) */
+ {
+ .offset = PCI_MSI_MASK_32 + 4,
+ .size = 4,
+ .init_val = 0x00000000,
+ .ro_mask = 0xFFFFFFFF,
+ .emu_mask = 0x00000000,
+ .init = xen_pt_pending_reg_init,
+ .u.dw.read = xen_pt_long_reg_read,
+ .u.dw.write = xen_pt_long_reg_write,
+ },
+ /* Pending reg (if PCI_MSI_FLAGS_MASKBIT set, for 64-bit devices) */
+ {
+ .offset = PCI_MSI_MASK_64 + 4,
+ .size = 4,
+ .init_val = 0x00000000,
+ .ro_mask = 0xFFFFFFFF,
+ .emu_mask = 0x00000000,
+ .init = xen_pt_pending_reg_init,
+ .u.dw.read = xen_pt_long_reg_read,
+ .u.dw.write = xen_pt_long_reg_write,
+ },
+ {
+ .size = 0,
+ },
+};
+
+
+/**************************************
+ * MSI-X Capability
+ */
+
+/* Message Control register for MSI-X */
+static int xen_pt_msixctrl_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ PCIDevice *d = &s->dev;
+ uint16_t reg_field = 0;
+
+ /* use I/O device register's value as initial value */
+ reg_field = pci_get_word(d->config + real_offset);
+
+ if (reg_field & PCI_MSIX_FLAGS_ENABLE) {
+ XEN_PT_LOG(d, "MSIX already enabled, disabling it first\n");
+ xen_host_pci_set_word(&s->real_device, real_offset,
+ reg_field & ~PCI_MSIX_FLAGS_ENABLE);
+ }
+
+ s->msix->ctrl_offset = real_offset;
+
+ *data = reg->init_val;
+ return 0;
+}
+static int xen_pt_msixctrl_reg_write(XenPCIPassthroughState *s,
+ XenPTReg *cfg_entry, uint16_t *val,
+ uint16_t dev_value, uint16_t valid_mask)
+{
+ XenPTRegInfo *reg = cfg_entry->reg;
+ uint16_t writable_mask = 0;
+ uint16_t throughable_mask = get_throughable_mask(s, reg, valid_mask);
+ int debug_msix_enabled_old;
+
+ /* modify emulate register */
+ writable_mask = reg->emu_mask & ~reg->ro_mask & valid_mask;
+ cfg_entry->data = XEN_PT_MERGE_VALUE(*val, cfg_entry->data, writable_mask);
+
+ /* create value for writing to I/O device register */
+ *val = XEN_PT_MERGE_VALUE(*val, dev_value, throughable_mask);
+
+ /* update MSI-X */
+ if ((*val & PCI_MSIX_FLAGS_ENABLE)
+ && !(*val & PCI_MSIX_FLAGS_MASKALL)) {
+ xen_pt_msix_update(s);
+ } else if (!(*val & PCI_MSIX_FLAGS_ENABLE) && s->msix->enabled) {
+ xen_pt_msix_disable(s);
+ }
+
+ debug_msix_enabled_old = s->msix->enabled;
+ s->msix->enabled = !!(*val & PCI_MSIX_FLAGS_ENABLE);
+ if (s->msix->enabled != debug_msix_enabled_old) {
+ XEN_PT_LOG(&s->dev, "%s MSI-X\n",
+ s->msix->enabled ? "enable" : "disable");
+ }
+
+ return 0;
+}
+
+/* MSI-X Capability Structure reg static information table */
+static XenPTRegInfo xen_pt_emu_reg_msix[] = {
+ /* Next Pointer reg */
+ {
+ .offset = PCI_CAP_LIST_NEXT,
+ .size = 1,
+ .init_val = 0x00,
+ .ro_mask = 0xFF,
+ .emu_mask = 0xFF,
+ .init = xen_pt_ptr_reg_init,
+ .u.b.read = xen_pt_byte_reg_read,
+ .u.b.write = xen_pt_byte_reg_write,
+ },
+ /* Message Control reg */
+ {
+ .offset = PCI_MSI_FLAGS,
+ .size = 2,
+ .init_val = 0x0000,
+ .res_mask = 0x3800,
+ .ro_mask = 0x07FF,
+ .emu_mask = 0x0000,
+ .init = xen_pt_msixctrl_reg_init,
+ .u.w.read = xen_pt_word_reg_read,
+ .u.w.write = xen_pt_msixctrl_reg_write,
+ },
+ {
+ .size = 0,
+ },
+};
+
+
+/****************************
+ * Capabilities
+ */
+
+/* capability structure register group size functions */
+
+static int xen_pt_reg_grp_size_init(XenPCIPassthroughState *s,
+ const XenPTRegGroupInfo *grp_reg,
+ uint32_t base_offset, uint8_t *size)
+{
+ *size = grp_reg->grp_size;
+ return 0;
+}
+/* get Vendor Specific Capability Structure register group size */
+static int xen_pt_vendor_size_init(XenPCIPassthroughState *s,
+ const XenPTRegGroupInfo *grp_reg,
+ uint32_t base_offset, uint8_t *size)
+{
+ *size = pci_get_byte(s->dev.config + base_offset + 0x02);
+ return 0;
+}
+/* get PCI Express Capability Structure register group size */
+static int xen_pt_pcie_size_init(XenPCIPassthroughState *s,
+ const XenPTRegGroupInfo *grp_reg,
+ uint32_t base_offset, uint8_t *size)
+{
+ PCIDevice *d = &s->dev;
+ uint8_t version = get_capability_version(s, base_offset);
+ uint8_t type = get_device_type(s, base_offset);
+ uint8_t pcie_size = 0;
+
+
+ /* calculate size depend on capability version and device/port type */
+ /* in case of PCI Express Base Specification Rev 1.x */
+ if (version == 1) {
+ /* The PCI Express Capabilities, Device Capabilities, and Device
+ * Status/Control registers are required for all PCI Express devices.
+ * The Link Capabilities and Link Status/Control are required for all
+ * Endpoints that are not Root Complex Integrated Endpoints. Endpoints
+ * are not required to implement registers other than those listed
+ * above and terminate the capability structure.
+ */
+ switch (type) {
+ case PCI_EXP_TYPE_ENDPOINT:
+ case PCI_EXP_TYPE_LEG_END:
+ pcie_size = 0x14;
+ break;
+ case PCI_EXP_TYPE_RC_END:
+ /* has no link */
+ pcie_size = 0x0C;
+ break;
+ /* only EndPoint passthrough is supported */
+ case PCI_EXP_TYPE_ROOT_PORT:
+ case PCI_EXP_TYPE_UPSTREAM:
+ case PCI_EXP_TYPE_DOWNSTREAM:
+ case PCI_EXP_TYPE_PCI_BRIDGE:
+ case PCI_EXP_TYPE_PCIE_BRIDGE:
+ case PCI_EXP_TYPE_RC_EC:
+ default:
+ XEN_PT_ERR(d, "Unsupported device/port type %#x.\n", type);
+ return -1;
+ }
+ }
+ /* in case of PCI Express Base Specification Rev 2.0 */
+ else if (version == 2) {
+ switch (type) {
+ case PCI_EXP_TYPE_ENDPOINT:
+ case PCI_EXP_TYPE_LEG_END:
+ case PCI_EXP_TYPE_RC_END:
+ /* For Functions that do not implement the registers,
+ * these spaces must be hardwired to 0b.
+ */
+ pcie_size = 0x3C;
+ break;
+ /* only EndPoint passthrough is supported */
+ case PCI_EXP_TYPE_ROOT_PORT:
+ case PCI_EXP_TYPE_UPSTREAM:
+ case PCI_EXP_TYPE_DOWNSTREAM:
+ case PCI_EXP_TYPE_PCI_BRIDGE:
+ case PCI_EXP_TYPE_PCIE_BRIDGE:
+ case PCI_EXP_TYPE_RC_EC:
+ default:
+ XEN_PT_ERR(d, "Unsupported device/port type %#x.\n", type);
+ return -1;
+ }
+ } else {
+ XEN_PT_ERR(d, "Unsupported capability version %#x.\n", version);
+ return -1;
+ }
+
+ *size = pcie_size;
+ return 0;
+}
+/* get MSI Capability Structure register group size */
+static int xen_pt_msi_size_init(XenPCIPassthroughState *s,
+ const XenPTRegGroupInfo *grp_reg,
+ uint32_t base_offset, uint8_t *size)
+{
+ PCIDevice *d = &s->dev;
+ uint16_t msg_ctrl = 0;
+ uint8_t msi_size = 0xa;
+
+ msg_ctrl = pci_get_word(d->config + (base_offset + PCI_MSI_FLAGS));
+
+ /* check if 64-bit address is capable of per-vector masking */
+ if (msg_ctrl & PCI_MSI_FLAGS_64BIT) {
+ msi_size += 4;
+ }
+ if (msg_ctrl & PCI_MSI_FLAGS_MASKBIT) {
+ msi_size += 10;
+ }
+
+ s->msi = g_new0(XenPTMSI, 1);
+ s->msi->pirq = XEN_PT_UNASSIGNED_PIRQ;
+
+ *size = msi_size;
+ return 0;
+}
+/* get MSI-X Capability Structure register group size */
+static int xen_pt_msix_size_init(XenPCIPassthroughState *s,
+ const XenPTRegGroupInfo *grp_reg,
+ uint32_t base_offset, uint8_t *size)
+{
+ int rc = 0;
+
+ rc = xen_pt_msix_init(s, base_offset);
+
+ if (rc < 0) {
+ XEN_PT_ERR(&s->dev, "Internal error: Invalid xen_pt_msix_init.\n");
+ return rc;
+ }
+
+ *size = grp_reg->grp_size;
+ return 0;
+}
+
+
+static const XenPTRegGroupInfo xen_pt_emu_reg_grps[] = {
+ /* Header Type0 reg group */
+ {
+ .grp_id = 0xFF,
+ .grp_type = XEN_PT_GRP_TYPE_EMU,
+ .grp_size = 0x40,
+ .size_init = xen_pt_reg_grp_size_init,
+ .emu_regs = xen_pt_emu_reg_header0,
+ },
+ /* PCI PowerManagement Capability reg group */
+ {
+ .grp_id = PCI_CAP_ID_PM,
+ .grp_type = XEN_PT_GRP_TYPE_EMU,
+ .grp_size = PCI_PM_SIZEOF,
+ .size_init = xen_pt_reg_grp_size_init,
+ .emu_regs = xen_pt_emu_reg_pm,
+ },
+ /* AGP Capability Structure reg group */
+ {
+ .grp_id = PCI_CAP_ID_AGP,
+ .grp_type = XEN_PT_GRP_TYPE_HARDWIRED,
+ .grp_size = 0x30,
+ .size_init = xen_pt_reg_grp_size_init,
+ },
+ /* Vital Product Data Capability Structure reg group */
+ {
+ .grp_id = PCI_CAP_ID_VPD,
+ .grp_type = XEN_PT_GRP_TYPE_EMU,
+ .grp_size = 0x08,
+ .size_init = xen_pt_reg_grp_size_init,
+ .emu_regs = xen_pt_emu_reg_vpd,
+ },
+ /* Slot Identification reg group */
+ {
+ .grp_id = PCI_CAP_ID_SLOTID,
+ .grp_type = XEN_PT_GRP_TYPE_HARDWIRED,
+ .grp_size = 0x04,
+ .size_init = xen_pt_reg_grp_size_init,
+ },
+ /* MSI Capability Structure reg group */
+ {
+ .grp_id = PCI_CAP_ID_MSI,
+ .grp_type = XEN_PT_GRP_TYPE_EMU,
+ .grp_size = 0xFF,
+ .size_init = xen_pt_msi_size_init,
+ .emu_regs = xen_pt_emu_reg_msi,
+ },
+ /* PCI-X Capabilities List Item reg group */
+ {
+ .grp_id = PCI_CAP_ID_PCIX,
+ .grp_type = XEN_PT_GRP_TYPE_HARDWIRED,
+ .grp_size = 0x18,
+ .size_init = xen_pt_reg_grp_size_init,
+ },
+ /* Vendor Specific Capability Structure reg group */
+ {
+ .grp_id = PCI_CAP_ID_VNDR,
+ .grp_type = XEN_PT_GRP_TYPE_EMU,
+ .grp_size = 0xFF,
+ .size_init = xen_pt_vendor_size_init,
+ .emu_regs = xen_pt_emu_reg_vendor,
+ },
+ /* SHPC Capability List Item reg group */
+ {
+ .grp_id = PCI_CAP_ID_SHPC,
+ .grp_type = XEN_PT_GRP_TYPE_HARDWIRED,
+ .grp_size = 0x08,
+ .size_init = xen_pt_reg_grp_size_init,
+ },
+ /* Subsystem ID and Subsystem Vendor ID Capability List Item reg group */
+ {
+ .grp_id = PCI_CAP_ID_SSVID,
+ .grp_type = XEN_PT_GRP_TYPE_HARDWIRED,
+ .grp_size = 0x08,
+ .size_init = xen_pt_reg_grp_size_init,
+ },
+ /* AGP 8x Capability Structure reg group */
+ {
+ .grp_id = PCI_CAP_ID_AGP3,
+ .grp_type = XEN_PT_GRP_TYPE_HARDWIRED,
+ .grp_size = 0x30,
+ .size_init = xen_pt_reg_grp_size_init,
+ },
+ /* PCI Express Capability Structure reg group */
+ {
+ .grp_id = PCI_CAP_ID_EXP,
+ .grp_type = XEN_PT_GRP_TYPE_EMU,
+ .grp_size = 0xFF,
+ .size_init = xen_pt_pcie_size_init,
+ .emu_regs = xen_pt_emu_reg_pcie,
+ },
+ /* MSI-X Capability Structure reg group */
+ {
+ .grp_id = PCI_CAP_ID_MSIX,
+ .grp_type = XEN_PT_GRP_TYPE_EMU,
+ .grp_size = 0x0C,
+ .size_init = xen_pt_msix_size_init,
+ .emu_regs = xen_pt_emu_reg_msix,
+ },
+ {
+ .grp_size = 0,
+ },
+};
+
+/* initialize Capabilities Pointer or Next Pointer register */
+static int xen_pt_ptr_reg_init(XenPCIPassthroughState *s,
+ XenPTRegInfo *reg, uint32_t real_offset,
+ uint32_t *data)
+{
+ int i;
+ uint8_t *config = s->dev.config;
+ uint32_t reg_field = pci_get_byte(config + real_offset);
+ uint8_t cap_id = 0;
+
+ /* find capability offset */
+ while (reg_field) {
+ for (i = 0; xen_pt_emu_reg_grps[i].grp_size != 0; i++) {
+ if (xen_pt_hide_dev_cap(&s->real_device,
+ xen_pt_emu_reg_grps[i].grp_id)) {
+ continue;
+ }
+
+ cap_id = pci_get_byte(config + reg_field + PCI_CAP_LIST_ID);
+ if (xen_pt_emu_reg_grps[i].grp_id == cap_id) {
+ if (xen_pt_emu_reg_grps[i].grp_type == XEN_PT_GRP_TYPE_EMU) {
+ goto out;
+ }
+ /* ignore the 0 hardwired capability, find next one */
+ break;
+ }
+ }
+
+ /* next capability */
+ reg_field = pci_get_byte(config + reg_field + PCI_CAP_LIST_NEXT);
+ }
+
+out:
+ *data = reg_field;
+ return 0;
+}
+
+
+/*************
+ * Main
+ */
+
+static uint8_t find_cap_offset(XenPCIPassthroughState *s, uint8_t cap)
+{
+ uint8_t id;
+ unsigned max_cap = PCI_CAP_MAX;
+ uint8_t pos = PCI_CAPABILITY_LIST;
+ uint8_t status = 0;
+
+ if (xen_host_pci_get_byte(&s->real_device, PCI_STATUS, &status)) {
+ return 0;
+ }
+ if ((status & PCI_STATUS_CAP_LIST) == 0) {
+ return 0;
+ }
+
+ while (max_cap--) {
+ if (xen_host_pci_get_byte(&s->real_device, pos, &pos)) {
+ break;
+ }
+ if (pos < PCI_CONFIG_HEADER_SIZE) {
+ break;
+ }
+
+ pos &= ~3;
+ if (xen_host_pci_get_byte(&s->real_device,
+ pos + PCI_CAP_LIST_ID, &id)) {
+ break;
+ }
+
+ if (id == 0xff) {
+ break;
+ }
+ if (id == cap) {
+ return pos;
+ }
+
+ pos += PCI_CAP_LIST_NEXT;
+ }
+ return 0;
+}
+
+static int xen_pt_config_reg_init(XenPCIPassthroughState *s,
+ XenPTRegGroup *reg_grp, XenPTRegInfo *reg)
+{
+ XenPTReg *reg_entry;
+ uint32_t data = 0;
+ int rc = 0;
+
+ reg_entry = g_new0(XenPTReg, 1);
+ reg_entry->reg = reg;
+
+ if (reg->init) {
+ /* initialize emulate register */
+ rc = reg->init(s, reg_entry->reg,
+ reg_grp->base_offset + reg->offset, &data);
+ if (rc < 0) {
+ g_free(reg_entry);
+ return rc;
+ }
+ if (data == XEN_PT_INVALID_REG) {
+ /* free unused BAR register entry */
+ g_free(reg_entry);
+ return 0;
+ }
+ /* set register value */
+ reg_entry->data = data;
+ }
+ /* list add register entry */
+ QLIST_INSERT_HEAD(&reg_grp->reg_tbl_list, reg_entry, entries);
+
+ return 0;
+}
+
+int xen_pt_config_init(XenPCIPassthroughState *s)
+{
+ int i, rc;
+
+ QLIST_INIT(&s->reg_grps);
+
+ for (i = 0; xen_pt_emu_reg_grps[i].grp_size != 0; i++) {
+ uint32_t reg_grp_offset = 0;
+ XenPTRegGroup *reg_grp_entry = NULL;
+
+ if (xen_pt_emu_reg_grps[i].grp_id != 0xFF) {
+ if (xen_pt_hide_dev_cap(&s->real_device,
+ xen_pt_emu_reg_grps[i].grp_id)) {
+ continue;
+ }
+
+ reg_grp_offset = find_cap_offset(s, xen_pt_emu_reg_grps[i].grp_id);
+
+ if (!reg_grp_offset) {
+ continue;
+ }
+ }
+
+ reg_grp_entry = g_new0(XenPTRegGroup, 1);
+ QLIST_INIT(&reg_grp_entry->reg_tbl_list);
+ QLIST_INSERT_HEAD(&s->reg_grps, reg_grp_entry, entries);
+
+ reg_grp_entry->base_offset = reg_grp_offset;
+ reg_grp_entry->reg_grp = xen_pt_emu_reg_grps + i;
+ if (xen_pt_emu_reg_grps[i].size_init) {
+ /* get register group size */
+ rc = xen_pt_emu_reg_grps[i].size_init(s, reg_grp_entry->reg_grp,
+ reg_grp_offset,
+ &reg_grp_entry->size);
+ if (rc < 0) {
+ xen_pt_config_delete(s);
+ return rc;
+ }
+ }
+
+ if (xen_pt_emu_reg_grps[i].grp_type == XEN_PT_GRP_TYPE_EMU) {
+ if (xen_pt_emu_reg_grps[i].emu_regs) {
+ int j = 0;
+ XenPTRegInfo *regs = xen_pt_emu_reg_grps[i].emu_regs;
+ /* initialize capability register */
+ for (j = 0; regs->size != 0; j++, regs++) {
+ /* initialize capability register */
+ rc = xen_pt_config_reg_init(s, reg_grp_entry, regs);
+ if (rc < 0) {
+ xen_pt_config_delete(s);
+ return rc;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* delete all emulate register */
+void xen_pt_config_delete(XenPCIPassthroughState *s)
+{
+ struct XenPTRegGroup *reg_group, *next_grp;
+ struct XenPTReg *reg, *next_reg;
+
+ /* free MSI/MSI-X info table */
+ if (s->msix) {
+ xen_pt_msix_delete(s);
+ }
+ if (s->msi) {
+ g_free(s->msi);
+ }
+
+ /* free all register group entry */
+ QLIST_FOREACH_SAFE(reg_group, &s->reg_grps, entries, next_grp) {
+ /* free all register entry */
+ QLIST_FOREACH_SAFE(reg, &reg_group->reg_tbl_list, entries, next_reg) {
+ QLIST_REMOVE(reg, entries);
+ g_free(reg);
+ }
+
+ QLIST_REMOVE(reg_group, entries);
+ g_free(reg_group);
+ }
+}
diff --git a/hw/xen/xen_pt_msi.c b/hw/xen/xen_pt_msi.c
new file mode 100644
index 00000000..263e0514
--- /dev/null
+++ b/hw/xen/xen_pt_msi.c
@@ -0,0 +1,623 @@
+/*
+ * Copyright (c) 2007, Intel Corporation.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Jiang Yunhong <yunhong.jiang@intel.com>
+ *
+ * This file implements direct PCI assignment to a HVM guest
+ */
+
+#include <sys/mman.h>
+
+#include "hw/xen/xen_backend.h"
+#include "xen_pt.h"
+#include "hw/i386/apic-msidef.h"
+
+
+#define XEN_PT_AUTO_ASSIGN -1
+
+/* shift count for gflags */
+#define XEN_PT_GFLAGS_SHIFT_DEST_ID 0
+#define XEN_PT_GFLAGS_SHIFT_RH 8
+#define XEN_PT_GFLAGS_SHIFT_DM 9
+#define XEN_PT_GFLAGSSHIFT_DELIV_MODE 12
+#define XEN_PT_GFLAGSSHIFT_TRG_MODE 15
+
+
+/*
+ * Helpers
+ */
+
+static inline uint8_t msi_vector(uint32_t data)
+{
+ return (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT;
+}
+
+static inline uint8_t msi_dest_id(uint32_t addr)
+{
+ return (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT;
+}
+
+static inline uint32_t msi_ext_dest_id(uint32_t addr_hi)
+{
+ return addr_hi & 0xffffff00;
+}
+
+static uint32_t msi_gflags(uint32_t data, uint64_t addr)
+{
+ uint32_t result = 0;
+ int rh, dm, dest_id, deliv_mode, trig_mode;
+
+ rh = (addr >> MSI_ADDR_REDIRECTION_SHIFT) & 0x1;
+ dm = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1;
+ dest_id = msi_dest_id(addr);
+ deliv_mode = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & 0x7;
+ trig_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1;
+
+ result = dest_id | (rh << XEN_PT_GFLAGS_SHIFT_RH)
+ | (dm << XEN_PT_GFLAGS_SHIFT_DM)
+ | (deliv_mode << XEN_PT_GFLAGSSHIFT_DELIV_MODE)
+ | (trig_mode << XEN_PT_GFLAGSSHIFT_TRG_MODE);
+
+ return result;
+}
+
+static inline uint64_t msi_addr64(XenPTMSI *msi)
+{
+ return (uint64_t)msi->addr_hi << 32 | msi->addr_lo;
+}
+
+static int msi_msix_enable(XenPCIPassthroughState *s,
+ uint32_t address,
+ uint16_t flag,
+ bool enable)
+{
+ uint16_t val = 0;
+
+ if (!address) {
+ return -1;
+ }
+
+ xen_host_pci_get_word(&s->real_device, address, &val);
+ if (enable) {
+ val |= flag;
+ } else {
+ val &= ~flag;
+ }
+ xen_host_pci_set_word(&s->real_device, address, val);
+ return 0;
+}
+
+static int msi_msix_setup(XenPCIPassthroughState *s,
+ uint64_t addr,
+ uint32_t data,
+ int *ppirq,
+ bool is_msix,
+ int msix_entry,
+ bool is_not_mapped)
+{
+ uint8_t gvec = msi_vector(data);
+ int rc = 0;
+
+ assert((!is_msix && msix_entry == 0) || is_msix);
+
+ if (gvec == 0) {
+ /* if gvec is 0, the guest is asking for a particular pirq that
+ * is passed as dest_id */
+ *ppirq = msi_ext_dest_id(addr >> 32) | msi_dest_id(addr);
+ if (!*ppirq) {
+ /* this probably identifies an misconfiguration of the guest,
+ * try the emulated path */
+ *ppirq = XEN_PT_UNASSIGNED_PIRQ;
+ } else {
+ XEN_PT_LOG(&s->dev, "requested pirq %d for MSI%s"
+ " (vec: %#x, entry: %#x)\n",
+ *ppirq, is_msix ? "-X" : "", gvec, msix_entry);
+ }
+ }
+
+ if (is_not_mapped) {
+ uint64_t table_base = 0;
+
+ if (is_msix) {
+ table_base = s->msix->table_base;
+ }
+
+ rc = xc_physdev_map_pirq_msi(xen_xc, xen_domid, XEN_PT_AUTO_ASSIGN,
+ ppirq, PCI_DEVFN(s->real_device.dev,
+ s->real_device.func),
+ s->real_device.bus,
+ msix_entry, table_base);
+ if (rc) {
+ XEN_PT_ERR(&s->dev,
+ "Mapping of MSI%s (err: %i, vec: %#x, entry %#x)\n",
+ is_msix ? "-X" : "", errno, gvec, msix_entry);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+static int msi_msix_update(XenPCIPassthroughState *s,
+ uint64_t addr,
+ uint32_t data,
+ int pirq,
+ bool is_msix,
+ int msix_entry,
+ int *old_pirq)
+{
+ PCIDevice *d = &s->dev;
+ uint8_t gvec = msi_vector(data);
+ uint32_t gflags = msi_gflags(data, addr);
+ int rc = 0;
+ uint64_t table_addr = 0;
+
+ XEN_PT_LOG(d, "Updating MSI%s with pirq %d gvec %#x gflags %#x"
+ " (entry: %#x)\n",
+ is_msix ? "-X" : "", pirq, gvec, gflags, msix_entry);
+
+ if (is_msix) {
+ table_addr = s->msix->mmio_base_addr;
+ }
+
+ rc = xc_domain_update_msi_irq(xen_xc, xen_domid, gvec,
+ pirq, gflags, table_addr);
+
+ if (rc) {
+ XEN_PT_ERR(d, "Updating of MSI%s failed. (err: %d)\n",
+ is_msix ? "-X" : "", errno);
+
+ if (xc_physdev_unmap_pirq(xen_xc, xen_domid, *old_pirq)) {
+ XEN_PT_ERR(d, "Unmapping of MSI%s pirq %d failed. (err: %d)\n",
+ is_msix ? "-X" : "", *old_pirq, errno);
+ }
+ *old_pirq = XEN_PT_UNASSIGNED_PIRQ;
+ }
+ return rc;
+}
+
+static int msi_msix_disable(XenPCIPassthroughState *s,
+ uint64_t addr,
+ uint32_t data,
+ int pirq,
+ bool is_msix,
+ bool is_binded)
+{
+ PCIDevice *d = &s->dev;
+ uint8_t gvec = msi_vector(data);
+ uint32_t gflags = msi_gflags(data, addr);
+ int rc = 0;
+
+ if (pirq == XEN_PT_UNASSIGNED_PIRQ) {
+ return 0;
+ }
+
+ if (is_binded) {
+ XEN_PT_LOG(d, "Unbind MSI%s with pirq %d, gvec %#x\n",
+ is_msix ? "-X" : "", pirq, gvec);
+ rc = xc_domain_unbind_msi_irq(xen_xc, xen_domid, gvec, pirq, gflags);
+ if (rc) {
+ XEN_PT_ERR(d, "Unbinding of MSI%s failed. (err: %d, pirq: %d, gvec: %#x)\n",
+ is_msix ? "-X" : "", errno, pirq, gvec);
+ return rc;
+ }
+ }
+
+ XEN_PT_LOG(d, "Unmap MSI%s pirq %d\n", is_msix ? "-X" : "", pirq);
+ rc = xc_physdev_unmap_pirq(xen_xc, xen_domid, pirq);
+ if (rc) {
+ XEN_PT_ERR(d, "Unmapping of MSI%s pirq %d failed. (err: %i)\n",
+ is_msix ? "-X" : "", pirq, errno);
+ return rc;
+ }
+
+ return 0;
+}
+
+/*
+ * MSI virtualization functions
+ */
+
+int xen_pt_msi_set_enable(XenPCIPassthroughState *s, bool enable)
+{
+ XEN_PT_LOG(&s->dev, "%s MSI.\n", enable ? "enabling" : "disabling");
+
+ if (!s->msi) {
+ return -1;
+ }
+
+ return msi_msix_enable(s, s->msi->ctrl_offset, PCI_MSI_FLAGS_ENABLE,
+ enable);
+}
+
+/* setup physical msi, but don't enable it */
+int xen_pt_msi_setup(XenPCIPassthroughState *s)
+{
+ int pirq = XEN_PT_UNASSIGNED_PIRQ;
+ int rc = 0;
+ XenPTMSI *msi = s->msi;
+
+ if (msi->initialized) {
+ XEN_PT_ERR(&s->dev,
+ "Setup physical MSI when it has been properly initialized.\n");
+ return -1;
+ }
+
+ rc = msi_msix_setup(s, msi_addr64(msi), msi->data, &pirq, false, 0, true);
+ if (rc) {
+ return rc;
+ }
+
+ if (pirq < 0) {
+ XEN_PT_ERR(&s->dev, "Invalid pirq number: %d.\n", pirq);
+ return -1;
+ }
+
+ msi->pirq = pirq;
+ XEN_PT_LOG(&s->dev, "MSI mapped with pirq %d.\n", pirq);
+
+ return 0;
+}
+
+int xen_pt_msi_update(XenPCIPassthroughState *s)
+{
+ XenPTMSI *msi = s->msi;
+ return msi_msix_update(s, msi_addr64(msi), msi->data, msi->pirq,
+ false, 0, &msi->pirq);
+}
+
+void xen_pt_msi_disable(XenPCIPassthroughState *s)
+{
+ XenPTMSI *msi = s->msi;
+
+ if (!msi) {
+ return;
+ }
+
+ xen_pt_msi_set_enable(s, false);
+
+ msi_msix_disable(s, msi_addr64(msi), msi->data, msi->pirq, false,
+ msi->initialized);
+
+ /* clear msi info */
+ msi->flags &= ~PCI_MSI_FLAGS_ENABLE;
+ msi->initialized = false;
+ msi->mapped = false;
+ msi->pirq = XEN_PT_UNASSIGNED_PIRQ;
+}
+
+/*
+ * MSI-X virtualization functions
+ */
+
+static int msix_set_enable(XenPCIPassthroughState *s, bool enabled)
+{
+ XEN_PT_LOG(&s->dev, "%s MSI-X.\n", enabled ? "enabling" : "disabling");
+
+ if (!s->msix) {
+ return -1;
+ }
+
+ return msi_msix_enable(s, s->msix->ctrl_offset, PCI_MSIX_FLAGS_ENABLE,
+ enabled);
+}
+
+static int xen_pt_msix_update_one(XenPCIPassthroughState *s, int entry_nr)
+{
+ XenPTMSIXEntry *entry = NULL;
+ int pirq;
+ int rc;
+
+ if (entry_nr < 0 || entry_nr >= s->msix->total_entries) {
+ return -EINVAL;
+ }
+
+ entry = &s->msix->msix_entry[entry_nr];
+
+ if (!entry->updated) {
+ return 0;
+ }
+
+ pirq = entry->pirq;
+
+ rc = msi_msix_setup(s, entry->addr, entry->data, &pirq, true, entry_nr,
+ entry->pirq == XEN_PT_UNASSIGNED_PIRQ);
+ if (rc) {
+ return rc;
+ }
+ if (entry->pirq == XEN_PT_UNASSIGNED_PIRQ) {
+ entry->pirq = pirq;
+ }
+
+ rc = msi_msix_update(s, entry->addr, entry->data, pirq, true,
+ entry_nr, &entry->pirq);
+
+ if (!rc) {
+ entry->updated = false;
+ }
+
+ return rc;
+}
+
+int xen_pt_msix_update(XenPCIPassthroughState *s)
+{
+ XenPTMSIX *msix = s->msix;
+ int i;
+
+ for (i = 0; i < msix->total_entries; i++) {
+ xen_pt_msix_update_one(s, i);
+ }
+
+ return 0;
+}
+
+void xen_pt_msix_disable(XenPCIPassthroughState *s)
+{
+ int i = 0;
+
+ msix_set_enable(s, false);
+
+ for (i = 0; i < s->msix->total_entries; i++) {
+ XenPTMSIXEntry *entry = &s->msix->msix_entry[i];
+
+ msi_msix_disable(s, entry->addr, entry->data, entry->pirq, true, true);
+
+ /* clear MSI-X info */
+ entry->pirq = XEN_PT_UNASSIGNED_PIRQ;
+ entry->updated = false;
+ }
+}
+
+int xen_pt_msix_update_remap(XenPCIPassthroughState *s, int bar_index)
+{
+ XenPTMSIXEntry *entry;
+ int i, ret;
+
+ if (!(s->msix && s->msix->bar_index == bar_index)) {
+ return 0;
+ }
+
+ for (i = 0; i < s->msix->total_entries; i++) {
+ entry = &s->msix->msix_entry[i];
+ if (entry->pirq != XEN_PT_UNASSIGNED_PIRQ) {
+ ret = xc_domain_unbind_pt_irq(xen_xc, xen_domid, entry->pirq,
+ PT_IRQ_TYPE_MSI, 0, 0, 0, 0);
+ if (ret) {
+ XEN_PT_ERR(&s->dev, "unbind MSI-X entry %d failed (err: %d)\n",
+ entry->pirq, errno);
+ }
+ entry->updated = true;
+ }
+ }
+ return xen_pt_msix_update(s);
+}
+
+static uint32_t get_entry_value(XenPTMSIXEntry *e, int offset)
+{
+ switch (offset) {
+ case PCI_MSIX_ENTRY_LOWER_ADDR:
+ return e->addr & UINT32_MAX;
+ case PCI_MSIX_ENTRY_UPPER_ADDR:
+ return e->addr >> 32;
+ case PCI_MSIX_ENTRY_DATA:
+ return e->data;
+ case PCI_MSIX_ENTRY_VECTOR_CTRL:
+ return e->vector_ctrl;
+ default:
+ return 0;
+ }
+}
+
+static void set_entry_value(XenPTMSIXEntry *e, int offset, uint32_t val)
+{
+ switch (offset) {
+ case PCI_MSIX_ENTRY_LOWER_ADDR:
+ e->addr = (e->addr & ((uint64_t)UINT32_MAX << 32)) | val;
+ break;
+ case PCI_MSIX_ENTRY_UPPER_ADDR:
+ e->addr = (uint64_t)val << 32 | (e->addr & UINT32_MAX);
+ break;
+ case PCI_MSIX_ENTRY_DATA:
+ e->data = val;
+ break;
+ case PCI_MSIX_ENTRY_VECTOR_CTRL:
+ e->vector_ctrl = val;
+ break;
+ }
+}
+
+static void pci_msix_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ XenPCIPassthroughState *s = opaque;
+ XenPTMSIX *msix = s->msix;
+ XenPTMSIXEntry *entry;
+ unsigned int entry_nr, offset;
+
+ entry_nr = addr / PCI_MSIX_ENTRY_SIZE;
+ if (entry_nr >= msix->total_entries) {
+ return;
+ }
+ entry = &msix->msix_entry[entry_nr];
+ offset = addr % PCI_MSIX_ENTRY_SIZE;
+
+ if (offset != PCI_MSIX_ENTRY_VECTOR_CTRL) {
+ const volatile uint32_t *vec_ctrl;
+
+ if (get_entry_value(entry, offset) == val
+ && entry->pirq != XEN_PT_UNASSIGNED_PIRQ) {
+ return;
+ }
+
+ /*
+ * If Xen intercepts the mask bit access, entry->vec_ctrl may not be
+ * up-to-date. Read from hardware directly.
+ */
+ vec_ctrl = s->msix->phys_iomem_base + entry_nr * PCI_MSIX_ENTRY_SIZE
+ + PCI_MSIX_ENTRY_VECTOR_CTRL;
+
+ if (msix->enabled && !(*vec_ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT)) {
+ if (!entry->warned) {
+ entry->warned = true;
+ XEN_PT_ERR(&s->dev, "Can't update msix entry %d since MSI-X is"
+ " already enabled.\n", entry_nr);
+ }
+ return;
+ }
+
+ entry->updated = true;
+ }
+
+ set_entry_value(entry, offset, val);
+
+ if (offset == PCI_MSIX_ENTRY_VECTOR_CTRL) {
+ if (msix->enabled && !(val & PCI_MSIX_ENTRY_CTRL_MASKBIT)) {
+ xen_pt_msix_update_one(s, entry_nr);
+ }
+ }
+}
+
+static uint64_t pci_msix_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ XenPCIPassthroughState *s = opaque;
+ XenPTMSIX *msix = s->msix;
+ int entry_nr, offset;
+
+ entry_nr = addr / PCI_MSIX_ENTRY_SIZE;
+ if (entry_nr < 0) {
+ XEN_PT_ERR(&s->dev, "asked MSI-X entry '%i' invalid!\n", entry_nr);
+ return 0;
+ }
+
+ offset = addr % PCI_MSIX_ENTRY_SIZE;
+
+ if (addr < msix->total_entries * PCI_MSIX_ENTRY_SIZE) {
+ return get_entry_value(&msix->msix_entry[entry_nr], offset);
+ } else {
+ /* Pending Bit Array (PBA) */
+ return *(uint32_t *)(msix->phys_iomem_base + addr);
+ }
+}
+
+static const MemoryRegionOps pci_msix_ops = {
+ .read = pci_msix_read,
+ .write = pci_msix_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+int xen_pt_msix_init(XenPCIPassthroughState *s, uint32_t base)
+{
+ uint8_t id = 0;
+ uint16_t control = 0;
+ uint32_t table_off = 0;
+ int i, total_entries, bar_index;
+ XenHostPCIDevice *hd = &s->real_device;
+ PCIDevice *d = &s->dev;
+ int fd = -1;
+ XenPTMSIX *msix = NULL;
+ int rc = 0;
+
+ rc = xen_host_pci_get_byte(hd, base + PCI_CAP_LIST_ID, &id);
+ if (rc) {
+ return rc;
+ }
+
+ if (id != PCI_CAP_ID_MSIX) {
+ XEN_PT_ERR(d, "Invalid id %#x base %#x\n", id, base);
+ return -1;
+ }
+
+ xen_host_pci_get_word(hd, base + PCI_MSIX_FLAGS, &control);
+ total_entries = control & PCI_MSIX_FLAGS_QSIZE;
+ total_entries += 1;
+
+ s->msix = g_malloc0(sizeof (XenPTMSIX)
+ + total_entries * sizeof (XenPTMSIXEntry));
+ msix = s->msix;
+
+ msix->total_entries = total_entries;
+ for (i = 0; i < total_entries; i++) {
+ msix->msix_entry[i].pirq = XEN_PT_UNASSIGNED_PIRQ;
+ }
+
+ memory_region_init_io(&msix->mmio, OBJECT(s), &pci_msix_ops,
+ s, "xen-pci-pt-msix",
+ (total_entries * PCI_MSIX_ENTRY_SIZE
+ + XC_PAGE_SIZE - 1)
+ & XC_PAGE_MASK);
+
+ xen_host_pci_get_long(hd, base + PCI_MSIX_TABLE, &table_off);
+ bar_index = msix->bar_index = table_off & PCI_MSIX_FLAGS_BIRMASK;
+ table_off = table_off & ~PCI_MSIX_FLAGS_BIRMASK;
+ msix->table_base = s->real_device.io_regions[bar_index].base_addr;
+ XEN_PT_LOG(d, "get MSI-X table BAR base 0x%"PRIx64"\n", msix->table_base);
+
+ fd = open("/dev/mem", O_RDWR);
+ if (fd == -1) {
+ rc = -errno;
+ XEN_PT_ERR(d, "Can't open /dev/mem: %s\n", strerror(errno));
+ goto error_out;
+ }
+ XEN_PT_LOG(d, "table_off = %#x, total_entries = %d\n",
+ table_off, total_entries);
+ msix->table_offset_adjust = table_off & 0x0fff;
+ msix->phys_iomem_base =
+ mmap(NULL,
+ total_entries * PCI_MSIX_ENTRY_SIZE + msix->table_offset_adjust,
+ PROT_READ,
+ MAP_SHARED | MAP_LOCKED,
+ fd,
+ msix->table_base + table_off - msix->table_offset_adjust);
+ close(fd);
+ if (msix->phys_iomem_base == MAP_FAILED) {
+ rc = -errno;
+ XEN_PT_ERR(d, "Can't map physical MSI-X table: %s\n", strerror(errno));
+ goto error_out;
+ }
+ msix->phys_iomem_base = (char *)msix->phys_iomem_base
+ + msix->table_offset_adjust;
+
+ XEN_PT_LOG(d, "mapping physical MSI-X table to %p\n",
+ msix->phys_iomem_base);
+
+ memory_region_add_subregion_overlap(&s->bar[bar_index], table_off,
+ &msix->mmio,
+ 2); /* Priority: pci default + 1 */
+
+ return 0;
+
+error_out:
+ g_free(s->msix);
+ s->msix = NULL;
+ return rc;
+}
+
+void xen_pt_msix_delete(XenPCIPassthroughState *s)
+{
+ XenPTMSIX *msix = s->msix;
+
+ if (!msix) {
+ return;
+ }
+
+ /* unmap the MSI-X memory mapped register area */
+ if (msix->phys_iomem_base) {
+ XEN_PT_LOG(&s->dev, "unmapping physical MSI-X table from %p\n",
+ msix->phys_iomem_base);
+ munmap(msix->phys_iomem_base, msix->total_entries * PCI_MSIX_ENTRY_SIZE
+ + msix->table_offset_adjust);
+ }
+
+ memory_region_del_subregion(&s->bar[msix->bar_index], &msix->mmio);
+
+ g_free(s->msix);
+ s->msix = NULL;
+}
diff --git a/hw/xenpv/Makefile.objs b/hw/xenpv/Makefile.objs
new file mode 100644
index 00000000..49f6e9e3
--- /dev/null
+++ b/hw/xenpv/Makefile.objs
@@ -0,0 +1,2 @@
+# Xen PV machine support
+obj-$(CONFIG_XEN) += xen_domainbuild.o xen_machine_pv.o
diff --git a/hw/xenpv/xen_domainbuild.c b/hw/xenpv/xen_domainbuild.c
new file mode 100644
index 00000000..c0ab7537
--- /dev/null
+++ b/hw/xenpv/xen_domainbuild.c
@@ -0,0 +1,299 @@
+#include <signal.h>
+#include "hw/xen/xen_backend.h"
+#include "xen_domainbuild.h"
+#include "qemu/timer.h"
+#include "qemu/log.h"
+
+#include <xenguest.h>
+
+static int xenstore_domain_mkdir(char *path)
+{
+ struct xs_permissions perms_ro[] = {{
+ .id = 0, /* set owner: dom0 */
+ },{
+ .id = xen_domid,
+ .perms = XS_PERM_READ,
+ }};
+ struct xs_permissions perms_rw[] = {{
+ .id = 0, /* set owner: dom0 */
+ },{
+ .id = xen_domid,
+ .perms = XS_PERM_READ | XS_PERM_WRITE,
+ }};
+ const char *writable[] = { "device", "control", "error", NULL };
+ char subpath[256];
+ int i;
+
+ if (!xs_mkdir(xenstore, 0, path)) {
+ fprintf(stderr, "%s: xs_mkdir %s: failed\n", __FUNCTION__, path);
+ return -1;
+ }
+ if (!xs_set_permissions(xenstore, 0, path, perms_ro, 2)) {
+ fprintf(stderr, "%s: xs_set_permissions failed\n", __FUNCTION__);
+ return -1;
+ }
+
+ for (i = 0; writable[i]; i++) {
+ snprintf(subpath, sizeof(subpath), "%s/%s", path, writable[i]);
+ if (!xs_mkdir(xenstore, 0, subpath)) {
+ fprintf(stderr, "%s: xs_mkdir %s: failed\n", __FUNCTION__, subpath);
+ return -1;
+ }
+ if (!xs_set_permissions(xenstore, 0, subpath, perms_rw, 2)) {
+ fprintf(stderr, "%s: xs_set_permissions failed\n", __FUNCTION__);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int xenstore_domain_init1(const char *kernel, const char *ramdisk,
+ const char *cmdline)
+{
+ char *dom, uuid_string[42], vm[256], path[256];
+ int i;
+
+ snprintf(uuid_string, sizeof(uuid_string), UUID_FMT,
+ qemu_uuid[0], qemu_uuid[1], qemu_uuid[2], qemu_uuid[3],
+ qemu_uuid[4], qemu_uuid[5], qemu_uuid[6], qemu_uuid[7],
+ qemu_uuid[8], qemu_uuid[9], qemu_uuid[10], qemu_uuid[11],
+ qemu_uuid[12], qemu_uuid[13], qemu_uuid[14], qemu_uuid[15]);
+ dom = xs_get_domain_path(xenstore, xen_domid);
+ snprintf(vm, sizeof(vm), "/vm/%s", uuid_string);
+
+ xenstore_domain_mkdir(dom);
+
+ xenstore_write_str(vm, "image/ostype", "linux");
+ if (kernel)
+ xenstore_write_str(vm, "image/kernel", kernel);
+ if (ramdisk)
+ xenstore_write_str(vm, "image/ramdisk", ramdisk);
+ if (cmdline)
+ xenstore_write_str(vm, "image/cmdline", cmdline);
+
+ /* name + id */
+ xenstore_write_str(vm, "name", qemu_name ? qemu_name : "no-name");
+ xenstore_write_str(vm, "uuid", uuid_string);
+ xenstore_write_str(dom, "name", qemu_name ? qemu_name : "no-name");
+ xenstore_write_int(dom, "domid", xen_domid);
+ xenstore_write_str(dom, "vm", vm);
+
+ /* memory */
+ xenstore_write_int(dom, "memory/target", ram_size >> 10); // kB
+ xenstore_write_int(vm, "memory", ram_size >> 20); // MB
+ xenstore_write_int(vm, "maxmem", ram_size >> 20); // MB
+
+ /* cpus */
+ for (i = 0; i < smp_cpus; i++) {
+ snprintf(path, sizeof(path), "cpu/%d/availability",i);
+ xenstore_write_str(dom, path, "online");
+ }
+ xenstore_write_int(vm, "vcpu_avail", smp_cpus);
+ xenstore_write_int(vm, "vcpus", smp_cpus);
+
+ /* vnc password */
+ xenstore_write_str(vm, "vncpassword", "" /* FIXME */);
+
+ free(dom);
+ return 0;
+}
+
+int xenstore_domain_init2(int xenstore_port, int xenstore_mfn,
+ int console_port, int console_mfn)
+{
+ char *dom;
+
+ dom = xs_get_domain_path(xenstore, xen_domid);
+
+ /* signal new domain */
+ xs_introduce_domain(xenstore,
+ xen_domid,
+ xenstore_mfn,
+ xenstore_port);
+
+ /* xenstore */
+ xenstore_write_int(dom, "store/ring-ref", xenstore_mfn);
+ xenstore_write_int(dom, "store/port", xenstore_port);
+
+ /* console */
+ xenstore_write_str(dom, "console/type", "ioemu");
+ xenstore_write_int(dom, "console/limit", 128 * 1024);
+ xenstore_write_int(dom, "console/ring-ref", console_mfn);
+ xenstore_write_int(dom, "console/port", console_port);
+ xen_config_dev_console(0);
+
+ free(dom);
+ return 0;
+}
+
+/* ------------------------------------------------------------- */
+
+static QEMUTimer *xen_poll;
+
+/* check domain state once per second */
+static void xen_domain_poll(void *opaque)
+{
+ struct xc_dominfo info;
+ int rc;
+
+ rc = xc_domain_getinfo(xen_xc, xen_domid, 1, &info);
+ if ((rc != 1) || (info.domid != xen_domid)) {
+ qemu_log("xen: domain %d is gone\n", xen_domid);
+ goto quit;
+ }
+ if (info.dying) {
+ qemu_log("xen: domain %d is dying (%s%s)\n", xen_domid,
+ info.crashed ? "crashed" : "",
+ info.shutdown ? "shutdown" : "");
+ goto quit;
+ }
+
+ timer_mod(xen_poll, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000);
+ return;
+
+quit:
+ qemu_system_shutdown_request();
+}
+
+static int xen_domain_watcher(void)
+{
+ int qemu_running = 1;
+ int fd[2], i, n, rc;
+ char byte;
+
+ if (pipe(fd) != 0) {
+ qemu_log("%s: Huh? pipe error: %s\n", __FUNCTION__, strerror(errno));
+ return -1;
+ }
+ if (fork() != 0)
+ return 0; /* not child */
+
+ /* close all file handles, except stdio/out/err,
+ * our watch pipe and the xen interface handle */
+ n = getdtablesize();
+ for (i = 3; i < n; i++) {
+ if (i == fd[0])
+ continue;
+ if (i == xc_fd(xen_xc)) {
+ continue;
+ }
+ close(i);
+ }
+
+ /* ignore term signals */
+ signal(SIGINT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+
+ /* wait for qemu exiting */
+ while (qemu_running) {
+ rc = read(fd[0], &byte, 1);
+ switch (rc) {
+ case -1:
+ if (errno == EINTR)
+ continue;
+ qemu_log("%s: Huh? read error: %s\n", __FUNCTION__, strerror(errno));
+ qemu_running = 0;
+ break;
+ case 0:
+ /* EOF -> qemu exited */
+ qemu_running = 0;
+ break;
+ default:
+ qemu_log("%s: Huh? data on the watch pipe?\n", __FUNCTION__);
+ break;
+ }
+ }
+
+ /* cleanup */
+ qemu_log("%s: destroy domain %d\n", __FUNCTION__, xen_domid);
+ xc_domain_destroy(xen_xc, xen_domid);
+ _exit(0);
+}
+
+/* normal cleanup */
+static void xen_domain_cleanup(void)
+{
+ char *dom;
+
+ dom = xs_get_domain_path(xenstore, xen_domid);
+ if (dom) {
+ xs_rm(xenstore, 0, dom);
+ free(dom);
+ }
+ xs_release_domain(xenstore, xen_domid);
+}
+
+int xen_domain_build_pv(const char *kernel, const char *ramdisk,
+ const char *cmdline)
+{
+ uint32_t ssidref = 0;
+ uint32_t flags = 0;
+ xen_domain_handle_t uuid;
+ unsigned int xenstore_port = 0, console_port = 0;
+ unsigned long xenstore_mfn = 0, console_mfn = 0;
+ int rc;
+
+ memcpy(uuid, qemu_uuid, sizeof(uuid));
+ rc = xc_domain_create(xen_xc, ssidref, uuid, flags, &xen_domid);
+ if (rc < 0) {
+ fprintf(stderr, "xen: xc_domain_create() failed\n");
+ goto err;
+ }
+ qemu_log("xen: created domain %d\n", xen_domid);
+ atexit(xen_domain_cleanup);
+ if (xen_domain_watcher() == -1) {
+ goto err;
+ }
+
+ xenstore_domain_init1(kernel, ramdisk, cmdline);
+
+ rc = xc_domain_max_vcpus(xen_xc, xen_domid, smp_cpus);
+ if (rc < 0) {
+ fprintf(stderr, "xen: xc_domain_max_vcpus() failed\n");
+ goto err;
+ }
+
+#if 0
+ rc = xc_domain_setcpuweight(xen_xc, xen_domid, 256);
+ if (rc < 0) {
+ fprintf(stderr, "xen: xc_domain_setcpuweight() failed\n");
+ goto err;
+ }
+#endif
+
+ rc = xc_domain_setmaxmem(xen_xc, xen_domid, ram_size >> 10);
+ if (rc < 0) {
+ fprintf(stderr, "xen: xc_domain_setmaxmem() failed\n");
+ goto err;
+ }
+
+ xenstore_port = xc_evtchn_alloc_unbound(xen_xc, xen_domid, 0);
+ console_port = xc_evtchn_alloc_unbound(xen_xc, xen_domid, 0);
+
+ rc = xc_linux_build(xen_xc, xen_domid, ram_size >> 20,
+ kernel, ramdisk, cmdline,
+ 0, flags,
+ xenstore_port, &xenstore_mfn,
+ console_port, &console_mfn);
+ if (rc < 0) {
+ fprintf(stderr, "xen: xc_linux_build() failed\n");
+ goto err;
+ }
+
+ xenstore_domain_init2(xenstore_port, xenstore_mfn,
+ console_port, console_mfn);
+
+ qemu_log("xen: unpausing domain %d\n", xen_domid);
+ rc = xc_domain_unpause(xen_xc, xen_domid);
+ if (rc < 0) {
+ fprintf(stderr, "xen: xc_domain_unpause() failed\n");
+ goto err;
+ }
+
+ xen_poll = timer_new_ms(QEMU_CLOCK_REALTIME, xen_domain_poll, NULL);
+ timer_mod(xen_poll, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000);
+ return 0;
+
+err:
+ return -1;
+}
diff --git a/hw/xenpv/xen_domainbuild.h b/hw/xenpv/xen_domainbuild.h
new file mode 100644
index 00000000..29a91ea7
--- /dev/null
+++ b/hw/xenpv/xen_domainbuild.h
@@ -0,0 +1,13 @@
+#ifndef QEMU_HW_XEN_DOMAINBUILD_H
+#define QEMU_HW_XEN_DOMAINBUILD_H 1
+
+#include "hw/xen/xen_common.h"
+
+int xenstore_domain_init1(const char *kernel, const char *ramdisk,
+ const char *cmdline);
+int xenstore_domain_init2(int xenstore_port, int xenstore_mfn,
+ int console_port, int console_mfn);
+int xen_domain_build_pv(const char *kernel, const char *ramdisk,
+ const char *cmdline);
+
+#endif /* QEMU_HW_XEN_DOMAINBUILD_H */
diff --git a/hw/xenpv/xen_machine_pv.c b/hw/xenpv/xen_machine_pv.c
new file mode 100644
index 00000000..2e545d24
--- /dev/null
+++ b/hw/xenpv/xen_machine_pv.c
@@ -0,0 +1,109 @@
+/*
+ * QEMU Xen PV Machine
+ *
+ * Copyright (c) 2007 Red Hat
+ *
+ * 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.
+ */
+
+#include "hw/hw.h"
+#include "hw/boards.h"
+#include "hw/xen/xen_backend.h"
+#include "xen_domainbuild.h"
+#include "sysemu/block-backend.h"
+
+static void xen_init_pv(MachineState *machine)
+{
+ const char *kernel_filename = machine->kernel_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ const char *initrd_filename = machine->initrd_filename;
+ DriveInfo *dinfo;
+ int i;
+
+ /* Initialize backend core & drivers */
+ if (xen_be_init() != 0) {
+ fprintf(stderr, "%s: xen backend core setup failed\n", __FUNCTION__);
+ exit(1);
+ }
+
+ switch (xen_mode) {
+ case XEN_ATTACH:
+ /* nothing to do, xend handles everything */
+ break;
+ case XEN_CREATE:
+ if (xen_domain_build_pv(kernel_filename, initrd_filename,
+ kernel_cmdline) < 0) {
+ fprintf(stderr, "xen pv domain creation failed\n");
+ exit(1);
+ }
+ break;
+ case XEN_EMULATE:
+ fprintf(stderr, "xen emulation not implemented (yet)\n");
+ exit(1);
+ break;
+ }
+
+ xen_be_register("console", &xen_console_ops);
+ xen_be_register("vkbd", &xen_kbdmouse_ops);
+ xen_be_register("vfb", &xen_framebuffer_ops);
+ xen_be_register("qdisk", &xen_blkdev_ops);
+ xen_be_register("qnic", &xen_netdev_ops);
+
+ /* configure framebuffer */
+ if (xenfb_enabled) {
+ xen_config_dev_vfb(0, "vnc");
+ xen_config_dev_vkbd(0);
+ }
+
+ /* configure disks */
+ for (i = 0; i < 16; i++) {
+ dinfo = drive_get(IF_XEN, 0, i);
+ if (!dinfo)
+ continue;
+ xen_config_dev_blk(dinfo);
+ }
+
+ /* configure nics */
+ for (i = 0; i < nb_nics; i++) {
+ if (!nd_table[i].model || 0 != strcmp(nd_table[i].model, "xen"))
+ continue;
+ xen_config_dev_nic(nd_table + i);
+ }
+
+ /* config cleanup hook */
+ atexit(xen_config_cleanup);
+
+ /* setup framebuffer */
+ xen_init_display(xen_domid);
+}
+
+static QEMUMachine xenpv_machine = {
+ .name = "xenpv",
+ .desc = "Xen Para-virtualized PC",
+ .init = xen_init_pv,
+ .max_cpus = 1,
+ .default_machine_opts = "accel=xen",
+};
+
+static void xenpv_machine_init(void)
+{
+ qemu_register_machine(&xenpv_machine);
+}
+
+machine_init(xenpv_machine_init);
diff --git a/hw/xtensa/Makefile.objs b/hw/xtensa/Makefile.objs
new file mode 100644
index 00000000..cb77dc37
--- /dev/null
+++ b/hw/xtensa/Makefile.objs
@@ -0,0 +1,3 @@
+obj-y += pic_cpu.o
+obj-y += sim.o
+obj-y += xtfpga.o
diff --git a/hw/xtensa/bootparam.h b/hw/xtensa/bootparam.h
new file mode 100644
index 00000000..955f4e86
--- /dev/null
+++ b/hw/xtensa/bootparam.h
@@ -0,0 +1,49 @@
+#ifndef HW_XTENSA_BOOTPARAM
+#define HW_XTENSA_BOOTPARAM
+
+#define BP_TAG_COMMAND_LINE 0x1001 /* command line (0-terminated string)*/
+#define BP_TAG_INITRD 0x1002 /* ramdisk addr and size (bp_meminfo) */
+#define BP_TAG_MEMORY 0x1003 /* memory addr and size (bp_meminfo) */
+#define BP_TAG_SERIAL_BAUDRATE 0x1004 /* baud rate of current console. */
+#define BP_TAG_SERIAL_PORT 0x1005 /* serial device of current console */
+#define BP_TAG_FDT 0x1006 /* flat device tree addr */
+
+#define BP_TAG_FIRST 0x7B0B /* first tag with a version number */
+#define BP_TAG_LAST 0x7E0B /* last tag */
+
+typedef struct BpTag {
+ uint16_t tag;
+ uint16_t size;
+} BpTag;
+
+typedef struct BpMemInfo {
+ uint32_t type;
+ uint32_t start;
+ uint32_t end;
+} BpMemInfo;
+
+#define MEMORY_TYPE_CONVENTIONAL 0x1000
+#define MEMORY_TYPE_NONE 0x2000
+
+static inline size_t get_tag_size(size_t data_size)
+{
+ return data_size + sizeof(BpTag) + 4;
+}
+
+static inline ram_addr_t put_tag(ram_addr_t addr, uint16_t tag,
+ size_t size, const void *data)
+{
+ BpTag bp_tag = {
+ .tag = tswap16(tag),
+ .size = tswap16((size + 3) & ~3),
+ };
+
+ cpu_physical_memory_write(addr, &bp_tag, sizeof(bp_tag));
+ addr += sizeof(bp_tag);
+ cpu_physical_memory_write(addr, data, size);
+ addr += (size + 3) & ~3;
+
+ return addr;
+}
+
+#endif
diff --git a/hw/xtensa/pic_cpu.c b/hw/xtensa/pic_cpu.c
new file mode 100644
index 00000000..18825d19
--- /dev/null
+++ b/hw/xtensa/pic_cpu.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2011, Max Filippov, Open Source and Linux Lab.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Open Source and Linux Lab nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "hw/hw.h"
+#include "qemu/log.h"
+#include "qemu/timer.h"
+
+void xtensa_advance_ccount(CPUXtensaState *env, uint32_t d)
+{
+ uint32_t old_ccount = env->sregs[CCOUNT] + 1;
+
+ env->sregs[CCOUNT] += d;
+
+ if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT)) {
+ int i;
+ for (i = 0; i < env->config->nccompare; ++i) {
+ if (env->sregs[CCOMPARE + i] - old_ccount < d) {
+ xtensa_timer_irq(env, i, 1);
+ }
+ }
+ }
+}
+
+void check_interrupts(CPUXtensaState *env)
+{
+ CPUState *cs = CPU(xtensa_env_get_cpu(env));
+ int minlevel = xtensa_get_cintlevel(env);
+ uint32_t int_set_enabled = env->sregs[INTSET] & env->sregs[INTENABLE];
+ int level;
+
+ /* If the CPU is halted advance CCOUNT according to the QEMU_CLOCK_VIRTUAL time
+ * elapsed since the moment when it was advanced last time.
+ */
+ if (cs->halted) {
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ xtensa_advance_ccount(env,
+ muldiv64(now - env->halt_clock,
+ env->config->clock_freq_khz, 1000000));
+ env->halt_clock = now;
+ }
+ for (level = env->config->nlevel; level > minlevel; --level) {
+ if (env->config->level_mask[level] & int_set_enabled) {
+ env->pending_irq_level = level;
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ qemu_log_mask(CPU_LOG_INT,
+ "%s level = %d, cintlevel = %d, "
+ "pc = %08x, a0 = %08x, ps = %08x, "
+ "intset = %08x, intenable = %08x, "
+ "ccount = %08x\n",
+ __func__, level, xtensa_get_cintlevel(env),
+ env->pc, env->regs[0], env->sregs[PS],
+ env->sregs[INTSET], env->sregs[INTENABLE],
+ env->sregs[CCOUNT]);
+ return;
+ }
+ }
+ env->pending_irq_level = 0;
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+}
+
+static void xtensa_set_irq(void *opaque, int irq, int active)
+{
+ CPUXtensaState *env = opaque;
+
+ if (irq >= env->config->ninterrupt) {
+ qemu_log("%s: bad IRQ %d\n", __func__, irq);
+ } else {
+ uint32_t irq_bit = 1 << irq;
+
+ if (active) {
+ env->sregs[INTSET] |= irq_bit;
+ } else if (env->config->interrupt[irq].inttype == INTTYPE_LEVEL) {
+ env->sregs[INTSET] &= ~irq_bit;
+ }
+
+ check_interrupts(env);
+ }
+}
+
+void xtensa_timer_irq(CPUXtensaState *env, uint32_t id, uint32_t active)
+{
+ qemu_set_irq(env->irq_inputs[env->config->timerint[id]], active);
+}
+
+void xtensa_rearm_ccompare_timer(CPUXtensaState *env)
+{
+ int i;
+ uint32_t wake_ccount = env->sregs[CCOUNT] - 1;
+
+ for (i = 0; i < env->config->nccompare; ++i) {
+ if (env->sregs[CCOMPARE + i] - env->sregs[CCOUNT] <
+ wake_ccount - env->sregs[CCOUNT]) {
+ wake_ccount = env->sregs[CCOMPARE + i];
+ }
+ }
+ env->wake_ccount = wake_ccount;
+ timer_mod(env->ccompare_timer, env->halt_clock +
+ muldiv64(wake_ccount - env->sregs[CCOUNT],
+ 1000000, env->config->clock_freq_khz));
+}
+
+static void xtensa_ccompare_cb(void *opaque)
+{
+ XtensaCPU *cpu = opaque;
+ CPUXtensaState *env = &cpu->env;
+ CPUState *cs = CPU(cpu);
+
+ if (cs->halted) {
+ env->halt_clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ xtensa_advance_ccount(env, env->wake_ccount - env->sregs[CCOUNT]);
+ if (!cpu_has_work(cs)) {
+ env->sregs[CCOUNT] = env->wake_ccount + 1;
+ xtensa_rearm_ccompare_timer(env);
+ }
+ }
+}
+
+void xtensa_irq_init(CPUXtensaState *env)
+{
+ XtensaCPU *cpu = xtensa_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(
+ xtensa_set_irq, env, env->config->ninterrupt);
+ if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT) &&
+ env->config->nccompare > 0) {
+ env->ccompare_timer =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, &xtensa_ccompare_cb, cpu);
+ }
+}
+
+void *xtensa_get_extint(CPUXtensaState *env, unsigned extint)
+{
+ if (extint < env->config->nextint) {
+ unsigned irq = env->config->extint[extint];
+ return env->irq_inputs[irq];
+ } else {
+ qemu_log("%s: trying to acquire invalid external interrupt %d\n",
+ __func__, extint);
+ return NULL;
+ }
+}
diff --git a/hw/xtensa/sim.c b/hw/xtensa/sim.c
new file mode 100644
index 00000000..328d2097
--- /dev/null
+++ b/hw/xtensa/sim.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2011, Max Filippov, Open Source and Linux Lab.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Open Source and Linux Lab nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+#include "qemu/error-report.h"
+
+static uint64_t translate_phys_addr(void *opaque, uint64_t addr)
+{
+ XtensaCPU *cpu = opaque;
+
+ return cpu_get_phys_page_debug(CPU(cpu), addr);
+}
+
+static void sim_reset(void *opaque)
+{
+ XtensaCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+static void xtensa_sim_init(MachineState *machine)
+{
+ XtensaCPU *cpu = NULL;
+ CPUXtensaState *env = NULL;
+ MemoryRegion *ram, *rom;
+ ram_addr_t ram_size = machine->ram_size;
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = machine->kernel_filename;
+ int n;
+
+ if (!cpu_model) {
+ cpu_model = XTENSA_DEFAULT_CPU_MODEL;
+ }
+
+ for (n = 0; n < smp_cpus; n++) {
+ cpu = cpu_xtensa_init(cpu_model);
+ if (cpu == NULL) {
+ error_report("unable to find CPU definition '%s'",
+ cpu_model);
+ exit(EXIT_FAILURE);
+ }
+ env = &cpu->env;
+
+ env->sregs[PRID] = n;
+ qemu_register_reset(sim_reset, cpu);
+ /* Need MMU initialized prior to ELF loading,
+ * so that ELF gets loaded into virtual addresses
+ */
+ sim_reset(cpu);
+ }
+
+ ram = g_malloc(sizeof(*ram));
+ memory_region_init_ram(ram, NULL, "xtensa.sram", ram_size, &error_abort);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(get_system_memory(), 0, ram);
+
+ rom = g_malloc(sizeof(*rom));
+ memory_region_init_ram(rom, NULL, "xtensa.rom", 0x1000, &error_abort);
+ vmstate_register_ram_global(rom);
+ memory_region_add_subregion(get_system_memory(), 0xfe000000, rom);
+
+ if (kernel_filename) {
+ uint64_t elf_entry;
+ uint64_t elf_lowaddr;
+#ifdef TARGET_WORDS_BIGENDIAN
+ int success = load_elf(kernel_filename, translate_phys_addr, cpu,
+ &elf_entry, &elf_lowaddr, NULL, 1, ELF_MACHINE, 0);
+#else
+ int success = load_elf(kernel_filename, translate_phys_addr, cpu,
+ &elf_entry, &elf_lowaddr, NULL, 0, ELF_MACHINE, 0);
+#endif
+ if (success > 0) {
+ env->pc = elf_entry;
+ }
+ }
+}
+
+static QEMUMachine xtensa_sim_machine = {
+ .name = "sim",
+ .desc = "sim machine (" XTENSA_DEFAULT_CPU_MODEL ")",
+ .is_default = true,
+ .init = xtensa_sim_init,
+ .max_cpus = 4,
+};
+
+static void xtensa_sim_machine_init(void)
+{
+ qemu_register_machine(&xtensa_sim_machine);
+}
+
+machine_init(xtensa_sim_machine_init);
diff --git a/hw/xtensa/xtfpga.c b/hw/xtensa/xtfpga.c
new file mode 100644
index 00000000..ab4d0e41
--- /dev/null
+++ b/hw/xtensa/xtfpga.c
@@ -0,0 +1,466 @@
+/*
+ * Copyright (c) 2011, Max Filippov, Open Source and Linux Lab.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Open Source and Linux Lab nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+#include "hw/char/serial.h"
+#include "net/net.h"
+#include "hw/sysbus.h"
+#include "hw/block/flash.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/char.h"
+#include "sysemu/device_tree.h"
+#include "qemu/error-report.h"
+#include "bootparam.h"
+
+typedef struct LxBoardDesc {
+ hwaddr flash_base;
+ size_t flash_size;
+ size_t flash_boot_base;
+ size_t flash_sector_size;
+ size_t sram_size;
+} LxBoardDesc;
+
+typedef struct Lx60FpgaState {
+ MemoryRegion iomem;
+ uint32_t leds;
+ uint32_t switches;
+} Lx60FpgaState;
+
+static void lx60_fpga_reset(void *opaque)
+{
+ Lx60FpgaState *s = opaque;
+
+ s->leds = 0;
+ s->switches = 0;
+}
+
+static uint64_t lx60_fpga_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ Lx60FpgaState *s = opaque;
+
+ switch (addr) {
+ case 0x0: /*build date code*/
+ return 0x09272011;
+
+ case 0x4: /*processor clock frequency, Hz*/
+ return 10000000;
+
+ case 0x8: /*LEDs (off = 0, on = 1)*/
+ return s->leds;
+
+ case 0xc: /*DIP switches (off = 0, on = 1)*/
+ return s->switches;
+ }
+ return 0;
+}
+
+static void lx60_fpga_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ Lx60FpgaState *s = opaque;
+
+ switch (addr) {
+ case 0x8: /*LEDs (off = 0, on = 1)*/
+ s->leds = val;
+ break;
+
+ case 0x10: /*board reset*/
+ if (val == 0xdead) {
+ qemu_system_reset_request();
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps lx60_fpga_ops = {
+ .read = lx60_fpga_read,
+ .write = lx60_fpga_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static Lx60FpgaState *lx60_fpga_init(MemoryRegion *address_space,
+ hwaddr base)
+{
+ Lx60FpgaState *s = g_malloc(sizeof(Lx60FpgaState));
+
+ memory_region_init_io(&s->iomem, NULL, &lx60_fpga_ops, s,
+ "lx60.fpga", 0x10000);
+ memory_region_add_subregion(address_space, base, &s->iomem);
+ lx60_fpga_reset(s);
+ qemu_register_reset(lx60_fpga_reset, s);
+ return s;
+}
+
+static void lx60_net_init(MemoryRegion *address_space,
+ hwaddr base,
+ hwaddr descriptors,
+ hwaddr buffers,
+ qemu_irq irq, NICInfo *nd)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ MemoryRegion *ram;
+
+ dev = qdev_create(NULL, "open_eth");
+ qdev_set_nic_properties(dev, nd);
+ qdev_init_nofail(dev);
+
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irq);
+ memory_region_add_subregion(address_space, base,
+ sysbus_mmio_get_region(s, 0));
+ memory_region_add_subregion(address_space, descriptors,
+ sysbus_mmio_get_region(s, 1));
+
+ ram = g_malloc(sizeof(*ram));
+ memory_region_init_ram(ram, OBJECT(s), "open_eth.ram", 16384, &error_abort);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(address_space, buffers, ram);
+}
+
+static uint64_t translate_phys_addr(void *opaque, uint64_t addr)
+{
+ XtensaCPU *cpu = opaque;
+
+ return cpu_get_phys_page_debug(CPU(cpu), addr);
+}
+
+static void lx60_reset(void *opaque)
+{
+ XtensaCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+static uint64_t lx60_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void lx60_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+}
+
+static const MemoryRegionOps lx60_io_ops = {
+ .read = lx60_io_read,
+ .write = lx60_io_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void lx_init(const LxBoardDesc *board, MachineState *machine)
+{
+#ifdef TARGET_WORDS_BIGENDIAN
+ int be = 1;
+#else
+ int be = 0;
+#endif
+ MemoryRegion *system_memory = get_system_memory();
+ XtensaCPU *cpu = NULL;
+ CPUXtensaState *env = NULL;
+ MemoryRegion *ram, *rom, *system_io;
+ DriveInfo *dinfo;
+ pflash_t *flash = NULL;
+ QemuOpts *machine_opts = qemu_get_machine_opts();
+ const char *cpu_model = machine->cpu_model;
+ const char *kernel_filename = qemu_opt_get(machine_opts, "kernel");
+ const char *kernel_cmdline = qemu_opt_get(machine_opts, "append");
+ const char *dtb_filename = qemu_opt_get(machine_opts, "dtb");
+ const char *initrd_filename = qemu_opt_get(machine_opts, "initrd");
+ int n;
+
+ if (!cpu_model) {
+ cpu_model = XTENSA_DEFAULT_CPU_MODEL;
+ }
+
+ for (n = 0; n < smp_cpus; n++) {
+ cpu = cpu_xtensa_init(cpu_model);
+ if (cpu == NULL) {
+ error_report("unable to find CPU definition '%s'",
+ cpu_model);
+ exit(EXIT_FAILURE);
+ }
+ env = &cpu->env;
+
+ env->sregs[PRID] = n;
+ qemu_register_reset(lx60_reset, cpu);
+ /* Need MMU initialized prior to ELF loading,
+ * so that ELF gets loaded into virtual addresses
+ */
+ cpu_reset(CPU(cpu));
+ }
+
+ ram = g_malloc(sizeof(*ram));
+ memory_region_init_ram(ram, NULL, "lx60.dram", machine->ram_size,
+ &error_abort);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(system_memory, 0, ram);
+
+ system_io = g_malloc(sizeof(*system_io));
+ memory_region_init_io(system_io, NULL, &lx60_io_ops, NULL, "lx60.io",
+ 224 * 1024 * 1024);
+ memory_region_add_subregion(system_memory, 0xf0000000, system_io);
+ lx60_fpga_init(system_io, 0x0d020000);
+ if (nd_table[0].used) {
+ lx60_net_init(system_io, 0x0d030000, 0x0d030400, 0x0d800000,
+ xtensa_get_extint(env, 1), nd_table);
+ }
+
+ if (!serial_hds[0]) {
+ serial_hds[0] = qemu_chr_new("serial0", "null", NULL);
+ }
+
+ serial_mm_init(system_io, 0x0d050020, 2, xtensa_get_extint(env, 0),
+ 115200, serial_hds[0], DEVICE_NATIVE_ENDIAN);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ if (dinfo) {
+ flash = pflash_cfi01_register(board->flash_base,
+ NULL, "lx60.io.flash", board->flash_size,
+ blk_by_legacy_dinfo(dinfo),
+ board->flash_sector_size,
+ board->flash_size / board->flash_sector_size,
+ 4, 0x0000, 0x0000, 0x0000, 0x0000, be);
+ if (flash == NULL) {
+ error_report("unable to mount pflash");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* Use presence of kernel file name as 'boot from SRAM' switch. */
+ if (kernel_filename) {
+ uint32_t entry_point = env->pc;
+ size_t bp_size = 3 * get_tag_size(0); /* first/last and memory tags */
+ uint32_t tagptr = 0xfe000000 + board->sram_size;
+ uint32_t cur_tagptr;
+ BpMemInfo memory_location = {
+ .type = tswap32(MEMORY_TYPE_CONVENTIONAL),
+ .start = tswap32(0),
+ .end = tswap32(machine->ram_size),
+ };
+ uint32_t lowmem_end = machine->ram_size < 0x08000000 ?
+ machine->ram_size : 0x08000000;
+ uint32_t cur_lowmem = QEMU_ALIGN_UP(lowmem_end / 2, 4096);
+
+ rom = g_malloc(sizeof(*rom));
+ memory_region_init_ram(rom, NULL, "lx60.sram", board->sram_size,
+ &error_abort);
+ vmstate_register_ram_global(rom);
+ memory_region_add_subregion(system_memory, 0xfe000000, rom);
+
+ if (kernel_cmdline) {
+ bp_size += get_tag_size(strlen(kernel_cmdline) + 1);
+ }
+ if (dtb_filename) {
+ bp_size += get_tag_size(sizeof(uint32_t));
+ }
+ if (initrd_filename) {
+ bp_size += get_tag_size(sizeof(BpMemInfo));
+ }
+
+ /* Put kernel bootparameters to the end of that SRAM */
+ tagptr = (tagptr - bp_size) & ~0xff;
+ cur_tagptr = put_tag(tagptr, BP_TAG_FIRST, 0, NULL);
+ cur_tagptr = put_tag(cur_tagptr, BP_TAG_MEMORY,
+ sizeof(memory_location), &memory_location);
+
+ if (kernel_cmdline) {
+ cur_tagptr = put_tag(cur_tagptr, BP_TAG_COMMAND_LINE,
+ strlen(kernel_cmdline) + 1, kernel_cmdline);
+ }
+ if (dtb_filename) {
+ int fdt_size;
+ void *fdt = load_device_tree(dtb_filename, &fdt_size);
+ uint32_t dtb_addr = tswap32(cur_lowmem);
+
+ if (!fdt) {
+ error_report("could not load DTB '%s'", dtb_filename);
+ exit(EXIT_FAILURE);
+ }
+
+ cpu_physical_memory_write(cur_lowmem, fdt, fdt_size);
+ cur_tagptr = put_tag(cur_tagptr, BP_TAG_FDT,
+ sizeof(dtb_addr), &dtb_addr);
+ cur_lowmem = QEMU_ALIGN_UP(cur_lowmem + fdt_size, 4096);
+ }
+ if (initrd_filename) {
+ BpMemInfo initrd_location = { 0 };
+ int initrd_size = load_ramdisk(initrd_filename, cur_lowmem,
+ lowmem_end - cur_lowmem);
+
+ if (initrd_size < 0) {
+ initrd_size = load_image_targphys(initrd_filename,
+ cur_lowmem,
+ lowmem_end - cur_lowmem);
+ }
+ if (initrd_size < 0) {
+ error_report("could not load initrd '%s'", initrd_filename);
+ exit(EXIT_FAILURE);
+ }
+ initrd_location.start = tswap32(cur_lowmem);
+ initrd_location.end = tswap32(cur_lowmem + initrd_size);
+ cur_tagptr = put_tag(cur_tagptr, BP_TAG_INITRD,
+ sizeof(initrd_location), &initrd_location);
+ cur_lowmem = QEMU_ALIGN_UP(cur_lowmem + initrd_size, 4096);
+ }
+ cur_tagptr = put_tag(cur_tagptr, BP_TAG_LAST, 0, NULL);
+ env->regs[2] = tagptr;
+
+ uint64_t elf_entry;
+ uint64_t elf_lowaddr;
+ int success = load_elf(kernel_filename, translate_phys_addr, cpu,
+ &elf_entry, &elf_lowaddr, NULL, be, ELF_MACHINE, 0);
+ if (success > 0) {
+ entry_point = elf_entry;
+ } else {
+ hwaddr ep;
+ int is_linux;
+ success = load_uimage(kernel_filename, &ep, NULL, &is_linux,
+ translate_phys_addr, cpu);
+ if (success > 0 && is_linux) {
+ entry_point = ep;
+ } else {
+ error_report("could not load kernel '%s'",
+ kernel_filename);
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (entry_point != env->pc) {
+ static const uint8_t jx_a0[] = {
+#ifdef TARGET_WORDS_BIGENDIAN
+ 0x0a, 0, 0,
+#else
+ 0xa0, 0, 0,
+#endif
+ };
+ env->regs[0] = entry_point;
+ cpu_physical_memory_write(env->pc, jx_a0, sizeof(jx_a0));
+ }
+ } else {
+ if (flash) {
+ MemoryRegion *flash_mr = pflash_cfi01_get_memory(flash);
+ MemoryRegion *flash_io = g_malloc(sizeof(*flash_io));
+
+ memory_region_init_alias(flash_io, NULL, "lx60.flash",
+ flash_mr, board->flash_boot_base,
+ board->flash_size - board->flash_boot_base < 0x02000000 ?
+ board->flash_size - board->flash_boot_base : 0x02000000);
+ memory_region_add_subregion(system_memory, 0xfe000000,
+ flash_io);
+ }
+ }
+}
+
+static void xtensa_lx60_init(MachineState *machine)
+{
+ static const LxBoardDesc lx60_board = {
+ .flash_base = 0xf8000000,
+ .flash_size = 0x00400000,
+ .flash_sector_size = 0x10000,
+ .sram_size = 0x20000,
+ };
+ lx_init(&lx60_board, machine);
+}
+
+static void xtensa_lx200_init(MachineState *machine)
+{
+ static const LxBoardDesc lx200_board = {
+ .flash_base = 0xf8000000,
+ .flash_size = 0x01000000,
+ .flash_sector_size = 0x20000,
+ .sram_size = 0x2000000,
+ };
+ lx_init(&lx200_board, machine);
+}
+
+static void xtensa_ml605_init(MachineState *machine)
+{
+ static const LxBoardDesc ml605_board = {
+ .flash_base = 0xf8000000,
+ .flash_size = 0x01000000,
+ .flash_sector_size = 0x20000,
+ .sram_size = 0x2000000,
+ };
+ lx_init(&ml605_board, machine);
+}
+
+static void xtensa_kc705_init(MachineState *machine)
+{
+ static const LxBoardDesc kc705_board = {
+ .flash_base = 0xf0000000,
+ .flash_size = 0x08000000,
+ .flash_boot_base = 0x06000000,
+ .flash_sector_size = 0x20000,
+ .sram_size = 0x2000000,
+ };
+ lx_init(&kc705_board, machine);
+}
+
+static QEMUMachine xtensa_lx60_machine = {
+ .name = "lx60",
+ .desc = "lx60 EVB (" XTENSA_DEFAULT_CPU_MODEL ")",
+ .init = xtensa_lx60_init,
+ .max_cpus = 4,
+};
+
+static QEMUMachine xtensa_lx200_machine = {
+ .name = "lx200",
+ .desc = "lx200 EVB (" XTENSA_DEFAULT_CPU_MODEL ")",
+ .init = xtensa_lx200_init,
+ .max_cpus = 4,
+};
+
+static QEMUMachine xtensa_ml605_machine = {
+ .name = "ml605",
+ .desc = "ml605 EVB (" XTENSA_DEFAULT_CPU_MODEL ")",
+ .init = xtensa_ml605_init,
+ .max_cpus = 4,
+};
+
+static QEMUMachine xtensa_kc705_machine = {
+ .name = "kc705",
+ .desc = "kc705 EVB (" XTENSA_DEFAULT_CPU_MODEL ")",
+ .init = xtensa_kc705_init,
+ .max_cpus = 4,
+};
+
+static void xtensa_lx_machines_init(void)
+{
+ qemu_register_machine(&xtensa_lx60_machine);
+ qemu_register_machine(&xtensa_lx200_machine);
+ qemu_register_machine(&xtensa_ml605_machine);
+ qemu_register_machine(&xtensa_kc705_machine);
+}
+
+machine_init(xtensa_lx_machines_init);